diff options
Diffstat (limited to 'src/plugins/igmp/igmp_query.c')
-rw-r--r-- | src/plugins/igmp/igmp_query.c | 307 |
1 files changed, 307 insertions, 0 deletions
diff --git a/src/plugins/igmp/igmp_query.c b/src/plugins/igmp/igmp_query.c new file mode 100644 index 00000000000..1513023df2e --- /dev/null +++ b/src/plugins/igmp/igmp_query.c @@ -0,0 +1,307 @@ +/* + *------------------------------------------------------------------ + * Copyright (c) 2018 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------ + */ + +#include <igmp/igmp_query.h> +#include <igmp/igmp_pkt.h> + +static f64 +igmp_get_random_resp_delay (const igmp_header_t * header) +{ + u32 seed; + + seed = vlib_time_now (vlib_get_main ()); + + return ((random_f64 (&seed) * igmp_header_get_max_resp_time (header))); + +} + +static ip46_address_t * +igmp_query_mk_source_list (const igmp_membership_query_v3_t * q) +{ + ip46_address_t *srcs = NULL; + const ip4_address_t *s; + u16 ii, n; + + n = clib_net_to_host_u16 (q->n_src_addresses); + + if (0 == n) + return (NULL); + + vec_validate (srcs, n - 1); + s = q->src_addresses; + + for (ii = 0; ii < n; ii++) + { + srcs[ii].ip4 = *s; + s++; + } + + return (srcs); +} + +static void +igmp_send_group_report_v3 (u32 obj, void *data) +{ + igmp_pkt_build_report_t br; + igmp_config_t *config; + ip46_address_t *srcs; + igmp_group_t *group; + igmp_main_t *im; + + im = &igmp_main; + srcs = data; + group = pool_elt_at_index (im->groups, obj); + config = pool_elt_at_index (im->configs, group->config); + + igmp_pkt_build_report_init (&br, config->sw_if_index); + ASSERT (group->timers[IGMP_GROUP_TIMER_QUERY_REPLY] != + IGMP_TIMER_ID_INVALID); + + IGMP_DBG ("send-group-report: %U", + format_vnet_sw_if_index_name, + vnet_get_main (), config->sw_if_index); + + if (NULL == srcs) + { + /* + * there were no sources specified, so this is a group-sepcific query. + * We should respond with all our sources + */ + igmp_pkt_report_v3_add_group (&br, group, + IGMP_MEMBERSHIP_GROUP_mode_is_include); + } + else + { + /* + * the sources stored in the timer object are the combined set of sources + * to be quired. We need to respond only to those queried, not our full set. + */ + ip46_address_t *intersect; + + intersect = igmp_group_new_intersect_present (group, + IGMP_FILTER_MODE_INCLUDE, + srcs); + + if (vec_len (intersect)) + { + igmp_pkt_report_v3_add_report (&br, + group->key, + intersect, + IGMP_MEMBERSHIP_GROUP_mode_is_include); + vec_free (intersect); + } + } + + igmp_pkt_report_v3_send (&br); + + igmp_timer_retire (&group->timers[IGMP_GROUP_TIMER_QUERY_REPLY]); + vec_free (srcs); +} + +static igmp_membership_group_v3_type_t +igmp_filter_mode_to_report_type (igmp_filter_mode_t mode) +{ + switch (mode) + { + case IGMP_FILTER_MODE_INCLUDE: + return (IGMP_MEMBERSHIP_GROUP_mode_is_include); + case IGMP_FILTER_MODE_EXCLUDE: + return (IGMP_MEMBERSHIP_GROUP_mode_is_exclude); + } + + return (IGMP_MEMBERSHIP_GROUP_mode_is_include); +} + +/** + * Send igmp membership general report. + */ +static void +igmp_send_general_report_v3 (u32 obj, void *data) +{ + igmp_pkt_build_report_t br; + igmp_config_t *config; + igmp_group_t *group; + igmp_main_t *im; + + im = &igmp_main; + config = pool_elt_at_index (im->configs, obj); + + ASSERT (config->timers[IGMP_CONFIG_TIMER_GENERAL_REPORT] != + IGMP_TIMER_ID_INVALID); + + igmp_timer_retire (&config->timers[IGMP_CONFIG_TIMER_GENERAL_REPORT]); + + IGMP_DBG ("send-general-report: %U", + format_vnet_sw_if_index_name, + vnet_get_main (), config->sw_if_index); + + igmp_pkt_build_report_init (&br, config->sw_if_index); + + /* *INDENT-OFF* */ + FOR_EACH_GROUP (group, config, + ({ + igmp_pkt_report_v3_add_group + (&br, group, + igmp_filter_mode_to_report_type(group->router_filter_mode)); + })); + /* *INDENT-ON* */ + + igmp_pkt_report_v3_send (&br); +} + +/** + * Called from the main thread on reception of a Query message + */ +void +igmp_handle_query (const igmp_query_args_t * args) +{ + igmp_config_t *config; + + config = igmp_config_lookup (args->sw_if_index); + + if (!config) + /* + * no IGMP config on the interface. quit + */ + return; + + if (IGMP_MODE_ROUTER == config->mode) + { + ASSERT (0); + // code here for querier election */ + } + + IGMP_DBG ("query-rx: %U", format_vnet_sw_if_index_name, + vnet_get_main (), args->sw_if_index); + + + /* + Section 5.2 + "When a system receives a Query, it does not respond immediately. + Instead, it delays its response by a random amount of time, bounded + by the Max Resp Time value derived from the Max Resp Code in the + received Query message. A system may receive a variety of Queries on + different interfaces and of different kinds (e.g., General Queries, + Group-Specific Queries, and Group-and-Source-Specific Queries), each + of which may require its own delayed response. + */ + if (igmp_membership_query_v3_is_geeral (args->query)) + { + IGMP_DBG ("...general-query-rx: %U", format_vnet_sw_if_index_name, + vnet_get_main (), args->sw_if_index); + + /* + * A general query has no info that needs saving from the response + */ + if (IGMP_TIMER_ID_INVALID == + config->timers[IGMP_CONFIG_TIMER_GENERAL_REPORT]) + { + f64 delay = igmp_get_random_resp_delay (&args->query[0].header); + + IGMP_DBG ("...general-query-rx: %U schedule for %f", + format_vnet_sw_if_index_name, vnet_get_main (), + args->sw_if_index, delay); + + /* + * no currently running timer, schedule a new one + */ + config->timers[IGMP_CONFIG_TIMER_GENERAL_REPORT] = + igmp_timer_schedule (delay, + igmp_config_index (config), + igmp_send_general_report_v3, NULL); + } + /* + * else + * don't reschedule timers, we'll reply soon enough.. + */ + } + else + { + /* + * G or SG query. we'll need to save the sources quered + */ + igmp_key_t key = { + .ip4 = args->query[0].group_address, + }; + ip46_address_t *srcs; + igmp_timer_id_t tid; + igmp_group_t *group; + + group = igmp_group_lookup (config, &key); + + /* + * If there is no group config, no worries, we can ignore this + * query. If the group state does come soon, we'll send a + * state-change report at that time. + */ + if (!group) + return; + + srcs = igmp_query_mk_source_list (args->query); + tid = group->timers[IGMP_GROUP_TIMER_QUERY_REPLY]; + + IGMP_DBG ("...group-query-rx: %U for (%U, %U)", + format_vnet_sw_if_index_name, + vnet_get_main (), args->sw_if_index, + format_igmp_src_addr_list, srcs, format_igmp_key, &key); + + + if (IGMP_TIMER_ID_INVALID != tid) + { + /* + * There is a timer already running, merge the sources list + */ + ip46_address_t *current, *s; + + current = igmp_timer_get_data (tid); + + vec_foreach (s, srcs) + { + if (~0 == vec_search_with_function (current, s, + ip46_address_is_equal)) + { + vec_add1 (current, *s); + } + } + + igmp_timer_set_data (tid, current); + } + else + { + /* + * schedule a new G-specific query + */ + f64 delay = igmp_get_random_resp_delay (&args->query[0].header); + + IGMP_DBG ("...group-query-rx: schedule:%f", delay); + + group->timers[IGMP_GROUP_TIMER_QUERY_REPLY] = + igmp_timer_schedule (delay, + igmp_group_index (group), + igmp_send_group_report_v3, srcs); + } + } +} + + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ |