背景
在 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
|
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 前加入:
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 规则是否命中
观察 match-set xxx_ipv4 dst 计数。
3. 实际路径验证
什么时候需要 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 时代,真正稳定的方案往往是: