From cbe25aab3be72154f2c706c39eeba6a77f34450f Mon Sep 17 00:00:00 2001 From: Neale Ranns Date: Mon, 30 Sep 2019 10:53:31 +0000 Subject: ip: Protocol Independent IP Neighbors Type: feature - ip-neighbour: generic neighbour handling; APIs, DBs, event handling, aging - arp: ARP protocol implementation - ip6-nd; IPv6 neighbor discovery implementation; separate ND, MLD, RA - ip6-link; manage link-local addresses - l2-arp-term; events separated from IP neighbours, since they are not the same. vnet retains just enough education to perform ND/ARP packet construction. arp and ip6-nd to be moved to plugins soon. Change-Id: I88dedd0006b299344f4c7024a0aa5baa6b9a8bbe Signed-off-by: Neale Ranns --- src/vnet/ip6-nd/ip6_nd.c | 482 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 482 insertions(+) create mode 100644 src/vnet/ip6-nd/ip6_nd.c (limited to 'src/vnet/ip6-nd/ip6_nd.c') diff --git a/src/vnet/ip6-nd/ip6_nd.c b/src/vnet/ip6-nd/ip6_nd.c new file mode 100644 index 00000000000..eb89eb817f6 --- /dev/null +++ b/src/vnet/ip6-nd/ip6_nd.c @@ -0,0 +1,482 @@ +/* + * 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 + +/** + * @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; + + +typedef enum +{ + ICMP6_NEIGHBOR_SOLICITATION_NEXT_DROP, + ICMP6_NEIGHBOR_SOLICITATION_NEXT_REPLY, + ICMP6_NEIGHBOR_SOLICITATION_N_NEXT, +} icmp6_neighbor_solicitation_or_advertisement_next_t; + +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) +{ + vnet_main_t *vnm = vnet_get_main (); + 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); + int bogus_length; + + 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; + 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, + .type = IP46_TYPE_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_is_sourced + (fei, FIB_SOURCE_IP6_ND_PROXY) || + fib_entry_is_sourced (fei, FIB_SOURCE_IP6_ND)) + { + /* 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); + else + { + next0 = 0; + error0 = error0 == ICMP6_ERROR_NONE ? + ICMP6_ERROR_NEIGHBOR_ADVERTISEMENTS_RX : error0; + } + + if (is_solicitation && error0 == ICMP6_ERROR_NONE) + { + vnet_sw_interface_t *sw_if0; + ethernet_interface_t *eth_if0; + ethernet_header_t *eth0; + + /* dst address is either source address or the all-nodes mcast addr */ + if (!ip6_sadd_unspecified) + ip0->dst_address = ip0->src_address; + else + ip6_set_reserved_multicast_address (&ip0->dst_address, + IP6_MULTICAST_SCOPE_link_local, + IP6_MULTICAST_GROUP_ID_all_hosts); + + ip0->src_address = h0->target_address; + ip0->hop_limit = 255; + h0->icmp.type = ICMP6_neighbor_advertisement; + + sw_if0 = vnet_get_sup_sw_interface (vnm, sw_if_index0); + ASSERT (sw_if0->type == VNET_SW_INTERFACE_TYPE_HARDWARE); + eth_if0 = + ethernet_get_interface (ðernet_main, sw_if0->hw_if_index); + if (eth_if0 && o0) + { + clib_memcpy (o0->ethernet_address, eth_if0->address, 6); + o0->header.type = + ICMP6_NEIGHBOR_DISCOVERY_OPTION_target_link_layer_address; + } + + h0->advertisement_flags = clib_host_to_net_u32 + (ICMP6_NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED + | ICMP6_NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE); + + h0->icmp.checksum = 0; + h0->icmp.checksum = + ip6_tcp_udp_icmp_compute_checksum (vm, p0, ip0, + &bogus_length); + ASSERT (bogus_length == 0); + + /* Reuse current MAC header, copy SMAC to DMAC and + * interface MAC to SMAC */ + vlib_buffer_advance (p0, -ethernet_buffer_header_size (p0)); + eth0 = vlib_buffer_get_current (p0); + clib_memcpy (eth0->dst_address, eth0->src_address, 6); + if (eth_if0) + clib_memcpy (eth0->src_address, eth_if0->address, 6); + + /* Setup input and output sw_if_index for packet */ + ASSERT (vnet_buffer (p0)->sw_if_index[VLIB_RX] == sw_if_index0); + vnet_buffer (p0)->sw_if_index[VLIB_TX] = sw_if_index0; + vnet_buffer (p0)->sw_if_index[VLIB_RX] = + vnet_main.local_interface_sw_if_index; + + 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); +} + +/* *INDENT-OFF* */ +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-drop", + }, +}; +/* *INDENT-ON* */ + +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 (IP46_TYPE_IP6, &ip6_nd_impl_vft); + + ip6_nd_delegate_id = ip6_link_delegate_register (&ip6_nd_delegate_vft); + + return 0; +} + +/* *INDENT-OFF* */ +VLIB_INIT_FUNCTION (ip6_nd_init) = +{ + .runs_after = VLIB_INITS("icmp6_init"), +}; +/* *INDENT-ON* */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ -- cgit 1.2.3-korg