文章目录
路由的核心是路由数据库的设计和管理,这篇笔记分析了IPv4路由数据库的初始化过程和路由数据库的组织方式。主要涉及如下文件:
源代码路径 | 说明 |
---|---|
include/net/ip_fib.h | IPv4路由数据库头文件 |
core/ipv4/fib_frontend.c | IPv4路由数据库对外接口实现文件 |
数据结构
路由表: fib_table
struct fib_table {
// 将该路由表链入系统全局哈希表中
struct hlist_node tb_hlist;
// 路由表ID,内核使用ID来唯一标识路由表
u32 tb_id;
unsigned tb_stamp;
int tb_default;
// 下面为一组操作该路由表的函数,这些成员在路由表创建时被赋值
int (*tb_lookup)(struct fib_table *tb, const struct flowi *flp, struct fib_result *res);
int (*tb_insert)(struct fib_table *, struct fib_config *);
int (*tb_delete)(struct fib_table *, struct fib_config *);
int (*tb_dump)(struct fib_table *table, struct sk_buff *skb,
struct netlink_callback *cb);
int (*tb_flush)(struct fib_table *table);
void (*tb_select_default)(struct fib_table *table,
const struct flowi *flp, struct fib_result *res);
// 不同实现可以在后续放置特有的数据内容
unsigned char tb_data[0];
};
所谓的路由数据库就是一个个的路由表集合,IPv4支持用不同的数据结构组织路由表(支持哈希方式和树的方式,最新的内核版本中已经删除了哈希方式)。无论选择哪种数据结构,路由表的定义是一致的,不同数据结构组织方式的tb_data和其中的回调函数不同。
初始化
IPv4路由数据库的初始化入口是ip_fib_init()。
ip_fib_init()
void __init ip_fib_init(void)
{
// 向路由Netlink注册三个命令用于增加、删除和查询路由项
rtnl_register(PF_INET, RTM_NEWROUTE, inet_rtm_newroute, NULL);
rtnl_register(PF_INET, RTM_DELROUTE, inet_rtm_delroute, NULL);
rtnl_register(PF_INET, RTM_GETROUTE, NULL, inet_dump_fib);
// 网络命名空间级别初始化
register_pernet_subsys(&fib_net_ops);
// 监听网络设备对象状态变化事件
register_netdevice_notifier(&fib_netdev_notifier);
// 监听IP地址变化事件
register_inetaddr_notifier(&fib_inetaddr_notifier);
// 不同数据结构的路由表实现方式提供不同的fib_hash_init()接口
fib_hash_init();
}
// 网络命名空间初始化
static int __net_init fib_net_init(struct net *net)
{
int error;
// 分配哈希表用于管理所有的路由表
error = ip_fib_net_init(net);
if (error < 0)
goto out;
// 注册一个Netlink协议NETLINK_FIB_LOOKUP,让用户态可以通过该协议直接查询路由表
error = nl_fib_lookup_init(net);
if (error < 0)
goto out_nlfl;
// 不同的路由表组织方式,实现不同
error = fib_proc_init(net);
if (error < 0)
goto out_proc;
out:
return error;
out_proc:
nl_fib_lookup_exit(net);
out_nlfl:
ip_fib_net_exit(net);
goto out;
}
ip_fib_net_init()
// net->ipv4
struct netns_ipv4 {
...
struct hlist_head *fib_table_hash;
...
};
// 支持多路由表时值为256,表示哈希桶大小
#define FIB_TABLE_HASHSZ 256
static int __net_init ip_fib_net_init(struct net *net)
{
int err;
unsigned int i;
// 分配保存所有路由表的哈希链表数组
net->ipv4.fib_table_hash = kzalloc(sizeof(struct hlist_head)*FIB_TABLE_HASHSZ, GFP_KERNEL);
if (net->ipv4.fib_table_hash == NULL)
return -ENOMEM;
for (i = 0; i < FIB_TABLE_HASHSZ; i++)
INIT_HLIST_HEAD(&net->ipv4.fib_table_hash[i]);
// IPv4策略路由初始化,如向框架注册IPv4的策略路由操作集
err = fib4_rules_init(net);
if (err < 0)
goto fail;
return 0;
fail:
kfree(net->ipv4.fib_table_hash);
return err;
}
可以看出,IPv4将所有的路由表组织到一个哈希表中,该哈希表桶大小为FIB_TABLE_HASHSZ(256),该哈希表用table ID % FIB_TABLE_HASHSZ做为哈希表的索引,每个冲突链上可以有多个路由表,所以FIB_TABLE_HASHSZ并非限制内核最多只能有这么多的路由表。IPv4对所有路由表的组织如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QTR5aRYq-1638596677670)(“G:\源码理解\kernel\IPv4\reference\IPv4路由表组织方式.png”)]
路由表查找
此处介绍的是根据路由表ID查找路由表对象fib_table,并非路由查找过程。
查找: fib_get_table()
struct fib_table *fib_get_table(struct net *net, u32 id)
{
struct fib_table *tb;
struct hlist_node *node;
struct hlist_head *head;
unsigned int h;
if (id == 0) // ID为0的表默认指向main表
id = RT_TABLE_MAIN;
// 计算数组索引值
h = id & (FIB_TABLE_HASHSZ - 1);
rcu_read_lock();
// 遍历冲突链,寻找指定ID的路由表
head = &net->ipv4.fib_table_hash[h];
hlist_for_each_entry_rcu(tb, node, head, tb_hlist) {
if (tb->tb_id == id) {
rcu_read_unlock();
return tb;
}
}
rcu_read_unlock();
return NULL;
}
查找&&新建: fib_new_table()
struct fib_table *fib_new_table(struct net *net, u32 id)
{
struct fib_table *tb;
unsigned int h;
if (id == 0)
id = RT_TABLE_MAIN;
// 先查找
tb = fib_get_table(net, id);
if (tb)
return tb;
// 如果没有则新建一个,并将新建的路由表加入哈希表中,不同的路由项组织方式,该接口的实现不同
tb = fib_hash_table(id);
if (!tb)
return NULL;
h = id & (FIB_TABLE_HASHSZ - 1);
// 将新建的路由表加入哈希表中
hlist_add_head_rcu(&tb->tb_hlist, &net->ipv4.fib_table_hash[h]);
return tb;
}
外部事件监听
在初始化时,IPv4路由数据库监听了网络设备状态变化和IP地址变化事件,在这些事件发生时,它需要做出一些处理。
响应网络设备状态变化: fib_netdev_event()
static int fib_netdev_event(struct notifier_block *this, unsigned long event, void *ptr)
{
struct net_device *dev = ptr;
// 找到网络设备对象上的IPv4配置块
struct in_device *in_dev = __in_dev_get_rtnl(dev);
if (event == NETDEV_UNREGISTER) {
// 网络设备对象去注册了,清理掉所有与该网络设备相关的路由表项
fib_disable_ip(dev, 2);
return NOTIFY_DONE;
}
if (!in_dev) // 该设备没有IPv4配置块,无需处理后续路由添加删除相关事情
return NOTIFY_DONE;
switch (event) {
case NETDEV_UP:
// 网络设备使能,尝试将设备的每个IP地址添加到路由数据库中,即生成本地路由
for_ifa(in_dev) {
fib_add_ifaddr(ifa);
} endfor_ifa(in_dev);
#ifdef CONFIG_IP_ROUTE_MULTIPATH
fib_sync_up(dev);
#endif
// 刷新路由缓存
rt_cache_flush(dev_net(dev), -1);
break;
case NETDEV_DOWN:
// 网络设备关闭,清除所有相关路由信息
fib_disable_ip(dev, 0);
break;
case NETDEV_CHANGEMTU:
case NETDEV_CHANGE:
// mtu发生变化,或者网络设备名称发生了变化,刷新缓存
rt_cache_flush(dev_net(dev), 0);
break;
}
return NOTIFY_DONE;
}
响应IP地址变化: fib_inetaddr_event()
从实现可以看出,处理逻辑和网络设备状态变化的处理逻辑大体上是类似的。
static int fib_inetaddr_event(struct notifier_block *this, unsigned long event, void *ptr)
{
struct in_ifaddr *ifa = (struct in_ifaddr *)ptr;
struct net_device *dev = ifa->ifa_dev->dev;
switch (event) {
case NETDEV_UP:
fib_add_ifaddr(ifa);
#ifdef CONFIG_IP_ROUTE_MULTIPATH
fib_sync_up(dev);
#endif
rt_cache_flush(dev_net(dev), -1);
break;
case NETDEV_DOWN:
fib_del_ifaddr(ifa);
if (ifa->ifa_dev->ifa_list == NULL) {
/* Last address was deleted from this interface.
Disable IP.
*/
fib_disable_ip(dev, 1);
} else {
rt_cache_flush(dev_net(dev), -1);
}
break;
}
return NOTIFY_DONE;
}
小结
上述两个事件处理的核心思想就是当网络设备上的IP地址可用时,将它们加入路由数据库,不可用时将其删除,对应的也会刷新路由缓存。
fib_add_ifaddr()/fib_del_ifaddr()
void fib_add_ifaddr(struct in_ifaddr *ifa)
{
struct in_device *in_dev = ifa->ifa_dev;
struct net_device *dev = in_dev->dev;
struct in_ifaddr *prim = ifa;
__be32 mask = ifa->ifa_mask;
__be32 addr = ifa->ifa_local;
__be32 prefix = ifa->ifa_address&mask;
if (ifa->ifa_flags&IFA_F_SECONDARY) {
// 如果是个辅地址,则找到主地址,路由管理只添加主地址
prim = inet_ifa_byprefix(in_dev, prefix, mask);
if (prim == NULL) {
printk(KERN_WARNING "fib_add_ifaddr: bug: prim == NULL\n");
return;
}
}
// 在路由数据库中添加一条目的地址为主地址的主机路由项(子网掩码长度为32)
fib_magic(RTM_NEWROUTE, RTN_LOCAL, addr, 32, prim);
if (!(dev->flags&IFF_UP)) // 网络设备尚未使能,后续地址不添加
return;
// 在路由数据库中添加一条到所在网络广播地址的主机路由项
if (ifa->ifa_broadcast && ifa->ifa_broadcast != htonl(0xFFFFFFFF))
fib_magic(RTM_NEWROUTE, RTN_BROADCAST, ifa->ifa_broadcast, 32, prim);
if (!ipv4_is_zeronet(prefix) && !(ifa->ifa_flags&IFA_F_SECONDARY) &&
(prefix != addr || ifa->ifa_prefixlen < 32)) {
// 添加的地址是某个子网中的一个主机地址,那么也需要生成到达该子网的路由项,
// 如果是环回接口,就在local表中,添加否则在main表中添加
fib_magic(RTM_NEWROUTE, dev->flags&IFF_LOOPBACK ? RTN_LOCAL :
RTN_UNICAST, prefix, ifa->ifa_prefixlen, prim);
/* Add network specific broadcasts, when it takes a sense */
// 类似的,生成到达该子网广播地址的路由项,这里可以看出,主机号全0和全1都是去往该网络的广播地址
if (ifa->ifa_prefixlen < 31) {
fib_magic(RTM_NEWROUTE, RTN_BROADCAST, prefix, 32, prim);
fib_magic(RTM_NEWROUTE, RTN_BROADCAST, prefix|~mask, 32, prim);
}
}
}
static void fib_del_ifaddr(struct in_ifaddr *ifa)
{
struct in_device *in_dev = ifa->ifa_dev;
struct net_device *dev = in_dev->dev;
struct in_ifaddr *ifa1;
struct in_ifaddr *prim = ifa;
__be32 brd = ifa->ifa_address|~ifa->ifa_mask;
__be32 any = ifa->ifa_address&ifa->ifa_mask;
#define LOCAL_OK 1
#define BRD_OK 2
#define BRD0_OK 4
#define BRD1_OK 8
unsigned ok = 0;
if (!(ifa->ifa_flags&IFA_F_SECONDARY))
// 删除主地址所在网络的路由
fib_magic(RTM_DELROUTE, dev->flags&IFF_LOOPBACK ? RTN_LOCAL :
RTN_UNICAST, any, ifa->ifa_prefixlen, prim);
else {
// 找到主地址
prim = inet_ifa_byprefix(in_dev, any, ifa->ifa_mask);
if (prim == NULL) {
printk(KERN_WARNING "fib_del_ifaddr: bug: prim == NULL\n");
return;
}
}
/* Deletion is more complicated than add.
We should take care of not to delete too much :-)
Scan address list to be sure that addresses are really gone.
*/
// 遍历该网络设备上的所有IP地址,标记它们的地址类型
for (ifa1 = in_dev->ifa_list; ifa1; ifa1 = ifa1->ifa_next) {
if (ifa->ifa_local == ifa1->ifa_local)
ok |= LOCAL_OK;
if (ifa->ifa_broadcast == ifa1->ifa_broadcast)
ok |= BRD_OK;
if (brd == ifa1->ifa_broadcast)
ok |= BRD1_OK;
if (any == ifa1->ifa_broadcast)
ok |= BRD0_OK;
}
// 删除对应的路由项
if (!(ok&BRD_OK))
fib_magic(RTM_DELROUTE, RTN_BROADCAST, ifa->ifa_broadcast, 32, prim);
if (!(ok&BRD1_OK))
fib_magic(RTM_DELROUTE, RTN_BROADCAST, brd, 32, prim);
if (!(ok&BRD0_OK))
fib_magic(RTM_DELROUTE, RTN_BROADCAST, any, 32, prim);
if (!(ok&LOCAL_OK)) {
fib_magic(RTM_DELROUTE, RTN_LOCAL, ifa->ifa_local, 32, prim);
/* Check, that this local address finally disappeared. */
if (inet_addr_type(dev_net(dev), ifa->ifa_local) != RTN_LOCAL) {
/* And the last, but not the least thing.
We must flush stray FIB entries.
First of all, we scan fib_info list searching
for stray nexthop entries, then ignite fib_flush.
*/
if (fib_sync_down_addr(dev_net(dev), ifa->ifa_local))
fib_flush(dev_net(dev));
}
}
#undef LOCAL_OK
#undef BRD_OK
#undef BRD0_OK
#undef BRD1_OK
}
小结
在添加IP地址时,内核会自动维护本机相关的路由项,具体如下:
- 在main表中添加一条目的地址为该IP地址的主机路由项(子网掩码长度为32),使得接收数据时可以精准匹配;
- 在local表中添加一条目的地址为该IP广播地址的主机路由项,使得收发的广播数据可以精准匹配;
- 在main表添加一条到该IP地址所在网络的网络路由项,使得可以到达该网络;
- 在local表中添加目的地址为该子网的广播地址路由项;
类似的,在删除IP地址时,会将上述路由项全部删除。
生成并配置路由项: fib_magic()
如上,添加和删除时都会调用该函数完成路由数据库的路由操作。
/* Prepare and feed intra-kernel routing request.
Really, it should be netlink message, but :-( netlink
can be not configured, so that we feed it directly
to fib engine. It is legal, because all events occur
only when netlink is already locked.
*/
static void fib_magic(int cmd, int type, __be32 dst, int dst_len, struct in_ifaddr *ifa)
{
struct net *net = dev_net(ifa->ifa_dev->dev);
struct fib_table *tb;
struct fib_config cfg = { // 生成路由项配置结构
.fc_protocol = RTPROT_KERNEL, // 路由项是由内核配置的
.fc_type = type, // 路由项类型
.fc_dst = dst,
.fc_dst_len = dst_len,
.fc_prefsrc = ifa->ifa_local, // 优选IP地址
.fc_oif = ifa->ifa_dev->dev->ifindex, // 出口网络设备
.fc_nlflags = NLM_F_CREATE | NLM_F_APPEND,
.fc_nlinfo = {
.nl_net = net,
},
};
// 获取要操作的路由表。单播路由是main表,其它类型是local表
if (type == RTN_UNICAST)
tb = fib_new_table(net, RT_TABLE_MAIN);
else
tb = fib_new_table(net, RT_TABLE_LOCAL);
if (tb == NULL)
return;
cfg.fc_table = tb->tb_id;
// 本地路由项的作用域为HOST,子网路由项作用域为LINK
if (type != RTN_LOCAL)
cfg.fc_scope = RT_SCOPE_LINK;
else
cfg.fc_scope = RT_SCOPE_HOST;
// 根据命令分别指定路由项的添加和删除操作
if (cmd == RTM_NEWROUTE)
tb->tb_insert(tb, &cfg);
else
tb->tb_delete(tb, &cfg);
}