一、igmpsnooping相关的数据结构
组播相关的数据结构主要有三个,下面分别分析:
1、struct net_bridge_mdb_htable
/*组播组数据库转发表,该结构体将所有的组播组数据库转发项通过hash数组连接到一起*/
struct net_bridge_mdb_htable
{
structhlist_head *mhash; //hash数组,将所有的net_bridge_mdb_entry链接到一起
structrcu_head rcu;
structnet_bridge_mdb_htable *old;
u32 size; //hash数组中所有hash链表中存在的所有net_bridge_mdb_entry项的总数
u32 max;//hash数组的最大值
u32 secret;
u32 ver;
};
2、net_bridge_mdb_entry
/*一个组播组数据库转发项,描述一个组播组的详细信息*/
struct net_bridge_mdb_entry
{
structhlist_node hlist[2];
structhlist_node mglist;
structnet_bridge *br;//桥
structnet_bridge_port_group *ports;//
structrcu_head rcu;
structtimer_list timer;//组播组数据库项失效定时器,若超时,则会将该组播端口从组播组数据库项的组播端口列表中删除
structtimer_list query_timer;//查询定时
__be32 addr;//组播组地址
u32 queries_sent;
};
其中port指向所有加入到组播组addr的组播端口
mglist用于将通过桥br接收到的igmp 加入报文创建的组播组数据库转发项连接起来
3、struct net_bridge_port_group
/*加入一个组播组的组播端口信息结构体*/
struct net_bridge_port_group {
structnet_bridge_port *port;//加入该组播组的桥端口
structnet_bridge_port_group *next;//下一个组播组详细参数结构体(可以有多个端口加入到同一个组播组)
structhlist_node mglist;
structrcu_head rcu;//rcu表头
structtimer_list timer;//组播端口失效定时器,若超时,则会将该组播端口从组播组数据库项的组播端口列表中删除
structtimer_list query_timer;//查询计时器
__be32 addr;//组播组地址,每一个组播端口也要一个组播组的原因是,通过这个值可以快速查找到其关联的组播组数据库项
u32 queries_sent;//已发送查询包的次数
};
其中,port链表用于将所有加入到组播组addr的对应桥端
Next指向下一个组播端口,该下一个组播组地址也为addr,仅仅是port值不同。
其中mglist用于将该组播端口加入到port的mglist链表中,通过桥端口的mglist链表,能够查找到该桥端口加入的所有组播组。
下面是这3个数据结构之间的关系
二、Igmpsnooping的初始化
Igmp snooping的初始化是使用函数br_multicast_init来实现的,在该函数里,主要组播组转发数据库表的hash数组的最大值进行设置,设置发送查询报文的间隔时间,以及发送查询报文的次数,并初始化桥的igmp查询的定时器。
void br_multicast_init(struct net_bridge*br)
{
br->hash_elasticity= 4;//每个组播组 ip中所能关联的端口个数
br->hash_max= 512; //mdb中hash数组的最大值
br->multicast_router= 1;
br->multicast_last_member_count= 2;
br->multicast_startup_query_count= 2;
br->multicast_last_member_interval= HZ;
br->multicast_query_response_interval= 10 * HZ; //组播查询最大回复时间
br->multicast_startup_query_interval= 125 * HZ / 4; //开启发送查询报文的间隔时间
br->multicast_query_interval= 125 * HZ; //查询包的发送间隔时间
br->multicast_querier_interval= 255 * HZ;
br->multicast_membership_interval= 260 * HZ;
spin_lock_init(&br->multicast_lock);
setup_timer(&br->multicast_router_timer,
br_multicast_local_router_expired, 0);
setup_timer(&br->multicast_querier_timer,
br_multicast_local_router_expired, 0);
setup_timer(&br->multicast_query_timer,br_multicast_query_expired,
(unsigned long)br);
}
2、桥与桥端口的查询定时器
对于桥与桥端口的查询定时器,主要是用来实现igmp snooping组播数据库转发表的持续更新的。
a)Br桥的查询定时器
对于定时器br->multicast_query_timer,在开启igmp snooping功能后就会开启桥端口的组播查询功能,主要分为如下情况:
i)当br设备up且开启igmp snooping时,就会调用函数br_multicast_open,开启一个立即超时的查询定时器
ii) 重新开始igmp snooping时,也会调用函数br_multicast_open,开启一个立即超时的查询定时器。
/*
开启一个桥的igmpsnooping功能
1、重置发送查询数据包计数
2、修改桥的查询定时器
*/
void br_multicast_open(struct net_bridge*br)
{
br->multicast_startup_queries_sent= 0;
if(br->multicast_disabled)
return;
mod_timer(&br->multicast_query_timer,jiffies);
}
当桥的查询定时器超时,就会调用函数br_multicast_query_expired进行处理,对于桥设备,该函数会发送一个组播通用查询包给上层进行处理,这时如果上层协议栈开启了igmp proxy功能,就会触发上层协议栈对lan侧的设备通用组播查询功能。对于上层开启igmp proxy功能,通过桥的查询定时器就能对lan侧设备进行周期的通用查询,然后就可以间接实现igmp snooping组播转发数据库的更新。
对于桥的查询定时器,因为其需要上层开启igmp proxy功能,且当上层开启igmp proxy功能后,如果开启了igmp snooping功能,就能通过发送通用的组播查询报文,实现快速的建立组播组数据库转发表。
从上面的程序可以看出,其是依赖上层是否开启igmp proxy,而不是直接发送通用组播组查询报文,这样也满足了igmp协议的要求,即只有组播组路由器才能发送组播查询报文。
B)桥端口的查询定时器
那如果上层协议栈不支持igmpproxy功能,上层协议栈收到通用查询报文就会丢弃掉,就不会触发上层协议栈对lan侧设备的通用查询,那不就没有办法更新igmp snooping的组播组转发数据库了?
代码编写者显然也注意到了这个问题,所以又增加了桥端口的组播查询定时器功能,在开启igmp snooping功能后就会开启桥端口的组播查询功能,分为下面两种情况:
1、 在一个桥端口up起来且开启igmp snooping时,通过间接调用__br_multicast_enable_port,启动一个立即到期的桥端口的查询定时器
2、 在重新开启igmp snooping功能时,通过调用__br_multicast_enable_port,启动一个立即到期的桥端口的查询定时器。
下面我们分析一下函数__br_multicast_enable_port。
static void__br_multicast_enable_port(struct net_bridge_port *port)
{
port->multicast_startup_queries_sent= 0;
if(try_to_del_timer_sync(&port->multicast_query_timer) >= 0 ||
del_timer(&port->multicast_query_timer))
mod_timer(&port->multicast_query_timer,jiffies);
}
该函数还是比较简单的,主要是开启一个立即超时的桥端口定时器,超时后即会执行超时处理函数br_multicast_port_query_expired。
static voidbr_multicast_port_query_expired(unsigned long data)
{
structnet_bridge_port *port = (void *)data;
structnet_bridge *br = port->br;
spin_lock(&br->multicast_lock);
if(port->state == BR_STATE_DISABLED ||
port->state == BR_STATE_BLOCKING)
gotoout;
if(port->multicast_startup_queries_sent <
br->multicast_startup_query_count)
port->multicast_startup_queries_sent++;
br_multicast_send_query(port->br,port,
port->multicast_startup_queries_sent);
out:
spin_unlock(&br->multicast_lock);
}
该函数的结构还是比较简单的,主要是判断桥端口是否处于forward状态,接着就会调用函数br_multicast_send_query发送一个通用查询报文。
对于函数br_multicast_send_query,其既可以向本地上层协议栈发送组播组查询报文,也可以将组播组通用查询报文从指定的桥端口发送出去。
下面分析一下函数br_multicast_send_query
/*
1、 调用函数br_multicast_alloc_query构造一个通用组播组查询报文
2、 对于桥端口不为空时,则将该查询报文从相应的端口发送出去
3、 对于桥端口为空时,则将该查询报文发送给上层协议栈
4、 更新桥或者桥端口的查询定时器。
*/
static void br_multicast_send_query(structnet_bridge *br,
struct net_bridge_port *port, u32 sent)
{
unsignedlong time;
structsk_buff *skb;
if(!netif_running(br->dev) || br->multicast_disabled ||
timer_pending(&br->multicast_querier_timer))
return;
/*构造一个通用查询数据包*/
skb= br_multicast_alloc_query(br, 0);
if(!skb)
gototimer;
if(port) {
__skb_push(skb,sizeof(struct ethhdr));
skb->dev= port->dev;
NF_HOOK(PF_BRIDGE,NF_BR_LOCAL_OUT, skb, NULL, skb->dev,
dev_queue_xmit);
}else
netif_rx(skb);
timer:
time= jiffies;
time+= sent < br->multicast_startup_query_count ?
br->multicast_startup_query_interval:
br->multicast_query_interval;
mod_timer(port? &port->multicast_query_timer :
&br->multicast_query_timer, time);
}
以上就是桥与桥端口的组播查询定时器的工作原理。
三、igmpsnooping 的开启与关闭
Igmp snooping的开启与关闭是通过函数br_multicast_toggle实现
对于igmp snooping的开启与关闭,目前是通过sysfs实现igmpsnooping的开启与关闭。
我们可以通过添加ioctl接口,实现通过brctl来开启或者关闭igmp SNOOPING
/*
功能:开启或者关闭igmpSNOOPING的函数。
只有当要设置的值与br->multicast_disabled的当前值不同时才允许修改。
当开启igmpsnooping功能时,则重新创建组播组转发数据库表,并调用br_multicast_open与__br_multicast_enable_port启动桥与桥端口的组播查询定时器。
*/
int br_multicast_toggle(struct net_bridge*br, unsigned long val)
{
structnet_bridge_port *port;
interr = -ENOENT;
spin_lock(&br->multicast_lock);
if(!netif_running(br->dev))
gotounlock;
err= 0;
/*如果要设置的值与当前的值相同,则直接返回*/
if(br->multicast_disabled == !val)
gotounlock;
br->multicast_disabled= !val;
/*当关闭igmp SNOOPING时,并没有释放br->mdb*/
if(br->multicast_disabled)
gotounlock;
/*
1、当开启IGMP SNOOPING时,如果br->mdb、br->mdb->old均不为0,
则说明br->mdb值有问题,此时则会关闭IGMP SNOOPING功能
2、当开启IGMP SNOOPING时,如果br->mdb不为0,且br->mdb->old为0,
则调用br_mdb_rehash,将原br->mdb中的组播组项重新计算hash值
并存放在新的br->mdb中,释放原br->mdb占用的空间*/
if(br->mdb) {
if(br->mdb->old) {
err= -EEXIST;
rollback:
br->multicast_disabled= !!val;
gotounlock;
}
err= br_mdb_rehash(&br->mdb, br->mdb->max,
br->hash_elasticity);
if(err)
gotorollback;
}
/*
1、reset br的发送查询包统计计数
2、修改br的组播查询定时器multicast_query_timer的值
*/
br_multicast_open(br);
/*
对于br桥组上的每一个端口
1、reset port的发送查询包统计计数
2、删除端口的查询定时器,并重新调度
*/
list_for_each_entry(port,&br->port_list, list) {
if(port->state == BR_STATE_DISABLED ||
port->state == BR_STATE_BLOCKING)
continue;
__br_multicast_enable_port(port);
}
unlock:
spin_unlock(&br->multicast_lock);
returnerr;
}
至此完成了igmpsnooping的数据结构与初始化的介绍,下节分析igmp snooping的接收处理函数。