diff options
author | Jason A. Donenfeld <Jason@zx2c4.com> | 2017-09-25 04:22:09 +0200 |
---|---|---|
committer | Jason A. Donenfeld <Jason@zx2c4.com> | 2017-10-02 02:45:53 +0200 |
commit | adc504c865ebe70cf112c5ecc150e081312180c3 (patch) | |
tree | a56297f841afdd1a44160edcd534fa5678b15f2f /src | |
parent | b3b65cf62fc7fb271f9a20456cbeb21a8fd95418 (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/Kbuild | 2 | ||||
-rw-r--r-- | src/Makefile | 4 | ||||
-rw-r--r-- | src/compat/compat.h | 37 | ||||
-rw-r--r-- | src/config.c | 353 | ||||
-rw-r--r-- | src/config.h | 13 | ||||
-rw-r--r-- | src/device.c | 24 | ||||
-rw-r--r-- | src/device.h | 1 | ||||
-rw-r--r-- | src/main.c | 11 | ||||
-rw-r--r-- | src/netlink.c | 494 | ||||
-rw-r--r-- | src/netlink.h | 9 | ||||
-rw-r--r-- | src/noise.c | 13 | ||||
-rw-r--r-- | src/peer.c | 2 | ||||
-rwxr-xr-x | src/tests/netns.sh | 39 | ||||
-rw-r--r-- | src/tests/qemu/Makefile | 8 | ||||
-rw-r--r-- | src/uapi.h | 166 | ||||
-rw-r--r-- | src/uapi/wireguard.h | 199 |
16 files changed, 805 insertions, 570 deletions
@@ -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; }; @@ -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); } @@ -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 |