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 * 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 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 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 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 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 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 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 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