Linux 路由表建立过程分析
Linux 路由表建立过程深度分析
一、工作原理
路由表是 Linux 网络栈的核心组件,用于决策数据包的转发路径。其建立过程分为三个层次:
-
内核初始化阶段
- 自动创建本地路由表(
local
表) - 为每个网络接口生成直连路由(
scope link
) - 本地地址路由(
scope host
)
- 自动创建本地路由表(
-
用户空间配置
- 静态路由:通过
ip route
或配置文件添加 - 动态路由:通过守护进程(如 BIRD/FRR)注入
- 静态路由:通过
-
内核决策机制
- 基于最长前缀匹配(LPM)算法
- 多路由表支持(策略路由)
- 路由缓存(已合并到主表)
二、实现机制与代码框架
核心路径: net/ipv4/route.c
和 net/ipv4/fib_*.c
详细流程解析:
-
用户空间发起请求
- 通过
ip route add
命令或Netlink API发送路由添加请求 - 消息类型:
RTM_NEWROUTE
(netlink消息头中的nlmsg_type)
- 通过
-
内核接收处理
- 入口函数:
rtnetlink_rcv()
(net/core/rtnetlink.c)
static void rtnetlink_rcv(struct sk_buff *skb) {netlink_rcv_skb(skb, &rtnetlink_rcv_msg); }
- 入口函数:
-
路由添加流程
- 核心函数:
rtm_to_fib_config()
(net/ipv4/fib_frontend.c)
static int rtm_to_fib_config(struct net *net, struct sk_buff *skb,struct nlmsghdr *nlh, struct fib_config *cfg) {// 解析netlink属性:目标网络、网关、设备等 }
- 核心函数:
-
路由表操作
- 路由表选择:
fib_get_table()
(net/ipv4/fib_trie.c)
struct fib_table *fib_get_table(struct net *net, u32 id) {// 根据ID获取路由表(main表ID=254) }
- 路由表选择:
-
Trie树插入
- 核心算法:
trie_insert()
(net/ipv4/fib_trie.c)
static int fib_insert_node(struct trie *t, struct key_vector *tp,struct fib_alias *new, t_key key) {// 在LC-Trie中插入新节点// 处理节点分裂和合并 }
- 核心算法:
-
路由信息更新
- 创建路由信息结构:
fib_create_info()
(net/ipv4/fib_semantics.c)
struct fib_info *fib_create_info(struct fib_config *cfg) {// 分配fib_info结构// 设置下一跳(fib_nh)、作用域(scope)等 }
- 创建路由信息结构:
-
通知机制
- 路由更新通知:
rtmsg_fib()
(net/ipv4/fib_frontend.c)
static void rtmsg_fib(int event, __be32 key, struct fib_alias *fa,int dst_len, u32 tb_id, struct nl_info *info) {// 通过netlink发送RTM_NEWROUTE通知 }
- 路由更新通知:
关键代码流程:
- 路由添加入口:
rtnetlink_rcv()
(net/core/rtnetlink.c
) - 路由表操作:
fib_table_insert()
(net/ipv4/fib_trie.c
) - Trie 树操作:
trie_insert()
(net/ipv4/fib_trie.c
) - 路由查找:
ip_route_input_slow()
(net/ipv4/route.c
)
三、核心数据结构
// 路由表抽象(include/net/ip_fib.h)
struct fib_table {u32 tb_id; // 路由表ID(e.g. RT_TABLE_MAIN)int (*tb_lookup)(...); // 查找函数struct trie *tb_data; // Trie 树根节点
};// Trie 树节点(net/ipv4/fib_trie.c)
struct tnode {t_key key; // 关键键值unsigned long pos; // 位位置struct tnode __rcu *child[0]; // 子节点
};// 路由信息(include/net/ip_fib.h)
struct fib_info {struct hlist_node fib_hash;int fib_treeref;u32 fib_prefsrc; // 首选源地址unsigned char fib_scope; // 作用域struct fib_nh fib_nh[0]; // 下一跳数组
};// 下一跳信息
struct fib_nh {struct net_device *nh_dev; // 出口设备u32 nh_gw; // 网关IP
};
四、路由添加实例代码
#include <arpa/inet.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <net/if.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>// 创建 netlink 路由消息
void add_route(const char *dst, int prefixlen, const char *gw, const char *dev) {int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);struct {struct nlmsghdr nh;struct rtmsg rt;char buf[1024];} req = {0};// 初始化 netlink 头req.nh.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));req.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL;req.nh.nlmsg_type = RTM_NEWROUTE;// 设置路由属性req.rt.rtm_family = AF_INET;req.rt.rtm_dst_len = prefixlen;req.rt.rtm_table = RT_TABLE_MAIN;req.rt.rtm_scope = RT_SCOPE_UNIVERSE;req.rt.rtm_protocol = RTPROT_STATIC;req.rt.rtm_type = RTN_UNICAST;// 添加目标网络struct rtattr *rta = (struct rtattr *)((char *)&req + NLMSG_ALIGN(req.nh.nlmsg_len));rta->rta_type = RTA_DST;rta->rta_len = RTA_LENGTH(4);inet_pton(AF_INET, dst, RTA_DATA(rta));req.nh.nlmsg_len += rta->rta_len;// 添加网关或设备if (gw) {rta = (struct rtattr *)((char *)&req + NLMSG_ALIGN(req.nh.nlmsg_len));rta->rta_type = RTA_GATEWAY;rta->rta_len = RTA_LENGTH(4);inet_pton(AF_INET, gw, RTA_DATA(rta));req.nh.nlmsg_len += rta->rta_len;} else {rta = (struct rtattr *)((char *)&req + NLMSG_ALIGN(req.nh.nlmsg_len));rta->rta_type = RTA_OIF;rta->rta_len = RTA_LENGTH(4);int ifindex = if_nametoindex(dev);memcpy(RTA_DATA(rta), &ifindex, 4);req.nh.nlmsg_len += rta->rta_len;}// 发送到内核send(sock, &req, req.nh.nlmsg_len, 0);close(sock);
}int main() {// 添加路由:目标 10.1.0.0/24 通过网关 192.168.1.1add_route("10.1.0.0", 24, "192.168.1.1", NULL);return 0;
}
编译命令:gcc route_add.c -o route_add
五、常用工具与调试命令
-
路由管理工具
# 查看路由表 ip route show table main ip -6 route # IPv6路由# 添加/删除路由 ip route add 192.168.2.0/24 via 10.0.0.1 dev eth0 ip route del default via 192.168.1.1# 策略路由 ip rule add from 192.168.1.100 lookup custom_table
-
调试命令
# 内核路由日志 dmesg | grep "IPv4:"# 路由查找模拟 ip route get 8.8.8.8 from 192.168.1.100 iif eth0# 路由表统计 cat /proc/net/stat/rt_cache# Netlink 监控 nlmon # 需要加载 nlmon 驱动
-
动态调试
# 启用内核调试 echo 8 > /proc/sys/kernel/printk echo "file net/ipv4/* +p" > /sys/kernel/debug/dynamic_debug/control# Ftrace 跟踪 echo function > /sys/kernel/tracing/current_tracer echo fib_table_lookup > /sys/kernel/tracing/set_ftrace_filter cat /sys/kernel/tracing/trace_pipe
####六、关键调试技巧
-
路由查找问题
- 检查
ip route get
输出是否匹配预期 - 验证反向路径过滤:
sysctl net.ipv4.conf.all.rp_filter
- 检查路由表ID:
ip route show table all
- 检查
-
路由表不一致
- 比较
ip route
和cat /proc/net/route
(十六进制格式) - 检查路由协议守护进程状态(如 BIRD/FRR)
- 比较
-
内核崩溃分析
- 使用
crash
工具检查fib_info
结构 - 分析
oops
日志中的函数调用栈
- 使用
七、性能优化
-
路由表结构选择
- 小规模网络:Hash 表
- 大规模路由:LC-Trie(Linux 默认)
- 超大规模:硬件卸载(如 DPDK)
-
路由缓存策略
- 禁用已弃用的路由缓存:
sysctl net.ipv4.route.flush=1
- 优化垃圾回收参数:
sysctl net.ipv4.route.gc_timeout
- 禁用已弃用的路由缓存:
-
多核扩展
- 使用每核路由表(Per-CPU Routing Tables)
- 配置
RPS
(Receive Packet Steering)
通过深入理解路由表建立机制,可有效诊断网络问题(如路由黑洞、转发环路),并优化大型网络环境下的路由性能。实际应用中需结合具体场景选择静态配置或动态路由协议(如 OSPF/BGP)。