Linux路由表实现(1)

本文的copyleft归gfree.wind@gmail.com所有,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,注明原作者及原链接,严禁用于任何商业用途。
作者: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的实现方式。


  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值