路由数据库之核心数据结构

这篇笔记将从上到下的方式来介绍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;
};

综上,所有这些数据结构之间的关系可以用下图来表示,图片来源于这里
在这里插入图片描述

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