1. 程式人生 > >路由資料庫之路由表項的新增

路由資料庫之路由表項的新增

對於AF_INET,如果已雜湊表方式組織路由項,那麼新增路由項功能實際上是由fn_hash_insert()實現的,該函式在路由表建立時被賦值給struct fib_table中tb_insert成員,這樣,當需要向該路由表新增路由項時,就可以回撥到該函式,下面就從這裡開始分析路由項的插入過程。

1. 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; //顯然的,IPv4的目的地址的掩碼長度不能大於32 if (cfg->fc_dst_len > 32) return -EINVAL; //根據目的地址掩碼長度找到路由區指標 fz = table->
fn_zones[cfg->fc_dst_len]; //如果該掩碼對應的路由區尚未分配,那麼呼叫fn_new_zone()分配一個新的路由區並將其加入到路由表中 if (!fz && !(fz = fn_new_zone(table, cfg->fc_dst_len))) return -ENOBUFS; //根據路由項的目的地址計算key值,該key值被用來從路由區的雜湊表中尋找指定的路由結點 key = 0; if (cfg->fc_dst) { if (cfg->fc_dst & ~FZ_MASK(fz)) return
-EINVAL; key = fz_key(cfg->fc_dst, fz); } //根據fib_config建立路由資訊 fi = fib_create_info(cfg); if (IS_ERR(fi)) return PTR_ERR(fi); //如果路由區中路由結點過多,會影響查詢效率,那麼對雜湊表進行擴充 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 //在路由結點的路由項列表中,那麼根據tos和priority尋找是否有匹配的路由項, //這種匹配只要求tos和priorit相同,後面我們稱這是一種基本匹配 fa = fib_find_alias(&f->fn_alias, tos, fi->fib_priority); //上述邏輯執行後,會產生三種結果: //1. fa!=NULL,說明路由表中已有相同的路由項,需要根據標記決定是否繼續插入 //2. fa==NULL,路由表中沒有和待插入項相同的路由項,可以插入 //3. 此時路由結點還有可能不存在,即f==NULL,所以可能還需要先新建路由結點 //對應上面的情形1,路由表中找到了和待插入路由基本匹配的路由項,那麼需要進一步判斷是否可以插入 if (fa && fa->fa_tos == tos && fa->fa_info->fib_priority == fi->fib_priority) { struct fib_alias *fa_first, *fa_match; err = -EEXIST; //如果使用者空間設定了NLM_F_EXCL標記,那麼表示不允許重複插入,返回-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 */ //從基本匹配的路由項的前一個結點開始搜尋是否有和待新增路由精確匹配的路由項(tos、priority、 //type、scope、fib_info),之所以是從前一個開始,是因為基本匹配可能也滿足精確匹配的條件。 //下面fa_first儲存當前已經找到的基本匹配的路由項,後面如果找到了精確匹配的路由項, //那麼用fa_match指向該路由項 fa_match = NULL; 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_info 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; //如果沒有設定追加標記,那麼插入首部 if (!(cfg->fc_nlflags & NLM_F_APPEND)) fa = fa_first; } //沒有精確匹配的路由項,需要新建,但是沒有設定新建標記,返回失敗 err = -ENOENT; if (!(cfg->fc_nlflags & NLM_F_CREATE)) goto out; err = -ENOBUFS; //操作的路由結點不存在,新建一個路由結點 if (!f) { new_f = kmem_cache_zalloc(fn_hash_kmem, GFP_KERNEL); if (new_f == NULL) goto out; INIT_HLIST_NODE(&new_f->fn_hash); INIT_LIST_HEAD(&new_f->fn_alias); new_f->fn_key = key; f = new_f; } //這裡沒看懂該嵌入式路由項的作用 new_fa = &f->fn_embedded_alias; if (new_fa->fa_info != NULL) { new_fa = kmem_cache_alloc(fn_alias_kmem, GFP_KERNEL); if (new_fa == NULL) goto out; } 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(cfg->fc_nlinfo.nl_net, -1); //向上層返回新增路由成功訊息 rtmsg_fib(RTM_NEWROUTE, key, new_fa, cfg->fc_dst_len, tb->tb_id, &cfg->fc_nlinfo, 0); return 0; out: if (new_f) kmem_cache_free(fn_hash_kmem, new_f); fib_release_info(fi); return err; }

從上面的新增過程可以看出,主要是路由項判重這裡有些繞,下面簡單總結一下:

如果路由表中已有基本匹配的路由項:

  1. 如果新增時設定了RTM_F_EXCL標記,那麼新增失敗
  2. 如果設定了RTM_F_REPLACE標記
    2.1. 如果有精確匹配的路由項,那麼如果精確匹配的路由項和基本匹配的路由項不是同一只,新增依然失敗;但是如果是一個,那麼說明要新增的路由項已經存在了,成功返回。
    2.2. 如果沒有精確匹配的路由項,那麼由於設定了替換標記,所以用新的替換舊的
  3. 如果沒有設定RTM_F_REPLACE標記
    3.1. 如果有精確匹配的路由項,那麼新增失敗
    3.2. 到這裡,說明是隻有基本匹配的路由項,但是沒有設定RTM_F_REPLACE標記,這種路由項可以共存,它們構成一個連結串列,並且標記RTM_F_APPEND會影響這些路由項的排列順序

如果路由表中沒有基本匹配的路由項:

  1. 如果沒有設定RTM_F_CREATE標記,由於這個時候需要新建路由項,所以返回失敗
  2. 一切正常,建立路由項並將其新增到路由表的指定位置

1.1 路由區分配

fn_new_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;
	
	//如果掩碼長度大於0,那麼初始路由區的雜湊桶(儲存路由結點)大小為16,否則為1
	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;
	}
	//fz_order就是掩碼長度
	fz->fz_order = z;
	//fz_mask是掩碼的大端表示
	fz->fz_mask = inet_make_mask(z);
	
	//新建了路由區,需要更新fz_next連結串列
	/* Find the first not empty zone with more specific mask */
	//fz_next連結串列是按照掩碼長度由大到小的順序維護的,所以這裡從z+1開始尋找
	for (i=z+1; i<=32; i++)
		if (table->fn_zones[i])
			break;
	write_lock_bh(&fib_hash_lock);
	if (i>32) {
	   	//新建的路由區的掩碼就是最長的,將其加入到fz_next的首部即可
		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;
	//路由表每變更一次,該變數就會加1。該變數是系統中所有路由表共享的
	fib_hash_genid++;
	write_unlock_bh(&fib_hash_lock);
	return fz;
}