为什么 VPN 路由在 ip route 输出中不显示网关
只有设备、没有网关的那一行是直连路由,而不是坏掉的路由。先读目标、dev 和 scope,再用 ip route get 确认。
只要网关一栏为空,路由表就常被误读成坏了,其实那一行本身通常是完整的。via 字段只在内核把数据包交给下一跳地址时出现;绑定到接口的路由则通过链路层解析或点对点对端到达目的地。从左到右读各列就能判断属于哪种情况,而这次读取应当发生在任何人修改默认路由之前。大多数关于网关缺失的工单都止于这一区别。
逐列读一行路由
每条记录都以目标及其前缀长度开头,随后用固定词汇追加修饰项:via、dev、proto、scope、src 和 metric。像 198.51.100.0/24 dev tun0 proto kernel scope link src 198.51.100.2 这样的行虽然密集,但已经完整指定。它什么也不缺,也没有暗示任何路由器地址。
有意思的地方是缺少 via。没有下一跳时,内核直接在指定接口上解析目标,对隧道而言就是把帧交给隧道设备,让封装从那里接手。proto kernel 记录的是该条目在分配地址时自动安装,而不是管理员手输的。scope link 表示该目标在该链路上无需经过路由器即可到达,src 则指明内核会优先选作匹配流量源地址的地址。
现在用同样的方式读一条默认路由:default via 192.0.2.254 dev eth0 proto static metric 100。这里出现 via,是因为数据包确实需要下一跳,而该下一跳自身必须在 eth0 上可达。两行并不是彼此的变体;它们回答的是流量去往何处的不同问题。
两种容易被混淆的路由形态
隧道子网路由和默认路由在截图里看着相似,行为却完全不同。把下表当作读字段的辅助工具,然后在你正在调试的机器上确认实际数字,而不是相信指南里的示例。
| Piece of the line | What it settles | What it says nothing about |
|---|---|---|
| The destination and mask | Which packets are eligible to match | Where those packets leave the host |
via plus its address | Which next hop receives them, and therefore that a router is involved | Whether that router is reachable or sane |
dev | Which interface carries the encapsulated or raw packet | Whether the interface is up or has a carrier |
proto and scope | Who installed the entry and how far the destination is considered to be | Whether a firewall later drops the traffic |
表格说不出来的是意图。经隧道通往私有网段的直连路由是有意设计的分流隧道;同样的形态出现在本应把所有流量都送进 VPN 的主机上,就是症状。意图存在于生成该条目的配置中,所以在断定内核出错之前,先看看是哪个工具安装了它。
同样,缺少网关并不等于可以凭空造一个。如果一条路由确实需要下一跳,该下一跳必须在承载它的接口上可达,而对点对点隧道来说,对端地址与本地地址不是同一个对象。把本地隧道地址当成以太网路由器,是造出一条看起来正确却什么都不转发的路由的常见做法。
问内核它实际会用哪一行
读表和真正执行查找不是一回事,因为内核在回答之前会查询策略规则、多张表和缓存条目。执行真正查找的动词是 ip route get,它会打印具有这些属性的数据包会走的路由。
ip route get 203.0.113.37 from 198.51.100.2 iif tun0
换成你真正关心的目标,然后在流量按源地址选择或被转发时加上 from 或 iif,因为普通本地查找可能选出与被转发客户端不同的路径。答复会给出接口、选定的源地址以及任何下一跳,这正是那一刻数据包被路由时所用的信息。
如果答复让你意外,先打印规则再动手:ip -4 rule show 会按优先级列出它们,而安装了自建表或为带标记数据包设置规则的 VPN 客户端根本不会出现在主表里。列出所有表就能补上这个缺口。
ip -4 route show table all | grep -E 'tun|default'替换默认路由之前先检查底层
隧道自身的包必须到达远程端点,而它们是经物理接口、使用 VPN 起来之前就已存在的路由做到的。不固定该端点就替换整条默认路由,会把封装后的包送进它们本该建立的那条隧道,连接随即崩溃。先用只针对端点地址的查找确认端点路径。
做改动时把三件事分开:必须留在物理接口上的端点路由、应当进入隧道的私有网段,以及决定其余一切的默认路由。按这个顺序拆开,一次只应用一条,每步之后重跑查找。事后 ping 失败本身说明不了什么,因为权限、回程路由、DNS 和服务本身都在正确路由的下游。
把改动前该行给出的四个值记下来:目标、下一跳或其缺失、设备,以及选定的源地址。如果之后的改动移动了其中任何一个,你就知道究竟改动了哪个决定,也能只恢复一条条目,而不必去猜整张表。