两个源IP,如果A和B同时向PC走流量,优先转发A的数据流。
第一步:支持TC tiny
xxxxxxxxxx
make menuconfig
选中
xxxxxxxxxx
-> Network
-> Routing and Redirection
<*> tc-tiny................................ Traffic control utility (minimal)
第二步:支持PRIO
修改
xxxxxxxxxx
package/kernel/linux/modules/netsupport.mk
在SCHED_MODULES_CORE
中添加 sch_prio
在define KernelPackage/sched-core
中增加 PRIO
xxxxxxxxxx
CONFIG_NET_SCHED=y \
CONFIG_NET_SCH_HFSC \
CONFIG_NET_SCH_HTB \
+ CONFIG_NET_SCH_PRIO \
CONFIG_NET_SCH_TBF \
假设PC端连接在OpenWrt设备的eth3口上,源IP A 为192.168.10.2,B为192.168.10.3
优先转发A的数据到eth7口上,配置如下
x
tc qdisc add dev eth3 root handle 1: prio
tc filter add dev eth3 protocol ip parent 1:0 prio 1 u32 match ip src 192.168.10.2 flowid 1:1
tc filter add dev eth3 protocol ip parent 1:0 prio 3 u32 match ip src 192.168.10.3 flowid 1:3
第一行在eth3上添加了一个root qdisc,句柄为1:
,qdisc的类行为prio
prio qdisc,默认创建3个子类,包含纯FIFO qdisc,默认根据ToS位进行分类。也可以使用过滤器来对流量进行分类,也可以在子类上附加其他qdisc替换默认的FIFO。
三个子类优先级从高到低分别为 1:1, 1:2, 1:3
xxxxxxxxxx
root@OpenWrt:/tmp# tc -s class ls dev eth3
class prio 1:1 parent 1:
Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0)
backlog 0b 0p requeues 0
class prio 1:2 parent 1:
Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0)
backlog 0b 0p requeues 0
class prio 1:3 parent 1:
Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0)
backlog 0b 0p requeues 0
第二行,是添加一个过滤器,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的流量正常
全称是 queueing discipline, 它是协议栈和网络接口之间的一个缓冲层。可以在qdiscs 上对数据包做一些操作,比如分类、整形、调度等。
qdisc分为无类(classless)qdisc和有类(classful)qdisc。无类qdisc的内部不再细分类,有类qdisc可以进一步包含多个分类,每个class上可以进一步包含子qdisc,子qdisc也可以是有类qdisc,这样就形成了树状的分层结构。
有类qdisc可以有多个类(class),有些qdisc预定义了子类(如prio),有些则需要用户添加类。一个类上又可以附加其他类。最末端没有子类的类称为叶子类,它上面附加了一个qdisc。当创建一个class的时候,默认会附加一个fifo qdisc,它只是一个简单的队列,不对数据包进行任何的操作。当在这个类上增加子类的时候,这个默认的qdisc被移除。可以将这个默认的fifo qdisc替换成任意其他的qdisc。
过滤器,用于有类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中必须是唯一的。
一个典型的层级结构如下:
x
1: root qdisc
|
1:1 child class
/ | \
/ | \
/ | \
1:10 1:11 1:12 child classes
| | |
| 11: | leaf class
| |
10: 12: qdisc
/ \ / \
10:1 10:2 12:1 12:2 leaf classes
内核只跟root qdisc进行通信,每当包需要入队或者出队的时候,都需要从root节点开始,最终到达叶子节点,从而决定入队到哪里,或者从哪里出队。
比如当一个包入队时,它可能经过如下路径
xxxxxxxxxx
1: -> 1:1 -> 1:12 -> 12: -> 12:2
也可能直接走如下路径
xxxxxxxxxx
1: -> 12:2
这种情况,就是root qdisc上的过滤器决定把包直接送到12:2
注意:入队时是根据过滤器和包的特征决定走哪条路径,而出队时则取决于qdisc本身的调度算法,比如FIFO、优先级队列、SFQ的顺序调度等。
过滤器用于将包分类到子类。tc支持很多类型的分类器,它们根据数据包香光的不同信息来作出决策。其中最常用的就是u32分类器,它根据数据包中的字段做出决策(如源IP地址等)。还有比如fw分类器,根据防火墙如何标记数据包来做出决策,可以使用iptables或nftables标记目标数据包,然后通过fw分类器进行过滤。另外还有诸如route分类器、cgroup分类器、bpf分类器等。
下面仅介绍最常见的u32分类器:
分类器一般接收以下几个公共的参数
protocol
分类器接受的协议,通常只接受IP流量。必须。
parent
分类器附加到哪个handle上。这个handle必须是一个已经存在的类。必须。
prio | perf
分类器的优先级。数字小的优先进行匹配尝试。
handle
这个handle对于不同的过滤器表示不同的含义。
u32过滤器最简单的格式是设置一组选择器对包进行匹配,匹配的包分到特定的子类中,或者执行一个action。u32分类器提供了多种不同的选择器,可以大致分成特殊选择器和通用选择器两类。
常用的有ip选择器和tcp选择器。特殊选择器简化了一些常用字段的设置,可以匹配包头中的各种字段,比如
x
tc filter add dev eth0 protocol ip parent 1:0 prio 10 u32 \
match ip src 192.168.8.0/24 flowid 1:4
上例匹配ip源地址在 192.168.8.0/24 子网的包
xxxxxxxxxx
tc filter add dev eth0 protocol ip parent 1:0 prio 10 u32 \
match ip protocol 0x6 0xff \
match tcp dport 53 0xffff \
flowid 1:2
上例匹配TCP协议(0x6)且目的端口为53的包
特殊选择器总是可以改写成对应的通用选择器,通用选择器可以匹配IP(或上层)头中的几乎任何位,不过相比特殊选择器较难编写和阅读。语法如下:
xxxxxxxxxx
match [ u32 | u16 | u8 ] PATTERN MASK at [OFFSET | nexthdr+OFFSET]
其中u32|u16|u8指定pattern的长度,分别为4个字节、2个字节、1个字节。PATTERN表示匹配的包的pattern,MASK告诉过滤器匹配哪些位,at表示从包的指定偏移处开始匹配。
如下例子:
xxxxxxxxxx
tc filter add dev eth0 protocol ip parent 1:0 pref 10 u32 \
match u32 00100000 00ff0000 at 0 flowid 1:10
选择器会匹配IP头第二个字节为0x10 的包,at 0
表示从头开始匹配,mask为00ff0000
所以只匹配第二个字节,pattern为00100000
即第二个字节为0x10
再看另一个例子:
x
tc filter add dev eth0 protocol ip parent 1:0 pref 10 u32 \
match u32 00000016 0000ffff at nexthdr+0 flowid 1:10
x
tc filter add dev eth3 protocol ip parent 1:0 pref 10 u32 match u32 00000016 0000ffff at nexthdr+0 flowid 1:10
nexthdr
选项表示封装在IP包里的下一个头,即上层协议的头。at nexthdr+0
表示从下一个头第一个字节开始匹配。因为mask为0000ffff
,所以匹配发生在头的第三和第四个字节。在TCP和UDP协议中这两个字节是包的目的端口。数字是由大端格式给出的,所以pattern 00000016
转换成十进制是22。即该选择器会匹配目的端口为22的包。