Compare commits

...

85 Commits

Author SHA1 Message Date
Jason A. Donenfeld 13f4ac4cb7 ipc: linux: enforce IFNAMSIZ limit
libmnl doesn't check lengths, so do our own checking before copying the
interface name to the netlink buffer.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-08-04 16:04:36 +02:00
Jason A. Donenfeld 729242a114 man: set private key in PreUp rather than PostUp
This is probably more sensible, since there's no point in letting
traffic flow before the interface is configured.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-05-18 16:39:49 +02:00
Daniel Gröber e6888dd74e wg-quick: run PreUp hook after creating interface
Currently PreUp hooks run before the interface is created. This is
problematic for moving the device into a Linux VRFs as this will
currently clear all assigned IPv6 addressess (possibly a bug), so if we
did this in PostUp (i.e. before add_addr) we'll have to manually re-add
all assigned addresses. This is obviously less than ideal.

Instead create the wg device just before running PreUp hooks. We apply
this to all platforms for consistency.

Test case:

    $ ip link add vrf-test type vrf table 1234
    $ ip link add wg-test type wireguard
    $ ip addr add dev wg-test 192.168.42.42/24
    $ ip addr add dev wg-test fe80::/64

    $ ip -br addr show wg-test
    wg-test          DOWN           192.168.42.42/24 fe80::/64

    $ ip link set dev wg-test master vrf-test

    $ ip -br addr show wg-test
    wg-test          DOWN           192.168.42.42/32

Signed-off-by: Daniel Gröber <dxld@darkboxed.org>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-05-18 16:38:34 +02:00
Dmitry Selivanov b4f6b4f229 show: fix show all endpoints output
Currently "wg show all endpoints" prints interface name only once
while other "show all" commands print it on each line as man says.

Signed-off-by: Dmitry Selivanov <dseliv@gmail.com>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-02-08 13:47:47 -03:00
Kyle Evans 139aac59a5 ipc: freebsd: NULL out some freed memory in kernel_set_device()
The `err` path in kernel_set_device() will attempt to free() allocated
nvl_peers, but these two cases meant we could end up attempting a use
after free or a double free, as we rely on nvlist_destroy(NULL) being
a NOP as well as free(NULL).

FreeBSD-Coverity:	1500421
Signed-off-by: Kyle Evans <kevans@FreeBSD.org>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2022-11-03 19:57:26 +01:00
Kyle Evans dbf49a7d17 ipc: freebsd: avoid leaking memory in kernel_get_device()
Primarily, front-load validation of an allowed-ip entry to before we
allocate `aip`, so that we don't need to free() it if we end up skipping
this entry.  Assert that `aip` is NULL after we exit the loop, as we
should have transfered ownership to the `peer` or freed it in all paths
through the allowed-ip loop.

FreeBSD-Coverity:	1500405
Signed-off-by: Kyle Evans <kevans@FreeBSD.org>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2022-11-03 19:57:21 +01:00
Jason A. Donenfeld ca2e89ff21 show: apply const to right part of pointer
Without this -Wcast-qual complains:

show.c:30:43: warning: cast from 'const void *' to 'const void **' drops const qualifier [-Wcast-qual]
        const struct wgpeer *a = *(const void **)first, *b = *(const void **)second;
                                                 ^
show.c:30:71: warning: cast from 'const void *' to 'const void **' drops const qualifier [-Wcast-qual]
        const struct wgpeer *a = *(const void **)first, *b = *(const void **)second;

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2022-10-31 15:39:30 +01:00
Kyle Evans 7b2ae7aa2f ipc: freebsd: move if_wg path to reflect new in-tree location
When we re-added if_wg to the tree, we changed directories in dev to
strip the if_ (we don't use this prefix for other interfaces'
directories). Adjust it here as a convenience, so that when we import
wireguard-tools to FreeBSD the path will just work as-is with our usual
build.

Signed-off-by: Kyle Evans <kevans@FreeBSD.org>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2022-10-29 03:51:47 +02:00
Tom Yan 71799a8f6d wg-quick: linux: prevent traffic from momentarily leaking into tunnel
The wireguard route table ip rule should stay as a no-op until the
`suppress_prefixlength 0 table main` rule is in effect. Therefore, add
the wireguard default route to its route table after the latter rule is
added.

Signed-off-by: Tom Yan <tom.ty89@gmail.com>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2022-06-17 13:53:43 +02:00
Jason A. Donenfeld 5b9c1d6d74 global: dual license core files as MIT for FreeBSD
To make it easier for FreeBSD to import wg(8), dual license the core
files as MIT, so that they don't have any trouble.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2022-06-10 19:35:38 +02:00
Jason A. Donenfeld c0b68d2eaf wg-quick: android: use right regex for host-vs-IP
Looks like the "is valid ifname" regex was copy and pasted from
wg-quick.bash instead of the "is valid IP" regex.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2022-05-10 12:40:49 +02:00
Jason A. Donenfeld 1fd9570839 reresolve-dns: use $EPOCHSECONDS instead of $(date +%s)
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2022-01-04 13:07:49 +01:00
Mikael Magnusson b906ecb614 embeddable-wg-library: add named wg_endpoint union
Define wg_endpoint as a named union to allow users of the emeddable
library to use the type in function arguments, variables etc.

Signed-off-by: Mikael Magnusson <mikma@users.sourceforge.net>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-10-22 13:26:04 -06:00
Jason A. Donenfeld 1ee37b8e48 ipc: use more clever PnP enumerator
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-10-06 17:18:40 -06:00
Jason A. Donenfeld 3ba6527130 version: bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-09-14 00:43:31 +02:00
Matt Dunwoodie 84ac6add7e wg-quick: openbsd: set DNS with resolvd(8)
OpenBSD has introduced a new daemon named resolvd(8) to manage
resolv.conf. This creates problems with the old "horrible way" of
completely replacing resolv.conf. Resolvd will attempt to merge manual
changes with DNS servers discovered through dhcpleased(8) and slaacd(8).
Unfortunately, resolvd puts any manual modifications at the end of
resolv.conf, meaning that the wg-quick name servers will be queried
last.

The process for handling multiple name servers (at least with libc) is
to try a name server, and if the query times out, try the next, until
out of name servers, then repeat trying all name servers until a maximum
number of retries are performed. The name servers are queried in the
order listed in resolv.conf and the timeout is 5 seconds.

With this patch, we ensure the wg-quick name server is first in
resolv.conf (as route creates the name server with "static" priority),
but cannot ensure it is exclusive. Therfore, it may be possible that
queries are leaked to other name servers if the wg-quick name server
doesn't respond within 5 seconds.

We have another problem however, and that is if resolvd detects unwind
is running, it will set 127.0.0.1 as the only name server in
resolv.conf. unwind does not have deterministic name server selection in
the default configuration.  This means, all a user would need to do to
inadvertently cause persistent query leaks would be to run `rcctl enable
unwind`.

There are warnings added when these situations may occur.

The next step is to add an exclusive flag and search to route and
resolvd.

Reported-by: Matthieu Herrb <matthieu@herrb.eu>
Signed-off-by: Matt Dunwoodie <ncon@noconroy.net>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-09-14 00:40:51 +02:00
Jason A. Donenfeld af260d529e wg-quick: android: adjust for android 12
https://android-review.googlesource.com/c/platform/system/netd/+/1671532

Reported-by: engstk <eng.stk@sapo.pt>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-09-09 23:36:29 +02:00
Laura Hausmann b3aafa6103 wg-quick: darwin: account for "link#XX" gateways
On macOS, under specific configurations, the `netstat -nr -f inet` and
`netstat -nr -f inet6` outputs break gateway collection.

Signed-off-by: Laura Hausmann <laura@hausmann.dev>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-08-12 22:03:04 +02:00
Jason A. Donenfeld 52597c3515 ipc: windows: use devpkey instead of nci for name
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-07-31 01:01:53 +02:00
Jason A. Donenfeld fabe24df3a ipc: windows: don't display disabled adapters
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-07-20 13:24:18 +02:00
Jason A. Donenfeld c70bea7a31 ipc: remove windows elevation
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-07-20 13:24:18 +02:00
Jason A. Donenfeld d58df7ed10 ipc: cache windows lookups to avoid O(n^2) with nested lookups
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-07-20 13:24:18 +02:00
Jason A. Donenfeld f65c82456d ipc: add wireguard-nt support
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-07-20 13:24:18 +02:00
Hangbin Liu 9a7e4364b1 contrib/launchd: fix xml syntax error
The current plist xml gets error "DOCTYPE improperly terminated" with xml
syntax checker[1]. The example in apple doc[2] also doesn't have semicolon
at the end of DOCTYPE line.

[1] https://www.w3schools.com/xml/xml_validator.asp
[2] https://opensource.apple.com/source/launchd/launchd-257/launchd/doc/HOWTO.html

Fixes: b30e74b595 ("wg-quick: darwin: support being called from launchd")
Signed-off-by: Hangbin Liu <liuhangbin@gmail.com>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-05-17 11:43:06 +02:00
Jason A. Donenfeld 197689a3cd man: mention BSD debugging
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-05-06 12:54:29 +02:00
Jason A. Donenfeld ecb1ea29d7 version: bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-04-24 16:43:19 -04:00
Jason A. Donenfeld 96e42feb3f wg-quick: kill route monitor when loop terminates
If the route monitor doesn't attempt to write more to stdout, then this
leaves a process hanging around. Kill it explicitly. We also switch to
using exec in the process substitution, to reduce a bash process.

Closes: https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=255286
Reported-by: Christos Chatzaras <chris@cretaforce.gr>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-04-20 21:36:19 -06:00
Jason A. Donenfeld 3124afbea3 wg-quick: freebsd: use ifconfig for determining if interface is up
We no longer need the arp hack, as these bugs have been fixed in the
FreeBSD kernel.

This partially reverts 090639ae90.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-04-18 20:40:02 -06:00
Jason A. Donenfeld 163cef8b90 wg-quick: freebsd: do not assume point-to-point interface flag
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-03-23 12:29:33 -06:00
Jason A. Donenfeld a43f0b634e wg-quick: freebsd: check for socket using -S, not -f
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-03-21 10:15:53 -06:00
Jason A. Donenfeld 622408872f version: bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-03-15 08:04:02 -06:00
Jason A. Donenfeld 9c811e0f2d wg-quick: freebsd: avoid writing private keys to /tmp
FreeBSD's bash doesn't handle <(...) safely, creating a temporary file
instead of using /proc/self/fd/N like on Linux. Work around this by
using a simple pipeline with /dev/stdin.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-03-13 21:20:19 -07:00
Jason A. Donenfeld 4e4867dc95 ipc: uniformly ignore preshared keys that are zero
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-03-11 15:35:15 -07:00
Jason A. Donenfeld f51349c52b ipc: freebsd: add initial FreeBSD support
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-03-11 15:02:07 -07:00
Jason A. Donenfeld 576e40056d wg-quick: freebsd: add kernel support
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-03-11 09:05:14 -07:00
Kyle Evans 396b85280a wireguard-tools: drag in headers for prototypes
ipc.c and terminal.c provide definitions for prototypes in their
respective headers, drag those in.

Signed-off-by: Kyle Evans <kevans@FreeBSD.org>
2021-03-10 17:35:20 -07:00
Kyle Evans 88bc64366e wireguard-tools: const correctness
Fixes much of the noise from a FreeBSD WARNS=6 build of wg(8)

Signed-off-by: Kyle Evans <kevans@FreeBSD.org>
2021-03-10 17:35:18 -07:00
Florian Eckert 957702af94 Makefile: fix version indicator
If we execute `wg --version` we get a different version string that does
not match with the version string in the openwrt makefile.

Current version string:
`wireguard-tools vreboot-13159-gac5caa2718 -https://git.zx2c4.com/wireguard-tools/`

Corrected versions string:
`wireguard-tools v1.0.20200319 -https://git.zx2c4.com/wireguard-tools/`

Signed-off-by: Florian Eckert <fe@dev.tdt.de>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-03-05 14:37:11 -07:00
Jason A. Donenfeld 6d3b876492 version: bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-02-23 19:32:18 +01:00
Jason A. Donenfeld e8fa0f662f ipc: read trailing responses after set operation
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-01-27 15:22:20 +01:00
Jason A. Donenfeld f97e81c094 man: LOG_LEVEL variables changed nae
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-01-26 23:02:37 +01:00
Jason A. Donenfeld 457f96b65e ipc: do not use fscanf with trailing \n
If the stream is not closed, then this winds up hanging forever. So
remove the trailing \n\n and check manually after.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-01-25 21:22:36 +01:00
Jason A. Donenfeld 66ed611bd0 sticky-sockets: do not use SO_REUSEADDR
This makes little sense for unicast UDP sockets.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-12-18 23:40:15 +01:00
Jason A. Donenfeld 7e506135f7 completion: add help and syncconf completions
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-12-13 22:29:09 +01:00
Jason A. Donenfeld 5e24780d4c wincompat: do not elevate by default
Elevation makes it detach from the console, which means the results are
hidden.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-12-11 14:17:27 +01:00
Jason A. Donenfeld 843a256697 wincompat: add resource and manifest and enable lto
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-12-11 13:59:14 +01:00
Jason A. Donenfeld 66714e2c47 wincompat: recent mingw has inet_ntop/inet_pton
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-11-09 11:46:01 +01:00
Jason A. Donenfeld b637db4692 embeddable-wg-library: sync latest from netlink.h
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-10-29 12:44:47 +01:00
Jason A. Donenfeld c3f26340e6 wg-quick: openbsd: no use for userspace support
With alignment between the kernel and userspace, along with userspace
packages, we can now rely on the kernel in the future always having
wg(4).

This also simplifies the interface selection logic, and stores the
wg-quick interface name as the description.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-10-19 13:29:28 +02:00
Jason A. Donenfeld 265e81a344 wg-quick: android: do not free iterated pointer
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-09-15 16:20:22 +02:00
Jason A. Donenfeld 7a321ce808 version: bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-08-27 10:22:09 +02:00
Jason A. Donenfeld 91fbeb4a92 Revert "wg-quick: wait on process substitutions"
This reverts commit 26683f6c9a, which
means the old problem comes back. That's an issue. But waiting on
process substitutions is not available with commonly used bash versions:

  # wg-quick up demo
  [#] ip link add demo type wireguard
  [#] wg setconf demo /dev/fd/63
  /usr/bin/wg-quick: line 251: wait: pid 2955 is not a child of this shell
  [#] ip link delete dev demo

This means we have to wait a few years before fixing this issue. IOW,
bash limitation; can't fix.

Reported-by: Theodore Mozzo <theodore.mozzo@gmail.com>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-08-27 10:19:31 +02:00
Jason A. Donenfeld 9a0d65e2af wg-quick: android: use iproute2 to bring up interface instead of ndc
Android 11's ndc regresses even more, but it turns out that netd doesn't
need to track up/down state via direct invocation, so just set the
interface up by way of normal iproute2.

Reported-by: Harsh Shandilya <me@msfjarvis.dev>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-08-25 21:54:22 +02:00
Jason A. Donenfeld fbca033c69 version: bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-08-20 12:06:22 +02:00
Jason A. Donenfeld 26683f6c9a wg-quick: wait on process substitutions
Bash does not propagate error values, which is a bummer, but process
substitutions are a useful feature. Introduce a new idiom to deal with
this: either "; wait $!" after the line to propagate the error, or "||
true" to indicate explicitly that we don't care about the error.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-08-06 17:47:14 +02:00
Jason A. Donenfeld 13fac76a71 ctype: use non-locale-specific ctype.h
We also make these constant time, even though we're never distinguishing
between bits of a secret using them. From that perspective, though, this
is markedly better than the locale-specific table lookups in glibc, even
though base64 characters span two cache lines and valid private keys
must hit both.

Co-authored-by: Samuel Neves <sneves@dei.uc.pt>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
Signed-off-by: Samuel Neves <sneves@dei.uc.pt>
2020-08-06 17:47:14 +02:00
Jason A. Donenfeld cf2bf09524 pubkey: isblank is a subset of isspace
Therefore, there's no need to test both.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-08-06 17:47:14 +02:00
Jason A. Donenfeld b4a8a18797 man: wg-quick: use syncconf instead of addconf for strip example
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-07-28 14:19:10 +02:00
Domonkos P. Tomcsanyi a66219fa10 systemd: add reload target to systemd unit
Users can now run `systemctl reload wg-quick@wgnet0`, as described in
the wg-quick(8) man page. Note that this won't adjust Address=, DNS=, or
the various other non-wg(8) fields.

Signed-off-by: Domonkos P. Tomcsanyi <domi@tomcsanyi.net>
[zx2c4: use exec for bash commands to reduce excess forks, and rewrite
        commit message]
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-07-24 16:23:47 +02:00
Jason A. Donenfeld eb4665ecf0 wincompat: fold random into genkey
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-05-25 18:07:49 -06:00
Jason A. Donenfeld 197995d50c ipc: split into separate files per-platform
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-05-25 17:21:18 -06:00
Jason A. Donenfeld c45d422a93 version: bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-05-13 18:29:41 -06:00
Jason A. Donenfeld 56cb39fb22 ipc: openbsd: switch to array ioctl interface
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-05-13 01:55:32 -06:00
Jason A. Donenfeld 0cfde946b1 Makefile: remember to install all systemd units
Reported-by: Unit 193 <unit193@unit193.net>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-05-11 21:31:05 -06:00
Jason A. Donenfeld 8137c14dc6 version: bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-05-10 22:20:44 -06:00
Jason A. Donenfeld 10b4e7677f wg-quick: cleanup openbsd support
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-05-10 22:15:08 -06:00
Matt Dunwoodie 9eda95d084 wg-quick: add support for openbsd kernel implementation
Signed-off-by: Matt Dunwoodie <ncon@noconroy.net>
2020-05-10 22:10:02 -06:00
Jason A. Donenfeld d4a32c97fd ipc: cleanup openbsd support
We also add a wg_if.h in the fallback include path.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-05-10 22:10:02 -06:00
Matt Dunwoodie 5c66f6ecd1 ipc: add support for openbsd kernel implementation
Signed-off-by: Matt Dunwoodie <ncon@noconroy.net>
2020-05-10 02:05:42 -06:00
Jason A. Donenfeld b60e30e196 ipc: remove extra space
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-05-10 01:28:57 -06:00
Jason A. Donenfeld 7f236c7957 wg-quick: support dns search domains
If DNS= has an IP in it, treat it as a DNS server. If DNS= has a non-IP
in it, treat it as a DNS search domain.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-05-09 00:29:53 -06:00
Martin Hauke 238ca40591 systemd: add wg-quick.target
Add file wg-quick.target, which allows starting and stopping all
wg-quick@.service instances at once.

Signed-off-by: Martin Hauke <mardnh@gmx.de>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-05-01 15:58:59 -06:00
Jason A. Donenfeld 891fb523a2 terminal: specialize color_mode to stdout only
By specializing this to stdout, we can cache the isatty result.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-04-20 22:52:35 -06:00
Jason A. Donenfeld 3377409bb3 git: add gitattributes so tarball doesn't have gitignore files
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-04-08 23:54:42 -06:00
Jason A. Donenfeld e189f9942d wg-quick: android: support application whitelist
Prior we only supported a blacklist, but actually a whitelist is an
easier algorithm because that's internally how netd considers it, so we
don't need to find range spans. This commit adds an IncludedApplications
key.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-04-05 19:38:11 -06:00
Jason A. Donenfeld 20e28d2b0f highlighter: insist on 256-bit keys, not 257-bit or 258-bit
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-04-03 23:14:07 -06:00
Jason A. Donenfeld dc00c8c577 Makefile: simplify silent cleaning
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-03-23 00:06:24 -06:00
Jason A. Donenfeld a8063adc8a version: bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-03-19 16:46:35 -06:00
Jason A. Donenfeld be969b7fe1 wincompat: use new protected prefix on Windows
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-03-19 16:33:14 -06:00
Jason A. Donenfeld e98b84ab84 wincompat: use string_list instead of inflatable_buffer
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-03-19 16:29:27 -06:00
Luis Ressel 828ffc88cd man: add a warning to the SaveConfig description
Signed-off-by: Luis Ressel <aranea@aixah.de>
[zx2c4: slightly adjusted wording]
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-03-05 21:10:17 +08:00
Jason A. Donenfeld bd4f847372 man: backlink wg-quick(8) in wg(8)
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-02-12 15:46:23 +01:00
Kai Haberzettl 6fabf9c2fb man: fix grammar in wg(8) and wg-quick(8)
This fixes a few grammatical errors.

Signed-off-by: Kai Haberzettl <khaberz@gmail.com>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-02-08 22:23:16 +01:00
Jason A. Donenfeld d68b8b189c curve25519: squelch warnings on clang
These are generic helper functions we don't want to move into the actual
implementations, so that it's easy to keep parity with the kernel code.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-02-07 15:46:59 +01:00
Jason A. Donenfeld e5b08c2849 netlink: initialize mostly unused field
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-02-06 17:20:15 +01:00
64 changed files with 2973 additions and 1368 deletions

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
.gitattributes export-ignore
.gitignore export-ignore

3
.gitignore vendored
View File

@ -1,6 +1,9 @@
cscope.out
*.o
*.d
*.lib
*.dll
*.gch
*.dwo
src/wg
src/wg.exe

View File

@ -2,7 +2,9 @@ set_dns() {
[[ ${#DNS[@]} -gt 0 ]] || return 0
if [[ $(resolvconf --version 2>/dev/null) == openresolv\ * ]]; then
printf 'nameserver %s\n' "${DNS[@]}" | cmd resolvconf -a "$INTERFACE" -m 0 -x
{ printf 'nameserver %s\n' "${DNS[@]}"
[[ ${#DNS_SEARCH[@]} -eq 0 ]] || printf 'search %s\n' "${DNS_SEARCH[*]}"
} | cmd resolvconf -a "$INTERFACE" -m 0 -x
else
echo "[#] mount \`${DNS[*]}' /etc/resolv.conf" >&2
[[ -e /etc/resolv.conf ]] || touch /etc/resolv.conf
@ -15,6 +17,7 @@ set_dns() {
_EOF
printf 'nameserver %s\n' "${DNS[@]}"
[[ ${#DNS_SEARCH[@]} -eq 0 ]] || printf 'search %s\n' "${DNS_SEARCH[*]}"
} | unshare -m --propagation shared bash -c "$(cat <<-_EOF
set -e
context="\$(stat -c %C /etc/resolv.conf 2>/dev/null)" || unset context

View File

@ -81,7 +81,6 @@ enum wgallowedip_attribute {
/* libmnl mini library: */
#define MNL_SOCKET_AUTOPID 0
#define MNL_SOCKET_BUFFER_SIZE (sysconf(_SC_PAGESIZE) < 8192L ? sysconf(_SC_PAGESIZE) : 8192L)
#define MNL_ALIGNTO 4
#define MNL_ALIGN(len) (((len)+MNL_ALIGNTO-1) & ~(MNL_ALIGNTO-1))
#define MNL_NLMSG_HDRLEN MNL_ALIGN(sizeof(struct nlmsghdr))
@ -129,6 +128,18 @@ typedef int (*mnl_cb_t)(const struct nlmsghdr *nlh, void *data);
#define MNL_ARRAY_SIZE(a) (sizeof(a)/sizeof((a)[0]))
#endif
static size_t mnl_ideal_socket_buffer_size(void)
{
static size_t size = 0;
if (size)
return size;
size = (size_t)sysconf(_SC_PAGESIZE);
if (size > 8192)
size = 8192;
return size;
}
static size_t mnl_nlmsg_size(size_t len)
{
return len + MNL_NLMSG_HDRLEN;
@ -163,7 +174,6 @@ static void *mnl_nlmsg_get_payload_offset(const struct nlmsghdr *nlh, size_t off
return (void *)nlh + MNL_NLMSG_HDRLEN + MNL_ALIGN(offset);
}
static bool mnl_nlmsg_ok(const struct nlmsghdr *nlh, int len)
{
return len >= (int)sizeof(struct nlmsghdr) &&
@ -341,7 +351,6 @@ static uint32_t mnl_attr_get_u32(const struct nlattr *attr)
return *((uint32_t *)mnl_attr_get_payload(attr));
}
static uint64_t mnl_attr_get_u64(const struct nlattr *attr)
{
uint64_t tmp;
@ -409,14 +418,12 @@ static bool mnl_attr_put_u8_check(struct nlmsghdr *nlh, size_t buflen,
return mnl_attr_put_check(nlh, buflen, type, sizeof(uint8_t), &data);
}
static bool mnl_attr_put_u16_check(struct nlmsghdr *nlh, size_t buflen,
uint16_t type, uint16_t data)
{
return mnl_attr_put_check(nlh, buflen, type, sizeof(uint16_t), &data);
}
static bool mnl_attr_put_u32_check(struct nlmsghdr *nlh, size_t buflen,
uint16_t type, uint32_t data)
{
@ -441,12 +448,12 @@ static void mnl_attr_nest_cancel(struct nlmsghdr *nlh, struct nlattr *start)
nlh->nlmsg_len -= mnl_nlmsg_get_payload_tail(nlh) - (void *)start;
}
static int mnl_cb_noop(const struct nlmsghdr *nlh, void *data)
static int mnl_cb_noop(__attribute__((unused)) const struct nlmsghdr *nlh, __attribute__((unused)) void *data)
{
return MNL_CB_OK;
}
static int mnl_cb_error(const struct nlmsghdr *nlh, void *data)
static int mnl_cb_error(const struct nlmsghdr *nlh, __attribute__((unused)) void *data)
{
const struct nlmsgerr *err = mnl_nlmsg_get_payload(nlh);
@ -463,7 +470,7 @@ static int mnl_cb_error(const struct nlmsghdr *nlh, void *data)
return err->error == 0 ? MNL_CB_STOP : MNL_CB_ERROR;
}
static int mnl_cb_stop(const struct nlmsghdr *nlh, void *data)
static int mnl_cb_stop(__attribute__((unused)) const struct nlmsghdr *nlh, __attribute__((unused)) void *data)
{
return MNL_CB_STOP;
}
@ -496,13 +503,11 @@ static int __mnl_cb_run(const void *buf, size_t numbytes,
return -1;
}
if (nlh->nlmsg_flags & NLM_F_DUMP_INTR) {
errno = EINTR;
return -1;
}
if (nlh->nlmsg_type >= NLMSG_MIN_TYPE) {
if (cb_data){
ret = cb_data(nlh, data);
@ -572,7 +577,6 @@ static struct mnl_socket *mnl_socket_open(int bus)
return __mnl_socket_open(bus, 0);
}
static int mnl_socket_bind(struct mnl_socket *nl, unsigned int groups, pid_t pid)
{
int ret;
@ -602,7 +606,6 @@ static int mnl_socket_bind(struct mnl_socket *nl, unsigned int groups, pid_t pid
return 0;
}
static ssize_t mnl_socket_sendto(const struct mnl_socket *nl, const void *buf,
size_t len)
{
@ -613,7 +616,6 @@ static ssize_t mnl_socket_sendto(const struct mnl_socket *nl, const void *buf,
(struct sockaddr *) &snl, sizeof(snl));
}
static ssize_t mnl_socket_recvfrom(const struct mnl_socket *nl, void *buf,
size_t bufsiz)
{
@ -750,7 +752,7 @@ static int mnlg_socket_recv_run(struct mnlg_socket *nlg, mnl_cb_t data_cb, void
do {
err = mnl_socket_recvfrom(nlg->nl, nlg->buf,
MNL_SOCKET_BUFFER_SIZE);
mnl_ideal_socket_buffer_size());
if (err <= 0)
break;
err = mnl_cb_run2(nlg->buf, err, nlg->seq, nlg->portid,
@ -796,9 +798,10 @@ static struct mnlg_socket *mnlg_socket_open(const char *family_name, uint8_t ver
nlg = malloc(sizeof(*nlg));
if (!nlg)
return NULL;
nlg->id = 0;
err = -ENOMEM;
nlg->buf = malloc(MNL_SOCKET_BUFFER_SIZE);
nlg->buf = malloc(mnl_ideal_socket_buffer_size());
if (!nlg->buf)
goto err_buf_alloc;
@ -942,7 +945,7 @@ static int fetch_device_names(struct string_list *list)
struct ifinfomsg *ifm;
ret = -ENOMEM;
rtnl_buffer = calloc(MNL_SOCKET_BUFFER_SIZE, 1);
rtnl_buffer = calloc(mnl_ideal_socket_buffer_size(), 1);
if (!rtnl_buffer)
goto cleanup;
@ -973,7 +976,7 @@ static int fetch_device_names(struct string_list *list)
}
another:
if ((len = mnl_socket_recvfrom(nl, rtnl_buffer, MNL_SOCKET_BUFFER_SIZE)) < 0) {
if ((len = mnl_socket_recvfrom(nl, rtnl_buffer, mnl_ideal_socket_buffer_size())) < 0) {
ret = -errno;
goto cleanup;
}
@ -1009,7 +1012,7 @@ static int add_del_iface(const char *ifname, bool add)
struct ifinfomsg *ifm;
struct nlattr *nest;
rtnl_buffer = calloc(MNL_SOCKET_BUFFER_SIZE, 1);
rtnl_buffer = calloc(mnl_ideal_socket_buffer_size(), 1);
if (!rtnl_buffer) {
ret = -ENOMEM;
goto cleanup;
@ -1041,7 +1044,7 @@ static int add_del_iface(const char *ifname, bool add)
ret = -errno;
goto cleanup;
}
if ((len = mnl_socket_recvfrom(nl, rtnl_buffer, MNL_SOCKET_BUFFER_SIZE)) < 0) {
if ((len = mnl_socket_recvfrom(nl, rtnl_buffer, mnl_ideal_socket_buffer_size())) < 0) {
ret = -errno;
goto cleanup;
}
@ -1096,10 +1099,10 @@ again:
for (peer = peer ? peer : dev->first_peer; peer; peer = peer->next_peer) {
uint32_t flags = 0;
peer_nest = mnl_attr_nest_start_check(nlh, MNL_SOCKET_BUFFER_SIZE, 0);
peer_nest = mnl_attr_nest_start_check(nlh, mnl_ideal_socket_buffer_size(), 0);
if (!peer_nest)
goto toobig_peers;
if (!mnl_attr_put_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_PUBLIC_KEY, sizeof(peer->public_key), peer->public_key))
if (!mnl_attr_put_check(nlh, mnl_ideal_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;
@ -1107,45 +1110,45 @@ again:
if (peer->flags & WGPEER_REPLACE_ALLOWEDIPS)
flags |= WGPEER_F_REPLACE_ALLOWEDIPS;
if (peer->flags & WGPEER_HAS_PRESHARED_KEY) {
if (!mnl_attr_put_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_PRESHARED_KEY, sizeof(peer->preshared_key), peer->preshared_key))
if (!mnl_attr_put_check(nlh, mnl_ideal_socket_buffer_size(), WGPEER_A_PRESHARED_KEY, sizeof(peer->preshared_key), peer->preshared_key))
goto toobig_peers;
}
if (peer->endpoint.addr.sa_family == AF_INET) {
if (!mnl_attr_put_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_ENDPOINT, sizeof(peer->endpoint.addr4), &peer->endpoint.addr4))
if (!mnl_attr_put_check(nlh, mnl_ideal_socket_buffer_size(), WGPEER_A_ENDPOINT, sizeof(peer->endpoint.addr4), &peer->endpoint.addr4))
goto toobig_peers;
} else if (peer->endpoint.addr.sa_family == AF_INET6) {
if (!mnl_attr_put_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_ENDPOINT, sizeof(peer->endpoint.addr6), &peer->endpoint.addr6))
if (!mnl_attr_put_check(nlh, mnl_ideal_socket_buffer_size(), WGPEER_A_ENDPOINT, sizeof(peer->endpoint.addr6), &peer->endpoint.addr6))
goto toobig_peers;
}
if (peer->flags & WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL) {
if (!mnl_attr_put_u16_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL, peer->persistent_keepalive_interval))
if (!mnl_attr_put_u16_check(nlh, mnl_ideal_socket_buffer_size(), WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL, peer->persistent_keepalive_interval))
goto toobig_peers;
}
}
if (flags) {
if (!mnl_attr_put_u32_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_FLAGS, flags))
if (!mnl_attr_put_u32_check(nlh, mnl_ideal_socket_buffer_size(), WGPEER_A_FLAGS, flags))
goto toobig_peers;
}
if (peer->first_allowedip) {
if (!allowedip)
allowedip = peer->first_allowedip;
allowedips_nest = mnl_attr_nest_start_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_ALLOWEDIPS);
allowedips_nest = mnl_attr_nest_start_check(nlh, mnl_ideal_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, MNL_SOCKET_BUFFER_SIZE, 0);
allowedip_nest = mnl_attr_nest_start_check(nlh, mnl_ideal_socket_buffer_size(), 0);
if (!allowedip_nest)
goto toobig_allowedips;
if (!mnl_attr_put_u16_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGALLOWEDIP_A_FAMILY, allowedip->family))
if (!mnl_attr_put_u16_check(nlh, mnl_ideal_socket_buffer_size(), WGALLOWEDIP_A_FAMILY, allowedip->family))
goto toobig_allowedips;
if (allowedip->family == AF_INET) {
if (!mnl_attr_put_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGALLOWEDIP_A_IPADDR, sizeof(allowedip->ip4), &allowedip->ip4))
if (!mnl_attr_put_check(nlh, mnl_ideal_socket_buffer_size(), WGALLOWEDIP_A_IPADDR, sizeof(allowedip->ip4), &allowedip->ip4))
goto toobig_allowedips;
} else if (allowedip->family == AF_INET6) {
if (!mnl_attr_put_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGALLOWEDIP_A_IPADDR, sizeof(allowedip->ip6), &allowedip->ip6))
if (!mnl_attr_put_check(nlh, mnl_ideal_socket_buffer_size(), WGALLOWEDIP_A_IPADDR, sizeof(allowedip->ip6), &allowedip->ip6))
goto toobig_allowedips;
}
if (!mnl_attr_put_u8_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGALLOWEDIP_A_CIDR_MASK, allowedip->cidr))
if (!mnl_attr_put_u8_check(nlh, mnl_ideal_socket_buffer_size(), WGALLOWEDIP_A_CIDR_MASK, allowedip->cidr))
goto toobig_allowedips;
mnl_attr_nest_end(nlh, allowedip_nest);
allowedip_nest = NULL;

View File

@ -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;

View File

@ -62,11 +62,32 @@ static bool is_valid_key(string_span_t s)
if (s.len != 44 || s.s[43] != '=')
return false;
for (size_t i = 0; i < 43; ++i) {
for (size_t i = 0; i < 42; ++i) {
if (!is_decimal(s.s[i]) && !is_alphabet(s.s[i]) &&
s.s[i] != '/' && s.s[i] != '+')
return false;
}
switch (s.s[42]) {
case 'A':
case 'E':
case 'I':
case 'M':
case 'Q':
case 'U':
case 'Y':
case 'c':
case 'g':
case 'k':
case 'o':
case 's':
case 'w':
case '4':
case '8':
case '0':
break;
default:
return false;
}
return true;
}
@ -316,11 +337,6 @@ static bool is_valid_network(string_span_t s)
return is_valid_ipv4(s) || is_valid_ipv6(s);
}
static bool is_valid_dns(string_span_t s)
{
return is_valid_ipv4(s) || is_valid_ipv6(s);
}
enum field {
InterfaceSection,
PrivateKey,
@ -430,7 +446,12 @@ static void highlight_multivalue_value(struct highlight_span_array *ret, const s
{
switch (section) {
case DNS:
append_highlight_span(ret, parent.s, s, is_valid_dns(s) ? HighlightIP : HighlightError);
if (is_valid_ipv4(s) || is_valid_ipv6(s))
append_highlight_span(ret, parent.s, s, HighlightIP);
else if (is_valid_hostname(s))
append_highlight_span(ret, parent.s, s, HighlightHost);
else
append_highlight_span(ret, parent.s, s, HighlightError);
break;
case Address:
case AllowedIPs: {

View File

@ -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>

View File

@ -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
}

View File

@ -195,10 +195,6 @@ int magic_create_sock4(uint16_t listen_port)
if (fd < 0)
return fd;
ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
if (ret < 0)
goto err;
ret = setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &on, sizeof(on));
if (ret < 0)
goto err;
@ -228,10 +224,6 @@ int magic_create_sock6(uint16_t listen_port)
if (fd < 0)
return fd;
ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
if (ret < 0)
goto err;
ret = setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on, sizeof(on));
if (ret < 0)
goto err;

View File

@ -38,7 +38,9 @@ endif
PLATFORM ?= $(shell uname -s | tr '[:upper:]' '[:lower:]')
CFLAGS ?= -O3
CFLAGS += -idirafter uapi
ifneq ($(wildcard uapi/$(PLATFORM)/.),)
CFLAGS += -idirafter 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

View File

@ -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[@]}" )

View File

@ -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);
@ -375,7 +375,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))
@ -501,7 +501,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 +555,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;

View File

@ -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);

View File

@ -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 {

29
src/ctype.h Normal file
View File

@ -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

View File

@ -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.
*/
@ -39,23 +39,9 @@ typedef int64_t s64;
#define le32_to_cpup(a) (*(a))
#define cpu_to_le64(a) (a)
#endif
static inline __le32 get_unaligned_le32(const u8 *a)
{
__le32 l;
__builtin_memcpy(&l, a, sizeof(l));
return le32_to_cpup(&l);
}
static inline __le64 get_unaligned_le64(const u8 *a)
{
__le64 l;
__builtin_memcpy(&l, a, sizeof(l));
return le64_to_cpup(&l);
}
static inline void put_unaligned_le64(u64 s, u8 *d)
{
__le64 l = cpu_to_le64(s);
__builtin_memcpy(d, &l, sizeof(l));
}
#ifndef __unused
#define __unused __attribute__((unused))
#endif
#ifndef __always_inline
#define __always_inline __inline __attribute__((__always_inline__))
#endif
@ -69,6 +55,24 @@ static inline void put_unaligned_le64(u64 s, u8 *d)
#define __force
#endif
static __always_inline __unused __le32 get_unaligned_le32(const u8 *a)
{
__le32 l;
__builtin_memcpy(&l, a, sizeof(l));
return le32_to_cpup(&l);
}
static __always_inline __unused __le64 get_unaligned_le64(const u8 *a)
{
__le64 l;
__builtin_memcpy(&l, a, sizeof(l));
return le64_to_cpup(&l);
}
static __always_inline __unused void put_unaligned_le64(u64 s, u8 *d)
{
__le64 l = cpu_to_le64(s);
__builtin_memcpy(d, &l, sizeof(l));
}
static noinline void memzero_explicit(void *s, size_t count)
{
memset(s, 0, count);

View File

@ -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.
*/

View File

@ -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.
*

View File

@ -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.
*/

View File

@ -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];

360
src/ipc-freebsd.h Normal file
View File

@ -0,0 +1,360 @@
// 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_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 *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;
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;
}

525
src/ipc-linux.h Normal file
View File

@ -0,0 +1,525 @@
// 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;
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;
}

281
src/ipc-openbsd.h Normal file
View File

@ -0,0 +1,281 @@
// 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) {
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;
}

119
src/ipc-uapi-unix.h Normal file
View File

@ -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;
}

107
src/ipc-uapi-windows.h Normal file
View File

@ -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;
}

297
src/ipc-uapi.h Normal file
View File

@ -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/%d\n", 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

450
src/ipc-windows.h Normal file
View File

@ -0,0 +1,450 @@
// 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) {
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;
}

905
src/ipc.c
View File

@ -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);

View File

@ -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.
*/

View File

@ -56,7 +56,7 @@ as usual.
The configuration file adds a few extra configuration values to the format understood by
.BR wg (8)
in order to configure additional attribute of an interface. It handles the
in order to configure additional attributes of an interface. It handles the
values that it understands, and then it passes the remaining ones directly to
.BR wg (8)
for further processing.
@ -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),

View File

@ -40,7 +40,7 @@ Sub-commands that take an INTERFACE must be passed a WireGuard interface.
Shows current WireGuard configuration and runtime information of specified \fI<interface>\fP.
If no \fI<interface>\fP is specified, \fI<interface>\fP defaults to \fIall\fP.
If \fIinterfaces\fP is specified, prints a list of all WireGuard interfaces,
one per line, and quit. If no options are given after the interface
one per line, and quits. If no options are given after the interface
specification, then prints a list of all attributes in a visually pleasing way
meant for the terminal. Otherwise, prints specified information grouped by
newlines and tabs, meant to be used in scripts. For this script-friendly display,
@ -61,7 +61,7 @@ Sets configuration values for the specified \fI<interface>\fP. Multiple
for a peer, that peer is removed, not configured. If \fIlisten-port\fP
is not specified, or set to 0, the port will be chosen randomly when the
interface comes up. Both \fIprivate-key\fP and \fIpreshared-key\fP must
be a files, because command line arguments are not considered private on
be files, because command line arguments are not considered private on
most systems but if you are using
.BR bash (1),
you may safely pass in a string by specifying as \fIprivate-key\fP or
@ -219,7 +219,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 +240,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),

View File

@ -737,6 +737,7 @@ static struct mnlg_socket *mnlg_socket_open(const char *family_name, uint8_t ver
nlg = malloc(sizeof(*nlg));
if (!nlg)
return NULL;
nlg->id = 0;
err = -ENOMEM;
nlg->buf = malloc(mnl_ideal_socket_buffer_size());

View File

@ -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;

View File

@ -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,7 @@
#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;

View File

@ -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.
*/
@ -98,7 +98,7 @@ static bool sync_conf(struct wgdevice *file)
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;

View File

@ -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;

View File

@ -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];

View File

@ -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

View File

@ -0,0 +1,2 @@
[Unit]
Description=WireGuard Tunnels via wg-quick(8)

View File

@ -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]

View File

@ -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);
}

View File

@ -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

View File

@ -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

View File

@ -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__ */

View File

@ -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

View File

@ -1,3 +1,3 @@
#ifndef WIREGUARD_TOOLS_VERSION
#define WIREGUARD_TOOLS_VERSION "1.0.20200206"
#define WIREGUARD_TOOLS_VERSION "1.0.20210914"
#endif

View File

@ -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)
@ -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]);

View File

@ -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
@ -67,7 +68,9 @@ parse_options() {
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 ;;
@ -191,14 +194,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 +216,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 +227,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 +297,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 +313,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 +324,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
}
@ -430,8 +452,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"

View File

@ -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
@ -84,7 +86,9 @@ parse_options() {
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 ;;
@ -111,6 +115,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 +152,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 +176,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 +284,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 +337,7 @@ add_route() {
}
set_config() {
cmd wg setconf "$INTERFACE" <(echo "$WG_CONFIG")
echo "$WG_CONFIG" | cmd wg setconf "$INTERFACE" /dev/stdin
}
save_config() {
@ -413,8 +420,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"

View File

@ -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"
@ -56,7 +57,9 @@ parse_options() {
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 ;;
@ -87,7 +90,7 @@ add_if() {
if ! cmd ip link add "$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
}
@ -150,7 +153,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 +220,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"
@ -322,8 +327,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"

View File

@ -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
"$@"
}
@ -56,7 +58,9 @@ parse_options() {
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 ;;
@ -84,23 +88,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 +144,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 +266,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
}
@ -390,8 +417,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 +436,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 +445,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
}

View File

@ -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);
}

View File

@ -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);

View File

@ -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);
}

View File

@ -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

View File

@ -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.

View File

@ -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;
}

View File

@ -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;
}

20
src/wincompat/loader.c Normal file
View File

@ -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;

View File

@ -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>

View File

@ -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