Linux内核IPv4路由选择子系统
一、基本知识
1.具体案例:直连路由
结构fib_nh表示下一跳,包含输出网络设备、外出接口索引等信息。
有两个以太网局域网 LAN1 和 LAN2,其中 LAN1 包含子网 192.168.1.0/24,而 LAN2 包含子网 192.168.2.0/24。在这两个 LAN 之间,有一台转发路由器,它有两个以太网网络接口卡,其中连接到 LAN 的网络接口为 eth0,地址为 192.168.1.200,连接到 LAN2 的网络接口 eth1,IP 地址为 192.168.2.200。简化考虑假设转发路由器没有运行防火墙守护程序。从 LAN1 向 LAN2 发送流量,对到来数据包(从 LAN1 发送到 LAN2 或反向发送)进行转发的过程被称为路由选择,是根据被称为路由选择表的数据结构进行,通过如下图形结构进行表示:
在该案例中,转发路由器的路由表需包含直连两个子网的路由条目,具体示例如下:
目的网络(Destination) 子网掩码(Netmask) 下一跳(Gateway) 出接口(Interface) 192.168.1.0 255.255.255.0 无需(直连) eth0 192.168.2.0 255.255.255.0 无需(直连) eth1
- 原理:
- 当路由器收到发往
192.168.1.0/24
的数据包时,通过路由表匹配到对应条目,从eth0
接口转发(因eth0
直连192.168.1.0/24
所在的 LAN1)。- 同理,发往
192.168.2.0/24
的数据包,匹配到对应条目后从eth1
接口转发(eth1
直连192.168.2.0/24
所在的 LAN2)。
这种直连路由无需额外下一跳地址,依靠路由器接口与子网的直接连接关系完成转发。
2.默认路由和默认网关
1. 默认路由与默认网关的概念
- 默认路由:路由选择表中一条特殊的路由条目,目标网络为
0.0.0.0/0
(无类域间路由表示法)。当数据包的目标地址无法匹配路由表中其他常规条目时,就会通过默认路由转发。 - 默认网关:默认路由中的 “下一跳” 地址,即数据包发送的目标网关设备。所有需通过默认路由转发的数据包,都会先发送到默认网关,再由网关进一步处理(如连接公网)。
2. 工作逻辑示例
假设某主机 IP 为 192.168.3.10
,属于子网 192.168.3.0/24
。当该主机访问外网(如 www.example.com
,IP 为公网地址)时:
- 主机先检查路由表,发现目标地址不匹配子网内其他路由条目;
- 触发默认路由(
0.0.0.0/0
),将数据包发送到默认网关(如192.168.3.2
); - 由默认网关负责后续转发(如通过 NAT 转换访问公网)。
3. Linux 命令示例
- 添加默认路由:
-
含义:设置IP ROUTE ADD DEFAULT VIA 192.168.3.2
192.168.3.2
为默认网关,所有匹配默认路由的数据包均通过该网关转发。 - 临时配置默认网关(简易方式):
-
效果与前者类似,用于快速配置默认网关,适用于临时网络调试场景。route add default GATEWAY 192.168.3.2
3.rtable
在路由选择子系统中找到匹配条目,FIB_LOOCUP()将创建一个包含各种路由选择参数FIB_RESULT对象,并返回0。RTABLE结构如下:
一、结构体功能与通俗理解
1.
rtable
结构体
- 功能:路由选择子系统匹配到路由条目后,存储数据包转发所需的最终参数,是转发动作的 “执行指南”。
- 通俗理解:类似 “快递分拣结果单”。当快递(数据包)需要派送时,分拣系统(路由选择子系统)匹配路线后基于fib_result的匹配结果,记录 “从哪个快递点(接口)发出”“是否需要中转站点(网关)” 等关键信息,确保快递按正确路径发出。
2.
fib_result
结构体
- 功能:在路由表中查找匹配条目时,记录路由匹配的中间结果,是路由查找过程的 “信息记录员”。例如:
中间结果(字段) 说明 具体例子 prefixlen
目标网络匹配的子网掩码长度 匹配到 192.168.1.0/24
时,prefixlen
记录为24
,表示子网掩码是255.255.255.0
。type
数据包处理方式(如转发、本地接收) 若目标 IP 是外网地址(如 203.0.113.10
),type
记录为 “转发”;若目标 IP 是本地主机地址(如192.168.1.5
),type
记录为 “本地接收”。fib_table
指向匹配的路由表 系统存在 “主路由表”,当路由匹配来自该表时, fib_table
指向主路由表对应的结构体。nh_seq
下一跳路由标识(关联下一跳信息) 路由匹配到需通过网关 192.168.1.1
转发,nh_seq
记录该下一跳路由的唯一标识,用于后续获取网关地址。
- 通俗理解:类似 “快递地址匹配记录”。当快递(数据包)要派送时,先查地址匹配情况,
fib_result
记录 “地址属于哪个区域(子网掩码长度)”“该区域快递该怎么处理(转发 / 接收)” 等信息,为后续分拣(转发)提供依据。
二、核心字段通俗讲解
rtable
关键字段:
rt_type
:
标识路由类型,比如 “直接派送”(直连路由)或 “需中转派送”(通过网关路由)。就像快递是直接从本地网点发出,还是要先送到中转站再发。rt_use_gateway
:
标记是否需要经过网关。值为1
表示 “需中转站点”(下一跳是网关),值为0
表示 “直接从本地网点发”(直连路由)。
fib_result
关键字段:
type
:
决定数据包的处理方式,比如 “转发出去” 或 “本地接收”。类似快递地址匹配后,判断是 “继续运输” 还是 “本地签收”。prefixlen
:
记录子网掩码长度(如24
对应255.255.255.0
),用于判断目标 IP 属于哪个子网。好比通过地址编码(如区号)判断快递属于哪个大区域。fib_table
:
指向匹配的路由表(FIB 表),多路由表场景下,明确当前路由结果来自哪张表。类似快递分拣时,确认是按 “普通件路由表” 还是 “加急件路由表” 匹配的结果。
二、FIB表
网络栈最重要目标是转发流量,对于Internet骨干中的核心路由器来说尤其如此。Linux IP层被称为路由选择子系统,负责转换数据包和维护转发数据包。接收或发送每个数据包时,都必须在路由选择子系统中查找。
路由选择子系统的主要数据结构是路由选择表,由结构fib_table表示。
每个路由选择条目都包含一个fib_info对象,其中存储最重要的路由选择条目参数,并非所有参数都存储在这里。fib_info对象由方法fib_create_info()创建,存储在散列表fib_info_hash中。
fib_info
并不完全等同于路由表中的一行(一个条目),在某些情况下它可以代表多个行。路由表条目与
fib_info
的一般对应关系通常而言,路由表中的每一行(路由条目)都有特定的转发规则,包含目标网络、子网掩码、下一跳地址、出接口等信息。
fib_info
是用来存储这些转发规则里关键参数(例如下一跳地址、出接口)的结构体。在简单场景中,一个路由条目对应一个fib_info
,因为每个条目可能有独特的转发规则。多个路由条目共享
fib_info
的情况当多个路由条目指向不同子网,但它们的转发参数(如下一跳地址和出接口)完全相同时,这些不同的路由条目就可以共享同一个
fib_info
。在这种情况下,一个fib_info
就代表了多个路由表中的行。举例说明
就像之前提到的子网 A(10.0.0.0/24)和子网 B(10.0.1.0/24),它们都通过网关 10.0.0.1 和接口 eth0 转发。这两个子网对应路由表中的两行,但它们共享同一个
fib_info
,这个fib_info
存储着下一跳地址 10.0.0.1 和出接口 eth0 这些转发信息。这就表明,一个fib_info
代表了这两个不同的路由条目。设计意义
这种设计能有效减少内存占用,避免重复存储相同的转发参数。如果每个路由条目都独立存储相同的转发信息,会造成内存的浪费。通过让多个路由条目共享
fib_info
,可以提高内存使用效率,同时不影响路由表的功能和路由查找的准确性。
目标网络 子网掩码 下一跳 出接口 fib_node fib_info 10.0.0.0 255.255.255.0 10.0.0.1 eth0 fib_node_A fib_info_1 10.0.1.0 255.255.255.0 10.0.0.1 eth0 fib_node_B fib_info_1
综上所述,在 Linux 内核中,路由表的一行信息由fib_node
和fib_info
共同表示:fib_table
作为路由表的管理者,通过哈希表组织fib_node
,每个fib_node
代表一个子网并聚合指向该子网的所有路由条目(比如目的地址同一子网的路由表中的不同行);而fib_info
存储具体的转发参数(如下一跳、出接口),一个fib_node
可关联多个fib_info
以支持同一子网下的不同转发策略。路由策略的实现依赖于这些结构的协作:fib_table
提供全局路由表框架,fib_node
通过子网聚合简化路由查找,fib_info
则根据策略需求为同一子网的不同路由条目提供差异化的转发指令,从而实现灵活的数据包转发逻辑。
由路由表fib_table根据ip定位到fib_info具体条目信息的示例流程如下:
一、核心流程
- 调用路由查找函数:
内核通过fib_lookup
函数执行路由查找,输入参数包括fib_table
实例、目标 IP 等信息。- 哈希定位条目分组:
利用目标 IP 计算哈希值,通过fib_table
的tb_data
字段(管理路由条目数据)快速定位到哈希表中的条目分组。- 前缀匹配路由条目:
遍历分组内的路由条目,对比目标 IP 与路由条目的子网前缀,找到匹配的条目。- 获取
fib_info
:
从匹配的路由条目中提取fib_info
,其包含路由的核心参数(如下一跳、出接口等)。二、关键源码示例(基于 Linux 内核
net/ipv4/fib_semantics.c
)相当于通过第二个参数中的ip在第一个参数的路由表中查找,结果存储在第三个参数中。
// 核心查找函数:fib_lookup(简化逻辑) int fib_lookup(struct fib_table *table, const struct flowi4 *flp, struct fib_result *res) { // 1. 计算目标 IP 的哈希值,定位 fib_table 的 tb_data 分组 unsigned int hash = fib4_hash(flp->daddr, table->tb_id); struct fib_node *node = rcu_dereference(table->tb_data[hash]); // 2. 遍历分组内的路由节点,匹配目标 IP if (node) { struct fib_node *n; hlist_for_each_entry(n, &node->fn_hlist, fn_hlist) { // 检查路由前缀是否匹配目标 IP if (fib4_prefix_match(n, flp->daddr)) { // 3. 匹配成功,获取 fib_info res->fib_info = n->fn_info; return 0; // 查找成功 } } } return -ESRCH; // 未找到匹配条目 }
三、字段关联说明
fib_table->tb_data
:作为路由条目的数据载体,通常结合哈希表管理路由条目分组。内核通过目标 IP 的哈希值,快速定位到tb_data
中对应的条目分组,大幅提升查找效率。- 路由条目与
fib_info
:每个路由条目(如struct fib_node
)关联一个fib_info
实例,通过匹配路由条目,最终获取fib_info
中存储的路由核心参数。
在 Linux 内核的路由系统中,fib_table
结构体的 tb_data
和 tb_id
字段在路由表的组织和查找过程中扮演着关键角色。
1.
tb_data
的作用:哈希表的表头数组
tb_data
是fib_table
结构体中的一个字段,其定义通常为:struct fib_table { // ... struct fib_node __rcu *tb_data[]; // ... };
- 哈希表的组织:
tb_data
是一个柔性数组(flexible array),其本质是一个指向fib_node
结构体的指针数组。这个数组构成了路由表的哈希表结构,每个数组元素对应一个哈希桶(hash bucket)。- 路由查找的定位:在路由查找时(如
fib_lookup
函数),内核会根据目标 IP 地址计算哈希值,然后通过tb_data[hash]
定位到对应的哈希桶。该桶中可能包含一个或多个fib_node
节点(通过链表连接),这些节点代表具有相同或相似前缀的路由条目。因此,
tb_data
是路由表哈希表的 “表头数组”,每个元素指向一个哈希桶的头部,用于快速定位路由条目。
2.
tb_id
的作用:路由表的唯一标识与哈希计算
tb_id
是fib_table
结构体中的另一个关键字段,其作用主要有两点:(1)路由表的唯一标识
- 内核中可以存在多个路由表(如主表、本地表、默认表等),每个路由表通过
tb_id
进行唯一标识。例如:
- 主路由表的
tb_id
通常为RT_TABLE_MAIN
(值为254
)。- 本地路由表的
tb_id
为RT_TABLE_LOCAL
(值为255
)。- 不同用途的路由表通过
tb_id
区分,确保路由查找时不会混淆。(2)参与哈希值计算
在计算目标 IP 的哈希值时,
tb_id
会与目标 IP 地址一起作为输入。例如,fib4_hash
函数的实现可能如下:
- 这里将目标 IP(
daddr
)与tb_id
进行异或运算,再对哈希表大小取模,生成最终的哈希值。- 作用:通过将
tb_id
纳入哈希计算,不同路由表的路由条目会被分配到不同的哈希桶中,避免了不同表之间的哈希冲突,确保路由查找的准确性和高效性。
3. 主路由表与本地路由表的区别
(1)用途不同
static inline unsigned int fib4_hash(__be32 daddr, int tb_id) { return (daddr ^ (tb_id << 24)) % FIB_TABLE_HASHSZ; }
- 主路由表(
RT_TABLE_MAIN
):
- 存储常规的路由条目,用于数据包的常规转发。
- 例如,通过
ip route
命令添加的路由通常存储在主表中。- 本地路由表(
RT_TABLE_LOCAL
):
- 存储与本地主机直接相关的路由,例如:
- 本地接口的 IP 地址。
- 广播地址和多播地址。
- 主机路由(如
127.0.0.1/8
)。- 这些路由用于处理本地主机直接接收或发送的数据包。
(2)优先级不同
- 本地路由表的优先级通常高于主路由表。在路由查找时,内核会优先查找本地路由表,若未找到匹配项,再查找主路由表。
- 这是因为本地路由表中的条目与主机直接相关,需要更高效的匹配。
(3)路由条目的类型
- 主路由表中的条目通常是网络路由(如
192.168.1.0/24
),指定数据包的下一跳和出接口。- 本地路由表中的条目更多是主机路由或本地接口相关的特殊路由,用于本地通信。
4. 总结
tb_data
:是路由表哈希表的表头数组,每个元素指向一个哈希桶,用于快速定位fib_node
节点。tb_id
:
- 作为路由表的唯一标识,区分不同用途的路由表(如主表和本地表)。
- 参与哈希值计算,确保不同路由表的条目分配到不同的哈希桶,避免冲突。
- 主表与本地表:
- 主表用于常规路由转发,本地表用于本地主机相关的特殊路由。
- 本地表的优先级更高,且包含主机地址、广播地址等特殊条目。
三、ICMP重定向消息
1.重定向的定义和类型
有时路由选择条目可能是次优的,在这种情况下将发送ICMP重定向消息。次优条目的主要判断标准是:输入设备和输出设备相同。
- 原理:路由器接收数据包时,会检查入接口(输入设备)和出接口(输出设备)。若两者相同,说明数据包进入路由器的接口,又要从同一接口转发出去,这违背了最优路由原则(正常应通过不同接口转发以抵达目标)。
- 意义:此时路由器判定该路由为次优,发送 ICMP 重定向消息,通知源主机更新路由表,避免后续流量继续使用次优路径,确保网络传输效率。
ICMP重定向消息的代码有4种:
ICMP_REDIR_NET(重定向网络)
- 作用:当路由器发现主机发往某一网络的数据包应通过更优路由转发时,向主机发送该消息,告知主机更新到目标网络的路由。
- 示例:主机 A 连接路由器 R1,R1 连接网络 X 和网络 Y。若主机 A 向网络 Y 发送数据包时选择了次优路由,R1 发现更优路径后,会向 A 发送 ICMP_REDIR_NET,通知 A 更新到网络 Y 的路由。
ICMP_REDIR_HOST(重定向主机)
- 作用:针对单一主机的重定向。当路由器发现主机发往某一目标主机的数据包应通过更优路由转发时,发送此消息。
- 示例:主机 B 向目标主机 C 发送数据包,经过路由器 R2。R2 发现 B 到 C 的路径非最优(如应通过另一路由器转发),则向 B 发送 ICMP_REDIR_HOST,告知 B 更新到 C 的主机路由。
ICMP_REDIR_NETTOS(针对 TOS 重定向网络)
- 作用:结合 “服务类型(TOS)” 参数,对特定服务类型的网络流量重定向。若路由器发现某网络流量(带特定 TOS)应通过更优路由转发,发送此消息。
- 示例:视频流(设 TOS 标记为 “低延迟”)从主机 D 发往网络 Z,路由器 R3 发现该视频流应通过支持低延迟的路由转发,便发送 ICMP_REDIR_NETTOS,让 D 按新路由发送同 TOS 的网络流量。
ICMP_REDIR_HOSTTOS(针对 TOS 重定向主机)
- 作用:针对带特定 TOS 的主机流量重定向。当路由器发现发往某主机的流量(带特定 TOS)存在更优路由时触发。
- 示例:主机 E 向主机 F 发送带 “高吞吐量” TOS 的数据,路由器 R4 发现该流量应通过更优路径转发,便发送 ICMP_REDIR_HOSTTOS,告知 E 更新到 F 的、适配该 TOS 的主机路由。
例如重定向到主机的案例:
案例中,AMD 服务器因配置路由,将本可直接发给苹果笔记本电脑(192.168.2.9)的数据包,先发给 Windows 服务器(192.168.2.10)。Windows 服务器作为中间设备,发现 AMD 服务器与苹果笔记本电脑在同一子网(192.168.2.x),AMD 本可直接通信,无需经自己转发。此时 Windows 服务器判定该路由为次优(输入设备和输出设备可能涉及同一网络逻辑),因此向 AMD 服务器发送 ICMP_REDIR_HOST
重定向消息,告知其更新到苹果笔记本电脑的最优路由。
2.生成ICMPv4重定向消息
存在次优路由时,将发送ICMPv4重定向消息。判断次优路由最重要的标准是:输入设备和输出设备相同。但同时还需要满足一些其他的条件。ICMPv4重定向消息的生成过程包括两个阶段:__mkroute_input()设置标志RTCF_DORIECT-------->ip_forward()发送ICMPv4定向消息。
ICMPv4 重定向消息生成分为两个核心阶段:
__mkroute_input
阶段:检查路由信息,判断是否满足次优路由条件,设置关键标志RTCF_DORICECT
,为后续重定向做准备。ip_forward
阶段:基于__mkroute_input
设置的标志,最终触发 ICMPv4 重定向消息发送。
__mkroute_input
函数重要流程(典型逻辑)
- 参数解析与初始化:
- 接收数据包缓冲区
skb
、路由结果res
、输入设备in_dev
等参数,获取数据包的路由信息、设备信息。- 次优路由条件检查:
- 输入 / 输出设备检查:验证输入设备
in_dev
和路由输出设备(通过res
获取)是否相同。这是次优路由的核心判断标准,若相同,说明数据包从某接口进入又从同一接口转发,违背最优路由原则。- 其他条件校验:可能包括检查路由类型、目标地址有效性等,过滤无效场景(如本地环回流量)。
- 设置重定向标志:
- 若满足次优路由条件,设置
res->rt_flags
中的RTCF_DORICECT
标志。该标志会被后续ip_forward
函数检测,触发 ICMP 重定向消息发送。- 结果返回:
- 处理完成后,返回结果给调用层,驱动后续流程(如进入
ip_forward
阶段)。
3.接收ICMPv4重定向消息
仅当ICMPv4重定向消息通过一些完整性检查后,才会对其进行处理,处理ICMPv4重定向消息的工作是由__ip_do_redirect()完成。
__ip_do_redirect 函数功能
__ip_do_redirect
是内核中处理接收的 ICMPv4 重定向消息的核心函数。它的主要作用是:
- 对 ICMPv4 重定向消息进行完整性校验,过滤无效或恶意的重定向请求。
- 根据 ICMPv4 重定向消息的代码(如重定向网络、主机等),执行对应逻辑,更新本地路由表或网络设备配置,使系统按照更优路由转发后续数据包。
__ip_do_redirect 函数重要流程解析
参数提取与初始化:
- 从数据包缓冲区
skb
中提取重定向消息携带的新网关new_gw
、旧网关old_gw
,以及关联的网络设备dev
、输入设备in_dev
等信息,为后续校验做准备。重定向代码分类(预留扩展):
- 通过
switch
语句匹配 ICMPv4 重定向消息的代码(如ICMP_REDIR_NET
、ICMP_REDIR_HOST
等)。当前代码中case
后仅break
,但预留了未来针对不同代码的差异化处理逻辑。网关一致性校验:
- 检查路由表项
rt->rt_gateway
是否与old_gw
不一致(if (rt->rt_gateway != old_gw)
)。若不一致,说明重定向消息来源可能不可靠,直接返回,拒绝处理。输入设备有效性检查:
- 通过
__in_dev_get_rcu(dev)
获取输入设备in_dev
,若获取失败(!in_dev
),说明设备状态异常,直接返回,不处理重定向。网络场景合法性校验:
- 检查新网关
new_gw
是否为多播地址(ip4_is_multicast(new_gw)
)、广播地址(ip4_is_broadcast(new_gw)
)或特殊地址(如零地址)。若符合任意一种情况,通过goto reject_redirect
拒绝重定向,因为这些场景不符合正常重定向逻辑。链路层兼容性检查:
- 对于共享介质网络(
IN_DEV_SHARED_MEDIA(in_dev)
,如以太网),检查新网关new_gw
是否与设备在同一链路上(!inet_addr_onlink(in_dev, new_gw, old_gw)
)。若不在同一链路,拒绝重定向。邻居子系统处理:
- 通过
__ip_neigh_lookup
在邻居子系统中查找新网关new_gw
。若找到对应的邻居表项,说明新网关是可达的,后续可基于此更新路由,完成重定向逻辑(如更新数据包转发路径)。整个函数通过多层校验,确保 ICMPv4 重定向消息的合法性和合理性,避免因错误或恶意重定向导致网络转发异常。