内核如何管理 local(主机)路由表

今天遇到一个有意思的问题:
Q:Linux系统在内核初始化完成后,默认创建了多少个路由表?
这个问题在技术社区中存在不少争议,无论是询问AI、Google搜索还是查阅博客,都能得到各种不同的答案。
问题的核心争议点在于:
内核如何管理主机路由(local路由)对应的本地路由表。
下面让我们逐步分析不同的观点:

step 1:咨询deepseek,系统创建了 4 个路由表

DeepSeek不仅给出了结论,还详细说明了分析过程,并提供了验证依据。

同时提供了验证方法。

1
cat /etc/iproute2/rt_tables

step 2. 手动测试发现,系统只创建了 local/main两个路由表

基于Deepseek的答案进行进一步验证,通过IP命令检查指定路由表可发现:

  • 实际仅存在local(255)和main(254)两个路由表
  • unspec(253)表实际不存在,该ID仅为保留编号
  • 表0是IPv4和IPv6所有路由的汇总视图,并非独立路由表

最终结论:系统仅创建了local和main两个路由表

step 3. 分析内核源代码

这两种观点都很有道理,论据充分。真相最终还是要看代码实现,让我们深入内核源码一探究竟。
以路由查找函数fib_lookup为例,内核实际上维护了两套不同的函数实现。
具体采用哪套方案,取决于编译时是否启用了CONFIG_IP_MULTIPLE_TABLES选项。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
292 #ifndef CONFIG_IP_MULTIPLE_TABLES
...
316 static inline int fib_lookup(struct net *net, const struct flowi4 *flp,
317 struct fib_result *res, unsigned int flags)
318 {
...
364 #else /* CONFIG_IP_MULTIPLE_TABLES */
...
371 int __fib_lookup(struct net *net, struct flowi4 *flp,
372 struct fib_result *res, unsigned int flags);
373
374 static inline int fib_lookup(struct net *net, struct flowi4 *flp,
375 struct fib_result *res, unsigned int flags)
376 {

不支持多路由表

#ifndef CONFIG_IP_MULTIPLE_TABLES

代码分析显示,内核路由查找函数fib_llookup的实现取决于编译选项CONFIG_IP_MULTIPLE_TABLES。当该选项未启用时,内核仅查询RT_TABLE_MAIN路由表。
由此可以确定:在此配置下,系统仅维护一个主路由main表。

初始化

1
2
3
4
5
==>  static int __net_init fib_net_init(struct net *net)
==> ==> ip_fib_net_init(net);
==> ==> ==> fib4_rules_init(net);
==> ==> ==> ==> main_table = fib_trie_table(RT_TABLE_MAIN, NULL);
==> ==> ==> ==> local_table = fib_trie_table(RT_TABLE_LOCAL, main_table);

这里重点关注函数fib4_rules_init
从代码调用看,确实创建了两个路由表:local和main。
但在创建main表时候,我们使用的一个alias的参数,并把main表当做参数传递过去的。

1
2
3
4
5
6
7
8
9
10
50 #ifndef CONFIG_IP_MULTIPLE_TABLES
51
52 static int __net_init fib4_rules_init(struct net *net)
53 {
54 struct fib_table *local_table, *main_table;
55
56 main_table = fib_trie_table(RT_TABLE_MAIN, NULL);
57 if (!main_table)
58 return -ENOMEM;
59

路由表初始化细节

fib4_rules_init函数中,我们可以看到路由表的创建过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static int __net_init fib4_rules_init(struct net *net)
{
struct fib_table *local_table, *main_table;

main_table = fib_trie_table(RT_TABLE_MAIN, NULL);
if (!main_table)
return -ENOMEM;

local_table = fib_trie_table(RT_TABLE_LOCAL, main_table);
if (!local_table) {
fib_trie_table_free(main_table);
return -ENOMEM;
}

rcu_assign_pointer(net->ipv4.fib_main, main_table);
rcu_assign_pointer(net->ipv4.fib_local, local_table);
return 0;
}

关键发现

  1. 两个独立的路由表:系统确实创建了local(255)和main(254)两个路由表
  2. 表关系:local表基于main表创建,形成层级关系
  3. 表0:不是独立路由表,而是所有路由的汇总视图
  4. 表253:unspec表只是保留编号,实际不存在

路由表功能解析

local路由表 (RT_TABLE_LOCAL = 255)

  • 管理主机本地路由(loopback、设备地址等)
  • 优先级最高,用于本地通信
  • 包含127.0.0.1/8、设备IP地址等

main路由表 (RT_TABLE_MAIN = 254)

  • 管理常规路由规则
  • 包含默认路由、静态路由等
  • 优先级次于local表

验证方法

查看路由表配置

1
cat /etc/iproute2/rt_tables

检查特定路由表

1
2
3
4
5
6
7
8
# 检查local路由表
ip route show table local

# 检查main路由表
ip route show table main

# 检查表0(汇总视图)
ip route show table 0

验证表253不存在

1
2
3
# 尝试查看表253会报错
ip route show table 253
# 输出:Error: argument "253" is wrong: table id value is invalid

总结

通过深入分析内核源代码,我们得出了明确的结论:

Linux内核在初始化完成后,默认只创建了两个路由表:

  1. local表 (255):管理主机本地路由
  2. main表 (254):管理常规路由规则

其他表编号(如0、253)并非独立的路由表:

  • 表0是所有路由的汇总视图
  • 表253是保留编号,实际不存在

这个问题的争议源于对路由表编号和功能的误解。正确的理解是:内核通过local和main两个表的协作,实现了完整的路由功能。

技术要点

  1. 编译选项影响CONFIG_IP_MULTIPLE_TABLES选项决定了是否支持多路由表
  2. 表层级关系:local表基于main表创建,形成路由查找的优先级关系
  3. 实际验证:通过iproute2工具可以验证路由表的实际存在状态

这个问题的分析过程展示了内核源码分析的重要性,也提醒我们在技术讨论中要基于事实和代码验证,而不是依赖表面的数字统计。