网络工具与分析方法
#网络 · #tcpdump · #Wireshark · #netstat · #排查 · #工具
网络问题排查不是靠猜,而是靠工具链的熟练运用。本文覆盖从抓包、连接诊断、性能测量到系统调用的完整工具矩阵,以及实战排查方法论。
1. tcpdump — 抓包分析
1.1 基础语法
bash
tcpdump [选项] [过滤表达式]
# 最常用选项
tcpdump -i eth0 # 指定网卡
tcpdump -nn # 不解析主机名和端口名(速度快)
tcpdump -v/-vv/-vvv # 详细程度
tcpdump -w file.pcap # 写入文件
tcpdump -r file.pcap # 读取文件
tcpdump -c 100 # 抓 100 个包后停止
tcpdump -s 0 # 抓完整包(默认只抓 262144 字节)
tcpdump -A # ASCII 显示包内容
tcpdump -X # HEX + ASCII 显示
tcpdump -tttt # 显示完整时间戳(微秒)1.2 BPF 过滤器(Berkeley Packet Filter)
bash
# === 主机过滤 ===
tcpdump host 10.0.0.1
tcpdump src host 10.0.0.1
tcpdump dst host 10.0.0.1
# === 端口过滤 ===
tcpdump port 80
tcpdump portrange 8000-9000
tcpdump src port 443
# === 协议过滤 ===
tcpdump tcp
tcpdump udp
tcpdump icmp
tcpdump 'ip proto 6' # 同 tcpdump tcp
# === 组合逻辑 ===
tcpdump 'host 10.0.0.1 and (port 80 or port 443)'
tcpdump 'src 10.0.0.0/24 and not port 22'
# === TCP 标志位过滤 ===
tcpdump 'tcp[tcpflags] & (tcp-syn) != 0' # 所有 SYN 包
tcpdump 'tcp[tcpflags] == tcp-syn' # 仅 SYN (不含 ACK)
tcpdump 'tcp[tcpflags] & (tcp-rst) != 0' # RST 包
tcpdump 'tcp[tcpflags] & (tcp-syn|tcp-fin) != 0' # SYN 或 FIN1.3 输出格式逐字段解读
14:23:01.123456 IP 192.168.1.5.54321 > 10.0.0.1.80: Flags [S], seq 123456789, win 65535, options [mss 1460,sackOK,TS val 123456 ecr 0,nop,wscale 7], length 0
解读:
14:23:01.123456 → 时间戳(微秒)
IP → 网络层协议
192.168.1.5.54321 → 源IP.源端口
> → 方向
10.0.0.1.80 → 目的IP.目的端口
Flags [S] → TCP 标志位: S=SYN, .=ACK, F=FIN, R=RST, P=PSH
seq 123456789 → 序列号
win 65535 → 接收窗口大小
options [...] → TCP 选项
mss 1460 → 最大分段大小
sackOK → 支持选择性确认
TS val 123456 → 时间戳值
wscale 7 → 窗口缩放因子 (×128)
length 0 → TCP 数据长度(SYN 包无数据)1.4 实战场景
bash
# 场景1: 抓取 HTTP 请求和响应
tcpdump -i eth0 -A -s 0 'tcp port 80 and (((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0)'
# 场景2: 监控 DNS 查询
tcpdump -i eth0 -nn udp port 53
# 场景3: 抓取完整 TCP 连接(三次握手到四次挥手)
tcpdump -i eth0 -w conn.pcap 'host 10.0.0.1 and port 8080'
# 用 Wireshark 打开 conn.pcap
# 场景4: 按包大小过滤(大包通常有数据)
tcpdump -i eth0 'greater 100'
# 场景5: 限制每个包捕获的字节数(保护隐私,只抓头部)
tcpdump -i eth0 -s 96 # 只抓前 96 字节(足够 TCP/IP 头)2. Wireshark — 图形化分析
2.1 常用显示过滤器
# 协议过滤
tcp / udp / http / dns / tls / icmp / arp
# IP 过滤
ip.addr == 10.0.0.1
ip.src == 192.168.1.0/24
!(ip.dst == 10.0.0.1)
# 端口过滤
tcp.port == 443
tcp.srcport == 8080
tcp.port in {80 443 8080}
# TCP 分析标志
tcp.flags.syn == 1 && tcp.flags.ack == 0 # 纯 SYN
tcp.flags.reset == 1 # RST
tcp.analysis.retransmission # 重传
tcp.analysis.duplicate_ack # 重复 ACK
tcp.analysis.zero_window # 零窗口
tcp.analysis.fast_retransmission # 快速重传
# HTTP
http.request.method == "POST"
http.response.code >= 400
http.host contains "example"
# TLS
tls.handshake.type == 1 # ClientHello
tls.handshake.type == 2 # ServerHello
tls.handshake.extensions_server_name # SNI2.2 关键功能
| 功能 | 路径 | 用途 |
|---|---|---|
| Follow TCP Stream | 右键 → Follow → TCP Stream | 看完整 TCP 对话(HTTP 请求/响应) |
| Follow TLS Stream | 右键 → Follow → TLS Stream | 解密后的 TLS 数据(需密钥) |
| Statistics → IO Graph | 菜单 | 流量趋势图 |
| Statistics → Flow Graph | 菜单 | TCP 流可视化 |
| Statistics → Protocol Hierarchy | 菜单 | 协议占比统计 |
| Expert Info | 底部状态栏 | 自动检测异常(重传/乱序/零窗口) |
| Time Column | 右键 → Time Display Format | 显示距上一个包的时间差 |
2.3 解密 TLS 流量
bash
# 方法1: 设置环境变量 (Chrome/Firefox)
export SSLKEYLOGFILE=/tmp/sslkeys.log
# Wireshark → Preferences → TLS → (Pre)-Master-Secret log filename
# 方法2: 使用 RSA 私钥 (仅 RSA 密钥交换, TLS 1.2 以下)
# Wireshark → Preferences → RSA Keys → 加载 .key 文件
# 方法3: 抓取前配置代理 (mitmproxy)
mitmproxy --mode transparent3. ss / netstat — 连接状态诊断
3.1 ss 命令(替代 netstat,更快)
bash
# 查看所有 TCP 连接
ss -tanp
# 按状态过滤
ss -tan state established # 已建立连接
ss -tan state time-wait # TIME_WAIT 连接数
ss -tan state close-wait # CLOSE_WAIT 排查
ss -tan state listen # 监听端口
ss -tan state syn-recv # 半连接队列(可能被 SYN Flood)
ss -tan state syn-sent # 正在建立连接
# 统计各状态数量
ss -tan | awk '{print $1}' | sort | uniq -c | sort -rn
# 查看进程打开的文件描述符(连接)
ss -tanp | grep <pid>
# 查看 listen 队列溢出
ss -lnt | grep -E 'Recv-Q|LISTEN'
# Recv-Q > 0 → backlog 不够,新连接排队中!
# 按目标 IP 分组(找热点)
ss -tan | awk '{print $5}' | awk -F: '{print $1}' | sort | uniq -c | sort -rn3.2 连接状态的含义解读
mermaid
flowchart LR
subgraph Normal["正常状态"]
EST["ESTABLISHED ✅"]
LISTEN["LISTEN ✅"]
end
subgraph Warning["需关注"]
TW["TIME_WAIT ⚠️ 过多→短连接"]
SR["SYN_RECV ⚠️ 过多→SYN Flood"]
end
subgraph Danger["危险信号"]
CW["CLOSE_WAIT ❌ 100%应用bug"]
LA["LAST_ACK ❌ 对端未响应"]
FS["FIN_WAIT2 ❌ 对端不发FIN"]
end3.3 netstat 补充用法
bash
# 统计各状态 (netstat 兼容写法)
netstat -an | awk '/^tcp/ {print $6}' | sort | uniq -c | sort -rn
# 查看网络接口统计(错误、丢包)
netstat -i
netstat -s # 各协议统计(重传、超时、reset等)
# 查看路由表
netstat -rn3.4 TCP 状态 + fd 耗尽排查 Runbook
当服务出现 too many open files、502/504、连接超时、连接被 reset 时,优先判断是不是连接状态异常或 fd 耗尽。
第一步:看全局状态分布
bash
# TCP 状态计数
ss -tan | awk 'NR>1 {print $1}' | sort | uniq -c | sort -rn
# socket 总览
ss -s
# 全机 socket 使用情况
cat /proc/net/sockstat第二步:看进程 fd 是否逼近上限
bash
pid=<pid>
# 当前打开 fd 数
ls /proc/$pid/fd | wc -l
# 进程 fd 上限
cat /proc/$pid/limits | grep 'open files'
# fd 类型分布
ls -l /proc/$pid/fd | awk '{print $NF}' | sed 's/\[.*\]/[socket]/' | sort | uniq -c | sort -rn | head第三步:定位异常连接属于哪条链路
bash
# CLOSE_WAIT 连接及进程
ss -tanp state close-wait
# 按本地地址/对端地址聚合
ss -tanp state close-wait | awk 'NR>1 {print $4, $5}' | sort | uniq -c | sort -rn | head -20
# TIME_WAIT 按对端聚合,判断是否连接固定 upstream 太频繁
ss -tan state time-wait | awk 'NR>1 {print $5}' | sort | uniq -c | sort -rn | head -20第四步:结合链路判断根因
| 状态 | 出现位置 | 高概率根因 | 下一步 |
|---|---|---|---|
大量 CLOSE_WAIT | 服务本地端口 | client 已关闭,服务未 close | 查 handler 是否退出、响应体是否关闭 |
大量 CLOSE_WAIT | 本地临时端口 → MySQL/Redis/HTTP 后端 | 下游已关闭,代理/客户端未 close | 查连接池、超时、错误分支 |
大量 TIME_WAIT | 本地临时端口 → 固定 upstream | 短连接过多,主动关闭方是本机 | 开启 keepalive/连接池,扩大端口范围只是止血 |
大量 FIN_WAIT2 | 本机主动关闭后等待对端 FIN | 对端应用迟迟不关闭 | 查对端应用、tcp_fin_timeout 只能缓解 |
大量 SYN_RECV | listen 端口 | SYN Flood 或 backlog 不够 | 查 ss -lnt、SYN Cookie、防火墙 |
Recv-Q 持续增长 | listen 或 established | 应用 accept/read 不及时 | 查 CPU、线程池、事件循环阻塞 |
第五步:确认应用是否真的没有 close
bash
# 追踪 close/shutdown/read/write/connect/accept
strace -f -e trace=close,shutdown,read,write,connect,accept -p $pid
# 找 socket inode,再反查 fd
ss -tanp state close-wait
ls -l /proc/$pid/fd | grep socket
# Go 服务额外看 goroutine 是否卡在 net.(*netFD).Read/Write 或数据库驱动
curl http://127.0.0.1:6060/debug/pprof/goroutine?debug=2第六步:止血与根治
| 类型 | 动作 | 注意 |
|---|---|---|
| 止血 | 重启进程释放 fd | 只能恢复服务,不能消除根因 |
| 止血 | 临时调大 ulimit -n | 只是在泄漏时延后崩溃 |
| 止血 | 降低入口流量 / 熔断下游 | 防止代理继续堆连接 |
| 根治 | 所有错误路径补 close() | 特别是超时、取消、部分读写失败 |
| 根治 | 设置 connect/read/write/request timeout | 不允许无限等待下游 |
| 根治 | 启用连接池和 keepalive | 降低 TIME_WAIT 和建连开销 |
| 根治 | 增加 fd、TCP 状态、连接池等待、下游 P99 监控 | 提前发现趋势 |
4. curl — HTTP 调试利器
4.1 基础调试
bash
# 详细输出(含 TLS 握手)
curl -v https://example.com
# 只看响应头
curl -I https://example.com
# 输出时间明细
curl -w "\n\
dns_resolve: %{time_namelookup}s\n\
tcp_connect: %{time_connect}s\n\
tls_handshake:%{time_appconnect}s\n\
first_byte: %{time_starttransfer}s\n\
total: %{time_total}s\n" \
-o /dev/null -s https://example.com
# 指定 DNS 解析(绕过 DNS)
curl --resolve example.com:443:1.2.3.4 https://example.com
# 指定网卡/IP
curl --interface eth0 https://example.com4.2 高级用法
bash
# 查看 TLS 握手详情
curl -v --tlsv1.3 https://example.com 2>&1 | grep -E 'TLS|cert|subject|issuer'
# 指定密码套件
curl --ciphers ECDHE-RSA-AES128-GCM-SHA256 https://example.com
# 测试 HTTP/2
curl --http2 -I https://example.com
# POST JSON + Header
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-H "Authorization: Bearer token123" \
-d '{"name":"Alice","age":30}'
# 跟踪重定向
curl -L https://example.com
# 下载并保存
curl -O https://example.com/file.tar.gz5. nc (netcat) — 网络瑞士军刀
bash
# 端口连通性测试
nc -zv 10.0.0.1 80 # TCP 扫描
nc -zvu 10.0.0.1 53 # UDP 扫描
# 构建简单 TCP 服务端
nc -l 8080
# 构建 TCP 客户端
echo "hello" | nc 10.0.0.1 8080
# 构建 HTTP 请求
printf "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n" | nc example.com 80
# 端口转发
mkfifo /tmp/fifo
nc -l 8080 < /tmp/fifo | nc 10.0.0.1 80 > /tmp/fifo
# 传输文件
# 接收方: nc -l 9999 > received_file
# 发送方: nc 10.0.0.1 9999 < file_to_send6. 网络性能测试
6.1 iperf3 — 带宽测试
bash
# 服务端
iperf3 -s
# 客户端 (TCP 测试)
iperf3 -c <server_ip> -t 30 # 30 秒测试
iperf3 -c <server_ip> -P 10 # 10 个并行流
iperf3 -c <server_ip> -R # 反向测试(下载)
# UDP 测试
iperf3 -c <server_ip> -u -b 100M # 100 Mbps UDP6.2 wrk / ab — HTTP 压测
bash
# wrk (推荐,更快)
wrk -t4 -c100 -d30s https://example.com
# -t4 : 4 线程
# -c100 : 100 并发连接
# -d30s : 持续 30 秒
# ab (Apache Bench)
ab -n 10000 -c 100 https://example.com/
# -n 10000 : 总请求数
# -c 100 : 并发数6.3 mtr — 综合路由诊断
bash
# My Traceroute (ping + traceroute 结合)
mtr 8.8.8.8
# 显示每一跳的丢包率、延迟、抖动7. DNS 诊断
bash
# dig 完整查询过程
dig +trace example.com
# 指定 DNS 服务器
dig @8.8.8.8 example.com
# 查询特定记录类型
dig example.com A
dig example.com AAAA
dig example.com MX
dig example.com TXT
# 反向解析
dig -x 8.8.8.8
# 简短输出
dig +short example.com
# nslookup (兼容 Windows)
nslookup example.com
nslookup example.com 8.8.8.88. 系统层面分析
8.1 strace — 追踪网络系统调用
bash
# 追踪所有网络相关系统调用
strace -e trace=network -p <pid>
# 追踪 socket 调用
strace -e trace=socket,connect,bind,listen,accept,close -p <pid>
# 追踪并统计耗时
strace -c -e trace=network -p <pid> # Ctrl+C 后输出统计
# 追踪连接建立
strace -e trace=connect,accept -p <pid>8.2 /proc 文件系统
bash
# TCP 连接信息
cat /proc/net/tcp # IPv4 TCP
cat /proc/net/tcp6 # IPv6 TCP
cat /proc/net/udp # UDP
# socket 统计
cat /proc/net/sockstat
# 网卡统计(丢包、错误)
cat /proc/net/dev
ip -s link show eth08.3 sysctl 内核参数
bash
# TCP 相关内核参数
sysctl -a | grep net.ipv4.tcp
# 常用调优
sysctl net.ipv4.tcp_fin_timeout # TIME_WAIT 时长
sysctl net.ipv4.tcp_tw_reuse # TIME_WAIT 重用
sysctl net.ipv4.tcp_max_syn_backlog # SYN 队列大小
sysctl net.core.somaxconn # accept 队列大小
sysctl net.ipv4.tcp_slow_start_after_idle # 空闲后重新慢启动
sysctl net.ipv4.tcp_congestion_control # 当前拥塞控制算法9. 实战排查方法论
mermaid
flowchart TD
PROBLEM["网络出问题了"] --> Q1{"什么症状?"}
Q1 -->|"连不上"| C1["1. ping/telnet 端口"]
C1 -->|"ping 不通"| C11["网线/交换机/IP/防火墙"]
C1 -->|"ping 通, port 不通"| C12["服务未启动/防火墙DROP"]
Q1 -->|"连接慢"| C2["2. curl -w 计时分析"]
C2 -->|"DNS 慢"| C21["dig 检查 / DNS服务器"]
C2 -->|"TCP 慢"| C22["traceroute + mtr 检查路由"]
C2 -->|"TLS 慢"| C23["检查证书链 / OCSP 超时"]
Q1 -->|"连接断开/重置"| C3["3. ss 查状态"]
C3 -->|"大量 CLOSE_WAIT"| C31["应用 bug: 未 close()"]
C3 -->|"大量 TIME_WAIT"| C32["短连接太多, 需复用"]
C3 -->|"SYN_RECV"| C33["SYN Flood / backlog 小"]
Q1 -->|"传输慢/卡"| C4["4. tcpdump 抓包 + Wireshark"]
C4 -->|"大量重传"| C41["丢包/拥塞/带宽不足"]
C4 -->|"零窗口"| C42["接收方处理不过来"]
C4 -->|"延迟高"| C43["TCP 延迟 ACK / Nagle"]
C1 & C2 & C3 & C4 --> FINAL["定位根因 → 修复"]9.1 常见问题速查表
| 症状 | 首选工具 | 诊断命令 | 可能原因 |
|---|---|---|---|
| 连接拒绝 | telnet / nc | nc -zv host port | 端口未监听/防火墙 |
| 连接超时 | tcpdump | 抓 SYN 是否有响应 | 防火墙丢包/路由不通 |
| TLS 证书错误 | openssl s_client | openssl s_client -connect | 证书过期/域名不匹配 |
| HTTP 502/504 | curl -v | 看响应头和耗时 | 上游服务挂了/超时 |
| 内存泄漏(连接) | ss -tanp | CLOSE_WAIT 堆积 | 代码未 close |
| 端口不够用 | ss -tan | TIME_WAIT 堆积 | 短连接过多 |
| DNS 解析失败 | dig | dig +trace | DNS 劫持/缓存污染 |
9.2 client → proxy → mysql 故障定位模板
mermaid
flowchart TD
A["用户报错: 超时/502/连接失败"] --> B["看 proxy 状态: ss -tanp"]
B --> C{"异常状态是什么?"}
C -->|"CLOSE_WAIT 指向 mysql:3306"| D["MySQL 已关连接<br/>Proxy 未 close 下游 fd"]
C -->|"TIME_WAIT 指向 mysql:3306"| E["Proxy 到 MySQL 短连接过多<br/>连接池/keepalive 不足"]
C -->|"ESTABLISHED 很多且 Recv-Q/Send-Q 高"| F["读写堵塞<br/>下游慢或上游不读"]
C -->|"listen Recv-Q 高"| G["accept 不及时<br/>事件循环/线程池被卡住"]
D --> H["查代码 close 路径、连接池归还、goroutine dump"]
E --> I["查连接池配置、端口范围、QPS、MySQL max_connections"]
F --> J["tcpdump/Wireshark 看 zero window、重传、RTT"]
G --> K["查 CPU、GC、锁、线程池、backlog"]| 观测项 | 命令 | 解释 |
|---|---|---|
| Proxy fd 数 | `ls /proc/$pid/fd | wc -l` |
| CLOSE_WAIT 对端 | ss -tanp state close-wait | 判断是 client 侧还是 mysql 侧 |
| MySQL 连接数 | show processlist / Threads_connected | 下游是否也被打满 |
| 连接池等待 | 应用指标 | 是否大量请求等连接 |
| Go goroutine | pprof/goroutine?debug=2 | 是否卡在 database/sql、netFD.Read/Write |
| 抓包 | tcpdump -i eth0 -nn host <mysql_ip> and port 3306 | 看 FIN/RST/重传/零窗口 |
判断口诀:
text
CLOSE_WAIT:对端已经关,我方没关 → 查应用 close。
TIME_WAIT:我方主动关太多 → 查短连接和连接复用。
FIN_WAIT2:我方想关,对方不关 → 查对端应用。
SYN_RECV:连接还没建完 → 查 backlog、SYN Flood、防火墙。
Recv-Q 高:内核有数据,应用不读 → 查事件循环/线程池/GC/锁。
Send-Q 高:应用写了,对端不收 → 查对端处理能力/网络拥塞。10. 按协议层分工具验证知识点
每一层的知识点都可以用对应的系统工具来"眼见为实"。以下按 OSI 层次从底到顶,给出工具 + 验证命令 + 预期输出解读。
10.1 链路层(L2)— ethtool / arping / bridge / ip link
bash
# === ethtool — 网卡物理层诊断 ===
# 查看网卡速率、双工模式、自协商状态
ethtool eth0
# Speed: 25000Mb/s ← 25Gbps
# Duplex: Full ← 全双工
# Auto-negotiation: on
# Link detected: yes ← 物理链路正常
# 查看网卡统计(丢包、CRC 错误、碰撞)
ethtool -S eth0 | grep -E 'rx_errors|tx_errors|rx_dropped|tx_dropped|crc'
# rx_errors: 0
# rx_crc_errors: 0 ← CRC 错误 > 0 说明网线/光模块有问题
# rx_dropped: 12 ← 内核来不及处理(ring buffer 满)
# 查看/修改 ring buffer 大小(解决高流量丢包)
ethtool -g eth0 # 查看当前和最大值
ethtool -G eth0 rx 4096 # 调大接收 ring buffer
# 查看网卡 offload 功能(TSO/GRO/GSO)
ethtool -k eth0 | grep -E 'tcp-segmentation|generic-receive|generic-segmentation'
# tcp-segmentation-offload: on ← TSO 开启,大包由网卡分段
# 关闭 GRO(排查抓包看不到小包的问题)
ethtool -K eth0 gro off
# === arping — ARP 层诊断 ===
# 检测 IP 冲突(同一 IP 是否有两个 MAC 响应)
arping -D -I eth0 192.168.1.100
# 如果收到响应 → IP 冲突!
# 查看 ARP 缓存
ip neigh show
# 192.168.1.1 dev eth0 lladdr aa:bb:cc:dd:ee:ff REACHABLE
# 清除 ARP 缓存(排查 ARP 缓存污染)
ip neigh flush dev eth0
# === bridge — 二层交换/VLAN 诊断 ===
# 查看 Linux bridge 的 MAC 转发表
bridge fdb show
# aa:bb:cc:dd:ee:ff dev veth0 master br0
# 查看 VLAN 过滤
bridge vlan show| 验证知识点 | 工具命令 | 预期输出/判断 |
|---|---|---|
| 网卡是否全双工 | ethtool eth0 | Duplex: Full |
| 物理链路是否正常 | ethtool eth0 | Link detected: yes |
| 是否有 CRC 错误(网线问题) | ethtool -S eth0 | rx_crc_errors > 0 |
| Ring buffer 是否溢出 | ethtool -S eth0 | rx_missed_errors > 0 |
| IP 地址冲突 | arping -D | 收到响应 = 冲突 |
| ARP 欺骗 | ip neigh show | 同一 IP 对应多个 MAC |
| TSO/GRO 是否影响抓包 | ethtool -k eth0 | gro: on 时抓包看到大包 |
10.2 IP 层(L3)— traceroute / ip route / iptables / nftables
bash
# === traceroute — 逐跳路由诊断 ===
# TCP traceroute(穿透防火墙,比 ICMP 更可靠)
traceroute -T -p 80 example.com
# UDP traceroute(默认)
traceroute example.com
# 指定源 IP(多网卡环境)
traceroute -s 10.0.0.5 example.com
# === ip route — 路由表诊断 ===
# 查看完整路由表
ip route show table all
# 查看某个目标走哪条路由
ip route get 8.8.8.8
# 8.8.8.8 via 10.0.0.1 dev eth0 src 10.0.0.5 uid 0
# 查看策略路由规则
ip rule show
# === iptables — 包过滤/NAT 诊断 ===
# 查看所有规则(含计数器)
iptables -L -n -v --line-numbers
iptables -t nat -L -n -v
# 实时监控被 DROP 的包
iptables -I INPUT -j LOG --log-prefix "IPT-DROP: " --log-level 4
dmesg -w | grep "IPT-DROP"
# 查看 conntrack 表(NAT 连接跟踪)
conntrack -L -n | head -20
conntrack -C # 当前连接跟踪数
cat /proc/sys/net/netfilter/nf_conntrack_max # 最大值
# conntrack 表满的症状: 新连接被丢弃
# 解决: sysctl -w net.netfilter.nf_conntrack_max=262144
# === nftables — 新一代包过滤 ===
nft list ruleset
nft list table inet filter
nft monitor trace # 实时追踪包经过的规则链bash
# 验证: MTU 和分片
# 发送不允许分片的大包,测试 Path MTU
ping -M do -s 1472 example.com
# 如果返回 "Frag needed" → MTU < 1500
# 查看接口 MTU
ip link show eth0 | grep mtu
# 验证: IP 分片重组
tcpdump -i eth0 'ip[6:2] & 0x3fff != 0' # 抓所有分片包| 验证知识点 | 工具命令 | 预期输出/判断 |
|---|---|---|
| 路由是否正确 | ip route get <dst> | 看 via/dev/src |
| 哪一跳丢包 | mtr <dst> | 某跳 Loss% > 0 |
| 防火墙是否拦截 | iptables -L -n -v | 看 DROP 规则计数 |
| conntrack 表是否满 | conntrack -C vs nf_conntrack_max | 接近上限 = 新连接丢弃 |
| MTU 是否匹配 | ping -M do -s 1472 | Frag needed = MTU 不够 |
| PMTUD 是否工作 | tcpdump icmp | 看 ICMP type 3 code 4 |
10.3 TCP 层(L4)— ss / tcpdump / sysctl / /proc/net
bash
# === 验证三次握手 ===
# 抓 SYN → SYN+ACK → ACK
tcpdump -i eth0 -nn 'tcp[tcpflags] & (tcp-syn) != 0' -c 10
# === 验证四次挥手 ===
# 抓 FIN → ACK → FIN → ACK
tcpdump -i eth0 -nn 'tcp[tcpflags] & (tcp-fin) != 0'
# === 验证 TIME_WAIT ===
ss -tan state time-wait | wc -l
# 查看 TIME_WAIT 超时时间(Linux 固定 60s,不可调)
cat /proc/sys/net/ipv4/tcp_fin_timeout # 注意: 这是 FIN_WAIT2 的超时
# === 验证拥塞控制算法 ===
# 查看当前算法
sysctl net.ipv4.tcp_congestion_control
# cubic / bbr / reno
# 切换到 BBR
sysctl -w net.ipv4.tcp_congestion_control=bbr
# 验证 BBR 是否生效(需要 fq 队列调度)
tc qdisc show dev eth0
# qdisc fq 8001: root ... ← BBR 需要 fq
# === 验证滑动窗口 ===
# 实时观察窗口大小变化
ss -ti dst 10.0.0.1
# 输出: cwnd:10 ssthresh:7 rtt:1.5/0.5 ... rcv_space:14600
# === 验证 TCP Keep-Alive ===
sysctl net.ipv4.tcp_keepalive_time # 空闲多久发探测 (默认 7200s)
sysctl net.ipv4.tcp_keepalive_intvl # 探测间隔 (默认 75s)
sysctl net.ipv4.tcp_keepalive_probes # 探测次数 (默认 9)
# === 验证 backlog 队列 ===
ss -lnt | head
# Recv-Q Send-Q Local Address:Port
# 0 128 0.0.0.0:80 ← Send-Q = backlog 大小
# 5 128 0.0.0.0:80 ← Recv-Q > 0 = 有连接等待 accept
# 查看 SYN 队列溢出统计
netstat -s | grep -i "SYNs to LISTEN"
# 或
nstat -az TcpExtListenOverflows TcpExtListenDrops
# === 验证重传 ===
# 实时统计重传率
nstat -az TcpRetransSegs TcpOutSegs
# 重传率 = TcpRetransSegs / TcpOutSegs
# 查看某连接的重传次数
ss -ti dst 10.0.0.1 | grep retrans
# retrans:0/3 ← 当前/累计重传次数
# === 验证 Nagle 算法 ===
# 抓包看小包是否被合并
tcpdump -i eth0 -nn 'dst port 80 and (ip[2:2] < 100)'
# 如果看到大量 < 100 字节的包 → Nagle 被关闭 (TCP_NODELAY)| 验证知识点 | 工具命令 | 预期输出/判断 |
|---|---|---|
| 三次握手是否完成 | tcpdump 抓 SYN/SYN+ACK/ACK | 三个包都有 = 正常 |
| TIME_WAIT 数量 | ss -tan state time-wait | wc -l | > 10000 需关注 |
| 拥塞窗口大小 | ss -ti dst <ip> | cwnd 值 |
| 当前拥塞算法 | sysctl net.ipv4.tcp_congestion_control | cubic/bbr |
| backlog 是否溢出 | nstat TcpExtListenOverflows | > 0 = 溢出 |
| 重传率 | nstat TcpRetransSegs | 持续增长 = 丢包 |
| 零窗口 | tcpdump 看 win 0 | 接收方处理不过来 |
10.4 UDP 层 — nc / tcpdump / ss
bash
# === 验证 UDP 连通性 ===
# 服务端
nc -ul 9999
# 客户端
echo "hello" | nc -u <server> 9999
# === 抓 UDP 包 ===
tcpdump -i eth0 -nn udp port 53
# 看 DNS 查询和响应
# === 查看 UDP 统计 ===
cat /proc/net/snmp | grep Udp
# Udp: InDatagrams NoPorts InErrors OutDatagrams RcvbufErrors SndbufErrors
# InErrors > 0 → 校验和错误或缓冲区溢出
# UDP 缓冲区溢出统计
nstat -az UdpRcvbufErrors UdpSndbufErrors
# === 调整 UDP 缓冲区 ===
sysctl net.core.rmem_max # 最大接收缓冲区
sysctl net.core.rmem_default # 默认接收缓冲区
sysctl -w net.core.rmem_max=26214400 # 调到 25MB
# === QUIC (UDP 443) 诊断 ===
# 抓 QUIC 初始握手包
tcpdump -i eth0 -nn 'udp port 443 and udp[0] & 0x80 != 0'
# 0x80 = Long Header (Initial/Handshake 包)
# 用 qlog 分析 QUIC 连接
# Chrome: chrome://flags → Enable QUIC → 设置 QUIC_LOG
# 或用 quiche 的 qlog 输出| 验证知识点 | 工具命令 | 预期输出/判断 |
|---|---|---|
| UDP 是否可达 | nc -uv <host> <port> | 无响应不代表不通(无连接) |
| UDP 丢包 | nstat UdpRcvbufErrors | > 0 = 缓冲区溢出丢包 |
| DNS 查询过程 | tcpdump udp port 53 | 看 query/response |
| QUIC 是否走 UDP | tcpdump udp port 443 | 有包 = QUIC 生效 |
10.5 HTTP 层 — curl / httpie / h2c / grpcurl / wscat
bash
# === HTTP/1.1 完整诊断 ===
# 查看完整请求/响应头
curl -v http://example.com 2>&1 | grep -E '^[<>*]'
# 分段计时(DNS/TCP/TLS/首字节/总时间)
curl -w "\
dns: %{time_namelookup}s\n\
tcp: %{time_connect}s\n\
tls: %{time_appconnect}s\n\
first_byte: %{time_starttransfer}s\n\
total: %{time_total}s\n\
size: %{size_download} bytes\n\
speed: %{speed_download} B/s\n" \
-o /dev/null -s https://example.com
# 验证 Keep-Alive 是否生效
curl -v http://example.com http://example.com 2>&1 | grep -E 'Re-using|Connected'
# "Re-using existing connection" = Keep-Alive 生效
# 验证 HTTP 压缩
curl -H "Accept-Encoding: gzip" -I http://example.com
# Content-Encoding: gzip ← 压缩生效
# 验证 HTTP 缓存
curl -I http://example.com
# Cache-Control: max-age=3600
# ETag: "abc123"
curl -H "If-None-Match: \"abc123\"" -I http://example.com
# HTTP/1.1 304 Not Modified ← 缓存命中
# === HTTP/2 诊断 ===
# 验证是否支持 HTTP/2
curl --http2 -I https://example.com 2>&1 | grep -i 'HTTP/2'
# h2c — HTTP/2 明文调试工具
# 安装: go install github.com/nicholasgasior/h2c@latest
h2c connect example.com:443
h2c get /api/users
# nghttp — HTTP/2 专用客户端
nghttp -v https://example.com
# 可以看到 SETTINGS/HEADERS/DATA/WINDOW_UPDATE 帧
# 验证 HTTP/2 多路复用(同一连接并发请求)
nghttp -a -n https://example.com
# 看 stream_id: 1, 3, 5, 7... 多个流并行
# === gRPC 诊断 ===
# grpcurl — gRPC 的 curl
# 列出服务
grpcurl -plaintext localhost:50051 list
# 调用方法
grpcurl -plaintext -d '{"name":"world"}' localhost:50051 helloworld.Greeter/SayHello
# 查看方法签名
grpcurl -plaintext localhost:50051 describe helloworld.Greeter
# grpc-health-probe — 健康检查
grpc-health-probe -addr=localhost:50051
# === WebSocket 诊断 ===
# wscat — WebSocket 交互式客户端
# 安装: npm install -g wscat
wscat -c ws://localhost:8080/ws
> {"type":"ping"}
< {"type":"pong"}
# websocat — Rust 实现,更强大
websocat ws://localhost:8080/ws
# 支持管道: echo '{"msg":"hi"}' | websocat ws://localhost:8080/ws
# 用 curl 测试 WebSocket 升级握手
curl -v -H "Upgrade: websocket" \
-H "Connection: Upgrade" \
-H "Sec-WebSocket-Key: dGhlIHNhbXBsZQ==" \
-H "Sec-WebSocket-Version: 13" \
http://localhost:8080/ws
# 期望: 101 Switching Protocols| 验证知识点 | 工具命令 | 预期输出/判断 |
|---|---|---|
| HTTP Keep-Alive | curl -v 连续请求 | "Re-using existing connection" |
| HTTP/2 支持 | curl --http2 -I | HTTP/2 200 |
| HTTP/2 多路复用 | nghttp -v | 多个 stream_id 并行 |
| gRPC 连通性 | grpcurl list | 列出服务 |
| WebSocket 握手 | curl -v + Upgrade 头 | 101 Switching Protocols |
| 响应压缩 | curl -H "Accept-Encoding: gzip" | Content-Encoding: gzip |
| 缓存协商 | curl -H "If-None-Match" | 304 Not Modified |
10.6 TLS/HTTPS 层 — openssl / ssldump / testssl.sh
bash
# === openssl s_client — TLS 握手完整诊断 ===
# 基础连接测试(看证书链、协议版本、密码套件)
openssl s_client -connect example.com:443 -servername example.com </dev/null
# 关键输出解读:
# ---
# Certificate chain
# 0 s:CN = example.com ← 服务器证书
# i:C = US, O = Let's Encrypt ← 签发者
# 1 s:C = US, O = Let's Encrypt ← 中间证书
# i:O = Digital Signature Trust ← 根 CA
# ---
# SSL-Session:
# Protocol : TLSv1.3 ← 协议版本
# Cipher : TLS_AES_256_GCM_SHA384 ← 密码套件
# Session-ID: ...
# Verify return code: 0 (ok) ← 证书验证通过
# 强制 TLS 1.3
openssl s_client -connect example.com:443 -tls1_3
# 强制 TLS 1.2(测试降级)
openssl s_client -connect example.com:443 -tls1_2
# 查看证书详情(有效期、SAN、签名算法)
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
| openssl x509 -text -noout | grep -A2 'Validity\|Subject Alternative'
# 检查证书过期时间
openssl s_client -connect example.com:443 </dev/null 2>/dev/null \
| openssl x509 -noout -dates
# notBefore=Jan 1 00:00:00 2024 GMT
# notAfter=Dec 31 23:59:59 2024 GMT
# 验证证书链完整性
openssl verify -CAfile ca-bundle.crt server.crt
# 测试 OCSP Stapling
openssl s_client -connect example.com:443 -status </dev/null | grep -A5 "OCSP Response"
# 测试 SNI(同一 IP 多证书)
openssl s_client -connect 1.2.3.4:443 -servername site-a.com </dev/null | grep "subject"
openssl s_client -connect 1.2.3.4:443 -servername site-b.com </dev/null | grep "subject"
# 不同 servername 返回不同证书 = SNI 正常
# === testssl.sh — 一键全面扫描 ===
# 安装: git clone https://github.com/drwetter/testssl.sh
./testssl.sh example.com
# 输出: 协议支持、密码套件强度、漏洞检测(Heartbleed/POODLE/BEAST)、证书信息
# === ssldump — TLS 流量解析 ===
ssldump -i eth0 -k server.key port 443
# 解密并显示 TLS 握手和应用数据(需要私钥,仅 RSA 密钥交换)
# === 测量 TLS 握手耗时 ===
openssl s_time -connect example.com:443 -new -num 100
# 100 次新建连接的平均耗时
# 对比 session 复用
openssl s_time -connect example.com:443 -reuse -num 100
# 复用 session 的耗时(应该明显更快)| 验证知识点 | 工具命令 | 预期输出/判断 |
|---|---|---|
| TLS 版本 | openssl s_client -tls1_3 | Protocol: TLSv1.3 |
| 证书链是否完整 | openssl s_client | depth=0,1,2 且 Verify=0 |
| 证书是否过期 | openssl x509 -dates | notAfter 在未来 |
| SNI 是否正确 | openssl s_client -servername | 返回对应域名证书 |
| OCSP Stapling | openssl s_client -status | OCSP Response Status: successful |
| Session 复用 | openssl s_time -reuse | 耗时明显低于 -new |
| 密码套件安全性 | testssl.sh | 无 WEAK/VULNERABLE 标记 |
10.7 lsof — 文件描述符与网络连接
bash
# === 查看进程的所有网络连接 ===
lsof -i -a -p <pid>
# COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
# server 1234 root 3u IPv4 12345 0t0 TCP *:8080 (LISTEN)
# server 1234 root 5u IPv4 12346 0t0 TCP 10.0.0.1:8080->10.0.0.2:54321 (ESTABLISHED)
# === 查看谁在监听某端口 ===
lsof -i :8080
lsof -i TCP:8080 -s TCP:LISTEN
# === 按状态过滤 ===
lsof -i -s TCP:CLOSE_WAIT # 所有 CLOSE_WAIT 连接
lsof -i -s TCP:ESTABLISHED # 所有已建立连接
# === 查看进程打开的 socket 文件 ===
lsof -p <pid> | grep -E 'IPv4|IPv6|socket|pipe'
# === 查看某个远端 IP 的所有连接 ===
lsof -i @10.0.0.1
# === 查看 Unix Domain Socket ===
lsof -U
# === fd 泄漏排查 ===
# 1. 看总数
lsof -p <pid> | wc -l
# 2. 按类型分组
lsof -p <pid> | awk '{print $5}' | sort | uniq -c | sort -rn
# 输出:
# 1024 IPv4 ← 大量网络连接
# 50 REG ← 普通文件
# 10 unix ← Unix socket
# 3. 对比 fd 上限
cat /proc/<pid>/limits | grep "open files"
# Max open files 65536 65536 files
# === lsof vs ss 的选择 ===
# lsof: 看进程维度(这个进程打开了什么)
# ss: 看连接维度(这个端口/状态有哪些连接)
# 两者结合使用效果最佳10.8 综合: 一个请求的全链路工具验证
text
用户请求: curl https://api.example.com/users
每一步对应的验证工具:
┌─────────────────────────────────────────────────────────────────┐
│ 步骤 │ 工具 │ 验证什么 │
├─────────────────────────┼─────────────────────┼───────────────────┤
│ 1. DNS 解析 │ dig / tcpdump :53 │ 解析耗时、结果 │
│ 2. TCP 三次握手 │ tcpdump / ss │ SYN-RTT、backlog │
│ 3. TLS 握手 │ openssl s_client │ 版本、证书、耗时 │
│ 4. HTTP 请求发送 │ curl -v / tcpdump │ 请求头、压缩 │
│ 5. 服务端处理 │ strace / pprof │ 系统调用、耗时 │
│ 6. HTTP 响应接收 │ curl -w / tcpdump │ 首字节、总时间 │
│ 7. TCP 连接复用/关闭 │ ss / tcpdump │ Keep-Alive/FIN │
│ 8. 网卡收发统计 │ ethtool -S │ 丢包、错误 │
└─────────────────────────────────────────────────────────────────┘bash
# 一键全链路诊断脚本
TARGET="api.example.com"
PORT=443
echo "=== 1. DNS ==="
dig +short $TARGET
dig $TARGET | grep "Query time"
echo "=== 2. TCP + TLS + HTTP 分段计时 ==="
curl -w "\
dns: %{time_namelookup}s\n\
tcp: %{time_connect}s\n\
tls: %{time_appconnect}s\n\
first_byte: %{time_starttransfer}s\n\
total: %{time_total}s\n\
http_code: %{http_code}\n\
remote_ip: %{remote_ip}\n" \
-o /dev/null -s "https://$TARGET/"
echo "=== 3. TLS 证书 ==="
openssl s_client -connect $TARGET:$PORT -servername $TARGET </dev/null 2>/dev/null \
| openssl x509 -noout -subject -dates -issuer
echo "=== 4. 路由 ==="
traceroute -T -p $PORT -m 10 $TARGET
echo "=== 5. 本机连接状态 ==="
ss -tan | grep $(dig +short $TARGET | head -1) | head -5
登录后即可发表评论 👇