我们知道,如果一个数据包要到达本地,那么它要经过两次查找过程(暂时不考虑conntrack):IP层查找路由和传输层查找socket。怎么合并这两者。
Linux内核协议栈采用了一种办法:在socket中增加一个dst字段作为缓存路由的手段,skb在查找路由之前首先查找socket,找到的话,就将缓存的dst设置到skb,接下来在查找路由的时候,发现已经有了dst,就省去了路由查找的过程。
问题是,socket的dst字段什么时候设置的呢?当然是“和该socket相关的第一个skb”到来的时候设置上的,无疑即便这第一个skb找到了socket,此时的socket上的dst也是NULL,那么它会去老老实实查找路由表,如果找到,则将找到的路由项设置到socket的dst字段。
这个特性在Linux的实现中叫做ip_early_demux,在内核文档中,描述如下:
ip_early_demux - BOOLEAN Optimize input packet processing down to one demux for
certain kinds of local sockets. Currently we only do this
for established TCP sockets.
It may add an additional cost for pure routing workloads that
reduces overall throughput, in such case you should disable it.
Default: 1
对于forward转发,这个特性必然要降低性能,但是我并不是想说这个显而易见的问题,我想说的有两点:
1.层次cache逻辑
我们知道,路由查找是一个“尽力而为”的多对一匹配过程,skb和路由entry并不是精确的一一对应关系,因此不能在路由entry中缓存socket,但是却可以在socket中缓存路由entry,因为socket和skb是一个一一对应的关系(我说的不是TCP listen socket..),同样,我也能在路由cache中缓存socket,因为路由cache和skb也是一一对应的关系。
然而linux内核还是去掉了路由cache的支持,不过这没关系,只要知道一点就够了:一一对应的精确匹配项可以缓存更加松散的非一一对应的匹配项。如果将目光移到conntrack,就知道该怎么做了,我曾经将路由entry缓存到了conntrack里面,按照这个逻辑,是合理的,同样的,socket也可以被缓存到conntrack里面,这一点已经有了iptables相关的match和target。
2.自动还是手动
既然Linux有了ip_early_demux配置参数,问题就是什么时候开启它而什么时候关闭它。特别是,在你事先不知道有多少包是到达本地,有多少包是forward的情况下,这个问题显得更加不易回答。此时,是相信管理员的非0即1的配置呢,还是让系统去动态自适应?
如何统计包显得尤为重要,典型情况下,如果有超过60%的包是到达本地的,那么就开启它,反之则关闭它。ip_early_demux配置参数作为一个全局的参数并没有什么不好,因为如果不是这样,那么就会出现另一个问题,即如何判断一个包是否要进行early_demux...对于边界非七层设备,一般而言,流量是分类的,分为管理面流量和数据面流量,对于前者而言,流量的终点就是本地,而对于后者,本机仅仅做forward,如果能事先高效地将包进行平面分类,那么两个ip_early_demux配置会显得更好,对于带外管理而言,Linux的nsnamespace可以很好地完成这个任务。
3.路由查找和socket查找哪个快
这个不一定,涉及到路由项和socket的数量。因此统计数据是重要的。然而要记住,socket查找是协议相关的,并不是在所有的socket中根据5元组查一个,如果非要和路由查找类比,它更像是策略路由的方式。