Skip site navigation (1)Skip section navigation (2)
Date:      Sat, 1 Feb 2025 09:02:04 GMT
From:      Gleb Smirnoff <glebius@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org
Subject:   git: fa1b961259bc - main - krpc: add kernel side client over netlink(4)
Message-ID:  <202502010902.511924Dp047792@gitrepo.freebsd.org>

next in thread | raw e-mail | index | archive | help
The branch main has been updated by glebius:

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

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

    krpc: add kernel side client over netlink(4)
    
    This shall be the official transport to connect kernel side RPC clients
    to userland side RPC servers.  All current kernel side clients that
    hijack unix(4) sockets will be converted to it.  Some implementation
    details are available inside new clnt_nl.c.
    
    The complementary RPC server over netlink(4) coming in next commit.
    
    Reviewed by:            rmacklem
    Differential Revision:  https://reviews.freebsd.org/D48549
---
 include/rpc/Makefile      |   6 +
 sys/conf/files            |   1 +
 sys/modules/krpc/Makefile |   1 +
 sys/rpc/clnt.h            |  11 +
 sys/rpc/clnt_nl.c         | 510 ++++++++++++++++++++++++++++++++++++++++++++++
 sys/rpc/clnt_nl.h         |  42 ++++
 sys/rpc/krpc.h            |   2 +
 sys/rpc/rpc_generic.c     |   3 +-
 8 files changed, 575 insertions(+), 1 deletion(-)

diff --git a/include/rpc/Makefile b/include/rpc/Makefile
index b6e3ab53adb4..db8d78826eb2 100644
--- a/include/rpc/Makefile
+++ b/include/rpc/Makefile
@@ -1,3 +1,6 @@
+.include <src.opts.mk>
+.PATH: ${SRCTOP}/sys/rpc
+
 .SUFFIXES: .x
 
 RPCCOM=	RPCGEN_CPP=${CPP:Q} rpcgen -C
@@ -11,6 +14,9 @@ HFILES=	auth.h auth_unix.h clnt.h clnt_soc.h clnt_stat.h \
 	rpc.h rpc_msg.h rpcb_clnt.h rpcent.h rpc_com.h rpcsec_gss.h \
 	svc.h svc_auth.h svc_soc.h svc_dg.h xdr.h
 
+# User visible declarations for kernel RPC modules
+HFILES+= clnt_nl.h
+
 # Secure RPC
 HFILES+= auth_des.h des.h des_crypt.h
 
diff --git a/sys/conf/files b/sys/conf/files
index 496f1617a67a..230d5229210e 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -5101,6 +5101,7 @@ rpc/auth_unix.c			optional krpc | nfslockd | nfscl | nfsd
 rpc/authunix_prot.c		optional krpc | nfslockd | nfscl | nfsd
 rpc/clnt_bck.c			optional krpc | nfslockd | nfscl | nfsd
 rpc/clnt_dg.c			optional krpc | nfslockd | nfscl | nfsd
+rpc/clnt_nl.c			optional krpc | nfslockd | nfscl | nfsd
 rpc/clnt_rc.c			optional krpc | nfslockd | nfscl | nfsd
 rpc/clnt_vc.c			optional krpc | nfslockd | nfscl | nfsd
 rpc/getnetconfig.c		optional krpc | nfslockd | nfscl | nfsd
diff --git a/sys/modules/krpc/Makefile b/sys/modules/krpc/Makefile
index a49207b78123..a47c5cbc55c0 100644
--- a/sys/modules/krpc/Makefile
+++ b/sys/modules/krpc/Makefile
@@ -5,6 +5,7 @@ SRCS=	auth_none.c \
 	authunix_prot.c \
 	clnt_bck.c \
 	clnt_dg.c \
+	clnt_nl.c \
 	clnt_rc.c \
 	clnt_vc.c \
 	getnetconfig.c \
diff --git a/sys/rpc/clnt.h b/sys/rpc/clnt.h
index d9fc372709cf..a237b00928e0 100644
--- a/sys/rpc/clnt.h
+++ b/sys/rpc/clnt.h
@@ -371,6 +371,17 @@ extern CLIENT *clnt_dg_create(struct socket *so,
     struct sockaddr *svcaddr, rpcprog_t program, rpcvers_t version,
     size_t sendsz, size_t recvsz);
 
+/*
+ * netlink(4) client that would multicast calls on genetlink(4) family
+ * named "rpcnl" (with dynamic id).  The server counterpart of this
+ * client is a userland application that uses libc/rpc/svc_nl.c to
+ * receive the calls and send replies.
+ *
+ *	const char *name			-- multicast group name
+ */
+extern CLIENT *client_nl_create(const char *name, const rpcprog_t prog,
+    const rpcvers_t version);
+
 /*
  *	struct socket *so;			-- socket
  *	struct sockaddr *svcaddr;		-- servers address
diff --git a/sys/rpc/clnt_nl.c b/sys/rpc/clnt_nl.c
new file mode 100644
index 000000000000..8f841e4240d7
--- /dev/null
+++ b/sys/rpc/clnt_nl.c
@@ -0,0 +1,510 @@
+/*-
+ * 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 <sys/param.h>
+#include <sys/lock.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/mutex.h>
+#include <sys/rwlock.h>
+#include <sys/mbuf.h>
+#include <sys/priv.h>
+#include <sys/proc.h>
+#include <sys/queue.h>
+#include <sys/tree.h>
+
+#include <rpc/rpc.h>
+#include <rpc/rpc_com.h>
+#include <rpc/krpc.h>
+#include <rpc/clnt_nl.h>
+
+#include <netlink/netlink.h>
+#include <netlink/netlink_ctl.h>
+#include <netlink/netlink_generic.h>
+
+/*
+ * Kernel RPC client over netlink(4), where kernel is RPC client and an
+ * application is a server.  See svc_nl.c in the libc/rpc as the counterpart.
+ *
+ * The module registers itself within generic netlink families list under name
+ * "rpc".  Every new client creates a new multicast group belonging to this
+ * family.  When a client starts RPC, the module will multicast the call to
+ * potential netlink listeners and sleep/retry until receiving a result.  The
+ * framing of the request:
+ *
+ * [netlink message header, type = "rpc" ID, seq == xid]
+ * [generic netlink header, cmd = RPCNL_REQUEST]
+ * [netlink attribute RPCNL_REQUEST_GROUP]
+ * [group ID]
+ * [netlink attribute RPCNL_REQUEST_BODY]
+ * [XDR encoded payload]
+ *
+ * Note: the generic netlink header and attributes aren't really necessary
+ * for successful communication, since the netlink multicast membership already
+ * guarantees us all needed filtering.  The working prototype was putting the
+ * XDR encoded payload right after netlink message header.  But we will provide
+ * this framing to allow for any future extensions.
+ *
+ * The expected RPC result from the userland shall be framed like this:
+ *
+ * [netlink message header, type = "rpc" ID, seq == xid]
+ * [generic netlink header, cmd = RPCNL_REPLY]
+ * [netlink attribute RPCNL_REPLY_GROUP]
+ * [group ID]
+ * [netlink attribute RPCNL_REPLY_BODY]
+ * [XDR encoded payload]
+ *
+ * Disclaimer: has been designed and tested only for the NFS related kernel
+ * RPC clients: kgssapi, RPC binding for NLM, TLS client and TLS server.
+ *
+ * Caveats:
+ * 1) Now the privilege checking is hardcoded to PRIV_NFS_DAEMON at the netlink
+ *    command and multicast layers.  If any new client in addition to NFS
+ *    service emerges, we may want to rewrite privelege checking at the client
+ *    level somehow.
+ * 2) Since we are using netlink attribute for the payload, payload size is
+ *    limited to UINT16_MAX.  Today it is smaller than RPC_MAXDATASIZE of 9000.
+ *    What if a future RPC wants more?
+ */
+
+static enum clnt_stat clnt_nl_call(CLIENT *, struct rpc_callextra *,
+    rpcproc_t, struct mbuf *, struct mbuf **, struct timeval);
+static void clnt_nl_close(CLIENT *);
+static void clnt_nl_destroy(CLIENT *);
+static bool_t clnt_nl_control(CLIENT *, u_int, void *);
+
+static const struct clnt_ops clnt_nl_ops = {
+	.cl_call =	clnt_nl_call,
+	.cl_close =	clnt_nl_close,
+	.cl_destroy =	clnt_nl_destroy,
+	.cl_control =	clnt_nl_control,
+};
+
+static int clnt_nl_reply(struct nlmsghdr *, struct nl_pstate *);
+
+static const struct genl_cmd clnt_cmds[] = {
+	{
+		.cmd_num = RPCNL_REPLY,
+		.cmd_name = "request",
+		.cmd_cb = clnt_nl_reply,
+		.cmd_priv = PRIV_NFS_DAEMON,
+	},
+};
+
+struct nl_reply_parsed {
+	uint32_t	group;
+	struct nlattr	*data;
+};
+static const struct nlattr_parser rpcnl_attr_parser[] = {
+#define	OUT(field)	offsetof(struct nl_reply_parsed, field)
+    { .type = RPCNL_REPLY_GROUP, .off = OUT(group), .cb = nlattr_get_uint32 },
+    { .type = RPCNL_REPLY_BODY, .off = OUT(data), .cb = nlattr_get_nla },
+#undef OUT
+};
+NL_DECLARE_PARSER(rpcnl_parser, struct genlmsghdr, nlf_p_empty,
+    rpcnl_attr_parser);
+
+struct nl_data {
+	struct mtx	nl_lock;
+	RB_ENTRY(nl_data) nl_tree;
+	TAILQ_HEAD(, ct_request) nl_pending;
+	uint32_t	nl_xid;
+	u_int		nl_mpos;
+	u_int		nl_authlen;
+	u_int		nl_retries;
+	struct {
+		struct genlmsghdr ghdr;
+		struct nlattr gattr;
+		uint32_t group;
+	} nl_hdr;	/* pre-initialized header */
+	char		nl_mcallc[MCALL_MSG_SIZE]; /* marshalled callmsg */
+	/* msleep(9) arguments */
+	const char *	nl_wchan;
+	int		nl_prio;
+	int		nl_timo;
+};
+
+static RB_HEAD(nl_data_t, nl_data) rpcnl_clients;
+static int32_t
+nl_data_compare(const struct nl_data *a, const struct nl_data *b)
+{
+	return ((int32_t)(a->nl_hdr.group - b->nl_hdr.group));
+}
+RB_GENERATE_STATIC(nl_data_t, nl_data, nl_tree, nl_data_compare);
+static struct rwlock rpcnl_global_lock;
+
+static const char rpcnl_family_name[] = "rpc";
+static uint16_t rpcnl_family_id;
+
+void
+rpcnl_init(void)
+{
+	bool rv __diagused;
+
+	rpcnl_family_id = genl_register_family(rpcnl_family_name, 0, 1, 1);
+	MPASS(rpcnl_family_id != 0);
+	rv = genl_register_cmds(rpcnl_family_name, clnt_cmds,
+	   nitems(clnt_cmds));
+	MPASS(rv);
+	rw_init(&rpcnl_global_lock, rpcnl_family_name);
+}
+
+CLIENT *
+client_nl_create(const char *name, const rpcprog_t program,
+    const rpcvers_t version)
+{
+	CLIENT *cl;
+	struct nl_data *nl;
+	struct timeval now;
+	struct rpc_msg call_msg;
+	XDR xdrs;
+	uint32_t group;
+	bool rv __diagused;
+
+	if ((group = genl_register_group(rpcnl_family_name, name)) == 0)
+		return (NULL);
+
+	nl = malloc(sizeof(*nl), M_RPC, M_WAITOK);
+	*nl = (struct nl_data){
+		.nl_pending = TAILQ_HEAD_INITIALIZER(nl->nl_pending),
+		.nl_hdr = {
+			.ghdr.cmd = RPCNL_REQUEST,
+			.gattr.nla_type = RPCNL_REQUEST_GROUP,
+			.gattr.nla_len = sizeof(struct nlattr) +
+			    sizeof(uint32_t),
+			.group = group,
+		},
+		.nl_wchan = rpcnl_family_name,
+		.nl_prio = PSOCK | PCATCH,
+		.nl_timo = 60 * hz,
+		.nl_retries = 1,
+	};
+	mtx_init(&nl->nl_lock, "rpc_clnt_nl", NULL, MTX_DEF);
+
+	/*
+	 * Initialize and pre-serialize the static part of the call message.
+	 */
+	getmicrotime(&now);
+	nl->nl_xid = __RPC_GETXID(&now);
+	call_msg = (struct rpc_msg ){
+		.rm_xid = nl->nl_xid,
+		.rm_direction = CALL,
+		.rm_call = {
+			.cb_rpcvers = RPC_MSG_VERSION,
+			.cb_prog = (uint32_t)program,
+			.cb_vers = (uint32_t)version,
+		},
+	};
+
+	cl = malloc(sizeof(*cl), M_RPC, M_WAITOK);
+	*cl = (CLIENT){
+		.cl_refs = 1,
+		.cl_ops = &clnt_nl_ops,
+		.cl_private = nl,
+		.cl_auth = authnone_create(),
+	};
+
+	/*
+	 * Experimentally learn how many bytes does procedure name plus
+	 * authnone header needs.  Use nl_mcallc as temporary scratch space.
+	 */
+	xdrmem_create(&xdrs, nl->nl_mcallc, MCALL_MSG_SIZE, XDR_ENCODE);
+	rv = xdr_putint32(&xdrs, &(rpcproc_t){0});
+	MPASS(rv);
+	rv = AUTH_MARSHALL(cl->cl_auth, 0, &xdrs, NULL);
+	MPASS(rv);
+	nl->nl_authlen = xdr_getpos(&xdrs);
+	xdr_destroy(&xdrs);
+
+	xdrmem_create(&xdrs, nl->nl_mcallc, MCALL_MSG_SIZE, XDR_ENCODE);
+	rv = xdr_callhdr(&xdrs, &call_msg);
+	MPASS(rv);
+	nl->nl_mpos = xdr_getpos(&xdrs);
+	xdr_destroy(&xdrs);
+
+	rw_wlock(&rpcnl_global_lock);
+	RB_INSERT(nl_data_t, &rpcnl_clients, nl);
+	rw_wunlock(&rpcnl_global_lock);
+
+	return (cl);
+}
+
+static enum clnt_stat
+clnt_nl_call(CLIENT *cl, struct rpc_callextra *ext, rpcproc_t proc,
+    struct mbuf *args, struct mbuf **resultsp, struct timeval utimeout)
+{
+	struct nl_writer nw;
+	struct nl_data *nl = cl->cl_private;
+	struct ct_request *cr;
+	struct rpc_err *errp, err;
+	enum clnt_stat stat;
+	AUTH *auth;
+	XDR xdrs;
+	void *mem;
+	uint32_t len, xlen;
+	u_int retries = 0;
+	bool rv __diagused;
+
+	cr = malloc(sizeof(struct ct_request), M_RPC, M_WAITOK);
+	*cr = (struct ct_request){
+		.cr_xid = atomic_fetchadd_32(&nl->nl_xid, 1),
+		.cr_error = ETIMEDOUT,
+	};
+
+	if (ext) {
+		auth = ext->rc_auth;
+		errp = &ext->rc_err;
+		len = RPC_MAXDATASIZE;	/* XXXGL: can be improved */
+	} else {
+		auth = cl->cl_auth;
+		errp = &err;
+		len = nl->nl_mpos + nl->nl_authlen + m_length(args, NULL);
+	}
+
+	mem = malloc(len, M_RPC, M_WAITOK);
+retry:
+	xdrmem_create(&xdrs, mem, len, XDR_ENCODE);
+
+	rv = xdr_putbytes(&xdrs, nl->nl_mcallc, nl->nl_mpos);
+	MPASS(rv);
+	rv = xdr_putint32(&xdrs, &proc);
+	MPASS(rv);
+	if (!AUTH_MARSHALL(auth, cr->cr_xid, &xdrs, args)) {
+		stat = errp->re_status = RPC_CANTENCODEARGS;
+		goto out;
+	} else
+		stat = errp->re_status = RPC_SUCCESS;
+
+	/* XXX: XID is the first thing in the request. */
+	*(uint32_t *)mem = htonl(cr->cr_xid);
+
+	xlen = xdr_getpos(&xdrs);
+	rv = nl_writer_group(&nw, xlen, NETLINK_GENERIC, nl->nl_hdr.group,
+	    PRIV_NFS_DAEMON, true);
+	MPASS(rv);
+
+	rv = nlmsg_add(&nw, 0, cr->cr_xid, rpcnl_family_id, 0,
+	    sizeof(nl->nl_hdr) + sizeof(struct nlattr) + xlen);
+	MPASS(rv);
+
+	memcpy(nlmsg_reserve_data_raw(&nw, sizeof(nl->nl_hdr)), &nl->nl_hdr,
+	    sizeof(nl->nl_hdr));
+
+	rv = nlattr_add(&nw, RPCNL_REQUEST_BODY, xlen, mem);
+	MPASS(rv);
+
+	rv = nlmsg_end(&nw);
+	MPASS(rv);
+
+	mtx_lock(&nl->nl_lock);
+	TAILQ_INSERT_TAIL(&nl->nl_pending, cr, cr_link);
+	mtx_unlock(&nl->nl_lock);
+
+	nlmsg_flush(&nw);
+
+	mtx_lock(&nl->nl_lock);
+	if (__predict_true(cr->cr_error == ETIMEDOUT))
+		(void)msleep(cr, &nl->nl_lock, nl->nl_prio, nl->nl_wchan,
+		    (nl->nl_timo ? nl->nl_timo : tvtohz(&utimeout)) /
+		    nl->nl_retries);
+	TAILQ_REMOVE(&nl->nl_pending, cr, cr_link);
+	mtx_unlock(&nl->nl_lock);
+
+	if (__predict_true(cr->cr_error == 0)) {
+		struct rpc_msg reply_msg = {
+			.acpted_rply.ar_verf.oa_base = cr->cr_verf,
+			.acpted_rply.ar_results.proc = (xdrproc_t)xdr_void,
+		};
+
+		MPASS(cr->cr_mrep);
+		if (ext && ext->rc_feedback)
+			ext->rc_feedback(FEEDBACK_OK, proc,
+			    ext->rc_feedback_arg);
+		xdrmbuf_create(&xdrs, cr->cr_mrep, XDR_DECODE);
+		rv = xdr_replymsg(&xdrs, &reply_msg);
+		if (__predict_false(!rv)) {
+			stat = errp->re_status = RPC_CANTDECODERES;
+			goto out;
+		}
+		if ((reply_msg.rm_reply.rp_stat == MSG_ACCEPTED) &&
+		    (reply_msg.acpted_rply.ar_stat == SUCCESS)) {
+			struct mbuf *results;
+
+                        stat = errp->re_status = RPC_SUCCESS;
+			results = xdrmbuf_getall(&xdrs);
+			if (__predict_true(AUTH_VALIDATE(auth, cr->cr_xid,
+			    &reply_msg.acpted_rply.ar_verf, &results))) {
+                                MPASS(results);
+                                *resultsp = results;
+				/* end successful completion */
+			} else {
+				stat = errp->re_status = RPC_AUTHERROR;
+				errp->re_why = AUTH_INVALIDRESP;
+			}
+		} else {
+			stat = _seterr_reply(&reply_msg, errp);
+		}
+		xdr_destroy(&xdrs);	/* frees cr->cr_mrep */
+	} else {
+		MPASS(cr->cr_mrep == NULL);
+		errp->re_errno = cr->cr_error;
+		stat = errp->re_status = RPC_CANTRECV;
+		if (cr->cr_error == ETIMEDOUT && ++retries < nl->nl_retries) {
+			cr->cr_xid = atomic_fetchadd_32(&nl->nl_xid, 1);
+			goto retry;
+		}
+	}
+out:
+	free(cr, M_RPC);
+	free(mem, M_RPC);
+
+	return (stat);
+}
+
+static int
+clnt_nl_reply(struct nlmsghdr *hdr, struct nl_pstate *npt)
+{
+	struct nl_reply_parsed attrs = {};
+	struct nl_data *nl;
+	struct ct_request *cr;
+	struct mchain mc;
+	int error;
+
+	if ((error = nl_parse_nlmsg(hdr, &rpcnl_parser, npt, &attrs)) != 0)
+		return (error);
+	if (attrs.data == NULL)
+		return (EINVAL);
+
+	error = mc_get(&mc, NLA_DATA_LEN(attrs.data), M_WAITOK, MT_DATA, 0);
+	MPASS(error == 0);
+	m_copyback(mc_first(&mc), 0, NLA_DATA_LEN(attrs.data),
+	    NLA_DATA(attrs.data));
+
+	rw_rlock(&rpcnl_global_lock);
+	if ((nl = RB_FIND(nl_data_t, &rpcnl_clients,
+	    &(struct nl_data){ .nl_hdr.group = attrs.group })) == NULL) {
+		rw_runlock(&rpcnl_global_lock);
+		mc_freem(&mc);
+		return (EPROGUNAVAIL);
+	};
+	mtx_lock(&nl->nl_lock);
+	rw_runlock(&rpcnl_global_lock);
+
+	TAILQ_FOREACH(cr, &nl->nl_pending, cr_link)
+		if (cr->cr_xid == hdr->nlmsg_seq)
+			break;
+	if (cr == NULL) {
+		mtx_unlock(&nl->nl_lock);
+		mc_freem(&mc);
+		return (EPROCUNAVAIL);
+	}
+	cr->cr_mrep = mc_first(&mc);
+	cr->cr_error = 0;
+	wakeup(cr);
+	mtx_unlock(&nl->nl_lock);
+
+	return (0);
+}
+
+static void
+clnt_nl_close(CLIENT *cl)
+{
+	struct nl_data *nl =  cl->cl_private;
+	struct ct_request *cr;
+
+	mtx_lock(&nl->nl_lock);
+	TAILQ_FOREACH(cr, &nl->nl_pending, cr_link) {
+		cr->cr_error = ESHUTDOWN;
+		wakeup(cr);
+	}
+	mtx_unlock(&nl->nl_lock);
+}
+
+static void
+clnt_nl_destroy(CLIENT *cl)
+{
+	struct nl_data *nl = cl->cl_private;
+
+	MPASS(TAILQ_EMPTY(&nl->nl_pending));
+
+	rw_wlock(&rpcnl_global_lock);
+	RB_REMOVE(nl_data_t, &rpcnl_clients, nl);
+	rw_wlock(&rpcnl_global_lock);
+
+	mtx_destroy(&nl->nl_lock);
+	free(nl, M_RPC);
+	free(cl, M_RPC);
+}
+
+static bool_t
+clnt_nl_control(CLIENT *cl, u_int request, void *info)
+{
+	struct nl_data *nl = (struct nl_data *)cl->cl_private;
+
+	mtx_lock(&nl->nl_lock);
+	switch (request) {
+	case CLSET_TIMEOUT:
+		nl->nl_timo = tvtohz((struct timeval *)info);
+		break;
+
+	case CLGET_TIMEOUT:
+		*(struct timeval *)info =
+		    (struct timeval){.tv_sec = nl->nl_timo / hz};
+		break;
+
+	case CLSET_RETRIES:
+		nl->nl_retries = *(u_int *)info;
+		break;
+
+	case CLSET_WAITCHAN:
+		nl->nl_wchan = (const char *)info;
+		break;
+
+	case CLGET_WAITCHAN:
+		*(const char **)info = nl->nl_wchan;
+		break;
+
+	case CLSET_INTERRUPTIBLE:
+		if (*(int *)info)
+			nl->nl_prio |= PCATCH;
+		else
+			nl->nl_prio &= ~PCATCH;
+		break;
+
+	case CLGET_INTERRUPTIBLE:
+		*(int *)info = (nl->nl_prio & PCATCH) ? TRUE : FALSE;
+		break;
+
+	default:
+		mtx_unlock(&nl->nl_lock);
+		printf("%s: unsupported request %u\n", __func__, request);
+		return (FALSE);
+	}
+
+	mtx_unlock(&nl->nl_lock);
+	return (TRUE);
+}
diff --git a/sys/rpc/clnt_nl.h b/sys/rpc/clnt_nl.h
new file mode 100644
index 000000000000..8b1c29ed7dd9
--- /dev/null
+++ b/sys/rpc/clnt_nl.h
@@ -0,0 +1,42 @@
+/*-
+ * 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.
+ */
+
+#ifndef _RPC_CLNT_NL_H
+#define _RPC_CLNT_NL_H
+
+enum rpcnl_cmds_t {
+	RPCNL_REQUEST = 1,	/* mcast: kernel -> userland */
+	RPCNL_REPLY,		/* unicast: userland -> kernel */
+};
+
+enum rpcnl_attr_t {
+	RPCNL_REQUEST_GROUP = 1,
+	RPCNL_REQUEST_BODY,
+	RPCNL_REPLY_GROUP,
+	RPCNL_REPLY_BODY,
+};
+#endif /* _RPC_CLNT_NL_H */
diff --git a/sys/rpc/krpc.h b/sys/rpc/krpc.h
index a5a34c8a4a8e..02c167d50d55 100644
--- a/sys/rpc/krpc.h
+++ b/sys/rpc/krpc.h
@@ -129,6 +129,8 @@ struct cf_conn {  /* kept in xprt->xp_p1 for actual connection */
 	bool_t eor;		/* reading last fragment of current record */
 };
 
+void rpcnl_init(void);
+
 #endif	/* _KERNEL */
 
 #endif	/* _RPC_KRPC_H_ */
diff --git a/sys/rpc/rpc_generic.c b/sys/rpc/rpc_generic.c
index 0d6c0f1b7c7d..b3cf2612bdbb 100644
--- a/sys/rpc/rpc_generic.c
+++ b/sys/rpc/rpc_generic.c
@@ -949,6 +949,7 @@ krpc_modevent(module_t mod, int type, void *data)
 
 	switch (type) {
 	case MOD_LOAD:
+		rpcnl_init();
 		error = rpctls_init();
 		break;
 	case MOD_UNLOAD:
@@ -967,7 +968,7 @@ static moduledata_t krpc_mod = {
 	krpc_modevent,
 	NULL,
 };
-DECLARE_MODULE(krpc, krpc_mod, SI_SUB_VFS, SI_ORDER_ANY);
+DECLARE_MODULE(krpc, krpc_mod, SI_SUB_VFS, SI_ORDER_FIRST);
 
 /* So that loader and kldload(2) can find us, wherever we are.. */
 MODULE_VERSION(krpc, 1);



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?202502010902.511924Dp047792>