路由數據庫之核心數據結構

這篇筆記將從上到下的方式來介紹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;
};

綜上,所有這些數據結構之間的關係可以用下圖來表示,圖片來源於這裏
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章