找回密码
 立即注册
img_loading
智能检测中

QQ登录

只需一步,快速开始

搜索
广告投放联系QQ68610888广告投放联系QQ68610888
查看: 9249|回复: 8

关于多线IPv6与mwan3均衡负载,正确进行源地址转换

[复制链接]
发表于 2024-2-19 19:57 | 显示全部楼层 |阅读模式
本帖最后由 阿泥基 于 2024-11-6 09:41 编辑

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脚本添加到防火墙
  1. config include
  2.     option path '/usr/nft-nat6.sh'
复制代码
每当网络接口发生变化时都会自动运行脚本,fw4 已不再使用 reload 选项
没有指定 type 选项,默认为 shell 脚本 script 。注意并不是 nft 脚本 nftables ,这可能会与其他防火墙自定义规则的教程稍有不同
/usr/nft-nat6.sh 可改成你实际的脚本路径

编辑防火墙脚本 /usr/nft-nat6.sh
  1. #!/bin/sh
  2. ct=$(ifstatus wanct_6 | jsonfilter -e '@["route"][0].source')
  3. cm=$(ifstatus wancm_6 | jsonfilter -e '@["route"][0].source')
  4. nft add table inet myrules
  5. nft delete chain inet myrules srcnat 2>/dev/null
  6. # [ -z "$CT" ] && exit
  7. # [ -z "$CM" ] && exit
  8. nft add chain inet myrules srcnat { type nat hook postrouting priority srcnat \; }
  9. nft add rule inet oniicyan SNAT oifname "pppoe-wanct" ip6 saddr $CMIP6 counter snat $CTIP6 2>/dev/null
  10. nft add rule inet oniicyan SNAT oifname "pppoe-wancm" ip6 saddr $CTIP6 counter snat $CMIP6 2>/dev/null
  11. nft add rule inet myrules srcnat oifname "pppoe-wanct" ip6 saddr != $ct snat ip6 to $ct 2>/dev/null
  12. nft add rule inet myrules srcnat oifname "pppoe-wancm" ip6 saddr != $cm snat ip6 to $cm 2>/dev/null
复制代码

以下对该防火墙脚本进行详细解说

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,首次执行时由于不存在该链,报错是预期情况,因此把它屏蔽。

判断接口是否存在,任意一个接口丢失时在此退出脚本
如果另一个接口丢失了,那就没必要继续后面的步骤。
但考虑到用户可能启用了 ULA 并作为源地址发起连接,这时候仍然需要 NAT,如不启用 ULA 可把此部分注释去掉。

添加链(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 2>/dev/null
nft add rule inet myrules srcnat oifname "pppoe-wancm" ip6 saddr != $cm snat ip6 to $cm 2>/dev/null

在表 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 != $ctip6 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
  1. #!/bin/sh
  2. ct=$(ifstatus wanct_6 | jsonfilter -e '@["route"][0].source')
  3. cm=$(ifstatus wancm_6 | jsonfilter -e '@["route"][0].source')
  4. CTIP6=$(ifstatus wanct_6 | jsonfilter -e '@["ipv6-address"][0]["address"]')
  5. CMIP6=$(ifstatus wancm_6 | jsonfilter -e '@["ipv6-address"][0]["address"]')
  6. nft add table inet myrules
  7. nft delete chain inet myrules srcnat 2>/dev/null
  8. # [ -z "$CT" ] && exit
  9. # [ -z "$CM" ] && exit
  10. nft add chain inet myrules srcnat { type nat hook postrouting priority srcnat \; }
  11. nft add rule inet myrules srcnat oifname "pppoe-wanct" ip6 saddr $CMIP6 snat $CTIP6 2>/dev/null
  12. nft add rule inet myrules srcnat oifname "pppoe-wancm" ip6 saddr $CTIP6 snat $CMIP6 2>/dev/null
  13. nft add rule inet myrules srcnat oifname "pppoe-wanct" ip6 saddr != $ct snat prefix to $ct 2>/dev/null
  14. nft add rule inet myrules srcnat oifname "pppoe-wancm" ip6 saddr != $cm snat prefix to $cm 2>/dev/null
复制代码

以下对该防火墙脚本进行详细解说

※方案二提及的部分不再重复

nft add rule inet myrules srcnat oifname "pppoe-wanct" ip6 saddr != $ct snat prefix to $ct 2>/dev/null
nft add rule inet myrules srcnat oifname "pppoe-wancm" ip6 saddr != $cm snat prefix to $cm 2>/dev/null

在表 inet myrules 的链 srcnat 下添加规则。
当出站设备 pppoe-wanct 的 IPv6 源地址不符合前缀 $ct 时,更改前缀为 $ct
当出站设备 pppoe-wancm 的 IPv6 源地址不符合前缀 $cm 时,更改前缀为 $cm

也就是说,当移动宽带源地址 2409::abcd:1234 经 mwan3 导流至电信出口时,源地址会变成 240e::abcd:1234仅改变前缀为电信前缀 $ct,与设备相关的后缀不变,反之亦然。

跟方案二相同,删除 ip6 saddr != $ctip6 saddr != $cm 的部分后,将会对该出站设备的所有 IPv6 连接都进行源地址转换。
也就是说,当移动宽带源地址 2409::abcd:1234 正常通过移动出口时,源地址会变成 2409::abcd:1234,反之亦然。
很明显,这是多此一举的。

  1. CTIP6=$(ifstatus wanct_6 | jsonfilter -e '@["ipv6-address"][0]["address"]')
  2. CMIP6=$(ifstatus wancm_6 | jsonfilter -e '@["ipv6-address"][0]["address"]')
  3. nft add rule inet myrules srcnat oifname "pppoe-wanct" ip6 saddr $CMIP6 snat $CTIP6 2>/dev/null
  4. nft add rule inet myrules srcnat oifname "pppoe-wancm" ip6 saddr $CTIP6 snat $CMIP6 2>/dev/null
复制代码

比起方案二还多了以上部分。
由于运营商考虑到不支持前缀的设备,除了前缀之外还会提供一个(或多个)单播地址。这些单播地址与前缀不在同一网段,因此需要单独转换。

需要注意的是脚本中只考虑了一个单播地址的情况,若你运营商分配多个地址,请自行适当修改。
此部分并非必要,因为即使没有正确配置,也不影响路由器下面设备的通信。但当路由器本身使用 IPv6 发起连接时,可能会使用单播地址,因此尽可能适配。


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×

评分

参与人数 1恩山币 +1 收起 理由
microka + 1 感谢大神无私分享,收藏研究! ...

查看全部评分

只谈技术、莫论政事!(点击见详情) | 恩山无线论坛欢迎您的来访,请互相尊重、友善交流,建议保持一颗平常心看待网友的评论,切勿过度反应。
发表于 2024-2-19 20:11 | 显示全部楼层
虽然我是小白,但是我依然感谢大神分享这个教程
只谈技术、莫论政事!(点击见详情) | 恩山无线论坛欢迎您的来访,请互相尊重、友善交流,建议保持一颗平常心看待网友的评论,切勿过度反应。
回复 支持 反对

使用道具 举报

发表于 2024-4-12 20:13 | 显示全部楼层
感谢大神分享,慢慢思考
只谈技术、莫论政事!(点击见详情) | 恩山无线论坛欢迎您的来访,请互相尊重、友善交流,建议保持一颗平常心看待网友的评论,切勿过度反应。
回复 支持 反对

使用道具 举报

发表于 2024-7-27 00:33 | 显示全部楼层
非常棒的帖子,我有个疑问,如果我的两个wan口 :wana6 和 wanb6 是通过4G模块获取到的ipv6地址,我试了ifstatus wanb6 | jsonfilter -e '@["route"][0].source'
终端返回值:::/0
似乎没有获取前缀,请问我应该如何设置呢? 谢谢楼主!

点评

抱歉没有及时看到回复 通常蜂窝网络只会分配单播地址,尽管地址可能可以透传到 CPE 以下的设备,但一般不会有前缀 这种情况只能使用 IP 地址伪装(Masquerading) 的方案进行负载均衡,也就是出口 IP 只能是固定的  详情 回复 发表于 2024-8-31 21:33
只谈技术、莫论政事!(点击见详情) | 恩山无线论坛欢迎您的来访,请互相尊重、友善交流,建议保持一颗平常心看待网友的评论,切勿过度反应。
回复 支持 反对

使用道具 举报

发表于 2024-8-4 19:37 | 显示全部楼层
感谢大神无私分享,收藏研究!
只谈技术、莫论政事!(点击见详情) | 恩山无线论坛欢迎您的来访,请互相尊重、友善交流,建议保持一颗平常心看待网友的评论,切勿过度反应。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2024-8-31 21:33 | 显示全部楼层
cyqtmxk 发表于 2024-7-27 00:33
非常棒的帖子,我有个疑问,如果我的两个wan口 :wana6 和 wanb6 是通过4G模块获取到的ipv6地址,我试了ifs ...

抱歉没有及时看到回复

通常蜂窝网络只会分配单播地址,尽管地址可能可以透传到 CPE 以下的设备,但一般不会有前缀
这种情况只能使用 IP 地址伪装(Masquerading) 的方案进行负载均衡,也就是出口 IP 只能是固定的单播地址
这方面跟 IPv4 是一样的,如果需要通过这个地址传入连接的话,需要配置端口转发

前缀转换方案的前提是有足够的地址空间进行映射

点评

大佬 这种方案下 mwan3 ipv6没办法负载均衡吧?  详情 回复 发表于 2024-11-21 14:34
只谈技术、莫论政事!(点击见详情) | 恩山无线论坛欢迎您的来访,请互相尊重、友善交流,建议保持一颗平常心看待网友的评论,切勿过度反应。
回复 支持 反对

使用道具 举报

发表于 2024-11-21 14:34 | 显示全部楼层
阿泥基 发表于 2024-8-31 21:33
抱歉没有及时看到回复

通常蜂窝网络只会分配单播地址,尽管地址可能可以透传到 CPE 以下的设备,但一般 ...

大佬 这种方案下 mwan3 ipv6没办法负载均衡吧?
只谈技术、莫论政事!(点击见详情) | 恩山无线论坛欢迎您的来访,请互相尊重、友善交流,建议保持一颗平常心看待网友的评论,切勿过度反应。
回复 支持 反对

使用道具 举报

发表于 2024-12-1 18:27 | 显示全部楼层
本帖最后由 a8184976 于 2024-12-1 18:29 编辑

fw3+iptables目前只能直接snat到对应的出口IP(参考方案二),无法像nftables那么灵活可以更改前缀。脚本我是放置在/etc/hotplug.d/iface/目录底下,接口有改变都会进行操作。

简易脚本如下:
  1. #!/bin/sh
  2. ct=$(ifstatus wan_6 | jsonfilter -e '@["route"][0].source')
  3. cu=$(ifstatus wan1_6 | jsonfilter -e '@["route"][0].source')
  4. CTIP6=$(ifstatus wan_6 | jsonfilter -e '@["ipv6-address"][0]["address"]')
  5. CUIP6=$(ifstatus wan1_6 | jsonfilter -e '@["ipv6-address"][0]["address"]')

  6. if [ -z "$ct" ] || [ -z "$cu" ] || [ -z "$CTIP6" ] || [ -z "$CUIP6" ]; then
  7.     #echo "One or more required variables are empty. Exiting."
  8.     exit 1
  9. fi
  10. check_wan=$(ip6tables -t nat -L -v -n | grep "$CTIP6")
  11. check_wan1=$(ip6tables -t nat -L -v -n | grep "$CUIP6")
  12. if [ ! -z "$check_wan" ] && [ ! -z "$check_wan1" ]; then
  13.     #echo "No Change"
  14.     exit 1
  15. fi

  16. ip6tables -t nat -F POSTROUTING
  17. ipset -N local_cu hash:net family inet6 2>/dev/null
  18. echo "create local_cu hash:net family inet6 hashsize 1024 maxelem 65536" > /tmp/local_ipv6.ipset
  19. echo $cu | sed -e "s/^/add local_cu /" >> /tmp/local_ipv6.ipset
  20. echo $CUIP6 | sed -e "s/^/add local_cu /" >> /tmp/local_ipv6.ipset
  21. ipset -! flush local_cu
  22. ipset -! restore < /tmp/local_ipv6.ipset 2>/dev/null
  23. rm -f /tmp/local_ipv6.ipset
  24. ipset -N local_ct hash:net family inet6 2>/dev/null
  25. echo "create local_ct hash:net family inet6 hashsize 1024 maxelem 65536" > /tmp/local_ipv6.ipset
  26. echo $ct | sed -e "s/^/add local_ct /" >> /tmp/local_ipv6.ipset
  27. echo $CTIP6 | sed -e "s/^/add local_ct /" >> /tmp/local_ipv6.ipset
  28. ipset -! flush local_ct
  29. ipset -! restore < /tmp/local_ipv6.ipset 2>/dev/null
  30. rm -f /tmp/local_ipv6.ipset
  31. ip6tables -t nat -A POSTROUTING -o pppoe-wan -m set --match-set local_cu src -j SNAT --to-source $CTIP6
  32. ip6tables -t nat -A POSTROUTING -o pppoe-wan1 -m set --match-set local_ct src -j SNAT --to-source $CUIP6
复制代码




只谈技术、莫论政事!(点击见详情) | 恩山无线论坛欢迎您的来访,请互相尊重、友善交流,建议保持一颗平常心看待网友的评论,切勿过度反应。
回复 支持 1 反对 0

使用道具 举报

发表于 2025-3-22 19:39 | 显示全部楼层
虽然我是小白,但是我依然感谢大神分享这个教程
非常感谢
只谈技术、莫论政事!(点击见详情) | 恩山无线论坛欢迎您的来访,请互相尊重、友善交流,建议保持一颗平常心看待网友的评论,切勿过度反应。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

欢迎大家光临恩山无线论坛

只谈技术、莫论政事!切勿转播谣言!为了你也为了他人。
只谈技术、莫论政事!(点击见详情) 切记不要随意传播谣言,把自己的日子过安稳了就行,为了自己好也为了大家好。 恩山无线论坛欢迎您的来访,请互相尊重、友善交流,建议保持一颗平常心看待网友的评论,切勿过度反应。

查看 »

有疑问请添加管理员QQ86788181|手机版|小黑屋|Archiver|恩山无线论坛(常州市恩山计算机开发有限公司版权所有) ( 苏ICP备05084872号 )

GMT+8, 2025-6-1 10:49

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

| 江苏省互联网有害信息举报中心 举报信箱:js12377 | @jischina.com.cn 举报电话:025-88802724 本站不良内容举报信箱:68610888@qq.com

快速回复 返回顶部 返回列表