git: c5d671b711c3 - main - libc/rpc: add userland side RPC server over netlink(4)

From: Gleb Smirnoff <glebius_at_FreeBSD.org>
Date: Sat, 01 Feb 2025 09:02:05 UTC
The branch main has been updated by glebius:

URL: https://cgit.FreeBSD.org/src/commit/?id=c5d671b711c34fb9eec55531663182feedd93f87

commit c5d671b711c34fb9eec55531663182feedd93f87
Author:     Gleb Smirnoff <glebius@FreeBSD.org>
AuthorDate: 2025-02-01 01:02:00 +0000
Commit:     Gleb Smirnoff <glebius@FreeBSD.org>
CommitDate: 2025-02-01 09:00:25 +0000

    libc/rpc: add userland side RPC server over netlink(4)
    
    To be used by NFS related daemons that provide RPC services to the kernel.
    Some implementation details inside the new svc_nl.c.
    
    Reviewed by:            rmacklem
    Differential Revision:  https://reviews.freebsd.org/D48550
---
 include/rpc/svc.h          |   6 +
 lib/libc/rpc/Makefile.inc  |   2 +-
 lib/libc/rpc/Symbol.map    |   4 +
 lib/libc/rpc/rpc_generic.c |   3 +-
 lib/libc/rpc/svc_nl.c      | 300 +++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 313 insertions(+), 2 deletions(-)

diff --git a/include/rpc/svc.h b/include/rpc/svc.h
index d4a84384bb92..deb626ad7c1d 100644
--- a/include/rpc/svc.h
+++ b/include/rpc/svc.h
@@ -455,6 +455,12 @@ extern SVCXPRT *svc_fd_create(const int, const u_int, const u_int);
  */
 extern SVCXPRT *svcunixfd_create(int, u_int, u_int);
 
+/*
+ * netlink(4) server creation.  To be used to service requests that
+ * originate from an in-kernel client.
+ */
+extern SVCXPRT *svc_nl_create(const char *);
+
 /*
  * Memory based rpc (for speed check and testing)
  */
diff --git a/lib/libc/rpc/Makefile.inc b/lib/libc/rpc/Makefile.inc
index 5ee4ffc256a2..87963d10eec1 100644
--- a/lib/libc/rpc/Makefile.inc
+++ b/lib/libc/rpc/Makefile.inc
@@ -8,7 +8,7 @@ SRCS+=	auth_none.c auth_unix.c authunix_prot.c bindresvport.c clnt_bcast.c \
 	rpc_callmsg.c rpc_generic.c rpc_soc.c rpcb_clnt.c rpcb_prot.c \
 	rpcb_st_xdr.c rpcsec_gss_stub.c svc.c svc_auth.c svc_dg.c \
 	svc_auth_unix.c svc_generic.c svc_raw.c svc_run.c svc_simple.c \
-	svc_vc.c
+	svc_vc.c svc_nl.c
 
 # Secure-RPC
 SRCS+=  auth_time.c auth_des.c authdes_prot.c des_crypt.c des_soft.c \
diff --git a/lib/libc/rpc/Symbol.map b/lib/libc/rpc/Symbol.map
index e4fecb83ec66..105d6fb6b54e 100644
--- a/lib/libc/rpc/Symbol.map
+++ b/lib/libc/rpc/Symbol.map
@@ -199,6 +199,10 @@ FBSD_1.0 {
 	__rpc_get_local_uid;
 };
 
+FBSD_1.8 {
+	svc_nl_create;
+};
+
 FBSDprivate_1.0 {
 	__des_crypt_LOCAL;
 	__key_encryptsession_pk_LOCAL;
diff --git a/lib/libc/rpc/rpc_generic.c b/lib/libc/rpc/rpc_generic.c
index d8b8e2cdf333..0e563f6a5996 100644
--- a/lib/libc/rpc/rpc_generic.c
+++ b/lib/libc/rpc/rpc_generic.c
@@ -95,7 +95,8 @@ static const struct netid_af na_cvt[] = {
 	{ "udp6", AF_INET6, IPPROTO_UDP },
 	{ "tcp6", AF_INET6, IPPROTO_TCP },
 #endif
-	{ "local", AF_LOCAL, 0 }
+	{ "local", AF_LOCAL, 0 },
+	{ "netlink", AF_NETLINK, 0 },
 };
 
 #if 0
diff --git a/lib/libc/rpc/svc_nl.c b/lib/libc/rpc/svc_nl.c
new file mode 100644
index 000000000000..f866acaf3015
--- /dev/null
+++ b/lib/libc/rpc/svc_nl.c
@@ -0,0 +1,300 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2025 Gleb Smirnoff <glebius@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <rpc/rpc.h>
+#include <rpc/clnt_nl.h>
+
+#include <netlink/netlink.h>
+#include <netlink/netlink_generic.h>
+#include <netlink/netlink_snl.h>
+#include <netlink/netlink_snl_generic.h>
+
+#include "rpc_com.h"
+
+/*
+ * RPC server to serve a kernel RPC client(s) over netlink(4).  See clnt_nl.c
+ * in sys/rpc as the counterpart.
+ *
+ * Upon creation the client will seek for specified multicast group within the
+ * generic netlink family named "rpc".  Then it would listen for incoming
+ * messages, process them and send replies over the same netlink socket.
+ * See clnt_nl.c for more transport protocol implementation details.
+ */
+
+static void svc_nl_destroy(SVCXPRT *);
+static bool_t svc_nl_recv(SVCXPRT *, struct rpc_msg *);
+static bool_t svc_nl_reply(SVCXPRT *, struct rpc_msg *);
+static enum xprt_stat svc_nl_stat(SVCXPRT *);
+static bool_t svc_nl_getargs(SVCXPRT *, xdrproc_t, void *);
+static bool_t svc_nl_freeargs(SVCXPRT *, xdrproc_t, void *);
+
+static struct xp_ops nl_ops = {
+	.xp_recv = svc_nl_recv,
+	.xp_reply = svc_nl_reply,
+	.xp_stat = svc_nl_stat,
+	.xp_getargs = svc_nl_getargs,
+	.xp_freeargs = svc_nl_freeargs,
+	.xp_destroy = svc_nl_destroy,
+};
+
+struct nl_softc {
+	struct snl_state snl;
+	XDR		xdrs;
+	struct nlmsghdr	*hdr;
+	size_t		mlen;
+	enum xprt_stat	stat;
+	uint32_t	xid;
+	uint32_t	group;
+	uint16_t	family;
+	u_int		errline;
+	int		error;
+};
+
+SVCXPRT *
+svc_nl_create(const char *service)
+{
+	static struct sockaddr_nl snl_null = {
+		.nl_len = sizeof(struct sockaddr_nl),
+		.nl_family = PF_NETLINK,
+	};
+	struct nl_softc *sc;
+	SVCXPRT *xprt;
+	void *buf = NULL;
+	uint16_t family;
+	ssize_t len = 1024;
+
+	if ((sc = calloc(1, sizeof(struct nl_softc))) == NULL)
+		return (NULL);
+	if (!snl_init(&sc->snl, NETLINK_GENERIC) || (sc->group =
+	    snl_get_genl_mcast_group(&sc->snl, "rpc", service, &family)) == 0)
+		goto fail;
+	if (setsockopt(sc->snl.fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP,
+	    &sc->group, sizeof(sc->group)) == -1)
+		goto fail;
+	if ((buf = malloc(len)) == NULL)
+		goto fail;
+	if ((xprt = svc_xprt_alloc()) == NULL)
+		goto fail;
+
+	sc->hdr = buf,
+	sc->mlen = len,
+	sc->stat = XPRT_IDLE,
+	sc->family = family;
+
+	xprt->xp_fd = sc->snl.fd,
+	xprt->xp_p1 = sc,
+	xprt->xp_ops = &nl_ops,
+	xprt->xp_rtaddr = (struct netbuf){
+		.maxlen = sizeof(struct sockaddr_nl),
+		.len = sizeof(struct sockaddr_nl),
+		.buf = &snl_null,
+	};
+	xprt_register(xprt);
+
+	return (xprt);
+fail:
+	free(buf);
+	snl_free(&sc->snl);
+	free(sc);
+	return (NULL);
+}
+
+static void
+svc_nl_destroy(SVCXPRT *xprt)
+{
+	struct nl_softc *sc = xprt->xp_p1;
+
+	snl_free(&sc->snl);
+	free(sc->hdr);
+	free(xprt->xp_p1);
+	svc_xprt_free(xprt);
+}
+
+#define	DIE(sc) do {							\
+	(sc)->stat = XPRT_DIED;						\
+	(sc)->errline = __LINE__;					\
+	(sc)->error = errno;						\
+	return (FALSE);							\
+} while (0)
+
+struct nl_request_parsed {
+	uint32_t	group;
+	struct nlattr	*data;
+};
+static const struct snl_attr_parser rpcnl_attr_parser[] = {
+#define	OUT(field)	offsetof(struct nl_request_parsed, field)
+    { .type = RPCNL_REQUEST_GROUP, .off = OUT(group),
+       .cb = snl_attr_get_uint32 },
+    { .type = RPCNL_REQUEST_BODY, .off = OUT(data), .cb = snl_attr_get_nla },
+#undef OUT
+};
+SNL_DECLARE_GENL_PARSER(request_parser, rpcnl_attr_parser);
+
+static bool_t
+svc_nl_recv(SVCXPRT *xprt, struct rpc_msg *msg)
+{
+	struct nl_request_parsed req;
+	struct nl_softc *sc = xprt->xp_p1;
+	struct nlmsghdr *hdr = sc->hdr;
+
+	switch (sc->stat) {
+	case XPRT_IDLE:
+		if (recv(xprt->xp_fd, hdr, sizeof(struct nlmsghdr),
+		    MSG_PEEK) != sizeof(struct nlmsghdr))
+			DIE(sc);
+		break;
+	case XPRT_MOREREQS:
+		sc->stat = XPRT_IDLE;
+		break;
+	case XPRT_DIED:
+		return (FALSE);
+	}
+
+	if (sc->mlen < hdr->nlmsg_len) {
+		if ((hdr = sc->hdr = realloc(hdr, hdr->nlmsg_len)) == NULL)
+			DIE(sc);
+		else
+			sc->mlen = hdr->nlmsg_len;
+	}
+	if (read(xprt->xp_fd, hdr, hdr->nlmsg_len) != hdr->nlmsg_len)
+		DIE(sc);
+
+	if (hdr->nlmsg_type != sc->family)
+		return (FALSE);
+
+	if (((struct genlmsghdr *)(hdr + 1))->cmd != RPCNL_REQUEST)
+		return (FALSE);
+
+	if (!snl_parse_nlmsg(NULL, hdr, &request_parser, &req))
+		return (FALSE);
+
+	if (req.group != sc->group)
+		return (FALSE);
+
+	xdrmem_create(&sc->xdrs, NLA_DATA(req.data), NLA_DATA_LEN(req.data),
+	    XDR_DECODE);
+	if (xdr_callmsg(&sc->xdrs, msg)) {
+		/* XXX: assert that xid == nlmsg_seq? */
+		sc->xid = msg->rm_xid;
+		return (TRUE);
+	} else
+		return (FALSE);
+}
+
+static bool_t
+svc_nl_reply(SVCXPRT *xprt, struct rpc_msg *msg)
+{
+	struct nl_softc *sc = xprt->xp_p1;
+	struct snl_state snl;
+	struct snl_writer nw;
+	struct nlattr *body;
+	bool_t rv;
+
+	msg->rm_xid = sc->xid;
+
+	if (__predict_false(!snl_clone(&snl, &sc->snl)))
+		return (FALSE);
+	snl_init_writer(&sc->snl, &nw);
+	snl_create_genl_msg_request(&nw, sc->family, RPCNL_REPLY);
+	snl_add_msg_attr_u32(&nw, RPCNL_REPLY_GROUP, sc->group);
+	body = snl_reserve_msg_attr_raw(&nw, RPCNL_REPLY_BODY, RPC_MAXDATASIZE);
+
+	xdrmem_create(&sc->xdrs, (char *)(body + 1), RPC_MAXDATASIZE,
+	    XDR_ENCODE);
+
+	if (msg->rm_reply.rp_stat == MSG_ACCEPTED &&
+	    msg->rm_reply.rp_acpt.ar_stat == SUCCESS) {
+		xdrproc_t xdr_proc;
+		char *xdr_where;
+		u_int pos;
+
+		xdr_proc = msg->acpted_rply.ar_results.proc;
+		xdr_where = msg->acpted_rply.ar_results.where;
+		msg->acpted_rply.ar_results.proc = (xdrproc_t) xdr_void;
+		msg->acpted_rply.ar_results.where = NULL;
+
+		pos = xdr_getpos(&sc->xdrs);
+		if (!xdr_replymsg(&sc->xdrs, msg) ||
+		    !SVCAUTH_WRAP(&SVC_AUTH(xprt), &sc->xdrs, xdr_proc,
+		    xdr_where)) {
+			xdr_setpos(&sc->xdrs, pos);
+			rv = FALSE;
+		} else
+			rv = TRUE;
+	} else
+		rv = xdr_replymsg(&sc->xdrs, msg);
+
+	if (rv) {
+		/* snl_finalize_msg() really doesn't work for us */
+		body->nla_len = sizeof(struct nlattr) + xdr_getpos(&sc->xdrs);
+		nw.hdr->nlmsg_len = ((char *)body - (char *)nw.hdr) +
+		    body->nla_len;
+		nw.hdr->nlmsg_type = sc->family;
+		nw.hdr->nlmsg_flags = NLM_F_REQUEST;
+		nw.hdr->nlmsg_seq = sc->xid;
+		if (write(xprt->xp_fd, nw.hdr, nw.hdr->nlmsg_len) !=
+		    nw.hdr->nlmsg_len)
+			DIE(sc);
+	}
+
+	snl_free(&snl);
+
+	return (rv);
+}
+
+static enum xprt_stat
+svc_nl_stat(SVCXPRT *xprt)
+{
+	struct nl_softc *sc = xprt->xp_p1;
+
+	if (sc->stat == XPRT_IDLE &&
+	    recv(xprt->xp_fd, sc->hdr, sizeof(struct nlmsghdr),
+	    MSG_PEEK | MSG_DONTWAIT) == sizeof(struct nlmsghdr))
+		sc->stat = XPRT_MOREREQS;
+
+	return (sc->stat);
+}
+
+static bool_t
+svc_nl_getargs(SVCXPRT *xprt, xdrproc_t xdr_args, void *args_ptr)
+{
+	struct nl_softc *sc = xprt->xp_p1;
+
+	return (SVCAUTH_UNWRAP(&SVC_AUTH(xprt), &sc->xdrs, xdr_args, args_ptr));
+}
+
+static bool_t
+svc_nl_freeargs(SVCXPRT *xprt, xdrproc_t xdr_args, void *args_ptr)
+{
+	struct nl_softc *sc = xprt->xp_p1;
+
+	sc->xdrs.x_op = XDR_FREE;
+	return ((*xdr_args)(&sc->xdrs, args_ptr));
+}