在 OpenWrt 下配置「源进源出」的策略路由(使用 nftables)

Posted by

on

前言

前文中,我们在 OpenWrt 下为不同的内网区域通过策略路由提供了不同的外网出口。在一般情况下,它能工作的很好。

问题

设想这样一种场景

  • 有两个外网区域,分别为 A、B。他们对应的 ip rule 规则为“规则 A”、“规则 B”
  • 这两个区域都有公网 IP
  • 我们为 Server 区域指定了出口方向使用外网规则 A。其他区域默认使用外网规则 B
  • Server 提供了一个服务,现在想要让两个公网 IP 都能够访问,以求负载均衡
  • 端口转发已正确配置

结果

A 区域的公网 IP 能够正常访问内网提供的服务,但是 B 区域不能。

为什么

我们只要模拟一下数据包的走向便可以理解。

去程: [入站数据包]  — A 区域 —>  [OpenWrt] — DNAT Server 区域 —> [服务器]

回程: X  <— B 区域 —  [OpenWrt] <— DNAT 还原 — [服务器]

因 DNAT 还原后的原始 IP 是 A 区域的 IP,回程数据包可能会被 B 区域线路的运营商因安全原因(涉嫌 IP spoofing)最终丢弃。

解决方案

让数据包从哪里来,便从哪里回去。即:“源进源出”。好在内核里有 conntrack 模块,能方便我们对数据包做标记。

那么,基本思路是对入站数据包进行打标记,回程的时候对有标记的数据包走对应标记的出口送出即可。

参考网上的 iptables 版本方案

CONNMARK又是啥?
CONNMARK是用于标识一条有状态连接的MARK。

不同于之前所述针对单个数据包的FWMARK,CONNMARK标识的是一条连接,比如对于TCP和UDP就是一条四元组。

如何设置CONNMARK?
我们可以使用iptables的mangle表,对流入进来的单条connection设置mark。

例如:

ip6tables -t mangle -A PREROUTING -i ens192 -j CONNMARK –set-mark 4134

通过这条规则,我们就对从ens192网卡流入进来的连接设置了一个CONNMARK为4134,但这里的CONNMARK针对的是连接而不是数据包,因此这里并不能根据我们设置ip rule转发。

如何让CONNMARK生效为PACKET MARK?
iptables的mangle表,可以使用-m connmark来匹配已经设置的connmark规则,然后使用-j CONNMARK –restore-mark来将已有的connmark变为数据包的fwmark

例如:

ip6tables -t mangle -A OUTPUT -m connmark –mark 4134 -j CONNMARK –restore-mark

通过这条命令,我们就把从OUTPUT链中出去的,之前被打上CONNMARK 4134的连接的数据包打上了4134的FWMARK。

注:OUTPUT为本机发送的数据包,如果作为路由器转发其它网卡进来的数据包,可改为PREROUTING链。

然而,在较新版本(23.05 版本现在)中,OpenWrt 已经弃用 iptables,并已不再默认安装。

鉴于此时大多数 Linux 发行版本也默认采用了 nftables,我们将其转换成 nftables 的实现。

我们在 “/etc/nftables.d” 中,创建 “20-source-in-source-out.nft” 文件:

chain source_in_source_out {
    # Hook on mangle prerouting
    type filter hook prerouting priority mangle; policy accept;
    iifname "<线路 A 接口>" ct state new counter ct mark set 0x200;
    iifname "<线路 B 接口>" ct state new counter ct mark set 0x254;
    ct mark 0x200 counter meta mark set ct mark;
    ct mark 0x254 counter meta mark set ct mark;
}

从 iptables 到 nftables 的规则的转换可以借助 “iptables-translate” 来进行辅助。

上述 nftables 规则,针对线路 A 与线路 B进来的连接(四元组)进行分别打连接标记,并对已有连接标记的数据包进一步打 fwmark 标记,以便数据包回程时,ip rule 方便区分。

结合我的案例

最后我们还需要配置 ip rule 命令。该部分可以在 OpenWrt LuCI Web 中进行。

Source-In-Source-Out PBR in OpenWrt

结合前文,我的情况下,这四条规则的顺序是:

  1.  在抑制前缀 /0 的情况下查询 main 表,如果有内网路由命中则结束匹配
  2.  针对 “线路 A” 配置的源进源出,匹配到 “0x200” 标记的数据包会被送入 ipoe 表(外网 A)
  3.  针对 “线路 B” 配置的源进源出,匹配到 “0x254” 标记的数据包会被送入 main 表(外网 B)
  4. 默认情况下,server 区域的数据包会被送入 untrustpub 表(外网 C,用于 server 对外访问时所用的 IP,意在保护隐私)

数据包标记只需要在图形界面中的 “Firewall mark” 中填入即可。此时数据包标记的风格与 iptables 命令中的一致。

测试后,发现能够达到我所需要的源进源出效果,并同时能让指定区域走指定出口正常访问网络。

参考

  1. IP策略分流(国内外分流、运营商分流等)和源进源出在openwrt上的实现, https://www.right.com.cn/forum/thread-8294123-1-1.html
  2. Linux NAT 应用进阶(多链路输出+原路返回), https://blog.csdn.net/yk_wing4/article/details/90474164
  3. 使用CONNMARK解决Linux上源进源出问题, https://blog.cyyself.name/connmark/

2 responses to “在 OpenWrt 下配置「源进源出」的策略路由(使用 nftables)”

Leave a Reply

Your email address will not be published. Required fields are marked *