diff --git a/sbin/ifconfig/ifwg.c b/sbin/ifconfig/ifwg.c index 105ee7ac31d1..86bacc59f50d 100644 --- a/sbin/ifconfig/ifwg.c +++ b/sbin/ifconfig/ifwg.c @@ -1,616 +1,642 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2020 Rubicon Communications, LLC (Netgate) * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #ifndef RESCUE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* NB: for offsetof */ #include #include #include #include "ifconfig.h" typedef enum { WGC_GET = 0x5, WGC_SET = 0x6, } wg_cmd_t; static nvlist_t *nvl_params; static bool do_peer; static int allowed_ips_count; static int allowed_ips_max; struct allowedip { struct sockaddr_storage a_addr; struct sockaddr_storage a_mask; }; struct allowedip *allowed_ips; #define ALLOWEDIPS_START 16 #define WG_KEY_LEN 32 #define WG_KEY_LEN_BASE64 ((((WG_KEY_LEN) + 2) / 3) * 4 + 1) #define WG_KEY_LEN_HEX (WG_KEY_LEN * 2 + 1) #define WG_MAX_STRLEN 64 static bool key_from_base64(uint8_t key[static WG_KEY_LEN], const char *base64) { if (strlen(base64) != WG_KEY_LEN_BASE64 - 1) { warnx("bad key len - need %d got %zu\n", WG_KEY_LEN_BASE64 - 1, strlen(base64)); return false; } if (base64[WG_KEY_LEN_BASE64 - 2] != '=') { warnx("bad key terminator, expected '=' got '%c'", base64[WG_KEY_LEN_BASE64 - 2]); return false; } return (b64_pton(base64, key, WG_KEY_LEN)); } static void parse_endpoint(const char *endpoint_) { int err; char *base, *endpoint, *port, *colon, *tmp; struct addrinfo hints, *res; endpoint = base = strdup(endpoint_); colon = rindex(endpoint, ':'); if (colon == NULL) errx(1, "bad endpoint format %s - no port delimiter found", endpoint); *colon = '\0'; port = colon + 1; /* [::]:<> */ if (endpoint[0] == '[') { endpoint++; tmp = index(endpoint, ']'); if (tmp == NULL) errx(1, "bad endpoint format %s - '[' found with no matching ']'", endpoint); *tmp = '\0'; } bzero(&hints, sizeof(hints)); hints.ai_family = AF_UNSPEC; err = getaddrinfo(endpoint, port, &hints, &res); if (err) errx(1, "%s", gai_strerror(err)); nvlist_add_binary(nvl_params, "endpoint", res->ai_addr, res->ai_addrlen); freeaddrinfo(res); free(base); } static void in_len2mask(struct in_addr *mask, u_int len) { u_int i; u_char *p; p = (u_char *)mask; memset(mask, 0, sizeof(*mask)); for (i = 0; i < len / NBBY; i++) p[i] = 0xff; if (len % NBBY) p[i] = (0xff00 >> (len % NBBY)) & 0xff; } static u_int in_mask2len(struct in_addr *mask) { u_int x, y; u_char *p; p = (u_char *)mask; for (x = 0; x < sizeof(*mask); x++) { if (p[x] != 0xff) break; } y = 0; if (x < sizeof(*mask)) { for (y = 0; y < NBBY; y++) { if ((p[x] & (0x80 >> y)) == 0) break; } } return x * NBBY + y; } static void in6_prefixlen2mask(struct in6_addr *maskp, int len) { static const u_char maskarray[NBBY] = {0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff}; int bytelen, bitlen, i; /* sanity check */ if (len < 0 || len > 128) { errx(1, "in6_prefixlen2mask: invalid prefix length(%d)\n", len); return; } memset(maskp, 0, sizeof(*maskp)); bytelen = len / NBBY; bitlen = len % NBBY; for (i = 0; i < bytelen; i++) maskp->s6_addr[i] = 0xff; if (bitlen) maskp->s6_addr[bytelen] = maskarray[bitlen - 1]; } static int in6_mask2len(struct in6_addr *mask, u_char *lim0) { int x = 0, y; u_char *lim = lim0, *p; /* ignore the scope_id part */ if (lim0 == NULL || lim0 - (u_char *)mask > sizeof(*mask)) lim = (u_char *)mask + sizeof(*mask); for (p = (u_char *)mask; p < lim; x++, p++) { if (*p != 0xff) break; } y = 0; if (p < lim) { for (y = 0; y < NBBY; y++) { if ((*p & (0x80 >> y)) == 0) break; } } /* * when the limit pointer is given, do a stricter check on the * remaining bits. */ if (p < lim) { if (y != 0 && (*p & (0x00ff >> y)) != 0) return -1; for (p = p + 1; p < lim; p++) if (*p != 0) return -1; } return x * NBBY + y; } static bool parse_ip(struct allowedip *aip, const char *value) { struct addrinfo hints, *res; int err; bzero(&aip->a_addr, sizeof(aip->a_addr)); bzero(&hints, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_flags = AI_NUMERICHOST; err = getaddrinfo(value, NULL, &hints, &res); if (err) errx(1, "%s", gai_strerror(err)); memcpy(&aip->a_addr, res->ai_addr, res->ai_addrlen); freeaddrinfo(res); return (true); } static void sa_ntop(const struct sockaddr *sa, char *buf, int *port) { const struct sockaddr_in *sin; const struct sockaddr_in6 *sin6; int err; err = getnameinfo(sa, sa->sa_len, buf, INET6_ADDRSTRLEN, NULL, 0, NI_NUMERICHOST); if (sa->sa_family == AF_INET) { sin = (const struct sockaddr_in *)sa; if (port) *port = sin->sin_port; } else if (sa->sa_family == AF_INET6) { sin6 = (const struct sockaddr_in6 *)sa; if (port) *port = sin6->sin6_port; } if (err) errx(1, "%s", gai_strerror(err)); } static void dump_peer(const nvlist_t *nvl_peer) { const void *key; const struct allowedip *aips; const struct sockaddr *endpoint; char outbuf[WG_MAX_STRLEN]; char addr_buf[INET6_ADDRSTRLEN]; size_t size; int count, port; + uint16_t persistent_keepalive; printf("[Peer]\n"); if (nvlist_exists_binary(nvl_peer, "public-key")) { key = nvlist_get_binary(nvl_peer, "public-key", &size); b64_ntop((const uint8_t *)key, size, outbuf, WG_MAX_STRLEN); printf("PublicKey = %s\n", outbuf); } if (nvlist_exists_binary(nvl_peer, "endpoint")) { endpoint = nvlist_get_binary(nvl_peer, "endpoint", &size); sa_ntop(endpoint, addr_buf, &port); printf("Endpoint = %s:%d\n", addr_buf, ntohs(port)); } - + if (nvlist_exists_number(nvl_peer, "persistent-keepalive-interval")) { + persistent_keepalive = nvlist_get_number(nvl_peer, + "persistent-keepalive-interval"); + printf("PersistentKeepalive = %d\n", persistent_keepalive); + } if (!nvlist_exists_binary(nvl_peer, "allowed-ips")) return; aips = nvlist_get_binary(nvl_peer, "allowed-ips", &size); if (size == 0 || size % sizeof(struct allowedip) != 0) { errx(1, "size %zu not integer multiple of allowedip", size); } printf("AllowedIPs = "); count = size / sizeof(struct allowedip); for (int i = 0; i < count; i++) { int mask; sa_family_t family; void *bitmask; struct sockaddr *sa; sa = __DECONST(void *, &aips[i].a_addr); bitmask = __DECONST(void *, ((const struct sockaddr *)&(&aips[i])->a_mask)->sa_data); family = aips[i].a_addr.ss_family; getnameinfo(sa, sa->sa_len, addr_buf, INET6_ADDRSTRLEN, NULL, 0, NI_NUMERICHOST); if (family == AF_INET) mask = in_mask2len(bitmask); else if (family == AF_INET6) mask = in6_mask2len(bitmask, NULL); else errx(1, "bad family in peer %d\n", family); printf("%s/%d", addr_buf, mask); if (i < count -1) printf(", "); } printf("\n"); } static int get_nvl_out_size(int sock, u_long op, size_t *size) { struct ifdrv ifd; int err; memset(&ifd, 0, sizeof(ifd)); strlcpy(ifd.ifd_name, name, sizeof(ifd.ifd_name)); ifd.ifd_cmd = op; ifd.ifd_len = 0; ifd.ifd_data = NULL; err = ioctl(sock, SIOCGDRVSPEC, &ifd); if (err) return (err); *size = ifd.ifd_len; return (0); } static int do_cmd(int sock, u_long op, void *arg, size_t argsize, int set) { struct ifdrv ifd; memset(&ifd, 0, sizeof(ifd)); strlcpy(ifd.ifd_name, name, sizeof(ifd.ifd_name)); ifd.ifd_cmd = op; ifd.ifd_len = argsize; ifd.ifd_data = arg; return (ioctl(sock, set ? SIOCSDRVSPEC : SIOCGDRVSPEC, &ifd)); } static DECL_CMD_FUNC(peerlist, val, d) { size_t size, peercount; void *packed; const nvlist_t *nvl, *nvl_peer; const nvlist_t *const *nvl_peerlist; if (get_nvl_out_size(s, WGC_GET, &size)) errx(1, "can't get peer list size"); if ((packed = malloc(size)) == NULL) errx(1, "malloc failed for peer list"); if (do_cmd(s, WGC_GET, packed, size, 0)) errx(1, "failed to obtain peer list"); nvl = nvlist_unpack(packed, size, 0); if (!nvlist_exists_nvlist_array(nvl, "peer-list")) return; nvl_peerlist = nvlist_get_nvlist_array(nvl, "peer-list", &peercount); for (int i = 0; i < peercount; i++, nvl_peerlist++) { nvl_peer = *nvl_peerlist; dump_peer(nvl_peer); } } static void peerfinish(int s, void *arg) { nvlist_t *nvl, **nvl_array; void *packed; size_t size; if ((nvl = nvlist_create(0)) == NULL) errx(1, "failed to allocate nvlist"); if ((nvl_array = calloc(sizeof(void *), 1)) == NULL) errx(1, "failed to allocate nvl_array"); if (!nvlist_exists_binary(nvl_params, "public-key")) errx(1, "must specify a public-key for adding peer"); if (allowed_ips_count == 0) errx(1, "must specify at least one range of allowed-ips to add a peer"); nvl_array[0] = nvl_params; nvlist_add_nvlist_array(nvl, "peer-list", (const nvlist_t * const *)nvl_array, 1); packed = nvlist_pack(nvl, &size); if (do_cmd(s, WGC_SET, packed, size, true)) errx(1, "failed to install peer"); } static DECL_CMD_FUNC(peerstart, val, d) { do_peer = true; callback_register(peerfinish, NULL); allowed_ips = malloc(ALLOWEDIPS_START * sizeof(struct allowedip)); allowed_ips_max = ALLOWEDIPS_START; if (allowed_ips == NULL) errx(1, "failed to allocate array for allowedips"); } static DECL_CMD_FUNC(setwglistenport, val, d) { struct addrinfo hints, *res; const struct sockaddr_in *sin; const struct sockaddr_in6 *sin6; u_long ul; int err; bzero(&hints, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_flags = AI_NUMERICHOST; err = getaddrinfo(NULL, val, &hints, &res); if (err) errx(1, "%s", gai_strerror(err)); if (res->ai_family == AF_INET) { sin = (struct sockaddr_in *)res->ai_addr; ul = sin->sin_port; } else if (res->ai_family == AF_INET6) { sin6 = (struct sockaddr_in6 *)res->ai_addr; ul = sin6->sin6_port; } else { errx(1, "unknown family"); } ul = ntohs((u_short)ul); nvlist_add_number(nvl_params, "listen-port", ul); } static DECL_CMD_FUNC(setwgprivkey, val, d) { uint8_t key[WG_KEY_LEN]; if (!key_from_base64(key, val)) errx(1, "invalid key %s", val); nvlist_add_binary(nvl_params, "private-key", key, WG_KEY_LEN); } static DECL_CMD_FUNC(setwgpubkey, val, d) { uint8_t key[WG_KEY_LEN]; if (!do_peer) errx(1, "setting public key only valid when adding peer"); if (!key_from_base64(key, val)) errx(1, "invalid key %s", val); nvlist_add_binary(nvl_params, "public-key", key, WG_KEY_LEN); } +static +DECL_CMD_FUNC(setwgpersistentkeepalive, val, d) +{ + unsigned long persistent_keepalive; + char *endp; + + if (!do_peer) + errx(1, "setting persistent keepalive only valid when adding peer"); + + errno = 0; + persistent_keepalive = strtoul(val, &endp, 0); + if (errno != 0 || *endp != '\0') + errx(1, "persistent-keepalive must be numeric (seconds)"); + if (persistent_keepalive > USHRT_MAX) + errx(1, "persistent-keepalive '%lu' too large", + persistent_keepalive); + nvlist_add_number(nvl_params, "persistent-keepalive-interval", + persistent_keepalive); +} + static DECL_CMD_FUNC(setallowedips, val, d) { char *base, *allowedip, *mask; u_long ul; char *endp; struct allowedip *aip; if (!do_peer) errx(1, "setting allowed ip only valid when adding peer"); if (allowed_ips_count == allowed_ips_max) { /* XXX grow array */ } aip = &allowed_ips[allowed_ips_count]; base = allowedip = strdup(val); mask = index(allowedip, '/'); if (mask == NULL) errx(1, "mask separator not found in allowedip %s", val); *mask = '\0'; mask++; parse_ip(aip, allowedip); ul = strtoul(mask, &endp, 0); if (*endp != '\0') errx(1, "invalid value for allowedip mask"); bzero(&aip->a_mask, sizeof(aip->a_mask)); if (aip->a_addr.ss_family == AF_INET) in_len2mask((struct in_addr *)&((struct sockaddr *)&aip->a_mask)->sa_data, ul); else if (aip->a_addr.ss_family == AF_INET6) in6_prefixlen2mask((struct in6_addr *)&((struct sockaddr *)&aip->a_mask)->sa_data, ul); else errx(1, "invalid address family %d\n", aip->a_addr.ss_family); allowed_ips_count++; if (allowed_ips_count > 1) nvlist_free_binary(nvl_params, "allowed-ips"); nvlist_add_binary(nvl_params, "allowed-ips", allowed_ips, allowed_ips_count*sizeof(*aip)); dump_peer(nvl_params); free(base); } static DECL_CMD_FUNC(setendpoint, val, d) { if (!do_peer) errx(1, "setting endpoint only valid when adding peer"); parse_endpoint(val); } static void wireguard_status(int s) { size_t size; void *packed; nvlist_t *nvl; char buf[WG_KEY_LEN_BASE64]; const void *key; uint16_t listen_port; if (get_nvl_out_size(s, WGC_GET, &size)) return; if ((packed = malloc(size)) == NULL) return; if (do_cmd(s, WGC_GET, packed, size, 0)) return; nvl = nvlist_unpack(packed, size, 0); if (nvlist_exists_number(nvl, "listen-port")) { listen_port = nvlist_get_number(nvl, "listen-port"); printf("\tlisten-port: %d\n", listen_port); } if (nvlist_exists_binary(nvl, "private-key")) { key = nvlist_get_binary(nvl, "private-key", &size); b64_ntop((const uint8_t *)key, size, buf, WG_MAX_STRLEN); printf("\tprivate-key: %s\n", buf); } if (nvlist_exists_binary(nvl, "public-key")) { key = nvlist_get_binary(nvl, "public-key", &size); b64_ntop((const uint8_t *)key, size, buf, WG_MAX_STRLEN); printf("\tpublic-key: %s\n", buf); } } static struct cmd wireguard_cmds[] = { DEF_CLONE_CMD_ARG("listen-port", setwglistenport), DEF_CLONE_CMD_ARG("private-key", setwgprivkey), DEF_CMD("peer-list", 0, peerlist), DEF_CMD("peer", 0, peerstart), DEF_CMD_ARG("public-key", setwgpubkey), + DEF_CMD_ARG("persistent-keepalive", setwgpersistentkeepalive), DEF_CMD_ARG("allowed-ips", setallowedips), DEF_CMD_ARG("endpoint", setendpoint), }; static struct afswtch af_wireguard = { .af_name = "af_wireguard", .af_af = AF_UNSPEC, .af_other_status = wireguard_status, }; static void wg_create(int s, struct ifreq *ifr) { struct iovec iov; void *packed; size_t size; setproctitle("ifconfig %s create ...\n", name); if (!nvlist_exists_number(nvl_params, "listen-port")) goto legacy; if (!nvlist_exists_binary(nvl_params, "private-key")) goto legacy; packed = nvlist_pack(nvl_params, &size); if (packed == NULL) errx(1, "failed to setup create request"); iov.iov_len = size; iov.iov_base = packed; ifr->ifr_data = (caddr_t)&iov; if (ioctl(s, SIOCIFCREATE2, ifr) < 0) err(1, "SIOCIFCREATE2"); return; legacy: ifr->ifr_data == NULL; if (ioctl(s, SIOCIFCREATE, ifr) < 0) err(1, "SIOCIFCREATE"); } static __constructor void wireguard_ctor(void) { int i; nvl_params = nvlist_create(0); for (i = 0; i < nitems(wireguard_cmds); i++) cmd_register(&wireguard_cmds[i]); af_register(&af_wireguard); clone_setdefcallback_prefix("wg", wg_create); } #endif diff --git a/sys/dev/if_wg/module/module.c b/sys/dev/if_wg/module/module.c index 6ae3bf9db022..ad2f17c1e803 100644 --- a/sys/dev/if_wg/module/module.c +++ b/sys/dev/if_wg/module/module.c @@ -1,925 +1,933 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2019-2020 Rubicon Communications, LLC (Netgate) * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include "opt_inet.h" #include "opt_inet6.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ifdi_if.h" #include #include #include #include #include MALLOC_DEFINE(M_WG, "WG", "wireguard"); #define WG_CAPS IFCAP_LINKSTATE #define ph_family PH_loc.eight[5] TASKQGROUP_DECLARE(if_io_tqg); struct wg_peer_export { struct sockaddr_storage endpoint; uint8_t public_key[WG_KEY_SIZE]; size_t endpoint_sz; struct wg_allowedip *aip; int aip_count; + uint16_t persistent_keepalive; }; static int clone_count; uma_zone_t ratelimit_zone; void wg_encrypt_dispatch(struct wg_softc *sc) { for (int i = 0; i < mp_ncpus; i++) { if (sc->sc_encrypt[i].gt_task.ta_flags & TASK_ENQUEUED) continue; GROUPTASK_ENQUEUE(&sc->sc_encrypt[i]); } } void wg_decrypt_dispatch(struct wg_softc *sc) { for (int i = 0; i < mp_ncpus; i++) { if (sc->sc_decrypt[i].gt_task.ta_flags & TASK_ENQUEUED) continue; GROUPTASK_ENQUEUE(&sc->sc_decrypt[i]); } } static void crypto_taskq_setup(struct wg_softc *sc) { device_t dev = iflib_get_dev(sc->wg_ctx); sc->sc_encrypt = malloc(sizeof(struct grouptask)*mp_ncpus, M_WG, M_WAITOK); sc->sc_decrypt = malloc(sizeof(struct grouptask)*mp_ncpus, M_WG, M_WAITOK); for (int i = 0; i < mp_ncpus; i++) { GROUPTASK_INIT(&sc->sc_encrypt[i], 0, (gtask_fn_t *)wg_softc_encrypt, sc); taskqgroup_attach_cpu(qgroup_if_io_tqg, &sc->sc_encrypt[i], sc, i, dev, NULL, "wg encrypt"); GROUPTASK_INIT(&sc->sc_decrypt[i], 0, (gtask_fn_t *)wg_softc_decrypt, sc); taskqgroup_attach_cpu(qgroup_if_io_tqg, &sc->sc_decrypt[i], sc, i, dev, NULL, "wg decrypt"); } } static void crypto_taskq_destroy(struct wg_softc *sc) { for (int i = 0; i < mp_ncpus; i++) { taskqgroup_detach(qgroup_if_io_tqg, &sc->sc_encrypt[i]); taskqgroup_detach(qgroup_if_io_tqg, &sc->sc_decrypt[i]); } free(sc->sc_encrypt, M_WG); free(sc->sc_decrypt, M_WG); } static int wg_cloneattach(if_ctx_t ctx, struct if_clone *ifc, const char *name, caddr_t params) { struct wg_softc *sc = iflib_get_softc(ctx); if_softc_ctx_t scctx; device_t dev; struct iovec iov; nvlist_t *nvl; void *packed; struct noise_local *local; uint8_t public[WG_KEY_SIZE]; struct noise_upcall noise_upcall; int err; uint16_t listen_port; const void *key; size_t size; err = 0; dev = iflib_get_dev(ctx); if (params == NULL) { key = NULL; listen_port = 0; nvl = NULL; packed = NULL; goto unpacked; } if (copyin(params, &iov, sizeof(iov))) return (EFAULT); /* check that this is reasonable */ size = iov.iov_len; packed = malloc(size, M_TEMP, M_WAITOK); if (copyin(iov.iov_base, packed, size)) { err = EFAULT; goto out; } nvl = nvlist_unpack(packed, size, 0); if (nvl == NULL) { device_printf(dev, "%s nvlist_unpack failed\n", __func__); err = EBADMSG; goto out; } if (!nvlist_exists_number(nvl, "listen-port")) { device_printf(dev, "%s listen-port not set\n", __func__); err = EBADMSG; goto nvl_out; } listen_port = nvlist_get_number(nvl, "listen-port"); if (!nvlist_exists_binary(nvl, "private-key")) { device_printf(dev, "%s private-key not set\n", __func__); err = EBADMSG; goto nvl_out; } key = nvlist_get_binary(nvl, "private-key", &size); if (size != CURVE25519_KEY_SIZE) { device_printf(dev, "%s bad length for private-key %zu\n", __func__, size); err = EBADMSG; goto nvl_out; } unpacked: local = &sc->sc_local; noise_upcall.u_arg = sc; noise_upcall.u_remote_get = (struct noise_remote *(*)(void *, uint8_t *))wg_remote_get; noise_upcall.u_index_set = (uint32_t (*)(void *, struct noise_remote *))wg_index_set; noise_upcall.u_index_drop = (void (*)(void *, uint32_t))wg_index_drop; noise_local_init(local, &noise_upcall); cookie_checker_init(&sc->sc_cookie, ratelimit_zone); sc->sc_socket.so_port = listen_port; if (key != NULL) { noise_local_set_private(local, __DECONST(uint8_t *, key)); noise_local_keys(local, public, NULL); cookie_checker_update(&sc->sc_cookie, public); } atomic_add_int(&clone_count, 1); scctx = sc->shared = iflib_get_softc_ctx(ctx); scctx->isc_capenable = WG_CAPS; scctx->isc_tx_csum_flags = CSUM_TCP | CSUM_UDP | CSUM_TSO | CSUM_IP6_TCP \ | CSUM_IP6_UDP | CSUM_IP6_TCP; sc->wg_ctx = ctx; sc->sc_ifp = iflib_get_ifp(ctx); mbufq_init(&sc->sc_handshake_queue, MAX_QUEUED_INCOMING_HANDSHAKES); mtx_init(&sc->sc_mtx, NULL, "wg softc lock", MTX_DEF); rw_init(&sc->sc_index_lock, "wg index lock"); sc->sc_encap_ring = buf_ring_alloc(MAX_QUEUED_PACKETS, M_WG, M_WAITOK, &sc->sc_mtx); sc->sc_decap_ring = buf_ring_alloc(MAX_QUEUED_PACKETS, M_WG, M_WAITOK, &sc->sc_mtx); GROUPTASK_INIT(&sc->sc_handshake, 0, (gtask_fn_t *)wg_softc_handshake_receive, sc); taskqgroup_attach(qgroup_if_io_tqg, &sc->sc_handshake, sc, dev, NULL, "wg tx initiation"); crypto_taskq_setup(sc); nvl_out: if (nvl != NULL) nvlist_destroy(nvl); out: free(packed, M_TEMP); return (err); } static int wg_transmit(struct ifnet *ifp, struct mbuf *m) { struct wg_softc *sc; sa_family_t family; struct epoch_tracker et; struct wg_peer *peer; struct wg_tag *t; uint32_t af; int rc; /* * Work around lifetime issue in the ipv6 mld code. */ if (__predict_false(ifp->if_flags & IFF_DYING)) return (ENXIO); rc = 0; sc = iflib_get_softc(ifp->if_softc); if ((t = wg_tag_get(m)) == NULL) { rc = ENOBUFS; goto early_out; } af = m->m_pkthdr.ph_family; BPF_MTAP2(ifp, &af, sizeof(af), m); NET_EPOCH_ENTER(et); peer = wg_route_lookup(&sc->sc_routes, m, OUT); if (__predict_false(peer == NULL)) { rc = ENOKEY; /* XXX log */ goto err; } family = atomic_load_acq(peer->p_endpoint.e_remote.r_sa.sa_family); if (__predict_false(family != AF_INET && family != AF_INET6)) { rc = EHOSTUNREACH; /* XXX log */ goto err; } t->t_peer = peer; t->t_mbuf = NULL; t->t_done = 0; t->t_mtu = ifp->if_mtu; rc = wg_queue_out(peer, m); if (rc == 0) wg_encrypt_dispatch(peer->p_sc); NET_EPOCH_EXIT(et); return (rc); err: NET_EPOCH_EXIT(et); early_out: if_inc_counter(sc->sc_ifp, IFCOUNTER_OERRORS, 1); /* XXX send ICMP unreachable */ m_free(m); return (rc); } static int wg_output(struct ifnet *ifp, struct mbuf *m, const struct sockaddr *sa, struct route *rt) { m->m_pkthdr.ph_family = sa->sa_family; return (wg_transmit(ifp, m)); } static int wg_attach_post(if_ctx_t ctx) { struct ifnet *ifp; struct wg_softc *sc; sc = iflib_get_softc(ctx); ifp = iflib_get_ifp(ctx); if_setmtu(ifp, ETHERMTU - 80); if_setflagbits(ifp, IFF_NOARP, IFF_POINTOPOINT); ifp->if_transmit = wg_transmit; ifp->if_output = wg_output; wg_hashtable_init(&sc->sc_hashtable); sc->sc_index = hashinit(HASHTABLE_INDEX_SIZE, M_DEVBUF, &sc->sc_index_mask); wg_route_init(&sc->sc_routes); return (0); } static int wg_mtu_set(if_ctx_t ctx, uint32_t mtu) { return (0); } static int wg_set_promisc(if_ctx_t ctx, int flags) { return (0); } static int wg_detach(if_ctx_t ctx) { struct wg_softc *sc; sc = iflib_get_softc(ctx); if_link_state_change(sc->sc_ifp, LINK_STATE_DOWN); NET_EPOCH_WAIT(); wg_socket_reinit(sc, NULL, NULL); taskqgroup_drain_all(qgroup_if_io_tqg); pause("link_down", hz/4); wg_peer_remove_all(sc); pause("link_down", hz); mtx_destroy(&sc->sc_mtx); rw_destroy(&sc->sc_index_lock); taskqgroup_detach(qgroup_if_io_tqg, &sc->sc_handshake); crypto_taskq_destroy(sc); buf_ring_free(sc->sc_encap_ring, M_WG); buf_ring_free(sc->sc_decap_ring, M_WG); wg_route_destroy(&sc->sc_routes); wg_hashtable_destroy(&sc->sc_hashtable); atomic_add_int(&clone_count, -1); return (0); } static void wg_init(if_ctx_t ctx) { struct ifnet *ifp; struct wg_softc *sc; int rc; if (iflib_in_detach(ctx)) return; sc = iflib_get_softc(ctx); ifp = iflib_get_ifp(ctx); if (sc->sc_socket.so_so4 != NULL) printf("XXX wg_init, socket non-NULL %p\n", sc->sc_socket.so_so4); wg_socket_reinit(sc, NULL, NULL); rc = wg_socket_init(sc); if (rc) return; if_link_state_change(ifp, LINK_STATE_UP); } static void wg_stop(if_ctx_t ctx) { struct wg_softc *sc; struct ifnet *ifp; sc = iflib_get_softc(ctx); ifp = iflib_get_ifp(ctx); if_link_state_change(ifp, LINK_STATE_DOWN); wg_socket_reinit(sc, NULL, NULL); } static int wg_peer_to_export(struct wg_peer *peer, struct wg_peer_export *exp) { struct wg_endpoint *ep; struct wg_route *rt; int i; /* Non-sleepable context. */ NET_EPOCH_ASSERT(); bzero(&exp->endpoint, sizeof(exp->endpoint)); ep = &peer->p_endpoint; if (ep->e_remote.r_sa.sa_family != 0) { exp->endpoint_sz = (ep->e_remote.r_sa.sa_family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6); memcpy(&exp->endpoint, &ep->e_remote, exp->endpoint_sz); } memcpy(exp->public_key, peer->p_remote.r_public, sizeof(exp->public_key)); + exp->persistent_keepalive = + peer->p_timers.t_persistent_keepalive_interval; + exp->aip_count = 0; CK_LIST_FOREACH(rt, &peer->p_routes, r_entry) { exp->aip_count++; } /* Early success; no allowed-ips to copy out. */ if (exp->aip_count == 0) return (0); exp->aip = malloc(exp->aip_count * sizeof(*exp->aip), M_TEMP, M_NOWAIT); if (exp->aip == NULL) return (ENOMEM); i = 0; CK_LIST_FOREACH(rt, &peer->p_routes, r_entry) { memcpy(&exp->aip[i++], &rt->r_cidr, sizeof(*exp->aip)); if (i == exp->aip_count) break; } /* Again, AllowedIPs might have shrank; update it. */ exp->aip_count = i; return (0); } static nvlist_t * wg_peer_export_to_nvl(struct wg_peer_export *exp) { nvlist_t *nvl; if ((nvl = nvlist_create(0)) == NULL) return (NULL); nvlist_add_binary(nvl, "public-key", exp->public_key, WG_KEY_SIZE); if (exp->endpoint_sz != 0) nvlist_add_binary(nvl, "endpoint", &exp->endpoint, exp->endpoint_sz); nvlist_add_binary(nvl, "allowed-ips", exp->aip, exp->aip_count * sizeof(*exp->aip)); + if (exp->persistent_keepalive != 0) + nvlist_add_number(nvl, "persistent-keepalive-interval", + exp->persistent_keepalive); + return (nvl); } static int wg_marshal_peers(struct wg_softc *sc, nvlist_t **nvlp, nvlist_t ***nvl_arrayp, int *peer_countp) { struct wg_peer *peer; int err, i, peer_count; nvlist_t *nvl, **nvl_array; struct epoch_tracker et; struct wg_peer_export *wpe; nvl = NULL; nvl_array = NULL; if (nvl_arrayp) *nvl_arrayp = NULL; if (nvlp) *nvlp = NULL; if (peer_countp) *peer_countp = 0; peer_count = sc->sc_hashtable.h_num_peers; if (peer_count == 0) { printf("no peers found\n"); return (ENOENT); } if (nvlp && (nvl = nvlist_create(0)) == NULL) return (ENOMEM); err = i = 0; nvl_array = malloc(peer_count*sizeof(void*), M_TEMP, M_WAITOK); wpe = malloc(peer_count*sizeof(*wpe), M_TEMP, M_WAITOK | M_ZERO); NET_EPOCH_ENTER(et); CK_LIST_FOREACH(peer, &sc->sc_hashtable.h_peers_list, p_entry) { if ((err = wg_peer_to_export(peer, &wpe[i])) != 0) { printf("wg_peer_to_export failed on %d peer, error %d\n", i, err); break; } i++; if (i == peer_count) break; } NET_EPOCH_EXIT(et); if (err != 0) goto out; /* Update the peer count, in case we found fewer entries. */ *peer_countp = peer_count = i; if (peer_count == 0) { printf("no peers found in list\n"); err = ENOENT; goto out; } for (i = 0; i < peer_count; i++) { nvl_array[i] = wg_peer_export_to_nvl(&wpe[i]); if (nvl_array[i] == NULL) { printf("wg_peer_export_to_nvl failed on %d peer\n", i); break; } } if (nvl) { nvlist_add_nvlist_array(nvl, "peer-list", (const nvlist_t * const *)nvl_array, peer_count); if ((err = nvlist_error(nvl))) { printf("nvlist_add_nvlist_array(%p, \"peer-list\", %p, %d) => %d\n", nvl, nvl_array, peer_count, err); goto out; } *nvlp = nvl; } *nvl_arrayp = nvl_array; err = 0; out: if (err != 0) { for (i = 0; i < peer_count; i++) { nvlist_destroy(nvl_array[i]); } free(nvl_array, M_TEMP); if (nvl != NULL) nvlist_destroy(nvl); } for (i = 0; i < peer_count; i++) free(wpe[i].aip, M_TEMP); free(wpe, M_TEMP); return (err); } static int wgc_get(struct wg_softc *sc, struct ifdrv *ifd) { nvlist_t *nvl, **nvl_array; void *packed; size_t size; int peer_count, err; nvl = nvlist_create(0); if (nvl == NULL) return (ENOMEM); err = 0; packed = NULL; if (sc->sc_socket.so_port != 0) nvlist_add_number(nvl, "listen-port", sc->sc_socket.so_port); if (sc->sc_local.l_has_identity) { nvlist_add_binary(nvl, "public-key", sc->sc_local.l_public, WG_KEY_SIZE); if (curthread->td_ucred->cr_uid == 0) nvlist_add_binary(nvl, "private-key", sc->sc_local.l_private, WG_KEY_SIZE); } if (sc->sc_hashtable.h_num_peers > 0) { err = wg_marshal_peers(sc, NULL, &nvl_array, &peer_count); if (err) goto out; nvlist_add_nvlist_array(nvl, "peer-list", (const nvlist_t * const *)nvl_array, peer_count); } packed = nvlist_pack(nvl, &size); if (packed == NULL) return (ENOMEM); if (ifd->ifd_len == 0) { ifd->ifd_len = size; goto out; } if (ifd->ifd_len < size) { err = ENOSPC; goto out; } if (ifd->ifd_data == NULL) { err = EFAULT; goto out; } err = copyout(packed, ifd->ifd_data, size); ifd->ifd_len = size; out: nvlist_destroy(nvl); free(packed, M_NVLIST); return (err); } static bool wg_allowedip_valid(const struct wg_allowedip *wip) { return (true); } static int wg_peer_add(struct wg_softc *sc, const nvlist_t *nvl) { uint8_t public[WG_KEY_SIZE]; const void *pub_key; const struct sockaddr *endpoint; int i, err, allowedip_count; device_t dev; size_t size; struct wg_peer *peer = NULL; bool need_insert = false; dev = iflib_get_dev(sc->wg_ctx); if (!nvlist_exists_binary(nvl, "public-key")) { device_printf(dev, "peer has no public-key\n"); return (EINVAL); } pub_key = nvlist_get_binary(nvl, "public-key", &size); if (size != CURVE25519_KEY_SIZE) { device_printf(dev, "%s bad length for public-key %zu\n", __func__, size); return (EINVAL); } if (noise_local_keys(&sc->sc_local, public, NULL) == 0 && bcmp(public, pub_key, WG_KEY_SIZE) == 0) { device_printf(dev, "public-key for peer already in use by host\n"); return (EINVAL); } peer = wg_peer_lookup(sc, pub_key); if (nvlist_exists_bool(nvl, "peer-remove") && nvlist_get_bool(nvl, "peer-remove")) { if (peer != NULL) { wg_hashtable_peer_remove(&sc->sc_hashtable, peer); wg_peer_destroy(peer); /* XXX free */ printf("peer removed\n"); } return (0); } if (nvlist_exists_bool(nvl, "replace-allowedips") && nvlist_get_bool(nvl, "replace-allowedips") && peer != NULL) { wg_route_delete(&peer->p_sc->sc_routes, peer); } if (peer == NULL) { need_insert = true; peer = wg_peer_alloc(sc); noise_remote_init(&peer->p_remote, pub_key, &sc->sc_local); cookie_maker_init(&peer->p_cookie, pub_key); } if (nvlist_exists_binary(nvl, "endpoint")) { endpoint = nvlist_get_binary(nvl, "endpoint", &size); if (size > sizeof(peer->p_endpoint.e_remote)) { device_printf(dev, "%s bad length for endpoint %zu\n", __func__, size); err = EBADMSG; goto out; } memcpy(&peer->p_endpoint.e_remote, endpoint, size); } if (nvlist_exists_binary(nvl, "pre-shared-key")) { const void *key; key = nvlist_get_binary(nvl, "pre-shared-key", &size); noise_remote_set_psk(&peer->p_remote, key); } if (nvlist_exists_number(nvl, "persistent-keepalive-interval")) { uint16_t pki; pki = nvlist_get_number(nvl, "persistent-keepalive-interval"); wg_timers_set_persistent_keepalive(&peer->p_timers, pki); } if (nvlist_exists_binary(nvl, "allowed-ips")) { const struct wg_allowedip *aip, *aip_base; aip = aip_base = nvlist_get_binary(nvl, "allowed-ips", &size); if (size % sizeof(struct wg_allowedip) != 0) { device_printf(dev, "%s bad length for allowed-ips %zu not integer multiple of struct size\n", __func__, size); err = EBADMSG; goto out; } allowedip_count = size/sizeof(struct wg_allowedip); for (i = 0; i < allowedip_count; i++) { if (!wg_allowedip_valid(&aip_base[i])) { device_printf(dev, "%s allowedip %d not valid\n", __func__, i); err = EBADMSG; goto out; } } for (int i = 0; i < allowedip_count; i++, aip++) { if ((err = wg_route_add(&sc->sc_routes, peer, aip)) != 0) { printf("route add %d failed -> %d\n", i, err); } } } if (need_insert) wg_hashtable_peer_insert(&sc->sc_hashtable, peer); return (0); out: wg_peer_destroy(peer); return (err); } static int wgc_set(struct wg_softc *sc, struct ifdrv *ifd) { uint8_t public[WG_KEY_SIZE]; void *nvlpacked; nvlist_t *nvl; device_t dev; ssize_t size; int err; if (ifd->ifd_len == 0 || ifd->ifd_data == NULL) return (EFAULT); dev = iflib_get_dev(sc->wg_ctx); nvlpacked = malloc(ifd->ifd_len, M_TEMP, M_WAITOK); err = copyin(ifd->ifd_data, nvlpacked, ifd->ifd_len); if (err) goto out; nvl = nvlist_unpack(nvlpacked, ifd->ifd_len, 0); if (nvl == NULL) { device_printf(dev, "%s nvlist_unpack failed\n", __func__); err = EBADMSG; goto out; } if (nvlist_exists_bool(nvl, "replace-peers") && nvlist_get_bool(nvl, "replace-peers")) wg_peer_remove_all(sc); if (nvlist_exists_number(nvl, "listen-port")) { int listen_port __unused = nvlist_get_number(nvl, "listen-port"); /* * Set listen port */ if_link_state_change(sc->sc_ifp, LINK_STATE_DOWN); pause("link_down", hz/4); wg_socket_reinit(sc, NULL, NULL); sc->sc_socket.so_port = listen_port; if ((err = wg_socket_init(sc)) != 0) goto out; if_link_state_change(sc->sc_ifp, LINK_STATE_UP); } if (nvlist_exists_binary(nvl, "private-key")) { struct noise_local *local; const void *key = nvlist_get_binary(nvl, "private-key", &size); if (size != CURVE25519_KEY_SIZE) { device_printf(dev, "%s bad length for private-key %zu\n", __func__, size); err = EBADMSG; goto nvl_out; } /* * set private key */ local = &sc->sc_local; noise_local_set_private(local, __DECONST(uint8_t *, key)); noise_local_keys(local, public, NULL); cookie_checker_update(&sc->sc_cookie, public); } if (nvlist_exists_number(nvl, "user-cookie")) { sc->sc_user_cookie = nvlist_get_number(nvl, "user-cookie"); /* * setsockopt */ } if (nvlist_exists_nvlist_array(nvl, "peer-list")) { size_t peercount; const nvlist_t * const*nvl_peers; nvl_peers = nvlist_get_nvlist_array(nvl, "peer-list", &peercount); for (int i = 0; i < peercount; i++) { wg_peer_add(sc, nvl_peers[i]); } } nvl_out: nvlist_destroy(nvl); out: free(nvlpacked, M_TEMP); return (err); } static int wg_priv_ioctl(if_ctx_t ctx, u_long command, caddr_t data) { struct wg_softc *sc = iflib_get_softc(ctx); struct ifdrv *ifd = (struct ifdrv *)data; int ifd_cmd; switch (command) { case SIOCGDRVSPEC: case SIOCSDRVSPEC: ifd_cmd = ifd->ifd_cmd; break; default: return (EINVAL); } switch (ifd_cmd) { case WGC_GET: return (wgc_get(sc, ifd)); break; case WGC_SET: if (priv_check(curthread, PRIV_NET_HWIOCTL)) return (EPERM); return (wgc_set(sc, ifd)); break; } return (ENOTSUP); } static device_method_t wg_if_methods[] = { DEVMETHOD(ifdi_cloneattach, wg_cloneattach), DEVMETHOD(ifdi_attach_post, wg_attach_post), DEVMETHOD(ifdi_detach, wg_detach), DEVMETHOD(ifdi_init, wg_init), DEVMETHOD(ifdi_stop, wg_stop), DEVMETHOD(ifdi_priv_ioctl, wg_priv_ioctl), DEVMETHOD(ifdi_mtu_set, wg_mtu_set), DEVMETHOD(ifdi_promisc_set, wg_set_promisc), DEVMETHOD_END }; static driver_t wg_iflib_driver = { "wg", wg_if_methods, sizeof(struct wg_softc) }; char wg_driver_version[] = "0.0.1"; static struct if_shared_ctx wg_sctx_init = { .isc_magic = IFLIB_MAGIC, .isc_driver_version = wg_driver_version, .isc_driver = &wg_iflib_driver, .isc_flags = IFLIB_PSEUDO, .isc_name = "wg", }; if_shared_ctx_t wg_sctx = &wg_sctx_init; static if_pseudo_t wg_pseudo; int wg_ctx_init(void) { ratelimit_zone = uma_zcreate("wg ratelimit", sizeof(struct ratelimit), NULL, NULL, NULL, NULL, 0, 0); return (0); } void wg_ctx_uninit(void) { uma_zdestroy(ratelimit_zone); } static int wg_module_init(void) { int rc; if ((rc = wg_ctx_init())) return (rc); wg_pseudo = iflib_clone_register(wg_sctx); if (wg_pseudo == NULL) return (ENXIO); return (0); } static void wg_module_deinit(void) { wg_ctx_uninit(); iflib_clone_deregister(wg_pseudo); } static int wg_module_event_handler(module_t mod, int what, void *arg) { int err; switch (what) { case MOD_LOAD: if ((err = wg_module_init()) != 0) return (err); break; case MOD_UNLOAD: if (clone_count == 0) wg_module_deinit(); else return (EBUSY); break; default: return (EOPNOTSUPP); } return (0); } static moduledata_t wg_moduledata = { "wg", wg_module_event_handler, NULL }; DECLARE_MODULE(wg, wg_moduledata, SI_SUB_PSEUDO, SI_ORDER_ANY); MODULE_VERSION(wg, 1); MODULE_DEPEND(wg, iflib, 1, 1, 1); #if defined(__amd64__) || defined(__i386__) /* Optimized blake2 implementations are only available on x86. */ MODULE_DEPEND(wg, blake2, 1, 1, 1); #endif MODULE_DEPEND(wg, crypto, 1, 1, 1);