【Linux 网络编程】DNS 与 ICMP:从域名解析到 ping、traceroute,把“网络通不通”问清楚

🔥 星光编译者 · 个人主页

📚 学习专栏: 《C/C++ 成长笔记》 · 《Linux 实践手册》 · 《数据结构与算法》

🌄 向云端飞扬,编译属于自己的代码星河。


☕ 写在开篇

  你好,这里是 星光编译者。

  这里记录我在 C/C++、Linux、数据结构与算法 学习中遇到的真实问题、亲手验证过的代码,以及那些容易被忽略的实现细节。

  比起简单罗列结论,我更愿意从问题出发,把一个知识点的来由讲清楚,把"为什么会这样"和"应该怎样解决"说明白,让每一次踩坑都沉淀成可以复用的经验。

  如果这篇记录能帮你少绕一点路,或让某个模糊的地方忽然变得清晰,那么这次分享便有了意义。愿我们在一次次阅读、编译与调试中稳步向前,慢慢搭起属于自己的技术世界。


🔥 本文定位:沿着"域名如何变成 IP → 数据能否到达 → 中间经过哪些节点"的路线,把 DNS、ICMP 和日常排障命令串起来。

💡 学习目标 :读懂域名层级和 dig 输出,分清递归与迭代、两种 TTL,解释 ping 没有端口号以及 traceroute 怎样发现中间节点。

📌 阅读范围 :以 Linux、传统 DNS 和 IPv4 为主,涉及 IPv6 时单独说明。文中的地址、命令输出和路径图均为教学示意;203.0.113.0/24 是文档示例网段,不应拿来期待真实公网连通。

你有没有遇到过这样的情况:浏览器打不开网站,第一反应是在终端里敲一条 ping;看到超时,就认定"服务器挂了";看到有回复,又开始怀疑"明明网络正常,为什么网页还是打不开"?

问题往往出在:我们把几个不同的问题,塞进了同一个"通不通"里。

一个名字能不能解析成地址,是一个问题。某种探测报文能不能往返,是另一个问题。TCP 连接、TLS 握手和应用处理是否正常,又是后面的事情。

DNS 和 ICMP 正好站在这条链路的两个位置:DNS 帮我们查到名称对应的资源,ICMP 帮我们获得部分网络诊断与差错反馈。 它们都很重要,却都不能单独证明整个业务已经正常。


文章目录

  1. [从 hosts 到 DNS:名字为什么需要一套系统](#从 hosts 到 DNS:名字为什么需要一套系统)
  2. [域名的树与记录:一个名字不只对应一个 IP](#域名的树与记录:一个名字不只对应一个 IP)
  3. 一次解析怎样完成:递归、迭代与缓存
  4. [读懂 dig:别只盯着 ANSWER 里的地址](#读懂 dig:别只盯着 ANSWER 里的地址)
  5. [从 URL 到网页:DNS 完成后还有什么](#从 URL 到网页:DNS 完成后还有什么)
  6. [ICMP:IP 的反馈机制,不是可靠性保证](#ICMP:IP 的反馈机制,不是可靠性保证)
  7. ping:测到了什么,又没有测到什么
  8. [traceroute:利用 TTL 逐跳获得线索](#traceroute:利用 TTL 逐跳获得线索)
  9. [离线实验:亲手拆开 DNS 首部与 ICMP 回显](#离线实验:亲手拆开 DNS 首部与 ICMP 回显)
  10. 排障顺序、常见误区与自测

一、从 hosts 到 DNS:名字为什么需要一套系统

1.1 人记名字,网络使用地址

对使用者而言,www.example.com 比一串地址容易理解,也更容易保持稳定。服务器迁移时,我们希望更换地址,不必让所有访问者重新记一个入口。

但普通 IP 转发使用的是目的 IP 地址。程序如果要连接一个用名字描述的远端,通常要先通过名称解析取得地址候选,再选择地址、端口和传输协议建立通信。

注意这里有三个不同对象:

对象 示例 描述的事情
域名 www.example.com 名称空间中的一个名字
IP 地址 203.0.113.80 网络层使用的地址
服务端口 TCP 443 传输层区分服务端点的重要字段

同一个 IP 可以承载多个网站,同一个域名也可以返回多个地址。因此,"域名就是某台物理服务器的永久编号"并不准确。

1.2 hosts 能解决小问题,却不适合维护整个互联网

最容易想到的办法,是在本机保存一张名字与地址的对照表。Linux 上可以查看:

bash 复制代码
cat /etc/hosts

一条教学示例是:

text 复制代码
203.0.113.80  web.demo.test

它表达本地映射,不会把这个名字自动发布给全世界,也不会顺便配置服务器、路由或 HTTPS 证书。只改 hosts 后访问失败,不能直接认为"映射没有生效"。

当主机越来越多,集中维护并让所有机器不断同步一份大文件,就会遇到更新、冲突和管理规模的问题。DNS 的重要变化,是把名称管理分散到不同管理者,并通过层次化命名和委派把它们组织起来。RFC 1034:DNS 的背景与设计

1.3 "一定先查 hosts"需要带上条件

在使用 glibc 名称服务机制的 Linux 系统中,查询来源与顺序由 /etc/nsswitch.conf 等配置共同决定。一个简化示例是:

text 复制代码
hosts: files dns

这表示先尝试本地文件,再查询 DNS;真实配置还可能包含 resolve、mdns 以及针对查询结果的动作规则。程序也可能使用自己的解析器,浏览器还可能采用独立的安全 DNS 配置。

所以,准确说法是:不少系统配置会优先使用 hosts,但这不是所有程序、所有机器都必须遵守的一条 DNS 协议规则。 Linux nsswitch.conf 手册

下面两个命令回答的问题也不同:

bash 复制代码
getent ahosts example.com
dig example.com A

前者通过系统名称服务接口查询,后者直接执行 DNS 查询。修改 hosts 后,getent 与 dig 的结果不同,可能恰好符合它们各自的工作方式。


二、域名的树与记录:一个名字不只对应一个 IP

2.1 从右向左理解层级

把完整名称写成 www.example.com.,最后那个点表示根。平时输入时通常省略它,但在 DNS 工具输出中经常能看到。

从右向左拆开:根 . → 顶级域 com → example.com → www.example.com。www 只是常见标签,不是必须出现的前缀,也不能强制决定服务使用 HTTP 还是 HTTPS。

.com、.net、.org 属于常见通用顶级域,.cn、.uk 属于国家和地区代码顶级域。不能仅凭 .com 或 .org 就判断站点的真实组织性质与可信程度。

"域名层级"与"注册规则"也不是同一件事:example.co.uk 这样的名称说明,可注册边界不总是机械地截取最后两段。本文重点是 DNS 如何组织和查询名字,不把域名的每个标签都当成一台服务器。

2.2 域、区域与权威服务器

域描述名称树中的一部分;区域(zone)是权威管理与数据维护的单位。父区域可以把某个子域委派出去,因此一个域下面的所有名称未必都由同一组权威服务器管理。

根服务器知道如何把查询引向相应顶级域,顶级域服务器再给出进一步的委派线索。它们不需要保存世界上每个网站的最终 A 记录。

这像一套有明确分工的目录:上级掌握"接下来由谁负责",具体记录由负责该区域的权威服务器提供。实际系统通常有多个权威服务器和冗余部署,图中用单个角色概括这一职责。

2.3 DNS 查询的是记录,不只是 IP

日常开发至少要认清以下几类:

类型 常见用途 阅读时注意
A 名称对应 IPv4 地址 可以返回多个地址
AAAA 名称对应 IPv6 地址 有 AAAA 不等于当前网络的 IPv6 路径可用
CNAME 别名指向另一个名称 目标还是名字,可能继续查询
NS 标识区域的权威服务器名称 不是直接返回网站业务地址
MX 邮件交换记录 含优先级和邮件服务器名称
TXT 文本信息 具体语义由使用它的机制规定
PTR 常用于反向查询 反向名称与正向记录分别维护

例如,教学数据可以写成:

text 复制代码
www.example.com.  300  IN  CNAME  edge.example.com.
edge.example.com.  60  IN  A      203.0.113.80
edge.example.com.  60  IN  A      203.0.113.81

第一行说"这个名字是另一个名字的别名";后两行才给出 IPv4 地址。这里的 IN 表示 Internet 类,数字是记录的 TTL,单位为秒。记录格式可对照 RFC 1035:资源记录。

多个地址给了客户端选择空间,却不保证任何一个地址都永远可达。客户端如何选择、重试以及处理 IPv4/IPv6 候选,还属于程序自身的连接策略。


三、一次解析怎样完成:递归、迭代与缓存

3.1 先把三个角色分清

**存根解析器(stub resolver)**通常位于客户端侧,把查询交给配置好的解析服务。递归解析器 替客户端寻找结果并缓存。权威服务器回答自己负责区域中的权威数据。

同一个软件可以承担不止一种职责,但理解一次查询时,先按角色区分更清楚。电脑上配置的"DNS 服务器地址",通常指向一个递归解析服务,或者一个会继续转交请求的本地代理。

3.2 递归是交付任务,迭代是继续问路

客户端对递归解析器的期待是:"请替我把这条记录查出来。"而解析器向外查询时,得到的可能是最终回答,也可能是"下一步去询问这些服务器"的转介。

假设查询 www.example.com 的 A 记录,缓存没有可用信息,解析器自行完成查找:

  1. 客户端向递归解析器提出查询。
  2. 解析器询问根服务器,取得 .com 的委派线索。
  3. 解析器询问 .com 服务器,取得 example.com 的委派线索。
  4. 解析器询问相应权威服务器,获得记录;若有别名,可能继续跟进。
  5. 解析器整理响应、缓存允许缓存的数据,再答复客户端。

图中的往返是按角色简化的过程,不是"客户端依次直连根、顶级域、权威服务器"。缓存可用时,很多外部步骤都会省略;使用转发器时,还可能由上游递归解析器继续完成任务。DNS 的递归和迭代模型见 RFC 1034 §4.3.1。

3.3 缓存让查找变快,也让变更需要时间

假设某条 A 记录的 TTL 为 60 秒,一个缓存于某时刻取得它。在普通缓存模型下,经过 20 秒再返回时,剩余 TTL 大约是 40 秒,而不是每次读取都重新获得 60 秒。

别名与地址记录可以有不同 TTL,不能只用一个计时器代表整个别名链。浏览器、操作系统解析服务、递归解析器也可能处于不同缓存层,不能从"本机清过缓存"推出"所有上游都已经更新"。

此外,不存在也可以被缓存 。NXDOMAIN 和某个类型没有记录的否定回答,可以按规则形成负缓存。因此,刚添加记录却暂时还查不到,既要核对权威配置,也要考虑此前的否定结果。RFC 2308:DNS 负缓存

TTL 是缓存期限机制,不是业务承诺:它不能保证网站在这段时间内一直在线,也不等于一次网络请求允许等待的超时时间。

在 Windows 中,下面的命令可查看 DNS 客户端解析缓存,输出也可能包含从 hosts 预载的条目:

powershell 复制代码
ipconfig /displaydns

它不展示所有浏览器内部缓存或远端解析器缓存。Microsoft:ipconfig

Linux 并没有一个适用于所有配置的统一缓存列表命令。若系统使用 systemd-resolved,可用 resolvectl status 查看 DNS 配置,用 resolvectl statistics 查看统计;它们与逐条列出所有缓存记录不是一回事。先确认实际使用的解析服务,再选择观察工具。systemd:resolvectl

3.4 DNS 的 TTL 与 IP 的 TTL 不要混记

比较项 DNS 记录 TTL IPv4 首部 TTL
描述对象 一条资源记录的缓存有效期 一个 IP 包的生存限制
日常解释 通常以秒表示缓存可复用时间 转发时至少递减 1,常作为跳数限制理解
归零后的处理 普通情况下需重新取得可用数据 转发途中耗尽则丢弃,可能产生超时报文
常见观察入口 dig 的回答字段 ping 的响应字段、traceroute 的探测机制

理解这一区别后,就不会把 dig 里的 60 和 ping 里的 ttl=54 当成同一种计数。


四、读懂 dig:别只盯着 ANSWER 里的地址

4.1 先做一条明确的查询

Linux 上,dig 通常由 BIND 的工具包提供。若系统尚未安装,可按发行版选用:

bash 复制代码
# Debian / Ubuntu
sudo apt install dnsutils

# Fedora / RHEL 系发行版
sudo dnf install bind-utils

安装后,从明确指定类型开始:

bash 复制代码
dig example.com A
dig example.com AAAA

若需要查看自己有权查询的指定解析器,用 @地址;例如下方地址只在本地网络确实提供 DNS 服务时适用:

bash 复制代码
dig @192.168.10.1 example.com A

这不是修改系统 DNS 配置,只是改变这一条查询发给谁。

4.2 把一份输出分成四块

下面是删去版本、时间与 EDNS 细节的教学输出,不代表该域名的实时记录:

text 复制代码
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 4660
;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;www.example.com.           IN A

;; ANSWER SECTION:
www.example.com.       300  IN CNAME edge.example.com.
edge.example.com.       60  IN A     203.0.113.80
edge.example.com.       60  IN A     203.0.113.81

;; Query time: 2 msec
;; SERVER: 192.168.10.1#53(192.168.10.1)

第一块是状态和标志。qr 表示响应,rd 表示请求递归,ra 表示服务器提供递归能力。aa 若出现,表示权威回答;ra 不表示本次一定进行了完整的根到权威查询。

第二块 QUESTION 是"本次究竟问了什么"。问 A 与问 AAAA 不是同一个问题,拿不同类型的结果直接比较,容易得出错误结论。

第三块 ANSWER 是记录集合。逐行看名称、TTL、类、类型和数据,才能区分别名与地址。ANSWER: 3 是记录数,不等于返回了三台网站服务器。

最后看 SERVER 和 Query time:前者是本次答复你的 DNS 服务端点,后者反映这次查询耗时,不是网站访问耗时。在使用本地 DNS 代理的系统上,SERVER 可能是 127.0.0.53 一类本地地址,而不是最终上游解析器。

4.3 NOERROR 不能直接翻译成"拿到 IP 了"

现象 应该如何理解
NOERROR 且有期望记录 本次 DNS 回答提供了该记录,继续检查地址是否适合业务
NOERROR 但没有期望类型 可能是 NODATA,也可能有转介等情况,要结合各区段判断
NXDOMAIN 名称不存在这一类否定结果
SERVFAIL 服务器未能完成处理,原因需要进一步核查
超时 在期限内没收到有效回答,不能等同于名称不存在

不能只看到 ANSWER: 0 就宣布 NXDOMAIN;也不能看到 SERVFAIL 就去重装浏览器。先辨明这是"得到了否定回答",还是"没有得到可用回答"。

4.4 三个实用选项,三个使用边界

bash 复制代码
dig example.com A +short
dig example.com A +tcp
dig example.com A +trace

+short 方便快速取结果,但隐藏状态、服务器等上下文;+tcp 让本次查询使用 TCP;+trace 让 dig 沿委派关系进行迭代追踪。后者需要当前机器能访问相关服务器,它与"看一遍当前递归解析器的内部日志"不是一回事。BIND dig 官方手册

传统 DNS 常使用 UDP 53,也支持 TCP 53;响应被截断时,客户端通常会改用 TCP 取得完整结果。TCP 并非只有区域传送才使用,DNS 也不是"永远只走 UDP"。加密 DNS 又有其他传输方式,排障时应先确认应用实际采用哪一种。RFC 7766:DNS over TCP


五、从 URL 到网页:DNS 完成后还有什么

输入 https://www.example.com/docs?q=dns 时,URL 至少携带方案、主机名和路径等信息。DNS 查询关心名称与记录,普通 A/AAAA 查询不会拿 /docs?q=dns 当成域名的一部分。

以新建 HTTP/1.1 或 HTTP/2 的 HTTPS 连接为简化模型,可以顺着下面的步骤讲:

text 复制代码
解析 URL 与检查已有缓存 / 连接
        ↓
必要时解析主机名,得到地址候选
        ↓
选择地址,通过路由与当前链路发送数据
        ↓
建立 TCP 连接,再进行 TLS 握手与身份验证
        ↓
发送 HTTP 请求,接收响应
        ↓
解析页面、加载子资源、布局与绘制

其中,路由选择和邻居解析服务于实际发包,DNS 查询本身也可能需要经过这些步骤。应用缓存命中、连接复用、代理和 Service Worker 等机制,都可能改变观察到的顺序。HTTP/3 则使用 QUIC,不能把 TCP 三次握手硬套给所有 HTTPS 访问。RFC 9114:HTTP/3

这一节最值得保留的排障思路是分层提问:名字解析到了哪个地址?程序实际连接的是哪个地址?连接建立到了哪一步?响应内容是否符合预期?

例如,直接把网址换成 IP 并不是严格等价的测试。基于域名的虚拟主机、TLS 的 SNI 和证书名称校验都可能受到影响。即便 DNS 没问题,这种替换也可能让请求访问错误站点或验证失败。

反过来,ping www.example.com 若先打印出了一个地址,只能说明它此次使用的名称解析路径取得了地址;浏览器可能走另一条解析路径、选择另一个地址族,甚至复用已有连接。


六、ICMP:IP 的反馈机制,不是可靠性保证

6.1 IP 尽力交付,ICMP 提供部分反馈

IPv4 不负责保证每个包都成功到达。有些包可能因为目的不可达、TTL 耗尽或其他原因被丢弃。ICMP 为其中一些情况提供反馈,也提供回显等诊断机制。

不过,"有反馈机制"不等于"每次失败一定有通知"。差错报文本身可能丢失,被过滤或被限速,有些场景按协议规则也不产生相应差错。ICMP 不会替原包自动完成可靠重传。RFC 792:ICMP 的目的与边界

这也纠正了一个常见说法:ping 可以探测一次 Echo 交互,不能据此确认此前所有业务 IP 包都成功交付。

6.2 封装在 IP 里,仍承担网络层控制职责

IPv4 中,ICMP 由 IP 承载,IPv4 首部的 Protocol 值为 1。它不因为出现在 IP 的数据区域中,就变成 TCP、UDP 那样的传输层协议。

ICMP 报文开头的公共字段是:Type 8 位、Code 8 位、Checksum 16 位。后续布局依消息类型而变化。对于 Echo 请求和应答,再跟随 Identifier、Sequence Number 和数据。

Identifier 与 Sequence Number 帮助关联 Echo 请求和响应,它们不是 TCP/UDP 的源端口与目的端口。这就是"ping 使用哪个端口"这道题不能套用 22、80、443 的原因。

6.3 先记住经常遇到的四种类型

ICMPv4 Type 名称 典型用途
8 Echo Request ping 发出的回显请求
0 Echo Reply 对回显请求的应答
3 Destination Unreachable 目的不可达,具体原因由 Code 区分
11 Time Exceeded 转发 TTL 耗尽,或分片重组超时等情况

同为 Type 3,Code 3 表示端口不可达,Code 4 表示需要分片但设置了 DF。Type 11 的 Code 0 对应转发途中 TTL 耗尽,Code 1 对应分片重组超时。读抓包时,不能只看 Type 而忽略 Code。

这里出现"端口不可达"也不矛盾:ICMP 可以报告原始 UDP 报文所遇到的端口问题,但 ICMP 自己没有因此获得 UDP 首部。差错报文会携带原始报文的一部分,供接收端关联触发它的通信。

IPv6 使用 ICMPv6,消息编号不能直接照搬 IPv4 表格。它还承担邻居发现等职责;例如 IPv6 路径 MTU 发现依赖 Packet Too Big 消息提供关键反馈。RFC 8201:IPv6 路径 MTU 发现

因此,按需管理回显探测与"一概丢掉所有 ICMP/ICMPv6"是不同的策略。后者可能连正常网络机制需要的信息一起阻断。


七、ping:测到了什么,又没有测到什么

7.1 先解析名字,再发 Echo

在 Linux 上可以运行:

bash 复制代码
ping -4 -c 4 example.com

-4 固定 IPv4,-c 4 发送四次,避免一直运行。目标位置应填主机名或 IP,不要填 https://example.com/path 这样的完整 URL。若系统禁用相关能力或套接字权限不足,ping 可能需要管理员调整权限;本地报权限错误不代表对端不通。

其主要交互是:主机 A 发出 Type 8、Code 0 的 Echo Request,目标在允许回应时返回 Type 0、Code 0 的 Echo Reply。响应沿自己的返回路径回到 A,不要求它与去程经过完全相同的路由器。

7.2 读懂一行响应

以下也是教学输出:

text 复制代码
64 bytes from 203.0.113.80: icmp_seq=1 ttl=54 time=18.2 ms

icmp_seq=1 用来关联探测序号;time=18.2 ms 是本次往返时间 RTT,不能直接当作单向传播时延;ttl=54 是收到的响应 IP 包的剩余 TTL,而不是 DNS 缓存期限。

如果猜测目标初始 TTL 为 64,可以得到返回方向经历约十次递减的估计。但初始值和路径并未从这一行中得到证实,因此不能把它当作准确拓扑,也不能单凭 TTL 精确识别操作系统。

在常见 iputils 默认设置下,56 字节数据加上 8 字节 ICMP Echo 首部,形成输出里常见的 64 字节;IPv4 首部与链路层开销不包含在这个 64 字节里。更换 payload 大小、协议版本或实现后,显示细节也可能变化。iputils ping 手册

7.3 超时与成功,都要带着范围解释

ping 成功,说明至少这些回显探测收到了匹配响应。它不检查 TCP 443 是否监听、TLS 证书是否有效、HTTP 是否返回 200,更不保证数据库查询正常。

ping 超时,可能是目标不回 Echo、路径过滤、临时丢包、响应限速,也可能是请求或响应方向的真实故障。单靠超时无法确认原因,更不能断言"主机已经关机"。

统计同样如此:四个包丢了一个,只能说明这次少量样本里出现了未收到响应的情况。要判断持续丢包与业务影响,需要更多时间维度的观测,并结合实际业务流量。

如果想把 DNS 与后续探测分开,可以先解析,再对本次取得的真实地址进行比较测试。不要把文章中的示例地址直接拿来当公共服务使用。


八、traceroute:利用 TTL 逐跳获得线索

8.1 每轮换一个 TTL,目标地址仍是终点

假设路径是 A → R1 → R2 → B。A 先发送 TTL 为 1 的探测包,目的 IP 仍为 B。R1 在转发时使 TTL 耗尽,于是丢弃探测包,并可能向 A 返回 ICMP Time Exceeded。

下一轮 TTL 为 2,探测可以越过 R1,却在 R2 处耗尽。第三轮 TTL 为 3,在这个简化拓扑中可以到达 B。工具通过各轮反馈,逐步得到沿途响应节点的信息。

关键点是:每轮主动发出去的是新的探测包,目标始终是 B;不是先知道每台路由器的 IP,再逐台 ping。

8.2 探测协议和返回反馈要分开

Linux 上常见 traceroute 默认使用 UDP 探测,并借助沿途返回的 ICMP 超时消息观察路径。还可以指定 ICMP 或 TCP 探测:

bash 复制代码
traceroute -4 -n example.com
traceroute -4 -n -I example.com
traceroute -4 -n -T -p 443 example.com

-n 关闭中间节点的反向名称解析,避免把额外 DNS 等待混进观察;它不表示输入的目标域名无需先解析。不同方法所需权限和可用选项取决于实现及系统配置。Linux traceroute 手册

默认 UDP 模式通常向不太可能有服务监听的目的端口发送探测;到达终点时,若收到端口不可达,可据此判断已触达目标。ICMP 模式以 Echo Reply 识别目标应答,TCP 模式可能借助 SYN/ACK 或 RST 判断终点。

因此,不能把"中间反馈常用 ICMP"简化成"所有 traceroute 发出的探测都必然是 ICMP"。TCP 探测触达 443,也不代表它已经完成一次有效的 HTTPS 页面请求。

Windows 的 tracert 使用 ICMP Echo 探测,常用写法是 tracert /4 /d example.com,其中 /d 关闭中间节点的名称解析。它与常见 Linux 默认 UDP 模式不同,比较两台系统的输出时,要把探测方法一起记录。Microsoft:tracert

8.3 星号不是一张断路证明

一行出现 * * *,通常表示对应探测在等待期限内未收到可用于展示的响应。中间路由器可能照常转发业务包,却不回复或限速回复给自己的诊断消息。

如果中间一跳是星号,而后续仍有响应,就不能简单宣布链路在该跳断开。同样,某跳 RTT 很高、后面各跳却恢复正常,可能反映诊断响应的处理优先级,而不是经过该设备的所有流量都同样缓慢。

每个 RTT 都是"本机发出探测到本机收到该次反馈"的往返时间,包含返回路径;不能把后一跳 RTT 减去前一跳 RTT,当成两台路由器之间的精确单向时延。

此外,负载均衡可能让不同探测走不同路径,设备也可能选择不同接口地址发送反馈。traceroute 给出的是观察线索,不是永远固定、必然完整的物理拓扑图。


九、离线实验:亲手拆开 DNS 首部与 ICMP 回显

前面反复出现"字段""类型"和"响应",如果能把它们变成字节,理解会更扎实。配套脚本 code/dns_icmp_lab.py 只使用 Python 3 标准库,在内存中构造并检查教学报文,不联网,也不修改系统配置。

9.1 用 12 字节读懂 DNS 首部

传统 DNS 消息的固定首部由六个 16 位字段组成:ID、Flags、QDCOUNT、ANCOUNT、NSCOUNT、ARCOUNT。Python 中可以用网络字节序解包:

python 复制代码
DNS_HEADER = struct.Struct("!6H")
ident, flags, qd, an, ns, ar = DNS_HEADER.unpack_from(message)
qr = (flags >> 15) & 1
rd = (flags >> 8) & 1
ra = (flags >> 7) & 1
rcode = flags & 0x0f

! 表示网络字节序及标准大小。脚本构造一个查询 example.com. A 记录的报文,再构造带一个 A 回答的响应。读出 QR=1、RCODE=0、ANCOUNT=1 后,能够对照前面的 dig 字段。

问题区的域名不是普通的点分字符串原样复制:标签前带长度,名称末尾用零长度标签结束。例如 example.com. 编码为长度 7、example、长度 3、com、结束字节 0。

完整 DNS 解析还涉及名称压缩、长度边界、各种记录类型和扩展。配套脚本只构造一个有限样本并解析固定首部,不是可以拿任意网络输入直接使用的完整 DNS 解析器。

9.2 计算 ICMP 校验和

ICMPv4 的校验和针对 ICMP 消息计算。把字节按 16 位分组做一补和,进位回卷,最后按位取反;如果原始长度为奇数,计算时在尾部补一个零字节。

python 复制代码
def checksum(data):
    if len(data) % 2:
        data += b"\x00"
    total = sum(struct.unpack(f"!{len(data) // 2}H", data))
    while total >> 16:
        total = (total & 0xffff) + (total >> 16)
    return (~total) & 0xffff

构造报文时先把 Checksum 字段写成 0,计算结果后再填回。对一个包含正确校验和的完整 ICMP 消息重新执行这个函数,应得到 0。

这里的补零只用于计算,不表示必须把补零字节加入发送数据。IPv4 Echo Reply 与 Request 的 Type 不同,不能只改 Type 而保留旧校验和。ICMPv6 校验还涉及伪首部,不能直接照搬本节的 IPv4 构造方式。

9.3 运行与观察

在文章包目录运行:

bash 复制代码
python3 code/dns_icmp_lab.py

Windows 的命令名如果是 python,改用 python code/dns_icmp_lab.py 即可。预期关键输出:

text 复制代码
DNS query: bytes=29 id=0x1234 qr=0 qd=1
DNS reply: bytes=45 qr=1 rcode=0 answers=1
ICMP request: type=8 code=0 id=4660 seq=1 bytes=17
ICMP reply: type=0 code=0 id=4660 seq=1 bytes=17
Checksum verification: request=0 reply=0
Corrupted packet detected: True
Self-checks passed: 12

实验既检查正确报文,也检查截断首部、错误的名称标签以及人为改坏的数据。它验证的是本地构造与字段计算,不代表已完成 Linux 实网抓包或证明某个公网服务可达。

可以进一步尝试:把序号改为 2,解释哪些字节变化;把数据改成偶数长度,观察补零分支;把 DNS 响应的 RCODE 改成 3,再思考为什么它只改变"响应状态",不会凭空带来一个有效 IP 地址。


十、排障顺序、常见误区与自测

10.1 让每一个命令回答一个具体问题

以"访问网站失败"为例,可以按下面的顺序收集证据:

当前问题 观察方式 结果的边界
系统把名字解析成什么 getent ahosts example.com 不一定与浏览器独立解析路径一致
指定 DNS 查询返回什么 dig example.com A / AAAA 不证明地址可达或应用正常
到真实地址应从哪里发出 ip route get <实际 IPv4 地址> 显示路由选择,不证明端到端成功
Echo 能否往返 ping -4 -c 4 <实际 IPv4 地址> 不检查 TCP 服务与 HTTP 内容
哪些中间节点回应探测 traceroute -4 -n <实际 IPv4 地址> 未回应不等于不转发
实际 HTTPS 请求在哪一步失败 curl -v --connect-timeout 5 --max-time 15 https://example.com/ 还需判断证书、状态码及响应内容

上表尖括号表示需要替换的值,不能原样作为 shell 命令执行。各项是独立证据,不能把前一项的失败设为"后续必然失败"的机械门槛,例如 Echo 被过滤时仍可以继续检查 HTTPS。

如果要对照某个地址并保留原主机名语义,可用 curl 的 --resolve。下方仅演示写法,应把示例地址换成服务当前可用、且你准备验证的真实地址:

bash 复制代码
curl -v --noproxy '*' --connect-timeout 5 --max-time 15 \
  --resolve example.com:443:203.0.113.80 https://example.com/

它为这次连接提供指定地址,同时保留 URL 中的主机名用于请求与证书校验;--noproxy '*' 用于这次直连对照,避免环境代理改变实验含义。若所在网络必须走代理,直连失败也不能直接归因于目标服务器。无需通过关闭证书校验来"证明连通"。curl 官方手册:--resolve

10.2 八个经常被过度简化的结论

容易说错 更准确的表达
DNS 就是一张域名到 IP 的表 它是分层、分布式的资源记录系统
hosts 永远比所有 DNS 查询优先 看系统名称服务配置与应用使用的解析方式
每次查域名都从根问到权威 缓存与转发可省略或改变过程
DNS 只用 UDP 53 传统 DNS 同样支持 TCP 53
NOERROR 就一定获得地址 还要看问题类型与响应区段
ping 通就说明网站正常 只证明相应 Echo 探测收到回复
ping 使用某个 TCP/UDP 端口 ICMP Echo 没有 TCP/UDP 端口字段
traceroute 星号说明该路由器坏了 只说明相关探测未及时得到可展示响应

10.3 自测一:为什么改 hosts 后 dig 没变化

题目: 系统使用 hosts: files dns,本地 hosts 为某个名字新增映射。getent 得到新地址,dig 仍返回 DNS 中的旧地址,是否一定是系统缓存没有清干净?

答案: 不一定。两种工具的查询路径不同:getent 使用系统名称服务机制,dig 执行 DNS 查询。本地文件修改不会自动更新远端 DNS 记录,先确认观测对象再讨论缓存。

10.4 自测二:两个 TTL 能互相推算吗

题目: dig 显示 A 记录 TTL 为 120,ping 显示响应 ttl=52,能否说"缓存经过了 68 台路由器"?

答案: 不能。前者是记录的缓存时间,后者是响应 IP 包的剩余生存限制。两者作用对象、计量意义完全不同,没有这种减法关系。

10.5 自测三:域名解析成功,ping 超时,HTTPS 成功

题目: 同一时段 DNS 能取得地址,ping 没有收到回应,但对正确站点的 HTTPS 请求成功。这三件事矛盾吗?

答案: 不矛盾。名称解析、ICMP Echo 和 HTTPS 是不同交互,过滤与响应策略可以不同。还应确认它们实际使用的目标地址、地址族和代理路径是否一致。

10.6 自测四:第二跳没有回复,第三跳有回复

题目: traceroute 第二行是星号,第三行又有节点响应,能否直接把第二跳判为故障点?

答案: 不能。第二跳可能不发送诊断反馈,却仍转发了 TTL 更大的探测;不同探测也可能走不同路径。需要结合连续观测、探测方法和业务表现判断。


写在最后

DNS 让名字与网络资源建立联系,ICMP 让部分网络状态有机会被看见。真正排查问题时,我们需要的不是一条万能命令,而是把每次观测放回它对应的层次。

看到一个 DNS 结果,先问"谁回答的、什么类型、缓存多久";看到一次 ping 回复,先问"哪个地址、哪个方向、证明了哪一次往返";看到一份 traceroute,先问"用了什么探测、哪些节点回应、星号还有哪些解释"。

当这些问题能被分别回答,"网络通不通"就不再是一个模糊判断,而会变成一条可以逐步验证的分析路径。

相关推荐
niucloud-admin3 小时前
NIUCLOUD PHP SAAS上门家政使用文档--服务订单:订单列表
开发语言·php
一枕槐安4873 小时前
大模型限流、重试、超时处理:API 调用健壮性方案
linux·服务器·php·大模型api·指数退避·限流控制·超时预算
黑十3 小时前
PyTorch 报 “no CUDA GPUs are available“ 怎么办?3 步排查
服务器·网络·人工智能·pytorch·python
西索斯coding3 小时前
gpt-image-2.5-sunburst 和 gemini-3.1-flash-image 差多少?60 组项目素材的成功成本评测框架
java·linux·gpt·ai
箓维3 小时前
应用层协议基础_序列化与反序列化
linux
李纲明3 小时前
WordPress 白屏或提示「出现了严重错误」:先开 WP_DEBUG 抓致命错误,再二分插件与主题
运维·php·debug·wordpress·白屏·插件冲突·devdiy
wuminyu4 小时前
Java字节码学习总结:字节码如何加载、解析和执行
java·linux·c语言·jvm·c++
十年磨一剑~4 小时前
Linux 内核中断处理机制与软硬件协同流程
linux·嵌入式硬件
Ruiery4 小时前
Linux 6.6内核并发与同步(五):mutex 可睡眠锁
linux·运维·服务器