1. 程式人生 > >Linux 路由 學習筆記 之二 路由新增流程分析

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 p