WireGuard 教程:使用 DNS-SD 进行 NAT-to-NAT 穿透

原文链接:https://fuckcloudnative.io/posts/wireguard-endpoint-discovery-nat-traversal/

WireGuard 教程:使用 DNS-SD 进行 NAT-to-NAT 穿透

WireGuard 是由 Jason A. Donenfeld 等人创建的下一代开源 VPN 协议,旨在解决许多困扰 IPSec/IKEv2、OpenVPN 或 L2TP 等其他 VPN 协议的问题。2020 年 1 月 29 日,WireGuard 正式合并进入 Linux 5.6 内核主线。

WireGuard 教程:使用 DNS-SD 进行 NAT-to-NAT 穿透

利用 WireGuard 我们可以实现很多非常奇妙的功能,比如跨公有云组建 Kubernetes 集群,本地直接访问公有云 Kubernetes 集群中的 Pod IP 和 Service IP,在家中没有公网 IP 的情况下直连家中的设备,等等。

如果你是第一次听说 WireGuard,建议你花点时间看看我之前写的 WireGuard 工作原理。然后可以参考下面两篇文章来快速上手:

WireGuard 快速安装教程

WireGuard 配置教程使用 wg-gen-web 来管理 WireGuard 的配置

如果遇到某些细节不太明白的,再去参考 WireGuard 配置详解。

本文将探讨 WireGuard 使用过程中遇到的一个重大难题:如何使两个位于 NAT 后面(且没有指定公网出口)的客户端之间直接建立连接。

WireGuard 不区分服务端和客户端,大家都是客户端,与自己连接的所有客户端都被称之为 Peer。

1. IP 不固定的 Peer

WireGuard 的核心部分是,它的工作原理是将公钥和 IP 地址列表(AllowedIPs)关联起来。每一个网络接口都有一个私钥和一个 Peer 列表,每一个 Peer 都有一个公钥和 IP 地址列表。发送数据时,可以把 IP 地址列表看成路由表;接收数据时,可以把 IP 地址列表看成访问控制列表。

公钥和 IP 地址列表的关联组成了 Peer 的必要配置,从隧道验证的角度看,根本不需要 Peer 具备静态 IP 地址。理论上,如果 Peer 的 IP 地址不同时发生变化,WireGuard 是可以实现 IP 漫游的。

现在回到最初的问题:假设两个 Peer 都在 NAT 后面,且这个 NAT 不受我们控制,无法配置 UDP 端口转发,即无法指定公网出口,要想建立连接,不仅要动态发现 Peer 的 IP 地址,还要发现 Peer 的端口。

找了一圈下来,现有的工具根本无法实现这个需求,本文将致力于不对 WireGuard 源码做任何改动的情况下实现上述需求。

2. 中心辐射型网络拓扑

你可能会问我为什么不使用中心辐射型(hub-and-spoke)网络拓扑?中心辐射型网络有一个 VPN 网关,这个网关通常都有一个静态 IP 地址,其他所有的客户端都需要连接这个 VPN 网关,再由网关将流量转发到其他的客户端。假设 Alice 和 Bob 都位于 NAT 后面,那么 Alice 和 Bob 都要和网关建立隧道,然后 Alice 和 Bob 之间就可以通过 VPN 网关转发流量来实现相互通信。

WireGuard 教程:使用 DNS-SD 进行 NAT-to-NAT 穿透

其实这个方法是如今大家都在用的方法,已经没什么可说的了,缺点相当明显:

当 Peer 越来越多时,VPN 网关就会变成垂直扩展的瓶颈。

通过 VPN 网关转发流量的成本很高,毕竟云服务器的流量很贵。

通过 VPN 网关转发流量会带来很高的延迟。

本文想探讨的是 Alice 和 Bob 之间直接建立隧道,中心辐射型(hub-and-spoke)网络拓扑是无法做到的。

3. NAT 穿透

要想在 Alice 和 Bob 之间直接建立一个 WireGuard 隧道,就需要它们能够穿过挡在它们面前的 NAT。由于 WireGuard 是通过 UDP 来相互通信的,所以理论上 UDP 打洞(UDP hole punching) 是最佳选择。

UDP 打洞(UDP hole punching)利用了这样一个事实:大多数 NAT 在将入站数据包与现有的连接进行匹配时都很宽松。这样就可以重复使用端口状态来打洞,因为 NAT 路由器不会限制只接收来自原始目的地址(信使服务器)的流量,其他客户端的流量也可以接收。

举个例子,假设 Alice 向新主机 Carol 发送一个 UDP 数据包,而 Bob 此时通过某种方法获取到了 Alice 的 NAT 在地址转换过程中使用的出站源 IP:Port,Bob 就可以向这个 IP:Port(2.2.2.2:7777) 发送 UDP 数据包来和 Alice 建立联系。

WireGuard 教程:使用 DNS-SD 进行 NAT-to-NAT 穿透

其实上面讨论的就是完全圆锥型 NAT(Full cone NAT),即一对一(one-to-one)NAT。它具有以下特点:

一旦内部地址(iAddr:iPort)映射到外部地址(eAddr:ePort),所有发自 iAddr:iPort 的数据包都经由 eAddr:ePort 向外发送。

任意外部主机都能经由发送数据包给 eAddr:ePort 到达 iAddr:iPort。

大部分的 NAT 都是这种 NAT,对于其他少数不常见的 NAT,这种打洞方法有一定的局限性,无法顺利使用。

4. STUN

回到上面的例子,UDP 打洞过程中有几个问题至关重要:

Alice 如何才能知道自己的公网 IP:Port?

Alice 如何与 Bob 建立连接?

在 WireGuard 中如何利用 UDP 打洞?

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zydgwd.html