为什么你的拆分隧道规则不再匹配某个服务
规则固定的是前缀,服务却已经迁移。先读清应用真正使用的目标地址,然后修复机制,而不是扩大例外。
拆分隧道通常以两种方式之一失败。要么本该走隧道的流量绕开了它,要么本该留在隧道外的流量被拉了进来。规则过时导致的故障属于前一种,也更令人困惑,因为规则完全按写好的样子在执行。变的是目标:服务现在应答的是一个规则从未指名的地址,而隧道本身没有任何问题。
规则固定了什么,DNS 又返回什么
拆分隧道规则是一条关于目标的静态声明,通常写成前缀、路由或地址列表。它在发送时按数据包的目标来评估。主机名是一次查询,返回一组带生存期和缓存的地址,而答案会随解析器、查询类型和分钟不同而变化。
这两个对象从不相互协商。规则继续匹配它拿到的前缀,服务继续更换它下发的地址,两者之间的缝隙在无声地变宽。所以症状出现在服务商迁移或证书续期之后,而不是配置变更之后。没有人改过隧道,于是没有人去看隧道,诊断就滑向应用,而不是策略。
匹配失效的三种方式
最明显的情况是服务迁移:运营方给了它新地址,DNS 跟上了,例外却没跟上。第二种是规则从未覆盖的地址族,当某个名字开始返回仅 IPv4 的例外会忽略的 AAAA 记录时就会发生。第三种是解析器差异:内部视图和公网视图可能为同一个名字返回不同地址,于是应用使用的地址并不是你测试时用的那个。
缓存连接是第四种变体,而且很容易被忽略。应用可能还在与一小时前解析出的地址通信,这意味着你刚做的查询和它已打开的 socket 对服务究竟在哪并不一致。
读清应用真正使用的目标
去问操作系统和 socket 表,而不是浏览器标签页。查询回答名字现在解析成什么,socket 表显示进程连接的地址,而那个地址可能更旧。先跑这两项再形成判断,因为大约一半的情况下,两者之间的差异本身就是答案。
getent ahosts api.example.com
dig +short api.example.com A
dig +short api.example.com AAAA
ss -tnp | grep -F api
ip route get 203.0.113.40
然后与规则覆盖的范围比较。如果当前地址落在例外之外,而请求仍然失败,你就找到了原因,而且不必改动隧道配置。如果应用连接的是符合规则的地址,而请求仍然失败,问题就在隧道之外,这次的排查方向是错的。
修的是机制,不是症状
如果产品支持可维护的基于域名的引导,就用它,并遵循其文档写明的刷新和范围行为。这用可维护的答案取代静态猜测,代价是依赖该功能如文档所述地工作。
如果设计依赖静态列表,就给它一个负责人和更新计划。计划应当说明地址如何发现、谁批准变更,以及期望列表多快跟上服务商迁移。没有负责人的列表,就是一场原因已知的未来故障。
不要仅仅为了抓住短测中见到的每一个地址,就把例外扩大到一大片相邻网段。那不是修复;那是对安全边界的永久改动,只要规则存在,它就会悄悄让无关服务绕过隧道。
验证意图的两半
拆分隧道设计有两个意图,一次修复应当同时保住两者。受保护的服务必须走隧道,被排除的类别必须继续绕开隧道。先测试到已修复目标的新连接,再测试一个本该留在外面的对象。
curl -sv --max-time 5 https://api.example.com/ -o /dev/null 2>&1 | grep -E 'Trying|Connected to'
curl -sS --max-time 5 https://public.example/ -o /dev/null -w '%{http_code}\n'
记录新地址、你选择的机制和日期。 正是这条记录,让下一个人不会得出 VPN 随机无视了自己配置的结论。