Featured image of post nftables 时代的 mwan3 + ipset 分流

nftables 时代的 mwan3 + ipset 分流

背景

在 fw4(nftables)时代,很多关于 mwan3 + ipset 的教程仍然沿用 fw3 / iptables 的思路:

  • /etc/config/firewall 里声明 ipset
  • 每次更新地址表就 reload firewall
  • helper / 脚本 / mwan3 职责混杂

这些做法在新架构下并非必须,反而容易带来不稳定性和维护成本

本文整理一套 fw4 时代可长期使用、工程上可接受的 mwan3 + ipset 分流方案,在“理论正确”和“现实可用”之间做出明确取舍。


设计原则

  • ipset 只负责数据
  • mwan3 负责策略与选路
  • firewall 不参与 ipset 管理
  • 地址表可频繁更新
  • 更新过程尽量不中断已有连接

地址表准备

统一放置在:

1
2
3
4
/etc/ipset/
├── ct.txt
├── cm.txt
└── glb.txt

文件格式:

1
2
3
4
# 注释
1.0.8.0/21
8.8.8.8
223.5.5.5/32
  • 一行一个 IP 或 CIDR
  • 支持空行和注释

ipset 自动加载脚本(核心)

该脚本只负责一件事:

将 txt 文件内容同步到内核 ipset 中

并在最后 重载 mwan3,确保运行态规则立即重新评估。

/usr/bin/ipset-load.sh

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#!/bin/sh
set -eu

load_set() {
  SET="$1"
  FILE="$2"

  [ -f "$FILE" ] || return 0

  awk -v SET="$SET" '
  BEGIN {
    print "create " SET " hash:net family inet -exist"
    print "flush "  SET
  }
  NF && $1 !~ /^#/ {
    print "add " SET " " $1 " -exist"
  }' "$FILE" | ipset restore
}

load_set ct_ipv4  /etc/ipset/ct.txt
load_set cm_ipv4  /etc/ipset/cm.txt
load_set glb_ipv4 /etc/ipset/glb.txt

# 关键:重载 mwan3(不重启接口)
/etc/init.d/mwan3 reload

exit 0

启用脚本:

1
chmod +x /usr/bin/ipset-load.sh

手动测试:

1
2
/usr/bin/ipset-load.sh
ipset list ct_ipv4 | head

ipset 的“持久化”

ipset 内容本身不会自动持久化,这是内核设计。

正确做法是 开机自动恢复

编辑 /etc/rc.local,在 exit 0 前加入:

1
/usr/bin/ipset-load.sh

mwan3 配置结构(最小集合)

interface(出口健康检测)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
config interface 'ct'
    option enabled '1'
    option family 'ipv4'
    list track_ip '223.5.5.5'

config interface 'cm'
    option enabled '1'
    option family 'ipv4'
    list track_ip '223.6.6.6'

config interface 'glb'
    option enabled '1'
    option family 'ipv4'
    list track_ip '8.8.8.8'

member(policy 与 interface 的桥梁)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
config member 'ct_m1'
    option interface 'ct'
    option metric '1'
    option weight '1'

config member 'cm_m1'
    option interface 'cm'
    option metric '1'
    option weight '1'

config member 'glb_m1'
    option interface 'glb'
    option metric '1'
    option weight '1'

policy(选路策略)

1
2
3
4
5
6
7
8
config policy 'ct_only'
    list use_member 'ct_m1'

config policy 'cm_only'
    list use_member 'cm_m1'

config policy 'glb_only'
    list use_member 'glb_m1'

rule(ipset → policy)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
config rule
    option name 'glb_match'
    option ipset 'glb_ipv4'
    option use_policy 'glb_only'

config rule
    option name 'ct_match'
    option ipset 'ct_ipv4'
    option use_policy 'ct_only'

config rule
    option name 'cm_match'
    option ipset 'cm_ipv4'
    option use_policy 'cm_only'

规则顺序非常重要
glb → ct → cm → default


firewall:刻意不参与

在 fw4 时代:

  • ❌ 不需要在 /etc/config/firewall 声明 ipset
  • ❌ 不需要为地址表更新 reload firewall
  • mwan3 生成的 nft 规则直接引用内核 ipset

验证方式

1. ipset 是否加载成功

1
ipset list glb_ipv4 | head

2. mwan3 规则是否命中

1
mwan3 status rules

观察 match-set xxx_ipv4 dst 计数。


3. 实际路径验证

1
traceroute 8.8.8.8

什么时候需要 reload / restart?

操作 是否需要
更新地址表 ❌(脚本已处理)
运行 ipset-load.sh
修改 mwan3 rule / policy / member
排错 手动

常见误区

1. 必须在 firewall 里声明 ipset

fw4 + mwan3 场景下并非必须,反而增加复杂度。

2. 更新地址表就要 reload firewall

这是旧时代习惯,会导致连接中断。

3. ipset 属于 firewall

实际上 ipset 是独立的内核数据结构。

4. helper 是“官方正确方式”

在 fw4 时代,简单脚本反而更透明、可控。


总结

ipset 是数据
mwan3 是策略
firewall 是执行器

在 fw4 时代,真正稳定的方案往往是:

  • 配置更少
  • 职责更清晰
  • 行为更可预期
使用 Hugo 构建
主题 StackJimmy 设计