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:
Lorenzo Bianconi 2024-04-23 15:25:44 +02:00 committed by Chuck Lever
parent cf619507ae
commit 16a4711774
5 changed files with 296 additions and 0 deletions

View file

@ -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

View file

@ -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 = {

View file

@ -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;

View file

@ -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

View file

@ -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)