* [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