你添加了一条策略路由,把某个 VPN 客户端的流量送到运行 SOCKS 监听器的主机 1080 端口上。数据包到了,监听器却毫无记录,连接一直挂着。路由本身没有问题:下一跳只决定包往哪里去,而 SOCKS 是必须由客户端主动发起的会话。把人们常说的三种“通过代理发送流量”的架构放在一起比较,你的配置缺了哪一块就很清楚了。

路由、DNAT 和 SOCKS 改的是不同的东西

策略路由改写的是一项第 3 层决策。把单个客户端的流量送到选定的下一跳,两行命令就够了:

ip route add default via 10.50.0.2 table 101
ip rule add from 10.77.0.2 lookup 101

之后内核把数据包交给 10.50.0.2,原始目的地址仍留在 IP 首部里。如果 10.50.0.2 是路由器,它会转发。如果它是 1080 上运行 SOCKS 守护进程的主机,这个包并没有被发给任何能接收它的东西,DNAT 到 1080 端口同样无效,因为转换后的包携带的仍是原始应用载荷,而不是 SOCKS 问候。

SOCKS5 以版本字节、方法列表、认证交换和一个指明目的地址的请求开始。那是应用数据。任何路由表项和 netfilter 目标都不会生成它,一句话的对比就是:路由选择路径,SOCKS 请求服务器再建立一条连接。

情形一:应用自己会说 SOCKS

程序如果有代理设置,这是最短的路径,也不需要动内核。把它指向监听器,服务器要求时提供凭据,并决定域名是在本地解析还是交给代理解析。在 curl 里这是不同的开关,而当地址只能由代理侧解析时,这个差别很关键:

curl --socks5 10.50.0.2:1080 https://docs.example.com
curl --socks5-hostname 10.50.0.2:1080 https://internal.example.com

忽略代理环境变量、或者直接打开原始套接字的应用,没法用这个办法。浏览器和大多数 HTTP 工具没问题;游戏客户端、邮件守护进程或厂商的任意二进制通常不行。边界是按应用、按协议划的,所以这个选项做测试很好,作为整个 VPN 的答案却不够。

情形二:把 IP 流转成代理会话的适配器

对不会说 SOCKS 的程序,转换必须发生在某个终止 IP 流量、并替它们打开代理会话的组件里。这个组件拥有一个隧道接口,接收策略路由选出的流,为每条流完成 SOCKS 握手,再把回应重建出来。路由把流量指向它;真正建立会话的是适配器。

把整个子网交给它之前,先读它的文档。很多适配器对 TCP 支持良好,UDP 只能通过 SOCKS5 的 UDP 关联处理,而它们都不会把 ICMP 变成代理流量,所以 ping 成功并不说明代理通路可用。DNS 是另一个陷阱:如果适配器在本地解析域名,只能内网解析的记录会被解析成错误答案,或者什么都没有。

防环是真正会咬人的运维细节。适配器自己连往 SOCKS 服务器的连接不能再被路由回适配器,所以它的流量需要在规则里开例外,或者使用专用源地址。这里做错,代理会话就会试图代理它自己。

情形三:主机其实是被路由的网关

有时能跑通的配置里根本没有 SOCKS。远端主机转发数据包或对它们做 NAT,基于源地址的规则只是把它选作出口。这种设计是正当的,也解释了为什么同一份配置在一个网络里能用、在另一个网络里失败:规则完全相同,缺的是对端的转发服务。

三种情形很快就能分开。先看实际在监听什么,再看到达主机的头几个字节:

ss -ltnp 'sport = :1080'
tcpdump -ni eth0 -X 'tcp port 1080'

真正的 SOCKS 客户端第一个载荷字节是 0x05,也就是问候里的版本号。如果抓包显示的是原始应用数据,那就不会有任何 SOCKS 守护进程应答,因为协商从未开始。如果 1080 上没有任何东西监听,这台主机就是网关,问题就变成它是否转发、是否存在回程路径。

用四个问题选层

程序有代理设置吗?有就用它,到此为止。流量里有没有必须存活的 UDP 或 ICMP?只有被路由或 NAT 的网关才承载 ICMP,而 UDP 需要两端都支持的 SOCKS5 关联。机器上每个进程都要覆盖,包括你无法配置的那些吗?那就需要一个拥有接口、并终止流的适配器。它的路由、DNS 和防环例外你也能一并接管吗?如果不能,普通网关的运维成本更低。

有一个区分值得说清楚。VPN 客户端自己的 socks-proxy 选项描述的是外层隧道连接如何到达它的服务器,与转换内层客户端流量毫无关系。这两件事在同一个工单里总是被混淆。动路由之前,先确定你需要的是哪种关系。

相关文章