banner
约 1,800 字
6 分钟

把 iptables 学明白

摘要

本文总结了 Linux iptables 的核心概念与实战应用。尽管 ufw 等工具更简单,但 iptables 在 Docker、Kubernetes 等复杂场景及底层网络原理中不可或缺。文章详细讲解了表、链、规则及数据流向,提供了家庭网关搭建脚本。此外,还介绍了调试技巧(如查看计数、LOG)、常见错误(如漏放 ESTABLISHED)、规则持久化及 Docker 集成注意事项。

iptables 是 Linux 上最让人头大的工具之一。此文是我学了一年多之后的总结,不是 man page 翻译,是"为什么这样设计"和"实战怎么用"。

为什么学 iptables

ufw / firewalld 不是更简单吗?,但:

  1. ufw 在复杂场景下还是会让你直接写 iptables 规则

  2. Docker 端口映射用的就是 iptables,不学就看不懂 docker 的网络行为

  3. Kubernetes / 各种云上 VPC,本质都是 iptables / nftables

  4. 调试时 iptables -L -v 看到的规则数比 ufw status 详细得多

所以,早晚要学,早学早解脱。

核心概念

表(Table)

iptables 里有 5 张表,最常用的两个:

  • filter:包过滤(默认表),决定包"过不过"

  • nat:网络地址转换(DNAT/SNAT/MASQUERADE)

其他:

  • mangle:改包头(TTL、TOS、MARK)

  • raw:连接跟踪豁免(高性能场景)

  • security:SELinux 联动

链(Chain)

每张表里有几条内置链:

  • PREROUTING:路由决策前(DNAT)

  • INPUT:发给本机的包

  • FORWARD:转发的包(路由决策后)

  • OUTPUT:本机发出的包

  • POSTROUTING:路由决策后(SNAT / MASQUERADE)

记忆口诀:PRE-/POST- 看路由决策的"前/后",INPUT/FORWARD/OUTPUT 看包的目的。

规则(Rule)和目标(Target)

规则:匹配条件 + 动作

目标:

  • ACCEPT:放行

  • DROP:丢弃,不响应

  • REJECT:拒绝,回 RST/ICMP unreachable

  • LOG:记日志,继续下一条

  • SNAT/DNAT/MASQUERADE:NAT

  • RETURN:跳出当前链,回上一层

匹配条件(Match)

常见:

  • -p tcp --dport 22:TCP 目标端口 22

  • -s 192.168.1.0/24:源 IP 段

  • -i eth0:入接口

  • -m state --state ESTABLISHED,RELATED:连接状态

  • -m multiport --dports 80,443:多端口

  • -m iprange --src-range 192.168.1.100-192.168.1.200:IP 范围

数据流图(最关键的)

纯文本
本机进程

                           │ (OUTPUT)


   入站   ┌──────────────┐  ┌──────────────┐  出站
   网卡 → │ PREROUTING   │→│ 路由决策     │→│ POSTROUTING → 出站网卡
          │ (DNAT)        │ │ (查路由表)  │ │ (SNAT/MASQUERADE)
          └──────┬────────┘ └──────┬──────┘ └──────┬────────┘
                 │                  │                │
                 │           ┌──────┴──────┐         │
                 └─→ INPUT  │              │ FORWARD │
                          ▼              ▼         ▼
                      本机进程        转发        转发
                                       包         包


                                             目的主机

关键点

  • 本机进程的包走 OUTPUT → POSTROUTING

  • 转发的包走 FORWARD → POSTROUTING

  • 发给本机的包走 INPUT

  • DNAT 在 PREROUTING,SNAT/MASQUERADE 在 POSTROUTING

实战:搭一个家庭网关

目标:

  • 外网网卡 eth0(公网 IP)

  • 内网网卡 eth1(192.168.1.1/24)

  • 内网机器能通过网关访问外网(NAT)

  • 外网能 SSH 进网关(22 端口)

  • 内网能 SSH 进网关

  • 外网能访问网关的 80 / 443(web 服务)

  • 拒绝其他所有入站

bash
#!/bin/bash
# /opt/firewall.sh - 家庭网关防火墙规则
# 重启前先在 cron 里加个 5 分钟自动解锁脚本,避免锁死

EXT_IF=eth0
INT_IF=eth1
INT_NET=192.168.1.0/24

# 清空
iptables -F
iptables -t nat -F
iptables -X

# 默认策略:DROP 入站 + FORWARD,ACCEPT 出站
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT

# 1. lo 全放
iptables -A INPUT -i lo -j ACCEPT

# 2. 已建立的连接放行(关键)
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT

# 3. ICMP(ping)
iptables -A INPUT -p icmp -j ACCEPT

# 4. 内网 SSH
iptables -A INPUT -i $INT_IF -p tcp --dport 22 -j ACCEPT

# 5. 外网 SSH(限定一个 IP 段,限速失败锁定用 fail2ban)
iptables -A INPUT -i $EXT_IF -p tcp --dport 22 -j ACCEPT

# 6. web 服务
iptables -A INPUT -i $EXT_IF -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -i $EXT_IF -p tcp --dport 443 -j ACCEPT

# 7. NAT:内网访问外网
iptables -t nat -A POSTROUTING -s $INT_NET -o $EXT_IF -j MASQUERADE

# 8. 转发允许(NAT 完才能转发)
iptables -A FORWARD -i $INT_IF -o $EXT_IF -j ACCEPT
# 已建立的回包(在第 2 条)

调试:定位"为什么不通"

这是最重要的部分。规则不生效时怎么排查?

1. 看规则命中计数

bash
iptables -L -v -n
# pkts bytes target     prot opt in     out     source               destination
# 1234  98K ACCEPT     all  --  lo     *       0.0.0.0/0            0.0.0.0/0
#  567  456K ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0            state RELATED,ESTABLISHED
#    0     0 ACCEPT     tcp  --  eth0   *       0.0.0.0/0            0.0.0.0/0            tcp dpt:22

# pkts 列的计数表示这条规则匹配过多少包
# 如果某条规则计数一直 0 而你期望它匹配,就说明条件不对

2. 临时插 LOG 规则

bash
# 在 INPUT 链最前面加 LOG
iptables -I INPUT 1 -j LOG --log-prefix "[INPUT-DROP] " --log-level 4
# 试一下从外网 ping
ping your-public-ip
# 看内核日志
sudo dmesg -T | tail
# [INPUT-DROP] IN=eth0 OUT= ... SRC=8.8.8.8 DST=... PROTO=ICMP ...

注意:LOG 不会停止匹配,所以配合下面这样用:

bash
# 先 LOG 再 DROP
iptables -I INPUT 1 -p tcp --dport 9999 -j LOG --log-prefix "[WHY-DROP]"
iptables -I INPUT 2 -p tcp --dport 9999 -j DROP

3. 跟踪连接

bash
# conntrack:看当前活动连接
sudo conntrack -L | grep "dport=22"
# tcp  6  47 ESTABLISHED src=192.168.1.20 dst=1.2.3.4 sport=54321 dport=22 src=1.2.3.4 dst=192.168.1.20 sport=22 dport=54321 [ASSURED]

# 看是否有STUNPLACEHOLDER000001STUN
sudo conntrack -L | grep "SYN_SENT"
# 这个值在涨 = 握手没回应,规则挡了或端口没起

4. nftables(iptables 的替代)

新一代 Linux 防火墙,语法更清晰,未来要切

bash
# 查看 nft 规则(系统可能自动转了)
sudo nft list ruleset

# 安装 nftables
sudo apt install -y nftables
sudo systemctl enable --now nftables

但目前还是 iptables 为主流,学完 iptables 再看 nft 不难

常见错误

  1. 默认策略忘了改:默认 ACCEPT 改 DROP 时,第一条规则一定是放行自己的 SSH,不然被锁外。

  2. ESTABLISHED 漏了:返回包被拦,看似建立了连接实际超时

  3. FORWARD 没允许:NAT 配了但转发没开,内网上不了外网

  4. INPUT 链有 eth0 的规则但忘了 ACCEPT lo:本地服务互相连不上。

  5. 规则没持久化:重启就丢。我每次写完都用 iptables-save > /etc/iptables.rules

持久化

bash
# Debian / Ubuntu
sudo apt install -y iptables-persistent
sudo netfilter-persistent save

# 或者手动
sudo iptables-save | sudo tee /etc/iptables.rules
# /etc/network/if-pre-up.d/iptables
cat > /etc/network/if-pre-up.d/iptables <<'EOF'
#!/bin/sh
iptables-restore < /etc/iptables.rules
EOF
sudo chmod +x /etc/network/if-pre-up.d/iptables

# Arch
sudo pacman -S iptables
sudo iptables-save | sudo tee /etc/iptables/iptables.rules
sudo systemctl enable --now iptables.service

配合 docker 的坑

docker 启动时会自己改 iptables 规则(创建 DOCKER-USER 链、添加 NAT)。如果你手写的规则和 docker 的冲突:

  • docker 会改 FORWARD

  • docker 会加 iptables -t nat -A POSTROUTING

  • 重启 docker 服务会重置部分自定义规则

调试时用:

bash
docker network ls
# 看 docker 创建的网络
docker network inspect bridge
# 看具体规则

# 自定义 docker iptables 行为
# /etc/docker/daemon.json
{
  "iptables": true   # 默认 true,改成 false docker 就不管 iptables 了
}

总结

iptables 的设计哲学是"如果某条规则命中,停止匹配;否则继续"。理解这一点,加上"5 张表 5 条链"的位置关系,基本就入门了。

剩下的就是多写多踩。我前 10 次配防火墙都至少有一个规则没写对,每次都学到东西。

参考

END