组播学习之——IGMP Snooping Linux kernel处理流程分析

 

一、br_multicast_rcv

该函数是igmp snooping留给网桥子系统的外部接口函数,当网桥接收了igmp数据包后就会调用该函数进行后续处理。

主要有2个地方会调用到这个函数

1、网桥数据转发流程

当网桥端口接收到接收到数据包后,经过一系列处理后,在br_handle_frame_finish里,如果判断数据包的目的mac地址为0x01开头后,即认为是组播数据包,此时则会调用br_multicast_rcv进行igmp snooping相关的处理。

2、网桥接口br0的数据发送

当数据需要从网桥端口br发送出去时,则会调用网桥设备的发送函数br_dev_xmit,在br_dev_xmit里,如果判断数据包的目的mac地址为0x01开头后,同样会调用函数br_multicast_rcv进行igmp snooping相关的处理。
 

/* 
 * 根据ip协议进行不同的处理
 */
int br_multicast_rcv(struct net_bridge *br, struct net_bridge_port *port, struct sk_buff *skb)
{
	BR_INPUT_SKB_CB(skb)->igmp = 0;
	BR_INPUT_SKB_CB(skb)->mrouters_only = 0;

	if (br->multicast_disabled)
		return 0;

	switch (skb->protocol) {
	case htons(ETH_P_IP):
		return br_multicast_ipv4_rcv(br, port, skb);
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
	case htons(ETH_P_IPV6):
		return br_multicast_ipv6_rcv(br, port, skb);
#endif
	}

	return 0;
}

由于函数br_multicast_ipv4_rcv有对skbuffcb[]数据段的引用,下面介绍一下igmp snooping下cb的定义:


struct br_input_skb_cb {
	struct net_device *brdev;
#ifdef CONFIG_BRIDGE_IGMP_SNOOPING
	int igmp;
	int mrouters_only;
#endif
};

对于支持igmpsnooping时,增加了两个参数:igmpmrouters_only

igmp:表示数据包是否是igmp类型的报文,因为当目的mac地址是0x01开头时,既可以是igmp类型的组播协议控制报文,也可以是组播数据流报文。

mrouters_only:在代码中只要igmp report报文才会将该值设置为1。在从网桥端口接收了一 个igmp 加入报文后,有如下走向:

  1. 若br->router_list.first不为空,则会将这个igmp加入报文从该链表上的所有端口发送出去。
  2. 将该igmp加入报文发送给上层协议栈进行处理。

在上面的函数中,首先将这两个值设置为0,然后对于三层协议为ip协议的数据,则会调用br_multicast_ipv4_rcv进行igmp协议的处理。

二、br_multicast_ipv4_rcv

该函数是对不同的igmp报文进行区分处理。

/* 
 * 1、对数据包的ip头部进行合理性检测,只有检查通过的数据包才会进行后续操作
 * 2、对于非igmp报文的数据包,则直接返回
 * 3、对于v1、v2的igmp report报文,调用br_multicast_add_group进行处理
 * 4、对于v3的igmp report报文,调用br_multicast_igmp3_report进行处理
 * 5、对于igmp leave报文,调用br_multicast_leave_group进行处理
 * 6、对于igmp查询报文,调用br_multicast_query进行处理
 */
static int br_multicast_ipv4_rcv(struct net_bridge *br,
				 struct net_bridge_port *port,
				 struct sk_buff *skb)
{
	struct sk_buff *skb2 = skb;
	struct iphdr *iph;
	struct igmphdr *ih;
	unsigned len;
	unsigned offset;
	int err;

	/* We treat OOM as packet loss for now. */
	if (!pskb_may_pull(skb, sizeof(*iph)))
		return -EINVAL;

	iph = ip_hdr(skb);

	if (iph->ihl < 5 || iph->version != 4)
		return -EINVAL;

	if (!pskb_may_pull(skb, ip_hdrlen(skb)))
		return -EINVAL;

	iph = ip_hdr(skb);

	if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl)))
		return -EINVAL;

	if (iph->protocol != IPPROTO_IGMP)
		return 0;

	len = ntohs(iph->tot_len);
	if (skb->len < len || len < ip_hdrlen(skb))
		return -EINVAL;

	if (skb->len > len) {
		skb2 = skb_clone(skb, GFP_ATOMIC);
		if (!skb2)
			return -ENOMEM;

		err = pskb_trim_rcsum(skb2, len);
		if (err)
			goto err_out;
	}

	len -= ip_hdrlen(skb2);
	offset = skb_network_offset(skb2) + ip_hdrlen(skb2);
	__skb_pull(skb2, offset);
	skb_reset_transport_header(skb2);

	err = -EINVAL;
	if (!pskb_may_pull(skb2, sizeof(*ih)))
		goto out;

	switch (skb2->ip_summed) {
	case CHECKSUM_COMPLETE:
		if (!csum_fold(skb2->csum))
			break;
		/* fall through */
	case CHECKSUM_NONE:
		skb2->csum = 0;
		if (skb_checksum_complete(skb2))
			goto out;
	}

	err = 0;

	BR_INPUT_SKB_CB(skb)->igmp = 1;
	ih = igmp_hdr(skb2);

	switch (ih->type) {
	case IGMP_HOST_MEMBERSHIP_REPORT:
	case IGMPV2_HOST_MEMBERSHIP_REPORT:
		BR_INPUT_SKB_CB(skb2)->mrouters_only = 1;/* 设置为1,以便于发送给上层协议栈以及桥接wan侧端口 */
		err = br_ip4_multicast_add_group(br, port, ih->group);
		break;
	case IGMPV3_HOST_MEMBERSHIP_REPORT:
		err = br_ip4_multicast_igmp3_report(br, port, skb2);
		break;
	case IGMP_HOST_MEMBERSHIP_QUERY:
		err = br_ip4_multicast_query(br, port, skb2);
		break;
	case IGMP_HOST_LEAVE_MESSAGE:
		br_ip4_multicast_leave_group(br, port, ih->group);
		break;
	}

out:
	__skb_push(skb2, offset);
err_out:
	if (skb2 != skb)
		kfree_skb(skb2);
	return err;
}

三、igmp report报文的处理

对于igpm report报文,当接收到这个报文后,说明有一个桥端口要加入到一个组播组中,当开启了igmp snooping时,需要完成以下操作:

1.  根据组播组ip地址,计算hash值,然后在组播组转发数据库表hash数组中找到相应的hash链表

2.  根据组播组ip地址,遍历hash链表,查找符合条件的组播组转发数据库项,此时存在两种情况
    (1) 若查找到符合条件的组播组转发数据库项,则遍历该组播组转发数据库项,判断该桥端口是否已加入该组播组转发数据库项
        (a) 若在组播组转发数据库项的组播端口链表中已经有该桥端口了,则更新组播端口的失效超时定时器
        (b) 若在组播组转发数据库项的组播端口链表中不存在该桥端口,则创建新的组播端口,并加入链表中,然后开启组播组端口的失效超时定时器
    (2) 若查找不到符合条件的组播组转发数据库项,则创建一个新的组播组转发数据库项,并添加到组播组转发数据库表的hash链表中, 创建组播端口,加入到链表中,然后开启组播组端口的失效超时定时器。

3.1、br_ip4_multicast_add_group

igmpv1,igmpv2处理函数。

/* 
 * 1、对于224.0.0.x的组播组地址,则不添加一个组播组数据库项(因为224.0.0.x的地址一般是系统预留的,
 * 包括224.0.0.1代表子系统中的所有pc,224.0.0.2代表子系统中的所有组播路由器)
 * 2、调用br_multicast_add_group
 */
static int br_ip4_multicast_add_group(struct net_bridge *br,
				      struct net_bridge_port *port,
				      __be32 group)
{
	struct br_ip br_group;

	if (ipv4_is_local_multicast(group))
		return 0;

	br_group.u.ip4 = group;
	br_group.proto = htons(ETH_P_IP);

	return br_multicast_add_group(br, port, &br_group);
}

该函数只是对组播组做了简单判断,接下里调用br_multicast_add_group

3.2、br_multicast_add_group

static int br_multicast_add_group(struct net_bridge *br,
				  struct net_bridge_port *port,
				  struct br_ip *group)
{
	struct net_bridge_mdb_entry *mp;
	struct net_bridge_port_group *p;
	struct net_bridge_port_group __rcu **pp;
	unsigned long now = jiffies;
	int err;

	spin_lock(&br->multicast_lock);
	if (!netif_running(br->dev) ||
	    (port && port->state == BR_STATE_DISABLED))
		goto out;
	/* 调用br_multicast_new_group创建或者获取一个已存在的组播组数据库项 */
	mp = br_multicast_new_group(br, port, group);
	err = PTR_ERR(mp);
	if (IS_ERR(mp))
		goto err;

	/* 若端口不存在,则设置组播数据库项失效定时器 */
	if (!port) {
		mp->mglist = true;
		mod_timer(&mp->timer, now + br->multicast_membership_interval);
		goto out;
	}

	/* 
	在组播组数据库项中,查找判断要加入的组播端口是否已存在:
		a)若存在,则更新组播端口失效定时器的值
		b)若不存在,则创建一个组播端口,并将其加入到组播组转发数据库项的组播端口链表中,并更新组播端口的失效定时器的值
	*/
	for (pp = &mp->ports; (p = mlock_dereference(*pp, br)) != NULL; pp = &p->next) {
		if (p->port == port)
			goto found;
		if ((unsigned long)p->port < (unsigned long)port)
			break;
	}

	p = kzalloc(sizeof(*p), GFP_ATOMIC);
	err = -ENOMEM;
	if (unlikely(!p))
		goto err;

	p->addr = *group;
	p->port = port;
	p->next = *pp;
	hlist_add_head(&p->mglist, &port->mglist);
	/* 
	更新组播端口的失效超时定时器,如果在失效超时定时器超时了,还没有更新组播端口的失效超时定时器,则会调用函数
	br_multicast_port_group_expired,删除该组播端口
	*/
	setup_timer(&p->timer, br_multicast_port_group_expired, (unsigned long)p);
	setup_timer(&p->query_timer, br_multicast_port_group_query_expired, (unsigned long)p);

	rcu_assign_pointer(*pp, p);

found:
	mod_timer(&p->timer, now + br->multicast_membership_interval);
out:
	err = 0;

err:
	spin_unlock(&br->multicast_lock);
	return err;
}

3.3、br_multicast_new_group

/* 功能:向br桥的组播组数据库表中链接的组播组数据库项中,加入一个组播端口 */
static struct net_bridge_mdb_entry *br_multicast_new_group(
	struct net_bridge *br, struct net_bridge_port *port,
	struct br_ip *group)
{
	struct net_bridge_mdb_htable *mdb;
	struct net_bridge_mdb_entry *mp;
	int hash;
	int err;
	
	/* 检查桥的组播数据库是否为空,为空则调用br_mdb_rehash新建一个组播数据库 */
	mdb = rcu_dereference_protected(br->mdb, 1);
	if (!mdb) {
		err = br_mdb_rehash(&br->mdb, BR_HASH_SIZE, 0);
		if (err)
			return ERR_PTR(err);
		goto rehash;
	}

	/* 就算该组播组ip的hash值,下面单独分析该函数 */
	hash = br_ip_hash(mdb, group);
	
	/* 调用br_multicast_get_group,下面单独分析该函数 */
	mp = br_multicast_get_group(br, port, group, hash);
	switch (PTR_ERR(mp)) {
	case 0:
		break;

	case -EAGAIN:
rehash:
		mdb = rcu_dereference_protected(br->mdb, 1);
		hash = br_ip_hash(mdb, group);
		break;

	default:
		goto out;
	}

	/* 新建一个组播组数据库转发项 */
	mp = kzalloc(sizeof(*mp), GFP_ATOMIC);
	if (unlikely(!mp))
		return ERR_PTR(-ENOMEM);

	mp->br = br;
	mp->addr = *group;
	
	/* 初始化该转发项的失效定时器 */
	setup_timer(&mp->timer, br_multicast_group_expired,
		    (unsigned long)mp);
	/* 初始化该转发项的查询定时器 */
	setup_timer(&mp->query_timer, br_multicast_group_query_expired,
		    (unsigned long)mp);

	/* 将该转发项加入到组播数据库对应的组播组项 */
	hlist_add_head_rcu(&mp->hlist[mdb->ver], &mdb->mhash[hash]);
	mdb->size++;

out:
	return mp;
}

接下来看br_mdb_rehash

3.4、br_mdb_rehash

/*
功能:组播组转发数据库表的初始化或者重新初始化
1、初始化
     为组播组转发数据库表申请内存,
     为组播组转发数据库表中的hash数组申请内存
     设置mdb->max、mdb->size的值
2、重新初始化
     对于已经存在的组播组数据库表,则重新申请内存进行初始化操作
*/
static int br_mdb_rehash(struct net_bridge_mdb_htable __rcu **mdbp, int max,
			 int elasticity)
{
	struct net_bridge_mdb_htable *old = rcu_dereference_protected(*mdbp, 1);
	struct net_bridge_mdb_htable *mdb;
	int err;
	
	/*  为组播组转发数据库表申请内存 */
	mdb = kmalloc(sizeof(*mdb), GFP_ATOMIC);
	if (!mdb)
		return -ENOMEM;

	/* 设置mdb->max */
	mdb->max = max;
	mdb->old = old;

	/* 为组播组转发数据库表中的hash数组申请内存 */
	mdb->mhash = kzalloc(max * sizeof(*mdb->mhash), GFP_ATOMIC);
	if (!mdb->mhash) {
		kfree(mdb);
		return -ENOMEM;
	}

	/* 设置mdb->size的值 */
	mdb->size = old ? old->size : 0;
	mdb->ver = old ? old->ver ^ 1 : 0;

	if (!old || elasticity)
		get_random_bytes(&mdb->secret, sizeof(mdb->secret));
	else
		mdb->secret = old->secret;

	/* 如果之前不存在组播组数据库表,则经过前面的初始化就结束了 */
	if (!old)
		goto out;

	/* 对于已经存在的组播组数据库表,则调用br_mdb_copy重新申请内存进行初始化操作 */
	err = br_mdb_copy(mdb, old, elasticity);
	if (err) {
		kfree(mdb->mhash);
		kfree(mdb);
		return err;
	}
	/* 调用br_mdb_free释放掉原组播组数据库表的内存 */
	call_rcu_bh(&mdb->rcu, br_mdb_free);

out:
	rcu_assign_pointer(*mdbp, mdb);

	return 0;
}

3.5、br_mdb_copy

/*
功能:将组播数据库old中的数据项迁移至new
*/
static int br_mdb_copy(struct net_bridge_mdb_htable *new,
		       struct net_bridge_mdb_htable *old,
		       int elasticity)
{
	struct net_bridge_mdb_entry *mp;
	struct hlist_node *p;
	int maxlen;
	int len;
	int i;

	/* 
	将原组播组数据库表中hash数组里所有的组播组数据库项,经过重新计算hash值后,
	重新链接到新的组播组数据库表的hash数组里的hash链表中
	*/	
	for (i = 0; i < old->max; i++)
		hlist_for_each_entry(mp, p, &old->mhash[i], hlist[old->ver])
			hlist_add_head(&mp->hlist[new->ver],
				       &new->mhash[br_ip_hash(new, &mp->addr)]);

	if (!elasticity)
		return 0;

	maxlen = 0;
	for (i = 0; i < new->max; i++) {
		len = 0;
		hlist_for_each_entry(mp, p, &new->mhash[i], hlist[new->ver])
			len++;
		if (len > maxlen)
			maxlen = len;
	}

	return maxlen > elasticity ? -EINVAL : 0;
}

接下来看br_multicast_get_group

3.6、br_multicast_get_group

/*
功能:从一个br桥的组播组数据库表中查找一个组播组数据库项
1、若查找到函数直接返回
2、若没有查找到,则执行如下操作
a)若组播组数据库表的hash表mdb->[mhash]中已经链接的组播组数据库的值大于br->hash_elasticity,则会调用br_mdb_rehash重新计算hash,重新创建新的组播组数据库表
	i)若调用成功,则 返回-EAGAIN,表示可以创建新的组播组数据库项,且组播组数据库表已更新,需要计算新的hash值
	ii)若调用不成功,则关闭igpmsnooping,并返回其他错误信息
b)若组播组数据库表的所有hash表中,已经链接的组播组数据库项的值大于mdb->max,则将hash数组的值扩大至2倍
	i)若扩大后的值大于br->hash_max的值,则关闭igmp snooping,并返回错误信息
	ii)若扩大后的值不大于br->hash_max,则调用br_mdb_rehash重新创建组播组数据库表,成功后返回-EAGAIN;否则关闭igmp snooping,flood所有组播流数据
c)若不属于以上两种情况,则直接返回NULL,表示可以创建新的组播组数据库项,且组播组数据库表没有更新
*/
--------------------- 
作者:jerry_chg 
来源:CSDN 
原文:https://blog.csdn.net/lickylin/article/details/24608439 
版权声明:本文为博主原创文章,转载请附上博文链接!
*/
static struct net_bridge_mdb_entry *br_multicast_get_group(
	struct net_bridge *br, struct net_bridge_port *port,
	struct br_ip *group, int hash)
{
	struct net_bridge_mdb_htable *mdb;
	struct net_bridge_mdb_entry *mp;
	struct hlist_node *p;
	unsigned count = 0;
	unsigned max;
	int elasticity;
	int err;

	/* 根据传进来的hash值,遍历对应链表,如果找到该组播组数据项,则直接返回 */
	mdb = rcu_dereference_protected(br->mdb, 1);
	hlist_for_each_entry(mp, p, &mdb->mhash[hash], hlist[mdb->ver]) {
		count++;
		if (unlikely(br_ip_equal(group, &mp->addr)))
			return mp;
	}

	elasticity = 0;
	max = mdb->max;
	
	/* 
	若组播组数据库表的hash表mdb->[mhash]中已经链接的组播组数据库的值大于br->hash_elasticity,
	则冲给elasticity赋值,后面的代码判断则会调用br_mdb_rehash重新计算hash,重新创建新的组播组数据库表
	*/
	if (unlikely(count > br->hash_elasticity && count)) {
		if (net_ratelimit())
			br_info(br, "Multicast hash table "
				"chain limit reached: %s\n",
				port ? port->dev->name : br->dev->name);

		elasticity = br->hash_elasticity;
	}

	/* 若已经链接的组播组数据库项的值大于mdb->max,则将hash数组的值扩大至2倍 */
	if (mdb->size >= max) {
		max *= 2;
		/* 扩大后的值大于等于 br->hash_max,则关闭igpmsnooping,返回错误信息 */
		if (unlikely(max >= br->hash_max)) {
			br_warn(br, "Multicast hash table maximum "
				"reached, disabling snooping: %s, %d\n",
				port ? port->dev->name : br->dev->name, max);
			err = -E2BIG;
disable:
			br->multicast_disabled = 1;
			goto err;
		}
	}

	/* 
	如果扩大后的值不大于br->hash_max 或者 elasticity不为0(上面的代码若组播组数据库表的hash表
	mdb->[mhash]中已经链接的组播组数据库的值大于br->hash_elasticity,就会给elasticity赋值),则
	重新创建新的组播组数据库表
	*/
	if (max > mdb->max || elasticity) {
		if (mdb->old) {
			if (net_ratelimit())
				br_info(br, "Multicast hash table "
					"on fire: %s\n",
					port ? port->dev->name : br->dev->name);
			err = -EEXIST;
			goto err;
		}

		err = br_mdb_rehash(&br->mdb, max, elasticity);
		/* 重新创建失败,则关闭igpmsnooping,返回错误信息 */
		if (err) {
			br_warn(br, "Cannot rehash multicast "
				"hash table, disabling snooping: %s, %d, %d\n",
				port ? port->dev->name : br->dev->name,
				mdb->size, err);
			goto disable;
		}
		/* 重新创建成功,表示可以创建新的组播组数据库项,且组播组数据库表已更新,需要计算新的hash值 */
		err = -EAGAIN;
		goto err;
	}

	return NULL;

err:
	mp = ERR_PTR(err);
	return mp;
}



对于这个函数,我感觉下面这点比较有疑惑:
当所有hash表项中的组播组数据库项的总数大于hash数组的最大值时,则会将hash数组的容量扩大一倍,按照这个实现的话,作者是想一个hash链表中最大只有一个链表节点,以提高查询性能,但是我们通过br->hash_elasticity设置了一个hash表中最大可以有几个hash节点,我感觉此处可以将判断 mdb->size >= max改成mdb->size >= max*br->hash_elasticity,我感觉这样比较合理一点,不然这个这都没啥用了。

另外该函数巧妙的一点,当通过执行代码,确定不能添加新的组播组数据库项时,函数不是直接返回失败,而是先把igmp snooping先关闭,然后再返回,这样一来,所有的组播组flood到所有桥端口,组播功能还能起作用。如果不关闭igmp snooping,也不创建新的组播组数据库项,则原来已加入的端口还均能接收到组播流,而想要新加入的那个桥端口则收不到新的组播流,会影响功能。

上面几个介绍的函数主要是组播组转发数据库表初始化、组播组转发数据库项的创建与查找、组播端口的创建,接下来我能我们分析组播端口的失效超时定时器的超时处理函数br_multicast_port_group_expired。

3.7、br_multicast_port_group_expired

/*
功能:组播端口的失效定时器的超时执行函数
*/
static void br_multicast_port_group_expired(unsigned long data)
{
	struct net_bridge_port_group *pg = (void *)data;
	struct net_bridge *br = pg->port->br;

	spin_lock(&br->multicast_lock);
	if (!netif_running(br->dev) || timer_pending(&pg->timer) ||
	    hlist_unhashed(&pg->mglist))
		goto out;
	/* 该函数的功能比较简单,调用br_multicast_del_pg删除组播端口 */
	br_multicast_del_pg(br, pg);

out:
	spin_unlock(&br->multicast_lock);
}

3.8、br_multicast_del_pg 

/* 删除一个组播端口 */
static void br_multicast_del_pg(struct net_bridge *br,
				struct net_bridge_port_group *pg)
{
	struct net_bridge_mdb_htable *mdb;
	struct net_bridge_mdb_entry *mp;
	struct net_bridge_port_group *p;
	struct net_bridge_port_group __rcu **pp;

	mdb = mlock_dereference(br->mdb, br);
	
	/* 通过ip查找到该组播端口关联的组播组数据库项 */
	mp = br_mdb_ip_get(mdb, &pg->addr);
	if (WARN_ON(!mp))
		return;

	for (pp = &mp->ports;
	     (p = mlock_dereference(*pp, br)) != NULL;
	     pp = &p->next) {
		if (p != pg)
			continue;

		rcu_assign_pointer(*pp, p->next);
		/* 将该组播端口从关联到的组播组数据库项的组播端口链表中删除 */
		hlist_del_init(&p->mglist);
		/* 删除组播端口的失效定时器和查询定时器 */
		del_timer(&p->timer);
		del_timer(&p->query_timer);
		/* 释放内存 */
		call_rcu_bh(&p->rcu, br_multicast_free_pg);

		/*
		若该组播端口关联的组播组数据库项的组播端口链表上已经没有组播端口,
		则更新该组播组数据库项的失效超时时间为当前时间,即组播组数据库
		的失效超时定时器立马到期,执行其超时处理函数
		*/
		if (!mp->ports && !mp->mglist &&
		    netif_running(br->dev))
			mod_timer(&mp->timer, jiffies);

		return;
	}

	WARN_ON(1);
}

static void br_multicast_free_pg(struct rcu_head *head)
{
	struct net_bridge_port_group *p =
		container_of(head, struct net_bridge_port_group, rcu);

	kfree(p);
}

3.9、br_ip4_multicast_igmp3_report 

该函数用来处理igmpv3的报文,但是该版本内核没有对v3做实质性处理,目前是按照v2的处理流程来。

四、igmp query报文的处理

对于接收到igmp query报文,主要有两个途径:

1)  本地上层协议开启igmp proxy功能,向br桥发送通用或者特定组播组查询报文

2)  桥中有一个桥接wan连接端口,该端口会接收到通用或者特定组播组查询报文

对于第一种情况,我们需要更新组播端口的失效超时定时器,而对于第二种情况,我们需要将这个桥端口加入到br->route_list链表中,以便桥接收到组播report报文后,将这些报文转发到这些桥端口中。
 

下面开始分析igmp query的处理函数br_ip4_multicast_query

4.1 br_ip4_multicast_query

/*
该函数首先会调用br_multicast_query_received,对于桥端口不为空的情况,将桥端口加入到br->route_list中,并开启桥端口的路由查询定时器以及桥的查询定时器。

接着对于特定组播组查询报文,会根据组播组地址查找相应的组播组转发数据库项,更新所有相关的组播端口的失效超时定时器
*/
static int br_ip4_multicast_query(struct net_bridge *br,
				  struct net_bridge_port *port,
				  struct sk_buff *skb)
{
	struct iphdr *iph = ip_hdr(skb);
	struct igmphdr *ih = igmp_hdr(skb);
	struct net_bridge_mdb_entry *mp;
	struct igmpv3_query *ih3;
	struct net_bridge_port_group *p;
	struct net_bridge_port_group __rcu **pp;
	unsigned long max_delay;
	unsigned long now = jiffies;
	__be32 group;
	int err = 0;

	spin_lock(&br->multicast_lock);
	if (!netif_running(br->dev) ||
	    (port && port->state == BR_STATE_DISABLED))
		goto out;

	/* 
	调用br_multicast_query_received,对于桥端口不为空的情况,将桥端口加入到br->route_list中,
	并开启桥端口的路由查询定时器以及桥的查询定时器
	*/
	br_multicast_query_received(br, port, !!iph->saddr);

	group = ih->group;

	/* igmpv1和igmpv2 */
	if (skb->len == sizeof(*ih)) {
		/* v1的该字段没用,v2表示最大响应时间,缺省10s,以0.1s为单位 */
		max_delay = ih->code * (HZ / IGMP_TIMER_SCALE);

		if (!max_delay) {
			max_delay = 10 * HZ;
			group = 0;
		}
	/* igmpv3 */
	} else {
		if (!pskb_may_pull(skb, sizeof(struct igmpv3_query))) {
			err = -EINVAL;
			goto out;
		}
		/* 源地址个数不为0,直接out? */
		ih3 = igmpv3_query_hdr(skb);
		if (ih3->nsrcs)
			goto out;

		/* 计算v3的最大相应时间 */
		max_delay = ih3->code ?
			    IGMPV3_MRC(ih3->code) * (HZ / IGMP_TIMER_SCALE) : 1;
	}

	if (!group)
		goto out;

	/* 根据组播地址获取该桥下的组播组转发数据库项 */
	mp = br_mdb_ip4_get(mlock_dereference(br->mdb, br), group);
	if (!mp)
		goto out;

	max_delay *= br->multicast_last_member_count;

	/* 更新组播组数据库项失效定时器 */
	if (mp->mglist &&
	    (timer_pending(&mp->timer) ?
	     time_after(mp->timer.expires, now + max_delay) :
	     try_to_del_timer_sync(&mp->timer) >= 0))
		mod_timer(&mp->timer, now + max_delay);

	/* 更新组播端口的失效定时器 */
	for (pp = &mp->ports;
	     (p = mlock_dereference(*pp, br)) != NULL;
	     pp = &p->next) {
		if (timer_pending(&p->timer) ?
		    time_after(p->timer.expires, now + max_delay) :
		    try_to_del_timer_sync(&p->timer) >= 0)
			mod_timer(&p->timer, now + max_delay);
	}

out:
	spin_unlock(&br->multicast_lock);
	return err;
}

4.2、br_multicast_query_received

static void br_multicast_query_received(struct net_bridge *br,
					struct net_bridge_port *port,
					int saddr)
{
	/* 若源地址不为空,更新桥组播查询定时器 */
	if (saddr)
		mod_timer(&br->multicast_querier_timer,
			  jiffies + br->multicast_querier_interval);
	/* 若定时器已经执行则直接返回 */
	else if (timer_pending(&br->multicast_querier_timer))
		return;

	br_multicast_mark_router(br, port);
}

 4.3、br_multicast_mark_router


static void br_multicast_mark_router(struct net_bridge *br,
				     struct net_bridge_port *port)
{
	unsigned long now = jiffies;

	/* 桥端口定时器不为空,则更新桥路由查询定时器 */
	if (!port) {
		if (br->multicast_router == 1)
			mod_timer(&br->multicast_router_timer,
				  now + br->multicast_querier_interval);
		return;
	}

	if (port->multicast_router != 1)
		return;

	if (!hlist_unhashed(&port->rlist))
		goto timer;

	/* 添加端口到rotuer_list */
	br_multicast_add_router(br, port);

	/* 更新桥端口路由查询定时器 */
timer:
	mod_timer(&port->multicast_router_timer,
		  now + br->multicast_querier_interval);
}

4.4、br_multicast_add_router 

/*
 * Add port to rotuer_list
 *  list is maintained ordered by pointer value
 *  and locked by br->multicast_lock and RCU
 */
static void br_multicast_add_router(struct net_bridge *br,
				    struct net_bridge_port *port)
{
	struct net_bridge_port *p;
	struct hlist_node *n, *slot = NULL;

	hlist_for_each_entry(p, n, &br->router_list, rlist) {
		if ((unsigned long) port >= (unsigned long) p)
			break;
		slot = n;
	}

	if (slot)
		hlist_add_after_rcu(slot, &port->rlist);
	else
		hlist_add_head_rcu(&port->rlist, &br->router_list);
}

4.5、 br_multicast_router_expired

对于桥端口的路由查询定时器,如果在查询定时器超时前没有再从这个桥端口收到查询报文,则会将该桥端口从br->route_list中删除(通过桥端口的路由查询定时器处理函数br_multicast_router_expired,该函数内容比较简单:


static void br_multicast_router_expired(unsigned long data)
{
	struct net_bridge_port *port = (void *)data;
	struct net_bridge *br = port->br;

	spin_lock(&br->multicast_lock);
	if (port->multicast_router != 1 ||
	    timer_pending(&port->multicast_router_timer) ||
	    hlist_unhashed(&port->rlist))
		goto out;

	hlist_del_init_rcu(&port->rlist);

out:
	spin_unlock(&br->multicast_lock);
}

对于桥的路由查询定时器,该版本内核没有实现,暂不分析。

五、igmp leave报文的处理

收到leave报文后,由br_ip4_multicast_leave_group处理:

5.1、br_ip4_multicast_leave_group

static void br_ip4_multicast_leave_group(struct net_bridge *br,
					 struct net_bridge_port *port,
					 __be32 group)
{
	struct br_ip br_group;

	/* 对于组播组地址为224.0.0.x的组播报文不进行处理 */
	if (ipv4_is_local_multicast(group))
		return;

	br_group.u.ip4 = group;
	br_group.proto = htons(ETH_P_IP);

	br_multicast_leave_group(br, port, &br_group);
}

接下来看br_multicast_leave_group.

5.2、br_multicast_leave_group


/*
功能:igmp leave报文的主要处理函数
1、根据组播地址,从组播数据库的hash表中查找对应的组播组转发数据库项
	i、 若找到了,则继续第2步
	ii、若没找到则直接返回
2、判断桥端口
	i、 若桥端口不存在,则开启组播组转发数据项的失效定时器和立即开启组播组转发项的查询定时器,然后直接返回
	ii、若桥端口存在,则进行第3步
3、在组播组转发数据库项的组播端口链表中查找对应的组播端口
	i、 若没有查到,则直接返回
	ii、若查到了,则开启组播端口的失效定时器并立即开启组播端口的查询定时器
*/

static void br_multicast_leave_group(struct net_bridge *br,
				     struct net_bridge_port *port,
				     struct br_ip *group)
{
	struct net_bridge_mdb_htable *mdb;
	struct net_bridge_mdb_entry *mp;
	struct net_bridge_port_group *p;
	unsigned long now;
	unsigned long time;

	spin_lock(&br->multicast_lock);
	if (!netif_running(br->dev) ||
	    (port && port->state == BR_STATE_DISABLED) ||
	    timer_pending(&br->multicast_querier_timer))
		goto out;

	/* 获取组播数据库 */
	mdb = mlock_dereference(br->mdb, br);
	/* 根据组播地址获取到组播组转发数据库项 */
	mp = br_mdb_ip_get(mdb, group);
	if (!mp)
		goto out;

	now = jiffies;
	time = now + br->multicast_last_member_count *
		     br->multicast_last_member_interval;
	
	/* 若桥端口为空*/
	if (!port) {
		if (mp->mglist &&
		    (timer_pending(&mp->timer) ?
		     time_after(mp->timer.expires, time) :
		     try_to_del_timer_sync(&mp->timer) >= 0)) {
			/* 开启组播组转发数据库项的失效定时器 */
			mod_timer(&mp->timer, time);
			/* 立即开启组播组转发数据库项的查询定时器 */
			mp->queries_sent = 0;
			mod_timer(&mp->query_timer, now);
		}

		goto out;
	}

	/* 在组播组转发数据库项的组播端口链表中查找对应的组播端口 */
	for (p = mlock_dereference(mp->ports, br);
	     p != NULL;
	     p = mlock_dereference(p->next, br)) {
		if (p->port != port)
			continue;

		if (!hlist_unhashed(&p->mglist) &&
		    (timer_pending(&p->timer) ?
		     time_after(p->timer.expires, time) :
		     try_to_del_timer_sync(&p->timer) >= 0)) {
			/* 开启组播端口的失效定时器 */
			mod_timer(&p->timer, time);
			/* 立即开启组播端口的查询定时器 */
			p->queries_sent = 0;
			mod_timer(&p->query_timer, now);
		}

		break;
	}

out:
	spin_unlock(&br->multicast_lock);
}

下一篇文章分析igmp proxy

  • 8
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值