Skip to content

Commit dcd7e9e

Browse files
committed
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 <[email protected]>
1 parent b906ecb commit dcd7e9e

File tree

8 files changed

+88
-6
lines changed

8 files changed

+88
-6
lines changed

src/config.c

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,30 @@ static inline bool parse_fwmark(uint32_t *fwmark, uint32_t *flags, const char *v
104104
return false;
105105
}
106106

107+
#ifdef __linux__
108+
static inline bool parse_bind_dev(uint32_t *bind_dev, char *bind_dev_name, uint32_t *flags, const char *value)
109+
{
110+
if (strlen (value) > IFNAMSIZ) {
111+
fprintf(stderr, "BindDev must be shorter or equal to %d chars, found: '%s'\n", IFNAMSIZ, value);
112+
return false;
113+
}
114+
115+
snprintf(bind_dev_name, IFNAMSIZ, "%s", value);
116+
unsigned int i = if_nametoindex(value);
117+
if (errno) {
118+
fprintf(stderr, "Failed to get ifIndex for BindDev '%s': %s\n", value, strerror(errno));
119+
return false;
120+
}
121+
122+
*flags |= WGDEVICE_HAS_BIND_DEV;
123+
*bind_dev = (int) i;
124+
125+
printf ("bind-dev %s translates to %d\n", value, i);
126+
127+
return true;
128+
}
129+
#endif
130+
107131
static inline bool parse_key(uint8_t key[static WG_KEY_LEN], const char *value)
108132
{
109133
if (!key_from_base64(key, value)) {
@@ -446,6 +470,10 @@ static bool process_line(struct config_ctx *ctx, const char *line)
446470
ret = parse_port(&ctx->device->listen_port, &ctx->device->flags, value);
447471
else if (key_match("FwMark"))
448472
ret = parse_fwmark(&ctx->device->fwmark, &ctx->device->flags, value);
473+
#ifdef __linux__
474+
else if (key_match("BindDev"))
475+
ret = parse_bind_dev(&ctx->device->bind_dev, (char *) &ctx->device->bind_dev_name, &ctx->device->flags, value);
476+
#endif
449477
else if (key_match("PrivateKey")) {
450478
ret = parse_key(ctx->device->private_key, value);
451479
if (ret)
@@ -582,6 +610,13 @@ struct wgdevice *config_read_cmd(const char *argv[], int argc)
582610
goto error;
583611
argv += 2;
584612
argc -= 2;
613+
#ifdef __linux__
614+
} else if (!strcmp(argv[0], "bind-dev") && argc >= 2 && !peer) {
615+
if (!parse_bind_dev(&device->bind_dev, (char *) device->bind_dev_name, &device->flags, argv[1]))
616+
goto error;
617+
argv += 2;
618+
argc -= 2;
619+
#endif
585620
} else if (!strcmp(argv[0], "private-key") && argc >= 2 && !peer) {
586621
if (!parse_keyfile(device->private_key, argv[1]))
587622
goto error;

src/containers.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ enum {
7171
WGDEVICE_HAS_PRIVATE_KEY = 1U << 1,
7272
WGDEVICE_HAS_PUBLIC_KEY = 1U << 2,
7373
WGDEVICE_HAS_LISTEN_PORT = 1U << 3,
74-
WGDEVICE_HAS_FWMARK = 1U << 4
74+
WGDEVICE_HAS_FWMARK = 1U << 4,
75+
WGDEVICE_HAS_BIND_DEV = 1U << 5
7576
};
7677

7778
struct wgdevice {
@@ -87,6 +88,9 @@ struct wgdevice {
8788
uint16_t listen_port;
8889

8990
struct wgpeer *first_peer, *last_peer;
91+
92+
uint32_t bind_dev;
93+
char bind_dev_name[IFNAMSIZ];
9094
};
9195

9296
#define for_each_wgpeer(__dev, __peer) for ((__peer) = (__dev)->first_peer; (__peer); (__peer) = (__peer)->next_peer)

src/ipc-linux.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,15 @@ struct interface {
3232
bool is_wireguard;
3333
};
3434

35+
static void get_bind_dev_name(char *ifname, const int ifindex)
36+
{
37+
if_indextoname (ifindex, ifname);
38+
if (errno) {
39+
fprintf(stderr, "Failed to get interface name for BindDev with ID '%d': %s\n", ifindex, strerror(errno));
40+
ifname = "ERROR";
41+
}
42+
}
43+
3544
static int parse_linkinfo(const struct nlattr *attr, void *data)
3645
{
3746
struct interface *interface = data;
@@ -165,6 +174,8 @@ static int kernel_set_device(struct wgdevice *dev)
165174
mnl_attr_put_u16(nlh, WGDEVICE_A_LISTEN_PORT, dev->listen_port);
166175
if (dev->flags & WGDEVICE_HAS_FWMARK)
167176
mnl_attr_put_u32(nlh, WGDEVICE_A_FWMARK, dev->fwmark);
177+
if (dev->flags & WGDEVICE_HAS_BIND_DEV)
178+
mnl_attr_put_u32(nlh, WGDEVICE_A_BIND_IFINDEX, dev->bind_dev);
168179
if (dev->flags & WGDEVICE_REPLACE_PEERS)
169180
flags |= WGDEVICE_F_REPLACE_PEERS;
170181
if (flags)
@@ -439,6 +450,15 @@ static int parse_device(const struct nlattr *attr, void *data)
439450
if (!mnl_attr_validate(attr, MNL_TYPE_U32))
440451
device->fwmark = mnl_attr_get_u32(attr);
441452
break;
453+
case WGDEVICE_A_BIND_IFINDEX:
454+
if (!mnl_attr_validate(attr, MNL_TYPE_U32)) {
455+
device->bind_dev = mnl_attr_get_u32(attr);
456+
if (device->bind_dev) {
457+
device->flags |= WGDEVICE_HAS_BIND_DEV;
458+
get_bind_dev_name(device->bind_dev_name, device->bind_dev);
459+
}
460+
}
461+
break;
442462
case WGDEVICE_A_PEERS:
443463
return mnl_attr_parse_nested(attr, parse_peers, device);
444464
}

src/man/wg.8

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ Sub-commands that take an INTERFACE must be passed a WireGuard interface.
3636
.SH COMMANDS
3737

3838
.TP
39-
\fBshow\fP { \fI<interface>\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]
39+
\fBshow\fP { \fI<interface>\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]
4040
Shows current WireGuard configuration and runtime information of specified \fI<interface>\fP.
4141
If no \fI<interface>\fP is specified, \fI<interface>\fP defaults to \fIall\fP.
4242
If \fIinterfaces\fP is specified, prints a list of all WireGuard interfaces,
@@ -55,7 +55,7 @@ transfer-rx, transfer-tx, persistent-keepalive.
5555
Shows the current configuration of \fI<interface>\fP in the format described
5656
by \fICONFIGURATION FILE FORMAT\fP below.
5757
.TP
58-
\fBset\fP \fI<interface>\fP [\fIlisten-port\fP \fI<port>\fP] [\fIfwmark\fP \fI<fwmark>\fP] [\fIprivate-key\fP \fI<file-path>\fP] [\fIpeer\fP \fI<base64-public-key>\fP [\fIremove\fP] [\fIpreshared-key\fP \fI<file-path>\fP] [\fIendpoint\fP \fI<ip>:<port>\fP] [\fIpersistent-keepalive\fP \fI<interval seconds>\fP] [\fIallowed-ips\fP \fI<ip1>/<cidr1>\fP[,\fI<ip2>/<cidr2>\fP]...] ]...
58+
\fBset\fP \fI<interface>\fP [\fIlisten-port\fP \fI<port>\fP] [\fIbind-dev\fP \fI<bind-dev>\fP] [\fIfwmark\fP \fI<fwmark>\fP] [\fIprivate-key\fP \fI<file-path>\fP] [\fIpeer\fP \fI<base64-public-key>\fP [\fIremove\fP] [\fIpreshared-key\fP \fI<file-path>\fP] [\fIendpoint\fP \fI<ip>:<port>\fP] [\fIpersistent-keepalive\fP \fI<interval seconds>\fP] [\fIallowed-ips\fP \fI<ip1>/<cidr1>\fP[,\fI<ip2>/<cidr2>\fP]...] ]...
5959
Sets configuration values for the specified \fI<interface>\fP. Multiple
6060
\fIpeer\fPs may be specified, and if the \fIremove\fP argument is given
6161
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.
137137
ListenPort \(em a 16-bit port for listening. Optional; if not specified, chosen
138138
randomly.
139139
.IP \(bu
140+
BindDev \(em an interface to bind the listening/sending UDP socket to. This can be
141+
use to make Wireguard send/receive encrypted packets via a VRF on Linux. Optional.
142+
.IP \(bu
140143
FwMark \(em a 32-bit fwmark for outgoing packets. If set to 0 or "off", this
141144
option is disabled. May be specified in hexadecimal by prepending "0x". Optional.
142145
.P

src/set.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ int set_main(int argc, const char *argv[])
1818
int ret = 1;
1919

2020
if (argc < 3) {
21-
fprintf(stderr, "Usage: %s %s <interface> [listen-port <port>] [fwmark <mark>] [private-key <file path>] [peer <base64 public key> [remove] [preshared-key <file path>] [endpoint <ip>:<port>] [persistent-keepalive <interval seconds>] [allowed-ips <ip1>/<cidr1>[,<ip2>/<cidr2>]...] ]...\n", PROG_NAME, argv[0]);
21+
fprintf(stderr, "Usage: %s %s <interface> [listen-port <port>] [bind-dev <interface>] [fwmark <mark>] [private-key <file path>] [peer <base64 public key> [remove] [preshared-key <file path>] [endpoint <ip>:<port>] [persistent-keepalive <interval seconds>] [allowed-ips <ip1>/<cidr1>[,<ip2>/<cidr2>]...] ]...\n", PROG_NAME, argv[0]);
2222
return 1;
2323
}
2424

src/show.c

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ static char *bytes(uint64_t b)
202202
static const char *COMMAND_NAME;
203203
static void show_usage(void)
204204
{
205-
fprintf(stderr, "Usage: %s %s { <interface> | 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);
205+
fprintf(stderr, "Usage: %s %s { <interface> | 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);
206206
}
207207

208208
static void pretty_print(struct wgdevice *device)
@@ -220,6 +220,8 @@ static void pretty_print(struct wgdevice *device)
220220
terminal_printf(" " TERMINAL_BOLD "listening port" TERMINAL_RESET ": %u\n", device->listen_port);
221221
if (device->fwmark)
222222
terminal_printf(" " TERMINAL_BOLD "fwmark" TERMINAL_RESET ": 0x%x\n", device->fwmark);
223+
if (device->bind_dev)
224+
terminal_printf(" " TERMINAL_BOLD "bind dev" TERMINAL_RESET ": %s\n", device->bind_dev_name);
223225
if (device->first_peer) {
224226
sort_peers(device);
225227
terminal_printf("\n");
@@ -261,7 +263,11 @@ static void dump_print(struct wgdevice *device, bool with_interface)
261263
printf("%s\t", maybe_key(device->public_key, device->flags & WGDEVICE_HAS_PUBLIC_KEY));
262264
printf("%u\t", device->listen_port);
263265
if (device->fwmark)
264-
printf("0x%x\n", device->fwmark);
266+
printf("0x%x\t", device->fwmark);
267+
else
268+
printf("off\t");
269+
if (device->bind_dev)
270+
printf("%s\n", device->bind_dev_name);
265271
else
266272
printf("off\n");
267273
for_each_wgpeer(device, peer) {
@@ -311,6 +317,17 @@ static bool ugly_print(struct wgdevice *device, const char *param, bool with_int
311317
printf("0x%x\n", device->fwmark);
312318
else
313319
printf("off\n");
320+
} else if (!strcmp(param, "bind_dev")) {
321+
if (with_interface)
322+
printf("%s\t", device->name);
323+
#ifdef __linux__
324+
if (device->bind_dev)
325+
printf("%s\n", device->bind_dev_name);
326+
else
327+
printf("off\n");
328+
#else
329+
printf("(unsupported)\n");
330+
#endif
314331
} else if (!strcmp(param, "endpoints")) {
315332
if (with_interface)
316333
printf("%s\t", device->name);

src/showconf.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ int showconf_main(int argc, const char *argv[])
4242
printf("ListenPort = %u\n", device->listen_port);
4343
if (device->fwmark)
4444
printf("FwMark = 0x%x\n", device->fwmark);
45+
if (device->bind_dev)
46+
printf("BindDev = %s\n", device->bind_dev_name);
4547
if (device->flags & WGDEVICE_HAS_PRIVATE_KEY) {
4648
key_to_base64(base64, device->private_key);
4749
printf("PrivateKey = %s\n", base64);

src/uapi/linux/linux/wireguard.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ enum wgdevice_attribute {
157157
WGDEVICE_A_LISTEN_PORT,
158158
WGDEVICE_A_FWMARK,
159159
WGDEVICE_A_PEERS,
160+
WGDEVICE_A_BIND_IFINDEX,
160161
__WGDEVICE_A_LAST
161162
};
162163
#define WGDEVICE_A_MAX (__WGDEVICE_A_LAST - 1)

0 commit comments

Comments
 (0)