aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNeale Ranns <nranns@cisco.com>2018-06-07 23:48:20 -0700
committerFlorin Coras <florin.coras@gmail.com>2018-07-09 21:10:53 +0000
commit947ea6222dad1ef04595c34273e9231395aef443 (patch)
tree8990854b2901ff8cc2241b9abfc99b0b4b54d517
parentdd47ecadcf63772a6037a1bb3715772d80e87f51 (diff)
IGMP improvements
- Enable/Disable an interface for IGMP - improve logging - refactor common code - no orphaned timers - IGMP state changes in main thread only - Large groups split over multiple state-change reports - SSM range configuration API. - more tests Change-Id: If5674f1044e7e97274a711f47807c9ba689d7b9a Signed-off-by: Neale Ranns <nranns@cisco.com>
-rw-r--r--src/plugins/igmp.am12
-rw-r--r--src/plugins/igmp/igmp.api197
-rw-r--r--src/plugins/igmp/igmp.c1255
-rw-r--r--src/plugins/igmp/igmp.h497
-rw-r--r--src/plugins/igmp/igmp_api.c290
-rw-r--r--src/plugins/igmp/igmp_api.h58
-rw-r--r--src/plugins/igmp/igmp_cli.c (renamed from src/plugins/igmp/cli.c)80
-rw-r--r--src/plugins/igmp/igmp_config.c96
-rw-r--r--src/plugins/igmp/igmp_config.h128
-rw-r--r--src/plugins/igmp/igmp_error.h (renamed from src/plugins/igmp/error.h)2
-rw-r--r--src/plugins/igmp/igmp_format.c86
-rw-r--r--src/plugins/igmp/igmp_format.h16
-rw-r--r--src/plugins/igmp/igmp_group.c272
-rw-r--r--src/plugins/igmp/igmp_group.h156
-rw-r--r--src/plugins/igmp/igmp_input.c (renamed from src/plugins/igmp/input.c)314
-rw-r--r--src/plugins/igmp/igmp_pkt.c534
-rw-r--r--src/plugins/igmp/igmp_pkt.h78
-rw-r--r--src/plugins/igmp/igmp_query.c307
-rw-r--r--src/plugins/igmp/igmp_query.h37
-rw-r--r--src/plugins/igmp/igmp_report.c193
-rw-r--r--src/plugins/igmp/igmp_report.h37
-rw-r--r--src/plugins/igmp/igmp_src.c151
-rw-r--r--src/plugins/igmp/igmp_src.h88
-rw-r--r--src/plugins/igmp/igmp_ssm_range.c162
-rw-r--r--src/plugins/igmp/igmp_ssm_range.h58
-rw-r--r--src/plugins/igmp/igmp_timer.c241
-rw-r--r--src/plugins/igmp/igmp_timer.h84
-rw-r--r--src/plugins/igmp/igmp_types.h77
-rw-r--r--src/vlibapi/api_helper_macros.h4
-rw-r--r--src/vnet.am1
-rw-r--r--src/vnet/fib/fib_test.c6
-rw-r--r--src/vnet/ip/igmp_packet.h88
-rw-r--r--src/vnet/ip/ip.api1
-rw-r--r--src/vnet/ip/ip4_error.h2
-rw-r--r--src/vnet/ip/ip_types.api6
-rw-r--r--src/vnet/ip/ip_types_api.c105
-rw-r--r--src/vnet/ip/ip_types_api.h50
-rw-r--r--src/vnet/mfib/mfib_types.h2
-rw-r--r--src/vppinfra/vec.h21
-rw-r--r--test/framework.py5
-rw-r--r--test/test_igmp.py732
-rw-r--r--test/vpp_igmp.py75
-rw-r--r--test/vpp_ip_route.py38
-rw-r--r--test/vpp_papi_provider.py35
44 files changed, 4672 insertions, 2005 deletions
diff --git a/src/plugins/igmp.am b/src/plugins/igmp.am
index 9829ea681ee..503d4cb14ca 100644
--- a/src/plugins/igmp.am
+++ b/src/plugins/igmp.am
@@ -15,10 +15,18 @@ vppplugins_LTLIBRARIES += igmp_plugin.la
igmp_plugin_la_SOURCES = \
igmp/igmp.c \
- igmp/cli.c \
+ igmp/igmp_query.c \
+ igmp/igmp_report.c \
+ igmp/igmp_group.c \
+ igmp/igmp_src.c \
+ igmp/igmp_config.c \
+ igmp/igmp_cli.c \
igmp/igmp_api.c \
+ igmp/igmp_input.c \
igmp/igmp_plugin.api.h \
- igmp/input.c \
+ igmp/igmp_timer.c \
+ igmp/igmp_pkt.c \
+ igmp/igmp_ssm_range.c \
igmp/igmp_format.c
nobase_apiinclude_HEADERS += \
diff --git a/src/plugins/igmp/igmp.api b/src/plugins/igmp/igmp.api
index 1533d666a1c..9bf654f0749 100644
--- a/src/plugins/igmp/igmp.api
+++ b/src/plugins/igmp/igmp.api
@@ -1,3 +1,4 @@
+/* Hey Emacs use -*- mode: C -*- */
/*
*------------------------------------------------------------------
* Copyright (c) 2017 Cisco and/or its affiliates.
@@ -16,51 +17,92 @@
*/
option version = "1.0.0";
+import "vnet/ip/ip_types.api";
-/** \brief
- Used by a 'host' to enable the recption/listening of packets for a specific
- multicast group
- @param client_index - opaque cookie to identify the sender
- @param context - sender context, to match reply w/ request
- @param enable - if set, enable igmp messages on configuration
- @param sw_if_index - interface sw index
- @param saddr - source address
- @param gaddr - group address
-*/
+/**
+ * @brief Filter mode
+ */
+enum filter_mode {
+ EXCLUDE = 0,
+ INCLUDE = 1,
+};
+
+
+/**
+ * @brief
+ * Used by a 'host' to enable the reception/listening of packets for a specific
+ * multicast group
+ *
+ * For each socket on which IPMulticastListen has been invoked, the
+ * system records the desired multicast reception state for that socket.
+ * That state conceptually consists of a set of records of the form:
+ *
+ * (interface, multicast-address, filter-mode, source-list)
+ *
+ * The socket state evolves in response to each invocation of
+ * IPMulticastListen on the socket, as follows:
+ *
+ * o If the requested filter mode is INCLUDE *and* the requested source
+ * list is empty, then the entry corresponding to the requested
+ * interface and multicast address is deleted if present. If no such
+ * entry is present, the request is ignored.
+ *
+ * o If the requested filter mode is EXCLUDE *or* the requested source
+ * list is non-empty, then the entry corresponding to the requested
+ * interface and multicast address, if present, is changed to contain
+ * the requested filter mode and source list. If no such entry is
+ * present, a new entry is created, using the parameters specified in
+ * the request.
+ *
+ * @param client_index - opaque cookie to identify the sender
+ * @param context - sender context, to match reply w/ request
+ * @param sw_if_index - interface sw index
+ * @param filter - filter mode
+ * @param saddr - source address
+ * @param gaddr - group address
+ */
+typeonly define igmp_group
+{
+ vl_api_filter_mode_t filter;
+ u8 n_srcs;
+ u32 sw_if_index;
+ vl_api_ip4_address_t gaddr;
+ vl_api_ip4_address_t saddrs[n_srcs];
+};
autoreply define igmp_listen
{
u32 client_index;
u32 context;
- u8 enable;
- u32 sw_if_index;
- u8 saddr[4];
- u8 gaddr[4];
+ vl_api_igmp_group_t group;
};
-/** \brief
- Used by a 'router' to enable the recption of IGMP packets and the
- construction of group state for hosts on the link
- multicast group
- @param client_index - opaque cookie to identify the sender
- @param context - sender context, to match reply w/ request
- @param enable - if set, enable igmp messages on configuration
- @param sw_if_index - interface sw index
-*/
+/**
+ * @brief
+ * Used by a 'router' and 'host' to enable the recption of IGMP packets.
+ * For hosts this must be called once before igmp_listen.
+ *
+ * @param client_index - opaque cookie to identify the sender
+ * @param context - sender context, to match reply w/ request
+ * @param enable - if set, enable igmp messages on configuration
+ * @param mode - Host (1) or router (0) mode
+ * @param sw_if_index - interface sw index
+ */
autoreply define igmp_enable_disable
{
u32 client_index;
u32 context;
u8 enable;
+ u8 mode;
u32 sw_if_index;
};
-/** \brief dump (S,G)s from interface
- @param client_index - opaque cookie to identify the sender
- @param context - sender context, to match reply w/ request
- @param sw_if_index - interface sw index
- @param dump_all - get (S,G)s from all interfaces
+/**
+ * @brief dump (S,G)s from interface
+ * @param client_index - opaque cookie to identify the sender
+ * @param context - sender context, to match reply w/ request
+ * @param sw_if_index - interface sw index (~0 for all)
*/
define igmp_dump
{
@@ -68,22 +110,22 @@ define igmp_dump
u32 context;
u32 sw_if_index;
- u8 dump_all;
};
-/** \brief igmp details
- @param context - sender context, to match reply w/ request
- @param sw_if_index - interface sw index
- @param saddr - source address
- @param gaddr - group address
-*/
+/**
+ * @brief igmp details
+ * @param context - sender context, to match reply w/ request
+ * @param sw_if_index - interface sw index
+ * @param saddr - source address
+ * @param gaddr - group address
+ */
define igmp_details
{
u32 context;
u32 sw_if_index;
- u8 saddr[4];
- u8 gaddr[4];
+ vl_api_ip4_address_t saddr;
+ vl_api_ip4_address_t gaddr;
};
/** \brief remove all (S,G)s from an interface
@@ -99,12 +141,13 @@ autoreply define igmp_clear_interface
u32 sw_if_index;
};
-/** \brief register for igmp events
- @param client_index - opaque cookie to identify the sender
- @param context - sender context, to match reply w/ request
- @param pid - sender's pid
- @param enable - 1 enable, 0 disable igmp events
-*/
+/**
+ * @brief register for igmp events
+ * @param client_index - opaque cookie to identify the sender
+ * @param context - sender context, to match reply w/ request
+ * @param pid - sender's pid
+ * @param enable - 1 enable, 0 disable igmp events
+ */
autoreply define want_igmp_events
{
u32 client_index;
@@ -119,24 +162,70 @@ service {
events igmp_event;
};
-/** \brief igmp event details
- @param client_index - opaque cookie to identify the sender
- @param context - sender context, to match reply w/ request
- @param sw_if_index - interface sw index
- @param saddr - source address
- @param gaddr - group address
- @param is_join - if set source is joining the group, else leaving
-*/
+/**
+ * @brief igmp event details
+ * @param client_index - opaque cookie to identify the sender
+ * @param context - sender context, to match reply w/ request
+ * @param sw_if_index - interface sw index
+ * @param saddr - source address
+ * @param gaddr - group address
+ *@param filter - filter mode
+ */
define igmp_event
{
u32 context;
u32 sw_if_index;
- u8 saddr[4];
- u8 gaddr[4];
- u8 is_join;
+ vl_api_filter_mode_t filter;
+ vl_api_ip4_address_t saddr;
+ vl_api_ip4_address_t gaddr;
+};
+
+/**
+ * @brief enum to specify either ASM or SSM semantics
+ */
+enum group_prefix_type
+{
+ ASM = 0,
+ SSM = 1,
+};
+
+/**
+ * @brief Definition of a Group prefix and its type
+ */
+typedef group_prefix
+{
+ vl_api_group_prefix_type_t type;
+ vl_api_prefix_t prefix;
+};
+
+/**
+ * @brief Configure a prefix for SSM or ASM semantics
+ * @param address - Prefix address
+ * @param address_length - Prefix length
+ */
+autoreply define igmp_group_prefix_set
+{
+ u32 client_index;
+ u32 context;
+
+ vl_api_group_prefix_t gp;
};
+define igmp_group_prefix_dump
+{
+ u32 client_index;
+ u32 context;
+};
+
+define igmp_group_prefix_details
+{
+ u32 context;
+
+ vl_api_group_prefix_t gp;
+};
+
+
/*
* Local Variables:
* eval: (c-set-style "gnu")
diff --git a/src/plugins/igmp/igmp.c b/src/plugins/igmp/igmp.c
index 71c91b05515..5cb8f02e9f5 100644
--- a/src/plugins/igmp/igmp.c
+++ b/src/plugins/igmp/igmp.c
@@ -27,891 +27,280 @@
#include <vnet/mfib/mfib_table.h>
#include <igmp/igmp.h>
+#include <igmp/igmp_format.h>
+#include <igmp/igmp_pkt.h>
#include <limits.h>
#include <float.h>
igmp_main_t igmp_main;
-void
-igmp_clear_group (igmp_config_t * config, igmp_group_t * group)
-{
- igmp_src_t *src;
-
- ASSERT (config);
- ASSERT (group);
-
- IGMP_DBG ("group_type %u, sw_if_index %d", group->type,
- config->sw_if_index);
-
- /* *INDENT-OFF* */
- pool_foreach (src, group->srcs, (
- {
- clib_mem_free (src->key);
- }));
- /* *INDENT-ON* */
- pool_free (group->srcs);
- hash_free (group->igmp_src_by_key);
-
- hash_unset_mem (config->igmp_group_by_key, group->key);
- clib_mem_free (group->key);
- pool_put (config->groups, group);
-}
-
-void
-igmp_clear_config (igmp_config_t * config)
-{
- igmp_main_t *im = &igmp_main;
- igmp_group_t *group;
-
- ASSERT (config);
- /* *INDENT-OFF* */
- pool_foreach (group, config->groups, (
- {
- igmp_clear_group (config, group);
- }));
- /* *INDENT-ON* */
- pool_free (config->groups);
- hash_free (config->igmp_group_by_key);
-
- hash_unset (im->igmp_config_by_sw_if_index, config->sw_if_index);
- pool_put (im->configs, config);
-}
-
-/** \brief igmp timer compare
- @param _a - igmp timer
- @param _b - igmp timer
-
- Compare function for igmp_timer_t sorting.
-*/
-int
-igmp_timer_compare (const void *_a, const void *_b)
-{
- const igmp_timer_t *a = _a;
- const igmp_timer_t *b = _b;
- f64 dt = b->exp_time - a->exp_time;
- return dt < 0 ? -1 : (dt > 0 ? +1 : 0);
-}
-
-void
-igmp_sort_timers (igmp_timer_t * timers)
-{
- vlib_main_t *vm = vlib_get_main ();
-
- qsort (timers, vec_len (timers), sizeof (igmp_timer_t), igmp_timer_compare);
-
- vlib_process_signal_event (vm, igmp_timer_process_node.index,
- IGMP_PROCESS_EVENT_UPDATE_TIMER, 0);
-}
-
-void
-igmp_create_int_timer (f64 time, u32 sw_if_index,
- igmp_timer_function_t * func)
-{
- igmp_main_t *im = &igmp_main;
- igmp_timer_t *timer;
-
- pool_get (im->timers, timer);
- memset (timer, 0, sizeof (igmp_timer_t));
- timer->func = func;
- timer->exp_time = time;
- timer->sw_if_index = sw_if_index;
-
- igmp_sort_timers (im->timers);
-}
-
-void
-igmp_create_group_timer (f64 time, u32 sw_if_index, igmp_key_t * gkey,
- igmp_timer_function_t * func)
-{
- igmp_main_t *im = &igmp_main;
- igmp_timer_t *timer;
-
- pool_get (im->timers, timer);
- memset (timer, 0, sizeof (igmp_timer_t));
- timer->func = func;
- timer->exp_time = time;
- timer->sw_if_index = sw_if_index;
-
-
- ASSERT (gkey);
- /* duplicate keys, to prevent segmentation fault if (S,G) is removed */
- timer->data = clib_mem_alloc (sizeof (igmp_key_t));
- clib_memcpy (&((igmp_key_t *) timer->data)[0], gkey, sizeof (igmp_key_t));
-
- igmp_sort_timers (im->timers);
-}
-
-void
-igmp_create_src_timer (f64 time, u32 sw_if_index, igmp_key_t * gkey,
- igmp_key_t * skey, igmp_timer_function_t * func)
-{
- igmp_main_t *im = &igmp_main;
- igmp_timer_t *timer;
-
- pool_get (im->timers, timer);
- memset (timer, 0, sizeof (igmp_timer_t));
- timer->func = func;
- timer->exp_time = time;
- timer->sw_if_index = sw_if_index;
-
- ASSERT (gkey);
- ASSERT (skey);
- /* duplicate keys, to prevent segmentation fault if (S,G) is removed */
- timer->data = clib_mem_alloc (sizeof (igmp_key_t) * 2);
- clib_memcpy (&((igmp_key_t *) timer->data)[0], gkey, sizeof (igmp_key_t));
- clib_memcpy (&((igmp_key_t *) timer->data)[1], skey, sizeof (igmp_key_t));
-
- igmp_sort_timers (im->timers);
-}
-
-/** \brief igmp get next timer
- @param im - igmp main
-
- Get next timer.
-*/
-always_inline igmp_timer_t *
-igmp_get_next_timer (igmp_main_t * im)
-{
- if (pool_elts (im->timers) > 0)
- return vec_elt_at_index (im->timers, pool_elts (im->timers) - 1);
- return NULL;
-}
-
-/*
-static void
-igmp_create_report_v2 (vlib_buffer_t * b, igmp_config_t * config)
-{
- ip_csum_t sum;
- u16 csum;
- igmp_main_t *im = &igmp_main;
- igmp_sg_t *sg;
-
- sg = vec_elt_at_index (config->sg, im->next_index.sg_index);
-
- igmp_message_t *igmp = (igmp_message_t *) (vlib_buffer_get_current (b));
- memset (igmp, 0, sizeof (igmp_message_t));
-
- clib_memcpy (&igmp->dst, &sg->gaddr.ip4, sizeof (ip4_address_t));
- igmp->header.type =
- (sg->group_type == IGMP_MEMBERSHIP_GROUP_block_old_sources) ?
- IGMP_TYPE_leave_group_v2 : IGMP_TYPE_membership_report_v2;
- sum = ip_incremental_checksum (0, igmp, sizeof (igmp_message_t));
- csum = ~ip_csum_fold (sum);
- igmp->header.checksum = csum;
+/* *INDENT-OFF* */
+/* General Query address */
+const static mfib_prefix_t mpfx_general_query = {
+ .fp_proto = FIB_PROTOCOL_IP4,
+ .fp_len = 32,
+ .fp_grp_addr = {
+ .ip4 = {
+ .as_u32 = IGMP_GENERAL_QUERY_ADDRESS,
+ },
+ },
+};
- b->current_data += sizeof (igmp_message_t);
- b->current_length += sizeof (igmp_message_t);
-}
-*/
+/* Report address */
+const static mfib_prefix_t mpfx_report = {
+ .fp_proto = FIB_PROTOCOL_IP4,
+ .fp_len = 32,
+ .fp_grp_addr = {
+ .ip4 = {
+ .as_u32 = IGMP_MEMBERSHIP_REPORT_ADDRESS,
+ },
+ },
+};
+/* *INDENT-ON* */
-/* TODO: divide (S,G)s to multiple reports...
- * - create report limited by <packet size|number of (S,G)s>?
- * - save loop state
- * - on next timer continue loop
- * - case of new query -> reset loop
+/**
+ * @brief igmp send query (igmp_timer_function_t)
+ *
+ * Send an igmp query.
+ * If the timer holds group key, send Group-Specific query,
+ * else send General query.
*/
-
-/** \brief igmp create report all (v3)
- @param b - vlib buffer
- @param config - igmp configuration
- @param group - igmp group
-
- Create IGMPv3 report. If group is NULL, send all groups on interface.
-*/
-static void
-igmp_create_report_v3 (vlib_buffer_t * b, igmp_config_t * config,
- igmp_group_t * group)
-{
- ip_csum_t sum;
- u16 csum;
- u32 len = 0;
- int i;
-
- igmp_src_t *src;
-
- igmp_membership_group_v3_t *igmp_group;
-
- len = sizeof (igmp_membership_report_v3_t);
-
- igmp_membership_report_v3_t *igmp =
- (igmp_membership_report_v3_t *) (vlib_buffer_get_current (b));
- memset (igmp, 0, sizeof (igmp_membership_report_v3_t));
-
- igmp->header.type = IGMP_TYPE_membership_report_v3;
- igmp->n_groups =
- clib_net_to_host_u16 ((group) ? 1 : pool_elts (config->groups));
-
- /* get pointer to first group */
- igmp_group = igmp->groups;
-
- /* if group is not NULL, send the specified group */
- if (group)
- {
- memset (igmp_group, 0, sizeof (igmp_membership_group_v3_t));
- igmp_group->type = group->type;
- igmp_group->n_src_addresses =
- clib_host_to_net_u16 (pool_elts (group->srcs));
- igmp_group->dst_address = group->addr.ip4;
- i = 0;
- /* *INDENT-OFF* */
- pool_foreach (src, group->srcs, (
- {
- igmp_group->src_addresses[i++] = src->addr.ip4;
- }));
- /* *INDENT-ON* */
- len += sizeof (ip4_address_t) * i;
- len += sizeof (igmp_membership_group_v3_t);
- }
- else
- {
- /* *INDENT-OFF* */
- pool_foreach (group, config->groups, (
- {
- memset (igmp_group, 0, sizeof (igmp_membership_group_v3_t));
- igmp_group->type = group->type;
- igmp_group->n_src_addresses =
- clib_host_to_net_u16 (pool_elts (group->srcs));
- igmp_group->dst_address = group->addr.ip4;
- i = 0;
- pool_foreach (src, group->srcs, (
- {
- igmp_group->src_addresses[i++] = src->addr.ip4;
- }));
- len += sizeof (ip4_address_t) * i;
- len += sizeof (igmp_membership_group_v3_t);
- igmp_group = group_ptr (igmp, len);
- }));
- /* *INDENT-ON* */
- }
-
- sum = ip_incremental_checksum (0, igmp, len);
- csum = ~ip_csum_fold (sum);
- igmp->header.checksum = csum;
-
- b->current_data += len;
- b->current_length += len;
-}
-
-/** \brief igmp create query (v3)
- @param b - vlib buffer
- @param config - configuration that sends the query
- @param group - if not NULL, create Group-specific query
-
- Create igmp v3 qeury inside vlib buffer b.
- If group == NULL create general query,
- else, create group specific query.
-*/
static void
-igmp_create_query_v3 (vlib_buffer_t * b, igmp_config_t * config,
- igmp_group_t * group)
+igmp_send_general_query (u32 obj, void *dat)
{
- vlib_main_t *vm = vlib_get_main ();
- ip_csum_t sum;
- u16 csum;
-
- igmp_membership_query_v3_t *igmp =
- (igmp_membership_query_v3_t *) (vlib_buffer_get_current (b));
- memset (igmp, 0, sizeof (igmp_membership_query_v3_t));
+ igmp_pkt_build_query_t bq;
+ igmp_config_t *config;
- igmp->header.type = IGMP_TYPE_membership_query;
- igmp->header.code = 100;
+ config = igmp_config_get (obj);
- config->flags &= ~IGMP_CONFIG_FLAG_QUERY_RESP_RECVED;
- igmp_create_int_timer (vlib_time_now (vm) + (f64) (igmp->header.code / 10),
- config->sw_if_index, igmp_query_resp_exp);
+ IGMP_DBG ("send-general-query: %U",
+ format_vnet_sw_if_index_name, vnet_get_main (),
+ config->sw_if_index);
- if (PREDICT_FALSE (group != NULL))
- clib_memcpy (&igmp->dst, &group->addr.ip4, sizeof (ip4_address_t));
+ igmp_timer_retire (&config->timers[IGMP_CONFIG_TIMER_GENERAL_QUERY]);
- sum =
- ip_incremental_checksum (0, igmp, sizeof (igmp_membership_query_v3_t));
- csum = ~ip_csum_fold (sum);
- igmp->header.checksum = csum;
+ igmp_pkt_build_query_init (&bq, config->sw_if_index);
+ igmp_pkt_query_v3_add_group (&bq, NULL, NULL);
+ igmp_pkt_query_v3_send (&bq);
- b->current_data += sizeof (igmp_membership_query_v3_t);
- b->current_length += sizeof (igmp_membership_query_v3_t);
+ /*
+ * re-schedule
+ */
+ config->timers[IGMP_CONFIG_TIMER_GENERAL_QUERY] =
+ igmp_timer_schedule (igmp_timer_type_get (IGMP_TIMER_QUERY),
+ igmp_config_index (config),
+ igmp_send_general_query, NULL);
}
-/** \brief igmp create ip4
- @param b - vlib buffer
- @param config - igmp configuration
- @param group - igmp membership group
- @param is_report - if zero create query, else create report
-
- Create ip4 header in vlib buffer b.
-*/
static void
-igmp_create_ip4 (vlib_buffer_t * b, igmp_config_t * config,
- igmp_group_t * group, u8 is_report)
+igmp_send_state_change_group_report_v3 (u32 sw_if_index,
+ const igmp_group_t * group)
{
- ip_lookup_main_t *lm = &ip4_main.lookup_main;
-
- ip4_header_t *ip4 = (ip4_header_t *) (vlib_buffer_get_current (b));
- memset (ip4, 0, sizeof (ip4_header_t));
- ip4->ip_version_and_header_length = 0x45;
- ip4->ttl = 1;
- ip4->protocol = 2;
- ip4->tos = 0xc0;
-
- u32 if_add_index =
- lm->if_address_pool_index_by_sw_if_index[config->sw_if_index];
- if (PREDICT_TRUE (if_add_index != ~0))
- {
- ip_interface_address_t *if_add =
- pool_elt_at_index (lm->if_address_pool, if_add_index);
- ip4_address_t *if_ip = ip_interface_address_get_address (lm, if_add);
- clib_memcpy (&ip4->src_address, if_ip, sizeof (ip4_address_t));
- }
-
- if (is_report)
- ip4->dst_address.as_u32 =
- clib_host_to_net_u32 (IGMP_MEMBERSHIP_REPORT_ADDRESS);
- else
- {
- if ((group != NULL))
- clib_memcpy (&ip4->dst_address, &group->addr.ip4,
- sizeof (ip4_address_t));
- else
- ip4->dst_address.as_u32 =
- clib_host_to_net_u32 (IGMP_GENERAL_QUERY_ADDRESS);
- }
-
- b->current_data += ip4_header_bytes (ip4);
- b->current_length += ip4_header_bytes (ip4);
+ igmp_pkt_build_report_t br;
- config->next_create_msg (b, config, group);
- ip4->length = clib_host_to_net_u16 (b->current_length);
+ IGMP_DBG ("state-change-group: %U", format_igmp_key, group->key);
- ip4->checksum = ip4_header_checksum (ip4);
+ igmp_pkt_build_report_init (&br, sw_if_index);
+ igmp_pkt_report_v3_add_group (&br,
+ group,
+ IGMP_MEMBERSHIP_GROUP_allow_new_sources);
+ igmp_pkt_report_v3_send (&br);
}
-
-/** \brief igmp send message
- @param vm - vlib main
- @param node - vlib runtime node
- @param im - igmp main
- @param config - igmp configuration
- @param group - igmp mebership group
- @param is_report - 0 == qeury, else report
-
- Send an igmp message. Get free vlib buffer fill it with igmp packet and transmit.
-*/
static void
-igmp_send_msg (vlib_main_t * vm, vlib_node_runtime_t * node,
- igmp_main_t * im, igmp_config_t * config, igmp_group_t * group,
- u8 is_report)
-{
- u32 *to_next = 0;
- u32 next_index = ip4_rewrite_node.index;
-
- u32 bi = 0;
- vlib_buffer_alloc (vm, &bi, 1);
-
- vlib_buffer_t *b = vlib_get_buffer (vm, bi);
- vlib_buffer_free_list_t *fl = vlib_buffer_get_free_list (vm,
- VLIB_BUFFER_DEFAULT_FREE_LIST_INDEX);
- vlib_buffer_init_for_free_list (b, fl);
-
- b->current_data = 0;
- b->current_length = 0;
-
- igmp_create_ip4 (b, config, group, is_report);
-
- b->current_data = 0;
-
- b->total_length_not_including_first_buffer = 0;
- b->flags = VLIB_BUFFER_TOTAL_LENGTH_VALID;
- vnet_buffer (b)->sw_if_index[VLIB_RX] = (u32) ~ 0;
- vnet_buffer (b)->ip.adj_index[VLIB_TX] = config->adj_index;
-
- b->flags |= VNET_BUFFER_F_LOCALLY_ORIGINATED;
-
-
- vlib_frame_t *f = vlib_get_frame_to_node (vm, next_index);
- to_next = vlib_frame_vector_args (f);
- to_next[0] = bi;
-
- f->n_vectors = 1;
-
- vlib_buffer_t *c = vlib_buffer_copy (vm, b);
- to_next += 1;
- to_next[0] = vlib_get_buffer_index (vm, c);
-
- vlib_put_frame_to_node (vm, next_index, f);
-}
-
-void
-igmp_send_query (vlib_main_t * vm, vlib_node_runtime_t * rt, igmp_main_t * im,
- igmp_timer_t * timer)
-{
- igmp_config_t *config;
-/* TODO: group-specific query: pass group key in timer */
- igmp_group_t *group = NULL;
-
- u32 sw_if_index = timer->sw_if_index;
-
- pool_put (im->timers, timer);
-
- config = igmp_config_lookup (im, sw_if_index);
- if (!config)
- return;
-
- /* TODO: implement IGMPv2 */
- config->next_create_msg = igmp_create_query_v3;
- igmp_send_msg (vm, rt, im, config, group, /* is_report */ 0);
-
- /* in case of group query we don't want to set up another qery timer */
- if (PREDICT_TRUE (!group))
- igmp_create_int_timer (vlib_time_now (vm) + IGMP_QUERY_TIMER, sw_if_index,
- igmp_send_query);
-}
-
-void
-igmp_query_resp_exp (vlib_main_t * vm, vlib_node_runtime_t * rt,
- igmp_main_t * im, igmp_timer_t * timer)
-{
- igmp_config_t *config;
-/* TODO: group-specific query: pass group key in timer */
- igmp_group_t *group = NULL;
-
- u32 sw_if_index = timer->sw_if_index;
-
- pool_put (im->timers, timer);
-
- config = igmp_config_lookup (im, sw_if_index);
- if (!config)
- return;
-
- /* if group != NULL this is a group-specific qeury timer */
- if (PREDICT_FALSE (group != NULL))
- {
- if ((group->flags & IGMP_GROUP_FLAG_QUERY_RESP_RECVED) == 0)
- {
- igmp_clear_group (config, group);
- return;
- }
- }
- /* if report not received in max resp time clear igmp on interface */
- if ((config->flags & IGMP_CONFIG_FLAG_QUERY_RESP_RECVED) == 0)
- {
- igmp_clear_config (config);
- }
-}
-
-void
-igmp_send_report (vlib_main_t * vm, vlib_node_runtime_t * rt,
- igmp_main_t * im, igmp_timer_t * timer)
+igmp_resend_state_change_group_report_v3 (u32 gi, void *data)
{
igmp_config_t *config;
+ igmp_group_t *group;
- u32 sw_if_index = timer->sw_if_index;
-
- pool_put (im->timers, timer);
+ group = igmp_group_get (gi);
+ config = igmp_config_get (group->config);
- config = igmp_config_lookup (im, sw_if_index);
- if (!config)
- return;
+ igmp_timer_retire (&group->timers[IGMP_GROUP_TIMER_RESEND_REPORT]);
+ igmp_send_state_change_group_report_v3 (config->sw_if_index, group);
- if (config->flags & IGMP_CONFIG_FLAG_CAN_SEND_REPORT)
+ if (++group->n_reports_sent < config->robustness_var)
{
- /* TODO: implement IGMPv2 and IGMPv1 */
- config->next_create_msg = igmp_create_report_v3;
- /* pass NULL as group to send all groups at once */
- igmp_send_msg (vm, rt, im, config, NULL, /* is_report */ 1);
- /* WIP: unset flag after all reports sent */
- config->flags &= ~IGMP_CONFIG_FLAG_CAN_SEND_REPORT;
+ group->timers[IGMP_GROUP_TIMER_RESEND_REPORT] =
+ igmp_timer_schedule (igmp_timer_type_get (IGMP_TIMER_REPORT_INTERVAL),
+ igmp_group_index (group),
+ igmp_resend_state_change_group_report_v3, NULL);
}
}
-void
-igmp_send_state_changed (vlib_main_t * vm, vlib_node_runtime_t * rt,
- igmp_main_t * im, igmp_timer_t * timer)
+int
+igmp_listen (vlib_main_t * vm,
+ igmp_filter_mode_t mode,
+ u32 sw_if_index,
+ const ip46_address_t * saddrs, const ip46_address_t * gaddr)
{
+ const ip46_address_t *saddr;
igmp_config_t *config;
igmp_group_t *group;
- igmp_src_t *src;
- igmp_key_t gkey;
-
- u32 sw_if_index = timer->sw_if_index;
- IGMP_DBG ("sw_if_index %d", sw_if_index);
- ASSERT (timer->data);
- clib_memcpy (&gkey, timer->data, sizeof (igmp_key_t));
-
- pool_put (im->timers, timer);
+ /*
+ * RFC 3376 Section 2
+ " For a given combination of socket, interface, and multicast address,
+ only a single filter mode and source list can be in effect at any one
+ time. However, either the filter mode or the source list, or both,
+ may be changed by subsequent IPMulticastListen requests that specify
+ the same socket, interface, and multicast address. Each subsequent
+ request completely replaces any earlier request for the given socket,
+ interface and multicast address."
+ */
+ int rv = 0;
+ IGMP_DBG ("listen: (%U, %U) %U %U",
+ format_igmp_src_addr_list, saddrs,
+ format_igmp_key, gaddr,
+ format_vnet_sw_if_index_name, vnet_get_main (),
+ sw_if_index, format_igmp_filter_mode, mode);
+ /*
+ * find configuration, if it dosn't exist, then this interface is
+ * not IGMP enabled
+ */
+ config = igmp_config_lookup (sw_if_index);
- config = igmp_config_lookup (im, sw_if_index);
if (!config)
- return;
-
- group = igmp_group_lookup (config, &gkey);
- if (!group)
- return;
-
- config->next_create_msg = igmp_create_report_v3;
- igmp_send_msg (vm, rt, im, config, group, /* is_report */ 1);
-
- IGMP_DBG ("group_type %u", group->type);
-
- if (group->type == IGMP_MEMBERSHIP_GROUP_change_to_filter_include)
{
- igmp_key_t new_gkey;
- igmp_group_t *new_group;
- igmp_src_t *new_src;
-
- clib_memcpy (&new_gkey.data, &group->addr, sizeof (ip46_address_t));
- new_gkey.group_type = IGMP_MEMBERSHIP_GROUP_mode_is_filter_include;
-
- new_group = igmp_group_lookup (config, &new_gkey);
- if (!new_group)
- {
- IGMP_DBG ("creating new group...");
- pool_get (config->groups, new_group);
- /* get valid pointer to old group */
- group = igmp_group_lookup (config, &gkey);
-
- memset (new_group, 0, sizeof (igmp_group_t));
-
- clib_memcpy (&new_group->addr, &group->addr,
- sizeof (ip46_address_t));
- new_group->n_srcs = 0;
- new_group->type = new_gkey.group_type;
-
- new_group->key = clib_mem_alloc (sizeof (igmp_key_t));
- clib_memcpy (new_group->key, &new_gkey, sizeof (igmp_key_t));
- new_group->igmp_src_by_key =
- hash_create_mem (0, sizeof (igmp_key_t), sizeof (uword));
- hash_set_mem (config->igmp_group_by_key, new_group->key,
- new_group - config->groups);
- }
- /* *INDENT-OFF* */
- /* loop through old group sources */
- pool_foreach (src, group->srcs, (
- {
- /* add sources to new group */
- new_src = igmp_src_lookup (new_group, src->key);
- if (!new_src)
- {
- pool_get (new_group->srcs, new_src);
- memset (new_src, 0, sizeof (igmp_src_t));
- new_group->n_srcs += 1;
- new_src->key = clib_mem_alloc (sizeof (igmp_key_t));
- clib_memcpy (new_src->key, src->key, sizeof (igmp_key_t));
- clib_memcpy (&new_src->addr, &src->addr,
- sizeof (ip46_address_t));
-
- hash_set_mem (new_group->igmp_src_by_key, new_src->key,
- new_src - new_group->srcs);
- }
- }));
- /* *INDENT-ON* */
+ rv = VNET_API_ERROR_INVALID_INTERFACE;
+ goto error;
}
-
- /* remove group */
- IGMP_DBG ("remove group");
- igmp_clear_group (config, group);
- if (pool_elts (config->groups) == 0)
+ if (config->mode != IGMP_MODE_HOST)
{
- hash_unset (im->igmp_config_by_sw_if_index, config->sw_if_index);
- pool_put (im->configs, config);
+ rv = VNET_API_ERROR_INVALID_INTERFACE;
+ goto error;
}
-}
-
-void
-igmp_src_exp (vlib_main_t * vm, vlib_node_runtime_t * rt,
- igmp_main_t * im, igmp_timer_t * timer)
-{
- igmp_config_t *config;
- igmp_group_t *group;
- igmp_src_t *src;
- ASSERT (timer->data);
+ /* find igmp group, if it dosn't exist, create new */
+ group = igmp_group_lookup (config, gaddr);
- igmp_key_t *gkey = (igmp_key_t *) & ((igmp_key_t *) timer->data)[0];
- igmp_key_t *skey = (igmp_key_t *) & ((igmp_key_t *) timer->data)[1];
-
- config = igmp_config_lookup (im, timer->sw_if_index);
- if (!config)
- goto done;
- group = igmp_group_lookup (config, gkey);
if (!group)
- goto done;
- src = igmp_src_lookup (group, skey);
- if (!src)
- goto done;
- /* check if this timer is valid */
- if (timer->exp_time != src->exp_time)
- {
- timer->exp_time = src->exp_time;
- igmp_sort_timers (im->timers);
- return;
- }
-
- ip46_address_t saddr;
- ip46_address_t gaddr;
- clib_memcpy (&saddr, skey->data, sizeof (ip46_address_t));
- clib_memcpy (&gaddr, gkey->data, sizeof (ip46_address_t));
-
- /* source timer expired, remove src */
- igmp_listen (vm, 0, timer->sw_if_index, saddr, gaddr, 0);
-done:
- clib_mem_free (timer->data);
- pool_put (im->timers, timer);
-}
-
-/** \brief igmp timer process
- @param vm - vlib main
- @param rt - vlib runtime node
- @param f - vlib frame
-
- Handle igmp timers.
-*/
-static uword
-igmp_timer_process (vlib_main_t * vm, vlib_node_runtime_t * rt,
- vlib_frame_t * f)
-{
- igmp_main_t *im = &igmp_main;
- uword *event_data = 0, event_type;
- f64 time_start;
- igmp_timer_t *timer = NULL;
- while (1)
{
- /* suspend util timer expires */
- if (NULL != timer)
- vlib_process_wait_for_event_or_clock (vm,
- timer->exp_time - time_start);
- else
- vlib_process_wait_for_event (vm);
- time_start = vlib_time_now (vm);
- event_type = vlib_process_get_events (vm, &event_data);
- vec_reset_length (event_data);
- if (event_type == IGMP_PROCESS_EVENT_UPDATE_TIMER)
- goto next_timer;
- IGMP_DBG ("time: %f", vlib_time_now (vm));
- /* timer expired */
- if (NULL != timer && timer->func != NULL)
- timer->func (vm, rt, im, timer);
- next_timer:
- timer = igmp_get_next_timer (im);
+ group = igmp_group_alloc (config, gaddr, mode);
+
+ /* new group implies create all sources */
+ vec_foreach (saddr, saddrs)
+ {
+ igmp_group_src_update (group, saddr, IGMP_MODE_HOST);
+ }
+
+ /*
+ * Send state changed event report for the group.
+ *
+ * RFC3376 Section 5.1
+ * "To cover the possibility of the State-Change Report being missed by
+ * one or more multicast routers, it is retransmitted [Robustness
+ * Variable] - 1 more times, at intervals chosen at random from the
+ * range (0, [Unsolicited Report Interval])."
+ */
+ igmp_send_state_change_group_report_v3 (config->sw_if_index, group);
+
+ igmp_timer_retire (&group->timers[IGMP_GROUP_TIMER_RESEND_REPORT]);
+
+ group->n_reports_sent = 1;
+ group->timers[IGMP_GROUP_TIMER_RESEND_REPORT] =
+ igmp_timer_schedule (igmp_timer_type_get (IGMP_TIMER_REPORT_INTERVAL),
+ igmp_group_index (group),
+ igmp_resend_state_change_group_report_v3, NULL);
}
- return 0;
-}
-
-/* *INDENT-OFF* */
-VLIB_REGISTER_NODE (igmp_timer_process_node) =
-{
- .function = igmp_timer_process,
- .type = VLIB_NODE_TYPE_PROCESS,
- .name = "igmp-timer-process",
- .n_next_nodes = IGMP_N_NEXT,
- .next_nodes = {
- [IGMP_NEXT_IP4_REWRITE_MCAST_NODE] = "ip4-rewrite-mcast",
- [IGMP_NEXT_IP6_REWRITE_MCAST_NODE] = "ip6-rewrite-mcast",
- }
-};
-/* *INDENT-ON* */
-
-int
-igmp_listen (vlib_main_t * vm, u8 enable, u32 sw_if_index,
- ip46_address_t saddr, ip46_address_t gaddr, u8 flags)
-{
- igmp_main_t *im = &igmp_main;
- igmp_config_t *config;
- igmp_group_t *group;
- igmp_src_t *src;
- igmp_key_t skey;
- igmp_key_t gkey;
-
- igmp_membership_group_v3_type_t group_type =
- (flags & IGMP_CONFIG_FLAG_CLI_API_CONFIGURED) ?
- IGMP_MEMBERSHIP_GROUP_change_to_filter_include :
- IGMP_MEMBERSHIP_GROUP_mode_is_filter_include;
- int rv = 0;
-
- /* set the lookup keys */
- skey.group_type = 0;
- gkey.group_type = group_type;
- clib_memcpy (&skey.data, &saddr, sizeof (ip46_address_t));
- clib_memcpy (&gkey.data, &gaddr, sizeof (ip46_address_t));
-
- if (enable)
+ else
{
- /* find configuration, if it dosn't exist, create new */
- config = igmp_config_lookup (im, sw_if_index);
- if (!config)
- {
- pool_get (im->configs, config);
- memset (config, 0, sizeof (igmp_config_t));
- config->sw_if_index = sw_if_index;
- config->igmp_group_by_key =
- hash_create_mem (0, sizeof (igmp_key_t), sizeof (uword));
- /* use IGMPv3 by default */
- config->igmp_ver = IGMP_V3;
- config->robustness_var = IGMP_DEFAULT_ROBUSTNESS_VARIABLE;
- config->flags |= IGMP_CONFIG_FLAG_QUERY_RESP_RECVED | flags;
-
- if ((flags & IGMP_CONFIG_FLAG_CLI_API_CONFIGURED) == 0)
- {
- /* create qery timer */
- igmp_create_int_timer (vlib_time_now (vm) + IGMP_QUERY_TIMER,
- sw_if_index, igmp_send_query);
- }
- config->adj_index =
- adj_mcast_add_or_lock (FIB_PROTOCOL_IP4, VNET_LINK_IP4,
- config->sw_if_index);
- hash_set (im->igmp_config_by_sw_if_index,
- config->sw_if_index, config - im->configs);
- }
- else if ((config->flags & IGMP_CONFIG_FLAG_CLI_API_CONFIGURED & flags)
- == 0)
+ IGMP_DBG ("... update (%U, %U) %U %U",
+ format_igmp_src_addr_list, saddrs,
+ format_igmp_key, gaddr,
+ format_vnet_sw_if_index_name, vnet_get_main (),
+ sw_if_index, format_igmp_filter_mode, mode);
+
+ /*
+ * RFC 3367 Section 5.1
+ *
+ * Old State New State State-Change Record Sent
+ * --------- --------- ------------------------
+ *
+ * 1) INCLUDE (A) INCLUDE (B) ALLOW (B-A), BLOCK (A-B)
+ * 2) EXCLUDE (A) EXCLUDE (B) ALLOW (A-B), BLOCK (B-A)
+ * 3) INCLUDE (A) EXCLUDE (B) TO_EX (B)
+ * 4) EXCLUDE (A) INCLUDE (B) TO_IN (B)
+ *
+ * N.B. We do not split state-change records for pending transfer
+ * hence there is no merge logic required.
+ */
+
+ if (IGMP_FILTER_MODE_INCLUDE == mode)
{
- rv = -2;
- goto error;
- }
- /* find igmp group, if it dosn't exist, create new */
- group = igmp_group_lookup (config, &gkey);
- if (!group)
- {
- pool_get (config->groups, group);
- memset (group, 0, sizeof (igmp_group_t));
- group->key = clib_mem_alloc (sizeof (igmp_key_t));
- clib_memcpy (group->key, &gkey, sizeof (igmp_key_t));
- clib_memcpy (&group->addr, &gaddr, sizeof (ip46_address_t));
- group->igmp_src_by_key =
- hash_create_mem (0, sizeof (igmp_key_t), sizeof (uword));
- group->n_srcs = 0;
- group->type = gkey.group_type;
- if (flags & IGMP_CONFIG_FLAG_CLI_API_CONFIGURED)
+ ip46_address_t *added, *removed;
+ igmp_pkt_build_report_t br;
+
+ /*
+ * find the list of sources that have been added and removed from
+ * the include set
+ */
+ removed =
+ igmp_group_present_minus_new (group, IGMP_FILTER_MODE_INCLUDE,
+ saddrs);
+ added =
+ igmp_group_new_minus_present (group, IGMP_FILTER_MODE_INCLUDE,
+ saddrs);
+
+ if (!(vec_len (added) || vec_len (removed)))
+ /* no change => done */
+ goto error;
+
+ igmp_pkt_build_report_init (&br, config->sw_if_index);
+
+ if (vec_len (added))
{
- /* create state-changed report timer with zero timeout */
- igmp_create_group_timer (0, sw_if_index, group->key,
- igmp_send_state_changed);
+ igmp_pkt_report_v3_add_report (&br,
+ group->key,
+ added,
+ IGMP_MEMBERSHIP_GROUP_allow_new_sources);
}
- hash_set_mem (config->igmp_group_by_key, group->key,
- group - config->groups);
- }
- /* find source, if it dosn't exist, create new */
- src = igmp_src_lookup (group, &skey);
- if (!src)
- {
- pool_get (group->srcs, src);
- memset (src, 0, sizeof (igmp_src_t));
- group->n_srcs += 1;
- src->key = clib_mem_alloc (sizeof (igmp_key_t));
- clib_memcpy (src->key, &skey, sizeof (igmp_key_t));
- clib_memcpy (&src->addr, &saddr, sizeof (ip46_address_t));
- if ((flags & IGMP_CONFIG_FLAG_CLI_API_CONFIGURED) == 0)
+ if (vec_len (removed))
{
- /* arm source timer (after expiration remove (S,G)) */
- igmp_event (im, config, group, src);
- src->exp_time = vlib_time_now (vm) + IGMP_SRC_TIMER;
- igmp_create_src_timer (src->exp_time, config->sw_if_index,
- group->key, src->key, igmp_src_exp);
+ igmp_pkt_report_v3_add_report (&br,
+ group->key,
+ removed,
+ IGMP_MEMBERSHIP_GROUP_block_old_sources);
}
- hash_set_mem (group->igmp_src_by_key, src->key, src - group->srcs);
- }
- else
- {
- rv = -1;
- goto error;
- }
- }
- else
- {
- config = igmp_config_lookup (im, sw_if_index);
- if (config)
- {
- gkey.group_type = IGMP_MEMBERSHIP_GROUP_mode_is_filter_include;
- group = igmp_group_lookup (config, &gkey);
- if (group)
- {
- src = igmp_src_lookup (group, &skey);
- if (src)
- {
- /* add source to block_all_sources group */
- igmp_key_t new_gkey;
- igmp_group_t *new_group;
+ IGMP_DBG ("... added %U", format_igmp_src_addr_list, added);
+ IGMP_DBG ("... removed %U", format_igmp_src_addr_list, removed);
- clib_memcpy (&new_gkey, &gkey, sizeof (igmp_key_t));
- new_gkey.group_type =
- IGMP_MEMBERSHIP_GROUP_block_old_sources;
- new_group = igmp_group_lookup (config, &new_gkey);
- if (!new_group)
- {
- pool_get (config->groups, new_group);
+ igmp_pkt_report_v3_send (&br);
- group = igmp_group_lookup (config, &gkey);
+ /*
+ * clear the group of the old sources and populate it with the new
+ * set requested
+ */
+ igmp_group_free_all_srcs (group);
- memset (new_group, 0, sizeof (igmp_group_t));
- new_group->key = clib_mem_alloc (sizeof (igmp_key_t));
- clib_memcpy (new_group->key, &new_gkey,
- sizeof (igmp_key_t));
- clib_memcpy (&new_group->addr, &group->addr,
- sizeof (ip46_address_t));
- new_group->igmp_src_by_key =
- hash_create_mem (0, sizeof (igmp_key_t),
- sizeof (uword));
- new_group->n_srcs = 0;
- new_group->type = new_gkey.group_type;
- hash_set_mem (config->igmp_group_by_key, new_group->key,
- new_group - config->groups);
- }
- igmp_src_t *new_src;
- new_src = igmp_src_lookup (new_group, &skey);
- if (!new_src)
- {
- pool_get (new_group->srcs, new_src);
- memset (new_src, 0, sizeof (igmp_src_t));
- new_group->n_srcs += 1;
- new_src->key = clib_mem_alloc (sizeof (igmp_key_t));
- clib_memcpy (new_src->key, src->key,
- sizeof (igmp_key_t));
- clib_memcpy (&new_src->addr, &src->addr,
- sizeof (ip46_address_t));
- hash_set_mem (new_group->igmp_src_by_key, new_src->key,
- new_src - new_group->srcs);
- }
+ vec_foreach (saddr, saddrs)
+ {
+ igmp_group_src_update (group, saddr, IGMP_MODE_HOST);
+ }
- /* notify all registered api clients */
- if ((flags & IGMP_CONFIG_FLAG_CLI_API_CONFIGURED) == 0)
- igmp_event (im, config, new_group, new_src);
- else
- igmp_create_group_timer (0, sw_if_index, new_group->key,
- igmp_send_state_changed);
- /* remove source form mode_is_filter_include group */
- hash_unset_mem (group->igmp_src_by_key, src->key);
- clib_mem_free (src->key);
- pool_put (group->srcs, src);
- group->n_srcs -= 1;
- if (group->n_srcs <= 0)
- igmp_clear_group (config, group);
- if (pool_elts (config->groups) <= 0)
- igmp_clear_config (config);
- }
- else
- {
- rv = -1;
- goto error;
- }
- }
- else
- {
- rv = -1;
- goto error;
- }
+ if (0 == igmp_group_n_srcs (group, mode))
+ igmp_group_clear (group);
+
+ vec_free (added);
+ vec_free (removed);
}
else
{
- rv = -1;
- goto error;
+ /*
+ * The control plane is excluding some sources.
+ * - First; check for those that are present in the include list
+ * - Second; check add them to the exlude list
+ *
+ * TODO
+ */
}
}
error:
- return rv;
+ return (rv);
}
/** \brief igmp hardware interface link up down
@@ -921,26 +310,149 @@ error:
If an interface goes down, remove its (S,G)s.
*/
-static clib_error_t *
-igmp_hw_interface_link_up_down (vnet_main_t * vnm, u32 hw_if_index, u32 flags)
+static walk_rc_t
+igmp_sw_if_down (vnet_main_t * vnm, u32 sw_if_index, void *ctx)
{
- igmp_main_t *im = &igmp_main;
igmp_config_t *config;
- clib_error_t *error = NULL;
- /* remove igmp from a down interface to prevent crashes... */
- config =
- igmp_config_lookup (im,
- vnet_get_hw_interface (vnm,
- hw_if_index)->sw_if_index);
- if (config)
+ config = igmp_config_lookup (sw_if_index);
+ IGMP_DBG ("down: %U",
+ format_vnet_sw_if_index_name, vnet_get_main (), sw_if_index);
+ if (NULL != config)
{
- if ((flags & VNET_HW_INTERFACE_FLAG_LINK_UP) == 0)
- igmp_clear_config (config);
+ igmp_clear_config (config);
}
+
+ return (WALK_CONTINUE);
+}
+
+static clib_error_t *
+igmp_hw_interface_link_up_down (vnet_main_t * vnm, u32 hw_if_index, u32 flags)
+{
+ clib_error_t *error = NULL;
+ /* remove igmp state from down interfaces */
+ if (!(flags & VNET_HW_INTERFACE_FLAG_LINK_UP))
+ vnet_hw_interface_walk_sw (vnm, hw_if_index, igmp_sw_if_down, NULL);
return error;
}
VNET_HW_INTERFACE_LINK_UP_DOWN_FUNCTION (igmp_hw_interface_link_up_down);
+int
+igmp_enable_disable (u32 sw_if_index, u8 enable, igmp_mode_t mode)
+{
+ igmp_config_t *config;
+ igmp_main_t *im = &igmp_main;
+ u32 mfib_index;
+ IGMP_DBG ("%s: %U", (enable ? "Enabled" : "Disabled"),
+ format_vnet_sw_if_index_name, vnet_get_main (), sw_if_index);
+
+ /* *INDENT-OFF* */
+ fib_route_path_t for_us_path =
+ {
+ .frp_proto = fib_proto_to_dpo (FIB_PROTOCOL_IP4),
+ .frp_addr = zero_addr,
+ .frp_sw_if_index = 0xffffffff,
+ .frp_fib_index = 0,
+ .frp_weight = 1,
+ .frp_flags = FIB_ROUTE_PATH_LOCAL,
+ };
+ fib_route_path_t via_itf_path =
+ {
+ .frp_proto = fib_proto_to_dpo (FIB_PROTOCOL_IP4),
+ .frp_addr = zero_addr,
+ .frp_sw_if_index = sw_if_index,
+ .frp_fib_index = 0,
+ .frp_weight = 1,
+ };
+ /* *INDENT-ON* */
+ /* find configuration, if it dosn't exist, create new */
+ config = igmp_config_lookup (sw_if_index);
+ mfib_index = mfib_table_get_index_for_sw_if_index (FIB_PROTOCOL_IP4,
+ sw_if_index);
+ if (!config && enable)
+ {
+ u32 ii;
+
+ vec_validate_init_empty (im->igmp_config_by_sw_if_index,
+ sw_if_index, ~0);
+ pool_get (im->configs, config);
+ memset (config, 0, sizeof (igmp_config_t));
+ config->sw_if_index = sw_if_index;
+ config->igmp_group_by_key =
+ hash_create_mem (0, sizeof (igmp_key_t), sizeof (uword));
+ config->robustness_var = IGMP_DEFAULT_ROBUSTNESS_VARIABLE;
+ config->mode = mode;
+
+ for (ii = 0; ii < IGMP_CONFIG_N_TIMERS; ii++)
+ config->timers[ii] = IGMP_TIMER_ID_INVALID;
+
+ if (IGMP_MODE_ROUTER == mode)
+ {
+ config->timers[IGMP_CONFIG_TIMER_GENERAL_QUERY] =
+ igmp_timer_schedule (igmp_timer_type_get (IGMP_TIMER_QUERY),
+ igmp_config_index (config),
+ igmp_send_general_query, NULL);
+ }
+
+ config->adj_index =
+ adj_mcast_add_or_lock (FIB_PROTOCOL_IP4, VNET_LINK_IP4,
+ config->sw_if_index);
+ im->igmp_config_by_sw_if_index[config->sw_if_index] =
+ (config - im->configs);
+ {
+ vec_validate (im->n_configs_per_mfib_index, mfib_index);
+ im->n_configs_per_mfib_index[mfib_index]++;
+ if (1 == im->n_configs_per_mfib_index[mfib_index])
+ {
+ /* first config in this FIB */
+ mfib_table_entry_path_update (mfib_index,
+ &mpfx_general_query,
+ MFIB_SOURCE_IGMP,
+ &for_us_path,
+ MFIB_ITF_FLAG_FORWARD);
+ mfib_table_entry_path_update (mfib_index,
+ &mpfx_report,
+ MFIB_SOURCE_IGMP,
+ &for_us_path,
+ MFIB_ITF_FLAG_FORWARD);
+ }
+ mfib_table_entry_path_update (mfib_index,
+ &mpfx_general_query,
+ MFIB_SOURCE_IGMP,
+ &via_itf_path, MFIB_ITF_FLAG_ACCEPT);
+ mfib_table_entry_path_update (mfib_index, &mpfx_report,
+ MFIB_SOURCE_IGMP, &via_itf_path,
+ MFIB_ITF_FLAG_ACCEPT);
+ }
+ }
+ else if (config && !enable)
+ {
+ vec_validate (im->n_configs_per_mfib_index, mfib_index);
+ im->n_configs_per_mfib_index[mfib_index]--;
+ if (0 == im->n_configs_per_mfib_index[mfib_index])
+ {
+ /* last config in this FIB */
+ mfib_table_entry_path_remove (mfib_index,
+ &mpfx_general_query,
+ MFIB_SOURCE_IGMP, &for_us_path);
+ mfib_table_entry_path_remove (mfib_index,
+ &mpfx_report,
+ MFIB_SOURCE_IGMP, &for_us_path);
+ }
+
+ mfib_table_entry_path_remove (mfib_index,
+ &mpfx_general_query,
+ MFIB_SOURCE_IGMP, &via_itf_path);
+ mfib_table_entry_path_remove (mfib_index,
+ &mpfx_report,
+ MFIB_SOURCE_IGMP, &via_itf_path);
+ igmp_clear_config (config);
+ im->igmp_config_by_sw_if_index[config->sw_if_index] = ~0;
+ hash_free (config->igmp_group_by_key);
+ pool_put (im->configs, config);
+ }
+
+ return (0);
+}
/** \brief igmp initialization
@param vm - vlib main
@@ -952,97 +464,24 @@ igmp_init (vlib_main_t * vm)
{
clib_error_t *error;
igmp_main_t *im = &igmp_main;
- int i;
+
if ((error = vlib_call_init_function (vm, ip4_lookup_init)))
return error;
- im->igmp_config_by_sw_if_index = hash_create (0, sizeof (u32));
- im->igmp_api_client_by_client_index = hash_create (0, sizeof (u32));
- ip4_register_protocol (IP_PROTOCOL_IGMP, igmp_input_node.index);
- igmp_type_info_t *ti;
- igmp_report_type_info_t *rti;
-#define igmp_type(n,s) \
-do { \
- vec_add2 (im->type_infos, ti, 1); \
- ti->type = n; \
- ti->name = (u8 *) #s; \
-} while (0);
-#define igmp_report_type(n,s) \
-do { \
- vec_add2 (im->report_type_infos, rti, 1); \
- rti->type = n; \
- rti->name = (u8 *) #s; \
-} while (0);
-#include "igmp.def"
-#undef igmp_type
-#undef igmp_report_type
- for (i = 0; i < vec_len (im->type_infos); i++)
- {
- ti = im->type_infos + i;
- hash_set (im->type_info_by_type, ti->type, i);
- }
-
- for (i = 0; i < vec_len (im->report_type_infos); i++)
- {
- rti = im->report_type_infos + i;
- hash_set (im->report_type_info_by_report_type, rti->type, i);
- }
- /* General Query address */
- ip46_address_t addr0 = {
- .as_u64[0] = 0,
- .as_u64[1] = 0
- };
- addr0.ip4.as_u32 = clib_host_to_net_u32 (IGMP_GENERAL_QUERY_ADDRESS);
-
- /* Report address */
- ip46_address_t addr1 = {
- .as_u64[0] = 0,
- .as_u64[1] = 0
- };
- addr1.ip4.as_u32 = clib_host_to_net_u32 (IGMP_MEMBERSHIP_REPORT_ADDRESS);
-
- fib_route_path_t path = {
- .frp_proto = fib_proto_to_dpo (FIB_PROTOCOL_IP4),
- .frp_addr = zero_addr,
- .frp_sw_if_index = 0xffffffff,
- .frp_fib_index = 0,
- .frp_weight = 0,
- .frp_flags = FIB_ROUTE_PATH_LOCAL,
- };
+ im->igmp_api_client_by_client_index = hash_create (0, sizeof (u32));
- const mfib_prefix_t mpfx0 = {
- .fp_proto = FIB_PROTOCOL_IP4,
- .fp_len = 32,
- .fp_grp_addr = addr0,
- };
+ im->logger = vlib_log_register_class ("igmp", 0);
- const mfib_prefix_t mpfx1 = {
- .fp_proto = FIB_PROTOCOL_IP4,
- .fp_len = 32,
- .fp_grp_addr = addr1,
- };
+ IGMP_DBG ("initialized");
- /* configure MFIB to accept IGMPv3 general query
- * and reports from all interfaces
- */
- mfib_table_entry_path_update (0, &mpfx0,
- MFIB_SOURCE_DEFAULT_ROUTE, &path,
- MFIB_ITF_FLAG_FORWARD);
- mfib_table_entry_path_update (0, &mpfx1,
- MFIB_SOURCE_DEFAULT_ROUTE, &path,
- MFIB_ITF_FLAG_FORWARD);
- mfib_table_entry_update (0, &mpfx0, MFIB_SOURCE_DEFAULT_ROUTE,
- 0, MFIB_ENTRY_FLAG_ACCEPT_ALL_ITF);
- mfib_table_entry_update (0, &mpfx1, MFIB_SOURCE_DEFAULT_ROUTE,
- 0, MFIB_ENTRY_FLAG_ACCEPT_ALL_ITF);
return (error);
}
VLIB_INIT_FUNCTION (igmp_init);
/* *INDENT-OFF* */
VLIB_PLUGIN_REGISTER () = {
- .version = VPP_BUILD_VER,
- .description = "IGMP messaging",
+ .version = VPP_BUILD_VER,
+ .description = "IGMP messaging",
};
/* *INDENT-ON* */
diff --git a/src/plugins/igmp/igmp.h b/src/plugins/igmp/igmp.h
index b8759ffa3b1..bf123ddbb79 100644
--- a/src/plugins/igmp/igmp.h
+++ b/src/plugins/igmp/igmp.h
@@ -23,436 +23,123 @@
#include <vlibapi/api_helper_macros.h>
#include <vnet/ip/igmp_packet.h>
#include <vnet/adj/adj_mcast.h>
+#include <igmp/igmp_types.h>
#include <igmp/igmp_format.h>
+#include <igmp/igmp_timer.h>
+#include <igmp/igmp_group.h>
+#include <igmp/igmp_config.h>
-#define IGMP_QUERY_TIMER (60)
-#define IGMP_SRC_TIMER (3 * IGMP_QUERY_TIMER)
+/**
+ * RFC 3376 Section 8.1
+ */
#define IGMP_DEFAULT_ROBUSTNESS_VARIABLE (2)
-#define ENABLE_IGMP_DBG 0
+#define IGMP_DBG(...) \
+ vlib_log_debug (igmp_main.logger, __VA_ARGS__);
-#if ENABLE_IGMP_DBG == 1
-#define IGMP_DBG(...) clib_warning(__VA_ARGS__)
+/**
+ * General Query address - 224.0.0.1
+ * Membership Report address - 224.0.0.22
+ * SSM default range 232/8
+ */
+#if CLIB_ARCH_IS_BIG_ENDIAN
+#define IGMP_GENERAL_QUERY_ADDRESS (0xE0000001)
+#define IGMP_MEMBERSHIP_REPORT_ADDRESS (0xE0000016)
+#define IGMP_SSM_DEFAULT (0xE8000000)
#else
-#define IGMP_DBG(...)
-#endif /* ENABLE_IGMP_DBG */
-
-/** General Query address - 224.0.0.1 */
-#define IGMP_GENERAL_QUERY_ADDRESS (0xE0000001)
-/** Membership Report address - 224.0.0.22 */
-#define IGMP_MEMBERSHIP_REPORT_ADDRESS (0xE0000016)
+#define IGMP_GENERAL_QUERY_ADDRESS (0x010000E0)
+#define IGMP_MEMBERSHIP_REPORT_ADDRESS (0x160000E0)
+#define IGMP_SSM_DEFAULT (0x000000E8)
+#endif
/** helper macro to get igmp mebership group from pointer plus offset */
-#define group_ptr(p, l) ((igmp_membership_group_v3_t *)((char*)p + l))
-
-#define foreach_igmp_config_flag \
- _(0, QUERY_RESP_RECVED, "query_response_received") \
- _(1, CAN_SEND_REPORT, "can_send_report") \
- _(2, CLI_API_CONFIGURED, "cli/api")
-
-typedef enum
-{
-#define _(a,b,c) IGMP_CONFIG_FLAG_##b = (1 << a),
- foreach_igmp_config_flag
-#undef _
-} igmp_config_flag_t;
-
-enum
-{
- IGMP_PROCESS_EVENT_UPDATE_TIMER = 1,
-} igmp_process_event_t;
-
-/*! Igmp versions */
-typedef enum
-{
- IGMP_V1,
- IGMP_V2,
- IGMP_V3,
-} igmp_ver_t;
-
-struct igmp_config_t_;
-
-typedef struct igmp_config_t_ igmp_config_t;
-
-struct igmp_group_t_;
-
-typedef struct igmp_group_t_ igmp_group_t;
-
-/** \brief create message
- @param b - vlib buffer
- @param config - igmp configuration
- @param group - igmp group
-
- Populate supplied bufefr with IGMP message.
-*/
-typedef void (create_msg_t) (vlib_buffer_t * b, igmp_config_t * config,
- igmp_group_t * group);
-
-/** \brief igmp key
- @param data - key data
- @param group_type - membership group type
-*/
-typedef struct
-{
- u64 data[2]; /*!< ip46_address_t.as_u64 */
- u64 group_type; /*!< zero in case of source key */
-} igmp_key_t;
-
-/** \brief igmp source
- @param addr - ip4/6 source address
- @param exp_time - expiration time
- @param key - pointer to key
-*/
-typedef struct
-{
- ip46_address_t addr;
-
- f64 exp_time;
-
- igmp_key_t *key;
-} igmp_src_t;
-
-/** \brief igmp group
- @param addr - ip4/6 group address
- @param exp_time - expiration time
- @param key - pointer to key
- @param type - membership group type
- @param n_srcs - number of sources
- @param flags - igmp group flags
- @param igmp_src_by_key - source by key hash
- @param srcs - pool of sources
-*/
-typedef struct igmp_group_t_
-{
- ip46_address_t addr;
-
- f64 exp_time;
-
- igmp_key_t *key;
-
- igmp_membership_group_v3_type_t type;
+#define group_ptr(p, l) ((igmp_membership_group_v3_t *)((u8*)(p) + (l)))
+#define group_cptr(p, l) ((const igmp_membership_group_v3_t *)((u8*)(p) + (l)))
- u16 n_srcs;
-
- u8 flags;
-/** reponse to query was received */
-#define IGMP_GROUP_FLAG_QUERY_RESP_RECVED (1 << 0)
-
- uword *igmp_src_by_key;
- igmp_src_t *srcs;
-} igmp_group_t;
-
-/** \brief igmp configuration
- @param sw_if_index - interface sw_if_index
- @param adj_index - adjacency index
- @param next_create_msg - specify next igmp message
- @param igmp_ver - igmp version
- @param robustness_var - robustness variable
- @param flags - igmp configuration falgs
- @param igmp_group_by_key - group by key hash
- @param groups - pool of groups
-*/
-typedef struct igmp_config_t_
-{
- u32 sw_if_index;
-
- adj_index_t adj_index;
-
- create_msg_t *next_create_msg;
-
- igmp_ver_t igmp_ver;
-
- u8 robustness_var;
-
- u8 flags;
-
- uword *igmp_group_by_key;
-
- igmp_group_t *groups;
-} igmp_config_t;
-
-struct igmp_timer_t_;
-
-typedef struct igmp_timer_t_ igmp_timer_t;
-
-typedef struct
-{
- u8 *name;
- igmp_type_t type;
-} igmp_type_info_t;
-
-typedef struct
-{
- u8 *name;
- igmp_membership_group_v3_type_t type;
-} igmp_report_type_info_t;
-
-/** \brief igmp main
- @param msg_id_base - API message ID base
- @param igmp_api_client_by_client_index - get api client by client_index
- @param api_clients - pool of api clients registered for join/leave notifications
- @param igmp_config_by_sw_if_index - get config index by config key
- @param configs - pool of igmp configurations
- @param buffers - buffer cache
- @param timers - pool of igmp timers
- @param type_infos - igmp type info
- @param report_type_infos - igmp report type info
- @param type_info_by_type -
- @param report_type_info_by_report_type -
- @param general_query_address - 224.0.0.1
- @param membership_report_address - 224.0.0.22
-*/
+/**
+ * collection of data related to IGMP
+ */
typedef struct igmp_main_t_
{
+ /**
+ * API base message ID
+ */
u16 msg_id_base;
uword *igmp_api_client_by_client_index;
+ /**
+ * API client registered for events
+ */
vpe_client_registration_t *api_clients;
- uword *igmp_config_by_sw_if_index;
+ /**
+ * per-interface DB of configs
+ */
+ u32 *igmp_config_by_sw_if_index;
- igmp_config_t *configs;
+ /**
+ * the number of igmp configs for each mfib_index (VRF)
+ */
+ u32 *n_configs_per_mfib_index;
- igmp_timer_t *timers;
+ /**
+ * logger - VLIB log class
+ */
+ vlib_log_class_t logger;
- igmp_type_info_t *type_infos;
- igmp_report_type_info_t *report_type_infos;
+ /**
+ * pool of configs
+ */
+ igmp_config_t *configs;
- uword *type_info_by_type;
- uword *report_type_info_by_report_type;
+ /**
+ * pool of groups
+ */
+ igmp_group_t *groups;
+ /**
+ * pool of sources
+ */
+ igmp_src_t *srcs;
} igmp_main_t;
extern igmp_main_t igmp_main;
-/** \brief igmp timer function
- @param vm - vlib main
- @param rt - vlib runtime node
- @param im - igmp main
- @param timer - igmp timer
-*/
-typedef void (igmp_timer_function_t) (vlib_main_t * vm,
- vlib_node_runtime_t * rt,
- igmp_main_t * im, igmp_timer_t * timer);
-
-/** \brief igmp timer
- @param exp_time - expiration time
- @param func - function to call on timer expiration
- @param sw_if_index - interface sw_if_index
- @param data - custom data
-*/
-typedef struct igmp_timer_t_
-{
- f64 exp_time;
- igmp_timer_function_t *func;
-
- u32 sw_if_index;
- void *data;
-} igmp_timer_t;
-
-extern vlib_node_registration_t igmp_timer_process_node;
-extern vlib_node_registration_t igmp_input_node;
-extern vlib_node_registration_t igmp_parse_query_node;
-extern vlib_node_registration_t igmp_parse_report_node;
-
-/** \brief igmp listen
- @param vm - vlib main
- @param enable - 0 == remove (S,G), else add (S,G)
- @param sw_if_index - interface sw_if_index
- @param saddr - source address
- @param gaddr - group address
- @param flags - igmp configuration flags
-
- Add/del (S,G) on an interface. If user configured,
- send a status change report from the interface.
- If a report was received on interface notify registered api clients.
-*/
-int igmp_listen (vlib_main_t * vm, u8 enable, u32 sw_if_index,
- ip46_address_t saddr, ip46_address_t gaddr, u8 flags);
-
-/** \brief igmp clear config
- @param config - igmp configuration
-
- Clear all (S,G)s on specified config and remove this config from pool.
-*/
-void igmp_clear_config (igmp_config_t * config);
-
-/** \brief igmp clear group
- @param config - igmp configuration
- @param group - the group to be removed
-
- Remove this group from interface (specified by configuration).
-*/
-void igmp_clear_group (igmp_config_t * config, igmp_group_t * group);
-
-/** \brief igmp sort timers
- @param timers - pool of igmp timers
-
- Sort igmp timers, so that the first to expire is at end.
-*/
-void igmp_sort_timers (igmp_timer_t * timers);
-
-/** \brief igmp create int timer
- @param time - expiration time (at this time the timer will expire)
- @param sw_if_index - interface sw_if_index
- @param func - function to all after timer expiration
-
-
- Creates new interface timer. Delayed reports, query msg, query resp.
-*/
-void igmp_create_int_timer (f64 time, u32 sw_if_index,
- igmp_timer_function_t * func);
-
-/** \brief igmp create group timer
- @param time - expiration time (at this time the timer will expire)
- @param sw_if_index - interface sw_if_index
- @param gkey - key to find the group by
- @param func - function to all after timer expiration
-
- Creates new group timer.
-*/
-void igmp_create_group_timer (f64 time, u32 sw_if_index, igmp_key_t * gkey,
- igmp_timer_function_t * func);
-
-/** \brief igmp create group timer
- @param time - expiration time (at this time the timer will expire)
- @param sw_if_index - interface sw_if_index
- @param gkey - key to find the group by
- @param skey - key to find the source by
- @param func - function to all after timer expiration
-
- Creates new source timer.
-*/
-void igmp_create_src_timer (f64 time, u32 sw_if_index, igmp_key_t * gkey,
- igmp_key_t * skey, igmp_timer_function_t * func);
-
-/** \brief igmp send query (igmp_timer_function_t)
-
- Send an igmp query.
- If the timer holds group key, send Group-Specific query,
- else send General query.
-*/
-void igmp_send_query (vlib_main_t * vm, vlib_node_runtime_t * rt,
- igmp_main_t * im, igmp_timer_t * timer);
-
-/** \brief igmp query response expiration (igmp_timer_function_t)
-
- If a response to a query didn't come in time, remove (S,G)s.
-*/
-void igmp_query_resp_exp (vlib_main_t * vm, vlib_node_runtime_t * rt,
- igmp_main_t * im, igmp_timer_t * timer);
-
-/** \brief igmp send report (igmp_timer_function_t)
-
- Send igmp membership report.
-*/
-void igmp_send_report (vlib_main_t * vm, vlib_node_runtime_t * rt,
- igmp_main_t * im, igmp_timer_t * timer);
-
-/** \brief igmp send state changed (igmp_timer_function_t)
-
- Send report if an (S,G) filter has changed.
-*/
-void igmp_send_state_changed (vlib_main_t * vm, vlib_node_runtime_t * rt,
- igmp_main_t * im, igmp_timer_t * timer);
-
-/** \brief igmp source expiration (igmp_timer_function_t)
-
- Remove expired (S,G) from group.
-*/
-void igmp_src_exp (vlib_main_t * vm, vlib_node_runtime_t * rt,
- igmp_main_t * im, igmp_timer_t * timer);
-
-static inline igmp_type_info_t *
-igmp_get_type_info (igmp_main_t * im, u32 type)
-{
- uword *p;
-
- p = hash_get (im->type_info_by_type, type);
- return p ? vec_elt_at_index (im->type_infos, p[0]) : 0;
-}
-
-static inline igmp_report_type_info_t *
-igmp_get_report_type_info (igmp_main_t * im, u8 report_type)
-{
- uword *p;
-
- p = hash_get (im->report_type_info_by_report_type, report_type);
- return p ? vec_elt_at_index (im->report_type_infos, p[0]) : 0;
-}
-
-/** \brief igmp event
- @param im - igmp main
- @param config - igmp configuration
- @param group - igmp group
- @param src - source
-
- Notify registered api clients of (S,G) filter update.
-*/
-void igmp_event (igmp_main_t * im, igmp_config_t * config,
- igmp_group_t * group, igmp_src_t * src);
-
-typedef enum
-{
- IGMP_NEXT_IP4_REWRITE_MCAST_NODE,
- IGMP_NEXT_IP6_REWRITE_MCAST_NODE,
- IGMP_N_NEXT,
-} igmp_next_t;
-
-/** \brief igmp config lookup
- @param im - igmp main
- @param sw_if_index - interface sw_if_index
-*/
-always_inline igmp_config_t *
-igmp_config_lookup (igmp_main_t * im, u32 sw_if_index)
-{
- uword *p;
- igmp_config_t *config = NULL;
-
- p = hash_get (im->igmp_config_by_sw_if_index, sw_if_index);
- if (p)
- config = vec_elt_at_index (im->configs, p[0]);
-
- return config;
-}
-
-/** \brief igmp group lookup
- @param config - igmp configuration
- @param key - igmp key
-*/
-always_inline igmp_group_t *
-igmp_group_lookup (igmp_config_t * config, igmp_key_t * key)
-{
- uword *p;
- igmp_group_t *group = NULL;
- if (!config)
- return NULL;
-
- p = hash_get_mem (config->igmp_group_by_key, key);
- if (p)
- group = vec_elt_at_index (config->groups, p[0]);
-
- return group;
-}
-
-/** \brief igmp group lookup
- @param group - igmp group
- @param key - igmp key
-*/
-always_inline igmp_src_t *
-igmp_src_lookup (igmp_group_t * group, igmp_key_t * key)
-{
- uword *p;
- igmp_src_t *src = NULL;
- if (!group)
- return NULL;
-
- p = hash_get_mem (group->igmp_src_by_key, key);
- if (p)
- src = vec_elt_at_index (group->srcs, p[0]);
-
- return src;
-}
+/**
+ * @brief IGMP interface enable/disable
+ * @param sw_if_index - Interface
+ * @param enable - enable/disable
+ * @param mode - Host or router
+ */
+int igmp_enable_disable (u32 sw_if_index, u8 enable, igmp_mode_t mode);
+
+/**
+ * @brief igmp listen
+ * Called by a host to request reception of multicast packets
+ * @param vm - vlib main
+ * @param filter - Filter mode
+ * @param sw_if_index - interface sw_if_index
+ * @param saddr - source address
+ * @param gaddr - group address
+ *
+ * Add/del (S,G) on an interface.
+ * send a status change report from the interface.
+ */
+int igmp_listen (vlib_main_t * vm,
+ igmp_filter_mode_t filter,
+ u32 sw_if_index,
+ const ip46_address_t * saddr, const ip46_address_t * gaddr);
+
+/**
+ * @brief Send an IGMP event to listening parties
+ * @param filter mode
+ * @param sw_if_index
+ * @param saddr
+ * @param gaddr
+ */
+void igmp_event (igmp_filter_mode_t filter,
+ u32 sw_if_index,
+ const ip46_address_t * saddr, const ip46_address_t * gaddr);
#endif /* _IGMP_H_ */
diff --git a/src/plugins/igmp/igmp_api.c b/src/plugins/igmp/igmp_api.c
index d283a0f3935..71fb3e22089 100644
--- a/src/plugins/igmp/igmp_api.c
+++ b/src/plugins/igmp/igmp_api.c
@@ -19,6 +19,8 @@
#include <vlibapi/api.h>
#include <vlibmemory/api.h>
+#include <vnet/ip/ip_types_api.h>
+#include <igmp/igmp_ssm_range.h>
/* define message IDs */
#include <igmp/igmp_msg_enum.h>
@@ -46,65 +48,76 @@
#include <vlibapi/api_helper_macros.h>
+#define IGMP_MSG_ID(_id) (_id + igmp_main.msg_id_base)
+
#define foreach_igmp_plugin_api_msg \
_(IGMP_LISTEN, igmp_listen) \
_(IGMP_ENABLE_DISABLE, igmp_enable_disable) \
-_(IGMP_DUMP, igmp_dump) \
-_(IGMP_CLEAR_INTERFACE, igmp_clear_interface) \
-_(WANT_IGMP_EVENTS, want_igmp_events) \
+_(IGMP_DUMP, igmp_dump) \
+_(IGMP_CLEAR_INTERFACE, igmp_clear_interface) \
+_(IGMP_CLEAR_INTERFACE, igmp_clear_interface) \
+_(IGMP_GROUP_PREFIX_SET, igmp_group_prefix_set) \
+_(IGMP_GROUP_PREFIX_DUMP, igmp_group_prefix_dump) \
+_(WANT_IGMP_EVENTS, want_igmp_events) \
static void
vl_api_igmp_listen_t_handler (vl_api_igmp_listen_t * mp)
{
vlib_main_t *vm = vlib_get_main ();
vnet_main_t *vnm = vnet_get_main ();
- igmp_main_t *im = &igmp_main;
vl_api_igmp_listen_reply_t *rmp;
- int rv = 0;
- ip46_address_t saddr, gaddr;
+ int ii, rv = 0;
+ ip46_address_t gaddr, *saddrs = NULL;
- if (!vnet_sw_interface_is_api_valid (vnm, ntohl (mp->sw_if_index)))
- {
- rv = VNET_API_ERROR_INVALID_SW_IF_INDEX;
- goto done;
- }
+ VALIDATE_SW_IF_INDEX (&mp->group);
- if ((vnet_sw_interface_get_flags (vnm, ntohl (mp->sw_if_index)) &&
+ if ((vnet_sw_interface_get_flags (vnm, ntohl (mp->group.sw_if_index)) &&
VNET_SW_INTERFACE_FLAG_ADMIN_UP) == 0)
{
+ // FIXME - don't we clear this state on interface down ...
rv = VNET_API_ERROR_UNEXPECTED_INTF_STATE;
goto done;
}
- clib_memcpy (&saddr.ip4.as_u8, mp->saddr, sizeof (u8) * 4);
- clib_memcpy (&gaddr.ip4.as_u8, mp->gaddr, sizeof (u8) * 4);
+ memset (&gaddr, 0, sizeof (gaddr));
+ clib_memcpy (&gaddr.ip4, &mp->group.gaddr, sizeof (ip4_address_t));
- rv =
- igmp_listen (vm, mp->enable, ntohl (mp->sw_if_index), saddr, gaddr,
- IGMP_CONFIG_FLAG_CLI_API_CONFIGURED);
+ vec_validate (saddrs, mp->group.n_srcs - 1);
-done:;
- unix_shared_memory_queue_t *q =
- vl_api_client_index_to_input_queue (mp->client_index);
- if (!q)
- return;
+ vec_foreach_index (ii, saddrs)
+ {
+ clib_memcpy (&saddrs[ii].ip4,
+ &mp->group.saddrs[ii], sizeof (ip4_address_t));
+ }
- rmp = vl_msg_api_alloc (sizeof (*rmp));
- rmp->_vl_msg_id = htons ((VL_API_IGMP_LISTEN_REPLY) + im->msg_id_base);
- rmp->context = mp->context;
- rmp->retval = htonl (rv);
+ rv = igmp_listen (vm,
+ (mp->group.filter ?
+ IGMP_FILTER_MODE_INCLUDE :
+ IGMP_FILTER_MODE_EXCLUDE),
+ ntohl (mp->group.sw_if_index), saddrs, &gaddr);
- vl_msg_api_send_shmem (q, (u8 *) & rmp);
+ vec_free (saddrs);
+
+ BAD_SW_IF_INDEX_LABEL;
+done:;
+ REPLY_MACRO (IGMP_MSG_ID (VL_API_IGMP_LISTEN_REPLY));
}
static void
vl_api_igmp_enable_disable_t_handler (vl_api_igmp_enable_disable_t * mp)
{
vl_api_igmp_enable_disable_reply_t *rmp;
- igmp_main_t *im = &igmp_main;
int rv = 0;
- REPLY_MACRO (VL_API_IGMP_ENABLE_DISABLE_REPLY + im->msg_id_base);
+ VALIDATE_SW_IF_INDEX (mp);
+
+ rv = igmp_enable_disable (ntohl (mp->sw_if_index),
+ mp->enable,
+ (mp->mode ? IGMP_MODE_HOST : IGMP_MODE_ROUTER));
+
+ BAD_SW_IF_INDEX_LABEL;
+
+ REPLY_MACRO (IGMP_MSG_ID (VL_API_IGMP_ENABLE_DISABLE_REPLY));
}
static void
@@ -117,89 +130,164 @@ send_igmp_details (unix_shared_memory_queue_t * q, igmp_main_t * im,
mp = vl_msg_api_alloc (sizeof (*mp));
memset (mp, 0, sizeof (*mp));
- mp->_vl_msg_id = htons (VL_API_IGMP_DETAILS + im->msg_id_base);
+ mp->_vl_msg_id = htons (IGMP_MSG_ID (VL_API_IGMP_DETAILS));
mp->context = context;
mp->sw_if_index = htonl (config->sw_if_index);
- clib_memcpy (mp->saddr, &src->addr.ip4, sizeof (u8) * 4);
- clib_memcpy (mp->gaddr, &group->addr.ip4, sizeof (u8) * 4);
+ clib_memcpy (mp->saddr.address, &src->key->ip4, sizeof (src->key->ip4));
+ clib_memcpy (mp->gaddr.address, &group->key->ip4, sizeof (group->key->ip6));
vl_msg_api_send_shmem (q, (u8 *) & mp);
}
static void
+igmp_config_dump (igmp_main_t * im,
+ unix_shared_memory_queue_t * q,
+ u32 context, igmp_config_t * config)
+{
+ igmp_group_t *group;
+ igmp_src_t *src;
+
+ /* *INDENT-OFF* */
+ FOR_EACH_GROUP (group, config,
+ ({
+ FOR_EACH_SRC (src, group, IGMP_FILTER_MODE_INCLUDE,
+ ({
+ send_igmp_details (q, im, config, group, src, context);
+ }));
+ }));
+ /* *INDENT-ON* */
+}
+
+static void
vl_api_igmp_dump_t_handler (vl_api_igmp_dump_t * mp)
{
+ unix_shared_memory_queue_t *q;
igmp_main_t *im = &igmp_main;
igmp_config_t *config;
- igmp_group_t *group;
- igmp_src_t *src;
+ u32 sw_if_index;
- unix_shared_memory_queue_t *q =
- vl_api_client_index_to_input_queue (mp->client_index);
+ q = vl_api_client_index_to_input_queue (mp->client_index);
if (!q)
return;
- if (mp->dump_all)
+ sw_if_index = ntohl (mp->sw_if_index);
+ if (~0 == sw_if_index)
{
/* *INDENT-OFF* */
- pool_foreach (config, im->configs, (
- {
- pool_foreach (group, config->groups, (
- {
- pool_foreach (src, group->srcs, (
- {
- send_igmp_details (q, im, config, group, src, mp->context);
- }));
- }));
+ pool_foreach (config, im->configs,
+ ({
+ igmp_config_dump(im, q, mp->context, config);
}));
/* *INDENT-ON* */
- return;
}
- config = igmp_config_lookup (im, ntohl (mp->sw_if_index));
- if (config)
+ else
{
- /* *INDENT-OFF* */
- pool_foreach (group, config->groups, (
+ config = igmp_config_lookup (sw_if_index);
+ if (config)
{
- pool_foreach (src, group->srcs, (
- {
- send_igmp_details (q, im, config, group, src, mp->context);
- }));
- }));
- /* *INDENT-ON* */
+ igmp_config_dump (im, q, mp->context, config);
+ }
}
}
static void
vl_api_igmp_clear_interface_t_handler (vl_api_igmp_clear_interface_t * mp)
{
- igmp_main_t *im = &igmp_main;
- igmp_config_t *config;
vl_api_igmp_clear_interface_reply_t *rmp;
+ igmp_config_t *config;
int rv = 0;
- config = igmp_config_lookup (im, ntohl (mp->sw_if_index));
+ config = igmp_config_lookup (ntohl (mp->sw_if_index));
if (config)
igmp_clear_config (config);
- unix_shared_memory_queue_t *q =
- vl_api_client_index_to_input_queue (mp->client_index);
+ REPLY_MACRO (IGMP_MSG_ID (VL_API_IGMP_CLEAR_INTERFACE_REPLY));
+}
+
+static vl_api_group_prefix_type_t
+igmp_group_type_int_to_api (igmp_group_prefix_type_t t)
+{
+ switch (t)
+ {
+ case IGMP_GROUP_PREFIX_TYPE_ASM:
+ return (htonl (ASM));
+ case IGMP_GROUP_PREFIX_TYPE_SSM:
+ return (htonl (SSM));
+ }
+
+ return (SSM);
+}
+
+static igmp_group_prefix_type_t
+igmp_group_type_api_to_int (vl_api_group_prefix_type_t t)
+{
+ switch (htonl (t))
+ {
+ case ASM:
+ return (IGMP_GROUP_PREFIX_TYPE_ASM);
+ case SSM:
+ return (IGMP_GROUP_PREFIX_TYPE_SSM);
+ }
+
+ return (IGMP_GROUP_PREFIX_TYPE_SSM);
+}
+
+static void
+vl_api_igmp_group_prefix_set_t_handler (vl_api_igmp_group_prefix_set_t * mp)
+{
+ vl_api_igmp_group_prefix_set_reply_t *rmp;
+ fib_prefix_t pfx;
+ int rv = 0;
+
+ ip_prefix_decode (&mp->gp.prefix, &pfx);
+ igmp_group_prefix_set (&pfx, igmp_group_type_api_to_int (mp->gp.type));
+
+ REPLY_MACRO (IGMP_MSG_ID (VL_API_IGMP_GROUP_PREFIX_SET_REPLY));
+}
+
+typedef struct igmp_ssm_range_walk_ctx_t_
+{
+ unix_shared_memory_queue_t *q;
+ u32 context;
+} igmp_ssm_range_walk_ctx_t;
+
+static walk_rc_t
+igmp_ssm_range_walk_dump (const fib_prefix_t * pfx,
+ igmp_group_prefix_type_t type, void *args)
+{
+ igmp_ssm_range_walk_ctx_t *ctx = args;
+ vl_api_igmp_group_prefix_details_t *mp;
+
+ mp = vl_msg_api_alloc (sizeof (*mp));
+ memset (mp, 0, sizeof (*mp));
+
+ mp->_vl_msg_id = htons (IGMP_MSG_ID (VL_API_IGMP_DETAILS));
+ mp->context = ctx->context;
+ mp->gp.type = igmp_group_type_int_to_api (type);
+ ip_prefix_encode (pfx, &mp->gp.prefix);
+
+ vl_msg_api_send_shmem (ctx->q, (u8 *) & mp);
+
+ return (WALK_CONTINUE);
+}
+
+static void
+vl_api_igmp_group_prefix_dump_t_handler (vl_api_igmp_dump_t * mp)
+{
+ unix_shared_memory_queue_t *q;
+
+ q = vl_api_client_index_to_input_queue (mp->client_index);
if (!q)
return;
- rmp = vl_msg_api_alloc (sizeof (*rmp));
- rmp->_vl_msg_id =
- htons ((VL_API_IGMP_CLEAR_INTERFACE_REPLY) + im->msg_id_base);
- rmp->context = mp->context;
- rmp->retval = htonl (rv);
+ igmp_ssm_range_walk_ctx_t ctx = {
+ .q = q,
+ .context = mp->context,
+ };
- vl_msg_api_send_shmem (q, (u8 *) & rmp);
+ igmp_ssm_range_walk (igmp_ssm_range_walk_dump, &ctx);
}
-/** \brief igmp group lookup
- @param im - igmp main
- @param client_index - client index
-*/
static vpe_client_registration_t *
igmp_api_client_lookup (igmp_main_t * im, u32 client_index)
{
@@ -247,17 +335,7 @@ vl_api_want_igmp_events_t_handler (vl_api_want_igmp_events_t * mp)
rv = VNET_API_ERROR_INVALID_REGISTRATION;
done:;
- unix_shared_memory_queue_t *q =
- vl_api_client_index_to_input_queue (mp->client_index);
- if (!q)
- return;
-
- rmp = vl_msg_api_alloc (sizeof (*rmp));
- rmp->_vl_msg_id = htons ((VL_API_WANT_IGMP_EVENTS_REPLY) + im->msg_id_base);
- rmp->context = mp->context;
- rmp->retval = htonl (rv);
-
- vl_msg_api_send_shmem (q, (u8 *) & rmp);
+ REPLY_MACRO (VL_API_WANT_IGMP_EVENTS_REPLY + im->msg_id_base);
}
static clib_error_t *
@@ -281,47 +359,51 @@ want_igmp_events_reaper (u32 client_index)
VL_MSG_API_REAPER_FUNCTION (want_igmp_events_reaper);
void
-send_igmp_event (unix_shared_memory_queue_t * q, u32 context,
- igmp_main_t * im, igmp_config_t * config,
- igmp_group_t * group, igmp_src_t * src)
+send_igmp_event (unix_shared_memory_queue_t * q,
+ u32 context,
+ igmp_filter_mode_t filter,
+ u32 sw_if_index,
+ const ip46_address_t * saddr, const ip46_address_t * gaddr)
{
vl_api_igmp_event_t *mp = vl_msg_api_alloc (sizeof (*mp));
memset (mp, 0, sizeof (*mp));
- mp->_vl_msg_id = ntohs ((VL_API_IGMP_EVENT) + im->msg_id_base);
+ mp->_vl_msg_id = ntohs ((VL_API_IGMP_EVENT) + igmp_main.msg_id_base);
mp->context = context;
- mp->sw_if_index = htonl (config->sw_if_index);
- clib_memcpy (&mp->saddr, &src->addr.ip4, sizeof (ip4_address_t));
- clib_memcpy (&mp->gaddr, &group->addr.ip4, sizeof (ip4_address_t));
- mp->is_join =
- (group->type == IGMP_MEMBERSHIP_GROUP_mode_is_filter_include) ? 1 : 0;
+ mp->sw_if_index = htonl (sw_if_index);
+ mp->filter = htonl (filter);
+ clib_memcpy (&mp->saddr, &saddr->ip4, sizeof (ip4_address_t));
+ clib_memcpy (&mp->gaddr, &gaddr->ip4, sizeof (ip4_address_t));
vl_msg_api_send_shmem (q, (u8 *) & mp);
}
void
-igmp_event (igmp_main_t * im, igmp_config_t * config, igmp_group_t * group,
- igmp_src_t * src)
+igmp_event (igmp_filter_mode_t filter,
+ u32 sw_if_index,
+ const ip46_address_t * saddr, const ip46_address_t * gaddr)
{
vpe_client_registration_t *api_client;
unix_shared_memory_queue_t *q;
+ igmp_main_t *im;
+
+ im = &igmp_main;
+
+ IGMP_DBG ("event: (%U, %U) %U %U",
+ format_ip46_address, saddr, IP46_TYPE_ANY,
+ format_ip46_address, saddr, IP46_TYPE_ANY,
+ format_vnet_sw_if_index_name,
+ vnet_get_main (), sw_if_index, format_igmp_filter_mode, filter);
+
+
/* *INDENT-OFF* */
pool_foreach (api_client, im->api_clients,
({
q = vl_api_client_index_to_input_queue (api_client->client_index);
if (q)
- send_igmp_event (q, 0, im, config, group, src);
+ send_igmp_event (q, 0, filter, sw_if_index, saddr, gaddr);
}));
/* *INDENT-ON* */
- if (group->type == IGMP_MEMBERSHIP_GROUP_block_old_sources)
- {
- igmp_clear_group (config, group);
- if (pool_elts (config->groups) == 0)
- {
- hash_unset (im->igmp_config_by_sw_if_index, config->sw_if_index);
- pool_put (im->configs, config);
- }
- }
}
#define vl_msg_name_crc_list
diff --git a/src/plugins/igmp/igmp_api.h b/src/plugins/igmp/igmp_api.h
new file mode 100644
index 00000000000..cfeab920c1d
--- /dev/null
+++ b/src/plugins/igmp/igmp_api.h
@@ -0,0 +1,58 @@
+/*
+ *------------------------------------------------------------------
+ * Copyright (c) 2017 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.
+ *------------------------------------------------------------------
+ */
+
+#ifndef _IGMP_API_H_
+#define _IGMP_API_H_
+
+#include <vlib/vlib.h>
+#include <vnet/ip/ip.h>
+
+/**
+ * @brief IGMP interface enable/disable
+ * Called by a router to enable/disable the reception of IGMP messages
+ * @param sw_if_index - Interface
+ * @param enable - enable/disable
+ * @param mode - Host (1) or router (0)
+ */
+int igmp_enable_disable (u32 sw_if_index, u8 enable, u8 mode);
+
+/**
+ * @brief igmp listen (RFC3376 Section 2).
+ * @param vm - vlib main
+ * @param enable - 0 == remove (S,G), else add (S,G), aka. include/exclue
+ * @param sw_if_index - interface sw_if_index
+ * @param saddr - source address
+ * @param gaddr - group address
+ * @param cli_api_configured - if zero, an igmp report has been received on interface
+ *
+ * Add/del (S,G) on an interface. If user configured,
+ * send a status change report from the interface.
+ * If a report was received on interface notify registered api clients.
+ */
+int igmp_listen (vlib_main_t * vm, u8 enable, u32 sw_if_index,
+ const ip46_address_t * saddr, const ip46_address_t * gaddr);
+
+
+#endif /* _IGMP_API_H_ */
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/igmp/cli.c b/src/plugins/igmp/igmp_cli.c
index 42f5932a7f2..5f09589c644 100644
--- a/src/plugins/igmp/cli.c
+++ b/src/plugins/igmp/igmp_cli.c
@@ -37,7 +37,6 @@ igmp_clear_interface_command_fn (vlib_main_t * vm, unformat_input_t * input,
vnet_main_t *vnm = vnet_get_main ();
u32 sw_if_index;
- igmp_main_t *im = &igmp_main;
igmp_config_t *config;
if (!unformat_user (input, unformat_line_input, line_input))
@@ -61,7 +60,7 @@ igmp_clear_interface_command_fn (vlib_main_t * vm, unformat_input_t * input,
}
}
- config = igmp_config_lookup (im, sw_if_index);
+ config = igmp_config_lookup (sw_if_index);
if (config)
igmp_clear_config (config);
@@ -128,8 +127,8 @@ igmp_listen_command_fn (vlib_main_t * vm, unformat_input_t * input,
goto done;
}
- rv = igmp_listen (vm, enable, sw_if_index, saddr, gaddr,
- IGMP_CONFIG_FLAG_CLI_API_CONFIGURED);
+ rv = igmp_listen (vm, enable, sw_if_index, &saddr, &gaddr);
+
if (rv == -1)
{
if (enable)
@@ -170,18 +169,19 @@ igmp_show_command_fn (vlib_main_t * vm, unformat_input_t * input,
igmp_src_t *src;
/* *INDENT-OFF* */
- pool_foreach (config, im->configs, (
- {
+ pool_foreach (config, im->configs,
+ ({
vlib_cli_output (vm, "interface: %U", format_vnet_sw_if_index_name,
- vnm, config->sw_if_index);
- pool_foreach (group, config->groups, (
- {
- vlib_cli_output (vm, "\t%U:%U", format_igmp_report_type, group->type, format_ip46_address, &group->addr, ip46_address_is_ip4 (&group->addr));
- pool_foreach (src, group->srcs, (
- {
- vlib_cli_output (vm, "\t\t%U", format_ip46_address, &src->addr, ip46_address_is_ip4 (&src->addr));
- }));
- }));
+ vnm, config->sw_if_index);
+
+ FOR_EACH_GROUP (group, config,
+ ({
+ vlib_cli_output (vm, "\t%U", format_igmp_key, group->key);
+ FOR_EACH_SRC (src, group, IGMP_FILTER_MODE_INCLUDE,
+ ({
+ vlib_cli_output (vm, "\t\t%U", format_igmp_key, src->key);
+ }));
+ }));
}));
/* *INDENT-ON* */
@@ -196,6 +196,56 @@ VLIB_CLI_COMMAND (igmp_show_command, static) = {
};
/* *INDENT-ON* */
+static clib_error_t *
+igmp_show_timers_command_fn (vlib_main_t * vm,
+ unformat_input_t * input,
+ vlib_cli_command_t * cmd)
+{
+#define _(n,f) vlib_cli_output (vm, "%s: %d", #f, igmp_timer_type_get(n));
+ foreach_igmp_timer_type
+#undef _
+ return (NULL);
+}
+
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (igmp_show_timers_command, static) = {
+ .path = "show igmp timers",
+ .short_help = "show igmp timers",
+ .function = igmp_show_timers_command_fn,
+};
+/* *INDENT-ON* */
+
+static clib_error_t *
+test_igmp_command_fn (vlib_main_t * vm,
+ unformat_input_t * input, vlib_cli_command_t * cmd)
+{
+ clib_error_t *error = NULL;
+ u32 value;
+
+ while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+ {
+ if (unformat (input, "query %d", &value))
+ igmp_timer_type_set (IGMP_TIMER_QUERY, value);
+ else if (unformat (input, "src %d", &value))
+ igmp_timer_type_set (IGMP_TIMER_SRC, value);
+ else if (unformat (input, "leave %d", &value))
+ igmp_timer_type_set (IGMP_TIMER_LEAVE, value);
+ else
+ error = clib_error_return (0, "query or src timers only");
+ }
+
+ return error;
+}
+
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (test_igmp_command, static) = {
+ .path = "test igmp timers",
+ .short_help = "Change the default values for IGMP timers - only sensible during unit tests",
+ .function = test_igmp_command_fn,
+};
+/* *INDENT-ON* */
+
+
clib_error_t *
igmp_cli_init (vlib_main_t * vm)
{
diff --git a/src/plugins/igmp/igmp_config.c b/src/plugins/igmp/igmp_config.c
new file mode 100644
index 00000000000..76e8ace14d9
--- /dev/null
+++ b/src/plugins/igmp/igmp_config.c
@@ -0,0 +1,96 @@
+/*
+ *------------------------------------------------------------------
+ * 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_config.h>
+#include <igmp/igmp.h>
+
+void
+igmp_clear_config (igmp_config_t * config)
+{
+ igmp_group_t *group;
+ u32 ii;
+
+ IGMP_DBG ("clear-config: %U",
+ format_vnet_sw_if_index_name,
+ vnet_get_main (), config->sw_if_index);
+
+ /* *INDENT-OFF* */
+ FOR_EACH_GROUP (group, config,
+ ({
+ igmp_group_clear (group);
+ }));
+ /* *INDENT-ON* */
+
+ for (ii = 0; ii < IGMP_CONFIG_N_TIMERS; ii++)
+ {
+ igmp_timer_retire (&config->timers[ii]);
+ }
+}
+
+igmp_config_t *
+igmp_config_lookup (u32 sw_if_index)
+{
+ igmp_main_t *im;
+
+ im = &igmp_main;
+
+ if (vec_len (im->igmp_config_by_sw_if_index) > sw_if_index)
+ {
+ u32 index;
+
+ index = im->igmp_config_by_sw_if_index[sw_if_index];
+
+ if (~0 != index)
+ return (vec_elt_at_index (im->configs, index));
+ }
+ return NULL;
+}
+
+u32
+igmp_config_index (const igmp_config_t * c)
+{
+ return (c - igmp_main.configs);
+}
+
+igmp_config_t *
+igmp_config_get (u32 index)
+{
+ return (pool_elt_at_index (igmp_main.configs, index));
+}
+
+igmp_group_t *
+igmp_group_lookup (igmp_config_t * config, const igmp_key_t * key)
+{
+ uword *p;
+ igmp_group_t *group = NULL;
+ if (!config)
+ return NULL;
+
+ p = hash_get_mem (config->igmp_group_by_key, key);
+ if (p)
+ group = pool_elt_at_index (igmp_main.groups, p[0]);
+
+ return group;
+}
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/igmp/igmp_config.h b/src/plugins/igmp/igmp_config.h
new file mode 100644
index 00000000000..ffd3dea9b28
--- /dev/null
+++ b/src/plugins/igmp/igmp_config.h
@@ -0,0 +1,128 @@
+/*
+ *------------------------------------------------------------------
+ * 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.
+ *------------------------------------------------------------------
+ */
+
+#ifndef __IGMP_CONFIG_H__
+#define __IGMP_CONFIG_H__
+
+#include <igmp/igmp_types.h>
+#include <igmp/igmp_timer.h>
+#include <igmp/igmp_group.h>
+
+typedef enum igmp_config_timer_type_t_
+{
+ /**
+ * On expiry send a general report
+ */
+ IGMP_CONFIG_TIMER_GENERAL_REPORT,
+
+ /**
+ * On expiry send a general query
+ */
+ IGMP_CONFIG_TIMER_GENERAL_QUERY,
+} igmp_config_timer_type_t;
+
+#define IGMP_CONFIG_N_TIMERS (IGMP_CONFIG_TIMER_GENERAL_QUERY + 1)
+
+/**
+ * @brief IGMP interface configuration
+*/
+typedef struct igmp_config_t_
+{
+ /**
+ * @param sw_if_index - interface sw_if_index
+ */
+ u32 sw_if_index;
+
+ /**
+ * @param adj_index - multicast adjacency index on the link
+ */
+ adj_index_t adj_index;
+
+ /**
+ * @param moe - host or router
+ */
+ igmp_mode_t mode;
+
+ /**
+ * Robustness variable (seciotn 5.1)
+ */
+ u8 robustness_var;
+
+ /**
+ * Database of groups joined on the link
+ */
+ uword *igmp_group_by_key;
+
+ /**
+ * A vector of scheduled query-respone timers
+ */
+ igmp_timer_id_t timers[IGMP_CONFIG_N_TIMERS];
+} igmp_config_t;
+
+#define FOR_EACH_GROUP(_group, _config, _body) \
+do { \
+ igmp_key_t *__key__; \
+ u32 __gid__; \
+ hash_foreach_mem(__key__, __gid__, _config->igmp_group_by_key, \
+ ({ \
+ _group = pool_elt_at_index(igmp_main.groups, __gid__); \
+ do { _body; } while (0); \
+ })); \
+ } while (0);
+
+/**
+ * @brief igmp clear config
+ * @param config - igmp configuration
+ *
+ * Clear all (S,G)s on specified config and remove this config from pool.
+ */
+extern void igmp_clear_config (igmp_config_t * config);
+
+/**
+ * @brief igmp config lookup
+ * @param im - igmp main
+ * @param sw_if_index - interface sw_if_index
+ */
+extern igmp_config_t *igmp_config_lookup (u32 sw_if_index);
+
+/**
+ * Get the pool index for a config
+ */
+extern u32 igmp_config_index (const igmp_config_t * c);
+
+/**
+ * Get the config from the pool index
+ */
+extern igmp_config_t *igmp_config_get (u32 index);
+
+/**
+ * @brief igmp group lookup
+ * @param config - igmp configuration
+ * @param key - igmp key
+*/
+extern igmp_group_t *igmp_group_lookup (igmp_config_t * config,
+ const igmp_key_t * key);
+
+#endif
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/igmp/error.h b/src/plugins/igmp/igmp_error.h
index faabfc1b03c..fbd0dc434f0 100644
--- a/src/plugins/igmp/error.h
+++ b/src/plugins/igmp/igmp_error.h
@@ -24,7 +24,7 @@
_ (INVALID_PROTOCOL, "invalid ip4 protocol") \
_ (BAD_CHECKSUM, "bad checksum") \
_ (UNKNOWN_TYPE, "unknown igmp message type") \
- _ (CLI_API_CONFIG, "CLI/API configured (S,G)s on interface") \
+ _ (NOT_ENABLED, "IGMP not enabled on this interface") \
typedef enum
{
diff --git a/src/plugins/igmp/igmp_format.c b/src/plugins/igmp/igmp_format.c
index fb4cc997c23..1ae04788a97 100644
--- a/src/plugins/igmp/igmp_format.c
+++ b/src/plugins/igmp/igmp_format.c
@@ -21,28 +21,44 @@
u8 *
format_igmp_type (u8 * s, va_list * args)
{
- igmp_type_t type = va_arg (*args, igmp_type_t);
- igmp_main_t *im = &igmp_main;
- igmp_type_info_t *ti = igmp_get_type_info (im, type);
+ igmp_type_t type = va_arg (*args, int);
- if (ti)
- return format (s, "%s", ti->name);
- else
- return format (s, "unknown %d", type);
+ switch (type)
+ {
+#define _(n,f) case IGMP_TYPE_##f: return (format (s, "%s", #f));
+ foreach_igmp_type
+#undef _
+ }
+ return format (s, "unknown:%d", type);
}
u8 *
-format_igmp_report_type (u8 * s, va_list * args)
+format_igmp_membership_group_type (u8 * s, va_list * args)
{
- igmp_membership_group_v3_type_t report_type =
- va_arg (*args, igmp_membership_group_v3_type_t);
- igmp_main_t *im = &igmp_main;
- igmp_report_type_info_t *rti = igmp_get_report_type_info (im, report_type);
+ igmp_membership_group_v3_type_t type = va_arg (*args, int);
+
+ switch (type)
+ {
+#define _(n,f) case IGMP_MEMBERSHIP_GROUP_##f: return (format (s, "%s", #f));
+ foreach_igmp_membership_group_v3_type
+#undef _
+ }
+ return (format (s, "unknown:%d", type));
+}
+
+u8 *
+format_igmp_filter_mode (u8 * s, va_list * args)
+{
+ igmp_filter_mode_t mode = va_arg (*args, igmp_filter_mode_t);
+
+ switch (mode)
+ {
+#define _(n,f) case IGMP_FILTER_MODE_##f: return (format (s, "%s", #f));
+ foreach_igmp_filter_mode
+#undef _
+ }
+ return (format (s, "unknown:%d", mode));
- if (rti)
- return format (s, "%s", rti->name);
- else
- return format (s, "unknown %d", report_type);
}
u8 *
@@ -92,8 +108,8 @@ format_igmp_report_v3 (u8 * s, va_list * args)
group = group_ptr (igmp, len);
s =
format (s, "\n%U%U: %U, sources %u", format_white_space, indent,
- format_igmp_report_type, group->type, format_ip4_address,
- &group->dst_address,
+ format_igmp_membership_group_type, group->type,
+ format_ip4_address, &group->group_address,
clib_net_to_host_u16 (group->n_src_addresses));
indent += 2;
for (j = 0; j < clib_net_to_host_u16 (group->n_src_addresses); j++)
@@ -129,17 +145,18 @@ format_igmp_query_v3 (u8 * s, va_list * args)
ip4_address_t tmp;
tmp.as_u32 = 0;
- if ((!ip4_address_compare (&igmp->dst, &tmp))
+ if ((!ip4_address_compare (&igmp->group_address, &tmp))
&& (igmp->n_src_addresses == 0))
s = format (s, "%UGeneral Query", format_white_space, indent);
else if (igmp->n_src_addresses == 0)
s = format (s, "%UGroup-Specific Query: %U", format_white_space, indent,
- format_ip4_address, &igmp->dst);
+ format_ip4_address, &igmp->group_address);
else
{
s =
format (s, "%UGroup-and-Source-Specific Query: %U",
- format_white_space, indent, format_ip4_address, &igmp->dst);
+ format_white_space, indent, format_ip4_address,
+ &igmp->group_address);
indent += 2;
for (i = 0; i < clib_net_to_host_u16 (igmp->n_src_addresses); i++)
{
@@ -150,6 +167,33 @@ format_igmp_query_v3 (u8 * s, va_list * args)
return s;
}
+u8 *
+format_igmp_src_addr_list (u8 * s, va_list * args)
+{
+ ip46_address_t *ss, *srcs;
+
+ srcs = va_arg (*args, ip46_address_t *);
+
+ s = format (s, "[");
+ vec_foreach (ss, srcs)
+ {
+ s = format (s, "%U ", format_ip46_address, ss, IP46_TYPE_ANY);
+ }
+ s = format (s, "]");
+
+ return (s);
+}
+
+u8 *
+format_igmp_key (u8 * s, va_list * args)
+{
+ const igmp_key_t *key = va_arg (*args, const igmp_key_t *);
+
+ s = format (s, "%U", format_ip46_address, key, IP46_TYPE_ANY);
+
+ return (s);
+}
+
/*
* fd.io coding-style-patch-verification: ON
*
diff --git a/src/plugins/igmp/igmp_format.h b/src/plugins/igmp/igmp_format.h
index 018bb63d582..92f58e5791a 100644
--- a/src/plugins/igmp/igmp_format.h
+++ b/src/plugins/igmp/igmp_format.h
@@ -18,15 +18,21 @@
#ifndef _IGMP_FORMAT_H_
#define _IGMP_FORMAT_H_
-u8 *format_igmp_type (u8 * s, va_list * args);
+extern u8 *format_igmp_type (u8 * s, va_list * args);
-u8 *format_igmp_report_type (u8 * s, va_list * args);
+extern u8 *format_igmp_membership_group_type (u8 * s, va_list * args);
-u8 *format_igmp_header (u8 * s, va_list * args);
+extern u8 *format_igmp_header (u8 * s, va_list * args);
-u8 *format_igmp_report_v3 (u8 * s, va_list * args);
+extern u8 *format_igmp_report_v3 (u8 * s, va_list * args);
-u8 *format_igmp_query_v3 (u8 * s, va_list * args);
+extern u8 *format_igmp_query_v3 (u8 * s, va_list * args);
+
+extern u8 *format_igmp_filter_mode (u8 * s, va_list * args);
+
+extern u8 *format_igmp_src_addr_list (u8 * s, va_list * args);
+
+extern u8 *format_igmp_key (u8 * s, va_list * args);
#endif /* IGMP_FORMAT_H */
diff --git a/src/plugins/igmp/igmp_group.c b/src/plugins/igmp/igmp_group.c
new file mode 100644
index 00000000000..fe023a46167
--- /dev/null
+++ b/src/plugins/igmp/igmp_group.c
@@ -0,0 +1,272 @@
+/*
+ *------------------------------------------------------------------
+ * 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_group.h>
+#include <igmp/igmp.h>
+
+void
+igmp_group_free_all_srcs (igmp_group_t * group)
+{
+ igmp_src_t *src;
+
+ /* *INDENT-OFF* */
+ FOR_EACH_SRC (src, group, IGMP_FILTER_MODE_INCLUDE,
+ ({
+ igmp_src_free(src);
+ }));
+ /* *INDENT-ON* */
+
+ hash_free (group->igmp_src_by_key[IGMP_FILTER_MODE_INCLUDE]);
+ hash_free (group->igmp_src_by_key[IGMP_FILTER_MODE_EXCLUDE]);
+}
+
+void
+igmp_group_src_remove (igmp_group_t * group, igmp_src_t * src)
+{
+ hash_unset_mem (group->igmp_src_by_key[IGMP_FILTER_MODE_INCLUDE], src->key);
+ hash_unset_mem (group->igmp_src_by_key[IGMP_FILTER_MODE_EXCLUDE], src->key);
+}
+
+igmp_src_t *
+igmp_group_src_update (igmp_group_t * group,
+ const igmp_key_t * skey, igmp_mode_t mode)
+{
+ igmp_src_t *src;
+
+ src = igmp_src_lookup (group, skey);
+
+ if (NULL == src)
+ {
+ src = igmp_src_alloc (igmp_group_index (group), skey, mode);
+
+ hash_set_mem (group->igmp_src_by_key[IGMP_FILTER_MODE_INCLUDE],
+ src->key, igmp_src_index (src));
+ }
+ else
+ {
+ igmp_src_refresh (src);
+ }
+
+ return (src);
+}
+
+void
+igmp_group_clear (igmp_group_t * group)
+{
+ igmp_config_t *config;
+ u32 ii;
+
+ ASSERT (group);
+
+ config = igmp_config_get (group->config);
+
+ IGMP_DBG ("clear-group: %U %U",
+ format_igmp_key, group->key,
+ format_vnet_sw_if_index_name,
+ vnet_get_main (), config->sw_if_index);
+
+ igmp_group_free_all_srcs (group);
+
+ for (ii = 0; ii < IGMP_GROUP_N_TIMERS; ii++)
+ {
+ igmp_timer_retire (&group->timers[ii]);
+ }
+
+ hash_unset_mem (config->igmp_group_by_key, group->key);
+ clib_mem_free (group->key);
+ pool_put (igmp_main.groups, group);
+}
+
+igmp_group_t *
+igmp_group_alloc (igmp_config_t * config,
+ const igmp_key_t * gkey, igmp_filter_mode_t mode)
+{
+ igmp_main_t *im = &igmp_main;
+ igmp_group_t *group;
+ u32 ii;
+
+ IGMP_DBG ("new-group: %U", format_igmp_key, gkey);
+ pool_get (im->groups, group);
+ memset (group, 0, sizeof (igmp_group_t));
+ group->key = clib_mem_alloc (sizeof (igmp_key_t));
+ clib_memcpy (group->key, gkey, sizeof (igmp_key_t));
+ group->igmp_src_by_key[IGMP_FILTER_MODE_INCLUDE] =
+ hash_create_mem (0, sizeof (igmp_key_t), sizeof (uword));
+ group->igmp_src_by_key[IGMP_FILTER_MODE_EXCLUDE] =
+ hash_create_mem (0, sizeof (igmp_key_t), sizeof (uword));
+ group->router_filter_mode = mode;
+ group->config = igmp_config_index (config);
+ group->n_reports_sent = 0;
+
+ for (ii = 0; ii < IGMP_GROUP_N_TIMERS; ii++)
+ group->timers[ii] = IGMP_TIMER_ID_INVALID;
+
+ hash_set_mem (config->igmp_group_by_key, group->key, group - im->groups);
+ return (group);
+}
+
+/**
+ * the set of present sources minus the new set
+ */
+ip46_address_t *
+igmp_group_present_minus_new (igmp_group_t * group,
+ igmp_filter_mode_t mode,
+ const ip46_address_t * saddrs)
+{
+ const ip46_address_t *s1;
+ ip46_address_t *pmn;
+ igmp_src_t *src;
+ u32 found;
+
+ pmn = NULL;
+
+ /* *INDENT-OFF* */
+ if (0 == vec_len(saddrs))
+ {
+ FOR_EACH_SRC(src, group, mode,
+ ({
+ vec_add1(pmn, *src->key);
+ }));
+ }
+ else
+ {
+ FOR_EACH_SRC(src, group, mode,
+ ({
+ found = 0;
+ vec_foreach(s1, saddrs)
+ {
+ if (ip46_address_is_equal(s1, src->key))
+ {
+ found = 1;
+ break;
+ }
+ }
+
+ if (!found)
+ vec_add1(pmn, *src->key);
+ }));
+ }
+ /* *INDENT-ON* */
+
+ return (pmn);
+}
+
+/**
+ * the set of new sources minus the present set
+ */
+ip46_address_t *
+igmp_group_new_minus_present (igmp_group_t * group,
+ igmp_filter_mode_t mode,
+ const ip46_address_t * saddrs)
+{
+ const ip46_address_t *s1;
+ ip46_address_t *npm;
+ igmp_src_t *src;
+ u32 found;
+
+ npm = NULL;
+
+ /* *INDENT-OFF* */
+ vec_foreach(s1, saddrs)
+ {
+ found = 0;
+ FOR_EACH_SRC(src, group, mode,
+ ({
+ if (ip46_address_is_equal(s1, src->key))
+ {
+ found = 1;
+ break;
+ }
+ }));
+
+ if (!found)
+ vec_add1(npm, *s1);
+ }
+ /* *INDENT-ON* */
+
+ return (npm);
+}
+
+ip46_address_t *
+igmp_group_new_intersect_present (igmp_group_t * group,
+ igmp_filter_mode_t mode,
+ const ip46_address_t * saddrs)
+{
+ ip46_address_t *intersect;
+ const ip46_address_t *s1;
+ igmp_src_t *src;
+
+ intersect = NULL;
+
+ /* *INDENT-OFF* */
+ FOR_EACH_SRC(src, group, mode,
+ ({
+ vec_foreach(s1, saddrs)
+ {
+ if (s1->ip4.as_u32 == src->key->ip4.as_u32)
+ {
+ vec_add1(intersect, *s1);
+ break;
+ }
+ }
+ }));
+ /* *INDENT-ON* */
+
+ return (intersect);
+}
+
+u32
+igmp_group_n_srcs (const igmp_group_t * group, igmp_filter_mode_t mode)
+{
+ return (hash_elts (group->igmp_src_by_key[mode]));
+}
+
+
+igmp_src_t *
+igmp_src_lookup (igmp_group_t * group, const igmp_key_t * key)
+{
+ uword *p;
+ igmp_src_t *src = NULL;
+ if (!group)
+ return NULL;
+
+ p = hash_get_mem (group->igmp_src_by_key[IGMP_FILTER_MODE_INCLUDE], key);
+ if (p)
+ src = vec_elt_at_index (igmp_main.srcs, p[0]);
+
+ return src;
+}
+
+u32
+igmp_group_index (const igmp_group_t * g)
+{
+ return (g - igmp_main.groups);
+}
+
+igmp_group_t *
+igmp_group_get (u32 index)
+{
+ return (pool_elt_at_index (igmp_main.groups, index));
+}
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/igmp/igmp_group.h b/src/plugins/igmp/igmp_group.h
new file mode 100644
index 00000000000..dc0fc7a0f8a
--- /dev/null
+++ b/src/plugins/igmp/igmp_group.h
@@ -0,0 +1,156 @@
+/*
+ *------------------------------------------------------------------
+ * 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.
+ *------------------------------------------------------------------
+ */
+
+#ifndef __IGMP_GROUP_H__
+#define __IGMP_GROUP_H__
+
+#include <igmp/igmp_types.h>
+#include <igmp/igmp_src.h>
+
+/**
+ * Types of timers maintained for each group
+ */
+typedef enum igmp_group_timer_type_t_
+{
+ /**
+ * Timer running to reply to a G/SG specific query
+ */
+ IGMP_GROUP_TIMER_QUERY_REPLY,
+ /**
+ * wait for response from a sent G/SG specfic query.
+ * Sent when a host leaves a group
+ */
+ IGMP_GROUP_TIMER_QUERY_SENT,
+ /**
+ * Timer running to resend report
+ */
+ IGMP_GROUP_TIMER_RESEND_REPORT,
+ /**
+ * filter-mode change timer, to check if the group can swap to
+ * INCLUDE mode (section 6.2.2)
+ */
+ IGMP_GROUP_TIMER_FILTER_MODE_CHANGE,
+} igmp_group_timer_type_t;
+
+#define IGMP_GROUP_N_TIMERS (IGMP_GROUP_TIMER_FILTER_MODE_CHANGE + 1)
+
+/**
+ * @brief IGMP group
+ * A multicast group address for which reception has been requested.
+ */
+typedef struct igmp_group_t_
+{
+ /** The group's key within the per-interface config */
+ igmp_key_t *key;
+
+ /**
+ * A vector of running timers for the group. this can include:
+ * - group-specific query, sent on reception of a host 'leave'
+ * - filter-mode change timer, to check if the group can swap to
+ * INCLUDE mode (section 6.2.2)
+ */
+ u32 timers[IGMP_GROUP_N_TIMERS];
+
+ /**
+ * The current filter mode of the group (see 6.2.1)
+ */
+ igmp_filter_mode_t router_filter_mode;
+
+ /**
+ * The pool index of the config object this group is in
+ */
+ u32 config;
+
+ /**
+ * The number of times the last report has been sent
+ */
+ u32 n_reports_sent;
+
+ /**
+ * Source list per-filter mode
+ */
+ uword *igmp_src_by_key[IGMP_N_FILTER_MODES];
+} igmp_group_t;
+
+#define FOR_EACH_SRC(_src, _group, _filter, _body) \
+do { \
+ igmp_key_t *__key__; \
+ u32 __sid__; \
+ hash_foreach_mem(__key__, __sid__, ((igmp_group_t*)_group)->igmp_src_by_key[(_filter)], \
+ ({ \
+ _src = pool_elt_at_index(igmp_main.srcs, __sid__); \
+ do { _body; } while (0); \
+ })); \
+ } while (0);
+
+/**
+ * Forward declarations
+ */
+struct igmp_config_t_;
+
+extern void igmp_group_clear (igmp_group_t * group);
+extern void igmp_group_free_all_srcs (igmp_group_t * group);
+
+extern igmp_group_t *igmp_group_alloc (struct igmp_config_t_ *config,
+ const igmp_key_t * gkey,
+ igmp_filter_mode_t mode);
+
+extern igmp_src_t *igmp_group_src_update (igmp_group_t * group,
+ const igmp_key_t * skey,
+ igmp_mode_t mode);
+
+extern void igmp_group_src_remove (igmp_group_t * group, igmp_src_t * src);
+
+extern ip46_address_t *igmp_group_present_minus_new (igmp_group_t * group,
+ igmp_filter_mode_t mode,
+ const ip46_address_t *
+ saddrs);
+
+extern ip46_address_t *igmp_group_new_minus_present (igmp_group_t * group,
+ igmp_filter_mode_t mode,
+ const ip46_address_t *
+ saddrs);
+
+extern ip46_address_t *igmp_group_new_intersect_present (igmp_group_t * group,
+ igmp_filter_mode_t
+ mode,
+ const ip46_address_t
+ * saddrs);
+
+extern u32 igmp_group_n_srcs (const igmp_group_t * group,
+ igmp_filter_mode_t mode);
+
+
+/** \brief igmp group lookup
+ @param group - igmp group
+ @param key - igmp key
+*/
+extern igmp_src_t *igmp_src_lookup (igmp_group_t * group,
+ const igmp_key_t * key);
+
+extern u32 igmp_group_index (const igmp_group_t * g);
+extern igmp_group_t *igmp_group_get (u32 index);
+
+#endif
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/igmp/input.c b/src/plugins/igmp/igmp_input.c
index 829317d4543..d4563bf366d 100644
--- a/src/plugins/igmp/input.c
+++ b/src/plugins/igmp/igmp_input.c
@@ -16,6 +16,7 @@
*/
#include <vlib/vlib.h>
+#include <vlibmemory/api.h>
#include <vnet/plugin/plugin.h>
#include <vpp/app/version.h>
#include <vnet/ip/ip.h>
@@ -23,18 +24,13 @@
#include <vnet/adj/adj_mcast.h>
#include <igmp/igmp.h>
-#include <igmp/error.h>
+#include <igmp/igmp_pkt.h>
+#include <igmp/igmp_query.h>
+#include <igmp/igmp_report.h>
+#include <igmp/igmp_error.h>
#include <limits.h>
-/* TODO: mld...
-typedef enum
-{
- MLD_INPUT_NEXT_DROP,
- ...
-} mld_input_next_t;
-*/
-
typedef enum
{
IGMP_INPUT_NEXT_DROP,
@@ -76,11 +72,10 @@ format_igmp_input_trace (u8 * s, va_list * va)
CLIB_UNUSED (vlib_node_t * node) = va_arg (*va, vlib_node_t *);
igmp_input_trace_t *t = va_arg (*va, igmp_input_trace_t *);
- s =
- format (s, "sw_if_index %u next-index %u", t->sw_if_index, t->next_index);
- s =
- format (s, "\n%U", format_igmp_header, t->packet_data,
- sizeof (t->packet_data));
+ s = format (s, "sw_if_index %u next-index %u",
+ t->sw_if_index, t->next_index);
+ s = format (s, "\n%U", format_igmp_header, t->packet_data,
+ sizeof (t->packet_data));
return s;
}
@@ -91,11 +86,10 @@ format_igmp_parse_report_trace (u8 * s, va_list * va)
CLIB_UNUSED (vlib_node_t * node) = va_arg (*va, vlib_node_t *);
igmp_input_trace_t *t = va_arg (*va, igmp_input_trace_t *);
- s =
- format (s, "sw_if_index %u next-index %u", t->sw_if_index, t->next_index);
- s =
- format (s, "\n%U", format_igmp_report_v3, t->packet_data,
- sizeof (t->packet_data));
+ s = format (s, "sw_if_index %u next-index %u",
+ t->sw_if_index, t->next_index);
+ s = format (s, "\n%U", format_igmp_report_v3, t->packet_data,
+ sizeof (t->packet_data));
return s;
}
@@ -106,28 +100,24 @@ format_igmp_parse_query_trace (u8 * s, va_list * va)
CLIB_UNUSED (vlib_node_t * node) = va_arg (*va, vlib_node_t *);
igmp_input_trace_t *t = va_arg (*va, igmp_input_trace_t *);
- s =
- format (s, "sw_if_index %u next-input %u", t->sw_if_index, t->next_index);
- s =
- format (s, "\n%U", format_igmp_query_v3, t->packet_data,
- sizeof (t->packet_data));
+ s = format (s, "sw_if_index %u next-input %u",
+ t->sw_if_index, t->next_index);
+ s = format (s, "\n%U", format_igmp_query_v3, t->packet_data,
+ sizeof (t->packet_data));
return s;
}
-uword
+static uword
igmp_input (vlib_main_t * vm, vlib_node_runtime_t * node,
vlib_frame_t * frame)
{
- IGMP_DBG ("IGMP_INPUT");
- u32 n_left_from, *from, *to_next;
igmp_parse_query_next_t next_index;
- vlib_node_runtime_t *error_node =
- vlib_node_get_runtime (vm, igmp_input_node.index);
+ u32 n_left_from, *from, *to_next;
+ vlib_node_runtime_t *error_node;
u8 error;
- ip_csum_t sum;
- u16 csum;
error = IGMP_ERROR_NONE;
+ error_node = node;
from = vlib_frame_vector_args (frame);
n_left_from = frame->n_vectors;
@@ -141,11 +131,14 @@ igmp_input (vlib_main_t * vm, vlib_node_runtime_t * node,
while (n_left_from > 0 && n_left_to_next > 0)
{
+ igmp_header_t *igmp;
+ u16 checksum, csum;
vlib_buffer_t *b;
ip4_header_t *ip;
+ ip_csum_t sum;
u32 bi, next;
- next = IGMP_INPUT_NEXT_DROP;
+ next = IGMP_INPUT_NEXT_DROP;
bi = from[0];
to_next[0] = bi;
from++;
@@ -156,7 +149,7 @@ igmp_input (vlib_main_t * vm, vlib_node_runtime_t * node,
b = vlib_get_buffer (vm, bi);
ip = vlib_buffer_get_current (b);
- if (ip->protocol != 2)
+ if (ip->protocol != IP_PROTOCOL_IGMP)
{
error = IGMP_ERROR_INVALID_PROTOCOL;
next = IGMP_INPUT_NEXT_DROP;
@@ -165,9 +158,9 @@ igmp_input (vlib_main_t * vm, vlib_node_runtime_t * node,
vlib_buffer_advance (b, ip4_header_bytes (ip));
- igmp_header_t *igmp = vlib_buffer_get_current (b);
+ igmp = vlib_buffer_get_current (b);
- u16 checksum = igmp->checksum;
+ checksum = igmp->checksum;
igmp->checksum = 0;
sum = ip_incremental_checksum (0, igmp,
clib_net_to_host_u16 (ip->length) -
@@ -180,6 +173,12 @@ igmp_input (vlib_main_t * vm, vlib_node_runtime_t * node,
next = IGMP_INPUT_NEXT_DROP;
goto next_buffer;
}
+ if (!igmp_config_lookup (vnet_buffer (b)->sw_if_index[VLIB_RX]))
+ {
+ error = IGMP_ERROR_NOT_ENABLED;
+ next = IGMP_INPUT_NEXT_DROP;
+ goto next_buffer;
+ }
/* TODO: IGMPv2 and IGMPv1 */
switch (igmp->type)
@@ -239,16 +238,12 @@ VLIB_REGISTER_NODE (igmp_input_node) =
};
/* *INDENT-ON* */
-uword
+static uword
igmp_parse_query (vlib_main_t * vm, vlib_node_runtime_t * node,
vlib_frame_t * frame)
{
- IGMP_DBG ("IGMP_PARSE_QUERY");
-
u32 n_left_from, *from, *to_next;
igmp_parse_query_next_t next_index;
- igmp_main_t *im = &igmp_main;
- igmp_config_t *config;
from = vlib_frame_vector_args (frame);
n_left_from = frame->n_vectors;
@@ -262,10 +257,12 @@ igmp_parse_query (vlib_main_t * vm, vlib_node_runtime_t * node,
while (n_left_from > 0 && n_left_to_next > 0)
{
+ igmp_membership_query_v3_t *igmp;
+ igmp_query_args_t *args;
vlib_buffer_t *b;
- u32 sw_if_index, bi, next;
- next = IGMP_PARSE_QUERY_NEXT_DROP;
+ u32 bi, next;
+ next = IGMP_PARSE_QUERY_NEXT_DROP;
bi = from[0];
to_next[0] = bi;
from++;
@@ -274,38 +271,9 @@ igmp_parse_query (vlib_main_t * vm, vlib_node_runtime_t * node,
n_left_to_next--;
b = vlib_get_buffer (vm, bi);
- sw_if_index = vnet_buffer (b)->sw_if_index[VLIB_RX];
-
- igmp_membership_query_v3_t *igmp = vlib_buffer_get_current (b);
+ igmp = vlib_buffer_get_current (b);
ASSERT (igmp->header.type == IGMP_TYPE_membership_query);
- /* if group address is zero, this is a general query */
- if (igmp->dst.as_u32 == 0)
- {
- config = igmp_config_lookup (im, sw_if_index);
- if (!config)
- {
- IGMP_DBG ("No config on interface %u", sw_if_index);
- }
- else
- {
- /* WIP
- *
- * TODO: divide to multipe reports in random time range [now, max resp time]
- */
- u32 seed = vlib_time_now (vm);
- f64 next_resp_time = random_f64 (&seed) *
- (f64) (igmp->header.code / 10) + vlib_time_now (vm);
- config->flags |= IGMP_CONFIG_FLAG_CAN_SEND_REPORT;
- igmp_create_int_timer (next_resp_time, sw_if_index,
- igmp_send_report);
- vlib_process_signal_event (vm,
- igmp_timer_process_node.index,
- IGMP_PROCESS_EVENT_UPDATE_TIMER,
- 0);
- }
- }
-
if (node->flags & VLIB_NODE_FLAG_TRACE)
{
igmp_input_trace_t *tr;
@@ -315,6 +283,21 @@ igmp_parse_query (vlib_main_t * vm, vlib_node_runtime_t * node,
clib_memcpy (tr->packet_data, vlib_buffer_get_current (b),
sizeof (tr->packet_data));
}
+
+ /*
+ * copy the contents of the query, and the interface, over
+ * to the main thread for processing
+ */
+ vlib_buffer_advance (b, -sizeof (u32));
+ args = vlib_buffer_get_current (b);
+ args->sw_if_index = vnet_buffer (b)->sw_if_index[VLIB_RX];
+
+ vl_api_rpc_call_main_thread (igmp_handle_query,
+ (u8 *) args,
+ sizeof (*args) +
+ igmp_membership_query_v3_length
+ (igmp));
+
vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next,
n_left_to_next, bi, next);
}
@@ -344,29 +327,12 @@ VLIB_REGISTER_NODE (igmp_parse_query_node) =
};
/* *INDENT-ON* */
-uword
+static uword
igmp_parse_report (vlib_main_t * vm, vlib_node_runtime_t * node,
vlib_frame_t * frame)
{
- IGMP_DBG ("IGMP_PARSE_REPORT");
-
- igmp_main_t *im = &igmp_main;
u32 n_left_from, *from, *to_next;
igmp_input_next_t next_index;
- igmp_config_t *config;
- igmp_group_t *group;
- igmp_src_t *src;
- igmp_membership_group_v3_t *igmp_group;
- ip4_address_t *src_addr;
- igmp_key_t gkey;
- igmp_key_t skey;
- memset (&gkey, 0, sizeof (igmp_key_t));
- memset (&skey, 0, sizeof (igmp_key_t));
- ip46_address_t saddr;
- memset (&saddr, 0, sizeof (ip46_address_t));
- ip46_address_t gaddr;
- memset (&gaddr, 0, sizeof (ip46_address_t));
- u32 len;
vlib_node_runtime_t *error_node =
vlib_node_get_runtime (vm, igmp_input_node.index);
u8 error;
@@ -383,8 +349,11 @@ igmp_parse_report (vlib_main_t * vm, vlib_node_runtime_t * node,
while (n_left_from > 0 && n_left_to_next > 0)
{
+ igmp_membership_report_v3_t *igmp;
+ igmp_report_args_t *args;
+ u32 bi, next;
vlib_buffer_t *b;
- u32 sw_if_index, bi, next;
+
next = IGMP_PARSE_REPORT_NEXT_DROP;
bi = from[0];
@@ -398,139 +367,10 @@ igmp_parse_report (vlib_main_t * vm, vlib_node_runtime_t * node,
error = IGMP_ERROR_NONE;
b->error = error_node->errors[error];
+ igmp = vlib_buffer_get_current (b);
- sw_if_index = vnet_buffer (b)->sw_if_index[VLIB_RX];
-
- igmp_membership_report_v3_t *igmp = vlib_buffer_get_current (b);
ASSERT (igmp->header.type == IGMP_TYPE_membership_report_v3);
- len = sizeof (igmp_membership_report_v3_t);
- /* if interface (S,G)s were configured by CLI/API goto next frame */
- config = igmp_config_lookup (im, sw_if_index);
- if (config)
- {
- config->flags |= IGMP_CONFIG_FLAG_QUERY_RESP_RECVED;
- if (config->flags & IGMP_CONFIG_FLAG_CLI_API_CONFIGURED)
- {
- IGMP_DBG ("Interface %u has (S,G)s configured by CLI/API",
- sw_if_index);
- error = IGMP_ERROR_CLI_API_CONFIG;
- b->error = error_node->errors[error];
- goto next_frame;
- }
- }
- IGMP_DBG ("interface %u", sw_if_index);
- int i, j = 0;
- for (i = 0; i < clib_net_to_host_u16 (igmp->n_groups); i++)
- {
- igmp_group = group_ptr (igmp, len);
- src_addr = igmp_group->src_addresses;
- if (igmp_group->type ==
- IGMP_MEMBERSHIP_GROUP_mode_is_filter_include)
- {
- ip46_address_set_ip4 ((ip46_address_t *) & gkey.data,
- &igmp_group->dst_address);
-
- gkey.group_type =
- IGMP_MEMBERSHIP_GROUP_mode_is_filter_include;
-
- group = igmp_group_lookup (config, &gkey);
- if (group)
- {
- for (j = 0;
- j <
- clib_net_to_host_u16 (igmp_group->n_src_addresses);
- j++)
- {
- /* update (S,G) expiration timer */
- ip46_address_set_ip4 ((ip46_address_t *) &
- skey.data, src_addr);
- src = igmp_src_lookup (group, &skey);
- if (src)
- src->exp_time =
- vlib_time_now (vm) + IGMP_SRC_TIMER;
- src_addr++;
- }
- }
- else
- {
- j = clib_net_to_host_u16 (igmp_group->n_src_addresses);
- }
- }
- else if (igmp_group->type ==
- IGMP_MEMBERSHIP_GROUP_mode_is_filter_exclude)
- {
- for (j = 0;
- j < clib_net_to_host_u16 (igmp_group->n_src_addresses);
- j++)
- {
- /* nothing for now... */
- src_addr++;
- }
- }
- else if (igmp_group->type ==
- IGMP_MEMBERSHIP_GROUP_change_to_filter_include)
- {
- for (j = 0;
- j < clib_net_to_host_u16 (igmp_group->n_src_addresses);
- j++)
- {
- /* add new (S,G) to interface */
- saddr.ip4 = *src_addr;
- gaddr.ip4 = igmp_group->dst_address;
- igmp_listen (vm, 1, sw_if_index, saddr, gaddr, 0);
- src_addr++;
- }
- }
- else if (igmp_group->type ==
- IGMP_MEMBERSHIP_GROUP_change_to_filter_exclude)
- {
- for (j = 0;
- j < clib_net_to_host_u16 (igmp_group->n_src_addresses);
- j++)
- {
- /* remove (S,G) from interface */
- saddr.ip4 = *src_addr;
- gaddr.ip4 = igmp_group->dst_address;
- igmp_listen (vm, 0, sw_if_index, saddr, gaddr, 0);
- src_addr++;
- }
- }
- else if (igmp_group->type ==
- IGMP_MEMBERSHIP_GROUP_allow_new_sources)
- {
- for (j = 0;
- j < clib_net_to_host_u16 (igmp_group->n_src_addresses);
- j++)
- {
- /* nothing for now... */
- src_addr++;
- }
- }
- else if (igmp_group->type ==
- IGMP_MEMBERSHIP_GROUP_block_old_sources)
- {
- for (j = 0;
- j < clib_net_to_host_u16 (igmp_group->n_src_addresses);
- j++)
- {
- /* remove (S,G) from interface */
- saddr.ip4 = *src_addr;
- gaddr.ip4 = igmp_group->dst_address;
- igmp_listen (vm, 0, sw_if_index, saddr, gaddr, 0);
- src_addr++;
- }
- }
- /*
- * Unrecognized Record Type values MUST be silently ignored.
- */
-
- /* move ptr to next Group Record */
- len +=
- sizeof (igmp_membership_group_v3_t) +
- (sizeof (ip4_address_t) * j);
- }
- next_frame:
if (node->flags & VLIB_NODE_FLAG_TRACE)
{
igmp_input_trace_t *tr;
@@ -540,6 +380,21 @@ igmp_parse_report (vlib_main_t * vm, vlib_node_runtime_t * node,
clib_memcpy (tr->packet_data, vlib_buffer_get_current (b),
sizeof (tr->packet_data));
}
+
+ /*
+ * copy the contents of the query, and the interface, over
+ * to the main thread for processing
+ */
+ vlib_buffer_advance (b, -sizeof (u32));
+ args = vlib_buffer_get_current (b);
+ args->sw_if_index = vnet_buffer (b)->sw_if_index[VLIB_RX];
+
+ vl_api_rpc_call_main_thread (igmp_handle_report,
+ (u8 *) args,
+ sizeof (*args) +
+ igmp_membership_report_v3_length
+ (igmp));
+
vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next,
n_left_to_next, bi, next);
}
@@ -569,6 +424,23 @@ VLIB_REGISTER_NODE (igmp_parse_report_node) =
};
/* *INDENT-ON* */
+static clib_error_t *
+igmp_input_init (vlib_main_t * vm)
+{
+ clib_error_t *error;
+
+ if ((error = vlib_call_init_function (vm, igmp_init)))
+ return error;
+
+ ip4_register_protocol (IP_PROTOCOL_IGMP, igmp_input_node.index);
+
+ IGMP_DBG ("input-initialized");
+
+ return (error);
+}
+
+VLIB_INIT_FUNCTION (igmp_input_init);
+
/*
* fd.io coding-style-patch-verification: ON
*
diff --git a/src/plugins/igmp/igmp_pkt.c b/src/plugins/igmp/igmp_pkt.c
new file mode 100644
index 00000000000..5dd829de712
--- /dev/null
+++ b/src/plugins/igmp/igmp_pkt.c
@@ -0,0 +1,534 @@
+/*
+ *------------------------------------------------------------------
+ * Copyright (c) 2017 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_pkt.h>
+
+static void
+vlib_buffer_append (vlib_buffer_t * b, uword l)
+{
+ b->current_data += l;
+ b->current_length += l;
+}
+
+static vlib_buffer_t *
+igmp_pkt_get_buffer (igmp_pkt_build_t * bk)
+{
+ vlib_buffer_free_list_t *fl;
+ vlib_main_t *vm;
+ vlib_buffer_t *b;
+ u32 bi;
+
+ vm = vlib_get_main ();
+
+ if (vlib_buffer_alloc (vm, &bi, 1) != 1)
+ return (NULL);
+
+ b = vlib_get_buffer (vm, bi);
+ fl = vlib_buffer_get_free_list (vm, VLIB_BUFFER_DEFAULT_FREE_LIST_INDEX);
+ vlib_buffer_init_for_free_list (b, fl);
+ VLIB_BUFFER_TRACE_TRAJECTORY_INIT (b);
+
+ b->flags |= VNET_BUFFER_F_LOCALLY_ORIGINATED;
+ b->flags |= VLIB_BUFFER_IS_TRACED;
+
+ /* clear out stale data */
+ vnet_buffer (b)->sw_if_index[VLIB_RX] = ~0;
+
+ /*
+ * save progress in the builder
+ */
+ vec_add1 (bk->buffers, bi);
+ bk->n_avail = vnet_sw_interface_get_mtu (vnet_get_main (),
+ bk->sw_if_index, VNET_MTU_IP4);
+
+ return (b);
+}
+
+static vlib_buffer_t *
+igmp_pkt_build_ip_header (igmp_pkt_build_t * bk,
+ igmp_msg_type_t msg_type,
+ const igmp_group_t * group)
+{
+ ip4_header_t *ip4;
+ vlib_buffer_t *b;
+ u8 *option;
+
+ b = igmp_pkt_get_buffer (bk);
+
+ if (NULL == b)
+ return (NULL);
+
+ ip4 = vlib_buffer_get_current (b);
+ memset (ip4, 0, sizeof (ip4_header_t));
+ ip4->ip_version_and_header_length = 0x46;
+ ip4->ttl = 1;
+ ip4->protocol = IP_PROTOCOL_IGMP;
+ ip4->tos = 0xc0;
+
+ ip4_src_address_for_packet (&ip4_main.lookup_main,
+ bk->sw_if_index, &ip4->src_address);
+
+ vlib_buffer_append (b, sizeof (*ip4));
+ bk->n_avail -= sizeof (*ip4);
+
+ switch (msg_type)
+ {
+ case IGMP_MSG_REPORT:
+ ip4->dst_address.as_u32 = IGMP_MEMBERSHIP_REPORT_ADDRESS;
+ break;
+ case IGMP_MSG_QUERY:
+ if (group != NULL)
+ clib_memcpy (&ip4->dst_address, &group->key->ip4,
+ sizeof (ip4_address_t));
+ else
+ ip4->dst_address.as_u32 = IGMP_GENERAL_QUERY_ADDRESS;
+ break;
+ }
+
+ /* add the router alert optnios */
+ option = vlib_buffer_get_current (b);
+ option[0] = 0x80 | 20; // IP4_ROUTER_ALERT_OPTION;
+ option[1] = 4; // length
+ option[2] = option[3] = 0;
+
+ vlib_buffer_append (b, 4);
+ bk->n_avail -= 4;
+
+ return (b);
+}
+
+static vlib_buffer_t *
+igmp_pkt_build_report_v3 (igmp_pkt_build_report_t * br,
+ const igmp_group_t * group)
+{
+ igmp_membership_report_v3_t *report;
+ vlib_buffer_t *b;
+
+ b = igmp_pkt_build_ip_header (&br->base, IGMP_MSG_REPORT, group);
+
+ if (NULL == b)
+ return (NULL);
+
+ report = vlib_buffer_get_current (b);
+ report->header.type = IGMP_TYPE_membership_report_v3;
+ report->header.code = 0;
+ report->header.checksum = 0;
+ report->unused = 0;
+
+ vlib_buffer_append (b, sizeof (igmp_membership_report_v3_t));
+ br->base.n_avail -= sizeof (igmp_membership_report_v3_t);
+ br->base.n_bytes += sizeof (igmp_membership_report_v3_t);
+
+ return (b);
+}
+
+static void
+igmp_pkt_tx (igmp_pkt_build_t * bk)
+{
+ const igmp_config_t *config;
+ vlib_buffer_t *b;
+ vlib_main_t *vm;
+ vlib_frame_t *f;
+ u32 *to_next;
+ u32 ii;
+
+ vm = vlib_get_main ();
+ config = igmp_config_lookup (bk->sw_if_index);
+ f = vlib_get_frame_to_node (vm, ip4_rewrite_mcast_node.index);
+ to_next = vlib_frame_vector_args (f);
+
+ vec_foreach_index (ii, bk->buffers)
+ {
+ b = vlib_get_buffer (vm, bk->buffers[ii]);
+ vnet_buffer (b)->ip.adj_index[VLIB_TX] = config->adj_index;
+ to_next[ii] = bk->buffers[ii];
+ f->n_vectors++;
+ }
+
+ vlib_put_frame_to_node (vm, ip4_rewrite_mcast_node.index, f);
+
+ IGMP_DBG (" ..tx: %U", format_vnet_sw_if_index_name,
+ vnet_get_main (), bk->sw_if_index);
+
+ vec_free (bk->buffers);
+ bk->buffers = 0;
+}
+
+static vlib_buffer_t *
+igmp_pkt_build_report_get_active (igmp_pkt_build_report_t * br)
+{
+ if (NULL == br->base.buffers)
+ return (NULL);
+
+ return (vlib_get_buffer (vlib_get_main (),
+ br->base.buffers[vec_len (br->base.buffers) - 1]));
+}
+
+static void
+igmp_pkt_build_report_bake (igmp_pkt_build_report_t * br)
+{
+ igmp_membership_report_v3_t *igmp;
+ ip4_header_t *ip4;
+ vlib_buffer_t *b;
+
+ b = igmp_pkt_build_report_get_active (br);
+
+ b->current_data = 0;
+
+ ip4 = vlib_buffer_get_current (b);
+ igmp = (igmp_membership_report_v3_t *) (((u32 *) ip4) + 6);
+
+ igmp->n_groups = clib_host_to_net_u16 (br->n_groups);
+
+ igmp->header.checksum =
+ ~ip_csum_fold (ip_incremental_checksum (0, igmp, br->base.n_bytes));
+
+ ip4->length = clib_host_to_net_u16 (b->current_length);
+ ip4->checksum = ip4_header_checksum (ip4);
+
+ br->base.n_bytes = br->base.n_avail = br->n_groups = 0;
+}
+
+void
+igmp_pkt_report_v3_send (igmp_pkt_build_report_t * br)
+{
+ if (NULL == br->base.buffers)
+ return;
+
+ igmp_pkt_build_report_bake (br);
+ igmp_pkt_tx (&br->base);
+}
+
+static u32
+igmp_pkt_report_v3_get_size (const igmp_group_t * group)
+{
+ ASSERT (IGMP_FILTER_MODE_INCLUDE == group->router_filter_mode);
+
+ return ((hash_elts (group->igmp_src_by_key[IGMP_FILTER_MODE_INCLUDE]) *
+ sizeof (ip4_address_t)) + sizeof (igmp_membership_group_v3_t));
+}
+
+static igmp_membership_group_v3_t *
+igmp_pkt_report_v3_append_group (igmp_pkt_build_report_t * br,
+ const ip46_address_t * grp,
+ igmp_membership_group_v3_type_t type)
+{
+ igmp_membership_group_v3_t *igmp_group;
+ vlib_buffer_t *b;
+
+ b = igmp_pkt_build_report_get_active (br);
+
+ if (br->base.n_avail < sizeof (igmp_membership_group_v3_t))
+ {
+ igmp_pkt_build_report_bake (br);
+ b = igmp_pkt_build_report_v3 (br, NULL);
+ if (NULL == b)
+ return (NULL);
+ }
+ br->base.n_avail -= sizeof (igmp_membership_group_v3_t);
+ br->base.n_bytes += sizeof (igmp_membership_group_v3_t);
+ br->n_groups++;
+ br->n_srcs = 0;
+
+ igmp_group = vlib_buffer_get_current (b);
+ vlib_buffer_append (b, sizeof (igmp_membership_group_v3_t));
+
+ igmp_group->type = type;
+ igmp_group->n_aux_u32s = 0;
+ igmp_group->n_src_addresses = 0;
+ igmp_group->group_address.as_u32 = grp->ip4.as_u32;
+
+ return (igmp_group);
+}
+
+/**
+ * 4.2.16
+ " If the set of Group Records required in a Report does not fit within
+ * the size limit of a single Report message (as determined by the MTU
+ * of the network on which it will be sent), the Group Records are sent
+ * in as many Report messages as needed to report the entire set.
+
+ * If a single Group Record contains so many source addresses that it
+ * does not fit within the size limit of a single Report message, if its
+ * Type is not MODE_IS_EXCLUDE or CHANGE_TO_EXCLUDE_MODE, it is split
+ * into multiple Group Records, each containing a different subset of
+ * the source addresses and each sent in a separate Report message. If
+ * its Type is MODE_IS_EXCLUDE or CHANGE_TO_EXCLUDE_MODE, a single Group
+ * Record is sent, containing as many source addresses as can fit, and
+ * the remaining source addresses are not reported; though the choice of
+ * which sources to report is arbitrary, it is preferable to report the
+ * same set of sources in each subsequent report, rather than reporting
+ * different sources each time."
+ */
+static igmp_membership_group_v3_t *
+igmp_pkt_report_v3_append_src (igmp_pkt_build_report_t * br,
+ igmp_membership_group_v3_t * igmp_group,
+ const ip46_address_t * grp,
+ igmp_membership_group_v3_type_t type,
+ const ip46_address_t * src)
+{
+ vlib_buffer_t *b;
+
+ b = igmp_pkt_build_report_get_active (br);
+
+ if (br->base.n_avail < sizeof (ip4_address_t))
+ {
+ igmp_group->n_src_addresses = clib_host_to_net_u16 (br->n_srcs);
+ igmp_pkt_build_report_bake (br);
+ b = igmp_pkt_build_report_v3 (br, NULL);
+ if (NULL == b)
+ return (NULL);
+ igmp_group = igmp_pkt_report_v3_append_group (br, grp, type);
+ }
+
+ igmp_group->src_addresses[br->n_srcs].as_u32 = src->ip4.as_u32;
+ br->n_srcs++;
+ br->base.n_avail -= sizeof (ip4_address_t);
+ br->base.n_bytes += sizeof (ip4_address_t);
+ vlib_buffer_append (b, sizeof (ip4_address_t));
+
+ return (igmp_group);
+}
+
+void
+igmp_pkt_report_v3_add_report (igmp_pkt_build_report_t * br,
+ const ip46_address_t * grp,
+ const ip46_address_t * srcs,
+ igmp_membership_group_v3_type_t type)
+{
+ igmp_membership_group_v3_t *igmp_group;
+ const ip46_address_t *s;
+ vlib_buffer_t *b;
+
+ b = igmp_pkt_build_report_get_active (br);
+
+ if (NULL == b)
+ {
+ b = igmp_pkt_build_report_v3 (br, NULL);
+ if (NULL == b)
+ /* failed to allocate buffer */
+ return;
+ }
+
+ igmp_group = igmp_pkt_report_v3_append_group (br, grp, type);
+
+ if (NULL == igmp_group)
+ return;
+
+ /* *INDENT-OFF* */
+ vec_foreach(s, srcs)
+ {
+ igmp_group = igmp_pkt_report_v3_append_src(br, igmp_group,
+ grp, type, s);
+ if (NULL == igmp_group)
+ return;
+ };
+ /* *INDENT-ON* */
+
+ igmp_group->n_src_addresses = clib_host_to_net_u16 (br->n_srcs);
+
+ IGMP_DBG (" ..add-group: %U", format_ip46_address, grp, IP46_TYPE_IP4);
+}
+
+void
+igmp_pkt_report_v3_add_group (igmp_pkt_build_report_t * br,
+ const igmp_group_t * group,
+ igmp_membership_group_v3_type_t type)
+{
+ igmp_membership_group_v3_t *igmp_group;
+ vlib_buffer_t *b;
+ igmp_src_t *src;
+
+ b = igmp_pkt_build_report_get_active (br);
+
+ if (NULL == b)
+ {
+ b = igmp_pkt_build_report_v3 (br, NULL);
+ if (NULL == b)
+ /* failed to allocate buffer */
+ return;
+ }
+
+ /*
+ * if the group won't fit in a partially full buffer, start again
+ */
+ if ((0 != br->n_groups) &&
+ (igmp_pkt_report_v3_get_size (group) > br->base.n_avail))
+ {
+ igmp_pkt_build_report_bake (br);
+ b = igmp_pkt_build_report_v3 (br, NULL);
+ if (NULL == b)
+ /* failed to allocate buffer */
+ return;
+ }
+
+ igmp_group = igmp_pkt_report_v3_append_group (br, group->key, type);
+
+ /* *INDENT-OFF* */
+ FOR_EACH_SRC (src, group, IGMP_FILTER_MODE_INCLUDE,
+ ({
+ igmp_group = igmp_pkt_report_v3_append_src(br, igmp_group,
+ group->key, type,
+ src->key);
+ if (NULL == igmp_group)
+ return;
+ }));
+ /* *INDENT-ON* */
+ igmp_group->n_src_addresses = clib_host_to_net_u16 (br->n_srcs);
+
+ IGMP_DBG (" ..add-group: %U srcs:%d",
+ format_igmp_key, group->key,
+ hash_elts (group->igmp_src_by_key[IGMP_FILTER_MODE_INCLUDE]));
+}
+
+void
+igmp_pkt_build_report_init (igmp_pkt_build_report_t * br, u32 sw_if_index)
+{
+ memset (br, 0, sizeof (*br));
+ br->base.sw_if_index = sw_if_index;
+}
+
+static vlib_buffer_t *
+igmp_pkt_build_query_get_active (igmp_pkt_build_query_t * bq)
+{
+ if (NULL == bq->base.buffers)
+ return (NULL);
+
+ return (vlib_get_buffer (vlib_get_main (),
+ bq->base.buffers[vec_len (bq->base.buffers) - 1]));
+}
+
+static vlib_buffer_t *
+igmp_pkt_build_query_v3 (igmp_pkt_build_query_t * bq,
+ const igmp_group_t * group)
+{
+ igmp_membership_query_v3_t *query;
+ vlib_buffer_t *b;
+
+ b = igmp_pkt_build_ip_header (&bq->base, IGMP_MSG_QUERY, group);
+
+ if (NULL == b)
+ return (NULL);
+
+ query = vlib_buffer_get_current (b);
+ query->header.type = IGMP_TYPE_membership_query;
+ query->header.code = 0;
+ query->header.checksum = 0;
+ query->qqi_code = 0;
+ query->resv_s_qrv = 0;
+
+ if (NULL != group)
+ query->group_address.as_u32 = group->key->ip4.as_u32;
+ else
+ query->group_address.as_u32 = 0;
+
+ vlib_buffer_append (b, sizeof (igmp_membership_query_v3_t));
+ bq->base.n_avail -= sizeof (igmp_membership_query_v3_t);
+ bq->base.n_bytes += sizeof (igmp_membership_query_v3_t);
+
+ return (b);
+}
+
+void
+igmp_pkt_query_v3_add_group (igmp_pkt_build_query_t * bq,
+ const igmp_group_t * group,
+ const ip46_address_t * srcs)
+{
+ vlib_buffer_t *b;
+
+ b = igmp_pkt_build_query_get_active (bq);
+
+ if (NULL == b)
+ {
+ b = igmp_pkt_build_query_v3 (bq, group);
+ if (NULL == b)
+ /* failed to allocate buffer */
+ return;
+ }
+
+ if (NULL != srcs)
+ {
+ igmp_membership_query_v3_t *query;
+ const ip46_address_t *src;
+
+ query = vlib_buffer_get_current (b);
+
+ vec_foreach (src, srcs)
+ {
+ query->src_addresses[bq->n_srcs++].as_u32 = src->ip4.as_u32;
+
+ vlib_buffer_append (b, sizeof (ip4_address_t));
+ bq->base.n_bytes += sizeof (ip4_address_t);
+ bq->base.n_avail += sizeof (ip4_address_t);
+ }
+ }
+ /*
+ * else
+ * general query and we're done
+ */
+}
+
+static void
+igmp_pkt_build_query_bake (igmp_pkt_build_query_t * bq)
+{
+ igmp_membership_query_v3_t *igmp;
+ ip4_header_t *ip4;
+ vlib_buffer_t *b;
+
+ b = igmp_pkt_build_query_get_active (bq);
+
+ b->current_data = 0;
+
+ ip4 = vlib_buffer_get_current (b);
+ // account for options
+ igmp = (igmp_membership_query_v3_t *) (((u32 *) ip4) + 6);
+
+ igmp->n_src_addresses = clib_host_to_net_u16 (bq->n_srcs);
+
+ igmp->header.checksum =
+ ~ip_csum_fold (ip_incremental_checksum (0, igmp, bq->base.n_bytes));
+
+ ip4->length = clib_host_to_net_u16 (b->current_length);
+ ip4->checksum = ip4_header_checksum (ip4);
+
+ bq->base.n_bytes = bq->base.n_avail = bq->n_srcs = 0;
+}
+
+void
+igmp_pkt_query_v3_send (igmp_pkt_build_query_t * bq)
+{
+ if (NULL == bq->base.buffers)
+ return;
+
+ igmp_pkt_build_query_bake (bq);
+ igmp_pkt_tx (&bq->base);
+}
+
+void
+igmp_pkt_build_query_init (igmp_pkt_build_query_t * bq, u32 sw_if_index)
+{
+ memset (bq, 0, sizeof (*bq));
+ bq->base.sw_if_index = sw_if_index;
+}
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/igmp/igmp_pkt.h b/src/plugins/igmp/igmp_pkt.h
new file mode 100644
index 00000000000..66fc5a7616b
--- /dev/null
+++ b/src/plugins/igmp/igmp_pkt.h
@@ -0,0 +1,78 @@
+/*
+ *------------------------------------------------------------------
+ * Copyright (c) 2017 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.
+ *------------------------------------------------------------------
+ */
+
+#ifndef __IGMP_PKT_H__
+#define __IGMP_PKT_H__
+
+#include <igmp/igmp.h>
+
+typedef struct igmp_pkt_build_t_
+{
+ u32 *buffers;
+ u32 sw_if_index;
+ u32 n_avail;
+ u32 n_bytes;
+} igmp_pkt_build_t;
+
+typedef struct igmp_pkt_build_report_t_
+{
+ igmp_pkt_build_t base;
+ u32 n_groups;
+ u16 n_srcs;
+} igmp_pkt_build_report_t;
+
+extern void igmp_pkt_build_report_init (igmp_pkt_build_report_t * br,
+ u32 sw_if_index);
+
+extern void igmp_pkt_report_v3_add_report (igmp_pkt_build_report_t * br,
+ const ip46_address_t * grp,
+ const ip46_address_t * srcs,
+ igmp_membership_group_v3_type_t
+ type);
+
+extern void igmp_pkt_report_v3_add_group (igmp_pkt_build_report_t * br,
+ const igmp_group_t * group,
+ igmp_membership_group_v3_type_t
+ type);
+
+extern void igmp_pkt_report_v3_send (igmp_pkt_build_report_t * br);
+
+
+typedef struct igmp_pkt_build_query_t_
+{
+ igmp_pkt_build_t base;
+ u32 n_srcs;
+} igmp_pkt_build_query_t;
+
+extern void igmp_pkt_build_query_init (igmp_pkt_build_query_t * bq,
+ u32 sw_if_index);
+
+extern void igmp_pkt_query_v3_add_group (igmp_pkt_build_query_t * bq,
+ const igmp_group_t * group,
+ const ip46_address_t * srcs);
+
+extern void igmp_pkt_query_v3_send (igmp_pkt_build_query_t * bq);
+
+#endif
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
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:
+ */
diff --git a/src/plugins/igmp/igmp_query.h b/src/plugins/igmp/igmp_query.h
new file mode 100644
index 00000000000..3dd24eeb08e
--- /dev/null
+++ b/src/plugins/igmp/igmp_query.h
@@ -0,0 +1,37 @@
+/*
+ *------------------------------------------------------------------
+ * 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.h>
+
+/**
+ * A copy of the query message sent from the worker to the main thread
+ */
+typedef struct igmp_query_args_t_
+{
+ u32 sw_if_index;
+ igmp_membership_query_v3_t query[0];
+} igmp_query_args_t;
+
+extern void igmp_handle_query (const igmp_query_args_t * args);
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/igmp/igmp_report.c b/src/plugins/igmp/igmp_report.c
new file mode 100644
index 00000000000..328b890c15e
--- /dev/null
+++ b/src/plugins/igmp/igmp_report.c
@@ -0,0 +1,193 @@
+/*
+ *------------------------------------------------------------------
+ * 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_report.h>
+#include <igmp/igmp_pkt.h>
+
+static ip46_address_t *
+igmp_group_mk_source_list (const igmp_membership_group_v3_t * r)
+{
+ ip46_address_t *srcs = NULL;
+ const ip4_address_t *s;
+ u16 ii, n;
+
+ n = clib_net_to_host_u16 (r->n_src_addresses);
+
+ if (0 == n)
+ return (NULL);
+
+ vec_validate (srcs, n - 1);
+ s = r->src_addresses;
+
+ for (ii = 0; ii < n; ii++)
+ {
+ srcs[ii].ip4 = *s;
+ s++;
+ }
+
+ return (srcs);
+}
+
+static void
+igmp_handle_group_update (igmp_config_t * config,
+ const igmp_membership_group_v3_t * igmp_group)
+{
+ ip46_address_t *src, *srcs;
+ igmp_group_t *group;
+ ip46_address_t key = {
+ .ip4 = igmp_group->group_address,
+ };
+
+ srcs = igmp_group_mk_source_list (igmp_group);
+ group = igmp_group_lookup (config, &key);
+
+ IGMP_DBG (" ..group-update: %U (%U, %U)",
+ format_vnet_sw_if_index_name,
+ vnet_get_main (), config->sw_if_index,
+ format_igmp_key, &key, format_igmp_src_addr_list, srcs);
+
+ if (NULL == group)
+ {
+ group = igmp_group_alloc (config, &key, IGMP_FILTER_MODE_INCLUDE);
+ }
+
+ /* create or update all sources */
+ vec_foreach (src, srcs)
+ {
+ igmp_group_src_update (group, src, IGMP_MODE_ROUTER);
+ }
+
+ vec_free (srcs);
+}
+
+static void
+igmp_handle_group_block (igmp_config_t * config,
+ const igmp_membership_group_v3_t * igmp_group)
+{
+ ip46_address_t *s, *srcs;
+ igmp_pkt_build_query_t bq;
+ igmp_group_t *group;
+ ip46_address_t key = {
+ .ip4 = igmp_group->group_address,
+ };
+
+ srcs = igmp_group_mk_source_list (igmp_group);
+ group = igmp_group_lookup (config, &key);
+
+ IGMP_DBG (" ..group-block: %U (%U, %U)",
+ format_vnet_sw_if_index_name,
+ vnet_get_main (), config->sw_if_index,
+ format_igmp_key, &key, format_igmp_src_addr_list, srcs);
+
+ if (group)
+ {
+ igmp_src_t *src;
+ /*
+ * sned a group+source specific query
+ */
+ igmp_pkt_build_query_init (&bq, config->sw_if_index);
+ igmp_pkt_query_v3_add_group (&bq, group, srcs);
+ igmp_pkt_query_v3_send (&bq);
+
+ /*
+ * for each source left/blocked drop the source expire timer to the leave
+ * latency timer
+ */
+ vec_foreach (s, srcs)
+ {
+ src = igmp_src_lookup (group, s);
+ if (NULL != src)
+ igmp_src_blocked (src);
+ }
+ }
+ /*
+ * a block/leave from a group for which we have no state
+ */
+
+ vec_free (srcs);
+}
+
+static void
+igmp_handle_group (igmp_config_t * config,
+ const igmp_membership_group_v3_t * igmp_group)
+{
+ IGMP_DBG ("rx-group-report: %U",
+ format_vnet_sw_if_index_name,
+ vnet_get_main (), config->sw_if_index);
+
+ switch (igmp_group->type)
+ {
+ case IGMP_MEMBERSHIP_GROUP_mode_is_include:
+ case IGMP_MEMBERSHIP_GROUP_change_to_include:
+ case IGMP_MEMBERSHIP_GROUP_allow_new_sources:
+ igmp_handle_group_update (config, igmp_group);
+ break;
+ case IGMP_MEMBERSHIP_GROUP_block_old_sources:
+ igmp_handle_group_block (config, igmp_group);
+ break;
+ case IGMP_MEMBERSHIP_GROUP_mode_is_exclude:
+ case IGMP_MEMBERSHIP_GROUP_change_to_exclude:
+ break;
+ /*
+ * all other types ignored
+ */
+ }
+}
+
+void
+igmp_handle_report (const igmp_report_args_t * args)
+{
+ const igmp_membership_group_v3_t *igmp_group;
+ igmp_config_t *config;
+ u16 n_groups, ii;
+
+ config = igmp_config_lookup (args->sw_if_index);
+
+ if (!config)
+ /*
+ * no IGMP config on the interface. quit
+ */
+ return;
+
+ if (IGMP_MODE_HOST == config->mode)
+ {
+ /*
+ * Hosts need not listen to the reports of other hosts.
+ * we're done here
+ */
+ return;
+ }
+
+ n_groups = clib_net_to_host_u16 (args->report[0].n_groups);
+ igmp_group = args->report[0].groups;
+
+ for (ii = 0; ii < n_groups; ii++)
+ {
+ igmp_handle_group (config, igmp_group);
+
+ igmp_group = group_cptr (igmp_group,
+ igmp_membership_group_v3_length (igmp_group));
+ }
+}
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/igmp/igmp_report.h b/src/plugins/igmp/igmp_report.h
new file mode 100644
index 00000000000..f64309fc479
--- /dev/null
+++ b/src/plugins/igmp/igmp_report.h
@@ -0,0 +1,37 @@
+/*
+ *------------------------------------------------------------------
+ * 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.h>
+
+/**
+ * A copy of the report message sent from the worker to the main thread
+ */
+typedef struct igmp_report_args_t_
+{
+ u32 sw_if_index;
+ igmp_membership_report_v3_t report[0];
+} igmp_report_args_t;
+
+extern void igmp_handle_report (const igmp_report_args_t * args);
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/igmp/igmp_src.c b/src/plugins/igmp/igmp_src.c
new file mode 100644
index 00000000000..5e6b6092df4
--- /dev/null
+++ b/src/plugins/igmp/igmp_src.c
@@ -0,0 +1,151 @@
+/*
+ *------------------------------------------------------------------
+ * 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_src.h>
+#include <igmp/igmp_group.h>
+#include <igmp/igmp.h>
+
+void
+igmp_src_free (igmp_src_t * src)
+{
+ igmp_main_t *im = &igmp_main;
+
+ IGMP_DBG ("free-src: (%U)", format_igmp_key, src->key);
+
+ igmp_timer_retire (&src->timers[IGMP_SRC_TIMER_EXP]);
+
+ clib_mem_free (src->key);
+ pool_put (im->srcs, src);
+}
+
+static void
+igmp_src_exp (u32 obj, void *dat)
+{
+ igmp_group_t *group;
+ igmp_src_t *src;
+
+ src = pool_elt_at_index (igmp_main.srcs, obj);
+ group = igmp_group_get (src->group);
+
+ IGMP_DBG ("src-exp: %U", format_igmp_key, src->key);
+
+ igmp_timer_retire (&src->timers[IGMP_SRC_TIMER_EXP]);
+
+ if (IGMP_MODE_ROUTER == src->mode)
+ {
+ igmp_config_t *config;
+ igmp_group_t *group;
+
+ /*
+ * inform interest parties
+ */
+ group = igmp_group_get (src->group);
+ config = igmp_config_get (group->config);
+
+ igmp_event (IGMP_FILTER_MODE_EXCLUDE,
+ config->sw_if_index, src->key, group->key);
+ }
+
+ igmp_group_src_remove (group, src);
+ igmp_src_free (src);
+
+ if (0 == igmp_group_n_srcs (group, IGMP_FILTER_MODE_INCLUDE))
+ igmp_group_clear (group);
+}
+
+igmp_src_t *
+igmp_src_alloc (u32 group_index, const igmp_key_t * skey, igmp_mode_t mode)
+{
+ igmp_main_t *im = &igmp_main;
+ igmp_src_t *src;
+
+ IGMP_DBG ("new-src: (%U)", format_igmp_key, skey);
+
+ pool_get (im->srcs, src);
+ memset (src, 0, sizeof (igmp_src_t));
+ src->mode = mode;
+ src->key = clib_mem_alloc (sizeof (*skey));
+ src->group = group_index;
+ clib_memcpy (src->key, skey, sizeof (*skey));
+
+ if (IGMP_MODE_ROUTER == mode)
+ {
+ igmp_config_t *config;
+ igmp_group_t *group;
+ /*
+ * start a timer that determines whether the source is still
+ * active o nthe link
+ */
+ src->timers[IGMP_SRC_TIMER_EXP] =
+ igmp_timer_schedule (igmp_timer_type_get (IGMP_TIMER_SRC),
+ src - im->srcs, igmp_src_exp, NULL);
+
+ /*
+ * inform interest parties
+ */
+ group = igmp_group_get (src->group);
+ config = igmp_config_get (group->config);
+
+ igmp_event (IGMP_FILTER_MODE_INCLUDE,
+ config->sw_if_index, src->key, group->key);
+ }
+ else
+ {
+ src->timers[IGMP_SRC_TIMER_EXP] = IGMP_TIMER_ID_INVALID;
+ }
+
+ return (src);
+}
+
+void
+igmp_src_refresh (igmp_src_t * src)
+{
+ IGMP_DBG ("refresh-src: (%U)", format_igmp_key, src->key);
+
+ igmp_timer_retire (&src->timers[IGMP_SRC_TIMER_EXP]);
+
+ src->timers[IGMP_SRC_TIMER_EXP] =
+ igmp_timer_schedule (igmp_timer_type_get (IGMP_TIMER_SRC),
+ igmp_src_index (src), igmp_src_exp, NULL);
+}
+
+void
+igmp_src_blocked (igmp_src_t * src)
+{
+ IGMP_DBG ("block-src: (%U)", format_igmp_key, src->key);
+
+ igmp_timer_retire (&src->timers[IGMP_SRC_TIMER_EXP]);
+
+ src->timers[IGMP_SRC_TIMER_EXP] =
+ igmp_timer_schedule (igmp_timer_type_get (IGMP_TIMER_LEAVE),
+ igmp_src_index (src), igmp_src_exp, NULL);
+}
+
+u32
+igmp_src_index (igmp_src_t * src)
+{
+ return (src - igmp_main.srcs);
+}
+
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/igmp/igmp_src.h b/src/plugins/igmp/igmp_src.h
new file mode 100644
index 00000000000..032066ec726
--- /dev/null
+++ b/src/plugins/igmp/igmp_src.h
@@ -0,0 +1,88 @@
+/*
+ *------------------------------------------------------------------
+ * 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.
+ *------------------------------------------------------------------
+ */
+
+#ifndef __IGMP_SOURCE_H__
+#define __IGMP_SOURCE_H__
+
+#include <igmp/igmp_types.h>
+
+/**
+ * IGMP Source timers
+ */
+typedef enum igmp_src_timer_t_
+{
+ /**
+ * On expiry the source has not been refreshed by a query
+ * and can now be reaped
+ */
+ IGMP_SRC_TIMER_EXP,
+} igmp_src_timer_t;
+
+#define IGMP_SRC_N_TIMERS (IGMP_SRC_TIMER_EXP + 1)
+
+/**
+ * @brief IGMP source
+ * The representation of a specified source address with in multicast group.
+ */
+typedef struct igmp_src_t_
+{
+ /**
+ * The source's key
+ */
+ igmp_key_t *key;
+
+ /**
+ * The liveness timer. Reset with each recieved report. on expiry
+ * the source is removed from the group.
+ */
+ u32 exp_timer;
+
+ /**
+ * The group this source is on
+ */
+ u32 group;
+
+ /**
+ * the mode that provided this source
+ */
+ igmp_mode_t mode;
+
+ /**
+ * Timers
+ */
+ u32 timers[IGMP_SRC_N_TIMERS];
+} igmp_src_t;
+
+extern void igmp_src_free (igmp_src_t * src);
+
+extern igmp_src_t *igmp_src_alloc (u32 group_index,
+ const igmp_key_t * skey, igmp_mode_t mode);
+
+extern u32 igmp_src_index (igmp_src_t * src);
+
+extern void igmp_src_refresh (igmp_src_t * src);
+extern void igmp_src_blocked (igmp_src_t * src);
+
+#endif
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/igmp/igmp_ssm_range.c b/src/plugins/igmp/igmp_ssm_range.c
new file mode 100644
index 00000000000..3d12712d3db
--- /dev/null
+++ b/src/plugins/igmp/igmp_ssm_range.c
@@ -0,0 +1,162 @@
+/*
+ *------------------------------------------------------------------
+ * Copyright (c) 2017 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_ssm_range.h>
+
+typedef struct igmp_group_prefix_t
+{
+ fib_prefix_t igp_prefix;
+ igmp_group_prefix_type_t igp_type;
+} igmp_group_prefix_t;
+
+static igmp_group_prefix_t *igmp_group_prefixs;
+
+u8 *
+format_igmp_group_prefix_type (u8 * s, va_list * args)
+{
+ igmp_group_prefix_type_t type = va_arg (*args, int);
+
+ switch (type)
+ {
+#define _(n,f) case IGMP_GROUP_PREFIX_TYPE_##f: return (format (s, "%s", #f));
+ foreach_igmp_group_prefix_type
+#undef _
+ }
+ return format (s, "unknown:%d", type);
+}
+
+static int
+igmp_group_prefix_cmp (const igmp_group_prefix_t * gp1,
+ const fib_prefix_t * p)
+{
+ return (fib_prefix_cmp (&gp1->igp_prefix, p));
+}
+
+void
+igmp_group_prefix_set (const fib_prefix_t * pfx,
+ igmp_group_prefix_type_t type)
+{
+ u32 pos;
+
+ pos =
+ vec_search_with_function (igmp_group_prefixs, pfx, igmp_group_prefix_cmp);
+
+ if ((~0 == pos) && (IGMP_GROUP_PREFIX_TYPE_SSM == type))
+ {
+ igmp_group_prefix_t gp = {
+ .igp_prefix = *pfx,
+ .igp_type = type,
+ };
+
+ vec_add1 (igmp_group_prefixs, gp);
+ }
+ if ((~0 != pos) && (IGMP_GROUP_PREFIX_TYPE_ASM == type))
+ {
+ vec_del1 (igmp_group_prefixs, pos);
+ }
+}
+
+static void
+igmp_ssm_range_populate (void)
+{
+ igmp_group_prefix_t *ssm_default;
+
+ vec_add2 (igmp_group_prefixs, ssm_default, 1);
+
+ ssm_default->igp_prefix.fp_addr.ip4.as_u32 = IGMP_SSM_DEFAULT;
+ ssm_default->igp_prefix.fp_proto = FIB_PROTOCOL_IP4;
+ ssm_default->igp_prefix.fp_len = 8;
+ ssm_default->igp_type = IGMP_GROUP_PREFIX_TYPE_SSM;
+}
+
+igmp_group_prefix_type_t
+igmp_group_prefix_get_type (const ip46_address_t * gaddr)
+{
+ igmp_group_prefix_t *igp;
+
+ vec_foreach (igp, igmp_group_prefixs)
+ {
+ if (ip4_destination_matches_route (&ip4_main,
+ &gaddr->ip4,
+ &igp->igp_prefix.fp_addr.ip4,
+ igp->igp_prefix.fp_len))
+ return (IGMP_GROUP_PREFIX_TYPE_SSM);
+ }
+
+ return (IGMP_GROUP_PREFIX_TYPE_ASM);
+}
+
+void
+igmp_ssm_range_walk (igmp_ssm_range_walk_t fn, void *ctx)
+{
+ igmp_group_prefix_t *igp;
+
+ vec_foreach (igp, igmp_group_prefixs)
+ {
+ if (WALK_STOP == fn (&igp->igp_prefix, igp->igp_type, ctx))
+ break;
+ }
+}
+
+static clib_error_t *
+igmp_ssm_range_show (vlib_main_t * vm,
+ unformat_input_t * input, vlib_cli_command_t * cmd)
+{
+ igmp_group_prefix_t *igp;
+
+ vec_foreach (igp, igmp_group_prefixs)
+ {
+ vlib_cli_output (vm, "%U => %U",
+ format_fib_prefix, &igp->igp_prefix,
+ format_igmp_group_prefix_type, igp->igp_type);
+ }
+ return (NULL);
+}
+
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (igmp_show_timers_command, static) = {
+ .path = "show igmp ssm-ranges",
+ .short_help = "show igmp ssm-ranges",
+ .function = igmp_ssm_range_show,
+};
+/* *INDENT-ON* */
+
+static clib_error_t *
+igmp_ssm_range_init (vlib_main_t * vm)
+{
+ clib_error_t *error;
+
+ if ((error = vlib_call_init_function (vm, igmp_init)))
+ return error;
+
+ igmp_ssm_range_populate ();
+
+ IGMP_DBG ("ssm-range-initialized");
+
+ return (error);
+}
+
+VLIB_INIT_FUNCTION (igmp_ssm_range_init);
+
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/igmp/igmp_ssm_range.h b/src/plugins/igmp/igmp_ssm_range.h
new file mode 100644
index 00000000000..30180ec1acd
--- /dev/null
+++ b/src/plugins/igmp/igmp_ssm_range.h
@@ -0,0 +1,58 @@
+/*
+ *------------------------------------------------------------------
+ * Copyright (c) 2017 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.
+ *------------------------------------------------------------------
+ */
+
+#ifndef __IGMP_SSM_RANGE_H__
+#define __IGMP_SSM_RANGE_H__
+
+#include <igmp/igmp.h>
+
+/**
+ * Make sure this remains in-sync with the .api enum definition
+ */
+#define foreach_igmp_group_prefix_type \
+ _ (0x0, ASM) \
+ _ (0x1, SSM)
+
+typedef enum igmp_group_prefix_type_t_
+{
+#define _(n,f) IGMP_GROUP_PREFIX_TYPE_##f = n,
+ foreach_igmp_group_prefix_type
+#undef _
+} igmp_group_prefix_type_t;
+
+extern igmp_group_prefix_type_t igmp_group_prefix_get_type (const
+ ip46_address_t *
+ gaddr);
+
+extern void igmp_group_prefix_set (const fib_prefix_t * pfx,
+ igmp_group_prefix_type_t type);
+
+typedef walk_rc_t (*igmp_ssm_range_walk_t) (const fib_prefix_t * pfx,
+ igmp_group_prefix_type_t type,
+ void *ctx);
+
+extern void igmp_ssm_range_walk (igmp_ssm_range_walk_t fn, void *ctx);
+
+#endif
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/igmp/igmp_timer.c b/src/plugins/igmp/igmp_timer.c
new file mode 100644
index 00000000000..278b7db917e
--- /dev/null
+++ b/src/plugins/igmp/igmp_timer.c
@@ -0,0 +1,241 @@
+/*
+ *------------------------------------------------------------------
+ * Copyright (c) 2017 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_timer.h>
+#include <igmp/igmp.h>
+
+/**
+ * Default timer values as per RFC
+ */
+
+static igmp_timer_type_t igmp_default_timer_values[] = {
+ [IGMP_TIMER_QUERY] = 60,
+ [IGMP_TIMER_SRC] = (3 * 60),
+ [IGMP_TIMER_LEAVE] = 60,
+ [IGMP_TIMER_REPORT_INTERVAL] = 1,
+};
+
+#define IGMP_N_TIMERS (IGMP_TIMER_REPORT_INTERVAL+1)
+
+/**
+ * Timer
+ */
+typedef struct igmp_timer_t_
+{
+ /** Expiration timer */
+ f64 exp_time;
+
+ /** Call-back function to invoke on expiry */
+ igmp_timer_function_t func;
+
+ /** index of the object that scheduled the timer */
+ u32 obj;
+
+ /** Data registered by the client and passed back when the timer expires */
+ void *data;
+} igmp_timer_t;
+
+enum
+{
+ IGMP_PROCESS_EVENT_UPDATE_TIMER = 1,
+} igmp_process_event_t;
+
+/**
+ * pool of timers
+ */
+static igmp_timer_t *timer_pool;
+
+/**
+ * Vector of pending timers
+ */
+static u32 *pending_timers;
+
+u32
+igmp_timer_type_get (igmp_timer_type_t t)
+{
+ ASSERT (t < IGMP_N_TIMERS);
+ return (igmp_default_timer_values[t]);
+}
+
+void
+igmp_timer_type_set (igmp_timer_type_t t, u32 v)
+{
+ ASSERT (t < IGMP_N_TIMERS);
+ igmp_default_timer_values[t] = v;
+}
+
+
+static int
+igmp_timer_compare (const void *_v1, const void *_v2)
+{
+ const u32 *i1 = _v1, *i2 = _v2;
+ const igmp_timer_t *t1, *t2;
+ f64 dt;
+
+ t1 = pool_elt_at_index (timer_pool, *i1);
+ t2 = pool_elt_at_index (timer_pool, *i2);
+
+ dt = t2->exp_time - t1->exp_time;
+
+ return (dt < 0 ? -1 : (dt > 0 ? +1 : 0));
+}
+
+/** \brief igmp get next timer
+ @param im - igmp main
+
+ Get next timer.
+*/
+u32
+igmp_get_next_timer (void)
+{
+ if (0 == vec_len (pending_timers))
+ return (IGMP_TIMER_ID_INVALID);
+
+ return (pending_timers[vec_len (pending_timers) - 1]);
+}
+
+void *
+igmp_timer_get_data (igmp_timer_id_t tid)
+{
+ igmp_timer_t *timer;
+
+ timer = pool_elt_at_index (timer_pool, tid);
+
+ return (timer->data);
+}
+
+void
+igmp_timer_set_data (igmp_timer_id_t tid, void *data)
+{
+ igmp_timer_t *timer;
+
+ timer = pool_elt_at_index (timer_pool, tid);
+
+ timer->data = data;
+}
+
+int
+igmp_timer_is_running (igmp_timer_id_t tid)
+{
+ return (IGMP_TIMER_ID_INVALID == tid);
+}
+
+/** \brief igmp timer process
+ @param vm - vlib main
+ @param rt - vlib runtime node
+ @param f - vlib frame
+
+ Handle igmp timers.
+*/
+static uword
+igmp_timer_process (vlib_main_t * vm, vlib_node_runtime_t * rt,
+ vlib_frame_t * f)
+{
+ uword *event_data = 0, event_type;
+ igmp_timer_id_t tid;
+ igmp_timer_t *timer;
+
+ tid = IGMP_TIMER_ID_INVALID;
+
+ while (1)
+ {
+ /* suspend util timer expires */
+ if (IGMP_TIMER_ID_INVALID != tid)
+ {
+ timer = pool_elt_at_index (timer_pool, tid);
+ vlib_process_wait_for_event_or_clock
+ (vm, timer->exp_time - vlib_time_now (vm));
+ }
+ else
+ vlib_process_wait_for_event (vm);
+
+ event_type = vlib_process_get_events (vm, &event_data);
+ vec_reset_length (event_data);
+
+ if (event_type == IGMP_PROCESS_EVENT_UPDATE_TIMER)
+ goto next_timer;
+
+ /* timer expired */
+ ASSERT (tid != IGMP_TIMER_ID_INVALID);
+
+ timer = pool_elt_at_index (timer_pool, tid);
+ ASSERT (timer->func != NULL);
+ timer->func (timer->obj, timer->data);
+
+ next_timer:
+ tid = igmp_get_next_timer ();
+ }
+ return 0;
+}
+
+/* *INDENT-OFF* */
+VLIB_REGISTER_NODE (igmp_timer_process_node) =
+{
+ .function = igmp_timer_process,
+ .type = VLIB_NODE_TYPE_PROCESS,
+ .name = "igmp-timer-process",
+ .n_next_nodes = 0,
+};
+/* *INDENT-ON* */
+
+igmp_timer_id_t
+igmp_timer_schedule (f64 when, u32 obj, igmp_timer_function_t fn, void *data)
+{
+ igmp_timer_t *timer;
+ vlib_main_t *vm;
+
+ ASSERT (fn);
+
+ vm = vlib_get_main ();
+ pool_get (timer_pool, timer);
+
+ timer->exp_time = vlib_time_now (vm) + when;
+ timer->obj = obj;
+ timer->func = fn;
+ timer->data = data;
+
+ vec_add1 (pending_timers, timer - timer_pool);
+
+ vec_sort_with_function (pending_timers, igmp_timer_compare);
+
+ vlib_process_signal_event (vm, igmp_timer_process_node.index,
+ IGMP_PROCESS_EVENT_UPDATE_TIMER, 0);
+
+ return (timer - timer_pool);
+}
+
+void
+igmp_timer_retire (igmp_timer_id_t * tid)
+{
+ if (IGMP_TIMER_ID_INVALID == *tid)
+ return;
+ vec_del1 (pending_timers, vec_search (pending_timers, *tid));
+ pool_put_index (timer_pool, *tid);
+ *tid = IGMP_TIMER_ID_INVALID;
+
+ vlib_process_signal_event (vlib_get_main (),
+ igmp_timer_process_node.index,
+ IGMP_PROCESS_EVENT_UPDATE_TIMER, 0);
+}
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/igmp/igmp_timer.h b/src/plugins/igmp/igmp_timer.h
new file mode 100644
index 00000000000..4847d6f6f6f
--- /dev/null
+++ b/src/plugins/igmp/igmp_timer.h
@@ -0,0 +1,84 @@
+/*
+ *------------------------------------------------------------------
+ * Copyright (c) 2017 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.
+ *------------------------------------------------------------------
+ */
+
+#ifndef _IGMP_TIMER_H_
+#define _IGMP_TIMER_H_
+
+#include <vlib/vlib.h>
+
+/**
+ * The id of a running timer
+ */
+typedef u32 igmp_timer_id_t;
+
+#define IGMP_TIMER_ID_INVALID (~0)
+
+/**
+ * A call-back function invoked when a timer expires;
+ * @param obj - the [pool] index of the object that scheduled the timer
+ * @param data - Data registered by the client at schedule time.
+ */
+typedef void (*igmp_timer_function_t) (u32 obj, void *data);
+
+/**
+ * @brief
+ * Scehdule a timer to expire in 'when' seconds
+ *
+ */
+extern igmp_timer_id_t igmp_timer_schedule (f64 when,
+ u32 obj,
+ igmp_timer_function_t fn,
+ void *data);
+
+extern void igmp_timer_retire (igmp_timer_id_t * tid);
+extern int igmp_timer_is_running (igmp_timer_id_t tid);
+
+extern f64 igmp_timer_get_expiry_time (igmp_timer_id_t t);
+extern void *igmp_timer_get_data (igmp_timer_id_t t);
+extern void igmp_timer_set_data (igmp_timer_id_t t, void *data);
+
+/**
+ * IGMP timer types and their values
+ * QUERY - the general query timer
+ * SRC - source expiration
+ * LEAVE - leave latency
+ */
+#define foreach_igmp_timer_type \
+ _ (0x1, QUERY) \
+ _ (0x2, SRC) \
+ _ (0x3, LEAVE) \
+ _ (0x4, REPORT_INTERVAL)
+
+typedef enum igmp_timer_type_t_
+{
+#define _(n,f) IGMP_TIMER_##f = n,
+ foreach_igmp_timer_type
+#undef _
+} igmp_timer_type_t;
+
+extern u32 igmp_timer_type_get (igmp_timer_type_t t);
+extern void igmp_timer_type_set (igmp_timer_type_t t, u32 v);
+
+#endif /* IGMP_TIMER_H_ */
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/igmp/igmp_types.h b/src/plugins/igmp/igmp_types.h
new file mode 100644
index 00000000000..0ed879e5d2e
--- /dev/null
+++ b/src/plugins/igmp/igmp_types.h
@@ -0,0 +1,77 @@
+/*
+ *------------------------------------------------------------------
+ * 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.
+ *------------------------------------------------------------------
+ */
+
+#ifndef __IGMP_TYPES_H__
+#define __IGMP_TYPES_H__
+
+#include <vnet/ip/ip.h>
+
+/** \brief IGMP source - where to the request for state arrive from
+ * host - from an API/CLI command to add the state
+ * network - from a received report
+ * Each source could be mode from both modes, so these are flags.
+ */
+#define foreach_igmp_mode \
+ _ (1, HOST) \
+ _ (2, ROUTER) \
+
+typedef enum igmp_mode_t_
+{
+#define _(n,f) IGMP_MODE_##f = n,
+ foreach_igmp_mode
+#undef _
+} igmp_mode_t;
+
+typedef enum igmp_msg_type_t_
+{
+ IGMP_MSG_REPORT,
+ IGMP_MSG_QUERY,
+} igmp_msg_type_t;
+
+/**
+ * @brief IGMP Key
+ * Used to index groups within an interface config and sources within a list
+ */
+typedef ip46_address_t igmp_key_t;
+
+/**
+ * @brief IGMP filter mode
+ * Exclude all source address except this one
+ * Include only this source address
+ */
+#define foreach_igmp_filter_mode \
+ _ (1, INCLUDE) \
+ _ (0, EXCLUDE) \
+
+typedef enum igmp_filter_mode_t_
+{
+#define _(n,f) IGMP_FILTER_MODE_##f = n,
+ foreach_igmp_filter_mode
+#undef _
+} igmp_filter_mode_t;
+
+#define IGMP_N_FILTER_MODES 2
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
+
+#endif
diff --git a/src/vlibapi/api_helper_macros.h b/src/vlibapi/api_helper_macros.h
index 5746f071cdb..626d8d4fc95 100644
--- a/src/vlibapi/api_helper_macros.h
+++ b/src/vlibapi/api_helper_macros.h
@@ -115,7 +115,7 @@ vnet_sw_if_index_is_api_valid (u32 sw_if_index)
}
#define VALIDATE_SW_IF_INDEX(mp) \
- do { u32 __sw_if_index = ntohl(mp->sw_if_index); \
+ do { u32 __sw_if_index = ntohl((mp)->sw_if_index); \
if (!vnet_sw_if_index_is_api_valid(__sw_if_index)) { \
rv = VNET_API_ERROR_INVALID_SW_IF_INDEX; \
goto bad_sw_if_index; \
@@ -129,7 +129,7 @@ bad_sw_if_index: \
} while (0);
#define VALIDATE_RX_SW_IF_INDEX(mp) \
- do { u32 __rx_sw_if_index = ntohl(mp->rx_sw_if_index); \
+ do { u32 __rx_sw_if_index = ntohl((mp)->rx_sw_if_index); \
if (!vnet_sw_if_index_is_api_valid(__rx_sw_if_index)) { \
rv = VNET_API_ERROR_INVALID_SW_IF_INDEX; \
goto bad_rx_sw_if_index; \
diff --git a/src/vnet.am b/src/vnet.am
index bb74cc85201..b9ae4083114 100644
--- a/src/vnet.am
+++ b/src/vnet.am
@@ -350,6 +350,7 @@ libvnet_la_SOURCES += \
vnet/ip/icmp4.c \
vnet/ip/icmp6.c \
vnet/ip/ip46_cli.c \
+ vnet/ip/ip_types_api.c \
vnet/ip/ip4_format.c \
vnet/ip/ip4_forward.c \
vnet/ip/ip4_punt_drop.c \
diff --git a/src/vnet/fib/fib_test.c b/src/vnet/fib/fib_test.c
index e46f670269a..95540834f52 100644
--- a/src/vnet/fib/fib_test.c
+++ b/src/vnet/fib/fib_test.c
@@ -851,9 +851,6 @@ fib_test_v4 (void)
/*
* if the IGMP plugin is loaded this adds two more entries to the v4 MFIB
*/
- if (vlib_get_plugin_symbol("igmp_plugin.so", "igmp_listen"))
- PNBR += 2;
-
FIB_TEST((0 == fib_path_list_db_size()), "path list DB is empty");
FIB_TEST((PNBR == fib_path_list_pool_size()), "path list pool size is %d",
fib_path_list_pool_size());
@@ -4375,9 +4372,6 @@ fib_test_v6 (void)
/*
* if the IGMP plugin is loaded this adds two more entries to the v4 MFIB
*/
- if (vlib_get_plugin_symbol("igmp_plugin.so", "igmp_listen"))
- PNPS += 2;
-
FIB_TEST((0 == fib_path_list_db_size()), "path list DB is empty");
FIB_TEST((PNPS == fib_path_list_pool_size()), "path list pool size is %d",
fib_path_list_pool_size());
diff --git a/src/vnet/ip/igmp_packet.h b/src/vnet/ip/igmp_packet.h
index a8e9db6d9ab..cd4a40d77ff 100644
--- a/src/vnet/ip/igmp_packet.h
+++ b/src/vnet/ip/igmp_packet.h
@@ -63,32 +63,46 @@ typedef enum
#define _(n,f) IGMP_TYPE_##f = n,
foreach_igmp_type
#undef _
-} igmp_type_t;
+} __attribute__ ((packed)) igmp_type_t;
typedef struct
{
- igmp_type_t type:8;
+ igmp_type_t type;
u8 code;
u16 checksum;
} igmp_header_t;
-typedef struct
+/**
+ * Calculate the maximum response time allowed from the header.
+ * - RFC 3367 Section 4.1.1
+ */
+always_inline f64
+igmp_header_get_max_resp_time (const igmp_header_t * header)
{
- /* membership_query, version <= 2 reports. */
- igmp_header_t header;
+ f64 qqi;
- /* Multicast destination address. */
- ip4_address_t dst;
-} igmp_message_t;
+ if (header->code < 128)
+ qqi = header->code;
+ else
+ {
+ u8 mant = header->code << 4;
+ u8 exp = (header->code & 0x7) << 1;
+
+ qqi = ((mant | 0x10) << (exp + 3));
+ }
+
+ /* Querier's Query Interval (QQI), is represented in units of seconds */
+ return (qqi / 10);
+}
typedef struct
{
/* type 0x11 (IGMPv3) */
igmp_header_t header;
- ip4_address_t dst;
+ ip4_address_t group_address;
/* Reserved, Suppress Router-Side Processing flag and
Querier's Robustness Variable RRRRSQQQ. */
@@ -101,11 +115,25 @@ typedef struct
ip4_address_t src_addresses[0];
} igmp_membership_query_v3_t;
+always_inline u32
+igmp_membership_query_v3_length (const igmp_membership_query_v3_t * q)
+{
+ return (sizeof (*q) +
+ (sizeof (ip4_address_t) *
+ clib_net_to_host_u16 (q->n_src_addresses)));
+}
+
+always_inline int
+igmp_membership_query_v3_is_geeral (const igmp_membership_query_v3_t * q)
+{
+ return (0 == q->group_address.as_u32);
+}
+
#define foreach_igmp_membership_group_v3_type \
- _ (1, mode_is_filter_include) \
- _ (2, mode_is_filter_exclude) \
- _ (3, change_to_filter_include) \
- _ (4, change_to_filter_exclude) \
+ _ (1, mode_is_include) \
+ _ (2, mode_is_exclude) \
+ _ (3, change_to_include) \
+ _ (4, change_to_exclude) \
_ (5, allow_new_sources) \
_ (6, block_old_sources)
@@ -114,11 +142,11 @@ typedef enum
#define _(n,f) IGMP_MEMBERSHIP_GROUP_##f = n,
foreach_igmp_membership_group_v3_type
#undef _
-} igmp_membership_group_v3_type_t;
+} __attribute__ ((packed)) igmp_membership_group_v3_type_t;
typedef struct
{
- igmp_membership_group_v3_type_t type:8;
+ igmp_membership_group_v3_type_t type;
/* Number of 32 bit words of aux data after source addresses. */
u8 n_aux_u32s;
@@ -126,12 +154,20 @@ typedef struct
/* Number of source addresses that follow. */
u16 n_src_addresses;
- /* Destination multicast address. */
- ip4_address_t dst_address;
+ /* Destination multicast group address. */
+ ip4_address_t group_address;
ip4_address_t src_addresses[0];
} igmp_membership_group_v3_t;
+always_inline u32
+igmp_membership_group_v3_length (const igmp_membership_group_v3_t * g)
+{
+ return (sizeof (*g) +
+ (sizeof (ip4_address_t) *
+ clib_net_to_host_u16 (g->n_src_addresses)));
+}
+
always_inline igmp_membership_group_v3_t *
igmp_membership_group_v3_next (igmp_membership_group_v3_t * g)
{
@@ -153,6 +189,24 @@ typedef struct
igmp_membership_group_v3_t groups[0];
} igmp_membership_report_v3_t;
+always_inline u32
+igmp_membership_report_v3_length (const igmp_membership_report_v3_t * r)
+{
+ const igmp_membership_group_v3_t *g;
+ u32 len, ii, glen;
+
+ len = sizeof (igmp_membership_report_v3_t);
+ g = r->groups;
+
+ for (ii = 0; ii < clib_net_to_host_u16 (r->n_groups); ii++)
+ {
+ glen = igmp_membership_group_v3_length (g);
+ g = (const igmp_membership_group_v3_t *) (((u8 *) g) + glen);
+ len += glen;
+ }
+ return (len);
+}
+
/* IP6 flavor of IGMP is called MLD which is embedded in ICMP6. */
typedef struct
{
diff --git a/src/vnet/ip/ip.api b/src/vnet/ip/ip.api
index 228e59158ff..4811a349d38 100644
--- a/src/vnet/ip/ip.api
+++ b/src/vnet/ip/ip.api
@@ -20,6 +20,7 @@
*/
option version = "1.3.0";
+import "vnet/ip/ip_types.api";
import "vnet/fib/fib_types.api";
/** \brief Add / del table request
diff --git a/src/vnet/ip/ip4_error.h b/src/vnet/ip/ip4_error.h
index fa93e8673e5..52c43adb12c 100644
--- a/src/vnet/ip/ip4_error.h
+++ b/src/vnet/ip/ip4_error.h
@@ -59,7 +59,7 @@
_ (SRC_LOOKUP_MISS, "ip4 source lookup miss") \
_ (DROP, "ip4 drop") \
_ (PUNT, "ip4 punt") \
- _ (SAME_INTERFACE, "ip4 egrees interface same as ingress") \
+ _ (SAME_INTERFACE, "ip4 egress interface same as ingress") \
\
/* Errors signalled by ip4-local. */ \
_ (UNKNOWN_PROTOCOL, "unknown ip protocol") \
diff --git a/src/vnet/ip/ip_types.api b/src/vnet/ip/ip_types.api
index ec6b9d0024c..72eadaf92df 100644
--- a/src/vnet/ip/ip_types.api
+++ b/src/vnet/ip/ip_types.api
@@ -1,3 +1,4 @@
+/* Hey Emacs use -*- mode: C -*- */
/*
* Copyright (c) 2018 Cisco and/or its affiliates.
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -35,3 +36,8 @@ typedef address {
vl_api_address_family_t af;
vl_api_address_union_t un;
};
+
+typedef prefix {
+ vl_api_address_t address;
+ u8 address_length;
+};
diff --git a/src/vnet/ip/ip_types_api.c b/src/vnet/ip/ip_types_api.c
new file mode 100644
index 00000000000..7fa8e404c78
--- /dev/null
+++ b/src/vnet/ip/ip_types_api.c
@@ -0,0 +1,105 @@
+/*
+ * 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 <vnet/ip/ip_types_api.h>
+
+#define vl_typedefs /* define message structures */
+#include <vnet/vnet_all_api_h.h>
+#undef vl_typedefs
+
+#define vl_endianfun /* define message structures */
+#include <vnet/vnet_all_api_h.h>
+#undef vl_endianfun
+
+/* instantiate all the print functions we know about */
+#define vl_print(handle, ...) vlib_cli_output (handle, __VA_ARGS__)
+#define vl_printfun
+#include <vnet/vnet_all_api_h.h>
+#undef vl_printfun
+
+
+void
+ip_address_decode (const vl_api_address_t * in, ip46_address_t * out)
+{
+ switch (in->af)
+ {
+ case ADDRESS_IP4:
+ memset (out, 0, sizeof (*out));
+ clib_memcpy (&out->ip4, &in->un.ip4, sizeof (out->ip4));
+ break;
+ case ADDRESS_IP6:
+ clib_memcpy (&out->ip6, &in->un.ip6, sizeof (out->ip6));
+ break;
+ }
+}
+
+void
+ip_address_encode (const ip46_address_t * in, vl_api_address_t * out)
+{
+ if (ip46_address_is_ip4 (in))
+ {
+ memset (out, 0, sizeof (*out));
+ out->af = ADDRESS_IP4;
+ clib_memcpy (&out->un.ip4, &in->ip4, sizeof (out->un.ip4));
+ }
+ else
+ {
+ out->af = ADDRESS_IP6;
+ clib_memcpy (&out->un.ip6, &in->ip6, sizeof (out->un.ip6));
+ }
+}
+
+void
+ip_prefix_decode (const vl_api_prefix_t * in, fib_prefix_t * out)
+{
+ switch (in->address.af)
+ {
+ case ADDRESS_IP4:
+ out->fp_proto = FIB_PROTOCOL_IP4;
+ break;
+ case ADDRESS_IP6:
+ out->fp_proto = FIB_PROTOCOL_IP6;
+ break;
+ }
+ out->fp_len = in->address_length;
+ ip_address_decode (&in->address, &out->fp_addr);
+}
+
+void
+ip_prefix_encode (const fib_prefix_t * in, vl_api_prefix_t * out)
+{
+ switch (in->fp_proto)
+ {
+ case FIB_PROTOCOL_IP4:
+ out->address.af = ADDRESS_IP4;
+ break;
+ case FIB_PROTOCOL_IP6:
+ out->address.af = ADDRESS_IP6;
+ break;
+ case FIB_PROTOCOL_MPLS:
+ ASSERT (0);
+ break;
+ }
+ out->address_length = in->fp_len;
+ ip_address_encode (&in->fp_addr, &out->address);
+}
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/vnet/ip/ip_types_api.h b/src/vnet/ip/ip_types_api.h
new file mode 100644
index 00000000000..2ad59ae438e
--- /dev/null
+++ b/src/vnet/ip/ip_types_api.h
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+#ifndef __IP_TYPES_API_H__
+#define __IP_TYPES_API_H__
+
+/**
+ * Conversion functions to/from (decode/encode) API types to VPP internal types
+ */
+
+#include <vnet/ip/ip.h>
+#include <vnet/fib/fib_types.h>
+
+/**
+ * Forward declarations so we need not #include the API definitions here
+ */
+struct _vl_api_address;
+struct _vl_api_prefix;
+
+extern void ip_address_decode (const struct _vl_api_address *in,
+ ip46_address_t * out);
+extern void ip_address_encode (const ip46_address_t * in,
+ struct _vl_api_address *out);
+
+extern void ip_prefix_decode (const struct _vl_api_prefix *in,
+ fib_prefix_t * out);
+extern void ip_prefix_encode (const fib_prefix_t * in,
+ struct _vl_api_prefix *out);
+
+#endif
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/vnet/mfib/mfib_types.h b/src/vnet/mfib/mfib_types.h
index ec68c1a5873..2ab0f7d4fef 100644
--- a/src/vnet/mfib/mfib_types.h
+++ b/src/vnet/mfib/mfib_types.h
@@ -170,6 +170,7 @@ typedef enum mfib_source_t_
MFIB_SOURCE_VXLAN_GPE,
MFIB_SOURCE_RR,
MFIB_SOURCE_GENEVE,
+ MFIB_SOURCE_IGMP,
MFIB_SOURCE_DEFAULT_ROUTE,
} mfib_source_t;
@@ -184,6 +185,7 @@ typedef enum mfib_source_t_
[MFIB_SOURCE_VXLAN_GPE] = "VXLAN-GPE", \
[MFIB_SOURCE_RR] = "Recursive-resolution", \
[MFIB_SOURCE_GENEVE] = "Geneve", \
+ [MFIB_SOURCE_IGMP] = "IGMP", \
[MFIB_SOURCE_DEFAULT_ROUTE] = "Default Route", \
}
diff --git a/src/vppinfra/vec.h b/src/vppinfra/vec.h
index f233ae1c48f..1b0378fe063 100644
--- a/src/vppinfra/vec.h
+++ b/src/vppinfra/vec.h
@@ -953,6 +953,27 @@ do { \
_v(i); \
})
+/** \brief Search a vector for the index of the entry that matches.
+
+ @param v1 Pointer to a vector
+ @param v2 Pointer to entry to match
+ @param fn Comparison function !0 => match
+ @return index of match or ~0
+*/
+#define vec_search_with_function(v,E,fn) \
+({ \
+ word _v(i) = 0; \
+ while (_v(i) < vec_len(v)) \
+ { \
+ if (0 != fn(&(v)[_v(i)], (E))) \
+ break; \
+ _v(i)++; \
+ } \
+ if (_v(i) == vec_len(v)) \
+ _v(i) = ~0; \
+ _v(i); \
+})
+
/** \brief Sort a vector using the supplied element comparison function
@param vec vector to sort
diff --git a/test/framework.py b/test/framework.py
index fdaba2b84d9..4ecb66fe408 100644
--- a/test/framework.py
+++ b/test/framework.py
@@ -837,12 +837,13 @@ class VppTestCase(unittest.TestCase):
"Finished sleep (%s) - slept %ss (wanted %ss)" % (
remark, after - before, timeout))
- def send_and_assert_no_replies(self, intf, pkts, remark=""):
+ def send_and_assert_no_replies(self, intf, pkts, remark="", timeout=None):
self.vapi.cli("clear trace")
intf.add_stream(pkts)
self.pg_enable_capture(self.pg_interfaces)
self.pg_start()
- timeout = 1
+ if not timeout:
+ timeout = 1
for i in self.pg_interfaces:
i.get_capture(0, timeout=timeout)
i.assert_nothing_captured(remark=remark)
diff --git a/test/test_igmp.py b/test/test_igmp.py
index 22b6d2f02e3..cfdd1a8aed3 100644
--- a/test/test_igmp.py
+++ b/test/test_igmp.py
@@ -1,16 +1,20 @@
#!/usr/bin/env python
import unittest
-import socket
from framework import VppTestCase, VppTestRunner, running_extended_tests
from vpp_igmp import *
-from scapy.packet import Raw
from scapy.layers.l2 import Ether
from scapy.layers.inet import IP
from scapy.contrib.igmpv3 import *
from scapy.contrib.igmp import *
+from vpp_ip_route import find_mroute, VppIpTable
+
+
+class IgmpMode:
+ HOST = 1
+ ROUTER = 0
class TestIgmp(VppTestCase):
@@ -19,11 +23,16 @@ class TestIgmp(VppTestCase):
def setUp(self):
super(TestIgmp, self).setUp()
- self.create_pg_interfaces(range(2))
+ self.create_pg_interfaces(range(4))
self.sg_list = []
self.config_list = []
self.ip_addr = []
+ self.ip_table = VppIpTable(self, 1)
+ self.ip_table.add_vpp_config()
+
+ for pg in self.pg_interfaces[2:]:
+ pg.set_table_ip4(1)
for pg in self.pg_interfaces:
pg.admin_up()
pg.config_ip4()
@@ -33,6 +42,7 @@ class TestIgmp(VppTestCase):
for pg in self.pg_interfaces:
self.vapi.igmp_clear_interface(pg.sw_if_index)
pg.unconfig_ip4()
+ pg.set_table_ip4(0)
pg.admin_down()
super(TestIgmp, self).tearDown()
@@ -41,271 +51,589 @@ class TestIgmp(VppTestCase):
self.pg_enable_capture(self.pg_interfaces)
self.pg_start()
- def test_igmp_parse_report(self):
- """ IGMP parse Membership Report """
+ def test_igmp_flush(self):
+ """ IGMP Link Up/down and Flush """
#
- # VPP acts as a router
+ # FIX THIS. Link down.
#
- self.vapi.want_igmp_events(1)
- # hos sends join IGMP 'join'
- p_join = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
- IP(src=self.pg0.remote_ip4, dst='224.0.0.22',
- tos=0xc0, ttl=1,
- options=IPOption(copy_flag=1, optclass=0,
- option="router_alert",
- length=2, value=0)) /
- IGMPv3() /
- IGMPv3mr(numgrp=1) /
- IGMPv3gr(rtype=3, maddr="224.1.1.1", srcaddrs=["10.1.1.1"]))
+ def test_igmp_enable(self):
+ """ IGMP enable/disable on an interface
- self.send(self.pg0, p_join)
+ check for the addition/removal of the IGMP mroutes """
- # search for the corresponding state created in VPP
- dump = self.vapi.igmp_dump()
- self.assertEqual(len(dump), 1)
- self.assertEqual(dump[0].sw_if_index, self.pg0.sw_if_index)
- self.assertEqual(dump[0].gaddr,
- socket.inet_pton(socket.AF_INET,
- "224.1.1.1"))
- self.assertEqual(dump[0].saddr,
- socket.inet_pton(socket.AF_INET,
- "10.1.1.1"))
+ self.vapi.igmp_enable_disable(self.pg0.sw_if_index, 1, IGMP_MODE.HOST)
+ self.vapi.igmp_enable_disable(self.pg1.sw_if_index, 1, IGMP_MODE.HOST)
- # VPP sends a notification that a new group has been joined
- ev = self.vapi.wait_for_event(2, "igmp_event")
-
- self.assertEqual(ev.saddr,
- socket.inet_pton(socket.AF_INET,
- "10.1.1.1"))
- self.assertEqual(ev.gaddr,
- socket.inet_pton(socket.AF_INET,
- "224.1.1.1"))
- self.assertEqual(ev.is_join, 1)
-
- # host sends IGMP leave
- p_leave = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
- IP(src=self.pg0.remote_ip4, dst='224.0.0.22', tos=0xc0) /
- IGMPv3() /
- IGMPv3mr(numgrp=1) /
- IGMPv3gr(rtype=4, maddr="224.1.1.1", srcaddrs=["10.1.1.1"]))
-
- self.send(self.pg0, p_leave)
-
- # VPP sends a notification that a new group has been left
- ev = self.vapi.wait_for_event(2, "igmp_event")
-
- self.assertEqual(ev.saddr,
- socket.inet_pton(socket.AF_INET,
- "10.1.1.1"))
- self.assertEqual(ev.gaddr,
- socket.inet_pton(socket.AF_INET,
- "224.1.1.1"))
- self.assertEqual(ev.is_join, 0)
-
- # state gone
- dump = self.vapi.igmp_dump()
- self.assertFalse(dump)
+ self.assertTrue(find_mroute(self, "224.0.0.1", "0.0.0.0", 32))
+ self.assertTrue(find_mroute(self, "224.0.0.22", "0.0.0.0", 32))
- # resend the join
- self.send(self.pg0, p_join)
- dump = self.vapi.igmp_dump()
- self.assertEqual(len(dump), 1)
- self.assertEqual(dump[0].sw_if_index, self.pg0.sw_if_index)
- self.assertEqual(dump[0].gaddr,
- socket.inet_pton(socket.AF_INET,
- "224.1.1.1"))
- self.assertEqual(dump[0].saddr,
- socket.inet_pton(socket.AF_INET,
- "10.1.1.1"))
-
- # IGMP block
- p_block = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
- IP(src=self.pg0.remote_ip4, dst='224.0.0.22', tos=0xc0) /
- IGMPv3() /
- IGMPv3mr(numgrp=1) /
- IGMPv3gr(rtype=6, maddr="224.1.1.1", srcaddrs=["10.1.1.1"]))
-
- self.send(self.pg0, p_block)
+ self.vapi.igmp_enable_disable(self.pg2.sw_if_index, 1, IGMP_MODE.HOST)
+ self.vapi.igmp_enable_disable(self.pg3.sw_if_index, 1, IGMP_MODE.HOST)
- dump = self.vapi.igmp_dump()
- self.assertFalse(dump)
+ self.assertTrue(find_mroute(self, "224.0.0.1", "0.0.0.0", 32,
+ table_id=1))
+ self.assertTrue(find_mroute(self, "224.0.0.22", "0.0.0.0", 32,
+ table_id=1))
+ self.vapi.igmp_enable_disable(self.pg0.sw_if_index, 0, IGMP_MODE.HOST)
+ self.vapi.igmp_enable_disable(self.pg1.sw_if_index, 0, IGMP_MODE.HOST)
+ self.vapi.igmp_enable_disable(self.pg2.sw_if_index, 0, IGMP_MODE.HOST)
+ self.vapi.igmp_enable_disable(self.pg3.sw_if_index, 0, IGMP_MODE.HOST)
+
+ self.assertFalse(find_mroute(self, "224.0.0.1", "0.0.0.0", 32))
+ self.assertFalse(find_mroute(self, "224.0.0.22", "0.0.0.0", 32))
+ self.assertFalse(find_mroute(self, "224.0.0.1", "0.0.0.0", 32,
+ table_id=1))
+ self.assertFalse(find_mroute(self, "224.0.0.22", "0.0.0.0", 32,
+ table_id=1))
def verify_general_query(self, p):
ip = p[IP]
+ self.assertEqual(len(ip.options), 1)
+ self.assertEqual(ip.options[0].option, 20)
self.assertEqual(ip.dst, "224.0.0.1")
self.assertEqual(ip.proto, 2)
igmp = p[IGMPv3]
self.assertEqual(igmp.type, 0x11)
self.assertEqual(igmp.gaddr, "0.0.0.0")
- def test_igmp_send_query(self):
- """ IGMP send General Query """
+ def verify_group_query(self, p, grp, srcs):
+ ip = p[IP]
+ self.assertEqual(ip.dst, grp)
+ self.assertEqual(ip.proto, 2)
+ self.assertEqual(len(ip.options), 1)
+ self.assertEqual(ip.options[0].option, 20)
+ self.assertEqual(ip.proto, 2)
+ igmp = p[IGMPv3]
+ self.assertEqual(igmp.type, 0x11)
+ self.assertEqual(igmp.gaddr, grp)
+
+ def verify_report(self, rx, records):
+ ip = rx[IP]
+ self.assertEqual(rx[IP].dst, "224.0.0.22")
+ self.assertEqual(len(ip.options), 1)
+ self.assertEqual(ip.options[0].option, 20)
+ self.assertEqual(ip.proto, 2)
+ self.assertEqual(IGMPv3.igmpv3types[rx[IGMPv3].type],
+ "Version 3 Membership Report")
+ self.assertEqual(rx[IGMPv3mr].numgrp, len(records))
+
+ received = rx[IGMPv3mr].records
+
+ for ii in range(len(records)):
+ gr = received[ii]
+ r = records[ii]
+ self.assertEqual(IGMPv3gr.igmpv3grtypes[gr.rtype], r.type)
+ self.assertEqual(gr.numsrc, len(r.sg.saddrs))
+ self.assertEqual(gr.maddr, r.sg.gaddr)
+ self.assertEqual(len(gr.srcaddrs), len(r.sg.saddrs))
+
+ self.assertEqual(sorted(gr.srcaddrs),
+ sorted(r.sg.saddrs))
+
+ def add_group(self, itf, sg, n_pkts=2):
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ hs = VppHostState(self,
+ IGMP_FILTER.INCLUDE,
+ itf.sw_if_index,
+ sg)
+ hs.add_vpp_config()
+
+ capture = itf.get_capture(n_pkts, timeout=10)
+
+ # reports are transmitted twice due to default rebostness value=2
+ self.verify_report(capture[0],
+ [IgmpRecord(sg, "Allow New Sources")]),
+ self.verify_report(capture[1],
+ [IgmpRecord(sg, "Allow New Sources")]),
+
+ return hs
+
+ def remove_group(self, hs):
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ hs.remove_vpp_config()
+
+ capture = self.pg0.get_capture(1, timeout=10)
+
+ self.verify_report(capture[0],
+ [IgmpRecord(hs.sg, "Block Old Sources")])
+
+ def test_igmp_host(self):
+ """ IGMP Host functions """
#
- # VPP acts as a router.
- # Send a membership report so VPP builds state
+ # Enable interface for host functions
#
- p_mr = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
- IP(src=self.pg0.remote_ip4, dst='224.0.0.22') /
- IGMPv3() /
- IGMPv3mr(numgrp=1) /
- IGMPv3gr(rtype=3, maddr="224.1.1.1", srcaddrs=["10.1.1.1"]))
+ self.vapi.igmp_enable_disable(self.pg0.sw_if_index,
+ 1,
+ IGMP_MODE.HOST)
- self.send(self.pg0, p_mr)
- self.logger.info(self.vapi.cli("sh igmp config"))
+ #
+ # Add one S,G of state and expect a state-change event report
+ # indicating the addition of the S,G
+ #
+ h1 = self.add_group(self.pg0, IgmpSG("239.1.1.1", ["1.1.1.1"]))
+
+ # search for the corresponding state created in VPP
+ dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
+ self.assertEqual(len(dump), 1)
+ self.assertTrue(find_igmp_state(dump, self.pg0,
+ "239.1.1.1", "1.1.1.1"))
#
- # wait for VPP to send out the General Query
+ # Send a general query (to the all router's address)
+ # expect VPP to respond with a membership report
#
- capture = self.pg0.get_capture(1, timeout=61)
+ p_g = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IP(src=self.pg0.remote_ip4, dst='224.0.0.1', tos=0xc0) /
+ IGMPv3(type="Membership Query", mrcode=100) /
+ IGMPv3mq(gaddr="0.0.0.0"))
- self.verify_general_query(capture[0])
+ self.send(self.pg0, p_g)
+
+ capture = self.pg0.get_capture(1, timeout=10)
+ self.verify_report(capture[0],
+ [IgmpRecord(h1.sg, "Mode Is Include")])
#
- # the state will expire in 10 more seconds
+ # Group specific query
#
- self.sleep(10)
- self.assertFalse(self.vapi.igmp_dump())
+ p_gs = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IP(src=self.pg0.remote_ip4, dst='239.1.1.1', tos=0xc0,
+ options=[IPOption(copy_flag=1, optclass="control",
+ option="router_alert")]) /
+ IGMPv3(type="Membership Query", mrcode=100) /
+ IGMPv3mq(gaddr="239.1.1.1"))
- @unittest.skipUnless(running_extended_tests(), "part of extended tests")
- def test_igmp_src_exp(self):
- """ IGMP per source timer """
+ self.send(self.pg0, p_gs)
+
+ capture = self.pg0.get_capture(1, timeout=10)
+ self.verify_report(capture[0],
+ [IgmpRecord(h1.sg, "Mode Is Include")])
#
- # VPP Acts as a router
+ # A group and source specific query, with the source matching
+ # the source VPP has
#
+ p_gs1 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IP(src=self.pg0.remote_ip4, dst='239.1.1.1', tos=0xc0,
+ options=[IPOption(copy_flag=1, optclass="control",
+ option="router_alert")]) /
+ IGMPv3(type="Membership Query", mrcode=100) /
+ IGMPv3mq(gaddr="239.1.1.1", srcaddrs=["1.1.1.1"]))
- # Host join for (10.1.1.1,224.1.1.1)
- p_mr1 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
- IP(src=self.pg0.remote_ip4, dst='224.0.0.22') /
- IGMPv3() /
- IGMPv3mr(numgrp=1) /
- IGMPv3gr(rtype=3, maddr="224.1.1.1", srcaddrs=["10.1.1.1"]))
+ self.send(self.pg0, p_gs1)
- self.send(self.pg0, p_mr1)
+ capture = self.pg0.get_capture(1, timeout=10)
+ self.verify_report(capture[0],
+ [IgmpRecord(h1.sg, "Mode Is Include")])
- # VPP (router) sends General Query
- capture = self.pg0.get_capture(1, timeout=61)
+ #
+ # A group and source specific query, with the source NOT matching
+ # the source VPP has. There should be no response.
+ #
+ p_gs2 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IP(src=self.pg0.remote_ip4, dst='239.1.1.1', tos=0xc0,
+ options=[IPOption(copy_flag=1, optclass="control",
+ option="router_alert")]) /
+ IGMPv3(type="Membership Query", mrcode=100) /
+ IGMPv3mq(gaddr="239.1.1.1", srcaddrs=["1.1.1.2"]))
- self.verify_general_query(capture[0])
+ self.send_and_assert_no_replies(self.pg0, p_gs2, timeout=10)
- # host join for same G and another S: (10.1.1.2,224.1.1.1)
- # therefore leaving (10.1.1.1,224.1.1.1)
- p_mr2 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
- IP(src=self.pg0.remote_ip4, dst='224.0.0.22') /
- IGMPv3() /
- IGMPv3mr(numgrp=1) /
- IGMPv3gr(rtype=2, maddr="224.1.1.1", srcaddrs=["10.1.1.2"]))
+ #
+ # A group and source specific query, with the multiple sources
+ # one of which matches the source VPP has.
+ # The report should contain only the source VPP has.
+ #
+ p_gs3 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IP(src=self.pg0.remote_ip4, dst='239.1.1.1', tos=0xc0,
+ options=[IPOption(copy_flag=1, optclass="control",
+ option="router_alert")]) /
+ IGMPv3(type="Membership Query", mrcode=100) /
+ IGMPv3mq(gaddr="239.1.1.1",
+ srcaddrs=["1.1.1.1", "1.1.1.2", "1.1.1.3"]))
- self.send(self.pg0, p_mr2)
+ self.send(self.pg0, p_gs3)
- # wait for VPP to send general query
- capture = self.pg0.get_capture(1, timeout=61)
- self.verify_general_query(capture[0])
+ capture = self.pg0.get_capture(1, timeout=10)
+ self.verify_report(capture[0],
+ [IgmpRecord(h1.sg, "Mode Is Include")])
- # host leaves (10.1.1.2,224.1.1.1)
- p_l = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
- IP(src=self.pg0.remote_ip4, dst='224.0.0.22') /
- IGMPv3() /
- IGMPv3mr(numgrp=1) /
- IGMPv3gr(rtype=2, maddr="224.1.1.1", srcaddrs=["10.1.1.2"]))
+ #
+ # Two source and group specific queires in qucik sucession, the
+ # first does not have VPPs source the second does. then vice-versa
+ #
+ self.send(self.pg0, [p_gs2, p_gs1])
+ capture = self.pg0.get_capture(1, timeout=10)
+ self.verify_report(capture[0],
+ [IgmpRecord(h1.sg, "Mode Is Include")])
- self.send(self.pg0, p_l)
+ self.send(self.pg0, [p_gs1, p_gs2])
+ capture = self.pg0.get_capture(1, timeout=10)
+ self.verify_report(capture[0],
+ [IgmpRecord(h1.sg, "Mode Is Include")])
- # FIXME BUG
- p_l = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
- IP(src=self.pg0.remote_ip4, dst='224.0.0.22') /
- IGMPv3() /
- IGMPv3mr(numgrp=1) /
- IGMPv3gr(rtype=2, maddr="224.1.1.1", srcaddrs=["10.1.1.1"]))
- self.send(self.pg0, p_l)
+ #
+ # remove state, expect the report for the removal
+ #
+ self.remove_group(h1)
+
+ dump = self.vapi.igmp_dump()
+ self.assertFalse(dump)
#
- # host has left all groups, no state left.
+ # A group with multiple sources
#
- self.sleep(10)
- self.logger.error(self.vapi.cli("sh igmp config"))
- self.assertFalse(self.vapi.igmp_dump())
+ h2 = self.add_group(self.pg0,
+ IgmpSG("239.1.1.1",
+ ["1.1.1.1", "1.1.1.2", "1.1.1.3"]))
+
+ # search for the corresponding state created in VPP
+ dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
+ self.assertEqual(len(dump), 3)
+ for s in h2.sg.saddrs:
+ self.assertTrue(find_igmp_state(dump, self.pg0,
+ "239.1.1.1", s))
+ #
+ # Send a general query (to the all router's address)
+ # expect VPP to respond with a membership report will all sources
+ #
+ self.send(self.pg0, p_g)
- def test_igmp_query_resp(self):
- """ IGMP General Query response """
+ capture = self.pg0.get_capture(1, timeout=10)
+ self.verify_report(capture[0],
+ [IgmpRecord(h2.sg, "Mode Is Include")])
#
- # VPP acting as a host.
- # Add a listener in VPP for (10.1.1.1,244.1.1.1)
+ # Group and source specific query; some present some not
#
- self.config_list.append(
- VppIgmpConfig(
- self, self.pg0.sw_if_index, IgmpSG(
- socket.inet_pton(
- socket.AF_INET, "10.1.1.1"), socket.inet_pton(
- socket.AF_INET, "224.1.1.1"))))
- self.config_list[0].add_vpp_config()
+ p_gs = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IP(src=self.pg0.remote_ip4, dst='239.1.1.1', tos=0xc0,
+ options=[IPOption(copy_flag=1, optclass="control",
+ option="router_alert")]) /
+ IGMPv3(type="Membership Query", mrcode=100) /
+ IGMPv3mq(gaddr="239.1.1.1",
+ srcaddrs=["1.1.1.1", "1.1.1.2", "1.1.1.4"]))
- # verify state exists
- self.assertTrue(self.vapi.igmp_dump(self.pg0.sw_if_index))
+ self.send(self.pg0, p_gs)
+
+ capture = self.pg0.get_capture(1, timeout=10)
+ self.verify_report(capture[0],
+ [IgmpRecord(
+ IgmpSG('239.1.1.1', ["1.1.1.1", "1.1.1.2"]),
+ "Mode Is Include")])
#
- # Send a general query (from a router)
+ # add loads more groups
#
- p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
- IP(src=self.pg0.remote_ip4, dst='224.0.0.1', tos=0xc0) /
- IGMPv3(type=0x11, mrcode=100) /
- IGMPv3mq(gaddr="0.0.0.0"))
+ h3 = self.add_group(self.pg0,
+ IgmpSG("239.1.1.2",
+ ["2.1.1.1", "2.1.1.2", "2.1.1.3"]))
+ h4 = self.add_group(self.pg0,
+ IgmpSG("239.1.1.3",
+ ["3.1.1.1", "3.1.1.2", "3.1.1.3"]))
+ h5 = self.add_group(self.pg0,
+ IgmpSG("239.1.1.4",
+ ["4.1.1.1", "4.1.1.2", "4.1.1.3"]))
+ h6 = self.add_group(self.pg0,
+ IgmpSG("239.1.1.5",
+ ["5.1.1.1", "5.1.1.2", "5.1.1.3"]))
+ h7 = self.add_group(self.pg0,
+ IgmpSG("239.1.1.6",
+ ["6.1.1.1", "6.1.1.2",
+ "6.1.1.3", "6.1.1.4",
+ "6.1.1.5", "6.1.1.6",
+ "6.1.1.7", "6.1.1.8",
+ "6.1.1.9", "6.1.1.10",
+ "6.1.1.11", "6.1.1.12",
+ "6.1.1.13", "6.1.1.14",
+ "6.1.1.15", "6.1.1.16"]))
- self.send(self.pg0, p)
+ #
+ # general query.
+ # the order the groups come in is not important, so what is
+ # checked for is what VPP is sending today.
+ #
+ self.send(self.pg0, p_g)
+
+ capture = self.pg0.get_capture(1, timeout=10)
+
+ self.verify_report(capture[0],
+ [IgmpRecord(h3.sg, "Mode Is Include"),
+ IgmpRecord(h2.sg, "Mode Is Include"),
+ IgmpRecord(h6.sg, "Mode Is Include"),
+ IgmpRecord(h4.sg, "Mode Is Include"),
+ IgmpRecord(h5.sg, "Mode Is Include"),
+ IgmpRecord(h7.sg, "Mode Is Include")])
#
- # expect VPP to respond with a membership report for the
- # (10.1.1.1, 224.1.1.1) state
+ # modify a group to add and remove some sources
#
+ h7.sg = IgmpSG("239.1.1.6",
+ ["6.1.1.1", "6.1.1.2",
+ "6.1.1.5", "6.1.1.6",
+ "6.1.1.7", "6.1.1.8",
+ "6.1.1.9", "6.1.1.10",
+ "6.1.1.11", "6.1.1.12",
+ "6.1.1.13", "6.1.1.14",
+ "6.1.1.15", "6.1.1.16",
+ "6.1.1.17", "6.1.1.18"])
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ h7.add_vpp_config()
+
capture = self.pg0.get_capture(1, timeout=10)
+ self.verify_report(capture[0],
+ [IgmpRecord(IgmpSG("239.1.1.6",
+ ["6.1.1.17", "6.1.1.18"]),
+ "Allow New Sources"),
+ IgmpRecord(IgmpSG("239.1.1.6",
+ ["6.1.1.3", "6.1.1.4"]),
+ "Block Old Sources")])
- self.assertEqual(capture[0][IGMPv3].type, 0x22)
- self.assertEqual(capture[0][IGMPv3mr].numgrp, 1)
- self.assertEqual(capture[0][IGMPv3gr].rtype, 1)
- self.assertEqual(capture[0][IGMPv3gr].numsrc, 1)
- self.assertEqual(capture[0][IGMPv3gr].maddr, "224.1.1.1")
- self.assertEqual(len(capture[0][IGMPv3gr].srcaddrs), 1)
- self.assertEqual(capture[0][IGMPv3gr].srcaddrs[0], "10.1.1.1")
+ #
+ # add an additional groups with many sources so that each group
+ # consumes the link MTU. We should therefore see multiple state
+ # state reports when queried.
+ #
+ self.vapi.sw_interface_set_mtu(self.pg0.sw_if_index, [560, 0, 0, 0])
+
+ src_list = []
+ for i in range(128):
+ src_list.append("10.1.1.%d" % i)
+
+ h8 = self.add_group(self.pg0,
+ IgmpSG("238.1.1.1", src_list))
+ h9 = self.add_group(self.pg0,
+ IgmpSG("238.1.1.2", src_list))
+
+ self.send(self.pg0, p_g)
+
+ capture = self.pg0.get_capture(4, timeout=10)
+
+ self.verify_report(capture[0],
+ [IgmpRecord(h3.sg, "Mode Is Include"),
+ IgmpRecord(h2.sg, "Mode Is Include"),
+ IgmpRecord(h6.sg, "Mode Is Include"),
+ IgmpRecord(h4.sg, "Mode Is Include"),
+ IgmpRecord(h5.sg, "Mode Is Include")])
+ self.verify_report(capture[1],
+ [IgmpRecord(h8.sg, "Mode Is Include")])
+ self.verify_report(capture[2],
+ [IgmpRecord(h7.sg, "Mode Is Include")])
+ self.verify_report(capture[3],
+ [IgmpRecord(h9.sg, "Mode Is Include")])
- def test_igmp_listen(self):
- """ IGMP listen (S,G)s """
+ #
+ # drop the MTU further (so a 128 sized group won't fit)
+ #
+ self.vapi.sw_interface_set_mtu(self.pg0.sw_if_index, [512, 0, 0, 0])
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ h10 = VppHostState(self,
+ IGMP_FILTER.INCLUDE,
+ self.pg0.sw_if_index,
+ IgmpSG("238.1.1.3", src_list))
+ h10.add_vpp_config()
+
+ capture = self.pg0.get_capture(2, timeout=10)
#
- # VPP acts as a host
- # Add IGMP group state to multiple interfaces and validate its
- # presence
+ # remove state, expect the report for the removal
+ # the dump should be empty
#
- for pg in self.pg_interfaces:
- self.sg_list.append(IgmpSG(socket.inet_pton(
- socket.AF_INET, "10.1.1.%d" % pg._sw_if_index),
- socket.inet_pton(socket.AF_INET, "224.1.1.1")))
+ self.vapi.sw_interface_set_mtu(self.pg0.sw_if_index, [600, 0, 0, 0])
+ self.remove_group(h8)
+ self.remove_group(h9)
+ self.remove_group(h2)
+ self.remove_group(h3)
+ self.remove_group(h4)
+ self.remove_group(h5)
+ self.remove_group(h6)
+ self.remove_group(h7)
+ self.remove_group(h10)
- for pg in self.pg_interfaces:
- self.config_list.append(
- VppIgmpConfig(
- self,
- pg._sw_if_index,
- self.sg_list))
- self.config_list[-1].add_vpp_config()
-
- for config in self.config_list:
- dump = self.vapi.igmp_dump(config.sw_if_index)
- self.assertTrue(dump)
- self.assertEqual(len(dump), len(config.sg_list))
- for idx, e in enumerate(dump):
- self.assertEqual(e.sw_if_index, config.sw_if_index)
- self.assertEqual(e.saddr, config.sg_list[idx].saddr)
- self.assertEqual(e.gaddr, config.sg_list[idx].gaddr)
-
- for config in self.config_list:
- config.remove_vpp_config()
+ self.logger.info(self.vapi.cli("sh igmp config"))
+ self.assertFalse(self.vapi.igmp_dump())
- dump = self.vapi.igmp_dump()
- self.assertFalse(dump)
+ #
+ # TODO
+ # ADD STATE ON MORE INTERFACES
+ #
+
+ self.vapi.igmp_enable_disable(self.pg0.sw_if_index,
+ 0,
+ IGMP_MODE.HOST)
+
+ def test_igmp_router(self):
+ """ IGMP Router Functions """
+
+ #
+ # Drop reports when not enabled
+ #
+ p_j = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
+ options=[IPOption(copy_flag=1, optclass="control",
+ option="router_alert")]) /
+ IGMPv3(type="Version 3 Membership Report") /
+ IGMPv3mr(numgrp=1) /
+ IGMPv3gr(rtype="Allow New Sources",
+ maddr="239.1.1.1", srcaddrs=["10.1.1.1", "10.1.1.2"]))
+ p_l = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0,
+ options=[IPOption(copy_flag=1, optclass="control",
+ option="router_alert")]) /
+ IGMPv3(type="Version 3 Membership Report") /
+ IGMPv3mr(numgrp=1) /
+ IGMPv3gr(rtype="Block Old Sources",
+ maddr="239.1.1.1", srcaddrs=["10.1.1.1", "10.1.1.2"]))
+
+ self.send(self.pg0, p_j)
+ self.assertFalse(self.vapi.igmp_dump())
+
+ #
+ # drop the default timer values so these tests execute in a
+ # reasonable time frame
+ #
+ self.vapi.cli("test igmp timers query 1 src 3 leave 1")
+
+ #
+ # enable router functions on the interface
+ #
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ self.vapi.igmp_enable_disable(self.pg0.sw_if_index,
+ 1,
+ IGMP_MODE.ROUTER)
+ self.vapi.want_igmp_events(1)
+
+ #
+ # wait for router to send general query
+ #
+ for ii in range(3):
+ capture = self.pg0.get_capture(1, timeout=2)
+ self.verify_general_query(capture[0])
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ #
+ # re-send the report. VPP should now hold state for the new group
+ # VPP sends a notification that a new group has been joined
+ #
+ self.send(self.pg0, p_j)
+
+ self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
+ "239.1.1.1", "10.1.1.1", 1))
+ self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
+ "239.1.1.1", "10.1.1.2", 1))
+ dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
+ self.assertEqual(len(dump), 2)
+ self.assertTrue(find_igmp_state(dump, self.pg0,
+ "239.1.1.1", "10.1.1.1"))
+ self.assertTrue(find_igmp_state(dump, self.pg0,
+ "239.1.1.1", "10.1.1.2"))
+
+ #
+ # wait for the per-source timer to expire
+ # the state should be reaped
+ # VPP sends a notification that the group has been left
+ #
+ self.assertTrue(wait_for_igmp_event(self, 4, self.pg0,
+ "239.1.1.1", "10.1.1.1", 0))
+ self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
+ "239.1.1.1", "10.1.1.2", 0))
+ self.assertFalse(self.vapi.igmp_dump())
+
+ #
+ # resend the join. wait for two queries and then send a current-state
+ # record to include all sources. this should reset the exiry time
+ # on the sources and thus they will still be present in 2 seconds time.
+ # If the source timer was not refreshed, then the state would have
+ # expired in 3 seconds.
+ #
+ self.send(self.pg0, p_j)
+ self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
+ "239.1.1.1", "10.1.1.1", 1))
+ self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
+ "239.1.1.1", "10.1.1.2", 1))
+ dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
+ self.assertEqual(len(dump), 2)
+
+ capture = self.pg0.get_capture(2, timeout=3)
+ self.verify_general_query(capture[0])
+ self.verify_general_query(capture[1])
+
+ p_cs = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0,
+ options=[IPOption(copy_flag=1, optclass="control",
+ option="router_alert")]) /
+ IGMPv3(type="Version 3 Membership Report") /
+ IGMPv3mr(numgrp=1) /
+ IGMPv3gr(rtype="Mode Is Include",
+ maddr="239.1.1.1", srcaddrs=["10.1.1.1", "10.1.1.2"]))
+
+ self.send(self.pg0, p_cs)
+
+ self.sleep(2)
+ dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
+ self.assertEqual(len(dump), 2)
+ self.assertTrue(find_igmp_state(dump, self.pg0,
+ "239.1.1.1", "10.1.1.1"))
+ self.assertTrue(find_igmp_state(dump, self.pg0,
+ "239.1.1.1", "10.1.1.2"))
+
+ #
+ # wait for the per-source timer to expire
+ # the state should be reaped
+ #
+ self.assertTrue(wait_for_igmp_event(self, 4, self.pg0,
+ "239.1.1.1", "10.1.1.1", 0))
+ self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
+ "239.1.1.1", "10.1.1.2", 0))
+ self.assertFalse(self.vapi.igmp_dump())
+
+ #
+ # resend the join, then a leave. Router sends a gruop+source
+ # specific query containing both sources
+ #
+ self.send(self.pg0, p_j)
+
+ self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
+ "239.1.1.1", "10.1.1.1", 1))
+ self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
+ "239.1.1.1", "10.1.1.2", 1))
+ dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
+ self.assertEqual(len(dump), 2)
+
+ self.send(self.pg0, p_l)
+ capture = self.pg0.get_capture(1, timeout=3)
+ self.verify_group_query(capture[0], "239.1.1.1",
+ ["10.1.1.1", "10.1.1.2"])
+
+ #
+ # the group specific query drops the timeout to leave (=1) seconds
+ #
+ self.assertTrue(wait_for_igmp_event(self, 2, self.pg0,
+ "239.1.1.1", "10.1.1.1", 0))
+ self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
+ "239.1.1.1", "10.1.1.2", 0))
+ self.assertFalse(self.vapi.igmp_dump())
+ self.assertFalse(self.vapi.igmp_dump())
+
+ #
+ # disable router config
+ #
+ self.vapi.igmp_enable_disable(self.pg0.sw_if_index,
+ 0,
+ IGMP_MODE.ROUTER)
if __name__ == '__main__':
diff --git a/test/vpp_igmp.py b/test/vpp_igmp.py
index d1a308878c5..8f0191644bd 100644
--- a/test/vpp_igmp.py
+++ b/test/vpp_igmp.py
@@ -1,39 +1,80 @@
from vpp_object import VppObject
+import socket
+
+
+class IGMP_MODE:
+ ROUTER = 0
+ HOST = 1
+
+
+class IGMP_FILTER:
+ INCLUDE = 1
+ EXCLUDE = 0
+
+
+def find_igmp_state(states, itf, gaddr, saddr):
+ for s in states:
+ if s.sw_if_index == itf.sw_if_index and \
+ s.gaddr.address == socket.inet_pton(socket.AF_INET, gaddr) and \
+ s.saddr.address == socket.inet_pton(socket.AF_INET, saddr):
+ return True
+ return False
+
+
+def wait_for_igmp_event(test, timeout, itf, gaddr, saddr, ff):
+ ev = test.vapi.wait_for_event(timeout, "igmp_event")
+ if ev.sw_if_index == itf.sw_if_index and \
+ ev.gaddr.address == socket.inet_pton(socket.AF_INET, gaddr) and \
+ ev.saddr.address == socket.inet_pton(socket.AF_INET, saddr) and \
+ ev.filter == ff:
+ return True
+ return False
class IgmpSG():
- def __init__(self, saddr, gaddr):
- self.saddr = saddr
+ def __init__(self, gaddr, saddrs):
self.gaddr = gaddr
+ self.gaddr_p = socket.inet_pton(socket.AF_INET, gaddr)
+ self.saddrs = saddrs
+ self.saddrs_p = []
+ self.saddrs_encoded = []
+ for s in saddrs:
+ ss = socket.inet_pton(socket.AF_INET, s)
+ self.saddrs_p.append(ss)
+ self.saddrs_encoded.append({'address': ss})
+
+class IgmpRecord():
+ def __init__(self, sg, type):
+ self.sg = sg
+ self.type = type
-class VppIgmpConfig(VppObject):
- def __init__(self, test, sw_if_index, sg=None):
+
+class VppHostState(VppObject):
+ def __init__(self, test, filter, sw_if_index, sg):
self._test = test
self.sw_if_index = sw_if_index
- if isinstance(sg, list):
- self.sg_list = sg
- else:
- self.sg_list = []
- self.sg_list.append(sg)
-
- def add_sg(self, sg):
- self.sg.append(sg)
+ self.filter = filter
+ self.sg = sg
def add_vpp_config(self):
- for e in self.sg_list:
- self._test.vapi.igmp_listen(
- 1, self.sw_if_index, e.saddr, e.gaddr)
+ self._test.vapi.igmp_listen(
+ self.filter, self.sw_if_index,
+ self.sg.saddrs_encoded, self.sg.gaddr_p)
def remove_vpp_config(self):
- self._test.vapi.igmp_clear_interface(self.sw_if_index)
+ self._test.vapi.igmp_listen(
+ self.filter,
+ self.sw_if_index,
+ [],
+ self.sg.gaddr_p)
def __str__(self):
return self.object_id()
def object_id(self):
- return "%s:%d" % (self.sg_list, self.sw_if_index)
+ return "%s:%d" % (self.sg, self.sw_if_index)
def query_vpp_config(self):
return self._test.vapi.igmp_dump()
diff --git a/test/vpp_ip_route.py b/test/vpp_ip_route.py
index 17a42fec706..9e5c53184e3 100644
--- a/test/vpp_ip_route.py
+++ b/test/vpp_ip_route.py
@@ -60,6 +60,25 @@ def find_route(test, ip_addr, len, table_id=0, inet=AF_INET):
return False
+def find_mroute(test, grp_addr, src_addr, grp_addr_len,
+ table_id=0, inet=AF_INET):
+ if inet == AF_INET:
+ s = 4
+ routes = test.vapi.ip_mfib_dump()
+ else:
+ s = 16
+ routes = test.vapi.ip6_mfib_dump()
+ gaddr = inet_pton(inet, grp_addr)
+ saddr = inet_pton(inet, src_addr)
+ for e in routes:
+ if gaddr == e.grp_address[:s] \
+ and grp_addr_len == e.address_length \
+ and saddr == e.src_address[:s] \
+ and table_id == e.table_id:
+ return True
+ return False
+
+
class VppIpTable(VppObject):
def __init__(self,
@@ -324,6 +343,8 @@ class VppIpMRoute(VppObject):
self.is_ip6 = is_ip6
self.rpf_id = rpf_id
+ self.grp_addr_p = grp_addr
+ self.src_addr_p = src_addr
if is_ip6:
self.grp_addr = inet_pton(AF_INET6, grp_addr)
self.src_addr = inet_pton(AF_INET6, src_addr)
@@ -406,17 +427,12 @@ class VppIpMRoute(VppObject):
is_ipv6=self.is_ip6)
def query_vpp_config(self):
- if self.is_ip6:
- dump = self._test.vapi.ip6_mfib_dump()
- else:
- dump = self._test.vapi.ip_mfib_dump()
- for e in dump:
- if self.grp_addr == e.grp_address \
- and self.grp_addr_len == e.address_length \
- and self.src_addr == e.src_address \
- and self.table_id == e.table_id:
- return True
- return False
+ return find_mroute(self._test,
+ self.grp_addr_p,
+ self.src_addr_p,
+ self.grp_addr_len,
+ self.table_id,
+ inet=AF_INET6 if self.is_ip6 == 1 else AF_INET)
def __str__(self):
return self.object_id()
diff --git a/test/vpp_papi_provider.py b/test/vpp_papi_provider.py
index 2d4b44764f3..5383b07fc4c 100644
--- a/test/vpp_papi_provider.py
+++ b/test/vpp_papi_provider.py
@@ -3474,7 +3474,14 @@ class VppPapiProvider(object):
'input_source': input_source,
'enable': enable})
- def igmp_listen(self, enable, sw_if_index, saddr, gaddr):
+ def igmp_enable_disable(self, sw_if_index, enable, host):
+ """ Enable/disable IGMP on a given interface """
+ return self.api(self.papi.igmp_enable_disable,
+ {'enable': enable,
+ 'mode': host,
+ 'sw_if_index': sw_if_index})
+
+ def igmp_listen(self, filter, sw_if_index, saddrs, gaddr):
""" Listen for new (S,G) on specified interface
:param enable: add/del
@@ -3483,20 +3490,26 @@ class VppPapiProvider(object):
:param gaddr: group ip4 addr
"""
return self.api(self.papi.igmp_listen,
- {'enable': enable,
- 'sw_if_index': sw_if_index,
- 'saddr': saddr,
- 'gaddr': gaddr})
+ {
+ 'group':
+ {
+ 'filter': filter,
+ 'sw_if_index': sw_if_index,
+ 'n_srcs': len(saddrs),
+ 'saddrs': saddrs,
+ 'gaddr':
+ {
+ 'address': gaddr
+ }
+ }
+ })
def igmp_dump(self, sw_if_index=None):
""" Dump all (S,G) interface configurations """
if sw_if_index is None:
- dump_all = 1
- sw_if_index = 0
- else:
- dump_all = 0
- return self.api(self.papi.igmp_dump, {'sw_if_index': sw_if_index,
- 'dump_all': dump_all})
+ sw_if_index = 0xffffffff
+ return self.api(self.papi.igmp_dump,
+ {'sw_if_index': sw_if_index})
def igmp_clear_interface(self, sw_if_index):
""" Remove all (S,G)s from specified interface