对于路由功能模块的学习,也已经很长时间了。关于路由项的创建与查找、策略规则相关的创建与查找、路由缓存的创建与查找,都是分开来分析的,没有说明这些模块是如何配合使用的,以及模块之间的联系。本节就分析一下这几个模块是如何联系在一起的,也作为路由模块学习的小结。
对于协议栈而言,内核中的路由功能模块主要就是提供路由查找功能,而查找功能就集合了策略规则、路由项、路由缓存的查找,而路由功能模块的查找接口对外是统一的。
目前而言,对于输入数据包与输出数据包,路由功能模块提供了两个接口函数,即ip_route_input和ip_route_output_key。下面就分两部分分析一下这两个函数。
1.1输入路由查找相关的接口
输入路由查找相关的接口有ip_route_input,下面分析一下这个函数。
该函数主要对输入数据包进行路由查找(即netif_receive_skb->ip_rcv->ip_rcv_finish).
对于输入的数据包,会有三个输出结果:
1.本机接收
2.本机转发
3.丢掉数据包
这个函数根据不同的查找结果,会有两个分支:
1.当路由缓存中已存在该数据包对应的路由,则通过查找相应的路由缓存表即可
(rt_hash_table[hash].chain)
2.当路由缓存中不存在该数据包对应的路由时,则通过调用ip_route_input_slow,通过查找路由表,来决定数据包的命运。
这个函数就综合了策略规则的查找、路由项的查找、路由缓存的查找、路由缓存的生成、路由缓存与arp邻居项的绑定。
*/
intip_route_input(structsk_buff*skb,__be32daddr,__be32saddr,
u8tos,structnet_device*dev)
{
structrtable*rth;
unsigned hash;
intiif=dev->ifindex;
tos&=IPTOS_RT_MASK;
hash=rt_hash(daddr,saddr,iif);
/*加上读锁*/
rcu_read_lock();
/*在hash数组rt_hash_table中,根据hash值找到相应的hash链表,遍历链表中的所用rtable成员
,查找符合条件的路由缓存,若找到的则返回0,并将skb->dst指向该路由缓存。
ip_rcv_finish就会调用skb->dst->input,对数据包进行处理,而在创建路由缓存时,已经将
dst->input的值设置为ip_local_deliver或者ip_forward,根据skb->dst->input函数,就决定了数据包是
发送给本机上层协议进行处理还是转发出去。
*/
for(rth=rcu_dereference(rt_hash_table[hash].chain);rth;
rth=rcu_dereference(rth->u.dst.rt_next)){
if(rth->fl.fl4_dst==daddr&&
rth->fl.fl4_src==saddr&&
rth->fl.iif==iif&&
rth->fl.oif==0&&
rth->fl.mark==skb->mark&&
rth->fl.fl4_tos==tos){
rth->u.dst.lastuse=jiffies;
dst_hold(&rth->u.dst);
rth->u.dst.__use++;
RT_CACHE_STAT_INC(in_hit);
rcu_read_unlock();
skb->dst=(structdst_entry*)rth;
return0;
}
RT_CACHE_STAT_INC(in_hlist_search);
}
rcu_read_unlock();
/*Multicastrecognitionlogicismovedfromroutecachetohere.
TheproblemwasthattoomanyEthernetcardshavebroken/missing
hardwaremulticastfilters:-(Asresultthehostonmulticasting
networkacquiresalotofuselessroutecacheentries,sortof
SDRmessagesfromalltheworld.Nowwetrytogetridofthem.
Really,providedsoftwareIPmulticastfilterisorganized
reasonably(atleast,hashed),itdoesnotresultinaslowdown
comparingwithroutecacherejectentries.
Note,thatmulticastroutersarenotaffected,because
routecacheentryiscreatedeventually.
*/
/*路由缓存没有命中,且目的地址为组播地址时,则进入组播处理流程*/
if(MULTICAST(daddr)){
structin_device*in_dev;
rcu_read_lock();
if((in_dev=__in_dev_get_rcu(dev))!=NULL){
intour=ip_check_mc(in_dev,daddr,saddr,
skb->nh.iph->protocol);
if(our
#ifdefCONFIG_IP_MROUTE
||(!LOCAL_MCAST(daddr)&&IN_DEV_MFORWARD(in_dev))
#endif
){
rcu_read_unlock();
returnip_route_input_mc(skb,daddr,saddr,
tos,dev,our);
}
}
rcu_read_unlock();
return-EINVAL;
}
/*当路由缓存查找没有命中,且目的地址不是组播地址时,则调用ip_route_input_slow
进行路由项的查找*/
returnip_route_input_slow(skb,daddr,saddr,tos,dev);
}
对于ip_route_input_slow函数,在路由项的查找一节中,没有进行分析,本节进行分析一下。
1.1.1ip_route_input_slow
该函数主要完成两个功能:
1.通过调用fib_lookup进行路由表查找
2.若在路由表中查找到符合条件的路由,则调用ip_mkroute_input创建
路由缓存,并将路由缓存与arp邻居项实现绑定。
对于fib_lookup来说,若系统支持策略路由,则首先进行策略规则的匹配,
然后根据策略规则找到相应的路由表,这就涉及到策略规则与路由表查找
相关的内容,关于这两方面的内容可以查看"路由表的查找"与"策略规则的查找"
的内容。
而对于ip_mkroute_input中,路由缓存与arp邻居项的绑定,可以查看“路由缓存相关的创建”与“邻居项相关”的分析文档。
这个函数将路由项查找、策略规则查找、路由缓存创建、路由缓存与邻居项的绑定关联了起来,是一个比较综合的接口函数。
*/
staticintip_route_input_slow(structsk_buff*skb,__be32daddr,__be32saddr,
u8tos,structnet_device*dev)
{
structfib_resultres;
/*获取输入设备的三层参数*/
structin_device*in_dev=in_dev_get(dev);
/*
根据传入的值,构造查找条件
*/
structflowifl={.nl_u={.ip4_u=
{.daddr=daddr,
.saddr=saddr,
.tos=tos,
.scope=RT_SCOPE_UNIVERSE,
}},
.mark=skb->mark,
.iif=dev->ifindex};
unsigned flags=0;
u32 itag=0;
structrtable*rth;
unsigned hash;
__be32 spec_dst;
int err=-EINVAL;
int free_res=0;
/*IPonthisdeviceisdisabled.*/
/*当该设备没有设置三层参数,则程序返回错误*/
if(!in_dev)
gotoout;
/*Checkforthemostweirdmartians,whichcanbenotdetected
byfib_lookup.
*/
/*当源ip地址为组播、广播或者环回地址,则返回错误。*/
if(MULTICAST(saddr)||BADCLASS(saddr)||LOOPBACK(saddr))
gotomartian_source;
if(daddr==htonl(0xFFFFFFFF)||(saddr==0&&daddr==0))
gotobrd_input;
/*Acceptzeroaddressesonlytolimitedbroadcast;
*Ievendonotknowtofixitornot.Waitingforcomplains:-)
*/
if(ZERONET(saddr))
gotomartian_source;
if(BADCLASS(daddr)||ZERONET(daddr)||LOOPBACK(daddr))
gotomartian_destination;
/*
* Nowwearereadytoroutepacket.
*/
if((err=fib_lookup(&fl,&res))!=0){
if(!IN_DEV_FORWARD(in_dev))
gotoe_hostunreach;
gotono_route;
}
free_res=1;
RT_CACHE_STAT_INC(in_slow_tot);
if(res.type==RTN_BROADCAST)
gotobrd_input;
/*对于路由类型为local的路由项,则*/
if(res.type==RTN_LOCAL){
intresult;
result=fib_validate_source(saddr,daddr,tos,
loopback_dev.ifindex,
dev,&spec_dst,&itag);
if(result<0)
gotomartian_source;
if(result)
flags|=RTCF_DIRECTSRC;
spec_dst=daddr;
gotolocal_input;
}
if(!IN_DEV_FORWARD(in_dev))
gotoe_hostunreach;
if(res.type!=RTN_UNICAST)
gotomartian_destination;
err=ip_mkroute_input(skb,&res,&fl,in_dev,daddr,saddr,tos);
if(err==-ENOBUFS)
gotoe_nobufs;
if(err==-EINVAL)
gotoe_inval;
done:
in_dev_put(in_dev);
if(free_res)
fib_res_put(&res);
out: returnerr;
brd_input:
if(skb->protocol!=htons(ETH_P_IP))
gotoe_inval;
if(ZERONET(saddr))
spec_dst=inet_select_addr(dev,0,RT_SCOPE_LINK);
else{
err=fib_validate_source(saddr,0,tos,0,dev,&spec_dst,
&itag);
if(err<0)
gotomartian_source;
if(err)
flags|=RTCF_DIRECTSRC;
}
flags|=RTCF_BROADCAST;
res.type=RTN_BROADCAST;
RT_CACHE_STAT_INC(in_brd);
local_input:
rth=dst_alloc(&ipv4_dst_ops);
if(!rth)
gotoe_nobufs;
rth->u.dst.output=ip_rt_bug;
atomic_set(&rth->u.dst.__refcnt,1);
rth->u.dst.flags=DST_HOST;
if(in_dev->cnf.no_policy)
rth->u.dst.flags|=DST_NOPOLICY;
rth->fl.fl4_dst =daddr;
rth->rt_dst =daddr;
rth->fl.fl4_tos =tos;
rth->fl.mark=skb->mark;
rth->fl.fl4_src =saddr;
rth->rt_src =saddr;
#ifdefCONFIG_NET_CLS_ROUTE
rth->u.dst.tclassid=itag;
#endif
rth->rt_iif =
rth->fl.iif =dev->ifindex;
rth->u.dst.dev =&loopback_dev;
dev_hold(rth->u.dst.dev);
rth->idev =in_dev_get(rth->u.dst.dev);
rth->rt_gateway =daddr;
rth->rt_spec_dst=spec_dst;
rth->u.dst.input=ip_local_deliver;
rth->rt_flags =flags|RTCF_LOCAL;
if(res.type==RTN_UNREACHABLE){
rth->u.dst.input=ip_error;
rth->u.dst.error=-err;
rth->rt_flags &=~RTCF_LOCAL;
}
rth->rt_type =res.type;
hash=rt_hash(daddr,saddr,fl.iif);
err=rt_intern_hash(hash,rth,(structrtable**)&skb->dst);
gotodone;
no_route:
RT_CACHE_STAT_INC(in_no_route);
spec_dst=inet_select_addr(dev,0,RT_SCOPE_UNIVERSE);
res.type=RTN_UNREACHABLE;
gotolocal_input;
/*
* Donotcachemartianaddresses:theyshouldbelogged(RFC1812)
*/
martian_destination:
RT_CACHE_STAT_INC(in_martian_dst);
#ifdefCONFIG_IP_ROUTE_VERBOSE
if(IN_DEV_LOG_MARTIANS(in_dev)&&net_ratelimit())
printk(KERN_WARNING"martiandestination%u.%u.%u.%ufrom"
"%u.%u.%u.%u,dev%s\n",
NIPQUAD(daddr),NIPQUAD(saddr),dev->name);
#endif
e_hostunreach:
err=-EHOSTUNREACH;
gotodone;
e_inval:
err=-EINVAL;
gotodone;
e_nobufs:
err=-ENOBUFS;
gotodone;
martian_source:
ip_handle_martian_source(dev,in_dev,skb,daddr,saddr);
gotoe_inval;
}
以上就是输入路由查找相关的接口函数的分析,通过这个函数,我们就能将以上几节分析的内容结合在一起了。就有了一个整体的框架了。而对于输出路由查找相关的接口函数,其作用与输入路由查找相关的接口函数类似,下面简要分析下。
1.2输出路由查找相关的接口函数
输出路由查找相关的接口函数有好几个,其中最外层为ip_route_output_key,其是
对函数ip_route_output_flow的封装,而ip_route_output_flow则是对
__ip_route_output_key的封装。所以我们主要分析一下函数__ip_route_output_key
该函数主要对输出数据包进行路由查找(本地发送udp、tcp数据包或者netfilter中修改了tos、mark值后,都会进行输出路由查找).
对于输出的数据包,会有两个输出结果:
1.本机发送
2.丢掉数据包
这个函数根据不同的查找结果,会有两个分支:
1.当路由缓存中已存在该数据包对应的路由,则通过查找相应的路由缓存表即可
(rt_hash_table[hash].chain)
2.当路由缓存中不存在该数据包对应的路由时,则通过调用ip_route_output_slow,通过查找
路由表,来决定数据包的命运。
int__ip_route_output_key(structrtable**rp,conststructflowi*flp)
{
unsignedhash;
structrtable*rth;
hash=rt_hash(flp->fl4_dst,flp->fl4_src,flp->oif);
rcu_read_lock_bh();
/*路由缓存查找*/
for(rth=rcu_dereference(rt_hash_table[hash].chain);rth;
rth=rcu_dereference(rth->u.dst.rt_next)){
if(rth->fl.fl4_dst==flp->fl4_dst&&
rth->fl.fl4_src==flp->fl4_src&&
rth->fl.iif==0&&
rth->fl.oif==flp->oif&&
rth->fl.mark==flp->mark&&
!((rth->fl.fl4_tos^flp->fl4_tos)&
(IPTOS_RT_MASK|RTO_ONLINK))){
/*checkformultipathroutesandchooseoneif
*necessary
*/
if(multipath_select_route(flp,rth,rp)){
dst_hold(&(*rp)->u.dst);
RT_CACHE_STAT_INC(out_hit);
rcu_read_unlock_bh();
return0;
}
rth->u.dst.lastuse=jiffies;
dst_hold(&rth->u.dst);
rth->u.dst.__use++;
RT_CACHE_STAT_INC(out_hit);
rcu_read_unlock_bh();
*rp=rth;
return0;
}
RT_CACHE_STAT_INC(out_hlist_search);
}
rcu_read_unlock_bh();
/*路由表查找*/
returnip_route_output_slow(rp,flp);
}
1.2.1ip_route_output_slow
路由项查找函数
1.调用fib_lookup进行路由项的查找(根据是否开启策略路由,会对应不同的查找函数)
2.若查找到路由项,则调用ip_mkroute_output创建对应的路由缓存
该函数就实现了将策略规则的查找、路由项的查找、路由缓存的创建、arp邻居项与路由缓存的绑定关联起来。
staticintip_route_output_slow(structrtable**rp,conststructflowi*oldflp)
{
u32tos =RT_FL_TOS(oldflp);
structflowifl={.nl_u={.ip4_u=
{.daddr=oldflp->fl4_dst,
.saddr=oldflp->fl4_src,
.tos=tos&IPTOS_RT_MASK,
.scope=((tos&RTO_ONLINK)?
RT_SCOPE_LINK:
RT_SCOPE_UNIVERSE),
}},
.mark=oldflp->mark,
.iif=loopback_dev.ifindex,
.oif=oldflp->oif};
structfib_resultres;
unsignedflags=0;
structnet_device*dev_out=NULL;
intfree_res=0;
interr;
res.fi =NULL;
#ifdefCONFIG_IP_MULTIPLE_TABLES
res.r =NULL;
#endif
if(oldflp->fl4_src){
err=-EINVAL;
if(MULTICAST(oldflp->fl4_src)||
BADCLASS(oldflp->fl4_src)||
ZERONET(oldflp->fl4_src))
gotoout;
/*Itisequivalenttoinet_addr_type(saddr)==RTN_LOCAL*/
dev_out=ip_dev_find(oldflp->fl4_src);
if(dev_out==NULL)
gotoout;
/*Iremovedcheckforoif==dev_out->oifhere.
Itwaswrongfortworeasons:
1.ip_dev_find(saddr)canreturnwrongiface,ifsaddris
assignedtomultipleinterfaces.
2.Moreover,weareallowedtosendpacketswithsaddr
ofanotheriface.–ANK
*/
if(oldflp->oif==0
&&(MULTICAST(oldflp->fl4_dst)||oldflp->fl4_dst==htonl(0xFFFFFFFF))){
/*Specialhack:usercandirectmulticasts
andlimitedbroadcastvianecessaryinterface
withoutfiddlingwithIP_MULTICAST_IForIP_PKTINFO.
Thishackisnotjustforfun,itallows
vic,vatandfriendstowork.
Theybindsockettoloopback,setttltozero
andexpectthatitwillwork.
Fromtheviewpointofroutingcachetheyarebroken,
becausewearenotallowedtobuildmulticastpath
withloopbacksourceaddr(look,routingcache
cannotknow,thatttliszero,sothatpacket
willnotleavethishostandrouteisvalid).
Luckily,thishackisgoodworkaround.
*/
fl.oif=dev_out->ifindex;
gotomake_route;
}
if(dev_out)
dev_put(dev_out);
dev_out=NULL;
}
if(oldflp->oif){
dev_out=dev_get_by_index(oldflp->oif);
err=-ENODEV;
if(dev_out==NULL)
gotoout;
/*RACE:Checkreturnvalueofinet_select_addrinstead.*/
if(__in_dev_get_rtnl(dev_out)==NULL){
dev_put(dev_out);
gotoout; /*Wrongerrorcode*/
}
if(LOCAL_MCAST(oldflp->fl4_dst)||oldflp->fl4_dst==htonl(0xFFFFFFFF)){
if(!fl.fl4_src)
fl.fl4_src=inet_select_addr(dev_out,0,
RT_SCOPE_LINK);
gotomake_route;
}
if(!fl.fl4_src){
if(MULTICAST(oldflp->fl4_dst))
fl.fl4_src=inet_select_addr(dev_out,0,
fl.fl4_scope);
elseif(!oldflp->fl4_dst)
fl.fl4_src=inet_select_addr(dev_out,0,
RT_SCOPE_HOST);
}
}
if(!fl.fl4_dst){
fl.fl4_dst=fl.fl4_src;
if(!fl.fl4_dst)
fl.fl4_dst=fl.fl4_src=htonl(INADDR_LOOPBACK);
if(dev_out)
dev_put(dev_out);
dev_out=&loopback_dev;
dev_hold(dev_out);
fl.oif=loopback_dev.ifindex;
res.type=RTN_LOCAL;
flags|=RTCF_LOCAL;
gotomake_route;
}
if(fib_lookup(&fl,&res)){
res.fi=NULL;
if(oldflp->oif){
/*Apparently,routingtablesarewrong.Assume,
thatthedestinationisonlink.
WHY?DW.
Becauseweareallowedtosendtoiface
evenifithasNOroutesandNOassigned
addresses.Whenoifisspecified,routing
tablesarelookedupwithonlyonepurpose:
tocatchifdestinationisgatewayed,ratherthan
direct.Moreover,ifMSG_DONTROUTEisset,
wesendpacket,ignoringbothroutingtables
andifaddrstate.–ANK
Wecouldmakeitevenifoifisunknown,
likelyIPv6,butwedonot.
*/
if(fl.fl4_src==0)
fl.fl4_src=inet_select_addr(dev_out,0,
RT_SCOPE_LINK);
res.type=RTN_UNICAST;
gotomake_route;
}
if(dev_out)
dev_put(dev_out);
err=-ENETUNREACH;
gotoout;
}
free_res=1;
if(res.type==RTN_LOCAL){
if(!fl.fl4_src)
fl.fl4_src=fl.fl4_dst;
if(dev_out)
dev_put(dev_out);
dev_out=&loopback_dev;
dev_hold(dev_out);
fl.oif=dev_out->ifindex;
if(res.fi)
fib_info_put(res.fi);
res.fi=NULL;
flags|=RTCF_LOCAL;
gotomake_route;
}
#ifdefCONFIG_IP_ROUTE_MULTIPATH
if(res.fi->fib_nhs>1&&fl.oif==0)
fib_select_multipath(&fl,&res);
else
#endif
/*
同时满足以下三种情况时,才会调用函数fib_select_default查找默认路由
1.查找的路由项的掩码为0
2.查找的路由项的类型为RTN_UNICAST
3.查找的路由项的出口设备值为空
*/
if(!res.prefixlen&&res.type==RTN_UNICAST&&!fl.oif)
fib_select_default(&fl,&res);
if(!fl.fl4_src)
fl.fl4_src=FIB_RES_PREFSRC(res);
if(dev_out)
dev_put(dev_out);
dev_out=FIB_RES_DEV(res);
dev_hold(dev_out);
fl.oif=dev_out->ifindex;
make_route:
err=ip_mkroute_output(rp,&res,&fl,oldflp,dev_out,flags);
if(free_res)
fib_res_put(&res);
if(dev_out)
dev_put(dev_out);
out: returnerr;
}
至此,对于路由模块的学习告一段落了,经过这一段时间的学习,对于路由模块里各子模块的工作机制都有了整体的把握与理解。
世界会向那些有目标和远见的人让路(冯两努——香港着名推销商