diff options
author | Neale Ranns <nranns@cisco.com> | 2018-06-07 23:48:20 -0700 |
---|---|---|
committer | Florin Coras <florin.coras@gmail.com> | 2018-07-09 21:10:53 +0000 |
commit | 947ea6222dad1ef04595c34273e9231395aef443 (patch) | |
tree | 8990854b2901ff8cc2241b9abfc99b0b4b54d517 /src/plugins/igmp/igmp.c | |
parent | dd47ecadcf63772a6037a1bb3715772d80e87f51 (diff) |
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 <nranns@cisco.com>
Diffstat (limited to 'src/plugins/igmp/igmp.c')
-rw-r--r-- | src/plugins/igmp/igmp.c | 1255 |
1 files changed, 347 insertions, 908 deletions
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 <vnet/mfib/mfib_table.h> #include <igmp/igmp.h> +#include <igmp/igmp_format.h> +#include <igmp/igmp_pkt.h> #include <limits.h> #include <float.h> 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 <packet size|number of (S,G)s>? - * - 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* */ |