跨平台用户空间实现
虽然 WireGuard 最初是为 Linux 内核开发的,但为了获得最佳性能,它可以使用单独的实现在用户空间中运行。目前wireguard-go
功能齐全,并且wireguard-rs
正在开发中。在文档中ip link add wg0 type wireguard
,您可以随时写成wireguard-go wg0
。其他一切都应该相同。
为了防止碎片化,所有用户空间实现都应遵循相同的协议和规范,从而具有与原始 Linux 内核完全相同的行为。此外,它还应遵守以下配置接口。
界面
用户空间实现应该具有以下极其有限的命令行界面:
# userspace-wg [-f/--foreground] INTERFACE-NAME
例如,可以按如下方式调用 Go 实现来创建wg0
接口:
# wireguard-go wg0
运行上述命令将创建一个名为 的虚拟 TUN 设备wg0
,然后将其守护。成功守护并启动接口后,它会创建/var/run/wireguard/wg0.sock
(或/run/wireguard/wg0.sock
取决于平台)作为以流模式运行的 UNIX 域套接字。在 Windows 上,在 中使用双向命名管道时使用相同的语义\\.\pipe\WireGuard\wg0
。
该wg(8)
工具用于配置接口,以便实现中的配置接口完全一致。该工具将在(或)wg(8)
中查找接口。用户空间实现应该在响应 SIGINT/SIGTERM、tun 接口的移除或 UNIX 域套接字文件的删除时正常终止。该工具连接到这些套接字并发送和接收以下基于文本的协议。/var/run/wireguard/*.sock
/run/wireguard/*.sock
wg(8)
配置协议
WireGuard 实现必须响应两个命令:get
和set
,均为1
撰写本文时的版本。wg(8)
发送get
如下命令:
get=1
{empty line}
wg(8)
发送set
如下命令:
set=1
key1=value1
key2=value2
key3=value3
key4=value4
key5=value5
...
{empty line}
用户空间实现用以下方式响应get
命令:
key1=value1
key2=value2
key3=value3
key4=value4
key5=value5
...
errno=0
{empty line}
用户空间实现用以下方式响应set
命令:
errno=0
{empty line}
如果发生错误,errno
则是来自的相应整数errno.h
。
get
请注意和命令末尾set
以及每个相应响应末尾的空行wg(8)
。
键和值如下:
private_key
:此密钥的值应为接口的小写十六进制编码私钥。在操作的情况下,该值可能为全零字符串set
,在这种情况下,它表示应删除私钥。listen_port
:此值是一个十进制字符串整数,对应于接口的监听端口。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}