Commit 16a47117 authored by Lorenzo Bianconi's avatar Lorenzo Bianconi Committed by Chuck Lever
Browse files

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: default avatarJeff Layton <jlayton@kernel.org>
Co-developed-by: default avatarJeff Layton <jlayton@kernel.org>
Signed-off-by: default avatarJeff Layton <jlayton@kernel.org>
Signed-off-by: default avatarLorenzo Bianconi <lorenzo@kernel.org>
Signed-off-by: default avatarChuck Lever <chuck.lever@oracle.com>
parent cf619507
Loading
Loading
Loading
Loading
+34 −0
Original line number Diff line number Diff line
@@ -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
+22 −0
Original line number Diff line number Diff line
@@ -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 = {
+3 −0
Original line number Diff line number Diff line
@@ -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 −0
Original line number Diff line number Diff line
@@ -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
+17 −0
Original line number Diff line number Diff line
@@ -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)