ifb的原理概述和tun一样,ifb也是一个虚拟网卡,和tun一样,ifb也是在数据包来自的地方和去往的地方做文章。对于tun而言,数据包在xmit中发往字符设备,而从字符设备写下来的数据包则在tun网卡上模拟一个rx操作,对于ifb而言,情况和这类似。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