前言
在前文中,我们在 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 中进行。
结合前文,我的情况下,这四条规则的顺序是:
- 在抑制前缀 /0 的情况下查询 main 表,如果有内网路由命中则结束匹配
- 针对 “线路 A” 配置的源进源出,匹配到 “0x200” 标记的数据包会被送入 ipoe 表(外网 A)
- 针对 “线路 B” 配置的源进源出,匹配到 “0x254” 标记的数据包会被送入 main 表(外网 B)
- 默认情况下,server 区域的数据包会被送入 untrustpub 表(外网 C,用于 server 对外访问时所用的 IP,意在保护隐私)
数据包标记只需要在图形界面中的 “Firewall mark” 中填入即可。此时数据包标记的风格与 iptables 命令中的一致。
测试后,发现能够达到我所需要的源进源出效果,并同时能让指定区域走指定出口正常访问网络。
参考
- IP策略分流(国内外分流、运营商分流等)和源进源出在openwrt上的实现, https://www.right.com.cn/forum/thread-8294123-1-1.html
Linux NAT 应用进阶(多链路输出+原路返回), https://blog.csdn.net/yk_wing4/article/details/90474164- 使用CONNMARK解决Linux上源进源出问题, https://blog.cyyself.name/connmark/
Leave a Reply