Linux 基础体系 · 第 19/85 篇。示例面向现代主流 Linux 发行版;发行版差异、权限和生产风险会明确说明。

Linux 路由、DNS 与网络诊断:ip、ss、dig、tcpdump 和抓包

所属系列:Linux 基础体系
所属模块:五、网络与性能
标签:Linux、网络、DNS、故障诊断
适用范围:现代主流 Linux 发行版

Linux 网络故障通常不是“网络不通”这么简单。一个客户端访问服务端,至少要经过名称解析、路由选择、邻居发现、链路收发、防火墙或 NAT、传输层连接建立,以及应用程序读写 Socket 等环节。不同命令观察的是不同层次:

  • ip:地址、链路、邻居、路由和策略路由;
  • ss:内核中的 Socket、监听状态和 TCP 连接状态;
  • dig:DNS 查询请求、响应和委派链;
  • tcpdump:经过指定抓包点的数据包;
  • 抓包分析:把命令输出与真实数据流按时间和方向对应起来。

如果只执行“ping 一下”或只查看某一条命令的结果,往往无法区分是名称解析失败、路由错误、端口未监听、SYN 被丢弃,还是服务端已经响应但应用处理缓慢。


一、先建立数据流模型:从域名到 Socket

以主机执行:

curl https://api.example.com:8443/v1/items

为例,简化后的路径如下:

sequenceDiagram
    participant App as 应用程序
    participant Res as DNS 解析器
    participant Kernel as Linux 内核
    participant FW as 防火墙/NAT
    participant Wire as 网络
    participant Server as 服务端

    App->>Res: 查询 api.example.com
    Res->>Kernel: 发送 DNS UDP/TCP 查询
    Kernel->>FW: 路由并过滤 DNS 数据包
    FW->>Wire: 发往 DNS 服务器
    Wire-->>Res: 返回 A/AAAA 记录
    App->>Kernel: connect(目标 IP:8443)
    Kernel->>Kernel: 查找策略和路由
    Kernel->>FW: 发送 TCP SYN
    FW->>Wire: 转发或执行 NAT
    Wire-->>Kernel: 返回 SYN-ACK
    Kernel-->>App: connect() 成功
    App->>Kernel: TLS 和 HTTP 数据
    Kernel->>FW: 分段、过滤、转发
    FW->>Wire: 发往服务端
    Server-->>Wire: 返回响应

这张图中有几个必须区分的对象:

  1. 域名是名称,不是路由目标。DNS 把名称解析为一个或多个 IP 地址。
  2. IP 地址是网络层目标,内核根据它选择出接口、下一跳和源地址。
  3. 端口标识传输层端点,例如 TCP 8443
  4. Socket是应用程序与内核之间的通信对象。监听 Socket 和已建立连接 Socket 是不同状态的对象。
  5. 数据包是网络中传输的单位。一个应用层写入的数据可能被拆成多个 TCP 段,一个 TCP 段也可能因网卡卸载特性在抓包中显示为不同形态。

因此:

  • dig 成功,只能说明某个 DNS 查询得到响应;
  • ip route get 成功,只能说明本机对某个目标存在路由选择;
  • ss 看到监听端口,只能说明某个进程在本机监听;
  • tcpdump 看到 SYN,只能说明数据包到达了抓包点;
  • 只有把这些证据放到同一条时间线中,才能定位完整故障路径。

二、ip:观察和修改 Linux 网络状态

ip 属于 iproute2 工具集。它通过 Netlink 与内核交互,主要操作以下对象:

  • link:网卡、虚拟接口、接口状态和链路属性;
  • addr:接口上的 IPv4/IPv6 地址;
  • route:路由表;
  • rule:策略路由规则;
  • neigh:ARP 或 IPv6 邻居缓存;
  • netns:网络命名空间。

常见命令的基本形式是:

ip [对象] [操作] [选择条件]

例如:

ip -br link
ip -br addr
ip route
ip neigh

-br 是便于阅读的 brief 输出选项,不改变内核状态。

2.1 接口状态不等于网络可用

查看接口:

ip -br link

可能得到:

lo               UNKNOWN        127.0.0.1/8 ::1/128
eth0             UP             ...

这里的 UP 主要表示接口被管理上启用;它不保证:

  • 网线或虚拟链路真的连通;
  • 已经配置 IP 地址;
  • 有正确路由;
  • 对端能返回数据;
  • 防火墙允许流量。

更详细的信息:

ip link show dev eth0

典型输出中:

state UP
mtu 1500
qdisc ...
  • state UP 表示接口当前链路状态为启用;
  • mtu 是最大传输单元,影响 IP 分片、TCP 分段以及路径 MTU;
  • qdisc 是排队规则,负责发送方向的数据排队和调度。

检查地址:

ip addr show dev eth0

可能看到:

inet 192.0.2.10/24 brd 192.0.2.255 scope global eth0
inet6 2001:db8:1::10/64 scope global

192.0.2.10/24 表示地址为 192.0.2.10,前缀长度为 24,即本地网络范围是 192.0.2.0/24scope global 表示这个地址可作为非本地通信地址使用;scope link 通常表示只在链路范围内有效,例如 IPv6 链路本地地址。

一个常见误解是“接口有 IP 就能访问外网”。还必须存在默认路由:

ip route

例如:

default via 192.0.2.1 dev eth0 proto dhcp src 192.0.2.10 metric 100
192.0.2.0/24 dev eth0 proto kernel scope link src 192.0.2.10

第一条是默认路由,第二条是接口地址自动产生的直连路由。

2.2 路由选择:最长前缀匹配、优先级和策略规则

对 IPv4 目标地址 d,路由表中一个前缀 p/n 能匹配它,当且仅当:

(d&Mn)=(p&Mn)(d \mathbin{\&} M_n) = (p \mathbin{\&} M_n)

其中:

  • M_n 是前缀长度为 n 的网络掩码;
  • & 是按位与;
  • 前缀长度越大,匹配范围越小,优先级通常越高。

例如有以下路由:

default via 192.0.2.1 dev eth0
10.0.0.0/8 via 192.0.2.254 dev eth0
10.20.0.0/16 via 192.0.2.253 dev eth0
10.20.30.0/24 dev eth1

访问 10.20.30.8 时,四条路由中:

  • default 匹配;
  • 10.0.0.0/8 匹配;
  • 10.20.0.0/16 匹配;
  • 10.20.30.0/24 匹配。

由于 /24/16/8/0 更具体,最终走 eth1

如果多个候选路由具有相同前缀长度,内核还会根据路由优先级、协议属性、度量值以及可能的 multipath 配置选择路径。不要仅凭“路由表里存在某条路由”判断实际路径,直接使用:

ip route get 10.20.30.8

示例:

10.20.30.8 dev eth1 src 10.20.30.2 uid 1000
    cache

这个命令回答的是“本机针对该目标实际会选择什么”,包括:

  • 出接口;
  • 下一跳;
  • 源地址;
  • 某些情况下的路由表和标记信息。

检查带源地址或端口的选择时,可以使用:

ip route get 203.0.113.20 from 192.0.2.10
ip route get 203.0.113.20 ipproto tcp dport 8443

不同发行版或 iproute2 版本支持的查询属性可能略有差异;遇到不识别参数时,应以本机 ip route get help 为准。

策略路由先于普通路由表

Linux 不一定只查 main 路由表。策略路由由 ip rule 控制:

ip rule show

典型结果:

0:      from all lookup local
32764:  from 192.0.2.0/24 lookup 100
32766:  from all lookup main
32767:  from all lookup default

内核大致按优先级从小到大处理规则:

  1. local 表处理本机地址、广播和本地路由;
  2. 源地址匹配 192.0.2.0/24 时查询表 100
  3. 其他流量查询 main
  4. 最后查询 default 表。

因此,ip route 只显示默认的 main 表时,不能代表所有流量的最终路径。应同时检查:

ip route show table all
ip rule show

策略路由常用于多出口、VPN、容器、VRF 和按源地址分流。错误的策略规则可能导致:

  • 请求从出口 A 发出,响应却按出口 B 返回;
  • 包从正确接口发出,但源地址不属于该接口;
  • 本地服务访问自身地址时行为与访问外部地址不同。

2.3 下一跳和邻居缓存:有路由不等于能发出帧

IP 路由决定“从哪个接口、经哪个下一跳发送”,但以太网还需要下一跳的 MAC 地址。IPv4 使用 ARP,IPv6 使用 Neighbor Discovery;Linux 统一通过邻居表观察:

ip neigh show dev eth0

可能得到:

192.0.2.1 lladdr 02:00:00:00:00:01 REACHABLE
192.0.2.20 FAILED

常见状态包括:

  • REACHABLE:近期确认可达;
  • STALE:信息可能过期,尚未证明不可达;
  • DELAYPROBE:正在重新确认;
  • FAILED:邻居解析失败;
  • INCOMPLETE:请求已发出但尚未收到响应。

对于同一二层网络的目标,主机通常需要解析目标本身的 MAC;对于非直连目标,只需解析默认网关或配置的下一跳 MAC。比如访问公网地址时,主机不会在局域网内寻找公网服务器的 MAC,而是寻找网关的 MAC。

可以用以下命令观察 ARP 或邻居发现:

sudo tcpdump -ni eth0 'arp or icmp6'

清理邻居缓存会改变系统状态,应谨慎执行:

sudo ip neigh flush dev eth0

这可能导致短暂的 ARP/ND 重解析,不应在不了解影响范围时对生产主机批量执行。

2.4 修改 ip 状态的风险

查看状态通常是低风险操作;以下命令会立即改变网络:

sudo ip addr add 192.0.2.11/24 dev eth0
sudo ip addr del 192.0.2.11/24 dev eth0
sudo ip link set dev eth0 down
sudo ip route replace default via 192.0.2.1 dev eth0

ip route replace 可能立即改变所有新连接的出口;删除默认路由可能使远程 SSH 断开。临时修改也可能在 NetworkManager、systemd-networkd 或发行版网络管理服务下一次重载时被覆盖。

持久配置应通过实际使用的网络管理系统完成,而不是只执行一次 ip 命令。诊断远程机器时,修改路由前应准备带外控制台或自动回滚方案。


三、ss:观察 Socket 和连接状态

ss 读取内核 Socket 信息,比传统 netstat 更适合现代 Linux。它观察的是本机传输层端点,而不是直接观察线路上的每个包。

最常用的监听检查:

sudo ss -lntup

选项含义:

  • -l:只显示监听 Socket;
  • -n:不把地址和端口反向解析为名称;
  • -t:TCP;
  • -u:UDP;
  • -p:显示关联进程,通常需要 root 权限。

示例:

LISTEN 0 4096 0.0.0.0:8443 0.0.0.0:* users:(("api-server",pid=2184,fd=9))
LISTEN 0 128  127.0.0.1:8080 0.0.0.0:* users:(("proxy",pid=1301,fd=6))

第一行表示进程在所有 IPv4 地址的 8443 端口监听;第二行只监听回环地址,因此远程主机不能直接连接 127.0.0.1:8080

IPv6 监听需要特别注意:

LISTEN 0 4096 [::]:8443 [::]:*

这是否同时接受 IPv4 连接,取决于 IPv6 Socket 的 IPV6_V6ONLY 设置及发行版默认值,不能仅凭 [::]:8443 的文本做绝对判断。应分别测试 IPv4 和 IPv6:

curl -4 --connect-timeout 3 https://example.com:8443/
curl -6 --connect-timeout 3 https://example.com:8443/

3.1 TCP 状态是连接生命周期的证据

TCP 连接建立过程可以简化为:

客户端                         服务端
  SYN ------------------------>
      <------------------- SYN-ACK
  ACK ------------------------>

对应状态变化通常是:

客户端:CLOSED -> SYN-SENT -> ESTABLISHED
服务端:LISTEN -> SYN-RECV -> ESTABLISHED

查看连接:

sudo ss -ntp

示例:

ESTAB 0 0 192.0.2.10:42136 203.0.113.20:8443 users:(("curl",pid=3012,fd=5))
SYN-SENT 0 1 192.0.2.10:42138 203.0.113.20:8443 users:(("curl",pid=3020,fd=5))

SYN-SENT 说明本机已发起连接,但尚未收到有效的 SYN-ACK。可能原因包括:

  • 路由走错;
  • 出口防火墙丢弃;
  • 对端或中间设备丢弃 SYN;
  • 返回路径错误;
  • 对端没有服务且显式返回 RST 的情况除外。

如果目标端口没有监听,常见表现是对端返回 TCP RST,客户端通常很快得到 Connection refused;如果包被静默丢弃,则更可能表现为超时。二者不能混为一谈。

常见 TCP 状态的含义:

  • LISTEN:等待入站连接;
  • SYN-SENT:已发起连接,等待响应;
  • SYN-RECV:收到 SYN,已发送 SYN-ACK,等待最终 ACK;
  • ESTAB:连接已建立;
  • FIN-WAIT-1/2:本端关闭方向后等待对端确认或关闭;
  • CLOSE-WAIT:对端已关闭,本端应用尚未关闭 Socket;
  • TIME-WAIT:主动关闭方保留连接四元组一段时间,防止旧报文干扰新连接。

CLOSE-WAIT 长期堆积通常指向应用没有及时关闭已收到 EOF 的连接;TIME-WAIT 较多不自动等于故障,它是 TCP 正常生命周期的一部分。是否有问题要结合端口耗尽、连接速率和应用行为判断。

3.2 队列、缓冲区和计数器

ss -lnt 中的 Recv-QSend-Q 在监听 Socket 上通常可理解为:

  • Recv-Q:当前等待应用 accept() 的已完成连接队列;
  • Send-Q:监听队列上限或相关 backlog 值,具体显示受内核和 Socket 类型影响。

如果监听进程处理不过来,Recv-Q 长期接近上限,可能出现新连接延迟、丢弃或重传。应进一步检查应用线程、CPU、文件描述符和内核 TCP 统计,而不是简单扩大 backlog。

已建立连接中的队列则表示接收或发送方向尚未被应用消费或确认的数据,不能直接等同于“网络带宽不足”。例如:

ss -ntmi dst 203.0.113.20

-i 可以显示更详细的 TCP 信息,例如拥塞控制、RTT、重传和窗口相关数据;字段会随内核版本变化,应以本机输出为准。

按进程、端口筛选:

sudo ss -lntp 'sport = :8443'
sudo ss -ntp 'dst 203.0.113.20'

过滤表达式属于 ss 的语法,不要把它与 tcpdump 的 BPF 语法混用。

UDP 也有 Socket,但没有 TCP 的连接握手和完整状态机:

sudo ss -lunp

看到 UDP 端口监听,只能证明有进程创建了接收 Socket;请求是否被处理、响应是否返回,仍需要 tcpdump 或应用日志确认。


四、dig:把 DNS 查询拆成可验证的步骤

DNS 是分布式名称系统。客户端通常先访问配置的递归解析器,由递归解析器代表客户端查询权威服务器并缓存结果。dig 是 DNS 查询诊断工具,它可以直接显示请求类型、响应状态、资源记录、标志位和查询耗时。

最基本的查询:

dig example.com

典型响应结构:

;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 12345
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

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

;; ANSWER SECTION:
example.com.   300     IN      A       203.0.113.20

;; Query time: 12 msec
;; SERVER: 192.0.2.53#53(192.0.2.53)

关键字段:

  • status: NOERROR:DNS 协议层面查询成功,不表示一定有答案;
  • NXDOMAIN:权威上表示该名称不存在;
  • SERVFAIL:服务器无法完成查询,可能是上游失败、DNSSEC 验证失败或配置问题;
  • REFUSED:服务器拒绝该查询;
  • ANSWER:回答区记录数;
  • AUTHORITY:授权区记录数;
  • ADDITIONAL:附加记录数;
  • rd:客户端请求递归;
  • ra:服务器声明支持递归;
  • aa:响应来自该名称所在区域的权威服务器。

NOERRORANSWER: 0 并不等于有 A 记录。可能是合法的空答案,例如查询一个只有 AAAA 记录的名称,或者查询存在但没有该类型记录的名称。

4.1 指定记录类型和服务器

dig example.com A
dig example.com AAAA
dig example.com MX
dig example.com TXT
dig -x 203.0.113.20

-x 执行反向解析,即查询 in-addr.arpaip6.arpa 名称。正向解析和反向解析是两套不同的 DNS 委派,反向解析不存在不代表正向解析有问题。

指定 DNS 服务器:

dig @192.0.2.53 example.com A
dig @1.1.1.1 example.com A

这一步可以区分:

  • 本机配置的解析器故障;
  • 特定递归解析器故障;
  • 网络无法到达 DNS 服务器;
  • 不同解析器因缓存、视图或地理策略返回不同地址。

查看 /etc/resolv.conf

cat /etc/resolv.conf

但它在不同发行版中可能是普通文件,也可能是指向 systemd-resolved、NetworkManager 或其他本地 stub 的符号链接。例如其中的 nameserver 127.0.0.53 可能表示本机监听的 DNS stub,而不是远端 DNS 服务器。此时可以用:

resolvectl status
resolvectl query example.com

resolvectl 只在使用 systemd-resolved 的系统上可用;不要假定所有发行版都具有相同解析链路。

4.2 DNS 查询的传输路径

传统 DNS 查询通常使用 UDP 53 端口,但以下情况可能使用 TCP:

  • UDP 响应被截断,响应头带 TC
  • DNSSEC 或其他记录导致响应较大;
  • 区域传送等场景本身要求 TCP;
  • 客户端或服务器显式配置 TCP。

强制测试:

dig +tcp @192.0.2.53 example.com A
dig +time=2 +tries=1 @192.0.2.53 example.com A

观察 DNS 包:

sudo tcpdump -ni any 'port 53'

更精确地限制某个服务器:

sudo tcpdump -ni eth0 'host 192.0.2.53 and (udp port 53 or tcp port 53)'

注意:加密 DNS(DoT、DoH)不会以普通 UDP/TCP 53 的明文形式出现。DoT 通常使用 TCP 853,DoH 通常是 HTTPS 流量;此时 dig 不能直接等价观察应用实际使用的解析协议。

4.3 从根开始追踪委派

dig +trace example.com

+tracedig 从根服务器开始,逐级查询:

根区 -> .com 顶级域 -> example.com 权威服务器

它有助于区分:

  • 本地递归解析器缓存或策略问题;
  • 顶级域委派问题;
  • 权威服务器不可达;
  • 权威区域本身缺少记录。

+trace 不是“绕过所有问题的真相模式”。它需要本机能够访问根、顶级域和权威服务器,且可能受到防火墙、网络隔离、DNSSEC、IPv6 或 EDNS 兼容性影响。企业内网的 split-horizon DNS 还可能使公网权威结果与内网客户端应看到的结果不同。

4.4 CNAME、A/AAAA 和应用选择

DNS 结果可能不是直接的 A 记录:

www.example.com. 300 IN CNAME edge.example.net.
edge.example.net. 60 IN A 203.0.113.20

应用需要继续解析 CNAME 的目标。一个名称同时拥有 A 和 AAAA 时,客户端可能尝试 IPv6,也可能根据地址排序、连接竞速和应用策略选择 IPv4;不能仅凭 dig A 成功就断言 HTTPS 一定使用 IPv4。

分别测试应用使用的地址族:

curl -v -4 https://example.com/
curl -v -6 https://example.com/

五、tcpdump:在数据包层观察事实

tcpdump 在指定接口上捕获数据包,并使用 BPF(Berkeley Packet Filter)过滤表达式筛选。它观察的是抓包点“看到”的包,而不是抽象的连接结果。

先列出接口:

sudo tcpdump -D

常见抓包命令:

sudo tcpdump -ni eth0

选项含义:

  • -i eth0:监听 eth0
  • -n:不解析地址名称;
  • -nn:同时不解析端口服务名称;
  • -v-vv:增加协议细节;
  • -c 20:捕获 20 个包后退出;
  • -s 0:尽量捕获完整包,而非只捕获包头;
  • -w file.pcap:写入 pcap 文件;
  • -r file.pcap:读取离线抓包文件。

抓取某个 TCP 服务的握手:

sudo tcpdump -ni eth0 -c 20 'host 203.0.113.20 and tcp port 8443'

可能输出:

192.0.2.10.42136 > 203.0.113.20.8443: Flags [S], seq 1000, win 64240, options [...]
203.0.113.20.8443 > 192.0.2.10.42136: Flags [S.], seq 7000, ack 1001, win 65160, options [...]
192.0.2.10.42136 > 203.0.113.20.8443: Flags [.], ack 7001, win 502, length 0

解释:

  • [S]:SYN,客户端发起连接;
  • [S.]:SYN 和 ACK,服务端接受并确认;
  • [.]:ACK;
  • seq:序列号;
  • ack:下一个期望收到的序列号;
  • win:接收窗口;
  • length:该包承载的数据长度。

现代系统通常启用 TCP 时间戳、窗口扩大、SACK 等选项。抓包中的绝对序列号可能因 TCP 随机化而不同,不应把示例中的数值当作固定协议常量。

5.1 BPF 过滤表达式

常用过滤器:

# 某个主机
host 203.0.113.20

# 源或目的地址
src host 192.0.2.10
dst host 203.0.113.20

# TCP 端口
tcp port 8443

# DNS
udp port 53 or tcp port 53

# TCP SYN,但排除 SYN-ACK
'tcp[tcpflags] & (tcp-syn|tcp-ack) == tcp-syn'

# ICMP
icmp

组合时使用括号:

sudo tcpdump -ni eth0 \
  'host 203.0.113.20 and (tcp port 8443 or icmp)'

Shell 会解释括号和 !,所以整个表达式应放在单引号中。BPF 过滤越精确,输出越容易对应某一条故障路径,也能减少抓包开销。

5.2 保存和离线分析

生产环境中更适合先保存有限范围的抓包:

sudo tcpdump -ni eth0 -s 0 -c 500 \
  -w /tmp/api-8443.pcap \
  'host 203.0.113.20 and tcp port 8443'

然后离线读取:

tcpdump -nn -tttt -r /tmp/api-8443.pcap

-tttt 输出可读时间。也可以用 Wireshark、tshark 等工具按 TCP 流重组、查看 DNS 层次和 TLS 握手。

抓包文件可能包含:

  • 用户访问的域名;
  • Cookie、Token 或其他应用数据;
  • 内网地址和拓扑;
  • 未加密协议中的密码或业务内容。

因此文件权限、保存时间和传输方式都必须按敏感数据处理。-w 写入文件时,捕获进程仍需要足够权限;读取抓包文件通常不需要 root。

5.3 抓包位置决定你能看到什么

-i any 在 Linux 上很方便:

sudo tcpdump -ni any 'host 203.0.113.20'

但它不是一个真实网卡,链路层头部、方向和重复显示方式可能与物理接口不同。精确判断二层帧、VLAN、接口收发方向时,应在具体接口抓包。

在主机上抓包,常见观察点包括:

  1. 应用写入内核后的发送路径;
  2. 物理接口发送前后;
  3. 接收进入协议栈后的路径;
  4. 容器 veth、bridge、物理接口;
  5. 网络命名空间内部接口。

容器场景必须进入正确的网络命名空间:

sudo nsenter -t <容器进程PID> -n ip addr
sudo nsenter -t <容器进程PID> -n tcpdump -ni any 'port 8080'

其中 <容器进程PID> 需要替换为该容器在宿主机上的进程 PID。只在宿主机物理接口抓包,可能看不到容器内部发送前的地址,或无法区分多个 veth 流。


六、为什么“抓到了包”不一定等于“线上就是这个包”

网卡和内核常启用硬件或软件卸载:

  • TSO/GSO:发送方向把大段数据交给网卡或后续层再分段;
  • GRO/LRO:接收方向合并多个包;
  • checksum offload:校验和在更晚阶段计算。

因此,主机上的 tcpdump 可能显示:

  • 大于物理 MTU 的 TCP 段;
  • 校验和看起来错误;
  • 包数量与线上交换机看到的数量不同。

这可能是抓包点和卸载时机造成的,不一定代表真实网络发送了坏包。需要在物理交换机、镜像端口、旁路探针或关闭卸载后再次验证。关闭卸载会改变性能和 CPU 消耗,生产环境不应随意执行:

sudo ethtool -k eth0

测试环境中可以临时调整,恢复前记录原状态:

sudo ethtool -K eth0 tso off gro off gso off

这类设置可能被驱动拒绝,也可能在重启后恢复;不同网卡和驱动行为存在差异。

另一个边界是过滤与硬件卸载:抓包在协议栈某个位置发生,防火墙、驱动和硬件可能在不同阶段处理包。因此,tcpdump 看到包不意味着它一定已经通过所有 Netfilter hook,也不意味着包一定离开了物理网卡。


七、用时间线诊断一条 TCP 连接

假设命令:

curl -v --connect-timeout 5 http://203.0.113.20:8443/

同时执行:

sudo ss -ntp
sudo tcpdump -ni eth0 -tttt \
  'host 203.0.113.20 and tcp port 8443'

根据抓包结果可以逐步判断。

情况一:没有任何 SYN

如果 curl 等待超时,但接口上完全没有目标地址的 SYN,优先检查:

ip route get 203.0.113.20
ip rule show
ip route show table all

可能原因:

  • 应用实际解析出的地址不是你过滤的地址;
  • 走了其他网络命名空间;
  • 路由失败;
  • 本机策略路由或防火墙在更早阶段阻止;
  • 抓错接口;
  • tcpdump 过滤器写错。

情况二:看到 SYN,但没有 SYN-ACK

192.0.2.10:42136 > 203.0.113.20:8443 [S]

说明发送方向至少到达了当前抓包点,但不能证明对端收到。继续在服务端或中间设备抓包,检查:

  • 服务端是否收到 SYN;
  • 服务端是否发出 SYN-ACK;
  • 返回路径是否经过正确接口;
  • 中间防火墙是否丢弃;
  • NAT 是否改写了地址和端口。

情况三:看到 SYN-ACK,但客户端没有完成 ACK

客户端 -> 服务端 [S]
服务端 -> 客户端 [S.]
客户端没有后续 [.]

重点检查:

  • 返回包是否真的到达客户端;
  • 客户端是否因策略路由、反向路径检查或防火墙丢弃;
  • SYN-ACK 是否发到错误地址;
  • 是否存在地址冲突或 NAT 映射错误。

情况四:三次握手成功,但应用超时

此时 ss 可能显示 ESTAB,tcpdump 也显示握手完成。问题已经越过“端口是否可达”这一层,转向:

  • TLS 握手;
  • HTTP 请求是否发出;
  • 服务端是否读取请求;
  • 应用线程、连接池或后端依赖是否阻塞;
  • 服务端响应是否在发送队列中;
  • MTU、PMTUD 或中间设备是否丢弃后续大包。

可以扩大过滤范围:

sudo tcpdump -ni eth0 -s 0 -w /tmp/http.pcap \
  'host 203.0.113.20 and tcp port 8443'

并结合:

ss -ntmi dst 203.0.113.20
curl -v --trace-time http://203.0.113.20:8443/

curl -v 显示的是应用层客户端视角,tcpdump 显示的是数据包视角,两者时间戳和方向可以相互校验。


八、DNS 故障的完整排查路径

假设应用访问:

curl https://api.example.com/

先确认应用到底使用了哪个地址:

dig api.example.com A
dig api.example.com AAAA

再指定本机配置的解析器:

dig @192.0.2.53 api.example.com A

若失败,检查 DNS 查询包:

sudo tcpdump -ni any \
  'host 192.0.2.53 and (udp port 53 or tcp port 53)'

可以按以下逻辑解释:

  1. 没有查询包
    应用可能使用本地缓存、其他解析库、代理,或者查询发生在另一个命名空间。

  2. 有请求,没有响应
    检查到 DNS 服务器的路由、UDP/TCP 53 防火墙、返回路径和服务器状态。

  3. 有响应但状态为 SERVFAIL
    这是 DNS 服务器返回的协议结果,应查看递归服务器日志、上游连通性和 DNSSEC 验证,而不是继续测试 TCP 8443。

  4. 有多个 A/AAAA 记录
    应用可能选择某个不可达地址。分别对每个地址执行:

    ip route get <IP>
    curl -v --connect-timeout 3 --resolve api.example.com:443:<IP> https://api.example.com/
    

--resolve 只替换指定主机名和端口的地址,仍保留正确的 Host/SNI,适合验证某个具体地址;它不是修改系统 DNS 配置。

还要区分 DNS 缓存和权威数据。dig 默认查询递归解析器,返回的 TTL 是剩余缓存时间。不同时间查询结果不同,可能是正常的 TTL 过期、负载均衡或地域 DNS 策略,不必立即归因于“DNS 随机故障”。


九、防火墙、NAT 与抓包证据如何对应

Linux 防火墙通常由 Netfilter 提供内核钩子,规则管理工具可能是 nftables、iptables 兼容层、firewalld 或发行版集成方案。查看当前规则时,应先确认实际后端:

sudo nft list ruleset
sudo iptables -S

两者输出不一定代表两套独立规则;某些系统的 iptables 命令实际使用 nftables 兼容后端。不要在不了解管理边界时直接混用规则工具。

NAT 会改变数据包中的地址或端口。例如客户端:

192.0.2.10:42136 -> 203.0.113.20:8443

经过源地址转换后,服务端可能看到:

198.51.100.5:51000 -> 203.0.113.20:8443

这会影响抓包解释:

  • 在客户端接口抓包,看到转换前的源地址;
  • 在 NAT 网关外侧抓包,看到转换后的源地址;
  • ss 在客户端只显示本地 Socket 的原始四元组;
  • 服务端 ss 显示的是服务端看到的对端地址。

因此,不能拿客户端 ss 的四元组与服务端抓包结果直接逐字段比较而不考虑 NAT。

防火墙的“默认拒绝”也可能有两种表现:

  • 显式返回 RST 或 ICMP,客户端快速失败;
  • 静默丢弃,客户端等待重传后超时。

要判断是哪一种,必须观察返回包,而不能只看应用错误字符串。


十、路由、端口和 DNS 的反例

反例一:ping 不通,但 TCP 正常

ping 使用 ICMP,可能被防火墙禁止;TCP 8443 可以被允许。因此:

ping 203.0.113.20
curl --connect-timeout 3 http://203.0.113.20:8443/

结果不同并不矛盾。ICMP 失败只能证明 ICMP 路径或策略存在问题,不能直接推出 TCP 失败。

反例二:端口监听,但远程无法连接

服务监听:

LISTEN ... 127.0.0.1:8443

远程客户端访问服务器的 203.0.113.20:8443 仍会失败,因为进程只绑定回环地址。即使改为 0.0.0.0:8443,仍可能被主机防火墙、云安全组或上游 ACL 拦截。

反例三:DNS 成功,但应用失败

dig api.example.com A

得到地址只说明 DNS 过程有答案。目标 IP 的路由、端口、TLS 证书、SNI、应用认证都可能继续失败。

反例四:ss 显示 ESTAB,但业务无响应

TCP 只保证字节流传输和可靠性,不保证应用会及时读取或回复。服务端进程可能死锁、等待数据库、耗尽线程,或者连接已经建立但 HTTP 请求格式不符合应用协议。

反例五:抓包看见重传,不一定是应用发送慢

TCP 重传可能来自:

  • 网络丢包;
  • 接收方确认延迟或窗口为零;
  • 中间设备丢包;
  • 抓包点漏包;
  • GRO/TSO 使本地观察与线上包形态不同。

需要同时比较双向抓包、TCP 时间戳、ACK、窗口和接口统计:

ip -s link show dev eth0
ss -s

十一、一个可重复的诊断流程

下面的顺序从低风险、信息密度高的观察开始,逐步进入数据包级别。

第一步:确认命名空间和接口

ip -br link
ip -br addr

确认目标应用是否运行在容器或其他网络命名空间中。宿主机看到的接口不一定是应用实际使用的接口。

第二步:确认路由和源地址

ip route get 203.0.113.20
ip rule show
ip route show table all

不要只执行 ip route,因为策略路由可能改变结果。

第三步:确认邻居和链路统计

ip neigh
ip -s link show dev eth0

如果下一跳是 FAILED,先解决 ARP/ND、VLAN、二层隔离或接口问题;继续分析 TCP 没有意义。

第四步:确认 DNS 的每个环节

dig api.example.com A
dig api.example.com AAAA
dig @<DNS服务器> api.example.com A

记录:

  • 使用的 DNS 服务器;
  • 返回状态;
  • A/AAAA 是否都有;
  • TTL;
  • 是否存在 CNAME;
  • 查询是否走 UDP 或 TCP。

第五步:确认本机 Socket

sudo ss -lntup
sudo ss -ntp dst 203.0.113.20

确认监听地址、端口、进程以及连接状态。

第六步:捕获最小范围的数据

sudo tcpdump -ni eth0 -s 0 -c 100 \
  'host 203.0.113.20 and (tcp port 8443 or icmp)'

先捕获有限数量,避免无限写入磁盘和泄露大量业务数据。

第七步:按握手、传输、应用阶段解释

  • 无 SYN:检查应用目标、命名空间、路由和过滤器;
  • 有 SYN 无 SYN-ACK:检查对端、返回路径、防火墙和 NAT;
  • 握手成功无应用响应:检查 TLS、HTTP 和服务端处理;
  • 有重传或零窗口:结合两端抓包和 Socket 指标判断;
  • DNS 包正常但应用连接失败:将 DNS 地址带入 ip route get 和 TCP 测试。

十二、权限、版本和生产风险

ip routessdig 的读取操作通常可以由普通用户执行,但以下信息或操作常需要 root 或相应 capability:

  • ss -p 查看其他用户进程;
  • tcpdump 打开抓包接口;
  • 修改接口地址、路由、邻居和防火墙;
  • 进入其他进程的网络命名空间。

发行版差异主要集中在:

  • DNS 管理:systemd-resolved、NetworkManager、传统 resolv.conf 或本地代理;
  • 防火墙后端:nftables、iptables 兼容层、firewalld;
  • 网卡命名:eth0ens160enp1s0 等;
  • 内核、iproute2、libpcap 版本导致输出字段或可用选项不同。

生产环境中的主要风险不是读取,而是改变状态和收集敏感数据:

  • 修改默认路由可能切断 SSH;
  • flush 邻居可能造成短暂通信中断;
  • 关闭网卡卸载可能增加 CPU 消耗;
  • 无过滤抓包可能占用磁盘并泄露凭据;
  • dig +trace 可能产生大量外部 DNS 查询;
  • 直接修改 /etc/resolv.conf 可能被网络管理服务覆盖;
  • 修改防火墙规则可能影响所有业务,而不仅是当前故障连接。

诊断的核心不是“多执行几个命令”,而是让每条证据回答一个明确问题:目标是什么、内核选择哪条路径、邻居是否可解析、Socket 是否存在、包是否发出、对端是否响应、应用是否继续处理。沿着这条数据流建立证据链,ipssdigtcpdump 才能从孤立工具变成一套完整的 Linux 网络诊断方法。


系列导航与关联阅读

官方资料

本文依据 Linux 内核、systemd 与主流发行版官方文档重新梳理;正文与实验由 WR BLOG 编写。