[pim] Initial pim 0.155
diff --git a/pimd/pim_igmpv3.c b/pimd/pim_igmpv3.c
new file mode 100644
index 0000000..f9fa123
--- /dev/null
+++ b/pimd/pim_igmpv3.c
@@ -0,0 +1,1717 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#include <zebra.h>
+#include "log.h"
+#include "memory.h"
+
+#include "pimd.h"
+#include "pim_iface.h"
+#include "pim_igmp.h"
+#include "pim_igmpv3.h"
+#include "pim_str.h"
+#include "pim_util.h"
+#include "pim_time.h"
+#include "pim_zebra.h"
+#include "pim_oil.h"
+
+static void group_retransmit_timer_on(struct igmp_group *group);
+static long igmp_group_timer_remain_msec(struct igmp_group *group);
+static long igmp_source_timer_remain_msec(struct igmp_source *source);
+static void group_query_send(struct igmp_group *group);
+static void source_query_send_by_flag(struct igmp_group *group,
+				      int num_sources_tosend);
+
+static void on_trace(const char *label,
+		     struct interface *ifp, struct in_addr from,
+		     struct in_addr group_addr,
+		     int num_sources, struct in_addr *sources)
+{
+  if (PIM_DEBUG_IGMP_TRACE) {
+    char from_str[100];
+    char group_str[100];
+
+    pim_inet4_dump("<from?>", from, from_str, sizeof(from_str));
+    pim_inet4_dump("<group?>", group_addr, group_str, sizeof(group_str));
+
+    zlog_debug("%s: from %s on %s: group=%s sources=%d",
+	       label, from_str, ifp->name, group_str, num_sources);
+  }
+}
+
+int igmp_group_compat_mode(const struct igmp_sock *igmp,
+			   const struct igmp_group *group)
+{
+  struct pim_interface *pim_ifp;
+  int64_t               now_dsec;
+  long                  older_host_present_interval_dsec;
+
+  zassert(igmp);
+  zassert(igmp->interface);
+  zassert(igmp->interface->info);
+
+  pim_ifp = igmp->interface->info;
+
+  /*
+    RFC 3376: 8.13. Older Host Present Interval
+
+    This value MUST be ((the Robustness Variable) times (the Query
+    Interval)) plus (one Query Response Interval).
+
+    older_host_present_interval_dsec = \
+      igmp->querier_robustness_variable * \
+      10 * igmp->querier_query_interval + \
+      pim_ifp->query_max_response_time_dsec;
+  */
+  older_host_present_interval_dsec =
+    PIM_IGMP_OHPI_DSEC(igmp->querier_robustness_variable,
+		       igmp->querier_query_interval,
+		       pim_ifp->igmp_query_max_response_time_dsec);
+
+  now_dsec = pim_time_monotonic_dsec();
+  if (now_dsec < 1) {
+    /* broken timer logged by pim_time_monotonic_dsec() */
+    return 3;
+  }
+
+  if ((now_dsec - group->last_igmp_v1_report_dsec) < older_host_present_interval_dsec)
+    return 1; /* IGMPv1 */
+
+  if ((now_dsec - group->last_igmp_v2_report_dsec) < older_host_present_interval_dsec)
+    return 2; /* IGMPv2 */
+
+  return 3; /* IGMPv3 */
+}
+
+void igmp_group_reset_gmi(struct igmp_group *group)
+{
+  long group_membership_interval_msec;
+  struct pim_interface *pim_ifp;
+  struct igmp_sock *igmp;
+  struct interface *ifp;
+
+  igmp = group->group_igmp_sock;
+  ifp = igmp->interface;
+  pim_ifp = ifp->info;
+
+  /*
+    RFC 3376: 8.4. Group Membership Interval
+
+    The Group Membership Interval is the amount of time that must pass
+    before a multicast router decides there are no more members of a
+    group or a particular source on a network.
+
+    This value MUST be ((the Robustness Variable) times (the Query
+    Interval)) plus (one Query Response Interval).
+
+    group_membership_interval_msec = querier_robustness_variable *
+                                     (1000 * querier_query_interval) +
+                                     100 * query_response_interval_dsec;
+  */
+  group_membership_interval_msec =
+    PIM_IGMP_GMI_MSEC(igmp->querier_robustness_variable,
+		      igmp->querier_query_interval,
+		      pim_ifp->igmp_query_max_response_time_dsec);
+
+  if (PIM_DEBUG_IGMP_TRACE) {
+    char group_str[100];
+    pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+    zlog_debug("Resetting group %s timer to GMI=%ld.%03ld sec on %s",
+	       group_str,
+	       group_membership_interval_msec / 1000,
+	       group_membership_interval_msec % 1000,
+	       ifp->name);
+  }
+
+  /*
+    RFC 3376: 6.2.2. Definition of Group Timers
+
+    The group timer is only used when a group is in EXCLUDE mode and
+    it represents the time for the *filter-mode* of the group to
+    expire and switch to INCLUDE mode.
+  */
+  zassert(group->group_filtermode_isexcl);
+
+  igmp_group_timer_on(group, group_membership_interval_msec, ifp->name);
+}
+
+static int igmp_source_timer(struct thread *t)
+{
+  struct igmp_source *source;
+  struct igmp_group *group;
+
+  zassert(t);
+  source = THREAD_ARG(t);
+  zassert(source);
+
+  group = source->source_group;
+
+  if (PIM_DEBUG_IGMP_TRACE) {
+    char group_str[100];
+    char source_str[100];
+    pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+    pim_inet4_dump("<source?>", source->source_addr, source_str, sizeof(source_str));
+    zlog_debug("%s: Source timer expired for group %s source %s on %s",
+	       __PRETTY_FUNCTION__,
+	       group_str, source_str,
+	       group->group_igmp_sock->interface->name);
+  }
+
+  zassert(source->t_source_timer);
+  source->t_source_timer = 0;
+
+  /*
+    RFC 3376: 6.3. IGMPv3 Source-Specific Forwarding Rules
+
+    Group
+    Filter-Mode    Source Timer Value    Action
+    -----------    ------------------    ------
+    INCLUDE        TIMER == 0            Suggest to stop forwarding
+                                         traffic from source and
+                                         remove source record.  If
+                                         there are no more source
+                                         records for the group, delete
+                                         group record.
+
+    EXCLUDE        TIMER == 0            Suggest to not forward
+                                         traffic from source
+                                         (DO NOT remove record)
+
+    Source timer switched from (T > 0) to (T == 0): disable forwarding.
+   */
+
+  zassert(!source->t_source_timer);
+
+  if (group->group_filtermode_isexcl) {
+    /* EXCLUDE mode */
+
+    igmp_source_forward_stop(source);
+  }
+  else {
+    /* INCLUDE mode */
+
+    /* igmp_source_delete() will stop forwarding source */
+    igmp_source_delete(source);
+
+    /*
+      If there are no more source records for the group, delete group
+      record.
+    */
+    if (!listcount(group->group_source_list)) {
+      igmp_group_delete_empty_include(group);
+    }
+  }
+
+  return 0;
+}
+
+static void source_timer_off(struct igmp_group *group,
+			     struct igmp_source *source)
+{
+  if (!source->t_source_timer)
+    return;
+  
+  if (PIM_DEBUG_IGMP_TRACE) {
+    char group_str[100];
+    char source_str[100];
+    pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+    pim_inet4_dump("<source?>", source->source_addr, source_str, sizeof(source_str));
+    zlog_debug("Cancelling TIMER event for group %s source %s on %s",
+	       group_str, source_str,
+	       group->group_igmp_sock->interface->name);
+  }
+
+  THREAD_OFF(source->t_source_timer);
+  zassert(!source->t_source_timer);
+}
+
+static void igmp_source_timer_on(struct igmp_group *group,
+				 struct igmp_source *source,
+				 long interval_msec)
+{
+  source_timer_off(group, source);
+
+  if (PIM_DEBUG_IGMP_EVENTS) {
+    char group_str[100];
+    char source_str[100];
+    pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+    pim_inet4_dump("<source?>", source->source_addr, source_str, sizeof(source_str));
+    zlog_debug("Scheduling %ld.%03ld sec TIMER event for group %s source %s on %s",
+	       interval_msec / 1000,
+	       interval_msec % 1000,
+	       group_str, source_str,
+	       group->group_igmp_sock->interface->name);
+  }
+
+  THREAD_TIMER_MSEC_ON(master, source->t_source_timer,
+		       igmp_source_timer,
+		       source, interval_msec);
+  zassert(source->t_source_timer);
+
+  /*
+    RFC 3376: 6.3. IGMPv3 Source-Specific Forwarding Rules
+    
+    Source timer switched from (T == 0) to (T > 0): enable forwarding.
+  */
+  igmp_source_forward_start(source);
+}
+
+void igmp_source_reset_gmi(struct igmp_sock *igmp,
+			   struct igmp_group *group,
+			   struct igmp_source *source)
+{
+  long group_membership_interval_msec;
+  struct pim_interface *pim_ifp;
+  struct interface *ifp;
+
+  ifp = igmp->interface;
+  pim_ifp = ifp->info;
+
+  group_membership_interval_msec =
+    PIM_IGMP_GMI_MSEC(igmp->querier_robustness_variable,
+		      igmp->querier_query_interval,
+		      pim_ifp->igmp_query_max_response_time_dsec);
+
+  if (PIM_DEBUG_IGMP_TRACE) {
+    char group_str[100];
+    char source_str[100];
+
+    pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+    pim_inet4_dump("<source?>", source->source_addr, source_str, sizeof(source_str));
+
+    zlog_debug("Resetting source %s timer to GMI=%ld.%03ld sec for group %s on %s",
+	       source_str,
+	       group_membership_interval_msec / 1000,
+	       group_membership_interval_msec % 1000,
+	       group_str,
+	       ifp->name);
+  }
+
+  igmp_source_timer_on(group, source,
+		       group_membership_interval_msec);
+}
+
+static void source_mark_delete_flag(struct list *source_list)
+{
+  struct listnode    *src_node;
+  struct igmp_source *src;
+
+  for (ALL_LIST_ELEMENTS_RO(source_list, src_node, src)) {
+    IGMP_SOURCE_DO_DELETE(src->source_flags);
+  }
+}
+
+static void source_mark_send_flag(struct list *source_list)
+{
+  struct listnode    *src_node;
+  struct igmp_source *src;
+
+  for (ALL_LIST_ELEMENTS_RO(source_list, src_node, src)) {
+    IGMP_SOURCE_DO_SEND(src->source_flags);
+  }
+}
+
+static int source_mark_send_flag_by_timer(struct list *source_list)
+{
+  struct listnode    *src_node;
+  struct igmp_source *src;
+  int                 num_marked_sources = 0;
+
+  for (ALL_LIST_ELEMENTS_RO(source_list, src_node, src)) {
+    /* Is source timer running? */
+    if (src->t_source_timer) {
+      IGMP_SOURCE_DO_SEND(src->source_flags);
+      ++num_marked_sources;
+    }
+    else {
+      IGMP_SOURCE_DONT_SEND(src->source_flags);
+    }
+  }
+
+  return num_marked_sources;
+}
+
+static void source_clear_send_flag(struct list *source_list)
+{
+  struct listnode    *src_node;
+  struct igmp_source *src;
+
+  for (ALL_LIST_ELEMENTS_RO(source_list, src_node, src)) {
+    IGMP_SOURCE_DONT_SEND(src->source_flags);
+  }
+}
+
+/*
+  Any source (*,G) is forwarded only if mode is EXCLUDE {empty}
+*/
+static void group_exclude_fwd_anysrc_ifempty(struct igmp_group *group)
+{
+  zassert(group->group_filtermode_isexcl);
+
+  if (listcount(group->group_source_list) < 1) {
+    igmp_anysource_forward_start(group);
+  }
+}
+
+void igmp_source_free(struct igmp_source *source)
+{
+  /* make sure there is no source timer running */
+  zassert(!source->t_source_timer);
+
+  XFREE(MTYPE_PIM_IGMP_GROUP_SOURCE, source);
+}
+
+static void source_channel_oil_detach(struct igmp_source *source)
+{
+  if (source->source_channel_oil) {
+    pim_channel_oil_del(source->source_channel_oil);
+    source->source_channel_oil = 0;
+  }
+}
+
+void igmp_source_delete(struct igmp_source *source)
+{
+  struct igmp_group *group;
+
+  group = source->source_group;
+
+  if (PIM_DEBUG_IGMP_TRACE) {
+    char group_str[100];
+    char source_str[100];
+    pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+    pim_inet4_dump("<source?>", source->source_addr, source_str, sizeof(source_str));
+    zlog_debug("Deleting IGMP source %s for group %s from socket %d interface %s",
+	       source_str, group_str,
+	       group->group_igmp_sock->fd,
+	       group->group_igmp_sock->interface->name);
+  }
+
+  source_timer_off(group, source);
+  igmp_source_forward_stop(source);
+
+  /* make sure forwarding is disabled */
+  zassert(!IGMP_SOURCE_TEST_FORWARDING(source->source_flags));
+
+  source_channel_oil_detach(source);
+
+  /*
+    notice that listnode_delete() can't be moved
+    into igmp_source_free() because the later is
+    called by list_delete_all_node()
+  */
+  listnode_delete(group->group_source_list, source);
+
+  igmp_source_free(source);
+
+  if (group->group_filtermode_isexcl) {
+    group_exclude_fwd_anysrc_ifempty(group);
+  }
+}
+
+static void source_delete_by_flag(struct list *source_list)
+{
+  struct listnode    *src_node;
+  struct listnode    *src_nextnode;
+  struct igmp_source *src;
+  
+  for (ALL_LIST_ELEMENTS(source_list, src_node, src_nextnode, src))
+    if (IGMP_SOURCE_TEST_DELETE(src->source_flags))
+      igmp_source_delete(src);
+}
+
+void igmp_source_delete_expired(struct list *source_list)
+{
+  struct listnode    *src_node;
+  struct listnode    *src_nextnode;
+  struct igmp_source *src;
+  
+  for (ALL_LIST_ELEMENTS(source_list, src_node, src_nextnode, src))
+    if (!src->t_source_timer)
+      igmp_source_delete(src);
+}
+
+struct igmp_source *igmp_find_source_by_addr(struct igmp_group *group,
+					     struct in_addr src_addr)
+{
+  struct listnode    *src_node;
+  struct igmp_source *src;
+
+  for (ALL_LIST_ELEMENTS_RO(group->group_source_list, src_node, src))
+    if (src_addr.s_addr == src->source_addr.s_addr)
+      return src;
+
+  return 0;
+}
+
+static struct igmp_source *source_new(struct igmp_group *group,
+				      struct in_addr src_addr,
+				      const char *ifname)
+{
+  struct igmp_source *src;
+
+  if (PIM_DEBUG_IGMP_TRACE) {
+    char group_str[100];
+    char source_str[100];
+    pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+    pim_inet4_dump("<source?>", src_addr, source_str, sizeof(source_str));
+    zlog_debug("Creating new IGMP source %s for group %s on socket %d interface %s",
+	       source_str, group_str,
+	       group->group_igmp_sock->fd,
+	       ifname);
+  }
+
+  src = XMALLOC(MTYPE_PIM_IGMP_GROUP_SOURCE, sizeof(*src));
+  if (!src) {
+    zlog_warn("%s %s: XMALLOC() failure",
+	      __FILE__, __PRETTY_FUNCTION__);
+    return 0; /* error, not found, could not create */
+  }
+  
+  src->t_source_timer                = 0;
+  src->source_group                  = group; /* back pointer */
+  src->source_addr                   = src_addr;
+  src->source_creation               = pim_time_monotonic_sec();
+  src->source_flags                  = 0;
+  src->source_query_retransmit_count = 0;
+  src->source_channel_oil            = 0;
+
+  listnode_add(group->group_source_list, src);
+
+  zassert(!src->t_source_timer); /* source timer == 0 */
+
+  /* Any source (*,G) is forwarded only if mode is EXCLUDE {empty} */
+  igmp_anysource_forward_stop(group);
+
+  return src;
+}
+
+static struct igmp_source *add_source_by_addr(struct igmp_sock *igmp,
+					      struct igmp_group *group,
+					      struct in_addr src_addr,
+					      const char *ifname)
+{
+  struct igmp_source *src;
+
+  src = igmp_find_source_by_addr(group, src_addr);
+  if (src) {
+    return src;
+  }
+
+  src = source_new(group, src_addr, ifname);
+  if (!src) {
+    return 0;
+  }
+
+  return src;
+}
+
+static void allow(struct igmp_sock *igmp, struct in_addr from,
+		  struct in_addr group_addr,
+		  int num_sources, struct in_addr *sources)
+{
+  struct interface *ifp = igmp->interface;
+  struct pim_interface *pim_ifp;
+  struct igmp_group *group;
+
+  pim_ifp = ifp->info;
+
+  /* non-existant group is created as INCLUDE {empty} */
+  group = igmp_add_group_by_addr(igmp, group_addr, ifp->name);
+  if (!group) {
+    return;
+  }
+
+  /* scan received sources */
+  for (int i = 0; i < num_sources; ++i) {
+    struct igmp_source *source;
+    struct in_addr     *src_addr;
+
+    src_addr = sources + i;
+
+    source = add_source_by_addr(igmp, group, *src_addr,	ifp->name);
+    if (!source) {
+      continue;
+    }
+
+    /*
+      RFC 3376: 6.4.1. Reception of Current-State Records
+
+      When receiving IS_IN reports for groups in EXCLUDE mode is
+      sources should be moved from set with (timers = 0) to set with
+      (timers > 0).
+
+      igmp_source_reset_gmi() below, resetting the source timers to
+      GMI, accomplishes this.
+    */
+    igmp_source_reset_gmi(igmp, group, source);
+
+  } /* scan received sources */
+}
+
+void igmpv3_report_isin(struct igmp_sock *igmp, struct in_addr from,
+			struct in_addr group_addr,
+			int num_sources, struct in_addr *sources)
+{
+  on_trace(__PRETTY_FUNCTION__,
+	   igmp->interface, from, group_addr, num_sources, sources);
+
+  allow(igmp, from, group_addr, num_sources, sources);
+}
+
+static void isex_excl(struct igmp_group *group,
+		      int num_sources, struct in_addr *sources)
+{
+  /* EXCLUDE mode */
+  zassert(group->group_filtermode_isexcl);
+  
+  /* E.1: set deletion flag for known sources (X,Y) */
+  source_mark_delete_flag(group->group_source_list);
+
+  /* scan received sources (A) */
+  for (int i = 0; i < num_sources; ++i) {
+    struct igmp_source *source;
+    struct in_addr     *src_addr;
+
+    src_addr = sources + i;
+
+    /* E.2: lookup reported source from (A) in (X,Y) */
+    source = igmp_find_source_by_addr(group, *src_addr);
+    if (source) {
+      /* E.3: if found, clear deletion flag: (X*A) or (Y*A) */
+      IGMP_SOURCE_DONT_DELETE(source->source_flags);
+    }
+    else {
+      /* E.4: if not found, create source with timer=GMI: (A-X-Y) */
+      source = source_new(group, *src_addr,
+			  group->group_igmp_sock->interface->name);
+      if (!source) {
+	/* ugh, internal malloc failure, skip source */
+	continue;
+      }
+      zassert(!source->t_source_timer); /* timer == 0 */
+      igmp_source_reset_gmi(group->group_igmp_sock, group, source);
+      zassert(source->t_source_timer); /* (A-X-Y) timer > 0 */
+    }
+
+  } /* scan received sources */
+
+  /* E.5: delete all sources marked with deletion flag: (X-A) and (Y-A) */
+  source_delete_by_flag(group->group_source_list);
+}
+
+static void isex_incl(struct igmp_group *group,
+		      int num_sources, struct in_addr *sources)
+{
+  /* INCLUDE mode */
+  zassert(!group->group_filtermode_isexcl);
+  
+  /* I.1: set deletion flag for known sources (A) */
+  source_mark_delete_flag(group->group_source_list);
+
+  /* scan received sources (B) */
+  for (int i = 0; i < num_sources; ++i) {
+    struct igmp_source *source;
+    struct in_addr     *src_addr;
+
+    src_addr = sources + i;
+
+    /* I.2: lookup reported source (B) */
+    source = igmp_find_source_by_addr(group, *src_addr);
+    if (source) {
+      /* I.3: if found, clear deletion flag (A*B) */
+      IGMP_SOURCE_DONT_DELETE(source->source_flags);
+    }
+    else {
+      /* I.4: if not found, create source with timer=0 (B-A) */
+      source = source_new(group, *src_addr,
+			  group->group_igmp_sock->interface->name);
+      if (!source) {
+	/* ugh, internal malloc failure, skip source */
+	continue;
+      }
+      zassert(!source->t_source_timer); /* (B-A) timer=0 */
+    }
+
+  } /* scan received sources */
+
+  /* I.5: delete all sources marked with deletion flag (A-B) */
+  source_delete_by_flag(group->group_source_list);
+
+  group->group_filtermode_isexcl = 1; /* boolean=true */
+
+  zassert(group->group_filtermode_isexcl);
+
+  group_exclude_fwd_anysrc_ifempty(group);
+}
+
+void igmpv3_report_isex(struct igmp_sock *igmp, struct in_addr from,
+			struct in_addr group_addr,
+			int num_sources, struct in_addr *sources)
+{
+  struct interface *ifp = igmp->interface;
+  struct pim_interface *pim_ifp;
+  struct igmp_group *group;
+
+  on_trace(__PRETTY_FUNCTION__,
+	   ifp, from, group_addr, num_sources, sources);
+
+  pim_ifp = ifp->info;
+
+  /* non-existant group is created as INCLUDE {empty} */
+  group = igmp_add_group_by_addr(igmp, group_addr, ifp->name);
+  if (!group) {
+    return;
+  }
+
+  if (group->group_filtermode_isexcl) {
+    /* EXCLUDE mode */
+    isex_excl(group, num_sources, sources);
+  }
+  else {
+    /* INCLUDE mode */
+    isex_incl(group, num_sources, sources);
+    zassert(group->group_filtermode_isexcl);
+  }
+
+  zassert(group->group_filtermode_isexcl);
+
+  igmp_group_reset_gmi(group);
+}
+
+static void toin_incl(struct igmp_group *group,
+		      int num_sources, struct in_addr *sources)
+{
+  struct igmp_sock *igmp = group->group_igmp_sock;
+  int num_sources_tosend = listcount(group->group_source_list);
+
+  /* Set SEND flag for all known sources (A) */
+  source_mark_send_flag(group->group_source_list);
+
+  /* Scan received sources (B) */
+  for (int i = 0; i < num_sources; ++i) {
+    struct igmp_source *source;
+    struct in_addr     *src_addr;
+
+    src_addr = sources + i;
+
+    /* Lookup reported source (B) */
+    source = igmp_find_source_by_addr(group, *src_addr);
+    if (source) {
+      /* If found, clear SEND flag (A*B) */
+      IGMP_SOURCE_DONT_SEND(source->source_flags);
+      --num_sources_tosend;
+    }
+    else {
+      /* If not found, create new source */
+      source = source_new(group, *src_addr,
+			  group->group_igmp_sock->interface->name);
+      if (!source) {
+	/* ugh, internal malloc failure, skip source */
+	continue;
+      }
+    }
+
+    /* (B)=GMI */
+    igmp_source_reset_gmi(igmp, group, source);
+  }
+
+  /* Send sources marked with SEND flag: Q(G,A-B) */
+  if (num_sources_tosend > 0) {
+    source_query_send_by_flag(group, num_sources_tosend);
+  }
+}
+
+static void toin_excl(struct igmp_group *group,
+		      int num_sources, struct in_addr *sources)
+{
+  struct igmp_sock *igmp = group->group_igmp_sock;
+  int num_sources_tosend;
+
+  /* Set SEND flag for X (sources with timer > 0) */
+  num_sources_tosend = source_mark_send_flag_by_timer(group->group_source_list);
+
+  /* Scan received sources (A) */
+  for (int i = 0; i < num_sources; ++i) {
+    struct igmp_source *source;
+    struct in_addr     *src_addr;
+
+    src_addr = sources + i;
+
+    /* Lookup reported source (A) */
+    source = igmp_find_source_by_addr(group, *src_addr);
+    if (source) {
+      if (source->t_source_timer) {
+	/* If found and timer running, clear SEND flag (X*A) */
+	IGMP_SOURCE_DONT_SEND(source->source_flags);
+	--num_sources_tosend;
+      }
+    }
+    else {
+      /* If not found, create new source */
+      source = source_new(group, *src_addr,
+			  group->group_igmp_sock->interface->name);
+      if (!source) {
+	/* ugh, internal malloc failure, skip source */
+	continue;
+      }
+    }
+
+    /* (A)=GMI */
+    igmp_source_reset_gmi(igmp, group, source);
+  }
+
+  /* Send sources marked with SEND flag: Q(G,X-A) */
+  if (num_sources_tosend > 0) {
+    source_query_send_by_flag(group, num_sources_tosend);
+  }
+
+  /* Send Q(G) */
+  group_query_send(group);
+}
+
+void igmpv3_report_toin(struct igmp_sock *igmp, struct in_addr from,
+			struct in_addr group_addr,
+			int num_sources, struct in_addr *sources)
+{
+  struct interface *ifp = igmp->interface;
+  struct pim_interface *pim_ifp;
+  struct igmp_group *group;
+
+  on_trace(__PRETTY_FUNCTION__,
+	   ifp, from, group_addr, num_sources, sources);
+
+  pim_ifp = ifp->info;
+
+  /* non-existant group is created as INCLUDE {empty} */
+  group = igmp_add_group_by_addr(igmp, group_addr, ifp->name);
+  if (!group) {
+    return;
+  }
+
+  if (group->group_filtermode_isexcl) {
+    /* EXCLUDE mode */
+    toin_excl(group, num_sources, sources);
+  }
+  else {
+    /* INCLUDE mode */
+    toin_incl(group, num_sources, sources);
+  }
+}
+
+static void toex_incl(struct igmp_group *group,
+		      int num_sources, struct in_addr *sources)
+{
+  int num_sources_tosend = 0;
+
+  zassert(!group->group_filtermode_isexcl);
+
+  /* Set DELETE flag for all known sources (A) */
+  source_mark_delete_flag(group->group_source_list);
+
+  /* Clear off SEND flag from all known sources (A) */
+  source_clear_send_flag(group->group_source_list);
+
+  /* Scan received sources (B) */
+  for (int i = 0; i < num_sources; ++i) {
+    struct igmp_source *source;
+    struct in_addr     *src_addr;
+
+    src_addr = sources + i;
+
+    /* Lookup reported source (B) */
+    source = igmp_find_source_by_addr(group, *src_addr);
+    if (source) {
+      /* If found, clear deletion flag: (A*B) */
+      IGMP_SOURCE_DONT_DELETE(source->source_flags);
+      /* and set SEND flag (A*B) */
+      IGMP_SOURCE_DO_SEND(source->source_flags);
+      ++num_sources_tosend;
+    }
+    else {
+      /* If source not found, create source with timer=0: (B-A)=0 */
+      source = source_new(group, *src_addr,
+			  group->group_igmp_sock->interface->name);
+      if (!source) {
+	/* ugh, internal malloc failure, skip source */
+	continue;
+      }
+      zassert(!source->t_source_timer); /* (B-A) timer=0 */
+    }
+
+  } /* Scan received sources (B) */
+
+  group->group_filtermode_isexcl = 1; /* boolean=true */
+
+  /* Delete all sources marked with DELETE flag (A-B) */
+  source_delete_by_flag(group->group_source_list);
+
+  /* Send sources marked with SEND flag: Q(G,A*B) */
+  if (num_sources_tosend > 0) {
+    source_query_send_by_flag(group, num_sources_tosend);
+  }
+
+  zassert(group->group_filtermode_isexcl);
+
+  group_exclude_fwd_anysrc_ifempty(group);
+}
+
+static void toex_excl(struct igmp_group *group,
+		      int num_sources, struct in_addr *sources)
+{
+  int num_sources_tosend = 0;
+
+  /* set DELETE flag for all known sources (X,Y) */
+  source_mark_delete_flag(group->group_source_list);
+
+  /* clear off SEND flag from all known sources (X,Y) */
+  source_clear_send_flag(group->group_source_list);
+
+  /* scan received sources (A) */
+  for (int i = 0; i < num_sources; ++i) {
+    struct igmp_source *source;
+    struct in_addr     *src_addr;
+    
+    src_addr = sources + i;
+    
+    /* lookup reported source (A) in known sources (X,Y) */
+    source = igmp_find_source_by_addr(group, *src_addr);
+    if (source) {
+      /* if found, clear off DELETE flag from reported source (A) */
+      IGMP_SOURCE_DONT_DELETE(source->source_flags);
+    }
+    else {
+      /* if not found, create source with Group Timer: (A-X-Y)=Group Timer */
+      long group_timer_msec;
+      source = source_new(group, *src_addr,
+			  group->group_igmp_sock->interface->name);
+      if (!source) {
+	/* ugh, internal malloc failure, skip source */
+	continue;
+      }
+
+      zassert(!source->t_source_timer); /* timer == 0 */
+      group_timer_msec = igmp_group_timer_remain_msec(group);
+      igmp_source_timer_on(group, source, group_timer_msec);
+      zassert(source->t_source_timer); /* (A-X-Y) timer > 0 */
+
+      /* make sure source is created with DELETE flag unset */
+      zassert(!IGMP_SOURCE_TEST_DELETE(source->source_flags));
+    }
+
+    /* make sure reported source has DELETE flag unset */
+    zassert(!IGMP_SOURCE_TEST_DELETE(source->source_flags));
+
+    if (source->t_source_timer) {
+      /* if source timer>0 mark SEND flag: Q(G,A-Y) */
+      IGMP_SOURCE_DO_SEND(source->source_flags);
+      ++num_sources_tosend;
+    }
+
+  } /* scan received sources (A) */
+
+  /*
+    delete all sources marked with DELETE flag:
+    Delete (X-A)
+    Delete (Y-A)
+  */
+  source_delete_by_flag(group->group_source_list);
+ 
+  /* send sources marked with SEND flag: Q(G,A-Y) */
+  if (num_sources_tosend > 0) {
+    source_query_send_by_flag(group, num_sources_tosend);
+  }
+}
+
+void igmpv3_report_toex(struct igmp_sock *igmp, struct in_addr from,
+			struct in_addr group_addr,
+			int num_sources, struct in_addr *sources)
+{
+  struct interface *ifp = igmp->interface;
+  struct pim_interface *pim_ifp;
+  struct igmp_group *group;
+
+  on_trace(__PRETTY_FUNCTION__,
+	   ifp, from, group_addr, num_sources, sources);
+
+  pim_ifp = ifp->info;
+
+  /* non-existant group is created as INCLUDE {empty} */
+  group = igmp_add_group_by_addr(igmp, group_addr, ifp->name);
+  if (!group) {
+    return;
+  }
+
+  if (group->group_filtermode_isexcl) {
+    /* EXCLUDE mode */
+    toex_excl(group, num_sources, sources);
+  }
+  else {
+    /* INCLUDE mode */
+    toex_incl(group, num_sources, sources);
+    zassert(group->group_filtermode_isexcl);
+  }
+  zassert(group->group_filtermode_isexcl);
+
+  /* Group Timer=GMI */
+  igmp_group_reset_gmi(group);
+}
+
+void igmpv3_report_allow(struct igmp_sock *igmp, struct in_addr from,
+			 struct in_addr group_addr,
+			 int num_sources, struct in_addr *sources)
+{
+  on_trace(__PRETTY_FUNCTION__,
+	   igmp->interface, from, group_addr, num_sources, sources);
+
+  allow(igmp, from, group_addr, num_sources, sources);
+}
+
+/*
+  RFC3376: 6.6.3.1. Building and Sending Group Specific Queries
+
+  When transmitting a group specific query, if the group timer is
+  larger than LMQT, the "Suppress Router-Side Processing" bit is set
+  in the query message.
+*/
+static void group_retransmit_group(struct igmp_group *group)
+{
+  char                  query_buf[PIM_IGMP_BUFSIZE_WRITE];
+  struct igmp_sock     *igmp;
+  struct pim_interface *pim_ifp;
+  long                  lmqc;      /* Last Member Query Count */
+  long                  lmqi_msec; /* Last Member Query Interval */
+  long                  lmqt_msec; /* Last Member Query Time */
+  int                   s_flag;
+
+  igmp = group->group_igmp_sock;
+  pim_ifp = igmp->interface->info;
+
+  lmqc      = igmp->querier_robustness_variable;
+  lmqi_msec = 100 * pim_ifp->igmp_query_max_response_time_dsec;
+  lmqt_msec = lmqc * lmqi_msec;
+
+  /*
+    RFC3376: 6.6.3.1. Building and Sending Group Specific Queries
+    
+    When transmitting a group specific query, if the group timer is
+    larger than LMQT, the "Suppress Router-Side Processing" bit is set
+    in the query message.
+  */
+  s_flag = igmp_group_timer_remain_msec(group) > lmqt_msec;
+
+  if (PIM_DEBUG_IGMP_TRACE) {
+    char group_str[100];
+    pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+    zlog_debug("retransmit_group_specific_query: group %s on %s: s_flag=%d count=%d",
+	       group_str, igmp->interface->name, s_flag,
+	       group->group_specific_query_retransmit_count);
+  }
+
+  /*
+    RFC3376: 4.1.12. IP Destination Addresses for Queries
+
+    Group-Specific and Group-and-Source-Specific Queries are sent with
+    an IP destination address equal to the multicast address of
+    interest.
+  */
+
+  pim_igmp_send_membership_query(group,
+				 igmp->fd,
+				 igmp->interface->name,
+				 query_buf,
+				 sizeof(query_buf),
+				 0 /* num_sources_tosend */,
+				 group->group_addr /* dst_addr */,
+				 group->group_addr /* group_addr */,
+				 pim_ifp->igmp_query_max_response_time_dsec,
+				 s_flag,
+				 igmp->querier_robustness_variable,
+				 igmp->querier_query_interval);
+}
+
+/*
+  RFC3376: 6.6.3.2. Building and Sending Group and Source Specific Queries
+
+  When building a group and source specific query for a group G, two
+  separate query messages are sent for the group.  The first one has
+  the "Suppress Router-Side Processing" bit set and contains all the
+  sources with retransmission state and timers greater than LMQT.  The
+  second has the "Suppress Router-Side Processing" bit clear and
+  contains all the sources with retransmission state and timers lower
+  or equal to LMQT.  If either of the two calculated messages does not
+  contain any sources, then its transmission is suppressed.
+ */
+static int group_retransmit_sources(struct igmp_group *group,
+				    int send_with_sflag_set)
+{
+  struct igmp_sock     *igmp;
+  struct pim_interface *pim_ifp;
+  long                  lmqc;      /* Last Member Query Count */
+  long                  lmqi_msec; /* Last Member Query Interval */
+  long                  lmqt_msec; /* Last Member Query Time */
+  char                  query_buf1[PIM_IGMP_BUFSIZE_WRITE]; /* 1 = with s_flag set */
+  char                  query_buf2[PIM_IGMP_BUFSIZE_WRITE]; /* 2 = with s_flag clear */
+  int                   query_buf1_max_sources;
+  int                   query_buf2_max_sources;
+  struct in_addr       *source_addr1;
+  struct in_addr       *source_addr2;
+  int                   num_sources_tosend1;
+  int                   num_sources_tosend2;
+  struct listnode      *src_node;
+  struct igmp_source   *src;
+  int                   num_retransmit_sources_left = 0;
+  
+  query_buf1_max_sources = (sizeof(query_buf1) - IGMP_V3_SOURCES_OFFSET) >> 2;
+  query_buf2_max_sources = (sizeof(query_buf2) - IGMP_V3_SOURCES_OFFSET) >> 2;
+  
+  source_addr1 = (struct in_addr *)(query_buf1 + IGMP_V3_SOURCES_OFFSET);
+  source_addr2 = (struct in_addr *)(query_buf2 + IGMP_V3_SOURCES_OFFSET);
+
+  igmp = group->group_igmp_sock;
+  pim_ifp = igmp->interface->info;
+
+  lmqc      = igmp->querier_robustness_variable;
+  lmqi_msec = 100 * pim_ifp->igmp_query_max_response_time_dsec;
+  lmqt_msec = lmqc * lmqi_msec;
+
+  /* Scan all group sources */
+  for (ALL_LIST_ELEMENTS_RO(group->group_source_list, src_node, src)) {
+
+    /* Source has retransmission state? */
+    if (src->source_query_retransmit_count < 1)
+      continue;
+
+    if (--src->source_query_retransmit_count > 0) {
+      ++num_retransmit_sources_left;
+    }
+
+    /* Copy source address into appropriate query buffer */
+    if (igmp_source_timer_remain_msec(src) > lmqt_msec) {
+      *source_addr1 = src->source_addr;
+      ++source_addr1;
+    }
+    else {
+      *source_addr2 = src->source_addr;
+      ++source_addr2;
+    }
+
+  }
+  
+  num_sources_tosend1 = source_addr1 - (struct in_addr *)(query_buf1 + IGMP_V3_SOURCES_OFFSET);
+  num_sources_tosend2 = source_addr2 - (struct in_addr *)(query_buf2 + IGMP_V3_SOURCES_OFFSET);
+
+  if (PIM_DEBUG_IGMP_TRACE) {
+    char group_str[100];
+    pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+    zlog_debug("retransmit_grp&src_specific_query: group %s on %s: srcs_with_sflag=%d srcs_wo_sflag=%d will_send_sflag=%d retransmit_src_left=%d",
+	       group_str, igmp->interface->name,
+	       num_sources_tosend1,
+	       num_sources_tosend2,
+	       send_with_sflag_set,
+	       num_retransmit_sources_left);
+  }
+
+  if (num_sources_tosend1 > 0) {
+    /*
+      Send group-and-source-specific query with s_flag set and all
+      sources with timers greater than LMQT.
+    */
+
+    if (send_with_sflag_set) {
+
+      query_buf1_max_sources = (sizeof(query_buf1) - IGMP_V3_SOURCES_OFFSET) >> 2;
+      if (num_sources_tosend1 > query_buf1_max_sources) {
+	char group_str[100];
+	pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+	zlog_warn("%s: group %s on %s: s_flag=1 unable to fit %d sources into buf_size=%d (max_sources=%d)",
+		  __PRETTY_FUNCTION__, group_str, igmp->interface->name,
+		  num_sources_tosend1, sizeof(query_buf1), query_buf1_max_sources);
+      }
+      else {
+	/*
+	  RFC3376: 4.1.12. IP Destination Addresses for Queries
+      
+	  Group-Specific and Group-and-Source-Specific Queries are sent with
+	  an IP destination address equal to the multicast address of
+	  interest.
+	*/
+    
+	pim_igmp_send_membership_query(group,
+				       igmp->fd,
+				       igmp->interface->name,
+				       query_buf1,
+				       sizeof(query_buf1),
+				       num_sources_tosend1,
+				       group->group_addr,
+				       group->group_addr,
+				       pim_ifp->igmp_query_max_response_time_dsec,
+				       1 /* s_flag */,
+				       igmp->querier_robustness_variable,
+				       igmp->querier_query_interval);
+    
+      }
+
+    } /* send_with_sflag_set */
+
+  }
+
+  if (num_sources_tosend2 > 0) {
+    /*
+      Send group-and-source-specific query with s_flag clear and all
+      sources with timers lower or equal to LMQT.
+    */
+  
+    query_buf2_max_sources = (sizeof(query_buf2) - IGMP_V3_SOURCES_OFFSET) >> 2;
+    if (num_sources_tosend2 > query_buf2_max_sources) {
+      char group_str[100];
+      pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+      zlog_warn("%s: group %s on %s: s_flag=0 unable to fit %d sources into buf_size=%d (max_sources=%d)",
+		__PRETTY_FUNCTION__, group_str, igmp->interface->name,
+		num_sources_tosend2, sizeof(query_buf2), query_buf2_max_sources);
+    }
+    else {
+      /*
+	RFC3376: 4.1.12. IP Destination Addresses for Queries
+
+	Group-Specific and Group-and-Source-Specific Queries are sent with
+	an IP destination address equal to the multicast address of
+	interest.
+      */
+
+      pim_igmp_send_membership_query(group,
+				     igmp->fd,
+				     igmp->interface->name,
+				     query_buf2,
+				     sizeof(query_buf2),
+				     num_sources_tosend2,
+				     group->group_addr,
+				     group->group_addr,
+				     pim_ifp->igmp_query_max_response_time_dsec,
+				     0 /* s_flag */,
+				     igmp->querier_robustness_variable,
+				     igmp->querier_query_interval);
+
+    }
+  }
+
+  return num_retransmit_sources_left;
+}
+
+static int igmp_group_retransmit(struct thread *t)
+{
+  struct igmp_group *group;
+  int num_retransmit_sources_left;
+  int send_with_sflag_set; /* boolean */
+
+  zassert(t);
+  group = THREAD_ARG(t);
+  zassert(group);
+
+  if (PIM_DEBUG_IGMP_TRACE) {
+    char group_str[100];
+    pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+    zlog_debug("group_retransmit_timer: group %s on %s",
+	       group_str, group->group_igmp_sock->interface->name);
+  }
+
+  /* Retransmit group-specific queries? (RFC3376: 6.6.3.1) */
+  if (group->group_specific_query_retransmit_count > 0) {
+
+    /* Retransmit group-specific queries (RFC3376: 6.6.3.1) */
+    group_retransmit_group(group);
+    --group->group_specific_query_retransmit_count;
+
+    /*
+      RFC3376: 6.6.3.2
+      If a group specific query is scheduled to be transmitted at the
+      same time as a group and source specific query for the same group,
+      then transmission of the group and source specific message with the
+      "Suppress Router-Side Processing" bit set may be suppressed.
+    */
+    send_with_sflag_set = 0; /* boolean=false */
+  }
+  else {
+    send_with_sflag_set = 1; /* boolean=true */
+  }
+
+  /* Retransmit group-and-source-specific queries (RFC3376: 6.6.3.2) */
+  num_retransmit_sources_left = group_retransmit_sources(group,
+							 send_with_sflag_set);
+
+  group->t_group_query_retransmit_timer = 0;
+
+  /*
+    Keep group retransmit timer running if there is any retransmit
+    counter pending
+  */
+  if ((num_retransmit_sources_left > 0) ||
+      (group->group_specific_query_retransmit_count > 0)) {
+    group_retransmit_timer_on(group);
+  }
+
+  return 0;
+}
+
+/*
+  group_retransmit_timer_on:
+  if group retransmit timer isn't running, starts it;
+  otherwise, do nothing
+*/
+static void group_retransmit_timer_on(struct igmp_group *group)
+{
+  struct igmp_sock     *igmp;
+  struct pim_interface *pim_ifp;
+  long                  lmqi_msec; /* Last Member Query Interval */
+
+  /* if group retransmit timer is running, do nothing */
+  if (group->t_group_query_retransmit_timer) {
+    return;
+  }
+
+  igmp = group->group_igmp_sock;
+  pim_ifp = igmp->interface->info;
+
+  lmqi_msec = 100 * pim_ifp->igmp_query_max_response_time_dsec;
+
+  if (PIM_DEBUG_IGMP_TRACE) {
+    char group_str[100];
+    pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+    zlog_debug("Scheduling %ld.%03ld sec retransmit timer for group %s on %s",
+	       lmqi_msec / 1000,
+	       lmqi_msec % 1000,
+	       group_str,
+	       igmp->interface->name);
+  }
+
+  THREAD_TIMER_MSEC_ON(master, group->t_group_query_retransmit_timer,
+		       igmp_group_retransmit,
+		       group, lmqi_msec);
+}
+
+static long igmp_group_timer_remain_msec(struct igmp_group *group)
+{
+  return pim_time_timer_remain_msec(group->t_group_timer);
+}
+
+static long igmp_source_timer_remain_msec(struct igmp_source *source)
+{
+  return pim_time_timer_remain_msec(source->t_source_timer);
+}
+
+/*
+  RFC3376: 6.6.3.1. Building and Sending Group Specific Queries
+*/
+static void group_query_send(struct igmp_group *group)
+{
+  long              lmqc;    /* Last Member Query Count */
+
+  lmqc = group->group_igmp_sock->querier_robustness_variable;
+
+  /* lower group timer to lmqt */
+  igmp_group_timer_lower_to_lmqt(group);
+
+  /* reset retransmission counter */
+  group->group_specific_query_retransmit_count = lmqc;
+
+  /* immediately send group specific query (decrease retransmit counter by 1)*/
+  group_retransmit_group(group);
+
+  /* make sure group retransmit timer is running */
+  group_retransmit_timer_on(group);
+}
+
+/*
+  RFC3376: 6.6.3.2. Building and Sending Group and Source Specific Queries
+*/
+static void source_query_send_by_flag(struct igmp_group *group,
+				      int num_sources_tosend)
+{
+  struct igmp_sock     *igmp;
+  struct pim_interface *pim_ifp;
+  struct listnode      *src_node;
+  struct igmp_source   *src;
+  long                  lmqc;      /* Last Member Query Count */
+  long                  lmqi_msec; /* Last Member Query Interval */
+  long                  lmqt_msec; /* Last Member Query Time */
+
+  zassert(num_sources_tosend > 0);
+
+  igmp = group->group_igmp_sock;
+  pim_ifp = igmp->interface->info;
+
+  lmqc      = igmp->querier_robustness_variable;
+  lmqi_msec = 100 * pim_ifp->igmp_query_max_response_time_dsec;
+  lmqt_msec = lmqc * lmqi_msec;
+
+  /*
+    RFC3376: 6.6.3.2. Building and Sending Group and Source Specific Queries
+
+    (...) for each of the sources in X of group G, with source timer larger
+    than LMQT:
+    o Set number of retransmissions for each source to [Last Member
+    Query Count].
+    o Lower source timer to LMQT.
+  */
+  for (ALL_LIST_ELEMENTS_RO(group->group_source_list, src_node, src)) {
+    if (IGMP_SOURCE_TEST_SEND(src->source_flags)) {
+      /* source "src" in X of group G */
+      if (igmp_source_timer_remain_msec(src) > lmqt_msec) {
+	src->source_query_retransmit_count = lmqc;
+	igmp_source_timer_lower_to_lmqt(src);
+      }
+    }
+  }
+
+  /* send group-and-source specific queries */
+  group_retransmit_sources(group, 1 /* send_with_sflag_set=true */);
+
+  /* make sure group retransmit timer is running */
+  group_retransmit_timer_on(group);
+}
+
+static void block_excl(struct igmp_group *group,
+		       int num_sources, struct in_addr *sources)
+{
+  int num_sources_tosend = 0;
+
+  /* 1. clear off SEND flag from all known sources (X,Y) */
+  source_clear_send_flag(group->group_source_list);
+
+  /* 2. scan received sources (A) */
+  for (int i = 0; i < num_sources; ++i) {
+    struct igmp_source *source;
+    struct in_addr     *src_addr;
+    
+    src_addr = sources + i;
+    
+    /* lookup reported source (A) in known sources (X,Y) */
+    source = igmp_find_source_by_addr(group, *src_addr);
+    if (!source) {
+      /* 3: if not found, create source with Group Timer: (A-X-Y)=Group Timer */
+      long group_timer_msec;
+      source = source_new(group, *src_addr,
+			  group->group_igmp_sock->interface->name);
+      if (!source) {
+	/* ugh, internal malloc failure, skip source */
+	continue;
+      }
+
+      zassert(!source->t_source_timer); /* timer == 0 */
+      group_timer_msec = igmp_group_timer_remain_msec(group);
+      igmp_source_timer_on(group, source, group_timer_msec);
+      zassert(source->t_source_timer); /* (A-X-Y) timer > 0 */
+    }
+
+    if (source->t_source_timer) {
+      /* 4. if source timer>0 mark SEND flag: Q(G,A-Y) */
+      IGMP_SOURCE_DO_SEND(source->source_flags);
+      ++num_sources_tosend;
+    }
+  }
+ 
+  /* 5. send sources marked with SEND flag: Q(G,A-Y) */
+  if (num_sources_tosend > 0) {
+    source_query_send_by_flag(group, num_sources_tosend);
+  }
+}
+
+static void block_incl(struct igmp_group *group,
+		       int num_sources, struct in_addr *sources)
+{
+  int num_sources_tosend = 0;
+
+  /* 1. clear off SEND flag from all known sources (B) */
+  source_clear_send_flag(group->group_source_list);
+
+  /* 2. scan received sources (A) */
+  for (int i = 0; i < num_sources; ++i) {
+    struct igmp_source *source;
+    struct in_addr     *src_addr;
+    
+    src_addr = sources + i;
+    
+    /* lookup reported source (A) in known sources (B) */
+    source = igmp_find_source_by_addr(group, *src_addr);
+    if (source) {
+      /* 3. if found (A*B), mark SEND flag: Q(G,A*B) */
+      IGMP_SOURCE_DO_SEND(source->source_flags);
+      ++num_sources_tosend;
+    }
+  } 
+ 
+  /* 4. send sources marked with SEND flag: Q(G,A*B) */
+  if (num_sources_tosend > 0) {
+    source_query_send_by_flag(group, num_sources_tosend);
+  }
+}
+
+void igmpv3_report_block(struct igmp_sock *igmp, struct in_addr from,
+			 struct in_addr group_addr,
+			 int num_sources, struct in_addr *sources)
+{
+  struct interface *ifp = igmp->interface;
+  struct pim_interface *pim_ifp;
+  struct igmp_group *group;
+
+  on_trace(__PRETTY_FUNCTION__,
+	   ifp, from, group_addr, num_sources, sources);
+
+  pim_ifp = ifp->info;
+
+  /* non-existant group is created as INCLUDE {empty} */
+  group = igmp_add_group_by_addr(igmp, group_addr, ifp->name);
+  if (!group) {
+    return;
+  }
+
+  if (group->group_filtermode_isexcl) {
+    /* EXCLUDE mode */
+    block_excl(group, num_sources, sources);
+  }
+  else {
+    /* INCLUDE mode */
+    block_incl(group, num_sources, sources);
+  }
+}
+
+void igmp_group_timer_lower_to_lmqt(struct igmp_group *group)
+{
+  struct igmp_sock     *igmp;
+  struct interface     *ifp;
+  struct pim_interface *pim_ifp;
+  char                 *ifname;
+  int   lmqi_dsec; /* Last Member Query Interval */
+  int   lmqc;      /* Last Member Query Count */
+  int   lmqt_msec; /* Last Member Query Time */
+
+  /*
+    RFC 3376: 6.2.2. Definition of Group Timers
+    
+    The group timer is only used when a group is in EXCLUDE mode and
+    it represents the time for the *filter-mode* of the group to
+    expire and switch to INCLUDE mode.
+  */
+  if (!group->group_filtermode_isexcl) {
+    return;
+  }
+
+  igmp    = group->group_igmp_sock;
+  ifp     = igmp->interface;
+  pim_ifp = ifp->info;
+  ifname  = ifp->name;
+
+  lmqi_dsec = pim_ifp->igmp_query_max_response_time_dsec;
+  lmqc      = igmp->querier_robustness_variable;
+  lmqt_msec = PIM_IGMP_LMQT_MSEC(lmqi_dsec, lmqc); /* lmqt_msec = (100 * lmqi_dsec) * lmqc */
+
+  if (PIM_DEBUG_IGMP_TRACE) {
+    char group_str[100];
+    pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+    zlog_debug("%s: group %s on %s: LMQC=%d LMQI=%d dsec LMQT=%d msec",
+	       __PRETTY_FUNCTION__,
+	       group_str, ifname,
+	       lmqc, lmqi_dsec, lmqt_msec);
+  }
+
+  zassert(group->group_filtermode_isexcl);
+
+  igmp_group_timer_on(group, lmqt_msec, ifname);
+}
+
+void igmp_source_timer_lower_to_lmqt(struct igmp_source *source)
+{
+  struct igmp_group    *group;
+  struct igmp_sock     *igmp;
+  struct interface     *ifp;
+  struct pim_interface *pim_ifp;
+  char                 *ifname;
+  int   lmqi_dsec; /* Last Member Query Interval */
+  int   lmqc;      /* Last Member Query Count */
+  int   lmqt_msec; /* Last Member Query Time */
+
+  group   = source->source_group;
+  igmp    = group->group_igmp_sock;
+  ifp     = igmp->interface;
+  pim_ifp = ifp->info;
+  ifname  = ifp->name;
+
+  lmqi_dsec = pim_ifp->igmp_query_max_response_time_dsec;
+  lmqc      = igmp->querier_robustness_variable;
+  lmqt_msec = PIM_IGMP_LMQT_MSEC(lmqi_dsec, lmqc); /* lmqt_msec = (100 * lmqi_dsec) * lmqc */
+
+  if (PIM_DEBUG_IGMP_TRACE) {
+    char group_str[100];
+    char source_str[100];
+    pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+    pim_inet4_dump("<source?>", source->source_addr, source_str, sizeof(source_str));
+    zlog_debug("%s: group %s source %s on %s: LMQC=%d LMQI=%d dsec LMQT=%d msec",
+	       __PRETTY_FUNCTION__,
+	       group_str, source_str, ifname,
+	       lmqc, lmqi_dsec, lmqt_msec);
+  }
+
+  igmp_source_timer_on(group, source, lmqt_msec);
+}
+
+/*
+  Copy sources to message:
+    
+  struct in_addr *sources = (struct in_addr *)(query_buf + IGMP_V3_SOURCES_OFFSET);
+  if (num_sources > 0) {
+  struct listnode    *node;
+  struct igmp_source *src;
+  int                 i = 0;
+
+  for (ALL_LIST_ELEMENTS_RO(source_list, node, src)) {
+  sources[i++] = src->source_addr;
+  }
+  }
+*/
+void pim_igmp_send_membership_query(struct igmp_group *group,
+				    int fd,
+				    const char *ifname,
+				    char *query_buf,
+				    int query_buf_size,
+				    int num_sources,
+				    struct in_addr dst_addr,
+				    struct in_addr group_addr,
+				    int query_max_response_time_dsec,
+				    uint8_t s_flag,
+				    uint8_t querier_robustness_variable,
+				    uint16_t querier_query_interval)
+{
+  ssize_t             msg_size;
+  uint8_t             max_resp_code;
+  uint8_t             qqic;
+  ssize_t             sent;
+  struct sockaddr_in  to;
+  socklen_t           tolen;
+  uint16_t            checksum;
+
+  zassert(num_sources >= 0);
+
+  msg_size = IGMP_V3_SOURCES_OFFSET + (num_sources << 2);
+  if (msg_size > query_buf_size) {
+    zlog_err("%s %s: unable to send: msg_size=%d larger than query_buf_size=%d",
+	     __FILE__, __PRETTY_FUNCTION__,
+	     msg_size, query_buf_size);
+    return;
+  }
+
+  s_flag = PIM_FORCE_BOOLEAN(s_flag);
+  zassert((s_flag == 0) || (s_flag == 1));
+
+  max_resp_code = igmp_msg_encode16to8(query_max_response_time_dsec);
+  qqic          = igmp_msg_encode16to8(querier_query_interval);
+
+  /*
+    RFC 3376: 4.1.6. QRV (Querier's Robustness Variable)
+
+    If non-zero, the QRV field contains the [Robustness Variable]
+    value used by the querier, i.e., the sender of the Query.  If the
+    querier's [Robustness Variable] exceeds 7, the maximum value of
+    the QRV field, the QRV is set to zero.
+  */
+  if (querier_robustness_variable > 7) {
+    querier_robustness_variable = 0;
+  }
+
+  query_buf[0]                                         = PIM_IGMP_MEMBERSHIP_QUERY;
+  query_buf[1]                                         = max_resp_code;
+  *(uint16_t *)(query_buf + IGMP_V3_CHECKSUM_OFFSET)   = 0; /* for computing checksum */
+  *(struct in_addr *)(query_buf + 4)                   = group_addr;
+  query_buf[8]                                         = (s_flag << 3) | querier_robustness_variable;
+  query_buf[9]                                         = qqic;
+  *(uint16_t *)(query_buf + IGMP_V3_NUMSOURCES_OFFSET) = htons(num_sources);
+
+  checksum = pim_inet_checksum(query_buf, msg_size);
+  *(uint16_t *)(query_buf + IGMP_V3_CHECKSUM_OFFSET) = checksum;
+
+  if (PIM_DEBUG_IGMP_PACKETS) {
+    char dst_str[100];
+    char group_str[100];
+    pim_inet4_dump("<dst?>", dst_addr, dst_str, sizeof(dst_str));
+    pim_inet4_dump("<group?>", group_addr, group_str, sizeof(group_str));
+    zlog_debug("%s: to %s on %s: group=%s sources=%d msg_size=%d s_flag=%x QRV=%u QQI=%u QQIC=%02x checksum=%x",
+	       __PRETTY_FUNCTION__,
+	       dst_str, ifname, group_str, num_sources,
+	       msg_size, s_flag, querier_robustness_variable,
+	       querier_query_interval, qqic, checksum);
+  }
+
+#if 0
+  memset(&to, 0, sizeof(to));
+#endif
+  to.sin_family = AF_INET;
+  to.sin_addr = dst_addr;
+#if 0
+  to.sin_port = htons(0);
+#endif
+  tolen = sizeof(to);
+
+  sent = sendto(fd, query_buf, msg_size, MSG_DONTWAIT, &to, tolen);
+  if (sent != (ssize_t) msg_size) {
+    int e = errno;
+    char dst_str[100];
+    char group_str[100];
+    pim_inet4_dump("<dst?>", dst_addr, dst_str, sizeof(dst_str));
+    pim_inet4_dump("<group?>", group_addr, group_str, sizeof(group_str));
+    if (sent < 0) {
+      zlog_warn("%s: sendto() failure to %s on %s: group=%s msg_size=%d: errno=%d: %s",
+		__PRETTY_FUNCTION__,
+		dst_str, ifname, group_str, msg_size,
+		e, strerror(e));
+    }
+    else {
+      zlog_warn("%s: sendto() partial to %s on %s: group=%s msg_size=%d: sent=%d",
+		__PRETTY_FUNCTION__,
+		dst_str, ifname, group_str,
+		msg_size, sent);
+    }
+    return;
+  }
+
+  /*
+    s_flag sanity test: s_flag must be set for general queries
+
+    RFC 3376: 6.6.1. Timer Updates
+
+    When a router sends or receives a query with a clear Suppress
+    Router-Side Processing flag, it must update its timers to reflect
+    the correct timeout values for the group or sources being queried.
+
+    General queries don't trigger timer update.
+  */
+  if (!s_flag) {
+    /* general query? */
+    if (PIM_INADDR_IS_ANY(group_addr)) {
+      char dst_str[100];
+      char group_str[100];
+      pim_inet4_dump("<dst?>", dst_addr, dst_str, sizeof(dst_str));
+      pim_inet4_dump("<group?>", group_addr, group_str, sizeof(group_str));
+      zlog_warn("%s: to %s on %s: group=%s sources=%d: s_flag is clear for general query!",
+		__PRETTY_FUNCTION__,
+		dst_str, ifname, group_str, num_sources);
+    }
+  }
+
+}