强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

第 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
查看 conntrackconntrack -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目的端口
SYNTCP 标志位

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 ssSocket 统计工具手册
Wireshark图形化抓包分析工具

本章小结

问题排查方法
连接被拒绝检查服务状态 + 防火墙规则
连接超时检查 DROP 规则 + 抓包
规则不生效检查规则顺序 + 匹配条件
端口映射失败检查 IP 转发 + FORWARD 链
conntrack 满调整 max + 缩短超时
NAT 不生效清除 conntrack 缓存

下一章第 18 章:最佳实践与运维规范,将总结生产环境的防火墙管理最佳实践。