同样的服务器 IP,两条路径:标记 OpenVPN 的外层流量
把 VPN 服务器排除在外的宿主路由,也会把发往它的 HTTPS 排除在外。改为标记加密信封,再把隧道交给未标记的流量。
常见的环路保护是一条宿主路由:把发往 VPN 服务器公网地址的流量从上行链路送出去,好让加密会话永远不会进入它自己的隧道。这招一直管用到你想通过隧道访问同一台服务器上的 Web 服务为止,因为豁免是按目的地绑定的,而目的地完全相同。出路是别再按目的地分类,而是按哪个套接字发出了这个包来分类。
从你想分开的两条流开始
一条流是加密的 OpenVPN 传输本身,它必须走上行链路出去,否则隧道就会承载自己的外层连接。另一条是发往同一公网地址的普通 HTTPS 请求,你想让它待在隧道里,以便从隧道源地址到达服务。目的地相同,很多时候端口号也相同,所需路径却不同。
本次演练假设客户端在 10.90.0.10 上、经 tun1 接入,服务器公网地址为 198.51.100.25,主表未被改动且默认路由指向上行链路。服务器一侧还必须在内层路径上接受 Web 服务,并把流量回送到隧道源地址;把包发往公网地址并不会覆盖服务器的监听器或防火墙策略。
动手之前,先记录主机当前的规则,并挑一组别人不用的值:一个标记、一个表号,以及三个规则优先级。留一条不经过这条连接的带外回路,因为这里的失误会夺走你自己的远程访问,而不只是弄坏一个浏览器标签页。
标记承载信封的那个套接字
客户端配置为加密的外发包取得一个标记,同时不再自行安装路由,这样替换策略就是唯一决定路径的东西:
mark 42
route-noexec
disable-dco
十进制 42 就是十六进制的 0x2a,而路由规则匹配的正是十六进制形式。link-options 参考把 mark 描述为作用于 Linux 上加密外发包的设置,那恰好就是你需要控制其路径的套接字。关掉数据通道卸载,能让这个例子留在用户态路径上,所述标记行为正是这一路径上的行为。
route-noexec 会压制 OpenVPN 自动添加和删除路由。它不会在其位置上安装任何东西,所以从这一刻起,路由的所有者必须维护隧道所需的每一条路由。如果变更后某个私有目的地消失了,那是缺失的替换策略,而不是标记的缺陷。
做这件事时,不要在主表里保留通往 198.51.100.25 的宿主路由。这样的路由会重新引入设计想要收窄的那条豁免,而且它会在下面任何策略被查询之前就为 HTTPS 流胜出。
把其余一切都送进隧道表
一旦隧道接口存在并持有自己的地址,策略就是这样:
ip -4 route add default dev tun1 src 10.90.0.10 table 47100
ip -4 rule add priority 20000 lookup main suppress_prefixlength 0
ip -4 rule add priority 20001 not fwmark 0x2a lookup 47100
ip -4 rule add priority 20002 not fwmark 0x2a unreachable
第一条规则让主机的具体路由保持可见,包括任何已获批准的本地网络,同时让主表的默认路由在这次查找中不可见。这个区别很重要:压制默认路由不等于忽略整张表,而你还需要的本地例外正是因此才得以保留。请逐条阅读那些例外,而不要假定结果是一个万能的紧急停止开关。
未标记的流量随后落进隧道表,取得隧道源地址,从 VPN 出去。已标记的流量在接下来两条规则的 fwmark 测试中失败,继续走到列表更下方的常规主表查找,那里仍然有上行链路的默认路由。最后那条 unreachable 规则,正是当隧道路由缺失时阻止未标记流悄悄回落到上行链路的东西。选择器自始至终是标记,目的地址从不进入决策。
把这道护栏在断线之间保持住,并随其余策略一起安装,而不是手工添加。一条随接口消失的规则不是策略,是巧合。隧道断开时删掉策略,会把失败模式从“被阻断”改成“已泄漏”,而稍后过期的陈旧隧道路由,比从错误接口溜出去的流量好解释得多。
让回程也看到同一个标记
出向标记只是机制的一半,而这正是大多数复现所省略的部分。加密会话的回包从上行链路到达,并要经受反向路径校验,它问的是源地址会不会按原路被路由回去。没有额外帮助时,那次查找会套用你未标记的隧道策略,从而断定包出现在错误的接口上。
修正属于防火墙的所有者:在出向连接上保存标记,把它还原到匹配的入向包上,并为相关路径启用源校验的标记处理。OpenVPN 设置套接字标记并不做这三件事中的任何一件,只列出客户端选项的配方是不完整的。请以书面形式要求这项集成,因为它是设计中最不显眼、也最容易在重建时丢掉的部分。
松散的反向路径检查同样不是替代品。松散模式仍然要求源地址存在某条路由,而你加上的 unreachable 护栏恰恰故意为未标记查找移除了它。在依赖这套设计之前,先把连接标记的集成做完并评审过,并把主机过滤器当成另一个问题:标记是路由信号,不是许可。如果集成缺失,症状就是会话建立起来,然后一等回包开始到达就安静了。
用两次路由查找来验证
比较内核对两种上下文分别作出什么决定,并且读 dev 和 src 字段,而不是看退出状态:
ip -4 route get 198.51.100.25
ip -4 route get 198.51.100.25 mark 0x2a
第一次查找应当解析到带隧道源地址的 tun1,第二次解析到上行链路。这些是合成查找,所以证明的是策略,而不是正在运行的进程:先单独确认运行中客户端的实际传输路径,然后做一次真正的 HTTPS 请求,并保持证书校验开启。
在宣布完成之前,先写下这套设计不覆盖什么。DNS、IPv6 和规则的生命周期各自都需要自己的决定,而这是 IPv4 路由的示例,不是完整的隐私配置。它的价值在于选择器:信封获得上行链路豁免,而同一地址上的普通服务不必继承这条豁免。