如何提高网速

 

400Mbps网速成就达成!主要介绍OpenWrt下的单线多拨,多线多拨以及一种简单且更高效的负载均衡配置方法(ECMP:等效多径路由),也为这些配置方法写了脚本,节约配置的时间,另外给出了负载均衡适用的场景的分析

400Mbps网速成就达成!主要介绍OpenWrt下的单线多拨,多线多拨以及一种简单且更高效的负载均衡配置方法(ECMP:等效多径路由),也为这些配置方法写了脚本,节约配置的时间,另外给出了负载均衡适用的场景的分析

前言

什么是多拨?简单的顾名思义,对最常见的PPPoE拨号接入,就是在一台设备上拨多个号,因为拨号依赖于网卡,所以先要解决的是网卡问题,这里可以通过添加网卡(虚拟网卡)来实现,然而仅仅是拨多个号还是不够的,查看路由表可以发现,多条PPPoE路由是有使用的先后次序的(metric),这一般作为提升网络可靠性的冗余

目标是提升网速,这就需要同时用到多条线路,这就需要做负载均衡,OpenWrt + mwan3 的教程网络上实在是太多了,大概翻阅了一下,缺的大概是脚本和细节上的说明吧(GUI实在是太繁琐了,费时费力),毕竟在学校,难免会有一些想法

写得有些冗长,对于在mwan3方面轻车熟路的同学不妨看下ECMP部分,对内核版本较高的OpenWrt系统,单ISP的情况下,实测的效果要远远好于mwan3

已有的解决方案

配置繁琐的这个坑还是有人填的

首先是lean的lede中的插件:lean/luci-app-syncdial,另外还有MWAN3分流助手

对mwan3的IPv6相关的问题以及负载均衡都有人写了patch,并放出了修改后的源码: 多拨插件添加ipv6负载均衡

我编译了下,看起来还不错,配置起来算是非常友好了(有后面的ECMP,我是不会去用这个了)

注意事项

受限于脚本写的时间,mwan3也一直在不断更新,部分内容很可能在某个时间点不再适用,所以仅仅是提供一个框架

另外对部分接口的命名还是要看设备的具体情况,如eth0.2是我一众设备的wan口,eth0.3是添加VLAN_ID=3的情况

最需要注意的是,使用该脚本是对设备应该量力而行,因为同时拨太多的好,对于设备来说,负担是非常大的,尤其是启动和重拨的时候,路由器web界面和命令行都是一卡一卡的,更不要说负载均衡了,当超过路由器的处理能力时会出现连接频繁中断的情况,而且拨数较多且负荷较高的情况下,效率可能只有80%,可能反而不如更少的拨数,这里给出个别参考的拨号数量:

Newifi Y1 mini:(MT7260,1C1T@580MHz),6拨以下,但是路由器的转发性能也就百兆左右

Phicomm K2P:(MT7260,1C1T@880MHz),测试过32和64拨,mwan3的话跑600Mps就差不多了,实际有HWNAT,目测可以跑到接近千兆,ECMP效率还要高一点

多拨

尽管已经有了上面极简的方法,本文依然适合更加极限一点的情况(拨号的数量更多,或者需要自定义),而且负载均衡方面也不局限于mwan3

准备

ISP是否支持多拨

避免无意义的折腾,先可以上网了解下,或者先用上面的方法尝试下简单的多拨,如果对下行或者上行有效果的话再做进一步的尝试

安装macvlan、mwan3

opkg update && opkg install kmod-macvlan

安装mwan3以及其Luci界面

opkg update
opkg install mwan3 & opkg install luci-app-mwan3

HWNAT

硬件转发,通过硬件加速来极大的提高路由器转发的速率,在网速提高的趋势下为了提高路由器的效率肯定会普及的一项技术,OpenWrt也是在18.06开始支持了MT7261的HWNAT,不过依然需要手动开启

实测在K2P不开启HWNAT的情况下,IPv6四线八拨的速度最高在300Mbps左右,开启HWNAT就有400Mbps了,也就到达了在学校宿舍四个百兆端口的情况下的最大带宽,此时CPU的占用在70%,相比不开HWNAT有很大的提高

单线多拨

如果只有一个IPS或者单条链路的带宽已经可以满足带宽需求的话

启动添加虚拟网卡

这里直接就设定为64了,即建立64个虚拟wan口,至于在那个vlan上设置,只要看下pppoe-wan是在哪个就好了

#添加启动项
for i in  `seq $1 $2`
do
  ip link add link eth0.2 name veth$i type macvlan
done

添加wan口并拨号

编辑脚本,输入账号密码,在运行脚本的时候再指定一下VWAN口命名的范围就好,这里设置的默认情况是禁用了VWAN的IPv6的

#!/bin/sh
username=
password=
for i in  `seq $1 $2`
do
  uci set network.VWAN$i=interface
  uci set network.VWAN$i.proto='pppoe'
  uci set network.VWAN$i.username="$username"
  uci set network.VWAN$i.password="$password"
  uci set network.VWAN$i.metric="$((i+1))"  
  uci set network.VWAN$i.ifname="veth$i"
  uci set network.VWAN$i.ipv6='0'
done
uci commit network

防火墙设定

这里为了避免影响原firewall zone中firewall.@zone[1].network=’wan wan6 VWAN2 wwan’ ,所以选择自己再添加一个firewall zone,这段也可以添加到ifup的hotlpug文件中,用uci add_list添加更多的接口到网络新建的zone

uci add firewall zone 1>/dev/null
uci set firewall.@zone[2]=zone
uci set firewall.@zone[2].name=pppoe
uci set firewall.@zone[2].input=ACCEPT
uci set firewall.@zone[2].output=ACCEPT
uci set firewall.@zone[2].forward=ACCEPT
uci set firewall.@zone[2].masq=1
uci set firewall.@zone[2].mtu_fix=1

for i in `uci show network | grep pppoe | awk -F. '{print $2}'`
do
  uci add_list firewall.@zone[2].network=$i
done

uci commit firewall

这里再说一句,OpenWrt的ash支持的语法相对bash实在太不方便了(当然可以再安装bash),在添加防火墙规则的时候,本来想避免重复添加,使用if [[ expression ]]来匹配已经添加的网口来避免防火墙的重复添加,可惜没有报错,但是后面验证应该是ash不支持的

负载均衡

以下为 Load_balance > Interface 设置部分,个人写了个脚本,方便快速在多拨和正常拨号之间切换

设计要达到的目标,用参数来控制配置的添加和删除

方便设置PPPoE的账号,密码

这段也可以添加到ifup的hotlpug文件夹中

MWAN3设置

最普通的一种负载均衡,同样使用脚本的运行参数来控制VWAN口命名编号的起始,至于更加高级的,之后再倒腾吧

需要说明的是,为了减轻路由器负担,个人原本track_ip设置的是上一层内网的出口IP,这里改成了114.114.114.114以供连接性的监测,对网络的可连通性的后续设置也是为了相同目的而简化,具体需要按照个人情况更改吧

#!/bin/sh
#mwan_add.sh

for i in  `seq $1 $2`
do
  uci set mwan3.VWAN$i=interface
  uci set mwan3.VWAN$i.enabled='1'
  uci set mwan3.VWAN$i.track_ip='114.114.114.114'
  uci set mwan3.VWAN$i.track_ip='223.6.6.6'
  uci set mwan3.VWAN$i.reliability='1'
  uci set mwan3.VWAN$i.count='1'
  uci set mwan3.VWAN$i.timeout='2'
  uci set mwan3.VWAN$i.interval='1'
  uci set mwan3.VWAN$i.down='1'
  uci set mwan3.VWAN$i.up='1'
done
uci commit mwan3

uci set mwan3.member_VWAN1=member
uci set mwan3.member_VWAN1.interface='wan'
uci set mwan3.member_VWAN1.metric='1'
uci set mwan3.member_VWAN1.weight='1'
for i in  `seq $1 $2`
do
  uci set mwan3.member_VWAN$i=member
  uci set mwan3.member_VWAN$i.interface="VWAN$i"
  uci set mwan3.member_VWAN$i.metric='1'
  uci set mwan3.member_VWAN$i.weight='1'
done

uci set mwan3.load_blance=policy
uci set mwan3.load_blance.last_resort='unreachable'
for i in  `seq $1 $2`
do
  uci add_list mwan3.load_blance.use_member="member_VWAN$i"
done

uci set mwan3.default_rule=rule
uci set mwan3.default_rule.use_policy='load_blance'
uci set mwan3.default_rule.proto='all'

uci commit mwan3
/etc/init.d/mwan3 restart

多线多拨

这就涉及到多链路路由负载均衡,这里要区分于一般的链路聚合——链路负载均衡,一般的家用路由器都是不支持的,下面的脚本需要对特定的链路数量和单链路上的端口数量做一定的修改

在单线多拨的速度瓶颈在于链路硬件的情况下,多线多拨就有必要了,首先自然是设置VLAN,主要是添加VLAN,之后再Interface选项中就会出现 Switch VLAN :”eth0.X” ,(X=3,4,5,…)

不知道什么原因,个人的路由器上面只能在Switch VLAN :”eth0.X” 上添加的虚拟网卡才能够使用(相比于eth0.2直接可以作为wan使用),反正在学校的这种情况下,一个Switch VLAN 上面要添加两个Ethernet Adapter (用macvlan添加虚拟网卡)才可以跑满端口的链路速度,不影响使用就是了

添加完虚拟网卡之后就和单线多拨的过程相似了,需要费点心思的地方主要是命名规则,跃点的设定,这里就只有简单的设置下

添加VLAN

VLAN是现在路由器普遍采用的技术,直接在Network > Switch 中设置即可,方法和原理网上也有,这里就不细说了,每个人的路由器情况不同

添加虚拟网卡

对虚拟网卡的命名采用了分段的手法,避免冲突,应该没有人会拨一百个号吧,下面就是四条链路,每条链路双拨的情况

以下代码添加到开机启动脚本即可

link=4;mutiple=2;
for i in  `seq 2 $((link+1))`
  do
    for j in `seq 1 $mutiple`
      do
        ip link add link eth0.$i name veth${i}0${j} type macvlan
      done
done

MWAN3设置

首先还是要手动添加一次macvlan,因为之前添加过开机启动脚本,所以下次启动的时候就会自动生效,这里也是方便一次性就设置好拨号和防火墙

#!/bin/sh
link=4;mutiple=2;
k=1
for i in  `seq 1 $link`
  do
    for j in `seq 1 $mutiple`
      do
        ip link add link eth0.$i name veth${i}0${j} type macvlan
        k=$(expr $k + 1)
        uci set network.VWAN$k=interface
        uci set network.VWAN$k.proto='pppoe'
        uci set network.VWAN$k.username="username"
        uci set network.VWAN$k.password="password"
        uci set network.VWAN$k.metric="$k"
        uci set network.VWAN$k.ifname="veth${i}0${j}"
        uci set network.VWAN$k.ipv6='0'
      done
done

uci add firewall zone 1>/dev/null
uci set firewall.@zone[2]=zone
uci set firewall.@zone[2].name=pppoe
uci set firewall.@zone[2].input=ACCEPT
uci set firewall.@zone[2].output=ACCEPT
uci set firewall.@zone[2].forward=ACCEPT
uci set firewall.@zone[2].masq=1
uci set firewall.@zone[2].mtu_fix=1

for i in `uci show network | grep pppoe | awk -F. '{print $2}'`
do
  uci add_list firewall.@zone[2].network=$i
done

uci commit firewall

uci commit network

/etc/init.d/network restart
/etc/init.d/firewall restart

for i in  `seq 2 $k`
do
  uci set mwan3.VWAN$i=interface
  uci set mwan3.VWAN$i.enabled='1'
  uci set mwan3.VWAN$i.track_ip='223.6.6.6'
  uci set mwan3.VWAN$i.track_ip='223.5.5.5'
  uci set mwan3.VWAN$i.reliability='1'
  uci set mwan3.VWAN$i.count='1'
  uci set mwan3.VWAN$i.timeout='2'
  uci set mwan3.VWAN$i.interval='1'
  uci set mwan3.VWAN$i.down='1'
  uci set mwan3.VWAN$i.up='1'
done
uci commit mwan3

uci set mwan3.member_VWAN1=member
uci set mwan3.member_VWAN1.interface='wan'
uci set mwan3.member_VWAN1.metric='1'
uci set mwan3.member_VWAN1.weight='1'
for i in  `seq 2 $k`
do
  uci set mwan3.member_VWAN$i=member
  uci set mwan3.member_VWAN$i.interface="VWAN$i"
  uci set mwan3.member_VWAN$i.metric='1'
  uci set mwan3.member_VWAN$i.weight='1'
done

uci set mwan3.load_blance=policy
uci set mwan3.load_blance.last_resort='unreachable'
for i in  `seq 2 $k`
do
  uci add_list mwan3.load_blance.use_member="member_VWAN$i"
done

uci set mwan3.default_rule=rule
uci set mwan3.default_rule.use_policy='load_blance'
uci set mwan3.default_rule.proto='all'

uci commit mwan3
/etc/init.d/mwan3 restart

负载均衡

分类

对提高网速的负载均衡方式有很多种,而且应用也很普遍,这里列举下常见的:

  • 链路负载均衡,直接提高物理带宽,一般需要设备支持,比如部分路由器和交换机的链路聚合(bonding),常用于提高内网网速

  • 路由负载均衡,可以用软件实现,根据负载均衡的网络层级可以分为:

    • L3:按地址路由,不同的源地址目标地址对的连接使用不同的出口,通过简单的策略路由就可以实现
    • L4:按连接路由,连接建立时把不同的连接(例如五元组:IP,端口,协议)分配到不同的出口上

    根据负载均衡的对象可以分为:

    • Per-Flow:面向流,比如一个连接的数据包流,本文介绍的都是这种
    • Per-Packet:面向包,比如多个连接中的数据包

使用场景

负载均衡的加速效果只对部分情形有用,把两种路由负载均衡分类组合起来就可以对应到使用场景,主要还是Per-Flow

Per-Packet

L3 Per-Packet已经是ECMP做L4-Perflow的基础,但是L4下暂时没有找到合适的解决方案,原因可能是常用的TCP连接一般是有序的收发包,如果按包负载均衡,包的到达顺序易受到网络状况的影响从而带来一些问题

L3 Per-Flow

典型的一种使用场景就是BT上传的P2P传输时,到不同IP的流量会从不同的出口流出

L4 Per-Flow

不需要关心IP,只要是不同的连接就可以做负载均衡,典型的如使用多线程下载软件下载一个文件,本文主要做到的都是这一步,尽管没有做到Per-Packet那么极致,但是稍微加速下日常的应用还是有帮助的

MWAN3

OpenWrt上用的最多的负载均衡软件是mwan3,另外还有luci-app-mwan3这个软件可以直观且细致地配置负载均衡的策略,同时可以通过GUI看到负载均衡的状态

版本历史

  • mwan3在2.0版本之后开始支持IPv6:8ff00a6 on 19 Nov 2015

  • luci-app-mwan3的IPv6更新却晚了一年多:e1e5743a on 31 Mar 2017,此时luci-app-mwan3迁移到了luci仓库中

  • 对LEDE 17.01,luci-app-mwan3的源码在package仓库中,也没有IPv6的选项,那个时候的mwan3版本是2.0.2

  • 所以mwan3的LuCI界面配置IPv6负载均衡就是18.06之后的事情了(不过已经不用mwan3了)

原理

源码看起来很比较累,大致就是通过iptables给各种连接打上mark,之后再根据mark做策略路由分配出口,其他的部分在官网有介绍,其中最重要的:

Linux outgoing network traffic load-balancing is performed on a per-IP connection basis – it is not channel-bonding, where a single connection (e.g. a single download) will use multiple WAN connections simultaneously As such load-balancing will help speed multiple separate downloads or traffic generated from a group of source PCs all accessing different sites but it will not speed up a single download from one PC (unless the download is spread across multiple IP streams such as by using a download manager)

也就是说mwan3是一个L4 负载均衡器,面向的是连接,所以适用的场景也就很清楚了

ECMP

配置IPv6 NAT时意外地触发了负载均衡,而且经过一番查找得知是iproute2的特性,不需要mwan3也可以实现,但是很可能因为安装了mwan3,才会有重复添加静态路由出现ECMP的情况(这部分的根据没有在mwan3的源码中找到),之后进一步查文档,看博客才知道这是Linux内核对网络负载均衡的一种解决方案,详细的情况见于网络负载均衡:ECMP,这里只留多拨的脚本:

当然,本文的主要还是“Just For Fun”,感受下高网速带来的快感,但是想一想最普遍的多拨网络环境,单个ISP意味着就只有一个网关,到网关是Equal Cost也没什么问题,内网一般都还配置好了NAT,mwan3的各种高级的策略大多数人也用不上,ECMP或许比mwan3更合适

Linux ECMP

The OpenWrt Project is a Linux operating system targeting embedded devices. 因为ECMP的可用情况取决于内核版本,所以先来看下OpenWrt内核版本的情况吧

Linux Kernel OpenWrt 内核版本 ECMP变更 ECMP形式
1997 | 2.1.68   IPV4 ECMP 加入内核 L3 Per-packet + route cache -> L3 Per-flow
2007 | 2.6.23   IPV4 multipath cached 移除 L3 Per-packet + route cache -> L3 Per-flow
2012.9 | 3.6   IPV4 route cache 被移除 L3 Per-packet -> L3 Per-packet
2012.10 | 3.8   IPV6 ECMP 加入内核 IPv6 Flowlabel -> L4 Per-flow
2015.9 | 4.4 15.05 | 3.18 IPV4 multipath 特性重新被加回 L3 Per-packet + L3 hash -> L3 Per-flow
2016.4 | 4.7   IPV4 ECMP: 增加邻居健康检查  
2017.2 17.01 | 4.4    
2017.3 | 4.12   IPV4 ECMP: 增加 L4 Hash L3 Per-packet + L3/L4 hash -> L3/L4 Per-flow
2017.11 | 4.14   IPV6 ECMP: ICMPv6 修复  
2018.4 | 4.16   IPv6 ECMP: 支持指定权重  
2018.7 18.06 | 4.9/4.14    
2018.10 | 4.19      

这里可以推出(实际早期版本我也没有测试过),在默认的编译选项和内核版本下,较为实用的L4负载均衡,IPv4需要在18.06及之后的版本

IPv6由于使用了flowlabel作为Hash的对象,应该一开始就可以L4负载均衡,看表格的话,OpenWrt在15.05之后的版本就可以实现

我是直接从18.06开始使用ECMP,所以以下步骤在18.06上可用:

拨号

# 拨数
NUM=

# 运行并添加到启动项
for i in  `seq 1 $NUM`
do
  ip link add link eth0.2 name veth$i type macvlan
done

# 拨数较多时建议禁用IPv6及自启(在循环中添加uci set network.VWAN$i.atuo='0')
# IPv6在几个接口上开启即可达到链路带宽上限
username=
password=
for i in  `seq 1 $NUM`
do
  uci set network.VWAN$i=interface
  uci set network.VWAN$i.proto='pppoe'
  uci set network.VWAN$i.username="$username"
  uci set network.VWAN$i.password="$password"
  uci set network.VWAN$i.metric="$((i+1))"  
  uci set network.VWAN$i.ifname="veth$i"
  uci set network.VWAN$i.ipv6='0'
done
uci commit network

#这里直接添加到默认的wan zone了,免去了再次配置转发
for i in `uci show network | grep pppoe | awk -F. '{print $2}'`
do
  uci add_list firewall.@zone[1].network=$i
done
uci commit firewall

#拨数较多的话 建议分批次启动以减轻负载
for i in `seq 1 $NUM`
do
 ifup VWAN$i
done

IPv6 ECMP

把下面的脚本添加到/etc/hotplug.d/iface/文件夹下的98-ECMPv6

#!/bin/sh
[ "$ACTION" = ifup ] || exit 0
ifaces=$(ubus call network.interface dump | jsonfilter -e '$.interface[@.proto="dhcpv6" && @.up=true].interface')
for iface_6 in $ifaces
do
  [ "$INTERFACE" = $iface_6 ]  || continue
  ipv6_devices=$(ubus call network.interface dump | jsonfilter -e '$.interface[@.proto="dhcpv6" && @.up=true].device')
  nexthops=""
  for ipv6_dev in $ipv6_devices
  do
    VAR=$(echo '$.interface[@.device="'$ipv6_dev'"].route[1].nexthop')
    ipv6_gw=$(ubus call network.interface dump | jsonfilter -e $VAR)
    nexthops=${nexthops}"nexthop via $ipv6_gw  dev $ipv6_dev "
  done
  status=$(ip -6 route replace default metric 1 $nexthops 2>&1)
  break
done
logger -t ECMPv6 "Done $ipv6_devices $status"

这里主要考虑了两种IPv6的接入情况,PPPoE和DHCP(有状态和无状态),因为他们的dev是不同的,另外考虑了多个不同的IPv6网关的情况

大意:

  • ifup时先便利判断是否为DHCPv6接口,如果是的话就开始执行修改路由表(添加全部的接口),修改一次路由表结束后直接退出,不再做多的判断
  • 如果都不是的话就这样结束循环而不会执行下面的语句
  • 如果确定是DHCPv6接口up就把所有的IPv6网关做一次添加

IPv4 ECMP

先开启L4 Hash以及邻居健康检查,前者是关键,理论上后者对提高效率有较大的作用

echo "net.ipv4.fib_multipath_hash_policy=1" >> /etc/sysctl.conf
echo "net.ipv4.fib_multipath_use_neigh=1" >> /etc/sysctl.conf
sysctl -p

把下面的脚本添加到/etc/hotplug.d/iface/文件夹下的97-ECMPv4中,和上面的脚本差不多,但是只适用于IPv4 PPPoE的情况了(多个DHCP的情况再改改呗)

#!/bin/sh
case "$ACTION" in
  ifup)
        ifaces=$(ubus call network.interface dump | jsonfilter -e '$.interface[@.proto="pppoe" && @.up=true].interface')         
        nexthops=""
        for iface_4 in $ifaces
        do
          [ "$INTERFACE" = $iface_4 ]  || continue
          for iface_4 in $ifaces
          do
            ipv4_dev=$(ifstatus $iface_4 | jsonfilter -e '$.l3_device')
            ipv4_gw=$(ifstatus $iface_4 | jsonfilter -e '$.route[0].nexthop')
            nexthops=${nexthops}"nexthop via $ipv4_gw  dev $ipv4_dev "
          done
          status=$(ip route replace default metric 1 $nexthops 2>&1)
          logger -t ECMPv4 "Done $ifaces $status"
          break
        done
        ;;
  ifdown)
        ifaces=$(ubus call network.interface dump | jsonfilter -e '$.interface[@.proto="pppoe" && @.up=true].interface')         
        nexthops=""
        for iface_4 in $ifaces
          do
            ipv4_dev=$(ifstatus $iface_4 | jsonfilter -e '$.l3_device')
            ipv4_gw=$(ifstatus $iface_4 | jsonfilter -e '$.route[0].nexthop')
            nexthops=${nexthops}"nexthop via $ipv4_gw  dev $ipv4_dev "
          done
        status=$(ip route replace default metric 1 $nexthops 2>&1)
        logger -t ECMPv4 "Done $ifaces $status "
     ;;
esac

IPv4的ECMP和IPv6其实有较大的不同,比如IPv4 ECMP的一个nexthop断连之后会导致整个ECMP失效,故脚本稍微长一点

已知的问题

如果是传入连接,如从外部使用IPv4通过IP访问路由器,会出现间断的情况,如果路由器还要提供对外服务的话还要进一步设置

应用设置

浏览器

参考自:浏览器 HTTP 并发请求规则探讨

Chrome对单个域名可以并发的请求是六个,也就有可能六个连接会被分到多个出口上,进而起到一定的加速效果,多线程下载同理,连接数越高,利用率就越高

浏览器这样做出发点是好的,但有时候我们想要个性化定制(比如做个插件开个挂之类的😈)就没那么方便了,那能不能修改限制上限呢?

Chrome 的并发请求数量是不能修改的,因为已经固定写到源码中了,具体可以查看:https://chromium.googlesource.com/chromium/src/+/65.0.3325.162/net/socket/client_socket_pool_manager.cc#44,可以使用 Firefox,如果非要修改可以尝试修改源码然后自行编译。

Firefox 是可以修改的:

  • 首先在地址栏输入 about:config
  • 搜索 http.max 关键字
  • network.http.max-connections 为全局 HTTP 同时最大的连接数量,默认为 900;
  • network.http.max-persistent-connections-per-server 为单个域名最大链接数量,默认为 6

至于修改到多少,个人尝试了下修改到32,利用率看起来还可以,尤其是快速滚动,刷新多图片的网页的时候比Chrome快很多

下载

  • P2P下载,尤其是连接的peer数量较多的时候,效率非常的可观
  • 多线程下载,这个软件就比较多了