5.5 iptables/nftables 防火墙实战
预计阅读时间:14 分钟
📖 目录
学习目标
学完本章后,你将能够:
- 理解 netfilter 框架和 nftables 的表/链/规则层级结构
- 编写 nftables 基础过滤规则(输入/转发/输出链策略)
- 配置 NAT(SNAT/DNAT/MASQUERADE)实现共享上网和端口转发
- 使用 conntrack 工具监控连接跟踪表
- 配置持久化 nftables 规则集和 ufw 前端
核心知识
- netfilter——Linux 内核网络栈中的包过滤框架,提供 5 个钩子(hook)点
- 表(table)——nftables 的顶级容器,按功能分类(filter/nat/route 等)
- 链(chain)——规则的集合,必须关联到钩子才能生效
- 规则(rule)——由匹配条件(match)+ 动作(verdict)组成
- 连接跟踪(conntrack,Connection Tracking 的缩写)——跟踪每条流的状态,让防火墙感知连接语境。类似于"记住每个会话的上下文",使防火墙能区分新连接、已建立连接和关联连接
- NAT——SNAT(改源 IP)、DNAT(改目标 IP)、MASQUERADE(动态 SNAT)
- 地址族——inet(IPv4+IPv6)、ip(仅 IPv4)、ip6(仅 IPv6)
知识关联
- 前置知识:4.5:网络故障排查 网络故障排查、1.7:用户与权限管理 用户与权限
- 后续影响:防火墙是 5.1:Linux 安全加固 安全加固的核心组件,也是 4.5:网络故障排查 网络故障排查的基础
- 深入阅读:5.10:WireGuard VPN WireGuard VPN(依赖防火墙放行)、6.10:云网络基础 云网络基础
原理讲解
为什么防火墙要分表/链/规则三层
iptables/nftables 的表-链-规则分层设计源于 Netfilter 项目的架构决策。Linux 内核需要在网络栈的多个位置(PREROUTING、INPUT、FORWARD、OUTPUT、POSTROUTING)拦截数据包,但不同位置的包处理逻辑差异很大——例如 NAT 转换必须在路由决策之前完成(PREROUTING),而包过滤可以在路由之后。如果把所有规则混在一起,既难以管理也难以优化性能。因此 Netfilter 引入了"表"的概念:filter 表负责包过滤、nat 表负责地址转换、mangle 表负责包修改、raw 表负责连接跟踪前处理。每张表只关心自己职责范围内的操作,内核可以按优先级并行处理不同表的规则,提高了吞吐量。
链(chain)对应 Netfilter 的钩子点,是规则的容器。规则按顺序匹配,一旦命中立即执行动作(accept/drop/return/jump),不再检查后续规则。这种"先匹配先处理"的设计简化了规则编写——你不需要为每条规则指定完整条件链,只需在合适的链中按顺序添加规则即可。
为什么推荐用 nftables 而不是 iptables
iptables 诞生于 1998 年,其底层基于 x_tables 框架,存在几个结构性问题:规则存储在内核内存中,每条规则独立评估,大量规则时性能线性下降;IPv4/IPv6 规则需要分别管理;没有原子化的规则集更新机制(修改规则时可能短暂中断连接)。nftables(2008 年引入)从底层重新设计:使用字节码虚拟机评估规则,支持集合(set)和映射(map)进行 O(1) 查找,规则集作为原子整体加载,且原生支持 IPv4+IPv6 双栈(inet 地址族)。性能方面,nftables 在规则数量超过 100 条时显著优于 iptables;在规则集更新时也不会中断现有连接。
netfilter 钩子与包处理流程
netfilter 在内核网络栈中嵌入 5 个钩子点(hook),每个数据包会依次经过这些钩子:
入站包路径:
[PREROUTING] → [路由决策] → [INPUT] → 本地进程
转发包路径:
[PREROUTING] → [路由决策] → [FORWARD] → [POSTROUTING]
出站包路径:
本地进程 → [OUTPUT] → [路由决策] → [POSTROUTING]
表/链遍历顺序
nftables 在同一钩子上可以有多个表,通过链的 priority 决定遍历顺序(数值越小越先执行):
| 表 | 钩子 | 典型 priority | 用途 |
|---|---|---|---|
| raw | prerouting, output | -300 | 连接跟踪前处理 |
| filter | input, forward, output | 0 | 包过滤 |
| nat | prerouting, output, postrouting | -100 / 100 | NAT 转换 |
连接跟踪(conntrack)
conntrack 记录每条流的状态,防火墙据此识别新连接、已建立连接和相关连接。状态包括:NEW(新连接请求)、ESTABLISHED(双向可见)、RELATED(关联连接,如 FTP 数据连接)、INVALID(无法识别)。没有 conntrack,防火墙只能对每个包独立决策,无法实现有状态过滤。
NAT 的完整处理流程
NAT 并不是一个独立动作,而是拆成两段协作:DNAT(目的地址转换)发生在 prerouting 钩子——数据包刚进网卡、还没做路由决策时改写目标地址;SNAT/MASQUERADE(源地址转换)发生在 postrouting 钩子——数据包即将发出网卡前改写源地址。中间的路由决策看到的是 DNAT 之后的目标,所以转发链上的过滤规则要匹配转换后的地址。
外网访问内网 Web 服务器(公网 203.0.113.10:8080 → 内网 10.0.1.10:80):
包进入 eth0
→ prerouting: tcp dport 8080 dnat to 10.0.1.10:80 # 目标被改写
→ 路由决策: 目标现在是内网地址,走转发路径
→ forward: ct state new,established accept # 放行转发
→ postrouting: ip saddr 10.0.0.0/8 masquerade # 源被改写(回程包才能回来)
→ 发出 eth1 到达内网服务器
一个常见误区:DNAT 只做"单程"改写,回程包需要反向转换。conntrack 会自动为已建立的连接维护反向 NAT 条目,前提是回程包能命中 ct state established,related 的放行规则——所以 forward 链的 established,related 规则是 NAT 能工作的前提。
firewalld 与 ufw 对比
三者都是 netfilter 的前端,但设计哲学不同:nftables 是"直接写规则集"的原生工具;ufw 定位"简单防火墙",隐藏了大部分细节;firewalld 定位"动态防火墙",引入 zone(区域)概念管理不同信任级别的网络。
| 维度 | nftables | firewalld | ufw |
|---|---|---|---|
| 底层引擎 | 直接操作 nft | nftables 规则集 | nftables 规则集 |
| 配置模型 | 表/链/规则 | Zone(区域)+ 服务 | 简单的 allow/deny 命令 |
| 热加载 | 需重新加载规则集 | 规则即时生效,不中断连接 | 规则即时追加 |
| 默认发行版 | Arch、Gentoo、手动安装 | RHEL/CentOS/Fedora | Ubuntu/Debian |
| 适用人群 | 运维/安全工程师 | 需要 zone 管理的主机防火墙 | 新手或桌面主机 |
生产环境选型建议:RHEL 系直接用 firewalld(与 NetworkManager 集成良好),Ubuntu 服务器可用 ufw 快速兜底,但需要精细控制(NAT、限速、集合)时应直接使用 nftables——无论哪个前端,最终都要能读懂 nft list ruleset 的输出。
示例代码
1. nftables 基础——表/链/规则
# 查看当前规则集
nft list ruleset
# 输出: table inet filter { ... }
# 创建表
nft add table inet filter
# 创建基础链
nft add chain inet filter input { type filter hook input priority 0\; }
nft add chain inet filter forward { type filter hook forward priority 0\; policy drop\; }
nft add chain inet filter output { type filter hook output priority 0\; policy accept\; }
# 添加规则
nft add rule inet filter input tcp dport 22 accept
nft add rule inet filter input ct state established,related accept
# 插入规则到指定位置(index 从 0 开始)
nft insert rule inet filter input index 0 tcp dport 443 accept
# 删除规则
nft delete rule inet filter input handle 3 # handle 通过 nft -a list 查看
# 清空表
nft flush table inet filter
2. 过滤规则——服务器防火墙模板
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
ct state established,related accept
iif lo accept
icmp type { echo-request, echo-reply } accept
icmpv6 type { echo-request, echo-reply, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert } accept
tcp dport { 22, 80, 443 } accept
tcp dport 3306 ip saddr 10.0.0.0/8 accept
tcp dport 6379 ip saddr 10.0.0.0/8 accept
log prefix "NFT_INPUT_DROP: " counter drop
}
chain forward {
type filter hook forward priority 0; policy drop;
ct state established,related accept
}
chain output {
type filter hook output priority 0; policy accept;
}
}
3. NAT/SNAT/DNAT 配置
# NAT 表
table inet nat {
chain prerouting {
type nat hook prerouting priority -100;
tcp dport 2222 dnat to 10.0.1.50:22
tcp dport 8080 dnat to 10.0.1.10:80
}
chain postrouting {
type nat hook postrouting priority 100;
ip saddr 10.0.0.0/8 oif eth0 masquerade
ip saddr 172.16.0.0/12 oif eth0 masquerade
ip saddr 192.168.0.0/16 oif eth0 masquerade
}
}
# 启用 IP 转发(NAT 必需)
sysctl -w net.ipv4.ip_forward=1
4. 端口转发实战
# 主机 80 端口 → 本地 8080 服务
nft add rule inet nat prerouting tcp dport 80 dnat to 127.0.0.1:8080
# 公网 2222 → 内网 10.0.1.50:22
nft add rule inet nat prerouting tcp dport 2222 dnat to 10.0.1.50:22
# HTTPS → 内部 8443
nft add rule inet nat prerouting tcp dport 443 dnat to 10.0.1.20:8443
nft list table inet nat
# 输出:
# table inet nat {
# chain prerouting { ... }
# chain postrouting { ... }
# }
5. 持久化配置
# 保存当前规则集
nft list ruleset > /etc/nftables.conf
# 使用 systemd 自动加载
systemctl enable --now nftables
# 手动加载
nft -f /etc/nftables.conf
# 验证加载
nft list ruleset | head -5
# 输出: table inet filter { ... }
6. ufw——nftables 前端
# ufw 底层生成 nft 规则到 inet ufw-* 表
ufw status verbose
# 常用命令
ufw enable
ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp
ufw allow 80/tcp
ufw deny 23
ufw allow from 10.0.0.0/8 to any port 3306
ufw status numbered
ufw delete 3
ufw disable
# 应用配置
ufw app list
ufw allow 'Nginx Full'
# 查看 ufw 生成的底层规则(ufw 经 iptables-nft 后端注册,表为 ip/ip6 filter)
nft list table ip filter
7. 命名集合与元素管理
# 命名集合:把允许的 IP 抽出来单独维护
nft add table inet filter
nft add set inet filter allowed_ipv4 { type ipv4_addr\; flags interval\; }
nft add set inet filter blocked_port { type inet_service\; }
# 向集合添加元素
nft add element inet filter allowed_ipv4 { 10.0.0.0/8, 192.168.1.0/24 }
nft add element inet filter blocked_port { 23, 3389 }
# 集合在规则中引用(维护时只需增删元素,不改规则)
nft add rule inet filter input ip saddr @allowed_ipv4 accept
nft add rule inet filter input tcp dport @blocked_port drop
# 查看集合与元素
nft list set inet filter allowed_ipv4
# 输出: table inet filter { set allowed_ipv4 { type ipv4_addr ... elements = { 10.0.0.0/8, 192.168.1.0/24 } } }
# 动态封禁脚本思路(配合 cron 或 fail2ban)
# nft add element inet filter blocked_ip { 203.0.113.66 }
8. 流量限速与日志策略
# 限制 SSH 每分钟新连接数(防暴力破解)
nft add rule inet filter input \
tcp dport 22 ct state new limit rate 5/minute accept
# 更精细:用 meter 按源 IP 限速(每个 IP 独立计数)
nft add rule inet filter input \
tcp dport 22 ct state new meter ssh_bruteforce \
{ ip saddr limit rate 5/minute } accept
# 超出限制的进入 log + drop
nft add rule inet filter input \
tcp dport 22 ct state new meter ssh_bruteforce \
{ ip saddr limit rate 5/minute } \
log prefix "SSH_RATE_DROP: " counter drop
# 丢弃前记录 ICMP 泛洪(每 10 秒最多 3 条日志)
nft add rule inet filter input icmp type echo-request \
limit rate 3/second burst 10 \
log prefix "ICMP_LOG: " accept
9. firewalld 快速上手
# 查看默认区域与已开放服务
firewall-cmd --get-default-zone
firewall-cmd --list-all
# 开放端口/服务(--permanent 持久化)
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-port=8080/tcp
firewall-cmd --reload
# 指定源区域:仅允许内网访问 3306
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.0.0.0/8" port port="3306" protocol="tcp" accept'
firewall-cmd --reload
# 查看防火墙底层 nft 规则
nft list table inet firewalld
常见错误
| 错误表现 | 根因 | 正确做法 |
|---|---|---|
| SSH 连接被自己加的规则切断 | nft 命令立即生效,链 policy drop 且未先放行 22 端口 | 先写入文件用 nft -f 加载,或开两个终端确保逃生通道 |
| DNAT 端口转发不生效 | 未启用 net.ipv4.ip_forward | sysctl -w net.ipv4.ip_forward=1 并写入 sysctl.conf |
| MASQUERADE 后内网无法上网 | FORWARD 链默认 drop 且未放行转发流量 | forward 链添加 ct state established,related accept |
| NAT 规则意外匹配了 DNS 查询 | DNAT 规则未限制目的 IP,DNS 查询也被转发 | DNAT 规则增加 ip daddr 限制目标地址 |
| conntrack table full, dropping packet | 连接跟踪表耗尽,默认 65536 条上限 | 增大 nf_conntrack_max 或缩短超时时间 |
nft 报 Error: syntax error, unexpected newline | 命令行中分号未转义 | 分号前加反斜杠:priority 0\; |
| DNAT 只做了半套(内网能通外网不通) | 回程包没有匹配 established 规则或源地址未转换 | forward 链保留 ct state established,related accept;确认 postrouting 的 masquerade 覆盖回程网段 |
| 修改集合后规则不生效 | 元素修改后未保存/未重新加载文件 | 交互式改动用 nft add element;文件方式改完执行 nft -f |
| firewalld 与 nftables 规则互相覆盖 | 两套前端同时操作同一内核钩子,优先级冲突 | 同一主机只保留一个前端;直接用 nft flush ruleset 会清掉 firewalld 的规则 |
| limit 限速对暴力破解无效 | limit 是全局计数,攻击者换源 IP 即绕过 | 使用 meter 按源 IP 限速,或配合 fail2ban 封禁 |
误执行 nft flush ruleset 导致所有规则丢失 | flush ruleset 清空全部表/链/规则,包括正在使用的防火墙策略 | 修改前先 nft list ruleset > /tmp/backup.nft 备份;用 nft -f 从文件恢复 |
最佳实践
| 实践 | 原理 | 示例 |
|---|---|---|
| 默认拒绝 + 白名单开放 | 最小权限——未明确允许的一律拒绝 | input 链 policy drop 后逐条放行必要端口 |
| 使用 inet 地址族代替 ip/ip6 | 一份规则同时覆盖 IPv4 和 IPv6 | table inet filter 而非 table ip filter |
| 先写文件再用 nft -f 加载 | 避免中间状态导致连接断开 | 编辑 /etc/nftables.conf → nft -f |
| 善用集合(set)简化规则 | 减少规则数量、提高可读性 | tcp dport { 80, 443, 8080 } accept |
| 规则中加入 log + counter | 排错时快速定位被丢弃的流量 | log prefix "NFT_DROP: " counter drop |
| 监控 conntrack 使用率 | 表满导致随机丢包,极难排查 | 监控 nf_conntrack_count / nf_conntrack_max |
| 用命名集合管理可变地址 | IP 变动只增删元素,不改规则、不重载整个规则集 | nft add set ... flags interval + nft add element |
| NAT 规则尽量收敛 | 过宽的 DNAT 会劫持无关流量(如 DNS 被转发) | DNAT 加 ip daddr 限制目标公网 IP,仅对指定端口生效 |
| 规则文件用 Git 管理 | 防火墙变更可审计、可回滚 | /etc/nftables.conf 纳入版本控制,变更走 PR 评审 |
练习题
- (概念)nftables 的
inet地址族和ip地址族有什么区别?推荐使用哪个? - (概念)MASQUERADE 和 SNAT 的适用场景分别是什么?为什么动态 IP 场景下应优先使用 MASQUERADE?
- (实操)编写
/etc/nftables.conf,实现:默认拒绝所有入站,放行 SSH/HTTP/HTTPS,允许已建立连接和相关连接。加载后用nft list ruleset验证。 - (实操)配置 DNAT 将主机 8080 端口转发到 127.0.0.1:80,开启 IP 转发。用
curl localhost:8080验证。 - (🔍 挑战)搭建 Linux 路由器:eth0 公网、eth1 内网,配置 MASQUERADE 让内网主机上网,限制内网仅可访问 80/443。用
conntrack -L验证连接跟踪。
点击查看答案
- (概念)
inet地址族同时支持 IPv4 和 IPv6,一张表覆盖双栈;ip仅支持 IPv4。推荐使用inet,简化配置且未来兼容。 - (概念)SNAT 固定修改源 IP 为指定地址,适合出口 IP 固定的场景;MASQUERADE 自动获取出口接口的当前 IP,适合动态 IP(如 PPPoE 拨号、DHCP 接口)。动态 IP 下若用 SNAT,IP 变化后规则失效。
- (实操)参考 chapter 内的 base ruleset 模板。验证命令:
nft list ruleset应显示三条 base chain 规则(input/output/forward)加一条 ct state 放行规则。 - (实操)
nft add rule ip nat prerouting tcp dport 8080 redirect to :80,确认net.ipv4.ip_forward=1。curl 应返回 Nginx 默认页。 - (🔍)eth0 配置 MASQUERADE,eth1 上限制
tcp dport { 80, 443 } accept后 drop 其余。conntrack -L 应看到 ESTABLISHED 条目。
学习检查点
学完本章后,请检验自己是否掌握以下内容:
| 检查项 | 自测问题 | 验证方法 |
|---|---|---|
| 概念理解 | 能用自己的话解释 nftables 的表、链、规则层次结构 | 尝试向他人讲解 |
| 命令操作 | 能不查文档完成 nftables 规则编写、表和链的创建删除 | 在终端实际执行 |
| 原理掌握 | 能说出 nftables 相比 iptables 的优势和数据包处理流程 | 画出流程图 |
| 故障排查 | 能独立排查 nftables 规则导致网络不通的问题 | 模拟故障并修复 |
| 最佳实践 | 能说明为什么推荐使用 nftables 而非 iptables | 对比不同方案 |
本章总结
nftables 是现代 Linux 防火墙的标准配置接口,统一了 IPv4/IPv6 语法,性能优于 iptables。核心理念是有状态包过滤——借助 conntrack 记录连接语境,大幅简化规则编写。NAT 分为 SNAT(改源)、DNAT(改目标)、MASQUERADE(动态 SNAT),端口转发需要配合 IP 转发。
速查表
| 命令/操作 | 用途 |
|---|---|
nft list ruleset | 查看全部规则 |
nft add table inet <name> | 创建表 |
nft add rule ... tcp dport 80 accept | 放行端口 |
nft -f /etc/nftables.conf | 从文件加载规则 |
nft list ruleset > /etc/nftables.conf | 保存规则集 |
nft add rule ... dnat to 10.0.1.50:22 | DNAT 端口转发 |
nft add rule ... masquerade | MASQUERADE |
sysctl -w net.ipv4.ip_forward=1 | 启用 IP 转发 |
conntrack -L | 查看连接跟踪表 |
conntrack -S | 查看连接跟踪统计 |
ufw allow 80/tcp | UFW 放行端口 |
学习路径建议
- 学完本章后建议阅读 5.1:Linux 安全加固 Linux 安全加固(将防火墙纳入整体安全策略)
- 进阶可学习 5.10:WireGuard VPN WireGuard VPN(依赖防火墙放行 VPN 流量)
- 专业运维可深入学习 eBPF/XDP 实现高性能包处理(5.12:eBPF 基础 eBPF 基础)
延伸阅读
- nftables 官方 Wiki
- netfilter/iptables 项目官网
- Ubuntu 防火墙文档
- Arch Wiki: nftables
- 推荐书籍:《Linux 防火墙(第 4 版)——Steve Suehring》