基于linux2.6.21
上一节分析了路由的hash链表存储方式相关的数据结构,本节就分析一下路由的添加。对于路由查找来说,当支持策略路由时,路由的查找就会较复杂一些,因此打算结合策略规则来分析路由相关的知识,因此以后介绍的路由添加、删除、查找,都是基于支持策略路由的。
一、应用层添加路由的方式
对于应用层来说,添加路由的方式有两种,分别是命令route与ip route。
#route add 192.168.11.0 mask 255.255.255.0 192.168.11.1
# ip route add 192.168.11.0/24 dev eth0 src 192.168.11.123
而对应于实现上来说,使用route命令即是通过socket的ioctl命令来实现路由添加的;而使用ip route命令即是通过netlink机制实现路由的添加的。本节不讨论socket的ioctl机制也不分析netlink机制,主要是分析路由的添加操作,不管使用哪一种机制,最终会触发如下代码段,实现路由的添加
tb = fib_new_table(cfg.fc_table);
if (tb)
err = tb->tb_insert(tb, &cfg);
以上代码段就是路由添加的最主要的代码:
1.调用函数fib_new_table,根据应用层传递的路由表的id,在全局路由表的hash链表数组fib_table_hash[]中查找是否存在该id对应的struct fib_table类型的变量,若存在则返回该变量的首地址;若不存在,则创建该id对应的struct fib_table类型的路由表,并返回该路由表对应的首地址。
2.调用路由表的函数指针tb_insert,进行路由项的增加操作(即函数fn_hash_insert)。
下面就主要分析下这两个函数,以及这两个函数所涉及的函数。
二、fib_new_table
此处分析的函数为支持策略路由的fib_new_table函数。该函数主要实现两个功能:
1.路由表的查找
2.路由表的创建。
2.1 路由表之间的联系
而对于系统创建的路由表,都会通过hash链表链接在一起。此处就需要介绍一个全局变量static struct hlist_head fib_table_hash[FIB_TABLE_HASHSZ];
这是一个链表类型的数组,其中FIB_TABLE_HASHSZ的值为256,而每一个数组元素都是一个链表,即总共有256链表头。
那怎么进行hash操作,让不同的路由表链接到不同的hash链表呢?
这个hash操作还是比较简单的,即table_id/256。
以上也说明了一个问题,即在该linux系统中,可以创建的路由表是没有限制的,而不是只能创建256个路由表。
2.2 函数分析
功能:查找一个路由表(当查找的路由表不存在时,则创建路由表)
1.若路由表的id为0,则设置为main表的id
2.若调用函数fib_get_table找到该路由表,则返回改路由表的首地址
3.调用函数fib_hash_init创建一个路由表变量
4.根据路由表的id与FIB_TABLE_HASHSZ的值,计算该路由表对应的hash值 为h
5.根据hash值h,获取对应的hash链表fib_table_hash[h]
6.将新的路由表添加到hash链表fib_table_hash[h]的表首
从这我们能够看出,可以创建的路由表不止FIB_TABLE_HASHSZ个,而是有FIB_TABLE_HASHSZ个hash链表,而每一个链表又可以链接多个路由表。
在linux2.6.16中,hash链表数组就是退化成一个数组,所以最多只有256个路由表
而在linux2.6.21中,hash链表数组fib_table_hash[]的每一个成员均是一个hash链表,而每一个hash链表可以链接多个路由表,所以在linux2.6.21中可创建的路由表不止256个。
struct fib_table *fib_new_table(u32 id)
{
struct fib_table *tb;
unsigned int h;
if (id == 0)
id = RT_TABLE_MAIN;
tb = fib_get_table(id);
if (tb)
return tb;
tb = fib_hash_init(id);
if (!tb)
return NULL;
h = id & (FIB_TABLE_HASHSZ - 1);
hlist_add_head_rcu(&tb->tb_hlist, &fib_table_hash[h]);
return tb;
}
2.2.1 路由表的创建 fib_hash_init
功能:路由表的创建与初始化
1.若缓存fn_hash_kmem或者fn_alias_kmem为空,则调用kmem_cache_create创建slab类型缓存块
2.调用函数kmalloc为一个路由表变量申请内存,而内存的大小为
sizeof(struct fib_table) + sizeof(struct fn_hash)。
3.为路由表对应的插入、删除、查找、默认路由查找等函数指针赋值,分别为函数
fn_hash_insert、fn_hash_delete、fn_hash_lookup、fn_hash_select_default、fn_hash_dump
*/
#ifdef CONFIG_IP_MULTIPLE_TABLES
struct fib_table * fib_hash_init(u32 id)
#else
struct fib_table * __init fib_hash_init(u32 id)
#endif
{
struct fib_table *tb;
if (fn_hash_kmem == NULL)
fn_hash_kmem = kmem_cache_create("ip_fib_hash",
sizeof(struct fib_node),
0, SLAB_HWCACHE_ALIGN,
NULL, NULL);
if (fn_alias_kmem == NULL)
fn_alias_kmem = kmem_cache_create("ip_fib_alias",
sizeof(struct fib_alias),
0, SLAB_HWCACHE_ALIGN,
NULL, NULL);
tb = kmalloc(sizeof(struct fib_table) + sizeof(struct fn_hash),
GFP_KERNEL);
if (tb == NULL)
return NULL;
tb->tb_id = id;
tb->tb_lookup = fn_hash_lookup;
tb->tb_insert = fn_hash_insert;
tb->tb_delete = fn_hash_delete;
tb->tb_flush = fn_hash_flush;
tb->tb_select_default = fn_hash_select_default;
tb->tb_dump = fn_hash_dump;
memset(tb->tb_data, 0, sizeof(struct fn_hash));
return tb;
}
这个函数里的几个函数指针是非常重要的,tb_lookup是路由查找的处理函数、tb_insert 为路由增加的处理函数、tb_delete 为路由删除的处理函数、tb_flush为路由flush的处理函数、tb_select_default 为查找默认路由的处理函数,以上几个函数都是非常重要的,后续小节都会继续分析这几个函数。
三、tb_insert
此处我们介绍路由项的添加函数,上面只是路由表的添加,下面就开始进行路由项的添加,在上面的分析中,我们已经知道路由添加的函数即为fn_hash_insert。
3.1 fn_hash_insert
功能:路由项的增加
1.根据掩码值,找到相应的fn_zone变量
a)若该fn_zone变量还不存在,则调用函数fn_new_zone创建,执行2
b)若已存在,则执行2
2.根据传输的目的地址以及掩码值,计算搜索关键字
3.根据用户传递参数,调用函数fib_create_info创建fib_info变量
4.判断是否需要对已查找到的fn_zone变量的hash数组进行容量扩充
a)若需要扩充,且当前内存也允许扩充,则调用函数fn_rehash_zone实现
5.根据2中获取的搜索关键字,在fn_zone变量的相应hash链表中查找符合条件
fib_node变量
a)
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, *f;
struct fib_alias *fa, *new_fa;
struct fn_zone *fz;
struct fib_info *fi;
u8 tos = cfg->fc_tos;
__be32 key;
int err;
/*cfg->fc_dst_len网络掩码长度*/
if (cfg->fc_dst_len > 32)
return -EINVAL;
/*根据掩码长度获取相应的fn_zone*/
fz = table->fn_zones[cfg->fc_dst_len];
if (!fz && !(fz = fn_new_zone(table, cfg->fc_dst_len)))
return -ENOBUFS;
/*根据路由的目的地址与掩码的值,获取该目的地址对应的网络地址。
即搜索关键字
*/
key = 0;
if (cfg->fc_dst) {
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);
/*如果在当前fn_zone变量的hash链表中添加的fib_node节点的数目已经大于当前
fn_zone变量的最大值时,则对该fn_zone变量的hash链表数组进行容量扩充。
扩充操作由函数fn_rehash_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);
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.
*/
/*
当一个fib_alias变量的tos与要添加的路由的tos相等,且该fib_alias关联的fib_info变量的优先级与
要添加的路由的优先级也相等时
a)若应用层添加路由的操作置位了flag的NLM_F_EXCL位时,则程序返回失败(路由已存在)
b)若应用层添加路由的操作置位了flag的NLM_F_REPLACE位时(即替换已存在的路由时),则
替换已存在且相等的路由项的fib_alias、fib_info变量
c)对于不满足上面a)、b)两点,则表示是需要添加的路由,此时就需要对fib_node下的路由项
进行精确匹配,即判断tos、type、scope、priority以及fib_info的匹配,
i)若找到一个匹配的路由项,则说明路由项已存在,不进行添加操作,程序返回
ii)若没有找到,则说明不存在相同的路由项,则执行添加操作。
*/
if (fa && fa->fa_tos == tos &&
fa->fa_info->fib_priority == fi->fib_priority) {
struct fib_alias *fa_orig;
err = -EEXIST;
if (cfg->fc_nlflags & NLM_F_EXCL)
goto out;
if (cfg->fc_nlflags & NLM_F_REPLACE) {
struct fib_info *fi_drop;
u8 state;
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(-1);
return 0;
}
/* Error if we find a perfect match which
* uses the same scope, type, and nexthop
* information.
*/
fa_orig = 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)
goto out;
}
/*这个主要是用于在表头添加fib_alias还是在表尾添加fib_alias*/
if (!(cfg->fc_nlflags & NLM_F_APPEND))
fa = fa_orig;
}
/*若用户传递过来的配置中,没有对flag的NLM_F_CREATE位置位,则不进行添加操作,程序返回*/
err = -ENOENT;
if (!(cfg->fc_nlflags & NLM_F_CREATE))
goto out;
/*
1.创建一个新的fib_alias变量
2.若fib_node变量也不存在,则创建新的fib_node变量,
并设置fn_key的值,并对fn_hash、fn_alias成员边界进行初始化;若已存在,则执行3
3.为新创建的fib_alias变量的fa_info、fa_tos、fa_type、fa_scope、fa_state变量进行赋值
4.若fib_node是新创建的,则调用fib_insert_node将该fib_node变量插入到fib_node->fz_hash[]相对
应的hash链表中,且fn_zone->fz_nent的统计计数加1
5.将新创建的fib_alias变量添加到fib_node->fn_alias链表中对应的位置。
*/
err = -ENOBUFS;
new_fa = kmem_cache_alloc(fn_alias_kmem, GFP_KERNEL);
if (new_fa == NULL)
goto out;
new_f = NULL;
if (!f) {
new_f = kmem_cache_alloc(fn_hash_kmem, GFP_KERNEL);
if (new_f == NULL)
goto out_free_new_fa;
INIT_HLIST_NODE(&new_f->fn_hash);
INIT_LIST_HEAD(&new_f->fn_alias);
new_f->fn_key = key;
f = new_f;
}
new_fa->fa_info = fi;
new_fa->fa_tos = tos;
new_fa->fa_type = cfg->fc_type;
new_fa->fa_scope = cfg->fc_scope;
new_fa->fa_state = 0;
/*
* Insert new entry to the list.
*/
write_lock_bh(&fib_hash_lock);
if (new_f)
fib_insert_node(fz, new_f);
list_add_tail(&new_fa->fa_list,
(fa ? &fa->fa_list : &f->fn_alias));
fib_hash_genid++;
write_unlock_bh(&fib_hash_lock);
if (new_f)
fz->fz_nent++;
rt_cache_flush(-1);
rtmsg_fib(RTM_NEWROUTE, key, new_fa, cfg->fc_dst_len, tb->tb_id,
&cfg->fc_nlinfo);
return 0;
out_free_new_fa:
kmem_cache_free(fn_alias_kmem, new_fa);
out:
fib_release_info(fi);
return err;
}
这个函数的信息量还是很大的, 调用的函数也是比较多的,包括了fn_zone的创建以及fn_zone的hash bucket的扩充、fib_node的创建、fib_alias的创建、fib_info的创建,以及相应的查找函数等等,有几个函数我们还是需要分析一下的:fn_new_zone、fz_key、fib_create_info、fn_rehash_zone、fib_find_node、fib_find_alias、fib_release_info、fib_insert_node。
3.1.1 fn_new_zone
功能:创建一个新的fn_zone,其中z为掩码长度
static struct fn_zone *
fn_new_zone(struct fn_hash *table, int z)
{
int i;
struct fn_zone *fz = kzalloc(sizeof(struct fn_zone), GFP_KERNEL);
if (!fz)
return NULL;
/*默认创建16个hash链表,每一个hash链表都用来将掩码为z的路由链接在一起*/
if (z) {
fz->fz_divisor = 16;
} else {
fz->fz_divisor = 1;
}
fz->fz_hashmask = (fz->fz_divisor - 1);
fz->fz_hash = fz_hash_alloc(fz->fz_divisor);
if (!fz->fz_hash) {
kfree(fz);
return NULL;
}
memset(fz->fz_hash, 0, fz->fz_divisor * sizeof(struct hlist_head *));
/*设置掩码长度,并根据掩码长度设置掩码,存放在fz_mask里*/
fz->fz_order = z;
fz->fz_mask = inet_make_mask(z);
/*
1.查找路由表的fn_zone数组中,是否已经创建了比当前创建的fn_zone的掩码更大的,
a)若查找到第一个符合要求的fn_zone,则将fn_zone的next指针指向当前创建的fn_zone
b)若没有查找到,则当前创建的fn_zone,在所有已创建的fn_zone的掩码最大,则将该fn_zone插入到table->fn_zone_list的表头。
这样操作主要是由于其路由查找是通过最长匹配来实现的,
当查找一个路由时,我们首先搜索掩码最长的fn_zone。这样保证了精确匹配。
*/
/* Find the first not empty zone with more specific mask */
for (i=z+1; i<=32; i++)
if (table->fn_zones[i])
break;
write_lock_bh(&fib_hash_lock);
if (i>32) {
/* No more specific masks, we are the first. */
fz->fz_next = table->fn_zone_list;
table->fn_zone_list = fz;
} else {
fz->fz_next = table->fn_zones[i]->fz_next;
table->fn_zones[i]->fz_next = fz;
}
table->fn_zones[z] = fz;
fib_hash_genid++;
write_unlock_bh(&fib_hash_lock);
return fz;
}
3.1.2 fz_key
功能:根据ip地址与fn_zone变量,获取ip的网络地址
static inline __be32 fz_key(__be32 dst, struct fn_zone *fz)
{
return dst & FZ_MASK(fz);
}
3.1.3 fib_create_info
功能:创建一个struct fib_info结构的变量
1.当前fib_info的数目大于等于fib_hash_size时,要对hash表
fib_info_hash、fib_info_laddrhash的内存空间扩容1倍
2.创建一个fib_info结构的变量,为该fib_info结构变量的fib_protocol、fib_flags、
fib_priority、fib_prefsrc成员进行赋值,并增加fib_info_cnt的统计计数
3.设置该fib_info变量的所有fib_nh变量的nh_parent指针指向该fib_info
4.根据传递的值,设置fib_metrics的值
5.判断应用层传递的路由项的fc_scope值是否正确,若不正确,则程序返回;
若正确,则继续执行
6.对下一跳网关对应的fib_nh结构变量的nh_scope、nh_dev等成员项进行赋值。
7.调用fib_find_info,判断刚申请并初始化的变量是否已存在系统中:
若存在,则对原来的fib_info变量的fib_treeref计数加一即可,则可以释放掉新申请的
fib_info变量占用的内存;
若不存在,则将新创建的fib_info变量添加到系统的hash表中。
struct fib_info *fib_create_info(struct fib_config *cfg)
{
int err;
struct fib_info *fi = NULL;
struct fib_info *ofi;
int nhs = 1;
/* Fast check to catch the most weird cases */
if (fib_props[cfg->fc_type].scope > cfg->fc_scope)
goto err_inval;
#ifdef CONFIG_IP_ROUTE_MULTIPATH
if (cfg->fc_mp) {
nhs = fib_count_nexthops(cfg->fc_mp, cfg->fc_mp_len);
if (nhs == 0)
goto err_inval;
}
#endif
#ifdef CONFIG_IP_ROUTE_MULTIPATH_CACHED
if (cfg->fc_mp_alg) {
if (cfg->fc_mp_alg < IP_MP_ALG_NONE ||
cfg->fc_mp_alg > IP_MP_ALG_MAX)
goto err_inval;
}
#endif
/*当前fib_info的数目大于等于fib_hash_size时,要对hash表
fib_info_hash、fib_info_laddrhash的内存空间扩容1倍*/
err = -ENOBUFS;
if (fib_info_cnt >= fib_hash_size) {
unsigned int new_size = fib_hash_size << 1;
struct hlist_head *new_info_hash;
struct hlist_head *new_laddrhash;
unsigned int bytes;
if (!new_size)
new_size = 1;
bytes = new_size * sizeof(struct hlist_head *);
new_info_hash = fib_hash_alloc(bytes);
new_laddrhash = fib_hash_alloc(bytes);
if (!new_info_hash || !new_laddrhash) {
fib_hash_free(new_info_hash, bytes);
fib_hash_free(new_laddrhash, bytes);
} else {
memset(new_info_hash, 0, bytes);
memset(new_laddrhash, 0, bytes);
fib_hash_move(new_info_hash, new_laddrhash, new_size);
}
if (!fib_hash_size)
goto failure;
}
/*创建一个fib_info结构的变量*/
fi = kzalloc(sizeof(*fi)+nhs*sizeof(struct fib_nh), GFP_KERNEL);
if (fi == NULL)
goto failure;
fib_info_cnt++;
/*为该fib_info结构变量的fib_protocol、fib_flags、fib_priority、fib_prefsrc成员进行赋值*/
fi->fib_protocol = cfg->fc_protocol;
fi->fib_flags = cfg->fc_flags;
fi->fib_priority = cfg->fc_priority;
fi->fib_prefsrc = cfg->fc_prefsrc;
/*下一跳网关对应的的fib_nh结构变量的个数*/
fi->fib_nhs = nhs;
/*设置该fib_info变量的所有fib_nh变量的nh_parent指针指向该fib_info*/
change_nexthops(fi) {
nh->nh_parent = fi;
} endfor_nexthops(fi)
/*若应用层有传递设置fib_metrics的参数,则下面的代码片段用来对
fib_metrics中的各值进行赋值*/
if (cfg->fc_mx) {
struct nlattr *nla;
int remaining;
nla_for_each_attr(nla, cfg->fc_mx, cfg->fc_mx_len, remaining) {
int type = nla->nla_type;
if (type) {
if (type > RTAX_MAX)
goto err_inval;
fi->fib_metrics[type - 1] = nla_get_u32(nla);
}
}
}
/*
1.当内核支持多路径路由时,则应用层传递的fc_mp大于0时,
则调用fib_get_nhs进行设置所有的fib_nh.
2.当内核不支持多路径路由时,且应用层传递的fc_map大于0时,则返回出错。
3.当应用层传递的fc_map为0时,则对该fib_info的fib_nh变量的的网关ip、输
出接口、flag等进行赋值。
*/
if (cfg->fc_mp) {
#ifdef CONFIG_IP_ROUTE_MULTIPATH
err = fib_get_nhs(fi, cfg->fc_mp, cfg->fc_mp_len, cfg);
if (err != 0)
goto failure;
if (cfg->fc_oif && fi->fib_nh->nh_oif != cfg->fc_oif)
goto err_inval;
if (cfg->fc_gw && fi->fib_nh->nh_gw != cfg->fc_gw)
goto err_inval;
#ifdef CONFIG_NET_CLS_ROUTE
if (cfg->fc_flow && fi->fib_nh->nh_tclassid != cfg->fc_flow)
goto err_inval;
#endif
#else
goto err_inval;
#endif
} else {
struct fib_nh *nh = fi->fib_nh;
nh->nh_oif = cfg->fc_oif;
nh->nh_gw = cfg->fc_gw;
nh->nh_flags = cfg->fc_flags;
#ifdef CONFIG_NET_CLS_ROUTE
nh->nh_tclassid = cfg->fc_flow;
#endif
#ifdef CONFIG_IP_ROUTE_MULTIPATH
nh->nh_weight = 1;
#endif
}
#ifdef CONFIG_IP_ROUTE_MULTIPATH_CACHED
fi->fib_mp_alg = cfg->fc_mp_alg;
#endif
if (fib_props[cfg->fc_type].error) {
if (cfg->fc_gw || cfg->fc_oif || cfg->fc_mp)
goto err_inval;
goto link_it;
}
/*对于应用层创建的路由,如果其路由scope大于RT_SCOPE_HOST,则返回错误*/
if (cfg->fc_scope > RT_SCOPE_HOST)
goto err_inval;
/*
1.当创建路由的scope值为RT_SCOPE_HOST,说明这是一个到本地接口的变量, 则此时的fib_info的fib_nh结构的成员变量的scope需要设置为
RT_SCOPE_NOWHERE,并设置nh_dev的值
a)若nhs值大于1时,则说明路由不对,因为对于scope为RT_SCOPE_HOST,
其nhs是不可能大于1的
b)若nhs为1,但是fib_info->fib_nh->nh_gw不为0时,则说明路由不对,因为
若下一跳网关的地址不为0,则当前路由的scope必须小于等于 RT_SCOPE_UNIVERSE。
2.当创建路由的scope值小于RT_SCOPE_HOST时,则对于该fib_info变量下的所
有fib_nh结构的变量,调用fib_check_nh函数进行合法性检查及设置到达下一跳地
址的出口设备
*/
if (cfg->fc_scope == RT_SCOPE_HOST) {
struct fib_nh *nh = fi->fib_nh;
/* Local address is added. */
if (nhs != 1 || nh->nh_gw)
goto err_inval;
nh->nh_scope = RT_SCOPE_NOWHERE;
nh->nh_dev = dev_get_by_index(fi->fib_nh->nh_oif);
err = -ENODEV;
if (nh->nh_dev == NULL)
goto failure;
} else {
change_nexthops(fi) {
if ((err = fib_check_nh(cfg, fi, nh)) != 0)
goto failure;
} endfor_nexthops(fi)
}
/*首先源地址?*/
if (fi->fib_prefsrc) {
if (cfg->fc_type != RTN_LOCAL || !cfg->fc_dst ||
fi->fib_prefsrc != cfg->fc_dst)
if (inet_addr_type(fi->fib_prefsrc) != RTN_LOCAL)
goto err_inval;
}
/*
1.若刚创建的fib_info结构的变量已经存在,则释放该fib_info变量,程序返回;
否则进入2
2.将该fib_info变量添加到相应的hash链表fib_info_hash[fib_info_hashfn(fi)]中
3.若该fib_info变量的首先源地址不为空,则将该fib_info变量添加到相应的hash
链表fib_info_laddrhash[fib_laddr_hashfn(fi->fib_prefsrc)]中
4.对于该fib_info变量的所有对应的fib_nh结构的变量中,若fib_nh->nh_dev不为
空,则将该fib_nh变量添加到hash数组fib_info_devhash对应的hash链表中
5.程序返回已创建的fib_info变量
*/
link_it:
if ((ofi = fib_find_info(fi)) != NULL) {
fi->fib_dead = 1;
free_fib_info(fi);
ofi->fib_treeref++;
return ofi;
}
fi->fib_treeref++;
atomic_inc(&fi->fib_clntref);
spin_lock_bh(&fib_info_lock);
hlist_add_head(&fi->fib_hash,
&fib_info_hash[fib_info_hashfn(fi)]);
if (fi->fib_prefsrc) {
struct hlist_head *head;
head = &fib_info_laddrhash[fib_laddr_hashfn(fi->fib_prefsrc)];
hlist_add_head(&fi->fib_lhash, head);
}
change_nexthops(fi) {
struct hlist_head *head;
unsigned int hash;
if (!nh->nh_dev)
continue;
hash = fib_devindex_hashfn(nh->nh_dev->ifindex);
head = &fib_info_devhash[hash];
hlist_add_head(&nh->nh_hash, head);
} endfor_nexthops(fi)
spin_unlock_bh(&fib_info_lock);
return fi;
err_inval:
err = -EINVAL;
failure:
if (fi) {
fi->fib_dead = 1;
free_fib_info(fi);
}
return ERR_PTR(err);
}
3.1.3.1 fib_hash_move
功能:将hash链表数组fib_info_hash、fib_info_laddrhash的中的hash项,移动到新的
hash链表数组new_info_hash、new_laddrhash中去
1.将fib_info_hash[]数组里的所有hash表的所有hash项都移动到new_info_hash[]中的 hash链表中
2.将fib_info_laddrhash[]数组里的所有hash表的所有hash项都移动到new_laddrhash[] 中的hash链表中
3.将原来fib_info_hash、fib_info_laddrhash占用的内存释放掉
static void fib_hash_move(struct hlist_head *new_info_hash,
struct hlist_head *new_laddrhash,
unsigned int new_size)
{
struct hlist_head *old_info_hash, *old_laddrhash;
unsigned int old_size = fib_hash_size;
unsigned int i, bytes;
spin_lock_bh(&fib_info_lock);
old_info_hash = fib_info_hash;
old_laddrhash = fib_info_laddrhash;
fib_hash_size = new_size;
for (i = 0; i < old_size; i++) {
struct hlist_head *head = &fib_info_hash[i];
struct hlist_node *node, *n;
struct fib_info *fi;
hlist_for_each_entry_safe(fi, node, n, head, fib_hash) {
struct hlist_head *dest;
unsigned int new_hash;
hlist_del(&fi->fib_hash);
new_hash = fib_info_hashfn(fi);
dest = &new_info_hash[new_hash];
hlist_add_head(&fi->fib_hash, dest);
}
}
fib_info_hash = new_info_hash;
for (i = 0; i < old_size; i++) {
struct hlist_head *lhead = &fib_info_laddrhash[i];
struct hlist_node *node, *n;
struct fib_info *fi;
hlist_for_each_entry_safe(fi, node, n, lhead, fib_lhash) {
struct hlist_head *ldest;
unsigned int new_hash;
hlist_del(&fi->fib_lhash);
new_hash = fib_laddr_hashfn(fi->fib_prefsrc);
ldest = &new_laddrhash[new_hash];
hlist_add_head(&fi->fib_lhash, ldest);
}
}
fib_info_laddrhash = new_laddrhash;
spin_unlock_bh(&fib_info_lock);
bytes = old_size * sizeof(struct hlist_head *);
fib_hash_free(old_info_hash, bytes);
fib_hash_free(old_laddrhash, bytes);
}
/*
功能:根据传入的fib_info结构变量,构建hash值
1.根据该计算得出的hash值,可以从数组fib_info_hash[]中取出相应的hash链表元素
2.然后将该fib_info变量插入到该hash链表中。
*/
static inline unsigned int fib_info_hashfn(const struct fib_info *fi)
{
unsigned int mask = (fib_hash_size - 1);
unsigned int val = fi->fib_nhs;
val ^= fi->fib_protocol;
val ^= (__force u32)fi->fib_prefsrc;
val ^= fi->fib_priority;
return (val ^ (val >> 7) ^ (val >> 12)) & mask;
}
3.1.3.2 inet_addr_type
/*
功能: 根据输入的ip地址,获取该地址对应的类型
1.若ip地址为0或者是广播地址,则返回RTN_BROADCAST
2.若ip地址为组播地址,返回RTN_MULTICAST
3. 若local 路由表存在(肯定存在),则首先将返回值设置为RTN_UNICAST
然后查找该路由表,若找到,则返回该路由项对应的type值,即为RTN_LOCAL;否则
则直接返回ret,即RTN_UNICAST
*/
unsigned inet_addr_type(__be32 addr)
{
struct flowi fl = { .nl_u = { .ip4_u = { .daddr = addr } } };
struct fib_result res;
unsigned ret = RTN_BROADCAST;
if (ZERONET(addr) || BADCLASS(addr))
return RTN_BROADCAST;
if (MULTICAST(addr))
return RTN_MULTICAST;
#ifdef CONFIG_IP_MULTIPLE_TABLES
res.r = NULL;
#endif
if (ip_fib_local_table) {
ret = RTN_UNICAST;
if (!ip_fib_local_table->tb_lookup(ip_fib_local_table,
&fl, &res)) {
ret = res.type;
fib_res_put(&res);
}
}
return ret;
}
3.1.4 fn_rehash_zone
功能:扩充一个fn_zone结构的变量的fz_hash的内存
static void fn_rehash_zone(struct fn_zone *fz)
{
struct hlist_head *ht, *old_ht;
int old_divisor, new_divisor;
u32 new_hashmask;
old_divisor = fz->fz_divisor;
switch (old_divisor) {
case 16:
new_divisor = 256;
break;
case 256:
new_divisor = 1024;
break;
default:
if ((old_divisor << 1) > FZ_MAX_DIVISOR) {
printk(KERN_CRIT "route.c: bad divisor %d!\n", old_divisor);
return;
}
new_divisor = (old_divisor << 1);
break;
}
new_hashmask = (new_divisor - 1);
#if RT_CACHE_DEBUG >= 2
printk("fn_rehash_zone: hash for zone %d grows from %d\n", fz->fz_order, old_divisor);
#endif
ht = fz_hash_alloc(new_divisor);
if (ht) {
memset(ht, 0, new_divisor * sizeof(struct hlist_head));
write_lock_bh(&fib_hash_lock);
old_ht = fz->fz_hash;
fz->fz_hash = ht;
fz->fz_hashmask = new_hashmask;
fz->fz_divisor = new_divisor;
fn_rebuild_zone(fz, old_ht, old_divisor);
fib_hash_genid++;
write_unlock_bh(&fib_hash_lock);
fz_hash_free(old_ht, old_divisor);
}
}
3.1.4.1 fn_rebuild_zone
/*
功能:将原hash链表结构的指针中的hash项,经过重新hash计算后,转移到fn_zone
结构的变量fz中的链表结构的指针fz_hash中。
*/
static inline void fn_rebuild_zone(struct fn_zone *fz,
struct hlist_head *old_ht,
int old_divisor)
{
int i;
for (i = 0; i < old_divisor; i++) {
struct hlist_node *node, *n;
struct fib_node *f;
hlist_for_each_entry_safe(f, node, n, &old_ht[i], fn_hash) {
struct hlist_head *new_head;
hlist_del(&f->fn_hash);
new_head = &fz->fz_hash[fn_hash(f->fn_key, fz)];
hlist_add_head(&f->fn_hash, new_head);
}
}
}
3.1.5 fib_find_node
功能:根据搜索关键字,在fn_zone变量fz中查找符合条件的fib_node变量
1.调用fn_hash,根据搜索关键字与fn_zone变量计算hash值为hash_index
2.根据hash值hash_index获取到hash链表的头部
3.调用函数hlist_for_each_entry遍历该链表,查找fib_node->fn_key等于传入的fn_zone,
若查找到则返回该fib_node变量;若没有找到返回NULL
static struct fib_node *fib_find_node(struct fn_zone *fz, __be32 key)
{
struct hlist_head *head = &fz->fz_hash[fn_hash(key, fz)];
struct hlist_node *node;
struct fib_node *f;
hlist_for_each_entry(f, node, head, fn_hash) {
if (f->fn_key == key)
return f;
}
return NULL;
}
3.1.6 fib_find_alias
功能:根据tos、priority查找符匹配的fib_alias变量
1.遍历链表fah ,查找tos小于传递的tos,且fib_priority大于或等于传递的prio的fib_alias变量
struct fib_alias *fib_find_alias(struct list_head *fah, u8 tos, u32 prio)
{
if (fah) {
struct fib_alias *fa;
list_for_each_entry(fa, fah, fa_list) {
if (fa->fa_tos > tos)
continue;
if (fa->fa_info->fib_priority >= prio ||
fa->fa_tos < tos)
return fa;
}
}
return NULL;
}
3.1.7 fib_insert_node
功能:讲一个fib_node变量添加到fn_zone变量的fz_hash[]链表中
1.调用fn_hash,计算该fib_node变量对应的hash值hash_index
2.根据计算的hash值hash_index,找到hash链表fb_zone->fz_hash[hash_index]
3.调用函数hlist_add_head,将fib_node添加到hash表fb_zone->fz_hash[hash_index]的表头
static inline void fib_insert_node(struct fn_zone *fz, struct fib_node *f)
{
struct hlist_head *head = &fz->fz_hash[fn_hash(f->fn_key, fz)];
hlist_add_head(&f->fn_hash, head);
}
3.1.8 fib_release_info
这个应该算是路由删除功能的代码,准备将路由删除功能单独作为一节进行分析。
功能:判断是否需要释放fib_info变量占用的内存,若需要释放,则调用函数fib_info_put执行释放内存操作
1.根据 fib_treeref的值来决定,是否将该fib_info变量从全局hash链表 数组fib_info_hash[]与fib_info_laddrhash[]中删除。若fib_treeref的值不为0,则程序返回
2.若是需要删除,则同时会将该fib_info关联的fib_info->fib_nh 从hash链表数组fib_info_devhash[]中删除
3.将fib_dead的值设置为1,主要是用于释放fib_info占用的内存时使用
4.调用函数fib_info_put,根据fib_clntref的值来决定是否需要是否fib_info所占用的内存
*/
void fib_release_info(struct fib_info *fi)
{
spin_lock_bh(&fib_info_lock);
if (fi && --fi->fib_treeref == 0) {
hlist_del(&fi->fib_hash);
if (fi->fib_prefsrc)
hlist_del(&fi->fib_lhash);
change_nexthops(fi) {
if (!nh->nh_dev)
continue;
hlist_del(&nh->nh_hash);
} endfor_nexthops(fi)
fi->fib_dead = 1;
fib_info_put(fi);
}
spin_unlock_bh(&fib_info_lock);
}
至此,基本上分析完了路由添加的代码处理流程,而对于程序设计流程基本上看了上一节数据结构之间的关联,对于路由的添加都会知道一个大致的步骤,本节主要是将linux中路由的添加的代码实现进行进一步的分析而已。