]> git.ipfire.org Git - thirdparty/wireguard-tools.git/commitdiff
ipc: freebsd: add initial FreeBSD support
authorJason A. Donenfeld <Jason@zx2c4.com>
Thu, 11 Mar 2021 03:24:40 +0000 (20:24 -0700)
committerJason A. Donenfeld <Jason@zx2c4.com>
Thu, 11 Mar 2021 22:02:07 +0000 (15:02 -0700)
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
src/Makefile
src/ipc-freebsd.h [new file with mode: 0644]
src/ipc.c
src/uapi/freebsd/dev/if_wg/if_wg.h [new file with mode: 0644]

index fc5e0c62d6dec4c9c31c11ff7b01efb2cd49bf8b..7b8969afec060a1181b8d80edc6c96f036f70061 100644 (file)
@@ -52,6 +52,9 @@ WIREGUARD_TOOLS_VERSION = $(patsubst v%,%,$(shell GIT_DIR="$(PWD)/../.git" git d
 ifneq ($(WIREGUARD_TOOLS_VERSION),)
 CFLAGS += -D'WIREGUARD_TOOLS_VERSION="$(WIREGUARD_TOOLS_VERSION)"'
 endif
+ifeq ($(PLATFORM),freebsd)
+LDLIBS += -lnv
+endif
 ifeq ($(PLATFORM),haiku)
 LDLIBS += -lnetwork -lbsd
 endif
diff --git a/src/ipc-freebsd.h b/src/ipc-freebsd.h
new file mode 100644 (file)
index 0000000..e8037af
--- /dev/null
@@ -0,0 +1,349 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright (C) 2015-2021 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
+ *
+ */
+
+#include <sys/nv.h>
+#include <sys/sockio.h>
+#include <dev/if_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_INET, 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;
+               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));
+                               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) {
+                       aip = calloc(1, sizeof(*aip));
+                       if (!aip)
+                               goto err_allowed_ips;
+                       if (!nvlist_exists_number(nvl_aips[j], "cidr"))
+                               continue;
+                       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 if (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));
+                       } else
+                               continue;
+
+                       if (!peer->first_allowedip)
+                               peer->first_allowedip = aip;
+                       else
+                               peer->last_allowedip->next_allowedip = aip;
+                       peer->last_allowedip = aip;
+                       continue;
+
+               err_allowed_ips:
+                       if (!ret)
+                               ret = -errno;
+                       free(aip);
+                       goto err_peer;
+               }
+       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;
+                       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]);
+               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);
+       }
+       wgd.wgd_data = nvlist_pack(nvl_device, &wgd.wgd_size);
+       nvlist_destroy(nvl_device);
+       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;
+}
index dce12df898d1125914fd1c13acaa926956f9fd82..4a9f00a4e17b7b64a6dee20241a40715b487596a 100644 (file)
--- a/src/ipc.c
+++ b/src/ipc.c
@@ -45,6 +45,8 @@ static int string_list_add(struct string_list *list, const char *str)
 #include "ipc-linux.h"
 #elif defined(__OpenBSD__)
 #include "ipc-openbsd.h"
+#elif defined(__FreeBSD__)
+#include "ipc-freebsd.h"
 #endif
 
 /* first\0second\0third\0forth\0last\0\0 */
diff --git a/src/uapi/freebsd/dev/if_wg/if_wg.h b/src/uapi/freebsd/dev/if_wg/if_wg.h
new file mode 100644 (file)
index 0000000..a4b3b13
--- /dev/null
@@ -0,0 +1,16 @@
+#ifndef __IF_WG_H__
+#define __IF_WG_H__
+
+#include <net/if.h>
+#include <netinet/in.h>
+
+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)
+
+#endif