aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
-rw-r--r--src/Kbuild2
-rw-r--r--src/Makefile4
-rw-r--r--src/compat/compat.h37
-rw-r--r--src/config.c353
-rw-r--r--src/config.h13
-rw-r--r--src/device.c24
-rw-r--r--src/device.h1
-rw-r--r--src/main.c11
-rw-r--r--src/netlink.c494
-rw-r--r--src/netlink.h9
-rw-r--r--src/noise.c13
-rw-r--r--src/peer.c2
-rwxr-xr-xsrc/tests/netns.sh39
-rw-r--r--src/tests/qemu/Makefile8
-rw-r--r--src/tools/config.c275
-rw-r--r--src/tools/config.h30
-rw-r--r--src/tools/containers.h95
-rw-r--r--src/tools/encoding.h2
-rw-r--r--src/tools/ipc.c601
-rw-r--r--src/tools/ipc.h1
-rw-r--r--src/tools/mnlg.c327
-rw-r--r--src/tools/mnlg.h22
-rw-r--r--src/tools/set.c13
-rw-r--r--src/tools/setconf.c12
-rw-r--r--src/tools/show.c148
-rw-r--r--src/tools/showconf.c41
-rw-r--r--src/uapi.h166
-rw-r--r--src/uapi/wireguard.h199
28 files changed, 1934 insertions, 1008 deletions
diff --git a/src/Kbuild b/src/Kbuild
index e6c7bc9..9815a4b 100644
--- a/src/Kbuild
+++ b/src/Kbuild
@@ -2,7 +2,7 @@ ccflags-y := -O3 -fvisibility=hidden
ccflags-$(CONFIG_WIREGUARD_DEBUG) += -DDEBUG -g
ccflags-y += -Wframe-larger-than=8192
ccflags-y += -D'pr_fmt(fmt)=KBUILD_MODNAME ": " fmt'
-wireguard-y := main.o noise.o device.o peer.o timers.o queueing.o send.o receive.o socket.o config.o hashtables.o routingtable.o ratelimiter.o cookie.o
+wireguard-y := main.o noise.o device.o peer.o timers.o queueing.o send.o receive.o socket.o hashtables.o routingtable.o ratelimiter.o cookie.o netlink.o
wireguard-y += crypto/curve25519.o crypto/chacha20poly1305.o crypto/blake2s.o
ifeq ($(CONFIG_X86_64),y)
diff --git a/src/Makefile b/src/Makefile
index 960bbb4..fca65d2 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -46,11 +46,13 @@ install:
DKMS_TOP_LEVEL := Makefile Kbuild Kconfig $(filter-out wireguard.mod.c, $(wildcard *.c)) $(filter-out version.h, $(wildcard *.h)) version.h dkms.conf
DKMS_SELFTEST_LEVEL := $(wildcard selftest/*.c) $(wildcard selftest/*.h)
DKMS_CRYPTO_LEVEL := $(wildcard crypto/*.c) $(wildcard crypto/*.h) $(wildcard crypto/*.S)
+DKMS_UAPI_LEVEL := $(wildcard uapi/*.h)
DKMS_COMPAT_LEVEL := $(shell find compat/ -name '*.c' -o -name '*.h' -o -name '*.include')
-dkms-install: $(DKMS_TOP_LEVEL) $(DKMS_SELFTEST_LEVEL) $(DKMS_CRYPTO_LEVEL) $(DKMS_COMPAT_LEVEL)
+dkms-install: $(DKMS_TOP_LEVEL) $(DKMS_SELFTEST_LEVEL) $(DKMS_CRYPTO_LEVEL) $(DKMS_UAPI_LEVEL) $(DKMS_COMPAT_LEVEL)
@install -v -m0644 -D -t$(DESTDIR)$(DKMSDIR) $(DKMS_TOP_LEVEL)
@install -v -m0644 -D -t$(DESTDIR)$(DKMSDIR)/selftest $(DKMS_SELFTEST_LEVEL)
@install -v -m0644 -D -t$(DESTDIR)$(DKMSDIR)/crypto $(DKMS_CRYPTO_LEVEL)
+ @install -v -m0644 -D -t$(DESTDIR)$(DKMSDIR)/uapi $(DKMS_UAPI_LEVEL)
@for file in $(DKMS_COMPAT_LEVEL); do install -v -m0644 -D $$file $(DESTDIR)$(DKMSDIR)/$$file; done
tools:
diff --git a/src/compat/compat.h b/src/compat/compat.h
index 28a7956..dcbb080 100644
--- a/src/compat/compat.h
+++ b/src/compat/compat.h
@@ -401,6 +401,43 @@ static inline void kvfree_ours(const void *addr)
#define newlink(a,b,c,d,e) newlink(a,b,c,d)
#endif
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 12, 0)
+#include <net/netlink.h>
+#include <net/genetlink.h>
+#define nlmsg_parse(a, b, c, d, e, f) nlmsg_parse(a, b, c, d, e)
+#define nla_parse_nested(a, b, c, d, e) nla_parse_nested(a, b, c, d)
+#endif
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
+static inline struct nlattr **genl_family_attrbuf(const struct genl_family *family)
+{
+ return family->attrbuf;
+}
+#endif
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 12, 0)
+#define PTR_ERR_OR_ZERO(p) PTR_RET(p)
+#endif
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 7, 0)
+#include <net/netlink.h>
+#define nla_put_u64_64bit(a, b, c, d) nla_put_u64(a, b, c)
+#endif
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 6, 0)
+#define GENL_UNS_ADMIN_PERM GENL_ADMIN_PERM
+#endif
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
+#include <net/genetlink.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 13, 0)
+#define genl_register_family(a) genl_register_family_with_ops(a, (struct genl_ops *)genl_ops, ARRAY_SIZE(genl_ops))
+#else
+#define genl_register_family(a) genl_register_family_with_ops(a, genl_ops)
+#endif
+#endif
+
+
/* https://lkml.org/lkml/2017/6/23/790 */
#if IS_ENABLED(CONFIG_NF_CONNTRACK)
#include <linux/ip.h>
diff --git a/src/config.c b/src/config.c
deleted file mode 100644
index a4a6782..0000000
--- a/src/config.c
+++ /dev/null
@@ -1,353 +0,0 @@
-/* Copyright (C) 2015-2017 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. */
-
-#include "config.h"
-#include "device.h"
-#include "socket.h"
-#include "queueing.h"
-#include "timers.h"
-#include "hashtables.h"
-#include "peer.h"
-#include "uapi.h"
-#include <crypto/algapi.h>
-
-static int set_device_port(struct wireguard_device *wg, u16 port)
-{
- struct wireguard_peer *peer, *temp;
- if (wg->incoming_port == port)
- return 0;
- socket_uninit(wg);
- wg->incoming_port = port;
- peer_for_each (wg, peer, temp, false)
- socket_clear_peer_endpoint_src(peer);
- if (!netif_running(wg->dev))
- return 0;
- return socket_init(wg);
-}
-
-static int set_ipmask(struct wireguard_peer *peer, void __user *user_ipmask)
-{
- int ret = -EINVAL;
- struct wgipmask in_ipmask;
-
- if (copy_from_user(&in_ipmask, user_ipmask, sizeof(in_ipmask)))
- return -EFAULT;
-
- if (in_ipmask.family == AF_INET && in_ipmask.cidr <= 32)
- ret = routing_table_insert_v4(&peer->device->peer_routing_table, &in_ipmask.ip4, in_ipmask.cidr, peer);
- else if (in_ipmask.family == AF_INET6 && in_ipmask.cidr <= 128)
- ret = routing_table_insert_v6(&peer->device->peer_routing_table, &in_ipmask.ip6, in_ipmask.cidr, peer);
-
- return ret;
-}
-
-static const u8 zeros[WG_KEY_LEN] = { 0 };
-
-static int set_peer(struct wireguard_device *wg, void __user *user_peer, size_t *len)
-{
- int ret = 0;
- size_t i;
- struct wgpeer in_peer;
- void __user *user_ipmask;
- struct wireguard_peer *peer = NULL;
-
- if (copy_from_user(&in_peer, user_peer, sizeof(in_peer)))
- return -EFAULT;
-
- if (!memcmp(zeros, in_peer.public_key, NOISE_PUBLIC_KEY_LEN))
- return -EINVAL; /* Can't add a peer with no public key. */
-
- peer = pubkey_hashtable_lookup(&wg->peer_hashtable, in_peer.public_key);
- if (!peer) { /* Peer doesn't exist yet. Add a new one. */
- if (in_peer.flags & WGPEER_REMOVE_ME)
- return -ENODEV; /* Tried to remove a non-existing peer. */
- if (in_peer.flags & WGPEER_REMOVE_PRESHARED_KEY)
- return -EINVAL; /* Tried to remove a psk for a non-existing peer. */
-
- down_read(&wg->static_identity.lock);
- if (wg->static_identity.has_identity && !memcmp(in_peer.public_key, wg->static_identity.static_public, NOISE_PUBLIC_KEY_LEN)) {
- /* We silently ignore peers that have the same public key as the device. The reason we do it silently
- * is that we'd like for people to be able to reuse the same set of API calls across peers. */
- up_read(&wg->static_identity.lock);
- goto out;
- }
- up_read(&wg->static_identity.lock);
-
- peer = peer_rcu_get(peer_create(wg, in_peer.public_key, in_peer.preshared_key));
- if (!peer)
- return -ENOMEM;
- }
-
- if (in_peer.flags & WGPEER_REMOVE_ME) {
- peer_put(peer);
- peer_remove(peer);
- goto out;
- }
-
- if (in_peer.flags & WGPEER_REMOVE_PRESHARED_KEY) {
- down_write(&peer->handshake.lock);
- memset(&peer->handshake.preshared_key, 0, NOISE_SYMMETRIC_KEY_LEN);
- up_write(&peer->handshake.lock);
- } else if (crypto_memneq(zeros, in_peer.preshared_key, WG_KEY_LEN)) {
- down_write(&peer->handshake.lock);
- memcpy(&peer->handshake.preshared_key, in_peer.preshared_key, NOISE_SYMMETRIC_KEY_LEN);
- up_write(&peer->handshake.lock);
- }
-
- if (in_peer.endpoint.addr.sa_family == AF_INET || in_peer.endpoint.addr.sa_family == AF_INET6) {
- struct endpoint endpoint = { { { 0 } } };
- memcpy(&endpoint, &in_peer.endpoint, sizeof(in_peer.endpoint));
- socket_set_peer_endpoint(peer, &endpoint);
- }
-
- if (in_peer.flags & WGPEER_REPLACE_IPMASKS)
- routing_table_remove_by_peer(&wg->peer_routing_table, peer);
- for (i = 0, user_ipmask = user_peer + sizeof(struct wgpeer); i < in_peer.num_ipmasks; ++i, user_ipmask += sizeof(struct wgipmask)) {
- ret = set_ipmask(peer, user_ipmask);
- if (ret)
- break;
- }
-
- if (in_peer.persistent_keepalive_interval != (u16)-1) {
- const bool send_keepalive = !peer->persistent_keepalive_interval && in_peer.persistent_keepalive_interval && netif_running(wg->dev);
- peer->persistent_keepalive_interval = (unsigned long)in_peer.persistent_keepalive_interval * HZ;
- if (send_keepalive)
- packet_send_keepalive(peer);
- }
-
- if (netif_running(wg->dev))
- packet_send_staged_packets(peer);
-
- peer_put(peer);
-
-out:
- if (!ret)
- *len = sizeof(struct wgpeer) + (in_peer.num_ipmasks * sizeof(struct wgipmask));
-
- return ret;
-}
-
-int config_set_device(struct wireguard_device *wg, void __user *user_device)
-{
- int ret;
- size_t i, offset;
- struct wireguard_peer *peer, *temp;
- struct wgdevice in_device;
- void __user *user_peer;
- bool modified_static_identity = false;
-
- BUILD_BUG_ON(WG_KEY_LEN != NOISE_PUBLIC_KEY_LEN);
- BUILD_BUG_ON(WG_KEY_LEN != NOISE_SYMMETRIC_KEY_LEN);
-
- mutex_lock(&wg->device_update_lock);
-
- ret = -EFAULT;
- if (copy_from_user(&in_device, user_device, sizeof(in_device)))
- goto out;
-
- ret = -EPROTO;
- if (in_device.version_magic != WG_API_VERSION_MAGIC)
- goto out;
-
- if (in_device.fwmark || (!in_device.fwmark && (in_device.flags & WGDEVICE_REMOVE_FWMARK))) {
- wg->fwmark = in_device.fwmark;
- peer_for_each (wg, peer, temp, false)
- socket_clear_peer_endpoint_src(peer);
- }
-
- if (in_device.port) {
- ret = set_device_port(wg, in_device.port);
- if (ret)
- goto out;
- }
-
- if (in_device.flags & WGDEVICE_REPLACE_PEERS)
- peer_remove_all(wg);
-
- if (in_device.flags & WGDEVICE_REMOVE_PRIVATE_KEY) {
- noise_set_static_identity_private_key(&wg->static_identity, NULL);
- modified_static_identity = true;
- } else if (crypto_memneq(zeros, in_device.private_key, WG_KEY_LEN)) {
- u8 public_key[NOISE_PUBLIC_KEY_LEN] = { 0 };
- struct wireguard_peer *peer;
- /* We remove before setting, to prevent race, which means doing two 25519-genpub ops. */
- bool unused __attribute((unused)) = curve25519_generate_public(public_key, in_device.private_key);
- peer = pubkey_hashtable_lookup(&wg->peer_hashtable, public_key);
- if (peer) {
- peer_put(peer);
- peer_remove(peer);
- }
-
- noise_set_static_identity_private_key(&wg->static_identity, in_device.private_key);
- modified_static_identity = true;
- }
-
- if (modified_static_identity) {
- peer_for_each (wg, peer, temp, false) {
- if (!noise_precompute_static_static(peer))
- peer_remove(peer);
- }
- cookie_checker_precompute_device_keys(&wg->cookie_checker);
- }
-
- for (i = 0, offset = 0, user_peer = user_device + sizeof(struct wgdevice); i < in_device.num_peers; ++i, user_peer += offset) {
- ret = set_peer(wg, user_peer, &offset);
- if (ret)
- goto out;
- }
- ret = 0;
-out:
- mutex_unlock(&wg->device_update_lock);
- memzero_explicit(&in_device.private_key, NOISE_PUBLIC_KEY_LEN);
- return ret;
-}
-
-struct data_remaining {
- void __user *data;
- size_t out_len;
- size_t count;
-};
-
-static inline int use_data(struct data_remaining *data, size_t size)
-{
- if (data->out_len < size)
- return -EMSGSIZE;
- data->out_len -= size;
- data->data += size;
- ++data->count;
- return 0;
-}
-
-static int populate_ipmask(void *ctx, union nf_inet_addr ip, u8 cidr, int family)
-{
- int ret;
- struct data_remaining *data = ctx;
- void __user *uipmask = data->data;
- struct wgipmask out_ipmask;
-
- memset(&out_ipmask, 0, sizeof(struct wgipmask));
-
- ret = use_data(data, sizeof(struct wgipmask));
- if (ret)
- return ret;
-
- out_ipmask.cidr = cidr;
- out_ipmask.family = family;
- if (family == AF_INET)
- out_ipmask.ip4 = ip.in;
- else if (family == AF_INET6)
- out_ipmask.ip6 = ip.in6;
-
- if (copy_to_user(uipmask, &out_ipmask, sizeof(out_ipmask)))
- ret = -EFAULT;
-
- return ret;
-}
-
-static int populate_peer(struct wireguard_peer *peer, struct data_remaining *data)
-{
- int ret = 0;
- void __user *upeer = data->data;
- struct wgpeer out_peer;
- struct data_remaining ipmasks_data = { NULL };
-
- memset(&out_peer, 0, sizeof(struct wgpeer));
-
- ret = use_data(data, sizeof(struct wgpeer));
- if (ret)
- return ret;
-
- down_read(&peer->handshake.lock);
- memcpy(out_peer.public_key, peer->handshake.remote_static, NOISE_PUBLIC_KEY_LEN);
- memcpy(out_peer.preshared_key, peer->handshake.preshared_key, NOISE_SYMMETRIC_KEY_LEN);
- up_read(&peer->handshake.lock);
-
- read_lock_bh(&peer->endpoint_lock);
- if (peer->endpoint.addr.sa_family == AF_INET)
- out_peer.endpoint.addr4 = peer->endpoint.addr4;
- else if (peer->endpoint.addr.sa_family == AF_INET6)
- out_peer.endpoint.addr6 = peer->endpoint.addr6;
- read_unlock_bh(&peer->endpoint_lock);
- out_peer.last_handshake_time = peer->walltime_last_handshake;
- out_peer.tx_bytes = peer->tx_bytes;
- out_peer.rx_bytes = peer->rx_bytes;
- out_peer.persistent_keepalive_interval = (u16)(peer->persistent_keepalive_interval / HZ);
-
- ipmasks_data.out_len = data->out_len;
- ipmasks_data.data = data->data;
- ret = routing_table_walk_ips_by_peer_sleepable(&peer->device->peer_routing_table, &ipmasks_data, peer, populate_ipmask);
- if (ret)
- return ret;
- data->out_len = ipmasks_data.out_len;
- data->data = ipmasks_data.data;
- out_peer.num_ipmasks = ipmasks_data.count;
-
- if (copy_to_user(upeer, &out_peer, sizeof(out_peer)))
- ret = -EFAULT;
- return ret;
-}
-
-int config_get_device(struct wireguard_device *wg, void __user *user_device)
-{
- int ret;
- struct wireguard_peer *peer, *temp;
- struct data_remaining peer_data = { NULL };
- struct wgdevice out_device;
- struct wgdevice in_device;
-
- BUILD_BUG_ON(WG_KEY_LEN != NOISE_PUBLIC_KEY_LEN);
- BUILD_BUG_ON(WG_KEY_LEN != NOISE_SYMMETRIC_KEY_LEN);
-
- memset(&out_device, 0, sizeof(struct wgdevice));
-
- mutex_lock(&wg->device_update_lock);
-
- if (!user_device) {
- ret = peer_total_count(wg) * sizeof(struct wgpeer)
- + routing_table_count_nodes(&wg->peer_routing_table) * sizeof(struct wgipmask);
- goto out;
- }
-
- ret = -EFAULT;
- if (copy_from_user(&in_device, user_device, sizeof(in_device)))
- goto out;
-
- ret = -EPROTO;
- if (in_device.version_magic != WG_API_VERSION_MAGIC)
- goto out;
-
- out_device.version_magic = WG_API_VERSION_MAGIC;
- out_device.port = wg->incoming_port;
- out_device.fwmark = wg->fwmark;
- memcpy(out_device.interface, wg->dev->name, IFNAMSIZ);
-
- down_read(&wg->static_identity.lock);
- if (wg->static_identity.has_identity) {
- memcpy(out_device.private_key, wg->static_identity.static_private, WG_KEY_LEN);
- memcpy(out_device.public_key, wg->static_identity.static_public, WG_KEY_LEN);
- }
- up_read(&wg->static_identity.lock);
-
- peer_data.out_len = in_device.peers_size;
- peer_data.data = user_device + sizeof(struct wgdevice);
-
- ret = 0;
- peer_for_each (wg, peer, temp, false) {
- ret = populate_peer(peer, &peer_data);
- if (ret)
- break;
- }
- if (ret)
- goto out;
- out_device.num_peers = peer_data.count;
-
- ret = -EFAULT;
- if (copy_to_user(user_device, &out_device, sizeof(out_device)))
- goto out;
-
- ret = 0;
-
-out:
- mutex_unlock(&wg->device_update_lock);
- memzero_explicit(&out_device.private_key, NOISE_PUBLIC_KEY_LEN);
- return ret;
-}
diff --git a/src/config.h b/src/config.h
deleted file mode 100644
index 90912b6..0000000
--- a/src/config.h
+++ /dev/null
@@ -1,13 +0,0 @@
-/* Copyright (C) 2015-2017 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. */
-
-#ifndef WGCONFIG_H
-#define WGCONFIG_H
-
-#include <linux/compiler.h>
-
-struct wireguard_device;
-
-int config_get_device(struct wireguard_device *wg, void __user *udevice);
-int config_set_device(struct wireguard_device *wg, void __user *udevice);
-
-#endif
diff --git a/src/device.c b/src/device.c
index 1fa7784..1868027 100644
--- a/src/device.c
+++ b/src/device.c
@@ -4,10 +4,8 @@
#include "socket.h"
#include "timers.h"
#include "device.h"
-#include "config.h"
#include "ratelimiter.h"
#include "peer.h"
-#include "uapi.h"
#include "messages.h"
#include <linux/module.h>
@@ -192,28 +190,11 @@ err:
return ret;
}
-static int ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
-{
- struct wireguard_device *wg = netdev_priv(dev);
-
- if (!ns_capable(dev_net(dev)->user_ns, CAP_NET_ADMIN))
- return -EPERM;
-
- switch (cmd) {
- case WG_GET_DEVICE:
- return config_get_device(wg, ifr->ifr_ifru.ifru_data);
- case WG_SET_DEVICE:
- return config_set_device(wg, ifr->ifr_ifru.ifru_data);
- }
- return -EINVAL;
-}
-
static const struct net_device_ops netdev_ops = {
.ndo_open = open,
.ndo_stop = stop,
.ndo_start_xmit = xmit,
- .ndo_get_stats64 = ip_tunnel_get_stats64,
- .ndo_do_ioctl = ioctl
+ .ndo_get_stats64 = ip_tunnel_get_stats64
};
static void destruct(struct net_device *dev)
@@ -290,6 +271,7 @@ static int newlink(struct net *src_net, struct net_device *dev, struct nlattr *t
routing_table_init(&wg->peer_routing_table);
cookie_checker_init(&wg->cookie_checker, wg);
INIT_LIST_HEAD(&wg->peer_list);
+ wg->device_update_gen = 1;
dev->tstats = netdev_alloc_pcpu_stats(struct pcpu_sw_netstats);
if (!dev->tstats)
@@ -372,7 +354,7 @@ int __init device_init(void)
return rtnl_link_register(&link_ops);
}
-void __exit device_uninit(void)
+void device_uninit(void)
{
rtnl_link_unregister(&link_ops);
#ifdef CONFIG_PM_SLEEP
diff --git a/src/device.h b/src/device.h
index f7c8d40..4d77d58 100644
--- a/src/device.h
+++ b/src/device.h
@@ -50,6 +50,7 @@ struct wireguard_device {
struct routing_table peer_routing_table;
struct mutex device_update_lock, socket_update_lock;
struct list_head device_list, peer_list;
+ unsigned int device_update_gen;
u32 fwmark;
u16 incoming_port;
};
diff --git a/src/main.c b/src/main.c
index 5034d7b..7776d00 100644
--- a/src/main.c
+++ b/src/main.c
@@ -5,13 +5,16 @@
#include "noise.h"
#include "queueing.h"
#include "ratelimiter.h"
+#include "netlink.h"
#include "crypto/chacha20poly1305.h"
#include "crypto/blake2s.h"
#include "crypto/curve25519.h"
+#include "uapi/wireguard.h"
#include <linux/version.h>
#include <linux/init.h>
#include <linux/module.h>
+#include <linux/genetlink.h>
#include <net/rtnetlink.h>
static int __init mod_init(void)
@@ -35,11 +38,17 @@ static int __init mod_init(void)
if (ret < 0)
goto err_device;
+ ret = netlink_init();
+ if (ret < 0)
+ goto err_netlink;
+
pr_info("WireGuard " WIREGUARD_VERSION " loaded. See www.wireguard.com for information.\n");
pr_info("Copyright (C) 2015-2017 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.\n");
return 0;
+err_netlink:
+ device_uninit();
err_device:
crypt_ctx_cache_uninit();
err_packet:
@@ -48,6 +57,7 @@ err_packet:
static void __exit mod_exit(void)
{
+ netlink_uninit();
device_uninit();
crypt_ctx_cache_uninit();
pr_debug("WireGuard unloaded\n");
@@ -60,3 +70,4 @@ MODULE_DESCRIPTION("Fast, secure, and modern VPN tunnel");
MODULE_AUTHOR("Jason A. Donenfeld <Jason@zx2c4.com>");
MODULE_VERSION(WIREGUARD_VERSION);
MODULE_ALIAS_RTNL_LINK(KBUILD_MODNAME);
+MODULE_ALIAS_GENL_FAMILY(WG_GENL_NAME);
diff --git a/src/netlink.c b/src/netlink.c
new file mode 100644
index 0000000..84ca850
--- /dev/null
+++ b/src/netlink.c
@@ -0,0 +1,494 @@
+/* Copyright (C) 2015-2017 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. */
+
+#include "netlink.h"
+#include "device.h"
+#include "peer.h"
+#include "socket.h"
+#include "queueing.h"
+#include "messages.h"
+#include "uapi/wireguard.h"
+#include <linux/if.h>
+#include <net/genetlink.h>
+#include <net/sock.h>
+
+static struct genl_family genl_family;
+
+static const struct nla_policy device_policy[WGDEVICE_A_MAX + 1] = {
+ [WGDEVICE_A_IFINDEX] = { .type = NLA_U32 },
+ [WGDEVICE_A_IFNAME] = { .type = NLA_NUL_STRING, .len = IFNAMSIZ - 1 },
+ [WGDEVICE_A_PRIVATE_KEY]= { .len = NOISE_PUBLIC_KEY_LEN },
+ [WGDEVICE_A_PUBLIC_KEY] = { .len = NOISE_PUBLIC_KEY_LEN },
+ [WGDEVICE_A_FLAGS] = { .type = NLA_U32 },
+ [WGDEVICE_A_LISTEN_PORT]= { .type = NLA_U16 },
+ [WGDEVICE_A_FWMARK] = { .type = NLA_U32 },
+ [WGDEVICE_A_PEERS] = { .type = NLA_NESTED }
+};
+
+static const struct nla_policy peer_policy[WGPEER_A_MAX + 1] = {
+ [WGPEER_A_PUBLIC_KEY] = { .len = NOISE_PUBLIC_KEY_LEN },
+ [WGPEER_A_PRESHARED_KEY] = { .len = NOISE_SYMMETRIC_KEY_LEN },
+ [WGPEER_A_FLAGS] = { .type = NLA_U32 },
+ [WGPEER_A_ENDPOINT] = { .len = sizeof(struct sockaddr) },
+ [WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL]= { .type = NLA_U16 },
+ [WGPEER_A_LAST_HANDSHAKE_TIME] = { .len = sizeof(struct timeval) },
+ [WGPEER_A_RX_BYTES] = { .type = NLA_U64 },
+ [WGPEER_A_TX_BYTES] = { .type = NLA_U64 },
+ [WGPEER_A_ALLOWEDIPS] = { .type = NLA_NESTED }
+};
+
+static const struct nla_policy allowedip_policy[WGALLOWEDIP_A_MAX + 1] = {
+ [WGALLOWEDIP_A_FAMILY] = { .type = NLA_U16 },
+ [WGALLOWEDIP_A_IPADDR] = { .len = sizeof(struct in_addr) },
+ [WGALLOWEDIP_A_CIDR_MASK] = { .type = NLA_U8 }
+};
+
+static struct wireguard_device *lookup_interface(struct nlattr **attrs, struct sk_buff *skb)
+{
+ struct net_device *dev = NULL;
+
+ if (!attrs[WGDEVICE_A_IFINDEX] == !attrs[WGDEVICE_A_IFNAME])
+ return ERR_PTR(-EBADR);
+ if (attrs[WGDEVICE_A_IFINDEX])
+ dev = dev_get_by_index(sock_net(skb->sk), nla_get_u32(attrs[WGDEVICE_A_IFINDEX]));
+ else if (attrs[WGDEVICE_A_IFNAME])
+ dev = dev_get_by_name(sock_net(skb->sk), nla_data(attrs[WGDEVICE_A_IFNAME]));
+ if (!dev)
+ return ERR_PTR(-ENODEV);
+ if (!dev->rtnl_link_ops || !dev->rtnl_link_ops->kind || strcmp(dev->rtnl_link_ops->kind, KBUILD_MODNAME)) {
+ dev_put(dev);
+ return ERR_PTR(-EOPNOTSUPP);
+ }
+ return netdev_priv(dev);
+}
+
+struct allowedips_ctx {
+ struct sk_buff *skb;
+ unsigned int idx_cursor, idx;
+};
+
+static int get_allowedips(void *ctx, union nf_inet_addr ip, u8 cidr, int family)
+{
+ struct nlattr *allowedip_nest;
+ struct allowedips_ctx *actx = ctx;
+ if (++actx->idx < actx->idx_cursor)
+ return 0;
+ allowedip_nest = nla_nest_start(actx->skb, actx->idx - 1);
+ if (!allowedip_nest)
+ return -EMSGSIZE;
+
+ if (nla_put_u8(actx->skb, WGALLOWEDIP_A_CIDR_MASK, cidr) || nla_put_u16(actx->skb, WGALLOWEDIP_A_FAMILY, family) ||
+ nla_put(actx->skb, WGALLOWEDIP_A_IPADDR, family == AF_INET6 ? sizeof(struct in6_addr) : sizeof(struct in_addr), &ip)) {
+ nla_nest_cancel(actx->skb, allowedip_nest);
+ return -EMSGSIZE;
+ }
+
+ nla_nest_end(actx->skb, allowedip_nest);
+ return 0;
+}
+
+static int get_peer(struct wireguard_peer *peer, unsigned int index, unsigned int *allowedips_idx_cursor, struct sk_buff *skb)
+{
+ struct allowedips_ctx ctx = { .skb = skb, .idx_cursor = *allowedips_idx_cursor };
+ struct nlattr *allowedips_nest, *peer_nest = nla_nest_start(skb, index);
+ bool fail;
+ if (!peer_nest)
+ return -EMSGSIZE;
+
+ down_read(&peer->handshake.lock);
+ fail = nla_put(skb, WGPEER_A_PUBLIC_KEY, NOISE_PUBLIC_KEY_LEN, peer->handshake.remote_static);
+ up_read(&peer->handshake.lock);
+ if (fail)
+ goto err;
+
+ if (!ctx.idx_cursor) {
+ down_read(&peer->handshake.lock);
+ fail = nla_put(skb, WGPEER_A_PRESHARED_KEY, NOISE_SYMMETRIC_KEY_LEN, peer->handshake.preshared_key);
+ up_read(&peer->handshake.lock);
+ if (fail)
+ goto err;
+
+ if (nla_put(skb, WGPEER_A_LAST_HANDSHAKE_TIME, sizeof(struct timeval), &peer->walltime_last_handshake) || nla_put_u16(skb, WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL, peer->persistent_keepalive_interval / HZ) ||
+ nla_put_u64_64bit(skb, WGPEER_A_TX_BYTES, peer->tx_bytes, WGPEER_A_UNSPEC) || nla_put_u64_64bit(skb, WGPEER_A_RX_BYTES, peer->rx_bytes, WGPEER_A_UNSPEC))
+ goto err;
+
+ read_lock_bh(&peer->endpoint_lock);
+ if (peer->endpoint.addr.sa_family == AF_INET)
+ fail = nla_put(skb, WGPEER_A_ENDPOINT, sizeof(struct sockaddr_in), &peer->endpoint.addr4);
+ else if (peer->endpoint.addr.sa_family == AF_INET6)
+ fail = nla_put(skb, WGPEER_A_ENDPOINT, sizeof(struct sockaddr_in6), &peer->endpoint.addr6);
+ read_unlock_bh(&peer->endpoint_lock);
+ if (fail)
+ goto err;
+ }
+
+ allowedips_nest = nla_nest_start(skb, WGPEER_A_ALLOWEDIPS);
+ if (!allowedips_nest)
+ goto err;
+ if (routing_table_walk_ips_by_peer_sleepable(&peer->device->peer_routing_table, &ctx, peer, get_allowedips)) {
+ *allowedips_idx_cursor = ctx.idx;
+ nla_nest_end(skb, allowedips_nest);
+ nla_nest_end(skb, peer_nest);
+ return -EMSGSIZE;
+ }
+ *allowedips_idx_cursor = 0;
+ nla_nest_end(skb, allowedips_nest);
+ nla_nest_end(skb, peer_nest);
+ return 0;
+err:
+ nla_nest_cancel(skb, peer_nest);
+ return -EMSGSIZE;
+}
+
+static int get_start(struct netlink_callback *cb)
+{
+ struct wireguard_device *wg;
+ struct nlattr **attrs = genl_family_attrbuf(&genl_family);
+ int ret = nlmsg_parse(cb->nlh, GENL_HDRLEN + genl_family.hdrsize, attrs, genl_family.maxattr, device_policy, NULL);
+ if (ret < 0)
+ return ret;
+ wg = lookup_interface(attrs, cb->skb);
+ if (IS_ERR(wg))
+ return PTR_ERR(wg);
+ cb->args[0] = (long)wg;
+ return 0;
+}
+
+static int get(struct sk_buff *skb, struct netlink_callback *cb)
+{
+ struct wireguard_device *wg = (struct wireguard_device *)cb->args[0];
+ struct wireguard_peer *peer, *next_peer_cursor = NULL, *last_peer_cursor = (struct wireguard_peer *)cb->args[1];
+ unsigned int peer_idx = 0, allowedips_idx_cursor = (unsigned int)cb->args[2];
+ struct nlattr *peers_nest;
+ bool done = true;
+ void *hdr;
+ int ret = -EMSGSIZE;
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0)
+ if (!wg) {
+ ret = get_start(cb);
+ if (ret)
+ return ret;
+ return get(skb, cb);
+ }
+#endif
+
+ rtnl_lock();
+ mutex_lock(&wg->device_update_lock);
+ cb->seq = wg->device_update_gen;
+
+ hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq, &genl_family, NLM_F_MULTI, WG_CMD_GET_DEVICE);
+ if (!hdr)
+ goto out;
+ genl_dump_check_consistent(cb, hdr, &genl_family);
+
+ if (!last_peer_cursor) {
+ if (nla_put_u16(skb, WGDEVICE_A_LISTEN_PORT, wg->incoming_port) || nla_put_u32(skb, WGDEVICE_A_FWMARK, wg->fwmark) || nla_put_u32(skb, WGDEVICE_A_IFINDEX, wg->dev->ifindex) || nla_put_string(skb, WGDEVICE_A_IFNAME, wg->dev->name))
+ goto out;
+
+ down_read(&wg->static_identity.lock);
+ if (wg->static_identity.has_identity) {
+ if (nla_put(skb, WGDEVICE_A_PRIVATE_KEY, NOISE_PUBLIC_KEY_LEN, wg->static_identity.static_private) || nla_put(skb, WGDEVICE_A_PUBLIC_KEY, NOISE_PUBLIC_KEY_LEN, wg->static_identity.static_public)) {
+ up_read(&wg->static_identity.lock);
+ goto out;
+ }
+ }
+ up_read(&wg->static_identity.lock);
+ }
+
+ peers_nest = nla_nest_start(skb, WGDEVICE_A_PEERS);
+ if (!peers_nest)
+ goto out;
+ ret = 0;
+ /* If the last cursor was removed via list_del_init in peer_remove, then we just treat
+ * this the same as there being no more peers left. The reason is that seq_nr should
+ * indicate to userspace that this isn't a coherent dump anyway, so they'll try again. */
+ if (list_empty(&wg->peer_list) || (last_peer_cursor && list_empty(&last_peer_cursor->peer_list))) {
+ nla_nest_cancel(skb, peers_nest);
+ goto out;
+ }
+ lockdep_assert_held(&wg->device_update_lock);
+ peer = list_prepare_entry(last_peer_cursor, &wg->peer_list, peer_list);
+ list_for_each_entry_continue (peer, &wg->peer_list, peer_list) {
+ if (get_peer(peer, peer_idx++, &allowedips_idx_cursor, skb)) {
+ done = false;
+ break;
+ }
+ next_peer_cursor = peer;
+ }
+ nla_nest_end(skb, peers_nest);
+
+out:
+ peer_put(last_peer_cursor);
+ if (!ret && !done)
+ next_peer_cursor = peer_rcu_get(next_peer_cursor);
+ mutex_unlock(&wg->device_update_lock);
+ rtnl_unlock();
+
+ if (ret) {
+ genlmsg_cancel(skb, hdr);
+ return ret;
+ }
+ genlmsg_end(skb, hdr);
+ if (done) {
+ cb->args[1] = cb->args[2] = 0;
+ return 0;
+ }
+ cb->args[1] = (long)next_peer_cursor;
+ cb->args[2] = (long)allowedips_idx_cursor;
+ return skb->len;
+
+ /* At this point, we can't really deal ourselves with safely zeroing out
+ * the private key material after usage. This will need an additional API
+ * in the kernel for marking skbs as zero_on_free. */
+}
+
+static int get_done(struct netlink_callback *cb)
+{
+ struct wireguard_device *wg = (struct wireguard_device *)cb->args[0];
+ struct wireguard_peer *peer = (struct wireguard_peer *)cb->args[1];
+ if (wg)
+ dev_put(wg->dev);
+ peer_put(peer);
+ return 0;
+}
+
+static int set_device_port(struct wireguard_device *wg, u16 port)
+{
+ struct wireguard_peer *peer, *temp;
+ if (wg->incoming_port == port)
+ return 0;
+ socket_uninit(wg);
+ wg->incoming_port = port;
+ peer_for_each (wg, peer, temp, false)
+ socket_clear_peer_endpoint_src(peer);
+ if (!netif_running(wg->dev))
+ return 0;
+ return socket_init(wg);
+}
+
+static int set_allowedip(struct wireguard_peer *peer, struct nlattr **attrs)
+{
+ int ret = -EINVAL;
+ u16 family;
+ u8 cidr;
+
+ if (!attrs[WGALLOWEDIP_A_FAMILY] || !attrs[WGALLOWEDIP_A_IPADDR] || !attrs[WGALLOWEDIP_A_CIDR_MASK])
+ return ret;
+ family = nla_get_u16(attrs[WGALLOWEDIP_A_FAMILY]);
+ cidr = nla_get_u8(attrs[WGALLOWEDIP_A_CIDR_MASK]);
+
+ if (family == AF_INET && cidr <= 32 && nla_len(attrs[WGALLOWEDIP_A_IPADDR]) == sizeof(struct in_addr))
+ ret = routing_table_insert_v4(&peer->device->peer_routing_table, nla_data(attrs[WGALLOWEDIP_A_IPADDR]), cidr, peer);
+ else if (family == AF_INET6 && cidr <= 128 && nla_len(attrs[WGALLOWEDIP_A_IPADDR]) == sizeof(struct in6_addr))
+ ret = routing_table_insert_v6(&peer->device->peer_routing_table, nla_data(attrs[WGALLOWEDIP_A_IPADDR]), cidr, peer);
+
+ return ret;
+}
+
+static int set_peer(struct wireguard_device *wg, struct nlattr **attrs)
+{
+ int ret;
+ u32 flags = 0;
+ struct wireguard_peer *peer = NULL;
+ u8 *public_key = NULL, *preshared_key = NULL;
+
+ ret = -EINVAL;
+ if (attrs[WGPEER_A_PUBLIC_KEY] && nla_len(attrs[WGPEER_A_PUBLIC_KEY]) == NOISE_PUBLIC_KEY_LEN)
+ public_key = nla_data(attrs[WGPEER_A_PUBLIC_KEY]);
+ else
+ goto out;
+ if (attrs[WGPEER_A_PRESHARED_KEY] && nla_len(attrs[WGPEER_A_PRESHARED_KEY]) == NOISE_SYMMETRIC_KEY_LEN)
+ preshared_key = nla_data(attrs[WGPEER_A_PRESHARED_KEY]);
+ if (attrs[WGPEER_A_FLAGS])
+ flags = nla_get_u32(attrs[WGPEER_A_FLAGS]);
+
+ peer = pubkey_hashtable_lookup(&wg->peer_hashtable, nla_data(attrs[WGPEER_A_PUBLIC_KEY]));
+ if (!peer) { /* Peer doesn't exist yet. Add a new one. */
+ ret = -ENODEV;
+ if (flags & WGPEER_F_REMOVE_ME)
+ goto out; /* Tried to remove a non-existing peer. */
+
+ down_read(&wg->static_identity.lock);
+ if (wg->static_identity.has_identity && !memcmp(nla_data(attrs[WGPEER_A_PUBLIC_KEY]), wg->static_identity.static_public, NOISE_PUBLIC_KEY_LEN)) {
+ /* We silently ignore peers that have the same public key as the device. The reason we do it silently
+ * is that we'd like for people to be able to reuse the same set of API calls across peers. */
+ up_read(&wg->static_identity.lock);
+ ret = 0;
+ goto out;
+ }
+ up_read(&wg->static_identity.lock);
+
+ ret = -ENOMEM;
+ peer = peer_rcu_get(peer_create(wg, public_key, preshared_key));
+ if (!peer)
+ goto out;
+ }
+
+ ret = 0;
+ if (flags & WGPEER_F_REMOVE_ME) {
+ peer_remove(peer);
+ goto out;
+ }
+
+ if (preshared_key) {
+ down_write(&peer->handshake.lock);
+ memcpy(&peer->handshake.preshared_key, preshared_key, NOISE_SYMMETRIC_KEY_LEN);
+ up_write(&peer->handshake.lock);
+ }
+
+ if (attrs[WGPEER_A_ENDPOINT]) {
+ struct sockaddr *addr = nla_data(attrs[WGPEER_A_ENDPOINT]);
+ size_t len = nla_len(attrs[WGPEER_A_ENDPOINT]);
+ if ((len == sizeof(struct sockaddr_in) && addr->sa_family == AF_INET) || (len == sizeof(struct sockaddr_in6) && addr->sa_family == AF_INET6)) {
+ struct endpoint endpoint = { { { 0 } } };
+ memcpy(&endpoint.addr, addr, len);
+ socket_set_peer_endpoint(peer, &endpoint);
+ }
+ }
+
+ if (flags & WGPEER_F_REPLACE_ALLOWEDIPS)
+ routing_table_remove_by_peer(&wg->peer_routing_table, peer);
+
+ if (attrs[WGPEER_A_ALLOWEDIPS]) {
+ int rem;
+ struct nlattr *attr, *allowedip[WGALLOWEDIP_A_MAX + 1];
+ nla_for_each_nested (attr, attrs[WGPEER_A_ALLOWEDIPS], rem) {
+ ret = nla_parse_nested(allowedip, WGALLOWEDIP_A_MAX, attr, allowedip_policy, NULL);
+ if (ret < 0)
+ goto out;
+ ret = set_allowedip(peer, allowedip);
+ if (ret < 0)
+ goto out;
+ }
+ }
+
+ if (attrs[WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL]) {
+ const u16 persistent_keepalive_interval = nla_get_u16(attrs[WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL]);
+ const bool send_keepalive = !peer->persistent_keepalive_interval && persistent_keepalive_interval && netif_running(wg->dev);
+ peer->persistent_keepalive_interval = (unsigned long)persistent_keepalive_interval * HZ;
+ if (send_keepalive)
+ packet_send_keepalive(peer);
+ }
+
+ if (netif_running(wg->dev))
+ packet_send_staged_packets(peer);
+
+out:
+ peer_put(peer);
+ if (attrs[WGPEER_A_PRESHARED_KEY])
+ memzero_explicit(nla_data(attrs[WGPEER_A_PRESHARED_KEY]), nla_len(attrs[WGPEER_A_PRESHARED_KEY]));
+ return ret;
+}
+
+static int set(struct sk_buff *skb, struct genl_info *info)
+{
+ int ret;
+ struct wireguard_device *wg = lookup_interface(info->attrs, skb);
+ if (IS_ERR(wg)) {
+ ret = PTR_ERR(wg);
+ goto out_nodev;
+ }
+
+ rtnl_lock();
+ mutex_lock(&wg->device_update_lock);
+ ++wg->device_update_gen;
+
+ if (info->attrs[WGDEVICE_A_FWMARK]) {
+ struct wireguard_peer *peer, *temp;
+ wg->fwmark = nla_get_u32(info->attrs[WGDEVICE_A_FWMARK]);
+ peer_for_each (wg, peer, temp, false)
+ socket_clear_peer_endpoint_src(peer);
+ }
+
+ if (info->attrs[WGDEVICE_A_LISTEN_PORT]) {
+ ret = set_device_port(wg, nla_get_u16(info->attrs[WGDEVICE_A_LISTEN_PORT]));
+ if (ret)
+ goto out;
+ }
+
+ if (info->attrs[WGDEVICE_A_FLAGS] && nla_get_u32(info->attrs[WGDEVICE_A_FLAGS]) & WGDEVICE_F_REPLACE_PEERS)
+ peer_remove_all(wg);
+
+ if (info->attrs[WGDEVICE_A_PRIVATE_KEY] && nla_len(info->attrs[WGDEVICE_A_PRIVATE_KEY]) == NOISE_PUBLIC_KEY_LEN) {
+ struct wireguard_peer *peer, *temp;
+ u8 public_key[NOISE_PUBLIC_KEY_LEN] = { 0 }, *private_key = nla_data(info->attrs[WGDEVICE_A_PRIVATE_KEY]);
+ /* We remove before setting, to prevent race, which means doing two 25519-genpub ops. */
+ bool unused __attribute((unused)) = curve25519_generate_public(public_key, private_key);
+ peer = pubkey_hashtable_lookup(&wg->peer_hashtable, public_key);
+ if (peer) {
+ peer_put(peer);
+ peer_remove(peer);
+ }
+ noise_set_static_identity_private_key(&wg->static_identity, private_key);
+ peer_for_each (wg, peer, temp, false) {
+ if (!noise_precompute_static_static(peer))
+ peer_remove(peer);
+ }
+ cookie_checker_precompute_device_keys(&wg->cookie_checker);
+ }
+
+ if (info->attrs[WGDEVICE_A_PEERS]) {
+ int rem;
+ struct nlattr *attr, *peer[WGPEER_A_MAX + 1];
+ nla_for_each_nested (attr, info->attrs[WGDEVICE_A_PEERS], rem) {
+ ret = nla_parse_nested(peer, WGPEER_A_MAX, attr, peer_policy, NULL);
+ if (ret < 0)
+ goto out;
+ ret = set_peer(wg, peer);
+ if (ret < 0)
+ goto out;
+ }
+ }
+ ret = 0;
+
+out:
+ mutex_unlock(&wg->device_update_lock);
+ rtnl_unlock();
+ dev_put(wg->dev);
+out_nodev:
+ if (info->attrs[WGDEVICE_A_PRIVATE_KEY])
+ memzero_explicit(nla_data(info->attrs[WGDEVICE_A_PRIVATE_KEY]), nla_len(info->attrs[WGDEVICE_A_PRIVATE_KEY]));
+ return ret;
+}
+
+static const struct genl_ops genl_ops[] = {
+ {
+ .cmd = WG_CMD_GET_DEVICE,
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0)
+ .start = get_start,
+#endif
+ .dumpit = get,
+ .done = get_done,
+ .policy = device_policy,
+ .flags = GENL_UNS_ADMIN_PERM
+ }, {
+ .cmd = WG_CMD_SET_DEVICE,
+ .doit = set,
+ .policy = device_policy,
+ .flags = GENL_UNS_ADMIN_PERM
+ }
+};
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)
+static struct genl_family genl_family __ro_after_init = {
+ .ops = genl_ops,
+ .n_ops = ARRAY_SIZE(genl_ops),
+#else
+static struct genl_family genl_family = {
+#endif
+ .name = WG_GENL_NAME,
+ .version = WG_GENL_VERSION,
+ .maxattr = WGDEVICE_A_MAX,
+ .module = THIS_MODULE,
+ .netnsok = true
+};
+
+int __init netlink_init(void)
+{
+ return genl_register_family(&genl_family);
+}
+
+void __exit netlink_uninit(void)
+{
+ genl_unregister_family(&genl_family);
+}
diff --git a/src/netlink.h b/src/netlink.h
new file mode 100644
index 0000000..debf42c
--- /dev/null
+++ b/src/netlink.h
@@ -0,0 +1,9 @@
+/* Copyright (C) 2015-2017 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. */
+
+#ifndef NETLINK_H
+#define NETLINK_H
+
+int netlink_init(void);
+void netlink_uninit(void);
+
+#endif
diff --git a/src/noise.c b/src/noise.c
index 3b02148..531306b 100644
--- a/src/noise.c
+++ b/src/noise.c
@@ -53,7 +53,8 @@ bool noise_handshake_init(struct noise_handshake *handshake, struct noise_static
handshake->entry.type = INDEX_HASHTABLE_HANDSHAKE;
handshake->entry.peer = peer;
memcpy(handshake->remote_static, peer_public_key, NOISE_PUBLIC_KEY_LEN);
- memcpy(handshake->preshared_key, peer_preshared_key, NOISE_SYMMETRIC_KEY_LEN);
+ if (peer_preshared_key)
+ memcpy(handshake->preshared_key, peer_preshared_key, NOISE_SYMMETRIC_KEY_LEN);
handshake->static_identity = static_identity;
handshake->state = HANDSHAKE_ZEROED;
return noise_precompute_static_static(peer);
@@ -203,14 +204,8 @@ bool noise_received_with_keypair(struct noise_keypairs *keypairs, struct noise_k
void noise_set_static_identity_private_key(struct noise_static_identity *static_identity, const u8 private_key[NOISE_PUBLIC_KEY_LEN])
{
down_write(&static_identity->lock);
- if (private_key) {
- memcpy(static_identity->static_private, private_key, NOISE_PUBLIC_KEY_LEN);
- static_identity->has_identity = curve25519_generate_public(static_identity->static_public, private_key);
- } else {
- memset(static_identity->static_private, 0, NOISE_PUBLIC_KEY_LEN);
- memset(static_identity->static_public, 0, NOISE_PUBLIC_KEY_LEN);
- static_identity->has_identity = false;
- }
+ memcpy(static_identity->static_private, private_key, NOISE_PUBLIC_KEY_LEN);
+ static_identity->has_identity = curve25519_generate_public(static_identity->static_public, private_key);
up_write(&static_identity->lock);
}
diff --git a/src/peer.c b/src/peer.c
index 01b12fa..0bc1612 100644
--- a/src/peer.c
+++ b/src/peer.c
@@ -80,7 +80,7 @@ void peer_remove(struct wireguard_peer *peer)
lockdep_assert_held(&peer->device->device_update_lock);
noise_handshake_clear(&peer->handshake);
noise_keypairs_clear(&peer->keypairs);
- list_del(&peer->peer_list);
+ list_del_init(&peer->peer_list);
timers_stop(peer);
routing_table_remove_by_peer(&peer->device->peer_routing_table, peer);
pubkey_hashtable_remove(&peer->device->peer_hashtable, peer);
diff --git a/src/tests/netns.sh b/src/tests/netns.sh
index 6f5ea8f..a11fb3f 100755
--- a/src/tests/netns.sh
+++ b/src/tests/netns.sh
@@ -192,7 +192,7 @@ exec 4< <(n1 ncat -l -u -p 1111)
nmap_pid=$!
waitncatudp $netns1
n2 ncat -u 192.168.241.1 1111 <<<"X"
-! read -r -N 1 -t 1 out <&4
+! read -r -N 1 -t 1 out <&4 || false
kill $nmap_pid
n1 wg set wg0 peer "$more_specific_key" remove
[[ $(n1 wg show wg0 endpoints) == "$pub2 [::1]:9997" ]]
@@ -369,3 +369,40 @@ ip1 link del veth1
ip1 link del veth3
ip1 link del wg0
ip2 link del wg0
+
+# We test that Netlink/IPC is working properly by doing things that usually cause split responses
+ip0 link add dev wg0 type wireguard
+config=( "[Interface]" "PrivateKey=$(wg genkey)" "[Peer]" "PublicKey=$(wg genkey)" )
+for a in {1..255}; do
+ for b in {0..255}; do
+ config+=( "AllowedIPs=$a.$b.0.0/16" )
+ done
+done
+n0 wg setconf wg0 <(printf '%s\n' "${config[@]}")
+i=0
+for ip in $(n0 wg show wg0 allowed-ips); do
+ ((++i))
+done
+((i == 65281))
+ip0 link del wg0
+ip0 link add dev wg0 type wireguard
+config=( "[Interface]" "PrivateKey=$(wg genkey)" )
+for a in {1..40}; do
+ config+=( "[Peer]" "PublicKey=$(wg genkey)" )
+ for b in {1..52}; do
+ config+=( "AllowedIPs=$a.$b.0.0/16" )
+ done
+done
+n0 wg setconf wg0 <(printf '%s\n' "${config[@]}")
+i=0
+while read -r line; do
+ j=0
+ for ip in $line; do
+ ((++j))
+ done
+ ((j == 53))
+ ((++i))
+done < <(n0 wg show wg0 allowed-ips)
+((i == 40))
+ip0 link del wg0
+! n0 wg show doesnotexist || false
diff --git a/src/tests/qemu/Makefile b/src/tests/qemu/Makefile
index 05081cd..225bb1c 100644
--- a/src/tests/qemu/Makefile
+++ b/src/tests/qemu/Makefile
@@ -1,7 +1,7 @@
PWD := $(shell pwd)
# Set these from the environment to override
-KERNEL_VERSION ?= 4.11.9
+KERNEL_VERSION ?= 4.13.4
BUILD_PATH ?= $(PWD)/../../../qemu-build
DISTFILES_PATH ?= $(PWD)/distfiles
DEBUG_KERNEL ?= no
@@ -15,7 +15,7 @@ MIRROR := https://download.wireguard.com/qemu-test/distfiles/
CHOST := $(shell gcc -dumpmachine)
ARCH := $(shell uname -m)
WIREGUARD_SOURCES := $(wildcard ../../*.c ../../*.h ../../selftest/*.h ../../crypto/*.c ../../crypto/*.h ../../crypto/*.S ../../compat/*.h)
-TOOLS_SOURCES := $(wildcard ../../tools/*.c ../../tools*.h ../../uapi.h)
+TOOLS_SOURCES := $(wildcard ../../tools/*.c ../../tools/*.h ../../uapi/*.h)
default: qemu
@@ -73,7 +73,7 @@ qemu: $(KERNEL_BZIMAGE)
$(QEMU_MACHINE) \
-cpu host \
-smp $(NR_CPUS) \
- -m 96M \
+ -m 192M \
-object rng-random,id=rng0,filename=/dev/urandom \
-device virtio-rng-pci,rng=rng0 \
-device virtio-serial,max_ports=2 \
@@ -164,7 +164,7 @@ $(LIBMNL_PATH)/src/.libs/libmnl.a: $(LIBMNL_PATH)/.installed $(MUSL_CC)
$(MAKE) -C $(LIBMNL_PATH)
$(BUILD_PATH)/tools/wg: $(MUSL_CC) $(TOOLS_SOURCES) $(LIBMNL_PATH)/src/.libs/libmnl.a | $(BUILD_PATH)/include/linux/.installed
- cp -pr ../../uapi.h ../../tools $(BUILD_PATH)/
+ cp -pr ../../uapi ../../tools $(BUILD_PATH)/
$(MAKE) -C $(BUILD_PATH)/tools clean
CC="$(MUSL_CC)" CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS) -L$(LIBMNL_PATH)/src/.libs" $(MAKE) -C $(BUILD_PATH)/tools LIBMNL_CFLAGS="-I$(LIBMNL_PATH)/include" LIBMNL_LDLIBS="-lmnl" wg
strip -s $@
diff --git a/src/tools/config.c b/src/tools/config.c
index af74bda..0d8daeb 100644
--- a/src/tools/config.c
+++ b/src/tools/config.c
@@ -1,6 +1,5 @@
/* Copyright (C) 2015-2017 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. */
-
#include <arpa/inet.h>
#include <limits.h>
#include <ctype.h>
@@ -14,36 +13,12 @@
#include <errno.h>
#include "config.h"
+#include "containers.h"
#include "ipc.h"
#include "encoding.h"
#define COMMENT_CHAR '#'
-#define max(a, b) (a > b ? a : b)
-
-static inline struct wgpeer *peer_from_offset(struct wgdevice *dev, size_t offset)
-{
- return (struct wgpeer *)((uint8_t *)dev + sizeof(struct wgdevice) + offset);
-}
-
-static int use_space(struct inflatable_device *buf, size_t space)
-{
- size_t expand_to;
- uint8_t *new_dev;
-
- if (buf->len - buf->pos < space) {
- expand_to = max(buf->len * 2, buf->len + space);
- new_dev = realloc(buf->dev, expand_to + sizeof(struct wgdevice));
- if (!new_dev)
- return -errno;
- memset(&new_dev[buf->len + sizeof(struct wgdevice)], 0, expand_to - buf->len);
- buf->dev = (struct wgdevice *)new_dev;
- buf->len = expand_to;
- }
- buf->pos += space;
- return 0;
-}
-
static const char *get_value(const char *line, const char *key)
{
size_t linelen = strlen(line);
@@ -58,10 +33,9 @@ static const char *get_value(const char *line, const char *key)
return line + keylen;
}
-static inline uint16_t parse_port(const char *value)
+static inline bool parse_port(uint16_t *port, uint32_t *flags, const char *value)
{
int ret;
- uint16_t port = 0;
struct addrinfo *resolved;
struct addrinfo hints = {
.ai_family = AF_UNSPEC,
@@ -72,27 +46,32 @@ static inline uint16_t parse_port(const char *value)
if (!strlen(value)) {
fprintf(stderr, "Unable to parse empty port\n");
- return 0;
+ return false;
}
ret = getaddrinfo(NULL, value, &hints, &resolved);
- if (ret != 0) {
+ if (ret) {
fprintf(stderr, "%s: `%s`\n", gai_strerror(ret), value);
- return 0;
+ return false;
}
- if (resolved->ai_family == AF_INET && resolved->ai_addrlen == sizeof(struct sockaddr_in))
- port = ntohs(((struct sockaddr_in *)resolved->ai_addr)->sin_port);
- else if (resolved->ai_family == AF_INET6 && resolved->ai_addrlen == sizeof(struct sockaddr_in6))
- port = ntohs(((struct sockaddr_in6 *)resolved->ai_addr)->sin6_port);
- else
+ ret = -1;
+ if (resolved->ai_family == AF_INET && resolved->ai_addrlen == sizeof(struct sockaddr_in)) {
+ *port = ntohs(((struct sockaddr_in *)resolved->ai_addr)->sin_port);
+ ret = 0;
+ } else if (resolved->ai_family == AF_INET6 && resolved->ai_addrlen == sizeof(struct sockaddr_in6)) {
+ *port = ntohs(((struct sockaddr_in6 *)resolved->ai_addr)->sin6_port);
+ ret = 0;
+ } else
fprintf(stderr, "Neither IPv4 nor IPv6 address found: `%s`\n", value);
freeaddrinfo(resolved);
- return port;
+ if (!ret)
+ *flags |= WGDEVICE_HAS_LISTEN_PORT;
+ return ret == 0;
}
-static inline bool parse_fwmark(uint32_t *fwmark, unsigned int *flags, const char *value)
+static inline bool parse_fwmark(uint32_t *fwmark, uint32_t *flags, const char *value)
{
unsigned long ret;
char *end;
@@ -100,7 +79,7 @@ static inline bool parse_fwmark(uint32_t *fwmark, unsigned int *flags, const cha
if (!strcasecmp(value, "off")) {
*fwmark = 0;
- *flags |= WGDEVICE_REMOVE_FWMARK;
+ *flags |= WGDEVICE_HAS_FWMARK;
return true;
}
@@ -112,8 +91,7 @@ static inline bool parse_fwmark(uint32_t *fwmark, unsigned int *flags, const cha
if (!*value || *end || ret > UINT32_MAX)
return false;
*fwmark = ret;
- if (!ret)
- *flags |= WGDEVICE_REMOVE_FWMARK;
+ *flags |= WGDEVICE_HAS_FWMARK;
return true;
}
@@ -126,17 +104,17 @@ static inline bool parse_key(uint8_t key[static WG_KEY_LEN], const char *value)
return true;
}
-static inline bool parse_ip(struct wgipmask *ipmask, const char *value)
+static inline bool parse_ip(struct wgallowedip *allowedip, const char *value)
{
- ipmask->family = AF_UNSPEC;
+ allowedip->family = AF_UNSPEC;
if (strchr(value, ':')) {
- if (inet_pton(AF_INET6, value, &ipmask->ip6) == 1)
- ipmask->family = AF_INET6;
+ if (inet_pton(AF_INET6, value, &allowedip->ip6) == 1)
+ allowedip->family = AF_INET6;
} else {
- if (inet_pton(AF_INET, value, &ipmask->ip4) == 1)
- ipmask->family = AF_INET;
+ if (inet_pton(AF_INET, value, &allowedip->ip4) == 1)
+ allowedip->family = AF_INET;
}
- if (ipmask->family == AF_UNSPEC) {
+ if (allowedip->family == AF_UNSPEC) {
fprintf(stderr, "Unable to parse IP address: `%s`\n", value);
return false;
}
@@ -215,13 +193,14 @@ static inline bool parse_endpoint(struct sockaddr *endpoint, const char *value)
return true;
}
-static inline bool parse_persistent_keepalive(__u16 *interval, const char *value)
+static inline bool parse_persistent_keepalive(uint16_t *interval, uint32_t *flags, const char *value)
{
unsigned long ret;
char *end;
if (!strcasecmp(value, "off")) {
*interval = 0;
+ *flags |= WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL;
return true;
}
@@ -231,22 +210,21 @@ static inline bool parse_persistent_keepalive(__u16 *interval, const char *value
return false;
}
- *interval = (__u16)ret;
+ *interval = (uint16_t)ret;
+ *flags |= WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL;
return true;
}
-static inline bool parse_ipmasks(struct inflatable_device *buf, size_t peer_offset, const char *value)
+static inline bool parse_allowedips(struct wgpeer *peer, struct wgallowedip **last_allowedip, const char *value)
{
- struct wgpeer *peer;
- struct wgipmask *ipmask;
+ struct wgallowedip *allowedip = *last_allowedip, *new_allowedip;
char *mask, *mutable = strdup(value), *sep;
if (!mutable) {
perror("strdup");
return false;
};
- peer = peer_from_offset(buf->dev, peer_offset);
- peer->flags |= WGPEER_REPLACE_IPMASKS;
+ peer->flags |= WGPEER_REPLACE_ALLOWEDIPS;
if (!strlen(value)) {
free(mutable);
return true;
@@ -255,15 +233,19 @@ static inline bool parse_ipmasks(struct inflatable_device *buf, size_t peer_offs
while ((mask = strsep(&sep, ","))) {
unsigned long cidr = ULONG_MAX;
char *end, *ip = strsep(&mask, "/");
- if (use_space(buf, sizeof(struct wgipmask)) < 0) {
- perror("use_space");
+ new_allowedip = calloc(1, sizeof(struct wgallowedip));
+ if (!new_allowedip) {
+ perror("calloc");
free(mutable);
return false;
}
- peer = peer_from_offset(buf->dev, peer_offset);
- ipmask = (struct wgipmask *)((uint8_t *)peer + sizeof(struct wgpeer) + (sizeof(struct wgipmask) * peer->num_ipmasks));
+ if (allowedip)
+ allowedip->next_allowedip = new_allowedip;
+ else
+ peer->first_allowedip = new_allowedip;
+ allowedip = new_allowedip;
- if (!parse_ip(ipmask, ip)) {
+ if (!parse_ip(allowedip, ip)) {
free(mutable);
return false;
}
@@ -272,16 +254,16 @@ static inline bool parse_ipmasks(struct inflatable_device *buf, size_t peer_offs
if (*end)
cidr = ULONG_MAX;
}
- if (ipmask->family == AF_INET)
+ if (allowedip->family == AF_INET)
cidr = cidr > 32 ? 32 : cidr;
- else if (ipmask->family == AF_INET6)
+ else if (allowedip->family == AF_INET6)
cidr = cidr > 128 ? 128 : cidr;
else
continue;
- ipmask->cidr = cidr;
- ++peer->num_ipmasks;
+ allowedip->cidr = cidr;
}
free(mutable);
+ *last_allowedip = allowedip;
return true;
}
@@ -296,16 +278,20 @@ static bool process_line(struct config_ctx *ctx, const char *line)
return true;
}
if (!strcasecmp(line, "[Peer]")) {
- ctx->peer_offset = ctx->buf.pos;
- if (use_space(&ctx->buf, sizeof(struct wgpeer)) < 0) {
- perror("use_space");
+ struct wgpeer *new_peer = calloc(1, sizeof(struct wgpeer));
+ if (!new_peer) {
+ perror("calloc");
return false;
}
- ++ctx->buf.dev->num_peers;
+ ctx->last_allowedip = NULL;
+ if (ctx->last_peer)
+ ctx->last_peer->next_peer = new_peer;
+ else
+ ctx->device->first_peer = new_peer;
+ ctx->last_peer = new_peer;
ctx->is_peer_section = true;
ctx->is_device_section = false;
- peer_from_offset(ctx->buf.dev, ctx->peer_offset)->flags |= WGPEER_REPLACE_IPMASKS;
- peer_from_offset(ctx->buf.dev, ctx->peer_offset)->persistent_keepalive_interval = (__u16)-1;
+ ctx->last_peer->flags |= WGPEER_REPLACE_ALLOWEDIPS;
return true;
}
@@ -313,28 +299,32 @@ static bool process_line(struct config_ctx *ctx, const char *line)
if (ctx->is_device_section) {
if (key_match("ListenPort"))
- ret = !!(ctx->buf.dev->port = parse_port(value));
+ ret = parse_port(&ctx->device->listen_port, &ctx->device->flags, value);
else if (key_match("FwMark"))
- ret = parse_fwmark(&ctx->buf.dev->fwmark, &ctx->buf.dev->flags, value);
+ ret = parse_fwmark(&ctx->device->fwmark, &ctx->device->flags, value);
else if (key_match("PrivateKey")) {
- ret = parse_key(ctx->buf.dev->private_key, value);
+ ret = parse_key(ctx->device->private_key, value);
if (!ret)
- memset(ctx->buf.dev->private_key, 0, WG_KEY_LEN);
+ memset(ctx->device->private_key, 0, WG_KEY_LEN);
+ else
+ ctx->device->flags |= WGDEVICE_HAS_PRIVATE_KEY;
} else
goto error;
} else if (ctx->is_peer_section) {
if (key_match("Endpoint"))
- ret = parse_endpoint(&peer_from_offset(ctx->buf.dev, ctx->peer_offset)->endpoint.addr, value);
+ ret = parse_endpoint(&ctx->last_peer->endpoint.addr, value);
else if (key_match("PublicKey"))
- ret = parse_key(peer_from_offset(ctx->buf.dev, ctx->peer_offset)->public_key, value);
+ ret = parse_key(ctx->last_peer->public_key, value);
else if (key_match("AllowedIPs"))
- ret = parse_ipmasks(&ctx->buf, ctx->peer_offset, value);
+ ret = parse_allowedips(ctx->last_peer, &ctx->last_allowedip, value);
else if (key_match("PersistentKeepalive"))
- ret = parse_persistent_keepalive(&peer_from_offset(ctx->buf.dev, ctx->peer_offset)->persistent_keepalive_interval, value);
+ ret = parse_persistent_keepalive(&ctx->last_peer->persistent_keepalive_interval, &ctx->last_peer->flags, value);
else if (key_match("PresharedKey")) {
- ret = parse_key(peer_from_offset(ctx->buf.dev, ctx->peer_offset)->preshared_key, value);
+ ret = parse_key(ctx->last_peer->preshared_key, value);
if (!ret)
- memset(peer_from_offset(ctx->buf.dev, ctx->peer_offset)->preshared_key, 0, WG_KEY_LEN);
+ memset(ctx->last_peer->preshared_key, 0, WG_KEY_LEN);
+ else
+ ctx->last_peer->flags |= WGPEER_HAS_PRESHARED_KEY;
} else
goto error;
} else
@@ -355,7 +345,8 @@ bool config_read_line(struct config_ctx *ctx, const char *input)
bool ret = true;
if (!line) {
perror("calloc");
- return false;
+ ret = false;
+ goto out;
}
if (!len)
goto out;
@@ -370,56 +361,54 @@ bool config_read_line(struct config_ctx *ctx, const char *input)
ret = process_line(ctx, line);
out:
free(line);
+ if (!ret)
+ free_wgdevice(ctx->device);
return ret;
}
-bool config_read_init(struct config_ctx *ctx, struct wgdevice **device, bool append)
+bool config_read_init(struct config_ctx *ctx, bool append)
{
memset(ctx, 0, sizeof(struct config_ctx));
- ctx->device = device;
- ctx->buf.dev = calloc(1, sizeof(struct wgdevice));
- if (!ctx->buf.dev) {
+ ctx->device = calloc(1, sizeof(struct wgdevice));
+ if (!ctx->device) {
perror("calloc");
return false;
}
if (!append)
- ctx->buf.dev->flags |= WGDEVICE_REPLACE_PEERS;
+ ctx->device->flags |= WGDEVICE_REPLACE_PEERS | WGDEVICE_HAS_PRIVATE_KEY | WGDEVICE_HAS_FWMARK | WGDEVICE_HAS_LISTEN_PORT;
return true;
}
-bool config_read_finish(struct config_ctx *ctx)
+struct wgdevice *config_read_finish(struct config_ctx *ctx)
{
- size_t i;
struct wgpeer *peer;
- if (ctx->buf.dev->flags & WGDEVICE_REPLACE_PEERS && key_is_zero(ctx->buf.dev->private_key)) {
- fprintf(stderr, "No private key configured\n");
+ if (ctx->device->flags & WGDEVICE_REPLACE_PEERS && key_is_zero(ctx->device->private_key)) {
+ fprintf(stderr, "No private key is configured\n");
goto err;
}
- if (ctx->buf.dev->flags & WGDEVICE_REPLACE_PEERS && !ctx->buf.dev->fwmark)
- ctx->buf.dev->flags |= WGDEVICE_REMOVE_FWMARK;
- for_each_wgpeer(ctx->buf.dev, peer, i) {
+ for_each_wgpeer (ctx->device, peer) {
if (key_is_zero(peer->public_key)) {
fprintf(stderr, "A peer is missing a public key\n");
goto err;
}
}
- *ctx->device = ctx->buf.dev;
- return true;
+ return ctx->device;
err:
- free(ctx->buf.dev);
- return false;
+ free_wgdevice(ctx->device);
+ return NULL;
}
-static int read_keyfile(char dst[WG_KEY_LEN_BASE64], const char *path)
+static bool read_keyfile(char dst[WG_KEY_LEN_BASE64], const char *path)
{
FILE *f;
- int ret = -1, c;
+ int c;
+ bool ret = false;
f = fopen(path, "r");
if (!f) {
perror("fopen");
- return -1;
+ return false;
}
if (fread(dst, WG_KEY_LEN_BASE64 - 1, 1, f) != 1) {
@@ -429,7 +418,9 @@ static int read_keyfile(char dst[WG_KEY_LEN_BASE64], const char *path)
}
/* If we're at the end and we didn't read anything, we're /dev/null. */
if (!ferror(f) && feof(f) && !ftell(f)) {
- ret = 1;
+ static const uint8_t zeros[WG_KEY_LEN] = { 0 };
+ key_to_base64(dst, zeros);
+ ret = true;
goto out;
}
@@ -448,7 +439,7 @@ static int read_keyfile(char dst[WG_KEY_LEN_BASE64], const char *path)
perror("getc");
goto out;
}
- ret = 0;
+ ret = true;
out:
fclose(f);
@@ -473,85 +464,84 @@ static char *strip_spaces(const char *in)
return out;
}
-bool config_read_cmd(struct wgdevice **device, char *argv[], int argc)
+struct wgdevice *config_read_cmd(char *argv[], int argc)
{
- struct inflatable_device buf = { 0 };
- size_t peer_offset = 0;
- buf.dev = calloc(1, sizeof(struct wgdevice));
- if (!buf.dev) {
+ struct wgdevice *device = calloc(1, sizeof(struct wgdevice));
+ struct wgpeer *peer = NULL;
+ struct wgallowedip *allowedip = NULL;
+ if (!device) {
perror("calloc");
return false;
}
while (argc > 0) {
- if (!strcmp(argv[0], "listen-port") && argc >= 2 && !buf.dev->num_peers) {
- buf.dev->port = parse_port(argv[1]);
- if (!buf.dev->port)
+ if (!strcmp(argv[0], "listen-port") && argc >= 2 && !peer) {
+ if (!parse_port(&device->listen_port, &device->flags, argv[1]))
goto error;
argv += 2;
argc -= 2;
- } else if (!strcmp(argv[0], "fwmark") && argc >= 2 && !buf.dev->num_peers) {
- if (!parse_fwmark(&buf.dev->fwmark, &buf.dev->flags, argv[1]))
+ } else if (!strcmp(argv[0], "fwmark") && argc >= 2 && !peer) {
+ if (!parse_fwmark(&device->fwmark, &device->flags, argv[1]))
goto error;
argv += 2;
argc -= 2;
- } else if (!strcmp(argv[0], "private-key") && argc >= 2 && !buf.dev->num_peers) {
+ } else if (!strcmp(argv[0], "private-key") && argc >= 2 && !peer) {
char key_line[WG_KEY_LEN_BASE64];
- int ret = read_keyfile(key_line, argv[1]);
- if (ret == 0) {
- if (!parse_key(buf.dev->private_key, key_line))
+ if (read_keyfile(key_line, argv[1])) {
+ if (!parse_key(device->private_key, key_line))
goto error;
- } else if (ret == 1)
- buf.dev->flags |= WGDEVICE_REMOVE_PRIVATE_KEY;
- else
+ device->flags |= WGDEVICE_HAS_PRIVATE_KEY;
+ } else
goto error;
argv += 2;
argc -= 2;
} else if (!strcmp(argv[0], "peer") && argc >= 2) {
- peer_offset = buf.pos;
- if (use_space(&buf, sizeof(struct wgpeer)) < 0) {
- perror("use_space");
+ struct wgpeer *new_peer = calloc(1, sizeof(struct wgpeer));
+ allowedip = NULL;
+ if (!new_peer) {
+ perror("calloc");
goto error;
}
- peer_from_offset(buf.dev, peer_offset)->persistent_keepalive_interval = (__u16)-1;
- ++buf.dev->num_peers;
- if (!parse_key(peer_from_offset(buf.dev, peer_offset)->public_key, argv[1]))
+ if (peer)
+ peer->next_peer = new_peer;
+ else
+ device->first_peer = new_peer;
+ peer = new_peer;
+ if (!parse_key(peer->public_key, argv[1]))
goto error;
argv += 2;
argc -= 2;
- } else if (!strcmp(argv[0], "remove") && argc >= 1 && buf.dev->num_peers) {
- peer_from_offset(buf.dev, peer_offset)->flags |= WGPEER_REMOVE_ME;
+ } else if (!strcmp(argv[0], "remove") && argc >= 1 && peer) {
+ peer->flags |= WGPEER_REMOVE_ME;
argv += 1;
argc -= 1;
- } else if (!strcmp(argv[0], "endpoint") && argc >= 2 && buf.dev->num_peers) {
- if (!parse_endpoint(&peer_from_offset(buf.dev, peer_offset)->endpoint.addr, argv[1]))
+ } else if (!strcmp(argv[0], "endpoint") && argc >= 2 && peer) {
+ if (!parse_endpoint(&peer->endpoint.addr, argv[1]))
goto error;
argv += 2;
argc -= 2;
- } else if (!strcmp(argv[0], "allowed-ips") && argc >= 2 && buf.dev->num_peers) {
+ } else if (!strcmp(argv[0], "allowed-ips") && argc >= 2 && peer) {
char *line = strip_spaces(argv[1]);
if (!line)
goto error;
- if (!parse_ipmasks(&buf, peer_offset, line)) {
+ if (!parse_allowedips(peer, &allowedip, line)) {
free(line);
goto error;
}
free(line);
argv += 2;
argc -= 2;
- } else if (!strcmp(argv[0], "persistent-keepalive") && argc >= 2 && buf.dev->num_peers) {
- if (!parse_persistent_keepalive(&peer_from_offset(buf.dev, peer_offset)->persistent_keepalive_interval, argv[1]))
+ } else if (!strcmp(argv[0], "persistent-keepalive") && argc >= 2 && peer) {
+ if (!parse_persistent_keepalive(&peer->persistent_keepalive_interval, &peer->flags, argv[1]))
goto error;
argv += 2;
argc -= 2;
- } else if (!strcmp(argv[0], "preshared-key") && argc >= 2 && buf.dev->num_peers) {
+ } else if (!strcmp(argv[0], "preshared-key") && argc >= 2 && peer) {
char key_line[WG_KEY_LEN_BASE64];
- int ret = read_keyfile(key_line, argv[1]);
- if (ret == 0) {
- if (!parse_key(peer_from_offset(buf.dev, peer_offset)->preshared_key, key_line))
+ if (read_keyfile(key_line, argv[1])) {
+ if (!parse_key(peer->preshared_key, key_line))
goto error;
- } else if (ret == 1)
- peer_from_offset(buf.dev, peer_offset)->flags |= WGPEER_REMOVE_PRESHARED_KEY;
- else
+ peer->flags |= WGPEER_HAS_PRESHARED_KEY;
+ } else
goto error;
argv += 2;
argc -= 2;
@@ -560,9 +550,8 @@ bool config_read_cmd(struct wgdevice **device, char *argv[], int argc)
goto error;
}
}
- *device = buf.dev;
- return true;
+ return device;
error:
- free(buf.dev);
+ free_wgdevice(device);
return false;
}
diff --git a/src/tools/config.h b/src/tools/config.h
index 5f7761f..63a272c 100644
--- a/src/tools/config.h
+++ b/src/tools/config.h
@@ -4,31 +4,21 @@
#define CONFIG_H
#include <stdbool.h>
-#include <stdint.h>
-#include <sys/types.h>
-#include <sys/ioctl.h>
-#include <net/if.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include "../uapi.h"
-struct inflatable_device {
- struct wgdevice *dev;
- size_t len;
- size_t pos;
-};
+struct wgdevice;
+struct wgpeer;
+struct wgallowedip;
struct config_ctx {
- struct inflatable_device buf;
- size_t peer_offset;
- struct wgdevice **device;
- bool is_peer_section;
- bool is_device_section;
+ struct wgdevice *device;
+ struct wgpeer *last_peer;
+ struct wgallowedip *last_allowedip;
+ bool is_peer_section, is_device_section;
};
-bool config_read_cmd(struct wgdevice **dev, char *argv[], int argc);
-bool config_read_init(struct config_ctx *ctx, struct wgdevice **device, bool append);
+struct wgdevice *config_read_cmd(char *argv[], int argc);
+bool config_read_init(struct config_ctx *ctx, bool append);
bool config_read_line(struct config_ctx *ctx, const char *line);
-bool config_read_finish(struct config_ctx *ctx);
+struct wgdevice *config_read_finish(struct config_ctx *ctx);
#endif
diff --git a/src/tools/containers.h b/src/tools/containers.h
new file mode 100644
index 0000000..b9c85e0
--- /dev/null
+++ b/src/tools/containers.h
@@ -0,0 +1,95 @@
+/* Copyright (C) 2015-2017 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. */
+
+
+#ifndef CONTAINERS_H
+#define CONTAINERS_H
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+
+#include "../uapi/wireguard.h"
+
+struct wgallowedip {
+ uint16_t family;
+ union {
+ struct in_addr ip4;
+ struct in6_addr ip6;
+ };
+ uint8_t cidr;
+ struct wgallowedip *next_allowedip;
+};
+
+enum {
+ WGPEER_REMOVE_ME = (1 << 0),
+ WGPEER_REPLACE_ALLOWEDIPS = (1 << 1),
+ WGPEER_HAS_PRESHARED_KEY = (1 << 2),
+ WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL = (1 << 3)
+};
+
+struct wgpeer {
+ uint32_t flags;
+
+ 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 timeval last_handshake_time;
+ uint64_t rx_bytes, tx_bytes;
+ uint16_t persistent_keepalive_interval;
+
+ struct wgallowedip *first_allowedip;
+ struct wgpeer *next_peer;
+};
+
+enum {
+ WGDEVICE_REPLACE_PEERS = (1 << 0),
+ WGDEVICE_HAS_PRIVATE_KEY = (1 << 1),
+ WGDEVICE_HAS_LISTEN_PORT = (1 << 2),
+ WGDEVICE_HAS_FWMARK = (1 << 3)
+};
+
+enum {
+ WG_API_VERSION_MAGIC = 0xbeef0003
+};
+
+struct wgdevice {
+ char name[IFNAMSIZ];
+ uint32_t ifindex;
+
+ uint32_t flags;
+
+ uint8_t public_key[WG_KEY_LEN];
+ uint8_t private_key[WG_KEY_LEN];
+
+ uint32_t fwmark;
+ uint16_t listen_port;
+
+ struct wgpeer *first_peer;
+};
+
+#define for_each_wgpeer(__dev, __peer) for ((__peer) = (__dev)->first_peer; (__peer); (__peer) = (__peer)->next_peer)
+#define for_each_wgallowedip(__peer, __allowedip) for ((__allowedip) = (__peer)->first_allowedip; (__allowedip); (__allowedip) = (__allowedip)->next_allowedip)
+#define max(a, b) ((a) > (b) ? (a) : (b))
+
+static inline void free_wgdevice(struct wgdevice *dev)
+{
+ if (!dev)
+ return;
+ for (struct wgpeer *peer = dev->first_peer, *np = peer ? peer->next_peer : NULL; peer; peer = np, np = peer ? peer->next_peer : NULL) {
+ for (struct wgallowedip *allowedip = peer->first_allowedip, *na = allowedip ? allowedip->next_allowedip : NULL; allowedip; allowedip = na, na = allowedip ? allowedip->next_allowedip : NULL)
+ free(allowedip);
+ free(peer);
+ }
+ free(dev);
+}
+
+#endif
diff --git a/src/tools/encoding.h b/src/tools/encoding.h
index 9db4c6e..1f79a2a 100644
--- a/src/tools/encoding.h
+++ b/src/tools/encoding.h
@@ -5,7 +5,7 @@
#include <stdbool.h>
#include <stdint.h>
-#include "../uapi.h"
+#include "containers.h"
#define WG_KEY_LEN_BASE64 ((((WG_KEY_LEN) + 2) / 3) * 4 + 1)
#define WG_KEY_LEN_HEX (WG_KEY_LEN * 2 + 1)
diff --git a/src/tools/ipc.c b/src/tools/ipc.c
index 45278ec..8f86eb0 100644
--- a/src/tools/ipc.c
+++ b/src/tools/ipc.c
@@ -5,6 +5,8 @@
#include <linux/if_link.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
+#include <linux/genetlink.h>
+#include "mnlg.h"
#endif
#include <netinet/in.h>
#include <net/if.h>
@@ -29,9 +31,10 @@
#include <arpa/inet.h>
#include "ipc.h"
+#include "containers.h"
#include "encoding.h"
#include "curve25519.h"
-#include "../uapi.h"
+#include "../uapi/wireguard.h"
#define SOCK_PATH RUNSTATEDIR "/wireguard/"
#define SOCK_SUFFIX ".sock"
@@ -44,8 +47,6 @@ struct inflatable_buffer {
size_t pos;
};
-#define max(a, b) ((a) > (b) ? (a) : (b))
-
static int add_next_to_inflatable_buffer(struct inflatable_buffer *buffer)
{
size_t len, expand_to;
@@ -84,6 +85,19 @@ static int add_next_to_inflatable_buffer(struct inflatable_buffer *buffer)
return 0;
}
+static void warn_unrecognized(const char *which)
+{
+ static bool once = false;
+ if (once)
+ return;
+ once = true;
+ fprintf(stderr,
+ "Warning: this program received from your %s one or more\n"
+ "attributes that it did not recognize. It is possible that\n"
+ "this version of wg(8) is older than your %s. You may\n"
+ "want to update this program.\n", which, which);
+}
+
static FILE *userspace_interface_file(const char *interface)
{
struct stat sbuf;
@@ -130,15 +144,28 @@ out:
static bool userspace_has_wireguard_interface(const char *interface)
{
struct stat sbuf;
- char path[PATH_MAX] = { 0 };
+ struct sockaddr_un addr = { .sun_family = AF_UNIX };
+ int fd, ret;
if (strchr(interface, '/'))
return false;
- if (snprintf(path, sizeof(path) - 1, SOCK_PATH "%s" SOCK_SUFFIX, interface) < 0)
+ if (snprintf(addr.sun_path, sizeof(addr.sun_path) - 1, SOCK_PATH "%s" SOCK_SUFFIX, interface) < 0)
+ return false;
+ if (stat(addr.sun_path, &sbuf) < 0)
+ return false;
+ if (!S_ISSOCK(sbuf.st_mode))
+ return false;
+ ret = fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (ret < 0)
return false;
- if (stat(path, &sbuf) < 0)
+ ret = connect(fd, (struct sockaddr *)&addr, sizeof(addr));
+ if (ret < 0 && errno == ECONNREFUSED) { /* If the process is gone, we try to clean up the socket. */
+ close(fd);
+ unlink(addr.sun_path);
return false;
- return S_ISSOCK(sbuf.st_mode);
+ }
+ close(fd);
+ return true;
}
static int userspace_get_wireguard_interfaces(struct inflatable_buffer *buffer)
@@ -177,42 +204,35 @@ static int userspace_set_device(struct wgdevice *dev)
{
char hex[WG_KEY_LEN_HEX], ip[INET6_ADDRSTRLEN], host[4096 + 1], service[512 + 1];
struct wgpeer *peer;
- struct wgipmask *ipmask;
+ struct wgallowedip *allowedip;
FILE *f;
int ret;
- size_t i, j;
socklen_t addr_len;
- f = userspace_interface_file(dev->interface);
+ f = userspace_interface_file(dev->name);
if (!f)
return -errno;
fprintf(f, "set=1\n");
- if (dev->flags & WGDEVICE_REMOVE_PRIVATE_KEY)
- fprintf(f, "private_key=\n");
- else if (!key_is_zero(dev->private_key)) {
+ if (dev->flags & WGDEVICE_HAS_PRIVATE_KEY) {
key_to_hex(hex, dev->private_key);
fprintf(f, "private_key=%s\n", hex);
}
- if (dev->port)
- fprintf(f, "listen_port=%u\n", dev->port);
- if (dev->flags & WGDEVICE_REMOVE_FWMARK)
- fprintf(f, "fwmark=\n");
- else if (dev->fwmark)
+ if (dev->flags & WGDEVICE_HAS_LISTEN_PORT)
+ fprintf(f, "listen_port=%u\n", dev->listen_port);
+ if (dev->flags & WGDEVICE_HAS_FWMARK)
fprintf(f, "fwmark=%u\n", dev->fwmark);
if (dev->flags & WGDEVICE_REPLACE_PEERS)
fprintf(f, "replace_peers=true\n");
- for_each_wgpeer(dev, peer, i) {
+ for_each_wgpeer (dev, peer) {
key_to_hex(hex, peer->public_key);
fprintf(f, "public_key=%s\n", hex);
if (peer->flags & WGPEER_REMOVE_ME) {
fprintf(f, "remove=true\n");
continue;
}
- if (peer->flags & WGPEER_REMOVE_PRESHARED_KEY)
- fprintf(f, "preshared_key=\n");
- else if (!key_is_zero(peer->preshared_key)) {
+ if (peer->flags & WGPEER_HAS_PRESHARED_KEY) {
key_to_hex(hex, peer->preshared_key);
fprintf(f, "preshared_key=%s\n", hex);
}
@@ -229,20 +249,20 @@ static int userspace_set_device(struct wgdevice *dev)
fprintf(f, "endpoint=%s:%s\n", host, service);
}
}
- if (peer->persistent_keepalive_interval != (uint16_t)-1)
+ if (peer->flags & WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL)
fprintf(f, "persistent_keepalive_interval=%u\n", peer->persistent_keepalive_interval);
- if (peer->flags & WGPEER_REPLACE_IPMASKS)
+ if (peer->flags & WGPEER_REPLACE_ALLOWEDIPS)
fprintf(f, "replace_allowed_ips=true\n");
- for_each_wgipmask(peer, ipmask, j) {
- if (ipmask->family == AF_INET) {
- if (!inet_ntop(AF_INET, &ipmask->ip4, ip, INET6_ADDRSTRLEN))
+ for_each_wgallowedip (peer, allowedip) {
+ if (allowedip->family == AF_INET) {
+ if (!inet_ntop(AF_INET, &allowedip->ip4, ip, INET6_ADDRSTRLEN))
continue;
- } else if (ipmask->family == AF_INET6) {
- if (!inet_ntop(AF_INET6, &ipmask->ip6, ip, INET6_ADDRSTRLEN))
+ } else if (allowedip->family == AF_INET6) {
+ if (!inet_ntop(AF_INET6, &allowedip->ip6, ip, INET6_ADDRSTRLEN))
continue;
} else
continue;
- fprintf(f, "allowed_ip=%s/%d\n", ip, ipmask->cidr);
+ fprintf(f, "allowed_ip=%s/%d\n", ip, allowedip->cidr);
}
}
fprintf(f, "\n");
@@ -255,24 +275,6 @@ static int userspace_set_device(struct wgdevice *dev)
return ret;
}
-#define ADD(bytes) ({ \
- if (buffer_len - buffer_end < bytes) { \
- ptrdiff_t peer_offset = (void *)peer - (void *)*out; \
- buffer_len = buffer_len * 2 + bytes; \
- *out = realloc(*out, buffer_len); \
- if (!*out) { \
- ret = -errno; \
- goto err; \
- } \
- memset((void *)*out + buffer_end, 0, buffer_len - buffer_end); \
- if (peer) \
- peer = (void *)*out + peer_offset; \
- dev = *out; \
- } \
- buffer_end += bytes; \
- (void *)*out + buffer_end - bytes; \
-})
-
#define NUM(max) ({ \
unsigned long long num; \
char *end; \
@@ -288,11 +290,16 @@ static int userspace_get_device(struct wgdevice **out, const char *interface)
{
struct wgdevice *dev;
struct wgpeer *peer = NULL;
- size_t buffer_len = 0, buffer_end = 0, line_buffer_len = 0, line_len;
+ struct wgallowedip *allowedip = NULL;
+ size_t line_buffer_len = 0, line_len;
char *key = NULL, *value;
FILE *f;
int ret = -EPROTO;
+ *out = dev = calloc(1, sizeof(struct wgdevice));
+ if (!dev)
+ return -errno;
+
f = userspace_interface_file(interface);
if (!f)
return -errno;
@@ -300,11 +307,8 @@ static int userspace_get_device(struct wgdevice **out, const char *interface)
fprintf(f, "get=1\n\n");
fflush(f);
- *out = NULL;
- dev = ADD(sizeof(struct wgdevice));
- dev->version_magic = WG_API_VERSION_MAGIC;
- strncpy(dev->interface, interface, IFNAMSIZ - 1);
- dev->interface[IFNAMSIZ - 1] = '\0';
+ strncpy(dev->name, interface, IFNAMSIZ - 1);
+ dev->name[IFNAMSIZ - 1] = '\0';
while (getline(&key, &line_buffer_len, f) > 0) {
line_len = strlen(key);
@@ -322,18 +326,31 @@ static int userspace_get_device(struct wgdevice **out, const char *interface)
if (!key_from_hex(dev->private_key, value))
break;
curve25519_generate_public(dev->public_key, dev->private_key);
- } else if (!peer && !strcmp(key, "listen_port"))
- dev->port = NUM(0xffffU);
- else if (!peer && !strcmp(key, "fwmark"))
+ dev->flags |= WGDEVICE_HAS_PRIVATE_KEY;
+ } else if (!peer && !strcmp(key, "listen_port")) {
+ dev->listen_port = NUM(0xffffU);
+ dev->flags |= WGDEVICE_HAS_LISTEN_PORT;
+ } else if (!peer && !strcmp(key, "fwmark")) {
dev->fwmark = NUM(0xffffffffU);
- else if (!strcmp(key, "public_key")) {
- peer = ADD(sizeof(struct wgpeer));
+ dev->flags |= WGDEVICE_HAS_FWMARK;
+ } else if (!strcmp(key, "public_key")) {
+ struct wgpeer *new_peer = calloc(1, sizeof(struct wgpeer));
+ if (!new_peer) {
+ ret = -ENOMEM;
+ goto err;
+ }
+ allowedip = NULL;
+ if (peer)
+ peer->next_peer = new_peer;
+ else
+ dev->first_peer = new_peer;
+ peer = new_peer;
if (!key_from_hex(peer->public_key, value))
break;
- ++dev->num_peers;
} else if (peer && !strcmp(key, "preshared_key")) {
if (!key_from_hex(peer->preshared_key, value))
break;
+ peer->flags |= WGPEER_HAS_PRESHARED_KEY;
} else if (peer && !strcmp(key, "endpoint")) {
char *begin, *end;
struct addrinfo *resolved;
@@ -371,26 +388,36 @@ static int userspace_get_device(struct wgdevice **out, const char *interface)
break;
}
freeaddrinfo(resolved);
- } else if (peer && !strcmp(key, "persistent_keepalive_interval"))
- peer->persistent_keepalive_interval = NUM(65535U);
- else if (peer && !strcmp(key, "allowed_ip")) {
- struct wgipmask *ipmask = ADD(sizeof(struct wgipmask));
+ } else if (peer && !strcmp(key, "persistent_keepalive_interval")) {
+ peer->persistent_keepalive_interval = NUM(0xffffU);
+ peer->flags |= WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL;
+ } else if (peer && !strcmp(key, "allowed_ip")) {
+ struct wgallowedip *new_allowedip;
char *end, *cidr = strchr(value, '/');
if (!cidr || strlen(cidr) <= 1)
break;
*cidr++ = '\0';
- ipmask->family = AF_UNSPEC;
+ new_allowedip = calloc(1, sizeof(struct wgallowedip));
+ if (!new_allowedip) {
+ ret = -ENOMEM;
+ goto err;
+ }
+ if (allowedip)
+ allowedip->next_allowedip = new_allowedip;
+ else
+ peer->first_allowedip = new_allowedip;
+ allowedip = new_allowedip;
+ allowedip->family = AF_UNSPEC;
if (strchr(value, ':')) {
- if (inet_pton(AF_INET6, value, &ipmask->ip6) == 1)
- ipmask->family = AF_INET6;
+ if (inet_pton(AF_INET6, value, &allowedip->ip6) == 1)
+ allowedip->family = AF_INET6;
} else {
- if (inet_pton(AF_INET, value, &ipmask->ip4) == 1)
- ipmask->family = AF_INET;
+ if (inet_pton(AF_INET, value, &allowedip->ip4) == 1)
+ allowedip->family = AF_INET;
}
- ipmask->cidr = strtoul(cidr, &end, 10);
- if (*end || ipmask->family == AF_UNSPEC || (ipmask->family == AF_INET6 && ipmask->cidr > 128) || (ipmask->family == AF_INET && ipmask->cidr > 32))
+ allowedip->cidr = strtoul(cidr, &end, 10);
+ if (*end || allowedip->family == AF_UNSPEC || (allowedip->family == AF_INET6 && allowedip->cidr > 128) || (allowedip->family == AF_INET && allowedip->cidr > 32))
break;
- ++peer->num_ipmasks;
} else if (peer && !strcmp(key, "last_handshake_time_sec"))
peer->last_handshake_time.tv_sec = NUM(0xffffffffffffffffULL);
else if (peer && !strcmp(key, "last_handshake_time_nsec"))
@@ -402,32 +429,21 @@ static int userspace_get_device(struct wgdevice **out, const char *interface)
else if (!strcmp(key, "errno"))
ret = -NUM(0x7fffffffU);
else
- break;
+ warn_unrecognized("daemon");
}
ret = -EPROTO;
err:
free(key);
- free(*out);
+ free_wgdevice(dev);
*out = NULL;
fclose(f);
errno = -ret;
return ret;
}
-#undef ADD
#undef NUM
-#undef KEY
#ifdef __linux__
-static int check_version_magic(struct wgdevice *device, int ret)
-{
- if (ret == -EPROTO || (!ret && device->version_magic != WG_API_VERSION_MAGIC)) {
- fprintf(stderr, "This program was built for a different version of WireGuard than\nwhat is currently running. Either this version of wg(8) is out\nof date, or the currently loaded WireGuard module is out of date.\nIf you have just updated your WireGuard installation, you may have\nforgotten to unload the previous running WireGuard module. Try\nrunning `rmmod wireguard` as root, and then try re-adding the interface\nand trying again.\n\n");
- errno = EPROTO;
- return -EPROTO;
- }
- return ret;
-}
static int parse_linkinfo(const struct nlattr *attr, void *data)
{
@@ -477,7 +493,7 @@ static int kernel_get_wireguard_interfaces(struct inflatable_buffer *buffer)
struct ifinfomsg *ifm;
ret = -ENOMEM;
- rtnl_buffer = calloc(4096, 1);
+ rtnl_buffer = calloc(MNL_SOCKET_BUFFER_SIZE, 1);
if (!rtnl_buffer)
goto cleanup;
@@ -508,7 +524,7 @@ static int kernel_get_wireguard_interfaces(struct inflatable_buffer *buffer)
}
another:
- if ((len = mnl_socket_recvfrom(nl, rtnl_buffer, 4096)) < 0) {
+ if ((len = mnl_socket_recvfrom(nl, rtnl_buffer, MNL_SOCKET_BUFFER_SIZE)) < 0) {
ret = -errno;
goto cleanup;
}
@@ -527,82 +543,368 @@ cleanup:
return ret;
}
-static bool kernel_has_wireguard_interface(const char *interface)
+static int kernel_set_device(struct wgdevice *dev)
{
- char *this_interface;
- struct inflatable_buffer buffer = { .len = 4096 };
+ int ret = 0;
+ size_t i, j;
+ struct wgpeer *peer = NULL;
+ struct wgallowedip *allowedip = NULL;
+ struct nlattr *peers_nest, *peer_nest, *allowedips_nest, *allowedip_nest;
+ struct nlmsghdr *nlh;
+ struct mnlg_socket *nlg;
- buffer.buffer = calloc(1, buffer.len);
- if (!buffer.buffer)
- return false;
- if (kernel_get_wireguard_interfaces(&buffer) < 0) {
- free(buffer.buffer);
- return false;
+ nlg= mnlg_socket_open(WG_GENL_NAME, WG_GENL_VERSION);
+ if (!nlg)
+ return -errno;
+
+again:
+ nlh = mnlg_msg_prepare(nlg, WG_CMD_SET_DEVICE, NLM_F_REQUEST | NLM_F_ACK);
+ mnl_attr_put_strz(nlh, WGDEVICE_A_IFNAME, dev->name);
+
+ if (!peer) {
+ uint32_t flags = 0;
+ if (dev->flags & WGDEVICE_HAS_PRIVATE_KEY)
+ mnl_attr_put(nlh, WGDEVICE_A_PRIVATE_KEY, sizeof(dev->private_key), dev->private_key);
+ if (dev->flags & WGDEVICE_HAS_LISTEN_PORT)
+ mnl_attr_put_u16(nlh, WGDEVICE_A_LISTEN_PORT, dev->listen_port);
+ if (dev->flags & WGDEVICE_HAS_FWMARK)
+ mnl_attr_put_u32(nlh, WGDEVICE_A_FWMARK, dev->fwmark);
+ if (dev->flags & WGDEVICE_REPLACE_PEERS)
+ flags |= WGDEVICE_F_REPLACE_PEERS;
+ if (flags)
+ mnl_attr_put_u32(nlh, WGDEVICE_A_FLAGS, flags);
}
- this_interface = buffer.buffer;
- for (size_t len = 0; (len = strlen(this_interface)); this_interface += len + 1) {
- if (!strcmp(interface, this_interface)) {
- free(buffer.buffer);
- return true;
+ if (!dev->first_peer)
+ goto send;
+ peers_nest = peer_nest = allowedips_nest = allowedip_nest = NULL;
+ peers_nest = mnl_attr_nest_start(nlh, WGDEVICE_A_PEERS);
+ for (i = 0, peer = peer ? peer : dev->first_peer; peer; peer = peer->next_peer) {
+ uint32_t flags = 0;
+ peer_nest = mnl_attr_nest_start_check(nlh, MNL_SOCKET_BUFFER_SIZE, i++);
+ if (!peer_nest)
+ goto toobig_peers;
+ if (!mnl_attr_put_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_PUBLIC_KEY, sizeof(peer->public_key), peer->public_key))
+ goto toobig_peers;
+ if (peer->flags & WGPEER_REMOVE_ME)
+ flags |= WGPEER_F_REMOVE_ME;
+ if (!allowedip) {
+ if (peer->flags & WGPEER_REPLACE_ALLOWEDIPS)
+ flags |= WGPEER_F_REPLACE_ALLOWEDIPS;
+ if (peer->flags & WGPEER_HAS_PRESHARED_KEY) {
+ if (!mnl_attr_put_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_PRESHARED_KEY, sizeof(peer->preshared_key), peer->preshared_key))
+ goto toobig_peers;
+ }
+ if (peer->endpoint.addr.sa_family == AF_INET) {
+ if (!mnl_attr_put_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_ENDPOINT, sizeof(peer->endpoint.addr4), &peer->endpoint.addr4))
+ goto toobig_peers;
+ } else if (peer->endpoint.addr.sa_family == AF_INET6) {
+ if (!mnl_attr_put_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_ENDPOINT, sizeof(peer->endpoint.addr6), &peer->endpoint.addr6))
+ goto toobig_peers;
+ }
+ if (peer->flags & WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL) {
+ if (!mnl_attr_put_u16_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL, peer->persistent_keepalive_interval))
+ goto toobig_peers;
+ }
}
+ if (flags) {
+ if (!mnl_attr_put_u32_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_FLAGS, flags))
+ goto toobig_peers;
+ }
+ if (peer->first_allowedip) {
+ if (!allowedip)
+ allowedip = peer->first_allowedip;
+ allowedips_nest = mnl_attr_nest_start_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_ALLOWEDIPS);
+ if (!allowedips_nest)
+ goto toobig_allowedips;
+ for (j = 0; allowedip; allowedip = allowedip->next_allowedip) {
+ allowedip_nest = mnl_attr_nest_start_check(nlh, MNL_SOCKET_BUFFER_SIZE, j++);
+ if (!allowedip_nest)
+ goto toobig_allowedips;
+ if (!mnl_attr_put_u16_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGALLOWEDIP_A_FAMILY, allowedip->family))
+ goto toobig_allowedips;
+ if (allowedip->family == AF_INET) {
+ if (!mnl_attr_put_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGALLOWEDIP_A_IPADDR, sizeof(allowedip->ip4), &allowedip->ip4))
+ goto toobig_allowedips;
+ } else if (allowedip->family == AF_INET6) {
+ if (!mnl_attr_put_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGALLOWEDIP_A_IPADDR, sizeof(allowedip->ip6), &allowedip->ip6))
+ goto toobig_allowedips;
+ }
+ if (!mnl_attr_put_u8_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGALLOWEDIP_A_CIDR_MASK, allowedip->cidr))
+ goto toobig_allowedips;
+ mnl_attr_nest_end(nlh, allowedip_nest);
+ allowedip_nest = NULL;
+ }
+ mnl_attr_nest_end(nlh, allowedips_nest);
+ allowedips_nest = NULL;
+ }
+
+ mnl_attr_nest_end(nlh, peer_nest);
+ peer_nest = NULL;
}
- free(buffer.buffer);
- return false;
+ mnl_attr_nest_end(nlh, peers_nest);
+ peers_nest = NULL;
+ goto send;
+toobig_allowedips:
+ if (allowedip_nest)
+ mnl_attr_nest_cancel(nlh, allowedip_nest);
+ if (allowedips_nest)
+ mnl_attr_nest_end(nlh, allowedips_nest);
+ mnl_attr_nest_end(nlh, peer_nest);
+ mnl_attr_nest_end(nlh, peers_nest);
+ goto send;
+toobig_peers:
+ if (peer_nest)
+ mnl_attr_nest_cancel(nlh, peer_nest);
+ mnl_attr_nest_end(nlh, peers_nest);
+ goto send;
+send:
+ if (mnlg_socket_send(nlg, nlh) < 0) {
+ ret = -errno;
+ goto out;
+ }
+ errno = 0;
+ if (mnlg_socket_recv_run(nlg, NULL, NULL) < 0) {
+ ret = errno ? -errno : -EINVAL;
+ goto out;
+ }
+ if (peer)
+ goto again;
+
+out:
+ mnlg_socket_close(nlg);
+ errno = -ret;
+ return ret;
}
-static int do_ioctl(int req, struct ifreq *ifreq)
+struct get_device_ctx {
+ struct wgdevice *device;
+ struct wgpeer *peer;
+ struct wgallowedip *allowedip;
+};
+
+static int parse_allowedip(const struct nlattr *attr, void *data)
{
- static int fd = -1;
+ struct get_device_ctx *ctx = data;
+
+ switch (mnl_attr_get_type(attr)) {
+ case WGALLOWEDIP_A_FAMILY:
+ if (!mnl_attr_validate(attr, MNL_TYPE_U16))
+ ctx->allowedip->family = mnl_attr_get_u16(attr);
+ break;
+ case WGALLOWEDIP_A_IPADDR:
+ if (mnl_attr_get_payload_len(attr) == sizeof(ctx->allowedip->ip4))
+ memcpy(&ctx->allowedip->ip4, mnl_attr_get_payload(attr), sizeof(ctx->allowedip->ip4));
+ else if (mnl_attr_get_payload_len(attr) == sizeof(ctx->allowedip->ip6))
+ memcpy(&ctx->allowedip->ip6, mnl_attr_get_payload(attr), sizeof(ctx->allowedip->ip6));
+ break;
+ case WGALLOWEDIP_A_CIDR_MASK:
+ if (!mnl_attr_validate(attr, MNL_TYPE_U8))
+ ctx->allowedip->cidr = mnl_attr_get_u8(attr);
+ break;
+ default:
+ warn_unrecognized("kernel");
+ }
+
+ return MNL_CB_OK;
+}
+
+static int parse_allowedips(const struct nlattr *attr, void *data)
+{
+ struct get_device_ctx *ctx = data;
+ struct wgallowedip *new_allowedip = calloc(1, sizeof(struct wgallowedip));
int ret;
- if (fd < 0) {
- fd = socket(AF_INET, SOCK_DGRAM, 0);
- if (fd < 0)
- return fd;
+ if (!new_allowedip) {
+ perror("calloc");
+ return MNL_CB_ERROR;
}
- ret = ioctl(fd, req, ifreq);
- if (ret == -1)
- ret = -errno;
- return ret;
+ if (ctx->allowedip)
+ ctx->allowedip->next_allowedip = new_allowedip;
+ else
+ ctx->peer->first_allowedip = new_allowedip;
+ ctx->allowedip = new_allowedip;
+ ret = mnl_attr_parse_nested(attr, parse_allowedip, ctx);
+ if (!ret)
+ return ret;
+ if (!((ctx->allowedip->family == AF_INET && ctx->allowedip->cidr <= 32) || (ctx->allowedip->family == AF_INET6 && ctx->allowedip->cidr <= 128)))
+ return MNL_CB_ERROR;
+ return MNL_CB_OK;
}
-static int kernel_set_device(struct wgdevice *dev)
+static int parse_peer(const struct nlattr *attr, void *data)
{
- struct ifreq ifreq = { .ifr_data = (char *)dev };
- memcpy(&ifreq.ifr_name, dev->interface, IFNAMSIZ);
- ifreq.ifr_name[IFNAMSIZ - 1] = 0;
- dev->version_magic = WG_API_VERSION_MAGIC;
- return check_version_magic(dev, do_ioctl(WG_SET_DEVICE, &ifreq));
+ struct get_device_ctx *ctx = data;
+
+ switch (mnl_attr_get_type(attr)) {
+ case WGPEER_A_PUBLIC_KEY:
+ if (mnl_attr_get_payload_len(attr) == sizeof(ctx->peer->public_key))
+ memcpy(ctx->peer->public_key, mnl_attr_get_payload(attr), sizeof(ctx->peer->public_key));
+ break;
+ case WGPEER_A_PRESHARED_KEY:
+ if (mnl_attr_get_payload_len(attr) == sizeof(ctx->peer->preshared_key))
+ memcpy(ctx->peer->preshared_key, mnl_attr_get_payload(attr), sizeof(ctx->peer->preshared_key));
+ break;
+ case WGPEER_A_ENDPOINT: {
+ struct sockaddr *addr;
+ if (mnl_attr_get_payload_len(attr) < sizeof(*addr))
+ break;
+ addr = mnl_attr_get_payload(attr);
+ if (addr->sa_family == AF_INET && mnl_attr_get_payload_len(attr) == sizeof(ctx->peer->endpoint.addr4))
+ memcpy(&ctx->peer->endpoint.addr4, addr, sizeof(ctx->peer->endpoint.addr4));
+ else if (addr->sa_family == AF_INET6 && mnl_attr_get_payload_len(attr) == sizeof(ctx->peer->endpoint.addr6))
+ memcpy(&ctx->peer->endpoint.addr6, addr, sizeof(ctx->peer->endpoint.addr6));
+ break;
+ }
+ case WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL:
+ if (!mnl_attr_validate(attr, MNL_TYPE_U16))
+ ctx->peer->persistent_keepalive_interval = mnl_attr_get_u16(attr);
+ break;
+ case WGPEER_A_LAST_HANDSHAKE_TIME:
+ if (mnl_attr_get_payload_len(attr) == sizeof(ctx->peer->last_handshake_time))
+ memcpy(&ctx->peer->last_handshake_time, mnl_attr_get_payload(attr), sizeof(ctx->peer->last_handshake_time));
+ break;
+ case WGPEER_A_RX_BYTES:
+ if (!mnl_attr_validate(attr, MNL_TYPE_U64))
+ ctx->peer->rx_bytes = mnl_attr_get_u64(attr);
+ break;
+ case WGPEER_A_TX_BYTES:
+ if (!mnl_attr_validate(attr, MNL_TYPE_U64))
+ ctx->peer->tx_bytes = mnl_attr_get_u64(attr);
+ break;
+ case WGPEER_A_ALLOWEDIPS:
+ return mnl_attr_parse_nested(attr, parse_allowedips, ctx);
+ default:
+ warn_unrecognized("kernel");
+ }
+
+ return MNL_CB_OK;
}
-static int kernel_get_device(struct wgdevice **dev, const char *interface)
+static int parse_peers(const struct nlattr *attr, void *data)
{
+ struct get_device_ctx *ctx = data;
+ struct wgpeer *new_peer = calloc(1, sizeof(struct wgpeer));
int ret;
- struct ifreq ifreq = { 0 };
- memcpy(&ifreq.ifr_name, interface, IFNAMSIZ);
- ifreq.ifr_name[IFNAMSIZ - 1] = 0;
- *dev = NULL;
- do {
- free(*dev);
- ret = do_ioctl(WG_GET_DEVICE, &ifreq);
- if (ret < 0)
- goto out;
- *dev = calloc(1, ret + sizeof(struct wgdevice));
- ret = -ENOMEM;
- if (!*dev)
- goto out;
- (*dev)->peers_size = ret;
- (*dev)->version_magic = WG_API_VERSION_MAGIC;
- ifreq.ifr_data = (char *)*dev;
- memcpy(&ifreq.ifr_name, interface, IFNAMSIZ);
- ifreq.ifr_name[IFNAMSIZ - 1] = 0;
- ret = do_ioctl(WG_GET_DEVICE, &ifreq);
- } while (ret == -EMSGSIZE);
- ret = check_version_magic(*dev, ret);
- if (ret < 0) {
- free(*dev);
+ if (!new_peer) {
+ perror("calloc");
+ return MNL_CB_ERROR;
+ }
+ if (ctx->peer)
+ ctx->peer->next_peer = new_peer;
+ else
+ ctx->device->first_peer = new_peer;
+ ctx->peer = new_peer;
+ ctx->allowedip = NULL;
+ ret = mnl_attr_parse_nested(attr, parse_peer, ctx);
+ if (!ret)
+ return ret;
+ if (key_is_zero(ctx->peer->public_key))
+ return MNL_CB_ERROR;
+ return MNL_CB_OK;
+}
+
+static int parse_device(const struct nlattr *attr, void *data)
+{
+ struct get_device_ctx *ctx = data;
+
+ switch (mnl_attr_get_type(attr)) {
+ case WGDEVICE_A_IFINDEX:
+ if (!mnl_attr_validate(attr, MNL_TYPE_U32))
+ ctx->device->ifindex = mnl_attr_get_u32(attr);
+ break;
+ case WGDEVICE_A_IFNAME:
+ if (!mnl_attr_validate(attr, MNL_TYPE_STRING))
+ strncpy(ctx->device->name, mnl_attr_get_str(attr), sizeof(ctx->device->name) - 1);
+ break;
+ case WGDEVICE_A_PRIVATE_KEY:
+ if (mnl_attr_get_payload_len(attr) == sizeof(ctx->device->private_key))
+ memcpy(ctx->device->private_key, mnl_attr_get_payload(attr), sizeof(ctx->device->private_key));
+ break;
+ case WGDEVICE_A_PUBLIC_KEY:
+ if (mnl_attr_get_payload_len(attr) == sizeof(ctx->device->public_key))
+ memcpy(ctx->device->public_key, mnl_attr_get_payload(attr), sizeof(ctx->device->public_key));
+ break;
+ case WGDEVICE_A_LISTEN_PORT:
+ if (!mnl_attr_validate(attr, MNL_TYPE_U16))
+ ctx->device->listen_port = mnl_attr_get_u16(attr);
+ break;
+ case WGDEVICE_A_FWMARK:
+ if (!mnl_attr_validate(attr, MNL_TYPE_U32))
+ ctx->device->fwmark = mnl_attr_get_u32(attr);
+ break;
+ case WGDEVICE_A_PEERS:
+ return mnl_attr_parse_nested(attr, parse_peers, ctx);
+ default:
+ warn_unrecognized("kernel");
+ }
+
+ return MNL_CB_OK;
+}
+
+static int read_device_cb(const struct nlmsghdr *nlh, void *data)
+{
+ return mnl_attr_parse(nlh, sizeof(struct genlmsghdr), parse_device, data);
+}
+
+static void coalesce_peers(struct wgdevice *device)
+{
+ struct wgallowedip *allowedip;
+ struct wgpeer *old_next_peer, *peer = device->first_peer;
+ while (peer && peer->next_peer) {
+ if (memcmp(peer->public_key, peer->next_peer->public_key, WG_KEY_LEN)) {
+ peer = peer->next_peer;
+ continue;
+ }
+ /* TODO: It would be more efficient to store the tail, rather than having to seek to the end each time. */
+ for (allowedip = peer->first_allowedip; allowedip && allowedip->next_allowedip; allowedip = allowedip->next_allowedip);
+
+ if (!allowedip)
+ peer->first_allowedip = peer->next_peer->first_allowedip;
+ else
+ allowedip->next_allowedip = peer->next_peer->first_allowedip;
+ old_next_peer = peer->next_peer;
+ peer->next_peer = old_next_peer->next_peer;
+ free(old_next_peer);
+ }
+}
+
+static int kernel_get_device(struct wgdevice **dev, const char *interface)
+{
+ int ret = 0;
+ struct nlmsghdr *nlh;
+ struct mnlg_socket *nlg;
+ struct get_device_ctx ctx = { 0 };
+
+ *dev = ctx.device = calloc(1, sizeof(struct wgdevice));
+ if (!*dev)
+ return -errno;
+
+ nlg= mnlg_socket_open(WG_GENL_NAME, WG_GENL_VERSION);
+ if (!nlg) {
+ free_wgdevice(*dev);
*dev = NULL;
+ return -errno;
}
+
+ nlh = mnlg_msg_prepare(nlg, WG_CMD_GET_DEVICE, NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP);
+ mnl_attr_put_strz(nlh, WGDEVICE_A_IFNAME, interface);
+ if (mnlg_socket_send(nlg, nlh) < 0) {
+ ret = -errno;
+ goto out;
+ }
+ errno = 0;
+ if (mnlg_socket_recv_run(nlg, read_device_cb, &ctx) < 0) {
+ ret = errno ? -errno : -EINVAL;
+ goto out;
+ }
+ coalesce_peers(*dev);
+
out:
+ if (nlg)
+ mnlg_socket_close(nlg);
+ if (ret) {
+ free_wgdevice(*dev);
+ *dev = NULL;
+ }
errno = -ret;
return ret;
}
@@ -611,7 +913,7 @@ out:
/* first\0second\0third\0forth\0last\0\0 */
char *ipc_list_devices(void)
{
- struct inflatable_buffer buffer = { .len = 4096 };
+ struct inflatable_buffer buffer = { .len = MNL_SOCKET_BUFFER_SIZE };
int ret;
ret = -ENOMEM;
@@ -652,19 +954,10 @@ int ipc_get_device(struct wgdevice **dev, const char *interface)
int ipc_set_device(struct wgdevice *dev)
{
#ifdef __linux__
- if (userspace_has_wireguard_interface(dev->interface))
+ if (userspace_has_wireguard_interface(dev->name))
return userspace_set_device(dev);
return kernel_set_device(dev);
#else
return userspace_set_device(dev);
#endif
}
-
-bool ipc_has_device(const char *interface)
-{
-#ifdef __linux__
- return userspace_has_wireguard_interface(interface) || kernel_has_wireguard_interface(interface);
-#else
- return userspace_has_wireguard_interface(interface);
-#endif
-}
diff --git a/src/tools/ipc.h b/src/tools/ipc.h
index 2412610..cb660bb 100644
--- a/src/tools/ipc.h
+++ b/src/tools/ipc.h
@@ -10,6 +10,5 @@ struct wgdevice;
int ipc_set_device(struct wgdevice *dev);
int ipc_get_device(struct wgdevice **dev, const char *interface);
char *ipc_list_devices(void);
-bool ipc_has_device(const char *interface);
#endif
diff --git a/src/tools/mnlg.c b/src/tools/mnlg.c
new file mode 100644
index 0000000..ff70bdc
--- /dev/null
+++ b/src/tools/mnlg.c
@@ -0,0 +1,327 @@
+/* Copyright (C) 2017 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
+ *
+ * Original author: Jiri Pirko <jiri@mellanox.com> */
+
+#ifdef __linux__
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <time.h>
+#include <libmnl/libmnl.h>
+#include <linux/genetlink.h>
+
+#include "mnlg.h"
+
+struct mnlg_socket {
+ struct mnl_socket *nl;
+ char *buf;
+ uint32_t id;
+ uint8_t version;
+ unsigned int seq;
+ unsigned int portid;
+};
+
+static struct nlmsghdr *__mnlg_msg_prepare(struct mnlg_socket *nlg, uint8_t cmd,
+ uint16_t flags, uint32_t id,
+ uint8_t version)
+{
+ struct nlmsghdr *nlh;
+ struct genlmsghdr *genl;
+
+ nlh = mnl_nlmsg_put_header(nlg->buf);
+ nlh->nlmsg_type = id;
+ nlh->nlmsg_flags = flags;
+ nlg->seq = time(NULL);
+ nlh->nlmsg_seq = nlg->seq;
+
+ genl = mnl_nlmsg_put_extra_header(nlh, sizeof(struct genlmsghdr));
+ genl->cmd = cmd;
+ genl->version = version;
+
+ return nlh;
+}
+
+struct nlmsghdr *mnlg_msg_prepare(struct mnlg_socket *nlg, uint8_t cmd,
+ uint16_t flags)
+{
+ return __mnlg_msg_prepare(nlg, cmd, flags, nlg->id, nlg->version);
+}
+
+int mnlg_socket_send(struct mnlg_socket *nlg, const struct nlmsghdr *nlh)
+{
+ return mnl_socket_sendto(nlg->nl, nlh, nlh->nlmsg_len);
+}
+
+static int mnlg_cb_noop(const struct nlmsghdr *nlh, void *data)
+{
+ (void)nlh;
+ (void)data;
+ return MNL_CB_OK;
+}
+
+static int mnlg_cb_error(const struct nlmsghdr *nlh, void *data)
+{
+ const struct nlmsgerr *err = mnl_nlmsg_get_payload(nlh);
+ (void)data;
+
+ if (nlh->nlmsg_len < mnl_nlmsg_size(sizeof(struct nlmsgerr))) {
+ errno = EBADMSG;
+ return MNL_CB_ERROR;
+ }
+ /* Netlink subsystems returns the errno value with different signess */
+ if (err->error < 0)
+ errno = -err->error;
+ else
+ errno = err->error;
+
+ return err->error == 0 ? MNL_CB_STOP : MNL_CB_ERROR;
+}
+
+static int mnlg_cb_stop(const struct nlmsghdr *nlh, void *data)
+{
+ (void)data;
+ if (nlh->nlmsg_flags & NLM_F_MULTI && nlh->nlmsg_len == mnl_nlmsg_size(sizeof(int))) {
+ int error = *(int *)mnl_nlmsg_get_payload(nlh);
+ /* Netlink subsystems returns the errno value with different signess */
+ if (error < 0)
+ errno = -error;
+ else
+ errno = error;
+
+ return error == 0 ? MNL_CB_STOP : MNL_CB_ERROR;
+ }
+ return MNL_CB_STOP;
+}
+
+static mnl_cb_t mnlg_cb_array[] = {
+ [NLMSG_NOOP] = mnlg_cb_noop,
+ [NLMSG_ERROR] = mnlg_cb_error,
+ [NLMSG_DONE] = mnlg_cb_stop,
+ [NLMSG_OVERRUN] = mnlg_cb_noop,
+};
+
+int mnlg_socket_recv_run(struct mnlg_socket *nlg, mnl_cb_t data_cb, void *data)
+{
+ int err;
+
+ do {
+ err = mnl_socket_recvfrom(nlg->nl, nlg->buf,
+ MNL_SOCKET_BUFFER_SIZE);
+ if (err <= 0)
+ break;
+ err = mnl_cb_run2(nlg->buf, err, nlg->seq, nlg->portid,
+ data_cb, data, mnlg_cb_array, sizeof(mnlg_cb_array) / sizeof(mnlg_cb_array[0]));
+ } while (err > 0);
+
+ return err;
+}
+
+struct group_info {
+ bool found;
+ uint32_t id;
+ const char *name;
+};
+
+static int parse_mc_grps_cb(const struct nlattr *attr, void *data)
+{
+ const struct nlattr **tb = data;
+ int type = mnl_attr_get_type(attr);
+
+ if (mnl_attr_type_valid(attr, CTRL_ATTR_MCAST_GRP_MAX) < 0)
+ return MNL_CB_OK;
+
+ switch (type) {
+ case CTRL_ATTR_MCAST_GRP_ID:
+ if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0)
+ return MNL_CB_ERROR;
+ break;
+ case CTRL_ATTR_MCAST_GRP_NAME:
+ if (mnl_attr_validate(attr, MNL_TYPE_STRING) < 0)
+ return MNL_CB_ERROR;
+ break;
+ }
+ tb[type] = attr;
+ return MNL_CB_OK;
+}
+
+static void parse_genl_mc_grps(struct nlattr *nested,
+ struct group_info *group_info)
+{
+ struct nlattr *pos;
+ const char *name;
+
+ mnl_attr_for_each_nested(pos, nested) {
+ struct nlattr *tb[CTRL_ATTR_MCAST_GRP_MAX + 1] = {};
+
+ mnl_attr_parse_nested(pos, parse_mc_grps_cb, tb);
+ if (!tb[CTRL_ATTR_MCAST_GRP_NAME] ||
+ !tb[CTRL_ATTR_MCAST_GRP_ID])
+ continue;
+
+ name = mnl_attr_get_str(tb[CTRL_ATTR_MCAST_GRP_NAME]);
+ if (strcmp(name, group_info->name) != 0)
+ continue;
+
+ group_info->id = mnl_attr_get_u32(tb[CTRL_ATTR_MCAST_GRP_ID]);
+ group_info->found = true;
+ }
+}
+
+static int get_group_id_attr_cb(const struct nlattr *attr, void *data)
+{
+ const struct nlattr **tb = data;
+ int type = mnl_attr_get_type(attr);
+
+ if (mnl_attr_type_valid(attr, CTRL_ATTR_MAX) < 0)
+ return MNL_CB_ERROR;
+
+ if (type == CTRL_ATTR_MCAST_GROUPS &&
+ mnl_attr_validate(attr, MNL_TYPE_NESTED) < 0)
+ return MNL_CB_ERROR;
+ tb[type] = attr;
+ return MNL_CB_OK;
+}
+
+static int get_group_id_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct group_info *group_info = data;
+ struct nlattr *tb[CTRL_ATTR_MAX + 1] = { 0 };
+
+ mnl_attr_parse(nlh, sizeof(struct genlmsghdr), get_group_id_attr_cb, tb);
+ if (!tb[CTRL_ATTR_MCAST_GROUPS])
+ return MNL_CB_ERROR;
+ parse_genl_mc_grps(tb[CTRL_ATTR_MCAST_GROUPS], group_info);
+ return MNL_CB_OK;
+}
+
+int mnlg_socket_group_add(struct mnlg_socket *nlg, const char *group_name)
+{
+ struct nlmsghdr *nlh;
+ struct group_info group_info;
+ int err;
+
+ nlh = __mnlg_msg_prepare(nlg, CTRL_CMD_GETFAMILY,
+ NLM_F_REQUEST | NLM_F_ACK, GENL_ID_CTRL, 1);
+ mnl_attr_put_u32(nlh, CTRL_ATTR_FAMILY_ID, nlg->id);
+
+ err = mnlg_socket_send(nlg, nlh);
+ if (err < 0)
+ return err;
+
+ group_info.found = false;
+ group_info.name = group_name;
+ err = mnlg_socket_recv_run(nlg, get_group_id_cb, &group_info);
+ if (err < 0)
+ return err;
+
+ if (!group_info.found) {
+ errno = ENOENT;
+ return -1;
+ }
+
+ err = mnl_socket_setsockopt(nlg->nl, NETLINK_ADD_MEMBERSHIP,
+ &group_info.id, sizeof(group_info.id));
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+static int get_family_id_attr_cb(const struct nlattr *attr, void *data)
+{
+ const struct nlattr **tb = data;
+ int type = mnl_attr_get_type(attr);
+
+ if (mnl_attr_type_valid(attr, CTRL_ATTR_MAX) < 0)
+ return MNL_CB_ERROR;
+
+ if (type == CTRL_ATTR_FAMILY_ID &&
+ mnl_attr_validate(attr, MNL_TYPE_U16) < 0)
+ return MNL_CB_ERROR;
+ tb[type] = attr;
+ return MNL_CB_OK;
+}
+
+static int get_family_id_cb(const struct nlmsghdr *nlh, void *data)
+{
+ uint32_t *p_id = data;
+ struct nlattr *tb[CTRL_ATTR_MAX + 1] = { 0 };
+
+ mnl_attr_parse(nlh, sizeof(struct genlmsghdr), get_family_id_attr_cb, tb);
+ if (!tb[CTRL_ATTR_FAMILY_ID])
+ return MNL_CB_ERROR;
+ *p_id = mnl_attr_get_u16(tb[CTRL_ATTR_FAMILY_ID]);
+ return MNL_CB_OK;
+}
+
+struct mnlg_socket *mnlg_socket_open(const char *family_name, uint8_t version)
+{
+ struct mnlg_socket *nlg;
+ struct nlmsghdr *nlh;
+ int err;
+
+ nlg = malloc(sizeof(*nlg));
+ if (!nlg)
+ return NULL;
+
+ err = -ENOMEM;
+ nlg->buf = malloc(MNL_SOCKET_BUFFER_SIZE);
+ if (!nlg->buf)
+ goto err_buf_alloc;
+
+ nlg->nl = mnl_socket_open(NETLINK_GENERIC);
+ if (!nlg->nl) {
+ err = -errno;
+ goto err_mnl_socket_open;
+ }
+
+ if (mnl_socket_bind(nlg->nl, 0, MNL_SOCKET_AUTOPID) < 0) {
+ err = -errno;
+ goto err_mnl_socket_bind;
+ }
+
+ nlg->portid = mnl_socket_get_portid(nlg->nl);
+
+ nlh = __mnlg_msg_prepare(nlg, CTRL_CMD_GETFAMILY,
+ NLM_F_REQUEST | NLM_F_ACK, GENL_ID_CTRL, 1);
+ mnl_attr_put_strz(nlh, CTRL_ATTR_FAMILY_NAME, family_name);
+
+ if (mnlg_socket_send(nlg, nlh) < 0) {
+ err = -errno;
+ goto err_mnlg_socket_send;
+ }
+
+ errno = 0;
+ if (mnlg_socket_recv_run(nlg, get_family_id_cb, &nlg->id) < 0) {
+ errno = errno == ENOENT ? EPROTONOSUPPORT : errno;
+ err = errno ? -errno : -ENOSYS;
+ goto err_mnlg_socket_recv_run;
+ }
+
+ nlg->version = version;
+ errno = 0;
+ return nlg;
+
+err_mnlg_socket_recv_run:
+err_mnlg_socket_send:
+err_mnl_socket_bind:
+ mnl_socket_close(nlg->nl);
+err_mnl_socket_open:
+ free(nlg->buf);
+err_buf_alloc:
+ free(nlg);
+ errno = -err;
+ return NULL;
+}
+
+void mnlg_socket_close(struct mnlg_socket *nlg)
+{
+ mnl_socket_close(nlg->nl);
+ free(nlg->buf);
+ free(nlg);
+}
+
+#endif
diff --git a/src/tools/mnlg.h b/src/tools/mnlg.h
new file mode 100644
index 0000000..46c53ba
--- /dev/null
+++ b/src/tools/mnlg.h
@@ -0,0 +1,22 @@
+/* Copyright (C) 2017 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
+ *
+ * Original author: Jiri Pirko <jiri@mellanox.com> */
+
+#ifndef MNLG_H
+#define MNLG_H
+#ifdef __linux__
+
+#include <libmnl/libmnl.h>
+
+struct mnlg_socket;
+
+struct nlmsghdr *mnlg_msg_prepare(struct mnlg_socket *nlg, uint8_t cmd,
+ uint16_t flags);
+int mnlg_socket_send(struct mnlg_socket *nlg, const struct nlmsghdr *nlh);
+int mnlg_socket_recv_run(struct mnlg_socket *nlg, mnl_cb_t data_cb, void *data);
+int mnlg_socket_group_add(struct mnlg_socket *nlg, const char *group_name);
+struct mnlg_socket *mnlg_socket_open(const char *family_name, uint8_t version);
+void mnlg_socket_close(struct mnlg_socket *nlg);
+
+#endif
+#endif
diff --git a/src/tools/set.c b/src/tools/set.c
index 497edcc..57a6428 100644
--- a/src/tools/set.c
+++ b/src/tools/set.c
@@ -3,9 +3,11 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include "subcommands.h"
+
+#include "containers.h"
#include "config.h"
#include "ipc.h"
+#include "subcommands.h"
int set_main(int argc, char *argv[])
{
@@ -17,10 +19,11 @@ int set_main(int argc, char *argv[])
return 1;
}
- if (!config_read_cmd(&device, argv + 2, argc - 2))
+ device = config_read_cmd(argv + 2, argc - 2);
+ if (!device)
goto cleanup;
- strncpy(device->interface, argv[1], IFNAMSIZ - 1);
- device->interface[IFNAMSIZ - 1] = 0;
+ strncpy(device->name, argv[1], IFNAMSIZ - 1);
+ device->name[IFNAMSIZ - 1] = 0;
if (ipc_set_device(device) != 0) {
perror("Unable to set device");
@@ -30,6 +33,6 @@ int set_main(int argc, char *argv[])
ret = 0;
cleanup:
- free(device);
+ free_wgdevice(device);
return ret;
}
diff --git a/src/tools/setconf.c b/src/tools/setconf.c
index c70edab..1857ff6 100644
--- a/src/tools/setconf.c
+++ b/src/tools/setconf.c
@@ -5,6 +5,7 @@
#include <stdlib.h>
#include <string.h>
+#include "containers.h"
#include "config.h"
#include "ipc.h"
#include "subcommands.h"
@@ -28,7 +29,7 @@ int setconf_main(int argc, char *argv[])
perror("fopen");
return 1;
}
- if (!config_read_init(&ctx, &device, !strcmp(argv[0], "addconf"))) {
+ if (!config_read_init(&ctx, !strcmp(argv[0], "addconf"))) {
fclose(config_input);
return 1;
}
@@ -38,12 +39,13 @@ int setconf_main(int argc, char *argv[])
goto cleanup;
}
}
- if (!config_read_finish(&ctx) || !device) {
+ device = config_read_finish(&ctx);
+ if (!device) {
fprintf(stderr, "Invalid configuration\n");
goto cleanup;
}
- strncpy(device->interface, argv[1], IFNAMSIZ - 1);
- device->interface[IFNAMSIZ - 1] = 0;
+ strncpy(device->name, argv[1], IFNAMSIZ - 1);
+ device->name[IFNAMSIZ - 1] = 0;
if (ipc_set_device(device) != 0) {
perror("Unable to set device");
@@ -56,6 +58,6 @@ cleanup:
if (config_input)
fclose(config_input);
free(config_buffer);
- free(device);
+ free_wgdevice(device);
return ret;
}
diff --git a/src/tools/show.c b/src/tools/show.c
index 6e5de96..476d002 100644
--- a/src/tools/show.c
+++ b/src/tools/show.c
@@ -13,11 +13,11 @@
#include <time.h>
#include <netdb.h>
+#include "containers.h"
#include "ipc.h"
-#include "subcommands.h"
#include "terminal.h"
#include "encoding.h"
-#include "../uapi.h"
+#include "subcommands.h"
static int peer_cmp(const void *first, const void *second)
{
@@ -37,42 +37,29 @@ static int peer_cmp(const void *first, const void *second)
return 0;
}
+/* This, hilariously, is not the right way to sort a linked list... */
static void sort_peers(struct wgdevice *device)
{
- uint8_t *new_device, *pos;
- struct wgpeer **peers;
- struct wgpeer *peer;
- size_t i, len;
+ size_t peer_count = 0, i = 0;
+ struct wgpeer *peer, **peers;
- peers = calloc(device->num_peers, sizeof(struct wgpeer *));
- if (!peers)
+ for_each_wgpeer (device, peer)
+ ++peer_count;
+ if (!peer_count)
return;
-
- len = sizeof(struct wgdevice);
- for_each_wgpeer(device, peer, i)
- len += sizeof(struct wgpeer) + (peer->num_ipmasks * sizeof(struct wgipmask));
- pos = new_device = malloc(len);
- if (!new_device) {
- free(peers);
+ peers = calloc(peer_count, sizeof(struct wgpeer *));
+ if (!peers)
return;
- }
-
- memcpy(pos, device, sizeof(struct wgdevice));
- pos += sizeof(struct wgdevice);
-
- for_each_wgpeer(device, peer, i)
- peers[i] = peer;
-
- qsort(peers, device->num_peers, sizeof(struct wgpeer *), peer_cmp);
- for (i = 0; i < device->num_peers; ++i) {
- len = sizeof(struct wgpeer) + (peers[i]->num_ipmasks * sizeof(struct wgipmask));
- memcpy(pos, peers[i], len);
- pos += len;
+ for_each_wgpeer (device, peer)
+ peers[i++] = peer;
+ qsort(peers, peer_count, sizeof(struct wgpeer *), peer_cmp);
+ device->first_peer = peers[0];
+ peers[0]->next_peer = NULL;
+ for (i = 1; i < peer_count; ++i) {
+ peers[i - 1]->next_peer = peers[i];
+ peers[i]->next_peer = NULL;
}
free(peers);
-
- memcpy(device, new_device, pos - new_device);
- free(new_device);
}
static char *key(const uint8_t key[static WG_KEY_LEN])
@@ -92,7 +79,7 @@ static char *masked_key(const uint8_t masked_key[static WG_KEY_LEN])
return "(hidden)";
}
-static char *ip(const struct wgipmask *ip)
+static char *ip(const struct wgallowedip *ip)
{
static char buf[INET6_ADDRSTRLEN + 1];
memset(buf, 0, INET6_ADDRSTRLEN + 1);
@@ -204,34 +191,33 @@ static void show_usage(void)
static void pretty_print(struct wgdevice *device)
{
- size_t i, j;
struct wgpeer *peer;
- struct wgipmask *ipmask;
+ struct wgallowedip *allowedip;
terminal_printf(TERMINAL_RESET);
- terminal_printf(TERMINAL_FG_GREEN TERMINAL_BOLD "interface" TERMINAL_RESET ": " TERMINAL_FG_GREEN "%s" TERMINAL_RESET "\n", device->interface);
+ terminal_printf(TERMINAL_FG_GREEN TERMINAL_BOLD "interface" TERMINAL_RESET ": " TERMINAL_FG_GREEN "%s" TERMINAL_RESET "\n", device->name);
if (!key_is_zero(device->public_key))
terminal_printf(" " TERMINAL_BOLD "public key" TERMINAL_RESET ": %s\n", key(device->public_key));
if (!key_is_zero(device->private_key))
terminal_printf(" " TERMINAL_BOLD "private key" TERMINAL_RESET ": %s\n", masked_key(device->private_key));
- if (device->port)
- terminal_printf(" " TERMINAL_BOLD "listening port" TERMINAL_RESET ": %u\n", device->port);
+ if (device->listen_port)
+ terminal_printf(" " TERMINAL_BOLD "listening port" TERMINAL_RESET ": %u\n", device->listen_port);
if (device->fwmark)
terminal_printf(" " TERMINAL_BOLD "fwmark" TERMINAL_RESET ": 0x%x\n", device->fwmark);
- if (device->num_peers) {
+ if (device->first_peer) {
sort_peers(device);
terminal_printf("\n");
}
- for_each_wgpeer(device, peer, i) {
+ for_each_wgpeer (device, peer) {
terminal_printf(TERMINAL_FG_YELLOW TERMINAL_BOLD "peer" TERMINAL_RESET ": " TERMINAL_FG_YELLOW "%s" TERMINAL_RESET "\n", key(peer->public_key));
if (!key_is_zero(peer->preshared_key))
terminal_printf(" " TERMINAL_BOLD "preshared key" TERMINAL_RESET ": %s\n", masked_key(peer->preshared_key));
if (peer->endpoint.addr.sa_family == AF_INET || peer->endpoint.addr.sa_family == AF_INET6)
terminal_printf(" " TERMINAL_BOLD "endpoint" TERMINAL_RESET ": %s\n", endpoint(&peer->endpoint.addr));
terminal_printf(" " TERMINAL_BOLD "allowed ips" TERMINAL_RESET ": ");
- if (peer->num_ipmasks) {
- for_each_wgipmask(peer, ipmask, j)
- terminal_printf("%s" TERMINAL_FG_CYAN "/" TERMINAL_RESET "%u%s", ip(ipmask), ipmask->cidr, j == (size_t)peer->num_ipmasks - 1 ? "\n" : ", ");
+ if (peer->first_allowedip) {
+ for_each_wgallowedip (peer, allowedip)
+ terminal_printf("%s" TERMINAL_FG_CYAN "/" TERMINAL_RESET "%u%s", ip(allowedip), allowedip->cidr, allowedip->next_allowedip ? ", " : "\n");
} else
terminal_printf("(none)\n");
if (peer->last_handshake_time.tv_sec)
@@ -243,38 +229,37 @@ static void pretty_print(struct wgdevice *device)
}
if (peer->persistent_keepalive_interval)
terminal_printf(" " TERMINAL_BOLD "persistent keepalive" TERMINAL_RESET ": %s\n", every(peer->persistent_keepalive_interval));
- if (i + 1 < device->num_peers)
+ if (peer->next_peer)
terminal_printf("\n");
}
}
static void dump_print(struct wgdevice *device, bool with_interface)
{
- size_t i, j;
struct wgpeer *peer;
- struct wgipmask *ipmask;
+ struct wgallowedip *allowedip;
if (with_interface)
- printf("%s\t", device->interface);
+ printf("%s\t", device->name);
printf("%s\t", key(device->private_key));
printf("%s\t", key(device->public_key));
- printf("%u\t", device->port);
+ printf("%u\t", device->listen_port);
if (device->fwmark)
printf("0x%x\n", device->fwmark);
else
printf("off\n");
- for_each_wgpeer(device, peer, i) {
+ for_each_wgpeer (device, peer) {
if (with_interface)
- printf("%s\t", device->interface);
+ printf("%s\t", device->name);
printf("%s\t", key(peer->public_key));
printf("%s\t", key(peer->preshared_key));
if (peer->endpoint.addr.sa_family == AF_INET || peer->endpoint.addr.sa_family == AF_INET6)
printf("%s\t", endpoint(&peer->endpoint.addr));
else
printf("(none)\t");
- if (peer->num_ipmasks) {
- for_each_wgipmask(peer, ipmask, j)
- printf("%s/%u%c", ip(ipmask), ipmask->cidr, j == (size_t)peer->num_ipmasks - 1 ? '\t' : ',');
+ if (peer->first_allowedip) {
+ for_each_wgallowedip (peer, allowedip)
+ printf("%s/%u%c", ip(allowedip), allowedip->cidr, allowedip->next_allowedip ? ',' : '\t');
} else
printf("(none)\t");
printf("%llu\t", (unsigned long long)peer->last_handshake_time.tv_sec);
@@ -288,32 +273,31 @@ static void dump_print(struct wgdevice *device, bool with_interface)
static bool ugly_print(struct wgdevice *device, const char *param, bool with_interface)
{
- size_t i, j;
struct wgpeer *peer;
- struct wgipmask *ipmask;
+ struct wgallowedip *allowedip;
if (!strcmp(param, "public-key")) {
if (with_interface)
- printf("%s\t", device->interface);
+ printf("%s\t", device->name);
printf("%s\n", key(device->public_key));
} else if (!strcmp(param, "private-key")) {
if (with_interface)
- printf("%s\t", device->interface);
+ printf("%s\t", device->name);
printf("%s\n", key(device->private_key));
} else if (!strcmp(param, "listen-port")) {
if (with_interface)
- printf("%s\t", device->interface);
- printf("%u\n", device->port);
+ printf("%s\t", device->name);
+ printf("%u\n", device->listen_port);
} else if (!strcmp(param, "fwmark")) {
if (with_interface)
- printf("%s\t", device->interface);
+ printf("%s\t", device->name);
if (device->fwmark)
printf("0x%x\n", device->fwmark);
else
printf("off\n");
} else if (!strcmp(param, "endpoints")) {
if (with_interface)
- printf("%s\t", device->interface);
- for_each_wgpeer(device, peer, i) {
+ printf("%s\t", device->name);
+ for_each_wgpeer (device, peer) {
printf("%s\t", key(peer->public_key));
if (peer->endpoint.addr.sa_family == AF_INET || peer->endpoint.addr.sa_family == AF_INET6)
printf("%s\n", endpoint(&peer->endpoint.addr));
@@ -321,48 +305,48 @@ static bool ugly_print(struct wgdevice *device, const char *param, bool with_int
printf("(none)\n");
}
} else if (!strcmp(param, "allowed-ips")) {
- for_each_wgpeer(device, peer, i) {
+ for_each_wgpeer (device, peer) {
if (with_interface)
- printf("%s\t", device->interface);
+ printf("%s\t", device->name);
printf("%s\t", key(peer->public_key));
- if (peer->num_ipmasks) {
- for_each_wgipmask(peer, ipmask, j)
- printf("%s/%u%c", ip(ipmask), ipmask->cidr, j == (size_t)peer->num_ipmasks - 1 ? '\n' : ' ');
+ if (peer->first_allowedip) {
+ for_each_wgallowedip (peer, allowedip)
+ printf("%s/%u%c", ip(allowedip), allowedip->cidr, allowedip->next_allowedip ? ' ' : '\n');
} else
printf("(none)\n");
}
} else if (!strcmp(param, "latest-handshakes")) {
- for_each_wgpeer(device, peer, i) {
+ for_each_wgpeer (device, peer) {
if (with_interface)
- printf("%s\t", device->interface);
+ printf("%s\t", device->name);
printf("%s\t%llu\n", key(peer->public_key), (unsigned long long)peer->last_handshake_time.tv_sec);
}
} else if (!strcmp(param, "transfer")) {
- for_each_wgpeer(device, peer, i) {
+ for_each_wgpeer (device, peer) {
if (with_interface)
- printf("%s\t", device->interface);
+ printf("%s\t", device->name);
printf("%s\t%" PRIu64 "\t%" PRIu64 "\n", key(peer->public_key), (uint64_t)peer->rx_bytes, (uint64_t)peer->tx_bytes);
}
} else if (!strcmp(param, "persistent-keepalive")) {
- for_each_wgpeer(device, peer, i) {
+ for_each_wgpeer (device, peer) {
if (with_interface)
- printf("%s\t", device->interface);
+ printf("%s\t", device->name);
if (peer->persistent_keepalive_interval)
printf("%s\t%u\n", key(peer->public_key), peer->persistent_keepalive_interval);
else
printf("%s\toff\n", key(peer->public_key));
}
} else if (!strcmp(param, "preshared-keys")) {
- for_each_wgpeer(device, peer, i) {
+ for_each_wgpeer (device, peer) {
if (with_interface)
- printf("%s\t", device->interface);
+ printf("%s\t", device->name);
printf("%s\t", key(peer->public_key));
printf("%s\n", key(peer->preshared_key));
}
} else if (!strcmp(param, "peers")) {
- for_each_wgpeer(device, peer, i) {
+ for_each_wgpeer (device, peer) {
if (with_interface)
- printf("%s\t", device->interface);
+ printf("%s\t", device->name);
printf("%s\n", key(peer->public_key));
}
} else if (!strcmp(param, "dump"))
@@ -401,7 +385,7 @@ int show_main(int argc, char *argv[])
if (argc == 3) {
if (!ugly_print(device, argv[2], true)) {
ret = 1;
- free(device);
+ free_wgdevice(device);
break;
}
} else {
@@ -409,7 +393,7 @@ int show_main(int argc, char *argv[])
if (strlen(interface + len + 1))
printf("\n");
}
- free(device);
+ free_wgdevice(device);
}
free(interfaces);
} else if (!strcmp(argv[1], "interfaces")) {
@@ -431,14 +415,8 @@ int show_main(int argc, char *argv[])
show_usage();
else {
struct wgdevice *device = NULL;
- if (!ipc_has_device(argv[1])) {
- fprintf(stderr, "`%s` is not a valid WireGuard interface\n", argv[1]);
- show_usage();
- return 1;
- }
if (ipc_get_device(&device, argv[1]) < 0) {
perror("Unable to get device");
- show_usage();
return 1;
}
if (argc == 3) {
@@ -446,7 +424,7 @@ int show_main(int argc, char *argv[])
ret = 1;
} else
pretty_print(device);
- free(device);
+ free_wgdevice(device);
}
return ret;
}
diff --git a/src/tools/showconf.c b/src/tools/showconf.c
index 09dc2ec..51f9a6f 100644
--- a/src/tools/showconf.c
+++ b/src/tools/showconf.c
@@ -9,10 +9,10 @@
#include <stdlib.h>
#include <netdb.h>
-#include "subcommands.h"
+#include "containers.h"
#include "encoding.h"
#include "ipc.h"
-#include "../uapi.h"
+#include "subcommands.h"
int showconf_main(int argc, char *argv[])
{
@@ -20,8 +20,7 @@ int showconf_main(int argc, char *argv[])
char ip[INET6_ADDRSTRLEN];
struct wgdevice *device = NULL;
struct wgpeer *peer;
- struct wgipmask *ipmask;
- size_t i, j;
+ struct wgallowedip *allowedip;
int ret = 1;
if (argc != 2) {
@@ -29,20 +28,14 @@ int showconf_main(int argc, char *argv[])
return 1;
}
- if (!ipc_has_device(argv[1])) {
- fprintf(stderr, "`%s` is not a valid WireGuard interface\n", argv[1]);
- fprintf(stderr, "Usage: %s %s <interface>\n", PROG_NAME, argv[0]);
- return 1;
- }
-
if (ipc_get_device(&device, argv[1])) {
perror("Unable to get device");
goto cleanup;
}
printf("[Interface]\n");
- if (device->port)
- printf("ListenPort = %u\n", device->port);
+ if (device->listen_port)
+ printf("ListenPort = %u\n", device->listen_port);
if (device->fwmark)
printf("FwMark = 0x%x\n", device->fwmark);
if (!key_is_zero(device->private_key)) {
@@ -50,29 +43,29 @@ int showconf_main(int argc, char *argv[])
printf("PrivateKey = %s\n", base64);
}
printf("\n");
- for_each_wgpeer(device, peer, i) {
+ for_each_wgpeer (device, peer) {
key_to_base64(base64, peer->public_key);
printf("[Peer]\nPublicKey = %s\n", base64);
if (!key_is_zero(peer->preshared_key)) {
key_to_base64(base64, peer->preshared_key);
printf("PresharedKey = %s\n", base64);
}
- if (peer->num_ipmasks)
+ if (peer->first_allowedip)
printf("AllowedIPs = ");
- for_each_wgipmask(peer, ipmask, j) {
- if (ipmask->family == AF_INET) {
- if (!inet_ntop(AF_INET, &ipmask->ip4, ip, INET6_ADDRSTRLEN))
+ for_each_wgallowedip (peer, allowedip) {
+ if (allowedip->family == AF_INET) {
+ if (!inet_ntop(AF_INET, &allowedip->ip4, ip, INET6_ADDRSTRLEN))
continue;
- } else if (ipmask->family == AF_INET6) {
- if (!inet_ntop(AF_INET6, &ipmask->ip6, ip, INET6_ADDRSTRLEN))
+ } else if (allowedip->family == AF_INET6) {
+ if (!inet_ntop(AF_INET6, &allowedip->ip6, ip, INET6_ADDRSTRLEN))
continue;
} else
continue;
- printf("%s/%d", ip, ipmask->cidr);
- if (j + 1 < (size_t)peer->num_ipmasks)
+ printf("%s/%d", ip, allowedip->cidr);
+ if (allowedip->next_allowedip)
printf(", ");
}
- if (peer->num_ipmasks)
+ if (peer->first_allowedip)
printf("\n");
if (peer->endpoint.addr.sa_family == AF_INET || peer->endpoint.addr.sa_family == AF_INET6) {
@@ -94,12 +87,12 @@ int showconf_main(int argc, char *argv[])
if (peer->persistent_keepalive_interval)
printf("PersistentKeepalive = %u\n", peer->persistent_keepalive_interval);
- if (i + 1 < device->num_peers)
+ if (peer->next_peer)
printf("\n");
}
ret = 0;
cleanup:
- free(device);
+ free_wgdevice(device);
return ret;
}
diff --git a/src/uapi.h b/src/uapi.h
deleted file mode 100644
index e699025..0000000
--- a/src/uapi.h
+++ /dev/null
@@ -1,166 +0,0 @@
-/* Copyright (C) 2015-2017 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
- *
- * Userspace API for WireGuard
- * ---------------------------
- *
- * ioctl(WG_GET_DEVICE, { .ifr_name: "wg0", .ifr_data: NULL }):
- *
- * Returns the number of bytes required to hold the peers of a device (`ret_peers_size`).
- *
- * ioctl(WG_GET_DEVICE, { .ifr_name: "wg0", .ifr_data: user_pointer }):
- *
- * Retrevies device info, peer info, and ipmask info.
- *
- * `user_pointer` must point to a region of memory of size `sizeof(struct wgdevice) + ret_peers_size`
- * and containing the structure `struct wgdevice { .peers_size: ret_peers_size }`.
- *
- * Writes to `user_pointer` a succession of structs:
- *
- * struct wgdevice { .num_peers = 3 }
- * struct wgpeer { .num_ipmasks = 4 }
- * struct wgipmask
- * struct wgipmask
- * struct wgipmask
- * struct wgipmask
- * struct wgpeer { .num_ipmasks = 2 }
- * struct wgipmask
- * struct wgipmask
- * struct wgpeer { .num_ipmasks = 0 }
- *
- * Returns 0 on success. Returns -EMSGSIZE if there is too much data for the size of passed-in
- * memory, in which case, this should be recalculated using the call above. Returns -errno if
- * another error occured.
- *
- * ioctl(WG_SET_DEVICE, { .ifr_name: "wg0", .ifr_data: user_pointer }):
- *
- * Sets device info, peer info, and ipmask info.
- *
- * `user_pointer` must point to a region of memory containing a succession of structs:
- *
- * struct wgdevice { .num_peers = 3 }
- * struct wgpeer { .num_ipmasks = 4 }
- * struct wgipmask
- * struct wgipmask
- * struct wgipmask
- * struct wgipmask
- * struct wgpeer { .num_ipmasks = 2 }
- * struct wgipmask
- * struct wgipmask
- * struct wgpeer { .num_ipmasks = 0 }
- *
- * If `wgdevice->flags & WGDEVICE_REPLACE_PEERS` is true, removes all peers of device before adding new ones.
- * If `wgpeer->flags & WGPEER_REMOVE_ME` is true, the peer identified by `wgpeer->public_key` is removed.
- * If `wgpeer->flags & WGPEER_REPLACE_IPMASKS` is true, removes all ipmasks before adding new ones.
- * If `wgdevice->private_key` is filled with zeros, no action is taken on the private key.
- * If `wgdevice->preshared_key` is filled with zeros, no action is taken on the preshared key.
- * If `wgdevice->flags & WGDEVICE_REMOVE_PRIVATE_KEY` is true, the private key is removed.
- * If `wgdevice->flags & WGDEVICE_REMOVE_PRESHARED_KEY` is true, the preshared key is removed.
- *
- * Returns 0 on success, or -errno if an error occurred.
- */
-
-
-#ifndef WGUAPI_H
-#define WGUAPI_H
-
-#ifdef __linux__
-#include <linux/types.h>
-#else
-#include <stdint.h>
-typedef uint8_t __u8;
-typedef uint16_t __u16;
-typedef uint32_t __u32;
-typedef uint64_t __u64;
-typedef int32_t __s32;
-#endif
-#ifdef __KERNEL__
-#include <linux/time.h>
-#include <linux/socket.h>
-#else
-#include <net/if.h>
-#include <netinet/in.h>
-#include <sys/time.h>
-#include <sys/socket.h>
-#endif
-
-#define WG_GET_DEVICE (SIOCDEVPRIVATE + 0)
-#define WG_SET_DEVICE (SIOCDEVPRIVATE + 1)
-
-#define WG_KEY_LEN 32
-
-struct wgipmask {
- __s32 family;
- union {
- struct in_addr ip4;
- struct in6_addr ip6;
- };
- __u8 cidr;
-};
-
-enum {
- WGPEER_REMOVE_ME = (1 << 0),
- WGPEER_REPLACE_IPMASKS = (1 << 1),
- WGPEER_REMOVE_PRESHARED_KEY = (1 << 2)
-};
-
-struct wgpeer {
- __u8 public_key[WG_KEY_LEN]; /* Get/Set */
- __u8 preshared_key[WG_KEY_LEN]; /* Get/Set */
- __u32 flags; /* Set */
-
- union {
- struct sockaddr addr;
- struct sockaddr_in addr4;
- struct sockaddr_in6 addr6;
- } endpoint; /* Get/Set */
-
- struct timeval last_handshake_time; /* Get */
- __u64 rx_bytes, tx_bytes; /* Get */
- __u16 persistent_keepalive_interval; /* Get/Set -- 0 = off, 0xffff = unset */
-
- __u16 num_ipmasks; /* Get/Set */
-};
-
-enum {
- WGDEVICE_REPLACE_PEERS = (1 << 0),
- WGDEVICE_REMOVE_PRIVATE_KEY = (1 << 1),
- WGDEVICE_REMOVE_FWMARK = (1 << 2)
-};
-
-enum {
- WG_API_VERSION_MAGIC = 0xbeef0003
-};
-
-struct wgdevice {
- __u32 version_magic; /* Must be value of WG_API_VERSION_MAGIC */
- char interface[IFNAMSIZ]; /* Get */
- __u32 flags; /* Set */
-
- __u8 public_key[WG_KEY_LEN]; /* Get */
- __u8 private_key[WG_KEY_LEN]; /* Get/Set */
- __u32 fwmark; /* Get/Set */
- __u16 port; /* Get/Set */
-
- union {
- __u32 num_peers; /* Get/Set */
- __u32 peers_size; /* Get */
- };
-};
-
-/* These are simply for convenience in iterating. It allows you to write something like:
- *
- * for_each_wgpeer(device, peer, i) {
- * for_each_wgipmask(peer, ipmask, j) {
- * do_something_with_ipmask(ipmask);
- * }
- * }
- */
-#define for_each_wgpeer(__dev, __peer, __i) for ((__i) = 0, (__peer) = (struct wgpeer *)((uint8_t *)(__dev) + sizeof(struct wgdevice)); \
- (__i) < (__dev)->num_peers; \
- ++(__i), (__peer) = (struct wgpeer *)((uint8_t *)(__peer) + sizeof(struct wgpeer) + (sizeof(struct wgipmask) * (__peer)->num_ipmasks)))
-
-#define for_each_wgipmask(__peer, __ipmask, __i) for ((__i) = 0, (__ipmask) = (struct wgipmask *)((uint8_t *)(__peer) + sizeof(struct wgpeer)); \
- (__i) < (__peer)->num_ipmasks; \
- ++(__i), (__ipmask) = (struct wgipmask *)((uint8_t *)(__ipmask) + sizeof(struct wgipmask)))
-
-#endif
diff --git a/src/uapi/wireguard.h b/src/uapi/wireguard.h
new file mode 100644
index 0000000..9c260be
--- /dev/null
+++ b/src/uapi/wireguard.h
@@ -0,0 +1,199 @@
+/* Copyright (C) 2015-2017 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
+ *
+ * The following MIT license applies only to this file:
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this file, to deal in this file without restriction, including without
+ * limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of this file, and to permit persons to
+ * whom this file is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of this file.
+ *
+ * THIS FILE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THIS FILE OR THE USE OR OTHER DEALINGS IN THIS
+ * FILE.
+ *
+ * Documentation
+ * =============
+ *
+ * The below enums and macros are for interfacing with WireGuard, using generic
+ * netlink, with family WG_GENL_NAME and version WG_GENL_VERSION. It defines two
+ * methods: get and set. Note that while they share many common attributes, these
+ * two functions actually accept a slightly different set of inputs and outputs.
+ *
+ * WG_CMD_GET_DEVICE
+ * -----------------
+ *
+ * May only be called via NLM_F_REQUEST | NLM_F_DUMP. The command should contain
+ * one but not both of:
+ *
+ * WGDEVICE_A_IFINDEX: NLA_U32
+ * WGDEVICE_A_IFNAME: NLA_NUL_STRING, maxlen IFNAMESIZ - 1
+ *
+ * The kernel will then return several messages (NLM_F_MULTI) containing the following
+ * tree of nested items:
+ *
+ * WGDEVICE_A_IFINDEX: NLA_U32
+ * WGDEVICE_A_IFNAME: NLA_NUL_STRING, maxlen IFNAMESIZ - 1
+ * WGDEVICE_A_PRIVATE_KEY: len WG_KEY_LEN
+ * WGDEVICE_A_PUBLIC_KEY: len WG_KEY_LEN
+ * WGDEVICE_A_LISTEN_PORT: NLA_U16
+ * WGDEVICE_A_FWMARK: NLA_U32
+ * WGDEVICE_A_PEERS: NLA_NESTED
+ * 0: NLA_NESTED
+ * WGPEER_A_PUBLIC_KEY: len WG_KEY_LEN
+ * WGPEER_A_PRESHARED_KEY: len WG_KEY_LEN
+ * WGPEER_A_ENDPOINT: struct sockaddr_in or struct sockaddr_in6
+ * WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL: NLA_U16
+ * WGPEER_A_LAST_HANDSHAKE_TIME: struct timeval
+ * WGPEER_A_RX_BYTES: NLA_U64
+ * WGPEER_A_TX_BYTES: NLA_U64
+ * WGPEER_A_ALLOWEDIPS: NLA_NESTED
+ * 0: NLA_NESTED
+ * WGALLOWEDIP_A_FAMILY: NLA_U16
+ * WGALLOWEDIP_A_IPADDR: struct in_addr or struct in6_addr
+ * WGALLOWEDIP_A_CIDR_MASK: NLA_U8
+ * 1: NLA_NESTED
+ * ...
+ * 2: NLA_NESTED
+ * ...
+ * ...
+ * 1: NLA_NESTED
+ * ...
+ * ...
+ *
+ * It is possible that all of the allowed IPs of a single peer will not
+ * fit within a single netlink message. In that case, the same peer will
+ * be written in the following message, except it will only contain
+ * WGPEER_A_PUBLIC_KEY and WGPEER_A_ALLOWEDIPS. This may occur several
+ * times in a row for the same peer. It is then up to the receiver to
+ * coalesce adjacent peers. Likewise, it is possible that all peers will
+ * not fit within a single message. So, subsequent peers will be sent
+ * in following messages, except those will only contain WGDEVICE_A_IFNAME
+ * and WGDEVICE_A_PEERS. It is then up to the receiver to coalesce these
+ * messages to form the complete list of peers.
+ *
+ * Since this is an NLA_F_DUMP command, the final message will always be
+ * NLMSG_DONE, even if an error occurs. However, this NLMSG_DONE message
+ * contains an integer error code. It is either zero or a negative error
+ * code corresponding to the errno.
+ *
+ * WG_CMD_SET_DEVICE
+ * -----------------
+ *
+ * May only be called via NLM_F_REQUEST. The command should contain the following
+ * tree of nested items, containing one but not both of WGDEVICE_A_IFINDEX
+ * and WGDEVICE_A_IFNAME:
+ *
+ * WGDEVICE_A_IFINDEX: NLA_U32
+ * WGDEVICE_A_IFNAME: NLA_NUL_STRING, maxlen IFNAMESIZ - 1
+ * WGDEVICE_A_FLAGS: NLA_U32, 0 or WGDEVICE_F_REPLACE_PEERS if all current
+ * peers should be removed prior to adding the list below.
+ * WGDEVICE_A_PRIVATE_KEY: len WG_KEY_LEN, all zeros to remove
+ * WGDEVICE_A_LISTEN_PORT: NLA_U16, 0 to choose randomly
+ * WGDEVICE_A_FWMARK: NLA_U32, 0 to disable
+ * WGDEVICE_A_PEERS: NLA_NESTED
+ * 0: NLA_NESTED
+ * WGPEER_A_PUBLIC_KEY: len WG_KEY_LEN
+ * WGPEER_A_FLAGS: NLA_U32, 0 and/or WGPEER_F_REMOVE_ME if the specified peer
+ * should be removed rather than added/updated and/or
+ * WGPEER_F_REPLACE_ALLOWEDIPS if all current allowed IPs of
+ * this peer should be removed prior to adding the list below.
+ * WGPEER_A_PRESHARED_KEY: len WG_KEY_LEN, all zeros to remove
+ * WGPEER_A_ENDPOINT: struct sockaddr_in or struct sockaddr_in6
+ * WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL: NLA_U16, 0 to disable
+ * WGPEER_A_ALLOWEDIPS: NLA_NESTED
+ * 0: NLA_NESTED
+ * WGALLOWEDIP_A_FAMILY: NLA_U16
+ * WGALLOWEDIP_A_IPADDR: struct in_addr or struct in6_addr
+ * WGALLOWEDIP_A_CIDR_MASK: NLA_U8
+ * 1: NLA_NESTED
+ * ...
+ * 2: NLA_NESTED
+ * ...
+ * ...
+ * 1: NLA_NESTED
+ * ...
+ * ...
+ *
+ * It is possible that the amount of configuration data exceeds that of
+ * the maximum message length accepted by the kernel. In that case,
+ * several messages should be sent one after another, with each
+ * successive one filling in information not contained in the prior. Note
+ * that if WGDEVICE_F_REPLACE_PEERS is specified in the first message, it
+ * probably should not be specified in fragments that come after, so that
+ * the list of peers is only cleared the first time but appened after.
+ * Likewise for peers, if WGPEER_F_REPLACE_ALLOWEDIPS is specified in the
+ * first message of a peer, it likely should not be specified in subsequent
+ * fragments.
+ *
+ * If an error occurs, NLMSG_ERROR will reply containing an errno.
+ */
+
+#ifndef UAPI_WIREGUARD_H
+#define UAPI_WIREGUARD_H
+
+#define WG_GENL_NAME "wireguard"
+#define WG_GENL_VERSION 1
+
+#define WG_KEY_LEN 32
+
+enum wg_cmd {
+ WG_CMD_GET_DEVICE,
+ WG_CMD_SET_DEVICE,
+ __WG_CMD_MAX
+};
+#define WG_CMD_MAX (__WG_CMD_MAX - 1)
+
+enum wgdevice_flag {
+ WGDEVICE_F_REPLACE_PEERS = (1 << 0)
+};
+enum wgdevice_attribute {
+ WGDEVICE_A_UNSPEC,
+ WGDEVICE_A_IFINDEX,
+ WGDEVICE_A_IFNAME,
+ WGDEVICE_A_PRIVATE_KEY,
+ WGDEVICE_A_PUBLIC_KEY,
+ WGDEVICE_A_FLAGS,
+ WGDEVICE_A_LISTEN_PORT,
+ WGDEVICE_A_FWMARK,
+ WGDEVICE_A_PEERS,
+ __WGDEVICE_A_LAST
+};
+#define WGDEVICE_A_MAX (__WGDEVICE_A_LAST - 1)
+
+enum wgpeer_flag {
+ WGPEER_F_REMOVE_ME = (1 << 0),
+ WGPEER_F_REPLACE_ALLOWEDIPS = (1 << 1)
+};
+enum wgpeer_attribute {
+ WGPEER_A_UNSPEC,
+ WGPEER_A_PUBLIC_KEY,
+ WGPEER_A_PRESHARED_KEY,
+ WGPEER_A_FLAGS,
+ WGPEER_A_ENDPOINT,
+ WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL,
+ WGPEER_A_LAST_HANDSHAKE_TIME,
+ WGPEER_A_RX_BYTES,
+ WGPEER_A_TX_BYTES,
+ WGPEER_A_ALLOWEDIPS,
+ __WGPEER_A_LAST
+};
+#define WGPEER_A_MAX (__WGPEER_A_LAST - 1)
+
+enum wgallowedip_attribute {
+ WGALLOWEDIP_A_UNSPEC,
+ WGALLOWEDIP_A_FAMILY,
+ WGALLOWEDIP_A_IPADDR,
+ WGALLOWEDIP_A_CIDR_MASK,
+ __WGALLOWEDIP_A_LAST
+};
+#define WGALLOWEDIP_A_MAX (__WGALLOWEDIP_A_LAST - 1)
+
+#endif