* [PATCH 0/5] nfsd/sunrpc: allow userland to handle rpcbind registration
@ 2026-09-10 18:00 Jeff Layton
2026-09-10 18:00 ` [PATCH 1/5] SUNRPC: allow a service to opt out of " Jeff Layton
` (4 more replies)
0 siblings, 5 replies; 9+ messages in thread
From: Jeff Layton @ 2026-09-10 18:00 UTC (permalink / raw)
To: Trond Myklebust, Anna Schumaker, Chuck Lever, NeilBrown,
Olga Kornievskaia, Dai Ngo, Tom Talpey, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Donald Hunter, Shuah Khan
Cc: linux-nfs, linux-kernel, netdev, linux-kselftest, Jeff Layton
Consider this series an RFC. While working on the recent hardening
patches for the nfsd listener netlink interface, Chuck mentioned that we
could allow userland to handle rpcbind registation itself.
This adds such a mechanism to the netlink listener set interface. The
main idea is to add a new optional flag to the netlink downcall that
tells the kernel to skip rpcbind registration altogether for the nfsd
listeners.
Note that NLM registration is not affected and is still handled by the
kernel. Handling that is trickier since it can be started by the
client.
Patches to nfsdctl will follow.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
Jeff Layton (5):
SUNRPC: allow a service to opt out of rpcbind registration
NFSD: add a userspace-rpcbind flag to listener_set
NFSD: honour the userspace-rpcbind flag in listener_set
NFSD: report registerable programs in the listener_set reply
selftests/nfsd: exercise the userspace-rpcbind listener_set flag
Documentation/netlink/specs/nfsd.yaml | 55 +++-
fs/nfsd/netlink.c | 5 +-
fs/nfsd/nfsctl.c | 162 +++++++++++-
fs/nfsd/nfsd.h | 4 +-
fs/nfsd/nfssvc.c | 67 +++--
include/linux/sunrpc/svc.h | 2 +
include/uapi/linux/nfsd_netlink.h | 20 ++
net/sunrpc/svc.c | 5 +
net/sunrpc/svc_xprt.c | 2 +-
.../testing/selftests/nfsd/nfsd_netlink_listener.c | 284 ++++++++++++++++++++-
10 files changed, 571 insertions(+), 35 deletions(-)
---
base-commit: 5965280c53faff01983ccf89de70a5d48318f035
change-id: 20260903-nfsd-norpcb-21cdcaf246bb
Best regards,
--
Jeff Layton <jlayton@kernel.org>
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH 1/5] SUNRPC: allow a service to opt out of rpcbind registration
2026-09-10 18:00 [PATCH 0/5] nfsd/sunrpc: allow userland to handle rpcbind registration Jeff Layton
@ 2026-09-10 18:00 ` Jeff Layton
2026-09-10 18:00 ` [PATCH 2/5] NFSD: add a userspace-rpcbind flag to listener_set Jeff Layton
` (3 subsequent siblings)
4 siblings, 0 replies; 9+ messages in thread
From: Jeff Layton @ 2026-09-10 18:00 UTC (permalink / raw)
To: Trond Myklebust, Anna Schumaker, Chuck Lever, NeilBrown,
Olga Kornievskaia, Dai Ngo, Tom Talpey, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Donald Hunter, Shuah Khan
Cc: linux-nfs, linux-kernel, netdev, linux-kselftest, Jeff Layton
svc_bind() creates the local rpcbind client, and svc_register() then
makes one synchronous call for each program and version. Both run under
the caller's mutex. A caller that registers from userland needs neither.
Add sv_no_rpcbind to struct svc_serv, and fix up the code to honor it.
No caller sets the flag yet, so behaviour does not change.
Assisted-by: LLM
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
include/linux/sunrpc/svc.h | 2 ++
net/sunrpc/svc.c | 5 +++++
net/sunrpc/svc_xprt.c | 2 +-
3 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/include/linux/sunrpc/svc.h b/include/linux/sunrpc/svc.h
index 5fa9417e034d..7f09db6a360c 100644
--- a/include/linux/sunrpc/svc.h
+++ b/include/linux/sunrpc/svc.h
@@ -87,6 +87,8 @@ struct svc_serv {
char * sv_name; /* service name */
bool sv_is_pooled; /* is this a pooled service? */
+ /* Caller registers with rpcbind itself. Set before svc_bind(). */
+ bool sv_no_rpcbind;
struct svc_pool * sv_pools; /* array of thread pools */
int (*sv_threadfn)(void *data);
diff --git a/net/sunrpc/svc.c b/net/sunrpc/svc.c
index f73412e123a1..7e23af94a719 100644
--- a/net/sunrpc/svc.c
+++ b/net/sunrpc/svc.c
@@ -337,6 +337,8 @@ static int svc_uses_rpcbind(struct svc_serv *serv)
int svc_bind(struct svc_serv *serv, struct net *net)
{
+ if (serv->sv_no_rpcbind)
+ return 0;
if (!svc_uses_rpcbind(serv))
return 0;
return svc_rpcb_setup(serv, net);
@@ -1235,6 +1237,9 @@ int svc_register(struct svc_serv *serv, struct net *net,
if (proto == 0 && port == 0)
return -EINVAL;
+ if (serv->sv_no_rpcbind)
+ return 0;
+
for (p = 0; p < serv->sv_nprogs; p++) {
struct svc_program *progp = &serv->sv_programs[p];
diff --git a/net/sunrpc/svc_xprt.c b/net/sunrpc/svc_xprt.c
index d5634dd6d6cc..1a2c87e4d951 100644
--- a/net/sunrpc/svc_xprt.c
+++ b/net/sunrpc/svc_xprt.c
@@ -1266,7 +1266,7 @@ void svc_xprt_destroy_all(struct svc_serv *serv, struct net *net,
msleep(delay++);
}
- if (unregister)
+ if (unregister && !serv->sv_no_rpcbind)
svc_rpcb_cleanup(serv, net);
}
EXPORT_SYMBOL_GPL(svc_xprt_destroy_all);
--
2.55.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH 2/5] NFSD: add a userspace-rpcbind flag to listener_set
2026-09-10 18:00 [PATCH 0/5] nfsd/sunrpc: allow userland to handle rpcbind registration Jeff Layton
2026-09-10 18:00 ` [PATCH 1/5] SUNRPC: allow a service to opt out of " Jeff Layton
@ 2026-09-10 18:00 ` Jeff Layton
2026-09-10 19:43 ` Chuck Lever
2026-09-10 18:00 ` [PATCH 3/5] NFSD: honour the userspace-rpcbind flag in listener_set Jeff Layton
` (2 subsequent siblings)
4 siblings, 1 reply; 9+ messages in thread
From: Jeff Layton @ 2026-09-10 18:00 UTC (permalink / raw)
To: Trond Myklebust, Anna Schumaker, Chuck Lever, NeilBrown,
Olga Kornievskaia, Dai Ngo, Tom Talpey, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Donald Hunter, Shuah Khan
Cc: linux-nfs, linux-kernel, netdev, linux-kselftest, Jeff Layton
Describe the interface that lets a caller take over rpcbind registration.
The request gains a userspace-rpcbind flag. The reply echoes the flag,
lists the programs and versions that the caller should register, and
names the listeners that came up.
NLM is absent. lockd owns its own svc_serv and still registers itself.
Assisted-by: LLM
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
Documentation/netlink/specs/nfsd.yaml | 55 ++++++++++++++++++++++++++++++++++-
fs/nfsd/netlink.c | 5 ++--
include/uapi/linux/nfsd_netlink.h | 20 +++++++++++++
3 files changed, 77 insertions(+), 3 deletions(-)
diff --git a/Documentation/netlink/specs/nfsd.yaml b/Documentation/netlink/specs/nfsd.yaml
index 642268819c6f..9ae37bf3ea71 100644
--- a/Documentation/netlink/specs/nfsd.yaml
+++ b/Documentation/netlink/specs/nfsd.yaml
@@ -42,6 +42,15 @@ definitions:
- none
- tls
- mtls
+ -
+ type: flags
+ name: rpcbind-flags
+ doc: >-
+ Constraints that apply to an rpcbind registration. no-udp means the
+ kernel would not have registered this program and version over UDP,
+ so the caller must skip the udp and udp6 netids for it.
+ entries:
+ - no-udp
attribute-sets:
-
@@ -159,6 +168,23 @@ attribute-sets:
-
name: transport-name
type: string
+ -
+ name: rpcbind
+ attributes:
+ -
+ name: program
+ type: u32
+ doc: RPC program number to register.
+ -
+ name: version
+ type: u32
+ doc: RPC version number to register.
+ -
+ name: flags
+ type: u32
+ enum: rpcbind-flags
+ enum-as-flags: true
+ doc: Constraints on the listeners this entry applies to.
-
name: server-sock
attributes:
@@ -167,6 +193,24 @@ attribute-sets:
type: nest
nested-attributes: sock
multi-attr: true
+ -
+ name: userspace-rpcbind
+ type: flag
+ doc: >-
+ The caller registers the listeners with rpcbind itself, so the
+ kernel must not do it. The kernel echoes this attribute in the
+ reply when it accepts the request. Ownership cannot change while
+ a server exists.
+ -
+ name: rpcbind
+ type: nest
+ nested-attributes: rpcbind
+ multi-attr: true
+ doc: >-
+ A program and version that the caller should register for every
+ listener reported in the same reply, except the netids that flags
+ rules out. Reply only. NLM is absent because lockd still
+ registers itself.
-
name: pool-mode
attributes:
@@ -483,13 +527,22 @@ operations:
- version
-
name: listener-set
- doc: set nfs running sockets
+ doc: >-
+ set nfs running sockets. A request that carries userspace-rpcbind
+ is answered with a reply rather than a bare ack, and the addr list
+ in that reply names only the listeners that have an rpcbind netid.
attribute-set: server-sock
flags: [admin-perm]
do:
request:
attributes:
- addr
+ - userspace-rpcbind
+ reply:
+ attributes:
+ - addr
+ - userspace-rpcbind
+ - rpcbind
-
name: listener-get
doc: get nfs running listeners
diff --git a/fs/nfsd/netlink.c b/fs/nfsd/netlink.c
index eba8b353f412..88a4a4ffcb7f 100644
--- a/fs/nfsd/netlink.c
+++ b/fs/nfsd/netlink.c
@@ -79,8 +79,9 @@ static const struct nla_policy nfsd_version_set_nl_policy[NFSD_A_SERVER_PROTO_VE
};
/* NFSD_CMD_LISTENER_SET - do */
-static const struct nla_policy nfsd_listener_set_nl_policy[NFSD_A_SERVER_SOCK_ADDR + 1] = {
+static const struct nla_policy nfsd_listener_set_nl_policy[NFSD_A_SERVER_SOCK_USERSPACE_RPCBIND + 1] = {
[NFSD_A_SERVER_SOCK_ADDR] = NLA_POLICY_NESTED(nfsd_sock_nl_policy),
+ [NFSD_A_SERVER_SOCK_USERSPACE_RPCBIND] = { .type = NLA_FLAG, },
};
/* NFSD_CMD_POOL_MODE_SET - do */
@@ -153,7 +154,7 @@ static const struct genl_split_ops nfsd_nl_ops[] = {
.cmd = NFSD_CMD_LISTENER_SET,
.doit = nfsd_nl_listener_set_doit,
.policy = nfsd_listener_set_nl_policy,
- .maxattr = NFSD_A_SERVER_SOCK_ADDR,
+ .maxattr = NFSD_A_SERVER_SOCK_USERSPACE_RPCBIND,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
},
{
diff --git a/include/uapi/linux/nfsd_netlink.h b/include/uapi/linux/nfsd_netlink.h
index 87da1d0bb21e..c125af0cc6a5 100644
--- a/include/uapi/linux/nfsd_netlink.h
+++ b/include/uapi/linux/nfsd_netlink.h
@@ -50,6 +50,15 @@ enum nfsd_xprtsec_mode {
NFSD_XPRTSEC_MODE_MTLS = 4,
};
+/*
+ * Constraints that apply to an rpcbind registration. no-udp means the kernel
+ * would not have registered this program and version over UDP, so the caller
+ * must skip the udp and udp6 netids for it.
+ */
+enum nfsd_rpcbind_flags {
+ NFSD_RPCBIND_FLAGS_NO_UDP = 1,
+};
+
enum {
NFSD_A_CACHE_NOTIFY_CACHE_TYPE = 1,
@@ -113,8 +122,19 @@ enum {
NFSD_A_SOCK_MAX = (__NFSD_A_SOCK_MAX - 1)
};
+enum {
+ NFSD_A_RPCBIND_PROGRAM = 1,
+ NFSD_A_RPCBIND_VERSION,
+ NFSD_A_RPCBIND_FLAGS,
+
+ __NFSD_A_RPCBIND_MAX,
+ NFSD_A_RPCBIND_MAX = (__NFSD_A_RPCBIND_MAX - 1)
+};
+
enum {
NFSD_A_SERVER_SOCK_ADDR = 1,
+ NFSD_A_SERVER_SOCK_USERSPACE_RPCBIND,
+ NFSD_A_SERVER_SOCK_RPCBIND,
__NFSD_A_SERVER_SOCK_MAX,
NFSD_A_SERVER_SOCK_MAX = (__NFSD_A_SERVER_SOCK_MAX - 1)
--
2.55.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH 3/5] NFSD: honour the userspace-rpcbind flag in listener_set
2026-09-10 18:00 [PATCH 0/5] nfsd/sunrpc: allow userland to handle rpcbind registration Jeff Layton
2026-09-10 18:00 ` [PATCH 1/5] SUNRPC: allow a service to opt out of " Jeff Layton
2026-09-10 18:00 ` [PATCH 2/5] NFSD: add a userspace-rpcbind flag to listener_set Jeff Layton
@ 2026-09-10 18:00 ` Jeff Layton
2026-09-10 19:44 ` Chuck Lever
2026-09-10 18:00 ` [PATCH 4/5] NFSD: report registerable programs in the listener_set reply Jeff Layton
2026-09-10 18:00 ` [PATCH 5/5] selftests/nfsd: exercise the userspace-rpcbind listener_set flag Jeff Layton
4 siblings, 1 reply; 9+ messages in thread
From: Jeff Layton @ 2026-09-10 18:00 UTC (permalink / raw)
To: Trond Myklebust, Anna Schumaker, Chuck Lever, NeilBrown,
Olga Kornievskaia, Dai Ngo, Tom Talpey, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Donald Hunter, Shuah Khan
Cc: linux-nfs, linux-kernel, netdev, linux-kselftest, Jeff Layton
A listener_set request that carries userspace-rpcbind now sets
sv_no_rpcbind on the serv. The kernel then makes no rpcbind call at all,
avoiding synchronous rpcbind RPCs under nfsd_mutex.
lockd is unaffected. It owns a separate svc_serv and still registers NLM.
Assisted-by: LLM
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
fs/nfsd/nfsctl.c | 23 +++++++++++++++++------
fs/nfsd/nfsd.h | 2 +-
fs/nfsd/nfssvc.c | 14 ++++++++++++--
3 files changed, 30 insertions(+), 9 deletions(-)
diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c
index 1be8f98a293d..63746334a46f 100644
--- a/fs/nfsd/nfsctl.c
+++ b/fs/nfsd/nfsctl.c
@@ -748,7 +748,7 @@ static ssize_t __write_ports_addfd(char *buf, struct net *net, const struct cred
return -EINVAL;
trace_nfsd_ctl_ports_addfd(net, fd);
- err = nfsd_create_serv(net);
+ err = nfsd_create_serv(net, false);
if (err != 0)
return err;
@@ -780,7 +780,7 @@ static ssize_t __write_ports_addxprt(char *buf, struct net *net, const struct cr
return -EINVAL;
trace_nfsd_ctl_ports_addxprt(net, transport, port);
- err = nfsd_create_serv(net);
+ err = nfsd_create_serv(net, false);
if (err != 0)
return err;
@@ -2095,6 +2095,7 @@ int nfsd_nl_listener_set_doit(struct sk_buff *skb, struct genl_info *info)
unsigned int rpcb_failures;
const struct nlattr *attr;
bool skipped_rpcb = false;
+ bool userspace_rpcbind;
bool bad_rpcb = false;
struct svc_serv *serv;
LIST_HEAD(permsocks);
@@ -2111,15 +2112,25 @@ int nfsd_nl_listener_set_doit(struct sk_buff *skb, struct genl_info *info)
if (err)
return err;
+ userspace_rpcbind = nla_get_flag(info->attrs[NFSD_A_SERVER_SOCK_USERSPACE_RPCBIND]);
+
mutex_lock(&nfsd_mutex);
- err = nfsd_create_serv(net);
+ nn = net_generic(net, nfsd_net_id);
+
+ if (nn->nfsd_serv && nn->nfsd_serv->sv_no_rpcbind != userspace_rpcbind) {
+ NL_SET_ERR_MSG(info->extack,
+ "cannot change rpcbind ownership while a server exists");
+ mutex_unlock(&nfsd_mutex);
+ return -EBUSY;
+ }
+
+ err = nfsd_create_serv(net, userspace_rpcbind);
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);
@@ -2213,12 +2224,12 @@ int nfsd_nl_listener_set_doit(struct sk_buff *skb, struct genl_info *info)
continue;
}
- flags = skipped_rpcb ? SVC_SOCK_ANONYMOUS : 0;
+ flags = (userspace_rpcbind || skipped_rpcb) ? SVC_SOCK_ANONYMOUS : 0;
ret = svc_xprt_create_from_sa(serv, xcl_name, net, sa, flags,
current_cred());
hit_rpcb = false;
- if (!skipped_rpcb &&
+ if (!userspace_rpcbind && !skipped_rpcb &&
svc_rpcb_failure_count(serv) != rpcb_failures) {
skipped_rpcb = true;
hit_rpcb = true;
diff --git a/fs/nfsd/nfsd.h b/fs/nfsd/nfsd.h
index a145294c59c8..dcce45d58322 100644
--- a/fs/nfsd/nfsd.h
+++ b/fs/nfsd/nfsd.h
@@ -119,7 +119,7 @@ enum vers_op {NFSD_SET, NFSD_CLEAR, NFSD_TEST, NFSD_AVAIL };
int nfsd_vers(struct nfsd_net *nn, int vers, enum vers_op change);
int nfsd_minorversion(struct nfsd_net *nn, u32 minorversion, enum vers_op change);
void nfsd_reset_versions(struct nfsd_net *nn);
-int nfsd_create_serv(struct net *net);
+int nfsd_create_serv(struct net *net, bool no_rpcbind);
void nfsd_destroy_serv(struct net *net);
#ifdef CONFIG_DEBUG_FS
diff --git a/fs/nfsd/nfssvc.c b/fs/nfsd/nfssvc.c
index c04ef9d180ce..ef520d0562d6 100644
--- a/fs/nfsd/nfssvc.c
+++ b/fs/nfsd/nfssvc.c
@@ -607,7 +607,14 @@ struct svc_rqst *nfsd_current_rqst(void)
return NULL;
}
-int nfsd_create_serv(struct net *net)
+/**
+ * nfsd_create_serv - create the svc_serv for a namespace if it has none
+ * @net: network namespace to operate within
+ * @no_rpcbind: true if the caller registers the listeners with rpcbind
+ *
+ * Return: 0 on success or a negative errno.
+ */
+int nfsd_create_serv(struct net *net, bool no_rpcbind)
{
int error;
struct nfsd_net *nn = net_generic(net, nfsd_net_id);
@@ -635,6 +642,9 @@ int nfsd_create_serv(struct net *net)
return -ENOMEM;
}
+ /* svc_bind() reads this, so set it first. */
+ serv->sv_no_rpcbind = no_rpcbind;
+
error = svc_bind(serv, net);
if (error < 0) {
svc_destroy(&serv);
@@ -775,7 +785,7 @@ nfsd_svc(int n, int *nthreads, struct net *net, const struct cred *cred, const c
strscpy(nn->nfsd_name, scope ? scope : utsname()->nodename,
sizeof(nn->nfsd_name));
- error = nfsd_create_serv(net);
+ error = nfsd_create_serv(net, false);
if (error)
goto out;
serv = nn->nfsd_serv;
--
2.55.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH 4/5] NFSD: report registerable programs in the listener_set reply
2026-09-10 18:00 [PATCH 0/5] nfsd/sunrpc: allow userland to handle rpcbind registration Jeff Layton
` (2 preceding siblings ...)
2026-09-10 18:00 ` [PATCH 3/5] NFSD: honour the userspace-rpcbind flag in listener_set Jeff Layton
@ 2026-09-10 18:00 ` Jeff Layton
2026-09-10 19:46 ` Chuck Lever
2026-09-10 18:00 ` [PATCH 5/5] selftests/nfsd: exercise the userspace-rpcbind listener_set flag Jeff Layton
4 siblings, 1 reply; 9+ messages in thread
From: Jeff Layton @ 2026-09-10 18:00 UTC (permalink / raw)
To: Trond Myklebust, Anna Schumaker, Chuck Lever, NeilBrown,
Olga Kornievskaia, Dai Ngo, Tom Talpey, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Donald Hunter, Shuah Khan
Cc: linux-nfs, linux-kernel, netdev, linux-kselftest, Jeff Layton
A caller that owns rpcbind must know what to register. It cannot work
that out for itself: nfsd_support_acl_version() reads nfsd_acl_version[],
which CONFIG_NFSD_V2_ACL and CONFIG_NFSD_V3_ACL build, and no netlink
command reports that.
Reply to a listener_set that carried userspace-rpcbind with the programs
and versions that nfsd would have registered, plus the listeners to
register them for. Send the reply only when the request asked for it, so
an older caller still gets a bare ack.
Assisted-by: LLM
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
fs/nfsd/nfsctl.c | 139 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
fs/nfsd/nfsd.h | 2 +
fs/nfsd/nfssvc.c | 53 +++++++++++++--------
3 files changed, 175 insertions(+), 19 deletions(-)
diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c
index 63746334a46f..102149aac2b6 100644
--- a/fs/nfsd/nfsctl.c
+++ b/fs/nfsd/nfsctl.c
@@ -2079,6 +2079,128 @@ static int nfsd_nl_validate_listeners(struct genl_info *info)
return 0;
}
+static size_t nfsd_nl_listener_set_msgsize(struct svc_serv *serv)
+{
+ size_t size = GENL_HDRLEN + /* genlmsg_iput() */
+ nla_total_size(0); /* userspace-rpcbind */
+ struct svc_xprt *xprt;
+ unsigned int p;
+
+ lockdep_assert_held(&nfsd_mutex);
+
+ for (p = 0; p < serv->sv_nprogs; p++)
+ size += serv->sv_programs[p].pg_nvers *
+ (nla_total_size(0) + /* rpcbind nest */
+ nla_total_size(sizeof(u32)) + /* program */
+ nla_total_size(sizeof(u32)) + /* version */
+ nla_total_size(sizeof(u32))); /* flags */
+
+ spin_lock_bh(&serv->sv_lock);
+ list_for_each_entry(xprt, &serv->sv_permsocks, xpt_list) {
+ if (!test_bit(XPT_RPCB_UNREG, &xprt->xpt_flags))
+ continue;
+ size += nla_total_size(0) + /* addr nest */
+ nla_total_size(strlen(xprt->xpt_class->xcl_name) + 1) +
+ nla_total_size(sizeof(struct sockaddr_storage));
+ }
+ spin_unlock_bh(&serv->sv_lock);
+
+ return size;
+}
+
+static struct sk_buff *
+nfsd_nl_listener_set_msg(struct genl_info *info, struct net *net,
+ struct svc_serv *serv)
+{
+ struct svc_xprt *xprt;
+ struct sk_buff *skb;
+ unsigned int p, i;
+ void *hdr;
+ int err;
+
+ lockdep_assert_held(&nfsd_mutex);
+
+ skb = genlmsg_new(nfsd_nl_listener_set_msgsize(serv), GFP_KERNEL);
+ if (!skb)
+ return ERR_PTR(-ENOMEM);
+
+ hdr = genlmsg_iput(skb, info);
+ if (!hdr) {
+ err = -EMSGSIZE;
+ goto err_free_msg;
+ }
+
+ if (nla_put_flag(skb, NFSD_A_SERVER_SOCK_USERSPACE_RPCBIND)) {
+ err = -EMSGSIZE;
+ goto err_free_msg;
+ }
+
+ for (p = 0; p < serv->sv_nprogs; p++) {
+ const struct svc_program *progp = &serv->sv_programs[p];
+
+ for (i = 0; i < progp->pg_nvers; i++) {
+ struct nlattr *attr;
+ u32 flags = 0;
+
+ if (!nfsd_version_registerable(net, progp, i))
+ continue;
+
+ if (progp->pg_vers[i]->vs_need_cong_ctrl)
+ flags |= NFSD_RPCBIND_FLAGS_NO_UDP;
+
+ attr = nla_nest_start(skb, NFSD_A_SERVER_SOCK_RPCBIND);
+ if (!attr) {
+ err = -EMSGSIZE;
+ goto err_free_msg;
+ }
+ if (nla_put_u32(skb, NFSD_A_RPCBIND_PROGRAM,
+ progp->pg_prog) ||
+ nla_put_u32(skb, NFSD_A_RPCBIND_VERSION, i) ||
+ (flags && nla_put_u32(skb, NFSD_A_RPCBIND_FLAGS,
+ flags))) {
+ err = -EMSGSIZE;
+ goto err_free_msg;
+ }
+ nla_nest_end(skb, attr);
+ }
+ }
+
+ spin_lock_bh(&serv->sv_lock);
+ list_for_each_entry(xprt, &serv->sv_permsocks, xpt_list) {
+ struct nlattr *attr;
+
+ if (!test_bit(XPT_RPCB_UNREG, &xprt->xpt_flags))
+ continue;
+
+ attr = nla_nest_start(skb, NFSD_A_SERVER_SOCK_ADDR);
+ if (!attr) {
+ err = -EMSGSIZE;
+ 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 = -EMSGSIZE;
+ goto err_serv_unlock;
+ }
+
+ nla_nest_end(skb, attr);
+ }
+ spin_unlock_bh(&serv->sv_lock);
+
+ genlmsg_end(skb, hdr);
+ return skb;
+
+err_serv_unlock:
+ spin_unlock_bh(&serv->sv_lock);
+err_free_msg:
+ nlmsg_free(skb);
+ return ERR_PTR(err);
+}
+
/**
* nfsd_nl_listener_set_doit - set the nfs running sockets
* @skb: reply buffer
@@ -2092,6 +2214,7 @@ int nfsd_nl_listener_set_doit(struct sk_buff *skb, struct genl_info *info)
const struct nlattr *bad_attr = NULL;
struct svc_xprt *xprt, *tmp;
const char *bad_xprt = NULL;
+ struct sk_buff *rskb = NULL;
unsigned int rpcb_failures;
const struct nlattr *attr;
bool skipped_rpcb = false;
@@ -2273,12 +2396,28 @@ int nfsd_nl_listener_set_doit(struct sk_buff *skb, struct genl_info *info)
"rpcbind did not answer, some listeners are not registered");
}
+ /*
+ * Build the reply before the serv can go away, and only on success.
+ * A caller that got an errno has nothing to register.
+ */
+ if (!err && userspace_rpcbind) {
+ rskb = nfsd_nl_listener_set_msg(info, net, serv);
+ if (IS_ERR(rskb)) {
+ err = PTR_ERR(rskb);
+ rskb = NULL;
+ }
+ }
+
if (!serv->sv_nrthreads && list_empty(&nn->nfsd_serv->sv_permsocks))
nfsd_destroy_serv(net);
out_unlock_mtx:
mutex_unlock(&nfsd_mutex);
+ /* rskb is only built once err is known to be zero. */
+ if (rskb)
+ return genlmsg_reply(rskb, info);
+
return err;
}
diff --git a/fs/nfsd/nfsd.h b/fs/nfsd/nfsd.h
index dcce45d58322..69e3e92b3ec1 100644
--- a/fs/nfsd/nfsd.h
+++ b/fs/nfsd/nfsd.h
@@ -117,6 +117,8 @@ extern const struct svc_version localio_version1;
enum vers_op {NFSD_SET, NFSD_CLEAR, NFSD_TEST, NFSD_AVAIL };
int nfsd_vers(struct nfsd_net *nn, int vers, enum vers_op change);
+bool nfsd_version_registerable(struct net *net,
+ const struct svc_program *progp, u32 version);
int nfsd_minorversion(struct nfsd_net *nn, u32 minorversion, enum vers_op change);
void nfsd_reset_versions(struct nfsd_net *nn);
int nfsd_create_serv(struct net *net, bool no_rpcbind);
diff --git a/fs/nfsd/nfssvc.c b/fs/nfsd/nfssvc.c
index ef520d0562d6..890458c08f2e 100644
--- a/fs/nfsd/nfssvc.c
+++ b/fs/nfsd/nfssvc.c
@@ -41,11 +41,6 @@
atomic_t nfsd_th_cnt = ATOMIC_INIT(0);
static int nfsd(void *vrqstp);
#if defined(CONFIG_NFSD_V2_ACL) || defined(CONFIG_NFSD_V3_ACL)
-static int nfsd_acl_rpcbind_set(struct net *,
- const struct svc_program *,
- u32, int,
- unsigned short,
- unsigned short);
static __be32 nfsd_acl_init_request(struct svc_rqst *,
const struct svc_program *,
struct svc_process_info *);
@@ -127,7 +122,7 @@ struct svc_program nfsd_programs[] = {
.pg_class = "nfsd",
.pg_authenticate = svc_set_client,
.pg_init_request = nfsd_acl_init_request,
- .pg_rpcbind_set = nfsd_acl_rpcbind_set,
+ .pg_rpcbind_set = nfsd_rpcbind_set,
},
#endif /* defined(CONFIG_NFSD_V2_ACL) || defined(CONFIG_NFSD_V3_ACL) */
#if IS_ENABLED(CONFIG_NFS_LOCALIO)
@@ -813,18 +808,6 @@ nfsd_support_acl_version(int vers)
return false;
}
-static int
-nfsd_acl_rpcbind_set(struct net *net, const struct svc_program *progp,
- u32 version, int family, unsigned short proto,
- unsigned short port)
-{
- if (!nfsd_support_acl_version(version) ||
- !nfsd_vers(net_generic(net, nfsd_net_id), version, NFSD_TEST))
- return 0;
- return svc_generic_rpcbind_set(net, progp, version, family,
- proto, port);
-}
-
static __be32
nfsd_acl_init_request(struct svc_rqst *rqstp,
const struct svc_program *progp,
@@ -859,12 +842,44 @@ nfsd_acl_init_request(struct svc_rqst *rqstp,
}
#endif
+/**
+ * nfsd_version_registerable - would nfsd register [@progp, @version]?
+ * @net: network namespace to query
+ * @progp: RPC program to query
+ * @version: RPC version to query
+ *
+ * Answers the question for a listener of any protocol. A caller that asks
+ * about one listener must apply vs_need_cong_ctrl itself.
+ *
+ * Return: true when the version is a candidate for rpcbind registration.
+ */
+bool nfsd_version_registerable(struct net *net,
+ const struct svc_program *progp, u32 version)
+{
+ struct nfsd_net *nn = net_generic(net, nfsd_net_id);
+
+ if (version >= progp->pg_nvers || !progp->pg_vers[version])
+ return false;
+
+ /* nfslocalio is hidden and never reaches rpcbind. */
+ if (progp->pg_vers[version]->vs_hidden)
+ return false;
+
+#if defined(CONFIG_NFSD_V2_ACL) || defined(CONFIG_NFSD_V3_ACL)
+ if (progp->pg_prog == NFS_ACL_PROGRAM &&
+ !nfsd_support_acl_version(version))
+ return false;
+#endif
+
+ return nfsd_vers(nn, version, NFSD_TEST);
+}
+
static int
nfsd_rpcbind_set(struct net *net, const struct svc_program *progp,
u32 version, int family, unsigned short proto,
unsigned short port)
{
- if (!nfsd_vers(net_generic(net, nfsd_net_id), version, NFSD_TEST))
+ if (!nfsd_version_registerable(net, progp, version))
return 0;
return svc_generic_rpcbind_set(net, progp, version, family,
proto, port);
--
2.55.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH 5/5] selftests/nfsd: exercise the userspace-rpcbind listener_set flag
2026-09-10 18:00 [PATCH 0/5] nfsd/sunrpc: allow userland to handle rpcbind registration Jeff Layton
` (3 preceding siblings ...)
2026-09-10 18:00 ` [PATCH 4/5] NFSD: report registerable programs in the listener_set reply Jeff Layton
@ 2026-09-10 18:00 ` Jeff Layton
4 siblings, 0 replies; 9+ messages in thread
From: Jeff Layton @ 2026-09-10 18:00 UTC (permalink / raw)
To: Trond Myklebust, Anna Schumaker, Chuck Lever, NeilBrown,
Olga Kornievskaia, Dai Ngo, Tom Talpey, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Donald Hunter, Shuah Khan
Cc: linux-nfs, linux-kernel, netdev, linux-kselftest, Jeff Layton
Cover the flag that moves rpcbind registration to the caller.
rpcb_userspace_no_traffic is the one that matters. The errno says nothing
about whether the kernel talked to rpcbind, but the stub counters do: a
request that carries the flag must leave both at zero, which is what shows
that svc_bind() and svc_register() were both skipped.
The rest cover the reply and the ownership rule:
- rpcb_userspace_reply. The ack flag, a non-empty program list, and the
listener that came up. nfslocalio is hidden, so it must be absent.
- rpcb_userspace_no_v4_udp. NFSv4 carries no-udp and NFSv3 does not.
- rpcb_userspace_nfsacl_follows_nfs. CONFIG_NFSD_V3_ACL is invisible to the
test, so assert only that nfsacl v3 implies nfs v3.
- rpcb_userspace_busy and rpcb_userspace_busy_reverse. Ownership cannot
change under a live serv, in either direction.
- rpcb_userspace_teardown. Create and destroy a flagged serv three times,
then confirm that a kernel-owned serv still reaches rpcbind. An
unbalanced rpcb_put_local() would break the last step.
- rpcb_userspace_many_listeners. Forty listeners, which is more than
GENLMSG_DEFAULT_SIZE would have held. A reply sized from that constant
instead of from its contents returns -EMSGSIZE with every listener
already up.
genl_request_reply() grew an attribute argument, because listener_set now
answers with a reply message rather than a bare ack.
Assisted-by: LLM
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
.../testing/selftests/nfsd/nfsd_netlink_listener.c | 284 ++++++++++++++++++++-
1 file changed, 281 insertions(+), 3 deletions(-)
diff --git a/tools/testing/selftests/nfsd/nfsd_netlink_listener.c b/tools/testing/selftests/nfsd/nfsd_netlink_listener.c
index 106360f87b99..56ac2c11c07f 100644
--- a/tools/testing/selftests/nfsd/nfsd_netlink_listener.c
+++ b/tools/testing/selftests/nfsd/nfsd_netlink_listener.c
@@ -46,6 +46,9 @@
#include "../kselftest_harness.h"
+#define NFS_PROGRAM 100003
+#define NFS_ACL_PROGRAM 100227
+
#define NLA_ALIGN4(len) (((len) + 3) & ~3)
#define TEST_PORT 20049
#define MAX_LISTENERS 8
@@ -173,15 +176,24 @@ static int genl_request(uint8_t cmd, const char *attrs, int attrs_len)
return ret;
}
-/* Send a command and return the full reply message; -errno on failure. */
-static int genl_request_reply(uint8_t cmd, char *rbuf, size_t rlen)
+/*
+ * Send a command with attributes and return the full reply message; -errno
+ * on failure. NLM_F_ACK is left off: the kernel reports an error either way,
+ * so the first message back is the reply whenever there is one.
+ */
+static int genl_request_reply_attrs(uint8_t cmd, const char *attrs,
+ int attrs_len, char *rbuf, size_t rlen)
{
- char buf[256];
+ char buf[1 << 20];
struct nlmsghdr *nlh = (void *)buf;
int fd = genl_open();
int off, n, ret;
off = genl_hdr(buf, nfsd_family, NLM_F_REQUEST, cmd);
+ if (attrs_len) {
+ memcpy(buf + off, attrs, attrs_len);
+ off += attrs_len;
+ }
nlh->nlmsg_len = off;
if (send(fd, buf, off, 0) < 0)
@@ -198,6 +210,11 @@ static int genl_request_reply(uint8_t cmd, char *rbuf, size_t rlen)
return ret;
}
+static int genl_request_reply(uint8_t cmd, char *rbuf, size_t rlen)
+{
+ return genl_request_reply_attrs(cmd, NULL, 0, rbuf, rlen);
+}
+
/* Resolve the "nfsd" genl family id; -1 if not registered. */
static int genl_resolve_nfsd(void)
{
@@ -383,6 +400,111 @@ static int version_set_only(uint32_t major, uint32_t minor)
return genl_request(NFSD_CMD_VERSION_SET, attrs, NLA_ALIGN4(inner));
}
+/* ------------------- userspace-rpcbind ------------------- */
+
+struct rpcb_ent {
+ uint32_t program;
+ uint32_t version;
+ uint32_t flags;
+};
+
+/* More listeners than GENLMSG_DEFAULT_SIZE would have held. */
+#define RPCB_MANY_LISTENERS 40
+
+struct rpcb_reply {
+ int acked; /* saw NFSD_A_SERVER_SOCK_USERSPACE_RPCBIND */
+ int nprog;
+ struct rpcb_ent prog[16];
+ int naddr; /* every addr nest, not just the stored ones */
+ int nlistener;
+ struct listener_ent listener[MAX_LISTENERS];
+};
+
+/* Append the userspace-rpcbind request flag. */
+static int put_userspace_rpcbind(char *buf, int off)
+{
+ return put_attr(buf, off, NFSD_A_SERVER_SOCK_USERSPACE_RPCBIND, NULL, 0);
+}
+
+static void parse_rpcb_nest(const struct nlattr *na, struct rpcb_ent *e)
+{
+ const struct nlattr *in = (const void *)((const char *)na + NLA_HDRLEN);
+ int ileft = na->nla_len - NLA_HDRLEN;
+
+ memset(e, 0, sizeof(*e));
+ while (ileft >= (int)NLA_HDRLEN) {
+ const void *d = (const char *)in + NLA_HDRLEN;
+
+ switch (in->nla_type & NLA_TYPE_MASK) {
+ case NFSD_A_RPCBIND_PROGRAM:
+ e->program = *(const uint32_t *)d;
+ break;
+ case NFSD_A_RPCBIND_VERSION:
+ e->version = *(const uint32_t *)d;
+ break;
+ case NFSD_A_RPCBIND_FLAGS:
+ e->flags = *(const uint32_t *)d;
+ break;
+ }
+ ileft -= NLA_ALIGN4(in->nla_len);
+ in = (const void *)((const char *)in + NLA_ALIGN4(in->nla_len));
+ }
+}
+
+/*
+ * Send a listener_set that asks to own rpcbind, and parse the reply.
+ * Returns 0 on success or -errno.
+ */
+static int listener_set_rpcb(char *attrs, int off, struct rpcb_reply *out)
+{
+ char rbuf[64 * 1024];
+ const struct nlmsghdr *nlh = (const void *)rbuf;
+ const struct nlattr *na;
+ int left, n;
+
+ off = put_userspace_rpcbind(attrs, off);
+ memset(out, 0, sizeof(*out));
+
+ n = genl_request_reply_attrs(NFSD_CMD_LISTENER_SET, attrs, off,
+ rbuf, sizeof(rbuf));
+ if (n < 0)
+ return n;
+
+ out->nlistener = parse_listener_get(rbuf, n, out->listener,
+ MAX_LISTENERS);
+
+ na = (const void *)(rbuf + NLMSG_HDRLEN + GENL_HDRLEN);
+ left = nlh->nlmsg_len - NLMSG_HDRLEN - GENL_HDRLEN;
+ while (left >= (int)NLA_HDRLEN) {
+ switch (na->nla_type & NLA_TYPE_MASK) {
+ case NFSD_A_SERVER_SOCK_USERSPACE_RPCBIND:
+ out->acked = 1;
+ break;
+ case NFSD_A_SERVER_SOCK_ADDR:
+ out->naddr++;
+ break;
+ case NFSD_A_SERVER_SOCK_RPCBIND:
+ if (out->nprog < (int)ARRAY_SIZE(out->prog))
+ parse_rpcb_nest(na, &out->prog[out->nprog++]);
+ break;
+ }
+ left -= NLA_ALIGN4(na->nla_len);
+ na = (const void *)((const char *)na + NLA_ALIGN4(na->nla_len));
+ }
+ return 0;
+}
+
+static struct rpcb_ent *find_rpcb(struct rpcb_reply *r, uint32_t prog,
+ uint32_t vers)
+{
+ int i;
+
+ for (i = 0; i < r->nprog; i++)
+ if (r->prog[i].program == prog && r->prog[i].version == vers)
+ return &r->prog[i];
+ return NULL;
+}
+
/* Fetch the current listeners; returns count (>=0) or -errno. */
static int listener_get(struct listener_ent *out, int max)
{
@@ -1282,6 +1404,162 @@ TEST_F(nfsd_listener, rpcb_unreg_stop_after_failure)
EXPECT_LE(three, one);
}
+/* ===================== userspace rpcbind ===================== */
+
+/*
+ * The point of the flag: nfsd must make no rpcbind call at all. svc_bind()
+ * pings rpcbind at client creation and svc_register() calls it once per
+ * program and version, so a silent stub is what proves both were skipped.
+ */
+TEST_F(nfsd_listener, rpcb_userspace_no_traffic)
+{
+ struct rpcb_reply r;
+ char attrs[64];
+ int off = put_listener(attrs, 0, "tcp", TEST_PORT);
+
+ ASSERT_EQ(0, rpcb_conns());
+ ASSERT_EQ(0, listener_set_rpcb(attrs, off, &r));
+ EXPECT_EQ(0, rpcb_conns());
+ EXPECT_EQ(0, rpcb_calls());
+}
+
+/*
+ * The reply has to tell the caller what to register. Without the program
+ * list it cannot know whether nfsacl is built in.
+ */
+TEST_F(nfsd_listener, rpcb_userspace_reply)
+{
+ struct rpcb_reply r;
+ char attrs[64];
+ int off = put_listener(attrs, 0, "tcp", TEST_PORT);
+
+ ASSERT_EQ(0, listener_set_rpcb(attrs, off, &r));
+ EXPECT_EQ(1, r.acked);
+ EXPECT_GT(r.nprog, 0);
+ /* the listener came up and is named, so the caller knows the port */
+ ASSERT_EQ(1, r.nlistener);
+ EXPECT_NE(NULL, find_listener(r.listener, r.nlistener, "tcp",
+ AF_INET, TEST_PORT));
+ /* nfslocalio is hidden and must never be offered for registration */
+ EXPECT_EQ(NULL, find_rpcb(&r, 400122, 1));
+}
+
+/*
+ * nfsd_nl_validate_listeners() allows far more listeners than the default
+ * genl buffer holds, so the reply has to be sized from its contents. If it
+ * is not, the listeners all come up and the caller still sees -EMSGSIZE.
+ */
+TEST_F(nfsd_listener, rpcb_userspace_many_listeners)
+{
+ char attrs[RPCB_MANY_LISTENERS * 64];
+ struct rpcb_reply r;
+ int off = 0, i;
+
+ for (i = 0; i < RPCB_MANY_LISTENERS; i++)
+ off = put_listener(attrs, off, "tcp", TEST_PORT + i);
+
+ ASSERT_EQ(0, listener_set_rpcb(attrs, off, &r));
+ EXPECT_EQ(1, r.acked);
+ EXPECT_GT(r.nprog, 0);
+ EXPECT_EQ(RPCB_MANY_LISTENERS, r.naddr);
+}
+
+/*
+ * NFSv4 sets vs_need_cong_ctrl, so the kernel never registered it on UDP.
+ * The reply cannot filter it out, because the rule depends on the listener,
+ * so it must carry the flag instead.
+ */
+TEST_F(nfsd_listener, rpcb_userspace_no_v4_udp)
+{
+ struct rpcb_ent *v4, *v3;
+ struct rpcb_reply r;
+ char attrs[64];
+ int off = put_listener(attrs, 0, "tcp", TEST_PORT);
+
+ ASSERT_EQ(0, listener_set_rpcb(attrs, off, &r));
+
+ v4 = find_rpcb(&r, NFS_PROGRAM, 4);
+ if (v4)
+ EXPECT_EQ(NFSD_RPCBIND_FLAGS_NO_UDP,
+ v4->flags & NFSD_RPCBIND_FLAGS_NO_UDP);
+
+ v3 = find_rpcb(&r, NFS_PROGRAM, 3);
+ if (v3)
+ EXPECT_EQ(0, v3->flags & NFSD_RPCBIND_FLAGS_NO_UDP);
+}
+
+/*
+ * nfsacl is the value userland cannot derive: CONFIG_NFSD_V3_ACL is not
+ * visible over netlink. Only assert self-consistency -- if the kernel
+ * offers nfsacl v3 then it must also offer nfs v3, since both gate on the
+ * same enabled version.
+ */
+TEST_F(nfsd_listener, rpcb_userspace_nfsacl_follows_nfs)
+{
+ struct rpcb_reply r;
+ char attrs[64];
+ int off = put_listener(attrs, 0, "tcp", TEST_PORT);
+
+ ASSERT_EQ(0, listener_set_rpcb(attrs, off, &r));
+ if (find_rpcb(&r, NFS_ACL_PROGRAM, 3))
+ EXPECT_NE(NULL, find_rpcb(&r, NFS_PROGRAM, 3));
+}
+
+/*
+ * svc_bind() decided whether to take the rpcb_users reference that teardown
+ * drops, so ownership cannot flip under a live serv.
+ */
+TEST_F(nfsd_listener, rpcb_userspace_busy)
+{
+ struct rpcb_reply r;
+ char attrs[64], plain[64];
+ int off = put_listener(attrs, 0, "tcp", TEST_PORT);
+ int poff = put_listener(plain, 0, "tcp", TEST_PORT);
+
+ ASSERT_EQ(0, listener_set_rpcb(attrs, off, &r));
+
+ /* same listeners, but now asking the kernel to own rpcbind */
+ EXPECT_EQ(-EBUSY, listener_set(plain, poff));
+ EXPECT_STRNE("", last_extack);
+}
+
+/* And the same the other way round. */
+TEST_F(nfsd_listener, rpcb_userspace_busy_reverse)
+{
+ struct rpcb_reply r;
+ char attrs[64], plain[64];
+ int poff = put_listener(plain, 0, "tcp", TEST_PORT);
+ int off = put_listener(attrs, 0, "tcp", TEST_PORT);
+
+ ASSERT_EQ(0, listener_set(plain, poff));
+ EXPECT_EQ(-EBUSY, listener_set_rpcb(attrs, off, &r));
+}
+
+/*
+ * rpcb_create_local() increments sn->rpcb_users and rpcb_put_local()
+ * decrements it. A serv that never took the reference must not drop it, or
+ * the next serv finds the count wrong. Cycle a few times, then confirm a
+ * kernel-owned serv can still reach rpcbind.
+ */
+TEST_F(nfsd_listener, rpcb_userspace_teardown)
+{
+ struct rpcb_reply r;
+ char attrs[64], plain[64];
+ int off, poff, i;
+
+ for (i = 0; i < 3; i++) {
+ off = put_listener(attrs, 0, "tcp", TEST_PORT);
+ ASSERT_EQ(0, listener_set_rpcb(attrs, off, &r));
+ /* empty list with no threads destroys the serv */
+ ASSERT_EQ(0, listener_set_rpcb(attrs, 0, &r));
+ ASSERT_EQ(0, rpcb_conns());
+ }
+
+ poff = put_listener(plain, 0, "tcp", TEST_PORT);
+ ASSERT_EQ(0, listener_set(plain, poff));
+ EXPECT_GT(rpcb_conns(), 0);
+}
+
/* ===================== threads / -EBUSY semantics ===================== */
TEST_F(nfsd_listener, sem_busy_on_change)
--
2.55.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH 2/5] NFSD: add a userspace-rpcbind flag to listener_set
2026-09-10 18:00 ` [PATCH 2/5] NFSD: add a userspace-rpcbind flag to listener_set Jeff Layton
@ 2026-09-10 19:43 ` Chuck Lever
0 siblings, 0 replies; 9+ messages in thread
From: Chuck Lever @ 2026-09-10 19:43 UTC (permalink / raw)
To: Jeff Layton
Cc: Trond Myklebust, Anna Schumaker, NeilBrown, Olga Kornievskaia,
Dai Ngo, Tom Talpey, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Donald Hunter,
Shuah Khan, linux-nfs, linux-kernel, netdev, linux-kselftest
On Thu, 10 Sep 2026, Jeff Layton wrote:
> Describe the interface that lets a caller take over rpcbind registration.
> --- a/Documentation/netlink/specs/nfsd.yaml
> +++ b/Documentation/netlink/specs/nfsd.yaml
> @@ -483,13 +527,22 @@ operations:
> -
> name: listener-set
> - doc: set nfs running sockets
> + doc: >-
> + set nfs running sockets. A request that carries userspace-rpcbind
> + is answered with a reply rather than a bare ack, and the addr list
> + in that reply names only the listeners that have an rpcbind netid.
"listeners that have an rpcbind netid" overstates the set. RDMA
listeners have netids (rdma, rdma6) but are omitted from the
reply.
Following the way Solaris handles RDMA transport, NFSD never
registered RDMA itself, so that is not a regression, but the doc
should say what your filter actually selects.
> attribute-set: server-sock
> flags: [admin-perm]
> do:
> request:
> attributes:
> - addr
> + - userspace-rpcbind
> + reply:
> + attributes:
> + - addr
> + - userspace-rpcbind
> + - rpcbind
The spec declares an unconditional reply, but patch 4 sends one only
when the request carried userspace-rpcbind. ynl has no way to express
a conditional reply, and no other in-tree spec does this.
Either always send the reply (drop the userspace_rpcbind gate in
patch 4, so a plain caller simply ignores it), or leave listener-set
without a reply and put the registration data in a separate get
operation. I lean toward always replying; that's is a little more
"declarative".
--
Chuck Lever (Come to NFS bake-a-thon! https://nfsv4bat.org)
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH 3/5] NFSD: honour the userspace-rpcbind flag in listener_set
2026-09-10 18:00 ` [PATCH 3/5] NFSD: honour the userspace-rpcbind flag in listener_set Jeff Layton
@ 2026-09-10 19:44 ` Chuck Lever
0 siblings, 0 replies; 9+ messages in thread
From: Chuck Lever @ 2026-09-10 19:44 UTC (permalink / raw)
To: Jeff Layton
Cc: Trond Myklebust, Anna Schumaker, NeilBrown, Olga Kornievskaia,
Dai Ngo, Tom Talpey, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Donald Hunter,
Shuah Khan, linux-nfs, linux-kernel, netdev, linux-kselftest
On Thu, 10 Sep 2026, Jeff Layton wrote:
> --- a/fs/nfsd/nfsctl.c
> +++ b/fs/nfsd/nfsctl.c
> @@ -2111,15 +2112,25 @@ int nfsd_nl_listener_set_doit(struct sk_buff *skb, struct genl_info *info)
> + userspace_rpcbind = nla_get_flag(info->attrs[NFSD_A_SERVER_SOCK_USERSPACE_RPCBIND]);
> +
> mutex_lock(&nfsd_mutex);
>
> - err = nfsd_create_serv(net);
> + nn = net_generic(net, nfsd_net_id);
> +
> + if (nn->nfsd_serv && nn->nfsd_serv->sv_no_rpcbind != userspace_rpcbind) {
> + NL_SET_ERR_MSG(info->extack,
> + "cannot change rpcbind ownership while a server exists");
> + mutex_unlock(&nfsd_mutex);
> + return -EBUSY;
> + }
> +
> + err = nfsd_create_serv(net, userspace_rpcbind);
This ownership check lives only in the netlink path, so it is
enforced in one direction. Consider: the serv is created here with
userspace-rpcbind, then an old rpc.nfsd writes a socket fd to
/proc/fs/nfsd/portlist. __write_ports_addfd() calls
nfsd_create_serv(net, false), which returns 0 at the existing
"if (nn->nfsd_serv) return 0;" without looking at the flag.
svc_addsock() then goes through svc_setup_socket() with pmap_register
set, and svc_register() returns 0 early because sv_no_rpcbind is set.
The write succeeds, and the new listener is neither registered by the
kernel nor reported to the userspace owner. The reverse order gets
-EBUSY from the check above.
Moving the mismatch test into nfsd_create_serv(), where every creator
already funnels, would make both paths agree. The extack message would
need a different home, but the errno is the important part.
--
Chuck Lever (Come to NFS bake-a-thon! https://nfsv4bat.org)
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH 4/5] NFSD: report registerable programs in the listener_set reply
2026-09-10 18:00 ` [PATCH 4/5] NFSD: report registerable programs in the listener_set reply Jeff Layton
@ 2026-09-10 19:46 ` Chuck Lever
0 siblings, 0 replies; 9+ messages in thread
From: Chuck Lever @ 2026-09-10 19:46 UTC (permalink / raw)
To: Jeff Layton
Cc: Trond Myklebust, Anna Schumaker, NeilBrown, Olga Kornievskaia,
Dai Ngo, Tom Talpey, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Donald Hunter,
Shuah Khan, linux-nfs, linux-kernel, netdev, linux-kselftest
On Thu, 10 Sep 2026, Jeff Layton wrote:
> --- a/fs/nfsd/nfsctl.c
> +++ b/fs/nfsd/nfsctl.c
> @@ -2273,12 +2396,28 @@ int nfsd_nl_listener_set_doit(struct sk_buff *skb, struct genl_info *info)
> + /*
> + * Build the reply before the serv can go away, and only on success.
> + * A caller that got an errno has nothing to register.
> + */
> + if (!err && userspace_rpcbind) {
> + rskb = nfsd_nl_listener_set_msg(info, net, serv);
> + if (IS_ERR(rskb)) {
> + err = PTR_ERR(rskb);
> + rskb = NULL;
> + }
> + }
The second sentence of the comment is not true after a partial
failure. Take a request with userspace-rpcbind carrying {tcp:2049,
tcp:<port already in use>}. The creation loop continues past the
second failure and keeps only the last errno. The 2049 xprt is on
sv_permsocks, so the serv survives the list_empty() check below, but
this gate skips the reply and the caller sees -EADDRINUSE. nfsd is
now serving on 2049 with nothing registered in rpcbind, and the caller
was never told which listeners came up or which programs to register.
The same end state results when nfsd_nl_listener_set_msg() itself
fails with -ENOMEM.
In kernel-owned mode the survivor would already have been registered
inside svc_xprt_create_from_sa(), so this is a behavioral gap specific
to the new mode. Either tear down the listeners that were created when
any of them fails, or send the reply whenever at least one listener
exists on the serv, regardless of err. The second keeps the existing
partial-success semantics; the caller can then act on what it got.
> +static size_t nfsd_nl_listener_set_msgsize(struct svc_serv *serv)
> +{
> + size_t size = GENL_HDRLEN + /* genlmsg_iput() */
> + nla_total_size(0); /* userspace-rpcbind */
genlmsg_new() already adds GENL_HDRLEN via genlmsg_total_size(), so
this term double-counts it. Harmless, but the comment will mislead the
next person sizing a reply. Pass only the attribute payload here.
> + spin_lock_bh(&serv->sv_lock);
> + list_for_each_entry(xprt, &serv->sv_permsocks, xpt_list) {
> + struct nlattr *attr;
> +
> + if (!test_bit(XPT_RPCB_UNREG, &xprt->xpt_flags))
> + continue;
> +
> + attr = nla_nest_start(skb, NFSD_A_SERVER_SOCK_ADDR);
> + if (!attr) {
> + err = -EMSGSIZE;
> + 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 = -EMSGSIZE;
> + goto err_serv_unlock;
> + }
> +
> + nla_nest_end(skb, attr);
> + }
This is the same nest emission as the loop in
nfsd_nl_listener_get_doit(), apart from the XPT_RPCB_UNREG filter and
the errno (that one returns -EINVAL). A small helper that emits one
addr nest for an xprt, called from both loops, would keep the two from
drifting, with the size accounting above as the third place to keep
in step.
Also, as noted on patch 2, this filter selects TCP and UDP only; the
spec doc should match.
> --- a/fs/nfsd/nfssvc.c
> +++ b/fs/nfsd/nfssvc.c
> @@ -859,12 +842,44 @@ nfsd_acl_init_request(struct svc_rqst *rqstp,
> +bool nfsd_version_registerable(struct net *net,
> + const struct svc_program *progp, u32 version)
> +{
> + struct nfsd_net *nn = net_generic(net, nfsd_net_id);
> +
> + if (version >= progp->pg_nvers || !progp->pg_vers[version])
> + return false;
> +
> + /* nfslocalio is hidden and never reaches rpcbind. */
> + if (progp->pg_vers[version]->vs_hidden)
> + return false;
> +
> +#if defined(CONFIG_NFSD_V2_ACL) || defined(CONFIG_NFSD_V3_ACL)
> + if (progp->pg_prog == NFS_ACL_PROGRAM &&
> + !nfsd_support_acl_version(version))
> + return false;
> +#endif
The ACL block is redundant with the first test. The ACL program has
pg_nvers = NFSD_ACL_NRVERS and pg_vers = nfsd_acl_version, whose
slots 0 and 1 and any compiled-out version are NULL, and
nfsd_support_acl_version() checks exactly that range and that array.
Dropping the block removes the function's only program-number special
case and the #if with it.
--
Chuck Lever (Come to NFS bake-a-thon! https://nfsv4bat.org)
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-09-10 19:47 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-10 18:00 [PATCH 0/5] nfsd/sunrpc: allow userland to handle rpcbind registration Jeff Layton
2026-09-10 18:00 ` [PATCH 1/5] SUNRPC: allow a service to opt out of " Jeff Layton
2026-09-10 18:00 ` [PATCH 2/5] NFSD: add a userspace-rpcbind flag to listener_set Jeff Layton
2026-09-10 19:43 ` Chuck Lever
2026-09-10 18:00 ` [PATCH 3/5] NFSD: honour the userspace-rpcbind flag in listener_set Jeff Layton
2026-09-10 19:44 ` Chuck Lever
2026-09-10 18:00 ` [PATCH 4/5] NFSD: report registerable programs in the listener_set reply Jeff Layton
2026-09-10 19:46 ` Chuck Lever
2026-09-10 18:00 ` [PATCH 5/5] selftests/nfsd: exercise the userspace-rpcbind listener_set flag Jeff Layton
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox