这不是一篇水文~

本文解决了网络各大论坛上广为讨论却鲜有解决办法的问题:Linux(非OpenWRT)同时连接两个不同ISP的宽带上网,实现带宽叠加,且不提高丢包率(实测0%)。

本人使用Ubuntu24.04,如果是旧版Ubuntu或者CentOS可能不采用netplan,旧的Linux内核也可能没有ECMP,照搬方法时请谨慎,并且做好服务器断网的思想准备

新服务器有两个有线网口,同时连接了电信200M宽带的光猫和移动300M宽带光猫。

刚装完系统后开机,发现居然连不上网。

定位到/etc/netplan,里面是空的,即没有默认网络配置文件。

于是手敲了一个配置文件00-installer-config.yaml

network:
version: 2
ethernets:
enp1s0:
dhcp4: true
dhcp6: true
enp2s0:
dhcp4: true
dhcp6: true

其中enp1s0和enp2s0是两个有线网卡,分别连接了电信和移动宽带。

重启后顺利连上网络。

使用一段时间后渐渐发现了问题:网速上限只有200M。

通过ifconfig检查,发现开机以来enp1s0流量几百G,而enp2s0只有几百M。

接下来开始寻找解决方案(此处省略两小时探索过程,包括前往工作室的路程成本)。

最初的想法是用 Linux Bond

模式编号模式名称描述交换机支持要求常用性
0Balance-RR(轮询模式)数据包依次发送到所有网络接口上,实现负载均衡。需要配置静态链路聚合 mode on常用
1Active-Backup(主备模式)只有一个接口处于活动状态,其他作为备份,活动接口故障时自动切换。不需要交换机做配置,仅需要划分对应的vlan非常常用
2Balance-XOR(平衡异或模式)根据源MAC地址和目的MAC地址的异或值来选择发送数据的接口。需要配置静态链路聚合 mode on ,同时需要设置对应的balance较少使用
3Broadcast(广播模式)所有接口都发送相同的数据包,适用于广播或多播场景。需要配置静态链路聚合 mode on很少使用
4802.3ad(LACP模式)遵循LACP协议,通过LACP协商实现链路聚合。需要配置lacp动态链路聚合 mode active常用
5Balance-TLB(自适应传输负载均衡模式)根据每个接口的负载情况动态调整数据包发送。不需要较少使用
6Balance-ALB(自适应负载均衡模式)Bond5模式的扩展,同时实现发送和接收的负载均衡。不需要较少使用

显然,由于我们使用的根本都不是一个运营商的上流交换机,所以所有需要交换机配置的都排除。

而轮询模式同样不行(实测丢包率约50%)

移动和电信光猫通常分配不同的IP段和网关,而Bonding(绑定)要求两端网络完全一致(同一子网、同一网关)。

同样地,tlb与alb效果均不佳。主备模式能用,但跟没用一个样。

接下来,想到策略路由。

但本人比较懒,策略路由配置起来比较费时,而且很可能无法充分利用500M宽带。

于是,尝试自动路由(进入正题)。

自动路由的工作原理

  1. ECMP (等价多路径路由)
    • 内核将流量分配到多个等价路径
    • 基于五元组(源IP、目的IP、源端口、目的端口、协议)进行哈希
    • 同一连接的所有数据包保持同一路径
  2. 连接跟踪
    • nf_conntrack 模块确保回复流量返回同一接口
    • 防止非对称路由问题
  3. ARP优化
    • 减少ARP广播对双网络的影响
    • 确保本地响应使用正确接口

首先,创建负载均衡脚本/usr/local/bin/enable-ecmp.sh):

#!/bin/bash

# 启用ECMP
sysctl -w net.ipv4.fib_multipath_hash_policy=1 # 改进的哈希策略
sysctl -w net.ipv4.fib_multipath_use_neigh=1 # 使用邻居信息
sysctl -w net.ipv4.conf.all.arp_ignore=1 # 忽略ARP查询
sysctl -w net.ipv4.conf.all.arp_announce=2 # 使用最佳本地地址响应ARP

# 设置连接跟踪保持同一路径
sysctl -w net.netfilter.nf_conntrack_tcp_be_liberal=1

# 添加ECMP默认路由
ip route replace default \
nexthop via $(ip route show dev enp1s0 | awk '/default/ {print $3}') dev enp1s0 weight 1 \
nexthop via $(ip route show dev enp2s0 | awk '/default/ {print $3}') dev enp2s0 weight 1

然后,设置脚本权限和自启动

sudo chmod +x /usr/local/bin/enable-ecmp.sh
sudo crontab -e
# 添加以下内容
@reboot sleep 15 && /usr/local/bin/enable-ecmp.sh

重启后即可使用。实测网速提升至400M,已经可以算是充分利用两条宽带了。

这就结束了?当然没有!

本人使用n2n组网,在开启ECMP之后,打洞始终无法成功,只能是转发模式。

问题可能来自移动宽带,也可能是ECMP本身的问题。总之,只要将目标为我那两台supernode的连接强制分流给网卡enp1s0(即电信宽带),问题就可以解决了。

于是,在前述负载均衡脚本的最后添加如下两行:

ip route add 公网超级节点1的IP地址 via $(ip route show dev enp1s0 | awk '/default/ {print $3}') dev enp1s0
ip route add 公网超级节点2的IP地址 via $(ip route show dev enp1s0 | awk '/default/ {print $3}') dev enp1s0

其中公网超级节点的IP地址在此隐藏一下,如需照搬代码记得替换成你的supernode。

重启后完美。

如需照搬本文代码,记得把enp1s0和enp2s0替换成你自己的网卡名称!

做好操作失败连不上网只能本地操作的准备!(我有ipmi嘿嘿嘿)

多说一句:OpenWRT上好像有一个叫mwan的应用可以实现利用不同ISP的双宽带,理论上可以在Ubuntu内用Docker跑一个OpenWRT作为旁路由。此外还有个叫NetworkManager可能有用。但它们的实现复杂程度显然均远超本文介绍的方法。

接下来简单介绍一下ECMP为什么可行

  • 当你首次访问某个主机时,系统会为该连接创建一个连接跟踪条目(conntrack entry)
  • 该条目会记录连接的五元组信息:
    • 源IP地址
    • 目标IP地址
    • 源端口
    • 目标端口
    • 协议类型(TCP/UDP等)

接下来,基于这个条目计算出哈希值,再通过哈希值确定使用哪张网卡。

后续的数据包,只要五元组信息完全相同,就会使用相同的网卡,从而避免了丢包的问题。

而对于实际多线程传输场景,每个线程的源端口一般是不同的,最终计算得到哈希值也会不同。于是有些线程走了移动,有些是电信,从而充分利用了双宽带。

进阶使用:加权分流

假如两条宽带相差很悬殊,比如500M电信+3000M移动,就可能要加权分流,应该把前面的weight改一下就行了。

本文为FWERKOR Castronaut原创,未经许可谢绝转载!

Castronaut的头像

作者 Castronaut

行走在地狱边缘,狂舞于悬崖之巅。

发表回复