LabHub
学习 学习路径 课程

网络基础 — 在 Linux 虚拟机里动手

路与名 — 路由表和 DNS 的层次

在 LabHub 中继续学习

一句话总结

路由通过最长前缀匹配决定“这个目标应从哪个 interface 发出、经过谁”;Linux 只有启用 ip_forward,才会转发别人的数据包。名称解析则由多个层次组成,包括 /etc/hosts、stub 和上游服务器,而 dig 只查看最后一层。

分层图: 某一 hop 不认识某个网段 · 回程路由。 · 端口 53 冲突。 · 附加 search domain。

为什么需要这些知识

发往子网之外的数据包会交给网关。网关再次查看自己的路由表,选择下一个网关。这条链路就是互联网。每一 hop 只知道“这个网段在那一侧”,没有任何设备掌握完整路径。因此,路由事故通常源于某一 hop 不认识某个网段。尤其常见的是去程存在、回程缺失;症状却只是 timeout,很难区分。

名称解析同样存在多个层次。过去,/etc/resolv.conf 中记录服务器地址,过程到此为止。如今 Ubuntu 使用 systemd-resolved127.0.0.53 监听 stub,并按不同 link 转发给不同上游服务器,还能通过 ~domain 路由规则,仅把特定域名发送给特定服务器。VPN 只把企业域名交给企业 DNS,正是利用了这一机制。它很方便,但如果不知道是哪一层作出的响应,就无法解释“dig 正常,curl 却失败”。

它是如何运作的

如果路由表同时包含 default via 10.20.1.110.99.0.0/24 via 10.20.2.10,那么 10.99.0.5 会走后一条。更长的 prefix 获胜,与记录顺序无关。ip route get 10.99.0.5 会直接向内核询问这一判断,比人手阅读路由表后推测更准确。

对于目标 10.99.0.5,default、10.0.0.0/8 和 10.99.0.0/24 三条路由都匹配,但匹配的 bit 数分别为 0、8 和 24,所以写在最下方的 24 bit 路由获胜,与记录顺序无关

Linux 默认不是路由器,会丢弃目标不是自身地址的数据包。net.ipv4.ip_forward=1 会改变这一行为,而且每个 namespace 都有独立设置。Docker 宿主机、Kubernetes 节点和 VPN 服务器都会把这个值设为 1。

名称解析从应用程序调用 libc 的 getaddrinfo 开始。按照 /etc/nsswitch.confhosts: files dns 的顺序,先查找 /etc/hosts;找不到时询问 resolv.conf 中的 stub;stub 再根据 link 设置转发到上游。dig 会跳过这些层,直接询问服务器。因此,服务器返回什么用 dig 查看,应用程序实际看到什么则用 getent hosts 查看。

在实际工作中会是什么样

回程路由。 接入新网段并在路由器上添加路径后,ping 仍然失败。原因是对端路由器不知道如何返回新网段。请求已经到达,响应却迷失路径。在双方分别运行 ip route get,就会看到其中一侧错误地通过 default 指向别处。

端口 53 冲突。 安装 dnsmasq 后无法启动。通过 ss -ulpn 'sport = :53' 查看,会发现 systemd-resolve 占用了 127.0.0.53。解决方法是在 dnsmasq 中使用 listen-addressbind-interfaces,只绑定自己的地址。

名称解析缓慢或异常时

即使路由正确,缓慢问题也经常来自名称解析。由于层次很多,很难看出时间消耗在哪里。

附加 search domain。 如果 resolv.confsearch 清单列出多个域名,查询短名称时,解析器会逐个附加这些域名并依次尝试。名称越不存在,尝试次数越多;服务器响应越慢,延迟还会成倍增加。这是 Kubernetes Pod 内查询外部域名缓慢的经典原因,因此解决办法是在末尾加点,以绝对名称查询

同时查询 IPv6。 getaddrinfo 会同时查询 A 和 AAAA。如果其中一个查询丢失,就会等待 timeout 后重试,因此出现“偶尔停顿大约 5 秒”的症状。5 秒通常正是 resolver 的默认 timeout,因此,时长恰好固定的延迟,应优先怀疑 timeout。

cache 过期不及时。 地址修改后仍持续返回旧值时,必须分辨是哪一层 cache。应用程序自身可能保存结果,Java runtime 就经常缓存很久;stub resolver 也可能缓存;上游服务器还会按 TTL 保存。正确做法是先降低 TTL,再修改地址。等到修改当天才降低 TTL,已经太迟。

诊断顺序仍遵循前面的原则:先用 getent hosts 查看应用程序实际看到的响应;如果异常,再检查 /etc/hosts 和 stub 设置;只有怀疑服务器本身时,才用 dig 直接查询。如果一开始就运行 dig,就会跳过中间层,无法重现应用程序的问题,只得到“服务器正常”的结论。

下一次实操要做什么

你将使用三个 namespace 搭建主机—路由器—主机拓扑,配置默认路由、ip_forward 和静态路由,再用 traceroute 统计 hop。随后,使用 dnsmasq 建立自己的 DNS 服务器,并接入 resolved 的路由域名;最后确认 /etc/hosts 的优先级高于 DNS,并列使用 getentdig 观察结果。