路由缓存表就是把路由表检索时匹配成功的条目加入进去,这样在下次该报文再检索路由时就可以在路由缓存表中匹配成功,无需检索路由表,加快检索效率,这和很多其他场合的道理是一样的,如内存读写访问时MMU优先访问CPU缓存而不是物理内存、库函数优先访问用户缓存而不是系统调用等等的道理是完全一样的,都是为了加快访问效率。
路由缓存表在内核中实际是由全局数组变量rt_hash_table描述的一个哈希表,以hash值为数组下表组成,数组每个成员是一个路由缓存表条目,其结构体为struct rtable,这是路由缓存表的核心数据结构,它还包括一些其他重要结构体成员,整个rtable存储了包括源目IP地址、tos、出入接口及索引、网关、邻居节点信息、输入输出方法、路由策略mark、路由类型以及引用计数、超时时间等大量信息,并且某些字段对于输入报文和输出报文的用途会有一些小差别,如对于输入报文的路由缓存条目需要匹配的是输入接口,而输出报文的路由缓存条目需要匹配的是输出接口等。
对于输入报文,需要匹配报文的源目IP、入接口、tos、策略mark、L4的相关协议的端口号(如果需要的话,一般这样的报文是发给本机)与路由缓存条目是否相同,并且该路由缓存条目必须为可用状态(未超时废弃);对于输出报文,需要匹配报文的源目IP、入接口、tos、策略mark与路由缓存条目是否相同,并且该路由缓存条目必须为可用状态(未超时废弃);此外对于输入报文还通过报文的连接跟踪标记对报文的流向进行检查(确保是进入本机的报文),并且检查报文所属的L3协议是否和路由缓存条目一致;
下面分别是输入报文和输出报文的路由缓存表检索过程:
输入报文的路由缓存表检索
输出报文的路由缓存表检索
不论输入报文处理还是输出报文处理,如果命中,则累积该路由缓存条目的引用计数及全局命中统计,并且把路由缓存条目的核心内容赋给报文skb的_skb_dst字段(skb_dst_set函数),这个核心内容是rtable下的dst_entry,rtable包含了应该匹配的项如源目IP、tos、入接口、策略mark等等匹配信息,dst_entry则包含了对应的出接口、邻居节点(确定邻居是为了确定MAC地址)、下一步的处理函数(如对于输入报文,发给本机为ip_local_deliver,转发为ip_forward)、跃点等结果信息,另外一些重要结果如下一跳IP、路由类型等在rtable中存储;
首先要检查从路由表检索结果中的出接口是否合法,是否和入接口相同,然后要进行“反向”检查,意思是说,该报文可以从接口1转发到接口2,那么还需要检查可不可以从接口2转发到接口1,即确保该报文的另外一个方向也可以路由转发,都满足之后就可以加入路由缓存条目了,如下图:
上面内容的重点是源目IP、tos、入接口、策略mark,它们是路由缓存表的匹配项,注意对于输入报文的匹配字段(flowi结构体成员fl)的出接口索引赋默认值0,这是为了适应路由缓存检索,此外注意这里设置了包括路由类型、下一跳IP、出接口等结果信息,邻居节点信息在该路由缓存条目实际加入到路由缓存表时再加入;
和输入报文类似,输出报文也是记录类似的内容,只是多了一些关于组播收发的处理,关于组播部分的内容见后面的分析,如下图:
创建路由缓存条目后就可以加入路由缓存表了,路由缓存表本质是一个以hash值为下表的哈希表数组,对于输入报文,以源目IP、入接口为参数计算hash值,对于输出报文以源目IP、出接口为参数计算hash值,计算hash值的函数是rt_hash;
然后调用rt_intern_hash函数把新创建的路由缓存条目加入路由缓存表,除了更新路由缓存表,这里还有一个重要的操作,如果这个路由缓存条目是要转发出本机的,这包括两种情况,一种是本机发出的报文(这种情况下入接口为环回接口)即所谓外出路由,另一种是本机收到报文要转发出去的(路由类型为RTN_UNICAST);无论哪种情况下都意味着报文是要从本机发出去,这里必须创建邻居,因为在发送的时候必须知道对方的MAC地址是多少,否则无法填充报文的目的MAC,这样的话是无法在二层正常转发的,所以这里必须调用函数arp_bind_neighbour创建邻居条目,并把这个邻居的信息绑定在路由缓存条目中(dst_entry下的hh_cache和neighbour),关于邻居和ARP详见7.4和7.5两节。
由此也再一次体现,报文在IP网络中的传输,虽然中间有二层设备,但实质上是依靠三层设备这样一跳一跳的转发的,每一跳只会修改报文的目的MAC地址,但IP地址永远不会改变。
对于路由缓存表的条目,既然是缓存,就有可能会被释放掉,不过一般情况下一个路由缓存表条目是不会被释放的,但在某些情况下会被释放掉,往往是在报文发送后收到不可达的返回(ICMP_UNREACHABLE)等类似情况下会释放掉该路由缓存条目,此外有的时候内核会调用函数rt_cache_flush清空整个路由缓存表,这往往是在诸如一个IP地址被创建删除或修改、一条路由表条目被删除、某接口的MAC地址变化等清空发生时被触发。关于路由缓存表的超时废弃标识是其dst_entry成员的expires字段,它标识了该路由缓存条目的有效期,是一个时间值,一个路由缓存条目是否有效是该字段和当前时间比较而得。
个人认为关于路由缓存条目重点在于理解其检索和创建,关于释放,了解即可。