你希望只有 SOCKS 代理的流量走 VPN,而主机其余部分保持原有路径。两种设计都能做到,区别在于出问题时坏掉的东西不同。一种把默认路由推进隧道,然后绕开它;另一种不动主表,把一个源地址送进第二张路由表。后者更干净,但它也有自己的陷阱。

两种设计,两种不同的爆炸半径

全隧道设计是多数 VPN 客户端的默认做法:替换默认路由,让所有流量跟随隧道,再把不该走隧道的东西排除掉。它容易推理,却很难保持范围狭窄,因为你每增加一个本地服务,它都会继承隧道,直到你把它排除。故障面很宽,因为一条错误的路由会连带主机自身的连通性一起断掉。

源地址选择设计走的是相反方向。主表保持原样,一条策略规则把来自某个本地地址的报文送进一张带隧道默认路由的表。其他一切都不变,也就是说爆炸半径只有一个地址。

这种狭窄同时也是限制:只有当代理的出站报文确实携带该地址时,这套设计才成立。

Dante 的 external 设置做了什么

external 设置选择代理用于出站连接的地址,它不触碰内核的路由策略。正是这种分离让第二种设计成为可能,也正是这里容易误读文档。把 external 指向隧道地址,会让代理以该地址作为出站连接的源地址,之后再由路由表决定这些报文去哪里。

监听器是另一回事。把 internal 限制在应当使用代理的客户端上,要求认证,并用防火墙封住端口,因为开放监听并不是 VPN 出口的特性。

确认配置的地址在代理的网络命名空间里确实属于该隧道。

构建源地址选择的路由表

对于 10.90.0.2/24 的 TUN 地址、10.90.0.1 上可达的对端,以及一个没有别的用途的表号,配置大致如下:

ip -4 route add table 100 10.90.0.0/24 dev tun0 src 10.90.0.2
ip -4 route add table 100 default via 10.90.0.1 dev tun0 metric 10
ip -4 route add table 100 unreachable default metric 32760
ip -4 rule add priority 1000 from 10.90.0.2/32 lookup 100

不可达路由是人们常常跳过的那部分。没有它,隧道路由一旦消失,查表就会落到普通默认路由上,代理便悄悄从 WAN 出去。有了它,查表会失败,这是可见的故障,而不是安静的泄漏。规则的优先级也必须排在任何匹配该源地址的条目之前。

这些数值只描述一种拓扑。点对点对端、net30 编址以及分配不同前缀的客户端都需要各自的路由,所以要照抄形状,而不是地址。

源地址规则会在哪里泄漏

匹配源地址的规则会影响所有使用该地址的进程,而不只是代理。如果另一个守护进程绑定了同一个地址,它的流量也会走隧道,故障看起来就像某个无关服务的问题。给代理一个没有别人使用的地址,或者接受范围是地址而不是进程。

DNS 是另一处泄漏。解析器的套接字未必使用代理的 external 数据连接源地址,因此域名查询仍可能从 WAN 出去。把解析与建立连接分开测试,并判断这个结果是否可以接受。

IPv6 需要自己的规则和路由,因为 IPv4 的配置并不能证明它的情况。

按你能承受的故障来选择

如果主机需要整体保护,并且你能维护排除列表,就选全隧道。如果主机必须保留自己的路径,而只需要改道一个服务,就选基于源地址的路由。两者都合理;错误在于选了其中一种,却按另一种方式去测试。

无论选哪种,都要从两侧验证:一次经代理的请求,和一次从主机直接发出的请求,并分别记录观察到的出口地址。

如果隧道断开,事先决定代理应当失败还是回退,并让路由表表达这个决定。

相关文章