🔥 星光编译者 · 个人主页
📚 学习专栏: 《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 帮我们获得部分网络诊断与差错反馈。 它们都很重要,却都不能单独证明整个业务已经正常。
文章目录
- [从 hosts 到 DNS:名字为什么需要一套系统](#从 hosts 到 DNS:名字为什么需要一套系统)
- [域名的树与记录:一个名字不只对应一个 IP](#域名的树与记录:一个名字不只对应一个 IP)
- 一次解析怎样完成:递归、迭代与缓存
- [读懂 dig:别只盯着 ANSWER 里的地址](#读懂 dig:别只盯着 ANSWER 里的地址)
- [从 URL 到网页:DNS 完成后还有什么](#从 URL 到网页:DNS 完成后还有什么)
- [ICMP:IP 的反馈机制,不是可靠性保证](#ICMP:IP 的反馈机制,不是可靠性保证)
- ping:测到了什么,又没有测到什么
- [traceroute:利用 TTL 逐跳获得线索](#traceroute:利用 TTL 逐跳获得线索)
- [离线实验:亲手拆开 DNS 首部与 ICMP 回显](#离线实验:亲手拆开 DNS 首部与 ICMP 回显)
- 排障顺序、常见误区与自测
一、从 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 记录,缓存没有可用信息,解析器自行完成查找:
- 客户端向递归解析器提出查询。
- 解析器询问根服务器,取得
.com的委派线索。 - 解析器询问
.com服务器,取得example.com的委派线索。 - 解析器询问相应权威服务器,获得记录;若有别名,可能继续跟进。
- 解析器整理响应、缓存允许缓存的数据,再答复客户端。

图中的往返是按角色简化的过程,不是"客户端依次直连根、顶级域、权威服务器"。缓存可用时,很多外部步骤都会省略;使用转发器时,还可能由上游递归解析器继续完成任务。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,先问"用了什么探测、哪些节点回应、星号还有哪些解释"。
当这些问题能被分别回答,"网络通不通"就不再是一个模糊判断,而会变成一条可以逐步验证的分析路径。