Linux 路由 学习笔记 之二 路由添加流程分析


 

基于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中路由的添加的代码实现进行进一步的分析而已。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值