aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--contrib/embeddable-wg-library/wireguard.h12
-rw-r--r--contrib/launchd/com.wireguard.wg0.plist2
-rwxr-xr-xcontrib/reresolve-dns/reresolve-dns.sh2
-rw-r--r--src/Makefile17
-rw-r--r--src/config.c25
-rw-r--r--src/config.h2
-rw-r--r--src/containers.h7
-rw-r--r--src/ctype.h2
-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.c2
-rw-r--r--src/ipc-freebsd.h31
-rw-r--r--src/ipc-linux.h8
-rw-r--r--src/ipc-openbsd.h12
-rw-r--r--src/ipc-uapi-windows.h140
-rw-r--r--src/ipc-uapi.h2
-rw-r--r--src/ipc-windows.h456
-rw-r--r--src/ipc.c4
-rw-r--r--src/ipc.h2
-rw-r--r--src/man/wg-quick.82
-rw-r--r--src/man/wg.815
-rw-r--r--src/pubkey.c2
-rw-r--r--src/set.c4
-rw-r--r--src/setconf.c45
-rw-r--r--src/show.c8
-rw-r--r--src/showconf.c2
-rw-r--r--src/subcommands.h2
-rw-r--r--src/terminal.c2
-rw-r--r--src/terminal.h2
-rw-r--r--src/uapi/freebsd/dev/wg/if_wg.h (renamed from src/uapi/freebsd/dev/if_wg/if_wg.h)0
-rw-r--r--src/uapi/linux/linux/wireguard.h9
-rw-r--r--src/uapi/windows/wireguard.h80
-rw-r--r--src/version.h2
-rw-r--r--src/wg-quick/android.c14
-rwxr-xr-xsrc/wg-quick/darwin.bash27
-rwxr-xr-xsrc/wg-quick/freebsd.bash41
-rwxr-xr-xsrc/wg-quick/linux.bash29
-rwxr-xr-xsrc/wg-quick/openbsd.bash39
-rw-r--r--src/wg.c2
-rw-r--r--src/wincompat/include/hashtable.h61
-rw-r--r--src/wincompat/init.c12
-rw-r--r--src/wincompat/loader.c20
45 files changed, 929 insertions, 228 deletions
diff --git a/.gitignore b/.gitignore
index 0f03c4d..cb0ae8d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,9 @@
cscope.out
*.o
*.d
+*.lib
+*.dll
+*.gch
*.dwo
src/wg
src/wg.exe
diff --git a/contrib/embeddable-wg-library/wireguard.h b/contrib/embeddable-wg-library/wireguard.h
index fbd8765..328fcb4 100644
--- a/contrib/embeddable-wg-library/wireguard.h
+++ b/contrib/embeddable-wg-library/wireguard.h
@@ -40,17 +40,19 @@ enum wg_peer_flags {
WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL = 1U << 4
};
+typedef union wg_endpoint {
+ struct sockaddr addr;
+ struct sockaddr_in addr4;
+ struct sockaddr_in6 addr6;
+} wg_endpoint;
+
typedef struct wg_peer {
enum wg_peer_flags flags;
wg_key public_key;
wg_key preshared_key;
- union {
- struct sockaddr addr;
- struct sockaddr_in addr4;
- struct sockaddr_in6 addr6;
- } endpoint;
+ wg_endpoint endpoint;
struct timespec64 last_handshake_time;
uint64_t rx_bytes, tx_bytes;
diff --git a/contrib/launchd/com.wireguard.wg0.plist b/contrib/launchd/com.wireguard.wg0.plist
index 9fc0141..ea1eab6 100644
--- a/contrib/launchd/com.wireguard.wg0.plist
+++ b/contrib/launchd/com.wireguard.wg0.plist
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd";>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
diff --git a/contrib/reresolve-dns/reresolve-dns.sh b/contrib/reresolve-dns/reresolve-dns.sh
index fd38cd4..711c332 100755
--- a/contrib/reresolve-dns/reresolve-dns.sh
+++ b/contrib/reresolve-dns/reresolve-dns.sh
@@ -16,7 +16,7 @@ INTERFACE="${BASH_REMATCH[1]}"
process_peer() {
[[ $PEER_SECTION -ne 1 || -z $PUBLIC_KEY || -z $ENDPOINT ]] && return 0
[[ $(wg show "$INTERFACE" latest-handshakes) =~ ${PUBLIC_KEY//+/\\+}\ ([0-9]+) ]] || return 0
- (( ($(date +%s) - ${BASH_REMATCH[1]}) > 135 )) || return 0
+ (( ($EPOCHSECONDS - ${BASH_REMATCH[1]}) > 135 )) || return 0
wg set "$INTERFACE" peer "$PUBLIC_KEY" endpoint "$ENDPOINT"
reset_peer_section
}
diff --git a/src/Makefile b/src/Makefile
index 7b8969a..1c4b3f6 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -39,7 +39,7 @@ PLATFORM ?= $(shell uname -s | tr '[:upper:]' '[:lower:]')
CFLAGS ?= -O3
ifneq ($(wildcard uapi/$(PLATFORM)/.),)
-CFLAGS += -idirafter uapi/$(PLATFORM)
+CFLAGS += -isystem uapi/$(PLATFORM)
endif
CFLAGS += -std=gnu99 -D_GNU_SOURCE
CFLAGS += -Wall -Wextra
@@ -59,12 +59,16 @@ ifeq ($(PLATFORM),haiku)
LDLIBS += -lnetwork -lbsd
endif
ifeq ($(PLATFORM),windows)
-CC := x86_64-w64-mingw32-gcc
-WINDRES := x86_64-w64-mingw32-windres
+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 -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/resources.o
+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
@@ -78,12 +82,13 @@ 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)))
clean:
- $(RM) wg *.o *.d
+ $(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"
diff --git a/src/config.c b/src/config.c
index 211e887..6b8aa58 100644
--- a/src/config.c
+++ b/src/config.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.
*/
@@ -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));
@@ -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);
diff --git a/src/config.h b/src/config.h
index c52b9ea..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.
*/
diff --git a/src/containers.h b/src/containers.h
index fb5434f..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.
*/
@@ -28,6 +28,10 @@ struct timespec64 {
int64_t tv_nsec;
};
+enum {
+ WGALLOWEDIP_REMOVE_ME = 1U << 0,
+};
+
struct wgallowedip {
uint16_t family;
union {
@@ -35,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
index 98b2283..7c9942c 100644
--- a/src/ctype.h
+++ b/src/ctype.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/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 759a89d..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.
*/
diff --git a/src/ipc-freebsd.h b/src/ipc-freebsd.h
index 2c10c10..58e5e71 100644
--- a/src/ipc-freebsd.h
+++ b/src/ipc-freebsd.h
@@ -4,9 +4,10 @@
*
*/
+#include <assert.h>
#include <sys/nv.h>
#include <sys/sockio.h>
-#include <dev/if_wg/if_wg.h>
+#include <dev/wg/if_wg.h>
#define IPC_SUPPORTS_KERNEL_INTERFACE
@@ -14,7 +15,7 @@ static int get_dgram_socket(void)
{
static int sock = -1;
if (sock < 0)
- sock = socket(AF_INET, SOCK_DGRAM, 0);
+ sock = socket(AF_LOCAL, SOCK_DGRAM, 0);
return sock;
}
@@ -118,7 +119,7 @@ static int kernel_get_device(struct wgdevice **device, const char *ifname)
goto skip_peers;
for (i = 0; i < peer_count; ++i) {
struct wgpeer *peer;
- struct wgallowedip *aip;
+ struct wgallowedip *aip = NULL;
const nvlist_t *const *nvl_aips;
size_t aip_count, j;
@@ -169,11 +170,13 @@ static int kernel_get_device(struct wgdevice **device, const char *ifname)
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;
- if (!nvlist_exists_number(nvl_aips[j], "cidr"))
- continue;
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);
@@ -184,7 +187,8 @@ static int kernel_get_device(struct wgdevice **device, const char *ifname)
aip->family = AF_INET;
aip->cidr = number;
memcpy(&aip->ip4, binary, sizeof(aip->ip4));
- } else if (nvlist_exists_binary(nvl_aips[j], "ipv6")) {
+ } else {
+ assert(nvlist_exists_binary(nvl_aips[j], "ipv6"));
binary = nvlist_get_binary(nvl_aips[j], "ipv6", &size);
if (!binary || number > 128) {
ret = EINVAL;
@@ -193,14 +197,14 @@ static int kernel_get_device(struct wgdevice **device, const char *ifname)
aip->family = AF_INET6;
aip->cidr = number;
memcpy(&aip->ip6, binary, sizeof(aip->ip6));
- } else
- continue;
+ }
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:
@@ -209,6 +213,9 @@ static int kernel_get_device(struct wgdevice **device, const char *ifname)
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;
@@ -300,6 +307,11 @@ static int kernel_set_device(struct wgdevice *dev)
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));
@@ -322,6 +334,7 @@ static int kernel_set_device(struct wgdevice *dev)
nvlist_destroy(nvl_aips[j]);
free(nvl_aips);
nvlist_destroy(nvl_peers[i]);
+ nvl_peers[i] = NULL;
goto err;
}
if (i) {
@@ -329,9 +342,11 @@ static int kernel_set_device(struct wgdevice *dev)
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();
diff --git a/src/ipc-linux.h b/src/ipc-linux.h
index 5883ffe..01247f1 100644
--- a/src/ipc-linux.h
+++ b/src/ipc-linux.h
@@ -228,6 +228,8 @@ again:
}
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;
}
@@ -479,6 +481,12 @@ static int kernel_get_device(struct wgdevice **device, const char *iface)
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));
diff --git a/src/ipc-openbsd.h b/src/ipc-openbsd.h
index c529d9a..6bb231e 100644
--- a/src/ipc-openbsd.h
+++ b/src/ipc-openbsd.h
@@ -252,16 +252,20 @@ static int kernel_set_device(struct wgdevice *dev)
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) {
+ if (aip->family == AF_INET)
memcpy(&wg_aip->a_ipv4, &aip->ip4, sizeof(wg_aip->a_ipv4));
- } else if (aip->family == AF_INET6) {
+ else if (aip->family == AF_INET6)
memcpy(&wg_aip->a_ipv6, &aip->ip6, sizeof(wg_aip->a_ipv6));
- } else {
+ else
continue;
- }
++aip_count;
++wg_aip;
}
diff --git a/src/ipc-uapi-windows.h b/src/ipc-uapi-windows.h
index 86fab07..8ea4f75 100644
--- a/src/ipc-uapi-windows.h
+++ b/src/ipc-uapi-windows.h
@@ -10,126 +10,96 @@
#include <stdio.h>
#include <stdbool.h>
#include <fcntl.h>
+#include <hashtable.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;
+ char fname[MAX_PATH];
+ HANDLE pipe_handle;
SID expected_sid;
- BOOL ret;
+ DWORD bytes = sizeof(expected_sid);
+ PSID pipe_sid;
+ PSECURITY_DESCRIPTOR pipe_sd;
+ bool equal;
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)
+ 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) {
- last_error = GetLastError();
CloseHandle(pipe_handle);
- goto err;
+ return NULL;
}
return _fdopen(fd, "r+");
-
+err_close:
+ CloseHandle(pipe_handle);
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 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 -GetLastError();
+ return -EIO;
do {
if (strncmp(prefix, find_data.cFileName, strlen(prefix)))
continue;
- ret = string_list_add(list, find_data.cFileName + strlen(prefix));
+ 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);
diff --git a/src/ipc-uapi.h b/src/ipc-uapi.h
index f582916..1d8a271 100644
--- a/src/ipc-uapi.h
+++ b/src/ipc-uapi.h
@@ -89,7 +89,7 @@ static int userspace_set_device(struct wgdevice *dev)
continue;
} else
continue;
- fprintf(f, "allowed_ip=%s/%d\n", ip, allowedip->cidr);
+ fprintf(f, "allowed_ip=%s%s/%d\n", (allowedip->flags & WGALLOWEDIP_REMOVE_ME) ? "-" : "", ip, allowedip->cidr);
}
}
fprintf(f, "\n");
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 4a9f00a..1155bd5 100644
--- a/src/ipc.c
+++ b/src/ipc.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.
*/
@@ -47,6 +47,8 @@ static int string_list_add(struct string_list *list, const char *str)
#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 */
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 b84eb64..bc9e145 100644
--- a/src/man/wg-quick.8
+++ b/src/man/wg-quick.8
@@ -168,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:
diff --git a/src/man/wg.8 b/src/man/wg.8
index 0f74751..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,6 +223,13 @@ by running as root:
\fB # modprobe wireguard && echo module wireguard +p > /sys/kernel/debug/dynamic_debug/control\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
diff --git a/src/pubkey.c b/src/pubkey.c
index b55c1fe..f191592 100644
--- a/src/pubkey.c
+++ b/src/pubkey.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/set.c b/src/set.c
index 6f0e0cf..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.
*/
@@ -18,7 +18,7 @@ int set_main(int argc, const char *argv[])
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 bfd0a3a..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,46 +55,51 @@ 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;
}
diff --git a/src/show.c b/src/show.c
index 761858b..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;
@@ -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));
diff --git a/src/showconf.c b/src/showconf.c
index 64f8b6e..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.
*/
diff --git a/src/subcommands.h b/src/subcommands.h
index 7c4ed88..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.
*/
diff --git a/src/terminal.c b/src/terminal.c
index c10b9f1..d3e6611 100644
--- a/src/terminal.c
+++ b/src/terminal.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/terminal.h b/src/terminal.h
index 58697fa..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.
*/
diff --git a/src/uapi/freebsd/dev/if_wg/if_wg.h b/src/uapi/freebsd/dev/wg/if_wg.h
index a4b3b13..a4b3b13 100644
--- a/src/uapi/freebsd/dev/if_wg/if_wg.h
+++ b/src/uapi/freebsd/dev/wg/if_wg.h
diff --git a/src/uapi/linux/linux/wireguard.h b/src/uapi/linux/linux/wireguard.h
index 0efd52c..6ca266a 100644
--- a/src/uapi/linux/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/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 d1afe9f..0a7ef8d 100644
--- a/src/version.h
+++ b/src/version.h
@@ -1,3 +1,3 @@
#ifndef WIREGUARD_TOOLS_VERSION
-#define WIREGUARD_TOOLS_VERSION "1.0.20210315"
+#define WIREGUARD_TOOLS_VERSION "1.0.20250521"
#endif
diff --git a/src/wg-quick/android.c b/src/wg-quick/android.c
index 326efa9..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)
{
@@ -727,7 +729,7 @@ static void up_if(unsigned int *netid, const char *iface, uint16_t listen_port)
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);
}
cmd("ip link set up dev %s", iface);
- cndc("network create %u vpn 1 1", *netid);
+ cndc(sdk_version < 31 ? "network create %u vpn 1 1" : "network create %u vpn 1", *netid);
cndc("network interface add %u %s", *netid, iface);
}
@@ -853,7 +855,7 @@ static void set_dnses(unsigned int netid, const char *dnses)
if (!len)
return;
- xregcomp(&regex_ipnothost, "^[a-zA-Z0-9_=+.-]{1,15}$", REG_EXTENDED | REG_NOSUB);
+ 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;
@@ -1045,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);
@@ -1278,6 +1280,10 @@ int main(int argc, char *argv[])
_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]);
diff --git a/src/wg-quick/darwin.bash b/src/wg-quick/darwin.bash
index cde1b54..1b7fe5e 100755
--- a/src/wg-quick/darwin.bash
+++ b/src/wg-quick/darwin.bash
@@ -62,6 +62,7 @@ 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
@@ -72,10 +73,10 @@ parse_options() {
[[ $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
@@ -194,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)
@@ -324,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
}
@@ -367,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() {
@@ -450,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 6211b7a..aeae18d 100755
--- a/src/wg-quick/freebsd.bash
+++ b/src/wg-quick/freebsd.bash
@@ -80,6 +80,7 @@ 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
@@ -90,10 +91,10 @@ parse_options() {
[[ $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
@@ -152,20 +153,6 @@ 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
if [[ -S /var/run/wireguard/$INTERFACE.sock ]]; then
@@ -173,7 +160,7 @@ del_if() {
else
cmd ifconfig "$INTERFACE" destroy
fi
- while if_exists; do
+ 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
@@ -190,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
}
@@ -298,17 +285,19 @@ 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
- 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
@@ -349,7 +338,7 @@ add_route() {
}
set_config() {
- echo "$WG_CONFIG" | cmd wg setconf "$INTERFACE" /dev/stdin
+ echo "$WG_CONFIG" | cmd wg addconf "$INTERFACE" /dev/stdin
}
save_config() {
@@ -432,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 e4d4c4f..34fa5f9 100755
--- a/src/wg-quick/linux.bash
+++ b/src/wg-quick/linux.bash
@@ -51,6 +51,7 @@ 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
@@ -61,10 +62,10 @@ parse_options() {
[[ $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
@@ -87,7 +88,7 @@ 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." >&2
@@ -123,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
@@ -131,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
@@ -220,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"
@@ -248,7 +249,7 @@ add_default() {
}
set_config() {
- cmd wg setconf "$INTERFACE" <(echo "$WG_CONFIG")
+ cmd wg addconf "$INTERFACE" <(echo "$WG_CONFIG")
}
save_config() {
@@ -327,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 15550c8..19b9909 100755
--- a/src/wg-quick/openbsd.bash
+++ b/src/wg-quick/openbsd.bash
@@ -52,6 +52,7 @@ 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
@@ -62,10 +63,10 @@ parse_options() {
[[ $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
@@ -266,30 +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[@]}"
- [[ ${#DNS_SEARCH[@]} -eq 0 ]] || cmd printf 'search %s\n' "${DNS_SEARCH[*]}"
- } > /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
}
@@ -325,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() {
@@ -405,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"
diff --git a/src/wg.c b/src/wg.c
index aed70b6..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.
*/
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/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/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;