A要发送报文给A,A到A路由的scope是RT_SCOPE_HOST,下一条为空,scope是RT_SCOPE_NOWHERE;
路由结束条件是路由查找的结果返回RT_SCOPE_HOST或者RT_SCOPE_LINK;RT_SCOPE_HOST表示目的地址是本机;RT_SCOPE_LINK表示目的地址与本机直连,可以通过L2协议进行发送;
2.2路由缓存
因此,在缓存中查找一个表项,首先计算出hash值,取出这组表项,然后遍历链表,找出指定的表项,这里需要完全匹配[src_ip,dst_ip,iif,tos,mark,net],实际上structrtable中有专门的属性用于缓存的查找键值–structflowi。
当找到表项后会更新表项的最后访问时间,并取出dst
dst_use(&rth->u.dst,jiffies);
skb_dst_set(skb,&rth->u.dst);
2.3路由缓存的创建
inet_init()->ip_init()->ip_rt_init()
2.4路由缓存插入条目
函数rt_intern_hash()如果新插入rt满足一定条件,还要与ARP邻居表进行绑定
Hint:
缓存的每个bucket是没有头结点的,单向链表,它所使用的插入和删除操作是值得学习的,简单实用。
2.5路由缓存删除条目
rt_del()
2.6路由表的创建
inet_init()->ip_init()->ip_fib_init()->fib_net_init()->ip_fib_net_init()
首先为路由表分配空间,这里的每个表项hlist_head实际都会链接一个单独的路由表,FIB_TABLE_HASHSZ表示了分配多少个路由表,一般情况下至少有两个– LOCAL和MAIN。
注意这里仅仅是表头的空间分配,还没有真正分配路由表空间。
net->ipv4.fib_table_hash=kzalloc(
sizeof(structhlist_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表。
关于这里的structfn_hash,它表示了不同子网掩码长度的hash表[即fn_zone],对于ipv4,从0~32共33个。
而fn_hash的实现则是fib_table的最后一个参数unsignedchartb_data[0]。
传入参数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。
?
?
?
?
?
?
路由表的查找效率是第一位的,因此内核在实现时使用了多级索引来进行加速:
第一级:
fn_zone,按不同掩码长度分类(如/5和/24);
第二级:
fib_node,按不同网络地址分类(如124.44.33.0/24);
第三级:
fib_info,按下一跳路由信息。
当然,我们创建路由表也要按照这个顺序。
(三)Linux路由功能实现
3.1数据包流程
如图4-1所示,ip_rcv是IPv4数据包的基本接收函数,由下层调用。
这个函数完成一系列的校验、协议处理等等,然后进入第一个HOOK点,这是在本机路由前的点。
在ip_rcv_finish函数中,会调用ip_route_input函数来进行本机路由,判断是发送给本机的,还是需要转发的,由此来知道下一处理函数是ip_local_deliver,还是ip_forward。
至于ip_route_input是如何进行路由的,将在下一节进行讲解。
ip_local_deliver以上,是本机数据包流程,这与路由无关,这里不做赘述。
ip_forward进行一些路由的处理,比如设置网关、MTU,TTL减1等等。
然后进入ip_forward_finish,根据之前设置的skb->dst->output函数来确定去处。
这个output也是在之前的路由过程中确定的,具体是单播、多播、还是广播等等,视之前的路由和协议而定。
3.2数据包路由过程
ip_route_input函数中,首先去路由缓存rt_hash_table中查找,如果找到则直接返回。
如果没有找到,则调用ip_route_input_slow来查找路由表。
ip_route_input_slow函数中调用fib_lookup来查找。
fib_lookup有两种定义,根据不同的功能编译开关。
一种是只查找main表和local表,这是低级路由。
另一种则会遍历rule表,先匹配应该查找哪一张路由表(可能是高级路由配置的),然后再对该表进行查询。
如果查询结果是本机的数据包,则会在ip_route_input_slow函数的后面部分进行数据包路由信息的更改,最重要的是入口函数改成ip_local_deliver。
然后调用rt_intern_hash函数来更新路由缓存。
如果结果是需要转发的,则调用ip_mkroute_input->__mkroute_input来做进一步的处理(ip_mkroute_input中有一个分支,多路路由,这里不作介绍)。
也就是把查找到的路由信息加入路由缓存,并把路由结果传递给数据包。
3.3相关代码分析
(四)路由接口主要函数分析
4.1路由转发表的检索过程(fib_lookup)
staticinlineint fib_lookup(structnet*net,conststructflowi*flp,
structfib_result*res)
{
structfib_table*table;
table= fib_get_table(net, RT_TABLE_LOCAL); //先查LOCAL
if(!
table->tb_lookup(table,flp,res))
return0;
table= fib_get_table(net, RT_TABLE_MAIN); //再查MAIN
if(!
table->tb_lookup(table,flp,res))
return0;
return-ENETUNREACH;
}
!
table->tb_lookup(table,flp,res)) 即fn_hash_lookup
staticint
fn_hash_lookup(structfib_table*tb,conststructflowi*flp,structfib_result*res)
{
interr;
structfn_zone*fz;
structfn_hash*t=(structfn_hash*)tb->tb_data; //获得路由区队列
read_lock(&fib_hash_lock);
for(fz= t->fn_zone_list;fz;fz=fz->fz_next){ //扫描网络区
structhlist_head*head;
structhlist_node*node;
structfib_node*f;
__be32k=fz_key(flp->fl4_dst,fz);
//取目标地址在该网络区的网络号 fl4_det&((fz)->fz_mask)
head=&fz->fz_hash[fn_hash(k,fz)];
//fn_hash(k,fz)得到hash关键字,获得hash链头
hlist_for_each_entry(f,node,head,fn_hash){
if(f->fn_key!
=k)
//通过fn_key找到匹配的fibnode节点
continue;
err=fib_semantic_match(&f->fn_alias,//进入fibsemantic查找
flp,res,
fz->fz_order);
if(err<=0)
gotoout;
}
}
err=1;
out:
read_unlock(&fib_hash_lock);
returnerr;
}
intfib_semantic_match(structlist_head*head,conststructflowi*flp,
structfib_result*res,intprefixlen)//这里head是f->fn_alias结构
{
structfib_alias*fa;
intnh_sel=0;
list_for_each_entry_rcu(fa,head,fa_list){
interr;
if(fa->fa_tos&&
fa->fa_tos!
=flp->fl4_tos) //比较TOS
continue;
if(fa->fa_scopefl4_scope) //比较路由范围scope
continue;
fa->fa_state|=FA_S_ACCESSED;
err=fib_props[fa->fa_type].error;
//取转发类型错误码,根据错误码进行特定处理
if(err==0){ //允许的转发类型
structfib_info*fi=fa->fa_info;
if(fi->fib_flags&RTNH_F_DEAD) //如果该转发节点不通
continue;