流量控制(Traffic Control, tc)是Linux内核提供的流量限速、整形和策略控制机制。它以qdisc-class-filter的树形结构来实现对流量的分层控制 。
功能
限制
对流量进行限速,这样可以平滑突发流量数据,使网络更为稳定。只适用于向外的流量。
调度
通过调度数据包的传输,可以在带宽范围内按照优先级分配带宽。只适用于向外的流量。
策略(policing)
用于处理接收的数据。
丢弃
如果流量超过某个设定的带宽,就丢弃数据包,不管是向内还是向外。
基本组成
- qdisc通过队列将数据包缓存起来,用来控制网络收发的速度
- class用来表示控制策略
- filter用来将数据包划分到具体的控制策略中
qdisc
qdisc通过队列将数据包缓存起来,用来控制网络收发的速度。实际上,每个网卡都有一个关联的qdisc。它包括以下几种:
- 无分类qdisc(只能应用于root队列)
- [p|b]fifo:简单先进先出
- pfifo_fast:根据数据包的tos将队列划分到3个band,每个band内部先进先出
- red:Random Early Detection,带带宽接近限制时随机丢包,适合高带宽应用
- sfq:Stochastic Fairness Queueing,按照会话对流量排序并循环发送每个会话的数据包
- tbf:Token Bucket Filter,只允许以不超过事先设定的速率到来的数据包通过 , 但可能允许短暂突发流量朝过设定值
- 有分类qdisc(可以包括多个队列)
- cbq:Class Based Queueing,借助EWMA(exponential weighted moving average, 指数加权移动均值 ) 算法确认链路的闲置时间足够长 , 以达到降低链路实际带宽的目的。如果发生越限 ,CBQ 就会禁止发包一段时间。
- htb:Hierarchy Token Bucket,在tbf的基础上增加了分层
- prio:分类优先算法并不进行整形 , 它仅仅根据你配置的过滤器把流量进一步细分。缺省会自动创建三个FIFO类。
注意,一般说到qdisc都是指egress qdisc。每块网卡实际上还可以添加一个ingress qdisc,不过它有诸多的限制
- ingress qdisc不能包含子类,而只能作过滤
- ingress qdisc只能用于简单的整形
如果相对ingress方向作流量控制的话,可以借助ifb( Intermediate Functional Block)内核模块。因为流入网络接口的流量是无法直接控制的,那么就需要把流入的包导入(通过 tc action)到一个中间的队列,该队列在 ifb 设备上,然后让这些包重走 tc 层,最后流入的包再重新入栈,流出的包重新出栈。
不可分类队列
sfq
Stochastic Fairness Queueing,按照会话对流量排序并循环发送每个会话的数据包。因为使用了散列算法,所以可能多个会话分配在同一个队列,从而共享发包的机会,也就是共享带宽,为了不让这种效应太明显,sfq会频繁地改变散列算法。
注意,只有当出口网卡确实已经挤满了的时候,sfq才会起作用,否则在你的linux机器中根本就不会有队列,sfq也就不会起作用。
参数:
- perttum: 多少秒后重新配置一次散列算法,如果取消设置,散列算法将永远不会重新配置。10s比较合适。
- quantum:一个流至少要传输多少字节后才切换到下一个队列。缺省值为一个最大包的长度。这个值不能小于MTU。
配置范例
tc qdisc add dev bond0 root sfq perturb 10
tbf
Token Bucket Filter,令牌桶过滤器,只允许以不超过事先设定的速率到来的数据包通过 , 但可能允许短暂突发流量不超过设定值。
一个缓冲器,不断地被一些叫做令牌的虚拟数据以特定速率填充。桶最重要的参数就是它的大小,也就是能存储令牌的数量。
每个到来的令牌从数据队列中手机一个数据包,然后从同种被删除。这个算法关联到连个流上:令牌流和数据流,于是我们得到3种情景:
- 数据流=令牌流,这样,每个到来的数据都可以无延迟的通过队列。
- 数据流<令牌流,数据传输只是用掉了一部分令牌,剩下的会在桶里积累下来直到满了,剩下的可以在需要以高于令牌流速率发送数据流的时候消耗掉,这种情况称为突发传输。
- 数据流>令牌流,这意味着桶内的令牌很快就会被耗尽,导致中断,称为越限,如果数据包持续到来,将发生丢包。
参数:
- limit/latency
limit确定最多有多少数据在队列中等待,latency参数确定了一个包在tbf中等待传输的最长等待时间,后者计算决定桶的大小,速率和峰值速率。
- burst/buffer/maxburst
桶的大小,以字节计,这个参数制定了最多可以有多少个令牌能够即刻被使用。
- mpu
决定了令牌的最低消耗。
- rate
速度操控杆
- perkrate
峰值速率可以用来指定令牌以多快的速度被删除。释放一个 数据包,之后等待足够的时间后再释放下一个。
- mtu/minburst
要实现更高的峰值速率,可以在一个时钟周期内发送多个数据包。最有效的办法就是:再创建一个令牌桶。
示例:
tc qdisc add dev bond0 root tbf rate 220kbit latency 50ms burst 1540
filter
filter用来将数据包划分到具体的控制策略中,包括以下几种:
- u32:根据协议、IP、端口等过滤数据包
- fwmark:根据iptables MARK来过滤数据包
- tos:根据tos字段过滤数据包
class
class用来表示控制策略,只用于有分类的qdisc上。每个class要么包含多个子类,要么只包含一个子qdisc。当然,每个class还包括一些列的filter,控制数据包流向不同的子类,或者是直接丢掉。
使用
步骤
- 为网卡配置一个队列;
- 在该队列上建立分类;
- 根据需要建立子队列和子分类;
- 为每个分类建立过滤器。
队列
查看
tc [-s | -d] qdisc show [dev DEV]
设置
tc qdisc [add|change|replace|link] dev DEV [parent qdisk-id|root][handle qdisc-id] qdisc[qdisc specific parameters]
示例如下:
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。
不可分类QDisc只能附属于设备的根。它们的用法如下:
tc qdisc add dev DEV root QDISC QDISC-PARAMETERS
要删除一个不可分类QDisc,需要使用如下命令:
tc qdisc del dev DEV root
类
查看
tc [-s | -d] class show [dev DEV]
配置
#tc class [add|change|replace] dev DEV parent qdisc-id [classid class-id] qdisc [qdisc specific parameters]
示例
#tc class add dev eth0 parent 1: classid 1:1 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 filter [-s | -d] show [dev DEV]
tc filter [add|change|replace] dev DEV [parent qdisc-id|root] protocol protocol prio priority filtertype [filtertype specific parameters] flowid flow-id
示例
#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:1 1。
命名规则
所有的QDisc、类和过滤器都有ID。ID可以手工设置,也可以有内核自动分配。ID由一个主序列号和一个从序列号组成,两个数字用一个冒号分开。
- QDISC: 一个QDisc会被分配一个主序列号,叫做句柄(handle),然后把从序列号作为类的命名空间。句柄采用象10:一样的表达方式。习惯上,需要为有子类的QDisc显式地分配一个句柄。
- class: 在同一个QDisc里面的类分享这个QDisc的主序列号,但是每个类都有自己的从序列号,叫做类识别符(classid)。类识别符只与父QDisc有关,和父类无关。类的命名习惯和QDisc的相同。
- filter: 过滤器的ID有三部分,只有在对过滤器进行散列组织才会用到。
在TC 中, 使用”major:minor”这样的句柄来标识队列和类别,其中major和minor都是数字。
对于队列来说,minor总是为0,即”major:0″这样的形式,也可以简写为”major: 比如,队列1:0可以简写为1:。需要注意的是,major在一个网卡的所有队列中必须是惟一的。对于类别来说,其major必须和它的父类别或父队列的major相同,而minor在一个队列内部则必须是惟一的(因为类别肯定是包含在某个队列中的)。
参数
tc可以使用以下命令对QDisc、类和过滤器进行操作:
- add: 在一个节点里加入一个QDisc、类或者过滤器。添加时,需要传递一个祖先作为参数,传递参数时既可以使用ID也可以直接传递设备的根。如果要建立一个QDisc或者过滤器,可以使用句柄(handle)来命名;如果要建立一个类,可以使用类识别符(classid)来命名。
- remove: 删除有某个句柄(handle)指定的QDisc,根QDisc(root)也可以删除。被删除QDisc上的所有子类以及附属于各个类的过滤器都会被自动删除。
- change: 以替代的方式修改某些条目。除了句柄(handle)和祖先不能修改以外,change命令的语法和add命令相同。换句话说,change命令不能一定节点的位置。
- replace: 对一个现有节点进行近于原子操作的删除/添加。如果节点不存在,这个命令就会建立节点。
- link: 只适用于DQisc,替代一个现有的节点。
单位说明
Kbps kiIobytes per second, 即”千字节每秒 ;
Mbps megabytes per second, 即”兆字节每秒 ,
Kbit kilobits per second,即”千比特每秒 ;
Mbit megabits per second, 即”兆比特每秒 。