| /* |
| 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; |
| } |
| } |
| |
| /* |
| 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->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); |
| |
| /* 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->group_addr, group_str, sizeof(group_str)); |
| pim_inet4_dump("<source?>", 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->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 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->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->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=%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->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->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->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->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->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=%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?>", 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=%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, &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=%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?>", 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); |
| } |
| } |
| |
| } |