
在 Linux 网络世界中，`Netfilter`、`iptables` 和 `nftables` 的关系常被简化为"新旧工具"。但要真正掌握 Linux 网络，需要深入到内核的 **钩子机制 (Hooks)**、**状态跟踪 (Conntrack)** 的哈希结构以及 **nftables 的字节码虚拟机 (VM)**。

为了方便理解，我们可以把 Linux 内核想象成一家大型公司的 **收发室**，所有的网络数据包（Packet）就是进出的 **快递件**。

---

## 1. Netfilter：内核协议栈的"钩子"矩阵

Netfilter 是一套内核中的规章制度，它在协议栈中设立了 5 个必经的 **检查站（Hooks）**。当数据包穿过协议栈（如 `ip_input.c` 或 `ip_output.c`）时，内核会调用对应的回调函数。

### 五大 Hook 点与流量流向

```mermaid
flowchart TD
    NIC_IN["网卡收包
eth0 ingress"]
    PRE["PREROUTING
大门口"]
    ROUTE1{"路由判断"}
    LOCAL_IN["LOCAL_IN
办公室门"]
    FORWARD["FORWARD
转运台"]
    POST["POSTROUTING
装车点"]
    APP["本机进程
nginx / sshd …"]
    LOCAL_OUT["LOCAL_OUT
寄件处"]
    NIC_OUT["网卡发包
eth1 egress"]
    ROUTE2{"路由判断"}

    NIC_IN --> PRE
    PRE --> ROUTE1
    ROUTE1 -- "目的 = 本机" --> LOCAL_IN
    ROUTE1 -- "目的 = 其他主机" --> FORWARD
    LOCAL_IN --> APP
    APP --> LOCAL_OUT
    LOCAL_OUT --> ROUTE2
    ROUTE2 --> POST
    FORWARD --> POST
    POST --> NIC_OUT

    style PRE fill:#4a90d9,color:#fff
    style LOCAL_IN fill:#4a90d9,color:#fff
    style FORWARD fill:#4a90d9,color:#fff
    style LOCAL_OUT fill:#4a90d9,color:#fff
    style POST fill:#4a90d9,color:#fff
    style ROUTE1 fill:#f5a623,color:#fff
    style ROUTE2 fill:#f5a623,color:#fff
```

每个 Hook 对应的典型用途：

1. **PREROUTING**：数据包刚进入网络栈，路由决策之前。这是做 DNAT（修改目的地址）的唯一正确位置，因为路由是根据目的地址决定转发方向的，必须先改地址再路由。
2. **LOCAL_IN**：路由决定包发给本机进程之后。用于本机防火墙过滤入站流量。
3. **FORWARD**：包只是路过，路由决定转发到其他接口。用于路由器或网关场景。
4. **LOCAL_OUT**：本机进程产生的外发包，路由决策之前。
5. **POSTROUTING**：包即将从网卡发出，路由决策之后。这是做 SNAT/Masquerade（修改源地址）的位置。

三种典型数据包路径：

```
# 路径一：外部流量到本机进程（如访问本机 80 端口）
eth0 → PREROUTING → [路由：目的=本机] → LOCAL_IN → nginx

# 路径二：外部流量穿越主机（如路由器转发）
eth0 → PREROUTING → [路由：目的=其他] → FORWARD → POSTROUTING → eth1

# 路径三：本机进程发出流量（如 curl 请求）
curl → LOCAL_OUT → [路由] → POSTROUTING → eth0
```

### 优先级 (Priority)

同一个 Hook 点可以注册多个功能模块，内核按优先级（数值从小到大）依次调用。以下是 Linux 内核头文件 `linux/netfilter_ipv4.h` 定义的标准优先级常量：

| 优先级数值 | 名称 | 说明 |
| ---: | :--- | :--- |
| -400 | CONNTRACK_DEFRAG | IP 分片重组（必须最先） |
| -300 | RAW | iptables raw 表，跳过 conntrack |
| -225 | SELINUX_FIRST | SELinux 第一阶段 |
| -200 | CONNTRACK | 连接追踪建立，识别包的身份 |
| -150 | MANGLE | 修改包头（TTL、TOS、Mark） |
| -100 | NAT_DST (DNAT) | 修改目的地址 |
| 0 | FILTER | 放行或丢弃决策 |
| 50 | SECURITY | 安全策略（SELinux/AppArmor） |
| 100 | NAT_SRC (SNAT) | 修改源地址（完成 NAT） |
| 300 | CONNTRACK_HELPER | conntrack helper（FTP、H.323 等） |
| MAXINT | CONNTRACK_CONFIRM | 将新连接正式写入哈希表 |

为什么 SNAT 优先级（100）必须在 FILTER（0）之后？因为我们要先决定这个包是否放行，才有必要修改它的源地址。如果 FILTER 先丢弃了包，SNAT 就完全没有执行的意义。

为什么 CONNTRACK（-200）必须在 FILTER（0）之前？因为 FILTER 中的 `-m conntrack --ctstate ESTABLISHED` 规则依赖 conntrack 已经识别了包的状态，conntrack 必须先运行。

### IPv6 的 Hook 体系

IPv6 拥有完全独立但结构对称的 Hook 体系（`NF_INET_*` 系列同时覆盖 IPv4/IPv6）。`nftables` 通过 `inet` 地址族同时挂载两套协议栈，避免重复写规则。

---

## 2. Conntrack：状态防火墙的"记忆体"

**Conntrack（连接追踪）** 是检查站旁边的 **"出入登记簿"**，它记录了谁正在和谁通话。这使得防火墙可以区分"主动建立的连接"和"可疑的无状态包"。

### 核心结构：元组 (Tuple)

内核用哈希表维护所有活跃连接。每个连接记录（`nf_conn`）由两个方向的 **Tuple（五元组）** 组成：

```
ORIGINAL Tuple（原始方向）:  saddr=10.0.0.1  daddr=8.8.8.8  sport=54321  dport=53  proto=UDP
REPLY Tuple  （回程方向）:   saddr=8.8.8.8   daddr=10.0.0.1  sport=53     dport=54321 proto=UDP
```

这两个 Tuple 都作为哈希键存入哈希表，所以无论是去程包还是回程包，内核都能以 O(1) 时间找到对应的连接记录。

### 读取连接追踪表

```bash
# 方法一：直接读取 proc 文件（原始格式）
cat /proc/net/nf_conntrack

# 方法二：使用 conntrack 工具（需安装 conntrack-tools）
conntrack -L

# 过滤查看特定协议
conntrack -L -p tcp
conntrack -L -p udp

# 查看连接总数
conntrack -C
```

`/proc/net/nf_conntrack` 的一行典型输出（TCP ESTABLISHED）：

```
ipv4  2  tcp  6  86399  ESTABLISHED
  src=10.0.0.1  dst=93.184.216.34  sport=54321  dport=443
  src=93.184.216.34  dst=10.0.0.1  sport=443  dport=54321
  [ASSURED]  mark=0  zone=0  use=2
```

字段解读：

| 字段 | 说明 |
| :--- | :--- |
| `ipv4` | L3 协议族 |
| `2` | L3 协议号（2 = IPv4） |
| `tcp` | L4 协议名 |
| `6` | L4 协议号（6 = TCP） |
| `86399` | 该条目距超时的剩余秒数 |
| `ESTABLISHED` | TCP 内部状态（仅 TCP 有） |
| `src=10.0.0.1` | 原始方向：客户端 IP |
| `dst=93.184.216.34` | 原始方向：服务器 IP |
| `sport=54321` | 原始方向：客户端端口 |
| `dport=443` | 原始方向：服务器端口 |
| `src=93.184.216.34` | 回程方向：服务器 IP |
| `dst=10.0.0.1` | 回程方向：客户端 IP |
| `sport=443` | 回程方向：服务器端口 |
| `dport=54321` | 回程方向：客户端端口 |
| `[ASSURED]` | 双向都有流量，连接稳定（不会因内存压力被提前淘汰） |
| `[UNREPLIED]` | 只见到单向流量（连接未被服务器回应） |
| `mark=0` | 数据包 nfmark（可被 mangle 设置，用于策略路由） |
| `zone=0` | conntrack zone（用于 VPN 等多路复用场景） |
| `use=2` | 内核内部引用计数 |

UDP 连接没有状态字段，仅凭超时判断是否活跃：

```
ipv4  2  udp  17  28
  src=10.0.0.1  dst=8.8.8.8  sport=53891  dport=53
  src=8.8.8.8  dst=10.0.0.1  sport=53  dport=53891
  mark=0  zone=0  use=2
```

### 四大 Conntrack 状态

这四个状态是 Netfilter 对 L4 协议做的 **高层抽象**，不是 TCP 状态机里的 SYN_SENT 等：

**NEW**：收到连接的第一个包，但尚未看到回程包。对 TCP 而言，就是 SYN 包。此时连接记录刚创建，标记为 `[UNREPLIED]`。

```bash
# 只允许由内网主动发起的 TCP 新连接
iptables -A FORWARD -m conntrack --ctstate NEW -i eth1 -o eth0 -j ACCEPT
```

**ESTABLISHED**：收到了回程包，双向通信已确认。TCP 是看到 SYN+ACK 后转入此状态。后续 99% 的数据包只需查哈希表就能放行，绕过所有复杂过滤规则，极大节省 CPU。

```bash
# 放行所有已建立的连接（几乎所有防火墙规则集都有这条）
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
```

**RELATED**：与已有连接关联的新连接。最典型的例子是 FTP 数据通道和 ICMP 错误报文。

FTP 协议本身的工作方式导致了 RELATED 的存在：

```
主动模式（PORT）：
  客户端  ─── 控制连接 TCP:21 ──►  服务器  (ESTABLISHED)
  客户端  ◄── 数据连接 TCP:20 ───  服务器  (RELATED，服务器主动发起)

被动模式（PASV）：
  客户端  ─── 控制连接 TCP:21 ──►  服务器  (ESTABLISHED)
  客户端  ─── 数据连接 TCP:高端口 ► 服务器  (RELATED)
```

内核模块 `nf_conntrack_ftp` 会解析 FTP 控制连接中的 PORT/PASV 命令，提前在 **期望表（expectation table）** 中登记将要出现的数据连接。当数据连接到达时，内核在期望表里找到匹配，将其标记为 RELATED 而不是 NEW，从而可以放行。

```bash
# 加载 FTP conntrack helper
modprobe nf_conntrack_ftp
# 之后即可用一条规则放行 FTP 数据通道
iptables -A INPUT -m conntrack --ctstate RELATED -j ACCEPT
```

**INVALID**：包的状态异常，如 TCP 标志位组合非法、不属于任何已知连接的 ACK 包、序列号错误等。应当直接丢弃：

```bash
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP
```

### TCP 内部状态机

conntrack 在 ESTABLISHED 状态下，内部还维护着更细粒度的 TCP 状态，可用于精确过滤：

| TCP 状态 | 说明 |
| :--- | :--- |
| `SYN_SENT` | 客户端发出 SYN，等待 SYN+ACK |
| `SYN_RECV` | 服务器发出 SYN+ACK，等待 ACK |
| `ESTABLISHED` | 三次握手完成 |
| `FIN_WAIT` | 一方发出 FIN，开始关闭 |
| `CLOSE_WAIT` | 收到 FIN，等待本端关闭 |
| `LAST_ACK` | 被动关闭方发出 FIN，等待最后的 ACK |
| `TIME_WAIT` | 等待网络中残留包消散（默认 120 秒） |
| `CLOSE` | 连接完全关闭 |

用 conntrack 工具可以直接看到这些状态：

```bash
conntrack -L -p tcp --tcp-flags SYN_SENT
```

### 性能调优

高并发场景下如果登记簿写满（`nf_conntrack_count` >= `nf_conntrack_max`），内核会丢弃新连接，`dmesg` 中出现 `nf_conntrack: table full, dropping packet`。

```bash
# 查看当前连接数与上限
sysctl net.netfilter.nf_conntrack_count
sysctl net.netfilter.nf_conntrack_max

# 查看哈希桶大小
cat /sys/module/nf_conntrack/parameters/hashsize

# 调整上限（建议值：服务器内存 / 16KB，例如 16GB 内存 → 约 1M 条目）
sysctl -w net.netfilter.nf_conntrack_max=1048576
# 对应哈希桶设为 max/4（减少碰撞同时节省内存）
echo 262144 > /sys/module/nf_conntrack/parameters/hashsize

# 持久化到 /etc/sysctl.d/99-conntrack.conf
cat >> /etc/sysctl.d/99-conntrack.conf << 'EOF'
net.netfilter.nf_conntrack_max = 1048576
EOF

# 调整超时（默认 TCP ESTABLISHED 超时高达 432000 秒 = 5 天！）
# 对于高并发 HTTP/HTTPS 服务，通常 1 小时足够
sysctl -w net.netfilter.nf_conntrack_tcp_timeout_established=3600

# TIME_WAIT 状态超时（默认 120 秒，一般不需要修改）
sysctl net.netfilter.nf_conntrack_tcp_timeout_time_wait

# UDP 超时（DNS 查询用 30 秒足够，但 UDP 流媒体可能需要更长）
sysctl -w net.netfilter.nf_conntrack_udp_timeout=30
sysctl -w net.netfilter.nf_conntrack_udp_timeout_stream=120
```

哈希表内存占用估算：

```
内存 = hashsize × 8 字节（桶指针）+ max × ~360 字节（每条连接记录）
示例：hashsize=262144, max=1048576
     = 262144 × 8 + 1048576 × 360
     ≈ 2 MB + 360 MB
     ≈ 362 MB
```

---

## 3. iptables：硬编码的线性匹配

**iptables** 就像一个查阅 **纸质活页夹** 的老保安，办事严谨但手段原始。它将规则组织在固定的 **表（Table）** 和 **链（Chain）** 中。

### 表的层次与链的可用性

iptables 有 5 张表，每张表只在特定 Hook 上生效：

| 表名 | 可用链 | 典型用途 |
| :--- | :--- | :--- |
| `raw` | PREROUTING, OUTPUT | 跳过 conntrack（NOTRACK） |
| `mangle` | 所有五条链 | 修改 TTL、TOS、MARK |
| `nat` | PREROUTING, INPUT, OUTPUT, POSTROUTING | 地址转换（DNAT/SNAT） |
| `filter` | INPUT, FORWARD, OUTPUT | 放行/丢弃（主防火墙） |
| `security` | INPUT, FORWARD, OUTPUT | SELinux 安全标记 |

同一 Hook 上多张表的执行顺序（以 PREROUTING 为例）：

```
数据包到达 PREROUTING Hook
    │
    ├─► raw 表 (-300)        ← 最先执行，可设 NOTRACK 跳过 conntrack
    ├─► mangle 表 (-150)     ← 修改包头
    └─► nat 表 (-100)        ← DNAT 改目的地址
```

### 规则结构

每条 iptables 规则 = 匹配条件 + 目标动作：

```bash
iptables -t <表> -A <链> <匹配条件> -j <目标>

# 目标类型：
# ACCEPT  放行，继续后续处理
# DROP    静默丢弃，发送方不知道被丢了
# REJECT  拒绝，向发送方回一个 ICMP 错误或 TCP RST
# LOG     记录到 syslog，然后继续匹配下一条规则
# RETURN  退出当前链，返回调用链
# DNAT    修改目的地址（仅 nat 表）
# SNAT    修改源地址（仅 nat 表）
# MASQUERADE  动态 SNAT（出口 IP 动态获取，适合 DHCP 场景）
```

### 示例一：基础有状态防火墙

这是 Linux 服务器最常见的防火墙模板：

```bash
# 清空现有规则
iptables -F
iptables -X

# 默认策略：INPUT 和 FORWARD 拒绝，OUTPUT 放行
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT

# 放行本地回环
iptables -A INPUT -i lo -j ACCEPT

# 丢弃状态异常的包（防止各种欺骗攻击）
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP

# 放行已建立/关联的连接（这条必须在 DROP 规则之前）
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# 放行 ICMP ping（可选，有助于诊断问题）
iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT

# 放行 SSH（仅来自管理网段）
iptables -A INPUT -s 192.168.10.0/24 -p tcp --dport 22 \
    -m conntrack --ctstate NEW -j ACCEPT

# 放行 HTTP/HTTPS
iptables -A INPUT -p tcp -m multiport --dports 80,443 \
    -m conntrack --ctstate NEW -j ACCEPT

# 记录并丢弃其余所有包（日志在默认策略 DROP 之前触发）
iptables -A INPUT -j LOG --log-prefix "[FW-DROP] " --log-level 4
```

查看规则（带序号，方便插入和删除）：

```bash
iptables -L INPUT -n -v --line-numbers
```

输出示例：

```
Chain INPUT (policy DROP 0 packets, 0 bytes)
num  pkts bytes target     prot opt in     out     source               destination
1       5   300 ACCEPT     all  --  lo     *       0.0.0.0/0            0.0.0.0/0
2       0     0 DROP       all  --  *      *       0.0.0.0/0            0.0.0.0/0  ctstate INVALID
3    1523  2.1M ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0  ctstate RELATED,ESTABLISHED
4       2   120 ACCEPT     tcp  --  *      *       192.168.10.0/24      0.0.0.0/0  tcp dpt:22 ctstate NEW
5      87  4.2K ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0  multiport dports 80,443 ctstate NEW
```

### 示例二：NAT 路由器（共享上网）

场景：内网 `192.168.1.0/24`，通过 `eth0`（公网 IP）出口上网：

```bash
# 开启内核 IP 转发（必须！）
echo 1 > /proc/sys/net/ipv4/ip_forward
# 持久化
echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.d/99-forwarding.conf

# MASQUERADE：自动使用 eth0 当前 IP 作为源地址（适合 DHCP 动态 IP）
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j MASQUERADE

# 如果公网 IP 是静态的，用 SNAT 更高效（避免每包查询出口 IP）
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j SNAT --to-source 1.2.3.4

# 放行内网到公网的转发
iptables -A FORWARD -i eth1 -o eth0 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT
iptables -A FORWARD -i eth0 -o eth1 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
```

### 示例三：端口转发（DNAT）

场景：将公网 8080 端口的流量转发到内网服务器 `192.168.1.10:80`：

```bash
# 在 PREROUTING 改目的地址（路由决策之前，确保包被路由到正确接口）
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 8080 \
    -j DNAT --to-destination 192.168.1.10:80

# 放行转发的流量
iptables -A FORWARD -d 192.168.1.10 -p tcp --dport 80 \
    -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT

# 如果本机也要访问转发的服务（同一台机器发出 → 需要 OUTPUT 中的 DNAT）
iptables -t nat -A OUTPUT -p tcp --dport 8080 \
    -j DNAT --to-destination 192.168.1.10:80
```

验证 NAT 规则：

```bash
# 查看 nat 表所有规则
iptables -t nat -L -n -v

# 实时查看匹配计数
watch -n1 'iptables -t nat -L PREROUTING -n -v'
```

### 示例四：防暴力破解（连接速率限制）

```bash
# 创建自定义链处理 SSH 速率限制
iptables -N SSH_CHECK

# 使用 recent 模块：60 秒内超过 5 次 NEW 连接则封锁
iptables -A SSH_CHECK -m recent --name SSH_BLOCK --update \
    --seconds 60 --hitcount 5 -j DROP
iptables -A SSH_CHECK -m recent --name SSH_BLOCK --set -j ACCEPT

# 将 SSH 新连接导入自定义链
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j SSH_CHECK
```

### 规则持久化

```bash
# Debian/Ubuntu
apt install iptables-persistent
iptables-save > /etc/iptables/rules.v4
ip6tables-save > /etc/iptables/rules.v6
# 重启后自动加载

# RHEL/CentOS
service iptables save
# 或
iptables-save > /etc/sysconfig/iptables
```

### 线性匹配的性能问题

iptables 采用 **线性顺序匹配**，每个数据包依次比对每条规则，直到命中为止。当规则数量大时，后面的规则命中延迟高。例如：

| 规则数量 | 每包比对次数（最坏情况） |
| ---: | :--- |
| 100 条 | 100 次比对 |
| 1000 条 | 1000 次比对 |
| 10000 条 | 10000 次比对（Kubernetes 集群的典型规模） |

此外，添加或删除规则时，iptables 需要将整个规则集 dump 出来、修改、再整体写回内核，这在规则频繁变化的场景（如 Kubernetes Service）下会造成明显的锁竞争和延迟。

---

## 4. nftables：基于寄存器的虚拟机 (VM)

**nftables** 是那个用 **电脑数据库** 办公的新生代保安。它在内核中实现了一个轻量级的 **字节码虚拟机**，用来解释规则引擎生成的指令。从 Linux 3.13（2014 年）开始合入主线，Debian 10+、RHEL 8+、Ubuntu 20.04+ 均已默认使用。

### 虚拟机架构

nftables 内核模块维护 16 个 128 位的 **寄存器**。匹配一个规则的流程是：

```
1. 提取（load）：从数据包头部读取字段（如 ip daddr）→ 存入寄存器 reg[1]
2. 比较（cmp）：将 reg[1] 与目标值比较
3. 判决（verdict）：写入判决寄存器 → ACCEPT / DROP / CONTINUE
```

这种设计的核心优势：规则被编译为紧凑的字节码，内核逐条执行字节码指令，而不是像 iptables 那样调用一长串函数指针。对于简单规则，字节码执行非常快。

查看某个规则的字节码（需要调试内核或使用 `nft --debug` 选项）：

```bash
nft --debug=netlink add rule inet filter input ip saddr 1.2.3.4 drop
```

### 层次结构：Table → Chain → Rule

nftables 没有 iptables 那样预定义的表和链，一切都是用户自己创建的：

```
Table（地址族 + 名字）
  └── Chain（类型 + Hook + 优先级 + 默认策略）
        └── Rule（匹配条件 + 动作）
              └── Set / Map（可被多条规则引用的数据结构）
```

地址族（address family）：

| 地址族 | 说明 |
| :--- | :--- |
| `ip` | 仅 IPv4 |
| `ip6` | 仅 IPv6 |
| `inet` | 同时覆盖 IPv4 和 IPv6（最常用） |
| `arp` | ARP 协议 |
| `bridge` | 网桥层 |
| `netdev` | 网卡入口（ingress），最早介入，可做 XDP 前的过滤 |

### 完整规则集示例

以下是一个生产可用的完整 `/etc/nftables.conf`，包含防火墙 + NAT：

```bash
#!/usr/sbin/nft -f

# 清空所有现有规则
flush ruleset

# ── 防火墙 ──────────────────────────────────────────────────
table inet filter {

    # 被封锁的 IP 集合（动态更新，带超时自动过期）
    set blocked_ips {
        type ipv4_addr
        flags dynamic, timeout
        timeout 1h
        comment "fail2ban 或手动封锁的 IP，1 小时后自动解封"
    }

    # 允许入站的 TCP 端口集合
    set allowed_tcp_ports {
        type inet_service
        flags interval
        elements = { 22, 80, 443, 8080-8090 }
    }

    chain input {
        type filter hook input priority filter
        policy drop                        # 默认丢弃

        iif "lo" accept                    # 回环必须放行

        ct state invalid drop              # 丢弃异常状态包
        ct state { established, related } accept  # 已建立连接放行

        # ICMP（允许 ping 和 MTU 探测）
        ip  protocol icmp   icmp  type { echo-request, destination-unreachable, time-exceeded } accept
        ip6 nexthdr  icmpv6 icmpv6 type { echo-request, nd-neighbor-solicit, nd-router-advert } accept

        # 检查封锁列表（在允许规则之前）
        ip saddr @blocked_ips drop

        # 允许的 TCP 端口（包含端口范围）
        tcp dport @allowed_tcp_ports ct state new accept

        # SSH 速率限制（防暴力破解）
        tcp dport 22 ct state new \
            limit rate over 5/minute burst 10 packets \
            log prefix "[SSH-RATELIMIT] " drop

        # 记录其他被丢弃的包（供审计）
        log prefix "[FW-DROP] " flags all
    }

    chain forward {
        type filter hook forward priority filter
        policy drop

        ct state { established, related } accept
        ct state invalid drop

        # 允许内网流量出站
        iif "eth1" oif "eth0" ct state new accept
    }

    chain output {
        type filter hook output priority filter
        policy accept
        # 出站默认放行；如有需要可在此限制本机对外连接
    }
}

# ── NAT ─────────────────────────────────────────────────────
table ip nat {

    chain prerouting {
        type nat hook prerouting priority dstnat

        # 端口转发：将 8080 → 内网 192.168.1.10:80
        iif "eth0" tcp dport 8080 dnat to 192.168.1.10:80
    }

    chain postrouting {
        type nat hook postrouting priority srcnat

        # 内网共享出口（动态 MASQUERADE）
        oif "eth0" ip saddr 192.168.1.0/24 masquerade
    }
}
```

应用规则集：

```bash
# 语法检查（不实际加载）
nft -c -f /etc/nftables.conf

# 加载
nft -f /etc/nftables.conf

# 查看当前规则集（带计数器）
nft list ruleset

# 开机自动加载（systemd）
systemctl enable nftables
```

### 集合 (Sets)：O(1) 匹配的核心

nftables 原生支持哈希集合，这是相对 iptables 最显著的性能提升：

```bash
# 创建一个 IP 集合（立即生效，不需重载规则集）
nft add set inet filter blocked_ips { type ipv4_addr \; }

# 批量添加元素
nft add element inet filter blocked_ips { 1.2.3.4, 5.6.7.8, 9.10.11.12 }

# 在规则中引用（@ 前缀）
nft add rule inet filter input ip saddr @blocked_ips drop

# 带超时的自动过期（例如 fail2ban 临时封锁）
nft add element inet filter blocked_ips { 1.2.3.4 timeout 30m }

# 删除单个元素
nft delete element inet filter blocked_ips { 1.2.3.4 }

# 查看集合内容
nft list set inet filter blocked_ips
```

集合支持多种类型和复合键：

```bash
# 端口范围集合（带 interval flag）
nft add set inet filter admin_ports {
    type inet_service
    flags interval
    elements = { 22, 8000-8999 }
}

# 带地址族的复合集合（IP + 端口对）
nft add set inet filter rate_limit_keys {
    type ipv4_addr . inet_service
    flags dynamic, timeout
    timeout 1m
    size 65536
}

# 在规则中用复合键
nft add rule inet filter input \
    ip saddr . tcp dport @rate_limit_keys \
    ct state new drop
```

**性能对比**：无论集合中有 10 个还是 100,000 个 IP，nftables 都通过哈希查找在 **O(1)** 时间完成匹配。iptables 使用 `ipset` 扩展也能做到 O(1)，但需要额外工具，且与 iptables 规则语法集成度较低。

### 字典映射 (Maps) 与判决映射 (Verdict Maps)

Maps 是 nftables 最强大的功能之一，允许用数据包字段直接查表得到动作或值：

```bash
# 判决映射：根据源 IP 决定动作
nft add map inet filter ip_actions {
    type ipv4_addr : verdict
    elements = {
        192.168.1.1 : accept,
        10.0.0.1    : drop,
        172.16.0.1  : continue,
    }
}
nft add rule inet filter input ip saddr vmap @ip_actions

# 端口转发映射：将外部端口映射到内网 IP:端口
nft add map ip nat port_forward {
    type inet_service : ipv4_addr . inet_service
    elements = {
        8080 : 192.168.1.10 . 80,
        8443 : 192.168.1.10 . 443,
        3306 : 192.168.1.20 . 3306,
    }
}
nft add rule ip nat prerouting \
    iif "eth0" \
    dnat ip addr . port to tcp dport map @port_forward
```

这种写法将多个端口转发规则压缩为一条规则 + 一个 Map，Map 查询是 O(1)，不管有多少个映射条目，性能恒定。

### Flowtable：绕过钩子的软件快速路径

对于长连接（如大文件传输），每个数据包仍然要走完整的 Netfilter Hook 链，即使规则只有"放行已建立连接"。**Flowtable** 通过在内核网络设备层直接转发已建立的 TCP/UDP 流，完全绕过 iptables/nftables 的规则处理：

```bash
table inet filter {
    flowtable ft {
        hook ingress priority 0
        devices = { eth0, eth1 }  # 两个接口之间的转发流量走快速路径
    }

    chain forward {
        type filter hook forward priority filter
        policy drop

        ct state { established, related } accept

        # 将已建立的 TCP/UDP 流卸载到 flowtable
        ip  protocol { tcp, udp } ct state established flow add @ft
        ip6 nexthdr  { tcp, udp } ct state established flow add @ft
    }
}
```

Flowtable 的效果：连接建立后，后续数据包直接在网卡驱动层转发，延迟更低，CPU 占用显著减少，适合大带宽路由场景。

### 从 iptables 迁移到 nftables

```bash
# 1. 导出现有 iptables 规则
iptables-save  > /tmp/iptables.rules
ip6tables-save > /tmp/ip6tables.rules

# 2. 自动翻译为 nftables 语法（iptables-translate 或 iptables-restore-translate）
iptables-restore-translate  -f /tmp/iptables.rules  > /tmp/nftables-from-v4.conf
ip6tables-restore-translate -f /tmp/ip6tables.rules >> /tmp/nftables-from-v4.conf

# 3. 检查生成的规则（翻译不一定 100% 准确，需人工审核）
cat /tmp/nftables-from-v4.conf

# 4. 语法检查
nft -c -f /tmp/nftables-from-v4.conf

# 5. 停用 iptables，启用 nftables
systemctl stop  iptables
systemctl disable iptables
systemctl enable --now nftables
```

单条规则转换（可用于理解语法差异）：

```bash
iptables-translate -A INPUT -p tcp --dport 443 -j ACCEPT
# 输出：nft add rule ip filter INPUT tcp dport 443 counter accept

iptables-translate -t nat -A POSTROUTING -o eth0 -j MASQUERADE
# 输出：nft add rule ip nat POSTROUTING oifname "eth0" counter masquerade
```

---

## 5. 常见调试命令速查

```bash
# ── Conntrack ───────────────────────────────────────────────
conntrack -L                          # 列出所有连接
conntrack -L -p tcp --dport 80        # 过滤 HTTP 连接
conntrack -D -s 1.2.3.4              # 删除指定源 IP 的连接（踢掉某个客户端）
conntrack -E                          # 实时监听连接事件（类似 tcpdump）
conntrack -C                          # 显示当前连接总数

# ── iptables ────────────────────────────────────────────────
iptables -L -n -v --line-numbers      # 查看 filter 表规则（带统计）
iptables -t nat -L -n -v             # 查看 nat 表
iptables -Z                           # 清零所有计数器（用于流量统计）
iptables -D INPUT 3                  # 按行号删除规则

# ── nftables ────────────────────────────────────────────────
nft list ruleset                      # 查看所有规则
nft list table inet filter            # 查看指定表
nft list chain inet filter input      # 查看指定链
nft list set inet filter blocked_ips  # 查看集合内容
nft monitor                           # 实时监听规则变更事件
nft -j list ruleset | jq .           # JSON 格式输出（便于脚本处理）

# ── 排查数据包路径 ──────────────────────────────────────────
# 在 raw 表跟踪特定数据包（nftables 方式）
nft add table inet debug_trace
nft add chain inet debug_trace prerouting { type filter hook prerouting priority -350\; }
nft add rule  inet debug_trace prerouting ip saddr 1.2.3.4 meta nftrace set 1
nft monitor trace   # 实时显示匹配过程

# 完成后清理
nft delete table inet debug_trace
```

---

## 6. 总结

| 特性 | Netfilter | iptables | nftables |
| :--- | :--- | :--- | :--- |
| 角色 | 基础设施层（Hook/Conntrack） | 经典执行引擎 | 现代 VM 引擎 |
| 匹配效率 | - | 线性匹配 O(n) | 哈希/集合 O(1) |
| 规则语言 | C API | 命令行参数拼接 | 结构化 DSL |
| 表/链 | 预定义固定结构 | 预定义固定结构 | 用户自定义 |
| IP 版本 | IPv4 + IPv6 分离 | 分两套命令 | inet 族统一 |
| 动态规则更新 | - | 全量替换（有锁竞争） | 原子增删元素 |
| 集合/映射 | 需外挂 ipset | 需外挂 ipset | 原生支持 |
| 状态追踪 | 核心支持（Conntrack） | 依赖 conntrack 模块 | 依赖 conntrack 模块 |
| Flowtable | - | 不支持 | 原生支持 |
| 内核版本要求 | 2.4+ | 2.4+ | 3.13+ |
| 推荐场景 | 内核开发/深层调优 | 维护旧系统 | 现代服务器/高性能防火墙 |

理解这套架构的本质：Netfilter 是不可绕过的基础设施，Conntrack 是有状态防火墙的记忆，iptables 和 nftables 只是在这个基础上的两种不同的 **规则执行引擎**。选择 nftables 的核心理由不是"新"，而是原生集合（O(1) 匹配）、原子更新（无锁竞争）和统一的 IPv4/IPv6 规则。

