路由資料庫之路由表組織相關資料結構
1. 全域性路由雜湊表
核心將所有的路由表(struct fib_table)組織到雜湊表net->ipv4.fib_table_hash中,如下:
//如果不支援策略路由,那麼系統中固定只有兩張路由表,所以雜湊表大小就是2,
//如果支援策略路由,那麼系統中可以支援多個表,所以定義一個衝突鏈數目為256的雜湊表
//來儲存這些路由表
#ifdef CONFIG_IP_ROUTE_MULTIPATH
#define FIB_TABLE_HASHSZ 2
#else
#define FIB_TABLE_HASHSZ 256
#endif
//分配雜湊表並初始化各個衝突鏈的頭結點
static int __net_init ip_fib_net_init(struct net *net)
{
...
net->ipv4.fib_table_hash = kzalloc(
sizeof(struct hlist_head)*FIB_TABLE_HASHSZ, GFP_KERNEL);
if (net->ipv4.fib_table_hash == NULL)
return -ENOMEM;
for (i = 0; i < FIB_TABLE_HASHSZ; i++)
INIT_HLIST_HEAD(&net->ipv4.fib_table_hash[ i]);
...
}
2. 路由表struct fib_table
struct fib_table {
//將該路由錶鏈入系統全域性雜湊表中
struct hlist_node tb_hlist;
//每張路由表在系統中都有一個唯一的ID
u32 tb_id;
unsigned tb_stamp;
int tb_default;
//下面為一組操作該路由表的函式,這些成員在路由表建立時被賦值
int (*tb_lookup)(struct fib_table *tb, const struct flowi *flp, struct fib_result *res) ;
int (*tb_insert)(struct fib_table *, struct fib_config *);
int (*tb_delete)(struct fib_table *, struct fib_config *);
int (*tb_dump)(struct fib_table *table, struct sk_buff *skb,
struct netlink_callback *cb);
int (*tb_flush)(struct fib_table *table);
void (*tb_select_default)(struct fib_table *table,
const struct flowi *flp, struct fib_result *res);
//這裡有一個零長度陣列,說明緊接著該結構後面會有內容
unsigned char tb_data[0];
};
3. 路由區struct fn_zone
注意到路由表結構struct fib_table的末尾有一個零長度陣列,在實際分配記憶體時,會在該結構末尾再分配一個struct fn_hash結構,該結構的定義如下:
struct fn_hash {
struct fn_zone *fn_zones[33];
struct fn_zone *fn_zone_list;
};
成員fn_zones是一個長度為33的指標陣列,系統將路由項按照目的地址的子網掩碼長度不同劃分為33個區管理(對於IPv4,掩碼長度可取的值為0~32,所以是33個),我們後續稱fn_zone為路由區。
成員fn_zone_list用於將所有活動的(含有路由表項)的路由區按照子網掩碼長度由大到小的順序串聯成單鏈表,之所以這樣設計,是為了更加方便按照最大長度匹配原則進行路由查詢。
fib_table和fn_hash的記憶體結果如下圖所示:
一個路由區管理的是一組路由表項,這些路由表項的目的地址的子網掩碼長度相同。然而,子網掩碼相同,網路地址還可以不同,為了高效的存取,路由區進一步用雜湊表來組織這些不同的網路地址(每個網路地址對應一個fib_node,後續我們稱該結構為路由結點),路由區的定義如下:
struct fn_zone {
//與struct fn_hash結構中的fn_zone_list一起,將所有活動的路由區按照子網掩碼長度由大到小的順序串聯成單鏈表
struct fn_zone *fz_next; /* Next not empty zone */
//儲存struct fib_node(即路由結點)的雜湊表指標
struct hlist_head *fz_hash; /* Hash table pointer */
//雜湊表中路由結點的數目
int fz_nent; /* Number of entries */
//雜湊表桶大小,雜湊表在實際使用過程中可能會重新分配更大的空間,避免衝突過多
int fz_divisor; /* Hash divisor */
//值為fz_divisor-1,用於計算雜湊值
u32 fz_hashmask; /* (fz_divisor - 1) */
#define FZ_HASHMASK(fz) ((fz)->fz_hashmask)
//子網掩碼中1的數目,即對於子網掩碼255.255.255.0,那麼fz_order為24
int fz_order; /* Zone order */
//子網掩碼的數值大端表示
__be32 fz_mask;
#define FZ_MASK(fz) ((fz)->fz_mask)
};
4. 路由結點struct fib_node
路由結點將所有目的地址的網路地址相同的路由表項組織到一起,由於目的地址相同,但是還可以根據tos、priority等引數配置不同的路由項,所以路由結點下面也可能會有多個路由表項,路由結點的定義如下:
struct fib_node {
//用於將路由結點組織到路由區的雜湊表中
struct hlist_node fn_hash;
//路由結點將可能存在的多個路由項組織成連結串列,連結串列成員為struct fib_alias
struct list_head fn_alias;
//該路由結點代表的網路地址,即IP地址與子網掩碼相與後的結果
__be32 fn_key;
//分配路由節點的時候同時也分配一個路由別名,所以稱為嵌入式的
struct fib_alias fn_embedded_alias;
};
5. 路由表項struct fib_alias
真正的路由表項是struct fib_alias,所有的目的地址的網路地址相同的路由表項被組織成一個路由結點,它們被路由結點組織成連結串列,路由表項的定義如下:
struct fib_alias {
//路由表項被路由結點組織成一個連結串列
struct list_head fa_list;
//命中該路由後,對資料包應該執行怎樣的操作,即如何路由,這些資訊被組織成struct fib_info
struct fib_info *fa_info;
//服務型別TOS,對於IP資料包,對應其頭部的TOS欄位
u8 fa_tos;
//路由型別
u8 fa_type;
//路由作用範圍
u8 fa_scope;
//路由項的狀態
u8 fa_state;
#ifdef CONFIG_IP_FIB_TRIE
struct rcu_head rcu;
#endif
};
6. 路由資訊struct fib_info
在實際中,可能有很多的路由表項命中後要執行的動作是一樣的,這些資訊是可以共用的,沒有必要每個路由表項都維護一份,所以將這部分資訊抽象成路由資訊,這就是struct fib_info,該結構定義如下:
/*
* This structure contains data shared by many of routes.
*/
struct fib_info {
//用於將所有的fib_info結構組織到fib_info_hash佇列中
struct hlist_node fib_hash;
//用於將所有的fib_info結構組織到fib_hash_laddrhash佇列中
struct hlist_node fib_lhash;
struct net *fib_net;
//該fib_info結構的引用計數
int fib_treeref;
atomic_t fib_clntref;
int fib_dead;
//RTNH_F_DEAD: 表示該路由資訊的下一跳地址無效
unsigned fib_flags;
int fib_protocol;
//優選IP地址,當路由所指網絡卡有多個IP地址時,可以通過該欄位指示優先選用什麼IP地址
__be32 fib_prefsrc;
u32 fib_priority;
u32 fib_metrics[RTAX_MAX];
#define fib_mtu fib_metrics[RTAX_MTU-1]
#define fib_window fib_metrics[RTAX_WINDOW-1]
#define fib_rtt fib_metrics[RTAX_RTT-1]
#define fib_advmss fib_metrics[RTAX_ADVMSS-1]
//fib_nh陣列的元素個數,即可用下一條地址數目,通常為1,只有在支援多路徑路由時
//該值才有可能大於1
int fib_nhs;
#ifdef CONFIG_IP_ROUTE_MULTIPATH
int fib_power;
#endif
//零長度陣列,指向一個struct fib_nh,該結構決定了下一跳地址資訊
struct fib_nh fib_nh[0];
#define fib_dev fib_nh[0].nh_dev
};
7. 路由下一跳struct fib_nh
進一步考慮,一臺機器的網路裝置畢竟是有限的,所以需要路由表項最終的下一條地址資訊是重複的,將這種下一跳資訊抽象出來就是struct fib_nh,定義如下:
struct fib_nh {
struct net_device *nh_dev;
struct hlist_node nh_hash;
struct fib_info *nh_parent;
unsigned nh_flags;
unsigned char nh_scope;
#ifdef CONFIG_IP_ROUTE_MULTIPATH
int nh_weight;
int nh_power;
#endif
#ifdef CONFIG_NET_CLS_ROUTE
__u32 nh_tclassid;
#endif
int nh_oif;
__be32 nh_gw;
};
以上就是路由表本身涉及到的核心資料結構,實際上還是非常複雜的,它們之間的連線關係見下圖: