注:本文基于linux kernel 2.6.39.4
一、IGMP Snooping基本数据结构
1、struct net_bridge_mdb_htable
组播组数据库转发表,该结构体将所有的组播组数据库转发项通过hash数组连接到一起
-
struct
net_bridge_mdb_htable
-
{
-
struct
hlist_head *mhash;
//hash数组,将所有的net_bridge_mdb_entry链接到一起
-
struct
rcu_head rcu;
-
struct
net_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
-
{
-
struct
hlist_node hlist[
2];
-
struct
net_bridge *br;
-
struct
net_bridge_port_group __rcu *ports;
-
struct
rcu_head rcu;
-
struct
timer_list timer;
//组播组数据库项失效定时器,若超时,则会将该组播端口从组播组数据库项的组播端口列表中删除
-
struct
timer_list query_timer;
//查询定时
-
struct
br_ip addr;
//组播组地址
-
bool mglist;
-
u32 queries_sent;
-
};
其中port指向所有加入到组播组addr的组播端口
mglist用于将通过桥br接收到的igmp 加入报文创建的组播组数据库转发项连接起来
3、struct net_bridge_port_group
加入一个组播组的组播端口信息结构体
-
struct
net_bridge_port_group {
-
struct
net_bridge_port *port;
//加入该组播组的桥端口
-
struct
net_bridge_port_group __rcu *next;
//下一个组播组详细参数结构体(可以有多个端口加入到同一个组播组)
-
struct
hlist_node mglist;
-
struct
rcu_head rcu;
-
struct
timer_list timer;
//组播端口失效定时器,若超时,则会将该组播端口从组播组数据库项的组播端口列表中删除
-
struct
timer_list query_timer;
//查询定时器
-
struct
br_ip addr;
//组播组地址,每一个组播端口也要一个组播组的原因是,通过这个值可以快速查找到其关联的组播组数据库项
-
u32 queries_sent;
//已发送查询包的次数
-
其中,port链表用于将所有加入到组播组addr的对应桥端
Next指向下一个组播端口,该下一个组播组地址也为addr,仅仅是port值不同。
其中mglist用于将该组播端口加入到port的mglist链表中,通过桥端口的mglist链表,能够查找到该桥端口加入的所有组播组。
下面是这3个数据结构之间的关系
二、IGMP Snooping初始化
1、初始化接口
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组播数据库转发表的持续更新的。
(1)Br桥的查询定时器
对于定时器br->multicast_query_timer,在开启igmp snooping功能后就会开启桥端口的组播查询功能,主要分为如下情况:
- 当br设备up(br_dev_open中调用)且开启igmp snooping时,就会调用函数br_multicast_open,开启一个立即超时的查询定时器
- 重新开始igmp snooping(br_multicast_toggle中调用)时,也会调用函数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协议的要求,即只有组播组路由器才能发送组播查询报文。
(2)桥端口的查询定时器
那如果上层协议栈不支持igmpproxy功能,上层协议栈收到通用查询报文就会丢弃掉,就不会触发上层协议栈对lan侧设备的通用查询,那不就没有办法更新igmp snooping的组播组转发数据库了?代码编写者显然也注意到了这个问题,所以又增加了桥端口的组播查询定时器功能,在开启igmp snooping功能后就会开启桥端口的组播查询功能,触发定时器分为下面两种情况:
- 在一个桥端口up起来且开启igmp snooping时,通过间接调用__br_multicast_enable_port,启动一个立即到期的桥端口的查询定时器
- 在重新开启igmp snooping功能时,通过调用__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);
-
}
该接口比较简单,干掉之前的定时器,立即重新启动查询定时器。
桥端口定时器的初始化是在桥端口创建时进行的,如下:
-
-
void br_multicast_add_port(struct net_bridge_port *port)
-
{
-
port->multicast_router =
1;
-
-
setup_timer(&port->multicast_router_timer, br_multicast_router_expired,
-
(
unsigned
long)port);
-
setup_timer(&port->multicast_query_timer,
-
br_multicast_port_query_expired, (
unsigned
long)port);
-
}
-
-
-
/* called with RTNL but without bridge lock */
-
static
struct
net_bridge_port *
new_nbp(
struct net_bridge *br,
struct net_device *dev)
-
{
-
int index;
-
struct
net_bridge_port *p;
-
-
index =
find_portno(br);
-
if (index <
0)
-
return
ERR_PTR(index);
-
-
p =
kzalloc(
sizeof(*p), GFP_KERNEL);
-
if (p ==
NULL)
-
return
ERR_PTR(-ENOMEM);
-
-
p->br = br;
-
dev_hold(dev);
-
p->dev = dev;
-
p->path_cost =
port_cost(dev);
-
p->priority =
0x8000 >> BR_PORT_BITS;
-
p->port_no = index;
-
p->flags =
0;
-
br_init_port(p);
-
p->state = BR_STATE_DISABLED;
-
br_stp_port_timer_init(p);
-
br_multicast_add_port(p);
-
-
return p;
-
}
-
-
/* called with RTNL but without bridge lock */
-
static
struct
net_bridge_port *
new_nbp(
struct net_bridge *br,
struct net_device *dev)
-
{
-
...
-
-
p =
new_nbp(br, dev);
-
-
...
-
}
(3)定时器功能分析
(1)和(2)的定时器回调br_multicast_query_expired和br_multicast_port_query_expired均调用了br_multicast_send_query,接下里主要看下这个函数
-
/*
-
** 1、调用函数br_multicast_alloc_query构造一个通用组播组查询报文
-
** 2、对于桥端口不为空时,则将该查询报文从相应的端口发送出去
-
** 3、对于桥端口为空时,则将该查询报文发送给上层协议栈
-
*/
-
static
void __br_multicast_send_query(
struct net_bridge *br,
-
struct net_bridge_port *port,
-
struct br_ip *ip)
-
{
-
struct
sk_buff *skb;
-
-
skb =
br_multicast_alloc_query(br, ip);
-
if (!skb)
-
return;
-
-
if (port) {
-
__skb_push(skb,
sizeof(
struct ethhdr));
-
skb->dev = port->dev;
-
NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_OUT, skb,
NULL, skb->dev,
-
dev_queue_xmit);
-
}
else
-
netif_rx(skb);
-
}
-
-
-
static void br_multicast_send_query(struct net_bridge *br,
-
struct net_bridge_port *port, u32 sent)
-
{
-
unsigned
long time;
-
struct
br_ip br_group;
-
-
if (!
netif_running(br->dev) || br->multicast_disabled ||
-
timer_pending(&br->multicast_querier_timer))
-
return;
-
-
memset(&br_group.u,
0,
sizeof(br_group.u));
-
-
br_group.proto =
htons(ETH_P_IP);
-
-
/* 1、调用内部函数 */
-
__br_multicast_send_query(br, port, &br_group);
-
-
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
-
br_group.proto =
htons(ETH_P_IPV6);
-
__br_multicast_send_query(br, port, &br_group);
-
#endif
-
/* 2、更新桥或者桥端口的查询定时器 */
-
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);
-
}
以上就是桥与桥端口的组播查询定时器的工作原理。
3、igmpsnooping 的开启与关闭
Igmp snooping的开启与关闭是通过函数br_multicast_toggle实现
对于igmp snooping的开启与关闭,目前是通过sysfs实现igmpsnooping的开启与关闭,例如./sys/devices/virtual/net/br0/bridge/multicast_snooping
-
-
int br_multicast_toggle(struct net_bridge *br, unsigned long val)
-
{
-
struct
net_bridge_port *port;
-
int err =
0;
-
struct
net_bridge_mdb_htable *mdb;
-
-
/*如果要设置的值与当前的值相同,则直接返回*/
-
spin_lock(&br->multicast_lock);
-
if (br->multicast_disabled == !val)
-
goto unlock;
-
-
br->multicast_disabled = !val;
-
/*当关闭igmp SNOOPING时,并没有释放br->mdb*/
-
if (br->multicast_disabled)
-
goto unlock;
-
-
if (!
netif_running(br->dev))
-
goto unlock;
-
/*
-
* 1、当开启IGMP SNOOPING时,如果mdb、mdb->old均不为0, 则说明mdb值有问题,此时则会关闭IGMP SNOOPING功能
-
* 2、当开启IGMP SNOOPING时,如果mdb不为0,且mdb->old为0,则调用br_mdb_rehash,将原br->mdb中的组播组项重新
-
* 计算hash值并存放在新的br->mdb中,释放原br->mdb占用的空间
-
*/
-
mdb =
mlock_dereference(br->mdb, br);
-
if (mdb) {
-
if (mdb->old) {
-
err = -EEXIST;
-
rollback:
-
br->multicast_disabled = !!val;
-
goto unlock;
-
}
-
-
err =
br_mdb_rehash(&br->mdb, mdb->max,
-
br->hash_elasticity);
-
if (err)
-
goto rollback;
-
}
-
-
/* 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);
-
-
return err;
-
}
至此,igmp snooping的初始化就完成了。