文章目录
这篇笔记从路由项的添加过程来加深对路由表组织方式的理解,hash方式路由表的路由添加回调为fn_hash_insert()。涉及的文件有:
源代码路径 | 说明 |
---|---|
include/net/ip_fib.h | IPv4路由数据库头文件 |
core/ipv4/fib_hash.c | 哈希方式的路由数据库相关实现 |
core/ipv4/fib_semantics.c | 路由项信息相关函数实现 |
fn_hash_insert()
static int fn_hash_insert(struct fib_table *tb, struct fib_config *cfg)
{
struct fn_hash *table = (struct fn_hash *) tb->tb_data;
struct fib_node *new_f = NULL;
struct fib_node *f;
struct fib_alias *fa, *new_fa;
struct fn_zone *fz;
struct fib_info *fi;
u8 tos = cfg->fc_tos;
__be32 key;
int err;
if (cfg->fc_dst_len > 32) // IPv4的掩码长度当然最大只能32
return -EINVAL;
// 根据路由项目的地址的子网掩码长度找到路由区
fz = table->fn_zones[cfg->fc_dst_len];
// 添加的是该路由区的第一条路由,先创建路由区,目的地址子网掩码长度作为第二个入参
if (!fz && !(fz = fn_new_zone(table, cfg->fc_dst_len)))
return -ENOBUFS;
// 计算路由结点在路由区哈希表中的索引key,计算的输入就是路由结点代表的目的网络地址
key = 0;
if (cfg->fc_dst) {
// 路由项中,所有的目的地址都应该只有网络号,主机号都应该为0(对于主机路由项,
// 子网掩码长度为32,没有主机号,所以下面条件也成立)
if (cfg->fc_dst & ~FZ_MASK(fz))
return -EINVAL;
key = fz_key(cfg->fc_dst, fz);
}
// 根据路由配置信息查询或者新建fib_info对象
fi = fib_create_info(cfg);
if (IS_ERR(fi))
return PTR_ERR(fi);
// 如果zone中路由项太多了会导致查找性能降低,此时尝试扩展zone的哈希表
if (fz->fz_nent > (fz->fz_divisor<<1) && fz->fz_divisor < FZ_MAX_DIVISOR &&
(cfg->fc_dst_len == 32 || (1 << cfg->fc_dst_len) > fz->fz_divisor))
fn_rehash_zone(fz);
// 根据网络号key寻找对应的node,如果node不存在,那么alias必然也不存在,
// 否则根据tos和优先级寻找匹配的alias
f = fib_find_node(fz, key);
if (!f)
fa = NULL;
else
fa = fib_find_alias(&f->fn_alias, tos, fi->fib_priority);
/* Now fa, if non-NULL, points to the first fib alias
* with the same keys [prefix,tos,priority], if such key already
* exists or to the node before which we will insert new one.
*
* If fa is NULL, we will need to allocate a new one and
* insert to the head of f.
*
* If f is NULL, no fib node matched the destination key
* and we need to allocate a new one of those as well.
*/
// 如注释所述,路由项的目的地址网络号、tos、优先级三个字段决定了是否是同一个fib_alias
if (fa && fa->fa_tos == tos && fa->fa_info->fib_priority == fi->fib_priority) {
struct fib_alias *fa_first, *fa_match;
err = -EEXIST;
if (cfg->fc_nlflags & NLM_F_EXCL) // 有相同路由项,但是参数指定了不能替换已有项
goto out;
/* We have 2 goals:
* 1. Find exact match for type, scope, fib_info to avoid
* duplicate routes
* 2. Find next 'fa' (or head), NLM_F_APPEND inserts before it
*/
// [prefix,tos,priority]三者只是较为粗略的匹配,下面查找精确的匹配fib_alias对象
fa_match = NULL;
// 由于路由结点中fa_alias对象在插入时也是有序的,所以只需要从当前fa的位置往后找即可
fa_first = fa;
fa = list_entry(fa->fa_list.prev, struct fib_alias, fa_list);
list_for_each_entry_continue(fa, &f->fn_alias, fa_list) {
if (fa->fa_tos != tos)
break;
if (fa->fa_info->fib_priority != fi->fib_priority)
break;
if (fa->fa_type == cfg->fc_type &&
fa->fa_scope == cfg->fc_scope &&
fa->fa_info == fi) {
fa_match = fa; // 路由项完全相同
break;
}
}
// 插入的路由项和已有路由项完全相同,并且指定了替换标记,那么替换旧的路由项,完成插入过程
if (cfg->fc_nlflags & NLM_F_REPLACE) {
struct fib_info *fi_drop;
u8 state;
fa = fa_first;
if (fa_match) {
if (fa == fa_match)
err = 0;
goto out;
}
write_lock_bh(&fib_hash_lock);
fi_drop = fa->fa_info;
fa->fa_info = fi;
fa->fa_type = cfg->fc_type;
fa->fa_scope = cfg->fc_scope;
state = fa->fa_state;
fa->fa_state &= ~FA_S_ACCESSED;
fib_hash_genid++;
write_unlock_bh(&fib_hash_lock);
fib_release_info(fi_drop);
if (state & FA_S_ACCESSED)
rt_cache_flush(cfg->fc_nlinfo.nl_net, -1);
rtmsg_fib(RTM_NEWROUTE, key, fa, cfg->fc_dst_len, tb->tb_id,
&cfg->fc_nlinfo, NLM_F_REPLACE);
return 0;
}
/* Error if we find a perfect match which
* uses the same scope, type, and nexthop
* information.
*/
if (fa_match)
goto out