linux内核等价多路径路由,Linux内核分析 - 网络[四]:路由表

路由表的创建

inet_init() -> ip_init() -> ip_fib_init() -> fib_net_init() -> ip_fib_net_init()[net\ipv4\fib_frontend.c]

首先为路由表分配空间,这里的每个表项hlist_head实际都会链接一个单独的路由表,FIB_TABLE_HASHSZ表示了分配多少个路由表,一般情况下至少有两个–LOCAL和MAIN。注意这里仅仅是表头的空间分配,还没有真正分配路由表空间。

net->ipv4.fib_table_hash = kzalloc(

sizeof(struct hlist_head)*FIB_TABLE_HASHSZ, GFP_KERNEL);

ip_fib_net_init() -> fib4_rules_init(),这里真正分配了路由表空间

local_table = fib_hash_table(RT_TABLE_LOCAL);

main_table= fib_hash_table(RT_TABLE_MAIN);

然后将local和main表链入之前的fib_table_hash中

hlist_add_head_rcu(&local_table->tb_hlist,

&net->ipv4.fib_table_hash[TABLE_LOCAL_INDEX]);

hlist_add_head_rcu(&main_table->tb_hlist,

&net->ipv4.fib_table_hash[TABLE_MAIN_INDEX]);

最终生成结构如图,LOCAL表位于fib_table_hash[0],MAIN表位于fib_table_hash[1];两张表通过结构tb_hlist链入链表,而tb_id则标识了功能,255是LOCAL表,254是MAIN表。

关于这里的struct fn_hash,它表示了不同子网掩码长度的hash表[即fn_zone],对于ipv4,从0~32共33个。而fn_hash的实现则是fib_table的最后一个参数unsigned char tb_data[0]。

aca24e0bdf3462c737bb46c9b3e2eaf2.gif

注意到这里fn_zone还只是空指针,我们还只完成了路由表初始化的一部分。在启动阶段还会调用inet_rtm_newroute() -> fib_table_insert() -> fn_new_zone() [fib_hash.c]来创建fn_zone结构,前面已经讲过,fn_zone一共有33个,其中掩码长度为0[/0]表示为默认路由,fn_zone可以理解为相同掩码的地址集合。

首先为fn_zone分配空间

struct fn_zone *fz = kzalloc(sizeof(struct fn_zone), GFP_KERNEL);

传入参数z代表掩码长度,z = 0的掩码用于默认路由,一般只有一个,所以fz_divisor只需设为1;其它设为16;这里要提到fz_divisor的作用,fz->fz_hash并不是个单链表,而是一个哈希表,而哈希表的大小就是fz_divisor。

if (z) {

fz->fz_divisor = 16;

} else {

fz->fz_divisor = 1;

}

fz_hashmask实际是用于求余数的,当算出hash值,再hash & fz_hashmask就得出了在哈希表的位置;而fz_hash就是下一层的哈希表了,前面已经提过路由表被多组分层了,这里fz_hash就是根据fz_divisor大小来创建的;fz_order就是子网掩码长度;fz_mask就是子网掩码。

fz->fz_hashmask = (fz->fz_divisor - 1);

fz->fz_hash = fz_hash_alloc(fz->fz_divisor);

fz->fz_order = z;

fz->fz_mask = inet_make_mask(z);

从子网长度大于新添加fz的fn_zone中挑选一个不为空的fn_zones[i],将新创建的fz设成fn_zones[i].next;然后将fz根据掩码长度添加到fn_zones[]中相应位置;fn_zone_list始终指向掩码长度最长的fn_zone。

for (i=z+1; i<=32; i++)

if (table->fn_zones[i])

break;

if (i>32) {

fz->fz_next = table->fn_zone_list;

table->fn_zone_list = fz;

} else {

fz->fz_next = table->fn_zones[i]->fz_next;

table->fn_zones[i]->fz_next = fz;

}

table->fn_zones[z] = fz;

这里的fn_hash是数组与链表的结合体,看下fn_hash定义

struct fn_hash {

struct fn_zone*fn_zones[33];

struct fn_zone*fn_zone_list;

};

fn_hash包含33数组元素,每个元素存放一定掩码长度的fn_zone,其中fn_zone[i]存储掩码长度为i。而fn_zone通过内部属性fz_next又彼此串连起来,形成单向链表,其中fn_zone_list可以看作链表头,而这里链表的组织顺序是倒序的,即从掩码长到短。

89b9200259e4660652bbb153ddc6b47e.gif

到这里,fz_hash所分配的哈希表还没有插入内容,这部分为fib_insert_node()完成。

inet_rtm_newroute() -> fib_table_insert() -> fib_insert_node() [net\ipv4\fib_hash.c]

这里f是fib_node,可以理解为具有相同网络地址的路由项集合。根据fn_key(网络地址)和fz(掩码长度)来计算hash值,决定将f插入fz_hash的哪个项。

struct hlist_head *head = &fz->fz_hash[fn_hash(f->fn_key, fz)];

hlist_add_head(&f->fn_hash, head);

}

如何fib_node还不存在,则会创建它,这里的kmem_cache_zalloc()其实就是内存分配

new_f = kmem_cache_zalloc(fn_hash_kmem, GFP_KERNEL);

if (new_f == NULL)

goto out;

INIT_HLIST_NODE(&new_f->fn_hash);

INIT_LIST_HEAD(&new_f->fn_alias);

new_f->fn_key = key;

f = new_f;

路由表最后一层是fib_info,具体的路由信息都存储在此,它由fib_create_info()创建。

首先为fib_info分配空间,由于fib_info的最后一个属性是struct fib_nh fib_nh[0],因此大小是fib_info + nhs * fib_nh,这里的fib_nh代表了下一跳(next hop)的信息,nhs代表了下一跳的数目,一般情况下nhs=1,除非配置了支持多路径。

fi = kzalloc(sizeof(*fi)+nhs*sizeof(struct fib_nh), GFP_KERNEL);

设置fi的相关属性

fi->fib_net = hold_net(net);

fi->fib_protocol = cfg->fc_protocol;

fi->fib_flags = cfg->fc_flags;

fi->fib_priority = cfg->fc_priority;

fi->fib_prefsrc = cfg->fc_prefsrc;

fi->fib_nhs = nhs;

使fi后面所有的nh->nh_parent指向fi,设置后如图所示

change_nexthops(fi) {

nexthop_nh->nh_parent = fi;

} endfor_nexthops(fi)

f9a2046523812bbf75d0cff65f3e36f4.gif

设置fib_nh的属性,这里仅展示了单一路径的情况:

struct fib_nh *nh = fi->fib_nh;

nh->nh_oif = cfg->fc_oif;

nh->nh_gw = cfg->fc_gw;

nh->nh_flags = cfg->fc_flags;

然后,再根据cfg->fc_scope值来设置nh的其余属性。如果scope是RT_SCOPE_HOST,则设置下一跳scope为RT_SCOPE_NOWHERE

if (cfg->fc_scope == RT_SCOPE_HOST) {

struct fib_nh *nh = fi->fib_nh;

nh->nh_scope = RT_SCOPE_NOWHERE;

nh->nh_dev = dev_get_by_index(net, fi->fib_nh->nh_oif);

}

如果scope是RT_SCOPE_LINK或RT_SCOPE_UNIVERSE,则设置下跳

change_nexthops(fi) {

if ((err = fib_check_nh(cfg, fi, nexthop_nh)) != 0)

goto failure;

} endfor_nexthops(fi)

最后,将fi链入链表中,这里要注意的是所有的fib_info(只要创建了的)都会加入fib_info_hash中,如果路由项使用了优先地址属性,还会加入fib_info_laddrhash中。

hlist_add_head(&fi->fib_hash,

&fib_info_hash[fib_info_hashfn(fi)]);

if (fi->fib_prefsrc) {

struct hlist_head *head;

head = &fib_info_laddrhash[fib_laddr_hashfn(fi->fib_prefsrc)];

hlist_add_head(&fi->fib_lhash, head);

}0b1331709591d260c1c78e86d0c51c18.png

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值