问题是在 The benefits of birthdays 这一章节:
Rather than open 1 port on the hard side and have the easy side try 65,535 possibilities, let’s open, say, 256 ports on the hard side (by having 256 sockets sending to the easy side’s ip:port), and have the easy side probe target ports at random.
基础知识
两台机器在不同的地方使用 tailscale 组网,tailscale 是基于 WireGuard 的。
机器 A 的内网 ip 192.168.1.2, 公网 ip 2.2.2.2
机器 B 的内网 ip 192.168.2.2, 公网 ip 3.3.3.3
假设 A 是 Hard NAT(Symmetric NAT ), 而 B 是 Easy NAT ( Fullcone NAT )。
对于 Hard NAT 和 Fullcone NAT 的一点解释:
A 的某个程序使用 192.168.1.2:1234 向 4.4.4.4:5678 发出一个请求, 那么在 NAT 映射后,是 2.2.2.2:4321 发往 4.4.4.4:5678 ,这样 4.4.4.4:5678 返回的请求,到达 NAT 也会被正常映射到设备 A 的 1234 端口。这是由 iptables -A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT 这种防火墙规则决定的。
但是对于 Hard NAT 的 A 来讲,如果是 5.5.5.5:5678 向 A 的 2.2.2.2:4321 发起一条请求,NAT 会检查源地址不是 4.4.4.4:5678 ,所以会 drop 掉这一条请求。
但对于 B 来讲,192.168.2.2:1234->3.3.3.3:4321 -> 4.4.4.4:5678, 建立了一个这样的 NAT 表,当其他的设备比如 5.5.5.5:9876 主动请求 3.3.3.3:4321 端口的时候,也会被 NAT 转发到 192.168.2.2:1234 ,这就是 Fullcone NAT ,表现为很容易 p2p 连接成功。
回到 The benefits of birthdays
假设 B 机器想要连接 A 机器,由于 A 是 Hard NAT ,无法直接选择一个合适的端口进行连接,但反过来是可以的,因为每一台运行 tailscale 程序的设备来讲,都会和 DERP 服务器进行着连接。
不管是谁连接谁,都先走一下 DERP 中继,这样起码就能获取到每一方的外部端口。理想情况下,两端都是 Easy NAT ,那它们使用连接 DERP 服务器的端口进行直连,基本上就可以 p2p 连接成功。
但 A 是 Hard NAT ,于是(下面是我的猜测) DERP 服务器将 B 的 ip 和端口告诉了 A ,虽然是 B 想主动连接 A ,但 tailscale 程序的实现是两端都进行尝试连接,这样 A 直连成功了 B ,192.168.1.2:1234->2.2.2.2:5678->3.3.3.3:8765->192.168.2.2:4321 ,这样它俩就建立了正常的直连。
文章中的举例是需要让 A 开 256 个端口连接 B ,B 通过遍历端口进行和 A 连接,当尝试了 1024 次时候,能和 A 成功连接的概率是 98% (生日悖论)。
我的疑问是,为什么需要 A 开很多端口进行尝试探测?我的猜测那种方案有什么不能实现的地方吗?
真实的实践
我尝试过很多次,对于复杂的网络环境,tailscale 有时候能打洞成功,有时候会失败。而且两端都是 Fullcone NAT, 并且双方都有 ipv6 地址。