A Universal Windows Platform (UWP) VPN Plug-in for WireGuard® written in Rust.
Windows provides a plug-in based model for adding 3rd-party VPN protocols. VPN profiles backed by such a plugin are referred to as Plugin/3rd-party/UWP profiles, as opposed to Native profiles (i.e. built-in SSTP, IKEv2).
WireGuard is a VPN protocol that aims to be: Fast, Modern and Secure. Principles which dovetail quite nicely with the Rust programming language. The actual noise-based WireGuard implementation comes from Cloudflare's boringtun.
With the rapidly maturing Rust for Windows bindings, this projects serve as a fun experiment in putting all the above together.
Make sure you have Rust installed. Then, once you've cloned the repo just simply run:
$ cargo build --release
The project currently only builds on Windows but given the Windows-specific nature, that's not considered a limitation.
Once you've successfully built the project, you can install it by running the following commands in a powershell prompt from the repo root:
copy appx\* target\release
Add-AppxPackage -Register .\target\release\AppxManifest.xml
NOTE: This does an in-place sort of installation in that the installed app will refer to
the binaries in your target\release
folder. So you may encounter issues if you modify those
after installation. This is just a stop-gap until a proper .appx
can be generated.
To get your VPN tunnel up and running:
- Open Windows Settings and navigate to the VPN page:
Network & Internet > VPN
. - Select
Add a VPN connection
. - From the
VPN provider
dropdown select WireGuard UWP VPN. - Give your new VPN profile a name under
Connection name
. - Enter the remote endpoint hostname or IP address under
Server name or address
. - Hit
Save
.
The settings you can tweak from the Windows Settings UI are limited to just the profile name and remote endpoint's hostname. To modify the private key, public key, remote port etc we must set those values manually. From a powershell prompt:
$vpnConfig = @'
<WireGuard>
<Interface>
<PrivateKey>...</PrivateKey>
<Address>10.0.0.2/32</Address>
<Address>2001:db8::2/64</Address>
<DNS>1.1.1.1</DNS>
<DNSSearch>vpn.example.com</DNSSearch>
<DNSSearch>foo.corp.example.com</DNSSearch>
</Interface>
<Peer>
<PublicKey>...</PublicKey>
<Port>51000</Port>
<AllowedIPs>10.0.0.0/24</AllowedIPs>
<AllowedIPs>10.10.0.0/24</AllowedIPs>
<AllowedIPs>10.20.0.0/24</AllowedIPs>
<AllowedIPs>2001:db8::/64</AllowedIPs>
<PersistentKeepalive>25</PersistentKeepalive>
</Peer>
</WireGuard>
'@
Set-VpnConnection -Name ProfileNameHere -CustomConfiguration $vpnConfig
The only required values are PrivateKey
, Address
, PublicKey
, & Port
. The rest are optional.
You may repeat Address
multiple times to assign multiple IPv4 & IPv6 addresses to the virtual
interface. Similarly, you may specify AllowedIPs
multiple times to define the routes that
should go over the virtual interface.
You should now be able to select the new profile and hit Connect
.
NOTE: Ideally, you could just specify Port
colon separated with the hostname but the
corresponding API for retrieving that value is statically typed as a HostName.
NOTE: You should make sure to set a PersistentKeepalive
value on the remote
side for each WireGuard UWP-based client because the UWP VPN plugin model
offers limited options for the plugin to perform periodic actions. Generally,
the plugin will only be woken if there are packets that need to be encapsulated or
if there are incoming buffers from the remote that need to be decapsulated. The
platform does provide a GetKeepAlivePayload
it will call on occasion but the
interval in which it will be called cannot be controlled by the plugin author or
the user but rather the platform itself. Hence it's important to make sure the
server will keep the tunnel alive by sending the periodic keep alives in-band.
NOTE: The main foreground app is planned to offer a simple UI for setting and modifying these values.
This has only been tested on Windows 10 21H1 (19043.1348) but should work on any updated Windows 10 or 11 release. It'll probably work on older versions but no guarantees.
You must specify one or more IPv4 and/or IPv6 addresses to assign to the virtual interface.
DNS servers are plumbed via Name Resolution Policy Table (NRPT) Rules. You can print the current set of rules applied while a VPN profile backed by the plugin is connected via either:
CMD:
netsh namespace show effectivepolicy
PowerShell:
Get-DnsClientNrptPolicy -Effective
If any number of DNS servers are specified in the config, the plugin will plumb one
NRPT rule with the namespace set to .
; this means the rule will apply to all
domains. While NRPT rules do support matching based on domain suffixes/prefixes
to apply domain-specific DNS servers, we just use a single wildcard rule.
You will note another rule plumbed while the plugin is connected, for the specific domain used to connect to the remote endpoint. This is added automatically by the platform so that if the tunnel is interrupted and needs to be re-established, we don't end up in a situation wherein we can't resolve the remote's hostname.
You can also specify one more optional search domains. These will be added to the
VPN interface's Connection-specific DNS Suffix Search List. The first specified
search domain will also be the primary Connection-specific DNS Suffix, as can be
confirmed with ipconfig /all
after connecting. You'll see an additional suffix-type
NRPT rule for each such search domain configured, with the specific DNS servers set
to whatever was configured.
If you'd like all traffic to flow over the VPN interface while connected, you can
just add a catch-all route (0.0.0.0/0
or ::/0
as appropriate):
$vpnConfig = @'
<WireGuard>
<Interface>
<PrivateKey>...</PrivateKey>
<Address>10.0.0.2/32</Address>
<DNS>1.1.1.1</DNS>
</Interface>
<Peer>
<PublicKey>...</PublicKey>
<Port>51000</Port>
<AllowedIPs>0.0.0.0/0</AllowedIPs>
<PersistentKeepalive>25</PersistentKeepalive>
</Peer>
</WireGuard>
'@
Set-VpnConnection -Name ProfileNameHere -CustomConfiguration $vpnConfig
If you'd like to exclude certain routes from going over the VPN interface, you
can specify one or more ExcludedIPs
elements:
$vpnConfig = @'
<WireGuard>
<Interface>
<PrivateKey>...</PrivateKey>
<Address>10.0.0.2/32</Address>
<DNS>1.1.1.1</DNS>
</Interface>
<Peer>
<PublicKey>...</PublicKey>
<Port>51000</Port>
<AllowedIPs>0.0.0.0/0</AllowedIPs>
<ExcludedIPs>192.168.1.0/24</ExcludedIPs>
<PersistentKeepalive>25</PersistentKeepalive>
</Peer>
</WireGuard>
'@
Set-VpnConnection -Name ProfileNameHere -CustomConfiguration $vpnConfig
This usually only makes sense if you have a catch-all route to carve out some exceptions. Also note that there will usually already be a specific route entry for your local subnet so no need to explicitly exclude it.
To print the routing table in cmd.exe
run:
route print
The plugin emits a number of ETW
under the Event Provider identified by a specific GUID (c4522a55-401f-4b81-93f9-aa0d1db734c4
). The events
are emitted with the help of the rust_win_etw crate which
provides a way to define & emit ETW events from your Rust code.
To consume these events, there are a number of different tools which can be used. rust_win_etw provides a quick rundown on how to capture them: https://github.com/microsoft/rust_win_etw#how-to-capture-and-view-events
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
"WireGuard" and the "WireGuard" logo are registered trademarks of Jason A. Donenfeld.