有所增添
在协议栈的FIB(Forwarding Information Base)模块中,结构体struct fib_info是一个很基本的单位,它表示一个路由信息,一个路由信息可以被多个路由共享。下面是其完整的定义:
struct fib_info { // /usr/src/linux-$(version)/include/net/ip_fib.h
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
};
现在就结合模块加载并初始化过程中为一个本地网络接口生成路由信息的实例,分析路由信息的创建过程。本地主机的网络设备接口eth0上配置的ip地址是 172.16.48.2。协议栈的fib_add_ifaddr函数会在模块被加载的时候主动往FIB表中插入五条路由相关信息,下面是一个完整列表:
类型 目的地址 地址长度
RTN_LOCAL 172.16.48.2 32
RTN_BROADCAST 172.16.48.255 32
RTN_UNICAST 172.16.48.0 24
RTN_BROADCAST 172.16.48.0 32
RTN_BROADCAST 172.16.48.255 32
其中第二条跟第五条完全相同,所以实际插入的是四条。第一条是本地接收,第二条是本地接收的广播,第三条是子网内的直接路由或者网关路由。第四条是子网广播。
首先看第一条会产生怎么样的路由信息struct fib_info。在这之前,fib_magic函数要为它产生三个结构体信息,用于传递给路由信息创建函数:
struct nlmsghdr // /usr/include/linux/netlink.h
{
__u32 nlmsg_len = sizeof( struct nlmsghdr + struct rtmsg );
__u16 nlmsg_type = RTM_NEWROUTE;
__u16 nlmsg_flags = NLM_F_REQUEST|NLM_F_CREATE|NLM_F_APPEND;
__u32 nlmsg_seq = 0;
__u32 nlmsg_pid = 0;
};
这是一个消息头结构,表示本条消息的内容是创建一个新的路由(RTM_NEWROUTE),这是一个请求消息(NLM_F_REQUEST),如果新的路由不存在,则要求创建(NLM_F_CREATE),并且新创建的路由加到列表尾部(NLM_F_APPEND)。由于这是内核发出的请求命令,所以序列号跟进程号都为0。
struct rtmsg // /usr/include/linux/renetlink.h
{
unsigned char rtm_family;
unsigned char rtm_dst_len = 32;
unsigned char rtm_src_len;
unsigned char rtm_tos;
unsigned char rtm_table = RT_TABLE_LOCAL;
unsigned char rtm_protocol = RTPROT_KERNEL;
unsigned char rtm_scope = RT_SCOPE_HOST;
unsigned char rtm_type = RTN_LOCAL;
unsigned rtm_flags;
};
这是消息体,表示这一条由内核发起的消息(RTPROT_KERNEL),因为路由类型是本地接收(RTN_LOCAL),所以操作对象是本地路由表 (RT_TABLE_LOCAL),同时目的地址也就是本地地址(RT_SCOPE_HOST),目的地址长度是32位。
struct kern_rta { // 这个没有grep到, /usrt 下只有 dn_kern_rta.
void *rta_dst = 172.16.48.2;
void *rta_src;
int *rta_iif;
int *rta_oif = indexof(eth0);
void *rta_gw;
u32 *rta_priority;
void *rta_prefsrc = 172.16.48.2 //接口上的primary地址。
struct rtattr *rta_mx;
struct rtattr *rta_mp;
unsigned char *rta_protoinfo;
u32 *rta_flow;
struct rta_cacheinfo *rta_ci;
struct rta_session *rta_sess;
u32 *rta_mp_alg;
};
该结构体提供该路由的属性,即本地地址,目的地址,输出接口。
函数fib_create_info用于创建路由信息,它首先要对请求信息的scope的有效性作一个判断,关于scope,内核定义了如下的枚举量:
enum rt_scope_t // /usr/src/linux$(version)/include/linux/renetlink.h
{
RT_SCOPE_UNIVERSE=0,
/* User defined values */
RT_SCOPE_SITE=200,
RT_SCOPE_LINK=253,
RT_SCOPE_HOST=254,
RT_SCOPE_NOWHERE=255
};
当前路由请求信息的type为RTN_LOCAL,它对应的scope应该是RT_SCOPE_HOST,如果scope小于这个值,则错误。
新创建的struct fib_info结构都会维护在一个哈希表struct hlist_head *fib_info_hash中备用。该哈项表的长度由变量fib_hash_size记录,变量fib_info_cnt记录哈希表中当前的路由信息数量。函数fib_create_info总是确保fib_hash_size>fib_info_cnt,如果不够了,就把哈希表扩大一倍。
结构体struct fib_info的最后一个成员是struct fib_nh fib_nh[0],它是一个路由的下一跳信息,目前还没看到它的实际用途。缺省struct fib_nh的数量为1,如果不为1,则由struct kern_rta的rta_mp传入具体信息。下面是该结构体的定义:
struct fib_nh { // 与 fib_info 在同一个文件,/usr/src/../include/net/ip_fib.h
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;
};
新创建的struct fib_info需要进行部分值的初始化,下面是被初始化的内容:
struct fib_info{
.fib_protocol = RTPROT_KERNEL;
.fib_nhs = 1;
.fib_flags = 0;
.fib_prefsrc = 172.16.48.2;
struct fib_nh{
.nh_oif = indexof(eth0);
.nh_flags = 0;
.nh_weight = 1;
.nh_scope = RT_SCOPE_NOWHERE;
.nh_dev = eth0;
}
}
由于创建完成的struct fib_info,还要拿到哈项表fib_info_hash中去匹配,因为有可能哈希表中已存在同样的一个路由信息了,如果存在,则要删掉刚刚创建的 struct fib_info,把表中已有的那项的成员fib_treeref加1即可。判断两个路由信息相同的依据是:fib_protocol,fib_prefsrc,fib_priority,fib_metrics,fib_flags全部相同,并且 fib_nh的数量和内容全部相同。
如果哈希表中还不存在,则把新创建的fib_info添加到哈项表中。并且,如果成员fib_prefsrc不为0,则同时把fib_info添加到哈希表fib_info_laddrhash中,fib_info_laddrhash是一个跟fib_info_hash同步维护的哈希表。
最后,还要把fib_info的所有struct fib_nh成员放到哈希表fib_info_devhash中,nh_dev相同的放到同一项中。
同理,第二项创建的struct fib_info如下:
struct fib_info{
.fib_protocol = RTPROT_KERNEL;
.fib_nhs = 1;
.fib_flags = 0;
.fib_prefsrc = 172.16.48.2;
struct fib_nh{
.nh_oif = indexof(eth0);
.nh_flags = 0;
.nh_weight = 1;
.nh_scope = RT_SCOPE_HOST;
.nh_dev = eth0;
}
}
依次类推,第三,第四项创建的struct fib_info与第二项完全相同,所以,最后,为网络设备接口eth0创建的struct fib_info共有两个,第一个的fib_treeref为1,第二个为3。