目录
文章目录
前文列表
《计算机网络基础 — 以太网》
《计算机网络基础 — 物理网络》
《计算机网络基础 — TCP/IP 网络模型》
《计算机网络基础 — Linux 内核网络协议栈》
《计算机网络基础 — 虚拟网络》
《计算机网络基础 — Linux 虚拟交换机》
《计算机网络基础 — Linux 路由器》
《计算机网络基础 — Linux 虚拟路由器》
流量控制与队列
队列就是一种用于组织未能立即开始的任务或数据流的方法,本质上,队列是一种调度的实现。网络链路通常要求数据包以一定的顺序发送,因此可以在网络设备(e.g. 主机、交换机、路由器等)的出口处使用队列来管理数据包。
流量控制就是在网络设备上通过一系列队列,对数据包进行排序以控制它们的发送顺序,并通过一系列策略控制收到的和发送的数据包是否应该被丢弃,同时还要对数据包的发送速率进行控制。
在大部分情况下,Linux 的流量控制只有一个队列,接收上层(内核协议栈)传递下来的(网卡)数据包,把它们放入队列,并以网卡所能支持的最大速度发送出去,这种类型的队列是作为常见的 FIFO。版本较新的 Linux 的默认队列规则是 pfifo_fast,这是一种比 FIFO 稍微复杂一点的队列。
流量控制的基本概念
数据流
一个数据流就是两台主机间建立的一条连接。任何经由这条连接发送的一系列数据包都可以看成这两台主机间的一条数据流。在 TCP 中,srcIP:Port 及 dstIP:Port 唯一决定了一条数据流,UDP 亦如此。流量控制可以将网络流量分割成不同类型的数据流,这些数据流可以作为一个整体进行传输。不同的流量控制结构可以将网络流量平均地分割成不同的数据流。
数据包调度
对队列中对数据包顺序进行排列或重排就叫做调度,一个调度器会对将要发送的数据包顺序进行排列或重排。最常见的调度器是 FIFO(先入先出队列)。由于数据包必须按顺序出队,因此队列实际上就是一个调度器。
对于不同的网络环境,我们可以使用不同的调度器。一个公平队列算法(e.g. SFQ)能防止一个客户端或一个数据流占用过多的带宽。一个循环算法(e.g. WRR)可以让各个客户端或数据流都有平等的使用网络的机会。还有一些更复杂的算法可用于防止骨干网流量过载,或者是对一些常见算法的改进。
数据包分类
把数据包按照不同类型进行划分叫做分类,分类器能把不同类型的网络流量划分到不同的队列中去。通常我们只对上行流量进行分类。在路由器接收、路由并转发一个数据包的时候,网络设备可以以几种不同的方式给数据包进行分类。其中一种方式是标记数据包,标记数据包的操作可以在一个网络中由管理员进行设置,也有可能在数据包经过每一跳时发生。
数据包策略
在流量控制中使用决策器来控制流量是非常简单的,决策器能计算并限制某个特定队列的流量。决策器通常会应用于网络边界上,某个节点使用的流量不会超过分配给它的流量。当网络流量在预设值以下时,决策器什么都不会做。但当网络流量超过预设值时,决策器就开始发挥作用,它能将流量速率控制在一个固定的水平之下。最不近人情的操作是即使在数据包能够继续分类的情况下依旧直接将其丢弃。
决策器只会区别对待两种情况,分别是入队数据包速率高于或低于预定速率。当入队速率低于预设值时,决策器就会允许数据包入队。当入队速率高于预设值时,决策器就执行其他操作(丢包或重新分类)。虽然决策器内部使用令牌桶来计算速率,但它并不像 shaping(塑形)那样会延迟数据包的发送。
令牌桶
为了控制队列中数据包出队的速率,就需要精确计算单位时间内出队的数据包数量以及大小,而这通常是很复杂的。为了进行简化,现在一般都使用另一种机制:系统以一定的速率产生令牌,每个数据包(或一个字节)对应一个令牌,只有当令牌充足的时候数据包才能出队。令牌桶算法在所有 I/O 系统中都非常常见,在流量整形中亦如此。
打个比方,在游乐园里有很多游客在排队等待乘坐过山车。让我们假设过山车按照固定的时间到来,游客必须等待一列过山车到来后才能乘坐。在这里,过山车上的位置就相当于令牌,而游客就相等于数据包。这就是网络速率限制,或者称为 Shaping(塑形)。在单位时间内,只能有固定数量的游客乘上过山车(出队)。假设某个时刻没有游客排队,而车站里有很多车子,这时候来了一大波游客,那么这些游客的大多数人(甚至是全部)都能立刻乘上过山车。车站里所能停放的过山车数量就是令牌桶中 “桶” 的大小。桶中积攒起来的令牌能在突然出现大量数据包时全部使用出去。游乐园里的过山车以恒定速率到来,如果没有游客排队的话,就停放在等待区里,直到等待区里停满了车子。在令牌桶模型中,令牌以恒定的速率填充到桶中,直到桶满了为止。使用令牌桶模型进行流量整形能应付诸如 HTTP 应用之类会产生突发数据传输的情形。
常见的令牌桶模型有 TBF(Classless qdiscs)和 HTB(Classful qdiscs)。
TBF 以 rate 的速率产生令牌,并在桶中有令牌的时候才发送数据包。当队列中没有数据包的时候,就暂时不需要使用令牌,这时令牌就会在桶中积累起来。如果桶的容量是无限制的,那就会失去流量整形的意义,因此,桶的容量必须是有限的。因为在桶中积累了一定数量的令牌,此时若队列中突然出现大量数据包需要出队,我们也有足够的令牌保证数据包能够顺畅出队。这就是说,一个装满的令牌桶能在一定时间内应付任何类型的流量。比较恒定的网络流量适合使用较小的令牌桶。经常有突发数据传输的网络则比较适合使用大的令牌桶,除非流量整形的目的是为了限制突发数据传输。概括来说,系统会以恒定的速率产生令牌,直到令牌桶满了为止。令牌桶能够在保证较长一段时间内网络流量在限制值以下,又能处理大速率的突发数据传输。
Linux 的流量控制组件
- scheduling:qdisc 就是一个调度器。调度器有比较简单的 FIFO,也有比较复杂的如 HTB 之类的调度器。
- shaping:class 提供流量整形功能。
- classifying:filter 会附着在 classifier 上进行分类工作。严格来说,分类器必须依靠过滤器才能正常工作。
- policing:policer 可以看成是 filter 的一个子功能。
- dropping:将 filter 和 policer 配合使用,并将 policer 的动作设为 DROP “丢包”,就可以丢弃指定的数据流。
- marking:dsmark,qdisc 用于给数据包打上标记。
调度器(Scheduler)
调度器,即:qdisc,也被称为排队规则。每个网络接口都会有一个调度器,默认的调度器是 FIFO。qdiscs 会根据调度器的规则重新排列数据包进入队列的顺序。
- classless qdiscs 是没有分类的。由于非分类的排队规则没有子对象,所以 classifying 中也没有针对其的操作。因此,非分类的排队规则无法和过滤器相关联。
- classful qdiscs 可以包含多个 class,数据包根据 filter 的设置被分发到特定的 classful qdiscs 上。分类排队规则可以没有子类,但这样做通常会导致系统资源被白白浪费。
许多人常常弄不清楚 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,除非特别说明,否则所有的操作都是默认针对 egress 进行的。
过滤器(Filter)
过滤器是 Linux 流量控制系统中最复杂的对象,它是连接各个流量控制核心组件的纽带。过滤器最简单和最常见的用法就是对数据包进行分类。Linux允许用户使用一个或多个过滤器将出站数据包分类并送到不同的出站队列上。
- 一个过滤器必须有一个 classifier。
- 一个过滤器可以有一个 policer。
过滤器能与 qdisc 相关联,也可以和 class 相关联。所有的出站数据包首先会通过 root qdisc,接着被与 root qdisc 关联的过滤器进行分类,进入子分类,并被子分类下的过滤器继续进行分类。
分类器(Classifier)
分类器可以根据数据包的元数据对数据包进行分类,识别不同类型的数据包。
分类仅存在于 classful qdiscs 之下(e.g. HTB CBQ 等)。理论上,类能无限扩展,一个类可以仅包含一个排队规则,也可以包含多个各自排队规则的子类。一个类之下可以包含多个分类的排队规则。这就给 Linux 流量控制系统予以了极大的可扩展性和灵活性。
每一个类都有一个数字编号,filter 将根据这个编号与指定的类相关联。对数据包进行重分类或丢弃操作时也需要用这个数字编号来指定相应的分类器对象。排队规则上的最末尾的分类成为叶子分类。叶子分类默认包含一个 FIFO 排队规则,且不会包含任何子分类。任何包含子分类的分类都不是叶子分类。
一个 qdisc 只能包含与其类型相同的类。举例来说,HTB 排队规则只能包含 HTB 分类,CBQ 排队规则不能将 HTB 分类作为自己的类。
决策器(Policer)
决策器只能配合 filter 使用。决策器只有两种操作,当流量高于用户指定值时执行一种操作,反之执行另一种操作。灵活使用决策器可以模拟出三色计(three-color meter)。
policing 和 shaping 都是 Linux 流量控制中的基本组件,两者都可以对带宽进行限制。但不同的是, shaping 能保存并延迟发送数据包,而 policing 只会直接丢弃数据包。
在 Linux 流量控制系统中,想要丢弃数据包只能依靠决策器;。决策器可以把进入队列的数据包流速限定在一个指定值之下。另外,它也可以配合分类器,丢弃特定类型的数据包。
句柄
每个 class 和分类的 qdisc 都需要一个唯一的标识符,即:句柄。标识符由一个主编号和一个子编号组成,编号必须是数字。
- 主编号:这个编号对内核来说没有意义。用户可以随意指定一个数字。但是,在流量控制结构中,一个对象下所有子对象的主编号必须相同。另外,按照惯例,root qdisc 的主编号通常应指定为 1。
- 子编号:qdisc 的子编号必须为 0,而分类器的子编号必须是非 0 值。一个对象下的所有分类器必须有相同的子编号。特别地,ingress qdisc 的编号总是 ffff:0。
在使用 tc filter 时,句柄将会作为 classid 和 flowid 的参数值,以指定操作对象。句柄仅供用户使用,内核会使用自己的一套编号来管理流量控制组件对象。
TC - Linux 流量控制工具
TC(Traffic Control)是 Linux 操作系统的流量控制器,它利用 “队列规定” 建立处理数据包的队列,并定义队列中的数据包被发送的方式, 从而实现对流量的控制。
示例:限制网卡 eth0 的速率为 10Mbps
tc qdisc add dev eth0 root tbf rate 10mbit burst 10kb lat 400.0ms
tc -s qdisc ls dev eth0
TC 的基本原理
- 数据包从 Input Interface 进入,经过 Ingress Policing 时丢弃了不符合规定的数据包,通过的数据包再由 Input De-Multiplexing(输入多路分配器)进行判断选择。
- 如果数据包的目的地是本主机,那么将数据包送给上层处理;否则需要进行 Forward(转发),将数据包交到 Forwarding Block(转发块)处理。Forwarding Block 同时也接收本主机上层(e.g. TCP、UDP 等网络协议栈)产生的包。 Forwarding Block 通过查看路由表,决定数据包的下一跳。然后,对数据包进行排列并传送到 Output Interface。
一般来说 Linux 只能限制网卡发送的数据包,而不能限制网卡接收的数据包,Linux 可以通过改变发送的次序来控制传输速率。Linux TC(Traffic Control)主要是在输出接口排列时进行处理和实现的。
TC 的组件
TC 主要由 qdisc(队列规定),class(类)和 filter(过滤器)这 3 个组件构成,绘图中一般用圆形表示 qdisc,用矩形表示 class:
Qdisc
Qdisc,队列,更准确应该称之为排队规则,是管理网卡输入/输出数据的一个算法,用于确定数据包的发送方式。每张网卡都与一个 Qdisc 关联,每当 Linux Kernel 需要将数据包从网卡发送时,都会先将数据包添加到该网卡所配置的 Qdisc 中,由 Qdisc 来决定数据包的发送顺序。因此可以说,所有的流量控制都发生在队列中。
TC 的 Qdisc 类型有很多,如:CBQ、HTB 等等。其中 CBQ 比较复杂,不容易理解。而 HTB(Hierarchical Token Bucket,分层令牌桶)是一个可分类的队列,与其他复杂的队列类型相比,HTB 具有功能强大、配置简单及容易上手等优点。下文示例中使用了 HTB。
总的来说,Qdisc 可分为两类:无类别队列规定 和 分类队列规定。前者相对简单,而分类队列规定则具有分类和过滤器等概念,是高级的流量控制功能。
无类别队列规定(Classless Qdiscs)
所谓 “无类别”,即:对进入网卡的数据包不加以区分,使用统一的队列规定。
- 无类别队列规定的队列支持:接受数据包、重新编排数据包、延迟或丢弃数据包,可以对整个网卡的流量进行整形,但不会细分各种情况。
- 常用的无类队别规定队列主要有:pfifo _fast(先进现出,默认队列)、TBF(令牌桶过滤器)、SFQ(随机公平队列)、ID(前向随机丢包)等等。
- 这类队列规定使用的流量整形手段主要是:排序、限速和丢包。
分类队列规定(Classful Qdiscs)
所谓 “分类”,即:对进入网络设备的数据包根据不同的需求以分类的方式区别对待。
- 数据包进入一个分类队列规定的队列后,就被送到了某一个类中,即对数据包进行分类处理。对数据包进行分类的工具是过滤器,过滤器会返回一个决定,队列规定就根据这个决定把数据包送入相应的分类中进行排队。
- 每个分类又可以拥有多个子类,而每个子类都可以再次使用各自的过滤器进一步的分类,直到不需要再次分类为止,数据包才进入该类包含的队列中排队。
- 除了能够包含其它队列规定之外,绝大多数的分类队列规定还能够对流量进行整形。这在需要同时进行调度和流量控制的场景中非常有用。
Class
如果一个类没有子类,那么这个类被称为叶子类,否则就被成为内部类。如上图,1:1 和 1:12 是内部类,其他均为叶子类。叶子类都拥有一个负责为这个类发送数据的 Qdiscs,而且这个 qdisc 是可以再次分类的,如 1:10 有一个分类的队列规定。
Filter
就是一些规则,根据这些规则对数据包进行分类,过滤器可以属于 Qdiscs,也可以属于内部类,若需要在叶子类上再实现分类,那就必须将过滤器与叶子类的分类队列规定关联起来,而不能与叶子类相关联。
最常用的是 U32 过滤器,由一个过滤器和一个动作组成,选择器用来对数据包进行匹配,一旦匹配成功就执行该动作。
使用 TC 进行流量控制
综上,Linux 要对网卡进行流量控制,需要进行如下的步骤:
- 为网卡配置一个队列
- 在该队列上建立分类
- 根据需要,建立子队列和子分类
- 为每个分类建立过滤器
创建队列
tc qdisc add dev eth0 root handle 1: htb default 11
- dev eth0:表示操作网卡 eth0
- root:表示为 eth0 添加的是一个根队列
- handle 1:表示队列的句柄为 1:
- htb:表示要添加的队列为 HTB 队列
- default 11:是 htb 队列特有的参数,表示所有未分类的流量都将被分配给类别 1:11
创建分类
tc class add dev eth0 parent 1: classid 1:11 htb rate 40mbit ceil 40mbit
tc class add dev eth0 parent 1: classid 1:12 htb rate 10mbit ceil 10mbit
- parent 1:表示父类别为根队列 1:
- classid1:11:表示创建一个句柄为 1:11 的子类别
- rate 40mbit:表示该子类别的带宽为 40mbit
- ceil 40mbit:表示该子类别的最大带宽为 40mbit
- burst 40mbit:表示该子类别的峰值带宽为 40mbit
设置过滤器
tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip dport 80 0xffff flowid 1:11
tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip dport 25 0xffff flowid 1:12
- protocol ip:表示该过滤器匹配的协议类型
- prio 1:表示该过滤器的优先级。系统将按照从小到大的优先级顺序来执行过滤器,而对于相同的优先级,系统将按照执行命令的先后顺序执行过滤器。
- u32:表示该过滤器的分类器,是最常用的分类器,命令中 u32 后面的部分用来匹配不同的数据流。
- dport:表示匹配数据包的 Destination Port 字段,如果该字段的值与 Oxffff 进行 “与” 操作得到的结果是 80 的话,则匹配。
- flowid 1:11:表示将把该数据包分配给类别 1:11,从而继承子类 1:11 的速率参数。
上行带宽限制
tc qdisc del dev eth0 root
tc qdisc add dev eth0 root handle 1: htb
tc class add dev eth0 parent 1: classid 1:1 htb rate 20mbit ceil 20mbit
tc class add dev eth0 parent 1:1 classid 1:10 htb rate 10mbit ceil 10mbit
tc qdisc add dev eth0 parent 1:10 sfq perturb 10
# 让 172.20.6.0/24 走默认队列,为了让这个 IP 的数据流不受限制
tc filter add dev eth0 protocol ip parent 1: prio 2 u32 match ip dst 172.20.6.0/24 flowid 1:1
# 默认让所有的流量都从特定队列通过,带宽受到限制
tc filter add dev eth0 protocol ip parent 1: prio 50 u32 match ip dst 0.0.0.0/0 flowid 1:10
下行带宽限制
modprobe ifb
ip link set dev ifb0 up
tc qdisc add dev eth0 handle ffff: ingress
tc filter add dev eth0 parent ffff: protocol ip u32 match u32 0 0 action mirred egress redirect dev ifb0
tc qdisc add dev ifb0 root handle 1: htb default 10
tc class add dev ifb0 parent 1: classid 1:1 htb rate 10mbit
tc class add dev ifb0 parent 1:1 classid 1:10 htb rate 10mbit ceil 10mbit
对 SrcIP 进行限速
tc qdisc add dev ifb0 root handle 1: htb default 20
tc class add dev ifb0 parent 1: classid 1:1 htb rate 10000mbit
tc class add dev ifb0 parent 1:1 classid 1:10 htb rate 2000mbit
tc class add dev ifb0 parent 1:1 classid 1:20 htb rate 1000mbit
tc class add dev ifb0 parent 1:1 classid 1:30 htb rate 500mbit
tc filter add dev ifb0 protocol ip parent 1:0 prio 1 u32 match ip src 129.9.123.85 flowid 1:10
tc filter add dev ifb0 protocol ip parent 1:0 prio 1 u32 match ip src 129.9.123.89 flowid 1:20
tc filter add dev ifb0 protocol ip parent 1:0 prio 1 u32 match ip src 129.9.123.88 flowid 1:20
来源:oschina
链接:https://my.oschina.net/u/4418236/blog/4262140