这些是必须掌握的基本概念,实际上非常简单。
它不是最底层的形式,这里无需深究,重点是知道它的特性。IP 数据包无法直接提供可靠传输,这对应用来说当然是很不方便的,于是就有了面向连接的 TCP 协议,它基于 IP 数据包,但实现了一套连接和可靠传输机制,大多数其它协议直接放 TCP 上面即可。
确定一个 TCP 连接的是“五元组”:
TCP 连接建立后,双方便可从自己的端口向对方的端口发送应用数据,这是全双工的,即双方都可以一边发送数据、一边接收数据。
“端口”这个概念它常和 IP 一起出现,但实际上它不属于 IP 协议,它属于更上层的 TCP、UDP 协议。TCP、UDP 是分别实现了“端口”这一标识方式,所以这两个协议的“端口”不会互相影响。
ping 用到的协议是 ICMP,它也是基于 IP 数据包,与 TCP、UDP 是类似的,但 ICMP 就没有“端口”这个概念。顺便提一句,常见的代理协议只能代理 TCP 或加个 UDP,不能代理 ICMP,所以就无法 ping,有此需求请用传统的 VPN。
虽然 UDP 和 TCP 一样是基于 IP 数据包的,但它异常简单,直接完全继承了 IP 数据包的特性:
可以这样简单理解:UDP 协议就只是在 IP 协议的基础上加了一个端口机制和校验而已。
对于 UDP 而言,TCP 的“连接”机制也是不存在的,即申请到一个本地 UDP 端口后,不需要握手/建立连接即可直接向任意 IP 的任意 UDP 端口发送应用数据。 不需要关心对方有没有收到数据,对方也不会告诉你有没有收到数据。(需要指出:存在一种 connected UDP 的中间状态,这只是在发送数据前确定一下目标地址,并没有真正去握手)
UDP 的这些特性催生了三种应用方式:
当然还有另一种对 UDP 的应用方式:基于 UDP 造新的通用可靠传输协议,比如 KCP、QUIC。为什么这些新协议不直接基于 IP 协议而要基于 UDP 协议?因为前者往往需要各级运营商进行设备、系统改造来支持,这显然不太现实,所以 UDP 成了更合适的选择。
这两个指的都是 NAT 行为,NAT 的全称为 Network Address Translation,就是家路由器、各级运营商做的事情:地址转换。NAT 的广泛存在是因为 IPv4 地址不足,另一方面它还可以保护局域网中的设备。
对于 TCP 而言,NAT 行为是什么并不重要,因为 TCP 是双向的流,本机每发起一个 TCP 连接往往会使用一个新的临时端口,从而对应一个新的五元组。
但对于 UDP,NAT 行为可太重要了,因为 UDP 是方向不定的包,使用同一个本地 UDP 端口向不同的目标二元组发包十分常见。
二元组:IP 和 Port,任一不同即视为不同的二元组 |
那么这种情况下 UDP 数据包到达路由器后,路由器要怎样转发它呢?这就取决于路由器的 NAT 行为了。
其实 NAT 行为多种多样,这里先举例介绍最具代表性的情况。首先有以下情景:
如果由于目标二元组不同,路由器把 A->M、A->N 分别映射成了自己的 A1->M、A2->N(一般为分别使用两个不同的端口发包),且严格限制回包来源,就属于 Symmetric。不难发现,这时候实际上变成了类似 TCP “连接”的通信模式,也是大多数运营商的做法。
而若路由器只看来源二元组 A,始终映射成自己的 A1 向 M、N 发包,就属于 Cone NAT;更进一步,如果 A1 收到了回包,路由器不管来源,直接把这个包发回给 A,就属于 FullCone,也是代理类软件能实现的最佳 NAT 等级、P2P 游戏必备神器(GTA NAT 开放)
上面是简单举例,现实中运营商大概率不会主动分配给你一个公网 IP,也就是说还需要经过层层 NAT,最终得到 Symmetric 很正常。所以为什么你用了代理协议,比如 Xray-core 的 Shadowsocks、Trojan 就可以获得 FullCone?
很简单,因为此时用到的是你的 VPS 的公网 IP,和你本地的 NAT 环境没有任何关系。
这就是为什么要特殊设置 VPS 的防火墙:它默认会过滤返回的包的来源,导致你只能得到某种 Restricted Cone 而不是 FullCone。
对于简单的 UDP 需求比如 DNS 查询,Symmetric 也不是不能用。但对于复杂的 UDP 需求,比如各类 P2P 场景,实现 FullCone 就非常重要了,因为应用程序需要对外使用一个固定的端口,通过这个端口不受限制地往任何目标发包、从任何目标收包(至少别测错了当前的 NAT 类型,后文会说明)。如果你主要是为了打游戏,可以让 UDP 走 SS 协议,因为它拥有原生 UDP 的特性。
这里提一下,Xray-core 正计划着推出更适合打游戏的协议。
Xray-core 同时支持 FullCone 和 Symmetric 两种模式,且对协议的支持也非常全面,是很理想的例子。
完美支持 FullCone 的有:
仅支持 Symmetric 的有:
众所周知 v2ray 对 UDP 的支持一言难尽,所以 Xray-core 是重构了相关架构和各个出入站的代码,外加反复测试和对很多细节问题的定位、修复,才实现了全面 FullCone 化(除非协议不支持,这种情况会为它准备没问题的 Symmetric,Clash 的 VMess 存在问题)
Xray-core 的 release note 都很有营养,再摘抄一段:
补充:Socks、SS 是原生 UDP,套 TLS/WSS 后它们的 UDP 并没有被特殊处理,除非开了 Mux。SS 的 SIP003 插件也不管 UDP。 UDP over TCP 简称 UoT,特别注意,即使你用 mKCP、QUIC 作为底层传输方式,UoT 的也并不会表现出原生 UDP 的那些特性。 |
这里主要是解惑,让 UDP 不再玄学。
对于第 3 点,简单来说是这样:
这种行为是预期之外的,再加上一些不标准的测试服务器,就会导致能给 v2ray、VMess 测出 FullCone,实际上却完全不起作用。
此外,Google 的一些应用会先自己测一下当前网络的 NAT 类型,若测出了假的 FullCone,就会导致奇奇怪怪的问题。
相信你已经发现了,NAT 行为并不只有 FullCone、Symmetric 这两种(但这是最极端的两种),实际上 NAT 行为由“发包时映射”和“收包时过滤”这两个行为来共同确定,FullCone 就是两者都最开放,Symmetric 就是两者都最严格,引用一张图:
可以看到,RFC 3489 定义了四种经典的 NAT 行为,v2ray 实际上不属于其中的任何一种,但它最接近 RFC 5780 的 Address and Port-Dependent Mapping 加 Endpoint-Independent Filtering,即图中的 NAT Type 7,只是可能会把返回的包的来源搞错。
这是本文的核心内容,其实原理很简单:把你本地的一个 UDP 端口映射为 VPS 的一个 UDP 端口,并使它们具有相同的效果。
拿一个最简单的场景举例:Socks 入站 + Freedom 出站
当然,FullCone 还需要调用方按常理使用 Socks 代理协议,诸如各种 tun2socks 实现一般是没问题的。
下面插一个 Shadowsocks 出入站进来:
Trojan 协议的 UDP 也是类似的原理,不同之处是每个来源二元组都会对应一条 TCP 连接,在 TCP 上传输 UDP 的“载荷与目标”。
那么为什么同样是 UoT 的 VMess 却无法实现 FullCone?
根本原因是 VMess 的 UoT 协议结构只能在最开始时传一个“目标”,后面的多个数据包只能传“载荷”而不能带“目标”,服务端会把后续的数据包都发往最开始的“目标”。服务端向客户端返回数据包是同理的,协议结构只有“载荷”,客户端会认为返回的数据包都来自于最开始的“目标”,这和 v2ray 设计上的问题倒是一脉相承的,自带的 Mux 当然也是这样。(VLESS 也没有幸免,不过在改了)
所以对于 VMess、Mux、VLESS,Xray-core 目前是按 Symmetric 来路由的。否则如果是 FullCone 模式,后面的包都会被发到第一个包的目标地址,这就是 Clash 的 VMess 存在的问题,但并不好解决。此外,存在此问题时 VMess 又会被测出 FullCone,假的。
为了让游戏机用上 Xray-core 实现 FullCone,通常需要一个 Linux 设备来透明代理,一个树莓派就可以搞定。
为什么透明代理 UDP 只能 TRPOXY 而不推荐 REDIRECT?
等需要往回发包时,代理软件会先在本地伪造出“返回的 UDP 包的来源二元组”的 socket,用这个 socket 把包发回去(这个原理就是可能会遇到 too many open files 的原因)。相比于其它软件,Xray-core 对这里有专门的优化,更优雅且有更好的性能。
若你在用 Windows 测透明代理的 NAT,一定注意要把当前网络设为 专用网络,这是很多人踩过的大坑,我也踩过。
提一下 QUIC:启用了 Xray-core 的 XTLS 时,通往 UDP 443 端口的流量默认会被拦截(拦截 QUIC),这样应用就不会使用 QUIC 而会使用 TLS,XTLS 才会真正生效。实际上,QUIC 本身也不适合被代理,因为 QUIC 自带了 TCP 的功能,UoT 就相当于两层 TCP 了。