Cross-platform Userspace Implementation
While WireGuard has initially been developed for the Linux kernel, for maximum performance, it may run in userspace using a separate implementation. Currently wireguard-go
is quite functional, and wireguard-rs
is on its way. Any time in the documentation you see ip link add wg0 type wireguard
, you can instead write, wireguard-go wg0
. Everything else should then be identical.
In order to prevent fragmentation, all userspace implementations should conform to the same protocol and specification, thereby having the exact same behavior as the original Linux kernel one. Furthermore, it should abide by the following configuration interface.
Interface
A userspace implementation should have the following extremely limited command line interface:
# userspace-wg [-f/--foreground] INTERFACE-NAME
For example, a Go implementation would be invoked as follows for creating a wg0
interface:
# wireguard-go wg0
Running the above command would create a virtual TUN device called wg0
, and then daemonize. After successfully daemonizing and bringing up the interface, it creates /var/run/wireguard/wg0.sock
(or /run/wireguard/wg0.sock
depending on the platform), as a UNIX domain socket operating in stream mode. On Windows the same semantics are used with a bidirectional named pipe in \\.\pipe\WireGuard\wg0
.
The wg(8)
tool is used for configuring the interface, so that there is complete uniformity in configuration interfaces across implementations. The wg(8)
tool will look for interfaces in /var/run/wireguard/*.sock
(or /run/wireguard/*.sock
). Userspace implementations should die gracefully in response to SIGINT/SIGTERM, the removal of the tun interface, or the deletion of the UNIX domain socket file. The wg(8)
tool connects to these sockets and sends and receives the following text-based protocol.
Configuration Protocol
A WireGuard implementation must respond to two commands: get
and set
, both at version 1
as of writing. wg(8)
sends a get
command that looks like this:
get=1
{empty line}
wg(8)
sends a set
command that looks like this:
set=1
key1=value1
key2=value2
key3=value3
key4=value4
key5=value5
...
{empty line}
A userspace implementation responds to a get
command with:
key1=value1
key2=value2
key3=value3
key4=value4
key5=value5
...
errno=0
{empty line}
A userspace implementation responds to a set
command with:
errno=0
{empty line}
If there was an error, errno
is the corresponding integer from errno.h
.
Note the empty line at the end of the get
and set
commands as well as at the end of each of their respective responses from wg(8)
.
The keys and values are as follows:
private_key
: The value for this key should be a lowercase hex-encoded private key of the interface. The value may be an all zero string in the case of aset
operation, in which case it indicates that the private key should be removed.listen_port
: The value for this is a decimal-string integer corresponding to the listening port of the interface.fwmark
: The value for this is a decimal-string integer corresponding to the fwmark of the interface. The value may 0 in the case of aset
operation, in which case it indicates that the fwmark should be removed.replace_peers=true
: This key/value combo is only valid in aset
operation, in which case it indicates that the subsequent peers (perhaps an empty list) should replace any existing peers, rather than append to the existing peer list.public_key
: The value for this key should be a lowercase hex-encoded public key of a new peer entry, which this command adds. The same public key value may not repeat during a single message.remove=true
: This key/value combo is only valid in aset
operation, in which case it indicates that the previously added peer entry should be removed from the interface.update_only=true
: This key/value combo is only valid in aset
operation, in which case it causes the operation only occurs if the peer already exists as part of the interface.preshared_key
: The value for this key should be a lowercase hex-encoded preshared-key of the previously added peer entry. The value may be an all zero string in the case of aset
operation, in which case it indicates that the preshared-key should be removed.endpoint
: The value for this key is eitherIP:port
for IPv4 or[IP]:port
for IPv6, indicating the endpoint of the previously added peer entry.persistent_keepalive_interval
: The value for this is a decimal-string integer corresponding to the persistent keepalive interval of the previously added peer entry. The value0
disables it.replace_allowed_ips=true
: This key/value combo is only valid in aset
operation, in which case it indicates that the subsequent allowed IPs (perhaps an empty list) should replace any existing ones of the previously added peer entry, rather than append to the existing allowed IPs list.allowed_ip
: The value for this isIP/cidr
, indicating a new added allowed IP entry for the previously added peer entry. If an identical value already exists as part of a prior peer, the allowed IP entry will be removed from that peer and added to this peer.rx_bytes
,tx_bytes
: Only valid in aget
operation, these indicate in decimal-string integer bytes the number of received and transmitted bytes for the previously added peer entry.last_handshake_time_sec
,last_handshake_time_nsec
: Only valid in aget
operation, these indicate in decimal-string integer the number of seconds and nano-seconds of the most recent handshake for the previously added peer entry, expressed relative to the Unix epoch.protocol_version
: This value should not be used or set by most users of this API. If unset, the corresponding peer will use the latest available protocol version. Otherwise this value must be1
.
All interface-level keys must proceed all per-level keys.
Example Dialog
wg(8)
sends:
get=1
{empty line}
Userspace WireGuard implementation responds:
private_key=e84b5a6d2717c1003a13b431570353dbaca9146cf150c5f8575680feba52027a
listen_port=12912
public_key=b85996fecc9c7f1fc6d2572a76eda11d59bcd20be8e543b15ce4bd85a8e75a33
preshared_key=188515093e952f5f22e865cef3012e72f8b5f0b598ac0309d5dacce3b70fcf52
allowed_ip=192.168.4.4/32
endpoint=[abcd:23::33%2]:51820
public_key=58402e695ba1772b1cc9309755f043251ea77fdcf10fbe63989ceb7e19321376
tx_bytes=38333
rx_bytes=2224
allowed_ip=192.168.4.6/32
persistent_keepalive_interval=111
endpoint=182.122.22.19:3233
public_key=662e14fd594556f522604703340351258903b64f35553763f19426ab2a515c58
endpoint=5.152.198.39:51820
allowed_ip=192.168.4.10/32
allowed_ip=192.168.4.11/32
tx_bytes=1212111
rx_bytes=1929999999
protocol_version=1
errno=0
{empty line}
wg(8)
sends:
set=1
private_key=e84b5a6d2717c1003a13b431570353dbaca9146cf150c5f8575680feba52027a
fwmark=0
listen_port=12912
replace_peers=true
public_key=b85996fecc9c7f1fc6d2572a76eda11d59bcd20be8e543b15ce4bd85a8e75a33
preshared_key=188515093e952f5f22e865cef3012e72f8b5f0b598ac0309d5dacce3b70fcf52
replace_allowed_ips=true
allowed_ip=192.168.4.4/32
endpoint=[abcd:23::33%2]:51820
public_key=58402e695ba1772b1cc9309755f043251ea77fdcf10fbe63989ceb7e19321376
replace_allowed_ips=true
allowed_ip=192.168.4.6/32
persistent_keepalive_interval=111
endpoint=182.122.22.19:3233
public_key=662e14fd594556f522604703340351258903b64f35553763f19426ab2a515c58
endpoint=5.152.198.39:51820
replace_allowed_ips=true
allowed_ip=192.168.4.10/32
allowed_ip=192.168.4.11/32
public_key=e818b58db5274087fcc1be5dc728cf53d3b5726b4cef6b9bab8f8f8c2452c25c
remove=true
{empty line}
Userspace WireGuard implementation responds:
errno=0
{empty line}