NFSD: Add NFSD_CMD_UNLOCK_IP netlink command

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>
This commit is contained in:
Chuck Lever 2026-04-19 14:53:02 -04:00 committed by Chuck Lever
parent 0a7c2aa584
commit 978cda83de
6 changed files with 85 additions and 7 deletions

View 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:

View 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[] = {

View 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,

View 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

View 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)
)
);

View 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)