你加了一行想保留本地默认路由,结果隧道再也到不了内部 DNS 服务器。那一行是 pull-filter ignore "route",它的意思并不是「忽略这条路由」,而是「丢掉所有文本以这五个字母开头的推送选项」;在普通服务器上,这还包括 route-gateway、route-metric 和 route-ipv6,以及路由本身。下面这份清单就是我在改动别人要用的配置之前会走的流程。

过滤器只作用于推送的选项

pull-filter 只处理服务器在隧道建立后发来的选项行,不处理任何别的东西。写在本地文件里的选项不受影响,应用流量也不受影响。如果你指望用它阻止某个进程走隧道,那完全是找错了工具,任何过滤器组合都做不到。

这个范围是有用的,因为它意味着你可以不动服务器配置,只在本地拒绝其中一部分。这也是值得写进工单的限制:过滤器改变的是这个客户端接受什么,而不是服务器提供什么。它不影响同一配置的其他使用者,也不会在换到不实现该选项的客户端后继续生效,所以在把它做成工作流之前值得先知道。

写匹配串之前,先确认服务器到底发了什么。把详细度调高后,客户端会打印收到的推送选项串,这是核对你要匹配的确切文本的唯一可靠来源:

sudo openvpn --config /etc/openvpn/corp.ovpn --verb 4
sudo grep -n 'pull-filter' /etc/openvpn/corp.ovpn

把推送行读成以逗号分隔的选项列表,注意选项名和它的参数之间是用空格分隔的。你的过滤文本就是和这一串流比较,所以下一步里空格才这么要紧。

匹配是前缀,所以空格就是全部

比较的是字符串前缀,而不是解析后的选项名。因此 "route" 会同时命中 route-gateway 10.8.0.1、route-ipv6 2001:db8::/32 和 route-metric 100。在末尾多加一个空格,就能收窄到你真正想说的那一族;写上前缀的完整内容,还能进一步收窄到具体目标。

Filter textWhat it actually matchesTypical outcome
"route"Every option beginning with those lettersRoutes, gateway, metric and IPv6 routes all dropped
"route "Route entries only, not the hyphenated siblingsThe routes go, the rest survives
"route 10.60."A single destination rangeThe narrowest rule, and the easiest to audit
"dhcp-option DNS"Pushed DNS settingsResolver stays local, internal names stop resolving

最后一行正是分流隧道工作里最容易踩的坑。保留自己的解析器是合理选择,但这意味着每个内部名称都依赖一个看不到私有区的解析器。这是应当主动接受的后果,而不是留到生产环境才发现的副作用。

前缀匹配还有一个含义:规则以后可能悄悄变宽。当服务器新增一个以你这段文本开头的选项时,你的过滤器就会开始作用于它,而没有人改过你的配置。这正是需要预先防范的失效方式,缓解办法是尽量多匹配选项的内容,而不是尽量少匹配。

三个动词,三种不同结果

accept、ignore 和 reject 并不是同一个想法的三种拼写。它们的区别在于客户端匹配后做什么,以及有没有人会知道。

ignore 会静默丢弃匹配到的选项,适合你主动替换掉的设置。reject 会丢弃并让客户端报错,适合你希望它的缺失出现在日志里、而不是日后才被发现的情况。accept 是肯定形式,用于建立允许列表,而不是逐条移除选项。

无论用哪个动词,过滤器都按出现顺序生效,所以两条重叠的规则会得到依赖顺序、极难复核的结果。一个目的只留一条规则,并在每条里写上最具体的文本。如果需要允许列表,宁可写少数几条 accept 规则,也不要堆一堆加起来效果相同的 ignore 规则。这样复核者可以从上到下读完并预测结果,而这是唯一能规模化的检验。

检查实际生效的路由和解析器,而不是配置文件

配置文件里看着正确的过滤器,仍可能让隧道什么有用的都不转发。要在运行中的系统上验证效果,因为推送的选项那时已经被应用或丢弃:

ip route show default
ip route show | grep -c 'proto static'
resolvectl status | grep -A4 'Link.*tun0'
curl -sS -o /dev/null -w '%{http_code}\n' --max-time 5 https://intranet.example.internal/

前两行告诉你推送的默认路由有没有到达,以及还剩多少条静态项。解析器查询显示隧道接口实际在用哪台服务器,DNS 的意外通常就出在这里。最后一行回答对用户唯一重要的问题。

在那里顺手把两个方向都测了。一条规则如果让本地互联网照常工作,却悄悄破坏内部域名解析,它能通过表面检查,却会在第一个真实任务上失败;所以同一个测试里要同时包含一个内部资源和一个公网站点。

记录这个例外和它的负责人

过滤器是对运营方设计的例外,所以要连同理由和复查点一起写下来。「为保留本地默认路由而忽略推送路由」是可以复核的;配置里孤零零的一行不是,下一个人要么删掉它,要么靠猜去扩展它。

版本在这里同样重要。匹配行为和确切允许的语法在 OpenVPN 2.4、2.5 和 2.6 中不断细化,所以请对照你交付的那个构建的手册确认写法,而不是对照关于旧版本的论坛帖子。OpenVPN 2.6 手册记录了该选项及其匹配规则。

最后,和负责服务器的人把这个例外谈定。客户端过滤器若与运维方有意为之的策略相悖,就是一个等着下次审计的支持事件;在配置交付之前解决它,要比等别人发现路由缺失之后再解决便宜得多。

相关文章