mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 10:40:15 +02:00 
			
		
		
		
	NFSD: add listener-{set,get} netlink command
Introduce write_ports netlink command. For listener-set, userspace is expected to provide a NFS listeners list it wants enabled. All other sockets will be closed. Reviewed-by: Jeff Layton <jlayton@kernel.org> Co-developed-by: Jeff Layton <jlayton@kernel.org> Signed-off-by: Jeff Layton <jlayton@kernel.org> Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org> Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
This commit is contained in:
		
							parent
							
								
									cf619507ae
								
							
						
					
					
						commit
						16a4711774
					
				
					 5 changed files with 296 additions and 0 deletions
				
			
		| 
						 | 
				
			
			@ -98,6 +98,23 @@ attribute-sets:
 | 
			
		|||
        type: nest
 | 
			
		||||
        nested-attributes: version
 | 
			
		||||
        multi-attr: true
 | 
			
		||||
  -
 | 
			
		||||
    name: sock
 | 
			
		||||
    attributes:
 | 
			
		||||
      -
 | 
			
		||||
        name: addr
 | 
			
		||||
        type: binary
 | 
			
		||||
      -
 | 
			
		||||
        name: transport-name
 | 
			
		||||
        type: string
 | 
			
		||||
  -
 | 
			
		||||
    name: server-sock
 | 
			
		||||
    attributes:
 | 
			
		||||
      -
 | 
			
		||||
        name: addr
 | 
			
		||||
        type: nest
 | 
			
		||||
        nested-attributes: sock
 | 
			
		||||
        multi-attr: true
 | 
			
		||||
 | 
			
		||||
operations:
 | 
			
		||||
  list:
 | 
			
		||||
| 
						 | 
				
			
			@ -163,3 +180,20 @@ operations:
 | 
			
		|||
        reply:
 | 
			
		||||
          attributes:
 | 
			
		||||
            - version
 | 
			
		||||
    -
 | 
			
		||||
      name: listener-set
 | 
			
		||||
      doc: set nfs running sockets
 | 
			
		||||
      attribute-set: server-sock
 | 
			
		||||
      flags: [ admin-perm ]
 | 
			
		||||
      do:
 | 
			
		||||
        request:
 | 
			
		||||
          attributes:
 | 
			
		||||
            - addr
 | 
			
		||||
    -
 | 
			
		||||
      name: listener-get
 | 
			
		||||
      doc: get nfs running listeners
 | 
			
		||||
      attribute-set: server-sock
 | 
			
		||||
      do:
 | 
			
		||||
        reply:
 | 
			
		||||
          attributes:
 | 
			
		||||
            - addr
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,6 +11,11 @@
 | 
			
		|||
#include <uapi/linux/nfsd_netlink.h>
 | 
			
		||||
 | 
			
		||||
/* Common nested types */
 | 
			
		||||
const struct nla_policy nfsd_sock_nl_policy[NFSD_A_SOCK_TRANSPORT_NAME + 1] = {
 | 
			
		||||
	[NFSD_A_SOCK_ADDR] = { .type = NLA_BINARY, },
 | 
			
		||||
	[NFSD_A_SOCK_TRANSPORT_NAME] = { .type = NLA_NUL_STRING, },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const struct nla_policy nfsd_version_nl_policy[NFSD_A_VERSION_ENABLED + 1] = {
 | 
			
		||||
	[NFSD_A_VERSION_MAJOR] = { .type = NLA_U32, },
 | 
			
		||||
	[NFSD_A_VERSION_MINOR] = { .type = NLA_U32, },
 | 
			
		||||
| 
						 | 
				
			
			@ -30,6 +35,11 @@ static const struct nla_policy nfsd_version_set_nl_policy[NFSD_A_SERVER_PROTO_VE
 | 
			
		|||
	[NFSD_A_SERVER_PROTO_VERSION] = NLA_POLICY_NESTED(nfsd_version_nl_policy),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* NFSD_CMD_LISTENER_SET - do */
 | 
			
		||||
static const struct nla_policy nfsd_listener_set_nl_policy[NFSD_A_SERVER_SOCK_ADDR + 1] = {
 | 
			
		||||
	[NFSD_A_SERVER_SOCK_ADDR] = NLA_POLICY_NESTED(nfsd_sock_nl_policy),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* Ops table for nfsd */
 | 
			
		||||
static const struct genl_split_ops nfsd_nl_ops[] = {
 | 
			
		||||
	{
 | 
			
		||||
| 
						 | 
				
			
			@ -63,6 +73,18 @@ static const struct genl_split_ops nfsd_nl_ops[] = {
 | 
			
		|||
		.doit	= nfsd_nl_version_get_doit,
 | 
			
		||||
		.flags	= GENL_CMD_CAP_DO,
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		.cmd		= NFSD_CMD_LISTENER_SET,
 | 
			
		||||
		.doit		= nfsd_nl_listener_set_doit,
 | 
			
		||||
		.policy		= nfsd_listener_set_nl_policy,
 | 
			
		||||
		.maxattr	= NFSD_A_SERVER_SOCK_ADDR,
 | 
			
		||||
		.flags		= GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		.cmd	= NFSD_CMD_LISTENER_GET,
 | 
			
		||||
		.doit	= nfsd_nl_listener_get_doit,
 | 
			
		||||
		.flags	= GENL_CMD_CAP_DO,
 | 
			
		||||
	},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct genl_family nfsd_nl_family __ro_after_init = {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,6 +12,7 @@
 | 
			
		|||
#include <uapi/linux/nfsd_netlink.h>
 | 
			
		||||
 | 
			
		||||
/* Common nested types */
 | 
			
		||||
extern const struct nla_policy nfsd_sock_nl_policy[NFSD_A_SOCK_TRANSPORT_NAME + 1];
 | 
			
		||||
extern const struct nla_policy nfsd_version_nl_policy[NFSD_A_VERSION_ENABLED + 1];
 | 
			
		||||
 | 
			
		||||
int nfsd_nl_rpc_status_get_start(struct netlink_callback *cb);
 | 
			
		||||
| 
						 | 
				
			
			@ -23,6 +24,8 @@ int nfsd_nl_threads_set_doit(struct sk_buff *skb, struct genl_info *info);
 | 
			
		|||
int nfsd_nl_threads_get_doit(struct sk_buff *skb, struct genl_info *info);
 | 
			
		||||
int nfsd_nl_version_set_doit(struct sk_buff *skb, struct genl_info *info);
 | 
			
		||||
int nfsd_nl_version_get_doit(struct sk_buff *skb, struct genl_info *info);
 | 
			
		||||
int nfsd_nl_listener_set_doit(struct sk_buff *skb, struct genl_info *info);
 | 
			
		||||
int nfsd_nl_listener_get_doit(struct sk_buff *skb, struct genl_info *info);
 | 
			
		||||
 | 
			
		||||
extern struct genl_family nfsd_nl_family;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										220
									
								
								fs/nfsd/nfsctl.c
									
									
									
									
									
								
							
							
						
						
									
										220
									
								
								fs/nfsd/nfsctl.c
									
									
									
									
									
								
							| 
						 | 
				
			
			@ -1946,6 +1946,226 @@ int nfsd_nl_version_get_doit(struct sk_buff *skb, struct genl_info *info)
 | 
			
		|||
	return err;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * nfsd_nl_listener_set_doit - set the nfs running sockets
 | 
			
		||||
 * @skb: reply buffer
 | 
			
		||||
 * @info: netlink metadata and command arguments
 | 
			
		||||
 *
 | 
			
		||||
 * Return 0 on success or a negative errno.
 | 
			
		||||
 */
 | 
			
		||||
int nfsd_nl_listener_set_doit(struct sk_buff *skb, struct genl_info *info)
 | 
			
		||||
{
 | 
			
		||||
	struct net *net = genl_info_net(info);
 | 
			
		||||
	struct svc_xprt *xprt, *tmp;
 | 
			
		||||
	const struct nlattr *attr;
 | 
			
		||||
	struct svc_serv *serv;
 | 
			
		||||
	LIST_HEAD(permsocks);
 | 
			
		||||
	struct nfsd_net *nn;
 | 
			
		||||
	int err, rem;
 | 
			
		||||
 | 
			
		||||
	mutex_lock(&nfsd_mutex);
 | 
			
		||||
 | 
			
		||||
	err = nfsd_create_serv(net);
 | 
			
		||||
	if (err) {
 | 
			
		||||
		mutex_unlock(&nfsd_mutex);
 | 
			
		||||
		return err;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	nn = net_generic(net, nfsd_net_id);
 | 
			
		||||
	serv = nn->nfsd_serv;
 | 
			
		||||
 | 
			
		||||
	spin_lock_bh(&serv->sv_lock);
 | 
			
		||||
 | 
			
		||||
	/* Move all of the old listener sockets to a temp list */
 | 
			
		||||
	list_splice_init(&serv->sv_permsocks, &permsocks);
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
	 * Walk the list of server_socks from userland and move any that match
 | 
			
		||||
	 * back to sv_permsocks
 | 
			
		||||
	 */
 | 
			
		||||
	nlmsg_for_each_attr(attr, info->nlhdr, GENL_HDRLEN, rem) {
 | 
			
		||||
		struct nlattr *tb[NFSD_A_SOCK_MAX + 1];
 | 
			
		||||
		const char *xcl_name;
 | 
			
		||||
		struct sockaddr *sa;
 | 
			
		||||
 | 
			
		||||
		if (nla_type(attr) != NFSD_A_SERVER_SOCK_ADDR)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		if (nla_parse_nested(tb, NFSD_A_SOCK_MAX, attr,
 | 
			
		||||
				     nfsd_sock_nl_policy, info->extack) < 0)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		if (!tb[NFSD_A_SOCK_ADDR] || !tb[NFSD_A_SOCK_TRANSPORT_NAME])
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		if (nla_len(tb[NFSD_A_SOCK_ADDR]) < sizeof(*sa))
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		xcl_name = nla_data(tb[NFSD_A_SOCK_TRANSPORT_NAME]);
 | 
			
		||||
		sa = nla_data(tb[NFSD_A_SOCK_ADDR]);
 | 
			
		||||
 | 
			
		||||
		/* Put back any matching sockets */
 | 
			
		||||
		list_for_each_entry_safe(xprt, tmp, &permsocks, xpt_list) {
 | 
			
		||||
			/* This shouldn't be possible */
 | 
			
		||||
			if (WARN_ON_ONCE(xprt->xpt_net != net)) {
 | 
			
		||||
				list_move(&xprt->xpt_list, &serv->sv_permsocks);
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			/* If everything matches, put it back */
 | 
			
		||||
			if (!strcmp(xprt->xpt_class->xcl_name, xcl_name) &&
 | 
			
		||||
			    rpc_cmp_addr_port(sa, (struct sockaddr *)&xprt->xpt_local)) {
 | 
			
		||||
				list_move(&xprt->xpt_list, &serv->sv_permsocks);
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* For now, no removing old sockets while server is running */
 | 
			
		||||
	if (serv->sv_nrthreads && !list_empty(&permsocks)) {
 | 
			
		||||
		list_splice_init(&permsocks, &serv->sv_permsocks);
 | 
			
		||||
		spin_unlock_bh(&serv->sv_lock);
 | 
			
		||||
		err = -EBUSY;
 | 
			
		||||
		goto out_unlock_mtx;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Close the remaining sockets on the permsocks list */
 | 
			
		||||
	while (!list_empty(&permsocks)) {
 | 
			
		||||
		xprt = list_first_entry(&permsocks, struct svc_xprt, xpt_list);
 | 
			
		||||
		list_move(&xprt->xpt_list, &serv->sv_permsocks);
 | 
			
		||||
 | 
			
		||||
		/*
 | 
			
		||||
		 * Newly-created sockets are born with the BUSY bit set. Clear
 | 
			
		||||
		 * it if there are no threads, since nothing can pick it up
 | 
			
		||||
		 * in that case.
 | 
			
		||||
		 */
 | 
			
		||||
		if (!serv->sv_nrthreads)
 | 
			
		||||
			clear_bit(XPT_BUSY, &xprt->xpt_flags);
 | 
			
		||||
 | 
			
		||||
		set_bit(XPT_CLOSE, &xprt->xpt_flags);
 | 
			
		||||
		spin_unlock_bh(&serv->sv_lock);
 | 
			
		||||
		svc_xprt_close(xprt);
 | 
			
		||||
		spin_lock_bh(&serv->sv_lock);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	spin_unlock_bh(&serv->sv_lock);
 | 
			
		||||
 | 
			
		||||
	/* walk list of addrs again, open any that still don't exist */
 | 
			
		||||
	nlmsg_for_each_attr(attr, info->nlhdr, GENL_HDRLEN, rem) {
 | 
			
		||||
		struct nlattr *tb[NFSD_A_SOCK_MAX + 1];
 | 
			
		||||
		const char *xcl_name;
 | 
			
		||||
		struct sockaddr *sa;
 | 
			
		||||
		int ret;
 | 
			
		||||
 | 
			
		||||
		if (nla_type(attr) != NFSD_A_SERVER_SOCK_ADDR)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		if (nla_parse_nested(tb, NFSD_A_SOCK_MAX, attr,
 | 
			
		||||
				     nfsd_sock_nl_policy, info->extack) < 0)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		if (!tb[NFSD_A_SOCK_ADDR] || !tb[NFSD_A_SOCK_TRANSPORT_NAME])
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		if (nla_len(tb[NFSD_A_SOCK_ADDR]) < sizeof(*sa))
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		xcl_name = nla_data(tb[NFSD_A_SOCK_TRANSPORT_NAME]);
 | 
			
		||||
		sa = nla_data(tb[NFSD_A_SOCK_ADDR]);
 | 
			
		||||
 | 
			
		||||
		xprt = svc_find_listener(serv, xcl_name, net, sa);
 | 
			
		||||
		if (xprt) {
 | 
			
		||||
			svc_xprt_put(xprt);
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ret = svc_xprt_create_from_sa(serv, xcl_name, net, sa,
 | 
			
		||||
					      SVC_SOCK_ANONYMOUS,
 | 
			
		||||
					      get_current_cred());
 | 
			
		||||
		/* always save the latest error */
 | 
			
		||||
		if (ret < 0)
 | 
			
		||||
			err = ret;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!serv->sv_nrthreads && list_empty(&nn->nfsd_serv->sv_permsocks))
 | 
			
		||||
		nfsd_destroy_serv(net);
 | 
			
		||||
 | 
			
		||||
out_unlock_mtx:
 | 
			
		||||
	mutex_unlock(&nfsd_mutex);
 | 
			
		||||
 | 
			
		||||
	return err;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * nfsd_nl_listener_get_doit - get the nfs running listeners
 | 
			
		||||
 * @skb: reply buffer
 | 
			
		||||
 * @info: netlink metadata and command arguments
 | 
			
		||||
 *
 | 
			
		||||
 * Return 0 on success or a negative errno.
 | 
			
		||||
 */
 | 
			
		||||
int nfsd_nl_listener_get_doit(struct sk_buff *skb, struct genl_info *info)
 | 
			
		||||
{
 | 
			
		||||
	struct svc_xprt *xprt;
 | 
			
		||||
	struct svc_serv *serv;
 | 
			
		||||
	struct nfsd_net *nn;
 | 
			
		||||
	void *hdr;
 | 
			
		||||
	int err;
 | 
			
		||||
 | 
			
		||||
	skb = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL);
 | 
			
		||||
	if (!skb)
 | 
			
		||||
		return -ENOMEM;
 | 
			
		||||
 | 
			
		||||
	hdr = genlmsg_iput(skb, info);
 | 
			
		||||
	if (!hdr) {
 | 
			
		||||
		err = -EMSGSIZE;
 | 
			
		||||
		goto err_free_msg;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	mutex_lock(&nfsd_mutex);
 | 
			
		||||
	nn = net_generic(genl_info_net(info), nfsd_net_id);
 | 
			
		||||
 | 
			
		||||
	/* no nfs server? Just send empty socket list */
 | 
			
		||||
	if (!nn->nfsd_serv)
 | 
			
		||||
		goto out_unlock_mtx;
 | 
			
		||||
 | 
			
		||||
	serv = nn->nfsd_serv;
 | 
			
		||||
	spin_lock_bh(&serv->sv_lock);
 | 
			
		||||
	list_for_each_entry(xprt, &serv->sv_permsocks, xpt_list) {
 | 
			
		||||
		struct nlattr *attr;
 | 
			
		||||
 | 
			
		||||
		attr = nla_nest_start(skb, NFSD_A_SERVER_SOCK_ADDR);
 | 
			
		||||
		if (!attr) {
 | 
			
		||||
			err = -EINVAL;
 | 
			
		||||
			goto err_serv_unlock;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (nla_put_string(skb, NFSD_A_SOCK_TRANSPORT_NAME,
 | 
			
		||||
				   xprt->xpt_class->xcl_name) ||
 | 
			
		||||
		    nla_put(skb, NFSD_A_SOCK_ADDR,
 | 
			
		||||
			    sizeof(struct sockaddr_storage),
 | 
			
		||||
			    &xprt->xpt_local)) {
 | 
			
		||||
			err = -EINVAL;
 | 
			
		||||
			goto err_serv_unlock;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		nla_nest_end(skb, attr);
 | 
			
		||||
	}
 | 
			
		||||
	spin_unlock_bh(&serv->sv_lock);
 | 
			
		||||
out_unlock_mtx:
 | 
			
		||||
	mutex_unlock(&nfsd_mutex);
 | 
			
		||||
	genlmsg_end(skb, hdr);
 | 
			
		||||
 | 
			
		||||
	return genlmsg_reply(skb, info);
 | 
			
		||||
 | 
			
		||||
err_serv_unlock:
 | 
			
		||||
	spin_unlock_bh(&serv->sv_lock);
 | 
			
		||||
	mutex_unlock(&nfsd_mutex);
 | 
			
		||||
err_free_msg:
 | 
			
		||||
	nlmsg_free(skb);
 | 
			
		||||
 | 
			
		||||
	return err;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * nfsd_net_init - Prepare the nfsd_net portion of a new net namespace
 | 
			
		||||
 * @net: a freshly-created network namespace
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -55,12 +55,29 @@ enum {
 | 
			
		|||
	NFSD_A_SERVER_PROTO_MAX = (__NFSD_A_SERVER_PROTO_MAX - 1)
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum {
 | 
			
		||||
	NFSD_A_SOCK_ADDR = 1,
 | 
			
		||||
	NFSD_A_SOCK_TRANSPORT_NAME,
 | 
			
		||||
 | 
			
		||||
	__NFSD_A_SOCK_MAX,
 | 
			
		||||
	NFSD_A_SOCK_MAX = (__NFSD_A_SOCK_MAX - 1)
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum {
 | 
			
		||||
	NFSD_A_SERVER_SOCK_ADDR = 1,
 | 
			
		||||
 | 
			
		||||
	__NFSD_A_SERVER_SOCK_MAX,
 | 
			
		||||
	NFSD_A_SERVER_SOCK_MAX = (__NFSD_A_SERVER_SOCK_MAX - 1)
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum {
 | 
			
		||||
	NFSD_CMD_RPC_STATUS_GET = 1,
 | 
			
		||||
	NFSD_CMD_THREADS_SET,
 | 
			
		||||
	NFSD_CMD_THREADS_GET,
 | 
			
		||||
	NFSD_CMD_VERSION_SET,
 | 
			
		||||
	NFSD_CMD_VERSION_GET,
 | 
			
		||||
	NFSD_CMD_LISTENER_SET,
 | 
			
		||||
	NFSD_CMD_LISTENER_GET,
 | 
			
		||||
 | 
			
		||||
	__NFSD_CMD_MAX,
 | 
			
		||||
	NFSD_CMD_MAX = (__NFSD_CMD_MAX - 1)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue