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 <Jason@zx2c4.com>
This commit is contained in:
Jason A. Donenfeld 2016-07-19 15:26:56 +02:00
parent fd14807259
commit b16641e30c
8 changed files with 268 additions and 50 deletions

View file

@ -3,12 +3,19 @@ DESTDIR ?=
BINDIR ?= $(PREFIX)/bin BINDIR ?= $(PREFIX)/bin
LIBDIR ?= $(PREFIX)/lib LIBDIR ?= $(PREFIX)/lib
MANDIR ?= $(PREFIX)/share/man MANDIR ?= $(PREFIX)/share/man
RUNSTATEDIR ?= /var/run
CFLAGS += $(shell pkg-config --cflags libmnl 2>/dev/null)
CFLAGS += -std=gnu11 CFLAGS += -std=gnu11
CFLAGS += -pedantic -Wall -Wextra CFLAGS += -pedantic -Wall -Wextra
CFLAGS += -MMD 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)) wg: $(patsubst %.c,%.o,$(wildcard *.c))

View file

@ -5,9 +5,11 @@
#include <unistd.h> #include <unistd.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <syscall.h>
#include <string.h> #include <string.h>
#include <fcntl.h> #include <fcntl.h>
#ifdef __linux
#include <syscall.h>
#endif
#include "curve25519.h" #include "curve25519.h"
#include "base64.h" #include "base64.h"
@ -17,7 +19,7 @@ static inline ssize_t get_random_bytes(uint8_t *out, size_t len)
{ {
ssize_t ret; ssize_t ret;
int fd; int fd;
#ifdef __NR_getrandom #if defined(__NR_getrandom) && defined(__linux__)
ret = syscall(__NR_getrandom, out, len, 0); ret = syscall(__NR_getrandom, out, len, 0);
if (ret >= 0) if (ret >= 0)
return ret; return ret;

View file

@ -1,25 +1,37 @@
/* Copyright 2015-2016 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. */ /* Copyright 2015-2016 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. */
#include <errno.h> #ifdef __linux__
#include <libmnl/libmnl.h> #include <libmnl/libmnl.h>
#include <linux/if_link.h> #include <linux/if_link.h>
#include <linux/netlink.h> #include <linux/netlink.h>
#include <linux/rtnetlink.h> #include <linux/rtnetlink.h>
#endif
#include <netinet/in.h> #include <netinet/in.h>
#include <net/if.h> #include <net/if.h>
#include <errno.h>
#include <stdbool.h> #include <stdbool.h>
#include <stddef.h> #include <stddef.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <dirent.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <time.h> #include <sys/types.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <sys/signal.h>
#include "kernel.h" #include "kernel.h"
#include "../uapi.h" #include "../uapi.h"
#define SOCK_PATH RUNSTATEDIR "/wireguard/"
#define SOCK_SUFFIX ".sock"
struct inflatable_buffer { struct inflatable_buffer {
char *buffer; char *buffer;
char *next; char *next;
@ -37,19 +49,24 @@ static int add_next_to_inflatable_buffer(struct inflatable_buffer *buffer)
if (!buffer->good || !buffer->next) { if (!buffer->good || !buffer->next) {
free(buffer->next); free(buffer->next);
buffer->good = false;
return 0; return 0;
} }
len = strlen(buffer->next) + 1; len = strlen(buffer->next) + 1;
if (len == 1) if (len == 1) {
free(buffer->next);
buffer->good = false;
return 0; return 0;
}
if (buffer->len - buffer->pos <= len) { if (buffer->len - buffer->pos <= len) {
expand_to = max(buffer->len * 2, buffer->len + len + 1); expand_to = max(buffer->len * 2, buffer->len + len + 1);
new_buffer = realloc(buffer->buffer, expand_to); new_buffer = realloc(buffer->buffer, expand_to);
if (!new_buffer) { if (!new_buffer) {
free(buffer->next); free(buffer->next);
buffer->good = false;
return -errno; return -errno;
} }
memset(&new_buffer[buffer->len], 0, expand_to - buffer->len); 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); memcpy(&buffer->buffer[buffer->pos], buffer->next, len);
free(buffer->next); free(buffer->next);
buffer->good = false;
buffer->pos += len; buffer->pos += len;
return 0; 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) static int parse_linkinfo(const struct nlattr *attr, void *data)
{ {
struct inflatable_buffer *buffer = 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; return MNL_CB_OK;
} }
/* first\0second\0third\0forth\0last\0\0 */ static int kernel_get_wireguard_interfaces(struct inflatable_buffer *buffer)
char *kernel_get_wireguard_interfaces(void)
{ {
struct mnl_socket *nl = NULL; struct mnl_socket *nl = NULL;
char *rtnl_buffer = NULL; char *rtnl_buffer = NULL;
@ -105,22 +260,13 @@ char *kernel_get_wireguard_interfaces(void)
unsigned int portid, seq; unsigned int portid, seq;
ssize_t len; ssize_t len;
int ret = 0; int ret = 0;
struct inflatable_buffer buffer = { 0 };
struct nlmsghdr *nlh; struct nlmsghdr *nlh;
struct ifinfomsg *ifm; struct ifinfomsg *ifm;
buffer.len = 4096; ret = -ENOMEM;
buffer.buffer = calloc(buffer.len, 1);
if (!buffer.buffer) {
ret = -errno;
goto cleanup;
}
rtnl_buffer = calloc(4096, 1); rtnl_buffer = calloc(4096, 1);
if (!rtnl_buffer) { if (!rtnl_buffer)
ret = -errno;
goto cleanup; goto cleanup;
}
nl = mnl_socket_open(NETLINK_ROUTE); nl = mnl_socket_open(NETLINK_ROUTE);
if (!nl) { if (!nl) {
@ -153,39 +299,41 @@ another:
ret = -errno; ret = -errno;
goto cleanup; 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; ret = -errno;
goto cleanup; goto cleanup;
} }
if (len == MNL_CB_OK + 1) if (len == MNL_CB_OK + 1)
goto another; goto another;
ret = 0;
cleanup: cleanup:
free(rtnl_buffer); free(rtnl_buffer);
if (nl) if (nl)
mnl_socket_close(nl); mnl_socket_close(nl);
errno = -ret; return ret;
if (errno) {
perror("Error when trying to get a list of Wireguard interfaces");
free(buffer.buffer);
return NULL;
}
return buffer.buffer;
} }
bool kernel_has_wireguard_interface(const char *interface) static bool kernel_has_wireguard_interface(const char *interface)
{ {
char *interfaces, *this_interface; char *this_interface;
this_interface = interfaces = kernel_get_wireguard_interfaces(); struct inflatable_buffer buffer = { .len = 4096 };
if (!interfaces)
buffer.buffer = calloc(buffer.len, 1);
if (!buffer.buffer)
return false; 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) { for (size_t len = 0; (len = strlen(this_interface)); this_interface += len + 1) {
if (!strcmp(interface, this_interface)) { if (!strcmp(interface, this_interface)) {
free(interfaces); free(buffer.buffer);
return true; return true;
} }
} }
free(interfaces); free(buffer.buffer);
return false; return false;
} }
@ -200,7 +348,7 @@ static int do_ioctl(int req, struct ifreq *ifreq)
return ioctl(fd, req, 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 }; struct ifreq ifreq = { .ifr_data = (char *)dev };
memcpy(&ifreq.ifr_name, dev->interface, IFNAMSIZ); 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); 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; int ret;
struct ifreq ifreq = { 0 }; struct ifreq ifreq = { 0 };
@ -222,7 +370,6 @@ int kernel_get_device(struct wgdevice **dev, const char *interface)
goto out; goto out;
*dev = calloc(ret + sizeof(struct wgdevice), 1); *dev = calloc(ret + sizeof(struct wgdevice), 1);
if (!*dev) { if (!*dev) {
perror("calloc");
ret = -ENOMEM; ret = -ENOMEM;
goto out; goto out;
} }
@ -240,3 +387,65 @@ out:
errno = -ret; errno = -ret;
return 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
}

View file

@ -7,10 +7,10 @@
struct wgdevice; struct wgdevice;
int kernel_set_device(struct wgdevice *dev); int set_device(struct wgdevice *dev);
int kernel_get_device(struct wgdevice **dev, const char *interface); int get_device(struct wgdevice **dev, const char *interface);
char *kernel_get_wireguard_interfaces(void); char *get_wireguard_interfaces(void);
bool kernel_has_wireguard_interface(const char *interface); 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)); \ #define for_each_wgpeer(__dev, __peer, __i) for ((__i) = 0, (__peer) = (typeof(__peer))((uint8_t *)(__dev) + sizeof(struct wgdevice)); \

View file

@ -22,7 +22,7 @@ int set_main(int argc, char *argv[])
strncpy(device->interface, argv[1], IFNAMSIZ - 1); strncpy(device->interface, argv[1], IFNAMSIZ - 1);
device->interface[IFNAMSIZ - 1] = 0; device->interface[IFNAMSIZ - 1] = 0;
if (kernel_set_device(device) != 0) { if (set_device(device) != 0) {
perror("Unable to set device"); perror("Unable to set device");
goto cleanup; goto cleanup;
} }

View file

@ -45,7 +45,7 @@ int setconf_main(int argc, char *argv[])
strncpy(device->interface, argv[1], IFNAMSIZ - 1); strncpy(device->interface, argv[1], IFNAMSIZ - 1);
device->interface[IFNAMSIZ - 1] = 0; device->interface[IFNAMSIZ - 1] = 0;
if (kernel_set_device(device) != 0) { if (set_device(device) != 0) {
perror("Unable to set device"); perror("Unable to set device");
goto cleanup; goto cleanup;
} }

View file

@ -326,7 +326,7 @@ int show_main(int argc, char *argv[])
} }
if (argc == 1 || !strcmp(argv[1], "all")) { if (argc == 1 || !strcmp(argv[1], "all")) {
char *interfaces = kernel_get_wireguard_interfaces(), *interface; char *interfaces = get_wireguard_interfaces(), *interface;
if (!interfaces) { if (!interfaces) {
perror("Unable to get devices"); perror("Unable to get devices");
return 1; return 1;
@ -334,7 +334,7 @@ int show_main(int argc, char *argv[])
interface = interfaces; interface = interfaces;
for (size_t len = 0; (len = strlen(interface)); interface += len + 1) { for (size_t len = 0; (len = strlen(interface)); interface += len + 1) {
struct wgdevice *device = NULL; struct wgdevice *device = NULL;
if (kernel_get_device(&device, interface) < 0) { if (get_device(&device, interface) < 0) {
perror("Unable to get device"); perror("Unable to get device");
continue; continue;
} }
@ -358,7 +358,7 @@ int show_main(int argc, char *argv[])
show_usage(); show_usage();
return 1; return 1;
} }
interfaces = kernel_get_wireguard_interfaces(); interfaces = get_wireguard_interfaces();
if (!interfaces) { if (!interfaces) {
perror("Unable to get devices"); perror("Unable to get devices");
return 1; return 1;
@ -371,12 +371,12 @@ int show_main(int argc, char *argv[])
show_usage(); show_usage();
else { else {
struct wgdevice *device = NULL; 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]); fprintf(stderr, "`%s` is not a valid WireGuard interface\n", argv[1]);
show_usage(); show_usage();
return 1; return 1;
} }
if (kernel_get_device(&device, argv[1]) < 0) { if (get_device(&device, argv[1]) < 0) {
perror("Unable to get device"); perror("Unable to get device");
show_usage(); show_usage();
return 1; return 1;

View file

@ -31,13 +31,13 @@ int showconf_main(int argc, char *argv[])
return 1; 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, "`%s` is not a valid WireGuard interface\n", argv[1]);
fprintf(stderr, "Usage: %s %s <interface>\n", PROG_NAME, argv[0]); fprintf(stderr, "Usage: %s %s <interface>\n", PROG_NAME, argv[0]);
return 1; return 1;
} }
if (kernel_get_device(&device, argv[1])) { if (get_device(&device, argv[1])) {
perror("Unable to get device"); perror("Unable to get device");
goto cleanup; goto cleanup;
} }