文章详情

  • 游戏榜单
  • 软件榜单
关闭导航
热搜榜
热门下载
热门标签
php爱好者> php文档>Linux的高级路由和流量控制HOWTO 重新整理版

Linux的高级路由和流量控制HOWTO 重新整理版

时间:2009-07-07  来源:刘嵩_

[转载]Linux流量控制

声明:本文章篇很长.这里发的是经整理过的!   原文叫<<Linux的高级路由和流量控制HOWTO>>  部分内容并没有写入. 这里可以看到原文:http://www.chinaunix.net/topics/salon/9/2005-07-01/8.shtml   

Linux流量控制

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!如果桶里存在令牌而且允许没有令牌,相当于不限制速率(缺省情况).If the
bucket contains tokens and is allowed to empty, by default it does so at infinite speed.
如果不希望这样,可以调整入下参数:
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章 所描述的的一些队列
规定:
1.如果想单纯地降低出口速率,使用令牌桶过滤器.调整桶的配置后可用于控制很高的带宽.如果你的链路  已经塞满了,而你想保证不会有某一个会话独占出口带宽,使用随机公平队列.
2.如果你有一个很大的骨干带宽,并且了解了相关技术后,可以考虑前向随机丢包(参见"高级"那一章).
3.如果希望对入口流量进行"整形"(不是转发流量),可使用入口流量策略,注意,这不是真正的"整形".
4.如果你正在转发数据包,在数据流出的网卡上应用TBF.除非你希望让数据包从多个网卡流出,也就是说入                            口网卡起决定性作用的时候,还是使用入口策略.
5.如果你并不希望进行流量整形,只是想看看你的网卡是否有比较高的负载而需要使用队列,使用pfifo队列(不是pfifo_fast).它缺乏内部频道但是可以统计backlog.
6.最后,你可以进行所谓的"社交整形".你不能通过技术手段解决一切问题.用户的经验技巧永远是不友善的.正确而友好的措辞可能帮助你的正确地分配带宽!

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

一个分类的队列规定可以拥有很多类,类内包含队列规定.
分类器
每个分类的队列规定都需要决定什么样的包使用什么类进行发送.分类器就是做这个用的.
过滤器
分类是通过过滤器完成的.一个过滤器包含若干的匹配条件,如果符合匹配条件,就按此过滤器分类.
调度
在分类器的帮助下,一个队列规定可以裁定某些数据包可以排在其他数据包之前发送.这种处理叫做"调度",比如此前提到的pfifo_fast就是这样的.调度也可以叫做"重排序",但这样容易混乱.
整形
在一个数据包发送之前进行适当的延迟,以免超过事先规定好的最大速率,这种处理叫做"整形".整形在egress处进行.习惯上,通过丢包来降速也经常被称为整形.
策略
通过延迟或是丢弃数据包来保证流量不超过事先规定的带宽。在Linux,里,策略总是规定丢弃数据包而不是延迟。即,不存在ingress队列。
Work-Conserving
对于一个work-conserving队列规定,如果得到一个数据包,它总是立刻对它进行分发。换句话说,只要网卡(egress队列规定)允许,它就不会延迟数据包的发送。
non-Work-Conserving  
有些队列——比如令牌桶过滤器——可能需要暂时停止发包以实现限制带宽。也就是说它们有时候即使有数据包需要处理,也可能拒绝发送。 现在我们简单了解了一些术语,让我们看看他们的位置:
用户级程序
^
|
+---------------+----------------------------------------------+
|               Y                                          |
|    -------> IP 协议栈                                    |
|   |              |                                       |
|   |              Y                                       |
|   |              Y                                       |
|   ^              |                                       |
|   |  / ----------> 转发-------->                          |
|   ^ /                           |                        |
|   |/                            Y                        |
|   |                             |                        |
|      ^                            Y    /-队列规定1-\    |
|   |                   Egress         /----队列规定2-- \    |
------>->Ingress                    分类器 --队列规定3----|-- ->
|  队列规定                             \_-队列规定4__/  |
|                                         \--队列规定N_/   |
|                                                          |
+----------------------------------------------------------------+
最左面的箭头表示从网络上进入机器的数据包.它们进入Ingress队列规定,并有可能被某些过滤器丢弃.即所谓策略.这些是很早就发生的(在进入内核更深的部分之前).这样早地丢弃数据有利于节省CPU时间.数据包顺利通过的话,如果它是发往本地进程的,就会进入IP协议栈处理并提交给该进程.如果它需要转发而不是进入本地进程,就会发往egress.本地进程也可以发送数据,交给Egress分类器.然后经过审查,并放入若干队列规定中的一个进行排队.这个过程叫做"入队".在不进行任何配置的情况下,只有一个egress队列规定——pfifo_fast——总是接收数据包.数据包进入队列后,就等待内核处理并通过某网卡发送.这个过程叫做"出队".这张图仅仅表示了机器上只有一块网卡的情况,图中的箭头不能代表所有情况.每块网卡都有它自己的ingress和egress.

9.5. 分类的队列规定
CBQ(Class Based Queueing,基于类的队列)
一旦数据包进入一个分类的队列规定,它就得被送到某一个类中——也就是需要分类.对数据包进行分类的工具是过滤器.一定要记住:"分类器"是从队列规定内部调用的,而不是从别处.过滤器会返回一个决定,队列规定就根据这个决定把数据包送入相应的类进行排队.每个子类都可以再次使用它们的过滤器进行进一步的分类.直到不需要进一步分类时,数据包才进入该类包含的队列规定排队.除了能够包含其它队列规定之外,绝大多数分类的队列规定都能够流量整形.这对于需要同时进行调度(如使用SFQ)和流量控制的场合非常有用.如果你用一个高速网卡(比如以太网卡)连接一个低速设备(比如cable modem或者ADSL modem)
时,也可以应用.如果你仅仅使用SFQ,那什么用也没有.因为数据包进,出路由器时没有任何延迟.虽然你的输出网卡远远快于实际连接速率,但路由器中却没有队列可以调度.

9.5.2. 队列规定家族:根,句柄,兄弟和父辈
每块网卡都有一个出口"根队列规定",缺省情况下是前面提到的pfifo_fast队列规定.每个队列规定都指定一个句柄,以便以后的配置语句能够引用这个队列规定.除了出口队列规定之外,每块网卡还有一个入口,以便policies进入的数据流.队列规定的句柄有两个部分:一个主号码和一个次号码.习惯上把根队列规定称
为"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:,which each query their siblings,然后试图从它们中进行dequeue()操作.也就是说,内和需要遍历整颗树,因为只有12:2中才有这个数据包.换句话说,类及其兄弟仅仅与其"父队列规定"进行交谈,而不会与网卡进行交谈.只有根队列规定才能由内核进行出队操作!更进一步,任何类的出队操作都不会比它们的父类更快.这恰恰是你所需要的:我们可以把SFQ作为一个子类,放到一个可以进行流量整形的父类中,从而能够同时得到SFQ的调度功能和其父类的流量整形功能.

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

9.5.3.1. PRIO的参数与使用
tc识别下列参数:
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 tc [email protected]:./
[email protected]'s 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 roundrobin,加权轮转)过程,从优先权值小的类开始.
那些队列中有数据的类就会被分组并被请求出队.在一个类收到允许若干字节数据出队的请求之后,再尝试下一个相同优先权值的类.下面是控制WRR过程的一些参数:
allot
当从外部请求一个CBQ发包的时候,它就会按照"priority"参数指定的顺序轮流尝试其内部的每一个类的队列规定.当轮到一个类发数据时,它只能发送一定量的数据."allot"参数就是这个量的基值.更多细节请参
照"weight"参数.
prio
CBQ可以象PRIO设备那样工作.其中"prio"值较低的类只要有数据就必须先服务,其他类要延后处理.
weight
"weight"参数控制WRR过程.每个类都轮流取得发包的机会.如果其中一个类要求的带宽显著地高于其他的类,就应该让它每次比其他的类发送更多的数据.
CBQ会把一个类下面所有的weight值加起来后归一化,所以数值可以任意定,只要保持比例合适就可以.人们常把"速率/10"作为参数的值来使用,实际工作得很好.归一化值后的值乘以"allot"参数后,决定了每
次传输多少数据.请注意,在一个CBQ内部所有的类都必须使用一致的主号码!

9.5.4.3. 决定链路的共享和借用的CBQ参数
除了纯粹地对某种数据流进行限速之外,CBQ还可以指定哪些类可以向其它哪些类借用或者出借一部分带宽.
Isolated/sharing
凡是使用"isolated"选项配置的类,就不会向其兄弟类出借带宽.如果你的链路上同时存在着竞争对手或者不友好的其它人,你就可以使用这个选项.选项"sharing"是"isolated"的反义选项.
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
如前所述,一个分类的队列规定需要调用过滤器来决定一个数据包应该发往哪个类去排队.除了调用过滤器,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 1000 split 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 1000 split 1:0 defmap 3f
53
"split队列规定"是1:0,也就是做出选择的地方.c0是二进制的11000000,3F是00111111,所以它们共同匹配所有的数据包.第一个类匹配第7和第6位,也就是负责"交互"和"控制"的数据包.第二个类匹配其余的数据包.节点1:0现在应该有了这样一个表格:
priority send to
0 1:3
1 1:3
2 1:3
3 1:3
4 1:3
5 1:3
6 1:2
7 1:2
为了更有趣,你还可以传递一个"change掩码",确切地指出你想改变哪个优先权值.你只有在使用了"tc class change"的时候才需要.比如,往1:2中添加besteffort数据流,应该执行:
# tc class change dev eth1 classid 1:2 cbq defmap 01/01现在,1:0上的优先权分布应该是:
priority send to
0 1:2
1 1:3
2 1:3
3 1:3
4 1:3
5 1:3
6 1:2
7 1:2
求助: 尚未测试过"tc class change",资料上这么写的.

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的例子一样.
# 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
作者建议2在那些类的下方放置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的时候应该把所
有的规则放到根上!再次强调:数据包只能向"下"进行入队操作!只有处队的时候才会上到网卡所在的位置来.他们不会落到树的最底层后送到网卡!

9.6.1. 过滤器的一些简单范例
就象在"分类器"那章所解释的,借助一些复杂的语法你可以详细地匹配任何事情.下面我们就开始,从简单地匹配一些比较明显的特征开始.比方说,我们有一个PRIO队列规定,叫做"10:",包含3个类,我们希望把去
往22口的数据流发送到最优先的频道中去.应该这样设置过滤器:
# tc filter add dev eth0 protocol ip parent 10: prio 1 u32 \
match 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 u32 match 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是1:'match ip protocol 1 0xff'.
根据fwmark
你可以使用ipchains/iptables给数据包做上标记,并且这个标记会在穿过网卡的路由过程中保留下来.如果你希望对来自eth0并从eth1发出的数据包做整形,这就很有用了.语法是这样的:
tc filter add dev eth1 protocol ip parent 1:0 prio 1 handle 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 \
m
atch ip tos 0x10 0xff flowid 1:4
想匹配大量传输的话,使用"0x08 0xff".关于更多的过滤命令,请参照"高级过滤"那一章.

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 -j IMQ --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网站.
第10章 多网卡的负载均衡
有多种手段实现这个功能.最简单,最直接的方法之一就是"TEQL"——真(或"普通的")链路均衡.就象用队列实现的大多数事情一样,负载均衡也需要双向实现.链路的两端都要参与,才有完整的效果.想象下列情况:
+-------+ eth1 +-------+
| |==========| |
"网络1" ------| A | | B |---- '网络 2'
| |==========| |
+-------+ eth2 +-------+
A和B是路由器,我们当然假定它们全是Linux机器.如果从网络1发往网络2的流量需要A路由器同时使用两条链路发给B路由器.B路由器需要进行配置以便适应这种情况.反向传输时也一样,当数据包从网络2发往网络1时,B路由器同时使用eth1和eth2.分配的功能是用"TEQL"设备实现的,象这样(没有比这更简单的了):
# tc qdisc add dev eth1 root teql0
# tc qdisc add dev eth2 root teql0
# ip link set dev teql0 up
别忘了"ip link set up"命令!
这在两台机器上都要做.teql0设备基本上是在eth1和eth2之间进行轮转发帧.用源也不会有数据从teql设备上进来,只是出现在原来的eth1和eth2上.我们现在有了网络设备,还需要有合适的路由.方法之一就是给两个链路分配一个/31的网络,teql0也一样:在A路由器上:
# ip addr add dev eth1 10.0.0.0/31
# ip addr add dev eth2 10.0.0.2/31
# ip addr add dev teql0 10.0.0.4/31在B路由器上:
# ip addr add dev eth1 10.0.0.1/31
# ip addr add dev eth2 10.0.0.3/31
# ip addr add dev teql0 10.0.0.5/31
A路由器现在应该能够通过2个真实链路和一个均衡网卡ping通10.0.0.1,
10.0.0.3和10.0.0.5.B路由器应该能够ping通10.0.0.0,10.0.0.2和10.0.0.4.如果成功的话,A路由器应该把10.0.0.5作为到达网络2的路由,B路由器应该把10.0.0.4作为去往网络1的路由.在网络1是你家里的网络,而网络2是Internet这种特定场合下,A路由器的缺省网关应该设为10.0.0.5.
/proc/sys/net/ipv4/conf/eth2/rp_filter
包的乱序也是一个大问题.比如,有6个数据包需要从A发到B,eth1可能分到第1,3,5个包,而eth2分到第2,4,6个.在理想情况下,B路由器会按顺序收到第1,2,3,4,5,6号包.但实际上B路由器的内核很可能按照类似2,1,4,3,6,5这样的随机顺序收到包.这个问题会把TCP/IP搞糊涂.虽然在链路上承载不同的TCP/IP会话并没有问题,但你无法通过捆绑多个链路来增加一个ftp文件的下载速度,除非两端的操作系统都是Linux,因为Linux的TCP/IP协议栈不那么容易被这种简单的乱序问题所蒙蔽.当然,对于大多数应用系统来说,链路的负载均衡是一个好主意.

10.2. 其它可能性
William Stearns已经利用高级隧道来达到捆绑多重Internet连接的效果.可以在他的隧道网页找到.
本HOWTO将来可能更多地描述这个问题.
/etc/iproute2/rt_tables
# ip rule add fwmark 1 table mail.out
# ip rule ls
0:from all lookup local
32764:
from all fwmark 1 lookup mail.out
32766:
from all lookup main
32767:
from all lookup default
现在我们建立一个通往那条便宜链路的路由,从而生成mail.out路由表:
# /sbin/ip route add default via 195.96.98.253 dev ppp0 table mail.out
这就做完了.我们可能需要一些例外,有很多方法都能达到目的.我们可以修改netfilter命令来排除一些主机,也可以插入一些优先权值更低的规则把需要排除的主机的数据包发往main路由表.我们还可以通过识别数据包的TOS位,来给不同服务类型的数据包打上不同的标记,再为它们分别建立规则.你甚至可以利用这种方法让诸如ISDN线路支持交互业务.
不用说,这当然也可以用于正在进行NAT("伪装")的机器上.重要提醒:我们收到报告说MASQ和SNAT功能与数据包标记有冲突.RustyRussell在这个帖子中作了解释.关闭反方向的过滤就可以正常工作.注意:想给数据包打标记的话,你的内和需要一些配置:
IP: advanced router (CONFIG_IP_ADVANCED_ROUTER) [Y/n/ ]
IP: policy routing (CONFIG_IP_MULTIPLE_TABLES) [Y/n/ ]
IP: use netfilter MARK value as routing key (CONFIG_IP_ROUTE_FWMARK) [Y/n/ ]
参考方便菜谱一章中的15.5.

第12章 对包进行分类的高级过滤器
就象在分类的队列规定一段中解释的,过滤器用与把数据包分类并放入相应的子队列.这些过滤器在分类的队列规定内部被调用.下面就是我们可用的分类器(部分):
fw
根据防火墙如何对这个数据包做标记进行判断.如果你不想学习tc的过
滤器语法,这倒是一个捷径.细节请参见队列那一章.
u32
根据数据包中的各个字段进行判断,如源IP地址等等.
route
根据数据包将被哪条路由进行路由来判断.
rsvp, rsvp6
根据数据包的RSVP情况进行判断.只能用于你自己的网络,互联网并不
遵守RSVP.
tcindex
用于DSMARK队列规定,参见相关章节.
通常来说,总有很多途径可实现对数据包分类,最终取决于你喜欢使用哪种系统.
分类器一般都能接受几个参数,为了方便我们列出来:
protocol
这个分类器所接受的协议.一般来说你只会接受IP数据.必要参数.
parent
这个分类器附带在哪个句柄上.句柄必须是一个已经存在的类.必要参数.
prio
这个分类器的优先权值.优先权值低的优先.
handle
对于不同过滤器,它的意义不同.
后面所有的节都假定你试图对去往HostA的流量进行整形.并且假定根类配置为
1:,并且你希望把选中的数据包送给1:1类.
64

12.1. u32分类器
U32分类器是当前实现中最先进的过滤器.全部基于哈希表实现,所以当有很多
过滤器的时候仍然能够保持健壮.U32过滤器最简单的形式就是一系列记录,每条记录包含两个部分:一个选择器和一个动作.下面要讲的选择器用来与IP包相匹配,一旦成功匹配就执行其指定的动作.最简单的动作就是把数据包发送到特定的类队列.用来配置过滤器的tc命令行由三部分组成:过滤器说明,选择器和动作.一个过滤器可以如下定义:
tc filter add dev IF [ protocol PROTO ]
[ (preference|priority) PRIO ]
[ parent CBQ ]
上面行中,protocol字段描述了过滤器要匹配的协议.我们将只讨论IP协议的情况.preference字段(也可以用priority代替)设置该过滤器的优先权.这非常重要,因为你可能有几条拥有不同优先权的过滤器.每个过滤器列表都按照输入的顺序被扫描一遍,然后优先权值低(更高的偏好值)的列表优先被处理."parent"字段定义了过滤器所属的CBQ的顶部(如1:0).上面描述的选项适用于所有过滤器,而不仅仅适用于U32.

12.1.1. U32选择器
u32选择器包含了能够对当前通过的数据包进行匹配的特征定义.它其实只是定义了IP包头中某些位的匹配而已,但这种看似简单的方法却非常有效.让我们看看这个从实际应用的系统中抄来的例子:
# tc filter add dev eth0 protocol ip parent 1:0 pref 10 u32 \
match u32 00100000 00ff0000 at 0 flowid 1:10
现在,命令的第一行已经不用解释了,前面都说过了.我们把精力集中在用"match"选项描述选择器的第二行.这个选择器将匹配那些IP头部的第二个字节是0x10的数据包.你应该猜到了,00ff就是匹配掩码,确切地告诉过滤器应该匹配哪些位.在这个例子中是0xff,所以会精确地匹配这个字节是否等于0x10.
"at"关键字的意思是指出从数据包的第几个字节开始匹配——本例中是从数据包的开头开始.完全地翻译成人类语言就是:"匹配那些TOS字段带有'最小延迟'属性的数据包".让我们看看另一个例子:
# tc filter add dev eth0 protocol ip parent 1:0 pref 10 u32 \
match u32 00000016 0000ffff at nexthdr+0 flowid 1:10
"nexthdr"选项意味着封装在IP包中的下一个PDU的头部,也就是说它的上层协议的头.匹配操作就是从这个协议的头部开始的,应该发生在头部开始的第16位处.在TCP和UDP协议的头部,这个部分存放的是这个报文的目标端口.数字是按照先高厚低的格式存储的,所以0x0016就是十进制的22(如果是TCP
的话就是ssh服务).其实,这个匹配在没有上下文的情况下含义很模糊,我们放在后面讨论.理解了上面的例子之后,下面这条选择器就很好懂了:  match c0a80100 ffffff00 at 16
表示了:匹配从IP头开始数的第17个字节到第19个字节.这个选择器将匹配所有去往192.168.1.0/24的数据包.成功分析完上面这个例子后,我们就已经掌握u32选择器了.

12.1.2. 普通选择器
普通选择器定义了要对数据包进行匹配的特征,掩码和偏移量.使用普通选择器,你实际上可以匹配IP(或者上层协议)头部的任意一个bit,虽然这样的选择器比特殊选择器难读和难写.一般选择器的语法是:
match [ u32 | u16 | u8 ] PATTERN MASK [ at OFFSET | nexthdr+OFFSET]利用u32,u16或u8三个关键字中的一个来指明特征的bit数.然后PATTERN和MASK应该按照它定义的长度紧挨着写.OFFSET参数是开始进行比较的偏移量(以字节计).如果给出了"nexthdr+"关键字,偏移量就移到上层协议头部开始的位置.一些例子:
# tc filter add dev ppp14 parent 1:0 prio 10 u32 \
match u8 64 0xff at 8 \
flowid 1:4
如果一个数据包的TTL值等于64,就将匹配这个选择器.TTL就位于IP包头的第9个字节.匹配带有ACK位的TCP数据包:
# tc filter add dev ppp14 parent 1:0 prio 10 u32 \
match ip protocol 6 0xff \
match u8 0x10 0xff at nexthdr+13 \
flowid 1:3
用这个匹配小于64字节的ACK包:
## match acks the hard way,
## IP protocol 6,
## IP header length 0x5(32 bit words),
## IP Total length 0x34 (ACK + 12 bytes of TCP options)
## TCP ack set (bit 5, offset 33)
# tc filter add dev ppp14 parent 1:0 protocol ip prio 10 u32 \
match ip protocol 6 0xff \
match u8 0x05 0x0f at 0 \
match u16 0x0000 0xffc0 at 2 \
match u8 0x10 0xff at 33 \
flowid 1:3
这个规则匹配了带有ACK位,且没有载荷的TCP数据包.这里我们看见了同时使用两个选择器的例子,这样用的结果是两个条件进行逻辑"与"运算.如果我们查查TCP头的结构,就会知道ACK标志位于第14个字节的第5个bit(0x10).作为第二个选择器,如果我们采用更复杂的表达,可以写成"match u8 0x06 0xffat 9",而不是使用特殊选择器protocol,因为TCP的协议号是6(写在IP头的第十个字节).另一方面,在这个例子中我们不使用特殊选择器也是因为没有用来匹配TCP的ACK标志的特殊选择器.下面这个选择器是上面选择器的改进版,区别在于它不检查IP头部的长度.为什么呢?因为上面的过滤器只能在32位系统上工作.
tc filter add dev ppp14 parent 1:0 protocol ip prio 10 u32 \
match ip protocol 6 0xff \
match u8 0x10 0xff at nexthdr+13 \
match u16 0x0000 0xffc0 at 2 \
flowid 1:3

12.1.3. 特殊选择器
下面的表收入了本节文档的作者从tc程序的源代码中找出的所有特殊选择器.它们能够让你更容易,更可靠地配置过滤器.
求助: table placeholder - the table is in separate file ,,selector.html''
求助: it's also still in Polish :-(
求助: must be sgml'ized
一些范例:
# tc filter add dev ppp0 parent 1:0 prio 10 u32 \
match ip tos 0x10 0xff \
flowid 1:4
求助: tcp dport match does not work as described below:
上述规则匹配那些TOS字段等于0x10的数据包.TOS字段位于数据包的第二个字节,所以与值等价的普通选择器就是:"match u8 0x10 0xff at 1".这其实给了我们一个关于U32过滤器的启示:特殊选择器全都可以翻译成等价的普通选择器,而且在内核的内存中,恰恰就是按这种方式存储的.这也可以导出另一个结
论:tcp和udp的选择器实际上是完全一样的,这也就是为什么不能仅用"matchtcp dport 53 0xffff"一个选择器去匹配发到指定端口的TCP包,因为它也会匹配送往指定端口的UDP包.你一定不能忘了还得匹配协议类型,按下述方式来表
示:
# tc filter add dev ppp0 parent 1:0 prio 10 u32 \
match tcp dport 53 0xffff \
match ip protocol 0x6 0xff \
flowid 1:2

12.2. 路由分类器
这个分类器过滤器基于路由表的路由结果.当一个数据包穿越一个类,并到达一个标有"route"的过滤器的时候,它就会按照路由表内的信息进行分裂.当一个数据包遍历类,并到达一个标记"路由"过滤器的时候,就会按照路由表的相应信息分类.
# tc filter add dev eth1 parent 1:0 protocol ip prio 100 route
我们向节点1:0里添加了一个优先级是100的路由分类器.当数据包到达这个节点时,就会查询路由表,如果匹配就会被发送到给定的类,并赋予优先级100.要最后完成,你还要添加一条适当的路由项:这里的窍门就是基于目的或者源地址来定义"realm".象这样做:
# ip route add Host/Network via Gateway dev Device realm RealmNumber
例如,我们可以把目标网络192.168.10.0定义为realm 10:
# ip route add 192.168.10.0/24 via 192.168.10.1 dev eth1 realm 10
我们再使用路由过滤器的时候,就可以用realm号码来表示网络或者主机了,并可以用来描述路由如何匹配过滤器:
# tc filter add dev eth1 parent 1:0 protocol ip prio 100 \
route to 10 classid 1:10
这个规则说:凡是去往192.168.10.0子网的数据包匹配到类1:10.路由过滤器也可以用来匹配源策略路由.比如,一个Linux路由器的eth2上连接了一个子网:
# ip route add 192.168.2.0/24 dev eth2 realm 2
# tc filter add dev eth1 parent 1:0 protocol ip prio 100 \
route from 2 classid 1:2
这个规则说:凡是来自192.168.2.0子网(realm 2)的数据包,匹配到1:2.

12.3. 管制分类器
为了能够实现更复杂的配置,你可以通过一些过滤器来匹配那些达到特定带宽的数据包.你可以声明一个过滤器来来按一定比率抑制传输速率,或者仅仅不匹配那些超过特定速率的数据包.如果现在的流量是5M,而你想把它管制在4M,那么你要么可以停止匹配整个的5M带宽,要么停止匹配1M带宽,来给所配置的类进行4M速率的传输.如果带宽超过了配置的速率,你可以丢包,可以重新分类或者看看是否别的过滤器能匹配它.

12.3.1. 管制的方式
有两种方法进行管制.如果你编译内核的时候加上了"Estimators",内核就可以替你为每一个过滤器测
量通过了多少数据,多了还是少了.这些评估对于CPU来讲非常轻松,它只不过是每秒钟累计25次通过了多少数据,计算出速率.另一种方法是在你的过滤器内部,通过TBF(令牌桶过滤器)来实现.TBF只匹配
到达到你配置带宽的数据流,超过的部分则按照事先指定的"越限动作"来处理.
12.3.1.1. 靠内核评估
这种方式非常简单,只有一个参数"avrate".所有低于avrate的数据包被保留,并被过滤器分到所指定的类中去,而那些超过了avrate的数据包则按照越限动作来处理,缺省的越限动作是"reclassify"(重分类).内核使用EWMA算法来核算带宽,以防止对瞬时突发过于敏感.
12.3.1.2. 靠令牌桶过滤器
使用下列参数:
buffer/maxburst
mtu/minburst
mpu
rate
它们的意义与前面介绍TBF时所说的完全一样.但仍然要指出的是:如果把一个TBF管制器的mtu参数设置过小的话,将没有数据包通过,whereas the egressTBF qdisc will just pass them slower.另一个区别是,管制器只能够通过或者丢弃一个数据包,而不能因为为了延迟而暂停发送.
12.3.2. 越限动作
如果你的过滤器认定一个数据包越限,就会按照"越限动作"来处理它.当前,支持三种动作:
continue
让这个过滤器不要匹配这个包,但是其它的过滤器可能会匹配它.
drop
这是个非常强硬的选项——让越限的数据包消失.它的用途不多,经常被用于ingress管制.比如,你的DNS在请求流量大于5Mbps的时候就会失灵,你就可以利用这个来保证请求量不会超标.
Pass/OK
让数据包通过.可用于避免复杂的过滤器,但要放在合适的地方.
reclassify
最经常用于对数据包进行重分类以达到最好效果.这是缺省动作.
12.3.3. 范例
现在最真实的范例就是下面第十五章提到的"防护SYN洪水攻
".求助: if you have used this, please share your experience with us
12.4. 当过滤器很多时如何使用散列表
如果你需要使用上千个规则——比如你有很多需要不同QoS的客户机——你可能会发现内核花了很多时间用于匹配那些规则.缺省情况下,所有的过滤器都是靠一个链表来组织的,链表按priority的降序排
列.如果你有1000个规则,那么就有可能需要1000次匹配来决定一个包如何处而如果你有256个链表,每个链表4条规则的话,这个过程可以更快.也就是说如果你能把数据包放到合适的链表上,可能只需要匹配4次就可以了.利用散列表可以实现.比如说你有1024个用户使用一个Cable MODEM,IP地
址范围是1.2.0.0到1.2.3.255,每个IP都需要不同容器来对待,比如"轻量级","中量级"和"重量级".你可能要写1024个规则,象这样:
# tc filter add dev eth1 parent 1:0 protocol ip prio 100 match ip src \
1.2.0.0 classid 1:1
# tc filter add dev eth1 parent 1:0 protocol ip prio 100 match ip src \
1.2.0.1 classid 1:1
# tc filter add dev eth1 parent 1:0 protocol ip prio 100 match ip src \
1.2.3.254 classid 1:3
# tc filter add dev eth1 parent 1:0 protocol ip prio 100 match ip src \
1.2.3.255 classid 1:2
为了提高效率,我们应该利用IP地址的后半部分作为散列因子,建立256个散列表项.第一个表项里的规则应该是这样:
# tc filter add dev eth1 parent 1:0 protocol ip prio 100 match ip src \
1.2.0.0 classid 1:1
# tc filter add dev eth1 parent 1:0 protocol ip prio 100 match ip src \
1.2.1.0 classid 1:1
# tc filter add dev eth1 parent 1:0 protocol ip prio 100 match ip src \
1.2.2.0 classid 1:3
# tc filter add dev eth1 parent 1:0 protocol ip prio 100 match ip src \
1.2.3.0 classid 1:2
下一个表项应该这么开始:
# tc filter add dev eth1 parent 1:0 protocol ip prio 100 match ip src \
1.2.0.1 classid 1:1
...
这样的话,最坏情况下也只需要4次匹配,平均2次.
具体配置有些复杂,但是如果你真有很多规则的话,还是值得的.我们首先生成root过滤器,然后创建一个256项的散列表:
# tc filter add dev eth1 parent 1:0 prio 5 protocol ip u32
# tc filter add dev eth1 parent 1:0 prio 5 handle 2: protocol ip u32 divisor 256
然后我们向表项中添加一些规则:
# tc filter add dev eth1 protocol ip parent 1:0 prio 5 u32 ht 2:7b: \
match ip src 1.2.0.123 flowid 1:1
# tc filter add dev eth1 protocol ip parent 1:0 prio 5 u32 ht 2:7b: \
match ip src 1.2.1.123 flowid 1:2
# tc filter add dev eth1 protocol ip parent 1:0 prio 5 u32 ht 2:7b: \
match ip src 1.2.3.123 flowid 1:3
# tc filter add dev eth1 protocol ip parent 1:0 prio 5 u32 ht 2:7b: \
match ip src 1.2.4.123 flowid 1:2
这是第123项,包含了为1.2.0.123,1.2.1.123,1.2.2.123和1.2.3.123准备的匹配规则,分别把它们发给1:1,1:2,1:3和1:2.注意,我们必须用16进制来表示散列表项,0x7b就是123.然后创建一个"散列过滤器",直接把数据包发给散列表中的合适表项:
# tc filter add dev eth1 protocol ip parent 1:0 prio 5 u32 ht 800:: \
match ip src 1.2.0.0/16 \
hashkey mask 0x000000ff at 12 \
link 2:
好了,有些数字需要解释.我们定义散列表叫做"800:",所有的过滤都从这里开始.然后我们选择源地址(它们位于IP头的第12,13,14,15字节),并声明我们只对它的最后一部分感兴趣.这个例子中,我们发送到了前面创建的第2个散列表项.这比较复杂,然而实际上确实有效而且性能令人惊讶.注意,这个例子我们也可以处理成理想情况——每个表项中只有一个过滤器!
/proc/sys/net/ipv4/conf//log_martians
求助: is setting the conf/[default,all]/* files enough - martijn
13.2. 深层设置
有很多参数可以修改.我们希望能够全列出来.在Documentation/ip-sysctl.txt中也有部分记载.
这些设置中的部分缺省值取决于你在内核配置时是否选择了"Configure as routerand not host".
Oskar Andreasson也有一个网页比我们讨论得更详细的网页:
http://ipsysctl-tutorial.frozentux.net/
第14章 不经常使用的高级队列规定
你应该发现有时候前面提到的那些队列不能满足你的需要,这里列出了一些内核包含的其它更多类型的队列。
14.1. bfifo/pfifo
这些无类的队列因为没有内部频道而显得比pfifo_fast还要简单,所有的流量都均等处理。但是它们还是有一个好处:他们可以做统计。所以即使你不想整形或排序,你也可是使用这个队列规定来检查网卡的后台日志。 pfifo的长度单位以包的个数计,而bfifo以字节计。  
14.1.1. 参数与使用
limit
规定了队列的长度。对于bfifo用字节计,对于pfifo用包的个数计。缺省值就是网卡的txqueuelen个包那么长(参见pfifo_fast那一章),对于bfifo就是txqueuelen*mtu个字节。
14.2. Clark-Shenker-Zhang算法 (CSZ)
它的理论性是如此之强,以至于连Alexey(CBQ的主要作者)都不指望去理解它。从它的原始资料来看:
David D. Clark、Scott Shenker和Lixia Zhang  在综合业务分组网络中支持实时应用系统:体系与机制。 据我的理解,它的主要意图是为每一个担保的服务创建WFQ流,并分配剩下的带宽给伪设备flow-0。Flow-0由服务预测和best effort traffic组成,并由一个优先调度器来处理??(谁能帮忙翻一
下?)。As I understand it, the main idea is to create WFQ flows for each guaranteed service and to allocate the rest of bandwith to dummy flow-0. Flow-0 comprises the predictive services and the best effort traffic; it is handled by a priority scheduler with the highest priority
band allocated for predictive services, and the rest --- to the best effort packets. Note that in CSZ flows are NOT limited to their bandwidth. It issupposed that the flow passed admission control at the edge of the QoS network and it doesn't need further shaping. Any attempt to improve the flow or to shape it to a token bucket at intermediate hops will
introduce undesired delays and raise jitter. 迄今为止,CSZ是唯一一个能够真正提供服务担保的调度器。其它的方案(包括CBQ)不提供延迟保证和随机抖动。" 现在看来不是一个好的候选方案,除非你已经阅读并理解了上面提到的文章。
14.3. DSMARK
Esteve Camps <[email protected]>
这些文字是从我2000年9月的《Linux的QoS》支持这篇论文上摘录的。  
参考文档:
* Draft-almesberger-wajhak-diffserv-linux-01.txt.  
* iproute2发行版中的范例。
* QOS论坛的QoS协议与体系白皮书和IP QoS常见问题。
本章作者:Esteve Camps <[email protected]>.
14.3.1. 介绍
首先的首先,你应该到IETF DiffServ工作组的网站和Werner Almesberger的网站(他为Linux的Differentiated Services撰写了代码)阅读一下有关的RFC(RFC2474、RFC2475、RFC2597和RFC2598).
14.3.2. Dsmark与什么相关?
Dsmark是一个队列规定,提供了Differentiated Services(也叫DiffServ,简称为DS)所需要的能力。DiffServ是两种QoS体系中的一种(另一种叫做Integrated Services),基于IP头中DS字段的值来工作。 最开始的实现方案中的一种是IP头中的服务类型字段(TOS值),也就是设计由IP来提供一些QoS级别。改变这个值,我们可以选择吞吐量、延迟或者可靠性的高/低级别。但是这并没有提供足够的灵活性来应付新的服务(比如说实时应用、交互应用等等)。后来,新的体系出现了。其中之一就是DiffServ,保留了TOS位并renamed DS字段。
14.3.3. Differentiated Services指导
Differentiated Services是面向组的。也就是说,我们没有必要了解数据流(那是Integrated Services的风格),我们只了解“流会聚”,并根据一个数据包属于哪个流会聚来采取不同的行动。 当一个数据包到达边缘节点(entry node to a DiffServ domain),并进入DiffServ 域之后,我们就得进行对它们进行策略控制、整形或标记(关于标记请参考“给DS字段分配一个值”。就象一头牛一样。? )。这个值将被DiffServ域中的内部/核心代码参考,以决定如何处置或者适用什么QoS级别。 如你的推断,DS包括一个“域”的概念,他规定了所有DS规则的作用范围。事实上你可以认为我把所有的数据包都分类到我的域中。他们一旦进入我的域,就会根据分类而隶属于某条规则,每个所经过的节点都会应用那个QoS级别。
其实你可以在你的本地域内使用你自己的策略控制器,但当与其它的DS域相连时要考虑一些服务级别协议。 这里,你可能由很多问题。DiffServ不仅限于我所解释的。其实你应该能理解我不可能在50行中引用3个RFC。
14.3.4. 使用Dsmark
根据DiffServ文档的说法,我们区分边界节点和内部节点。这是传输路径上的两个关键点。但数据包到达时二者都会对包进行分类。在数据包被发送到网络上之前,它的分类结果可能会被这个DS过程的其他地方用到。正是因此,Diffserv的代码提供了一个叫做sk_buff的结构,包含了一个新的字段叫做skb->tc_index
用来存储一开始分类的结果,以便其它可能用到这个结果的地方引用。
skb->tc_index的值被DSMARK队列规定根据每个IP包头的DS字段的值进行初
始设置。此外,cls_tcindex分类器将读取skb->tcindex的全部或部分,并用来选择类。 但是,首先看一看DSMARK队列规定的命令行和参数:
... dsmark indices INDICES [ default_index DEFAULT_INDEX ] [ set_tc_index ]
这些参数是什么意思呢?
* indices:存储(掩码,数值)的表格尺寸。最大值是2^n (n≥0)。
* Default_index: 当分类器没有找到匹配项时的缺省表格项索引。
* Set_tc_index: 给dsmark设置接收DS字段并存储到skb->tc_index规定。
让我们看看DSMARK的工作过程.
14.3.5. SCH_DSMARK如何工作
这个队列规定将进行下列步骤:
* 如果我们在队列规定的命令行中声明了set_tc_index选项,DS字段的值将被取出并存储在skb->tc_index中。
* 调用分类器。分类器被调用并将返回将被存储于skb_tcindex的类编号。如果没有找到能匹配的过滤器,会返回default_index的值。如果既没有声明set_tc_index又没有声明default_index的值,结果可能是不可预知的。
* 在数据包被发送到你可以重用分类器的结果的内部队列之后,由内部队列规定返回的类代码被保存在skb->tc_index。我们将来检索mask- value 表的时候还会用到这个值。随后的结果将决定数据包下一步的处理: New_Ds_field = ( Old_DS_field & mask ) | value
* 然后,值将与ds_field的值和掩码进行“与”运算,然后再与参数值相“或”。下面的图表有利于你理解这个过程:

如何进行标记?只需改变你想重新标记的类的掩码和值就行了。看着一行代码: tc class change dev eth0 classid 1:1 dsmark mask 0x3 value 0xb8 它改变了散列表中的(mask,value)记录,来重新标记属于类1:1的数据包。你必须使用“change”命令,因为(mask,value)记录已经有了缺省值。(see table below). 现在,我们解释一下TC_INDEX过滤器如何工作和适用场合。此外,TC_INDEX过滤器也可以应用于没有DS服务的配置下。
14.3.6. TC_INDEX过滤器
这是声明TC_INDEX 过滤器的基本命令:
... tcindex [ hash SIZE ] [ mask MASK ] [ shift SHIFT ]
            [ pass_on | fall_through ]
            [ classid CLASSID ] [ police POLICE_SPEC ]
接下来,我们说明一下用来解释TC_INDEX操作模式的例子。注意斜体字:
tc qdisc add dev eth0 handle 1:0 root dsmark indices 64 set_tc_index  
tc filter add dev eth0 parent 1:0 protocol ip prio 1 tcindex mask 0xfc shift 2  
tc qdisc add dev eth0 parent 1:0 handle 2:0 cbq bandwidth 10Mbit cell 8 avpkt 1000 mpu 64 # EF
traffic class  
tc class add dev eth0 parent 2:0 classid 2:1 cbq bandwidth 10Mbit rate 1500Kbit avpkt 1000 prio
1 bounded isolated allot 1514 weight 1 maxburst 10 # Packet fifo qdisc for EF traffic  
tc qdisc add dev eth0 parent 2:1 pfifo limit 5 tc filter add dev eth0 parent 2:0 protocol ip
prio 1 handle 0x2e tcindex classid 2:1 pass_on  
(这里的代码尚未完成。只是从iproute2发行中的EFCBQ范例中摘出来的。)。 首先,假设我们收到标记为“EF”的数据包。如果你读了RFC2598,你就会明白DSCP推荐EF数据包的值是101110。也就是说DS字段的值是10111000(记住TOS字节中次要的位没有在DS中使用)或者是16进制的0xb8。
               
数据包到来后就把DS字段设置为0xb8。象前面所解释的,例子中被标记为1:0的dsmark队列规定找到DS字段的值并存储在skb->tc_index变量中。下一步将对应关联到这个队列规定的过滤器(例子中的第二行)。这将进行下列操作:
Value1 = skb->tc_index & MASK
Key = Value1 >> SHIFT
在例子中,MASK=0xFC i SHIFT=2.  
Value1 = 10111000 & 11111100 = 10111000
Key = 10111000 >> 2 = 00101110 -> 0x2E in hexadecimal
返回值将对应一个队列规定的内部过滤器句柄(比如说:2:0)。如果这个编号的过滤器存在的话,将核对管制和测量条件(如果过滤器有这些的话),并返回类编号(我们的例子中是2:1),存储于skb->tc_index变量。 但是如果找到了那个编号的过滤器,结果将取决于是否声明了fall_through标志。如果有,key值将作为类编号返回。如果没有,将返回一个错误并继续处理其它过滤器。要当心的是如果你使用了fall_through标志,并且在skb->tc_index和类编号之间存在着一个简单的关系。Be careful if you use fall_through flag; this can be done if a simple relation exists between values of skb->tc_index variable and class id's. 最后要说的参数是hash和pass_on。前者与散列表的大小有关。后者用于指出如果找不到这个过滤器的返回值指定的类编号,就进行下一个过滤器。缺省行为是is fall_through (参见下表)。 最后,让我们看一看TCINDEX的参数都有那些可能的值:  
TC Name                 Value           Default
-----------------------------------------------------------------
Hash                    1...0x10000     Implementation dependent
Mask                    0...0xffff        0xffff
Shift                     0...15           0
Fall through / Pass_on  Flag             Fall_through
Classid                  Major:minor     None
Police                      .....           None
这种过滤器的功能非常强大,有必要挖掘它所有的可能性。此外,这种过滤器不仅仅适用于Diffserv配置,你可以与其它任意类型的过滤器一起使用。 我建议你看看iproute2中自带的所有的Diffserv范例。我保证我会尽力补全这个文档。此外,我所解释的内容是很多测试的结果。如果你能指出我的错误我将非常感激。
14.4. 入口队列规定
迄今为止所讨论的队列规定都是出口队列规定。每个网卡其实还都可以拥有一个入口队列规定,它不是用来控制向网络上发数据包的,而是用来让你对从网络上收到的数据包应用tc过滤器的,而不管它们是发到本机还是需要转发。 由于tc过滤器包括了完整地实现了令牌桶过滤器,并且也能够匹配内核的流评估,所以能够实现非常多的功能。这些能够有效地帮助你对输入流进行管制,甚至在它尚未进入IP协议层之前。
14.4.1. 参数与使用
输入队列规定本身并不需要任何参数。它不同于其它队列规定之处在于它不占用网卡的根。象这样设置:  
# tc qdisc add dev eth0 ingress
这能够让你除了拥有输入队列规定之外,还有其它的发送队列规定。 关于输入队列的一些人为的例子,请参看“方便菜谱”。
14.5. RED(Random Early Detection,随机提前检测)  
这一节的意图是引入骨干路由,经常包括<100Mbps的速率,这同您家里的ADSL MODEM和CABLE MODEM的实现有所不同。 Internet上路由器里面的队列的普遍行为称作“截尾”。截尾的原理就是排队到一定量之后就开始丢弃后来“溢出”的数据包。这非常的不公平,并且还会引发重发同步问题,也就是说路由器突然地突发性丢包会导致被丢包者后来突发地重发,进一步地造成路由器拥塞。 为了克服链路上的瞬时拥塞,骨干路由器经常做成大队列。不幸的是,这样做提高了吞吐量的同时也造成了延迟的增加,并导致TCP连接在拥挤的时候速率变得非常不均衡。 这些由于截尾带来的问题在Internet上不友好的应用程序越来越多而变得越来越棘手。Linux内核提供了RED(Random Early Detection,随机提前检测),更直观的名字叫Random Early Drop(随机提前丢包)。 RED并不能解决所有问题,那些未能实现指数backoff的应用程序仍然会不公平地共享带宽,但是有了RED至少它们不会对其它连接的吞吐量和延迟造成太大的影响。 RED在数据流没有达到它的带宽硬限制之前就统计地丢弃流中的数据包。让拥塞的骨干链路比较缓和地慢下来,从而防止重传同步问题。因为有意地丢弃了一
些数据包,从而可控地减小队列尺寸和延迟,这有助于让TCP更快地找到它们“公平的”传输速率。特定连接的包被丢弃的可能性取决于它的带宽占用,而不取决于它发包的数量。  RED在骨干网上一种很好的队列,尤其是那些无法提供公平队列的场合,因为基于会话状态的分别跟踪很困难。 为了使用RED,你必须确定三个参数:min、max和burst。Min设置了队列达到多少字节时开始进行丢包,Max是一个软上限,让算法尽量不要超过,burst设置了最多有多少个数据包能够突发通过。 对于min的值,你应该算出可以接受的最高延迟,然后乘以你的带宽。比如,在我的64kbps的ISDN上,我希望基础队列延迟是200ms,所以我设置min的值是1600字节。min的值设置得太小会影响吞吐量,而太大则会影响延迟。在慢速的链路上,把min设小并不能代替减小MTU来改善交互响应。 为了避免重传同步,你至少要把max的值设置为min的两倍。在慢速链路上,由于min的值较小,最好应该设置max的值是min的四倍以上。 Burst控制RED算法如何响应突发。Burst必须设置大于min/avpkt。经验上讲,我发现 (min+min+max)/(3*avpkt)就不错。 另外,你需要设置limit和avpkt。Limit是一个非常安全的值,当队列中有了这么多字节之后,RED就开始截尾。我一般设置limit值为max的八倍。Avpkt应该是你的平均包大小,在MTU是1500字节的Internet链路上,1000就可以。 关于技术信息,请阅读Sally Floyd和Van Jacobson 的关于RED队列的文章。
14.6. GRED(Generic Random Early Detection,一般的随机提前检测)
关于GRED不了解多少。好象GRED有很多内置的队列,根据Diffserv tcindex的值来决定选择哪个内部队列。根据这里找到的一个幻灯片,它包含了CISCO的“分布式加权RED”和Dave Clark的RIO的功能。 每个虚拟队列都能分别设置丢包参数。 求助: get Jamal or Werner to tell us more
14.7. VC/ATM模拟
你可以在TCP/IP套接字上建立虚点路,这是Werner Almesberger的一个只要成果。虚电路是ATM的一个概念。  更多细节请参照ATM on Linux 网页。
14.8. WRR(Weighted Round Robin,加权轮转)
这个队列规定没有包括在标准内核中,弹你可以从这里下载。迄今,这个队列规定仅在Linux 2.2上测试过,但大概也能够在2.4/2.5上工作。 WRR队列规定在使用了WRR的各个类之间分配带宽。也就是说,想CBQ队列规定那样,它能够包含子类并插入任意队列规定。所有满足要求的类都会按照该类权值的比例分得带宽。权值可以利用tc程序手工设置。但对于那些要传输达量数据的类也可以设置为自动递减。 队列规定有一个内置分类器,用来把来自或发往不同机器的数据包分到不同的类。可以使用MAC地址/IP地址和源IP地址/目标IP地址。MAC只有在你的Linux机器作为一个网桥的时候才能使用。根据数据包的情况,自动把类分配到某一台机器上。 这个队列规定对于象宿舍这样许多不相关的个体共享一个Internet连接的时候非常有用。由一套脚本来为WRR中心的机器设置相关的行为。
15.1. 用不同的SLA运行多个网站。
你有很多种方法实现这个。Apache的有些模块可以支持这个功能,但是我们会让你看看Linux如何处理这个问题,并能够提供其它多种服务的。这些命令都是从Jamal Hadi 的演示中偷学来的。 比如说我们有两个顾客,需要http、ftp和音频流服务,我们需要向他们出售一定量的带宽。我们在服务器上这么做。
A顾客应该拥有最多2Mbps的带宽,B顾客则交了5M的钱。我们在服务器上分配不同的IP地址来区分它们。
# ip address add 188.177.166.1 dev eth0
# ip address add 188.177.166.2 dev eth0
给服务器赋予那些IP地址完全取决于你。当前所有的守护程序都支持这个特性。 我们首先往eth0上附加一个CBQ队列规定:
# tc qdisc add dev eth0 root handle 1: cbq bandwidth 10Mbit cell 8 avpkt 1000 \
  mpu 64
为我们的顾客创建类:
# tc class add dev eth0 parent 1:0 classid 1:1 cbq bandwidth 10Mbit rate \
  2MBit avpkt 1000 prio 5 bounded isolated allot 1514 weight 1 maxburst 21
# tc class add dev eth0 parent 1:0 classid 1:2 cbq bandwidth 10Mbit rate \
  5Mbit avpkt 1000 prio 5 bounded isolated allot 1514 weight 1 maxburst 21
然后为我们的客户添加过滤器:
##求助: Why this line, what does it do?, what is a divisor?:
##求助: A divisor has something to do with a hash table, and the number of
##       buckets - ahu
# tc filter add dev eth0 parent 1:0 protocol ip prio 5 handle 1: u32 divisor 1
# tc filter add dev eth0 parent 1:0 prio 5 u32 match ip src 188.177.166.1
  flowid 1:1
# tc filter add dev eth0 parent 1:0 prio 5 u32 match ip src 188.177.166.2
  flowid 1:2   做完了。
求助: why no token bucket filter? is there a default pfifo_fast fallback somewhere?
15.2. 防护SYN洪水攻击
根据Alexey的iproute文档和许多非正式的netfilter方法而设计。如果你使用这个脚本,请认真调整其中的数值以适应你的网络。 如果你需要保护整个网络,就不要考虑这个脚本了,它最适合于单机使用。 乎你需要很新版本的iproute2工具才能利用2.4.0的内核工作。
#! /bin/sh -x
# sample script on using the ingress capabilities
# this script shows how one can rate limit incoming SYNs
# Useful for TCP-SYN attack protection. You can use
# IPchains to have more powerful additions to the SYN (eg  
# in addition the subnet)
#path to various utilities;
#change to reflect yours.
TC=/sbin/tc
IP=/sbin/ip
IPTABLES=/sbin/iptables
INDEV=eth2
# tag all incoming SYN packets through $INDEV as mark value 1
############################################################  
$iptables -A PREROUTING -i $INDEV -t mangle -p tcp --syn \
  -j MARK --set-mark 1
########################################################
# install the ingress qdisc on the ingress interface
########################################################
$TC qdisc add dev $INDEV handle ffff: ingress
######################################################### SYN packets are 40 bytes (320 bits) so three SYNs equals
# 960 bits (approximately 1kbit); so we rate limit below
# the incoming SYNs to 3/sec (not very useful really; but
#serves to show the point - JHS
########################################################
$TC filter add dev $INDEV parent ffff: protocol ip prio 50 handle 1 fw \
police rate 1kbit burst 40 mtu 9k drop flowid :1
########################################################
echo "---- qdisc parameters Ingress  ----------"
$TC qdisc ls dev $INDEV
echo "---- Class parameters Ingress  ----------"
$TC class ls dev $INDEV
echo "---- filter parameters Ingress ----------"
$TC filter ls dev $INDEV parent ffff:
#deleting the ingress qdisc
#$TC qdisc del $INDEV ingress
15.3. 为防止DDoS而对ICMP限速
最近一段,分布式拒绝服务攻击成了Internet上最让人讨厌的东西。通过对你的网络正确地设置过滤和限速可以避免成为攻击的目标和跳板。 你应该过滤你的网络,禁止非本地IP源地址的数据包离开网络,这可以阻止其它人向Internet上发送垃圾包。 限速就象以前所展示的那样。如果忘了,就是这张图:
[Internet] ---<E3、T3之类>--- [Linux路由器] --- [办公室+ISP]
                             eth1        eth0
我们先进行预备设置:
# tc qdisc add dev eth0 root handle 10: cbq bandwidth 10Mbit avpkt 1000
# tc class add dev eth0 parent 10:0 classid 10:1 cbq bandwidth 10Mbit rate \
  10Mbit allot 1514 prio 5 maxburst 20 avpkt 1000
如果你有一个100Mbps的网卡,调整这些数据。现在你需要决定允许多大的ICMP流量。你可以用tcpdump测量段时间,把结果写入一个文件,看一看有多少ICMP数据包流经网络。别忘了适当延长监测时间! 如果没有条件进行测量,不妨就选择带宽的5%。设置我们的类:  
# tc class add dev eth0 parent 10:1 classid 10:100 cbq bandwidth 10Mbit rate \
  100Kbit allot 1514 weight 800Kbit prio 5 maxburst 20 avpkt 250 \
  bounded
限速为100Kbps。然后我们设置过滤器把ICMP数据包送给这个类:
# tc filter add dev eth0 parent 10:0 protocol ip prio 100 u32 match ip
  protocol 1 0xFF flowid 10:100
15.4. 为交互流量设置优先权
当有很多上行和下行数据充斥了你的链路,而你正在使用telnet或者ssh维护一些系统的话,情况会很糟。器它的数据包会挡住你的按键数据包。有没有一种方法,能让我们的交互数据包在大批数据传输中取得优先呢?Linux可以实现! 像以前一样,我们得处理量个方%
相关阅读 更多 +
排行榜 更多 +
鹰眼IV

鹰眼IV

飞行射击 下载
火柴人史诗射手

火柴人史诗射手

飞行射击 下载
10发子弹安卓版

10发子弹安卓版

飞行射击 下载