之前的笔记介绍过,IPv4路由数据库是支持用不同数据结构组织路由表的,这篇笔记介绍哈希表方式的路由表实现中涉及的数据结构,就是这些数据结构组成了路由表,理解它们之间的组织关系非常重要。涉及的文件有:
源代码路径 | 说明 |
---|---|
include/net/ip_fib.h | IPv4路由数据库头文件 |
路由表私有数据对象: 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三个数据结构中。