iptables 与 nftables:防火墙到底在干什么

ufw 够用,但遇到「Docker 绕过防火墙」「规则不生效」这类问题,还是得懂底层。

一、结构:表 → 链 → 规则

bash
iptables
  ├─ filter 表(默认,做放行/拒绝)
  │    ├─ INPUT    进本机的包
  │    ├─ FORWARD 经过本机转发的包
  │    └─ OUTPUT  从本机发出的包
  ├─ nat 表(地址转换)
  │    ├─ PREROUTING / POSTROUTING / OUTPUT
  └─ mangle 表(改包头,较少用)

日常 90% 的操作都在 filter 表的 INPUT 链

二、包怎么走

bash
包到达网卡
  → PREROUTING (nat)        # 改目标地址(端口转发在这)
  → 路由判断:是给我的吗?
      ├─ 是 → INPUT (filter) → 本机进程
      └─ 否 → FORWARD (filter) → POSTROUTING (nat) → 转发出去
本机发出:OUTPUT (filter) → POSTROUTING (nat)

三、常用规则

bash
# 查看(-n 不反解域名,-v 详细,--line-numbers 带行号)
sudo iptables -L -n -v --line-numbers
sudo iptables -t nat -L -n -v

# 允许已建立的连接(这条几乎必须有)
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# 允许回环
sudo iptables -A INPUT -i lo -j ACCEPT

# 放行端口
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPT

# 允许 ping(可选)
sudo iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT

# 限制来源 IP
sudo iptables -A INPUT -p tcp --dport 3306 -s 10.0.0.5 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 3306 -j DROP

# 限流:每分钟最多 25 个新连接(防暴力破解)
sudo iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW \
  -m limit --limit 25/minute --limit-burst 100 -j ACCEPT

# 默认策略:拒绝所有进来的
sudo iptables -P INPUT DROP

插入与删除:

bash
sudo iptables -I INPUT 1 -p tcp --dport 8080 -j ACCEPT   # 插到第 1 条
sudo iptables -D INPUT 3                                  # 删第 3 条
sudo iptables -F                                          # 清空所有规则(危险)

修改默认策略为 DROP 之前,一定要先放行 SSH,否则立刻把自己锁在外面。

四、保存规则

iptables 规则默认在内存里,重启就没了:

bash
# Debian/Ubuntu
sudo apt install -y iptables-persistent
sudo netfilter-persistent save
# 规则存到 /etc/iptables/rules.v4

# 手动保存/恢复
sudo iptables-save > /etc/iptables/rules.v4
sudo iptables-restore < /etc/iptables/rules.v4

五、nftables 是新一代

Debian 11+ / Ubuntu 22.04+ 默认后端已经是 nftables,iptables 命令是兼容层。

bash
sudo nft list ruleset
nginx
# /etc/nftables.conf 片段
table inet filter {
    chain input {
        type filter hook input priority 0;
        policy drop;

        iif lo accept
        ct state established,related accept
        tcp dport { 22, 80, 443 } accept
        icmp type echo-request accept
    }
}

nftables 的优势:语法更简洁、支持集合({ 22, 80, 443 } 一条规则搞定)、IPv4/IPv6 统一管理(inet 族)。

六、ufw 其实是个包装器

bash
sudo ufw allow 22/tcp
sudo ufw deny 3306
sudo ufw status numbered
sudo ufw delete 3

它生成的规则最终还是落到 iptables/nftables:

bash
sudo iptables -L -n | head -20    # 能看到 ufw 创建的链

七、经典坑:Docker 绕过防火墙

现象:ufw deny 8080 了,但容器映射的 8080 端口外网照样能访问

原因:Docker 直接往 iptables 的 PREROUTINGFORWARD 里插规则,数据包在到达 ufw 的链之前就被转发走了。

解法几种:

  1. 只监听本机(最推荐):
yaml
ports:
  - "127.0.0.1:8080:80"      # 只有本机和 Nginx 反代能访问
  1. DOCKER-USER 链(Docker 专门留给用户插规则的地方):
bash
sudo iptables -I DOCKER-USER -i eth0 ! -s 10.0.0.0/8 -p tcp --dport 8080 -j DROP
  1. 关闭 Docker 的 iptables 操作(/etc/docker/daemon.json"iptables": false),代价是容器网络要自己管。

我的习惯:容器端口一律只映射 127.0.0.1,对外统一由 Nginx 反代。这样防火墙规则就是唯一入口,也少一个暴露面。

八、检查清单

bash
# 对外暴露了哪些端口
sudo ss -lntp
sudo iptables -L INPUT -n -v

# 从外网扫一遍(用另一台机器或在线工具)
nmap -Pn your-server-ip
  • 默认策略是 DROP
  • 只放行必要端口
  • 数据库、缓存端口不对外
  • 容器映射只绑 127.0.0.1
  • 规则已持久化

防火墙的核心思路就一句:默认拒绝,按需放行。