Linux操作系统中的流量控制器TC(Traffic Control)用于Linux内核的流量控制,它利用队列规定(qdisc)建立处理数据包的队列,并定义队列中的数据包被发送的方式, 从而实现对流量的控制。TC模块实现流量控制功能使用的队列规定分为两类,一类是无类队列规定(classless qdisc), 另一类是分类队列规定(classful qdisc)。 无类队列规定相对简单,而分类队列规定则引出了分类和过滤器等概念,使其流量控制功能增强。
TC的基本原理是将准备发给接口发送的报重新进行排序、整理和设置优先级,使得数据包在发送的最后关头,按照设置的限制重新组合。
TC对于包的处理方式有四种:
1. SHAPING:控制,当流量被shaped,它的传输速率就被控制了,但是shaping不仅仅是降低可用的带宽,也被用于平抑流量中的突发流量,可以实现更好的网络体验。shaping只发送在流量出口的地方。
2. SCHEDULING:调度,通过调度数据包的传输,可以提高对需要它的通信的交互性,同时还可以保证带宽的传输。这种重新排序也被称为按照优先级排序,并且只发生在出口。
3. POLICING:监管,与shaping处理数据报的传递对应的,监管属于处理数据报到达时的处理方法,因此POLICING发生在入口处。
4. DROPPING:丢弃,超出指定带宽的数据报可能会立即被抛弃,这在入口和出口都会发生。
一般来说,TC只对网络设备发出的流量进行限制,不对接收的流量进行限制,所以在设计规则时,要注意数据的流向,比如上行速率的限制都是在外网接口进行,下行速率的限制都是在内网接口进行
TC通过三种结构qdisc(队列)、class(分类)和filter(过滤器)来组合成各种定制的数据包分发结构,这种结构类似于一个树,qdisc是树的节点,是每一个结构节点的载体,规定和限制了该节点可以承担怎样的功能,派生出怎样的子节点;class可以看做树上节点的值,它具体定义了该节点对于流量的带宽、优先级、延迟和burst的规定;filter就是节点的闸门,管理者数据包是否应该流入该节点。整个流量在TC树中的流动,就是这样从上到下,从小的minor_id到大的minor_id的类依次轮询,也有例外的就是,如果一个类下面的子类设置有不同的优先级,则流量会优先满足优先级高的子类(prio_id小的)。
首先,进入的是根qdisc,即根class,然后在根class下的filter之前过滤,决定该包进入哪一个子类,如果都没有匹配到,则进入default的子类,如果匹配到一个子类,则进入该子类进行进一步的filter,直到匹配到没有子类的子类,或被分配到默认的子类中。
无类别的qdisc,包括以下三种
tc qdisc add dev DEV root QDISC QDISC-PARAMETERS
使用如下命令删除该设备的qdisc:tc qdisc del dev DEV root
。在没有配置qdisc时,pfifo_fast就是默认的qdisc。
有类别qdisc包括:
- CBQ:Class Based Queueing,基于类别排队,实现了一个具有丰富共享层次结构的类结构,它既有shaping限制带宽的能力,也具有带宽优先级管理的能力,带宽限制是通过计算机连接的空闲时间完成的,而空闲时间的计算标准就是数据报离队事件的频率和下层数据链路层的带宽。
- HTB:Hierarchy Token Bucket,层次结构令牌桶,实现了一个具有丰富链接共享的层次结构,重点是与现有的实践相一致。使用HTB可以很容易的保证每个类别的带宽,虽然它也允许特定的类可以突破带宽上限,占用别的类的带宽。它包含基于TBF的限制元素来限制带宽,可以为类设置优先级。
- PRIO:不能限制带宽,因为不同类别的数据包是顺序离队的,使用PRIO qdisc可以很容易的对流量进行优先级管理,只有高优先级的数据包全部发送完之后,低优先级的数据包才会继续被发送。为了方便管理,需要使用iptables或ipchain(iptables的前身)处理数据包的TOS。
分类class和qdisc总是成对出现,class为qdisc指定具体的整形、限流和优先级规则,由于不同的qdisc功能差别较大,其所属class的规则格式也差别很大,在使用时,需要查看具体的文档说明。注意,无类别qdisc没有class。
filter用于指定数据包被分配给哪一个类,除了默认的类之外,每一个class都应该有对应的filter来为他匹配数据包。
handle_id:
,该handle_id
就是其下所有子类的major_id,这个id是表示该子类所属的标志,不能更改。每个子类分配的qdisc,使用该子类的minor_id构建类似minor_id的格式来产生自己的handle。tc qdisc add dev eth0 root handle 2: htb default 100
major_id:minor_id
,其中major_id必须和父类的major_id
以及父qdisc的handle_id
一致,所以在一个设备下,所有类的major_id
都是一致的,这也就意味着该设备下的所有类的minor_id
不能重复,不然内核会报出该配置重复的错误RTNETLINK answers: File exists
。tc class add dev eth0 parent 2:0 classid 2:1 htb rate 5000kbit burst 15k
tc class add dev eth0 parent 2:1 classid 2:20 htb rate 1000kbit ceil 1000kbit burst 15k
"tc filter add dev eth0 parent 2:0 protocol ip handle 0x0101 fw classid 2:20",
TC的工作原理是在输出端口建立一个队列进行流量控制,控制的对象是从该端口发出数据包,控制的方式是基于路由,即根据数据包的IP地址、目的子网地址、MARK值等进行的流量控制。TC功能主要通过qdisc、class和filter三种功能模块实现,在添加TC命令时,也是通过这三种功能模块和路由(Route、iptables)的组合来实现具体的限速、整形功能。
以下例子中的队列和分类基于常用的HTB实现,过滤器基于iptables功能,实现一个简单的单层TC命令树,实现对内网特定设备的下载速度的限制,控制设备为内网路由器,LAN侧网卡eth0,IP:192.168.2.1,内网设备1,mac:11:11:11:11:11:11,ip:192.168.2.102, 限速1MB/s,内网设备2,mac:22:22:22:22:22:22,ip:192.168.2.103,限速500KB/s,其余设备不限速。
基本步骤如下:
为控制内网设备的下载速度,需要控制路由器发给该设备的流量,即路由器eth0接口发往内网设备的流量,所以在eth0上建立tc规则如下:
tc qdisc add dev eth0 root handle 10: htb default 100
tc建立htb根队列,开头和添加其他qdisc一样tc qdisc add
;随后指定添加队列的设备dev eht0
,并指定这是根队列,没有parent,所以后面跟root
;再指定handle 10:
,这是qdisc命名的普遍格式,注意随后添加的分类major_id
要和handle冒号前的值一致;随后,指出此次添加的qdisc类型为htb
,到此为止,之前的格式都是通用的,所有的qdisc
的添加都会采用类似的格式,除了根队列需要添加root
,而子队列需要制定parent
;最后,每种qdisc可能需要不同的参数,有的是必填的,有的是可选的,htb必填的参数只有default
,其后跟着流量默认走向的子类的minor_id
,该子类可以不去手动添加,系统会自动按照默认的配置生成一个隐藏的子类,但是通过tc show
的命令并不能看到该子类,所以一般建议是添加该默认子类,以便对流量进行监控和统计。
然后,为该根队列绑定一个根分类,如下:
tc class add dev eth0 parent 10:0 classid 10:1 htb rate 1000Mbit burst 15k
开头和所有添加class的命令一致tc class add dev eth0
,随后指定父类,由于是根class,所以父类为parent 10:0
,其实表示的就是根qidsc,10:0
的表示和10:
的表示是一样的;指定分类IDclassid 10:1
,指定分类所属qdisc类型htb
,随后是针对该分类的属性配置,rate 1000Mbit
表示分配给该类的带宽,注意这里是bit
而不是B
,换算成网速需要除以8,而且如果对于整个网络设备的整个带宽不做限制的话,这里的rate
其实是远远超过实际带宽的,如果需要对整个带宽做限制,在这里就需要对rate
做出具体的限制;burst 15k
表示在指定带宽基础上,允许的burst
脉冲,即1000Mbit
带宽基础上,允许超出该值得最大波动,即带宽波动到(1000000+15)kbit
都是合理且允许的。
根据要求,eth0下面有两个设备需要限速,其余设备不进行限速,所以需要建立两个class为这两个设备限速进行使用,再建立一个默认的class作为默认的流量通道。
tc class add dev eth0 parent 10:1 classid 10:2 htb rate 8000kbit ceil 8000kbit burst 15k
tc class add dev eth0 parent 10:1 classid 10:3 htb rate 4000kbit ceil 4000kbit burst 15k
tc class add dev eth0 parent 10:1 classid 10:100 htb rate 100kbit ceil 1000Mbit burst 15k
第一条为设备1的tc规则,classid 10:2
,限速rate 8000kbit
即1MB/s,ceil 8000kbit
是htb的一个特殊属性,在htb中存在一种借用机制,即同一个父类下面的子类,允许向其他子类借用其闲置的带宽资源,但是这个借用的大小有限制,借用的带宽+自身rate的带宽<=ceil,这里对设备1进行限速,所以讲rate和ceil都设为8000kbit(其实默认情况下,ceil会被自动设置为rate一样的值)。
第二条为设备2的tc规则,classid 10:3
,格式与1一样,只是限速值为4000kbit
。
第三条为默认子类的tc规则,这里rate 100kbit
是一个很小的值,但是ceil 1000Mbit
是整个设置的带宽的大小,这表示其余设备虽然被分配了很小的带宽,但是允许借用设备下所有的限制带宽流量,这也是为了在网络拥堵的情况下,防止陌生设备过度占用带宽。
tc为每个分类默认的定义一个pfifo_fast
的qdisc,用于处理该分类所接收的流量,但是如果遇到某一设备下的数据量很大,大量占用带宽的情况,其余走同一分类的流量较小的设备就无法及时得到处理和响应。所以,这里不采用默认的pfifo_fast
队列,而是采用sfq
队列,该队列会将数据包打乱发送,以避免上述情况发生,使得每个设备的数据都有较为公平的机会被处理。
由于这里针对具体设备进行过滤,并且知道设备的mac地址,所以tc为每个分类建立的filter可以基于iptables,通过iptables为制定mac和流向的数据打上mark
,在tc中通过mark
来匹配响应控制的流量。
tc qdisc add dev eth0 parent 10:2 handle 2: sfq perturb 10
tc filter add dev eth0 parent 10:0 protocol ip handle 0x0002 fw classid 10:2
tc qdisc add dev eth0 parent 10:3 handle 3: sfq perturb 10
tc filter add dev eth0 parent 10:0 protocol ip handle 0x0003 fw classid 10:3
tc qdisc add dev eth0 parent 10:100 handle 100: sfq perturb 10
为一个分类建立qdisc
时,需要注意parent 10:2
为该分类的classid
,handle
为该分类classid
的minor_id
,指定qdisc类型为sfq
,sfq
必须的参数为perturb 10
,表示每10s产生一次随机算法的扰动,即每10s对随机排队算法进行一次小的调整,防止固定的随机算法会同样造成默写设备的流量被一直阻塞或被排在较低的优先级。该值不能太小,一般建议在60,否则会导致一些数据包的丢失或重新录入。
建立filter
时,开头一致为tc filter add dev eth0
,随后是指定parent 10:0
,因为这里的filter
是挂在根class下面的,所以父节点为根class的classid,随后指定过滤器起作用的协议为ipprotocol ip
,handle 0x0002
表示过滤的目标为mark = 0x0002
的数据包,fw classid 10:2
表示匹配到目标数据包后将其调度到classid
为10:2
的分类中。
最后一条为默认分类建立sfq
,不再需要添加filter
。
由于需要限制eth0->设备1以及eth0->设备2的流量,所以将该方向上的数据加上mark
,注意set-mark
的动作只能在mangle
表中进行。
iptables -t mangle -A FOR_WARD -J tc_ctl
iptables -t mangle -A tc_ctl -d 192.168.2.102 -o eth0 -j MARK --set-mark 0x0002
iptables -t mangle -A tc_ctl -d 192.168.2.103 -o eth0 -j MARK --set-mark 0x0003
在tc运行过程中,通过tc show
命令来查看tc命令是否正确的添加和生效,通过iptables -t mangle -nvL
命令查看添加的iptables命令。
qdisc
,可以显示qdisc
的handle、默认分类等信息,加-s
查看详细信息,包括发送的数据包总数,便于查看设置的限速规则是否正确生效。#tc qdisc dev eth0 show
qdisc htb 10: root refcnt 2 r2q 10 default 100 direct_packets_stat 0
qdisc sfq 2: parent 10:2 limit 127p quantum 1514b divisor 1024 perturb 10sec
qdisc sfq 3: parent 10:3 limit 127p quantum 1514b divisor 1024 perturb 10sec
#tc -s qdisc dev eth0 show
qdisc htb 10: root refcnt 2 r2q 10 default 20 direct_packets_stat 0
Sent 62218 bytes 229 pkt (dropped 0, overlimits 2 requeues 0)
backlog 0b 0p requeues 0
qdisc sfq 2: parent 2:10 limit 127p quantum 1514b divisor 1024 perturb 10sec
Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0)
qdisc sfq 3: parent 2:20 limit 127p quantum 1514b divisor 1024 perturb 10sec
Sent 62218 bytes 222 pkt (dropped 0, overlimits 0 requeues 0)
backlog 0b 0p requeues 0
class
,加-s
查看详细信息# tc class show dev eth0
class htb 10:2 parent 10:1 leaf 2: prio 0 rate 8000Kbit ceil 8000Kbit burst 15Kb cburst 1600b
class htb 10:1 root rate 1000Mbit ceil 1000Mbit burst 15125b cburst 1375b
class htb 10:3 parent 10:1 leaf 3: prio 0 rate 4000kbit ceil 4000kbit burst 15Kb cburst 1375b
class htb 10:100 parent 10:100 leaf 100: prio 0 rate 100kbit ceil 1000Mbit burst 15Kb cburst 1375b
# tc -s class show dev eth0
class htb 10:2 parent 10:1 leaf 2: prio 0 rate 8000Kbit ceil 8000Kbit burst 15Kb cburst 1600b
Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0)
rate 0bit 0pps backlog 0b 0p requeues 0
lended: 0 borrowed: 0 giants: 0
tokens: 480000 ctokens: 50000
class htb 10:3 parent 10:1 leaf 3: prio 0 rate 4000Kbit ceil 4000Kbit burst 15Kb cburst 1600b
Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0)
rate 0bit 0pps backlog 0b 0p requeues 0
lended: 0 borrowed: 0 giants: 0
tokens: 480000 ctokens: 50000
class htb 10:1 root rate 1000Mbit ceil 1000Mbit burst 15125b cburst 1375b
Sent 92361 bytes 608 pkt (dropped 0, overlimits 0 requeues 0)
rate 264bit 0pps backlog 0b 0p requeues 0
lended: 7 borrowed: 0 giants: 0
tokens: 1906 ctokens: 187
class htb 10:100 parent 10:1 leaf 100: prio 0 rate 100000bit ceil 1000Mbit burst 15Kb cburst 1375b
Sent 92361 bytes 608 pkt (dropped 0, overlimits 0 requeues 0)
rate 264bit 0pps backlog 0b 0p requeues 0
lended: 594 borrowed: 7 giants: 0
tokens: 19120000 ctokens: 187
# tc filter show dev eth0
filter parent 10: protocol ip pref 49152 fw
filter parent 10: protocol ip pref 49152 fw handle 0x2 classid 10:2
filter parent 10: protocol ip pref 49152 fw handle 0x3 classid 10:3
iptables
规则# iptables -t mangle -nvL
...
Chain FORWARD (policy ACCEPT 42 packets, 2608 bytes)
pkts bytes target prot opt in out source destination
42 2608 tc_ctl all -- * * 0.0.0.0/0 0.0.0.0/0
...
Chain ip_ctl (1 references)
pkts bytes target prot opt in out source destination
0 0 MARK all -- -- eth0 0.0.0.0/0 192.168.2.102/24 MARK set 0x2
0 0 MARK all -- -- eth0 0.0.0.0/0 192.168.2.103/24 MARK set 0x3
在tc
运行过程中,需要根据实际运行情况动态调整tc
规则,tc
提供了chang
、add
、delete
等命令可以动态的调整tc
规则,但是要注意,有些qdisc
可以动态调整,但是有些是不能动态调整的。