组播学习之——IGMP Snooping Linux kernel数据结构及初始化

 注:本文基于linux kernel 2.6.39.4

一、IGMP Snooping基本数据结构

1、struct net_bridge_mdb_htable

组播组数据库转发表,该结构体将所有的组播组数据库转发项通过hash数组连接到一起


   
   
  1. struct net_bridge_mdb_htable
  2. {
  3. struct hlist_head *mhash; //hash数组,将所有的net_bridge_mdb_entry链接到一起
  4. struct rcu_head rcu;
  5. struct net_bridge_mdb_htable *old;
  6. u32 size; //hash数组中所有hash链表中存在的所有net_bridge_mdb_entry项的总数
  7. u32 max; //hash数组的最大值
  8. u32 secret;
  9. u32 ver;
  10. };

 2、net_bridge_mdb_entry

一个组播组数据库转发项,描述一个组播组的详细信息


   
   
  1. struct net_bridge_mdb_entry
  2. {
  3. struct hlist_node hlist[ 2];
  4. struct net_bridge *br;
  5. struct net_bridge_port_group __rcu *ports;
  6. struct rcu_head rcu;
  7. struct timer_list timer; //组播组数据库项失效定时器,若超时,则会将该组播端口从组播组数据库项的组播端口列表中删除
  8. struct timer_list query_timer; //查询定时
  9. struct br_ip addr; //组播组地址
  10. bool mglist;
  11. u32 queries_sent;
  12. };

其中port指向所有加入到组播组addr的组播端口

mglist用于将通过桥br接收到的igmp 加入报文创建的组播组数据库转发项连接起来

 3、struct net_bridge_port_group

加入一个组播组的组播端口信息结构体


   
   
  1. struct net_bridge_port_group {
  2. struct net_bridge_port *port; //加入该组播组的桥端口
  3. struct net_bridge_port_group __rcu *next; //下一个组播组详细参数结构体(可以有多个端口加入到同一个组播组)
  4. struct hlist_node mglist;
  5. struct rcu_head rcu;
  6. struct timer_list timer; //组播端口失效定时器,若超时,则会将该组播端口从组播组数据库项的组播端口列表中删除
  7. struct timer_list query_timer; //查询定时器
  8. struct br_ip addr; //组播组地址,每一个组播端口也要一个组播组的原因是,通过这个值可以快速查找到其关联的组播组数据库项
  9. u32 queries_sent; //已发送查询包的次数

其中,port链表用于将所有加入到组播组addr的对应桥端

Next指向下一个组播端口,该下一个组播组地址也为addr,仅仅是port值不同。

其中mglist用于将该组播端口加入到port的mglist链表中,通过桥端口的mglist链表,能够查找到该桥端口加入的所有组播组。

下面是这3个数据结构之间的关系

 

二、IGMP Snooping初始化

1、初始化接口

Igmp snooping的初始化是当创建一个桥设备时,使用函数br_multicast_init来实现的,在该函数里,主要组播组转发数据库表的hash数组的最大值进行设置,设置发送查询报文的间隔时间,以及发送查询报文的次数,并初始化桥的igmp查询的定时器。


   
   
  1. void br_multicast_init(struct net_bridge *br)
  2. {
  3. br->hash_elasticity = 4; //每个组播组 ip中所能关联的端口个数
  4. br->hash_max = 512; //mdb中hash数组的最大值
  5. br->multicast_router = 1;
  6. br->multicast_last_member_count = 2;
  7. br->multicast_startup_query_count = 2;
  8. br->multicast_last_member_interval = HZ;
  9. br->multicast_query_response_interval = 10 * HZ; //组播查询最大回复时间
  10. br->multicast_startup_query_interval = 125 * HZ / 4; //开启发送查询报文的间隔时间
  11. br->multicast_query_interval = 125 * HZ; //查询包的发送间隔时间
  12. br->multicast_querier_interval = 255 * HZ;
  13. br->multicast_membership_interval = 260 * HZ;
  14. spin_lock_init(&br->multicast_lock);
  15. setup_timer(&br->multicast_router_timer,
  16. br_multicast_local_router_expired, 0);
  17. setup_timer(&br->multicast_querier_timer,
  18. br_multicast_local_router_expired, 0);
  19. setup_timer(&br->multicast_query_timer, br_multicast_query_expired,
  20. ( unsigned long)br);
  21. }

2、桥与桥端口的查询定时器

对于桥与桥端口的查询定时器,主要是用来实现igmp snooping组播数据库转发表的持续更新的。

(1)Br桥的查询定时器

对于定时器br->multicast_query_timer,在开启igmp snooping功能后就会开启桥端口的组播查询功能,主要分为如下情况:

  1. 当br设备up(br_dev_open中调用)且开启igmp snooping时,就会调用函数br_multicast_open,开启一个立即超时的查询定时器
  2. 重新开始igmp snooping(br_multicast_toggle中调用)时,也会调用函数br_multicast_open,开启一个立即超时的查询定时器。
     

   
   
  1. /*
  2. 开启一个桥的igmpsnooping功能
  3. 1、重置发送查询数据包计数
  4. 2、修改桥的查询定时器
  5. */
  6. void br_multicast_open(struct net_bridge *br)
  7. {
  8. br->multicast_startup_queries_sent = 0;
  9. if (br->multicast_disabled)
  10. return;
  11. mod_timer(&br->multicast_query_timer, jiffies);
  12. }

当桥的查询定时器超时,就会调用函数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功能后就会开启桥端口的组播查询功能,触发定时器分为下面两种情况:

  1. 在一个桥端口up起来且开启igmp snooping时,通过间接调用__br_multicast_enable_port,启动一个立即到期的桥端口的查询定时器
  2. 在重新开启igmp snooping功能时,通过调用__br_multicast_enable_port,启动一个立即到期的桥端口的查询定时器。

   
   
  1. static void __br_multicast_enable_port( struct net_bridge_port *port)
  2. {
  3. port->multicast_startup_queries_sent = 0;
  4. if ( try_to_del_timer_sync(&port->multicast_query_timer) >= 0 ||
  5. del_timer(&port->multicast_query_timer))
  6. mod_timer(&port->multicast_query_timer, jiffies);
  7. }

该接口比较简单,干掉之前的定时器,立即重新启动查询定时器。

桥端口定时器的初始化是在桥端口创建时进行的,如下:


   
   
  1. void br_multicast_add_port(struct net_bridge_port *port)
  2. {
  3. port->multicast_router = 1;
  4. setup_timer(&port->multicast_router_timer, br_multicast_router_expired,
  5. ( unsigned long)port);
  6. setup_timer(&port->multicast_query_timer,
  7. br_multicast_port_query_expired, ( unsigned long)port);
  8. }
  9. /* called with RTNL but without bridge lock */
  10. static struct net_bridge_port * new_nbp( struct net_bridge *br, struct net_device *dev)
  11. {
  12. int index;
  13. struct net_bridge_port *p;
  14. index = find_portno(br);
  15. if (index < 0)
  16. return ERR_PTR(index);
  17. p = kzalloc( sizeof(*p), GFP_KERNEL);
  18. if (p == NULL)
  19. return ERR_PTR(-ENOMEM);
  20. p->br = br;
  21. dev_hold(dev);
  22. p->dev = dev;
  23. p->path_cost = port_cost(dev);
  24. p->priority = 0x8000 >> BR_PORT_BITS;
  25. p->port_no = index;
  26. p->flags = 0;
  27. br_init_port(p);
  28. p->state = BR_STATE_DISABLED;
  29. br_stp_port_timer_init(p);
  30. br_multicast_add_port(p);
  31. return p;
  32. }
  33. /* called with RTNL but without bridge lock */
  34. static struct net_bridge_port * new_nbp( struct net_bridge *br, struct net_device *dev)
  35. {
  36. ...
  37. p = new_nbp(br, dev);
  38. ...
  39. }
(3)定时器功能分析

(1)和(2)的定时器回调br_multicast_query_expiredbr_multicast_port_query_expired均调用了br_multicast_send_query,接下里主要看下这个函数


   
   
  1. /*
  2. ** 1、调用函数br_multicast_alloc_query构造一个通用组播组查询报文
  3. ** 2、对于桥端口不为空时,则将该查询报文从相应的端口发送出去
  4. ** 3、对于桥端口为空时,则将该查询报文发送给上层协议栈
  5. */
  6. static void __br_multicast_send_query( struct net_bridge *br,
  7. struct net_bridge_port *port,
  8. struct br_ip *ip)
  9. {
  10. struct sk_buff *skb;
  11. skb = br_multicast_alloc_query(br, ip);
  12. if (!skb)
  13. return;
  14. if (port) {
  15. __skb_push(skb, sizeof( struct ethhdr));
  16. skb->dev = port->dev;
  17. NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_OUT, skb, NULL, skb->dev,
  18. dev_queue_xmit);
  19. } else
  20. netif_rx(skb);
  21. }
  22. static void br_multicast_send_query(struct net_bridge *br,
  23. struct net_bridge_port *port, u32 sent)
  24. {
  25. unsigned long time;
  26. struct br_ip br_group;
  27. if (! netif_running(br->dev) || br->multicast_disabled ||
  28. timer_pending(&br->multicast_querier_timer))
  29. return;
  30. memset(&br_group.u, 0, sizeof(br_group.u));
  31. br_group.proto = htons(ETH_P_IP);
  32. /* 1、调用内部函数 */
  33. __br_multicast_send_query(br, port, &br_group);
  34. #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
  35. br_group.proto = htons(ETH_P_IPV6);
  36. __br_multicast_send_query(br, port, &br_group);
  37. #endif
  38. /* 2、更新桥或者桥端口的查询定时器 */
  39. time = jiffies;
  40. time += sent < br->multicast_startup_query_count ?
  41. br->multicast_startup_query_interval :
  42. br->multicast_query_interval;
  43. mod_timer(port ? &port->multicast_query_timer :
  44. &br->multicast_query_timer, time);
  45. }

以上就是桥与桥端口的组播查询定时器的工作原理。

3、igmpsnooping 的开启与关闭

Igmp snooping的开启与关闭是通过函数br_multicast_toggle实现

对于igmp snooping的开启与关闭,目前是通过sysfs实现igmpsnooping的开启与关闭,例如./sys/devices/virtual/net/br0/bridge/multicast_snooping


   
   
  1. int br_multicast_toggle(struct net_bridge *br, unsigned long val)
  2. {
  3. struct net_bridge_port *port;
  4. int err = 0;
  5. struct net_bridge_mdb_htable *mdb;
  6. /*如果要设置的值与当前的值相同,则直接返回*/
  7. spin_lock(&br->multicast_lock);
  8. if (br->multicast_disabled == !val)
  9. goto unlock;
  10. br->multicast_disabled = !val;
  11. /*当关闭igmp SNOOPING时,并没有释放br->mdb*/
  12. if (br->multicast_disabled)
  13. goto unlock;
  14. if (! netif_running(br->dev))
  15. goto unlock;
  16.     /*
  17. * 1、当开启IGMP SNOOPING时,如果mdb、mdb->old均不为0, 则说明mdb值有问题,此时则会关闭IGMP SNOOPING功能
  18.     * 2、当开启IGMP SNOOPING时,如果mdb不为0,且mdb->old为0,则调用br_mdb_rehash,将原br->mdb中的组播组项重新
  19. * 计算hash值并存放在新的br->mdb中,释放原br->mdb占用的空间
  20. */
  21. mdb = mlock_dereference(br->mdb, br);
  22. if (mdb) {
  23. if (mdb->old) {
  24. err = -EEXIST;
  25. rollback:
  26. br->multicast_disabled = !!val;
  27. goto unlock;
  28. }
  29. err = br_mdb_rehash(&br->mdb, mdb->max,
  30. br->hash_elasticity);
  31. if (err)
  32. goto rollback;
  33. }
  34. /* 1、reset br的发送查询包统计计数
  35. * 2、修改br的组播查询定时器multicast_query_timer的值
  36. */
  37. br_multicast_open(br);
  38. /* 对于br桥组上的每一个端口
  39. * 1、reset port的发送查询包统计计数
  40. * 2、删除端口的查询定时器,并重新调度
  41. */
  42. list_for_each_entry(port, &br->port_list, list) {
  43. if (port->state == BR_STATE_DISABLED ||
  44. port->state == BR_STATE_BLOCKING)
  45. continue;
  46. __br_multicast_enable_port(port);
  47. }
  48. unlock:
  49. spin_unlock(&br->multicast_lock);
  50. return err;
  51. }

至此,igmp snooping的初始化就完成了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值