为什么在 OpenVPN 的 route 指令中使用通配符无法奏效
route 安装的是地址前缀,因此主机名模式没有任何东西可以匹配。网络请使用前缀,而在必须由域名决定时请使用代理或 PAC 文件。
意图本身合理:把某个公司域名的全部流量送进隧道,其余流量则保持原样。正在编辑的指令安装的是路由,而路由根本不包含可供匹配的主机名概念。任何把通配符写进去的尝试,要么产生解析错误,要么更糟,产生一条今天恰好能用、只因为该名字解析到了某一个地址的路由。这等于要求两套不同的机制去做对方的工作。
route 安装了什么,又从不读取什么
一条路由条目说的是某个目标前缀可以经由某个接口到达。内核用这些前缀来匹配数据包,因此参与其中的标识符只有地址和掩码。主机名永远不会出现在转发决策里,所以在那里无法匹配任何模式。
OpenVPN 可以在某些 route 参数中接受可解析的名字,而这种宽容正是混淆的来源。名字在应用配置时被解析,产生一个或多个地址,这些地址随后成为普通路由。此后,名字就完全从画面中消失了。
所以,由名字构建出来的路由是一个快照,而不是一条规则。它覆盖的只是隧道启动那一刻该名字解析到的结果,而这恰恰是你在面对会变化的一组主机时所不想要的行为。明天移动的记录会让路由继续指向昨天。
当目标确实是一个前缀时
如果目标是已知的私有网络,就写出前缀。以文档地址空间为例,单个主机和一个子网看起来是这样:
route 10.60.4.20 255.255.255.255
route 10.60.0.0 255.255.0.0
两条条目都标明了地址和掩码,而且二者都能挺过 DNS 变化,因为它们当中没有任何东西依赖名字。这就是全部优势:路由是关于拓扑的陈述,而不是关于某次只发生一次的查询。
不要从单个 DNS 回答推断出一个大前缀。一个解析到单个地址的主机名,并不能告诉你它背后网络有多大;而一个猜出来的前缀,正是隧道最终截获了本无人打算送进去的流量的原因。与其从一次查询去推断,不如向网络所有者询问真实的前缀。
当工作单位是主机名时
按域名做选择,需要某种能读取域名的东西,也就是应用层组件。常见的答案是位于隧道另一侧的 HTTP 代理,由客户端决定把哪些请求发给它。
代理配置文件可以表达这种选择。下面这些示例规则把一个域名及其子域名送往代理,其余全部直连:
function FindProxyForURL(url, host) {
var h = host.toLowerCase();
if (h === "corp.example" || dnsDomainIs(h, ".corp.example")) {
return "PROXY 10.60.0.12:3128";
}
return "DIRECT";
}
精确名称比较覆盖域名本身,后缀比较则覆盖它的子级,同时不会匹配到只是以相似字符结尾的名字。两者都必不可少,缺少任何一个都会得到一条看似正确、却漏掉顶点或冒名者的规则。
这些规则选择的是代理;它们并不创建代理,也不会改变任何操作系统路由。代理地址仍然需要经由隧道的自己的路径,而被标记为直连的请求依旧遵循机器已有的路由策略。
代理选项没有覆盖的部分
忽略浏览器代理配置的应用程序不会受到这一切影响。命令行工具、更新程序以及拥有自有网络栈的应用取用的是路由表,而不是 PAC 文件,因此这些情况需要受支持的按应用机制或另一种设计。在承诺浏览器规则能覆盖整个工作流之前,先清点真正重要的客户端。
直连返回也不是逃生通道。如果机器的路由策略已经把一切送进隧道,那么被标记为直连的请求仍然留在隧道里,因为代理选择与路由表是不同层次上做出的不同决定。
名称解析是第三个独立的问题。选择哪些请求走代理,并不能说明由哪个解析器来回答它们;把两者当成同一个设置,会产生直到有人从外部测试才会显现的泄漏。决定每个查询在哪里得到回答,并把这一决定写在代理规则旁边。因为隧道启用而变化的解析器,会悄悄地把答案挪走。
测试两条路径,而不只是顺利的那条
测试一个本应匹配的主机名、一个本不应匹配的主机名,以及域名顶点,因为顶点正是仅靠后缀的规则最常漏掉的情况。然后确认每个请求实际走了哪条路径,而不是从页面是否加载来推断。
最后,测试失败分支。无法访问的代理应当以你事先决定的方式失败,而不是悄悄回退到直连。有关网络选项的参考精确记录了路由参数;这个设计的域名那一半完全生活在它之外。