openwrt 22.03 使用tc实现流量优先级控制

目标

两个源IP,如果A和B同时向PC走流量,优先转发A的数据流。

实现

支持TC及PRIO的编译

第一步:支持TC tiny

选中

 

第二步:支持PRIO

修改

SCHED_MODULES_CORE中添加 sch_prio

define KernelPackage/sched-core中增加 PRIO

 

配置实现

假设PC端连接在OpenWrt设备的eth3口上,源IP A 为192.168.10.2,B为192.168.10.3

优先转发A的数据到eth7口上,配置如下

解释说明

第一行在eth3上添加了一个root qdisc,句柄为1:,qdisc的类行为prio

prio qdisc,默认创建3个子类,包含纯FIFO qdisc,默认根据ToS位进行分类。也可以使用过滤器来对流量进行分类,也可以在子类上附加其他qdisc替换默认的FIFO。

三个子类优先级从高到低分别为 1:1, 1:2, 1:3

第二行,是添加一个过滤器,parent 1:0表示在根节点上添加该过滤器, prio 1是过滤器的优先级,如果有很多过滤器会根据优先级的值按顺序进行尝试。u32表示使用u32 分类器。match ip src 192.168.10.2表示匹配源IP为192.168.10.2的包,flowid 1:1将匹配到的包分类到1:1子类中。

第三行,将源IP为192.168.10.3的包,匹配到包分类的 1:3子类中

 

最终的效果是,进入1:1的数据包有最高的发送优先级。

流量测试

1、在测试仪上,先打从B到PC的流量,包长1460,流量800M,流量正常无丢包

2、10秒后,开始打从A到PC的流量,包长1460,流量800M,发现之前的从B到PC的流量会逐渐减小到0,而A到PC的流量正常

TC基本概念

QDISCS

全称是 queueing discipline, 它是协议栈和网络接口之间的一个缓冲层。可以在qdiscs 上对数据包做一些操作,比如分类、整形、调度等。

qdisc分为无类(classless)qdisc和有类(classful)qdisc。无类qdisc的内部不再细分类,有类qdisc可以进一步包含多个分类,每个class上可以进一步包含子qdisc,子qdisc也可以是有类qdisc,这样就形成了树状的分层结构。

CLASSES

有类qdisc可以有多个类(class),有些qdisc预定义了子类(如prio),有些则需要用户添加类。一个类上又可以附加其他类。最末端没有子类的类称为叶子类,它上面附加了一个qdisc。当创建一个class的时候,默认会附加一个fifo qdisc,它只是一个简单的队列,不对数据包进行任何的操作。当在这个类上增加子类的时候,这个默认的qdisc被移除。可以将这个默认的fifo qdisc替换成任意其他的qdisc。

FILTERS

过滤器,用于有类qdisc中,决定将包入队到哪个类中。每当一个包到达有子类的类时,就需要进行分类。其中一种分类的方法就是使用过滤器(另外两个是ToS 和 skb->priority)。所有附加到类上的过滤器会被依次调用,直到其中一个返回裁决。一个filter包含了一些条件,当一个包到达该节点时,会根据包的特征判断是否匹配。

 

以上3个是TC中最基本的三个概念,任何复杂的流量控制都是通过这个三元组递归实现的。

层级结构

每个接口有一个 egress 'root qdisc',默认是pfifo_fast。每个qdisc和class都分配一个句柄handle,句柄用于在后续的配置语句中进行引用。除了egress qdisc,一个接口也可以有一个ingress qdisc,负责管制入站的流量。但是ingress qdisc相比classful qdisc其功能非常有限。(所以才哟所谓的控发不控收,对入站流量进行控制通常需要借助ifb[6]或者imq)。

这些qdisc的handles有两个部分组成,一个major数和一个minor数:<major>:<minor>。习惯上将root qdisc命名为1:,等价于1:0。一个qdisc的minor数总是0

子类需要跟它们的parent有相同的major数。major数在一个egress或ingress内必须是唯一的,minor数在一个qdisc和它的class中必须是唯一的。

一个典型的层级结构如下:

内核只跟root qdisc进行通信,每当包需要入队或者出队的时候,都需要从root节点开始,最终到达叶子节点,从而决定入队到哪里,或者从哪里出队。

比如当一个包入队时,它可能经过如下路径

也可能直接走如下路径

这种情况,就是root qdisc上的过滤器决定把包直接送到12:2

注意:入队时是根据过滤器和包的特征决定走哪条路径,而出队时则取决于qdisc本身的调度算法,比如FIFO、优先级队列、SFQ的顺序调度等。

过滤器

过滤器用于将包分类到子类。tc支持很多类型的分类器,它们根据数据包香光的不同信息来作出决策。其中最常用的就是u32分类器,它根据数据包中的字段做出决策(如源IP地址等)。还有比如fw分类器,根据防火墙如何标记数据包来做出决策,可以使用iptables或nftables标记目标数据包,然后通过fw分类器进行过滤。另外还有诸如route分类器cgroup分类器bpf分类器等。

下面仅介绍最常见的u32分类器:

公共参数

分类器一般接收以下几个公共的参数

u32分类器

u32过滤器最简单的格式是设置一组选择器对包进行匹配,匹配的包分到特定的子类中,或者执行一个action。u32分类器提供了多种不同的选择器,可以大致分成特殊选择器和通用选择器两类。

特殊选择器

常用的有ip选择器和tcp选择器。特殊选择器简化了一些常用字段的设置,可以匹配包头中的各种字段,比如

上例匹配ip源地址在 192.168.8.0/24 子网的包

上例匹配TCP协议(0x6)且目的端口为53的包

通用选择器

特殊选择器总是可以改写成对应的通用选择器,通用选择器可以匹配IP(或上层)头中的几乎任何位,不过相比特殊选择器较难编写和阅读。语法如下:

其中u32|u16|u8指定pattern的长度,分别为4个字节、2个字节、1个字节。PATTERN表示匹配的包的pattern,MASK告诉过滤器匹配哪些位,at表示从包的指定偏移处开始匹配。

如下例子:

选择器会匹配IP头第二个字节为0x10 的包,at 0 表示从头开始匹配,mask为00ff0000所以只匹配第二个字节,pattern为00100000即第二个字节为0x10

再看另一个例子:

nexthdr选项表示封装在IP包里的下一个头,即上层协议的头。at nexthdr+0表示从下一个头第一个字节开始匹配。因为mask为0000ffff,所以匹配发生在头的第三和第四个字节。在TCP和UDP协议中这两个字节是包的目的端口。数字是由大端格式给出的,所以pattern 00000016转换成十进制是22。即该选择器会匹配目的端口为22的包。

 

参考