summaryrefslogtreecommitdiffstats
path: root/src/plugins/igmp/igmp_query.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/igmp/igmp_query.c')
-rw-r--r--src/plugins/igmp/igmp_query.c307
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:
+ */