tc流量控制

来源:互联网 发布:淘宝汽车服务 编辑:程序博客网 时间:2024/05/18 10:04

来自:http://zhangchong105.blog.163.com/blog/static/844814802012114112830295/

tc的工作原理

通过设置不同类型的网络接口队列,从而改变数据包发送的速率和优先级,达到流量控制的目的。内核如果需要通过某个网络接口发送数据包,它都需要按照为这个接口配置的qdisc(队列规则)把数据包加入队列,然后内核会尽可能多的从qdisc里取出数据包,把它们交给网络适配器驱动模块。

流量的处理由三种对象控制:qdisc(队列规定),class(类),filter(分类器)

qdisc(队列规定):用来实现控制网络的收发速度和调度。通过队列,linux可以将网络数据包缓存起来,然后根据用户的设置,在尽量不中断连接的前提下平滑网络流量,linux对接收队列控制不够好,所以一般只控制发送队列(’控发不控收‘)。Qdisc封装了classfilter

  • 无类队列规定:pfifo_fast,tbf ,sfq,流量整形手段是排序,限速和丢包。

  • 分类队列规定:htbcbqprio,适用于同时进行调度和流量控制的场合。

class(类):控制策略,当对多种数据流需要区别对待的时候,需要class来表示不同的控制策略。

filter(分类器):用来将不同的用户划入到具体的控制策略中(即不同的class中)。常用的分类器有u32(根据数据包中的各个字段进行判断),route(根据数据包将被哪条路由进行路由来判断)。

#tc filter add dev eth0 parent 10:0 protocol ip prio 1 u32 match ip src 1.2.3.4/32 flowid 10:1

//把来自1.2.3.4的数据包送到最高有限的队列。


分类器相关参数:

protocol:分类器所接受的协议。一般来说只会接受IP数据。

parent:分类器附带在哪个句柄上。必须是一个已经存在的类的句柄。

prio:分类其的优先权值,优先权值低的优先。

handle:过滤器不同,意义不同。


Linux内核支持的队列有无类队列和分类队列两种

无类队列规定:内部不包含可配置子类的队列规定。

1)pfifo_fast(先进先出队列规定):该队列规定是系统缺省配置。可通过‘ip link list’命令来查看当前的网络队列设置。

pfifo_fast只起到调度作用,对数据流量不进行控制。

2)TBF(令牌桶过滤器):只允许不超过事先设定的速率到来的数据包通过,但允许短暂突发流量超过设定值。(适合于把速率降低到某个值)

原理:TBF的实现在于一个缓冲器(),不断地被一些叫做“令牌”的虚拟数据以特定速率填充着。 桶最重要的参数就是它的大小,也就是它能够存储令牌的数量。每个到来的令牌从数据队列中收集一个数据包,然后从桶中被删除。

注意:当数据流以大于令牌流的速率到达TBF,这意味着桶里的令牌很快就被耗尽,导致TBF中断一段时间,称为‘越限’,如果数据包持续到来,将发生丢包。它对数据通过过滤器的速率进行整形。它实际的实现是针对数据的字节数进行的,而不是针对数据包进行。

#tc qdisc add dev eth0 root tbf rate 6mbit burst 15k latency 50ms

//速率为6mbit,突发传输15k,最大延迟50ms


参数说明:

rate:期望中的传输速率

burst:桶的大小,以字节计。指定了最多可以有多少个令牌能够即刻使用。

latency:确定了一个数据包在tbf中等待传输的最长等待时间。

3)SFQ(随机公平队列):主要针对一个TCP会话或者UDP流,流量被分成相当多数量的fifo队列中,每个队列对应一个会话,数据按照简单轮转的方法发送,每个会话都按顺序得到发送机会。SFQ使用一种散列算法,把所有的会话映射到有限的几个队列中。SFQ会频繁的改变散列算法,使多个会话分配在同一个队列中而共享带宽的机率减小。

SFQ只进行调度,而不进行流量控制。

#tc qdisc add dev eth0 root sfq perturb 10

eth0上设置sfq队列,每10s钟重新设置一次散列算法


参数说明:

perturb:多少秒后重新配置一次散列算法

quantum:一个流至少要传输多少字节后才切换到下一个队列。缺省设置为最大包的长度。


分类队列规定:内部包含一个或多个子类,使用过滤器对数据包进行处理分类,然后交给相应的子类处理。

1)HTB(分层的令牌桶):一个分类的令牌桶过滤器。

htb可以很容易的保证每个类别的带宽,允许特定的类可以突破带宽上限,占用别的类的带宽。htb可以通过tbf实现带宽限制,也可以划分类别的优先级。

应用:

#tc qdisc add dev eth0 root handle 1: htb default 1

#tc class add dev eth0 parent 1: classid 1:1 htb rate 6mbit burst 15k

限制整个网络的速率小于6mbit,突发传输为15k

2)CBQ基于类别排队,既有限制带宽的能力,也具有带宽优先级管理的能力。

工作机制:确认链路的闲置时间足够长,以达到降低链路实际带宽的目的。所以,它要计算两个数据包的平均发送间隔。

最佳链路负载情况下,avgidle0,数据包严格按照计算出来的时间间隔到来。

过载链路时,avgidle的值应该是负的。若负值太严重,cbq就会暂时禁止发包,即overlimit(越限)。

闲置链路时,应该有很大的avgidle值,这样闲置几个小时后,会造成链路允许非常大的带宽通过。为了避免这种情况出现,用maxidle来限制avgidle的值不能太大。

# tc qdisc add dev eth0 root handle 1: cbq bandwidth 10Mbit avpkt 1000 cell 8 mpu 64
# 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,并且绑定了类11.即整个带宽不超过6mbit,且不能借入带宽


参数说明:

bandwidth:网卡的物理带宽,用来计算闲置时间。

avpkt:平均包的大小,单位是字节。

cell:用于设置时间力度,通常设置为8,必须是2的整数次幂。

mpu:最小包大小。

weight:偏袒数量。当其中一个类要求的带宽高于其他的类,那它每次就会比其他类处理weight数量的数据。

prio:优先级参数。当prio值较低时,只要有数据就必须先服务,其他类要延后处理。

allot:当一个类发送数据时,它发送的数据量的基值。

maxburst:决定了计算maxidle所使用的数据包的个数。在avgidle跌落到0之前,maxburst的数据包可以突发传输出去。值越高,越能容纳突发传输。

miniburst:当发生越限时,cbq禁止发包。一段时间后,突发性传输miniburst个数据包。值越大,整形越精确,一定程度上会有越多的突发传输。

bounded/borrow:bounded表示不会向其兄弟类借入带宽。borrowbounded相反。

设置根上的过滤器,以保证数据流被送到正确的队列规定中去。

# tc filter add dev eth0 parent 1:0 protocol ip prio 1 u32 match ip dport 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

//dport表示去往sport表示来自

3)PRIO不能限制带宽,因为属于不同类别的数据包是顺序离队的。但可以很容易实现流量的优先级管理。只有属于高优先级类别的数据包全部发送完毕,才会发送属于低优先级类别的数据包。

PRIOpfifo_fast的衍生,它的每个频道都是一个单独的类。缺省情况下类的命名为‘主标号:1/2/3’,数据包过滤到‘主标号:1’有最高优先级。

#tc qdisc add dev eth0 root handle 1: prio     //该命令运行后即创建了类1:1,1:2,1:3

#tc qdisc add dev eth0 parent 1:1 handle 10: sfq

#tc qdisc add dev eth0 parent 1:2 handle 20: tbf rate 20kbit buffer 1600 limit 3000

#tc qdisc add dev eth0 parent 1:3 handle 30: sfq

大批量数据(如scp操作)都是经过30:处理

交互数据则经过高优先级的10:或20:处理


参数说明:

limit/latency:确定最多有多少数据(字节数)在队列中等待令牌。Latency确定了一个包在TBF中等待传输的最长等待时间。

buffer:同burst

基本实现步骤

1)针对网络物理设备(如eth0)绑定一个队列规定qdisc

   一般情况下,针对一个网卡只需要建立一个队列。

2)在该队列规定上建立分类class

   一般情况下,针对一个队列需要建立一个根分类,然后再在其上建立子分类。对于分类,按其分类的编号顺序起作用,编号小的优先,一旦符合某个分类匹配规则,通过该分类发送数据包,则其后的分类不再起作用。

3)为每一个分类建立一个过滤器filter

4)最后与过滤器相配合,将流量送入相应的类进行排队;


TC命令

基本命令格式:

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

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

       tc filter [ add | change | replace ] dev DEV [ parent qdisc-id | root ] protocol protocol prio priority filtertype [ filtertype specific param‐eters ] flowid flow-id


add:在一个节点里加入一个qdiscclassfilter。建立qdiscfilter,可以使用句柄handle来命名;建立class,使用classid来命名。

change:修改节点的qdiscclassfilter。语法与add相同。

replace:对一个现有节点进行近于原子操作的删除/添加。如果节点不存在,会建立节点。

link:只适用于qdisc,替代一个现有的节点。

delete:删除有某个句柄指定的qdisc,根qdiscroot)也可删除。被删除qdisc上的所有子类及附属于各个类的过滤器都会被自动删除。


流量监视:

1.显示队列的状况

#tc qd ls dev eth0       //简单显示指定设备的队列状况

#tc -s qd ls dev eth0       //详细显示指定设备的队列状况

2.显示分类状况

#tc class ls dev eth0      //简单显示指定设备的分类状况

#tc -s class ls dev eth0        //详细显示指定设备的分类状况

3.显示过滤器状况

#tc -s filter ls dev eth0





参考: Linux TC流量控制HOWTO中文版(一) 
Linux TC流量控制HOWTO中文版(二) 

Linux 2.2/2.4完全能象那些最高端的专用带宽管理系统一样来管理带宽。甚至比帧中继和ATM还要优秀。
既然有这么好的系统为我们提供流量管理,下面我们当然要一探究竟!

为避免概念混乱,tc 采用如下规定来描述带宽:
mbps = 1024 kbps = 1024 * 1024 bps => byte/s
mbit = 1024 kbit => kilo bit/s.
mb = 1024 kb = 1024 * 1024 b => byte
mbit = 1024 kbit => kilo bit.

内定:数字以bps和b方式储存。
但当tc输出速率时,使用如下表示:
1Mbit = 1024 Kbit = 1024 * 1024 bps => byte/s

9.1. 解释队列和队列规定
利用队列,我们可以控制数据发送的方式。记住我们只能对发送数据进行控制(或称为整形)。
其实,我们无法直接控制别人向我们发送什么数据。然而,Internet主要依靠TCP/IP,它的一些特性很有用。因为TCP/IP没办法知道两个主机之间的网络容量,所以它会试图越来越快地发送数据(所谓的“慢起技术”) ,当因为网络容量不够而开始丢失数据时,再放慢速度。实际情况要比这种方法更聪明,我们以后再讨论。
这就象你嘴巴里面塞满东西,希望停止进食。在Internet上可以做到这一点。(译注:这个例子并不恰当,TCP/IP的这种机制并不是在网络层实现的,而是要靠传输层的TCP协议)
如果你有一个路由器,并且希望防止某些主机下载速度太快,你需要在你路由器的内网卡——也就是链接你的网内主机发送数据包的网卡上进行流量控制。
你还要保证你正在控制的是流量瓶颈环节。如果你有一个100M以太网卡,而你的路由器的链路速度是256k,你必须保证你发送的数据量没有超过路由器的处 理能力。否则,就是路由器在控制链路和对带宽进行整形,而不是你。可以说,我们需要拥有的队列必须是一系列链路中最慢的环节。

9.2. 简单的无类队列规定
如前所述,利用队列,我们决定了数据被发送的方式。无类队列规定就是那样,能够接受数据和重新编排、延迟或丢弃数据包。
这可以用作对于整个网卡的流量进行整形,而不细分各种情况。在我们进一步学习分类的队列规定之前,理解这部分是必不可少的!
最广泛应用的规定是pfifo_fast队列规定,因为它是缺省配置。还有其他队列规定,过后谈及。每种队列都有它们各自的优势和弱点。
9.2.1. pfifo_fast
这个队列的特点就象它的名字——先进先出(FIFO),也就是说没有任何数据包被特殊对待。这个队列有3个所谓的“频道”。FIFO规则应用于每一个频道。并且:如果在0频道有数据包等待发送,1频道的包就不会被处理,1频道和2频道之间的关系也是如此。
内核遵照数据包的TOS标记,把带有“最小延迟”标记的包放进0频道。

9.2.1.1. 参数与使用
pfifo_fast队列规定作为硬性的缺省设置,你不能对它进行配置。它缺省是这样配置的:
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的后面还有一个bit,所以TOS字段的实际值是上述值的2倍。(Tcpdump -v -v 可以让你看到整个TOS字段的情况,而不仅仅是这4个bit)也就是你在下表的第一列看到的值:
TOS     Bits        意义         Linux优先权   频道
------------------------------------------------------------
0x0           0             正常服务        0 最好效果     1
0x2           1             最小成本(mmc)   1 填充         2
0x4           2             最大可靠性(mr)  0 最好效果      1
0x6           3             mmc+mr          0 最好效果     1
0x8           4             最大吞吐量(mt)   2 大量传输     2
0xa           5             mmc+mt          2 大量传输     2
0xc           6             mr+mt           2 大量传输     2
0xe           7             mmc+mr+mt       2 大量传输     2
0x10           8           最小延迟(md)    6 交互         0
0x12           9           mmc+md          6 交互         0
0x14           10           mr+md           6 交互         0
0x16           11           mmc+mr+md       6 交互         0
0x18           12           mt+md           4 交互+大量传输 1
0x1a           13           mmc+mt+md       4 交互+大量传输 1
0x1c           14           mr+mt+md        4 交互+大量传输 1
0x1e           15           mmc+mr+mt+md    4 交互+大量传输 1
第二列写着与4个TOS位相关的数值,接着是它们的意义。比如,15表示一个数据包要求最小成本、最大可靠性、最大吞吐量和最小延迟。我想称之为“人代会车队”。(因为每种代表一个等级)
第四列写出了Linux内核对于TOS位的理解,并表明了它们对应哪种优先权。
最后一列表明缺省的权限图。在命令行里,缺省的权限图应该是:
1, 2, 2, 2, 1, 2, 0, 0 , 1, 1, 1, 1, 1, 1, 1, 1
也就是说,比如优先权4将被映射到1频道。权限图允许你列出更高的优先权值(只要小于7),它们不对应TOS映射,但是有其它的意图。

下表来自RFC 1349,告诉你应用程序可能如何设置它们的TOS:

队列的长度来自网卡的配置,你可以用ifconfig和ip命令修改。如设置队列长度为10,执行:ifconfig eth0 txqueuelen 10(不能用tc命令设置这个!)

9.2.2. 令牌桶过滤器(TBF)
令牌桶过滤器(TBF)是一个简单的队列规定:只允许以不超过事先设定的速率到来的数据包通过,但可能允许短暂突发流量超过设定值。
TBF很精确,对于网络和处理器的影响都很小。所以如果您想对一个网卡限速,它应该是最好选择!
TBF的实现在于一个缓冲器(桶),不断地被一些叫做“令牌”的虚拟数据以特定速率填充着。 (token rate)。桶最重要的参数就是它的大小,也就是它能够存储令牌的数量。
每个到来的令牌从数据队列中收集一个数据包,然后从桶中被删除。这个算法关联到两个流上——令牌流和数据流,于是我们得到3种情景:
  1)数据流以等于令牌流的速率到达TBF。这种情况下,每个到来的数据包都能对应一个令牌,然后无延迟地通过队列。
  2)数据流以小于令牌流的速度到达TBF。通过队列的数据包只消耗了一部分令牌,剩下的令牌会在桶里积累下来,直到桶被装满。剩下的令牌可以在需要以高于令牌流速率发送数据流的时候消耗掉,这种情况下会发生突发传输。
  3)数据流以大于令牌流的速率到达TBF。这意味着桶里的令牌很快就会被耗尽。导致TBF中断一段时间,称为“越限”。如果数据包持续到来,将发生丢包。
第三种情景非常重要,因为它会对数据通过过滤器的速率进行整形。令牌的积累可以导致越限的数据进行短时间的突发传输而不必丢包,但是持续越限的话会导致传输延迟直至丢包。实际的实现是针对数据的字节数进行的,而不是针对数据包进行的。
9.2.2.1. 参数与使用
TBF提供了一些可调控的参数。第一个参数永远可用:
limit/latency
limit确定最多有多少数据(字节数)在队列中等待令牌。你也可以通过设置latency来指定这个参数,latency参数确定了一个包在TBF中等待传输的最长等待时间。两者计算决定桶的大小、速率和峰值速率。
burst/buffer/maxburst
桶的大小,以字节计。这个参数指定了最多可以有多少个令牌能够即刻被使用。通常,管理的带宽越大,需要的缓冲器就越大。在Intel体系上,10兆bit/s的速率需要至少10k字节的缓冲区才能达到期望的速率。
如果你的缓冲区太小,就会导致到达的令牌没有地方放(桶满了),这会导致潜在的丢包。
mpu
一个零长度的包并不是不耗费带宽。比如以太网,数据帧不会小于64字节。Mpu(Minimum Packet Unit,最小分组单位)决定了令牌的最低消耗。
rate
速度操纵杆。参见上面的limits!
如果桶里存在令牌而且允许没有令牌,相当于不限制速率(缺省情况)。如果不希望这样,可以调整入下参数:
peakrate
如果有可用的令牌,数据包一旦到来就会立刻被发送出去,就象光速一样。那可能并不是你希望的,特别是你有一个比较大的桶的时候。
峰值速率可以用来指定令牌以多块的速度被删除。用书面语言来说,就是:释放一个数据包,但后等待足够的时间后再释放下一个。我们通过计算等待时间来控制峰值速率。例如:UNIX定时器的分辨率是10毫秒,如果平均包长10k bit,我们的峰值速率被限制在了1Mbps。
mtu/minburst
但是如果你的常规速率比较高,1Mbps的峰值速率就需要调整。要实现更高的峰值速率,可以在一个时钟周期内发送多个数据包。最有效的办法就是:再创建一个令牌桶!这第二个令牌桶缺省情况下为一个单个的数据包,并非一个真正的桶。
要计算峰值速率,用mtu乘以100就行了。 (应该说是乘以HZ数,Intel体系上是100,Alpha体系上是1024)

9.2.2.2. 配置范例
这是一个非常简单而实用的例子:
# tc qdisc add dev ppp0 root tbf rate 220kbit latency 50ms burst 1540
为什么它很实用呢?如果你有一个队列较长的网络设备,比如DSL modem或者cable modem什么的,并通过一个快速设备(如以太网卡)与之相连,你会发现上传数据会破坏交互性。
这是因为上传数据会充满modem的队列,而这个队列为了改善上载数据的吞吐量而设置的特别大。你可能为了提高交互性而需要一个不太大的队列。也就是说你希望在发送数据的时候干点别的事情。
上面的命令行并非直接影响了modem中的队列,而是通过控制Linux中的队列而放慢了发送数据的速度。
把220kbit修改为你实际的上载速度再减去几个百分点。如果你的modem确实很快,就把“burst”值提高一点。

 

9.2.3. 随机公平队列(SFQ)
SFQ(Stochastic Fairness Queueing,随机公平队列)简单实现公平队列算法。它的精确性不如其它的方法,但是它在实现高度公平的同时,需要的计算量却很少。
SFQ的关键词是“会话”(或称作“流”) ,主要针对一个TCP会话或者UDP流。流量被分成相当多数量的FIFO队列中,每个队列对应一个会话。数据按照简单轮转的方式发送, 每个会话都按顺序得到发送机会。
这种方式非常公平,保证了每一个会话都不会没其它会话所淹没。SFQ之所以被称为“随机”,是因为它并不是真的为每一个会话创建一个队列,而是使用一个散列算法,把所有的会话映射到有限的几个队列中去。
因为使用了散列,所以可能多个会话分配在同一个队列里,从而需要共享发包的机会,也就是共享带宽。为了不让这种效应太明显,SFQ会频繁地改变散列算法,以便把这种效应控制在几秒钟之内。
有很重要的一点需要声明:只有当你的出口网卡确实已经挤满了的时候,SFQ才会起作用!否则在你的Linux机器中根本就不会有队列,SFQ也就不会起作用。稍后我们会描述如何把SFQ与其它的队列规定结合在一起,以保证两种情况下都比较好的结果。
特别地,在你使用DSL modem或者cable modem的以太网卡上设置SFQ而不进行任何进一步地流量整形是无谋的!
9.2.3.1. 参数与使用
SFQ基本上不需要手工调整:
perturb:多少秒后重新配置一次散列算法。如果取消设置,散列算法将永远不会重新配置(不建议这样做)。10秒应该是一个合适的值。
quantum:一个流至少要传输多少字节后才切换到下一个队列。却省设置为一个最大包的长度(MTU的大小)。不要设置这个数值低于MTU!
9.2.3.2. 配置范例
如果你有一个网卡,它的链路速度与实际可用速率一致——比如一个电话MODEM——如下配置可以提高公平性:
# tc qdisc add dev ppp0 root sfq perturb 10
# tc -s -d qdisc ls
qdisc sfq 800c: dev ppp0 quantum 1514b limit 128p flows 128/1024 perturb 10sec
Sent 4812 bytes 62 pkts (dropped 0, overlimits 0)

“800c:”这个号码是系统自动分配的一个句柄号,“limit”意思是这个队列中可以有128个数据包排队等待。一共可以有1024个散列目标可以用于速率审计,而其中128个可以同时激活。(no more packets fit in the queue!)每隔10秒种散列算法更换一次。

 

9.3. 关于什么时候用哪种队列的建议
总之,我们有几种简单的队列,分别使用排序、限速和丢包等手段来进行流量整形。
下列提示可以帮你决定使用哪一种队列。涉及到了第14章所描述的的一些队列规定:
   如果想单纯地降低出口速率,使用令牌桶过滤器(tbf)。调整桶的配置后可用于控制很高的带宽。
   如果你的链路已经塞满了,而你想保证不会有某一个会话独占出口带宽,使用随机公平队列(sfq)。
   如果你有一个很大的骨干带宽,并且了解了相关技术后,可以考虑前向随机丢包(red)。
   如果希望对入口流量进行“整形”(不是转发流量),可使用入口流量策略,注意,这不是真正的“整形”。
   如果你正在转发数据包,在数据流出的网卡上应用TBF。因为入口网卡起决定性作用的时候,除非你希望让数据包从多个网卡流出。还是使用入口策略。
   如果你并不希望进行流量整形,只是想看看你的网卡是否有比较高的负载而需要使用队列,使用pfifo队列(不是pfifo_fast)。它缺乏内部频道但是可以统计backlog。(好像更适合用于做流量监控)
最后,你可以进行所谓的“社交整形”。你不能通过技术手段解决一切问题。用户的经验技巧永远是不友善的。正确而友好的措辞可能帮助你的正确地分配带宽!(真够废话的,就是自己找人沟通)

9.4. 术语
为了正确地理解更多的复杂配置,有必要先解释一些概念。由于这个主题的历史不长和其本身的复杂性,人们经常在说同一件事的时候使用各种词汇。
以下来自draft-ietf-diffserv-model-06.txt,Diffserv路由器的建议管理模型。可以在以下地址找到:
http://www.ietf.org/internet-drafts/draft-ietf-diffserv-model-06.txt.
关于这些词语的严格定义请参考这个文档。
队列规定
管理设备输入(ingress)或输出(egress)的一个算法。
无类的队列规定
一个内部不包含可配置子类的队列规定。
分类的队列规定
一个分类的队列规定内可一包含更多的类。其中每个类又进一步地包含一个队列规定,这个队列规定可以是分类的,也可以是无类的。


一个分类的队列规定可以拥有很多类,类内包含队列规定。
分类器(就是过滤器)
每个分类的队列规定都需要决定什么样的包使用什么类进行发送。分类器就是做这个用的。
过滤器(就是分类器)
分类是通过过滤器完成的。一个过滤器包含若干的匹配条件,如果符合匹配条件,就按此过滤器分类。
调度
在分类器的帮助下,一个队列规定可以裁定某些数据包可以排在其他数据包之前发送。这种处理叫做“调度”,比如此前提到的pfifo_fast就是这样的。

整形
在一个数据包发送之前进行适当的延迟,以免超过事先规定好的最大速率,这种处理叫做“整形”。整形在egress处进行。习惯上,通过丢包来降速也经常被称为整形。
策略
通过延迟或是丢弃数据包来保证流量不超过事先规定的带宽。在Linux里,策略总是规定丢弃数据包而不是延迟。即,不存在ingress队列。
Work-Conserving
对于一个work-conserving队列规定,如果得到一个数据包,它总是立刻对它进行分发。换句话说,只要网卡(egress队列规定)允许,它就不会延迟数据包的发送。
non-Work-Conserving
有些队列——比如令牌桶过滤器——可能需要暂时停止发包以实现限制带宽。也就是说它们有时候即使有数据包需要处理,也可能拒绝发送。
现在我们简单了解了一些术语,让我们看看他们的位置:

    感谢Jamal Hadi Salim制作的ASCII字符图像。
整个大方框表示内核。

1、最左面的箭头表示从网络上进入机器的数据包。它们进入Ingress队列规定,并有可能被某些过滤器丢弃即所谓策略。(在进入内核之前丢弃数据有利于节约CPU时间)。
2、数据包顺利通过的话,如果它是发往本地进程的,就会进入IP协议栈处理并提交给该进程。

3、如果它需要转发而不是进入本地进程,就会发往egress。本地进程也可以发送数据,交给Egress分类器。
4、然后经过审查,并放入若干队列规定中的一个进行排队。这个过程叫做“入队”。在不进行任何配置的情况下,只有一个egress队列规定——pfifo_fast——总是接收数据包。
5、数据包进入队列后,就等待内核处理并通过某网卡发送。这个过程叫做“出队”。

这张图仅仅表示了机器上只有一块网卡的情况,图中的箭头不能代表所有情况。每块网卡都有它自己的ingress和egress。

 

9.5. 分类的队列规定


如果你有多种数据流需要进行区别对待,分类的队列规定就非常有用了。其中一种叫CBQ(Class Based Queueing,基于类的队列)经常被提起,以至于大家认为CBQ就是鉴别队列是否分类的标准,这是不对的:
CBQ不过是家族中最大的孩子而已,同时也是最复杂的。它并不能为你做所有的事情。以此很多人认为不可思议,因为他们受“sendmail效应”影响较深,总是认为只要是复杂的+并且没有文档的技术=肯定是最好的。


9.5.1. 分类的队列规定及其类中的数据流向
一旦数据包进入一个分类的队列规定,它就得被送到某一个类中——也就是需要分类。对数据包进行分类的工具是过滤器。一定要记住:“分类器”是从队列规定内部调用的,而不是从别处。
过滤器会返回一个决定,队列规定就根据这个决定把数据包送入相应的类进行排队。每个子类都可以再次使用它们自己的过滤器进行进一步的分类。直到不需要分类为止,数据包才进入该类包含的队列规定等待处理。
除了能够包含其它队列规定之外,绝大多数分类的队列规定还能够流量整形。这对于需要同时进行调度(如使用SFQ)和流量控制的场合非常有用。

如果你仅仅使用SFQ,那什么用也没有。因为数据包进、出路由器时没有任何延迟。虽然你的输出网卡远远快于实际连接速率,但路由器中却没有队列可以调度。


9.5.2. 队列规定家族:根、句柄、兄弟和父辈 
每块网卡都有一个出口“根队列规定”,缺省情况下是前面提到的pfifo_fast队列规定。每个队列规定都指定一个句柄(就是队列代号),以便以后的配置语句能够引用这个队列规定。除了出口队列规定之外,每块网卡还有一个入口,以便策略进入的数据流。
队列规定的句柄有两个部分:一个主号码和一个次号码。习惯上把根队列规定称为“1:”,等价于“1:0”。队列规定的次号码永远是0。
类的主号码必须与它们父辈的主号码一致。
9.5.2.1. 如何用过滤器进行分类
下图给出一个典型的分层关系:
          根 1:
                   |
                 1:1
            /      |      \
         10:    11:    12:
        /   \             /   \
    10:1 10:2   12:1 12:2

数据包是在根队列规定处入队和出队的,而内核只与“根”打交道。
一个数据包可能是按照下面这个链状流程进行分类的:
1: -> 1:1 -> 12: -> 12:2
数据包现在应该处于12:2下属的某个队列规定中的某个队列中。在这个例子中,树的每个节点都附带着一个过滤器,用来选择下一步进入哪个分支。这样比较直观。然而,这样也是允许的:
1: -> 12:2
也就是说,根所附带的一个过滤器要求把数据包直接交给12:2。
9.5.2.2. 数据包如何出队并交给硬件
当内核决定把一个数据包发给网卡的时候,根队列规定1:会得到一个出队请求,然后把它传给1:1,然后依次传给10:、11:和12:,然后试图从它们中 进行dequeue(出列)操作。也就是说,内核需要遍历整颗树,因为只有12:2中才有这个数据包。换句话说,类及其兄弟仅仅与其“父队列规定”进行交 谈,而不会与网卡进行交谈。只有根队列规定才能由内核进行出队操作!
更进一步,任何类的出队操作都不会比它们的父类更快。这恰恰是你所需要的:我们可以把SFQ作为一个子类,放到一个可以进行流量整形的父类中,从而能够同时得到其父类的流量整形功能SFQ的调度功能

 

9.5.3. PRIO队列规定
PRIO队列规定并不进行整形,它仅仅根据你配置的过滤器把流量进一步细分。你可以认为PRIO队列规定是pfifo_fast的一种衍生物,区别在每个频道都是一个单独的类,而非简单的FIFO。
当数据包进入PRIO队列规定后,根据你给的过滤器设置选择一个类,缺省情况下有三个类。这些类仅包含纯FIFO队列规定而没有更多的内部结构。你可以把它们替换成你需要的任何队列规定。
每当有一个数据包需要出队时,首先处理:1类。只有当标号更小的类中没有需要处理的数据时,才会处理标号更大的类。
当你希望不仅仅依靠包的TOS,而是想使用tc所提供的更强大的功能来进行数据包的优先权划分时,可以使用这个队列规定。它也可以包含更多的队列规定,而pfifo_fast却只能包含简单的fifo队列规定。
因为它不进行整形,所以使用时与SFQ有相同的考虑:要么确保这个网卡的带宽确实已经占满,要么把它包含在一个能够整形的分类的队列规定的内部。严格地说,PRIO队列规定是一种Work-Conserving调度。
9.5.3.1. PRIO的参数与使用
PRIO识别下列参数:
bands
创建频道的数目。每个频道实际上就是一个类。如果你修改了这个数值,你必须同时修改:
priomap
如果你不给tc提供任何过滤器,PRIO队列规定将参考TC_PRIO的优先级来决定如何给数据包入队。
它的行为就像前面提到过的pfifo_fast队列规定(先入先出)

其实频道是类,缺省情况下命名为“主标号:1”到“主标号:3”。如果你的PRIO队列规定是“12: ”,把数据包过滤到“12:1”将得到最高优先级。注意:0频道的次标号是1;1频道的次标号是2,以此类推。
9.5.3.2. 配置范例
我们想创建这个树:
   root        1:         prio
             /    |    \
          1:1 1:2 1:3
            |     |     |
         10:  20:  30:
        sfq   tbf   sfq
band 0      1      2

大批量数据使用30:交互数据使用20:或10:。
命令如下:
# tc qdisc add dev eth0 root handle 1: prio
## 这个命令立即创建了类: 1:1, 1:2, 1:3
# tc qdisc add dev eth0 parent 1:1 handle 10: sfq
# tc qdisc add dev eth0 parent 1:2 handle 20: tbf rate 20kbit buffer 1600 limit 3000
# tc qdisc add dev eth0 parent 1:3 handle 30: sfq

我们看看结果如何:
# tc -s qdisc ls dev eth0
qdisc sfq 30: quantum 1514b
Sent 0 bytes 0 pkts (dropped 0, overlimits 0)
qdisc tbf 20: rate 20Kbit burst 1599b lat 667.6ms
Sent 0 bytes 0 pkts (dropped 0, overlimits 0)
qdisc sfq 10: quantum 1514b
Sent 132 bytes 2 pkts (dropped 0, overlimits 0)
qdisc prio 1: bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
Sent 174 bytes 3 pkts (dropped 0, overlimits 0)

如你所见,0频道已经有了一些流量,那是在运行这个命令之后发送了一个包!
现在我们来点大批量数据传输(使用能够正确设置TOS标记的工具):
# scp tcroot@10.0.0.1
root@10.0.0.1
 password:
tc 100% |*****************************| 353 KB 00:00
# tc -s qdisc ls dev eth0
qdisc sfq 30: quantum 1514b
Sent 384228 bytes 274 pkts (dropped 0, overlimits 0)
qdisc tbf 20: rate 20Kbit burst 1599b lat 667.6ms
Sent 2640 bytes 20 pkts (dropped 0, overlimits 0)
qdisc sfq 10: quantum 1514b
Sent 2230 bytes 31 pkts (dropped 0, overlimits 0)
qdisc prio 1: bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
Sent 389140 bytes 326 pkts (dropped 0, overlimits 0)
如你所见,所有的流量都是经过30:处理的,优先权最低。现在我们验证一下交互数据传输经过更高优先级的频道,我们生成一些交互数据传输:
# tc -s qdisc ls dev eth0
qdisc sfq 30: quantum 1514b
Sent 384228 bytes 274 pkts (dropped 0, overlimits 0)
qdisc tbf 20: rate 20Kbit burst 1599b lat 667.6ms
Sent 2640 bytes 20 pkts (dropped 0, overlimits 0)
qdisc sfq 10: quantum 1514b
Sent 14926 bytes 193 pkts (dropped 0, overlimits 0)
qdisc prio 1: bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
Sent 401836 bytes 488 pkts (dropped 0, overlimits 0)

正常,所有额外的流量都是经10:这个更高优先级的队列规定处理的。与先前的整个scp不同,没有数据经过最低优先级的队列规定。

 

9.5.4. 著名的CBQ队列规定
如前所述,CBQ是最复杂、最琐碎、最难以理解、最刁钻的队列规定。这并不是因为其作者的恶毒或者不称职,而是因为CBQ算法本身的不精确,而且与Linux的内在机制不协调造成的。
除了可以分类之外,CBQ也是一个整形器,但是从表面上看来工作得并不好。它应该是这样的:如果你试图把一个10Mbps的连接整形成1Mbps的速率,就应该让链路90%的时间处于闲置状态,必要的话我们就强制,以保证90%的闲置时间。
但闲置时间的测量非常困难,所以CBQ就采用了它一个近似值——来自硬件层的两个传输请求之间的毫秒数来代替它。这个参数可以近似地表现这个链路的繁忙程度。
这样做相当慎重,而且不一定能够得到正确的结论。比如,由于驱动程序方面或者其它原因造成一块网卡的实际传输速率不能够达到它的标称速率,该怎么办?由于总线设计的原因,PCMCIA网卡永远也不会达到100Mbps。那么我们该怎么计算闲置时间呢?
如果我们引入非物理网卡——像PPPoE、PPTP——情况会变得更糟糕。因为相当一部分有效带宽耗费在了链路维护上。那些做过了测试的人们都发现CBQ 总是不精确甚至完全失去了其本来意义。但是,在很多场合下它还是能够很好地工作。根据下面的文档,你应该能够较好地配置CBQ来解决大多数问题。

9.5.4.1. CBQ整形的细节:
CBQ的工作机制是确认链路的闲置时间足够长,以达到降低链路实际带宽的目的。为此,它要计算两个数据包的平均发送间隔。
操作期间,有效闲置时间的测量使用EWMA(exponential weighted moving average,指数加权移动均值)算法,也就是说最近处理的数据包的权值比以前的数据包按指数增加。UNIX的平均负载也是这样算出来的。
计算出来的平均时间值减去EWMA测量值,得出的结果叫做“avgidle”。最佳的链路负载情况下,这个值应当是0:数据包严格按照计算出来的时间间隔到来。
在一个过载的链路上,avgidle值应当是负的。如果这个负值太严重,CBQ就会暂时禁止发包,称为“overlimit”(越限)。
相反地,一个闲置的链路应该有很大的avgidle值,这样闲置几个小时后,会造成链路允许非常大的带宽通过。为了避免这种局面,我们用maxidle来限制avgidle的值不能太大。

理论上讲,如果发生越限,CBQ就会禁止发包一段时间(长度就是事先计算出来的传输数据包之间的时间间隔),然后通过一个数据包后再次禁止发包。但是最好参照一下下面的minburst参数。
下面是配置整形时需要指定的一些参数:
avpkt
平均包大小,单位是字节。计算maxidle(最大闲置)时需要,maxidle从maxburst得出。
bandwidth
网卡的物理带宽,用来计算闲置时间。
cell
一个数据包被发送出去的时间可以是基于包长度而阶梯增长的。一个800 字节的包和一个806字节的包可以认为耗费相同的时间。也就是说它用作设置时间力度。通常设置为8,必须是2的整数次幂。
maxburst
这个参数的值决定了计算maxidle所使用的数据包的个数。在avgidle跌落到0之前,这么多的数据包可以突发传输出去。这个值越高,越能够容纳突发传输。你无法直接设置maxidle的值,必须通过这个参数来控制。
minburst
如前所述,发生越限时CBQ会禁止发包。实现这个的理想方案是根据事先计算出的闲置时间进行延迟之后,发一个数据包。然而,UNIX的内核一般来说都有一 个固定的调度周期(一般不大于10ms),所以最好是这样:禁止发包的时间稍长一些,然后突发性地传输minburst个数据包,而不是一个一个地传输。 等待的时间叫做offtime。
从大的时间尺度上说,minburst值越大,整形越精确。但是,从毫秒级的时间尺度上说,就会有越多的突发传输。
minidle
如果avgidle值降到0,也就是发生了越限,就需要等待,直到avgidle的值足够大才发送数据包。为避免因关闭链路太久而引起的以外突发传输,在avgidle的值太低的时候会被强制设置为minidle的值。
参数minidle的值是以负微秒记的。所以10代表avgidle被限制在-10us上。
mpu
最小包尺寸——因为即使是0长度的数据包,在以太网上也要生成封装成64字节的帧,而需要一定时间去传输。为了精确计算闲置时间,CBQ需要知道这个值。
rate
期望中的传输速率。也就是“油门”!

在CBQ的内部由很多的微调参数。比如,那些已知队列中没有数据的类就不参加计算、越限的类将被惩罚性地降低优先级等等。都非常巧妙和复杂。


9.5.4.2. CBQ在分类方面的行为
除了使用上述idletime近似值进行整形之外,CBQ还可以象PRIO队列那样,把各种类赋予不同的优先级,优先权数值小的类会比优先权值大的类被优先处理。
每当网卡请求把数据包发送到网络上时,都会开始一个WRR(weighted round robin,加权轮转)过程,从优先权值小的类开始。
那些队列中有数据的类就会被分组并被请求出队。在一个类收到允许若干字节数据出队的请求之后,再处理下一个相同优先权值的类。

下面是控制WRR过程的一些参数:
allot

当从外部请求一个CBQ发包的时候,它就会按照“priority(prio)”参数指定的顺序轮流尝试其内部的每一个类的队列规定。当轮到一个类发数据时,它只能发送一定量的数据。“allot”参数就是这个量的基值。更多细节请参照“weight”参数。
prio
CBQ可以象PRIO设备那样工作。其中“prio”值较低的类只要有数据就必须先服务,其他类要延后处理。
weight
“weight”参数控制WRR过程。每个类都轮流取得发包的机会。如果其中一个类要求的带宽显著地高于其他的类,就应该让它每次比其他的类发送更多的数据。(以字节为单位,可以理解为偏袒数量,例如weight 200Kbit 就相当于每次处理优先级的数据比普通数据多处理200Kbit)
CBQ会把一个类下面所有的weight值加起来后归一化,所以数值可以任意定,只要保持比例合适就可以。人们常把“速率/10”作为参数的值来使用,实际工作得很好。归一化值后的值乘以“allot”参数后,决定了每次传输多少数据。

 

9.5.4.3. 决定链路的共享和借用的CBQ参数
除了纯粹地对某种数据流进行限速之外,CBQ还可以指定哪些类可以向其它哪些类借用或者出借一部分带宽。
Isolated/sharing (isolated字面意思:独立,单独的)
凡是使用“isolated”选项配置的类,就不会向其兄弟类借出带宽。如果你的链路上同时存在着不友好的人,你就可以使用这个选项。
选项“sharing”是“isolated”的反义选项。
bounded/borrow (bounded字面意思:受限制的,有限的;borrow=借入)
一个类也可以用“bounded”选项配置,意味着它不会向其兄弟类借入带宽。选项“borrow”是“bounded”的反义选项。
一个典型的情况就是你的一个链路上有多个客户都设置成了“isolated”和“bounded”,那就是说他们都被限制在其要求的速率之下,且互相之间不会借用带宽。(就是我们常说的带宽独享)在这样的一个类的内部的子类之间是可以互相借用带宽的。

9.5.4.4. 配置范例
这个配置把WEB服务器的流量控制为5Mbps、SMTP流量控制在3Mbps上。而且二者一共不得超过6Mbps,互相之间允许借用带宽。我们的网卡是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。
如前所述,CBQ需要调整很多的参数。其实所有的参数上面都解释过了。相应的待会介绍的HTB配置则要简明得多。(期待吧?!哈哈)
# 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 class add” 在一个队列规定中创建了类,然后使用“tc qdisc add”在类中创建队列规定。
你可能想知道,那些没有被那两条规则分类的数据流怎样处理了呢?从这个例子来说,它们被1:0直接处理,没有限制。
如果SMTP+web的总带宽需求大于6Mbps,那么这6M带宽将按照两个类的weight参数的比例情况进行分割:WEB服务器得到 5/8的带宽,SMTP得到3/8的带宽。从这个例子来说,可以这么认为:WEB数据流总是会得到5/8*6Mbps=3.75Mbps的带宽。
9.5.4.5. 其它CBQ参数:split和defmap (split:分离、分裂)
如前所述,一个分类的队列规定需要调用过滤器来决定一个数据包应该发往哪个类去排队。
除了调用过滤器,CBQ还提供了其他方式,defmap和split。很难掌握,但好在无关大局。但是现在是解释defmap和split的最佳时机,我会尽力解释。
因为你经常是仅仅需要根据TOS来进行分类,所以提供了一种特殊的语法。当CBQ需要决定了数据包要在哪里入队时,要检查这个节点是否为“split节 点”。如果是,子队列规定中的一个应该指出它接收所有带有某种优先权值的数据包,权值可以来自TOS字段或者应用程序设置的套接字选项。
数据包的优先权位与defmap字段的值进行“或”运算来决定是否存在这样的匹配。换句话说,这是一个可以快捷创建仅仅匹配某种优先权值数据包的过滤器的方法。如果defmap等于0xff,就会匹配所有包,0则是不匹配。下面的实例可以帮助理解:
# tc qdisc add dev eth1 root handle 1: cbq bandwidth 10Mbit allot 1514 cell 8 avpkt 1000 mpu 64
# tc class add dev eth1 parent 1:0 classid 1:1 cbq bandwidth 10Mbit rate 10Mbit allot 1514 cell 8 weight 1Mbit prio 8 maxburst 20 avpkt 1000

一个标准的CBQ前导。
Defmap参照TC_PRIO位(我从来不直接使用数字!):
TC_PRIO..      Num      对应 TOS
-------------------------------------------------
BESTEFFORT      0       最高可靠性
FILLER          1       最低成本
BULK            2       最大吞吐量(0x8)
INTERACTIVE_BULK 4
INTERACTIVE     6       最小延迟(0x10)
CONTROL         7
TC_PRIO..的数值对应它右面的bit。关于TOS位如何换算成优先权值的细节可以参照前面pfifo_fast章节。
然后是交互和大吞吐量的类:
# tc class add dev eth1 parent 1:1 classid 1:2 cbq bandwidth 10Mbit rate 1Mbit allot 1514 cell 8 weight 100Kbit prio 3 maxburst 20 avpkt 1000split 1:0 defmap c0
# tc class add dev eth1 parent 1:1 classid 1:3 cbq bandwidth 10Mbit rate 8Mbit allot 1514 cell 8 weight 800Kbit prio 7 maxburst 20 avpkt 1000split 1:0 defmap 3f

“split队列规定”是1:0,也就是做出选择的地方。c0是二进制的11000000,3F是00111111,所以它们共同匹配所有的数据包。第一个类匹配第7和第6位,也就是负责“交互”和“控制”的数据包。第二个类匹配其余的数据包。
节点1:0现在应该有了这样一个表格:

为了更有趣,你还可以传递一个“change掩码”,确切地指出你想改变哪个优先权值。你只有在使用了“tc class change”的时候才需要。比如,往1:2中添加best effort数据流,应该执行:
# tc class change dev eth1 classid 1:2 cbq defmap 01/01
现在,1:0上的优先权分布应该是:

9.5.5. HTB(Hierarchical Token Bucket, 分层的令牌桶)
Martin Devera 意识到CBQ太复杂,而且并没有按照多数常见情况进行优化。他的Hierarchical能够很好地满足这样一种情况:你有一个固定速率的链路,希望分割给多种不同的用途使用。为每种用途做出带宽承诺并实现定量的带宽借用。
HTB就象CBQ一样工作,但是并不靠计算闲置时间来整形。它是一个分类的令牌桶过滤器。它只有很少的参数,并且在它的网站能够找到很好的文档。
随着你的HTB配置越来越复杂,你的配置工作也会变得复杂。但是使用CBQ的话,即使在很简单的情况下配置也会非常复杂!HTB3 (关于它的版本情况,请参阅它的网站)已经成了官方内核的一部分(2.4.20-pre1、2.5.31及其后)。然而,你可能仍然要为你的tc命令打上HTB3支持补丁,否则你的tc命令不理解HTB3。
如果你已经有了一个新版内核或者已经打了补丁,请尽量考虑使用HTB。

9.5.5.1. 配置范例
环境与要求与上述CBQ的例子一样:

把WEB服务器的流量控制为5Mbps、SMTP流量控制在3Mbps上。而且二者一共不得超过6Mbps,互相之间允许借用带宽。我们的网卡是100Mbps的。
# tc qdisc add dev eth0 root handle 1: htb default 30
# tc class add dev eth0 parent 1: classid 1:1 htb rate 6mbit burst 15k
# tc class add dev eth0 parent 1:1 classid 1:10 htb rate 5mbit burst 15k
# tc class add dev eth0 parent 1:1 classid 1:20 htb rate 3mbit ceil 6mbit burst 15k
# tc class add dev eth0 parent 1:1 classid 1:30 htb rate 1kbit ceil 6mbit burst 15k

作者建议在那些类的下方放置SFQ: 
# tc qdisc add dev eth0 parent 1:10 handle 10: sfq perturb 10
# tc qdisc add dev eth0 parent 1:20 handle 20: sfq perturb 10
# tc qdisc add dev eth0 parent 1:30 handle 30: sfq perturb 10
 
添加过滤器,直接把流量导向相应的类:
# U32="tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32"
# $U32 match ip dport 80 0xffff flowid 1:10
# $U32 match ip sport 25 0xffff flowid 1:20

不错,没有奇怪的数字,没有复杂的参数。
HTB完成得相当不错,如果10:和20:都得到了保证的速率,剩下的就是分割了,它们借用的比率是5:3,正如你期望的那样。
未被分类的流量被送到了30:,仅有一点点带宽,但是却可以任意借用剩下的带宽。因为我们内部使用了SFQ,而可以公平发包。
9.6. 使用过滤器对数据包进行分类
为了决定用哪个类处理数据包,必须调用所谓的“分类器链” 进行选择。这个链中包含了这个分类队列规定所需的所有过滤器。
重复前面那棵树:
    根          1:
                   |
               _1:1_
             /      |      \
        10:     11:     12:
         / \                  / \
  10:1 10:2     12:1 12:2

当一个数据包入队的时候,每一个分支处都会咨询过滤器链如何进行下一步。典型的配置是在1:1处有一个过滤器把数据包交给12:,然后12:处的过滤器在把包交给12:2。
你可以把后一个过滤器同时放在1:1处,可因为?having more specific tests lower in the chain.?而得到效率的提高。
另外,你不能用过滤器把数据包向“上”送。而且,使用HTB的时候应该把所有的规则放到根上!
再次强调:数据包只能向“下”进行入队操作!只有处队的时候才会上到网卡所在的位置来。他们不会落到树的最底层后送到网卡!

本文于2010年7月27日由郭占峰对其语法表达及错字进行修正
9.6.1. 过滤器的一些简单范例
就象在“分类器”那章所解释的,借助一些复杂的语法你可以详细地匹配任何事情。下面我们就开始简单地匹配一些比较有明显特征的语法开始。
比方说,我们有一个PRIO队列规定,叫做“10:”,包含3个类,我们希望把去往22口的数据流发送到最优先的频道中去。应该这样设置过滤器:
tc filter add dev eth0 protocol ip parent 10: prio 1 u32match ip dport 22 0xffff flowid 10:1
tc filter add dev eth0 protocol ip parent 10: prio 1 u32 match ip sport 80 0xffff flowid 10:1
# tc filter add dev eth0 protocol ip parent 10: prio 2 flowid 10:2

意思是说:
向eth0上的10:节点添加一个u32过滤规则,它的优先权是1:凡是去往22口(精确匹配)的IP数据包,发送到频道10:1。
向eth0上的10:节点添加一个u32过滤规则,它的优先权是1:凡是来自80口(精确匹配)的IP数据包,发送到频道10:1。

向eth0上的10:节点添加一个过滤规则,它的优先权是2:凡是上面未匹配的IP数据包,发送到频道10:2。
别忘了添加“dev eth0”(你的网卡或许叫别的名字),因为每个网卡的句柄都有完全相同的命名空间。
想通过IP地址进行筛选的话,这么敲:
# tc filter add dev eth0 parent 10:0 protocol ip prio 1 u32 match ip dst 4.3.2.1/32 flowid 10:1
# tc filter add dev eth0 parent 10:0 protocol ip prio 1 u32 match ip src 1.2.3.4/32 flowid 10:1
# tc filter add dev eth0 protocol ip parent 10: prio 2 flowid 10:2

这个例子把去往4.3.2.1和来自1.2.3.4的数据包送到了最高优先的队列,其它的则送到次高权限的队列。
你可以连续使用match,想匹配来自1.2.3.4的80口的数据包的话,就这么敲:
tc filter add dev eth0 parent 10:0 protocol ip prio 1 u32match ip src 4.3.2.1/32 match ip sport 80 0xffff flowid 10:1

9.6.2. 常用到的过滤命令一览
这里列出的绝大多数命令都根据这个命令改编而来:
# 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
单个IP地址使用“/32”作为掩码即可。
根据源/目的端口,所有IP协议
源    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(防火墙标记功能)你可以使用ipchains/iptables给数据包做上标记,并且这个标记会在穿过网卡的路由过程中保留下来。如果你希望对来自eth0并从eth1发出的数据包做整形,这就很有用了。语法是这样的:
#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数字6是可以任意指定的)
如果你不想去学习所有的tc语法,就可以与iptables结合,仅仅学习按fwmark匹配就行了。?
按TOS字段选择交互和最小延迟的数据流:
# tc filter add dev ppp0 parent 1:0 protocol ip prio 10 u32 match iptos 0x10 0xff flowid 1:4
想匹配大量传输的话,使用“0x08 0xff”。
关于更多的过滤命令,请参照“高级过滤”那一章。

本文于2010年7月27日由郭占峰对其语法表达及错字进行修正 

9.7. IMQ(Intermediate queueing device,中介队列设备)
中介队列设备不是一个队列规定,但它的使用与队列规定是紧密相连的。

就Linux而言,队列规定是附带在网卡上的,所有在这个网卡上排队的数据都排进这个队列规定。所以出现了两个局限:
1. 只能进行出口整形(虽然也存在入口队列规定,但在上面实现分类的队列规定的可能性非常小)。
2. 一个队列规定只能处理一块网卡的流量,无法设置全局的限速。


IMQ就是用来解决上述两个局限的。简单地说,你可以往一个队列规定中放任何东西。被打了特定标记的数据包在netfilter的NF_IP_PRE_ROUTING 和NF_IP_POST_ROUTING两个钩子函数处被拦截,并被送到一个队列规定中,该队列规定附加到一个IMQ设备上。对数据包打标记要用到iptables的一种处理方法。
这样你就可以对刚刚进入网卡的数据包打上标记进行入口整形,或者把网卡们当成一个个的类来看待而进行全局整形设置。你还可以做很多事情,比如:把http流量放到一个队列规定中去、把新的连接请求放到一个队列规定中去
9.7.1. 配置范例
我们首先想到的是进行入口整形,以便让你自己得到高保证的带宽?。就象配置其它网卡一样:
#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 10.0.0.230/32 flowid 1:10

在这个例子中,使用了u32进行分类。其它的分类器应该也能实现。然后,被打上标记的包被送到imq0排队。
#iptables -t mangle -A PREROUTING -i eth0 -jIMQ --todev 0
#ip link set imq0 up

iptables的IMQ处理方法只能用在PREROUTING和POSTROUTING链的mangle表中。语法是:
IMQ [ --todev n ]
n: imq设备的编号
注:ip6tables也提供了这种处理方法。
请注意,如果数据流是事后才匹配到IMQ处理方法上的,数据就不会入队。数据流进入imq的确切位置取决于这个数据流究竟是流进的还是流出的。下面是netfilter(也就是iptables)在内核中预先定义优先级:
enum nf_ip_hook_priorities {
NF_IP_PRI_FIRST = INT_MIN,
NF_IP_PRI_CONNTRACK = -200,
NF_IP_PRI_MANGLE = -150,
NF_IP_PRI_NAT_DST = -100,
NF_IP_PRI_FILTER = 0,
NF_IP_PRI_NAT_SRC = 100,
NF_IP_PRI_LAST = INT_MAX,
};
对于流入的包,imq把自己注册为优先权等于NF_IP_PRI_MANGLE+1,也就是说数据包在经过了PREROUTING链的mangle表之后才进入imq设备。
对于流出的包,imq使用优先权等于NF_IP_PRI_LAST,也就是说不会白白处理本应该被filter表丢弃的数据包。
关于补丁和更多的文档请参阅imq网站。


转自:TC流量控制

tc流量控制实例:

  流量控制器上的以太网卡(eth0) 的IP地址为192.168.1.66,在其上建立一个CBQ队列。假设包的平均大小为1000字节,包间隔发送单元的大小为8字节,可接收冲突的发送最长包数目为20字节。
  假如有三种类型的流量需要控制:
  1) 是发往主机1的,其IP地址为192.168.1.24。其流量带宽控制在8Mbit,优先级为2;
  2) 是发往主机2的,其IP地址为192.168.1.26。其流量带宽控制在1Mbit,优先级为1;
  3) 是发往子网1的,其子网号为192.168.1.0,子网掩码为255.255.255.0。流量带宽控制在1Mbit,优先级为6。
1. 建立队列
  一般情况下,针对一个网卡只需建立一个队列。
  将一个cbq队列绑定到网络物理设备eth0上,其编号为1:0;网络物理设备eth0的实际带宽为10 Mbit,包的平均大小为1000字节;包间隔发送单元的大小为8字节,最小传输包大小为64字节。
  ·tc qdisc add dev eth0 root handle 1: cbq bandwidth 10Mbit avpkt 1000 cell 8 mpu 64
2. 建立分类
  分类建立在队列之上。一般情况下,针对一个队列需建立一个根分类,然后再在其上建立子分类。对于分类,按其分类的编号顺序起作用,编号小的优先;一旦符合某个分类匹配规则,通过该分类发送数据包,则其后的分类不再起作用。
1) 创建根分类1:1;分配带宽为10Mbit,优先级别为8。
·tc class add dev eth0 parent 1:0 classid 1:1 cbq bandwidth 10Mbit rate 10Mbit maxburst 20 allot 1514 prio 8 avpkt 1000 cell 8 weight 1Mbit
  该队列的最大可用带宽为10Mbit,实际分配的带宽为10Mbit,可接收冲突的发送最长包数目为20字节;最大传输单元加MAC头的大小为 1514字节,优先级别为8,包的平均大小为1000字节,包间隔发送单元的大小为8字节,相应于实际带宽的加权速率为1Mbit。
2)创建分类1:2,其父分类为1:1,分配带宽为8Mbit,优先级别为2。
·tc class add dev eth0 parent 1:1 classid 1:2 cbq bandwidth 10Mbit rate 8Mbit maxburst 20 allot 1514 prio 2 avpkt 1000 cell 8 weight 800Kbit split 1:0 bounded
  该队列的最大可用带宽为10Mbit,实际分配的带宽为 8Mbit,可接收冲突的发送最长包数目为20字节;最大传输单元加MAC头的大小为1514字节,优先级别为1,包的平均大小为1000字节,包间隔发 送单元的大小为8字节,相应于实际带宽的加权速率为800Kbit,分类的分离点为1:0,且不可借用未使用带宽。
3)创建分类1:3,其父分类为1:1,分配带宽为1Mbit,优先级别为1。
·tc class add dev eth0 parent 1:1 classid 1:3 cbq bandwidth 10Mbit rate 1Mbit maxburst 20 allot 1514 prio 1 avpkt 1000 cell 8 weight 100Kbit split 1:0
  该队列的最大可用带宽为10Mbit,实际分配的带宽为 1Mbit,可接收冲突的发送最长包数目为20字节;最大传输单元加MAC头的大小为1514字节,优先级别为2,包的平均大小为1000字节,包间隔发 送单元的大小为8字节,相应于实际带宽的加权速率为100Kbit,分类的分离点为1:0。
4)创建分类1:4,其父分类为1:1,分配带宽为1Mbit,优先级别为6。
·tc class add dev eth0 parent 1:1 classid 1:4 cbq bandwidth 10Mbit rate 1Mbit maxburst 20 allot 1514 prio 6 avpkt 1000 cell 8 weight 100Kbit split 1:0
  该队列的最大可用带宽为10Mbit,实际分配的带宽为 1Mbit,可接收冲突的发送最长包数目为20字节;最大传输单元加MAC头的大小为1514字节,优先级别为1,包的平均大小为1000字节,包间隔 发送单元的大小为8字节,相应于实际带宽的加权速率为100Kbit,分类的分离点为1:0。
3. 建立过滤器
过滤器主要服务于分类。一般只需针对根分类提供一个过滤器,然后为每个子分类提供路由映射。
1) 应用路由分类器到cbq队列的根,父分类编号为1:0;过滤协议为ip,优先级别为100,过滤器为基于路由表。
·tc filter add dev eth0 parent 1:0 protocol ip prio 100 route
2) 建立路由映射分类1:2, 1:3, 1:4
·tc filter add dev eth0 parent 1:0 protocol ip prio 100 route to 2 flowid 1:2
·tc filter add dev eth0 parent 1:0 protocol ip prio 100 route to 3 flowid 1:3
·tc filter add dev eth0 parent 1:0 protocol ip prio 100 route to 4 flowid 1:4
4.建立路由
该路由是与前面所建立的路由映射一一对应。
1) 发往主机192.168.1.24的数据包通过分类2转发(分类2的速率8Mbit)
·ip route add 192.168.1.24 dev eth0 via 192.168.1.66 realm 2
2) 发往主机192.168.1.30的数据包通过分类3转发(分类3的速率1Mbit)
·ip route add 192.168.1.30 dev eth0 via 192.168.1.66 realm 3
3)发往子网192.168.1.0/24的数据包通过分类4转发(分类4的速率1Mbit)
·ip route add 192.168.1.0/24 dev eth0 via 192.168.1.66 realm 4
  注:一般对于流量控制器所直接连接的网段建议使用IP主机地址流量控制限制,不要使用子网流量控制限制。如一定需要对直连子网使用子网流量控制限制,则在建立该子网的路由映射前,需将原先由系统建立的路由删除,才可完成相应步骤。
5. 监视
  主要包括对现有队列、分类、过滤器和路由的状况进行监视。
1)显示队列的状况
简单显示指定设备(这里为eth0)的队列状况
·tc qdisc ls dev eth0
qdisc cbq 1: rate 10Mbit (bounded,isolated) prio no-transmit
详细显示指定设备(这里为eth0)的队列状况
·tc -s qdisc ls dev eth0
qdisc cbq 1: rate 10Mbit (bounded,isolated) prio no-transmit
Sent 7646731 bytes 13232 pkts (dropped 0, overlimits 0)
borrowed 0 overactions 0 avgidle 31 undertime 0
  这里主要显示了通过该队列发送了13232个数据包,数据流量为7646731个字节,丢弃的包数目为0,超过速率限制的包数目为0。
2)显示分类的状况
简单显示指定设备(这里为eth0)的分类状况
·tc class ls dev eth0
class cbq 1: root rate 10Mbit (bounded,isolated) prio no-transmit
class cbq 1:1 parent 1: rate 10Mbit prio no-transmit #no-transmit表示优先级为8
class cbq 1:2 parent 1:1 rate 8Mbit prio 2
class cbq 1:3 parent 1:1 rate 1Mbit prio 1
class cbq 1:4 parent 1:1 rate 1Mbit prio 6
详细显示指定设备(这里为eth0)的分类状况
·tc -s class ls dev eth0
class cbq 1: root rate 10Mbit (bounded,isolated) prio no-transmit
Sent 17725304 bytes 32088 pkts (dropped 0, overlimits 0)
borrowed 0 overactions 0 avgidle 31 undertime 0
class cbq 1:1 parent 1: rate 10Mbit prio no-transmit
Sent 16627774 bytes 28884 pkts (dropped 0, overlimits 0)
borrowed 16163 overactions 0 avgidle 587 undertime 0
class cbq 1:2 parent 1:1 rate 8Mbit prio 2
Sent 628829 bytes 3130 pkts (dropped 0, overlimits 0)
borrowed 0 overactions 0 avgidle 4137 undertime 0
class cbq 1:3 parent 1:1 rate 1Mbit prio 1
Sent 0 bytes 0 pkts (dropped 0, overlimits 0)
borrowed 0 overactions 0 avgidle 159654 undertime 0
class cbq 1:4 parent 1:1 rate 1Mbit prio 6
Sent 5552879 bytes 8076 pkts (dropped 0, overlimits 0)
borrowed 3797 overactions 0 avgidle 159557 undertime 0
  这里主要显示了通过不同分类发送的数据包,数据流量,丢弃的包数目,超过速率限制的包数目等等。其中根分类(class cbq 1:0)的状况应与队列的状况类似。
  例如,分类class cbq 1:4发送了8076个数据包,数据流量为5552879个字节,丢弃的包数目为0,超过速率限制的包数目为0。
显示过滤器的状况
·tc -s filter ls dev eth0
filter parent 1: protocol ip pref 100 route
filter parent 1: protocol ip pref 100 route fh 0xffff0002 flowid 1:2 to 2
filter parent 1: protocol ip pref 100 route fh 0xffff0003 flowid 1:3 to 3
filter parent 1: protocol ip pref 100 route fh 0xffff0004 flowid 1:4 to 4
这里flowid 1:2代表分类class cbq 1:2,to 2代表通过路由2发送。
显示现有路由的状况
·ip route
192.168.1.66 dev eth0 scope link
192.168.1.24 via 192.168.1.66 dev eth0 realm 2
202.102.24.216 dev ppp0 proto kernel scope link src 202.102.76.5
192.168.1.30 via 192.168.1.66 dev eth0 realm 3
192.168.1.0/24 via 192.168.1.66 dev eth0 realm 4
192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.66
172.16.1.0/24 via 192.168.1.66 dev eth0 scope link
127.0.0.0/8 dev lo scope link
default via 202.102.24.216 dev ppp0
default via 192.168.1.254 dev eth0
  如上所示,结尾包含有realm的显示行是起作用的路由过滤器。
6. 维护
  主要包括对队列、分类、过滤器和路由的增添、修改和删除。
  增添动作一般依照"队列->;分类->;过滤器->;路由"的顺序进行;修改动作则没有什么要求;删除则依照"路由->;过滤器->;分类->;队列"的顺序进行。
1)队列的维护
一般对于一台流量控制器来说,出厂时针对每个以太网卡均已配置好一个队列了,通常情况下对队列无需进行增添、修改和删除动作了。
2)分类的维护
增添
增添动作通过tc class add命令实现,如前面所示。
修改
修改动作通过tc class change命令实现,如下所示:
·tc class change dev eth0 parent 1:1 classid 1:2 cbq bandwidth 10Mbit rate 7Mbit maxburst 20 allot 1514 prio 2 avpkt 1000 cell 8 weight 700Kbit split 1:0 bounded
对于bounded命令应慎用,一旦添加后就进行修改,只可通过删除后再添加来实现。
删除
删除动作只在该分类没有工作前才可进行,一旦通过该分类发送过数据,则无法删除它了。因此,需要通过shell文件方式来修改,通过重新启动来完成删除动作。
3)过滤器的维护
增添
增添动作通过tc filter add命令实现,如前面所示。
修改
修改动作通过tc filter change命令实现,如下所示:
·tc filter change dev eth0 parent 1:0 protocol ip prio 100 route to 10 flowid 1:8
删除
删除动作通过tc filter del命令实现,如下所示:
·tc filter del dev eth0 parent 1:0 protocol ip prio 100 route to 10
4)与过滤器一一映射路由的维护
增添
增添动作通过ip route add命令实现,如前面所示。
修改
修改动作通过ip route change命令实现,如下所示:
·ip route change 192.168.1.30 dev eth0 via 192.168.1.66 realm 8
删除
删除动作通过ip route del命令实现,如下所示:
·ip route del 192.168.1.30 dev eth0 via 192.168.1.66 realm 8
·ip route del 192.168.1.0/24 dev eth0 via 192.168.1.66 realm 4


0 0