第 17 章:故障排查与调试
第 17 章:故障排查与调试
本章目标:掌握 iptables 相关问题的系统化排查方法,学会使用日志、tcpdump、conntrack 等工具定位防火墙规则问题。
17.1 排查方法论
17.1.1 系统化排查流程
┌─────────────────────────────────────────┐
│ 步骤 1:确认问题现象 │
│ - 什么流量被阻断/放行了? │
│ - 从哪个方向(入站/出站/转发)? │
│ - 是所有流量还是特定流量? │
└────────────────┬────────────────────────┘
▼
┌─────────────────────────────────────────┐
│ 步骤 2:检查当前规则 │
│ - iptables -L -n -v --line-numbers │
│ - iptables -t nat -L -n -v │
│ - 查看规则的 pkts/bytes 计数器 │
└────────────────┬────────────────────────┘
▼
┌─────────────────────────────────────────┐
│ 步骤 3:添加日志规则 │
│ - 在可疑位置添加 LOG 规则 │
│ - 查看 dmesg / syslog │
└────────────────┬────────────────────────┘
▼
┌─────────────────────────────────────────┐
│ 步骤 4:使用诊断工具 │
│ - tcpdump 抓包 │
│ - conntrack -L 查看连接状态 │
│ - ss / netstat 查看 socket 状态 │
└────────────────┬────────────────────────┘
▼
┌─────────────────────────────────────────┐
│ 步骤 5:定位问题并修复 │
│ - 调整规则顺序/添加缺失规则 │
│ - 验证修复效果 │
└─────────────────────────────────────────┘
17.1.2 快速诊断命令速查
| 用途 | 命令 |
|---|---|
| 查看规则和计数 | iptables -L -n -v --line-numbers |
| 查看 NAT 规则 | iptables -t nat -L -n -v |
| 查看 conntrack | conntrack -L |
| 统计连接数 | conntrack -C |
| 查看监听端口 | ss -tlnp |
| 查看所有连接 | ss -tunap |
| 抓包分析 | tcpdump -i eth0 -n |
| 查看内核日志 | dmesg | grep -i ipt |
| 查看系统日志 | journalctl -k | grep -i ipt |
17.2 常见问题与解决方案
17.2.1 问题 1:连接被拒绝(Connection refused)
症状:
$ curl http://10.0.0.10:80
curl: (7) Failed to connect to 10.0.0.10 port 80: Connection refused
排查步骤:
# 1. 确认目标端口是否在监听
ss -tlnp | grep :80
# 如果没有输出 → 服务未启动,不是防火墙问题
# 2. 确认防火墙是否阻止
iptables -L INPUT -n -v | grep :80
# 如果没有匹配的规则 → 检查默认策略
# 3. 检查默认策略
iptables -L INPUT -n | head -1
# Chain INPUT (policy DROP) ← 可能是默认拒绝
# 4. 测试从本机访问
curl http://127.0.0.1:80
# 如果本机可以 → 防火墙阻止了外部访问
解决方案:
# 添加允许规则
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
17.2.2 问题 2:连接超时(Connection timed out)
症状:
$ curl http://10.0.0.10:80
curl: (28) Connection timed out after 30000 milliseconds
区别:
- Connection refused:有 ICMP/TCP RST 响应 → 服务未监听或 REJECT 规则
- Connection timed out:完全没有响应 → 通常是 DROP 规则或网络不可达
排查步骤:
# 1. 检查网络可达性
ping 10.0.0.10
# 2. 用 TCP 方式测试端口
nc -zv 10.0.0.10 80 -w 3
# 3. 在目标机器上检查是否有 DROP 规则命中
# 在目标机器执行:
iptables -L INPUT -n -v | grep DROP
# 4. 添加日志查看被丢弃的包
iptables -I INPUT -p tcp --dport 80 -j LOG --log-prefix "DEBUG-80: "
# 然后再次尝试连接,查看日志
dmesg | grep "DEBUG-80"
17.2.3 问题 3:新规则不生效
症状:添加了 ACCEPT 规则但流量仍被拒绝。
原因分析:
# 原因 1:规则顺序错误(被前面的 DROP 规则先匹配)
iptables -L INPUT -n --line-numbers
# 1 DROP tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80
# 2 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 ← 永远不会被执行
# 原因 2:规则加到了错误的表或链
iptables -t nat -A INPUT ... # ← nat 表的 INPUT 链
# 原因 3:匹配条件不正确
iptables -L INPUT -n -v
# 查看 pkts 字段,确认规则是否被命中(pkts > 0)
解决方案:
# 插入规则到正确位置(而不是追加到末尾)
iptables -I INPUT 1 -p tcp --dport 80 -j ACCEPT
# 或删除前面的 DROP 规则
iptables -D INPUT 1 # 删除第 1 条规则
17.2.4 问题 4:端口映射不工作
症状:配置了 DNAT 但从外部无法访问内网服务。
排查步骤:
# 1. 检查 IP 转发是否启用
sysctl net.ipv4.ip_forward
# 应该是 1
# 2. 检查 NAT 规则
iptables -t nat -L PREROUTING -n -v
# 确认 DNAT 规则存在且有命中计数
# 3. 检查 FORWARD 链
iptables -L FORWARD -n -v
# 确认有允许转发的规则
# 4. 在网关上抓包
tcpdump -i eth0 -n port 80
# 确认数据包到达了网关
# 5. 在内网服务器上抓包
tcpdump -i eth0 -n port 80
# 确认数据包被转发到了内网服务器
常见原因:
| 原因 | 解决方案 |
|---|---|
| IP 转发未启用 | sysctl -w net.ipv4.ip_forward=1 |
| FORWARD 链默认 DROP 且无允许规则 | 添加 FORWARD 允许规则 |
| NAT 规则接口不匹配 | 检查 -i 参数 |
| conntrack 条目缓存 | conntrack -D 清除 |
| 内网服务器网关未指向 NAT 主机 | 修改内网服务器的默认网关 |
17.2.5 问题 5:Docker 端口映射被防火墙规则阻止
症状:Docker 容器映射了端口但从外部无法访问。
# 1. 确认容器端口映射正确
docker port <container_name>
# 2. 确认容器正在运行
docker ps
# 3. 从宿主机本地测试
curl http://127.0.0.1:<mapped_port>
# 4. 检查 DOCKER 链
iptables -L DOCKER -n -v
iptables -t nat -L DOCKER -n -v
# 5. 检查 DOCKER-USER 链
iptables -L DOCKER-USER -n -v
# 6. 检查 FORWARD 链
iptables -L FORWARD -n -v
解决方案:
# 在 DOCKER-USER 链中添加允许规则
iptables -I DOCKER-USER -i eth0 -p tcp --dport 80 -j ACCEPT
# 不要在 INPUT 链中添加 Docker 端口映射规则(无效)
17.2.6 问题 6:conntrack 表满
症状:dmesg 中出现 nf_conntrack: table full, dropping packet。
# 检查当前使用量
conntrack -C
sysctl net.netfilter.nf_conntrack_max
# 查看使用率百分比
COUNT=$(conntrack -C)
MAX=$(sysctl -n net.netfilter.nf_conntrack_max)
echo "使用率: $(( COUNT * 100 / MAX ))%"
# 查看连接状态分布
conntrack -L | awk '{print $4}' | sort | uniq -c | sort -rn
# 查看占用连接最多的 IP
conntrack -L | grep -oP 'src=\K[^ ]+' | sort | uniq -c | sort -rn | head -20
解决方案:
# 临时方案:增大表
sysctl -w net.netfilter.nf_conntrack_max=262144
# 长期方案:缩短超时
sysctl -w net.netfilter.nf_conntrack_tcp_timeout_established=1800
sysctl -w net.netfilter.nf_conntrack_tcp_timeout_time_wait=15
# 对不需要跟踪的流量使用 NOTRACK
iptables -t raw -A PREROUTING -p tcp --dport 2049 -j NOTRACK
17.2.7 问题 7:NAT 规则修改后不生效
症状:修改了 DNAT/SNAT 规则,但已有连接仍走旧规则。
原因:conntrack 缓存了旧的 NAT 映射。
解决方案:
# 清除特定连接的 conntrack 条目
conntrack -D -p tcp --dport 80
# 清除所有 conntrack(危险!会导致所有已有连接丢失状态)
conntrack -F
17.3 使用日志调试
17.3.1 添加调试日志
#!/bin/bash
# ═══════════════════════════════════════════════════
# 防火墙调试脚本
# 在规则的关键位置插入 LOG 规则
# ═══════════════════════════════════════════════════
# 创建调试链
iptables -N DEBUG_LOG
# 记录所有匹配的数据包
iptables -A DEBUG_LOG \
-j LOG \
--log-prefix "FW-DEBUG: " \
--log-level 4 \
--log-ip-options \
--log-tcp-options
# 在 INPUT 链最前面插入调试规则
# 针对特定端口
iptables -I INPUT 1 -p tcp --dport 80 -j DEBUG_LOG
# 针对特定源 IP
iptables -I INPUT 1 -s 192.168.1.100 -j DEBUG_LOG
# 针对所有流量(仅在调试时使用,会生成大量日志)
# iptables -I INPUT 1 -j DEBUG_LOG
17.3.2 分析日志
# 实时查看日志
tail -f /var/log/messages # CentOS/RHEL
tail -f /var/log/syslog # Debian/Ubuntu
tail -f /var/log/kern.log # 通用
# 如果配置了 rsyslog 分离 iptables 日志
tail -f /var/log/iptables.log
# 使用 dmesg 查看
dmesg -w | grep "FW-DEBUG"
# 过滤特定字段
dmesg | grep "FW-DEBUG" | grep "DPT=80"
# 提取被拒绝的源 IP
dmesg | grep "FW-DEBUG" | grep -oP 'SRC=\K[^ ]+' | sort | uniq -c | sort -rn | head -20
17.3.3 日志字段解读
FW-DEBUG: IN=eth0 OUT= MAC=00:11:22:33:44:55:aa:bb:cc:dd:ee:ff:08:00
SRC=192.168.1.100 DST=10.0.0.10 LEN=60 TOS=0x00 PREC=0x00 TTL=64
ID=12345 DF PROTO=TCP SPT=54321 DPT=80 WINDOW=29200 RES=0x00 SYN
URGP=0
| 字段 | 含义 |
|---|---|
IN=eth0 | 入接口 |
OUT= | 出接口(INPUT 链为空) |
MAC= | 目的 MAC + 源 MAC + 协议类型 |
SRC=192.168.1.100 | 源 IP |
DST=10.0.0.10 | 目的 IP |
LEN=60 | 数据包长度(字节) |
TOS=0x00 | 服务类型 |
TTL=64 | 生存时间 |
PROTO=TCP | 协议 |
SPT=54321 | 源端口 |
DPT=80 | 目的端口 |
SYN | TCP 标志位 |
17.4 使用 tcpdump 调试
17.4.1 基本抓包
# 在所有接口抓取 TCP 80 端口的包
sudo tcpdump -i any -n port 80
# 在特定接口抓包
sudo tcpdump -i eth0 -n port 80
# 抓取特定源 IP 的包
sudo tcpdump -i eth0 -n src host 192.168.1.100
# 抓取特定目的 IP 和端口
sudo tcpdump -i eth0 -n dst host 10.0.0.10 and dst port 80
# 抓取 SYN 包(新建连接)
sudo tcpdump -i eth0 -n 'tcp[tcpflags] & tcp-syn != 0'
# 抓取 ICMP 包
sudo tcpdump -i eth0 -n icmp
# 保存到文件(用于后续分析)
sudo tcpdump -i eth0 -n port 80 -w /tmp/capture.pcap
# 读取 pcap 文件
sudo tcpdump -r /tmp/capture.pcap -n
17.4.2 对比防火墙两侧的抓包
# ═══════════════════════════════════════════════════
# 方法:在防火墙的内外两侧同时抓包
# 对比结果可以确定数据包在哪个环节被丢弃
# ═══════════════════════════════════════════════════
# 终端 1:在外部接口抓包(数据包到达防火墙前)
sudo tcpdump -i eth0 -n port 80 -w /tmp/outside.pcap
# 终端 2:在内部接口抓包(数据包经过防火墙后)
sudo tcpdump -i eth1 -n port 80 -w /tmp/inside.pcap
# 执行测试请求
curl http://10.0.0.10:80
# 对比两个 pcap 文件
# 如果 outside 有请求但 inside 没有 → 防火墙阻止了转发
# 如果 inside 有请求但没有响应 → 目标服务器不响应
17.5 使用 conntrack 调试
17.5.1 监控连接状态
# 实时监控连接变化
conntrack -E
# 输出示例:
# [NEW] tcp 6 120 SYN_SENT src=192.168.1.100 dst=10.0.0.10 sport=54321 dport=80
# [UPDATE] tcp 6 60 SYN_RECV src=192.168.1.100 dst=10.0.0.10 sport=54321 dport=80
# [UPDATE] tcp 6 432000 ESTABLISHED src=192.168.1.100 dst=10.0.0.10 sport=54321 dport=80
# 查看特定连接
conntrack -L -p tcp --dport 80 -s 192.168.1.100
# 查看 NAT 转换记录
conntrack -L -p tcp --dst-nat
conntrack -L -p tcp --src-nat
17.5.2 连接状态分析
# 查看连接状态分布
conntrack -L | awk '{print $4}' | sort | uniq -c | sort -rn
# 典型输出:
# 15000 ESTABLISHED
# 200 SYN_SENT
# 50 TIME_WAIT
# 10 SYN_RECV
# 5 FIN_WAIT
# 如果 SYN_SENT 数量远大于 ESTABLISHED → 可能有大量连接超时
# 如果 TIME_WAIT 过多 → 调整 tcp_timeout_time_wait
17.5.3 检查 NAT 映射
# 查看 DNAT 映射
conntrack -L -p tcp --dst-nat | head -10
# 输出示例:
# tcp 6 118 SYN_SENT src=203.0.113.50 dst=203.0.113.1 sport=54321 dport=80
# [UNREPLIED] src=10.0.0.10 dst=203.0.113.50 sport=80 dport=54321
# use=1
# 字段解读:
# - 原始包:src=203.0.113.50 → dst=203.0.113.1:80
# - NAT 转换后:dst 变为 10.0.0.10:80
# - [UNREPLIED] 表示还没有收到响应
17.6 规则审计
17.6.1 检查规则有效性
#!/bin/bash
# ═══════════════════════════════════════════════════
# 规则审计脚本
# 检查哪些规则从未被命中
# ═══════════════════════════════════════════════════
echo "=== 未命中的规则(pkts = 0)==="
iptables -L INPUT -n -v --line-numbers | awk 'NR>2 && $2+0 == 0 {print}'
iptables -L FORWARD -n -v --line-numbers | awk 'NR>2 && $2+0 == 0 {print}'
iptables -L OUTPUT -n -v --line-numbers | awk 'NR>2 && $2+0 == 0 {print}'
echo ""
echo "=== 最常命中的规则 ==="
iptables -L INPUT -n -v --line-numbers | sort -k1 -n -r | head -10
echo ""
echo "=== 默认策略丢弃的包数 ==="
iptables -L INPUT -n | grep "policy DROP" | awk '{print "INPUT DROP: " $4}'
iptables -L FORWARD -n | grep "policy DROP" | awk '{print "FORWARD DROP: " $4}'
echo ""
echo "=== 规则总数 ==="
echo "INPUT: $(iptables -L INPUT -n | wc -l) 条"
echo "FORWARD: $(iptables -L FORWARD -n | wc -l) 条"
echo "OUTPUT: $(iptables -L OUTPUT -n | wc -l) 条"
17.6.2 清零计数器后重新统计
# 清零所有计数器
iptables -Z INPUT
iptables -Z FORWARD
# 等待一段时间(如 1 小时)
sleep 3600
# 查看命中的规则
iptables -L INPUT -n -v --line-numbers | awk 'NR>2 && $2+0 > 0 {print}'
17.7 性能调试
17.7.1 规则匹配性能分析
# 查看规则数量
iptables -L INPUT -n | wc -l
iptables -L FORWARD -n | wc -l
# 查看每个规则的命中率
# 高命中率的规则应该放在前面
iptables -L INPUT -n -v --line-numbers
# 规则优化建议:
# 1. ESTABLISHED 规则应该在第一条(命中率最高)
# 2. 高频端口规则在前(80, 443)
# 3. 低频端口规则在后
# 4. 使用集合/黑名单减少规则数量
17.7.2 conntrack 性能
# 查看 conntrack 统计
conntrack -S
# 关注以下字段:
# - new: 新建连接数
# - invalid: 无效包数(可能指示问题)
# - insert_failed: 插入失败数(表满时增加)
# - drop: 丢弃数
# 查看 hash 表性能
cat /proc/sys/net/netfilter/nf_conntrack_buckets
# 桶数应该 >= conntrack_max / 4
17.8 常见场景排查清单
17.8.1 场景:SSH 连接不上
□ 1. 确认 SSH 服务在运行:systemctl status sshd
□ 2. 确认端口在监听:ss -tlnp | grep :22
□ 3. 从本机测试:ssh localhost
□ 4. 检查防火墙规则:iptables -L INPUT -n | grep 22
□ 5. 检查默认策略:iptables -L INPUT -n | head -1
□ 6. 添加日志:iptables -I INPUT -p tcp --dport 22 -j LOG
□ 7. 检查 TCP wrappers:cat /etc/hosts.deny
□ 8. 检查 sshd 配置:cat /etc/ssh/sshd_config | grep -i listen
17.8.2 场景:Web 服务访问不了
□ 1. 确认服务在运行:systemctl status nginx
□ 2. 确认端口在监听:ss -tlnp | grep :80
□ 3. 从本机测试:curl http://localhost
□ 4. 检查 INPUT 链:iptables -L INPUT -n | grep :80
□ 5. 检查 FORWARD 链(如果是 Docker):iptables -L FORWARD -n
□ 6. 检查 DOCKER-USER 链:iptables -L DOCKER-USER -n
□ 7. 检查 NAT 规则:iptables -t nat -L -n | grep :80
□ 8. 抓包测试:tcpdump -i eth0 -n port 80
17.8.3 场景:无法访问外网
□ 1. 检查 DNS:nslookup google.com
□ 2. 检查路由:ip route show
□ 3. 检查 OUTPUT 链:iptables -L OUTPUT -n
□ 4. 检查 FORWARD 链(如果是网关):iptables -L FORWARD -n
□ 5. 检查 NAT 规则:iptables -t nat -L POSTROUTING -n
□ 6. 检查 IP 转发:sysctl net.ipv4.ip_forward
□ 7. 抓包测试:tcpdump -i eth0 -n host 8.8.8.8
17.9 注意事项
⚠️ 调试规则要及时清理:LOG 规则会影响性能,调试完成后务必删除。
# 调试完成后清理
iptables -D INPUT -p tcp --dport 80 -j LOG --log-prefix "DEBUG: "
iptables -X DEBUG_LOG
⚠️ 生产环境慎用:在生产环境添加调试规则前,评估日志量对性能的影响。
⚠️ 远程操作注意:如果通过 SSH 调试,确保不会因规则变更而断开连接。建议在另一个终端保持 SSH 会话,并设置超时自动恢复。
# 安全调试:设置超时自动恢复
iptables-save > /tmp/backup.rules
iptables -I INPUT -p tcp --dport 80 -j LOG
# 设置 5 分钟后自动恢复
(sleep 300 && iptables-restore < /tmp/backup.rules && echo "Rules restored") &
17.10 扩展阅读
| 资源 | 说明 |
|---|---|
man tcpdump | 抓包工具手册 |
man conntrack | 连接跟踪工具手册 |
man ss | Socket 统计工具手册 |
| Wireshark | 图形化抓包分析工具 |
本章小结
| 问题 | 排查方法 |
|---|---|
| 连接被拒绝 | 检查服务状态 + 防火墙规则 |
| 连接超时 | 检查 DROP 规则 + 抓包 |
| 规则不生效 | 检查规则顺序 + 匹配条件 |
| 端口映射失败 | 检查 IP 转发 + FORWARD 链 |
| conntrack 满 | 调整 max + 缩短超时 |
| NAT 不生效 | 清除 conntrack 缓存 |
下一章:第 18 章:最佳实践与运维规范,将总结生产环境的防火墙管理最佳实践。