* [PATCH v2 0/5] nfsd/sunrpc: allow userland to handle rpcbind registration
@ 2026-09-14 15:20 Jeff Layton
2026-09-14 15:20 ` [PATCH v2 1/5] SUNRPC: allow a service to opt out of " Jeff Layton
` (4 more replies)
0 siblings, 5 replies; 6+ messages in thread
From: Jeff Layton @ 2026-09-14 15:20 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
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>
---
Changes in v2:
- Refuse the legacy portlist add-fd write against a userspace-rpcbind
serv. nfsd_create_serv() cannot flip ownership back for an existing
serv, svc_addsock() expects svc_register() to register the listener,
and there is no way to tell the rpcbind owner about it.
- selftests: require NFSv3 in the reply and skip where NFSv4 is not
built, rather than passing when neither is present.
- selftests: cover the portlist refusal.
- Link to v1: https://lore.kernel.org/r/20260910-nfsd-norpcb-v1-0-b4d5182d634c@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 | 169 +++++++++-
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 | 361 ++++++++++++++++++++-
10 files changed, 655 insertions(+), 35 deletions(-)
---
base-commit: 4d46e877c21d07ddcad03da7f3b05ad2cc22b5b3
change-id: 20260903-nfsd-norpcb-21cdcaf246bb
Best regards,
--
Jeff Layton <jlayton@kernel.org>
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v2 1/5] SUNRPC: allow a service to opt out of rpcbind registration
2026-09-14 15:20 [PATCH v2 0/5] nfsd/sunrpc: allow userland to handle rpcbind registration Jeff Layton
@ 2026-09-14 15:20 ` Jeff Layton
2026-09-14 15:20 ` [PATCH v2 2/5] NFSD: add a userspace-rpcbind flag to listener_set Jeff Layton
` (3 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Jeff Layton @ 2026-09-14 15:20 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 24698856eb40..cfb5ab17dabd 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 5e210bc68422..08cfe8a31189 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] 6+ messages in thread
* [PATCH v2 2/5] NFSD: add a userspace-rpcbind flag to listener_set
2026-09-14 15:20 [PATCH v2 0/5] nfsd/sunrpc: allow userland to handle rpcbind registration Jeff Layton
2026-09-14 15:20 ` [PATCH v2 1/5] SUNRPC: allow a service to opt out of " Jeff Layton
@ 2026-09-14 15:20 ` Jeff Layton
2026-09-14 15:20 ` [PATCH v2 3/5] NFSD: honour the userspace-rpcbind flag in listener_set Jeff Layton
` (2 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Jeff Layton @ 2026-09-14 15:20 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] 6+ messages in thread
* [PATCH v2 3/5] NFSD: honour the userspace-rpcbind flag in listener_set
2026-09-14 15:20 [PATCH v2 0/5] nfsd/sunrpc: allow userland to handle rpcbind registration Jeff Layton
2026-09-14 15:20 ` [PATCH v2 1/5] SUNRPC: allow a service to opt out of " Jeff Layton
2026-09-14 15:20 ` [PATCH v2 2/5] NFSD: add a userspace-rpcbind flag to listener_set Jeff Layton
@ 2026-09-14 15:20 ` Jeff Layton
2026-09-14 15:20 ` [PATCH v2 4/5] NFSD: report registerable programs in the listener_set reply Jeff Layton
2026-09-14 15:20 ` [PATCH v2 5/5] selftests/nfsd: exercise the userspace-rpcbind listener_set flag Jeff Layton
4 siblings, 0 replies; 6+ messages in thread
From: Jeff Layton @ 2026-09-14 15:20 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.
The legacy portlist add-fd interface refuses to run against such a serv.
nfsd_create_serv() returns early when the serv exists, so it cannot flip
ownership back, svc_addsock() does not pass SVC_SOCK_ANONYMOUS and so
expects svc_register() to register the listener, and there is no way to
tell the rpcbind owner about it. -EBUSY, matching listener_set. add-xprt
already passes SVC_SOCK_ANONYMOUS and never registered anything.
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 | 30 ++++++++++++++++++++++++------
fs/nfsd/nfsd.h | 2 +-
fs/nfsd/nfssvc.c | 14 ++++++++++++--
3 files changed, 37 insertions(+), 9 deletions(-)
diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c
index 1be8f98a293d..207a1bed3928 100644
--- a/fs/nfsd/nfsctl.c
+++ b/fs/nfsd/nfsctl.c
@@ -748,7 +748,14 @@ 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);
+ /*
+ * svc_register() is a no-op once userland owns rpcbind, and this
+ * interface has no way to hand the new listener to that owner.
+ */
+ if (nn->nfsd_serv && nn->nfsd_serv->sv_no_rpcbind)
+ return -EBUSY;
+
+ err = nfsd_create_serv(net, false);
if (err != 0)
return err;
@@ -780,7 +787,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 +2102,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 +2119,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 +2231,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] 6+ messages in thread
* [PATCH v2 4/5] NFSD: report registerable programs in the listener_set reply
2026-09-14 15:20 [PATCH v2 0/5] nfsd/sunrpc: allow userland to handle rpcbind registration Jeff Layton
` (2 preceding siblings ...)
2026-09-14 15:20 ` [PATCH v2 3/5] NFSD: honour the userspace-rpcbind flag in listener_set Jeff Layton
@ 2026-09-14 15:20 ` Jeff Layton
2026-09-14 15:20 ` [PATCH v2 5/5] selftests/nfsd: exercise the userspace-rpcbind listener_set flag Jeff Layton
4 siblings, 0 replies; 6+ messages in thread
From: Jeff Layton @ 2026-09-14 15:20 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 207a1bed3928..bb09d89e860e 100644
--- a/fs/nfsd/nfsctl.c
+++ b/fs/nfsd/nfsctl.c
@@ -2086,6 +2086,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
@@ -2099,6 +2221,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;
@@ -2280,12 +2403,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] 6+ messages in thread
* [PATCH v2 5/5] selftests/nfsd: exercise the userspace-rpcbind listener_set flag
2026-09-14 15:20 [PATCH v2 0/5] nfsd/sunrpc: allow userland to handle rpcbind registration Jeff Layton
` (3 preceding siblings ...)
2026-09-14 15:20 ` [PATCH v2 4/5] NFSD: report registerable programs in the listener_set reply Jeff Layton
@ 2026-09-14 15:20 ` Jeff Layton
4 siblings, 0 replies; 6+ messages in thread
From: Jeff Layton @ 2026-09-14 15:20 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. NFSv3 must be offered and must not carry
no-udp; NFSv4 carries it, or the test skips where v4 is not built.
- 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_portlist_busy. The legacy portlist add-fd write is
refused against a flagged serv and still works against a kernel-owned
one. nfsdfs is mounted in the test's netns, so the write cannot reach
the host's nfsd.
- 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 | 361 ++++++++++++++++++++-
1 file changed, 358 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..c01941ee262f 100644
--- a/tools/testing/selftests/nfsd/nfsd_netlink_listener.c
+++ b/tools/testing/selftests/nfsd/nfsd_netlink_listener.c
@@ -20,6 +20,7 @@
*/
#define _GNU_SOURCE
#include <errno.h>
+#include <fcntl.h>
#include <poll.h>
#include <sched.h>
#include <signal.h>
@@ -46,6 +47,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 +177,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 +211,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 +401,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 +1405,238 @@ 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));
+
+ /* v3 is always built in and on by default, so it must be offered */
+ v3 = find_rpcb(&r, NFS_PROGRAM, 3);
+ ASSERT_NE(NULL, v3);
+ EXPECT_EQ(0, v3->flags & NFSD_RPCBIND_FLAGS_NO_UDP);
+
+ v4 = find_rpcb(&r, NFS_PROGRAM, 4);
+ if (!v4)
+ SKIP(return, "NFSv4 is not enabled");
+ EXPECT_EQ(NFSD_RPCBIND_FLAGS_NO_UDP,
+ v4->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));
+}
+
+#define NFSDFS_DIR "/run/nfsdfs"
+
+/* nfsdfs binds to the netns it is mounted in, so mount a private one */
+static int mount_nfsdfs(void)
+{
+ if (mkdir(NFSDFS_DIR, 0700) < 0 && errno != EEXIST)
+ return -1;
+ return mount("nfsd", NFSDFS_DIR, "nfsd", 0, NULL);
+}
+
+/* simple_transaction allows one write per open, so open every time */
+static int write_portlist(int sock)
+{
+ char buf[32];
+ int fd, len, ret = 0;
+
+ fd = open(NFSDFS_DIR "/portlist", O_WRONLY);
+ if (fd < 0)
+ return -errno;
+ len = snprintf(buf, sizeof(buf), "%d\n", sock);
+ if (write(fd, buf, len) < 0)
+ ret = -errno;
+ close(fd);
+ return ret;
+}
+
+static int tcp_listener(int port)
+{
+ struct sockaddr_in sa = {
+ .sin_family = AF_INET,
+ .sin_addr.s_addr = htonl(INADDR_LOOPBACK),
+ .sin_port = htons(port),
+ };
+ int fd = socket(AF_INET, SOCK_STREAM, 0);
+
+ if (fd < 0)
+ return -1;
+ if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0 ||
+ listen(fd, 1) < 0) {
+ close(fd);
+ return -1;
+ }
+ return fd;
+}
+
+/*
+ * The legacy portlist interface cannot hand a listener to the rpcbind owner
+ * and svc_register() will not register it, so it has to refuse.
+ */
+TEST_F(nfsd_listener, rpcb_userspace_portlist_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);
+ int sock;
+
+ if (mount_nfsdfs() < 0)
+ SKIP(return, "cannot mount nfsdfs: %s", strerror(errno));
+
+ sock = tcp_listener(TEST_PORT + 1);
+ ASSERT_GE(sock, 0);
+
+ ASSERT_EQ(0, listener_set_rpcb(attrs, off, &r));
+ EXPECT_EQ(-EBUSY, write_portlist(sock));
+
+ /* the same write against a kernel-owned serv still works */
+ ASSERT_EQ(0, listener_set_rpcb(attrs, 0, &r)); /* destroys the serv */
+ ASSERT_EQ(0, listener_set(plain, poff));
+ EXPECT_EQ(0, write_portlist(sock));
+
+ close(sock);
+}
+
+/*
+ * 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] 6+ messages in thread
end of thread, other threads:[~2026-09-14 15:21 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-14 15:20 [PATCH v2 0/5] nfsd/sunrpc: allow userland to handle rpcbind registration Jeff Layton
2026-09-14 15:20 ` [PATCH v2 1/5] SUNRPC: allow a service to opt out of " Jeff Layton
2026-09-14 15:20 ` [PATCH v2 2/5] NFSD: add a userspace-rpcbind flag to listener_set Jeff Layton
2026-09-14 15:20 ` [PATCH v2 3/5] NFSD: honour the userspace-rpcbind flag in listener_set Jeff Layton
2026-09-14 15:20 ` [PATCH v2 4/5] NFSD: report registerable programs in the listener_set reply Jeff Layton
2026-09-14 15:20 ` [PATCH v2 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