public inbox for linux-nfs@vger.kernel.org
 help / color / mirror / Atom feed
From: Jeff Layton <jlayton@kernel.org>
To: Steve Dickson <steved@redhat.com>
Cc: Chuck Lever <chuck.lever@oracle.com>, NeilBrown <neil@brown.name>,
	 Olga Kornievskaia <okorniev@redhat.com>,
	Dai Ngo <Dai.Ngo@oracle.com>,  Tom Talpey <tom@talpey.com>,
	Trond Myklebust <trondmy@kernel.org>,
	 Anna Schumaker <anna@kernel.org>,
	linux-nfs@vger.kernel.org,  Jeff Layton <jlayton@kernel.org>
Subject: [PATCH nfs-utils 09/17] exportd/mountd: add netlink support for the auth.unix.ip cache
Date: Mon, 16 Mar 2026 11:16:47 -0400	[thread overview]
Message-ID: <20260316-exportd-netlink-v1-9-9a408a0b389d@kernel.org> (raw)
In-Reply-To: <20260316-exportd-netlink-v1-0-9a408a0b389d@kernel.org>

Refactor cache_nfsd_nl_open() into a generic cache_genl_open() helper
that takes the family name, multicast group name, and output pointers
for the command socket, notification socket, and resolved family ID.
Convert cache_nfsd_nl_open() to use it.

Add the sunrpc genl family socket setup for handling auth.unix.ip and
auth.unix.gid cache upcalls. The sunrpc family is resolved at startup
and silently falls back if the kernel doesn't support it.

Add the ip_map (auth.unix.ip) netlink cache handler. For each pending
request, the handler resolves the IP address using client_resolve()
and responds via SUNRPC_CMD_IP_MAP_SET_REQS with the domain name or a
negative entry.

Wire the sunrpc notification socket into cache_open(), cache_set_fds(),
and cache_process_req().

Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
 support/export/cache.c | 366 +++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 342 insertions(+), 24 deletions(-)

diff --git a/support/export/cache.c b/support/export/cache.c
index c350662fd97c33c40c1d59297b9638141a67befb..50c2de08c504da1a05631938ee51251d82c52377 100644
--- a/support/export/cache.c
+++ b/support/export/cache.c
@@ -48,6 +48,12 @@
 #include "nfsd_netlink.h"
 #endif
 
+#ifdef USE_SYSTEM_SUNRPC_NETLINK_H
+#include <linux/sunrpc_netlink.h>
+#else
+#include "sunrpc_netlink.h"
+#endif
+
 #ifdef USE_BLKID
 #include "blkid/blkid.h"
 #endif
@@ -1108,58 +1114,71 @@ static struct nl_sock *nl_sock_setup(void)
 	return sock;
 }
 
-static int cache_nfsd_nl_open(void)
+static int cache_genl_open(const char *family_name, const char *mcgrp_name,
+			   struct nl_sock **cmd_sock,
+			   struct nl_sock **notify_sock, int *family_out)
 {
 	int grp;
 
-	nfsd_nl_family = 0;
+	*family_out = 0;
 
-	nfsd_nl_cmd_sock = nl_sock_setup();
-	if (!nfsd_nl_cmd_sock) {
-		xlog(D_NETLINK, "cache_nfsd_nl_open: failed to allocate command socket");
+	*cmd_sock = nl_sock_setup();
+	if (!*cmd_sock) {
+		xlog(D_NETLINK, "%s: failed to allocate command socket",
+		     family_name);
 		return -ENOMEM;
 	}
 
-	nfsd_nl_family = genl_ctrl_resolve(nfsd_nl_cmd_sock, NFSD_FAMILY_NAME);
-	if (nfsd_nl_family < 0) {
-		xlog(D_NETLINK, "cache_nfsd_nl_open: nfsd netlink family not found");
+	*family_out = genl_ctrl_resolve(*cmd_sock, family_name);
+	if (*family_out < 0) {
+		xlog(D_NETLINK, "%s: netlink family not found", family_name);
 		goto out_free_cmd;
 	}
 
-	grp = genl_ctrl_resolve_grp(nfsd_nl_cmd_sock, NFSD_FAMILY_NAME,
-				    NFSD_MCGRP_EXPORTD);
+	grp = genl_ctrl_resolve_grp(*cmd_sock, family_name, mcgrp_name);
 	if (grp < 0) {
-		xlog(D_NETLINK, "cache_nfsd_nl_open: exportd multicast group not found");
+		xlog(D_NETLINK, "%s: %s multicast group not found",
+		     family_name, mcgrp_name);
 		goto out_free_cmd;
 	}
 
-	nfsd_nl_notify_sock = nl_sock_setup();
-	if (!nfsd_nl_notify_sock) {
-		xlog(D_NETLINK, "cache_nfsd_nl_open: failed to allocate notify socket");
+	*notify_sock = nl_sock_setup();
+	if (!*notify_sock) {
+		xlog(D_NETLINK, "%s: failed to allocate notify socket",
+		     family_name);
 		goto out_free_cmd;
 	}
 
-	nl_socket_disable_seq_check(nfsd_nl_notify_sock);
+	nl_socket_disable_seq_check(*notify_sock);
 
-	if (nl_socket_add_membership(nfsd_nl_notify_sock, grp)) {
-		xlog(L_WARNING, "cache_nfsd_nl_open: failed to join exportd multicast group");
+	if (nl_socket_add_membership(*notify_sock, grp)) {
+		xlog(L_WARNING, "%s: failed to join %s multicast group",
+		     family_name, mcgrp_name);
 		goto out_free_notify;
 	}
 
-	nl_socket_set_nonblocking(nfsd_nl_notify_sock);
-	xlog(D_NETLINK, "cache_nfsd_nl_open: listening for export notifications");
+	nl_socket_set_nonblocking(*notify_sock);
+	xlog(D_NETLINK, "%s: listening for %s notifications",
+	     family_name, mcgrp_name);
 	return 0;
 
 out_free_notify:
-	nl_socket_free(nfsd_nl_notify_sock);
-	nfsd_nl_notify_sock = NULL;
+	nl_socket_free(*notify_sock);
+	*notify_sock = NULL;
 out_free_cmd:
-	nl_socket_free(nfsd_nl_cmd_sock);
-	nfsd_nl_cmd_sock = NULL;
-	nfsd_nl_family = 0;
+	nl_socket_free(*cmd_sock);
+	*cmd_sock = NULL;
+	*family_out = 0;
 	return -ENOENT;
 }
 
+static int cache_nfsd_nl_open(void)
+{
+	return cache_genl_open(NFSD_FAMILY_NAME, NFSD_MCGRP_EXPORTD,
+			       &nfsd_nl_cmd_sock, &nfsd_nl_notify_sock,
+			       &nfsd_nl_family);
+}
+
 static int nfsd_nl_notify_handler(struct nl_msg *UNUSED(msg), void *UNUSED(arg))
 {
 	return NL_OK;
@@ -1904,6 +1923,296 @@ static void cache_nfsd_nl_process(void)
 	cache_nl_process_expkey();
 }
 
+/*
+ * Netlink-based sunrpc cache support.
+ *
+ * The sunrpc genl family handles auth.unix.ip and auth.unix.gid caches.
+ * A SUNRPC_CMD_CACHE_NOTIFY on the "exportd" multicast group signals
+ * pending cache requests.
+ */
+static struct nl_sock *sunrpc_nl_notify_sock;
+static struct nl_sock *sunrpc_nl_cmd_sock;
+static int sunrpc_nl_family;
+
+static int cache_sunrpc_nl_open(void)
+{
+	return cache_genl_open(SUNRPC_FAMILY_NAME, SUNRPC_MCGRP_EXPORTD,
+			       &sunrpc_nl_cmd_sock, &sunrpc_nl_notify_sock,
+			       &sunrpc_nl_family);
+}
+
+static void cache_sunrpc_nl_drain(void)
+{
+	struct nl_cb *cb;
+
+	cb = nl_cb_alloc(NL_CB_DEFAULT);
+	if (!cb)
+		return;
+
+	nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, nfsd_nl_notify_handler, NULL);
+	nl_recvmsgs(sunrpc_nl_notify_sock, cb);
+	nl_cb_put(cb);
+}
+
+/*
+ * ip_map (auth.unix.ip) netlink handler
+ */
+struct ip_map_req {
+	char	*class;
+	char	*addr;
+};
+
+struct get_ip_map_reqs_data {
+	struct ip_map_req	*reqs;
+	int			nreqs;
+	int			maxreqs;
+	int			err;
+};
+
+static int get_ip_map_reqs_cb(struct nl_msg *msg, void *arg)
+{
+	struct get_ip_map_reqs_data *data = arg;
+	struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
+	struct nlattr *attr;
+	int rem;
+
+	nla_for_each_attr(attr, genlmsg_attrdata(gnlh, 0),
+			  genlmsg_attrlen(gnlh, 0), rem) {
+		struct nlattr *tb[SUNRPC_A_IP_MAP_EXPIRY + 1];
+		struct ip_map_req *req;
+
+		if (nla_type(attr) != SUNRPC_A_IP_MAP_REQS_REQUESTS)
+			continue;
+
+		if (nla_parse_nested(tb, SUNRPC_A_IP_MAP_EXPIRY, attr,
+				     NULL))
+			continue;
+
+		if (!tb[SUNRPC_A_IP_MAP_CLASS] ||
+		    !tb[SUNRPC_A_IP_MAP_ADDR])
+			continue;
+
+		if (data->nreqs >= data->maxreqs) {
+			int newmax = data->maxreqs ? data->maxreqs * 2 : 16;
+			struct ip_map_req *tmp;
+
+			tmp = realloc(data->reqs, newmax * sizeof(*tmp));
+			if (!tmp) {
+				data->err = -ENOMEM;
+				return NL_STOP;
+			}
+			data->reqs = tmp;
+			data->maxreqs = newmax;
+		}
+
+		req = &data->reqs[data->nreqs++];
+		req->class = strdup(nla_get_string(tb[SUNRPC_A_IP_MAP_CLASS]));
+		req->addr = strdup(nla_get_string(tb[SUNRPC_A_IP_MAP_ADDR]));
+
+		if (!req->class || !req->addr) {
+			data->err = -ENOMEM;
+			return NL_STOP;
+		}
+	}
+
+	return NL_OK;
+}
+
+static int cache_nl_get_ip_map_reqs(struct ip_map_req **reqs_out,
+				    int *nreqs_out)
+{
+	struct get_ip_map_reqs_data data = { };
+	struct nl_msg *msg;
+	struct nl_cb *cb;
+	int done = 0;
+	int ret;
+
+	msg = cache_nl_new_msg(sunrpc_nl_family,
+			       SUNRPC_CMD_IP_MAP_GET_REQS, NLM_F_DUMP);
+	if (!msg)
+		return -ENOMEM;
+
+	cb = nl_cb_alloc(NL_CB_DEFAULT);
+	if (!cb) {
+		nlmsg_free(msg);
+		return -ENOMEM;
+	}
+
+	nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, get_ip_map_reqs_cb, &data);
+	nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, nl_finish_cb, &done);
+	nl_cb_err(cb, NL_CB_CUSTOM, nl_error_cb, &done);
+
+	ret = nl_send_auto(sunrpc_nl_cmd_sock, msg);
+	nlmsg_free(msg);
+	if (ret < 0) {
+		nl_cb_put(cb);
+		return ret;
+	}
+
+	while (!done) {
+		ret = nl_recvmsgs(sunrpc_nl_cmd_sock, cb);
+		if (ret < 0)
+			break;
+	}
+
+	nl_cb_put(cb);
+
+	if (data.err) {
+		int i;
+		for (i = 0; i < data.nreqs; i++) {
+			free(data.reqs[i].class);
+			free(data.reqs[i].addr);
+		}
+		free(data.reqs);
+		return data.err;
+	}
+
+	*reqs_out = data.reqs;
+	*nreqs_out = data.nreqs;
+	return 0;
+}
+
+static int nl_add_ip_map(struct nl_msg *msg, char *class, char *addr,
+			 char *domain)
+{
+	struct nlattr *nest;
+	time_t now = time(0);
+
+	nest = nla_nest_start(msg, SUNRPC_A_IP_MAP_REQS_REQUESTS);
+	if (!nest)
+		return -1;
+
+	if (nla_put_string(msg, SUNRPC_A_IP_MAP_CLASS, class) < 0 ||
+	    nla_put_string(msg, SUNRPC_A_IP_MAP_ADDR, addr) < 0 ||
+	    nla_put_u64(msg, SUNRPC_A_IP_MAP_EXPIRY,
+			now + default_ttl) < 0)
+		goto nla_failure;
+
+	if (domain) {
+		if (nla_put_string(msg, SUNRPC_A_IP_MAP_DOMAIN,
+				   domain) < 0)
+			goto nla_failure;
+	} else {
+		if (nla_put_flag(msg, SUNRPC_A_IP_MAP_NEGATIVE) < 0)
+			goto nla_failure;
+	}
+
+	nla_nest_end(msg, nest);
+	return 0;
+
+nla_failure:
+	nla_nest_cancel(msg, nest);
+	return -1;
+}
+
+static void cache_nl_process_ip_map(void)
+{
+	struct ip_map_req *reqs = NULL;
+	int nreqs = 0;
+	struct nl_msg *msg;
+	int i;
+
+	if (cache_nl_get_ip_map_reqs(&reqs, &nreqs)) {
+		xlog(L_WARNING, "cache_nl_process_ip_map: failed to get ip_map requests");
+		return;
+	}
+
+	if (!nreqs)
+		return;
+
+	xlog(D_CALL, "cache_nl_process_ip_map: %d pending ip_map requests",
+	     nreqs);
+
+	msg = cache_nl_new_msg(sunrpc_nl_family,
+			       SUNRPC_CMD_IP_MAP_SET_REQS, 0);
+	if (!msg)
+		goto out_free;
+
+	for (i = 0; i < nreqs; i++) {
+		char *class = reqs[i].class;
+		char *ipaddr = reqs[i].addr;
+		char *client = NULL;
+		char *domain = NULL;
+		char *dom_alloc = NULL;
+		struct addrinfo *tmp = NULL;
+
+		if (strcmp(class, "nfsd") == 0) {
+			tmp = host_pton(ipaddr);
+			if (tmp) {
+				struct addrinfo *ai;
+
+				ai = client_resolve(tmp->ai_addr);
+				if (ai) {
+					client = client_compose(ai);
+					nfs_freeaddrinfo(ai);
+				}
+			}
+
+			if (use_ipaddr && client) {
+				dom_alloc = malloc(strlen(ipaddr) + 2);
+				if (dom_alloc) {
+					dom_alloc[0] = '$';
+					strcpy(dom_alloc + 1, ipaddr);
+					domain = dom_alloc;
+				}
+			} else if (client) {
+				domain = *client ? client : "DEFAULT";
+			}
+		}
+
+		if (nl_add_ip_map(msg, class, ipaddr, domain) < 0) {
+			cache_nl_set_reqs(sunrpc_nl_cmd_sock, msg);
+			nlmsg_free(msg);
+			msg = cache_nl_new_msg(sunrpc_nl_family,
+					       SUNRPC_CMD_IP_MAP_SET_REQS, 0);
+			if (!msg) {
+				free(dom_alloc);
+				free(client);
+				nfs_freeaddrinfo(tmp);
+				goto out_free;
+			}
+			if (nl_add_ip_map(msg, class, ipaddr, domain) < 0)
+				xlog(L_WARNING, "%s: skipping oversized "
+				     "entry for %s", __func__, ipaddr);
+		}
+
+		if (tmp && !client)
+			xlog(D_AUTH, "failed authentication for IP %s",
+			     ipaddr);
+		else if (client && !use_ipaddr)
+			xlog(D_AUTH, "successful authentication for IP %s as %s",
+			     ipaddr, *client ? client : "DEFAULT");
+		else if (client)
+			xlog(D_AUTH, "successful authentication for IP %s",
+			     ipaddr);
+
+		free(dom_alloc);
+		free(client);
+		nfs_freeaddrinfo(tmp);
+	}
+
+	cache_nl_set_reqs(sunrpc_nl_cmd_sock, msg);
+	nlmsg_free(msg);
+
+out_free:
+	for (i = 0; i < nreqs; i++) {
+		free(reqs[i].class);
+		free(reqs[i].addr);
+	}
+	free(reqs);
+}
+
+static void cache_sunrpc_nl_process(void)
+{
+	/* Drain pending sunrpc notifications */
+	cache_sunrpc_nl_drain();
+
+	auth_reload();
+
+	/* Handle any pending ip_map requests */
+	cache_nl_process_ip_map();
+}
+
 static int can_reexport_via_fsidnum(struct exportent *exp, struct statfs *st)
 {
 	if (st->f_type != 0x6969 /* NFS_SUPER_MAGIC */)
@@ -2476,6 +2785,7 @@ void cache_open(void)
 		cachelist[i].f = open(path, O_RDWR);
 	}
 	cache_nfsd_nl_open();
+	cache_sunrpc_nl_open();
 }
 
 /**
@@ -2491,6 +2801,8 @@ void cache_set_fds(fd_set *fdset)
 	}
 	if (nfsd_nl_notify_sock)
 		FD_SET(nl_socket_get_fd(nfsd_nl_notify_sock), fdset);
+	if (sunrpc_nl_notify_sock)
+		FD_SET(nl_socket_get_fd(sunrpc_nl_notify_sock), fdset);
 }
 
 /**
@@ -2515,6 +2827,12 @@ int cache_process_req(fd_set *readfds)
 		cache_nfsd_nl_process();
 		FD_CLR(nl_socket_get_fd(nfsd_nl_notify_sock), readfds);
 	}
+	if (sunrpc_nl_notify_sock &&
+	    FD_ISSET(nl_socket_get_fd(sunrpc_nl_notify_sock), readfds)) {
+		cnt++;
+		cache_sunrpc_nl_process();
+		FD_CLR(nl_socket_get_fd(sunrpc_nl_notify_sock), readfds);
+	}
 	return cnt;
 }
 

-- 
2.53.0


  parent reply	other threads:[~2026-03-16 15:17 UTC|newest]

Thread overview: 19+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-03-16 15:16 [PATCH nfs-utils 00/17] exportfs/exportd/mountd: allow them to use netlink for up/downcalls Jeff Layton
2026-03-16 15:16 ` [PATCH nfs-utils 01/17] nfsdctl: move *_netlink.h to support/include/ Jeff Layton
2026-03-16 15:16 ` [PATCH nfs-utils 02/17] support/export: remove unnecessary static variables in nfsd_fh expkey lookup Jeff Layton
2026-03-16 15:16 ` [PATCH nfs-utils 03/17] exportfs: remove obsolete legacy mode documentation from manpage Jeff Layton
2026-03-16 15:16 ` [PATCH nfs-utils 04/17] support/include: update netlink headers for all cache upcalls Jeff Layton
2026-03-16 15:16 ` [PATCH nfs-utils 05/17] build: add libnl3 and netlink header support for exportd and mountd Jeff Layton
2026-03-16 15:16 ` [PATCH nfs-utils 06/17] xlog: claim D_FAC3 as D_NETLINK Jeff Layton
2026-03-16 15:16 ` [PATCH nfs-utils 07/17] exportd/mountd: add netlink support for svc_export cache Jeff Layton
2026-03-16 15:16 ` [PATCH nfs-utils 08/17] exportd/mountd: add netlink support for the nfsd.fh cache Jeff Layton
2026-03-16 15:16 ` Jeff Layton [this message]
2026-03-16 15:16 ` [PATCH nfs-utils 10/17] exportd/mountd: add netlink support for the auth.unix.gid cache Jeff Layton
2026-03-16 15:16 ` [PATCH nfs-utils 11/17] mountd/exportd: only use /proc interfaces if netlink setup fails Jeff Layton
2026-03-16 15:16 ` [PATCH nfs-utils 12/17] support/export: check for pending requests after opening netlink sockets Jeff Layton
2026-03-16 15:16 ` [PATCH nfs-utils 13/17] exportd/mountd: use cache type from notifications to target scanning Jeff Layton
2026-03-16 15:16 ` [PATCH nfs-utils 14/17] nfsd/sunrpc: add cache flush command and attribute enums Jeff Layton
2026-03-16 15:16 ` [PATCH nfs-utils 15/17] exportfs: add netlink support for cache flush with /proc fallback Jeff Layton
2026-03-16 15:16 ` [PATCH nfs-utils 16/17] exportfs: use netlink to probe kernel support, skip export_test Jeff Layton
2026-03-16 15:16 ` [PATCH nfs-utils 17/17] mountd/exportd/exportfs: add --no-netlink option to disable netlink Jeff Layton
2026-03-16 15:26   ` Jeff Layton

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260316-exportd-netlink-v1-9-9a408a0b389d@kernel.org \
    --to=jlayton@kernel.org \
    --cc=Dai.Ngo@oracle.com \
    --cc=anna@kernel.org \
    --cc=chuck.lever@oracle.com \
    --cc=linux-nfs@vger.kernel.org \
    --cc=neil@brown.name \
    --cc=okorniev@redhat.com \
    --cc=steved@redhat.com \
    --cc=tom@talpey.com \
    --cc=trondmy@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox