Home | History | Annotate | Line # | Download | only in libwg
      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