后台服务的健康检查每隔几分钟就报一次超时,登上机器翻日志却干干净净——这种"看着没问题但就是不通"的故障,十有八九落在三层到四层之间的缝隙里。我这几年排查这类问题,翻来覆去用的就是三样东西: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 客户端数量是天文数字,如果每个客户端都自己从根服务器开始迭代,根服务器早就被打穿了。让少量递归服务器承担完整的查询链路并做缓存,是整个体系能撑住的关键。
一次完整的冷查询大致是这样走的:
- 客户端向配置的递归服务器发查询,置 RD(Recursion Desired)位为 1。
- 递归服务器无缓存,向根服务器询问,根服务器返回顶级域服务器的 NS 记录(这是参考,不是答案)。
- 递归服务器向顶级域服务器询问,拿到权威服务器的 NS 记录。
- 递归服务器向权威服务器询问,拿到最终的 A/AAAA 记录。
- 递归服务器把结果返回给客户端,并按 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 的组合。
| Type | Code | 含义 | 出现场景 |
|---|---|---|---|
| 0 | 0 | Echo Reply | ping的回包 |
| 3 | 0 | 网络不可达 | 路由表里没有目标网段 |
| 3 | 1 | 主机不可达 | ARP 失败或主机离线 |
| 3 | 3 | 端口不可达 | UDP 服务未监听,也是 UDP 探测的判据 |
| 3 | 4 | 需要分片但设置了 DF | PMTU 探测的关键信号 |
| 8 | 0 | Echo Request | ping的请求 |
| 11 | 0 | TTL 超时 | traceroute的核心机制 |
| 11 | 1 | 分片重组超时 | 分片报文没凑齐 |
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 ESTABLISHED | 432000 秒(5 天) | 长连接能撑住,但也容易堆积 |
| TCP SYN_SENT | 120 秒 | 握手未完成,超时短 |
| TCP TIME_WAIT | 120 秒 | 主动关闭方等待 |
| UDP | 30~180 秒 | 不同内核版本差异较大 |
| ICMP | 30 秒 | 每个 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 MASQUERADEMASQUERADE和SNAT的区别在于前者动态读取出口地址,适合出口 IP 会变的场景(比如拨号),代价是每次都要查询一次网卡地址,性能略差;出口 IP 固定的环境用SNAT --to-source更高效。
配置完别忘了开转发:
sysctl -w net.ipv4.ip_forward=1这个开关不开,所有转发规则都是摆设,而它的默认值在很多发行版上是 0。这个坑我至少踩过三次,每次都是配完规则发现不通,回头一查才发现是转发没开。
5. 三个协议在实际排查中的联动
5.1 一套通用的分层排查顺序
遇到"服务连不上",我基本固定按这个顺序推:
- 本机网络栈是否正常:
ip addr看有没有拿到地址,ip route看默认路由在不在。 - 网关是否可达:
ping网关地址。不通就查二层(网线、VLAN、ARP)。 - 名字能否解析:
dig或nslookup目标域名。解析失败先换一个递归服务器对比,判断是本地配置问题还是服务端问题。 - 目标 IP 是否可达:直接
ping解析出来的 IP。这一步要留意目标可能禁 ping,所以 ping 不通不等于不可达。 - 端口是否开放:
nc -zv或者curl -v直接打端口。这一步才真正验证服务。 - 路径是否有异常:
traceroute看在哪一跳开始延迟飙升或者丢包。 - 跨网段场景补查 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抓实际业务流量。抓下来的时序大致是:
- 一条 UDP 53 的查询出去,一条 UDP 53 的响应回来——DNS 完成。
- 如果中间路径有问题,会夹杂 ICMP Time Exceeded 或 Destination Unreachable。
- 然后是 TCP 三次握手,SYN、SYN-ACK、ACK——业务连接建立。
- 之后是 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 层可达,不能证明端口开放;解析成功只能证明名字到地址的映射存在,不能证明那个地址上的服务活着。把每条结论的边界想清楚,排查效率会有质的提升。
如果只能记一句话,我会说:先分层定位,再纵向深入,别一上来就盯着应用日志翻。日志里写的是"我连不上",而网络层告诉你的才是"为什么连不上"。