本帖最后由 阿泥基 于 2024-8-22 03:23 编辑
QQ 上看到群友在讨论爱快收费固件的一个功能——多线 IPv6 均衡负载。
刚开始感到诧异,这个也要收钱?但马上回忆起来,自己早些年也因为这个需求折腾了一段时间,确实也说得过去。
下面把当时做的笔记整理一下,希望能给大家一点解决问题的思路。
※由于当时解决了自己的需求后就再也没有深入,所以方案可能不完美或者有过时、不严谨的地方,敬请见谅!
首先要了解多线 IPv6 与 mwan3 负载均衡实现上的难点。
早期流行一个说法指,IPv6 与 mwan3 不兼容。其实不然,mwan3 很好地完成了它的分内事——负载均衡,但问题出在后面的步骤——源地址转换(SNAT)。
※参照 https://openwrt.org/docs/guide-user/network/wan/multiwan/mwan3#ipv6_support
举个例子,设备以移动宽带的源地址 2409:abcd::1234 发起连接,经过mwan3导流至电信宽带的网关 240e:abcd::1,运营商一看你这源地址我不认识,自然就会把它丢弃,导致无法预期工作。
在 IPv4 环境下,出站接口理所当然地启用了 IP 地址伪装(Masquerading),在进入运营商的网关前就已经把源地址转换为合法地址,因此无需担心上述的问题。
而早期很多使用者甚至开发者认为,IPv6 下仍然使用NAT是一种倒行逆施,对于这类需求多有不屑的态度,因此功能也迟迟未能完善。
虽然这个想法在一些使用者之间仍然根深蒂固,但至少工具已经完成了充分的实现。
最终效果如下
移动前缀 2409 的源地址发起的连接,经过 SNAT 后可正常出站至电信网络
文本就多线 IPv6 经过 mwan3 负载均衡后的关键步骤——源地址转换(SNAT)的3种实现方案进行说明与分析。
一、IP地址伪装(Masquerading)
二、1对1NAT(NAT pooling)
三、前缀转换(NETMAP)
一、IP地址伪装(Masquerading)
上文提到,IPv4 环境下出站接口(防火墙的 WAN 区域)默认启用了 IP 地址伪装,IPv6 同样可以使用这个方法解决负载均衡的源地址问题。
早期 IPv6 的 Masquerading 还只能通过命令行启用,不知哪个版本开始,WEB 管理页上添加了对应的开关。
网络 -- 防火墙 -- WAN 区域 -- 编辑 -- 高级设置--启用 “IPv6 地址伪装”
这个方案的优点是配置简单,单纯的宽带叠加及分流的话,WEB 上点几下就可以解决。
而缺点也很明显,所有设备的出口源地址都会变成相同的,这严重地限制了 IPv6 公网地址的可用性。
二、1对1NAT(NAT pooling)
为了充分发挥 IPv6 公网地址的可用性,我们开始使用基于 nftables 的防火墙脚本实现灵活的配置,请确认你所使用的框架。
在 SSH 下运行 fw4 ,若提示 -ash: fw4: not found 则表示不支持。
fw3 + iptables 或许也能实现类似的效果,但不在本文的讨论范围。
先编辑 /etc/config/firewall ,把nft脚本添加到防火墙
- config include
- option path '/usr/nft-nat6.sh'
复制代码 每当网络接口发生变化时都会自动运行脚本,fw4 已不再使用 reload 选项
没有指定 type 选项,默认为 shell 脚本 script 。注意并不是 nft 脚本 nftables ,这可能会与其他防火墙自定义规则的教程稍有不同
/usr/nft-nat6.sh 可改成你实际的脚本路径
编辑防火墙脚本 /usr/nft-nat6.sh
- #!/bin/sh
- ct=$(ifstatus wanct_6 | jsonfilter -e '@["route"][0].source')
- cm=$(ifstatus wancm_6 | jsonfilter -e '@["route"][0].source')
- nft add table inet myrules
- nft delete chain inet myrules srcnat 2>/dev/null
- [ -z "$CT" ] && exit
- [ -z "$CM" ] && exit
- nft add chain inet myrules srcnat { type nat hook postrouting priority srcnat \; }
- nft add rule inet oniicyan SNAT oifname "pppoe-wanct" ip6 saddr $CMIP6 counter snat $CTIP6
- nft add rule inet oniicyan SNAT oifname "pppoe-wancm" ip6 saddr $CTIP6 counter snat $CMIP6
- nft add rule inet myrules srcnat oifname "pppoe-wanct" ip6 saddr != $ct snat ip6 to $ct
- nft add rule inet myrules srcnat oifname "pppoe-wancm" ip6 saddr != $cm snat ip6 to $cm
复制代码
以下对该防火墙脚本进行详细解说
ct=$(ifstatus wanct_6 | jsonfilter -e '@["route"][0].source')
cm=$(ifstatus wancm_6 | jsonfilter -e '@["route"][0].source')
先获取 IPv6 接口的前缀信息。
这里以电信(ct)和移动(cm)的双线为例,注意 wanct_6 与 wancm_6 要改为你实际的接口名称。可以先在 SSH 下运行小括号内的命令确认能否正确获取前缀,注意是否有前缀长度。
※这部分无法在 nft 脚本下执行,因此防火墙配置中的脚本类型必须是 shell 脚本 script ,而不是 nft 脚本 nftables
nft add table inet myrules
nft add chain inet myrules srcnat { type nat hook postrouting priority srcnat \; }
对表和链进行添加,创建或清空操作。
添加表(add table),没有使用创建(create),因此即使存在同名表也不会报错。
该表名称为 myrules,地址族(family)为 inet,即 IPv4 与 IPv6 双栈——虽然本文针对 IPv6,但考虑用户可能有其他自定义规则,将其集中在一个表中方便管理,可根据实际情况修改。
当存在同名表时,该命令不会报错也不会对已有内容进行修改。
删除链(delete chain),首次执行时由于不存在该链,报错是预期情况,因此把它屏蔽。
判断接口是否存在,任意一个接口丢失时在此退出脚本
如果另一个接口丢失了,那就没必要继续后面的步骤。
添加链(create chain),没有使用创建(create),因此即使存在同名表也不会报错。
该链位于表 inet myrules 下,名称为 srcnat,类型为 nat,HOOK 点为 postrouting,优先级为 srcnat。
通常网络接口发生变化时,IPv6 前缀信息也会随着改变,因此需要更新已有规则。
直接删除重新添加,简单暴力。
nft add rule inet myrules srcnat oifname "pppoe-wanct" ip6 saddr != $ct snat ip6 to $ct
nft add rule inet myrules srcnat oifname "pppoe-wancm" ip6 saddr != $cm snat ip6 to $cm
在表 inet myrules 的链 srcnat 下添加规则。
当出站设备 pppoe-wanct 的 IPv6 源地址不符合前缀 $ct 时,修改源地址为前缀 $ct 地址池中的随机地址
当出站设备 pppoe-wancm 的 IPv6 源地址不符合前缀 $cm 时,修改源地址为前缀 $cm 地址池中的随机地址
※注意这里的网络设备名称是执行 ifconfig 命令时显示的名称,而非上述 ifstatus 命令中用到的网络接口名称
这里有个重要的表述——地址池中的随机地址。
也就是说,当移动宽带源地址 2409::abcd:1234 经 mwan3 导流至电信出口时,源地址可能会变成 240e::aabb:5566 或者其他电信前缀的随机地址,反之亦然。
而当移动宽带源地址 2409::abcd:1234 正常通过移动出口时,源地址则不变,保持为 2409::abcd:1234。
这个规则还有一个特性,每次匹配到规则后,都会根据地址池进行随机源地址转换,也就是说每个连接的源地址都会不同,即为 1 对 1 NAT,或者有个更熟悉的叫法——对称型 NAT。
另外,删除 ip6 saddr != $ct 或 ip6 saddr != $cm 的部分后,将会对该出站设备的所有 IPv6 连接都进行随机源地址转换。
也就是说,即使是移动宽带源地址 2409::abcd:1234 正常通过移动出口时,源地址仍然会变成 2409::aabb:5566 或者其他移动前缀的随机地址,反之亦然。
三、前缀转换(NETMAP)
大家应该也注意到,上面的 1 对 1 NAT 方案比起 IP 地址伪装方案,更加限制了 IPv6 公网地址的可用性。
对称型 NAT 下,连 P2P 穿透都无法实现。(没错,IPv6 也有针对防火墙的穿透场景,这里不赘述)
除非有特殊的安全需求,否则应该没人会用上面的方案。
我们使用多线 IPv6,大多数情况下都希望设备拥有的所有 IPv6 公网地址都可用。
为此,我们真正想实现的是,根据出口修改运营商的前缀,保留设备自己的后缀。
为了实现这个目的,需要用到的是基于 NETMAP 的 SNAT。
nftables 的 NETMAP 支持于2020年4月提交,虽然示例中仅展示 IPv4,但实际上同样支持 IPv6。
题外话,在本文撰写时,OPENWRT 官网的 mwan3 文档中已提及 NETMAP,但仍未给出示例。而我当时查阅的时候,几乎没有任何有意义的线索。
以下是示例,与方案二的类似,只是脚本稍微不同
编辑防火墙脚本 /usr/nft-nat6.sh
- #!/bin/sh
- ct=$(ifstatus wanct_6 | jsonfilter -e '@["route"][0].source')
- cm=$(ifstatus wancm_6 | jsonfilter -e '@["route"][0].source')
- CTIP6=$(ifstatus wanct_6 | jsonfilter -e '@["ipv6-address"][0]["address"]')
- CMIP6=$(ifstatus wancm_6 | jsonfilter -e '@["ipv6-address"][0]["address"]')
- nft add table inet myrules
- nft delete chain inet myrules srcnat 2>/dev/null
- [ -z "$CT" ] && exit
- [ -z "$CM" ] && exit
- nft add chain inet myrules srcnat { type nat hook postrouting priority srcnat \; }
- nft add rule inet myrules srcnat oifname "pppoe-wanct" ip6 saddr $CMIP6 snat $CTIP6 2>/dev/null
- nft add rule inet myrules srcnat oifname "pppoe-wancm" ip6 saddr $CTIP6 snat $CMIP6 2>/dev/null
- nft add rule inet myrules srcnat oifname "pppoe-wanct" ip6 saddr != $ct snat ip6 saddr map { ::/60 : $ct }
- nft add rule inet myrules srcnat oifname "pppoe-wancm" ip6 saddr != $cm snat ip6 saddr map { ::/60 : $cm }
复制代码
以下对该防火墙脚本进行详细解说
※方案二提及的部分不再重复
nft add rule inet myrules srcnat oifname "pppoe-wanct" ip6 saddr != $ct snat ip6 saddr map { ::/60 : $ct }
nft add rule inet myrules srcnat oifname "pppoe-wancm" ip6 saddr != $cm snat to ip6 saddr map { ::/60 : $cm }
在表 inet myrules 的链 srcnat 下添加规则。
当出站设备 pppoe-wanct 的 IPv6 源地址不符合前缀 $ct 时,从网络地址映射表 { ::/60 : $ct } 中选择合适的源地址
当出站设备 pppoe-wancm 的 IPv6 源地址不符合前缀 $cm 时,从网络地址映射表 { ::/60 : $cm } 中选择合适的源地址
跟方案二中的随机源地址不同,这里是从网络地址映射表中选择合适的源地址进行转换。
也就是说,当移动宽带源地址 2409::abcd:1234 经 mwan3 导流至电信出口时,源地址会变成 240e::abcd:1234;仅改变前缀为电信前缀 $ct,与设备相关的后缀不变,反之亦然。
这里有个注意的地方,由于我设置宽带获取的前缀长度是 /60,因此映射表左侧为 ::/60,对应的右侧也是 240e:xxxx::/60 或 2409:xxxx::/60
你可以根据自己实际的前缀长度进行修改,或者直接改为 { ::/0 : $ct } 或 { ::/0 : $cm }。我也不确定会不会占用多点内存。
跟方案二相同,删除 ip6 saddr != $ct 或 ip6 saddr != $cm 的部分后,将会对该出站设备的所有 IPv6 连接都进行源地址转换。
也就是说,当移动宽带源地址 2409::abcd:1234 正常通过移动出口时,源地址会变成 2409::abcd:1234,反之亦然。
很明显,这是多此一举的。
- CTIP6=$(ifstatus wanct_6 | jsonfilter -e '@["ipv6-address"][0]["address"]')
- CMIP6=$(ifstatus wancm_6 | jsonfilter -e '@["ipv6-address"][0]["address"]')
- nft add rule inet myrules srcnat oifname "pppoe-wanct" ip6 saddr $CMIP6 snat $CTIP6 2>/dev/null
- nft add rule inet myrules srcnat oifname "pppoe-wancm" ip6 saddr $CTIP6 snat $CMIP6 2>/dev/null
复制代码
比起方案二还多了以上部分。
由于运营商考虑到不支持前缀的设备,除了前缀之外还会提供一个(或多个)单播地址。这些单播地址与前缀不在同一网段,因此需要单独转换。
需要注意的是脚本中只考虑了一个单播地址的情况,若你运营商分配多个地址,请自行适当修改。
此部分并非必要,因为即使没有正确配置,也不影响路由器下面设备的通信。但当路由器本身使用 IPv6 发起连接时,可能会使用单播地址,因此尽可能适配。
请不要胡乱输入以及粘贴、复制等方式灌水
请尊重作者、并共同维护网站的正常阅读,否则账户将会被限制发帖、回帖,并且积分可能会被清零,站内短信以及阅读权限等都会受到影响,谢谢。
具体限制方式:https://www.right.com.cn/forum/thread-8307840-1-1.html
|