Commit dfab92f2 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'nfs-for-6.5-1' of git://git.linux-nfs.org/projects/trondmy/linux-nfs

Pull NFS client updates from Trond Myklebust:
"Stable fixes and other bugfixes:

   - nfs: don't report STATX_BTIME in ->getattr

   - Revert 'NFSv4: Retry LOCK on OLD_STATEID during delegation return'
     since it breaks NFSv4 state recovery.

   - NFSv4.1: freeze the session table upon receiving NFS4ERR_BADSESSION

   - Fix the NFSv4.2 xattr cache shrinker_id

   - Force a ctime update after a NFSv4.2 SETXATTR call

  Features and cleanups:

   - NFS and RPC over TLS client code from Chuck Lever

   - Support for use of abstract unix socket addresses with the rpcbind
     daemon

   - Sysfs API to allow shutdown of the kernel RPC client and prevent
     umount() hangs if the server is known to be permanently down

   - XDR cleanups from Anna"

* tag 'nfs-for-6.5-1' of git://git.linux-nfs.org/projects/trondmy/linux-nfs: (33 commits)
  Revert "NFSv4: Retry LOCK on OLD_STATEID during delegation return"
  NFS: Don't cleanup sysfs superblock entry if uninitialized
  nfs: don't report STATX_BTIME in ->getattr
  NFSv4.1: freeze the session table upon receiving NFS4ERR_BADSESSION
  NFSv4.2: fix wrong shrinker_id
  NFSv4: Clean up some shutdown loops
  NFS: Cancel all existing RPC tasks when shutdown
  NFS: add sysfs shutdown knob
  NFS: add a sysfs link to the acl rpc_client
  NFS: add a sysfs link to the lockd rpc_client
  NFS: Add sysfs links to sunrpc clients for nfs_clients
  NFS: add superblock sysfs entries
  NFS: Make all of /sys/fs/nfs network-namespace unique
  NFS: Open-code the nfs_kset kset_create_and_add()
  NFS: rename nfs_client_kobj to nfs_net_kobj
  NFS: rename nfs_client_kset to nfs_kset
  NFS: Add an "xprtsec=" NFS mount option
  NFS: Have struct nfs_client carry a TLS policy field
  SUNRPC: Add a TCP-with-TLS RPC transport class
  SUNRPC: Capture CMSG metadata on client-side receive
  ...
parents f8566aa4 5b4a82a0
...@@ -93,6 +93,12 @@ void nlmclnt_prepare_block(struct nlm_wait *block, struct nlm_host *host, struct ...@@ -93,6 +93,12 @@ void nlmclnt_prepare_block(struct nlm_wait *block, struct nlm_host *host, struct
block->b_status = nlm_lck_blocked; block->b_status = nlm_lck_blocked;
} }
struct rpc_clnt *nlmclnt_rpc_clnt(struct nlm_host *host)
{
return host->h_rpcclnt;
}
EXPORT_SYMBOL_GPL(nlmclnt_rpc_clnt);
/* /*
* Queue up a lock for blocking so that the GRANTED request can see it * Queue up a lock for blocking so that the GRANTED request can see it
*/ */
......
...@@ -184,6 +184,7 @@ struct nfs_client *nfs_alloc_client(const struct nfs_client_initdata *cl_init) ...@@ -184,6 +184,7 @@ struct nfs_client *nfs_alloc_client(const struct nfs_client_initdata *cl_init)
clp->cl_net = get_net(cl_init->net); clp->cl_net = get_net(cl_init->net);
clp->cl_principal = "*"; clp->cl_principal = "*";
clp->cl_xprtsec = cl_init->xprtsec;
return clp; return clp;
error_cleanup: error_cleanup:
...@@ -326,6 +327,10 @@ static struct nfs_client *nfs_match_client(const struct nfs_client_initdata *dat ...@@ -326,6 +327,10 @@ static struct nfs_client *nfs_match_client(const struct nfs_client_initdata *dat
sap)) sap))
continue; continue;
/* Match the xprt security policy */
if (clp->cl_xprtsec.policy != data->xprtsec.policy)
continue;
refcount_inc(&clp->cl_count); refcount_inc(&clp->cl_count);
return clp; return clp;
} }
...@@ -458,6 +463,7 @@ void nfs_init_timeout_values(struct rpc_timeout *to, int proto, ...@@ -458,6 +463,7 @@ void nfs_init_timeout_values(struct rpc_timeout *to, int proto,
switch (proto) { switch (proto) {
case XPRT_TRANSPORT_TCP: case XPRT_TRANSPORT_TCP:
case XPRT_TRANSPORT_TCP_TLS:
case XPRT_TRANSPORT_RDMA: case XPRT_TRANSPORT_RDMA:
if (retrans == NFS_UNSPEC_RETRANS) if (retrans == NFS_UNSPEC_RETRANS)
to->to_retries = NFS_DEF_TCP_RETRANS; to->to_retries = NFS_DEF_TCP_RETRANS;
...@@ -510,6 +516,7 @@ int nfs_create_rpc_client(struct nfs_client *clp, ...@@ -510,6 +516,7 @@ int nfs_create_rpc_client(struct nfs_client *clp,
.version = clp->rpc_ops->version, .version = clp->rpc_ops->version,
.authflavor = flavor, .authflavor = flavor,
.cred = cl_init->cred, .cred = cl_init->cred,
.xprtsec = cl_init->xprtsec,
}; };
if (test_bit(NFS_CS_DISCRTRY, &clp->cl_flags)) if (test_bit(NFS_CS_DISCRTRY, &clp->cl_flags))
...@@ -592,6 +599,7 @@ static int nfs_start_lockd(struct nfs_server *server) ...@@ -592,6 +599,7 @@ static int nfs_start_lockd(struct nfs_server *server)
server->nlm_host = host; server->nlm_host = host;
server->destroy = nfs_destroy_server; server->destroy = nfs_destroy_server;
nfs_sysfs_link_rpc_client(server, nlmclnt_rpc_clnt(host), NULL);
return 0; return 0;
} }
...@@ -621,6 +629,7 @@ int nfs_init_server_rpcclient(struct nfs_server *server, ...@@ -621,6 +629,7 @@ int nfs_init_server_rpcclient(struct nfs_server *server,
if (server->flags & NFS_MOUNT_SOFT) if (server->flags & NFS_MOUNT_SOFT)
server->client->cl_softrtry = 1; server->client->cl_softrtry = 1;
nfs_sysfs_link_rpc_client(server, server->client, NULL);
return 0; return 0;
} }
EXPORT_SYMBOL_GPL(nfs_init_server_rpcclient); EXPORT_SYMBOL_GPL(nfs_init_server_rpcclient);
...@@ -675,6 +684,7 @@ static int nfs_init_server(struct nfs_server *server, ...@@ -675,6 +684,7 @@ static int nfs_init_server(struct nfs_server *server,
.cred = server->cred, .cred = server->cred,
.nconnect = ctx->nfs_server.nconnect, .nconnect = ctx->nfs_server.nconnect,
.init_flags = (1UL << NFS_CS_REUSEPORT), .init_flags = (1UL << NFS_CS_REUSEPORT),
.xprtsec = ctx->xprtsec,
}; };
struct nfs_client *clp; struct nfs_client *clp;
int error; int error;
...@@ -690,6 +700,8 @@ static int nfs_init_server(struct nfs_server *server, ...@@ -690,6 +700,8 @@ static int nfs_init_server(struct nfs_server *server,
return PTR_ERR(clp); return PTR_ERR(clp);
server->nfs_client = clp; server->nfs_client = clp;
nfs_sysfs_add_server(server);
nfs_sysfs_link_rpc_client(server, clp->cl_rpcclient, "_state");
/* Initialise the client representation from the mount data */ /* Initialise the client representation from the mount data */
server->flags = ctx->flags; server->flags = ctx->flags;
...@@ -944,6 +956,8 @@ void nfs_server_remove_lists(struct nfs_server *server) ...@@ -944,6 +956,8 @@ void nfs_server_remove_lists(struct nfs_server *server)
} }
EXPORT_SYMBOL_GPL(nfs_server_remove_lists); EXPORT_SYMBOL_GPL(nfs_server_remove_lists);
static DEFINE_IDA(s_sysfs_ids);
/* /*
* Allocate and initialise a server record * Allocate and initialise a server record
*/ */
...@@ -955,6 +969,12 @@ struct nfs_server *nfs_alloc_server(void) ...@@ -955,6 +969,12 @@ struct nfs_server *nfs_alloc_server(void)
if (!server) if (!server)
return NULL; return NULL;
server->s_sysfs_id = ida_alloc(&s_sysfs_ids, GFP_KERNEL);
if (server->s_sysfs_id < 0) {
kfree(server);
return NULL;
}
server->client = server->client_acl = ERR_PTR(-EINVAL); server->client = server->client_acl = ERR_PTR(-EINVAL);
/* Zero out the NFS state stuff */ /* Zero out the NFS state stuff */
...@@ -1001,6 +1021,12 @@ void nfs_free_server(struct nfs_server *server) ...@@ -1001,6 +1021,12 @@ void nfs_free_server(struct nfs_server *server)
nfs_put_client(server->nfs_client); nfs_put_client(server->nfs_client);
if (server->kobj.state_initialized) {
nfs_sysfs_remove_server(server);
kobject_put(&server->kobj);
}
ida_free(&s_sysfs_ids, server->s_sysfs_id);
ida_destroy(&server->lockowner_id); ida_destroy(&server->lockowner_id);
ida_destroy(&server->openowner_id); ida_destroy(&server->openowner_id);
nfs_free_iostats(server->io_stats); nfs_free_iostats(server->io_stats);
...@@ -1102,6 +1128,11 @@ struct nfs_server *nfs_clone_server(struct nfs_server *source, ...@@ -1102,6 +1128,11 @@ struct nfs_server *nfs_clone_server(struct nfs_server *source,
server->fsid = fattr->fsid; server->fsid = fattr->fsid;
nfs_sysfs_add_server(server);
nfs_sysfs_link_rpc_client(server,
server->nfs_client->cl_rpcclient, "_state");
error = nfs_init_server_rpcclient(server, error = nfs_init_server_rpcclient(server,
source->client->cl_timeout, source->client->cl_timeout,
flavor); flavor);
...@@ -1385,6 +1416,7 @@ int __init nfs_fs_proc_init(void) ...@@ -1385,6 +1416,7 @@ int __init nfs_fs_proc_init(void)
void nfs_fs_proc_exit(void) void nfs_fs_proc_exit(void)
{ {
remove_proc_subtree("fs/nfsfs", NULL); remove_proc_subtree("fs/nfsfs", NULL);
ida_destroy(&s_sysfs_ids);
} }
#endif /* CONFIG_PROC_FS */ #endif /* CONFIG_PROC_FS */
...@@ -18,6 +18,9 @@ ...@@ -18,6 +18,9 @@
#include <linux/nfs_fs.h> #include <linux/nfs_fs.h>
#include <linux/nfs_mount.h> #include <linux/nfs_mount.h>
#include <linux/nfs4_mount.h> #include <linux/nfs4_mount.h>
#include <net/handshake.h>
#include "nfs.h" #include "nfs.h"
#include "internal.h" #include "internal.h"
...@@ -88,6 +91,7 @@ enum nfs_param { ...@@ -88,6 +91,7 @@ enum nfs_param {
Opt_vers, Opt_vers,
Opt_wsize, Opt_wsize,
Opt_write, Opt_write,
Opt_xprtsec,
}; };
enum { enum {
...@@ -194,6 +198,7 @@ static const struct fs_parameter_spec nfs_fs_parameters[] = { ...@@ -194,6 +198,7 @@ static const struct fs_parameter_spec nfs_fs_parameters[] = {
fsparam_string("vers", Opt_vers), fsparam_string("vers", Opt_vers),
fsparam_enum ("write", Opt_write, nfs_param_enums_write), fsparam_enum ("write", Opt_write, nfs_param_enums_write),
fsparam_u32 ("wsize", Opt_wsize), fsparam_u32 ("wsize", Opt_wsize),
fsparam_string("xprtsec", Opt_xprtsec),
{} {}
}; };
...@@ -267,6 +272,20 @@ static const struct constant_table nfs_secflavor_tokens[] = { ...@@ -267,6 +272,20 @@ static const struct constant_table nfs_secflavor_tokens[] = {
{} {}
}; };
enum {
Opt_xprtsec_none,
Opt_xprtsec_tls,
Opt_xprtsec_mtls,
nr__Opt_xprtsec
};
static const struct constant_table nfs_xprtsec_policies[] = {
{ "none", Opt_xprtsec_none },
{ "tls", Opt_xprtsec_tls },
{ "mtls", Opt_xprtsec_mtls },
{}
};
/* /*
* Sanity-check a server address provided by the mount command. * Sanity-check a server address provided by the mount command.
* *
...@@ -320,9 +339,21 @@ static int nfs_validate_transport_protocol(struct fs_context *fc, ...@@ -320,9 +339,21 @@ static int nfs_validate_transport_protocol(struct fs_context *fc,
default: default:
ctx->nfs_server.protocol = XPRT_TRANSPORT_TCP; ctx->nfs_server.protocol = XPRT_TRANSPORT_TCP;
} }
if (ctx->xprtsec.policy != RPC_XPRTSEC_NONE)
switch (ctx->nfs_server.protocol) {
case XPRT_TRANSPORT_TCP:
ctx->nfs_server.protocol = XPRT_TRANSPORT_TCP_TLS;
break;
default:
goto out_invalid_xprtsec_policy;
}
return 0; return 0;
out_invalid_transport_udp: out_invalid_transport_udp:
return nfs_invalf(fc, "NFS: Unsupported transport protocol udp"); return nfs_invalf(fc, "NFS: Unsupported transport protocol udp");
out_invalid_xprtsec_policy:
return nfs_invalf(fc, "NFS: Transport does not support xprtsec");
} }
/* /*
...@@ -430,6 +461,29 @@ static int nfs_parse_security_flavors(struct fs_context *fc, ...@@ -430,6 +461,29 @@ static int nfs_parse_security_flavors(struct fs_context *fc,
return 0; return 0;
} }
static int nfs_parse_xprtsec_policy(struct fs_context *fc,
struct fs_parameter *param)
{
struct nfs_fs_context *ctx = nfs_fc2context(fc);
trace_nfs_mount_assign(param->key, param->string);
switch (lookup_constant(nfs_xprtsec_policies, param->string, -1)) {
case Opt_xprtsec_none:
ctx->xprtsec.policy = RPC_XPRTSEC_NONE;
break;
case Opt_xprtsec_tls:
ctx->xprtsec.policy = RPC_XPRTSEC_TLS_ANON;
break;
case Opt_xprtsec_mtls:
ctx->xprtsec.policy = RPC_XPRTSEC_TLS_X509;
break;
default:
return nfs_invalf(fc, "NFS: Unrecognized transport security policy");
}
return 0;
}
static int nfs_parse_version_string(struct fs_context *fc, static int nfs_parse_version_string(struct fs_context *fc,
const char *string) const char *string)
{ {
...@@ -696,6 +750,11 @@ static int nfs_fs_context_parse_param(struct fs_context *fc, ...@@ -696,6 +750,11 @@ static int nfs_fs_context_parse_param(struct fs_context *fc,
if (ret < 0) if (ret < 0)
return ret; return ret;
break; break;
case Opt_xprtsec:
ret = nfs_parse_xprtsec_policy(fc, param);
if (ret < 0)
return ret;
break;
case Opt_proto: case Opt_proto:
if (!param->string) if (!param->string)
...@@ -791,16 +850,19 @@ static int nfs_fs_context_parse_param(struct fs_context *fc, ...@@ -791,16 +850,19 @@ static int nfs_fs_context_parse_param(struct fs_context *fc,
ctx->mount_server.addrlen = len; ctx->mount_server.addrlen = len;
break; break;
case Opt_nconnect: case Opt_nconnect:
trace_nfs_mount_assign(param->key, param->string);
if (result.uint_32 < 1 || result.uint_32 > NFS_MAX_CONNECTIONS) if (result.uint_32 < 1 || result.uint_32 > NFS_MAX_CONNECTIONS)
goto out_of_bounds; goto out_of_bounds;
ctx->nfs_server.nconnect = result.uint_32; ctx->nfs_server.nconnect = result.uint_32;
break; break;
case Opt_max_connect: case Opt_max_connect:
trace_nfs_mount_assign(param->key, param->string);
if (result.uint_32 < 1 || result.uint_32 > NFS_MAX_TRANSPORTS) if (result.uint_32 < 1 || result.uint_32 > NFS_MAX_TRANSPORTS)
goto out_of_bounds; goto out_of_bounds;
ctx->nfs_server.max_connect = result.uint_32; ctx->nfs_server.max_connect = result.uint_32;
break; break;
case Opt_lookupcache: case Opt_lookupcache:
trace_nfs_mount_assign(param->key, param->string);
switch (result.uint_32) { switch (result.uint_32) {
case Opt_lookupcache_all: case Opt_lookupcache_all:
ctx->flags &= ~(NFS_MOUNT_LOOKUP_CACHE_NONEG|NFS_MOUNT_LOOKUP_CACHE_NONE); ctx->flags &= ~(NFS_MOUNT_LOOKUP_CACHE_NONEG|NFS_MOUNT_LOOKUP_CACHE_NONE);
...@@ -817,6 +879,7 @@ static int nfs_fs_context_parse_param(struct fs_context *fc, ...@@ -817,6 +879,7 @@ static int nfs_fs_context_parse_param(struct fs_context *fc,
} }
break; break;
case Opt_local_lock: case Opt_local_lock:
trace_nfs_mount_assign(param->key, param->string);
switch (result.uint_32) { switch (result.uint_32) {
case Opt_local_lock_all: case Opt_local_lock_all:
ctx->flags |= (NFS_MOUNT_LOCAL_FLOCK | ctx->flags |= (NFS_MOUNT_LOCAL_FLOCK |
...@@ -837,6 +900,7 @@ static int nfs_fs_context_parse_param(struct fs_context *fc, ...@@ -837,6 +900,7 @@ static int nfs_fs_context_parse_param(struct fs_context *fc,
} }
break; break;
case Opt_write: case Opt_write:
trace_nfs_mount_assign(param->key, param->string);
switch (result.uint_32) { switch (result.uint_32) {
case Opt_write_lazy: case Opt_write_lazy:
ctx->flags &= ctx->flags &=
...@@ -1569,6 +1633,9 @@ static int nfs_init_fs_context(struct fs_context *fc) ...@@ -1569,6 +1633,9 @@ static int nfs_init_fs_context(struct fs_context *fc)
ctx->selected_flavor = RPC_AUTH_MAXFLAVOR; ctx->selected_flavor = RPC_AUTH_MAXFLAVOR;
ctx->minorversion = 0; ctx->minorversion = 0;
ctx->need_mount = true; ctx->need_mount = true;
ctx->xprtsec.policy = RPC_XPRTSEC_NONE;
ctx->xprtsec.cert_serial = TLS_NO_CERT;
ctx->xprtsec.privkey_serial = TLS_NO_PRIVKEY;
fc->s_iflags |= SB_I_STABLE_WRITES; fc->s_iflags |= SB_I_STABLE_WRITES;
} }
......
...@@ -845,7 +845,7 @@ int nfs_getattr(struct mnt_idmap *idmap, const struct path *path, ...@@ -845,7 +845,7 @@ int nfs_getattr(struct mnt_idmap *idmap, const struct path *path,
request_mask &= STATX_TYPE | STATX_MODE | STATX_NLINK | STATX_UID | request_mask &= STATX_TYPE | STATX_MODE | STATX_NLINK | STATX_UID |
STATX_GID | STATX_ATIME | STATX_MTIME | STATX_CTIME | STATX_GID | STATX_ATIME | STATX_MTIME | STATX_CTIME |
STATX_INO | STATX_SIZE | STATX_BLOCKS | STATX_BTIME | STATX_INO | STATX_SIZE | STATX_BLOCKS |
STATX_CHANGE_COOKIE; STATX_CHANGE_COOKIE;
if ((query_flags & AT_STATX_DONT_SYNC) && !force_sync) { if ((query_flags & AT_STATX_DONT_SYNC) && !force_sync) {
......
...@@ -81,6 +81,7 @@ struct nfs_client_initdata { ...@@ -81,6 +81,7 @@ struct nfs_client_initdata {
struct net *net; struct net *net;
const struct rpc_timeout *timeparms; const struct rpc_timeout *timeparms;
const struct cred *cred; const struct cred *cred;
struct xprtsec_parms xprtsec;
}; };
/* /*
...@@ -101,6 +102,7 @@ struct nfs_fs_context { ...@@ -101,6 +102,7 @@ struct nfs_fs_context {
unsigned int bsize; unsigned int bsize;
struct nfs_auth_info auth_info; struct nfs_auth_info auth_info;
rpc_authflavor_t selected_flavor; rpc_authflavor_t selected_flavor;
struct xprtsec_parms xprtsec;
char *client_address; char *client_address;
unsigned int version; unsigned int version;
unsigned int minorversion; unsigned int minorversion;
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
#include <linux/sunrpc/addr.h> #include <linux/sunrpc/addr.h>
#include "internal.h" #include "internal.h"
#include "nfs3_fs.h" #include "nfs3_fs.h"
#include "netns.h"
#include "sysfs.h"
#ifdef CONFIG_NFS_V3_ACL #ifdef CONFIG_NFS_V3_ACL
static struct rpc_stat nfsacl_rpcstat = { &nfsacl_program }; static struct rpc_stat nfsacl_rpcstat = { &nfsacl_program };
...@@ -31,6 +33,8 @@ static void nfs_init_server_aclclient(struct nfs_server *server) ...@@ -31,6 +33,8 @@ static void nfs_init_server_aclclient(struct nfs_server *server)
if (IS_ERR(server->client_acl)) if (IS_ERR(server->client_acl))
goto out_noacl; goto out_noacl;
nfs_sysfs_link_rpc_client(server, server->client_acl, NULL);
/* No errors! Assume that Sun nfsacls are supported */ /* No errors! Assume that Sun nfsacls are supported */
server->caps |= NFS_CAP_ACLS; server->caps |= NFS_CAP_ACLS;
return; return;
...@@ -93,6 +97,7 @@ struct nfs_client *nfs3_set_ds_client(struct nfs_server *mds_srv, ...@@ -93,6 +97,7 @@ struct nfs_client *nfs3_set_ds_client(struct nfs_server *mds_srv,
.net = mds_clp->cl_net, .net = mds_clp->cl_net,
.timeparms = &ds_timeout, .timeparms = &ds_timeout,
.cred = mds_srv->cred, .cred = mds_srv->cred,
.xprtsec = mds_clp->cl_xprtsec,
}; };
struct nfs_client *clp; struct nfs_client *clp;
char buf[INET6_ADDRSTRLEN + 1]; char buf[INET6_ADDRSTRLEN + 1];
...@@ -102,8 +107,12 @@ struct nfs_client *nfs3_set_ds_client(struct nfs_server *mds_srv, ...@@ -102,8 +107,12 @@ struct nfs_client *nfs3_set_ds_client(struct nfs_server *mds_srv,
return ERR_PTR(-EINVAL); return ERR_PTR(-EINVAL);
cl_init.hostname = buf; cl_init.hostname = buf;
if (mds_clp->cl_nconnect > 1 && ds_proto == XPRT_TRANSPORT_TCP) switch (ds_proto) {
cl_init.nconnect = mds_clp->cl_nconnect; case XPRT_TRANSPORT_TCP:
case XPRT_TRANSPORT_TCP_TLS:
if (mds_clp->cl_nconnect > 1)
cl_init.nconnect = mds_clp->cl_nconnect;
}
if (mds_srv->flags & NFS_MOUNT_NORESVPORT) if (mds_srv->flags & NFS_MOUNT_NORESVPORT)
__set_bit(NFS_CS_NORESVPORT, &cl_init.init_flags); __set_bit(NFS_CS_NORESVPORT, &cl_init.init_flags);
......
...@@ -1190,15 +1190,19 @@ static int _nfs42_proc_setxattr(struct inode *inode, const char *name, ...@@ -1190,15 +1190,19 @@ static int _nfs42_proc_setxattr(struct inode *inode, const char *name,
const void *buf, size_t buflen, int flags) const void *buf, size_t buflen, int flags)
{ {
struct nfs_server *server = NFS_SERVER(inode); struct nfs_server *server = NFS_SERVER(inode);
__u32 bitmask[NFS_BITMASK_SZ];
struct page *pages[NFS4XATTR_MAXPAGES]; struct page *pages[NFS4XATTR_MAXPAGES];
struct nfs42_setxattrargs arg = { struct nfs42_setxattrargs arg = {
.fh = NFS_FH(inode), .fh = NFS_FH(inode),
.bitmask = bitmask,
.xattr_pages = pages, .xattr_pages = pages,
.xattr_len = buflen, .xattr_len = buflen,
.xattr_name = name, .xattr_name = name,
.xattr_flags = flags, .xattr_flags = flags,
}; };
struct nfs42_setxattrres res; struct nfs42_setxattrres res = {
.server = server,
};
struct rpc_message msg = { struct rpc_message msg = {
.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_SETXATTR], .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_SETXATTR],
.rpc_argp = &arg, .rpc_argp = &arg,
...@@ -1210,13 +1214,22 @@ static int _nfs42_proc_setxattr(struct inode *inode, const char *name, ...@@ -1210,13 +1214,22 @@ static int _nfs42_proc_setxattr(struct inode *inode, const char *name,
if (buflen > server->sxasize) if (buflen > server->sxasize)
return -ERANGE; return -ERANGE;
res.fattr = nfs_alloc_fattr();
if (!res.fattr)
return -ENOMEM;
if (buflen > 0) { if (buflen > 0) {
np = nfs4_buf_to_pages_noslab(buf, buflen, arg.xattr_pages); np = nfs4_buf_to_pages_noslab(buf, buflen, arg.xattr_pages);
if (np < 0) if (np < 0) {
return np; ret = np;
goto out;
}
} else } else
np = 0; np = 0;
nfs4_bitmask_set(bitmask, server->cache_consistency_bitmask,
inode, NFS_INO_INVALID_CHANGE);
ret = nfs4_call_sync(server->client, server, &msg, &arg.seq_args, ret = nfs4_call_sync(server->client, server, &msg, &arg.seq_args,
&res.seq_res, 1); &res.seq_res, 1);
trace_nfs4_setxattr(inode, name, ret); trace_nfs4_setxattr(inode, name, ret);
...@@ -1224,9 +1237,13 @@ static int _nfs42_proc_setxattr(struct inode *inode, const char *name, ...@@ -1224,9 +1237,13 @@ static int _nfs42_proc_setxattr(struct inode *inode, const char *name,
for (; np > 0; np--) for (; np > 0; np--)
put_page(pages[np - 1]); put_page(pages[np - 1]);
if (!ret) if (!ret) {
nfs4_update_changeattr(inode, &res.cinfo, timestamp, 0); nfs4_update_changeattr(inode, &res.cinfo, timestamp, 0);
ret = nfs_post_op_update_inode(inode, res.fattr);
}
out:
kfree(res.fattr);
return ret; return ret;
} }
......
...@@ -991,6 +991,29 @@ static void nfs4_xattr_cache_init_once(void *p) ...@@ -991,6 +991,29 @@ static void nfs4_xattr_cache_init_once(void *p)
INIT_LIST_HEAD(&cache->dispose); INIT_LIST_HEAD(&cache->dispose);
} }
static int nfs4_xattr_shrinker_init(struct shrinker *shrinker,
struct list_lru *lru, const char *name)
{
int ret = 0;
ret = register_shrinker(shrinker, name);
if (ret)
return ret;
ret = list_lru_init_memcg(lru, shrinker);
if (ret)
unregister_shrinker(shrinker);
return ret;
}
static void nfs4_xattr_shrinker_destroy(struct shrinker *shrinker,
struct list_lru *lru)
{
unregister_shrinker(shrinker);
list_lru_destroy(lru);
}
int __init nfs4_xattr_cache_init(void) int __init nfs4_xattr_cache_init(void)
{ {
int ret = 0; int ret = 0;
...@@ -1002,44 +1025,30 @@ int __init nfs4_xattr_cache_init(void) ...@@ -1002,44 +1025,30 @@ int __init nfs4_xattr_cache_init(void)
if (nfs4_xattr_cache_cachep == NULL) if (nfs4_xattr_cache_cachep == NULL)
return -ENOMEM; return -ENOMEM;
ret = list_lru_init_memcg(&nfs4_xattr_large_entry_lru, ret = nfs4_xattr_shrinker_init(&nfs4_xattr_cache_shrinker,
&nfs4_xattr_large_entry_shrinker); &nfs4_xattr_cache_lru,
if (ret) "nfs-xattr_cache");
goto out4;
ret = list_lru_init_memcg(&nfs4_xattr_entry_lru,
&nfs4_xattr_entry_shrinker);
if (ret)
goto out3;
ret = list_lru_init_memcg(&nfs4_xattr_cache_lru,
&nfs4_xattr_cache_shrinker);
if (ret)
goto out2;
ret = register_shrinker(&nfs4_xattr_cache_shrinker, "nfs-xattr_cache");
if (ret) if (ret)
goto out1; goto out1;
ret = register_shrinker(&nfs4_xattr_entry_shrinker, "nfs-xattr_entry"); ret = nfs4_xattr_shrinker_init(&nfs4_xattr_entry_shrinker,
&nfs4_xattr_entry_lru,
"nfs-xattr_entry");
if (ret) if (ret)
goto out; goto out2;
ret = register_shrinker(&nfs4_xattr_large_entry_shrinker, ret = nfs4_xattr_shrinker_init(&nfs4_xattr_large_entry_shrinker,
"nfs-xattr_large_entry"); &nfs4_xattr_large_entry_lru,
"nfs-xattr_large_entry");
if (!ret) if (!ret)
return 0; return 0;
unregister_shrinker(&nfs4_xattr_entry_shrinker); nfs4_xattr_shrinker_destroy(&nfs4_xattr_entry_shrinker,
out: &nfs4_xattr_entry_lru);
unregister_shrinker(&nfs4_xattr_cache_shrinker);
out1:
list_lru_destroy(&nfs4_xattr_cache_lru);
out2: out2:
list_lru_destroy(&nfs4_xattr_entry_lru); nfs4_xattr_shrinker_destroy(&nfs4_xattr_cache_shrinker,
out3: &nfs4_xattr_cache_lru);
list_lru_destroy(&nfs4_xattr_large_entry_lru); out1:
out4:
kmem_cache_destroy(nfs4_xattr_cache_cachep); kmem_cache_destroy(nfs4_xattr_cache_cachep);
return ret; return ret;
...@@ -1047,11 +1056,11 @@ int __init nfs4_xattr_cache_init(void) ...@@ -1047,11 +1056,11 @@ int __init nfs4_xattr_cache_init(void)
void nfs4_xattr_cache_exit(void) void nfs4_xattr_cache_exit(void)
{ {
unregister_shrinker(&nfs4_xattr_large_entry_shrinker); nfs4_xattr_shrinker_destroy(&nfs4_xattr_large_entry_shrinker,
unregister_shrinker(&nfs4_xattr_entry_shrinker); &nfs4_xattr_large_entry_lru);
unregister_shrinker(&nfs4_xattr_cache_shrinker); nfs4_xattr_shrinker_destroy(&nfs4_xattr_entry_shrinker,
list_lru_destroy(&nfs4_xattr_large_entry_lru); &nfs4_xattr_entry_lru);
list_lru_destroy(&nfs4_xattr_entry_lru); nfs4_xattr_shrinker_destroy(&nfs4_xattr_cache_shrinker,
list_lru_destroy(&nfs4_xattr_cache_lru); &nfs4_xattr_cache_lru);
kmem_cache_destroy(nfs4_xattr_cache_cachep); kmem_cache_destroy(nfs4_xattr_cache_cachep);
} }
This diff is collapsed.
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
#include "nfs4idmap.h" #include "nfs4idmap.h"
#include "pnfs.h" #include "pnfs.h"
#include "netns.h" #include "netns.h"
#include "sysfs.h"
#define NFSDBG_FACILITY NFSDBG_CLIENT #define NFSDBG_FACILITY NFSDBG_CLIENT
...@@ -896,7 +897,8 @@ static int nfs4_set_client(struct nfs_server *server, ...@@ -896,7 +897,8 @@ static int nfs4_set_client(struct nfs_server *server,
int proto, const struct rpc_timeout *timeparms, int proto, const struct rpc_timeout *timeparms,
u32 minorversion, unsigned int nconnect, u32 minorversion, unsigned int nconnect,
unsigned int max_connect, unsigned int max_connect,
struct net *net) struct net *net,
struct xprtsec_parms *xprtsec)
{ {
struct nfs_client_initdata cl_init = { struct nfs_client_initdata cl_init = {
.hostname = hostname, .hostname = hostname,
...@@ -909,6 +911,7 @@ static int nfs4_set_client(struct nfs_server *server, ...@@ -909,6 +911,7 @@ static int nfs4_set_client(struct nfs_server *server,
.net = net, .net = net,
.timeparms = timeparms, .timeparms = timeparms,
.cred = server->cred, .cred = server->cred,
.xprtsec = *xprtsec,
}; };
struct nfs_client *clp; struct nfs_client *clp;
...@@ -916,8 +919,11 @@ static int nfs4_set_client(struct nfs_server *server, ...@@ -916,8 +919,11 @@ static int nfs4_set_client(struct nfs_server *server,
__set_bit(NFS_CS_REUSEPORT, &cl_init.init_flags); __set_bit(NFS_CS_REUSEPORT, &cl_init.init_flags);
else else
cl_init.max_connect = max_connect; cl_init.max_connect = max_connect;
if (proto == XPRT_TRANSPORT_TCP) switch (proto) {
case XPRT_TRANSPORT_TCP:
case XPRT_TRANSPORT_TCP_TLS:
cl_init.nconnect = nconnect; cl_init.nconnect = nconnect;
}
if (server->flags & NFS_MOUNT_NORESVPORT) if (server->flags & NFS_MOUNT_NORESVPORT)
__set_bit(NFS_CS_NORESVPORT, &cl_init.init_flags); __set_bit(NFS_CS_NORESVPORT, &cl_init.init_flags);
...@@ -947,6 +953,9 @@ static int nfs4_set_client(struct nfs_server *server, ...@@ -947,6 +953,9 @@ static int nfs4_set_client(struct nfs_server *server,
set_bit(NFS_CS_CHECK_LEASE_TIME, &clp->cl_res_state); set_bit(NFS_CS_CHECK_LEASE_TIME, &clp->cl_res_state);
server->nfs_client = clp; server->nfs_client = clp;
nfs_sysfs_add_server(server);
nfs_sysfs_link_rpc_client(server, clp->cl_rpcclient, "_state");
return 0; return 0;
} }
...@@ -978,6 +987,7 @@ struct nfs_client *nfs4_set_ds_client(struct nfs_server *mds_srv, ...@@ -978,6 +987,7 @@ struct nfs_client *nfs4_set_ds_client(struct nfs_server *mds_srv,
.net = mds_clp->cl_net, .net = mds_clp->cl_net,
.timeparms = &ds_timeout, .timeparms = &ds_timeout,
.cred = mds_srv->cred, .cred = mds_srv->cred,
.xprtsec = mds_srv->nfs_client->cl_xprtsec,
}; };
char buf[INET6_ADDRSTRLEN + 1]; char buf[INET6_ADDRSTRLEN + 1];
...@@ -985,9 +995,13 @@ struct nfs_client *nfs4_set_ds_client(struct nfs_server *mds_srv, ...@@ -985,9 +995,13 @@ struct nfs_client *nfs4_set_ds_client(struct nfs_server *mds_srv,
return ERR_PTR(-EINVAL); return ERR_PTR(-EINVAL);
cl_init.hostname = buf; cl_init.hostname = buf;
if (mds_clp->cl_nconnect > 1 && ds_proto == XPRT_TRANSPORT_TCP) { switch (ds_proto) {
cl_init.nconnect = mds_clp->cl_nconnect; case XPRT_TRANSPORT_TCP:
cl_init.max_connect = NFS_MAX_TRANSPORTS; case XPRT_TRANSPORT_TCP_TLS:
if (mds_clp->cl_nconnect > 1) {
cl_init.nconnect = mds_clp->cl_nconnect;
cl_init.max_connect = NFS_MAX_TRANSPORTS;
}
} }
if (mds_srv->flags & NFS_MOUNT_NORESVPORT) if (mds_srv->flags & NFS_MOUNT_NORESVPORT)
...@@ -1157,7 +1171,8 @@ static int nfs4_init_server(struct nfs_server *server, struct fs_context *fc) ...@@ -1157,7 +1171,8 @@ static int nfs4_init_server(struct nfs_server *server, struct fs_context *fc)
ctx->minorversion, ctx->minorversion,
ctx->nfs_server.nconnect, ctx->nfs_server.nconnect,
ctx->nfs_server.max_connect, ctx->nfs_server.max_connect,
fc->net_ns); fc->net_ns,
&ctx->xprtsec);
if (error < 0) if (error < 0)
return error; return error;
...@@ -1219,8 +1234,8 @@ struct nfs_server *nfs4_create_referral_server(struct fs_context *fc) ...@@ -1219,8 +1234,8 @@ struct nfs_server *nfs4_create_referral_server(struct fs_context *fc)
struct nfs_fs_context *ctx = nfs_fc2context(fc); struct nfs_fs_context *ctx = nfs_fc2context(fc);
struct nfs_client *parent_client; struct nfs_client *parent_client;
struct nfs_server *server, *parent_server; struct nfs_server *server, *parent_server;
int proto, error;
bool auth_probe; bool auth_probe;
int error;
server = nfs_alloc_server(); server = nfs_alloc_server();
if (!server) if (!server)
...@@ -1247,23 +1262,28 @@ struct nfs_server *nfs4_create_referral_server(struct fs_context *fc) ...@@ -1247,23 +1262,28 @@ struct nfs_server *nfs4_create_referral_server(struct fs_context *fc)
parent_client->cl_mvops->minor_version, parent_client->cl_mvops->minor_version,
parent_client->cl_nconnect, parent_client->cl_nconnect,
parent_client->cl_max_connect, parent_client->cl_max_connect,
parent_client->cl_net); parent_client->cl_net,
&parent_client->cl_xprtsec);
if (!error) if (!error)
goto init_server; goto init_server;
#endif /* IS_ENABLED(CONFIG_SUNRPC_XPRT_RDMA) */ #endif /* IS_ENABLED(CONFIG_SUNRPC_XPRT_RDMA) */
proto = XPRT_TRANSPORT_TCP;
if (parent_client->cl_xprtsec.policy != RPC_XPRTSEC_NONE)
proto = XPRT_TRANSPORT_TCP_TLS;
rpc_set_port(&ctx->nfs_server.address, NFS_PORT); rpc_set_port(&ctx->nfs_server.address, NFS_PORT);
error = nfs4_set_client(server, error = nfs4_set_client(server,
ctx->nfs_server.hostname, ctx->nfs_server.hostname,
&ctx->nfs_server._address, &ctx->nfs_server._address,
ctx->nfs_server.addrlen, ctx->nfs_server.addrlen,
parent_client->cl_ipaddr, parent_client->cl_ipaddr,
XPRT_TRANSPORT_TCP, proto,
parent_server->client->cl_timeout, parent_server->client->cl_timeout,
parent_client->cl_mvops->minor_version, parent_client->cl_mvops->minor_version,
parent_client->cl_nconnect, parent_client->cl_nconnect,
parent_client->cl_max_connect, parent_client->cl_max_connect,
parent_client->cl_net); parent_client->cl_net,
&parent_client->cl_xprtsec);
if (error < 0) if (error < 0)
goto error; goto error;
...@@ -1314,6 +1334,7 @@ int nfs4_update_server(struct nfs_server *server, const char *hostname, ...@@ -1314,6 +1334,7 @@ int nfs4_update_server(struct nfs_server *server, const char *hostname,
.dstaddr = (struct sockaddr *)sap, .dstaddr = (struct sockaddr *)sap,
.addrlen = salen, .addrlen = salen,
.servername = hostname, .servername = hostname,
/* cel: bleh. We might need to pass TLS parameters here */
}; };
char buf[INET6_ADDRSTRLEN + 1]; char buf[INET6_ADDRSTRLEN + 1];
struct sockaddr_storage address; struct sockaddr_storage address;
...@@ -1336,7 +1357,8 @@ int nfs4_update_server(struct nfs_server *server, const char *hostname, ...@@ -1336,7 +1357,8 @@ int nfs4_update_server(struct nfs_server *server, const char *hostname,
error = nfs4_set_client(server, hostname, sap, salen, buf, error = nfs4_set_client(server, hostname, sap, salen, buf,
clp->cl_proto, clnt->cl_timeout, clp->cl_proto, clnt->cl_timeout,
clp->cl_minorversion, clp->cl_minorversion,
clp->cl_nconnect, clp->cl_max_connect, net); clp->cl_nconnect, clp->cl_max_connect,
net, &clp->cl_xprtsec);
clear_bit(NFS_MIG_TSM_POSSIBLE, &server->mig_status); clear_bit(NFS_MIG_TSM_POSSIBLE, &server->mig_status);
if (error != 0) { if (error != 0) {
nfs_server_insert_lists(server); nfs_server_insert_lists(server);
......
...@@ -921,6 +921,7 @@ static int nfs41_sequence_process(struct rpc_task *task, ...@@ -921,6 +921,7 @@ static int nfs41_sequence_process(struct rpc_task *task,
out_noaction: out_noaction:
return ret; return ret;
session_recover: session_recover:
set_bit(NFS4_SLOT_TBL_DRAINING, &session->fc_slot_table.slot_tbl_state);
nfs4_schedule_session_recovery(session, status); nfs4_schedule_session_recovery(session, status);
dprintk("%s ERROR: %d Reset session\n", __func__, status); dprintk("%s ERROR: %d Reset session\n", __func__, status);
nfs41_sequence_free_slot(res); nfs41_sequence_free_slot(res);
...@@ -7159,7 +7160,6 @@ static void nfs4_lock_done(struct rpc_task *task, void *calldata) ...@@ -7159,7 +7160,6 @@ static void nfs4_lock_done(struct rpc_task *task, void *calldata)
{ {
struct nfs4_lockdata *data = calldata; struct nfs4_lockdata *data = calldata;
struct nfs4_lock_state *lsp = data->lsp; struct nfs4_lock_state *lsp = data->lsp;
struct nfs_server *server = NFS_SERVER(d_inode(data->ctx->dentry));
if (!nfs4_sequence_done(task, &data->res.seq_res)) if (!nfs4_sequence_done(task, &data->res.seq_res))
return; return;
...@@ -7167,7 +7167,8 @@ static void nfs4_lock_done(struct rpc_task *task, void *calldata) ...@@ -7167,7 +7167,8 @@ static void nfs4_lock_done(struct rpc_task *task, void *calldata)
data->rpc_status = task->tk_status; data->rpc_status = task->tk_status;
switch (task->tk_status) { switch (task->tk_status) {
case 0: case 0:
renew_lease(server, data->timestamp); renew_lease(NFS_SERVER(d_inode(data->ctx->dentry)),
data->timestamp);
if (data->arg.new_lock && !data->cancelled) { if (data->arg.new_lock && !data->cancelled) {
data->fl.fl_flags &= ~(FL_SLEEP | FL_ACCESS); data->fl.fl_flags &= ~(FL_SLEEP | FL_ACCESS);
if (locks_lock_inode_wait(lsp->ls_state->inode, &data->fl) < 0) if (locks_lock_inode_wait(lsp->ls_state->inode, &data->fl) < 0)
...@@ -7188,8 +7189,6 @@ static void nfs4_lock_done(struct rpc_task *task, void *calldata) ...@@ -7188,8 +7189,6 @@ static void nfs4_lock_done(struct rpc_task *task, void *calldata)
if (!nfs4_stateid_match(&data->arg.open_stateid, if (!nfs4_stateid_match(&data->arg.open_stateid,
&lsp->ls_state->open_stateid)) &lsp->ls_state->open_stateid))
goto out_restart; goto out_restart;
else if (nfs4_async_handle_error(task, server, lsp->ls_state, NULL) == -EAGAIN)
goto out_restart;
} else if (!nfs4_stateid_match(&data->arg.lock_stateid, } else if (!nfs4_stateid_match(&data->arg.lock_stateid,
&lsp->ls_stateid)) &lsp->ls_stateid))
goto out_restart; goto out_restart;
...@@ -9371,7 +9370,7 @@ static void nfs41_sequence_call_done(struct rpc_task *task, void *data) ...@@ -9371,7 +9370,7 @@ static void nfs41_sequence_call_done(struct rpc_task *task, void *data)
return; return;
trace_nfs4_sequence(clp, task->tk_status); trace_nfs4_sequence(clp, task->tk_status);
if (task->tk_status < 0) { if (task->tk_status < 0 && !task->tk_client->cl_shutdown) {
dprintk("%s ERROR %d\n", __func__, task->tk_status); dprintk("%s ERROR %d\n", __func__, task->tk_status);
if (refcount_read(&clp->cl_count) == 1) if (refcount_read(&clp->cl_count) == 1)
return; return;
......
...@@ -1210,6 +1210,9 @@ void nfs4_schedule_state_manager(struct nfs_client *clp) ...@@ -1210,6 +1210,9 @@ void nfs4_schedule_state_manager(struct nfs_client *clp)
struct task_struct *task; struct task_struct *task;
char buf[INET6_ADDRSTRLEN + sizeof("-manager") + 1]; char buf[INET6_ADDRSTRLEN + sizeof("-manager") + 1];
if (clp->cl_rpcclient->cl_shutdown)
return;
set_bit(NFS4CLNT_RUN_MANAGER, &clp->cl_state); set_bit(NFS4CLNT_RUN_MANAGER, &clp->cl_state);
if (test_and_set_bit(NFS4CLNT_MANAGER_AVAILABLE, &clp->cl_state) != 0) { if (test_and_set_bit(NFS4CLNT_MANAGER_AVAILABLE, &clp->cl_state) != 0) {
wake_up_var(&clp->cl_state); wake_up_var(&clp->cl_state);
......
...@@ -59,6 +59,8 @@ ...@@ -59,6 +59,8 @@
#include <linux/uaccess.h> #include <linux/uaccess.h>
#include <linux/nfs_ssc.h> #include <linux/nfs_ssc.h>
#include <uapi/linux/tls.h>
#include "nfs4_fs.h" #include "nfs4_fs.h"
#include "callback.h" #include "callback.h"
#include "delegation.h" #include "delegation.h"
...@@ -68,6 +70,8 @@ ...@@ -68,6 +70,8 @@
#include "nfs4session.h" #include "nfs4session.h"
#include "pnfs.h" #include "pnfs.h"
#include "nfs.h" #include "nfs.h"
#include "netns.h"
#include "sysfs.h"
#define NFSDBG_FACILITY NFSDBG_VFS #define NFSDBG_FACILITY NFSDBG_VFS
...@@ -491,6 +495,16 @@ static void nfs_show_mount_options(struct seq_file *m, struct nfs_server *nfss, ...@@ -491,6 +495,16 @@ static void nfs_show_mount_options(struct seq_file *m, struct nfs_server *nfss,
seq_printf(m, ",timeo=%lu", 10U * nfss->client->cl_timeout->to_initval / HZ); seq_printf(m, ",timeo=%lu", 10U * nfss->client->cl_timeout->to_initval / HZ);
seq_printf(m, ",retrans=%u", nfss->client->cl_timeout->to_retries); seq_printf(m, ",retrans=%u", nfss->client->cl_timeout->to_retries);
seq_printf(m, ",sec=%s", nfs_pseudoflavour_to_name(nfss->client->cl_auth->au_flavor)); seq_printf(m, ",sec=%s", nfs_pseudoflavour_to_name(nfss->client->cl_auth->au_flavor));
switch (clp->cl_xprtsec.policy) {
case RPC_XPRTSEC_TLS_ANON:
seq_puts(m, ",xprtsec=tls");
break;
case RPC_XPRTSEC_TLS_X509:
seq_puts(m, ",xprtsec=mtls");
break;
default:
break;
}
if (version != 4) if (version != 4)
nfs_show_mountd_options(m, nfss, showdefaults); nfs_show_mountd_options(m, nfss, showdefaults);
...@@ -1077,6 +1091,7 @@ static void nfs_fill_super(struct super_block *sb, struct nfs_fs_context *ctx) ...@@ -1077,6 +1091,7 @@ static void nfs_fill_super(struct super_block *sb, struct nfs_fs_context *ctx)
&sb->s_blocksize_bits); &sb->s_blocksize_bits);
nfs_super_set_maxbytes(sb, server->maxfilesize); nfs_super_set_maxbytes(sb, server->maxfilesize);
nfs_sysfs_move_server_to_sb(sb);
server->has_sec_mnt_opts = ctx->has_sec_mnt_opts; server->has_sec_mnt_opts = ctx->has_sec_mnt_opts;
} }
...@@ -1319,13 +1334,14 @@ int nfs_get_tree_common(struct fs_context *fc) ...@@ -1319,13 +1334,14 @@ int nfs_get_tree_common(struct fs_context *fc)
} }
/* /*
* Destroy an NFS2/3 superblock * Destroy an NFS superblock
*/ */
void nfs_kill_super(struct super_block *s) void nfs_kill_super(struct super_block *s)
{ {
struct nfs_server *server = NFS_SB(s); struct nfs_server *server = NFS_SB(s);
dev_t dev = s->s_dev; dev_t dev = s->s_dev;
nfs_sysfs_move_sb_to_server(server);
generic_shutdown_super(s); generic_shutdown_super(s);
nfs_fscache_release_super_cookie(s); nfs_fscache_release_super_cookie(s);
......
...@@ -12,17 +12,18 @@ ...@@ -12,17 +12,18 @@
#include <linux/string.h> #include <linux/string.h>
#include <linux/nfs_fs.h> #include <linux/nfs_fs.h>
#include <linux/rcupdate.h> #include <linux/rcupdate.h>
#include <linux/lockd/lockd.h>
#include "nfs4_fs.h" #include "nfs4_fs.h"
#include "netns.h" #include "netns.h"
#include "sysfs.h" #include "sysfs.h"
struct kobject *nfs_client_kobj; static struct kset *nfs_kset;
static struct kset *nfs_client_kset;
static void nfs_netns_object_release(struct kobject *kobj) static void nfs_kset_release(struct kobject *kobj)
{ {
kfree(kobj); struct kset *kset = container_of(kobj, struct kset, kobj);
kfree(kset);
} }
static const struct kobj_ns_type_operations *nfs_netns_object_child_ns_type( static const struct kobj_ns_type_operations *nfs_netns_object_child_ns_type(
...@@ -31,46 +32,42 @@ static const struct kobj_ns_type_operations *nfs_netns_object_child_ns_type( ...@@ -31,46 +32,42 @@ static const struct kobj_ns_type_operations *nfs_netns_object_child_ns_type(
return &net_ns_type_operations; return &net_ns_type_operations;
} }
static struct kobj_type nfs_netns_object_type = { static struct kobj_type nfs_kset_type = {
.release = nfs_netns_object_release, .release = nfs_kset_release,
.sysfs_ops = &kobj_sysfs_ops, .sysfs_ops = &kobj_sysfs_ops,
.child_ns_type = nfs_netns_object_child_ns_type, .child_ns_type = nfs_netns_object_child_ns_type,
}; };
static struct kobject *nfs_netns_object_alloc(const char *name, int nfs_sysfs_init(void)
struct kset *kset, struct kobject *parent)
{ {
struct kobject *kobj; int ret;
nfs_kset = kzalloc(sizeof(*nfs_kset), GFP_KERNEL);
if (!nfs_kset)
return -ENOMEM;
kobj = kzalloc(sizeof(*kobj), GFP_KERNEL); ret = kobject_set_name(&nfs_kset->kobj, "nfs");
if (kobj) { if (ret) {
kobj->kset = kset; kfree(nfs_kset);
if (kobject_init_and_add(kobj, &nfs_netns_object_type, return ret;
parent, "%s", name) == 0)
return kobj;
kobject_put(kobj);
} }
return NULL;
}
int nfs_sysfs_init(void) nfs_kset->kobj.parent = fs_kobj;
{ nfs_kset->kobj.ktype = &nfs_kset_type;
nfs_client_kset = kset_create_and_add("nfs", NULL, fs_kobj); nfs_kset->kobj.kset = NULL;
if (!nfs_client_kset)
return -ENOMEM; ret = kset_register(nfs_kset);
nfs_client_kobj = nfs_netns_object_alloc("net", nfs_client_kset, NULL); if (ret) {
if (!nfs_client_kobj) { kfree(nfs_kset);
kset_unregister(nfs_client_kset); return ret;
nfs_client_kset = NULL;
return -ENOMEM;
} }
return 0; return 0;
} }
void nfs_sysfs_exit(void) void nfs_sysfs_exit(void)
{ {
kobject_put(nfs_client_kobj); kset_unregister(nfs_kset);
kset_unregister(nfs_client_kset);
} }
static ssize_t nfs_netns_identifier_show(struct kobject *kobj, static ssize_t nfs_netns_identifier_show(struct kobject *kobj,
...@@ -127,7 +124,6 @@ static void nfs_netns_client_release(struct kobject *kobj) ...@@ -127,7 +124,6 @@ static void nfs_netns_client_release(struct kobject *kobj)
kobject); kobject);
kfree(rcu_dereference_raw(c->identifier)); kfree(rcu_dereference_raw(c->identifier));
kfree(c);
} }
static const void *nfs_netns_client_namespace(const struct kobject *kobj) static const void *nfs_netns_client_namespace(const struct kobject *kobj)
...@@ -151,6 +147,25 @@ static struct kobj_type nfs_netns_client_type = { ...@@ -151,6 +147,25 @@ static struct kobj_type nfs_netns_client_type = {
.namespace = nfs_netns_client_namespace, .namespace = nfs_netns_client_namespace,
}; };
static void nfs_netns_object_release(struct kobject *kobj)
{
struct nfs_netns_client *c = container_of(kobj,
struct nfs_netns_client,
nfs_net_kobj);
kfree(c);
}
static const void *nfs_netns_namespace(const struct kobject *kobj)
{
return container_of(kobj, struct nfs_netns_client, nfs_net_kobj)->net;
}
static struct kobj_type nfs_netns_object_type = {
.release = nfs_netns_object_release,
.sysfs_ops = &kobj_sysfs_ops,
.namespace = nfs_netns_namespace,
};
static struct nfs_netns_client *nfs_netns_client_alloc(struct kobject *parent, static struct nfs_netns_client *nfs_netns_client_alloc(struct kobject *parent,
struct net *net) struct net *net)
{ {
...@@ -159,10 +174,19 @@ static struct nfs_netns_client *nfs_netns_client_alloc(struct kobject *parent, ...@@ -159,10 +174,19 @@ static struct nfs_netns_client *nfs_netns_client_alloc(struct kobject *parent,
p = kzalloc(sizeof(*p), GFP_KERNEL); p = kzalloc(sizeof(*p), GFP_KERNEL);
if (p) { if (p) {
p->net = net; p->net = net;
p->kobject.kset = nfs_client_kset; p->kobject.kset = nfs_kset;
p->nfs_net_kobj.kset = nfs_kset;
if (kobject_init_and_add(&p->nfs_net_kobj, &nfs_netns_object_type,
parent, "net") != 0) {
kobject_put(&p->nfs_net_kobj);
return NULL;
}
if (kobject_init_and_add(&p->kobject, &nfs_netns_client_type, if (kobject_init_and_add(&p->kobject, &nfs_netns_client_type,
parent, "nfs_client") == 0) &p->nfs_net_kobj, "nfs_client") == 0)
return p; return p;
kobject_put(&p->kobject); kobject_put(&p->kobject);
} }
return NULL; return NULL;
...@@ -172,7 +196,7 @@ void nfs_netns_sysfs_setup(struct nfs_net *netns, struct net *net) ...@@ -172,7 +196,7 @@ void nfs_netns_sysfs_setup(struct nfs_net *netns, struct net *net)
{ {
struct nfs_netns_client *clp; struct nfs_netns_client *clp;
clp = nfs_netns_client_alloc(nfs_client_kobj, net); clp = nfs_netns_client_alloc(&nfs_kset->kobj, net);
if (clp) { if (clp) {
netns->nfs_client = clp; netns->nfs_client = clp;
kobject_uevent(&clp->kobject, KOBJ_ADD); kobject_uevent(&clp->kobject, KOBJ_ADD);
...@@ -187,6 +211,149 @@ void nfs_netns_sysfs_destroy(struct nfs_net *netns) ...@@ -187,6 +211,149 @@ void nfs_netns_sysfs_destroy(struct nfs_net *netns)
kobject_uevent(&clp->kobject, KOBJ_REMOVE); kobject_uevent(&clp->kobject, KOBJ_REMOVE);
kobject_del(&clp->kobject); kobject_del(&clp->kobject);
kobject_put(&clp->kobject); kobject_put(&clp->kobject);
kobject_del(&clp->nfs_net_kobj);
kobject_put(&clp->nfs_net_kobj);
netns->nfs_client = NULL; netns->nfs_client = NULL;
} }
} }
static bool shutdown_match_client(const struct rpc_task *task, const void *data)
{
return true;
}
static void shutdown_client(struct rpc_clnt *clnt)
{
clnt->cl_shutdown = 1;
rpc_cancel_tasks(clnt, -EIO, shutdown_match_client, NULL);
}
static ssize_t
shutdown_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf)
{
struct nfs_server *server = container_of(kobj, struct nfs_server, kobj);
bool shutdown = server->flags & NFS_MOUNT_SHUTDOWN;
return sysfs_emit(buf, "%d\n", shutdown);
}
static ssize_t
shutdown_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count)
{
struct nfs_server *server;
int ret, val;
server = container_of(kobj, struct nfs_server, kobj);
ret = kstrtoint(buf, 0, &val);
if (ret)
return ret;
if (val != 1)
return -EINVAL;
/* already shut down? */
if (server->flags & NFS_MOUNT_SHUTDOWN)
goto out;
server->flags |= NFS_MOUNT_SHUTDOWN;
shutdown_client(server->client);
shutdown_client(server->nfs_client->cl_rpcclient);
if (!IS_ERR(server->client_acl))
shutdown_client(server->client_acl);
if (server->nlm_host)
shutdown_client(server->nlm_host->h_rpcclnt);
out:
return count;
}
static struct kobj_attribute nfs_sysfs_attr_shutdown = __ATTR_RW(shutdown);
#define RPC_CLIENT_NAME_SIZE 64
void nfs_sysfs_link_rpc_client(struct nfs_server *server,
struct rpc_clnt *clnt, const char *uniq)
{
char name[RPC_CLIENT_NAME_SIZE];
int ret;
strcpy(name, clnt->cl_program->name);
strcat(name, uniq ? uniq : "");
strcat(name, "_client");
ret = sysfs_create_link_nowarn(&server->kobj,
&clnt->cl_sysfs->kobject, name);
if (ret < 0)
pr_warn("NFS: can't create link to %s in sysfs (%d)\n",
name, ret);
}
EXPORT_SYMBOL_GPL(nfs_sysfs_link_rpc_client);
static void nfs_sysfs_sb_release(struct kobject *kobj)
{
/* no-op: why? see lib/kobject.c kobject_cleanup() */
}
static const void *nfs_netns_server_namespace(const struct kobject *kobj)
{
return container_of(kobj, struct nfs_server, kobj)->nfs_client->cl_net;
}
static struct kobj_type nfs_sb_ktype = {
.release = nfs_sysfs_sb_release,
.sysfs_ops = &kobj_sysfs_ops,
.namespace = nfs_netns_server_namespace,
.child_ns_type = nfs_netns_object_child_ns_type,
};
void nfs_sysfs_add_server(struct nfs_server *server)
{
int ret;
ret = kobject_init_and_add(&server->kobj, &nfs_sb_ktype,
&nfs_kset->kobj, "server-%d", server->s_sysfs_id);
if (ret < 0) {
pr_warn("NFS: nfs sysfs add server-%d failed (%d)\n",
server->s_sysfs_id, ret);
return;
}
ret = sysfs_create_file_ns(&server->kobj, &nfs_sysfs_attr_shutdown.attr,
nfs_netns_server_namespace(&server->kobj));
if (ret < 0)
pr_warn("NFS: sysfs_create_file_ns for server-%d failed (%d)\n",
server->s_sysfs_id, ret);
}
EXPORT_SYMBOL_GPL(nfs_sysfs_add_server);
void nfs_sysfs_move_server_to_sb(struct super_block *s)
{
struct nfs_server *server = s->s_fs_info;
int ret;
ret = kobject_rename(&server->kobj, s->s_id);
if (ret < 0)
pr_warn("NFS: rename sysfs %s failed (%d)\n",
server->kobj.name, ret);
}
void nfs_sysfs_move_sb_to_server(struct nfs_server *server)
{
const char *s;
int ret = -ENOMEM;
s = kasprintf(GFP_KERNEL, "server-%d", server->s_sysfs_id);
if (s)
ret = kobject_rename(&server->kobj, s);
if (ret < 0)
pr_warn("NFS: rename sysfs %s failed (%d)\n",
server->kobj.name, ret);
}
/* unlink, not dec-ref */
void nfs_sysfs_remove_server(struct nfs_server *server)
{
kobject_del(&server->kobj);
}
...@@ -10,11 +10,12 @@ ...@@ -10,11 +10,12 @@
struct nfs_netns_client { struct nfs_netns_client {
struct kobject kobject; struct kobject kobject;
struct kobject nfs_net_kobj;
struct net *net; struct net *net;
const char __rcu *identifier; const char __rcu *identifier;
}; };
extern struct kobject *nfs_client_kobj; extern struct kobject *nfs_net_kobj;
extern int nfs_sysfs_init(void); extern int nfs_sysfs_init(void);
extern void nfs_sysfs_exit(void); extern void nfs_sysfs_exit(void);
...@@ -22,4 +23,11 @@ extern void nfs_sysfs_exit(void); ...@@ -22,4 +23,11 @@ extern void nfs_sysfs_exit(void);
void nfs_netns_sysfs_setup(struct nfs_net *netns, struct net *net); void nfs_netns_sysfs_setup(struct nfs_net *netns, struct net *net);
void nfs_netns_sysfs_destroy(struct nfs_net *netns); void nfs_netns_sysfs_destroy(struct nfs_net *netns);
void nfs_sysfs_link_rpc_client(struct nfs_server *server,
struct rpc_clnt *clnt, const char *sysfs_prefix);
void nfs_sysfs_add_server(struct nfs_server *s);
void nfs_sysfs_move_server_to_sb(struct super_block *s);
void nfs_sysfs_move_sb_to_server(struct nfs_server *s);
void nfs_sysfs_remove_server(struct nfs_server *s);
#endif #endif
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
/* Dummy declarations */ /* Dummy declarations */
struct svc_rqst; struct svc_rqst;
struct rpc_task; struct rpc_task;
struct rpc_clnt;
/* /*
* This is the set of functions for lockd->nfsd communication * This is the set of functions for lockd->nfsd communication
...@@ -56,6 +57,7 @@ struct nlmclnt_initdata { ...@@ -56,6 +57,7 @@ struct nlmclnt_initdata {
extern struct nlm_host *nlmclnt_init(const struct nlmclnt_initdata *nlm_init); extern struct nlm_host *nlmclnt_init(const struct nlmclnt_initdata *nlm_init);
extern void nlmclnt_done(struct nlm_host *host); extern void nlmclnt_done(struct nlm_host *host);
extern struct rpc_clnt *nlmclnt_rpc_clnt(struct nlm_host *host);
/* /*
* NLM client operations provide a means to modify RPC processing of NLM * NLM client operations provide a means to modify RPC processing of NLM
......
...@@ -63,7 +63,8 @@ struct nfs_client { ...@@ -63,7 +63,8 @@ struct nfs_client {
u32 cl_minorversion;/* NFSv4 minorversion */ u32 cl_minorversion;/* NFSv4 minorversion */
unsigned int cl_nconnect; /* Number of connections */ unsigned int cl_nconnect; /* Number of connections */
unsigned int cl_max_connect; /* max number of xprts allowed */ unsigned int cl_max_connect; /* max number of xprts allowed */
const char * cl_principal; /* used for machine cred */ const char * cl_principal; /* used for machine cred */
struct xprtsec_parms cl_xprtsec; /* xprt security policy */
#if IS_ENABLED(CONFIG_NFS_V4) #if IS_ENABLED(CONFIG_NFS_V4)
struct list_head cl_ds_clients; /* auth flavor data servers */ struct list_head cl_ds_clients; /* auth flavor data servers */
...@@ -153,6 +154,7 @@ struct nfs_server { ...@@ -153,6 +154,7 @@ struct nfs_server {
#define NFS_MOUNT_WRITE_EAGER 0x01000000 #define NFS_MOUNT_WRITE_EAGER 0x01000000
#define NFS_MOUNT_WRITE_WAIT 0x02000000 #define NFS_MOUNT_WRITE_WAIT 0x02000000
#define NFS_MOUNT_TRUNK_DISCOVERY 0x04000000 #define NFS_MOUNT_TRUNK_DISCOVERY 0x04000000
#define NFS_MOUNT_SHUTDOWN 0x08000000
unsigned int fattr_valid; /* Valid attributes */ unsigned int fattr_valid; /* Valid attributes */
unsigned int caps; /* server capabilities */ unsigned int caps; /* server capabilities */
...@@ -183,6 +185,7 @@ struct nfs_server { ...@@ -183,6 +185,7 @@ struct nfs_server {
change_attr_type;/* Description of change attribute */ change_attr_type;/* Description of change attribute */
struct nfs_fsid fsid; struct nfs_fsid fsid;
int s_sysfs_id; /* sysfs dentry index */
__u64 maxfilesize; /* maximum file size */ __u64 maxfilesize; /* maximum file size */
struct timespec64 time_delta; /* smallest time granularity */ struct timespec64 time_delta; /* smallest time granularity */
unsigned long mount_time; /* when this fs was mounted */ unsigned long mount_time; /* when this fs was mounted */
...@@ -259,6 +262,7 @@ struct nfs_server { ...@@ -259,6 +262,7 @@ struct nfs_server {
/* User namespace info */ /* User namespace info */
const struct cred *cred; const struct cred *cred;
bool has_sec_mnt_opts; bool has_sec_mnt_opts;
struct kobject kobj;
}; };
/* Server capabilities */ /* Server capabilities */
......
...@@ -1528,6 +1528,7 @@ struct nfs42_seek_res { ...@@ -1528,6 +1528,7 @@ struct nfs42_seek_res {
struct nfs42_setxattrargs { struct nfs42_setxattrargs {
struct nfs4_sequence_args seq_args; struct nfs4_sequence_args seq_args;
struct nfs_fh *fh; struct nfs_fh *fh;
const u32 *bitmask;
const char *xattr_name; const char *xattr_name;
u32 xattr_flags; u32 xattr_flags;
size_t xattr_len; size_t xattr_len;
...@@ -1537,6 +1538,8 @@ struct nfs42_setxattrargs { ...@@ -1537,6 +1538,8 @@ struct nfs42_setxattrargs {
struct nfs42_setxattrres { struct nfs42_setxattrres {
struct nfs4_sequence_res seq_res; struct nfs4_sequence_res seq_res;
struct nfs4_change_info cinfo; struct nfs4_change_info cinfo;
struct nfs_fattr *fattr;
const struct nfs_server *server;
}; };
struct nfs42_getxattrargs { struct nfs42_getxattrargs {
......
...@@ -120,6 +120,7 @@ struct rpc_authops { ...@@ -120,6 +120,7 @@ struct rpc_authops {
struct rpcsec_gss_info *); struct rpcsec_gss_info *);
int (*key_timeout)(struct rpc_auth *, int (*key_timeout)(struct rpc_auth *,
struct rpc_cred *); struct rpc_cred *);
int (*ping)(struct rpc_clnt *clnt);
}; };
struct rpc_credops { struct rpc_credops {
...@@ -144,6 +145,7 @@ struct rpc_credops { ...@@ -144,6 +145,7 @@ struct rpc_credops {
extern const struct rpc_authops authunix_ops; extern const struct rpc_authops authunix_ops;
extern const struct rpc_authops authnull_ops; extern const struct rpc_authops authnull_ops;
extern const struct rpc_authops authtls_ops;
int __init rpc_init_authunix(void); int __init rpc_init_authunix(void);
int __init rpcauth_init_module(void); int __init rpcauth_init_module(void);
......
...@@ -30,7 +30,13 @@ ...@@ -30,7 +30,13 @@
#include <linux/sunrpc/xprtmultipath.h> #include <linux/sunrpc/xprtmultipath.h>
struct rpc_inode; struct rpc_inode;
struct rpc_sysfs_client; struct rpc_sysfs_client {
struct kobject kobject;
struct net *net;
struct rpc_clnt *clnt;
struct rpc_xprt_switch *xprt_switch;
};
/* /*
* The high-level client handle * The high-level client handle
...@@ -57,7 +63,9 @@ struct rpc_clnt { ...@@ -57,7 +63,9 @@ struct rpc_clnt {
cl_discrtry : 1,/* disconnect before retry */ cl_discrtry : 1,/* disconnect before retry */
cl_noretranstimeo: 1,/* No retransmit timeouts */ cl_noretranstimeo: 1,/* No retransmit timeouts */
cl_autobind : 1,/* use getport() */ cl_autobind : 1,/* use getport() */
cl_chatty : 1;/* be verbose */ cl_chatty : 1,/* be verbose */
cl_shutdown : 1;/* rpc immediate -EIO */
struct xprtsec_parms cl_xprtsec; /* transport security policy */
struct rpc_rtt * cl_rtt; /* RTO estimator data */ struct rpc_rtt * cl_rtt; /* RTO estimator data */
const struct rpc_timeout *cl_timeout; /* Timeout strategy */ const struct rpc_timeout *cl_timeout; /* Timeout strategy */
...@@ -139,6 +147,7 @@ struct rpc_create_args { ...@@ -139,6 +147,7 @@ struct rpc_create_args {
struct svc_xprt *bc_xprt; /* NFSv4.1 backchannel */ struct svc_xprt *bc_xprt; /* NFSv4.1 backchannel */
const struct cred *cred; const struct cred *cred;
unsigned int max_connect; unsigned int max_connect;
struct xprtsec_parms xprtsec;
}; };
struct rpc_add_xprt_test { struct rpc_add_xprt_test {
......
...@@ -129,6 +129,21 @@ struct rpc_rqst { ...@@ -129,6 +129,21 @@ struct rpc_rqst {
#define rq_svec rq_snd_buf.head #define rq_svec rq_snd_buf.head
#define rq_slen rq_snd_buf.len #define rq_slen rq_snd_buf.len
/* RPC transport layer security policies */
enum xprtsec_policies {
RPC_XPRTSEC_NONE = 0,
RPC_XPRTSEC_TLS_ANON,
RPC_XPRTSEC_TLS_X509,
};
struct xprtsec_parms {
enum xprtsec_policies policy;
/* authentication material */
key_serial_t cert_serial;
key_serial_t privkey_serial;
};
struct rpc_xprt_ops { struct rpc_xprt_ops {
void (*set_buffer_size)(struct rpc_xprt *xprt, size_t sndsize, size_t rcvsize); void (*set_buffer_size)(struct rpc_xprt *xprt, size_t sndsize, size_t rcvsize);
int (*reserve_xprt)(struct rpc_xprt *xprt, struct rpc_task *task); int (*reserve_xprt)(struct rpc_xprt *xprt, struct rpc_task *task);
...@@ -185,6 +200,7 @@ enum xprt_transports { ...@@ -185,6 +200,7 @@ enum xprt_transports {
XPRT_TRANSPORT_RDMA = 256, XPRT_TRANSPORT_RDMA = 256,
XPRT_TRANSPORT_BC_RDMA = XPRT_TRANSPORT_RDMA | XPRT_TRANSPORT_BC, XPRT_TRANSPORT_BC_RDMA = XPRT_TRANSPORT_RDMA | XPRT_TRANSPORT_BC,
XPRT_TRANSPORT_LOCAL = 257, XPRT_TRANSPORT_LOCAL = 257,
XPRT_TRANSPORT_TCP_TLS = 258,
}; };
struct rpc_sysfs_xprt; struct rpc_sysfs_xprt;
...@@ -229,6 +245,7 @@ struct rpc_xprt { ...@@ -229,6 +245,7 @@ struct rpc_xprt {
*/ */
unsigned long bind_timeout, unsigned long bind_timeout,
reestablish_timeout; reestablish_timeout;
struct xprtsec_parms xprtsec;
unsigned int connect_cookie; /* A cookie that gets bumped unsigned int connect_cookie; /* A cookie that gets bumped
every time the transport every time the transport
is reconnected */ is reconnected */
...@@ -333,6 +350,7 @@ struct xprt_create { ...@@ -333,6 +350,7 @@ struct xprt_create {
struct svc_xprt *bc_xprt; /* NFSv4.1 backchannel */ struct svc_xprt *bc_xprt; /* NFSv4.1 backchannel */
struct rpc_xprt_switch *bc_xps; struct rpc_xprt_switch *bc_xps;
unsigned int flags; unsigned int flags;
struct xprtsec_parms xprtsec;
}; };
struct xprt_class { struct xprt_class {
......
...@@ -57,9 +57,11 @@ struct sock_xprt { ...@@ -57,9 +57,11 @@ struct sock_xprt {
struct work_struct error_worker; struct work_struct error_worker;
struct work_struct recv_worker; struct work_struct recv_worker;
struct mutex recv_mutex; struct mutex recv_mutex;
struct completion handshake_done;
struct sockaddr_storage srcaddr; struct sockaddr_storage srcaddr;
unsigned short srcport; unsigned short srcport;
int xprt_err; int xprt_err;
struct rpc_clnt *clnt;
/* /*
* UDP socket buffer size parameters * UDP socket buffer size parameters
...@@ -90,5 +92,6 @@ struct sock_xprt { ...@@ -90,5 +92,6 @@ struct sock_xprt {
#define XPRT_SOCK_WAKE_DISCONNECT (7) #define XPRT_SOCK_WAKE_DISCONNECT (7)
#define XPRT_SOCK_CONNECT_SENT (8) #define XPRT_SOCK_CONNECT_SENT (8)
#define XPRT_SOCK_NOSPACE (9) #define XPRT_SOCK_NOSPACE (9)
#define XPRT_SOCK_IGNORE_RECV (10)
#endif /* _LINUX_SUNRPC_XPRTSOCK_H */ #endif /* _LINUX_SUNRPC_XPRTSOCK_H */
...@@ -139,36 +139,68 @@ DEFINE_RPC_CLNT_EVENT(release); ...@@ -139,36 +139,68 @@ DEFINE_RPC_CLNT_EVENT(release);
DEFINE_RPC_CLNT_EVENT(replace_xprt); DEFINE_RPC_CLNT_EVENT(replace_xprt);
DEFINE_RPC_CLNT_EVENT(replace_xprt_err); DEFINE_RPC_CLNT_EVENT(replace_xprt_err);
TRACE_DEFINE_ENUM(RPC_XPRTSEC_NONE);
TRACE_DEFINE_ENUM(RPC_XPRTSEC_TLS_X509);
#define rpc_show_xprtsec_policy(policy) \
__print_symbolic(policy, \
{ RPC_XPRTSEC_NONE, "none" }, \
{ RPC_XPRTSEC_TLS_ANON, "tls-anon" }, \
{ RPC_XPRTSEC_TLS_X509, "tls-x509" })
#define rpc_show_create_flags(flags) \
__print_flags(flags, "|", \
{ RPC_CLNT_CREATE_HARDRTRY, "HARDRTRY" }, \
{ RPC_CLNT_CREATE_AUTOBIND, "AUTOBIND" }, \
{ RPC_CLNT_CREATE_NONPRIVPORT, "NONPRIVPORT" }, \
{ RPC_CLNT_CREATE_NOPING, "NOPING" }, \
{ RPC_CLNT_CREATE_DISCRTRY, "DISCRTRY" }, \
{ RPC_CLNT_CREATE_QUIET, "QUIET" }, \
{ RPC_CLNT_CREATE_INFINITE_SLOTS, \
"INFINITE_SLOTS" }, \
{ RPC_CLNT_CREATE_NO_IDLE_TIMEOUT, \
"NO_IDLE_TIMEOUT" }, \
{ RPC_CLNT_CREATE_NO_RETRANS_TIMEOUT, \
"NO_RETRANS_TIMEOUT" }, \
{ RPC_CLNT_CREATE_SOFTERR, "SOFTERR" }, \
{ RPC_CLNT_CREATE_REUSEPORT, "REUSEPORT" })
TRACE_EVENT(rpc_clnt_new, TRACE_EVENT(rpc_clnt_new,
TP_PROTO( TP_PROTO(
const struct rpc_clnt *clnt, const struct rpc_clnt *clnt,
const struct rpc_xprt *xprt, const struct rpc_xprt *xprt,
const char *program, const struct rpc_create_args *args
const char *server
), ),
TP_ARGS(clnt, xprt, program, server), TP_ARGS(clnt, xprt, args),
TP_STRUCT__entry( TP_STRUCT__entry(
__field(unsigned int, client_id) __field(unsigned int, client_id)
__field(unsigned long, xprtsec)
__field(unsigned long, flags)
__string(program, clnt->cl_program->name)
__string(server, xprt->servername)
__string(addr, xprt->address_strings[RPC_DISPLAY_ADDR]) __string(addr, xprt->address_strings[RPC_DISPLAY_ADDR])
__string(port, xprt->address_strings[RPC_DISPLAY_PORT]) __string(port, xprt->address_strings[RPC_DISPLAY_PORT])
__string(program, program)
__string(server, server)
), ),
TP_fast_assign( TP_fast_assign(
__entry->client_id = clnt->cl_clid; __entry->client_id = clnt->cl_clid;
__entry->xprtsec = args->xprtsec.policy;
__entry->flags = args->flags;
__assign_str(program, clnt->cl_program->name);
__assign_str(server, xprt->servername);
__assign_str(addr, xprt->address_strings[RPC_DISPLAY_ADDR]); __assign_str(addr, xprt->address_strings[RPC_DISPLAY_ADDR]);
__assign_str(port, xprt->address_strings[RPC_DISPLAY_PORT]); __assign_str(port, xprt->address_strings[RPC_DISPLAY_PORT]);
__assign_str(program, program);
__assign_str(server, server);
), ),
TP_printk("client=" SUNRPC_TRACE_CLID_SPECIFIER TP_printk("client=" SUNRPC_TRACE_CLID_SPECIFIER " peer=[%s]:%s"
" peer=[%s]:%s program=%s server=%s", " program=%s server=%s xprtsec=%s flags=%s",
__entry->client_id, __get_str(addr), __get_str(port), __entry->client_id, __get_str(addr), __get_str(port),
__get_str(program), __get_str(server)) __get_str(program), __get_str(server),
rpc_show_xprtsec_policy(__entry->xprtsec),
rpc_show_create_flags(__entry->flags)
)
); );
TRACE_EVENT(rpc_clnt_new_err, TRACE_EVENT(rpc_clnt_new_err,
...@@ -1493,6 +1525,50 @@ TRACE_EVENT(rpcb_unregister, ...@@ -1493,6 +1525,50 @@ TRACE_EVENT(rpcb_unregister,
) )
); );
/**
** RPC-over-TLS tracepoints
**/
DECLARE_EVENT_CLASS(rpc_tls_class,
TP_PROTO(
const struct rpc_clnt *clnt,
const struct rpc_xprt *xprt
),
TP_ARGS(clnt, xprt),
TP_STRUCT__entry(
__field(unsigned long, requested_policy)
__field(u32, version)
__string(servername, xprt->servername)
__string(progname, clnt->cl_program->name)
),
TP_fast_assign(
__entry->requested_policy = clnt->cl_xprtsec.policy;
__entry->version = clnt->cl_vers;
__assign_str(servername, xprt->servername);
__assign_str(progname, clnt->cl_program->name)
),
TP_printk("server=%s %sv%u requested_policy=%s",
__get_str(servername), __get_str(progname), __entry->version,
rpc_show_xprtsec_policy(__entry->requested_policy)
)
);
#define DEFINE_RPC_TLS_EVENT(name) \
DEFINE_EVENT(rpc_tls_class, rpc_tls_##name, \
TP_PROTO( \
const struct rpc_clnt *clnt, \
const struct rpc_xprt *xprt \
), \
TP_ARGS(clnt, xprt))
DEFINE_RPC_TLS_EVENT(unavailable);
DEFINE_RPC_TLS_EVENT(not_started);
/* Record an xdr_buf containing a fully-formed RPC message */ /* Record an xdr_buf containing a fully-formed RPC message */
DECLARE_EVENT_CLASS(svc_xdr_msg_class, DECLARE_EVENT_CLASS(svc_xdr_msg_class,
TP_PROTO( TP_PROTO(
......
...@@ -9,7 +9,7 @@ obj-$(CONFIG_SUNRPC_GSS) += auth_gss/ ...@@ -9,7 +9,7 @@ obj-$(CONFIG_SUNRPC_GSS) += auth_gss/
obj-$(CONFIG_SUNRPC_XPRT_RDMA) += xprtrdma/ obj-$(CONFIG_SUNRPC_XPRT_RDMA) += xprtrdma/
sunrpc-y := clnt.o xprt.o socklib.o xprtsock.o sched.o \ sunrpc-y := clnt.o xprt.o socklib.o xprtsock.o sched.o \
auth.o auth_null.o auth_unix.o \ auth.o auth_null.o auth_tls.o auth_unix.o \
svc.o svcsock.o svcauth.o svcauth_unix.o \ svc.o svcsock.o svcauth.o svcauth_unix.o \
addr.o rpcb_clnt.o timer.o xdr.o \ addr.o rpcb_clnt.o timer.o xdr.o \
sunrpc_syms.o cache.o rpc_pipe.o sysfs.o \ sunrpc_syms.o cache.o rpc_pipe.o sysfs.o \
......
...@@ -32,7 +32,7 @@ static unsigned int auth_hashbits = RPC_CREDCACHE_DEFAULT_HASHBITS; ...@@ -32,7 +32,7 @@ static unsigned int auth_hashbits = RPC_CREDCACHE_DEFAULT_HASHBITS;
static const struct rpc_authops __rcu *auth_flavors[RPC_AUTH_MAXFLAVOR] = { static const struct rpc_authops __rcu *auth_flavors[RPC_AUTH_MAXFLAVOR] = {
[RPC_AUTH_NULL] = (const struct rpc_authops __force __rcu *)&authnull_ops, [RPC_AUTH_NULL] = (const struct rpc_authops __force __rcu *)&authnull_ops,
[RPC_AUTH_UNIX] = (const struct rpc_authops __force __rcu *)&authunix_ops, [RPC_AUTH_UNIX] = (const struct rpc_authops __force __rcu *)&authunix_ops,
NULL, /* others can be loadable modules */ [RPC_AUTH_TLS] = (const struct rpc_authops __force __rcu *)&authtls_ops,
}; };
static LIST_HEAD(cred_unused); static LIST_HEAD(cred_unused);
......
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2021, 2022 Oracle. All rights reserved.
*
* The AUTH_TLS credential is used only to probe a remote peer
* for RPC-over-TLS support.
*/
#include <linux/types.h>
#include <linux/module.h>
#include <linux/sunrpc/clnt.h>
static const char *starttls_token = "STARTTLS";
static const size_t starttls_len = 8;
static struct rpc_auth tls_auth;
static struct rpc_cred tls_cred;
static void tls_encode_probe(struct rpc_rqst *rqstp, struct xdr_stream *xdr,
const void *obj)
{
}
static int tls_decode_probe(struct rpc_rqst *rqstp, struct xdr_stream *xdr,
void *obj)
{
return 0;
}
static const struct rpc_procinfo rpcproc_tls_probe = {
.p_encode = tls_encode_probe,
.p_decode = tls_decode_probe,
};
static void rpc_tls_probe_call_prepare(struct rpc_task *task, void *data)
{
task->tk_flags &= ~RPC_TASK_NO_RETRANS_TIMEOUT;
rpc_call_start(task);
}
static void rpc_tls_probe_call_done(struct rpc_task *task, void *data)
{
}
static const struct rpc_call_ops rpc_tls_probe_ops = {
.rpc_call_prepare = rpc_tls_probe_call_prepare,
.rpc_call_done = rpc_tls_probe_call_done,
};
static int tls_probe(struct rpc_clnt *clnt)
{
struct rpc_message msg = {
.rpc_proc = &rpcproc_tls_probe,
};
struct rpc_task_setup task_setup_data = {
.rpc_client = clnt,
.rpc_message = &msg,
.rpc_op_cred = &tls_cred,
.callback_ops = &rpc_tls_probe_ops,
.flags = RPC_TASK_SOFT | RPC_TASK_SOFTCONN,
};
struct rpc_task *task;
int status;
task = rpc_run_task(&task_setup_data);
if (IS_ERR(task))
return PTR_ERR(task);
status = task->tk_status;
rpc_put_task(task);
return status;
}
static struct rpc_auth *tls_create(const struct rpc_auth_create_args *args,
struct rpc_clnt *clnt)
{
refcount_inc(&tls_auth.au_count);
return &tls_auth;
}
static void tls_destroy(struct rpc_auth *auth)
{
}
static struct rpc_cred *tls_lookup_cred(struct rpc_auth *auth,
struct auth_cred *acred, int flags)
{
return get_rpccred(&tls_cred);
}
static void tls_destroy_cred(struct rpc_cred *cred)
{
}
static int tls_match(struct auth_cred *acred, struct rpc_cred *cred, int taskflags)
{
return 1;
}
static int tls_marshal(struct rpc_task *task, struct xdr_stream *xdr)
{
__be32 *p;
p = xdr_reserve_space(xdr, 4 * XDR_UNIT);
if (!p)
return -EMSGSIZE;
/* Credential */
*p++ = rpc_auth_tls;
*p++ = xdr_zero;
/* Verifier */
*p++ = rpc_auth_null;
*p = xdr_zero;
return 0;
}
static int tls_refresh(struct rpc_task *task)
{
set_bit(RPCAUTH_CRED_UPTODATE, &task->tk_rqstp->rq_cred->cr_flags);
return 0;
}
static int tls_validate(struct rpc_task *task, struct xdr_stream *xdr)
{
__be32 *p;
void *str;
p = xdr_inline_decode(xdr, XDR_UNIT);
if (!p)
return -EIO;
if (*p != rpc_auth_null)
return -EIO;
if (xdr_stream_decode_opaque_inline(xdr, &str, starttls_len) != starttls_len)
return -EIO;
if (memcmp(str, starttls_token, starttls_len))
return -EIO;
return 0;
}
const struct rpc_authops authtls_ops = {
.owner = THIS_MODULE,
.au_flavor = RPC_AUTH_TLS,
.au_name = "NULL",
.create = tls_create,
.destroy = tls_destroy,
.lookup_cred = tls_lookup_cred,
.ping = tls_probe,
};
static struct rpc_auth tls_auth = {
.au_cslack = NUL_CALLSLACK,
.au_rslack = NUL_REPLYSLACK,
.au_verfsize = NUL_REPLYSLACK,
.au_ralign = NUL_REPLYSLACK,
.au_ops = &authtls_ops,
.au_flavor = RPC_AUTH_TLS,
.au_count = REFCOUNT_INIT(1),
};
static const struct rpc_credops tls_credops = {
.cr_name = "AUTH_TLS",
.crdestroy = tls_destroy_cred,
.crmatch = tls_match,
.crmarshal = tls_marshal,
.crwrap_req = rpcauth_wrap_req_encode,
.crrefresh = tls_refresh,
.crvalidate = tls_validate,
.crunwrap_resp = rpcauth_unwrap_resp_decode,
};
static struct rpc_cred tls_cred = {
.cr_lru = LIST_HEAD_INIT(tls_cred.cr_lru),
.cr_auth = &tls_auth,
.cr_ops = &tls_credops,
.cr_count = REFCOUNT_INIT(2),
.cr_flags = 1UL << RPCAUTH_CRED_UPTODATE,
};
...@@ -385,6 +385,7 @@ static struct rpc_clnt * rpc_new_client(const struct rpc_create_args *args, ...@@ -385,6 +385,7 @@ static struct rpc_clnt * rpc_new_client(const struct rpc_create_args *args,
if (!clnt) if (!clnt)
goto out_err; goto out_err;
clnt->cl_parent = parent ? : clnt; clnt->cl_parent = parent ? : clnt;
clnt->cl_xprtsec = args->xprtsec;
err = rpc_alloc_clid(clnt); err = rpc_alloc_clid(clnt);
if (err) if (err)
...@@ -434,7 +435,7 @@ static struct rpc_clnt * rpc_new_client(const struct rpc_create_args *args, ...@@ -434,7 +435,7 @@ static struct rpc_clnt * rpc_new_client(const struct rpc_create_args *args,
if (parent) if (parent)
refcount_inc(&parent->cl_count); refcount_inc(&parent->cl_count);
trace_rpc_clnt_new(clnt, xprt, program->name, args->servername); trace_rpc_clnt_new(clnt, xprt, args);
return clnt; return clnt;
out_no_path: out_no_path:
...@@ -532,6 +533,7 @@ struct rpc_clnt *rpc_create(struct rpc_create_args *args) ...@@ -532,6 +533,7 @@ struct rpc_clnt *rpc_create(struct rpc_create_args *args)
.addrlen = args->addrsize, .addrlen = args->addrsize,
.servername = args->servername, .servername = args->servername,
.bc_xprt = args->bc_xprt, .bc_xprt = args->bc_xprt,
.xprtsec = args->xprtsec,
}; };
char servername[48]; char servername[48];
struct rpc_clnt *clnt; struct rpc_clnt *clnt;
...@@ -565,8 +567,12 @@ struct rpc_clnt *rpc_create(struct rpc_create_args *args) ...@@ -565,8 +567,12 @@ struct rpc_clnt *rpc_create(struct rpc_create_args *args)
servername[0] = '\0'; servername[0] = '\0';
switch (args->address->sa_family) { switch (args->address->sa_family) {
case AF_LOCAL: case AF_LOCAL:
snprintf(servername, sizeof(servername), "%s", if (sun->sun_path[0])
sun->sun_path); snprintf(servername, sizeof(servername), "%s",
sun->sun_path);
else
snprintf(servername, sizeof(servername), "@%s",
sun->sun_path+1);
break; break;
case AF_INET: case AF_INET:
snprintf(servername, sizeof(servername), "%pI4", snprintf(servername, sizeof(servername), "%pI4",
...@@ -727,6 +733,7 @@ int rpc_switch_client_transport(struct rpc_clnt *clnt, ...@@ -727,6 +733,7 @@ int rpc_switch_client_transport(struct rpc_clnt *clnt,
struct rpc_clnt *parent; struct rpc_clnt *parent;
int err; int err;
args->xprtsec = clnt->cl_xprtsec;
xprt = xprt_create_transport(args); xprt = xprt_create_transport(args);
if (IS_ERR(xprt)) if (IS_ERR(xprt))
return PTR_ERR(xprt); return PTR_ERR(xprt);
...@@ -1717,6 +1724,11 @@ call_start(struct rpc_task *task) ...@@ -1717,6 +1724,11 @@ call_start(struct rpc_task *task)
trace_rpc_request(task); trace_rpc_request(task);
if (task->tk_client->cl_shutdown) {
rpc_call_rpcerror(task, -EIO);
return;
}
/* Increment call count (version might not be valid for ping) */ /* Increment call count (version might not be valid for ping) */
if (clnt->cl_program->version[clnt->cl_vers]) if (clnt->cl_program->version[clnt->cl_vers])
clnt->cl_program->version[clnt->cl_vers]->counts[idx]++; clnt->cl_program->version[clnt->cl_vers]->counts[idx]++;
...@@ -2826,6 +2838,9 @@ static int rpc_ping(struct rpc_clnt *clnt) ...@@ -2826,6 +2838,9 @@ static int rpc_ping(struct rpc_clnt *clnt)
struct rpc_task *task; struct rpc_task *task;
int status; int status;
if (clnt->cl_auth->au_ops->ping)
return clnt->cl_auth->au_ops->ping(clnt);
task = rpc_call_null_helper(clnt, NULL, NULL, 0, NULL, NULL); task = rpc_call_null_helper(clnt, NULL, NULL, 0, NULL, NULL);
if (IS_ERR(task)) if (IS_ERR(task))
return PTR_ERR(task); return PTR_ERR(task);
...@@ -3046,6 +3061,7 @@ int rpc_clnt_add_xprt(struct rpc_clnt *clnt, ...@@ -3046,6 +3061,7 @@ int rpc_clnt_add_xprt(struct rpc_clnt *clnt,
if (!xprtargs->ident) if (!xprtargs->ident)
xprtargs->ident = ident; xprtargs->ident = ident;
xprtargs->xprtsec = clnt->cl_xprtsec;
xprt = xprt_create_transport(xprtargs); xprt = xprt_create_transport(xprtargs);
if (IS_ERR(xprt)) { if (IS_ERR(xprt)) {
ret = PTR_ERR(xprt); ret = PTR_ERR(xprt);
......
...@@ -36,6 +36,7 @@ ...@@ -36,6 +36,7 @@
#include "netns.h" #include "netns.h"
#define RPCBIND_SOCK_PATHNAME "/var/run/rpcbind.sock" #define RPCBIND_SOCK_PATHNAME "/var/run/rpcbind.sock"
#define RPCBIND_SOCK_ABSTRACT_NAME "\0/run/rpcbind.sock"
#define RPCBIND_PROGRAM (100000u) #define RPCBIND_PROGRAM (100000u)
#define RPCBIND_PORT (111u) #define RPCBIND_PORT (111u)
...@@ -216,21 +217,22 @@ static void rpcb_set_local(struct net *net, struct rpc_clnt *clnt, ...@@ -216,21 +217,22 @@ static void rpcb_set_local(struct net *net, struct rpc_clnt *clnt,
sn->rpcb_users = 1; sn->rpcb_users = 1;
} }
/* Evaluate to actual length of the `sockaddr_un' structure. */
# define SUN_LEN(ptr) (offsetof(struct sockaddr_un, sun_path) \
+ 1 + strlen((ptr)->sun_path + 1))
/* /*
* Returns zero on success, otherwise a negative errno value * Returns zero on success, otherwise a negative errno value
* is returned. * is returned.
*/ */
static int rpcb_create_local_unix(struct net *net) static int rpcb_create_af_local(struct net *net,
const struct sockaddr_un *addr)
{ {
static const struct sockaddr_un rpcb_localaddr_rpcbind = {
.sun_family = AF_LOCAL,
.sun_path = RPCBIND_SOCK_PATHNAME,
};
struct rpc_create_args args = { struct rpc_create_args args = {
.net = net, .net = net,
.protocol = XPRT_TRANSPORT_LOCAL, .protocol = XPRT_TRANSPORT_LOCAL,
.address = (struct sockaddr *)&rpcb_localaddr_rpcbind, .address = (struct sockaddr *)addr,
.addrsize = sizeof(rpcb_localaddr_rpcbind), .addrsize = SUN_LEN(addr),
.servername = "localhost", .servername = "localhost",
.program = &rpcb_program, .program = &rpcb_program,
.version = RPCBVERS_2, .version = RPCBVERS_2,
...@@ -269,6 +271,26 @@ static int rpcb_create_local_unix(struct net *net) ...@@ -269,6 +271,26 @@ static int rpcb_create_local_unix(struct net *net)
return result; return result;
} }
static int rpcb_create_local_abstract(struct net *net)
{
static const struct sockaddr_un rpcb_localaddr_abstract = {
.sun_family = AF_LOCAL,
.sun_path = RPCBIND_SOCK_ABSTRACT_NAME,
};
return rpcb_create_af_local(net, &rpcb_localaddr_abstract);
}
static int rpcb_create_local_unix(struct net *net)
{
static const struct sockaddr_un rpcb_localaddr_unix = {
.sun_family = AF_LOCAL,
.sun_path = RPCBIND_SOCK_PATHNAME,
};
return rpcb_create_af_local(net, &rpcb_localaddr_unix);
}
/* /*
* Returns zero on success, otherwise a negative errno value * Returns zero on success, otherwise a negative errno value
* is returned. * is returned.
...@@ -332,7 +354,8 @@ int rpcb_create_local(struct net *net) ...@@ -332,7 +354,8 @@ int rpcb_create_local(struct net *net)
if (rpcb_get_local(net)) if (rpcb_get_local(net))
goto out; goto out;
if (rpcb_create_local_unix(net) != 0) if (rpcb_create_local_abstract(net) != 0 &&
rpcb_create_local_unix(net) != 0)
result = rpcb_create_local_net(net); result = rpcb_create_local_net(net);
out: out:
......
...@@ -239,6 +239,7 @@ static ssize_t rpc_sysfs_xprt_dstaddr_store(struct kobject *kobj, ...@@ -239,6 +239,7 @@ static ssize_t rpc_sysfs_xprt_dstaddr_store(struct kobject *kobj,
if (!xprt) if (!xprt)
return 0; return 0;
if (!(xprt->xprt_class->ident == XPRT_TRANSPORT_TCP || if (!(xprt->xprt_class->ident == XPRT_TRANSPORT_TCP ||
xprt->xprt_class->ident == XPRT_TRANSPORT_TCP_TLS ||
xprt->xprt_class->ident == XPRT_TRANSPORT_RDMA)) { xprt->xprt_class->ident == XPRT_TRANSPORT_RDMA)) {
xprt_put(xprt); xprt_put(xprt);
return -EOPNOTSUPP; return -EOPNOTSUPP;
......
...@@ -5,13 +5,6 @@ ...@@ -5,13 +5,6 @@
#ifndef __SUNRPC_SYSFS_H #ifndef __SUNRPC_SYSFS_H
#define __SUNRPC_SYSFS_H #define __SUNRPC_SYSFS_H
struct rpc_sysfs_client {
struct kobject kobject;
struct net *net;
struct rpc_clnt *clnt;
struct rpc_xprt_switch *xprt_switch;
};
struct rpc_sysfs_xprt_switch { struct rpc_sysfs_xprt_switch {
struct kobject kobject; struct kobject kobject;
struct net *net; struct net *net;
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment