路由表基本概念
1、路由是由多个不同的数据结构的组合来描述的,每个数据结构代表路由信息的不同部分。例如,一个fib_node对应一个单独的子网,一个fib_alias对应一条路由。这样做的原因是只需通过部分字段可以区分多条路由。路由子系统不是维护一个庞大而臃肿的结构而是将路由分散为多个片段,这样更容易在相似的路由间共享通用的信息,因而就可以分离出不同的函数,并在这些函数之间定义更加清晰的接口。
数据结构之间的关系如下所示:fib_table结构包含一个由33个指针组成的向量,每个指针对应一个网络掩码并指向一个类型为fn_zone的数据结构。fn_zone结构将路由组成hash表,每个单独的子网对应一个fib_node实例,用变量fn_key识别,例如对于子网10.1.1.0/24,fn_key为10.1.1。目的子网相同的不同路由共享同一个fib_node,每条路由都有自己的fib_alias结构,而每个fib_alias实例都与一个fib_info结构相关联,该结构中保存着真实路由信息。需要注意的是,fib_alias和fib_info之间不是一对一的关联,多个fib_alias结构可能共享同一个fib_info结构。例如,有五条到不同目的网络的路由碰巧使用同一个下一跳网关,那么对所有这五条路由来说下一跳信息都是相同的,因而就可以共享相同的下一跳信息。所以这种情况下就有五个fib_node结构和五个fib_alias结构,但只有一个fib_info结构。
每个fib_info结构可以包含一个或多个fib_nh结构,每个fib_nh结构表示下一跳路由器,一个下一跳路由器的信息中包括通过哪个设备能到达该路由器。
2、向路由表中插入一条新的路由是通过fn_hash_insert函数实现的,事实上,调用此函数的还有处理路由的尾部追加(append)、首部追加(prepend)、改变(change)和替换(replace)操作,这些不同的操作是由传入的NLM_F_XXX标识参数来区分的。路由的删除由fn_hash_delete完成,删除路由要比添加路由简单,因为删除路由只有一种操作
3、垃圾回收:在一定条件下,fib_sync_down会变量路由表,设置RTNH_F_DEAD标识来标记满足删除条件的路由项。之后,调用fib_flush再次遍历路由表并删除设置了该标志的路由项。注意:没有周期性的函数来清理路由表。
4、对于路由的查找,总是首先搜索路由缓存,当路由缓存没有查找到时,通过ip_route_input_slow和ip_route_output_slow函数来查找路由表
5、不论是哪个方向的流量,都是利用fib_lookup来查找路由表,fib_lookup函数是对每一个路由表所提供的一个包含查找函数的包裹函数,当不支持策略路由时,查找函数版本是针对local表和main表,当支持策略路由时,逻辑更为复杂,需要查找由策略路由提供的路由表。
6、所有的路由表查找,不论路由表是否由策略路由提供,也不论流量方向如何,都是利用fn_hash_lookup来查找。fn_hash_lookup搜索一个能够将封包路由到特定目的地的fib_node实例。遍历上图中的fn_zone,再通过fn_key为关键字,查找到fn_zone中hash表中对应的fib_node。之后,它还需要检查每一个潜在的路由项,来查找与输入参数struct flowi *flp中其他字段相匹配的路由,这个检查由fib_semantic_match完成
fib_semantic_match被调用以发现在与给定的fib_node相关的路由(fib_alias结构)中,是否存在与搜索关键字所有字段都匹配的路由项。
7、dst->input和dst->output的可能的组合如下图所示:
路由表初始化
1、在系统启动时总是创建下面两个路由表,它们与内核配置选项无关:
-
- ip_fib_local_table:内核将本地地址的路由放在该表中,包括到相关的子网网络地址以及子网广播地址的路由。用户不能直接配置该路由表。
- ip_fib_main_table:所有其他的路由表项(包括用户配置的静态路由,路由协议生成的动态路由)都放在该表内。
2、外部事件:路由子系统尤其对两类事件感兴趣:(1)、网络设备状态的变化,(2)、网络设备上IP配置的变化,我们将看到,许多事件都会刷新路由缓存
3、每当一个接口上配置了一个IP地址时,内核就将一组特殊的路由项添加到一个独立的名为ip_fib_local_table的路由表中,负责添加这些特殊路由项的程序是fib_addr_ifaddr,它对每一条新路由表项调用fib_magic来完成添加
4、通过添加IP地址10.0.1.1/24可以导出如下的路由:
(1)、到地址10.0.1.1/32的路由,简单地讲就是到特定主机的路由
(2)、到网络地址10.0.1.0/24的路由,这是从IP地址和其子网掩码导出的路由
(3)、到广播地址10.0.1.255/32和受限广播地址10.0.1.0/32的路由
5、IP地址是属于系统的,而不是属于所配置的那个接口,因此,到IP地址的路由总是被添加到ip_fib_local_table路由表内,而与设备状态无关。但是在设备关闭时,网络地址和广播地址都是不可达的,所以不能为它们创建路由。因此,在一个关闭的设备上配置一个IP地址时,只能添加IP地址的路由,当该设备后来启动时,将再次调用fib_add_ifaddr来添加所有的路由。
6、注销一个设备和关闭一个设备对路由表将产生不同的影响,在设备被注销时,会从路由表中删除使用该设备的所有路由,而当设备被关闭时到IP地址的路由没有被删除,这是因为它的IP地址属于主机而不是属于接口
输入路由
ip_route_input_slow的调用过程如下所示:
1、ip_route_input在调用fib_lookup查找路由成功时,会将封包发往本地或者转发,但两者都需要执行一些共同的任务,例如对于源地址的合理性检查,通过fib_validate_source检查欺骗企图,之后再创建和初始化一条新缓存表项
路由缓存
1、当对缓存表进行插入、删除或查找时等操作时,路由子系统根据源IP地址、目的IP地址、TOS字段、入口设备或出口设备的组合来选择缓存表中的一个bucket。虽然对于入口流量总是能够对应的入口设备,但对于出口流量可能还不知道相应的出口设备,除非路由查找关键字中已经包含了出口设备,否则只有在路由查找之后才能知道出口设备
2、路由表缓存表项的分配使用dst_alloc函数,它返回一个void型指针,该指针可以被创建者映射为正确的数据类型。从该函数名看好像分配的是dst_entry结构,但是实际上分配的是包含dst_entry结构的更大的表项,例如,对于IPv4分配的是rtable结构,这个函数可以为不同的协议分配不同大小的数据结构,所分配的数据结构的大小通过一个entry_size虚函数来表示
3、将路由缓存绑定到ARP缓存:大多数路由缓存表项要被绑定到该路由下一跳地址对应的ARP缓存表项。这意味着一条路由缓存表项需要一个已有的ARP缓存表项,或者对同一个下一跳地址在进行ARP查找时能够成功。对于本地生成的封包(其入口设备ID为NULL)的输出路由和单播转发路由尤其需要绑定,在这两种情况下,需要ARP来解析下一跳的L2地址,而转发目的地址为广播地址、多播地址和本地地址则不需要ARP解析,因为使用其他方法可以得到这个地址
外部事件对路由表的影响
1、设备状态改变: 路由子系统将收到通知,调用fib_netdev_event来处理该事件。当事件类型为NETDEV_UP时,必须将与该设备上所有IP地址有关的路由表项添加到本地路由表ip_fib_local_table中。这是通过对配置在该设备上的每一个IP地址,都调用fib_add_ifaddr函数来完成的
注意:由上图可知,当设备被关闭时到IP地址的路由并没有被删除,这是因为它的IP地址属于主机而不是属于接口,只要与该地址相关联的设备存在,那么该地址就一直存在。