From d3ce5281677ad2eabc2d71ff67e873371f157356 Mon Sep 17 00:00:00 2001 From: Maximilian Wilhelm Date: Sun, 14 Nov 2021 23:49:54 +0100 Subject: [PATCH] wg: Add support to set bind-dev on Linux This introduces support for binding Wireguards UDP socket(s) to a given interface allowing to send/receive encapsulated packets via a Linux VRF. Signed-off-by: Maximilian Wilhelm --- src/config.c | 35 ++++++++++++++++++++++++++++++++ src/containers.h | 6 +++++- src/ipc-linux.h | 20 ++++++++++++++++++ src/man/wg.8 | 7 +++++-- src/set.c | 2 +- src/show.c | 21 +++++++++++++++++-- src/showconf.c | 2 ++ src/uapi/linux/linux/wireguard.h | 1 + 8 files changed, 88 insertions(+), 6 deletions(-) diff --git a/src/config.c b/src/config.c index 211e887..3ce2bf6 100644 --- a/src/config.c +++ b/src/config.c @@ -104,6 +104,30 @@ static inline bool parse_fwmark(uint32_t *fwmark, uint32_t *flags, const char *v return false; } +#ifdef __linux__ +static inline bool parse_bind_dev(uint32_t *bind_dev, char *bind_dev_name, uint32_t *flags, const char *value) +{ + if (strlen (value) > IFNAMSIZ) { + fprintf(stderr, "BindDev must be shorter or equal to %d chars, found: '%s'\n", IFNAMSIZ, value); + return false; + } + + snprintf(bind_dev_name, IFNAMSIZ, "%s", value); + unsigned int i = if_nametoindex(value); + if (errno) { + fprintf(stderr, "Failed to get ifIndex for BindDev '%s': %s\n", value, strerror(errno)); + return false; + } + + *flags |= WGDEVICE_HAS_BIND_DEV; + *bind_dev = (int) i; + + printf ("bind-dev %s translates to %d\n", value, i); + + return true; +} +#endif + static inline bool parse_key(uint8_t key[static WG_KEY_LEN], const char *value) { if (!key_from_base64(key, value)) { @@ -446,6 +470,10 @@ static bool process_line(struct config_ctx *ctx, const char *line) ret = parse_port(&ctx->device->listen_port, &ctx->device->flags, value); else if (key_match("FwMark")) ret = parse_fwmark(&ctx->device->fwmark, &ctx->device->flags, value); +#ifdef __linux__ + else if (key_match("BindDev")) + ret = parse_bind_dev(&ctx->device->bind_dev, (char *) &ctx->device->bind_dev_name, &ctx->device->flags, value); +#endif else if (key_match("PrivateKey")) { ret = parse_key(ctx->device->private_key, value); if (ret) @@ -582,6 +610,13 @@ struct wgdevice *config_read_cmd(const char *argv[], int argc) goto error; argv += 2; argc -= 2; +#ifdef __linux__ + } else if (!strcmp(argv[0], "bind-dev") && argc >= 2 && !peer) { + if (!parse_bind_dev(&device->bind_dev, (char *) device->bind_dev_name, &device->flags, argv[1])) + goto error; + argv += 2; + argc -= 2; +#endif } else if (!strcmp(argv[0], "private-key") && argc >= 2 && !peer) { if (!parse_keyfile(device->private_key, argv[1])) goto error; diff --git a/src/containers.h b/src/containers.h index fb5434f..babb399 100644 --- a/src/containers.h +++ b/src/containers.h @@ -71,7 +71,8 @@ enum { WGDEVICE_HAS_PRIVATE_KEY = 1U << 1, WGDEVICE_HAS_PUBLIC_KEY = 1U << 2, WGDEVICE_HAS_LISTEN_PORT = 1U << 3, - WGDEVICE_HAS_FWMARK = 1U << 4 + WGDEVICE_HAS_FWMARK = 1U << 4, + WGDEVICE_HAS_BIND_DEV = 1U << 5 }; struct wgdevice { @@ -87,6 +88,9 @@ struct wgdevice { uint16_t listen_port; struct wgpeer *first_peer, *last_peer; + + uint32_t bind_dev; + char bind_dev_name[IFNAMSIZ]; }; #define for_each_wgpeer(__dev, __peer) for ((__peer) = (__dev)->first_peer; (__peer); (__peer) = (__peer)->next_peer) diff --git a/src/ipc-linux.h b/src/ipc-linux.h index 5883ffe..43d4383 100644 --- a/src/ipc-linux.h +++ b/src/ipc-linux.h @@ -32,6 +32,15 @@ struct interface { bool is_wireguard; }; +static void get_bind_dev_name(char *ifname, const int ifindex) +{ + if_indextoname (ifindex, ifname); + if (errno) { + fprintf(stderr, "Failed to get interface name for BindDev with ID '%d': %s\n", ifindex, strerror(errno)); + ifname = "ERROR"; + } +} + static int parse_linkinfo(const struct nlattr *attr, void *data) { struct interface *interface = data; @@ -165,6 +174,8 @@ static int kernel_set_device(struct wgdevice *dev) 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_HAS_BIND_DEV) + mnl_attr_put_u32(nlh, WGDEVICE_A_BIND_IFINDEX, dev->bind_dev); if (dev->flags & WGDEVICE_REPLACE_PEERS) flags |= WGDEVICE_F_REPLACE_PEERS; if (flags) @@ -439,6 +450,15 @@ static int parse_device(const struct nlattr *attr, void *data) if (!mnl_attr_validate(attr, MNL_TYPE_U32)) device->fwmark = mnl_attr_get_u32(attr); break; + case WGDEVICE_A_BIND_IFINDEX: + if (!mnl_attr_validate(attr, MNL_TYPE_U32)) { + device->bind_dev = mnl_attr_get_u32(attr); + if (device->bind_dev) { + device->flags |= WGDEVICE_HAS_BIND_DEV; + get_bind_dev_name(device->bind_dev_name, device->bind_dev); + } + } + break; case WGDEVICE_A_PEERS: return mnl_attr_parse_nested(attr, parse_peers, device); } diff --git a/src/man/wg.8 b/src/man/wg.8 index 7984539..441fc33 100644 --- a/src/man/wg.8 +++ b/src/man/wg.8 @@ -36,7 +36,7 @@ Sub-commands that take an INTERFACE must be passed a WireGuard interface. .SH COMMANDS .TP -\fBshow\fP { \fI\fP | \fIall\fP | \fIinterfaces\fP } [\fIpublic-key\fP | \fIprivate-key\fP | \fIlisten-port\fP | \fIfwmark\fP | \fIpeers\fP | \fIpreshared-keys\fP | \fIendpoints\fP | \fIallowed-ips\fP | \fIlatest-handshakes\fP | \fIpersistent-keepalive\fP | \fItransfer\fP | \fIdump\fP] +\fBshow\fP { \fI\fP | \fIall\fP | \fIinterfaces\fP } [\fIpublic-key\fP | \fIprivate-key\fP | \fIlisten-port\fP | \fIbind-dev\fP | \fIfwmark\fP | \fIpeers\fP | \fIpreshared-keys\fP | \fIendpoints\fP | \fIallowed-ips\fP | \fIlatest-handshakes\fP | \fIpersistent-keepalive\fP | \fItransfer\fP | \fIdump\fP] Shows current WireGuard configuration and runtime information of specified \fI\fP. If no \fI\fP is specified, \fI\fP defaults to \fIall\fP. If \fIinterfaces\fP is specified, prints a list of all WireGuard interfaces, @@ -55,7 +55,7 @@ transfer-rx, transfer-tx, persistent-keepalive. Shows the current configuration of \fI\fP in the format described by \fICONFIGURATION FILE FORMAT\fP below. .TP -\fBset\fP \fI\fP [\fIlisten-port\fP \fI\fP] [\fIfwmark\fP \fI\fP] [\fIprivate-key\fP \fI\fP] [\fIpeer\fP \fI\fP [\fIremove\fP] [\fIpreshared-key\fP \fI\fP] [\fIendpoint\fP \fI:\fP] [\fIpersistent-keepalive\fP \fI\fP] [\fIallowed-ips\fP \fI/\fP[,\fI/\fP]...] ]... +\fBset\fP \fI\fP [\fIlisten-port\fP \fI\fP] [\fIbind-dev\fP \fI\fP] [\fIfwmark\fP \fI\fP] [\fIprivate-key\fP \fI\fP] [\fIpeer\fP \fI\fP [\fIremove\fP] [\fIpreshared-key\fP \fI\fP] [\fIendpoint\fP \fI:\fP] [\fIpersistent-keepalive\fP \fI\fP] [\fIallowed-ips\fP \fI/\fP[,\fI/\fP]...] ]... Sets configuration values for the specified \fI\fP. Multiple \fIpeer\fPs may be specified, and if the \fIremove\fP argument is given for a peer, that peer is removed, not configured. If \fIlisten-port\fP @@ -137,6 +137,9 @@ PrivateKey \(em a base64 private key generated by \fIwg genkey\fP. Required. ListenPort \(em a 16-bit port for listening. Optional; if not specified, chosen randomly. .IP \(bu +BindDev \(em an interface to bind the listening/sending UDP socket to. This can be +use to make Wireguard send/receive encrypted packets via a VRF on Linux. Optional. +.IP \(bu FwMark \(em a 32-bit fwmark for outgoing packets. If set to 0 or "off", this option is disabled. May be specified in hexadecimal by prepending "0x". Optional. .P diff --git a/src/set.c b/src/set.c index 6f0e0cf..b9e3878 100644 --- a/src/set.c +++ b/src/set.c @@ -18,7 +18,7 @@ int set_main(int argc, const char *argv[]) int ret = 1; if (argc < 3) { - fprintf(stderr, "Usage: %s %s [listen-port ] [fwmark ] [private-key ] [peer [remove] [preshared-key ] [endpoint :] [persistent-keepalive ] [allowed-ips /[,/]...] ]...\n", PROG_NAME, argv[0]); + fprintf(stderr, "Usage: %s %s [listen-port ] [bind-dev ] [fwmark ] [private-key ] [peer [remove] [preshared-key ] [endpoint :] [persistent-keepalive ] [allowed-ips /[,/]...] ]...\n", PROG_NAME, argv[0]); return 1; } diff --git a/src/show.c b/src/show.c index 761858b..7a0b1d5 100644 --- a/src/show.c +++ b/src/show.c @@ -202,7 +202,7 @@ static char *bytes(uint64_t b) static const char *COMMAND_NAME; static void show_usage(void) { - fprintf(stderr, "Usage: %s %s { | all | interfaces } [public-key | private-key | listen-port | fwmark | peers | preshared-keys | endpoints | allowed-ips | latest-handshakes | transfer | persistent-keepalive | dump]\n", PROG_NAME, COMMAND_NAME); + fprintf(stderr, "Usage: %s %s { | all | interfaces } [public-key | private-key | listen-port | bind-dev | fwmark | peers | preshared-keys | endpoints | allowed-ips | latest-handshakes | transfer | persistent-keepalive | dump]\n", PROG_NAME, COMMAND_NAME); } static void pretty_print(struct wgdevice *device) @@ -220,6 +220,8 @@ static void pretty_print(struct wgdevice *device) 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->bind_dev) + terminal_printf(" " TERMINAL_BOLD "bind dev" TERMINAL_RESET ": %s\n", device->bind_dev_name); if (device->first_peer) { sort_peers(device); terminal_printf("\n"); @@ -261,7 +263,11 @@ static void dump_print(struct wgdevice *device, bool with_interface) printf("%s\t", maybe_key(device->public_key, device->flags & WGDEVICE_HAS_PUBLIC_KEY)); printf("%u\t", device->listen_port); if (device->fwmark) - printf("0x%x\n", device->fwmark); + printf("0x%x\t", device->fwmark); + else + printf("off\t"); + if (device->bind_dev) + printf("%s\n", device->bind_dev_name); else printf("off\n"); for_each_wgpeer(device, peer) { @@ -311,6 +317,17 @@ static bool ugly_print(struct wgdevice *device, const char *param, bool with_int printf("0x%x\n", device->fwmark); else printf("off\n"); + } else if (!strcmp(param, "bind-dev")) { + if (with_interface) + printf("%s\t", device->name); +#ifdef __linux__ + if (device->bind_dev) + printf("%s\n", device->bind_dev_name); + else + printf("off\n"); +#else + printf("(unsupported)\n"); +#endif } else if (!strcmp(param, "endpoints")) { if (with_interface) printf("%s\t", device->name); diff --git a/src/showconf.c b/src/showconf.c index 64f8b6e..918f163 100644 --- a/src/showconf.c +++ b/src/showconf.c @@ -42,6 +42,8 @@ int showconf_main(int argc, const char *argv[]) printf("ListenPort = %u\n", device->listen_port); if (device->fwmark) printf("FwMark = 0x%x\n", device->fwmark); + if (device->bind_dev) + printf("BindDev = %s\n", device->bind_dev_name); if (device->flags & WGDEVICE_HAS_PRIVATE_KEY) { key_to_base64(base64, device->private_key); printf("PrivateKey = %s\n", base64); diff --git a/src/uapi/linux/linux/wireguard.h b/src/uapi/linux/linux/wireguard.h index 0efd52c..4666884 100644 --- a/src/uapi/linux/linux/wireguard.h +++ b/src/uapi/linux/linux/wireguard.h @@ -157,6 +157,7 @@ enum wgdevice_attribute { WGDEVICE_A_LISTEN_PORT, WGDEVICE_A_FWMARK, WGDEVICE_A_PEERS, + WGDEVICE_A_BIND_IFINDEX, __WGDEVICE_A_LAST }; #define WGDEVICE_A_MAX (__WGDEVICE_A_LAST - 1)