绝大多数人对代理协议中的 UDP 部分完全没概念,目前很多经验丰富的使用者甚至是开发者一遇到 UDP 就变成小白,导致大多数关于 UDP 的问题悬而不决。鉴于 UDP 正扮演着越来越重要的角色,却没有一篇文章讲代理协议中的 UDP,我干脆写了这篇文章。
这篇文章的目的就是扭转现状,让大家完全参透 UDP,以便更游刃有余地使用Xray-core或其它代理类软件。 —— RPRX
IP Packet:一个个符合 IP 协议的数据包,允许丢包,允许乱序(即接收时的顺序不同于发送时的顺序),属于非可靠传输。
它不是最底层的形式,这里无需深究,重点是知道它的特性。IP 数据包无法直接提供可靠传输,这对应用来说当然是很不方便的,于是就有了面向连接的 TCP 协议,它基于 IP 数据包,但实现了一套连接和可靠传输机制,大多数其它协议直接放 TCP 上面即可。
TCP 连接建立后,双方便可从自己的端口向对方的端口发送应用数据,这是全双工的,即双方都可以一边发送数据、一边接收数据。
“端口”这个概念各位都不陌生,它常和 IP 一起出现,但实际上它不属于 IP 协议(没想到吧.jpg),它属于更上层的 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 造新的通用可靠传输协议,比如 KCP、QUIC。为什么这些新协议不直接基于 IP 协议而要基于 UDP 协议?因为前者往往需要各级运营商进行设备、系统改造来支持,这显然不太现实,所以 UDP 成了更合适的选择。
这两个指的都是 NAT 行为,NAT 的全称为Network Address Translation,就是你家路由器、各级运营商做的事情:地址转换。NAT 的广泛存在是因为 IPv4 地址不足,另一方面它还可以保护局域网中的设备。
对于 TCP 而言,NAT 行为是什么并不重要,因为 TCP 是双向的流,本机每发起一个 TCP 连接往往会使用一个新的临时端口,从而对应一个新的五元组。
但对于 UDP,NAT 行为可太重要了,因为 UDP 是方向不定的包,使用同一个本地 UDP 端口向不同的目标二元组发包十分常见。
那么这种情况下 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 的特性。
众所周知 v2ray 对 UDP 的支持一言难尽clashx 设置白名单,所以 Xray-core 是重构了相关架构和各个出入站的代码,外加反复测试和对很多细节问题的定位、修复,才实现了全面 FullCone 化(除非协议不支持,这种情况会为它准备没问题的 Symmetric,Clash 的 VMess 存在问题)
UDP over TCP 简称 UoT,特别注意,即使你用 mKCP、QUIC 作为底层传输方式,UoT 的也并不会表现出原生 UDP 的那些特性。
正常的 Symmetric NAT,A 对 M 发过包clash修改ip地址,只能收到从 M 返回的包,其它的会被过滤掉
如果中间插一个 v2ray,即使 A 没对 N 发过包,A 也能收到 N 发过来的包
重点是此时 N 的地址被丢掉了,A 会以为这个包是 M 发给它的,绝无仅有的迷惑行为
这种行为是预期之外的clash修改ip地址,,再加上一些不标准的测试服务器,就会导致能给 v2rayclashroyale卡组查询、VMess 测出 FullCone,实际上却完全不起作用。
去年七月底我在某个开发者群内说过这个问题(不标准的测试服务器比如 Google 的那些,以及 v2ray UDP 的迷惑行为),随后NatTypeTester的更新只保留了五个标准的测试服务器,并特意验证了返回的包的源地址,测 v2ray 会显示 UnsupportedServer。
此外,Google 的一些应用会先自己测一下当前网络的 NAT 类型,若测出了假的 FullCone,就会导致奇奇怪怪的问题。
相信你已经发现了,NAT 行为并不只有 FullCone、Symmetric 这两种(但这是最极端的两种),实际上 NAT 行为由“发包时映射”和“收包时过滤”这两个行为来共同确定,FullCone 就是两者都最开放,Symmetric 就是两者都最严格,引用一张图:
这是本文的核心内容,其实原理很简单:把你本地的一个 UDP 端口映射为 VPS 的一个 UDP 端口,并使它们具有相同的效果。
当然,FullCone 还需要调用方按常理使用 Socks 代理协议,诸如各种 tun2socks 实现一般是没问题的。
Trojan 协议的 UDP 也是类似的原理,不同之处是每个来源二元组都会对应一条 TCP 连接,在 TCP 上传输 UDP 的“载荷与目标”。
根本原因是 VMess 的 UoT 协议结构只能在最开始时传一个“目标”,后面的多个数据包只能传“载荷”而不能带“目标”,服务端会把后续的数据包都发往最开始的“目标”。服务端向客户端返回数据包是同理的,协议结构只有“载荷”,客户端会认为返回的数据包都来自于最开始的“目标”,这和 v2ray 设计上的问题倒是一脉相承的,自带的 Mux 当然也是这样。(VLESS 也没有幸免,不过在改了)
所以对于 VMess、Mux、VLESS,Xray-core 目前是按 Symmetric 来路由的。否则如果是 FullCone 模式,后面的包都会被发到第一个包的目标地址,这就是 Clash 的 VMess 存在的问题,但并不好解决。此外,存在此问题时 VMess 又会被测出 FullCone,假的。
为了让游戏机用上 Xray-core 实现 FullCone,通常需要一个 Linux 设备来透明代理,一个树莓派就可以搞定。
等需要往回发包时,代理软件会先在本地伪造出“返回的 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 了。