发新话题
打印

WireGuard基本原理(转)

WireGuard基本原理(转)

WireGuard:下一代内核网络隧道
摘要:
WireGuard的优势
  • 更轻便:以Linux内核模块的形式运行,资源占用小。
  • 更高效:相比目前主流的IPSec、OpenVPN等协议,WireGuard的效率要更高。
  • 更快速:比目前主流的VPN协议,连接速度要更快。
  • 更安全:使用了更先进的加密技术。
  • 更易搭建:部署难度相对更低。
  • 更隐蔽:以UDP协议进行数据传输,比TCP协议更低调。
  • 不易被封锁:TCP阻断对WireGuard无效,IP被墙的情况下仍然可用。
  • 更省电:不使用时不进行数据传输,移动端更省电。
简介与动机
WireGuard消除了这些分层分隔。WireGuard没有提供复杂的IPsec和xfrm层,而是提供了一个虚拟接口(例如wg0),然后可以使用标准的ip和ifconfig实用程序对其进行管理。在使用私钥(以及可选的预共享对称密钥和它将安全通信的对等点的各种公钥配置接口之后,隧道就简单地工作了。密钥交换、连接、断开、重连接、发现等在幕后透明而可靠地发生,管理员不需要担心这些细节。换句话说,从管理的角度来看,WireGuard接口似乎是无状态的。然后可以使用普通的基础结构为防火墙接口配置防火墙规则,并确保对来自WireGuard接口的数据包进行身份验证和加密。与IPsec相比,WireGuard简单明了,不容易发生灾难性故障和配置错误。需要强调的是,IPsec的分层是正确无误的。使用IPsec,一切都在正确的地方,以达到学术上的完美。但是,正如抽象的正确性经常发生的那样,它严重缺乏可用性,并且很难实现可验证的安全实现。相反,WireGuard从有缺陷的分层违规开始,然后试图纠正这种合并产生的问题,使用实用的工程解决方案和解决现实世界问题的密码技术。
对于密钥分发,WireGuard从OpenSSH汲取灵感,其常见用法包括一种非常简单的密钥管理方法。通过一组不同的带外机制,两个对等点通常交换其静态公钥。有时,它很简单,就像PGP签名的电子邮件一样,而有时,它是使用LDAP和证书颁发机构的复杂密钥分发机制。重要的是,大多数情况下,OpenSSH密钥分发完全不可知。 WireGuard也效仿。两个WireGuard对等方通过某种未指定的机制交换其公钥,之后便可以进行通信。换句话说,WireGuard对待密钥分发的态度是,这是解决特定问题的错误层,因此界面足够简单,因此可以与任何密钥分发解决方案一起使用。另外一个优点是,公钥只有32个字节长,并且可以轻松地用44个字符的Base64编码表示,这对于通过各种不同的介质传输密钥非常有用。
同样,WireGuard仅适用于第3层。这是确保数据包真实性和可归属性的最干净的方法。 作者认为,layer 3是桥接多个IP网络的正确方法,将其应用到WireGuard上可以实现许多简化,从而产生一个更清晰、更容易实现的协议。它支持IPv4和IPv6的第3层,可以封装v4-in-v6和v6-in-v4
安全VPN的基本原理是对等节点(peer)之间的关联,每个节点的IP地址都允许用作源IP。在WireGuard中,对等点(peer)严格由它们的公钥(32字节Curve25519点)识别。这意味着在公钥和一组允许的IP地址之间存在一个简单的关联映射。检查下面的密钥路由表:
接口本身有一个私有密钥和一个UDP端口,它在上面侦听,后面是对等点列表。每个对等点(peer)由其公钥标识。每个ip都有一个允许源ip的列表。
有了这个非常简单的原则,管理员可以依赖于简单的防火墙规则。例如,在接口wg0上的一个源IP为10.10.10.230的数据包可以被认为是来自具有gN65…Bz6E公钥的对等方。更一般的情况是,到达WireGuard接口的任何数据包都将具有可靠的可信源IP(当然,这是为了保证传输的完美前向保密)。请注意,这是唯一可能的,因为WireGuard是严格基于第3层。与一些常见的VPN协议(如L2TP/IPsec)不同,在第3层使用经过身份验证的对等点标识可以实现更明确的网络设计。
Configuration 2a
[td]
Interface Public KeyInterface Private KeyListening UDP Port
gN65…z6EAgI6E…fWGE21841
Peer Public Key HIgo…8ykwAllowed Source IPs 0.0.0.0/0
当然,对等点能够在特定的互联网端点向彼此发送加密的WireGuard UDP数据包是很重要的。密钥路由表中的每个对等点可以选择性地预先指定该对等点端点的已知外部IP地址和UDP端口。它是可选的原因是,如果它没有被指定并且WireGuard从一个对等点接收到一个正确的经过身份验证的包,它将使用外部源IP地址来确定端点。
Configuration 2b
[td]
Interface Public KeyInterface Private KeyListening UDP Port
gN65…z6EAgI6E…fWGE21841
Peer Public KeyAllowed Source IPsInternet Endpoint
HIgo…8ykw0.0.0.0/0192.95.5.69:41414
Configuration 1b
[td]
Interface Public KeyInterface Private KeyListening UDP Port
HIgo…8ykwyAnz…fBmk41414
Peer Public KeyAllowed Source IPsInternet Endpoint
xTIB…p8Dg10.192.122.3/32, 10.192.124.0/24
TrMv…WXX010.192.122.4/32, 192.168.0.0/16
gN65…z6EA10.10.10.230/32192.95.5.64:21841
这种设计带来了极大的便利和最小的配置。 虽然拥有活跃中间人的攻击者当然可以修改这些未经身份验证的外部源IP,但是攻击者将无法解密或修改任何有效载荷,这仅相当于拒绝服务攻击, 只需从假定的中间人位置丢弃原始数据包,这已经很容易实现。 而且,无法解密并随后回复数据包的主机将很快被遗忘。
发送和接收的数据流
本地生成(或转发)数据包,并准备在出方向接口wg0上传输:
  • 明文包到达WireGuard接口wg0。
  • 数据包的目的IP地址192.168.87.21被检查,它与对等节点TrMv…WXX0匹配。(如果它没有匹配的对等点,它被丢弃,并且发送者被一个标准的ICMP“no route to host”数据包通知,并且返回-ENOKEY到用户空间。)
  • 与对等方关联的安全会话的发送对称加密密钥和 nonce随机数计数器,
    TrMv … WXX0用于使用ChaCha20Poly1305算法加密明文数据包。
  • 包含在第后续章节中说明的各个字段的标头被放在现在加密的数据包之前。
  • 此标头和加密的数据包一起作为UDP数据包发送到与对等TrMv … WXX0关联的Internet UDP / IP端点,从而导致外部UDP / IP数据包包含标头和加密的内部- 包。 对等端点是预先配置的,或者是从接收到的最新经过正确身份验证的数据包的外部外部源IP标头字段中获悉的。 (否则,如果无法确定端点,则将数据包丢弃,发送ICMP消息,并将-EHOSTUNREACH返回到用户空间。)
可以将允许的IP列表分为两个列表—一个用于检查传入数据包的源地址,另一个用于根据目标地址选择对等方。但是,通过将它们保留在同一列表中,可以实现类似于反向路径过滤的功能。发送数据包时,将根据目标IP查询该列表。当接收到数据包时,将使用相同的列表来确定是否允许源IP。但是,与其询问接收数据包的发送对等方是否具有该源IP作为其允许的IP列表的一部分,它还可以询问一个更全局的问题-在表中为该源IP选择哪个对等方,并且这样做将从匹配的对等方接收数据包。这将强制执行发送和接收IP地址的一对一映射,因此,如果从特定对等方接收到数据包,则将确保对该IP的答复到达该同一个对等方。
基本用法(Basic Usage)
考虑一个具有单个物理网络接口eth0的Linux环境,它使用192.95.5.69的公共IP将其连接到Internet。可以添加WireGuard接口wg0并使用标准ip实用程序进行配置在/ 24子网中具有10.192.122.3的隧道IP地址,如左图所示。然后可以使用wg(8)工具以多种方式配置密钥路由表,包括从配置文件中读取信息,如右图所示:
此时,向该系统上的10.10.10.230发送数据包将通过wg0接口发送数据,该接口将使用与公钥gN65…z6EA相关联的安全会话加密数据包和发送加密和经过UDP封装的数据包到192.95.5.70:54421。当在wg0上收到来自10.10.10.230的数据包时,管理员可以确信它是来自gN65…z6EA。
协议和加密学(Protocol&Cryptography)
WireGuard的一个设计目标是避免在身份验证之前存储任何状态,并且不对未经身份验证的数据包发送任何响应。由于没有存储未经身份验证的数据包的状态,也没有生成响应,因此,WireGuard对非法对等端和网络扫描程序是不可见的。通过不允许未经身份验证的数据包影响任何状态,可以避免几类攻击。而且更普遍的是,有可能以一种根本不需要动态内存分配的方式来实现WireGuard,即使对于经过身份验证的数据包也是如此。但是,此属性要求响应者收到的第一条消息就对发起者进行身份验证。 像这样在第一个数据包中进行身份验证可能会使响应者面临重放攻击的风险。攻击者可以重播初始握手消息,以诱使响应者重新生成其临时密钥,从而使合法发起者的会话无效(尽管不会影响任何消息的保密性或真实性)。为防止这种情况,在第一个消息中包含12字节的TAI64N [7]时间戳,并对其进行加密和身份验证。响应者跟踪每个对等方收到的最大时间戳,并丢弃包含小于或等于该时间戳的数据包。 (实际上,它甚至不必是准确的时间戳;它仅必须是按对等方单调递增的96位数字。)如果响应方重新启动并丢失了此状态,那不是问题:即使初始可以重播较早的数据包,它不可能中断任何正在进行的安全会话,因为响应者刚刚重新启动,因此没有活动的安全会话要中断。一旦发起方在重新发起后与响应方重新建立了安全会话,发起方将使用更大的时间戳,从而使前一个时间戳无效。此时间戳可确保攻击者不会通过重播攻击中断发起者和响应者之间的当前会话。 (这也意味着两个不同的对等点不应该共享私钥,因为在这种情况下,发送给一个对等点的数据包可能会被重放给另一个对等点,随后产生的响应将导致发起者不由自主地从一个对等点漫游到另一个。但无论如何,一开始就不应该共享私钥。)从实现的角度来看,TAI64N[7]非常方便,因为它是big-endian,允许使用标准memcmp()对两个12字节的时间戳进行比较。由于WireGuard不使用签名,为了获得一定程度的可否认性,第一条消息仅依赖于两个对等点静态密钥的Diffie-Hellman结果进行身份验证。这意味着,如果它们中的任何一个静态密钥被破坏,攻击者将能够伪造包含最大时间戳值的初始消息(尽管它无法完成完整的handshap),从而阻止所有未来的连接成功。虽然这看起来类似于传统的密钥泄露模拟漏洞(WireGuard并不是它的弱点),但它实际上非常不同。因为,如果一个密钥泄露使得攻击者能够阻止其他节点再次使用他们泄露的密钥,那么攻击者实际上已经帮助了对这种泄露的正确响应。如果TIA64N时间戳的精度造成了不合适的信息泄漏,实现可能会截断时间戳的24位纳秒部分。
可选的预共享对称密钥模式
WireGuard依赖于先与对等方(peer)彼此交换静态公钥,作为它们的静态身份。发送的所有数据的保密性取决于Curve25519 ECDH功能的安全性。为了减轻将来在量子计算方面的任何进步,WireGuard还支持一种模式,在该模式下,任何对对端都可以在彼此之间预共享一个256位对称加密密钥,以便添加一层对称加密。这里的攻击模型是,攻击者可能会长期记录加密的流量,以期有一天能够打破Curve25519并解密过去的流量。虽然预共享对称加密密钥通常从密钥管理的角度来看很麻烦,并且可能更容易被盗,但是这种想法是,随着量子计算的发展打破了Curve25519,这种长共享对称密钥早已被人们遗忘。而且,更重要的是,在短期内,如果预共享的对称密钥遭到破坏,Curve25519密钥仍然可以提供足够的保护。替代使用完整的后量子密码系统(在撰写本文时不适合在此处使用),此预共享对称密钥的可选混合方法可补充椭圆曲线密码,为用户提供了合理的折衷方案。极度偏执。此外,它还允许在WireGuard上构建复杂的键旋转方案,以实现各种类型的折衷后安全性。
缓解拒绝服务攻击和Cookies
响应方维护一个秘密的随机值,该值每两分钟更改一次。cookie只是使用这个变化的密钥作为MAC密钥计算发起者源IP地址的MAC的结果。发起方在重新发送它的消息时,使用这个cookie作为MAC密钥发送它的消息的MAC。当responder接收到消息时,如果它处于负载状态,它可以根据是否有正确的MAC使用cookie作为密钥来选择是否接受和处理消息。这种机制将从发起者发送的消息绑定到它的IP地址,提供IP所有权的证明,允许使用经典的IP速率限制算法来限制速率(令牌桶等)。
对于第一个问题,为了响应方保持沉默,即使在负载下,所有消息都有一个使用响应方公钥的第一个MAC (msg.mac1)。这意味着,为了引出任何响应,发送消息的对等方至少必须知道它在与谁交谈(通过知道它的公钥)。无论是否加载,第一个MAC (msg.mac1)都必须存在且有效。虽然响应方的公钥本身不是秘密的,但在这个攻击模型中它是足够秘密的,其目标是确保服务的私密性,因此知道响应方的公钥就足以证明已经知道它的存在。(值得注意的是,第一个MAC允许被动攻击者猜测数据包的目的是哪个公钥,这略微削弱了身份隐藏属性,尽管正确的猜测不会构成密码证明,因为在生成MAC时没有使用私有材料。)
最后,为了解决第三个问题,我们使用AEAD的“附加数据”字段对传输中的cookie进行加密,对引发cookie回复消息的初始消息的第一个MAC (msg.mac1)进行附加认证。这确保了没有中间人的攻击者不能发送无效的cookie的种子到发起者,以防止他们验证一个正确的cookie。(攻击者一个中间人的位置可以简单地把cookie应答消息无论如何防止连接,所以这种情况下是不相关的,但攻击者,只是被动的中间人的地位的确可能伪造这些数据包,这不是明显不同于一个拒绝服务攻击的TCP)。换句话说,我们使用AD字段将cookie响应绑定到启动消息。
总之,响应方在计算了这些mac并将其与收到的消息进行比较后,必须总是拒绝带有无效msg的消息。当负载不足时,可能会拒绝带有无效msg.mac2的消息。如果响应者收到一个有效的消息。mac1,但有一个无效的mac2,并在负载下,它可能会响应一个cookie回复消息。这大大改进了DTLS和IKEv2使用的cookie方案。
有四种类型的消息,每一种都以一个单字节消息类型标识符作为前缀,标记为msg.type如下:
  • 开始建立安全会话的握手过程的握手启动消息。
  • 对结束握手的初始化消息的握手响应,在此之后可以建立安全会话
  • 对握手启动消息或握手响应消息的响应,它通信一个加密的cookie值,用于重新发送被拒绝的握手启动消息或握手响应消息。
  • 一种封装和加密的IP包,它使用通过握手协商的安全会话。
协议概述:
在大多数情况下,握手在1-RTT将完成,之后传输数据如下:
如果有一个对等端负载不足,则在握手时添加一条cookie回复消息,以防止拒绝服务攻击:
与所有Linux网络接口一样,WireGuard集成到网络名称空间基础结构中。这意味着管理员可以拥有几个完全不同的网络子系统,并选择每个子系统中都存在哪些接口。
WireGuard使用UDP套接字发送和接收加密数据包。此套接字始终位于命名空间A-原始出生地命名空间中。这允许一些非常酷的属性。也就是说,您可以在一个命名空间(A)中创建WireGuard接口,将其移动到另一个命名空间(B),并通过命名空间A中的UDP套接字。
(请注意,通过在一个名称空间中创建套接字文件描述符,然后再更改为另一个名称空间,并使先前名称空间中的文件描述符保持打开状态,这种相同的技术可用于基于用户空间TUN的接口。)
这提供了一些非常好的可能性。
普通容器:

[/td][td]1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
17: wg0: <OINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1423 qdisc noqueue state UNKNOWN group default qlen 1
    link/none
    inet 192.168.4.33/32 scope global wg0
       valid_lft forever preferred_lft forever
[/td][/tr][/table]


一种不太明显的用法,但功能极其强大,是使用WireGuard的此特性通过WireGuard重定向所有普通Internet流量。但是首先,让我们回顾一下执行此操作的旧的常用解决方案:
经典解决方案:
最简单的技术是仅替换默认路由,为WireGuard端点添加一个明确的规则:
这很有效并且相对简单,但是不幸的是,DHCP守护程序等都希望撤消我们刚才所做的事情。
覆盖默认路由

[/td][td]# ip route add 0.0.0.0/1 dev wg0
# ip route add 128.0.0.0/1 dev wg0
# ip route add 163.172.161.0/32 via 192.168.1.1 dev eth0
[/td][/tr][/table]

有些人更喜欢使用基于规则的路由和多个路由表。工作方式是我们为WireGuard路由创建一个路由表,为明文Internet路由创建一个路由表,然后添加规则来确定使用哪个路由表:
现在,我们能够将路由表分开。不幸的是,缺点是仍然需要添加显式端点规则,并且在删除接口时没有清理,需要更复杂的路由规则重复。
改进的基于规则的路由

[/td][td]# wg set wg0 fwmark 1234
# ip route add default dev wg0 table 2468
# ip rule add not fwmark 1234 table 2468
# ip rule add table main suppress_prefixlength 0
[/td][/tr][/table]

WireGuard的作者有兴趣在内核中添加一个名为notoif的特性来覆盖隧道用例。这将允许接口说“不要用我自己作为接口路由,以避免路由环路。”WireGuard将能够添加像.flowi4_not_oif = wg0_idx这样的行,基于用户空间tun的接口将能够在他们的输出套接字上设置一个选项,比如set so kopt(fd,so_NOTOIF, Tun0_idx);
新的命名空间解决方案

  • 首先,我们创建“physical”网络名称空间:
    # ip netns add physical
  • 现在,将eth0和wlan0移到“physical”名称空间中:

    [/td][td]# ip link set eth0 netns physical
    # iw phy phy0 set netns name physical
    [/td][/tr][/table]
    (请注意,必须使用iw并通过指定物理设备phy0来移动无线设备。)
  • 现在,我们在“physical”名称空间中具有这些接口,而在“init”名称空间中没有接口:

    [/td][td]# ip -n physical link
    1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1
        link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    2: eth0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN mode DEFAULT group default qlen 1000
        link/ether ab:cd:ef:g1:23:45 brd ff:ff:ff:ff:ff:ff
    3: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DORMANT group default qlen 1000
        link/ether 01:23:45:67:89:ab brd ff:ff:ff:ff:ff:ff


    # ip link
    1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1
        link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    [/td][/tr][/table]
  • 现在,我们将WireGuard接口直接添加到“physical”名称空间:
    # ip -n physical link add wg0 type wireguard
    wg0的出生地命名空间现在是physical命名空间,这意味着密文UDP套接字将分配给eth0和wlan0等设备。我们现在可以将wg0移动到init命名空间中;然而,它仍然会记得套接字的出生地。
  • 我们将“ 1”指定为“ init”命名空间,因为这是系统上第一个进程的PID。现在,“ init”名称空间具有wg0设备:

    [/td][td]# ip -n physical link set wg0 netns 1
    # ip link
    1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1
        link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    17: wg0: <OINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1423 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1
        link/none
    [/td][/tr][/table]
  • 现在,我们可以使用普通工具配置物理设备,但是我们在“physical”网络名称空间内启动它们:

    [/td][td]# ip netns exec physical dhcpd wlan0
    # ip netns exec physical wpa_supplicant -iwlan0 -c/etc/wpa_supplicant/wpa_supplicant.conf
    # ip -n physical addr add 192.168.12.52/24 dev eth0
    [/td][/tr][/table]
  • 依此类推。最后,我们可以像平常一样配置wg0接口,并将其设置为默认路由:

    [/td][td]# wg setconf wg0 /etc/wireguard/wg0.conf
    # ip addr add 10.2.4.5/32 dev wg0
    # ip link set wg0 up
    # ip route add default dev wg0
    [/td][/tr][/table]
  • 完成了!在这一点上,系统上的所有普通进程都将通过“init”命名空间路由它们的数据包,该命名空间只包含wg0接口和wg0路由。然而,wg0的UDP套接字位于physical命名空间中,这意味着它将把流量从eth0或wlan0发送出去。正常的进程甚至不会意识到eth0或wlan0,除了dhcpcd和wpa_Request icant,它们是在“physical”命名空间中生成的。
  • 然而,有时您可能希望打开一个网页或使用physical命名空间快速执行某些操作。例如,也许你计划像往常一样通过WireGuard路由所有的流量,但是你所在的咖啡店要求你在它之前使用网站进行身份验证会给你一个真正的互联网链接。因此,可以使用物理接口执行选择进程(作为您的本地用户):

    [/td][td]$ sudo -E ip netns exec physical sudo -E -u \#$(id -u) -g \#$(id -g) chromium
    [/td][/tr][/table]
    当然,可以将它做成.bashrc的一个不错的函数:

    [/td][td]g) "$@"; }physexec() { sudo -E ip netns exec physical sudo -E -u \#$(id -u) -g \#$(id -g) "$@"; }
    [/td][/tr][/table]
  • 现在,您可以编写以下代码在“physical”名称空间中打开chromium。

    [/td][td]$ physexec chromium
    [/td][/tr][/table]
  • 当您完成登录咖啡店网络的操作后,像往常一样生成一个浏览器,然后平静地冲浪,知道您的所有流量都受到WireGuard的保护:
    $ chromium
以下示例脚本可以另存为/usr/local/bin/ wgphys,并用于诸如wgphys up,wgphys down和wgphys exec之类的命令:
wireguard支持各平台。安装教程可参考官网教程。
https://www.wogong.net/blog/2019/01/how-to-configure-wireguard
https://lijiangwei.github.io/2019/06/17/wireguard/

[/td][td][root@centos7 wireguard]# more wg0.conf  
[Interface]
PrivateKey = xxxx
PostUp   = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; i
ptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; i
ptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
Address = 192.168.2.1/32
ListenPort = 51820
DNS = 8.8.8.8
MTU = 1420




[Peer]
PublicKey = xxx
AllowedIPs = 192.168.2.2/32
[/td][/tr][/table]

如果需要服务端转发流量才需要配置PostUp,PostDown,只配置点对点通信的话不需要。

[/td][td][root@localhost wireguard]# more wg0.conf
[Interface]
Address = 192.168.2.2/32
PrivateKey = XXX
ListenPort = 21841


[Peer]
PublicKey = XXX
Endpoint = 140.143.154.78:51820
AllowedIPs = 192.168.2.1/32, 0.0.0.0/0


# This is for if you're behind a NAT and
# want the connection to be kept alive.
PersistentKeepalive = 25
[/td][/tr][/table]

要想服务端转发客户端所有流量,在配置文件添加0.0.0.0/0即可。
其他链接推荐:
https://itigic.com/zh-CN/wireguard-vpn-installation-and-configuration/


原文: https://cshihong.blog.csdn.net/article/details/109589352


[ 本帖最后由 linda 于 2021-1-20 17:27 编辑 ]

TOP

发新话题