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