1. 程式人生 > >內核路由表設置

內核路由表設置

net multiple gist 刪除 connect cif 當我 lse nis

一、內核中路由表

在ip_rt_init函數中,初始化了一個緩沖結構:

if (!rt_hash_table)
panic("Failed to allocate IP route cache hash table\n");

註意:這個並不是內核中我們創建的靜態路由表,這個靜態路由表是我們通過 ip route add添加的,這個添加的內容是添加到fib_init中創建的fib表,這個表是一個靜態的概念,也就是用戶設置或者某些可靠途徑設置的路由表類型。這些路由表是正規軍,也就是這些路由表是查找路由的框架性通配符,拿makefile中的例子,這些是一些通配符規則,但是某些精確的內容則是特定規則。

二、網卡設置對路由的影響

void __init ip_fib_init(void)
{
#ifdef CONFIG_PROC_FS
proc_net_create("route",0,fib_get_procinfo);
#endif /* CONFIG_PROC_FS */

#ifndef CONFIG_IP_MULTIPLE_TABLES
local_table = fib_hash_init(RT_TABLE_LOCAL);
main_table = fib_hash_init(RT_TABLE_MAIN);
#else
fib_rules_init();
#endif

register_netdevice_notifier(&fib_netdev_notifier);

register_inetaddr_notifier(&fib_inetaddr_notifier);
}

註意這裏創建的路由表並不是rt_hash_table,而是local_table和main_table,後兩者是真正的內核靜態和backbone路由表,通過cat /prot/net/route文件可以看到系統中的靜態路由表配置信息。

這裏還有兩個重要的通知鏈接口,其中就是在添加或者刪除一個網卡的時候,此時將會添加一個路由,這個路由就是我們最為常見的 <網卡地址 + 網卡掩碼> 的一個路由表,這個是一個默認路由表,也是內核中一個比較可靠、自動的路由表

三、rt_cache的由來

當我們發送或者接受一個報文的時候都可以在其中添加新的路由表類型。當我們向一個特定的IP發送了報文的時候,就可以將這個查找的過程省略。也就是我們這裏cache了路由的查找過程。也就是對於特定的確定目的IP,它對應的網口信息是確定的,也就是我們可以確定這個報文應該走哪個網口。

另一方面,當我們從某個網口接受到一個報文的時候,我們也可以從這個接受的報文中獲得一個有用的信息,者也就是一個學習的過程。這是一個我們容易忽略的問題,就是從接受的報文中學習到路由信息,這個路由包含了源IP地址以及其MAC地址

通過 cat /proc/net/rt_cache可以看到,其中緩沖的大部分都是確定地址的一個發送規則,並且這個變化比較快。包括我們沒有發送成功的報文,在其中都有緩沖,這樣也是有好處的,因為這樣可以說明某個網絡地址是不可達的,並且及時我們再次查詢一下靜態路由表,這個查找的結果應該還是不會變化的。

四、從IP層到MAC層

ip_queue_xmit--->>>ip_queue_xmit2--->>> return skb->dst->output(skb);
在這個函數中完成一次輸出

ip_finish_output2--->>>dst->neighbour->output(skb)

一個neighbour的分配在arp_bind_neighbour函數中完成,

rt_intern_hash-->>

其中的這個rt_inern_hash就是我們剛才說的在學習到一個mac報文或者是在從靜態路由表中找到一個目的地址的路由之後,在這個接口完成內部的一些hash,同時也分配了一個neighbour接口,通過這個接口可以摘掉系統中的所有的鄰接系統。

在arp_constructor函數中,對於這個neighbour的output的設置為:

if (neigh->nud_state&NUD_VALID)
neigh->output = neigh->ops->connected_output;
else
neigh->output = neigh->ops->output;

由於generic的設置為

static struct neigh_ops arp_generic_ops = {
family: AF_INET,
solicit: arp_solicit,
error_report: arp_error_report,
output: neigh_resolve_output,
connected_output: neigh_connected_output,
hh_output: dev_queue_xmit,
queue_xmit: dev_queue_xmit,
};

所以這裏就是執行neighbour_resolve_output函數完成的真正的輸出

五、路由表的查詢

正如作者所有,這裏的ip_route_output_slow是主要的路由表操作,正是這個函數完成了路由表的查詢

/*
* Major route resolver routine.
*/

int ip_route_output_slow(struct rtable **rp, const struct rt_key *oldkey)

在該函數中,使用的主要操作就是在fib_lookup

if (fib_lookup(&key, &res)) {
對於簡單的查找,一般是通過

fn_hash_lookup(struct fib_table *tb, const struct rt_key *key, struct fib_result *res)
函數完成路由的查找,這個函數其實也比較簡單,就是按照路由中最有的配置來完成。舉例來說,當我們路由表配置的是

192.254.0.0 255.255.0.0. eth0

192.254.100,0 255.255.255.0 eth1

這樣的話,當我們遇到一個

192.254.100.200

的時候,明顯地,我們應該選擇eth1,。或者簡單的說,我們應該尋則掩碼最多的哪一個路由表,從而匹配度更加的高

在fib_hash.c的實現中,其中的確有一個zone的概念,總共分了32個zone。所有的路由按照掩碼來進行劃分,然後在lookup的時候從其中進行匹配。

在這個結構中完成了fz_next和fn_next兩個鏈表,其中的fz鏈表是一個以zone為單位的鏈表,而接下來的fn_next則是以node為單位進行的節點連接

這個表為什麽沒有按照循環查找,我想是一個簡單的優化。因為可能很多的zone可能都是空的,所以循環是通過

fn_hash_lookup(struct fib_table *tb, const struct rt_key *key, struct fib_result *res)
{
int err;
struct fn_zone *fz;
struct fn_hash *t = (struct fn_hash*)tb->tb_data;

read_lock(&fib_hash_lock);
for (fz = t->fn_zone_list; fz; fz = fz->fz_next) {
struct fib_node *f;
fn_key_t k = fz_key(key->dst, fz);
來完成的。

另一方面,由於key(也就是目的地址)是一個32bits的整數,而zone的長度是32,或者說zone的下表是該路由表項中掩碼的長度

其中一個表格中,這一點在fn_hash_insert函數中可以看到:

fz = table->fn_zones[z];
其中同一個fn_zone中的路由表項具有相同的路由掩碼長度

static struct fn_zone *
fn_new_zone(struct fn_hash *table, int z)

函數中,新添加的zone之間同樣是按照掩碼從長到短的順序通過fz_next連接在一起的,因為下面的for循環是從z+1開始,然後將新的表項插入到該項的後面

/* 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;

內核路由表設置