0%

Linux Traffic Control

流量控制(Traffic Controltc)是Linux内核提供的流量限速、整形和策略控制机制。

它以qdisc-class-filter的树形结构来实现对流量的分层控制 :

Traffic Control

Traffic Control

tc最佳的参考就是Linux Traffic Control HOWTO,详细介绍了tc的原理和使用方法。

基本组成

从上图中可以看到,tc由qdiscfitlerclass三部分组成:

  • qdisc通过队列将数据包缓存起来,用来控制网络收发的速度
  • class用来表示控制策略
  • filter用来将数据包划分到具体的控制策略中

qdisc

qdisc,全称是 queueing discipline,通过队列将数据包缓存起来,用来控制网络收发的速度。实际上,每个网卡都有一个关联的qdisc。每当内核需要将报文分组从网卡发送出去,都会首先将该报文分组添加到该网卡所配置的队列中,由该队列决定报文分组的发送顺序。因此可以说,所有的流量控制都发生在队列中。它包括以下几种:

  • 无分类qdisc(只能应用于root队列)
    • [p|b]fifo:简单先进先出
    • pfifo_fast:根据数据包的tos将队列划分到3个band,每个band内部先进先出
    • redRandom Early Detection,带带宽接近限制时随机丢包,适合高带宽应用
    • sfqStochastic Fairness Queueing,按照会话对流量排序并循环发送每个会话的数据包
    • tbfToken Bucket Filter,只允许以不超过事先设定的速率到来的数据包通过 , 但可能允许短暂突发流量朝过设定值
  • 有分类qdisc(可以包括多个队列)
    • cbqClass Based Queueing,借助EWMA(exponential weighted moving average, 指数加权移动均值 ) 算法确认链路的闲置时间足够长 , 以达到降低链路实际带宽的目的。如果发生越限 ,CBQ 就会禁止发包一段时间。
    • htbHierarchy Token Bucket,在tbf的基础上增加了分层
    • prio:分类优先算法并不进行整形 , 它仅仅根据你配置的过滤器把流量进一步细分。缺省会自动创建三个FIFO类。

注意,一般说到qdisc都是指egress qdisc。每块网卡实际上还可以添加一个ingress qdisc,不过它有诸多的限制

  • ingress qdisc不能包含子类,而只能作过滤
  • ingress qdisc只能用于简单的整形

如果相对ingress方向作流量控制的话,可以借助 ifb( Intermediate Functional Block) 内核模块。因为流入网络接口的流量是无法直接控制的,那么就需要把流入的包导入(通过 tc action)到一个中间的队列,该队列在 ifb 设备上,然后让这些包重走 tc 层,最后流入的包再重新入栈,流出的包重新出栈。

Intermediate Funcational Block

filter

filter用来将数据包划分到具体的控制策略中,包括以下几种:

  • u32:根据协议、IP、端口等过滤数据包
  • fwmark:根据iptables MARK来过滤数据包
  • tos:根据tos字段过滤数据包

class

class用来表示控制策略,只用于有分类的qdisc上。每个class要么包含多个子类,要么只包含一个子qdisc。当然,每个class还包括一些列的filter,控制数据包流向不同的子类,或者是直接丢掉。

使用TC

在Linux中,流量控制都是通过TC这个工具来完成的。通常,要对网卡进行流量控制的配置,需要进行如下的步骤:

 - 为网卡配置一个队列;
 - 在该队列上建立分类
 - 根据需要建立子队列和子分类
 - 为每个分类建立过滤器

在Linux中,可以配置很多类型的队列,比如CBQ、HTB等,其中CBQ 比较复杂,不容易理解。HTB( Hierarchical Token Bucket )是一个可分类的队列, 与其他复杂的队列类型相比,HTB具有功能强大、配置简单及容易上手等优点。在TC中,使用”major:minor”这样的句柄来标识队列类别,其中major和minor都是数字。

对于队列来说,minor总是为0,即”major:0”这样的形式,也可以简写为”major: “比如,队列1:0可以简写为1:。需要注意的是,major在一个网卡的所有队列中必须是惟一的。对于类别来说,其major必须和它的父类别或父队列的major相同,而minor在一个队列内部则必须是惟一的(因为类别肯定是包含在某个队列中的)。举个例子,如果队列2:包含两个类别,则这两个类别的句柄必须是2:x这样的形式,并且它们的x不能相同,比如2:1和2:2。

下面,将以HTB队列为主,结合需求来讲述TC的使用。假设eth0出口有100mbit/s的带宽,分配给WWW、E-mail和Telnet三种数据流量,其中分配给WWW的带宽为40Mbit/s,分配给Email的带宽为40Mbit/s,分配给Telnet的带宽为20Mbit/S。

创建HTB队列

有关队列的TC命令的一般形式为:

1
#tc qdisc [add | change | replace | link] dev DEV [parent qdisk-id |root] [handle qdisc-id] qdisc [qdisc specific parameters]

首先,需要为网卡eth0配置一个HTB队列,使用下列命令:

1
#tc qdisc add dev eth0 root handle 1:htb default 11
  • 命令中的add表示要添加,
  • dev eth0表示要操作的网卡为eth0。
  • root 表示为网卡eth0添加的是一个根队列。
  • handle 1: 表示队列的句柄为1: 。
  • htb 表示要添加的队列为HTB队列。
  • 命令最后的default 11 是htb特有的队列参数,意思是所有未分类的流量都将分配给类别1:11。

为根队列创建相应的类别

有关类别的TC 命令的一般形式为:

1
#tc class [add | change | replace] dev DEV parent qdisc-id [classid class-id] qdisc [qdisc specific parameters]

可以利用下面这三个命令为根队列1创建三个类别,分别是1:11、1:12和1:13,它们分别占用40、40和20mb[t的带宽。

1
2
3
4
5
#tc class add dev eth0 parent 1: classid 1:11 htb rate 40mbit ceil 40mbit

#tc class add dev eth0 parent 1: classid 1:12 htb rate 40mbit ceil 40mbit

#tc class add dev eth0 parent 1: cllassid 1:13 htb rate 20mbit ceil 20mbit
  • parent 1:表示类别的父亲为根队列1:
  • classid1:11 表示创建一个标识为1:11的类别,
  • rate 40mbit 表示系统将为该类别确保带宽40mbit,
  • ceil 40mbit,表示该类别的最高可占用带宽为40mbit。

为各个类别设置过滤器

有关过滤器的TC 命令的一般形式为:

1
#tc filter [add | change | replace] dev DEV [parent qdisc-id | root] protocol protocol prio priority filtertype [filtertype specific parameters] flowid flow-id

由于需要将WWW、E-mail、Telnet三种流量分配到三个类别,即上述1:11、1:12和1:13,因此,需要创建三个过滤器,如下面的三个命令:

1
2
3
4
5
#tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip dport 80 0xffff flowid 1:11

#tc filter add dev eth0 prtocol ip parent 1:0 prio 1 u32 match ip dport 25 0xffff flowid 1:12

#tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip dport 23 oxffff flowid 1:13
  • protocol ip 表示该过滤器应该检查报文分组的协议字段。
  • prio 1 表示它们对报文处理的优先级是相同的,对于不同优先级的过滤器,系统将按照从小到大的优先级顺序来执行过滤器,对于相同的优先级,系统将按照命令的先后顺序执行。这几个过滤器还用到了u32选择器(命令中u32后面的部分)来匹配不同的数据流。以第一个命令为例,判断的是dport字段,如果该字段与Oxffff进行与操作的结果是8O,则flowid 1:11表示将把该数据流分配给类别1:11。更加详细的有关TC的用法可以参考TC的手册页。

复杂的实例

在上面的例子中, 三种数据流(www、Email、Telnet)之间是互相排斥的。当某个数据流的流量没有达到配额时,其剩余的带宽并不能被其他两个数据流所借用。在这里将涉及如何使不同的数据流可以共享一定的带宽。

首先需要用到HTB的一个特性, 即对于一个类别中的所有子类别,它们将共享该父类别所拥有的带宽,同时,又可以使得各个子类别申请的各自带宽得到保证。这也就是说,当某个数据流的实际使用带宽没有达到其配额时,其剩余的带宽可以借给其他的数据流。而在借出的过程中,如果本数据流的数据量增大,则借出的带宽部分将收回,以保证本数据流的带宽配额。

下面考虑这样的需求,同样是三个数据流WWW、E-mail和Telnet, 其中的Telnet独立分配20Mbit/s的带宽。另一方面,WWW 和SMTP各自分配40Mbit/s的带宽。同时,它们又是共享的关系,即它们可以互相借用带宽。

需要的TC命令如下:

1
2
3
4
5
6
7
8
#tc qdisc add dev eth0 root handle 1: htb default 21
#tc class add dev eth0 partent 1: classid 1:1 htb rate 20mbit ceil 20mbit
#tc class add dev eth0 parent 1: classid 1:2 htb rate 80mbit ceil 80mbit
#tc class add dev eth0 parent 1: classid 1:21 htb rate 40mbit ceil 20mbit
#tc class add dev eth0 parent 1:2 classid 1:22 htb rate 40mbit ceil 80mbit
#tc filter add dev eth0 protocol parent 10 prio 1 u32 match ip dport 80 0xffff flowid 1:21
#tc filter add dev eth0 protocol parent 1:0 prio 1 u32 match ip dport 25 0xffff flowid 1:22
#tc filter add dev eth0 protocol parent 1:0 prio 1 u32 match ip dport 23 0xffff flowid 1:1

这里为根队列1创建两个根类别,即1:1和1:2,其中1:1对应Telnet数据流,1:2对应80Mbit的数据流。然后,在1:2中,创建两个子类别1:21和1:22,分别对应WWW和E-mail数据流。由于类别1:21和1:22是类别1:2的子类别,因此他们可以共享分配的80Mbit带宽。同时,又确保当需要时,自己的带宽至少有40Mbit。

从这个例子可以看出,利用HTB中类别和子类别的包含关系,可以构建更加复杂的多层次类别树,从而实现的更加灵活的带宽共享和独占模式,达到企业级的带宽管理目的。


htb示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# add qdisc
tc qdisc add dev eth0 root handle 1: htb default 2 r2q 100
# add default class
tc class add dev eth0 parent 1:0 classid 1:1 htb rate 1000mbit ceil 1000mbit
tc class add dev eth0 parent 1:1 classid 1:2 htb prio 5 rate 1000mbit ceil 1000mbit
tc qdisc add dev eth0 parent 1:2 handle 2: pfifo limit 500
# add default filter
tc filter add dev eth0 parent 1:0 prio 5 protocol ip u32
tc filter add dev eth0 parent 1:0 prio 5 handle 3: protocol ip u32 divisor 256
tc filter add dev eth0 parent 1:0 prio 5 protocol ip u32 ht 800:: match ip src 192.168.0.0/16 hashkey mask 0x000000ff at 12 link 3:

# add egress rules for 192.168.0.9
tc class add dev eth0 parent 1:1 classid 1:9 htb prio 5 rate 3mbit ceil 3mbit
tc qdisc add dev eth0 parent 1:9 handle 9: pfifo limit 500
tc filter add dev eth0 parent 1: protocol ip prio 5 u32 ht 3:9: match ip src "192.168.0.9" flowid 1:9

ifb示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# init ifb
modprobe ifb numifbs=1
ip link set ifb0 up
# redirect ingress to ifb0
tc qdisc add dev eth0 ingress handle ffff:
tc filter add dev eth0 parent ffff: protocol ip prio 0 u32 match u32 0 0 flowid ffff: action mirred egress redirect dev ifb0
# add qdisc
tc qdisc add dev ifb0 root handle 1: htb default 2 r2q 100
# add default class
tc class add dev ifb0 parent 1:0 classid 1:1 htb rate 1000mbit ceil 1000mbit
tc class add dev ifb0 parent 1:1 classid 1:2 htb prio 5 rate 1000mbit ceil 1000mbit
tc qdisc add dev ifb0 parent 1:2 handle 2: pfifo limit 500
# add default filter
tc filter add dev ifb0 parent 1:0 prio 5 protocol ip u32
tc filter add dev ifb0 parent 1:0 prio 5 handle 4: protocol ip u32 divisor 256
tc filter add dev ifb0 parent 1:0 prio 5 protocol ip u32 ht 800:: match ip dst 192.168.0.0/16 hashkey mask 0x000000ff at 16 link 4:

# add ingress rules for 192.168.0.9
tc class add dev ifb0 parent 1:1 classid 1:9 htb prio 5 rate 3mbit ceil 3mbit
tc qdisc add dev ifb0 parent 1:9 handle 9: pfifo limit 500
tc filter add dev ifb0 parent 1: protocol ip prio 5 u32 ht 4:9: match ip dst "192.168.0.9" flowid 1:9

参考文档