From b16641e30c6d9d3de356ce8ba29fc84157ba07f0 Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Tue, 19 Jul 2016 15:26:56 +0200 Subject: [PATCH] wg: first additions of userspace integration This is designed to work with a server that follows this: struct sockaddr_un addr = { .sun_family = AF_UNIX, .sun_path = "/var/run/wireguard/wguserspace0.sock" }; int fd, ret; ssize_t len; socklen_t socklen; struct wgdevice *device; fd = socket(AF_UNIX, SOCK_DGRAM, 0); if (fd < 0) exit(1); if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) exit(1); for (;;) { /* First we look at how big the next message is, so we know how much to * allocate. Note on BSD you can instead use ioctl(fd, FIONREAD, &len). */ len = recv(fd, NULL, 0, MSG_PEEK | MSG_TRUNC); if (len < 0) { handle_error(); continue; } /* Next we allocate a buffer for the received data. */ device = NULL; if (len) { device = malloc(len); if (!device) { handle_error(); continue; } } /* Finally we receive the data, storing too the return address. */ socklen = sizeof(addr); len = recvfrom(fd, device, len, 0, (struct sockaddr *)&addr, (socklen_t *)&socklen); if (len < 0) { handle_error(); free(device); continue; } if (!len) { /* If len is zero, it's a "get" request, so we send our device back. */ device = get_current_wireguard_device(&len); sendto(fd, device, len, 0, (struct sockaddr *)&addr, socklen); } else { /* Otherwise, we just received a wgdevice, so we should "set" and send back the return status. */ ret = set_current_wireguard_device(device); sendto(fd, &ret, sizeof(ret), 0, (struct sockaddr *)&addr, socklen); free(device); } } Signed-off-by: Jason A. Donenfeld --- src/Makefile | 11 +- src/genkey.c | 6 +- src/kernel.c | 275 +++++++++++++++++++++++++++++++++++++++++++------ src/kernel.h | 8 +- src/set.c | 2 +- src/setconf.c | 2 +- src/show.c | 10 +- src/showconf.c | 4 +- 8 files changed, 268 insertions(+), 50 deletions(-) diff --git a/src/Makefile b/src/Makefile index 060ae18..0436f69 100644 --- a/src/Makefile +++ b/src/Makefile @@ -3,12 +3,19 @@ DESTDIR ?= BINDIR ?= $(PREFIX)/bin LIBDIR ?= $(PREFIX)/lib MANDIR ?= $(PREFIX)/share/man +RUNSTATEDIR ?= /var/run -CFLAGS += $(shell pkg-config --cflags libmnl 2>/dev/null) CFLAGS += -std=gnu11 CFLAGS += -pedantic -Wall -Wextra CFLAGS += -MMD -LDLIBS += -lresolv $(shell pkg-config --libs libmnl 2>/dev/null || echo -lmnl) +CFLAGS += -DRUNSTATEDIR="\"$(RUNSTATEDIR)\"" +LDLIBS += -lresolv +ifeq ($(shell uname -s),Linux) +LIBMNL_CFLAGS := $(shell pkg-config --cflags libmnl 2>/dev/null) +LIBMNL_LDLIBS := $(shell pkg-config --libs libmnl 2>/dev/null || echo -lmnl) +CFLAGS += $(LIBMNL_CFLAGS) +LDLIBS += $(LIBMNL_LDLIBS) +endif wg: $(patsubst %.c,%.o,$(wildcard *.c)) diff --git a/src/genkey.c b/src/genkey.c index a312b46..af2765f 100644 --- a/src/genkey.c +++ b/src/genkey.c @@ -5,9 +5,11 @@ #include #include #include -#include #include #include +#ifdef __linux +#include +#endif #include "curve25519.h" #include "base64.h" @@ -17,7 +19,7 @@ static inline ssize_t get_random_bytes(uint8_t *out, size_t len) { ssize_t ret; int fd; -#ifdef __NR_getrandom +#if defined(__NR_getrandom) && defined(__linux__) ret = syscall(__NR_getrandom, out, len, 0); if (ret >= 0) return ret; diff --git a/src/kernel.c b/src/kernel.c index 0448308..da80d95 100644 --- a/src/kernel.c +++ b/src/kernel.c @@ -1,25 +1,37 @@ /* Copyright 2015-2016 Jason A. Donenfeld . All Rights Reserved. */ -#include +#ifdef __linux__ #include #include #include #include +#endif #include #include +#include #include #include #include #include #include +#include +#include +#include +#include #include #include #include -#include +#include +#include +#include +#include #include "kernel.h" #include "../uapi.h" +#define SOCK_PATH RUNSTATEDIR "/wireguard/" +#define SOCK_SUFFIX ".sock" + struct inflatable_buffer { char *buffer; char *next; @@ -37,19 +49,24 @@ static int add_next_to_inflatable_buffer(struct inflatable_buffer *buffer) if (!buffer->good || !buffer->next) { free(buffer->next); + buffer->good = false; return 0; } len = strlen(buffer->next) + 1; - if (len == 1) + if (len == 1) { + free(buffer->next); + buffer->good = false; return 0; + } if (buffer->len - buffer->pos <= len) { expand_to = max(buffer->len * 2, buffer->len + len + 1); new_buffer = realloc(buffer->buffer, expand_to); if (!new_buffer) { free(buffer->next); + buffer->good = false; return -errno; } memset(&new_buffer[buffer->len], 0, expand_to - buffer->len); @@ -58,10 +75,149 @@ static int add_next_to_inflatable_buffer(struct inflatable_buffer *buffer) } memcpy(&buffer->buffer[buffer->pos], buffer->next, len); free(buffer->next); + buffer->good = false; buffer->pos += len; return 0; } +static int userspace_interface_fd(const char *interface) +{ + struct stat sbuf; + struct sockaddr_un addr = { .sun_family = AF_UNIX }; + int fd = -1, ret; + ret = snprintf(addr.sun_path, sizeof(addr.sun_path) - 1, SOCK_PATH "%s" SOCK_SUFFIX, interface); + if (ret < 0) + goto out; + ret = stat(addr.sun_path, &sbuf); + if (ret < 0) + goto out; + ret = -EBADF; + if (!S_ISSOCK(sbuf.st_mode)) + goto out; + + ret = fd = socket(AF_UNIX, SOCK_DGRAM, 0); + if (ret < 0) + goto out; + ret = bind(fd, (struct sockaddr *)&addr, sizeof(sa_family_t)); + if (ret < 0) + goto out; + ret = connect(fd, (struct sockaddr *)&addr, sizeof(addr)); + if (ret < 0) { + if (errno == ECONNREFUSED) /* If the process is gone, we try to clean up the socket. */ + unlink(addr.sun_path); + goto out; + } +out: + if (ret && fd >= 0) + close(fd); + if (!ret) + ret = fd; + return ret; +} + +static bool userspace_has_wireguard_interface(const char *interface) +{ + int fd = userspace_interface_fd(interface); + if (fd < 0) + return false; + close(fd); + return true; +} + +static int userspace_get_wireguard_interfaces(struct inflatable_buffer *buffer) +{ + DIR *dir; + struct dirent *ent; + size_t len; + char *end; + int ret = 0; + + dir = opendir(SOCK_PATH); + if (!dir) + return errno == ENOENT ? 0 : errno; + while ((ent = readdir(dir))) { + len = strlen(ent->d_name); + if (len <= strlen(SOCK_SUFFIX)) + continue; + end = &ent->d_name[len - strlen(SOCK_SUFFIX)]; + if (strncmp(end, SOCK_SUFFIX, strlen(SOCK_SUFFIX))) + continue; + *end = '\0'; + if (!userspace_has_wireguard_interface(ent->d_name)) + continue; + buffer->next = strdup(ent->d_name); + buffer->good = true; + ret = add_next_to_inflatable_buffer(buffer); + if (ret < 0) + goto out; + } +out: + closedir(dir); + return ret; +} + +static int userspace_set_device(struct wgdevice *dev) +{ + struct wgpeer *peer; + size_t len; + ssize_t ret; + int ret_code; + int fd = userspace_interface_fd(dev->interface); + if (fd < 0) + return fd; + for_each_wgpeer(dev, peer, len); + len = (unsigned char *)peer - (unsigned char *)dev; + ret = -EBADMSG; + if (!len) + goto out; + ret = send(fd, dev, len, 0); + if (ret < 0) + goto out; + ret = recv(fd, &ret_code, sizeof(ret_code), 0); + if (ret < 0) + goto out; + ret = ret_code; +out: + close(fd); + return (int)ret; +} + +static int userspace_get_device(struct wgdevice **dev, const char *interface) +{ + ssize_t len; + int ret, fd = userspace_interface_fd(interface); + if (fd < 0) + return fd; + *dev = NULL; + ret = send(fd, NULL, 0, 0); + if (ret < 0) + goto out; + + ret = len = recv(fd, NULL, 0, MSG_PEEK | MSG_TRUNC); + if (len < 0) + goto out; + ret = -EBADMSG; + if ((size_t)len < sizeof(struct wgdevice)) + goto out; + + ret = -ENOMEM; + *dev = calloc(len, 1); + if (!*dev) + goto out; + + ret = recv(fd, *dev, len, 0); + if (ret < 0) + goto out; + ret = 0; +out: + if (*dev && ret) + free(*dev); + close(fd); + errno = -ret; + return ret; +} + +#ifdef __linux__ static int parse_linkinfo(const struct nlattr *attr, void *data) { struct inflatable_buffer *buffer = data; @@ -96,8 +252,7 @@ static int read_devices_cb(const struct nlmsghdr *nlh, void *data) return MNL_CB_OK; } -/* first\0second\0third\0forth\0last\0\0 */ -char *kernel_get_wireguard_interfaces(void) +static int kernel_get_wireguard_interfaces(struct inflatable_buffer *buffer) { struct mnl_socket *nl = NULL; char *rtnl_buffer = NULL; @@ -105,22 +260,13 @@ char *kernel_get_wireguard_interfaces(void) unsigned int portid, seq; ssize_t len; int ret = 0; - struct inflatable_buffer buffer = { 0 }; struct nlmsghdr *nlh; struct ifinfomsg *ifm; - buffer.len = 4096; - buffer.buffer = calloc(buffer.len, 1); - if (!buffer.buffer) { - ret = -errno; - goto cleanup; - } - + ret = -ENOMEM; rtnl_buffer = calloc(4096, 1); - if (!rtnl_buffer) { - ret = -errno; + if (!rtnl_buffer) goto cleanup; - } nl = mnl_socket_open(NETLINK_ROUTE); if (!nl) { @@ -153,39 +299,41 @@ another: ret = -errno; goto cleanup; } - if ((len = mnl_cb_run(rtnl_buffer, len, seq, portid, read_devices_cb, &buffer)) < 0) { + if ((len = mnl_cb_run(rtnl_buffer, len, seq, portid, read_devices_cb, buffer)) < 0) { ret = -errno; goto cleanup; } if (len == MNL_CB_OK + 1) goto another; + ret = 0; cleanup: free(rtnl_buffer); if (nl) mnl_socket_close(nl); - errno = -ret; - if (errno) { - perror("Error when trying to get a list of Wireguard interfaces"); - free(buffer.buffer); - return NULL; - } - return buffer.buffer; + return ret; } -bool kernel_has_wireguard_interface(const char *interface) +static bool kernel_has_wireguard_interface(const char *interface) { - char *interfaces, *this_interface; - this_interface = interfaces = kernel_get_wireguard_interfaces(); - if (!interfaces) + char *this_interface; + struct inflatable_buffer buffer = { .len = 4096 }; + + buffer.buffer = calloc(buffer.len, 1); + if (!buffer.buffer) return false; + if (kernel_get_wireguard_interfaces(&buffer) < 0) { + free(buffer.buffer); + return false; + } + this_interface = buffer.buffer; for (size_t len = 0; (len = strlen(this_interface)); this_interface += len + 1) { if (!strcmp(interface, this_interface)) { - free(interfaces); + free(buffer.buffer); return true; } } - free(interfaces); + free(buffer.buffer); return false; } @@ -200,7 +348,7 @@ static int do_ioctl(int req, struct ifreq *ifreq) return ioctl(fd, req, ifreq); } -int kernel_set_device(struct wgdevice *dev) +static int kernel_set_device(struct wgdevice *dev) { struct ifreq ifreq = { .ifr_data = (char *)dev }; memcpy(&ifreq.ifr_name, dev->interface, IFNAMSIZ); @@ -208,7 +356,7 @@ int kernel_set_device(struct wgdevice *dev) return do_ioctl(WG_SET_DEVICE, &ifreq); } -int kernel_get_device(struct wgdevice **dev, const char *interface) +static int kernel_get_device(struct wgdevice **dev, const char *interface) { int ret; struct ifreq ifreq = { 0 }; @@ -222,7 +370,6 @@ int kernel_get_device(struct wgdevice **dev, const char *interface) goto out; *dev = calloc(ret + sizeof(struct wgdevice), 1); if (!*dev) { - perror("calloc"); ret = -ENOMEM; goto out; } @@ -240,3 +387,65 @@ out: errno = -ret; return ret; } +#endif + +/* first\0second\0third\0forth\0last\0\0 */ +char *get_wireguard_interfaces(void) +{ + struct inflatable_buffer buffer = { .len = 4096 }; + int ret; + + ret = -ENOMEM; + buffer.buffer = calloc(buffer.len, 1); + if (!buffer.buffer) + goto cleanup; + +#ifdef __linux__ + ret = kernel_get_wireguard_interfaces(&buffer); + if (ret < 0) + goto cleanup; +#endif + ret = userspace_get_wireguard_interfaces(&buffer); + if (ret < 0) + goto cleanup; + +cleanup: + errno = -ret; + if (errno) { + perror("Error when trying to get a list of WireGuard interfaces"); + free(buffer.buffer); + return NULL; + } + return buffer.buffer; +} + +int get_device(struct wgdevice **dev, const char *interface) +{ +#ifdef __linux__ + if (userspace_has_wireguard_interface(interface)) + return userspace_get_device(dev, interface); + return kernel_get_device(dev, interface); +#else + return userspace_get_device(dev, interface); +#endif +} + +int set_device(struct wgdevice *dev) +{ +#ifdef __linux__ + if (userspace_has_wireguard_interface(dev->interface)) + return userspace_set_device(dev); + return kernel_set_device(dev); +#else + return userspace_set_device(dev); +#endif +} + +bool has_wireguard_interface(const char *interface) +{ +#ifdef __linux__ + return userspace_has_wireguard_interface(interface) || kernel_has_wireguard_interface(interface); +#else + return userspace_has_wireguard_interface(interface); +#endif +} diff --git a/src/kernel.h b/src/kernel.h index 0525ce1..8aa0f82 100644 --- a/src/kernel.h +++ b/src/kernel.h @@ -7,10 +7,10 @@ struct wgdevice; -int kernel_set_device(struct wgdevice *dev); -int kernel_get_device(struct wgdevice **dev, const char *interface); -char *kernel_get_wireguard_interfaces(void); -bool kernel_has_wireguard_interface(const char *interface); +int set_device(struct wgdevice *dev); +int get_device(struct wgdevice **dev, const char *interface); +char *get_wireguard_interfaces(void); +bool has_wireguard_interface(const char *interface); #define for_each_wgpeer(__dev, __peer, __i) for ((__i) = 0, (__peer) = (typeof(__peer))((uint8_t *)(__dev) + sizeof(struct wgdevice)); \ diff --git a/src/set.c b/src/set.c index c7656e8..8278151 100644 --- a/src/set.c +++ b/src/set.c @@ -22,7 +22,7 @@ int set_main(int argc, char *argv[]) strncpy(device->interface, argv[1], IFNAMSIZ - 1); device->interface[IFNAMSIZ - 1] = 0; - if (kernel_set_device(device) != 0) { + if (set_device(device) != 0) { perror("Unable to set device"); goto cleanup; } diff --git a/src/setconf.c b/src/setconf.c index 81faa64..a9787eb 100644 --- a/src/setconf.c +++ b/src/setconf.c @@ -45,7 +45,7 @@ int setconf_main(int argc, char *argv[]) strncpy(device->interface, argv[1], IFNAMSIZ - 1); device->interface[IFNAMSIZ - 1] = 0; - if (kernel_set_device(device) != 0) { + if (set_device(device) != 0) { perror("Unable to set device"); goto cleanup; } diff --git a/src/show.c b/src/show.c index 7927534..d606b4e 100644 --- a/src/show.c +++ b/src/show.c @@ -326,7 +326,7 @@ int show_main(int argc, char *argv[]) } if (argc == 1 || !strcmp(argv[1], "all")) { - char *interfaces = kernel_get_wireguard_interfaces(), *interface; + char *interfaces = get_wireguard_interfaces(), *interface; if (!interfaces) { perror("Unable to get devices"); return 1; @@ -334,7 +334,7 @@ int show_main(int argc, char *argv[]) interface = interfaces; for (size_t len = 0; (len = strlen(interface)); interface += len + 1) { struct wgdevice *device = NULL; - if (kernel_get_device(&device, interface) < 0) { + if (get_device(&device, interface) < 0) { perror("Unable to get device"); continue; } @@ -358,7 +358,7 @@ int show_main(int argc, char *argv[]) show_usage(); return 1; } - interfaces = kernel_get_wireguard_interfaces(); + interfaces = get_wireguard_interfaces(); if (!interfaces) { perror("Unable to get devices"); return 1; @@ -371,12 +371,12 @@ int show_main(int argc, char *argv[]) show_usage(); else { struct wgdevice *device = NULL; - if (!kernel_has_wireguard_interface(argv[1])) { + if (!has_wireguard_interface(argv[1])) { fprintf(stderr, "`%s` is not a valid WireGuard interface\n", argv[1]); show_usage(); return 1; } - if (kernel_get_device(&device, argv[1]) < 0) { + if (get_device(&device, argv[1]) < 0) { perror("Unable to get device"); show_usage(); return 1; diff --git a/src/showconf.c b/src/showconf.c index 95d2e17..68084c0 100644 --- a/src/showconf.c +++ b/src/showconf.c @@ -31,13 +31,13 @@ int showconf_main(int argc, char *argv[]) return 1; } - if (!kernel_has_wireguard_interface(argv[1])) { + if (!has_wireguard_interface(argv[1])) { fprintf(stderr, "`%s` is not a valid WireGuard interface\n", argv[1]); fprintf(stderr, "Usage: %s %s \n", PROG_NAME, argv[0]); return 1; } - if (kernel_get_device(&device, argv[1])) { + if (get_device(&device, argv[1])) { perror("Unable to get device"); goto cleanup; }