netlink: switch from ioctl to netlink for configuration

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
Jason A. Donenfeld 2017-09-25 04:22:09 +02:00
parent 9a0790b50a
commit 5b65f87e9f
12 changed files with 1134 additions and 443 deletions

View file

@ -1,6 +1,5 @@
/* Copyright (C) 2015-2017 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. */
#include <arpa/inet.h>
#include <limits.h>
#include <ctype.h>
@ -14,36 +13,12 @@
#include <errno.h>
#include "config.h"
#include "containers.h"
#include "ipc.h"
#include "encoding.h"
#define COMMENT_CHAR '#'
#define max(a, b) (a > b ? a : b)
static inline struct wgpeer *peer_from_offset(struct wgdevice *dev, size_t offset)
{
return (struct wgpeer *)((uint8_t *)dev + sizeof(struct wgdevice) + offset);
}
static int use_space(struct inflatable_device *buf, size_t space)
{
size_t expand_to;
uint8_t *new_dev;
if (buf->len - buf->pos < space) {
expand_to = max(buf->len * 2, buf->len + space);
new_dev = realloc(buf->dev, expand_to + sizeof(struct wgdevice));
if (!new_dev)
return -errno;
memset(&new_dev[buf->len + sizeof(struct wgdevice)], 0, expand_to - buf->len);
buf->dev = (struct wgdevice *)new_dev;
buf->len = expand_to;
}
buf->pos += space;
return 0;
}
static const char *get_value(const char *line, const char *key)
{
size_t linelen = strlen(line);
@ -58,10 +33,9 @@ static const char *get_value(const char *line, const char *key)
return line + keylen;
}
static inline uint16_t parse_port(const char *value)
static inline bool parse_port(uint16_t *port, uint32_t *flags, const char *value)
{
int ret;
uint16_t port = 0;
struct addrinfo *resolved;
struct addrinfo hints = {
.ai_family = AF_UNSPEC,
@ -72,27 +46,32 @@ static inline uint16_t parse_port(const char *value)
if (!strlen(value)) {
fprintf(stderr, "Unable to parse empty port\n");
return 0;
return false;
}
ret = getaddrinfo(NULL, value, &hints, &resolved);
if (ret != 0) {
if (ret) {
fprintf(stderr, "%s: `%s`\n", gai_strerror(ret), value);
return 0;
return false;
}
if (resolved->ai_family == AF_INET && resolved->ai_addrlen == sizeof(struct sockaddr_in))
port = ntohs(((struct sockaddr_in *)resolved->ai_addr)->sin_port);
else if (resolved->ai_family == AF_INET6 && resolved->ai_addrlen == sizeof(struct sockaddr_in6))
port = ntohs(((struct sockaddr_in6 *)resolved->ai_addr)->sin6_port);
else
ret = -1;
if (resolved->ai_family == AF_INET && resolved->ai_addrlen == sizeof(struct sockaddr_in)) {
*port = ntohs(((struct sockaddr_in *)resolved->ai_addr)->sin_port);
ret = 0;
} else if (resolved->ai_family == AF_INET6 && resolved->ai_addrlen == sizeof(struct sockaddr_in6)) {
*port = ntohs(((struct sockaddr_in6 *)resolved->ai_addr)->sin6_port);
ret = 0;
} else
fprintf(stderr, "Neither IPv4 nor IPv6 address found: `%s`\n", value);
freeaddrinfo(resolved);
return port;
if (!ret)
*flags |= WGDEVICE_HAS_LISTEN_PORT;
return ret == 0;
}
static inline bool parse_fwmark(uint32_t *fwmark, unsigned int *flags, const char *value)
static inline bool parse_fwmark(uint32_t *fwmark, uint32_t *flags, const char *value)
{
unsigned long ret;
char *end;
@ -100,7 +79,7 @@ static inline bool parse_fwmark(uint32_t *fwmark, unsigned int *flags, const cha
if (!strcasecmp(value, "off")) {
*fwmark = 0;
*flags |= WGDEVICE_REMOVE_FWMARK;
*flags |= WGDEVICE_HAS_FWMARK;
return true;
}
@ -112,8 +91,7 @@ static inline bool parse_fwmark(uint32_t *fwmark, unsigned int *flags, const cha
if (!*value || *end || ret > UINT32_MAX)
return false;
*fwmark = ret;
if (!ret)
*flags |= WGDEVICE_REMOVE_FWMARK;
*flags |= WGDEVICE_HAS_FWMARK;
return true;
}
@ -126,17 +104,17 @@ static inline bool parse_key(uint8_t key[static WG_KEY_LEN], const char *value)
return true;
}
static inline bool parse_ip(struct wgipmask *ipmask, const char *value)
static inline bool parse_ip(struct wgallowedip *allowedip, const char *value)
{
ipmask->family = AF_UNSPEC;
allowedip->family = AF_UNSPEC;
if (strchr(value, ':')) {
if (inet_pton(AF_INET6, value, &ipmask->ip6) == 1)
ipmask->family = AF_INET6;
if (inet_pton(AF_INET6, value, &allowedip->ip6) == 1)
allowedip->family = AF_INET6;
} else {
if (inet_pton(AF_INET, value, &ipmask->ip4) == 1)
ipmask->family = AF_INET;
if (inet_pton(AF_INET, value, &allowedip->ip4) == 1)
allowedip->family = AF_INET;
}
if (ipmask->family == AF_UNSPEC) {
if (allowedip->family == AF_UNSPEC) {
fprintf(stderr, "Unable to parse IP address: `%s`\n", value);
return false;
}
@ -215,13 +193,14 @@ static inline bool parse_endpoint(struct sockaddr *endpoint, const char *value)
return true;
}
static inline bool parse_persistent_keepalive(__u16 *interval, const char *value)
static inline bool parse_persistent_keepalive(uint16_t *interval, uint32_t *flags, const char *value)
{
unsigned long ret;
char *end;
if (!strcasecmp(value, "off")) {
*interval = 0;
*flags |= WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL;
return true;
}
@ -231,22 +210,21 @@ static inline bool parse_persistent_keepalive(__u16 *interval, const char *value
return false;
}
*interval = (__u16)ret;
*interval = (uint16_t)ret;
*flags |= WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL;
return true;
}
static inline bool parse_ipmasks(struct inflatable_device *buf, size_t peer_offset, const char *value)
static inline bool parse_allowedips(struct wgpeer *peer, struct wgallowedip **last_allowedip, const char *value)
{
struct wgpeer *peer;
struct wgipmask *ipmask;
struct wgallowedip *allowedip = *last_allowedip, *new_allowedip;
char *mask, *mutable = strdup(value), *sep;
if (!mutable) {
perror("strdup");
return false;
};
peer = peer_from_offset(buf->dev, peer_offset);
peer->flags |= WGPEER_REPLACE_IPMASKS;
peer->flags |= WGPEER_REPLACE_ALLOWEDIPS;
if (!strlen(value)) {
free(mutable);
return true;
@ -255,15 +233,19 @@ static inline bool parse_ipmasks(struct inflatable_device *buf, size_t peer_offs
while ((mask = strsep(&sep, ","))) {
unsigned long cidr = ULONG_MAX;
char *end, *ip = strsep(&mask, "/");
if (use_space(buf, sizeof(struct wgipmask)) < 0) {
perror("use_space");
new_allowedip = calloc(1, sizeof(struct wgallowedip));
if (!new_allowedip) {
perror("calloc");
free(mutable);
return false;
}
peer = peer_from_offset(buf->dev, peer_offset);
ipmask = (struct wgipmask *)((uint8_t *)peer + sizeof(struct wgpeer) + (sizeof(struct wgipmask) * peer->num_ipmasks));
if (allowedip)
allowedip->next_allowedip = new_allowedip;
else
peer->first_allowedip = new_allowedip;
allowedip = new_allowedip;
if (!parse_ip(ipmask, ip)) {
if (!parse_ip(allowedip, ip)) {
free(mutable);
return false;
}
@ -272,16 +254,16 @@ static inline bool parse_ipmasks(struct inflatable_device *buf, size_t peer_offs
if (*end)
cidr = ULONG_MAX;
}
if (ipmask->family == AF_INET)
if (allowedip->family == AF_INET)
cidr = cidr > 32 ? 32 : cidr;
else if (ipmask->family == AF_INET6)
else if (allowedip->family == AF_INET6)
cidr = cidr > 128 ? 128 : cidr;
else
continue;
ipmask->cidr = cidr;
++peer->num_ipmasks;
allowedip->cidr = cidr;
}
free(mutable);
*last_allowedip = allowedip;
return true;
}
@ -296,16 +278,20 @@ static bool process_line(struct config_ctx *ctx, const char *line)
return true;
}
if (!strcasecmp(line, "[Peer]")) {
ctx->peer_offset = ctx->buf.pos;
if (use_space(&ctx->buf, sizeof(struct wgpeer)) < 0) {
perror("use_space");
struct wgpeer *new_peer = calloc(1, sizeof(struct wgpeer));
if (!new_peer) {
perror("calloc");
return false;
}
++ctx->buf.dev->num_peers;
ctx->last_allowedip = NULL;
if (ctx->last_peer)
ctx->last_peer->next_peer = new_peer;
else
ctx->device->first_peer = new_peer;
ctx->last_peer = new_peer;
ctx->is_peer_section = true;
ctx->is_device_section = false;
peer_from_offset(ctx->buf.dev, ctx->peer_offset)->flags |= WGPEER_REPLACE_IPMASKS;
peer_from_offset(ctx->buf.dev, ctx->peer_offset)->persistent_keepalive_interval = (__u16)-1;
ctx->last_peer->flags |= WGPEER_REPLACE_ALLOWEDIPS;
return true;
}
@ -313,28 +299,32 @@ static bool process_line(struct config_ctx *ctx, const char *line)
if (ctx->is_device_section) {
if (key_match("ListenPort"))
ret = !!(ctx->buf.dev->port = parse_port(value));
ret = parse_port(&ctx->device->listen_port, &ctx->device->flags, value);
else if (key_match("FwMark"))
ret = parse_fwmark(&ctx->buf.dev->fwmark, &ctx->buf.dev->flags, value);
ret = parse_fwmark(&ctx->device->fwmark, &ctx->device->flags, value);
else if (key_match("PrivateKey")) {
ret = parse_key(ctx->buf.dev->private_key, value);
ret = parse_key(ctx->device->private_key, value);
if (!ret)
memset(ctx->buf.dev->private_key, 0, WG_KEY_LEN);
memset(ctx->device->private_key, 0, WG_KEY_LEN);
else
ctx->device->flags |= WGDEVICE_HAS_PRIVATE_KEY;
} else
goto error;
} else if (ctx->is_peer_section) {
if (key_match("Endpoint"))
ret = parse_endpoint(&peer_from_offset(ctx->buf.dev, ctx->peer_offset)->endpoint.addr, value);
ret = parse_endpoint(&ctx->last_peer->endpoint.addr, value);
else if (key_match("PublicKey"))
ret = parse_key(peer_from_offset(ctx->buf.dev, ctx->peer_offset)->public_key, value);
ret = parse_key(ctx->last_peer->public_key, value);
else if (key_match("AllowedIPs"))
ret = parse_ipmasks(&ctx->buf, ctx->peer_offset, value);
ret = parse_allowedips(ctx->last_peer, &ctx->last_allowedip, value);
else if (key_match("PersistentKeepalive"))
ret = parse_persistent_keepalive(&peer_from_offset(ctx->buf.dev, ctx->peer_offset)->persistent_keepalive_interval, value);
ret = parse_persistent_keepalive(&ctx->last_peer->persistent_keepalive_interval, &ctx->last_peer->flags, value);
else if (key_match("PresharedKey")) {
ret = parse_key(peer_from_offset(ctx->buf.dev, ctx->peer_offset)->preshared_key, value);
ret = parse_key(ctx->last_peer->preshared_key, value);
if (!ret)
memset(peer_from_offset(ctx->buf.dev, ctx->peer_offset)->preshared_key, 0, WG_KEY_LEN);
memset(ctx->last_peer->preshared_key, 0, WG_KEY_LEN);
else
ctx->last_peer->flags |= WGPEER_HAS_PRESHARED_KEY;
} else
goto error;
} else
@ -355,7 +345,8 @@ bool config_read_line(struct config_ctx *ctx, const char *input)
bool ret = true;
if (!line) {
perror("calloc");
return false;
ret = false;
goto out;
}
if (!len)
goto out;
@ -370,56 +361,54 @@ bool config_read_line(struct config_ctx *ctx, const char *input)
ret = process_line(ctx, line);
out:
free(line);
if (!ret)
free_wgdevice(ctx->device);
return ret;
}
bool config_read_init(struct config_ctx *ctx, struct wgdevice **device, bool append)
bool config_read_init(struct config_ctx *ctx, bool append)
{
memset(ctx, 0, sizeof(struct config_ctx));
ctx->device = device;
ctx->buf.dev = calloc(1, sizeof(struct wgdevice));
if (!ctx->buf.dev) {
ctx->device = calloc(1, sizeof(struct wgdevice));
if (!ctx->device) {
perror("calloc");
return false;
}
if (!append)
ctx->buf.dev->flags |= WGDEVICE_REPLACE_PEERS;
ctx->device->flags |= WGDEVICE_REPLACE_PEERS | WGDEVICE_HAS_PRIVATE_KEY | WGDEVICE_HAS_FWMARK | WGDEVICE_HAS_LISTEN_PORT;
return true;
}
bool config_read_finish(struct config_ctx *ctx)
struct wgdevice *config_read_finish(struct config_ctx *ctx)
{
size_t i;
struct wgpeer *peer;
if (ctx->buf.dev->flags & WGDEVICE_REPLACE_PEERS && key_is_zero(ctx->buf.dev->private_key)) {
fprintf(stderr, "No private key configured\n");
if (ctx->device->flags & WGDEVICE_REPLACE_PEERS && key_is_zero(ctx->device->private_key)) {
fprintf(stderr, "No private key is configured\n");
goto err;
}
if (ctx->buf.dev->flags & WGDEVICE_REPLACE_PEERS && !ctx->buf.dev->fwmark)
ctx->buf.dev->flags |= WGDEVICE_REMOVE_FWMARK;
for_each_wgpeer(ctx->buf.dev, peer, i) {
for_each_wgpeer (ctx->device, peer) {
if (key_is_zero(peer->public_key)) {
fprintf(stderr, "A peer is missing a public key\n");
goto err;
}
}
*ctx->device = ctx->buf.dev;
return true;
return ctx->device;
err:
free(ctx->buf.dev);
return false;
free_wgdevice(ctx->device);
return NULL;
}
static int read_keyfile(char dst[WG_KEY_LEN_BASE64], const char *path)
static bool read_keyfile(char dst[WG_KEY_LEN_BASE64], const char *path)
{
FILE *f;
int ret = -1, c;
int c;
bool ret = false;
f = fopen(path, "r");
if (!f) {
perror("fopen");
return -1;
return false;
}
if (fread(dst, WG_KEY_LEN_BASE64 - 1, 1, f) != 1) {
@ -429,7 +418,9 @@ static int read_keyfile(char dst[WG_KEY_LEN_BASE64], const char *path)
}
/* If we're at the end and we didn't read anything, we're /dev/null. */
if (!ferror(f) && feof(f) && !ftell(f)) {
ret = 1;
static const uint8_t zeros[WG_KEY_LEN] = { 0 };
key_to_base64(dst, zeros);
ret = true;
goto out;
}
@ -448,7 +439,7 @@ static int read_keyfile(char dst[WG_KEY_LEN_BASE64], const char *path)
perror("getc");
goto out;
}
ret = 0;
ret = true;
out:
fclose(f);
@ -473,85 +464,84 @@ static char *strip_spaces(const char *in)
return out;
}
bool config_read_cmd(struct wgdevice **device, char *argv[], int argc)
struct wgdevice *config_read_cmd(char *argv[], int argc)
{
struct inflatable_device buf = { 0 };
size_t peer_offset = 0;
buf.dev = calloc(1, sizeof(struct wgdevice));
if (!buf.dev) {
struct wgdevice *device = calloc(1, sizeof(struct wgdevice));
struct wgpeer *peer = NULL;
struct wgallowedip *allowedip = NULL;
if (!device) {
perror("calloc");
return false;
}
while (argc > 0) {
if (!strcmp(argv[0], "listen-port") && argc >= 2 && !buf.dev->num_peers) {
buf.dev->port = parse_port(argv[1]);
if (!buf.dev->port)
if (!strcmp(argv[0], "listen-port") && argc >= 2 && !peer) {
if (!parse_port(&device->listen_port, &device->flags, argv[1]))
goto error;
argv += 2;
argc -= 2;
} else if (!strcmp(argv[0], "fwmark") && argc >= 2 && !buf.dev->num_peers) {
if (!parse_fwmark(&buf.dev->fwmark, &buf.dev->flags, argv[1]))
} else if (!strcmp(argv[0], "fwmark") && argc >= 2 && !peer) {
if (!parse_fwmark(&device->fwmark, &device->flags, argv[1]))
goto error;
argv += 2;
argc -= 2;
} else if (!strcmp(argv[0], "private-key") && argc >= 2 && !buf.dev->num_peers) {
} else if (!strcmp(argv[0], "private-key") && argc >= 2 && !peer) {
char key_line[WG_KEY_LEN_BASE64];
int ret = read_keyfile(key_line, argv[1]);
if (ret == 0) {
if (!parse_key(buf.dev->private_key, key_line))
if (read_keyfile(key_line, argv[1])) {
if (!parse_key(device->private_key, key_line))
goto error;
} else if (ret == 1)
buf.dev->flags |= WGDEVICE_REMOVE_PRIVATE_KEY;
else
device->flags |= WGDEVICE_HAS_PRIVATE_KEY;
} else
goto error;
argv += 2;
argc -= 2;
} else if (!strcmp(argv[0], "peer") && argc >= 2) {
peer_offset = buf.pos;
if (use_space(&buf, sizeof(struct wgpeer)) < 0) {
perror("use_space");
struct wgpeer *new_peer = calloc(1, sizeof(struct wgpeer));
allowedip = NULL;
if (!new_peer) {
perror("calloc");
goto error;
}
peer_from_offset(buf.dev, peer_offset)->persistent_keepalive_interval = (__u16)-1;
++buf.dev->num_peers;
if (!parse_key(peer_from_offset(buf.dev, peer_offset)->public_key, argv[1]))
if (peer)
peer->next_peer = new_peer;
else
device->first_peer = new_peer;
peer = new_peer;
if (!parse_key(peer->public_key, argv[1]))
goto error;
argv += 2;
argc -= 2;
} else if (!strcmp(argv[0], "remove") && argc >= 1 && buf.dev->num_peers) {
peer_from_offset(buf.dev, peer_offset)->flags |= WGPEER_REMOVE_ME;
} else if (!strcmp(argv[0], "remove") && argc >= 1 && peer) {
peer->flags |= WGPEER_REMOVE_ME;
argv += 1;
argc -= 1;
} else if (!strcmp(argv[0], "endpoint") && argc >= 2 && buf.dev->num_peers) {
if (!parse_endpoint(&peer_from_offset(buf.dev, peer_offset)->endpoint.addr, argv[1]))
} else if (!strcmp(argv[0], "endpoint") && argc >= 2 && peer) {
if (!parse_endpoint(&peer->endpoint.addr, argv[1]))
goto error;
argv += 2;
argc -= 2;
} else if (!strcmp(argv[0], "allowed-ips") && argc >= 2 && buf.dev->num_peers) {
} else if (!strcmp(argv[0], "allowed-ips") && argc >= 2 && peer) {
char *line = strip_spaces(argv[1]);
if (!line)
goto error;
if (!parse_ipmasks(&buf, peer_offset, line)) {
if (!parse_allowedips(peer, &allowedip, line)) {
free(line);
goto error;
}
free(line);
argv += 2;
argc -= 2;
} else if (!strcmp(argv[0], "persistent-keepalive") && argc >= 2 && buf.dev->num_peers) {
if (!parse_persistent_keepalive(&peer_from_offset(buf.dev, peer_offset)->persistent_keepalive_interval, argv[1]))
} else if (!strcmp(argv[0], "persistent-keepalive") && argc >= 2 && peer) {
if (!parse_persistent_keepalive(&peer->persistent_keepalive_interval, &peer->flags, argv[1]))
goto error;
argv += 2;
argc -= 2;
} else if (!strcmp(argv[0], "preshared-key") && argc >= 2 && buf.dev->num_peers) {
} else if (!strcmp(argv[0], "preshared-key") && argc >= 2 && peer) {
char key_line[WG_KEY_LEN_BASE64];
int ret = read_keyfile(key_line, argv[1]);
if (ret == 0) {
if (!parse_key(peer_from_offset(buf.dev, peer_offset)->preshared_key, key_line))
if (read_keyfile(key_line, argv[1])) {
if (!parse_key(peer->preshared_key, key_line))
goto error;
} else if (ret == 1)
peer_from_offset(buf.dev, peer_offset)->flags |= WGPEER_REMOVE_PRESHARED_KEY;
else
peer->flags |= WGPEER_HAS_PRESHARED_KEY;
} else
goto error;
argv += 2;
argc -= 2;
@ -560,9 +550,8 @@ bool config_read_cmd(struct wgdevice **device, char *argv[], int argc)
goto error;
}
}
*device = buf.dev;
return true;
return device;
error:
free(buf.dev);
free_wgdevice(device);
return false;
}

View file

@ -4,31 +4,21 @@
#define CONFIG_H
#include <stdbool.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "../uapi.h"
struct inflatable_device {
struct wgdevice *dev;
size_t len;
size_t pos;
};
struct wgdevice;
struct wgpeer;
struct wgallowedip;
struct config_ctx {
struct inflatable_device buf;
size_t peer_offset;
struct wgdevice **device;
bool is_peer_section;
bool is_device_section;
struct wgdevice *device;
struct wgpeer *last_peer;
struct wgallowedip *last_allowedip;
bool is_peer_section, is_device_section;
};
bool config_read_cmd(struct wgdevice **dev, char *argv[], int argc);
bool config_read_init(struct config_ctx *ctx, struct wgdevice **device, bool append);
struct wgdevice *config_read_cmd(char *argv[], int argc);
bool config_read_init(struct config_ctx *ctx, bool append);
bool config_read_line(struct config_ctx *ctx, const char *line);
bool config_read_finish(struct config_ctx *ctx);
struct wgdevice *config_read_finish(struct config_ctx *ctx);
#endif

95
src/containers.h Normal file
View file

@ -0,0 +1,95 @@
/* Copyright (C) 2015-2017 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. */
#ifndef CONTAINERS_H
#define CONTAINERS_H
#include <stdint.h>
#include <stdlib.h>
#include <net/if.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <sys/socket.h>
#include "../uapi/wireguard.h"
struct wgallowedip {
uint16_t family;
union {
struct in_addr ip4;
struct in6_addr ip6;
};
uint8_t cidr;
struct wgallowedip *next_allowedip;
};
enum {
WGPEER_REMOVE_ME = (1 << 0),
WGPEER_REPLACE_ALLOWEDIPS = (1 << 1),
WGPEER_HAS_PRESHARED_KEY = (1 << 2),
WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL = (1 << 3)
};
struct wgpeer {
uint32_t flags;
uint8_t public_key[WG_KEY_LEN];
uint8_t preshared_key[WG_KEY_LEN];
union {
struct sockaddr addr;
struct sockaddr_in addr4;
struct sockaddr_in6 addr6;
} endpoint;
struct timeval last_handshake_time;
uint64_t rx_bytes, tx_bytes;
uint16_t persistent_keepalive_interval;
struct wgallowedip *first_allowedip;
struct wgpeer *next_peer;
};
enum {
WGDEVICE_REPLACE_PEERS = (1 << 0),
WGDEVICE_HAS_PRIVATE_KEY = (1 << 1),
WGDEVICE_HAS_LISTEN_PORT = (1 << 2),
WGDEVICE_HAS_FWMARK = (1 << 3)
};
enum {
WG_API_VERSION_MAGIC = 0xbeef0003
};
struct wgdevice {
char name[IFNAMSIZ];
uint32_t ifindex;
uint32_t flags;
uint8_t public_key[WG_KEY_LEN];
uint8_t private_key[WG_KEY_LEN];
uint32_t fwmark;
uint16_t listen_port;
struct wgpeer *first_peer;
};
#define for_each_wgpeer(__dev, __peer) for ((__peer) = (__dev)->first_peer; (__peer); (__peer) = (__peer)->next_peer)
#define for_each_wgallowedip(__peer, __allowedip) for ((__allowedip) = (__peer)->first_allowedip; (__allowedip); (__allowedip) = (__allowedip)->next_allowedip)
#define max(a, b) ((a) > (b) ? (a) : (b))
static inline void free_wgdevice(struct wgdevice *dev)
{
if (!dev)
return;
for (struct wgpeer *peer = dev->first_peer, *np = peer ? peer->next_peer : NULL; peer; peer = np, np = peer ? peer->next_peer : NULL) {
for (struct wgallowedip *allowedip = peer->first_allowedip, *na = allowedip ? allowedip->next_allowedip : NULL; allowedip; allowedip = na, na = allowedip ? allowedip->next_allowedip : NULL)
free(allowedip);
free(peer);
}
free(dev);
}
#endif

View file

@ -5,7 +5,7 @@
#include <stdbool.h>
#include <stdint.h>
#include "../uapi.h"
#include "containers.h"
#define WG_KEY_LEN_BASE64 ((((WG_KEY_LEN) + 2) / 3) * 4 + 1)
#define WG_KEY_LEN_HEX (WG_KEY_LEN * 2 + 1)

611
src/ipc.c
View file

@ -5,6 +5,8 @@
#include <linux/if_link.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/genetlink.h>
#include "mnlg.h"
#endif
#include <netinet/in.h>
#include <net/if.h>
@ -29,9 +31,10 @@
#include <arpa/inet.h>
#include "ipc.h"
#include "containers.h"
#include "encoding.h"
#include "curve25519.h"
#include "../uapi.h"
#include "../uapi/wireguard.h"
#define SOCK_PATH RUNSTATEDIR "/wireguard/"
#define SOCK_SUFFIX ".sock"
@ -44,8 +47,6 @@ struct inflatable_buffer {
size_t pos;
};
#define max(a, b) ((a) > (b) ? (a) : (b))
static int add_next_to_inflatable_buffer(struct inflatable_buffer *buffer)
{
size_t len, expand_to;
@ -84,6 +85,19 @@ static int add_next_to_inflatable_buffer(struct inflatable_buffer *buffer)
return 0;
}
static void warn_unrecognized(const char *which)
{
static bool once = false;
if (once)
return;
once = true;
fprintf(stderr,
"Warning: this program received from your %s one or more\n"
"attributes that it did not recognize. It is possible that\n"
"this version of wg(8) is older than your %s. You may\n"
"want to update this program.\n", which, which);
}
static FILE *userspace_interface_file(const char *interface)
{
struct stat sbuf;
@ -130,15 +144,28 @@ out:
static bool userspace_has_wireguard_interface(const char *interface)
{
struct stat sbuf;
char path[PATH_MAX] = { 0 };
struct sockaddr_un addr = { .sun_family = AF_UNIX };
int fd, ret;
if (strchr(interface, '/'))
return false;
if (snprintf(path, sizeof(path) - 1, SOCK_PATH "%s" SOCK_SUFFIX, interface) < 0)
if (snprintf(addr.sun_path, sizeof(addr.sun_path) - 1, SOCK_PATH "%s" SOCK_SUFFIX, interface) < 0)
return false;
if (stat(path, &sbuf) < 0)
if (stat(addr.sun_path, &sbuf) < 0)
return false;
return S_ISSOCK(sbuf.st_mode);
if (!S_ISSOCK(sbuf.st_mode))
return false;
ret = fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (ret < 0)
return false;
ret = connect(fd, (struct sockaddr *)&addr, sizeof(addr));
if (ret < 0 && errno == ECONNREFUSED) { /* If the process is gone, we try to clean up the socket. */
close(fd);
unlink(addr.sun_path);
return false;
}
close(fd);
return true;
}
static int userspace_get_wireguard_interfaces(struct inflatable_buffer *buffer)
@ -177,42 +204,35 @@ static int userspace_set_device(struct wgdevice *dev)
{
char hex[WG_KEY_LEN_HEX], ip[INET6_ADDRSTRLEN], host[4096 + 1], service[512 + 1];
struct wgpeer *peer;
struct wgipmask *ipmask;
struct wgallowedip *allowedip;
FILE *f;
int ret;
size_t i, j;
socklen_t addr_len;
f = userspace_interface_file(dev->interface);
f = userspace_interface_file(dev->name);
if (!f)
return -errno;
fprintf(f, "set=1\n");
if (dev->flags & WGDEVICE_REMOVE_PRIVATE_KEY)
fprintf(f, "private_key=\n");
else if (!key_is_zero(dev->private_key)) {
if (dev->flags & WGDEVICE_HAS_PRIVATE_KEY) {
key_to_hex(hex, dev->private_key);
fprintf(f, "private_key=%s\n", hex);
}
if (dev->port)
fprintf(f, "listen_port=%u\n", dev->port);
if (dev->flags & WGDEVICE_REMOVE_FWMARK)
fprintf(f, "fwmark=\n");
else if (dev->fwmark)
if (dev->flags & WGDEVICE_HAS_LISTEN_PORT)
fprintf(f, "listen_port=%u\n", dev->listen_port);
if (dev->flags & WGDEVICE_HAS_FWMARK)
fprintf(f, "fwmark=%u\n", dev->fwmark);
if (dev->flags & WGDEVICE_REPLACE_PEERS)
fprintf(f, "replace_peers=true\n");
for_each_wgpeer(dev, peer, i) {
for_each_wgpeer (dev, peer) {
key_to_hex(hex, peer->public_key);
fprintf(f, "public_key=%s\n", hex);
if (peer->flags & WGPEER_REMOVE_ME) {
fprintf(f, "remove=true\n");
continue;
}
if (peer->flags & WGPEER_REMOVE_PRESHARED_KEY)
fprintf(f, "preshared_key=\n");
else if (!key_is_zero(peer->preshared_key)) {
if (peer->flags & WGPEER_HAS_PRESHARED_KEY) {
key_to_hex(hex, peer->preshared_key);
fprintf(f, "preshared_key=%s\n", hex);
}
@ -229,20 +249,20 @@ static int userspace_set_device(struct wgdevice *dev)
fprintf(f, "endpoint=%s:%s\n", host, service);
}
}
if (peer->persistent_keepalive_interval != (uint16_t)-1)
if (peer->flags & WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL)
fprintf(f, "persistent_keepalive_interval=%u\n", peer->persistent_keepalive_interval);
if (peer->flags & WGPEER_REPLACE_IPMASKS)
if (peer->flags & WGPEER_REPLACE_ALLOWEDIPS)
fprintf(f, "replace_allowed_ips=true\n");
for_each_wgipmask(peer, ipmask, j) {
if (ipmask->family == AF_INET) {
if (!inet_ntop(AF_INET, &ipmask->ip4, ip, INET6_ADDRSTRLEN))
for_each_wgallowedip (peer, allowedip) {
if (allowedip->family == AF_INET) {
if (!inet_ntop(AF_INET, &allowedip->ip4, ip, INET6_ADDRSTRLEN))
continue;
} else if (ipmask->family == AF_INET6) {
if (!inet_ntop(AF_INET6, &ipmask->ip6, ip, INET6_ADDRSTRLEN))
} else if (allowedip->family == AF_INET6) {
if (!inet_ntop(AF_INET6, &allowedip->ip6, ip, INET6_ADDRSTRLEN))
continue;
} else
continue;
fprintf(f, "allowed_ip=%s/%d\n", ip, ipmask->cidr);
fprintf(f, "allowed_ip=%s/%d\n", ip, allowedip->cidr);
}
}
fprintf(f, "\n");
@ -255,24 +275,6 @@ static int userspace_set_device(struct wgdevice *dev)
return ret;
}
#define ADD(bytes) ({ \
if (buffer_len - buffer_end < bytes) { \
ptrdiff_t peer_offset = (void *)peer - (void *)*out; \
buffer_len = buffer_len * 2 + bytes; \
*out = realloc(*out, buffer_len); \
if (!*out) { \
ret = -errno; \
goto err; \
} \
memset((void *)*out + buffer_end, 0, buffer_len - buffer_end); \
if (peer) \
peer = (void *)*out + peer_offset; \
dev = *out; \
} \
buffer_end += bytes; \
(void *)*out + buffer_end - bytes; \
})
#define NUM(max) ({ \
unsigned long long num; \
char *end; \
@ -288,11 +290,16 @@ static int userspace_get_device(struct wgdevice **out, const char *interface)
{
struct wgdevice *dev;
struct wgpeer *peer = NULL;
size_t buffer_len = 0, buffer_end = 0, line_buffer_len = 0, line_len;
struct wgallowedip *allowedip = NULL;
size_t line_buffer_len = 0, line_len;
char *key = NULL, *value;
FILE *f;
int ret = -EPROTO;
*out = dev = calloc(1, sizeof(struct wgdevice));
if (!dev)
return -errno;
f = userspace_interface_file(interface);
if (!f)
return -errno;
@ -300,11 +307,8 @@ static int userspace_get_device(struct wgdevice **out, const char *interface)
fprintf(f, "get=1\n\n");
fflush(f);
*out = NULL;
dev = ADD(sizeof(struct wgdevice));
dev->version_magic = WG_API_VERSION_MAGIC;
strncpy(dev->interface, interface, IFNAMSIZ - 1);
dev->interface[IFNAMSIZ - 1] = '\0';
strncpy(dev->name, interface, IFNAMSIZ - 1);
dev->name[IFNAMSIZ - 1] = '\0';
while (getline(&key, &line_buffer_len, f) > 0) {
line_len = strlen(key);
@ -322,18 +326,31 @@ static int userspace_get_device(struct wgdevice **out, const char *interface)
if (!key_from_hex(dev->private_key, value))
break;
curve25519_generate_public(dev->public_key, dev->private_key);
} else if (!peer && !strcmp(key, "listen_port"))
dev->port = NUM(0xffffU);
else if (!peer && !strcmp(key, "fwmark"))
dev->flags |= WGDEVICE_HAS_PRIVATE_KEY;
} else if (!peer && !strcmp(key, "listen_port")) {
dev->listen_port = NUM(0xffffU);
dev->flags |= WGDEVICE_HAS_LISTEN_PORT;
} else if (!peer && !strcmp(key, "fwmark")) {
dev->fwmark = NUM(0xffffffffU);
else if (!strcmp(key, "public_key")) {
peer = ADD(sizeof(struct wgpeer));
dev->flags |= WGDEVICE_HAS_FWMARK;
} else if (!strcmp(key, "public_key")) {
struct wgpeer *new_peer = calloc(1, sizeof(struct wgpeer));
if (!new_peer) {
ret = -ENOMEM;
goto err;
}
allowedip = NULL;
if (peer)
peer->next_peer = new_peer;
else
dev->first_peer = new_peer;
peer = new_peer;
if (!key_from_hex(peer->public_key, value))
break;
++dev->num_peers;
} else if (peer && !strcmp(key, "preshared_key")) {
if (!key_from_hex(peer->preshared_key, value))
break;
peer->flags |= WGPEER_HAS_PRESHARED_KEY;
} else if (peer && !strcmp(key, "endpoint")) {
char *begin, *end;
struct addrinfo *resolved;
@ -371,26 +388,36 @@ static int userspace_get_device(struct wgdevice **out, const char *interface)
break;
}
freeaddrinfo(resolved);
} else if (peer && !strcmp(key, "persistent_keepalive_interval"))
peer->persistent_keepalive_interval = NUM(65535U);
else if (peer && !strcmp(key, "allowed_ip")) {
struct wgipmask *ipmask = ADD(sizeof(struct wgipmask));
} else if (peer && !strcmp(key, "persistent_keepalive_interval")) {
peer->persistent_keepalive_interval = NUM(0xffffU);
peer->flags |= WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL;
} else if (peer && !strcmp(key, "allowed_ip")) {
struct wgallowedip *new_allowedip;
char *end, *cidr = strchr(value, '/');
if (!cidr || strlen(cidr) <= 1)
break;
*cidr++ = '\0';
ipmask->family = AF_UNSPEC;
if (strchr(value, ':')) {
if (inet_pton(AF_INET6, value, &ipmask->ip6) == 1)
ipmask->family = AF_INET6;
} else {
if (inet_pton(AF_INET, value, &ipmask->ip4) == 1)
ipmask->family = AF_INET;
new_allowedip = calloc(1, sizeof(struct wgallowedip));
if (!new_allowedip) {
ret = -ENOMEM;
goto err;
}
ipmask->cidr = strtoul(cidr, &end, 10);
if (*end || ipmask->family == AF_UNSPEC || (ipmask->family == AF_INET6 && ipmask->cidr > 128) || (ipmask->family == AF_INET && ipmask->cidr > 32))
if (allowedip)
allowedip->next_allowedip = new_allowedip;
else
peer->first_allowedip = new_allowedip;
allowedip = new_allowedip;
allowedip->family = AF_UNSPEC;
if (strchr(value, ':')) {
if (inet_pton(AF_INET6, value, &allowedip->ip6) == 1)
allowedip->family = AF_INET6;
} else {
if (inet_pton(AF_INET, value, &allowedip->ip4) == 1)
allowedip->family = AF_INET;
}
allowedip->cidr = strtoul(cidr, &end, 10);
if (*end || allowedip->family == AF_UNSPEC || (allowedip->family == AF_INET6 && allowedip->cidr > 128) || (allowedip->family == AF_INET && allowedip->cidr > 32))
break;
++peer->num_ipmasks;
} else if (peer && !strcmp(key, "last_handshake_time_sec"))
peer->last_handshake_time.tv_sec = NUM(0xffffffffffffffffULL);
else if (peer && !strcmp(key, "last_handshake_time_nsec"))
@ -402,32 +429,21 @@ static int userspace_get_device(struct wgdevice **out, const char *interface)
else if (!strcmp(key, "errno"))
ret = -NUM(0x7fffffffU);
else
break;
warn_unrecognized("daemon");
}
ret = -EPROTO;
err:
free(key);
free(*out);
free_wgdevice(dev);
*out = NULL;
fclose(f);
errno = -ret;
return ret;
}
#undef ADD
#undef NUM
#undef KEY
#ifdef __linux__
static int check_version_magic(struct wgdevice *device, int ret)
{
if (ret == -EPROTO || (!ret && device->version_magic != WG_API_VERSION_MAGIC)) {
fprintf(stderr, "This program was built for a different version of WireGuard than\nwhat is currently running. Either this version of wg(8) is out\nof date, or the currently loaded WireGuard module is out of date.\nIf you have just updated your WireGuard installation, you may have\nforgotten to unload the previous running WireGuard module. Try\nrunning `rmmod wireguard` as root, and then try re-adding the interface\nand trying again.\n\n");
errno = EPROTO;
return -EPROTO;
}
return ret;
}
static int parse_linkinfo(const struct nlattr *attr, void *data)
{
@ -477,7 +493,7 @@ static int kernel_get_wireguard_interfaces(struct inflatable_buffer *buffer)
struct ifinfomsg *ifm;
ret = -ENOMEM;
rtnl_buffer = calloc(4096, 1);
rtnl_buffer = calloc(MNL_SOCKET_BUFFER_SIZE, 1);
if (!rtnl_buffer)
goto cleanup;
@ -508,7 +524,7 @@ static int kernel_get_wireguard_interfaces(struct inflatable_buffer *buffer)
}
another:
if ((len = mnl_socket_recvfrom(nl, rtnl_buffer, 4096)) < 0) {
if ((len = mnl_socket_recvfrom(nl, rtnl_buffer, MNL_SOCKET_BUFFER_SIZE)) < 0) {
ret = -errno;
goto cleanup;
}
@ -527,82 +543,368 @@ cleanup:
return ret;
}
static bool kernel_has_wireguard_interface(const char *interface)
static int kernel_set_device(struct wgdevice *dev)
{
char *this_interface;
struct inflatable_buffer buffer = { .len = 4096 };
int ret = 0;
size_t i, j;
struct wgpeer *peer = NULL;
struct wgallowedip *allowedip = NULL;
struct nlattr *peers_nest, *peer_nest, *allowedips_nest, *allowedip_nest;
struct nlmsghdr *nlh;
struct mnlg_socket *nlg;
buffer.buffer = calloc(1, buffer.len);
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(buffer.buffer);
return true;
}
}
free(buffer.buffer);
return false;
}
nlg= mnlg_socket_open(WG_GENL_NAME, WG_GENL_VERSION);
if (!nlg)
return -errno;
static int do_ioctl(int req, struct ifreq *ifreq)
{
static int fd = -1;
int ret;
if (fd < 0) {
fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0)
return fd;
again:
nlh = mnlg_msg_prepare(nlg, WG_CMD_SET_DEVICE, NLM_F_REQUEST | NLM_F_ACK);
mnl_attr_put_strz(nlh, WGDEVICE_A_IFNAME, dev->name);
if (!peer) {
uint32_t flags = 0;
if (dev->flags & WGDEVICE_HAS_PRIVATE_KEY)
mnl_attr_put(nlh, WGDEVICE_A_PRIVATE_KEY, sizeof(dev->private_key), dev->private_key);
if (dev->flags & WGDEVICE_HAS_LISTEN_PORT)
mnl_attr_put_u16(nlh, WGDEVICE_A_LISTEN_PORT, dev->listen_port);
if (dev->flags & WGDEVICE_HAS_FWMARK)
mnl_attr_put_u32(nlh, WGDEVICE_A_FWMARK, dev->fwmark);
if (dev->flags & WGDEVICE_REPLACE_PEERS)
flags |= WGDEVICE_F_REPLACE_PEERS;
if (flags)
mnl_attr_put_u32(nlh, WGDEVICE_A_FLAGS, flags);
}
ret = ioctl(fd, req, ifreq);
if (ret == -1)
if (!dev->first_peer)
goto send;
peers_nest = peer_nest = allowedips_nest = allowedip_nest = NULL;
peers_nest = mnl_attr_nest_start(nlh, WGDEVICE_A_PEERS);
for (i = 0, peer = peer ? peer : dev->first_peer; peer; peer = peer->next_peer) {
uint32_t flags = 0;
peer_nest = mnl_attr_nest_start_check(nlh, MNL_SOCKET_BUFFER_SIZE, i++);
if (!peer_nest)
goto toobig_peers;
if (!mnl_attr_put_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_PUBLIC_KEY, sizeof(peer->public_key), peer->public_key))
goto toobig_peers;
if (peer->flags & WGPEER_REMOVE_ME)
flags |= WGPEER_F_REMOVE_ME;
if (!allowedip) {
if (peer->flags & WGPEER_REPLACE_ALLOWEDIPS)
flags |= WGPEER_F_REPLACE_ALLOWEDIPS;
if (peer->flags & WGPEER_HAS_PRESHARED_KEY) {
if (!mnl_attr_put_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_PRESHARED_KEY, sizeof(peer->preshared_key), peer->preshared_key))
goto toobig_peers;
}
if (peer->endpoint.addr.sa_family == AF_INET) {
if (!mnl_attr_put_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_ENDPOINT, sizeof(peer->endpoint.addr4), &peer->endpoint.addr4))
goto toobig_peers;
} else if (peer->endpoint.addr.sa_family == AF_INET6) {
if (!mnl_attr_put_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_ENDPOINT, sizeof(peer->endpoint.addr6), &peer->endpoint.addr6))
goto toobig_peers;
}
if (peer->flags & WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL) {
if (!mnl_attr_put_u16_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL, peer->persistent_keepalive_interval))
goto toobig_peers;
}
}
if (flags) {
if (!mnl_attr_put_u32_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_FLAGS, flags))
goto toobig_peers;
}
if (peer->first_allowedip) {
if (!allowedip)
allowedip = peer->first_allowedip;
allowedips_nest = mnl_attr_nest_start_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_ALLOWEDIPS);
if (!allowedips_nest)
goto toobig_allowedips;
for (j = 0; allowedip; allowedip = allowedip->next_allowedip) {
allowedip_nest = mnl_attr_nest_start_check(nlh, MNL_SOCKET_BUFFER_SIZE, j++);
if (!allowedip_nest)
goto toobig_allowedips;
if (!mnl_attr_put_u16_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGALLOWEDIP_A_FAMILY, allowedip->family))
goto toobig_allowedips;
if (allowedip->family == AF_INET) {
if (!mnl_attr_put_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGALLOWEDIP_A_IPADDR, sizeof(allowedip->ip4), &allowedip->ip4))
goto toobig_allowedips;
} else if (allowedip->family == AF_INET6) {
if (!mnl_attr_put_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGALLOWEDIP_A_IPADDR, sizeof(allowedip->ip6), &allowedip->ip6))
goto toobig_allowedips;
}
if (!mnl_attr_put_u8_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGALLOWEDIP_A_CIDR_MASK, allowedip->cidr))
goto toobig_allowedips;
mnl_attr_nest_end(nlh, allowedip_nest);
allowedip_nest = NULL;
}
mnl_attr_nest_end(nlh, allowedips_nest);
allowedips_nest = NULL;
}
mnl_attr_nest_end(nlh, peer_nest);
peer_nest = NULL;
}
mnl_attr_nest_end(nlh, peers_nest);
peers_nest = NULL;
goto send;
toobig_allowedips:
if (allowedip_nest)
mnl_attr_nest_cancel(nlh, allowedip_nest);
if (allowedips_nest)
mnl_attr_nest_end(nlh, allowedips_nest);
mnl_attr_nest_end(nlh, peer_nest);
mnl_attr_nest_end(nlh, peers_nest);
goto send;
toobig_peers:
if (peer_nest)
mnl_attr_nest_cancel(nlh, peer_nest);
mnl_attr_nest_end(nlh, peers_nest);
goto send;
send:
if (mnlg_socket_send(nlg, nlh) < 0) {
ret = -errno;
goto out;
}
errno = 0;
if (mnlg_socket_recv_run(nlg, NULL, NULL) < 0) {
ret = errno ? -errno : -EINVAL;
goto out;
}
if (peer)
goto again;
out:
mnlg_socket_close(nlg);
errno = -ret;
return ret;
}
static int kernel_set_device(struct wgdevice *dev)
struct get_device_ctx {
struct wgdevice *device;
struct wgpeer *peer;
struct wgallowedip *allowedip;
};
static int parse_allowedip(const struct nlattr *attr, void *data)
{
struct ifreq ifreq = { .ifr_data = (char *)dev };
memcpy(&ifreq.ifr_name, dev->interface, IFNAMSIZ);
ifreq.ifr_name[IFNAMSIZ - 1] = 0;
dev->version_magic = WG_API_VERSION_MAGIC;
return check_version_magic(dev, do_ioctl(WG_SET_DEVICE, &ifreq));
struct get_device_ctx *ctx = data;
switch (mnl_attr_get_type(attr)) {
case WGALLOWEDIP_A_FAMILY:
if (!mnl_attr_validate(attr, MNL_TYPE_U16))
ctx->allowedip->family = mnl_attr_get_u16(attr);
break;
case WGALLOWEDIP_A_IPADDR:
if (mnl_attr_get_payload_len(attr) == sizeof(ctx->allowedip->ip4))
memcpy(&ctx->allowedip->ip4, mnl_attr_get_payload(attr), sizeof(ctx->allowedip->ip4));
else if (mnl_attr_get_payload_len(attr) == sizeof(ctx->allowedip->ip6))
memcpy(&ctx->allowedip->ip6, mnl_attr_get_payload(attr), sizeof(ctx->allowedip->ip6));
break;
case WGALLOWEDIP_A_CIDR_MASK:
if (!mnl_attr_validate(attr, MNL_TYPE_U8))
ctx->allowedip->cidr = mnl_attr_get_u8(attr);
break;
default:
warn_unrecognized("kernel");
}
return MNL_CB_OK;
}
static int parse_allowedips(const struct nlattr *attr, void *data)
{
struct get_device_ctx *ctx = data;
struct wgallowedip *new_allowedip = calloc(1, sizeof(struct wgallowedip));
int ret;
if (!new_allowedip) {
perror("calloc");
return MNL_CB_ERROR;
}
if (ctx->allowedip)
ctx->allowedip->next_allowedip = new_allowedip;
else
ctx->peer->first_allowedip = new_allowedip;
ctx->allowedip = new_allowedip;
ret = mnl_attr_parse_nested(attr, parse_allowedip, ctx);
if (!ret)
return ret;
if (!((ctx->allowedip->family == AF_INET && ctx->allowedip->cidr <= 32) || (ctx->allowedip->family == AF_INET6 && ctx->allowedip->cidr <= 128)))
return MNL_CB_ERROR;
return MNL_CB_OK;
}
static int parse_peer(const struct nlattr *attr, void *data)
{
struct get_device_ctx *ctx = data;
switch (mnl_attr_get_type(attr)) {
case WGPEER_A_PUBLIC_KEY:
if (mnl_attr_get_payload_len(attr) == sizeof(ctx->peer->public_key))
memcpy(ctx->peer->public_key, mnl_attr_get_payload(attr), sizeof(ctx->peer->public_key));
break;
case WGPEER_A_PRESHARED_KEY:
if (mnl_attr_get_payload_len(attr) == sizeof(ctx->peer->preshared_key))
memcpy(ctx->peer->preshared_key, mnl_attr_get_payload(attr), sizeof(ctx->peer->preshared_key));
break;
case WGPEER_A_ENDPOINT: {
struct sockaddr *addr;
if (mnl_attr_get_payload_len(attr) < sizeof(*addr))
break;
addr = mnl_attr_get_payload(attr);
if (addr->sa_family == AF_INET && mnl_attr_get_payload_len(attr) == sizeof(ctx->peer->endpoint.addr4))
memcpy(&ctx->peer->endpoint.addr4, addr, sizeof(ctx->peer->endpoint.addr4));
else if (addr->sa_family == AF_INET6 && mnl_attr_get_payload_len(attr) == sizeof(ctx->peer->endpoint.addr6))
memcpy(&ctx->peer->endpoint.addr6, addr, sizeof(ctx->peer->endpoint.addr6));
break;
}
case WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL:
if (!mnl_attr_validate(attr, MNL_TYPE_U16))
ctx->peer->persistent_keepalive_interval = mnl_attr_get_u16(attr);
break;
case WGPEER_A_LAST_HANDSHAKE_TIME:
if (mnl_attr_get_payload_len(attr) == sizeof(ctx->peer->last_handshake_time))
memcpy(&ctx->peer->last_handshake_time, mnl_attr_get_payload(attr), sizeof(ctx->peer->last_handshake_time));
break;
case WGPEER_A_RX_BYTES:
if (!mnl_attr_validate(attr, MNL_TYPE_U64))
ctx->peer->rx_bytes = mnl_attr_get_u64(attr);
break;
case WGPEER_A_TX_BYTES:
if (!mnl_attr_validate(attr, MNL_TYPE_U64))
ctx->peer->tx_bytes = mnl_attr_get_u64(attr);
break;
case WGPEER_A_ALLOWEDIPS:
return mnl_attr_parse_nested(attr, parse_allowedips, ctx);
default:
warn_unrecognized("kernel");
}
return MNL_CB_OK;
}
static int parse_peers(const struct nlattr *attr, void *data)
{
struct get_device_ctx *ctx = data;
struct wgpeer *new_peer = calloc(1, sizeof(struct wgpeer));
int ret;
if (!new_peer) {
perror("calloc");
return MNL_CB_ERROR;
}
if (ctx->peer)
ctx->peer->next_peer = new_peer;
else
ctx->device->first_peer = new_peer;
ctx->peer = new_peer;
ctx->allowedip = NULL;
ret = mnl_attr_parse_nested(attr, parse_peer, ctx);
if (!ret)
return ret;
if (key_is_zero(ctx->peer->public_key))
return MNL_CB_ERROR;
return MNL_CB_OK;
}
static int parse_device(const struct nlattr *attr, void *data)
{
struct get_device_ctx *ctx = data;
switch (mnl_attr_get_type(attr)) {
case WGDEVICE_A_IFINDEX:
if (!mnl_attr_validate(attr, MNL_TYPE_U32))
ctx->device->ifindex = mnl_attr_get_u32(attr);
break;
case WGDEVICE_A_IFNAME:
if (!mnl_attr_validate(attr, MNL_TYPE_STRING))
strncpy(ctx->device->name, mnl_attr_get_str(attr), sizeof(ctx->device->name) - 1);
break;
case WGDEVICE_A_PRIVATE_KEY:
if (mnl_attr_get_payload_len(attr) == sizeof(ctx->device->private_key))
memcpy(ctx->device->private_key, mnl_attr_get_payload(attr), sizeof(ctx->device->private_key));
break;
case WGDEVICE_A_PUBLIC_KEY:
if (mnl_attr_get_payload_len(attr) == sizeof(ctx->device->public_key))
memcpy(ctx->device->public_key, mnl_attr_get_payload(attr), sizeof(ctx->device->public_key));
break;
case WGDEVICE_A_LISTEN_PORT:
if (!mnl_attr_validate(attr, MNL_TYPE_U16))
ctx->device->listen_port = mnl_attr_get_u16(attr);
break;
case WGDEVICE_A_FWMARK:
if (!mnl_attr_validate(attr, MNL_TYPE_U32))
ctx->device->fwmark = mnl_attr_get_u32(attr);
break;
case WGDEVICE_A_PEERS:
return mnl_attr_parse_nested(attr, parse_peers, ctx);
default:
warn_unrecognized("kernel");
}
return MNL_CB_OK;
}
static int read_device_cb(const struct nlmsghdr *nlh, void *data)
{
return mnl_attr_parse(nlh, sizeof(struct genlmsghdr), parse_device, data);
}
static void coalesce_peers(struct wgdevice *device)
{
struct wgallowedip *allowedip;
struct wgpeer *old_next_peer, *peer = device->first_peer;
while (peer && peer->next_peer) {
if (memcmp(peer->public_key, peer->next_peer->public_key, WG_KEY_LEN)) {
peer = peer->next_peer;
continue;
}
/* TODO: It would be more efficient to store the tail, rather than having to seek to the end each time. */
for (allowedip = peer->first_allowedip; allowedip && allowedip->next_allowedip; allowedip = allowedip->next_allowedip);
if (!allowedip)
peer->first_allowedip = peer->next_peer->first_allowedip;
else
allowedip->next_allowedip = peer->next_peer->first_allowedip;
old_next_peer = peer->next_peer;
peer->next_peer = old_next_peer->next_peer;
free(old_next_peer);
}
}
static int kernel_get_device(struct wgdevice **dev, const char *interface)
{
int ret;
struct ifreq ifreq = { 0 };
memcpy(&ifreq.ifr_name, interface, IFNAMSIZ);
ifreq.ifr_name[IFNAMSIZ - 1] = 0;
*dev = NULL;
do {
free(*dev);
ret = do_ioctl(WG_GET_DEVICE, &ifreq);
if (ret < 0)
goto out;
*dev = calloc(1, ret + sizeof(struct wgdevice));
ret = -ENOMEM;
int ret = 0;
struct nlmsghdr *nlh;
struct mnlg_socket *nlg;
struct get_device_ctx ctx = { 0 };
*dev = ctx.device = calloc(1, sizeof(struct wgdevice));
if (!*dev)
return -errno;
nlg= mnlg_socket_open(WG_GENL_NAME, WG_GENL_VERSION);
if (!nlg) {
free_wgdevice(*dev);
*dev = NULL;
return -errno;
}
nlh = mnlg_msg_prepare(nlg, WG_CMD_GET_DEVICE, NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP);
mnl_attr_put_strz(nlh, WGDEVICE_A_IFNAME, interface);
if (mnlg_socket_send(nlg, nlh) < 0) {
ret = -errno;
goto out;
(*dev)->peers_size = ret;
(*dev)->version_magic = WG_API_VERSION_MAGIC;
ifreq.ifr_data = (char *)*dev;
memcpy(&ifreq.ifr_name, interface, IFNAMSIZ);
ifreq.ifr_name[IFNAMSIZ - 1] = 0;
ret = do_ioctl(WG_GET_DEVICE, &ifreq);
} while (ret == -EMSGSIZE);
ret = check_version_magic(*dev, ret);
if (ret < 0) {
free(*dev);
}
errno = 0;
if (mnlg_socket_recv_run(nlg, read_device_cb, &ctx) < 0) {
ret = errno ? -errno : -EINVAL;
goto out;
}
coalesce_peers(*dev);
out:
if (nlg)
mnlg_socket_close(nlg);
if (ret) {
free_wgdevice(*dev);
*dev = NULL;
}
out:
errno = -ret;
return ret;
}
@ -611,7 +913,7 @@ out:
/* first\0second\0third\0forth\0last\0\0 */
char *ipc_list_devices(void)
{
struct inflatable_buffer buffer = { .len = 4096 };
struct inflatable_buffer buffer = { .len = MNL_SOCKET_BUFFER_SIZE };
int ret;
ret = -ENOMEM;
@ -652,19 +954,10 @@ int ipc_get_device(struct wgdevice **dev, const char *interface)
int ipc_set_device(struct wgdevice *dev)
{
#ifdef __linux__
if (userspace_has_wireguard_interface(dev->interface))
if (userspace_has_wireguard_interface(dev->name))
return userspace_set_device(dev);
return kernel_set_device(dev);
#else
return userspace_set_device(dev);
#endif
}
bool ipc_has_device(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

@ -10,6 +10,5 @@ struct wgdevice;
int ipc_set_device(struct wgdevice *dev);
int ipc_get_device(struct wgdevice **dev, const char *interface);
char *ipc_list_devices(void);
bool ipc_has_device(const char *interface);
#endif

327
src/mnlg.c Normal file
View file

@ -0,0 +1,327 @@
/* Copyright (C) 2017 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*
* Original author: Jiri Pirko <jiri@mellanox.com> */
#ifdef __linux__
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <libmnl/libmnl.h>
#include <linux/genetlink.h>
#include "mnlg.h"
struct mnlg_socket {
struct mnl_socket *nl;
char *buf;
uint32_t id;
uint8_t version;
unsigned int seq;
unsigned int portid;
};
static struct nlmsghdr *__mnlg_msg_prepare(struct mnlg_socket *nlg, uint8_t cmd,
uint16_t flags, uint32_t id,
uint8_t version)
{
struct nlmsghdr *nlh;
struct genlmsghdr *genl;
nlh = mnl_nlmsg_put_header(nlg->buf);
nlh->nlmsg_type = id;
nlh->nlmsg_flags = flags;
nlg->seq = time(NULL);
nlh->nlmsg_seq = nlg->seq;
genl = mnl_nlmsg_put_extra_header(nlh, sizeof(struct genlmsghdr));
genl->cmd = cmd;
genl->version = version;
return nlh;
}
struct nlmsghdr *mnlg_msg_prepare(struct mnlg_socket *nlg, uint8_t cmd,
uint16_t flags)
{
return __mnlg_msg_prepare(nlg, cmd, flags, nlg->id, nlg->version);
}
int mnlg_socket_send(struct mnlg_socket *nlg, const struct nlmsghdr *nlh)
{
return mnl_socket_sendto(nlg->nl, nlh, nlh->nlmsg_len);
}
static int mnlg_cb_noop(const struct nlmsghdr *nlh, void *data)
{
(void)nlh;
(void)data;
return MNL_CB_OK;
}
static int mnlg_cb_error(const struct nlmsghdr *nlh, void *data)
{
const struct nlmsgerr *err = mnl_nlmsg_get_payload(nlh);
(void)data;
if (nlh->nlmsg_len < mnl_nlmsg_size(sizeof(struct nlmsgerr))) {
errno = EBADMSG;
return MNL_CB_ERROR;
}
/* Netlink subsystems returns the errno value with different signess */
if (err->error < 0)
errno = -err->error;
else
errno = err->error;
return err->error == 0 ? MNL_CB_STOP : MNL_CB_ERROR;
}
static int mnlg_cb_stop(const struct nlmsghdr *nlh, void *data)
{
(void)data;
if (nlh->nlmsg_flags & NLM_F_MULTI && nlh->nlmsg_len == mnl_nlmsg_size(sizeof(int))) {
int error = *(int *)mnl_nlmsg_get_payload(nlh);
/* Netlink subsystems returns the errno value with different signess */
if (error < 0)
errno = -error;
else
errno = error;
return error == 0 ? MNL_CB_STOP : MNL_CB_ERROR;
}
return MNL_CB_STOP;
}
static mnl_cb_t mnlg_cb_array[] = {
[NLMSG_NOOP] = mnlg_cb_noop,
[NLMSG_ERROR] = mnlg_cb_error,
[NLMSG_DONE] = mnlg_cb_stop,
[NLMSG_OVERRUN] = mnlg_cb_noop,
};
int mnlg_socket_recv_run(struct mnlg_socket *nlg, mnl_cb_t data_cb, void *data)
{
int err;
do {
err = mnl_socket_recvfrom(nlg->nl, nlg->buf,
MNL_SOCKET_BUFFER_SIZE);
if (err <= 0)
break;
err = mnl_cb_run2(nlg->buf, err, nlg->seq, nlg->portid,
data_cb, data, mnlg_cb_array, sizeof(mnlg_cb_array) / sizeof(mnlg_cb_array[0]));
} while (err > 0);
return err;
}
struct group_info {
bool found;
uint32_t id;
const char *name;
};
static int parse_mc_grps_cb(const struct nlattr *attr, void *data)
{
const struct nlattr **tb = data;
int type = mnl_attr_get_type(attr);
if (mnl_attr_type_valid(attr, CTRL_ATTR_MCAST_GRP_MAX) < 0)
return MNL_CB_OK;
switch (type) {
case CTRL_ATTR_MCAST_GRP_ID:
if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0)
return MNL_CB_ERROR;
break;
case CTRL_ATTR_MCAST_GRP_NAME:
if (mnl_attr_validate(attr, MNL_TYPE_STRING) < 0)
return MNL_CB_ERROR;
break;
}
tb[type] = attr;
return MNL_CB_OK;
}
static void parse_genl_mc_grps(struct nlattr *nested,
struct group_info *group_info)
{
struct nlattr *pos;
const char *name;
mnl_attr_for_each_nested(pos, nested) {
struct nlattr *tb[CTRL_ATTR_MCAST_GRP_MAX + 1] = {};
mnl_attr_parse_nested(pos, parse_mc_grps_cb, tb);
if (!tb[CTRL_ATTR_MCAST_GRP_NAME] ||
!tb[CTRL_ATTR_MCAST_GRP_ID])
continue;
name = mnl_attr_get_str(tb[CTRL_ATTR_MCAST_GRP_NAME]);
if (strcmp(name, group_info->name) != 0)
continue;
group_info->id = mnl_attr_get_u32(tb[CTRL_ATTR_MCAST_GRP_ID]);
group_info->found = true;
}
}
static int get_group_id_attr_cb(const struct nlattr *attr, void *data)
{
const struct nlattr **tb = data;
int type = mnl_attr_get_type(attr);
if (mnl_attr_type_valid(attr, CTRL_ATTR_MAX) < 0)
return MNL_CB_ERROR;
if (type == CTRL_ATTR_MCAST_GROUPS &&
mnl_attr_validate(attr, MNL_TYPE_NESTED) < 0)
return MNL_CB_ERROR;
tb[type] = attr;
return MNL_CB_OK;
}
static int get_group_id_cb(const struct nlmsghdr *nlh, void *data)
{
struct group_info *group_info = data;
struct nlattr *tb[CTRL_ATTR_MAX + 1] = { 0 };
mnl_attr_parse(nlh, sizeof(struct genlmsghdr), get_group_id_attr_cb, tb);
if (!tb[CTRL_ATTR_MCAST_GROUPS])
return MNL_CB_ERROR;
parse_genl_mc_grps(tb[CTRL_ATTR_MCAST_GROUPS], group_info);
return MNL_CB_OK;
}
int mnlg_socket_group_add(struct mnlg_socket *nlg, const char *group_name)
{
struct nlmsghdr *nlh;
struct group_info group_info;
int err;
nlh = __mnlg_msg_prepare(nlg, CTRL_CMD_GETFAMILY,
NLM_F_REQUEST | NLM_F_ACK, GENL_ID_CTRL, 1);
mnl_attr_put_u32(nlh, CTRL_ATTR_FAMILY_ID, nlg->id);
err = mnlg_socket_send(nlg, nlh);
if (err < 0)
return err;
group_info.found = false;
group_info.name = group_name;
err = mnlg_socket_recv_run(nlg, get_group_id_cb, &group_info);
if (err < 0)
return err;
if (!group_info.found) {
errno = ENOENT;
return -1;
}
err = mnl_socket_setsockopt(nlg->nl, NETLINK_ADD_MEMBERSHIP,
&group_info.id, sizeof(group_info.id));
if (err < 0)
return err;
return 0;
}
static int get_family_id_attr_cb(const struct nlattr *attr, void *data)
{
const struct nlattr **tb = data;
int type = mnl_attr_get_type(attr);
if (mnl_attr_type_valid(attr, CTRL_ATTR_MAX) < 0)
return MNL_CB_ERROR;
if (type == CTRL_ATTR_FAMILY_ID &&
mnl_attr_validate(attr, MNL_TYPE_U16) < 0)
return MNL_CB_ERROR;
tb[type] = attr;
return MNL_CB_OK;
}
static int get_family_id_cb(const struct nlmsghdr *nlh, void *data)
{
uint32_t *p_id = data;
struct nlattr *tb[CTRL_ATTR_MAX + 1] = { 0 };
mnl_attr_parse(nlh, sizeof(struct genlmsghdr), get_family_id_attr_cb, tb);
if (!tb[CTRL_ATTR_FAMILY_ID])
return MNL_CB_ERROR;
*p_id = mnl_attr_get_u16(tb[CTRL_ATTR_FAMILY_ID]);
return MNL_CB_OK;
}
struct mnlg_socket *mnlg_socket_open(const char *family_name, uint8_t version)
{
struct mnlg_socket *nlg;
struct nlmsghdr *nlh;
int err;
nlg = malloc(sizeof(*nlg));
if (!nlg)
return NULL;
err = -ENOMEM;
nlg->buf = malloc(MNL_SOCKET_BUFFER_SIZE);
if (!nlg->buf)
goto err_buf_alloc;
nlg->nl = mnl_socket_open(NETLINK_GENERIC);
if (!nlg->nl) {
err = -errno;
goto err_mnl_socket_open;
}
if (mnl_socket_bind(nlg->nl, 0, MNL_SOCKET_AUTOPID) < 0) {
err = -errno;
goto err_mnl_socket_bind;
}
nlg->portid = mnl_socket_get_portid(nlg->nl);
nlh = __mnlg_msg_prepare(nlg, CTRL_CMD_GETFAMILY,
NLM_F_REQUEST | NLM_F_ACK, GENL_ID_CTRL, 1);
mnl_attr_put_strz(nlh, CTRL_ATTR_FAMILY_NAME, family_name);
if (mnlg_socket_send(nlg, nlh) < 0) {
err = -errno;
goto err_mnlg_socket_send;
}
errno = 0;
if (mnlg_socket_recv_run(nlg, get_family_id_cb, &nlg->id) < 0) {
errno = errno == ENOENT ? EPROTONOSUPPORT : errno;
err = errno ? -errno : -ENOSYS;
goto err_mnlg_socket_recv_run;
}
nlg->version = version;
errno = 0;
return nlg;
err_mnlg_socket_recv_run:
err_mnlg_socket_send:
err_mnl_socket_bind:
mnl_socket_close(nlg->nl);
err_mnl_socket_open:
free(nlg->buf);
err_buf_alloc:
free(nlg);
errno = -err;
return NULL;
}
void mnlg_socket_close(struct mnlg_socket *nlg)
{
mnl_socket_close(nlg->nl);
free(nlg->buf);
free(nlg);
}
#endif

22
src/mnlg.h Normal file
View file

@ -0,0 +1,22 @@
/* Copyright (C) 2017 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*
* Original author: Jiri Pirko <jiri@mellanox.com> */
#ifndef MNLG_H
#define MNLG_H
#ifdef __linux__
#include <libmnl/libmnl.h>
struct mnlg_socket;
struct nlmsghdr *mnlg_msg_prepare(struct mnlg_socket *nlg, uint8_t cmd,
uint16_t flags);
int mnlg_socket_send(struct mnlg_socket *nlg, const struct nlmsghdr *nlh);
int mnlg_socket_recv_run(struct mnlg_socket *nlg, mnl_cb_t data_cb, void *data);
int mnlg_socket_group_add(struct mnlg_socket *nlg, const char *group_name);
struct mnlg_socket *mnlg_socket_open(const char *family_name, uint8_t version);
void mnlg_socket_close(struct mnlg_socket *nlg);
#endif
#endif

View file

@ -3,9 +3,11 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "subcommands.h"
#include "containers.h"
#include "config.h"
#include "ipc.h"
#include "subcommands.h"
int set_main(int argc, char *argv[])
{
@ -17,10 +19,11 @@ int set_main(int argc, char *argv[])
return 1;
}
if (!config_read_cmd(&device, argv + 2, argc - 2))
device = config_read_cmd(argv + 2, argc - 2);
if (!device)
goto cleanup;
strncpy(device->interface, argv[1], IFNAMSIZ - 1);
device->interface[IFNAMSIZ - 1] = 0;
strncpy(device->name, argv[1], IFNAMSIZ - 1);
device->name[IFNAMSIZ - 1] = 0;
if (ipc_set_device(device) != 0) {
perror("Unable to set device");
@ -30,6 +33,6 @@ int set_main(int argc, char *argv[])
ret = 0;
cleanup:
free(device);
free_wgdevice(device);
return ret;
}

View file

@ -5,6 +5,7 @@
#include <stdlib.h>
#include <string.h>
#include "containers.h"
#include "config.h"
#include "ipc.h"
#include "subcommands.h"
@ -28,7 +29,7 @@ int setconf_main(int argc, char *argv[])
perror("fopen");
return 1;
}
if (!config_read_init(&ctx, &device, !strcmp(argv[0], "addconf"))) {
if (!config_read_init(&ctx, !strcmp(argv[0], "addconf"))) {
fclose(config_input);
return 1;
}
@ -38,12 +39,13 @@ int setconf_main(int argc, char *argv[])
goto cleanup;
}
}
if (!config_read_finish(&ctx) || !device) {
device = config_read_finish(&ctx);
if (!device) {
fprintf(stderr, "Invalid configuration\n");
goto cleanup;
}
strncpy(device->interface, argv[1], IFNAMSIZ - 1);
device->interface[IFNAMSIZ - 1] = 0;
strncpy(device->name, argv[1], IFNAMSIZ - 1);
device->name[IFNAMSIZ - 1] = 0;
if (ipc_set_device(device) != 0) {
perror("Unable to set device");
@ -56,6 +58,6 @@ cleanup:
if (config_input)
fclose(config_input);
free(config_buffer);
free(device);
free_wgdevice(device);
return ret;
}

View file

@ -13,11 +13,11 @@
#include <time.h>
#include <netdb.h>
#include "containers.h"
#include "ipc.h"
#include "subcommands.h"
#include "terminal.h"
#include "encoding.h"
#include "../uapi.h"
#include "subcommands.h"
static int peer_cmp(const void *first, const void *second)
{
@ -37,42 +37,29 @@ static int peer_cmp(const void *first, const void *second)
return 0;
}
/* This, hilariously, is not the right way to sort a linked list... */
static void sort_peers(struct wgdevice *device)
{
uint8_t *new_device, *pos;
struct wgpeer **peers;
struct wgpeer *peer;
size_t i, len;
size_t peer_count = 0, i = 0;
struct wgpeer *peer, **peers;
peers = calloc(device->num_peers, sizeof(struct wgpeer *));
for_each_wgpeer (device, peer)
++peer_count;
if (!peer_count)
return;
peers = calloc(peer_count, sizeof(struct wgpeer *));
if (!peers)
return;
len = sizeof(struct wgdevice);
for_each_wgpeer(device, peer, i)
len += sizeof(struct wgpeer) + (peer->num_ipmasks * sizeof(struct wgipmask));
pos = new_device = malloc(len);
if (!new_device) {
free(peers);
return;
}
memcpy(pos, device, sizeof(struct wgdevice));
pos += sizeof(struct wgdevice);
for_each_wgpeer(device, peer, i)
peers[i] = peer;
qsort(peers, device->num_peers, sizeof(struct wgpeer *), peer_cmp);
for (i = 0; i < device->num_peers; ++i) {
len = sizeof(struct wgpeer) + (peers[i]->num_ipmasks * sizeof(struct wgipmask));
memcpy(pos, peers[i], len);
pos += len;
for_each_wgpeer (device, peer)
peers[i++] = peer;
qsort(peers, peer_count, sizeof(struct wgpeer *), peer_cmp);
device->first_peer = peers[0];
peers[0]->next_peer = NULL;
for (i = 1; i < peer_count; ++i) {
peers[i - 1]->next_peer = peers[i];
peers[i]->next_peer = NULL;
}
free(peers);
memcpy(device, new_device, pos - new_device);
free(new_device);
}
static char *key(const uint8_t key[static WG_KEY_LEN])
@ -92,7 +79,7 @@ static char *masked_key(const uint8_t masked_key[static WG_KEY_LEN])
return "(hidden)";
}
static char *ip(const struct wgipmask *ip)
static char *ip(const struct wgallowedip *ip)
{
static char buf[INET6_ADDRSTRLEN + 1];
memset(buf, 0, INET6_ADDRSTRLEN + 1);
@ -204,34 +191,33 @@ static void show_usage(void)
static void pretty_print(struct wgdevice *device)
{
size_t i, j;
struct wgpeer *peer;
struct wgipmask *ipmask;
struct wgallowedip *allowedip;
terminal_printf(TERMINAL_RESET);
terminal_printf(TERMINAL_FG_GREEN TERMINAL_BOLD "interface" TERMINAL_RESET ": " TERMINAL_FG_GREEN "%s" TERMINAL_RESET "\n", device->interface);
terminal_printf(TERMINAL_FG_GREEN TERMINAL_BOLD "interface" TERMINAL_RESET ": " TERMINAL_FG_GREEN "%s" TERMINAL_RESET "\n", device->name);
if (!key_is_zero(device->public_key))
terminal_printf(" " TERMINAL_BOLD "public key" TERMINAL_RESET ": %s\n", key(device->public_key));
if (!key_is_zero(device->private_key))
terminal_printf(" " TERMINAL_BOLD "private key" TERMINAL_RESET ": %s\n", masked_key(device->private_key));
if (device->port)
terminal_printf(" " TERMINAL_BOLD "listening port" TERMINAL_RESET ": %u\n", device->port);
if (device->listen_port)
terminal_printf(" " TERMINAL_BOLD "listening port" TERMINAL_RESET ": %u\n", device->listen_port);
if (device->fwmark)
terminal_printf(" " TERMINAL_BOLD "fwmark" TERMINAL_RESET ": 0x%x\n", device->fwmark);
if (device->num_peers) {
if (device->first_peer) {
sort_peers(device);
terminal_printf("\n");
}
for_each_wgpeer(device, peer, i) {
for_each_wgpeer (device, peer) {
terminal_printf(TERMINAL_FG_YELLOW TERMINAL_BOLD "peer" TERMINAL_RESET ": " TERMINAL_FG_YELLOW "%s" TERMINAL_RESET "\n", key(peer->public_key));
if (!key_is_zero(peer->preshared_key))
terminal_printf(" " TERMINAL_BOLD "preshared key" TERMINAL_RESET ": %s\n", masked_key(peer->preshared_key));
if (peer->endpoint.addr.sa_family == AF_INET || peer->endpoint.addr.sa_family == AF_INET6)
terminal_printf(" " TERMINAL_BOLD "endpoint" TERMINAL_RESET ": %s\n", endpoint(&peer->endpoint.addr));
terminal_printf(" " TERMINAL_BOLD "allowed ips" TERMINAL_RESET ": ");
if (peer->num_ipmasks) {
for_each_wgipmask(peer, ipmask, j)
terminal_printf("%s" TERMINAL_FG_CYAN "/" TERMINAL_RESET "%u%s", ip(ipmask), ipmask->cidr, j == (size_t)peer->num_ipmasks - 1 ? "\n" : ", ");
if (peer->first_allowedip) {
for_each_wgallowedip (peer, allowedip)
terminal_printf("%s" TERMINAL_FG_CYAN "/" TERMINAL_RESET "%u%s", ip(allowedip), allowedip->cidr, allowedip->next_allowedip ? ", " : "\n");
} else
terminal_printf("(none)\n");
if (peer->last_handshake_time.tv_sec)
@ -243,38 +229,37 @@ static void pretty_print(struct wgdevice *device)
}
if (peer->persistent_keepalive_interval)
terminal_printf(" " TERMINAL_BOLD "persistent keepalive" TERMINAL_RESET ": %s\n", every(peer->persistent_keepalive_interval));
if (i + 1 < device->num_peers)
if (peer->next_peer)
terminal_printf("\n");
}
}
static void dump_print(struct wgdevice *device, bool with_interface)
{
size_t i, j;
struct wgpeer *peer;
struct wgipmask *ipmask;
struct wgallowedip *allowedip;
if (with_interface)
printf("%s\t", device->interface);
printf("%s\t", device->name);
printf("%s\t", key(device->private_key));
printf("%s\t", key(device->public_key));
printf("%u\t", device->port);
printf("%u\t", device->listen_port);
if (device->fwmark)
printf("0x%x\n", device->fwmark);
else
printf("off\n");
for_each_wgpeer(device, peer, i) {
for_each_wgpeer (device, peer) {
if (with_interface)
printf("%s\t", device->interface);
printf("%s\t", device->name);
printf("%s\t", key(peer->public_key));
printf("%s\t", key(peer->preshared_key));
if (peer->endpoint.addr.sa_family == AF_INET || peer->endpoint.addr.sa_family == AF_INET6)
printf("%s\t", endpoint(&peer->endpoint.addr));
else
printf("(none)\t");
if (peer->num_ipmasks) {
for_each_wgipmask(peer, ipmask, j)
printf("%s/%u%c", ip(ipmask), ipmask->cidr, j == (size_t)peer->num_ipmasks - 1 ? '\t' : ',');
if (peer->first_allowedip) {
for_each_wgallowedip (peer, allowedip)
printf("%s/%u%c", ip(allowedip), allowedip->cidr, allowedip->next_allowedip ? ',' : '\t');
} else
printf("(none)\t");
printf("%llu\t", (unsigned long long)peer->last_handshake_time.tv_sec);
@ -288,32 +273,31 @@ static void dump_print(struct wgdevice *device, bool with_interface)
static bool ugly_print(struct wgdevice *device, const char *param, bool with_interface)
{
size_t i, j;
struct wgpeer *peer;
struct wgipmask *ipmask;
struct wgallowedip *allowedip;
if (!strcmp(param, "public-key")) {
if (with_interface)
printf("%s\t", device->interface);
printf("%s\t", device->name);
printf("%s\n", key(device->public_key));
} else if (!strcmp(param, "private-key")) {
if (with_interface)
printf("%s\t", device->interface);
printf("%s\t", device->name);
printf("%s\n", key(device->private_key));
} else if (!strcmp(param, "listen-port")) {
if (with_interface)
printf("%s\t", device->interface);
printf("%u\n", device->port);
printf("%s\t", device->name);
printf("%u\n", device->listen_port);
} else if (!strcmp(param, "fwmark")) {
if (with_interface)
printf("%s\t", device->interface);
printf("%s\t", device->name);
if (device->fwmark)
printf("0x%x\n", device->fwmark);
else
printf("off\n");
} else if (!strcmp(param, "endpoints")) {
if (with_interface)
printf("%s\t", device->interface);
for_each_wgpeer(device, peer, i) {
printf("%s\t", device->name);
for_each_wgpeer (device, peer) {
printf("%s\t", key(peer->public_key));
if (peer->endpoint.addr.sa_family == AF_INET || peer->endpoint.addr.sa_family == AF_INET6)
printf("%s\n", endpoint(&peer->endpoint.addr));
@ -321,48 +305,48 @@ static bool ugly_print(struct wgdevice *device, const char *param, bool with_int
printf("(none)\n");
}
} else if (!strcmp(param, "allowed-ips")) {
for_each_wgpeer(device, peer, i) {
for_each_wgpeer (device, peer) {
if (with_interface)
printf("%s\t", device->interface);
printf("%s\t", device->name);
printf("%s\t", key(peer->public_key));
if (peer->num_ipmasks) {
for_each_wgipmask(peer, ipmask, j)
printf("%s/%u%c", ip(ipmask), ipmask->cidr, j == (size_t)peer->num_ipmasks - 1 ? '\n' : ' ');
if (peer->first_allowedip) {
for_each_wgallowedip (peer, allowedip)
printf("%s/%u%c", ip(allowedip), allowedip->cidr, allowedip->next_allowedip ? ' ' : '\n');
} else
printf("(none)\n");
}
} else if (!strcmp(param, "latest-handshakes")) {
for_each_wgpeer(device, peer, i) {
for_each_wgpeer (device, peer) {
if (with_interface)
printf("%s\t", device->interface);
printf("%s\t", device->name);
printf("%s\t%llu\n", key(peer->public_key), (unsigned long long)peer->last_handshake_time.tv_sec);
}
} else if (!strcmp(param, "transfer")) {
for_each_wgpeer(device, peer, i) {
for_each_wgpeer (device, peer) {
if (with_interface)
printf("%s\t", device->interface);
printf("%s\t", device->name);
printf("%s\t%" PRIu64 "\t%" PRIu64 "\n", key(peer->public_key), (uint64_t)peer->rx_bytes, (uint64_t)peer->tx_bytes);
}
} else if (!strcmp(param, "persistent-keepalive")) {
for_each_wgpeer(device, peer, i) {
for_each_wgpeer (device, peer) {
if (with_interface)
printf("%s\t", device->interface);
printf("%s\t", device->name);
if (peer->persistent_keepalive_interval)
printf("%s\t%u\n", key(peer->public_key), peer->persistent_keepalive_interval);
else
printf("%s\toff\n", key(peer->public_key));
}
} else if (!strcmp(param, "preshared-keys")) {
for_each_wgpeer(device, peer, i) {
for_each_wgpeer (device, peer) {
if (with_interface)
printf("%s\t", device->interface);
printf("%s\t", device->name);
printf("%s\t", key(peer->public_key));
printf("%s\n", key(peer->preshared_key));
}
} else if (!strcmp(param, "peers")) {
for_each_wgpeer(device, peer, i) {
for_each_wgpeer (device, peer) {
if (with_interface)
printf("%s\t", device->interface);
printf("%s\t", device->name);
printf("%s\n", key(peer->public_key));
}
} else if (!strcmp(param, "dump"))
@ -401,7 +385,7 @@ int show_main(int argc, char *argv[])
if (argc == 3) {
if (!ugly_print(device, argv[2], true)) {
ret = 1;
free(device);
free_wgdevice(device);
break;
}
} else {
@ -409,7 +393,7 @@ int show_main(int argc, char *argv[])
if (strlen(interface + len + 1))
printf("\n");
}
free(device);
free_wgdevice(device);
}
free(interfaces);
} else if (!strcmp(argv[1], "interfaces")) {
@ -431,14 +415,8 @@ int show_main(int argc, char *argv[])
show_usage();
else {
struct wgdevice *device = NULL;
if (!ipc_has_device(argv[1])) {
fprintf(stderr, "`%s` is not a valid WireGuard interface\n", argv[1]);
show_usage();
return 1;
}
if (ipc_get_device(&device, argv[1]) < 0) {
perror("Unable to get device");
show_usage();
return 1;
}
if (argc == 3) {
@ -446,7 +424,7 @@ int show_main(int argc, char *argv[])
ret = 1;
} else
pretty_print(device);
free(device);
free_wgdevice(device);
}
return ret;
}

View file

@ -9,10 +9,10 @@
#include <stdlib.h>
#include <netdb.h>
#include "subcommands.h"
#include "containers.h"
#include "encoding.h"
#include "ipc.h"
#include "../uapi.h"
#include "subcommands.h"
int showconf_main(int argc, char *argv[])
{
@ -20,8 +20,7 @@ int showconf_main(int argc, char *argv[])
char ip[INET6_ADDRSTRLEN];
struct wgdevice *device = NULL;
struct wgpeer *peer;
struct wgipmask *ipmask;
size_t i, j;
struct wgallowedip *allowedip;
int ret = 1;
if (argc != 2) {
@ -29,20 +28,14 @@ int showconf_main(int argc, char *argv[])
return 1;
}
if (!ipc_has_device(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]);
return 1;
}
if (ipc_get_device(&device, argv[1])) {
perror("Unable to get device");
goto cleanup;
}
printf("[Interface]\n");
if (device->port)
printf("ListenPort = %u\n", device->port);
if (device->listen_port)
printf("ListenPort = %u\n", device->listen_port);
if (device->fwmark)
printf("FwMark = 0x%x\n", device->fwmark);
if (!key_is_zero(device->private_key)) {
@ -50,29 +43,29 @@ int showconf_main(int argc, char *argv[])
printf("PrivateKey = %s\n", base64);
}
printf("\n");
for_each_wgpeer(device, peer, i) {
for_each_wgpeer (device, peer) {
key_to_base64(base64, peer->public_key);
printf("[Peer]\nPublicKey = %s\n", base64);
if (!key_is_zero(peer->preshared_key)) {
key_to_base64(base64, peer->preshared_key);
printf("PresharedKey = %s\n", base64);
}
if (peer->num_ipmasks)
if (peer->first_allowedip)
printf("AllowedIPs = ");
for_each_wgipmask(peer, ipmask, j) {
if (ipmask->family == AF_INET) {
if (!inet_ntop(AF_INET, &ipmask->ip4, ip, INET6_ADDRSTRLEN))
for_each_wgallowedip (peer, allowedip) {
if (allowedip->family == AF_INET) {
if (!inet_ntop(AF_INET, &allowedip->ip4, ip, INET6_ADDRSTRLEN))
continue;
} else if (ipmask->family == AF_INET6) {
if (!inet_ntop(AF_INET6, &ipmask->ip6, ip, INET6_ADDRSTRLEN))
} else if (allowedip->family == AF_INET6) {
if (!inet_ntop(AF_INET6, &allowedip->ip6, ip, INET6_ADDRSTRLEN))
continue;
} else
continue;
printf("%s/%d", ip, ipmask->cidr);
if (j + 1 < (size_t)peer->num_ipmasks)
printf("%s/%d", ip, allowedip->cidr);
if (allowedip->next_allowedip)
printf(", ");
}
if (peer->num_ipmasks)
if (peer->first_allowedip)
printf("\n");
if (peer->endpoint.addr.sa_family == AF_INET || peer->endpoint.addr.sa_family == AF_INET6) {
@ -94,12 +87,12 @@ int showconf_main(int argc, char *argv[])
if (peer->persistent_keepalive_interval)
printf("PersistentKeepalive = %u\n", peer->persistent_keepalive_interval);
if (i + 1 < device->num_peers)
if (peer->next_peer)
printf("\n");
}
ret = 0;
cleanup:
free(device);
free_wgdevice(device);
return ret;
}