Cross-platform Userspace Implementation

While WireGuard has initially been developed for the Linux kernel, for maximum performance, it is planned to release several userspace implementations in Go and Rust, and perhaps other language and platforms. There are already small test programs written in Go, Rust, and Haskell in the external-tests contrib folder.

In order to prevent fragmentation, all userspace implementations should conform to the same protocol and specification, thereby having the exact same behavior as the original Linux kernel one. Furthermore, it should abide by the following configuration interface.

Interface

A userspace implementation should have the following extremely limited command line interface:

# userspace-wg [-f/--foreground] INTERFACE-NAME

For example, a Go implementation would be invoked as follows for creating a wg0 interface:

# wireguard-go wg0

Running the above command would create a virtual TUN device called wg0, and then daemonize. After successfully daemonizing and bringing up the interface, it creates /var/run/wireguard/wg0.sock (or /run/wireguard/wg0.sock depending on the platform), as a UNIX domain socket operating in stream mode. On Windows the same semantics can be used with a bidirectional named pipe.

The wg(8) tool is used for configuring the interface, so that there is complete uniformity in configuration interfaces across implementations. The wg(8) tool will look for interfaces in /var/run/wireguard/*.sock (or /run/wireguard/*.sock). Userspace implementations should die gracefully in response to SIGINT/SIGTERM. The wg(8) tool connects to these sockets and sends and receives the following messages, specified in uapi.h and the same as ordinarily exchanged with the kernel:

This way, the userspace-userspace interface remains the same as the userspace-kernel interface.

Example Daemon

The following code demonstrates an example of how to implement this interface, using the uapi.h header.

#include "uapi.h"

struct sockaddr_un addr = {
    .sun_family = AF_UNIX,
    .sun_path = "/var/run/wireguard/wguserspace0.sock"
};
struct pollfd pollfd;
size_t i;
int len;
int fd, client, ret;
struct wgdevice *device;
struct wgpeer *peer;
mode_t old_umask;
unsigned char *buffer;

fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0)
    exit(1);
old_umask = umask(0077);
unlink(addr.sun_path);
mkdir("/var/run/wireguard", 0700);
if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
    exit(1);
umask(old_umask);
ret = listen(fd, 100);
if (ret < 0)
    exit(1);

for (;;) {
    buffer = NULL;
    /* Accept a new client connection. */
    client = accept(fd, NULL, NULL);
    if (client < 0)
        goto err;
    /* Wait until we can read. */
    pollfd.fd = client;
    pollfd.events = POLLIN;
    pollfd.revents = 0;
    if (poll(&pollfd, 1, -1) < 0 || (pollfd.revents & (POLLERR | POLLHUP | POLLNVAL)) || !(pollfd.revents & POLLIN))
        goto err;
    /* First we look at how big the next message is, so we know how much to allocate. */
    ret = ioctl(client, FIONREAD, &len);
    if (ret < 0 || len == 0)
        goto err;
    /* Allocate a buffer for the received data. */
    buffer = malloc(len);
    if (!buffer)
        goto err;
    /* Finally we receive the data. */
    len = read(client, buffer, len);
    if (len <= 0)
        goto err;
    if (len == 1 && *buffer == 0) { /* If len is 1 and it is a NULL byte, it's a "get" request, so we send our device back. */
        device = get_current_wireguard_device(&len);
        write(client, device, len);
    } else { /* Otherwise, we "set" the received wgdevice and send back the return status. */
        /* We want it to be big enough. */
        if (len < sizeof(struct wgdevice))
            goto err;
        device = (struct wgdevice *)buffer;
        /* Check that we're not out of bounds. */
        for_each_wgpeer(device, peer, i) {
            if ((uint8_t *)peer + sizeof(struct wgpeer) > (uint8_t *)device + len)
                goto err;
            if ((uint8_t *)peer + sizeof(struct wgpeer) + sizeof(struct wgipmask) * peer->num_ipmasks > (uint8_t *)device + len)
                goto err;
        }
        ret = set_current_wireguard_device(device);
        write(client, &ret, sizeof(ret));
    }
err:
    free(buffer);
    if (client >= 0)
        close(client);
}