这篇笔记将从上到下的方式来介绍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;
};
综上,所有这些数据结构之间的关系可以用下图来表示,图片来源于这里。