lockd: add netlink control interface
authorJeff Layton <jlayton@kernel.org>
Wed, 8 Jan 2025 21:00:15 +0000 (16:00 -0500)
committerChuck Lever <chuck.lever@oracle.com>
Mon, 10 Mar 2025 13:10:53 +0000 (09:10 -0400)
The legacy rpc.nfsd tool will set the nlm_grace_period if the NFSv4
grace period is set. nfsdctl is missing this functionality, so add a new
netlink control interface for lockd that it can use. For now, it only
allows setting the grace period, and the tcp and udp listener ports.

lockd currently uses module parameters and sysctls for configuration, so
all of its settings are global. With this change, lockd now tracks these
values on a per-net-ns basis. It will only fall back to using the global
values if any of them are 0.

Finally, as a backward compatibility measure, if updating the nlm
settings in the init_net namespace, also update the legacy global
values to match.

Link: https://issues.redhat.com/browse/RHEL-71698
Signed-off-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
Documentation/netlink/specs/lockd.yaml [new file with mode: 0644]
fs/lockd/Makefile
fs/lockd/netlink.c [new file with mode: 0644]
fs/lockd/netlink.h [new file with mode: 0644]
fs/lockd/netns.h
fs/lockd/svc.c
include/uapi/linux/lockd_netlink.h [new file with mode: 0644]

diff --git a/Documentation/netlink/specs/lockd.yaml b/Documentation/netlink/specs/lockd.yaml
new file mode 100644 (file)
index 0000000..bbd4da5
--- /dev/null
@@ -0,0 +1,45 @@
+# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
+
+name: lockd
+protocol: genetlink
+uapi-header: linux/lockd_netlink.h
+
+doc: lockd configuration over generic netlink
+
+attribute-sets:
+  -
+    name: server
+    attributes:
+      -
+        name: gracetime
+        type: u32
+      -
+        name: tcp-port
+        type: u16
+      -
+        name: udp-port
+        type: u16
+
+operations:
+  list:
+    -
+      name: server-set
+      doc: set the lockd server parameters
+      attribute-set: server
+      flags: [ admin-perm ]
+      do:
+        request:
+          attributes:
+            - gracetime
+            - tcp-port
+            - udp-port
+    -
+      name: server-get
+      doc: get the lockd server parameters
+      attribute-set: server
+      do:
+        reply:
+          attributes:
+            - gracetime
+            - tcp-port
+            - udp-port
index fe3e23dd29c32af8a0191686113b68541e6cb183..51bbe22d21e3545764260fb4472524211b4aa550 100644 (file)
@@ -8,6 +8,6 @@ ccflags-y += -I$(src)                   # needed for trace events
 obj-$(CONFIG_LOCKD) += lockd.o
 
 lockd-y := clntlock.o clntproc.o clntxdr.o host.o svc.o svclock.o \
-          svcshare.o svcproc.o svcsubs.o mon.o trace.o xdr.o
+          svcshare.o svcproc.o svcsubs.o mon.o trace.o xdr.o netlink.o
 lockd-$(CONFIG_LOCKD_V4) += clnt4xdr.o xdr4.o svc4proc.o
 lockd-$(CONFIG_PROC_FS) += procfs.o
diff --git a/fs/lockd/netlink.c b/fs/lockd/netlink.c
new file mode 100644 (file)
index 0000000..6e00b02
--- /dev/null
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
+/* Do not edit directly, auto-generated from: */
+/*     Documentation/netlink/specs/lockd.yaml */
+/* YNL-GEN kernel source */
+
+#include <net/netlink.h>
+#include <net/genetlink.h>
+
+#include "netlink.h"
+
+#include <uapi/linux/lockd_netlink.h>
+
+/* LOCKD_CMD_SERVER_SET - do */
+static const struct nla_policy lockd_server_set_nl_policy[LOCKD_A_SERVER_UDP_PORT + 1] = {
+       [LOCKD_A_SERVER_GRACETIME] = { .type = NLA_U32, },
+       [LOCKD_A_SERVER_TCP_PORT] = { .type = NLA_U16, },
+       [LOCKD_A_SERVER_UDP_PORT] = { .type = NLA_U16, },
+};
+
+/* Ops table for lockd */
+static const struct genl_split_ops lockd_nl_ops[] = {
+       {
+               .cmd            = LOCKD_CMD_SERVER_SET,
+               .doit           = lockd_nl_server_set_doit,
+               .policy         = lockd_server_set_nl_policy,
+               .maxattr        = LOCKD_A_SERVER_UDP_PORT,
+               .flags          = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
+       },
+       {
+               .cmd    = LOCKD_CMD_SERVER_GET,
+               .doit   = lockd_nl_server_get_doit,
+               .flags  = GENL_CMD_CAP_DO,
+       },
+};
+
+struct genl_family lockd_nl_family __ro_after_init = {
+       .name           = LOCKD_FAMILY_NAME,
+       .version        = LOCKD_FAMILY_VERSION,
+       .netnsok        = true,
+       .parallel_ops   = true,
+       .module         = THIS_MODULE,
+       .split_ops      = lockd_nl_ops,
+       .n_split_ops    = ARRAY_SIZE(lockd_nl_ops),
+};
diff --git a/fs/lockd/netlink.h b/fs/lockd/netlink.h
new file mode 100644 (file)
index 0000000..1920543
--- /dev/null
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
+/* Do not edit directly, auto-generated from: */
+/*     Documentation/netlink/specs/lockd.yaml */
+/* YNL-GEN kernel header */
+
+#ifndef _LINUX_LOCKD_GEN_H
+#define _LINUX_LOCKD_GEN_H
+
+#include <net/netlink.h>
+#include <net/genetlink.h>
+
+#include <uapi/linux/lockd_netlink.h>
+
+int lockd_nl_server_set_doit(struct sk_buff *skb, struct genl_info *info);
+int lockd_nl_server_get_doit(struct sk_buff *skb, struct genl_info *info);
+
+extern struct genl_family lockd_nl_family;
+
+#endif /* _LINUX_LOCKD_GEN_H */
index 17432c445fe6f0841b3c821ca1c71babf4e50b7a..88e8e2a973977433703cac3a046e0f09501b26b5 100644 (file)
@@ -10,6 +10,9 @@ struct lockd_net {
        unsigned int nlmsvc_users;
        unsigned long next_gc;
        unsigned long nrhosts;
+       u32 gracetime;
+       u16 tcp_port;
+       u16 udp_port;
 
        struct delayed_work grace_period_end;
        struct lock_manager lockd_manager;
index 2c8eedc6c2cc9ebcfe521d90364de84e00eae40f..256284669aaabf5b2153a52050575b2bff9d39f1 100644 (file)
@@ -41,6 +41,7 @@
 
 #include "netns.h"
 #include "procfs.h"
+#include "netlink.h"
 
 #define NLMDBG_FACILITY                NLMDBG_SVC
 #define LOCKD_BUFSIZE          (1024 + NLMSVC_XDRSIZE)
@@ -83,8 +84,14 @@ static const int             nlm_port_min = 0, nlm_port_max = 65535;
 static struct ctl_table_header * nlm_sysctl_table;
 #endif
 
-static unsigned long get_lockd_grace_period(void)
+static unsigned long get_lockd_grace_period(struct net *net)
 {
+       struct lockd_net *ln = net_generic(net, lockd_net_id);
+
+       /* Return the net-ns specific grace period, if there is one */
+       if (ln->gracetime)
+               return ln->gracetime * HZ;
+
        /* Note: nlm_timeout should always be nonzero */
        if (nlm_grace_period)
                return roundup(nlm_grace_period, nlm_timeout) * HZ;
@@ -103,7 +110,7 @@ static void grace_ender(struct work_struct *grace)
 
 static void set_grace_period(struct net *net)
 {
-       unsigned long grace_period = get_lockd_grace_period();
+       unsigned long grace_period = get_lockd_grace_period(net);
        struct lockd_net *ln = net_generic(net, lockd_net_id);
 
        locks_start_grace(net, &ln->lockd_manager);
@@ -166,15 +173,16 @@ static int create_lockd_listener(struct svc_serv *serv, const char *name,
 static int create_lockd_family(struct svc_serv *serv, struct net *net,
                               const int family, const struct cred *cred)
 {
+       struct lockd_net *ln = net_generic(net, lockd_net_id);
        int err;
 
-       err = create_lockd_listener(serv, "udp", net, family, nlm_udpport,
-                       cred);
+       err = create_lockd_listener(serv, "udp", net, family,
+                                   ln->udp_port ? ln->udp_port : nlm_udpport, cred);
        if (err < 0)
                return err;
 
-       return create_lockd_listener(serv, "tcp", net, family, nlm_tcpport,
-                       cred);
+       return create_lockd_listener(serv, "tcp", net, family,
+                                    ln->tcp_port ? ln->tcp_port : nlm_tcpport, cred);
 }
 
 /*
@@ -588,6 +596,10 @@ static int __init init_nlm(void)
        if (err)
                goto err_pernet;
 
+       err = genl_register_family(&lockd_nl_family);
+       if (err)
+               goto err_netlink;
+
        err = lockd_create_procfs();
        if (err)
                goto err_procfs;
@@ -595,6 +607,8 @@ static int __init init_nlm(void)
        return 0;
 
 err_procfs:
+       genl_unregister_family(&lockd_nl_family);
+err_netlink:
        unregister_pernet_subsys(&lockd_net_ops);
 err_pernet:
 #ifdef CONFIG_SYSCTL
@@ -608,6 +622,7 @@ static void __exit exit_nlm(void)
 {
        /* FIXME: delete all NLM clients */
        nlm_shutdown_hosts();
+       genl_unregister_family(&lockd_nl_family);
        lockd_remove_procfs();
        unregister_pernet_subsys(&lockd_net_ops);
 #ifdef CONFIG_SYSCTL
@@ -710,3 +725,94 @@ static struct svc_program  nlmsvc_program = {
        .pg_init_request        = svc_generic_init_request,
        .pg_rpcbind_set         = svc_generic_rpcbind_set,
 };
+
+/**
+ * lockd_nl_server_set_doit - set the lockd server parameters via netlink
+ * @skb: reply buffer
+ * @info: netlink metadata and command arguments
+ *
+ * This updates the per-net values. When updating the values in the init_net
+ * namespace, also update the "legacy" global values.
+ *
+ * Return 0 on success or a negative errno.
+ */
+int lockd_nl_server_set_doit(struct sk_buff *skb, struct genl_info *info)
+{
+       struct net *net = genl_info_net(info);
+       struct lockd_net *ln = net_generic(net, lockd_net_id);
+       const struct nlattr *attr;
+
+       if (GENL_REQ_ATTR_CHECK(info, LOCKD_A_SERVER_GRACETIME))
+               return -EINVAL;
+
+       if (info->attrs[LOCKD_A_SERVER_GRACETIME] ||
+           info->attrs[LOCKD_A_SERVER_TCP_PORT] ||
+           info->attrs[LOCKD_A_SERVER_UDP_PORT]) {
+               attr = info->attrs[LOCKD_A_SERVER_GRACETIME];
+               if (attr) {
+                       u32 gracetime = nla_get_u32(attr);
+
+                       if (gracetime > nlm_grace_period_max)
+                               return -EINVAL;
+
+                       ln->gracetime = gracetime;
+
+                       if (net == &init_net)
+                               nlm_grace_period = gracetime;
+               }
+
+               attr = info->attrs[LOCKD_A_SERVER_TCP_PORT];
+               if (attr) {
+                       ln->tcp_port = nla_get_u16(attr);
+                       if (net == &init_net)
+                               nlm_tcpport = ln->tcp_port;
+               }
+
+               attr = info->attrs[LOCKD_A_SERVER_UDP_PORT];
+               if (attr) {
+                       ln->udp_port = nla_get_u16(attr);
+                       if (net == &init_net)
+                               nlm_udpport = ln->udp_port;
+               }
+       }
+       return 0;
+}
+
+/**
+ * lockd_nl_server_get_doit - get lockd server parameters via netlink
+ * @skb: reply buffer
+ * @info: netlink metadata and command arguments
+ *
+ * Return 0 on success or a negative errno.
+ */
+int lockd_nl_server_get_doit(struct sk_buff *skb, struct genl_info *info)
+{
+       struct net *net = genl_info_net(info);
+       struct lockd_net *ln = net_generic(net, lockd_net_id);
+       void *hdr;
+       int err;
+
+       skb = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL);
+       if (!skb)
+               return -ENOMEM;
+
+       hdr = genlmsg_iput(skb, info);
+       if (!hdr) {
+               err = -EMSGSIZE;
+               goto err_free_msg;
+       }
+
+       err = nla_put_u32(skb, LOCKD_A_SERVER_GRACETIME, ln->gracetime) ||
+             nla_put_u16(skb, LOCKD_A_SERVER_TCP_PORT, ln->tcp_port) ||
+             nla_put_u16(skb, LOCKD_A_SERVER_UDP_PORT, ln->udp_port);
+       if (err)
+               goto err_free_msg;
+
+       genlmsg_end(skb, hdr);
+
+       return genlmsg_reply(skb, info);
+err_free_msg:
+       nlmsg_free(skb);
+
+       return err;
+}
diff --git a/include/uapi/linux/lockd_netlink.h b/include/uapi/linux/lockd_netlink.h
new file mode 100644 (file)
index 0000000..21c65ae
--- /dev/null
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
+/* Do not edit directly, auto-generated from: */
+/*     Documentation/netlink/specs/lockd.yaml */
+/* YNL-GEN uapi header */
+
+#ifndef _UAPI_LINUX_LOCKD_NETLINK_H
+#define _UAPI_LINUX_LOCKD_NETLINK_H
+
+#define LOCKD_FAMILY_NAME      "lockd"
+#define LOCKD_FAMILY_VERSION   1
+
+enum {
+       LOCKD_A_SERVER_GRACETIME = 1,
+       LOCKD_A_SERVER_TCP_PORT,
+       LOCKD_A_SERVER_UDP_PORT,
+
+       __LOCKD_A_SERVER_MAX,
+       LOCKD_A_SERVER_MAX = (__LOCKD_A_SERVER_MAX - 1)
+};
+
+enum {
+       LOCKD_CMD_SERVER_SET = 1,
+       LOCKD_CMD_SERVER_GET,
+
+       __LOCKD_CMD_MAX,
+       LOCKD_CMD_MAX = (__LOCKD_CMD_MAX - 1)
+};
+
+#endif /* _UAPI_LINUX_LOCKD_NETLINK_H */