From 947ea6222dad1ef04595c34273e9231395aef443 Mon Sep 17 00:00:00 2001 From: Neale Ranns Date: Thu, 7 Jun 2018 23:48:20 -0700 Subject: [PATCH] IGMP improvements - Enable/Disable an interface for IGMP - improve logging - refactor common code - no orphaned timers - IGMP state changes in main thread only - Large groups split over multiple state-change reports - SSM range configuration API. - more tests Change-Id: If5674f1044e7e97274a711f47807c9ba689d7b9a Signed-off-by: Neale Ranns --- src/plugins/igmp.am | 12 +- src/plugins/igmp/igmp.api | 197 +++-- src/plugins/igmp/igmp.c | 1255 ++++++++-------------------- src/plugins/igmp/igmp.h | 497 ++--------- src/plugins/igmp/igmp_api.c | 290 ++++--- src/plugins/igmp/igmp_api.h | 58 ++ src/plugins/igmp/{cli.c => igmp_cli.c} | 80 +- src/plugins/igmp/igmp_config.c | 96 +++ src/plugins/igmp/igmp_config.h | 128 +++ src/plugins/igmp/{error.h => igmp_error.h} | 2 +- src/plugins/igmp/igmp_format.c | 86 +- src/plugins/igmp/igmp_format.h | 16 +- src/plugins/igmp/igmp_group.c | 272 ++++++ src/plugins/igmp/igmp_group.h | 156 ++++ src/plugins/igmp/{input.c => igmp_input.c} | 314 +++---- src/plugins/igmp/igmp_pkt.c | 534 ++++++++++++ src/plugins/igmp/igmp_pkt.h | 78 ++ src/plugins/igmp/igmp_query.c | 307 +++++++ src/plugins/igmp/igmp_query.h | 37 + src/plugins/igmp/igmp_report.c | 193 +++++ src/plugins/igmp/igmp_report.h | 37 + src/plugins/igmp/igmp_src.c | 151 ++++ src/plugins/igmp/igmp_src.h | 88 ++ src/plugins/igmp/igmp_ssm_range.c | 162 ++++ src/plugins/igmp/igmp_ssm_range.h | 58 ++ src/plugins/igmp/igmp_timer.c | 241 ++++++ src/plugins/igmp/igmp_timer.h | 84 ++ src/plugins/igmp/igmp_types.h | 77 ++ src/vlibapi/api_helper_macros.h | 4 +- src/vnet.am | 1 + src/vnet/fib/fib_test.c | 6 - src/vnet/ip/igmp_packet.h | 88 +- src/vnet/ip/ip.api | 1 + src/vnet/ip/ip4_error.h | 2 +- src/vnet/ip/ip_types.api | 6 + src/vnet/ip/ip_types_api.c | 105 +++ src/vnet/ip/ip_types_api.h | 50 ++ src/vnet/mfib/mfib_types.h | 2 + src/vppinfra/vec.h | 21 + test/framework.py | 5 +- test/test_igmp.py | 732 +++++++++++----- test/vpp_igmp.py | 75 +- test/vpp_ip_route.py | 38 +- test/vpp_papi_provider.py | 35 +- 44 files changed, 4672 insertions(+), 2005 deletions(-) create mode 100644 src/plugins/igmp/igmp_api.h rename src/plugins/igmp/{cli.c => igmp_cli.c} (72%) create mode 100644 src/plugins/igmp/igmp_config.c create mode 100644 src/plugins/igmp/igmp_config.h rename src/plugins/igmp/{error.h => igmp_error.h} (95%) create mode 100644 src/plugins/igmp/igmp_group.c create mode 100644 src/plugins/igmp/igmp_group.h rename src/plugins/igmp/{input.c => igmp_input.c} (59%) create mode 100644 src/plugins/igmp/igmp_pkt.c create mode 100644 src/plugins/igmp/igmp_pkt.h create mode 100644 src/plugins/igmp/igmp_query.c create mode 100644 src/plugins/igmp/igmp_query.h create mode 100644 src/plugins/igmp/igmp_report.c create mode 100644 src/plugins/igmp/igmp_report.h create mode 100644 src/plugins/igmp/igmp_src.c create mode 100644 src/plugins/igmp/igmp_src.h create mode 100644 src/plugins/igmp/igmp_ssm_range.c create mode 100644 src/plugins/igmp/igmp_ssm_range.h create mode 100644 src/plugins/igmp/igmp_timer.c create mode 100644 src/plugins/igmp/igmp_timer.h create mode 100644 src/plugins/igmp/igmp_types.h create mode 100644 src/vnet/ip/ip_types_api.c create mode 100644 src/vnet/ip/ip_types_api.h diff --git a/src/plugins/igmp.am b/src/plugins/igmp.am index 9829ea681ee..503d4cb14ca 100644 --- a/src/plugins/igmp.am +++ b/src/plugins/igmp.am @@ -15,10 +15,18 @@ vppplugins_LTLIBRARIES += igmp_plugin.la igmp_plugin_la_SOURCES = \ igmp/igmp.c \ - igmp/cli.c \ + igmp/igmp_query.c \ + igmp/igmp_report.c \ + igmp/igmp_group.c \ + igmp/igmp_src.c \ + igmp/igmp_config.c \ + igmp/igmp_cli.c \ igmp/igmp_api.c \ + igmp/igmp_input.c \ igmp/igmp_plugin.api.h \ - igmp/input.c \ + igmp/igmp_timer.c \ + igmp/igmp_pkt.c \ + igmp/igmp_ssm_range.c \ igmp/igmp_format.c nobase_apiinclude_HEADERS += \ diff --git a/src/plugins/igmp/igmp.api b/src/plugins/igmp/igmp.api index 1533d666a1c..9bf654f0749 100644 --- a/src/plugins/igmp/igmp.api +++ b/src/plugins/igmp/igmp.api @@ -1,3 +1,4 @@ +/* Hey Emacs use -*- mode: C -*- */ /* *------------------------------------------------------------------ * Copyright (c) 2017 Cisco and/or its affiliates. @@ -16,51 +17,92 @@ */ option version = "1.0.0"; +import "vnet/ip/ip_types.api"; -/** \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 -*/ +/** + * @brief Filter mode + */ +enum filter_mode { + EXCLUDE = 0, + INCLUDE = 1, +}; + + +/** + * @brief + * Used by a 'host' to enable the reception/listening of packets for a specific + * multicast group + * + * For each socket on which IPMulticastListen has been invoked, the + * system records the desired multicast reception state for that socket. + * That state conceptually consists of a set of records of the form: + * + * (interface, multicast-address, filter-mode, source-list) + * + * The socket state evolves in response to each invocation of + * IPMulticastListen on the socket, as follows: + * + * o If the requested filter mode is INCLUDE *and* the requested source + * list is empty, then the entry corresponding to the requested + * interface and multicast address is deleted if present. If no such + * entry is present, the request is ignored. + * + * o If the requested filter mode is EXCLUDE *or* the requested source + * list is non-empty, then the entry corresponding to the requested + * interface and multicast address, if present, is changed to contain + * the requested filter mode and source list. If no such entry is + * present, a new entry is created, using the parameters specified in + * the request. + * + * @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 filter - filter mode + * @param saddr - source address + * @param gaddr - group address + */ +typeonly define igmp_group +{ + vl_api_filter_mode_t filter; + u8 n_srcs; + u32 sw_if_index; + vl_api_ip4_address_t gaddr; + vl_api_ip4_address_t saddrs[n_srcs]; +}; autoreply define igmp_listen { u32 client_index; u32 context; - u8 enable; - u32 sw_if_index; - u8 saddr[4]; - u8 gaddr[4]; + vl_api_igmp_group_t group; }; -/** \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 -*/ +/** + * @brief + * Used by a 'router' and 'host' to enable the recption of IGMP packets. + * For hosts this must be called once before igmp_listen. + * + * @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 mode - Host (1) or router (0) mode + * @param sw_if_index - interface sw index + */ autoreply define igmp_enable_disable { u32 client_index; u32 context; u8 enable; + u8 mode; 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 +/** + * @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 (~0 for all) */ define igmp_dump { @@ -68,22 +110,22 @@ define igmp_dump 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 -*/ +/** + * @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]; + vl_api_ip4_address_t saddr; + vl_api_ip4_address_t gaddr; }; /** \brief remove all (S,G)s from an interface @@ -99,12 +141,13 @@ autoreply define igmp_clear_interface 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 -*/ +/** + * @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; @@ -119,24 +162,70 @@ service { 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 -*/ +/** + * @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 filter - filter mode + */ define igmp_event { u32 context; u32 sw_if_index; - u8 saddr[4]; - u8 gaddr[4]; - u8 is_join; + vl_api_filter_mode_t filter; + vl_api_ip4_address_t saddr; + vl_api_ip4_address_t gaddr; +}; + +/** + * @brief enum to specify either ASM or SSM semantics + */ +enum group_prefix_type +{ + ASM = 0, + SSM = 1, +}; + +/** + * @brief Definition of a Group prefix and its type + */ +typedef group_prefix +{ + vl_api_group_prefix_type_t type; + vl_api_prefix_t prefix; +}; + +/** + * @brief Configure a prefix for SSM or ASM semantics + * @param address - Prefix address + * @param address_length - Prefix length + */ +autoreply define igmp_group_prefix_set +{ + u32 client_index; + u32 context; + + vl_api_group_prefix_t gp; }; +define igmp_group_prefix_dump +{ + u32 client_index; + u32 context; +}; + +define igmp_group_prefix_details +{ + u32 context; + + vl_api_group_prefix_t gp; +}; + + /* * Local Variables: * eval: (c-set-style "gnu") diff --git a/src/plugins/igmp/igmp.c b/src/plugins/igmp/igmp.c index 71c91b05515..5cb8f02e9f5 100644 --- a/src/plugins/igmp/igmp.c +++ b/src/plugins/igmp/igmp.c @@ -27,891 +27,280 @@ #include #include +#include +#include #include #include igmp_main_t igmp_main; -void -igmp_clear_group (igmp_config_t * config, igmp_group_t * group) -{ - igmp_src_t *src; - - ASSERT (config); - ASSERT (group); - - IGMP_DBG ("group_type %u, sw_if_index %d", group->type, - config->sw_if_index); - - /* *INDENT-OFF* */ - pool_foreach (src, group->srcs, ( - { - clib_mem_free (src->key); - })); - /* *INDENT-ON* */ - pool_free (group->srcs); - hash_free (group->igmp_src_by_key); - - hash_unset_mem (config->igmp_group_by_key, group->key); - clib_mem_free (group->key); - pool_put (config->groups, group); -} - -void -igmp_clear_config (igmp_config_t * config) -{ - igmp_main_t *im = &igmp_main; - igmp_group_t *group; - - ASSERT (config); - /* *INDENT-OFF* */ - pool_foreach (group, config->groups, ( - { - igmp_clear_group (config, group); - })); - /* *INDENT-ON* */ - pool_free (config->groups); - hash_free (config->igmp_group_by_key); - - hash_unset (im->igmp_config_by_sw_if_index, config->sw_if_index); - pool_put (im->configs, config); -} - -/** \brief igmp timer compare - @param _a - igmp timer - @param _b - igmp timer - - Compare function for igmp_timer_t sorting. -*/ -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); -} - -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_group_timer (f64 time, u32 sw_if_index, igmp_key_t * gkey, - 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; - - - ASSERT (gkey); - /* duplicate keys, to prevent segmentation fault if (S,G) is removed */ - timer->data = clib_mem_alloc (sizeof (igmp_key_t)); - clib_memcpy (&((igmp_key_t *) timer->data)[0], gkey, sizeof (igmp_key_t)); - - igmp_sort_timers (im->timers); -} - -void -igmp_create_src_timer (f64 time, u32 sw_if_index, igmp_key_t * gkey, - igmp_key_t * skey, 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; - - ASSERT (gkey); - ASSERT (skey); - /* duplicate keys, to prevent segmentation fault if (S,G) is removed */ - timer->data = clib_mem_alloc (sizeof (igmp_key_t) * 2); - clib_memcpy (&((igmp_key_t *) timer->data)[0], gkey, sizeof (igmp_key_t)); - clib_memcpy (&((igmp_key_t *) timer->data)[1], skey, sizeof (igmp_key_t)); - - igmp_sort_timers (im->timers); -} - -/** \brief igmp get next timer - @param im - igmp main - - Get next timer. -*/ -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; +/* *INDENT-OFF* */ +/* General Query address */ +const static mfib_prefix_t mpfx_general_query = { + .fp_proto = FIB_PROTOCOL_IP4, + .fp_len = 32, + .fp_grp_addr = { + .ip4 = { + .as_u32 = IGMP_GENERAL_QUERY_ADDRESS, + }, + }, +}; - b->current_data += sizeof (igmp_message_t); - b->current_length += sizeof (igmp_message_t); -} -*/ +/* Report address */ +const static mfib_prefix_t mpfx_report = { + .fp_proto = FIB_PROTOCOL_IP4, + .fp_len = 32, + .fp_grp_addr = { + .ip4 = { + .as_u32 = IGMP_MEMBERSHIP_REPORT_ADDRESS, + }, + }, +}; +/* *INDENT-ON* */ -/* 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 +/** + * @brief igmp send query (igmp_timer_function_t) + * + * Send an igmp query. + * If the timer holds group key, send Group-Specific query, + * else send General query. */ - -/** \brief igmp create report all (v3) - @param b - vlib buffer - @param config - igmp configuration - @param group - igmp group - - Create IGMPv3 report. If group is NULL, send all groups on interface. -*/ -static void -igmp_create_report_v3 (vlib_buffer_t * b, igmp_config_t * config, - igmp_group_t * group) -{ - ip_csum_t sum; - u16 csum; - u32 len = 0; - int i; - - igmp_src_t *src; - - igmp_membership_group_v3_t *igmp_group; - - 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_net_to_host_u16 ((group) ? 1 : pool_elts (config->groups)); - - /* get pointer to first group */ - igmp_group = igmp->groups; - - /* if group is not NULL, send the specified group */ - if (group) - { - memset (igmp_group, 0, sizeof (igmp_membership_group_v3_t)); - igmp_group->type = group->type; - igmp_group->n_src_addresses = - clib_host_to_net_u16 (pool_elts (group->srcs)); - igmp_group->dst_address = group->addr.ip4; - i = 0; - /* *INDENT-OFF* */ - pool_foreach (src, group->srcs, ( - { - igmp_group->src_addresses[i++] = src->addr.ip4; - })); - /* *INDENT-ON* */ - len += sizeof (ip4_address_t) * i; - len += sizeof (igmp_membership_group_v3_t); - } - else - { - /* *INDENT-OFF* */ - pool_foreach (group, config->groups, ( - { - memset (igmp_group, 0, sizeof (igmp_membership_group_v3_t)); - igmp_group->type = group->type; - igmp_group->n_src_addresses = - clib_host_to_net_u16 (pool_elts (group->srcs)); - igmp_group->dst_address = group->addr.ip4; - i = 0; - pool_foreach (src, group->srcs, ( - { - igmp_group->src_addresses[i++] = src->addr.ip4; - })); - len += sizeof (ip4_address_t) * i; - len += sizeof (igmp_membership_group_v3_t); - igmp_group = group_ptr (igmp, len); - })); - /* *INDENT-ON* */ - } - - sum = ip_incremental_checksum (0, igmp, len); - csum = ~ip_csum_fold (sum); - igmp->header.checksum = csum; - - b->current_data += len; - b->current_length += len; -} - -/** \brief igmp create query (v3) - @param b - vlib buffer - @param config - configuration that sends the query - @param group - if not NULL, create Group-specific query - - Create igmp v3 qeury inside vlib buffer b. - If group == NULL create general query, - else, create group specific query. -*/ static void -igmp_create_query_v3 (vlib_buffer_t * b, igmp_config_t * config, - igmp_group_t * group) +igmp_send_general_query (u32 obj, void *dat) { - vlib_main_t *vm = vlib_get_main (); - ip_csum_t sum; - u16 csum; - - igmp_membership_query_v3_t *igmp = - (igmp_membership_query_v3_t *) (vlib_buffer_get_current (b)); - memset (igmp, 0, sizeof (igmp_membership_query_v3_t)); + igmp_pkt_build_query_t bq; + igmp_config_t *config; - igmp->header.type = IGMP_TYPE_membership_query; - igmp->header.code = 100; + config = igmp_config_get (obj); - 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); + IGMP_DBG ("send-general-query: %U", + format_vnet_sw_if_index_name, vnet_get_main (), + config->sw_if_index); - if (PREDICT_FALSE (group != NULL)) - clib_memcpy (&igmp->dst, &group->addr.ip4, sizeof (ip4_address_t)); + igmp_timer_retire (&config->timers[IGMP_CONFIG_TIMER_GENERAL_QUERY]); - sum = - ip_incremental_checksum (0, igmp, sizeof (igmp_membership_query_v3_t)); - csum = ~ip_csum_fold (sum); - igmp->header.checksum = csum; + igmp_pkt_build_query_init (&bq, config->sw_if_index); + igmp_pkt_query_v3_add_group (&bq, NULL, NULL); + igmp_pkt_query_v3_send (&bq); - b->current_data += sizeof (igmp_membership_query_v3_t); - b->current_length += sizeof (igmp_membership_query_v3_t); + /* + * re-schedule + */ + config->timers[IGMP_CONFIG_TIMER_GENERAL_QUERY] = + igmp_timer_schedule (igmp_timer_type_get (IGMP_TIMER_QUERY), + igmp_config_index (config), + igmp_send_general_query, NULL); } -/** \brief igmp create ip4 - @param b - vlib buffer - @param config - igmp configuration - @param group - igmp membership group - @param is_report - if zero create query, else create report - - Create ip4 header in vlib buffer b. -*/ static void -igmp_create_ip4 (vlib_buffer_t * b, igmp_config_t * config, - igmp_group_t * group, u8 is_report) +igmp_send_state_change_group_report_v3 (u32 sw_if_index, + const igmp_group_t * group) { - 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)); - } - - if (is_report) - ip4->dst_address.as_u32 = - clib_host_to_net_u32 (IGMP_MEMBERSHIP_REPORT_ADDRESS); - else - { - if ((group != NULL)) - clib_memcpy (&ip4->dst_address, &group->addr.ip4, - sizeof (ip4_address_t)); - else - ip4->dst_address.as_u32 = - clib_host_to_net_u32 (IGMP_GENERAL_QUERY_ADDRESS); - } - - b->current_data += ip4_header_bytes (ip4); - b->current_length += ip4_header_bytes (ip4); + igmp_pkt_build_report_t br; - config->next_create_msg (b, config, group); - ip4->length = clib_host_to_net_u16 (b->current_length); + IGMP_DBG ("state-change-group: %U", format_igmp_key, group->key); - ip4->checksum = ip4_header_checksum (ip4); + igmp_pkt_build_report_init (&br, sw_if_index); + igmp_pkt_report_v3_add_group (&br, + group, + IGMP_MEMBERSHIP_GROUP_allow_new_sources); + igmp_pkt_report_v3_send (&br); } - -/** \brief igmp send message - @param vm - vlib main - @param node - vlib runtime node - @param im - igmp main - @param config - igmp configuration - @param group - igmp mebership group - @param is_report - 0 == qeury, else report - - Send an igmp message. Get free vlib buffer fill it with igmp packet and transmit. -*/ static void -igmp_send_msg (vlib_main_t * vm, vlib_node_runtime_t * node, - igmp_main_t * im, igmp_config_t * config, igmp_group_t * group, - u8 is_report) -{ - u32 *to_next = 0; - u32 next_index = ip4_rewrite_node.index; - - u32 bi = 0; - vlib_buffer_alloc (vm, &bi, 1); - - vlib_buffer_t *b = vlib_get_buffer (vm, bi); - vlib_buffer_free_list_t *fl = vlib_buffer_get_free_list (vm, - VLIB_BUFFER_DEFAULT_FREE_LIST_INDEX); - vlib_buffer_init_for_free_list (b, fl); - - b->current_data = 0; - b->current_length = 0; - - igmp_create_ip4 (b, config, group, 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; - - - vlib_frame_t *f = vlib_get_frame_to_node (vm, next_index); - to_next = vlib_frame_vector_args (f); - to_next[0] = bi; - - f->n_vectors = 1; - - vlib_buffer_t *c = vlib_buffer_copy (vm, b); - to_next += 1; - to_next[0] = vlib_get_buffer_index (vm, c); - - vlib_put_frame_to_node (vm, next_index, f); -} - -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; -/* TODO: group-specific query: pass group key in timer */ - igmp_group_t *group = NULL; - - 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_query_v3; - igmp_send_msg (vm, rt, im, config, group, /* is_report */ 0); - - /* in case of group query we don't want to set up another qery timer */ - if (PREDICT_TRUE (!group)) - 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; -/* TODO: group-specific query: pass group key in timer */ - igmp_group_t *group = NULL; - - 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 group != NULL this is a group-specific qeury timer */ - if (PREDICT_FALSE (group != NULL)) - { - if ((group->flags & IGMP_GROUP_FLAG_QUERY_RESP_RECVED) == 0) - { - igmp_clear_group (config, group); - return; - } - } - /* if report not received 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_resend_state_change_group_report_v3 (u32 gi, void *data) { igmp_config_t *config; + igmp_group_t *group; - u32 sw_if_index = timer->sw_if_index; - - pool_put (im->timers, timer); + group = igmp_group_get (gi); + config = igmp_config_get (group->config); - config = igmp_config_lookup (im, sw_if_index); - if (!config) - return; + igmp_timer_retire (&group->timers[IGMP_GROUP_TIMER_RESEND_REPORT]); + igmp_send_state_change_group_report_v3 (config->sw_if_index, group); - if (config->flags & IGMP_CONFIG_FLAG_CAN_SEND_REPORT) + if (++group->n_reports_sent < config->robustness_var) { - /* TODO: implement IGMPv2 and IGMPv1 */ - config->next_create_msg = igmp_create_report_v3; - /* pass NULL as group to send all groups at once */ - igmp_send_msg (vm, rt, im, config, NULL, /* is_report */ 1); - /* WIP: unset flag after all reports sent */ - config->flags &= ~IGMP_CONFIG_FLAG_CAN_SEND_REPORT; + group->timers[IGMP_GROUP_TIMER_RESEND_REPORT] = + igmp_timer_schedule (igmp_timer_type_get (IGMP_TIMER_REPORT_INTERVAL), + igmp_group_index (group), + igmp_resend_state_change_group_report_v3, NULL); } } -void -igmp_send_state_changed (vlib_main_t * vm, vlib_node_runtime_t * rt, - igmp_main_t * im, igmp_timer_t * timer) +int +igmp_listen (vlib_main_t * vm, + igmp_filter_mode_t mode, + u32 sw_if_index, + const ip46_address_t * saddrs, const ip46_address_t * gaddr) { + const ip46_address_t *saddr; igmp_config_t *config; igmp_group_t *group; - igmp_src_t *src; - igmp_key_t gkey; - - u32 sw_if_index = timer->sw_if_index; - IGMP_DBG ("sw_if_index %d", sw_if_index); - ASSERT (timer->data); - clib_memcpy (&gkey, timer->data, sizeof (igmp_key_t)); - - pool_put (im->timers, timer); + /* + * RFC 3376 Section 2 + " For a given combination of socket, interface, and multicast address, + only a single filter mode and source list can be in effect at any one + time. However, either the filter mode or the source list, or both, + may be changed by subsequent IPMulticastListen requests that specify + the same socket, interface, and multicast address. Each subsequent + request completely replaces any earlier request for the given socket, + interface and multicast address." + */ + int rv = 0; + IGMP_DBG ("listen: (%U, %U) %U %U", + format_igmp_src_addr_list, saddrs, + format_igmp_key, gaddr, + format_vnet_sw_if_index_name, vnet_get_main (), + sw_if_index, format_igmp_filter_mode, mode); + /* + * find configuration, if it dosn't exist, then this interface is + * not IGMP enabled + */ + config = igmp_config_lookup (sw_if_index); - config = igmp_config_lookup (im, sw_if_index); if (!config) - return; - - group = igmp_group_lookup (config, &gkey); - if (!group) - return; - - config->next_create_msg = igmp_create_report_v3; - igmp_send_msg (vm, rt, im, config, group, /* is_report */ 1); - - IGMP_DBG ("group_type %u", group->type); - - if (group->type == IGMP_MEMBERSHIP_GROUP_change_to_filter_include) { - igmp_key_t new_gkey; - igmp_group_t *new_group; - igmp_src_t *new_src; - - clib_memcpy (&new_gkey.data, &group->addr, sizeof (ip46_address_t)); - new_gkey.group_type = IGMP_MEMBERSHIP_GROUP_mode_is_filter_include; - - new_group = igmp_group_lookup (config, &new_gkey); - if (!new_group) - { - IGMP_DBG ("creating new group..."); - pool_get (config->groups, new_group); - /* get valid pointer to old group */ - group = igmp_group_lookup (config, &gkey); - - memset (new_group, 0, sizeof (igmp_group_t)); - - clib_memcpy (&new_group->addr, &group->addr, - sizeof (ip46_address_t)); - new_group->n_srcs = 0; - new_group->type = new_gkey.group_type; - - new_group->key = clib_mem_alloc (sizeof (igmp_key_t)); - clib_memcpy (new_group->key, &new_gkey, sizeof (igmp_key_t)); - new_group->igmp_src_by_key = - hash_create_mem (0, sizeof (igmp_key_t), sizeof (uword)); - hash_set_mem (config->igmp_group_by_key, new_group->key, - new_group - config->groups); - } - /* *INDENT-OFF* */ - /* loop through old group sources */ - pool_foreach (src, group->srcs, ( - { - /* add sources to new group */ - new_src = igmp_src_lookup (new_group, src->key); - if (!new_src) - { - pool_get (new_group->srcs, new_src); - memset (new_src, 0, sizeof (igmp_src_t)); - new_group->n_srcs += 1; - new_src->key = clib_mem_alloc (sizeof (igmp_key_t)); - clib_memcpy (new_src->key, src->key, sizeof (igmp_key_t)); - clib_memcpy (&new_src->addr, &src->addr, - sizeof (ip46_address_t)); - - hash_set_mem (new_group->igmp_src_by_key, new_src->key, - new_src - new_group->srcs); - } - })); - /* *INDENT-ON* */ + rv = VNET_API_ERROR_INVALID_INTERFACE; + goto error; } - - /* remove group */ - IGMP_DBG ("remove group"); - igmp_clear_group (config, group); - if (pool_elts (config->groups) == 0) + if (config->mode != IGMP_MODE_HOST) { - hash_unset (im->igmp_config_by_sw_if_index, config->sw_if_index); - pool_put (im->configs, config); + rv = VNET_API_ERROR_INVALID_INTERFACE; + goto error; } -} - -void -igmp_src_exp (vlib_main_t * vm, vlib_node_runtime_t * rt, - igmp_main_t * im, igmp_timer_t * timer) -{ - igmp_config_t *config; - igmp_group_t *group; - igmp_src_t *src; - ASSERT (timer->data); + /* find igmp group, if it dosn't exist, create new */ + group = igmp_group_lookup (config, gaddr); - igmp_key_t *gkey = (igmp_key_t *) & ((igmp_key_t *) timer->data)[0]; - igmp_key_t *skey = (igmp_key_t *) & ((igmp_key_t *) timer->data)[1]; - - config = igmp_config_lookup (im, timer->sw_if_index); - if (!config) - goto done; - group = igmp_group_lookup (config, gkey); if (!group) - goto done; - src = igmp_src_lookup (group, skey); - if (!src) - goto done; - /* check if this timer is valid */ - if (timer->exp_time != src->exp_time) - { - timer->exp_time = src->exp_time; - igmp_sort_timers (im->timers); - return; - } - - ip46_address_t saddr; - ip46_address_t gaddr; - clib_memcpy (&saddr, skey->data, sizeof (ip46_address_t)); - clib_memcpy (&gaddr, gkey->data, sizeof (ip46_address_t)); - - /* source timer expired, remove src */ - igmp_listen (vm, 0, timer->sw_if_index, saddr, gaddr, 0); -done: - clib_mem_free (timer->data); - pool_put (im->timers, timer); -} - -/** \brief igmp timer process - @param vm - vlib main - @param rt - vlib runtime node - @param f - vlib frame - - Handle igmp timers. -*/ -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; - igmp_timer_t *timer = NULL; - while (1) { - /* suspend util timer expires */ - if (NULL != timer) - 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; - IGMP_DBG ("time: %f", vlib_time_now (vm)); - /* timer expired */ - if (NULL != timer && timer->func != NULL) - timer->func (vm, rt, im, timer); - next_timer: - timer = igmp_get_next_timer (im); + group = igmp_group_alloc (config, gaddr, mode); + + /* new group implies create all sources */ + vec_foreach (saddr, saddrs) + { + igmp_group_src_update (group, saddr, IGMP_MODE_HOST); + } + + /* + * Send state changed event report for the group. + * + * RFC3376 Section 5.1 + * "To cover the possibility of the State-Change Report being missed by + * one or more multicast routers, it is retransmitted [Robustness + * Variable] - 1 more times, at intervals chosen at random from the + * range (0, [Unsolicited Report Interval])." + */ + igmp_send_state_change_group_report_v3 (config->sw_if_index, group); + + igmp_timer_retire (&group->timers[IGMP_GROUP_TIMER_RESEND_REPORT]); + + group->n_reports_sent = 1; + group->timers[IGMP_GROUP_TIMER_RESEND_REPORT] = + igmp_timer_schedule (igmp_timer_type_get (IGMP_TIMER_REPORT_INTERVAL), + igmp_group_index (group), + igmp_resend_state_change_group_report_v3, NULL); } - 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 flags) -{ - igmp_main_t *im = &igmp_main; - igmp_config_t *config; - igmp_group_t *group; - igmp_src_t *src; - igmp_key_t skey; - igmp_key_t gkey; - - igmp_membership_group_v3_type_t group_type = - (flags & IGMP_CONFIG_FLAG_CLI_API_CONFIGURED) ? - IGMP_MEMBERSHIP_GROUP_change_to_filter_include : - IGMP_MEMBERSHIP_GROUP_mode_is_filter_include; - int rv = 0; - - /* set the lookup keys */ - skey.group_type = 0; - gkey.group_type = group_type; - clib_memcpy (&skey.data, &saddr, sizeof (ip46_address_t)); - clib_memcpy (&gkey.data, &gaddr, sizeof (ip46_address_t)); - - if (enable) + else { - /* find configuration, if it dosn't exist, create new */ - 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_group_by_key = - hash_create_mem (0, sizeof (igmp_key_t), sizeof (uword)); - /* use IGMPv3 by default */ - config->igmp_ver = IGMP_V3; - config->robustness_var = IGMP_DEFAULT_ROBUSTNESS_VARIABLE; - config->flags |= IGMP_CONFIG_FLAG_QUERY_RESP_RECVED | flags; - - if ((flags & IGMP_CONFIG_FLAG_CLI_API_CONFIGURED) == 0) - { - /* create qery timer */ - 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 (im->igmp_config_by_sw_if_index, - config->sw_if_index, config - im->configs); - } - else if ((config->flags & IGMP_CONFIG_FLAG_CLI_API_CONFIGURED & flags) - == 0) + IGMP_DBG ("... update (%U, %U) %U %U", + format_igmp_src_addr_list, saddrs, + format_igmp_key, gaddr, + format_vnet_sw_if_index_name, vnet_get_main (), + sw_if_index, format_igmp_filter_mode, mode); + + /* + * RFC 3367 Section 5.1 + * + * Old State New State State-Change Record Sent + * --------- --------- ------------------------ + * + * 1) INCLUDE (A) INCLUDE (B) ALLOW (B-A), BLOCK (A-B) + * 2) EXCLUDE (A) EXCLUDE (B) ALLOW (A-B), BLOCK (B-A) + * 3) INCLUDE (A) EXCLUDE (B) TO_EX (B) + * 4) EXCLUDE (A) INCLUDE (B) TO_IN (B) + * + * N.B. We do not split state-change records for pending transfer + * hence there is no merge logic required. + */ + + if (IGMP_FILTER_MODE_INCLUDE == mode) { - rv = -2; - goto error; - } - /* find igmp group, if it dosn't exist, create new */ - group = igmp_group_lookup (config, &gkey); - if (!group) - { - pool_get (config->groups, group); - memset (group, 0, sizeof (igmp_group_t)); - group->key = clib_mem_alloc (sizeof (igmp_key_t)); - clib_memcpy (group->key, &gkey, sizeof (igmp_key_t)); - clib_memcpy (&group->addr, &gaddr, sizeof (ip46_address_t)); - group->igmp_src_by_key = - hash_create_mem (0, sizeof (igmp_key_t), sizeof (uword)); - group->n_srcs = 0; - group->type = gkey.group_type; - if (flags & IGMP_CONFIG_FLAG_CLI_API_CONFIGURED) + ip46_address_t *added, *removed; + igmp_pkt_build_report_t br; + + /* + * find the list of sources that have been added and removed from + * the include set + */ + removed = + igmp_group_present_minus_new (group, IGMP_FILTER_MODE_INCLUDE, + saddrs); + added = + igmp_group_new_minus_present (group, IGMP_FILTER_MODE_INCLUDE, + saddrs); + + if (!(vec_len (added) || vec_len (removed))) + /* no change => done */ + goto error; + + igmp_pkt_build_report_init (&br, config->sw_if_index); + + if (vec_len (added)) { - /* create state-changed report timer with zero timeout */ - igmp_create_group_timer (0, sw_if_index, group->key, - igmp_send_state_changed); + igmp_pkt_report_v3_add_report (&br, + group->key, + added, + IGMP_MEMBERSHIP_GROUP_allow_new_sources); } - hash_set_mem (config->igmp_group_by_key, group->key, - group - config->groups); - } - /* find source, if it dosn't exist, create new */ - src = igmp_src_lookup (group, &skey); - if (!src) - { - pool_get (group->srcs, src); - memset (src, 0, sizeof (igmp_src_t)); - group->n_srcs += 1; - src->key = clib_mem_alloc (sizeof (igmp_key_t)); - clib_memcpy (src->key, &skey, sizeof (igmp_key_t)); - clib_memcpy (&src->addr, &saddr, sizeof (ip46_address_t)); - if ((flags & IGMP_CONFIG_FLAG_CLI_API_CONFIGURED) == 0) + if (vec_len (removed)) { - /* arm source timer (after expiration remove (S,G)) */ - igmp_event (im, config, group, src); - src->exp_time = vlib_time_now (vm) + IGMP_SRC_TIMER; - igmp_create_src_timer (src->exp_time, config->sw_if_index, - group->key, src->key, igmp_src_exp); + igmp_pkt_report_v3_add_report (&br, + group->key, + removed, + IGMP_MEMBERSHIP_GROUP_block_old_sources); } - hash_set_mem (group->igmp_src_by_key, src->key, src - group->srcs); - } - else - { - rv = -1; - goto error; - } - } - else - { - config = igmp_config_lookup (im, sw_if_index); - if (config) - { - gkey.group_type = IGMP_MEMBERSHIP_GROUP_mode_is_filter_include; - group = igmp_group_lookup (config, &gkey); - if (group) - { - src = igmp_src_lookup (group, &skey); - if (src) - { - /* add source to block_all_sources group */ - igmp_key_t new_gkey; - igmp_group_t *new_group; + IGMP_DBG ("... added %U", format_igmp_src_addr_list, added); + IGMP_DBG ("... removed %U", format_igmp_src_addr_list, removed); - clib_memcpy (&new_gkey, &gkey, sizeof (igmp_key_t)); - new_gkey.group_type = - IGMP_MEMBERSHIP_GROUP_block_old_sources; - new_group = igmp_group_lookup (config, &new_gkey); - if (!new_group) - { - pool_get (config->groups, new_group); + igmp_pkt_report_v3_send (&br); - group = igmp_group_lookup (config, &gkey); + /* + * clear the group of the old sources and populate it with the new + * set requested + */ + igmp_group_free_all_srcs (group); - memset (new_group, 0, sizeof (igmp_group_t)); - new_group->key = clib_mem_alloc (sizeof (igmp_key_t)); - clib_memcpy (new_group->key, &new_gkey, - sizeof (igmp_key_t)); - clib_memcpy (&new_group->addr, &group->addr, - sizeof (ip46_address_t)); - new_group->igmp_src_by_key = - hash_create_mem (0, sizeof (igmp_key_t), - sizeof (uword)); - new_group->n_srcs = 0; - new_group->type = new_gkey.group_type; - hash_set_mem (config->igmp_group_by_key, new_group->key, - new_group - config->groups); - } - igmp_src_t *new_src; - new_src = igmp_src_lookup (new_group, &skey); - if (!new_src) - { - pool_get (new_group->srcs, new_src); - memset (new_src, 0, sizeof (igmp_src_t)); - new_group->n_srcs += 1; - new_src->key = clib_mem_alloc (sizeof (igmp_key_t)); - clib_memcpy (new_src->key, src->key, - sizeof (igmp_key_t)); - clib_memcpy (&new_src->addr, &src->addr, - sizeof (ip46_address_t)); - hash_set_mem (new_group->igmp_src_by_key, new_src->key, - new_src - new_group->srcs); - } + vec_foreach (saddr, saddrs) + { + igmp_group_src_update (group, saddr, IGMP_MODE_HOST); + } - /* notify all registered api clients */ - if ((flags & IGMP_CONFIG_FLAG_CLI_API_CONFIGURED) == 0) - igmp_event (im, config, new_group, new_src); - else - igmp_create_group_timer (0, sw_if_index, new_group->key, - igmp_send_state_changed); - /* remove source form mode_is_filter_include group */ - hash_unset_mem (group->igmp_src_by_key, src->key); - clib_mem_free (src->key); - pool_put (group->srcs, src); - group->n_srcs -= 1; - if (group->n_srcs <= 0) - igmp_clear_group (config, group); - if (pool_elts (config->groups) <= 0) - igmp_clear_config (config); - } - else - { - rv = -1; - goto error; - } - } - else - { - rv = -1; - goto error; - } + if (0 == igmp_group_n_srcs (group, mode)) + igmp_group_clear (group); + + vec_free (added); + vec_free (removed); } else { - rv = -1; - goto error; + /* + * The control plane is excluding some sources. + * - First; check for those that are present in the include list + * - Second; check add them to the exlude list + * + * TODO + */ } } error: - return rv; + return (rv); } /** \brief igmp hardware interface link up down @@ -921,26 +310,149 @@ error: If an interface goes down, remove its (S,G)s. */ -static clib_error_t * -igmp_hw_interface_link_up_down (vnet_main_t * vnm, u32 hw_if_index, u32 flags) +static walk_rc_t +igmp_sw_if_down (vnet_main_t * vnm, u32 sw_if_index, void *ctx) { - 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) + config = igmp_config_lookup (sw_if_index); + IGMP_DBG ("down: %U", + format_vnet_sw_if_index_name, vnet_get_main (), sw_if_index); + if (NULL != config) { - if ((flags & VNET_HW_INTERFACE_FLAG_LINK_UP) == 0) - igmp_clear_config (config); + igmp_clear_config (config); } + + return (WALK_CONTINUE); +} + +static clib_error_t * +igmp_hw_interface_link_up_down (vnet_main_t * vnm, u32 hw_if_index, u32 flags) +{ + clib_error_t *error = NULL; + /* remove igmp state from down interfaces */ + if (!(flags & VNET_HW_INTERFACE_FLAG_LINK_UP)) + vnet_hw_interface_walk_sw (vnm, hw_if_index, igmp_sw_if_down, NULL); return error; } VNET_HW_INTERFACE_LINK_UP_DOWN_FUNCTION (igmp_hw_interface_link_up_down); +int +igmp_enable_disable (u32 sw_if_index, u8 enable, igmp_mode_t mode) +{ + igmp_config_t *config; + igmp_main_t *im = &igmp_main; + u32 mfib_index; + IGMP_DBG ("%s: %U", (enable ? "Enabled" : "Disabled"), + format_vnet_sw_if_index_name, vnet_get_main (), sw_if_index); + + /* *INDENT-OFF* */ + fib_route_path_t for_us_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 = 1, + .frp_flags = FIB_ROUTE_PATH_LOCAL, + }; + fib_route_path_t via_itf_path = + { + .frp_proto = fib_proto_to_dpo (FIB_PROTOCOL_IP4), + .frp_addr = zero_addr, + .frp_sw_if_index = sw_if_index, + .frp_fib_index = 0, + .frp_weight = 1, + }; + /* *INDENT-ON* */ + /* find configuration, if it dosn't exist, create new */ + config = igmp_config_lookup (sw_if_index); + mfib_index = mfib_table_get_index_for_sw_if_index (FIB_PROTOCOL_IP4, + sw_if_index); + if (!config && enable) + { + u32 ii; + + vec_validate_init_empty (im->igmp_config_by_sw_if_index, + sw_if_index, ~0); + pool_get (im->configs, config); + memset (config, 0, sizeof (igmp_config_t)); + config->sw_if_index = sw_if_index; + config->igmp_group_by_key = + hash_create_mem (0, sizeof (igmp_key_t), sizeof (uword)); + config->robustness_var = IGMP_DEFAULT_ROBUSTNESS_VARIABLE; + config->mode = mode; + + for (ii = 0; ii < IGMP_CONFIG_N_TIMERS; ii++) + config->timers[ii] = IGMP_TIMER_ID_INVALID; + + if (IGMP_MODE_ROUTER == mode) + { + config->timers[IGMP_CONFIG_TIMER_GENERAL_QUERY] = + igmp_timer_schedule (igmp_timer_type_get (IGMP_TIMER_QUERY), + igmp_config_index (config), + igmp_send_general_query, NULL); + } + + config->adj_index = + adj_mcast_add_or_lock (FIB_PROTOCOL_IP4, VNET_LINK_IP4, + config->sw_if_index); + im->igmp_config_by_sw_if_index[config->sw_if_index] = + (config - im->configs); + { + vec_validate (im->n_configs_per_mfib_index, mfib_index); + im->n_configs_per_mfib_index[mfib_index]++; + if (1 == im->n_configs_per_mfib_index[mfib_index]) + { + /* first config in this FIB */ + mfib_table_entry_path_update (mfib_index, + &mpfx_general_query, + MFIB_SOURCE_IGMP, + &for_us_path, + MFIB_ITF_FLAG_FORWARD); + mfib_table_entry_path_update (mfib_index, + &mpfx_report, + MFIB_SOURCE_IGMP, + &for_us_path, + MFIB_ITF_FLAG_FORWARD); + } + mfib_table_entry_path_update (mfib_index, + &mpfx_general_query, + MFIB_SOURCE_IGMP, + &via_itf_path, MFIB_ITF_FLAG_ACCEPT); + mfib_table_entry_path_update (mfib_index, &mpfx_report, + MFIB_SOURCE_IGMP, &via_itf_path, + MFIB_ITF_FLAG_ACCEPT); + } + } + else if (config && !enable) + { + vec_validate (im->n_configs_per_mfib_index, mfib_index); + im->n_configs_per_mfib_index[mfib_index]--; + if (0 == im->n_configs_per_mfib_index[mfib_index]) + { + /* last config in this FIB */ + mfib_table_entry_path_remove (mfib_index, + &mpfx_general_query, + MFIB_SOURCE_IGMP, &for_us_path); + mfib_table_entry_path_remove (mfib_index, + &mpfx_report, + MFIB_SOURCE_IGMP, &for_us_path); + } + + mfib_table_entry_path_remove (mfib_index, + &mpfx_general_query, + MFIB_SOURCE_IGMP, &via_itf_path); + mfib_table_entry_path_remove (mfib_index, + &mpfx_report, + MFIB_SOURCE_IGMP, &via_itf_path); + igmp_clear_config (config); + im->igmp_config_by_sw_if_index[config->sw_if_index] = ~0; + hash_free (config->igmp_group_by_key); + pool_put (im->configs, config); + } + + return (0); +} /** \brief igmp initialization @param vm - vlib main @@ -952,97 +464,24 @@ igmp_init (vlib_main_t * vm) { clib_error_t *error; igmp_main_t *im = &igmp_main; - int i; + if ((error = vlib_call_init_function (vm, ip4_lookup_init))) return error; - im->igmp_config_by_sw_if_index = hash_create (0, sizeof (u32)); - im->igmp_api_client_by_client_index = hash_create (0, sizeof (u32)); - 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 = { - .as_u64[0] = 0, - .as_u64[1] = 0 - }; - addr0.ip4.as_u32 = clib_host_to_net_u32 (IGMP_GENERAL_QUERY_ADDRESS); - - /* Report address */ - ip46_address_t addr1 = { - .as_u64[0] = 0, - .as_u64[1] = 0 - }; - addr1.ip4.as_u32 = clib_host_to_net_u32 (IGMP_MEMBERSHIP_REPORT_ADDRESS); - - 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, - }; + im->igmp_api_client_by_client_index = hash_create (0, sizeof (u32)); - const mfib_prefix_t mpfx0 = { - .fp_proto = FIB_PROTOCOL_IP4, - .fp_len = 32, - .fp_grp_addr = addr0, - }; + im->logger = vlib_log_register_class ("igmp", 0); - const mfib_prefix_t mpfx1 = { - .fp_proto = FIB_PROTOCOL_IP4, - .fp_len = 32, - .fp_grp_addr = addr1, - }; + IGMP_DBG ("initialized"); - /* 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 (error); } VLIB_INIT_FUNCTION (igmp_init); /* *INDENT-OFF* */ VLIB_PLUGIN_REGISTER () = { - .version = VPP_BUILD_VER, - .description = "IGMP messaging", + .version = VPP_BUILD_VER, + .description = "IGMP messaging", }; /* *INDENT-ON* */ diff --git a/src/plugins/igmp/igmp.h b/src/plugins/igmp/igmp.h index b8759ffa3b1..bf123ddbb79 100644 --- a/src/plugins/igmp/igmp.h +++ b/src/plugins/igmp/igmp.h @@ -23,436 +23,123 @@ #include #include #include +#include #include +#include +#include +#include -#define IGMP_QUERY_TIMER (60) -#define IGMP_SRC_TIMER (3 * IGMP_QUERY_TIMER) +/** + * RFC 3376 Section 8.1 + */ #define IGMP_DEFAULT_ROBUSTNESS_VARIABLE (2) -#define ENABLE_IGMP_DBG 0 +#define IGMP_DBG(...) \ + vlib_log_debug (igmp_main.logger, __VA_ARGS__); -#if ENABLE_IGMP_DBG == 1 -#define IGMP_DBG(...) clib_warning(__VA_ARGS__) +/** + * General Query address - 224.0.0.1 + * Membership Report address - 224.0.0.22 + * SSM default range 232/8 + */ +#if CLIB_ARCH_IS_BIG_ENDIAN +#define IGMP_GENERAL_QUERY_ADDRESS (0xE0000001) +#define IGMP_MEMBERSHIP_REPORT_ADDRESS (0xE0000016) +#define IGMP_SSM_DEFAULT (0xE8000000) #else -#define IGMP_DBG(...) -#endif /* ENABLE_IGMP_DBG */ - -/** General Query address - 224.0.0.1 */ -#define IGMP_GENERAL_QUERY_ADDRESS (0xE0000001) -/** Membership Report address - 224.0.0.22 */ -#define IGMP_MEMBERSHIP_REPORT_ADDRESS (0xE0000016) +#define IGMP_GENERAL_QUERY_ADDRESS (0x010000E0) +#define IGMP_MEMBERSHIP_REPORT_ADDRESS (0x160000E0) +#define IGMP_SSM_DEFAULT (0x000000E8) +#endif /** helper macro to get igmp mebership group from pointer plus offset */ -#define group_ptr(p, l) ((igmp_membership_group_v3_t *)((char*)p + l)) - -#define foreach_igmp_config_flag \ - _(0, QUERY_RESP_RECVED, "query_response_received") \ - _(1, CAN_SEND_REPORT, "can_send_report") \ - _(2, CLI_API_CONFIGURED, "cli/api") - -typedef enum -{ -#define _(a,b,c) IGMP_CONFIG_FLAG_##b = (1 << a), - foreach_igmp_config_flag -#undef _ -} igmp_config_flag_t; - -enum -{ - IGMP_PROCESS_EVENT_UPDATE_TIMER = 1, -} igmp_process_event_t; - -/*! Igmp versions */ -typedef enum -{ - IGMP_V1, - IGMP_V2, - IGMP_V3, -} igmp_ver_t; - -struct igmp_config_t_; - -typedef struct igmp_config_t_ igmp_config_t; - -struct igmp_group_t_; - -typedef struct igmp_group_t_ igmp_group_t; - -/** \brief create message - @param b - vlib buffer - @param config - igmp configuration - @param group - igmp group - - Populate supplied bufefr with IGMP message. -*/ -typedef void (create_msg_t) (vlib_buffer_t * b, igmp_config_t * config, - igmp_group_t * group); - -/** \brief igmp key - @param data - key data - @param group_type - membership group type -*/ -typedef struct -{ - u64 data[2]; /*!< ip46_address_t.as_u64 */ - u64 group_type; /*!< zero in case of source key */ -} igmp_key_t; - -/** \brief igmp source - @param addr - ip4/6 source address - @param exp_time - expiration time - @param key - pointer to key -*/ -typedef struct -{ - ip46_address_t addr; - - f64 exp_time; - - igmp_key_t *key; -} igmp_src_t; - -/** \brief igmp group - @param addr - ip4/6 group address - @param exp_time - expiration time - @param key - pointer to key - @param type - membership group type - @param n_srcs - number of sources - @param flags - igmp group flags - @param igmp_src_by_key - source by key hash - @param srcs - pool of sources -*/ -typedef struct igmp_group_t_ -{ - ip46_address_t addr; - - f64 exp_time; - - igmp_key_t *key; - - igmp_membership_group_v3_type_t type; +#define group_ptr(p, l) ((igmp_membership_group_v3_t *)((u8*)(p) + (l))) +#define group_cptr(p, l) ((const igmp_membership_group_v3_t *)((u8*)(p) + (l))) - u16 n_srcs; - - u8 flags; -/** reponse to query was received */ -#define IGMP_GROUP_FLAG_QUERY_RESP_RECVED (1 << 0) - - uword *igmp_src_by_key; - igmp_src_t *srcs; -} igmp_group_t; - -/** \brief igmp configuration - @param sw_if_index - interface sw_if_index - @param adj_index - adjacency index - @param next_create_msg - specify next igmp message - @param igmp_ver - igmp version - @param robustness_var - robustness variable - @param flags - igmp configuration falgs - @param igmp_group_by_key - group by key hash - @param groups - pool of groups -*/ -typedef struct igmp_config_t_ -{ - u32 sw_if_index; - - adj_index_t adj_index; - - create_msg_t *next_create_msg; - - igmp_ver_t igmp_ver; - - u8 robustness_var; - - u8 flags; - - uword *igmp_group_by_key; - - igmp_group_t *groups; -} igmp_config_t; - -struct igmp_timer_t_; - -typedef struct igmp_timer_t_ igmp_timer_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; - -/** \brief igmp main - @param msg_id_base - API message ID base - @param igmp_api_client_by_client_index - get api client by client_index - @param api_clients - pool of api clients registered for join/leave notifications - @param igmp_config_by_sw_if_index - get config index by config key - @param configs - pool of igmp configurations - @param buffers - buffer cache - @param timers - pool of igmp timers - @param type_infos - igmp type info - @param report_type_infos - igmp report type info - @param type_info_by_type - - @param report_type_info_by_report_type - - @param general_query_address - 224.0.0.1 - @param membership_report_address - 224.0.0.22 -*/ +/** + * collection of data related to IGMP + */ typedef struct igmp_main_t_ { + /** + * API base message ID + */ u16 msg_id_base; uword *igmp_api_client_by_client_index; + /** + * API client registered for events + */ vpe_client_registration_t *api_clients; - uword *igmp_config_by_sw_if_index; + /** + * per-interface DB of configs + */ + u32 *igmp_config_by_sw_if_index; - igmp_config_t *configs; + /** + * the number of igmp configs for each mfib_index (VRF) + */ + u32 *n_configs_per_mfib_index; - igmp_timer_t *timers; + /** + * logger - VLIB log class + */ + vlib_log_class_t logger; - igmp_type_info_t *type_infos; - igmp_report_type_info_t *report_type_infos; + /** + * pool of configs + */ + igmp_config_t *configs; - uword *type_info_by_type; - uword *report_type_info_by_report_type; + /** + * pool of groups + */ + igmp_group_t *groups; + /** + * pool of sources + */ + igmp_src_t *srcs; } igmp_main_t; extern igmp_main_t igmp_main; -/** \brief igmp timer function - @param vm - vlib main - @param rt - vlib runtime node - @param im - igmp main - @param timer - igmp timer -*/ -typedef void (igmp_timer_function_t) (vlib_main_t * vm, - vlib_node_runtime_t * rt, - igmp_main_t * im, igmp_timer_t * timer); - -/** \brief igmp timer - @param exp_time - expiration time - @param func - function to call on timer expiration - @param sw_if_index - interface sw_if_index - @param data - custom data -*/ -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; - -/** \brief igmp listen - @param vm - vlib main - @param enable - 0 == remove (S,G), else add (S,G) - @param sw_if_index - interface sw_if_index - @param saddr - source address - @param gaddr - group address - @param flags - igmp configuration flags - - Add/del (S,G) on an interface. If user configured, - send a status change report from the interface. - If a report was received on interface notify registered api clients. -*/ -int igmp_listen (vlib_main_t * vm, u8 enable, u32 sw_if_index, - ip46_address_t saddr, ip46_address_t gaddr, u8 flags); - -/** \brief igmp clear config - @param config - igmp configuration - - Clear all (S,G)s on specified config and remove this config from pool. -*/ -void igmp_clear_config (igmp_config_t * config); - -/** \brief igmp clear group - @param config - igmp configuration - @param group - the group to be removed - - Remove this group from interface (specified by configuration). -*/ -void igmp_clear_group (igmp_config_t * config, igmp_group_t * group); - -/** \brief igmp sort timers - @param timers - pool of igmp timers - - Sort igmp timers, so that the first to expire is at end. -*/ -void igmp_sort_timers (igmp_timer_t * timers); - -/** \brief igmp create int timer - @param time - expiration time (at this time the timer will expire) - @param sw_if_index - interface sw_if_index - @param func - function to all after timer expiration - - - Creates new interface timer. Delayed reports, query msg, query resp. -*/ -void igmp_create_int_timer (f64 time, u32 sw_if_index, - igmp_timer_function_t * func); - -/** \brief igmp create group timer - @param time - expiration time (at this time the timer will expire) - @param sw_if_index - interface sw_if_index - @param gkey - key to find the group by - @param func - function to all after timer expiration - - Creates new group timer. -*/ -void igmp_create_group_timer (f64 time, u32 sw_if_index, igmp_key_t * gkey, - igmp_timer_function_t * func); - -/** \brief igmp create group timer - @param time - expiration time (at this time the timer will expire) - @param sw_if_index - interface sw_if_index - @param gkey - key to find the group by - @param skey - key to find the source by - @param func - function to all after timer expiration - - Creates new source timer. -*/ -void igmp_create_src_timer (f64 time, u32 sw_if_index, igmp_key_t * gkey, - igmp_key_t * skey, igmp_timer_function_t * func); - -/** \brief igmp send query (igmp_timer_function_t) - - Send an igmp query. - If the timer holds group key, send Group-Specific query, - else send General query. -*/ -void igmp_send_query (vlib_main_t * vm, vlib_node_runtime_t * rt, - igmp_main_t * im, igmp_timer_t * timer); - -/** \brief igmp query response expiration (igmp_timer_function_t) - - If a response to a query didn't come in time, remove (S,G)s. -*/ -void igmp_query_resp_exp (vlib_main_t * vm, vlib_node_runtime_t * rt, - igmp_main_t * im, igmp_timer_t * timer); - -/** \brief igmp send report (igmp_timer_function_t) - - Send igmp membership report. -*/ -void igmp_send_report (vlib_main_t * vm, vlib_node_runtime_t * rt, - igmp_main_t * im, igmp_timer_t * timer); - -/** \brief igmp send state changed (igmp_timer_function_t) - - Send report if an (S,G) filter has changed. -*/ -void igmp_send_state_changed (vlib_main_t * vm, vlib_node_runtime_t * rt, - igmp_main_t * im, igmp_timer_t * timer); - -/** \brief igmp source expiration (igmp_timer_function_t) - - Remove expired (S,G) from group. -*/ -void igmp_src_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; -} - -/** \brief igmp event - @param im - igmp main - @param config - igmp configuration - @param group - igmp group - @param src - source - - Notify registered api clients of (S,G) filter update. -*/ -void igmp_event (igmp_main_t * im, igmp_config_t * config, - igmp_group_t * group, igmp_src_t * src); - -typedef enum -{ - IGMP_NEXT_IP4_REWRITE_MCAST_NODE, - IGMP_NEXT_IP6_REWRITE_MCAST_NODE, - IGMP_N_NEXT, -} igmp_next_t; - -/** \brief igmp config lookup - @param im - igmp main - @param sw_if_index - interface sw_if_index -*/ -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 (im->igmp_config_by_sw_if_index, sw_if_index); - if (p) - config = vec_elt_at_index (im->configs, p[0]); - - return config; -} - -/** \brief igmp group lookup - @param config - igmp configuration - @param key - igmp key -*/ -always_inline igmp_group_t * -igmp_group_lookup (igmp_config_t * config, igmp_key_t * key) -{ - uword *p; - igmp_group_t *group = NULL; - if (!config) - return NULL; - - p = hash_get_mem (config->igmp_group_by_key, key); - if (p) - group = vec_elt_at_index (config->groups, p[0]); - - return group; -} - -/** \brief igmp group lookup - @param group - igmp group - @param key - igmp key -*/ -always_inline igmp_src_t * -igmp_src_lookup (igmp_group_t * group, igmp_key_t * key) -{ - uword *p; - igmp_src_t *src = NULL; - if (!group) - return NULL; - - p = hash_get_mem (group->igmp_src_by_key, key); - if (p) - src = vec_elt_at_index (group->srcs, p[0]); - - return src; -} +/** + * @brief IGMP interface enable/disable + * @param sw_if_index - Interface + * @param enable - enable/disable + * @param mode - Host or router + */ +int igmp_enable_disable (u32 sw_if_index, u8 enable, igmp_mode_t mode); + +/** + * @brief igmp listen + * Called by a host to request reception of multicast packets + * @param vm - vlib main + * @param filter - Filter mode + * @param sw_if_index - interface sw_if_index + * @param saddr - source address + * @param gaddr - group address + * + * Add/del (S,G) on an interface. + * send a status change report from the interface. + */ +int igmp_listen (vlib_main_t * vm, + igmp_filter_mode_t filter, + u32 sw_if_index, + const ip46_address_t * saddr, const ip46_address_t * gaddr); + +/** + * @brief Send an IGMP event to listening parties + * @param filter mode + * @param sw_if_index + * @param saddr + * @param gaddr + */ +void igmp_event (igmp_filter_mode_t filter, + u32 sw_if_index, + const ip46_address_t * saddr, const ip46_address_t * gaddr); #endif /* _IGMP_H_ */ diff --git a/src/plugins/igmp/igmp_api.c b/src/plugins/igmp/igmp_api.c index d283a0f3935..71fb3e22089 100644 --- a/src/plugins/igmp/igmp_api.c +++ b/src/plugins/igmp/igmp_api.c @@ -19,6 +19,8 @@ #include #include +#include +#include /* define message IDs */ #include @@ -46,65 +48,76 @@ #include +#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) \ -_(WANT_IGMP_EVENTS, want_igmp_events) \ +_(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) { 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; + int ii, rv = 0; + ip46_address_t gaddr, *saddrs = NULL; - if (!vnet_sw_interface_is_api_valid (vnm, ntohl (mp->sw_if_index))) - { - rv = VNET_API_ERROR_INVALID_SW_IF_INDEX; - goto done; - } + VALIDATE_SW_IF_INDEX (&mp->group); - if ((vnet_sw_interface_get_flags (vnm, ntohl (mp->sw_if_index)) && + if ((vnet_sw_interface_get_flags (vnm, ntohl (mp->group.sw_if_index)) && VNET_SW_INTERFACE_FLAG_ADMIN_UP) == 0) { + // FIXME - don't we clear this state on interface down ... 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); + memset (&gaddr, 0, sizeof (gaddr)); + clib_memcpy (&gaddr.ip4, &mp->group.gaddr, sizeof (ip4_address_t)); - rv = - igmp_listen (vm, mp->enable, ntohl (mp->sw_if_index), saddr, gaddr, - IGMP_CONFIG_FLAG_CLI_API_CONFIGURED); + vec_validate (saddrs, mp->group.n_srcs - 1); -done:; - unix_shared_memory_queue_t *q = - vl_api_client_index_to_input_queue (mp->client_index); - if (!q) - return; + vec_foreach_index (ii, saddrs) + { + clib_memcpy (&saddrs[ii].ip4, + &mp->group.saddrs[ii], sizeof (ip4_address_t)); + } - 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); + rv = igmp_listen (vm, + (mp->group.filter ? + IGMP_FILTER_MODE_INCLUDE : + IGMP_FILTER_MODE_EXCLUDE), + ntohl (mp->group.sw_if_index), saddrs, &gaddr); - vl_msg_api_send_shmem (q, (u8 *) & rmp); + vec_free (saddrs); + + BAD_SW_IF_INDEX_LABEL; +done:; + REPLY_MACRO (IGMP_MSG_ID (VL_API_IGMP_LISTEN_REPLY)); } 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); + VALIDATE_SW_IF_INDEX (mp); + + rv = igmp_enable_disable (ntohl (mp->sw_if_index), + mp->enable, + (mp->mode ? IGMP_MODE_HOST : IGMP_MODE_ROUTER)); + + BAD_SW_IF_INDEX_LABEL; + + REPLY_MACRO (IGMP_MSG_ID (VL_API_IGMP_ENABLE_DISABLE_REPLY)); } static void @@ -117,89 +130,164 @@ send_igmp_details (unix_shared_memory_queue_t * q, igmp_main_t * im, 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->_vl_msg_id = htons (IGMP_MSG_ID (VL_API_IGMP_DETAILS)); mp->context = context; mp->sw_if_index = htonl (config->sw_if_index); - clib_memcpy (mp->saddr, &src->addr.ip4, sizeof (u8) * 4); - clib_memcpy (mp->gaddr, &group->addr.ip4, sizeof (u8) * 4); + clib_memcpy (mp->saddr.address, &src->key->ip4, sizeof (src->key->ip4)); + clib_memcpy (mp->gaddr.address, &group->key->ip4, sizeof (group->key->ip6)); vl_msg_api_send_shmem (q, (u8 *) & mp); } +static void +igmp_config_dump (igmp_main_t * im, + unix_shared_memory_queue_t * q, + u32 context, igmp_config_t * config) +{ + igmp_group_t *group; + igmp_src_t *src; + + /* *INDENT-OFF* */ + FOR_EACH_GROUP (group, config, + ({ + FOR_EACH_SRC (src, group, IGMP_FILTER_MODE_INCLUDE, + ({ + send_igmp_details (q, im, config, group, src, context); + })); + })); + /* *INDENT-ON* */ +} + static void vl_api_igmp_dump_t_handler (vl_api_igmp_dump_t * mp) { + unix_shared_memory_queue_t *q; igmp_main_t *im = &igmp_main; igmp_config_t *config; - igmp_group_t *group; - igmp_src_t *src; + u32 sw_if_index; - unix_shared_memory_queue_t *q = - vl_api_client_index_to_input_queue (mp->client_index); + q = vl_api_client_index_to_input_queue (mp->client_index); if (!q) return; - if (mp->dump_all) + sw_if_index = ntohl (mp->sw_if_index); + if (~0 == sw_if_index) { /* *INDENT-OFF* */ - pool_foreach (config, im->configs, ( - { - pool_foreach (group, config->groups, ( - { - pool_foreach (src, group->srcs, ( - { - send_igmp_details (q, im, config, group, src, mp->context); - })); - })); + pool_foreach (config, im->configs, + ({ + igmp_config_dump(im, q, mp->context, config); })); /* *INDENT-ON* */ - return; } - config = igmp_config_lookup (im, ntohl (mp->sw_if_index)); - if (config) + else { - /* *INDENT-OFF* */ - pool_foreach (group, config->groups, ( + config = igmp_config_lookup (sw_if_index); + if (config) { - pool_foreach (src, group->srcs, ( - { - send_igmp_details (q, im, config, group, src, mp->context); - })); - })); - /* *INDENT-ON* */ + igmp_config_dump (im, q, mp->context, config); + } } } 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; + igmp_config_t *config; int rv = 0; - config = igmp_config_lookup (im, ntohl (mp->sw_if_index)); + config = igmp_config_lookup (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); + REPLY_MACRO (IGMP_MSG_ID (VL_API_IGMP_CLEAR_INTERFACE_REPLY)); +} + +static vl_api_group_prefix_type_t +igmp_group_type_int_to_api (igmp_group_prefix_type_t t) +{ + switch (t) + { + case IGMP_GROUP_PREFIX_TYPE_ASM: + return (htonl (ASM)); + case IGMP_GROUP_PREFIX_TYPE_SSM: + return (htonl (SSM)); + } + + return (SSM); +} + +static igmp_group_prefix_type_t +igmp_group_type_api_to_int (vl_api_group_prefix_type_t t) +{ + switch (htonl (t)) + { + case ASM: + return (IGMP_GROUP_PREFIX_TYPE_ASM); + case SSM: + return (IGMP_GROUP_PREFIX_TYPE_SSM); + } + + return (IGMP_GROUP_PREFIX_TYPE_SSM); +} + +static void +vl_api_igmp_group_prefix_set_t_handler (vl_api_igmp_group_prefix_set_t * mp) +{ + vl_api_igmp_group_prefix_set_reply_t *rmp; + fib_prefix_t pfx; + int rv = 0; + + ip_prefix_decode (&mp->gp.prefix, &pfx); + igmp_group_prefix_set (&pfx, igmp_group_type_api_to_int (mp->gp.type)); + + REPLY_MACRO (IGMP_MSG_ID (VL_API_IGMP_GROUP_PREFIX_SET_REPLY)); +} + +typedef struct igmp_ssm_range_walk_ctx_t_ +{ + unix_shared_memory_queue_t *q; + u32 context; +} igmp_ssm_range_walk_ctx_t; + +static walk_rc_t +igmp_ssm_range_walk_dump (const fib_prefix_t * pfx, + igmp_group_prefix_type_t type, void *args) +{ + igmp_ssm_range_walk_ctx_t *ctx = args; + vl_api_igmp_group_prefix_details_t *mp; + + mp = vl_msg_api_alloc (sizeof (*mp)); + memset (mp, 0, sizeof (*mp)); + + mp->_vl_msg_id = htons (IGMP_MSG_ID (VL_API_IGMP_DETAILS)); + mp->context = ctx->context; + mp->gp.type = igmp_group_type_int_to_api (type); + ip_prefix_encode (pfx, &mp->gp.prefix); + + vl_msg_api_send_shmem (ctx->q, (u8 *) & mp); + + return (WALK_CONTINUE); +} + +static void +vl_api_igmp_group_prefix_dump_t_handler (vl_api_igmp_dump_t * mp) +{ + unix_shared_memory_queue_t *q; + + 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); + igmp_ssm_range_walk_ctx_t ctx = { + .q = q, + .context = mp->context, + }; - vl_msg_api_send_shmem (q, (u8 *) & rmp); + igmp_ssm_range_walk (igmp_ssm_range_walk_dump, &ctx); } -/** \brief igmp group lookup - @param im - igmp main - @param client_index - client index -*/ static vpe_client_registration_t * igmp_api_client_lookup (igmp_main_t * im, u32 client_index) { @@ -247,17 +335,7 @@ vl_api_want_igmp_events_t_handler (vl_api_want_igmp_events_t * mp) 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); + REPLY_MACRO (VL_API_WANT_IGMP_EVENTS_REPLY + im->msg_id_base); } static clib_error_t * @@ -281,47 +359,51 @@ want_igmp_events_reaper (u32 client_index) VL_MSG_API_REAPER_FUNCTION (want_igmp_events_reaper); void -send_igmp_event (unix_shared_memory_queue_t * q, u32 context, - igmp_main_t * im, igmp_config_t * config, - igmp_group_t * group, igmp_src_t * src) +send_igmp_event (unix_shared_memory_queue_t * q, + u32 context, + igmp_filter_mode_t filter, + u32 sw_if_index, + const ip46_address_t * saddr, const ip46_address_t * gaddr) { 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->_vl_msg_id = ntohs ((VL_API_IGMP_EVENT) + igmp_main.msg_id_base); mp->context = context; - mp->sw_if_index = htonl (config->sw_if_index); - clib_memcpy (&mp->saddr, &src->addr.ip4, sizeof (ip4_address_t)); - clib_memcpy (&mp->gaddr, &group->addr.ip4, sizeof (ip4_address_t)); - mp->is_join = - (group->type == IGMP_MEMBERSHIP_GROUP_mode_is_filter_include) ? 1 : 0; + mp->sw_if_index = htonl (sw_if_index); + mp->filter = htonl (filter); + clib_memcpy (&mp->saddr, &saddr->ip4, sizeof (ip4_address_t)); + clib_memcpy (&mp->gaddr, &gaddr->ip4, sizeof (ip4_address_t)); vl_msg_api_send_shmem (q, (u8 *) & mp); } void -igmp_event (igmp_main_t * im, igmp_config_t * config, igmp_group_t * group, - igmp_src_t * src) +igmp_event (igmp_filter_mode_t filter, + u32 sw_if_index, + const ip46_address_t * saddr, const ip46_address_t * gaddr) { vpe_client_registration_t *api_client; unix_shared_memory_queue_t *q; + igmp_main_t *im; + + im = &igmp_main; + + IGMP_DBG ("event: (%U, %U) %U %U", + format_ip46_address, saddr, IP46_TYPE_ANY, + format_ip46_address, saddr, IP46_TYPE_ANY, + format_vnet_sw_if_index_name, + vnet_get_main (), sw_if_index, format_igmp_filter_mode, filter); + + /* *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, group, src); + send_igmp_event (q, 0, filter, sw_if_index, saddr, gaddr); })); /* *INDENT-ON* */ - if (group->type == IGMP_MEMBERSHIP_GROUP_block_old_sources) - { - igmp_clear_group (config, group); - if (pool_elts (config->groups) == 0) - { - hash_unset (im->igmp_config_by_sw_if_index, config->sw_if_index); - pool_put (im->configs, config); - } - } } #define vl_msg_name_crc_list diff --git a/src/plugins/igmp/igmp_api.h b/src/plugins/igmp/igmp_api.h new file mode 100644 index 00000000000..cfeab920c1d --- /dev/null +++ b/src/plugins/igmp/igmp_api.h @@ -0,0 +1,58 @@ +/* + *------------------------------------------------------------------ + * 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_API_H_ +#define _IGMP_API_H_ + +#include +#include + +/** + * @brief IGMP interface enable/disable + * Called by a router to enable/disable the reception of IGMP messages + * @param sw_if_index - Interface + * @param enable - enable/disable + * @param mode - Host (1) or router (0) + */ +int igmp_enable_disable (u32 sw_if_index, u8 enable, u8 mode); + +/** + * @brief igmp listen (RFC3376 Section 2). + * @param vm - vlib main + * @param enable - 0 == remove (S,G), else add (S,G), aka. include/exclue + * @param sw_if_index - interface sw_if_index + * @param saddr - source address + * @param gaddr - group address + * @param cli_api_configured - if zero, an igmp report has been received on interface + * + * Add/del (S,G) on an interface. If user configured, + * send a status change report from the interface. + * If a report was received on interface notify registered api clients. + */ +int igmp_listen (vlib_main_t * vm, u8 enable, u32 sw_if_index, + const ip46_address_t * saddr, const ip46_address_t * gaddr); + + +#endif /* _IGMP_API_H_ */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/igmp/cli.c b/src/plugins/igmp/igmp_cli.c similarity index 72% rename from src/plugins/igmp/cli.c rename to src/plugins/igmp/igmp_cli.c index 42f5932a7f2..5f09589c644 100644 --- a/src/plugins/igmp/cli.c +++ b/src/plugins/igmp/igmp_cli.c @@ -37,7 +37,6 @@ igmp_clear_interface_command_fn (vlib_main_t * vm, unformat_input_t * input, 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)) @@ -61,7 +60,7 @@ igmp_clear_interface_command_fn (vlib_main_t * vm, unformat_input_t * input, } } - config = igmp_config_lookup (im, sw_if_index); + config = igmp_config_lookup (sw_if_index); if (config) igmp_clear_config (config); @@ -128,8 +127,8 @@ igmp_listen_command_fn (vlib_main_t * vm, unformat_input_t * input, goto done; } - rv = igmp_listen (vm, enable, sw_if_index, saddr, gaddr, - IGMP_CONFIG_FLAG_CLI_API_CONFIGURED); + rv = igmp_listen (vm, enable, sw_if_index, &saddr, &gaddr); + if (rv == -1) { if (enable) @@ -170,18 +169,19 @@ igmp_show_command_fn (vlib_main_t * vm, unformat_input_t * input, igmp_src_t *src; /* *INDENT-OFF* */ - pool_foreach (config, im->configs, ( - { + pool_foreach (config, im->configs, + ({ vlib_cli_output (vm, "interface: %U", format_vnet_sw_if_index_name, - vnm, config->sw_if_index); - pool_foreach (group, config->groups, ( - { - vlib_cli_output (vm, "\t%U:%U", format_igmp_report_type, group->type, format_ip46_address, &group->addr, ip46_address_is_ip4 (&group->addr)); - pool_foreach (src, group->srcs, ( - { - vlib_cli_output (vm, "\t\t%U", format_ip46_address, &src->addr, ip46_address_is_ip4 (&src->addr)); - })); - })); + vnm, config->sw_if_index); + + FOR_EACH_GROUP (group, config, + ({ + vlib_cli_output (vm, "\t%U", format_igmp_key, group->key); + FOR_EACH_SRC (src, group, IGMP_FILTER_MODE_INCLUDE, + ({ + vlib_cli_output (vm, "\t\t%U", format_igmp_key, src->key); + })); + })); })); /* *INDENT-ON* */ @@ -196,6 +196,56 @@ VLIB_CLI_COMMAND (igmp_show_command, static) = { }; /* *INDENT-ON* */ +static clib_error_t * +igmp_show_timers_command_fn (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ +#define _(n,f) vlib_cli_output (vm, "%s: %d", #f, igmp_timer_type_get(n)); + foreach_igmp_timer_type +#undef _ + return (NULL); +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (igmp_show_timers_command, static) = { + .path = "show igmp timers", + .short_help = "show igmp timers", + .function = igmp_show_timers_command_fn, +}; +/* *INDENT-ON* */ + +static clib_error_t * +test_igmp_command_fn (vlib_main_t * vm, + unformat_input_t * input, vlib_cli_command_t * cmd) +{ + clib_error_t *error = NULL; + u32 value; + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (input, "query %d", &value)) + igmp_timer_type_set (IGMP_TIMER_QUERY, value); + else if (unformat (input, "src %d", &value)) + igmp_timer_type_set (IGMP_TIMER_SRC, value); + else if (unformat (input, "leave %d", &value)) + igmp_timer_type_set (IGMP_TIMER_LEAVE, value); + else + error = clib_error_return (0, "query or src timers only"); + } + + return error; +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (test_igmp_command, static) = { + .path = "test igmp timers", + .short_help = "Change the default values for IGMP timers - only sensible during unit tests", + .function = test_igmp_command_fn, +}; +/* *INDENT-ON* */ + + clib_error_t * igmp_cli_init (vlib_main_t * vm) { diff --git a/src/plugins/igmp/igmp_config.c b/src/plugins/igmp/igmp_config.c new file mode 100644 index 00000000000..76e8ace14d9 --- /dev/null +++ b/src/plugins/igmp/igmp_config.c @@ -0,0 +1,96 @@ +/* + *------------------------------------------------------------------ + * 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 + +void +igmp_clear_config (igmp_config_t * config) +{ + igmp_group_t *group; + u32 ii; + + IGMP_DBG ("clear-config: %U", + format_vnet_sw_if_index_name, + vnet_get_main (), config->sw_if_index); + + /* *INDENT-OFF* */ + FOR_EACH_GROUP (group, config, + ({ + igmp_group_clear (group); + })); + /* *INDENT-ON* */ + + for (ii = 0; ii < IGMP_CONFIG_N_TIMERS; ii++) + { + igmp_timer_retire (&config->timers[ii]); + } +} + +igmp_config_t * +igmp_config_lookup (u32 sw_if_index) +{ + igmp_main_t *im; + + im = &igmp_main; + + if (vec_len (im->igmp_config_by_sw_if_index) > sw_if_index) + { + u32 index; + + index = im->igmp_config_by_sw_if_index[sw_if_index]; + + if (~0 != index) + return (vec_elt_at_index (im->configs, index)); + } + return NULL; +} + +u32 +igmp_config_index (const igmp_config_t * c) +{ + return (c - igmp_main.configs); +} + +igmp_config_t * +igmp_config_get (u32 index) +{ + return (pool_elt_at_index (igmp_main.configs, index)); +} + +igmp_group_t * +igmp_group_lookup (igmp_config_t * config, const igmp_key_t * key) +{ + uword *p; + igmp_group_t *group = NULL; + if (!config) + return NULL; + + p = hash_get_mem (config->igmp_group_by_key, key); + if (p) + group = pool_elt_at_index (igmp_main.groups, p[0]); + + return group; +} + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/igmp/igmp_config.h b/src/plugins/igmp/igmp_config.h new file mode 100644 index 00000000000..ffd3dea9b28 --- /dev/null +++ b/src/plugins/igmp/igmp_config.h @@ -0,0 +1,128 @@ +/* + *------------------------------------------------------------------ + * 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_CONFIG_H__ +#define __IGMP_CONFIG_H__ + +#include +#include +#include + +typedef enum igmp_config_timer_type_t_ +{ + /** + * On expiry send a general report + */ + IGMP_CONFIG_TIMER_GENERAL_REPORT, + + /** + * On expiry send a general query + */ + IGMP_CONFIG_TIMER_GENERAL_QUERY, +} igmp_config_timer_type_t; + +#define IGMP_CONFIG_N_TIMERS (IGMP_CONFIG_TIMER_GENERAL_QUERY + 1) + +/** + * @brief IGMP interface configuration +*/ +typedef struct igmp_config_t_ +{ + /** + * @param sw_if_index - interface sw_if_index + */ + u32 sw_if_index; + + /** + * @param adj_index - multicast adjacency index on the link + */ + adj_index_t adj_index; + + /** + * @param moe - host or router + */ + igmp_mode_t mode; + + /** + * Robustness variable (seciotn 5.1) + */ + u8 robustness_var; + + /** + * Database of groups joined on the link + */ + uword *igmp_group_by_key; + + /** + * A vector of scheduled query-respone timers + */ + igmp_timer_id_t timers[IGMP_CONFIG_N_TIMERS]; +} igmp_config_t; + +#define FOR_EACH_GROUP(_group, _config, _body) \ +do { \ + igmp_key_t *__key__; \ + u32 __gid__; \ + hash_foreach_mem(__key__, __gid__, _config->igmp_group_by_key, \ + ({ \ + _group = pool_elt_at_index(igmp_main.groups, __gid__); \ + do { _body; } while (0); \ + })); \ + } while (0); + +/** + * @brief igmp clear config + * @param config - igmp configuration + * + * Clear all (S,G)s on specified config and remove this config from pool. + */ +extern void igmp_clear_config (igmp_config_t * config); + +/** + * @brief igmp config lookup + * @param im - igmp main + * @param sw_if_index - interface sw_if_index + */ +extern igmp_config_t *igmp_config_lookup (u32 sw_if_index); + +/** + * Get the pool index for a config + */ +extern u32 igmp_config_index (const igmp_config_t * c); + +/** + * Get the config from the pool index + */ +extern igmp_config_t *igmp_config_get (u32 index); + +/** + * @brief igmp group lookup + * @param config - igmp configuration + * @param key - igmp key +*/ +extern igmp_group_t *igmp_group_lookup (igmp_config_t * config, + const igmp_key_t * key); + +#endif + +/* + * 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/igmp_error.h similarity index 95% rename from src/plugins/igmp/error.h rename to src/plugins/igmp/igmp_error.h index faabfc1b03c..fbd0dc434f0 100644 --- a/src/plugins/igmp/error.h +++ b/src/plugins/igmp/igmp_error.h @@ -24,7 +24,7 @@ _ (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") \ + _ (NOT_ENABLED, "IGMP not enabled on this interface") \ typedef enum { diff --git a/src/plugins/igmp/igmp_format.c b/src/plugins/igmp/igmp_format.c index fb4cc997c23..1ae04788a97 100644 --- a/src/plugins/igmp/igmp_format.c +++ b/src/plugins/igmp/igmp_format.c @@ -21,28 +21,44 @@ 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); + igmp_type_t type = va_arg (*args, int); - if (ti) - return format (s, "%s", ti->name); - else - return format (s, "unknown %d", type); + switch (type) + { +#define _(n,f) case IGMP_TYPE_##f: return (format (s, "%s", #f)); + foreach_igmp_type +#undef _ + } + return format (s, "unknown:%d", type); } u8 * -format_igmp_report_type (u8 * s, va_list * args) +format_igmp_membership_group_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); + igmp_membership_group_v3_type_t type = va_arg (*args, int); + + switch (type) + { +#define _(n,f) case IGMP_MEMBERSHIP_GROUP_##f: return (format (s, "%s", #f)); + foreach_igmp_membership_group_v3_type +#undef _ + } + return (format (s, "unknown:%d", type)); +} + +u8 * +format_igmp_filter_mode (u8 * s, va_list * args) +{ + igmp_filter_mode_t mode = va_arg (*args, igmp_filter_mode_t); + + switch (mode) + { +#define _(n,f) case IGMP_FILTER_MODE_##f: return (format (s, "%s", #f)); + foreach_igmp_filter_mode +#undef _ + } + return (format (s, "unknown:%d", mode)); - if (rti) - return format (s, "%s", rti->name); - else - return format (s, "unknown %d", report_type); } u8 * @@ -92,8 +108,8 @@ format_igmp_report_v3 (u8 * s, va_list * args) 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, + format_igmp_membership_group_type, group->type, + format_ip4_address, &group->group_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++) @@ -129,17 +145,18 @@ format_igmp_query_v3 (u8 * s, va_list * args) ip4_address_t tmp; tmp.as_u32 = 0; - if ((!ip4_address_compare (&igmp->dst, &tmp)) + if ((!ip4_address_compare (&igmp->group_address, &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); + format_ip4_address, &igmp->group_address); else { s = format (s, "%UGroup-and-Source-Specific Query: %U", - format_white_space, indent, format_ip4_address, &igmp->dst); + format_white_space, indent, format_ip4_address, + &igmp->group_address); indent += 2; for (i = 0; i < clib_net_to_host_u16 (igmp->n_src_addresses); i++) { @@ -150,6 +167,33 @@ format_igmp_query_v3 (u8 * s, va_list * args) return s; } +u8 * +format_igmp_src_addr_list (u8 * s, va_list * args) +{ + ip46_address_t *ss, *srcs; + + srcs = va_arg (*args, ip46_address_t *); + + s = format (s, "["); + vec_foreach (ss, srcs) + { + s = format (s, "%U ", format_ip46_address, ss, IP46_TYPE_ANY); + } + s = format (s, "]"); + + return (s); +} + +u8 * +format_igmp_key (u8 * s, va_list * args) +{ + const igmp_key_t *key = va_arg (*args, const igmp_key_t *); + + s = format (s, "%U", format_ip46_address, key, IP46_TYPE_ANY); + + 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 018bb63d582..92f58e5791a 100644 --- a/src/plugins/igmp/igmp_format.h +++ b/src/plugins/igmp/igmp_format.h @@ -18,15 +18,21 @@ #ifndef _IGMP_FORMAT_H_ #define _IGMP_FORMAT_H_ -u8 *format_igmp_type (u8 * s, va_list * args); +extern u8 *format_igmp_type (u8 * s, va_list * args); -u8 *format_igmp_report_type (u8 * s, va_list * args); +extern u8 *format_igmp_membership_group_type (u8 * s, va_list * args); -u8 *format_igmp_header (u8 * s, va_list * args); +extern u8 *format_igmp_header (u8 * s, va_list * args); -u8 *format_igmp_report_v3 (u8 * s, va_list * args); +extern u8 *format_igmp_report_v3 (u8 * s, va_list * args); -u8 *format_igmp_query_v3 (u8 * s, va_list * args); +extern u8 *format_igmp_query_v3 (u8 * s, va_list * args); + +extern u8 *format_igmp_filter_mode (u8 * s, va_list * args); + +extern u8 *format_igmp_src_addr_list (u8 * s, va_list * args); + +extern u8 *format_igmp_key (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 new file mode 100644 index 00000000000..fe023a46167 --- /dev/null +++ b/src/plugins/igmp/igmp_group.c @@ -0,0 +1,272 @@ +/* + *------------------------------------------------------------------ + * 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 + +void +igmp_group_free_all_srcs (igmp_group_t * group) +{ + igmp_src_t *src; + + /* *INDENT-OFF* */ + FOR_EACH_SRC (src, group, IGMP_FILTER_MODE_INCLUDE, + ({ + igmp_src_free(src); + })); + /* *INDENT-ON* */ + + hash_free (group->igmp_src_by_key[IGMP_FILTER_MODE_INCLUDE]); + hash_free (group->igmp_src_by_key[IGMP_FILTER_MODE_EXCLUDE]); +} + +void +igmp_group_src_remove (igmp_group_t * group, igmp_src_t * src) +{ + hash_unset_mem (group->igmp_src_by_key[IGMP_FILTER_MODE_INCLUDE], src->key); + hash_unset_mem (group->igmp_src_by_key[IGMP_FILTER_MODE_EXCLUDE], src->key); +} + +igmp_src_t * +igmp_group_src_update (igmp_group_t * group, + const igmp_key_t * skey, igmp_mode_t mode) +{ + igmp_src_t *src; + + src = igmp_src_lookup (group, skey); + + if (NULL == src) + { + src = igmp_src_alloc (igmp_group_index (group), skey, mode); + + hash_set_mem (group->igmp_src_by_key[IGMP_FILTER_MODE_INCLUDE], + src->key, igmp_src_index (src)); + } + else + { + igmp_src_refresh (src); + } + + return (src); +} + +void +igmp_group_clear (igmp_group_t * group) +{ + igmp_config_t *config; + u32 ii; + + ASSERT (group); + + config = igmp_config_get (group->config); + + IGMP_DBG ("clear-group: %U %U", + format_igmp_key, group->key, + format_vnet_sw_if_index_name, + vnet_get_main (), config->sw_if_index); + + igmp_group_free_all_srcs (group); + + for (ii = 0; ii < IGMP_GROUP_N_TIMERS; ii++) + { + igmp_timer_retire (&group->timers[ii]); + } + + hash_unset_mem (config->igmp_group_by_key, group->key); + clib_mem_free (group->key); + pool_put (igmp_main.groups, group); +} + +igmp_group_t * +igmp_group_alloc (igmp_config_t * config, + const igmp_key_t * gkey, igmp_filter_mode_t mode) +{ + igmp_main_t *im = &igmp_main; + igmp_group_t *group; + u32 ii; + + IGMP_DBG ("new-group: %U", format_igmp_key, gkey); + pool_get (im->groups, group); + memset (group, 0, sizeof (igmp_group_t)); + group->key = clib_mem_alloc (sizeof (igmp_key_t)); + clib_memcpy (group->key, gkey, sizeof (igmp_key_t)); + group->igmp_src_by_key[IGMP_FILTER_MODE_INCLUDE] = + hash_create_mem (0, sizeof (igmp_key_t), sizeof (uword)); + group->igmp_src_by_key[IGMP_FILTER_MODE_EXCLUDE] = + hash_create_mem (0, sizeof (igmp_key_t), sizeof (uword)); + group->router_filter_mode = mode; + group->config = igmp_config_index (config); + group->n_reports_sent = 0; + + for (ii = 0; ii < IGMP_GROUP_N_TIMERS; ii++) + group->timers[ii] = IGMP_TIMER_ID_INVALID; + + hash_set_mem (config->igmp_group_by_key, group->key, group - im->groups); + return (group); +} + +/** + * the set of present sources minus the new set + */ +ip46_address_t * +igmp_group_present_minus_new (igmp_group_t * group, + igmp_filter_mode_t mode, + const ip46_address_t * saddrs) +{ + const ip46_address_t *s1; + ip46_address_t *pmn; + igmp_src_t *src; + u32 found; + + pmn = NULL; + + /* *INDENT-OFF* */ + if (0 == vec_len(saddrs)) + { + FOR_EACH_SRC(src, group, mode, + ({ + vec_add1(pmn, *src->key); + })); + } + else + { + FOR_EACH_SRC(src, group, mode, + ({ + found = 0; + vec_foreach(s1, saddrs) + { + if (ip46_address_is_equal(s1, src->key)) + { + found = 1; + break; + } + } + + if (!found) + vec_add1(pmn, *src->key); + })); + } + /* *INDENT-ON* */ + + return (pmn); +} + +/** + * the set of new sources minus the present set + */ +ip46_address_t * +igmp_group_new_minus_present (igmp_group_t * group, + igmp_filter_mode_t mode, + const ip46_address_t * saddrs) +{ + const ip46_address_t *s1; + ip46_address_t *npm; + igmp_src_t *src; + u32 found; + + npm = NULL; + + /* *INDENT-OFF* */ + vec_foreach(s1, saddrs) + { + found = 0; + FOR_EACH_SRC(src, group, mode, + ({ + if (ip46_address_is_equal(s1, src->key)) + { + found = 1; + break; + } + })); + + if (!found) + vec_add1(npm, *s1); + } + /* *INDENT-ON* */ + + return (npm); +} + +ip46_address_t * +igmp_group_new_intersect_present (igmp_group_t * group, + igmp_filter_mode_t mode, + const ip46_address_t * saddrs) +{ + ip46_address_t *intersect; + const ip46_address_t *s1; + igmp_src_t *src; + + intersect = NULL; + + /* *INDENT-OFF* */ + FOR_EACH_SRC(src, group, mode, + ({ + vec_foreach(s1, saddrs) + { + if (s1->ip4.as_u32 == src->key->ip4.as_u32) + { + vec_add1(intersect, *s1); + break; + } + } + })); + /* *INDENT-ON* */ + + return (intersect); +} + +u32 +igmp_group_n_srcs (const igmp_group_t * group, igmp_filter_mode_t mode) +{ + return (hash_elts (group->igmp_src_by_key[mode])); +} + + +igmp_src_t * +igmp_src_lookup (igmp_group_t * group, const igmp_key_t * key) +{ + uword *p; + igmp_src_t *src = NULL; + if (!group) + return NULL; + + p = hash_get_mem (group->igmp_src_by_key[IGMP_FILTER_MODE_INCLUDE], key); + if (p) + src = vec_elt_at_index (igmp_main.srcs, p[0]); + + return src; +} + +u32 +igmp_group_index (const igmp_group_t * g) +{ + return (g - igmp_main.groups); +} + +igmp_group_t * +igmp_group_get (u32 index) +{ + return (pool_elt_at_index (igmp_main.groups, index)); +} + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/igmp/igmp_group.h b/src/plugins/igmp/igmp_group.h new file mode 100644 index 00000000000..dc0fc7a0f8a --- /dev/null +++ b/src/plugins/igmp/igmp_group.h @@ -0,0 +1,156 @@ +/* + *------------------------------------------------------------------ + * 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_GROUP_H__ +#define __IGMP_GROUP_H__ + +#include +#include + +/** + * Types of timers maintained for each group + */ +typedef enum igmp_group_timer_type_t_ +{ + /** + * Timer running to reply to a G/SG specific query + */ + IGMP_GROUP_TIMER_QUERY_REPLY, + /** + * wait for response from a sent G/SG specfic query. + * Sent when a host leaves a group + */ + IGMP_GROUP_TIMER_QUERY_SENT, + /** + * Timer running to resend report + */ + IGMP_GROUP_TIMER_RESEND_REPORT, + /** + * filter-mode change timer, to check if the group can swap to + * INCLUDE mode (section 6.2.2) + */ + IGMP_GROUP_TIMER_FILTER_MODE_CHANGE, +} igmp_group_timer_type_t; + +#define IGMP_GROUP_N_TIMERS (IGMP_GROUP_TIMER_FILTER_MODE_CHANGE + 1) + +/** + * @brief IGMP group + * A multicast group address for which reception has been requested. + */ +typedef struct igmp_group_t_ +{ + /** The group's key within the per-interface config */ + igmp_key_t *key; + + /** + * A vector of running timers for the group. this can include: + * - group-specific query, sent on reception of a host 'leave' + * - filter-mode change timer, to check if the group can swap to + * INCLUDE mode (section 6.2.2) + */ + u32 timers[IGMP_GROUP_N_TIMERS]; + + /** + * The current filter mode of the group (see 6.2.1) + */ + igmp_filter_mode_t router_filter_mode; + + /** + * The pool index of the config object this group is in + */ + u32 config; + + /** + * The number of times the last report has been sent + */ + u32 n_reports_sent; + + /** + * Source list per-filter mode + */ + uword *igmp_src_by_key[IGMP_N_FILTER_MODES]; +} igmp_group_t; + +#define FOR_EACH_SRC(_src, _group, _filter, _body) \ +do { \ + igmp_key_t *__key__; \ + u32 __sid__; \ + hash_foreach_mem(__key__, __sid__, ((igmp_group_t*)_group)->igmp_src_by_key[(_filter)], \ + ({ \ + _src = pool_elt_at_index(igmp_main.srcs, __sid__); \ + do { _body; } while (0); \ + })); \ + } while (0); + +/** + * Forward declarations + */ +struct igmp_config_t_; + +extern void igmp_group_clear (igmp_group_t * group); +extern void igmp_group_free_all_srcs (igmp_group_t * group); + +extern igmp_group_t *igmp_group_alloc (struct igmp_config_t_ *config, + const igmp_key_t * gkey, + igmp_filter_mode_t mode); + +extern igmp_src_t *igmp_group_src_update (igmp_group_t * group, + const igmp_key_t * skey, + igmp_mode_t mode); + +extern void igmp_group_src_remove (igmp_group_t * group, igmp_src_t * src); + +extern ip46_address_t *igmp_group_present_minus_new (igmp_group_t * group, + igmp_filter_mode_t mode, + const ip46_address_t * + saddrs); + +extern ip46_address_t *igmp_group_new_minus_present (igmp_group_t * group, + igmp_filter_mode_t mode, + const ip46_address_t * + saddrs); + +extern ip46_address_t *igmp_group_new_intersect_present (igmp_group_t * group, + igmp_filter_mode_t + mode, + const ip46_address_t + * saddrs); + +extern u32 igmp_group_n_srcs (const igmp_group_t * group, + igmp_filter_mode_t mode); + + +/** \brief igmp group lookup + @param group - igmp group + @param key - igmp key +*/ +extern igmp_src_t *igmp_src_lookup (igmp_group_t * group, + const igmp_key_t * key); + +extern u32 igmp_group_index (const igmp_group_t * g); +extern igmp_group_t *igmp_group_get (u32 index); + +#endif + +/* + * 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/igmp_input.c similarity index 59% rename from src/plugins/igmp/input.c rename to src/plugins/igmp/igmp_input.c index 829317d4543..d4563bf366d 100644 --- a/src/plugins/igmp/input.c +++ b/src/plugins/igmp/igmp_input.c @@ -16,6 +16,7 @@ */ #include +#include #include #include #include @@ -23,18 +24,13 @@ #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, @@ -76,11 +72,10 @@ format_igmp_input_trace (u8 * s, va_list * va) 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)); + 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; } @@ -91,11 +86,10 @@ format_igmp_parse_report_trace (u8 * s, va_list * va) 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)); + 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; } @@ -106,28 +100,24 @@ format_igmp_parse_query_trace (u8 * s, va_list * va) 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)); + 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 +static uword igmp_input (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * frame) { - IGMP_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); + u32 n_left_from, *from, *to_next; + vlib_node_runtime_t *error_node; u8 error; - ip_csum_t sum; - u16 csum; error = IGMP_ERROR_NONE; + error_node = node; from = vlib_frame_vector_args (frame); n_left_from = frame->n_vectors; @@ -141,11 +131,14 @@ igmp_input (vlib_main_t * vm, vlib_node_runtime_t * node, while (n_left_from > 0 && n_left_to_next > 0) { + igmp_header_t *igmp; + u16 checksum, csum; vlib_buffer_t *b; ip4_header_t *ip; + ip_csum_t sum; u32 bi, next; - next = IGMP_INPUT_NEXT_DROP; + next = IGMP_INPUT_NEXT_DROP; bi = from[0]; to_next[0] = bi; from++; @@ -156,7 +149,7 @@ igmp_input (vlib_main_t * vm, vlib_node_runtime_t * node, b = vlib_get_buffer (vm, bi); ip = vlib_buffer_get_current (b); - if (ip->protocol != 2) + if (ip->protocol != IP_PROTOCOL_IGMP) { error = IGMP_ERROR_INVALID_PROTOCOL; next = IGMP_INPUT_NEXT_DROP; @@ -165,9 +158,9 @@ igmp_input (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_buffer_advance (b, ip4_header_bytes (ip)); - igmp_header_t *igmp = vlib_buffer_get_current (b); + igmp = vlib_buffer_get_current (b); - u16 checksum = igmp->checksum; + checksum = igmp->checksum; igmp->checksum = 0; sum = ip_incremental_checksum (0, igmp, clib_net_to_host_u16 (ip->length) - @@ -180,6 +173,12 @@ igmp_input (vlib_main_t * vm, vlib_node_runtime_t * node, next = IGMP_INPUT_NEXT_DROP; goto next_buffer; } + if (!igmp_config_lookup (vnet_buffer (b)->sw_if_index[VLIB_RX])) + { + error = IGMP_ERROR_NOT_ENABLED; + next = IGMP_INPUT_NEXT_DROP; + goto next_buffer; + } /* TODO: IGMPv2 and IGMPv1 */ switch (igmp->type) @@ -239,16 +238,12 @@ VLIB_REGISTER_NODE (igmp_input_node) = }; /* *INDENT-ON* */ -uword +static uword igmp_parse_query (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * frame) { - IGMP_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; @@ -262,10 +257,12 @@ igmp_parse_query (vlib_main_t * vm, vlib_node_runtime_t * node, while (n_left_from > 0 && n_left_to_next > 0) { + igmp_membership_query_v3_t *igmp; + igmp_query_args_t *args; vlib_buffer_t *b; - u32 sw_if_index, bi, next; - next = IGMP_PARSE_QUERY_NEXT_DROP; + u32 bi, next; + next = IGMP_PARSE_QUERY_NEXT_DROP; bi = from[0]; to_next[0] = bi; from++; @@ -274,38 +271,9 @@ igmp_parse_query (vlib_main_t * vm, vlib_node_runtime_t * node, 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); + 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) - { - IGMP_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; @@ -315,6 +283,21 @@ igmp_parse_query (vlib_main_t * vm, vlib_node_runtime_t * node, clib_memcpy (tr->packet_data, vlib_buffer_get_current (b), sizeof (tr->packet_data)); } + + /* + * copy the contents of the query, and the interface, over + * to the main thread for processing + */ + vlib_buffer_advance (b, -sizeof (u32)); + args = vlib_buffer_get_current (b); + args->sw_if_index = vnet_buffer (b)->sw_if_index[VLIB_RX]; + + vl_api_rpc_call_main_thread (igmp_handle_query, + (u8 *) args, + sizeof (*args) + + igmp_membership_query_v3_length + (igmp)); + vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next, n_left_to_next, bi, next); } @@ -344,29 +327,12 @@ VLIB_REGISTER_NODE (igmp_parse_query_node) = }; /* *INDENT-ON* */ -uword +static uword igmp_parse_report (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * frame) { - IGMP_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_group_t *group; - igmp_src_t *src; - igmp_membership_group_v3_t *igmp_group; - ip4_address_t *src_addr; - igmp_key_t gkey; - igmp_key_t skey; - memset (&gkey, 0, sizeof (igmp_key_t)); - memset (&skey, 0, sizeof (igmp_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; @@ -383,8 +349,11 @@ igmp_parse_report (vlib_main_t * vm, vlib_node_runtime_t * node, while (n_left_from > 0 && n_left_to_next > 0) { + igmp_membership_report_v3_t *igmp; + igmp_report_args_t *args; + u32 bi, next; vlib_buffer_t *b; - u32 sw_if_index, bi, next; + next = IGMP_PARSE_REPORT_NEXT_DROP; bi = from[0]; @@ -398,139 +367,10 @@ igmp_parse_report (vlib_main_t * vm, vlib_node_runtime_t * node, error = IGMP_ERROR_NONE; b->error = error_node->errors[error]; + igmp = vlib_buffer_get_current (b); - 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->flags & IGMP_CONFIG_FLAG_CLI_API_CONFIGURED) - { - IGMP_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; - } - } - IGMP_DBG ("interface %u", sw_if_index); - int i, j = 0; - for (i = 0; i < clib_net_to_host_u16 (igmp->n_groups); i++) - { - igmp_group = group_ptr (igmp, len); - src_addr = igmp_group->src_addresses; - if (igmp_group->type == - IGMP_MEMBERSHIP_GROUP_mode_is_filter_include) - { - ip46_address_set_ip4 ((ip46_address_t *) & gkey.data, - &igmp_group->dst_address); - - gkey.group_type = - IGMP_MEMBERSHIP_GROUP_mode_is_filter_include; - - group = igmp_group_lookup (config, &gkey); - if (group) - { - for (j = 0; - j < - clib_net_to_host_u16 (igmp_group->n_src_addresses); - j++) - { - /* update (S,G) expiration timer */ - ip46_address_set_ip4 ((ip46_address_t *) & - skey.data, src_addr); - src = igmp_src_lookup (group, &skey); - if (src) - src->exp_time = - vlib_time_now (vm) + IGMP_SRC_TIMER; - src_addr++; - } - } - else - { - j = clib_net_to_host_u16 (igmp_group->n_src_addresses); - } - } - else if (igmp_group->type == - IGMP_MEMBERSHIP_GROUP_mode_is_filter_exclude) - { - for (j = 0; - j < clib_net_to_host_u16 (igmp_group->n_src_addresses); - j++) - { - /* nothing for now... */ - src_addr++; - } - } - else if (igmp_group->type == - IGMP_MEMBERSHIP_GROUP_change_to_filter_include) - { - for (j = 0; - j < clib_net_to_host_u16 (igmp_group->n_src_addresses); - j++) - { - /* add new (S,G) to interface */ - saddr.ip4 = *src_addr; - gaddr.ip4 = igmp_group->dst_address; - igmp_listen (vm, 1, sw_if_index, saddr, gaddr, 0); - src_addr++; - } - } - else if (igmp_group->type == - IGMP_MEMBERSHIP_GROUP_change_to_filter_exclude) - { - for (j = 0; - j < clib_net_to_host_u16 (igmp_group->n_src_addresses); - j++) - { - /* remove (S,G) from interface */ - saddr.ip4 = *src_addr; - gaddr.ip4 = igmp_group->dst_address; - igmp_listen (vm, 0, sw_if_index, saddr, gaddr, 0); - src_addr++; - } - } - else if (igmp_group->type == - IGMP_MEMBERSHIP_GROUP_allow_new_sources) - { - for (j = 0; - j < clib_net_to_host_u16 (igmp_group->n_src_addresses); - j++) - { - /* nothing for now... */ - src_addr++; - } - } - else if (igmp_group->type == - IGMP_MEMBERSHIP_GROUP_block_old_sources) - { - for (j = 0; - j < clib_net_to_host_u16 (igmp_group->n_src_addresses); - j++) - { - /* remove (S,G) from interface */ - saddr.ip4 = *src_addr; - gaddr.ip4 = igmp_group->dst_address; - igmp_listen (vm, 0, sw_if_index, saddr, gaddr, 0); - src_addr++; - } - } - /* - * 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; @@ -540,6 +380,21 @@ igmp_parse_report (vlib_main_t * vm, vlib_node_runtime_t * node, clib_memcpy (tr->packet_data, vlib_buffer_get_current (b), sizeof (tr->packet_data)); } + + /* + * copy the contents of the query, and the interface, over + * to the main thread for processing + */ + vlib_buffer_advance (b, -sizeof (u32)); + args = vlib_buffer_get_current (b); + args->sw_if_index = vnet_buffer (b)->sw_if_index[VLIB_RX]; + + vl_api_rpc_call_main_thread (igmp_handle_report, + (u8 *) args, + sizeof (*args) + + igmp_membership_report_v3_length + (igmp)); + vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next, n_left_to_next, bi, next); } @@ -569,6 +424,23 @@ VLIB_REGISTER_NODE (igmp_parse_report_node) = }; /* *INDENT-ON* */ +static clib_error_t * +igmp_input_init (vlib_main_t * vm) +{ + clib_error_t *error; + + if ((error = vlib_call_init_function (vm, igmp_init))) + return error; + + ip4_register_protocol (IP_PROTOCOL_IGMP, igmp_input_node.index); + + IGMP_DBG ("input-initialized"); + + return (error); +} + +VLIB_INIT_FUNCTION (igmp_input_init); + /* * fd.io coding-style-patch-verification: ON * diff --git a/src/plugins/igmp/igmp_pkt.c b/src/plugins/igmp/igmp_pkt.c new file mode 100644 index 00000000000..5dd829de712 --- /dev/null +++ b/src/plugins/igmp/igmp_pkt.c @@ -0,0 +1,534 @@ +/* + *------------------------------------------------------------------ + * 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 + +static void +vlib_buffer_append (vlib_buffer_t * b, uword l) +{ + b->current_data += l; + b->current_length += l; +} + +static vlib_buffer_t * +igmp_pkt_get_buffer (igmp_pkt_build_t * bk) +{ + vlib_buffer_free_list_t *fl; + vlib_main_t *vm; + vlib_buffer_t *b; + u32 bi; + + vm = vlib_get_main (); + + if (vlib_buffer_alloc (vm, &bi, 1) != 1) + return (NULL); + + b = vlib_get_buffer (vm, bi); + fl = vlib_buffer_get_free_list (vm, VLIB_BUFFER_DEFAULT_FREE_LIST_INDEX); + vlib_buffer_init_for_free_list (b, fl); + VLIB_BUFFER_TRACE_TRAJECTORY_INIT (b); + + b->flags |= VNET_BUFFER_F_LOCALLY_ORIGINATED; + b->flags |= VLIB_BUFFER_IS_TRACED; + + /* clear out stale data */ + vnet_buffer (b)->sw_if_index[VLIB_RX] = ~0; + + /* + * save progress in the builder + */ + vec_add1 (bk->buffers, bi); + bk->n_avail = vnet_sw_interface_get_mtu (vnet_get_main (), + bk->sw_if_index, VNET_MTU_IP4); + + return (b); +} + +static vlib_buffer_t * +igmp_pkt_build_ip_header (igmp_pkt_build_t * bk, + igmp_msg_type_t msg_type, + const igmp_group_t * group) +{ + ip4_header_t *ip4; + vlib_buffer_t *b; + u8 *option; + + b = igmp_pkt_get_buffer (bk); + + if (NULL == b) + return (NULL); + + ip4 = vlib_buffer_get_current (b); + memset (ip4, 0, sizeof (ip4_header_t)); + ip4->ip_version_and_header_length = 0x46; + ip4->ttl = 1; + ip4->protocol = IP_PROTOCOL_IGMP; + ip4->tos = 0xc0; + + ip4_src_address_for_packet (&ip4_main.lookup_main, + bk->sw_if_index, &ip4->src_address); + + vlib_buffer_append (b, sizeof (*ip4)); + bk->n_avail -= sizeof (*ip4); + + switch (msg_type) + { + case IGMP_MSG_REPORT: + ip4->dst_address.as_u32 = IGMP_MEMBERSHIP_REPORT_ADDRESS; + break; + case IGMP_MSG_QUERY: + if (group != NULL) + clib_memcpy (&ip4->dst_address, &group->key->ip4, + sizeof (ip4_address_t)); + else + ip4->dst_address.as_u32 = IGMP_GENERAL_QUERY_ADDRESS; + break; + } + + /* add the router alert optnios */ + option = vlib_buffer_get_current (b); + option[0] = 0x80 | 20; // IP4_ROUTER_ALERT_OPTION; + option[1] = 4; // length + option[2] = option[3] = 0; + + vlib_buffer_append (b, 4); + bk->n_avail -= 4; + + return (b); +} + +static vlib_buffer_t * +igmp_pkt_build_report_v3 (igmp_pkt_build_report_t * br, + const igmp_group_t * group) +{ + igmp_membership_report_v3_t *report; + vlib_buffer_t *b; + + b = igmp_pkt_build_ip_header (&br->base, IGMP_MSG_REPORT, group); + + if (NULL == b) + return (NULL); + + report = vlib_buffer_get_current (b); + report->header.type = IGMP_TYPE_membership_report_v3; + report->header.code = 0; + report->header.checksum = 0; + report->unused = 0; + + vlib_buffer_append (b, sizeof (igmp_membership_report_v3_t)); + br->base.n_avail -= sizeof (igmp_membership_report_v3_t); + br->base.n_bytes += sizeof (igmp_membership_report_v3_t); + + return (b); +} + +static void +igmp_pkt_tx (igmp_pkt_build_t * bk) +{ + const igmp_config_t *config; + vlib_buffer_t *b; + vlib_main_t *vm; + vlib_frame_t *f; + u32 *to_next; + u32 ii; + + vm = vlib_get_main (); + config = igmp_config_lookup (bk->sw_if_index); + f = vlib_get_frame_to_node (vm, ip4_rewrite_mcast_node.index); + to_next = vlib_frame_vector_args (f); + + vec_foreach_index (ii, bk->buffers) + { + b = vlib_get_buffer (vm, bk->buffers[ii]); + vnet_buffer (b)->ip.adj_index[VLIB_TX] = config->adj_index; + to_next[ii] = bk->buffers[ii]; + f->n_vectors++; + } + + vlib_put_frame_to_node (vm, ip4_rewrite_mcast_node.index, f); + + IGMP_DBG (" ..tx: %U", format_vnet_sw_if_index_name, + vnet_get_main (), bk->sw_if_index); + + vec_free (bk->buffers); + bk->buffers = 0; +} + +static vlib_buffer_t * +igmp_pkt_build_report_get_active (igmp_pkt_build_report_t * br) +{ + if (NULL == br->base.buffers) + return (NULL); + + return (vlib_get_buffer (vlib_get_main (), + br->base.buffers[vec_len (br->base.buffers) - 1])); +} + +static void +igmp_pkt_build_report_bake (igmp_pkt_build_report_t * br) +{ + igmp_membership_report_v3_t *igmp; + ip4_header_t *ip4; + vlib_buffer_t *b; + + b = igmp_pkt_build_report_get_active (br); + + b->current_data = 0; + + ip4 = vlib_buffer_get_current (b); + igmp = (igmp_membership_report_v3_t *) (((u32 *) ip4) + 6); + + igmp->n_groups = clib_host_to_net_u16 (br->n_groups); + + igmp->header.checksum = + ~ip_csum_fold (ip_incremental_checksum (0, igmp, br->base.n_bytes)); + + ip4->length = clib_host_to_net_u16 (b->current_length); + ip4->checksum = ip4_header_checksum (ip4); + + br->base.n_bytes = br->base.n_avail = br->n_groups = 0; +} + +void +igmp_pkt_report_v3_send (igmp_pkt_build_report_t * br) +{ + if (NULL == br->base.buffers) + return; + + igmp_pkt_build_report_bake (br); + igmp_pkt_tx (&br->base); +} + +static u32 +igmp_pkt_report_v3_get_size (const igmp_group_t * group) +{ + ASSERT (IGMP_FILTER_MODE_INCLUDE == group->router_filter_mode); + + return ((hash_elts (group->igmp_src_by_key[IGMP_FILTER_MODE_INCLUDE]) * + sizeof (ip4_address_t)) + sizeof (igmp_membership_group_v3_t)); +} + +static igmp_membership_group_v3_t * +igmp_pkt_report_v3_append_group (igmp_pkt_build_report_t * br, + const ip46_address_t * grp, + igmp_membership_group_v3_type_t type) +{ + igmp_membership_group_v3_t *igmp_group; + vlib_buffer_t *b; + + b = igmp_pkt_build_report_get_active (br); + + if (br->base.n_avail < sizeof (igmp_membership_group_v3_t)) + { + igmp_pkt_build_report_bake (br); + b = igmp_pkt_build_report_v3 (br, NULL); + if (NULL == b) + return (NULL); + } + br->base.n_avail -= sizeof (igmp_membership_group_v3_t); + br->base.n_bytes += sizeof (igmp_membership_group_v3_t); + br->n_groups++; + br->n_srcs = 0; + + igmp_group = vlib_buffer_get_current (b); + vlib_buffer_append (b, sizeof (igmp_membership_group_v3_t)); + + igmp_group->type = type; + igmp_group->n_aux_u32s = 0; + igmp_group->n_src_addresses = 0; + igmp_group->group_address.as_u32 = grp->ip4.as_u32; + + return (igmp_group); +} + +/** + * 4.2.16 + " If the set of Group Records required in a Report does not fit within + * the size limit of a single Report message (as determined by the MTU + * of the network on which it will be sent), the Group Records are sent + * in as many Report messages as needed to report the entire set. + + * If a single Group Record contains so many source addresses that it + * does not fit within the size limit of a single Report message, if its + * Type is not MODE_IS_EXCLUDE or CHANGE_TO_EXCLUDE_MODE, it is split + * into multiple Group Records, each containing a different subset of + * the source addresses and each sent in a separate Report message. If + * its Type is MODE_IS_EXCLUDE or CHANGE_TO_EXCLUDE_MODE, a single Group + * Record is sent, containing as many source addresses as can fit, and + * the remaining source addresses are not reported; though the choice of + * which sources to report is arbitrary, it is preferable to report the + * same set of sources in each subsequent report, rather than reporting + * different sources each time." + */ +static igmp_membership_group_v3_t * +igmp_pkt_report_v3_append_src (igmp_pkt_build_report_t * br, + igmp_membership_group_v3_t * igmp_group, + const ip46_address_t * grp, + igmp_membership_group_v3_type_t type, + const ip46_address_t * src) +{ + vlib_buffer_t *b; + + b = igmp_pkt_build_report_get_active (br); + + if (br->base.n_avail < sizeof (ip4_address_t)) + { + igmp_group->n_src_addresses = clib_host_to_net_u16 (br->n_srcs); + igmp_pkt_build_report_bake (br); + b = igmp_pkt_build_report_v3 (br, NULL); + if (NULL == b) + return (NULL); + igmp_group = igmp_pkt_report_v3_append_group (br, grp, type); + } + + igmp_group->src_addresses[br->n_srcs].as_u32 = src->ip4.as_u32; + br->n_srcs++; + br->base.n_avail -= sizeof (ip4_address_t); + br->base.n_bytes += sizeof (ip4_address_t); + vlib_buffer_append (b, sizeof (ip4_address_t)); + + return (igmp_group); +} + +void +igmp_pkt_report_v3_add_report (igmp_pkt_build_report_t * br, + const ip46_address_t * grp, + const ip46_address_t * srcs, + igmp_membership_group_v3_type_t type) +{ + igmp_membership_group_v3_t *igmp_group; + const ip46_address_t *s; + vlib_buffer_t *b; + + b = igmp_pkt_build_report_get_active (br); + + if (NULL == b) + { + b = igmp_pkt_build_report_v3 (br, NULL); + if (NULL == b) + /* failed to allocate buffer */ + return; + } + + igmp_group = igmp_pkt_report_v3_append_group (br, grp, type); + + if (NULL == igmp_group) + return; + + /* *INDENT-OFF* */ + vec_foreach(s, srcs) + { + igmp_group = igmp_pkt_report_v3_append_src(br, igmp_group, + grp, type, s); + if (NULL == igmp_group) + return; + }; + /* *INDENT-ON* */ + + igmp_group->n_src_addresses = clib_host_to_net_u16 (br->n_srcs); + + IGMP_DBG (" ..add-group: %U", format_ip46_address, grp, IP46_TYPE_IP4); +} + +void +igmp_pkt_report_v3_add_group (igmp_pkt_build_report_t * br, + const igmp_group_t * group, + igmp_membership_group_v3_type_t type) +{ + igmp_membership_group_v3_t *igmp_group; + vlib_buffer_t *b; + igmp_src_t *src; + + b = igmp_pkt_build_report_get_active (br); + + if (NULL == b) + { + b = igmp_pkt_build_report_v3 (br, NULL); + if (NULL == b) + /* failed to allocate buffer */ + return; + } + + /* + * if the group won't fit in a partially full buffer, start again + */ + if ((0 != br->n_groups) && + (igmp_pkt_report_v3_get_size (group) > br->base.n_avail)) + { + igmp_pkt_build_report_bake (br); + b = igmp_pkt_build_report_v3 (br, NULL); + if (NULL == b) + /* failed to allocate buffer */ + return; + } + + igmp_group = igmp_pkt_report_v3_append_group (br, group->key, type); + + /* *INDENT-OFF* */ + FOR_EACH_SRC (src, group, IGMP_FILTER_MODE_INCLUDE, + ({ + igmp_group = igmp_pkt_report_v3_append_src(br, igmp_group, + group->key, type, + src->key); + if (NULL == igmp_group) + return; + })); + /* *INDENT-ON* */ + igmp_group->n_src_addresses = clib_host_to_net_u16 (br->n_srcs); + + IGMP_DBG (" ..add-group: %U srcs:%d", + format_igmp_key, group->key, + hash_elts (group->igmp_src_by_key[IGMP_FILTER_MODE_INCLUDE])); +} + +void +igmp_pkt_build_report_init (igmp_pkt_build_report_t * br, u32 sw_if_index) +{ + memset (br, 0, sizeof (*br)); + br->base.sw_if_index = sw_if_index; +} + +static vlib_buffer_t * +igmp_pkt_build_query_get_active (igmp_pkt_build_query_t * bq) +{ + if (NULL == bq->base.buffers) + return (NULL); + + return (vlib_get_buffer (vlib_get_main (), + bq->base.buffers[vec_len (bq->base.buffers) - 1])); +} + +static vlib_buffer_t * +igmp_pkt_build_query_v3 (igmp_pkt_build_query_t * bq, + const igmp_group_t * group) +{ + igmp_membership_query_v3_t *query; + vlib_buffer_t *b; + + b = igmp_pkt_build_ip_header (&bq->base, IGMP_MSG_QUERY, group); + + if (NULL == b) + return (NULL); + + query = vlib_buffer_get_current (b); + query->header.type = IGMP_TYPE_membership_query; + query->header.code = 0; + query->header.checksum = 0; + query->qqi_code = 0; + query->resv_s_qrv = 0; + + if (NULL != group) + query->group_address.as_u32 = group->key->ip4.as_u32; + else + query->group_address.as_u32 = 0; + + vlib_buffer_append (b, sizeof (igmp_membership_query_v3_t)); + bq->base.n_avail -= sizeof (igmp_membership_query_v3_t); + bq->base.n_bytes += sizeof (igmp_membership_query_v3_t); + + return (b); +} + +void +igmp_pkt_query_v3_add_group (igmp_pkt_build_query_t * bq, + const igmp_group_t * group, + const ip46_address_t * srcs) +{ + vlib_buffer_t *b; + + b = igmp_pkt_build_query_get_active (bq); + + if (NULL == b) + { + b = igmp_pkt_build_query_v3 (bq, group); + if (NULL == b) + /* failed to allocate buffer */ + return; + } + + if (NULL != srcs) + { + igmp_membership_query_v3_t *query; + const ip46_address_t *src; + + query = vlib_buffer_get_current (b); + + vec_foreach (src, srcs) + { + query->src_addresses[bq->n_srcs++].as_u32 = src->ip4.as_u32; + + vlib_buffer_append (b, sizeof (ip4_address_t)); + bq->base.n_bytes += sizeof (ip4_address_t); + bq->base.n_avail += sizeof (ip4_address_t); + } + } + /* + * else + * general query and we're done + */ +} + +static void +igmp_pkt_build_query_bake (igmp_pkt_build_query_t * bq) +{ + igmp_membership_query_v3_t *igmp; + ip4_header_t *ip4; + vlib_buffer_t *b; + + b = igmp_pkt_build_query_get_active (bq); + + b->current_data = 0; + + ip4 = vlib_buffer_get_current (b); + // account for options + igmp = (igmp_membership_query_v3_t *) (((u32 *) ip4) + 6); + + igmp->n_src_addresses = clib_host_to_net_u16 (bq->n_srcs); + + igmp->header.checksum = + ~ip_csum_fold (ip_incremental_checksum (0, igmp, bq->base.n_bytes)); + + ip4->length = clib_host_to_net_u16 (b->current_length); + ip4->checksum = ip4_header_checksum (ip4); + + bq->base.n_bytes = bq->base.n_avail = bq->n_srcs = 0; +} + +void +igmp_pkt_query_v3_send (igmp_pkt_build_query_t * bq) +{ + if (NULL == bq->base.buffers) + return; + + igmp_pkt_build_query_bake (bq); + igmp_pkt_tx (&bq->base); +} + +void +igmp_pkt_build_query_init (igmp_pkt_build_query_t * bq, u32 sw_if_index) +{ + memset (bq, 0, sizeof (*bq)); + bq->base.sw_if_index = sw_if_index; +} + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/igmp/igmp_pkt.h b/src/plugins/igmp/igmp_pkt.h new file mode 100644 index 00000000000..66fc5a7616b --- /dev/null +++ b/src/plugins/igmp/igmp_pkt.h @@ -0,0 +1,78 @@ +/* + *------------------------------------------------------------------ + * 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_PKT_H__ +#define __IGMP_PKT_H__ + +#include + +typedef struct igmp_pkt_build_t_ +{ + u32 *buffers; + u32 sw_if_index; + u32 n_avail; + u32 n_bytes; +} igmp_pkt_build_t; + +typedef struct igmp_pkt_build_report_t_ +{ + igmp_pkt_build_t base; + u32 n_groups; + u16 n_srcs; +} igmp_pkt_build_report_t; + +extern void igmp_pkt_build_report_init (igmp_pkt_build_report_t * br, + u32 sw_if_index); + +extern void igmp_pkt_report_v3_add_report (igmp_pkt_build_report_t * br, + const ip46_address_t * grp, + const ip46_address_t * srcs, + igmp_membership_group_v3_type_t + type); + +extern void igmp_pkt_report_v3_add_group (igmp_pkt_build_report_t * br, + const igmp_group_t * group, + igmp_membership_group_v3_type_t + type); + +extern void igmp_pkt_report_v3_send (igmp_pkt_build_report_t * br); + + +typedef struct igmp_pkt_build_query_t_ +{ + igmp_pkt_build_t base; + u32 n_srcs; +} igmp_pkt_build_query_t; + +extern void igmp_pkt_build_query_init (igmp_pkt_build_query_t * bq, + u32 sw_if_index); + +extern void igmp_pkt_query_v3_add_group (igmp_pkt_build_query_t * bq, + const igmp_group_t * group, + const ip46_address_t * srcs); + +extern void igmp_pkt_query_v3_send (igmp_pkt_build_query_t * bq); + +#endif + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/igmp/igmp_query.c b/src/plugins/igmp/igmp_query.c new file mode 100644 index 00000000000..1513023df2e --- /dev/null +++ b/src/plugins/igmp/igmp_query.c @@ -0,0 +1,307 @@ +/* + *------------------------------------------------------------------ + * 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 + +static f64 +igmp_get_random_resp_delay (const igmp_header_t * header) +{ + u32 seed; + + seed = vlib_time_now (vlib_get_main ()); + + return ((random_f64 (&seed) * igmp_header_get_max_resp_time (header))); + +} + +static ip46_address_t * +igmp_query_mk_source_list (const igmp_membership_query_v3_t * q) +{ + ip46_address_t *srcs = NULL; + const ip4_address_t *s; + u16 ii, n; + + n = clib_net_to_host_u16 (q->n_src_addresses); + + if (0 == n) + return (NULL); + + vec_validate (srcs, n - 1); + s = q->src_addresses; + + for (ii = 0; ii < n; ii++) + { + srcs[ii].ip4 = *s; + s++; + } + + return (srcs); +} + +static void +igmp_send_group_report_v3 (u32 obj, void *data) +{ + igmp_pkt_build_report_t br; + igmp_config_t *config; + ip46_address_t *srcs; + igmp_group_t *group; + igmp_main_t *im; + + im = &igmp_main; + srcs = data; + group = pool_elt_at_index (im->groups, obj); + config = pool_elt_at_index (im->configs, group->config); + + igmp_pkt_build_report_init (&br, config->sw_if_index); + ASSERT (group->timers[IGMP_GROUP_TIMER_QUERY_REPLY] != + IGMP_TIMER_ID_INVALID); + + IGMP_DBG ("send-group-report: %U", + format_vnet_sw_if_index_name, + vnet_get_main (), config->sw_if_index); + + if (NULL == srcs) + { + /* + * there were no sources specified, so this is a group-sepcific query. + * We should respond with all our sources + */ + igmp_pkt_report_v3_add_group (&br, group, + IGMP_MEMBERSHIP_GROUP_mode_is_include); + } + else + { + /* + * the sources stored in the timer object are the combined set of sources + * to be quired. We need to respond only to those queried, not our full set. + */ + ip46_address_t *intersect; + + intersect = igmp_group_new_intersect_present (group, + IGMP_FILTER_MODE_INCLUDE, + srcs); + + if (vec_len (intersect)) + { + igmp_pkt_report_v3_add_report (&br, + group->key, + intersect, + IGMP_MEMBERSHIP_GROUP_mode_is_include); + vec_free (intersect); + } + } + + igmp_pkt_report_v3_send (&br); + + igmp_timer_retire (&group->timers[IGMP_GROUP_TIMER_QUERY_REPLY]); + vec_free (srcs); +} + +static igmp_membership_group_v3_type_t +igmp_filter_mode_to_report_type (igmp_filter_mode_t mode) +{ + switch (mode) + { + case IGMP_FILTER_MODE_INCLUDE: + return (IGMP_MEMBERSHIP_GROUP_mode_is_include); + case IGMP_FILTER_MODE_EXCLUDE: + return (IGMP_MEMBERSHIP_GROUP_mode_is_exclude); + } + + return (IGMP_MEMBERSHIP_GROUP_mode_is_include); +} + +/** + * Send igmp membership general report. + */ +static void +igmp_send_general_report_v3 (u32 obj, void *data) +{ + igmp_pkt_build_report_t br; + igmp_config_t *config; + igmp_group_t *group; + igmp_main_t *im; + + im = &igmp_main; + config = pool_elt_at_index (im->configs, obj); + + ASSERT (config->timers[IGMP_CONFIG_TIMER_GENERAL_REPORT] != + IGMP_TIMER_ID_INVALID); + + igmp_timer_retire (&config->timers[IGMP_CONFIG_TIMER_GENERAL_REPORT]); + + IGMP_DBG ("send-general-report: %U", + format_vnet_sw_if_index_name, + vnet_get_main (), config->sw_if_index); + + igmp_pkt_build_report_init (&br, config->sw_if_index); + + /* *INDENT-OFF* */ + FOR_EACH_GROUP (group, config, + ({ + igmp_pkt_report_v3_add_group + (&br, group, + igmp_filter_mode_to_report_type(group->router_filter_mode)); + })); + /* *INDENT-ON* */ + + igmp_pkt_report_v3_send (&br); +} + +/** + * Called from the main thread on reception of a Query message + */ +void +igmp_handle_query (const igmp_query_args_t * args) +{ + igmp_config_t *config; + + config = igmp_config_lookup (args->sw_if_index); + + if (!config) + /* + * no IGMP config on the interface. quit + */ + return; + + if (IGMP_MODE_ROUTER == config->mode) + { + ASSERT (0); + // code here for querier election */ + } + + IGMP_DBG ("query-rx: %U", format_vnet_sw_if_index_name, + vnet_get_main (), args->sw_if_index); + + + /* + Section 5.2 + "When a system receives a Query, it does not respond immediately. + Instead, it delays its response by a random amount of time, bounded + by the Max Resp Time value derived from the Max Resp Code in the + received Query message. A system may receive a variety of Queries on + different interfaces and of different kinds (e.g., General Queries, + Group-Specific Queries, and Group-and-Source-Specific Queries), each + of which may require its own delayed response. + */ + if (igmp_membership_query_v3_is_geeral (args->query)) + { + IGMP_DBG ("...general-query-rx: %U", format_vnet_sw_if_index_name, + vnet_get_main (), args->sw_if_index); + + /* + * A general query has no info that needs saving from the response + */ + if (IGMP_TIMER_ID_INVALID == + config->timers[IGMP_CONFIG_TIMER_GENERAL_REPORT]) + { + f64 delay = igmp_get_random_resp_delay (&args->query[0].header); + + IGMP_DBG ("...general-query-rx: %U schedule for %f", + format_vnet_sw_if_index_name, vnet_get_main (), + args->sw_if_index, delay); + + /* + * no currently running timer, schedule a new one + */ + config->timers[IGMP_CONFIG_TIMER_GENERAL_REPORT] = + igmp_timer_schedule (delay, + igmp_config_index (config), + igmp_send_general_report_v3, NULL); + } + /* + * else + * don't reschedule timers, we'll reply soon enough.. + */ + } + else + { + /* + * G or SG query. we'll need to save the sources quered + */ + igmp_key_t key = { + .ip4 = args->query[0].group_address, + }; + ip46_address_t *srcs; + igmp_timer_id_t tid; + igmp_group_t *group; + + group = igmp_group_lookup (config, &key); + + /* + * If there is no group config, no worries, we can ignore this + * query. If the group state does come soon, we'll send a + * state-change report at that time. + */ + if (!group) + return; + + srcs = igmp_query_mk_source_list (args->query); + tid = group->timers[IGMP_GROUP_TIMER_QUERY_REPLY]; + + IGMP_DBG ("...group-query-rx: %U for (%U, %U)", + format_vnet_sw_if_index_name, + vnet_get_main (), args->sw_if_index, + format_igmp_src_addr_list, srcs, format_igmp_key, &key); + + + if (IGMP_TIMER_ID_INVALID != tid) + { + /* + * There is a timer already running, merge the sources list + */ + ip46_address_t *current, *s; + + current = igmp_timer_get_data (tid); + + vec_foreach (s, srcs) + { + if (~0 == vec_search_with_function (current, s, + ip46_address_is_equal)) + { + vec_add1 (current, *s); + } + } + + igmp_timer_set_data (tid, current); + } + else + { + /* + * schedule a new G-specific query + */ + f64 delay = igmp_get_random_resp_delay (&args->query[0].header); + + IGMP_DBG ("...group-query-rx: schedule:%f", delay); + + group->timers[IGMP_GROUP_TIMER_QUERY_REPLY] = + igmp_timer_schedule (delay, + igmp_group_index (group), + igmp_send_group_report_v3, srcs); + } + } +} + + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/igmp/igmp_query.h b/src/plugins/igmp/igmp_query.h new file mode 100644 index 00000000000..3dd24eeb08e --- /dev/null +++ b/src/plugins/igmp/igmp_query.h @@ -0,0 +1,37 @@ +/* + *------------------------------------------------------------------ + * 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 + +/** + * A copy of the query message sent from the worker to the main thread + */ +typedef struct igmp_query_args_t_ +{ + u32 sw_if_index; + igmp_membership_query_v3_t query[0]; +} igmp_query_args_t; + +extern void igmp_handle_query (const igmp_query_args_t * args); + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/igmp/igmp_report.c b/src/plugins/igmp/igmp_report.c new file mode 100644 index 00000000000..328b890c15e --- /dev/null +++ b/src/plugins/igmp/igmp_report.c @@ -0,0 +1,193 @@ +/* + *------------------------------------------------------------------ + * 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 + +static ip46_address_t * +igmp_group_mk_source_list (const igmp_membership_group_v3_t * r) +{ + ip46_address_t *srcs = NULL; + const ip4_address_t *s; + u16 ii, n; + + n = clib_net_to_host_u16 (r->n_src_addresses); + + if (0 == n) + return (NULL); + + vec_validate (srcs, n - 1); + s = r->src_addresses; + + for (ii = 0; ii < n; ii++) + { + srcs[ii].ip4 = *s; + s++; + } + + return (srcs); +} + +static void +igmp_handle_group_update (igmp_config_t * config, + const igmp_membership_group_v3_t * igmp_group) +{ + ip46_address_t *src, *srcs; + igmp_group_t *group; + ip46_address_t key = { + .ip4 = igmp_group->group_address, + }; + + srcs = igmp_group_mk_source_list (igmp_group); + group = igmp_group_lookup (config, &key); + + IGMP_DBG (" ..group-update: %U (%U, %U)", + format_vnet_sw_if_index_name, + vnet_get_main (), config->sw_if_index, + format_igmp_key, &key, format_igmp_src_addr_list, srcs); + + if (NULL == group) + { + group = igmp_group_alloc (config, &key, IGMP_FILTER_MODE_INCLUDE); + } + + /* create or update all sources */ + vec_foreach (src, srcs) + { + igmp_group_src_update (group, src, IGMP_MODE_ROUTER); + } + + vec_free (srcs); +} + +static void +igmp_handle_group_block (igmp_config_t * config, + const igmp_membership_group_v3_t * igmp_group) +{ + ip46_address_t *s, *srcs; + igmp_pkt_build_query_t bq; + igmp_group_t *group; + ip46_address_t key = { + .ip4 = igmp_group->group_address, + }; + + srcs = igmp_group_mk_source_list (igmp_group); + group = igmp_group_lookup (config, &key); + + IGMP_DBG (" ..group-block: %U (%U, %U)", + format_vnet_sw_if_index_name, + vnet_get_main (), config->sw_if_index, + format_igmp_key, &key, format_igmp_src_addr_list, srcs); + + if (group) + { + igmp_src_t *src; + /* + * sned a group+source specific query + */ + igmp_pkt_build_query_init (&bq, config->sw_if_index); + igmp_pkt_query_v3_add_group (&bq, group, srcs); + igmp_pkt_query_v3_send (&bq); + + /* + * for each source left/blocked drop the source expire timer to the leave + * latency timer + */ + vec_foreach (s, srcs) + { + src = igmp_src_lookup (group, s); + if (NULL != src) + igmp_src_blocked (src); + } + } + /* + * a block/leave from a group for which we have no state + */ + + vec_free (srcs); +} + +static void +igmp_handle_group (igmp_config_t * config, + const igmp_membership_group_v3_t * igmp_group) +{ + IGMP_DBG ("rx-group-report: %U", + format_vnet_sw_if_index_name, + vnet_get_main (), config->sw_if_index); + + switch (igmp_group->type) + { + case IGMP_MEMBERSHIP_GROUP_mode_is_include: + case IGMP_MEMBERSHIP_GROUP_change_to_include: + case IGMP_MEMBERSHIP_GROUP_allow_new_sources: + igmp_handle_group_update (config, igmp_group); + break; + case IGMP_MEMBERSHIP_GROUP_block_old_sources: + igmp_handle_group_block (config, igmp_group); + break; + case IGMP_MEMBERSHIP_GROUP_mode_is_exclude: + case IGMP_MEMBERSHIP_GROUP_change_to_exclude: + break; + /* + * all other types ignored + */ + } +} + +void +igmp_handle_report (const igmp_report_args_t * args) +{ + const igmp_membership_group_v3_t *igmp_group; + igmp_config_t *config; + u16 n_groups, ii; + + config = igmp_config_lookup (args->sw_if_index); + + if (!config) + /* + * no IGMP config on the interface. quit + */ + return; + + if (IGMP_MODE_HOST == config->mode) + { + /* + * Hosts need not listen to the reports of other hosts. + * we're done here + */ + return; + } + + n_groups = clib_net_to_host_u16 (args->report[0].n_groups); + igmp_group = args->report[0].groups; + + for (ii = 0; ii < n_groups; ii++) + { + igmp_handle_group (config, igmp_group); + + igmp_group = group_cptr (igmp_group, + igmp_membership_group_v3_length (igmp_group)); + } +} + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/igmp/igmp_report.h b/src/plugins/igmp/igmp_report.h new file mode 100644 index 00000000000..f64309fc479 --- /dev/null +++ b/src/plugins/igmp/igmp_report.h @@ -0,0 +1,37 @@ +/* + *------------------------------------------------------------------ + * 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 + +/** + * A copy of the report message sent from the worker to the main thread + */ +typedef struct igmp_report_args_t_ +{ + u32 sw_if_index; + igmp_membership_report_v3_t report[0]; +} igmp_report_args_t; + +extern void igmp_handle_report (const igmp_report_args_t * args); + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/igmp/igmp_src.c b/src/plugins/igmp/igmp_src.c new file mode 100644 index 00000000000..5e6b6092df4 --- /dev/null +++ b/src/plugins/igmp/igmp_src.c @@ -0,0 +1,151 @@ +/* + *------------------------------------------------------------------ + * 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 + +void +igmp_src_free (igmp_src_t * src) +{ + igmp_main_t *im = &igmp_main; + + IGMP_DBG ("free-src: (%U)", format_igmp_key, src->key); + + igmp_timer_retire (&src->timers[IGMP_SRC_TIMER_EXP]); + + clib_mem_free (src->key); + pool_put (im->srcs, src); +} + +static void +igmp_src_exp (u32 obj, void *dat) +{ + igmp_group_t *group; + igmp_src_t *src; + + src = pool_elt_at_index (igmp_main.srcs, obj); + group = igmp_group_get (src->group); + + IGMP_DBG ("src-exp: %U", format_igmp_key, src->key); + + igmp_timer_retire (&src->timers[IGMP_SRC_TIMER_EXP]); + + if (IGMP_MODE_ROUTER == src->mode) + { + igmp_config_t *config; + igmp_group_t *group; + + /* + * inform interest parties + */ + group = igmp_group_get (src->group); + config = igmp_config_get (group->config); + + igmp_event (IGMP_FILTER_MODE_EXCLUDE, + config->sw_if_index, src->key, group->key); + } + + igmp_group_src_remove (group, src); + igmp_src_free (src); + + if (0 == igmp_group_n_srcs (group, IGMP_FILTER_MODE_INCLUDE)) + igmp_group_clear (group); +} + +igmp_src_t * +igmp_src_alloc (u32 group_index, const igmp_key_t * skey, igmp_mode_t mode) +{ + igmp_main_t *im = &igmp_main; + igmp_src_t *src; + + IGMP_DBG ("new-src: (%U)", format_igmp_key, skey); + + pool_get (im->srcs, src); + memset (src, 0, sizeof (igmp_src_t)); + src->mode = mode; + src->key = clib_mem_alloc (sizeof (*skey)); + src->group = group_index; + clib_memcpy (src->key, skey, sizeof (*skey)); + + if (IGMP_MODE_ROUTER == mode) + { + igmp_config_t *config; + igmp_group_t *group; + /* + * start a timer that determines whether the source is still + * active o nthe link + */ + src->timers[IGMP_SRC_TIMER_EXP] = + igmp_timer_schedule (igmp_timer_type_get (IGMP_TIMER_SRC), + src - im->srcs, igmp_src_exp, NULL); + + /* + * inform interest parties + */ + group = igmp_group_get (src->group); + config = igmp_config_get (group->config); + + igmp_event (IGMP_FILTER_MODE_INCLUDE, + config->sw_if_index, src->key, group->key); + } + else + { + src->timers[IGMP_SRC_TIMER_EXP] = IGMP_TIMER_ID_INVALID; + } + + return (src); +} + +void +igmp_src_refresh (igmp_src_t * src) +{ + IGMP_DBG ("refresh-src: (%U)", format_igmp_key, src->key); + + igmp_timer_retire (&src->timers[IGMP_SRC_TIMER_EXP]); + + src->timers[IGMP_SRC_TIMER_EXP] = + igmp_timer_schedule (igmp_timer_type_get (IGMP_TIMER_SRC), + igmp_src_index (src), igmp_src_exp, NULL); +} + +void +igmp_src_blocked (igmp_src_t * src) +{ + IGMP_DBG ("block-src: (%U)", format_igmp_key, src->key); + + igmp_timer_retire (&src->timers[IGMP_SRC_TIMER_EXP]); + + src->timers[IGMP_SRC_TIMER_EXP] = + igmp_timer_schedule (igmp_timer_type_get (IGMP_TIMER_LEAVE), + igmp_src_index (src), igmp_src_exp, NULL); +} + +u32 +igmp_src_index (igmp_src_t * src) +{ + return (src - igmp_main.srcs); +} + + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/igmp/igmp_src.h b/src/plugins/igmp/igmp_src.h new file mode 100644 index 00000000000..032066ec726 --- /dev/null +++ b/src/plugins/igmp/igmp_src.h @@ -0,0 +1,88 @@ +/* + *------------------------------------------------------------------ + * 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_SOURCE_H__ +#define __IGMP_SOURCE_H__ + +#include + +/** + * IGMP Source timers + */ +typedef enum igmp_src_timer_t_ +{ + /** + * On expiry the source has not been refreshed by a query + * and can now be reaped + */ + IGMP_SRC_TIMER_EXP, +} igmp_src_timer_t; + +#define IGMP_SRC_N_TIMERS (IGMP_SRC_TIMER_EXP + 1) + +/** + * @brief IGMP source + * The representation of a specified source address with in multicast group. + */ +typedef struct igmp_src_t_ +{ + /** + * The source's key + */ + igmp_key_t *key; + + /** + * The liveness timer. Reset with each recieved report. on expiry + * the source is removed from the group. + */ + u32 exp_timer; + + /** + * The group this source is on + */ + u32 group; + + /** + * the mode that provided this source + */ + igmp_mode_t mode; + + /** + * Timers + */ + u32 timers[IGMP_SRC_N_TIMERS]; +} igmp_src_t; + +extern void igmp_src_free (igmp_src_t * src); + +extern igmp_src_t *igmp_src_alloc (u32 group_index, + const igmp_key_t * skey, igmp_mode_t mode); + +extern u32 igmp_src_index (igmp_src_t * src); + +extern void igmp_src_refresh (igmp_src_t * src); +extern void igmp_src_blocked (igmp_src_t * src); + +#endif + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/igmp/igmp_ssm_range.c b/src/plugins/igmp/igmp_ssm_range.c new file mode 100644 index 00000000000..3d12712d3db --- /dev/null +++ b/src/plugins/igmp/igmp_ssm_range.c @@ -0,0 +1,162 @@ +/* + *------------------------------------------------------------------ + * 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 + +typedef struct igmp_group_prefix_t +{ + fib_prefix_t igp_prefix; + igmp_group_prefix_type_t igp_type; +} igmp_group_prefix_t; + +static igmp_group_prefix_t *igmp_group_prefixs; + +u8 * +format_igmp_group_prefix_type (u8 * s, va_list * args) +{ + igmp_group_prefix_type_t type = va_arg (*args, int); + + switch (type) + { +#define _(n,f) case IGMP_GROUP_PREFIX_TYPE_##f: return (format (s, "%s", #f)); + foreach_igmp_group_prefix_type +#undef _ + } + return format (s, "unknown:%d", type); +} + +static int +igmp_group_prefix_cmp (const igmp_group_prefix_t * gp1, + const fib_prefix_t * p) +{ + return (fib_prefix_cmp (&gp1->igp_prefix, p)); +} + +void +igmp_group_prefix_set (const fib_prefix_t * pfx, + igmp_group_prefix_type_t type) +{ + u32 pos; + + pos = + vec_search_with_function (igmp_group_prefixs, pfx, igmp_group_prefix_cmp); + + if ((~0 == pos) && (IGMP_GROUP_PREFIX_TYPE_SSM == type)) + { + igmp_group_prefix_t gp = { + .igp_prefix = *pfx, + .igp_type = type, + }; + + vec_add1 (igmp_group_prefixs, gp); + } + if ((~0 != pos) && (IGMP_GROUP_PREFIX_TYPE_ASM == type)) + { + vec_del1 (igmp_group_prefixs, pos); + } +} + +static void +igmp_ssm_range_populate (void) +{ + igmp_group_prefix_t *ssm_default; + + vec_add2 (igmp_group_prefixs, ssm_default, 1); + + ssm_default->igp_prefix.fp_addr.ip4.as_u32 = IGMP_SSM_DEFAULT; + ssm_default->igp_prefix.fp_proto = FIB_PROTOCOL_IP4; + ssm_default->igp_prefix.fp_len = 8; + ssm_default->igp_type = IGMP_GROUP_PREFIX_TYPE_SSM; +} + +igmp_group_prefix_type_t +igmp_group_prefix_get_type (const ip46_address_t * gaddr) +{ + igmp_group_prefix_t *igp; + + vec_foreach (igp, igmp_group_prefixs) + { + if (ip4_destination_matches_route (&ip4_main, + &gaddr->ip4, + &igp->igp_prefix.fp_addr.ip4, + igp->igp_prefix.fp_len)) + return (IGMP_GROUP_PREFIX_TYPE_SSM); + } + + return (IGMP_GROUP_PREFIX_TYPE_ASM); +} + +void +igmp_ssm_range_walk (igmp_ssm_range_walk_t fn, void *ctx) +{ + igmp_group_prefix_t *igp; + + vec_foreach (igp, igmp_group_prefixs) + { + if (WALK_STOP == fn (&igp->igp_prefix, igp->igp_type, ctx)) + break; + } +} + +static clib_error_t * +igmp_ssm_range_show (vlib_main_t * vm, + unformat_input_t * input, vlib_cli_command_t * cmd) +{ + igmp_group_prefix_t *igp; + + vec_foreach (igp, igmp_group_prefixs) + { + vlib_cli_output (vm, "%U => %U", + format_fib_prefix, &igp->igp_prefix, + format_igmp_group_prefix_type, igp->igp_type); + } + return (NULL); +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (igmp_show_timers_command, static) = { + .path = "show igmp ssm-ranges", + .short_help = "show igmp ssm-ranges", + .function = igmp_ssm_range_show, +}; +/* *INDENT-ON* */ + +static clib_error_t * +igmp_ssm_range_init (vlib_main_t * vm) +{ + clib_error_t *error; + + if ((error = vlib_call_init_function (vm, igmp_init))) + return error; + + igmp_ssm_range_populate (); + + IGMP_DBG ("ssm-range-initialized"); + + return (error); +} + +VLIB_INIT_FUNCTION (igmp_ssm_range_init); + + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/igmp/igmp_ssm_range.h b/src/plugins/igmp/igmp_ssm_range.h new file mode 100644 index 00000000000..30180ec1acd --- /dev/null +++ b/src/plugins/igmp/igmp_ssm_range.h @@ -0,0 +1,58 @@ +/* + *------------------------------------------------------------------ + * 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_SSM_RANGE_H__ +#define __IGMP_SSM_RANGE_H__ + +#include + +/** + * Make sure this remains in-sync with the .api enum definition + */ +#define foreach_igmp_group_prefix_type \ + _ (0x0, ASM) \ + _ (0x1, SSM) + +typedef enum igmp_group_prefix_type_t_ +{ +#define _(n,f) IGMP_GROUP_PREFIX_TYPE_##f = n, + foreach_igmp_group_prefix_type +#undef _ +} igmp_group_prefix_type_t; + +extern igmp_group_prefix_type_t igmp_group_prefix_get_type (const + ip46_address_t * + gaddr); + +extern void igmp_group_prefix_set (const fib_prefix_t * pfx, + igmp_group_prefix_type_t type); + +typedef walk_rc_t (*igmp_ssm_range_walk_t) (const fib_prefix_t * pfx, + igmp_group_prefix_type_t type, + void *ctx); + +extern void igmp_ssm_range_walk (igmp_ssm_range_walk_t fn, void *ctx); + +#endif + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/igmp/igmp_timer.c b/src/plugins/igmp/igmp_timer.c new file mode 100644 index 00000000000..278b7db917e --- /dev/null +++ b/src/plugins/igmp/igmp_timer.c @@ -0,0 +1,241 @@ +/* + *------------------------------------------------------------------ + * 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 + +/** + * Default timer values as per RFC + */ + +static igmp_timer_type_t igmp_default_timer_values[] = { + [IGMP_TIMER_QUERY] = 60, + [IGMP_TIMER_SRC] = (3 * 60), + [IGMP_TIMER_LEAVE] = 60, + [IGMP_TIMER_REPORT_INTERVAL] = 1, +}; + +#define IGMP_N_TIMERS (IGMP_TIMER_REPORT_INTERVAL+1) + +/** + * Timer + */ +typedef struct igmp_timer_t_ +{ + /** Expiration timer */ + f64 exp_time; + + /** Call-back function to invoke on expiry */ + igmp_timer_function_t func; + + /** index of the object that scheduled the timer */ + u32 obj; + + /** Data registered by the client and passed back when the timer expires */ + void *data; +} igmp_timer_t; + +enum +{ + IGMP_PROCESS_EVENT_UPDATE_TIMER = 1, +} igmp_process_event_t; + +/** + * pool of timers + */ +static igmp_timer_t *timer_pool; + +/** + * Vector of pending timers + */ +static u32 *pending_timers; + +u32 +igmp_timer_type_get (igmp_timer_type_t t) +{ + ASSERT (t < IGMP_N_TIMERS); + return (igmp_default_timer_values[t]); +} + +void +igmp_timer_type_set (igmp_timer_type_t t, u32 v) +{ + ASSERT (t < IGMP_N_TIMERS); + igmp_default_timer_values[t] = v; +} + + +static int +igmp_timer_compare (const void *_v1, const void *_v2) +{ + const u32 *i1 = _v1, *i2 = _v2; + const igmp_timer_t *t1, *t2; + f64 dt; + + t1 = pool_elt_at_index (timer_pool, *i1); + t2 = pool_elt_at_index (timer_pool, *i2); + + dt = t2->exp_time - t1->exp_time; + + return (dt < 0 ? -1 : (dt > 0 ? +1 : 0)); +} + +/** \brief igmp get next timer + @param im - igmp main + + Get next timer. +*/ +u32 +igmp_get_next_timer (void) +{ + if (0 == vec_len (pending_timers)) + return (IGMP_TIMER_ID_INVALID); + + return (pending_timers[vec_len (pending_timers) - 1]); +} + +void * +igmp_timer_get_data (igmp_timer_id_t tid) +{ + igmp_timer_t *timer; + + timer = pool_elt_at_index (timer_pool, tid); + + return (timer->data); +} + +void +igmp_timer_set_data (igmp_timer_id_t tid, void *data) +{ + igmp_timer_t *timer; + + timer = pool_elt_at_index (timer_pool, tid); + + timer->data = data; +} + +int +igmp_timer_is_running (igmp_timer_id_t tid) +{ + return (IGMP_TIMER_ID_INVALID == tid); +} + +/** \brief igmp timer process + @param vm - vlib main + @param rt - vlib runtime node + @param f - vlib frame + + Handle igmp timers. +*/ +static uword +igmp_timer_process (vlib_main_t * vm, vlib_node_runtime_t * rt, + vlib_frame_t * f) +{ + uword *event_data = 0, event_type; + igmp_timer_id_t tid; + igmp_timer_t *timer; + + tid = IGMP_TIMER_ID_INVALID; + + while (1) + { + /* suspend util timer expires */ + if (IGMP_TIMER_ID_INVALID != tid) + { + timer = pool_elt_at_index (timer_pool, tid); + vlib_process_wait_for_event_or_clock + (vm, timer->exp_time - vlib_time_now (vm)); + } + else + vlib_process_wait_for_event (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; + + /* timer expired */ + ASSERT (tid != IGMP_TIMER_ID_INVALID); + + timer = pool_elt_at_index (timer_pool, tid); + ASSERT (timer->func != NULL); + timer->func (timer->obj, timer->data); + + next_timer: + tid = igmp_get_next_timer (); + } + 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 = 0, +}; +/* *INDENT-ON* */ + +igmp_timer_id_t +igmp_timer_schedule (f64 when, u32 obj, igmp_timer_function_t fn, void *data) +{ + igmp_timer_t *timer; + vlib_main_t *vm; + + ASSERT (fn); + + vm = vlib_get_main (); + pool_get (timer_pool, timer); + + timer->exp_time = vlib_time_now (vm) + when; + timer->obj = obj; + timer->func = fn; + timer->data = data; + + vec_add1 (pending_timers, timer - timer_pool); + + vec_sort_with_function (pending_timers, igmp_timer_compare); + + vlib_process_signal_event (vm, igmp_timer_process_node.index, + IGMP_PROCESS_EVENT_UPDATE_TIMER, 0); + + return (timer - timer_pool); +} + +void +igmp_timer_retire (igmp_timer_id_t * tid) +{ + if (IGMP_TIMER_ID_INVALID == *tid) + return; + vec_del1 (pending_timers, vec_search (pending_timers, *tid)); + pool_put_index (timer_pool, *tid); + *tid = IGMP_TIMER_ID_INVALID; + + vlib_process_signal_event (vlib_get_main (), + igmp_timer_process_node.index, + IGMP_PROCESS_EVENT_UPDATE_TIMER, 0); +} + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/igmp/igmp_timer.h b/src/plugins/igmp/igmp_timer.h new file mode 100644 index 00000000000..4847d6f6f6f --- /dev/null +++ b/src/plugins/igmp/igmp_timer.h @@ -0,0 +1,84 @@ +/* + *------------------------------------------------------------------ + * 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_TIMER_H_ +#define _IGMP_TIMER_H_ + +#include + +/** + * The id of a running timer + */ +typedef u32 igmp_timer_id_t; + +#define IGMP_TIMER_ID_INVALID (~0) + +/** + * A call-back function invoked when a timer expires; + * @param obj - the [pool] index of the object that scheduled the timer + * @param data - Data registered by the client at schedule time. + */ +typedef void (*igmp_timer_function_t) (u32 obj, void *data); + +/** + * @brief + * Scehdule a timer to expire in 'when' seconds + * + */ +extern igmp_timer_id_t igmp_timer_schedule (f64 when, + u32 obj, + igmp_timer_function_t fn, + void *data); + +extern void igmp_timer_retire (igmp_timer_id_t * tid); +extern int igmp_timer_is_running (igmp_timer_id_t tid); + +extern f64 igmp_timer_get_expiry_time (igmp_timer_id_t t); +extern void *igmp_timer_get_data (igmp_timer_id_t t); +extern void igmp_timer_set_data (igmp_timer_id_t t, void *data); + +/** + * IGMP timer types and their values + * QUERY - the general query timer + * SRC - source expiration + * LEAVE - leave latency + */ +#define foreach_igmp_timer_type \ + _ (0x1, QUERY) \ + _ (0x2, SRC) \ + _ (0x3, LEAVE) \ + _ (0x4, REPORT_INTERVAL) + +typedef enum igmp_timer_type_t_ +{ +#define _(n,f) IGMP_TIMER_##f = n, + foreach_igmp_timer_type +#undef _ +} igmp_timer_type_t; + +extern u32 igmp_timer_type_get (igmp_timer_type_t t); +extern void igmp_timer_type_set (igmp_timer_type_t t, u32 v); + +#endif /* IGMP_TIMER_H_ */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/igmp/igmp_types.h b/src/plugins/igmp/igmp_types.h new file mode 100644 index 00000000000..0ed879e5d2e --- /dev/null +++ b/src/plugins/igmp/igmp_types.h @@ -0,0 +1,77 @@ +/* + *------------------------------------------------------------------ + * 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_TYPES_H__ +#define __IGMP_TYPES_H__ + +#include + +/** \brief IGMP source - where to the request for state arrive from + * host - from an API/CLI command to add the state + * network - from a received report + * Each source could be mode from both modes, so these are flags. + */ +#define foreach_igmp_mode \ + _ (1, HOST) \ + _ (2, ROUTER) \ + +typedef enum igmp_mode_t_ +{ +#define _(n,f) IGMP_MODE_##f = n, + foreach_igmp_mode +#undef _ +} igmp_mode_t; + +typedef enum igmp_msg_type_t_ +{ + IGMP_MSG_REPORT, + IGMP_MSG_QUERY, +} igmp_msg_type_t; + +/** + * @brief IGMP Key + * Used to index groups within an interface config and sources within a list + */ +typedef ip46_address_t igmp_key_t; + +/** + * @brief IGMP filter mode + * Exclude all source address except this one + * Include only this source address + */ +#define foreach_igmp_filter_mode \ + _ (1, INCLUDE) \ + _ (0, EXCLUDE) \ + +typedef enum igmp_filter_mode_t_ +{ +#define _(n,f) IGMP_FILTER_MODE_##f = n, + foreach_igmp_filter_mode +#undef _ +} igmp_filter_mode_t; + +#define IGMP_N_FILTER_MODES 2 + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ + +#endif diff --git a/src/vlibapi/api_helper_macros.h b/src/vlibapi/api_helper_macros.h index 5746f071cdb..626d8d4fc95 100644 --- a/src/vlibapi/api_helper_macros.h +++ b/src/vlibapi/api_helper_macros.h @@ -115,7 +115,7 @@ vnet_sw_if_index_is_api_valid (u32 sw_if_index) } #define VALIDATE_SW_IF_INDEX(mp) \ - do { u32 __sw_if_index = ntohl(mp->sw_if_index); \ + do { u32 __sw_if_index = ntohl((mp)->sw_if_index); \ if (!vnet_sw_if_index_is_api_valid(__sw_if_index)) { \ rv = VNET_API_ERROR_INVALID_SW_IF_INDEX; \ goto bad_sw_if_index; \ @@ -129,7 +129,7 @@ bad_sw_if_index: \ } while (0); #define VALIDATE_RX_SW_IF_INDEX(mp) \ - do { u32 __rx_sw_if_index = ntohl(mp->rx_sw_if_index); \ + do { u32 __rx_sw_if_index = ntohl((mp)->rx_sw_if_index); \ if (!vnet_sw_if_index_is_api_valid(__rx_sw_if_index)) { \ rv = VNET_API_ERROR_INVALID_SW_IF_INDEX; \ goto bad_rx_sw_if_index; \ diff --git a/src/vnet.am b/src/vnet.am index bb74cc85201..b9ae4083114 100644 --- a/src/vnet.am +++ b/src/vnet.am @@ -350,6 +350,7 @@ libvnet_la_SOURCES += \ vnet/ip/icmp4.c \ vnet/ip/icmp6.c \ vnet/ip/ip46_cli.c \ + vnet/ip/ip_types_api.c \ vnet/ip/ip4_format.c \ vnet/ip/ip4_forward.c \ vnet/ip/ip4_punt_drop.c \ diff --git a/src/vnet/fib/fib_test.c b/src/vnet/fib/fib_test.c index e46f670269a..95540834f52 100644 --- a/src/vnet/fib/fib_test.c +++ b/src/vnet/fib/fib_test.c @@ -851,9 +851,6 @@ fib_test_v4 (void) /* * 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()); @@ -4375,9 +4372,6 @@ fib_test_v6 (void) /* * 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 a8e9db6d9ab..cd4a40d77ff 100644 --- a/src/vnet/ip/igmp_packet.h +++ b/src/vnet/ip/igmp_packet.h @@ -63,32 +63,46 @@ typedef enum #define _(n,f) IGMP_TYPE_##f = n, foreach_igmp_type #undef _ -} igmp_type_t; +} __attribute__ ((packed)) igmp_type_t; typedef struct { - igmp_type_t type:8; + igmp_type_t type; u8 code; u16 checksum; } igmp_header_t; -typedef struct +/** + * Calculate the maximum response time allowed from the header. + * - RFC 3367 Section 4.1.1 + */ +always_inline f64 +igmp_header_get_max_resp_time (const igmp_header_t * header) { - /* membership_query, version <= 2 reports. */ - igmp_header_t header; + f64 qqi; - /* Multicast destination address. */ - ip4_address_t dst; -} igmp_message_t; + if (header->code < 128) + qqi = header->code; + else + { + u8 mant = header->code << 4; + u8 exp = (header->code & 0x7) << 1; + + qqi = ((mant | 0x10) << (exp + 3)); + } + + /* Querier's Query Interval (QQI), is represented in units of seconds */ + return (qqi / 10); +} typedef struct { /* type 0x11 (IGMPv3) */ igmp_header_t header; - ip4_address_t dst; + ip4_address_t group_address; /* Reserved, Suppress Router-Side Processing flag and Querier's Robustness Variable RRRRSQQQ. */ @@ -101,11 +115,25 @@ typedef struct ip4_address_t src_addresses[0]; } igmp_membership_query_v3_t; +always_inline u32 +igmp_membership_query_v3_length (const igmp_membership_query_v3_t * q) +{ + return (sizeof (*q) + + (sizeof (ip4_address_t) * + clib_net_to_host_u16 (q->n_src_addresses))); +} + +always_inline int +igmp_membership_query_v3_is_geeral (const igmp_membership_query_v3_t * q) +{ + return (0 == q->group_address.as_u32); +} + #define foreach_igmp_membership_group_v3_type \ - _ (1, mode_is_filter_include) \ - _ (2, mode_is_filter_exclude) \ - _ (3, change_to_filter_include) \ - _ (4, change_to_filter_exclude) \ + _ (1, mode_is_include) \ + _ (2, mode_is_exclude) \ + _ (3, change_to_include) \ + _ (4, change_to_exclude) \ _ (5, allow_new_sources) \ _ (6, block_old_sources) @@ -114,11 +142,11 @@ typedef enum #define _(n,f) IGMP_MEMBERSHIP_GROUP_##f = n, foreach_igmp_membership_group_v3_type #undef _ -} igmp_membership_group_v3_type_t; +} __attribute__ ((packed)) igmp_membership_group_v3_type_t; typedef struct { - igmp_membership_group_v3_type_t type:8; + igmp_membership_group_v3_type_t type; /* Number of 32 bit words of aux data after source addresses. */ u8 n_aux_u32s; @@ -126,12 +154,20 @@ typedef struct /* Number of source addresses that follow. */ u16 n_src_addresses; - /* Destination multicast address. */ - ip4_address_t dst_address; + /* Destination multicast group address. */ + ip4_address_t group_address; ip4_address_t src_addresses[0]; } igmp_membership_group_v3_t; +always_inline u32 +igmp_membership_group_v3_length (const igmp_membership_group_v3_t * g) +{ + return (sizeof (*g) + + (sizeof (ip4_address_t) * + clib_net_to_host_u16 (g->n_src_addresses))); +} + always_inline igmp_membership_group_v3_t * igmp_membership_group_v3_next (igmp_membership_group_v3_t * g) { @@ -153,6 +189,24 @@ typedef struct igmp_membership_group_v3_t groups[0]; } igmp_membership_report_v3_t; +always_inline u32 +igmp_membership_report_v3_length (const igmp_membership_report_v3_t * r) +{ + const igmp_membership_group_v3_t *g; + u32 len, ii, glen; + + len = sizeof (igmp_membership_report_v3_t); + g = r->groups; + + for (ii = 0; ii < clib_net_to_host_u16 (r->n_groups); ii++) + { + glen = igmp_membership_group_v3_length (g); + g = (const igmp_membership_group_v3_t *) (((u8 *) g) + glen); + len += glen; + } + return (len); +} + /* IP6 flavor of IGMP is called MLD which is embedded in ICMP6. */ typedef struct { diff --git a/src/vnet/ip/ip.api b/src/vnet/ip/ip.api index 228e59158ff..4811a349d38 100644 --- a/src/vnet/ip/ip.api +++ b/src/vnet/ip/ip.api @@ -20,6 +20,7 @@ */ option version = "1.3.0"; +import "vnet/ip/ip_types.api"; import "vnet/fib/fib_types.api"; /** \brief Add / del table request diff --git a/src/vnet/ip/ip4_error.h b/src/vnet/ip/ip4_error.h index fa93e8673e5..52c43adb12c 100644 --- a/src/vnet/ip/ip4_error.h +++ b/src/vnet/ip/ip4_error.h @@ -59,7 +59,7 @@ _ (SRC_LOOKUP_MISS, "ip4 source lookup miss") \ _ (DROP, "ip4 drop") \ _ (PUNT, "ip4 punt") \ - _ (SAME_INTERFACE, "ip4 egrees interface same as ingress") \ + _ (SAME_INTERFACE, "ip4 egress interface same as ingress") \ \ /* Errors signalled by ip4-local. */ \ _ (UNKNOWN_PROTOCOL, "unknown ip protocol") \ diff --git a/src/vnet/ip/ip_types.api b/src/vnet/ip/ip_types.api index ec6b9d0024c..72eadaf92df 100644 --- a/src/vnet/ip/ip_types.api +++ b/src/vnet/ip/ip_types.api @@ -1,3 +1,4 @@ +/* Hey Emacs use -*- mode: C -*- */ /* * Copyright (c) 2018 Cisco and/or its affiliates. * Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,3 +36,8 @@ typedef address { vl_api_address_family_t af; vl_api_address_union_t un; }; + +typedef prefix { + vl_api_address_t address; + u8 address_length; +}; diff --git a/src/vnet/ip/ip_types_api.c b/src/vnet/ip/ip_types_api.c new file mode 100644 index 00000000000..7fa8e404c78 --- /dev/null +++ b/src/vnet/ip/ip_types_api.c @@ -0,0 +1,105 @@ +/* + * 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 + +#define vl_typedefs /* define message structures */ +#include +#undef vl_typedefs + +#define vl_endianfun /* define message structures */ +#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 + + +void +ip_address_decode (const vl_api_address_t * in, ip46_address_t * out) +{ + switch (in->af) + { + case ADDRESS_IP4: + memset (out, 0, sizeof (*out)); + clib_memcpy (&out->ip4, &in->un.ip4, sizeof (out->ip4)); + break; + case ADDRESS_IP6: + clib_memcpy (&out->ip6, &in->un.ip6, sizeof (out->ip6)); + break; + } +} + +void +ip_address_encode (const ip46_address_t * in, vl_api_address_t * out) +{ + if (ip46_address_is_ip4 (in)) + { + memset (out, 0, sizeof (*out)); + out->af = ADDRESS_IP4; + clib_memcpy (&out->un.ip4, &in->ip4, sizeof (out->un.ip4)); + } + else + { + out->af = ADDRESS_IP6; + clib_memcpy (&out->un.ip6, &in->ip6, sizeof (out->un.ip6)); + } +} + +void +ip_prefix_decode (const vl_api_prefix_t * in, fib_prefix_t * out) +{ + switch (in->address.af) + { + case ADDRESS_IP4: + out->fp_proto = FIB_PROTOCOL_IP4; + break; + case ADDRESS_IP6: + out->fp_proto = FIB_PROTOCOL_IP6; + break; + } + out->fp_len = in->address_length; + ip_address_decode (&in->address, &out->fp_addr); +} + +void +ip_prefix_encode (const fib_prefix_t * in, vl_api_prefix_t * out) +{ + switch (in->fp_proto) + { + case FIB_PROTOCOL_IP4: + out->address.af = ADDRESS_IP4; + break; + case FIB_PROTOCOL_IP6: + out->address.af = ADDRESS_IP6; + break; + case FIB_PROTOCOL_MPLS: + ASSERT (0); + break; + } + out->address_length = in->fp_len; + ip_address_encode (&in->fp_addr, &out->address); +} + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vnet/ip/ip_types_api.h b/src/vnet/ip/ip_types_api.h new file mode 100644 index 00000000000..2ad59ae438e --- /dev/null +++ b/src/vnet/ip/ip_types_api.h @@ -0,0 +1,50 @@ +/* + * 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 __IP_TYPES_API_H__ +#define __IP_TYPES_API_H__ + +/** + * Conversion functions to/from (decode/encode) API types to VPP internal types + */ + +#include +#include + +/** + * Forward declarations so we need not #include the API definitions here + */ +struct _vl_api_address; +struct _vl_api_prefix; + +extern void ip_address_decode (const struct _vl_api_address *in, + ip46_address_t * out); +extern void ip_address_encode (const ip46_address_t * in, + struct _vl_api_address *out); + +extern void ip_prefix_decode (const struct _vl_api_prefix *in, + fib_prefix_t * out); +extern void ip_prefix_encode (const fib_prefix_t * in, + struct _vl_api_prefix *out); + +#endif + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vnet/mfib/mfib_types.h b/src/vnet/mfib/mfib_types.h index ec68c1a5873..2ab0f7d4fef 100644 --- a/src/vnet/mfib/mfib_types.h +++ b/src/vnet/mfib/mfib_types.h @@ -170,6 +170,7 @@ typedef enum mfib_source_t_ MFIB_SOURCE_VXLAN_GPE, MFIB_SOURCE_RR, MFIB_SOURCE_GENEVE, + MFIB_SOURCE_IGMP, MFIB_SOURCE_DEFAULT_ROUTE, } mfib_source_t; @@ -184,6 +185,7 @@ typedef enum mfib_source_t_ [MFIB_SOURCE_VXLAN_GPE] = "VXLAN-GPE", \ [MFIB_SOURCE_RR] = "Recursive-resolution", \ [MFIB_SOURCE_GENEVE] = "Geneve", \ + [MFIB_SOURCE_IGMP] = "IGMP", \ [MFIB_SOURCE_DEFAULT_ROUTE] = "Default Route", \ } diff --git a/src/vppinfra/vec.h b/src/vppinfra/vec.h index f233ae1c48f..1b0378fe063 100644 --- a/src/vppinfra/vec.h +++ b/src/vppinfra/vec.h @@ -953,6 +953,27 @@ do { \ _v(i); \ }) +/** \brief Search a vector for the index of the entry that matches. + + @param v1 Pointer to a vector + @param v2 Pointer to entry to match + @param fn Comparison function !0 => match + @return index of match or ~0 +*/ +#define vec_search_with_function(v,E,fn) \ +({ \ + word _v(i) = 0; \ + while (_v(i) < vec_len(v)) \ + { \ + if (0 != fn(&(v)[_v(i)], (E))) \ + break; \ + _v(i)++; \ + } \ + if (_v(i) == vec_len(v)) \ + _v(i) = ~0; \ + _v(i); \ +}) + /** \brief Sort a vector using the supplied element comparison function @param vec vector to sort diff --git a/test/framework.py b/test/framework.py index fdaba2b84d9..4ecb66fe408 100644 --- a/test/framework.py +++ b/test/framework.py @@ -837,12 +837,13 @@ class VppTestCase(unittest.TestCase): "Finished sleep (%s) - slept %ss (wanted %ss)" % ( remark, after - before, timeout)) - def send_and_assert_no_replies(self, intf, pkts, remark=""): + def send_and_assert_no_replies(self, intf, pkts, remark="", timeout=None): self.vapi.cli("clear trace") intf.add_stream(pkts) self.pg_enable_capture(self.pg_interfaces) self.pg_start() - timeout = 1 + if not timeout: + timeout = 1 for i in self.pg_interfaces: i.get_capture(0, timeout=timeout) i.assert_nothing_captured(remark=remark) diff --git a/test/test_igmp.py b/test/test_igmp.py index 22b6d2f02e3..cfdd1a8aed3 100644 --- a/test/test_igmp.py +++ b/test/test_igmp.py @@ -1,16 +1,20 @@ #!/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 * +from vpp_ip_route import find_mroute, VppIpTable + + +class IgmpMode: + HOST = 1 + ROUTER = 0 class TestIgmp(VppTestCase): @@ -19,11 +23,16 @@ class TestIgmp(VppTestCase): def setUp(self): super(TestIgmp, self).setUp() - self.create_pg_interfaces(range(2)) + self.create_pg_interfaces(range(4)) self.sg_list = [] self.config_list = [] self.ip_addr = [] + self.ip_table = VppIpTable(self, 1) + self.ip_table.add_vpp_config() + + for pg in self.pg_interfaces[2:]: + pg.set_table_ip4(1) for pg in self.pg_interfaces: pg.admin_up() pg.config_ip4() @@ -33,6 +42,7 @@ class TestIgmp(VppTestCase): for pg in self.pg_interfaces: self.vapi.igmp_clear_interface(pg.sw_if_index) pg.unconfig_ip4() + pg.set_table_ip4(0) pg.admin_down() super(TestIgmp, self).tearDown() @@ -41,271 +51,589 @@ class TestIgmp(VppTestCase): self.pg_enable_capture(self.pg_interfaces) self.pg_start() - def test_igmp_parse_report(self): - """ IGMP parse Membership Report """ + def test_igmp_flush(self): + """ IGMP Link Up/down and Flush """ # - # VPP acts as a router + # FIX THIS. Link down. # - 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, ttl=1, - options=IPOption(copy_flag=1, optclass=0, - option="router_alert", - length=2, value=0)) / - IGMPv3() / - IGMPv3mr(numgrp=1) / - IGMPv3gr(rtype=3, maddr="224.1.1.1", srcaddrs=["10.1.1.1"])) + def test_igmp_enable(self): + """ IGMP enable/disable on an interface - self.send(self.pg0, p_join) + check for the addition/removal of the IGMP mroutes """ - # 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")) + 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.HOST) - # 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) + self.assertTrue(find_mroute(self, "224.0.0.1", "0.0.0.0", 32)) + self.assertTrue(find_mroute(self, "224.0.0.22", "0.0.0.0", 32)) - # 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) + self.vapi.igmp_enable_disable(self.pg2.sw_if_index, 1, IGMP_MODE.HOST) + self.vapi.igmp_enable_disable(self.pg3.sw_if_index, 1, IGMP_MODE.HOST) - dump = self.vapi.igmp_dump() - self.assertFalse(dump) + self.assertTrue(find_mroute(self, "224.0.0.1", "0.0.0.0", 32, + table_id=1)) + self.assertTrue(find_mroute(self, "224.0.0.22", "0.0.0.0", 32, + table_id=1)) + self.vapi.igmp_enable_disable(self.pg0.sw_if_index, 0, IGMP_MODE.HOST) + self.vapi.igmp_enable_disable(self.pg1.sw_if_index, 0, IGMP_MODE.HOST) + self.vapi.igmp_enable_disable(self.pg2.sw_if_index, 0, IGMP_MODE.HOST) + self.vapi.igmp_enable_disable(self.pg3.sw_if_index, 0, IGMP_MODE.HOST) + + self.assertFalse(find_mroute(self, "224.0.0.1", "0.0.0.0", 32)) + self.assertFalse(find_mroute(self, "224.0.0.22", "0.0.0.0", 32)) + self.assertFalse(find_mroute(self, "224.0.0.1", "0.0.0.0", 32, + table_id=1)) + self.assertFalse(find_mroute(self, "224.0.0.22", "0.0.0.0", 32, + table_id=1)) def verify_general_query(self, p): ip = p[IP] + self.assertEqual(len(ip.options), 1) + self.assertEqual(ip.options[0].option, 20) 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") - def test_igmp_send_query(self): - """ IGMP send General Query """ + def verify_group_query(self, p, grp, srcs): + ip = p[IP] + self.assertEqual(ip.dst, grp) + self.assertEqual(ip.proto, 2) + self.assertEqual(len(ip.options), 1) + self.assertEqual(ip.options[0].option, 20) + self.assertEqual(ip.proto, 2) + igmp = p[IGMPv3] + self.assertEqual(igmp.type, 0x11) + self.assertEqual(igmp.gaddr, grp) + + def verify_report(self, rx, records): + ip = rx[IP] + self.assertEqual(rx[IP].dst, "224.0.0.22") + self.assertEqual(len(ip.options), 1) + self.assertEqual(ip.options[0].option, 20) + self.assertEqual(ip.proto, 2) + self.assertEqual(IGMPv3.igmpv3types[rx[IGMPv3].type], + "Version 3 Membership Report") + self.assertEqual(rx[IGMPv3mr].numgrp, len(records)) + + received = rx[IGMPv3mr].records + + for ii in range(len(records)): + gr = received[ii] + r = records[ii] + self.assertEqual(IGMPv3gr.igmpv3grtypes[gr.rtype], r.type) + self.assertEqual(gr.numsrc, len(r.sg.saddrs)) + self.assertEqual(gr.maddr, r.sg.gaddr) + self.assertEqual(len(gr.srcaddrs), len(r.sg.saddrs)) + + self.assertEqual(sorted(gr.srcaddrs), + sorted(r.sg.saddrs)) + + def add_group(self, itf, sg, n_pkts=2): + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + hs = VppHostState(self, + IGMP_FILTER.INCLUDE, + itf.sw_if_index, + sg) + hs.add_vpp_config() + + capture = itf.get_capture(n_pkts, timeout=10) + + # reports are transmitted twice due to default rebostness value=2 + self.verify_report(capture[0], + [IgmpRecord(sg, "Allow New Sources")]), + self.verify_report(capture[1], + [IgmpRecord(sg, "Allow New Sources")]), + + return hs + + def remove_group(self, hs): + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + hs.remove_vpp_config() + + capture = self.pg0.get_capture(1, timeout=10) + + self.verify_report(capture[0], + [IgmpRecord(hs.sg, "Block Old Sources")]) + + def test_igmp_host(self): + """ IGMP Host functions """ # - # VPP acts as a router. - # Send a membership report so VPP builds state + # Enable interface for host functions # - 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.vapi.igmp_enable_disable(self.pg0.sw_if_index, + 1, + IGMP_MODE.HOST) - self.send(self.pg0, p_mr) - self.logger.info(self.vapi.cli("sh igmp config")) + # + # Add one S,G of state and expect a state-change event report + # indicating the addition of the S,G + # + h1 = self.add_group(self.pg0, IgmpSG("239.1.1.1", ["1.1.1.1"])) + + # search for the corresponding state created in VPP + dump = self.vapi.igmp_dump(self.pg0.sw_if_index) + self.assertEqual(len(dump), 1) + self.assertTrue(find_igmp_state(dump, self.pg0, + "239.1.1.1", "1.1.1.1")) # - # wait for VPP to send out the General Query + # Send a general query (to the all router's address) + # expect VPP to respond with a membership report # - capture = self.pg0.get_capture(1, timeout=61) + p_g = (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="Membership Query", mrcode=100) / + IGMPv3mq(gaddr="0.0.0.0")) - self.verify_general_query(capture[0]) + self.send(self.pg0, p_g) + + capture = self.pg0.get_capture(1, timeout=10) + self.verify_report(capture[0], + [IgmpRecord(h1.sg, "Mode Is Include")]) # - # the state will expire in 10 more seconds + # Group specific query # - self.sleep(10) - self.assertFalse(self.vapi.igmp_dump()) + p_gs = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / + IP(src=self.pg0.remote_ip4, dst='239.1.1.1', tos=0xc0, + options=[IPOption(copy_flag=1, optclass="control", + option="router_alert")]) / + IGMPv3(type="Membership Query", mrcode=100) / + IGMPv3mq(gaddr="239.1.1.1")) - @unittest.skipUnless(running_extended_tests(), "part of extended tests") - def test_igmp_src_exp(self): - """ IGMP per source timer """ + self.send(self.pg0, p_gs) + + capture = self.pg0.get_capture(1, timeout=10) + self.verify_report(capture[0], + [IgmpRecord(h1.sg, "Mode Is Include")]) # - # VPP Acts as a router + # A group and source specific query, with the source matching + # the source VPP has # + p_gs1 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / + IP(src=self.pg0.remote_ip4, dst='239.1.1.1', tos=0xc0, + options=[IPOption(copy_flag=1, optclass="control", + option="router_alert")]) / + IGMPv3(type="Membership Query", mrcode=100) / + IGMPv3mq(gaddr="239.1.1.1", srcaddrs=["1.1.1.1"])) - # 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_gs1) - self.send(self.pg0, p_mr1) + capture = self.pg0.get_capture(1, timeout=10) + self.verify_report(capture[0], + [IgmpRecord(h1.sg, "Mode Is Include")]) - # VPP (router) sends General Query - capture = self.pg0.get_capture(1, timeout=61) + # + # A group and source specific query, with the source NOT matching + # the source VPP has. There should be no response. + # + p_gs2 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / + IP(src=self.pg0.remote_ip4, dst='239.1.1.1', tos=0xc0, + options=[IPOption(copy_flag=1, optclass="control", + option="router_alert")]) / + IGMPv3(type="Membership Query", mrcode=100) / + IGMPv3mq(gaddr="239.1.1.1", srcaddrs=["1.1.1.2"])) - self.verify_general_query(capture[0]) + self.send_and_assert_no_replies(self.pg0, p_gs2, timeout=10) - # 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"])) + # + # A group and source specific query, with the multiple sources + # one of which matches the source VPP has. + # The report should contain only the source VPP has. + # + p_gs3 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / + IP(src=self.pg0.remote_ip4, dst='239.1.1.1', tos=0xc0, + options=[IPOption(copy_flag=1, optclass="control", + option="router_alert")]) / + IGMPv3(type="Membership Query", mrcode=100) / + IGMPv3mq(gaddr="239.1.1.1", + srcaddrs=["1.1.1.1", "1.1.1.2", "1.1.1.3"])) - self.send(self.pg0, p_mr2) + self.send(self.pg0, p_gs3) - # wait for VPP to send general query - capture = self.pg0.get_capture(1, timeout=61) - self.verify_general_query(capture[0]) + capture = self.pg0.get_capture(1, timeout=10) + self.verify_report(capture[0], + [IgmpRecord(h1.sg, "Mode Is Include")]) - # 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"])) + # + # Two source and group specific queires in qucik sucession, the + # first does not have VPPs source the second does. then vice-versa + # + self.send(self.pg0, [p_gs2, p_gs1]) + capture = self.pg0.get_capture(1, timeout=10) + self.verify_report(capture[0], + [IgmpRecord(h1.sg, "Mode Is Include")]) - self.send(self.pg0, p_l) + self.send(self.pg0, [p_gs1, p_gs2]) + capture = self.pg0.get_capture(1, timeout=10) + self.verify_report(capture[0], + [IgmpRecord(h1.sg, "Mode Is Include")]) - # 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) + # + # remove state, expect the report for the removal + # + self.remove_group(h1) + + dump = self.vapi.igmp_dump() + self.assertFalse(dump) # - # host has left all groups, no state left. + # A group with multiple sources # - self.sleep(10) - self.logger.error(self.vapi.cli("sh igmp config")) - self.assertFalse(self.vapi.igmp_dump()) + h2 = self.add_group(self.pg0, + IgmpSG("239.1.1.1", + ["1.1.1.1", "1.1.1.2", "1.1.1.3"])) + + # search for the corresponding state created in VPP + dump = self.vapi.igmp_dump(self.pg0.sw_if_index) + self.assertEqual(len(dump), 3) + for s in h2.sg.saddrs: + self.assertTrue(find_igmp_state(dump, self.pg0, + "239.1.1.1", s)) + # + # Send a general query (to the all router's address) + # expect VPP to respond with a membership report will all sources + # + self.send(self.pg0, p_g) - def test_igmp_query_resp(self): - """ IGMP General Query response """ + capture = self.pg0.get_capture(1, timeout=10) + self.verify_report(capture[0], + [IgmpRecord(h2.sg, "Mode Is Include")]) # - # VPP acting as a host. - # Add a listener in VPP for (10.1.1.1,244.1.1.1) + # Group and source specific query; some present some not # - 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() + p_gs = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / + IP(src=self.pg0.remote_ip4, dst='239.1.1.1', tos=0xc0, + options=[IPOption(copy_flag=1, optclass="control", + option="router_alert")]) / + IGMPv3(type="Membership Query", mrcode=100) / + IGMPv3mq(gaddr="239.1.1.1", + srcaddrs=["1.1.1.1", "1.1.1.2", "1.1.1.4"])) - # verify state exists - self.assertTrue(self.vapi.igmp_dump(self.pg0.sw_if_index)) + self.send(self.pg0, p_gs) + + capture = self.pg0.get_capture(1, timeout=10) + self.verify_report(capture[0], + [IgmpRecord( + IgmpSG('239.1.1.1', ["1.1.1.1", "1.1.1.2"]), + "Mode Is Include")]) # - # Send a general query (from a router) + # add loads more groups # - 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")) + h3 = self.add_group(self.pg0, + IgmpSG("239.1.1.2", + ["2.1.1.1", "2.1.1.2", "2.1.1.3"])) + h4 = self.add_group(self.pg0, + IgmpSG("239.1.1.3", + ["3.1.1.1", "3.1.1.2", "3.1.1.3"])) + h5 = self.add_group(self.pg0, + IgmpSG("239.1.1.4", + ["4.1.1.1", "4.1.1.2", "4.1.1.3"])) + h6 = self.add_group(self.pg0, + IgmpSG("239.1.1.5", + ["5.1.1.1", "5.1.1.2", "5.1.1.3"])) + h7 = self.add_group(self.pg0, + IgmpSG("239.1.1.6", + ["6.1.1.1", "6.1.1.2", + "6.1.1.3", "6.1.1.4", + "6.1.1.5", "6.1.1.6", + "6.1.1.7", "6.1.1.8", + "6.1.1.9", "6.1.1.10", + "6.1.1.11", "6.1.1.12", + "6.1.1.13", "6.1.1.14", + "6.1.1.15", "6.1.1.16"])) - self.send(self.pg0, p) + # + # general query. + # the order the groups come in is not important, so what is + # checked for is what VPP is sending today. + # + self.send(self.pg0, p_g) + + capture = self.pg0.get_capture(1, timeout=10) + + self.verify_report(capture[0], + [IgmpRecord(h3.sg, "Mode Is Include"), + IgmpRecord(h2.sg, "Mode Is Include"), + IgmpRecord(h6.sg, "Mode Is Include"), + IgmpRecord(h4.sg, "Mode Is Include"), + IgmpRecord(h5.sg, "Mode Is Include"), + IgmpRecord(h7.sg, "Mode Is Include")]) # - # expect VPP to respond with a membership report for the - # (10.1.1.1, 224.1.1.1) state + # modify a group to add and remove some sources # + h7.sg = IgmpSG("239.1.1.6", + ["6.1.1.1", "6.1.1.2", + "6.1.1.5", "6.1.1.6", + "6.1.1.7", "6.1.1.8", + "6.1.1.9", "6.1.1.10", + "6.1.1.11", "6.1.1.12", + "6.1.1.13", "6.1.1.14", + "6.1.1.15", "6.1.1.16", + "6.1.1.17", "6.1.1.18"]) + + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + h7.add_vpp_config() + capture = self.pg0.get_capture(1, timeout=10) + self.verify_report(capture[0], + [IgmpRecord(IgmpSG("239.1.1.6", + ["6.1.1.17", "6.1.1.18"]), + "Allow New Sources"), + IgmpRecord(IgmpSG("239.1.1.6", + ["6.1.1.3", "6.1.1.4"]), + "Block Old Sources")]) - 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") + # + # add an additional groups with many sources so that each group + # consumes the link MTU. We should therefore see multiple state + # state reports when queried. + # + self.vapi.sw_interface_set_mtu(self.pg0.sw_if_index, [560, 0, 0, 0]) + + src_list = [] + for i in range(128): + src_list.append("10.1.1.%d" % i) + + h8 = self.add_group(self.pg0, + IgmpSG("238.1.1.1", src_list)) + h9 = self.add_group(self.pg0, + IgmpSG("238.1.1.2", src_list)) + + self.send(self.pg0, p_g) + + capture = self.pg0.get_capture(4, timeout=10) + + self.verify_report(capture[0], + [IgmpRecord(h3.sg, "Mode Is Include"), + IgmpRecord(h2.sg, "Mode Is Include"), + IgmpRecord(h6.sg, "Mode Is Include"), + IgmpRecord(h4.sg, "Mode Is Include"), + IgmpRecord(h5.sg, "Mode Is Include")]) + self.verify_report(capture[1], + [IgmpRecord(h8.sg, "Mode Is Include")]) + self.verify_report(capture[2], + [IgmpRecord(h7.sg, "Mode Is Include")]) + self.verify_report(capture[3], + [IgmpRecord(h9.sg, "Mode Is Include")]) - def test_igmp_listen(self): - """ IGMP listen (S,G)s """ + # + # drop the MTU further (so a 128 sized group won't fit) + # + self.vapi.sw_interface_set_mtu(self.pg0.sw_if_index, [512, 0, 0, 0]) + + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + h10 = VppHostState(self, + IGMP_FILTER.INCLUDE, + self.pg0.sw_if_index, + IgmpSG("238.1.1.3", src_list)) + h10.add_vpp_config() + + capture = self.pg0.get_capture(2, timeout=10) # - # VPP acts as a host - # Add IGMP group state to multiple interfaces and validate its - # presence + # remove state, expect the report for the removal + # the dump should be empty # - 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"))) + self.vapi.sw_interface_set_mtu(self.pg0.sw_if_index, [600, 0, 0, 0]) + self.remove_group(h8) + self.remove_group(h9) + self.remove_group(h2) + self.remove_group(h3) + self.remove_group(h4) + self.remove_group(h5) + self.remove_group(h6) + self.remove_group(h7) + self.remove_group(h10) - 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() + self.logger.info(self.vapi.cli("sh igmp config")) + self.assertFalse(self.vapi.igmp_dump()) - dump = self.vapi.igmp_dump() - self.assertFalse(dump) + # + # TODO + # ADD STATE ON MORE INTERFACES + # + + self.vapi.igmp_enable_disable(self.pg0.sw_if_index, + 0, + IGMP_MODE.HOST) + + def test_igmp_router(self): + """ IGMP Router Functions """ + + # + # Drop reports when not enabled + # + p_j = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / + IP(src=self.pg0.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="Allow New Sources", + maddr="239.1.1.1", srcaddrs=["10.1.1.1", "10.1.1.2"])) + p_l = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / + IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0, + options=[IPOption(copy_flag=1, optclass="control", + option="router_alert")]) / + IGMPv3(type="Version 3 Membership Report") / + IGMPv3mr(numgrp=1) / + IGMPv3gr(rtype="Block Old Sources", + maddr="239.1.1.1", srcaddrs=["10.1.1.1", "10.1.1.2"])) + + self.send(self.pg0, p_j) + self.assertFalse(self.vapi.igmp_dump()) + + # + # drop the default timer values so these tests execute in a + # reasonable time frame + # + self.vapi.cli("test igmp timers query 1 src 3 leave 1") + + # + # enable router functions on the interface + # + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + self.vapi.igmp_enable_disable(self.pg0.sw_if_index, + 1, + IGMP_MODE.ROUTER) + self.vapi.want_igmp_events(1) + + # + # wait for router to send general query + # + for ii in range(3): + capture = self.pg0.get_capture(1, timeout=2) + self.verify_general_query(capture[0]) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + # + # re-send the report. VPP should now hold state for the new group + # VPP sends a notification that a new group has been joined + # + self.send(self.pg0, p_j) + + self.assertTrue(wait_for_igmp_event(self, 1, self.pg0, + "239.1.1.1", "10.1.1.1", 1)) + self.assertTrue(wait_for_igmp_event(self, 1, self.pg0, + "239.1.1.1", "10.1.1.2", 1)) + dump = self.vapi.igmp_dump(self.pg0.sw_if_index) + self.assertEqual(len(dump), 2) + self.assertTrue(find_igmp_state(dump, self.pg0, + "239.1.1.1", "10.1.1.1")) + self.assertTrue(find_igmp_state(dump, self.pg0, + "239.1.1.1", "10.1.1.2")) + + # + # wait for the per-source timer to expire + # the state should be reaped + # VPP sends a notification that the group has been left + # + self.assertTrue(wait_for_igmp_event(self, 4, self.pg0, + "239.1.1.1", "10.1.1.1", 0)) + self.assertTrue(wait_for_igmp_event(self, 1, self.pg0, + "239.1.1.1", "10.1.1.2", 0)) + self.assertFalse(self.vapi.igmp_dump()) + + # + # resend the join. wait for two queries and then send a current-state + # record to include all sources. this should reset the exiry time + # on the sources and thus they will still be present in 2 seconds time. + # If the source timer was not refreshed, then the state would have + # expired in 3 seconds. + # + self.send(self.pg0, p_j) + self.assertTrue(wait_for_igmp_event(self, 1, self.pg0, + "239.1.1.1", "10.1.1.1", 1)) + self.assertTrue(wait_for_igmp_event(self, 1, self.pg0, + "239.1.1.1", "10.1.1.2", 1)) + dump = self.vapi.igmp_dump(self.pg0.sw_if_index) + self.assertEqual(len(dump), 2) + + capture = self.pg0.get_capture(2, timeout=3) + self.verify_general_query(capture[0]) + self.verify_general_query(capture[1]) + + p_cs = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / + IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0, + options=[IPOption(copy_flag=1, optclass="control", + option="router_alert")]) / + IGMPv3(type="Version 3 Membership Report") / + IGMPv3mr(numgrp=1) / + IGMPv3gr(rtype="Mode Is Include", + maddr="239.1.1.1", srcaddrs=["10.1.1.1", "10.1.1.2"])) + + self.send(self.pg0, p_cs) + + self.sleep(2) + dump = self.vapi.igmp_dump(self.pg0.sw_if_index) + self.assertEqual(len(dump), 2) + self.assertTrue(find_igmp_state(dump, self.pg0, + "239.1.1.1", "10.1.1.1")) + self.assertTrue(find_igmp_state(dump, self.pg0, + "239.1.1.1", "10.1.1.2")) + + # + # wait for the per-source timer to expire + # the state should be reaped + # + self.assertTrue(wait_for_igmp_event(self, 4, self.pg0, + "239.1.1.1", "10.1.1.1", 0)) + self.assertTrue(wait_for_igmp_event(self, 1, self.pg0, + "239.1.1.1", "10.1.1.2", 0)) + self.assertFalse(self.vapi.igmp_dump()) + + # + # resend the join, then a leave. Router sends a gruop+source + # specific query containing both sources + # + self.send(self.pg0, p_j) + + self.assertTrue(wait_for_igmp_event(self, 1, self.pg0, + "239.1.1.1", "10.1.1.1", 1)) + self.assertTrue(wait_for_igmp_event(self, 1, self.pg0, + "239.1.1.1", "10.1.1.2", 1)) + dump = self.vapi.igmp_dump(self.pg0.sw_if_index) + self.assertEqual(len(dump), 2) + + self.send(self.pg0, p_l) + capture = self.pg0.get_capture(1, timeout=3) + self.verify_group_query(capture[0], "239.1.1.1", + ["10.1.1.1", "10.1.1.2"]) + + # + # the group specific query drops the timeout to leave (=1) seconds + # + self.assertTrue(wait_for_igmp_event(self, 2, self.pg0, + "239.1.1.1", "10.1.1.1", 0)) + self.assertTrue(wait_for_igmp_event(self, 1, self.pg0, + "239.1.1.1", "10.1.1.2", 0)) + self.assertFalse(self.vapi.igmp_dump()) + self.assertFalse(self.vapi.igmp_dump()) + + # + # disable router config + # + self.vapi.igmp_enable_disable(self.pg0.sw_if_index, + 0, + IGMP_MODE.ROUTER) if __name__ == '__main__': diff --git a/test/vpp_igmp.py b/test/vpp_igmp.py index d1a308878c5..8f0191644bd 100644 --- a/test/vpp_igmp.py +++ b/test/vpp_igmp.py @@ -1,39 +1,80 @@ from vpp_object import VppObject +import socket + + +class IGMP_MODE: + ROUTER = 0 + HOST = 1 + + +class IGMP_FILTER: + INCLUDE = 1 + EXCLUDE = 0 + + +def find_igmp_state(states, itf, gaddr, saddr): + for s in states: + if s.sw_if_index == itf.sw_if_index and \ + s.gaddr.address == socket.inet_pton(socket.AF_INET, gaddr) and \ + s.saddr.address == socket.inet_pton(socket.AF_INET, saddr): + return True + return False + + +def wait_for_igmp_event(test, timeout, itf, gaddr, saddr, ff): + ev = test.vapi.wait_for_event(timeout, "igmp_event") + if ev.sw_if_index == itf.sw_if_index and \ + ev.gaddr.address == socket.inet_pton(socket.AF_INET, gaddr) and \ + ev.saddr.address == socket.inet_pton(socket.AF_INET, saddr) and \ + ev.filter == ff: + return True + return False class IgmpSG(): - def __init__(self, saddr, gaddr): - self.saddr = saddr + def __init__(self, gaddr, saddrs): self.gaddr = gaddr + self.gaddr_p = socket.inet_pton(socket.AF_INET, gaddr) + self.saddrs = saddrs + self.saddrs_p = [] + self.saddrs_encoded = [] + for s in saddrs: + ss = socket.inet_pton(socket.AF_INET, s) + self.saddrs_p.append(ss) + self.saddrs_encoded.append({'address': ss}) + +class IgmpRecord(): + def __init__(self, sg, type): + self.sg = sg + self.type = type -class VppIgmpConfig(VppObject): - def __init__(self, test, sw_if_index, sg=None): + +class VppHostState(VppObject): + def __init__(self, test, filter, sw_if_index, sg): 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) + self.filter = filter + self.sg = 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) + self._test.vapi.igmp_listen( + self.filter, self.sw_if_index, + self.sg.saddrs_encoded, self.sg.gaddr_p) def remove_vpp_config(self): - self._test.vapi.igmp_clear_interface(self.sw_if_index) + self._test.vapi.igmp_listen( + self.filter, + self.sw_if_index, + [], + self.sg.gaddr_p) def __str__(self): return self.object_id() def object_id(self): - return "%s:%d" % (self.sg_list, self.sw_if_index) + return "%s:%d" % (self.sg, self.sw_if_index) def query_vpp_config(self): return self._test.vapi.igmp_dump() diff --git a/test/vpp_ip_route.py b/test/vpp_ip_route.py index 17a42fec706..9e5c53184e3 100644 --- a/test/vpp_ip_route.py +++ b/test/vpp_ip_route.py @@ -60,6 +60,25 @@ def find_route(test, ip_addr, len, table_id=0, inet=AF_INET): return False +def find_mroute(test, grp_addr, src_addr, grp_addr_len, + table_id=0, inet=AF_INET): + if inet == AF_INET: + s = 4 + routes = test.vapi.ip_mfib_dump() + else: + s = 16 + routes = test.vapi.ip6_mfib_dump() + gaddr = inet_pton(inet, grp_addr) + saddr = inet_pton(inet, src_addr) + for e in routes: + if gaddr == e.grp_address[:s] \ + and grp_addr_len == e.address_length \ + and saddr == e.src_address[:s] \ + and table_id == e.table_id: + return True + return False + + class VppIpTable(VppObject): def __init__(self, @@ -324,6 +343,8 @@ class VppIpMRoute(VppObject): self.is_ip6 = is_ip6 self.rpf_id = rpf_id + self.grp_addr_p = grp_addr + self.src_addr_p = src_addr if is_ip6: self.grp_addr = inet_pton(AF_INET6, grp_addr) self.src_addr = inet_pton(AF_INET6, src_addr) @@ -406,17 +427,12 @@ class VppIpMRoute(VppObject): is_ipv6=self.is_ip6) def query_vpp_config(self): - if self.is_ip6: - dump = self._test.vapi.ip6_mfib_dump() - else: - dump = self._test.vapi.ip_mfib_dump() - for e in dump: - if self.grp_addr == e.grp_address \ - and self.grp_addr_len == e.address_length \ - and self.src_addr == e.src_address \ - and self.table_id == e.table_id: - return True - return False + return find_mroute(self._test, + self.grp_addr_p, + self.src_addr_p, + self.grp_addr_len, + self.table_id, + inet=AF_INET6 if self.is_ip6 == 1 else AF_INET) def __str__(self): return self.object_id() diff --git a/test/vpp_papi_provider.py b/test/vpp_papi_provider.py index 2d4b44764f3..5383b07fc4c 100644 --- a/test/vpp_papi_provider.py +++ b/test/vpp_papi_provider.py @@ -3474,7 +3474,14 @@ class VppPapiProvider(object): 'input_source': input_source, 'enable': enable}) - def igmp_listen(self, enable, sw_if_index, saddr, gaddr): + def igmp_enable_disable(self, sw_if_index, enable, host): + """ Enable/disable IGMP on a given interface """ + return self.api(self.papi.igmp_enable_disable, + {'enable': enable, + 'mode': host, + 'sw_if_index': sw_if_index}) + + def igmp_listen(self, filter, sw_if_index, saddrs, gaddr): """ Listen for new (S,G) on specified interface :param enable: add/del @@ -3483,20 +3490,26 @@ class VppPapiProvider(object): :param gaddr: group ip4 addr """ return self.api(self.papi.igmp_listen, - {'enable': enable, - 'sw_if_index': sw_if_index, - 'saddr': saddr, - 'gaddr': gaddr}) + { + 'group': + { + 'filter': filter, + 'sw_if_index': sw_if_index, + 'n_srcs': len(saddrs), + 'saddrs': saddrs, + 'gaddr': + { + 'address': 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}) + sw_if_index = 0xffffffff + return self.api(self.papi.igmp_dump, + {'sw_if_index': sw_if_index}) def igmp_clear_interface(self, sw_if_index): """ Remove all (S,G)s from specified interface -- 2.16.6