1 /* $NetBSD: wg_user.c,v 1.5 2026/07/04 22:22:33 riastradh Exp $ */ 2 3 /* 4 * Copyright (C) Ryota Ozaki <ozaki.ryota (at) gmail.com> 5 * All rights reserved. 6 * 7 * Based on wg_user.c by Antti Kantee. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS 19 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 * SUCH DAMAGE. 29 */ 30 31 #include <sys/cdefs.h> 32 __KERNEL_RCSID(0, "$NetBSD: wg_user.c,v 1.5 2026/07/04 22:22:33 riastradh Exp $"); 33 34 #include <sys/types.h> 35 #include <sys/ioctl.h> 36 #include <sys/uio.h> 37 #include <sys/socket.h> 38 #include <sys/param.h> 39 40 #include <net/if.h> 41 #include <net/if_tun.h> 42 43 #include <netinet/in.h> 44 45 #include <assert.h> 46 #include <errno.h> 47 #include <fcntl.h> 48 #include <inttypes.h> 49 #include <poll.h> 50 #include <pthread.h> 51 #include <stdio.h> 52 #include <stdlib.h> 53 #include <string.h> 54 #include <unistd.h> 55 56 #include <rump/rumpuser_component.h> 57 58 #include "wg_user.h" 59 60 struct wg_user { 61 struct wg_softc *wgu_sc; 62 int wgu_devnum; 63 char wgu_tun_name[IFNAMSIZ]; 64 65 int wgu_fd; 66 int wgu_sock4; 67 int wgu_sock6; 68 int wgu_pipe[2]; 69 pthread_t wgu_rcvthr; 70 71 int wgu_dying; 72 73 struct { 74 union { 75 struct sockaddr sa; 76 struct sockaddr_in sin; 77 struct sockaddr_in6 sin6; 78 } addr; 79 char payload[9018]; /* jumbo frame max len */ 80 } wgu_rcvbuf; 81 }; 82 83 static int 84 open_tun(const char *tun_name) 85 { 86 char tun_path[MAXPATHLEN]; 87 int n, fd, error; 88 89 n = snprintf(tun_path, sizeof(tun_path), "/dev/%s", tun_name); 90 if (n == MAXPATHLEN) 91 return E2BIG; 92 93 fd = open(tun_path, O_RDWR); 94 if (fd == -1) { 95 fprintf(stderr, "%s: can't open %s: %s\n", 96 __func__, tun_name, strerror(errno)); 97 } 98 99 int i = 1; 100 error = ioctl(fd, TUNSLMODE, &i); 101 if (error == -1) { 102 close(fd); 103 fd = -1; 104 } 105 106 return fd; 107 } 108 109 static void 110 close_tun(struct wg_user *wgu) 111 { 112 int s; 113 struct ifreq ifr = {}; 114 115 close(wgu->wgu_fd); 116 117 s = socket(AF_INET, SOCK_DGRAM, 0); 118 if (s == -1) 119 return; /* XXX */ 120 strcpy(ifr.ifr_name, wgu->wgu_tun_name); 121 (void)ioctl(s, SIOCIFDESTROY, &ifr); 122 close(s); 123 } 124 125 static void * 126 wg_user_rcvthread(void *aaargh) 127 { 128 struct wg_user *wgu = aaargh; 129 struct pollfd pfd[4]; 130 ssize_t nn = 0; 131 int prv; 132 133 rumpuser_component_kthread(); 134 135 pfd[0].fd = wgu->wgu_fd; 136 pfd[0].events = POLLIN; 137 pfd[1].fd = wgu->wgu_pipe[0]; 138 pfd[1].events = POLLIN; 139 pfd[2].fd = wgu->wgu_sock4; 140 pfd[2].events = POLLIN; 141 pfd[3].fd = wgu->wgu_sock6; 142 pfd[3].events = POLLIN; 143 144 while (!wgu->wgu_dying) { 145 prv = poll(pfd, 4, -1); 146 if (prv == 0) 147 continue; 148 if (prv == -1) { 149 /* XXX */ 150 fprintf(stderr, "%s: poll error: %d\n", 151 wgu->wgu_tun_name, errno); 152 sleep(1); 153 continue; 154 } 155 156 /* rumpuser_wg_destroy notified us it's time */ 157 if (pfd[1].revents & POLLIN) 158 continue; 159 160 /* Receive user packets from tun */ 161 if (pfd[0].revents & POLLIN) { 162 const struct sockaddr *dst; 163 const void *pkt; 164 size_t pktlen; 165 166 nn = read(wgu->wgu_fd, &wgu->wgu_rcvbuf, 167 sizeof(wgu->wgu_rcvbuf)); 168 if (nn == -1 && errno == EAGAIN) 169 continue; 170 171 if (nn < 1) { 172 /* XXX */ 173 fprintf(stderr, "%s: receive failed\n", 174 wgu->wgu_tun_name); 175 sleep(1); 176 continue; 177 } 178 179 dst = &wgu->wgu_rcvbuf.addr.sa; 180 pkt = (const char *)dst + dst->sa_len; 181 pktlen = (size_t)nn - dst->sa_len; 182 183 rumpuser_component_schedule(NULL); 184 rumpkern_wg_recv_user(wgu->wgu_sc, dst, pkt, pktlen); 185 rumpuser_component_unschedule(); 186 } 187 188 /* Receive wg UDP/IPv4 packets from a peer */ 189 if (pfd[2].revents & POLLIN) { 190 struct sockaddr *src = &wgu->wgu_rcvbuf.addr.sa; 191 socklen_t len = sizeof(wgu->wgu_rcvbuf.addr.sin); 192 const void *pkt; 193 size_t pktlen; 194 195 nn = recvfrom(wgu->wgu_sock4, wgu->wgu_rcvbuf.payload, 196 sizeof(wgu->wgu_rcvbuf.payload), 0, src, &len); 197 if (nn == -1) 198 continue; 199 if (len != sizeof(wgu->wgu_rcvbuf.addr.sin)) 200 continue; 201 pkt = wgu->wgu_rcvbuf.payload; 202 pktlen = (size_t)nn; 203 204 rumpuser_component_schedule(NULL); 205 rumpkern_wg_recv_peer(wgu->wgu_sc, src, pkt, pktlen); 206 rumpuser_component_unschedule(); 207 } 208 209 /* Receive wg UDP/IPv6 packets from a peer */ 210 if (pfd[3].revents & POLLIN) { 211 struct sockaddr *src = &wgu->wgu_rcvbuf.addr.sa; 212 socklen_t len = sizeof(wgu->wgu_rcvbuf.addr.sin6); 213 const void *pkt; 214 size_t pktlen; 215 216 nn = recvfrom(wgu->wgu_sock6, wgu->wgu_rcvbuf.payload, 217 sizeof(wgu->wgu_rcvbuf.payload), 0, src, &len); 218 if (nn == -1) 219 continue; 220 if (len != sizeof(wgu->wgu_rcvbuf.addr.sin6)) 221 continue; 222 pkt = wgu->wgu_rcvbuf.payload; 223 pktlen = (size_t)nn; 224 225 rumpuser_component_schedule(NULL); 226 rumpkern_wg_recv_peer(wgu->wgu_sc, src, pkt, pktlen); 227 rumpuser_component_unschedule(); 228 } 229 } 230 231 assert(wgu->wgu_dying); 232 233 rumpuser_component_kthread_release(); 234 return NULL; 235 } 236 237 int 238 rumpuser_wg_create(const char *tun_name, struct wg_softc *wg, 239 struct wg_user **wgup) 240 { 241 struct wg_user *wgu = NULL; 242 void *cookie; 243 int rv; 244 245 cookie = rumpuser_component_unschedule(); 246 247 wgu = malloc(sizeof(*wgu)); 248 if (wgu == NULL) { 249 rv = errno; 250 goto oerr1; 251 } 252 253 if (strlcpy(wgu->wgu_tun_name, tun_name, sizeof(wgu->wgu_tun_name)) 254 >= sizeof(wgu->wgu_tun_name)) { 255 rv = EINVAL; 256 goto oerr2; 257 } 258 wgu->wgu_sc = wg; 259 260 wgu->wgu_fd = open_tun(tun_name); 261 if (wgu->wgu_fd == -1) { 262 rv = errno; 263 goto oerr2; 264 } 265 266 if (pipe(wgu->wgu_pipe) == -1) { 267 rv = errno; 268 goto oerr3; 269 } 270 271 wgu->wgu_sock4 = socket(AF_INET, SOCK_DGRAM, 0); 272 wgu->wgu_sock6 = socket(AF_INET6, SOCK_DGRAM, 0); 273 if (wgu->wgu_sock4 == -1 || wgu->wgu_sock6 == -1) { 274 rv = errno; 275 goto oerr4; 276 } 277 278 rv = pthread_create(&wgu->wgu_rcvthr, NULL, wg_user_rcvthread, wgu); 279 if (rv != 0) 280 goto oerr5; 281 282 rumpuser_component_schedule(cookie); 283 *wgup = wgu; 284 return 0; 285 286 oerr5: 287 if (wgu->wgu_sock4 != -1) 288 close(wgu->wgu_sock4); 289 if (wgu->wgu_sock6 != -1) 290 close(wgu->wgu_sock6); 291 oerr4: 292 close(wgu->wgu_pipe[0]); 293 close(wgu->wgu_pipe[1]); 294 oerr3: 295 close_tun(wgu); 296 oerr2: 297 free(wgu); 298 oerr1: 299 rumpuser_component_schedule(cookie); 300 return rumpuser_component_errtrans(rv); 301 } 302 303 /* 304 * Send decrypted packets to users via a tun. 305 */ 306 void 307 rumpuser_wg_send_user(struct wg_user *wgu, const struct sockaddr *dst, 308 const void *pkt, size_t pktlen) 309 { 310 void *cookie = rumpuser_component_unschedule(); 311 struct iovec iov[2]; 312 int iovlen; 313 ssize_t idontcare __attribute__((__unused__)); 314 315 memset(iov, 0, sizeof(iov)); 316 iov[0].iov_base = __UNCONST(dst); 317 iov[0].iov_len = dst->sa_len; 318 iov[1].iov_base = __UNCONST(pkt); 319 iov[1].iov_len = pktlen; 320 iovlen = 2; 321 322 /* 323 * no need to check for return value; packets may be dropped 324 * 325 * ... sorry, I spoke too soon. We need to check it because 326 * apparently gcc reinvented const poisoning and it's very 327 * hard to say "thanks, I know I'm not using the result, 328 * but please STFU and let's get on with something useful". 329 * So let's trick gcc into letting us share the compiler 330 * experience. 331 */ 332 idontcare = writev(wgu->wgu_fd, iov, iovlen); 333 334 rumpuser_component_schedule(cookie); 335 } 336 337 /* 338 * Send wg messages to a peer. 339 */ 340 int 341 rumpuser_wg_send_peer(struct wg_user *wgu, const struct sockaddr *dst, 342 const void *pkt, size_t pktlen) 343 { 344 void *cookie = rumpuser_component_unschedule(); 345 int s, error = 0; 346 ssize_t sent; 347 348 switch (dst->sa_family) { 349 case AF_INET: 350 s = wgu->wgu_sock4; 351 break; 352 case AF_INET6: 353 s = wgu->wgu_sock6; 354 break; 355 default: 356 error = EAFNOSUPPORT; 357 goto out; 358 } 359 360 sent = sendto(s, pkt, pktlen, 0, dst, dst->sa_len); 361 if (sent == -1) 362 error = errno; 363 else if ((size_t)sent != pktlen) 364 error = EIO; 365 366 out: rumpuser_component_schedule(cookie); 367 368 return error; 369 } 370 371 int 372 rumpuser_wg_ioctl(struct wg_user *wgu, u_long cmd, void *data, int af) 373 { 374 void *cookie = rumpuser_component_unschedule(); 375 int s, error; 376 377 s = socket(af, SOCK_DGRAM, 0); 378 if (s == -1) 379 return errno; 380 error = ioctl(s, cmd, data); 381 close(s); 382 383 rumpuser_component_schedule(cookie); 384 385 return error == -1 ? errno : 0; 386 } 387 388 int 389 rumpuser_wg_sock_bind(struct wg_user *wgu, const uint16_t port) 390 { 391 union { 392 struct sockaddr sa; 393 struct sockaddr_in sin; 394 struct sockaddr_in6 sin6; 395 } u; 396 397 memset(&u.sin, 0, sizeof(u.sin)); 398 u.sin.sin_family = AF_INET; 399 u.sin.sin_len = sizeof(u.sin); 400 u.sin.sin_addr.s_addr = INADDR_ANY; 401 u.sin.sin_port = htons(port); 402 403 if (bind(wgu->wgu_sock4, &u.sa, sizeof(u.sin)) == -1) 404 return errno; 405 406 memset(&u.sin6, 0, sizeof(u.sin6)); 407 u.sin6.sin6_family = AF_INET6; 408 u.sin6.sin6_len = sizeof(u.sin6); 409 u.sin6.sin6_addr = in6addr_any; 410 u.sin6.sin6_port = htons(port); 411 412 if (bind(wgu->wgu_sock6, &u.sa, sizeof(u.sin6)) == -1) 413 return errno; 414 415 return 0; 416 } 417 418 void 419 rumpuser_wg_destroy(struct wg_user *wgu) 420 { 421 void *cookie = rumpuser_component_unschedule(); 422 423 wgu->wgu_dying = 1; 424 if (write(wgu->wgu_pipe[1], 425 &wgu->wgu_dying, sizeof(wgu->wgu_dying)) == -1) { 426 /* 427 * this is here mostly to avoid a compiler warning 428 * about ignoring the return value of write() 429 */ 430 fprintf(stderr, "%s: failed to signal thread\n", 431 wgu->wgu_tun_name); 432 } 433 pthread_join(wgu->wgu_rcvthr, NULL); 434 close_tun(wgu); 435 close(wgu->wgu_pipe[0]); 436 close(wgu->wgu_pipe[1]); 437 free(wgu); 438 439 rumpuser_component_schedule(cookie); 440 } 441 442 char * 443 rumpuser_wg_get_tunname(struct wg_user *wgu) 444 { 445 446 return wgu->wgu_tun_name; 447 } 448