IGMP plugin 66/9766/34
authorJakub Grajciar <jgrajcia@cisco.com>
Fri, 8 Dec 2017 15:28:42 +0000 (16:28 +0100)
committerChris Luke <chris_luke@comcast.com>
Mon, 19 Mar 2018 15:53:19 +0000 (15:53 +0000)
- host mode:
  igmp_listen - API to signal that the host has joined an (S,G)

- route mode:
  igmp_enable - API to enable the reception of host IGMP messages
  igmp_event - API to report the host join/leave from an (S,G)

Change-Id: Id180ec27dee617d33ab3088f5dcf6125d3aa9c8f
Signed-off-by: Jakub Grajciar <jgrajcia@cisco.com>
20 files changed:
src/configure.ac
src/plugins/Makefile.am
src/plugins/igmp.am [new file with mode: 0644]
src/plugins/igmp/cli.c [new file with mode: 0644]
src/plugins/igmp/error.h [new file with mode: 0644]
src/plugins/igmp/igmp.api [new file with mode: 0644]
src/plugins/igmp/igmp.c [new file with mode: 0644]
src/plugins/igmp/igmp.def [new file with mode: 0644]
src/plugins/igmp/igmp.h [new file with mode: 0644]
src/plugins/igmp/igmp_all_api_h.h [new file with mode: 0644]
src/plugins/igmp/igmp_api.c [new file with mode: 0644]
src/plugins/igmp/igmp_format.c [new file with mode: 0644]
src/plugins/igmp/igmp_format.h [new file with mode: 0644]
src/plugins/igmp/igmp_msg_enum.h [new file with mode: 0644]
src/plugins/igmp/input.c [new file with mode: 0644]
src/vnet/fib/fib_test.c
src/vnet/ip/igmp_packet.h
test/test_igmp.py [new file with mode: 0644]
test/vpp_igmp.py [new file with mode: 0644]
test/vpp_papi_provider.py

index 26c2eb5..c455423 100644 (file)
@@ -216,6 +216,7 @@ PLUGIN_ENABLED(dpdk)
 PLUGIN_ENABLED(flowprobe)
 PLUGIN_ENABLED(gbp)
 PLUGIN_ENABLED(gtpu)
+PLUGIN_ENABLED(igmp)
 PLUGIN_ENABLED(ila)
 PLUGIN_ENABLED(ioam)
 PLUGIN_ENABLED(ixge)
index 0381502..37b2e25 100644 (file)
@@ -51,6 +51,10 @@ if ENABLE_GTPU_PLUGIN
 include gtpu.am
 endif
 
+if ENABLE_IGMP_PLUGIN
+include igmp.am
+endif
+
 if ENABLE_ILA_PLUGIN
 include ila.am
 endif
diff --git a/src/plugins/igmp.am b/src/plugins/igmp.am
new file mode 100644 (file)
index 0000000..9829ea6
--- /dev/null
@@ -0,0 +1,31 @@
+# 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.
+
+vppplugins_LTLIBRARIES += igmp_plugin.la
+
+igmp_plugin_la_SOURCES =               \
+       igmp/igmp.c                     \
+       igmp/cli.c                      \
+       igmp/igmp_api.c                 \
+       igmp/igmp_plugin.api.h          \
+       igmp/input.c                    \
+       igmp/igmp_format.c
+
+nobase_apiinclude_HEADERS +=           \
+       igmp/igmp_all_api_h.h           \
+       igmp/igmp_msg_enum.h            \
+       igmp/igmp.api.h
+
+API_FILES += igmp/igmp.api
+
+# vi:syntax=automake
diff --git a/src/plugins/igmp/cli.c b/src/plugins/igmp/cli.c
new file mode 100644 (file)
index 0000000..a69070f
--- /dev/null
@@ -0,0 +1,211 @@
+/*
+ *------------------------------------------------------------------
+ * 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 <stdint.h>
+#include <sys/ioctl.h>
+#include <inttypes.h>
+
+#include <vlib/vlib.h>
+#include <vlib/unix/unix.h>
+#include <vnet/ip/ip.h>
+#include <vnet/fib/fib_entry.h>
+#include <vnet/fib/fib_table.h>
+#include <vnet/mfib/mfib_table.h>
+
+#include <igmp/igmp.h>
+
+static clib_error_t *
+igmp_clear_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;
+  clib_error_t *error = NULL;
+  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))
+    {
+      error =
+       clib_error_return (0, "'help clear igmp' or 'clear igmp ?' for help");
+      return error;
+    }
+
+  while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat
+         (line_input, "int %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;
+       }
+    }
+
+  config = igmp_config_lookup (im, sw_if_index);
+  if (config)
+    igmp_clear_config (config);
+
+done:
+  unformat_free (line_input);
+  return error;
+}
+
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (igmp_clear_interface_command, static) = {
+  .path = "clear igmp",
+  .short_help = "clear igmp int <interface>",
+  .function = igmp_clear_interface_command_fn,
+};
+/* *INDENT-ON* */
+
+static clib_error_t *
+igmp_listen_command_fn (vlib_main_t * vm, unformat_input_t * input,
+                       vlib_cli_command_t * cmd)
+{
+  unformat_input_t _line_input, *line_input = &_line_input;
+  clib_error_t *error = NULL;
+  u8 enable = 1;
+  ip46_address_t saddr, gaddr;
+  vnet_main_t *vnm = vnet_get_main ();
+  u32 sw_if_index;
+  int rv;
+
+  if (!unformat_user (input, unformat_line_input, line_input))
+    {
+      error =
+       clib_error_return (0,
+                          "'help igmp listen' or 'igmp listen ?' for help");
+      return error;
+    }
+
+  while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (line_input, "enable"))
+       enable = 1;
+      else if (unformat (line_input, "disable"))
+       enable = 0;
+      else
+       if (unformat
+           (line_input, "int %U", unformat_vnet_sw_interface, vnm,
+            &sw_if_index));
+      else
+       if (unformat (line_input, "saddr %U", unformat_ip46_address, &saddr));
+      else
+       if (unformat (line_input, "gaddr %U", unformat_ip46_address, &gaddr));
+      else
+       {
+         error =
+           clib_error_return (0, "unknown input '%U'", format_unformat_error,
+                              line_input);
+         goto done;
+       }
+    }
+
+  if ((vnet_sw_interface_get_flags (vnm, sw_if_index)
+       && VNET_SW_INTERFACE_FLAG_ADMIN_UP) == 0)
+    {
+      error = clib_error_return (0, "Interface is down");
+      goto done;
+    }
+
+  rv = igmp_listen (vm, enable, sw_if_index, saddr, gaddr,
+                   /* cli_api_listen */ 1);
+  if (rv == -1)
+    {
+      if (enable)
+       error =
+         clib_error_return (0, "This igmp configuration already exists");
+      else
+       error =
+         clib_error_return (0, "This igmp configuration does not nexist");
+    }
+  else if (rv == -2)
+    error =
+      clib_error_return (0,
+                        "Failed to add configuration, interface is in router mode");
+
+done:
+  unformat_free (line_input);
+  return error;
+}
+
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (igmp_listen_command, static) = {
+  .path = "igmp listen",
+  .short_help = "igmp listen [<enable|disable>] "
+                "int <interface> saddr <ip4-address> gaddr <ip4-address>",
+  .function = igmp_listen_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)
+{
+  clib_error_t *error = NULL;
+  igmp_main_t *im = &igmp_main;
+  vnet_main_t *vnm = vnet_get_main ();
+  igmp_config_t *config;
+  igmp_sg_t *sg;
+
+  /* *INDENT-OFF* */
+  pool_foreach (config, im->configs, (
+    {
+      vlib_cli_output (vm, "interface: %U", format_vnet_sw_if_index_name,
+                      vnm, config->sw_if_index);
+       pool_foreach (sg, config->sg, (
+         {
+           vlib_cli_output (vm, "\t(S,G): %U:%U:%U", format_ip46_address,
+                            &sg->saddr, ip46_address_is_ip4 (&sg->saddr),
+                            format_ip46_address, &sg->gaddr, ip46_address_is_ip4
+                            (&sg->gaddr), format_igmp_report_type, sg->group_type);
+         }));
+    }));
+  /* *INDENT-ON* */
+
+  return error;
+}
+
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (igmp_show_command, static) = {
+  .path = "show igmp config",
+  .short_help = "show igmp config",
+  .function = igmp_show_command_fn,
+};
+/* *INDENT-ON* */
+
+clib_error_t *
+igmp_cli_init (vlib_main_t * vm)
+{
+  return 0;
+}
+
+VLIB_INIT_FUNCTION (igmp_cli_init);
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/igmp/error.h b/src/plugins/igmp/error.h
new file mode 100644 (file)
index 0000000..faabfc1
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ *------------------------------------------------------------------
+ * 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_ERROR_H_
+#define _IGMP_ERROR_H_
+
+#define foreach_igmp_error                                     \
+  _ (NONE, "valid igmp packets")                               \
+  _ (UNSPECIFIED, "unspecified error")                         \
+  _ (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") \
+
+typedef enum
+{
+#define _(sym,str) IGMP_ERROR_##sym,
+  foreach_igmp_error
+#undef _
+    IGMP_N_ERROR,
+} igmp_error_t;
+
+#endif /* IGMP_ERROR_H */
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/igmp/igmp.api b/src/plugins/igmp/igmp.api
new file mode 100644 (file)
index 0000000..1533d66
--- /dev/null
@@ -0,0 +1,144 @@
+/*
+ *------------------------------------------------------------------
+ * 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.
+ *------------------------------------------------------------------
+ */
+
+option version = "1.0.0";
+
+/** \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
+*/
+autoreply define igmp_listen
+{
+  u32 client_index;
+  u32 context;
+
+  u8 enable;
+  u32 sw_if_index;
+  u8 saddr[4];
+  u8 gaddr[4];
+};
+
+/** \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
+*/
+autoreply define igmp_enable_disable
+{
+  u32 client_index;
+  u32 context;
+
+  u8 enable;
+  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
+*/
+define igmp_dump
+{
+  u32 client_index;
+  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
+*/
+define igmp_details
+{
+  u32 context;
+
+  u32 sw_if_index;
+  u8 saddr[4];
+  u8 gaddr[4];
+};
+
+/** \brief remove all (S,G)s from an 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
+*/
+autoreply define igmp_clear_interface
+{
+  u32 client_index;
+  u32 context;
+
+  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
+*/
+autoreply define want_igmp_events
+{
+  u32 client_index;
+  u32 context;
+
+  u32 enable;
+  u32 pid;
+};
+
+service {
+  rpc want_igmp_events returns want_igmp_events_reply
+    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
+*/
+define igmp_event
+{
+  u32 context;
+
+  u32 sw_if_index;
+  u8 saddr[4];
+  u8 gaddr[4];
+  u8 is_join;
+};
+
+/*
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/igmp/igmp.c b/src/plugins/igmp/igmp.c
new file mode 100644 (file)
index 0000000..d71e77a
--- /dev/null
@@ -0,0 +1,849 @@
+/*
+ *------------------------------------------------------------------
+ * 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 <vlib/vlib.h>
+#include <vnet/plugin/plugin.h>
+#include <vpp/app/version.h>
+#include <vnet/ip/ip.h>
+#include <vnet/mfib/mfib_entry.h>
+#include <vlib/unix/unix.h>
+#include <vnet/adj/adj_mcast.h>
+#include <vnet/fib/fib_entry.h>
+#include <vnet/fib/fib_table.h>
+#include <vnet/mfib/mfib_table.h>
+
+#include <igmp/igmp.h>
+
+#include <limits.h>
+#include <float.h>
+
+igmp_main_t igmp_main;
+
+/* clear all (S,G)s on specified config and remove this config from pool */
+void
+igmp_clear_config (igmp_config_t * config)
+{
+  igmp_main_t *im = &igmp_main;
+  igmp_sg_t *sg;
+
+  ASSERT (config);
+  /* *INDENT-OFF* */
+  pool_foreach (sg, config->sg, (
+    {
+      clib_mem_free (sg->key);
+    }));
+  /* *INDENT-ON* */
+  pool_free (config->sg);
+  hash_free (config->igmp_sg_by_key);
+
+  hash_unset_mem (im->igmp_config_by_sw_if_index, &config->sw_if_index);
+  pool_put (im->configs, config);
+}
+
+/* sort igmp timers, so that the first to expire is at end */
+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);
+}
+
+/* create new per interface timer
+ *
+ * - delayed reports
+ * - query msg
+ * - query resp
+ */
+
+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_sg_timer (f64 time, u32 sw_if_index, igmp_sg_key_t * key,
+                     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;
+  /* duplicate key, to prevent segmentation fault if (S,G) is removed */
+  timer->data = clib_mem_alloc (sizeof (igmp_sg_key_t));
+  clib_memcpy (timer->data, key, sizeof (igmp_sg_key_t));
+
+  igmp_sort_timers (im->timers);
+}
+
+/* get next timer to expire */
+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;
+
+  b->current_data += sizeof (igmp_message_t);
+  b->current_length += sizeof (igmp_message_t);
+}
+*/
+
+/* create IGMPv3 report with single (S,G)
+ * used to send state chenge reports
+ */
+static void
+igmp_create_report_v31 (vlib_buffer_t * b, igmp_config_t * config)
+{
+  ip_csum_t sum;
+  u16 csum;
+  igmp_main_t *im = &igmp_main;
+  igmp_sg_t *sg;
+  u32 len = 0;
+
+  sg = vec_elt_at_index (config->sg, im->next_index.sg_index);
+
+  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_host_to_net_u16 (1);
+
+  len += sizeof (igmp_membership_group_v3_t);
+  memset (igmp->groups, 0, sizeof (igmp_membership_group_v3_t));
+  igmp->groups[0].type = sg->group_type;
+  igmp->groups[0].n_aux_u32s = 0;
+  clib_memcpy (&igmp->groups[0].dst_address, &sg->gaddr.ip4,
+              sizeof (ip4_address_t));
+
+  igmp->groups[0].n_src_addresses = clib_host_to_net_u16 (1);
+
+  len += sizeof (ip4_address_t);
+  clib_memcpy (&igmp->groups[0].src_addresses[0], &sg->saddr.ip4,
+              sizeof (ip4_address_t));
+
+  sum = ip_incremental_checksum (0, igmp, len);
+  csum = ~ip_csum_fold (sum);
+  igmp->header.checksum = csum;
+
+  b->current_data += len;
+  b->current_length += len;
+}
+
+u8
+ip4_lookup (ip4_address_t * a, igmp_membership_report_v3_t * igmp, u16 n,
+           igmp_membership_group_v3_type_t type)
+{
+  u16 i;
+  u8 rv = 0;
+  u32 l = sizeof (igmp_membership_report_v3_t);
+
+  for (i = 0; i < n; i++)
+    {
+      if ((!ip4_address_compare (a, &group_ptr (igmp, l)->dst_address)) &&
+         (type == group_ptr (igmp, l)->type))
+       {
+         rv = 1;
+         break;
+       }
+      l += sizeof (igmp_membership_group_v3_t) +
+       clib_net_to_host_u16 (group_ptr (igmp, l)->n_src_addresses) *
+       sizeof (ip4_address_t);
+    }
+
+  return rv;
+}
+
+/* create IGMPv3 report with all (S,G)s on config
+ * used to respond to general queries
+ */
+static void
+igmp_create_report_v32 (vlib_buffer_t * b, igmp_config_t * config)
+{
+  ip_csum_t sum;
+  u16 csum;
+  igmp_sg_t *sg0, *sg1;
+  u32 len = 0;
+  u16 n_groups = 0, n_srcs = 0;
+  u32 grp_s = sizeof (igmp_membership_group_v3_t);
+
+  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;
+
+/* 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
+ */
+  /* *INDENT-OFF* */
+  pool_foreach (sg0, config->sg, (
+    {
+      if (ip4_lookup (&sg0->gaddr.ip4, igmp, n_groups, sg0->group_type))
+       continue;
+      memset (igmp + len, 0, grp_s);
+      clib_memcpy (&group_ptr (igmp, len)->dst_address, &sg0->gaddr.ip4, sizeof (ip4_address_t));
+      group_ptr (igmp, len)->type = sg0->group_type;
+      len += grp_s;
+      n_srcs = 0;
+      pool_foreach (sg1, config->sg, (
+       {
+         if ((!ip4_address_compare (&group_ptr (igmp, len - grp_s)->dst_address,
+              &sg1->gaddr.ip4)) && (group_ptr (igmp, len - grp_s)->type == (sg1->group_type)))
+           {
+             clib_memcpy (group_ptr (igmp, len + sizeof (ip4_address_t) * n_srcs),
+                          &sg1->saddr.ip4, sizeof (ip4_address_t));
+             n_srcs++;
+           }
+       }));
+      group_ptr (igmp, len - grp_s)->n_src_addresses = clib_host_to_net_u16 (n_srcs);
+      len += sizeof (ip4_address_t) * n_srcs;
+      n_groups++;
+    }));
+  /* *INDENT-ON* */
+
+  igmp->n_groups = clib_host_to_net_u16 (n_groups);
+
+  sum = ip_incremental_checksum (0, igmp, len);
+  csum = ~ip_csum_fold (sum);
+  igmp->header.checksum = csum;
+
+  b->current_data += len;
+  b->current_length += len;
+}
+
+static void
+igmp_create_general_query_v3 (vlib_buffer_t * b, igmp_config_t * config)
+{
+  vlib_main_t *vm = vlib_get_main ();
+  ip_csum_t sum;
+  u16 csum;
+
+  igmp_message_t *igmp = (igmp_message_t *) (vlib_buffer_get_current (b));
+  memset (igmp, 0, sizeof (igmp_membership_query_v3_t));
+
+  igmp->header.type = IGMP_TYPE_membership_query;
+  igmp->header.code = 100;
+
+  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);
+
+  sum =
+    ip_incremental_checksum (0, igmp, sizeof (igmp_membership_query_v3_t));
+  csum = ~ip_csum_fold (sum);
+  igmp->header.checksum = csum;
+
+  b->current_data += sizeof (igmp_membership_query_v3_t);
+  b->current_length += sizeof (igmp_membership_query_v3_t);
+}
+
+
+static void
+igmp_create_ip4 (vlib_buffer_t * b, igmp_config_t * config, u8 is_report)
+{
+  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));
+    }
+  ip4->dst_address.as_u8[0] = 224;
+  ip4->dst_address.as_u8[1] = 0;
+  ip4->dst_address.as_u8[2] = 0;
+  ip4->dst_address.as_u8[3] = is_report ? 22 : 1;
+
+  b->current_data += ip4_header_bytes (ip4);
+  b->current_length += ip4_header_bytes (ip4);
+
+  config->next_create_msg (b, config);
+  ip4->length = clib_host_to_net_u16 (b->current_length);
+
+  ip4->checksum = ip4_header_checksum (ip4);
+}
+
+static void
+igmp_send_msg (vlib_main_t * vm, vlib_node_runtime_t * node,
+              igmp_main_t * im, igmp_config_t * config, u8 is_report)
+{
+  u32 thread_index = vlib_get_thread_index ();
+  u32 *to_next;
+  u32 next_index = IGMP_NEXT_IP4_REWRITE_MCAST_NODE;
+
+  u32 n_free_bufs = vec_len (im->buffers[thread_index]);
+  if (PREDICT_FALSE (n_free_bufs < 1))
+    {
+      vec_validate (im->buffers[thread_index], 1 + n_free_bufs - 1);
+      n_free_bufs +=
+       vlib_buffer_alloc (vm, &im->buffers[thread_index][n_free_bufs], 1);
+      _vec_len (im->buffers[thread_index]) = n_free_bufs;
+    }
+
+  u32 n_left_to_next;
+  u32 next0 = next_index;
+  vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);
+
+  if (n_left_to_next > 0)
+    {
+      vlib_buffer_t *b = 0;
+      u32 bi = 0;
+
+      if (n_free_bufs)
+       {
+         u32 last_buf = vec_len (im->buffers[thread_index]) - 1;
+         bi = im->buffers[thread_index][last_buf];
+         b = vlib_get_buffer (vm, bi);
+         _vec_len (im->buffers[thread_index]) = last_buf;
+         n_free_bufs--;
+         if (PREDICT_FALSE (n_free_bufs == 0))
+           {
+             n_free_bufs += vlib_buffer_alloc (vm,
+                                               &im->buffers[thread_index]
+                                               [n_free_bufs], 1);
+             _vec_len (im->buffers[thread_index]) = n_free_bufs;
+           }
+
+         b->current_data = 0;
+         b->current_length = 0;
+
+         igmp_create_ip4 (b, config, 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;
+       }
+
+      to_next[0] = bi;
+      to_next += 1;
+      n_left_to_next -= 1;
+
+      vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next,
+                                      n_left_to_next, bi, next0);
+    }
+  vlib_put_next_frame (vm, node, next_index, n_left_to_next);
+}
+
+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;
+
+  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_general_query_v3;
+  igmp_send_msg (vm, rt, im, config, /* is_report */ 0);
+
+  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;
+
+  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 report not reveived 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_config_t *config;
+
+  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 (config->flags & IGMP_CONFIG_FLAG_CAN_SEND_REPORT)
+    {
+      /* TODO: implement IGMPv2 and IGMPv1 */
+      config->next_create_msg = igmp_create_report_v32;
+      igmp_send_msg (vm, rt, im, config, /* is_report */ 1);
+      /* WIP: unset flag after all reports sent */
+      config->flags &= ~IGMP_CONFIG_FLAG_CAN_SEND_REPORT;
+    }
+}
+
+void
+igmp_send_state_changed (vlib_main_t * vm, vlib_node_runtime_t * rt,
+                        igmp_main_t * im, igmp_timer_t * timer)
+{
+  igmp_config_t *config;
+  igmp_sg_t *sg;
+
+  pool_put (im->timers, timer);
+
+  config = vec_elt_at_index (im->configs, im->next_index.config_index);
+  sg = vec_elt_at_index (config->sg, im->next_index.sg_index);
+
+  config->next_create_msg = igmp_create_report_v31;
+  igmp_send_msg (vm, rt, im, config, /* is_report */ 1);
+
+
+  if (sg->group_type == IGMP_MEMBERSHIP_GROUP_change_to_filter_include)
+    {
+      sg->group_type = IGMP_MEMBERSHIP_GROUP_mode_is_filter_include;
+    }
+  else if (sg->group_type == IGMP_MEMBERSHIP_GROUP_block_old_sources)
+    {
+      /* remove API/CLI configured (S,G) */
+      hash_unset_mem (config->igmp_sg_by_key, sg->key);
+      clib_mem_free (sg->key);
+      pool_put (config->sg, sg);
+      if (pool_elts (config->sg) == 0)
+       {
+         hash_unset_mem (im->igmp_config_by_sw_if_index,
+                         &config->sw_if_index);
+         pool_put (im->configs, config);
+       }
+    }
+
+}
+
+void
+igmp_sg_exp (vlib_main_t * vm, vlib_node_runtime_t * rt, igmp_main_t * im,
+            igmp_timer_t * timer)
+{
+  igmp_config_t *config;
+  igmp_sg_t *sg;
+
+  igmp_sg_key_t *key = (igmp_sg_key_t *) timer->data;
+
+  config = igmp_config_lookup (im, timer->sw_if_index);
+  if (!config)
+    goto done;
+  sg = igmp_sg_lookup (config, key);
+  if (!sg)
+    goto done;
+
+  /* check if this timer is valid */
+  if (timer->exp_time != sg->exp_time)
+    {
+      timer->exp_time = sg->exp_time;
+      igmp_sort_timers (im->timers);
+      return;
+    }
+
+  /* source timer expired, remove (S,G) */
+  igmp_listen (vm, 0, timer->sw_if_index, key->saddr, key->gaddr, 0);
+
+done:
+  pool_put (im->timers, timer);
+}
+
+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;
+  u8 enabled = 0;
+  igmp_timer_t *timer = NULL;
+
+  while (1)
+    {
+      if (enabled)
+       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;
+
+      DBG ("time: %f", vlib_time_now (vm));
+
+      /* timer expired */
+      timer->func (vm, rt, im, timer);
+
+    next_timer:
+      timer = igmp_get_next_timer (im);
+      if (timer == NULL)
+       enabled = 0;
+      else
+       enabled = 1;
+    }
+  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 cli_api_configured)
+{
+  igmp_main_t *im = &igmp_main;
+  igmp_config_t *config;
+  igmp_sg_t *sg;
+  igmp_sg_key_t key;
+  int rv = 0;
+
+  /* set the lookup key */
+  clib_memcpy (&key.saddr, &saddr, sizeof (ip46_address_t));
+  clib_memcpy (&key.gaddr, &gaddr, sizeof (ip46_address_t));
+
+  if (enable)
+    {
+      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_sg_by_key =
+           hash_create_mem (0, sizeof (igmp_sg_key_t), sizeof (uword));
+         config->cli_api_configured = cli_api_configured;
+         /* use IGMPv3 by default */
+         config->igmp_ver = IGMP_V3;
+         config->robustness_var = IGMP_DEFAULT_ROBUSTNESS_VARIABLE;
+         config->flags |= IGMP_CONFIG_FLAG_QUERY_RESP_RECVED;
+         if (!cli_api_configured)
+           {
+             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_mem (im->igmp_config_by_sw_if_index, &config->sw_if_index,
+                       config - im->configs);
+       }
+      else if (config->cli_api_configured != cli_api_configured)
+       {
+         rv = -2;
+         goto error;
+       }
+      sg = igmp_sg_lookup (config, &key);
+      if (!sg)
+       {
+         pool_get (config->sg, sg);
+         memset (sg, 0, sizeof (igmp_sg_t));
+         sg->key = clib_mem_alloc (sizeof (igmp_sg_key_t));
+         clib_memcpy (sg->key, &key, sizeof (igmp_sg_key_t));
+         clib_memcpy (&sg->saddr, &saddr, sizeof (ip46_address_t));
+         clib_memcpy (&sg->gaddr, &gaddr, sizeof (ip46_address_t));
+         sg->group_type = IGMP_MEMBERSHIP_GROUP_change_to_filter_include;
+         if (cli_api_configured)
+           {
+             /* create state-changed report timer with zero timeout */
+             igmp_create_int_timer (0, sw_if_index, igmp_send_state_changed);
+           }
+         else
+           {
+             sg->group_type = IGMP_MEMBERSHIP_GROUP_mode_is_filter_include;
+             sg->exp_time = vlib_time_now (vm) + IGMP_SG_TIMER;
+             igmp_create_sg_timer (sg->exp_time, config->sw_if_index,
+                                   sg->key, igmp_sg_exp);
+             /* notify all registered api clients */
+             igmp_event (im, config, sg);
+           }
+         hash_set_mem (config->igmp_sg_by_key, sg->key, sg - config->sg);
+       }
+      else
+       {
+         rv = -1;
+         goto error;
+       }
+
+      im->next_index.config_index = config - im->configs;
+      im->next_index.sg_index = sg - config->sg;
+    }
+  else
+    {
+      config = igmp_config_lookup (im, sw_if_index);
+      if (config)
+       {
+         sg = igmp_sg_lookup (config, &key);
+         if (sg)
+           {
+             sg->group_type = IGMP_MEMBERSHIP_GROUP_block_old_sources;
+             im->next_index.config_index = config - im->configs;
+             im->next_index.sg_index = sg - config->sg;
+             /* notify all registered api clients */
+             if (!cli_api_configured)
+               igmp_event (im, config, sg);
+             else
+               igmp_create_int_timer (0, sw_if_index,
+                                      igmp_send_state_changed);
+           }
+         else
+           {
+             rv = -1;
+             goto error;
+           }
+       }
+      else
+       {
+         rv = -1;
+         goto error;
+       }
+    }
+
+error:
+  return rv;
+}
+
+static clib_error_t *
+igmp_hw_interface_link_up_down (vnet_main_t * vnm, u32 hw_if_index, u32 flags)
+{
+  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)
+    {
+      if ((flags & VNET_HW_INTERFACE_FLAG_LINK_UP) == 0)
+       igmp_clear_config (config);
+    }
+  return error;
+}
+
+VNET_HW_INTERFACE_LINK_UP_DOWN_FUNCTION (igmp_hw_interface_link_up_down);
+
+static clib_error_t *
+igmp_init (vlib_main_t * vm)
+{
+  igmp_main_t *im = &igmp_main;
+  vlib_thread_main_t *tm = vlib_get_thread_main ();
+  int i;
+
+
+  im->igmp_config_by_sw_if_index =
+    hash_create_mem (0, sizeof (u32), sizeof (uword));
+  im->igmp_api_client_by_client_index =
+    hash_create_mem (0, sizeof (u32), sizeof (uword));
+
+  vec_validate_aligned (im->buffers, tm->n_vlib_mains - 1,
+                       CLIB_CACHE_LINE_BYTES);
+
+  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;
+  addr0.ip4.as_u8[0] = 224;
+  addr0.ip4.as_u8[1] = 0;
+  addr0.ip4.as_u8[2] = 0;
+  addr0.ip4.as_u8[3] = 1;
+  /* Report address */
+  ip46_address_t addr1;
+  addr1.ip4.as_u8[0] = 224;
+  addr1.ip4.as_u8[1] = 0;
+  addr1.ip4.as_u8[2] = 0;
+  addr1.ip4.as_u8[3] = 22;
+
+  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,
+  };
+  const mfib_prefix_t mpfx0 = {
+    .fp_proto = FIB_PROTOCOL_IP4,
+    .fp_len = 32,
+    .fp_grp_addr = addr0,
+  };
+  const mfib_prefix_t mpfx1 = {
+    .fp_proto = FIB_PROTOCOL_IP4,
+    .fp_len = 32,
+    .fp_grp_addr = addr1,
+  };
+  /* 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 0;
+}
+
+VLIB_INIT_FUNCTION (igmp_init);
+
+/* *INDENT-OFF* */
+VLIB_PLUGIN_REGISTER () = {
+    .version = VPP_BUILD_VER,
+    .description = "IGMP messaging",
+};
+/* *INDENT-ON* */
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/igmp/igmp.def b/src/plugins/igmp/igmp.def
new file mode 100644 (file)
index 0000000..d21753f
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ *------------------------------------------------------------------
+ * 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.
+ *------------------------------------------------------------------
+ */
+
+igmp_type (0x11, membership_query)
+igmp_type (0x12, membership_report_v1)
+igmp_type (0x13, dvmrp)
+igmp_type (0x14, pim_v1)
+igmp_type (0x15, cisco_trace)
+igmp_type (0x16, membership_report_v2)
+igmp_type (0x17, leave_group_v2)
+igmp_type (0x1e, traceroute_response)
+igmp_type (0x1f, traceroute_request)
+igmp_type (0x22, membership_report_v3)
+igmp_type (0x30, router_advertisement)
+igmp_type (0x32, router_termination)
+igmp_report_type (1, mode_is_filter_include)
+igmp_report_type (2, mode_is_filter_exclude)
+igmp_report_type (3, change_to_filter_include)
+igmp_report_type (4, change_to_filter_exclude)
+igmp_report_type (5, allow_new_sources)
+igmp_report_type (6, block_old_sources)
diff --git a/src/plugins/igmp/igmp.h b/src/plugins/igmp/igmp.h
new file mode 100644 (file)
index 0000000..c98cbd1
--- /dev/null
@@ -0,0 +1,286 @@
+/*
+ *------------------------------------------------------------------
+ * 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_H_
+#define _IGMP_H_
+
+#include <vlib/vlib.h>
+#include <vnet/ip/ip.h>
+#include <vnet/ip/igmp_packet.h>
+#include <vnet/adj/adj_mcast.h>
+#include <igmp/igmp_format.h>
+
+#define IGMP_QUERY_TIMER                       (60)
+#define IGMP_SG_TIMER                          (3 * IGMP_QUERY_TIMER)
+#define IGMP_DEFAULT_ROBUSTNESS_VARIABLE       (2)
+
+#define IGMP_DBG 1
+
+#if IGMP_DBG
+#define DBG(...) clib_warning(__VA_ARGS__)
+#else
+#define DBG(...)
+#endif /* IGMP_DBG */
+
+#define group_ptr(p, l) ((igmp_membership_group_v3_t *)((void*)p + l))
+
+enum
+{
+  IGMP_PROCESS_EVENT_UPDATE_TIMER = 1,
+} igmp_process_event_t;
+
+typedef enum
+{
+  IGMP_V1,
+  IGMP_V2,
+  IGMP_V3,
+} igmp_ver_t;
+
+struct igmp_config_t_;
+
+typedef struct igmp_config_t_ igmp_config_t;
+
+/* populate supplied bufefr with IGMP message */
+typedef void (create_msg_t) (vlib_buffer_t * b, igmp_config_t * config);
+
+typedef struct igmp_index_t_
+{
+  u32 config_index;
+  u32 sg_index;
+} igmp_index_t;
+
+typedef struct igmp_sg_key_t_
+{
+  ip46_address_t gaddr;
+  ip46_address_t saddr;
+} igmp_sg_key_t;
+
+typedef struct igmp_sg_t_
+{
+  ip46_address_t gaddr;
+  ip46_address_t saddr;
+
+  igmp_membership_group_v3_type_t group_type;
+
+  /* check if expired (S,G) timer is valid */
+  f64 exp_time;
+
+  igmp_sg_key_t *key;
+} igmp_sg_t;
+
+typedef struct igmp_config_t_
+{
+  u32 sw_if_index;
+
+  adj_index_t adj_index;
+
+  u8 cli_api_configured;
+
+  create_msg_t *next_create_msg;
+
+  igmp_ver_t igmp_ver;
+
+  u8 robustness_var;
+
+  u8 flags;
+#define IGMP_CONFIG_FLAG_QUERY_RESP_RECVED     (1 << 0)
+#define IGMP_CONFIG_FLAG_CAN_SEND_REPORT       (1 << 1)
+
+  uword *igmp_sg_by_key;
+
+  /* pool of (S,G)s per interface */
+  igmp_sg_t *sg;
+} igmp_config_t;
+
+struct igmp_timer_t_;
+
+typedef struct igmp_timer_t_ igmp_timer_t;
+
+typedef struct igmp_api_client_t_
+{
+  u32 client_index;
+  u32 pid;
+} igmp_api_client_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;
+
+typedef struct igmp_main_t_
+{
+  /** API message ID base */
+  u16 msg_id_base;
+
+  /* get api client by client_index */
+  uword *igmp_api_client_by_client_index;
+
+  /** pool of api clients registered for join/leave notifications */
+  igmp_api_client_t *api_clients;
+
+  /* get config index by config key */
+  uword *igmp_config_by_sw_if_index;
+
+  /** pool of igmp configurations */
+  igmp_config_t *configs;
+
+  /** buffer cache */
+  u32 **buffers;
+
+  /* next report/deletion */
+  igmp_index_t next_index;
+
+  /** pool of igmp timers */
+  igmp_timer_t *timers;
+
+  igmp_type_info_t *type_infos;
+  igmp_report_type_info_t *report_type_infos;
+
+  uword *type_info_by_type;
+  uword *report_type_info_by_report_type;
+
+} igmp_main_t;
+
+extern igmp_main_t igmp_main;
+
+typedef void (igmp_timer_function_t) (vlib_main_t * vm,
+                                     vlib_node_runtime_t * rt,
+                                     igmp_main_t * im, igmp_timer_t * timer);
+
+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;
+
+int igmp_listen (vlib_main_t * vm, u8 enable, u32 sw_if_index,
+                ip46_address_t saddr, ip46_address_t gaddr,
+                u8 cli_api_configured);
+
+void igmp_clear_config (igmp_config_t * config);
+
+void igmp_sort_timers (igmp_timer_t * timers);
+
+void igmp_create_int_timer (f64 time, u32 sw_if_index,
+                           igmp_timer_function_t * func);
+void igmp_create_sg_timer (f64 time, u32 sw_if_index, igmp_sg_key_t * key,
+                          igmp_timer_function_t * func);
+
+void igmp_send_query (vlib_main_t * vm, vlib_node_runtime_t * rt,
+                     igmp_main_t * im, igmp_timer_t * timer);
+void igmp_query_resp_exp (vlib_main_t * vm, vlib_node_runtime_t * rt,
+                         igmp_main_t * im, igmp_timer_t * timer);
+void igmp_send_report (vlib_main_t * vm, vlib_node_runtime_t * rt,
+                      igmp_main_t * im, igmp_timer_t * timer);
+void igmp_send_state_changed (vlib_main_t * vm, vlib_node_runtime_t * rt,
+                             igmp_main_t * im, igmp_timer_t * timer);
+void igmp_sg_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;
+}
+
+void igmp_event (igmp_main_t * im, igmp_config_t * config, igmp_sg_t * sg);
+
+typedef enum
+{
+  IGMP_NEXT_IP4_REWRITE_MCAST_NODE,
+  IGMP_NEXT_IP6_REWRITE_MCAST_NODE,
+  IGMP_N_NEXT,
+} igmp_next_t;
+
+
+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_mem (im->igmp_config_by_sw_if_index, &sw_if_index);
+  if (p)
+    config = vec_elt_at_index (im->configs, p[0]);
+
+  return config;
+}
+
+always_inline igmp_sg_t *
+igmp_sg_lookup (igmp_config_t * config, igmp_sg_key_t * key)
+{
+  uword *p;
+  igmp_sg_t *sg = NULL;
+  if (!config)
+    return NULL;
+
+  p = hash_get_mem (config->igmp_sg_by_key, key);
+  if (p)
+    sg = vec_elt_at_index (config->sg, p[0]);
+
+  return sg;
+}
+
+always_inline igmp_api_client_t *
+igmp_api_client_lookup (igmp_main_t * im, u32 client_index)
+{
+  uword *p;
+  igmp_api_client_t *api_client = NULL;
+
+  p = hash_get_mem (im->igmp_api_client_by_client_index, &client_index);
+  if (p)
+    api_client = vec_elt_at_index (im->api_clients, p[0]);
+
+  return api_client;
+}
+
+#endif /* _IGMP_H_ */
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/igmp/igmp_all_api_h.h b/src/plugins/igmp/igmp_all_api_h.h
new file mode 100644 (file)
index 0000000..4f17a46
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ *------------------------------------------------------------------
+ * 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.api.h>
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/igmp/igmp_api.c b/src/plugins/igmp/igmp_api.c
new file mode 100644 (file)
index 0000000..256f924
--- /dev/null
@@ -0,0 +1,337 @@
+/*
+ *------------------------------------------------------------------
+ * 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.h>
+
+#include <vlibapi/api.h>
+#include <vlibmemory/api.h>
+
+/* define message IDs */
+#include <igmp/igmp_msg_enum.h>
+
+/* define message structures */
+#define vl_typedefs
+#include <igmp/igmp_all_api_h.h>
+#undef vl_typedefs
+
+/* define generated endian-swappers */
+#define vl_endianfun
+#include <igmp/igmp_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 <igmp/igmp_all_api_h.h>
+#undef vl_printfun
+
+/* Get the API version number */
+#define vl_api_version(n,v) static u32 api_version=(v);
+#include <igmp/igmp_all_api_h.h>
+#undef vl_api_version
+
+#include <vlibapi/api_helper_macros.h>
+
+#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)                   \
+
+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;
+
+  if (!vnet_sw_interface_is_api_valid (vnm, ntohl (mp->sw_if_index)))
+    {
+      rv = VNET_API_ERROR_INVALID_SW_IF_INDEX;
+      goto done;
+    }
+
+  if ((vnet_sw_interface_get_flags (vnm, ntohl (mp->sw_if_index)) &&
+       VNET_SW_INTERFACE_FLAG_ADMIN_UP) == 0)
+    {
+      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);
+
+  rv = igmp_listen (vm, mp->enable, ntohl (mp->sw_if_index), saddr, gaddr, 1);
+
+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_IGMP_LISTEN_REPLY) + im->msg_id_base);
+  rmp->context = mp->context;
+  rmp->retval = htonl (rv);
+
+  vl_msg_api_send_shmem (q, (u8 *) & rmp);
+}
+
+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);
+}
+
+static void
+send_igmp_details (unix_shared_memory_queue_t * q, igmp_main_t * im,
+                  igmp_config_t * config, igmp_sg_t * sg, u32 context)
+{
+  vl_api_igmp_details_t *mp;
+
+  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->context = context;
+  mp->sw_if_index = htonl (config->sw_if_index);
+  clib_memcpy (mp->saddr, &sg->saddr.ip4, sizeof (u8) * 4);
+  clib_memcpy (mp->gaddr, &sg->gaddr.ip4, sizeof (u8) * 4);
+
+  vl_msg_api_send_shmem (q, (u8 *) & mp);
+}
+
+static void
+vl_api_igmp_dump_t_handler (vl_api_igmp_dump_t * mp)
+{
+  igmp_main_t *im = &igmp_main;
+  igmp_config_t *config;
+  igmp_sg_t *sg;
+
+  unix_shared_memory_queue_t *q =
+    vl_api_client_index_to_input_queue (mp->client_index);
+  if (!q)
+    return;
+
+  if (mp->dump_all)
+    {
+      /* *INDENT-OFF* */
+      pool_foreach (config, im->configs, (
+        {
+           pool_foreach (sg, config->sg, (
+             {
+               send_igmp_details (q, im, config, sg, mp->context);
+             }));
+        }));
+      /* *INDENT-ON* */
+      return;
+    }
+  config = igmp_config_lookup (im, ntohl (mp->sw_if_index));
+  if (config)
+    {
+      /* *INDENT-OFF* */
+      pool_foreach (sg, config->sg, (
+       {
+         send_igmp_details (q, im, config, sg, mp->context);
+       }));
+      /* *INDENT-ON* */
+    }
+}
+
+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;
+  int rv = 0;
+
+  config = igmp_config_lookup (im, 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);
+  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);
+
+  vl_msg_api_send_shmem (q, (u8 *) & rmp);
+}
+
+static void
+vl_api_want_igmp_events_t_handler (vl_api_want_igmp_events_t * mp)
+{
+  igmp_main_t *im = &igmp_main;
+  igmp_api_client_t *api_client;
+  vl_api_want_igmp_events_reply_t *rmp;
+  int rv = 0;
+
+  api_client = igmp_api_client_lookup (im, mp->client_index);
+  if (api_client)
+    {
+      if (mp->enable)
+       {
+         rv = VNET_API_ERROR_INVALID_REGISTRATION;
+         goto done;
+       }
+      hash_unset_mem (im->igmp_api_client_by_client_index,
+                     &api_client->client_index);
+      pool_put (im->api_clients, api_client);
+      goto done;
+    }
+  if (mp->enable)
+    {
+      pool_get (im->api_clients, api_client);
+      memset (api_client, 0, sizeof (igmp_api_client_t));
+      api_client->client_index = mp->client_index;
+      api_client->pid = mp->pid;
+      hash_set_mem (im->igmp_api_client_by_client_index,
+                   &mp->client_index, api_client - im->api_clients);
+      goto done;
+    }
+  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);
+}
+
+void
+send_igmp_event (unix_shared_memory_queue_t * q, u32 context,
+                igmp_main_t * im, igmp_config_t * config, igmp_sg_t * sg)
+{
+  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->context = context;
+  mp->sw_if_index = htonl (config->sw_if_index);
+  clib_memcpy (&mp->saddr, &sg->saddr.ip4, sizeof (ip4_address_t));
+  clib_memcpy (&mp->gaddr, &sg->gaddr.ip4, sizeof (ip4_address_t));
+  mp->is_join =
+    (sg->group_type == IGMP_MEMBERSHIP_GROUP_mode_is_filter_include) ? 1 : 0;
+
+  vl_msg_api_send_shmem (q, (u8 *) & mp);
+}
+
+void
+igmp_event (igmp_main_t * im, igmp_config_t * config, igmp_sg_t * sg)
+{
+  igmp_api_client_t *api_client;
+  unix_shared_memory_queue_t *q;
+  /* *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, sg);
+    }));
+  /* *INDENT-ON* */
+  if (sg->group_type == IGMP_MEMBERSHIP_GROUP_block_old_sources)
+    {
+      hash_unset_mem (config->igmp_sg_by_key, sg->key);
+      clib_mem_free (sg->key);
+      pool_put (config->sg, sg);
+      if (pool_elts (config->sg) == 0)
+       {
+         hash_unset_mem (im->igmp_config_by_sw_if_index,
+                         &config->sw_if_index);
+         pool_put (im->configs, config);
+       }
+    }
+}
+
+#define vl_msg_name_crc_list
+#include <igmp/igmp_all_api_h.h>
+#undef vl_msg_name_crc_list
+
+static void
+setup_message_id_table (igmp_main_t * im, api_main_t * am)
+{
+#define _(id,n,crc) \
+  vl_msg_api_add_msg_name_crc (am, #n "_" #crc, id + im->msg_id_base);
+  foreach_vl_msg_name_crc_igmp;
+#undef _
+}
+
+/* Set up the API message handling tables */
+static clib_error_t *
+igmp_plugin_api_hookup (vlib_main_t * vm)
+{
+  igmp_main_t *im = &igmp_main;
+  api_main_t *am = &api_main;
+  u8 *name;
+
+  /* Construct the API name */
+  name = format (0, "igmp_%08x%c", api_version, 0);
+
+  /* Ask for a correctly-sized block of API message decode slots */
+  im->msg_id_base = vl_msg_api_get_msg_ids
+    ((char *) name, VL_MSG_FIRST_AVAILABLE);
+
+#define _(N,n)                                                  \
+    vl_msg_api_set_handlers((VL_API_##N + im->msg_id_base),     \
+                           #n,                                  \
+                           vl_api_##n##_t_handler,              \
+                           vl_noop_handler,                     \
+                           vl_api_##n##_t_endian,               \
+                           vl_api_##n##_t_print,                \
+                           sizeof(vl_api_##n##_t), 1);
+  foreach_igmp_plugin_api_msg;
+#undef _
+
+  /*
+   * Set up the (msg_name, crc, message-id) table
+   */
+  setup_message_id_table (im, am);
+
+  vec_free (name);
+  return 0;
+}
+
+VLIB_API_INIT_FUNCTION (igmp_plugin_api_hookup);
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/igmp/igmp_format.c b/src/plugins/igmp/igmp_format.c
new file mode 100644 (file)
index 0000000..fb4cc99
--- /dev/null
@@ -0,0 +1,159 @@
+/*
+ *------------------------------------------------------------------
+ * 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>
+#include <vnet/ip/ip.h>
+
+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);
+
+  if (ti)
+    return format (s, "%s", ti->name);
+  else
+    return format (s, "unknown %d", type);
+}
+
+u8 *
+format_igmp_report_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);
+
+  if (rti)
+    return format (s, "%s", rti->name);
+  else
+    return format (s, "unknown %d", report_type);
+}
+
+u8 *
+format_igmp_header (u8 * s, va_list * args)
+{
+  igmp_header_t *hdr = va_arg (*args, igmp_header_t *);
+  u32 max_header_bytes = va_arg (*args, u32);
+  u32 indent;
+
+  if (max_header_bytes < sizeof (hdr[0]))
+    return format (s, "IGMP header truncated");
+
+  indent = format_get_indent (s);
+  indent += 2;
+
+  s =
+    format (s, "%U%U: code %u, checksum 0x%04x", format_white_space, indent,
+           format_igmp_type, hdr->type, hdr->code,
+           clib_net_to_host_u16 (hdr->checksum));
+  return s;
+}
+
+u8 *
+format_igmp_report_v3 (u8 * s, va_list * args)
+{
+  igmp_membership_report_v3_t *igmp =
+    va_arg (*args, igmp_membership_report_v3_t *);
+  u32 max_header_bytes = va_arg (*args, u32);
+  igmp_membership_group_v3_t *group;
+
+  u32 len = sizeof (igmp_membership_report_v3_t);
+  u32 indent;
+
+  if (max_header_bytes < sizeof (igmp[0]))
+    return format (s, "IGMP report truncated");
+
+  indent = format_get_indent (s);
+  indent += 2;
+
+  s =
+    format (s, "%Un_groups %u", format_white_space, indent,
+           clib_net_to_host_u16 (igmp->n_groups));
+  indent += 2;
+  int i, j = 0;
+  for (i = 0; i < clib_net_to_host_u16 (igmp->n_groups); i++)
+    {
+      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,
+               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++)
+       {
+         s =
+           format (s, "\n%U%U", format_white_space, indent,
+                   format_ip4_address, &group->src_addresses[j]);
+       }
+      indent -= 2;
+      len +=
+       sizeof (igmp_membership_group_v3_t) +
+       (sizeof (ip4_address_t) *
+        clib_net_to_host_u16 (group->n_src_addresses));
+    }
+  return s;
+}
+
+u8 *
+format_igmp_query_v3 (u8 * s, va_list * args)
+{
+  igmp_membership_query_v3_t *igmp =
+    va_arg (*args, igmp_membership_query_v3_t *);
+  u32 max_header_bytes = va_arg (*args, u32);
+  u32 indent;
+  int i;
+
+  if (max_header_bytes < sizeof (igmp[0]))
+    return format (s, "IGMP query truncated");
+
+  indent = format_get_indent (s);
+  indent += 2;
+
+  ip4_address_t tmp;
+  tmp.as_u32 = 0;
+
+  if ((!ip4_address_compare (&igmp->dst, &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);
+  else
+    {
+      s =
+       format (s, "%UGroup-and-Source-Specific Query: %U",
+               format_white_space, indent, format_ip4_address, &igmp->dst);
+      indent += 2;
+      for (i = 0; i < clib_net_to_host_u16 (igmp->n_src_addresses); i++)
+       {
+         s = format (s, "\n%U%U", format_white_space, indent,
+                     format_ip4_address, &igmp->src_addresses[i]);
+       }
+    }
+  return s;
+}
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/igmp/igmp_format.h b/src/plugins/igmp/igmp_format.h
new file mode 100644 (file)
index 0000000..018bb63
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ *------------------------------------------------------------------
+ * 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_FORMAT_H_
+#define _IGMP_FORMAT_H_
+
+u8 *format_igmp_type (u8 * s, va_list * args);
+
+u8 *format_igmp_report_type (u8 * s, va_list * args);
+
+u8 *format_igmp_header (u8 * s, va_list * args);
+
+u8 *format_igmp_report_v3 (u8 * s, va_list * args);
+
+u8 *format_igmp_query_v3 (u8 * s, va_list * args);
+
+#endif /* IGMP_FORMAT_H */
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/igmp/igmp_msg_enum.h b/src/plugins/igmp/igmp_msg_enum.h
new file mode 100644 (file)
index 0000000..9848ceb
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ *------------------------------------------------------------------
+ * 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_MSG_ENUM_H_
+#define _IGMP_MSG_ENUM_H_
+
+#include <vppinfra/byte_order.h>
+
+#define vl_msg_id(n,h) n,
+typedef enum
+{
+#include <igmp/igmp_all_api_h.h>
+  VL_MSG_FIRST_AVAILABLE,
+} vl_msg_id_t;
+#undef vl_msg_id
+
+#endif /* IGMP_MSG_ENUM_H_ */
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/igmp/input.c b/src/plugins/igmp/input.c
new file mode 100644 (file)
index 0000000..9e46e60
--- /dev/null
@@ -0,0 +1,550 @@
+/*
+ *------------------------------------------------------------------
+ * 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 <vlib/vlib.h>
+#include <vnet/plugin/plugin.h>
+#include <vpp/app/version.h>
+#include <vnet/ip/ip.h>
+#include <vlib/unix/unix.h>
+#include <vnet/adj/adj_mcast.h>
+
+#include <igmp/igmp.h>
+#include <igmp/error.h>
+
+#include <limits.h>
+
+/* TODO: mld...
+typedef enum
+{
+  MLD_INPUT_NEXT_DROP,
+  ...
+} mld_input_next_t;
+*/
+
+typedef enum
+{
+  IGMP_INPUT_NEXT_DROP,
+  IGMP_INPUT_NEXT_PARSE_QUERY,
+  IGMP_INPUT_NEXT_PARSE_REPORT,
+  IGMP_INPUT_N_NEXT,
+} igmp_input_next_t;
+
+typedef enum
+{
+  IGMP_PARSE_QUERY_NEXT_DROP,
+  IGMP_PARSE_QUERY_N_NEXT,
+} igmp_parse_query_next_t;
+
+typedef enum
+{
+  IGMP_PARSE_REPORT_NEXT_DROP,
+  IGMP_PARSE_REPORT_N_NEXT,
+} igmp_parse_report_next_t;
+
+char *igmp_error_strings[] = {
+#define _(sym,string) string,
+  foreach_igmp_error
+#undef _
+};
+
+typedef struct
+{
+  u32 next_index;
+  u32 sw_if_index;
+
+  u8 packet_data[64];
+} igmp_input_trace_t;
+
+static u8 *
+format_igmp_input_trace (u8 * s, va_list * va)
+{
+  CLIB_UNUSED (vlib_main_t * vm) = va_arg (*va, vlib_main_t *);
+  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));
+  return s;
+}
+
+static u8 *
+format_igmp_parse_report_trace (u8 * s, va_list * va)
+{
+  CLIB_UNUSED (vlib_main_t * vm) = va_arg (*va, vlib_main_t *);
+  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));
+  return s;
+}
+
+static u8 *
+format_igmp_parse_query_trace (u8 * s, va_list * va)
+{
+  CLIB_UNUSED (vlib_main_t * vm) = va_arg (*va, vlib_main_t *);
+  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));
+  return s;
+}
+
+uword
+igmp_input (vlib_main_t * vm, vlib_node_runtime_t * node,
+           vlib_frame_t * frame)
+{
+  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);
+  u8 error;
+  ip_csum_t sum;
+  u16 csum;
+
+  error = IGMP_ERROR_NONE;
+
+  from = vlib_frame_vector_args (frame);
+  n_left_from = frame->n_vectors;
+  next_index = node->cached_next_index;
+
+  while (n_left_from > 0)
+    {
+      u32 n_left_to_next;
+
+      vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);
+
+      while (n_left_from > 0 && n_left_to_next > 0)
+       {
+         vlib_buffer_t *b;
+         ip4_header_t *ip;
+         u32 bi, next;
+         next = IGMP_INPUT_NEXT_DROP;
+
+         bi = from[0];
+         to_next[0] = bi;
+         from++;
+         to_next++;
+         n_left_from--;
+         n_left_to_next--;
+
+         b = vlib_get_buffer (vm, bi);
+         ip = vlib_buffer_get_current (b);
+
+         if (ip->protocol != 2)
+           {
+             error = IGMP_ERROR_INVALID_PROTOCOL;
+             next = IGMP_INPUT_NEXT_DROP;
+             goto next_buffer;
+           }
+
+         vlib_buffer_advance (b, ip4_header_bytes (ip));
+
+         igmp_header_t *igmp = vlib_buffer_get_current (b);
+
+         u16 checksum = igmp->checksum;
+         igmp->checksum = 0;
+         sum = ip_incremental_checksum (0, igmp,
+                                        clib_net_to_host_u16 (ip->length) -
+                                        ip4_header_bytes (ip));
+         igmp->checksum = checksum;
+         csum = ~ip_csum_fold (sum);
+         if (checksum != csum)
+           {
+             error = IGMP_ERROR_BAD_CHECKSUM;
+             next = IGMP_INPUT_NEXT_DROP;
+             goto next_buffer;
+           }
+
+         /* TODO: IGMPv2 and IGMPv1 */
+         switch (igmp->type)
+           {
+           case IGMP_TYPE_membership_query:
+             next = IGMP_INPUT_NEXT_PARSE_QUERY;
+             break;
+           case IGMP_TYPE_membership_report_v3:
+             next = IGMP_INPUT_NEXT_PARSE_REPORT;
+             break;
+           default:
+             error = IGMP_ERROR_UNKNOWN_TYPE;
+             next = IGMP_INPUT_NEXT_DROP;
+             break;
+           }
+       next_buffer:
+         b->error = error_node->errors[error];
+
+         if (node->flags & VLIB_NODE_FLAG_TRACE)
+           {
+             igmp_input_trace_t *tr;
+             tr = vlib_add_trace (vm, node, b, sizeof (*tr));
+             tr->next_index = next;
+             tr->sw_if_index = vnet_buffer (b)->sw_if_index[VLIB_RX];
+             clib_memcpy (tr->packet_data, vlib_buffer_get_current (b),
+                          sizeof (tr->packet_data));
+           }
+
+         vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next,
+                                          n_left_to_next, bi, next);
+       }
+      vlib_put_next_frame (vm, node, next_index, n_left_to_next);
+    }
+
+  return frame->n_vectors;
+}
+
+/* *INDENT-OFF* */
+VLIB_REGISTER_NODE (igmp_input_node) =
+{
+  .function = igmp_input,
+  .name = "igmp-input",
+  .vector_size = sizeof (u32),
+
+  .format_buffer = format_igmp_header,
+  .format_trace = format_igmp_input_trace,
+
+  .n_errors = IGMP_N_ERROR,
+  .error_strings = igmp_error_strings,
+
+  .n_next_nodes = IGMP_INPUT_N_NEXT,
+  .next_nodes = {
+      [IGMP_INPUT_NEXT_DROP] = "error-drop",
+      [IGMP_INPUT_NEXT_PARSE_QUERY] = "igmp-parse-query",
+      [IGMP_INPUT_NEXT_PARSE_REPORT] = "igmp-parse-report",
+  }
+};
+/* *INDENT-ON* */
+
+uword
+igmp_parse_query (vlib_main_t * vm, vlib_node_runtime_t * node,
+                 vlib_frame_t * frame)
+{
+  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;
+  next_index = node->cached_next_index;
+
+  while (n_left_from > 0)
+    {
+      u32 n_left_to_next;
+
+      vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);
+
+      while (n_left_from > 0 && n_left_to_next > 0)
+       {
+         vlib_buffer_t *b;
+         u32 sw_if_index, bi, next;
+         next = IGMP_PARSE_QUERY_NEXT_DROP;
+
+         bi = from[0];
+         to_next[0] = bi;
+         from++;
+         to_next++;
+         n_left_from--;
+         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);
+         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)
+               {
+                 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;
+             tr = vlib_add_trace (vm, node, b, sizeof (*tr));
+             tr->next_index = next;
+             tr->sw_if_index = vnet_buffer (b)->sw_if_index[VLIB_RX];
+             clib_memcpy (tr->packet_data, vlib_buffer_get_current (b),
+                          sizeof (tr->packet_data));
+           }
+         vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next,
+                                          n_left_to_next, bi, next);
+       }
+      vlib_put_next_frame (vm, node, next_index, n_left_to_next);
+    }
+
+  return frame->n_vectors;
+}
+
+/* *INDENT-OFF* */
+VLIB_REGISTER_NODE (igmp_parse_query_node) =
+{
+  .function = igmp_parse_query,
+  .name = "igmp-parse-query",
+  .vector_size = sizeof (u32),
+
+  .format_buffer = format_igmp_query_v3,
+  .format_trace = format_igmp_parse_query_trace,
+
+  .n_errors = IGMP_N_ERROR,
+  .error_strings = igmp_error_strings,
+
+  .n_next_nodes = IGMP_PARSE_QUERY_N_NEXT,
+  .next_nodes = {
+    [IGMP_PARSE_QUERY_NEXT_DROP] = "error-drop",
+  }
+};
+/* *INDENT-ON* */
+
+uword
+igmp_parse_report (vlib_main_t * vm, vlib_node_runtime_t * node,
+                  vlib_frame_t * frame)
+{
+  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_sg_t *sg;
+  igmp_membership_group_v3_t *group;
+  ip4_address_t *src;
+  igmp_sg_key_t key;
+  memset (&key, 0, sizeof (igmp_sg_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;
+
+  from = vlib_frame_vector_args (frame);
+  n_left_from = frame->n_vectors;
+  next_index = node->cached_next_index;
+
+  while (n_left_from > 0)
+    {
+      u32 n_left_to_next;
+
+      vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);
+
+      while (n_left_from > 0 && n_left_to_next > 0)
+       {
+         vlib_buffer_t *b;
+         u32 sw_if_index, bi, next;
+         next = IGMP_PARSE_REPORT_NEXT_DROP;
+
+         bi = from[0];
+         to_next[0] = bi;
+         from++;
+         to_next++;
+         n_left_from--;
+         n_left_to_next--;
+
+         b = vlib_get_buffer (vm, bi);
+
+         error = IGMP_ERROR_NONE;
+         b->error = error_node->errors[error];
+
+         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->cli_api_configured)
+               {
+                 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;
+               }
+           }
+         DBG ("interface %u", sw_if_index);
+         int i, j = 0;
+         for (i = 0; i < clib_net_to_host_u16 (igmp->n_groups); i++)
+           {
+             group = group_ptr (igmp, len);
+             src = group->src_addresses;
+             if (group->type == IGMP_MEMBERSHIP_GROUP_mode_is_filter_include)
+               {
+                 for (j = 0;
+                      j < clib_net_to_host_u16 (group->n_src_addresses); j++)
+                   {
+                     /* update (S,G) expiration timer */
+                     key.saddr.ip4 = *src;
+                     key.gaddr.ip4 = group->dst_address;
+                     sg = igmp_sg_lookup (config, &key);
+                     if (sg)
+                       sg->exp_time = vlib_time_now (vm) + IGMP_SG_TIMER;
+                     src++;
+                   }
+               }
+             else if (group->type ==
+                      IGMP_MEMBERSHIP_GROUP_mode_is_filter_exclude)
+               {
+                 for (j = 0;
+                      j < clib_net_to_host_u16 (group->n_src_addresses); j++)
+                   {
+                     /* nothing for now... */
+                     src++;
+                   }
+               }
+             else if (group->type ==
+                      IGMP_MEMBERSHIP_GROUP_change_to_filter_include)
+               {
+                 for (j = 0;
+                      j < clib_net_to_host_u16 (group->n_src_addresses); j++)
+                   {
+                     /* add new (S,G) to interface */
+                     saddr.ip4 = *src;
+                     gaddr.ip4 = group->dst_address;
+                     igmp_listen (vm, 1, sw_if_index, saddr, gaddr, 0);
+                     src++;
+                   }
+               }
+             else if (group->type ==
+                      IGMP_MEMBERSHIP_GROUP_change_to_filter_exclude)
+               {
+                 for (j = 0;
+                      j < clib_net_to_host_u16 (group->n_src_addresses); j++)
+                   {
+                     /* remove (S,G) from interface */
+                     saddr.ip4 = *src;
+                     gaddr.ip4 = group->dst_address;
+                     igmp_listen (vm, 0, sw_if_index, saddr, gaddr, 0);
+                     src++;
+                   }
+               }
+             else if (group->type == IGMP_MEMBERSHIP_GROUP_allow_new_sources)
+               {
+                 for (j = 0;
+                      j < clib_net_to_host_u16 (group->n_src_addresses); j++)
+                   {
+                     /* nothing for now... */
+                     src++;
+                   }
+               }
+             else if (group->type == IGMP_MEMBERSHIP_GROUP_block_old_sources)
+               {
+                 for (j = 0;
+                      j < clib_net_to_host_u16 (group->n_src_addresses); j++)
+                   {
+                     /* remove (S,G) from interface */
+                     saddr.ip4 = *src;
+                     gaddr.ip4 = group->dst_address;
+                     igmp_listen (vm, 0, sw_if_index, saddr, gaddr, 0);
+                     src++;
+                   }
+               }
+             /*
+              * 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;
+             tr = vlib_add_trace (vm, node, b, sizeof (*tr));
+             tr->next_index = next;
+             tr->sw_if_index = vnet_buffer (b)->sw_if_index[VLIB_RX];
+             clib_memcpy (tr->packet_data, vlib_buffer_get_current (b),
+                          sizeof (tr->packet_data));
+           }
+         vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next,
+                                          n_left_to_next, bi, next);
+       }
+      vlib_put_next_frame (vm, node, next_index, n_left_to_next);
+    }
+
+  return frame->n_vectors;
+}
+
+/* *INDENT-OFF* */
+VLIB_REGISTER_NODE (igmp_parse_report_node) =
+{
+  .function = igmp_parse_report,
+  .name = "igmp-parse-report",
+  .vector_size = sizeof (u32),
+
+  .format_buffer = format_igmp_report_v3,
+  .format_trace = format_igmp_parse_report_trace,
+
+  .n_errors = IGMP_N_ERROR,
+  .error_strings = igmp_error_strings,
+
+  .n_next_nodes = IGMP_PARSE_REPORT_N_NEXT,
+  .next_nodes = {
+    [IGMP_PARSE_REPORT_NEXT_DROP] = "error-drop",
+  }
+};
+/* *INDENT-ON* */
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
index 8f7bba0..61ba934 100644 (file)
@@ -40,6 +40,8 @@
 #include <vnet/fib/fib_node_list.h>
 #include <vnet/fib/fib_urpf_list.h>
 
+#include <vlib/unix/plugin.h>
+
 /*
  * Add debugs for passing tests
  */
@@ -827,7 +829,15 @@ fib_test_v4 (void)
      * table, and 4 path-lists in the v6 MFIB table
      */
 #define ENBR (5+5+2)
-#define PNBR (5+5+6)
+
+    u32 PNBR = 5+5+2+4;
+
+    /*
+     * 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());
@@ -4343,7 +4353,13 @@ fib_test_v6 (void)
      * All entries are special so no path-list sharing.
      */
 #define ENPS (5+4)
-#define PNPS (5+4+4)
+    u32 PNPS = (5+4+4);
+    /*
+     * 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 503259e..a8e9db6 100644 (file)
@@ -83,6 +83,24 @@ typedef struct
   ip4_address_t dst;
 } igmp_message_t;
 
+typedef struct
+{
+  /* type 0x11 (IGMPv3) */
+  igmp_header_t header;
+
+  ip4_address_t dst;
+
+  /* Reserved, Suppress Router-Side Processing flag and
+     Querier's Robustness Variable RRRRSQQQ. */
+  u8 resv_s_qrv;
+
+  /* Querier's Query Interval Code */
+  u8 qqi_code;
+
+  u16 n_src_addresses;
+  ip4_address_t src_addresses[0];
+} igmp_membership_query_v3_t;
+
 #define foreach_igmp_membership_group_v3_type  \
   _ (1, mode_is_filter_include)                        \
   _ (2, mode_is_filter_exclude)                        \
diff --git a/test/test_igmp.py b/test/test_igmp.py
new file mode 100644 (file)
index 0000000..62b1c96
--- /dev/null
@@ -0,0 +1,323 @@
+#!/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 *
+
+
+def checkIGMPv3():
+    try:
+        tmp = IGMPv3()
+        tmp = IGMPv3mr()
+        tmp = IGMPv3gr()
+        tmp = IGMPv3mq()
+    except NameError:
+        return False
+    return True
+
+
+class TestIgmp(VppTestCase):
+    """ IGMP Test Case """
+
+    def setUp(self):
+        super(TestIgmp, self).setUp()
+
+        self.create_pg_interfaces(range(2))
+        self.sg_list = []
+        self.config_list = []
+
+        self.ip_addr = []
+        for pg in self.pg_interfaces:
+            pg.admin_up()
+            pg.config_ip4()
+            pg.resolve_arp()
+
+    def tearDown(self):
+        for pg in self.pg_interfaces:
+            self.vapi.igmp_clear_interface(pg.sw_if_index)
+            pg.unconfig_ip4()
+            pg.admin_down()
+        super(TestIgmp, self).tearDown()
+
+    def send(self, ti, pkts):
+        ti.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+
+    @unittest.skipUnless(checkIGMPv3(), "missing scapy igmpv3 implementation")
+    def test_igmp_parse_report(self):
+        """ IGMP parse Membership Report """
+
+        #
+        # VPP acts as a router
+        #
+        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) /
+                  IGMPv3() /
+                  IGMPv3mr(numgrp=1) /
+                  IGMPv3gr(rtype=3, maddr="224.1.1.1", srcaddrs=["10.1.1.1"]))
+
+        self.send(self.pg0, p_join)
+
+        # 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"))
+
+        # 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)
+
+        # 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)
+
+        dump = self.vapi.igmp_dump()
+        self.assertFalse(dump)
+
+    def verify_general_query(self, p):
+        ip = p[IP]
+        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")
+
+    @unittest.skipUnless(checkIGMPv3(), "missing scapy igmpv3 implementation")
+    def test_igmp_send_query(self):
+        """ IGMP send General Query """
+
+        #
+        # VPP acts as a router.
+        #   Send a membership report so VPP builds state
+        #
+        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.send(self.pg0, p_mr)
+        self.logger.info(self.vapi.cli("sh igmp config"))
+
+        #
+        # wait for VPP to send out the General Query
+        #
+        capture = self.pg0.get_capture(1, timeout=61)
+
+        self.verify_general_query(capture[0])
+
+        #
+        # the state will expire in 10 more seconds
+        #
+        self.sleep(10)
+        self.assertFalse(self.vapi.igmp_dump())
+
+    @unittest.skipUnless(checkIGMPv3(), "missing scapy igmpv3 implementation")
+    def test_igmp_src_exp(self):
+        """ IGMP per source timer """
+
+        #
+        # VPP Acts as a router
+        #
+
+        # 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_mr1)
+
+        # VPP (router) sends General Query
+        capture = self.pg0.get_capture(1, timeout=61)
+
+        self.verify_general_query(capture[0])
+
+        # 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"]))
+
+        self.send(self.pg0, p_mr2)
+
+        # wait for VPP to send general query
+        capture = self.pg0.get_capture(1, timeout=61)
+        self.verify_general_query(capture[0])
+
+        # 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"]))
+
+        self.send(self.pg0, p_l)
+
+        # 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)
+
+        #
+        # host has left all groups, no state left.
+        #
+        self.sleep(10)
+        self.logger.error(self.vapi.cli("sh igmp config"))
+        self.assertFalse(self.vapi.igmp_dump())
+
+    @unittest.skipUnless(checkIGMPv3(), "missing scapy igmpv3 implementation")
+    def test_igmp_query_resp(self):
+        """ IGMP General Query response """
+
+        #
+        # VPP acting as a host.
+        #  Add a listener in VPP for (10.1.1.1,244.1.1.1)
+        #
+        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()
+
+        # verify state exists
+        self.assertTrue(self.vapi.igmp_dump(self.pg0.sw_if_index))
+
+        #
+        # Send a general query (from a router)
+        #
+        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"))
+
+        self.send(self.pg0, p)
+
+        #
+        # expect VPP to respond with a membership report for the
+        # (10.1.1.1, 224.1.1.1) state
+        #
+        capture = self.pg0.get_capture(1, timeout=10)
+
+        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")
+
+    @unittest.skipUnless(checkIGMPv3(), "missing scapy igmpv3 implementation")
+    def test_igmp_listen(self):
+        """ IGMP listen (S,G)s """
+
+        #
+        # VPP acts as a host
+        #  Add IGMP group state to multiple interfaces and validate its
+        #  presence
+        #
+        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")))
+
+        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()
+
+        dump = self.vapi.igmp_dump()
+        self.assertFalse(dump)
+
+
+if __name__ == '__main__':
+    unittest.main(testRunner=VppTestRunner)
diff --git a/test/vpp_igmp.py b/test/vpp_igmp.py
new file mode 100644 (file)
index 0000000..d1a3088
--- /dev/null
@@ -0,0 +1,39 @@
+
+from vpp_object import VppObject
+
+
+class IgmpSG():
+    def __init__(self, saddr, gaddr):
+        self.saddr = saddr
+        self.gaddr = gaddr
+
+
+class VppIgmpConfig(VppObject):
+    def __init__(self, test, sw_if_index, sg=None):
+        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)
+
+    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)
+
+    def remove_vpp_config(self):
+        self._test.vapi.igmp_clear_interface(self.sw_if_index)
+
+    def __str__(self):
+        return self.object_id()
+
+    def object_id(self):
+        return "%s:%d" % (self.sg_list, self.sw_if_index)
+
+    def query_vpp_config(self):
+        return self._test.vapi.igmp_dump()
index da59bc8..dd553cb 100644 (file)
@@ -3291,3 +3291,39 @@ class VppPapiProvider(object):
                         {'sw_if_index': sw_if_index,
                          'input_source': input_source,
                          'enable': enable})
+
+    def igmp_listen(self, enable, sw_if_index, saddr, gaddr):
+        """ Listen for new (S,G) on specified interface
+
+        :param enable: add/del
+        :param sw_if_index: interface sw index
+        :param saddr: source ip4 addr
+        :param gaddr: group ip4 addr
+        """
+        return self.api(self.papi.igmp_listen,
+                        {'enable': enable,
+                         'sw_if_index': sw_if_index,
+                         'saddr': saddr,
+                         'gaddr': 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})
+
+    def igmp_clear_interface(self, sw_if_index):
+        """ Remove all (S,G)s from specified interface
+            doesn't send IGMP report!
+        """
+        return self.api(
+            self.papi.igmp_clear_interface, {
+                'sw_if_index': sw_if_index})
+
+    def want_igmp_events(self, enable=1):
+        return self.api(self.papi.want_igmp_events, {'enable': enable,
+                                                     'pid': os.getpid()})