From: Jakub Grajciar Date: Fri, 8 Dec 2017 15:28:42 +0000 (+0100) Subject: IGMP plugin X-Git-Tag: v18.04-rc1~106 X-Git-Url: https://gerrit.fd.io/r/gitweb?p=vpp.git;a=commitdiff_plain;h=7b867a8e491357058d37838091ed67a2e77bce2c IGMP plugin - 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 --- diff --git a/src/configure.ac b/src/configure.ac index 26c2eb5510d..c4554231308 100644 --- a/src/configure.ac +++ b/src/configure.ac @@ -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) diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am index 0381502d3da..37b2e259056 100644 --- a/src/plugins/Makefile.am +++ b/src/plugins/Makefile.am @@ -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 index 00000000000..9829ea681ee --- /dev/null +++ b/src/plugins/igmp.am @@ -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 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 +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +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 ", + .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 [] " + "int saddr gaddr ", + .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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +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 ? + * - 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 +#include +#include +#include +#include + +#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 + +/* + * 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 + +#include +#include + +/* define message IDs */ +#include + +/* define message structures */ +#define vl_typedefs +#include +#undef vl_typedefs + +/* define generated endian-swappers */ +#define vl_endianfun +#include +#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 +#undef vl_printfun + +/* Get the API version number */ +#define vl_api_version(n,v) static u32 api_version=(v); +#include +#undef vl_api_version + +#include + +#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 +#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 +#include + +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 + +#define vl_msg_id(n,h) n, +typedef enum +{ +#include + 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 +#include +#include +#include +#include +#include + +#include +#include + +#include + +/* 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: + */ diff --git a/src/vnet/fib/fib_test.c b/src/vnet/fib/fib_test.c index 8f7bba0369a..61ba9348fd8 100644 --- a/src/vnet/fib/fib_test.c +++ b/src/vnet/fib/fib_test.c @@ -40,6 +40,8 @@ #include #include +#include + /* * 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()); diff --git a/src/vnet/ip/igmp_packet.h b/src/vnet/ip/igmp_packet.h index 503259ece7c..a8e9db6d9ab 100644 --- a/src/vnet/ip/igmp_packet.h +++ b/src/vnet/ip/igmp_packet.h @@ -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 index 00000000000..62b1c9637d6 --- /dev/null +++ b/test/test_igmp.py @@ -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 index 00000000000..d1a308878c5 --- /dev/null +++ b/test/vpp_igmp.py @@ -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() diff --git a/test/vpp_papi_provider.py b/test/vpp_papi_provider.py index da59bc86ca0..dd553cb473e 100644 --- a/test/vpp_papi_provider.py +++ b/test/vpp_papi_provider.py @@ -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()})