Linux内核协议栈处理流程的重构
个人觉得,Linux网络处理还有一个不对称的地方,那就是路由后的转发函数,我们知道Linux的网络处理在路由之后有个分叉,根据目的地的不同,处理逻辑就此分道扬镳,如果路由结果带有LOCAL标志,那么就调用ip_local_deliver,反之调用ip_forward(具体参看ip_route_input_slow中对rth->u.dst.input的赋值)。如此一来,LOCAL数据就径直发往本地了,这其实也是RFC的建议实现,它简要的描述了路由算法:先看目标地址是不是本地,如果是就本地接收...然而我认为(虽然总是带有一些不为人知的偏见),完全没有必要分道扬镳,通过一个函数发送会更加好一点,比如发往本地的数据包同样发往一块网卡处理,只是该块网卡是一块LOOPBACK网卡,这样整个IP接收例程就可以统一描述了。类似的,对于本地数据发送也可以统一由一个虚拟的LOOPBACK网卡发送,而不是直接发送给路由模块。整体如下图所示:
虽然这么对称处理看似影响了效率,逻辑上好像数据包到了第三层后又回到了第二层,然后第二层的本地LOOKBACK网卡调用ip_local_deliver本地接收,但是落实到代码上,也就是几次函数调用而已,完全可以在从ip_forward到dev_queue_xmit这条路上为LOCAL设置直通路线,只要经过这条路即可,这样一来有4个好处:
1.不再需要INPUT这个HOOK点;
2.不再需要FORWARD这个HOOK点;
3.不再需要OUTPUT这个HOOK点,和第1点,第2点一起让Netfilter的整体架构也完全脱离了马鞍面造型;由此,本机发出的数据在DNAT后再也不用reroute了,其实,本来NAT模块中处理路由就很别扭...
4.策略路由可以更加策略化,因为即使目标地址是local表的路由,也可以将其热direct到别的策略表中。
就着以上第3点再做一下引申,我们可以很容易实现N多种虚拟网卡设备,在其中实现几乎任意的功能,比如这个ingress的流控,就可以很容易实现,不需要改内核和做复杂的配置,只要写一个虚拟网卡,配置几条策略路由即可。如此重新实现的协议栈处理逻辑可能某种程度上违背了协议栈分层设计的原则,但是确实能带来很多的好处,当然,这些好处是有代价的。值得注意的是,少了3个HOOK点是一个比较重要的问题,一直以来,虽然OUTPUT在路由后挂载,可是它实际上应该是路由前的处理,INPUT难道不是路由后吗?为何要把POSTROUTING又区分为INPUT和FORWARD,另外FORWARD其实也是路由后的一种...真正合理的是,INPUT和FORWARD应该是挂载在POSTROUTING上的subHOOK Point,可以将它们实现成一个HOOK Operation,对于OUTPUT,直接去除!HOOK点并不区分具体的逻辑,也不应该区分,这种逻辑应该让HOOK Operation来区分。
Linux的IMQ补丁
在实现了自己的虚拟网卡并配置好可用的ingress流控之后,我看了Linux内核的IMQ实现,鉴于之前从未有过流控的需求,一直以来都不是很关注IMQ,本着什么东西都要先自己试着实现一个或者给出个自己的方案(起码也要有一个思想实验方案)然后再与标准实现(所谓的标准一词并不是那么经得起推敲,实际上它只是“大家都接受的实现”的另一种说法,并无真正的标准可言)对比的原则,我在上面已经给出了IMQ的思想。
IMQ补丁,其核心侧重在以下4点:
1.命名。IMQ中的Intermediate,我觉得非常好,明确指出使用一个中间层来适配igress的流控;
2.实现一个虚拟网卡设备。即所谓的Intermediate设备;
3.NF_QUEUE的使用。使用Netfilter的NF_QUEUE机制将需要流控的数据包直接导入虚拟设备而不是通过策略路由间接将数据引入虚拟设备。
4.扩充了skb_buff数据结构,引入和IMQ相关的管理字段。个人认为这是它的不足,我并不倾向于修改核心代码。然而在我自己的虚拟设备实现中,由于ip_local_deliver函数并没有被核心导出(EXPORT),导致我不得不使用/proc/kallsym来查找它的位置,这么做确实并不标准,我不得不修改了核心,虽然只是添加了一行代码:
EXPORT_SYMBOL(ip_local_deliver);
但是还是觉得不爽!
IMQ的总图如下: