/* 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 #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_str, sizeof(from_str)); pim_inet4_dump("", 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_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_addr, group_str, sizeof(group_str)); pim_inet4_dump("", 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_addr, group_str, sizeof(group_str)); pim_inet4_dump("", 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_addr, group_str, sizeof(group_str)); pim_inet4_dump("", 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_addr, group_str, sizeof(group_str)); pim_inet4_dump("", 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; } } /* igmp_source_delete: stop fowarding, and delete the source igmp_source_forward_stop: stop fowarding, but keep the source */ 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_addr, group_str, sizeof(group_str)); pim_inet4_dump("", 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); /* sanity check that forwarding has been disabled */ if (IGMP_SOURCE_TEST_FORWARDING(source->source_flags)) { char group_str[100]; char source_str[100]; pim_inet4_dump("", group->group_addr, group_str, sizeof(group_str)); pim_inet4_dump("", source->source_addr, source_str, sizeof(source_str)); zlog_warn("%s: forwarding=ON(!) IGMP source %s for group %s from socket %d interface %s", __PRETTY_FUNCTION__, source_str, group_str, group->group_igmp_sock->fd, group->group_igmp_sock->interface->name); /* warning only */ } 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_addr, group_str, sizeof(group_str)); pim_inet4_dump("", 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 igmp_group *group; int i; /* 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 (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) { int i; /* 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 (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) { int i; /* 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 (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 igmp_group *group; on_trace(__PRETTY_FUNCTION__, ifp, from, group_addr, num_sources, sources); /* 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); int i; /* Set SEND flag for all known sources (A) */ source_mark_send_flag(group->group_source_list); /* Scan received sources (B) */ for (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; int i; /* 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 (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 igmp_group *group; on_trace(__PRETTY_FUNCTION__, ifp, from, group_addr, num_sources, sources); /* 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; int i; 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 (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; int i; /* 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 (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 igmp_group *group; on_trace(__PRETTY_FUNCTION__, ifp, from, group_addr, num_sources, sources); /* 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_specific_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_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_specific_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_specific_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_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_addr, group_str, sizeof(group_str)); zlog_warn("%s: group %s on %s: s_flag=1 unable to fit %d sources into buf_size=%zu (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_specific_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_addr, group_str, sizeof(group_str)); zlog_warn("%s: group %s on %s: s_flag=0 unable to fit %d sources into buf_size=%zu (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_specific_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_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_specific_query_max_response_time_dsec; if (PIM_DEBUG_IGMP_TRACE) { char group_str[100]; pim_inet4_dump("", 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_specific_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; int i; /* 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 (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; int i; /* 1. clear off SEND flag from all known sources (B) */ source_clear_send_flag(group->group_source_list); /* 2. scan received sources (A) */ for (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 igmp_group *group; on_trace(__PRETTY_FUNCTION__, ifp, from, group_addr, num_sources, sources); /* 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_specific_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_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_specific_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_addr, group_str, sizeof(group_str)); pim_inet4_dump("", 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=%zd 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 */ memcpy(query_buf+4, &group_addr, sizeof(struct in_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 = in_cksum(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_addr, dst_str, sizeof(dst_str)); pim_inet4_dump("", group_addr, group_str, sizeof(group_str)); zlog_debug("%s: to %s on %s: group=%s sources=%d msg_size=%zd 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, (struct sockaddr *)&to, tolen); if (sent != (ssize_t) msg_size) { int e = errno; char dst_str[100]; char group_str[100]; pim_inet4_dump("", dst_addr, dst_str, sizeof(dst_str)); pim_inet4_dump("", group_addr, group_str, sizeof(group_str)); if (sent < 0) { zlog_warn("%s: sendto() failure to %s on %s: group=%s msg_size=%zd: errno=%d: %s", __PRETTY_FUNCTION__, dst_str, ifname, group_str, msg_size, e, safe_strerror(e)); } else { zlog_warn("%s: sendto() partial to %s on %s: group=%s msg_size=%zd: sent=%zd", __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_addr, dst_str, sizeof(dst_str)); pim_inet4_dump("", 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); } } }