IGMP improvements 38/13138/11
authorNeale Ranns <nranns@cisco.com>
Fri, 8 Jun 2018 06:48:20 +0000 (23:48 -0700)
committerFlorin Coras <florin.coras@gmail.com>
Mon, 9 Jul 2018 21:10:53 +0000 (21:10 +0000)
- 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>
44 files changed:
src/plugins/igmp.am
src/plugins/igmp/igmp.api
src/plugins/igmp/igmp.c
src/plugins/igmp/igmp.h
src/plugins/igmp/igmp_api.c
src/plugins/igmp/igmp_api.h [new file with mode: 0644]
src/plugins/igmp/igmp_cli.c [moved from src/plugins/igmp/cli.c with 72% similarity]
src/plugins/igmp/igmp_config.c [new file with mode: 0644]
src/plugins/igmp/igmp_config.h [new file with mode: 0644]
src/plugins/igmp/igmp_error.h [moved from src/plugins/igmp/error.h with 95% similarity]
src/plugins/igmp/igmp_format.c
src/plugins/igmp/igmp_format.h
src/plugins/igmp/igmp_group.c [new file with mode: 0644]
src/plugins/igmp/igmp_group.h [new file with mode: 0644]
src/plugins/igmp/igmp_input.c [moved from src/plugins/igmp/input.c with 59% similarity]
src/plugins/igmp/igmp_pkt.c [new file with mode: 0644]
src/plugins/igmp/igmp_pkt.h [new file with mode: 0644]
src/plugins/igmp/igmp_query.c [new file with mode: 0644]
src/plugins/igmp/igmp_query.h [new file with mode: 0644]
src/plugins/igmp/igmp_report.c [new file with mode: 0644]
src/plugins/igmp/igmp_report.h [new file with mode: 0644]
src/plugins/igmp/igmp_src.c [new file with mode: 0644]
src/plugins/igmp/igmp_src.h [new file with mode: 0644]
src/plugins/igmp/igmp_ssm_range.c [new file with mode: 0644]
src/plugins/igmp/igmp_ssm_range.h [new file with mode: 0644]
src/plugins/igmp/igmp_timer.c [new file with mode: 0644]
src/plugins/igmp/igmp_timer.h [new file with mode: 0644]
src/plugins/igmp/igmp_types.h [new file with mode: 0644]
src/vlibapi/api_helper_macros.h
src/vnet.am
src/vnet/fib/fib_test.c
src/vnet/ip/igmp_packet.h
src/vnet/ip/ip.api
src/vnet/ip/ip4_error.h
src/vnet/ip/ip_types.api
src/vnet/ip/ip_types_api.c [new file with mode: 0644]
src/vnet/ip/ip_types_api.h [new file with mode: 0644]
src/vnet/mfib/mfib_types.h
src/vppinfra/vec.h
test/framework.py
test/test_igmp.py
test/vpp_igmp.py
test/vpp_ip_route.py
test/vpp_papi_provider.py

index 9829ea6..503d4cb 100644 (file)
@@ -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 +=           \
index 1533d66..9bf654f 100644 (file)
@@ -1,3 +1,4 @@
+/* Hey Emacs use -*- mode: C -*- */
 /*
  *------------------------------------------------------------------
  * Copyright (c) 2017 Cisco and/or its affiliates.
  */
 
 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")
index 71c91b0..5cb8f02 100644 (file)
 #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* */
 
index b8759ff..bf123dd 100644 (file)
 #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_ */
 
index d283a0f..71fb3e2 100644 (file)
@@ -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>
 
 #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 (file)
index 0000000..cfeab92
--- /dev/null
@@ -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:
+ */
similarity index 72%
rename from src/plugins/igmp/cli.c
rename to src/plugins/igmp/igmp_cli.c
index 42f5932..5f09589 100644 (file)
@@ -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 (file)
index 0000000..76e8ace
--- /dev/null
@@ -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 (file)
index 0000000..ffd3dea
--- /dev/null
@@ -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:
+ */
similarity index 95%
rename from src/plugins/igmp/error.h
rename to src/plugins/igmp/igmp_error.h
index faabfc1..fbd0dc4 100644 (file)
@@ -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
 {
index fb4cc99..1ae0478 100644 (file)
 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
  *
index 018bb63..92f58e5 100644 (file)
 #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 (file)
index 0000000..fe023a4
--- /dev/null
@@ -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 (file)
index 0000000..dc0fc7a
--- /dev/null
@@ -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:
+ */
similarity index 59%
rename from src/plugins/igmp/input.c
rename to src/plugins/igmp/igmp_input.c
index 829317d..d4563bf 100644 (file)
@@ -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>
 #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 (file)
index 0000000..5dd829d
--- /dev/null
@@ -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 (file)
index 0000000..66fc5a7
--- /dev/null
@@ -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 (file)
index 0000000..1513023
--- /dev/null
@@ -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 (file)
index 0000000..3dd24ee
--- /dev/null
@@ -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 (file)
index 0000000..328b890
--- /dev/null
@@ -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 (file)
index 0000000..f64309f
--- /dev/null
@@ -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 (file)
index 0000000..5e6b609
--- /dev/null
@@ -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 (file)
index 0000000..032066e
--- /dev/null
@@ -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 (file)
index 0000000..3d12712
--- /dev/null
@@ -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 (file)
index 0000000..30180ec
--- /dev/null
@@ -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 (file)
index 0000000..278b7db
--- /dev/null
@@ -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 (file)
index 0000000..4847d6f
--- /dev/null
@@ -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 (file)
index 0000000..0ed879e
--- /dev/null
@@ -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
index 5746f07..626d8d4 100644 (file)
@@ -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;                               \
index bb74cc8..b9ae408 100644 (file)
@@ -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                       \
index e46f670..9554083 100644 (file)
@@ -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());
index a8e9db6..cd4a40d 100644 (file)
@@ -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
 {
index 228e591..4811a34 100644 (file)
@@ -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
index fa93e86..52c43ad 100644 (file)
@@ -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")                          \
index ec6b9d0..72eadaf 100644 (file)
@@ -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 (file)
index 0000000..7fa8e40
--- /dev/null
@@ -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 (file)
index 0000000..2ad59ae
--- /dev/null
@@ -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:
+ */
index ec68c1a..2ab0f7d 100644 (file)
@@ -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", \
 }
 
index f233ae1..1b0378f 100644 (file)
@@ -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
index fdaba2b..4ecb66f 100644 (file)
@@ -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)
index 22b6d2f..cfdd1a8 100644 (file)
@@ -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__':
index d1a3088..8f01916 100644 (file)
@@ -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()
index 17a42fe..9e5c531 100644 (file)
@@ -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()
index 2d4b447..5383b07 100644 (file)
@@ -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