一个朋友在做RouterOS的负载均衡配置使用了ECMP,得到了一个意外的发现,因此对ECMP的策略做了新的研究,查阅资料时看到这样一篇文章,一共有三部分,对Linux ECMP做了详细的讲解。
首先,我在本文讨论的是基于NAT转换的多路径ECMP应用,而基于三层路由的ECMP不受Per-Packet策略影响,因为在三层路由中不对称路由是允许的。
提醒:在国内不要拿不同运营商的线路用来做负载均衡!除非你有解决方案!

Linux ECMP历史

ECMP-“Equal-Cost Multi-Path Routing”即等价多路径路由,由于自己一直在使用RouterOS系统,对ECMP技术发展了解受限,最近因为一些阴差阳错的原因重新对Linux的ECMP做了相关的了解,Linux发展变化这里有一个时间线,下面是ECMP的内核开发中过程:
  • 1997 年 11 月 v2.1.68 初步支持 IPv4 多路径路由。包括对加权 ECMP 的支持,下一跳以伪随机方式选择路径(使用jiffies随机数,Linux的一个基于时间的全局变量),也就是使用基于数据包(Per-Packet)的负载均衡。
  • 2012 年 9 月 v3.6 IPv4 路由缓存被删除,这使得多路径路由无法与面向连接的协议(如 TCP)一起使用,因为可以为属于同一流的数据包选择不同的下一跳。
  • 2013 年 2 月 v3.8 初步支持 IPv6 多路径路由。基于IP数据流(Per-Flow)的哈希多路径选择,相同flow的数据包始终被路由到相同的下一跳。
  • 2016 年 1 月 v4.4 IPv4 的多路径路由切换到基于per-flow的哈希路径选择,使得面向连接的协议再次与 ECMP 一起使用。

Per-Packet和Per-Flow的区别:

例如,主机A到Web服务器进行通信,主机:192.168.88.2—> Web服务器:8.88.88.88 ,他们之间建立了TCP连接(会话),在这个连接会话中的数据通信被看作一个Flow,会话中通信传输数据根据相同源和目的分成一份一份数据包(Packet)
在路由器中建立了大量的会话,在这些会话终端各个flow的数据包会被依次转发,如果基于Per-Packet的ECMP,会把所有会话中的Flow随机分配到多条线路,完全不考虑这个数据包属于哪个会话,这样导致多线出口下,目标服务器收到的请求来着多个出口的IP,导致不信任的IP请求会被服务器拒绝;

如果基于Per-Flow的使用哈希算法的ECMP,可以让一个Flow始终保持相同的出口路径到达目标服务器,在服务器看到请求IP始终是同一个出口,唯一可能破坏这个线路的只有出口数量或路由表调整,重新计算哈希路径。PCC的策略也是Per-Flow,RouterOS还有一个分流的策略Nth,这种策略属于Per-Packet,具体可以参考:探讨下RouterOS PCC的工作原理与应用延伸

RouterOS的ECMP

早期根据MikroTik官方WiKi的一份文档(https://wiki.mikrotik.com/wiki/ECMP_load_balancing_with_masquerade),对RouterOS v6使用ECMP负载均衡,会导致两个问题:
Routing table flush can be caused by 2 things:
1) routing table change (dynamic routing protocol update, user manual changes),路由表配置改变
2) every 10 minutes routing table is flushed for security reasons (to avoid possible DoS attacks),每10分钟重新计算路径,为防止DoS攻击,
视乎这个文档没有提到Per-Packet的ECMP策略,但10分钟重新计算路径,会导致路径重新计算,使得NAT的会话连接在每10分钟后大概率产生错误。
RouterOS v6是基于Linux 3.3的内核,RouterOS v7基于Linux 5.x后的内核,也就是说RouterOS v6和之前的版本ECMP策略都会导致在出口多路径下出现连接错误问题,我整理了从3.24开始的RouterOS版本发展历史:
  • 2009年,MikroTik发布RouterOS v3.24,支持PCC负载均衡,Per connecton classifier(每次链接分类哈希算法),通过iptables扩展调用PCC,解决了多线路负载均衡问题,基于Per-Flow;
  • 2009年 RouterOS v4发布,寿命很短的版本;
  • 2010年 RouterOS v5发布, Linux内核是 2.6+;
  • 2013年 RouterOS v6发布, Linux内核是v3.3.5+,寿命最长的一个版本;
  • ** 2016 年 1 月Linux内核 v4.4 IPv4 的多路径路由切换到基于会话哈希的路径选择,也意味着Linux的ECMP支持与PCC类似的Per-Flow负载均衡;
  • 2020年,RouterOS v7发布,Linux内核 5.x+ 非常遗憾的是RouterOS从v6到v7用了7年。
也就是2016年后,Linux 4.4内核,支持Per-Flow的hash算法,这样可以对L3或L4的NAT会话保持相同的出口线路,解决了之前ECMP Per-Packet的问题,实现了与PCC类似的Per-Flow策略。我估计2016年后基于Linux核心的路由器,就可以利用ECMP实现可靠的负载均衡策略,但不得不说MikroTik在解决Flow的负载均衡策略走在了前面!
这个朋友阴差阳错的配置了v7的ECMP多线路,开始我认为会出现不定时的连接错误,但在使用中没有出现游戏连接中断或其他访问网页错误,同样达到负载均衡的效果。查阅ECMP资料后,个人认为ECMP可以代替PCC,PCC的mangle中会创建各种标记规则,线路越多对CPU性能消耗越大,如果使用ECMP,无需配置mangle标记策略,可以更节约CPU资源。
为此还咨询MikroTik官方关于v7的ECMP是否可以代替PCC的问题,官方并没有正面回答,ECMP某种程度上可以实现PCC功能,但认为PCC还是当前最好的选择。
multipath hash policy在7.16支持,是否Maris B回复错误?
Linux的ECMP是支持权重参数,但在权重的配置上,RouterOS并没有添加这个参数设置,所有路由规则的权重是一样的,也就是说ECMP无法实现按照权重比例分配多线的负载,官方也没有计划添加权重参数。

RouterOS如何设置ECMP策略

在RouterOS v7.16增加了 ip/ipv6 – added multipath hash policy settings ,也就是ECMP的哈希策略算法,/ip/settings或/ipv6/settings目录下可以设置IPv4/IPv6的哈希算法策略用于ECMP路由
  • l3 — 基于三层源IP和目标IP哈希算法
  • l3-inner — 基于三层源IP和目标IP哈希或 使用内层的IP包头哈希算法(例如在VPN或隧道场景(如IPsec、GRE)中,外层报头用于路由,但原始数据包(内层数据包)也有其自己的Layer 3报头)
  • l4 — 基于四层的源IP、目标IP、IP协议、源端口和目标端口哈希算法
这个和PCC的both-address(三层哈希),both-address-port(四层哈希)类似。
PS:L3代表OSI七层模型的第三层-网络层,L4就是第四层-传输层,OSI七层参考模型是网络入门的基础,不管TCP/IP是五层,还是四层都来自于OSI七层参考模型。

配置实例

实例是有三条外线,分别是接入ether1,ether2,ether3,如下图:
创建3个外网的出口IP地址
[admin@MikroTik] > /ip address
[admin@MikroTik] /ip/address>add address=172.16.5.2/24 interface=ether1
[admin@MikroTik] /ip/address>add address=172.16.6.2/24 interface=ether2
[admin@MikroTik] /ip/address>add address=172.16.7.2/24 interface=ether3
创建内网IP地址192.168.88.1/24
[admin@MikroTik] /ip/address>add address=192.168.88.1/24 interface=ether4
创建ecmp路由表,不推荐在main路由表使用ecmp路由,这样会导致路由器本地访问出现问题
[admin@MikroTik] /ip/address> /routing/table
[admin@MikroTik] /routing/table> add disabled=no fib name=ecmp
在ecmp路由表添加三条出口的网关,实现ECMP路由
[admin@MikroTik] /routing/table>/ip route
[admin@MikroTik] /ip/route> add disabled=no gateway=172.16.5.254 routing-table=ecmp
[admin@MikroTik] /ip/route> add disabled=no gateway=172.16.6.254 routing-table=ecmp
[admin@MikroTik] /ip/route> add disabled=no gateway=172.16.7.254 routing-table=ecmp
查看路由表,可以看到As+,代表三条路由已经实现ECMP
[admin@MikroTik] /ip/route> print
Flags: D - DYNAMIC; A - ACTIVE; c - CONNECT, s - STATIC, d - DHCP; + - ECMP
Columns: DST-ADDRESS, GATEWAY, DISTANCE
# DST-ADDRESS GATEWAY DISTANCE
DAc 172.16.5.0/24 ether1 0
DAc 172.16.6.0/24 ether2 0
DAc 172.16.7.0/24 ether3 0
DAc 192.168.88.0/24 ether4 0
0 As+ 0.0.0.0/0 172.16.5.254 1
1 As+ 0.0.0.0/0 172.16.6.254 1
2 As+ 0.0.0.0/0 172.16.7.254 1
在routing rule下创建策略路由,指定内网192.168.88.0/24,设置到源IP走ecmp路由表(rule策略可以比mangle更节约CPU资源)
[admin@MikroTik] /ip/route>/routing rule
[admin@MikroTik] /routing/rule> export
add action=lookup disabled=no src-address=192.168.88.0/24 table=ecmp
解决内网IP因为策略路由无法到达路由器网关(如无法访问RouterOS本地的DNS服务),或路由器上还配置有其他IP段无法互通问题,可以使用min-prefix解决
[admin@MikroTik] /routing/rule> add action=lookup table=main min-prefix=0
[admin@MikroTik] /routing/rule> print
Flags: X - disabled, I - inactive
0 src-address=192.168.88.0/24 action=lookup table=ecmp
1 action=lookup table=main min-prefix=0

将min-prefix规则,通过move 命令移动到队列最前:

[admin@MikroTik] /routing/rule> move 1 0
[admin@MikroTik] /routing/rule> print
Flags: X - disabled, I - inactive
0 action=lookup table=main min-prefix=0
1 src-address=192.168.88.0/24 action=lookup table=ecmp
配置nat规则
[admin@MikroTik] /ip/route>/ip firewall nat
[admin@MikroTik] /ip/firewall/nat>add action=masquerade out-interface=ether1 chain=srcnat
[admin@MikroTik] /ip/firewall/nat>add action=masquerade out-interface=ether2 chain=srcnat
[admin@MikroTik] /ip/firewall/nat>add action=masquerade out-interface=ether3 chain=srcnatr
如果有端口映射,目前暂时想到的办法将被映射主机从ECMP策略分离出来,通过rule策略让主机机走做了映射线路的出口!
如果涉及到更复杂的路由策略配置,就需要在ip firewall mangle中配置,例如我们需要指定TCP的443端口走ecmp的策略出口
[admin@MikroTik] /ip/firewall/mangle>add action=mark-routing chain=prerouting dst-port=443 new-routing-mark=ecmp protocol=tcp
调用指定的地址列表走ecmp策略路由
[admin@MikroTik] /ip/firewall/mangle>add action=mark-routing chain=prerouting dst-address-list=cnlist new-routing-mark=ecmp src-address=192.168.88.0/24
ECMP使用L4策略会更加均衡,因为加入了协议,源和目标端口,在这篇文章提到ECMP的哈希策略使用L4,会遇到TCP被挂起的可能,大概意思是:
如果选择L4的哈希策略,会把五元组做哈希,但ICMP不在其中,TCP协议会使用ICMP进行路径的MTU探测,由于无法被正确的ECMP到相同出口,导致TCP通信被挂起,解决这个问题在Linux内核4.4的IPv4支持 ECMP的任播设置,ipv4栈中的转发逻辑是查看 ICMP 错误消息,并根据触发错误有问题的 IP 数据报头(嵌入在 ICMP 消息中)做出路由决策,让它们与所属的流一起路由,最大限度地减少多路径流出错的可能性。在cloudflare的blog也对ECMP导致ICMP问题做了解释
需要注意,当网关的线路数量改变时,会破坏当前的哈希路径算法,会对线路分配重新计算,导致连接中断,这个是无法避免的!
至于RouterOS ECMP是否能代替PCC,也期待大家的验证结果!

打赏

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

文章很值,打赏犒劳作者一下