您现在的位置是:首页 >技术杂谈 >Linux 操作系统原理 — tc 流量控制技术解析网站首页技术杂谈

Linux 操作系统原理 — tc 流量控制技术解析

范桂飓 2024-10-01 12:01:04
简介Linux 操作系统原理 — tc 流量控制技术解析

目录

Traffic Control

Traffic Control 技术主要用于提供以下 2 种网络服务类型:

  1. Bandwidth Management(带宽管理):管理流量在某个接口上的 Bandwidth Size。
  2. QoS(服务质量保障):保障流量在整个端到端路径上的服务质量。

Traffic Control 技术的本质就是在网络设备(包括:主机网卡、交换机、路由器等)的 Ingress 和 Egress 处使用一系列的 Queues(队列)来对数据报文进行排队,继而控制它们的发送顺序和速率。同时,还可以针对不同的 Queue 施加相应的 Policy(策略)。

根据数据报文的类型不同,通常还可以分为以下 2 种:

  1. L2 Frame Traffic Control
  2. L3 Packets Traffic Control

在本文中,我们主要讨论 Linux Kernel 中实现的 L3 Packets Traffic Control 及其对应的 tc CLI。

Traffic Control 的基本实现原理

流量处理的三个层面

最基本的 Traffic Control 实现原理应该分为 3 个不同的层面:

  1. 流量分类(Classifier):在 IP 网络中,常用 IP 5-tuple(srcIP、srcPort、dstIP、dstPort、Protocol)来粗粒度地标识一条 Flow(数据流)。也可以更细粒度的根据每个 Packet(数据包)的 Header 信息进行分类,例如:根据 IPv4 Header 种的 ToS 字段进行分类。Traffic Classifier(分类器)能把不同类型的 Flows 或 Packets 划分到不同的 Queues 中去。

在这里插入图片描述

  1. 流量标记(Marker):Traffic Marker 可以由应用程序或者网络设备来充当,并根据需要对 Packets 进行标记,例如:修改 IPv4 Header 中的 ToS 字段。以此辅助更加灵活且个性化的流量分类需求。
    在这里插入图片描述

  2. 流量策略(Policier):在完成了流量的分类和标记之后,Traffic Policier 可以根据不同的 Policies 对相应的 Traffic 进行 Controlling 和 Scheduling。
    在这里插入图片描述

流量处理的关键流程

更具体而言,下图所示是一个常见的从 Ingress 到 Egress 的 Traffic Control 实现模型。

报文的标记与分类

  1. 标记:Packet 进入 Marker 之后,根据 Flow 的类型,为每个 Packets 打上不同的标记(e.g. DSCP 优先级)。
  2. 分类:通过将不同的 Filters 挂载到 Classifier 上实现灵活的分类配置,Classifier 根据 Filter 和标记类型对每个 Packets 进行分类,写入到不同的 Queues 中。

拥塞避免:Classifier 在将 Packets 写入 Queues 的过程中需要注意避免队列拥塞。可以通过实时监控网络资源(e.g. 队列或内存缓冲区)的使用情况来实现。如果拥塞水位超过阈值,则 Classifier 应该主动丢弃报文。

拥塞管理:通常而言,Traffic Control 只能限制 Egress 的 Packets,而不能限制 Ingress 的 Packets,所以直接影响 Egress 的拥塞管理是 Traffic Control 的核心,在满足 Bandwidth Mgmt、QoS 等网络服务的前提下,还需要保障网络不会处于拥塞的危险中。主要通过以下手段来实现:

  1. 带宽管理:将流量限制在特定的带宽内。当流量超过额定带宽时,超过的部分将被丢弃。满足服务需求的同时,也可以防止个别业务或用户无限制地占用带宽。
  2. 流量塑形:主动调整流的输出速率,将超出带宽的流量缓存到内存中,再根据实际情况发出。使流量能够比较平稳地传送给下游设备,避免不必要的报文丢弃和拥塞。
  3. 接口限速:针对某个特定的网络接口设备,直接限制其 Ingress 或 Egress 的总速率。在粗粒度管理场景中,具有简单高效的配置效果。
  4. 流量调度:通过加载不同的 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)实现了令牌桶算法,该算法的设计比较简单:

  1. Bucket(桶)会以一定的 Rate(速率)产生 Tokens(令牌),并且 Bucket 的容量是有限的。
  2. 一个 Packet 对应一个 Token,进入到 Queue 中的 Packet 只有从 Bucket 中获得了 Token 之后才可以出队。
  3. Bucket 通过控制 Token 生成的 Rate,继而控制 Packet 出队的 Rate。
  4. 当没有 Packet 要出队时,Bucket 中的 Tokens 会累积起来,以应对 Burst(突发)流量。

可见,网络流量比较恒定的场景中适合使用较小的令牌桶,而经常有突发流量的网络则适合使用大的令牌桶。

常见的令牌桶算法和队列主要有 2 种:

  1. TBF(Token Bucket Filter,令牌桶过滤器)队列
    在这里插入图片描述
  2. 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 可分为两大类型:

  1. Classless Qdiscs(无类别队列):实现相对简单,因为无需对 Packets 进行分类。包括:PFIFO_FAST(先进现出,默认队列)、SFQ(随机公平队列)、TBF(令牌桶过滤器)、ID(前向随机丢包)等队列类型。Classless Qdiscs 只支持接受数据包、重新编排数据包、延迟或丢弃数据包,可以对整个网卡接口的流量进行整形,但不会细分各种情况。常用于简单的排序、限速和丢包场景。

  2. 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 个步骤:

  1. 为网卡接口配置一个 Qdisc;
  2. 在该队列上建立 Class;
  3. 根据需要,建立 Sub-qdisc 和 Sub-class;
  4. 为每个 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
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。