路由表(FIB)详解

本文深入解析Linux内核中的路由策略宏CONFIG_IP_MULTIPLE_TABLES及其对路由表的影响,详细阐述FIB数据结构的组成及路由项的添加策略,包括主表(main)、本地表(local)的区别,以及路由项的类型、状态和相关数据结构如fn_hash、fib_info、fib_nh等的使用。
摘要由CSDN通过智能技术生成
 宏CONFIG_IP_MULTIPLE_TABLES表示路由策略,当定义了该宏,也即意味着内核配置了“路由策略”。产生的最大的不同就是内核可以使用多达256张FIB。其实,这256张FIB在内核中的表示是一个全局数组:
        struct fib_table *myfib_tables[RT_TABLE_MAX+1];
而宏RT_TABLE_MAX定义如下:
        enum rt_class_t
        {
            RT_TABLE_UNSPEC=0,
            RT_TABLE_DEFAULT=253,
            RT_TABLE_MAIN=254,
            RT_TABLE_LOCAL=255,
            __RT_TABLE_MAX
        };
        #define RT_TABLE_MAX (__RT_TABLE_MAX - 1)
    我们可以看到,虽然这张表多达256项,但枚举类型rt_class_t给出的表示最常用的也就三项,在系统初始化时,由内核配置生成的路由表只有RT_TABLE_MAIN,RT_TABLE_LOCAL两张。
    main表中存放的是路由类型为RTN_UNICAST的所有路由项,即网关或直接连接的路由。在myfib_add_ifaddr函数中是这样添加 main表项的:对于某个网络设备接口的一个IP地址,如果目的地址的网络号不是零网络(网络号与子网号全为零),并且它是primary地址,同时,它 不是D类地址(网络号与子网号占32位)。最后一个条件是:它不是一个环回地址(device上有flag IFF_LOOPBACK)。那么,就添加为main表项,如果是环回地址,则添加为local表的一个表项。
    在我们的系统中,有两个已开启的网络设备接口eth0和lo,eth0上配置的primary IP地址是172.16.48.2,所以,相应的,main表中就只有一项。为main表添加路由项的时候,该路由项的目的地址是子网内的所有主机(把主 机号部分字节清零),而对应于lo,在local表中也有一项,其类型为RTN_LOCAL(注:前一篇文章中的local表的hash 8中的路由项表述有误,类型应该是RTN_LOCAL,而不是RTN_BORADCAST)。
    而其它的路由项全部归入local表,主要是广播路由项和本地路由项。在我们的系统环境下,local表共有7项,每个网络设备接口占三项。分别是本地地 址(源跟目的地址一致),子网广播地址(主机号全为1),子网广播地址(主机号为零)。再加上一个lo的RTN_LOCAL项。
    现在我们再来看myfib_add_ifaddr函数的路由添加策略。对于一个传入的ip地址(结构struct in_ifaddr表示),如果它是secondary地址,首先要确保同一个网络设备接口上存在一个跟其同类型的primary地址(网络号与子网号完 全一致),因为,路由项的信息中的源地址全是primary的,secondary地址其实没有实际使用,它不会在路由表中产生路由项。然后,向 local表添加一项目的地址是它本身的,类型为RTN_LOCAL的路由项;如果该ip地址结构中存在广播地址,并且不是受限广播地址 (255.255.255.255),那么向local表添加一个广播路由项;然后,对符合加入main表的条件进行判断,如果符合,除了加入main 表,最后,如果不是D类地址,还要加入两个广播地址(其实,已经跟前面有重叠,很多情况下不会实际触发加入的动作,只要记住,一个ip地址项对应最多有两 个广播地址就可以了)。
    下面我们来看FIB的数据结构。一张FIB在内核中被表示为一个对象struct fib_table,之所以说它是一个对象,而不是一个结构,是因为它不仅仅是一组数据集合,它还包含了定义在该对象之上的方法,包括表项的插入,查找, 删除,刷新等等。下面是其定义:
        struct fib_table {
            unsigned char   tb_id;
            unsigned        tb_stamp;
            int (*tb_lookup)(struct fib_table *tb,
                    const struct flowi *flp, struct fib_result *res);
            int (*tb_insert)(struct fib_table *table, struct rtmsg *r,
                    struct kern_rta *rta, struct nlmsghdr *n,
                    struct netlink_skb_parms *req);
            int (*tb_delete)(struct fib_table *table, struct rtmsg *r,
                    struct kern_rta *rta, struct nlmsghdr *n,
                    struct netlink_skb_parms *req);
            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];
        };
    这些成员函数我们在分析代码时都会提供其完整的实现,现在重点关注其数据成员。tb_id表明该表的用途(RT_TABLE_LOCAL, RT_TABLE_MAIN等),同时也表明它在全局数组myfib_tables中的位置(RT_TABLE_LOCAL==255, RT_TABLE_MAIN==254)。tb_data是一个很重要的数据成员,它包含了所在FIB的全部路由信息,可能会有点令人费>,因为它 的类型仅仅是一个unsigned char的数组而已,甚至更奇怪的是,它的长度是零,也就是说,根本不存在。看了下面的代码就可以明了:
    struct fib_table *tb = kmalloc( sizeof(struct fib_table)
                                + sizeof(struct fn_hash), GFP_KERNEL );
    memset( tb->tb_data, 0, sizeof(struct fn_hash) );
    所以,tb_data实际上是一个指向结构struct fn_hash的指针。下面是结构struct fn_hash的定义:
        struct fn_hash{
            struct fn_zone  *fn_zones[33];
            struct fn_zone  *fn_zone_list;
        };
    在解释struct fn_hash之前,先看一下strut fn_zone:
        struct fn_zone{
            struct fn_zone      *fz_next;
            struct hlist_head   *fz_hash;
            int         fz_nent;
            int         fz_divisor;
            u32         fz_hashmask;
            int         fz_order;
            u32         fz_mask;
        };
    这是一个区域,所有目的地址长度相同的路由项划入同一个区域,以链表的形式组织在fz_hash成员中,同时,fz_order记录目的地址长度, fz_mask为目的地址掩码(比如:fz_order为24,则fz_mask为ffffff)。比如,在我们的系统配置环境中,两个网络设备接口有共 七个路由项。其中的6项,其目的地址长度为6,归入同一个zone中,放在fz_hash成员中。成员fz_nent表明该zone中的路由项的数量,为 6。fz_hash其实是一个哈项数组,共有fz_divisor项(初始为16),fz_hashmask为数组的掩码(初始为f)。路由项以目的IP 地址为主键,定位到数组的某一项。
    对于ipv4来讲,目的地址不会超过32位,所以fz_order的取值范围是0-32。所以,struct fn_hash中fn_zones被定义为一个具有33项的数组,对应33个区域。在我们的配置系统中,fn_zones[32]和fn_zones [8]被用到了。同时,fn_zone_list把fn_zones中的已创建出来的zone按fz_order从大到小的顺序组织成一个链表。这些做法 都是出于效率的考虑。
    所以,fn_hash是一个组织路由域的数据结构,同一个域里的fn_zone通过fz_next组织成一个链表。
    同一个域中,所有的路由项组织在哈希表fz_hash中,一个路由项由结构struct fib_node表示。下面是该结构的定义:
        struct fib_node {
            struct hlist_node   fn_hash;
            struct list_head    fn_alias;
            u32         fn_key;
        };
    fn_hash用于在zone中组织链表,关于内核的一些基本数据结构,我们将专门进行分析,这里不再赘述。fn_key即目的地址IP, fn_alias指向一个结构体struct fib_alias的链表,下面是结构struct fib_alias的定义:
    struct fib_alias {
        struct list_head    fa_list;
        struct rcu_head rcu;
        struct fib_info     *fa_info;
        u8          fa_tos;
        u8          fa_type;
        u8          fa_scope;
        u8          fa_state;
    };
    fa_tos表示服务类型,一般为0,即一般服务;fa_type的值在我们的系统中为RTN_LOCAL, RTN_UNICAST和RTN_BORADCAST。fa_scope其实表示的是到目的地址的距离,对本地接收来说,就是 RT_SCOPE_HOST,对子网内广播和子网内其它地址来说,就是RT_SCOPE_LINK。fa_state只有在本地接收时,为 FA_S_ACCESSED,其它暂无定义。
    fa_info作为struct fib_alias的成员,含有更为详细的路由项信息。下面是其定义:
        struct fib_info {
            struct hlist_node   fib_hash;
            struct hlist_node   fib_lhash;
            int         fib_treeref;
            atomic_t    fib_clntref;
            int         fib_dead;
            unsigned    fib_flags;
            int         fib_protocol;
            u32         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
#ifdef CONFIG_IP_ROUTE_MULTIPATH_CACHED
            u32         fib_mp_alg;
#endif
            struct fib_nh       fib_nh[0];
#define fib_dev     fib_nh[0].nh_dev
        };
    fib_treeref表示本路由信息项在struct fib_table结构的整个树型结构中被引用的次数,而fib_clntref是另一个引用计数。fib_dead表示本项当前是否是活的。 fib_protocol表示该路由信息是通过什么途径建立起来的,其可能有取值有:
        #define RTPROT_UNSPEC   0
        #define RTPROT_REDIRECT 1   //该路由是由ICMP重定义安装的。
        #define RTPROT_KERNEL   2   //该路由是由内核安装的。
        #define RTPROT_BOOT 3   3   //该路由是在系统启动时安装的。
        #define RTPROT_STATIC   4   //该路由是由管理员安装的。
    除此之外,还有一些取值,不过是用于用户态的,我们当前在模块初始化过程中安装的路由都是RTPROT_KERNEL的。fib_prefsrc是我们的 local地址。最后,fib_nh是一个结构struct fib_nh的的数组,数组的大小由fib_nhs决定。该结构表示路由中的下一跳,下面是其定义:
        struct fib_nh {
            struct net_device   *nh_dev;
            struct hlist_node   nh_hash;
            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;
            u32         nh_gw;
        };
    关于这个结构体,我们在以后用到时再进行分析。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值