一篇讲解IPv4路由表

如下IP命令添加路由表项 , 默认情况下路由添加在main路由表中:
# ip route add 192.2.0.0/16 via 192.168.1.106## ip route show table main 192.2.0.0/16 via 192.168.1.106 dev ens32也可指定路由表 , 如下在local/default表中添加相同目的网络 , 但是网关不同的路由项:
# ip route add 192.2.0.0/16 via 192.168.1.1 table local## ip route show table local192.2.0.0/16 via 192.168.1.1 dev ens32 ### ip route add 192.2.0.0/16 via 192.168.1.200 table default## ip route show table default192.2.0.0/16 via 192.168.1.200 dev ens32 下面使用ping命令测试到目的网段192.2.0.0/16 , 可见其使用的是local表中的路由项 , 而不是main表中项:
# tcpdump -i ens32 host 192.2.0.1 -n -e &// 后台启动tcpdump## ping 192.2.0.1PING 192.2.0.1 (192.2.0.1) 56(84) bytes of data.23:05:53.330337 00:0c:29:1e:20:e3 > 00:90:27:fe:c9:34, ethertype IPv4 (0x0800), length 98: 192.168.1.140 > 192.2.0.1: ICMP echo request, id 64942, seq 1, length 64 # # ip neigh show192.168.1.1 dev ens32 lladdr 00:90:27:fe:c9:34 REACHABLE以下删除local表中添加的路由 , 再执行ping操作 , 这次是main表中的路由生效 , 网关使用的是192.168.1.106:
# ip route del 192.2.0.0/16 via 192.168.1.1 table local## ping 192.2.0.1PING 192.2.0.1 (192.2.0.1) 56(84) bytes of data.23:21:47.752867 00:0c:29:1e:20:e3 > 00:60:e0:6f:9c:e3, ethertype IPv4 (0x0800), length 98: 192.168.1.140 > 192.2.0.1: ICMP echo request, id 528, seq 1, length 64## ip neigh show192.168.1.106 dev ens32 lladdr 00:60:e0:6f:9c:e3 STALE 
最后 , 删除main中的路由 , 
# ip route del 192.2.0.0/16 via 192.168.1.106 table main## ping 192.2.0.1PING 192.2.0.1 (192.2.0.1) 56(84) bytes of data.23:50:55.690358 00:0c:29:1e:20:e3 > 00:60:e0:85:7a:06, ethertype IPv4 (0x0800), length 98: 192.168.1.140 > 192.2.0.1: ICMP echo request, id 2079, seq 1, length 64# # ip neigh 192.168.1.200 dev ens32 lladdr 00:60:e0:85:7a:06 STALE路由表创建
在添加表项时 , 没有指定路由表ID , 或者指定的表ID等于0 , 内核使用main表RT_TABLE_MAIN , 函数fib_trie_table分配一个新的fib_table结构 , 代表一个新的路由表 。对于main表 , 将其制赋值给命名空间中的fib_main成员 。最后将其链接到哈希桶fib_table_hash的对应链表中 。
如果指定的路由表ID等于RT_TABLE_LOCAL , 但是此命名空间中没有配置过IPv4策略路由 , 也使用main路由表 , 作为alias , 参见fib_trie_table 。这种情况下 , fib_new_table会在调用自身 , 参数ID使用RT_TABLE_MAIN , 获取main表的结构 , 赋值与alias 。
对于default表 , 目前不太清楚其使用情况 , 在创建之后 , 内核将其赋值给命名空间的fib_default成员 。
struct fib_table *fib_new_table(struct.NET *net, u32 id){struct fib_table *tb, *alias = NULL;unsigned int h;if (id == 0)id = RT_TABLE_MAIN;tb = fib_get_table(net, id);if (tb)return tb;if (id == RT_TABLE_LOCAL && !net->ipv4.fib_has_custom_rules)alias = fib_new_table(net, RT_TABLE_MAIN);tb = fib_trie_table(id, alias);if (!tb)return NULL;switch (id) {case RT_TABLE_MAIN:rcu_assign_pointer(net->ipv4.fib_main, tb);break;case RT_TABLE_DEFAULT:rcu_assign_pointer(net->ipv4.fib_default, tb);break;default:break;}h = id & (FIB_TABLE_HASHSZ - 1);hlist_add_head_rcu(&tb->tb_hlist, &net->ipv4.fib_table_hash[h]);return tb;对于main路由表 , 以及其它路由表 , fib_trie_table的参数alias为空;但是对于local路由表 , alias执向main表结构 , 就不用重新分配trie结构了 。对于所有的路由表 , 都需要分配一个fib_table结构 。
对于local路由表 , 其数据字段指向main路由表的数据字段 。可见local表不是一个完全单独的路由表 , 其数据与main表是公用的 。所以local路由表不需要进行以下对数据字段的初始化操作 。
struct fib_table *fib_trie_table(u32 id, struct fib_table *alias){struct fib_table *tb;struct trie *t;size_t sz = sizeof(*tb);if (!alias)sz += sizeof(struct trie);tb = kzalloc(sz, GFP_KERNEL);if (!tb)return NULL;tb->tb_id = id;tb->tb_num_default = 0;tb->tb_data = https://www.isolves.com/it/wl/zs/2022-08-13/(alias ? alias->__data : tb->__data);if (alias)return tb;t = (struct trie *) tb->tb_data;t->kv[0].pos = KEYLENGTH;t->kv[0].slen = KEYLENGTH;#ifdef CONFIG_IP_FIB_TRIE_STATSt->stats = alloc_percpu(struct trie_use_stats);if (!t->stats) {kfree(tb);tb = NULL;}#endifreturn tb;路由查找如下内核的路由查询入口函数fib_lookup , 可见其查询顺序为:路由策略->main路由表->default路由表 。对local路由表的查询包含在main路由表查询中 。


推荐阅读