Linux 路由 学习笔记 之十一 输入、输出路由查找相关的接口函数

对于路由功能模块的学习,也已经很长时间了。关于路由项的创建与查找、策略规则相关的创建与查找、路由缓存的创建与查找,都是分开来分析的,没有说明这些模块是如何配合使用的,以及模块之间的联系。本节就分析一下这几个模块是如何联系在一起的,也作为路由模块学习的小结。

对于协议栈而言,内核中的路由功能模块主要就是提供路由查找功能,而查找功能就集合了策略规则、路由项、路由缓存的查找,而路由功能模块的查找接口对外是统一的。

目前而言,对于输入数据包与输出数据包,路由功能模块提供了两个接口函数,即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;

}

至此,对于路由模块的学习告一段落了,经过这一段时间的学习,对于路由模块里各子模块的工作机制都有了整体的把握与理解。

世界会向那些有目标和远见的人让路(冯两努——香港着名推销商

Linux 路由 学习笔记 之十一 输入、输出路由查找相关的接口函数

相关文章:

你感兴趣的文章:

标签云: