]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
NFSD: Add NFSD_CMD_UNLOCK_IP netlink command
authorChuck Lever <chuck.lever@oracle.com>
Sun, 19 Apr 2026 18:53:02 +0000 (14:53 -0400)
committerChuck Lever <cel@kernel.org>
Tue, 9 Jun 2026 20:32:59 +0000 (16:32 -0400)
The existing write_unlock_ip procfs interface releases NLM file
locks held by a specific client IP address, but procfs provides
no structured way to extend that operation to other scopes such as
revoking NFSv4 state.

Add NFSD_CMD_UNLOCK_IP as a dedicated netlink command for
releasing NLM locks by client address. The command accepts a
binary sockaddr_in or sockaddr_in6 in its address attribute.
The handler validates the address family and length, then calls
nlmsvc_unlock_all_by_ip() to release matching NLM locks.  Because
lockd is a single global instance, that call operates across
all network namespaces regardless of which namespace the caller
inhabits.

A separate netlink command for filesystem-scoped unlock is added in
a subsequent commit.

The nfsd_ctl_unlock_ip tracepoint is updated from string-based
address logging to __sockaddr, which stores the binary sockaddr
and formats it with %pISpc. This affects both the new netlink path
and the existing procfs write_unlock_ip path, giving consistent
structured output in both cases.

Reviewed-by: Jeff Layton <jlayton@kernel.org>
Tested-by: Dai Ngo <dai.ngo@oracle.com>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
Documentation/netlink/specs/nfsd.yaml
fs/nfsd/netlink.c
fs/nfsd/netlink.h
fs/nfsd/nfsctl.c
fs/nfsd/trace.h
include/uapi/linux/nfsd_netlink.h

index 25497b533185735ac74676b53fdec25aca1a21dc..bf41682464c398b0bcb56423f2f1bedd9214cb9d 100644 (file)
@@ -301,6 +301,15 @@ attribute-sets:
         type: u32
         enum: cache-type
         enum-as-flags: true
+  -
+    name: unlock-ip
+    attributes:
+      -
+        name: address
+        type: binary
+        doc: struct sockaddr_in or struct sockaddr_in6.
+        checks:
+          min-len: 16
 
 operations:
   list:
@@ -455,6 +464,15 @@ operations:
         request:
           attributes:
             - mask
+    -
+      name: unlock-ip
+      doc: release NLM locks held by an IP address
+      attribute-set: unlock-ip
+      flags: [admin-perm]
+      do:
+        request:
+          attributes:
+            - address
 
 mcast-groups:
   list:
index f99add477cc7acf98263eea1a49c65415d636b5d..5830627c0288eb557c1a590f1116182117c726d7 100644 (file)
@@ -103,6 +103,11 @@ static const struct nla_policy nfsd_cache_flush_nl_policy[NFSD_A_CACHE_FLUSH_MAS
        [NFSD_A_CACHE_FLUSH_MASK] = NLA_POLICY_MASK(NLA_U32, 0x3),
 };
 
+/* NFSD_CMD_UNLOCK_IP - do */
+static const struct nla_policy nfsd_unlock_ip_nl_policy[NFSD_A_UNLOCK_IP_ADDRESS + 1] = {
+       [NFSD_A_UNLOCK_IP_ADDRESS] = NLA_POLICY_MIN_LEN(16),
+};
+
 /* Ops table for nfsd */
 static const struct genl_split_ops nfsd_nl_ops[] = {
        {
@@ -189,6 +194,13 @@ static const struct genl_split_ops nfsd_nl_ops[] = {
                .maxattr        = NFSD_A_CACHE_FLUSH_MASK,
                .flags          = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
        },
+       {
+               .cmd            = NFSD_CMD_UNLOCK_IP,
+               .doit           = nfsd_nl_unlock_ip_doit,
+               .policy         = nfsd_unlock_ip_nl_policy,
+               .maxattr        = NFSD_A_UNLOCK_IP_ADDRESS,
+               .flags          = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
+       },
 };
 
 static const struct genl_multicast_group nfsd_nl_mcgrps[] = {
index cc89732ed71bd77c1eee011dd07f130c6909462b..88edbbc68453d0d991a9a61528a5930068f210b7 100644 (file)
@@ -39,6 +39,7 @@ 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);
+int nfsd_nl_unlock_ip_doit(struct sk_buff *skb, struct genl_info *info);
 
 enum {
        NFSD_NLGRP_NONE,
index bfb937e8ad7ca70de997e3ac6493e47b7dc68f0a..cf246652e4784004e8e8343c2e5ef2fc420c9f54 100644 (file)
@@ -247,7 +247,7 @@ static ssize_t write_unlock_ip(struct file *file, char *buf, size_t size)
        if (rpc_pton(net, fo_path, size, sap, salen) == 0)
                return -EINVAL;
 
-       trace_nfsd_ctl_unlock_ip(net, buf);
+       trace_nfsd_ctl_unlock_ip(net, sap, svc_addr_len(sap));
        return nlmsvc_unlock_all_by_ip(sap);
 }
 
@@ -2276,6 +2276,44 @@ int nfsd_cache_notify(struct cache_detail *cd, struct cache_head *h, u32 cache_t
                                       NFSD_NLGRP_EXPORTD, GFP_KERNEL);
 }
 
+/**
+ * nfsd_nl_unlock_ip_doit - release NLM locks held by an IP address
+ * @skb: reply buffer
+ * @info: netlink metadata and command arguments
+ *
+ * Return: 0 on success or a negative errno.
+ */
+int nfsd_nl_unlock_ip_doit(struct sk_buff *skb, struct genl_info *info)
+{
+       struct sockaddr *sap;
+
+       if (GENL_REQ_ATTR_CHECK(info, NFSD_A_UNLOCK_IP_ADDRESS))
+               return -EINVAL;
+       sap = nla_data(info->attrs[NFSD_A_UNLOCK_IP_ADDRESS]);
+       switch (sap->sa_family) {
+       case AF_INET:
+               if (nla_len(info->attrs[NFSD_A_UNLOCK_IP_ADDRESS]) <
+                   sizeof(struct sockaddr_in))
+                       return -EINVAL;
+               break;
+       case AF_INET6:
+               if (nla_len(info->attrs[NFSD_A_UNLOCK_IP_ADDRESS]) <
+                   sizeof(struct sockaddr_in6))
+                       return -EINVAL;
+               break;
+       default:
+               return -EAFNOSUPPORT;
+       }
+       /*
+        * nlmsvc_unlock_all_by_ip() releases matching locks
+        * across all network namespaces because lockd operates
+        * a single global instance.
+        */
+       trace_nfsd_ctl_unlock_ip(genl_info_net(info), sap,
+                                svc_addr_len(sap));
+       return nlmsvc_unlock_all_by_ip(sap);
+}
+
 /**
  * nfsd_net_init - Prepare the nfsd_net portion of a new net namespace
  * @net: a freshly-created network namespace
index b631a472222be39fa273b786504540becb814159..9a73e7a1acf2a7256fe92fa91163aadd5d3177cb 100644 (file)
@@ -1985,19 +1985,20 @@ TRACE_EVENT(nfsd_cb_recall_any_done,
 TRACE_EVENT(nfsd_ctl_unlock_ip,
        TP_PROTO(
                const struct net *net,
-               const char *address
+               const struct sockaddr *addr,
+               const unsigned int addrlen
        ),
-       TP_ARGS(net, address),
+       TP_ARGS(net, addr, addrlen),
        TP_STRUCT__entry(
                __field(unsigned int, netns_ino)
-               __string(address, address)
+               __sockaddr(addr, addrlen)
        ),
        TP_fast_assign(
                __entry->netns_ino = net->ns.inum;
-               __assign_str(address);
+               __assign_sockaddr(addr, addr, addrlen);
        ),
-       TP_printk("address=%s",
-               __get_str(address)
+       TP_printk("addr=%pISpc",
+               __get_sockaddr(addr)
        )
 );
 
index 060c43675599f0a3b7085ddcab6bb095d3d2cdb4..90ef1e6867699f8259358a9a138e526fdca18253 100644 (file)
@@ -204,6 +204,13 @@ enum {
        NFSD_A_CACHE_FLUSH_MAX = (__NFSD_A_CACHE_FLUSH_MAX - 1)
 };
 
+enum {
+       NFSD_A_UNLOCK_IP_ADDRESS = 1,
+
+       __NFSD_A_UNLOCK_IP_MAX,
+       NFSD_A_UNLOCK_IP_MAX = (__NFSD_A_UNLOCK_IP_MAX - 1)
+};
+
 enum {
        NFSD_CMD_RPC_STATUS_GET = 1,
        NFSD_CMD_THREADS_SET,
@@ -220,6 +227,7 @@ enum {
        NFSD_CMD_EXPKEY_GET_REQS,
        NFSD_CMD_EXPKEY_SET_REQS,
        NFSD_CMD_CACHE_FLUSH,
+       NFSD_CMD_UNLOCK_IP,
 
        __NFSD_CMD_MAX,
        NFSD_CMD_MAX = (__NFSD_CMD_MAX - 1)