☰
DNS、ICMP与NAT:网络连通性故障分层排查实战
2026/10/26 4:47:02 网站建设 项目流程

后台服务的健康检查每隔几分钟就报一次超时,登上机器翻日志却干干净净——这种"看着没问题但就是不通"的故障,十有八九落在三层到四层之间的缝隙里。我这几年排查这类问题,翻来覆去用的就是三样东西:DNS 解析、ICMP 探测、NAT 转换。标题里那个"NDS"我猜是 DNS 的手滑,这个笔误在搜索框里出现频率高得离谱,顺手在这儿纠正一下,免得后面看资料时对不上号。

这三个协议之所以总被捆在一起讲,是因为它们构成了"一台机器怎么找到另一台机器"的完整链条:DNS 负责把人类记得住的名字翻译成地址,ICMP 负责告诉你路径通不通、卡在哪个节点,NAT 则决定了这个地址在跨网段时会被改写成什么样。做运维、做后端、做客户端网络库,甚至只是家里想跑个自建服务做端口映射,绕不开这三块。下面我按"为什么这么设计—核心细节怎么抠—实操怎么落地—坑怎么绕"的顺序摊开讲,内容偏实战,基础概念会带一句但不会停在概念上。

1. 三个协议为什么总被放在一起讲

1.1 从一次"网站打不开"的排查现场说起

我印象最深的一次,某公司内网的业务系统早上集体打不开,报错五花八门:有浏览器转圈到超时的,有直接甩ERR_NAME_NOT_RESOLVED的,还有一部分人莫名其妙能开但慢得要命。三个现象指向三个完全不同的层,如果按"先怀疑应用"的惯性思维去查代码,那天基本就废了。

实际的排查路径是这样的:先在故障机上ping网关,通;再ping一个公网地址,通;然后nslookup域名,超时。到这里问题范围就收敛到 DNS 了。往下追,发现内网 DNS 服务器的上游转发配置指了一个早就下线的地址,缓存过期后所有新查询全部超时,而缓存里还有记录的那部分用户恰好能开——这就是为什么"有的人能开有的人不能开"。

这个案例里三个协议各司其职:ping用的是 ICMP,它的"通"只说明 IP 层可达,不代表 TCP 端口能连、更不代表名字能解析;nslookup走的是 DNS,负责名字到地址的映射;如果这台机器藏在 NAT 后面,你还得额外确认 NAT 有没有把回包的转换做对。把"能 ping 通"当成"服务正常"是新手最容易犯的错,也是最容易在排查时把自己带沟里的认知。

1.2 分工模型:名字解析、可达性探测、地址转换

把三者的职责摆到一张表里,关系就一目了然了。

协议解决的问题工作层次典型端口/类型号常见误用
DNS名字 → 地址的映射与分发应用层(UDP/TCP 53)53、853、443用 ping 域名判断 DNS 是否正常
ICMP路径可达性、差错回报、路径发现网络层(IP 协议号 1)Type/Code 组合用 ping 通断定端口开放
NAT私网地址与公网地址的转换网络层/传输层之间无独立协议,依附 IP/TCP/UDP认为 NAT 只是"改个源地址"

这三者的一个关键差异在于:DNS 是"有状态的应用协议",ICMP 是"无连接的差错信道",NAT 是"设备上的转换行为"而不是一个可以被单独抓出来的协议。NAT 严格来说不算协议,它是路由器/防火墙在转发时对报文头做的改写动作,但因为改写的规则直接影响了 TCP/UDP 的连通性判断,所以业内习惯把它和协议放在一起讨论。

我个人的经验是,把这三个东西当成"三层排查工具"来记最省脑子:名字层出问题看 DNS,IP 层出问题看 ICMP,跨网段出问题看 NAT。真出故障时,按这个顺序从下往上推,八成能在十分钟内把范围框住。

1.3 分层视角下的封装关系与抓包位置

理解封装关系,才知道抓包该在哪抓、抓到的东西长什么样。DNS 查询会被塞进 UDP 报文(大响应或区域传送时走 TCP),UDP 再塞进 IP,IP 再塞进以太网帧。所以在链路上抓包,你看到的是"以太网头 + IP 头 + UDP 头 + DNS 报文"这一串套娃。

ICMP 稍微特殊一点:它虽然被封装在 IP 里(协议号 1),但它不是传输层协议,没有端口概念。这就解释了为什么ping不需要指定端口,也解释了为什么防火墙按端口做策略时经常会"漏掉"ICMP——很多策略模板只写 TCP/UDP 规则,ICMP 默认跟随后面的兜底策略走。

NAT 则发生在转发路径上,位置取决于设备:家用路由器上它发生在 LAN 口到 WAN 口的转发过程中,云环境的 NAT 网关上是发生在虚拟网络边界。抓包的黄金位置是NAT 设备的两侧同时抓,对比同一个五元组在进出两个方向的差异,能立刻看出转换有没有做对、端口有没有被改。

提醒一句:在 NAT 设备内侧抓包看到的是私网地址,在外侧抓包看到的是公网地址,同一个连接在两边的五元组是不一样的,比对时别拿源端口硬对,要先找到能对应上的特征(比如序列号或者载荷内容)。

2. DNS:把名字翻译成地址的那套体系

2.1 递归查询与迭代查询,到底谁在干活

DNS 最容易被讲糊涂的地方就是"递归"和"迭代"。我用一个类比:你去问路,递归是你只问一个人,这个人负责帮你把路问到底,最后直接告诉你终点怎么走;迭代是你问一个人,他告诉你"往东走两公里问下一个人",你自己一段一段问过去。

在真实世界里,两种角色是并存的。你电脑上的 stub resolver(系统里的解析器)发出的是递归查询,要求对方"必须给我最终答案";而本地 DNS 服务器在拿不到缓存时,向根、顶级域、权威服务器发出的是迭代查询,一级一级往下问。这个分工不是随便定的,因为全球的 stub 客户端数量是天文数字,如果每个客户端都自己从根服务器开始迭代,根服务器早就被打穿了。让少量递归服务器承担完整的查询链路并做缓存,是整个体系能撑住的关键。

一次完整的冷查询大致是这样走的:

  1. 客户端向配置的递归服务器发查询,置 RD(Recursion Desired)位为 1。
  2. 递归服务器无缓存,向根服务器询问,根服务器返回顶级域服务器的 NS 记录(这是参考,不是答案)。
  3. 递归服务器向顶级域服务器询问,拿到权威服务器的 NS 记录。
  4. 递归服务器向权威服务器询问,拿到最终的 A/AAAA 记录。
  5. 递归服务器把结果返回给客户端,并按 TTL 缓存。

整个过程对客户端来说是"一次往返",对递归服务器来说是"四次往返"。所以首次解析慢、后续解析快是正常现象,别把冷启动的延迟当成故障。

2.2 记录类型速查:A、AAAA、CNAME、MX、NS、TXT 各自管什么

实际工作里天天打交道的就那几种,我整理了一份带使用场景的速查表。

类型含义典型用途容易踩的坑
A名字 → IPv4 地址常规域名解析多条 A 记录会轮询,不是负载均衡的严格实现
AAAA名字 → IPv6 地址双栈服务客户端有 IPv6 但链路不通时会长时间超时
CNAME别名 → 另一个名字CDN 接入、域名迁移根域名(@)不要轻易设 CNAME,会和 MX/NS 冲突
MX邮件交换记录邮件投递优先级数字越小越优先,别写反
NS该域由哪些服务器负责解析域名托管修改 NS 后生效依赖上级 TTL
TXT任意文本域名所有权验证、邮件防伪策略单条长度有限,内容长了要拆多条
PTR地址 → 名字反向解析需要向地址段的管理方申请,自己配不了
SRV服务名 + 端口 + 目标服务发现客户端支持度参差,别指望所有库都认

关于 CNAME 有个实战细节:很多 CDN 厂商要求你把域名 CNAME 到他们给的别名上,这时候如果你同时还想在这个域名上收邮件,就得把 MX 记录挂在别的子域上,因为CNAME 记录一旦存在,同名的其他类型记录就都不该存在。这个限制来自 RFC,不是某家厂商的任性规定,违反了会出现解析结果不稳定。

2.3 TTL 与缓存:性能与变更速度的拉锯

TTL 是 DNS 里最容易做错决定的一个参数。它决定了任何一级缓存(系统缓存、递归服务器缓存、甚至某些中间设备)能把这个答案保留多久。

选择的逻辑很简单:你要的是"改得快"还是"查得少"。

  • 面向大量用户的稳定服务,TTL 设长(3600 秒甚至 86400 秒)能显著降低递归服务器的查询压力,也减少解析延迟。
  • 准备做迁移或切换的域名,提前把 TTL 调短(比如 60 秒或 300 秒),等旧 TTL 自然过期后再切,切换时影响面最小。
  • 临时性、需要快速回滚的记录,TTL 压到 30~60 秒。

我踩过最典型的一个坑:某次紧急切换解析,把 A 记录直接改了,结果有一部分用户访问到了旧地址,持续了将近一天。原因就是旧记录的长 TTL 还在各级缓存里躺着,你改了权威服务器的记录,缓存并不会立刻失效。正确姿势是提前一个 TTL 周期(比如原 TTL 是 24 小时,就提前 24 小时以上)把 TTL 降到 300 秒,等旧缓存自然淘汰,再改记录,改完观察没问题后再把 TTL 调回来。这个流程看着繁琐,但能省掉一次线上事故。

另一个坑是"负缓存"。查询不存在的名字时,返回的 NXDOMAIN 也会被缓存,缓存时长由 SOA 记录里的 minimum 字段控制。所以如果你刚创建了一条记录却发现"还是查不到",很可能是之前查过不存在的负缓存还没过期,等一会儿或者换个递归服务器验证。

2.4 用 dig 把一次解析过程完整看一遍

nslookup够用,但想看细节还得靠dig。下面这条命令能把从根到权威的完整路径打出来:

dig +trace www.example.com A

输出会依次列出根服务器返回、顶级域返回、权威服务器返回的每一跳,哪一跳断了一眼就能看出来。如果只想看某个指定递归服务器的回答,用:

dig @192.0.2.53 www.example.com A +noall +answer

+noall +answer这个组合只保留答案段,输出干净,适合塞进脚本里做解析结果的自动化校验。诊断解析慢的时候,我会加上+stats看查询耗时:

dig @192.0.2.53 www.example.com A +stats

输出末尾的Query time: 128 msec是单个查询的耗时。如果这个值稳定在几百毫秒,说明递归服务器本身在往上迭代查(缓存没命中),需要检查它的缓存配置或者上游转发设置;如果偶尔飙到几秒,多半是网络抖动或者上游服务器超时重试。

排查"解析结果不一致"时,我会用同一组命令对着多个递归服务器各跑一遍,对比答案段里的 IP 和 TTL。TTL 相差很多是正常的(各级缓存剩余时间不同),但IP 完全不同就说明有多套权威配置或者存在解析劫持,这时候要看权威服务器上到底配了什么,而不是在客户端瞎折腾。

关于 DNS 的传输安全,现在有个常见做法是把查询封装进加密通道,也就是常说的 DoT(DNS over TLS,853 端口)和 DoH(DNS over HTTPS,443 端口)。它们解决的问题是"查询内容在链路上以明文传输、可被中间设备看到甚至篡改"。是否启用要看场景:企业内部可以自建加密解析服务统一出口策略,客户端侧启用则要确认系统或应用支持,并且注意别让两条解析通道(系统默认和加密通道)给出不一致的结果,那种"浏览器能开、命令行不能开"的怪现象往往就是这么来的。

3. ICMP:网络层那个不太起眼的传令兵

3.1 报文结构:Type 与 Code 的组合语义

ICMP 报文结构非常简单:类型(Type)、代码(Code)、校验和,然后是四个字节的"类型相关字段",再往后是数据。真正携带语义的是 Type 和 Code 的组合。

TypeCode含义出现场景
00Echo Replyping的回包
30网络不可达路由表里没有目标网段
31主机不可达ARP 失败或主机离线
33端口不可达UDP 服务未监听,也是 UDP 探测的判据
34需要分片但设置了 DFPMTU 探测的关键信号
80Echo Requestping的请求
110TTL 超时traceroute的核心机制
111分片重组超时分片报文没凑齐

Type 3 Code 3 这个组合值得单独说:因为 UDP 是无连接的,没有握手过程,客户端无法从协议本身得知"对方端口有没有开"。这时候 ICMP 的端口不可达消息就成了唯一的反馈信号。端口扫描工具里用 UDP 扫描时,收到 ICMP 端口不可达就判定"端口关闭",什么都没收到则判定"开放或被过滤",这个判断逻辑就是建立在这个组合上的。

3.2 ping 背后到底发生了什么

ping的流程比想象中简单:构造 ICMP Echo Request,填一个序列号和发送时间戳,发出去,等待同序列号的 Echo Reply,收到后用当前时间减去报文里的时间戳,得到 RTT。

ping -c 4 -s 1472 198.51.100.10

这条命令里的-s 1472是个关键参数。以太网 MTU 是 1500 字节,减去 IP 头 20 字节、ICMP 头 8 字节,留给数据的空间正好是 1472。用这个大小发包能验证链路上是否允许最大尺寸的报文通过,如果 1472 通、1473 不通,那 MTU 就是标准值;如果 1472 不通而小包通,说明链路上有设备把 MTU 压小了,这就是典型的 MTU 不匹配问题。

ping的输出里几个字段值得盯:

  • time=的值反映 RTT。局域网内应该在 1ms 以内,跨地域几十毫秒正常,超过 200ms 就要看是不是绕路了。
  • icmp_seq连续但 time 波动大,说明链路质量不稳。
  • 出现DUP!表示收到了重复回包,往往是链路存在二层环路或者有设备在做报文复制。
  • 全部Request timeout,先别急着说网络坏了,看看是不是对方禁了 ICMP。

3.3 traceroute 的两种实现思路

traceroute的巧妙之处在于它利用了 TTL 机制。IP 头里的 TTL 每经过一个路由器减 1,减到 0 时路由器会丢弃报文并回一个 ICMP Time Exceeded(Type 11)。那么只要从 TTL=1 开始递增,就能逐个逼出路径上的每一跳。

具体有两种实现路线:

第一种是传统做法,发 UDP 报文到目标的高位端口(比如 33434 起),TTL 从 1 递增,靠中间路由器的 Time Exceeded 定位跳数,靠最终目标的 Port Unreachable 判定到达。

第二种是 ICMP 路线,直接发 Echo Request,TTL 递增,中间设备照旧回 Time Exceeded,目标回 Echo Reply。某些系统上traceroute -I就是这个模式。

traceroute -n -w 1 -q 1 203.0.113.20

-n不反解域名(快得多,也避免 DNS 拖慢输出),-w 1把单次等待压到 1 秒,-q 1每跳只发一个包。这个组合在排查时特别好用,输出干净,三十秒内就能把整条路径摸清。

输出里的* * *有两层含义:可能是该跳的设备不回应 ICMP(很多核心路由器为了减轻 CPU 负担会限制 ICMP 生成速率甚至完全关闭回应),也可能是真的丢包。判断方法是看后续跳数是否还能继续推进——如果某一跳全是星号但更远的跳数正常显示,说明只是那台设备不回,路径本身是通的。

3.4 PMTU 与"小包能通、大包卡死"

这是 ICMP 相关故障里最隐蔽、也最让人抓狂的一类。现象很统一:ping小包全通,网页也能开个标题,但一传大文件、一提交长表单就卡住不动,超时后重试又是同样的结果。

根因在路径 MTU 发现(PMTU Discovery)。发送端默认按自己的网卡 MTU(通常 1500)发大包并设置 DF 标志(不允许分片),如果路径中间有一段链路的 MTU 只有 1400,那台设备应该回一个 ICMP Type 3 Code 4,告诉发送端"需要分片但 DF 置位了,请把包改小"。发送端收到后就会降低 MSS 重新发。

问题在于,很多网络设备出于安全策略会丢弃或过滤 ICMP 差错报文,发送端永远收不到这个提示,于是一直用 1500 发,一直失败,表现为"连接建立成功后传不了数据"。这类故障被业内称为 PMTU 黑洞。

排查和处理的思路:

手段操作适用场景
手动探测ping -M do -s <size>二分找临界值定位路径实际 MTU
钳制 MSS在网关上做 TCP MSS clamp,把 MSS 改小无法改变中间链路时的兜底
调小网卡 MTU把出口网卡 MTU 设为探测值可控终端上最简单
放行必要 ICMP至少放行 Type 3 Code 4 和 Type 11从根上解决

ping -M do的意思是"禁止分片"(Linux 下的写法,其他系统参数名不同),配合-s逐级往下试:

ping -M do -s 1472 198.51.100.10 # 通 ping -M do -s 1400 198.51.100.10 # 不通

二分找到临界值后,加上 28(IP 头 20 + ICMP 头 8)就是路径 MTU。比如-s 1372通、-s 1373不通,那路径 MTU 就是 1400。

我在实际项目里更倾向于在网络边界设备上直接做 MSS 钳制,因为它对终端透明、一次配置长期有效。但这么做有个前提:你必须知道路径里最小的 MTU 是多少,否则钳多了影响吞吐、钳少了照样出问题。所以还是建议先把探测做一遍,心里有数再动手。

4. NAT:让私有地址跑通公网的那层壳

4.1 从静态 NAT 到 NAPT 的演进逻辑

最早期的 NAT 很朴素,就是一对一:内部有个 10.0.0.5,外部给它配一个 203.0.113.5,进出双向改写 IP 地址,端口不动。这种叫静态 NAT,好处是规则简单、外部可以主动访问内部,坏处是地址消耗量跟设备数一样多——那还不如直接用公网地址。

真正让 NAT 普及开的是NAPT(Network Address Port Translation,也常被叫 PAT 或端口复用)。它不只改 IP,还改源端口,用"IP + 端口"这个组合来区分不同的内部连接。这样一来,一个公网地址理论上可以承载几万个并发连接,家庭宽带和企业出口都是靠这个才撑起来的。

选择 NAPT 的代价是外部无法主动发起连接,因为外网根本不知道某个内部连接被映射到了哪个端口。这就引出了后面要讲的端口映射和打洞问题。设计上的取舍很清楚:用"失去主动可达性"换"地址复用",在没有更多公网地址的前提下,这笔交易是划算的。

在企业环境里,三种形态经常混用:内部访问互联网用 SNAT(源地址转换),对外发布服务用 DNAT(目的地址转换),内部访问自己对外发布的域名时还要靠 hairpin NAT(回环转换)把流量绕回来。

4.2 连接跟踪表:NAT 真正的心脏

理解了连接跟踪(conntrack),NAT 就没那么神秘了。设备在转发第一个报文时,会在内存里建一条表项,记录"原始五元组"和"转换后五元组"的对应关系,后续报文直接查表改写,不再重复计算。

在 Linux 上可以直接查看这张表:

# 查看当前连接跟踪数量 cat /proc/sys/net/netfilter/nf_conntrack_count # 查看上限 cat /proc/sys/net/netfilter/nf_conntrack_max # 查看具体表项(需要 conntrack 工具) conntrack -L | head -20

一条典型表项长这样:

tcp 6 86399 ESTABLISHED src=10.0.0.15 dst=203.0.113.20 sport=51234 dport=443 src=203.0.113.5 dst=203.0.113.20 sport=40001 dport=443 [ASSURED] mark=0 use=1

前半段是内网视角的原五元组,后半段是转换后的公网五元组。回包进来时,设备按dst=203.0.113.5 dport=40001反查,改回dst=10.0.0.15 dport=51234再转发给内部主机。

表项是有生命周期的,不同协议的超时不一样:

协议/状态默认超时(常见值)说明
TCP ESTABLISHED432000 秒(5 天)长连接能撑住,但也容易堆积
TCP SYN_SENT120 秒握手未完成,超时短
TCP TIME_WAIT120 秒主动关闭方等待
UDP30~180 秒不同内核版本差异较大
ICMP30 秒每个 echo 会话单独记

这张表有容量上限,nf_conntrack_max打满之后新连接会被直接丢弃,现象是"设备 CPU 不高、带宽不满、但新连接就是建不上"。这是我遇到过最隐蔽的故障之一,因为从应用层看只是"偶尔连不上",没有任何明确的错误码。

排查方法很直接:监控nf_conntrack_count / nf_conntrack_max的比值,超过 80% 就该告警了。调优手段有三个方向:调大上限、缩短超时(尤其是长连接和 UDP)、排查是否有异常流量占满了表项。

4.3 NAT 类型差异与 P2P 连通性

家用网络和企业网络里,NAT 的实现并不完全一样,这个差异直接影响点对点通信能不能打通。业内常用的分类是四种:

类型行为特征打洞难度
Full Cone内部某端口映射后,任何外部地址都能发进来最容易
Restricted Cone只有内部先发过包的地址才能发进来(不限端口)较易
Port Restricted Cone只有内部发过包的目标"地址 + 端口"才能发进来中等
Symmetric每个目标地址都分配不同的外部端口最难

Symmetric NAT 是打洞的死敌:你从内网发往 A 服务器时被映射成外部端口 40001,发往 B 服务器时被映射成 40002,那么 A 服务器告诉对端"请连 40001",对端连过来时由于目标地址不同,映射关系不匹配,包直接被丢。这就是很多点对点应用在部分网络环境下连不上的根本原因,跟软件实现没太大关系,是 NAT 行为决定的。

实际处理时,通常靠一个中继服务器兜底:能打洞成功就走直连(延迟低、成本低),打不通就退回中继转发。这套"先尝试直连、失败则中继"的策略是行业通用做法,区别只在于中继的调度策略和成本控制。

补充一点:UDP 打洞的前提是双方的 NAT 映射在打洞期间保持有效。UDP 表项的超时通常只有几十秒,所以保活间隔一定要小于超时时间,常见做法是 15~25 秒发一个心跳。心跳间隔设得太长(比如 60 秒),连接会莫名其妙间断;设得太短又浪费电量和流量,这个参数需要按实际网络的超时值来定。

4.4 端口映射与转发规则实操

对外发布内部服务,最常用的就是 DNAT。下面这段规则把到达公网地址 443 端口的流量转给内网的 10.0.0.20:

# 目的地址转换 iptables -t nat -A PREROUTING -d 203.0.113.5 -p tcp --dport 443 \ -j DNAT --to-destination 10.0.0.20:8443 # 源地址转换(内网访问自己对外域名时需要) iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -d 10.0.0.20 -p tcp --dport 8443 \ -j SNAT --to-source 10.0.0.1 # 放行转发链 iptables -A FORWARD -d 10.0.0.20 -p tcp --dport 8443 -j ACCEPT

第二段规则就是前面提到的 hairpin NAT。因为内网主机访问公网地址时,DNAT 会把目的地址改成 10.0.0.20,但源地址还是 10.0.0.x,服务器回包时会直接走内网路由回给源地址,源主机一看"我请求的是 203.0.113.5,怎么回包来自 10.0.0.20",直接判定为无效包丢弃。加上 SNAT 把源地址也统一改成网关地址,回包路径就对了。

出口方向的 SNAT 更简单,用 MASQUERADE 让设备自动取出口网卡的地址:

iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -o eth0 -j MASQUERADE

MASQUERADE和SNAT的区别在于前者动态读取出口地址,适合出口 IP 会变的场景(比如拨号),代价是每次都要查询一次网卡地址,性能略差;出口 IP 固定的环境用SNAT --to-source更高效。

配置完别忘了开转发:

sysctl -w net.ipv4.ip_forward=1

这个开关不开,所有转发规则都是摆设,而它的默认值在很多发行版上是 0。这个坑我至少踩过三次,每次都是配完规则发现不通,回头一查才发现是转发没开。

5. 三个协议在实际排查中的联动

5.1 一套通用的分层排查顺序

遇到"服务连不上",我基本固定按这个顺序推:

  1. 本机网络栈是否正常:ip addr看有没有拿到地址,ip route看默认路由在不在。
  2. 网关是否可达:ping网关地址。不通就查二层(网线、VLAN、ARP)。
  3. 名字能否解析:dig或nslookup目标域名。解析失败先换一个递归服务器对比,判断是本地配置问题还是服务端问题。
  4. 目标 IP 是否可达:直接ping解析出来的 IP。这一步要留意目标可能禁 ping,所以 ping 不通不等于不可达。
  5. 端口是否开放:nc -zv或者curl -v直接打端口。这一步才真正验证服务。
  6. 路径是否有异常:traceroute看在哪一跳开始延迟飙升或者丢包。
  7. 跨网段场景补查 NAT:conntrack -L看表项建没建、转换对不对。

这个顺序的核心逻辑是从下往上、由近及远,每一步都把问题范围砍掉一半。最忌讳的是跳步——比如直接curl一个域名,失败了不知道是 DNS 的问题、路由的问题、还是服务本身的问题,然后又回头从头查,时间全浪费在反复上。

5.2 抓包视角:一次请求里三个协议出现在哪

tcpdump抓一次完整的域名访问,能把三者串起来看:

tcpdump -i any -nn 'port 53 or icmp or (tcp port 443)' -c 50

过滤表达式里port 53抓 DNS,icmp抓 ping/traceroute 的信令,tcp port 443抓实际业务流量。抓下来的时序大致是:

  1. 一条 UDP 53 的查询出去,一条 UDP 53 的响应回来——DNS 完成。
  2. 如果中间路径有问题,会夹杂 ICMP Time Exceeded 或 Destination Unreachable。
  3. 然后是 TCP 三次握手,SYN、SYN-ACK、ACK——业务连接建立。
  4. 之后是 TLS 握手和数据传输。

如果只看到 DNS 查询没看到响应,问题在解析链路;如果 DNS 正常但 SYN 发出去没有 SYN-ACK,问题在路由或对端服务;如果 SYN 有去有回但后续数据卡住,就要怀疑 MTU 或者 NAT 表项的问题了。

在 NAT 设备两侧同时抓包是最有说服力的做法。内侧抓到10.0.0.15:51234 → 203.0.113.20:443,外侧应该抓到203.0.113.5:40001 → 203.0.113.20:443。如果外侧抓不到任何包,说明转发规则没生效或者路由不对;如果外侧有出包没回包,说明对端或者中间链路的问题;如果外侧有回包但内侧收不到,那就是 NAT 的反向转换没做对,回到conntrack -L去查表项。

5.3 常见故障速查表

现象优先怀疑快速验证
域名解析超时递归服务器不可达 / 上游配置错dig @<另一个DNS>对比
解析结果不对缓存未过期 / 权威配置错dig +trace看权威返回
ping 通但端口连不上服务未监听 / 防火墙拦端口nc -zv、ss -lntp
ping 不通但服务能用中间设备禁 ICMP直接测端口,不要依赖 ping
小包通大包不通PMTU 黑洞ping -M do -s二分探测
偶尔连不上、无错误码conntrack 表满查 count 与 max 比值
内网访问对外域名失败缺少 hairpin NAT检查是否配了内网侧 SNAT
长连接莫名断开NAT 表项超时对比超时值与心跳间隔

这张表我贴在工位上用了很久,大部分问题扫一眼就能对上号。

6. 踩坑记录与几条实操心得

6.1 DNS 相关的坑:别在客户端瞎折腾

最常见的错误是"解析不了就改 hosts"。hosts 确实能救急,但它是绕过了整个解析体系,一旦问题消失你就失去了排查线索,而且 hosts 条目会被遗忘,几个月后变成新的故障源。我的做法是:hosts 只用于临时验证"是不是解析的问题",验证完立刻删掉。

第二个坑是AAAA记录带来的超时。某些环境里客户端拿到了 IPv6 地址,但实际链路不通(或者 IPv6 出口没配好),于是每次连接都要先尝试 IPv6、超时后再回退到 IPv4,表现为"每次首次访问都要等几秒"。这个现象特别容易被误判成"服务端慢"。验证方法很简单:curl -6和curl -4分别试一次,谁慢一目了然。

第三个是多套解析通道不一致。系统默认 DNS、浏览器内置的加密解析、某些应用自带的解析库,可能走的是完全不同的链路,出问题时给出的结果也不一样。我一般先确认"问题只出现在某个应用还是全系统",只出现在单个应用就去看它有没有自带解析配置,别一股脑去改系统设置。

6.2 ICMP 相关的坑:不要用它下结论

ping不通就判定"网络断了"是新手最常犯的错,实际上大量网络设备对 ICMP 做了限速甚至完全静默。我见过一台核心交换机每秒只回 5 个 ICMP,超过的直接丢,这时候ping -f(快速模式)会显示大量丢包,但实际业务流量一点问题没有。

反过来,ping通也不能说明什么。ICMP 走的是网络层,跟 TCP 端口、应用服务完全是两码事。曾经有一次,ping目标地址通得很稳,但业务就是连不上,最后查到是目标服务器的 TCP 队列被打满了——ping走的是完全不同的处理路径,压力再大也能回。

还有一点:traceroute输出里的星号不要过度解读。路由器的控制平面 CPU 优先级很低,生成 ICMP 超时报文是要消耗 CPU 的,很多设备会主动限制这个行为。判断路径是否正常,看的是"后续跳数能不能继续推进",而不是"某一跳有没有星号"。

6.3 NAT 相关的坑:表项、超时和回环

NAT 这块的坑主要集中在三处。

一是表项容量。前面提过 conntrack 打满的问题,这里补充一个细节:不只是数量,表项的创建速率也会成为瓶颈。每秒新建连接数过高时,即使总量没到上限,也可能出现丢包。排查时除了看 count,还要看netstat -s | grep conntrack里的插入失败计数。

二是超时。UDP 表项的超时通常只有 30 秒左右,任何超过这个间隔才发心跳的长连接都会被 NAT 提前踢掉。表现是"每 30 秒断一次",非常规律。解决方式要么缩短心跳间隔,要么在 NAT 设备上调大 UDP 超时(但要注意内存消耗)。

三是回环。内网访问自己对外发布的域名,如果没有 hairpin NAT,会出现"外网能访问、内网访问不了"的诡异现象。这个问题的迷惑性在于,从外部测试一切正常,只有内部用户报障,很容易被当成"用户环境问题"忽略掉。配置方法在前面 4.4 已经给过了,核心就是把内网侧的源地址也做一次转换。

另外还有一个容易被忽略的点:做 DNAT 时如果只改目的地址不改端口,--to-destination后面的端口可以省略;一旦写了端口,就必须写全IP:端口的形式,只写端口号会报错。这种细节文档里往往一笔带过,实际配置时却经常卡住。

6.4 我个人总结的一条经验

这些年排查下来,最大的感受是:这三个协议的价值不在于"用它们解决问题",而在于"用它们排除可能性"。DNS 正常,说明名字层没问题;ICMP 通,说明 IP 层可达;NAT 表项正确,说明转换没出错。每一步排除掉的都是整片区域,剩下的范围自然就小了。

真正难的不是命令怎么敲,而是知道每个命令的结果能证明什么、不能证明什么。ping通只能证明 IP 层可达,不能证明端口开放;解析成功只能证明名字到地址的映射存在,不能证明那个地址上的服务活着。把每条结论的边界想清楚,排查效率会有质的提升。

如果只能记一句话,我会说:先分层定位,再纵向深入,别一上来就盯着应用日志翻。日志里写的是"我连不上",而网络层告诉你的才是"为什么连不上"。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询