/*
*------------------------------------------------------------------
* 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;
/*
* we validated this packet when we accepted it in the DP, so
* this number is safe to use
*/
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-specific 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 required. 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_general (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:
*/