4. Linux流量控制中的组件

表 1. 流量控制的概念与Linux中流量控制组件的对应关系

流量控制概念Linux流量控制组件
shapingclass提供流量整形功能。
schedulingqdisc就是一个调度器。调度器有比较简单的FIFO,也有比较复杂的如HTB之类的调度器。
classifyingfilter会附着在classifier上进行分类工作。 严格来说,分类器必须依靠过滤器才能正常工作。
policingpolicer可以看成是filter的一个子功能。
droppingfilterpolicer配合使用,并将policer的动作设为丢包,就可以丢弃指定的数据流。
markingdsmark qdisc用于给数据包打上标记。

4.1. qdisc

简单来说,qdisc其实就是一个调度器(第 3.2 节 “调度”)。每个网络接口都会有一个调度器,默认的调度器是FIFO。qdiscs会根据调度器的规则重新排列数据包进入队列的顺序。

qdisc 是 Linux 流量控制系统的核心。qdisc 也被称为排队规则。

classful qdiscs 可以包含多个 class,数据包根据 filter 的设置被分发到特定的 classful qdiscs 上。分类排队规则可以没有子类,但这样做通常会导致系统资源被白白浪费。

classless qdiscs 是没有分类的。由于非分类的排队规则没有子对象,所以 classifying 中也没有针对其的操作。因此,非分类的排队规则无法和过滤器相关联。

许多人常常弄不清楚 root qdisc 和 ingress qdisc 有何区别。这两者都不是真正的排队规则,他们只是一个供我们建立自己的流量控制结构的地方。出站数据包规则可以附加到 egress 上,而入站数据包可以附加到 ingress 上。

egress 和 ingress 存在于每一个网络接口上,在流量控制中最常用的是 egress,也就是我们所熟知的 root qdisc。root qdisc 能与任何类型的排队规则相关联,形成流量控制结构。每一个出站的数据包都要经过 egress,或者说都要经过 root qdisc 排队规则。

网络接口上收到的数据包都将经过 ingress qdisc。ingress qdisc 下无法创建任何分类,且只能与一个 filter 相关联。一般情况下,ingress qdisc 仅仅被当成是一个用于附加 policer 来控制入站流量的对象。

简而言之,egress 要比 ingress 更强大,更像是一个真正的排队规则。ingress 只能和决策器配合使用。本文也将重点介绍 root qdisc,除非特别说明,否则所有的操作都是默认针对 ingress 进行的。

4.2. 分类

类仅存在于可分类的 qdisc 之下(如 HTB CBQ 等)。理论上,类能无限扩展,一个类可以仅包含一个排队规则,也可以包含多个各自排队规则的子类。 [5]. 一个类之下可以包含多个分类的排队规则。这就给 Linux 流量控制系统予以了极大的可扩展性和灵活性。

每一个类都有一个数字编号,filter 将根据这个编号与指定的类相关联。对数据包进行重分类或丢弃操作时也需要用这个数字编号来指定相应的分类器对象。

排队规则上的最末尾的分类成为叶子分类。叶子分类默认包含一个 FIFO 排队规则,且不会包含任何子分类。任何包含子分类的分类都不是叶子分类。

4.3. 过滤器

过滤器是 Linux 流量控制系统中最复杂的对象,它是连接各个流量控制核心组件的纽带。过滤器最简单和最常见的用法就是对数据包进行分类。Linux允许用户使用一个或多个过滤器将出站数据包分类并送到不同的出站队列上(参考第 3.3 节 “分类”)。

  • 一个过滤器必须有一个 classifier

  • 一个过滤器可以有一个 policer

过滤器能与 qdisc 相关联,也可以和 class 相关联。所有的出站数据包首先会通过 root qdisc,接着被与 root qdisc 关联的过滤器进行分类,进入子分类,并被子分类下的过滤器继续进行分类。

4.4. 分类器

过滤器可以使用 tc 命令来进行操作,根据实际需求可以选择使用不同的分类器。最常用的分类器是 u32 分类器,它可以根据数据包中的属性对数据包进行分类。

分类器可以根据数据包的元数据对数据包进行分类,识别不同类型的数据包。在 Linux 流量控制中,分类器的使用是最基本的东西。

4.5. 决策器

决策器只能配合 filter 使用。决策器只有两种操作,当流量高于用户指定值时执行一种操作,反之执行另一种操作。灵活使用决策器可以模拟出三色计(three-color meter)。请参考第 10 节 “图表”

policingshaping 都是 Linux 流量控制中的基本组件,两者都可以对带宽进行限制。但不同的是, shaping 能保存并延迟发送数据包,而 policing 只会直接丢弃数据包。请参考 例 5 “tc filter

4.6. 丢包

丢包操作只能在 policer 中使用。任何已经与 filter 关联的 policer 都可以使用丢包操作。

[注意]注意
在 Linux 流量控制系统中,想要丢弃数据包只能依靠决策器;。决策器可以把进入队列的数据包流速限定在一个指定值之下。另外,它也可以配合分类器,丢弃特定类型的数据包。 [6].

然而,丢包也会造成副作用。比如, a packet will be dropped if the scheduler employed uses this method to control flows as the GRED does.

另外,当网络流量很大时,整形器或调度器可能会用光分配给它们的内存,这时候整形器和调度器也会开始丢弃数据包。

4.7. 句柄

每个 class 和分类的 qdisc (参考 第 7 节 “分类排队规则(分类 qdisc)”)都需要一个唯一的标识符。标识符就是句柄。标识符由一个主编号和一个子编号组成,编号必须是数字。用户可以自由指定编号,但必须遵守以下规则: [7]

分类器和排队规则句柄编号规则

主编号

这个编号对内核来说没有意义。用户可以随意指定一个数字。但是,在流量控制结构中,一个对象下所有子对象的主编号必须相同。另外,按照惯例,root qdisc 的主编号通常应指定为1。

子编号

排队规则的子编号必须为0,而分类器的子编号必须是非0值。一个对象下的所有分类器必须有相同的子编号。

特别地,ingress qdisc 的编号总是 ffff:0。

The handle is used as the target in classid and 在使用 tc filter 时,句柄将会作为 classidflowid 的参数值,以指定操作对象。句柄仅供用户使用,内核会使用自己的一套编号来管理流量控制组件对象。



[5] 一个排队规则只能包含与其类型相同的类。举例来说,HTB 排队规则只能包含 HTB 分类,CBQ 排队规则不能将 HTB 分类作为自己的类。

[6] 想要实现这种功能,你需要一个 filter 和一个 classifier,使用分类器匹配特定类型的数据包,最后关联上一个 policer,并将决策器的操作指定为“丢包”,就像下面这样:police rate 1bps burst 1 action drop/drop

[7] 我不知道编号数字的范围是多大,但我估计其范围应该是一个无符号32位整数(编号以十六进制表示)。