您现在的位置是:首页 >技术杂谈 >Linux 操作系统原理 — tc 流量控制技术解析网站首页技术杂谈
Linux 操作系统原理 — tc 流量控制技术解析
目录
文章目录
Traffic Control
Traffic Control 技术主要用于提供以下 2 种网络服务类型:
- Bandwidth Management(带宽管理):管理流量在某个接口上的 Bandwidth Size。
- QoS(服务质量保障):保障流量在整个端到端路径上的服务质量。
Traffic Control 技术的本质就是在网络设备(包括:主机网卡、交换机、路由器等)的 Ingress 和 Egress 处使用一系列的 Queues(队列)来对数据报文进行排队,继而控制它们的发送顺序和速率。同时,还可以针对不同的 Queue 施加相应的 Policy(策略)。
根据数据报文的类型不同,通常还可以分为以下 2 种:
- L2 Frame Traffic Control
- L3 Packets Traffic Control
在本文中,我们主要讨论 Linux Kernel 中实现的 L3 Packets Traffic Control 及其对应的 tc CLI。
Traffic Control 的基本实现原理
流量处理的三个层面
最基本的 Traffic Control 实现原理应该分为 3 个不同的层面:
- 流量分类(Classifier):在 IP 网络中,常用 IP 5-tuple(srcIP、srcPort、dstIP、dstPort、Protocol)来粗粒度地标识一条 Flow(数据流)。也可以更细粒度的根据每个 Packet(数据包)的 Header 信息进行分类,例如:根据 IPv4 Header 种的 ToS 字段进行分类。Traffic Classifier(分类器)能把不同类型的 Flows 或 Packets 划分到不同的 Queues 中去。
-
流量标记(Marker):Traffic Marker 可以由应用程序或者网络设备来充当,并根据需要对 Packets 进行标记,例如:修改 IPv4 Header 中的 ToS 字段。以此辅助更加灵活且个性化的流量分类需求。
-
流量策略(Policier):在完成了流量的分类和标记之后,Traffic Policier 可以根据不同的 Policies 对相应的 Traffic 进行 Controlling 和 Scheduling。
流量处理的关键流程
更具体而言,下图所示是一个常见的从 Ingress 到 Egress 的 Traffic Control 实现模型。
报文的标记与分类:
- 标记:Packet 进入 Marker 之后,根据 Flow 的类型,为每个 Packets 打上不同的标记(e.g. DSCP 优先级)。
- 分类:通过将不同的 Filters 挂载到 Classifier 上实现灵活的分类配置,Classifier 根据 Filter 和标记类型对每个 Packets 进行分类,写入到不同的 Queues 中。
拥塞避免:Classifier 在将 Packets 写入 Queues 的过程中需要注意避免队列拥塞。可以通过实时监控网络资源(e.g. 队列或内存缓冲区)的使用情况来实现。如果拥塞水位超过阈值,则 Classifier 应该主动丢弃报文。
拥塞管理:通常而言,Traffic Control 只能限制 Egress 的 Packets,而不能限制 Ingress 的 Packets,所以直接影响 Egress 的拥塞管理是 Traffic Control 的核心,在满足 Bandwidth Mgmt、QoS 等网络服务的前提下,还需要保障网络不会处于拥塞的危险中。主要通过以下手段来实现:
- 带宽管理:将流量限制在特定的带宽内。当流量超过额定带宽时,超过的部分将被丢弃。满足服务需求的同时,也可以防止个别业务或用户无限制地占用带宽。
- 流量塑形:主动调整流的输出速率,将超出带宽的流量缓存到内存中,再根据实际情况发出。使流量能够比较平稳地传送给下游设备,避免不必要的报文丢弃和拥塞。
- 接口限速:针对某个特定的网络接口设备,直接限制其 Ingress 或 Egress 的总速率。在粗粒度管理场景中,具有简单高效的配置效果。
- 流量调度:通过加载不同的 Scheduler(调度器)来实现相应的 Policy。
流量队列的类型
Queue 是实现 TC 的关键,在长久的发展过程中,针对不同的业务需求,也诞生了多种类型的 Queues 和算法。
FIFO 队列
最简单的是 FIFO(先入先出)队列,也是早期 Linux Kernel 默认使用的发包队列类型。当 IP Packet 从 TCP/IP Stack 传递到 Network Device Layer 之后,就进入到 TC Egress FIFO Queue,并以 NIC 所能支持的最大速率发送出去。
PFIFO_FAST 队列
PFIFO_FAST 队列是 FIFO 队列的改进版本,作为新版 Kernel 的默认队列类型。在 FIFO 队列的基础上,支持识别 IPv4 Header 中的 ToS(Type of Service,服务类型)字段,继而进入到不同的优先级 Queues 中,以此来实现对不同服务类型的 QoS 保障。
SFQ 队列
SFQ 公平队列,顾名思义,它对所有 IP Packets 一视同仁,常用于防止某一个 Client 或 Flow 占用过多的带宽。
令牌桶队列
令牌桶队列(Token Bucket)实现了令牌桶算法,该算法的设计比较简单:
- Bucket(桶)会以一定的 Rate(速率)产生 Tokens(令牌),并且 Bucket 的容量是有限的。
- 一个 Packet 对应一个 Token,进入到 Queue 中的 Packet 只有从 Bucket 中获得了 Token 之后才可以出队。
- Bucket 通过控制 Token 生成的 Rate,继而控制 Packet 出队的 Rate。
- 当没有 Packet 要出队时,Bucket 中的 Tokens 会累积起来,以应对 Burst(突发)流量。
可见,网络流量比较恒定的场景中适合使用较小的令牌桶,而经常有突发流量的网络则适合使用大的令牌桶。
常见的令牌桶算法和队列主要有 2 种:
- TBF(Token Bucket Filter,令牌桶过滤器)队列
- HTB(Hierarchical Token Bucket,分层令牌桶)队列
Kernel Traffic Control 的工作原理
下图所示,TC 位于 L3 Sub-system 的外边界。IP Packets 进入 L3 Sub-system 之前需要先经过 TC Ingress(入方向),IP Packets 从 L3 Sub-system 出来后也会经过 TC Egress(出方向)。下面再逐一介绍 Kernel Traffic Control 的组部分。
Qdisc(队列描述)
Qdisc(Queue describer,队列描述),在 Linux 中的每个 Network Interface 设备都可以关联一个 Qdisc。
当 Kernel 发送 IP Packets 时,首先在 L3 sub-system 完成 Routing 确定一个 Next Hop Interface,然后再把 Packets 入队到 Interface 关联的 Qdisc 队列中。最终由 Qdisc 来决定 Packet 的发送顺序和速率。
每个 Qdisc 都具有一个队列类型,例如前文中介绍过的 FIFO、SFQ、令牌桶队列等。从实现原理上,Qdisc 可分为两大类型:
-
Classless Qdiscs(无类别队列):实现相对简单,因为无需对 Packets 进行分类。包括:PFIFO_FAST(先进现出,默认队列)、SFQ(随机公平队列)、TBF(令牌桶过滤器)、ID(前向随机丢包)等队列类型。Classless Qdiscs 只支持接受数据包、重新编排数据包、延迟或丢弃数据包,可以对整个网卡接口的流量进行整形,但不会细分各种情况。常用于简单的排序、限速和丢包场景。
-
Classful Qdiscs(分类队列):实现复杂,必须要关联到 Class 和 Filter 这两个高级分类概念上。包括:HTB(Hierarchical Token Bucket,分层令牌桶)队列等。当数据包进入一个 Classful Qdiscs 后,就会被 Filter 分类到某一个 Class 中。每个 Class 又可以具有多个 Sub-classes 和 Filters。直到不再分类为止,数据包才最终确定进入一个队列中。
值得注意的是,每个 Network Interface 都可以同时具有 TC Ingress 和 Egress,但实际上 Ingress 只能使用 Classless Qdiscs,其被当作一个用于附加 Policer 来控制入站流量的对象。而 Egress 则可以使用 Classful Qdiscs,是 Traffic Control 的关键位置。
Class(分类)
Class 作用于 Classful Qdiscs(e.g. HTB),被设计成树状结构,虽然一个 Class 仅可以关联一个 Qdiscs。但是一个 Class 可以具有任意个 Sub-Classes。所以理论上 Class 可以无限扩展,这样的设计使得 Linux 流量控制系统具有了极大的可扩展性和灵活性。
在一棵 Class Tree 中,可能会具有多种 Class 类型:
- Root Class(根分类)
- Inner Class(内部分类)
- Leaf Class(叶子分类)
叶子分类都拥有一个负责发送该类数据的 Qdiscs,而且这个 Qdisc 是可以被再次分类的。但需要注意的是,一个 Qdisc 只能包含与其类型相同的 Class。例如:HTB Qdisc 只能包含 HTB Class,CBQ Qdisc 不能包含 HTB Class。
Filter(过滤器)
Filter 由 Classifier(分类器)和 Policer(策略器)组成,可以关联到 Qdisc 也可以关联到 Inner Class。每一个 Class 都有一个 ID,filter 将根据这个 ID 与指定的 Class 相关联。
所有 Egress 的 Packets 首先会通过 Interface 的 Root Qdisc,接着被与 Root Qdisc 关联的 Filter 对 Packets 进行分类,进入子分类,然后由子分类下的 Filter 继续进行分类。所以,如果需要在 Leaf Class 上再实现分类,那就必须将 Filter 与 Leaf Class 的 Qdisc 关联起来,而不能直接与 Leaf Qdisc 相关联。
Policer(策略器)
Policer 只能配合 Filter 使用。Policer 只有两种操作,当流量高于用户指定值时执行一种操作,反之执行另一种操作。
Policing 和 Shaping 都是 Linux 流量控制中的基本组件,两者都可以对带宽进行限制。但不同的是,Shaping 能保存并延迟发送数据包,而 Policing 只会直接丢弃数据包。所以,想要丢弃数据包只能依靠 Policer。
使用 tc CLI 进行流量控制的示例
使用 TC 进行流量控制通常需要 4 个步骤:
- 为网卡接口配置一个 Qdisc;
- 在该队列上建立 Class;
- 根据需要,建立 Sub-qdisc 和 Sub-class;
- 为每个 Class 建立 Filter。
1. 创建队列
$ 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;
2. 创建分类
$ 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
3. 设置过滤器
$ 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 的速率参数。
4. 上行带宽限制
$ 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
5. 下行带宽限制
$ 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
6. 对指定 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
HTB 示例
# add qdisc
$ tc qdisc add dev eth0 root handle 1: htb default 2 r2q 100
# add default class
$ tc class add dev eth0 parent 1:0 classid 1:1 htb rate 1000mbit ceil 1000mbit
$ tc class add dev eth0 parent 1:1 classid 1:2 htb prio 5 rate 1000mbit ceil 1000mbit
$ tc qdisc add dev eth0 parent 1:2 handle 2: pfifo limit 500
# add default filter
$ tc filter add dev eth0 parent 1:0 prio 5 protocol ip u32
$ tc filter add dev eth0 parent 1:0 prio 5 handle 3: protocol ip u32 divisor 256
$ tc filter add dev eth0 parent 1:0 prio 5 protocol ip u32 ht 800:: match ip src 192.168.0.0/16 hashkey mask 0x000000ff at 12 link 3:
# add egress rules for 192.168.0.9
$ tc class add dev eth0 parent 1:1 classid 1:9 htb prio 5 rate 3mbit ceil 3mbit
$ tc qdisc add dev eth0 parent 1:9 handle 9: pfifo limit 500
$ tc filter add dev eth0 parent 1: protocol ip prio 5 u32 ht 3:9: match ip src "192.168.0.9" flowid 1:9