所谓路由,就是根据报文不同的特征,寻找其应该的去处,对于收到的报文需要判断是应该去往本机还是转发,对于转发的报文和本机要发送的报文需要判断应该从哪个接口发出:
路由是L3网际层最重要的任务,对收发的报文,合法的不合法的,广播组播单播的,都要做出相应的处理。
在接收端,linux内核中对IP报文的处理入口是ip_rcv函数,在检测IP包头相关字段合法性后进行路由前netfilter处理(NF_INET_PRE_ROUTING),然后进入ip_rcv_finish函数,如若报文自身未携带路由信息,则必须立刻调用ip_route_input函数为该报文确定路由,注意每个输入IP报文的路由信息是skb的_skb_dst成员,它以结构体dst_entry描述;在发送端,本机发出的报文也必须确定其路由,无论哪种传输层协议,在其发送接口中就会在真正发送之前调用ip_route_output_flow函数必须确定路由,否则不能发送,最终的转发都是调用函数dst_output(对于IP协议实际为函数ip_output),该函数实际是L3和L2.5即邻居子系统(实际就是IP和ARP)的接口部分。
无论是输入报文还是输出报文的路由检索,都是通过路由表和路由缓存表实现,考虑到某些路由器的路由表可能很庞大,为了加快转发效率,linux内核实现了一个路由缓存表,不论输入或输出、广播或组播或单播的报文,都共用这一个路由缓存表,通过把检索匹配的路由表的信息加入到路由缓存表,实现在一段时间内相同路由的报文无需检索整个路由表即可快速查到其路由信息,加快检索转发效率。
路由缓存表在最开始是没有条目的,只有检索匹配的路由表条目才会加入到路由缓存表,所以路由表是最根本的核心,每个linux机器上至少要有1个路由表,用于给本机传输层转发报文,当开启转发即作为网关路由器时,至少要有2个路由表,一个用于给本机传输层转发外,另一个用于从本机其他接口转发出去,开启多路径路由(CONFIG_IP_MULTIPLE_TABLES)时,路由表最多可达256个,不过除非路由表条目很多或者路由策略很复杂,一般的路由器的具体实现还是2个路由表(2.10方案就是如此)。具体每个系统最多可以有多少个路由表,由宏FIB_TABLE_HASHSZ的值决定,如2.10方案的实现如下:若不考虑策略路由,路由条目的内容就是L3的协议字段,对于IP协议,包括源目IP地址、tos、出入接口等内容,事实上在很多场合中实际并不需要什么策略路由,所以路由条目的信息就是L3协议的相关字段,简单的说,路由转发就是根据源目IP地址、入接口、tos共同决定它的出接口是哪里,和其他东西诸如传输层协议相关内容无关;
若考虑策略路由,则在检索路由表时必须首先查看匹配哪种策略,然后在该策略对应的路由表中查找是否存在对应的路由条目,匹配某策略的报文会包括一个mark字段,mark字段隶属于skb,由用户通过iptable工具或socket编程接口把非L3协议字段的内容配置在相应netfilter或socket层,然后在由iproute2工具在对应的路由表中做对应配置,使含有该mark的输入输出报文被选择以相应的路由,这就是策略路由的基本原理);
另外,路由条目是由用户工具iproute2配置,诸如ifconfig等命令均是如此,本质上是用户进程与内核的路由netlink套接字进行交互实现。
无论输入报文还是输出报文,每次检索路由时,都是首先查找路由缓存表,如果命中则成功返回,否则需要查找路由表,在定制了策略路由时需首先检索匹配哪一个策略,并在所匹配的策略中指示的路由表中进一步匹配路由表条目,若找不到匹配的表项则失败返回,若找到匹配表项则把它更新入路由缓存表并成功返回,在未定制策略路由时直接在默认路由表 (默认路由表是初始化时创建的转发本机和转发出接口的两个路由表)中寻找匹配的表项;
下图是输入报文或输出报文的路由过程简图: