* [PATCH 01/14] nfsd: move struct nfsd_genl_rqstp to nfsctl.c
2026-03-16 15:14 [PATCH 00/14] nfsd/sunrpc: add support for netlink upcalls for mountd/exportd Jeff Layton
@ 2026-03-16 15:14 ` Jeff Layton
2026-03-16 15:14 ` [PATCH 02/14] sunrpc: rename sunrpc_cache_pipe_upcall() to sunrpc_cache_upcall() Jeff Layton
` (12 subsequent siblings)
13 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-03-16 15:14 UTC (permalink / raw)
To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
Cc: Trond Myklebust, Anna Schumaker, linux-nfs, linux-kernel,
Jeff Layton
It's not used outside of that file.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
fs/nfsd/nfsctl.c | 15 +++++++++++++++
fs/nfsd/nfsd.h | 15 ---------------
2 files changed, 15 insertions(+), 15 deletions(-)
diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c
index 988a79ec4a79af2f9c9406be9740d1922c2b4ec8..dc294c4f8c58a6692b9dfbeb98fedbd649ae1b95 100644
--- a/fs/nfsd/nfsctl.c
+++ b/fs/nfsd/nfsctl.c
@@ -1412,6 +1412,21 @@ static int create_proc_exports_entry(void)
unsigned int nfsd_net_id;
+struct nfsd_genl_rqstp {
+ struct sockaddr rq_daddr;
+ struct sockaddr rq_saddr;
+ unsigned long rq_flags;
+ ktime_t rq_stime;
+ __be32 rq_xid;
+ u32 rq_vers;
+ u32 rq_prog;
+ u32 rq_proc;
+
+ /* NFSv4 compound */
+ u32 rq_opcnt;
+ u32 rq_opnum[16];
+};
+
static int nfsd_genl_rpc_status_compose_msg(struct sk_buff *skb,
struct netlink_callback *cb,
struct nfsd_genl_rqstp *genl_rqstp)
diff --git a/fs/nfsd/nfsd.h b/fs/nfsd/nfsd.h
index 7c009f07c90b50d7074695d4665a2eca85140eda..260bf8badb032de18f973556fa5deabe7e67870d 100644
--- a/fs/nfsd/nfsd.h
+++ b/fs/nfsd/nfsd.h
@@ -60,21 +60,6 @@ struct readdir_cd {
/* Maximum number of operations per session compound */
#define NFSD_MAX_OPS_PER_COMPOUND 200
-struct nfsd_genl_rqstp {
- struct sockaddr rq_daddr;
- struct sockaddr rq_saddr;
- unsigned long rq_flags;
- ktime_t rq_stime;
- __be32 rq_xid;
- u32 rq_vers;
- u32 rq_prog;
- u32 rq_proc;
-
- /* NFSv4 compound */
- u32 rq_opcnt;
- u32 rq_opnum[16];
-};
-
extern struct svc_program nfsd_programs[];
extern const struct svc_version nfsd_version2, nfsd_version3, nfsd_version4;
extern struct mutex nfsd_mutex;
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH 02/14] sunrpc: rename sunrpc_cache_pipe_upcall() to sunrpc_cache_upcall()
2026-03-16 15:14 [PATCH 00/14] nfsd/sunrpc: add support for netlink upcalls for mountd/exportd Jeff Layton
2026-03-16 15:14 ` [PATCH 01/14] nfsd: move struct nfsd_genl_rqstp to nfsctl.c Jeff Layton
@ 2026-03-16 15:14 ` Jeff Layton
2026-03-16 15:14 ` [PATCH 03/14] sunrpc: rename sunrpc_cache_pipe_upcall_timeout() Jeff Layton
` (11 subsequent siblings)
13 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-03-16 15:14 UTC (permalink / raw)
To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
Cc: Trond Myklebust, Anna Schumaker, linux-nfs, linux-kernel,
Jeff Layton
Since it will soon also send an upcall via netlink, if configured.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
fs/nfsd/export.c | 4 ++--
include/linux/sunrpc/cache.h | 2 +-
net/sunrpc/cache.c | 6 +++---
net/sunrpc/svcauth_unix.c | 2 +-
4 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/fs/nfsd/export.c b/fs/nfsd/export.c
index 7f4a51b832ef75f6ecf1b70e7ab4ca9da2e5a1dc..e83e88e69d90ab48c7aff58ac2b36cd1a6e1bb71 100644
--- a/fs/nfsd/export.c
+++ b/fs/nfsd/export.c
@@ -64,7 +64,7 @@ static void expkey_put(struct kref *ref)
static int expkey_upcall(struct cache_detail *cd, struct cache_head *h)
{
- return sunrpc_cache_pipe_upcall(cd, h);
+ return sunrpc_cache_upcall(cd, h);
}
static void expkey_request(struct cache_detail *cd,
@@ -388,7 +388,7 @@ static void svc_export_put(struct kref *ref)
static int svc_export_upcall(struct cache_detail *cd, struct cache_head *h)
{
- return sunrpc_cache_pipe_upcall(cd, h);
+ return sunrpc_cache_upcall(cd, h);
}
static void svc_export_request(struct cache_detail *cd,
diff --git a/include/linux/sunrpc/cache.h b/include/linux/sunrpc/cache.h
index b1e595c2615bd4be4d9ad19f71a8f4d08bd74a9b..981af830a0033f80090c5f6140e22e02f21ceccc 100644
--- a/include/linux/sunrpc/cache.h
+++ b/include/linux/sunrpc/cache.h
@@ -189,7 +189,7 @@ sunrpc_cache_update(struct cache_detail *detail,
struct cache_head *new, struct cache_head *old, int hash);
extern int
-sunrpc_cache_pipe_upcall(struct cache_detail *detail, struct cache_head *h);
+sunrpc_cache_upcall(struct cache_detail *detail, struct cache_head *h);
extern int
sunrpc_cache_pipe_upcall_timeout(struct cache_detail *detail,
struct cache_head *h);
diff --git a/net/sunrpc/cache.c b/net/sunrpc/cache.c
index d5941cc24303d1a80a974a4625a0b91d36da5d3f..5553a1182c9b8d0d9c8b2be9e9b1bed5bfa58057 100644
--- a/net/sunrpc/cache.c
+++ b/net/sunrpc/cache.c
@@ -1247,13 +1247,13 @@ static int cache_pipe_upcall(struct cache_detail *detail, struct cache_head *h)
return ret;
}
-int sunrpc_cache_pipe_upcall(struct cache_detail *detail, struct cache_head *h)
+int sunrpc_cache_upcall(struct cache_detail *detail, struct cache_head *h)
{
if (test_and_set_bit(CACHE_PENDING, &h->flags))
return 0;
return cache_pipe_upcall(detail, h);
}
-EXPORT_SYMBOL_GPL(sunrpc_cache_pipe_upcall);
+EXPORT_SYMBOL_GPL(sunrpc_cache_upcall);
int sunrpc_cache_pipe_upcall_timeout(struct cache_detail *detail,
struct cache_head *h)
@@ -1263,7 +1263,7 @@ int sunrpc_cache_pipe_upcall_timeout(struct cache_detail *detail,
trace_cache_entry_no_listener(detail, h);
return -EINVAL;
}
- return sunrpc_cache_pipe_upcall(detail, h);
+ return sunrpc_cache_upcall(detail, h);
}
EXPORT_SYMBOL_GPL(sunrpc_cache_pipe_upcall_timeout);
diff --git a/net/sunrpc/svcauth_unix.c b/net/sunrpc/svcauth_unix.c
index 3be69c145d2a8055acd39ff5e0faacb1084936b7..9d5e07b900e11a8f6da767557eb6a46a65a57c26 100644
--- a/net/sunrpc/svcauth_unix.c
+++ b/net/sunrpc/svcauth_unix.c
@@ -152,7 +152,7 @@ static struct cache_head *ip_map_alloc(void)
static int ip_map_upcall(struct cache_detail *cd, struct cache_head *h)
{
- return sunrpc_cache_pipe_upcall(cd, h);
+ return sunrpc_cache_upcall(cd, h);
}
static void ip_map_request(struct cache_detail *cd,
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH 03/14] sunrpc: rename sunrpc_cache_pipe_upcall_timeout()
2026-03-16 15:14 [PATCH 00/14] nfsd/sunrpc: add support for netlink upcalls for mountd/exportd Jeff Layton
2026-03-16 15:14 ` [PATCH 01/14] nfsd: move struct nfsd_genl_rqstp to nfsctl.c Jeff Layton
2026-03-16 15:14 ` [PATCH 02/14] sunrpc: rename sunrpc_cache_pipe_upcall() to sunrpc_cache_upcall() Jeff Layton
@ 2026-03-16 15:14 ` Jeff Layton
2026-03-16 15:14 ` [PATCH 04/14] sunrpc: rename cache_pipe_upcall() to cache_do_upcall() Jeff Layton
` (10 subsequent siblings)
13 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-03-16 15:14 UTC (permalink / raw)
To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
Cc: Trond Myklebust, Anna Schumaker, linux-nfs, linux-kernel,
Jeff Layton
This function doesn't have anything to do with a timeout. The only
difference is that it warns if there are no listeners. Rename it to
sunrpc_cache_upcall_warn().
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
fs/nfs/dns_resolve.c | 2 +-
fs/nfsd/nfs4idmap.c | 4 ++--
include/linux/sunrpc/cache.h | 2 +-
net/sunrpc/auth_gss/svcauth_gss.c | 2 +-
net/sunrpc/cache.c | 6 +++---
net/sunrpc/svcauth_unix.c | 2 +-
6 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/fs/nfs/dns_resolve.c b/fs/nfs/dns_resolve.c
index 2ed2126201f4185fa9d6341f661e23be08d422ea..acd3511c04a6c74301c393e05e39aa8fcd52b390 100644
--- a/fs/nfs/dns_resolve.c
+++ b/fs/nfs/dns_resolve.c
@@ -156,7 +156,7 @@ static int nfs_dns_upcall(struct cache_detail *cd,
if (!nfs_cache_upcall(cd, key->hostname))
return 0;
clear_bit(CACHE_PENDING, &ch->flags);
- return sunrpc_cache_pipe_upcall_timeout(cd, ch);
+ return sunrpc_cache_upcall_warn(cd, ch);
}
static int nfs_dns_match(struct cache_head *ca,
diff --git a/fs/nfsd/nfs4idmap.c b/fs/nfsd/nfs4idmap.c
index ba06d3d3e6dd9eeb368be42b5d1d83c05049a70c..71ba61b5d0a3aacfc196e8d3aa3c06b00fe0765b 100644
--- a/fs/nfsd/nfs4idmap.c
+++ b/fs/nfsd/nfs4idmap.c
@@ -126,7 +126,7 @@ idtoname_hash(struct ent *ent)
static int
idtoname_upcall(struct cache_detail *cd, struct cache_head *h)
{
- return sunrpc_cache_pipe_upcall_timeout(cd, h);
+ return sunrpc_cache_upcall_warn(cd, h);
}
static void
@@ -306,7 +306,7 @@ nametoid_hash(struct ent *ent)
static int
nametoid_upcall(struct cache_detail *cd, struct cache_head *h)
{
- return sunrpc_cache_pipe_upcall_timeout(cd, h);
+ return sunrpc_cache_upcall_warn(cd, h);
}
static void
diff --git a/include/linux/sunrpc/cache.h b/include/linux/sunrpc/cache.h
index 981af830a0033f80090c5f6140e22e02f21ceccc..80a3f17731d8fbc1c5252a830b202016faa41a18 100644
--- a/include/linux/sunrpc/cache.h
+++ b/include/linux/sunrpc/cache.h
@@ -191,7 +191,7 @@ sunrpc_cache_update(struct cache_detail *detail,
extern int
sunrpc_cache_upcall(struct cache_detail *detail, struct cache_head *h);
extern int
-sunrpc_cache_pipe_upcall_timeout(struct cache_detail *detail,
+sunrpc_cache_upcall_warn(struct cache_detail *detail,
struct cache_head *h);
diff --git a/net/sunrpc/auth_gss/svcauth_gss.c b/net/sunrpc/auth_gss/svcauth_gss.c
index 161d02cc1c2c97321f311815c0324fade1e703fe..d14209031e1807e4ec19de44a2829d48e81e4d6c 100644
--- a/net/sunrpc/auth_gss/svcauth_gss.c
+++ b/net/sunrpc/auth_gss/svcauth_gss.c
@@ -206,7 +206,7 @@ static struct cache_head *rsi_alloc(void)
static int rsi_upcall(struct cache_detail *cd, struct cache_head *h)
{
- return sunrpc_cache_pipe_upcall_timeout(cd, h);
+ return sunrpc_cache_upcall_warn(cd, h);
}
static void rsi_request(struct cache_detail *cd,
diff --git a/net/sunrpc/cache.c b/net/sunrpc/cache.c
index 5553a1182c9b8d0d9c8b2be9e9b1bed5bfa58057..5e36f6404400aea5700d0893c00b6d69c1ec128e 100644
--- a/net/sunrpc/cache.c
+++ b/net/sunrpc/cache.c
@@ -1255,8 +1255,8 @@ int sunrpc_cache_upcall(struct cache_detail *detail, struct cache_head *h)
}
EXPORT_SYMBOL_GPL(sunrpc_cache_upcall);
-int sunrpc_cache_pipe_upcall_timeout(struct cache_detail *detail,
- struct cache_head *h)
+int sunrpc_cache_upcall_warn(struct cache_detail *detail,
+ struct cache_head *h)
{
if (!cache_listeners_exist(detail)) {
warn_no_listener(detail);
@@ -1265,7 +1265,7 @@ int sunrpc_cache_pipe_upcall_timeout(struct cache_detail *detail,
}
return sunrpc_cache_upcall(detail, h);
}
-EXPORT_SYMBOL_GPL(sunrpc_cache_pipe_upcall_timeout);
+EXPORT_SYMBOL_GPL(sunrpc_cache_upcall_warn);
/*
* parse a message from user-space and pass it
diff --git a/net/sunrpc/svcauth_unix.c b/net/sunrpc/svcauth_unix.c
index 9d5e07b900e11a8f6da767557eb6a46a65a57c26..87732c4cb8383c64b440538a0d3f3113a3009b4e 100644
--- a/net/sunrpc/svcauth_unix.c
+++ b/net/sunrpc/svcauth_unix.c
@@ -467,7 +467,7 @@ static struct cache_head *unix_gid_alloc(void)
static int unix_gid_upcall(struct cache_detail *cd, struct cache_head *h)
{
- return sunrpc_cache_pipe_upcall_timeout(cd, h);
+ return sunrpc_cache_upcall_warn(cd, h);
}
static void unix_gid_request(struct cache_detail *cd,
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH 04/14] sunrpc: rename cache_pipe_upcall() to cache_do_upcall()
2026-03-16 15:14 [PATCH 00/14] nfsd/sunrpc: add support for netlink upcalls for mountd/exportd Jeff Layton
` (2 preceding siblings ...)
2026-03-16 15:14 ` [PATCH 03/14] sunrpc: rename sunrpc_cache_pipe_upcall_timeout() Jeff Layton
@ 2026-03-16 15:14 ` Jeff Layton
2026-03-19 13:54 ` Chuck Lever
2026-03-16 15:14 ` [PATCH 05/14] sunrpc: add a cache_notify callback Jeff Layton
` (9 subsequent siblings)
13 siblings, 1 reply; 28+ messages in thread
From: Jeff Layton @ 2026-03-16 15:14 UTC (permalink / raw)
To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
Cc: Trond Myklebust, Anna Schumaker, linux-nfs, linux-kernel,
Jeff Layton
...since it will also handle netlink upcalls.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
net/sunrpc/cache.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/net/sunrpc/cache.c b/net/sunrpc/cache.c
index 5e36f6404400aea5700d0893c00b6d69c1ec128e..7081b6e0e9090d2ba7da68c1f36b4c170fb228cb 100644
--- a/net/sunrpc/cache.c
+++ b/net/sunrpc/cache.c
@@ -1206,7 +1206,7 @@ static bool cache_listeners_exist(struct cache_detail *detail)
*
* Each request is at most one page long.
*/
-static int cache_pipe_upcall(struct cache_detail *detail, struct cache_head *h)
+static int cache_do_upcall(struct cache_detail *detail, struct cache_head *h)
{
char *buf;
struct cache_request *crq;
@@ -1251,7 +1251,7 @@ int sunrpc_cache_upcall(struct cache_detail *detail, struct cache_head *h)
{
if (test_and_set_bit(CACHE_PENDING, &h->flags))
return 0;
- return cache_pipe_upcall(detail, h);
+ return cache_do_upcall(detail, h);
}
EXPORT_SYMBOL_GPL(sunrpc_cache_upcall);
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* Re: [PATCH 04/14] sunrpc: rename cache_pipe_upcall() to cache_do_upcall()
2026-03-16 15:14 ` [PATCH 04/14] sunrpc: rename cache_pipe_upcall() to cache_do_upcall() Jeff Layton
@ 2026-03-19 13:54 ` Chuck Lever
0 siblings, 0 replies; 28+ messages in thread
From: Chuck Lever @ 2026-03-19 13:54 UTC (permalink / raw)
To: Jeff Layton, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
Cc: Trond Myklebust, Anna Schumaker, linux-nfs, linux-kernel
On 3/16/26 11:14 AM, Jeff Layton wrote:
> ...since it will also handle netlink upcalls.
>
> Signed-off-by: Jeff Layton <jlayton@kernel.org>
> ---
> net/sunrpc/cache.c | 4 ++--
> 1 file changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/net/sunrpc/cache.c b/net/sunrpc/cache.c
> index 5e36f6404400aea5700d0893c00b6d69c1ec128e..7081b6e0e9090d2ba7da68c1f36b4c170fb228cb 100644
> --- a/net/sunrpc/cache.c
> +++ b/net/sunrpc/cache.c
> @@ -1206,7 +1206,7 @@ static bool cache_listeners_exist(struct cache_detail *detail)
> *
> * Each request is at most one page long.
> */
> -static int cache_pipe_upcall(struct cache_detail *detail, struct cache_head *h)
> +static int cache_do_upcall(struct cache_detail *detail, struct cache_head *h)
> {
> char *buf;
> struct cache_request *crq;
Nit: The block comment just above still reads "queue it up for read() by
the upcall daemon", which is will be made stale (or at least a little
misleading) later in the series.
> @@ -1251,7 +1251,7 @@ int sunrpc_cache_upcall(struct cache_detail *detail, struct cache_head *h)
> {
> if (test_and_set_bit(CACHE_PENDING, &h->flags))
> return 0;
> - return cache_pipe_upcall(detail, h);
> + return cache_do_upcall(detail, h);
> }
> EXPORT_SYMBOL_GPL(sunrpc_cache_upcall);
>
>
--
Chuck Lever
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH 05/14] sunrpc: add a cache_notify callback
2026-03-16 15:14 [PATCH 00/14] nfsd/sunrpc: add support for netlink upcalls for mountd/exportd Jeff Layton
` (3 preceding siblings ...)
2026-03-16 15:14 ` [PATCH 04/14] sunrpc: rename cache_pipe_upcall() to cache_do_upcall() Jeff Layton
@ 2026-03-16 15:14 ` Jeff Layton
2026-03-19 15:13 ` Chuck Lever
2026-03-16 15:14 ` [PATCH 06/14] sunrpc: add helpers to count and snapshot pending cache requests Jeff Layton
` (8 subsequent siblings)
13 siblings, 1 reply; 28+ messages in thread
From: Jeff Layton @ 2026-03-16 15:14 UTC (permalink / raw)
To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
Cc: Trond Myklebust, Anna Schumaker, linux-nfs, linux-kernel,
Jeff Layton
A later patch will be changing the kernel to send a netlink notification
when there is a pending cache_request. Add a new cache_notify operation
to struct cache_detail for this purpose.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
include/linux/sunrpc/cache.h | 3 +++
net/sunrpc/cache.c | 3 +++
2 files changed, 6 insertions(+)
diff --git a/include/linux/sunrpc/cache.h b/include/linux/sunrpc/cache.h
index 80a3f17731d8fbc1c5252a830b202016faa41a18..c358151c23950ab48e83991c6138bb7d0e049ace 100644
--- a/include/linux/sunrpc/cache.h
+++ b/include/linux/sunrpc/cache.h
@@ -80,6 +80,9 @@ struct cache_detail {
int (*cache_upcall)(struct cache_detail *,
struct cache_head *);
+ int (*cache_notify)(struct cache_detail *cd,
+ struct cache_head *h);
+
void (*cache_request)(struct cache_detail *cd,
struct cache_head *ch,
char **bpp, int *blen);
diff --git a/net/sunrpc/cache.c b/net/sunrpc/cache.c
index 7081b6e0e9090d2ba7da68c1f36b4c170fb228cb..819f12add8f26562fdc6aaa200f55dec0180bfbc 100644
--- a/net/sunrpc/cache.c
+++ b/net/sunrpc/cache.c
@@ -33,6 +33,7 @@
#include <linux/sunrpc/cache.h>
#include <linux/sunrpc/stats.h>
#include <linux/sunrpc/rpc_pipe_fs.h>
+#include <net/genetlink.h>
#include <trace/events/sunrpc.h>
#include "netns.h"
@@ -1239,6 +1240,8 @@ static int cache_do_upcall(struct cache_detail *detail, struct cache_head *h)
/* Lost a race, no longer PENDING, so don't enqueue */
ret = -EAGAIN;
spin_unlock(&detail->queue_lock);
+ if (detail->cache_notify)
+ detail->cache_notify(detail, h);
wake_up(&detail->queue_wait);
if (ret == -EAGAIN) {
kfree(buf);
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* Re: [PATCH 05/14] sunrpc: add a cache_notify callback
2026-03-16 15:14 ` [PATCH 05/14] sunrpc: add a cache_notify callback Jeff Layton
@ 2026-03-19 15:13 ` Chuck Lever
0 siblings, 0 replies; 28+ messages in thread
From: Chuck Lever @ 2026-03-19 15:13 UTC (permalink / raw)
To: Jeff Layton, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
Cc: Trond Myklebust, Anna Schumaker, linux-nfs, linux-kernel
On 3/16/26 11:14 AM, Jeff Layton wrote:
> A later patch will be changing the kernel to send a netlink notification
> when there is a pending cache_request. Add a new cache_notify operation
> to struct cache_detail for this purpose.
>
> Signed-off-by: Jeff Layton <jlayton@kernel.org>
> ---
> include/linux/sunrpc/cache.h | 3 +++
> net/sunrpc/cache.c | 3 +++
> 2 files changed, 6 insertions(+)
>
> diff --git a/include/linux/sunrpc/cache.h b/include/linux/sunrpc/cache.h
> index 80a3f17731d8fbc1c5252a830b202016faa41a18..c358151c23950ab48e83991c6138bb7d0e049ace 100644
> --- a/include/linux/sunrpc/cache.h
> +++ b/include/linux/sunrpc/cache.h
> @@ -80,6 +80,9 @@ struct cache_detail {
> int (*cache_upcall)(struct cache_detail *,
> struct cache_head *);
>
> + int (*cache_notify)(struct cache_detail *cd,
> + struct cache_head *h);
> +
> void (*cache_request)(struct cache_detail *cd,
> struct cache_head *ch,
> char **bpp, int *blen);
> diff --git a/net/sunrpc/cache.c b/net/sunrpc/cache.c
> index 7081b6e0e9090d2ba7da68c1f36b4c170fb228cb..819f12add8f26562fdc6aaa200f55dec0180bfbc 100644
> --- a/net/sunrpc/cache.c
> +++ b/net/sunrpc/cache.c
> @@ -33,6 +33,7 @@
> #include <linux/sunrpc/cache.h>
> #include <linux/sunrpc/stats.h>
> #include <linux/sunrpc/rpc_pipe_fs.h>
> +#include <net/genetlink.h>
Nit: Adding this here might be premature. Should it be moved to a
subsequent patch in this series?
> #include <trace/events/sunrpc.h>
>
> #include "netns.h"
> @@ -1239,6 +1240,8 @@ static int cache_do_upcall(struct cache_detail *detail, struct cache_head *h)
> /* Lost a race, no longer PENDING, so don't enqueue */
> ret = -EAGAIN;
> spin_unlock(&detail->queue_lock);
> + if (detail->cache_notify)
> + detail->cache_notify(detail, h);
> wake_up(&detail->queue_wait);
> if (ret == -EAGAIN) {
> kfree(buf);
>
When ret == -EAGAIN, the CACHE_PENDING bit was already cleared by
another thread that won the race. Calling cache_notify in this case
sends a netlink notification for an entry that is no longer pending. The
winning thread already queued the request and will trigger its own
notification. If you agree this observation is not a false positive, a
possible fix might be:
spin_unlock(&detail->queue_lock);
if (ret != -EAGAIN && detail->cache_notify)
detail->cache_notify(detail, h);
wake_up(&detail->queue_wait);
--
Chuck Lever
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH 06/14] sunrpc: add helpers to count and snapshot pending cache requests
2026-03-16 15:14 [PATCH 00/14] nfsd/sunrpc: add support for netlink upcalls for mountd/exportd Jeff Layton
` (4 preceding siblings ...)
2026-03-16 15:14 ` [PATCH 05/14] sunrpc: add a cache_notify callback Jeff Layton
@ 2026-03-16 15:14 ` Jeff Layton
2026-03-19 18:07 ` Chuck Lever
2026-03-16 15:14 ` [PATCH 07/14] sunrpc: add a generic netlink family for cache upcalls Jeff Layton
` (7 subsequent siblings)
13 siblings, 1 reply; 28+ messages in thread
From: Jeff Layton @ 2026-03-16 15:14 UTC (permalink / raw)
To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
Cc: Trond Myklebust, Anna Schumaker, linux-nfs, linux-kernel,
Jeff Layton
Add sunrpc_cache_requests_count() and sunrpc_cache_requests_snapshot()
to allow callers to count and snapshot the pending upcall request list
without exposing struct cache_request outside of cache.c.
Both functions skip entries that no longer have CACHE_PENDING set.
The snapshot function takes a cache_get() reference on each item so the
caller can safely use them after the queue_lock is released.
These will be used by the nfsd generic netlink dumpit handler for
svc_export upcall requests.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
include/linux/sunrpc/cache.h | 5 ++++
net/sunrpc/cache.c | 57 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 62 insertions(+)
diff --git a/include/linux/sunrpc/cache.h b/include/linux/sunrpc/cache.h
index c358151c23950ab48e83991c6138bb7d0e049ace..343f0fb675d761e019989e47e03645484e0aa30f 100644
--- a/include/linux/sunrpc/cache.h
+++ b/include/linux/sunrpc/cache.h
@@ -251,6 +251,11 @@ extern int sunrpc_cache_register_pipefs(struct dentry *parent, const char *,
extern void sunrpc_cache_unregister_pipefs(struct cache_detail *);
extern void sunrpc_cache_unhash(struct cache_detail *, struct cache_head *);
+int sunrpc_cache_requests_count(struct cache_detail *cd);
+int sunrpc_cache_requests_snapshot(struct cache_detail *cd,
+ struct cache_head **items,
+ u64 *seqnos, int max);
+
/* Must store cache_detail in seq_file->private if using next three functions */
extern void *cache_seq_start_rcu(struct seq_file *file, loff_t *pos);
extern void *cache_seq_next_rcu(struct seq_file *file, void *p, loff_t *pos);
diff --git a/net/sunrpc/cache.c b/net/sunrpc/cache.c
index 819f12add8f26562fdc6aaa200f55dec0180bfbc..2a78560edee5ca07be0fce90f87ce43a19df245f 100644
--- a/net/sunrpc/cache.c
+++ b/net/sunrpc/cache.c
@@ -1906,3 +1906,60 @@ void sunrpc_cache_unhash(struct cache_detail *cd, struct cache_head *h)
spin_unlock(&cd->hash_lock);
}
EXPORT_SYMBOL_GPL(sunrpc_cache_unhash);
+
+/**
+ * sunrpc_cache_requests_count - count pending upcall requests
+ * @cd: cache_detail to query
+ *
+ * Returns the number of requests on the cache's request list that
+ * still have CACHE_PENDING set.
+ */
+int sunrpc_cache_requests_count(struct cache_detail *cd)
+{
+ struct cache_request *crq;
+ int cnt = 0;
+
+ spin_lock(&cd->queue_lock);
+ list_for_each_entry(crq, &cd->requests, list) {
+ if (test_bit(CACHE_PENDING, &crq->item->flags))
+ cnt++;
+ }
+ spin_unlock(&cd->queue_lock);
+ return cnt;
+}
+EXPORT_SYMBOL_GPL(sunrpc_cache_requests_count);
+
+/**
+ * sunrpc_cache_requests_snapshot - snapshot pending upcall requests
+ * @cd: cache_detail to query
+ * @items: array to fill with cache_head pointers (caller-allocated)
+ * @seqnos: array to fill with sequence numbers (caller-allocated)
+ * @max: size of the arrays
+ *
+ * Only entries with CACHE_PENDING set are included. Takes a reference
+ * on each cache_head via cache_get(). Caller must call cache_put()
+ * on each returned item when done.
+ *
+ * Returns the number of entries filled.
+ */
+int sunrpc_cache_requests_snapshot(struct cache_detail *cd,
+ struct cache_head **items,
+ u64 *seqnos, int max)
+{
+ struct cache_request *crq;
+ int i = 0;
+
+ spin_lock(&cd->queue_lock);
+ list_for_each_entry(crq, &cd->requests, list) {
+ if (i >= max)
+ break;
+ if (!test_bit(CACHE_PENDING, &crq->item->flags))
+ continue;
+ items[i] = cache_get(crq->item);
+ seqnos[i] = crq->seqno;
+ i++;
+ }
+ spin_unlock(&cd->queue_lock);
+ return i;
+}
+EXPORT_SYMBOL_GPL(sunrpc_cache_requests_snapshot);
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* Re: [PATCH 06/14] sunrpc: add helpers to count and snapshot pending cache requests
2026-03-16 15:14 ` [PATCH 06/14] sunrpc: add helpers to count and snapshot pending cache requests Jeff Layton
@ 2026-03-19 18:07 ` Chuck Lever
2026-03-19 18:22 ` Jeff Layton
0 siblings, 1 reply; 28+ messages in thread
From: Chuck Lever @ 2026-03-19 18:07 UTC (permalink / raw)
To: Jeff Layton, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
Cc: Trond Myklebust, Anna Schumaker, linux-nfs, linux-kernel
On 3/16/26 11:14 AM, Jeff Layton wrote:
> Add sunrpc_cache_requests_count() and sunrpc_cache_requests_snapshot()
> to allow callers to count and snapshot the pending upcall request list
> without exposing struct cache_request outside of cache.c.
>
> Both functions skip entries that no longer have CACHE_PENDING set.
>
> The snapshot function takes a cache_get() reference on each item so the
> caller can safely use them after the queue_lock is released.
>
> These will be used by the nfsd generic netlink dumpit handler for
> svc_export upcall requests.
>
> Signed-off-by: Jeff Layton <jlayton@kernel.org>
> ---
> include/linux/sunrpc/cache.h | 5 ++++
> net/sunrpc/cache.c | 57 ++++++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 62 insertions(+)
>
> diff --git a/include/linux/sunrpc/cache.h b/include/linux/sunrpc/cache.h
> index c358151c23950ab48e83991c6138bb7d0e049ace..343f0fb675d761e019989e47e03645484e0aa30f 100644
> --- a/include/linux/sunrpc/cache.h
> +++ b/include/linux/sunrpc/cache.h
> @@ -251,6 +251,11 @@ extern int sunrpc_cache_register_pipefs(struct dentry *parent, const char *,
> extern void sunrpc_cache_unregister_pipefs(struct cache_detail *);
> extern void sunrpc_cache_unhash(struct cache_detail *, struct cache_head *);
>
> +int sunrpc_cache_requests_count(struct cache_detail *cd);
> +int sunrpc_cache_requests_snapshot(struct cache_detail *cd,
> + struct cache_head **items,
> + u64 *seqnos, int max);
> +
> /* Must store cache_detail in seq_file->private if using next three functions */
> extern void *cache_seq_start_rcu(struct seq_file *file, loff_t *pos);
> extern void *cache_seq_next_rcu(struct seq_file *file, void *p, loff_t *pos);
> diff --git a/net/sunrpc/cache.c b/net/sunrpc/cache.c
> index 819f12add8f26562fdc6aaa200f55dec0180bfbc..2a78560edee5ca07be0fce90f87ce43a19df245f 100644
> --- a/net/sunrpc/cache.c
> +++ b/net/sunrpc/cache.c
> @@ -1906,3 +1906,60 @@ void sunrpc_cache_unhash(struct cache_detail *cd, struct cache_head *h)
> spin_unlock(&cd->hash_lock);
> }
> EXPORT_SYMBOL_GPL(sunrpc_cache_unhash);
> +
> +/**
> + * sunrpc_cache_requests_count - count pending upcall requests
> + * @cd: cache_detail to query
> + *
> + * Returns the number of requests on the cache's request list that
> + * still have CACHE_PENDING set.
> + */
> +int sunrpc_cache_requests_count(struct cache_detail *cd)
> +{
> + struct cache_request *crq;
> + int cnt = 0;
> +
> + spin_lock(&cd->queue_lock);
> + list_for_each_entry(crq, &cd->requests, list) {
> + if (test_bit(CACHE_PENDING, &crq->item->flags))
> + cnt++;
> + }
> + spin_unlock(&cd->queue_lock);
> + return cnt;
> +}
> +EXPORT_SYMBOL_GPL(sunrpc_cache_requests_count);
> +
> +/**
> + * sunrpc_cache_requests_snapshot - snapshot pending upcall requests
> + * @cd: cache_detail to query
> + * @items: array to fill with cache_head pointers (caller-allocated)
> + * @seqnos: array to fill with sequence numbers (caller-allocated)
> + * @max: size of the arrays
> + *
> + * Only entries with CACHE_PENDING set are included. Takes a reference
> + * on each cache_head via cache_get(). Caller must call cache_put()
> + * on each returned item when done.
> + *
> + * Returns the number of entries filled.
> + */
> +int sunrpc_cache_requests_snapshot(struct cache_detail *cd,
> + struct cache_head **items,
> + u64 *seqnos, int max)
> +{
> + struct cache_request *crq;
> + int i = 0;
> +
> + spin_lock(&cd->queue_lock);
> + list_for_each_entry(crq, &cd->requests, list) {
> + if (i >= max)
> + break;
> + if (!test_bit(CACHE_PENDING, &crq->item->flags))
> + continue;
> + items[i] = cache_get(crq->item);
> + seqnos[i] = crq->seqno;
> + i++;
> + }
> + spin_unlock(&cd->queue_lock);
> + return i;
> +}
> +EXPORT_SYMBOL_GPL(sunrpc_cache_requests_snapshot);
>
This API architecture introduces a TOCTOU, since as soon as the
queue_lock is dropped, the count can immediately become stale.
The count returned by sunrpc_cache_requests_count() is used as the array
bound. To wit:
cnt = sunrpc_cache_requests_count(cd);
items = kcalloc(cnt, sizeof(*items), GFP_KERNEL);
seqnos = kcalloc(cnt, sizeof(*seqnos), GFP_KERNEL);
cnt = sunrpc_cache_requests_snapshot(cd, items, seqnos, cnt);
This appears in all four dumpit handlers (ip_map, unix_gid, svc_export,
expkey).
This isn't a memory safety issue; _snapshot() caps its output at max, so
if the list grows between the two calls, entries are silently truncated
rather than overflowing the buffer. If the list shrinks, the arrays are
merely oversized.
However, the practical risk is incomplete data returned to userspace. If
the caller is guaranteed to be re-invoked (e.g., the netlink dumpit
callback gets called again until it returns 0), then missing items due
to list growth between count() and snapshot() is harmless: they'll be
picked up on the next pass.
But looking at the callers, they all do this:
if (cb->args[0])
return 0;
and then set cb->args[0] after the single snapshot dump.
The dumpit is a one-shot: it snapshots once and signals completion. If
the list grows between count() and snapshot(), the truncated entries are
silently lost and there's no subsequent pass to pick them up, IIUC.
--
Chuck Lever
^ permalink raw reply [flat|nested] 28+ messages in thread* Re: [PATCH 06/14] sunrpc: add helpers to count and snapshot pending cache requests
2026-03-19 18:07 ` Chuck Lever
@ 2026-03-19 18:22 ` Jeff Layton
2026-03-19 18:47 ` Chuck Lever
0 siblings, 1 reply; 28+ messages in thread
From: Jeff Layton @ 2026-03-19 18:22 UTC (permalink / raw)
To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
Cc: Trond Myklebust, Anna Schumaker, linux-nfs, linux-kernel
On Thu, 2026-03-19 at 14:07 -0400, Chuck Lever wrote:
> On 3/16/26 11:14 AM, Jeff Layton wrote:
> > Add sunrpc_cache_requests_count() and sunrpc_cache_requests_snapshot()
> > to allow callers to count and snapshot the pending upcall request list
> > without exposing struct cache_request outside of cache.c.
> >
> > Both functions skip entries that no longer have CACHE_PENDING set.
> >
> > The snapshot function takes a cache_get() reference on each item so the
> > caller can safely use them after the queue_lock is released.
> >
> > These will be used by the nfsd generic netlink dumpit handler for
> > svc_export upcall requests.
> >
> > Signed-off-by: Jeff Layton <jlayton@kernel.org>
> > ---
> > include/linux/sunrpc/cache.h | 5 ++++
> > net/sunrpc/cache.c | 57 ++++++++++++++++++++++++++++++++++++++++++++
> > 2 files changed, 62 insertions(+)
> >
> > diff --git a/include/linux/sunrpc/cache.h b/include/linux/sunrpc/cache.h
> > index c358151c23950ab48e83991c6138bb7d0e049ace..343f0fb675d761e019989e47e03645484e0aa30f 100644
> > --- a/include/linux/sunrpc/cache.h
> > +++ b/include/linux/sunrpc/cache.h
> > @@ -251,6 +251,11 @@ extern int sunrpc_cache_register_pipefs(struct dentry *parent, const char *,
> > extern void sunrpc_cache_unregister_pipefs(struct cache_detail *);
> > extern void sunrpc_cache_unhash(struct cache_detail *, struct cache_head *);
> >
> > +int sunrpc_cache_requests_count(struct cache_detail *cd);
> > +int sunrpc_cache_requests_snapshot(struct cache_detail *cd,
> > + struct cache_head **items,
> > + u64 *seqnos, int max);
> > +
> > /* Must store cache_detail in seq_file->private if using next three functions */
> > extern void *cache_seq_start_rcu(struct seq_file *file, loff_t *pos);
> > extern void *cache_seq_next_rcu(struct seq_file *file, void *p, loff_t *pos);
> > diff --git a/net/sunrpc/cache.c b/net/sunrpc/cache.c
> > index 819f12add8f26562fdc6aaa200f55dec0180bfbc..2a78560edee5ca07be0fce90f87ce43a19df245f 100644
> > --- a/net/sunrpc/cache.c
> > +++ b/net/sunrpc/cache.c
> > @@ -1906,3 +1906,60 @@ void sunrpc_cache_unhash(struct cache_detail *cd, struct cache_head *h)
> > spin_unlock(&cd->hash_lock);
> > }
> > EXPORT_SYMBOL_GPL(sunrpc_cache_unhash);
> > +
> > +/**
> > + * sunrpc_cache_requests_count - count pending upcall requests
> > + * @cd: cache_detail to query
> > + *
> > + * Returns the number of requests on the cache's request list that
> > + * still have CACHE_PENDING set.
> > + */
> > +int sunrpc_cache_requests_count(struct cache_detail *cd)
> > +{
> > + struct cache_request *crq;
> > + int cnt = 0;
> > +
> > + spin_lock(&cd->queue_lock);
> > + list_for_each_entry(crq, &cd->requests, list) {
> > + if (test_bit(CACHE_PENDING, &crq->item->flags))
> > + cnt++;
> > + }
> > + spin_unlock(&cd->queue_lock);
> > + return cnt;
> > +}
> > +EXPORT_SYMBOL_GPL(sunrpc_cache_requests_count);
> > +
> > +/**
> > + * sunrpc_cache_requests_snapshot - snapshot pending upcall requests
> > + * @cd: cache_detail to query
> > + * @items: array to fill with cache_head pointers (caller-allocated)
> > + * @seqnos: array to fill with sequence numbers (caller-allocated)
> > + * @max: size of the arrays
> > + *
> > + * Only entries with CACHE_PENDING set are included. Takes a reference
> > + * on each cache_head via cache_get(). Caller must call cache_put()
> > + * on each returned item when done.
> > + *
> > + * Returns the number of entries filled.
> > + */
> > +int sunrpc_cache_requests_snapshot(struct cache_detail *cd,
> > + struct cache_head **items,
> > + u64 *seqnos, int max)
> > +{
> > + struct cache_request *crq;
> > + int i = 0;
> > +
> > + spin_lock(&cd->queue_lock);
> > + list_for_each_entry(crq, &cd->requests, list) {
> > + if (i >= max)
> > + break;
> > + if (!test_bit(CACHE_PENDING, &crq->item->flags))
> > + continue;
> > + items[i] = cache_get(crq->item);
> > + seqnos[i] = crq->seqno;
> > + i++;
> > + }
> > + spin_unlock(&cd->queue_lock);
> > + return i;
> > +}
> > +EXPORT_SYMBOL_GPL(sunrpc_cache_requests_snapshot);
> >
>
> This API architecture introduces a TOCTOU, since as soon as the
> queue_lock is dropped, the count can immediately become stale.
>
> The count returned by sunrpc_cache_requests_count() is used as the array
> bound. To wit:
>
> cnt = sunrpc_cache_requests_count(cd);
>
> items = kcalloc(cnt, sizeof(*items), GFP_KERNEL);
>
> seqnos = kcalloc(cnt, sizeof(*seqnos), GFP_KERNEL);
>
> cnt = sunrpc_cache_requests_snapshot(cd, items, seqnos, cnt);
>
>
>
> This appears in all four dumpit handlers (ip_map, unix_gid, svc_export,
> expkey).
>
> This isn't a memory safety issue; _snapshot() caps its output at max, so
> if the list grows between the two calls, entries are silently truncated
> rather than overflowing the buffer. If the list shrinks, the arrays are
> merely oversized.
>
> However, the practical risk is incomplete data returned to userspace. If
> the caller is guaranteed to be re-invoked (e.g., the netlink dumpit
> callback gets called again until it returns 0), then missing items due
> to list growth between count() and snapshot() is harmless: they'll be
> picked up on the next pass.
>
> But looking at the callers, they all do this:
>
> if (cb->args[0])
> return 0;
>
> and then set cb->args[0] after the single snapshot dump.
>
> The dumpit is a one-shot: it snapshots once and signals completion. If
> the list grows between count() and snapshot(), the truncated entries are
> silently lost and there's no subsequent pass to pick them up, IIUC.
>
Userland will receive a separate notify request whenever a
cache_request is queued, and that notification is only sent after the
cache_request is queued.
So while it might not receive all of the queued requests from the
kernel in the race you describe, it won't matter because select() will
return the notify descriptor again on the next pass and mountd will
pick up the remaining entries at that point.
--
Jeff Layton <jlayton@kernel.org>
^ permalink raw reply [flat|nested] 28+ messages in thread* Re: [PATCH 06/14] sunrpc: add helpers to count and snapshot pending cache requests
2026-03-19 18:22 ` Jeff Layton
@ 2026-03-19 18:47 ` Chuck Lever
0 siblings, 0 replies; 28+ messages in thread
From: Chuck Lever @ 2026-03-19 18:47 UTC (permalink / raw)
To: Jeff Layton, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
Cc: Trond Myklebust, Anna Schumaker, linux-nfs, linux-kernel
On 3/19/26 2:22 PM, Jeff Layton wrote:
> On Thu, 2026-03-19 at 14:07 -0400, Chuck Lever wrote:
>> On 3/16/26 11:14 AM, Jeff Layton wrote:
>>> Add sunrpc_cache_requests_count() and sunrpc_cache_requests_snapshot()
>>> to allow callers to count and snapshot the pending upcall request list
>>> without exposing struct cache_request outside of cache.c.
>>>
>>> Both functions skip entries that no longer have CACHE_PENDING set.
>>>
>>> The snapshot function takes a cache_get() reference on each item so the
>>> caller can safely use them after the queue_lock is released.
>>>
>>> These will be used by the nfsd generic netlink dumpit handler for
>>> svc_export upcall requests.
>>>
>>> Signed-off-by: Jeff Layton <jlayton@kernel.org>
>>> ---
>>> include/linux/sunrpc/cache.h | 5 ++++
>>> net/sunrpc/cache.c | 57 ++++++++++++++++++++++++++++++++++++++++++++
>>> 2 files changed, 62 insertions(+)
>>>
>>> diff --git a/include/linux/sunrpc/cache.h b/include/linux/sunrpc/cache.h
>>> index c358151c23950ab48e83991c6138bb7d0e049ace..343f0fb675d761e019989e47e03645484e0aa30f 100644
>>> --- a/include/linux/sunrpc/cache.h
>>> +++ b/include/linux/sunrpc/cache.h
>>> @@ -251,6 +251,11 @@ extern int sunrpc_cache_register_pipefs(struct dentry *parent, const char *,
>>> extern void sunrpc_cache_unregister_pipefs(struct cache_detail *);
>>> extern void sunrpc_cache_unhash(struct cache_detail *, struct cache_head *);
>>>
>>> +int sunrpc_cache_requests_count(struct cache_detail *cd);
>>> +int sunrpc_cache_requests_snapshot(struct cache_detail *cd,
>>> + struct cache_head **items,
>>> + u64 *seqnos, int max);
>>> +
>>> /* Must store cache_detail in seq_file->private if using next three functions */
>>> extern void *cache_seq_start_rcu(struct seq_file *file, loff_t *pos);
>>> extern void *cache_seq_next_rcu(struct seq_file *file, void *p, loff_t *pos);
>>> diff --git a/net/sunrpc/cache.c b/net/sunrpc/cache.c
>>> index 819f12add8f26562fdc6aaa200f55dec0180bfbc..2a78560edee5ca07be0fce90f87ce43a19df245f 100644
>>> --- a/net/sunrpc/cache.c
>>> +++ b/net/sunrpc/cache.c
>>> @@ -1906,3 +1906,60 @@ void sunrpc_cache_unhash(struct cache_detail *cd, struct cache_head *h)
>>> spin_unlock(&cd->hash_lock);
>>> }
>>> EXPORT_SYMBOL_GPL(sunrpc_cache_unhash);
>>> +
>>> +/**
>>> + * sunrpc_cache_requests_count - count pending upcall requests
>>> + * @cd: cache_detail to query
>>> + *
>>> + * Returns the number of requests on the cache's request list that
>>> + * still have CACHE_PENDING set.
>>> + */
>>> +int sunrpc_cache_requests_count(struct cache_detail *cd)
>>> +{
>>> + struct cache_request *crq;
>>> + int cnt = 0;
>>> +
>>> + spin_lock(&cd->queue_lock);
>>> + list_for_each_entry(crq, &cd->requests, list) {
>>> + if (test_bit(CACHE_PENDING, &crq->item->flags))
>>> + cnt++;
>>> + }
>>> + spin_unlock(&cd->queue_lock);
>>> + return cnt;
>>> +}
>>> +EXPORT_SYMBOL_GPL(sunrpc_cache_requests_count);
>>> +
>>> +/**
>>> + * sunrpc_cache_requests_snapshot - snapshot pending upcall requests
>>> + * @cd: cache_detail to query
>>> + * @items: array to fill with cache_head pointers (caller-allocated)
>>> + * @seqnos: array to fill with sequence numbers (caller-allocated)
>>> + * @max: size of the arrays
>>> + *
>>> + * Only entries with CACHE_PENDING set are included. Takes a reference
>>> + * on each cache_head via cache_get(). Caller must call cache_put()
>>> + * on each returned item when done.
>>> + *
>>> + * Returns the number of entries filled.
>>> + */
>>> +int sunrpc_cache_requests_snapshot(struct cache_detail *cd,
>>> + struct cache_head **items,
>>> + u64 *seqnos, int max)
>>> +{
>>> + struct cache_request *crq;
>>> + int i = 0;
>>> +
>>> + spin_lock(&cd->queue_lock);
>>> + list_for_each_entry(crq, &cd->requests, list) {
>>> + if (i >= max)
>>> + break;
>>> + if (!test_bit(CACHE_PENDING, &crq->item->flags))
>>> + continue;
>>> + items[i] = cache_get(crq->item);
>>> + seqnos[i] = crq->seqno;
>>> + i++;
>>> + }
>>> + spin_unlock(&cd->queue_lock);
>>> + return i;
>>> +}
>>> +EXPORT_SYMBOL_GPL(sunrpc_cache_requests_snapshot);
>>>
>>
>> This API architecture introduces a TOCTOU, since as soon as the
>> queue_lock is dropped, the count can immediately become stale.
>>
>> The count returned by sunrpc_cache_requests_count() is used as the array
>> bound. To wit:
>>
>> cnt = sunrpc_cache_requests_count(cd);
>>
>> items = kcalloc(cnt, sizeof(*items), GFP_KERNEL);
>>
>> seqnos = kcalloc(cnt, sizeof(*seqnos), GFP_KERNEL);
>>
>> cnt = sunrpc_cache_requests_snapshot(cd, items, seqnos, cnt);
>>
>>
>>
>> This appears in all four dumpit handlers (ip_map, unix_gid, svc_export,
>> expkey).
>>
>> This isn't a memory safety issue; _snapshot() caps its output at max, so
>> if the list grows between the two calls, entries are silently truncated
>> rather than overflowing the buffer. If the list shrinks, the arrays are
>> merely oversized.
>>
>> However, the practical risk is incomplete data returned to userspace. If
>> the caller is guaranteed to be re-invoked (e.g., the netlink dumpit
>> callback gets called again until it returns 0), then missing items due
>> to list growth between count() and snapshot() is harmless: they'll be
>> picked up on the next pass.
>>
>> But looking at the callers, they all do this:
>>
>> if (cb->args[0])
>> return 0;
>>
>> and then set cb->args[0] after the single snapshot dump.
>>
>> The dumpit is a one-shot: it snapshots once and signals completion. If
>> the list grows between count() and snapshot(), the truncated entries are
>> silently lost and there's no subsequent pass to pick them up, IIUC.
>>
>
> Userland will receive a separate notify request whenever a
> cache_request is queued, and that notification is only sent after the
> cache_request is queued.
>
> So while it might not receive all of the queued requests from the
> kernel in the race you describe, it won't matter because select() will
> return the notify descriptor again on the next pass and mountd will
> pick up the remaining entries at that point.
Makes sense. But perhaps should be documented somewhere; the "retry"
loop is not obvious from the source code introduced here.
"Caller is expected to ..." or some such language.
--
Chuck Lever
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH 07/14] sunrpc: add a generic netlink family for cache upcalls
2026-03-16 15:14 [PATCH 00/14] nfsd/sunrpc: add support for netlink upcalls for mountd/exportd Jeff Layton
` (5 preceding siblings ...)
2026-03-16 15:14 ` [PATCH 06/14] sunrpc: add helpers to count and snapshot pending cache requests Jeff Layton
@ 2026-03-16 15:14 ` Jeff Layton
2026-03-19 18:44 ` Chuck Lever
2026-03-19 19:14 ` Chuck Lever
2026-03-16 15:14 ` [PATCH 08/14] sunrpc: add netlink upcall for the auth.unix.ip cache Jeff Layton
` (6 subsequent siblings)
13 siblings, 2 replies; 28+ messages in thread
From: Jeff Layton @ 2026-03-16 15:14 UTC (permalink / raw)
To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
Cc: Trond Myklebust, Anna Schumaker, linux-nfs, linux-kernel,
Jeff Layton
The auth.unix.ip and auth.unix.gid caches live in the sunrpc module,
so they cannot use the nfsd generic netlink family. Create a new
"sunrpc" generic netlink family with its own "exportd" multicast
group to support cache upcall notifications for sunrpc-resident
caches.
Define a YAML spec (sunrpc_cache.yaml) with a cache-type enum
(ip_map, unix_gid), a cache-notify multicast event, and the
corresponding uapi header.
Implement sunrpc_cache_notify() which mirrors the nfsd_cache_notify()
pattern: check for listeners on the exportd multicast group, build
and send SUNRPC_CMD_CACHE_NOTIFY with the cache-type attribute.
Register/unregister the sunrpc_nl_family in init_sunrpc() and
cleanup_sunrpc().
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
Documentation/netlink/specs/sunrpc_cache.yaml | 40 ++++++++++++++++
fs/nfsd/netlink.c | 2 +-
include/uapi/linux/sunrpc_netlink.h | 35 ++++++++++++++
net/sunrpc/Makefile | 2 +-
net/sunrpc/netlink.c | 66 +++++++++++++++++++++++++++
net/sunrpc/netlink.h | 25 ++++++++++
net/sunrpc/sunrpc_syms.c | 10 ++++
7 files changed, 178 insertions(+), 2 deletions(-)
diff --git a/Documentation/netlink/specs/sunrpc_cache.yaml b/Documentation/netlink/specs/sunrpc_cache.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..f4aa699598bca9ce0215bbc418d9ddcae25c0110
--- /dev/null
+++ b/Documentation/netlink/specs/sunrpc_cache.yaml
@@ -0,0 +1,40 @@
+# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
+---
+name: sunrpc
+protocol: genetlink
+uapi-header: linux/sunrpc_netlink.h
+
+doc: SUNRPC cache upcall support over generic netlink.
+
+definitions:
+ -
+ type: flags
+ name: cache-type
+ entries: [ip_map, unix_gid]
+
+attribute-sets:
+ -
+ name: cache-notify
+ attributes:
+ -
+ name: cache-type
+ type: u32
+ enum: cache-type
+
+operations:
+ list:
+ -
+ name: cache-notify
+ doc: Notification that there are cache requests that need servicing
+ attribute-set: cache-notify
+ mcgrp: exportd
+ event:
+ attributes:
+ - cache-type
+
+mcast-groups:
+ list:
+ -
+ name: none
+ -
+ name: exportd
diff --git a/fs/nfsd/netlink.c b/fs/nfsd/netlink.c
index 4e08c1a6b3943cda5b44c2b64bcf3a00173a08db..81c943345d13db849483bf0d6773458115ff0134 100644
--- a/fs/nfsd/netlink.c
+++ b/fs/nfsd/netlink.c
@@ -59,7 +59,7 @@ static const struct genl_split_ops nfsd_nl_ops[] = {
.cmd = NFSD_CMD_THREADS_SET,
.doit = nfsd_nl_threads_set_doit,
.policy = nfsd_threads_set_nl_policy,
- .maxattr = NFSD_A_SERVER_MAX,
+ .maxattr = NFSD_A_SERVER_FH_KEY,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
},
{
diff --git a/include/uapi/linux/sunrpc_netlink.h b/include/uapi/linux/sunrpc_netlink.h
new file mode 100644
index 0000000000000000000000000000000000000000..6135d9b3eef155a9192d9710c8c690283ec49073
--- /dev/null
+++ b/include/uapi/linux/sunrpc_netlink.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
+/* Do not edit directly, auto-generated from: */
+/* Documentation/netlink/specs/sunrpc_cache.yaml */
+/* YNL-GEN uapi header */
+/* To regenerate run: tools/net/ynl/ynl-regen.sh */
+
+#ifndef _UAPI_LINUX_SUNRPC_NETLINK_H
+#define _UAPI_LINUX_SUNRPC_NETLINK_H
+
+#define SUNRPC_FAMILY_NAME "sunrpc"
+#define SUNRPC_FAMILY_VERSION 1
+
+enum sunrpc_cache_type {
+ SUNRPC_CACHE_TYPE_IP_MAP = 1,
+ SUNRPC_CACHE_TYPE_UNIX_GID = 2,
+};
+
+enum {
+ SUNRPC_A_CACHE_NOTIFY_CACHE_TYPE = 1,
+
+ __SUNRPC_A_CACHE_NOTIFY_MAX,
+ SUNRPC_A_CACHE_NOTIFY_MAX = (__SUNRPC_A_CACHE_NOTIFY_MAX - 1)
+};
+
+enum {
+ SUNRPC_CMD_CACHE_NOTIFY = 1,
+
+ __SUNRPC_CMD_MAX,
+ SUNRPC_CMD_MAX = (__SUNRPC_CMD_MAX - 1)
+};
+
+#define SUNRPC_MCGRP_NONE "none"
+#define SUNRPC_MCGRP_EXPORTD "exportd"
+
+#endif /* _UAPI_LINUX_SUNRPC_NETLINK_H */
diff --git a/net/sunrpc/Makefile b/net/sunrpc/Makefile
index f89c10fe7e6acc71d47273200d85425a2891a08a..96727df3aa85435a2de63a8483eab9d75d5b3495 100644
--- a/net/sunrpc/Makefile
+++ b/net/sunrpc/Makefile
@@ -14,7 +14,7 @@ sunrpc-y := clnt.o xprt.o socklib.o xprtsock.o sched.o \
addr.o rpcb_clnt.o timer.o xdr.o \
sunrpc_syms.o cache.o rpc_pipe.o sysfs.o \
svc_xprt.o \
- xprtmultipath.o
+ xprtmultipath.o netlink.o
sunrpc-$(CONFIG_SUNRPC_DEBUG) += debugfs.o
sunrpc-$(CONFIG_SUNRPC_BACKCHANNEL) += backchannel_rqst.o
sunrpc-$(CONFIG_PROC_FS) += stats.o
diff --git a/net/sunrpc/netlink.c b/net/sunrpc/netlink.c
new file mode 100644
index 0000000000000000000000000000000000000000..e59ee82dfab358fc367045d9f04c394000c812ec
--- /dev/null
+++ b/net/sunrpc/netlink.c
@@ -0,0 +1,66 @@
+// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
+/* Do not edit directly, auto-generated from: */
+/* Documentation/netlink/specs/sunrpc_cache.yaml */
+/* YNL-GEN kernel source */
+/* To regenerate run: tools/net/ynl/ynl-regen.sh */
+
+#include <net/netlink.h>
+#include <net/genetlink.h>
+#include <linux/sunrpc/cache.h>
+
+#include "netlink.h"
+
+#include <uapi/linux/sunrpc_netlink.h>
+
+/* Ops table for sunrpc */
+static const struct genl_split_ops sunrpc_nl_ops[] = {
+};
+
+static const struct genl_multicast_group sunrpc_nl_mcgrps[] = {
+ [SUNRPC_NLGRP_NONE] = { "none", },
+ [SUNRPC_NLGRP_EXPORTD] = { "exportd", },
+};
+
+struct genl_family sunrpc_nl_family __ro_after_init = {
+ .name = SUNRPC_FAMILY_NAME,
+ .version = SUNRPC_FAMILY_VERSION,
+ .netnsok = true,
+ .parallel_ops = true,
+ .module = THIS_MODULE,
+ .split_ops = sunrpc_nl_ops,
+ .n_split_ops = ARRAY_SIZE(sunrpc_nl_ops),
+ .mcgrps = sunrpc_nl_mcgrps,
+ .n_mcgrps = ARRAY_SIZE(sunrpc_nl_mcgrps),
+};
+
+int sunrpc_cache_notify(struct cache_detail *cd, struct cache_head *h,
+ u32 cache_type)
+{
+ struct genlmsghdr *hdr;
+ struct sk_buff *msg;
+
+ if (!genl_has_listeners(&sunrpc_nl_family, cd->net,
+ SUNRPC_NLGRP_EXPORTD))
+ return -ENOLINK;
+
+ msg = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+
+ hdr = genlmsg_put(msg, 0, 0, &sunrpc_nl_family, 0,
+ SUNRPC_CMD_CACHE_NOTIFY);
+ if (!hdr) {
+ nlmsg_free(msg);
+ return -ENOMEM;
+ }
+
+ if (nla_put_u32(msg, SUNRPC_A_CACHE_NOTIFY_CACHE_TYPE, cache_type)) {
+ nlmsg_free(msg);
+ return -ENOMEM;
+ }
+
+ genlmsg_end(msg, hdr);
+ return genlmsg_multicast_netns(&sunrpc_nl_family, cd->net, msg, 0,
+ SUNRPC_NLGRP_EXPORTD, GFP_KERNEL);
+}
+EXPORT_SYMBOL_GPL(sunrpc_cache_notify);
diff --git a/net/sunrpc/netlink.h b/net/sunrpc/netlink.h
new file mode 100644
index 0000000000000000000000000000000000000000..efb05f87b89513fe738964a1b27637a09f9b88a9
--- /dev/null
+++ b/net/sunrpc/netlink.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
+/* Do not edit directly, auto-generated from: */
+/* Documentation/netlink/specs/sunrpc_cache.yaml */
+/* YNL-GEN kernel header */
+/* To regenerate run: tools/net/ynl/ynl-regen.sh */
+
+#ifndef _LINUX_SUNRPC_GEN_H
+#define _LINUX_SUNRPC_GEN_H
+
+#include <net/netlink.h>
+#include <net/genetlink.h>
+
+#include <uapi/linux/sunrpc_netlink.h>
+
+enum {
+ SUNRPC_NLGRP_NONE,
+ SUNRPC_NLGRP_EXPORTD,
+};
+
+extern struct genl_family sunrpc_nl_family;
+
+int sunrpc_cache_notify(struct cache_detail *cd, struct cache_head *h,
+ u32 cache_type);
+
+#endif /* _LINUX_SUNRPC_GEN_H */
diff --git a/net/sunrpc/sunrpc_syms.c b/net/sunrpc/sunrpc_syms.c
index bab6cab2940524a970422b62b3fa4212c61c4f43..ab88ce46afb556cb0a397fe5c9df3901813ad01e 100644
--- a/net/sunrpc/sunrpc_syms.c
+++ b/net/sunrpc/sunrpc_syms.c
@@ -23,9 +23,12 @@
#include <linux/sunrpc/rpc_pipe_fs.h>
#include <linux/sunrpc/xprtsock.h>
+#include <net/genetlink.h>
+
#include "sunrpc.h"
#include "sysfs.h"
#include "netns.h"
+#include "netlink.h"
unsigned int sunrpc_net_id;
EXPORT_SYMBOL_GPL(sunrpc_net_id);
@@ -108,6 +111,10 @@ init_sunrpc(void)
if (err)
goto out5;
+ err = genl_register_family(&sunrpc_nl_family);
+ if (err)
+ goto out6;
+
sunrpc_debugfs_init();
#if IS_ENABLED(CONFIG_SUNRPC_DEBUG)
rpc_register_sysctl();
@@ -116,6 +123,8 @@ init_sunrpc(void)
init_socket_xprt(); /* clnt sock transport */
return 0;
+out6:
+ rpc_sysfs_exit();
out5:
unregister_rpc_pipefs();
out4:
@@ -131,6 +140,7 @@ init_sunrpc(void)
static void __exit
cleanup_sunrpc(void)
{
+ genl_unregister_family(&sunrpc_nl_family);
rpc_sysfs_exit();
rpc_cleanup_clids();
xprt_cleanup_ids();
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* Re: [PATCH 07/14] sunrpc: add a generic netlink family for cache upcalls
2026-03-16 15:14 ` [PATCH 07/14] sunrpc: add a generic netlink family for cache upcalls Jeff Layton
@ 2026-03-19 18:44 ` Chuck Lever
2026-03-19 19:14 ` Chuck Lever
1 sibling, 0 replies; 28+ messages in thread
From: Chuck Lever @ 2026-03-19 18:44 UTC (permalink / raw)
To: Jeff Layton, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
Cc: Trond Myklebust, Anna Schumaker, linux-nfs, linux-kernel
On 3/16/26 11:14 AM, Jeff Layton wrote:
> The auth.unix.ip and auth.unix.gid caches live in the sunrpc module,
> so they cannot use the nfsd generic netlink family. Create a new
> "sunrpc" generic netlink family with its own "exportd" multicast
> group to support cache upcall notifications for sunrpc-resident
> caches.
>
> Define a YAML spec (sunrpc_cache.yaml) with a cache-type enum
> (ip_map, unix_gid), a cache-notify multicast event, and the
> corresponding uapi header.
>
> Implement sunrpc_cache_notify() which mirrors the nfsd_cache_notify()
> pattern: check for listeners on the exportd multicast group, build
> and send SUNRPC_CMD_CACHE_NOTIFY with the cache-type attribute.
>
> Register/unregister the sunrpc_nl_family in init_sunrpc() and
> cleanup_sunrpc().
>
> Signed-off-by: Jeff Layton <jlayton@kernel.org>
> ---
> Documentation/netlink/specs/sunrpc_cache.yaml | 40 ++++++++++++++++
> fs/nfsd/netlink.c | 2 +-
> include/uapi/linux/sunrpc_netlink.h | 35 ++++++++++++++
> net/sunrpc/Makefile | 2 +-
> net/sunrpc/netlink.c | 66 +++++++++++++++++++++++++++
> net/sunrpc/netlink.h | 25 ++++++++++
> net/sunrpc/sunrpc_syms.c | 10 ++++
> 7 files changed, 178 insertions(+), 2 deletions(-)
>
> diff --git a/Documentation/netlink/specs/sunrpc_cache.yaml b/Documentation/netlink/specs/sunrpc_cache.yaml
> new file mode 100644
> index 0000000000000000000000000000000000000000..f4aa699598bca9ce0215bbc418d9ddcae25c0110
> --- /dev/null
> +++ b/Documentation/netlink/specs/sunrpc_cache.yaml
> @@ -0,0 +1,40 @@
> +# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
> +---
> +name: sunrpc
> +protocol: genetlink
> +uapi-header: linux/sunrpc_netlink.h
> +
> +doc: SUNRPC cache upcall support over generic netlink.
> +
> +definitions:
> + -
> + type: flags
> + name: cache-type
> + entries: [ip_map, unix_gid]
> +
> +attribute-sets:
> + -
> + name: cache-notify
> + attributes:
> + -
> + name: cache-type
> + type: u32
> + enum: cache-type
> +
> +operations:
> + list:
> + -
> + name: cache-notify
> + doc: Notification that there are cache requests that need servicing
> + attribute-set: cache-notify
> + mcgrp: exportd
> + event:
> + attributes:
> + - cache-type
> +
> +mcast-groups:
> + list:
> + -
> + name: none
> + -
> + name: exportd
> diff --git a/fs/nfsd/netlink.c b/fs/nfsd/netlink.c
> index 4e08c1a6b3943cda5b44c2b64bcf3a00173a08db..81c943345d13db849483bf0d6773458115ff0134 100644
> --- a/fs/nfsd/netlink.c
> +++ b/fs/nfsd/netlink.c
> @@ -59,7 +59,7 @@ static const struct genl_split_ops nfsd_nl_ops[] = {
> .cmd = NFSD_CMD_THREADS_SET,
> .doit = nfsd_nl_threads_set_doit,
> .policy = nfsd_threads_set_nl_policy,
> - .maxattr = NFSD_A_SERVER_MAX,
> + .maxattr = NFSD_A_SERVER_FH_KEY,
> .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
> },
> {
> diff --git a/include/uapi/linux/sunrpc_netlink.h b/include/uapi/linux/sunrpc_netlink.h
> new file mode 100644
> index 0000000000000000000000000000000000000000..6135d9b3eef155a9192d9710c8c690283ec49073
> --- /dev/null
> +++ b/include/uapi/linux/sunrpc_netlink.h
> @@ -0,0 +1,35 @@
> +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
> +/* Do not edit directly, auto-generated from: */
> +/* Documentation/netlink/specs/sunrpc_cache.yaml */
> +/* YNL-GEN uapi header */
> +/* To regenerate run: tools/net/ynl/ynl-regen.sh */
> +
> +#ifndef _UAPI_LINUX_SUNRPC_NETLINK_H
> +#define _UAPI_LINUX_SUNRPC_NETLINK_H
> +
> +#define SUNRPC_FAMILY_NAME "sunrpc"
> +#define SUNRPC_FAMILY_VERSION 1
> +
> +enum sunrpc_cache_type {
> + SUNRPC_CACHE_TYPE_IP_MAP = 1,
> + SUNRPC_CACHE_TYPE_UNIX_GID = 2,
> +};
> +
> +enum {
> + SUNRPC_A_CACHE_NOTIFY_CACHE_TYPE = 1,
> +
> + __SUNRPC_A_CACHE_NOTIFY_MAX,
> + SUNRPC_A_CACHE_NOTIFY_MAX = (__SUNRPC_A_CACHE_NOTIFY_MAX - 1)
> +};
> +
> +enum {
> + SUNRPC_CMD_CACHE_NOTIFY = 1,
> +
> + __SUNRPC_CMD_MAX,
> + SUNRPC_CMD_MAX = (__SUNRPC_CMD_MAX - 1)
> +};
> +
> +#define SUNRPC_MCGRP_NONE "none"
> +#define SUNRPC_MCGRP_EXPORTD "exportd"
> +
> +#endif /* _UAPI_LINUX_SUNRPC_NETLINK_H */
> diff --git a/net/sunrpc/Makefile b/net/sunrpc/Makefile
> index f89c10fe7e6acc71d47273200d85425a2891a08a..96727df3aa85435a2de63a8483eab9d75d5b3495 100644
> --- a/net/sunrpc/Makefile
> +++ b/net/sunrpc/Makefile
> @@ -14,7 +14,7 @@ sunrpc-y := clnt.o xprt.o socklib.o xprtsock.o sched.o \
> addr.o rpcb_clnt.o timer.o xdr.o \
> sunrpc_syms.o cache.o rpc_pipe.o sysfs.o \
> svc_xprt.o \
> - xprtmultipath.o
> + xprtmultipath.o netlink.o
> sunrpc-$(CONFIG_SUNRPC_DEBUG) += debugfs.o
> sunrpc-$(CONFIG_SUNRPC_BACKCHANNEL) += backchannel_rqst.o
> sunrpc-$(CONFIG_PROC_FS) += stats.o
> diff --git a/net/sunrpc/netlink.c b/net/sunrpc/netlink.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..e59ee82dfab358fc367045d9f04c394000c812ec
> --- /dev/null
> +++ b/net/sunrpc/netlink.c
> @@ -0,0 +1,66 @@
> +// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
> +/* Do not edit directly, auto-generated from: */
> +/* Documentation/netlink/specs/sunrpc_cache.yaml */
> +/* YNL-GEN kernel source */
> +/* To regenerate run: tools/net/ynl/ynl-regen.sh */
> +
> +#include <net/netlink.h>
> +#include <net/genetlink.h>
> +#include <linux/sunrpc/cache.h>
> +
> +#include "netlink.h"
> +
> +#include <uapi/linux/sunrpc_netlink.h>
> +
> +/* Ops table for sunrpc */
> +static const struct genl_split_ops sunrpc_nl_ops[] = {
> +};
> +
> +static const struct genl_multicast_group sunrpc_nl_mcgrps[] = {
> + [SUNRPC_NLGRP_NONE] = { "none", },
> + [SUNRPC_NLGRP_EXPORTD] = { "exportd", },
> +};
> +
> +struct genl_family sunrpc_nl_family __ro_after_init = {
> + .name = SUNRPC_FAMILY_NAME,
> + .version = SUNRPC_FAMILY_VERSION,
> + .netnsok = true,
> + .parallel_ops = true,
> + .module = THIS_MODULE,
> + .split_ops = sunrpc_nl_ops,
> + .n_split_ops = ARRAY_SIZE(sunrpc_nl_ops),
> + .mcgrps = sunrpc_nl_mcgrps,
> + .n_mcgrps = ARRAY_SIZE(sunrpc_nl_mcgrps),
> +};
> +
> +int sunrpc_cache_notify(struct cache_detail *cd, struct cache_head *h,
> + u32 cache_type)
> +{
> + struct genlmsghdr *hdr;
> + struct sk_buff *msg;
> +
> + if (!genl_has_listeners(&sunrpc_nl_family, cd->net,
> + SUNRPC_NLGRP_EXPORTD))
> + return -ENOLINK;
> +
> + msg = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL);
> + if (!msg)
> + return -ENOMEM;
> +
> + hdr = genlmsg_put(msg, 0, 0, &sunrpc_nl_family, 0,
> + SUNRPC_CMD_CACHE_NOTIFY);
> + if (!hdr) {
> + nlmsg_free(msg);
> + return -ENOMEM;
> + }
> +
> + if (nla_put_u32(msg, SUNRPC_A_CACHE_NOTIFY_CACHE_TYPE, cache_type)) {
> + nlmsg_free(msg);
> + return -ENOMEM;
> + }
> +
> + genlmsg_end(msg, hdr);
> + return genlmsg_multicast_netns(&sunrpc_nl_family, cd->net, msg, 0,
> + SUNRPC_NLGRP_EXPORTD, GFP_KERNEL);
> +}
> +EXPORT_SYMBOL_GPL(sunrpc_cache_notify);
Is sunrpc_cache_notify() hand-written? If it is, can you find another
initial landing place for it (I think it is moved out in a later patch)?
> diff --git a/net/sunrpc/netlink.h b/net/sunrpc/netlink.h
> new file mode 100644
> index 0000000000000000000000000000000000000000..efb05f87b89513fe738964a1b27637a09f9b88a9
> --- /dev/null
> +++ b/net/sunrpc/netlink.h
> @@ -0,0 +1,25 @@
> +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
> +/* Do not edit directly, auto-generated from: */
> +/* Documentation/netlink/specs/sunrpc_cache.yaml */
> +/* YNL-GEN kernel header */
> +/* To regenerate run: tools/net/ynl/ynl-regen.sh */
> +
> +#ifndef _LINUX_SUNRPC_GEN_H
> +#define _LINUX_SUNRPC_GEN_H
> +
> +#include <net/netlink.h>
> +#include <net/genetlink.h>
> +
> +#include <uapi/linux/sunrpc_netlink.h>
> +
> +enum {
> + SUNRPC_NLGRP_NONE,
> + SUNRPC_NLGRP_EXPORTD,
> +};
> +
> +extern struct genl_family sunrpc_nl_family;
> +
> +int sunrpc_cache_notify(struct cache_detail *cd, struct cache_head *h,
> + u32 cache_type);
Ditto...
> +
> +#endif /* _LINUX_SUNRPC_GEN_H */
> diff --git a/net/sunrpc/sunrpc_syms.c b/net/sunrpc/sunrpc_syms.c
> index bab6cab2940524a970422b62b3fa4212c61c4f43..ab88ce46afb556cb0a397fe5c9df3901813ad01e 100644
> --- a/net/sunrpc/sunrpc_syms.c
> +++ b/net/sunrpc/sunrpc_syms.c
> @@ -23,9 +23,12 @@
> #include <linux/sunrpc/rpc_pipe_fs.h>
> #include <linux/sunrpc/xprtsock.h>
>
> +#include <net/genetlink.h>
> +
> #include "sunrpc.h"
> #include "sysfs.h"
> #include "netns.h"
> +#include "netlink.h"
>
> unsigned int sunrpc_net_id;
> EXPORT_SYMBOL_GPL(sunrpc_net_id);
> @@ -108,6 +111,10 @@ init_sunrpc(void)
> if (err)
> goto out5;
>
> + err = genl_register_family(&sunrpc_nl_family);
> + if (err)
> + goto out6;
> +
> sunrpc_debugfs_init();
> #if IS_ENABLED(CONFIG_SUNRPC_DEBUG)
> rpc_register_sysctl();
> @@ -116,6 +123,8 @@ init_sunrpc(void)
> init_socket_xprt(); /* clnt sock transport */
> return 0;
>
> +out6:
> + rpc_sysfs_exit();
> out5:
> unregister_rpc_pipefs();
> out4:
> @@ -131,6 +140,7 @@ init_sunrpc(void)
> static void __exit
> cleanup_sunrpc(void)
> {
> + genl_unregister_family(&sunrpc_nl_family);
> rpc_sysfs_exit();
> rpc_cleanup_clids();
> xprt_cleanup_ids();
>
--
Chuck Lever
^ permalink raw reply [flat|nested] 28+ messages in thread* Re: [PATCH 07/14] sunrpc: add a generic netlink family for cache upcalls
2026-03-16 15:14 ` [PATCH 07/14] sunrpc: add a generic netlink family for cache upcalls Jeff Layton
2026-03-19 18:44 ` Chuck Lever
@ 2026-03-19 19:14 ` Chuck Lever
2026-03-19 19:19 ` Jeff Layton
1 sibling, 1 reply; 28+ messages in thread
From: Chuck Lever @ 2026-03-19 19:14 UTC (permalink / raw)
To: Jeff Layton, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
Cc: Trond Myklebust, Anna Schumaker, linux-nfs, linux-kernel
On 3/16/26 11:14 AM, Jeff Layton wrote:
> diff --git a/fs/nfsd/netlink.c b/fs/nfsd/netlink.c
> index 4e08c1a6b3943cda5b44c2b64bcf3a00173a08db..81c943345d13db849483bf0d6773458115ff0134 100644
> --- a/fs/nfsd/netlink.c
> +++ b/fs/nfsd/netlink.c
> @@ -59,7 +59,7 @@ static const struct genl_split_ops nfsd_nl_ops[] = {
> .cmd = NFSD_CMD_THREADS_SET,
> .doit = nfsd_nl_threads_set_doit,
> .policy = nfsd_threads_set_nl_policy,
> - .maxattr = NFSD_A_SERVER_MAX,
> + .maxattr = NFSD_A_SERVER_FH_KEY,
> .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
> },
> {
This hunk is clearly not related to adding "a generic netlink family for
cache upcalls". Should I apply it instead to the appropriate FH-signing
patch, which is still in my nfsd-testing branch?
--
Chuck Lever
^ permalink raw reply [flat|nested] 28+ messages in thread* Re: [PATCH 07/14] sunrpc: add a generic netlink family for cache upcalls
2026-03-19 19:14 ` Chuck Lever
@ 2026-03-19 19:19 ` Jeff Layton
2026-03-19 19:20 ` Chuck Lever
0 siblings, 1 reply; 28+ messages in thread
From: Jeff Layton @ 2026-03-19 19:19 UTC (permalink / raw)
To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
Cc: Trond Myklebust, Anna Schumaker, linux-nfs, linux-kernel
On Thu, 2026-03-19 at 15:14 -0400, Chuck Lever wrote:
> On 3/16/26 11:14 AM, Jeff Layton wrote:
>
> > diff --git a/fs/nfsd/netlink.c b/fs/nfsd/netlink.c
> > index 4e08c1a6b3943cda5b44c2b64bcf3a00173a08db..81c943345d13db849483bf0d6773458115ff0134 100644
> > --- a/fs/nfsd/netlink.c
> > +++ b/fs/nfsd/netlink.c
> > @@ -59,7 +59,7 @@ static const struct genl_split_ops nfsd_nl_ops[] = {
> > .cmd = NFSD_CMD_THREADS_SET,
> > .doit = nfsd_nl_threads_set_doit,
> > .policy = nfsd_threads_set_nl_policy,
> > - .maxattr = NFSD_A_SERVER_MAX,
> > + .maxattr = NFSD_A_SERVER_FH_KEY,
> > .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
> > },
> > {
>
> This hunk is clearly not related to adding "a generic netlink family for
> cache upcalls". Should I apply it instead to the appropriate FH-signing
> patch, which is still in my nfsd-testing branch?
>
I noticed that too. I think this is due to a change in the ynl tool.
The new way seems more correct since the "*_MAX" value is fluid.
If you wanted, we could just regenerate the files with the new tool and
commit those changes first and then layer the new stuff on top.
--
Jeff Layton <jlayton@kernel.org>
^ permalink raw reply [flat|nested] 28+ messages in thread* Re: [PATCH 07/14] sunrpc: add a generic netlink family for cache upcalls
2026-03-19 19:19 ` Jeff Layton
@ 2026-03-19 19:20 ` Chuck Lever
2026-03-19 19:31 ` Chuck Lever
0 siblings, 1 reply; 28+ messages in thread
From: Chuck Lever @ 2026-03-19 19:20 UTC (permalink / raw)
To: Jeff Layton, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
Cc: Trond Myklebust, Anna Schumaker, linux-nfs, linux-kernel
On 3/19/26 3:19 PM, Jeff Layton wrote:
> On Thu, 2026-03-19 at 15:14 -0400, Chuck Lever wrote:
>> On 3/16/26 11:14 AM, Jeff Layton wrote:
>>
>>> diff --git a/fs/nfsd/netlink.c b/fs/nfsd/netlink.c
>>> index 4e08c1a6b3943cda5b44c2b64bcf3a00173a08db..81c943345d13db849483bf0d6773458115ff0134 100644
>>> --- a/fs/nfsd/netlink.c
>>> +++ b/fs/nfsd/netlink.c
>>> @@ -59,7 +59,7 @@ static const struct genl_split_ops nfsd_nl_ops[] = {
>>> .cmd = NFSD_CMD_THREADS_SET,
>>> .doit = nfsd_nl_threads_set_doit,
>>> .policy = nfsd_threads_set_nl_policy,
>>> - .maxattr = NFSD_A_SERVER_MAX,
>>> + .maxattr = NFSD_A_SERVER_FH_KEY,
>>> .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
>>> },
>>> {
>>
>> This hunk is clearly not related to adding "a generic netlink family for
>> cache upcalls". Should I apply it instead to the appropriate FH-signing
>> patch, which is still in my nfsd-testing branch?
>>
>
> I noticed that too. I think this is due to a change in the ynl tool.
> The new way seems more correct since the "*_MAX" value is fluid.
>
> If you wanted, we could just regenerate the files with the new tool and
> commit those changes first and then layer the new stuff on top.
I checked the tool, it hasn't changed.
--
Chuck Lever
^ permalink raw reply [flat|nested] 28+ messages in thread* Re: [PATCH 07/14] sunrpc: add a generic netlink family for cache upcalls
2026-03-19 19:20 ` Chuck Lever
@ 2026-03-19 19:31 ` Chuck Lever
0 siblings, 0 replies; 28+ messages in thread
From: Chuck Lever @ 2026-03-19 19:31 UTC (permalink / raw)
To: Jeff Layton, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
Cc: Trond Myklebust, Anna Schumaker, linux-nfs, linux-kernel
On 3/19/26 3:20 PM, Chuck Lever wrote:
> On 3/19/26 3:19 PM, Jeff Layton wrote:
>> On Thu, 2026-03-19 at 15:14 -0400, Chuck Lever wrote:
>>> On 3/16/26 11:14 AM, Jeff Layton wrote:
>>>
>>>> diff --git a/fs/nfsd/netlink.c b/fs/nfsd/netlink.c
>>>> index 4e08c1a6b3943cda5b44c2b64bcf3a00173a08db..81c943345d13db849483bf0d6773458115ff0134 100644
>>>> --- a/fs/nfsd/netlink.c
>>>> +++ b/fs/nfsd/netlink.c
>>>> @@ -59,7 +59,7 @@ static const struct genl_split_ops nfsd_nl_ops[] = {
>>>> .cmd = NFSD_CMD_THREADS_SET,
>>>> .doit = nfsd_nl_threads_set_doit,
>>>> .policy = nfsd_threads_set_nl_policy,
>>>> - .maxattr = NFSD_A_SERVER_MAX,
>>>> + .maxattr = NFSD_A_SERVER_FH_KEY,
>>>> .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
>>>> },
>>>> {
>>>
>>> This hunk is clearly not related to adding "a generic netlink family for
>>> cache upcalls". Should I apply it instead to the appropriate FH-signing
>>> patch, which is still in my nfsd-testing branch?
>>>
>>
>> I noticed that too. I think this is due to a change in the ynl tool.
>> The new way seems more correct since the "*_MAX" value is fluid.
>>
>> If you wanted, we could just regenerate the files with the new tool and
>> commit those changes first and then layer the new stuff on top.
>
> I checked the tool, it hasn't changed.
I think "NFSD: Add a key for signing filehandles" introduced this
anomaly, so I've gone back to that patch and re-ran the tool. This was
the only change that was generated, so I applied it and pushed it to
nfsd-testing.
--
Chuck Lever
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH 08/14] sunrpc: add netlink upcall for the auth.unix.ip cache
2026-03-16 15:14 [PATCH 00/14] nfsd/sunrpc: add support for netlink upcalls for mountd/exportd Jeff Layton
` (6 preceding siblings ...)
2026-03-16 15:14 ` [PATCH 07/14] sunrpc: add a generic netlink family for cache upcalls Jeff Layton
@ 2026-03-16 15:14 ` Jeff Layton
2026-03-16 15:14 ` [PATCH 09/14] sunrpc: add netlink upcall for the auth.unix.gid cache Jeff Layton
` (5 subsequent siblings)
13 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-03-16 15:14 UTC (permalink / raw)
To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
Cc: Trond Myklebust, Anna Schumaker, linux-nfs, linux-kernel,
Jeff Layton
Add netlink-based cache upcall support for the ip_map (auth.unix.ip)
cache, using the sunrpc generic netlink family.
Add ip-map attribute-set (seqno, class, addr, domain, negative, expiry),
ip-map-reqs wrapper, and ip-map-get-reqs / ip-map-set-reqs operations
to the sunrpc_cache YAML spec and generated headers.
Implement sunrpc_nl_ip_map_get_reqs_dumpit() which snapshots pending
ip_map cache requests and sends each entry's seqno, class name, and
IP address over netlink.
Implement sunrpc_nl_ip_map_set_reqs_doit() which parses ip_map cache
responses from userspace (class, addr, expiry, and domain name or
negative flag) and updates the cache via __ip_map_lookup() /
__ip_map_update().
Wire up ip_map_notify() callback in ip_map_cache_template so cache
misses trigger SUNRPC_CMD_CACHE_NOTIFY multicast events with
SUNRPC_CACHE_TYPE_IP_MAP.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
Documentation/netlink/specs/sunrpc_cache.yaml | 47 +++++
include/uapi/linux/sunrpc_netlink.h | 21 +++
net/sunrpc/netlink.c | 34 ++++
net/sunrpc/netlink.h | 8 +
net/sunrpc/svcauth_unix.c | 241 ++++++++++++++++++++++++++
5 files changed, 351 insertions(+)
diff --git a/Documentation/netlink/specs/sunrpc_cache.yaml b/Documentation/netlink/specs/sunrpc_cache.yaml
index f4aa699598bca9ce0215bbc418d9ddcae25c0110..8bcd43f65f3258ba43df4f80a7cfda5f09f2f13e 100644
--- a/Documentation/netlink/specs/sunrpc_cache.yaml
+++ b/Documentation/netlink/specs/sunrpc_cache.yaml
@@ -20,6 +20,35 @@ attribute-sets:
name: cache-type
type: u32
enum: cache-type
+ -
+ name: ip-map
+ attributes:
+ -
+ name: seqno
+ type: u64
+ -
+ name: class
+ type: string
+ -
+ name: addr
+ type: string
+ -
+ name: domain
+ type: string
+ -
+ name: negative
+ type: flag
+ -
+ name: expiry
+ type: u64
+ -
+ name: ip-map-reqs
+ attributes:
+ -
+ name: requests
+ type: nest
+ nested-attributes: ip-map
+ multi-attr: true
operations:
list:
@@ -31,6 +60,24 @@ operations:
event:
attributes:
- cache-type
+ -
+ name: ip-map-get-reqs
+ doc: Dump all pending ip_map requests
+ attribute-set: ip-map-reqs
+ flags: [admin-perm]
+ dump:
+ request:
+ attributes:
+ - requests
+ -
+ name: ip-map-set-reqs
+ doc: Respond to one or more ip_map requests
+ attribute-set: ip-map-reqs
+ flags: [admin-perm]
+ do:
+ request:
+ attributes:
+ - requests
mcast-groups:
list:
diff --git a/include/uapi/linux/sunrpc_netlink.h b/include/uapi/linux/sunrpc_netlink.h
index 6135d9b3eef155a9192d9710c8c690283ec49073..b44befb5a34b956e70065e0e12b816e2943da66e 100644
--- a/include/uapi/linux/sunrpc_netlink.h
+++ b/include/uapi/linux/sunrpc_netlink.h
@@ -22,8 +22,29 @@ enum {
SUNRPC_A_CACHE_NOTIFY_MAX = (__SUNRPC_A_CACHE_NOTIFY_MAX - 1)
};
+enum {
+ SUNRPC_A_IP_MAP_SEQNO = 1,
+ SUNRPC_A_IP_MAP_CLASS,
+ SUNRPC_A_IP_MAP_ADDR,
+ SUNRPC_A_IP_MAP_DOMAIN,
+ SUNRPC_A_IP_MAP_NEGATIVE,
+ SUNRPC_A_IP_MAP_EXPIRY,
+
+ __SUNRPC_A_IP_MAP_MAX,
+ SUNRPC_A_IP_MAP_MAX = (__SUNRPC_A_IP_MAP_MAX - 1)
+};
+
+enum {
+ SUNRPC_A_IP_MAP_REQS_REQUESTS = 1,
+
+ __SUNRPC_A_IP_MAP_REQS_MAX,
+ SUNRPC_A_IP_MAP_REQS_MAX = (__SUNRPC_A_IP_MAP_REQS_MAX - 1)
+};
+
enum {
SUNRPC_CMD_CACHE_NOTIFY = 1,
+ SUNRPC_CMD_IP_MAP_GET_REQS,
+ SUNRPC_CMD_IP_MAP_SET_REQS,
__SUNRPC_CMD_MAX,
SUNRPC_CMD_MAX = (__SUNRPC_CMD_MAX - 1)
diff --git a/net/sunrpc/netlink.c b/net/sunrpc/netlink.c
index e59ee82dfab358fc367045d9f04c394000c812ec..baeaf28fda02f120c2cd4778fcb444850ae8868a 100644
--- a/net/sunrpc/netlink.c
+++ b/net/sunrpc/netlink.c
@@ -12,8 +12,42 @@
#include <uapi/linux/sunrpc_netlink.h>
+/* Common nested types */
+const struct nla_policy sunrpc_ip_map_nl_policy[SUNRPC_A_IP_MAP_EXPIRY + 1] = {
+ [SUNRPC_A_IP_MAP_SEQNO] = { .type = NLA_U64, },
+ [SUNRPC_A_IP_MAP_CLASS] = { .type = NLA_NUL_STRING, },
+ [SUNRPC_A_IP_MAP_ADDR] = { .type = NLA_NUL_STRING, },
+ [SUNRPC_A_IP_MAP_DOMAIN] = { .type = NLA_NUL_STRING, },
+ [SUNRPC_A_IP_MAP_NEGATIVE] = { .type = NLA_FLAG, },
+ [SUNRPC_A_IP_MAP_EXPIRY] = { .type = NLA_U64, },
+};
+
+/* SUNRPC_CMD_IP_MAP_GET_REQS - dump */
+static const struct nla_policy sunrpc_ip_map_get_reqs_nl_policy[SUNRPC_A_IP_MAP_REQS_REQUESTS + 1] = {
+ [SUNRPC_A_IP_MAP_REQS_REQUESTS] = NLA_POLICY_NESTED(sunrpc_ip_map_nl_policy),
+};
+
+/* SUNRPC_CMD_IP_MAP_SET_REQS - do */
+static const struct nla_policy sunrpc_ip_map_set_reqs_nl_policy[SUNRPC_A_IP_MAP_REQS_REQUESTS + 1] = {
+ [SUNRPC_A_IP_MAP_REQS_REQUESTS] = NLA_POLICY_NESTED(sunrpc_ip_map_nl_policy),
+};
+
/* Ops table for sunrpc */
static const struct genl_split_ops sunrpc_nl_ops[] = {
+ {
+ .cmd = SUNRPC_CMD_IP_MAP_GET_REQS,
+ .dumpit = sunrpc_nl_ip_map_get_reqs_dumpit,
+ .policy = sunrpc_ip_map_get_reqs_nl_policy,
+ .maxattr = SUNRPC_A_IP_MAP_REQS_REQUESTS,
+ .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DUMP,
+ },
+ {
+ .cmd = SUNRPC_CMD_IP_MAP_SET_REQS,
+ .doit = sunrpc_nl_ip_map_set_reqs_doit,
+ .policy = sunrpc_ip_map_set_reqs_nl_policy,
+ .maxattr = SUNRPC_A_IP_MAP_REQS_REQUESTS,
+ .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
+ },
};
static const struct genl_multicast_group sunrpc_nl_mcgrps[] = {
diff --git a/net/sunrpc/netlink.h b/net/sunrpc/netlink.h
index efb05f87b89513fe738964a1b27637a09f9b88a9..6849faec517ea33ddc5cafab827381984e0dc602 100644
--- a/net/sunrpc/netlink.h
+++ b/net/sunrpc/netlink.h
@@ -12,6 +12,14 @@
#include <uapi/linux/sunrpc_netlink.h>
+/* Common nested types */
+extern const struct nla_policy sunrpc_ip_map_nl_policy[SUNRPC_A_IP_MAP_EXPIRY + 1];
+
+int sunrpc_nl_ip_map_get_reqs_dumpit(struct sk_buff *skb,
+ struct netlink_callback *cb);
+int sunrpc_nl_ip_map_set_reqs_doit(struct sk_buff *skb,
+ struct genl_info *info);
+
enum {
SUNRPC_NLGRP_NONE,
SUNRPC_NLGRP_EXPORTD,
diff --git a/net/sunrpc/svcauth_unix.c b/net/sunrpc/svcauth_unix.c
index 87732c4cb8383c64b440538a0d3f3113a3009b4e..e4b196742877bd3abf199f2bf815b90615a2be04 100644
--- a/net/sunrpc/svcauth_unix.c
+++ b/net/sunrpc/svcauth_unix.c
@@ -17,11 +17,14 @@
#include <net/ipv6.h>
#include <linux/kernel.h>
#include <linux/user_namespace.h>
+#include <net/genetlink.h>
+#include <uapi/linux/sunrpc_netlink.h>
#include <trace/events/sunrpc.h>
#define RPCDBG_FACILITY RPCDBG_AUTH
#include "netns.h"
+#include "netlink.h"
/*
* AUTHUNIX and AUTHNULL credentials are both handled here.
@@ -1017,12 +1020,250 @@ struct auth_ops svcauth_unix = {
.set_client = svcauth_unix_set_client,
};
+static int ip_map_notify(struct cache_detail *cd, struct cache_head *h)
+{
+ return sunrpc_cache_notify(cd, h, SUNRPC_CACHE_TYPE_IP_MAP);
+}
+
+/**
+ * sunrpc_nl_ip_map_get_reqs_dumpit - dump pending ip_map requests
+ * @skb: reply buffer
+ * @cb: netlink metadata and command arguments
+ *
+ * Walk the ip_map cache's pending request list and create a netlink
+ * message with a nested entry for each cache_request, containing the
+ * seqno, class and addr.
+ *
+ * Returns the size of the reply or a negative errno.
+ */
+int sunrpc_nl_ip_map_get_reqs_dumpit(struct sk_buff *skb,
+ struct netlink_callback *cb)
+{
+ struct sunrpc_net *sn;
+ struct cache_detail *cd;
+ struct cache_head **items;
+ u64 *seqnos;
+ int cnt, i;
+ void *hdr;
+ int ret;
+
+ sn = net_generic(sock_net(skb->sk), sunrpc_net_id);
+
+ cd = sn->ip_map_cache;
+ if (!cd)
+ return -ENODEV;
+
+ /* Second call means we've already dumped everything */
+ if (cb->args[0])
+ return 0;
+
+ cnt = sunrpc_cache_requests_count(cd);
+ if (!cnt)
+ return 0;
+
+ items = kcalloc(cnt, sizeof(*items), GFP_KERNEL);
+ seqnos = kcalloc(cnt, sizeof(*seqnos), GFP_KERNEL);
+ if (!items || !seqnos) {
+ ret = -ENOMEM;
+ goto out_alloc;
+ }
+
+ cnt = sunrpc_cache_requests_snapshot(cd, items, seqnos, cnt);
+
+ hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid,
+ cb->nlh->nlmsg_seq, &sunrpc_nl_family,
+ NLM_F_MULTI, SUNRPC_CMD_IP_MAP_GET_REQS);
+ if (!hdr) {
+ ret = -ENOBUFS;
+ goto out_put;
+ }
+
+ for (i = 0; i < cnt; i++) {
+ struct ip_map *im;
+ struct nlattr *nest;
+ char text_addr[40];
+
+ im = container_of(items[i], struct ip_map, h);
+
+ if (ipv6_addr_v4mapped(&im->m_addr))
+ snprintf(text_addr, 20, "%pI4",
+ &im->m_addr.s6_addr32[3]);
+ else
+ snprintf(text_addr, 40, "%pI6", &im->m_addr);
+
+ nest = nla_nest_start(skb, SUNRPC_A_IP_MAP_REQS_REQUESTS);
+ if (!nest) {
+ ret = -ENOBUFS;
+ goto out_cancel;
+ }
+
+ if (nla_put_u64_64bit(skb, SUNRPC_A_IP_MAP_SEQNO,
+ seqnos[i], 0) ||
+ nla_put_string(skb, SUNRPC_A_IP_MAP_CLASS,
+ im->m_class) ||
+ nla_put_string(skb, SUNRPC_A_IP_MAP_ADDR, text_addr)) {
+ nla_nest_cancel(skb, nest);
+ ret = -ENOBUFS;
+ goto out_cancel;
+ }
+
+ nla_nest_end(skb, nest);
+ }
+
+ genlmsg_end(skb, hdr);
+ cb->args[0] = 1;
+ ret = skb->len;
+ goto out_put;
+
+out_cancel:
+ genlmsg_cancel(skb, hdr);
+out_put:
+ for (i = 0; i < cnt; i++)
+ cache_put(items[i], cd);
+out_alloc:
+ kfree(seqnos);
+ kfree(items);
+ return ret;
+}
+
+/**
+ * sunrpc_nl_parse_one_ip_map - parse one ip_map entry from netlink
+ * @cd: cache_detail for the ip_map cache
+ * @attr: nested attribute containing ip_map fields
+ *
+ * Parses one ip_map entry from a netlink message and updates the
+ * cache. Mirrors the logic in ip_map_parse().
+ *
+ * Returns 0 on success or a negative errno.
+ */
+static int sunrpc_nl_parse_one_ip_map(struct cache_detail *cd,
+ struct nlattr *attr)
+{
+ struct nlattr *tb[SUNRPC_A_IP_MAP_EXPIRY + 1];
+ union {
+ struct sockaddr sa;
+ struct sockaddr_in s4;
+ struct sockaddr_in6 s6;
+ } address;
+ struct sockaddr_in6 sin6;
+ struct ip_map *ipmp;
+ struct auth_domain *dom = NULL;
+ struct unix_domain *udom = NULL;
+ struct timespec64 boot;
+ time64_t expiry;
+ char class[8];
+ int err;
+ int len;
+
+ err = nla_parse_nested(tb, SUNRPC_A_IP_MAP_EXPIRY, attr,
+ sunrpc_ip_map_nl_policy, NULL);
+ if (err)
+ return err;
+
+ /* class (required) */
+ if (!tb[SUNRPC_A_IP_MAP_CLASS])
+ return -EINVAL;
+ len = nla_len(tb[SUNRPC_A_IP_MAP_CLASS]);
+ if (len <= 0 || len > sizeof(class))
+ return -EINVAL;
+ nla_strscpy(class, tb[SUNRPC_A_IP_MAP_CLASS], sizeof(class));
+
+ /* addr (required) */
+ if (!tb[SUNRPC_A_IP_MAP_ADDR])
+ return -EINVAL;
+ if (rpc_pton(cd->net, nla_data(tb[SUNRPC_A_IP_MAP_ADDR]),
+ nla_len(tb[SUNRPC_A_IP_MAP_ADDR]) - 1,
+ &address.sa, sizeof(address)) == 0)
+ return -EINVAL;
+
+ switch (address.sa.sa_family) {
+ case AF_INET:
+ sin6.sin6_family = AF_INET6;
+ ipv6_addr_set_v4mapped(address.s4.sin_addr.s_addr,
+ &sin6.sin6_addr);
+ break;
+#if IS_ENABLED(CONFIG_IPV6)
+ case AF_INET6:
+ memcpy(&sin6, &address.s6, sizeof(sin6));
+ break;
+#endif
+ default:
+ return -EINVAL;
+ }
+
+ /* expiry (required, wallclock seconds) */
+ if (!tb[SUNRPC_A_IP_MAP_EXPIRY])
+ return -EINVAL;
+ getboottime64(&boot);
+ expiry = nla_get_u64(tb[SUNRPC_A_IP_MAP_EXPIRY]) - boot.tv_sec;
+
+ /* domain name or negative */
+ if (tb[SUNRPC_A_IP_MAP_NEGATIVE]) {
+ udom = NULL;
+ } else if (tb[SUNRPC_A_IP_MAP_DOMAIN]) {
+ dom = unix_domain_find(nla_data(tb[SUNRPC_A_IP_MAP_DOMAIN]));
+ if (!dom)
+ return -ENOENT;
+ udom = container_of(dom, struct unix_domain, h);
+ } else {
+ return -EINVAL;
+ }
+
+ ipmp = __ip_map_lookup(cd, class, &sin6.sin6_addr);
+ if (ipmp)
+ err = __ip_map_update(cd, ipmp, udom, expiry);
+ else
+ err = -ENOMEM;
+
+ if (dom)
+ auth_domain_put(dom);
+
+ cache_flush();
+ return err;
+}
+
+/**
+ * sunrpc_nl_ip_map_set_reqs_doit - respond to ip_map requests
+ * @skb: reply buffer
+ * @info: netlink metadata and command arguments
+ *
+ * Parse one or more ip_map cache responses from userspace and
+ * update the ip_map cache accordingly.
+ *
+ * Returns 0 on success or a negative errno.
+ */
+int sunrpc_nl_ip_map_set_reqs_doit(struct sk_buff *skb,
+ struct genl_info *info)
+{
+ struct sunrpc_net *sn;
+ struct cache_detail *cd;
+ const struct nlattr *attr;
+ int rem, ret = 0;
+
+ sn = net_generic(genl_info_net(info), sunrpc_net_id);
+
+ cd = sn->ip_map_cache;
+ if (!cd)
+ return -ENODEV;
+
+ nlmsg_for_each_attr_type(attr, SUNRPC_A_IP_MAP_REQS_REQUESTS,
+ info->nlhdr, GENL_HDRLEN, rem) {
+ ret = sunrpc_nl_parse_one_ip_map(cd,
+ (struct nlattr *)attr);
+ if (ret)
+ break;
+ }
+
+ return ret;
+}
+
static const struct cache_detail ip_map_cache_template = {
.owner = THIS_MODULE,
.hash_size = IP_HASHMAX,
.name = "auth.unix.ip",
.cache_put = ip_map_put,
.cache_upcall = ip_map_upcall,
+ .cache_notify = ip_map_notify,
.cache_request = ip_map_request,
.cache_parse = ip_map_parse,
.cache_show = ip_map_show,
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH 09/14] sunrpc: add netlink upcall for the auth.unix.gid cache
2026-03-16 15:14 [PATCH 00/14] nfsd/sunrpc: add support for netlink upcalls for mountd/exportd Jeff Layton
` (7 preceding siblings ...)
2026-03-16 15:14 ` [PATCH 08/14] sunrpc: add netlink upcall for the auth.unix.ip cache Jeff Layton
@ 2026-03-16 15:14 ` Jeff Layton
2026-03-20 14:32 ` Chuck Lever
2026-03-16 15:14 ` [PATCH 10/14] nfsd: add new netlink spec for svc_export upcall Jeff Layton
` (4 subsequent siblings)
13 siblings, 1 reply; 28+ messages in thread
From: Jeff Layton @ 2026-03-16 15:14 UTC (permalink / raw)
To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
Cc: Trond Myklebust, Anna Schumaker, linux-nfs, linux-kernel,
Jeff Layton
Add netlink-based cache upcall support for the unix_gid (auth.unix.gid)
cache, using the sunrpc generic netlink family.
Add unix-gid attribute-set (seqno, uid, gids multi-attr, negative,
expiry), unix-gid-reqs wrapper, and unix-gid-get-reqs /
unix-gid-set-reqs operations to the sunrpc_cache YAML spec and
generated headers.
Implement sunrpc_nl_unix_gid_get_reqs_dumpit() which snapshots pending
unix_gid cache requests and sends each entry's seqno and uid over
netlink.
Implement sunrpc_nl_unix_gid_set_reqs_doit() which parses unix_gid
cache responses from userspace (uid, expiry, gids as u32 multi-attr
or negative flag) and updates the cache via unix_gid_lookup() /
sunrpc_cache_update().
Wire up unix_gid_notify() callback in unix_gid_cache_template so
cache misses trigger SUNRPC_CMD_CACHE_NOTIFY multicast events with
SUNRPC_CACHE_TYPE_UNIX_GID.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
Documentation/netlink/specs/sunrpc_cache.yaml | 45 +++++
include/uapi/linux/sunrpc_netlink.h | 20 +++
net/sunrpc/netlink.c | 32 ++++
net/sunrpc/netlink.h | 5 +
net/sunrpc/svcauth_unix.c | 229 ++++++++++++++++++++++++++
5 files changed, 331 insertions(+)
diff --git a/Documentation/netlink/specs/sunrpc_cache.yaml b/Documentation/netlink/specs/sunrpc_cache.yaml
index 8bcd43f65f3258ba43df4f80a7cfda5f09f2f13e..ed0ddb61ebcf22b6ad889b0760f8a6f470295dbd 100644
--- a/Documentation/netlink/specs/sunrpc_cache.yaml
+++ b/Documentation/netlink/specs/sunrpc_cache.yaml
@@ -49,6 +49,33 @@ attribute-sets:
type: nest
nested-attributes: ip-map
multi-attr: true
+ -
+ name: unix-gid
+ attributes:
+ -
+ name: seqno
+ type: u64
+ -
+ name: uid
+ type: u32
+ -
+ name: gids
+ type: u32
+ multi-attr: true
+ -
+ name: negative
+ type: flag
+ -
+ name: expiry
+ type: u64
+ -
+ name: unix-gid-reqs
+ attributes:
+ -
+ name: requests
+ type: nest
+ nested-attributes: unix-gid
+ multi-attr: true
operations:
list:
@@ -78,6 +105,24 @@ operations:
request:
attributes:
- requests
+ -
+ name: unix-gid-get-reqs
+ doc: Dump all pending unix_gid requests
+ attribute-set: unix-gid-reqs
+ flags: [admin-perm]
+ dump:
+ request:
+ attributes:
+ - requests
+ -
+ name: unix-gid-set-reqs
+ doc: Respond to one or more unix_gid requests
+ attribute-set: unix-gid-reqs
+ flags: [admin-perm]
+ do:
+ request:
+ attributes:
+ - requests
mcast-groups:
list:
diff --git a/include/uapi/linux/sunrpc_netlink.h b/include/uapi/linux/sunrpc_netlink.h
index b44befb5a34b956e70065e0e12b816e2943da66e..d71c623e92aba4566e3114cc23d0aa553cbdb885 100644
--- a/include/uapi/linux/sunrpc_netlink.h
+++ b/include/uapi/linux/sunrpc_netlink.h
@@ -41,10 +41,30 @@ enum {
SUNRPC_A_IP_MAP_REQS_MAX = (__SUNRPC_A_IP_MAP_REQS_MAX - 1)
};
+enum {
+ SUNRPC_A_UNIX_GID_SEQNO = 1,
+ SUNRPC_A_UNIX_GID_UID,
+ SUNRPC_A_UNIX_GID_GIDS,
+ SUNRPC_A_UNIX_GID_NEGATIVE,
+ SUNRPC_A_UNIX_GID_EXPIRY,
+
+ __SUNRPC_A_UNIX_GID_MAX,
+ SUNRPC_A_UNIX_GID_MAX = (__SUNRPC_A_UNIX_GID_MAX - 1)
+};
+
+enum {
+ SUNRPC_A_UNIX_GID_REQS_REQUESTS = 1,
+
+ __SUNRPC_A_UNIX_GID_REQS_MAX,
+ SUNRPC_A_UNIX_GID_REQS_MAX = (__SUNRPC_A_UNIX_GID_REQS_MAX - 1)
+};
+
enum {
SUNRPC_CMD_CACHE_NOTIFY = 1,
SUNRPC_CMD_IP_MAP_GET_REQS,
SUNRPC_CMD_IP_MAP_SET_REQS,
+ SUNRPC_CMD_UNIX_GID_GET_REQS,
+ SUNRPC_CMD_UNIX_GID_SET_REQS,
__SUNRPC_CMD_MAX,
SUNRPC_CMD_MAX = (__SUNRPC_CMD_MAX - 1)
diff --git a/net/sunrpc/netlink.c b/net/sunrpc/netlink.c
index baeaf28fda02f120c2cd4778fcb444850ae8868a..44a38aba820d9ad25bd50d0d8c7a827dfe37c2bd 100644
--- a/net/sunrpc/netlink.c
+++ b/net/sunrpc/netlink.c
@@ -32,6 +32,24 @@ static const struct nla_policy sunrpc_ip_map_set_reqs_nl_policy[SUNRPC_A_IP_MAP_
[SUNRPC_A_IP_MAP_REQS_REQUESTS] = NLA_POLICY_NESTED(sunrpc_ip_map_nl_policy),
};
+const struct nla_policy sunrpc_unix_gid_nl_policy[SUNRPC_A_UNIX_GID_EXPIRY + 1] = {
+ [SUNRPC_A_UNIX_GID_SEQNO] = { .type = NLA_U64, },
+ [SUNRPC_A_UNIX_GID_UID] = { .type = NLA_U32, },
+ [SUNRPC_A_UNIX_GID_GIDS] = { .type = NLA_U32, },
+ [SUNRPC_A_UNIX_GID_NEGATIVE] = { .type = NLA_FLAG, },
+ [SUNRPC_A_UNIX_GID_EXPIRY] = { .type = NLA_U64, },
+};
+
+/* SUNRPC_CMD_UNIX_GID_GET_REQS - dump */
+static const struct nla_policy sunrpc_unix_gid_get_reqs_nl_policy[SUNRPC_A_UNIX_GID_REQS_REQUESTS + 1] = {
+ [SUNRPC_A_UNIX_GID_REQS_REQUESTS] = NLA_POLICY_NESTED(sunrpc_unix_gid_nl_policy),
+};
+
+/* SUNRPC_CMD_UNIX_GID_SET_REQS - do */
+static const struct nla_policy sunrpc_unix_gid_set_reqs_nl_policy[SUNRPC_A_UNIX_GID_REQS_REQUESTS + 1] = {
+ [SUNRPC_A_UNIX_GID_REQS_REQUESTS] = NLA_POLICY_NESTED(sunrpc_unix_gid_nl_policy),
+};
+
/* Ops table for sunrpc */
static const struct genl_split_ops sunrpc_nl_ops[] = {
{
@@ -48,6 +66,20 @@ static const struct genl_split_ops sunrpc_nl_ops[] = {
.maxattr = SUNRPC_A_IP_MAP_REQS_REQUESTS,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
},
+ {
+ .cmd = SUNRPC_CMD_UNIX_GID_GET_REQS,
+ .dumpit = sunrpc_nl_unix_gid_get_reqs_dumpit,
+ .policy = sunrpc_unix_gid_get_reqs_nl_policy,
+ .maxattr = SUNRPC_A_UNIX_GID_REQS_REQUESTS,
+ .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DUMP,
+ },
+ {
+ .cmd = SUNRPC_CMD_UNIX_GID_SET_REQS,
+ .doit = sunrpc_nl_unix_gid_set_reqs_doit,
+ .policy = sunrpc_unix_gid_set_reqs_nl_policy,
+ .maxattr = SUNRPC_A_UNIX_GID_REQS_REQUESTS,
+ .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
+ },
};
static const struct genl_multicast_group sunrpc_nl_mcgrps[] = {
diff --git a/net/sunrpc/netlink.h b/net/sunrpc/netlink.h
index 6849faec517ea33ddc5cafab827381984e0dc602..f01477c13f98f6708f3f24391cde164edb21a860 100644
--- a/net/sunrpc/netlink.h
+++ b/net/sunrpc/netlink.h
@@ -14,11 +14,16 @@
/* Common nested types */
extern const struct nla_policy sunrpc_ip_map_nl_policy[SUNRPC_A_IP_MAP_EXPIRY + 1];
+extern const struct nla_policy sunrpc_unix_gid_nl_policy[SUNRPC_A_UNIX_GID_EXPIRY + 1];
int sunrpc_nl_ip_map_get_reqs_dumpit(struct sk_buff *skb,
struct netlink_callback *cb);
int sunrpc_nl_ip_map_set_reqs_doit(struct sk_buff *skb,
struct genl_info *info);
+int sunrpc_nl_unix_gid_get_reqs_dumpit(struct sk_buff *skb,
+ struct netlink_callback *cb);
+int sunrpc_nl_unix_gid_set_reqs_doit(struct sk_buff *skb,
+ struct genl_info *info);
enum {
SUNRPC_NLGRP_NONE,
diff --git a/net/sunrpc/svcauth_unix.c b/net/sunrpc/svcauth_unix.c
index e4b196742877bd3abf199f2bf815b90615a2be04..b84511ff726c1836f777c802943f6d8e112a0998 100644
--- a/net/sunrpc/svcauth_unix.c
+++ b/net/sunrpc/svcauth_unix.c
@@ -585,12 +585,241 @@ static int unix_gid_show(struct seq_file *m,
return 0;
}
+static int unix_gid_notify(struct cache_detail *cd, struct cache_head *h)
+{
+ return sunrpc_cache_notify(cd, h, SUNRPC_CACHE_TYPE_UNIX_GID);
+}
+
+/**
+ * sunrpc_nl_unix_gid_get_reqs_dumpit - dump pending unix_gid requests
+ * @skb: reply buffer
+ * @cb: netlink metadata and command arguments
+ *
+ * Walk the unix_gid cache's pending request list and create a netlink
+ * message with a nested entry for each cache_request, containing the
+ * seqno and uid.
+ *
+ * Returns the size of the reply or a negative errno.
+ */
+int sunrpc_nl_unix_gid_get_reqs_dumpit(struct sk_buff *skb,
+ struct netlink_callback *cb)
+{
+ struct sunrpc_net *sn;
+ struct cache_detail *cd;
+ struct cache_head **items;
+ u64 *seqnos;
+ int cnt, i;
+ void *hdr;
+ int ret;
+
+ sn = net_generic(sock_net(skb->sk), sunrpc_net_id);
+
+ cd = sn->unix_gid_cache;
+ if (!cd)
+ return -ENODEV;
+
+ /* Second call means we've already dumped everything */
+ if (cb->args[0])
+ return 0;
+
+ cnt = sunrpc_cache_requests_count(cd);
+ if (!cnt)
+ return 0;
+
+ items = kcalloc(cnt, sizeof(*items), GFP_KERNEL);
+ seqnos = kcalloc(cnt, sizeof(*seqnos), GFP_KERNEL);
+ if (!items || !seqnos) {
+ ret = -ENOMEM;
+ goto out_alloc;
+ }
+
+ cnt = sunrpc_cache_requests_snapshot(cd, items, seqnos, cnt);
+
+ hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid,
+ cb->nlh->nlmsg_seq, &sunrpc_nl_family,
+ NLM_F_MULTI, SUNRPC_CMD_UNIX_GID_GET_REQS);
+ if (!hdr) {
+ ret = -ENOBUFS;
+ goto out_put;
+ }
+
+ for (i = 0; i < cnt; i++) {
+ struct unix_gid *ug;
+ struct nlattr *nest;
+
+ ug = container_of(items[i], struct unix_gid, h);
+
+ nest = nla_nest_start(skb,
+ SUNRPC_A_UNIX_GID_REQS_REQUESTS);
+ if (!nest) {
+ ret = -ENOBUFS;
+ goto out_cancel;
+ }
+
+ if (nla_put_u64_64bit(skb, SUNRPC_A_UNIX_GID_SEQNO,
+ seqnos[i], 0) ||
+ nla_put_u32(skb, SUNRPC_A_UNIX_GID_UID,
+ from_kuid(&init_user_ns, ug->uid))) {
+ nla_nest_cancel(skb, nest);
+ ret = -ENOBUFS;
+ goto out_cancel;
+ }
+
+ nla_nest_end(skb, nest);
+ }
+
+ genlmsg_end(skb, hdr);
+ cb->args[0] = 1;
+ ret = skb->len;
+ goto out_put;
+
+out_cancel:
+ genlmsg_cancel(skb, hdr);
+out_put:
+ for (i = 0; i < cnt; i++)
+ cache_put(items[i], cd);
+out_alloc:
+ kfree(seqnos);
+ kfree(items);
+ return ret;
+}
+
+/**
+ * sunrpc_nl_parse_one_unix_gid - parse one unix_gid entry from netlink
+ * @cd: cache_detail for the unix_gid cache
+ * @attr: nested attribute containing unix_gid fields
+ *
+ * Parses one unix_gid entry from a netlink message and updates the
+ * cache. Mirrors the logic in unix_gid_parse().
+ *
+ * Returns 0 on success or a negative errno.
+ */
+static int sunrpc_nl_parse_one_unix_gid(struct cache_detail *cd,
+ struct nlattr *attr)
+{
+ struct nlattr *tb[SUNRPC_A_UNIX_GID_EXPIRY + 1];
+ struct unix_gid ug, *ugp;
+ struct timespec64 boot;
+ struct nlattr *gid_attr;
+ int err, rem, gids = 0;
+ kuid_t uid;
+
+ err = nla_parse_nested(tb, SUNRPC_A_UNIX_GID_EXPIRY, attr,
+ sunrpc_unix_gid_nl_policy, NULL);
+ if (err)
+ return err;
+
+ /* uid (required) */
+ if (!tb[SUNRPC_A_UNIX_GID_UID])
+ return -EINVAL;
+ uid = make_kuid(current_user_ns(),
+ nla_get_u32(tb[SUNRPC_A_UNIX_GID_UID]));
+ ug.uid = uid;
+
+ /* expiry (required, wallclock seconds) */
+ if (!tb[SUNRPC_A_UNIX_GID_EXPIRY])
+ return -EINVAL;
+ getboottime64(&boot);
+ ug.h.flags = 0;
+ ug.h.expiry_time = nla_get_u64(tb[SUNRPC_A_UNIX_GID_EXPIRY]) -
+ boot.tv_sec;
+
+ if (tb[SUNRPC_A_UNIX_GID_NEGATIVE]) {
+ ug.gi = groups_alloc(0);
+ if (!ug.gi)
+ return -ENOMEM;
+ } else {
+ /* Count gids */
+ nla_for_each_nested_type(gid_attr, SUNRPC_A_UNIX_GID_GIDS,
+ attr, rem)
+ gids++;
+
+ if (gids > 8192)
+ return -EINVAL;
+
+ ug.gi = groups_alloc(gids);
+ if (!ug.gi)
+ return -ENOMEM;
+
+ gids = 0;
+ nla_for_each_nested_type(gid_attr, SUNRPC_A_UNIX_GID_GIDS,
+ attr, rem) {
+ kgid_t kgid;
+
+ kgid = make_kgid(current_user_ns(),
+ nla_get_u32(gid_attr));
+ if (!gid_valid(kgid)) {
+ err = -EINVAL;
+ goto out;
+ }
+ ug.gi->gid[gids++] = kgid;
+ }
+ groups_sort(ug.gi);
+ }
+
+ ugp = unix_gid_lookup(cd, uid);
+ if (ugp) {
+ struct cache_head *ch;
+
+ ch = sunrpc_cache_update(cd, &ug.h, &ugp->h,
+ unix_gid_hash(uid));
+ if (!ch) {
+ err = -ENOMEM;
+ } else {
+ err = 0;
+ cache_put(ch, cd);
+ }
+ } else {
+ err = -ENOMEM;
+ }
+out:
+ if (ug.gi)
+ put_group_info(ug.gi);
+ return err;
+}
+
+/**
+ * sunrpc_nl_unix_gid_set_reqs_doit - respond to unix_gid requests
+ * @skb: reply buffer
+ * @info: netlink metadata and command arguments
+ *
+ * Parse one or more unix_gid cache responses from userspace and
+ * update the unix_gid cache accordingly.
+ *
+ * Returns 0 on success or a negative errno.
+ */
+int sunrpc_nl_unix_gid_set_reqs_doit(struct sk_buff *skb,
+ struct genl_info *info)
+{
+ struct sunrpc_net *sn;
+ struct cache_detail *cd;
+ const struct nlattr *attr;
+ int rem, ret = 0;
+
+ sn = net_generic(genl_info_net(info), sunrpc_net_id);
+
+ cd = sn->unix_gid_cache;
+ if (!cd)
+ return -ENODEV;
+
+ nlmsg_for_each_attr_type(attr, SUNRPC_A_UNIX_GID_REQS_REQUESTS,
+ info->nlhdr, GENL_HDRLEN, rem) {
+ ret = sunrpc_nl_parse_one_unix_gid(cd,
+ (struct nlattr *)attr);
+ if (ret)
+ break;
+ }
+
+ return ret;
+}
+
static const struct cache_detail unix_gid_cache_template = {
.owner = THIS_MODULE,
.hash_size = GID_HASHMAX,
.name = "auth.unix.gid",
.cache_put = unix_gid_put,
.cache_upcall = unix_gid_upcall,
+ .cache_notify = unix_gid_notify,
.cache_request = unix_gid_request,
.cache_parse = unix_gid_parse,
.cache_show = unix_gid_show,
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* Re: [PATCH 09/14] sunrpc: add netlink upcall for the auth.unix.gid cache
2026-03-16 15:14 ` [PATCH 09/14] sunrpc: add netlink upcall for the auth.unix.gid cache Jeff Layton
@ 2026-03-20 14:32 ` Chuck Lever
0 siblings, 0 replies; 28+ messages in thread
From: Chuck Lever @ 2026-03-20 14:32 UTC (permalink / raw)
To: Jeff Layton, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
Cc: Trond Myklebust, Anna Schumaker, linux-nfs, linux-kernel
On 3/16/26 11:14 AM, Jeff Layton wrote:
> diff --git a/net/sunrpc/svcauth_unix.c b/net/sunrpc/svcauth_unix.c
> index e4b196742877bd3abf199f2bf815b90615a2be04..b84511ff726c1836f777c802943f6d8e112a0998 100644
> --- a/net/sunrpc/svcauth_unix.c
> +++ b/net/sunrpc/svcauth_unix.c
> @@ -585,12 +585,241 @@ static int unix_gid_show(struct seq_file *m,
> return 0;
> }
>
> +static int unix_gid_notify(struct cache_detail *cd, struct cache_head *h)
> +{
> + return sunrpc_cache_notify(cd, h, SUNRPC_CACHE_TYPE_UNIX_GID);
> +}
> +
> +/**
> + * sunrpc_nl_unix_gid_get_reqs_dumpit - dump pending unix_gid requests
> + * @skb: reply buffer
> + * @cb: netlink metadata and command arguments
> + *
> + * Walk the unix_gid cache's pending request list and create a netlink
> + * message with a nested entry for each cache_request, containing the
> + * seqno and uid.
> + *
> + * Returns the size of the reply or a negative errno.
> + */
> +int sunrpc_nl_unix_gid_get_reqs_dumpit(struct sk_buff *skb,
> + struct netlink_callback *cb)
> +{
> + struct sunrpc_net *sn;
> + struct cache_detail *cd;
> + struct cache_head **items;
> + u64 *seqnos;
> + int cnt, i;
> + void *hdr;
> + int ret;
> +
> + sn = net_generic(sock_net(skb->sk), sunrpc_net_id);
> +
> + cd = sn->unix_gid_cache;
> + if (!cd)
> + return -ENODEV;
> +
> + /* Second call means we've already dumped everything */
> + if (cb->args[0])
> + return 0;
> +
> + cnt = sunrpc_cache_requests_count(cd);
> + if (!cnt)
> + return 0;
> +
> + items = kcalloc(cnt, sizeof(*items), GFP_KERNEL);
> + seqnos = kcalloc(cnt, sizeof(*seqnos), GFP_KERNEL);
> + if (!items || !seqnos) {
> + ret = -ENOMEM;
> + goto out_alloc;
> + }
> +
> + cnt = sunrpc_cache_requests_snapshot(cd, items, seqnos, cnt);
> +
> + hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid,
> + cb->nlh->nlmsg_seq, &sunrpc_nl_family,
> + NLM_F_MULTI, SUNRPC_CMD_UNIX_GID_GET_REQS);
> + if (!hdr) {
> + ret = -ENOBUFS;
> + goto out_put;
> + }
> +
> + for (i = 0; i < cnt; i++) {
> + struct unix_gid *ug;
> + struct nlattr *nest;
> +
> + ug = container_of(items[i], struct unix_gid, h);
> +
> + nest = nla_nest_start(skb,
> + SUNRPC_A_UNIX_GID_REQS_REQUESTS);
> + if (!nest) {
> + ret = -ENOBUFS;
> + goto out_cancel;
> + }
> +
> + if (nla_put_u64_64bit(skb, SUNRPC_A_UNIX_GID_SEQNO,
> + seqnos[i], 0) ||
> + nla_put_u32(skb, SUNRPC_A_UNIX_GID_UID,
> + from_kuid(&init_user_ns, ug->uid))) {
> + nla_nest_cancel(skb, nest);
> + ret = -ENOBUFS;
> + goto out_cancel;
> + }
> +
> + nla_nest_end(skb, nest);
> + }
> +
> + genlmsg_end(skb, hdr);
> + cb->args[0] = 1;
> + ret = skb->len;
> + goto out_put;
> +
> +out_cancel:
> + genlmsg_cancel(skb, hdr);
> +out_put:
> + for (i = 0; i < cnt; i++)
> + cache_put(items[i], cd);
> +out_alloc:
> + kfree(seqnos);
> + kfree(items);
> + return ret;
> +}
sunrpc_nl_unix_gid_get_reqs_dumpit() packs its reply entries into a
single netlink message. The default SKB size is ~8 KiB, and each entry
encodes to ~24 bytes, so the buffer fills at roughly 340 entries. When
nla_put fails, the function returns -ENOBUFS but never sets
cb->args[0] = 1;
causing the netlink subsystem to retry indefinitely. A big NFS server
can easily exceed 340 pending GID cache entries.
Should we implement proper netlink dump continuation: snapshot once
then emit entries across multiple dumpit calls using a cursor in
cb->args?
--
Chuck Lever
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH 10/14] nfsd: add new netlink spec for svc_export upcall
2026-03-16 15:14 [PATCH 00/14] nfsd/sunrpc: add support for netlink upcalls for mountd/exportd Jeff Layton
` (8 preceding siblings ...)
2026-03-16 15:14 ` [PATCH 09/14] sunrpc: add netlink upcall for the auth.unix.gid cache Jeff Layton
@ 2026-03-16 15:14 ` Jeff Layton
2026-03-20 15:17 ` Chuck Lever
2026-03-16 15:14 ` [PATCH 11/14] nfsd: add netlink upcall for the svc_export cache Jeff Layton
` (3 subsequent siblings)
13 siblings, 1 reply; 28+ messages in thread
From: Jeff Layton @ 2026-03-16 15:14 UTC (permalink / raw)
To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
Cc: Trond Myklebust, Anna Schumaker, linux-nfs, linux-kernel,
Jeff Layton
...and generate the headers.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
Documentation/netlink/specs/nfsd.yaml | 172 ++++++++++++++++++++++++++++++++++
fs/nfsd/netlink.c | 61 ++++++++++++
fs/nfsd/netlink.h | 13 +++
include/uapi/linux/nfsd_netlink.h | 102 ++++++++++++++++++++
net/sunrpc/netlink.c | 49 ++--------
net/sunrpc/netlink.h | 6 +-
6 files changed, 357 insertions(+), 46 deletions(-)
diff --git a/Documentation/netlink/specs/nfsd.yaml b/Documentation/netlink/specs/nfsd.yaml
index 8ab43c8253b2e83bcc178c3f4fe8c41c2997d153..08322bc3dee7458e6a202372dd332067d03e1be6 100644
--- a/Documentation/netlink/specs/nfsd.yaml
+++ b/Documentation/netlink/specs/nfsd.yaml
@@ -6,7 +6,49 @@ uapi-header: linux/nfsd_netlink.h
doc: NFSD configuration over generic netlink.
+definitions:
+ -
+ type: flags
+ name: cache-type
+ entries: [svc_export]
+ -
+ type: flags
+ name: export-flags
+ entries:
+ - readonly
+ - insecure-port
+ - rootsquash
+ - allsquash
+ - async
+ - gathered-writes
+ - noreaddirplus
+ - security-label
+ - sign-fh
+ - nohide
+ - nosubtreecheck
+ - noauthnlm
+ - msnfs
+ - fsid
+ - crossmount
+ - noacl
+ - v4root
+ - pnfs
+ -
+ type: flags
+ name: xprtsec-mode
+ entries:
+ - none
+ - tls
+ - mtls
+
attribute-sets:
+ -
+ name: cache-notify
+ attributes:
+ -
+ name: cache-type
+ type: u32
+ enum: cache-type
-
name: rpc-status
attributes:
@@ -132,6 +174,103 @@ attribute-sets:
-
name: npools
type: u32
+ -
+ name: fslocation
+ attributes:
+ -
+ name: host
+ type: string
+ -
+ name: path
+ type: string
+ -
+ name: fslocations
+ attributes:
+ -
+ name: location
+ type: nest
+ nested-attributes: fslocation
+ multi-attr: true
+ -
+ name: auth-flavor
+ attributes:
+ -
+ name: pseudoflavor
+ type: u32
+ -
+ name: flags
+ type: u32
+ enum: export-flags
+ enum-as-flags: true
+ -
+ name: svc-export-req
+ attributes:
+ -
+ name: seqno
+ type: u64
+ -
+ name: client
+ type: string
+ -
+ name: path
+ type: string
+ -
+ name: svc-export
+ attributes:
+ -
+ name: seqno
+ type: u64
+ -
+ name: client
+ type: string
+ -
+ name: path
+ type: string
+ -
+ name: negative
+ type: flag
+ -
+ name: expiry
+ type: u64
+ -
+ name: anon-uid
+ type: u32
+ -
+ name: anon-gid
+ type: u32
+ -
+ name: fslocations
+ type: nest
+ nested-attributes: fslocations
+ -
+ name: uuid
+ type: binary
+ -
+ name: secinfo
+ type: nest
+ nested-attributes: auth-flavor
+ multi-attr: true
+ -
+ name: xprtsec
+ type: u32
+ enum: xprtsec-mode
+ multi-attr: true
+ -
+ name: flags
+ type: u32
+ enum: export-flags
+ enum-as-flags: true
+ -
+ name: fsid
+ type: s32
+ -
+ name: svc-export-reqs
+ attributes:
+ -
+ name: requests
+ type: nest
+ nested-attributes: svc-export
+ multi-attr: true
operations:
list:
@@ -233,3 +372,36 @@ operations:
attributes:
- mode
- npools
+ -
+ name: cache-notify
+ doc: Notification that there are cache requests that need servicing
+ attribute-set: cache-notify
+ mcgrp: exportd
+ event:
+ attributes:
+ - cache-type
+ -
+ name: svc-export-get-reqs
+ doc: Dump all pending svc_export requests
+ attribute-set: svc-export-reqs
+ flags: [admin-perm]
+ dump:
+ request:
+ attributes:
+ - requests
+ -
+ name: svc-export-set-reqs
+ doc: Respond to one or more svc_export requests
+ attribute-set: svc-export-reqs
+ flags: [admin-perm]
+ do:
+ request:
+ attributes:
+ - requests
+
+mcast-groups:
+ list:
+ -
+ name: none
+ -
+ name: exportd
diff --git a/fs/nfsd/netlink.c b/fs/nfsd/netlink.c
index 81c943345d13db849483bf0d6773458115ff0134..fb401d7302afb9e41cb074581f7b94e8ece6cf0c 100644
--- a/fs/nfsd/netlink.c
+++ b/fs/nfsd/netlink.c
@@ -12,11 +12,41 @@
#include <uapi/linux/nfsd_netlink.h>
/* Common nested types */
+const struct nla_policy nfsd_auth_flavor_nl_policy[NFSD_A_AUTH_FLAVOR_FLAGS + 1] = {
+ [NFSD_A_AUTH_FLAVOR_PSEUDOFLAVOR] = { .type = NLA_U32, },
+ [NFSD_A_AUTH_FLAVOR_FLAGS] = NLA_POLICY_MASK(NLA_U32, 0x3ffff),
+};
+
+const struct nla_policy nfsd_fslocation_nl_policy[NFSD_A_FSLOCATION_PATH + 1] = {
+ [NFSD_A_FSLOCATION_HOST] = { .type = NLA_NUL_STRING, },
+ [NFSD_A_FSLOCATION_PATH] = { .type = NLA_NUL_STRING, },
+};
+
+const struct nla_policy nfsd_fslocations_nl_policy[NFSD_A_FSLOCATIONS_LOCATION + 1] = {
+ [NFSD_A_FSLOCATIONS_LOCATION] = NLA_POLICY_NESTED(nfsd_fslocation_nl_policy),
+};
+
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_svc_export_nl_policy[NFSD_A_SVC_EXPORT_FSID + 1] = {
+ [NFSD_A_SVC_EXPORT_SEQNO] = { .type = NLA_U64, },
+ [NFSD_A_SVC_EXPORT_CLIENT] = { .type = NLA_NUL_STRING, },
+ [NFSD_A_SVC_EXPORT_PATH] = { .type = NLA_NUL_STRING, },
+ [NFSD_A_SVC_EXPORT_NEGATIVE] = { .type = NLA_FLAG, },
+ [NFSD_A_SVC_EXPORT_EXPIRY] = { .type = NLA_U64, },
+ [NFSD_A_SVC_EXPORT_ANON_UID] = { .type = NLA_U32, },
+ [NFSD_A_SVC_EXPORT_ANON_GID] = { .type = NLA_U32, },
+ [NFSD_A_SVC_EXPORT_FSLOCATIONS] = NLA_POLICY_NESTED(nfsd_fslocations_nl_policy),
+ [NFSD_A_SVC_EXPORT_UUID] = { .type = NLA_BINARY, },
+ [NFSD_A_SVC_EXPORT_SECINFO] = NLA_POLICY_NESTED(nfsd_auth_flavor_nl_policy),
+ [NFSD_A_SVC_EXPORT_XPRTSEC] = NLA_POLICY_MASK(NLA_U32, 0x7),
+ [NFSD_A_SVC_EXPORT_FLAGS] = NLA_POLICY_MASK(NLA_U32, 0x3ffff),
+ [NFSD_A_SVC_EXPORT_FSID] = { .type = NLA_S32, },
+};
+
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, },
@@ -48,6 +78,16 @@ static const struct nla_policy nfsd_pool_mode_set_nl_policy[NFSD_A_POOL_MODE_MOD
[NFSD_A_POOL_MODE_MODE] = { .type = NLA_NUL_STRING, },
};
+/* NFSD_CMD_SVC_EXPORT_GET_REQS - dump */
+static const struct nla_policy nfsd_svc_export_get_reqs_nl_policy[NFSD_A_SVC_EXPORT_REQS_REQUESTS + 1] = {
+ [NFSD_A_SVC_EXPORT_REQS_REQUESTS] = NLA_POLICY_NESTED(nfsd_svc_export_nl_policy),
+};
+
+/* NFSD_CMD_SVC_EXPORT_SET_REQS - do */
+static const struct nla_policy nfsd_svc_export_set_reqs_nl_policy[NFSD_A_SVC_EXPORT_REQS_REQUESTS + 1] = {
+ [NFSD_A_SVC_EXPORT_REQS_REQUESTS] = NLA_POLICY_NESTED(nfsd_svc_export_nl_policy),
+};
+
/* Ops table for nfsd */
static const struct genl_split_ops nfsd_nl_ops[] = {
{
@@ -103,6 +143,25 @@ static const struct genl_split_ops nfsd_nl_ops[] = {
.doit = nfsd_nl_pool_mode_get_doit,
.flags = GENL_CMD_CAP_DO,
},
+ {
+ .cmd = NFSD_CMD_SVC_EXPORT_GET_REQS,
+ .dumpit = nfsd_nl_svc_export_get_reqs_dumpit,
+ .policy = nfsd_svc_export_get_reqs_nl_policy,
+ .maxattr = NFSD_A_SVC_EXPORT_REQS_REQUESTS,
+ .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DUMP,
+ },
+ {
+ .cmd = NFSD_CMD_SVC_EXPORT_SET_REQS,
+ .doit = nfsd_nl_svc_export_set_reqs_doit,
+ .policy = nfsd_svc_export_set_reqs_nl_policy,
+ .maxattr = NFSD_A_SVC_EXPORT_REQS_REQUESTS,
+ .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
+ },
+};
+
+static const struct genl_multicast_group nfsd_nl_mcgrps[] = {
+ [NFSD_NLGRP_NONE] = { "none", },
+ [NFSD_NLGRP_EXPORTD] = { "exportd", },
};
struct genl_family nfsd_nl_family __ro_after_init = {
@@ -113,4 +172,6 @@ struct genl_family nfsd_nl_family __ro_after_init = {
.module = THIS_MODULE,
.split_ops = nfsd_nl_ops,
.n_split_ops = ARRAY_SIZE(nfsd_nl_ops),
+ .mcgrps = nfsd_nl_mcgrps,
+ .n_mcgrps = ARRAY_SIZE(nfsd_nl_mcgrps),
};
diff --git a/fs/nfsd/netlink.h b/fs/nfsd/netlink.h
index 478117ff6b8c0d6e83d6ece09a938935e031c62b..d6ed8d9b0bb149faa4d6493ba94972addf9c26ed 100644
--- a/fs/nfsd/netlink.h
+++ b/fs/nfsd/netlink.h
@@ -13,7 +13,11 @@
#include <uapi/linux/nfsd_netlink.h>
/* Common nested types */
+extern const struct nla_policy nfsd_auth_flavor_nl_policy[NFSD_A_AUTH_FLAVOR_FLAGS + 1];
+extern const struct nla_policy nfsd_fslocation_nl_policy[NFSD_A_FSLOCATION_PATH + 1];
+extern const struct nla_policy nfsd_fslocations_nl_policy[NFSD_A_FSLOCATIONS_LOCATION + 1];
extern const struct nla_policy nfsd_sock_nl_policy[NFSD_A_SOCK_TRANSPORT_NAME + 1];
+extern const struct nla_policy nfsd_svc_export_nl_policy[NFSD_A_SVC_EXPORT_FSID + 1];
extern const struct nla_policy nfsd_version_nl_policy[NFSD_A_VERSION_ENABLED + 1];
int nfsd_nl_rpc_status_get_dumpit(struct sk_buff *skb,
@@ -26,6 +30,15 @@ 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);
int nfsd_nl_pool_mode_set_doit(struct sk_buff *skb, struct genl_info *info);
int nfsd_nl_pool_mode_get_doit(struct sk_buff *skb, struct genl_info *info);
+int nfsd_nl_svc_export_get_reqs_dumpit(struct sk_buff *skb,
+ struct netlink_callback *cb);
+int nfsd_nl_svc_export_set_reqs_doit(struct sk_buff *skb,
+ struct genl_info *info);
+
+enum {
+ NFSD_NLGRP_NONE,
+ NFSD_NLGRP_EXPORTD,
+};
extern struct genl_family nfsd_nl_family;
diff --git a/include/uapi/linux/nfsd_netlink.h b/include/uapi/linux/nfsd_netlink.h
index 97c7447f4d14df97c1cba8cdf1f24fba0a7918b3..1ba1c2c167fd06cd0c845d947f5a03702356d991 100644
--- a/include/uapi/linux/nfsd_netlink.h
+++ b/include/uapi/linux/nfsd_netlink.h
@@ -10,6 +10,44 @@
#define NFSD_FAMILY_NAME "nfsd"
#define NFSD_FAMILY_VERSION 1
+enum nfsd_cache_type {
+ NFSD_CACHE_TYPE_SVC_EXPORT = 1,
+};
+
+enum nfsd_export_flags {
+ NFSD_EXPORT_FLAGS_READONLY = 1,
+ NFSD_EXPORT_FLAGS_INSECURE_PORT = 2,
+ NFSD_EXPORT_FLAGS_ROOTSQUASH = 4,
+ NFSD_EXPORT_FLAGS_ALLSQUASH = 8,
+ NFSD_EXPORT_FLAGS_ASYNC = 16,
+ NFSD_EXPORT_FLAGS_GATHERED_WRITES = 32,
+ NFSD_EXPORT_FLAGS_NOREADDIRPLUS = 64,
+ NFSD_EXPORT_FLAGS_SECURITY_LABEL = 128,
+ NFSD_EXPORT_FLAGS_SIGN_FH = 256,
+ NFSD_EXPORT_FLAGS_NOHIDE = 512,
+ NFSD_EXPORT_FLAGS_NOSUBTREECHECK = 1024,
+ NFSD_EXPORT_FLAGS_NOAUTHNLM = 2048,
+ NFSD_EXPORT_FLAGS_MSNFS = 4096,
+ NFSD_EXPORT_FLAGS_FSID = 8192,
+ NFSD_EXPORT_FLAGS_CROSSMOUNT = 16384,
+ NFSD_EXPORT_FLAGS_NOACL = 32768,
+ NFSD_EXPORT_FLAGS_V4ROOT = 65536,
+ NFSD_EXPORT_FLAGS_PNFS = 131072,
+};
+
+enum nfsd_xprtsec_mode {
+ NFSD_XPRTSEC_MODE_NONE = 1,
+ NFSD_XPRTSEC_MODE_TLS = 2,
+ NFSD_XPRTSEC_MODE_MTLS = 4,
+};
+
+enum {
+ NFSD_A_CACHE_NOTIFY_CACHE_TYPE = 1,
+
+ __NFSD_A_CACHE_NOTIFY_MAX,
+ NFSD_A_CACHE_NOTIFY_MAX = (__NFSD_A_CACHE_NOTIFY_MAX - 1)
+};
+
enum {
NFSD_A_RPC_STATUS_XID = 1,
NFSD_A_RPC_STATUS_FLAGS,
@@ -81,6 +119,64 @@ enum {
NFSD_A_POOL_MODE_MAX = (__NFSD_A_POOL_MODE_MAX - 1)
};
+enum {
+ NFSD_A_FSLOCATION_HOST = 1,
+ NFSD_A_FSLOCATION_PATH,
+
+ __NFSD_A_FSLOCATION_MAX,
+ NFSD_A_FSLOCATION_MAX = (__NFSD_A_FSLOCATION_MAX - 1)
+};
+
+enum {
+ NFSD_A_FSLOCATIONS_LOCATION = 1,
+
+ __NFSD_A_FSLOCATIONS_MAX,
+ NFSD_A_FSLOCATIONS_MAX = (__NFSD_A_FSLOCATIONS_MAX - 1)
+};
+
+enum {
+ NFSD_A_AUTH_FLAVOR_PSEUDOFLAVOR = 1,
+ NFSD_A_AUTH_FLAVOR_FLAGS,
+
+ __NFSD_A_AUTH_FLAVOR_MAX,
+ NFSD_A_AUTH_FLAVOR_MAX = (__NFSD_A_AUTH_FLAVOR_MAX - 1)
+};
+
+enum {
+ NFSD_A_SVC_EXPORT_REQ_SEQNO = 1,
+ NFSD_A_SVC_EXPORT_REQ_CLIENT,
+ NFSD_A_SVC_EXPORT_REQ_PATH,
+
+ __NFSD_A_SVC_EXPORT_REQ_MAX,
+ NFSD_A_SVC_EXPORT_REQ_MAX = (__NFSD_A_SVC_EXPORT_REQ_MAX - 1)
+};
+
+enum {
+ NFSD_A_SVC_EXPORT_SEQNO = 1,
+ NFSD_A_SVC_EXPORT_CLIENT,
+ NFSD_A_SVC_EXPORT_PATH,
+ NFSD_A_SVC_EXPORT_NEGATIVE,
+ NFSD_A_SVC_EXPORT_EXPIRY,
+ NFSD_A_SVC_EXPORT_ANON_UID,
+ NFSD_A_SVC_EXPORT_ANON_GID,
+ NFSD_A_SVC_EXPORT_FSLOCATIONS,
+ NFSD_A_SVC_EXPORT_UUID,
+ NFSD_A_SVC_EXPORT_SECINFO,
+ NFSD_A_SVC_EXPORT_XPRTSEC,
+ NFSD_A_SVC_EXPORT_FLAGS,
+ NFSD_A_SVC_EXPORT_FSID,
+
+ __NFSD_A_SVC_EXPORT_MAX,
+ NFSD_A_SVC_EXPORT_MAX = (__NFSD_A_SVC_EXPORT_MAX - 1)
+};
+
+enum {
+ NFSD_A_SVC_EXPORT_REQS_REQUESTS = 1,
+
+ __NFSD_A_SVC_EXPORT_REQS_MAX,
+ NFSD_A_SVC_EXPORT_REQS_MAX = (__NFSD_A_SVC_EXPORT_REQS_MAX - 1)
+};
+
enum {
NFSD_CMD_RPC_STATUS_GET = 1,
NFSD_CMD_THREADS_SET,
@@ -91,9 +187,15 @@ enum {
NFSD_CMD_LISTENER_GET,
NFSD_CMD_POOL_MODE_SET,
NFSD_CMD_POOL_MODE_GET,
+ NFSD_CMD_CACHE_NOTIFY,
+ NFSD_CMD_SVC_EXPORT_GET_REQS,
+ NFSD_CMD_SVC_EXPORT_SET_REQS,
__NFSD_CMD_MAX,
NFSD_CMD_MAX = (__NFSD_CMD_MAX - 1)
};
+#define NFSD_MCGRP_NONE "none"
+#define NFSD_MCGRP_EXPORTD "exportd"
+
#endif /* _UAPI_LINUX_NFSD_NETLINK_H */
diff --git a/net/sunrpc/netlink.c b/net/sunrpc/netlink.c
index 44a38aba820d9ad25bd50d0d8c7a827dfe37c2bd..3ac6b0cac5fece964f6e6591f90d074f40e96af1 100644
--- a/net/sunrpc/netlink.c
+++ b/net/sunrpc/netlink.c
@@ -6,7 +6,6 @@
#include <net/netlink.h>
#include <net/genetlink.h>
-#include <linux/sunrpc/cache.h>
#include "netlink.h"
@@ -22,6 +21,14 @@ const struct nla_policy sunrpc_ip_map_nl_policy[SUNRPC_A_IP_MAP_EXPIRY + 1] = {
[SUNRPC_A_IP_MAP_EXPIRY] = { .type = NLA_U64, },
};
+const struct nla_policy sunrpc_unix_gid_nl_policy[SUNRPC_A_UNIX_GID_EXPIRY + 1] = {
+ [SUNRPC_A_UNIX_GID_SEQNO] = { .type = NLA_U64, },
+ [SUNRPC_A_UNIX_GID_UID] = { .type = NLA_U32, },
+ [SUNRPC_A_UNIX_GID_GIDS] = { .type = NLA_U32, },
+ [SUNRPC_A_UNIX_GID_NEGATIVE] = { .type = NLA_FLAG, },
+ [SUNRPC_A_UNIX_GID_EXPIRY] = { .type = NLA_U64, },
+};
+
/* SUNRPC_CMD_IP_MAP_GET_REQS - dump */
static const struct nla_policy sunrpc_ip_map_get_reqs_nl_policy[SUNRPC_A_IP_MAP_REQS_REQUESTS + 1] = {
[SUNRPC_A_IP_MAP_REQS_REQUESTS] = NLA_POLICY_NESTED(sunrpc_ip_map_nl_policy),
@@ -32,14 +39,6 @@ static const struct nla_policy sunrpc_ip_map_set_reqs_nl_policy[SUNRPC_A_IP_MAP_
[SUNRPC_A_IP_MAP_REQS_REQUESTS] = NLA_POLICY_NESTED(sunrpc_ip_map_nl_policy),
};
-const struct nla_policy sunrpc_unix_gid_nl_policy[SUNRPC_A_UNIX_GID_EXPIRY + 1] = {
- [SUNRPC_A_UNIX_GID_SEQNO] = { .type = NLA_U64, },
- [SUNRPC_A_UNIX_GID_UID] = { .type = NLA_U32, },
- [SUNRPC_A_UNIX_GID_GIDS] = { .type = NLA_U32, },
- [SUNRPC_A_UNIX_GID_NEGATIVE] = { .type = NLA_FLAG, },
- [SUNRPC_A_UNIX_GID_EXPIRY] = { .type = NLA_U64, },
-};
-
/* SUNRPC_CMD_UNIX_GID_GET_REQS - dump */
static const struct nla_policy sunrpc_unix_gid_get_reqs_nl_policy[SUNRPC_A_UNIX_GID_REQS_REQUESTS + 1] = {
[SUNRPC_A_UNIX_GID_REQS_REQUESTS] = NLA_POLICY_NESTED(sunrpc_unix_gid_nl_policy),
@@ -98,35 +97,3 @@ struct genl_family sunrpc_nl_family __ro_after_init = {
.mcgrps = sunrpc_nl_mcgrps,
.n_mcgrps = ARRAY_SIZE(sunrpc_nl_mcgrps),
};
-
-int sunrpc_cache_notify(struct cache_detail *cd, struct cache_head *h,
- u32 cache_type)
-{
- struct genlmsghdr *hdr;
- struct sk_buff *msg;
-
- if (!genl_has_listeners(&sunrpc_nl_family, cd->net,
- SUNRPC_NLGRP_EXPORTD))
- return -ENOLINK;
-
- msg = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL);
- if (!msg)
- return -ENOMEM;
-
- hdr = genlmsg_put(msg, 0, 0, &sunrpc_nl_family, 0,
- SUNRPC_CMD_CACHE_NOTIFY);
- if (!hdr) {
- nlmsg_free(msg);
- return -ENOMEM;
- }
-
- if (nla_put_u32(msg, SUNRPC_A_CACHE_NOTIFY_CACHE_TYPE, cache_type)) {
- nlmsg_free(msg);
- return -ENOMEM;
- }
-
- genlmsg_end(msg, hdr);
- return genlmsg_multicast_netns(&sunrpc_nl_family, cd->net, msg, 0,
- SUNRPC_NLGRP_EXPORTD, GFP_KERNEL);
-}
-EXPORT_SYMBOL_GPL(sunrpc_cache_notify);
diff --git a/net/sunrpc/netlink.h b/net/sunrpc/netlink.h
index f01477c13f98f6708f3f24391cde164edb21a860..2aec57d27a586e4c6b2fc65c7b4505b0996d9577 100644
--- a/net/sunrpc/netlink.h
+++ b/net/sunrpc/netlink.h
@@ -18,8 +18,7 @@ extern const struct nla_policy sunrpc_unix_gid_nl_policy[SUNRPC_A_UNIX_GID_EXPIR
int sunrpc_nl_ip_map_get_reqs_dumpit(struct sk_buff *skb,
struct netlink_callback *cb);
-int sunrpc_nl_ip_map_set_reqs_doit(struct sk_buff *skb,
- struct genl_info *info);
+int sunrpc_nl_ip_map_set_reqs_doit(struct sk_buff *skb, struct genl_info *info);
int sunrpc_nl_unix_gid_get_reqs_dumpit(struct sk_buff *skb,
struct netlink_callback *cb);
int sunrpc_nl_unix_gid_set_reqs_doit(struct sk_buff *skb,
@@ -32,7 +31,4 @@ enum {
extern struct genl_family sunrpc_nl_family;
-int sunrpc_cache_notify(struct cache_detail *cd, struct cache_head *h,
- u32 cache_type);
-
#endif /* _LINUX_SUNRPC_GEN_H */
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* Re: [PATCH 10/14] nfsd: add new netlink spec for svc_export upcall
2026-03-16 15:14 ` [PATCH 10/14] nfsd: add new netlink spec for svc_export upcall Jeff Layton
@ 2026-03-20 15:17 ` Chuck Lever
2026-03-23 20:00 ` Jeff Layton
0 siblings, 1 reply; 28+ messages in thread
From: Chuck Lever @ 2026-03-20 15:17 UTC (permalink / raw)
To: Jeff Layton, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
Cc: Trond Myklebust, Anna Schumaker, linux-nfs, linux-kernel
On 3/16/26 11:14 AM, Jeff Layton wrote:
> ...and generate the headers.
>
> Signed-off-by: Jeff Layton <jlayton@kernel.org>
> ---
> Documentation/netlink/specs/nfsd.yaml | 172 ++++++++++++++++++++++++++++++++++
> fs/nfsd/netlink.c | 61 ++++++++++++
> fs/nfsd/netlink.h | 13 +++
> include/uapi/linux/nfsd_netlink.h | 102 ++++++++++++++++++++
> net/sunrpc/netlink.c | 49 ++--------
> net/sunrpc/netlink.h | 6 +-
> 6 files changed, 357 insertions(+), 46 deletions(-)
>
This is the last patch in this series that applied cleanly to the
current nfsd-testing branch, so I'm stopping with this one.
I'm going to whine only a little about the commit message being not even
a full sentence. </whine>
review-prompts seems to think that removing sunrpc_cache_notify in this
patch will break the build (temporarily). I mention that only for due
diligence -- my earlier request to move sunrpc_cache_notify out of
tool-generated files will probably make this moot.
The svc-export-req attribute set isn't used in this patch; perhaps
its introduction should be deferred to a patch where it is used.
NFSD_CMD_CACHE_NOTIFY looks like the same: dead code for now.
0x3ffff and 0x7 in the NLA policy masks correspond to NFSEXP_ALLFLAGS
and NFSEXP_XPRTSEC_ALL from include/uapi/linux/nfsd/export.h, but the
policy uses raw hex. Adding a new export flag requires updating three
independent locations with no compile-time check. Using the named
constants directly in the policy would make the link more explicit. I
don't have a good suggestion about the list of flags in the YAML spec.
Several AI reviewers noted that GENLMSG_DEFAULT_SIZE is 8KB, yet the
request and reply attributes for some of the commands added in this
series are no larger than sizeof(u32).
Recommend you add Jakub to the cc: for the series for closer human
inspection of the YAML / netlink protocol aspects.
--
Chuck Lever
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH 10/14] nfsd: add new netlink spec for svc_export upcall
2026-03-20 15:17 ` Chuck Lever
@ 2026-03-23 20:00 ` Jeff Layton
0 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-03-23 20:00 UTC (permalink / raw)
To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
Cc: Trond Myklebust, Anna Schumaker, linux-nfs, linux-kernel
On Fri, 2026-03-20 at 11:17 -0400, Chuck Lever wrote:
> On 3/16/26 11:14 AM, Jeff Layton wrote:
> > ...and generate the headers.
> >
> > Signed-off-by: Jeff Layton <jlayton@kernel.org>
> > ---
> > Documentation/netlink/specs/nfsd.yaml | 172 ++++++++++++++++++++++++++++++++++
> > fs/nfsd/netlink.c | 61 ++++++++++++
> > fs/nfsd/netlink.h | 13 +++
> > include/uapi/linux/nfsd_netlink.h | 102 ++++++++++++++++++++
> > net/sunrpc/netlink.c | 49 ++--------
> > net/sunrpc/netlink.h | 6 +-
> > 6 files changed, 357 insertions(+), 46 deletions(-)
> >
>
>
> This is the last patch in this series that applied cleanly to the
> current nfsd-testing branch, so I'm stopping with this one.
>
> I'm going to whine only a little about the commit message being not even
> a full sentence. </whine>
>
> review-prompts seems to think that removing sunrpc_cache_notify in this
> patch will break the build (temporarily). I mention that only for due
> diligence -- my earlier request to move sunrpc_cache_notify out of
> tool-generated files will probably make this moot.
>
Fixed in my current tree.
> The svc-export-req attribute set isn't used in this patch; perhaps
> its introduction should be deferred to a patch where it is used.
> NFSD_CMD_CACHE_NOTIFY looks like the same: dead code for now.
>
Ok. I'll see about moving those bits.
> 0x3ffff and 0x7 in the NLA policy masks correspond to NFSEXP_ALLFLAGS
> and NFSEXP_XPRTSEC_ALL from include/uapi/linux/nfsd/export.h, but the
> policy uses raw hex. Adding a new export flag requires updating three
> independent locations with no compile-time check. Using the named
> constants directly in the policy would make the link more explicit. I
> don't have a good suggestion about the list of flags in the YAML spec.
>
+ [NFSD_A_SVC_EXPORT_XPRTSEC] = NLA_POLICY_MASK(NLA_U32, 0x7),
+ [NFSD_A_SVC_EXPORT_FLAGS] = NLA_POLICY_MASK(NLA_U32, 0x3ffff),
These are generated by ynl from the provided flags in the spec. I'm not
sure there is much we can do here. ynl just has no idea that those
constants exist. All it cares about is filtering out flags that it
doesn't understand.
Ease of adding new flags is a valid concern though. That's one of the
main reasons for doing this. I'll think about this some more.
> Several AI reviewers noted that GENLMSG_DEFAULT_SIZE is 8KB, yet the
> request and reply attributes for some of the commands added in this
> series are no larger than sizeof(u32).
>
> Recommend you add Jakub to the cc: for the series for closer human
> inspection of the YAML / netlink protocol aspects.
>
I'll do that on the next posting.
Thanks for all the review so far! I'm working on addressing your other
comments too.
--
Jeff Layton <jlayton@kernel.org>
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH 11/14] nfsd: add netlink upcall for the svc_export cache
2026-03-16 15:14 [PATCH 00/14] nfsd/sunrpc: add support for netlink upcalls for mountd/exportd Jeff Layton
` (9 preceding siblings ...)
2026-03-16 15:14 ` [PATCH 10/14] nfsd: add new netlink spec for svc_export upcall Jeff Layton
@ 2026-03-16 15:14 ` Jeff Layton
2026-03-16 15:14 ` [PATCH 12/14] nfsd: add netlink upcall for the nfsd.fh cache Jeff Layton
` (2 subsequent siblings)
13 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-03-16 15:14 UTC (permalink / raw)
To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
Cc: Trond Myklebust, Anna Schumaker, linux-nfs, linux-kernel,
Jeff Layton
Add netlink-based cache upcall support for the svc_export
(nfsd.export) cache.
Implement nfsd_cache_notify() which sends a NFSD_CMD_CACHE_NOTIFY
multicast event to the "exportd" group, carrying the cache type so
userspace knows which cache has pending requests.
Implement nfsd_nl_svc_export_get_reqs_dumpit() which snapshots
pending svc_export cache requests and sends each entry's seqno,
client name, and path over netlink.
Implement nfsd_nl_svc_export_set_reqs_doit() which parses svc_export
cache responses from userspace (client, path, expiry, flags, anon
uid/gid, fslocations, uuid, secinfo, xprtsec, fsid, or negative
flag) and updates the cache via svc_export_lookup() /
svc_export_update().
Wire up the svc_export_notify() callback in svc_export_cache_template
so cache misses trigger NFSD_CMD_CACHE_NOTIFY multicast events with
NFSD_CACHE_TYPE_SVC_EXPORT.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
fs/nfsd/export.c | 440 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-
fs/nfsd/nfsctl.c | 28 ++++
fs/nfsd/nfsd.h | 2 +-
3 files changed, 465 insertions(+), 5 deletions(-)
diff --git a/fs/nfsd/export.c b/fs/nfsd/export.c
index e83e88e69d90ab48c7aff58ac2b36cd1a6e1bb71..45a8e7b9866377131aac8639debdc4f877dc5788 100644
--- a/fs/nfsd/export.c
+++ b/fs/nfsd/export.c
@@ -17,6 +17,8 @@
#include <linux/module.h>
#include <linux/exportfs.h>
#include <linux/sunrpc/svc_xprt.h>
+#include <net/genetlink.h>
+#include <uapi/linux/nfsd_netlink.h>
#include "nfsd.h"
#include "nfsfh.h"
@@ -24,6 +26,7 @@
#include "pnfs.h"
#include "filecache.h"
#include "trace.h"
+#include "netlink.h"
#define NFSDDBG_FACILITY NFSDDBG_EXPORT
@@ -386,11 +389,443 @@ static void svc_export_put(struct kref *ref)
queue_rcu_work(nfsd_export_wq, &exp->ex_rwork);
}
+/**
+ * nfsd_nl_svc_export_get_reqs_dumpit - dump pending svc_export requests
+ * @skb: reply buffer
+ * @cb: netlink metadata and command arguments
+ *
+ * Walk the svc_export cache's pending request list and create a netlink
+ * message with a nested entry for each cache_request, containing the
+ * seqno, client string, and path.
+ *
+ * Returns the size of the reply or a negative errno.
+ */
+int nfsd_nl_svc_export_get_reqs_dumpit(struct sk_buff *skb,
+ struct netlink_callback *cb)
+{
+ struct nfsd_net *nn;
+ struct cache_detail *cd;
+ struct cache_head **items;
+ u64 *seqnos;
+ int cnt, i;
+ char *pathbuf;
+ void *hdr;
+ int ret;
+
+ nn = net_generic(sock_net(skb->sk), nfsd_net_id);
+
+ mutex_lock(&nfsd_mutex);
+
+ cd = nn->svc_export_cache;
+ if (!cd) {
+ ret = -ENODEV;
+ goto out_unlock;
+ }
+
+ /* Second call means we've already dumped everything */
+ if (cb->args[0]) {
+ ret = 0;
+ goto out_unlock;
+ }
+
+ cnt = sunrpc_cache_requests_count(cd);
+ if (!cnt) {
+ ret = 0;
+ goto out_unlock;
+ }
+
+ items = kcalloc(cnt, sizeof(*items), GFP_KERNEL);
+ seqnos = kcalloc(cnt, sizeof(*seqnos), GFP_KERNEL);
+ pathbuf = kmalloc(PATH_MAX, GFP_KERNEL);
+ if (!items || !seqnos || !pathbuf) {
+ ret = -ENOMEM;
+ goto out_alloc;
+ }
+
+ cnt = sunrpc_cache_requests_snapshot(cd, items, seqnos, cnt);
+
+ hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid,
+ cb->nlh->nlmsg_seq, &nfsd_nl_family,
+ NLM_F_MULTI, NFSD_CMD_SVC_EXPORT_GET_REQS);
+ if (!hdr) {
+ ret = -ENOBUFS;
+ goto out_put;
+ }
+
+ for (i = 0; i < cnt; i++) {
+ struct svc_export *exp;
+ struct nlattr *nest;
+ char *pth;
+
+ exp = container_of(items[i], struct svc_export, h);
+
+ pth = d_path(&exp->ex_path, pathbuf, PATH_MAX);
+ if (IS_ERR(pth))
+ continue;
+
+ nest = nla_nest_start(skb,
+ NFSD_A_SVC_EXPORT_REQS_REQUESTS);
+ if (!nest) {
+ ret = -ENOBUFS;
+ goto out_cancel;
+ }
+
+ if (nla_put_u64_64bit(skb, NFSD_A_SVC_EXPORT_SEQNO,
+ seqnos[i], 0) ||
+ nla_put_string(skb, NFSD_A_SVC_EXPORT_CLIENT,
+ exp->ex_client->name) ||
+ nla_put_string(skb, NFSD_A_SVC_EXPORT_PATH, pth)) {
+ nla_nest_cancel(skb, nest);
+ ret = -ENOBUFS;
+ goto out_cancel;
+ }
+
+ nla_nest_end(skb, nest);
+ }
+
+ genlmsg_end(skb, hdr);
+ cb->args[0] = 1;
+ ret = skb->len;
+ goto out_put;
+
+out_cancel:
+ genlmsg_cancel(skb, hdr);
+out_put:
+ for (i = 0; i < cnt; i++)
+ cache_put(items[i], cd);
+out_alloc:
+ kfree(pathbuf);
+ kfree(seqnos);
+ kfree(items);
+out_unlock:
+ mutex_unlock(&nfsd_mutex);
+ return ret;
+}
+
+/**
+ * nfsd_nl_parse_fslocations - parse fslocations from netlink
+ * @attr: NFSD_A_SVC_EXPORT_FSLOCATIONS nested attribute
+ * @fsloc: fslocations struct to fill in
+ *
+ * Returns 0 on success or a negative errno.
+ */
+static int nfsd_nl_parse_fslocations(struct nlattr *attr,
+ struct nfsd4_fs_locations *fsloc)
+{
+ struct nlattr *loc_attr;
+ int rem, count = 0;
+ int err;
+
+ if (fsloc->locations)
+ return -EINVAL;
+
+ /* Count locations first */
+ nla_for_each_nested_type(loc_attr, NFSD_A_FSLOCATIONS_LOCATION,
+ attr, rem)
+ count++;
+
+ if (count > MAX_FS_LOCATIONS)
+ return -EINVAL;
+ if (!count)
+ return 0;
+
+ fsloc->locations = kcalloc(count, sizeof(struct nfsd4_fs_location),
+ GFP_KERNEL);
+ if (!fsloc->locations)
+ return -ENOMEM;
+
+ nla_for_each_nested_type(loc_attr, NFSD_A_FSLOCATIONS_LOCATION,
+ attr, rem) {
+ struct nlattr *tb[NFSD_A_FSLOCATION_PATH + 1];
+ struct nfsd4_fs_location *loc;
+
+ err = nla_parse_nested(tb, NFSD_A_FSLOCATION_PATH, loc_attr,
+ nfsd_fslocation_nl_policy, NULL);
+ if (err)
+ goto out_free;
+
+ if (!tb[NFSD_A_FSLOCATION_HOST] ||
+ !tb[NFSD_A_FSLOCATION_PATH]) {
+ err = -EINVAL;
+ goto out_free;
+ }
+
+ loc = &fsloc->locations[fsloc->locations_count++];
+ loc->hosts = kstrdup(nla_data(tb[NFSD_A_FSLOCATION_HOST]),
+ GFP_KERNEL);
+ loc->path = kstrdup(nla_data(tb[NFSD_A_FSLOCATION_PATH]),
+ GFP_KERNEL);
+ if (!loc->hosts || !loc->path) {
+ err = -ENOMEM;
+ goto out_free;
+ }
+ }
+
+ return 0;
+out_free:
+ nfsd4_fslocs_free(fsloc);
+ return err;
+}
+
+static struct svc_export *svc_export_update(struct svc_export *new,
+ struct svc_export *old);
+static struct svc_export *svc_export_lookup(struct svc_export *);
+static int check_export(const struct path *path, int *flags,
+ unsigned char *uuid);
+
+/**
+ * @cd: cache_detail for the svc_export cache
+ * @attr: nested attribute containing svc-export fields
+ *
+ * Parses one svc-export entry from a netlink message and updates the
+ * cache. Mirrors the logic in svc_export_parse().
+ *
+ * Returns 0 on success or a negative errno.
+ */
+static int nfsd_nl_parse_one_export(struct cache_detail *cd,
+ struct nlattr *attr)
+{
+ struct nlattr *tb[NFSD_A_SVC_EXPORT_FSID + 1];
+ struct auth_domain *dom = NULL;
+ struct svc_export exp = {}, *expp;
+ struct nlattr *secinfo_attr;
+ struct timespec64 boot;
+ int err, rem;
+
+ err = nla_parse_nested(tb, NFSD_A_SVC_EXPORT_FSID, attr,
+ nfsd_svc_export_nl_policy, NULL);
+ if (err)
+ return err;
+
+ /* client (required) */
+ if (!tb[NFSD_A_SVC_EXPORT_CLIENT])
+ return -EINVAL;
+
+ dom = auth_domain_find(nla_data(tb[NFSD_A_SVC_EXPORT_CLIENT]));
+ if (!dom)
+ return -ENOENT;
+
+ /* path (required) */
+ if (!tb[NFSD_A_SVC_EXPORT_PATH]) {
+ err = -EINVAL;
+ goto out_dom;
+ }
+
+ err = kern_path(nla_data(tb[NFSD_A_SVC_EXPORT_PATH]), 0,
+ &exp.ex_path);
+ if (err)
+ goto out_dom;
+
+ exp.ex_client = dom;
+ exp.cd = cd;
+ exp.ex_devid_map = NULL;
+ exp.ex_xprtsec_modes = NFSEXP_XPRTSEC_ALL;
+
+ /* expiry (required, wallclock seconds) */
+ if (!tb[NFSD_A_SVC_EXPORT_EXPIRY]) {
+ err = -EINVAL;
+ goto out_path;
+ }
+ getboottime64(&boot);
+ exp.h.expiry_time = nla_get_u64(tb[NFSD_A_SVC_EXPORT_EXPIRY]) -
+ boot.tv_sec;
+
+ if (tb[NFSD_A_SVC_EXPORT_NEGATIVE]) {
+ set_bit(CACHE_NEGATIVE, &exp.h.flags);
+ } else {
+ /* flags */
+ if (tb[NFSD_A_SVC_EXPORT_FLAGS])
+ exp.ex_flags = nla_get_u32(tb[NFSD_A_SVC_EXPORT_FLAGS]);
+
+ /* anon uid */
+ if (tb[NFSD_A_SVC_EXPORT_ANON_UID]) {
+ u32 uid = nla_get_u32(tb[NFSD_A_SVC_EXPORT_ANON_UID]);
+
+ exp.ex_anon_uid = make_kuid(current_user_ns(), uid);
+ }
+
+ /* anon gid */
+ if (tb[NFSD_A_SVC_EXPORT_ANON_GID]) {
+ u32 gid = nla_get_u32(tb[NFSD_A_SVC_EXPORT_ANON_GID]);
+
+ exp.ex_anon_gid = make_kgid(current_user_ns(), gid);
+ }
+
+ /* fsid */
+ if (tb[NFSD_A_SVC_EXPORT_FSID])
+ exp.ex_fsid = nla_get_s32(tb[NFSD_A_SVC_EXPORT_FSID]);
+
+ /* fslocations */
+ if (tb[NFSD_A_SVC_EXPORT_FSLOCATIONS]) {
+ struct nlattr *fsl = tb[NFSD_A_SVC_EXPORT_FSLOCATIONS];
+
+ err = nfsd_nl_parse_fslocations(fsl,
+ &exp.ex_fslocs);
+ if (err)
+ goto out_path;
+ }
+
+ /* uuid */
+ if (tb[NFSD_A_SVC_EXPORT_UUID]) {
+ if (nla_len(tb[NFSD_A_SVC_EXPORT_UUID]) !=
+ EX_UUID_LEN) {
+ err = -EINVAL;
+ goto out_fslocs;
+ }
+ exp.ex_uuid = kmemdup(nla_data(tb[NFSD_A_SVC_EXPORT_UUID]),
+ EX_UUID_LEN, GFP_KERNEL);
+ if (!exp.ex_uuid) {
+ err = -ENOMEM;
+ goto out_fslocs;
+ }
+ }
+
+ /* secinfo (multi-attr) */
+ nla_for_each_nested_type(secinfo_attr,
+ NFSD_A_SVC_EXPORT_SECINFO,
+ attr, rem) {
+ struct nlattr *ftb[NFSD_A_AUTH_FLAVOR_FLAGS + 1];
+ struct exp_flavor_info *f;
+
+ if (exp.ex_nflavors >= MAX_SECINFO_LIST) {
+ err = -EINVAL;
+ goto out_uuid;
+ }
+
+ err = nla_parse_nested(ftb,
+ NFSD_A_AUTH_FLAVOR_FLAGS,
+ secinfo_attr,
+ nfsd_auth_flavor_nl_policy,
+ NULL);
+ if (err)
+ goto out_uuid;
+
+ f = &exp.ex_flavors[exp.ex_nflavors++];
+
+ if (ftb[NFSD_A_AUTH_FLAVOR_PSEUDOFLAVOR])
+ f->pseudoflavor = nla_get_u32(ftb[NFSD_A_AUTH_FLAVOR_PSEUDOFLAVOR]);
+
+ if (ftb[NFSD_A_AUTH_FLAVOR_FLAGS])
+ f->flags = nla_get_u32(ftb[NFSD_A_AUTH_FLAVOR_FLAGS]);
+
+ /* Only some flags are allowed to differ between flavors: */
+ if (~NFSEXP_SECINFO_FLAGS & (f->flags ^ exp.ex_flags)) {
+ err = -EINVAL;
+ goto out_uuid;
+ }
+ }
+
+ /* xprtsec (multi-attr u32) */
+ if (tb[NFSD_A_SVC_EXPORT_XPRTSEC]) {
+ struct nlattr *xp_attr;
+
+ exp.ex_xprtsec_modes = 0;
+ nla_for_each_nested_type(xp_attr,
+ NFSD_A_SVC_EXPORT_XPRTSEC,
+ attr, rem) {
+ u32 mode = nla_get_u32(xp_attr);
+
+ if (mode > NFSEXP_XPRTSEC_MTLS) {
+ err = -EINVAL;
+ goto out_uuid;
+ }
+ exp.ex_xprtsec_modes |= mode;
+ }
+ }
+
+ err = check_export(&exp.ex_path, &exp.ex_flags,
+ exp.ex_uuid);
+ if (err)
+ goto out_uuid;
+
+ if (exp.h.expiry_time < seconds_since_boot())
+ goto out_uuid;
+
+ err = -EINVAL;
+ if (!uid_valid(exp.ex_anon_uid))
+ goto out_uuid;
+ if (!gid_valid(exp.ex_anon_gid))
+ goto out_uuid;
+ err = 0;
+
+ nfsd4_setup_layout_type(&exp);
+ }
+
+ expp = svc_export_lookup(&exp);
+ if (!expp) {
+ err = -ENOMEM;
+ goto out_uuid;
+ }
+ expp = svc_export_update(&exp, expp);
+ if (expp) {
+ trace_nfsd_export_update(expp);
+ cache_flush();
+ exp_put(expp);
+ } else {
+ err = -ENOMEM;
+ }
+
+out_uuid:
+ kfree(exp.ex_uuid);
+out_fslocs:
+ nfsd4_fslocs_free(&exp.ex_fslocs);
+out_path:
+ path_put(&exp.ex_path);
+out_dom:
+ auth_domain_put(dom);
+ return err;
+}
+
+/**
+ * nfsd_nl_svc_export_set_reqs_doit - respond to svc_export requests
+ * @skb: reply buffer
+ * @info: netlink metadata and command arguments
+ *
+ * Parse one or more svc_export cache responses from userspace and
+ * update the export cache accordingly.
+ *
+ * Returns 0 on success or a negative errno.
+ */
+int nfsd_nl_svc_export_set_reqs_doit(struct sk_buff *skb,
+ struct genl_info *info)
+{
+ struct nfsd_net *nn;
+ struct cache_detail *cd;
+ const struct nlattr *attr;
+ int rem, ret = 0;
+
+ nn = net_generic(genl_info_net(info), nfsd_net_id);
+
+ mutex_lock(&nfsd_mutex);
+
+ cd = nn->svc_export_cache;
+ if (!cd) {
+ ret = -ENODEV;
+ goto out_unlock;
+ }
+
+ nlmsg_for_each_attr_type(attr, NFSD_A_SVC_EXPORT_REQS_REQUESTS,
+ info->nlhdr, GENL_HDRLEN, rem) {
+ ret = nfsd_nl_parse_one_export(cd, (struct nlattr *)attr);
+ if (ret)
+ break;
+ }
+
+out_unlock:
+ mutex_unlock(&nfsd_mutex);
+ return ret;
+}
+
static int svc_export_upcall(struct cache_detail *cd, struct cache_head *h)
{
return sunrpc_cache_upcall(cd, h);
}
+static int svc_export_notify(struct cache_detail *cd, struct cache_head *h)
+{
+ return nfsd_cache_notify(cd, h, NFSD_CACHE_TYPE_SVC_EXPORT);
+}
+
static void svc_export_request(struct cache_detail *cd,
struct cache_head *h,
char **bpp, int *blen)
@@ -410,10 +845,6 @@ static void svc_export_request(struct cache_detail *cd,
(*bpp)[-1] = '\n';
}
-static struct svc_export *svc_export_update(struct svc_export *new,
- struct svc_export *old);
-static struct svc_export *svc_export_lookup(struct svc_export *);
-
static int check_export(const struct path *path, int *flags, unsigned char *uuid)
{
struct inode *inode = d_inode(path->dentry);
@@ -907,6 +1338,7 @@ static const struct cache_detail svc_export_cache_template = {
.name = "nfsd.export",
.cache_put = svc_export_put,
.cache_upcall = svc_export_upcall,
+ .cache_notify = svc_export_notify,
.cache_request = svc_export_request,
.cache_parse = svc_export_parse,
.cache_show = svc_export_show,
diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c
index dc294c4f8c58a6692b9dfbeb98fedbd649ae1b95..7edcbe4504a91966db4805a099368edb92ca38ee 100644
--- a/fs/nfsd/nfsctl.c
+++ b/fs/nfsd/nfsctl.c
@@ -2215,6 +2215,34 @@ int nfsd_nl_pool_mode_get_doit(struct sk_buff *skb, struct genl_info *info)
return err;
}
+int nfsd_cache_notify(struct cache_detail *cd, struct cache_head *h, u32 cache_type)
+{
+ struct genlmsghdr *hdr;
+ struct sk_buff *msg;
+
+ if (!genl_has_listeners(&nfsd_nl_family, cd->net, NFSD_NLGRP_EXPORTD))
+ return -ENOLINK;
+
+ msg = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+
+ hdr = genlmsg_put(msg, 0, 0, &nfsd_nl_family, 0, NFSD_CMD_CACHE_NOTIFY);
+ if (!hdr) {
+ nlmsg_free(msg);
+ return -ENOMEM;
+ }
+
+ if (nla_put_u32(msg, NFSD_A_CACHE_NOTIFY_CACHE_TYPE, cache_type)) {
+ nlmsg_free(msg);
+ return -ENOMEM;
+ }
+
+ genlmsg_end(msg, hdr);
+ return genlmsg_multicast_netns(&nfsd_nl_family, cd->net, msg, 0,
+ NFSD_NLGRP_EXPORTD, GFP_KERNEL);
+}
+
/**
* nfsd_net_init - Prepare the nfsd_net portion of a new net namespace
* @net: a freshly-created network namespace
diff --git a/fs/nfsd/nfsd.h b/fs/nfsd/nfsd.h
index 260bf8badb032de18f973556fa5deabe7e67870d..709da3c7a5fa7cdb4f5e91b488b02295c5f54392 100644
--- a/fs/nfsd/nfsd.h
+++ b/fs/nfsd/nfsd.h
@@ -108,7 +108,7 @@ struct dentry *nfsd_client_mkdir(struct nfsd_net *nn,
const struct tree_descr *,
struct dentry **fdentries);
void nfsd_client_rmdir(struct dentry *dentry);
-
+int nfsd_cache_notify(struct cache_detail *cd, struct cache_head *h, u32 cache_type);
#if defined(CONFIG_NFSD_V2_ACL) || defined(CONFIG_NFSD_V3_ACL)
#ifdef CONFIG_NFSD_V2_ACL
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH 12/14] nfsd: add netlink upcall for the nfsd.fh cache
2026-03-16 15:14 [PATCH 00/14] nfsd/sunrpc: add support for netlink upcalls for mountd/exportd Jeff Layton
` (10 preceding siblings ...)
2026-03-16 15:14 ` [PATCH 11/14] nfsd: add netlink upcall for the svc_export cache Jeff Layton
@ 2026-03-16 15:14 ` Jeff Layton
2026-03-16 15:14 ` [PATCH 13/14] sunrpc: add SUNRPC_CMD_CACHE_FLUSH netlink command Jeff Layton
2026-03-16 15:14 ` [PATCH 14/14] nfsd: add NFSD_CMD_CACHE_FLUSH " Jeff Layton
13 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-03-16 15:14 UTC (permalink / raw)
To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
Cc: Trond Myklebust, Anna Schumaker, linux-nfs, linux-kernel,
Jeff Layton
Add netlink-based cache upcall support for the expkey (nfsd.fh) cache,
following the same pattern as the existing svc_export netlink support.
Add expkey to the cache-type enum, a new expkey attribute-set with
client, fsidtype, fsid, negative, expiry, and path fields, and the
expkey-get-reqs / expkey-set-reqs operations to the nfsd YAML spec
and generated headers.
Implement nfsd_nl_expkey_get_reqs_dumpit() which snapshots pending
expkey cache requests and sends each entry's seqno, client name,
fsidtype, and fsid over netlink.
Implement nfsd_nl_expkey_set_reqs_doit() which parses expkey cache
responses from userspace (client, fsidtype, fsid, expiry, and path
or negative flag) and updates the cache via svc_expkey_lookup() /
svc_expkey_update().
Wire up the expkey_notify() callback in svc_expkey_cache_template
so cache misses trigger NFSD_CMD_CACHE_NOTIFY multicast events with
NFSD_CACHE_TYPE_EXPKEY.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
Documentation/netlink/specs/nfsd.yaml | 52 ++++++-
fs/nfsd/export.c | 263 ++++++++++++++++++++++++++++++++++
fs/nfsd/netlink.c | 34 +++++
fs/nfsd/netlink.h | 4 +
include/uapi/linux/nfsd_netlink.h | 23 +++
5 files changed, 375 insertions(+), 1 deletion(-)
diff --git a/Documentation/netlink/specs/nfsd.yaml b/Documentation/netlink/specs/nfsd.yaml
index 08322bc3dee7458e6a202372dd332067d03e1be6..65fd9caf6bf66abca9302ffa3b947b589dbf0dd3 100644
--- a/Documentation/netlink/specs/nfsd.yaml
+++ b/Documentation/netlink/specs/nfsd.yaml
@@ -10,7 +10,7 @@ definitions:
-
type: flags
name: cache-type
- entries: [svc_export]
+ entries: [svc_export, expkey]
-
type: flags
name: export-flags
@@ -271,6 +271,38 @@ attribute-sets:
type: nest
nested-attributes: svc-export
multi-attr: true
+ -
+ name: expkey
+ attributes:
+ -
+ name: seqno
+ type: u64
+ -
+ name: client
+ type: string
+ -
+ name: fsidtype
+ type: u8
+ -
+ name: fsid
+ type: binary
+ -
+ name: negative
+ type: flag
+ -
+ name: expiry
+ type: u64
+ -
+ name: path
+ type: string
+ -
+ name: expkey-reqs
+ attributes:
+ -
+ name: requests
+ type: nest
+ nested-attributes: expkey
+ multi-attr: true
operations:
list:
@@ -398,6 +430,24 @@ operations:
request:
attributes:
- requests
+ -
+ name: expkey-get-reqs
+ doc: Dump all pending expkey requests
+ attribute-set: expkey-reqs
+ flags: [admin-perm]
+ dump:
+ request:
+ attributes:
+ - requests
+ -
+ name: expkey-set-reqs
+ doc: Respond to one or more expkey requests
+ attribute-set: expkey-reqs
+ flags: [admin-perm]
+ do:
+ request:
+ attributes:
+ - requests
mcast-groups:
list:
diff --git a/fs/nfsd/export.c b/fs/nfsd/export.c
index 45a8e7b9866377131aac8639debdc4f877dc5788..33988de28e95d30948b2f7e44453742c4cb62697 100644
--- a/fs/nfsd/export.c
+++ b/fs/nfsd/export.c
@@ -266,12 +266,18 @@ static void expkey_flush(void)
mutex_unlock(&nfsd_mutex);
}
+static int expkey_notify(struct cache_detail *cd, struct cache_head *h)
+{
+ return nfsd_cache_notify(cd, h, NFSD_CACHE_TYPE_EXPKEY);
+}
+
static const struct cache_detail svc_expkey_cache_template = {
.owner = THIS_MODULE,
.hash_size = EXPKEY_HASHMAX,
.name = "nfsd.fh",
.cache_put = expkey_put,
.cache_upcall = expkey_upcall,
+ .cache_notify = expkey_notify,
.cache_request = expkey_request,
.cache_parse = expkey_parse,
.cache_show = expkey_show,
@@ -322,6 +328,263 @@ svc_expkey_update(struct cache_detail *cd, struct svc_expkey *new,
return NULL;
}
+/**
+ * nfsd_nl_expkey_get_reqs_dumpit - dump pending expkey requests
+ * @skb: reply buffer
+ * @cb: netlink metadata and command arguments
+ *
+ * Walk the expkey cache's pending request list and create a netlink
+ * message with a nested entry for each cache_request, containing the
+ * seqno, client string, fsidtype and fsid.
+ *
+ * Returns the size of the reply or a negative errno.
+ */
+int nfsd_nl_expkey_get_reqs_dumpit(struct sk_buff *skb,
+ struct netlink_callback *cb)
+{
+ struct nfsd_net *nn;
+ struct cache_detail *cd;
+ struct cache_head **items;
+ u64 *seqnos;
+ int cnt, i;
+ void *hdr;
+ int ret;
+
+ nn = net_generic(sock_net(skb->sk), nfsd_net_id);
+
+ mutex_lock(&nfsd_mutex);
+
+ cd = nn->svc_expkey_cache;
+ if (!cd) {
+ ret = -ENODEV;
+ goto out_unlock;
+ }
+
+ /* Second call means we've already dumped everything */
+ if (cb->args[0]) {
+ ret = 0;
+ goto out_unlock;
+ }
+
+ cnt = sunrpc_cache_requests_count(cd);
+ if (!cnt) {
+ ret = 0;
+ goto out_unlock;
+ }
+
+ items = kcalloc(cnt, sizeof(*items), GFP_KERNEL);
+ seqnos = kcalloc(cnt, sizeof(*seqnos), GFP_KERNEL);
+ if (!items || !seqnos) {
+ ret = -ENOMEM;
+ goto out_alloc;
+ }
+
+ cnt = sunrpc_cache_requests_snapshot(cd, items, seqnos, cnt);
+
+ hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid,
+ cb->nlh->nlmsg_seq, &nfsd_nl_family,
+ NLM_F_MULTI, NFSD_CMD_EXPKEY_GET_REQS);
+ if (!hdr) {
+ ret = -ENOBUFS;
+ goto out_put;
+ }
+
+ for (i = 0; i < cnt; i++) {
+ struct svc_expkey *ek;
+ struct nlattr *nest;
+
+ ek = container_of(items[i], struct svc_expkey, h);
+
+ nest = nla_nest_start(skb, NFSD_A_EXPKEY_REQS_REQUESTS);
+ if (!nest) {
+ ret = -ENOBUFS;
+ goto out_cancel;
+ }
+
+ if (nla_put_u64_64bit(skb, NFSD_A_EXPKEY_SEQNO,
+ seqnos[i], 0) ||
+ nla_put_string(skb, NFSD_A_EXPKEY_CLIENT,
+ ek->ek_client->name) ||
+ nla_put_u8(skb, NFSD_A_EXPKEY_FSIDTYPE,
+ ek->ek_fsidtype) ||
+ nla_put(skb, NFSD_A_EXPKEY_FSID,
+ key_len(ek->ek_fsidtype), ek->ek_fsid)) {
+ nla_nest_cancel(skb, nest);
+ ret = -ENOBUFS;
+ goto out_cancel;
+ }
+
+ nla_nest_end(skb, nest);
+ }
+
+ genlmsg_end(skb, hdr);
+ cb->args[0] = 1;
+ ret = skb->len;
+ goto out_put;
+
+out_cancel:
+ genlmsg_cancel(skb, hdr);
+out_put:
+ for (i = 0; i < cnt; i++)
+ cache_put(items[i], cd);
+out_alloc:
+ kfree(seqnos);
+ kfree(items);
+out_unlock:
+ mutex_unlock(&nfsd_mutex);
+ return ret;
+}
+
+/**
+ * nfsd_nl_parse_one_expkey - parse one expkey entry from netlink
+ * @cd: cache_detail for the expkey cache
+ * @attr: nested attribute containing expkey fields
+ *
+ * Parses one expkey entry from a netlink message and updates the
+ * cache. Mirrors the logic in expkey_parse().
+ *
+ * Returns 0 on success or a negative errno.
+ */
+static int nfsd_nl_parse_one_expkey(struct cache_detail *cd,
+ struct nlattr *attr)
+{
+ struct nlattr *tb[NFSD_A_EXPKEY_PATH + 1];
+ struct auth_domain *dom = NULL;
+ struct svc_expkey key;
+ struct svc_expkey *ek = NULL;
+ struct timespec64 boot;
+ int err;
+ u8 fsidtype;
+ int fsid_len;
+
+ err = nla_parse_nested(tb, NFSD_A_EXPKEY_PATH, attr,
+ nfsd_expkey_nl_policy, NULL);
+ if (err)
+ return err;
+
+ /* client (required) */
+ if (!tb[NFSD_A_EXPKEY_CLIENT])
+ return -EINVAL;
+
+ dom = auth_domain_find(nla_data(tb[NFSD_A_EXPKEY_CLIENT]));
+ if (!dom)
+ return -ENOENT;
+
+ /* fsidtype (required) */
+ if (!tb[NFSD_A_EXPKEY_FSIDTYPE]) {
+ err = -EINVAL;
+ goto out_dom;
+ }
+ fsidtype = nla_get_u8(tb[NFSD_A_EXPKEY_FSIDTYPE]);
+ if (key_len(fsidtype) == 0) {
+ err = -EINVAL;
+ goto out_dom;
+ }
+
+ /* fsid (required) */
+ if (!tb[NFSD_A_EXPKEY_FSID]) {
+ err = -EINVAL;
+ goto out_dom;
+ }
+ fsid_len = nla_len(tb[NFSD_A_EXPKEY_FSID]);
+ if (fsid_len != key_len(fsidtype)) {
+ err = -EINVAL;
+ goto out_dom;
+ }
+
+ /* expiry (required, wallclock seconds) */
+ if (!tb[NFSD_A_EXPKEY_EXPIRY]) {
+ err = -EINVAL;
+ goto out_dom;
+ }
+
+ key.h.flags = 0;
+ getboottime64(&boot);
+ key.h.expiry_time = nla_get_u64(tb[NFSD_A_EXPKEY_EXPIRY]) -
+ boot.tv_sec;
+ key.ek_client = dom;
+ key.ek_fsidtype = fsidtype;
+ memcpy(key.ek_fsid, nla_data(tb[NFSD_A_EXPKEY_FSID]), fsid_len);
+
+ ek = svc_expkey_lookup(cd, &key);
+ if (!ek) {
+ err = -ENOMEM;
+ goto out_dom;
+ }
+
+ if (tb[NFSD_A_EXPKEY_NEGATIVE]) {
+ set_bit(CACHE_NEGATIVE, &key.h.flags);
+ ek = svc_expkey_update(cd, &key, ek);
+ if (ek)
+ trace_nfsd_expkey_update(ek, NULL);
+ else
+ err = -ENOMEM;
+ } else if (tb[NFSD_A_EXPKEY_PATH]) {
+ err = kern_path(nla_data(tb[NFSD_A_EXPKEY_PATH]), 0,
+ &key.ek_path);
+ if (err)
+ goto out_ek;
+ ek = svc_expkey_update(cd, &key, ek);
+ if (ek)
+ trace_nfsd_expkey_update(ek,
+ nla_data(tb[NFSD_A_EXPKEY_PATH]));
+ else
+ err = -ENOMEM;
+ path_put(&key.ek_path);
+ } else {
+ err = -EINVAL;
+ goto out_ek;
+ }
+
+ cache_flush();
+
+out_ek:
+ if (ek)
+ cache_put(&ek->h, cd);
+out_dom:
+ auth_domain_put(dom);
+ return err;
+}
+
+/**
+ * nfsd_nl_expkey_set_reqs_doit - respond to expkey requests
+ * @skb: reply buffer
+ * @info: netlink metadata and command arguments
+ *
+ * Parse one or more expkey cache responses from userspace and
+ * update the expkey cache accordingly.
+ *
+ * Returns 0 on success or a negative errno.
+ */
+int nfsd_nl_expkey_set_reqs_doit(struct sk_buff *skb,
+ struct genl_info *info)
+{
+ struct nfsd_net *nn;
+ struct cache_detail *cd;
+ const struct nlattr *attr;
+ int rem, ret = 0;
+
+ nn = net_generic(genl_info_net(info), nfsd_net_id);
+
+ mutex_lock(&nfsd_mutex);
+
+ cd = nn->svc_expkey_cache;
+ if (!cd) {
+ ret = -ENODEV;
+ goto out_unlock;
+ }
+
+ nlmsg_for_each_attr_type(attr, NFSD_A_EXPKEY_REQS_REQUESTS,
+ info->nlhdr, GENL_HDRLEN, rem) {
+ ret = nfsd_nl_parse_one_expkey(cd, (struct nlattr *)attr);
+ if (ret)
+ break;
+ }
+
+out_unlock:
+ mutex_unlock(&nfsd_mutex);
+ return ret;
+}
#define EXPORT_HASHBITS 8
#define EXPORT_HASHMAX (1<< EXPORT_HASHBITS)
diff --git a/fs/nfsd/netlink.c b/fs/nfsd/netlink.c
index fb401d7302afb9e41cb074581f7b94e8ece6cf0c..394230e250a5b07fa0bb6a5b76f7282758e94565 100644
--- a/fs/nfsd/netlink.c
+++ b/fs/nfsd/netlink.c
@@ -17,6 +17,16 @@ const struct nla_policy nfsd_auth_flavor_nl_policy[NFSD_A_AUTH_FLAVOR_FLAGS + 1]
[NFSD_A_AUTH_FLAVOR_FLAGS] = NLA_POLICY_MASK(NLA_U32, 0x3ffff),
};
+const struct nla_policy nfsd_expkey_nl_policy[NFSD_A_EXPKEY_PATH + 1] = {
+ [NFSD_A_EXPKEY_SEQNO] = { .type = NLA_U64, },
+ [NFSD_A_EXPKEY_CLIENT] = { .type = NLA_NUL_STRING, },
+ [NFSD_A_EXPKEY_FSIDTYPE] = { .type = NLA_U8, },
+ [NFSD_A_EXPKEY_FSID] = { .type = NLA_BINARY, },
+ [NFSD_A_EXPKEY_NEGATIVE] = { .type = NLA_FLAG, },
+ [NFSD_A_EXPKEY_EXPIRY] = { .type = NLA_U64, },
+ [NFSD_A_EXPKEY_PATH] = { .type = NLA_NUL_STRING, },
+};
+
const struct nla_policy nfsd_fslocation_nl_policy[NFSD_A_FSLOCATION_PATH + 1] = {
[NFSD_A_FSLOCATION_HOST] = { .type = NLA_NUL_STRING, },
[NFSD_A_FSLOCATION_PATH] = { .type = NLA_NUL_STRING, },
@@ -88,6 +98,16 @@ static const struct nla_policy nfsd_svc_export_set_reqs_nl_policy[NFSD_A_SVC_EXP
[NFSD_A_SVC_EXPORT_REQS_REQUESTS] = NLA_POLICY_NESTED(nfsd_svc_export_nl_policy),
};
+/* NFSD_CMD_EXPKEY_GET_REQS - dump */
+static const struct nla_policy nfsd_expkey_get_reqs_nl_policy[NFSD_A_EXPKEY_REQS_REQUESTS + 1] = {
+ [NFSD_A_EXPKEY_REQS_REQUESTS] = NLA_POLICY_NESTED(nfsd_expkey_nl_policy),
+};
+
+/* NFSD_CMD_EXPKEY_SET_REQS - do */
+static const struct nla_policy nfsd_expkey_set_reqs_nl_policy[NFSD_A_EXPKEY_REQS_REQUESTS + 1] = {
+ [NFSD_A_EXPKEY_REQS_REQUESTS] = NLA_POLICY_NESTED(nfsd_expkey_nl_policy),
+};
+
/* Ops table for nfsd */
static const struct genl_split_ops nfsd_nl_ops[] = {
{
@@ -157,6 +177,20 @@ static const struct genl_split_ops nfsd_nl_ops[] = {
.maxattr = NFSD_A_SVC_EXPORT_REQS_REQUESTS,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
},
+ {
+ .cmd = NFSD_CMD_EXPKEY_GET_REQS,
+ .dumpit = nfsd_nl_expkey_get_reqs_dumpit,
+ .policy = nfsd_expkey_get_reqs_nl_policy,
+ .maxattr = NFSD_A_EXPKEY_REQS_REQUESTS,
+ .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DUMP,
+ },
+ {
+ .cmd = NFSD_CMD_EXPKEY_SET_REQS,
+ .doit = nfsd_nl_expkey_set_reqs_doit,
+ .policy = nfsd_expkey_set_reqs_nl_policy,
+ .maxattr = NFSD_A_EXPKEY_REQS_REQUESTS,
+ .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
+ },
};
static const struct genl_multicast_group nfsd_nl_mcgrps[] = {
diff --git a/fs/nfsd/netlink.h b/fs/nfsd/netlink.h
index d6ed8d9b0bb149faa4d6493ba94972addf9c26ed..f5b3387772850692b220bbbf8a66bc416b67801e 100644
--- a/fs/nfsd/netlink.h
+++ b/fs/nfsd/netlink.h
@@ -14,6 +14,7 @@
/* Common nested types */
extern const struct nla_policy nfsd_auth_flavor_nl_policy[NFSD_A_AUTH_FLAVOR_FLAGS + 1];
+extern const struct nla_policy nfsd_expkey_nl_policy[NFSD_A_EXPKEY_PATH + 1];
extern const struct nla_policy nfsd_fslocation_nl_policy[NFSD_A_FSLOCATION_PATH + 1];
extern const struct nla_policy nfsd_fslocations_nl_policy[NFSD_A_FSLOCATIONS_LOCATION + 1];
extern const struct nla_policy nfsd_sock_nl_policy[NFSD_A_SOCK_TRANSPORT_NAME + 1];
@@ -34,6 +35,9 @@ int nfsd_nl_svc_export_get_reqs_dumpit(struct sk_buff *skb,
struct netlink_callback *cb);
int nfsd_nl_svc_export_set_reqs_doit(struct sk_buff *skb,
struct genl_info *info);
+int nfsd_nl_expkey_get_reqs_dumpit(struct sk_buff *skb,
+ struct netlink_callback *cb);
+int nfsd_nl_expkey_set_reqs_doit(struct sk_buff *skb, struct genl_info *info);
enum {
NFSD_NLGRP_NONE,
diff --git a/include/uapi/linux/nfsd_netlink.h b/include/uapi/linux/nfsd_netlink.h
index 1ba1c2c167fd06cd0c845d947f5a03702356d991..e96cc1d23366bce13624084fd476dda65aef140a 100644
--- a/include/uapi/linux/nfsd_netlink.h
+++ b/include/uapi/linux/nfsd_netlink.h
@@ -12,6 +12,7 @@
enum nfsd_cache_type {
NFSD_CACHE_TYPE_SVC_EXPORT = 1,
+ NFSD_CACHE_TYPE_EXPKEY = 2,
};
enum nfsd_export_flags {
@@ -177,6 +178,26 @@ enum {
NFSD_A_SVC_EXPORT_REQS_MAX = (__NFSD_A_SVC_EXPORT_REQS_MAX - 1)
};
+enum {
+ NFSD_A_EXPKEY_SEQNO = 1,
+ NFSD_A_EXPKEY_CLIENT,
+ NFSD_A_EXPKEY_FSIDTYPE,
+ NFSD_A_EXPKEY_FSID,
+ NFSD_A_EXPKEY_NEGATIVE,
+ NFSD_A_EXPKEY_EXPIRY,
+ NFSD_A_EXPKEY_PATH,
+
+ __NFSD_A_EXPKEY_MAX,
+ NFSD_A_EXPKEY_MAX = (__NFSD_A_EXPKEY_MAX - 1)
+};
+
+enum {
+ NFSD_A_EXPKEY_REQS_REQUESTS = 1,
+
+ __NFSD_A_EXPKEY_REQS_MAX,
+ NFSD_A_EXPKEY_REQS_MAX = (__NFSD_A_EXPKEY_REQS_MAX - 1)
+};
+
enum {
NFSD_CMD_RPC_STATUS_GET = 1,
NFSD_CMD_THREADS_SET,
@@ -190,6 +211,8 @@ enum {
NFSD_CMD_CACHE_NOTIFY,
NFSD_CMD_SVC_EXPORT_GET_REQS,
NFSD_CMD_SVC_EXPORT_SET_REQS,
+ NFSD_CMD_EXPKEY_GET_REQS,
+ NFSD_CMD_EXPKEY_SET_REQS,
__NFSD_CMD_MAX,
NFSD_CMD_MAX = (__NFSD_CMD_MAX - 1)
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH 13/14] sunrpc: add SUNRPC_CMD_CACHE_FLUSH netlink command
2026-03-16 15:14 [PATCH 00/14] nfsd/sunrpc: add support for netlink upcalls for mountd/exportd Jeff Layton
` (11 preceding siblings ...)
2026-03-16 15:14 ` [PATCH 12/14] nfsd: add netlink upcall for the nfsd.fh cache Jeff Layton
@ 2026-03-16 15:14 ` Jeff Layton
2026-03-16 15:14 ` [PATCH 14/14] nfsd: add NFSD_CMD_CACHE_FLUSH " Jeff Layton
13 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-03-16 15:14 UTC (permalink / raw)
To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
Cc: Trond Myklebust, Anna Schumaker, linux-nfs, linux-kernel,
Jeff Layton
Add a new SUNRPC_CMD_CACHE_FLUSH generic netlink command that allows
userspace to flush the sunrpc auth caches (ip_map and unix_gid) without
writing to /proc/net/rpc/*/flush.
An optional SUNRPC_A_CACHE_FLUSH_MASK u32 attribute selects which caches
to flush (bit 1 = ip_map, bit 2 = unix_gid). If the attribute is
omitted, all sunrpc caches are flushed.
This is used by exportfs to replace its /proc-based cache_flush() with a
netlink equivalent, with /proc fallback for older kernels.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
Documentation/netlink/specs/sunrpc_cache.yaml | 17 ++++++++++
include/uapi/linux/sunrpc_netlink.h | 8 +++++
net/sunrpc/netlink.c | 45 +++++++++++++++++++++++++++
net/sunrpc/netlink.h | 4 +++
net/sunrpc/svcauth_unix.c | 32 +++++++++++++++++++
5 files changed, 106 insertions(+)
diff --git a/Documentation/netlink/specs/sunrpc_cache.yaml b/Documentation/netlink/specs/sunrpc_cache.yaml
index ed0ddb61ebcf22b6ad889b0760f8a6f470295dbd..55dabc914dbc8693e10a8765a654b11021b32872 100644
--- a/Documentation/netlink/specs/sunrpc_cache.yaml
+++ b/Documentation/netlink/specs/sunrpc_cache.yaml
@@ -76,6 +76,14 @@ attribute-sets:
type: nest
nested-attributes: unix-gid
multi-attr: true
+ -
+ name: cache-flush
+ attributes:
+ -
+ name: mask
+ type: u32
+ enum: cache-type
+ enum-as-flags: true
operations:
list:
@@ -123,6 +131,15 @@ operations:
request:
attributes:
- requests
+ -
+ name: cache-flush
+ doc: Flush sunrpc caches (ip_map and/or unix_gid)
+ attribute-set: cache-flush
+ flags: [admin-perm]
+ do:
+ request:
+ attributes:
+ - mask
mcast-groups:
list:
diff --git a/include/uapi/linux/sunrpc_netlink.h b/include/uapi/linux/sunrpc_netlink.h
index d71c623e92aba4566e3114cc23d0aa553cbdb885..34677f0ec2f958961f1f460c1dc81c8377cc5157 100644
--- a/include/uapi/linux/sunrpc_netlink.h
+++ b/include/uapi/linux/sunrpc_netlink.h
@@ -59,12 +59,20 @@ enum {
SUNRPC_A_UNIX_GID_REQS_MAX = (__SUNRPC_A_UNIX_GID_REQS_MAX - 1)
};
+enum {
+ SUNRPC_A_CACHE_FLUSH_MASK = 1,
+
+ __SUNRPC_A_CACHE_FLUSH_MAX,
+ SUNRPC_A_CACHE_FLUSH_MAX = (__SUNRPC_A_CACHE_FLUSH_MAX - 1)
+};
+
enum {
SUNRPC_CMD_CACHE_NOTIFY = 1,
SUNRPC_CMD_IP_MAP_GET_REQS,
SUNRPC_CMD_IP_MAP_SET_REQS,
SUNRPC_CMD_UNIX_GID_GET_REQS,
SUNRPC_CMD_UNIX_GID_SET_REQS,
+ SUNRPC_CMD_CACHE_FLUSH,
__SUNRPC_CMD_MAX,
SUNRPC_CMD_MAX = (__SUNRPC_CMD_MAX - 1)
diff --git a/net/sunrpc/netlink.c b/net/sunrpc/netlink.c
index 3ac6b0cac5fece964f6e6591f90d074f40e96af1..47491c2e63ebb8cacf4f8fe2fa913e31541c77a5 100644
--- a/net/sunrpc/netlink.c
+++ b/net/sunrpc/netlink.c
@@ -6,6 +6,7 @@
#include <net/netlink.h>
#include <net/genetlink.h>
+#include <linux/sunrpc/cache.h>
#include "netlink.h"
@@ -49,6 +50,11 @@ static const struct nla_policy sunrpc_unix_gid_set_reqs_nl_policy[SUNRPC_A_UNIX_
[SUNRPC_A_UNIX_GID_REQS_REQUESTS] = NLA_POLICY_NESTED(sunrpc_unix_gid_nl_policy),
};
+/* SUNRPC_CMD_CACHE_FLUSH - do */
+static const struct nla_policy sunrpc_cache_flush_nl_policy[SUNRPC_A_CACHE_FLUSH_MASK + 1] = {
+ [SUNRPC_A_CACHE_FLUSH_MASK] = NLA_POLICY_MASK(NLA_U32, 0x3),
+};
+
/* Ops table for sunrpc */
static const struct genl_split_ops sunrpc_nl_ops[] = {
{
@@ -79,6 +85,13 @@ static const struct genl_split_ops sunrpc_nl_ops[] = {
.maxattr = SUNRPC_A_UNIX_GID_REQS_REQUESTS,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
},
+ {
+ .cmd = SUNRPC_CMD_CACHE_FLUSH,
+ .doit = sunrpc_nl_cache_flush_doit,
+ .policy = sunrpc_cache_flush_nl_policy,
+ .maxattr = SUNRPC_A_CACHE_FLUSH_MASK,
+ .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
+ },
};
static const struct genl_multicast_group sunrpc_nl_mcgrps[] = {
@@ -97,3 +110,35 @@ struct genl_family sunrpc_nl_family __ro_after_init = {
.mcgrps = sunrpc_nl_mcgrps,
.n_mcgrps = ARRAY_SIZE(sunrpc_nl_mcgrps),
};
+
+int sunrpc_cache_notify(struct cache_detail *cd, struct cache_head *h,
+ u32 cache_type)
+{
+ struct genlmsghdr *hdr;
+ struct sk_buff *msg;
+
+ if (!genl_has_listeners(&sunrpc_nl_family, cd->net,
+ SUNRPC_NLGRP_EXPORTD))
+ return -ENOLINK;
+
+ msg = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+
+ hdr = genlmsg_put(msg, 0, 0, &sunrpc_nl_family, 0,
+ SUNRPC_CMD_CACHE_NOTIFY);
+ if (!hdr) {
+ nlmsg_free(msg);
+ return -ENOMEM;
+ }
+
+ if (nla_put_u32(msg, SUNRPC_A_CACHE_NOTIFY_CACHE_TYPE, cache_type)) {
+ nlmsg_free(msg);
+ return -ENOMEM;
+ }
+
+ genlmsg_end(msg, hdr);
+ return genlmsg_multicast_netns(&sunrpc_nl_family, cd->net, msg, 0,
+ SUNRPC_NLGRP_EXPORTD, GFP_KERNEL);
+}
+EXPORT_SYMBOL_GPL(sunrpc_cache_notify);
diff --git a/net/sunrpc/netlink.h b/net/sunrpc/netlink.h
index 2aec57d27a586e4c6b2fc65c7b4505b0996d9577..7eec78cb32ff3c7ae9ed2b1b1c6b75e2a403f68e 100644
--- a/net/sunrpc/netlink.h
+++ b/net/sunrpc/netlink.h
@@ -23,6 +23,7 @@ int sunrpc_nl_unix_gid_get_reqs_dumpit(struct sk_buff *skb,
struct netlink_callback *cb);
int sunrpc_nl_unix_gid_set_reqs_doit(struct sk_buff *skb,
struct genl_info *info);
+int sunrpc_nl_cache_flush_doit(struct sk_buff *skb, struct genl_info *info);
enum {
SUNRPC_NLGRP_NONE,
@@ -31,4 +32,7 @@ enum {
extern struct genl_family sunrpc_nl_family;
+int sunrpc_cache_notify(struct cache_detail *cd, struct cache_head *h,
+ u32 cache_type);
+
#endif /* _LINUX_SUNRPC_GEN_H */
diff --git a/net/sunrpc/svcauth_unix.c b/net/sunrpc/svcauth_unix.c
index b84511ff726c1836f777c802943f6d8e112a0998..dd90beebd74c1e243535d11e753668589ae1fe18 100644
--- a/net/sunrpc/svcauth_unix.c
+++ b/net/sunrpc/svcauth_unix.c
@@ -813,6 +813,38 @@ int sunrpc_nl_unix_gid_set_reqs_doit(struct sk_buff *skb,
return ret;
}
+/**
+ * sunrpc_nl_cache_flush_doit - flush sunrpc caches via netlink
+ * @skb: reply buffer
+ * @info: netlink metadata and command arguments
+ *
+ * Flush the ip_map and/or unix_gid caches. If SUNRPC_A_CACHE_FLUSH_MASK
+ * is provided, only flush the caches indicated by the bitmask (bit 1 =
+ * ip_map, bit 2 = unix_gid). If omitted, flush both.
+ *
+ * Return 0 on success or a negative errno.
+ */
+int sunrpc_nl_cache_flush_doit(struct sk_buff *skb, struct genl_info *info)
+{
+ struct sunrpc_net *sn;
+ u32 mask = ~0U;
+
+ sn = net_generic(genl_info_net(info), sunrpc_net_id);
+
+ if (info->attrs[SUNRPC_A_CACHE_FLUSH_MASK])
+ mask = nla_get_u32(info->attrs[SUNRPC_A_CACHE_FLUSH_MASK]);
+
+ if ((mask & SUNRPC_CACHE_TYPE_IP_MAP) &&
+ sn->ip_map_cache)
+ cache_purge(sn->ip_map_cache);
+
+ if ((mask & SUNRPC_CACHE_TYPE_UNIX_GID) &&
+ sn->unix_gid_cache)
+ cache_purge(sn->unix_gid_cache);
+
+ return 0;
+}
+
static const struct cache_detail unix_gid_cache_template = {
.owner = THIS_MODULE,
.hash_size = GID_HASHMAX,
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH 14/14] nfsd: add NFSD_CMD_CACHE_FLUSH netlink command
2026-03-16 15:14 [PATCH 00/14] nfsd/sunrpc: add support for netlink upcalls for mountd/exportd Jeff Layton
` (12 preceding siblings ...)
2026-03-16 15:14 ` [PATCH 13/14] sunrpc: add SUNRPC_CMD_CACHE_FLUSH netlink command Jeff Layton
@ 2026-03-16 15:14 ` Jeff Layton
13 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-03-16 15:14 UTC (permalink / raw)
To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
Cc: Trond Myklebust, Anna Schumaker, linux-nfs, linux-kernel,
Jeff Layton
Add a new NFSD_CMD_CACHE_FLUSH generic netlink command that allows
userspace to flush the nfsd export caches (svc_export and expkey)
without writing to /proc/net/rpc/*/flush.
An optional NFSD_A_CACHE_FLUSH_MASK u32 attribute selects which caches
to flush (bit 1 = svc_export, bit 2 = expkey). If the attribute is
omitted, all nfsd caches are flushed.
This is used by exportfs to replace its /proc-based cache_flush() with a
netlink equivalent, with /proc fallback for older kernels.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
Documentation/netlink/specs/nfsd.yaml | 17 +++++++++++++++++
fs/nfsd/netlink.c | 12 ++++++++++++
fs/nfsd/netlink.h | 1 +
fs/nfsd/nfsctl.c | 32 ++++++++++++++++++++++++++++++++
include/uapi/linux/nfsd_netlink.h | 8 ++++++++
5 files changed, 70 insertions(+)
diff --git a/Documentation/netlink/specs/nfsd.yaml b/Documentation/netlink/specs/nfsd.yaml
index 65fd9caf6bf66abca9302ffa3b947b589dbf0dd3..09faeff3b7c20032cd61c992170f1e81f922605e 100644
--- a/Documentation/netlink/specs/nfsd.yaml
+++ b/Documentation/netlink/specs/nfsd.yaml
@@ -303,6 +303,14 @@ attribute-sets:
type: nest
nested-attributes: expkey
multi-attr: true
+ -
+ name: cache-flush
+ attributes:
+ -
+ name: mask
+ type: u32
+ enum: cache-type
+ enum-as-flags: true
operations:
list:
@@ -448,6 +456,15 @@ operations:
request:
attributes:
- requests
+ -
+ name: cache-flush
+ doc: Flush nfsd caches (svc_export and/or expkey)
+ attribute-set: cache-flush
+ flags: [admin-perm]
+ do:
+ request:
+ attributes:
+ - mask
mcast-groups:
list:
diff --git a/fs/nfsd/netlink.c b/fs/nfsd/netlink.c
index 394230e250a5b07fa0bb6a5b76f7282758e94565..30c4f8be3df98d8ae98ecddbfac488b5f997ab2f 100644
--- a/fs/nfsd/netlink.c
+++ b/fs/nfsd/netlink.c
@@ -108,6 +108,11 @@ static const struct nla_policy nfsd_expkey_set_reqs_nl_policy[NFSD_A_EXPKEY_REQS
[NFSD_A_EXPKEY_REQS_REQUESTS] = NLA_POLICY_NESTED(nfsd_expkey_nl_policy),
};
+/* NFSD_CMD_CACHE_FLUSH - do */
+static const struct nla_policy nfsd_cache_flush_nl_policy[NFSD_A_CACHE_FLUSH_MASK + 1] = {
+ [NFSD_A_CACHE_FLUSH_MASK] = NLA_POLICY_MASK(NLA_U32, 0x3),
+};
+
/* Ops table for nfsd */
static const struct genl_split_ops nfsd_nl_ops[] = {
{
@@ -191,6 +196,13 @@ static const struct genl_split_ops nfsd_nl_ops[] = {
.maxattr = NFSD_A_EXPKEY_REQS_REQUESTS,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
},
+ {
+ .cmd = NFSD_CMD_CACHE_FLUSH,
+ .doit = nfsd_nl_cache_flush_doit,
+ .policy = nfsd_cache_flush_nl_policy,
+ .maxattr = NFSD_A_CACHE_FLUSH_MASK,
+ .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
+ },
};
static const struct genl_multicast_group nfsd_nl_mcgrps[] = {
diff --git a/fs/nfsd/netlink.h b/fs/nfsd/netlink.h
index f5b3387772850692b220bbbf8a66bc416b67801e..cc89732ed71bd77c1eee011dd07f130c6909462b 100644
--- a/fs/nfsd/netlink.h
+++ b/fs/nfsd/netlink.h
@@ -38,6 +38,7 @@ int nfsd_nl_svc_export_set_reqs_doit(struct sk_buff *skb,
int nfsd_nl_expkey_get_reqs_dumpit(struct sk_buff *skb,
struct netlink_callback *cb);
int nfsd_nl_expkey_set_reqs_doit(struct sk_buff *skb, struct genl_info *info);
+int nfsd_nl_cache_flush_doit(struct sk_buff *skb, struct genl_info *info);
enum {
NFSD_NLGRP_NONE,
diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c
index 7edcbe4504a91966db4805a099368edb92ca38ee..98eeb2be56ce4b826611ba378074c522d761cb1e 100644
--- a/fs/nfsd/nfsctl.c
+++ b/fs/nfsd/nfsctl.c
@@ -10,6 +10,7 @@
#include <linux/ctype.h>
#include <linux/fs_context.h>
+#include <linux/sunrpc/cache.h>
#include <linux/sunrpc/svcsock.h>
#include <linux/lockd/bind.h>
#include <linux/sunrpc/addr.h>
@@ -2215,6 +2216,37 @@ int nfsd_nl_pool_mode_get_doit(struct sk_buff *skb, struct genl_info *info)
return err;
}
+/**
+ * nfsd_nl_cache_flush_doit - flush nfsd caches via netlink
+ * @skb: reply buffer
+ * @info: netlink metadata and command arguments
+ *
+ * Flush the svc_export and/or expkey caches. If NFSD_A_CACHE_FLUSH_MASK
+ * is provided, only flush the caches indicated by the bitmask (bit 0 =
+ * svc_export, bit 1 = expkey). If omitted, flush both.
+ *
+ * Return 0 on success or a negative errno.
+ */
+int nfsd_nl_cache_flush_doit(struct sk_buff *skb, struct genl_info *info)
+{
+ struct net *net = genl_info_net(info);
+ struct nfsd_net *nn = net_generic(net, nfsd_net_id);
+ u32 mask = ~0U;
+
+ if (info->attrs[NFSD_A_CACHE_FLUSH_MASK])
+ mask = nla_get_u32(info->attrs[NFSD_A_CACHE_FLUSH_MASK]);
+
+ if ((mask & NFSD_CACHE_TYPE_SVC_EXPORT) &&
+ nn->svc_export_cache)
+ cache_purge(nn->svc_export_cache);
+
+ if ((mask & NFSD_CACHE_TYPE_EXPKEY) &&
+ nn->svc_expkey_cache)
+ cache_purge(nn->svc_expkey_cache);
+
+ return 0;
+}
+
int nfsd_cache_notify(struct cache_detail *cd, struct cache_head *h, u32 cache_type)
{
struct genlmsghdr *hdr;
diff --git a/include/uapi/linux/nfsd_netlink.h b/include/uapi/linux/nfsd_netlink.h
index e96cc1d23366bce13624084fd476dda65aef140a..08b8a77179f88f596d482d7a2ec5c19ee98b2e77 100644
--- a/include/uapi/linux/nfsd_netlink.h
+++ b/include/uapi/linux/nfsd_netlink.h
@@ -198,6 +198,13 @@ enum {
NFSD_A_EXPKEY_REQS_MAX = (__NFSD_A_EXPKEY_REQS_MAX - 1)
};
+enum {
+ NFSD_A_CACHE_FLUSH_MASK = 1,
+
+ __NFSD_A_CACHE_FLUSH_MAX,
+ NFSD_A_CACHE_FLUSH_MAX = (__NFSD_A_CACHE_FLUSH_MAX - 1)
+};
+
enum {
NFSD_CMD_RPC_STATUS_GET = 1,
NFSD_CMD_THREADS_SET,
@@ -213,6 +220,7 @@ enum {
NFSD_CMD_SVC_EXPORT_SET_REQS,
NFSD_CMD_EXPKEY_GET_REQS,
NFSD_CMD_EXPKEY_SET_REQS,
+ NFSD_CMD_CACHE_FLUSH,
__NFSD_CMD_MAX,
NFSD_CMD_MAX = (__NFSD_CMD_MAX - 1)
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread