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