IFB简介
背景
通过前面几章的介绍,你会发现我们能对上行做到较好的控制,而对下行却基本做不了什么复杂的操作。
这是 tc 所局限的,因为 tc 的初衷就是为了控制本机往外发送的数据,而外部向本机发送的数据 tc 则不是那么关心。正如之前所列出的 tc 原理图一样,你可以很清楚地看到,tc管的是上行:
Userspace programs
^
|
+---------------+-----------------------------------------+
| Y |
| -------> IP Stack |
| | | |
| | Y |
| | Y |
| ^ | |
| | / ----------> Forwarding -> |
| ^ / | |
| |/ Y |
| | | |
| ^ Y /-qdisc1-\ |
| | Egress /--qdisc2--\ |
--->->Ingress Classifier ---qdisc3---- | ->
| Qdisc \__qdisc4__/ |
| \-qdiscN_/ |
| |
+----------------------------------------------------------+
仔细地观察,你会发现在 Ingress 处 tc 的结构很简单,而Egress相比于此则要复杂许多。
为了加强对 Ingress 的控制,我们需要使用到 ifb 的功能,使用 ifb 与 tc 配合你可以理解为这样子:
Userspace programs
^
|
+---------------+-----------------------------------------+
| Y |
| IP Stack <-------------------------------------------------------|
| | | |
| Y | |
| Y | |
| | | |
| Forwarding -> | |
| | | |
| Y | |
| | | |
| Y /-qdisc1-\ | |
| Egress /--qdisc2--\ | |
--->->Ingress Classifier ---qdisc3---- | -> |
| Qdisc \__qdisc4__/ | |
| | \-qdiscN_/ | |
| | | |
+-----|----------------------------------------------------+ |
| ens33 |
| |
| +---------------+-----------------------------------------+ |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | / ----------> Forwarding -> | |
| | / | | |
| | / Y | |
| | | | | |
| | ^ Y /-qdisc1-\ | |
| | | Egress /--qdisc2--\ | |
|--------->->Ingress Classifier ---qdisc3---- | ---|
| Qdisc \__qdisc4__/ |
| \-qdiscN_/ |
| |
+----------------------------------------------------------+
ifb
观察这张图,你就会发现 tc 与 ifb 合作实现对 Ingress 的控制的原理实现,分为三个步骤:
- 将 ifb 虚拟网卡拉起来
- 将 真实网卡(例如ens33) 的 Ingress 重定向到 ifb 的 Ingress
- 对 ifb 的 Engress 进行控制
Hint : 需要注意的是 ifb 的 Egress 重定向到真实网卡(例如 ens33)的 IP Stack 这个过程是 ifb 自己实现的;你也会发现 ifb 的结构比 真实网卡(例如 ens33) 简单,这也是其本身的特性决定的。
只有 ifb 才能办到此功能,其他虚拟网卡的方式是行不通的,例如使用 ifconfig 构造虚拟网卡。ifb之所以行的同是因为其构造出来的网卡具有自己的 MAC 地址,而像 ifconfig 这种方式构造出来的虚拟网卡,MAC 地址是一样的,所以不行。
IFB的原理概述
和tun一样,ifb也是一个虚拟网卡,和tun一样,ifb也是在数据包来自的地方和去往的地方做文章。对于tun而言,数据包在xmit中发往字符设备,而从字符设备写下来的数据包则在tun网卡上模拟一个rx操作,对于ifb而言,情况和这类似。
ifb驱动模拟一块虚拟网卡,它可以被看作是一个只有TC过滤功能的虚拟网卡,说它只有过滤功能,是因为它并不改变数据包的方向,即对于往外发的数据包被重定向到ifb之后,经过ifb的TC过滤之后,依然是通过重定向之前的网卡发出去,对于一个网卡接收的数据包,被重定向到ifb之后,经过ifb的TC过滤之后,依然被重定向之前的网卡继续进行接收处理,不管是从一块网卡发送数据包还是从一块网卡接收数据包,重定向到ifb之后,都要经过一个经由ifb虚拟网卡的dev_queue_xmit操作。
INGRESS队列
Linux TC是一个控发不控收的框架,然而这是对于TC所置于的位置而言的,而不是TC本身的限制,事实上,你完全可以自己在ingress点上实现一个队列机制,说TC控发不控收只是因为Linux TC目前的实现没有实现ingress队列而已。
Linux的协议栈上有诸多的钩子点,Netfilter当然是最显然的了,它不但可以实现防火墙和NAT,也可以将一个数据包在PREROUTING钩子点上queue到一个队列,然后再将此队列的数据包发往一个虚拟网卡,虚拟网卡的xmit回调函数将数据包重新放回Netfilter将数据包STOLEN走的点上,在发往虚拟网卡的时候做发送流控从而变相地实现ingress队列,这就是IMQ的原理,它工作地不错,但是需要在skb中增加字段,使用起来也要牵扯到Netfilter的配置,不是那么纯粹,于是在这个思想的基础上实现了ifb驱动,这个驱动直接挂在TC本身的ingress钩子上,并不和Netfilter发生关系,但是由于TC的钩子机制并没有将一个数据包偷走再放回的机制,于是只有在做完ifb的流控后在ifb网卡的xmit函数中重新调用实际网卡的rx一次,这个实现和Linux Bridge的实现中完成local deliver的实现如出一辙。
QDISC的多网卡共享
除了ingress队列之外,在多个网卡之间共享一个根Qdisc是ifb实现的另一个初衷,可以从文件头的注释中看出来。如果你有10块网卡,想在这10块网卡上实现相同的流控策略,你需要配置10遍吗?将相同的东西抽出来,实现一个ifb虚拟网卡,然后将这10块网卡的流量全部重定向到这个ifb虚拟网卡上,此时只需要在这个虚拟网卡上配置一个Qdisc就可以了。
性能问题
也许你觉得,将多块网卡的流量重定向到一块ifb网卡,这岂不是要将所有的本属于不同的网卡队列被不同CPU处理的数据包排队到ifb虚拟网卡的一个队列被一个CPU处理吗?事实上,这种担心是多余的。
是的,ifb虚拟网卡只有一个网卡接收队列和发送队列,但是这个队列并非被一个CPU处理的,而是被原来处理该数据包的CPU(只是尽量,但不能保证就是原来处理该数据包的那个CPU)继续处理,怎么做到的呢?事实上ifb采用了tasklet来对待数据包的发送和接收,在数据包进入fib的xmit函数之后,将数据包排入队列,然后在本CPU上,注意这个CPU就是原来处理数据包的那个CPU,在本CPU上调度一个tasklet,当tasklet被执行的时候,会取出队列中的数据包进行处理,如果是egress上被重定向到了ifb,就调用原始网卡的xmit,如果是ingress上被重定向到了ifb,就调用原始网卡的rx。当然,tasklet中只是在队列中取出第一个数据包,这个数据包不一定就是在这个CPU上被排入的,这也许会损失一点cache的高利用率带来的性能提升,但不管怎样,如果是多CPU系统,那么显然tasklet不会只在一个CPU上被调度执行。另外,开销还是有一点的,那就是操作单一队列时的自旋锁开销。
优化方式是显然的,那就是将队列实现成“每CPU”的,这样不但可以保证cache的高利用率,也免去了操作单一队列的锁开销。
一个示例配置
# 加载ifb驱动并创建ifb网卡(使用ifconfig -a 如果看到已有则无需该步骤)
modprobe ifb numifbs=1
# up网卡
ip link set dev ifb0 up
# 清除原有的根队列(根据实际情况操作,非必要)
tc qdisc del dev eth0 root 2>/dev/null
tc qdisc del dev eth0 ingress 2>/dev/null
tc qdisc del dev ifb0 root 2>/dev/null
# 将eth0的ingress流量全部重定向到 ifb0 处理
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
# eth0的出向限速:eth0添加根队列,使用htb,添加1:1类,使用htb
tc qdisc add dev eth0 root handle 1: htb r2q 625 default 65
tc class add dev eth0 parent 1: classid 1:1 htb rate 1000Mbit
# eth0的入向限速:ifb0添加根队列,使用htb,添加1:1类,使用htb
tc qdisc add dev ifb0 root handle 1: htb r2q 625 default 65
tc class add dev ifb0 parent 1: classid 1:1 htb rate 1000Mbit
# eth0的出向限速:eth0设备添加子类\对应的filter配置规则和子类的队列
tc class add dev eth0 parent 1:1 classid 1:10 htb rate 10Mbit
tc filter add dev eth0 parent 1: protocol all prio 1 u32 match ip dst 192.168.0.2 classid 1:10
tc qdisc add dev eth0 parent 1:10 handle 10: sfq
# eth0的出向限速:eth0设备添加子类\对应的filter配置规则和子类的队列
tc class add dev eth0 parent 1:1 classid 1:11 htb rate 20Mbit
tc filter add dev eth0 parent 1: protocol all prio 1 u32 match ip dst 192.168.0.3 classid 1:11
tc qdisc add dev eth0 parent 1:11 handle 11: sfq
# eth0的入向限速:ifb0设备添加子类\对应的filter配置规则和子类的队列
tc class add dev ifb0 parent 1:1 classid 1:10 htb rate 10Mbit
tc filter add dev ifb0 parent 1: protocol all prio 1 u32 match ip src 192.168.0.2 classid 1:10
tc qdisc add dev ifb0 parent 1:10 handle 10: sfq
# eth0的入向限速:ifb0设备添加子类\对应的filter配置规则和子类的队列
tc class add dev ifb0 parent 1:1 classid 1:11 htb rate 20Mbit
tc filter add dev ifb0 parent 1: protocol all prio 1 u32 match ip src 192.168.0.3 classid 1:11
tc qdisc add dev ifb0 parent 1:11 handle 11: sfq
开启 ifb
正如tc所需要的模块支持,ifb也是需要对应的模块支持的。
以下是 Linux 内核编译文件 Kconfig 中 IFB 模块的原文:
config IFB
tristate "Intermediate Functional Block support"
depends on NET_CLS_ACT
---help---
This is an intermediate driver that allows sharing of
resources.
To compile this driver as a module, choose M here: the module
will be called ifb. If you want to use more than one ifb
device at a time, you need to compile this driver as a module.
Instead of 'ifb', the devices will then be called 'ifb0',
'ifb1' etc.
Look at the iproute2 documentation directory for usage etc
可以看到IFB依赖于其他模块 NET_CLS_ACT,如果编译失败的话很可能是 NET_CLS_ACT 没有开启。(但是一般是不会的,因为开启一个模块,对应它所依赖的模块也会被开启,而且此过程是递归的。)
翻到最上面,看到以下内容:
menuconfig NETDEVICES
default y if UML
depends on NET
bool "Network device support"
记录下这个名字,它在上层应该是被设置成。
但是在 make menuconfig 中并没有发现 Network device support,而且在当前文件没有找到包含 Network device support 的menu,所以这个文件肯定被别人包含走了;于是确认当前文件名为 “/drivers/net/Kconfig”,然后全文检索 “source “drivers/net/Kconfig””,找到包含此句的文件名为 “/drivers/Kconfig”, 然后在此文件查找 menu 可以找到如下内容:
menu "Device Drivers"
所以得出结论,在 make menuconfig 中,IFB模块的位置为:
Device Drivers
-> Network device support
-> Intermediate Functional Block support
按照这里路径,在 make menuconfig 中看到以下画面:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dXo3eNdu-1635818973119)(./images/09_ifb简介_make_menuconfig.png)]
可以看到 IFB 是没有开启的,将它开启即可。