summaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorJason A. Donenfeld <Jason@zx2c4.com>2017-09-25 04:22:09 +0200
committerJason A. Donenfeld <Jason@zx2c4.com>2017-10-02 02:45:53 +0200
commitadc504c865ebe70cf112c5ecc150e081312180c3 (patch)
treea56297f841afdd1a44160edcd534fa5678b15f2f /src
parentb3b65cf62fc7fb271f9a20456cbeb21a8fd95418 (diff)
netlink: switch from ioctl to netlink for configuration
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
Diffstat (limited to 'src')
-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/uapi.h166
-rw-r--r--src/uapi/wireguard.h199
16 files changed, 805 insertions, 570 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/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