Skip to content

Commit

Permalink
NFSD: add listener-{set,get} netlink command
Browse files Browse the repository at this point in the history
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 <[email protected]>
Co-developed-by: Jeff Layton <[email protected]>
Signed-off-by: Jeff Layton <[email protected]>
Signed-off-by: Lorenzo Bianconi <[email protected]>
Signed-off-by: Chuck Lever <[email protected]>
  • Loading branch information
LorenzoBianconi authored and chucklever committed May 6, 2024
1 parent cf61950 commit 16a4711
Show file tree
Hide file tree
Showing 5 changed files with 296 additions and 0 deletions.
34 changes: 34 additions & 0 deletions Documentation/netlink/specs/nfsd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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 changes: 22 additions & 0 deletions fs/nfsd/netlink.c
Original file line number Diff line number Diff line change
Expand Up @@ -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, },
Expand All @@ -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[] = {
{
Expand Down Expand Up @@ -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 = {
Expand Down
3 changes: 3 additions & 0 deletions fs/nfsd/netlink.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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;

Expand Down
220 changes: 220 additions & 0 deletions fs/nfsd/nfsctl.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 17 additions & 0 deletions include/uapi/linux/nfsd_netlink.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit 16a4711

Please sign in to comment.