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;
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(buf);
137 snl_free(&sc->snl);
138 free(sc);
139 return (NULL);
140 }
141
142 static void
svc_nl_destroy(SVCXPRT * xprt)143 svc_nl_destroy(SVCXPRT *xprt)
144 {
145 struct nl_softc *sc = xprt->xp_p1;
146
147 snl_free(&sc->snl);
148 free(sc->hdr);
149 free(xprt->xp_p1);
150 svc_xprt_free(xprt);
151 }
152
153 #define DIE(sc) do { \
154 (sc)->stat = XPRT_DIED; \
155 (sc)->errline = __LINE__; \
156 (sc)->error = errno; \
157 return (FALSE); \
158 } while (0)
159
160 struct nl_request_parsed {
161 uint32_t group;
162 struct nlattr *data;
163 };
164 static const struct snl_attr_parser rpcnl_attr_parser[] = {
165 #define OUT(field) offsetof(struct nl_request_parsed, field)
166 { .type = RPCNL_REQUEST_GROUP, .off = OUT(group),
167 .cb = snl_attr_get_uint32 },
168 { .type = RPCNL_REQUEST_BODY, .off = OUT(data), .cb = snl_attr_get_nla },
169 #undef OUT
170 };
171 SNL_DECLARE_GENL_PARSER(request_parser, rpcnl_attr_parser);
172
173 static bool_t
svc_nl_recv(SVCXPRT * xprt,struct rpc_msg * msg)174 svc_nl_recv(SVCXPRT *xprt, struct rpc_msg *msg)
175 {
176 struct nl_request_parsed req;
177 struct nl_softc *sc = xprt->xp_p1;
178 struct nlmsghdr *hdr = sc->hdr;
179
180 switch (sc->stat) {
181 case XPRT_IDLE:
182 if (recv(xprt->xp_fd, hdr, sizeof(struct nlmsghdr),
183 MSG_PEEK) != sizeof(struct nlmsghdr))
184 DIE(sc);
185 break;
186 case XPRT_MOREREQS:
187 sc->stat = XPRT_IDLE;
188 break;
189 case XPRT_DIED:
190 return (FALSE);
191 }
192
193 if (sc->mlen < hdr->nlmsg_len) {
194 if ((hdr = sc->hdr = realloc(hdr, hdr->nlmsg_len)) == NULL)
195 DIE(sc);
196 else
197 sc->mlen = hdr->nlmsg_len;
198 }
199 if (read(xprt->xp_fd, hdr, hdr->nlmsg_len) != hdr->nlmsg_len)
200 DIE(sc);
201
202 if (hdr->nlmsg_type != sc->family)
203 return (FALSE);
204
205 if (((struct genlmsghdr *)(hdr + 1))->cmd != RPCNL_REQUEST)
206 return (FALSE);
207
208 if (!snl_parse_nlmsg(NULL, hdr, &request_parser, &req))
209 return (FALSE);
210
211 if (req.group != sc->group)
212 return (FALSE);
213
214 xdrmem_create(&sc->xdrs, NLA_DATA(req.data), NLA_DATA_LEN(req.data),
215 XDR_DECODE);
216 if (xdr_callmsg(&sc->xdrs, msg)) {
217 /* XXX: assert that xid == nlmsg_seq? */
218 sc->xid = msg->rm_xid;
219 return (TRUE);
220 } else
221 return (FALSE);
222 }
223
224 /*
225 * Reenterable reply method. Note that both the softc and xprt are declared
226 * const. The qualifier for xprt is commented out to match the library
227 * prototype. If doing any substantial changes to the function please
228 * temporarily uncomment the const for xprt and check your changes.
229 *
230 * Applications that want to use svc_nl_reply in a spawned thread context
231 * should do the following hacks in self:
232 * 1) - Create xprt with svc_nl_create() with libc in threaded mode, e.g.
233 * at least one pthread_create() shall happen before svc_nl_create().
234 * - After xprt creation query it for the pthread_key_t with the
235 * SVCNL_GET_XIDKEY control and save this key.
236 * 2) In the RPC function body that wants to become multithreaded:
237 * - Make a copy of the arguments and of the xid with help of
238 * pthread_getspecific() using the key.
239 * - pthread_create() the worker function, pointing it at the copy of
240 * arguments and xid.
241 * - return FALSE, so that RPC generated code doesn't do anything.
242 * 3) In the spawned thread:
243 * - Use arguments provided in the copy by the parent.
244 * - Allocate appropriately typed result on stack.
245 * - *** do the actual work ***
246 * - Populate the on-stack result same way as pointed result is populated
247 * in a regular RPC function.
248 * - Point the thread specific storage to the copy of xid provided by the
249 * parent with help of pthread_setspecific().
250 * - Call svc_sendreply() just like the rpcgen(1) generated code does.
251 *
252 * If all done correctly svc_nl_reply() will use thread specific xid for
253 * a call that was processed asynchronously and most recent xid when entered
254 * synchronously. So you can make only some methods of your application
255 * reentrable, keeping others as is.
256 */
257
258 static bool_t
svc_nl_reply(SVCXPRT * xprt,struct rpc_msg * msg)259 svc_nl_reply(/* const */ SVCXPRT *xprt, struct rpc_msg *msg)
260 {
261 const struct nl_softc *sc = xprt->xp_p1;
262 struct snl_state snl;
263 struct snl_writer nw;
264 XDR xdrs;
265 struct nlattr *body;
266 bool_t rv;
267
268 msg->rm_xid = __isthreaded ?
269 *(uint32_t *)pthread_getspecific(sc->xidkey) :
270 sc->xid;
271
272 if (__predict_false(!snl_clone(&snl, &sc->snl)))
273 return (FALSE);
274 snl_init_writer(&snl, &nw);
275 snl_create_genl_msg_request(&nw, sc->family, RPCNL_REPLY);
276 snl_add_msg_attr_u32(&nw, RPCNL_REPLY_GROUP, sc->group);
277 body = snl_reserve_msg_attr_raw(&nw, RPCNL_REPLY_BODY, RPC_MAXDATASIZE);
278
279 xdrmem_create(&xdrs, (char *)(body + 1), RPC_MAXDATASIZE, XDR_ENCODE);
280
281 if (msg->rm_reply.rp_stat == MSG_ACCEPTED &&
282 msg->rm_reply.rp_acpt.ar_stat == SUCCESS) {
283 xdrproc_t xdr_proc;
284 char *xdr_where;
285 u_int pos;
286
287 xdr_proc = msg->acpted_rply.ar_results.proc;
288 xdr_where = msg->acpted_rply.ar_results.where;
289 msg->acpted_rply.ar_results.proc = (xdrproc_t) xdr_void;
290 msg->acpted_rply.ar_results.where = NULL;
291
292 pos = xdr_getpos(&xdrs);
293 if (!xdr_replymsg(&xdrs, msg) ||
294 !SVCAUTH_WRAP(&SVC_AUTH(xprt), &xdrs, xdr_proc,
295 xdr_where)) {
296 xdr_setpos(&xdrs, pos);
297 rv = FALSE;
298 } else
299 rv = TRUE;
300 } else
301 rv = xdr_replymsg(&xdrs, msg);
302
303 if (rv) {
304 /* snl_finalize_msg() really doesn't work for us */
305 body->nla_len = sizeof(struct nlattr) + xdr_getpos(&xdrs);
306 nw.hdr->nlmsg_len = ((char *)body - (char *)nw.hdr) +
307 body->nla_len;
308 nw.hdr->nlmsg_type = sc->family;
309 nw.hdr->nlmsg_flags = NLM_F_REQUEST;
310 nw.hdr->nlmsg_seq = msg->rm_xid;
311 if (write(xprt->xp_fd, nw.hdr, nw.hdr->nlmsg_len) !=
312 nw.hdr->nlmsg_len)
313 DIE(__DECONST(struct nl_softc *, sc));
314 }
315
316 snl_free(&snl);
317
318 return (rv);
319 }
320
321 static bool_t
svc_nl_control(SVCXPRT * xprt,const u_int req,void * v)322 svc_nl_control(SVCXPRT *xprt, const u_int req, void *v)
323 {
324 struct nl_softc *sc = xprt->xp_p1;
325
326 switch (req) {
327 case SVCNL_GET_XIDKEY:
328 if (!__isthreaded) {
329 /*
330 * Report to application that it had created xprt not
331 * in threaded mode, but definitly plans to use it with
332 * threads. If it tries so, it would very likely crash.
333 */
334 errno = EDOOFUS;
335 DIE(sc);
336 };
337 *(pthread_key_t *)v = sc->xidkey;
338 return (TRUE);
339 default:
340 return (FALSE);
341 }
342 }
343
344 static enum xprt_stat
svc_nl_stat(SVCXPRT * xprt)345 svc_nl_stat(SVCXPRT *xprt)
346 {
347 struct nl_softc *sc = xprt->xp_p1;
348
349 if (sc->stat == XPRT_IDLE &&
350 recv(xprt->xp_fd, sc->hdr, sizeof(struct nlmsghdr),
351 MSG_PEEK | MSG_DONTWAIT) == sizeof(struct nlmsghdr))
352 sc->stat = XPRT_MOREREQS;
353
354 return (sc->stat);
355 }
356
357 static bool_t
svc_nl_getargs(SVCXPRT * xprt,xdrproc_t xdr_args,void * args_ptr)358 svc_nl_getargs(SVCXPRT *xprt, xdrproc_t xdr_args, void *args_ptr)
359 {
360 struct nl_softc *sc = xprt->xp_p1;
361
362 return (SVCAUTH_UNWRAP(&SVC_AUTH(xprt), &sc->xdrs, xdr_args, args_ptr));
363 }
364
365 static bool_t
svc_nl_freeargs(SVCXPRT * xprt,xdrproc_t xdr_args,void * args_ptr)366 svc_nl_freeargs(SVCXPRT *xprt, xdrproc_t xdr_args, void *args_ptr)
367 {
368 struct nl_softc *sc = xprt->xp_p1;
369
370 sc->xdrs.x_op = XDR_FREE;
371 return ((*xdr_args)(&sc->xdrs, args_ptr));
372 }
373