路由表(FIB)详解

宏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 chartb_id;
unsignedtb_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 chartb_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;
intfz_nent;
intfz_divisor;
u32fz_hashmask;
intfz_order;
u32fz_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_nodefn_hash;
struct list_headfn_alias;
u32fn_key;
};
fn_hash用于在zone中组织链表,关于内核的一些基本数据结构,我们将专门进行分析,这里不再赘述。fn_key即目的地址IP, fn_alias指向一个结构体struct fib_alias的链表,下面是结构struct fib_alias的定义:
struct fib_alias {
struct list_headfa_list;
struct rcu_head rcu;
struct fib_info*fa_info;
u8fa_tos;
u8fa_type;
u8fa_scope;
u8fa_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_nodefib_hash;
struct hlist_nodefib_lhash;
intfib_treeref;
atomic_tfib_clntref;
intfib_dead;
unsignedfib_flags;
intfib_protocol;
u32fib_prefsrc;
u32fib_priority;
u32fib_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]
intfib_nhs;
#ifdef CONFIG_IP_ROUTE_MULTIPATH
intfib_power;
#endif
#ifdef CONFIG_IP_ROUTE_MULTIPATH_CACHED
u32fib_mp_alg;
#endif
struct fib_nhfib_nh[0];
#define fib_devfib_nh[0].nh_dev
};
fib_treeref表示本路由信息项在struct fib_table结构的整个树型结构中被引用的次数,而fib_clntref是另一个引用计数。fib_dead表示本项当前是否是活的。 fib_protocol表示该路由信息是通过什么途径建立起来的,其可能有取值有:
#define RTPROT_UNSPEC0
#define RTPROT_REDIRECT 1//该路由是由ICMP重定义安装的。
#define RTPROT_KERNEL2//该路由是由内核安装的。
#define RTPROT_BOOT 33//该路由是在系统启动时安装的。
#define RTPROT_STATIC4//该路由是由管理员安装的。
除此之外,还有一些取值,不过是用于用户态的,我们当前在模块初始化过程中安装的路由都是RTPROT_KERNEL的。fib_prefsrc是我们的 local地址。最后,fib_nh是一个结构struct fib_nh的的数组,数组的大小由fib_nhs决定。该结构表示路由中的下一跳,下面是其定义:
struct fib_nh {
struct net_device*nh_dev;
struct hlist_nodenh_hash;
struct fib_info*nh_parent;
unsignednh_flags;
unsigned charnh_scope;
#ifdef CONFIG_IP_ROUTE_MULTIPATH
intnh_weight;
intnh_power;
#endif
#ifdef CONFIG_NET_CLS_ROUTE
__u32nh_tclassid;
#endif
intnh_oif;
u32nh_gw;
};
关于这个结构体,我们在以后用到时再进行分析。

    推荐阅读