這篇筆記將從上到下的方式來介紹Linux內核爲路由數據庫定義的數據結構,以及這些數據結構之間的關係。
源代碼路徑 | 說明 |
---|---|
include/net/ip_fib.h | 路由數據庫頭文件 |
net/ipv4/fib_hash.c | 哈希方式的路由數據庫相關實現 |
路由表的組織
系統可以僅支持local和main兩張表,也可以通過打開特性宏CONFIG_IP_MULTIPLE_TABLES以支持創建更多的路由表,由於後者使用廣泛,這裏就直接介紹多路由表的情況。
所有的路由表被組織在一個全局的哈希表中,該全局變量爲net->ipv4.fib_table_hash,其內存在路由數據庫初始化過程中的ip_fib_net_init()中被創建,具體見路由數據庫之初始化。
struct net {
...
struct netns_ipv4 ipv4;
...
}
struct netns_ipv4 {
...
struct hlist_head *fib_table_hash;
...
}
fib_table_hash的成員就是路由表struct fib_table。
路由表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];
};
無論最終是使用哈希方式還是Tire的方式組織路由項,路由表結構struct fib_table的定義是統一的,只是函數指針以及最後的tb_data的內容不同而已,下面看哈希方式組織路由項時相關數據結構的定義。
struct fn_hash
對於哈希方式的路由項組織算法,路由表struct fib_table中的tb_data字段指向的就是struct fn_hash,這兩個結構是一起分配的,它們在內存上是連續的。
struct fn_hash {
struct fn_zone *fn_zones[33]; //目的地址子網掩碼中1的個數爲數組索引
//子網掩碼越長,在fn_zone_list中的位置就越靠前
struct fn_zone *fn_zone_list;
};
fn_hash的組織思想也很簡單,就是按照路由項中目的地址的子網掩碼將路由項分類,IPv4的子網掩碼有32種,每種子網掩碼叫做一個zone(後面稱稱之爲路由區),所以有fn_zones[33]。此外,路由表中,並非每種子網掩碼都有對應的路由項,所以fn_zones[]的有些元素就是空的,爲了查找方便,還將非空的fn_zone結構以鏈表的方式組織起來,表頭就是fn_zone_list字段。
路由區: struct fn_zone
同一個路由區中的目的地址的子網掩碼長度相同,但是它們的網絡地址部分是可以不同的,比如192.168.1.0/24和193.168.1.0/24。
struct fn_zone {
//將路由區組織到fn_hash->fn_zone_list中
struct fn_zone *fz_next; /* Next not empty zone */
//不同網絡地址的路由項繼續用哈希表組織
struct hlist_head *fz_hash;
//記錄該路由區中路由項的個數
int fz_nent;
//下面兩個字段表示fz_hash哈希表的大小,由於哈希表的大小是根據路由項的數目動態調整的,
//所以這裏用了兩個變量來記錄
int fz_divisor; // 哈希表桶大小
u32 fz_hashmask; /* (fz_divisor - 1) */
#define FZ_HASHMASK(fz) ((fz)->fz_hashmask)
//路由區的階,實際上就是該路由區表示的子網掩碼的長度
int fz_order; /* Zone order */
//路由區子網掩碼的網絡字節序表示,比如255.255.255.0的網絡字節序
__be32 fz_mask;
#define FZ_MASK(fz) ((fz)->fz_mask)
};
路由結點: struct fib_node
路由區繼續劃分,它將相同網絡地址的路由項定義爲路由結點,但是由於相同網絡地址的路由項之間可以有其它路由參數不同,如TOS等,所以這些路由項並不完全相同,所以需要將這些路由項進一步組織(定義爲struct fn_alias),定義如下:
struct fib_node {
//將路由項組織到路由區的fz_hash中
struct hlist_node fn_hash;
//路由結點進一步將網絡地址相同,其它參數不同的路由項組織成雙向鏈表
struct list_head fn_alias;
//相同路由結點表示的同一個網絡地址的路由項,fn_key就是網絡地址
__be32 fn_key;
//該變量保存了該fib_node中第一個創建的fib_alias
struct fib_alias fn_embedded_alias;
};
路由項: struct fib_alias
真正唯一能夠表示一個路由項的數據結構是struct fib_alias。
struct fib_alias {
//網絡地址相同,其它參數不同的路由項共享一個路由結點,
//這些路由項在路由結點中組織成一個鏈表
struct list_head fa_list;
//fa_info存儲着當數據包匹配該路由項後,需要的一些信息,如下一跳給哪個網卡
struct fib_info *fa_info;
u8 fa_tos;
u8 fa_type;
u8 fa_scope;
u8 fa_state;
};
路由項信息struct fib_info
如下面註釋所述,之所以將一些路由信息再次抽象成struct fib_info,是爲了讓多個路由項之間能夠共享這些信息。
/*
* This structure contains data shared by many of routes.
*/
struct fib_info {
//系統中所有的fib_info實例都會被插入全局的fib_hash_info散列表中
struct hlist_node fib_hash;
//如果路由項配置了首選源地址,那麼該路由項就會被插入到全局的fib_info_laddrhash中
struct hlist_node fib_lhash;
struct net *fib_net;
//持有該fib_info的路由結點的個數(爲什麼不是fib_alias呢?)
int fib_treeref;
//路由查詢成功後,外部的TCB都會持有一個fib_info的引用計數
atomic_t fib_clntref;
//如果該字段爲1,表示路由項正在被刪除,這時該路由項將不能被使用
int fib_dead;
unsigned fib_flags;
//表示該路由是由哪個路由協議配置的,比如常見的有kernel、static
int fib_protocol;
//首選源IP地址,即如果設置了該字段,那麼當數據包匹配該路由項時,可以已該IP地址作爲源IP
__be32 fib_prefsrc;
//值越小,表示優先級越高,當添加路由時,如果沒有指定,那麼優先級爲0,即最高優先級
u32 fib_priority;
u32 fib_metrics[RTAX_MAX]; //和路由netlink配置中的RTA_METRICS屬性字段的內容對應,以屬性類型爲索引
#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]
//可用的下一跳數量,一般爲1,只有支持多路徑路由時,纔可能大於1
int fib_nhs;
#ifdef CONFIG_IP_ROUTE_MULTIPATH
int fib_power;
#endif
//下一跳信息
struct fib_nh fib_nh[0];
#define fib_dev fib_nh[0].nh_dev
};
下一跳地址: struct fib_nh
struct fib_nh {
//輸出網絡設備
struct net_device *nh_dev;
//所有的fib_nh實例被維護在全局的nh_hash中
struct hlist_node nh_hash;
//指向上一級的fib_info
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;
};
綜上,所有這些數據結構之間的關係可以用下圖來表示,圖片來源於這裏。