From f129bdab552d21ae3cd77412342b94b5f23587b2 Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Mon, 25 Sep 2017 04:22:09 +0200 Subject: netlink: switch from ioctl to netlink for configuration --- src/tools/config.c | 275 +++++++++++----------- src/tools/config.h | 30 +-- src/tools/containers.h | 95 ++++++++ src/tools/encoding.h | 2 +- src/tools/ipc.c | 601 ++++++++++++++++++++++++++++++++++++------------- src/tools/ipc.h | 1 - src/tools/mnlg.c | 327 +++++++++++++++++++++++++++ src/tools/mnlg.h | 22 ++ src/tools/set.c | 13 +- src/tools/setconf.c | 12 +- src/tools/show.c | 148 ++++++------ src/tools/showconf.c | 41 ++-- 12 files changed, 1129 insertions(+), 438 deletions(-) create mode 100644 src/tools/containers.h create mode 100644 src/tools/mnlg.c create mode 100644 src/tools/mnlg.h (limited to 'src/tools') diff --git a/src/tools/config.c b/src/tools/config.c index af74bda..0d8daeb 100644 --- a/src/tools/config.c +++ b/src/tools/config.c @@ -1,6 +1,5 @@ /* Copyright (C) 2015-2017 Jason A. Donenfeld . All Rights Reserved. */ - #include #include #include @@ -14,36 +13,12 @@ #include #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; } diff --git a/src/tools/config.h b/src/tools/config.h index 5f7761f..63a272c 100644 --- a/src/tools/config.h +++ b/src/tools/config.h @@ -4,31 +4,21 @@ #define CONFIG_H #include -#include -#include -#include -#include -#include -#include -#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 diff --git a/src/tools/containers.h b/src/tools/containers.h new file mode 100644 index 0000000..b9c85e0 --- /dev/null +++ b/src/tools/containers.h @@ -0,0 +1,95 @@ +/* Copyright (C) 2015-2017 Jason A. Donenfeld . All Rights Reserved. */ + + +#ifndef CONTAINERS_H +#define CONTAINERS_H + +#include +#include +#include +#include +#include +#include + +#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 diff --git a/src/tools/encoding.h b/src/tools/encoding.h index 9db4c6e..1f79a2a 100644 --- a/src/tools/encoding.h +++ b/src/tools/encoding.h @@ -5,7 +5,7 @@ #include #include -#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) diff --git a/src/tools/ipc.c b/src/tools/ipc.c index 45278ec..8f86eb0 100644 --- a/src/tools/ipc.c +++ b/src/tools/ipc.c @@ -5,6 +5,8 @@ #include #include #include +#include +#include "mnlg.h" #endif #include #include @@ -29,9 +31,10 @@ #include #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(addr.sun_path, &sbuf) < 0) + return false; + if (!S_ISSOCK(sbuf.st_mode)) + return false; + ret = fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (ret < 0) return false; - if (stat(path, &sbuf) < 0) + 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; - return S_ISSOCK(sbuf.st_mode); + } + 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; + new_allowedip = calloc(1, sizeof(struct wgallowedip)); + if (!new_allowedip) { + ret = -ENOMEM; + goto err; + } + 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, &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; } - 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)) + 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; + nlg= mnlg_socket_open(WG_GENL_NAME, WG_GENL_VERSION); + if (!nlg) + return -errno; + +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); } - 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; + 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; } - free(buffer.buffer); - return false; + 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 do_ioctl(int req, struct ifreq *ifreq) +struct get_device_ctx { + struct wgdevice *device; + struct wgpeer *peer; + struct wgallowedip *allowedip; +}; + +static int parse_allowedip(const struct nlattr *attr, void *data) { - static int fd = -1; + 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 (fd < 0) { - fd = socket(AF_INET, SOCK_DGRAM, 0); - if (fd < 0) - return fd; + if (!new_allowedip) { + perror("calloc"); + return MNL_CB_ERROR; } - ret = ioctl(fd, req, ifreq); - if (ret == -1) - ret = -errno; - return ret; + 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 kernel_set_device(struct wgdevice *dev) +static int parse_peer(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 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 kernel_get_device(struct wgdevice **dev, const char *interface) +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; - 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; - if (!*dev) - 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); + 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 = 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; + } + 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; + } 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 -} diff --git a/src/tools/ipc.h b/src/tools/ipc.h index 2412610..cb660bb 100644 --- a/src/tools/ipc.h +++ b/src/tools/ipc.h @@ -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 diff --git a/src/tools/mnlg.c b/src/tools/mnlg.c new file mode 100644 index 0000000..ff70bdc --- /dev/null +++ b/src/tools/mnlg.c @@ -0,0 +1,327 @@ +/* Copyright (C) 2017 Jason A. Donenfeld . All Rights Reserved. + * + * Original author: Jiri Pirko */ + +#ifdef __linux__ + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 diff --git a/src/tools/mnlg.h b/src/tools/mnlg.h new file mode 100644 index 0000000..46c53ba --- /dev/null +++ b/src/tools/mnlg.h @@ -0,0 +1,22 @@ +/* Copyright (C) 2017 Jason A. Donenfeld . All Rights Reserved. + * + * Original author: Jiri Pirko */ + +#ifndef MNLG_H +#define MNLG_H +#ifdef __linux__ + +#include + +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 diff --git a/src/tools/set.c b/src/tools/set.c index 497edcc..57a6428 100644 --- a/src/tools/set.c +++ b/src/tools/set.c @@ -3,9 +3,11 @@ #include #include #include -#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; } diff --git a/src/tools/setconf.c b/src/tools/setconf.c index c70edab..1857ff6 100644 --- a/src/tools/setconf.c +++ b/src/tools/setconf.c @@ -5,6 +5,7 @@ #include #include +#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; } diff --git a/src/tools/show.c b/src/tools/show.c index 6e5de96..476d002 100644 --- a/src/tools/show.c +++ b/src/tools/show.c @@ -13,11 +13,11 @@ #include #include +#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 *)); - if (!peers) + for_each_wgpeer (device, peer) + ++peer_count; + if (!peer_count) 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); + peers = calloc(peer_count, sizeof(struct wgpeer *)); + if (!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; } diff --git a/src/tools/showconf.c b/src/tools/showconf.c index 09dc2ec..51f9a6f 100644 --- a/src/tools/showconf.c +++ b/src/tools/showconf.c @@ -9,10 +9,10 @@ #include #include -#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 \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; } -- cgit v1.2.3-59-g8ed1b