diff --git a/lib/libifconfig/Makefile b/lib/libifconfig/Makefile --- a/lib/libifconfig/Makefile +++ b/lib/libifconfig/Makefile @@ -19,7 +19,8 @@ libifconfig_internal.c \ libifconfig_lagg.c \ libifconfig_media.c \ - libifconfig_sfp.c + libifconfig_sfp.c \ + libifconfig_wg.c GEN= libifconfig_sfp_tables.h \ libifconfig_sfp_tables.c \ diff --git a/lib/libifconfig/libifconfig.h b/lib/libifconfig/libifconfig.h --- a/lib/libifconfig/libifconfig.h +++ b/lib/libifconfig/libifconfig.h @@ -27,6 +27,7 @@ #pragma once #include +#include #include @@ -289,6 +290,40 @@ struct in6_addr carpr_addr6; }; +#define WG_KEY_LEN 32 + +struct ifconfig_wg_allowed_ip { + uint16_t family; + union { + struct in_addr ip4; + struct in_addr ip6; + }; + uint8_t cidr; + STAILQ_ENTRY(ifconfig_wg_allowed_ip) next; +}; + +struct ifconfig_wg_peer { + uint8_t public_key[WG_KEY_LEN]; + uint8_t preshared_key[WG_KEY_LEN]; + + union { + struct sockaddr addr; + struct sockaddr_in addr4; + struct sockaddr_in6 addr6; + } endpoint; + + struct timespec last_handshake_time; + uint16_t persistent_keepalive_interval; + STAILQ_HEAD(, ifconfig_wg_allowed_ip) allowed_ips; + STAILQ_ENTRY(ifconfig_wg_peer) next; +}; + +struct ifconfig_wg { + uint8_t public_key[WG_KEY_LEN]; + uint16_t listen_port; + STAILQ_HEAD(, ifconfig_wg_peer) peers; +}; + int ifconfig_carp_get_vhid(ifconfig_handle_t *h, const char *name, struct ifconfig_carp *carpr, uint32_t vhid); int ifconfig_carp_get_info(ifconfig_handle_t *h, const char *name, @@ -375,3 +410,7 @@ * length of *lenp * IFNAMSIZ bytes. */ int ifconfig_list_cloners(ifconfig_handle_t *h, char **bufp, size_t *lenp); + +int ifconfig_wg_get_info(ifconfig_handle_t *h, const char *name, + struct ifconfig_wg *wgr); +void ifconfig_wg_free(struct ifconfig_wg *dev); diff --git a/lib/libifconfig/libifconfig_wg.c b/lib/libifconfig/libifconfig_wg.c new file mode 100644 --- /dev/null +++ b/lib/libifconfig/libifconfig_wg.c @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2015-2021 Jason A. Donenfeld . All Rights Reserved. + * Copyright (C) 2023 Baptiste Daroussin . + * + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + + +#include +#include + +struct wg_data_io { + char wgd_name[IFNAMSIZ]; + void *wgd_data; + size_t wgd_size; +}; + +#define SIOCSWG _IOWR('i', 210, struct wg_data_io) +#define SIOCGWG _IOWR('i', 211, struct wg_data_io) + +void +ifconfig_wg_free(struct ifconfig_wg *dev) +{ + struct ifconfig_wg_peer *peer, *tpeer; + struct ifconfig_wg_allowed_ip *aip, *taip; + if (dev == NULL) + return; + STAILQ_FOREACH_SAFE(peer, &dev->peers, next, tpeer) { + STAILQ_FOREACH_SAFE(aip, &peer->allowed_ips, next, taip) { + STAILQ_REMOVE(&peer->allowed_ips, aip, ifconfig_wg_allowed_ip, next); + free(aip); + } + STAILQ_REMOVE(&dev->peers, peer, ifconfig_wg_peer, next); + free(peer); + } +} + +static int +ifconfig_wg_get_allowed_ips(ifconfig_handle_t *h, const nvlist_t *nvl_peer, + struct ifconfig_wg_peer *peer) +{ + struct ifconfig_wg_allowed_ip *aip = NULL; + const nvlist_t *const *nvl_aips; + size_t aip_count, size; + const void *binary; + uint64_t number; + + if (!nvlist_exists_nvlist_array(nvl_peer, "allowed-ips")) + return (0); + nvl_aips = nvlist_get_nvlist_array(nvl_peer, "allowed-ips", &aip_count); + if (aip_count == 0 || nvl_aips == NULL) + return (-1); + for (size_t i = 0; i < aip_count; i++) { + if (!nvlist_exists_number(nvl_aips[i], "cidr")) + continue; + if (!nvlist_exists_binary(nvl_aips[i], "ipv4") && + !nvlist_exists_binary(nvl_aips[i], "ipv6")) + continue; + if ((aip = calloc(1, sizeof(*aip))) == NULL) { + h->error.errtype = OTHER; + h->error.errcode = ENOMEM; + return (-1); + } + number = nvlist_get_number(nvl_aips[i], "cidr"); + if (nvlist_exists_binary(nvl_aips[i], "ipv4")) { + binary = nvlist_get_binary(nvl_aips[i], "ipv4", &size); + if (!binary || number > 32) { + h->error.errtype = OTHER; + h->error.errcode = EINVAL; + free(aip); + return (-1); + } + aip->family = AF_INET; + aip->cidr = number; + memcpy(&aip->ip4, binary, sizeof(aip->ip4)); + } else { + binary = nvlist_get_binary(nvl_aips[i], "ipv6", &size); + if (!binary || number > 128) { + h->error.errtype = OTHER; + h->error.errcode = EINVAL; + free(aip); + return (-1); + } + aip->family = AF_INET6; + aip->cidr = number; + memcpy(&aip->ip6, binary, sizeof(aip->ip6)); + } + STAILQ_INSERT_TAIL(&peer->allowed_ips, aip, next); + } + return (0); +} + +static int +ifconfig_wg_get_peers(ifconfig_handle_t *h, nvlist_t *nvl_device, + struct ifconfig_wg *dev) +{ + const nvlist_t *const *nvl_peers; + size_t size, peer_count; + const void *binary; + uint64_t number; + + if (!nvlist_exists_nvlist_array(nvl_device, "peers")) + return (0); + nvl_peers = nvlist_get_nvlist_array(nvl_device, "peers", &peer_count); + if (nvl_peers == NULL) + return (0); + for (size_t i = 0; i < peer_count; ++i) { + struct ifconfig_wg_peer *peer = NULL; + + if ((peer = calloc(1, sizeof(*peer))) == NULL) { + h->error.errtype = OTHER; + h->error.errcode = ENOMEM; + return (-1); + } + STAILQ_INIT(&peer->allowed_ips); + if (nvlist_exists_binary(nvl_peers[i], "public-key")) { + binary = nvlist_get_binary(nvl_peers[i], "public-key", &size); + if (binary && size == sizeof(peer->public_key)) + memcpy(peer->public_key, binary, sizeof(peer->public_key)); + } + if (nvlist_exists_number(nvl_peers[i], "persistent-keepalive-interval")) { + number = nvlist_get_number(nvl_peers[i], "persistent-keepalive-interval"); + if (number <= UINT16_MAX) + peer->persistent_keepalive_interval = number; + } + if (nvlist_exists_binary(nvl_peers[i], "endpoint")) { + const struct sockaddr *endpoint = nvlist_get_binary(nvl_peers[i], "endpoint", &size); + if (endpoint && size <= sizeof(peer->endpoint) && size >= sizeof(peer->endpoint.addr) && + (endpoint->sa_family == AF_INET || endpoint->sa_family == AF_INET6)) + memcpy(&peer->endpoint.addr, endpoint, size); + } + if (nvlist_exists_binary(nvl_peers[i], "last-handshake-time")) { + binary = nvlist_get_binary(nvl_peers[i], "last-handshake-time", &size); + if (binary && size == sizeof(peer->last_handshake_time)) + memcpy(&peer->last_handshake_time, binary, sizeof(peer->last_handshake_time)); + } + if (ifconfig_wg_get_allowed_ips(h, nvl_peers[i], peer) == -1) { + free(peer); + return (-1); + } + STAILQ_INSERT_TAIL(&dev->peers, peer, next); + } + return (0); +} + +int +ifconfig_wg_get_info(ifconfig_handle_t *h, const char *ifname, + struct ifconfig_wg *dev) +{ + struct wg_data_io wgd = { 0 }; + nvlist_t *nvl_device = NULL; + size_t size; + uint64_t number; + const void *binary; + int ret = -1; + + strlcpy(wgd.wgd_name, ifname, sizeof(wgd.wgd_name)); + if (ifconfig_ioctlwrap(h, AF_INET, SIOCGWG, &wgd) <0) + goto err; + + wgd.wgd_data = malloc(wgd.wgd_size); + if (!wgd.wgd_data) + goto err; + if (ifconfig_ioctlwrap(h, AF_INET, SIOCGWG, &wgd) <0) + goto err; + + STAILQ_INIT(&dev->peers); + nvl_device = nvlist_unpack(wgd.wgd_data, wgd.wgd_size, 0); + if (!nvl_device) + goto err; + + if (nvlist_exists_number(nvl_device, "listen-port")) { + number = nvlist_get_number(nvl_device, "listen-port"); + if (number <= UINT16_MAX) + dev->listen_port = number; + } + if (nvlist_exists_binary(nvl_device, "public-key")) { + binary = nvlist_get_binary(nvl_device, "public-key", &size); + if (binary && size == sizeof(dev->public_key)) + memcpy(dev->public_key, binary, sizeof(dev->public_key)); + } + if (ifconfig_wg_get_peers(h, nvl_device, dev) == -1) + goto err; + + ret = 0; +err: + if (ret != 0) + ifconfig_wg_free(dev); + free(wgd.wgd_data); + nvlist_destroy(nvl_device); + return ret; +} diff --git a/sbin/ifconfig/Makefile b/sbin/ifconfig/Makefile --- a/sbin/ifconfig/Makefile +++ b/sbin/ifconfig/Makefile @@ -35,6 +35,7 @@ SRCS+= ifgre.c # GRE keys etc SRCS+= ifgif.c # GIF reversed header workaround SRCS+= ifipsec.c # IPsec VTI +SRCS+= wg.c # Wireguard SRCS+= sfp.c # SFP/SFP+ information LIBADD+= ifconfig m util diff --git a/sbin/ifconfig/wg.c b/sbin/ifconfig/wg.c new file mode 100644 --- /dev/null +++ b/sbin/ifconfig/wg.c @@ -0,0 +1,114 @@ +#include +#include "ifconfig.h" +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +static void +print_endpoint(const struct sockaddr *addr) +{ + char host[4096 + 1]; + char service[512 + 1]; + int ret; + socklen_t addr_len = 0; + + if (addr->sa_family == AF_INET) + addr_len = sizeof(struct sockaddr_in); + else if (addr->sa_family == AF_INET6) + addr_len = sizeof(struct sockaddr_in6); + + ret = getnameinfo(addr, addr_len, host, sizeof(host), service, sizeof(service), NI_DGRAM | NI_NUMERICSERV | NI_NUMERICHOST); + if (ret) { + printf("%s", gai_strerror(ret)); + } else + printf((addr->sa_family == AF_INET6 && strchr(host, ':')) ? "[%s]:%s" : "%s:%s", host, service); +} + +static void +print_ip(const struct ifconfig_wg_allowed_ip *wgaip) +{ + char buf[INET6_ADDRSTRLEN + 1]; + + memset(buf, 0, sizeof(buf)); + if (wgaip->family == AF_INET) + inet_ntop(AF_INET, &wgaip->ip4, buf, sizeof(buf)); + else + inet_ntop(AF_INET, &wgaip->ip6, buf, sizeof(buf)); + printf("%s", buf); +} + +static void +print_key(void *key, size_t len) +{ + char b64key[BUFSIZ]; + + memset(b64key, 0, sizeof(b64key)); + b64_ntop(key, len, b64key, sizeof(b64key)); + printf("%s", b64key); +} + +static void +wg_status(if_ctx *ctx __unused) +{ + struct ifconfig_wg wg; + struct ifconfig_wg_peer *wgpeer; + struct ifconfig_wg_allowed_ip *wgaip; + + memset(&wg, 0, sizeof(wg)); + if (ifconfig_wg_get_info(lifh, ctx->ifname, &wg) != 0) + return; + if (wg.public_key[0] != '\0') { + printf("\twg public key "); + print_key(wg.public_key, sizeof(wg.public_key)); + printf("\n"); + } + if (wg.listen_port > 0) + printf("\twg listen-port %hu\n", wg.listen_port); + + STAILQ_FOREACH(wgpeer, &wg.peers, next) { + printf("\twg peer "); + print_key(wgpeer->public_key, sizeof(wgpeer->public_key)); + printf("\n"); + if (wgpeer->endpoint.addr.sa_family == AF_INET || wgpeer->endpoint.addr.sa_family == AF_INET6) { + printf("\t endpoint: "); + print_endpoint(&wgpeer->endpoint.addr); + printf("\n"); + } + if (!STAILQ_EMPTY(&wgpeer->allowed_ips)) + printf("\t allowed ips: "); + STAILQ_FOREACH(wgaip, &wgpeer->allowed_ips, next) { + print_ip(wgaip); + printf("/%u", wgaip->cidr); + if (STAILQ_NEXT(wgaip, next) != NULL) + printf(", "); + else + printf("\n"); + } + if (wgpeer->last_handshake_time.tv_sec > 0) { + time_t now = time(NULL); + printf("\t last handshake: %"PRIdMAX" seconds\n", (intmax_t)now - wgpeer->last_handshake_time.tv_sec); + } + if (wgpeer->persistent_keepalive_interval > 0) + printf("\t keepalive: %"PRIdMAX" seconds\n", (intmax_t)wgpeer->persistent_keepalive_interval); + } + ifconfig_wg_free(&wg); +} + +static struct afswtch af_wg = { + .af_name = "af_wg", + .af_af = AF_UNSPEC, + .af_other_status = wg_status, +}; + +static __constructor void +wg_ctor(void) +{ + af_register(&af_wg); +}