您的位置:首页 > 运维架构 > Linux

Linux IGMP PROXY 学习笔记 之二 igmp proxy的处理流程分析

2014-06-01 21:52 806 查看
上一节中我们分析了linux kernel中igmp proxy相关的数据结构与实现需求分析,本节我们分析kernel中对组播数据流和组播数据的处理流程。

对于目的ip地址为组播地址的数据,可以分为两类:

1、 四层协议类型为igmp的igmp管理数据包

2、 四层协议类型为udo的组播数据流

而协议栈ip层数据处理完后,在函数ip_rcv_finish里则会进行组播路由的查找,并根据组播数据的类型会调用不同的四层接收处理函数。

一、igmp数据流程分析



在对具体函数进行分析之前,我们先理解整体的igmp数据流程,上图就是igmp处理的主要流程,主要包括三个方面。

a) 三层ip处理完成后,查找组播路由和组播路由缓存,决定igmp数据的走向,即发往本地应用层igmppronxy应用还是将组播流转发给多播路由的下行接口。

b) 对于需要转发的组播数据流,则调用output相关的程序,实现将数据从br0发送给lan侧端口中,衔接igmp snooping。

c) 通过ip_set_sockopt/ip_get_sockopt实现应用层igmp proxy与kernel中组播路由缓存、组播路由的建立于删除。

下面我们分析代码时也会根据这几个方面来进行分析。

一、kernel组播数据处理

对于接收到的组播数据的处理,在三层时首先经过ip_rcv、ip_rcv_finish进行处理,在ip_rcv_finish中当调用函数ip_route_input进行路由查找时对于组播和单播数据,其路由查找流程是不一样的。

组播路由的查找分支如下:

只有在两种情况下才认为查找组播路由成功,才会更新路由缓存,并设置skb->dst

1.查找组播路由成功,则添加路由缓存,并更新组播路由表

2.查找组播路由失败,且目的组播地址不是224.0.0.x,且设备开启igmp proxy功能

此时也会更新路由缓存。

满足上面任一种情况,则会调用函数ip_route_input_mc添加路由缓存

if (ipv4_is_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,

ip_hdr(skb)->protocol);

if(our

#ifdef CONFIG_IP_MROUTE

||

(!ipv4_is_local_multicast(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_mc

/*

功能:添加一个组播路由缓存

主要有几个方面需要注意

1、当our为true时,需要再路由flag中增加local项

2、当our为true且ip地址为224.0.0.x时,input函数为ip_local_deliver

3、当支持igmp proxy,且ip地址不是224.0.0.x时,input函数为ip_mr_input

*/

static int ip_route_input_mc(struct sk_buff*skb, __be32 daddr, __be32 saddr,

u8tos, struct net_device *dev, int our)

{

unsignedhash;

structrtable *rth;

__be32spec_dst;

structin_device *in_dev = in_dev_get(dev);

u32itag = 0;

/*Primary sanity checks. */

if(in_dev == NULL)

return-EINVAL;

if(ipv4_is_multicast(saddr) || ipv4_is_lbcast(saddr) ||

ipv4_is_loopback(saddr) || skb->protocol!= htons(ETH_P_IP))

gotoe_inval;

if(ipv4_is_zeronet(saddr)) {

if(!ipv4_is_local_multicast(daddr))

gotoe_inval;

spec_dst= inet_select_addr(dev, 0, RT_SCOPE_LINK);

}else if (fib_validate_source(saddr, 0, tos, 0,

dev,&spec_dst, &itag, 0) < 0)

gotoe_inval;

rth= dst_alloc(&ipv4_dst_ops);

if(!rth)

gotoe_nobufs;

rth->u.dst.output= ip_rt_bug;

rth->u.dst.obsolete= -1;

atomic_set(&rth->u.dst.__refcnt,1);

rth->u.dst.flags=DST_HOST;

if(IN_DEV_CONF_GET(in_dev, NOPOLICY))

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;

#ifdef CONFIG_NET_CLS_ROUTE

rth->u.dst.tclassid= itag;

#endif

rth->rt_iif =

rth->fl.iif = dev->ifindex;

rth->u.dst.dev = init_net.loopback_dev;

dev_hold(rth->u.dst.dev);

rth->idev = in_dev_get(rth->u.dst.dev);

rth->fl.oif = 0;

rth->rt_gateway = daddr;

rth->rt_spec_dst=spec_dst;

rth->rt_genid = rt_genid(dev_net(dev));

rth->rt_flags = RTCF_MULTICAST;

rth->rt_type = RTN_MULTICAST;

if(our) {

rth->u.dst.input=ip_local_deliver;

rth->rt_flags|= RTCF_LOCAL;

}

/*对于支持多播路由的设备来说,需要将input指向ip_mr_input

对于非多播路由设备来说,则input指向ip_local_deliver*/

#ifdef CONFIG_IP_MROUTE

if(!ipv4_is_local_multicast(daddr) && IN_DEV_MFORWARD(in_dev))

rth->u.dst.input= ip_mr_input;

#endif

RT_CACHE_STAT_INC(in_slow_mc);

in_dev_put(in_dev);

hash= rt_hash(daddr, saddr, dev->ifindex, rt_genid(dev_net(dev)));

returnrt_intern_hash(hash, rth, NULL, skb, dev->ifindex);

e_nobufs:

in_dev_put(in_dev);

return-ENOBUFS;

e_inval:

in_dev_put(in_dev);

return-EINVAL;

}

通过上面几个函数,我们可以获取以下信息:

1、 对于组播流数据与组播协议控制数据其input函数是不一样的(对于加入、离开、查询报文,其目的地址为224.0.0.x)

2、 对于目的地址非224.0.0.x的数据,即使在组播路由中没有查找到,但如果支持igmp proxy功能,同样会创建路由缓存,而会在函数ip_mr_input里对数据包进行最终处理(发送给上层、转发还是丢弃)

a)组播地址为224.0.0.x的数据处理

我们知道对于组播协议控制数据,其目的mac地址一般是224.0.0.x,且已经加入组播路由上网组播组来说,会将其input函数为ip_local_deliver。

而ip_local_deliver会调用函数ip_local_deliver_finish进行分发处理,这里主要会有两个方向:

1、 首先调用raw_local_deliver,将数据发送给应用层原始socket进行处理,对于支持igmpproxy来说,即将数据发送给了应用层igmpproxy deamon程序进行后续处理

2、 根据数据包的四层协议类型,调用相应的函数进行处理,对于igmp来说,其函数为igmp_rcv。

b)组播地址不是224.0.0.x的数据处理

对于组播地址不是224.0.0.x的数据,即为组播流数据,由上面分析我们知道,对于组播流数据,则会调用函数ip_mr_input进行后续处理。下面我们分析这个函数

功能:查找组播路由缓存

1、 组播路由缓存查找到,则调用函数ip_mr_forward进行处理

2、 组播路由缓存没有查找到时,则会调用函数ipmr_cache_unresolved,通过kernel向igmp proxy deamon发送一个igmp report,使igmp proxy deamon触发一个添加组播路由缓存的set_soctopt操作。

3、 对于路由标签为local的路由缓存,则将数据的一个拷贝发送给函数ip_local_deliver进行处理。

int ip_mr_input(struct sk_buff *skb)

{

structmfc_cache *cache;

structnet *net = dev_net(skb->dev);

intlocal = skb_rtable(skb)->rt_flags & RTCF_LOCAL;

/*Packet is looped back after forward, it should not be

forwarded second time, but still can bedelivered locally.

*/

if(IPCB(skb)->flags&IPSKB_FORWARDED)

gotodont_forward;

if(!local) {

/*判断是否为router_alert option(也就是保存发送端的ip),如果有的话,

调用ip_call_ra_chain处理*/

if (IPCB(skb)->opt.router_alert) {

if (ip_call_ra_chain(skb))

return 0;

} else if (ip_hdr(skb)->protocol ==IPPROTO_IGMP){

/* IGMPv1 (and broken IGMPv2implementations sort of

Cisco IOS <= 11.2(8)) do not putrouter alert

option to IGMP packets destined toroutable

groups. It is very bad, because it means

that we can forward NO IGMP messages.

*/

/*对于igmp 控制数据,则发送给igmpproxydeamon进行处理*/

read_lock(&mrt_lock);

if (net->ipv4.mroute_sk) {

nf_reset(skb);

raw_rcv(net->ipv4.mroute_sk, skb);

read_unlock(&mrt_lock);

return 0;

}

read_unlock(&mrt_lock);

}

}

/*查找路由缓存*/

read_lock(&mrt_lock);

cache= ipmr_cache_find(net, ip_hdr(skb)->saddr, ip_hdr(skb)->daddr);

/*

* Nousable cache entry

*/

if(cache == NULL) {

intvif;

/*对于路由缓存为空,且组播路由存在时,则会调用ip_local_deliver,将数据的一个拷贝

发送给ip_local_deliver进行处理*/

if(local) {

structsk_buff *skb2 = skb_clone(skb, GFP_ATOMIC);

ip_local_deliver(skb);

if(skb2 == NULL) {

read_unlock(&mrt_lock);

return-ENOBUFS;

}

skb= skb2;

}

/*对于组播流来说,此时已经找到了组播路由,只是组播路由缓存还没有添加

此时需要通过调用ipmr_cache_unresolved,让内核给igmp proxy发送一个igmp报文,让igmp proxy

添加组播路由缓存,并将数据缓存在mfc_cache.mfc_un.unres.unresolved中*/

vif= ipmr_find_vif(skb->dev);

if(vif >= 0) {

interr = ipmr_cache_unresolved(net, vif, skb);

read_unlock(&mrt_lock);

returnerr;

}

read_unlock(&mrt_lock);

kfree_skb(skb);

return-ENODEV;

}

/*调用函数ip_mr_forward对组播数据流进行转发*/

ip_mr_forward(skb,cache, local);

read_unlock(&mrt_lock);

if(local)

returnip_local_deliver(skb);

return0;

dont_forward:

if(local)

returnip_local_deliver(skb);

kfree_skb(skb);

return0;

}

接着分析ip_mr_forward

功能:通过调用ipmr_queue_xmit,实现数据的转发

/* "local" means that we shouldpreserve one skb (for local delivery) */

static int ip_mr_forward(struct sk_buff*skb, struct mfc_cache *cache, int local)

{

intpsend = -1;

intvif, ct;

structnet *net = mfc_net(cache);

vif= cache->mfc_parent;

cache->mfc_un.res.pkt++;

cache->mfc_un.res.bytes+= skb->len;

/*

* Wrong interface: drop packet and (maybe)send PIM assert.

*/

if(net->ipv4.vif_table[vif].dev != skb->dev) {

inttrue_vifi;

if(skb_rtable(skb)->fl.iif == 0) {

/*It is our own packet, looped back.

Very complicated situation...

The best workaround until routing daemonswill be

fixed is not to redistribute packet, if itwas

send through wrong interface. It means, that

multicast applications WILL NOT work for

(S,G), which have default multicast routepointing

to wrong oif. In any case, it is not a good

idea to use multicasting applications onrouter.

*/

gotodont_forward;

}

cache->mfc_un.res.wrong_if++;

true_vifi= ipmr_find_vif(skb->dev);

if(true_vifi >= 0 && net->ipv4.mroute_do_assert &&

/* pimsm uses asserts, when switching fromRPT to SPT,

so that we cannot check that packetarrived on an oif.

It is bad, but otherwise we would needto move pretty

large chunk of pimd to kernel. Ough...--ANK

*/

(net->ipv4.mroute_do_pim ||

cache->mfc_un.res.ttls[true_vifi] <255) &&

time_after(jiffies,

cache->mfc_un.res.last_assert +MFC_ASSERT_THRESH)) {

cache->mfc_un.res.last_assert= jiffies;

ipmr_cache_report(net,skb, true_vifi, IGMPMSG_WRONGVIF);

}

gotodont_forward;

}

net->ipv4.vif_table[vif].pkt_in++;

net->ipv4.vif_table[vif].bytes_in+= skb->len;

/*

* Forwardthe frame

*/

for(ct = cache->mfc_un.res.maxvif-1; ct >= cache->mfc_un.res.minvif;ct--) {

if(ip_hdr(skb)->ttl > cache->mfc_un.res.ttls[ct]) {

if(psend != -1) {

structsk_buff *skb2 = skb_clone(skb, GFP_ATOMIC);

if(skb2)

ipmr_queue_xmit(skb2,cache, psend);

}

psend= ct;

}

}

if(psend != -1) {

if(local) {

structsk_buff *skb2 = skb_clone(skb, GFP_ATOMIC);

if(skb2)

ipmr_queue_xmit(skb2,cache, psend);

}else {

ipmr_queue_xmit(skb,cache, psend);

return0;

}

}

dont_forward:

if(!local)

kfree_skb(skb);

return0;

}

而在函数ipmr_queue_xmit里,通过调用ip_route_output_key查找路由,将dst_output设置为ip_mc_output;接着调用ipmr_forward_finish,从而调用到函数ip_mc_output进行处理;

ip_mc_output->ip_finish_output->ip_finish_output2->neigh_hh_output->dev_queue_xmit->dev->hard_start_xmit->br_dev_xmit(对于组播路由器,一般将下行接口设备br0);当调用到函数br_dev_xmit,就进入到了桥的处理,此时对于支持igmp snooping与不支持igmp snooping又会有不同的处理分支,关于这部分代码,请看以前的分析文档。

以上基本上分析完了内核对组播数据的处理。

二、kernel与userspace之间交互的接口

上面我们分析组播数据处理的时候,主要有组播路由添加与删除、组播路由缓存添加与删除。

在igmp proxydeamon的初始化时,

1、首先就会创建一个协议类型为igmp 的原始套接字;

2、接着就会将224.0.0.2加入组播路由,主要是为了接收igmp report、leave报文(IP_ADD_MEMBERSHIP)

3、为上行接口与下行接口创建虚拟接口(MRT_ADD_VIF)

4、当需要加入到一个组播组组时,首先需要加入组播路由(IP_ADD_MEMBERSHIP)

5、当有组播数据流通过,且已经加入该组播组后,则添加组播路由缓存(IP_ADD_MEMBERSHIP)

6、当接收到igmp leave报文后,则需要删除组播路由与组播路由缓存(IP_DEL_MEMBERSHIP、MRT_DEL_VIF)

因为本文主要是分析kernel的处理流程,对于igmpproxy的应用层实现就不做分析了,至此基本分析完了linux kernel igmp proxy的处理。

在上面的分析中,我们知道igmp proxy的功能:

对于IGMPPROXY,主要是拦截lan側pc发送的igmp报文,其在wan側作为客户端相应上行路由的查询操作,而在lan側则作为服务端定期发送查询报文。

当lan側加入的组播组在IGMPPROXY设备上没有相应的组播路由时,才会给上层发送组播加入报文,当lan側加入的组播组在IGMPPROXY设备上已经存在时,则无需再将加入报文转发出去。这样不仅能够达到有效抑制二层组播泛滥的问题,且能更有效的获取和控制用户信息,降低网络负载。

但是我们在上面分析组播处理的时候,并没有基于上面功能的实现,其实这些功能,kernel都放在应用层程序实现了,包括组播路由列表的更新及超时处理都由应用层实现了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: