使用 nftables 按需路由流量至虚拟网卡
这篇文章记录了在服务器上部署 Mihomo 时遇到的一个常见痛点:默认的 auto-route 机制会把全局流量都拉到 TUN 设备里,影响内网访问和部分运营商定向流量。最终的解决方案是手动关闭 Mihomo 的自动路由功能,改用 nftables 标记需要走代理的流量,再配合 ip rule/ip route 把这些带标记的数据包导向 Mihomo 的虚拟网卡。为了让规则在网卡创建后自动生效,还需要用 udev 监听设备事件并触发 Systemd 服务。
1. 关闭 Mihomo 的自动路由
Mihomo 的 auto-route 会自动在内核里写入策略路由规则,把大部分流量转发到 TUN。虽然简单,但:
- 规则不可控,无法过滤内网网段;
- 和宿主机上的其他路由策略可能冲突;
auto-route配置隐藏在 Mihomo 内部,排错成本高。
因此第一步就是显式关闭它,交还控制权给系统: 编辑 Mihomo 配置文件: nano /etc/mihomo/config.yaml
1 | tun: |
修改后重启 Mihomo:
systemctl restart mihomo
2. 用 nftables 标记需要走 Mihomo 的流量
2.1 了解 nftables
nftables 是 iptables 的继任者,核心优势是统一的表达式语法、更高性能和更好的状态查询能力。我们用它来实现「只标记特定目标地址」的需求:当数据包符合条件时,把 fwmark(防火墙标记)设为特定十六进制值,后面策略路由会读取这个标记。
2.2 清理旧防火墙并确认后端
Ubuntu 默认带有 ufw(一个 iptables 封装),为了避免规则冲突,先把 ufw 关闭并禁用启动:
1 | sudo systemctl stop ufw |
确认当前 Netfilter 后端已经切换到 nftables(输出应为 nf_tables):
1 | sudo update-alternatives --display iptables |
如果结果不是 nf_tables,可通过 update-alternatives --config iptables 手动选择,同时为 ip6tables 做同样切换。
2.3 编写 nftables 规则集
编辑 /etc/nftables.conf(这是 Systemd 默认加载的规则文件): nano /etc/nftables.conf
1 | #!/usr/sbin/nft -f |
几点说明:
prerouting链在数据包路由前执行,适合做标记;priority raw确保在连接跟踪之前执行;meta mark设置的fwmark会让后续策略路由捕获;return表示不用继续执行当前链,以免重复标记。
保存后立即加载规则:
1 | sudo nft -f /etc/nftables.conf |
启用系统服务,保证开机自动应用:
1 | sudo systemctl enable nftables |
排错技巧:
sudo nft list ruleset可以查看当前生效的完整规则,确认标记链是否存在;sudo nft monitor trace能实时观察匹配情况。
3. 用策略路由把标记流量导向 Mihomo
3.1 简述 ip rule / ip route
Linux 的策略路由(Policy Routing)通过 Routing Policy Database (RPDB) 与多路由表配合工作:
ip rule控制匹配条件(如源地址、接口、fwmark)以及命中后使用的路由表;ip route为每个路由表配置指向的网关、设备或黑洞行为。
我们需要做的是:当数据包携带 fwmark 0x135 时,改用自定义的 table 100,把它的默认路由指到 Mihomo TUN 接口。
3.2 创建 Systemd oneshot 单元
这样做的好处是将所有规则写在一个受管服务里,方便启动、停止和排错: nano /etc/systemd/system/mihomo-rules.service
1 | [Unit] |
执行以下命令让服务立即生效并设置为开机启动:
1 | sudo systemctl daemon-reload |
验证:
1 | ip rule show | grep 0x135 |
若输出中出现 lookup 100 以及指向 mtun 的默认路由,说明策略路由已加载成功。
4. 使用 udev 在 TUN 设备出现时刷新规则
4.1 为什么需要 udev
当 Mihomo 重启或系统在网络服务启动后才加载 mtun 设备时,策略路由可能在接口准备好之前就执行,导致路由表中缺少正确的 Nexthop。udev 是 Linux 设备管理层,用来监听内核发出的硬件/虚拟设备事件。我们可以写一条规则:一旦名为 mtun 的网络接口被创建,就重新执行策略路由服务。
4.2 编写 udev 规则
nano /etc/udev/rules.d/99-mihomo.rules
1 | ACTION=="add", SUBSYSTEM=="net", NAME=="mtun", RUN+="/bin/systemctl restart mihomo-rules.service" |
让规则立即生效:
1 | sudo udevadm control --reload |
如果 udevadm trigger 成功触发,可在 journalctl -u mihomo-rules.service -f 中看到服务被重启。
5. 验证流程
- 确认 nftables 标记: 在客户端对需要代理的目标发起连接,然后使用
sudo nft monitor trace验证prerouting-tun链是否被命中。 - 确认策略路由:
ip rule show和ip route show table 100应该分别显示 fwmark 规则和指向mtun的默认路由。 - 抓包验证: 使用
sudo tcpdump -i mtun观察是否有代理流量经过 Mihomo 的虚拟网卡。 - 回退机制: 如果需要临时停用策略,可运行
sudo systemctl stop mihomo-rules.service并执行sudo nft flush ruleset。
6. 常见问题与排查
- 流量无法访问外网: 检查
/etc/mihomo/config.yaml中的网关地址,确认198.18.0.1等 placeholder 是否替换成真实地址。 nft命令报错: 可能是语法不兼容旧版本内核,可用nft --check -f /etc/nftables.conf先验证。mtun设备不存在: 确保 Mihomo 配置tun.enable=true,并检查journalctl -u mihomo日志。- 策略路由未生效: 查看
journalctl -u mihomo-rules.service,确认服务运行是否报错;必要时在服务单元里加入ExecStartPre=/usr/bin/sleep 2等延时。
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
Link to this article: https://blog.axell.top/archives/%E4%BD%BF%E7%94%A8-nftables-%E6%8C%89%E9%9C%80%E8%B7%AF%E7%94%B1%E6%B5%81%E9%87%8F%E8%87%B3%E8%99%9A%E6%8B%9F%E7%BD%91%E5%8D%A1/
