* [PATCH v1 0/3] nfsd: expose NFSv4 client state through Netlink
@ 2026-08-25 6:51 Prabhakar Pujeri
2026-08-25 6:51 ` [PATCH v1 1/3] nfsd: report NFSv4 grace " Prabhakar Pujeri
` (2 more replies)
0 siblings, 3 replies; 5+ messages in thread
From: Prabhakar Pujeri @ 2026-08-25 6:51 UTC (permalink / raw)
To: cel, jlayton
Cc: neil, okorniev, Dai.Ngo, tom, donald.hunter, kuba, linux-nfs,
linux-kernel, Prabhakar Pujeri
Administrators can inspect NFSv4 clients through the nfsd filesystem, but
collecting a server-wide view requires walking a directory and opening
multiple files for every client. That is useful for detailed inspection,
but awkward for monitoring and automation.
Add a compact query interface to the existing nfsd Generic Netlink family:
1. Extend threads-get with an explicit NFSv4 recovery-grace value.
2. Add a privileged client-get dump with basic identity, lease,
confirmation, reclaim, and callback information.
3. Extend each client reply with session and stateid record counts.
The split keeps the grace query independent, establishes the minimum useful
client ABI before adding the more expensive state traversal, and permits
older user space to ignore the count extension. The existing nfsd
filesystem files and their output remain unchanged.
client-get emits one bounded message per client. The dump pins each client,
uses the existing client and state locks for mutable fields, and serializes
against server shutdown. Client churn can still make a multi-message dump a
best-effort snapshot; the interface documentation states this explicitly.
The Netlink client ID places cl_boot in the upper 32 bits and cl_id in the
lower 32 bits. This is an architecture-independent representation matching
NFSD's logical and tracepoint representation, rather than the legacy procfs
rendering of the in-memory bytes.
A companion nfs-utils series adds `nfsdctl clients` and displays the grace
value from `nfsdctl threads`.
Validation performed:
- strict checkpatch on each patch: 0 errors, 0 warnings, 0 checks
- YNL regeneration and headers_install
- scoped NFS admin and Netlink documentation builds with SPHINXOPTS=-W
- W=1 NFSD builds with CONFIG_NFSD_V4=y and CONFIG_NFSD_V4=n
- two-vCPU QEMU test with a live NFSv4.2 loopback mount
- grace yes/no transition checked through both raw Netlink and nfsdctl
- one live client with one session, open stateid, lock stateid, and
delegation stateid; zero layout stateids
- legacy /proc/fs/nfsd/clients output checked for unchanged fields
The series is based on the nfsd-testing branch. The exact base is recorded
in the base-commit trailer below.
Prabhakar Pujeri (3):
nfsd: report NFSv4 grace state through Netlink
nfsd: add a Netlink dump of NFSv4 clients
nfsd: report per-client NFSv4 state usage through Netlink
.../admin-guide/nfs/nfsd-admin-interfaces.rst | 22 ++
Documentation/netlink/specs/nfsd.yaml | 106 ++++++++
fs/nfsd/netlink.c | 5 +
fs/nfsd/netlink.h | 1 +
fs/nfsd/nfs4ctl.h | 10 +
fs/nfsd/nfs4state.c | 238 +++++++++++++++++-
fs/nfsd/nfsctl.c | 22 +-
include/uapi/linux/nfsd_netlink.h | 41 +++
8 files changed, 443 insertions(+), 2 deletions(-)
base-commit: 46ff234c7129c7c10ed493413d4f60d0cf4dd1f1
--
2.54.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v1 1/3] nfsd: report NFSv4 grace state through Netlink
2026-08-25 6:51 [PATCH v1 0/3] nfsd: expose NFSv4 client state through Netlink Prabhakar Pujeri
@ 2026-08-25 6:51 ` Prabhakar Pujeri
2026-08-25 12:10 ` Jeff Layton
2026-08-25 6:51 ` [PATCH v1 2/3] nfsd: add a Netlink dump of NFSv4 clients Prabhakar Pujeri
2026-08-25 6:51 ` [PATCH v1 3/3] nfsd: report per-client NFSv4 state usage through Netlink Prabhakar Pujeri
2 siblings, 1 reply; 5+ messages in thread
From: Prabhakar Pujeri @ 2026-08-25 6:51 UTC (permalink / raw)
To: cel, jlayton
Cc: neil, okorniev, Dai.Ngo, tom, donald.hunter, kuba, linux-nfs,
linux-kernel, Prabhakar Pujeri
Operational tooling needs to distinguish a server that is accepting
normal NFSv4 state operations from one that is still accepting only
recovery requests. The existing Netlink threads query reports
server-wide configuration, but recovery state is available only through
the nfsd filesystem.
Add an in-grace value to the threads-get reply. Return it in every reply
so user space can distinguish a server that is not in grace from an older
kernel that does not provide the attribute.
Signed-off-by: Prabhakar Pujeri <prabhakar.pujeri@dell.com>
---
Documentation/admin-guide/nfs/nfsd-admin-interfaces.rst | 3 +++
Documentation/netlink/specs/nfsd.yaml | 6 ++++++
fs/nfsd/nfsctl.c | 9 ++++++++-
include/uapi/linux/nfsd_netlink.h | 1 +
4 files changed, 18 insertions(+), 1 deletion(-)
diff --git a/Documentation/admin-guide/nfs/nfsd-admin-interfaces.rst b/Documentation/admin-guide/nfs/nfsd-admin-interfaces.rst
index c05926f79054..35c174000ab3 100644
--- a/Documentation/admin-guide/nfs/nfsd-admin-interfaces.rst
+++ b/Documentation/admin-guide/nfs/nfsd-admin-interfaces.rst
@@ -25,6 +25,9 @@ udp and one tcp listener at port 2049 (see nfsd_init_socks).
On startup, nfsd and lockd grace periods start. nfsd is shut down by a write of
0 to nfsd/threads. All locks and state are thrown away at that point.
+The ``in-grace`` value in the ``threads-get`` Generic Netlink reply reports
+whether the NFSv4 server's recovery grace period is still active.
+
Between startup and shutdown, the number of threads may be adjusted up
or down by additional writes to nfsd/threads or by writes to
nfsd/pool_threads.
diff --git a/Documentation/netlink/specs/nfsd.yaml b/Documentation/netlink/specs/nfsd.yaml
index 642268819c6f..d01d93f39392 100644
--- a/Documentation/netlink/specs/nfsd.yaml
+++ b/Documentation/netlink/specs/nfsd.yaml
@@ -130,6 +130,11 @@ attribute-sets:
type: binary
checks:
exact-len: 16
+ -
+ name: in-grace
+ type: u8
+ doc: One while the NFSv4 server is in its recovery grace period,
+ otherwise zero.
-
name: version
attributes:
@@ -464,6 +469,7 @@ operations:
- leasetime
- scope
- min-threads
+ - in-grace
-
name: version-set
doc: set nfs enabled versions
diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c
index 6e63950a99e1..7ea865b372cc 100644
--- a/fs/nfsd/nfsctl.c
+++ b/fs/nfsd/nfsctl.c
@@ -1775,6 +1775,7 @@ int nfsd_nl_threads_get_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);
+ bool in_grace = false;
void *hdr;
int err;
@@ -1790,6 +1791,11 @@ int nfsd_nl_threads_get_doit(struct sk_buff *skb, struct genl_info *info)
mutex_lock(&nfsd_mutex);
+#ifdef CONFIG_NFSD_V4
+ in_grace = nfsd_vers(nn, 4, NFSD_TEST) &&
+ test_bit(NFSD_NET_UP, &nn->flags) &&
+ !test_bit(NFSD_NET_GRACE_ENDED, &nn->flags);
+#endif
err = nla_put_u32(skb, NFSD_A_SERVER_GRACETIME,
nn->nfsd4_grace) ||
nla_put_u32(skb, NFSD_A_SERVER_LEASETIME,
@@ -1797,7 +1803,8 @@ int nfsd_nl_threads_get_doit(struct sk_buff *skb, struct genl_info *info)
nla_put_u32(skb, NFSD_A_SERVER_MIN_THREADS,
nn->min_threads) ||
nla_put_string(skb, NFSD_A_SERVER_SCOPE,
- nn->nfsd_name);
+ nn->nfsd_name) ||
+ nla_put_u8(skb, NFSD_A_SERVER_IN_GRACE, in_grace);
if (err)
goto err_unlock;
diff --git a/include/uapi/linux/nfsd_netlink.h b/include/uapi/linux/nfsd_netlink.h
index 87da1d0bb21e..e1fc2db00046 100644
--- a/include/uapi/linux/nfsd_netlink.h
+++ b/include/uapi/linux/nfsd_netlink.h
@@ -84,6 +84,7 @@ enum {
NFSD_A_SERVER_SCOPE,
NFSD_A_SERVER_MIN_THREADS,
NFSD_A_SERVER_FH_KEY,
+ NFSD_A_SERVER_IN_GRACE,
__NFSD_A_SERVER_MAX,
NFSD_A_SERVER_MAX = (__NFSD_A_SERVER_MAX - 1)
--
2.54.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v1 2/3] nfsd: add a Netlink dump of NFSv4 clients
2026-08-25 6:51 [PATCH v1 0/3] nfsd: expose NFSv4 client state through Netlink Prabhakar Pujeri
2026-08-25 6:51 ` [PATCH v1 1/3] nfsd: report NFSv4 grace " Prabhakar Pujeri
@ 2026-08-25 6:51 ` Prabhakar Pujeri
2026-08-25 6:51 ` [PATCH v1 3/3] nfsd: report per-client NFSv4 state usage through Netlink Prabhakar Pujeri
2 siblings, 0 replies; 5+ messages in thread
From: Prabhakar Pujeri @ 2026-08-25 6:51 UTC (permalink / raw)
To: cel, jlayton
Cc: neil, okorniev, Dai.Ngo, tom, donald.hunter, kuba, linux-nfs,
linux-kernel, Prabhakar Pujeri
Administrators currently have to walk one nfsd filesystem directory per
client to correlate basic NFSv4 identity, lease, and callback
information. That interface is useful for detailed inspection, but it
is awkward for monitoring tools and provides no atomic way to enumerate
the client set.
Add a privileged client-get dump to the nfsd Generic Netlink family.
Emit one bounded message per confirmed or unconfirmed client with its
server-generated client ID, peer address, minor version, client and
callback states, signed lease time remaining, and RECLAIM_COMPLETE
status.
Pin each client while taking a snapshot, protect mutable lease and
confirmation fields with the per-net client lock, and serialize against
server shutdown with nfsd_mutex. Netlink dump cursors track the table,
bucket, and position; client churn can still cause a best-effort
snapshot, which is documented. Leave the existing nfsd filesystem
interface unchanged.
Signed-off-by: Prabhakar Pujeri <prabhakar.pujeri@dell.com>
---
.../admin-guide/nfs/nfsd-admin-interfaces.rst | 15 ++
Documentation/netlink/specs/nfsd.yaml | 75 +++++++
fs/nfsd/netlink.c | 5 +
fs/nfsd/netlink.h | 1 +
fs/nfsd/nfs4ctl.h | 10 +
fs/nfsd/nfs4state.c | 200 ++++++++++++++++++
fs/nfsd/nfsctl.c | 13 ++
include/uapi/linux/nfsd_netlink.h | 35 +++
8 files changed, 354 insertions(+)
diff --git a/Documentation/admin-guide/nfs/nfsd-admin-interfaces.rst b/Documentation/admin-guide/nfs/nfsd-admin-interfaces.rst
index 35c174000ab3..bd7a8f71592c 100644
--- a/Documentation/admin-guide/nfs/nfsd-admin-interfaces.rst
+++ b/Documentation/admin-guide/nfs/nfsd-admin-interfaces.rst
@@ -32,6 +32,21 @@ Between startup and shutdown, the number of threads may be adjusted up
or down by additional writes to nfsd/threads or by writes to
nfsd/pool_threads.
+NFSv4 client visibility
+=======================
+
+The privileged ``client-get`` dump in the ``nfsd`` Generic Netlink family
+emits one message for each NFSv4 client. Each message identifies the client
+by its server-generated client ID and transport address, then reports its
+minor version, client and callback states, signed lease time remaining, and
+whether an NFSv4.1 or later client sent RECLAIM_COMPLETE.
+
+The dump is a best-effort snapshot; clients can change between messages.
+
+The existing ``/proc/fs/nfsd/clients/`` files remain available for inspection.
+The ``states`` file contains individual stateids, and writing ``expire`` to
+``ctl`` forcibly removes the client and all state it owns.
+
For more detail about files under nfsd/ and what they control, see
fs/nfsd/nfsctl.c; most of them have detailed comments.
diff --git a/Documentation/netlink/specs/nfsd.yaml b/Documentation/netlink/specs/nfsd.yaml
index d01d93f39392..5df329ebe0b1 100644
--- a/Documentation/netlink/specs/nfsd.yaml
+++ b/Documentation/netlink/specs/nfsd.yaml
@@ -42,6 +42,24 @@ definitions:
- none
- tls
- mtls
+ -
+ type: enum
+ name: client-state
+ doc: State of an NFSv4 client record.
+ entries:
+ - unconfirmed
+ - active
+ - courtesy
+ - expirable
+ -
+ type: enum
+ name: callback-state
+ doc: State of an NFSv4 client's callback channel.
+ entries:
+ - up
+ - unknown
+ - down
+ - fault
attribute-sets:
-
@@ -420,6 +438,48 @@ attribute-sets:
type: nest
nested-attributes: server-proc-entry
multi-attr: true
+ -
+ name: client
+ attributes:
+ -
+ name: clientid
+ type: u64
+ doc: >-
+ Server-generated NFSv4 client ID, with the boot value in the upper
+ 32 bits and the per-boot ID in the lower 32 bits.
+ -
+ name: pad
+ type: pad
+ -
+ name: address
+ type: binary
+ doc: >-
+ Transport peer address recorded when the client was created, as
+ struct sockaddr_in or struct sockaddr_in6.
+ checks:
+ min-len: 16
+ -
+ name: minor-version
+ type: u32
+ doc: Negotiated NFSv4 minor version.
+ -
+ name: state
+ type: u32
+ enum: client-state
+ doc: Confirmation and courtesy-state status of the client record.
+ -
+ name: lease-remaining
+ type: s64
+ doc: Signed seconds until the client's lease expires; negative means overdue.
+ -
+ name: reclaim-complete
+ type: flag
+ doc: The NFSv4.1 or later client sent RECLAIM_COMPLETE.
+ -
+ name: callback-state
+ type: u32
+ enum: callback-state
+ doc: Health of the client's callback channel.
operations:
list:
@@ -633,6 +693,21 @@ operations:
- proc4-ops
- proc4ops-ops
- proc4cb-ops
+ -
+ name: client-get
+ doc: dump NFSv4 clients
+ attribute-set: client
+ flags: [admin-perm]
+ dump:
+ reply:
+ attributes:
+ - clientid
+ - address
+ - minor-version
+ - state
+ - lease-remaining
+ - reclaim-complete
+ - callback-state
mcast-groups:
list:
diff --git a/fs/nfsd/netlink.c b/fs/nfsd/netlink.c
index eba8b353f412..48bc499136b5 100644
--- a/fs/nfsd/netlink.c
+++ b/fs/nfsd/netlink.c
@@ -230,6 +230,11 @@ static const struct genl_split_ops nfsd_nl_ops[] = {
.dumpit = nfsd_nl_server_stats_get_dumpit,
.flags = GENL_CMD_CAP_DUMP,
},
+ {
+ .cmd = NFSD_CMD_CLIENT_GET,
+ .dumpit = nfsd_nl_client_get_dumpit,
+ .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DUMP,
+ },
};
static const struct genl_multicast_group nfsd_nl_mcgrps[] = {
diff --git a/fs/nfsd/netlink.h b/fs/nfsd/netlink.h
index 027e2953db26..de7593e64082 100644
--- a/fs/nfsd/netlink.h
+++ b/fs/nfsd/netlink.h
@@ -44,6 +44,7 @@ int nfsd_nl_unlock_filesystem_doit(struct sk_buff *skb, struct genl_info *info);
int nfsd_nl_unlock_export_doit(struct sk_buff *skb, struct genl_info *info);
int nfsd_nl_server_stats_get_dumpit(struct sk_buff *skb,
struct netlink_callback *cb);
+int nfsd_nl_client_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb);
enum {
NFSD_NLGRP_NONE,
diff --git a/fs/nfsd/nfs4ctl.h b/fs/nfsd/nfs4ctl.h
index bcec4c4ef1d5..44cbb0fac588 100644
--- a/fs/nfsd/nfs4ctl.h
+++ b/fs/nfsd/nfs4ctl.h
@@ -20,8 +20,10 @@
struct net;
struct inode;
struct dentry;
+struct sk_buff;
struct svc_rqst;
struct nfsd_net;
+struct netlink_callback;
#ifdef CONFIG_NFSD_V4
extern unsigned long max_delegations;
@@ -37,6 +39,8 @@ bool nfsd4_spo_must_allow(struct svc_rqst *rqstp);
int nfsd4_create_laundry_wq(void);
void nfsd4_destroy_laundry_wq(void);
bool nfsd_wait_for_delegreturn(struct svc_rqst *rqstp, struct inode *inode);
+int nfsd4_nl_client_get_dumpit(struct sk_buff *skb,
+ struct netlink_callback *cb);
extern int nfsd4_is_junction(struct dentry *dentry);
extern int register_cld_notifier(void);
@@ -68,6 +72,12 @@ static inline bool nfsd_wait_for_delegreturn(struct svc_rqst *rqstp,
return false;
}
+static inline int nfsd4_nl_client_get_dumpit(struct sk_buff *skb,
+ struct netlink_callback *cb)
+{
+ return 0;
+}
+
static inline int nfsd4_is_junction(struct dentry *dentry)
{
return 0;
diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
index 2c1b8b2cbbb5..9add35dfb58b 100644
--- a/fs/nfsd/nfs4state.c
+++ b/fs/nfsd/nfs4state.c
@@ -59,6 +59,7 @@
#include "pnfs.h"
#include "filecache.h"
#include "nfs4xdr_gen.h"
+#include "netlink.h"
#include "trace.h"
#define NFSDDBG_FACILITY NFSDDBG_PROC
@@ -3232,6 +3233,205 @@ static const char *cb_state2str(int state)
return "UNDEFINED";
}
+enum nfsd4_nl_client_table {
+ NFSD4_NL_CLIENT_CONFIRMED,
+ NFSD4_NL_CLIENT_UNCONFIRMED,
+ NFSD4_NL_CLIENT_DONE,
+};
+
+struct nfsd4_nl_client {
+ struct sockaddr_storage address;
+ u64 clientid;
+ s64 lease_remaining;
+ u32 minor_version;
+ u32 state;
+ u32 callback_state;
+ bool reclaim_complete;
+};
+
+static u32 nfsd4_nl_client_state(bool confirmed, unsigned int state)
+{
+ if (!confirmed)
+ return NFSD_CLIENT_STATE_UNCONFIRMED;
+
+ switch (state) {
+ case NFSD4_COURTESY:
+ return NFSD_CLIENT_STATE_COURTESY;
+ case NFSD4_EXPIRABLE:
+ return NFSD_CLIENT_STATE_EXPIRABLE;
+ default:
+ return NFSD_CLIENT_STATE_ACTIVE;
+ }
+}
+
+static u32 nfsd4_nl_callback_state(int state)
+{
+ switch (state) {
+ case NFSD4_CB_UP:
+ return NFSD_CALLBACK_STATE_UP;
+ case NFSD4_CB_DOWN:
+ return NFSD_CALLBACK_STATE_DOWN;
+ case NFSD4_CB_FAULT:
+ return NFSD_CALLBACK_STATE_FAULT;
+ default:
+ return NFSD_CALLBACK_STATE_UNKNOWN;
+ }
+}
+
+static struct nfs4_client *
+nfsd4_nl_get_client(struct nfsd_net *nn, enum nfsd4_nl_client_table table,
+ unsigned long bucket, unsigned long skip)
+{
+ struct nfs4_client *clp = NULL;
+ struct nfs4_client *pos;
+ struct list_head *head;
+ unsigned long index = 0;
+
+ lockdep_assert_held(&nfsd_mutex);
+
+ if (table == NFSD4_NL_CLIENT_CONFIRMED)
+ head = &nn->conf_id_hashtbl[bucket];
+ else
+ head = &nn->unconf_id_hashtbl[bucket];
+
+ spin_lock(&nn->client_lock);
+ list_for_each_entry(pos, head, cl_idhash) {
+ if (index++ != skip)
+ continue;
+ kref_get(&pos->cl_nfsdfs.cl_ref);
+ clp = pos;
+ break;
+ }
+ spin_unlock(&nn->client_lock);
+ return clp;
+}
+
+static void nfsd4_nl_client_snapshot(struct nfsd_net *nn,
+ struct nfs4_client *clp,
+ struct nfsd4_nl_client *client)
+{
+ unsigned int state;
+ time64_t last_renew;
+ bool confirmed;
+
+ spin_lock(&nn->client_lock);
+ last_renew = clp->cl_time;
+ confirmed = test_bit(NFSD4_CLIENT_CONFIRMED, &clp->cl_flags);
+ state = READ_ONCE(clp->cl_state);
+ spin_unlock(&nn->client_lock);
+
+ memcpy(&client->address, &clp->cl_addr, sizeof(client->address));
+ client->clientid = (u64)clp->cl_clientid.cl_boot << 32 |
+ clp->cl_clientid.cl_id;
+ client->lease_remaining = last_renew ?
+ last_renew + READ_ONCE(nn->nfsd4_lease) -
+ ktime_get_boottime_seconds() : 0;
+ client->minor_version = clp->cl_minorversion;
+ client->state = nfsd4_nl_client_state(confirmed, state);
+ client->callback_state =
+ nfsd4_nl_callback_state(READ_ONCE(clp->cl_cb_state));
+ client->reclaim_complete =
+ test_bit(NFSD4_CLIENT_RECLAIM_COMPLETE, &clp->cl_flags);
+}
+
+static int nfsd4_nl_client_compose_msg(struct sk_buff *skb,
+ struct netlink_callback *cb,
+ const struct nfsd4_nl_client *client)
+{
+ void *hdr;
+
+ hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid,
+ cb->nlh->nlmsg_seq, &nfsd_nl_family, NLM_F_MULTI,
+ NFSD_CMD_CLIENT_GET);
+ if (!hdr)
+ return -EMSGSIZE;
+
+ if (nla_put_u64_64bit(skb, NFSD_A_CLIENT_CLIENTID, client->clientid,
+ NFSD_A_CLIENT_PAD) ||
+ nla_put(skb, NFSD_A_CLIENT_ADDRESS,
+ svc_addr_len((const struct sockaddr *)&client->address),
+ &client->address) ||
+ nla_put_u32(skb, NFSD_A_CLIENT_MINOR_VERSION,
+ client->minor_version) ||
+ nla_put_u32(skb, NFSD_A_CLIENT_STATE, client->state) ||
+ nla_put_s64(skb, NFSD_A_CLIENT_LEASE_REMAINING,
+ client->lease_remaining, NFSD_A_CLIENT_PAD) ||
+ (client->reclaim_complete &&
+ nla_put_flag(skb, NFSD_A_CLIENT_RECLAIM_COMPLETE)) ||
+ nla_put_u32(skb, NFSD_A_CLIENT_CALLBACK_STATE,
+ client->callback_state))
+ goto err_cancel;
+
+ genlmsg_end(skb, hdr);
+ return 0;
+
+err_cancel:
+ genlmsg_cancel(skb, hdr);
+ return -EMSGSIZE;
+}
+
+/**
+ * nfsd4_nl_client_get_dumpit - dump NFSv4 client information
+ * @skb: reply buffer
+ * @cb: netlink metadata and command arguments
+ *
+ * One netlink message is emitted for each client. cb->args tracks the client
+ * table, hash bucket, and offset within that bucket. The dump is a best-effort
+ * snapshot because clients can be added, confirmed, or removed between calls.
+ *
+ * Returns the size of the reply or a negative errno.
+ */
+int nfsd4_nl_client_get_dumpit(struct sk_buff *skb,
+ struct netlink_callback *cb)
+{
+ struct nfsd4_nl_client client;
+ struct nfs4_client *clp;
+ struct nfsd_net *nn;
+ struct net *net;
+ int ret = 0;
+
+ net = sock_net(skb->sk);
+ nn = net_generic(net, nfsd_net_id);
+ mutex_lock(&nfsd_mutex);
+ if (!test_bit(NFSD_NET_UP, &nn->flags)) {
+ ret = -ENODEV;
+ goto out_unlock;
+ }
+
+ while (cb->args[0] < NFSD4_NL_CLIENT_DONE) {
+ if (cb->args[1] >= CLIENT_HASH_SIZE) {
+ cb->args[0]++;
+ cb->args[1] = 0;
+ cb->args[2] = 0;
+ continue;
+ }
+
+ clp = nfsd4_nl_get_client(nn, cb->args[0], cb->args[1],
+ cb->args[2]);
+ if (!clp) {
+ cb->args[1]++;
+ cb->args[2] = 0;
+ continue;
+ }
+
+ memset(&client, 0, sizeof(client));
+ nfsd4_nl_client_snapshot(nn, clp, &client);
+ ret = nfsd4_nl_client_compose_msg(skb, cb, &client);
+ nfsd4_put_client(clp);
+ if (ret) {
+ if (skb->len)
+ ret = skb->len;
+ goto out_unlock;
+ }
+ cb->args[2]++;
+ }
+ ret = skb->len;
+
+out_unlock:
+ mutex_unlock(&nfsd_mutex);
+ return ret;
+}
+
static int client_info_show(struct seq_file *m, void *v)
{
struct inode *inode = file_inode(m->file);
diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c
index 7ea865b372cc..5079f74d3d65 100644
--- a/fs/nfsd/nfsctl.c
+++ b/fs/nfsd/nfsctl.c
@@ -1634,6 +1634,19 @@ int nfsd_nl_rpc_status_get_dumpit(struct sk_buff *skb,
return ret;
}
+/**
+ * nfsd_nl_client_get_dumpit - dump NFSv4 client information
+ * @skb: reply buffer
+ * @cb: netlink metadata and command arguments
+ *
+ * Returns the size of the reply or a negative errno.
+ */
+int nfsd_nl_client_get_dumpit(struct sk_buff *skb,
+ struct netlink_callback *cb)
+{
+ return nfsd4_nl_client_get_dumpit(skb, cb);
+}
+
/**
* nfsd_nl_fh_key_set - helper to copy fh_key from userspace
* @attr: nlattr NFSD_A_SERVER_FH_KEY
diff --git a/include/uapi/linux/nfsd_netlink.h b/include/uapi/linux/nfsd_netlink.h
index e1fc2db00046..ae495382092d 100644
--- a/include/uapi/linux/nfsd_netlink.h
+++ b/include/uapi/linux/nfsd_netlink.h
@@ -50,6 +50,26 @@ enum nfsd_xprtsec_mode {
NFSD_XPRTSEC_MODE_MTLS = 4,
};
+/*
+ * State of an NFSv4 client record.
+ */
+enum nfsd_client_state {
+ NFSD_CLIENT_STATE_UNCONFIRMED,
+ NFSD_CLIENT_STATE_ACTIVE,
+ NFSD_CLIENT_STATE_COURTESY,
+ NFSD_CLIENT_STATE_EXPIRABLE,
+};
+
+/*
+ * State of an NFSv4 client's callback channel.
+ */
+enum nfsd_callback_state {
+ NFSD_CALLBACK_STATE_UP,
+ NFSD_CALLBACK_STATE_UNKNOWN,
+ NFSD_CALLBACK_STATE_DOWN,
+ NFSD_CALLBACK_STATE_FAULT,
+};
+
enum {
NFSD_A_CACHE_NOTIFY_CACHE_TYPE = 1,
@@ -261,6 +281,20 @@ enum {
NFSD_A_SERVER_STATS_MAX = (__NFSD_A_SERVER_STATS_MAX - 1)
};
+enum {
+ NFSD_A_CLIENT_CLIENTID = 1,
+ NFSD_A_CLIENT_PAD,
+ NFSD_A_CLIENT_ADDRESS,
+ NFSD_A_CLIENT_MINOR_VERSION,
+ NFSD_A_CLIENT_STATE,
+ NFSD_A_CLIENT_LEASE_REMAINING,
+ NFSD_A_CLIENT_RECLAIM_COMPLETE,
+ NFSD_A_CLIENT_CALLBACK_STATE,
+
+ __NFSD_A_CLIENT_MAX,
+ NFSD_A_CLIENT_MAX = (__NFSD_A_CLIENT_MAX - 1)
+};
+
enum {
NFSD_CMD_RPC_STATUS_GET = 1,
NFSD_CMD_THREADS_SET,
@@ -281,6 +315,7 @@ enum {
NFSD_CMD_UNLOCK_FILESYSTEM,
NFSD_CMD_UNLOCK_EXPORT,
NFSD_CMD_SERVER_STATS_GET,
+ NFSD_CMD_CLIENT_GET,
__NFSD_CMD_MAX,
NFSD_CMD_MAX = (__NFSD_CMD_MAX - 1)
--
2.54.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v1 3/3] nfsd: report per-client NFSv4 state usage through Netlink
2026-08-25 6:51 [PATCH v1 0/3] nfsd: expose NFSv4 client state through Netlink Prabhakar Pujeri
2026-08-25 6:51 ` [PATCH v1 1/3] nfsd: report NFSv4 grace " Prabhakar Pujeri
2026-08-25 6:51 ` [PATCH v1 2/3] nfsd: add a Netlink dump of NFSv4 clients Prabhakar Pujeri
@ 2026-08-25 6:51 ` Prabhakar Pujeri
2 siblings, 0 replies; 5+ messages in thread
From: Prabhakar Pujeri @ 2026-08-25 6:51 UTC (permalink / raw)
To: cel, jlayton
Cc: neil, okorniev, Dai.Ngo, tom, donald.hunter, kuba, linux-nfs,
linux-kernel, Prabhakar Pujeri
A client can retain substantial server-side state even when its lease
and callback channel look healthy. Monitoring tools need compact
per-client totals to identify which client owns that state before doing
a detailed inspection through the nfsd filesystem.
Extend client-get replies with separate counts for sessions and open,
lock, delegation, and layout stateid records. A lock stateid represents
a lock-owner/file pair and can cover multiple byte-range locks, so
document that these are record counts rather than protocol operation or
byte-range counts.
Walk both the session list and stateid IDR under cl_lock. free_client()
already detaches its session list under that lock, so a client pinned by
the dump cannot race this traversal with session teardown.
Signed-off-by: Prabhakar Pujeri <prabhakar.pujeri@dell.com>
---
.../admin-guide/nfs/nfsd-admin-interfaces.rst | 4 ++
Documentation/netlink/specs/nfsd.yaml | 27 ++++++++++++-
fs/nfsd/nfs4state.c | 40 ++++++++++++++++++-
include/uapi/linux/nfsd_netlink.h | 5 +++
4 files changed, 73 insertions(+), 3 deletions(-)
diff --git a/Documentation/admin-guide/nfs/nfsd-admin-interfaces.rst b/Documentation/admin-guide/nfs/nfsd-admin-interfaces.rst
index bd7a8f71592c..076b20f4bf6a 100644
--- a/Documentation/admin-guide/nfs/nfsd-admin-interfaces.rst
+++ b/Documentation/admin-guide/nfs/nfsd-admin-interfaces.rst
@@ -41,6 +41,10 @@ by its server-generated client ID and transport address, then reports its
minor version, client and callback states, signed lease time remaining, and
whether an NFSv4.1 or later client sent RECLAIM_COMPLETE.
+The dump also reports separate counts for sessions and open, lock, delegation,
+and layout stateids. A lock stateid represents state for one lock owner and
+file, not necessarily one byte-range lock.
+
The dump is a best-effort snapshot; clients can change between messages.
The existing ``/proc/fs/nfsd/clients/`` files remain available for inspection.
diff --git a/Documentation/netlink/specs/nfsd.yaml b/Documentation/netlink/specs/nfsd.yaml
index 5df329ebe0b1..1707de076d43 100644
--- a/Documentation/netlink/specs/nfsd.yaml
+++ b/Documentation/netlink/specs/nfsd.yaml
@@ -480,6 +480,26 @@ attribute-sets:
type: u32
enum: callback-state
doc: Health of the client's callback channel.
+ -
+ name: sessions
+ type: u32
+ doc: Number of NFSv4.1 or later sessions owned by the client.
+ -
+ name: open-stateids
+ type: u32
+ doc: Number of open stateid records owned by the client.
+ -
+ name: lock-stateids
+ type: u32
+ doc: Number of lock-owner/file stateid records, not byte-range locks.
+ -
+ name: delegation-stateids
+ type: u32
+ doc: Number of delegation stateid records owned by the client.
+ -
+ name: layout-stateids
+ type: u32
+ doc: Number of pNFS layout stateid records owned by the client.
operations:
list:
@@ -695,7 +715,7 @@ operations:
- proc4cb-ops
-
name: client-get
- doc: dump NFSv4 clients
+ doc: dump NFSv4 clients and their current state usage
attribute-set: client
flags: [admin-perm]
dump:
@@ -708,6 +728,11 @@ operations:
- lease-remaining
- reclaim-complete
- callback-state
+ - sessions
+ - open-stateids
+ - lock-stateids
+ - delegation-stateids
+ - layout-stateids
mcast-groups:
list:
diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
index 9add35dfb58b..f023cfff818d 100644
--- a/fs/nfsd/nfs4state.c
+++ b/fs/nfsd/nfs4state.c
@@ -2822,7 +2822,7 @@ free_client(struct nfs4_client *clp)
{
LIST_HEAD(reaplist);
- /* client_info_show() walks cl_sessions under cl_lock */
+ /* Client reporting walks cl_sessions under cl_lock. */
spin_lock(&clp->cl_lock);
list_splice_init(&clp->cl_sessions, &reaplist);
spin_unlock(&clp->cl_lock);
@@ -3246,6 +3246,11 @@ struct nfsd4_nl_client {
u32 minor_version;
u32 state;
u32 callback_state;
+ u32 sessions;
+ u32 open_stateids;
+ u32 lock_stateids;
+ u32 delegation_stateids;
+ u32 layout_stateids;
bool reclaim_complete;
};
@@ -3310,6 +3315,8 @@ static void nfsd4_nl_client_snapshot(struct nfsd_net *nn,
struct nfs4_client *clp,
struct nfsd4_nl_client *client)
{
+ struct nfs4_stid *stid;
+ unsigned long id, tmp;
unsigned int state;
time64_t last_renew;
bool confirmed;
@@ -3332,6 +3339,26 @@ static void nfsd4_nl_client_snapshot(struct nfsd_net *nn,
nfsd4_nl_callback_state(READ_ONCE(clp->cl_cb_state));
client->reclaim_complete =
test_bit(NFSD4_CLIENT_RECLAIM_COMPLETE, &clp->cl_flags);
+
+ spin_lock(&clp->cl_lock);
+ client->sessions = list_count_nodes(&clp->cl_sessions);
+ idr_for_each_entry_ul(&clp->cl_stateids, stid, tmp, id) {
+ switch (stid->sc_type) {
+ case SC_TYPE_OPEN:
+ client->open_stateids++;
+ break;
+ case SC_TYPE_LOCK:
+ client->lock_stateids++;
+ break;
+ case SC_TYPE_DELEG:
+ client->delegation_stateids++;
+ break;
+ case SC_TYPE_LAYOUT:
+ client->layout_stateids++;
+ break;
+ }
+ }
+ spin_unlock(&clp->cl_lock);
}
static int nfsd4_nl_client_compose_msg(struct sk_buff *skb,
@@ -3359,7 +3386,16 @@ static int nfsd4_nl_client_compose_msg(struct sk_buff *skb,
(client->reclaim_complete &&
nla_put_flag(skb, NFSD_A_CLIENT_RECLAIM_COMPLETE)) ||
nla_put_u32(skb, NFSD_A_CLIENT_CALLBACK_STATE,
- client->callback_state))
+ client->callback_state) ||
+ nla_put_u32(skb, NFSD_A_CLIENT_SESSIONS, client->sessions) ||
+ nla_put_u32(skb, NFSD_A_CLIENT_OPEN_STATEIDS,
+ client->open_stateids) ||
+ nla_put_u32(skb, NFSD_A_CLIENT_LOCK_STATEIDS,
+ client->lock_stateids) ||
+ nla_put_u32(skb, NFSD_A_CLIENT_DELEGATION_STATEIDS,
+ client->delegation_stateids) ||
+ nla_put_u32(skb, NFSD_A_CLIENT_LAYOUT_STATEIDS,
+ client->layout_stateids))
goto err_cancel;
genlmsg_end(skb, hdr);
diff --git a/include/uapi/linux/nfsd_netlink.h b/include/uapi/linux/nfsd_netlink.h
index ae495382092d..49c9531a98b7 100644
--- a/include/uapi/linux/nfsd_netlink.h
+++ b/include/uapi/linux/nfsd_netlink.h
@@ -290,6 +290,11 @@ enum {
NFSD_A_CLIENT_LEASE_REMAINING,
NFSD_A_CLIENT_RECLAIM_COMPLETE,
NFSD_A_CLIENT_CALLBACK_STATE,
+ NFSD_A_CLIENT_SESSIONS,
+ NFSD_A_CLIENT_OPEN_STATEIDS,
+ NFSD_A_CLIENT_LOCK_STATEIDS,
+ NFSD_A_CLIENT_DELEGATION_STATEIDS,
+ NFSD_A_CLIENT_LAYOUT_STATEIDS,
__NFSD_A_CLIENT_MAX,
NFSD_A_CLIENT_MAX = (__NFSD_A_CLIENT_MAX - 1)
--
2.54.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH v1 1/3] nfsd: report NFSv4 grace state through Netlink
2026-08-25 6:51 ` [PATCH v1 1/3] nfsd: report NFSv4 grace " Prabhakar Pujeri
@ 2026-08-25 12:10 ` Jeff Layton
0 siblings, 0 replies; 5+ messages in thread
From: Jeff Layton @ 2026-08-25 12:10 UTC (permalink / raw)
To: Prabhakar Pujeri, cel
Cc: neil, okorniev, Dai.Ngo, tom, donald.hunter, kuba, linux-nfs,
linux-kernel
On Tue, 2026-08-25 at 06:51 +0000, Prabhakar Pujeri wrote:
> Operational tooling needs to distinguish a server that is accepting
> normal NFSv4 state operations from one that is still accepting only
> recovery requests. The existing Netlink threads query reports
> server-wide configuration, but recovery state is available only through
> the nfsd filesystem.
>
> Add an in-grace value to the threads-get reply. Return it in every reply
> so user space can distinguish a server that is not in grace from an older
> kernel that does not provide the attribute.
>
> Signed-off-by: Prabhakar Pujeri <prabhakar.pujeri@dell.com>
> ---
> Documentation/admin-guide/nfs/nfsd-admin-interfaces.rst | 3 +++
> Documentation/netlink/specs/nfsd.yaml | 6 ++++++
> fs/nfsd/nfsctl.c | 9 ++++++++-
> include/uapi/linux/nfsd_netlink.h | 1 +
> 4 files changed, 18 insertions(+), 1 deletion(-)
>
> diff --git a/Documentation/admin-guide/nfs/nfsd-admin-interfaces.rst b/Documentation/admin-guide/nfs/nfsd-admin-interfaces.rst
> index c05926f79054..35c174000ab3 100644
> --- a/Documentation/admin-guide/nfs/nfsd-admin-interfaces.rst
> +++ b/Documentation/admin-guide/nfs/nfsd-admin-interfaces.rst
> @@ -25,6 +25,9 @@ udp and one tcp listener at port 2049 (see nfsd_init_socks).
> On startup, nfsd and lockd grace periods start. nfsd is shut down by a write of
> 0 to nfsd/threads. All locks and state are thrown away at that point.
>
> +The ``in-grace`` value in the ``threads-get`` Generic Netlink reply reports
> +whether the NFSv4 server's recovery grace period is still active.
> +
> Between startup and shutdown, the number of threads may be adjusted up
> or down by additional writes to nfsd/threads or by writes to
> nfsd/pool_threads.
> diff --git a/Documentation/netlink/specs/nfsd.yaml b/Documentation/netlink/specs/nfsd.yaml
> index 642268819c6f..d01d93f39392 100644
> --- a/Documentation/netlink/specs/nfsd.yaml
> +++ b/Documentation/netlink/specs/nfsd.yaml
> @@ -130,6 +130,11 @@ attribute-sets:
> type: binary
> checks:
> exact-len: 16
> + -
> + name: in-grace
> + type: u8
> + doc: One while the NFSv4 server is in its recovery grace period,
> + otherwise zero.
> -
> name: version
> attributes:
> @@ -464,6 +469,7 @@ operations:
> - leasetime
> - scope
> - min-threads
> + - in-grace
> -
> name: version-set
> doc: set nfs enabled versions
> diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c
> index 6e63950a99e1..7ea865b372cc 100644
> --- a/fs/nfsd/nfsctl.c
> +++ b/fs/nfsd/nfsctl.c
> @@ -1775,6 +1775,7 @@ int nfsd_nl_threads_get_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);
> + bool in_grace = false;
> void *hdr;
> int err;
>
> @@ -1790,6 +1791,11 @@ int nfsd_nl_threads_get_doit(struct sk_buff *skb, struct genl_info *info)
>
> mutex_lock(&nfsd_mutex);
>
> +#ifdef CONFIG_NFSD_V4
> + in_grace = nfsd_vers(nn, 4, NFSD_TEST) &&
> + test_bit(NFSD_NET_UP, &nn->flags) &&
> + !test_bit(NFSD_NET_GRACE_ENDED, &nn->flags);
> +#endif
What's your interest in reporting this info?
Just because the v4 recovery is done does not mean that you will be
able to access files. The overall system grace period is a union of
both the NLM and v4 grace periods.
This patch is reporting just the v4 one. If the goal is for the admin
to reliably tell when the grace period has been lifted, then this won't
tell us that.
Maybe this should just return the result of locks_in_grace() instead?
Or, do you plan to add similar functionality to lockd? Then you can
just do the union in userland to figure out if it has been lifted.
> err = nla_put_u32(skb, NFSD_A_SERVER_GRACETIME,
> nn->nfsd4_grace) ||
> nla_put_u32(skb, NFSD_A_SERVER_LEASETIME,
> @@ -1797,7 +1803,8 @@ int nfsd_nl_threads_get_doit(struct sk_buff *skb, struct genl_info *info)
> nla_put_u32(skb, NFSD_A_SERVER_MIN_THREADS,
> nn->min_threads) ||
> nla_put_string(skb, NFSD_A_SERVER_SCOPE,
> - nn->nfsd_name);
> + nn->nfsd_name) ||
> + nla_put_u8(skb, NFSD_A_SERVER_IN_GRACE, in_grace);
> if (err)
> goto err_unlock;
>
> diff --git a/include/uapi/linux/nfsd_netlink.h b/include/uapi/linux/nfsd_netlink.h
> index 87da1d0bb21e..e1fc2db00046 100644
> --- a/include/uapi/linux/nfsd_netlink.h
> +++ b/include/uapi/linux/nfsd_netlink.h
> @@ -84,6 +84,7 @@ enum {
> NFSD_A_SERVER_SCOPE,
> NFSD_A_SERVER_MIN_THREADS,
> NFSD_A_SERVER_FH_KEY,
> + NFSD_A_SERVER_IN_GRACE,
>
> __NFSD_A_SERVER_MAX,
> NFSD_A_SERVER_MAX = (__NFSD_A_SERVER_MAX - 1)
--
Jeff Layton <jlayton@kernel.org>
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-08-25 12:10 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-25 6:51 [PATCH v1 0/3] nfsd: expose NFSv4 client state through Netlink Prabhakar Pujeri
2026-08-25 6:51 ` [PATCH v1 1/3] nfsd: report NFSv4 grace " Prabhakar Pujeri
2026-08-25 12:10 ` Jeff Layton
2026-08-25 6:51 ` [PATCH v1 2/3] nfsd: add a Netlink dump of NFSv4 clients Prabhakar Pujeri
2026-08-25 6:51 ` [PATCH v1 3/3] nfsd: report per-client NFSv4 state usage through Netlink Prabhakar Pujeri
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox