你把 route-nopull 写进客户端配置,本想阻止隧道吞掉默认路由,结果办公室子网变得不可达,而客户端仍显示已连接。这两个现象都是真的,也并没有任何东西坏掉。这个选项只是让客户端跳过服务器推送的路由和 DHCP 条目,比大多数人添加它时所预想的范围要窄得多。剩下的是一条能工作、却对哪些目标该走它毫无主见的隧道。

这个空缺正是本文要讨论的全部决策。要么由服务器描述网络,要么由客户端描述,而这个选项把这件事交给客户端,却不替你做其中任何一步。

连接时它到底关掉了什么

处于拉取模式的 OpenVPN 客户端通常会在认证之后从服务器拿到一整套东西:路由、默认网关的重定向、DNS 服务器,以及若干调优指令。启用 route-nopull 后,客户端依然接受会话、依然校验证书、依然协商加密算法与 keepalive,也依然把接口拉起来。它丢掉的只是对被推送的 route 行和 dhcp-option 行的应用,其中也包括 DNS。

困惑来自一颗绿灯挨着一个已经死掉的应用程序。隧道是存在的;从目标到这条隧道的映射不存在。客户端状态面板里没有任何东西能区分“已连接并在路由”和“已连接但不起作用”,因为从隧道的角度看,这两种状态完全一样。

为什么隧道活着却什么都路由不到

接口带着自己的地址出现,而在点对点链路上,往往具体不到一条指向对端的主机路由。只有当某条路由以正确的前缀指向该接口时,私有子网才可达;而推送被关掉之后,那条路由永远不会到来。下载下来的配置通常也帮不上忙,因为它是按“服务器会提供这些信息”的假设写成的。这就是为什么接口看起来很健康,子网却依旧是黑的。

所以读配置几乎证明不了什么。不如直接在已连接的机器上读运行状态:

ip addr show tun0
ip route show dev tun0
ip route get 10.20.5.10

第二条命令里只有地址、没有子网路由,就是这个问题的典型特征。第三条命令回答的才是用户真正关心的问题,因为它报告的是内核为某个具体目标会挑选的接口,而不是你希望它使用的那张表。如果答案是走物理链路出去的,那么隧道根本不在路径上。

域名解析失败的原因与此相关,而不是另一回事。被推送的 dhcp-option DNS 条目被同一个选项跳过,所以查询内网主机会发给本地网络提供的那个解析器。那个解析器没有办公室的区,查询要么失败要么返回公网答案,于是故障看起来像服务器问题,实际上是客户端配置问题。先用地址测试、再用名字测试,才能把两者分开。

在选项与前缀过滤器之间做选择

这两个方案解决的是同一种抱怨的不同形态,而选错了才会造成“连上了却没用”的状态。一个整体替换被推送的策略,另一个按前缀去编辑它。下表给出简要版本,后面的段落讲的是最容易让人栽跟头的那部分。

SettingWhat the client keepsReasonable when
no optionevery pushed route and DHCP entrythe server's view of the network is correct
route-nopullthe session, none of the pushed routing or DNSyou will write the full local policy yourself
pull-filtereverything except lines matching your prefixesthe push is nearly right and one or two lines are wrong

pull-filter 是一个带三个动词的前缀匹配器,因此它可以接受一族行、静默忽略它们,或者在某个前缀出现时直接拒绝连接。最后一种行为才值得了解:一条拒绝规则会把策略分歧变成一次失败的握手,而不是一次安静的省略。OpenVPN 2.6 手册 列出了可接受的前缀以及每个动词的确切语义。

一种常见的折中是只拒绝 redirect-gateway,让办公室的路由和 DNS 照原意到达。这样既让服务器比你更懂的那部分推送继续生效,又能挡掉 那一行会夺走你默认路由的指令。

手工补上路由和解析器

如果你保留这个选项,配置就必须写出服务器本来会说的内容。客户端的 route 指令在连接建立之后才生效,所以它们与这个选项并存,而不是被它挡住。要把它们写进配置,而不是敲在交互式命令行里,否则下次重连不会重现那套能用的状态。

route-nopull
route 10.20.0.0 255.255.0.0
dhcp-option DNS 10.20.0.53

DNS 那一行处在一个尴尬的位置:它是客户端的指令,但有些客户端只通过平台的 DNS 集成来应用它,而链接变化时集成可能会把它覆盖掉。在 systemd 主机上,直接把解析器设到隧道接口上,比指望客户端在优先级争夺中胜出更可预测:

resolvectl dns tun0 10.20.0.53
resolvectl domain tun0 ~corp.example.com

路由域和地址一样重要。没有它,公共名字的查询也可能被送到内网解析器,而任何拒绝递归的内网解析器都会让浏览器失效,同时私有区还照常工作。有了它,只有该后缀下的名字才走隧道解析器,其余一切跟随物理链路。

检查结果:地址、名字与外部路径

三种测试可以区分可能的故障。先用 IP 取私有地址,再用名字取同一台主机,最后打开一个普通的公共站点。IP 通而名字不通指向解析器;IP 不通而名字通指向路由;两者都不通通常意味着隧道不在路径上。

然后往外看,而不是往里看。隧道端点自己的路由必须在改动后存活,任何故障阻断都要限定在隧道内,不能把端点也抓进去。隧道断开后客户端还在上网,那是策略问题而不是路由问题,上面三种测试都发现不了它。

把最终清单写进配置的文档里:路由、解析器、域,以及每一样存在的理由。一次能重现同一张表的重连,才是本地策略完整的唯一证据。如果隧道已通、三种测试全过而应用依旧失败,下一个该看的地方是“连上却离线”那一类问题,而不是已经验证过的证书。

相关文章