xref: /freebsd/lib/libc/rpc/svc_nl.c (revision 220cdd1b394109a5db9e5b402141c746095c4292)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2025 Gleb Smirnoff <glebius@FreeBSD.org>
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27 
28 #include <stdlib.h>
29 #include <unistd.h>
30 #include <errno.h>
31 #include <pthread.h>
32 #include <rpc/rpc.h>
33 #include <rpc/clnt_nl.h>
34 
35 #include <netlink/netlink.h>
36 #include <netlink/netlink_generic.h>
37 #include <netlink/netlink_snl.h>
38 #include <netlink/netlink_snl_generic.h>
39 
40 #include "rpc_com.h"
41 #include "libc_private.h"
42 
43 /*
44  * RPC server to serve a kernel RPC client(s) over netlink(4).  See clnt_nl.c
45  * in sys/rpc as the counterpart.
46  *
47  * Upon creation the client will seek for specified multicast group within the
48  * generic netlink family named "rpc".  Then it would listen for incoming
49  * messages, process them and send replies over the same netlink socket.
50  * See clnt_nl.c for more transport protocol implementation details.
51  */
52 
53 static void svc_nl_destroy(SVCXPRT *);
54 static bool_t svc_nl_recv(SVCXPRT *, struct rpc_msg *);
55 static bool_t svc_nl_reply(SVCXPRT *, struct rpc_msg *);
56 static enum xprt_stat svc_nl_stat(SVCXPRT *);
57 static bool_t svc_nl_getargs(SVCXPRT *, xdrproc_t, void *);
58 static bool_t svc_nl_freeargs(SVCXPRT *, xdrproc_t, void *);
59 static bool_t svc_nl_control(SVCXPRT *, const u_int, void *);
60 
61 static struct xp_ops nl_ops = {
62 	.xp_recv = svc_nl_recv,
63 	.xp_reply = svc_nl_reply,
64 	.xp_stat = svc_nl_stat,
65 	.xp_getargs = svc_nl_getargs,
66 	.xp_freeargs = svc_nl_freeargs,
67 	.xp_destroy = svc_nl_destroy,
68 };
69 static struct xp_ops2 nl_ops2 = {
70 	.xp_control = svc_nl_control,
71 };
72 
73 struct nl_softc {
74 	struct snl_state snl;
75 	XDR		xdrs;
76 	struct nlmsghdr	*hdr;
77 	pthread_key_t	xidkey;
78 	size_t		mlen;
79 	enum xprt_stat	stat;
80 	uint32_t	xid;
81 	uint32_t	group;
82 	uint16_t	family;
83 	u_int		errline;
84 	int		error;
85 };
86 
87 SVCXPRT *
svc_nl_create(const char * service)88 svc_nl_create(const char *service)
89 {
90 	static struct sockaddr_nl snl_null = {
91 		.nl_len = sizeof(struct sockaddr_nl),
92 		.nl_family = PF_NETLINK,
93 	};
94 	struct nl_softc *sc;
95 	SVCXPRT *xprt = NULL;
96 	void *buf = NULL;
97 	uint16_t family;
98 	ssize_t len = 1024;
99 
100 	if ((sc = calloc(1, sizeof(struct nl_softc))) == NULL)
101 		return (NULL);
102 	if (!snl_init(&sc->snl, NETLINK_GENERIC) || (sc->group =
103 	    snl_get_genl_mcast_group(&sc->snl, "rpc", service, &family)) == 0)
104 		goto fail;
105 	if (setsockopt(sc->snl.fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP,
106 	    &sc->group, sizeof(sc->group)) == -1)
107 		goto fail;
108 	if ((buf = malloc(len)) == NULL)
109 		goto fail;
110 	if ((xprt = svc_xprt_alloc()) == NULL)
111 		goto fail;
112 
113 	sc->hdr = buf,
114 	sc->mlen = len,
115 	sc->stat = XPRT_IDLE,
116 	sc->family = family;
117 
118 	if (__isthreaded &&
119 	    (pthread_key_create(&sc->xidkey, NULL) != 0 ||
120 	    pthread_setspecific(sc->xidkey, &sc->xid) != 0))
121 		goto fail;
122 
123 	xprt->xp_fd = sc->snl.fd,
124 	xprt->xp_p1 = sc,
125 	xprt->xp_ops = &nl_ops,
126 	xprt->xp_ops2 = &nl_ops2,
127 	xprt->xp_rtaddr = (struct netbuf){
128 		.maxlen = sizeof(struct sockaddr_nl),
129 		.len = sizeof(struct sockaddr_nl),
130 		.buf = &snl_null,
131 	};
132 	xprt_register(xprt);
133 
134 	return (xprt);
135 fail:
136 	free(xprt);
137 	free(buf);
138 	snl_free(&sc->snl);
139 	free(sc);
140 	return (NULL);
141 }
142 
143 static void
svc_nl_destroy(SVCXPRT * xprt)144 svc_nl_destroy(SVCXPRT *xprt)
145 {
146 	struct nl_softc *sc = xprt->xp_p1;
147 
148 	snl_free(&sc->snl);
149 	free(sc->hdr);
150 	free(xprt->xp_p1);
151 	svc_xprt_free(xprt);
152 }
153 
154 #define	DIE(sc) do {							\
155 	(sc)->stat = XPRT_DIED;						\
156 	(sc)->errline = __LINE__;					\
157 	(sc)->error = errno;						\
158 	return (FALSE);							\
159 } while (0)
160 
161 struct nl_request_parsed {
162 	uint32_t	group;
163 	struct nlattr	*data;
164 };
165 static const struct snl_attr_parser rpcnl_attr_parser[] = {
166 #define	OUT(field)	offsetof(struct nl_request_parsed, field)
167     { .type = RPCNL_REQUEST_GROUP, .off = OUT(group),
168        .cb = snl_attr_get_uint32 },
169     { .type = RPCNL_REQUEST_BODY, .off = OUT(data), .cb = snl_attr_get_nla },
170 #undef OUT
171 };
172 SNL_DECLARE_GENL_PARSER(request_parser, rpcnl_attr_parser);
173 
174 static bool_t
svc_nl_recv(SVCXPRT * xprt,struct rpc_msg * msg)175 svc_nl_recv(SVCXPRT *xprt, struct rpc_msg *msg)
176 {
177 	struct nl_request_parsed req;
178 	struct nl_softc *sc = xprt->xp_p1;
179 	struct nlmsghdr *hdr = sc->hdr;
180 
181 	switch (sc->stat) {
182 	case XPRT_IDLE:
183 		if (recv(xprt->xp_fd, hdr, sizeof(struct nlmsghdr),
184 		    MSG_PEEK) != sizeof(struct nlmsghdr))
185 			DIE(sc);
186 		break;
187 	case XPRT_MOREREQS:
188 		sc->stat = XPRT_IDLE;
189 		break;
190 	case XPRT_DIED:
191 		return (FALSE);
192 	}
193 
194 	if (sc->mlen < hdr->nlmsg_len) {
195 		if ((hdr = sc->hdr = realloc(hdr, hdr->nlmsg_len)) == NULL)
196 			DIE(sc);
197 		else
198 			sc->mlen = hdr->nlmsg_len;
199 	}
200 	if (read(xprt->xp_fd, hdr, hdr->nlmsg_len) != hdr->nlmsg_len)
201 		DIE(sc);
202 
203 	if (hdr->nlmsg_type != sc->family)
204 		return (FALSE);
205 
206 	if (((struct genlmsghdr *)(hdr + 1))->cmd != RPCNL_REQUEST)
207 		return (FALSE);
208 
209 	if (!snl_parse_nlmsg(NULL, hdr, &request_parser, &req))
210 		return (FALSE);
211 
212 	if (req.group != sc->group)
213 		return (FALSE);
214 
215 	xdrmem_create(&sc->xdrs, NLA_DATA(req.data), NLA_DATA_LEN(req.data),
216 	    XDR_DECODE);
217 	if (xdr_callmsg(&sc->xdrs, msg)) {
218 		/* XXX: assert that xid == nlmsg_seq? */
219 		sc->xid = msg->rm_xid;
220 		return (TRUE);
221 	} else
222 		return (FALSE);
223 }
224 
225 /*
226  * Reenterable reply method.  Note that both the softc and xprt are declared
227  * const.  The qualifier for xprt is commented out to match the library
228  * prototype.  If doing any substantial changes to the function please
229  * temporarily uncomment the const for xprt and check your changes.
230  *
231  * Applications that want to use svc_nl_reply in a spawned thread context
232  * should do the following hacks in self:
233  * 1) - Create xprt with svc_nl_create() with libc in threaded mode, e.g.
234  *      at least one pthread_create() shall happen before svc_nl_create().
235  *    - After xprt creation query it for the pthread_key_t with the
236  *      SVCNL_GET_XIDKEY control and save this key.
237  * 2) In the RPC function body that wants to become multithreaded:
238  *    - Make a copy of the arguments and of the xid with help of
239  *      pthread_getspecific() using the key.
240  *    - pthread_create() the worker function, pointing it at the copy of
241  *      arguments and xid.
242  *    - return FALSE, so that RPC generated code doesn't do anything.
243  * 3) In the spawned thread:
244  *    - Use arguments provided in the copy by the parent.
245  *    - Allocate appropriately typed result on stack.
246  *    - *** do the actual work ***
247  *    - Populate the on-stack result same way as pointed result is populated
248  *      in a regular RPC function.
249  *    - Point the thread specific storage to the copy of xid provided by the
250  *      parent with help of pthread_setspecific().
251  *    - Call svc_sendreply() just like the rpcgen(1) generated code does.
252  *
253  * If all done correctly svc_nl_reply() will use thread specific xid for
254  * a call that was processed asynchronously and most recent xid when entered
255  * synchronously.  So you can make only some methods of your application
256  * reentrable, keeping others as is.
257  */
258 
259 static bool_t
svc_nl_reply(SVCXPRT * xprt,struct rpc_msg * msg)260 svc_nl_reply(/* const */ SVCXPRT *xprt, struct rpc_msg *msg)
261 {
262 	const struct nl_softc *sc = xprt->xp_p1;
263 	struct snl_state snl;
264 	struct snl_writer nw;
265 	XDR xdrs;
266 	struct nlattr *body;
267 	bool_t rv;
268 
269 	msg->rm_xid = __isthreaded ?
270 	    *(uint32_t *)pthread_getspecific(sc->xidkey) :
271 	    sc->xid;
272 
273 	if (__predict_false(!snl_clone(&snl, &sc->snl)))
274 		return (FALSE);
275 	snl_init_writer(&snl, &nw);
276 	snl_create_genl_msg_request(&nw, sc->family, RPCNL_REPLY);
277 	snl_add_msg_attr_u32(&nw, RPCNL_REPLY_GROUP, sc->group);
278 	body = snl_reserve_msg_attr_raw(&nw, RPCNL_REPLY_BODY, RPC_MAXDATASIZE);
279 
280 	xdrmem_create(&xdrs, (char *)(body + 1), RPC_MAXDATASIZE, XDR_ENCODE);
281 
282 	if (msg->rm_reply.rp_stat == MSG_ACCEPTED &&
283 	    msg->rm_reply.rp_acpt.ar_stat == SUCCESS) {
284 		xdrproc_t xdr_proc;
285 		char *xdr_where;
286 		u_int pos;
287 
288 		xdr_proc = msg->acpted_rply.ar_results.proc;
289 		xdr_where = msg->acpted_rply.ar_results.where;
290 		msg->acpted_rply.ar_results.proc = (xdrproc_t) xdr_void;
291 		msg->acpted_rply.ar_results.where = NULL;
292 
293 		pos = xdr_getpos(&xdrs);
294 		if (!xdr_replymsg(&xdrs, msg) ||
295 		    !SVCAUTH_WRAP(&SVC_AUTH(xprt), &xdrs, xdr_proc,
296 		    xdr_where)) {
297 			xdr_setpos(&xdrs, pos);
298 			rv = FALSE;
299 		} else
300 			rv = TRUE;
301 	} else
302 		rv = xdr_replymsg(&xdrs, msg);
303 
304 	if (rv) {
305 		/* snl_finalize_msg() really doesn't work for us */
306 		body->nla_len = sizeof(struct nlattr) + xdr_getpos(&xdrs);
307 		nw.hdr->nlmsg_len = ((char *)body - (char *)nw.hdr) +
308 		    body->nla_len;
309 		nw.hdr->nlmsg_type = sc->family;
310 		nw.hdr->nlmsg_flags = NLM_F_REQUEST;
311 		nw.hdr->nlmsg_seq = msg->rm_xid;
312 		if (write(xprt->xp_fd, nw.hdr, nw.hdr->nlmsg_len) !=
313 		    nw.hdr->nlmsg_len)
314 			DIE(__DECONST(struct nl_softc *, sc));
315 	}
316 
317 	snl_free(&snl);
318 
319 	return (rv);
320 }
321 
322 static bool_t
svc_nl_control(SVCXPRT * xprt,const u_int req,void * v)323 svc_nl_control(SVCXPRT *xprt, const u_int req, void *v)
324 {
325 	struct nl_softc *sc = xprt->xp_p1;
326 
327 	switch (req) {
328 	case SVCNL_GET_XIDKEY:
329 		if (!__isthreaded) {
330 			/*
331 			 * Report to application that it had created xprt not
332 			 * in threaded mode, but definitly plans to use it with
333 			 * threads.  If it tries so, it would very likely crash.
334 			 */
335 			errno = EDOOFUS;
336 			DIE(sc);
337 		};
338 		*(pthread_key_t *)v = sc->xidkey;
339 		return (TRUE);
340 	default:
341 		return (FALSE);
342 	}
343 }
344 
345 static enum xprt_stat
svc_nl_stat(SVCXPRT * xprt)346 svc_nl_stat(SVCXPRT *xprt)
347 {
348 	struct nl_softc *sc = xprt->xp_p1;
349 
350 	if (sc->stat == XPRT_IDLE &&
351 	    recv(xprt->xp_fd, sc->hdr, sizeof(struct nlmsghdr),
352 	    MSG_PEEK | MSG_DONTWAIT) == sizeof(struct nlmsghdr))
353 		sc->stat = XPRT_MOREREQS;
354 
355 	return (sc->stat);
356 }
357 
358 static bool_t
svc_nl_getargs(SVCXPRT * xprt,xdrproc_t xdr_args,void * args_ptr)359 svc_nl_getargs(SVCXPRT *xprt, xdrproc_t xdr_args, void *args_ptr)
360 {
361 	struct nl_softc *sc = xprt->xp_p1;
362 
363 	return (SVCAUTH_UNWRAP(&SVC_AUTH(xprt), &sc->xdrs, xdr_args, args_ptr));
364 }
365 
366 static bool_t
svc_nl_freeargs(SVCXPRT * xprt,xdrproc_t xdr_args,void * args_ptr)367 svc_nl_freeargs(SVCXPRT *xprt, xdrproc_t xdr_args, void *args_ptr)
368 {
369 	struct nl_softc *sc = xprt->xp_p1;
370 
371 	sc->xdrs.x_op = XDR_FREE;
372 	return ((*xdr_args)(&sc->xdrs, args_ptr));
373 }
374