igmpproxy_Linux IGMP PROXY 学习笔记 之二 igmp proxy的处理流程分析
上一節中我們分析了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都放在應用層程序實現了,包括組播路由列表的更新及超時處理都由應用層實現了。
總結
以上是生活随笔為你收集整理的igmpproxy_Linux IGMP PROXY 学习笔记 之二 igmp proxy的处理流程分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql decode语句_mysql
- 下一篇: 文治者必有武备不然长大了挨欺负_“有文事