/*- * SPDX-License-Identifier: ISC * * Copyright (C) 2019-2020 Jason A. Donenfeld . All Rights Reserved. * Copyright (C) 2019-2020 Matt Dunwoodie * Copyright (c) 2023-2024 Aaron LI * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include /* getaddrinfo(), getnameinfo() */ #include /* b64_pton(), b64_ntop() */ #include #include /* ptrdiff_t */ #include #include #include #include #include /* timespec_get() */ #include "ifconfig.h" /* * WG_BASE64_KEY_LEN is the size of a base64 encoded WireGuard key. * For every 4 input (base64) bytes, 3 output bytes wil be produced. * The output will be padded with 0 bits, therefore we need more than * the regular 32 bytes of space. */ #define WG_BASE64_KEY_LEN (4 * ((WG_KEY_SIZE + 2) / 3)) static struct wg_data_io wg_data; static struct wg_interface_io *wg_interface; static struct wg_peer_io *wg_peer; static struct wg_aip_io *wg_aip; static void wg_data_init(void) { if (wg_interface != NULL) return; strlcpy(wg_data.wgd_name, IfName, sizeof(wg_data.wgd_name)); wg_data.wgd_size = sizeof(*wg_interface); wg_data.wgd_interface = wg_interface = calloc(1, wg_data.wgd_size); if (wg_interface == NULL) err(1, "calloc"); } static void wg_data_grow(size_t by) { ptrdiff_t peer_offset, aip_offset; wg_data_init(); peer_offset = (char *)wg_peer - (char *)wg_interface; aip_offset = (char *)wg_aip - (char *)wg_interface; wg_data.wgd_size += by; wg_data.wgd_interface = realloc(wg_interface, wg_data.wgd_size); if (wg_data.wgd_interface == NULL) err(1, "realloc"); wg_interface = wg_data.wgd_interface; memset((char *)wg_interface + wg_data.wgd_size - by, 0, by); if (wg_peer != NULL) wg_peer = (void *)((char *)wg_interface + peer_offset); if (wg_aip != NULL) wg_aip = (void *)((char *)wg_interface + aip_offset); } static void wg_callback(int s, void *arg __unused) { if (ioctl(s, SIOCSWG, &wg_data) == -1) err(1, "%s: SIOCSWG", wg_data.wgd_name); } static bool wg_cb_registered; #define WG_REGISTER_CALLBACK() \ if (!wg_cb_registered) { \ callback_register(wg_callback, NULL); \ wg_cb_registered = true; \ } static void wg_setkey(const char *privkey, int arg __unused, int s __unused, const struct afswtch *afp __unused) { wg_data_init(); if (b64_pton(privkey, wg_interface->i_private, WG_KEY_SIZE) != WG_KEY_SIZE) errx(1, "wgkey: invalid private key: %s", privkey); wg_interface->i_flags |= WG_INTERFACE_HAS_PRIVATE; WG_REGISTER_CALLBACK(); } static void wg_setport(const char *port, int arg __unused, int s __unused, const struct afswtch *afp __unused) { const char *errmsg = NULL; wg_data_init(); wg_interface->i_port = (in_port_t)strtonum(port, 0, 65535, &errmsg); if (errmsg != NULL) errx(1, "wgport: invalid port %s: %s", port, errmsg); wg_interface->i_flags |= WG_INTERFACE_HAS_PORT; WG_REGISTER_CALLBACK(); } static void wg_setcookie(const char *cookie, int arg __unused, int s __unused, const struct afswtch *afp __unused) { const char *errmsg = NULL; wg_data_init(); wg_interface->i_cookie = (uint32_t)strtonum(cookie, 0, UINT32_MAX, &errmsg); if (errmsg != NULL) errx(1, "wgcookie: invalid cookie %s: %s", cookie, errmsg); wg_interface->i_flags |= WG_INTERFACE_HAS_COOKIE; WG_REGISTER_CALLBACK(); } static void wg_unsetcookie(const char *x __unused, int arg __unused, int s __unused, const struct afswtch *afp __unused) { wg_data_init(); /* Unset cookie by setting it to value 0. */ wg_interface->i_cookie = 0; wg_interface->i_flags |= WG_INTERFACE_HAS_COOKIE; WG_REGISTER_CALLBACK(); } static void wg_setpeer(const char *peerkey, int arg __unused, int s __unused, const struct afswtch *afp __unused) { wg_data_grow(sizeof(*wg_peer)); if (wg_aip == NULL) wg_peer = &wg_interface->i_peers[0]; /* first peer */ else wg_peer = (struct wg_peer_io *)wg_aip; /* end of last peer */ wg_aip = &wg_peer->p_aips[0]; if (b64_pton(peerkey, wg_peer->p_public, WG_KEY_SIZE) != WG_KEY_SIZE) errx(1, "wgpeer: invalid peer key: %s", peerkey); wg_peer->p_flags |= WG_PEER_HAS_PUBLIC; wg_interface->i_peers_count++; WG_REGISTER_CALLBACK(); } static void wg_unsetpeer(const char *peerkey, int arg, int s, const struct afswtch *afp) { wg_setpeer(peerkey, arg, s, afp); wg_peer->p_flags |= WG_PEER_REMOVE; } static void wg_unsetpeerall(const char *x __unused, int arg __unused, int s __unused, const struct afswtch *afp __unused) { wg_data_init(); wg_interface->i_flags |= WG_INTERFACE_REPLACE_PEERS; WG_REGISTER_CALLBACK(); } /* * Manually parse the CIDR instead of using inet_net_pton() because: * 1. it uses a legacy IPv6 CIDR format (e.g., 1:2:3:4/64) and fails to parse * some now valid IPv6 CIDRs (e.g., 1:2:3:4::/64); * 2. it's not standard and behaves differently across BSDs and Linux. */ static int wg_aip_parse(const char *aip, struct wg_aip_io *waip) { const char *errmsg = NULL; char *p, buf[INET6_ADDRSTRLEN + sizeof("/128")]; int plen; if (snprintf(buf, sizeof(buf), "%s", aip) >= (int)sizeof(buf)) return (-1); plen = 128; if ((p = strchr(buf, '/')) != NULL) { *p = '\0'; plen = (int)strtonum(p + 1, 0, 128, &errmsg); if (errmsg != NULL) return (-1); } if (inet_pton(AF_INET6, buf, &waip->a_ipv6) == 1) { if (plen < 0 || plen > 128) return (-1); waip->a_cidr = plen; waip->a_af = AF_INET6; return (0); } if (inet_pton(AF_INET, buf, &waip->a_ipv4) == 1) { if (plen == 128) plen = 32; if (plen < 0 || plen > 32) return (-1); waip->a_cidr = plen; waip->a_af = AF_INET; return (0); } return (-1); } static void wg_setpeeraip(const char *aip, int arg __unused, int s __unused, const struct afswtch *afp __unused) { if (wg_peer == NULL) errx(1, "wgaip: wgpeer not set"); wg_data_grow(sizeof(*wg_aip)); if (wg_aip_parse(aip, wg_aip) == -1) errx(1, "wgaip: bad address: %s", aip); wg_peer->p_flags |= WG_PEER_REPLACE_AIPS; wg_peer->p_aips_count++; wg_aip++; WG_REGISTER_CALLBACK(); } static void wg_setpeerpsk(const char *psk, int arg __unused, int s __unused, const struct afswtch *afp __unused) { if (wg_peer == NULL) errx(1, "wgpsk: wgpeer not set"); if (b64_pton(psk, wg_peer->p_psk, WG_KEY_SIZE) != WG_KEY_SIZE) errx(1, "wgpsk: invalid key: %s", psk); wg_peer->p_flags |= WG_PEER_HAS_PSK; WG_REGISTER_CALLBACK(); } static void wg_unsetpeerpsk(const char *x __unused, int arg __unused, int s __unused, const struct afswtch *afp __unused) { if (wg_peer == NULL) errx(1, "-wgpsk: wgpeer not set"); /* Unset PSK by setting it to empty. */ memset(wg_peer->p_psk, 0, sizeof(wg_peer->p_psk)); wg_peer->p_flags |= WG_PEER_HAS_PSK; WG_REGISTER_CALLBACK(); } static void wg_setpeerpka(const char *pka, int arg __unused, int s __unused, const struct afswtch *afp __unused) { const char *errmsg = NULL; if (wg_peer == NULL) errx(1, "wgpka: wgpeer not set"); /* 43200 seconds == 12h, reasonable for a uint16_t value */ wg_peer->p_pka = (uint16_t)strtonum(pka, 0, 43200, &errmsg); if (errmsg != NULL) errx(1, "wgpka: invalid pka %s: %s", pka, errmsg); wg_peer->p_flags |= WG_PEER_HAS_PKA; WG_REGISTER_CALLBACK(); } static void wg_unsetpeerpka(const char *x __unused, int arg __unused, int s __unused, const struct afswtch *afp __unused) { if (wg_peer == NULL) errx(1, "-wgpka: wgpeer not set"); wg_peer->p_pka = 0; wg_peer->p_flags |= WG_PEER_HAS_PKA; WG_REGISTER_CALLBACK(); } static void wg_setpeerendpoint(const char *host, const char *service, int s __unused, const struct afswtch *afp __unused) { struct addrinfo *ai; int error; if (wg_peer == NULL) errx(1, "wgendpoint: wgpeer not set"); if ((error = getaddrinfo(host, service, NULL, &ai)) != 0) errx(1, "%s", gai_strerror(error)); memcpy(&wg_peer->p_sa, ai->ai_addr, ai->ai_addrlen); wg_peer->p_flags |= WG_PEER_HAS_ENDPOINT; freeaddrinfo(ai); WG_REGISTER_CALLBACK(); } static void wg_setpeerdesc(const char *desc, int arg __unused, int s __unused, const struct afswtch *afp __unused) { if (wg_peer == NULL) errx(1, "wgdescr: wgpeer not set"); strlcpy(wg_peer->p_description, desc, sizeof(wg_peer->p_description)); wg_peer->p_flags |= WG_PEER_SET_DESCRIPTION; WG_REGISTER_CALLBACK(); } static void wg_unsetpeerdesc(const char *x __unused, int arg __unused, int s __unused, const struct afswtch *afp __unused) { if (wg_peer == NULL) errx(1, "-wgpsk: wgpeer not set"); memset(wg_peer->p_description, 0, sizeof(wg_peer->p_description)); wg_peer->p_flags |= WG_PEER_SET_DESCRIPTION; WG_REGISTER_CALLBACK(); } static void wg_status(int s) { struct timespec now; size_t i, j, last_size; char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; char key[WG_BASE64_KEY_LEN + 1]; memset(&wg_data, 0, sizeof(wg_data)); strlcpy(wg_data.wgd_name, IfName, sizeof(wg_data.wgd_name)); for (;;) { last_size = wg_data.wgd_size; if (ioctl(s, SIOCGWG, &wg_data) == -1) { if (errno == EINVAL || errno == ENOTTY) goto out; err(1, "%s: SIOCGWG", wg_data.wgd_name); } if (last_size >= wg_data.wgd_size) break; wg_interface = calloc(1, wg_data.wgd_size); if (wg_interface == NULL) err(1, "calloc"); free(wg_data.wgd_interface); wg_data.wgd_interface = wg_interface; } wg_interface = wg_data.wgd_interface; if (wg_interface->i_flags & WG_INTERFACE_HAS_PORT) printf("\twgport: %hu\n", wg_interface->i_port); if (wg_interface->i_flags & WG_INTERFACE_HAS_COOKIE) printf("\twgcookie: %u\n", wg_interface->i_cookie); if (wg_interface->i_flags & WG_INTERFACE_HAS_PRIVATE && printkeys) { b64_ntop(wg_interface->i_private, WG_KEY_SIZE, key, sizeof(key)); printf("\twgkey: %s\n", key); } if (wg_interface->i_flags & WG_INTERFACE_HAS_PUBLIC) { b64_ntop(wg_interface->i_public, WG_KEY_SIZE, key, sizeof(key)); printf("\twgpubkey: %s\n", key); } wg_peer = &wg_interface->i_peers[0]; for (i = 0; i < wg_interface->i_peers_count; i++) { b64_ntop(wg_peer->p_public, WG_KEY_SIZE, key, sizeof(key)); printf("\twgpeer: %s\n", key); printf("\t\tid: %" PRIu64 "\n", wg_peer->p_id); if (wg_peer->p_description[0] != '\0') printf("\t\twgdescr: %s\n", wg_peer->p_description); if (wg_peer->p_flags & WG_PEER_HAS_PSK) { if (printkeys) { b64_ntop(wg_peer->p_psk, WG_KEY_SIZE, key, sizeof(key)); printf("\t\twgpsk: %s\n", key); } else { printf("\t\twgpsk: (present)\n"); } } if ((wg_peer->p_flags & WG_PEER_HAS_PKA) && wg_peer->p_pka > 0) printf("\t\twgpka: %u (seconds)\n", wg_peer->p_pka); if (wg_peer->p_flags & WG_PEER_HAS_ENDPOINT) { if (getnameinfo(&wg_peer->p_sa, wg_peer->p_sa.sa_len, hbuf, sizeof(hbuf), sbuf, sizeof(sbuf), NI_NUMERICHOST | NI_NUMERICSERV) == 0) printf("\t\twgendpoint: %s %s\n", hbuf, sbuf); else printf("\t\twgendpoint: (unable to print)\n"); } printf("\t\ttx: %" PRIu64 " (bytes), rx: %" PRIu64 " (bytes)\n", wg_peer->p_txbytes, wg_peer->p_rxbytes); if (wg_peer->p_last_handshake.tv_sec != 0) { timespec_get(&now, TIME_UTC); printf("\t\tlast handshake: %ld seconds ago\n", now.tv_sec - wg_peer->p_last_handshake.tv_sec); } for (j = 0; j < wg_peer->p_aips_count; j++) { wg_aip = &wg_peer->p_aips[j]; inet_ntop(wg_aip->a_af, &wg_aip->a_addr, hbuf, sizeof(hbuf)); printf("\t\twgaip: %s/%d\n", hbuf, wg_aip->a_cidr); } wg_aip = &wg_peer->p_aips[wg_peer->p_aips_count]; wg_peer = (struct wg_peer_io *)wg_aip; } out: free(wg_data.wgd_interface); } static struct cmd wg_cmds[] = { DEF_CMD_ARG("wgkey", wg_setkey), DEF_CMD_ARG("wgport", wg_setport), DEF_CMD_ARG("wgcookie", wg_setcookie), DEF_CMD("-wgcookie", 0, wg_unsetcookie), DEF_CMD_ARG("wgpeer", wg_setpeer), DEF_CMD_ARG("-wgpeer", wg_unsetpeer), DEF_CMD("-wgpeerall", 0, wg_unsetpeerall), DEF_CMD_ARG("wgaip", wg_setpeeraip), DEF_CMD_ARG("wgpsk", wg_setpeerpsk), DEF_CMD("-wgpsk", 0, wg_unsetpeerpsk), DEF_CMD_ARG("wgpka", wg_setpeerpka), DEF_CMD("-wgpka", 0, wg_unsetpeerpka), DEF_CMD_ARG2("wgendpoint", wg_setpeerendpoint), DEF_CMD_ARG("wgdescr", wg_setpeerdesc), DEF_CMD_ARG("wgdescription", wg_setpeerdesc), DEF_CMD("-wgdescr", 0, wg_unsetpeerdesc), DEF_CMD("-wgdescription", 0, wg_unsetpeerdesc), }; static struct afswtch af_wg = { .af_name = "af_wg", /* dummy */ .af_af = AF_UNSPEC, .af_other_status = wg_status, }; __constructor(143) static void wg_ctor(void) { size_t i; for (i = 0; i < nitems(wg_cmds); i++) cmd_register(&wg_cmds[i]); af_register(&af_wg); }