TC流量控制

Introduction

Linux操作系统中的流量控制器TC(Traffic Control)用于Linux内核的流量控制,它利用队列规定建立处理数据包的队列,并定义队列中的数据包被发送的方式,从而实现对流量的控制。

TC模块实现流量控制功能使用的队列规定分为两类,一类是无类队列规定,另一类是分类队列规定。无类队列规定相对简单,而分类队列规定则引出了分类和过滤器等概念,使其流量控制功能增强。

无类队列规定是对进入网络设备(网卡)的数据流不加区分统一对待的队列规定。使用无类队列规定形成的队列能够接受数据包以及重新编排、延迟或丢弃数据包。这类队列规定形成的队列可以对整个网络设备( 网卡)的流量进行整形,但不能细分各种情况。常用的无类队列规定主要有pfifo _fast (先进现出)、TBF(令牌桶过滤器)、SFQ(随机公平队列)、ID (前向随机丢包)等等。这类队列规定使用的流量整形手段主要是排序、限速和丢包。

分类队列规定是对进入网络设备的数据包根据不同的需求以分类的方式区分对待的队列规定。数据包进入一个分类的队列后,它就需要被送到某一个类中,也就是说需要对数据包做分类处理。对数据包进行分类的工具是过滤器,过滤器会返回一个决定,队列规定就根据这个决定把数据包送入相应的类进行排队。每个子类都可以再次使用它们的过滤器进行进一步的分类。直到不需要进一步分类时,数据包才进入该类包含的队列排队。除了能够包含其它队列规定之外,绝大多数分类的队列规定还能够对流量进行整形。这对于需要同时进行调度(如使用SFQ)和流量控制的场合非常有用。

Principle

一 内核处理流程示意图

	     用户级程序
                 ^
                 |
 +---------------+-------------------------------------+
 |               Y                                     |
 |    -------> IP 协议栈                                |
 |    |          |                                     |
 |    |          Y                                     |
 |    |          Y                                     |
 |    ^          |                                     |
 |    |  / ------> 转发-------->                       |
 |    ^ /                      |                       |
 |    |/                       Y                       |
 |    |                        |                       |
 |  多路分配器                  Y      /-队列规定 1-\    |
 |    |                       Egress /--队列规定 2--\   |
In--->->Ingress               分类器-----队列规定 3------|-->Out
 |     队列规定                       \__队列规定 4__/   |
 |                                    \-队列规定 N_/    |
 |                                                     |
 +-----------------------------------------------------+

接收包从输入接口(Input Interface)进来后,经过流量限制(Ingress Policing)丢弃不符合规定的数据包,由输入多路分配器(Input De-Multiplexing)进行判断选择:如果接收包的目的是本主机,那么将该包送给上层处理;否则需要进行转发,将接收包交到转发块(Forwarding Block)处理。转发块同时也接收本主机上层(TCP、UDP等)产生的包。转发块通过查看路由表,决定所处理包的下一跳。然后对包按照定义的队列规定进行排列以便将它们传送到输出接口(Output Interface)。Linux流量控制主要是在输出接口排列时进行处理和实现的。

二 TC规则

1.流量控制方式

流量控制包括以下几种方式:

SHAPING(限制) 当流量被限制,它的传输速率就被控制在某个值以下。限制值可以大大小于有效带宽,这样可以平滑突发数据流量,使网络更为稳定。shaping(限制)只适用于向外的流量。

SCHEDULING(调度) 通过调度数据包的传输,可以在带宽范围内,按照优先级分配带宽。SCHEDULING(调度)也只适于向外的流量。

POLICING(策略) SHAPING用于处理向外的流量,而POLICIING(策略)用于处理接收到的数据。

DROPPING(丢弃) 如果流量超过某个设定的带宽,就丢弃数据包,不管是向内还是向外。

2.流量控制处理对象

流量的处理由三种对象控制,它们是:qdisc(排队规则)、class(类别)和filter(过滤器)。

Qdisc(排队规则)是queueing discipline的简写,它是理解流量控制(traffic control)的基础。无论何时,内核如果需要通过某个网络接口发送数据包,它都需要按照为这个接口配置的qdisc(排队规则)把数据包加入队列。然后,内核会尽可能多地从qdisc里面取出数据包,把它们交给网络适配器驱动模块。

TC的大多数队列规定(qdisc)都是用于输出方向的,输入方向只有一个队列规定,即Ingress qdisc。因此一般主要是限制网卡发送的数据包,而不是限制网卡接收的数据包。可以通过定义的若干个队列规定来改变数据包的发送次序,实现传输速率的控制。

而Ingress qdisc本身的功能很有限,只能通过Filter过滤出需要处理的数据包Drop掉。通过Ingress qdisc可以把输入方向的数据包重定向到一个虚拟设备ifb,然后在ifb的输出方向可以配置多种qdisc,就可以达到对输入方向的流量做队列调度的目的。

Class(类别)组成一个树,每个类都只有一个父类,而一个类可以有多个子类。某些QDisc(例如:CBQ和HTB)允许在运行时动态添加类,而其它的QDisc(例如:PRIO)不允许动态建立类。允许动态添加类的QDisc可以有零个或者多个子类,由它们为数据包排队。

此外,每个类都有一个叶子QDisc,默认情况下,这个叶子QDisc使用pfifo的方式排队,我们也可以使用其它类型的QDisc代替这个默认的QDisc。当一个数据包进入一个分类QDisc,它会被归入某个子类。

3.命名规则

所有的QDisc、类和过滤器都有ID。ID可以手工设置,也可以有内核自动分配。

ID由一个主序列号和一个从序列号组成,两个数字用一个冒号分开。

QDISC,一个QDisc会被分配一个主序列号,叫做句柄(handle),然后把从序列号作为类的命名空间。句柄采用象10:一样的表达方式。习惯上,需要为有子类的QDisc显式地分配一个句柄。

类(CLASS),在同一个QDisc里面的类分享这个QDisc的主序列号,但是每个类都有自己的从序列号,叫做类识别符(classid)。类识别符只与父QDisc有关,和父类无关。类的命名习惯和QDisc的相同。

过滤器(FILTER),过滤器的ID有三部分,只有在对过滤器进行散列组织才会用到。详情请参考tc-filters手册页。

HOWTO

一 限制下载

先看个简单的限制下载速度的例子

#创建一个HTB的根,默认类为1:20
tc qdisc add dev eth0 root handle 1: htb default 20

#创建一个HTB的类,流量的限制就是在这里限制的,并设置突发。
tc class add dev eth0 parent 1: classid 1:1 htb rate 800kbit ceil 800kbit burst 80k

#创建一个过滤规则把要限制流量的数据过滤出来,并发给上面的类来限制速度。
tc filter add dev eth0 parent 1: prio 1 protocol ip u32 match ip src 1.1.1.1/32 match ip sport 80 0xffff flowid 1:1

通过这样简单的配置,就实现了对web服务器下载流量的限制,限制每个客户端流量为800Kbits。

参数说明:

rate     rate allocated to this class (class can still borrow) 是一个类保证得到的带宽值,如果有不只一个类,请保证所有子类总和是小于或等于父类
burst    max bytes burst which can be accumulated during idle period  突发流量
ceil     definite upper class rate (no borrows) 是一个类最大能得到带宽值
prio     priority of leaf; lower are served first 是优先权的设置,数值越大,优先权越小,如果是分配剩余带宽,就是数值小的会最优先取得剩余的空闲的带宽权

从上面这个例子中,看到TC实现流量控制的基本步骤——建立队列,建立分类,建立过滤器。

通过以下命令可以查看队列,分类,过滤器的状态:

[root@test ~]# tc qdisc ls dev eth0		##列出当前的队列规定
qdisc htb 1: r2q 10 default 20 direct_packets_stat 611

[root@test ~]# tc class ls dev eth0		##列出当前的分类
class htb 1:1 root prio 0 rate 800000bit ceil 800000bit burst 80Kb cburst 2000b

[root@test ~]# tc filter ls dev eth0		##列出当前的过滤器
filter parent 1: protocol ip pref 1 u32
filter parent 1: protocol ip pref 1 u32 fh 800: ht divisor 1
filter parent 1: protocol ip pref 1 u32 fh 800::800 order 2048 key ht 800 bkt 0 flowid 1:1
  match 0a795f40/ffffffff at 12
  match 00500000/ffff0000 at 20
[root@test ~]# tc -s -d qdisc show dev eth0		##显示当前队列的详细状态数据
qdisc htb 1: r2q 10 default 20 direct_packets_stat 5356 ver 3.17
 Sent 415693 bytes 5356 pkt (dropped 0, overlimits 0 requeues 0)
 rate 0bit 0pps backlog 0b 0p requeues 0

[root@test ~]# tc -s -d filter show dev eth0		##显示当前过滤器的详细状态数据
filter parent 1: protocol ip pref 1 u32
filter parent 1: protocol ip pref 1 u32 fh 800: ht divisor 1
filter parent 1: protocol ip pref 1 u32 fh 800::800 order 2048 key ht 800 bkt 0 flowid 1:1  (rule hit 6626 success 0)
  match 0a795f40/ffffffff at 12 (success 6626 )
  match 00500000/ffff0000 at 20 (success 0 )

通过以下命令可以删除添加的队列规定:

[root@test ~]# tc qdisc del dev eth0 root

TC其他的命令如替换,改变等,可以查看命令的help或man

[root@test ~]# tc qdisc help
Usage: tc qdisc [ add | del | replace | change | get ] dev STRING
       [ handle QHANDLE ] [ root | ingress | parent CLASSID ]
       [ estimator INTERVAL TIME_CONSTANT ]
       [ [ QDISC_KIND ] [ help | OPTIONS ] ]

       tc qdisc show [ dev STRING ] [ingress]

再详细说一下U32过滤器,U32可以根据数据包中各个字段进行匹配,非常强大。

从我们之前的例子中来说明一下U32过滤器的用法,命令如下:

u32 match ip src 1.1.1.1/32 match ip sport 80 0xffff

这条匹配的意思是源地址为1.1.1.1,源端口为80的数据包,我们的例子是对一台web服务器限下载速度,对于下载来说,数据包都是从web服务器发出的。

再看一下查看过滤器状态时的显示:

match 01010101/ffffffff at 12 01010101转换为10进制就是上面的IP,ffffffff是掩码,at 12 代表从数据包的ip头的12字节开始匹配,从IP头结构可知第13字节开始是IP源地址 match 00500000/ffff0000 at 20 00500000实际是源端口和目的端口组合,一共占4个字节,前2个字节是源端口,50转换成10进制就是80端口,ffff0000相当于掩码,源端口严格限定80,目的端口不限

同理,根据源/目的地址:

match ip src 0.0.0.0/0  

match ip dst 1.2.3.0/24  

根据源/目的端口可以这样表示:

match ip sport 80 0xffff  

match ip dport 80 0xffff

根据IP协议:

match ip protocol (udp tcp icmp gre ipsec)

比如icmp协议是1 match ip protocol 1 0xff

二 限制上传

因为上传对于服务器来说,是入站流量,因此只能通过Ingress qdisc来处理,但Ingress qdisc本身功能很弱,所以我们这里使用虚拟设备ifb来处理入站数据

首先需要加载内核模块,启用虚拟网卡设备ifb0

[root@test ~]# modprobe ifb
[root@test ~]# ip link set dev ifb0 up

然后对入站流量做设置,镜像到ifb0上

[root@test ~]# tc qdisc add dev eth0 ingress
[root@test ~]# tc filter add dev eth0 parent ffff: protocol ip u32 match u32 0 0 flowid 1:1 action mirred egress redirect dev ifb0

然后我们就可以对ifb0接口的out方向流量做各种限制了

[root@test ~]# tc qdisc add dev ifb0 root handle 1: htb default 1
[root@test ~]# tc class add dev ifb0 parent 1: classid 1:1 htb rate 800kbit ceil 800kbit burst 80k
[root@test ~]# tc filter add dev ifb0 parent 1: prio 1 protocol ip u32 match ip dst 1.1.1.1/32 match ip dport 80 0xffff flowid 1:1

上传一个文件测试限制是否生效

[root@test ~]# tc -s -d filter show dev ifb0
filter parent 1: protocol ip pref 1 u32
filter parent 1: protocol ip pref 1 u32 fh 800: ht divisor 1
filter parent 1: protocol ip pref 1 u32 fh 800::800 order 2048 key ht 800 bkt 0 flowid 1:1  (rule hit 98978 success 7718)
  match 01010101/ffffffff at 16 (success 94889 )
  match 00000050/0000ffff at 20 (success 7718 )

[root@test ~]# tc -s -d class show dev ifb0
class htb 1:1 root prio 0 quantum 10000 rate 800000bit ceil 800000bit burst 80Kb/8 mpu 0b overhead 0b cburst 2000b/8 mpu 0b overhead 0b level 0
 Sent 20401293 bytes 99941 pkt (dropped 1068, overlimits 0 requeues 0)
 rate 22928bit 39pps backlog 0b 0p requeues 0
 lended: 99941 borrowed: 0 giants: 0
 tokens: 818160 ctokens: 18960

[root@test ~]# tc -s -d qdisc show dev ifb0
qdisc htb 1: r2q 10 default 1 direct_packets_stat 116 ver 3.17
 Sent 20523886 bytes 101601 pkt (dropped 1068, overlimits 22466 requeues 0)
 rate 0bit 0pps backlog 0b 0p requeues 0

从上面的数据中可以看出,有1068个包被Drop掉了。

三 一点思考

TC适合用在什么场景下?

TC适合用于限制出站流量的场景,比如提供软件下载的服务器。因为TC对出站流量有很多种队列规定可用,而且在服务器上控制发送数据的速率,真正实现了对带宽占用的减少。

TC并不太适合用于限制入站流量的场景。TC入站流量限制的功能很弱,而在作为接收端的服务器上做限制,超过速率限制后,服务器端是将数据包drop掉了,导致发送端重传,一定程度上造成了资源的浪费,用户体验的降低。