OpenVPN 的 pull-filter 会匹配到比你预期更多的路由
pull-filter 把你的文本当作前缀与推送的选项比较,所以 route 也会命中 route-gateway 和 route-ipv6。先检查动词,再检查路由。
你加了一行想保留本地默认路由,结果隧道再也到不了内部 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 text | What it actually matches | Typical outcome |
|---|---|---|
"route" | Every option beginning with those letters | Routes, gateway, metric and IPv6 routes all dropped |
"route " | Route entries only, not the hyphenated siblings | The routes go, the rest survives |
"route 10.60." | A single destination range | The narrowest rule, and the easiest to audit |
"dhcp-option DNS" | Pushed DNS settings | Resolver 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 手册记录了该选项及其匹配规则。
最后,和负责服务器的人把这个例外谈定。客户端过滤器若与运维方有意为之的策略相悖,就是一个等着下次审计的支持事件;在配置交付之前解决它,要比等别人发现路由缺失之后再解决便宜得多。