Linux3.5内核以后的路由下一跳缓存

在Linux3.5版本(包括)之前,存在一个路由cache,这个路由cache的初衷是美好的,但是现实往往是令人遗憾的。以下是陈列得出的两个问题:
1.面临针对hash算法的ddos问题(描述该问题的文章已经汗牛充栋,不再赘述);
2.缓存出口设备是p2p设备的路由项会降低性能。
这些问题本质上是由于路由cache的查找方式和路由表的查找方式互不相容引起的。路由cache必须是精确的元组匹配,因此它必须设计成一维的hash 表,而路由表查找算法是最前前缀匹配,因此它可以是多维的。路由查找最终会找到路由项,在不考虑策略路由的前提下,我们来看一下把出口设备为p2p设备的路由项塞进路由cache是多么的没有意义。

p2p设备的邻居集合里只有一个下一跳,那就是它的对端,因此对于p2p设备,甚至都不需要进行邻居绑定的过程!然而如果将这类路由塞进路由cache的话,将会占据巨量的内存,试想如果有10w个IP地址需要通信,源IP集合中同样有10w个IP地址,将有可能会建立100w条路由cache项,极端一点,如果此时系统中只有不多的几条路由表项的话,查找路由表的开销可能会反而低于查找路由cache的开销,特别地,如果路由结果是p2p设备,事实上只要想办法cache这唯一的一个条目即可。这就是一和多的区别,这次,我们发现不光零到一有意义,一到多也同样不可小觑。

如果系统中有一块以太网卡eth0,由于同一网段会有多个邻居,不同的目标IP地址,其下一跳可能会有所不同,我们不得不cache每一个与eth0相关的路由项,然后针对每一个数据包进行精确匹配,然而如果系统中有一块p2p网卡,它的邻居只有一个,对于点对点设备而言,其对端逻辑上只有一个设备,它是唯一的且确定的,它是该点对点设备的邻居集合中的唯一一个邻居,因此事实上无需进行邻居绑定过程,只要从点对点设备将数据包发出,该数据包就一定会到达唯一的对端,在这种情况下,如果我们还cache每一个与该p2p网卡相关的路由项,意义就不大了,然而,对于Linux的路由cache机制而言,这是无法做的的,因为在查找路由cache以及查找路由表之前,我们无从知道这个数据包就是最终要从一个p2p网卡发送出去的。

一个解决方案是,如果查找路由表的结果表明其出口设备是p2p设备,则设置一个NOCACHE标志,表示不cache它,待到数据包发送完毕即释放,我想这个实现是简单而明了的,本来去年9月份想实现掉它,也是为了我们的一个网关产品可以提高性能,但是后面我离职了,此事也就不了了之,直到最近,我再次面临了此问题。然而我有了更好的建议,那就是升级内核到3.6+,不过这是后话,事实上,如果你必须维护基于低版本内核的老产品的话,修改代码就是避不开的,幸运的是,不管是老公司,还是新公司,我与2.6.32版本的代码打交道已经6年了。

扩大点说,路由查找这东西确实很尴尬,可以肯定,一台设备上可能会有数十万条的路由,然而与其相连的邻居集合内的节点数却可以用一个字节来表示,而且大多数节点的邻居可能只有不超过10个!我们消耗了大量的精力,什么cache查询,什么最长前缀匹配,最终就是为了在数十万数量级的大海中捞出几根针,所以说,这一直都是一个比较有挑战性的领域,与TCP加速相比,这个领域更加闭环,它不受其它影响,只有算法本身影响它!事实上,不光p2p设备,就连 ethX设备,结局也是悲哀的,配置几十条路由,最终的下一跳可能只有五六个,p2p设备只是更加极端一些罢了,对于p2p设备,我们一般这么写路由即可:
route add -host/net a.b.c.d/e dev tunlX
然而对于ethX设备而言,一般来说我们必须写路由:
route add -host/net a.b.c.d/e gw A.B.C.D
也就是说,p2p设备直接告知了数据包从设备发出去即可,然而对于ethX设备(或者所有的广播网络设备以及NBMA设备),必须进行地址解析或者下一跳解析才会知道从哪里发出去。不光如此,路由cache还会对邻居子系统造成影响,简单的说,就是路由项引用邻居,路由项释放之前,邻居不能被释放,即便 p2p设备不需要邻居解析,在代码层面也必须特殊处理,不幸的是,Linux内核中并没有看到这种特殊处理,p2p设备的路由项依然会塞进路由 cache。

以上就是路由查找的困境。困境在于多对一或者多对少的映射过程,这种情况下,营造一个精确匹配的cache可能使结局更加悲哀,因此,用一种统一的方式进行调优可能更加符合人之常情。Linux3.6以后,去除了路由cache的支持,所有的数据包要想发送出去,必须查找路由表!如今的过程可能会变成以下的逻辑:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/15444.html