带宽管理的队列规定
你能够了解到什么
在这里你将了解到 tc 核心三组件中最核心的部分(qdisc),其余两个分别是 filter 以及 class,由于连贯性的问题,class也会放在这里说明。
了解完本章,你将从一个应用层的角度了解清楚 tc 的使用,以及它背后映射出来的一些原理。如果你想对tc进行灵活地应用,强烈建议你读完本章所有内容,它是值得的。
约定
为避免概念混乱, tc 采用如下规定来描述带宽:
bit or a bare number Bits per second
kbit Kilobits per second
mbit Megabits per second
gbit Gigabits per second
tbit Terabits per second
bps Bytes per second
kbps Kilobytes per second
mbps Megabytes per second
gbps Gigabytes per second
tbps Terabytes per second
内部数字以 bps 和 b 方式储存,但是当 tc 输出速率时,使用如下表示:
1Mbit = 1024 Kbit = 1024 * 1024 bps => byte/s
解释队列和队列规定
利用队列,我们决定了数据被发送的方式。必须认识到,我们只能对发送数据进行整形。
根据 Internet 的工作方式,我们无法直接控制别人向我们发送什么数据。有点像我们家里的信报箱, 你不可能控制全界,联系每一个人,修改别人对你发送邮件的数量。
然而, Internet 主要依靠 TCP/IP, 它的一些特性很有用。 因为 TCP/IP 没办法知道两个主机之间的网络容量,所以它会试图越来越快地发送数据(所谓的“慢起技术” ) , 当因为网络容量不够而开始丢失数据时, 再放慢速度。 实际情况要比这种方法更聪明,我们以后再讨论。
慢启动机制实际上将的是 tcp 的拥塞控制,下图给出了 tcp 拥塞控制的 4 种情况
这就像当你尚未读完一半邮件时,希望别人停止给你寄信。与现实世界不同,在 Internet 上可以做到这一点。(译注: 这个例子并不恰当, TCP/IP 的这种机制并不是在网络层实现的,而是要靠传输层的 TCP 协议)
如果你有一个路由器, 并且希望能够防止某些主机下载速度太快, 你需要在你路由器的内网卡——也就是向你的网内主机发送数据包的网卡——上进行流量整形。
你还要保证你正在控制的是瓶颈环节 如果你有一个 100M 以太网卡,而你的路由器的链路速度是 256k,你必须保证你发送的数据量没有超过路由器的处理能力。否则,就是路由器在控制链路和对带宽进行整形,而不是你。可以说,我们需要拥有的队列必须是一系列链路中最慢的环节。幸运的是这很容易。
简单的无类队列规定
如前所述,利用队列,我们决定了数据被发送的方式。无类队列规定就是那样,能够接受数据和重新编排、延迟或丢弃数据包。
这可以用作对于整个网卡的流量进行整形, 而不细分各种情况。在我们进一步学习分类的队列规定之前,理解这部分是必不可少的!
最广泛应用的规定是 pfifo_fast 队列规定,因为它是缺省配置。这也解释了为什么其它那些复杂的功能为何如此健壮, 因为那些都与缺省配置相似, 只不过是其他类型的队列而已。
在没有认为干预下,网卡的缓冲区采取的队列就是最简单的 FIFO队列,通过修改为不同的队列,即可以实现不同的功能
每种队列都有它们各自的优势和弱点。
pfifo_fast
这个队列的特点就象它的名字——先进先出(FIFO),也就是说没有任何数据包被特殊对待。至少不是非常特殊。这个队有 3 个所谓的“频道”。 FIFO 规则应用于每一个频道。 并且:如果在 0 频道有数据包等待发送,1 频道的包就不会被处理,1 频道和 2 频道之间的关系也是如此。
内核遵照数据包的 TOS 标记,把带有“最小延迟”标记的包放进 0 频道。
补充概念 TOS ,这是一个很少人了解的字段,它存在于IP报文的第二个字节,在下图你能很清楚的看到它,有关于它的解释,在下面会更进一步地讲述清楚。
不要把这个无类的简单队列规定与分类的 PRIO 相混淆! 虽然它们的行为有些类似,但对于无类的 pfifo_fast 而言,你不能使用 tc 命令向其中添加其它的队列规定。
但是这并不代表你对它不能进行操作,这是 tc 指令无法实现,同属于 iproute2 的ip指令是有能力直接修改此字段(TOS)的
参数与使用
pfifo_fast 队列规定作为硬性的缺省设置,你不能对它进行配置。它缺省是这样配置的:
内核规定, 根据数据包的优先权情况, 对应相应的频道。 这个对应是根据数据包的 TOS 字节进行的。 TOS 看上去是这样的:
priomap:
0 1 2 3 4 5 6 7
+-----+-----+-----+-----+-----+-----+-----+-----+
| | | |
| 优先权 | TOS | MBZ |
| | | |
+-----+-----+-----+-----+-----+-----+-----+-----+
TOS 字段的 4 个 bit 是如下定义的:
二进制 | 十进制 | 意义 |
---|---|---|
1000 | 8 | 最小延迟(md) |
0100 | 4 | 最大througput(mt) |
0010 | 2 | 最大可靠性(mr) |
0001 | 1 | 最小成本(mmc) |
0000 | 0 | 正常服务 |
因为在这 4bit 的后面还有一个 bit,所以 TOS 字段的实际值是上述值的 2倍。(这里其实讲的是TOS位于第3到第6位,而读取的时候是从高位读起的,相当于结果右移了一位)
TOS | Bits | 意义 | Linux优先级 | 频道 |
---|---|---|---|---|
0x00 | 0 | 正常服务 | 0 最好效果 | 1 |
0x02 | 1 | 最小成本(mmc) | 1 填充 | 2 |
0x04 | 2 | 最大可靠性(mr) | 0 最好效果 | 1 |
0x06 | 3 | mmc+mr | 0 最好效果 | 1 |
0x08 | 4 | 最大吞吐量(mt) | 2 大量传输 | 2 |
0x0a | 5 | mmc+mt | 2 大量传输 | 2 |
0x0c | 6 | mr+mt | 2 大量传输 | 2 |
0x0e | 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 |
第一列是TOS在priomap中的值。第二列写着与 4 个 TOS 位相关的数值, 接着是它们的意义。
比如, 15 表示一个数据包要求最小成本、最大可靠性、最大吞吐量和最
小延迟。
第四列写出了 Linux 内核对于 TOS 位的理解,并表明了它们对应哪种优先权。
最后一列表明缺省的权限图。
下表来自 RFC 1349,告诉你应用程序可能如何设置它们的 TOS:
协议 | 命令 | TOS | 意义 |
---|---|---|---|
TELNET | 1000 | (minimize delay) | |
FTP | 控制 | 1000 | (minimize delay) |
FTP | 数据 | 0100 | (maximize throughput) |
TFTP | 1000 | (minimize delay) | |
SMTP | 命令阶段 | 1000 | (minimize delay) |
SMTP | 数据阶段 | 0100 | (maximize throughput) |
Domain Name Service | UDP 查询 | 1000 | (minimize delay) |
Domain Name Service | TCP 查询 | 0000 | |
Domain Name Service | 区域传输 | 0100 | (maximize throughput) |
NNTP | 0001 | (minimize monetary cost) | |
ICMP | 报错 | 0000 | |
ICMP | 请求 | 0000 | (mostly) |
ICMP | 响应 | 0000 | (mostly) |
txqueuelen 队列的长度来自网卡的配置, 你可以用 ifconfig 和 ip 命令修改。 如设置队列长度为 10,执行: ifconfig eth0 txqueuelen 10 。 你不能用 tc 命令设置这个!
令牌桶过滤器(TBF)
令牌桶过滤器(TBF)是一个简单的队列规定:只允许以不超过事先设定的速率到来的数据包通过,但可能允许短暂突发流量朝过设定值。
TBF 很精确,对于网络和处理器的影响都很小。所以如果您想对一个网卡限速,它应该成为您的第一选择!
TBF 的实现在于一个缓冲器(桶), 不断地被一些叫做“令牌” 的虚拟数据以特定速率填充着。 (token rate)。桶最重要的参数就是它的大小,也就是它能够存储令牌的数量。
每个到来的令牌从数据队列中收集一个数据包,然后从桶中被删除。这个算法关联到两个流上——令牌流和数据流于是我们得到 3 种情景:
- 数据流以等于令牌流的速率到达 TBF。这种情况下,每个到来的数据包都能对应一个令牌,然后无延迟地通过队列。
- 数据流以小于令牌流的速度到达 TBF。通过队列的数据包只消耗了一部分令牌,剩下的令牌会在桶里积累下来,直到桶被装满。剩下的令牌可以在需要以高于令牌流速率发送数据流的时候消耗掉,这种情况下会发生突发传输。
- 数据流以大于令牌流的速率到达 TBF。这意味着桶里的令牌很快就会被耗尽。导致 TBF 中断一段时间, 称为“越限”。如果数据包持续到来,将发生丢包。
最后一种情景非常重要,因为它可以用来对数据通过过滤器的速率进行整形。
令牌的积累可以导致越限的数据进行短时间的突发传输而不必丢包, 但是持续越限的话会导致传输延迟直至丢包。
请注意,实际的实现是针对数据的字节数进行的,而不是针对数据包进行的。
TBF参数与使用
即使如此, 你还是可能需要进行修改, 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)
配置范例
这是一个非常简单而实用的例子:
# pp0 修改为你具体想控制网卡
tc qdisc add dev ppp0 root tbf rate 220kbit latency 50ms burst 1540
为什么它很实用呢?如果你有一个队列较长的网络设备,比如 DSL modem 或者cable modem 什么的,并通过一个快速设备(如以太网卡)与之相连,你会发现上载数据绝对会破坏交互性。
这是因为上载数据会充满 modem 的队列,而这个队列为了改善上载数据的吞吐量而设置的特别大。但这并不是你需要的,你可能为了提高交互性而需要一个不太大的队列。也就是说你希望在发送数据的时候干点别的事情。
上面的一行命令并非直接影响了 modem 中的队列,而是通过控制 Linux 中的队列而放慢了发送数据的速度。
把 220kbit 修改为你实际的上载速度再减去几个百分点。如果你的 modem 确实很快,就把“ burst”值提高一点。
随机公平队列(SFQ)
SFQ(Stochastic Fairness Queueing,随机公平队列)是公平队列算法家族中的一个简单实现。它的精确性不如其它的方法,但是它在实现高度公平的同时,需要的计算量却很少。
SFQ 的关键词是“会话” (或称作“流” ),主要针对一个 TCP 会话或者 UDP 流。流量被分成相当多数量的 FIFO 队列中,每个队列对应一个会话。数据按照简单轮转的方式发送, 每个会话都按顺序得到发送机会。
这种方式非常公平, 保证了每一个会话都不会没其它会话所淹没。 SFQ 之所以被称为“随机”,是因为它并不是真的为每一个会话创建一个队列,而是使用一个散列算法,把所有的会话映射到有限的几个队列中去。
也就是逻辑上每个会话独占一个队列,实现上使用一个队列,采用哈希表方式来在逻辑上模拟此行为。
因为使用了散列,所以可能多个会话分配在同一个队列里,从而需要共享发包的机会,也就是共享带宽。为了不让这种效应太明显,SFQ 会频繁地改变散列算法,以便把这种效应控制在几秒钟之内。
有很重要的一点需要声明:只有当你的出口网卡确实已经挤满了的时候,SFQ 才会起作用!否则在你的 Linux 机器中根本就不会有队列,SFQ 也就不会起作用。稍后我们会描述如何把 SFQ 与其它的队列规定结合在一起,以保证两种情况下都比较好的结果。
特别地, 在你使用 DSL modem 或者 cable modem 的以太网卡上设置 SFQ 而不进行任何进一步地流量整形是无谋的!
SFQ参数与使用
SFQ 基本上不需要手工调整:
- perturb : 多少秒后重新配置一次散列算法。 如果取消设置, 散列算法将永远不会重新配置(不建议这样做)。 10 秒应该是一个合适的值。
- quantum : 一个流至少要传输多少字节后才切换到下一个队列。却省设置为一个最大包的长度(MTU 的大小, MTU 最小传输单元)。不要设置这个数值低于 MTU!
SFQ配置范例
# 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 秒种散列算法更换一次。
关于什么时候用哪种队列的建议
- 如果想单纯地降低出口速率,使用令牌桶过滤器。调整桶的配置后可用于控制很高的带宽。
- 如果你的链路已经塞满了,而你想保证不会有某一个会话独占出口带宽,使用随机公平队列。
- 如果你有一个很大的骨干带宽,并且了解了相关技术后,可以考虑前向随机丢包。
- 如果希望对入口流量进行“整形” (不是转发流量), 可使用入口流量策略,注意,这不是真正的“整形”。
- 如果你正在转发数据包,在数据流出的网卡上应用 TBF。除非你希望让数据包从多个网卡流出,也就是说入口网卡起决定性作用的时候, 还是使用入口策略。
- 如果你并不希望进行流量整形,只是想看看你的网卡是否有比较高的负载而需要使用队列,使用 pfifo 队列(不是 pfifo_fast)。它缺乏内部频道但是可以统计 backlog。
- 最后,你可以进行所谓的“社交整形”。你不能通过技术手段解决一切问
题。用户的经验技巧永远是不友善的。正确而友好的措辞可能帮助你的正确地分配带宽!
术语
为了正确地理解更多的复杂配置, 有必要先解释一些概念。 由于这个主题的历史不长和其本身的复杂性,人们经常在说同一件事的时候使用各种词汇。
- 队列规定:
管理设备输入(ingress)或输出(egress)的一个算法。 - 无类的队列规定:
一个内部不包含可配置子类的队列规定。 - 分类的队列规定:
一个分类的队列规定内可一包含更多的类。其中每个类又进一步地包含一
个队列规定,这个队列规定可以是分类的,也可以是无类的。根据这个定义,严格地说 pfifo_fast 算是分类的,因为它实际上包含 3 个频道(实际上可以认为是子类)。然而从用户的角度来看它是无类的,因为其内部的子类无法用 tc 工具进行配置。 - 类:
一个分类的队列规定可以拥有很多类,类内包含队列规定。 - 分类器:
每个分类的队列规定都需要决定什么样的包使用什么类进行发送。分类器
就是做这个用的。 - 过滤器:
分类是通过过滤器完成的。 一个过滤器包含若干的匹配条件, 如果符合匹配条件,就按此过滤器分类。 - 调度:
在分类器的帮助下, 一个队列规定可以裁定某些数据包可以排在其他数据包之前发送。 这种处理叫做“调度”, 比如此前提到的 pfifo_fast 就是这样的。调度也可以叫做“重排序”,但这样容易混乱。 - 整形:
在一个数据包发送之前进行适当的延迟,以免超过事先规定好的最大速
率,这种处理叫做“整形”。整形在 egress 处进行。习惯上,通过丢包来降速也经常被称为整形。 - 策略:
通过延迟或是丢弃数据包来保证流量不超过事先规定的带宽。在 Linux,
里,策略总是规定丢弃数据包而不是延迟。即,不存在 ingress 队列。 - Work-Conserving:
对于一个 work-conserving 队列规定,如果得到一个数据包,它总是立刻对它进行分发。换句话说,只要网卡(egress 队列规定)允许,它就不会延迟数据包的发送。 - non-Work-Conserving:
有些队列——比如令牌桶过滤器——可能需要暂时停止发包以实现限制
带宽。也就是说它们有时候即使有数据包需要处理,也可能拒绝发送。
现在我们简单了解了一些术语,让我们看看他们的位置:
Userspace programs
^
|
+---------------+-----------------------------------------+
| Y |
| -------> IP Stack |
| | | |
| | Y |
| | Y |
| ^ | |
| | / ----------> Forwarding -> |
| ^ / | |
| |/ Y |
| | | |
| ^ Y /-qdisc1-\ |
| | Egress /--qdisc2--\ |
--->->Ingress Classifier ---qdisc3---- | ->
| Qdisc \__qdisc4__/ |
| \-qdiscN_/ |
| |
+----------------------------------------------------------+
现在从这张图这里你也可以看到实质上我们对流量流进的控制十分有限,tc的主要功能是集中在于流量流出
感谢 Jamal Hadi Salim 制作的 ASCII 字符图像。
整个大方框表示内核。 最左面的箭头表示从网络上进入机器的数据包。 它们进入 ingress 队列规定,并有可能被某些过滤器丢弃。即所谓策略。
这些是很早就发生的(在进入内核更深的部分之前)。这样早地丢弃数据有利于节省 CPU 时间。
数据包顺利通过的话,如果它是发往本地进程的,就会进入 IP 协议栈处理并提交给该进程。如果它需要转发而不是进入本地进程, 就会发往 egress。本地进程也可以发送数据,交给 egress 分类器。
然后经过审查, 并放入若干队列规定中的一个进行排队。 这个过程叫做“入队”。在不进行任何配置的情况下, 只有一个 egress 队列规定——pfifo_fast——总是接收数据包。
数据包进入队列后, 就等待内核处理并通过某网卡发送。 这个过程叫做“出队”。
这张图仅仅表示了机器上只有一块网卡的情况,图中的箭头不能代表所有情况。
每块网卡都有它自己的 ingress 和 egress。
分类的队列规定
如果你有多种数据流需要进行区别对待, 分类的队列规定就非常有用了。 众多分类的队列规定中的一种——CBQ(Class Based Queueing, 基于类的队列)——经常被提起, 以至于造成大家认为 CBQ 就是鉴别队列是否分类的标准, 这是不对的。
CBQ 不过是家族中最大的孩子而已,同时也是最复杂的。它并不能为你做所有的事情。 对于某些人而言这有些不可思议, 因为他们受“ sendmail 效应” 影响较深,总是认为只要是复杂的并且没有文档的技术肯定是最好的。
分类的队列规定及其类中的数据流向
一旦数据包进入一个分类的队列规定, 它就得被送到某一个类中——也就是需要分类。对数据包进行分类的工具是过滤器。一定要记住:“分类器”是从队列规定内部调用的,而不是从别处。
过滤器会返回一个决定, 队列规定就根据这个决定把数据包送入相应的类进行排队。 每个子类都可以再次使用它们的过滤器进行进一步的分类。 直到不需要进一步分类时,数据包才进入该类包含的队列规定排队。
除了能够包含其它队列规定之外, 绝大多数分类的队列规定黑能够流量整形。 这对于需要同时进行调度(如使用 SFQ)和流量控制的场合非常有用。 如果你用一个高速网卡(比如以太网卡)连接一个低速设备(比如 cable modem或者 ADSL modem)时,也可以应用。
如果你仅仅使用 SFQ, 那什么用也没有。 因为数据包进、 出路由器时没有任何延迟。虽然你的输出网卡远远快于实际连接速率, 但路由器中却没有队列可以调度。
很遗憾的是在这里你并不获取分类器完全的用法,它的完整介绍会在接下来的章节专门介绍。
队列规定家族:根、句柄、兄弟和父辈
每块网卡都有一个出口“根队列规定”, 缺省情况下是前面提到的 pfifo_fast 队列规定。 每个队列规定都指定一个句柄, 以便以后的配置语句能够引用这个队列规定。除了出口队列规定之外, 每块网卡还有一个入口,以便 policies 进入的数据流。
队列规定的句柄有两个部分: 一个主号码和一个次号码。习惯上把根队列规定称为“ 1:”,等价于“ 1:0”。队列规定的次号码永远是 0。
类的主号码必须与它们父辈的次号码一致。
如何用过滤器进行分类
下图给出一个典型的分层关系:
1: root qdisc
|
1:1 child class
/ | \
/ | \
/ | \
/ | \
1:10 1:11 1:12 child classes
| | |
| 11: | leaf class
| |
10: 12: qdisc
/ \ / \
10:1 10:2 12:1 12:2 leaf classes
不要误解这张图! 你千万不要想象内核处在树的顶点而下面部分是网络。 数据包是在根队列规定处入队和出队的,而内核只同根打交道。
一个数据包可能是按照下面这个链状流程进行分类的:
1: -> 1:1 -> 12: -> 12:2
数据包现在应该处于 12:2 下属的某个队列规定中的某个队列中。 在这个例子中,树的每个节点都附带着一个过滤器, 用来选择下一步进入哪个分支。 这样比较直观。然而,这样也是允许的:
1: -> 12:2
但是我们一般希望你遵守树的规则,否则你建立此树的意义就没有了。
也就是说,根所附带的一个过滤器要求把数据包直接交给 12:2。
数据包如何出队并交给硬件
当内核决定把一个数据包发给网卡的时候,根队列规定 1:会得到一个出队请求,然后把它传给 1:1, 然后依次传给 10:、 11:和 12:, which each query their siblings,然后试图从它们中进行 dequeue()操作。也就是说,内核需要遍历整颗树,因为只有 12:2 中才有这个数据包。
换句话说, 类及其兄弟仅仅与其“父队列规定” 进行交谈, 而不会与网卡进行交谈。只有根队列规定才能由内核进行出队操作!
更进一步,任何类的出队操作都不会比它们的父类更快。这恰恰是你所需要的:我们可以把 SFQ 作为一个子类,放到一个可以进行流量整形的父类中,从而能够同时得到 SFQ 的调度功能和其父类的流量整形功能。
PRIO 队列规定
PRIO 队列规定并不进行整形,它仅仅根据你配置的过滤器把流量进一步细分。你可以认为 PRIO 队列规定是 pfifo_fast 的一种衍生物,区别在每个频道都是一个单独的类,而非简单的 FIFO。
当数据包进入 PRIO 队列规定后, 将根据你给定的过滤器设置选择一个类。缺省情况下有三个类,这些类仅包含纯 FIFO 队列规定而没有更多的内部结构。你可以把它们替换成你需要的任何队列规定。
每当有一个数据包需要出队时,首先处理:1 类。只有当标号更小的类中没有需要处理的包时,才会标号大的类。
当你希望不仅仅依靠包的 TOS,而是想使用 tc 所提供的更强大的功能来进行数据包的优先权划分时,可以使用这个队列规定。它也可以包含更多的队列规定,而 pfifo_fast 却只能包含简单的 fifo 队列规定。
因为它不进行整形,所以使用时与 SFQ 有相同的考虑:要么确保这个网卡的带宽确实已经占满, 要么把它包含在一个能够整形的分类的队列规定的内部。 后者几乎涵盖了所有 cable modems 和 DSL 设备。
严格地说, PRIO 队列规定是一种 Work-Conserving 调度。
PRIO 的参数与使用
tc 识别下列参数:
- bands:
创建频道的数目。每个频道实际上就是一个类。如果你修改了这个数值,
你必须同时修改: - priomap:
如果你不给 tc 提供任何过滤器, PRIO 队列规定将参考 TC_PRIO 的优先级来决定如何给数据包入队。
它的行为就像前面提到过的 pfifo_fast 队列规定, 关于细节参考前面章节。
频道是类,缺省情况下命名为主标号:1 到主标号:3。如果你的 PRIO 队列规定是12:,把数据包过滤到 12:1 将得到最高优先级。
注意: 0 频道的次标号是 1! 1 频道的次标号是 2,以此类推。
PRIO 配置范例
警告:你最好不要在目标机上运行以下指令,除非你已经做好了重新重启的觉悟,因为不要忘记 telnet 也是要联网的,所以基本上你会散失对目标机的控制权。后面过滤器就是为且了解决这个问题出现的。
我们想创建这个树:
1: root qdisc
/ | \
/ | \
/ | \
1:1 1:2 1:3 classes
| | |
10: 20: 30: qdiscs qdiscs
sfq tbf sfq
band 0 1 2
大批量数据使用 30:,交互数据使用 20:或 10:。
命令如下:
# 这个命令立即创建了类: 1:1, 1:2, 1:3
tc qdisc add dev eth0 root handle 1: prio
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 标记的工具):
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:处理的,优先权最低。现在我们验证一下交互数据传输经过更高优先级的频道,我们生成一些交互数据传输:
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 不同,没有数据经过最低优先级的队列规定。
如果对 tc 带类的队列不是很熟悉的话, piro 是一个很好选择,剩下的两个队列并不容易理解,或者说你需要一点时间好好地读一下下面的文档。
著名的 CBQ 队列规定
如前所述, CBQ 是最复杂、最琐碎、最难以理解、 最刁钻的队列规定。这并不是因为其作者的恶毒或者不称职,而是因为 CBQ 算法本身的不精确,而且与 Linux 的内在机制不协调造成的。
除了可以分类之外, CBQ 也是一个整形器,但是从表面上看来工作得并不好。
它应该是这样的: 如果你试图把一个 10Mbps 的连接整形成 1Mbps 的速率, 就应该让链路 90%的时间处于闲置状态,必要的话我们就强制,以保证 90%的闲置时间。
但闲置时间的测量非常困难,所以 CBQ 就采用了它一个近似值——来自硬件层的两个传输请求之间的毫秒数——来代替它。这个参数可以近似地表征这个链路的繁忙程度。
这样做相当慎重, 而且不一定能够得到正确的结论。 比如, 由于驱动程序方面或者其它原因造成一块网卡的实际传输速率不能够达到它的标称速率,该怎么办?
由于总线设计的原因, PCMCIA 网卡永远也不会达到 100Mbps。 那么我们该怎么计算闲置时间呢?
如果我们引入非物理网卡——像 PPPoE、 PPTP——情况会变得更糟糕。因为相当一部分有效带宽耗费在了链路维护上。
那些实现了测量的人们都发现 CBQ 总不是非常精确甚至完全失去了其本来意义。
但是, 在很多场合下它还是能够很好地工作。 根据下面的文档, 你应该能够较好地配置 CBQ 来解决多数问题。
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 的内部由很多的微调参数。比如,那些已知队列中没有数据的类就不参加计算、越限的类将被惩罚性地降低优先级等等。都非常巧妙和复杂。
CBQ 在分类方面的行为
除了使用上述 idletime 近似值进行整形之外,CBQ还可以象 PRIO 队列那样,把各种类赋予不同的优先级,优先权数值小的类会比优先权值大的类被优先处理。
每当网卡请求把数据包发送到网络上时,都会开始一个 WRR(weighted round robin,加权轮转)过程,从优先权值小的类开始。
那些队列中有数据的类就会被分组并被请求出队。在一个类收到允许若干字节数据出队的请求之后,再尝试下一个相同优先权值的类。
下面是控制 WRR 过程的一些参数:
- allot:
当从外部请求一个 CBQ 发包的时候,它就会按照“ priority”参数指定的顺序轮流尝试其内部的每一个类的队列规定。 当轮到一个类发数据时, 它只能发送一定量的数据。“ allot”参数就是这个量的基值。更多细节请参照“ weight”参数。 - prio:
CBQ 可以象 PRIO 设备那样工作。 其中“ prio” 值较低的类只要有数据就必须先服务,其他类要延后处理。 - weight:
“ weight”参数控制 WRR 过程。每个类都轮流取得发包的机会。如果其
中一个类要求的带宽显著地高于其他的类, 就应该让它每次比其他的类发送更多的数据。
CBQ 会把一个类下面所有的 weight 值加起来后归一化,所以数值可以任意定,只要保持比例合适就可以。人们常把“速率/10”作为参数的值来
使用, 实际工作得很好。 归一化值后的值乘以“ allot” 参数后, 决定了每次传输多少数据。
请注意,在一个 CBQ 内部所有的类都必须使用一致的主号码!
决定链路的共享和借用的 CBQ 参数
除了纯粹地对某种数据流进行限速之外, CBQ 还可以指定哪些类可以向其它哪些类借用或者出借一部分带宽。
- Isolated/sharing:
凡是使用“ isolated” 选项配置的类, 就不会向其兄弟类出借带宽。 如果你的链路上同时存在着竞争对手或者不友好的其它人, 你就可以使用这个选项。(选项“ sharing”是“ isolated”的反义选项。) - bounded/borrow:
一个类也可以用“ bounded”选项配置,意味着它不会向其兄弟类借用带
宽。(选项“ borrow”是“ bounded”的反义选项。)
一个典型的情况就是你的一个链路上有多个客户都设置成了“isolated”和“ bounded”, 那就是说他们都被限制在其要求的速率之下, 且互相之间不会借用带宽。
在这样的一个类的内部的子类之间是可以互相借用带宽的。
CBQ配置范例
这个配置把 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
这些命令规定了根上的过滤器,保证数据流被送到正确的队列规定中去。
至此,工作就已经做完了,这是你第一次接触到过滤器的概念,从上面的流程中你可以看出来过滤器是建立在类与类之间的,或者具体一点说是建立在父类到子类的传输过程中的,在此中间添加约束,就是过滤器的本质。可以在仔细的看看 parent 以及 flowid 究竟是什么东西,其余的部分在后面有专门的章节讲解。
注意: 我们先使用了“ 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 的带宽。
其它 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
“ 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 中添加 best effort 数据流,应该执行:
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 |
HTB(Hierarchical Token Bucket, 分层的令牌桶)
Martin Devera (<devik>
)正确地意识到 CBQ 太复杂, 而且并没有按照多数常见情况进行优化。他的 Hierarchical 能够很好地满足这样一种情况:你有一个固定速率的链路, 希望分割给多种不同的用途使用。 为每种用途做出带宽承诺并实现定量的带宽借用。
HTB 就象 CBQ 一样工作, 但是并不靠计算闲置时间来整形。 它是一个分类的令牌桶过滤器。它只有很少的参数,并且在它的网站(http://luxik.cdi.cz/~devik/qos/htb/
)能够找到很好的文档。
随着你的 HTB 配置越来越复杂, 你的配置工作也会变得复杂。 但是使用 CBQ 的话, 即使在很简单的情况下配置也会非常复杂! HTB3 (关于它的版本情况, 请参阅它的网站(http://luxik.cdi.cz/~devik/qos/htb/
))已经成了官方内核的一部分(2.4.20-pre1、 2.5.31 及其后)。然而,你可能仍然要为你的 tc 命令打上 HTB3 支持补丁, 否则你的 tc 命令不理解 HTB3。
如果你已经有了一个新版内核或者已经打了补丁,请尽量考虑使用 HTB。
HTB配置范例
环境与要求与上述 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
作者建议在那些类的下方放置 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,而可以公平发包。
使用过滤器对数据包进行分类
为了决定用哪个类处理数据包,必须调用所谓的“分类器链” 进行选择。这个链中包含了这个分类队列规定所需的所有过滤器。
重复前面那棵树:
1: root qdisc
|
1:1 child class
/ | \
/ | \
/ | \
/ | \
1:10 1:11 1:12 child classes
| | |
| 11: | leaf class
| |
10: 12: qdisc
/ \ / \
10:1 10:2 12:1 12:2 leaf classes
当一个数据包入队的时候, 每一个分支处都会咨询过滤器链如何进行下一步。 典型的配置是在 1:1 处有一个过滤器把数据包交给 12:,然后 12:处的过滤器在把包交给 12:2。
你可以把后一个过滤器同时放在 1:1 处, 可因为…having more specific tests lower in the chain.…而得到效率的提高。
另外,你不能用过滤器把数据包向“上”送。而且,使用 HTB 的时候应该把所有的规则放到根上!
再次强调: 数据包只能向“下” 进行入队操作! 只有出队的时候才会上到网卡所在的位置来。他们不会落到树的最底层后送到网卡!
过滤器的一些简单范例
就象在“分类器” 那章所解释的, 借助一些复杂的语法你可以详细地匹配任何事情。下面我们就开始,从简单地匹配一些比较明显的特征开始。
比方说,我们有一个 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 口的数据包的话,就这么敲:
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
常用到的过滤命令一览
这里列出的绝大多数命令都根据这个命令改编而来:
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’.
总结
看到这里,你在这个章节究竟了解到了什么呢。qdisc这个名词你是否知道是什么意思,或者说队列纪律。如果你还没有感性的认识的话,请看看这几个名词: pfifo_fast、TBF、SFQ、prio、CBQ、HTB, 它们都是队列。讲述这几种队列的目的绝不仅仅只是让你看看而已,你应该认识到不同的队列具有不同的特性,可以用此完成不同的工作。(当然内核需要开启对应的模块支持,你可以再看看 02 内核支持,这时候你对那时候的操作应该就清楚了)。
有一点是非常遗憾的,本章主要内容主要来源于2003年的一篇汉化文档,它的原文写自于2001年,至今已有20年的历史。这是很奇怪的,这么优秀的文档居然没有人去维护。时至今日,可选用的队列已经多了很多种了,我并没有一一去了解,期待你的探索:
仔细观察 02 内核支持,你应该发现有些这里没有讲到的内核模块被开启了,例如 red 。这些我没有讲述的队列模块是我在查阅资料的过程中看到有人使用的,但是具体它们的功能我并不知晓。
也有一点尴尬,类的使用贯穿于整章,我并没有讲述,但是我一直在使用,观察一下示例,它们并不难。
此外,一个新的队列 netem 是值得期待的,它是新支持的功能,我并没有将他融于着一个章节,而是单独为其设立一章讲解,也就是下章了。