public inbox for netdev@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 00/13] nfsd/sunrpc: add support for netlink upcalls for mountd/exportd
@ 2026-03-25 14:40 Jeff Layton
  2026-03-25 14:40 ` [PATCH v2 01/13] nfsd: move struct nfsd_genl_rqstp to nfsctl.c Jeff Layton
                   ` (12 more replies)
  0 siblings, 13 replies; 14+ messages in thread
From: Jeff Layton @ 2026-03-25 14:40 UTC (permalink / raw)
  To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, Donald Hunter
  Cc: Trond Myklebust, Anna Schumaker, linux-nfs, linux-kernel, netdev,
	Jeff Layton

This version should address most of Chuck's review comments. The
userland patch series is unchanged. I've added the netdev folks this
time too in order to get more experienced eyes on the netlink bits.

Original cover letter follows:

mountd/exportd use the sunrpc cache mechanism for some of its internal
caches that are populated by userland. These currently use some very
antiquated interfaces in /proc to handle upcalls and downcalls. While it
has worked well for decades and is relatively stable, it has some
problems.

Most notably, it's very difficult to extend to add support for new
export options. There is also the matter of requiring /proc which is not
always desirable in a container.

This patchset adds new netlink-based interfaces for handling the sunrpc
cache upcalls. The basic idea is to add a new "cache_notify" operation
to struct cache_detail. That causes the kernel to send a notification to
userland which then fetches any outstanding cache_requests and then
responds to them via netlink.

There is also a companion patchset for nfs-utils that adds the necessary
support for these interfaces to mountd/exportd and exportfs.

Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
Changes in v2:
- Reword comment above cache_do_upcall()
- Delay adding include of genetlink.h until needed
- Implement proper netlink dump continuation support in all downcalls
- Only ->cache_notify if request is still PENDING
- Add comments to svc_export netlink flag enums
- Shrink size passed to genlmsg_new for notify requests to sizeof(u32)
- move nfsd_cache_notify() out of autogenerated file
- Link to v1: https://lore.kernel.org/r/20260316-exportd-netlink-v1-0-6125dc62b955@kernel.org

---
Jeff Layton (13):
      nfsd: move struct nfsd_genl_rqstp to nfsctl.c
      sunrpc: rename sunrpc_cache_pipe_upcall() to sunrpc_cache_upcall()
      sunrpc: rename sunrpc_cache_pipe_upcall_timeout()
      sunrpc: rename cache_pipe_upcall() to cache_do_upcall()
      sunrpc: add a cache_notify callback
      sunrpc: add helpers to count and snapshot pending cache requests
      sunrpc: add a generic netlink family for cache upcalls
      sunrpc: add netlink upcall for the auth.unix.ip cache
      sunrpc: add netlink upcall for the auth.unix.gid cache
      nfsd: add netlink upcall for the svc_export cache
      nfsd: add netlink upcall for the nfsd.fh cache
      sunrpc: add SUNRPC_CMD_CACHE_FLUSH netlink command
      nfsd: add NFSD_CMD_CACHE_FLUSH netlink command

 Documentation/netlink/specs/nfsd.yaml         | 241 +++++++++
 Documentation/netlink/specs/sunrpc_cache.yaml | 149 ++++++
 fs/nfs/dns_resolve.c                          |   2 +-
 fs/nfsd/export.c                              | 713 +++++++++++++++++++++++++-
 fs/nfsd/netlink.c                             | 107 ++++
 fs/nfsd/netlink.h                             |  18 +
 fs/nfsd/nfs4idmap.c                           |   4 +-
 fs/nfsd/nfsctl.c                              |  79 +++
 fs/nfsd/nfsd.h                                |  17 +-
 include/linux/sunrpc/cache.h                  |  15 +-
 include/uapi/linux/nfsd_netlink.h             | 141 +++++
 include/uapi/linux/sunrpc_netlink.h           |  84 +++
 net/sunrpc/Makefile                           |   2 +-
 net/sunrpc/auth_gss/svcauth_gss.c             |   2 +-
 net/sunrpc/cache.c                            | 127 ++++-
 net/sunrpc/netlink.c                          | 111 ++++
 net/sunrpc/netlink.h                          |  35 ++
 net/sunrpc/sunrpc_syms.c                      |  10 +
 net/sunrpc/svcauth_unix.c                     | 516 ++++++++++++++++++-
 19 files changed, 2332 insertions(+), 41 deletions(-)
---
base-commit: e111174758bddc84136446ae283c741d855c7f8f
change-id: 20260316-exportd-netlink-1c9fb52536e3

Best regards,
-- 
Jeff Layton <jlayton@kernel.org>


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH v2 01/13] nfsd: move struct nfsd_genl_rqstp to nfsctl.c
  2026-03-25 14:40 [PATCH v2 00/13] nfsd/sunrpc: add support for netlink upcalls for mountd/exportd Jeff Layton
@ 2026-03-25 14:40 ` Jeff Layton
  2026-03-25 14:40 ` [PATCH v2 02/13] sunrpc: rename sunrpc_cache_pipe_upcall() to sunrpc_cache_upcall() Jeff Layton
                   ` (11 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: Jeff Layton @ 2026-03-25 14:40 UTC (permalink / raw)
  To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, Donald Hunter
  Cc: Trond Myklebust, Anna Schumaker, linux-nfs, linux-kernel, netdev,
	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] 14+ messages in thread

* [PATCH v2 02/13] sunrpc: rename sunrpc_cache_pipe_upcall() to sunrpc_cache_upcall()
  2026-03-25 14:40 [PATCH v2 00/13] nfsd/sunrpc: add support for netlink upcalls for mountd/exportd Jeff Layton
  2026-03-25 14:40 ` [PATCH v2 01/13] nfsd: move struct nfsd_genl_rqstp to nfsctl.c Jeff Layton
@ 2026-03-25 14:40 ` Jeff Layton
  2026-03-25 14:40 ` [PATCH v2 03/13] sunrpc: rename sunrpc_cache_pipe_upcall_timeout() Jeff Layton
                   ` (10 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: Jeff Layton @ 2026-03-25 14:40 UTC (permalink / raw)
  To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, Donald Hunter
  Cc: Trond Myklebust, Anna Schumaker, linux-nfs, linux-kernel, netdev,
	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 7081c1214e6c3226f8ac82c8bc7ff6c36f598744..4729412ecd72241cb050dbed0ee2863629a8bef4 100644
--- a/net/sunrpc/cache.c
+++ b/net/sunrpc/cache.c
@@ -1241,13 +1241,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)
@@ -1257,7 +1257,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] 14+ messages in thread

* [PATCH v2 03/13] sunrpc: rename sunrpc_cache_pipe_upcall_timeout()
  2026-03-25 14:40 [PATCH v2 00/13] nfsd/sunrpc: add support for netlink upcalls for mountd/exportd Jeff Layton
  2026-03-25 14:40 ` [PATCH v2 01/13] nfsd: move struct nfsd_genl_rqstp to nfsctl.c Jeff Layton
  2026-03-25 14:40 ` [PATCH v2 02/13] sunrpc: rename sunrpc_cache_pipe_upcall() to sunrpc_cache_upcall() Jeff Layton
@ 2026-03-25 14:40 ` Jeff Layton
  2026-03-25 14:40 ` [PATCH v2 04/13] sunrpc: rename cache_pipe_upcall() to cache_do_upcall() Jeff Layton
                   ` (9 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: Jeff Layton @ 2026-03-25 14:40 UTC (permalink / raw)
  To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, Donald Hunter
  Cc: Trond Myklebust, Anna Schumaker, linux-nfs, linux-kernel, netdev,
	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 4729412ecd72241cb050dbed0ee2863629a8bef4..1b97102790f6642fa649ad6aab94fcba8158fa8e 100644
--- a/net/sunrpc/cache.c
+++ b/net/sunrpc/cache.c
@@ -1249,8 +1249,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);
@@ -1259,7 +1259,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] 14+ messages in thread

* [PATCH v2 04/13] sunrpc: rename cache_pipe_upcall() to cache_do_upcall()
  2026-03-25 14:40 [PATCH v2 00/13] nfsd/sunrpc: add support for netlink upcalls for mountd/exportd Jeff Layton
                   ` (2 preceding siblings ...)
  2026-03-25 14:40 ` [PATCH v2 03/13] sunrpc: rename sunrpc_cache_pipe_upcall_timeout() Jeff Layton
@ 2026-03-25 14:40 ` Jeff Layton
  2026-03-25 14:40 ` [PATCH v2 05/13] sunrpc: add a cache_notify callback Jeff Layton
                   ` (8 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: Jeff Layton @ 2026-03-25 14:40 UTC (permalink / raw)
  To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, Donald Hunter
  Cc: Trond Myklebust, Anna Schumaker, linux-nfs, linux-kernel, netdev,
	Jeff Layton

Rename cache_pipe_upcall() to cache_do_upcall() in anticipation of the
addition of a netlink-based upcall mechanism.

Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
 net/sunrpc/cache.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/net/sunrpc/cache.c b/net/sunrpc/cache.c
index 1b97102790f6642fa649ad6aab94fcba8158fa8e..8a66128a1bfabca42b52f274ea34c1b594a5920b 100644
--- a/net/sunrpc/cache.c
+++ b/net/sunrpc/cache.c
@@ -1195,12 +1195,12 @@ static bool cache_listeners_exist(struct cache_detail *detail)
 }
 
 /*
- * register an upcall request to user-space and queue it up for read() by the
- * upcall daemon.
+ * register an upcall request to user-space and queue it up to be fetched by
+ * the upcall daemon.
  *
  * 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;
@@ -1245,7 +1245,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] 14+ messages in thread

* [PATCH v2 05/13] sunrpc: add a cache_notify callback
  2026-03-25 14:40 [PATCH v2 00/13] nfsd/sunrpc: add support for netlink upcalls for mountd/exportd Jeff Layton
                   ` (3 preceding siblings ...)
  2026-03-25 14:40 ` [PATCH v2 04/13] sunrpc: rename cache_pipe_upcall() to cache_do_upcall() Jeff Layton
@ 2026-03-25 14:40 ` Jeff Layton
  2026-03-25 14:40 ` [PATCH v2 06/13] sunrpc: add helpers to count and snapshot pending cache requests Jeff Layton
                   ` (7 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: Jeff Layton @ 2026-03-25 14:40 UTC (permalink / raw)
  To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, Donald Hunter
  Cc: Trond Myklebust, Anna Schumaker, linux-nfs, linux-kernel, netdev,
	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           | 2 ++
 2 files changed, 5 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 8a66128a1bfabca42b52f274ea34c1b594a5920b..a182a179a1bfdb883ceda417a5809d967659be5d 100644
--- a/net/sunrpc/cache.c
+++ b/net/sunrpc/cache.c
@@ -1233,6 +1233,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 (ret != -EAGAIN && 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] 14+ messages in thread

* [PATCH v2 06/13] sunrpc: add helpers to count and snapshot pending cache requests
  2026-03-25 14:40 [PATCH v2 00/13] nfsd/sunrpc: add support for netlink upcalls for mountd/exportd Jeff Layton
                   ` (4 preceding siblings ...)
  2026-03-25 14:40 ` [PATCH v2 05/13] sunrpc: add a cache_notify callback Jeff Layton
@ 2026-03-25 14:40 ` Jeff Layton
  2026-03-25 14:40 ` [PATCH v2 07/13] sunrpc: add a generic netlink family for cache upcalls Jeff Layton
                   ` (6 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: Jeff Layton @ 2026-03-25 14:40 UTC (permalink / raw)
  To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, Donald Hunter
  Cc: Trond Myklebust, Anna Schumaker, linux-nfs, linux-kernel, netdev,
	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 |  6 +++++
 net/sunrpc/cache.c           | 61 ++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 67 insertions(+)

diff --git a/include/linux/sunrpc/cache.h b/include/linux/sunrpc/cache.h
index c358151c23950ab48e83991c6138bb7d0e049ace..f88dc6bb17c7078781b3cf7e0371f369eddcb563 100644
--- a/include/linux/sunrpc/cache.h
+++ b/include/linux/sunrpc/cache.h
@@ -251,6 +251,12 @@ 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,
+				   u64 min_seqno);
+
 /* 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 a182a179a1bfdb883ceda417a5809d967659be5d..1282c61030d35efd924072e2739109cfae3472e2 100644
--- a/net/sunrpc/cache.c
+++ b/net/sunrpc/cache.c
@@ -1899,3 +1899,64 @@ 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
+ * @min_seqno: only include entries with seqno > min_seqno (0 for all)
+ *
+ * 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,
+				   u64 min_seqno)
+{
+	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;
+		if (crq->seqno <= min_seqno)
+			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] 14+ messages in thread

* [PATCH v2 07/13] sunrpc: add a generic netlink family for cache upcalls
  2026-03-25 14:40 [PATCH v2 00/13] nfsd/sunrpc: add support for netlink upcalls for mountd/exportd Jeff Layton
                   ` (5 preceding siblings ...)
  2026-03-25 14:40 ` [PATCH v2 06/13] sunrpc: add helpers to count and snapshot pending cache requests Jeff Layton
@ 2026-03-25 14:40 ` Jeff Layton
  2026-03-25 14:40 ` [PATCH v2 08/13] sunrpc: add netlink upcall for the auth.unix.ip cache Jeff Layton
                   ` (5 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: Jeff Layton @ 2026-03-25 14:40 UTC (permalink / raw)
  To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, Donald Hunter
  Cc: Trond Myklebust, Anna Schumaker, linux-nfs, linux-kernel, netdev,
	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() in cache.c, which checks for
listeners on the exportd multicast group, builds and sends a
SUNRPC_CMD_CACHE_NOTIFY message 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 ++++++++++++++++++++++++
 include/linux/sunrpc/cache.h                  |  2 ++
 include/uapi/linux/sunrpc_netlink.h           | 35 +++++++++++++++++++++
 net/sunrpc/Makefile                           |  2 +-
 net/sunrpc/cache.c                            | 44 +++++++++++++++++++++++++++
 net/sunrpc/netlink.c                          | 34 +++++++++++++++++++++
 net/sunrpc/netlink.h                          | 22 ++++++++++++++
 net/sunrpc/sunrpc_syms.c                      | 10 ++++++
 8 files changed, 188 insertions(+), 1 deletion(-)

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/include/linux/sunrpc/cache.h b/include/linux/sunrpc/cache.h
index f88dc6bb17c7078781b3cf7e0371f369eddcb563..2735c332ddb736ef043f811b5e9e6ad2f57c9ce7 100644
--- a/include/linux/sunrpc/cache.h
+++ b/include/linux/sunrpc/cache.h
@@ -256,6 +256,8 @@ int sunrpc_cache_requests_snapshot(struct cache_detail *cd,
 				   struct cache_head **items,
 				   u64 *seqnos, int max,
 				   u64 min_seqno);
+int sunrpc_cache_notify(struct cache_detail *cd, struct cache_head *h,
+			u32 cache_type);
 
 /* 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);
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/cache.c b/net/sunrpc/cache.c
index 1282c61030d35efd924072e2739109cfae3472e2..d477b19dbfa15fdff71f946ade2643b56c35d491 100644
--- a/net/sunrpc/cache.c
+++ b/net/sunrpc/cache.c
@@ -33,9 +33,11 @@
 #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"
+#include "netlink.h"
 #include "fail.h"
 
 #define	 RPCDBG_FACILITY RPCDBG_CACHE
@@ -1960,3 +1962,45 @@ int sunrpc_cache_requests_snapshot(struct cache_detail *cd,
 	return i;
 }
 EXPORT_SYMBOL_GPL(sunrpc_cache_requests_snapshot);
+
+/**
+ * sunrpc_cache_notify - send a netlink notification for a cache event
+ * @cd: cache_detail for the cache
+ * @h: cache_head entry (unused, reserved for future use)
+ * @cache_type: cache type identifier (e.g. SUNRPC_CACHE_TYPE_UNIX_GID)
+ *
+ * Sends a SUNRPC_CMD_CACHE_NOTIFY multicast message on the "exportd"
+ * group if any listeners are present. Returns 0 on success or a
+ * negative errno.
+ */
+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(nla_total_size(sizeof(u32)), 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.c b/net/sunrpc/netlink.c
new file mode 100644
index 0000000000000000000000000000000000000000..952de6de85e3f647ef9bc9c1e99651a247649abb
--- /dev/null
+++ b/net/sunrpc/netlink.c
@@ -0,0 +1,34 @@
+// 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),
+};
diff --git a/net/sunrpc/netlink.h b/net/sunrpc/netlink.h
new file mode 100644
index 0000000000000000000000000000000000000000..74cf5183d745d778174abbbfed9514c4b6693e30
--- /dev/null
+++ b/net/sunrpc/netlink.h
@@ -0,0 +1,22 @@
+/* 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;
+
+#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] 14+ messages in thread

* [PATCH v2 08/13] sunrpc: add netlink upcall for the auth.unix.ip cache
  2026-03-25 14:40 [PATCH v2 00/13] nfsd/sunrpc: add support for netlink upcalls for mountd/exportd Jeff Layton
                   ` (6 preceding siblings ...)
  2026-03-25 14:40 ` [PATCH v2 07/13] sunrpc: add a generic netlink family for cache upcalls Jeff Layton
@ 2026-03-25 14:40 ` Jeff Layton
  2026-03-25 14:40 ` [PATCH v2 09/13] sunrpc: add netlink upcall for the auth.unix.gid cache Jeff Layton
                   ` (4 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: Jeff Layton @ 2026-03-25 14:40 UTC (permalink / raw)
  To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, Donald Hunter
  Cc: Trond Myklebust, Anna Schumaker, linux-nfs, linux-kernel, netdev,
	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                     | 246 ++++++++++++++++++++++++++
 5 files changed, 356 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 952de6de85e3f647ef9bc9c1e99651a247649abb..f57eb17fc27dfb958bcb29a171ea6b88834042e3 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 74cf5183d745d778174abbbfed9514c4b6693e30..68b773960b3972536a9aa77861ce332721f2819e 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..b09b911c532a46bc629b720e71d5c6113d158b1a 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,255 @@ 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.
+ *
+ * Uses cb->args[0] as a seqno cursor for dump continuation across
+ * multiple netlink messages.
+ *
+ * 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, emitted;
+	void *hdr;
+	int ret;
+
+	sn = net_generic(sock_net(skb->sk), sunrpc_net_id);
+
+	cd = sn->ip_map_cache;
+	if (!cd)
+		return -ENODEV;
+
+	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,
+					     cb->args[0]);
+	if (!cnt) {
+		ret = 0;
+		goto out_alloc;
+	}
+
+	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;
+	}
+
+	emitted = 0;
+	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)
+			break;
+
+		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);
+			break;
+		}
+
+		nla_nest_end(skb, nest);
+		cb->args[0] = seqnos[i];
+		emitted++;
+	}
+
+	if (!emitted) {
+		genlmsg_cancel(skb, hdr);
+		ret = -EMSGSIZE;
+		goto out_put;
+	}
+
+	genlmsg_end(skb, hdr);
+	ret = skb->len;
+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] 14+ messages in thread

* [PATCH v2 09/13] sunrpc: add netlink upcall for the auth.unix.gid cache
  2026-03-25 14:40 [PATCH v2 00/13] nfsd/sunrpc: add support for netlink upcalls for mountd/exportd Jeff Layton
                   ` (7 preceding siblings ...)
  2026-03-25 14:40 ` [PATCH v2 08/13] sunrpc: add netlink upcall for the auth.unix.ip cache Jeff Layton
@ 2026-03-25 14:40 ` Jeff Layton
  2026-03-25 14:40 ` [PATCH v2 10/13] nfsd: add netlink upcall for the svc_export cache Jeff Layton
                   ` (3 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: Jeff Layton @ 2026-03-25 14:40 UTC (permalink / raw)
  To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, Donald Hunter
  Cc: Trond Myklebust, Anna Schumaker, linux-nfs, linux-kernel, netdev,
	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                     | 234 ++++++++++++++++++++++++++
 5 files changed, 336 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 f57eb17fc27dfb958bcb29a171ea6b88834042e3..41843f007c37a3ccb6480d11ec31de201c5aa5e7 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 68b773960b3972536a9aa77861ce332721f2819e..16b87519a4096a72798e686dc20d2702ef329e52 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 b09b911c532a46bc629b720e71d5c6113d158b1a..7703523d424617a6033fa7ab48e25728b4c14abb 100644
--- a/net/sunrpc/svcauth_unix.c
+++ b/net/sunrpc/svcauth_unix.c
@@ -585,12 +585,246 @@ 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.
+ *
+ * Uses cb->args[0] as a seqno cursor for dump continuation across
+ * multiple netlink messages.
+ *
+ * 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, emitted;
+	void *hdr;
+	int ret;
+
+	sn = net_generic(sock_net(skb->sk), sunrpc_net_id);
+
+	cd = sn->unix_gid_cache;
+	if (!cd)
+		return -ENODEV;
+
+	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,
+					     cb->args[0]);
+	if (!cnt) {
+		ret = 0;
+		goto out_alloc;
+	}
+
+	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;
+	}
+
+	emitted = 0;
+	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)
+			break;
+
+		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);
+			break;
+		}
+
+		nla_nest_end(skb, nest);
+		cb->args[0] = seqnos[i];
+		emitted++;
+	}
+
+	if (!emitted) {
+		genlmsg_cancel(skb, hdr);
+		ret = -EMSGSIZE;
+		goto out_put;
+	}
+
+	genlmsg_end(skb, hdr);
+	ret = skb->len;
+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] 14+ messages in thread

* [PATCH v2 10/13] nfsd: add netlink upcall for the svc_export cache
  2026-03-25 14:40 [PATCH v2 00/13] nfsd/sunrpc: add support for netlink upcalls for mountd/exportd Jeff Layton
                   ` (8 preceding siblings ...)
  2026-03-25 14:40 ` [PATCH v2 09/13] sunrpc: add netlink upcall for the auth.unix.gid cache Jeff Layton
@ 2026-03-25 14:40 ` Jeff Layton
  2026-03-25 14:40 ` [PATCH v2 11/13] nfsd: add netlink upcall for the nfsd.fh cache Jeff Layton
                   ` (2 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: Jeff Layton @ 2026-03-25 14:40 UTC (permalink / raw)
  To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, Donald Hunter
  Cc: Trond Myklebust, Anna Schumaker, linux-nfs, linux-kernel, netdev,
	Jeff Layton

Add netlink-based cache upcall support for the svc_export (nfsd.export)
cache to Documentation/netlink/specs/nfsd.yaml and regenerate the
resulting files.

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.

Note that the export-flags and xprtsec-mode enums are organized to match
their counterparts in include/uapi/linux/nfsd/export.h. The intent is
that future export options will only be added to the netlink headers,
which should eliminate the need to keep so much in sync.

Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
 Documentation/netlink/specs/nfsd.yaml | 174 +++++++++++++
 fs/nfsd/export.c                      | 443 +++++++++++++++++++++++++++++++++-
 fs/nfsd/netlink.c                     |  61 +++++
 fs/nfsd/netlink.h                     |  13 +
 fs/nfsd/nfsctl.c                      |  28 +++
 fs/nfsd/nfsd.h                        |   2 +-
 include/uapi/linux/nfsd_netlink.h     | 110 +++++++++
 net/sunrpc/netlink.c                  |  17 +-
 net/sunrpc/netlink.h                  |   3 +-
 9 files changed, 835 insertions(+), 16 deletions(-)

diff --git a/Documentation/netlink/specs/nfsd.yaml b/Documentation/netlink/specs/nfsd.yaml
index 8ab43c8253b2e83bcc178c3f4fe8c41c2997d153..709751502f8b56bd4b68462fa15337df5e3e035e 100644
--- a/Documentation/netlink/specs/nfsd.yaml
+++ b/Documentation/netlink/specs/nfsd.yaml
@@ -6,7 +6,51 @@ 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
+    doc: These flags are ordered to match the NFSEXP_* flags in include/linux/nfsd/export.h
+    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
+    doc: These flags are ordered to match the NFSEXP_XPRTSEC_* flags in include/linux/nfsd/export.h
+    entries:
+      - none
+      - tls
+      - mtls
+
 attribute-sets:
+  -
+    name: cache-notify
+    attributes:
+      -
+        name: cache-type
+        type: u32
+        enum: cache-type
   -
     name: rpc-status
     attributes:
@@ -132,6 +176,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 +374,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/export.c b/fs/nfsd/export.c
index e83e88e69d90ab48c7aff58ac2b36cd1a6e1bb71..cfe542fd7b22e27a7971a1f792061767068ac439 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,446 @@ 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.
+ *
+ * Uses cb->args[0] as a seqno cursor for dump continuation across
+ * multiple netlink messages.
+ *
+ * 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, emitted;
+	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;
+	}
+
+	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,
+					     cb->args[0]);
+	if (!cnt) {
+		ret = 0;
+		goto out_alloc;
+	}
+
+	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;
+	}
+
+	emitted = 0;
+	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)
+			break;
+
+		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);
+			break;
+		}
+
+		nla_nest_end(skb, nest);
+		cb->args[0] = seqnos[i];
+		emitted++;
+	}
+
+	if (!emitted) {
+		genlmsg_cancel(skb, hdr);
+		ret = -EMSGSIZE;
+		goto out_put;
+	}
+
+	genlmsg_end(skb, hdr);
+	ret = skb->len;
+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 +848,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 +1341,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/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/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c
index dc294c4f8c58a6692b9dfbeb98fedbd649ae1b95..0f236046adfd239d0f88f4ed82a14e1a41cb6abe 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(nla_total_size(sizeof(u32)), 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
diff --git a/include/uapi/linux/nfsd_netlink.h b/include/uapi/linux/nfsd_netlink.h
index 97c7447f4d14df97c1cba8cdf1f24fba0a7918b3..eae426e9c8e737a95181e7d7b0904b86fc35cff6 100644
--- a/include/uapi/linux/nfsd_netlink.h
+++ b/include/uapi/linux/nfsd_netlink.h
@@ -10,6 +10,52 @@
 #define NFSD_FAMILY_NAME	"nfsd"
 #define NFSD_FAMILY_VERSION	1
 
+enum nfsd_cache_type {
+	NFSD_CACHE_TYPE_SVC_EXPORT = 1,
+};
+
+/*
+ * These flags are ordered to match the NFSEXP_* flags in
+ * include/linux/nfsd/export.h
+ */
+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,
+};
+
+/*
+ * These flags are ordered to match the NFSEXP_XPRTSEC_* flags in
+ * include/linux/nfsd/export.h
+ */
+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 +127,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 +195,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 41843f007c37a3ccb6480d11ec31de201c5aa5e7..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),
diff --git a/net/sunrpc/netlink.h b/net/sunrpc/netlink.h
index 16b87519a4096a72798e686dc20d2702ef329e52..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,

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v2 11/13] nfsd: add netlink upcall for the nfsd.fh cache
  2026-03-25 14:40 [PATCH v2 00/13] nfsd/sunrpc: add support for netlink upcalls for mountd/exportd Jeff Layton
                   ` (9 preceding siblings ...)
  2026-03-25 14:40 ` [PATCH v2 10/13] nfsd: add netlink upcall for the svc_export cache Jeff Layton
@ 2026-03-25 14:40 ` Jeff Layton
  2026-03-25 14:40 ` [PATCH v2 12/13] sunrpc: add SUNRPC_CMD_CACHE_FLUSH netlink command Jeff Layton
  2026-03-25 14:40 ` [PATCH v2 13/13] nfsd: add NFSD_CMD_CACHE_FLUSH " Jeff Layton
  12 siblings, 0 replies; 14+ messages in thread
From: Jeff Layton @ 2026-03-25 14:40 UTC (permalink / raw)
  To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, Donald Hunter
  Cc: Trond Myklebust, Anna Schumaker, linux-nfs, linux-kernel, netdev,
	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                      | 266 ++++++++++++++++++++++++++++++++++
 fs/nfsd/netlink.c                     |  34 +++++
 fs/nfsd/netlink.h                     |   4 +
 include/uapi/linux/nfsd_netlink.h     |  23 +++
 5 files changed, 378 insertions(+), 1 deletion(-)

diff --git a/Documentation/netlink/specs/nfsd.yaml b/Documentation/netlink/specs/nfsd.yaml
index 709751502f8b56bd4b68462fa15337df5e3e035e..ae9563ca58ee0bf7373fd96d7d2253df411316fd 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
@@ -273,6 +273,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:
@@ -400,6 +432,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 cfe542fd7b22e27a7971a1f792061767068ac439..07d40e786aa77488b27fa0e47082ae5000f6f286 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,266 @@ 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.
+ *
+ * Uses cb->args[0] as a seqno cursor for dump continuation across
+ * multiple netlink messages.
+ *
+ * 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, emitted;
+	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;
+	}
+
+	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,
+					     cb->args[0]);
+	if (!cnt) {
+		ret = 0;
+		goto out_alloc;
+	}
+
+	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;
+	}
+
+	emitted = 0;
+	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)
+			break;
+
+		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);
+			break;
+		}
+
+		nla_nest_end(skb, nest);
+		cb->args[0] = seqnos[i];
+		emitted++;
+	}
+
+	if (!emitted) {
+		genlmsg_cancel(skb, hdr);
+		ret = -EMSGSIZE;
+		goto out_put;
+	}
+
+	genlmsg_end(skb, hdr);
+	ret = skb->len;
+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 eae426e9c8e737a95181e7d7b0904b86fc35cff6..2cbd2a36f00ca38e313e95a4ccfaa46a5c1c0df3 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,
 };
 
 /*
@@ -185,6 +186,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,
@@ -198,6 +219,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] 14+ messages in thread

* [PATCH v2 12/13] sunrpc: add SUNRPC_CMD_CACHE_FLUSH netlink command
  2026-03-25 14:40 [PATCH v2 00/13] nfsd/sunrpc: add support for netlink upcalls for mountd/exportd Jeff Layton
                   ` (10 preceding siblings ...)
  2026-03-25 14:40 ` [PATCH v2 11/13] nfsd: add netlink upcall for the nfsd.fh cache Jeff Layton
@ 2026-03-25 14:40 ` Jeff Layton
  2026-03-25 14:40 ` [PATCH v2 13/13] nfsd: add NFSD_CMD_CACHE_FLUSH " Jeff Layton
  12 siblings, 0 replies; 14+ messages in thread
From: Jeff Layton @ 2026-03-25 14:40 UTC (permalink / raw)
  To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, Donald Hunter
  Cc: Trond Myklebust, Anna Schumaker, linux-nfs, linux-kernel, netdev,
	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                          | 12 ++++++++++
 net/sunrpc/netlink.h                          |  1 +
 net/sunrpc/svcauth_unix.c                     | 32 +++++++++++++++++++++++++++
 5 files changed, 70 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..5ccf0967809c4d3cbc58fde5dd20279e1ec29301 100644
--- a/net/sunrpc/netlink.c
+++ b/net/sunrpc/netlink.c
@@ -49,6 +49,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 +84,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[] = {
diff --git a/net/sunrpc/netlink.h b/net/sunrpc/netlink.h
index 2aec57d27a586e4c6b2fc65c7b4505b0996d9577..2c1012303d48bcbaad01192eca1c306790a4522b 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,
diff --git a/net/sunrpc/svcauth_unix.c b/net/sunrpc/svcauth_unix.c
index 7703523d424617a6033fa7ab48e25728b4c14abb..64a2658faddbe6c217f548c565a3a434b5573a76 100644
--- a/net/sunrpc/svcauth_unix.c
+++ b/net/sunrpc/svcauth_unix.c
@@ -818,6 +818,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] 14+ messages in thread

* [PATCH v2 13/13] nfsd: add NFSD_CMD_CACHE_FLUSH netlink command
  2026-03-25 14:40 [PATCH v2 00/13] nfsd/sunrpc: add support for netlink upcalls for mountd/exportd Jeff Layton
                   ` (11 preceding siblings ...)
  2026-03-25 14:40 ` [PATCH v2 12/13] sunrpc: add SUNRPC_CMD_CACHE_FLUSH netlink command Jeff Layton
@ 2026-03-25 14:40 ` Jeff Layton
  12 siblings, 0 replies; 14+ messages in thread
From: Jeff Layton @ 2026-03-25 14:40 UTC (permalink / raw)
  To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, Donald Hunter
  Cc: Trond Myklebust, Anna Schumaker, linux-nfs, linux-kernel, netdev,
	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                      | 36 +++++++++++++++++++++++++++++++++++
 include/uapi/linux/nfsd_netlink.h     |  8 ++++++++
 5 files changed, 74 insertions(+)

diff --git a/Documentation/netlink/specs/nfsd.yaml b/Documentation/netlink/specs/nfsd.yaml
index ae9563ca58ee0bf7373fd96d7d2253df411316fd..f8ca70178c67d0e4bd074b86d19dcd1514f127ff 100644
--- a/Documentation/netlink/specs/nfsd.yaml
+++ b/Documentation/netlink/specs/nfsd.yaml
@@ -305,6 +305,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:
@@ -450,6 +458,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 0f236046adfd239d0f88f4ed82a14e1a41cb6abe..3241bcfc2c6ff0b3273a81642180d17cf6c5b4a3 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,41 @@ 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]);
+
+	mutex_lock(&nfsd_mutex);
+
+	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);
+
+	mutex_unlock(&nfsd_mutex);
+
+	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 2cbd2a36f00ca38e313e95a4ccfaa46a5c1c0df3..2d708d24cbd23770f800fa7e52b351886aec785c 100644
--- a/include/uapi/linux/nfsd_netlink.h
+++ b/include/uapi/linux/nfsd_netlink.h
@@ -206,6 +206,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,
@@ -221,6 +228,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] 14+ messages in thread

end of thread, other threads:[~2026-03-25 14:41 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-25 14:40 [PATCH v2 00/13] nfsd/sunrpc: add support for netlink upcalls for mountd/exportd Jeff Layton
2026-03-25 14:40 ` [PATCH v2 01/13] nfsd: move struct nfsd_genl_rqstp to nfsctl.c Jeff Layton
2026-03-25 14:40 ` [PATCH v2 02/13] sunrpc: rename sunrpc_cache_pipe_upcall() to sunrpc_cache_upcall() Jeff Layton
2026-03-25 14:40 ` [PATCH v2 03/13] sunrpc: rename sunrpc_cache_pipe_upcall_timeout() Jeff Layton
2026-03-25 14:40 ` [PATCH v2 04/13] sunrpc: rename cache_pipe_upcall() to cache_do_upcall() Jeff Layton
2026-03-25 14:40 ` [PATCH v2 05/13] sunrpc: add a cache_notify callback Jeff Layton
2026-03-25 14:40 ` [PATCH v2 06/13] sunrpc: add helpers to count and snapshot pending cache requests Jeff Layton
2026-03-25 14:40 ` [PATCH v2 07/13] sunrpc: add a generic netlink family for cache upcalls Jeff Layton
2026-03-25 14:40 ` [PATCH v2 08/13] sunrpc: add netlink upcall for the auth.unix.ip cache Jeff Layton
2026-03-25 14:40 ` [PATCH v2 09/13] sunrpc: add netlink upcall for the auth.unix.gid cache Jeff Layton
2026-03-25 14:40 ` [PATCH v2 10/13] nfsd: add netlink upcall for the svc_export cache Jeff Layton
2026-03-25 14:40 ` [PATCH v2 11/13] nfsd: add netlink upcall for the nfsd.fh cache Jeff Layton
2026-03-25 14:40 ` [PATCH v2 12/13] sunrpc: add SUNRPC_CMD_CACHE_FLUSH netlink command Jeff Layton
2026-03-25 14:40 ` [PATCH v2 13/13] nfsd: add NFSD_CMD_CACHE_FLUSH " Jeff Layton

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox