Linux内核内置了一个Traffic Control框架,可以实现流量限速,流量整形,策略应用(丢弃,NAT等)。和 TC框架比较相似的是Netfilter框架,但是二者却又有很大的不同。
TC是linux自带的模块,在内核2.4.18以上。
Netfilter:网络协议栈上内核路径上过滤数据包,检测要得出一个结果动作:
丢弃,接受,等等。Netfilter框架关注的是只需针对一个数据包得出一个结果即可。
TC:是对数据包或者数据流提供一种服务,比如限速,整形,等等。TC框架关注的是如何执行而不是仅仅想要得到一个要执行的动作。
总结:Netfilter框架关键做什么,而TC框架关注怎么做。
队列(qdisc):
流量控制的一个基本概念是队列(Qdisc),每个网卡都与一个队列(Qdisc)相联系,每当内核需要将报文分组从网卡发送出去,都会首先将该报文分组添加到该网卡所配置的队列中,由该队列决定报文分组的发送顺序。因此可以说,所有的流量控制都发生在队列中。
有些队列的功能是非常简单的,它们对报文分组实行先来先走的策略。有些队列则功能复杂,会将不同的报文分组进行排队、分类,并根据不同的原则,以不同的顺序发送队列中的报文分组。为实现这样的功能,这些复杂的队列需要使用不同的过滤器(Filter)来把报文分组分成不同的类别(Class)。这里把这些复杂的队列称为可分类(Classiful)的队列。因此,类别(Class)和过滤器(Filter)也是流量控制的另外两个重要的基本概念。
实例:
这个队列有三个频道(0.1.2),FIFO应用于每一个频道,并且:如果0频道有数据发送就不会处理1频道的数据,1和2频道关系也是这样。
参数
priomap:数据包优先权。根据一个数据包中的TOS字节进行分类。
tos看上去是这样子的。
|
0 1 2 |
3 4 5 6 |
7
|
优先权 |
TOS |
MBZ
TOS字段4个bit定义如下:
1000 8 最小延时(md)
0100 4 最大throughput(mt)
0010 2 最大可靠性(mr)
0001 1 最小成本(mmc)
0000 0 正常服务
因为在这个4bit之后还有一位MBZ,所以tos的字段的实际值是上述的2倍
流控中默认的pfifo权限是这样的1,2,2,2,1,2,0,0,1,1,1,1,1,1,1,1 对应下表。
上述表意思:例如15表示一个数据包要求最小成本、最大可靠性、最大吞吐量、和最小延时的发送出去。
下表来自RFC1349,告诉你应用程序是如何设置他们的QOS:
使用iptables修改程序的QOS
例如修改telnet的QOS为md:iptables -A PREROUTING -t mangle -p tcp --dport telnet -j TOS --set-tos Minimize-Delay
令牌桶过滤器(TBF,Token Bucket Filter)是一个简单的队列规则:只允许以不超过事先设定的速率到来的数据包通过,但可能允许短暂突发流量超过设定值。
TBF很精确,对于网络和处理器的影响都很小。所以如果您想对一个网卡限速,它应该是最好选择!
TBF的实现在于一个缓冲器(桶),该缓冲器(桶)不断地被一些叫做”令牌”的虚拟数据以特定速率(token rate)填充着。桶最重要的参数就是它的大小,也就是它能够存储令牌的数量。
这个算法关联到两个流上——令牌流和数据流,得出3种情景:
1.数据流以等于令牌流的速率到达TBF。这种情况下,每个到来的数据包都能对应一个令牌,然后无延迟地通过队列。
2.数据流以小于令牌流的速度到达TBF。通过队列的数据包只消耗了一部分令牌,剩下的令牌会在桶里积累下来,直到桶被装满。剩下的令牌可以在需要以高于令牌流速率发送数据流的时候消耗掉,这种情况下会发生突发传输。
3.数据流以大于令牌流的速率到达TBF。这意味着桶里的令牌很快就会被耗尽。导致TBF中断一段时间,称为”越限”(overlimit)。如果数据包持续到来,将发生丢包。
配置参数:
limit latency
limit确定最多有多少数据(字节数)在队列中等待可用令牌
latency确定一个包在TBF中等待传输的最长时间,计算桶大小,速率,峰值速率。
burst buffer maxburst
桶的大小,以字节计,指定最多可以有多少令牌能够即刻被用,
10兆bit/s的速率至少需要10k字节的缓冲区才可以达到期望的速率。
rate
速度操纵杆,类似limits
例:
tc qdisc add dev DEV root tbf rate 220kbit latency 50ms burst 1540
SFQ(Stochastic Fairness Queueing,随机公平队列)简单实现公平队列算法。它的精确性不如其它的方法,但是它在实现高度公平的同时,需要的计算量却很少。
SFQ的关键词是”会话”或“流”,主要针对一个TCP会话或者UDP流。流量被分成相当多数量的FIFO队列中,每个队列对应一个会话。数据按照简单轮转的方式发送,每个会话都按顺序得到发送机会。
参数
perturb: 多少秒后重新配置一次散列算法。如果取消设置,散列算法将永远不会重新配置(不建议这样做)。10秒应该是一个合适的值。
quantum: 一个流至少要传输多少字节后才切换到下一个队列。缺省设置为一个最大包的长度(MTU的大小)。不要设置这个数值低于MTU!
例:
tc qdisc add dev ppp0 root sfq perturb 10
一旦有数据包进入一个分类队列中,它就得被送到一个类中,也就是需要分类,对数据包进行分类的工具是过滤器,分类器是从队列内部的调用。
过滤器一般会返回一个决定,队列规定就根据这样的一个决定将数据包送入相应的类中进行排队。每一个子类可以再次使用他们的过滤器进行进一步分类,直到不需要进行分类时候,数据包才进入包含队列规定中排队。
队列规定家族:根、句柄、兄弟、父辈
每一个网卡都有一个根队列规定模式是pfifo_fast,每一个队列都指定一个句柄,以便以后的配置语句能够引用队列规定。
队列的规定的句柄有两个部分:一个主号码一个和次号码一个,一般情况下习惯吧根队列规定称为“1:”等价于“1:0”队列规定的次号码永远是0
类的主号码必须与他们父类的主号码一致。
一个典型的分层关系:
数据包可能按照1:–>1:1–>12:–>12:2
数据包现在应该处于12:2下属的某一个队列中,上述图中树的每一个节点都附带一个过滤器,用来选择下一步进入那个分支,这样也比较直观,也可以允许这样子,1:–>12:2(也就是说根所带的过滤器要求把数据包直接交给12:2)
PRIO队列规定并不进行整形,仅仅根据你配置的过滤器把流量进一步细分。也可以认为PRIO队列是pfifo_fast的一种衍生物,区别在于每一个频道是一个单独的类。当一个数据包进入PRIO队列中,会根据过滤器选择一个类,缺省的是3种类别。当一个数据包需要出队列时候,首先处理:1类,当没有小号类中数据包发送时候才可以发送大号类的数据包
PRIO参数
bands创建的频道的个数,每一个频道就是一个类。
priomap如果不提供任何过滤器,PRIO队列会规定参考TC_PRIO的优先级来决定如何给数据包入队。
实例:
大量数据使用30:,交互数据使用20:或者10:
tc qdisc add dev DEV root handle 1:prio
#这个命令创建了3个子类: 1:1,1:2,1:3
tc qdisc add dev DEV parent 1:1 handle 10: sfq
tc qdisc add dev DEV parent 1:2 handle 20: tbf rate 20kbit buffer 1600 limit 3000
tc qdisc add dev DEV parent 1:3 handle 30: sfq
CBQ是最复杂、最琐碎、最难以理解、最刁钻的队列规则。这并不是因为其作者设计不称职,而是因为CBQ算法本身的不精确,而且与Linux的内在机制不协调造成的。
CBQ就采用了它一个近似值——来自硬件层的两个传输请求之间的毫秒数来代替它。这个参数可以近似地表现这个链路的繁忙程度。这样也不能得到正确的理论,比如某些网卡,总线设置,非物理网卡都会影响闲置时间。
有效闲置时间的测量使用EWMA测出一个结果,链路的真实值减去测量值,得出的一个结果是“avgidle”最佳的链路是0,负数表示链路阻塞验证,应停止发包成为overlimit,相反,一个闲置的链路应该有很大的avgidle值,如果时间过长,会造成链路允许非常大的带宽通过,我们使用maxidle来限制avgidle的值不能太大。
参数:
bandwidth:网卡的物理带宽,用来计算闲置时间。
avpkt:平均包的大小,单位是字节。
cell:用于设置时间力度,通常设置为8,必须是2的整数次幂。
mpu:最小包大小。
weight:偏袒数量。当其中一个类要求的带宽高于其他的类,那它每次就会比其他类处理weight数量的数据。
prio:优先级参数。当prio值较低时,只要有数据就必须先服务,其他类要延后处理。
allot:当一个类发送数据时,它发送的数据量的基值。
maxburst:决定了计算maxidle所使用的数据包的个数。在avgidle跌落到0之前,maxburst的数据包可以突发传输出去。值越高,越能容纳突发传输。
miniburst:当发生越限时,cbq禁止发包。一段时间后,突发性传输miniburst个数据包。值越大,整形越精确,一定程度上会有越多的突发传输。
bounded/borrow:bounded表示不会向其兄弟类借入带宽。borrow与bounded相反。
rate:期望中的传输速率。也就是“速度操纵杆”!
决定链路的共享和借用的CBQ参数
除了纯粹地对某种数据流进行限速之外,CBQ还可以指定哪些类可以向其它哪些类借用或者出借一部分带宽。
Isolated/Sharing (isolated字面意思:独立,单独的)
凡是使用“isolated”选项配置的类,就不会向其兄弟类借出带宽。如果你的链路上同时存在着不友好的人,你就可以使用这个选项。
选项“sharing”是“isolated”的反义选项。
Bounded/Borrow (bounded字面意思:受限制的,有限的;borrow=借入)
一个类也可以用“bounded”选项配置,意味着它不会向其兄弟类借入带宽。选项“borrow”是“bounded”的反义选项。
一个典型的情况就是你的一个链路上有多个客户都设置成了“isolated”和“bounded”,那就是说他们都被限制在其要求的速率之下,且互相之间不会借用带宽(就是我们常说的带宽独享)。在这样的一个类的内部的子类之间是可以互相借用带宽的。
其它CBQ参数:split和defmap (split:分离、分裂)(不做解释)
实例:
这个配置把WEB服务器的流量控制为5mbit、SMTP流量控制在3mbit上。而且二者一共不得超过6mbit,互相之间允许借用带宽。我们的网卡是100Mbps的。
#tc qdisc add dev eth0 root handle 1:0 cbq bandwidth 100Mbit avpkt 1000 cell 8
#tc class add dev eth0 parent 1:0 classid 1:1 cbq bandwidth 100Mbit rate 6Mbit weight 0.6Mbit prio 8 allot 1514 cell 8 maxburst 20 avpkt 1000 bounded
这部分按惯例设置了根为1:0,并且绑定了类1:1。也就是说整个带宽不能超过6Mbps。
#tc class add dev eth0 parent 1:1 classid 1:3 cbq bandwidth 100Mbit rate 5Mbit weight 0.5Mbit prio 5 allot 1514 cell 8 maxburst 20 avpkt 1000
#tc class add dev eth0 parent 1:1 classid 1:4 cbq bandwidth 100Mbit rate 3Mbit weight 0.3Mbit prio 5 allot 1514 cell 8 maxburst 20 avpkt 1000
我们建立了2个类。注意我们如何根据带宽来调整weight参数的。两个类都没有配置成“bounded”,但它们都连接到了类1:1上,而1:1设置了 “bounded”。所以两个类的总带宽不会超过6Mbps。别忘了,同一个CBQ下面的子类的主号码都必须与CBQ自己的号码相一致!
#tc qdisc add dev eth0 parent 1:3 handle 30: sfq
#tc qdisc add dev eth0 parent 1:4 handle 40: sfq
缺省情况下,两个类都有一个FIFO队列规则。但是我们把它换成SFQ队列,以保证每个数据流都公平对待。
#tc filter add dev eth0 parent 1:0 protocol ip prio 1 u32 match ip sport 80 0xffff flowid 1:3
#tc filter add dev eth0 parent 1:0 protocol ip prio 1 u32 match ip sport 25 0xffff flowid 1:4
这些命令规则了根上的过滤器,保证数据流被送到正确的队列规则中去。
这里列出的绝大多数命令都根据这个命令改编而来:
#tc filter add dev eth0 parent 1:0 protocol ip prio 1 u32 这些是所谓的“u32”匹配,可以匹配数据包的任意部分。
根据源/目的地址: 源地址段 match ip src 1.2.3.0/24
目的地址段 match ip dst 4.3.2.0/24
根据源/目的端口: 源 match ip sport 80 0xffff
目的 match ip dport 80 0xffff
根据IP协议(tcp, udp, icmp, gre, ipsec)使用/etc/protocols所指定的数字。
比如: icmp是 match ip protocol 1 0xff
根据fwmark:
#tc filter add dev eth1 protocol ip parent 1:0 prio 1handle 6 fw flowid 1:1
注意,这不是一个u32匹配!
你可以象这样给数据包打标记:
#iptables -A PREROUTING -t mangle -i eth0 -j MARK --set-mark 6
按TOS字段选择交互和最小延迟的数据流:
#tc filter add dev ppp0 parent 1:0 protocol ip prio 10 u32 match iptos 0x10 0xff flowid 1:4
实例:将以HTB队列为主,结合需求来讲述TC的使用。假设eth0出口有100mbit/s的带宽, 分配给WWW 、E-mail和Telnet三种数据流量, 其中分配给WWW的带宽为40Mbit/s,分配给Email的带宽为40Mbit/s, 分配给Telnet的带宽为20Mbit/S。
首先,需要为网卡eth0配置一个HTB队列,使用下列命令:
#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 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。
由于需要将WWW、E-mail、Telnet三种流量分配到三个类别,即上述1:11、1:12和1:13,因此,需要创建三个过滤器,如下面的三个命令:
#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 protocol 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"表示该过滤器应该检查报文分组的协议字段。“pr[o 1” 表示它们对报文处理的优先级是相同的,对于不同优先级的过滤器, 系统将按照从小到大的优先级。
上述中三种数据流(www、Email、Telnet)之间是互相排斥的。当某个数据流的流量没有达到配额时,其剩余的带宽并不能被其他两个数据流所借用。在这里将涉及如何使不同的数据流可以共享一定的带宽。
首先需要用到HTB的一个特性, 即对于一个类别中的所有子类别,它们将共享该父类别所拥有的带宽,同时,又可以使得各个子类别申请的各自带宽得到保证。这也就是说,当某个数据流的实际使用带宽没有达到其配额时, 其剩余的带宽可以借给其他的数据流。而在借出的过程中,如果本数据流的数据量增大,则借出的带宽部分将收回, 以保证本数据流的带宽配额。
下面考虑这样的需求, 同样是三个数据流WWW、E-mail和Telnet, 其中的Telnet独立分配20Mbit/s的带宽。另一方面,WWW 和SMTP各自分配40Mbit/s。同时,它们又是共享的关系, 即它们可以互相借用带宽。
#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:2 classid 1:21 htb rate 40mbit ceil 80mbit
#tc class add dev eth0 parent 1:2 classid 1:22 htb rate 40mbit ceil 80mbit
#tc filter add dev eth0 protocol parent 1:0 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中类别和子类别的包含关系,可以构建更加复杂的多层次类别树,从而实现的更加灵活的带宽共享和独占模式,达到企业级的带宽管理目的。
中介队列不是一个队列规定,但它的使用与队列规定是紧密相连的。就linux而言,队列是附加在网卡上的,出现了两个局限:
1.只能进行出口整形,(虽然也存在入口队列,但是上面实现的分类的队列规定可能性非常小)
2.一个队列规定只能处理一块网卡的流量,无法设置全局的限速。
IMQ就是解决了上述的两个局限,简单的说,你可以往队列中放任何东西。被打了特定的数据包在netfilter的BF_IP_PRE_ROUTING和NF_IP_POST_ROUTING的两个钩子函数被拦截,并被发送到一个队列中,该队列附加在一个IMQ的设备上,对数据包打标签可以用到iptables一种处理方法。
配置范例:
首先想到的是入口整形,以便配置得到高保证的带宽,就像配置其他网卡一样:
#tc qdisc add dev imq0 root handle 1: htb default 20
#tc class add dev imq0 parent 1: classid 1:1 htb rate 2mbit burst 15k
#tc class add dev imq0 parent 1:1 classid 1:10 htb rate 1mbit
#tc class add dev imq0 parent 1:1 classid 1:20 htb rate 1mbit
#tc qdisc add dev imq0 parent 1:10 handle 10: pfifo
#tc qdisc add dev imq0 parent 1:20 handle 20: sfq
#tc filter add dev imq0 parent 10:0 protocol ip prio 1 u32 match ip dst ip flowid 1:10
在这个列子中,使用32 匹配目的ip,然后被打上标记的数据包被送到imq0中排队。
iptables -t mangle -A PERROUTING -i eth0 -j IMQ --todev 0
ip link set imq0 up