linux tc流量控制(一):classless qdisc

什么是tc

tc全称为traffic control,是iproute2包中控制内核中流量的工具。在内核的网络协议栈中,专门有这样一个处理网络流量的地方(在XDP之后,netfilter之前),tc就是在这个地方读取网络数据包(此时已经是sk_buffer)进行控制、分发、丢弃等操作。需要注意的是,tc既可以处理传出的数据包(egress),也可以处理传入的数据包(ingress),但对传入的数据包处理的功能较少。本文不涉及ingress内容。

核心概念:qdisc

我们使用tc的时候,最先会遇到一个叫做qdisc的名词,其实我们经常能看见它,就是在每次我们执行ip命令的时时候

root@ubuntu:~# ip l
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
    link/ether 00:0c:29:58:48:55 brd ff:ff:ff:ff:ff:ff
    altname enp2s1

比如在这里,我们可以看到每个网络设备都在mtu后面都有一个qdisc,qdisc后面还有一个词,上面的lo的qdisc是noqueue,ens33的qdisc是fq_codel。

qdisc实际是queueing discipline的缩写,我们可以将其看作一个具有一定规则的队列。当tc处理网络包时,会将包入队到qdisc中,这些包会根据指定的规则被内核按照一定顺序取出。tc中已经内置了很多不同的qdisc,有些qdisc可以带参数,比如ens33上面的qdisc参数是这样的。

root@ubuntu:~# tc qdisc show dev ens33
qdisc fq_codel 0: root refcnt 2 limit 10240p flows 1024 quantum 1514 target 5.0ms interval 100.0ms memory_limit 32Mb ecn 

注意到lo上面的qdisc是noqueue,那我们可不可以把ens33的qdisc删掉呢,尝试:

root@ubuntu:~# tc qdisc del dev ens33 root
Error: Cannot delete qdisc with handle of zero.

先暂时忽略命令里的root。这条删除命令失败,网络上的一篇文章给出的结论是,对于这种非虚拟的网络设备(虽然是虚拟机的网卡,它也不是虚拟设备哈),不可以将其qdisc删除掉使其设置为noqueue,所以如果我们想修改他的qdisc,不能先删除再添加,但可以通过add和replace进行修改,该文章也提到了add和replace效果是一样的。对此我有不同的看法。

首先试试再添加一个其他类型的qdisc上去:

root@ubuntu:~# tc qdisc add dev ens33 root pfifo
root@ubuntu:~# tc qdisc show dev ens33
qdisc pfifo 8005: root refcnt 2 limit 1000p

添加成功,那我们在添加一个其他类型的qdisc上去:

root@ubuntu:~# tc qdisc add dev ens33 root pfifo_fast
Error: Exclusivity flag on, cannot modify.
root@ubuntu:~# tc qdisc show dev ens33
qdisc pfifo 8005: root refcnt 2 limit 1000p

这样会报错,还是使用原先的qdisc。此时我们尝试使用del进行删除

root@ubuntu:~# tc qdisc del dev ens33 root
root@ubuntu:~# tc qdisc show dev ens33
qdisc fq_codel 0: root refcnt 2 limit 10240p flows 1024 quantum 1514 target 5.0ms interval 100.0ms memory_limit 32Mb ecn 

发现它又变回了fq_codel。如果我们这里使用replace再删除,结果是这样的

root@ubuntu:~# tc qdisc replace dev ens33 root pfifo_fast
root@ubuntu:~# tc qdisc show dev ens33
qdisc pfifo_fast 8006: root refcnt 2 bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
root@ubuntu:~# tc qdisc replace dev ens33 root pfifo
root@ubuntu:~# tc qdisc show dev ens33
qdisc pfifo 8007: root refcnt 2 limit 1000p
root@ubuntu:~# tc qdisc del dev ens33 root
root@ubuntu:~# tc qdisc del dev ens33 root
Error: Cannot delete qdisc with handle of zero.
root@ubuntu:~# tc qdisc show dev ens33
qdisc fq_codel 0: root refcnt 2 limit 10240p flows 1024 quantum 1514 target 5.0ms interval 100.0ms memory_limit 32Mb ecn 

所以我们可以推测:fq_codel是非虚拟设备的默认值,此时网络设备并没有设置qdisc,所以执行删除会报错,但我们可以为其设定qdisc(使用add或者replace),此时设置了qdisc,删除便不会失败,删除后qdisc又变成了默认值。注意到lo的值是noqueue,如果我们用ip link命令手动添加设备,它们的qdisc也是noqueue,所以我们也可以推测虚拟设备的默认值是noqueue。

这里的命令中有一个root参数,实际上这个参数表示对出口流量(egress)处理的根节点,从上面的命令可以看出,一个网络设备的root点只能设置一个qdisc。执行tc qdisc show命令,可以看到当前qdisc的参数,有些qdisc不可以指定参数,有些可以,如果需要指定参数,可以在add命令的结尾添加:

tc qdisc add dev <dev> root <qdisc> <qdisc-param>

qdisc有两类,一类qdisc比较简单,向上面展示的一样,只能设置一些参数然后在设备的(egress)root点生效,这种的叫做classless qdisc。另一类比较复杂,他们内部还包括叫做class的组件,还可以进一步将包传递给其他的qdisc,所有的数据包在一个类似树的结构中流动,这种叫做classful qdisc。这篇文章只会介绍相对简单的classless qdisc。

一些classless qdisc

tc内置了以下的classless qdisc

  • choke
  • codel
  • bfifo、qfifo
  • fq
  • fq_codel
  • gred
  • hhf
  • ingress
  • mqprio
  • multiq
  • netem
  • pfifo_fast
  • pie
  • red
  • rr
  • sfb
  • sfq
  • tbf

这里介绍其中的几种qdisc

tbf

tbf全称为token bucket filter,从名字可以看出,tbf具有令牌和桶两个概念。其工作原理如下图所示。

到来的数据包需要获取的令牌与数据包的大小有关,并且任意大小的数据包都需要消耗令牌,如果某个数据包没有足够的令牌,将会放入队列中,等待令牌补充,后续到来的数据包同样进入队列中等待,直到队列被填满,后续包将被丢弃。可以看到,如果我们限制桶的容量,或者限制令牌的补充速度,就可以限制整体的包处理速度。

tbf可以配置的参数如下

  • limit:队列可以保存的数据包的容量,bytes单位。或者可以通过另一个参数latency来指定类似的等待效果
  • burst:桶的容量,bytes单位
  • rate:令牌添加数量
  • mpu:指定最少需要的令牌数量
  • peakrate:桶的最大消耗速率,tbf通过添加第二个容量较小的桶来实现
  • mtu/minburst:第二个桶的容量

测试一下tbf限速的效果

安装iperf3启动服务,分别测试noqueue和tbf限制时的效果:

#另一终端执行 iperf3 -s 
​
root@ubuntu:~# ip l show lo
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
​
root@ubuntu:~# iperf3 -c 127.0.0.1
Connecting to host 127.0.0.1, port 5201
[  5] local 127.0.0.1 port 56294 connected to 127.0.0.1 port 5201
[ ID] Interval           Transfer     Bitrate         Retr  Cwnd
[  5]   0.00-1.00   sec  4.49 GBytes  38.6 Gbits/sec    0   3.25 MBytes       
[  5]   1.00-2.00   sec  4.47 GBytes  38.4 Gbits/sec    0   3.25 MBytes       
[  5]   2.00-3.00   sec  4.41 GBytes  37.9 Gbits/sec    0   3.25 MBytes       
[  5]   3.00-4.00   sec  4.39 GBytes  37.8 Gbits/sec    0   3.25 MBytes       
[  5]   4.00-5.00   sec  4.63 GBytes  39.8 Gbits/sec    0   3.25 MBytes       
[  5]   5.00-6.00   sec  4.72 GBytes  40.5 Gbits/sec    0   3.25 MBytes       
[  5]   6.00-7.00   sec  4.58 GBytes  39.4 Gbits/sec    0   3.25 MBytes       
[  5]   7.00-8.00   sec  4.59 GBytes  39.4 Gbits/sec    0   3.25 MBytes       
[  5]   8.00-9.00   sec  4.61 GBytes  39.6 Gbits/sec    0   3.25 MBytes       
[  5]   9.00-10.00  sec  4.66 GBytes  40.0 Gbits/sec    0   3.25 MBytes       
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bitrate         Retr
[  5]   0.00-10.00  sec  45.6 GBytes  39.1 Gbits/sec    0             sender
[  5]   0.00-10.00  sec  45.6 GBytes  39.1 Gbits/sec                  receiver
​
iperf Done.
​
​
root@ubuntu:~# tc qdisc add dev lo  root tbf  limit 100mb rate 10mbit burst 5mb
root@ubuntu:~# tc qdisc show dev lo
qdisc tbf 800a: root refcnt 2 rate 10Mbit burst 5Mb lat 79.7s 
root@ubuntu:~# iperf3 -c 127.0.0.1
Connecting to host 127.0.0.1, port 5201
[  5] local 127.0.0.1 port 56304 connected to 127.0.0.1 port 5201
[ ID] Interval           Transfer     Bitrate         Retr  Cwnd
[  5]   0.00-1.00   sec  8.75 MBytes  73.3 Mbits/sec    1   1.37 MBytes       
[  5]   1.00-2.00   sec  1.25 MBytes  10.5 Mbits/sec    0   1.37 MBytes       
[  5]   2.00-3.00   sec  1.25 MBytes  10.5 Mbits/sec    0   1.37 MBytes       
[  5]   3.00-4.00   sec  1.25 MBytes  10.5 Mbits/sec    0   1.37 MBytes       
[  5]   4.00-5.00   sec  1.25 MBytes  10.5 Mbits/sec    0   1.37 MBytes       
[  5]   5.00-6.00   sec  1.25 MBytes  10.5 Mbits/sec    0   1.37 MBytes       
[  5]   6.00-7.00   sec  1.25 MBytes  10.5 Mbits/sec    0   1.37 MBytes       
[  5]   7.00-8.00   sec  1.25 MBytes  10.5 Mbits/sec    0   1.37 MBytes       
[  5]   8.00-9.00   sec  1.25 MBytes  10.5 Mbits/sec    0   1.37 MBytes       
[  5]   9.00-10.00  sec  1.25 MBytes  10.5 Mbits/sec    0   1.37 MBytes       
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bitrate         Retr
[  5]   0.00-10.00  sec  20.0 MBytes  16.8 Mbits/sec    1             sender
[  5]   0.00-10.09  sec  16.9 MBytes  14.0 Mbits/sec                  receiver
​
iperf Done.
​

可以看到,网络带宽明显的下降了

比较神奇的是,tc的man page里面介绍tbf是classless qdisc,而tc-tbf里面说它是classful qdisc。尝试一下给tbf添加class,结果会报错,所以tbf应为classless qdisc吧,怀疑tc-tbf的man-page把classic写错成classful了。

root@ubuntu:~# tc qdisc add dev lo root handle 1:0 tbf limit 100M rate 100Mbit burst 100M
root@ubuntu:~# tc qdisc show dev lo
qdisc tbf 1: root refcnt 2 rate 100Mbit burst 100Mb lat 0us 
qdisc ingress ffff: parent ffff:fff1 ---------------- 
root@ubuntu:~# tc class add dev lo parent 1: classid 1:1 htb rate 100Mbit
RTNETLINK answers: File exists

netem

netem时network emulator的缩写,可以模拟网络延迟、丢包率等网络特征。还是拿本地环路接口进行测试,手动将其延迟变为100ms(ping 发送和接收icmp延迟翻倍),以及添加丢包率

root@ubuntu:~# tc qdisc del dev lo root
root@ubuntu:~# ping 127.0.0.1 -c 3
PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.034 ms
64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.052 ms
64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.040 ms
​
--- 127.0.0.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2026ms
rtt min/avg/max/mdev = 0.034/0.042/0.052/0.007 ms
root@ubuntu:~# tc qdisc add dev lo root netem delay 100ms
root@ubuntu:~# ping 127.0.0.1 -c 3
PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=200 ms
64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=200 ms
64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=201 ms
​
--- 127.0.0.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2004ms
rtt min/avg/max/mdev = 200.426/200.586/200.855/0.191 ms
​
root@ubuntu:~# tc qdisc del dev lo root
​
root@ubuntu:~# iperf3 -c 127.0.0.1 -u -i 10 -b 100M
Connecting to host 127.0.0.1, port 5201
[  5] local 127.0.0.1 port 58834 connected to 127.0.0.1 port 5201
[ ID] Interval           Transfer     Bitrate         Total Datagrams
[  5]   0.00-10.00  sec   119 MBytes   100 Mbits/sec  3815  
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bitrate         Jitter    Lost/Total Datagrams
[  5]   0.00-10.00  sec   119 MBytes   100 Mbits/sec  0.000 ms  0/3815 (0%)  sender
[  5]   0.00-10.00  sec   119 MBytes   100 Mbits/sec  0.002 ms  0/3815 (0%)  receiver
​
iperf Done.
root@ubuntu:~# tc qdisc add dev lo root netem loss 5%
root@ubuntu:~# iperf3 -c 127.0.0.1 -u -i 10 -b 100M
Connecting to host 127.0.0.1, port 5201
iperf3: error - unable to read from stream socket: Resource temporarily unavailable
root@ubuntu:~# tc qdisc change dev lo root netem loss 2%
root@ubuntu:~# iperf3 -c 127.0.0.1 -u -i 10 -b 100M
Connecting to host 127.0.0.1, port 5201
[  5] local 127.0.0.1 port 45366 connected to 127.0.0.1 port 5201
[ ID] Interval           Transfer     Bitrate         Total Datagrams
[  5]   0.00-10.00  sec   119 MBytes   100 Mbits/sec  3815  
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bitrate         Jitter    Lost/Total Datagrams
[  5]   0.00-10.00  sec   119 MBytes   100 Mbits/sec  0.000 ms  0/3815 (0%)  sender
[  5]   0.00-10.00  sec   117 MBytes  97.9 Mbits/sec  0.002 ms  79/3815 (2.1%)  receiver
​
iperf Done.
​

bfifo、pfifo

这两个应该是最简单的qdisc了,数据包的处理只遵循先入先出规则,bfifo的队列的容量为能够保存数据包的大小,pfifo的队列容量为包的数目

pfifo_fast

很多文章说pfifo_fast是默认的qdisc,但是我的虚拟机里面是fq_codel,可能新版本的内核更换了默认qdisc。pfifo_fast可以看作是增强版的fifo,内部维护了三个不同优先级的队列(band 0 1 2),处理数据包会根据优先级(0最高、2最低)依次处理队列中的包,即最高优先级队列里的包处理结束后才会处理下一个优先级的队列。

数据包进入其中哪一个队列取决于ip数据包头的tos(type of service)段,tos段的内容是这样的:

7 6 5 4 3 2 1 0
(IP Precedence) (IP Precedence) (IP Precedence) TOS(lowdelay) TOS(throughput) TOS(reliability) TOS(lowcost ) MBZ(Must be zero)
  • 最低的bit位:MBZ位,一定是零
  • 1~4:TOS段,这些bit任意数量可以为0或者为1,当只设定一个bit时的含义如下
    • 1000:最小延迟(minial delay,md)
    • 0100:最大带宽(maximize throughput,mt)
    • 0010:最大可靠性(Maximize reliability,mr)
    • 0001:最小开销(Minimize monetary cost,mmc)
  • 5~7:IP优先权:这个字段的值越高,标记更优先被处理,0为正常处理

上面所述的1~4bit的TOS段决定了包将会进入哪个band,如果IP优先权设置为0的话,整个8bit的TOS段的值和对应的band如下

band 0 band 1 band 2
0x10 md 0x0 0x8 mt
0x12 mmc + md 0x2 mmc 0xa mmc + mt
0x14 mr+md 0x4 mr 0xc mr + mt
0x16 mmc + mr + md 0x6 mmc + mr 0xe mmc + mr + mt
0x18 mt + md
0x1a mmc + mt + md
0x1c mr + mt + md
0x1e mmc + mr + mt + md

详细的信息可以在tc-prio的man-page看到。

总结

使用tc可以为网络设备设置一类叫做classless qdisc的排队规则,这类规则依照不同的算法,在网络设备的tc处理点中,对向外部发送的数据包进行处理,可以进行限制发送速度、更改发送顺序、丢包等操作。部分classless qdisc还可以使用参数配置,使其以想要的效果运行。但是,classless qdisc无法将数据包进行分类,不能进行更精确的控制。后续将介绍classful qdisc,我们可以用它来实现更多的功能。

猜你喜欢

转载自blog.csdn.net/buhuidage/article/details/128331608
今日推荐