Linux内核协议栈的socket查找缓存路由机制
是查路由表快呢?还是查socket哈希表快?这不是问题的根本。问题的根本是怎么有效利用这两者,让两者成为合作者而不是竞争者。这是怎么回事?
我们知道,如果一个数据包要到达本地,那么它要经过两次查找过程(暂时不考虑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可以很好地完成这个任务。