aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/src/ipc-freebsd.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/ipc-freebsd.h')
-rw-r--r--src/ipc-freebsd.h365
1 files changed, 365 insertions, 0 deletions
diff --git a/src/ipc-freebsd.h b/src/ipc-freebsd.h
new file mode 100644
index 0000000..58e5e71
--- /dev/null
+++ b/src/ipc-freebsd.h
@@ -0,0 +1,365 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright (C) 2015-2021 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
+ *
+ */
+
+#include <assert.h>
+#include <sys/nv.h>
+#include <sys/sockio.h>
+#include <dev/wg/if_wg.h>
+
+#define IPC_SUPPORTS_KERNEL_INTERFACE
+
+static int get_dgram_socket(void)
+{
+ static int sock = -1;
+ if (sock < 0)
+ sock = socket(AF_LOCAL, SOCK_DGRAM, 0);
+ return sock;
+}
+
+static int kernel_get_wireguard_interfaces(struct string_list *list)
+{
+ struct ifgroupreq ifgr = { .ifgr_name = "wg" };
+ struct ifg_req *ifg;
+ int s = get_dgram_socket(), ret = 0;
+
+ if (s < 0)
+ return -errno;
+
+ if (ioctl(s, SIOCGIFGMEMB, (caddr_t)&ifgr) < 0)
+ return errno == ENOENT ? 0 : -errno;
+
+ ifgr.ifgr_groups = calloc(1, ifgr.ifgr_len);
+ if (!ifgr.ifgr_groups)
+ return -errno;
+ if (ioctl(s, SIOCGIFGMEMB, (caddr_t)&ifgr) < 0) {
+ ret = -errno;
+ goto out;
+ }
+
+ for (ifg = ifgr.ifgr_groups; ifg && ifgr.ifgr_len > 0; ++ifg) {
+ if ((ret = string_list_add(list, ifg->ifgrq_member)) < 0)
+ goto out;
+ ifgr.ifgr_len -= sizeof(struct ifg_req);
+ }
+
+out:
+ free(ifgr.ifgr_groups);
+ return ret;
+}
+
+static int kernel_get_device(struct wgdevice **device, const char *ifname)
+{
+ struct wg_data_io wgd = { 0 };
+ nvlist_t *nvl_device = NULL;
+ const nvlist_t *const *nvl_peers;
+ struct wgdevice *dev = NULL;
+ size_t size, peer_count, i;
+ uint64_t number;
+ const void *binary;
+ int ret = 0, s;
+
+ *device = NULL;
+ s = get_dgram_socket();
+ if (s < 0)
+ goto err;
+
+ strlcpy(wgd.wgd_name, ifname, sizeof(wgd.wgd_name));
+ if (ioctl(s, SIOCGWG, &wgd) < 0)
+ goto err;
+
+ wgd.wgd_data = malloc(wgd.wgd_size);
+ if (!wgd.wgd_data)
+ goto err;
+ if (ioctl(s, SIOCGWG, &wgd) < 0)
+ goto err;
+
+ dev = calloc(1, sizeof(*dev));
+ if (!dev)
+ goto err;
+ strlcpy(dev->name, ifname, sizeof(dev->name));
+ 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;
+ dev->flags |= WGDEVICE_HAS_LISTEN_PORT;
+ }
+ }
+ if (nvlist_exists_number(nvl_device, "user-cookie")) {
+ number = nvlist_get_number(nvl_device, "user-cookie");
+ if (number <= UINT32_MAX) {
+ dev->fwmark = number;
+ dev->flags |= WGDEVICE_HAS_FWMARK;
+ }
+ }
+ 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));
+ dev->flags |= WGDEVICE_HAS_PUBLIC_KEY;
+ }
+ }
+ if (nvlist_exists_binary(nvl_device, "private-key")) {
+ binary = nvlist_get_binary(nvl_device, "private-key", &size);
+ if (binary && size == sizeof(dev->private_key)) {
+ memcpy(dev->private_key, binary, sizeof(dev->private_key));
+ dev->flags |= WGDEVICE_HAS_PRIVATE_KEY;
+ }
+ }
+ if (!nvlist_exists_nvlist_array(nvl_device, "peers"))
+ goto skip_peers;
+ nvl_peers = nvlist_get_nvlist_array(nvl_device, "peers", &peer_count);
+ if (!nvl_peers)
+ goto skip_peers;
+ for (i = 0; i < peer_count; ++i) {
+ struct wgpeer *peer;
+ struct wgallowedip *aip = NULL;
+ const nvlist_t *const *nvl_aips;
+ size_t aip_count, j;
+
+ peer = calloc(1, sizeof(*peer));
+ if (!peer)
+ goto err_peer;
+ 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));
+ peer->flags |= WGPEER_HAS_PUBLIC_KEY;
+ }
+ }
+ if (nvlist_exists_binary(nvl_peers[i], "preshared-key")) {
+ binary = nvlist_get_binary(nvl_peers[i], "preshared-key", &size);
+ if (binary && size == sizeof(peer->preshared_key)) {
+ memcpy(peer->preshared_key, binary, sizeof(peer->preshared_key));
+ if (!key_is_zero(peer->preshared_key))
+ peer->flags |= WGPEER_HAS_PRESHARED_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;
+ peer->flags |= WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL;
+ }
+ }
+ 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_number(nvl_peers[i], "rx-bytes"))
+ peer->rx_bytes = nvlist_get_number(nvl_peers[i], "rx-bytes");
+ if (nvlist_exists_number(nvl_peers[i], "tx-bytes"))
+ peer->tx_bytes = nvlist_get_number(nvl_peers[i], "tx-bytes");
+ 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 (!nvlist_exists_nvlist_array(nvl_peers[i], "allowed-ips"))
+ goto skip_allowed_ips;
+ nvl_aips = nvlist_get_nvlist_array(nvl_peers[i], "allowed-ips", &aip_count);
+ if (!aip_count || !nvl_aips)
+ goto skip_allowed_ips;
+ for (j = 0; j < aip_count; ++j) {
+ if (!nvlist_exists_number(nvl_aips[j], "cidr"))
+ continue;
+ if (!nvlist_exists_binary(nvl_aips[j], "ipv4") && !nvlist_exists_binary(nvl_aips[j], "ipv6"))
+ continue;
+ aip = calloc(1, sizeof(*aip));
+ if (!aip)
+ goto err_allowed_ips;
+ number = nvlist_get_number(nvl_aips[j], "cidr");
+ if (nvlist_exists_binary(nvl_aips[j], "ipv4")) {
+ binary = nvlist_get_binary(nvl_aips[j], "ipv4", &size);
+ if (!binary || number > 32) {
+ ret = EINVAL;
+ goto err_allowed_ips;
+ }
+ aip->family = AF_INET;
+ aip->cidr = number;
+ memcpy(&aip->ip4, binary, sizeof(aip->ip4));
+ } else {
+ assert(nvlist_exists_binary(nvl_aips[j], "ipv6"));
+ binary = nvlist_get_binary(nvl_aips[j], "ipv6", &size);
+ if (!binary || number > 128) {
+ ret = EINVAL;
+ goto err_allowed_ips;
+ }
+ aip->family = AF_INET6;
+ aip->cidr = number;
+ memcpy(&aip->ip6, binary, sizeof(aip->ip6));
+ }
+
+ if (!peer->first_allowedip)
+ peer->first_allowedip = aip;
+ else
+ peer->last_allowedip->next_allowedip = aip;
+ peer->last_allowedip = aip;
+ aip = NULL;
+ continue;
+
+ err_allowed_ips:
+ if (!ret)
+ ret = -errno;
+ free(aip);
+ goto err_peer;
+ }
+
+ /* Nothing leaked, hopefully -- ownership transferred or aip freed. */
+ assert(aip == NULL);
+ skip_allowed_ips:
+ if (!dev->first_peer)
+ dev->first_peer = peer;
+ else
+ dev->last_peer->next_peer = peer;
+ dev->last_peer = peer;
+ continue;
+
+ err_peer:
+ if (!ret)
+ ret = -errno;
+ free(peer);
+ goto err;
+ }
+
+skip_peers:
+ free(wgd.wgd_data);
+ nvlist_destroy(nvl_device);
+ *device = dev;
+ return 0;
+
+err:
+ if (!ret)
+ ret = -errno;
+ free(wgd.wgd_data);
+ nvlist_destroy(nvl_device);
+ free(dev);
+ return ret;
+}
+
+
+static int kernel_set_device(struct wgdevice *dev)
+{
+ struct wg_data_io wgd = { 0 };
+ nvlist_t *nvl_device = NULL, **nvl_peers = NULL;
+ size_t peer_count = 0, i = 0;
+ struct wgpeer *peer;
+ int ret = 0, s;
+
+ strlcpy(wgd.wgd_name, dev->name, sizeof(wgd.wgd_name));
+
+ nvl_device = nvlist_create(0);
+ if (!nvl_device)
+ goto err;
+
+ for_each_wgpeer(dev, peer)
+ ++peer_count;
+ if (peer_count) {
+ nvl_peers = calloc(peer_count, sizeof(*nvl_peers));
+ if (!nvl_peers)
+ goto err;
+ }
+ if (dev->flags & WGDEVICE_HAS_PRIVATE_KEY)
+ nvlist_add_binary(nvl_device, "private-key", dev->private_key, sizeof(dev->private_key));
+ if (dev->flags & WGDEVICE_HAS_LISTEN_PORT)
+ nvlist_add_number(nvl_device, "listen-port", dev->listen_port);
+ if (dev->flags & WGDEVICE_HAS_FWMARK)
+ nvlist_add_number(nvl_device, "user-cookie", dev->fwmark);
+ if (dev->flags & WGDEVICE_REPLACE_PEERS)
+ nvlist_add_bool(nvl_device, "replace-peers", true);
+
+ for_each_wgpeer(dev, peer) {
+ size_t aip_count = 0, j = 0;
+ nvlist_t **nvl_aips = NULL;
+ struct wgallowedip *aip;
+
+ nvl_peers[i] = nvlist_create(0);
+ if (!nvl_peers[i])
+ goto err_peer;
+ for_each_wgallowedip(peer, aip)
+ ++aip_count;
+ if (aip_count) {
+ nvl_aips = calloc(aip_count, sizeof(*nvl_aips));
+ if (!nvl_aips)
+ goto err_peer;
+ }
+ nvlist_add_binary(nvl_peers[i], "public-key", peer->public_key, sizeof(peer->public_key));
+ if (peer->flags & WGPEER_HAS_PRESHARED_KEY)
+ nvlist_add_binary(nvl_peers[i], "preshared-key", peer->preshared_key, sizeof(peer->preshared_key));
+ if (peer->flags & WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL)
+ nvlist_add_number(nvl_peers[i], "persistent-keepalive-interval", peer->persistent_keepalive_interval);
+ if (peer->endpoint.addr.sa_family == AF_INET || peer->endpoint.addr.sa_family == AF_INET6)
+ nvlist_add_binary(nvl_peers[i], "endpoint", &peer->endpoint.addr, peer->endpoint.addr.sa_len);
+ if (peer->flags & WGPEER_REPLACE_ALLOWEDIPS)
+ nvlist_add_bool(nvl_peers[i], "replace-allowedips", true);
+ if (peer->flags & WGPEER_REMOVE_ME)
+ nvlist_add_bool(nvl_peers[i], "remove", true);
+ for_each_wgallowedip(peer, aip) {
+ nvl_aips[j] = nvlist_create(0);
+ if (!nvl_aips[j])
+ goto err_peer;
+ if (aip->flags) {
+ //TODO: implement me
+ ret = -EOPNOTSUPP;
+ goto err_peer;
+ }
+ nvlist_add_number(nvl_aips[j], "cidr", aip->cidr);
+ if (aip->family == AF_INET)
+ nvlist_add_binary(nvl_aips[j], "ipv4", &aip->ip4, sizeof(aip->ip4));
+ else if (aip->family == AF_INET6)
+ nvlist_add_binary(nvl_aips[j], "ipv6", &aip->ip6, sizeof(aip->ip6));
+ ++j;
+ }
+ if (j) {
+ nvlist_add_nvlist_array(nvl_peers[i], "allowed-ips", (const nvlist_t *const *)nvl_aips, j);
+ for (j = 0; j < aip_count; ++j)
+ nvlist_destroy(nvl_aips[j]);
+ free(nvl_aips);
+ }
+ ++i;
+ continue;
+
+ err_peer:
+ ret = -errno;
+ for (j = 0; j < aip_count && nvl_aips; ++j)
+ nvlist_destroy(nvl_aips[j]);
+ free(nvl_aips);
+ nvlist_destroy(nvl_peers[i]);
+ nvl_peers[i] = NULL;
+ goto err;
+ }
+ if (i) {
+ nvlist_add_nvlist_array(nvl_device, "peers", (const nvlist_t *const *)nvl_peers, i);
+ for (i = 0; i < peer_count; ++i)
+ nvlist_destroy(nvl_peers[i]);
+ free(nvl_peers);
+ nvl_peers = NULL;
+ }
+ wgd.wgd_data = nvlist_pack(nvl_device, &wgd.wgd_size);
+ nvlist_destroy(nvl_device);
+ nvl_device = NULL;
+ if (!wgd.wgd_data)
+ goto err;
+ s = get_dgram_socket();
+ if (s < 0)
+ return -errno;
+ return ioctl(s, SIOCSWG, &wgd);
+
+err:
+ if (!ret)
+ ret = -errno;
+ for (i = 0; i < peer_count && nvl_peers; ++i)
+ nvlist_destroy(nvl_peers[i]);
+ free(nvl_peers);
+ nvlist_destroy(nvl_device);
+ return ret;
+}