[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);
+ }
+ }
+
+}