IPv4路由数据库之hash方式路由表数据结构

本文介绍了IPv4路由数据库中采用哈希方式实现的路由表结构,包括路由表私有数据对象fn_hash、路由区fn_zone、路由结点fib_node、路由项fib_alias和下一跳地址fib_nh。fn_hash按子网掩码长度分类路由项,fn_zone_list链表按掩码长度排序,便于高效查找。fib_node和fib_alias分别组织目的网络相同的路由项和到达同一网络的路由,而fib_info和fib_nh存储路由信息和下一跳地址。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


之前的笔记介绍过,IPv4路由数据库是支持用不同数据结构组织路由表的,这篇笔记介绍哈希表方式的路由表实现中涉及的数据结构,就是这些数据结构组成了路由表,理解它们之间的组织关系非常重要。涉及的文件有:

源代码路径说明
include/net/ip_fib.hIPv4路由数据库头文件

路由表私有数据对象: fn_hash

对于哈希方式实现的路由表,路由表对象fib_table中的tb_data字段指向数据结构就是 fn_hash,在内存分配时和fib_table一起分配,所以它们在内存上是连续的。

struct fn_hash {
	struct fn_zone *fn_zones[33]; // 路由项目的地址子网掩码长度为数组索引
	// 子网掩码越长,在fn_zone_list中的位置就越靠前
	struct fn_zone *fn_zone_list;
};

fn_hash的设计思想很简单:它根据路由项目的地址的子网掩码长度将路由项分类,IPv4的子网掩码有33种(包括长度0和长度32),每种分类叫做一个zone(后面称称之为路由区),所以fn_zones的长度为33。此外,由于实际的路由表中,并非每个路由区都会配置路由项,也就时说有些fn_zones[]是空的,为了查找更加高效,也将那些非空的路由区组织到fn_zone_list链表中。

特别注意:fn_zone_list中的路由区是按照子网掩码长度排序的,子网掩码长度越长,位置越靠前。这种设计依然是为了方便路由查找。因为子网掩码越长,表示该路由项的目的地网络越具体,这种路由项在查找时当然应该优先匹配。路由区的创建见fib_new_zone()。

路由区: 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;
	// 用哈希表组织到达不同网络的路由项,哈希算法就是目的地址的网络号,见fz_key()
	struct hlist_head *fz_hash;
	// 记录该路由区中路由项的个数
	int	fz_nent;
	// 下面两个字段表示fz_hash哈希表的大小,哈希表的大小会根据路由项的数目动态调整,
	int	fz_divisor;	// fz_hash哈希表桶大小
	u32	fz_hashmask; /* (fz_divisor - 1) */
#define FZ_HASHMASK(fz)		((fz)->fz_hashmask)
	// 路由区的阶,就是该路由区对应的子网掩码长度,比如24
	int	fz_order;
	// 路由区对应的子网掩码的网络字节序表示,比如255.255.255.0的网络字节序
	__be32	fz_mask;
#define FZ_MASK(fz)		((fz)->fz_mask)
};

路由结点: fib_node

路由区继续划分为路由结点,显然,路由结点用于组织那些到达同一个目的网络的路由项。由于这些路由项之间的其它路由参数可以不同,如TOS,所以到达同一个目的网络的路由项也是有可能会有多个的,因此需要将这些路由项进一步组织。

struct fib_node {
	// 将路由结点组织到路由区的fz_hash哈希表中
	struct hlist_node fn_hash;
	// 到达相同网络的路由项进一步以链表组织组织到一起
	struct list_head fn_alias;
	// 该路由结点在路由区的fz_hash哈希表中的索引,实际上就是该路由结点对应的网络号
	__be32	fn_key;
	// 实际中,到达同一个网络的的路由项大多数情况下只有一个,所以这里设置一个内嵌的变量方便访问,
	// 它记录了该路由节点的第一条路由项
	struct fib_alias fn_embedded_alias;
};

路由项: fib_alias

真正代表一条路由的数据结构是fib_alias,之所以叫“别名”,个人理解是因为它描述的是到达一个网络的路由,而对于IP网络,到达一个网络和到达网络内的任意主机实际是一回事。

struct fib_alias {
	// 将路由项组织到路由结点的fn_alias链表中
	struct list_head fa_list;
	// fa_info保存了路由项所包含的所有信息
	struct fib_info	*fa_info;
	u8 fa_tos; // 路由项的tos
	u8 fa_type; // 路由项的类型
	u8 fa_scope; // 路由项的作用域
	u8 fa_state; // 路由项的状态
};

路由项信息: fib_info

如下面注释所述,之所以将一些路由信息再次抽象出fib_info,是为了让多个路由项之间能够共享这些信息。

// 用于组织fib_info对象的全局哈希表和计数器
static DEFINE_SPINLOCK(fib_info_lock);
static struct hlist_head *fib_info_hash;
static struct hlist_head *fib_info_laddrhash;
static unsigned int fib_hash_size;
static unsigned int fib_info_cnt; // 当前路由项信息个数

/*
 * 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;
	// 和路由netlink配置中的RTA_METRICS属性字段的内容对应,以属性类型为索引
	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]
	// 可用的下一跳数量,一般为1,只有支持多路径路由时,才可能大于1
	int	fib_nhs;
#ifdef CONFIG_IP_ROUTE_MULTIPATH
	int			fib_power;
#endif
	// 下一跳信息,0长度数组,内存分配时紧挨着fib_info,如果支持多路径路由,那么该数组长度大于1
	struct fib_nh fib_nh[0];
#define fib_dev		fib_nh[0].nh_dev
};

下一跳地址: fib_nh

特别注意的是,在内存上,fib_nh和fib_info是连续的。

// 将所有fib_info对象的下一跳地址fib_nh对象组织到全局哈希表中
#define DEVINDEX_HASHBITS 8
#define DEVINDEX_HASHSIZE (1U << DEVINDEX_HASHBITS)
static struct hlist_head fib_info_devhash[DEVINDEX_HASHSIZE];

struct fib_nh {
	// 输出网络设备对象
	struct net_device *nh_dev;
	// 所有的fib_nh实例被维护在全局的哈希表fib_info_devhash中
	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;
	// 路由项的网关IP地址,即匹配该路由项时,下一跳应该将数据包交给谁
	__be32 nh_gw;
};

综上,所有这些数据结构之间的关系可以用下图来表示,图片来源于这里
在这里插入图片描述
从上面可以看出,路由区、路由结点都是为了组织路由表的,真正的路由项信息包含在fib_alias、fib_info和fib_nh三个数据结构中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值