本文的copyleft归gfree.wind@gmail.com所有,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,注明原作者及原链接,严禁用于任何商业用途。
作者:gfree.wind@gmail.com
博客:linuxfocus.blog.chinaunix.net
作者:gfree.wind@gmail.com
博客:linuxfocus.blog.chinaunix.net
今天开始学习kernel中的路由表FIB的实现,kernel支持两种FIB的存储方式:一种是hash,另外一种是单词查找树trie。分别由编译选项CONFIG_IP_FIB_HASH和CONFIG_IP_FIB_TRIE决定。
(据说新版本的kernel已经去掉了hash的实现方式)
下面首先看一下FIB中的数据结构,大部分数据结构都在文件ip_fib.h中
struct fib_config {
//目的地址的掩码
u8 fc_dst_len;
//TOS
u8 fc_tos;
//路由协议,其实更像是指该条路由是从何而来,参见RTPROT_STATIC等宏
//RTPROT_STATIC表示该route为管理员添加的静态路由,RTPROT_ZEBRA为由zebra添加的路由
u8 fc_protocol;
//参见rt_scope_t的定义
u8 fc_scope;
//类型如RTN_UNICAST:直连路由,参见类似的定义
u8 fc_type;
/* 3 bytes unused */
//指示哪个路由表,如RT6_TABLE_MAIN
u32 fc_table;
//目的地址
__be32 fc_dst;
//网关
__be32 fc_gw;
//出口的网卡
int fc_oif;
//路由标志
u32 fc_flags;
//优先级
u32 fc_priority;
//prefer 源地址,暂不知道用途
__be32 fc_prefsrc;
struct nlattr *fc_mx;
struct rtnexthop *fc_mp;
int fc_mx_len;
int fc_mp_len;
u32 fc_flow;
u32 fc_nlflags;
struct nl_info fc_nlinfo;
};
struct fib_config主要用于将外部的route的配置/参数形式转为FIB的内部配置形式。如使用IP命令添加/删除route时,或者路由daemon向linux添加/删除时,都需要将其传入系统API的参数转为FIB的内部配置结构。
/*
* This structure contains data shared by many of routes.
*/
struct fib_info {
struct hlist_node fib_hash;
struct hlist_node fib_lhash;
struct net *fib_net;
int fib_treeref;
atomic_t fib_clntref;
int fib_dead;
unsigned fib_flags;
int fib_protocol;
__be32 fib_prefsrc;
u32 fib_priority;
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]
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_info用于存储路由条目的一些共用的参数。
struct fib_table {
/* 负载成员,用于将所有的fib_table链接起来 */
struct hlist_node tb_hlist;
u32 tb_id;
int tb_default;
/* 路由表数据:因为有hash和trie两种方式,所以使用零长数组 */
unsigned char tb_data[0];
};
恩,下面开始学习代码,对于路由来说,最重要的就是路由的查询:
static inline int fib_lookup(struct net *net, const struct flowi *flp,
struct fib_result *res)
{
struct fib_table *table;
/*
先查询本地地址路由表
本地路由表保存本地地址,多播等,属于需要发送到本机的地址信息。
*/
table = fib_get_table(net, RT_TABLE_LOCAL);
if (!fib_table_lookup(table, flp, res))
return 0;
/*
再查询main路由表,这个是咱们设置路由或路由daemon设置的路由表
*/
table = fib_get_table(net, RT_TABLE_MAIN);
if (!fib_table_lookup(table, flp, res))
return 0;
return -ENETUNREACH;
}
fib_get_table比较简单,就是通过id,从net->ipv4.fib_table_hash中取出对应的路由表——对于IPv4来说。
那么查找路由的核心函数就为fib_table_lookup,这个函数的实现依赖于编译选项,共有两种实现方式,hash和trie。其中hash的实现比较简单,今天先学习hash的实现方式。
int fib_table_lookup(struct fib_table *tb,
const struct flowi *flp, struct fib_result *res)
{
int err;
struct fn_zone *fz;
/* 得到真正的route hash表 */
struct fn_hash *t = (struct fn_hash *)tb->tb_data;
read_lock(&fib_hash_lock);
/* 按顺序从fn_zone中查找路由,详细解释见后文 */
for (fz = t->fn_zone_list; fz; fz = fz->fz_next) {
struct hlist_head *head;
struct hlist_node *node;
struct fib_node *f;
/* 计算key,实际就是dst的网络部分 */
__be32 k = fz_key(flp->fl4_dst, fz);
/* 计算hash */
head = &fz->fz_hash[fn_hash(k, fz)];
/* 在桶中遍历所有路由条目 */
hlist_for_each_entry(f, node, head, fn_hash) {
/* 地址网络部分的值不同,该路由肯定不符,跳过 */
if (f->fn_key != k)
continue;
/* 对路由进行其它的比较,如tos等 */
err = fib_semantic_match(&f->fn_alias,
flp, res,
fz->fz_order);
if (err <= 0)
goto out;
}
}
err = 1;
out:
read_unlock(&fib_hash_lock);
return err;
}
从上面看,路由表的hash查找是比较简单的。唯一的疑惑就是这个struct fn_zone。
下面是路由hash表的数据结构
struct fn_hash {
struct fn_zone *fn_zones[33];
struct fn_zone *fn_zone_list;
};
路由hash表将路由条目按照掩码长度分到不同的33个fn_zones中,掩码长度为0即对应fn_zones[0],掩码长度为1即fn_zones[1],一直到掩码长度为32,则对应fn_zones[32]。而fn_zone_list指向的是最长的有路由条目的fn_zones。这样查找路由的时候,通过fn_zone_list顺序查找,从而实现了路由的最佳匹配。
今天基本上学习了kernel的路由表的hash实现方式。下面有可能将hash实现的完整的路由数据组织结构写出来,或者转到路由表trie的实现方式。