From 97748cae2e6261d8fdc7c331a4d82828ac51ed81 Mon Sep 17 00:00:00 2001 From: Jakub Grajciar Date: Thu, 4 Oct 2018 11:05:35 +0200 Subject: [PATCH] IGMP: proxy device Create 'proxy device' per VRF and add one upstream and one or many downstream interfaces. Change-Id: I1cef05fb01e73a5b483c7a2f4debaaeffe2c8155 Signed-off-by: Jakub Grajciar --- src/plugins/igmp/CMakeLists.txt | 1 + src/plugins/igmp/igmp.api | 42 +++++ src/plugins/igmp/igmp.c | 13 ++ src/plugins/igmp/igmp.h | 11 ++ src/plugins/igmp/igmp_api.c | 57 +++++- src/plugins/igmp/igmp_cli.c | 132 ++++++++++++- src/plugins/igmp/igmp_config.h | 7 +- src/plugins/igmp/igmp_format.c | 10 + src/plugins/igmp/igmp_format.h | 2 + src/plugins/igmp/igmp_group.c | 17 ++ src/plugins/igmp/igmp_proxy.c | 397 ++++++++++++++++++++++++++++++++++++++++ src/plugins/igmp/igmp_proxy.h | 62 +++++++ src/plugins/igmp/igmp_report.c | 2 + src/plugins/igmp/igmp_src.c | 2 + src/plugins/igmp/igmp_src.h | 6 + test/test_igmp.py | 87 +++++++++ test/vpp_papi_provider.py | 12 ++ 17 files changed, 848 insertions(+), 12 deletions(-) create mode 100644 src/plugins/igmp/igmp_proxy.c create mode 100644 src/plugins/igmp/igmp_proxy.h diff --git a/src/plugins/igmp/CMakeLists.txt b/src/plugins/igmp/CMakeLists.txt index 089046e9720..725f5185569 100644 --- a/src/plugins/igmp/CMakeLists.txt +++ b/src/plugins/igmp/CMakeLists.txt @@ -26,6 +26,7 @@ add_vpp_plugin(igmp igmp_pkt.c igmp_ssm_range.c igmp_format.c + igmp_proxy.c API_FILES igmp.api diff --git a/src/plugins/igmp/igmp.api b/src/plugins/igmp/igmp.api index 2a2e68030f6..db47905ae6f 100644 --- a/src/plugins/igmp/igmp.api +++ b/src/plugins/igmp/igmp.api @@ -98,6 +98,48 @@ autoreply define igmp_enable_disable u32 sw_if_index; }; +/** + * @brief + * Add/del proxy device on specified VRF. + * Interface must be IGMP enabled in HOST mode. + * + * @param client_index - opaque cookie to identify the sender + * @param context - sender context, to match reply w/ request + * @param add - add (1) del (0) + * @param vrf_id - VRF id + * @param sw_if_index - upstream interface sw index + */ +autoreply define igmp_proxy_device_add_del +{ + u32 client_index; + u32 context; + + u8 add; + u32 vrf_id; + u32 sw_if_index; +}; + +/** + * @brief + * Add/del downstream interface to/from proxy device. + * Interface must be IGMP enabled in ROUTER mode. + * + * @param client_index - opaque cookie to identify the sender + * @param context - sender context, to match reply w/ request + * @param add - add (1) del (0) + * @param vrf_id - VRF id + * @param sw_if_index - downstream interface sw index + */ +autoreply define igmp_proxy_device_add_del_interface +{ + u32 client_index; + u32 context; + + u8 add; + u32 vrf_id; + u32 sw_if_index; +}; + /** * @brief dump (S,G)s from interface * @param client_index - opaque cookie to identify the sender diff --git a/src/plugins/igmp/igmp.c b/src/plugins/igmp/igmp.c index 85ba35c31d6..61a9970af14 100644 --- a/src/plugins/igmp/igmp.c +++ b/src/plugins/igmp/igmp.c @@ -381,6 +381,7 @@ igmp_enable_disable (u32 sw_if_index, u8 enable, igmp_mode_t mode) hash_create_mem (0, sizeof (igmp_key_t), sizeof (uword)); config->robustness_var = IGMP_DEFAULT_ROBUSTNESS_VARIABLE; config->mode = mode; + config->proxy_device_id = ~0; for (ii = 0; ii < IGMP_CONFIG_N_TIMERS; ii++) config->timers[ii] = IGMP_TIMER_ID_INVALID; @@ -445,6 +446,18 @@ igmp_enable_disable (u32 sw_if_index, u8 enable, igmp_mode_t mode) mfib_table_entry_path_remove (mfib_index, &mpfx_report, MFIB_SOURCE_IGMP, &via_itf_path); + + /* + * remove interface from proxy device + * if this device is upstream, delete proxy device + */ + if (config->mode == IGMP_MODE_ROUTER) + igmp_proxy_device_add_del_interface (config->proxy_device_id, + config->sw_if_index, 0); + else if (config->mode == IGMP_MODE_HOST) + igmp_proxy_device_add_del (config->proxy_device_id, + config->sw_if_index, 0); + igmp_clear_config (config); im->igmp_config_by_sw_if_index[config->sw_if_index] = ~0; hash_free (config->igmp_group_by_key); diff --git a/src/plugins/igmp/igmp.h b/src/plugins/igmp/igmp.h index 626d9879158..9f9b611a649 100644 --- a/src/plugins/igmp/igmp.h +++ b/src/plugins/igmp/igmp.h @@ -28,6 +28,7 @@ #include #include #include +#include /** * RFC 3376 Section 8.1 @@ -101,6 +102,16 @@ typedef struct igmp_main_t_ * pool of sources */ igmp_src_t *srcs; + + /** + * per-vrf DB of proxy devices + */ + u32 *igmp_proxy_device_by_vrf_id; + + /** + * pool of proxy devices + */ + igmp_proxy_device_t *proxy_devices; } igmp_main_t; extern igmp_main_t igmp_main; diff --git a/src/plugins/igmp/igmp_api.c b/src/plugins/igmp/igmp_api.c index 06bf26bbc17..5d836967b4b 100644 --- a/src/plugins/igmp/igmp_api.c +++ b/src/plugins/igmp/igmp_api.c @@ -50,15 +50,17 @@ #define IGMP_MSG_ID(_id) (_id + igmp_main.msg_id_base) -#define foreach_igmp_plugin_api_msg \ -_(IGMP_LISTEN, igmp_listen) \ -_(IGMP_ENABLE_DISABLE, igmp_enable_disable) \ -_(IGMP_DUMP, igmp_dump) \ -_(IGMP_CLEAR_INTERFACE, igmp_clear_interface) \ -_(IGMP_CLEAR_INTERFACE, igmp_clear_interface) \ -_(IGMP_GROUP_PREFIX_SET, igmp_group_prefix_set) \ -_(IGMP_GROUP_PREFIX_DUMP, igmp_group_prefix_dump) \ -_(WANT_IGMP_EVENTS, want_igmp_events) \ +#define foreach_igmp_plugin_api_msg \ +_(IGMP_LISTEN, igmp_listen) \ +_(IGMP_ENABLE_DISABLE, igmp_enable_disable) \ +_(IGMP_PROXY_DEVICE_ADD_DEL, igmp_proxy_device_add_del) \ +_(IGMP_PROXY_DEVICE_ADD_DEL_INTERFACE, igmp_proxy_device_add_del_interface) \ +_(IGMP_DUMP, igmp_dump) \ +_(IGMP_CLEAR_INTERFACE, igmp_clear_interface) \ +_(IGMP_CLEAR_INTERFACE, igmp_clear_interface) \ +_(IGMP_GROUP_PREFIX_SET, igmp_group_prefix_set) \ +_(IGMP_GROUP_PREFIX_DUMP, igmp_group_prefix_dump) \ +_(WANT_IGMP_EVENTS, want_igmp_events) \ static void vl_api_igmp_listen_t_handler (vl_api_igmp_listen_t * mp) @@ -120,6 +122,43 @@ vl_api_igmp_enable_disable_t_handler (vl_api_igmp_enable_disable_t * mp) REPLY_MACRO (IGMP_MSG_ID (VL_API_IGMP_ENABLE_DISABLE_REPLY)); } +static void +vl_api_igmp_proxy_device_add_del_t_handler (vl_api_igmp_proxy_device_add_del_t + * mp) +{ + vl_api_igmp_proxy_device_add_del_reply_t *rmp; + int rv = 0; + + VALIDATE_SW_IF_INDEX (mp); + + rv = + igmp_proxy_device_add_del (ntohl (mp->vrf_id), ntohl (mp->sw_if_index), + mp->add); + + BAD_SW_IF_INDEX_LABEL; + + REPLY_MACRO (IGMP_MSG_ID (VL_API_IGMP_PROXY_DEVICE_ADD_DEL_REPLY)); +} + +static void + vl_api_igmp_proxy_device_add_del_interface_t_handler + (vl_api_igmp_proxy_device_add_del_interface_t * mp) +{ + vl_api_igmp_proxy_device_add_del_interface_reply_t *rmp; + int rv = 0; + + VALIDATE_SW_IF_INDEX (mp); + + rv = + igmp_proxy_device_add_del_interface (ntohl (mp->vrf_id), + ntohl (mp->sw_if_index), mp->add); + + BAD_SW_IF_INDEX_LABEL; + + REPLY_MACRO (IGMP_MSG_ID + (VL_API_IGMP_PROXY_DEVICE_ADD_DEL_INTERFACE_REPLY)); +} + static void send_igmp_details (unix_shared_memory_queue_t * q, igmp_main_t * im, igmp_config_t * config, igmp_group_t * group, diff --git a/src/plugins/igmp/igmp_cli.c b/src/plugins/igmp/igmp_cli.c index e943fbb8349..6247f9a9388 100644 --- a/src/plugins/igmp/igmp_cli.c +++ b/src/plugins/igmp/igmp_cli.c @@ -219,6 +219,134 @@ VLIB_CLI_COMMAND (igmp_enable_command, static) = { }; /* *INDENT-ON* */ +static clib_error_t * +igmp_proxy_device_add_del_command_fn (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + unformat_input_t _line_input, *line_input = &_line_input; + vnet_main_t *vnm = vnet_get_main (); + clib_error_t *error = NULL; + u32 sw_if_index = ~0; + u32 vrf_id = ~0; + u8 add = 1; + int rv; + + if (!unformat_user (input, unformat_line_input, line_input)) + return error; + + while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (line_input, "add")) + add = 1; + else if (unformat (line_input, "del")) + add = 0; + else if (unformat (line_input, "vrf-id %u", &vrf_id)) + ; + else if (unformat (line_input, "%U", + unformat_vnet_sw_interface, vnm, &sw_if_index)); + else + { + error = + clib_error_return (0, "unknown input '%U'", format_unformat_error, + line_input); + goto done; + } + } + + if (~0 == sw_if_index) + { + error = clib_error_return (0, "interface must be specified"); + goto done; + } + + if (~0 == vrf_id) + { + error = clib_error_return (0, "VRF must be specified"); + goto done; + } + + rv = igmp_proxy_device_add_del (vrf_id, sw_if_index, add); + + if (0 != rv) + error = clib_error_return (0, "result: %d", rv); + +done: + unformat_free (line_input); + return error; +} +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (igmp_proxy_device_add_del_command, static) = { + .path = "igmp proxy-dev", + .short_help = "igmp proxy-dev vrf-id ", + .function = igmp_proxy_device_add_del_command_fn, +}; +/* *INDENT-ON* */ + +static clib_error_t * +igmp_proxy_device_add_del_interface_command_fn (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + unformat_input_t _line_input, *line_input = &_line_input; + vnet_main_t *vnm = vnet_get_main (); + clib_error_t *error = NULL; + u32 sw_if_index = ~0; + u32 vrf_id = ~0; + u8 add = 1; + int rv; + + if (!unformat_user (input, unformat_line_input, line_input)) + return error; + + while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (line_input, "add")) + add = 1; + else if (unformat (line_input, "del")) + add = 0; + else if (unformat (line_input, "vrf-id %u", &vrf_id)) + ; + else if (unformat (line_input, "%U", + unformat_vnet_sw_interface, vnm, &sw_if_index)); + else + { + error = + clib_error_return (0, "unknown input '%U'", format_unformat_error, + line_input); + goto done; + } + } + + if (~0 == sw_if_index) + { + error = clib_error_return (0, "interface must be specified"); + goto done; + } + + if (~0 == vrf_id) + { + error = clib_error_return (0, "VRF must be specified"); + goto done; + } + + rv = igmp_proxy_device_add_del_interface (vrf_id, sw_if_index, add); + + if (0 != rv) + error = clib_error_return (0, "result: %d", rv); + +done: + unformat_free (line_input); + return error; +} +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (igmp_proxy_device_add_del_interface_command, static) = { + .path = "igmp proxy-dev itf", + .short_help = "igmp proxy-dev itf vrf-id ", + .function = igmp_proxy_device_add_del_interface_command_fn, +}; +/* *INDENT-ON* */ + static clib_error_t * igmp_show_command_fn (vlib_main_t * vm, unformat_input_t * input, vlib_cli_command_t * cmd) @@ -233,9 +361,9 @@ igmp_show_command_fn (vlib_main_t * vm, unformat_input_t * input, /* *INDENT-OFF* */ pool_foreach (config, im->configs, ({ - vlib_cli_output (vm, "interface: %U mode:%U", + vlib_cli_output (vm, "interface: %U mode: %U %U", format_vnet_sw_if_index_name, vnm, config->sw_if_index, - format_igmp_mode, config->mode); + format_igmp_mode, config->mode, format_igmp_proxy_device_id, config->proxy_device_id); FOR_EACH_GROUP (group, config, ({ diff --git a/src/plugins/igmp/igmp_config.h b/src/plugins/igmp/igmp_config.h index a9f48a768fa..0da2525a851 100644 --- a/src/plugins/igmp/igmp_config.h +++ b/src/plugins/igmp/igmp_config.h @@ -53,7 +53,7 @@ typedef struct igmp_config_t_ adj_index_t adj_index; /** - * @param moe - host or router + * @param mode - host or router */ igmp_mode_t mode; @@ -71,6 +71,11 @@ typedef struct igmp_config_t_ * A vector of scheduled query-response timers */ igmp_timer_id_t timers[IGMP_CONFIG_N_TIMERS]; + + /** + * ID of a proxy device this configuration is on + */ + u32 proxy_device_id; } igmp_config_t; #define FOR_EACH_GROUP(_group, _config, _body) \ diff --git a/src/plugins/igmp/igmp_format.c b/src/plugins/igmp/igmp_format.c index 08b5f68a74d..7606fc5bf5d 100644 --- a/src/plugins/igmp/igmp_format.c +++ b/src/plugins/igmp/igmp_format.c @@ -209,6 +209,16 @@ format_igmp_key (u8 * s, va_list * args) return (s); } +u8 * +format_igmp_proxy_device_id (u8 * s, va_list * args) +{ + u32 id = va_arg (*args, u32); + + s = (id == ~0) ? s : format (s, "proxy device: %u", id); + + return (s); +} + /* * fd.io coding-style-patch-verification: ON * diff --git a/src/plugins/igmp/igmp_format.h b/src/plugins/igmp/igmp_format.h index 84862bb3afc..b7e6c954ac9 100644 --- a/src/plugins/igmp/igmp_format.h +++ b/src/plugins/igmp/igmp_format.h @@ -36,6 +36,8 @@ extern u8 *format_igmp_src_addr_list (u8 * s, va_list * args); extern u8 *format_igmp_key (u8 * s, va_list * args); +extern u8 *format_igmp_proxy_device_id (u8 * s, va_list * args); + #endif /* IGMP_FORMAT_H */ /* diff --git a/src/plugins/igmp/igmp_group.c b/src/plugins/igmp/igmp_group.c index fe023a46167..9f50ad9b05a 100644 --- a/src/plugins/igmp/igmp_group.c +++ b/src/plugins/igmp/igmp_group.c @@ -74,6 +74,14 @@ igmp_group_clear (igmp_group_t * group) config = igmp_config_get (group->config); + /* If interface is in ROUTER mode and IGMP proxy is enabled + * remove mfib path. + */ + if (config->mode == IGMP_MODE_ROUTER) + { + igmp_proxy_device_mfib_path_add_del (group, /* add */ 0); + } + IGMP_DBG ("clear-group: %U %U", format_igmp_key, group->key, format_vnet_sw_if_index_name, @@ -116,6 +124,15 @@ igmp_group_alloc (igmp_config_t * config, group->timers[ii] = IGMP_TIMER_ID_INVALID; hash_set_mem (config->igmp_group_by_key, group->key, group - im->groups); + + /* If interface is in ROUTER mode and IGMP proxy is enabled + * add mfib path. + */ + if (config->mode == IGMP_MODE_ROUTER) + { + igmp_proxy_device_mfib_path_add_del (group, /* add */ 1); + } + return (group); } diff --git a/src/plugins/igmp/igmp_proxy.c b/src/plugins/igmp/igmp_proxy.c new file mode 100644 index 00000000000..4a439d650c9 --- /dev/null +++ b/src/plugins/igmp/igmp_proxy.c @@ -0,0 +1,397 @@ +/* + *------------------------------------------------------------------ + * Copyright (c) 2018 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------ + */ + +#include +#include +#include + +#include +#include +#include + +void +igmp_proxy_device_mfib_path_add_del (igmp_group_t * group, u8 add) +{ + igmp_config_t *config; + u32 mfib_index; + + config = igmp_config_get (group->config); + mfib_index = + mfib_table_get_index_for_sw_if_index (FIB_PROTOCOL_IP4, + config->sw_if_index); + + /* *INDENT-OFF* */ + mfib_prefix_t mpfx_group_addr = { + .fp_proto = FIB_PROTOCOL_IP4, + .fp_len = 32, + .fp_grp_addr = { + .ip4 = (*group->key).ip4, + }, + }; + fib_route_path_t via_itf_path = + { + .frp_proto = fib_proto_to_dpo (FIB_PROTOCOL_IP4), + .frp_addr = zero_addr, + .frp_sw_if_index = config->sw_if_index, + .frp_fib_index = 0, + .frp_weight = 1, + }; + /* *INDENT-ON* */ + + if (add) + mfib_table_entry_path_update (mfib_index, &mpfx_group_addr, + MFIB_SOURCE_IGMP, &via_itf_path, + MFIB_ITF_FLAG_FORWARD); + else + mfib_table_entry_path_remove (mfib_index, &mpfx_group_addr, + MFIB_SOURCE_IGMP, &via_itf_path); +} + +igmp_proxy_device_t * +igmp_proxy_device_lookup (u32 vrf_id) +{ + igmp_main_t *im = &igmp_main; + + if (vec_len (im->igmp_proxy_device_by_vrf_id) > vrf_id) + { + u32 index; + index = im->igmp_proxy_device_by_vrf_id[vrf_id]; + if (index != ~0) + return (vec_elt_at_index (im->proxy_devices, index)); + } + return NULL; +} + +int +igmp_proxy_device_add_del (u32 vrf_id, u32 sw_if_index, u8 add) +{ + igmp_main_t *im = &igmp_main; + igmp_proxy_device_t *proxy_device; + igmp_config_t *config; + u32 mfib_index; + + /* check VRF id */ + mfib_index = + mfib_table_get_index_for_sw_if_index (FIB_PROTOCOL_IP4, sw_if_index); + if (mfib_index == ~0) + return VNET_API_ERROR_INVALID_INTERFACE; + if (vrf_id != mfib_table_get (mfib_index, FIB_PROTOCOL_IP4)->mft_table_id) + return VNET_API_ERROR_INVALID_INTERFACE; + + /* check IGMP configuration */ + config = igmp_config_lookup (sw_if_index); + if (!config) + return VNET_API_ERROR_INVALID_INTERFACE; + if (config->mode != IGMP_MODE_HOST) + return VNET_API_ERROR_INVALID_INTERFACE; + + proxy_device = igmp_proxy_device_lookup (vrf_id); + if (!proxy_device && add) + { + vec_validate_init_empty (im->igmp_proxy_device_by_vrf_id, vrf_id, ~0); + pool_get (im->proxy_devices, proxy_device); + im->igmp_proxy_device_by_vrf_id[vrf_id] = + proxy_device - im->proxy_devices; + memset (proxy_device, 0, sizeof (igmp_proxy_device_t)); + proxy_device->vrf_id = vrf_id; + proxy_device->upstream_if = sw_if_index; + config->proxy_device_id = vrf_id; + /* lock mfib table */ + mfib_table_lock (mfib_index, FIB_PROTOCOL_IP4, MFIB_SOURCE_IGMP); + } + else if (proxy_device && !add) + { + while (vec_len (proxy_device->downstream_ifs) > 0) + { + igmp_proxy_device_add_del_interface (vrf_id, + proxy_device->downstream_ifs + [0], 0); + } + vec_free (proxy_device->downstream_ifs); + proxy_device->downstream_ifs = NULL; + im->igmp_proxy_device_by_vrf_id[vrf_id] = ~0; + pool_put (im->proxy_devices, proxy_device); + config->proxy_device_id = ~0; + /* clear proxy database */ + igmp_clear_config (config); + /* unlock mfib table */ + mfib_table_unlock (mfib_index, FIB_PROTOCOL_IP4, MFIB_SOURCE_IGMP); + } + else + return -1; + + return 0; +} + +int +igmp_proxy_device_add_del_interface (u32 vrf_id, u32 sw_if_index, u8 add) +{ + igmp_proxy_device_t *proxy_device; + u32 index; + u32 mfib_index; + + proxy_device = igmp_proxy_device_lookup (vrf_id); + if (!proxy_device) + return -1; + + /* check VRF id */ + mfib_index = + mfib_table_get_index_for_sw_if_index (FIB_PROTOCOL_IP4, sw_if_index); + if (mfib_index == ~0) + return VNET_API_ERROR_INVALID_INTERFACE; + if (vrf_id != mfib_table_get (mfib_index, FIB_PROTOCOL_IP4)->mft_table_id) + return VNET_API_ERROR_INVALID_INTERFACE; + + /* check IGMP configuration */ + igmp_config_t *config; + config = igmp_config_lookup (sw_if_index); + if (!config) + return VNET_API_ERROR_INVALID_INTERFACE; + if (config->mode != IGMP_MODE_ROUTER) + return VNET_API_ERROR_INVALID_INTERFACE; + + if (add) + { + if (proxy_device->downstream_ifs) + { + index = vec_search (proxy_device->downstream_ifs, sw_if_index); + if (index != ~0) + return -1; + } + vec_add1 (proxy_device->downstream_ifs, sw_if_index); + config->proxy_device_id = vrf_id; + } + else + { + if (!proxy_device->downstream_ifs) + return -2; + index = vec_search (proxy_device->downstream_ifs, sw_if_index); + if (index == ~0) + return -3; + /* remove (S,G)s belonging to this interface from proxy database */ + igmp_proxy_device_merge_config (config, /* block */ 1); + vec_del1 (proxy_device->downstream_ifs, index); + config->proxy_device_id = ~0; + } + + return 0; +} + +void +igmp_proxy_device_block_src (igmp_config_t * config, igmp_group_t * group, + igmp_src_t * src) +{ + igmp_proxy_device_t *proxy_device; + igmp_config_t *proxy_config; + igmp_group_t *proxy_group; + igmp_src_t *proxy_src; + u8 *ref; + + proxy_device = igmp_proxy_device_lookup (config->proxy_device_id); + if (!proxy_device) + return; + + proxy_config = igmp_config_lookup (proxy_device->upstream_if); + ASSERT (proxy_config); + + proxy_group = igmp_group_lookup (proxy_config, group->key); + if (proxy_group == NULL) + return; + + proxy_src = igmp_src_lookup (proxy_group, src->key); + if (proxy_src == NULL) + return; + + if (vec_len (proxy_src->referance_by_config_index) <= group->config) + { + IGMP_DBG ("proxy block src: invalid config %u", group->config); + return; + } + proxy_src->referance_by_config_index[group->config] = 0; + vec_foreach (ref, proxy_src->referance_by_config_index) + { + if ((*ref) > 0) + return; + } + + /* build "Block Old Sources" report */ + igmp_pkt_build_report_t br; + ip46_address_t *srcaddrs = NULL; + + igmp_pkt_build_report_init (&br, proxy_config->sw_if_index); + vec_add1 (srcaddrs, *proxy_src->key); + igmp_pkt_report_v3_add_report (&br, proxy_group->key, srcaddrs, + IGMP_MEMBERSHIP_GROUP_block_old_sources); + igmp_pkt_report_v3_send (&br); + + + igmp_group_src_remove (proxy_group, proxy_src); + igmp_src_free (proxy_src); + + if (igmp_group_n_srcs (proxy_group, IGMP_FILTER_MODE_INCLUDE) == 0) + { + igmp_proxy_device_mfib_path_add_del (proxy_group, 0); + igmp_proxy_device_mfib_path_add_del (group, 0); + igmp_group_clear (proxy_group); + } +} + +always_inline void +igmp_proxy_device_merge_src (igmp_group_t * proxy_group, igmp_src_t * src, + ip46_address_t ** srcaddrs, u8 block) +{ + igmp_src_t *proxy_src; + u32 d_config; + + proxy_src = igmp_src_lookup (proxy_group, src->key); + + if (proxy_src == NULL) + { + if (block) + return; + /* store downstream config index */ + d_config = igmp_group_get (src->group)->config; + + proxy_src = + igmp_src_alloc (igmp_group_index (proxy_group), src->key, + IGMP_MODE_HOST); + + hash_set_mem (proxy_group->igmp_src_by_key + [proxy_group->router_filter_mode], proxy_src->key, + igmp_src_index (proxy_src)); + + vec_validate_init_empty (proxy_src->referance_by_config_index, d_config, + 0); + proxy_src->referance_by_config_index[d_config] = 1; + vec_add1 (*srcaddrs, *proxy_src->key); + } + else + { + if (block) + { + d_config = igmp_group_get (src->group)->config; + if (vec_len (proxy_src->referance_by_config_index) <= d_config) + { + IGMP_DBG ("proxy block src: invalid config %u", d_config); + return; + } + proxy_src->referance_by_config_index[d_config] = 0; + u8 *ref; + vec_foreach (ref, proxy_src->referance_by_config_index) + { + if ((*ref) > 0) + return; + } + + vec_add1 (*srcaddrs, *proxy_src->key); + + igmp_group_src_remove (proxy_group, proxy_src); + igmp_src_free (proxy_src); + + if (igmp_group_n_srcs (proxy_group, IGMP_FILTER_MODE_INCLUDE) == 0) + { + igmp_proxy_device_mfib_path_add_del (proxy_group, 0); + igmp_group_clear (proxy_group); + } + return; + } + d_config = igmp_group_get (src->group)->config; + vec_validate (proxy_src->referance_by_config_index, d_config); + proxy_src->referance_by_config_index[d_config] = 1; + return; + } +} + +always_inline igmp_group_t * +igmp_proxy_device_merge_group (igmp_proxy_device_t * proxy_device, + igmp_group_t * group, + ip46_address_t ** srcaddrs, u8 block) +{ + igmp_config_t *proxy_config; + igmp_group_t *proxy_group; + igmp_src_t *src; + + proxy_config = igmp_config_lookup (proxy_device->upstream_if); + ASSERT (proxy_config); + + proxy_group = igmp_group_lookup (proxy_config, group->key); + if (!proxy_group) + { + if (block) + return NULL; + u32 tmp = igmp_group_index (group); + proxy_group = + igmp_group_alloc (proxy_config, group->key, + group->router_filter_mode); + igmp_proxy_device_mfib_path_add_del (proxy_group, 1); + group = igmp_group_get (tmp); + } + if (block) + { + igmp_proxy_device_mfib_path_add_del (group, 0); + } + + /* *INDENT-OFF* */ + FOR_EACH_SRC (src, group, group->router_filter_mode, + ({ + igmp_proxy_device_merge_src (proxy_group, src, srcaddrs, block); + })); + /* *INDENT-ON* */ + return proxy_group; +} + +void +igmp_proxy_device_merge_config (igmp_config_t * config, u8 block) +{ + igmp_proxy_device_t *proxy_device; + igmp_group_t *group; + igmp_group_t *proxy_group; + ip46_address_t *srcaddrs = NULL; + igmp_pkt_build_report_t br; + + proxy_device = igmp_proxy_device_lookup (config->proxy_device_id); + if (!proxy_device) + return; + + igmp_pkt_build_report_init (&br, proxy_device->upstream_if); + + /* *INDENT-OFF* */ + FOR_EACH_GROUP(group, config, + ({ + proxy_group = igmp_proxy_device_merge_group (proxy_device, group, &srcaddrs, block); + + if ((vec_len(srcaddrs) > 0) && proxy_group) + { + igmp_pkt_report_v3_add_report (&br, proxy_group->key, srcaddrs, + block ? IGMP_MEMBERSHIP_GROUP_block_old_sources : + IGMP_MEMBERSHIP_GROUP_allow_new_sources); + } + vec_free (srcaddrs); + })); + /* *INDENT-ON* */ + + igmp_pkt_report_v3_send (&br); + +} + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/igmp/igmp_proxy.h b/src/plugins/igmp/igmp_proxy.h new file mode 100644 index 00000000000..6d49159518b --- /dev/null +++ b/src/plugins/igmp/igmp_proxy.h @@ -0,0 +1,62 @@ +/* + *------------------------------------------------------------------ + * Copyright (c) 2018 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------ + */ + +#ifndef _IGMP_PROXY_H_ +#define _IGMP_PROXY_H_ + +#include +#include + +typedef struct +{ + /* VRF index */ + u32 vrf_id; + + /* upstrema interface */ + u32 upstream_if; + + /* downstream interfaces */ + u32 *downstream_ifs; +} igmp_proxy_device_t; + +/** + * @brief IGMP proxy device add/del + * @param vrf_id - VRF id + * @param sw_if_index - upstream interface + * @param add - add/del + * + * Add/del IGMP proxy device. Interface must be IGMP enabled in HOST mode. + */ +int igmp_proxy_device_add_del (u32 vfr_id, u32 sw_if_index, u8 add); + +/** + * @brief IGMP proxy device add/del interface + * @param vrf_id - VRF id + * @param sw_if_index - downstream interface + * @param add - add/del + * + * Add/del IGMP enabled interface in ROUTER mode to proxy device. + */ +int igmp_proxy_device_add_del_interface (u32 vrf_id, u32 sw_if_index, u8 add); + +void igmp_proxy_device_merge_config (igmp_config_t *config, u8 block); + +void igmp_proxy_device_block_src (igmp_config_t *config, igmp_group_t *group, igmp_src_t *src); + +void igmp_proxy_device_mfib_path_add_del (igmp_group_t *group, u8 add); + +#endif /* IGMP_PROXY_H */ diff --git a/src/plugins/igmp/igmp_report.c b/src/plugins/igmp/igmp_report.c index e0c127d8015..7c08f342199 100644 --- a/src/plugins/igmp/igmp_report.c +++ b/src/plugins/igmp/igmp_report.c @@ -196,6 +196,8 @@ igmp_handle_report (const igmp_report_args_t * args) igmp_group = group_cptr (igmp_group, igmp_membership_group_v3_length (igmp_group)); } + + igmp_proxy_device_merge_config (config, 0); } /* diff --git a/src/plugins/igmp/igmp_src.c b/src/plugins/igmp/igmp_src.c index c70514a8693..0bdde83214c 100644 --- a/src/plugins/igmp/igmp_src.c +++ b/src/plugins/igmp/igmp_src.c @@ -58,6 +58,8 @@ igmp_src_exp (u32 obj, void *dat) igmp_event (IGMP_FILTER_MODE_EXCLUDE, config->sw_if_index, src->key, group->key); + + igmp_proxy_device_block_src (config, group, src); } igmp_group_src_remove (group, src); diff --git a/src/plugins/igmp/igmp_src.h b/src/plugins/igmp/igmp_src.h index 6b27cb6de10..86a043fa5c8 100644 --- a/src/plugins/igmp/igmp_src.h +++ b/src/plugins/igmp/igmp_src.h @@ -65,6 +65,12 @@ typedef struct igmp_src_t_ * Timers */ u32 timers[IGMP_SRC_N_TIMERS]; + + /** + * Tells us which configurations + * have this source. + */ + u8 *referance_by_config_index; } igmp_src_t; extern void igmp_src_free (igmp_src_t * src); diff --git a/test/test_igmp.py b/test/test_igmp.py index 128ac5e8eff..da2fa9a304e 100644 --- a/test/test_igmp.py +++ b/test/test_igmp.py @@ -651,6 +651,93 @@ class TestIgmp(VppTestCase): 0, IGMP_MODE.ROUTER) + def _create_igmpv3_pck(self, itf, rtype, maddr, srcaddrs): + p = (Ether(dst=itf.local_mac, src=itf.remote_mac) / + IP(src=itf.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1, + options=[IPOption(copy_flag=1, optclass="control", + option="router_alert")]) / + IGMPv3(type="Version 3 Membership Report") / + IGMPv3mr(numgrp=1) / + IGMPv3gr(rtype=rtype, + maddr=maddr, srcaddrs=srcaddrs)) + return p + + def test_igmp_proxy_device(self): + """ IGMP proxy device """ + self.pg2.admin_down() + self.pg2.unconfig_ip4() + self.pg2.set_table_ip4(0) + self.pg2.config_ip4() + self.pg2.admin_up() + + self.vapi.cli('test igmp timers query 10 src 3 leave 1') + + # enable IGMP + self.vapi.igmp_enable_disable(self.pg0.sw_if_index, 1, IGMP_MODE.HOST) + self.vapi.igmp_enable_disable(self.pg1.sw_if_index, 1, + IGMP_MODE.ROUTER) + self.vapi.igmp_enable_disable(self.pg2.sw_if_index, 1, + IGMP_MODE.ROUTER) + + # create IGMP proxy device + self.vapi.igmp_proxy_device_add_del(0, self.pg0.sw_if_index, 1) + self.vapi.igmp_proxy_device_add_del_interface(0, + self.pg1.sw_if_index, 1) + self.vapi.igmp_proxy_device_add_del_interface(0, + self.pg2.sw_if_index, 1) + + # send join on pg1. join should be proxied by pg0 + p_j = self._create_igmpv3_pck(self.pg1, "Allow New Sources", + "239.1.1.1", ["10.1.1.1", "10.1.1.2"]) + self.send(self.pg1, p_j) + + capture = self.pg0.get_capture(1, timeout=1) + self.verify_report(capture[0], [IgmpRecord(IgmpSG("239.1.1.1", + ["10.1.1.1", "10.1.1.2"]), "Allow New Sources")]) + self.assertTrue(find_mroute(self, "239.1.1.1", "0.0.0.0", 32)) + + # send join on pg2. join should be proxied by pg0. + # the group should contain only 10.1.1.3 as + # 10.1.1.1 was already reported + p_j = self._create_igmpv3_pck(self.pg2, "Allow New Sources", + "239.1.1.1", ["10.1.1.1", "10.1.1.3"]) + self.send(self.pg2, p_j) + + capture = self.pg0.get_capture(1, timeout=1) + self.verify_report(capture[0], [IgmpRecord(IgmpSG("239.1.1.1", + ["10.1.1.3"]), "Allow New Sources")]) + self.assertTrue(find_mroute(self, "239.1.1.1", "0.0.0.0", 32)) + + # send leave on pg2. leave for 10.1.1.3 should be proxyed + # as pg2 was the only interface interested in 10.1.1.3 + p_l = self._create_igmpv3_pck(self.pg2, "Block Old Sources", + "239.1.1.1", ["10.1.1.3"]) + self.send(self.pg2, p_l) + + capture = self.pg0.get_capture(1, timeout=2) + self.verify_report(capture[0], [IgmpRecord(IgmpSG("239.1.1.1", + ["10.1.1.3"]), "Block Old Sources")]) + self.assertTrue(find_mroute(self, "239.1.1.1", "0.0.0.0", 32)) + + # disable igmp on pg1 (also removes interface from proxy device) + # proxy leave for 10.1.1.2. pg2 is still interested in 10.1.1.1 + self.pg_enable_capture(self.pg_interfaces) + self.vapi.igmp_enable_disable(self.pg1.sw_if_index, 0, + IGMP_MODE.ROUTER) + + capture = self.pg0.get_capture(1, timeout=1) + self.verify_report(capture[0], [IgmpRecord(IgmpSG("239.1.1.1", + ["10.1.1.2"]), "Block Old Sources")]) + self.assertTrue(find_mroute(self, "239.1.1.1", "0.0.0.0", 32)) + + # disable IGMP on pg0 and pg1. + # disabling IGMP on pg0 (proxy device upstream interface) + # removes this proxy device + self.vapi.igmp_enable_disable(self.pg0.sw_if_index, 0, IGMP_MODE.HOST) + self.vapi.igmp_enable_disable(self.pg2.sw_if_index, 0, + IGMP_MODE.ROUTER) + self.assertFalse(find_mroute(self, "239.1.1.1", "0.0.0.0", 32)) + if __name__ == '__main__': unittest.main(testRunner=VppTestRunner) diff --git a/test/vpp_papi_provider.py b/test/vpp_papi_provider.py index 1f922a07b2b..4e7809f6960 100644 --- a/test/vpp_papi_provider.py +++ b/test/vpp_papi_provider.py @@ -3637,6 +3637,18 @@ class VppPapiProvider(object): 'mode': host, 'sw_if_index': sw_if_index}) + def igmp_proxy_device_add_del(self, vrf_id, sw_if_index, add): + """ Add/del IGMP proxy device """ + return self.api(self.papi.igmp_proxy_device_add_del, + {'vrf_id': vrf_id, 'sw_if_index': sw_if_index, + 'add': add}) + + def igmp_proxy_device_add_del_interface(self, vrf_id, sw_if_index, add): + """ Add/del interface to/from IGMP proxy device """ + return self.api(self.papi.igmp_proxy_device_add_del_interface, + {'vrf_id': vrf_id, 'sw_if_index': sw_if_index, + 'add': add}) + def igmp_listen(self, filter, sw_if_index, saddrs, gaddr): """ Listen for new (S,G) on specified interface -- 2.16.6