原文地址:TCP/IP学习(38)——Kernel中路由表的实现(1) 作者:GFree_Wind
本文的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的实现方式。