IGMP: proxy device 33/15133/7
authorJakub Grajciar <jgrajcia@cisco.com>
Thu, 4 Oct 2018 09:05:35 +0000 (11:05 +0200)
committerNeale Ranns <nranns@cisco.com>
Tue, 16 Oct 2018 08:28:15 +0000 (08:28 +0000)
Create 'proxy device' per VRF and add one upstream
and one or many downstream interfaces.

Change-Id: I1cef05fb01e73a5b483c7a2f4debaaeffe2c8155
Signed-off-by: Jakub Grajciar <jgrajcia@cisco.com>
17 files changed:
src/plugins/igmp/CMakeLists.txt
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_cli.c
src/plugins/igmp/igmp_config.h
src/plugins/igmp/igmp_format.c
src/plugins/igmp/igmp_format.h
src/plugins/igmp/igmp_group.c
src/plugins/igmp/igmp_proxy.c [new file with mode: 0644]
src/plugins/igmp/igmp_proxy.h [new file with mode: 0644]
src/plugins/igmp/igmp_report.c
src/plugins/igmp/igmp_src.c
src/plugins/igmp/igmp_src.h
test/test_igmp.py
test/vpp_papi_provider.py

index 089046e..725f518 100644 (file)
@@ -26,6 +26,7 @@ add_vpp_plugin(igmp
   igmp_pkt.c
   igmp_ssm_range.c
   igmp_format.c
+  igmp_proxy.c
 
   API_FILES
   igmp.api
index 2a2e680..db47905 100644 (file)
@@ -98,6 +98,48 @@ autoreply define igmp_enable_disable
   u32 sw_if_index;
 };
 
+/**
+ * @brief
+ *  Add/del proxy device on specified VRF.
+ *  Interface must be IGMP enabled in HOST mode.
+ *
+ *   @param client_index - opaque cookie to identify the sender
+ *   @param context - sender context, to match reply w/ request
+ *   @param add - add (1) del (0)
+ *   @param vrf_id - VRF id
+ *   @param sw_if_index - upstream interface sw index
+ */
+autoreply define igmp_proxy_device_add_del
+{
+  u32 client_index;
+  u32 context;
+
+  u8 add;
+  u32 vrf_id;
+  u32 sw_if_index;
+};
+
+/**
+ * @brief
+ *  Add/del downstream interface to/from proxy device.
+ *  Interface must be IGMP enabled in ROUTER mode.
+ *
+ *   @param client_index - opaque cookie to identify the sender
+ *   @param context - sender context, to match reply w/ request
+ *   @param add - add (1) del (0)
+ *   @param vrf_id - VRF id
+ *   @param sw_if_index - downstream interface sw index
+ */
+autoreply define igmp_proxy_device_add_del_interface
+{
+  u32 client_index;
+  u32 context;
+
+  u8 add;
+  u32 vrf_id;
+  u32 sw_if_index;
+};
+
 /**
  * @brief dump (S,G)s from interface
  * @param client_index - opaque cookie to identify the sender
index 85ba35c..61a9970 100644 (file)
@@ -381,6 +381,7 @@ igmp_enable_disable (u32 sw_if_index, u8 enable, igmp_mode_t mode)
        hash_create_mem (0, sizeof (igmp_key_t), sizeof (uword));
       config->robustness_var = IGMP_DEFAULT_ROBUSTNESS_VARIABLE;
       config->mode = mode;
+      config->proxy_device_id = ~0;
 
       for (ii = 0; ii < IGMP_CONFIG_N_TIMERS; ii++)
        config->timers[ii] = IGMP_TIMER_ID_INVALID;
@@ -445,6 +446,18 @@ igmp_enable_disable (u32 sw_if_index, u8 enable, igmp_mode_t mode)
       mfib_table_entry_path_remove (mfib_index,
                                    &mpfx_report,
                                    MFIB_SOURCE_IGMP, &via_itf_path);
+
+      /*
+       * remove interface from proxy device
+       * if this device is upstream, delete proxy device
+       */
+      if (config->mode == IGMP_MODE_ROUTER)
+       igmp_proxy_device_add_del_interface (config->proxy_device_id,
+                                            config->sw_if_index, 0);
+      else if (config->mode == IGMP_MODE_HOST)
+       igmp_proxy_device_add_del (config->proxy_device_id,
+                                  config->sw_if_index, 0);
+
       igmp_clear_config (config);
       im->igmp_config_by_sw_if_index[config->sw_if_index] = ~0;
       hash_free (config->igmp_group_by_key);
index 626d987..9f9b611 100644 (file)
@@ -28,6 +28,7 @@
 #include <igmp/igmp_timer.h>
 #include <igmp/igmp_group.h>
 #include <igmp/igmp_config.h>
+#include <igmp/igmp_proxy.h>
 
 /**
  * RFC 3376 Section 8.1
@@ -101,6 +102,16 @@ typedef struct igmp_main_t_
    * pool of sources
    */
   igmp_src_t *srcs;
+
+  /**
+   * per-vrf DB of proxy devices
+   */
+  u32 *igmp_proxy_device_by_vrf_id;
+
+  /**
+   * pool of proxy devices
+   */
+  igmp_proxy_device_t *proxy_devices;
 } igmp_main_t;
 
 extern igmp_main_t igmp_main;
index 06bf26b..5d83696 100644 (file)
 
 #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)            \
-_(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)                    \
+#define foreach_igmp_plugin_api_msg                                            \
+_(IGMP_LISTEN, igmp_listen)                                                    \
+_(IGMP_ENABLE_DISABLE, igmp_enable_disable)                                    \
+_(IGMP_PROXY_DEVICE_ADD_DEL, igmp_proxy_device_add_del)                        \
+_(IGMP_PROXY_DEVICE_ADD_DEL_INTERFACE, igmp_proxy_device_add_del_interface)    \
+_(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)
@@ -120,6 +122,43 @@ vl_api_igmp_enable_disable_t_handler (vl_api_igmp_enable_disable_t * mp)
   REPLY_MACRO (IGMP_MSG_ID (VL_API_IGMP_ENABLE_DISABLE_REPLY));
 }
 
+static void
+vl_api_igmp_proxy_device_add_del_t_handler (vl_api_igmp_proxy_device_add_del_t
+                                           * mp)
+{
+  vl_api_igmp_proxy_device_add_del_reply_t *rmp;
+  int rv = 0;
+
+  VALIDATE_SW_IF_INDEX (mp);
+
+  rv =
+    igmp_proxy_device_add_del (ntohl (mp->vrf_id), ntohl (mp->sw_if_index),
+                              mp->add);
+
+  BAD_SW_IF_INDEX_LABEL;
+
+  REPLY_MACRO (IGMP_MSG_ID (VL_API_IGMP_PROXY_DEVICE_ADD_DEL_REPLY));
+}
+
+static void
+  vl_api_igmp_proxy_device_add_del_interface_t_handler
+  (vl_api_igmp_proxy_device_add_del_interface_t * mp)
+{
+  vl_api_igmp_proxy_device_add_del_interface_reply_t *rmp;
+  int rv = 0;
+
+  VALIDATE_SW_IF_INDEX (mp);
+
+  rv =
+    igmp_proxy_device_add_del_interface (ntohl (mp->vrf_id),
+                                        ntohl (mp->sw_if_index), mp->add);
+
+  BAD_SW_IF_INDEX_LABEL;
+
+  REPLY_MACRO (IGMP_MSG_ID
+              (VL_API_IGMP_PROXY_DEVICE_ADD_DEL_INTERFACE_REPLY));
+}
+
 static void
 send_igmp_details (unix_shared_memory_queue_t * q, igmp_main_t * im,
                   igmp_config_t * config, igmp_group_t * group,
index e943fbb..6247f9a 100644 (file)
@@ -219,6 +219,134 @@ VLIB_CLI_COMMAND (igmp_enable_command, static) = {
 };
 /* *INDENT-ON* */
 
+static clib_error_t *
+igmp_proxy_device_add_del_command_fn (vlib_main_t * vm,
+                                     unformat_input_t * input,
+                                     vlib_cli_command_t * cmd)
+{
+  unformat_input_t _line_input, *line_input = &_line_input;
+  vnet_main_t *vnm = vnet_get_main ();
+  clib_error_t *error = NULL;
+  u32 sw_if_index = ~0;
+  u32 vrf_id = ~0;
+  u8 add = 1;
+  int rv;
+
+  if (!unformat_user (input, unformat_line_input, line_input))
+    return error;
+
+  while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (line_input, "add"))
+       add = 1;
+      else if (unformat (line_input, "del"))
+       add = 0;
+      else if (unformat (line_input, "vrf-id %u", &vrf_id))
+       ;
+      else if (unformat (line_input, "%U",
+                        unformat_vnet_sw_interface, vnm, &sw_if_index));
+      else
+       {
+         error =
+           clib_error_return (0, "unknown input '%U'", format_unformat_error,
+                              line_input);
+         goto done;
+       }
+    }
+
+  if (~0 == sw_if_index)
+    {
+      error = clib_error_return (0, "interface must be specified");
+      goto done;
+    }
+
+  if (~0 == vrf_id)
+    {
+      error = clib_error_return (0, "VRF must be specified");
+      goto done;
+    }
+
+  rv = igmp_proxy_device_add_del (vrf_id, sw_if_index, add);
+
+  if (0 != rv)
+    error = clib_error_return (0, "result: %d", rv);
+
+done:
+  unformat_free (line_input);
+  return error;
+}
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (igmp_proxy_device_add_del_command, static) = {
+  .path = "igmp proxy-dev",
+  .short_help = "igmp proxy-dev <add|del> vrf-id <table-id> <interface>",
+  .function = igmp_proxy_device_add_del_command_fn,
+};
+/* *INDENT-ON* */
+
+static clib_error_t *
+igmp_proxy_device_add_del_interface_command_fn (vlib_main_t * vm,
+                                               unformat_input_t * input,
+                                               vlib_cli_command_t * cmd)
+{
+  unformat_input_t _line_input, *line_input = &_line_input;
+  vnet_main_t *vnm = vnet_get_main ();
+  clib_error_t *error = NULL;
+  u32 sw_if_index = ~0;
+  u32 vrf_id = ~0;
+  u8 add = 1;
+  int rv;
+
+  if (!unformat_user (input, unformat_line_input, line_input))
+    return error;
+
+  while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (line_input, "add"))
+       add = 1;
+      else if (unformat (line_input, "del"))
+       add = 0;
+      else if (unformat (line_input, "vrf-id %u", &vrf_id))
+       ;
+      else if (unformat (line_input, "%U",
+                        unformat_vnet_sw_interface, vnm, &sw_if_index));
+      else
+       {
+         error =
+           clib_error_return (0, "unknown input '%U'", format_unformat_error,
+                              line_input);
+         goto done;
+       }
+    }
+
+  if (~0 == sw_if_index)
+    {
+      error = clib_error_return (0, "interface must be specified");
+      goto done;
+    }
+
+  if (~0 == vrf_id)
+    {
+      error = clib_error_return (0, "VRF must be specified");
+      goto done;
+    }
+
+  rv = igmp_proxy_device_add_del_interface (vrf_id, sw_if_index, add);
+
+  if (0 != rv)
+    error = clib_error_return (0, "result: %d", rv);
+
+done:
+  unformat_free (line_input);
+  return error;
+}
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (igmp_proxy_device_add_del_interface_command, static) = {
+  .path = "igmp proxy-dev itf",
+  .short_help = "igmp proxy-dev itf <add|del> vrf-id <table-id> <interface>",
+  .function = igmp_proxy_device_add_del_interface_command_fn,
+};
+/* *INDENT-ON* */
+
 static clib_error_t *
 igmp_show_command_fn (vlib_main_t * vm, unformat_input_t * input,
                      vlib_cli_command_t * cmd)
@@ -233,9 +361,9 @@ igmp_show_command_fn (vlib_main_t * vm, unformat_input_t * input,
   /* *INDENT-OFF* */
   pool_foreach (config, im->configs,
     ({
-      vlib_cli_output (vm, "interface: %U mode:%U",
+      vlib_cli_output (vm, "interface: %U mode: %U %U",
                        format_vnet_sw_if_index_name, vnm, config->sw_if_index,
-                       format_igmp_mode, config->mode);
+                       format_igmp_mode, config->mode, format_igmp_proxy_device_id, config->proxy_device_id);
 
       FOR_EACH_GROUP (group, config,
         ({
index a9f48a7..0da2525 100644 (file)
@@ -53,7 +53,7 @@ typedef struct igmp_config_t_
   adj_index_t adj_index;
 
   /**
-   * @param moe - host or router
+   * @param mode - host or router
    */
   igmp_mode_t mode;
 
@@ -71,6 +71,11 @@ typedef struct igmp_config_t_
    * A vector of scheduled query-response timers
    */
   igmp_timer_id_t timers[IGMP_CONFIG_N_TIMERS];
+
+  /**
+   * ID of a proxy device this configuration is on
+   */
+  u32 proxy_device_id;
 } igmp_config_t;
 
 #define FOR_EACH_GROUP(_group, _config, _body)                          \
index 08b5f68..7606fc5 100644 (file)
@@ -209,6 +209,16 @@ format_igmp_key (u8 * s, va_list * args)
   return (s);
 }
 
+u8 *
+format_igmp_proxy_device_id (u8 * s, va_list * args)
+{
+  u32 id = va_arg (*args, u32);
+
+  s = (id == ~0) ? s : format (s, "proxy device: %u", id);
+
+  return (s);
+}
+
 /*
  * fd.io coding-style-patch-verification: ON
  *
index 84862bb..b7e6c95 100644 (file)
@@ -36,6 +36,8 @@ extern u8 *format_igmp_src_addr_list (u8 * s, va_list * args);
 
 extern u8 *format_igmp_key (u8 * s, va_list * args);
 
+extern u8 *format_igmp_proxy_device_id (u8 * s, va_list * args);
+
 #endif /* IGMP_FORMAT_H */
 
 /*
index fe023a4..9f50ad9 100644 (file)
@@ -74,6 +74,14 @@ igmp_group_clear (igmp_group_t * group)
 
   config = igmp_config_get (group->config);
 
+  /* If interface is in ROUTER mode and IGMP proxy is enabled
+   * remove mfib path.
+   */
+  if (config->mode == IGMP_MODE_ROUTER)
+    {
+      igmp_proxy_device_mfib_path_add_del (group, /* add */ 0);
+    }
+
   IGMP_DBG ("clear-group: %U %U",
            format_igmp_key, group->key,
            format_vnet_sw_if_index_name,
@@ -116,6 +124,15 @@ igmp_group_alloc (igmp_config_t * config,
     group->timers[ii] = IGMP_TIMER_ID_INVALID;
 
   hash_set_mem (config->igmp_group_by_key, group->key, group - im->groups);
+
+  /* If interface is in ROUTER mode and IGMP proxy is enabled
+   * add mfib path.
+   */
+  if (config->mode == IGMP_MODE_ROUTER)
+    {
+      igmp_proxy_device_mfib_path_add_del (group, /* add */ 1);
+    }
+
   return (group);
 }
 
diff --git a/src/plugins/igmp/igmp_proxy.c b/src/plugins/igmp/igmp_proxy.c
new file mode 100644 (file)
index 0000000..4a439d6
--- /dev/null
@@ -0,0 +1,397 @@
+/*
+ *------------------------------------------------------------------
+ * 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 <vlib/vlib.h>
+#include <vnet/mfib/mfib_entry.h>
+#include <vnet/mfib/mfib_table.h>
+
+#include <igmp/igmp_proxy.h>
+#include <igmp/igmp.h>
+#include <igmp/igmp_pkt.h>
+
+void
+igmp_proxy_device_mfib_path_add_del (igmp_group_t * group, u8 add)
+{
+  igmp_config_t *config;
+  u32 mfib_index;
+
+  config = igmp_config_get (group->config);
+  mfib_index =
+    mfib_table_get_index_for_sw_if_index (FIB_PROTOCOL_IP4,
+                                         config->sw_if_index);
+
+  /* *INDENT-OFF* */
+  mfib_prefix_t mpfx_group_addr = {
+      .fp_proto = FIB_PROTOCOL_IP4,
+      .fp_len = 32,
+      .fp_grp_addr = {
+       .ip4 = (*group->key).ip4,
+      },
+    };
+  fib_route_path_t via_itf_path =
+    {
+      .frp_proto = fib_proto_to_dpo (FIB_PROTOCOL_IP4),
+      .frp_addr = zero_addr,
+      .frp_sw_if_index = config->sw_if_index,
+      .frp_fib_index = 0,
+      .frp_weight = 1,
+    };
+  /* *INDENT-ON* */
+
+  if (add)
+    mfib_table_entry_path_update (mfib_index, &mpfx_group_addr,
+                                 MFIB_SOURCE_IGMP, &via_itf_path,
+                                 MFIB_ITF_FLAG_FORWARD);
+  else
+    mfib_table_entry_path_remove (mfib_index, &mpfx_group_addr,
+                                 MFIB_SOURCE_IGMP, &via_itf_path);
+}
+
+igmp_proxy_device_t *
+igmp_proxy_device_lookup (u32 vrf_id)
+{
+  igmp_main_t *im = &igmp_main;
+
+  if (vec_len (im->igmp_proxy_device_by_vrf_id) > vrf_id)
+    {
+      u32 index;
+      index = im->igmp_proxy_device_by_vrf_id[vrf_id];
+      if (index != ~0)
+       return (vec_elt_at_index (im->proxy_devices, index));
+    }
+  return NULL;
+}
+
+int
+igmp_proxy_device_add_del (u32 vrf_id, u32 sw_if_index, u8 add)
+{
+  igmp_main_t *im = &igmp_main;
+  igmp_proxy_device_t *proxy_device;
+  igmp_config_t *config;
+  u32 mfib_index;
+
+  /* check VRF id */
+  mfib_index =
+    mfib_table_get_index_for_sw_if_index (FIB_PROTOCOL_IP4, sw_if_index);
+  if (mfib_index == ~0)
+    return VNET_API_ERROR_INVALID_INTERFACE;
+  if (vrf_id != mfib_table_get (mfib_index, FIB_PROTOCOL_IP4)->mft_table_id)
+    return VNET_API_ERROR_INVALID_INTERFACE;
+
+  /* check IGMP configuration */
+  config = igmp_config_lookup (sw_if_index);
+  if (!config)
+    return VNET_API_ERROR_INVALID_INTERFACE;
+  if (config->mode != IGMP_MODE_HOST)
+    return VNET_API_ERROR_INVALID_INTERFACE;
+
+  proxy_device = igmp_proxy_device_lookup (vrf_id);
+  if (!proxy_device && add)
+    {
+      vec_validate_init_empty (im->igmp_proxy_device_by_vrf_id, vrf_id, ~0);
+      pool_get (im->proxy_devices, proxy_device);
+      im->igmp_proxy_device_by_vrf_id[vrf_id] =
+       proxy_device - im->proxy_devices;
+      memset (proxy_device, 0, sizeof (igmp_proxy_device_t));
+      proxy_device->vrf_id = vrf_id;
+      proxy_device->upstream_if = sw_if_index;
+      config->proxy_device_id = vrf_id;
+      /* lock mfib table */
+      mfib_table_lock (mfib_index, FIB_PROTOCOL_IP4, MFIB_SOURCE_IGMP);
+    }
+  else if (proxy_device && !add)
+    {
+      while (vec_len (proxy_device->downstream_ifs) > 0)
+       {
+         igmp_proxy_device_add_del_interface (vrf_id,
+                                              proxy_device->downstream_ifs
+                                              [0], 0);
+       }
+      vec_free (proxy_device->downstream_ifs);
+      proxy_device->downstream_ifs = NULL;
+      im->igmp_proxy_device_by_vrf_id[vrf_id] = ~0;
+      pool_put (im->proxy_devices, proxy_device);
+      config->proxy_device_id = ~0;
+      /* clear proxy database */
+      igmp_clear_config (config);
+      /* unlock mfib table */
+      mfib_table_unlock (mfib_index, FIB_PROTOCOL_IP4, MFIB_SOURCE_IGMP);
+    }
+  else
+    return -1;
+
+  return 0;
+}
+
+int
+igmp_proxy_device_add_del_interface (u32 vrf_id, u32 sw_if_index, u8 add)
+{
+  igmp_proxy_device_t *proxy_device;
+  u32 index;
+  u32 mfib_index;
+
+  proxy_device = igmp_proxy_device_lookup (vrf_id);
+  if (!proxy_device)
+    return -1;
+
+  /* check VRF id */
+  mfib_index =
+    mfib_table_get_index_for_sw_if_index (FIB_PROTOCOL_IP4, sw_if_index);
+  if (mfib_index == ~0)
+    return VNET_API_ERROR_INVALID_INTERFACE;
+  if (vrf_id != mfib_table_get (mfib_index, FIB_PROTOCOL_IP4)->mft_table_id)
+    return VNET_API_ERROR_INVALID_INTERFACE;
+
+  /* check IGMP configuration */
+  igmp_config_t *config;
+  config = igmp_config_lookup (sw_if_index);
+  if (!config)
+    return VNET_API_ERROR_INVALID_INTERFACE;
+  if (config->mode != IGMP_MODE_ROUTER)
+    return VNET_API_ERROR_INVALID_INTERFACE;
+
+  if (add)
+    {
+      if (proxy_device->downstream_ifs)
+       {
+         index = vec_search (proxy_device->downstream_ifs, sw_if_index);
+         if (index != ~0)
+           return -1;
+       }
+      vec_add1 (proxy_device->downstream_ifs, sw_if_index);
+      config->proxy_device_id = vrf_id;
+    }
+  else
+    {
+      if (!proxy_device->downstream_ifs)
+       return -2;
+      index = vec_search (proxy_device->downstream_ifs, sw_if_index);
+      if (index == ~0)
+       return -3;
+      /* remove (S,G)s belonging to this interface from proxy database */
+      igmp_proxy_device_merge_config (config, /* block */ 1);
+      vec_del1 (proxy_device->downstream_ifs, index);
+      config->proxy_device_id = ~0;
+    }
+
+  return 0;
+}
+
+void
+igmp_proxy_device_block_src (igmp_config_t * config, igmp_group_t * group,
+                            igmp_src_t * src)
+{
+  igmp_proxy_device_t *proxy_device;
+  igmp_config_t *proxy_config;
+  igmp_group_t *proxy_group;
+  igmp_src_t *proxy_src;
+  u8 *ref;
+
+  proxy_device = igmp_proxy_device_lookup (config->proxy_device_id);
+  if (!proxy_device)
+    return;
+
+  proxy_config = igmp_config_lookup (proxy_device->upstream_if);
+  ASSERT (proxy_config);
+
+  proxy_group = igmp_group_lookup (proxy_config, group->key);
+  if (proxy_group == NULL)
+    return;
+
+  proxy_src = igmp_src_lookup (proxy_group, src->key);
+  if (proxy_src == NULL)
+    return;
+
+  if (vec_len (proxy_src->referance_by_config_index) <= group->config)
+    {
+      IGMP_DBG ("proxy block src: invalid config %u", group->config);
+      return;
+    }
+  proxy_src->referance_by_config_index[group->config] = 0;
+  vec_foreach (ref, proxy_src->referance_by_config_index)
+  {
+    if ((*ref) > 0)
+      return;
+  }
+
+  /* build "Block Old Sources" report */
+  igmp_pkt_build_report_t br;
+  ip46_address_t *srcaddrs = NULL;
+
+  igmp_pkt_build_report_init (&br, proxy_config->sw_if_index);
+  vec_add1 (srcaddrs, *proxy_src->key);
+  igmp_pkt_report_v3_add_report (&br, proxy_group->key, srcaddrs,
+                                IGMP_MEMBERSHIP_GROUP_block_old_sources);
+  igmp_pkt_report_v3_send (&br);
+
+
+  igmp_group_src_remove (proxy_group, proxy_src);
+  igmp_src_free (proxy_src);
+
+  if (igmp_group_n_srcs (proxy_group, IGMP_FILTER_MODE_INCLUDE) == 0)
+    {
+      igmp_proxy_device_mfib_path_add_del (proxy_group, 0);
+      igmp_proxy_device_mfib_path_add_del (group, 0);
+      igmp_group_clear (proxy_group);
+    }
+}
+
+always_inline void
+igmp_proxy_device_merge_src (igmp_group_t * proxy_group, igmp_src_t * src,
+                            ip46_address_t ** srcaddrs, u8 block)
+{
+  igmp_src_t *proxy_src;
+  u32 d_config;
+
+  proxy_src = igmp_src_lookup (proxy_group, src->key);
+
+  if (proxy_src == NULL)
+    {
+      if (block)
+       return;
+      /* store downstream config index */
+      d_config = igmp_group_get (src->group)->config;
+
+      proxy_src =
+       igmp_src_alloc (igmp_group_index (proxy_group), src->key,
+                       IGMP_MODE_HOST);
+
+      hash_set_mem (proxy_group->igmp_src_by_key
+                   [proxy_group->router_filter_mode], proxy_src->key,
+                   igmp_src_index (proxy_src));
+
+      vec_validate_init_empty (proxy_src->referance_by_config_index, d_config,
+                              0);
+      proxy_src->referance_by_config_index[d_config] = 1;
+      vec_add1 (*srcaddrs, *proxy_src->key);
+    }
+  else
+    {
+      if (block)
+       {
+         d_config = igmp_group_get (src->group)->config;
+         if (vec_len (proxy_src->referance_by_config_index) <= d_config)
+           {
+             IGMP_DBG ("proxy block src: invalid config %u", d_config);
+             return;
+           }
+         proxy_src->referance_by_config_index[d_config] = 0;
+         u8 *ref;
+         vec_foreach (ref, proxy_src->referance_by_config_index)
+         {
+           if ((*ref) > 0)
+             return;
+         }
+
+         vec_add1 (*srcaddrs, *proxy_src->key);
+
+         igmp_group_src_remove (proxy_group, proxy_src);
+         igmp_src_free (proxy_src);
+
+         if (igmp_group_n_srcs (proxy_group, IGMP_FILTER_MODE_INCLUDE) == 0)
+           {
+             igmp_proxy_device_mfib_path_add_del (proxy_group, 0);
+             igmp_group_clear (proxy_group);
+           }
+         return;
+       }
+      d_config = igmp_group_get (src->group)->config;
+      vec_validate (proxy_src->referance_by_config_index, d_config);
+      proxy_src->referance_by_config_index[d_config] = 1;
+      return;
+    }
+}
+
+always_inline igmp_group_t *
+igmp_proxy_device_merge_group (igmp_proxy_device_t * proxy_device,
+                              igmp_group_t * group,
+                              ip46_address_t ** srcaddrs, u8 block)
+{
+  igmp_config_t *proxy_config;
+  igmp_group_t *proxy_group;
+  igmp_src_t *src;
+
+  proxy_config = igmp_config_lookup (proxy_device->upstream_if);
+  ASSERT (proxy_config);
+
+  proxy_group = igmp_group_lookup (proxy_config, group->key);
+  if (!proxy_group)
+    {
+      if (block)
+       return NULL;
+      u32 tmp = igmp_group_index (group);
+      proxy_group =
+       igmp_group_alloc (proxy_config, group->key,
+                         group->router_filter_mode);
+      igmp_proxy_device_mfib_path_add_del (proxy_group, 1);
+      group = igmp_group_get (tmp);
+    }
+  if (block)
+    {
+      igmp_proxy_device_mfib_path_add_del (group, 0);
+    }
+
+  /* *INDENT-OFF* */
+  FOR_EACH_SRC (src, group, group->router_filter_mode,
+    ({
+      igmp_proxy_device_merge_src (proxy_group, src, srcaddrs, block);
+    }));
+  /* *INDENT-ON* */
+  return proxy_group;
+}
+
+void
+igmp_proxy_device_merge_config (igmp_config_t * config, u8 block)
+{
+  igmp_proxy_device_t *proxy_device;
+  igmp_group_t *group;
+  igmp_group_t *proxy_group;
+  ip46_address_t *srcaddrs = NULL;
+  igmp_pkt_build_report_t br;
+
+  proxy_device = igmp_proxy_device_lookup (config->proxy_device_id);
+  if (!proxy_device)
+    return;
+
+  igmp_pkt_build_report_init (&br, proxy_device->upstream_if);
+
+  /* *INDENT-OFF* */
+  FOR_EACH_GROUP(group, config,
+    ({
+      proxy_group = igmp_proxy_device_merge_group (proxy_device, group, &srcaddrs, block);
+
+      if ((vec_len(srcaddrs) > 0) && proxy_group)
+       {
+         igmp_pkt_report_v3_add_report (&br, proxy_group->key, srcaddrs,
+                                        block ? IGMP_MEMBERSHIP_GROUP_block_old_sources :
+                                        IGMP_MEMBERSHIP_GROUP_allow_new_sources);
+       }
+      vec_free (srcaddrs);
+    }));
+  /* *INDENT-ON* */
+
+  igmp_pkt_report_v3_send (&br);
+
+}
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/igmp/igmp_proxy.h b/src/plugins/igmp/igmp_proxy.h
new file mode 100644 (file)
index 0000000..6d49159
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ *------------------------------------------------------------------
+ * 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_PROXY_H_
+#define _IGMP_PROXY_H_
+
+#include <igmp/igmp_types.h>
+#include <igmp/igmp_config.h>
+
+typedef struct
+{
+  /* VRF index */
+  u32 vrf_id;
+
+  /* upstrema interface */
+  u32 upstream_if;
+
+  /* downstream interfaces */
+  u32 *downstream_ifs;
+} igmp_proxy_device_t;
+
+/**
+ * @brief IGMP proxy device add/del
+ *  @param vrf_id - VRF id
+ *  @param sw_if_index - upstream interface
+ *  @param add - add/del
+ *
+ * Add/del IGMP proxy device. Interface must be IGMP enabled in HOST mode.
+ */
+int igmp_proxy_device_add_del (u32 vfr_id, u32 sw_if_index, u8 add);
+
+/**
+ * @brief IGMP proxy device add/del interface
+ *  @param vrf_id - VRF id
+ *  @param sw_if_index - downstream interface
+ *  @param add - add/del
+ *
+ * Add/del IGMP enabled interface in ROUTER mode to proxy device.
+ */
+int igmp_proxy_device_add_del_interface (u32 vrf_id, u32 sw_if_index, u8 add);
+
+void igmp_proxy_device_merge_config (igmp_config_t *config, u8 block);
+
+void igmp_proxy_device_block_src (igmp_config_t *config, igmp_group_t *group, igmp_src_t *src);
+
+void igmp_proxy_device_mfib_path_add_del (igmp_group_t *group, u8 add);
+
+#endif /* IGMP_PROXY_H */
index e0c127d..7c08f34 100644 (file)
@@ -196,6 +196,8 @@ igmp_handle_report (const igmp_report_args_t * args)
       igmp_group = group_cptr (igmp_group,
                               igmp_membership_group_v3_length (igmp_group));
     }
+
+  igmp_proxy_device_merge_config (config, 0);
 }
 
 /*
index c70514a..0bdde83 100644 (file)
@@ -58,6 +58,8 @@ igmp_src_exp (u32 obj, void *dat)
 
       igmp_event (IGMP_FILTER_MODE_EXCLUDE,
                  config->sw_if_index, src->key, group->key);
+
+      igmp_proxy_device_block_src (config, group, src);
     }
 
   igmp_group_src_remove (group, src);
index 6b27cb6..86a043f 100644 (file)
@@ -65,6 +65,12 @@ typedef struct igmp_src_t_
    * Timers
    */
   u32 timers[IGMP_SRC_N_TIMERS];
+
+  /**
+   * Tells us which configurations
+   * have this source.
+   */
+  u8 *referance_by_config_index;
 } igmp_src_t;
 
 extern void igmp_src_free (igmp_src_t * src);
index 128ac5e..da2fa9a 100644 (file)
@@ -651,6 +651,93 @@ class TestIgmp(VppTestCase):
                                       0,
                                       IGMP_MODE.ROUTER)
 
+    def _create_igmpv3_pck(self, itf, rtype, maddr, srcaddrs):
+        p = (Ether(dst=itf.local_mac, src=itf.remote_mac) /
+             IP(src=itf.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=rtype,
+                      maddr=maddr, srcaddrs=srcaddrs))
+        return p
+
+    def test_igmp_proxy_device(self):
+        """ IGMP proxy device """
+        self.pg2.admin_down()
+        self.pg2.unconfig_ip4()
+        self.pg2.set_table_ip4(0)
+        self.pg2.config_ip4()
+        self.pg2.admin_up()
+
+        self.vapi.cli('test igmp timers query 10 src 3 leave 1')
+
+        # enable IGMP
+        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.ROUTER)
+        self.vapi.igmp_enable_disable(self.pg2.sw_if_index, 1,
+                                      IGMP_MODE.ROUTER)
+
+        # create IGMP proxy device
+        self.vapi.igmp_proxy_device_add_del(0, self.pg0.sw_if_index, 1)
+        self.vapi.igmp_proxy_device_add_del_interface(0,
+                                                      self.pg1.sw_if_index, 1)
+        self.vapi.igmp_proxy_device_add_del_interface(0,
+                                                      self.pg2.sw_if_index, 1)
+
+        # send join on pg1. join should be proxied by pg0
+        p_j = self._create_igmpv3_pck(self.pg1, "Allow New Sources",
+                                      "239.1.1.1", ["10.1.1.1", "10.1.1.2"])
+        self.send(self.pg1, p_j)
+
+        capture = self.pg0.get_capture(1, timeout=1)
+        self.verify_report(capture[0], [IgmpRecord(IgmpSG("239.1.1.1",
+                           ["10.1.1.1", "10.1.1.2"]), "Allow New Sources")])
+        self.assertTrue(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
+
+        # send join on pg2. join should be proxied by pg0.
+        # the group should contain only 10.1.1.3 as
+        # 10.1.1.1 was already reported
+        p_j = self._create_igmpv3_pck(self.pg2, "Allow New Sources",
+                                      "239.1.1.1", ["10.1.1.1", "10.1.1.3"])
+        self.send(self.pg2, p_j)
+
+        capture = self.pg0.get_capture(1, timeout=1)
+        self.verify_report(capture[0], [IgmpRecord(IgmpSG("239.1.1.1",
+                           ["10.1.1.3"]), "Allow New Sources")])
+        self.assertTrue(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
+
+        # send leave on pg2. leave for 10.1.1.3 should be proxyed
+        # as pg2 was the only interface interested in 10.1.1.3
+        p_l = self._create_igmpv3_pck(self.pg2, "Block Old Sources",
+                                      "239.1.1.1", ["10.1.1.3"])
+        self.send(self.pg2, p_l)
+
+        capture = self.pg0.get_capture(1, timeout=2)
+        self.verify_report(capture[0], [IgmpRecord(IgmpSG("239.1.1.1",
+                           ["10.1.1.3"]), "Block Old Sources")])
+        self.assertTrue(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
+
+        # disable igmp on pg1 (also removes interface from proxy device)
+        # proxy leave for 10.1.1.2. pg2 is still interested in 10.1.1.1
+        self.pg_enable_capture(self.pg_interfaces)
+        self.vapi.igmp_enable_disable(self.pg1.sw_if_index, 0,
+                                      IGMP_MODE.ROUTER)
+
+        capture = self.pg0.get_capture(1, timeout=1)
+        self.verify_report(capture[0], [IgmpRecord(IgmpSG("239.1.1.1",
+                           ["10.1.1.2"]), "Block Old Sources")])
+        self.assertTrue(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
+
+        # disable IGMP on pg0 and pg1.
+        #   disabling IGMP on pg0 (proxy device upstream interface)
+        #   removes this proxy device
+        self.vapi.igmp_enable_disable(self.pg0.sw_if_index, 0, IGMP_MODE.HOST)
+        self.vapi.igmp_enable_disable(self.pg2.sw_if_index, 0,
+                                      IGMP_MODE.ROUTER)
+        self.assertFalse(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
+
 
 if __name__ == '__main__':
     unittest.main(testRunner=VppTestRunner)
index 1f922a0..4e7809f 100644 (file)
@@ -3637,6 +3637,18 @@ class VppPapiProvider(object):
                          'mode': host,
                          'sw_if_index': sw_if_index})
 
+    def igmp_proxy_device_add_del(self, vrf_id, sw_if_index, add):
+        """ Add/del IGMP proxy device """
+        return self.api(self.papi.igmp_proxy_device_add_del,
+                        {'vrf_id': vrf_id, 'sw_if_index': sw_if_index,
+                         'add': add})
+
+    def igmp_proxy_device_add_del_interface(self, vrf_id, sw_if_index, add):
+        """ Add/del interface to/from IGMP proxy device """
+        return self.api(self.papi.igmp_proxy_device_add_del_interface,
+                        {'vrf_id': vrf_id, 'sw_if_index': sw_if_index,
+                         'add': add})
+
     def igmp_listen(self, filter, sw_if_index, saddrs, gaddr):
         """ Listen for new (S,G) on specified interface