/* * ip/ip6_neighbor.c: IP6 neighbor handling * * Copyright (c) 2010 Cisco and/or its affiliates. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include /** * @file * @brief IPv6 Neighbor Adjacency and Neighbor Discovery. * * The files contains the API and CLI code for managing IPv6 neighbor * adjacency tables and neighbor discovery logic. */ #define DEF_MAX_RADV_INTERVAL 200 #define DEF_MIN_RADV_INTERVAL .75 * DEF_MAX_RADV_INTERVAL typedef struct ip6_nd_t_ { /* local information */ u32 sw_if_index; /* stats */ u32 n_solicitations_rcvd; u32 n_solicitations_dropped; } ip6_nd_t; static ip6_link_delegate_id_t ip6_nd_delegate_id; static ip6_nd_t *ip6_nd_pool; static_always_inline uword icmp6_neighbor_solicitation_or_advertisement (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * frame, uword is_solicitation) { ip6_main_t *im = &ip6_main; uword n_packets = frame->n_vectors; u32 *from, *to_next; u32 n_left_from, n_left_to_next, next_index, n_advertisements_sent; icmp6_neighbor_discovery_option_type_t option_type; vlib_node_runtime_t *error_node = vlib_node_get_runtime (vm, ip6_icmp_input_node.index); from = vlib_frame_vector_args (frame); n_left_from = n_packets; next_index = node->cached_next_index; if (node->flags & VLIB_NODE_FLAG_TRACE) vlib_trace_frame_buffers_only (vm, node, from, frame->n_vectors, /* stride */ 1, sizeof (icmp6_input_trace_t)); option_type = (is_solicitation ? ICMP6_NEIGHBOR_DISCOVERY_OPTION_source_link_layer_address : ICMP6_NEIGHBOR_DISCOVERY_OPTION_target_link_layer_address); n_advertisements_sent = 0; while (n_left_from > 0) { vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next); while (n_left_from > 0 && n_left_to_next > 0) { vlib_buffer_t *p0; ip6_header_t *ip0; icmp6_neighbor_solicitation_or_advertisement_header_t *h0; icmp6_neighbor_discovery_ethernet_link_layer_address_option_t *o0; u32 bi0, options_len0, sw_if_index0, next0, error0; u32 ip6_sadd_link_local, ip6_sadd_unspecified; ip_neighbor_counter_type_t c_type; int is_rewrite0; u32 ni0; bi0 = to_next[0] = from[0]; from += 1; to_next += 1; n_left_from -= 1; n_left_to_next -= 1; p0 = vlib_get_buffer (vm, bi0); ip0 = vlib_buffer_get_current (p0); h0 = ip6_next_header (ip0); options_len0 = clib_net_to_host_u16 (ip0->payload_length) - sizeof (h0[0]); error0 = ICMP6_ERROR_NONE; sw_if_index0 = vnet_buffer (p0)->sw_if_index[VLIB_RX]; ip6_sadd_link_local = ip6_address_is_link_local_unicast (&ip0->src_address); ip6_sadd_unspecified = ip6_address_is_unspecified (&ip0->src_address); /* Check that source address is unspecified, link-local or else on-link. */ if (!ip6_sadd_unspecified && !ip6_sadd_link_local) { u32 src_adj_index0 = ip6_src_lookup_for_packet (im, p0, ip0); if (ADJ_INDEX_INVALID != src_adj_index0) { ip_adjacency_t *adj0 = adj_get (src_adj_index0); /* Allow all realistic-looking rewrite adjacencies to pass */ ni0 = adj0->lookup_next_index; is_rewrite0 = (ni0 >= IP_LOOKUP_NEXT_ARP) && (ni0 < IP6_LOOKUP_N_NEXT); error0 = ((adj0->rewrite_header.sw_if_index != sw_if_index0 || !is_rewrite0) ? ICMP6_ERROR_NEIGHBOR_SOLICITATION_SOURCE_NOT_ON_LINK : error0); } else { error0 = ICMP6_ERROR_NEIGHBOR_SOLICITATION_SOURCE_NOT_ON_LINK; } } o0 = (void *) (h0 + 1); o0 = ((options_len0 == 8 && o0->header.type == option_type && o0->header.n_data_u64s == 1) ? o0 : 0); /* If src address unspecified or link local, donot learn neighbor MAC */ if (PREDICT_TRUE (error0 == ICMP6_ERROR_NONE && o0 != 0 && !ip6_sadd_unspecified)) { ip_neighbor_learn_t learn = { .sw_if_index = sw_if_index0, .ip = { .version = AF_IP6, .ip.ip6 = (is_solicitation ? ip0->src_address : h0->target_address), } }; memcpy (&learn.mac, o0->ethernet_address, sizeof (learn.mac)); ip_neighbor_learn_dp (&learn); } if (is_solicitation && error0 == ICMP6_ERROR_NONE) { /* Check that target address is local to this router. */ fib_node_index_t fei; u32 fib_index; fib_index = ip6_fib_table_get_index_for_sw_if_index (sw_if_index0); if (~0 == fib_index) { error0 = ICMP6_ERROR_NEIGHBOR_SOLICITATION_SOURCE_UNKNOWN; } else { if (ip6_address_is_link_local_unicast (&h0->target_address)) { fei = ip6_fib_table_lookup_exact_match (ip6_ll_fib_get (sw_if_index0), &h0->target_address, 128); } else { fei = ip6_fib_table_lookup_exact_match (fib_index, &h0->target_address, 128); } if (FIB_NODE_INDEX_INVALID == fei) { /* The target address is not in the FIB */ error0 = ICMP6_ERROR_NEIGHBOR_SOLICITATION_SOURCE_UNKNOWN; } else { if (FIB_ENTRY_FLAG_LOCAL & fib_entry_get_flags_for_source (fei, FIB_SOURCE_INTERFACE)) { /* It's an address that belongs to one of our interfaces * that's good. */ } else if (FIB_ENTRY_FLAG_LOCAL & fib_entry_get_flags_for_source ( fei, FIB_SOURCE_IP6_ND)) { /* It's one of our link local addresses * that's good. */ } else if (fib_entry_is_sourced (fei, FIB_SOURCE_IP6_ND_PROXY)) { /* The address was added by IPv6 Proxy ND config. * We should only respond to these if the NS arrived on * the link that has a matching covering prefix */ } else { error0 = ICMP6_ERROR_NEIGHBOR_SOLICITATION_SOURCE_UNKNOWN; } } } } if (is_solicitation) { next0 = (error0 != ICMP6_ERROR_NONE ? ICMP6_NEIGHBOR_SOLICITATION_NEXT_DROP : ICMP6_NEIGHBOR_SOLICITATION_NEXT_REPLY); c_type = IP_NEIGHBOR_CTR_REQUEST; } else { next0 = 0; error0 = error0 == ICMP6_ERROR_NONE ? ICMP6_ERROR_NEIGHBOR_ADVERTISEMENTS_RX : error0; c_type = IP_NEIGHBOR_CTR_REPLY; } vlib_increment_simple_counter ( &ip_neighbor_counters[AF_IP6].ipnc[VLIB_RX][c_type], vm->thread_index, sw_if_index0, 1); if (is_solicitation && error0 == ICMP6_ERROR_NONE) { icmp6_send_neighbor_advertisement (vm, p0, ip0, h0, o0, sw_if_index0); n_advertisements_sent++; } p0->error = error_node->errors[error0]; vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next, n_left_to_next, bi0, next0); } vlib_put_next_frame (vm, node, next_index, n_left_to_next); } /* Account for advertisements sent. */ vlib_error_count (vm, error_node->node_index, ICMP6_ERROR_NEIGHBOR_ADVERTISEMENTS_TX, n_advertisements_sent); return frame->n_vectors; } static const ethernet_interface_t * ip6_nd_get_eth_itf (u32 sw_if_index) { const vnet_sw_interface_t *sw; /* lookup radv container - ethernet interfaces only */ sw = vnet_get_sup_sw_interface (vnet_get_main (), sw_if_index); if (sw->type == VNET_SW_INTERFACE_TYPE_HARDWARE) return (ethernet_get_interface (ðernet_main, sw->hw_if_index)); return (NULL); } /** * @brief called when IP6 is enabled on a link. * create and initialize router advertisement parameters with default * values for this intfc */ static void ip6_nd_link_enable (u32 sw_if_index) { const ethernet_interface_t *eth; ip6_nd_t *ind; eth = ip6_nd_get_eth_itf (sw_if_index); if (NULL == eth) return; ASSERT (INDEX_INVALID == ip6_link_delegate_get (sw_if_index, ip6_nd_delegate_id)); pool_get_zero (ip6_nd_pool, ind); ind->sw_if_index = sw_if_index; ip6_link_delegate_update (sw_if_index, ip6_nd_delegate_id, ind - ip6_nd_pool); } static void ip6_nd_delegate_disable (index_t indi) { ip6_nd_t *ind; ind = pool_elt_at_index (ip6_nd_pool, indi); pool_put (ip6_nd_pool, ind); } static uword icmp6_neighbor_solicitation (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * frame) { return icmp6_neighbor_solicitation_or_advertisement (vm, node, frame, /* is_solicitation */ 1); } static uword icmp6_neighbor_advertisement (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * frame) { return icmp6_neighbor_solicitation_or_advertisement (vm, node, frame, /* is_solicitation */ 0); } VLIB_REGISTER_NODE (ip6_icmp_neighbor_solicitation_node,static) = { .function = icmp6_neighbor_solicitation, .name = "icmp6-neighbor-solicitation", .vector_size = sizeof (u32), .format_trace = format_icmp6_input_trace, .n_next_nodes = ICMP6_NEIGHBOR_SOLICITATION_N_NEXT, .next_nodes = { [ICMP6_NEIGHBOR_SOLICITATION_NEXT_DROP] = "ip6-drop", [ICMP6_NEIGHBOR_SOLICITATION_NEXT_REPLY] = "interface-output", }, }; VLIB_REGISTER_NODE (ip6_icmp_neighbor_advertisement_node,static) = { .function = icmp6_neighbor_advertisement, .name = "icmp6-neighbor-advertisement", .vector_size = sizeof (u32), .format_trace = format_icmp6_input_trace, .n_next_nodes = 1, .next_nodes = { [0] = "ip6-punt", }, }; static u8 * format_ip6_nd (u8 * s, va_list * args) { CLIB_UNUSED (index_t indi) = va_arg (*args, index_t); u32 indent = va_arg (*args, u32); s = format (s, "%UNeighbor Discovery: enabled\n", format_white_space, indent); s = format (s, "%UICMP redirects are disabled\n", format_white_space, indent + 2); s = format (s, "%UICMP unreachables are not sent\n", format_white_space, indent + 2); s = format (s, "%UND DAD is disabled\n", format_white_space, indent + 2); //s = format (s, "%UND reachable time is %d milliseconds\n",); return (s); } /** * VFT to act as an implementation of a neighbour protocol */ const static ip_neighbor_vft_t ip6_nd_impl_vft = { .inv_proxy6_add = ip6_nd_proxy_add, .inv_proxy6_del = ip6_nd_proxy_del, }; /** * VFT for registering as a delegate to an IP6 link */ const static ip6_link_delegate_vft_t ip6_nd_delegate_vft = { .ildv_disable = ip6_nd_delegate_disable, .ildv_enable = ip6_nd_link_enable, .ildv_format = format_ip6_nd, }; static clib_error_t * ip6_nd_init (vlib_main_t * vm) { icmp6_register_type (vm, ICMP6_neighbor_solicitation, ip6_icmp_neighbor_solicitation_node.index); icmp6_register_type (vm, ICMP6_neighbor_advertisement, ip6_icmp_neighbor_advertisement_node.index); ip_neighbor_register (AF_IP6, &ip6_nd_impl_vft); ip6_nd_delegate_id = ip6_link_delegate_register (&ip6_nd_delegate_vft); return 0; } VLIB_INIT_FUNCTION (ip6_nd_init) = { .runs_after = VLIB_INITS("icmp6_init"), }; /* * fd.io coding-style-patch-verification: ON * * Local Variables: * eval: (c-set-style "gnu") * End: */