aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile38
-rw-r--r--src/completion/wg.bash-completion6
-rw-r--r--src/config.c41
-rw-r--r--src/config.h4
-rw-r--r--src/containers.h15
-rw-r--r--src/ctype.h29
-rw-r--r--src/curve25519.c2
-rw-r--r--src/curve25519.h2
-rw-r--r--src/encoding.c2
-rw-r--r--src/encoding.h2
-rw-r--r--src/genkey.c12
-rw-r--r--src/ipc-freebsd.h365
-rw-r--r--src/ipc-linux.h527
-rw-r--r--src/ipc-openbsd.h286
-rw-r--r--src/ipc-uapi-unix.h119
-rw-r--r--src/ipc-uapi-windows.h107
-rw-r--r--src/ipc-uapi.h297
-rw-r--r--src/ipc-windows.h456
-rw-r--r--src/ipc.c905
-rw-r--r--src/ipc.h2
-rw-r--r--src/man/wg-quick.812
-rw-r--r--src/man/wg.818
-rw-r--r--src/pubkey.c8
-rw-r--r--src/set.c6
-rw-r--r--src/setconf.c47
-rw-r--r--src/show.c14
-rw-r--r--src/showconf.c4
-rw-r--r--src/subcommands.h14
-rw-r--r--src/systemd/wg-quick.target2
-rw-r--r--src/systemd/wg-quick@.service2
-rw-r--r--src/terminal.c30
-rw-r--r--src/terminal.h3
-rw-r--r--src/uapi/freebsd/dev/wg/if_wg.h16
-rw-r--r--src/uapi/linux/linux/wireguard.h (renamed from src/uapi/linux/wireguard.h)9
-rw-r--r--src/uapi/openbsd/net/if_wg.h92
-rw-r--r--src/uapi/windows/wireguard.h80
-rw-r--r--src/version.h2
-rw-r--r--src/wg-quick/android.c131
-rwxr-xr-xsrc/wg-quick/darwin.bash57
-rwxr-xr-xsrc/wg-quick/freebsd.bash72
-rwxr-xr-xsrc/wg-quick/linux.bash42
-rwxr-xr-xsrc/wg-quick/openbsd.bash94
-rw-r--r--src/wg.c8
-rw-r--r--src/wincompat/compat.h4
-rw-r--r--src/wincompat/getrandom.c12
-rw-r--r--src/wincompat/include/hashtable.h61
-rw-r--r--src/wincompat/include/sys/ioctl.h0
-rw-r--r--src/wincompat/include/sys/un.h0
-rw-r--r--src/wincompat/init.c12
-rw-r--r--src/wincompat/ipc.c138
-rw-r--r--src/wincompat/libc.c36
-rw-r--r--src/wincompat/loader.c20
-rw-r--r--src/wincompat/manifest.xml23
-rw-r--r--src/wincompat/resources.rc40
54 files changed, 2984 insertions, 1342 deletions
diff --git a/src/Makefile b/src/Makefile
index ec5d3a6..1c4b3f6 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -38,7 +38,9 @@ endif
PLATFORM ?= $(shell uname -s | tr '[:upper:]' '[:lower:]')
CFLAGS ?= -O3
-CFLAGS += -idirafter uapi
+ifneq ($(wildcard uapi/$(PLATFORM)/.),)
+CFLAGS += -isystem uapi/$(PLATFORM)
+endif
CFLAGS += -std=gnu99 -D_GNU_SOURCE
CFLAGS += -Wall -Wextra
CFLAGS += -MMD -MP
@@ -46,18 +48,29 @@ CFLAGS += -DRUNSTATEDIR="\"$(RUNSTATEDIR)\""
ifeq ($(DEBUG),yes)
CFLAGS += -g
endif
-WIREGUARD_TOOLS_VERSION = $(patsubst v%,%,$(shell GIT_CEILING_DIRECTORIES="$(PWD)/../.." git describe --dirty 2>/dev/null))
+WIREGUARD_TOOLS_VERSION = $(patsubst v%,%,$(shell GIT_DIR="$(PWD)/../.git" git describe --dirty 2>/dev/null))
ifneq ($(WIREGUARD_TOOLS_VERSION),)
CFLAGS += -D'WIREGUARD_TOOLS_VERSION="$(WIREGUARD_TOOLS_VERSION)"'
endif
+ifeq ($(PLATFORM),freebsd)
+LDLIBS += -lnv
+endif
ifeq ($(PLATFORM),haiku)
LDLIBS += -lnetwork -lbsd
endif
ifeq ($(PLATFORM),windows)
-CC := x86_64-w64-mingw32-gcc
-CFLAGS += -Iwincompat/include -include wincompat/compat.h
-LDLIBS += -lws2_32
-wg: wincompat/libc.o wincompat/init.o
+CC := x86_64-w64-mingw32-clang
+WINDRES := $(shell $(CC) $(CFLAGS) -print-prog-name=windres 2>/dev/null)
+CFLAGS += -Iwincompat/include -include wincompat/compat.h -DWINVER=0x0601 -D_WIN32_WINNT=0x0601 -flto
+LDLIBS += -lws2_32 -lsetupapi -lole32 -ladvapi32 -lntdll -Lwincompat
+LDFLAGS += -flto -Wl,--dynamicbase -Wl,--nxcompat -Wl,--tsaware -mconsole
+LDFLAGS += -Wl,--major-os-version=6 -Wl,--minor-os-version=1 -Wl,--major-subsystem-version=6 -Wl,--minor-subsystem-version=1
+# The use of -Wl,/delayload: here implies we're using llvm-mingw
+LDFLAGS += -Wl,/delayload:ws2_32.dll -Wl,/delayload:setupapi.dll -Wl,/delayload:ole32.dll -Wl,/delayload:advapi32.dll
+VERSION := $(patsubst "%",%,$(filter "%",$(file < version.h)))
+wg: wincompat/libc.o wincompat/init.o wincompat/loader.o wincompat/resources.o
+wincompat/resources.o: wincompat/resources.rc wincompat/manifest.xml
+ $(WINDRES) -DVERSION_STR=$(VERSION) -O coff -c 65001 -i $< -o $@
endif
ifneq ($(V),1)
@@ -67,18 +80,15 @@ LINK.o += $(BUILT_IN_LINK.o)
BUILT_IN_COMPILE.c := $(COMPILE.c)
COMPILE.c = @echo " CC $@";
COMPILE.c += $(BUILT_IN_COMPILE.c)
+BUILT_IN_RM := $(RM)
+RM := @a() { echo " CLEAN $$@"; $(BUILT_IN_RM) "$$@"; }; a
+WINDRES := @a() { echo " WINDRES $${@: -1}"; $(WINDRES) "$$@"; }; a
endif
wg: $(sort $(patsubst %.c,%.o,$(wildcard *.c)))
-ifneq ($(V),1)
clean:
- @echo " CLEAN {wg,*.o,*.d}"
- @$(RM) wg *.o *.d
-else
-clean:
- $(RM) wg *.o *.d
-endif
+ $(RM) wg *.o *.d $(wildcard wincompat/*.o wincompat/*.lib wincompat/*.dll)
install: wg
@install -v -d "$(DESTDIR)$(BINDIR)" && install -v -m 0755 wg "$(DESTDIR)$(BINDIR)/wg"
@@ -92,7 +102,7 @@ install: wg
@[ "$(WITH_WGQUICK)" = "yes" -a "$(WITH_BASHCOMPLETION)" = "yes" ] || exit 0; \
install -v -m 0644 completion/wg-quick.bash-completion "$(DESTDIR)$(BASHCOMPDIR)/wg-quick"
@[ "$(WITH_WGQUICK)" = "yes" -a "$(WITH_SYSTEMDUNITS)" = "yes" ] || exit 0; \
- install -v -d "$(DESTDIR)$(SYSTEMDUNITDIR)" && install -v -m 0644 systemd/wg-quick@.service "$(DESTDIR)$(SYSTEMDUNITDIR)/wg-quick@.service"
+ install -v -d "$(DESTDIR)$(SYSTEMDUNITDIR)" && install -v -m 0644 systemd/* "$(DESTDIR)$(SYSTEMDUNITDIR)/"
check: clean
scan-build --html-title=wireguard-tools -maxloop 100 --view --keep-going $(MAKE) wg
diff --git a/src/completion/wg.bash-completion b/src/completion/wg.bash-completion
index 445f0e8..3c062b4 100644
--- a/src/completion/wg.bash-completion
+++ b/src/completion/wg.bash-completion
@@ -5,12 +5,12 @@ _wg_completion() {
local a
if [[ $COMP_CWORD -eq 1 ]]; then
- COMPREPLY+=( $(compgen -W "show showconf set setconf addconf genkey genpsk pubkey" -- "${COMP_WORDS[1]}") )
+ COMPREPLY+=( $(compgen -W "help show showconf set setconf addconf syncconf genkey genpsk pubkey" -- "${COMP_WORDS[1]}") )
return
fi
case "${COMP_WORDS[1]}" in
genkey|genpsk|pubkey|help) return; ;;
- show|showconf|set|setconf|addconf) ;;
+ show|showconf|set|setconf|addconf|syncconf) ;;
*) return;
esac
@@ -26,7 +26,7 @@ _wg_completion() {
return
fi
- if [[ $COMP_CWORD -eq 3 && ( ${COMP_WORDS[1]} == setconf || ${COMP_WORDS[1]} == addconf ) ]]; then
+ if [[ $COMP_CWORD -eq 3 && ( ${COMP_WORDS[1]} == setconf || ${COMP_WORDS[1]} == addconf || ${COMP_WORDS[1]} == syncconf) ]]; then
compopt -o filenames
mapfile -t a < <(compgen -f -- "${COMP_WORDS[3]}")
COMPREPLY+=( "${a[@]}" )
diff --git a/src/config.c b/src/config.c
index b8394a5..6b8aa58 100644
--- a/src/config.c
+++ b/src/config.c
@@ -1,11 +1,10 @@
-// SPDX-License-Identifier: GPL-2.0
+// SPDX-License-Identifier: GPL-2.0 OR MIT
/*
* Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*/
#include <arpa/inet.h>
#include <limits.h>
-#include <ctype.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
@@ -19,6 +18,7 @@
#include "containers.h"
#include "ipc.h"
#include "encoding.h"
+#include "ctype.h"
#define COMMENT_CHAR '#'
@@ -86,7 +86,7 @@ static inline bool parse_fwmark(uint32_t *fwmark, uint32_t *flags, const char *v
return true;
}
- if (!isdigit(value[0]))
+ if (!char_is_digit(value[0]))
goto err;
if (strlen(value) > 2 && value[0] == '0' && value[1] == 'x')
@@ -141,7 +141,7 @@ static bool parse_keyfile(uint8_t key[static WG_KEY_LEN], const char *path)
dst[WG_KEY_LEN_BASE64 - 1] = '\0';
while ((c = getc(f)) != EOF) {
- if (!isspace(c)) {
+ if (!char_is_space(c)) {
fprintf(stderr, "Found trailing character in key file: `%c'\n", c);
goto out;
}
@@ -290,7 +290,7 @@ static inline bool parse_persistent_keepalive(uint16_t *interval, uint32_t *flag
return true;
}
- if (!isdigit(value[0]))
+ if (!char_is_digit(value[0]))
goto err;
ret = strtoul(value, &end, 10);
@@ -337,6 +337,20 @@ static bool validate_netmask(struct wgallowedip *allowedip)
return true;
}
+static inline void parse_ip_prefix(struct wgpeer *peer, uint32_t *flags, char **mask)
+{
+ /* If the IP is prefixed with either '+' or '-' consider this an
+ * incremental change. Disable WGPEER_REPLACE_ALLOWEDIPS. */
+ switch ((*mask)[0]) {
+ case '-':
+ *flags |= WGALLOWEDIP_REMOVE_ME;
+ /* fall through */
+ case '+':
+ peer->flags &= ~WGPEER_REPLACE_ALLOWEDIPS;
+ ++(*mask);
+ }
+}
+
static inline bool parse_allowedips(struct wgpeer *peer, struct wgallowedip **last_allowedip, const char *value)
{
struct wgallowedip *allowedip = *last_allowedip, *new_allowedip;
@@ -353,10 +367,18 @@ static inline bool parse_allowedips(struct wgpeer *peer, struct wgallowedip **la
}
sep = mutable;
while ((mask = strsep(&sep, ","))) {
+ uint32_t flags = 0;
unsigned long cidr;
char *end, *ip;
+ parse_ip_prefix(peer, &flags, &mask);
+
saved_entry = strdup(mask);
+ if (!saved_entry) {
+ perror("strdup");
+ free(mutable);
+ return false;
+ }
ip = strsep(&mask, "/");
new_allowedip = calloc(1, sizeof(*new_allowedip));
@@ -375,7 +397,7 @@ static inline bool parse_allowedips(struct wgpeer *peer, struct wgallowedip **la
}
if (mask) {
- if (!isdigit(mask[0]))
+ if (!char_is_digit(mask[0]))
goto err;
cidr = strtoul(mask, &end, 10);
if (*end || (cidr > 32 && new_allowedip->family == AF_INET) || (cidr > 128 && new_allowedip->family == AF_INET6))
@@ -387,6 +409,7 @@ static inline bool parse_allowedips(struct wgpeer *peer, struct wgallowedip **la
else
goto err;
new_allowedip->cidr = cidr;
+ new_allowedip->flags = flags;
if (!validate_netmask(new_allowedip))
fprintf(stderr, "Warning: AllowedIP has nonzero host part: %s/%s\n", ip, mask);
@@ -501,7 +524,7 @@ bool config_read_line(struct config_ctx *ctx, const char *input)
}
for (size_t i = 0; i < len; ++i) {
- if (!isspace(input[i]))
+ if (!char_is_space(input[i]))
line[cleaned_len++] = input[i];
}
if (!cleaned_len)
@@ -555,13 +578,13 @@ static char *strip_spaces(const char *in)
return NULL;
}
for (i = 0, l = 0; i < t; ++i) {
- if (!isspace(in[i]))
+ if (!char_is_space(in[i]))
out[l++] = in[i];
}
return out;
}
-struct wgdevice *config_read_cmd(char *argv[], int argc)
+struct wgdevice *config_read_cmd(const char *argv[], int argc)
{
struct wgdevice *device = calloc(1, sizeof(*device));
struct wgpeer *peer = NULL;
diff --git a/src/config.h b/src/config.h
index 8bbe236..443cf21 100644
--- a/src/config.h
+++ b/src/config.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0 */
+/* SPDX-License-Identifier: GPL-2.0 OR MIT */
/*
* Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*/
@@ -19,7 +19,7 @@ struct config_ctx {
bool is_peer_section, is_device_section;
};
-struct wgdevice *config_read_cmd(char *argv[], int argc);
+struct wgdevice *config_read_cmd(const 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);
struct wgdevice *config_read_finish(struct config_ctx *ctx);
diff --git a/src/containers.h b/src/containers.h
index 2ffc230..8fd813a 100644
--- a/src/containers.h
+++ b/src/containers.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0 */
+/* SPDX-License-Identifier: GPL-2.0 OR MIT */
/*
* Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*/
@@ -12,7 +12,15 @@
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/in.h>
+#if defined(__linux__)
#include <linux/wireguard.h>
+#elif defined(__OpenBSD__)
+#include <net/if_wg.h>
+#endif
+
+#ifndef WG_KEY_LEN
+#define WG_KEY_LEN 32
+#endif
/* Cross platform __kernel_timespec */
struct timespec64 {
@@ -20,6 +28,10 @@ struct timespec64 {
int64_t tv_nsec;
};
+enum {
+ WGALLOWEDIP_REMOVE_ME = 1U << 0,
+};
+
struct wgallowedip {
uint16_t family;
union {
@@ -27,6 +39,7 @@ struct wgallowedip {
struct in6_addr ip6;
};
uint8_t cidr;
+ uint32_t flags;
struct wgallowedip *next_allowedip;
};
diff --git a/src/ctype.h b/src/ctype.h
new file mode 100644
index 0000000..7c9942c
--- /dev/null
+++ b/src/ctype.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0 OR MIT */
+/*
+ * Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
+ *
+ * Specialized constant-time ctype.h reimplementations that aren't locale-specific.
+ */
+
+#ifndef CTYPE_H
+#define CTYPE_H
+
+#include <stdbool.h>
+
+static inline bool char_is_space(int c)
+{
+ unsigned char d = c - 9;
+ return (0x80001FU >> (d & 31)) & (1U >> (d >> 5));
+}
+
+static inline bool char_is_digit(int c)
+{
+ return (unsigned int)(('0' - 1 - c) & (c - ('9' + 1))) >> (sizeof(c) * 8 - 1);
+}
+
+static inline bool char_is_alpha(int c)
+{
+ return (unsigned int)(('a' - 1 - (c | 32)) & ((c | 32) - ('z' + 1))) >> (sizeof(c) * 8 - 1);
+}
+
+#endif
diff --git a/src/curve25519.c b/src/curve25519.c
index 1739a9e..7121d1e 100644
--- a/src/curve25519.c
+++ b/src/curve25519.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: GPL-2.0
+// SPDX-License-Identifier: GPL-2.0 OR MIT
/*
* Copyright (C) 2018-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*/
diff --git a/src/curve25519.h b/src/curve25519.h
index 1569824..b05432f 100644
--- a/src/curve25519.h
+++ b/src/curve25519.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0 */
+/* SPDX-License-Identifier: GPL-2.0 OR MIT */
/*
* Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*/
diff --git a/src/encoding.c b/src/encoding.c
index 2540e5b..9b2cda5 100644
--- a/src/encoding.c
+++ b/src/encoding.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: GPL-2.0
+// SPDX-License-Identifier: GPL-2.0 OR MIT
/*
* Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*
diff --git a/src/encoding.h b/src/encoding.h
index 2d2c1e0..3cabe9c 100644
--- a/src/encoding.h
+++ b/src/encoding.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0 */
+/* SPDX-License-Identifier: GPL-2.0 OR MIT */
/*
* Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*/
diff --git a/src/genkey.c b/src/genkey.c
index d1bb643..0201b28 100644
--- a/src/genkey.c
+++ b/src/genkey.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: GPL-2.0
+// SPDX-License-Identifier: GPL-2.0 OR MIT
/*
* Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*/
@@ -28,7 +28,7 @@
#include "encoding.h"
#include "subcommands.h"
-#ifndef WINCOMPAT
+#ifndef _WIN32
static inline bool __attribute__((__warn_unused_result__)) get_random_bytes(uint8_t *out, size_t len)
{
ssize_t ret = 0;
@@ -65,10 +65,14 @@ static inline bool __attribute__((__warn_unused_result__)) get_random_bytes(uint
return i == len;
}
#else
-#include "wincompat/getrandom.c"
+#include <ntsecapi.h>
+static inline bool __attribute__((__warn_unused_result__)) get_random_bytes(uint8_t *out, size_t len)
+{
+ return RtlGenRandom(out, len);
+}
#endif
-int genkey_main(int argc, char *argv[])
+int genkey_main(int argc, const char *argv[])
{
uint8_t key[WG_KEY_LEN];
char base64[WG_KEY_LEN_BASE64];
diff --git a/src/ipc-freebsd.h b/src/ipc-freebsd.h
new file mode 100644
index 0000000..58e5e71
--- /dev/null
+++ b/src/ipc-freebsd.h
@@ -0,0 +1,365 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright (C) 2015-2021 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
+ *
+ */
+
+#include <assert.h>
+#include <sys/nv.h>
+#include <sys/sockio.h>
+#include <dev/wg/if_wg.h>
+
+#define IPC_SUPPORTS_KERNEL_INTERFACE
+
+static int get_dgram_socket(void)
+{
+ static int sock = -1;
+ if (sock < 0)
+ sock = socket(AF_LOCAL, SOCK_DGRAM, 0);
+ return sock;
+}
+
+static int kernel_get_wireguard_interfaces(struct string_list *list)
+{
+ struct ifgroupreq ifgr = { .ifgr_name = "wg" };
+ struct ifg_req *ifg;
+ int s = get_dgram_socket(), ret = 0;
+
+ if (s < 0)
+ return -errno;
+
+ if (ioctl(s, SIOCGIFGMEMB, (caddr_t)&ifgr) < 0)
+ return errno == ENOENT ? 0 : -errno;
+
+ ifgr.ifgr_groups = calloc(1, ifgr.ifgr_len);
+ if (!ifgr.ifgr_groups)
+ return -errno;
+ if (ioctl(s, SIOCGIFGMEMB, (caddr_t)&ifgr) < 0) {
+ ret = -errno;
+ goto out;
+ }
+
+ for (ifg = ifgr.ifgr_groups; ifg && ifgr.ifgr_len > 0; ++ifg) {
+ if ((ret = string_list_add(list, ifg->ifgrq_member)) < 0)
+ goto out;
+ ifgr.ifgr_len -= sizeof(struct ifg_req);
+ }
+
+out:
+ free(ifgr.ifgr_groups);
+ return ret;
+}
+
+static int kernel_get_device(struct wgdevice **device, const char *ifname)
+{
+ struct wg_data_io wgd = { 0 };
+ nvlist_t *nvl_device = NULL;
+ const nvlist_t *const *nvl_peers;
+ struct wgdevice *dev = NULL;
+ size_t size, peer_count, i;
+ uint64_t number;
+ const void *binary;
+ int ret = 0, s;
+
+ *device = NULL;
+ s = get_dgram_socket();
+ if (s < 0)
+ goto err;
+
+ strlcpy(wgd.wgd_name, ifname, sizeof(wgd.wgd_name));
+ if (ioctl(s, SIOCGWG, &wgd) < 0)
+ goto err;
+
+ wgd.wgd_data = malloc(wgd.wgd_size);
+ if (!wgd.wgd_data)
+ goto err;
+ if (ioctl(s, SIOCGWG, &wgd) < 0)
+ goto err;
+
+ dev = calloc(1, sizeof(*dev));
+ if (!dev)
+ goto err;
+ strlcpy(dev->name, ifname, sizeof(dev->name));
+ nvl_device = nvlist_unpack(wgd.wgd_data, wgd.wgd_size, 0);
+ if (!nvl_device)
+ goto err;
+
+ if (nvlist_exists_number(nvl_device, "listen-port")) {
+ number = nvlist_get_number(nvl_device, "listen-port");
+ if (number <= UINT16_MAX) {
+ dev->listen_port = number;
+ dev->flags |= WGDEVICE_HAS_LISTEN_PORT;
+ }
+ }
+ if (nvlist_exists_number(nvl_device, "user-cookie")) {
+ number = nvlist_get_number(nvl_device, "user-cookie");
+ if (number <= UINT32_MAX) {
+ dev->fwmark = number;
+ dev->flags |= WGDEVICE_HAS_FWMARK;
+ }
+ }
+ if (nvlist_exists_binary(nvl_device, "public-key")) {
+ binary = nvlist_get_binary(nvl_device, "public-key", &size);
+ if (binary && size == sizeof(dev->public_key)) {
+ memcpy(dev->public_key, binary, sizeof(dev->public_key));
+ dev->flags |= WGDEVICE_HAS_PUBLIC_KEY;
+ }
+ }
+ if (nvlist_exists_binary(nvl_device, "private-key")) {
+ binary = nvlist_get_binary(nvl_device, "private-key", &size);
+ if (binary && size == sizeof(dev->private_key)) {
+ memcpy(dev->private_key, binary, sizeof(dev->private_key));
+ dev->flags |= WGDEVICE_HAS_PRIVATE_KEY;
+ }
+ }
+ if (!nvlist_exists_nvlist_array(nvl_device, "peers"))
+ goto skip_peers;
+ nvl_peers = nvlist_get_nvlist_array(nvl_device, "peers", &peer_count);
+ if (!nvl_peers)
+ goto skip_peers;
+ for (i = 0; i < peer_count; ++i) {
+ struct wgpeer *peer;
+ struct wgallowedip *aip = NULL;
+ const nvlist_t *const *nvl_aips;
+ size_t aip_count, j;
+
+ peer = calloc(1, sizeof(*peer));
+ if (!peer)
+ goto err_peer;
+ if (nvlist_exists_binary(nvl_peers[i], "public-key")) {
+ binary = nvlist_get_binary(nvl_peers[i], "public-key", &size);
+ if (binary && size == sizeof(peer->public_key)) {
+ memcpy(peer->public_key, binary, sizeof(peer->public_key));
+ peer->flags |= WGPEER_HAS_PUBLIC_KEY;
+ }
+ }
+ if (nvlist_exists_binary(nvl_peers[i], "preshared-key")) {
+ binary = nvlist_get_binary(nvl_peers[i], "preshared-key", &size);
+ if (binary && size == sizeof(peer->preshared_key)) {
+ memcpy(peer->preshared_key, binary, sizeof(peer->preshared_key));
+ if (!key_is_zero(peer->preshared_key))
+ peer->flags |= WGPEER_HAS_PRESHARED_KEY;
+ }
+ }
+ if (nvlist_exists_number(nvl_peers[i], "persistent-keepalive-interval")) {
+ number = nvlist_get_number(nvl_peers[i], "persistent-keepalive-interval");
+ if (number <= UINT16_MAX) {
+ peer->persistent_keepalive_interval = number;
+ peer->flags |= WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL;
+ }
+ }
+ if (nvlist_exists_binary(nvl_peers[i], "endpoint")) {
+ const struct sockaddr *endpoint = nvlist_get_binary(nvl_peers[i], "endpoint", &size);
+ if (endpoint && size <= sizeof(peer->endpoint) && size >= sizeof(peer->endpoint.addr) &&
+ (endpoint->sa_family == AF_INET || endpoint->sa_family == AF_INET6))
+ memcpy(&peer->endpoint.addr, endpoint, size);
+ }
+ if (nvlist_exists_number(nvl_peers[i], "rx-bytes"))
+ peer->rx_bytes = nvlist_get_number(nvl_peers[i], "rx-bytes");
+ if (nvlist_exists_number(nvl_peers[i], "tx-bytes"))
+ peer->tx_bytes = nvlist_get_number(nvl_peers[i], "tx-bytes");
+ if (nvlist_exists_binary(nvl_peers[i], "last-handshake-time")) {
+ binary = nvlist_get_binary(nvl_peers[i], "last-handshake-time", &size);
+ if (binary && size == sizeof(peer->last_handshake_time))
+ memcpy(&peer->last_handshake_time, binary, sizeof(peer->last_handshake_time));
+ }
+
+ if (!nvlist_exists_nvlist_array(nvl_peers[i], "allowed-ips"))
+ goto skip_allowed_ips;
+ nvl_aips = nvlist_get_nvlist_array(nvl_peers[i], "allowed-ips", &aip_count);
+ if (!aip_count || !nvl_aips)
+ goto skip_allowed_ips;
+ for (j = 0; j < aip_count; ++j) {
+ if (!nvlist_exists_number(nvl_aips[j], "cidr"))
+ continue;
+ if (!nvlist_exists_binary(nvl_aips[j], "ipv4") && !nvlist_exists_binary(nvl_aips[j], "ipv6"))
+ continue;
+ aip = calloc(1, sizeof(*aip));
+ if (!aip)
+ goto err_allowed_ips;
+ number = nvlist_get_number(nvl_aips[j], "cidr");
+ if (nvlist_exists_binary(nvl_aips[j], "ipv4")) {
+ binary = nvlist_get_binary(nvl_aips[j], "ipv4", &size);
+ if (!binary || number > 32) {
+ ret = EINVAL;
+ goto err_allowed_ips;
+ }
+ aip->family = AF_INET;
+ aip->cidr = number;
+ memcpy(&aip->ip4, binary, sizeof(aip->ip4));
+ } else {
+ assert(nvlist_exists_binary(nvl_aips[j], "ipv6"));
+ binary = nvlist_get_binary(nvl_aips[j], "ipv6", &size);
+ if (!binary || number > 128) {
+ ret = EINVAL;
+ goto err_allowed_ips;
+ }
+ aip->family = AF_INET6;
+ aip->cidr = number;
+ memcpy(&aip->ip6, binary, sizeof(aip->ip6));
+ }
+
+ if (!peer->first_allowedip)
+ peer->first_allowedip = aip;
+ else
+ peer->last_allowedip->next_allowedip = aip;
+ peer->last_allowedip = aip;
+ aip = NULL;
+ continue;
+
+ err_allowed_ips:
+ if (!ret)
+ ret = -errno;
+ free(aip);
+ goto err_peer;
+ }
+
+ /* Nothing leaked, hopefully -- ownership transferred or aip freed. */
+ assert(aip == NULL);
+ skip_allowed_ips:
+ if (!dev->first_peer)
+ dev->first_peer = peer;
+ else
+ dev->last_peer->next_peer = peer;
+ dev->last_peer = peer;
+ continue;
+
+ err_peer:
+ if (!ret)
+ ret = -errno;
+ free(peer);
+ goto err;
+ }
+
+skip_peers:
+ free(wgd.wgd_data);
+ nvlist_destroy(nvl_device);
+ *device = dev;
+ return 0;
+
+err:
+ if (!ret)
+ ret = -errno;
+ free(wgd.wgd_data);
+ nvlist_destroy(nvl_device);
+ free(dev);
+ return ret;
+}
+
+
+static int kernel_set_device(struct wgdevice *dev)
+{
+ struct wg_data_io wgd = { 0 };
+ nvlist_t *nvl_device = NULL, **nvl_peers = NULL;
+ size_t peer_count = 0, i = 0;
+ struct wgpeer *peer;
+ int ret = 0, s;
+
+ strlcpy(wgd.wgd_name, dev->name, sizeof(wgd.wgd_name));
+
+ nvl_device = nvlist_create(0);
+ if (!nvl_device)
+ goto err;
+
+ for_each_wgpeer(dev, peer)
+ ++peer_count;
+ if (peer_count) {
+ nvl_peers = calloc(peer_count, sizeof(*nvl_peers));
+ if (!nvl_peers)
+ goto err;
+ }
+ if (dev->flags & WGDEVICE_HAS_PRIVATE_KEY)
+ nvlist_add_binary(nvl_device, "private-key", dev->private_key, sizeof(dev->private_key));
+ if (dev->flags & WGDEVICE_HAS_LISTEN_PORT)
+ nvlist_add_number(nvl_device, "listen-port", dev->listen_port);
+ if (dev->flags & WGDEVICE_HAS_FWMARK)
+ nvlist_add_number(nvl_device, "user-cookie", dev->fwmark);
+ if (dev->flags & WGDEVICE_REPLACE_PEERS)
+ nvlist_add_bool(nvl_device, "replace-peers", true);
+
+ for_each_wgpeer(dev, peer) {
+ size_t aip_count = 0, j = 0;
+ nvlist_t **nvl_aips = NULL;
+ struct wgallowedip *aip;
+
+ nvl_peers[i] = nvlist_create(0);
+ if (!nvl_peers[i])
+ goto err_peer;
+ for_each_wgallowedip(peer, aip)
+ ++aip_count;
+ if (aip_count) {
+ nvl_aips = calloc(aip_count, sizeof(*nvl_aips));
+ if (!nvl_aips)
+ goto err_peer;
+ }
+ nvlist_add_binary(nvl_peers[i], "public-key", peer->public_key, sizeof(peer->public_key));
+ if (peer->flags & WGPEER_HAS_PRESHARED_KEY)
+ nvlist_add_binary(nvl_peers[i], "preshared-key", peer->preshared_key, sizeof(peer->preshared_key));
+ if (peer->flags & WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL)
+ nvlist_add_number(nvl_peers[i], "persistent-keepalive-interval", peer->persistent_keepalive_interval);
+ if (peer->endpoint.addr.sa_family == AF_INET || peer->endpoint.addr.sa_family == AF_INET6)
+ nvlist_add_binary(nvl_peers[i], "endpoint", &peer->endpoint.addr, peer->endpoint.addr.sa_len);
+ if (peer->flags & WGPEER_REPLACE_ALLOWEDIPS)
+ nvlist_add_bool(nvl_peers[i], "replace-allowedips", true);
+ if (peer->flags & WGPEER_REMOVE_ME)
+ nvlist_add_bool(nvl_peers[i], "remove", true);
+ for_each_wgallowedip(peer, aip) {
+ nvl_aips[j] = nvlist_create(0);
+ if (!nvl_aips[j])
+ goto err_peer;
+ if (aip->flags) {
+ //TODO: implement me
+ ret = -EOPNOTSUPP;
+ goto err_peer;
+ }
+ nvlist_add_number(nvl_aips[j], "cidr", aip->cidr);
+ if (aip->family == AF_INET)
+ nvlist_add_binary(nvl_aips[j], "ipv4", &aip->ip4, sizeof(aip->ip4));
+ else if (aip->family == AF_INET6)
+ nvlist_add_binary(nvl_aips[j], "ipv6", &aip->ip6, sizeof(aip->ip6));
+ ++j;
+ }
+ if (j) {
+ nvlist_add_nvlist_array(nvl_peers[i], "allowed-ips", (const nvlist_t *const *)nvl_aips, j);
+ for (j = 0; j < aip_count; ++j)
+ nvlist_destroy(nvl_aips[j]);
+ free(nvl_aips);
+ }
+ ++i;
+ continue;
+
+ err_peer:
+ ret = -errno;
+ for (j = 0; j < aip_count && nvl_aips; ++j)
+ nvlist_destroy(nvl_aips[j]);
+ free(nvl_aips);
+ nvlist_destroy(nvl_peers[i]);
+ nvl_peers[i] = NULL;
+ goto err;
+ }
+ if (i) {
+ nvlist_add_nvlist_array(nvl_device, "peers", (const nvlist_t *const *)nvl_peers, i);
+ for (i = 0; i < peer_count; ++i)
+ nvlist_destroy(nvl_peers[i]);
+ free(nvl_peers);
+ nvl_peers = NULL;
+ }
+ wgd.wgd_data = nvlist_pack(nvl_device, &wgd.wgd_size);
+ nvlist_destroy(nvl_device);
+ nvl_device = NULL;
+ if (!wgd.wgd_data)
+ goto err;
+ s = get_dgram_socket();
+ if (s < 0)
+ return -errno;
+ return ioctl(s, SIOCSWG, &wgd);
+
+err:
+ if (!ret)
+ ret = -errno;
+ for (i = 0; i < peer_count && nvl_peers; ++i)
+ nvlist_destroy(nvl_peers[i]);
+ free(nvl_peers);
+ nvlist_destroy(nvl_device);
+ return ret;
+}
diff --git a/src/ipc-linux.h b/src/ipc-linux.h
new file mode 100644
index 0000000..01247f1
--- /dev/null
+++ b/src/ipc-linux.h
@@ -0,0 +1,527 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
+ */
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <time.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <linux/genetlink.h>
+#include <linux/if_link.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <linux/wireguard.h>
+#include <netinet/in.h>
+#include "containers.h"
+#include "encoding.h"
+#include "netlink.h"
+
+#define IPC_SUPPORTS_KERNEL_INTERFACE
+
+#define SOCKET_BUFFER_SIZE (mnl_ideal_socket_buffer_size())
+
+struct interface {
+ const char *name;
+ bool is_wireguard;
+};
+
+static int parse_linkinfo(const struct nlattr *attr, void *data)
+{
+ struct interface *interface = data;
+
+ if (mnl_attr_get_type(attr) == IFLA_INFO_KIND && !strcmp(WG_GENL_NAME, mnl_attr_get_str(attr)))
+ interface->is_wireguard = true;
+ return MNL_CB_OK;
+}
+
+static int parse_infomsg(const struct nlattr *attr, void *data)
+{
+ struct interface *interface = data;
+
+ if (mnl_attr_get_type(attr) == IFLA_LINKINFO)
+ return mnl_attr_parse_nested(attr, parse_linkinfo, data);
+ else if (mnl_attr_get_type(attr) == IFLA_IFNAME)
+ interface->name = mnl_attr_get_str(attr);
+ return MNL_CB_OK;
+}
+
+static int read_devices_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct string_list *list = data;
+ struct interface interface = { 0 };
+ int ret;
+
+ ret = mnl_attr_parse(nlh, sizeof(struct ifinfomsg), parse_infomsg, &interface);
+ if (ret != MNL_CB_OK)
+ return ret;
+ if (interface.name && interface.is_wireguard)
+ ret = string_list_add(list, interface.name);
+ if (ret < 0)
+ return ret;
+ if (nlh->nlmsg_type != NLMSG_DONE)
+ return MNL_CB_OK + 1;
+ return MNL_CB_OK;
+}
+
+static int kernel_get_wireguard_interfaces(struct string_list *list)
+{
+ struct mnl_socket *nl = NULL;
+ char *rtnl_buffer = NULL;
+ size_t message_len;
+ unsigned int portid, seq;
+ ssize_t len;
+ int ret = 0;
+ struct nlmsghdr *nlh;
+ struct ifinfomsg *ifm;
+
+ ret = -ENOMEM;
+ rtnl_buffer = calloc(SOCKET_BUFFER_SIZE, 1);
+ if (!rtnl_buffer)
+ goto cleanup;
+
+ nl = mnl_socket_open(NETLINK_ROUTE);
+ if (!nl) {
+ ret = -errno;
+ goto cleanup;
+ }
+
+ if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) {
+ ret = -errno;
+ goto cleanup;
+ }
+
+ seq = time(NULL);
+ portid = mnl_socket_get_portid(nl);
+ nlh = mnl_nlmsg_put_header(rtnl_buffer);
+ nlh->nlmsg_type = RTM_GETLINK;
+ nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP;
+ nlh->nlmsg_seq = seq;
+ ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm));
+ ifm->ifi_family = AF_UNSPEC;
+ message_len = nlh->nlmsg_len;
+
+ if (mnl_socket_sendto(nl, rtnl_buffer, message_len) < 0) {
+ ret = -errno;
+ goto cleanup;
+ }
+
+another:
+ if ((len = mnl_socket_recvfrom(nl, rtnl_buffer, SOCKET_BUFFER_SIZE)) < 0) {
+ ret = -errno;
+ goto cleanup;
+ }
+ if ((len = mnl_cb_run(rtnl_buffer, len, seq, portid, read_devices_cb, list)) < 0) {
+ /* Netlink returns NLM_F_DUMP_INTR if the set of all tunnels changed
+ * during the dump. That's unfortunate, but is pretty common on busy
+ * systems that are adding and removing tunnels all the time. Rather
+ * than retrying, potentially indefinitely, we just work with the
+ * partial results. */
+ if (errno != EINTR) {
+ ret = -errno;
+ goto cleanup;
+ }
+ }
+ if (len == MNL_CB_OK + 1)
+ goto another;
+ ret = 0;
+
+cleanup:
+ free(rtnl_buffer);
+ if (nl)
+ mnl_socket_close(nl);
+ return ret;
+}
+
+static int kernel_set_device(struct wgdevice *dev)
+{
+ int ret = 0;
+ 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;
+
+ 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);
+ }
+ 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 (peer = peer ? peer : dev->first_peer; peer; peer = peer->next_peer) {
+ uint32_t flags = 0;
+
+ peer_nest = mnl_attr_nest_start_check(nlh, SOCKET_BUFFER_SIZE, 0);
+ if (!peer_nest)
+ goto toobig_peers;
+ if (!mnl_attr_put_check(nlh, 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, 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, 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, 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, SOCKET_BUFFER_SIZE, WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL, peer->persistent_keepalive_interval))
+ goto toobig_peers;
+ }
+ }
+ if (flags) {
+ if (!mnl_attr_put_u32_check(nlh, 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, SOCKET_BUFFER_SIZE, WGPEER_A_ALLOWEDIPS);
+ if (!allowedips_nest)
+ goto toobig_allowedips;
+ for (; allowedip; allowedip = allowedip->next_allowedip) {
+ allowedip_nest = mnl_attr_nest_start_check(nlh, SOCKET_BUFFER_SIZE, 0);
+ if (!allowedip_nest)
+ goto toobig_allowedips;
+ if (!mnl_attr_put_u16_check(nlh, SOCKET_BUFFER_SIZE, WGALLOWEDIP_A_FAMILY, allowedip->family))
+ goto toobig_allowedips;
+ if (allowedip->family == AF_INET) {
+ if (!mnl_attr_put_check(nlh, 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, SOCKET_BUFFER_SIZE, WGALLOWEDIP_A_IPADDR, sizeof(allowedip->ip6), &allowedip->ip6))
+ goto toobig_allowedips;
+ }
+ if (!mnl_attr_put_u8_check(nlh, SOCKET_BUFFER_SIZE, WGALLOWEDIP_A_CIDR_MASK, allowedip->cidr))
+ goto toobig_allowedips;
+ if (allowedip->flags && !mnl_attr_put_u32_check(nlh, SOCKET_BUFFER_SIZE, WGALLOWEDIP_A_FLAGS, allowedip->flags))
+ goto toobig_allowedips;
+ mnl_attr_nest_end(nlh, allowedip_nest);
+ allowedip_nest = NULL;
+ }
+ mnl_attr_nest_end(nlh, allowedips_nest);
+ allowedips_nest = NULL;
+ }
+
+ mnl_attr_nest_end(nlh, peer_nest);
+ peer_nest = NULL;
+ }
+ mnl_attr_nest_end(nlh, peers_nest);
+ peers_nest = NULL;
+ goto send;
+toobig_allowedips:
+ if (allowedip_nest)
+ mnl_attr_nest_cancel(nlh, allowedip_nest);
+ if (allowedips_nest)
+ mnl_attr_nest_end(nlh, allowedips_nest);
+ mnl_attr_nest_end(nlh, peer_nest);
+ mnl_attr_nest_end(nlh, peers_nest);
+ goto send;
+toobig_peers:
+ if (peer_nest)
+ mnl_attr_nest_cancel(nlh, peer_nest);
+ mnl_attr_nest_end(nlh, peers_nest);
+ goto send;
+send:
+ if (mnlg_socket_send(nlg, nlh) < 0) {
+ ret = -errno;
+ goto out;
+ }
+ errno = 0;
+ if (mnlg_socket_recv_run(nlg, NULL, NULL) < 0) {
+ ret = errno ? -errno : -EINVAL;
+ goto out;
+ }
+ if (peer)
+ goto again;
+
+out:
+ mnlg_socket_close(nlg);
+ errno = -ret;
+ return ret;
+}
+
+static int parse_allowedip(const struct nlattr *attr, void *data)
+{
+ struct wgallowedip *allowedip = data;
+
+ switch (mnl_attr_get_type(attr)) {
+ case WGALLOWEDIP_A_UNSPEC:
+ break;
+ case WGALLOWEDIP_A_FAMILY:
+ if (!mnl_attr_validate(attr, MNL_TYPE_U16))
+ allowedip->family = mnl_attr_get_u16(attr);
+ break;
+ case WGALLOWEDIP_A_IPADDR:
+ if (mnl_attr_get_payload_len(attr) == sizeof(allowedip->ip4))
+ memcpy(&allowedip->ip4, mnl_attr_get_payload(attr), sizeof(allowedip->ip4));
+ else if (mnl_attr_get_payload_len(attr) == sizeof(allowedip->ip6))
+ memcpy(&allowedip->ip6, mnl_attr_get_payload(attr), sizeof(allowedip->ip6));
+ break;
+ case WGALLOWEDIP_A_CIDR_MASK:
+ if (!mnl_attr_validate(attr, MNL_TYPE_U8))
+ allowedip->cidr = mnl_attr_get_u8(attr);
+ break;
+ }
+
+ return MNL_CB_OK;
+}
+
+static int parse_allowedips(const struct nlattr *attr, void *data)
+{
+ struct wgpeer *peer = data;
+ struct wgallowedip *new_allowedip = calloc(1, sizeof(*new_allowedip));
+ int ret;
+
+ if (!new_allowedip) {
+ perror("calloc");
+ return MNL_CB_ERROR;
+ }
+ if (!peer->first_allowedip)
+ peer->first_allowedip = peer->last_allowedip = new_allowedip;
+ else {
+ peer->last_allowedip->next_allowedip = new_allowedip;
+ peer->last_allowedip = new_allowedip;
+ }
+ ret = mnl_attr_parse_nested(attr, parse_allowedip, new_allowedip);
+ if (!ret)
+ return ret;
+ if (!((new_allowedip->family == AF_INET && new_allowedip->cidr <= 32) || (new_allowedip->family == AF_INET6 && new_allowedip->cidr <= 128)))
+ return MNL_CB_ERROR;
+ return MNL_CB_OK;
+}
+
+static int parse_peer(const struct nlattr *attr, void *data)
+{
+ struct wgpeer *peer = data;
+
+ switch (mnl_attr_get_type(attr)) {
+ case WGPEER_A_UNSPEC:
+ break;
+ case WGPEER_A_PUBLIC_KEY:
+ if (mnl_attr_get_payload_len(attr) == sizeof(peer->public_key)) {
+ memcpy(peer->public_key, mnl_attr_get_payload(attr), sizeof(peer->public_key));
+ peer->flags |= WGPEER_HAS_PUBLIC_KEY;
+ }
+ break;
+ case WGPEER_A_PRESHARED_KEY:
+ if (mnl_attr_get_payload_len(attr) == sizeof(peer->preshared_key)) {
+ memcpy(peer->preshared_key, mnl_attr_get_payload(attr), sizeof(peer->preshared_key));
+ if (!key_is_zero(peer->preshared_key))
+ peer->flags |= WGPEER_HAS_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(peer->endpoint.addr4))
+ memcpy(&peer->endpoint.addr4, addr, sizeof(peer->endpoint.addr4));
+ else if (addr->sa_family == AF_INET6 && mnl_attr_get_payload_len(attr) == sizeof(peer->endpoint.addr6))
+ memcpy(&peer->endpoint.addr6, addr, sizeof(peer->endpoint.addr6));
+ break;
+ }
+ case WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL:
+ if (!mnl_attr_validate(attr, MNL_TYPE_U16))
+ peer->persistent_keepalive_interval = mnl_attr_get_u16(attr);
+ break;
+ case WGPEER_A_LAST_HANDSHAKE_TIME:
+ if (mnl_attr_get_payload_len(attr) == sizeof(peer->last_handshake_time))
+ memcpy(&peer->last_handshake_time, mnl_attr_get_payload(attr), sizeof(peer->last_handshake_time));
+ break;
+ case WGPEER_A_RX_BYTES:
+ if (!mnl_attr_validate(attr, MNL_TYPE_U64))
+ peer->rx_bytes = mnl_attr_get_u64(attr);
+ break;
+ case WGPEER_A_TX_BYTES:
+ if (!mnl_attr_validate(attr, MNL_TYPE_U64))
+ peer->tx_bytes = mnl_attr_get_u64(attr);
+ break;
+ case WGPEER_A_ALLOWEDIPS:
+ return mnl_attr_parse_nested(attr, parse_allowedips, peer);
+ }
+
+ return MNL_CB_OK;
+}
+
+static int parse_peers(const struct nlattr *attr, void *data)
+{
+ struct wgdevice *device = data;
+ struct wgpeer *new_peer = calloc(1, sizeof(*new_peer));
+ int ret;
+
+ if (!new_peer) {
+ perror("calloc");
+ return MNL_CB_ERROR;
+ }
+ if (!device->first_peer)
+ device->first_peer = device->last_peer = new_peer;
+ else {
+ device->last_peer->next_peer = new_peer;
+ device->last_peer = new_peer;
+ }
+ ret = mnl_attr_parse_nested(attr, parse_peer, new_peer);
+ if (!ret)
+ return ret;
+ if (!(new_peer->flags & WGPEER_HAS_PUBLIC_KEY))
+ return MNL_CB_ERROR;
+ return MNL_CB_OK;
+}
+
+static int parse_device(const struct nlattr *attr, void *data)
+{
+ struct wgdevice *device = data;
+
+ switch (mnl_attr_get_type(attr)) {
+ case WGDEVICE_A_UNSPEC:
+ break;
+ case WGDEVICE_A_IFINDEX:
+ if (!mnl_attr_validate(attr, MNL_TYPE_U32))
+ device->ifindex = mnl_attr_get_u32(attr);
+ break;
+ case WGDEVICE_A_IFNAME:
+ if (!mnl_attr_validate(attr, MNL_TYPE_STRING)) {
+ strncpy(device->name, mnl_attr_get_str(attr), sizeof(device->name) - 1);
+ device->name[sizeof(device->name) - 1] = '\0';
+ }
+ break;
+ case WGDEVICE_A_PRIVATE_KEY:
+ if (mnl_attr_get_payload_len(attr) == sizeof(device->private_key)) {
+ memcpy(device->private_key, mnl_attr_get_payload(attr), sizeof(device->private_key));
+ device->flags |= WGDEVICE_HAS_PRIVATE_KEY;
+ }
+ break;
+ case WGDEVICE_A_PUBLIC_KEY:
+ if (mnl_attr_get_payload_len(attr) == sizeof(device->public_key)) {
+ memcpy(device->public_key, mnl_attr_get_payload(attr), sizeof(device->public_key));
+ device->flags |= WGDEVICE_HAS_PUBLIC_KEY;
+ }
+ break;
+ case WGDEVICE_A_LISTEN_PORT:
+ if (!mnl_attr_validate(attr, MNL_TYPE_U16))
+ device->listen_port = mnl_attr_get_u16(attr);
+ break;
+ case WGDEVICE_A_FWMARK:
+ if (!mnl_attr_validate(attr, MNL_TYPE_U32))
+ device->fwmark = mnl_attr_get_u32(attr);
+ break;
+ case WGDEVICE_A_PEERS:
+ return mnl_attr_parse_nested(attr, parse_peers, device);
+ }
+
+ 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 wgpeer *old_next_peer, *peer = device->first_peer;
+
+ while (peer && peer->next_peer) {
+ if (memcmp(peer->public_key, peer->next_peer->public_key, sizeof(peer->public_key))) {
+ peer = peer->next_peer;
+ continue;
+ }
+ if (!peer->first_allowedip) {
+ peer->first_allowedip = peer->next_peer->first_allowedip;
+ peer->last_allowedip = peer->next_peer->last_allowedip;
+ } else {
+ peer->last_allowedip->next_allowedip = peer->next_peer->first_allowedip;
+ peer->last_allowedip = peer->next_peer->last_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 **device, const char *iface)
+{
+ int ret;
+ struct nlmsghdr *nlh;
+ struct mnlg_socket *nlg;
+
+ /* libmnl doesn't check the buffer size, so enforce that before using. */
+ if (strlen(iface) >= IFNAMSIZ) {
+ errno = ENAMETOOLONG;
+ return -ENAMETOOLONG;
+ }
+
+try_again:
+ ret = 0;
+ *device = calloc(1, sizeof(**device));
+ if (!*device)
+ return -errno;
+
+ nlg = mnlg_socket_open(WG_GENL_NAME, WG_GENL_VERSION);
+ if (!nlg) {
+ free_wgdevice(*device);
+ *device = 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, iface);
+ if (mnlg_socket_send(nlg, nlh) < 0) {
+ ret = -errno;
+ goto out;
+ }
+ errno = 0;
+ if (mnlg_socket_recv_run(nlg, read_device_cb, *device) < 0) {
+ ret = errno ? -errno : -EINVAL;
+ goto out;
+ }
+ coalesce_peers(*device);
+
+out:
+ if (nlg)
+ mnlg_socket_close(nlg);
+ if (ret) {
+ free_wgdevice(*device);
+ if (ret == -EINTR)
+ goto try_again;
+ *device = NULL;
+ }
+ errno = -ret;
+ return ret;
+}
diff --git a/src/ipc-openbsd.h b/src/ipc-openbsd.h
new file mode 100644
index 0000000..6bb231e
--- /dev/null
+++ b/src/ipc-openbsd.h
@@ -0,0 +1,286 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
+ */
+
+#include <errno.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <sys/types.h>
+#include <net/if.h>
+#include <net/if_wg.h>
+#include <netinet/in.h>
+#include "containers.h"
+
+#define IPC_SUPPORTS_KERNEL_INTERFACE
+
+static int get_dgram_socket(void)
+{
+ static int sock = -1;
+ if (sock < 0)
+ sock = socket(AF_INET, SOCK_DGRAM, 0);
+ return sock;
+}
+
+static int kernel_get_wireguard_interfaces(struct string_list *list)
+{
+ struct ifgroupreq ifgr = { .ifgr_name = "wg" };
+ struct ifg_req *ifg;
+ int s = get_dgram_socket(), ret = 0;
+
+ if (s < 0)
+ return -errno;
+
+ if (ioctl(s, SIOCGIFGMEMB, (caddr_t)&ifgr) < 0)
+ return errno == ENOENT ? 0 : -errno;
+
+ ifgr.ifgr_groups = calloc(1, ifgr.ifgr_len);
+ if (!ifgr.ifgr_groups)
+ return -errno;
+ if (ioctl(s, SIOCGIFGMEMB, (caddr_t)&ifgr) < 0) {
+ ret = -errno;
+ goto out;
+ }
+
+ for (ifg = ifgr.ifgr_groups; ifg && ifgr.ifgr_len > 0; ++ifg) {
+ if ((ret = string_list_add(list, ifg->ifgrq_member)) < 0)
+ goto out;
+ ifgr.ifgr_len -= sizeof(struct ifg_req);
+ }
+
+out:
+ free(ifgr.ifgr_groups);
+ return ret;
+}
+
+static int kernel_get_device(struct wgdevice **device, const char *iface)
+{
+ struct wg_data_io wgdata = { .wgd_size = 0 };
+ struct wg_interface_io *wg_iface;
+ struct wg_peer_io *wg_peer;
+ struct wg_aip_io *wg_aip;
+ struct wgdevice *dev;
+ struct wgpeer *peer;
+ struct wgallowedip *aip;
+ int s = get_dgram_socket(), ret;
+
+ if (s < 0)
+ return -errno;
+
+ *device = NULL;
+ strlcpy(wgdata.wgd_name, iface, sizeof(wgdata.wgd_name));
+ for (size_t last_size = wgdata.wgd_size;; last_size = wgdata.wgd_size) {
+ if (ioctl(s, SIOCGWG, (caddr_t)&wgdata) < 0)
+ goto out;
+ if (last_size >= wgdata.wgd_size)
+ break;
+ wgdata.wgd_interface = realloc(wgdata.wgd_interface, wgdata.wgd_size);
+ if (!wgdata.wgd_interface)
+ goto out;
+ }
+
+ wg_iface = wgdata.wgd_interface;
+ dev = calloc(1, sizeof(*dev));
+ if (!dev)
+ goto out;
+ strlcpy(dev->name, iface, sizeof(dev->name));
+
+ if (wg_iface->i_flags & WG_INTERFACE_HAS_RTABLE) {
+ dev->fwmark = wg_iface->i_rtable;
+ dev->flags |= WGDEVICE_HAS_FWMARK;
+ }
+
+ if (wg_iface->i_flags & WG_INTERFACE_HAS_PORT) {
+ dev->listen_port = wg_iface->i_port;
+ dev->flags |= WGDEVICE_HAS_LISTEN_PORT;
+ }
+
+ if (wg_iface->i_flags & WG_INTERFACE_HAS_PUBLIC) {
+ memcpy(dev->public_key, wg_iface->i_public, sizeof(dev->public_key));
+ dev->flags |= WGDEVICE_HAS_PUBLIC_KEY;
+ }
+
+ if (wg_iface->i_flags & WG_INTERFACE_HAS_PRIVATE) {
+ memcpy(dev->private_key, wg_iface->i_private, sizeof(dev->private_key));
+ dev->flags |= WGDEVICE_HAS_PRIVATE_KEY;
+ }
+
+ wg_peer = &wg_iface->i_peers[0];
+ for (size_t i = 0; i < wg_iface->i_peers_count; ++i) {
+ peer = calloc(1, sizeof(*peer));
+ if (!peer)
+ goto out;
+
+ if (dev->first_peer == NULL)
+ dev->first_peer = peer;
+ else
+ dev->last_peer->next_peer = peer;
+ dev->last_peer = peer;
+
+ if (wg_peer->p_flags & WG_PEER_HAS_PUBLIC) {
+ memcpy(peer->public_key, wg_peer->p_public, sizeof(peer->public_key));
+ peer->flags |= WGPEER_HAS_PUBLIC_KEY;
+ }
+
+ if (wg_peer->p_flags & WG_PEER_HAS_PSK) {
+ memcpy(peer->preshared_key, wg_peer->p_psk, sizeof(peer->preshared_key));
+ if (!key_is_zero(peer->preshared_key))
+ peer->flags |= WGPEER_HAS_PRESHARED_KEY;
+ }
+
+ if (wg_peer->p_flags & WG_PEER_HAS_PKA) {
+ peer->persistent_keepalive_interval = wg_peer->p_pka;
+ peer->flags |= WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL;
+ }
+
+ if (wg_peer->p_flags & WG_PEER_HAS_ENDPOINT && wg_peer->p_sa.sa_len <= sizeof(peer->endpoint.addr))
+ memcpy(&peer->endpoint.addr, &wg_peer->p_sa, wg_peer->p_sa.sa_len);
+
+ peer->rx_bytes = wg_peer->p_rxbytes;
+ peer->tx_bytes = wg_peer->p_txbytes;
+
+ peer->last_handshake_time.tv_sec = wg_peer->p_last_handshake.tv_sec;
+ peer->last_handshake_time.tv_nsec = wg_peer->p_last_handshake.tv_nsec;
+
+ wg_aip = &wg_peer->p_aips[0];
+ for (size_t j = 0; j < wg_peer->p_aips_count; ++j) {
+ aip = calloc(1, sizeof(*aip));
+ if (!aip)
+ goto out;
+
+ if (peer->first_allowedip == NULL)
+ peer->first_allowedip = aip;
+ else
+ peer->last_allowedip->next_allowedip = aip;
+ peer->last_allowedip = aip;
+
+ aip->family = wg_aip->a_af;
+ if (wg_aip->a_af == AF_INET) {
+ memcpy(&aip->ip4, &wg_aip->a_ipv4, sizeof(aip->ip4));
+ aip->cidr = wg_aip->a_cidr;
+ } else if (wg_aip->a_af == AF_INET6) {
+ memcpy(&aip->ip6, &wg_aip->a_ipv6, sizeof(aip->ip6));
+ aip->cidr = wg_aip->a_cidr;
+ }
+ ++wg_aip;
+ }
+ wg_peer = (struct wg_peer_io *)wg_aip;
+ }
+ *device = dev;
+ errno = 0;
+out:
+ ret = -errno;
+ free(wgdata.wgd_interface);
+ return ret;
+}
+
+static int kernel_set_device(struct wgdevice *dev)
+{
+ struct wg_data_io wgdata = { .wgd_size = sizeof(struct wg_interface_io) };
+ struct wg_interface_io *wg_iface;
+ struct wg_peer_io *wg_peer;
+ struct wg_aip_io *wg_aip;
+ struct wgpeer *peer;
+ struct wgallowedip *aip;
+ int s = get_dgram_socket(), ret;
+ size_t peer_count, aip_count;
+
+ if (s < 0)
+ return -errno;
+
+ for_each_wgpeer(dev, peer) {
+ wgdata.wgd_size += sizeof(struct wg_peer_io);
+ for_each_wgallowedip(peer, aip)
+ wgdata.wgd_size += sizeof(struct wg_aip_io);
+ }
+ wg_iface = wgdata.wgd_interface = calloc(1, wgdata.wgd_size);
+ if (!wgdata.wgd_interface)
+ return -errno;
+ strlcpy(wgdata.wgd_name, dev->name, sizeof(wgdata.wgd_name));
+
+ if (dev->flags & WGDEVICE_HAS_PRIVATE_KEY) {
+ memcpy(wg_iface->i_private, dev->private_key, sizeof(wg_iface->i_private));
+ wg_iface->i_flags |= WG_INTERFACE_HAS_PRIVATE;
+ }
+
+ if (dev->flags & WGDEVICE_HAS_LISTEN_PORT) {
+ wg_iface->i_port = dev->listen_port;
+ wg_iface->i_flags |= WG_INTERFACE_HAS_PORT;
+ }
+
+ if (dev->flags & WGDEVICE_HAS_FWMARK) {
+ wg_iface->i_rtable = dev->fwmark;
+ wg_iface->i_flags |= WG_INTERFACE_HAS_RTABLE;
+ }
+
+ if (dev->flags & WGDEVICE_REPLACE_PEERS)
+ wg_iface->i_flags |= WG_INTERFACE_REPLACE_PEERS;
+
+ peer_count = 0;
+ wg_peer = &wg_iface->i_peers[0];
+ for_each_wgpeer(dev, peer) {
+ wg_peer->p_flags = WG_PEER_HAS_PUBLIC;
+ memcpy(wg_peer->p_public, peer->public_key, sizeof(wg_peer->p_public));
+
+ if (peer->flags & WGPEER_HAS_PRESHARED_KEY) {
+ memcpy(wg_peer->p_psk, peer->preshared_key, sizeof(wg_peer->p_psk));
+ wg_peer->p_flags |= WG_PEER_HAS_PSK;
+ }
+
+ if (peer->flags & WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL) {
+ wg_peer->p_pka = peer->persistent_keepalive_interval;
+ wg_peer->p_flags |= WG_PEER_HAS_PKA;
+ }
+
+ if ((peer->endpoint.addr.sa_family == AF_INET || peer->endpoint.addr.sa_family == AF_INET6) &&
+ peer->endpoint.addr.sa_len <= sizeof(wg_peer->p_endpoint)) {
+ memcpy(&wg_peer->p_endpoint, &peer->endpoint.addr, peer->endpoint.addr.sa_len);
+ wg_peer->p_flags |= WG_PEER_HAS_ENDPOINT;
+ }
+
+ if (peer->flags & WGPEER_REPLACE_ALLOWEDIPS)
+ wg_peer->p_flags |= WG_PEER_REPLACE_AIPS;
+
+ if (peer->flags & WGPEER_REMOVE_ME)
+ wg_peer->p_flags |= WG_PEER_REMOVE;
+
+ aip_count = 0;
+ wg_aip = &wg_peer->p_aips[0];
+ for_each_wgallowedip(peer, aip) {
+ if (aip->flags) {
+ //TODO: implement me
+ errno = EOPNOTSUPP;
+ goto out;
+ }
+ wg_aip->a_af = aip->family;
+ wg_aip->a_cidr = aip->cidr;
+
+ if (aip->family == AF_INET)
+ memcpy(&wg_aip->a_ipv4, &aip->ip4, sizeof(wg_aip->a_ipv4));
+ else if (aip->family == AF_INET6)
+ memcpy(&wg_aip->a_ipv6, &aip->ip6, sizeof(wg_aip->a_ipv6));
+ else
+ continue;
+ ++aip_count;
+ ++wg_aip;
+ }
+ wg_peer->p_aips_count = aip_count;
+ ++peer_count;
+ wg_peer = (struct wg_peer_io *)wg_aip;
+ }
+ wg_iface->i_peers_count = peer_count;
+
+ if (ioctl(s, SIOCSWG, (caddr_t)&wgdata) < 0)
+ goto out;
+ errno = 0;
+
+out:
+ ret = -errno;
+ free(wgdata.wgd_interface);
+ return ret;
+}
diff --git a/src/ipc-uapi-unix.h b/src/ipc-uapi-unix.h
new file mode 100644
index 0000000..aaf60ca
--- /dev/null
+++ b/src/ipc-uapi-unix.h
@@ -0,0 +1,119 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
+ */
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+
+#define SOCK_PATH RUNSTATEDIR "/wireguard/"
+#define SOCK_SUFFIX ".sock"
+
+static FILE *userspace_interface_file(const char *iface)
+{
+ struct stat sbuf;
+ struct sockaddr_un addr = { .sun_family = AF_UNIX };
+ int fd = -1, ret;
+ FILE *f = NULL;
+
+ errno = EINVAL;
+ if (strchr(iface, '/'))
+ goto out;
+ ret = snprintf(addr.sun_path, sizeof(addr.sun_path), SOCK_PATH "%s" SOCK_SUFFIX, iface);
+ if (ret < 0)
+ goto out;
+ ret = stat(addr.sun_path, &sbuf);
+ if (ret < 0)
+ goto out;
+ errno = EBADF;
+ if (!S_ISSOCK(sbuf.st_mode))
+ goto out;
+
+ ret = fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (ret < 0)
+ goto out;
+
+ ret = connect(fd, (struct sockaddr *)&addr, sizeof(addr));
+ if (ret < 0) {
+ if (errno == ECONNREFUSED) /* If the process is gone, we try to clean up the socket. */
+ unlink(addr.sun_path);
+ goto out;
+ }
+ f = fdopen(fd, "r+");
+ if (f)
+ errno = 0;
+out:
+ ret = -errno;
+ if (ret) {
+ if (fd >= 0)
+ close(fd);
+ errno = -ret;
+ return NULL;
+ }
+ return f;
+}
+
+static bool userspace_has_wireguard_interface(const char *iface)
+{
+ struct stat sbuf;
+ struct sockaddr_un addr = { .sun_family = AF_UNIX };
+ int fd, ret;
+
+ if (strchr(iface, '/'))
+ return false;
+ if (snprintf(addr.sun_path, sizeof(addr.sun_path), SOCK_PATH "%s" SOCK_SUFFIX, iface) < 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;
+ ret = connect(fd, (struct sockaddr *)&addr, sizeof(addr));
+ if (ret < 0 && errno == ECONNREFUSED) { /* If the process is gone, we try to clean up the socket. */
+ close(fd);
+ unlink(addr.sun_path);
+ return false;
+ }
+ close(fd);
+ return true;
+}
+
+static int userspace_get_wireguard_interfaces(struct string_list *list)
+{
+ DIR *dir;
+ struct dirent *ent;
+ size_t len;
+ char *end;
+ int ret = 0;
+
+ dir = opendir(SOCK_PATH);
+ if (!dir)
+ return errno == ENOENT ? 0 : -errno;
+ while ((ent = readdir(dir))) {
+ len = strlen(ent->d_name);
+ if (len <= strlen(SOCK_SUFFIX))
+ continue;
+ end = &ent->d_name[len - strlen(SOCK_SUFFIX)];
+ if (strncmp(end, SOCK_SUFFIX, strlen(SOCK_SUFFIX)))
+ continue;
+ *end = '\0';
+ if (!userspace_has_wireguard_interface(ent->d_name))
+ continue;
+ ret = string_list_add(list, ent->d_name);
+ if (ret < 0)
+ goto out;
+ }
+out:
+ closedir(dir);
+ return ret;
+}
diff --git a/src/ipc-uapi-windows.h b/src/ipc-uapi-windows.h
new file mode 100644
index 0000000..8ea4f75
--- /dev/null
+++ b/src/ipc-uapi-windows.h
@@ -0,0 +1,107 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
+ */
+
+#include <windows.h>
+#include <tlhelp32.h>
+#include <accctrl.h>
+#include <aclapi.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <fcntl.h>
+#include <hashtable.h>
+
+static FILE *userspace_interface_file(const char *iface)
+{
+ char fname[MAX_PATH];
+ HANDLE pipe_handle;
+ SID expected_sid;
+ DWORD bytes = sizeof(expected_sid);
+ PSID pipe_sid;
+ PSECURITY_DESCRIPTOR pipe_sd;
+ bool equal;
+ int fd;
+
+ if (!CreateWellKnownSid(WinLocalSystemSid, NULL, &expected_sid, &bytes))
+ goto err;
+
+ snprintf(fname, sizeof(fname), "\\\\.\\pipe\\ProtectedPrefix\\Administrators\\WireGuard\\%s", iface);
+ pipe_handle = CreateFileA(fname, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
+ if (pipe_handle == INVALID_HANDLE_VALUE)
+ goto err;
+ if (GetSecurityInfo(pipe_handle, SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION, &pipe_sid, NULL, NULL, NULL, &pipe_sd) != ERROR_SUCCESS)
+ goto err_close;
+ equal = EqualSid(&expected_sid, pipe_sid);
+ LocalFree(pipe_sd);
+ if (!equal)
+ goto err_close;
+ fd = _open_osfhandle((intptr_t)pipe_handle, _O_RDWR);
+ if (fd == -1) {
+ CloseHandle(pipe_handle);
+ return NULL;
+ }
+ return _fdopen(fd, "r+");
+err_close:
+ CloseHandle(pipe_handle);
+err:
+ errno = EACCES;
+ return NULL;
+}
+
+static bool have_cached_interfaces;
+static struct hashtable cached_interfaces;
+
+static bool userspace_has_wireguard_interface(const char *iface)
+{
+ char fname[MAX_PATH];
+ WIN32_FIND_DATA find_data;
+ HANDLE find_handle;
+ bool ret = false;
+
+ if (have_cached_interfaces)
+ return hashtable_find_entry(&cached_interfaces, iface) != NULL;
+
+ snprintf(fname, sizeof(fname), "ProtectedPrefix\\Administrators\\WireGuard\\%s", iface);
+ find_handle = FindFirstFile("\\\\.\\pipe\\*", &find_data);
+ if (find_handle == INVALID_HANDLE_VALUE)
+ return -EIO;
+ do {
+ if (!strcmp(fname, find_data.cFileName)) {
+ ret = true;
+ break;
+ }
+ } while (FindNextFile(find_handle, &find_data));
+ FindClose(find_handle);
+ return ret;
+}
+
+static int userspace_get_wireguard_interfaces(struct string_list *list)
+{
+ static const char prefix[] = "ProtectedPrefix\\Administrators\\WireGuard\\";
+ WIN32_FIND_DATA find_data;
+ HANDLE find_handle;
+ char *iface;
+ int ret = 0;
+
+ find_handle = FindFirstFile("\\\\.\\pipe\\*", &find_data);
+ if (find_handle == INVALID_HANDLE_VALUE)
+ return -EIO;
+ do {
+ if (strncmp(prefix, find_data.cFileName, strlen(prefix)))
+ continue;
+ iface = find_data.cFileName + strlen(prefix);
+ ret = string_list_add(list, iface);
+ if (ret < 0)
+ goto out;
+ if (!hashtable_find_or_insert_entry(&cached_interfaces, iface)) {
+ ret = -errno;
+ goto out;
+ }
+ } while (FindNextFile(find_handle, &find_data));
+ have_cached_interfaces = true;
+
+out:
+ FindClose(find_handle);
+ return ret;
+}
diff --git a/src/ipc-uapi.h b/src/ipc-uapi.h
new file mode 100644
index 0000000..1d8a271
--- /dev/null
+++ b/src/ipc-uapi.h
@@ -0,0 +1,297 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
+ */
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <limits.h>
+#include <net/if.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include "containers.h"
+#include "curve25519.h"
+#include "encoding.h"
+#include "ctype.h"
+
+#ifdef _WIN32
+#include "ipc-uapi-windows.h"
+#else
+#include "ipc-uapi-unix.h"
+#endif
+
+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 wgallowedip *allowedip;
+ FILE *f;
+ int ret, set_errno = -EPROTO;
+ socklen_t addr_len;
+ size_t line_buffer_len = 0, line_len;
+ char *key = NULL, *value;
+
+ f = userspace_interface_file(dev->name);
+ if (!f)
+ return -errno;
+ fprintf(f, "set=1\n");
+
+ if (dev->flags & WGDEVICE_HAS_PRIVATE_KEY) {
+ key_to_hex(hex, dev->private_key);
+ fprintf(f, "private_key=%s\n", hex);
+ }
+ 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) {
+ 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_HAS_PRESHARED_KEY) {
+ key_to_hex(hex, peer->preshared_key);
+ fprintf(f, "preshared_key=%s\n", hex);
+ }
+ if (peer->endpoint.addr.sa_family == AF_INET || peer->endpoint.addr.sa_family == AF_INET6) {
+ addr_len = 0;
+ if (peer->endpoint.addr.sa_family == AF_INET)
+ addr_len = sizeof(struct sockaddr_in);
+ else if (peer->endpoint.addr.sa_family == AF_INET6)
+ addr_len = sizeof(struct sockaddr_in6);
+ if (!getnameinfo(&peer->endpoint.addr, addr_len, host, sizeof(host), service, sizeof(service), NI_DGRAM | NI_NUMERICSERV | NI_NUMERICHOST)) {
+ if (peer->endpoint.addr.sa_family == AF_INET6 && strchr(host, ':'))
+ fprintf(f, "endpoint=[%s]:%s\n", host, service);
+ else
+ fprintf(f, "endpoint=%s:%s\n", host, service);
+ }
+ }
+ if (peer->flags & WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL)
+ fprintf(f, "persistent_keepalive_interval=%u\n", peer->persistent_keepalive_interval);
+ if (peer->flags & WGPEER_REPLACE_ALLOWEDIPS)
+ fprintf(f, "replace_allowed_ips=true\n");
+ for_each_wgallowedip(peer, allowedip) {
+ if (allowedip->family == AF_INET) {
+ if (!inet_ntop(AF_INET, &allowedip->ip4, ip, INET6_ADDRSTRLEN))
+ continue;
+ } else if (allowedip->family == AF_INET6) {
+ if (!inet_ntop(AF_INET6, &allowedip->ip6, ip, INET6_ADDRSTRLEN))
+ continue;
+ } else
+ continue;
+ fprintf(f, "allowed_ip=%s%s/%d\n", (allowedip->flags & WGALLOWEDIP_REMOVE_ME) ? "-" : "", ip, allowedip->cidr);
+ }
+ }
+ fprintf(f, "\n");
+ fflush(f);
+
+ while (getline(&key, &line_buffer_len, f) > 0) {
+ line_len = strlen(key);
+ ret = set_errno;
+ if (line_len == 1 && key[0] == '\n')
+ goto out;
+ value = strchr(key, '=');
+ if (!value || line_len == 0 || key[line_len - 1] != '\n')
+ break;
+ *value++ = key[--line_len] = '\0';
+
+ if (!strcmp(key, "errno")) {
+ long long num;
+ char *end;
+ if (value[0] != '-' && !char_is_digit(value[0]))
+ break;
+ num = strtoll(value, &end, 10);
+ if (*end || num > INT_MAX || num < INT_MIN)
+ break;
+ set_errno = num;
+ }
+ }
+ ret = errno ? -errno : -EPROTO;
+out:
+ free(key);
+ fclose(f);
+ errno = -ret;
+ return ret;
+}
+
+#define NUM(max) ({ \
+ unsigned long long num; \
+ char *end; \
+ if (!char_is_digit(value[0])) \
+ break; \
+ num = strtoull(value, &end, 10); \
+ if (*end || num > max) \
+ break; \
+ num; \
+})
+
+static int userspace_get_device(struct wgdevice **out, const char *iface)
+{
+ struct wgdevice *dev;
+ struct wgpeer *peer = NULL;
+ 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(*dev));
+ if (!dev)
+ return -errno;
+
+ f = userspace_interface_file(iface);
+ if (!f) {
+ ret = -errno;
+ free(dev);
+ *out = NULL;
+ return ret;
+ }
+
+ fprintf(f, "get=1\n\n");
+ fflush(f);
+
+ strncpy(dev->name, iface, IFNAMSIZ - 1);
+ dev->name[IFNAMSIZ - 1] = '\0';
+
+ while (getline(&key, &line_buffer_len, f) > 0) {
+ line_len = strlen(key);
+ if (line_len == 1 && key[0] == '\n')
+ goto err;
+ value = strchr(key, '=');
+ if (!value || line_len == 0 || key[line_len - 1] != '\n')
+ break;
+ *value++ = key[--line_len] = '\0';
+
+ if (!peer && !strcmp(key, "private_key")) {
+ if (!key_from_hex(dev->private_key, value))
+ break;
+ curve25519_generate_public(dev->public_key, dev->private_key);
+ dev->flags |= WGDEVICE_HAS_PRIVATE_KEY | WGDEVICE_HAS_PUBLIC_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);
+ dev->flags |= WGDEVICE_HAS_FWMARK;
+ } else if (!strcmp(key, "public_key")) {
+ struct wgpeer *new_peer = calloc(1, sizeof(*new_peer));
+
+ 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;
+ peer->flags |= WGPEER_HAS_PUBLIC_KEY;
+ } else if (peer && !strcmp(key, "preshared_key")) {
+ if (!key_from_hex(peer->preshared_key, value))
+ break;
+ if (!key_is_zero(peer->preshared_key))
+ peer->flags |= WGPEER_HAS_PRESHARED_KEY;
+ } else if (peer && !strcmp(key, "endpoint")) {
+ char *begin, *end;
+ struct addrinfo *resolved;
+ struct addrinfo hints = {
+ .ai_family = AF_UNSPEC,
+ .ai_socktype = SOCK_DGRAM,
+ .ai_protocol = IPPROTO_UDP
+ };
+ if (!strlen(value))
+ break;
+ if (value[0] == '[') {
+ begin = &value[1];
+ end = strchr(value, ']');
+ if (!end)
+ break;
+ *end++ = '\0';
+ if (*end++ != ':' || !*end)
+ break;
+ } else {
+ begin = value;
+ end = strrchr(value, ':');
+ if (!end || !*(end + 1))
+ break;
+ *end++ = '\0';
+ }
+ if (getaddrinfo(begin, end, &hints, &resolved) != 0) {
+ ret = ENETUNREACH;
+ goto err;
+ }
+ if ((resolved->ai_family == AF_INET && resolved->ai_addrlen == sizeof(struct sockaddr_in)) ||
+ (resolved->ai_family == AF_INET6 && resolved->ai_addrlen == sizeof(struct sockaddr_in6)))
+ memcpy(&peer->endpoint.addr, resolved->ai_addr, resolved->ai_addrlen);
+ else {
+ freeaddrinfo(resolved);
+ break;
+ }
+ freeaddrinfo(resolved);
+ } 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, *mask = value, *ip = strsep(&mask, "/");
+
+ if (!mask || !char_is_digit(mask[0]))
+ break;
+ new_allowedip = calloc(1, sizeof(*new_allowedip));
+ 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(ip, ':')) {
+ if (inet_pton(AF_INET6, ip, &allowedip->ip6) == 1)
+ allowedip->family = AF_INET6;
+ } else {
+ if (inet_pton(AF_INET, ip, &allowedip->ip4) == 1)
+ allowedip->family = AF_INET;
+ }
+ allowedip->cidr = strtoul(mask, &end, 10);
+ if (*end || allowedip->family == AF_UNSPEC || (allowedip->family == AF_INET6 && allowedip->cidr > 128) || (allowedip->family == AF_INET && allowedip->cidr > 32))
+ break;
+ } else if (peer && !strcmp(key, "last_handshake_time_sec"))
+ peer->last_handshake_time.tv_sec = NUM(0x7fffffffffffffffULL);
+ else if (peer && !strcmp(key, "last_handshake_time_nsec"))
+ peer->last_handshake_time.tv_nsec = NUM(0x7fffffffffffffffULL);
+ else if (peer && !strcmp(key, "rx_bytes"))
+ peer->rx_bytes = NUM(0xffffffffffffffffULL);
+ else if (peer && !strcmp(key, "tx_bytes"))
+ peer->tx_bytes = NUM(0xffffffffffffffffULL);
+ else if (!strcmp(key, "errno"))
+ ret = -NUM(0x7fffffffU);
+ }
+ ret = -EPROTO;
+err:
+ free(key);
+ if (ret) {
+ free_wgdevice(dev);
+ *out = NULL;
+ }
+ fclose(f);
+ errno = -ret;
+ return ret;
+
+}
+#undef NUM
diff --git a/src/ipc-windows.h b/src/ipc-windows.h
new file mode 100644
index 0000000..a71911e
--- /dev/null
+++ b/src/ipc-windows.h
@@ -0,0 +1,456 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2015-2021 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
+ */
+
+#include "containers.h"
+#include <windows.h>
+#include <setupapi.h>
+#include <cfgmgr32.h>
+#include <iphlpapi.h>
+#include <initguid.h>
+#include <devguid.h>
+#include <ddk/ndisguid.h>
+#include <wireguard.h>
+#include <hashtable.h>
+
+#define IPC_SUPPORTS_KERNEL_INTERFACE
+
+static bool have_cached_kernel_interfaces;
+static struct hashtable cached_kernel_interfaces;
+static const DEVPROPKEY devpkey_name = DEVPKEY_WG_NAME;
+extern bool is_win7;
+
+static int kernel_get_wireguard_interfaces(struct string_list *list)
+{
+ HDEVINFO dev_info = SetupDiGetClassDevsExW(&GUID_DEVCLASS_NET, is_win7 ? L"ROOT\\WIREGUARD" : L"SWD\\WireGuard", NULL, DIGCF_PRESENT, NULL, NULL, NULL);
+ bool will_have_cached_kernel_interfaces = true;
+
+ if (dev_info == INVALID_HANDLE_VALUE) {
+ errno = EACCES;
+ return -errno;
+ }
+
+ for (DWORD i = 0;; ++i) {
+ DWORD buf_len;
+ WCHAR adapter_name[MAX_ADAPTER_NAME];
+ SP_DEVINFO_DATA dev_info_data = { .cbSize = sizeof(SP_DEVINFO_DATA) };
+ DEVPROPTYPE prop_type;
+ ULONG status, problem_code;
+ char *interface_name;
+ struct hashtable_entry *entry;
+
+ if (!SetupDiEnumDeviceInfo(dev_info, i, &dev_info_data)) {
+ if (GetLastError() == ERROR_NO_MORE_ITEMS)
+ break;
+ continue;
+ }
+
+ if (!SetupDiGetDevicePropertyW(dev_info, &dev_info_data, &devpkey_name,
+ &prop_type, (PBYTE)adapter_name,
+ sizeof(adapter_name), NULL, 0) ||
+ prop_type != DEVPROP_TYPE_STRING)
+ continue;
+ adapter_name[_countof(adapter_name) - 1] = L'0';
+ if (!adapter_name[0])
+ continue;
+ buf_len = WideCharToMultiByte(CP_UTF8, 0, adapter_name, -1, NULL, 0, NULL, NULL);
+ if (!buf_len)
+ continue;
+ interface_name = malloc(buf_len);
+ if (!interface_name)
+ continue;
+ buf_len = WideCharToMultiByte(CP_UTF8, 0, adapter_name, -1, interface_name, buf_len, NULL, NULL);
+ if (!buf_len) {
+ free(interface_name);
+ continue;
+ }
+
+ if (CM_Get_DevNode_Status(&status, &problem_code, dev_info_data.DevInst, 0) == CR_SUCCESS &&
+ (status & (DN_DRIVER_LOADED | DN_STARTED)) == (DN_DRIVER_LOADED | DN_STARTED))
+ string_list_add(list, interface_name);
+
+ entry = hashtable_find_or_insert_entry(&cached_kernel_interfaces, interface_name);
+ free(interface_name);
+ if (!entry)
+ continue;
+
+ if (SetupDiGetDeviceInstanceIdW(dev_info, &dev_info_data, NULL, 0, &buf_len) || GetLastError() != ERROR_INSUFFICIENT_BUFFER)
+ continue;
+ entry->value = calloc(sizeof(WCHAR), buf_len);
+ if (!entry->value)
+ continue;
+ if (!SetupDiGetDeviceInstanceIdW(dev_info, &dev_info_data, entry->value, buf_len, &buf_len)) {
+ free(entry->value);
+ entry->value = NULL;
+ continue;
+ }
+
+ will_have_cached_kernel_interfaces = true;
+ }
+ SetupDiDestroyDeviceInfoList(dev_info);
+ have_cached_kernel_interfaces = will_have_cached_kernel_interfaces;
+ return 0;
+}
+
+static HANDLE kernel_interface_handle(const char *iface)
+{
+ HDEVINFO dev_info;
+ WCHAR *interfaces = NULL;
+ HANDLE handle;
+
+ if (have_cached_kernel_interfaces) {
+ struct hashtable_entry *entry = hashtable_find_entry(&cached_kernel_interfaces, iface);
+ if (entry) {
+ DWORD buf_len;
+ if (CM_Get_Device_Interface_List_SizeW(
+ &buf_len, (GUID *)&GUID_DEVINTERFACE_NET, (DEVINSTID_W)entry->value,
+ CM_GET_DEVICE_INTERFACE_LIST_PRESENT) != CR_SUCCESS)
+ goto err_hash;
+ interfaces = calloc(buf_len, sizeof(*interfaces));
+ if (!interfaces)
+ goto err_hash;
+ if (CM_Get_Device_Interface_ListW(
+ (GUID *)&GUID_DEVINTERFACE_NET, (DEVINSTID_W)entry->value, interfaces, buf_len,
+ CM_GET_DEVICE_INTERFACE_LIST_PRESENT) != CR_SUCCESS || !interfaces[0]) {
+ free(interfaces);
+ interfaces = NULL;
+ goto err_hash;
+ }
+ handle = CreateFileW(interfaces, GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
+ OPEN_EXISTING, 0, NULL);
+ free(interfaces);
+ if (handle == INVALID_HANDLE_VALUE)
+ goto err_hash;
+ return handle;
+err_hash:
+ errno = EACCES;
+ return NULL;
+ }
+ }
+
+ dev_info = SetupDiGetClassDevsExW(&GUID_DEVCLASS_NET, is_win7 ? L"ROOT\\WIREGUARD" : L"SWD\\WireGuard", NULL, DIGCF_PRESENT, NULL, NULL, NULL);
+ if (dev_info == INVALID_HANDLE_VALUE)
+ return NULL;
+
+ for (DWORD i = 0; !interfaces; ++i) {
+ bool found;
+ DWORD buf_len;
+ WCHAR *buf, adapter_name[MAX_ADAPTER_NAME];
+ SP_DEVINFO_DATA dev_info_data = { .cbSize = sizeof(SP_DEVINFO_DATA) };
+ DEVPROPTYPE prop_type;
+ char *interface_name;
+
+ if (!SetupDiEnumDeviceInfo(dev_info, i, &dev_info_data)) {
+ if (GetLastError() == ERROR_NO_MORE_ITEMS)
+ break;
+ continue;
+ }
+
+ if (!SetupDiGetDevicePropertyW(dev_info, &dev_info_data, &devpkey_name,
+ &prop_type, (PBYTE)adapter_name,
+ sizeof(adapter_name), NULL, 0) ||
+ prop_type != DEVPROP_TYPE_STRING)
+ continue;
+ adapter_name[_countof(adapter_name) - 1] = L'0';
+ if (!adapter_name[0])
+ continue;
+ buf_len = WideCharToMultiByte(CP_UTF8, 0, adapter_name, -1, NULL, 0, NULL, NULL);
+ if (!buf_len)
+ continue;
+ interface_name = malloc(buf_len);
+ if (!interface_name)
+ continue;
+ buf_len = WideCharToMultiByte(CP_UTF8, 0, adapter_name, -1, interface_name, buf_len, NULL, NULL);
+ if (!buf_len) {
+ free(interface_name);
+ continue;
+ }
+ found = !strcmp(interface_name, iface);
+ free(interface_name);
+ if (!found)
+ continue;
+
+ if (SetupDiGetDeviceInstanceIdW(dev_info, &dev_info_data, NULL, 0, &buf_len) || GetLastError() != ERROR_INSUFFICIENT_BUFFER)
+ continue;
+ buf = calloc(sizeof(*buf), buf_len);
+ if (!buf)
+ continue;
+ if (!SetupDiGetDeviceInstanceIdW(dev_info, &dev_info_data, buf, buf_len, &buf_len))
+ goto cleanup_instance_id;
+ if (CM_Get_Device_Interface_List_SizeW(
+ &buf_len, (GUID *)&GUID_DEVINTERFACE_NET, (DEVINSTID_W)buf,
+ CM_GET_DEVICE_INTERFACE_LIST_PRESENT) != CR_SUCCESS)
+ goto cleanup_instance_id;
+ interfaces = calloc(buf_len, sizeof(*interfaces));
+ if (!interfaces)
+ goto cleanup_instance_id;
+ if (CM_Get_Device_Interface_ListW(
+ (GUID *)&GUID_DEVINTERFACE_NET, (DEVINSTID_W)buf, interfaces, buf_len,
+ CM_GET_DEVICE_INTERFACE_LIST_PRESENT) != CR_SUCCESS || !interfaces[0]) {
+ free(interfaces);
+ interfaces = NULL;
+ goto cleanup_instance_id;
+ }
+cleanup_instance_id:
+ free(buf);
+ }
+ SetupDiDestroyDeviceInfoList(dev_info);
+ if (!interfaces) {
+ errno = ENOENT;
+ return NULL;
+ }
+ handle = CreateFileW(interfaces, GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
+ OPEN_EXISTING, 0, NULL);
+ free(interfaces);
+ if (handle == INVALID_HANDLE_VALUE) {
+ errno = EACCES;
+ return NULL;
+ }
+ return handle;
+}
+
+static int kernel_get_device(struct wgdevice **device, const char *iface)
+{
+ WG_IOCTL_INTERFACE *wg_iface;
+ WG_IOCTL_PEER *wg_peer;
+ WG_IOCTL_ALLOWED_IP *wg_aip;
+ void *buf = NULL;
+ DWORD buf_len = 0;
+ HANDLE handle = kernel_interface_handle(iface);
+ struct wgdevice *dev;
+ struct wgpeer *peer;
+ struct wgallowedip *aip;
+ int ret;
+
+ *device = NULL;
+
+ if (!handle)
+ return -errno;
+
+ while (!DeviceIoControl(handle, WG_IOCTL_GET, NULL, 0, buf, buf_len, &buf_len, NULL)) {
+ free(buf);
+ if (GetLastError() != ERROR_MORE_DATA) {
+ errno = EACCES;
+ return -errno;
+ }
+ buf = malloc(buf_len);
+ if (!buf)
+ return -errno;
+ }
+
+ wg_iface = (WG_IOCTL_INTERFACE *)buf;
+ dev = calloc(1, sizeof(*dev));
+ if (!dev)
+ goto out;
+ strncpy(dev->name, iface, sizeof(dev->name));
+ dev->name[sizeof(dev->name) - 1] = '\0';
+
+ if (wg_iface->Flags & WG_IOCTL_INTERFACE_HAS_LISTEN_PORT) {
+ dev->listen_port = wg_iface->ListenPort;
+ dev->flags |= WGDEVICE_HAS_LISTEN_PORT;
+ }
+
+ if (wg_iface->Flags & WG_IOCTL_INTERFACE_HAS_PUBLIC_KEY) {
+ memcpy(dev->public_key, wg_iface->PublicKey, sizeof(dev->public_key));
+ dev->flags |= WGDEVICE_HAS_PUBLIC_KEY;
+ }
+
+ if (wg_iface->Flags & WG_IOCTL_INTERFACE_HAS_PRIVATE_KEY) {
+ memcpy(dev->private_key, wg_iface->PrivateKey, sizeof(dev->private_key));
+ dev->flags |= WGDEVICE_HAS_PRIVATE_KEY;
+ }
+
+ wg_peer = buf + sizeof(WG_IOCTL_INTERFACE);
+ for (ULONG i = 0; i < wg_iface->PeersCount; ++i) {
+ peer = calloc(1, sizeof(*peer));
+ if (!peer)
+ goto out;
+
+ if (dev->first_peer == NULL)
+ dev->first_peer = peer;
+ else
+ dev->last_peer->next_peer = peer;
+ dev->last_peer = peer;
+
+ if (wg_peer->Flags & WG_IOCTL_PEER_HAS_PUBLIC_KEY) {
+ memcpy(peer->public_key, wg_peer->PublicKey, sizeof(peer->public_key));
+ peer->flags |= WGPEER_HAS_PUBLIC_KEY;
+ }
+
+ if (wg_peer->Flags & WG_IOCTL_PEER_HAS_PRESHARED_KEY) {
+ memcpy(peer->preshared_key, wg_peer->PresharedKey, sizeof(peer->preshared_key));
+ if (!key_is_zero(peer->preshared_key))
+ peer->flags |= WGPEER_HAS_PRESHARED_KEY;
+ }
+
+ if (wg_peer->Flags & WG_IOCTL_PEER_HAS_PERSISTENT_KEEPALIVE) {
+ peer->persistent_keepalive_interval = wg_peer->PersistentKeepalive;
+ peer->flags |= WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL;
+ }
+
+ if (wg_peer->Flags & WG_IOCTL_PEER_HAS_ENDPOINT) {
+ if (wg_peer->Endpoint.si_family == AF_INET)
+ peer->endpoint.addr4 = wg_peer->Endpoint.Ipv4;
+ else if (wg_peer->Endpoint.si_family == AF_INET6)
+ peer->endpoint.addr6 = wg_peer->Endpoint.Ipv6;
+ }
+
+ peer->rx_bytes = wg_peer->RxBytes;
+ peer->tx_bytes = wg_peer->TxBytes;
+
+ if (wg_peer->LastHandshake) {
+ peer->last_handshake_time.tv_sec = wg_peer->LastHandshake / 10000000 - 11644473600LL;
+ peer->last_handshake_time.tv_nsec = wg_peer->LastHandshake % 10000000 * 100;
+ }
+
+ wg_aip = (void *)wg_peer + sizeof(WG_IOCTL_PEER);
+ for (ULONG j = 0; j < wg_peer->AllowedIPsCount; ++j) {
+ aip = calloc(1, sizeof(*aip));
+ if (!aip)
+ goto out;
+
+ if (peer->first_allowedip == NULL)
+ peer->first_allowedip = aip;
+ else
+ peer->last_allowedip->next_allowedip = aip;
+ peer->last_allowedip = aip;
+
+ aip->family = wg_aip->AddressFamily;
+ if (wg_aip->AddressFamily == AF_INET) {
+ memcpy(&aip->ip4, &wg_aip->Address.V4, sizeof(aip->ip4));
+ aip->cidr = wg_aip->Cidr;
+ } else if (wg_aip->AddressFamily == AF_INET6) {
+ memcpy(&aip->ip6, &wg_aip->Address.V6, sizeof(aip->ip6));
+ aip->cidr = wg_aip->Cidr;
+ }
+ ++wg_aip;
+ }
+ wg_peer = (WG_IOCTL_PEER *)wg_aip;
+ }
+ *device = dev;
+ errno = 0;
+out:
+ ret = -errno;
+ free(buf);
+ CloseHandle(handle);
+ return ret;
+}
+
+static int kernel_set_device(struct wgdevice *dev)
+{
+ WG_IOCTL_INTERFACE *wg_iface = NULL;
+ WG_IOCTL_PEER *wg_peer;
+ WG_IOCTL_ALLOWED_IP *wg_aip;
+ DWORD buf_len = sizeof(WG_IOCTL_INTERFACE);
+ HANDLE handle = kernel_interface_handle(dev->name);
+ struct wgpeer *peer;
+ struct wgallowedip *aip;
+ size_t peer_count, aip_count;
+ int ret = 0;
+
+ if (!handle)
+ return -errno;
+
+ for_each_wgpeer(dev, peer) {
+ if (DWORD_MAX - buf_len < sizeof(WG_IOCTL_PEER)) {
+ errno = EOVERFLOW;
+ goto out;
+ }
+ buf_len += sizeof(WG_IOCTL_PEER);
+ for_each_wgallowedip(peer, aip) {
+ if (DWORD_MAX - buf_len < sizeof(WG_IOCTL_ALLOWED_IP)) {
+ errno = EOVERFLOW;
+ goto out;
+ }
+ buf_len += sizeof(WG_IOCTL_ALLOWED_IP);
+ }
+ }
+ wg_iface = calloc(1, buf_len);
+ if (!wg_iface)
+ goto out;
+
+ if (dev->flags & WGDEVICE_HAS_PRIVATE_KEY) {
+ memcpy(wg_iface->PrivateKey, dev->private_key, sizeof(wg_iface->PrivateKey));
+ wg_iface->Flags |= WG_IOCTL_INTERFACE_HAS_PRIVATE_KEY;
+ }
+
+ if (dev->flags & WGDEVICE_HAS_LISTEN_PORT) {
+ wg_iface->ListenPort = dev->listen_port;
+ wg_iface->Flags |= WG_IOCTL_INTERFACE_HAS_LISTEN_PORT;
+ }
+
+ if (dev->flags & WGDEVICE_REPLACE_PEERS)
+ wg_iface->Flags |= WG_IOCTL_INTERFACE_REPLACE_PEERS;
+
+ peer_count = 0;
+ wg_peer = (void *)wg_iface + sizeof(WG_IOCTL_INTERFACE);
+ for_each_wgpeer(dev, peer) {
+ wg_peer->Flags = WG_IOCTL_PEER_HAS_PUBLIC_KEY;
+ memcpy(wg_peer->PublicKey, peer->public_key, sizeof(wg_peer->PublicKey));
+
+ if (peer->flags & WGPEER_HAS_PRESHARED_KEY) {
+ memcpy(wg_peer->PresharedKey, peer->preshared_key, sizeof(wg_peer->PresharedKey));
+ wg_peer->Flags |= WG_IOCTL_PEER_HAS_PRESHARED_KEY;
+ }
+
+ if (peer->flags & WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL) {
+ wg_peer->PersistentKeepalive = peer->persistent_keepalive_interval;
+ wg_peer->Flags |= WG_IOCTL_PEER_HAS_PERSISTENT_KEEPALIVE;
+ }
+
+ if (peer->endpoint.addr.sa_family == AF_INET) {
+ wg_peer->Endpoint.Ipv4 = peer->endpoint.addr4;
+ wg_peer->Flags |= WG_IOCTL_PEER_HAS_ENDPOINT;
+ } else if (peer->endpoint.addr.sa_family == AF_INET6) {
+ wg_peer->Endpoint.Ipv6 = peer->endpoint.addr6;
+ wg_peer->Flags |= WG_IOCTL_PEER_HAS_ENDPOINT;
+ }
+
+ if (peer->flags & WGPEER_REPLACE_ALLOWEDIPS)
+ wg_peer->Flags |= WG_IOCTL_PEER_REPLACE_ALLOWED_IPS;
+
+ if (peer->flags & WGPEER_REMOVE_ME)
+ wg_peer->Flags |= WG_IOCTL_PEER_REMOVE;
+
+ aip_count = 0;
+ wg_aip = (void *)wg_peer + sizeof(WG_IOCTL_PEER);
+ for_each_wgallowedip(peer, aip) {
+ if (aip->flags) {
+ //TODO: implement me
+ errno = EOPNOTSUPP;
+ goto out;
+ }
+
+ wg_aip->AddressFamily = aip->family;
+ wg_aip->Cidr = aip->cidr;
+
+ if (aip->family == AF_INET)
+ wg_aip->Address.V4 = aip->ip4;
+ else if (aip->family == AF_INET6)
+ wg_aip->Address.V6 = aip->ip6;
+ else
+ continue;
+ ++aip_count;
+ ++wg_aip;
+ }
+ wg_peer->AllowedIPsCount = aip_count;
+ ++peer_count;
+ wg_peer = (WG_IOCTL_PEER *)wg_aip;
+ }
+ wg_iface->PeersCount = peer_count;
+
+ if (!DeviceIoControl(handle, WG_IOCTL_SET, NULL, 0, wg_iface, buf_len, &buf_len, NULL)) {
+ errno = EACCES;
+ goto out;
+ }
+ errno = 0;
+
+out:
+ ret = -errno;
+ free(wg_iface);
+ CloseHandle(handle);
+ return ret;
+}
diff --git a/src/ipc.c b/src/ipc.c
index b9d2532..1155bd5 100644
--- a/src/ipc.c
+++ b/src/ipc.c
@@ -1,49 +1,13 @@
-// SPDX-License-Identifier: GPL-2.0
+// SPDX-License-Identifier: GPL-2.0 OR MIT
/*
* Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*/
-#ifdef __linux__
-#include <linux/if_link.h>
-#include <linux/rtnetlink.h>
-#include <linux/wireguard.h>
-#include "netlink.h"
-#endif
-#include <netinet/in.h>
-#include <sys/socket.h>
-#include <net/if.h>
-#include <errno.h>
-#include <stdbool.h>
-#include <stddef.h>
-#include <stdio.h>
-#include <stdlib.h>
#include <string.h>
-#include <ctype.h>
-#include <unistd.h>
-#include <time.h>
-#include <dirent.h>
-#include <signal.h>
-#include <netdb.h>
-#include <limits.h>
-#include <sys/types.h>
-#include <sys/ioctl.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/un.h>
-#include <arpa/inet.h>
-
-#include "ipc.h"
+#include <stdlib.h>
+#include <errno.h>
#include "containers.h"
-#include "encoding.h"
-#include "curve25519.h"
-
-#define SOCK_PATH RUNSTATEDIR "/wireguard/"
-#define SOCK_SUFFIX ".sock"
-#ifdef __linux__
-#define SOCKET_BUFFER_SIZE (mnl_ideal_socket_buffer_size())
-#else
-#define SOCKET_BUFFER_SIZE 8192
-#endif
+#include "ipc.h"
struct string_list {
char *buffer;
@@ -62,7 +26,7 @@ static int string_list_add(struct string_list *list, const char *str)
char *new_buffer;
size_t new_cap = list->cap * 2;
- if (new_cap < list->len +len + 1)
+ if (new_cap < list->len + len + 1)
new_cap = list->len + len + 1;
new_buffer = realloc(list->buffer, new_cap);
if (!new_buffer)
@@ -76,850 +40,15 @@ static int string_list_add(struct string_list *list, const char *str)
return 0;
}
-#ifndef WINCOMPAT
-static FILE *userspace_interface_file(const char *iface)
-{
- struct stat sbuf;
- struct sockaddr_un addr = { .sun_family = AF_UNIX };
- int fd = -1, ret;
- FILE *f = NULL;
-
- errno = EINVAL;
- if (strchr(iface, '/'))
- goto out;
- ret = snprintf(addr.sun_path, sizeof(addr.sun_path), SOCK_PATH "%s" SOCK_SUFFIX, iface);
- if (ret < 0)
- goto out;
- ret = stat(addr.sun_path, &sbuf);
- if (ret < 0)
- goto out;
- errno = EBADF;
- if (!S_ISSOCK(sbuf.st_mode))
- goto out;
-
- ret = fd = socket(AF_UNIX, SOCK_STREAM, 0);
- if (ret < 0)
- goto out;
-
- ret = connect(fd, (struct sockaddr *)&addr, sizeof(addr));
- if (ret < 0) {
- if (errno == ECONNREFUSED) /* If the process is gone, we try to clean up the socket. */
- unlink(addr.sun_path);
- goto out;
- }
- f = fdopen(fd, "r+");
- if (f)
- errno = 0;
-out:
- ret = -errno;
- if (ret) {
- if (fd >= 0)
- close(fd);
- errno = -ret;
- return NULL;
- }
- return f;
-}
-
-static bool userspace_has_wireguard_interface(const char *iface)
-{
- struct stat sbuf;
- struct sockaddr_un addr = { .sun_family = AF_UNIX };
- int fd, ret;
-
- if (strchr(iface, '/'))
- return false;
- if (snprintf(addr.sun_path, sizeof(addr.sun_path), SOCK_PATH "%s" SOCK_SUFFIX, iface) < 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;
- ret = connect(fd, (struct sockaddr *)&addr, sizeof(addr));
- if (ret < 0 && errno == ECONNREFUSED) { /* If the process is gone, we try to clean up the socket. */
- close(fd);
- unlink(addr.sun_path);
- return false;
- }
- close(fd);
- return true;
-}
-
-static int userspace_get_wireguard_interfaces(struct string_list *list)
-{
- DIR *dir;
- struct dirent *ent;
- size_t len;
- char *end;
- int ret = 0;
-
- dir = opendir(SOCK_PATH);
- if (!dir)
- return errno == ENOENT ? 0 : -errno;
- while ((ent = readdir(dir))) {
- len = strlen(ent->d_name);
- if (len <= strlen(SOCK_SUFFIX))
- continue;
- end = &ent->d_name[len - strlen(SOCK_SUFFIX)];
- if (strncmp(end, SOCK_SUFFIX, strlen(SOCK_SUFFIX)))
- continue;
- *end = '\0';
- if (!userspace_has_wireguard_interface(ent->d_name))
- continue;
- ret = string_list_add(list, ent->d_name);
- if (ret < 0)
- goto out;
- }
-out:
- closedir(dir);
- return ret;
-}
-#else
-#include "wincompat/ipc.c"
-#endif
-
-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 wgallowedip *allowedip;
- FILE *f;
- int ret;
- socklen_t addr_len;
-
- f = userspace_interface_file(dev->name);
- if (!f)
- return -errno;
- fprintf(f, "set=1\n");
-
- if (dev->flags & WGDEVICE_HAS_PRIVATE_KEY) {
- key_to_hex(hex, dev->private_key);
- fprintf(f, "private_key=%s\n", hex);
- }
- 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) {
- 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_HAS_PRESHARED_KEY) {
- key_to_hex(hex, peer->preshared_key);
- fprintf(f, "preshared_key=%s\n", hex);
- }
- if (peer->endpoint.addr.sa_family == AF_INET || peer->endpoint.addr.sa_family == AF_INET6) {
- addr_len = 0;
- if (peer->endpoint.addr.sa_family == AF_INET)
- addr_len = sizeof(struct sockaddr_in);
- else if (peer->endpoint.addr.sa_family == AF_INET6)
- addr_len = sizeof(struct sockaddr_in6);
- if (!getnameinfo(&peer->endpoint.addr, addr_len, host, sizeof(host), service, sizeof(service), NI_DGRAM | NI_NUMERICSERV | NI_NUMERICHOST)) {
- if (peer->endpoint.addr.sa_family == AF_INET6 && strchr(host, ':'))
- fprintf(f, "endpoint=[%s]:%s\n", host, service);
- else
- fprintf(f, "endpoint=%s:%s\n", host, service);
- }
- }
- if (peer->flags & WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL)
- fprintf(f, "persistent_keepalive_interval=%u\n", peer->persistent_keepalive_interval);
- if (peer->flags & WGPEER_REPLACE_ALLOWEDIPS)
- fprintf(f, "replace_allowed_ips=true\n");
- for_each_wgallowedip(peer, allowedip) {
- if (allowedip->family == AF_INET) {
- if (!inet_ntop(AF_INET, &allowedip->ip4, ip, INET6_ADDRSTRLEN))
- continue;
- } 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, allowedip->cidr);
- }
- }
- fprintf(f, "\n");
- fflush(f);
-
- if (fscanf(f, "errno=%d\n\n", &ret) != 1)
- ret = errno ? -errno : -EPROTO;
- fclose(f);
- errno = -ret;
- return ret;
-}
-
-#define NUM(max) ({ \
- unsigned long long num; \
- char *end; \
- if (!isdigit(value[0])) \
- break; \
- num = strtoull(value, &end, 10); \
- if (*end || num > max) \
- break; \
- num; \
-})
-
-static int userspace_get_device(struct wgdevice **out, const char *iface)
-{
- struct wgdevice *dev;
- struct wgpeer *peer = NULL;
- 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(*dev));
- if (!dev)
- return -errno;
-
- f = userspace_interface_file(iface);
- if (!f) {
- ret = -errno;
- free(dev);
- *out = NULL;
- return ret;
- }
-
- fprintf(f, "get=1\n\n");
- fflush(f);
-
- strncpy(dev->name, iface, IFNAMSIZ - 1);
- dev->name[IFNAMSIZ - 1] = '\0';
-
- while (getline(&key, &line_buffer_len, f) > 0) {
- line_len = strlen(key);
- if (line_len == 1 && key[0] == '\n')
- goto err;
- value = strchr(key, '=');
- if (!value || line_len == 0 || key[line_len - 1] != '\n')
- break;
- *value++ = key[--line_len] = '\0';
-
- if (!peer && !strcmp(key, "private_key")) {
- if (!key_from_hex(dev->private_key, value))
- break;
- curve25519_generate_public(dev->public_key, dev->private_key);
- dev->flags |= WGDEVICE_HAS_PRIVATE_KEY | WGDEVICE_HAS_PUBLIC_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);
- dev->flags |= WGDEVICE_HAS_FWMARK;
- } else if (!strcmp(key, "public_key")) {
- struct wgpeer *new_peer = calloc(1, sizeof(*new_peer));
-
- 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;
- peer->flags |= WGPEER_HAS_PUBLIC_KEY;
- } else if (peer && !strcmp(key, "preshared_key")) {
- if (!key_from_hex(peer->preshared_key, value))
- break;
- if (!key_is_zero(peer->preshared_key))
- peer->flags |= WGPEER_HAS_PRESHARED_KEY;
- } else if (peer && !strcmp(key, "endpoint")) {
- char *begin, *end;
- struct addrinfo *resolved;
- struct addrinfo hints = {
- .ai_family = AF_UNSPEC,
- .ai_socktype = SOCK_DGRAM,
- .ai_protocol = IPPROTO_UDP
- };
- if (!strlen(value))
- break;
- if (value[0] == '[') {
- begin = &value[1];
- end = strchr(value, ']');
- if (!end)
- break;
- *end++ = '\0';
- if (*end++ != ':' || !*end)
- break;
- } else {
- begin = value;
- end = strrchr(value, ':');
- if (!end || !*(end + 1))
- break;
- *end++ = '\0';
- }
- if (getaddrinfo(begin, end, &hints, &resolved) != 0) {
- ret = ENETUNREACH;
- goto err;
- }
- if ((resolved->ai_family == AF_INET && resolved->ai_addrlen == sizeof(struct sockaddr_in)) ||
- (resolved->ai_family == AF_INET6 && resolved->ai_addrlen == sizeof(struct sockaddr_in6)))
- memcpy(&peer->endpoint.addr, resolved->ai_addr, resolved->ai_addrlen);
- else {
- freeaddrinfo(resolved);
- break;
- }
- freeaddrinfo(resolved);
- } 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, *mask = value, *ip = strsep(&mask, "/");
-
- if (!mask || !isdigit(mask[0]))
- break;
- new_allowedip = calloc(1, sizeof(*new_allowedip));
- 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(ip, ':')) {
- if (inet_pton(AF_INET6, ip, &allowedip->ip6) == 1)
- allowedip->family = AF_INET6;
- } else {
- if (inet_pton(AF_INET, ip, &allowedip->ip4) == 1)
- allowedip->family = AF_INET;
- }
- allowedip->cidr = strtoul(mask, &end, 10);
- if (*end || allowedip->family == AF_UNSPEC || (allowedip->family == AF_INET6 && allowedip->cidr > 128) || (allowedip->family == AF_INET && allowedip->cidr > 32))
- break;
- } else if (peer && !strcmp(key, "last_handshake_time_sec"))
- peer->last_handshake_time.tv_sec = NUM(0x7fffffffffffffffULL);
- else if (peer && !strcmp(key, "last_handshake_time_nsec"))
- peer->last_handshake_time.tv_nsec = NUM(0x7fffffffffffffffULL);
- else if (peer && !strcmp(key, "rx_bytes"))
- peer->rx_bytes = NUM(0xffffffffffffffffULL);
- else if (peer && !strcmp(key, "tx_bytes"))
- peer->tx_bytes = NUM(0xffffffffffffffffULL);
- else if (!strcmp(key, "errno"))
- ret = -NUM(0x7fffffffU);
- }
- ret = -EPROTO;
-err:
- free(key);
- if (ret) {
- free_wgdevice(dev);
- *out = NULL;
- }
- fclose(f);
- errno = -ret;
- return ret;
-
-}
-#undef NUM
-
-#ifdef __linux__
-
-struct interface {
- const char *name;
- bool is_wireguard;
-};
-
-static int parse_linkinfo(const struct nlattr *attr, void *data)
-{
- struct interface *interface = data;
-
- if (mnl_attr_get_type(attr) == IFLA_INFO_KIND && !strcmp(WG_GENL_NAME, mnl_attr_get_str(attr)))
- interface->is_wireguard = true;
- return MNL_CB_OK;
-}
-
-static int parse_infomsg(const struct nlattr *attr, void *data)
-{
- struct interface *interface = data;
-
- if (mnl_attr_get_type(attr) == IFLA_LINKINFO)
- return mnl_attr_parse_nested(attr, parse_linkinfo, data);
- else if (mnl_attr_get_type(attr) == IFLA_IFNAME)
- interface->name = mnl_attr_get_str(attr);
- return MNL_CB_OK;
-}
-
-static int read_devices_cb(const struct nlmsghdr *nlh, void *data)
-{
- struct string_list *list = data;
- struct interface interface = { 0 };
- int ret;
-
- ret = mnl_attr_parse(nlh, sizeof(struct ifinfomsg), parse_infomsg, &interface);
- if (ret != MNL_CB_OK)
- return ret;
- if (interface.name && interface.is_wireguard)
- ret = string_list_add(list, interface.name);
- if (ret < 0)
- return ret;
- if (nlh->nlmsg_type != NLMSG_DONE)
- return MNL_CB_OK + 1;
- return MNL_CB_OK;
-}
-
-static int kernel_get_wireguard_interfaces(struct string_list *list)
-{
- struct mnl_socket *nl = NULL;
- char *rtnl_buffer = NULL;
- size_t message_len;
- unsigned int portid, seq;
- ssize_t len;
- int ret = 0;
- struct nlmsghdr *nlh;
- struct ifinfomsg *ifm;
-
- ret = -ENOMEM;
- rtnl_buffer = calloc(SOCKET_BUFFER_SIZE, 1);
- if (!rtnl_buffer)
- goto cleanup;
-
- nl = mnl_socket_open(NETLINK_ROUTE);
- if (!nl) {
- ret = -errno;
- goto cleanup;
- }
-
- if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) {
- ret = -errno;
- goto cleanup;
- }
-
- seq = time(NULL);
- portid = mnl_socket_get_portid(nl);
- nlh = mnl_nlmsg_put_header(rtnl_buffer);
- nlh->nlmsg_type = RTM_GETLINK;
- nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP;
- nlh->nlmsg_seq = seq;
- ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm));
- ifm->ifi_family = AF_UNSPEC;
- message_len = nlh->nlmsg_len;
-
- if (mnl_socket_sendto(nl, rtnl_buffer, message_len) < 0) {
- ret = -errno;
- goto cleanup;
- }
-
-another:
- if ((len = mnl_socket_recvfrom(nl, rtnl_buffer, SOCKET_BUFFER_SIZE)) < 0) {
- ret = -errno;
- goto cleanup;
- }
- if ((len = mnl_cb_run(rtnl_buffer, len, seq, portid, read_devices_cb, list)) < 0) {
- /* Netlink returns NLM_F_DUMP_INTR if the set of all tunnels changed
- * during the dump. That's unfortunate, but is pretty common on busy
- * systems that are adding and removing tunnels all the time. Rather
- * than retrying, potentially indefinitely, we just work with the
- * partial results. */
- if (errno != EINTR) {
- ret = -errno;
- goto cleanup;
- }
- }
- if (len == MNL_CB_OK + 1)
- goto another;
- ret = 0;
-
-cleanup:
- free(rtnl_buffer);
- if (nl)
- mnl_socket_close(nl);
- return ret;
-}
-
-static int kernel_set_device(struct wgdevice *dev)
-{
- int ret = 0;
- 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;
-
- 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);
- }
- 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 (peer = peer ? peer : dev->first_peer; peer; peer = peer->next_peer) {
- uint32_t flags = 0;
-
- peer_nest = mnl_attr_nest_start_check(nlh, SOCKET_BUFFER_SIZE, 0);
- if (!peer_nest)
- goto toobig_peers;
- if (!mnl_attr_put_check(nlh, 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, 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, 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, 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, SOCKET_BUFFER_SIZE, WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL, peer->persistent_keepalive_interval))
- goto toobig_peers;
- }
- }
- if (flags) {
- if (!mnl_attr_put_u32_check(nlh, 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, SOCKET_BUFFER_SIZE, WGPEER_A_ALLOWEDIPS);
- if (!allowedips_nest)
- goto toobig_allowedips;
- for (; allowedip; allowedip = allowedip->next_allowedip) {
- allowedip_nest = mnl_attr_nest_start_check(nlh, SOCKET_BUFFER_SIZE, 0);
- if (!allowedip_nest)
- goto toobig_allowedips;
- if (!mnl_attr_put_u16_check(nlh, SOCKET_BUFFER_SIZE, WGALLOWEDIP_A_FAMILY, allowedip->family))
- goto toobig_allowedips;
- if (allowedip->family == AF_INET) {
- if (!mnl_attr_put_check(nlh, 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, SOCKET_BUFFER_SIZE, WGALLOWEDIP_A_IPADDR, sizeof(allowedip->ip6), &allowedip->ip6))
- goto toobig_allowedips;
- }
- if (!mnl_attr_put_u8_check(nlh, SOCKET_BUFFER_SIZE, WGALLOWEDIP_A_CIDR_MASK, allowedip->cidr))
- goto toobig_allowedips;
- mnl_attr_nest_end(nlh, allowedip_nest);
- allowedip_nest = NULL;
- }
- mnl_attr_nest_end(nlh, allowedips_nest);
- allowedips_nest = NULL;
- }
-
- mnl_attr_nest_end(nlh, peer_nest);
- peer_nest = NULL;
- }
- mnl_attr_nest_end(nlh, peers_nest);
- peers_nest = NULL;
- goto send;
-toobig_allowedips:
- if (allowedip_nest)
- mnl_attr_nest_cancel(nlh, allowedip_nest);
- if (allowedips_nest)
- mnl_attr_nest_end(nlh, allowedips_nest);
- mnl_attr_nest_end(nlh, peer_nest);
- mnl_attr_nest_end(nlh, peers_nest);
- goto send;
-toobig_peers:
- if (peer_nest)
- mnl_attr_nest_cancel(nlh, peer_nest);
- mnl_attr_nest_end(nlh, peers_nest);
- goto send;
-send:
- if (mnlg_socket_send(nlg, nlh) < 0) {
- ret = -errno;
- goto out;
- }
- errno = 0;
- if (mnlg_socket_recv_run(nlg, NULL, NULL) < 0) {
- ret = errno ? -errno : -EINVAL;
- goto out;
- }
- if (peer)
- goto again;
-
-out:
- mnlg_socket_close(nlg);
- errno = -ret;
- return ret;
-}
-
-static int parse_allowedip(const struct nlattr *attr, void *data)
-{
- struct wgallowedip *allowedip = data;
-
- switch (mnl_attr_get_type(attr)) {
- case WGALLOWEDIP_A_UNSPEC:
- break;
- case WGALLOWEDIP_A_FAMILY:
- if (!mnl_attr_validate(attr, MNL_TYPE_U16))
- allowedip->family = mnl_attr_get_u16(attr);
- break;
- case WGALLOWEDIP_A_IPADDR:
- if (mnl_attr_get_payload_len(attr) == sizeof(allowedip->ip4))
- memcpy(&allowedip->ip4, mnl_attr_get_payload(attr), sizeof(allowedip->ip4));
- else if (mnl_attr_get_payload_len(attr) == sizeof(allowedip->ip6))
- memcpy(&allowedip->ip6, mnl_attr_get_payload(attr), sizeof(allowedip->ip6));
- break;
- case WGALLOWEDIP_A_CIDR_MASK:
- if (!mnl_attr_validate(attr, MNL_TYPE_U8))
- allowedip->cidr = mnl_attr_get_u8(attr);
- break;
- }
-
- return MNL_CB_OK;
-}
-
-static int parse_allowedips(const struct nlattr *attr, void *data)
-{
- struct wgpeer *peer = data;
- struct wgallowedip *new_allowedip = calloc(1, sizeof(*new_allowedip));
- int ret;
-
- if (!new_allowedip) {
- perror("calloc");
- return MNL_CB_ERROR;
- }
- if (!peer->first_allowedip)
- peer->first_allowedip = peer->last_allowedip = new_allowedip;
- else {
- peer->last_allowedip->next_allowedip = new_allowedip;
- peer->last_allowedip = new_allowedip;
- }
- ret = mnl_attr_parse_nested(attr, parse_allowedip, new_allowedip);
- if (!ret)
- return ret;
- if (!((new_allowedip->family == AF_INET && new_allowedip->cidr <= 32) || (new_allowedip->family == AF_INET6 && new_allowedip->cidr <= 128)))
- return MNL_CB_ERROR;
- return MNL_CB_OK;
-}
-
-static int parse_peer(const struct nlattr *attr, void *data)
-{
- struct wgpeer *peer = data;
-
- switch (mnl_attr_get_type(attr)) {
- case WGPEER_A_UNSPEC:
- break;
- case WGPEER_A_PUBLIC_KEY:
- if (mnl_attr_get_payload_len(attr) == sizeof(peer->public_key)) {
- memcpy(peer->public_key, mnl_attr_get_payload(attr), sizeof(peer->public_key));
- peer->flags |= WGPEER_HAS_PUBLIC_KEY;
- }
- break;
- case WGPEER_A_PRESHARED_KEY:
- if (mnl_attr_get_payload_len(attr) == sizeof(peer->preshared_key)) {
- memcpy(peer->preshared_key, mnl_attr_get_payload(attr), sizeof(peer->preshared_key));
- if (!key_is_zero(peer->preshared_key))
- peer->flags |= WGPEER_HAS_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(peer->endpoint.addr4))
- memcpy(&peer->endpoint.addr4, addr, sizeof(peer->endpoint.addr4));
- else if (addr->sa_family == AF_INET6 && mnl_attr_get_payload_len(attr) == sizeof(peer->endpoint.addr6))
- memcpy(&peer->endpoint.addr6, addr, sizeof(peer->endpoint.addr6));
- break;
- }
- case WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL:
- if (!mnl_attr_validate(attr, MNL_TYPE_U16))
- peer->persistent_keepalive_interval = mnl_attr_get_u16(attr);
- break;
- case WGPEER_A_LAST_HANDSHAKE_TIME:
- if (mnl_attr_get_payload_len(attr) == sizeof(peer->last_handshake_time))
- memcpy(&peer->last_handshake_time, mnl_attr_get_payload(attr), sizeof(peer->last_handshake_time));
- break;
- case WGPEER_A_RX_BYTES:
- if (!mnl_attr_validate(attr, MNL_TYPE_U64))
- peer->rx_bytes = mnl_attr_get_u64(attr);
- break;
- case WGPEER_A_TX_BYTES:
- if (!mnl_attr_validate(attr, MNL_TYPE_U64))
- peer->tx_bytes = mnl_attr_get_u64(attr);
- break;
- case WGPEER_A_ALLOWEDIPS:
- return mnl_attr_parse_nested(attr, parse_allowedips, peer);
- }
-
- return MNL_CB_OK;
-}
-
-static int parse_peers(const struct nlattr *attr, void *data)
-{
- struct wgdevice *device = data;
- struct wgpeer *new_peer = calloc(1, sizeof(*new_peer));
- int ret;
-
- if (!new_peer) {
- perror("calloc");
- return MNL_CB_ERROR;
- }
- if (!device->first_peer)
- device->first_peer = device->last_peer = new_peer;
- else {
- device->last_peer->next_peer = new_peer;
- device->last_peer = new_peer;
- }
- ret = mnl_attr_parse_nested(attr, parse_peer, new_peer);
- if (!ret)
- return ret;
- if (!(new_peer->flags & WGPEER_HAS_PUBLIC_KEY))
- return MNL_CB_ERROR;
- return MNL_CB_OK;
-}
-
-static int parse_device(const struct nlattr *attr, void *data)
-{
- struct wgdevice *device = data;
-
- switch (mnl_attr_get_type(attr)) {
- case WGDEVICE_A_UNSPEC:
- break;
- case WGDEVICE_A_IFINDEX:
- if (!mnl_attr_validate(attr, MNL_TYPE_U32))
- device->ifindex = mnl_attr_get_u32(attr);
- break;
- case WGDEVICE_A_IFNAME:
- if (!mnl_attr_validate(attr, MNL_TYPE_STRING)) {
- strncpy(device->name, mnl_attr_get_str(attr), sizeof(device->name) - 1);
- device->name[sizeof(device->name) - 1] = '\0';
- }
- break;
- case WGDEVICE_A_PRIVATE_KEY:
- if (mnl_attr_get_payload_len(attr) == sizeof(device->private_key)) {
- memcpy(device->private_key, mnl_attr_get_payload(attr), sizeof(device->private_key));
- device->flags |= WGDEVICE_HAS_PRIVATE_KEY;
- }
- break;
- case WGDEVICE_A_PUBLIC_KEY:
- if (mnl_attr_get_payload_len(attr) == sizeof(device->public_key)) {
- memcpy(device->public_key, mnl_attr_get_payload(attr), sizeof(device->public_key));
- device->flags |= WGDEVICE_HAS_PUBLIC_KEY;
- }
- break;
- case WGDEVICE_A_LISTEN_PORT:
- if (!mnl_attr_validate(attr, MNL_TYPE_U16))
- device->listen_port = mnl_attr_get_u16(attr);
- break;
- case WGDEVICE_A_FWMARK:
- if (!mnl_attr_validate(attr, MNL_TYPE_U32))
- device->fwmark = mnl_attr_get_u32(attr);
- break;
- case WGDEVICE_A_PEERS:
- return mnl_attr_parse_nested(attr, parse_peers, device);
- }
-
- 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 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;
- }
- if (!peer->first_allowedip) {
- peer->first_allowedip = peer->next_peer->first_allowedip;
- peer->last_allowedip = peer->next_peer->last_allowedip;
- } else {
- peer->last_allowedip->next_allowedip = peer->next_peer->first_allowedip;
- peer->last_allowedip = peer->next_peer->last_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 **device, const char *iface)
-{
- int ret;
- struct nlmsghdr *nlh;
- struct mnlg_socket *nlg;
-
-try_again:
- ret = 0;
- *device = calloc(1, sizeof(**device));
- if (!*device)
- return -errno;
-
- nlg = mnlg_socket_open(WG_GENL_NAME, WG_GENL_VERSION);
- if (!nlg) {
- free_wgdevice(*device);
- *device = 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, iface);
- if (mnlg_socket_send(nlg, nlh) < 0) {
- ret = -errno;
- goto out;
- }
- errno = 0;
- if (mnlg_socket_recv_run(nlg, read_device_cb, *device) < 0) {
- ret = errno ? -errno : -EINVAL;
- goto out;
- }
- coalesce_peers(*device);
-
-out:
- if (nlg)
- mnlg_socket_close(nlg);
- if (ret) {
- free_wgdevice(*device);
- if (ret == -EINTR)
- goto try_again;
- *device = NULL;
- }
- errno = -ret;
- return ret;
-}
+#include "ipc-uapi.h"
+#if defined(__linux__)
+#include "ipc-linux.h"
+#elif defined(__OpenBSD__)
+#include "ipc-openbsd.h"
+#elif defined(__FreeBSD__)
+#include "ipc-freebsd.h"
+#elif defined(_WIN32)
+#include "ipc-windows.h"
#endif
/* first\0second\0third\0forth\0last\0\0 */
@@ -928,7 +57,7 @@ char *ipc_list_devices(void)
struct string_list list = { 0 };
int ret;
-#ifdef __linux__
+#ifdef IPC_SUPPORTS_KERNEL_INTERFACE
ret = kernel_get_wireguard_interfaces(&list);
if (ret < 0)
goto cleanup;
@@ -948,7 +77,7 @@ cleanup:
int ipc_get_device(struct wgdevice **dev, const char *iface)
{
-#ifdef __linux__
+#ifdef IPC_SUPPORTS_KERNEL_INTERFACE
if (userspace_has_wireguard_interface(iface))
return userspace_get_device(dev, iface);
return kernel_get_device(dev, iface);
@@ -959,7 +88,7 @@ int ipc_get_device(struct wgdevice **dev, const char *iface)
int ipc_set_device(struct wgdevice *dev)
{
-#ifdef __linux__
+#ifdef IPC_SUPPORTS_KERNEL_INTERFACE
if (userspace_has_wireguard_interface(dev->name))
return userspace_set_device(dev);
return kernel_set_device(dev);
diff --git a/src/ipc.h b/src/ipc.h
index c51c8e7..bc0fd60 100644
--- a/src/ipc.h
+++ b/src/ipc.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0 */
+/* SPDX-License-Identifier: GPL-2.0 OR MIT */
/*
* Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*/
diff --git a/src/man/wg-quick.8 b/src/man/wg-quick.8
index c38c7d9..bc9e145 100644
--- a/src/man/wg-quick.8
+++ b/src/man/wg-quick.8
@@ -76,7 +76,8 @@ Address \(em a comma-separated list of IP (v4 or v6) addresses (optionally with
to be assigned to the interface. May be specified multiple times.
.IP \(bu
DNS \(em a comma-separated list of IP (v4 or v6) addresses to be set as the interface's
-DNS servers. May be specified multiple times. Upon bringing the interface up, this runs
+DNS servers, or non-IP hostnames to be set as the interface's DNS search domains. May be
+specified multiple times. Upon bringing the interface up, this runs
`resolvconf -a tun.\fIINTERFACE\fP -m 0 -x` and upon bringing it down, this runs
`resolvconf -d tun.\fIINTERFACE\fP`. If these particular invocations of
.BR resolvconf (8)
@@ -99,7 +100,8 @@ is expanded to \fIINTERFACE\fP. Each one may be specified multiple times, in whi
the commands are executed in order.
.IP \(bu
SaveConfig \(em if set to `true', the configuration is saved from the current state of the
-interface upon shutdown.
+interface upon shutdown. Any changes made to the configuration file before the
+interface is removed will therefore be overwritten.
.P
Recommended \fIINTERFACE\fP names include `wg0' or `wgvpn0' or even `wgmgmtlan0'.
@@ -166,7 +168,7 @@ sockets, which bypass Netfilter.) When IPv6 is in use, additional similar lines
Or, perhaps it is desirable to store private keys in encrypted form, such as through use of
.BR pass (1):
- \fBPostUp = wg set %i private-key <(pass WireGuard/private-keys/%i)\fP
+ \fBPreUp = wg set %i private-key <(pass WireGuard/private-keys/%i)\fP
.br
For use on a server, the following is a more complicated example involving multiple peers:
@@ -252,9 +254,7 @@ This will load the configuration file `/etc/wireguard/wgnet0.conf'.
The \fIstrip\fP command is useful for reloading configuration files without disrupting active
sessions:
-\fB # wg addconf wgnet0 <(wg-quick strip wgnet0)\fP
-
-(Note that the above command will add and update peers but will not remove peers.)
+\fB # wg syncconf wgnet0 <(wg-quick strip wgnet0)\fP
.SH SEE ALSO
.BR wg (8),
diff --git a/src/man/wg.8 b/src/man/wg.8
index f5edfec..a0fc04c 100644
--- a/src/man/wg.8
+++ b/src/man/wg.8
@@ -55,7 +55,7 @@ transfer-rx, transfer-tx, persistent-keepalive.
Shows the current configuration of \fI<interface>\fP in the format described
by \fICONFIGURATION FILE FORMAT\fP below.
.TP
-\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]...] ]...
+\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]...] ]...
Sets configuration values for the specified \fI<interface>\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
@@ -72,7 +72,11 @@ the device. The use of \fIpreshared-key\fP is optional, and may be omitted;
it adds an additional layer of symmetric-key cryptography to be mixed into
the already existing public-key cryptography, for post-quantum resistance.
If \fIallowed-ips\fP is specified, but the value is the empty string, all
-allowed ips are removed from the peer. The use of \fIpersistent-keepalive\fP
+allowed ips are removed from the peer. By default, \fIallowed-ips\fP replaces
+a peer's allowed ips. If + or - is prepended to any of the ips then
+the update is incremental; ips prefixed with '+' or '' are added to the peer's
+allowed ips if not present while ips prefixed with '-' are removed if present.
+The use of \fIpersistent-keepalive\fP
is optional and is by default off; setting it to 0 or "off" disables it.
Otherwise it represents, in seconds, between 1 and 65535 inclusive, how often
to send an authenticated empty packet to the peer, for the purpose of keeping
@@ -219,7 +223,14 @@ by running as root:
\fB # modprobe wireguard && echo module wireguard +p > /sys/kernel/debug/dynamic_debug/control\fP
-On userspace implementations, it is customary to set the \fILOG_LEVEL\fP environment variable to \fIdebug\fP.
+On OpenBSD and FreeBSD, debugging information can be written into
+.BR dmesg (1)
+on a per-interface basis by using
+.BR ifconfig (1):
+
+\fB # ifconfig wg0 debug
+
+On userspace implementations, it is customary to set the \fILOG_LEVEL\fP environment variable to \fIverbose\fP.
.SH ENVIRONMENT VARIABLES
.TP
@@ -233,6 +244,7 @@ If set to \fInever\fP, then the pretty-printing \fBshow\fP sub-command will show
If set to an integer or to \fIinfinity\fP, DNS resolution for each peer's endpoint will be retried that many times for non-permanent errors, with an increasing delay between retries. If unset, the default is 15 retries.
.SH SEE ALSO
+.BR wg-quick (8),
.BR ip (8),
.BR ip-link (8),
.BR ip-address (8),
diff --git a/src/pubkey.c b/src/pubkey.c
index 19235fc..f191592 100644
--- a/src/pubkey.c
+++ b/src/pubkey.c
@@ -1,17 +1,17 @@
-// SPDX-License-Identifier: GPL-2.0
+// SPDX-License-Identifier: GPL-2.0 OR MIT
/*
* Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*/
#include <errno.h>
#include <stdio.h>
-#include <ctype.h>
#include "curve25519.h"
#include "encoding.h"
#include "subcommands.h"
+#include "ctype.h"
-int pubkey_main(int argc, char *argv[])
+int pubkey_main(int argc, const char *argv[])
{
uint8_t key[WG_KEY_LEN] __attribute__((aligned(sizeof(uintptr_t))));
char base64[WG_KEY_LEN_BASE64];
@@ -31,7 +31,7 @@ int pubkey_main(int argc, char *argv[])
for (;;) {
trailing_char = getc(stdin);
- if (!trailing_char || isspace(trailing_char) || isblank(trailing_char))
+ if (!trailing_char || char_is_space(trailing_char))
continue;
if (trailing_char == EOF)
break;
diff --git a/src/set.c b/src/set.c
index 0a98f7b..992ffa2 100644
--- a/src/set.c
+++ b/src/set.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: GPL-2.0
+// SPDX-License-Identifier: GPL-2.0 OR MIT
/*
* Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*/
@@ -12,13 +12,13 @@
#include "ipc.h"
#include "subcommands.h"
-int set_main(int argc, char *argv[])
+int set_main(int argc, const char *argv[])
{
struct wgdevice *device = NULL;
int ret = 1;
if (argc < 3) {
- 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]);
+ 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]);
return 1;
}
diff --git a/src/setconf.c b/src/setconf.c
index 89b3023..4f830a4 100644
--- a/src/setconf.c
+++ b/src/setconf.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: GPL-2.0
+// SPDX-License-Identifier: GPL-2.0 OR MIT
/*
* Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*/
@@ -13,15 +13,15 @@
#include "ipc.h"
#include "subcommands.h"
-struct pubkey_origin {
- uint8_t *pubkey;
+struct peer_origin {
+ struct wgpeer *peer;
bool from_file;
};
-static int pubkey_cmp(const void *first, const void *second)
+static int peer_cmp(const void *first, const void *second)
{
- const struct pubkey_origin *a = first, *b = second;
- int ret = memcmp(a->pubkey, b->pubkey, WG_KEY_LEN);
+ const struct peer_origin *a = first, *b = second;
+ int ret = memcmp(a->peer->public_key, b->peer->public_key, WG_KEY_LEN);
if (ret)
return ret;
return a->from_file - b->from_file;
@@ -31,7 +31,7 @@ static bool sync_conf(struct wgdevice *file)
{
struct wgdevice *runtime;
struct wgpeer *peer;
- struct pubkey_origin *pubkeys;
+ struct peer_origin *peers;
size_t peer_count = 0, i = 0;
if (!file->first_peer)
@@ -55,50 +55,55 @@ static bool sync_conf(struct wgdevice *file)
for_each_wgpeer(runtime, peer)
++peer_count;
- pubkeys = calloc(peer_count, sizeof(*pubkeys));
- if (!pubkeys) {
+ peers = calloc(peer_count, sizeof(*peers));
+ if (!peers) {
free_wgdevice(runtime);
- perror("Public key allocation");
+ perror("Peer list allocation");
return false;
}
for_each_wgpeer(file, peer) {
- pubkeys[i].pubkey = peer->public_key;
- pubkeys[i].from_file = true;
+ peers[i].peer = peer;
+ peers[i].from_file = true;
++i;
}
for_each_wgpeer(runtime, peer) {
- pubkeys[i].pubkey = peer->public_key;
- pubkeys[i].from_file = false;
+ peers[i].peer = peer;
+ peers[i].from_file = false;
++i;
}
- qsort(pubkeys, peer_count, sizeof(*pubkeys), pubkey_cmp);
+ qsort(peers, peer_count, sizeof(*peers), peer_cmp);
for (i = 0; i < peer_count; ++i) {
- if (pubkeys[i].from_file)
+ if (peers[i].from_file)
continue;
- if (i == peer_count - 1 || !pubkeys[i + 1].from_file || memcmp(pubkeys[i].pubkey, pubkeys[i + 1].pubkey, WG_KEY_LEN)) {
+ if (i == peer_count - 1 || !peers[i + 1].from_file || memcmp(peers[i].peer->public_key, peers[i + 1].peer->public_key, WG_KEY_LEN)) {
peer = calloc(1, sizeof(struct wgpeer));
if (!peer) {
free_wgdevice(runtime);
- free(pubkeys);
+ free(peers);
perror("Peer allocation");
return false;
}
peer->flags = WGPEER_REMOVE_ME;
- memcpy(peer->public_key, pubkeys[i].pubkey, WG_KEY_LEN);
+ memcpy(peer->public_key, peers[i].peer->public_key, WG_KEY_LEN);
peer->next_peer = file->first_peer;
file->first_peer = peer;
if (!file->last_peer)
file->last_peer = peer;
+ } else if (i < peer_count - 1 && peers[i + 1].from_file &&
+ (peers[i].peer->flags & WGPEER_HAS_PRESHARED_KEY) && !(peers[i + 1].peer->flags & WGPEER_HAS_PRESHARED_KEY) &&
+ !memcmp(peers[i].peer->public_key, peers[i + 1].peer->public_key, WG_KEY_LEN)) {
+ memset(peers[i + 1].peer->preshared_key, 0, WG_KEY_LEN);
+ peers[i + 1].peer->flags |= WGPEER_HAS_PRESHARED_KEY;
}
}
free_wgdevice(runtime);
- free(pubkeys);
+ free(peers);
return true;
}
-int setconf_main(int argc, char *argv[])
+int setconf_main(int argc, const char *argv[])
{
struct wgdevice *device = NULL;
struct config_ctx ctx;
diff --git a/src/show.c b/src/show.c
index e772339..13777cf 100644
--- a/src/show.c
+++ b/src/show.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: GPL-2.0
+// SPDX-License-Identifier: GPL-2.0 OR MIT
/*
* Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*/
@@ -27,7 +27,7 @@
static int peer_cmp(const void *first, const void *second)
{
time_t diff;
- const struct wgpeer *a = *(const void **)first, *b = *(const void **)second;
+ const struct wgpeer *a = *(void *const *)first, *b = *(void *const *)second;
if (!a->last_handshake_time.tv_sec && !a->last_handshake_time.tv_nsec && (b->last_handshake_time.tv_sec || b->last_handshake_time.tv_nsec))
return 1;
@@ -75,14 +75,14 @@ static char *key(const uint8_t key[static WG_KEY_LEN])
return base64;
}
-static char *maybe_key(const uint8_t maybe_key[static WG_KEY_LEN], bool have_it)
+static const char *maybe_key(const uint8_t maybe_key[static WG_KEY_LEN], bool have_it)
{
if (!have_it)
return "(none)";
return key(maybe_key);
}
-static char *masked_key(const uint8_t masked_key[static WG_KEY_LEN])
+static const char *masked_key(const uint8_t masked_key[static WG_KEY_LEN])
{
const char *var = getenv("WG_HIDE_KEYS");
@@ -312,9 +312,9 @@ static bool ugly_print(struct wgdevice *device, const char *param, bool with_int
else
printf("off\n");
} else if (!strcmp(param, "endpoints")) {
- if (with_interface)
- printf("%s\t", device->name);
for_each_wgpeer(device, peer) {
+ if (with_interface)
+ printf("%s\t", device->name);
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));
@@ -376,7 +376,7 @@ static bool ugly_print(struct wgdevice *device, const char *param, bool with_int
return true;
}
-int show_main(int argc, char *argv[])
+int show_main(int argc, const char *argv[])
{
int ret = 0;
diff --git a/src/showconf.c b/src/showconf.c
index 6e6a4a5..62070dc 100644
--- a/src/showconf.c
+++ b/src/showconf.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: GPL-2.0
+// SPDX-License-Identifier: GPL-2.0 OR MIT
/*
* Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*/
@@ -18,7 +18,7 @@
#include "ipc.h"
#include "subcommands.h"
-int showconf_main(int argc, char *argv[])
+int showconf_main(int argc, const char *argv[])
{
char base64[WG_KEY_LEN_BASE64];
char ip[INET6_ADDRSTRLEN];
diff --git a/src/subcommands.h b/src/subcommands.h
index 68e9334..4308b5b 100644
--- a/src/subcommands.h
+++ b/src/subcommands.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0 */
+/* SPDX-License-Identifier: GPL-2.0 OR MIT */
/*
* Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*/
@@ -7,11 +7,11 @@
#define SUBCOMMANDS_H
extern const char *PROG_NAME;
-int show_main(int argc, char *argv[]);
-int showconf_main(int argc, char *argv[]);
-int set_main(int argc, char *argv[]);
-int setconf_main(int argc, char *argv[]);
-int genkey_main(int argc, char *argv[]);
-int pubkey_main(int argc, char *argv[]);
+int show_main(int argc, const char *argv[]);
+int showconf_main(int argc, const char *argv[]);
+int set_main(int argc, const char *argv[]);
+int setconf_main(int argc, const char *argv[]);
+int genkey_main(int argc, const char *argv[]);
+int pubkey_main(int argc, const char *argv[]);
#endif
diff --git a/src/systemd/wg-quick.target b/src/systemd/wg-quick.target
new file mode 100644
index 0000000..c209890
--- /dev/null
+++ b/src/systemd/wg-quick.target
@@ -0,0 +1,2 @@
+[Unit]
+Description=WireGuard Tunnels via wg-quick(8)
diff --git a/src/systemd/wg-quick@.service b/src/systemd/wg-quick@.service
index 7c5f9d1..dbdab44 100644
--- a/src/systemd/wg-quick@.service
+++ b/src/systemd/wg-quick@.service
@@ -2,6 +2,7 @@
Description=WireGuard via wg-quick(8) for %I
After=network-online.target nss-lookup.target
Wants=network-online.target nss-lookup.target
+PartOf=wg-quick.target
Documentation=man:wg-quick(8)
Documentation=man:wg(8)
Documentation=https://www.wireguard.com/
@@ -14,6 +15,7 @@ Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/wg-quick up %i
ExecStop=/usr/bin/wg-quick down %i
+ExecReload=/bin/bash -c 'exec /usr/bin/wg syncconf %i <(exec /usr/bin/wg-quick strip %i)'
Environment=WG_ENDPOINT_RESOLUTION_RETRIES=infinity
[Install]
diff --git a/src/terminal.c b/src/terminal.c
index ba88597..d3e6611 100644
--- a/src/terminal.c
+++ b/src/terminal.c
@@ -1,9 +1,8 @@
-// SPDX-License-Identifier: GPL-2.0
+// SPDX-License-Identifier: GPL-2.0 OR MIT
/*
* Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*/
-#include <ctype.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
@@ -11,8 +10,10 @@
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
+#include "ctype.h"
+#include "terminal.h"
-static bool color_mode(FILE *file)
+static bool color_mode(void)
{
static int mode = -1;
const char *var;
@@ -25,17 +26,17 @@ static bool color_mode(FILE *file)
else if (var && !strcmp(var, "never"))
mode = false;
else
- return isatty(fileno(file));
+ mode = isatty(fileno(stdout));
return mode;
}
-static void filter_ansi(FILE *file, const char *fmt, va_list args)
+static void filter_ansi(const char *fmt, va_list args)
{
char *str = NULL;
size_t len, i, j;
- if (color_mode(file)) {
- vfprintf(file, fmt, args);
+ if (color_mode()) {
+ vfprintf(stdout, fmt, args);
return;
}
@@ -46,7 +47,7 @@ static void filter_ansi(FILE *file, const char *fmt, va_list args)
if (str[i] == '\x1b' && str[i + 1] == '[') {
str[i] = str[i + 1] = '\0';
for (j = i + 2; j < len; ++j) {
- if (isalpha(str[j]))
+ if (char_is_alpha(str[j]))
break;
str[j] = '\0';
}
@@ -55,7 +56,7 @@ static void filter_ansi(FILE *file, const char *fmt, va_list args)
}
}
for (i = 0; i < len; i = j) {
- fputs(&str[i], file);
+ fputs(&str[i], stdout);
for (j = i + strlen(&str[i]); j < len; ++j) {
if (str[j] != '\0')
break;
@@ -70,15 +71,6 @@ void terminal_printf(const char *fmt, ...)
va_list args;
va_start(args, fmt);
- filter_ansi(stdout, fmt, args);
- va_end(args);
-}
-
-void terminal_fprintf(FILE *file, const char *fmt, ...)
-{
- va_list args;
-
- va_start(args, fmt);
- filter_ansi(file, fmt, args);
+ filter_ansi(fmt, args);
va_end(args);
}
diff --git a/src/terminal.h b/src/terminal.h
index e8cb570..50b1686 100644
--- a/src/terminal.h
+++ b/src/terminal.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0 */
+/* SPDX-License-Identifier: GPL-2.0 OR MIT */
/*
* Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*/
@@ -47,6 +47,5 @@
#define TERMINAL_CLEAR_ALL "\x1b[2J"
void terminal_printf(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
-void terminal_fprintf(FILE *file, const char *fmt, ...) __attribute__((format(printf, 2, 3)));
#endif
diff --git a/src/uapi/freebsd/dev/wg/if_wg.h b/src/uapi/freebsd/dev/wg/if_wg.h
new file mode 100644
index 0000000..a4b3b13
--- /dev/null
+++ b/src/uapi/freebsd/dev/wg/if_wg.h
@@ -0,0 +1,16 @@
+#ifndef __IF_WG_H__
+#define __IF_WG_H__
+
+#include <net/if.h>
+#include <netinet/in.h>
+
+struct wg_data_io {
+ char wgd_name[IFNAMSIZ];
+ void *wgd_data;
+ size_t wgd_size;
+};
+
+#define SIOCSWG _IOWR('i', 210, struct wg_data_io)
+#define SIOCGWG _IOWR('i', 211, struct wg_data_io)
+
+#endif
diff --git a/src/uapi/linux/wireguard.h b/src/uapi/linux/linux/wireguard.h
index 0efd52c..6ca266a 100644
--- a/src/uapi/linux/wireguard.h
+++ b/src/uapi/linux/linux/wireguard.h
@@ -101,6 +101,10 @@
* WGALLOWEDIP_A_FAMILY: NLA_U16
* WGALLOWEDIP_A_IPADDR: struct in_addr or struct in6_addr
* WGALLOWEDIP_A_CIDR_MASK: NLA_U8
+ * WGALLOWEDIP_A_FLAGS: NLA_U32, WGALLOWEDIP_F_REMOVE_ME if
+ * the specified IP should be removed;
+ * otherwise, this IP will be added if
+ * it is not already present.
* 0: NLA_NESTED
* ...
* 0: NLA_NESTED
@@ -184,11 +188,16 @@ enum wgpeer_attribute {
};
#define WGPEER_A_MAX (__WGPEER_A_LAST - 1)
+enum wgallowedip_flag {
+ WGALLOWEDIP_F_REMOVE_ME = 1U << 0,
+ __WGALLOWEDIP_F_ALL = WGALLOWEDIP_F_REMOVE_ME
+};
enum wgallowedip_attribute {
WGALLOWEDIP_A_UNSPEC,
WGALLOWEDIP_A_FAMILY,
WGALLOWEDIP_A_IPADDR,
WGALLOWEDIP_A_CIDR_MASK,
+ WGALLOWEDIP_A_FLAGS,
__WGALLOWEDIP_A_LAST
};
#define WGALLOWEDIP_A_MAX (__WGALLOWEDIP_A_LAST - 1)
diff --git a/src/uapi/openbsd/net/if_wg.h b/src/uapi/openbsd/net/if_wg.h
new file mode 100644
index 0000000..bd33a88
--- /dev/null
+++ b/src/uapi/openbsd/net/if_wg.h
@@ -0,0 +1,92 @@
+/* SPDX-License-Identifier: ISC */
+/*
+ * Copyright (C) 2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
+ * Copyright (c) 2020 Matt Dunwoodie <ncon@noconroy.net>
+ */
+
+#ifndef __IF_WG_H__
+#define __IF_WG_H__
+
+#include <sys/limits.h>
+#include <sys/errno.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+
+
+/*
+ * This is the public interface to the WireGuard network interface.
+ *
+ * It is designed to be used by tools such as ifconfig(8) and wg(8).
+ */
+
+#define WG_KEY_LEN 32
+
+#define SIOCSWG _IOWR('i', 210, struct wg_data_io)
+#define SIOCGWG _IOWR('i', 211, struct wg_data_io)
+
+#define a_ipv4 a_addr.addr_ipv4
+#define a_ipv6 a_addr.addr_ipv6
+
+struct wg_aip_io {
+ sa_family_t a_af;
+ int a_cidr;
+ union wg_aip_addr {
+ struct in_addr addr_ipv4;
+ struct in6_addr addr_ipv6;
+ } a_addr;
+};
+
+#define WG_PEER_HAS_PUBLIC (1 << 0)
+#define WG_PEER_HAS_PSK (1 << 1)
+#define WG_PEER_HAS_PKA (1 << 2)
+#define WG_PEER_HAS_ENDPOINT (1 << 3)
+#define WG_PEER_REPLACE_AIPS (1 << 4)
+#define WG_PEER_REMOVE (1 << 5)
+#define WG_PEER_UPDATE (1 << 6)
+
+#define p_sa p_endpoint.sa_sa
+#define p_sin p_endpoint.sa_sin
+#define p_sin6 p_endpoint.sa_sin6
+
+struct wg_peer_io {
+ int p_flags;
+ int p_protocol_version;
+ uint8_t p_public[WG_KEY_LEN];
+ uint8_t p_psk[WG_KEY_LEN];
+ uint16_t p_pka;
+ union wg_peer_endpoint {
+ struct sockaddr sa_sa;
+ struct sockaddr_in sa_sin;
+ struct sockaddr_in6 sa_sin6;
+ } p_endpoint;
+ uint64_t p_txbytes;
+ uint64_t p_rxbytes;
+ struct timespec p_last_handshake; /* nanotime */
+ size_t p_aips_count;
+ struct wg_aip_io p_aips[];
+};
+
+#define WG_INTERFACE_HAS_PUBLIC (1 << 0)
+#define WG_INTERFACE_HAS_PRIVATE (1 << 1)
+#define WG_INTERFACE_HAS_PORT (1 << 2)
+#define WG_INTERFACE_HAS_RTABLE (1 << 3)
+#define WG_INTERFACE_REPLACE_PEERS (1 << 4)
+
+struct wg_interface_io {
+ uint8_t i_flags;
+ in_port_t i_port;
+ int i_rtable;
+ uint8_t i_public[WG_KEY_LEN];
+ uint8_t i_private[WG_KEY_LEN];
+ size_t i_peers_count;
+ struct wg_peer_io i_peers[];
+};
+
+struct wg_data_io {
+ char wgd_name[IFNAMSIZ];
+ size_t wgd_size; /* total size of the memory pointed to by wgd_interface */
+ struct wg_interface_io *wgd_interface;
+};
+
+#endif /* __IF_WG_H__ */
diff --git a/src/uapi/windows/wireguard.h b/src/uapi/windows/wireguard.h
new file mode 100644
index 0000000..5c5938e
--- /dev/null
+++ b/src/uapi/windows/wireguard.h
@@ -0,0 +1,80 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Copyright (C) 2021 WireGuard LLC. All Rights Reserved.
+ */
+
+#ifndef _WIREGUARD_NT_H
+#define _WIREGUARD_NT_H
+
+#include <ntdef.h>
+#include <ws2def.h>
+#include <ws2ipdef.h>
+#include <inaddr.h>
+#include <in6addr.h>
+
+#define WG_KEY_LEN 32
+
+typedef struct _WG_IOCTL_ALLOWED_IP
+{
+ union
+ {
+ IN_ADDR V4;
+ IN6_ADDR V6;
+ } Address;
+ ADDRESS_FAMILY AddressFamily;
+ UCHAR Cidr;
+} __attribute__((aligned(8))) WG_IOCTL_ALLOWED_IP;
+
+typedef enum
+{
+ WG_IOCTL_PEER_HAS_PUBLIC_KEY = 1 << 0,
+ WG_IOCTL_PEER_HAS_PRESHARED_KEY = 1 << 1,
+ WG_IOCTL_PEER_HAS_PERSISTENT_KEEPALIVE = 1 << 2,
+ WG_IOCTL_PEER_HAS_ENDPOINT = 1 << 3,
+ WG_IOCTL_PEER_HAS_PROTOCOL_VERSION = 1 << 4,
+ WG_IOCTL_PEER_REPLACE_ALLOWED_IPS = 1 << 5,
+ WG_IOCTL_PEER_REMOVE = 1 << 6,
+ WG_IOCTL_PEER_UPDATE = 1 << 7
+} WG_IOCTL_PEER_FLAG;
+
+typedef struct _WG_IOCTL_PEER
+{
+ WG_IOCTL_PEER_FLAG Flags;
+ ULONG ProtocolVersion; /* 0 = latest protocol, 1 = this protocol. */
+ UCHAR PublicKey[WG_KEY_LEN];
+ UCHAR PresharedKey[WG_KEY_LEN];
+ USHORT PersistentKeepalive;
+ SOCKADDR_INET Endpoint;
+ ULONG64 TxBytes;
+ ULONG64 RxBytes;
+ ULONG64 LastHandshake;
+ ULONG AllowedIPsCount;
+} __attribute__((aligned(8))) WG_IOCTL_PEER;
+
+typedef enum
+{
+ WG_IOCTL_INTERFACE_HAS_PUBLIC_KEY = 1 << 0,
+ WG_IOCTL_INTERFACE_HAS_PRIVATE_KEY = 1 << 1,
+ WG_IOCTL_INTERFACE_HAS_LISTEN_PORT = 1 << 2,
+ WG_IOCTL_INTERFACE_REPLACE_PEERS = 1 << 3
+} WG_IOCTL_INTERFACE_FLAG;
+
+typedef struct _WG_IOCTL_INTERFACE
+{
+ WG_IOCTL_INTERFACE_FLAG Flags;
+ USHORT ListenPort;
+ UCHAR PrivateKey[WG_KEY_LEN];
+ UCHAR PublicKey[WG_KEY_LEN];
+ ULONG PeersCount;
+} __attribute__((aligned(8))) WG_IOCTL_INTERFACE;
+
+#define WG_IOCTL_GET CTL_CODE(45208U, 321, METHOD_OUT_DIRECT, FILE_READ_DATA | FILE_WRITE_DATA)
+#define WG_IOCTL_SET CTL_CODE(45208U, 322, METHOD_IN_DIRECT, FILE_READ_DATA | FILE_WRITE_DATA)
+
+#define DEVPKEY_WG_NAME (DEVPROPKEY) { \
+ { 0x65726957, 0x7547, 0x7261, { 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x4b, 0x65, 0x79 } }, \
+ DEVPROPID_FIRST_USABLE + 1 \
+ }
+
+
+#endif
diff --git a/src/version.h b/src/version.h
index c1782f6..0a7ef8d 100644
--- a/src/version.h
+++ b/src/version.h
@@ -1,3 +1,3 @@
#ifndef WIREGUARD_TOOLS_VERSION
-#define WIREGUARD_TOOLS_VERSION "1.0.20200206"
+#define WIREGUARD_TOOLS_VERSION "1.0.20250521"
#endif
diff --git a/src/wg-quick/android.c b/src/wg-quick/android.c
index 54ea81c..3ed05e5 100644
--- a/src/wg-quick/android.c
+++ b/src/wg-quick/android.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
/*
- * Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
+ * Copyright (C) 2015-2021 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*
* This is a shell script written in C. It very intentionally still functions like
* a shell script, calling out to external executables such as ip(8).
@@ -25,6 +25,7 @@
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/param.h>
+#include <sys/system_properties.h>
#ifndef WG_PACKAGE_NAME
#define WG_PACKAGE_NAME "com.wireguard.android"
@@ -39,6 +40,7 @@
static bool is_exiting = false;
static bool binder_available = false;
+static unsigned int sdk_version;
static void *xmalloc(size_t size)
{
@@ -726,8 +728,8 @@ static void up_if(unsigned int *netid, const char *iface, uint16_t listen_port)
cmd("iptables -I INPUT 1 -p udp --dport %u -j ACCEPT -m comment --comment \"wireguard rule %s\"", listen_port, iface);
cmd("ip6tables -I INPUT 1 -p udp --dport %u -j %s -m comment --comment \"wireguard rule %s\"", listen_port, should_block_ipv6(iface) ? "DROP" : "ACCEPT", iface);
}
- cndc("interface setcfg %s up", iface);
- cndc("network create %u vpn 1 1", *netid);
+ cmd("ip link set up dev %s", iface);
+ cndc(sdk_version < 31 ? "network create %u vpn 1 1" : "network create %u vpn 1", *netid);
cndc("network interface add %u %s", *netid, iface);
}
@@ -782,31 +784,50 @@ static uid_t *get_uid_list(const char *selected_applications)
return uid_list;
}
-static void set_users(unsigned int netid, const char *excluded_applications)
+static void set_users(unsigned int netid, const char *excluded_applications, const char *included_applications)
{
- _cleanup_free_ uid_t *excluded_uids = get_uid_list(excluded_applications);
+ _cleanup_free_ uid_t *uids = NULL;
+ uid_t *uid;
unsigned int args_per_command = 0;
_cleanup_free_ char *ranges = NULL;
char range[22];
uid_t start;
- for (start = 0; *excluded_uids; start = *excluded_uids + 1, ++excluded_uids) {
- if (start > *excluded_uids - 1)
- continue;
- else if (start == *excluded_uids - 1)
- snprintf(range, sizeof(range), "%u", start);
- else
- snprintf(range, sizeof(range), "%u-%u", start, *excluded_uids - 1);
- ranges = concat_and_free(ranges, " ", range);
- if (++args_per_command % 18 == 0) {
- cndc("network users add %u %s", netid, ranges);
- free(ranges);
- ranges = NULL;
- }
+ if (excluded_applications && included_applications) {
+ fprintf(stderr, "Error: only one of ExcludedApplications and IncludedApplications may be specified, but not both\n");
+ exit(EEXIST);
}
- if (start < 99999) {
- snprintf(range, sizeof(range), "%u-99999", start);
- ranges = concat_and_free(ranges, " ", range);
+
+ if (excluded_applications || !included_applications) {
+ uid = uids = get_uid_list(excluded_applications);
+ for (start = 0; *uid; start = *uid + 1, ++uid) {
+ if (start > *uid - 1)
+ continue;
+ else if (start == *uid - 1)
+ snprintf(range, sizeof(range), "%u", start);
+ else
+ snprintf(range, sizeof(range), "%u-%u", start, *uid - 1);
+ ranges = concat_and_free(ranges, " ", range);
+ if (++args_per_command % 18 == 0) {
+ cndc("network users add %u %s", netid, ranges);
+ free(ranges);
+ ranges = NULL;
+ }
+ }
+ if (start < 99999) {
+ snprintf(range, sizeof(range), "%u-99999", start);
+ ranges = concat_and_free(ranges, " ", range);
+ }
+ } else {
+ for (uid = uids = get_uid_list(included_applications); *uid; ++uid) {
+ snprintf(range, sizeof(range), "%u", *uid);
+ ranges = concat_and_free(ranges, " ", range);
+ if (++args_per_command % 18 == 0) {
+ cndc("network users add %u %s", netid, ranges);
+ free(ranges);
+ ranges = NULL;
+ }
+ }
}
if (ranges)
@@ -819,37 +840,49 @@ static void set_dnses(unsigned int netid, const char *dnses)
if (len > (1<<16))
return;
_cleanup_free_ char *mutable = xstrdup(dnses);
- _cleanup_free_ char *shell_arglist = xmalloc(len * 4 + 1);
- _cleanup_free_ char *function_arglist = xmalloc(len * 4 + 1);
+ _cleanup_free_ char *dns_shell_arglist = xmalloc(len * 4 + 1);
+ _cleanup_free_ char *dns_search_shell_arglist = xmalloc(len * 4 + 1);
+ _cleanup_free_ char *dns_function_arglist = xmalloc(len * 4 + 1);
+ _cleanup_free_ char *dns_search_function_arglist = xmalloc(len * 4 + 1);
_cleanup_free_ char *arg = xmalloc(len + 4);
_cleanup_free_ char **dns_list = NULL;
+ _cleanup_free_ char **dns_search_list = NULL;
_cleanup_binder_ AIBinder *handle = NULL;
- size_t dns_list_size = 0;
+ _cleanup_regfree_ regex_t regex_ipnothost = { 0 };
+ size_t dns_list_size = 0, dns_search_list_size = 0;
+ bool is_ip;
if (!len)
return;
+
+ xregcomp(&regex_ipnothost, "(^[0-9.]+$)|(^.*:.*$)", REG_EXTENDED | REG_NOSUB);
for (char *dns = strtok(mutable, ", \t\n"); dns; dns = strtok(NULL, ", \t\n")) {
if (strchr(dns, '\'') || strchr(dns, '\\'))
continue;
- ++dns_list_size;
+ ++*(!regexec(&regex_ipnothost, dns, 0, NULL, 0) ? &dns_list_size : &dns_search_list_size);
}
if (!dns_list_size)
return;
dns_list = xcalloc(dns_list_size + 1, sizeof(*dns_list));
+ dns_search_list = xcalloc(dns_search_list_size + 1, sizeof(*dns_search_list));
free(mutable);
mutable = xstrdup(dnses);
- shell_arglist[0] = '\0';
- function_arglist[0] = '\0';
+ dns_shell_arglist[0] = '\0';
+ dns_search_shell_arglist[0] = '\0';
+ dns_function_arglist[0] = '\0';
+ dns_search_function_arglist[0] = '\0';
dns_list_size = 0;
+ dns_search_list_size = 0;
for (char *dns = strtok(mutable, ", \t\n"); dns; dns = strtok(NULL, ", \t\n")) {
if (strchr(dns, '\'') || strchr(dns, '\\'))
continue;
+ is_ip = !regexec(&regex_ipnothost, dns, 0, NULL, 0);
snprintf(arg, len + 3, "'%s' ", dns);
- strncat(shell_arglist, arg, len * 4 - 1);
- snprintf(arg, len + 2, function_arglist[0] == '\0' ? "%s" : ", %s", dns);
- strncat(function_arglist, arg, len * 4 - 1);
- dns_list[dns_list_size++] = dns;
+ strncat(is_ip ? dns_shell_arglist : dns_search_shell_arglist, arg, len * 4 - 1);
+ snprintf(arg, len + 2, (is_ip ? dns_function_arglist[0] : dns_search_function_arglist[0]) == '\0' ? "%s" : ", %s", dns);
+ strncat(is_ip ? dns_function_arglist : dns_search_function_arglist, arg, len * 4 - 1);
+ *(is_ip ? &dns_list[dns_list_size++] : &dns_search_list[dns_search_list_size++]) = dns;
}
if ((handle = dnsresolver_get_handle())) {
@@ -871,15 +904,16 @@ static void set_dnses(unsigned int netid, const char *dnses)
.base_timeout_msec = DNSRESOLVER_BASE_TIMEOUT,
.retry_count = DNSRESOLVER_RETRY_COUNT,
.servers = dns_list,
- .domains = (char *[]){NULL},
+ .domains = dns_search_list,
.tls_name = "",
.tls_servers = (char *[]){NULL},
.tls_fingerprints = (char *[]){NULL}
};
- printf("[#] <binder>::dnsResolver->setResolverConfiguration(%u, [%s], [], %d, %d, %d, %d, %d, %d, [], [])\n",
- netid, function_arglist, DNSRESOLVER_SAMPLE_VALIDITY, DNSRESOLVER_SUCCESS_THRESHOLD,
- DNSRESOLVER_MIN_SAMPLES, DNSRESOLVER_MAX_SAMPLES, DNSRESOLVER_BASE_TIMEOUT, DNSRESOLVER_RETRY_COUNT);
+ printf("[#] <binder>::dnsResolver->setResolverConfiguration(%u, [%s], [%s], %d, %d, %d, %d, %d, %d, [], [])\n",
+ netid, dns_function_arglist, dns_search_function_arglist, DNSRESOLVER_SAMPLE_VALIDITY,
+ DNSRESOLVER_SUCCESS_THRESHOLD, DNSRESOLVER_MIN_SAMPLES, DNSRESOLVER_MAX_SAMPLES,
+ DNSRESOLVER_BASE_TIMEOUT, DNSRESOLVER_RETRY_COUNT);
status = dnsresolver_set_resolver_configuration(handle, &params);
if (status != 0) {
@@ -887,7 +921,7 @@ static void set_dnses(unsigned int netid, const char *dnses)
exit(ENONET);
}
} else
- cndc("resolver setnetdns %u '' %s", netid, shell_arglist);
+ cndc("resolver setnetdns %u '%s' %s", netid, dns_search_shell_arglist, dns_shell_arglist);
}
static void add_addr(const char *iface, const char *addr)
@@ -1013,7 +1047,7 @@ static void set_routes(const char *iface, unsigned int netid)
static void set_config(const char *iface, const char *config)
{
FILE *config_writer;
- _cleanup_free_ char *cmd = concat("wg setconf ", iface, " /proc/self/fd/0", NULL);
+ _cleanup_free_ char *cmd = concat("wg addconf ", iface, " /proc/self/fd/0", NULL);
int ret;
printf("[#] %s\n", cmd);
@@ -1063,7 +1097,8 @@ static void cmd_usage(const char *program)
" IP addresses (with an optional CIDR mask) to be set for the interface.\n"
" - MTU: an optional MTU for the interface; if unspecified, auto-calculated.\n"
" - DNS: an optional DNS server to use while the device is up.\n"
- " - ExcludedApplications: optional applications to exclude from the tunnel.\n\n"
+ " - ExcludedApplications: optional blacklist of applications to exclude from the tunnel.\n\n"
+ " - IncludedApplications: optional whitelist of applications to include in the tunnel.\n\n"
" See wg-quick(8) for more info and examples.\n");
}
@@ -1077,7 +1112,7 @@ static void cmd_up_cleanup(void)
free(cleanup_iface);
}
-static void cmd_up(const char *iface, const char *config, unsigned int mtu, const char *addrs, const char *dnses, const char *excluded_applications)
+static void cmd_up(const char *iface, const char *config, unsigned int mtu, const char *addrs, const char *dnses, const char *excluded_applications, const char *included_applications)
{
DEFINE_CMD(c);
unsigned int netid = 0;
@@ -1099,7 +1134,7 @@ static void cmd_up(const char *iface, const char *config, unsigned int mtu, cons
set_dnses(netid, dnses);
set_routes(iface, netid);
set_mtu(iface, mtu);
- set_users(netid, excluded_applications);
+ set_users(netid, excluded_applications, included_applications);
broadcast_change();
free(cleanup_iface);
@@ -1131,7 +1166,7 @@ static void cmd_down(const char *iface)
exit(EXIT_SUCCESS);
}
-static void parse_options(char **iface, char **config, unsigned int *mtu, char **addrs, char **dnses, char **excluded_applications, const char *arg)
+static void parse_options(char **iface, char **config, unsigned int *mtu, char **addrs, char **dnses, char **excluded_applications, char **included_applications, const char *arg)
{
_cleanup_fclose_ FILE *file = NULL;
_cleanup_free_ char *line = NULL;
@@ -1215,6 +1250,9 @@ static void parse_options(char **iface, char **config, unsigned int *mtu, char *
} else if (!strncasecmp(clean, "ExcludedApplications=", 21) && j > 4) {
*excluded_applications = concat_and_free(*excluded_applications, ",", clean + 21);
continue;
+ } else if (!strncasecmp(clean, "IncludedApplications=", 21) && j > 4) {
+ *included_applications = concat_and_free(*included_applications, ",", clean + 21);
+ continue;
} else if (!strncasecmp(clean, "MTU=", 4) && j > 4) {
*mtu = atoi(clean + 4);
continue;
@@ -1240,17 +1278,22 @@ int main(int argc, char *argv[])
_cleanup_free_ char *addrs = NULL;
_cleanup_free_ char *dnses = NULL;
_cleanup_free_ char *excluded_applications = NULL;
+ _cleanup_free_ char *included_applications = NULL;
unsigned int mtu;
+ char prop[PROP_VALUE_MAX + 1];
+
+ if (__system_property_get("ro.build.version.sdk", prop))
+ sdk_version = atoi(prop);
if (argc == 2 && (!strcmp(argv[1], "help") || !strcmp(argv[1], "--help") || !strcmp(argv[1], "-h")))
cmd_usage(argv[0]);
else if (argc == 3 && !strcmp(argv[1], "up")) {
auto_su(argc, argv);
- parse_options(&iface, &config, &mtu, &addrs, &dnses, &excluded_applications, argv[2]);
- cmd_up(iface, config, mtu, addrs, dnses, excluded_applications);
+ parse_options(&iface, &config, &mtu, &addrs, &dnses, &excluded_applications, &included_applications, argv[2]);
+ cmd_up(iface, config, mtu, addrs, dnses, excluded_applications, included_applications);
} else if (argc == 3 && !strcmp(argv[1], "down")) {
auto_su(argc, argv);
- parse_options(&iface, &config, &mtu, &addrs, &dnses, &excluded_applications, argv[2]);
+ parse_options(&iface, &config, &mtu, &addrs, &dnses, &excluded_applications, &included_applications, argv[2]);
cmd_down(iface);
} else {
cmd_usage(argv[0]);
diff --git a/src/wg-quick/darwin.bash b/src/wg-quick/darwin.bash
index d9d07cf..1b7fe5e 100755
--- a/src/wg-quick/darwin.bash
+++ b/src/wg-quick/darwin.bash
@@ -18,6 +18,7 @@ INTERFACE=""
ADDRESSES=( )
MTU=""
DNS=( )
+DNS_SEARCH=( )
TABLE=""
PRE_UP=( )
POST_UP=( )
@@ -43,7 +44,7 @@ die() {
CONFIG_SEARCH_PATHS=( /etc/wireguard /usr/local/etc/wireguard )
parse_options() {
- local interface_section=0 line key value stripped path
+ local interface_section=0 line key value stripped path v
CONFIG_FILE="$1"
if [[ $CONFIG_FILE =~ ^[a-zA-Z0-9_=+.-]{1,15}$ ]]; then
for path in "${CONFIG_SEARCH_PATHS[@]}"; do
@@ -61,18 +62,21 @@ parse_options() {
stripped="${line%%\#*}"
key="${stripped%%=*}"; key="${key##*([[:space:]])}"; key="${key%%*([[:space:]])}"
value="${stripped#*=}"; value="${value##*([[:space:]])}"; value="${value%%*([[:space:]])}"
+ unstripped_value="${line#*=}"; unstripped_value="${unstripped_value##*([[:space:]])}"; unstripped_value="${unstripped_value%%*([[:space:]])}"
[[ $key == "["* ]] && interface_section=0
[[ $key == "[Interface]" ]] && interface_section=1
if [[ $interface_section -eq 1 ]]; then
case "$key" in
Address) ADDRESSES+=( ${value//,/ } ); continue ;;
MTU) MTU="$value"; continue ;;
- DNS) DNS+=( ${value//,/ } ); continue ;;
+ DNS) for v in ${value//,/ }; do
+ [[ $v =~ (^[0-9.]+$)|(^.*:.*$) ]] && DNS+=( $v ) || DNS_SEARCH+=( $v )
+ done; continue ;;
Table) TABLE="$value"; continue ;;
- PreUp) PRE_UP+=( "$value" ); continue ;;
- PreDown) PRE_DOWN+=( "$value" ); continue ;;
- PostUp) POST_UP+=( "$value" ); continue ;;
- PostDown) POST_DOWN+=( "$value" ); continue ;;
+ PreUp) PRE_UP+=( "$unstripped_value" ); continue ;;
+ PreDown) PRE_DOWN+=( "$unstripped_value" ); continue ;;
+ PostUp) POST_UP+=( "$unstripped_value" ); continue ;;
+ PostDown) POST_DOWN+=( "$unstripped_value" ); continue ;;
SaveConfig) read_bool SAVE_CONFIG "$value"; continue ;;
esac
fi
@@ -191,14 +195,14 @@ collect_gateways() {
GATEWAY4=""
while read -r destination gateway _; do
- [[ $destination == default ]] || continue
+ [[ $destination == default && $gateway != "link#"* ]] || continue
GATEWAY4="$gateway"
break
done < <(netstat -nr -f inet)
GATEWAY6=""
while read -r destination gateway _; do
- [[ $destination == default ]] || continue
+ [[ $destination == default && $gateway != "link#"* ]] || continue
GATEWAY6="$gateway"
break
done < <(netstat -nr -f inet6)
@@ -213,6 +217,7 @@ collect_endpoints() {
}
declare -A SERVICE_DNS
+declare -A SERVICE_DNS_SEARCH
collect_new_service_dns() {
local service get_response
local -A found_services
@@ -223,10 +228,16 @@ collect_new_service_dns() {
get_response="$(cmd networksetup -getdnsservers "$service")"
[[ $get_response == *" "* ]] && get_response="Empty"
[[ -n $get_response ]] && SERVICE_DNS["$service"]="$get_response"
+ get_response="$(cmd networksetup -getsearchdomains "$service")"
+ [[ $get_response == *" "* ]] && get_response="Empty"
+ [[ -n $get_response ]] && SERVICE_DNS_SEARCH["$service"]="$get_response"
done; } < <(networksetup -listallnetworkservices)
for service in "${!SERVICE_DNS[@]}"; do
- [[ -n ${found_services["$service"]} ]] || unset SERVICE_DNS["$service"]
+ if ! [[ -n ${found_services["$service"]} ]]; then
+ unset SERVICE_DNS["$service"]
+ unset SERVICE_DNS_SEARCH["$service"]
+ fi
done
}
@@ -287,7 +298,14 @@ set_dns() {
for service in "${!SERVICE_DNS[@]}"; do
while read -r response; do
[[ $response == *Error* ]] && echo "$response" >&2
- done < <(cmd networksetup -setdnsservers "$service" "${DNS[@]}")
+ done < <(
+ cmd networksetup -setdnsservers "$service" "${DNS[@]}"
+ if [[ ${#DNS_SEARCH[@]} -eq 0 ]]; then
+ cmd networksetup -setsearchdomains "$service" Empty
+ else
+ cmd networksetup -setsearchdomains "$service" "${DNS_SEARCH[@]}"
+ fi
+ )
done
}
@@ -296,7 +314,10 @@ del_dns() {
for service in "${!SERVICE_DNS[@]}"; do
while read -r response; do
[[ $response == *Error* ]] && echo "$response" >&2
- done < <(cmd networksetup -setdnsservers "$service" ${SERVICE_DNS["$service"]} || true)
+ done < <(
+ cmd networksetup -setdnsservers "$service" ${SERVICE_DNS["$service"]} || true
+ cmd networksetup -setsearchdomains "$service" ${SERVICE_DNS_SEARCH["$service"]} || true
+ )
done
}
@@ -304,22 +325,24 @@ monitor_daemon() {
echo "[+] Backgrounding route monitor" >&2
(trap 'del_routes; del_dns; exit 0' INT TERM EXIT
exec >/dev/null 2>&1
- local event pid=$BASHPID
+ exec 19< <(exec route -n monitor)
+ local event bpid=$BASHPID mpid=$!
[[ ${#DNS[@]} -gt 0 ]] && trap set_dns ALRM
# TODO: this should also check to see if the endpoint actually changes
# in response to incoming packets, and then call set_endpoint_direct_route
# then too. That function should be able to gracefully cleanup if the
# endpoints change.
- while read -r event; do
+ while read -u 19 -r event; do
[[ $event == RTM_* ]] || continue
ifconfig "$REAL_INTERFACE" >/dev/null 2>&1 || break
[[ $AUTO_ROUTE4 -eq 1 || $AUTO_ROUTE6 -eq 1 ]] && set_endpoint_direct_route
[[ -z $MTU ]] && set_mtu
if [[ ${#DNS[@]} -gt 0 ]]; then
set_dns
- sleep 2 && kill -ALRM $pid 2>/dev/null &
+ sleep 2 && kill -ALRM $bpid 2>/dev/null &
fi
- done < <(route -n monitor)) &
+ done
+ kill $mpid) &
[[ -n $LAUNCHED_BY_LAUNCHD ]] || disown
}
@@ -347,7 +370,7 @@ add_route() {
}
set_config() {
- cmd wg setconf "$REAL_INTERFACE" <(echo "$WG_CONFIG")
+ cmd wg addconf "$REAL_INTERFACE" <(echo "$WG_CONFIG")
}
save_config() {
@@ -430,8 +453,8 @@ cmd_up() {
local i
get_real_interface && die "\`$INTERFACE' already exists as \`$REAL_INTERFACE'"
trap 'del_if; del_routes; exit' INT TERM EXIT
- execute_hooks "${PRE_UP[@]}"
add_if
+ execute_hooks "${PRE_UP[@]}"
set_config
for i in "${ADDRESSES[@]}"; do
add_addr "$i"
diff --git a/src/wg-quick/freebsd.bash b/src/wg-quick/freebsd.bash
index c390dcc..aeae18d 100755
--- a/src/wg-quick/freebsd.bash
+++ b/src/wg-quick/freebsd.bash
@@ -8,6 +8,7 @@ set -e -o pipefail
shopt -s extglob
export LC_ALL=C
+exec 3>&2
SELF="$(readlink -f "${BASH_SOURCE[0]}")"
export PATH="${SELF%/*}:$PATH"
@@ -16,6 +17,7 @@ INTERFACE=""
ADDRESSES=( )
MTU=""
DNS=( )
+DNS_SEARCH=( )
TABLE=""
PRE_UP=( )
POST_UP=( )
@@ -27,7 +29,7 @@ PROGRAM="${0##*/}"
ARGS=( "$@" )
cmd() {
- echo "[#] $*" >&2
+ echo "[#] $*" >&3
"$@"
}
@@ -60,7 +62,7 @@ clean_temp() {
}
parse_options() {
- local interface_section=0 line key value stripped path
+ local interface_section=0 line key value stripped path v
CONFIG_FILE="$1"
if [[ $CONFIG_FILE =~ ^[a-zA-Z0-9_=+.-]{1,15}$ ]]; then
for path in "${CONFIG_SEARCH_PATHS[@]}"; do
@@ -78,18 +80,21 @@ parse_options() {
stripped="${line%%\#*}"
key="${stripped%%=*}"; key="${key##*([[:space:]])}"; key="${key%%*([[:space:]])}"
value="${stripped#*=}"; value="${value##*([[:space:]])}"; value="${value%%*([[:space:]])}"
+ unstripped_value="${line#*=}"; unstripped_value="${unstripped_value##*([[:space:]])}"; unstripped_value="${unstripped_value%%*([[:space:]])}"
[[ $key == "["* ]] && interface_section=0
[[ $key == "[Interface]" ]] && interface_section=1
if [[ $interface_section -eq 1 ]]; then
case "$key" in
Address) ADDRESSES+=( ${value//,/ } ); continue ;;
MTU) MTU="$value"; continue ;;
- DNS) DNS+=( ${value//,/ } ); continue ;;
+ DNS) for v in ${value//,/ }; do
+ [[ $v =~ (^[0-9.]+$)|(^.*:.*$) ]] && DNS+=( $v ) || DNS_SEARCH+=( $v )
+ done; continue ;;
Table) TABLE="$value"; continue ;;
- PreUp) PRE_UP+=( "$value" ); continue ;;
- PreDown) PRE_DOWN+=( "$value" ); continue ;;
- PostUp) POST_UP+=( "$value" ); continue ;;
- PostDown) POST_DOWN+=( "$value" ); continue ;;
+ PreUp) PRE_UP+=( "$unstripped_value" ); continue ;;
+ PreDown) PRE_DOWN+=( "$unstripped_value" ); continue ;;
+ PostUp) POST_UP+=( "$unstripped_value" ); continue ;;
+ PostDown) POST_DOWN+=( "$unstripped_value" ); continue ;;
SaveConfig) read_bool SAVE_CONFIG "$value"; continue ;;
esac
fi
@@ -111,6 +116,16 @@ auto_su() {
}
add_if() {
+ local ret rc
+ if ret="$(cmd ifconfig wg create name "$INTERFACE" 2>&1 >/dev/null)"; then
+ return 0
+ fi
+ rc=$?
+ if [[ $ret == *"ifconfig: ioctl SIOCSIFNAME (set name): File exists"* ]]; then
+ echo "$ret" >&3
+ return $rc
+ fi
+ echo "[!] Missing WireGuard kernel support ($ret). Falling back to slow userspace implementation." >&3
cmd "${WG_QUICK_USERSPACE_IMPLEMENTATION:-wireguard-go}" "$INTERFACE"
}
@@ -138,24 +153,14 @@ del_routes() {
done
}
-if_exists() {
- # HACK: The goal is simply to determine whether or not the interface exists. The
- # straight-forward way of doing this would be `ifconfig $INTERFACE`, but this
- # invokes the SIOCGIFSTATUS ioctl, which races with interface shutdown inside
- # the tun driver, resulting in a kernel panic. So we work around it the stupid
- # way by using the one utility that appears to call if_nametoindex fairly early
- # and fails if it doesn't exist: `arp`.
- if arp -i "$INTERFACE" -a -n >/dev/null 2>&1; then
- return 0
- else
- return 1
- fi
-}
-
del_if() {
[[ $HAVE_SET_DNS -eq 0 ]] || unset_dns
- cmd rm -f "/var/run/wireguard/$INTERFACE.sock"
- while if_exists; do
+ if [[ -S /var/run/wireguard/$INTERFACE.sock ]]; then
+ cmd rm -f "/var/run/wireguard/$INTERFACE.sock"
+ else
+ cmd ifconfig "$INTERFACE" destroy
+ fi
+ while ifconfig "$INTERFACE" >/dev/null 2>&1; do
# HACK: it would be nice to `route monitor` here and wait for RTM_IFANNOUNCE
# but it turns out that the announcement is made before the interface
# disappears so we sometimes get a hang. So, we're instead left with polling
@@ -172,7 +177,7 @@ add_addr() {
if [[ $1 == *:* ]]; then
cmd ifconfig "$INTERFACE" inet6 "$1" alias
else
- cmd ifconfig "$INTERFACE" inet "$1" "${1%%/*}" alias
+ cmd ifconfig "$INTERFACE" inet "$1" alias
fi
}
@@ -280,24 +285,27 @@ monitor_daemon() {
(make_temp
trap 'del_routes; clean_temp; exit 0' INT TERM EXIT
exec >/dev/null 2>&1
- local event
+ exec 19< <(exec route -n monitor)
+ local event pid=$!
# TODO: this should also check to see if the endpoint actually changes
# in response to incoming packets, and then call set_endpoint_direct_route
# then too. That function should be able to gracefully cleanup if the
# endpoints change.
- while read -r event; do
+ while read -u 19 -r event; do
[[ $event == RTM_* ]] || continue
- [[ -e /var/run/wireguard/$INTERFACE.sock ]] || break
- if_exists || break
+ ifconfig "$INTERFACE" >/dev/null 2>&1 || break
[[ $AUTO_ROUTE4 -eq 1 || $AUTO_ROUTE6 -eq 1 ]] && set_endpoint_direct_route
# TODO: set the mtu as well, but only if up
- done < <(route -n monitor)) & disown
+ done
+ kill $pid) & disown
}
HAVE_SET_DNS=0
set_dns() {
[[ ${#DNS[@]} -gt 0 ]] || return 0
- printf 'nameserver %s\n' "${DNS[@]}" | cmd resolvconf -a "$INTERFACE" -x
+ { printf 'nameserver %s\n' "${DNS[@]}"
+ [[ ${#DNS_SEARCH[@]} -eq 0 ]] || printf 'search %s\n' "${DNS_SEARCH[*]}"
+ } | cmd resolvconf -a "$INTERFACE" -x
HAVE_SET_DNS=1
}
@@ -330,7 +338,7 @@ add_route() {
}
set_config() {
- cmd wg setconf "$INTERFACE" <(echo "$WG_CONFIG")
+ echo "$WG_CONFIG" | cmd wg addconf "$INTERFACE" /dev/stdin
}
save_config() {
@@ -413,8 +421,8 @@ cmd_up() {
local i
[[ -z $(ifconfig "$INTERFACE" 2>/dev/null) ]] || die "\`$INTERFACE' already exists"
trap 'del_if; del_routes; clean_temp; exit' INT TERM EXIT
- execute_hooks "${PRE_UP[@]}"
add_if
+ execute_hooks "${PRE_UP[@]}"
set_config
for i in "${ADDRESSES[@]}"; do
add_addr "$i"
diff --git a/src/wg-quick/linux.bash b/src/wg-quick/linux.bash
index 7c2c002..34fa5f9 100755
--- a/src/wg-quick/linux.bash
+++ b/src/wg-quick/linux.bash
@@ -16,6 +16,7 @@ INTERFACE=""
ADDRESSES=( )
MTU=""
DNS=( )
+DNS_SEARCH=( )
TABLE=""
PRE_UP=( )
POST_UP=( )
@@ -37,7 +38,7 @@ die() {
}
parse_options() {
- local interface_section=0 line key value stripped
+ local interface_section=0 line key value stripped v
CONFIG_FILE="$1"
[[ $CONFIG_FILE =~ ^[a-zA-Z0-9_=+.-]{1,15}$ ]] && CONFIG_FILE="/etc/wireguard/$CONFIG_FILE.conf"
[[ -e $CONFIG_FILE ]] || die "\`$CONFIG_FILE' does not exist"
@@ -50,18 +51,21 @@ parse_options() {
stripped="${line%%\#*}"
key="${stripped%%=*}"; key="${key##*([[:space:]])}"; key="${key%%*([[:space:]])}"
value="${stripped#*=}"; value="${value##*([[:space:]])}"; value="${value%%*([[:space:]])}"
+ unstripped_value="${line#*=}"; unstripped_value="${unstripped_value##*([[:space:]])}"; unstripped_value="${unstripped_value%%*([[:space:]])}"
[[ $key == "["* ]] && interface_section=0
[[ $key == "[Interface]" ]] && interface_section=1
if [[ $interface_section -eq 1 ]]; then
case "$key" in
Address) ADDRESSES+=( ${value//,/ } ); continue ;;
MTU) MTU="$value"; continue ;;
- DNS) DNS+=( ${value//,/ } ); continue ;;
+ DNS) for v in ${value//,/ }; do
+ [[ $v =~ (^[0-9.]+$)|(^.*:.*$) ]] && DNS+=( $v ) || DNS_SEARCH+=( $v )
+ done; continue ;;
Table) TABLE="$value"; continue ;;
- PreUp) PRE_UP+=( "$value" ); continue ;;
- PreDown) PRE_DOWN+=( "$value" ); continue ;;
- PostUp) POST_UP+=( "$value" ); continue ;;
- PostDown) POST_DOWN+=( "$value" ); continue ;;
+ PreUp) PRE_UP+=( "$unstripped_value" ); continue ;;
+ PreDown) PRE_DOWN+=( "$unstripped_value" ); continue ;;
+ PostUp) POST_UP+=( "$unstripped_value" ); continue ;;
+ PostDown) POST_DOWN+=( "$unstripped_value" ); continue ;;
SaveConfig) read_bool SAVE_CONFIG "$value"; continue ;;
esac
fi
@@ -84,10 +88,10 @@ auto_su() {
add_if() {
local ret
- if ! cmd ip link add "$INTERFACE" type wireguard; then
+ if ! cmd ip link add dev "$INTERFACE" type wireguard; then
ret=$?
[[ -e /sys/module/wireguard ]] || ! command -v "${WG_QUICK_USERSPACE_IMPLEMENTATION:-wireguard-go}" >/dev/null && exit $ret
- echo "[!] Missing WireGuard kernel module. Falling back to slow userspace implementation."
+ echo "[!] Missing WireGuard kernel module. Falling back to slow userspace implementation." >&2
cmd "${WG_QUICK_USERSPACE_IMPLEMENTATION:-wireguard-go}" "$INTERFACE"
fi
}
@@ -120,7 +124,7 @@ add_addr() {
}
set_mtu_up() {
- local mtu=0 endpoint output
+ local mtu=2147483647 endpoint output
if [[ -n $MTU ]]; then
cmd ip link set mtu "$MTU" up dev "$INTERFACE"
return
@@ -128,18 +132,18 @@ set_mtu_up() {
while read -r _ endpoint; do
[[ $endpoint =~ ^\[?([a-z0-9:.]+)\]?:[0-9]+$ ]] || continue
output="$(ip route get "${BASH_REMATCH[1]}" || true)"
- [[ ( $output =~ mtu\ ([0-9]+) || ( $output =~ dev\ ([^ ]+) && $(ip link show dev "${BASH_REMATCH[1]}") =~ mtu\ ([0-9]+) ) ) && ${BASH_REMATCH[1]} -gt $mtu ]] && mtu="${BASH_REMATCH[1]}"
+ [[ ( $output =~ mtu\ ([0-9]+) || ( $output =~ dev\ ([^ ]+) && $(ip link show dev "${BASH_REMATCH[1]}") =~ mtu\ ([0-9]+) ) ) && ${BASH_REMATCH[1]} -lt $mtu ]] && mtu="${BASH_REMATCH[1]}"
done < <(wg show "$INTERFACE" endpoints)
- if [[ $mtu -eq 0 ]]; then
+ if [[ $mtu -eq 2147483647 ]]; then
read -r output < <(ip route show default || true) || true
- [[ ( $output =~ mtu\ ([0-9]+) || ( $output =~ dev\ ([^ ]+) && $(ip link show dev "${BASH_REMATCH[1]}") =~ mtu\ ([0-9]+) ) ) && ${BASH_REMATCH[1]} -gt $mtu ]] && mtu="${BASH_REMATCH[1]}"
+ [[ ( $output =~ mtu\ ([0-9]+) || ( $output =~ dev\ ([^ ]+) && $(ip link show dev "${BASH_REMATCH[1]}") =~ mtu\ ([0-9]+) ) ) && ${BASH_REMATCH[1]} -lt $mtu ]] && mtu="${BASH_REMATCH[1]}"
fi
- [[ $mtu -gt 0 ]] || mtu=1500
+ [[ $mtu -gt 0 && $mtu -lt 2147483647 ]] || mtu=1500
cmd ip link set mtu $(( mtu - 80 )) up dev "$INTERFACE"
}
resolvconf_iface_prefix() {
- [[ -f /etc/resolvconf/interface-order ]] || return 0
+ [[ -f /etc/resolvconf/interface-order && ! -L $(type -P resolvconf) ]] || return 0
local iface
while read -r iface; do
[[ $iface =~ ^([A-Za-z0-9-]+)\*$ ]] || continue
@@ -150,7 +154,9 @@ resolvconf_iface_prefix() {
HAVE_SET_DNS=0
set_dns() {
[[ ${#DNS[@]} -gt 0 ]] || return 0
- printf 'nameserver %s\n' "${DNS[@]}" | cmd resolvconf -a "$(resolvconf_iface_prefix)$INTERFACE" -m 0 -x
+ { printf 'nameserver %s\n' "${DNS[@]}"
+ [[ ${#DNS_SEARCH[@]} -eq 0 ]] || printf 'search %s\n' "${DNS_SEARCH[*]}"
+ } | cmd resolvconf -a "$(resolvconf_iface_prefix)$INTERFACE" -m 0 -x
HAVE_SET_DNS=1
}
@@ -215,9 +221,9 @@ add_default() {
fi
local proto=-4 iptables=iptables pf=ip
[[ $1 == *:* ]] && proto=-6 iptables=ip6tables pf=ip6
- cmd ip $proto route add "$1" dev "$INTERFACE" table $table
cmd ip $proto rule add not fwmark $table table $table
cmd ip $proto rule add table main suppress_prefixlength 0
+ cmd ip $proto route add "$1" dev "$INTERFACE" table $table
local marker="-m comment --comment \"wg-quick(8) rule for $INTERFACE\"" restore=$'*raw\n' nftable="wg-quick-$INTERFACE" nftcmd
printf -v nftcmd '%sadd table %s %s\n' "$nftcmd" "$pf" "$nftable"
@@ -243,7 +249,7 @@ add_default() {
}
set_config() {
- cmd wg setconf "$INTERFACE" <(echo "$WG_CONFIG")
+ cmd wg addconf "$INTERFACE" <(echo "$WG_CONFIG")
}
save_config() {
@@ -322,8 +328,8 @@ cmd_up() {
local i
[[ -z $(ip link show dev "$INTERFACE" 2>/dev/null) ]] || die "\`$INTERFACE' already exists"
trap 'del_if; exit' INT TERM EXIT
- execute_hooks "${PRE_UP[@]}"
add_if
+ execute_hooks "${PRE_UP[@]}"
set_config
for i in "${ADDRESSES[@]}"; do
add_addr "$i"
diff --git a/src/wg-quick/openbsd.bash b/src/wg-quick/openbsd.bash
index 8d458d1..19b9909 100755
--- a/src/wg-quick/openbsd.bash
+++ b/src/wg-quick/openbsd.bash
@@ -8,6 +8,7 @@ set -e -o pipefail
shopt -s extglob
export LC_ALL=C
+exec 3>&2
SELF="$(readlink -f "${BASH_SOURCE[0]}")"
export PATH="${SELF%/*}:$PATH"
@@ -16,6 +17,7 @@ INTERFACE=""
ADDRESSES=( )
MTU=""
DNS=( )
+DNS_SEARCH=( )
TABLE=""
PRE_UP=( )
POST_UP=( )
@@ -27,7 +29,7 @@ PROGRAM="${0##*/}"
ARGS=( "$@" )
cmd() {
- echo "[#] $*" >&2
+ echo "[#] $*" >&3
"$@"
}
@@ -50,18 +52,21 @@ parse_options() {
stripped="${line%%\#*}"
key="${stripped%%=*}"; key="${key##*([[:space:]])}"; key="${key%%*([[:space:]])}"
value="${stripped#*=}"; value="${value##*([[:space:]])}"; value="${value%%*([[:space:]])}"
+ unstripped_value="${line#*=}"; unstripped_value="${unstripped_value##*([[:space:]])}"; unstripped_value="${unstripped_value%%*([[:space:]])}"
[[ $key == "["* ]] && interface_section=0
[[ $key == "[Interface]" ]] && interface_section=1
if [[ $interface_section -eq 1 ]]; then
case "$key" in
Address) ADDRESSES+=( ${value//,/ } ); continue ;;
MTU) MTU="$value"; continue ;;
- DNS) DNS+=( ${value//,/ } ); continue ;;
+ DNS) for v in ${value//,/ }; do
+ [[ $v =~ (^[0-9.]+$)|(^.*:.*$) ]] && DNS+=( $v ) || DNS_SEARCH+=( $v )
+ done; continue ;;
Table) TABLE="$value"; continue ;;
- PreUp) PRE_UP+=( "$value" ); continue ;;
- PreDown) PRE_DOWN+=( "$value" ); continue ;;
- PostUp) POST_UP+=( "$value" ); continue ;;
- PostDown) POST_DOWN+=( "$value" ); continue ;;
+ PreUp) PRE_UP+=( "$unstripped_value" ); continue ;;
+ PreDown) PRE_DOWN+=( "$unstripped_value" ); continue ;;
+ PostUp) POST_UP+=( "$unstripped_value" ); continue ;;
+ PostDown) POST_DOWN+=( "$unstripped_value" ); continue ;;
SaveConfig) read_bool SAVE_CONFIG "$value"; continue ;;
esac
fi
@@ -84,23 +89,33 @@ auto_su() {
get_real_interface() {
- local interface diff
- wg show interfaces >/dev/null
- [[ -f "/var/run/wireguard/$INTERFACE.name" ]] || return 1
- interface="$(< "/var/run/wireguard/$INTERFACE.name")"
- [[ -n $interface && -S "/var/run/wireguard/$interface.sock" ]] || return 1
- diff=$(( $(stat -f %m "/var/run/wireguard/$interface.sock" 2>/dev/null || echo 200) - $(stat -f %m "/var/run/wireguard/$INTERFACE.name" 2>/dev/null || echo 100) ))
- [[ $diff -ge 2 || $diff -le -2 ]] && return 1
- REAL_INTERFACE="$interface"
- echo "[+] Interface for $INTERFACE is $REAL_INTERFACE" >&2
- return 0
+ local interface line
+ while IFS= read -r line; do
+ if [[ $line =~ ^([a-z]+[0-9]+):\ .+ ]]; then
+ interface="${BASH_REMATCH[1]}"
+ continue
+ fi
+ if [[ $interface == wg* && $line =~ ^\ description:\ wg-quick:\ (.+) && ${BASH_REMATCH[1]} == "$INTERFACE" ]]; then
+ REAL_INTERFACE="$interface"
+ return 0
+ fi
+ done < <(ifconfig)
+ return 1
}
add_if() {
- export WG_TUN_NAME_FILE="/var/run/wireguard/$INTERFACE.name"
- mkdir -p "/var/run/wireguard/"
- cmd "${WG_QUICK_USERSPACE_IMPLEMENTATION:-wireguard-go}" tun
- get_real_interface
+ while true; do
+ local -A existing_ifs="( $(wg show interfaces | sed 's/\([^ ]*\)/[\1]=1/g') )"
+ local index ret
+ for ((index=0; index <= 2147483647; ++index)); do [[ -v existing_ifs[wg$index] ]] || break; done
+ if ret="$(cmd ifconfig wg$index create description "wg-quick: $INTERFACE" 2>&1)"; then
+ REAL_INTERFACE="wg$index"
+ return 0
+ fi
+ [[ $ret == *"ifconfig: SIOCIFCREATE: File exists"* ]] && continue
+ echo "$ret" >&3
+ return 1
+ done
}
del_routes() {
@@ -130,8 +145,7 @@ del_routes() {
del_if() {
unset_dns
- [[ -z $REAL_INTERFACE ]] || cmd rm -f "/var/run/wireguard/$REAL_INTERFACE.sock"
- cmd rm -f "/var/run/wireguard/$INTERFACE.name"
+ [[ -n $REAL_INTERFACE ]] && cmd ifconfig $REAL_INTERFACE destroy
}
up_if() {
@@ -253,28 +267,42 @@ monitor_daemon() {
echo "[+] Backgrounding route monitor" >&2
(trap 'del_routes; exit 0' INT TERM EXIT
exec >/dev/null 2>&1
- local event
+ exec 19< <(exec route -n monitor)
+ local event pid=$!
# TODO: this should also check to see if the endpoint actually changes
# in response to incoming packets, and then call set_endpoint_direct_route
# then too. That function should be able to gracefully cleanup if the
# endpoints change.
- while read -r event; do
+ while read -u 19 -r event; do
[[ $event == RTM_* ]] || continue
ifconfig "$REAL_INTERFACE" >/dev/null 2>&1 || break
[[ $AUTO_ROUTE4 -eq 1 || $AUTO_ROUTE6 -eq 1 ]] && set_endpoint_direct_route
# TODO: set the mtu as well, but only if up
- done < <(route -n monitor)) & disown
+ done
+ kill $pid) & disown
}
set_dns() {
[[ ${#DNS[@]} -gt 0 ]] || return 0
- # TODO: this is a horrible way of doing it. Has OpenBSD no resolvconf?
+
+ # TODO: add exclusive support for nameservers
+ if pgrep -qx unwind; then
+ echo "[!] WARNING: unwind will leak DNS queries" >&2
+ elif pgrep -qx resolvd; then
+ echo "[!] WARNING: resolvd may leak DNS queries" >&2
+ else
+ echo "[+] resolvd is not running, DNS will not be configured" >&2
+ return 0
+ fi
+
cmd cp /etc/resolv.conf "/etc/resolv.conf.wg-quick-backup.$INTERFACE"
- cmd printf 'nameserver %s\n' "${DNS[@]}" > /etc/resolv.conf
+ [[ ${#DNS_SEARCH[@]} -eq 0 ]] || cmd printf 'search %s\n' "${DNS_SEARCH[*]}" > /etc/resolv.conf
+ route nameserver ${REAL_INTERFACE} ${DNS[@]}
}
unset_dns() {
[[ -f "/etc/resolv.conf.wg-quick-backup.$INTERFACE" ]] || return 0
+ route nameserver ${REAL_INTERFACE}
cmd mv "/etc/resolv.conf.wg-quick-backup.$INTERFACE" /etc/resolv.conf
}
@@ -310,7 +338,7 @@ add_route() {
}
set_config() {
- cmd wg setconf "$REAL_INTERFACE" <(echo "$WG_CONFIG")
+ cmd wg addconf "$REAL_INTERFACE" <(echo "$WG_CONFIG")
}
save_config() {
@@ -390,8 +418,8 @@ cmd_up() {
local i
get_real_interface && die "\`$INTERFACE' already exists as \`$REAL_INTERFACE'"
trap 'del_if; del_routes; exit' INT TERM EXIT
- execute_hooks "${PRE_UP[@]}"
add_if
+ execute_hooks "${PRE_UP[@]}"
set_config
for i in "${ADDRESSES[@]}"; do
add_addr "$i"
@@ -409,9 +437,7 @@ cmd_up() {
}
cmd_down() {
- if ! get_real_interface || [[ " $(wg show interfaces) " != *" $REAL_INTERFACE "* ]]; then
- die "\`$INTERFACE' is not a WireGuard interface"
- fi
+ get_real_interface || die "\`$INTERFACE' is not a WireGuard interface"
execute_hooks "${PRE_DOWN[@]}"
[[ $SAVE_CONFIG -eq 0 ]] || save_config
del_if
@@ -420,9 +446,7 @@ cmd_down() {
}
cmd_save() {
- if ! get_real_interface || [[ " $(wg show interfaces) " != *" $REAL_INTERFACE "* ]]; then
- die "\`$INTERFACE' is not a WireGuard interface"
- fi
+ get_real_interface || die "\`$INTERFACE' is not a WireGuard interface"
save_config
}
diff --git a/src/wg.c b/src/wg.c
index b9952eb..6480970 100644
--- a/src/wg.c
+++ b/src/wg.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: GPL-2.0
+// SPDX-License-Identifier: GPL-2.0 OR MIT
/*
* Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*/
@@ -14,7 +14,7 @@ const char *PROG_NAME;
static const struct {
const char *subcommand;
- int (*function)(int, char**);
+ int (*function)(int, const char**);
const char *description;
} subcommands[] = {
{ "show", show_main, "Shows the current configuration and device information" },
@@ -37,7 +37,7 @@ static void show_usage(FILE *file)
fprintf(file, "You may pass `--help' to any of these subcommands to view usage.\n");
}
-int main(int argc, char *argv[])
+int main(int argc, const char *argv[])
{
PROG_NAME = argv[0];
@@ -51,7 +51,7 @@ int main(int argc, char *argv[])
}
if (argc == 1) {
- static char *new_argv[] = { "show", NULL };
+ static const char *new_argv[] = { "show", NULL };
return show_main(1, new_argv);
}
diff --git a/src/wincompat/compat.h b/src/wincompat/compat.h
index 5decc66..4c5b368 100644
--- a/src/wincompat/compat.h
+++ b/src/wincompat/compat.h
@@ -18,8 +18,6 @@
#undef min
#undef max
-#define WINCOMPAT
-
#define IFNAMSIZ 64
#define EAI_SYSTEM -99
@@ -27,5 +25,3 @@
char *strsep(char **str, const char *sep);
ssize_t getdelim(char **buf, size_t *bufsiz, int delimiter, FILE *fp);
ssize_t getline(char **buf, size_t *bufsiz, FILE *fp);
-int inet_pton(int af, const char *src, void *dst);
-const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
diff --git a/src/wincompat/getrandom.c b/src/wincompat/getrandom.c
deleted file mode 100644
index b064b04..0000000
--- a/src/wincompat/getrandom.c
+++ /dev/null
@@ -1,12 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
- */
-
-#include <stdbool.h>
-#include <ntsecapi.h>
-
-static inline bool __attribute__((__warn_unused_result__)) get_random_bytes(uint8_t *out, size_t len)
-{
- return RtlGenRandom(out, len);
-}
diff --git a/src/wincompat/include/hashtable.h b/src/wincompat/include/hashtable.h
new file mode 100644
index 0000000..bd83bbb
--- /dev/null
+++ b/src/wincompat/include/hashtable.h
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Copyright (C) 2018-2021 WireGuard LLC. All Rights Reserved.
+ */
+
+#ifndef _HASHTABLE_H
+#define _HASHTABLE_H
+
+#include <string.h>
+
+enum { HASHTABLE_ENTRY_BUCKETS_POW2 = 1 << 10 };
+
+struct hashtable_entry {
+ char *key;
+ void *value;
+ struct hashtable_entry *next;
+};
+
+struct hashtable {
+ struct hashtable_entry *entry_buckets[HASHTABLE_ENTRY_BUCKETS_POW2];
+};
+
+static unsigned int hashtable_bucket(const char *str)
+{
+ unsigned long hash = 5381;
+ char c;
+ while ((c = *str++))
+ hash = ((hash << 5) + hash) ^ c;
+ return hash & (HASHTABLE_ENTRY_BUCKETS_POW2 - 1);
+}
+
+static struct hashtable_entry *hashtable_find_entry(struct hashtable *hashtable, const char *key)
+{
+ struct hashtable_entry *entry;
+ for (entry = hashtable->entry_buckets[hashtable_bucket(key)]; entry; entry = entry->next) {
+ if (!strcmp(entry->key, key))
+ return entry;
+ }
+ return NULL;
+}
+
+static struct hashtable_entry *hashtable_find_or_insert_entry(struct hashtable *hashtable, const char *key)
+{
+ struct hashtable_entry **entry;
+ for (entry = &hashtable->entry_buckets[hashtable_bucket(key)]; *entry; entry = &(*entry)->next) {
+ if (!strcmp((*entry)->key, key))
+ return *entry;
+ }
+ *entry = calloc(1, sizeof(**entry));
+ if (!*entry)
+ return NULL;
+ (*entry)->key = strdup(key);
+ if (!(*entry)->key) {
+ free(*entry);
+ *entry = NULL;
+ return NULL;
+ }
+ return *entry;
+}
+
+#endif
diff --git a/src/wincompat/include/sys/ioctl.h b/src/wincompat/include/sys/ioctl.h
deleted file mode 100644
index e69de29..0000000
--- a/src/wincompat/include/sys/ioctl.h
+++ /dev/null
diff --git a/src/wincompat/include/sys/un.h b/src/wincompat/include/sys/un.h
deleted file mode 100644
index e69de29..0000000
--- a/src/wincompat/include/sys/un.h
+++ /dev/null
diff --git a/src/wincompat/init.c b/src/wincompat/init.c
index 8943fff..f92c0a9 100644
--- a/src/wincompat/init.c
+++ b/src/wincompat/init.c
@@ -10,12 +10,22 @@
#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x4
#endif
+extern void NTAPI RtlGetNtVersionNumbers(DWORD *major, DWORD *minor, DWORD *build);
+bool is_win7 = false;
+
__attribute__((constructor)) static void init(void)
{
char *colormode;
- DWORD console_mode;
+ DWORD console_mode, major, minor;
HANDLE stdout_handle;
WSADATA wsaData;
+
+ if (!SetDllDirectoryA("") || !SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_SYSTEM32))
+ abort();
+
+ RtlGetNtVersionNumbers(&major, &minor, NULL);
+ is_win7 = (major == 6 && minor <= 1) || major < 6;
+
WSAStartup(MAKEWORD(2, 2), &wsaData);
stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE); // We don't close this.
diff --git a/src/wincompat/ipc.c b/src/wincompat/ipc.c
deleted file mode 100644
index 9dce816..0000000
--- a/src/wincompat/ipc.c
+++ /dev/null
@@ -1,138 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
- */
-
-#include <windows.h>
-#include <tlhelp32.h>
-#include <accctrl.h>
-#include <aclapi.h>
-#include <stdio.h>
-#include <stdbool.h>
-#include <fcntl.h>
-
-static FILE *userspace_interface_file(const char *iface)
-{
- char fname[MAX_PATH], error_message[1024 * 128] = { 0 };
- HANDLE thread_token, process_snapshot, winlogon_process, winlogon_token, duplicated_token, pipe_handle = INVALID_HANDLE_VALUE;
- PROCESSENTRY32 entry = { .dwSize = sizeof(PROCESSENTRY32) };
- PSECURITY_DESCRIPTOR pipe_sd;
- PSID pipe_sid;
- SID expected_sid;
- BOOL ret;
- int fd;
- DWORD last_error = ERROR_SUCCESS, bytes = sizeof(expected_sid);
- TOKEN_PRIVILEGES privileges = {
- .PrivilegeCount = 1,
- .Privileges = {{ .Attributes = SE_PRIVILEGE_ENABLED }}
- };
-
- if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &privileges.Privileges[0].Luid))
- goto err;
- if (!CreateWellKnownSid(WinLocalSystemSid, NULL, &expected_sid, &bytes))
- goto err;
-
- process_snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
- if (process_snapshot == INVALID_HANDLE_VALUE)
- goto err;
- for (ret = Process32First(process_snapshot, &entry); ret; last_error = GetLastError(), ret = Process32Next(process_snapshot, &entry)) {
- if (strcasecmp(entry.szExeFile, "winlogon.exe"))
- continue;
-
- RevertToSelf();
- if (!ImpersonateSelf(SecurityImpersonation))
- continue;
- if (!OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES, FALSE, &thread_token))
- continue;
- if (!AdjustTokenPrivileges(thread_token, FALSE, &privileges, sizeof(privileges), NULL, NULL)) {
- last_error = GetLastError();
- CloseHandle(thread_token);
- continue;
- }
- CloseHandle(thread_token);
-
- winlogon_process = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, entry.th32ProcessID);
- if (!winlogon_process)
- continue;
- if (!OpenProcessToken(winlogon_process, TOKEN_IMPERSONATE | TOKEN_DUPLICATE, &winlogon_token))
- continue;
- CloseHandle(winlogon_process);
- if (!DuplicateToken(winlogon_token, SecurityImpersonation, &duplicated_token)) {
- last_error = GetLastError();
- RevertToSelf();
- continue;
- }
- CloseHandle(winlogon_token);
- if (!SetThreadToken(NULL, duplicated_token)) {
- last_error = GetLastError();
- CloseHandle(duplicated_token);
- continue;
- }
- CloseHandle(duplicated_token);
-
- snprintf(fname, sizeof(fname), "\\\\.\\pipe\\ProtectedPrefix\\Administrators\\WireGuard\\%s", iface);
- pipe_handle = CreateFile(fname, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
- last_error = GetLastError();
- if (pipe_handle == INVALID_HANDLE_VALUE)
- continue;
- last_error = GetSecurityInfo(pipe_handle, SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION, &pipe_sid, NULL, NULL, NULL, &pipe_sd);
- if (last_error != ERROR_SUCCESS) {
- CloseHandle(pipe_handle);
- continue;
- }
- last_error = EqualSid(&expected_sid, pipe_sid) ? ERROR_SUCCESS : ERROR_ACCESS_DENIED;
- LocalFree(pipe_sd);
- if (last_error != ERROR_SUCCESS) {
- CloseHandle(pipe_handle);
- continue;
- }
- last_error = ERROR_SUCCESS;
- break;
- }
- RevertToSelf();
- CloseHandle(process_snapshot);
-
- if (last_error != ERROR_SUCCESS || pipe_handle == INVALID_HANDLE_VALUE)
- goto err;
- fd = _open_osfhandle((intptr_t)pipe_handle, _O_RDWR);
- if (fd == -1) {
- last_error = GetLastError();
- CloseHandle(pipe_handle);
- goto err;
- }
- return _fdopen(fd, "r+");
-
-err:
- if (last_error == ERROR_SUCCESS)
- last_error = GetLastError();
- if (last_error == ERROR_SUCCESS)
- last_error = ERROR_ACCESS_DENIED;
- FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, last_error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), error_message, sizeof(error_message) - 1, NULL);
- fprintf(stderr, "Error: Unable to open IPC handle via SYSTEM impersonation: %ld: %s\n", last_error, error_message);
- errno = EACCES;
- return NULL;
-}
-
-static int userspace_get_wireguard_interfaces(struct inflatable_buffer *buffer)
-{
- WIN32_FIND_DATA find_data;
- HANDLE find_handle;
- int ret = 0;
-
- find_handle = FindFirstFile("\\\\.\\pipe\\*", &find_data);
- if (find_handle == INVALID_HANDLE_VALUE)
- return -GetLastError();
- do {
- if (strncmp("WireGuard\\", find_data.cFileName, 10))
- continue;
- buffer->next = strdup(find_data.cFileName + 10);
- buffer->good = true;
- ret = add_next_to_inflatable_buffer(buffer);
- if (ret < 0)
- goto out;
- } while (FindNextFile(find_handle, &find_data));
-
-out:
- FindClose(find_handle);
- return ret;
-}
diff --git a/src/wincompat/libc.c b/src/wincompat/libc.c
index 870d913..3138370 100644
--- a/src/wincompat/libc.c
+++ b/src/wincompat/libc.c
@@ -6,8 +6,6 @@
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
-#include <winsock2.h>
-#include <ws2tcpip.h>
#include <windows.h>
char *strsep(char **str, const char *sep)
@@ -69,37 +67,3 @@ ssize_t getline(char **buf, size_t *bufsiz, FILE *fp)
{
return getdelim(buf, bufsiz, '\n', fp);
}
-
-int inet_pton(int af, const char *src, void *dst)
-{
- struct sockaddr_storage ss = { 0 };
- int size = sizeof(ss);
- char s[INET6_ADDRSTRLEN + 1];
-
- strncpy(s, src, INET6_ADDRSTRLEN + 1);
- s[INET6_ADDRSTRLEN] = '\0';
-
- if (WSAStringToAddress(s, af, NULL, (struct sockaddr *)&ss, &size))
- return 0;
- if (af == AF_INET)
- *(struct in_addr *)dst = ((struct sockaddr_in *)&ss)->sin_addr;
- else if (af == AF_INET6)
- *(struct in6_addr *)dst = ((struct sockaddr_in6 *)&ss)->sin6_addr;
- else
- return 0;
- return 1;
-}
-
-const char *inet_ntop(int af, const void *src, char *dst, socklen_t size)
-{
- struct sockaddr_storage ss = { .ss_family = af };
- unsigned long s = size;
-
- if (af == AF_INET)
- ((struct sockaddr_in *)&ss)->sin_addr = *(struct in_addr *)src;
- else if (af == AF_INET6)
- ((struct sockaddr_in6 *)&ss)->sin6_addr = *(struct in6_addr *)src;
- else
- return NULL;
- return WSAAddressToString((struct sockaddr *)&ss, sizeof(ss), NULL, dst, &s) ? NULL : dst;
-}
diff --git a/src/wincompat/loader.c b/src/wincompat/loader.c
new file mode 100644
index 0000000..72367b4
--- /dev/null
+++ b/src/wincompat/loader.c
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2015-2021 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
+ */
+
+#include <windows.h>
+#include <delayimp.h>
+
+static FARPROC WINAPI delayed_load_library_hook(unsigned dliNotify, PDelayLoadInfo pdli)
+{
+ HMODULE library;
+ if (dliNotify != dliNotePreLoadLibrary)
+ return NULL;
+ library = LoadLibraryExA(pdli->szDll, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
+ if (!library)
+ abort();
+ return (FARPROC)library;
+}
+
+PfnDliHook __pfnDliNotifyHook2 = delayed_load_library_hook;
diff --git a/src/wincompat/manifest.xml b/src/wincompat/manifest.xml
new file mode 100644
index 0000000..fb9a1b4
--- /dev/null
+++ b/src/wincompat/manifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+ <assemblyIdentity version="1.0.0.0" processorArchitecture="*" name="wg" type="win32" />
+ <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+ <application>
+ <!-- Windows 10 -->
+ <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
+ <!-- Windows 8.1 -->
+ <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
+ <!-- Windows 8 -->
+ <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
+ <!-- Windows 7 -->
+ <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
+ </application>
+ </compatibility>
+ <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
+ <security>
+ <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
+ <requestedExecutionLevel level="asInvoker" />
+ </requestedPrivileges>
+ </security>
+ </trustInfo>
+</assembly>
diff --git a/src/wincompat/resources.rc b/src/wincompat/resources.rc
new file mode 100644
index 0000000..2a0330c
--- /dev/null
+++ b/src/wincompat/resources.rc
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Copyright (C) 2020 Jason A. Donenfeld. All Rights Reserved.
+ */
+
+#include <windows.h>
+
+#pragma code_page(65001) // UTF-8
+
+LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
+CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST manifest.xml
+
+#define STRINGIZE(x) #x
+#define EXPAND(x) STRINGIZE(x)
+
+VS_VERSION_INFO VERSIONINFO
+FILEOS VOS_NT_WINDOWS32
+FILETYPE VFT_APP
+FILESUBTYPE VFT2_UNKNOWN
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0"
+ BEGIN
+ VALUE "CompanyName", "WireGuard LLC"
+ VALUE "FileDescription", "WireGuard wg(8) CLI: Fast, Modern, Secure VPN Tunnel"
+ VALUE "FileVersion", EXPAND(VERSION_STR)
+ VALUE "InternalName", "wg"
+ VALUE "LegalCopyright", "Copyright © 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved."
+ VALUE "OriginalFilename", "wg.exe"
+ VALUE "ProductName", "WireGuard"
+ VALUE "ProductVersion", EXPAND(VERSION_STR)
+ VALUE "Comments", "https://www.wireguard.com/"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 0x4b0
+ END
+END