aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/igmp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/igmp')
-rw-r--r--src/plugins/igmp/cli.c211
-rw-r--r--src/plugins/igmp/error.h45
-rw-r--r--src/plugins/igmp/igmp.api144
-rw-r--r--src/plugins/igmp/igmp.c849
-rw-r--r--src/plugins/igmp/igmp.def35
-rw-r--r--src/plugins/igmp/igmp.h286
-rw-r--r--src/plugins/igmp/igmp_all_api_h.h26
-rw-r--r--src/plugins/igmp/igmp_api.c337
-rw-r--r--src/plugins/igmp/igmp_format.c159
-rw-r--r--src/plugins/igmp/igmp_format.h39
-rw-r--r--src/plugins/igmp/igmp_msg_enum.h39
-rw-r--r--src/plugins/igmp/input.c550
12 files changed, 2720 insertions, 0 deletions
diff --git a/src/plugins/igmp/cli.c b/src/plugins/igmp/cli.c
new file mode 100644
index 00000000000..a69070f23ce
--- /dev/null
+++ b/src/plugins/igmp/cli.c
@@ -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
index 00000000000..faabfc1b03c
--- /dev/null
+++ b/src/plugins/igmp/error.h
@@ -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
index 00000000000..1533d666a1c
--- /dev/null
+++ b/src/plugins/igmp/igmp.api
@@ -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
index 00000000000..d71e77a74cd
--- /dev/null
+++ b/src/plugins/igmp/igmp.c
@@ -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
index 00000000000..d21753f5095
--- /dev/null
+++ b/src/plugins/igmp/igmp.def
@@ -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
index 00000000000..c98cbd1dc0b
--- /dev/null
+++ b/src/plugins/igmp/igmp.h
@@ -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
index 00000000000..4f17a461414
--- /dev/null
+++ b/src/plugins/igmp/igmp_all_api_h.h
@@ -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
index 00000000000..256f9245e35
--- /dev/null
+++ b/src/plugins/igmp/igmp_api.c
@@ -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
index 00000000000..fb4cc997c23
--- /dev/null
+++ b/src/plugins/igmp/igmp_format.c
@@ -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
index 00000000000..018bb63d582
--- /dev/null
+++ b/src/plugins/igmp/igmp_format.h
@@ -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
index 00000000000..9848ceba86c
--- /dev/null
+++ b/src/plugins/igmp/igmp_msg_enum.h
@@ -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
index 00000000000..9e46e600950
--- /dev/null
+++ b/src/plugins/igmp/input.c
@@ -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:
+ */