diff options
Diffstat (limited to 'src/vnet/ip-neighbor')
-rw-r--r-- | src/vnet/ip-neighbor/ip4_neighbor.c | 319 | ||||
-rw-r--r-- | src/vnet/ip-neighbor/ip4_neighbor.h | 85 | ||||
-rw-r--r-- | src/vnet/ip-neighbor/ip6_neighbor.c | 338 | ||||
-rw-r--r-- | src/vnet/ip-neighbor/ip6_neighbor.h | 112 | ||||
-rw-r--r-- | src/vnet/ip-neighbor/ip_neighbor.api | 168 | ||||
-rw-r--r-- | src/vnet/ip-neighbor/ip_neighbor.c | 1664 | ||||
-rw-r--r-- | src/vnet/ip-neighbor/ip_neighbor.h | 124 | ||||
-rw-r--r-- | src/vnet/ip-neighbor/ip_neighbor_api.c | 298 | ||||
-rw-r--r-- | src/vnet/ip-neighbor/ip_neighbor_dp.c | 39 | ||||
-rw-r--r-- | src/vnet/ip-neighbor/ip_neighbor_dp.h | 38 | ||||
-rw-r--r-- | src/vnet/ip-neighbor/ip_neighbor_types.c | 83 | ||||
-rw-r--r-- | src/vnet/ip-neighbor/ip_neighbor_types.h | 127 | ||||
-rw-r--r-- | src/vnet/ip-neighbor/ip_neighbor_watch.c | 283 | ||||
-rw-r--r-- | src/vnet/ip-neighbor/ip_neighbor_watch.h | 42 |
14 files changed, 3720 insertions, 0 deletions
diff --git a/src/vnet/ip-neighbor/ip4_neighbor.c b/src/vnet/ip-neighbor/ip4_neighbor.c new file mode 100644 index 00000000000..2a9e2675a78 --- /dev/null +++ b/src/vnet/ip-neighbor/ip4_neighbor.c @@ -0,0 +1,319 @@ +/* + * Copyright (c) 2015 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. + */ +/* + * ip/ip4_forward.c: IP v4 forwarding + * + * Copyright (c) 2008 Eliot Dresselhaus + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include <vnet/ip-neighbor/ip4_neighbor.h> +#include <vnet/ethernet/ethernet.h> + +void +ip4_neighbor_probe_dst (const ip_adjacency_t * adj, const ip4_address_t * dst) +{ + ip_interface_address_t *ia; + ip4_address_t *src; + + src = ip4_interface_address_matching_destination + (&ip4_main, + &adj->sub_type.nbr.next_hop.ip4, adj->rewrite_header.sw_if_index, &ia); + if (!src) + return; + + ip4_neighbor_probe (vlib_get_main (), vnet_get_main (), adj, src, dst); +} + +void +ip4_neighbor_advertise (vlib_main_t * vm, + vnet_main_t * vnm, + u32 sw_if_index, const ip4_address_t * addr) +{ + vnet_hw_interface_t *hi = vnet_get_sup_hw_interface (vnm, sw_if_index); + ip4_main_t *i4m = &ip4_main; + u8 *rewrite, rewrite_len; + + if (NULL == addr) + { + ip4_main_t *i4m = &ip4_main; + addr = ip4_interface_first_address (i4m, sw_if_index, 0); + } + + if (addr) + { + clib_warning ("Sending GARP for IP4 address %U on sw_if_idex %d", + format_ip4_address, addr, sw_if_index); + + /* Form GARP packet for output - Gratuitous ARP is an ARP request packet + where the interface IP/MAC pair is used for both source and request + MAC/IP pairs in the request */ + u32 bi = 0; + ethernet_arp_header_t *h = vlib_packet_template_get_packet + (vm, &i4m->ip4_arp_request_packet_template, &bi); + + if (!h) + return; + + mac_address_from_bytes (&h->ip4_over_ethernet[0].mac, hi->hw_address); + mac_address_from_bytes (&h->ip4_over_ethernet[1].mac, hi->hw_address); + h->ip4_over_ethernet[0].ip4 = addr[0]; + h->ip4_over_ethernet[1].ip4 = addr[0]; + + /* Setup MAC header with ARP Etype and broadcast DMAC */ + vlib_buffer_t *b = vlib_get_buffer (vm, bi); + rewrite = + ethernet_build_rewrite (vnm, sw_if_index, VNET_LINK_ARP, + VNET_REWRITE_FOR_SW_INTERFACE_ADDRESS_BROADCAST); + rewrite_len = vec_len (rewrite); + vlib_buffer_advance (b, -rewrite_len); + ethernet_header_t *e = vlib_buffer_get_current (b); + clib_memcpy_fast (e->dst_address, rewrite, rewrite_len); + vec_free (rewrite); + + /* Send GARP packet out the specified interface */ + vnet_buffer (b)->sw_if_index[VLIB_RX] = + vnet_buffer (b)->sw_if_index[VLIB_TX] = sw_if_index; + vlib_frame_t *f = vlib_get_frame_to_node (vm, hi->output_node_index); + u32 *to_next = vlib_frame_vector_args (f); + to_next[0] = bi; + f->n_vectors = 1; + vlib_put_frame_to_node (vm, hi->output_node_index, f); + } +} + +always_inline uword +ip4_arp_inline (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * frame, int is_glean) +{ + vnet_main_t *vnm = vnet_get_main (); + ip4_main_t *im = &ip4_main; + ip_lookup_main_t *lm = &im->lookup_main; + u32 *from, *to_next_drop; + uword n_left_from, n_left_to_next_drop, next_index; + u32 thread_index = vm->thread_index; + u64 seed; + + if (node->flags & VLIB_NODE_FLAG_TRACE) + ip4_forward_next_trace (vm, node, frame, VLIB_TX); + + seed = throttle_seed (&im->arp_throttle, thread_index, vlib_time_now (vm)); + + from = vlib_frame_vector_args (frame); + n_left_from = frame->n_vectors; + next_index = node->cached_next_index; + if (next_index == IP4_ARP_NEXT_DROP) + next_index = IP4_ARP_N_NEXT; /* point to first interface */ + + while (n_left_from > 0) + { + vlib_get_next_frame (vm, node, IP4_ARP_NEXT_DROP, + to_next_drop, n_left_to_next_drop); + + while (n_left_from > 0 && n_left_to_next_drop > 0) + { + u32 pi0, adj_index0, sw_if_index0; + ip4_address_t resolve0, src0; + vlib_buffer_t *p0, *b0; + ip_adjacency_t *adj0; + u64 r0; + + pi0 = from[0]; + p0 = vlib_get_buffer (vm, pi0); + + from += 1; + n_left_from -= 1; + to_next_drop[0] = pi0; + to_next_drop += 1; + n_left_to_next_drop -= 1; + + adj_index0 = vnet_buffer (p0)->ip.adj_index[VLIB_TX]; + adj0 = adj_get (adj_index0); + sw_if_index0 = adj0->rewrite_header.sw_if_index; + + if (is_glean) + { + /* resolve the packet's destination */ + ip4_header_t *ip0 = vlib_buffer_get_current (p0); + resolve0 = ip0->dst_address; + src0 = adj0->sub_type.glean.receive_addr.ip4; + } + else + { + /* resolve the incomplete adj */ + resolve0 = adj0->sub_type.nbr.next_hop.ip4; + /* Src IP address in ARP header. */ + if (ip4_src_address_for_packet (lm, sw_if_index0, &src0)) + { + /* No source address available */ + p0->error = node->errors[IP4_ARP_ERROR_NO_SOURCE_ADDRESS]; + continue; + } + } + + /* combine the address and interface for the hash key */ + r0 = (u64) resolve0.data_u32 << 32; + r0 |= sw_if_index0; + + if (throttle_check (&im->arp_throttle, thread_index, r0, seed)) + { + p0->error = node->errors[IP4_ARP_ERROR_THROTTLED]; + continue; + } + + /* + * the adj has been updated to a rewrite but the node the DPO that got + * us here hasn't - yet. no big deal. we'll drop while we wait. + */ + if (IP_LOOKUP_NEXT_REWRITE == adj0->lookup_next_index) + { + p0->error = node->errors[IP4_ARP_ERROR_RESOLVED]; + continue; + } + + /* + * Can happen if the control-plane is programming tables + * with traffic flowing; at least that's today's lame excuse. + */ + if ((is_glean && adj0->lookup_next_index != IP_LOOKUP_NEXT_GLEAN) + || (!is_glean && adj0->lookup_next_index != IP_LOOKUP_NEXT_ARP)) + { + p0->error = node->errors[IP4_ARP_ERROR_NON_ARP_ADJ]; + continue; + } + + /* Send ARP request. */ + b0 = ip4_neighbor_probe (vm, vnm, adj0, &src0, &resolve0); + + if (PREDICT_TRUE (NULL != b0)) + { + /* copy the persistent fields from the original */ + clib_memcpy_fast (b0->opaque2, p0->opaque2, + sizeof (p0->opaque2)); + p0->error = node->errors[IP4_ARP_ERROR_REQUEST_SENT]; + } + else + { + p0->error = node->errors[IP4_ARP_ERROR_NO_BUFFERS]; + continue; + } + } + + vlib_put_next_frame (vm, node, IP4_ARP_NEXT_DROP, n_left_to_next_drop); + } + + return frame->n_vectors; +} + +VLIB_NODE_FN (ip4_arp_node) (vlib_main_t * vm, vlib_node_runtime_t * node, + vlib_frame_t * frame) +{ + return (ip4_arp_inline (vm, node, frame, 0)); +} + +VLIB_NODE_FN (ip4_glean_node) (vlib_main_t * vm, vlib_node_runtime_t * node, + vlib_frame_t * frame) +{ + return (ip4_arp_inline (vm, node, frame, 1)); +} + +static char *ip4_arp_error_strings[] = { + [IP4_ARP_ERROR_THROTTLED] = "ARP requests throttled", + [IP4_ARP_ERROR_RESOLVED] = "ARP requests resolved", + [IP4_ARP_ERROR_NO_BUFFERS] = "ARP requests out of buffer", + [IP4_ARP_ERROR_REQUEST_SENT] = "ARP requests sent", + [IP4_ARP_ERROR_NON_ARP_ADJ] = "ARPs to non-ARP adjacencies", + [IP4_ARP_ERROR_NO_SOURCE_ADDRESS] = "no source address for ARP request", +}; + +/* *INDENT-OFF* */ +VLIB_REGISTER_NODE (ip4_arp_node) = +{ + .name = "ip4-arp", + .vector_size = sizeof (u32), + .format_trace = format_ip4_forward_next_trace, + .n_errors = ARRAY_LEN (ip4_arp_error_strings), + .error_strings = ip4_arp_error_strings, + .n_next_nodes = IP4_ARP_N_NEXT, + .next_nodes = { + [IP4_ARP_NEXT_DROP] = "ip4-drop", + }, +}; + +VLIB_REGISTER_NODE (ip4_glean_node) = +{ + .name = "ip4-glean", + .vector_size = sizeof (u32), + .format_trace = format_ip4_forward_next_trace, + .n_errors = ARRAY_LEN (ip4_arp_error_strings), + .error_strings = ip4_arp_error_strings, + .n_next_nodes = IP4_ARP_N_NEXT, + .next_nodes = { + [IP4_ARP_NEXT_DROP] = "ip4-drop", + }, +}; +/* *INDENT-ON* */ + +#define foreach_notrace_ip4_arp_error \ +_(THROTTLED) \ +_(RESOLVED) \ +_(NO_BUFFERS) \ +_(REQUEST_SENT) \ +_(NON_ARP_ADJ) \ +_(NO_SOURCE_ADDRESS) + +static clib_error_t * +arp_notrace_init (vlib_main_t * vm) +{ + vlib_node_runtime_t *rt = vlib_node_get_runtime (vm, ip4_arp_node.index); + + /* don't trace ARP request packets */ +#define _(a) \ + vnet_pcap_drop_trace_filter_add_del \ + (rt->errors[IP4_ARP_ERROR_##a], \ + 1 /* is_add */); + foreach_notrace_ip4_arp_error; +#undef _ + return 0; +} + +VLIB_INIT_FUNCTION (arp_notrace_init); + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vnet/ip-neighbor/ip4_neighbor.h b/src/vnet/ip-neighbor/ip4_neighbor.h new file mode 100644 index 00000000000..c52e2d446af --- /dev/null +++ b/src/vnet/ip-neighbor/ip4_neighbor.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2019 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 __IP4_NEIGHBOR_H__ +#define __IP4_NEIGHBOR_H__ + +#include <vnet/ip/ip.h> +#include <vnet/ethernet/arp_packet.h> + +extern void ip4_neighbor_probe_dst (const ip_adjacency_t * adj, + const ip4_address_t * dst); +extern void ip4_neighbor_advertise (vlib_main_t * vm, + vnet_main_t * vnm, + u32 sw_if_index, + const ip4_address_t * addr); + +always_inline vlib_buffer_t * +ip4_neighbor_probe (vlib_main_t * vm, + vnet_main_t * vnm, + const ip_adjacency_t * adj0, + const ip4_address_t * src, const ip4_address_t * dst) +{ + vnet_hw_interface_t *hw_if0; + ethernet_arp_header_t *h0; + vlib_buffer_t *b0; + u32 bi0; + + /* Send ARP request. */ + h0 = vlib_packet_template_get_packet (vm, + &ip4_main.ip4_arp_request_packet_template, + &bi0); + /* Seems we're out of buffers */ + if (PREDICT_FALSE (!h0)) + return (NULL); + + b0 = vlib_get_buffer (vm, bi0); + + /* Add rewrite/encap string for ARP packet. */ + vnet_rewrite_one_header (adj0[0], h0, sizeof (ethernet_header_t)); + + hw_if0 = vnet_get_sup_hw_interface (vnm, adj0->rewrite_header.sw_if_index); + + /* Src ethernet address in ARP header. */ + mac_address_from_bytes (&h0->ip4_over_ethernet[0].mac, hw_if0->hw_address); + + h0->ip4_over_ethernet[0].ip4 = *src; + h0->ip4_over_ethernet[1].ip4 = *dst; + + VLIB_BUFFER_TRACE_TRAJECTORY_INIT (b0); + vnet_buffer (b0)->sw_if_index[VLIB_TX] = adj0->rewrite_header.sw_if_index; + + vlib_buffer_advance (b0, -adj0->rewrite_header.data_bytes); + + { + vlib_frame_t *f = vlib_get_frame_to_node (vm, hw_if0->output_node_index); + u32 *to_next = vlib_frame_vector_args (f); + to_next[0] = bi0; + f->n_vectors = 1; + vlib_put_frame_to_node (vm, hw_if0->output_node_index, f); + } + + return b0; +} + +#endif + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vnet/ip-neighbor/ip6_neighbor.c b/src/vnet/ip-neighbor/ip6_neighbor.c new file mode 100644 index 00000000000..325db8c6277 --- /dev/null +++ b/src/vnet/ip-neighbor/ip6_neighbor.c @@ -0,0 +1,338 @@ +/* + * 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 <vnet/ip-neighbor/ip6_neighbor.h> + +void +ip6_neighbor_probe_dst (const ip_adjacency_t * adj, const ip6_address_t * dst) +{ + ip_interface_address_t *ia; + ip6_address_t *src; + + src = ip6_interface_address_matching_destination + (&ip6_main, dst, adj->rewrite_header.sw_if_index, &ia); + + if (!src) + return; + + ip6_neighbor_probe (vlib_get_main (), vnet_get_main (), adj, src, dst); +} + +void +ip6_neighbor_advertise (vlib_main_t * vm, + vnet_main_t * vnm, + u32 sw_if_index, const ip6_address_t * addr) +{ + vnet_hw_interface_t *hi = vnet_get_sup_hw_interface (vnm, sw_if_index); + ip6_main_t *i6m = &ip6_main; + u8 *rewrite, rewrite_len; + u8 dst_address[6]; + + if (NULL == addr) + addr = ip6_interface_first_address (i6m, sw_if_index); + + if (addr) + { + clib_warning + ("Sending unsolicitated NA IP6 address %U on sw_if_idex %d", + format_ip6_address, addr, sw_if_index); + + /* Form unsolicited neighbor advertisement packet from NS pkt template */ + int bogus_length; + u32 bi = 0; + icmp6_neighbor_solicitation_header_t *h = + vlib_packet_template_get_packet (vm, + &ip6_neighbor_packet_template, + &bi); + if (!h) + return; + + ip6_set_reserved_multicast_address (&h->ip.dst_address, + IP6_MULTICAST_SCOPE_link_local, + IP6_MULTICAST_GROUP_ID_all_hosts); + h->ip.src_address = addr[0]; + h->neighbor.icmp.type = ICMP6_neighbor_advertisement; + h->neighbor.target_address = addr[0]; + h->neighbor.advertisement_flags = clib_host_to_net_u32 + (ICMP6_NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE); + h->link_layer_option.header.type = + ICMP6_NEIGHBOR_DISCOVERY_OPTION_target_link_layer_address; + clib_memcpy (h->link_layer_option.ethernet_address, + hi->hw_address, vec_len (hi->hw_address)); + h->neighbor.icmp.checksum = + ip6_tcp_udp_icmp_compute_checksum (vm, 0, &h->ip, &bogus_length); + ASSERT (bogus_length == 0); + + /* Setup MAC header with IP6 Etype and mcast DMAC */ + vlib_buffer_t *b = vlib_get_buffer (vm, bi); + ip6_multicast_ethernet_address (dst_address, + IP6_MULTICAST_GROUP_ID_all_hosts); + rewrite = + ethernet_build_rewrite (vnm, sw_if_index, VNET_LINK_IP6, dst_address); + rewrite_len = vec_len (rewrite); + vlib_buffer_advance (b, -rewrite_len); + ethernet_header_t *e = vlib_buffer_get_current (b); + clib_memcpy (e->dst_address, rewrite, rewrite_len); + vec_free (rewrite); + + /* Send unsolicited ND advertisement packet out the specified interface */ + vnet_buffer (b)->sw_if_index[VLIB_RX] = + vnet_buffer (b)->sw_if_index[VLIB_TX] = sw_if_index; + vlib_frame_t *f = vlib_get_frame_to_node (vm, hi->output_node_index); + u32 *to_next = vlib_frame_vector_args (f); + to_next[0] = bi; + f->n_vectors = 1; + vlib_put_frame_to_node (vm, hi->output_node_index, f); + } +} + +typedef enum +{ + IP6_NBR_NEXT_DROP, + IP6_NBR_NEXT_REPLY_TX, + IP6_NBR_N_NEXT, +} ip6_discover_neighbor_next_t; + +typedef enum +{ + IP6_NBR_ERROR_DROP, + IP6_NBR_ERROR_REQUEST_SENT, + IP6_NBR_ERROR_NO_SOURCE_ADDRESS, + IP6_NBR_ERROR_NO_BUFFERS, +} ip6_discover_neighbor_error_t; + +static uword +ip6_discover_neighbor_inline (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * frame, int is_glean) +{ + vnet_main_t *vnm = vnet_get_main (); + ip6_main_t *im = &ip6_main; + u32 *from, *to_next_drop; + uword n_left_from, n_left_to_next_drop; + u64 seed; + u32 thread_index = vm->thread_index; + + if (node->flags & VLIB_NODE_FLAG_TRACE) + ip6_forward_next_trace (vm, node, frame, VLIB_TX); + + seed = throttle_seed (&im->nd_throttle, thread_index, vlib_time_now (vm)); + + from = vlib_frame_vector_args (frame); + n_left_from = frame->n_vectors; + + while (n_left_from > 0) + { + vlib_get_next_frame (vm, node, IP6_NBR_NEXT_DROP, + to_next_drop, n_left_to_next_drop); + + while (n_left_from > 0 && n_left_to_next_drop > 0) + { + u32 pi0, adj_index0, sw_if_index0, drop0, r0; + vnet_hw_interface_t *hw_if0; + vlib_buffer_t *p0, *b0; + ip_adjacency_t *adj0; + ip6_address_t src; + ip6_header_t *ip0; + + pi0 = from[0]; + + p0 = vlib_get_buffer (vm, pi0); + + adj_index0 = vnet_buffer (p0)->ip.adj_index[VLIB_TX]; + + ip0 = vlib_buffer_get_current (p0); + + adj0 = adj_get (adj_index0); + + if (!is_glean) + { + ip0->dst_address.as_u64[0] = + adj0->sub_type.nbr.next_hop.ip6.as_u64[0]; + ip0->dst_address.as_u64[1] = + adj0->sub_type.nbr.next_hop.ip6.as_u64[1]; + } + + sw_if_index0 = adj0->rewrite_header.sw_if_index; + vnet_buffer (p0)->sw_if_index[VLIB_TX] = sw_if_index0; + + /* combine the address and interface for a hash */ + r0 = ip6_address_hash_to_u64 (&ip0->dst_address) ^ sw_if_index0; + + drop0 = throttle_check (&im->nd_throttle, thread_index, r0, seed); + + from += 1; + n_left_from -= 1; + to_next_drop[0] = pi0; + to_next_drop += 1; + n_left_to_next_drop -= 1; + + hw_if0 = vnet_get_sup_hw_interface (vnm, sw_if_index0); + + /* If the interface is link-down, drop the pkt */ + if (!(hw_if0->flags & VNET_HW_INTERFACE_FLAG_LINK_UP)) + drop0 = 1; + + if (!ip6_link_is_enabled (sw_if_index0)) + drop0 = 1; + + /* + * the adj has been updated to a rewrite but the node the DPO that got + * us here hasn't - yet. no big deal. we'll drop while we wait. + */ + if (IP_LOOKUP_NEXT_REWRITE == adj0->lookup_next_index) + drop0 = 1; + + if (drop0) + { + p0->error = node->errors[IP6_NBR_ERROR_DROP]; + continue; + } + + /* + * Choose source address based on destination lookup + * adjacency. + */ + if (!ip6_src_address_for_packet (sw_if_index0, + &ip0->dst_address, &src)) + { + /* There is no address on the interface */ + p0->error = node->errors[IP6_NBR_ERROR_NO_SOURCE_ADDRESS]; + continue; + } + + b0 = ip6_neighbor_probe (vm, vnm, adj0, &src, &ip0->dst_address); + + if (PREDICT_TRUE (NULL != b0)) + { + clib_memcpy_fast (b0->opaque2, p0->opaque2, + sizeof (p0->opaque2)); + b0->flags |= p0->flags & VLIB_BUFFER_IS_TRACED; + b0->trace_handle = p0->trace_handle; + p0->error = node->errors[IP6_NBR_ERROR_REQUEST_SENT]; + } + else + { + /* There is no address on the interface */ + p0->error = node->errors[IP6_NBR_ERROR_NO_BUFFERS]; + continue; + } + } + + vlib_put_next_frame (vm, node, IP6_NBR_NEXT_DROP, n_left_to_next_drop); + } + + return frame->n_vectors; +} + +static uword +ip6_discover_neighbor (vlib_main_t * vm, + vlib_node_runtime_t * node, vlib_frame_t * frame) +{ + return (ip6_discover_neighbor_inline (vm, node, frame, 0)); +} + +static uword +ip6_glean (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * frame) +{ + return (ip6_discover_neighbor_inline (vm, node, frame, 1)); +} + +static char *ip6_discover_neighbor_error_strings[] = { + [IP6_NBR_ERROR_DROP] = "address overflow drops", + [IP6_NBR_ERROR_REQUEST_SENT] = "neighbor solicitations sent", + [IP6_NBR_ERROR_NO_SOURCE_ADDRESS] = "no source address for ND solicitation", + [IP6_NBR_ERROR_NO_BUFFERS] = "no buffers", +}; + +/* *INDENT-OFF* */ +VLIB_REGISTER_NODE (ip6_glean_node) = +{ + .function = ip6_glean, + .name = "ip6-glean", + .vector_size = sizeof (u32), + .format_trace = format_ip6_forward_next_trace, + .n_errors = ARRAY_LEN (ip6_discover_neighbor_error_strings), + .error_strings = ip6_discover_neighbor_error_strings, + .n_next_nodes = IP6_NBR_N_NEXT, + .next_nodes = + { + [IP6_NBR_NEXT_DROP] = "ip6-drop", + [IP6_NBR_NEXT_REPLY_TX] = "ip6-rewrite-mcast", + }, +}; +VLIB_REGISTER_NODE (ip6_discover_neighbor_node) = +{ + .function = ip6_discover_neighbor, + .name = "ip6-discover-neighbor", + .vector_size = sizeof (u32), + .format_trace = format_ip6_forward_next_trace, + .n_errors = ARRAY_LEN (ip6_discover_neighbor_error_strings), + .error_strings = ip6_discover_neighbor_error_strings, + .n_next_nodes = IP6_NBR_N_NEXT, + .next_nodes = + { + [IP6_NBR_NEXT_DROP] = "ip6-drop", + [IP6_NBR_NEXT_REPLY_TX] = "ip6-rewrite-mcast", + }, +}; +/* *INDENT-ON* */ + +/* Template used to generate IP6 neighbor solicitation packets. */ +vlib_packet_template_t ip6_neighbor_packet_template; + +static clib_error_t * +ip6_neighbor_init (vlib_main_t * vm) +{ + icmp6_neighbor_solicitation_header_t p; + + clib_memset (&p, 0, sizeof (p)); + + p.ip.ip_version_traffic_class_and_flow_label = + clib_host_to_net_u32 (0x6 << 28); + p.ip.payload_length = + clib_host_to_net_u16 (sizeof (p) - + STRUCT_OFFSET_OF + (icmp6_neighbor_solicitation_header_t, neighbor)); + p.ip.protocol = IP_PROTOCOL_ICMP6; + p.ip.hop_limit = 255; + ip6_set_solicited_node_multicast_address (&p.ip.dst_address, 0); + + p.neighbor.icmp.type = ICMP6_neighbor_solicitation; + + p.link_layer_option.header.type = + ICMP6_NEIGHBOR_DISCOVERY_OPTION_source_link_layer_address; + p.link_layer_option.header.n_data_u64s = + sizeof (p.link_layer_option) / sizeof (u64); + + vlib_packet_template_init (vm, + &ip6_neighbor_packet_template, &p, sizeof (p), + /* alloc chunk size */ 8, + "ip6 neighbor discovery"); + + return NULL; +} + +VLIB_INIT_FUNCTION (ip6_neighbor_init); + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vnet/ip-neighbor/ip6_neighbor.h b/src/vnet/ip-neighbor/ip6_neighbor.h new file mode 100644 index 00000000000..934a2885ee1 --- /dev/null +++ b/src/vnet/ip-neighbor/ip6_neighbor.h @@ -0,0 +1,112 @@ +/* + * ip/ip6_neighbor.h: IP6 NS transmit + * + * Copyright (c) 2019 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 __IP6_NEIGHBOR_H__ +#define __IP6_NEIGHBOR_H__ + +#include <vlib/vlib.h> +#include <vnet/ip/format.h> +#include <vnet/ip/ip6.h> +#include <vnet/ip/ip6_link.h> +#include <vnet/ip/icmp46_packet.h> +#include <vnet/ethernet/ethernet.h> +#include <vnet/adj/adj_internal.h> + +/* Template used to generate IP6 neighbor solicitation packets. */ +extern vlib_packet_template_t ip6_neighbor_packet_template; + +extern void ip6_neighbor_advertise (vlib_main_t * vm, + vnet_main_t * vnm, + u32 sw_if_index, + const ip6_address_t * addr); + +extern void ip6_neighbor_probe_dst (const ip_adjacency_t * adj, + const ip6_address_t * dst); + +always_inline vlib_buffer_t * +ip6_neighbor_probe (vlib_main_t * vm, + vnet_main_t * vnm, + const ip_adjacency_t * adj, + const ip6_address_t * src, const ip6_address_t * dst) +{ + icmp6_neighbor_solicitation_header_t *h0; + vnet_hw_interface_t *hw_if0; + vlib_buffer_t *b0; + int bogus_length; + u32 bi0 = 0; + + h0 = vlib_packet_template_get_packet + (vm, &ip6_neighbor_packet_template, &bi0); + if (!h0) + return NULL;; + + b0 = vlib_get_buffer (vm, bi0); + + hw_if0 = vnet_get_sup_hw_interface (vnm, adj->rewrite_header.sw_if_index); + + /* + * Destination address is a solicited node multicast address. + * We need to fill in + * the low 24 bits with low 24 bits of target's address. + */ + h0->ip.src_address = *src; + h0->ip.dst_address.as_u8[13] = dst->as_u8[13]; + h0->ip.dst_address.as_u8[14] = dst->as_u8[14]; + h0->ip.dst_address.as_u8[15] = dst->as_u8[15]; + + h0->neighbor.target_address = *dst; + + clib_memcpy (h0->link_layer_option.ethernet_address, + hw_if0->hw_address, vec_len (hw_if0->hw_address)); + + /* $$$$ appears we need this; why is the checksum non-zero? */ + h0->neighbor.icmp.checksum = 0; + h0->neighbor.icmp.checksum = + ip6_tcp_udp_icmp_compute_checksum (vm, 0, &h0->ip, &bogus_length); + + ASSERT (bogus_length == 0); + VLIB_BUFFER_TRACE_TRAJECTORY_INIT (b0); + + vnet_buffer (b0)->sw_if_index[VLIB_TX] = adj->rewrite_header.sw_if_index; + + /* Use the link's mcast adj to ship the packet */ + vnet_buffer (b0)->ip.adj_index[VLIB_TX] = + ip6_link_get_mcast_adj (adj->rewrite_header.sw_if_index); + adj = adj_get (vnet_buffer (b0)->ip.adj_index[VLIB_TX]); + + b0->flags |= VNET_BUFFER_F_LOCALLY_ORIGINATED; + + { + vlib_frame_t *f = vlib_get_frame_to_node (vm, adj->ia_node_index); + u32 *to_next = vlib_frame_vector_args (f); + to_next[0] = bi0; + f->n_vectors = 1; + vlib_put_frame_to_node (vm, adj->ia_node_index, f); + } + + return b0; +} + +#endif + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vnet/ip-neighbor/ip_neighbor.api b/src/vnet/ip-neighbor/ip_neighbor.api new file mode 100644 index 00000000000..a8fb24a712c --- /dev/null +++ b/src/vnet/ip-neighbor/ip_neighbor.api @@ -0,0 +1,168 @@ +/* Hey Emacs use -*- mode: C -*- */ +/* + * 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. + */ + +/** \file + + This file defines vpp IP neighbor control-plane API messages which are generally + called through a shared memory interface. +*/ + +option version = "1.0.0"; + +import "vnet/ip/ip_types.api"; +import "vnet/ethernet/ethernet_types.api"; +import "vnet/interface_types.api"; + +/** \brief IP neighbor flags + @param is_static - A static neighbor Entry - there are not flushed + If the interface goes down. + @param is_no_fib_entry - Do not create a corresponding entry in the FIB + table for the neighbor. +*/ +enum ip_neighbor_flags: u8 +{ + IP_API_NEIGHBOR_FLAG_NONE = 0, + IP_API_NEIGHBOR_FLAG_STATIC = 0x1, + IP_API_NEIGHBOR_FLAG_NO_FIB_ENTRY = 0x2, +}; + +/** \brief IP neighbor + @param sw_if_index - interface used to reach neighbor + @param flags - flags for the nieghbor + @param mac_address - l2 address of the neighbor + @param ip_address - ip4 or ip6 address of the neighbor +*/ +typedef ip_neighbor { + vl_api_interface_index_t sw_if_index; + vl_api_ip_neighbor_flags_t flags; + vl_api_mac_address_t mac_address; + vl_api_address_t ip_address; +}; + +/** \brief IP neighbor add / del request + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param is_add - 1 to add neighbor, 0 to delete + @param neighbor - the neighor to add/remove +*/ +define ip_neighbor_add_del +{ + u32 client_index; + u32 context; + /* 1 = add, 0 = delete */ + bool is_add; + vl_api_ip_neighbor_t neighbor; +}; +/** \brief IP neighbor add / del reply + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param retval - return value + @param stats_index - the index to use for this neighbor in the stats segement +*/ +define ip_neighbor_add_del_reply +{ + u32 context; + i32 retval; + u32 stats_index; +}; + +/** \brief Dump IP neighboors + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param sw_if_index - the interface to dump neighboors, ~0 == all + @param af - address family is ipv[6|4] +*/ +define ip_neighbor_dump +{ + u32 client_index; + u32 context; + vl_api_interface_index_t sw_if_index [default=0xffffffff]; + vl_api_address_family_t af; +}; + +/** \brief IP neighboors dump response + @param context - sender context which was passed in the request + @param neighbour - the neighbor +*/ +define ip_neighbor_details { + u32 context; + vl_api_ip_neighbor_t neighbor; +}; + +/** \brief Enable/disable periodic IP neighbor scan + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param af - Address family v4/v6 + @param max_number - The maximum number of neighbours that will be created. + default 50k + @param max_age - The maximum age (in seconds) before an inactive neighbour + is flushed + default 0 => never + @param recycle - If max_number of neighbours is reached and new ones need + to be created should the oldest neighbour be 'recycled'. +*/ +autoreply define ip_neighbor_config +{ + u32 client_index; + u32 context; + vl_api_address_family_t af; + u32 max_number; + u32 max_age; + bool recycle; +}; + +/** \brief Register for IP4 ARP resolution event on receing ARP reply or + MAC/IP info from ARP requests in L2 BDs + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param enable - 1 => register for events, 0 => cancel registration + @param pid - sender's pid + @param ip - exact IP address of interested neighbor resolution event + @param sw_if_index - interface on which the IP address is present. +*/ +autoreply define want_ip_neighbor_events +{ + u32 client_index; + u32 context; + bool enable; + u32 pid; + vl_api_address_t ip; + vl_api_interface_index_t sw_if_index [default=0xffffffff]; +}; + +/** \brief Tell client about an IP4 ARP resolution event or + MAC/IP info from ARP requests in L2 BDs + @param client_index - opaque cookie to identify the sender + @param pid - client pid registered to receive notification + @param neighbor - new neighbor created +*/ +define ip_neighbor_event +{ + u32 client_index; + u32 pid; + vl_api_ip_neighbor_t neighbor; +}; + +service { + rpc want_ip_neighbor_events returns want_ip_neighbor_events_reply + events ip_neighbor_event; +}; + +/* + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vnet/ip-neighbor/ip_neighbor.c b/src/vnet/ip-neighbor/ip_neighbor.c new file mode 100644 index 00000000000..11684eb9de5 --- /dev/null +++ b/src/vnet/ip-neighbor/ip_neighbor.c @@ -0,0 +1,1664 @@ +/* + * src/vnet/ip/ip_neighboor.c: ip neighbor generic handling + * + * 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 <vppinfra/llist.h> + +#include <vnet/ip-neighbor/ip_neighbor.h> +#include <vnet/ip-neighbor/ip4_neighbor.h> +#include <vnet/ip-neighbor/ip6_neighbor.h> +#include <vnet/ip-neighbor/ip_neighbor_watch.h> + +#include <vnet/ip/ip6_ll_table.h> +#include <vnet/fib/fib_table.h> +#include <vnet/adj/adj_mcast.h> + +/** Pool for All IP neighbors */ +static ip_neighbor_t *ip_neighbor_pool; + +/** protocol specific lists of time sorted neighbors */ +index_t ip_neighbor_list_head[IP46_N_TYPES]; + +typedef struct ip_neighbor_elt_t_ +{ + clib_llist_anchor_t ipne_anchor; + index_t ipne_index; +} ip_neighbor_elt_t; + +/** Pool of linked list elemeents */ +ip_neighbor_elt_t *ip_neighbor_elt_pool; + +typedef struct ip_neighbor_db_t_ +{ + /** per interface hash */ + uword **ipndb_hash; + /** per-protocol limit - max number of neighbors*/ + u32 ipndb_limit; + /** max age of a neighbor before it's forcibly evicted */ + u32 ipndb_age; + /** when the limit is reached and new neighbors are created, should + * we recycle an old one */ + bool ipndb_recycle; + /** per-protocol number of elements */ + u32 ipndb_n_elts; + /** per-protocol number of elements per-fib-index*/ + u32 *ipndb_n_elts_per_fib; +} ip_neighbor_db_t; + +static vlib_log_class_t ipn_logger; + +/* DBs of neighbours one per AF */ +/* *INDENT-OFF* */ +static ip_neighbor_db_t ip_neighbor_db[IP46_N_TYPES] = { + [IP46_TYPE_IP4] = { + .ipndb_limit = 50000, + /* Default to not aging and not recycling */ + .ipndb_age = 0, + .ipndb_recycle = false, + }, + [IP46_TYPE_IP6] = { + .ipndb_limit = 50000, + /* Default to not aging and not recycling */ + .ipndb_age = 0, + .ipndb_recycle = false, + } +}; +/* *INDENT-ON* */ + +#define IP_NEIGHBOR_DBG(...) \ + vlib_log_debug (ipn_logger, __VA_ARGS__); + +#define IP_NEIGHBOR_INFO(...) \ + vlib_log_notice (ipn_logger, __VA_ARGS__); + +ip_neighbor_t * +ip_neighbor_get (index_t ipni) +{ + if (pool_is_free_index (ip_neighbor_pool, ipni)) + return (NULL); + + return (pool_elt_at_index (ip_neighbor_pool, ipni)); +} + +static index_t +ip_neighbor_get_index (const ip_neighbor_t * ipn) +{ + return (ipn - ip_neighbor_pool); +} + +static bool +ip_neighbor_is_dynamic (const ip_neighbor_t * ipn) +{ + return (ipn->ipn_flags & IP_NEIGHBOR_FLAG_DYNAMIC); +} + +const ip46_address_t * +ip_neighbor_get_ip (const ip_neighbor_t * ipn) +{ + return (&ipn->ipn_key->ipnk_ip); +} + +const mac_address_t * +ip_neighbor_get_mac (const ip_neighbor_t * ipn) +{ + return (&ipn->ipn_mac); +} + +const u32 +ip_neighbor_get_sw_if_index (const ip_neighbor_t * ipn) +{ + return (ipn->ipn_key->ipnk_sw_if_index); +} + +static void +ip_neighbor_list_remove (ip_neighbor_t * ipn) +{ + /* new neighbours, are added to the head of the list, since the + * list is time sorted, newest first */ + ip_neighbor_elt_t *elt; + + if (~0 != ipn->ipn_elt) + { + elt = pool_elt_at_index (ip_neighbor_elt_pool, ipn->ipn_elt); + + clib_llist_remove (ip_neighbor_elt_pool, ipne_anchor, elt); + } +} + +static void +ip_neighbor_refresh (ip_neighbor_t * ipn) +{ + /* new neighbours, are added to the head of the list, since the + * list is time sorted, newest first */ + ip_neighbor_elt_t *elt, *head; + + ipn->ipn_time_last_updated = vlib_time_now (vlib_get_main ()); + ipn->ipn_n_probes = 0; + + if (ip_neighbor_is_dynamic (ipn)) + { + if (~0 == ipn->ipn_elt) + /* first time insertion */ + pool_get_zero (ip_neighbor_elt_pool, elt); + else + { + /* already inserted - extract first */ + elt = pool_elt_at_index (ip_neighbor_elt_pool, ipn->ipn_elt); + + clib_llist_remove (ip_neighbor_elt_pool, ipne_anchor, elt); + } + head = pool_elt_at_index (ip_neighbor_elt_pool, + ip_neighbor_list_head[ipn-> + ipn_key->ipnk_type]); + + elt->ipne_index = ip_neighbor_get_index (ipn); + clib_llist_add (ip_neighbor_elt_pool, ipne_anchor, elt, head); + ipn->ipn_elt = elt - ip_neighbor_elt_pool; + } +} + +static void +ip_neighbor_db_add (const ip_neighbor_t * ipn) +{ + vec_validate (ip_neighbor_db[ipn->ipn_key->ipnk_type].ipndb_hash, + ipn->ipn_key->ipnk_sw_if_index); + + if (!ip_neighbor_db[ipn->ipn_key->ipnk_type].ipndb_hash + [ipn->ipn_key->ipnk_sw_if_index]) + ip_neighbor_db[ipn->ipn_key->ipnk_type].ipndb_hash[ipn-> + ipn_key->ipnk_sw_if_index] + = hash_create_mem (0, sizeof (ip_neighbor_key_t), sizeof (index_t)); + + hash_set_mem (ip_neighbor_db[ipn->ipn_key->ipnk_type].ipndb_hash + [ipn->ipn_key->ipnk_sw_if_index], ipn->ipn_key, + ip_neighbor_get_index (ipn)); + + ip_neighbor_db[ipn->ipn_key->ipnk_type].ipndb_n_elts++; +} + +static void +ip_neighbor_db_remove (const ip_neighbor_key_t * key) +{ + vec_validate (ip_neighbor_db[key->ipnk_type].ipndb_hash, + key->ipnk_sw_if_index); + + hash_unset_mem (ip_neighbor_db[key->ipnk_type].ipndb_hash + [key->ipnk_sw_if_index], key); + + ip_neighbor_db[key->ipnk_type].ipndb_n_elts--; +} + +static ip_neighbor_t * +ip_neighbor_db_find (const ip_neighbor_key_t * key) +{ + uword *p; + + if (key->ipnk_sw_if_index >= + vec_len (ip_neighbor_db[key->ipnk_type].ipndb_hash)) + return NULL; + + p = + hash_get_mem (ip_neighbor_db[key->ipnk_type].ipndb_hash + [key->ipnk_sw_if_index], key); + + if (p) + return ip_neighbor_get (p[0]); + + return (NULL); +} + +static u8 +ip46_type_pfx_len (ip46_type_t type) +{ + return (type == IP46_TYPE_IP4 ? 32 : 128); +} + +static void +ip_neighbor_adj_fib_add (ip_neighbor_t * ipn, u32 fib_index) +{ + if (ipn->ipn_key->ipnk_type == IP46_TYPE_IP6 && + ip6_address_is_link_local_unicast (&ipn->ipn_key->ipnk_ip.ip6)) + { + ip6_ll_prefix_t pfx = { + .ilp_addr = ipn->ipn_key->ipnk_ip.ip6, + .ilp_sw_if_index = ipn->ipn_key->ipnk_sw_if_index, + }; + ipn->ipn_fib_entry_index = + ip6_ll_table_entry_update (&pfx, FIB_ROUTE_PATH_FLAG_NONE); + } + else + { + fib_protocol_t fproto; + + fproto = fib_proto_from_ip46 (ipn->ipn_key->ipnk_type); + + fib_prefix_t pfx = { + .fp_len = ip46_type_pfx_len (ipn->ipn_key->ipnk_type), + .fp_proto = fproto, + .fp_addr = ipn->ipn_key->ipnk_ip, + }; + + ipn->ipn_fib_entry_index = + fib_table_entry_path_add (fib_index, &pfx, FIB_SOURCE_ADJ, + FIB_ENTRY_FLAG_ATTACHED, + fib_proto_to_dpo (fproto), + &pfx.fp_addr, + ipn->ipn_key->ipnk_sw_if_index, + ~0, 1, NULL, FIB_ROUTE_PATH_FLAG_NONE); + + vec_validate (ip_neighbor_db + [ipn->ipn_key->ipnk_type].ipndb_n_elts_per_fib, + fib_index); + + ip_neighbor_db[ipn->ipn_key-> + ipnk_type].ipndb_n_elts_per_fib[fib_index]++; + + if (1 == + ip_neighbor_db[ipn->ipn_key-> + ipnk_type].ipndb_n_elts_per_fib[fib_index]) + fib_table_lock (fib_index, fproto, FIB_SOURCE_ADJ); + } +} + +static void +ip_neighbor_adj_fib_remove (ip_neighbor_t * ipn, u32 fib_index) +{ + if (FIB_NODE_INDEX_INVALID != ipn->ipn_fib_entry_index) + { + if (ipn->ipn_key->ipnk_type == IP46_TYPE_IP6 && + ip6_address_is_link_local_unicast (&ipn->ipn_key->ipnk_ip.ip6)) + { + ip6_ll_prefix_t pfx = { + .ilp_addr = ipn->ipn_key->ipnk_ip.ip6, + .ilp_sw_if_index = ipn->ipn_key->ipnk_sw_if_index, + }; + ip6_ll_table_entry_delete (&pfx); + } + else + { + fib_protocol_t fproto; + + fproto = fib_proto_from_ip46 (ipn->ipn_key->ipnk_type); + + fib_prefix_t pfx = { + .fp_len = ip46_type_pfx_len (ipn->ipn_key->ipnk_type), + .fp_proto = fproto, + .fp_addr = ipn->ipn_key->ipnk_ip, + }; + + fib_table_entry_path_remove (fib_index, + &pfx, + FIB_SOURCE_ADJ, + fib_proto_to_dpo (fproto), + &pfx.fp_addr, + ipn->ipn_key->ipnk_sw_if_index, + ~0, 1, FIB_ROUTE_PATH_FLAG_NONE); + + ip_neighbor_db[ipn->ipn_key-> + ipnk_type].ipndb_n_elts_per_fib[fib_index]--; + + if (0 == + ip_neighbor_db[ipn->ipn_key-> + ipnk_type].ipndb_n_elts_per_fib[fib_index]) + fib_table_unlock (fib_index, fproto, FIB_SOURCE_ADJ); + } + } +} + +static void +ip_neighbor_mk_complete (adj_index_t ai, ip_neighbor_t * ipn) +{ + adj_nbr_update_rewrite (ai, ADJ_NBR_REWRITE_FLAG_COMPLETE, + ethernet_build_rewrite (vnet_get_main (), + ipn-> + ipn_key->ipnk_sw_if_index, + adj_get_link_type (ai), + ipn->ipn_mac.bytes)); +} + +static void +ip_neighbor_mk_incomplete (adj_index_t ai) +{ + ip_adjacency_t *adj = adj_get (ai); + + adj_nbr_update_rewrite (ai, + ADJ_NBR_REWRITE_FLAG_INCOMPLETE, + ethernet_build_rewrite (vnet_get_main (), + adj-> + rewrite_header.sw_if_index, + adj_get_link_type (ai), + VNET_REWRITE_FOR_SW_INTERFACE_ADDRESS_BROADCAST)); +} + +static adj_walk_rc_t +ip_neighbor_mk_complete_walk (adj_index_t ai, void *ctx) +{ + ip_neighbor_t *ipn = ctx; + + ip_neighbor_mk_complete (ai, ipn); + + return (ADJ_WALK_RC_CONTINUE); +} + +static adj_walk_rc_t +ip_neighbor_mk_incomplete_walk (adj_index_t ai, void *ctx) +{ + ip_neighbor_mk_incomplete (ai); + + return (ADJ_WALK_RC_CONTINUE); +} + +static void +ip_neighbor_free (ip_neighbor_t * ipn) +{ + IP_NEIGHBOR_DBG ("free: %U", format_ip_neighbor, + ip_neighbor_get_index (ipn)); + + adj_nbr_walk_nh (ipn->ipn_key->ipnk_sw_if_index, + fib_proto_from_ip46 (ipn->ipn_key->ipnk_type), + &ipn->ipn_key->ipnk_ip, + ip_neighbor_mk_incomplete_walk, ipn); + ip_neighbor_adj_fib_remove + (ipn, + fib_table_get_index_for_sw_if_index + (fib_proto_from_ip46 (ipn->ipn_key->ipnk_type), + ipn->ipn_key->ipnk_sw_if_index)); + + ip_neighbor_list_remove (ipn); + ip_neighbor_db_remove (ipn->ipn_key); + clib_mem_free (ipn->ipn_key); + + pool_put (ip_neighbor_pool, ipn); +} + +static bool +ip_neighbor_force_reuse (ip46_type_t type) +{ + if (!ip_neighbor_db[type].ipndb_recycle) + return false; + + /* pluck the oldest entry, which is the one from the end of the list */ + ip_neighbor_elt_t *elt, *head; + + head = + pool_elt_at_index (ip_neighbor_elt_pool, ip_neighbor_list_head[type]); + + if (clib_llist_is_empty (ip_neighbor_elt_pool, ipne_anchor, head)) + return (false); + + elt = clib_llist_prev (ip_neighbor_elt_pool, ipne_anchor, head); + ip_neighbor_free (ip_neighbor_get (elt->ipne_index)); + + return (true); +} + +static ip_neighbor_t * +ip_neighbor_alloc (const ip_neighbor_key_t * key, + const mac_address_t * mac, ip_neighbor_flags_t flags) +{ + ip_neighbor_t *ipn; + + if (ip_neighbor_db[key->ipnk_type].ipndb_limit && + (ip_neighbor_db[key->ipnk_type].ipndb_n_elts >= + ip_neighbor_db[key->ipnk_type].ipndb_limit)) + { + if (!ip_neighbor_force_reuse (key->ipnk_type)) + return (NULL); + } + + pool_get_zero (ip_neighbor_pool, ipn); + + ipn->ipn_key = clib_mem_alloc (sizeof (*ipn->ipn_key)); + clib_memcpy (ipn->ipn_key, key, sizeof (*ipn->ipn_key)); + + ipn->ipn_fib_entry_index = FIB_NODE_INDEX_INVALID; + ipn->ipn_flags = flags; + ipn->ipn_elt = ~0; + + mac_address_copy (&ipn->ipn_mac, mac); + + ip_neighbor_db_add (ipn); + + /* create the adj-fib. the entry in the FIB table for the peer's interface */ + if (!(ipn->ipn_flags & IP_NEIGHBOR_FLAG_NO_FIB_ENTRY)) + ip_neighbor_adj_fib_add + (ipn, fib_table_get_index_for_sw_if_index + (fib_proto_from_ip46 (ipn->ipn_key->ipnk_type), + ipn->ipn_key->ipnk_sw_if_index)); + + return (ipn); +} + +int +ip_neighbor_add (const ip46_address_t * ip, + ip46_type_t type, + const mac_address_t * mac, + u32 sw_if_index, + ip_neighbor_flags_t flags, u32 * stats_index) +{ + fib_protocol_t fproto; + ip_neighbor_t *ipn; + + /* main thread only */ + ASSERT (0 == vlib_get_thread_index ()); + + fproto = fib_proto_from_ip46 (type); + + const ip_neighbor_key_t key = { + .ipnk_ip = *ip, + .ipnk_sw_if_index = sw_if_index, + .ipnk_type = type, + }; + + ipn = ip_neighbor_db_find (&key); + + if (ipn) + { + IP_NEIGHBOR_DBG ("update: %U, %U", + format_vnet_sw_if_index_name, vnet_get_main (), + sw_if_index, format_ip46_address, ip, type, + format_ip_neighbor_flags, flags, format_mac_address_t, + mac); + + /* Refuse to over-write static neighbor entry. */ + if (!(flags & IP_NEIGHBOR_FLAG_STATIC) && + (ipn->ipn_flags & IP_NEIGHBOR_FLAG_STATIC)) + { + /* if MAC address match, still check to send event */ + if (0 == mac_address_cmp (&ipn->ipn_mac, mac)) + goto check_customers; + return -2; + } + + /* + * prevent a DoS attack from the data-plane that + * spams us with no-op updates to the MAC address + */ + if (0 == mac_address_cmp (&ipn->ipn_mac, mac)) + { + ip_neighbor_refresh (ipn); + goto check_customers; + } + + mac_address_copy (&ipn->ipn_mac, mac); + + /* A dynamic entry can become static, but not vice-versa. + * i.e. since if it was programmed by the CP then it must + * be removed by the CP */ + if ((flags & IP_NEIGHBOR_FLAG_STATIC) && + !(ipn->ipn_flags & IP_NEIGHBOR_FLAG_STATIC)) + { + ip_neighbor_list_remove (ipn); + ipn->ipn_flags |= IP_NEIGHBOR_FLAG_STATIC; + ipn->ipn_flags &= ~IP_NEIGHBOR_FLAG_DYNAMIC; + } + } + else + { + IP_NEIGHBOR_INFO ("add: %U, %U", + format_vnet_sw_if_index_name, vnet_get_main (), + sw_if_index, format_ip46_address, ip, type, + format_ip_neighbor_flags, flags, format_mac_address_t, + mac); + + ipn = ip_neighbor_alloc (&key, mac, flags); + + if (NULL == ipn) + return VNET_API_ERROR_LIMIT_EXCEEDED; + } + + /* Update time stamp and flags. */ + ip_neighbor_refresh (ipn); + + adj_nbr_walk_nh (ipn->ipn_key->ipnk_sw_if_index, + fproto, &ipn->ipn_key->ipnk_ip, + ip_neighbor_mk_complete_walk, ipn); + +check_customers: + /* Customer(s) requesting event for this address? */ + ip_neighbor_publish (ip_neighbor_get_index (ipn)); + + if (stats_index) + *stats_index = adj_nbr_find (fproto, + fib_proto_to_link (fproto), + &ipn->ipn_key->ipnk_ip, + ipn->ipn_key->ipnk_sw_if_index); + return 0; +} + +int +ip_neighbor_del (const ip46_address_t * ip, ip46_type_t type, u32 sw_if_index) +{ + ip_neighbor_t *ipn; + + /* main thread only */ + ASSERT (0 == vlib_get_thread_index ()); + + IP_NEIGHBOR_INFO ("delete: %U, %U", + format_vnet_sw_if_index_name, vnet_get_main (), + sw_if_index, format_ip46_address, ip, type); + + const ip_neighbor_key_t key = { + .ipnk_ip = *ip, + .ipnk_sw_if_index = sw_if_index, + .ipnk_type = type, + }; + + ipn = ip_neighbor_db_find (&key); + + if (NULL == ipn) + return (VNET_API_ERROR_NO_SUCH_ENTRY); + + ip_neighbor_free (ipn); + + return (0); +} + +void +ip_neighbor_update (vnet_main_t * vnm, adj_index_t ai) +{ + ip_neighbor_t *ipn; + ip_adjacency_t *adj; + + adj = adj_get (ai); + + ip_neighbor_key_t key = { + .ipnk_ip = adj->sub_type.nbr.next_hop, + .ipnk_type = fib_proto_to_ip46 (adj->ia_nh_proto), + .ipnk_sw_if_index = adj->rewrite_header.sw_if_index, + }; + ipn = ip_neighbor_db_find (&key); + + switch (adj->lookup_next_index) + { + case IP_LOOKUP_NEXT_ARP: + if (NULL != ipn) + { + adj_nbr_walk_nh (adj->rewrite_header.sw_if_index, + adj->ia_nh_proto, + &ipn->ipn_key->ipnk_ip, + ip_neighbor_mk_complete_walk, ipn); + } + else + { + /* + * no matching ARP entry. + * construct the rewrite required to for an ARP packet, and stick + * that in the adj's pipe to smoke. + */ + adj_nbr_update_rewrite + (ai, + ADJ_NBR_REWRITE_FLAG_INCOMPLETE, + ethernet_build_rewrite + (vnm, + adj->rewrite_header.sw_if_index, + VNET_LINK_ARP, + VNET_REWRITE_FOR_SW_INTERFACE_ADDRESS_BROADCAST)); + + /* + * since the FIB has added this adj for a route, it makes sense it + * may want to forward traffic sometime soon. Let's send a + * speculative ARP. just one. If we were to do periodically that + * wouldn't be bad either, but that's more code than i'm prepared to + * write at this time for relatively little reward. + */ + ip_neighbor_probe (adj); + } + break; + case IP_LOOKUP_NEXT_GLEAN: + case IP_LOOKUP_NEXT_BCAST: + case IP_LOOKUP_NEXT_MCAST: + case IP_LOOKUP_NEXT_DROP: + case IP_LOOKUP_NEXT_PUNT: + case IP_LOOKUP_NEXT_LOCAL: + case IP_LOOKUP_NEXT_REWRITE: + case IP_LOOKUP_NEXT_MCAST_MIDCHAIN: + case IP_LOOKUP_NEXT_MIDCHAIN: + case IP_LOOKUP_NEXT_ICMP_ERROR: + case IP_LOOKUP_N_NEXT: + ASSERT (0); + break; + } +} + +void +ip_neighbor_learn (const ip_neighbor_learn_t * l) +{ + ip_neighbor_add (&l->ip, l->type, &l->mac, l->sw_if_index, + IP_NEIGHBOR_FLAG_DYNAMIC, NULL); +} + +static clib_error_t * +ip_neighbor_cmd (vlib_main_t * vm, + unformat_input_t * input, vlib_cli_command_t * cmd) +{ + ip46_address_t ip = ip46_address_initializer; + mac_address_t mac = ZERO_MAC_ADDRESS; + vnet_main_t *vnm = vnet_get_main (); + ip_neighbor_flags_t flags; + u32 sw_if_index = ~0; + int is_add = 1; + int count = 1; + + flags = IP_NEIGHBOR_FLAG_DYNAMIC; + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + /* set ip arp TenGigE1/1/0/1 1.2.3.4 aa:bb:... or aabb.ccdd... */ + if (unformat (input, "%U %U %U", + unformat_vnet_sw_interface, vnm, &sw_if_index, + unformat_ip46_address, &ip, IP46_TYPE_ANY, + unformat_mac_address_t, &mac)) + ; + else if (unformat (input, "delete") || unformat (input, "del")) + is_add = 0; + else if (unformat (input, "static")) + { + flags |= IP_NEIGHBOR_FLAG_STATIC; + flags &= ~IP_NEIGHBOR_FLAG_DYNAMIC; + } + else if (unformat (input, "no-fib-entry")) + flags |= IP_NEIGHBOR_FLAG_NO_FIB_ENTRY; + else if (unformat (input, "count %d", &count)) + ; + else + break; + } + + if (sw_if_index == ~0 || + ip46_address_is_zero (&ip) || mac_address_is_zero (&mac)) + return clib_error_return (0, + "specify interface, IP address and MAC: `%U'", + format_unformat_error, input); + + while (count) + { + if (is_add) + ip_neighbor_add (&ip, ip46_address_get_type (&ip), &mac, sw_if_index, + flags, NULL); + else + ip_neighbor_del (&ip, ip46_address_get_type (&ip), sw_if_index); + + ip46_address_increment (ip46_address_get_type (&ip), &ip); + mac_address_increment (&mac); + + --count; + } + + return NULL; +} + +/* *INDENT-OFF* */ +/*? + * Add or delete IPv4 ARP cache entries. + * + * @note 'set ip neighbor' options (e.g. delete, static, 'fib-id <id>', + * 'count <number>', 'interface ip4_addr mac_addr') can be added in + * any order and combination. + * + * @cliexpar + * @parblock + * Add or delete IPv4 ARP cache entries as follows. MAC Address can be in + * either aa:bb:cc:dd:ee:ff format or aabb.ccdd.eeff format. + * @cliexcmd{set ip neighbor GigabitEthernet2/0/0 6.0.0.3 dead.beef.babe} + * @cliexcmd{set ip neighbor delete GigabitEthernet2/0/0 6.0.0.3 de:ad:be:ef:ba:be} + * + * To add or delete an IPv4 ARP cache entry to or from a specific fib + * table: + * @cliexcmd{set ip neighbor fib-id 1 GigabitEthernet2/0/0 6.0.0.3 dead.beef.babe} + * @cliexcmd{set ip neighbor fib-id 1 delete GigabitEthernet2/0/0 6.0.0.3 dead.beef.babe} + * + * Add or delete IPv4 static ARP cache entries as follows: + * @cliexcmd{set ip neighbor static GigabitEthernet2/0/0 6.0.0.3 dead.beef.babe} + * @cliexcmd{set ip neighbor static delete GigabitEthernet2/0/0 6.0.0.3 dead.beef.babe} + * + * For testing / debugging purposes, the 'set ip neighbor' command can add or + * delete multiple entries. Supply the 'count N' parameter: + * @cliexcmd{set ip neighbor count 10 GigabitEthernet2/0/0 6.0.0.3 dead.beef.babe} + * @endparblock + ?*/ +VLIB_CLI_COMMAND (ip_neighbor_command, static) = { + .path = "set ip neighbor", + .short_help = + "set ip neighbor [del] <intfc> <ip-address> <mac-address> [static] [no-fib-entry] [count <count>] [fib-id <fib-id>] [proxy <lo-addr> - <hi-addr>]", + .function = ip_neighbor_cmd, +}; +VLIB_CLI_COMMAND (ip_neighbor_command2, static) = { + .path = "ip neighbor", + .short_help = + "ip neighbor [del] <intfc> <ip-address> <mac-address> [static] [no-fib-entry] [count <count>] [fib-id <fib-id>] [proxy <lo-addr> - <hi-addr>]", + .function = ip_neighbor_cmd, +}; +/* *INDENT-ON* */ + +static int +ip_neighbor_sort (void *a1, void *a2) +{ + index_t *ipni1 = a1, *ipni2 = a2; + ip_neighbor_t *ipn1, *ipn2; + int cmp; + + ipn1 = ip_neighbor_get (*ipni1); + ipn2 = ip_neighbor_get (*ipni2); + + cmp = vnet_sw_interface_compare (vnet_get_main (), + ipn1->ipn_key->ipnk_sw_if_index, + ipn2->ipn_key->ipnk_sw_if_index); + if (!cmp) + cmp = ip46_address_cmp (&ipn1->ipn_key->ipnk_ip, &ipn2->ipn_key->ipnk_ip); + return cmp; +} + +static index_t * +ip_neighbor_entries (u32 sw_if_index, ip46_type_t type) +{ + index_t *ipnis = NULL; + ip_neighbor_t *ipn; + + /* *INDENT-OFF* */ + pool_foreach (ipn, ip_neighbor_pool, + ({ + if (sw_if_index != ~0 && + ipn->ipn_key->ipnk_sw_if_index != sw_if_index && + (IP46_TYPE_ANY == type || + (ipn->ipn_key->ipnk_type == type))) + continue; + vec_add1 (ipnis, ip_neighbor_get_index(ipn)); + })); + + /* *INDENT-ON* */ + + if (ipnis) + vec_sort_with_function (ipnis, ip_neighbor_sort); + return ipnis; +} + +static clib_error_t * +ip_neighbor_show_sorted_i (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd, ip46_type_t type) +{ + ip_neighbor_elt_t *elt, *head; + + head = pool_elt_at_index (ip_neighbor_elt_pool, + ip_neighbor_list_head[type]); + + + vlib_cli_output (vm, "%=12s%=40s%=6s%=20s%=24s", "Time", "IP", + "Flags", "Ethernet", "Interface"); + + /* *INDENT-OFF*/ + /* the list is time sorted, newest first, so start from the back + * and work forwards. Stop when we get to one that is alive */ + clib_llist_foreach_reverse(ip_neighbor_elt_pool, + ipne_anchor, head, elt, + ({ + vlib_cli_output (vm, "%U", format_ip_neighbor, elt->ipne_index); + })); + /* *INDENT-ON*/ + + return (NULL); +} + +static clib_error_t * +ip_neighbor_show_i (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd, ip46_type_t type) +{ + index_t *ipni, *ipnis = NULL; + u32 sw_if_index; + + /* Filter entries by interface if given. */ + sw_if_index = ~0; + (void) unformat_user (input, unformat_vnet_sw_interface, vnet_get_main (), + &sw_if_index); + + ipnis = ip_neighbor_entries (sw_if_index, type); + + if (ipnis) + vlib_cli_output (vm, "%=12s%=40s%=6s%=20s%=24s", "Time", "IP", + "Flags", "Ethernet", "Interface"); + + vec_foreach (ipni, ipnis) + { + vlib_cli_output (vm, "%U", format_ip_neighbor, *ipni); + } + vec_free (ipnis); + + return (NULL); +} + +static clib_error_t * +ip_neighbor_show (vlib_main_t * vm, + unformat_input_t * input, vlib_cli_command_t * cmd) +{ + return (ip_neighbor_show_i (vm, input, cmd, IP46_TYPE_ANY)); +} + +static clib_error_t * +ip6_neighbor_show (vlib_main_t * vm, + unformat_input_t * input, vlib_cli_command_t * cmd) +{ + return (ip_neighbor_show_i (vm, input, cmd, IP46_TYPE_IP6)); +} + +static clib_error_t * +ip4_neighbor_show (vlib_main_t * vm, + unformat_input_t * input, vlib_cli_command_t * cmd) +{ + return (ip_neighbor_show_i (vm, input, cmd, IP46_TYPE_IP4)); +} + +static clib_error_t * +ip6_neighbor_show_sorted (vlib_main_t * vm, + unformat_input_t * input, vlib_cli_command_t * cmd) +{ + return (ip_neighbor_show_sorted_i (vm, input, cmd, IP46_TYPE_IP6)); +} + +static clib_error_t * +ip4_neighbor_show_sorted (vlib_main_t * vm, + unformat_input_t * input, vlib_cli_command_t * cmd) +{ + return (ip_neighbor_show_sorted_i (vm, input, cmd, IP46_TYPE_IP4)); +} + +/*? + * Display all the IP neighbor entries. + * + * @cliexpar + * Example of how to display the IPv4 ARP table: + * @cliexstart{show ip neighbor} + * Time FIB IP4 Flags Ethernet Interface + * 346.3028 0 6.1.1.3 de:ad:be:ef:ba:be GigabitEthernet2/0/0 + * 3077.4271 0 6.1.1.4 S de:ad:be:ef:ff:ff GigabitEthernet2/0/0 + * 2998.6409 1 6.2.2.3 de:ad:be:ef:00:01 GigabitEthernet2/0/0 + * Proxy arps enabled for: + * Fib_index 0 6.0.0.1 - 6.0.0.11 + * @cliexend + ?*/ +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (show_ip_neighbors_cmd_node, static) = { + .path = "show ip neighbors", + .function = ip_neighbor_show, + .short_help = "show ip neighbors [interface]", +}; +VLIB_CLI_COMMAND (show_ip4_neighbors_cmd_node, static) = { + .path = "show ip4 neighbors", + .function = ip4_neighbor_show, + .short_help = "show ip4 neighbors [interface]", +}; +VLIB_CLI_COMMAND (show_ip6_neighbors_cmd_node, static) = { + .path = "show ip6 neighbors", + .function = ip6_neighbor_show, + .short_help = "show ip6 neighbors [interface]", +}; +VLIB_CLI_COMMAND (show_ip_neighbor_cmd_node, static) = { + .path = "show ip neighbor", + .function = ip_neighbor_show, + .short_help = "show ip neighbor [interface]", +}; +VLIB_CLI_COMMAND (show_ip4_neighbor_cmd_node, static) = { + .path = "show ip4 neighbor", + .function = ip4_neighbor_show, + .short_help = "show ip4 neighbor [interface]", +}; +VLIB_CLI_COMMAND (show_ip6_neighbor_cmd_node, static) = { + .path = "show ip6 neighbor", + .function = ip6_neighbor_show, + .short_help = "show ip6 neighbor [interface]", +}; +VLIB_CLI_COMMAND (show_ip4_neighbor_sorted_cmd_node, static) = { + .path = "show ip4 neighbor-sorted", + .function = ip4_neighbor_show_sorted, + .short_help = "show ip4 neighbor-sorted", +}; +VLIB_CLI_COMMAND (show_ip6_neighbor_sorted_cmd_node, static) = { + .path = "show ip6 neighbor-sorted", + .function = ip6_neighbor_show_sorted, + .short_help = "show ip6 neighbor-sorted", +}; +/* *INDENT-ON* */ + +static ip_neighbor_vft_t ip_nbr_vfts[IP46_N_TYPES]; + +void +ip_neighbor_register (ip46_type_t type, const ip_neighbor_vft_t * vft) +{ + ip_nbr_vfts[type] = *vft; +} + +void +ip_neighbor_probe_dst (const ip_adjacency_t * adj, const ip46_address_t * dst) +{ + if (!vnet_sw_interface_is_admin_up (vnet_get_main (), + adj->rewrite_header.sw_if_index)) + return; + + switch (adj->ia_nh_proto) + { + case FIB_PROTOCOL_IP6: + ip6_neighbor_probe_dst (adj, &dst->ip6); + break; + case FIB_PROTOCOL_IP4: + ip4_neighbor_probe_dst (adj, &dst->ip4); + break; + case FIB_PROTOCOL_MPLS: + ASSERT (0); + break; + } +} + +void +ip_neighbor_probe (const ip_adjacency_t * adj) +{ + ip_neighbor_probe_dst (adj, &adj->sub_type.nbr.next_hop); +} + +void +ip_neighbor_advertise (vlib_main_t * vm, + ip46_type_t type, + const ip46_address_t * addr, u32 sw_if_index) +{ + vnet_main_t *vnm = vnet_get_main (); + + if (type == IP46_TYPE_IP4 || type == IP46_TYPE_BOTH) + ip4_neighbor_advertise (vm, vnm, sw_if_index, &addr->ip4); + if (type == IP46_TYPE_IP6 || type == IP46_TYPE_BOTH) + ip6_neighbor_advertise (vm, vnm, sw_if_index, &addr->ip6); +} + +void +ip_neighbor_walk (ip46_type_t type, + u32 sw_if_index, ip_neighbor_walk_cb_t cb, void *ctx) +{ + ip_neighbor_key_t *key; + index_t ipni; + + if (~0 == sw_if_index) + { + uword **hash; + + vec_foreach (hash, ip_neighbor_db[type].ipndb_hash) + { + /* *INDENT-OFF* */ + hash_foreach (key, ipni, *hash, + ({ + cb (ipni, ctx); + })); + /* *INDENT-ON* */ + } + } + else + { + uword *hash; + + if (vec_len (ip_neighbor_db[type].ipndb_hash) <= sw_if_index) + return; + hash = ip_neighbor_db[type].ipndb_hash[sw_if_index]; + + /* *INDENT-OFF* */ + hash_foreach (key, ipni, hash, + ({ + cb (ipni, ctx); + })); + /* *INDENT-ON* */ + } +} + +int +ip4_neighbor_proxy_add (u32 fib_index, + const ip4_address_t * start, + const ip4_address_t * end) +{ + if (ip_nbr_vfts[IP46_TYPE_IP4].inv_proxy4_add) + { + return (ip_nbr_vfts[IP46_TYPE_IP4].inv_proxy4_add + (fib_index, start, end)); + } + + return (-1); +} + +int +ip4_neighbor_proxy_delete (u32 fib_index, + const ip4_address_t * start, + const ip4_address_t * end) +{ + if (ip_nbr_vfts[IP46_TYPE_IP4].inv_proxy4_del) + { + return (ip_nbr_vfts[IP46_TYPE_IP4].inv_proxy4_del + (fib_index, start, end)); + } + return -1; +} + +int +ip4_neighbor_proxy_enable (u32 sw_if_index) +{ + if (ip_nbr_vfts[IP46_TYPE_IP4].inv_proxy4_enable) + { + return (ip_nbr_vfts[IP46_TYPE_IP4].inv_proxy4_enable (sw_if_index)); + } + return -1; +} + +int +ip4_neighbor_proxy_disable (u32 sw_if_index) +{ + if (ip_nbr_vfts[IP46_TYPE_IP4].inv_proxy4_disable) + { + return (ip_nbr_vfts[IP46_TYPE_IP4].inv_proxy4_disable (sw_if_index)); + } + return -1; +} + +int +ip6_neighbor_proxy_add (u32 sw_if_index, const ip6_address_t * addr) +{ + if (ip_nbr_vfts[IP46_TYPE_IP6].inv_proxy6_add) + { + return (ip_nbr_vfts[IP46_TYPE_IP6].inv_proxy6_add (sw_if_index, addr)); + } + return -1; +} + +int +ip6_neighbor_proxy_del (u32 sw_if_index, const ip6_address_t * addr) +{ + if (ip_nbr_vfts[IP46_TYPE_IP6].inv_proxy6_del) + { + return (ip_nbr_vfts[IP46_TYPE_IP6].inv_proxy6_del (sw_if_index, addr)); + } + return -1; +} + +static void +ip_neighbor_ethernet_change_mac (ethernet_main_t * em, + u32 sw_if_index, uword opaque) +{ + ip_neighbor_t *ipn; + adj_index_t ai; + + IP_NEIGHBOR_DBG ("mac-change: %U", + format_vnet_sw_if_index_name, vnet_get_main (), + sw_if_index); + + /* *INDENT-OFF* */ + pool_foreach (ipn, ip_neighbor_pool, + ({ + if (ipn->ipn_key->ipnk_sw_if_index == sw_if_index) + adj_nbr_walk_nh (ipn->ipn_key->ipnk_sw_if_index, + fib_proto_from_ip46(ipn->ipn_key->ipnk_type), + &ipn->ipn_key->ipnk_ip, + ip_neighbor_mk_complete_walk, + ipn); + })); + /* *INDENT-ON* */ + + ai = adj_glean_get (FIB_PROTOCOL_IP4, sw_if_index); + + if (ADJ_INDEX_INVALID != ai) + adj_glean_update_rewrite (ai); +} + +void +ip_neighbor_populate (ip46_type_t type, u32 sw_if_index) +{ + index_t *ipnis = NULL, *ipni; + ip_neighbor_t *ipn; + + IP_NEIGHBOR_DBG ("populate: %U %U", + format_vnet_sw_if_index_name, vnet_get_main (), + sw_if_index, format_ip46_type, type); + + /* *INDENT-OFF* */ + pool_foreach (ipn, ip_neighbor_pool, + ({ + if (ipn->ipn_key->ipnk_type == type && + ipn->ipn_key->ipnk_sw_if_index == sw_if_index) + vec_add1 (ipnis, ipn - ip_neighbor_pool); + })); + /* *INDENT-ON* */ + + vec_foreach (ipni, ipnis) + { + ipn = ip_neighbor_get (*ipni); + + adj_nbr_walk_nh (ipn->ipn_key->ipnk_sw_if_index, + fib_proto_from_ip46 (ipn->ipn_key->ipnk_type), + &ipn->ipn_key->ipnk_ip, + ip_neighbor_mk_complete_walk, ipn); + } + vec_free (ipnis); +} + +void +ip_neighbor_flush (ip46_type_t type, u32 sw_if_index) +{ + index_t *ipnis = NULL, *ipni; + ip_neighbor_t *ipn; + + IP_NEIGHBOR_DBG ("flush: %U %U", + format_vnet_sw_if_index_name, vnet_get_main (), + sw_if_index, format_ip46_type, type); + + /* *INDENT-OFF* */ + pool_foreach (ipn, ip_neighbor_pool, + ({ + if (ipn->ipn_key->ipnk_type == type && + ipn->ipn_key->ipnk_sw_if_index == sw_if_index && + ip_neighbor_is_dynamic (ipn)) + vec_add1 (ipnis, ipn - ip_neighbor_pool); + })); + /* *INDENT-ON* */ + + vec_foreach (ipni, ipnis) ip_neighbor_free (ip_neighbor_get (*ipni)); + vec_free (ipnis); +} + +/* + * Remove any arp entries associated with the specified interface + */ +static clib_error_t * +ip_neighbor_interface_admin_change (vnet_main_t * vnm, + u32 sw_if_index, u32 flags) +{ + ip46_type_t type; + + IP_NEIGHBOR_DBG ("interface-admin: %U %s", + format_vnet_sw_if_index_name, vnet_get_main (), + sw_if_index, + (flags & VNET_SW_INTERFACE_FLAG_ADMIN_UP ? "up" : "down")); + + if (flags & VNET_SW_INTERFACE_FLAG_ADMIN_UP) + { + FOREACH_IP46_TYPE (type) ip_neighbor_populate (type, sw_if_index); + } + else + { + /* admin down, flush all neighbours */ + FOREACH_IP46_TYPE (type) ip_neighbor_flush (type, sw_if_index); + } + + return (NULL); +} + +VNET_SW_INTERFACE_ADMIN_UP_DOWN_FUNCTION (ip_neighbor_interface_admin_change); + +/* + * Remove any arp entries associated with the specified interface + */ +static clib_error_t * +ip_neighbor_delete_sw_interface (vnet_main_t * vnm, + u32 sw_if_index, u32 is_add) +{ + IP_NEIGHBOR_DBG ("interface-change: %U %s", + format_vnet_sw_if_index_name, vnet_get_main (), + sw_if_index, (is_add ? "add" : "del")); + + if (!is_add && sw_if_index != ~0) + { + ip46_type_t type; + + FOREACH_IP46_TYPE (type) ip_neighbor_flush (type, sw_if_index); + } + + return (NULL); +} + +VNET_SW_INTERFACE_ADD_DEL_FUNCTION (ip_neighbor_delete_sw_interface); + +typedef struct ip_neighbor_walk_covered_ctx_t_ +{ + ip46_type_t type; + ip46_address_t addr; + u32 length; + index_t *ipnis; +} ip_neighbor_walk_covered_ctx_t; + +static walk_rc_t +ip_neighbor_walk_covered (index_t ipni, void *arg) +{ + ip_neighbor_walk_covered_ctx_t *ctx = arg; + ip_neighbor_t *ipn; + + ipn = ip_neighbor_get (ipni); + + ASSERT (ipn->ipn_key->ipnk_type == ctx->type); + + if (IP46_TYPE_IP4 == ctx->type) + { + if (ip4_destination_matches_route (&ip4_main, + &ipn->ipn_key->ipnk_ip.ip4, + &ctx->addr.ip4, + ctx->length) && + ip_neighbor_is_dynamic (ipn)) + { + vec_add1 (ctx->ipnis, ip_neighbor_get_index (ipn)); + } + } + return (WALK_CONTINUE); +} + + +/* + * callback when an interface address is added or deleted + */ +static void +ip_neighbor_add_del_interface_address_v4 (ip4_main_t * im, + uword opaque, + u32 sw_if_index, + ip4_address_t * address, + u32 address_length, + u32 if_address_index, u32 is_del) +{ + /* + * Flush the ARP cache of all entries covered by the address + * that is being removed. + */ + IP_NEIGHBOR_DBG ("addr-%d: %U, %U/%d", + (is_del ? "del" : "add"), + format_vnet_sw_if_index_name, vnet_get_main (), + sw_if_index, format_ip4_address, address, address_length); + + if (is_del) + { + ip_neighbor_walk_covered_ctx_t ctx = { + .addr.ip4 = *address, + .type = IP46_TYPE_IP4, + .length = address_length, + }; + index_t *ipni; + + ip_neighbor_walk (IP46_TYPE_IP4, sw_if_index, + ip_neighbor_walk_covered, &ctx); + + vec_foreach (ipni, ctx.ipnis) + ip_neighbor_free (ip_neighbor_get (*ipni)); + + vec_free (ctx.ipnis); + } +} + +/* + * callback when an interface address is added or deleted + */ +static void +ip_neighbor_add_del_interface_address_v6 (ip6_main_t * im, + uword opaque, + u32 sw_if_index, + ip6_address_t * address, + u32 address_length, + u32 if_address_index, u32 is_del) +{ + /* + * Flush the ARP cache of all entries covered by the address + * that is being removed. + */ + IP_NEIGHBOR_DBG ("addr-change: %U, %U/%d %s", + format_vnet_sw_if_index_name, vnet_get_main (), + sw_if_index, format_ip6_address, address, address_length, + (is_del ? "del" : "add")); + + if (is_del) + { + ip_neighbor_walk_covered_ctx_t ctx = { + .addr.ip6 = *address, + .type = IP46_TYPE_IP6, + .length = address_length, + }; + index_t *ipni; + + ip_neighbor_walk (IP46_TYPE_IP6, sw_if_index, + ip_neighbor_walk_covered, &ctx); + + vec_foreach (ipni, ctx.ipnis) + ip_neighbor_free (ip_neighbor_get (*ipni)); + + vec_free (ctx.ipnis); + } +} + +typedef struct ip_neighbor_table_bind_ctx_t_ +{ + u32 new_fib_index; + u32 old_fib_index; +} ip_neighbor_table_bind_ctx_t; + +static walk_rc_t +ip_neighbor_walk_table_bind (index_t ipni, void *arg) +{ + ip_neighbor_table_bind_ctx_t *ctx = arg; + ip_neighbor_t *ipn; + + ipn = ip_neighbor_get (ipni); + ip_neighbor_adj_fib_remove (ipn, ctx->old_fib_index); + ip_neighbor_adj_fib_add (ipn, ctx->new_fib_index); + + return (WALK_CONTINUE); +} + +static void +ip_neighbor_table_bind_v4 (ip4_main_t * im, + uword opaque, + u32 sw_if_index, + u32 new_fib_index, u32 old_fib_index) +{ + ip_neighbor_table_bind_ctx_t ctx = { + .old_fib_index = old_fib_index, + .new_fib_index = new_fib_index, + }; + + ip_neighbor_walk (IP46_TYPE_IP4, sw_if_index, + ip_neighbor_walk_table_bind, &ctx); +} + +static void +ip_neighbor_table_bind_v6 (ip6_main_t * im, + uword opaque, + u32 sw_if_index, + u32 new_fib_index, u32 old_fib_index) +{ + ip_neighbor_table_bind_ctx_t ctx = { + .old_fib_index = old_fib_index, + .new_fib_index = new_fib_index, + }; + + ip_neighbor_walk (IP46_TYPE_IP6, sw_if_index, + ip_neighbor_walk_table_bind, &ctx); +} + +typedef enum ip_neighbor_age_state_t_ +{ + IP_NEIGHBOR_AGE_ALIVE, + IP_NEIGHBOR_AGE_PROBE, + IP_NEIGHBOR_AGE_DEAD, +} ip_neighbor_age_state_t; + +#define IP_NEIGHBOR_PROCESS_SLEEP_LONG (0) + +static ip_neighbor_age_state_t +ip_neighbour_age_out (index_t ipni, f64 now, f64 * wait) +{ + ip_neighbor_t *ipn; + f64 ttl; + + ipn = ip_neighbor_get (ipni); + ttl = now - ipn->ipn_time_last_updated; + *wait = IP_NEIGHBOR_PROCESS_SLEEP_LONG; + + if (ttl > ip_neighbor_db[ipn->ipn_key->ipnk_type].ipndb_age) + { + IP_NEIGHBOR_DBG ("aged: %U @%f - %f > %d", + format_ip_neighbor, ipni, now, + ipn->ipn_time_last_updated, + ip_neighbor_db[ipn->ipn_key->ipnk_type].ipndb_age); + if (ipn->ipn_n_probes > 2) + { + /* 3 strikes and yea-re out */ + IP_NEIGHBOR_DBG ("dead: %U", format_ip_neighbor, ipni); + return (IP_NEIGHBOR_AGE_DEAD); + } + else + { + adj_index_t ai; + + ai = adj_glean_get (fib_proto_from_ip46 (ipn->ipn_key->ipnk_type), + ip_neighbor_get_sw_if_index (ipn)); + + if (ADJ_INDEX_INVALID != ai) + ip_neighbor_probe_dst (adj_get (ai), ip_neighbor_get_ip (ipn)); + + ipn->ipn_n_probes++; + *wait = 1; + } + } + else + { + *wait = ttl; + return (IP_NEIGHBOR_AGE_ALIVE); + } + + return (IP_NEIGHBOR_AGE_PROBE); +} + +typedef enum ip_neighbor_process_event_t_ +{ + IP_NEIGHBOR_AGE_PROCESS_WAKEUP, +} ip_neighbor_process_event_t; + +static uword +ip_neighbor_age_loop (vlib_main_t * vm, + vlib_node_runtime_t * rt, + vlib_frame_t * f, ip46_type_t type) +{ + uword event_type, *event_data = NULL; + f64 timeout; + + /* Set the timeout to an effectively infinite value when the process starts */ + timeout = IP_NEIGHBOR_PROCESS_SLEEP_LONG; + + while (1) + { + f64 now; + + if (!timeout) + vlib_process_wait_for_event (vm); + else + vlib_process_wait_for_event_or_clock (vm, timeout); + + event_type = vlib_process_get_events (vm, &event_data); + vec_reset_length (event_data); + + now = vlib_time_now (vm); + + switch (event_type) + { + case ~0: + { + /* timer expired */ + ip_neighbor_elt_t *elt, *head; + f64 wait; + + timeout = 1e5; + head = pool_elt_at_index (ip_neighbor_elt_pool, + ip_neighbor_list_head[type]); + + /* *INDENT-OFF*/ + /* the list is time sorted, newest first, so start from the back + * and work forwards. Stop when we get to one that is alive */ + restart: + clib_llist_foreach_reverse(ip_neighbor_elt_pool, + ipne_anchor, head, elt, + ({ + ip_neighbor_age_state_t res; + + res = ip_neighbour_age_out(elt->ipne_index, now, &wait); + + if (IP_NEIGHBOR_AGE_ALIVE == res) { + /* the oldest neighbor has not yet expired, go back to sleep */ + break; + } + else if (IP_NEIGHBOR_AGE_DEAD == res) { + /* the oldest neighbor is dead, pop it, then restart the walk + * again from the back */ + ip_neighbor_free (ip_neighbor_get(elt->ipne_index)); + goto restart; + } + + timeout = clib_min (wait, timeout); + })); + /* *INDENT-ON* */ + break; + } + case IP_NEIGHBOR_AGE_PROCESS_WAKEUP: + { + + if (!ip_neighbor_db[type].ipndb_age) + { + /* aging has been disabled */ + timeout = 0; + break; + } + ip_neighbor_elt_t *elt, *head; + + head = pool_elt_at_index (ip_neighbor_elt_pool, + ip_neighbor_list_head[type]); + elt = clib_llist_prev (ip_neighbor_elt_pool, ipne_anchor, head); + + /* poke the oldset neighbour for aging, which returns how long we sleep for */ + if (IP_NEIGHBOR_AGE_PROBE == + ip_neighbour_age_out (elt->ipne_index, now, &timeout)) + /* we probed for the oldest entry, sleep for a short time to get to the next */ + timeout = 0.01; + break; + } + } + } + return 0; +} + +static uword +ip4_neighbor_age_process (vlib_main_t * vm, + vlib_node_runtime_t * rt, vlib_frame_t * f) +{ + return (ip_neighbor_age_loop (vm, rt, f, IP46_TYPE_IP4)); +} + +static uword +ip6_neighbor_age_process (vlib_main_t * vm, + vlib_node_runtime_t * rt, vlib_frame_t * f) +{ + return (ip_neighbor_age_loop (vm, rt, f, IP46_TYPE_IP6)); +} + +/* *INDENT-OFF* */ +VLIB_REGISTER_NODE (ip4_neighbor_age_process_node,static) = { + .function = ip4_neighbor_age_process, + .type = VLIB_NODE_TYPE_PROCESS, + .name = "ip4-neighbor-age-process", +}; +VLIB_REGISTER_NODE (ip6_neighbor_age_process_node,static) = { + .function = ip6_neighbor_age_process, + .type = VLIB_NODE_TYPE_PROCESS, + .name = "ip6-neighbor-age-process", +}; +/* *INDENT-ON* */ + +int +ip_neighbor_config (ip46_type_t type, u32 limit, u32 age, bool recycle) +{ + ip_neighbor_db[type].ipndb_limit = limit; + ip_neighbor_db[type].ipndb_recycle = recycle; + ip_neighbor_db[type].ipndb_age = age; + + vlib_process_signal_event (vlib_get_main (), + (IP46_TYPE_IP4 == type ? + ip4_neighbor_age_process_node.index : + ip6_neighbor_age_process_node.index), + IP_NEIGHBOR_AGE_PROCESS_WAKEUP, 0); + + return (0); +} + +static clib_error_t * +ip_neighbor_config_show (vlib_main_t * vm, + unformat_input_t * input, vlib_cli_command_t * cmd) +{ + ip46_type_t type; + + /* *INDENT-OFF* */ + FOREACH_IP46_TYPE(type) { + vlib_cli_output (vm, "%U:", format_ip46_type, type); + vlib_cli_output (vm, " limit:%d, age:%d, recycle:%d", + ip_neighbor_db[type].ipndb_limit, + ip_neighbor_db[type].ipndb_age, + ip_neighbor_db[type].ipndb_recycle); + } + + /* *INDENT-ON* */ + return (NULL); +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (show_ip_neighbor_cfg_cmd_node, static) = { + .path = "show ip neighbor-config", + .function = ip_neighbor_config_show, + .short_help = "show ip neighbor-config", +}; +/* *INDENT-ON* */ + +static clib_error_t * +ip_neighbor_init (vlib_main_t * vm) +{ + { + ip4_add_del_interface_address_callback_t cb = { + .function = ip_neighbor_add_del_interface_address_v4, + }; + vec_add1 (ip4_main.add_del_interface_address_callbacks, cb); + } + { + ip6_add_del_interface_address_callback_t cb = { + .function = ip_neighbor_add_del_interface_address_v6, + }; + vec_add1 (ip6_main.add_del_interface_address_callbacks, cb); + } + { + ip4_table_bind_callback_t cb = { + .function = ip_neighbor_table_bind_v4, + }; + vec_add1 (ip4_main.table_bind_callbacks, cb); + } + { + ip6_table_bind_callback_t cb = { + .function = ip_neighbor_table_bind_v6, + }; + vec_add1 (ip6_main.table_bind_callbacks, cb); + } + { + ethernet_address_change_ctx_t ctx = { + .function = ip_neighbor_ethernet_change_mac, + .function_opaque = 0, + }; + vec_add1 (ethernet_main.address_change_callbacks, ctx); + } + + ipn_logger = vlib_log_register_class ("ip", "neighbor"); + + ip46_type_t type; + + FOREACH_IP46_TYPE (type) + ip_neighbor_list_head[type] = + clib_llist_make_head (ip_neighbor_elt_pool, ipne_anchor); + + return (NULL); +} + +/* *INDENT-OFF* */ +VLIB_INIT_FUNCTION (ip_neighbor_init) = +{ + .runs_after = VLIB_INITS("ip_main_init"), +}; +/* *INDENT-ON* */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vnet/ip-neighbor/ip_neighbor.h b/src/vnet/ip-neighbor/ip_neighbor.h new file mode 100644 index 00000000000..cb384c5e240 --- /dev/null +++ b/src/vnet/ip-neighbor/ip_neighbor.h @@ -0,0 +1,124 @@ +/* + * ip_neighboor.h: ip neighbor generic services + * + * 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 __INCLUDE_IP_NEIGHBOR_H__ +#define __INCLUDE_IP_NEIGHBOR_H__ + +#include <vnet/ip-neighbor/ip_neighbor_types.h> + +#include <vnet/adj/adj.h> + +void ip_neighbor_scan_enable_disable (ip_neighbor_scan_arg_t * arg); + + +/***** + * APIs external modules can invoke on the neighbor subsystem + */ + +extern ip_neighbor_t *ip_neighbor_get (index_t ipni); +extern int ip_neighbor_add (const ip46_address_t * ip, + ip46_type_t type, + const mac_address_t * mac, + u32 sw_if_index, + ip_neighbor_flags_t flags, u32 * stats_index); +extern int ip_neighbor_del (const ip46_address_t * ip, + ip46_type_t type, u32 sw_if_index); + +extern int ip_neighbor_config (ip46_type_t type, u32 limit, u32 age, + bool recycle); + +typedef walk_rc_t (*ip_neighbor_walk_cb_t) (index_t ipni, void *ctx); +extern void ip_neighbor_walk (ip46_type_t type, + u32 sw_if_index, + ip_neighbor_walk_cb_t fn, void *ctx); + +extern const ip46_address_t *ip_neighbor_get_ip (const ip_neighbor_t * ipn); +extern const mac_address_t *ip_neighbor_get_mac (const ip_neighbor_t * ipn); +extern const u32 ip_neighbor_get_sw_if_index (const ip_neighbor_t * ipn); + +extern void ip_neighbor_learn (const ip_neighbor_learn_t * l); + +extern void ip_neighbor_update (vnet_main_t * vnm, adj_index_t ai); + +extern void ip_neighbor_advertise (vlib_main_t * vm, + ip46_type_t tyoe, + const ip46_address_t * addr, + u32 sw_if_index); +extern void ip_neighbor_probe (const ip_adjacency_t * adj); +extern void ip_neighbor_probe_dst (const ip_adjacency_t * adj, + const ip46_address_t * ip); + +/** + * From the watcher to the API to publish a new neighbor + */ +extern void ip_neighbor_handle_event (const ip_neighbor_event_t * ipne); + +/** + * The set of function that vnet requires from the IP neighbour module. + * Note that an implementation of these functions will not exist + * if the ip-neighbour plugin is not loaded. so check the error codes! + */ +extern int ip4_neighbor_proxy_add (u32 fib_index, + const ip4_address_t * start, + const ip4_address_t * end); +extern int ip4_neighbor_proxy_delete (u32 fib_index, + const ip4_address_t * start, + const ip4_address_t * end); +extern int ip4_neighbor_proxy_enable (u32 sw_if_index); +extern int ip4_neighbor_proxy_disable (u32 sw_if_index); +extern int ip6_neighbor_proxy_add (u32 sw_if_index, + const ip6_address_t * addr); +extern int ip6_neighbor_proxy_del (u32 sw_if_index, + const ip6_address_t * addr); + +/** + * neighbor protocol implementation registration functions + * this are provided by ARP and IP-ND + */ +typedef int (*ip4_neighbor_proxy_addr_t) (u32 fib_index, + const ip4_address_t * start, + const ip4_address_t * end); +typedef int (*ip4_neighbor_proxy_cfg_t) (u32 sw_if_index); +typedef int (*ip6_neighbor_proxy_cfg_t) (u32 sw_if_index, + const ip6_address_t * addr); + +/** + * Virtual function Table for neighbor protocol implementations to register + */ +typedef struct ip_neighbor_vft_t_ +{ + ip4_neighbor_proxy_cfg_t inv_proxy4_enable; + ip4_neighbor_proxy_cfg_t inv_proxy4_disable; + ip4_neighbor_proxy_addr_t inv_proxy4_add; + ip4_neighbor_proxy_addr_t inv_proxy4_del; + ip6_neighbor_proxy_cfg_t inv_proxy6_add; + ip6_neighbor_proxy_cfg_t inv_proxy6_del; +} ip_neighbor_vft_t; + +extern void ip_neighbor_register (ip46_type_t type, + const ip_neighbor_vft_t * vft); + + +#endif /* __INCLUDE_IP_NEIGHBOR_H__ */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vnet/ip-neighbor/ip_neighbor_api.c b/src/vnet/ip-neighbor/ip_neighbor_api.c new file mode 100644 index 00000000000..b066423afe5 --- /dev/null +++ b/src/vnet/ip-neighbor/ip_neighbor_api.c @@ -0,0 +1,298 @@ +/* + *------------------------------------------------------------------ + * ip_api.c - vnet ip api + * + * Copyright (c) 2016 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 <stddef.h> + +#include <vnet/ip-neighbor/ip_neighbor.h> +#include <vnet/ip-neighbor/ip_neighbor_watch.h> +#include <vnet/ip/ip_types_api.h> +#include <vnet/ethernet/ethernet_types_api.h> + +#include <vlibapi/api.h> +#include <vlibmemory/api.h> + +#include <vnet/ip-neighbor/ip_neighbor.api_enum.h> +#include <vnet/ip-neighbor/ip_neighbor.api_types.h> + +static u16 msg_id_base; +#define REPLY_MSG_ID_BASE msg_id_base + +#include <vlibapi/api_helper_macros.h> + +#include <vnet/format_fns.h> + + +static ip46_type_t +ip46_type_from_af (ip_address_family_t af) +{ + return (AF_IP4 == af ? IP46_TYPE_IP4 : IP46_TYPE_IP6); +} + +static vl_api_ip_neighbor_flags_t +ip_neighbor_flags_encode (ip_neighbor_flags_t f) +{ + vl_api_ip_neighbor_flags_t v = IP_API_NEIGHBOR_FLAG_NONE; + + if (f & IP_NEIGHBOR_FLAG_STATIC) + v |= IP_API_NEIGHBOR_FLAG_STATIC; + if (f & IP_NEIGHBOR_FLAG_NO_FIB_ENTRY) + v |= IP_API_NEIGHBOR_FLAG_NO_FIB_ENTRY; + + return (v); +} + +static void +ip_neighbor_encode (vl_api_ip_neighbor_t * api, const ip_neighbor_t * ipn) +{ + api->sw_if_index = htonl (ipn->ipn_key->ipnk_sw_if_index); + api->flags = ip_neighbor_flags_encode (ipn->ipn_flags); + + ip_address_encode (&ipn->ipn_key->ipnk_ip, + ipn->ipn_key->ipnk_type, &api->ip_address); + mac_address_encode (&ipn->ipn_mac, api->mac_address); +} + +void +ip_neighbor_handle_event (const ip_neighbor_event_t * ipne) +{ + vl_api_ip_neighbor_event_t *mp; + vl_api_registration_t *reg; + const ip_neighbor_t *ipn; + + ipn = ip_neighbor_get (ipne->ipne_index); + + if (NULL == ipn) + /* Client can cancel, die, etc. */ + return; + + /* Customer(s) requesting event for this neighbor */ + reg = vl_api_client_index_to_registration (ipne->ipne_watch.ipw_client); + if (!reg) + return; + + if (vl_api_can_send_msg (reg)) + { + mp = vl_msg_api_alloc (sizeof (*mp)); + clib_memset (mp, 0, sizeof (*mp)); + mp->_vl_msg_id = ntohs (VL_API_IP_NEIGHBOR_EVENT + REPLY_MSG_ID_BASE); + mp->client_index = ipne->ipne_watch.ipw_client; + mp->pid = ipne->ipne_watch.ipw_pid; + + ip_neighbor_encode (&mp->neighbor, ipn); + + vl_api_send_msg (reg, (u8 *) mp); + } + else + { + static f64 last_time; + /* + * Throttle syslog msgs. + * It's pretty tempting to just revoke the registration... + */ + if (vlib_time_now (vlib_get_main ()) > last_time + 10.0) + { + clib_warning ("ip6 nd event for %U to pid %d: queue stuffed!", + format_ip46_address, &ipn->ipn_key->ipnk_ip, + IP46_TYPE_ANY, ipne->ipne_watch.ipw_pid); + last_time = vlib_time_now (vlib_get_main ()); + } + } +} + +typedef struct ip_neighbor_dump_ctx_t_ +{ + vl_api_registration_t *reg; + u32 context; +} ip_neighbor_dump_ctx_t; + +static walk_rc_t +send_ip_neighbor_details (index_t ipni, void *arg) +{ + ip_neighbor_dump_ctx_t *ctx = arg; + vl_api_ip_neighbor_details_t *mp; + ip_neighbor_t *ipn; + + ipn = ip_neighbor_get (ipni); + mp = vl_msg_api_alloc (sizeof (*mp)); + clib_memset (mp, 0, sizeof (*mp)); + mp->_vl_msg_id = ntohs (VL_API_IP_NEIGHBOR_DETAILS + REPLY_MSG_ID_BASE); + mp->context = ctx->context; + ip_neighbor_encode (&mp->neighbor, ipn); + + vl_api_send_msg (ctx->reg, (u8 *) mp); + + return (WALK_CONTINUE); +} + +static void +vl_api_ip_neighbor_dump_t_handler (vl_api_ip_neighbor_dump_t * mp) +{ + vl_api_registration_t *reg; + ip_address_family_t af; + int rv; + + reg = vl_api_client_index_to_registration (mp->client_index); + if (!reg) + return; + + u32 sw_if_index = ntohl (mp->sw_if_index); + + rv = ip_address_family_decode (mp->af, &af); + + if (rv) + return; + + ip_neighbor_dump_ctx_t ctx = { + .reg = reg, + .context = mp->context, + }; + + // walk all neighbours on all interfaces + ip_neighbor_walk ((af == AF_IP4 ? + IP46_TYPE_IP4 : + IP46_TYPE_IP6), + sw_if_index, send_ip_neighbor_details, &ctx); +} + +static ip_neighbor_flags_t +ip_neighbor_flags_decode (vl_api_ip_neighbor_flags_t v) +{ + ip_neighbor_flags_t f = IP_NEIGHBOR_FLAG_NONE; + + if (v & IP_API_NEIGHBOR_FLAG_STATIC) + f |= IP_NEIGHBOR_FLAG_STATIC; + if (v & IP_API_NEIGHBOR_FLAG_NO_FIB_ENTRY) + f |= IP_NEIGHBOR_FLAG_NO_FIB_ENTRY; + + return (f); +} + +static void +vl_api_ip_neighbor_add_del_t_handler (vl_api_ip_neighbor_add_del_t * mp, + vlib_main_t * vm) +{ + vl_api_ip_neighbor_add_del_reply_t *rmp; + ip_neighbor_flags_t flags; + u32 stats_index = ~0; + ip46_address_t ip = ip46_address_initializer; + mac_address_t mac; + ip46_type_t type; + int rv; + + VALIDATE_SW_IF_INDEX ((&mp->neighbor)); + + flags = ip_neighbor_flags_decode (mp->neighbor.flags); + type = ip_address_decode (&mp->neighbor.ip_address, &ip); + mac_address_decode (mp->neighbor.mac_address, &mac); + + /* must be static or dynamic, default to dynamic */ + if (!(flags & IP_NEIGHBOR_FLAG_STATIC) && + !(flags & IP_NEIGHBOR_FLAG_DYNAMIC)) + flags |= IP_NEIGHBOR_FLAG_DYNAMIC; + + /* + * there's no validation here of the ND/ARP entry being added. + * The expectation is that the FIB will ensure that nothing bad + * will come of adding bogus entries. + */ + if (mp->is_add) + rv = ip_neighbor_add (&ip, type, &mac, + ntohl (mp->neighbor.sw_if_index), + flags, &stats_index); + else + rv = ip_neighbor_del (&ip, type, ntohl (mp->neighbor.sw_if_index)); + + BAD_SW_IF_INDEX_LABEL; + + /* *INDENT-OFF* */ + REPLY_MACRO2 (VL_API_IP_NEIGHBOR_ADD_DEL_REPLY, + ({ + rmp->stats_index = htonl (stats_index); + })); + /* *INDENT-ON* */ +} + +static void +vl_api_want_ip_neighbor_events_t_handler (vl_api_want_ip_neighbor_events_t * + mp) +{ + vl_api_want_ip_neighbor_events_reply_t *rmp; + ip46_address_t ip; + ip46_type_t itype; + int rv = 0; + + if (mp->sw_if_index != ~0) + VALIDATE_SW_IF_INDEX (mp); + itype = ip_address_decode (&mp->ip, &ip); + + ip_neighbor_watcher_t watch = { + .ipw_client = mp->client_index, + .ipw_pid = mp->pid, + }; + + if (mp->enable) + ip_neighbor_watch (&ip, itype, ntohl (mp->sw_if_index), &watch); + else + ip_neighbor_unwatch (&ip, itype, ntohl (mp->sw_if_index), &watch); + + BAD_SW_IF_INDEX_LABEL; + REPLY_MACRO (VL_API_WANT_IP_NEIGHBOR_EVENTS_REPLY); +} + +static void +vl_api_ip_neighbor_config_t_handler (vl_api_ip_neighbor_config_t * mp) +{ + vl_api_ip_neighbor_config_reply_t *rmp; + ip_address_family_t af; + int rv; + + rv = ip_address_family_decode (mp->af, &af); + + if (!rv) + rv = ip_neighbor_config (ip46_type_from_af (af), + ntohl (mp->max_number), + ntohl (mp->max_age), mp->recycle); + + REPLY_MACRO (VL_API_IP_NEIGHBOR_CONFIG_REPLY); +} + +#define vl_msg_name_crc_list +#include <vnet/ip-neighbor/ip_neighbor.api.h> +#undef vl_msg_name_crc_list + +#include <vnet/ip-neighbor/ip_neighbor.api.c> + +static clib_error_t * +ip_neighbor_api_init (vlib_main_t * vm) +{ + /* Ask for a correctly-sized block of API message decode slots */ + msg_id_base = setup_message_id_table (); + + return 0; +} + +VLIB_INIT_FUNCTION (ip_neighbor_api_init); + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vnet/ip-neighbor/ip_neighbor_dp.c b/src/vnet/ip-neighbor/ip_neighbor_dp.c new file mode 100644 index 00000000000..6219a730af4 --- /dev/null +++ b/src/vnet/ip-neighbor/ip_neighbor_dp.c @@ -0,0 +1,39 @@ +/* + * ip_neighboor.h: ip neighbor generic services + * + * 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 <vlibmemory/api.h> + +#include <vnet/ip-neighbor/ip_neighbor_dp.h> +#include <vnet/ip-neighbor/ip_neighbor.h> + +/** + * APIs invoked by neighbor implementation (i.s. ARP and ND) that can be + * called from the DP when the protocol has resolved a neighbor + */ +void +ip_neighbor_learn_dp (const ip_neighbor_learn_t * l) +{ + vl_api_rpc_call_main_thread (ip_neighbor_learn, (u8 *) l, sizeof (*l)); +} + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vnet/ip-neighbor/ip_neighbor_dp.h b/src/vnet/ip-neighbor/ip_neighbor_dp.h new file mode 100644 index 00000000000..b5664c2d195 --- /dev/null +++ b/src/vnet/ip-neighbor/ip_neighbor_dp.h @@ -0,0 +1,38 @@ +/* + * ip_neighboor.h: ip neighbor generic services + * + * 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 __INCLUDE_IP_NEIGHBOR_DP_H__ +#define __INCLUDE_IP_NEIGHBOR_DP_H__ + +#include <vnet/ip-neighbor/ip_neighbor_types.h> + +/** + * APIs invoked by neighbor implementation (i.s. ARP and ND) that can be + * called from the DP when the protocol has resolved a neighbor + */ + +extern void ip_neighbor_learn_dp (const ip_neighbor_learn_t * l); + +#endif /* __INCLUDE_IP_NEIGHBOR_H__ */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vnet/ip-neighbor/ip_neighbor_types.c b/src/vnet/ip-neighbor/ip_neighbor_types.c new file mode 100644 index 00000000000..27262a5d62c --- /dev/null +++ b/src/vnet/ip-neighbor/ip_neighbor_types.c @@ -0,0 +1,83 @@ +/* + * ip_neighboor.h: ip neighbor generic services + * + * 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 <vnet/ip-neighbor/ip_neighbor_types.h> + +u8 * +format_ip_neighbor_flags (u8 * s, va_list * args) +{ + ip_neighbor_flags_t flags = va_arg (*args, int); + + if (flags & IP_NEIGHBOR_FLAG_STATIC) + s = format (s, "S"); + + if (flags & IP_NEIGHBOR_FLAG_DYNAMIC) + s = format (s, "D"); + + if (flags & IP_NEIGHBOR_FLAG_NO_FIB_ENTRY) + s = format (s, "N"); + + return s; +} + + +u8 * +format_ip_neighbor_key (u8 * s, va_list * va) +{ + ip_neighbor_key_t *key = va_arg (*va, ip_neighbor_key_t *); + + return (format (s, "[%U, %U]", + format_vnet_sw_if_index_name, vnet_get_main (), + key->ipnk_sw_if_index, + format_ip46_address, &key->ipnk_ip, key->ipnk_type)); +} + +u8 * +format_ip_neighbor_watcher (u8 * s, va_list * va) +{ + ip_neighbor_watcher_t *watcher = va_arg (*va, ip_neighbor_watcher_t *); + + return (format (s, "[pid:%d, client:%d]", + clib_host_to_net_u32 (watcher->ipw_pid), + clib_host_to_net_u32 (watcher->ipw_client))); +} + +u8 * +format_ip_neighbor (u8 * s, va_list * va) +{ + index_t ipni = va_arg (*va, index_t); + ip_neighbor_t *ipn; + + ipn = ip_neighbor_get (ipni); + + return (format (s, "%=12U%=40U%=6U%=20U%U", + format_vlib_time, vlib_get_main (), + ipn->ipn_time_last_updated, + format_ip46_address, &ipn->ipn_key->ipnk_ip, IP46_TYPE_ANY, + format_ip_neighbor_flags, ipn->ipn_flags, + format_mac_address_t, &ipn->ipn_mac, + format_vnet_sw_if_index_name, vnet_get_main (), + ipn->ipn_key->ipnk_sw_if_index)); +} + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vnet/ip-neighbor/ip_neighbor_types.h b/src/vnet/ip-neighbor/ip_neighbor_types.h new file mode 100644 index 00000000000..c6d4e104e8b --- /dev/null +++ b/src/vnet/ip-neighbor/ip_neighbor_types.h @@ -0,0 +1,127 @@ +/* + * ip_neighbor.h: ip neighbor generic services + * + * 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 __INCLUDE_IP_NEIGHBOR_TYPES_H__ +#define __INCLUDE_IP_NEIGHBOR_TYPES_H__ + +#include <vnet/ip/ip6_packet.h> +#include <vnet/ethernet/mac_address.h> +#include <vnet/fib/fib_types.h> + +#define IP_SCAN_DISABLED 0 +#define IP_SCAN_V4_NEIGHBORS (1 << 0) +#define IP_SCAN_V6_NEIGHBORS (1 << 1) +#define IP_SCAN_V46_NEIGHBORS (IP_SCAN_V4_NEIGHBORS | IP_SCAN_V6_NEIGHBORS) + +typedef struct +{ + u8 mode; /* 0: disable, 1: ip4, 2: ip6, 3: both */ + u8 scan_interval; /* neighbor scan interval in minutes */ + u8 max_proc_time; /* max processing time per run, in usecs */ + u8 max_update; /* max probe/delete operations per run */ + u8 scan_int_delay; /* delay in msecs, to resume scan on max */ + u8 stale_threshold; /* Threshold in minutes to delete nei entry */ +} ip_neighbor_scan_arg_t; + +typedef enum ip_neighbor_flags_t_ +{ + IP_NEIGHBOR_FLAG_NONE = 0, + IP_NEIGHBOR_FLAG_STATIC = (1 << 0), + IP_NEIGHBOR_FLAG_DYNAMIC = (1 << 1), + IP_NEIGHBOR_FLAG_NO_FIB_ENTRY = (1 << 2), + IP_NEIGHBOR_FLAG_PENDING = (1 << 3), +} __attribute__ ((packed)) ip_neighbor_flags_t; + +typedef struct ip_neighbor_watcher_t_ +{ + u32 ipw_pid; + u32 ipw_client; +} ip_neighbor_watcher_t; + +extern u8 *format_ip_neighbor_watcher (u8 * s, va_list * args); + +typedef struct ip_neighbor_key_t_ +{ + ip46_address_t ipnk_ip; + ip46_type_t ipnk_type; + u32 ipnk_sw_if_index; +} ip_neighbor_key_t; + +/** + * A representation of an IP neighbour/peer + */ +typedef struct ip_neighbor_t_ +{ + /** + * The idempotent key + */ + ip_neighbor_key_t *ipn_key; + + /** + * The learned MAC address of the neighbour + */ + mac_address_t ipn_mac; + + /** + * Falgs for this object + */ + ip_neighbor_flags_t ipn_flags; + + /** + * Aging related data + * - last time the neighbour was probed + * - number of probes - 3 and it's dead + */ + f64 ipn_time_last_updated; + u8 ipn_n_probes; + index_t ipn_elt; + + /** + * The index of the adj fib created for this neighbour + */ + fib_node_index_t ipn_fib_entry_index; +} ip_neighbor_t; + +extern u8 *format_ip_neighbor_flags (u8 * s, va_list * args); +extern u8 *format_ip_neighbor_key (u8 * s, va_list * args); +extern u8 *format_ip_neighbor (u8 * s, va_list * args); + +extern ip_neighbor_t *ip_neighbor_get (index_t ipni); + +typedef struct ip_neighbor_learn_t_ +{ + ip46_address_t ip; + ip46_type_t type; + mac_address_t mac; + u32 sw_if_index; +} ip_neighbor_learn_t; + +typedef struct ip_neighbor_event_t_ +{ + ip_neighbor_watcher_t ipne_watch; + index_t ipne_index; +} ip_neighbor_event_t; + +#endif /* __INCLUDE_IP_NEIGHBOR_H__ */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vnet/ip-neighbor/ip_neighbor_watch.c b/src/vnet/ip-neighbor/ip_neighbor_watch.c new file mode 100644 index 00000000000..7464ee62189 --- /dev/null +++ b/src/vnet/ip-neighbor/ip_neighbor_watch.c @@ -0,0 +1,283 @@ +/* + * ip_neighboor_watch.c; IP neighbor watching + * + * Copyright (c) 2019 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 <vnet/ip-neighbor/ip_neighbor.h> +#include <vnet/ip/ip_types_api.h> +#include <vnet/ethernet/ethernet_types_api.h> + +#include <vnet/ip-neighbor/ip_neighbor.api_enum.h> +#include <vnet/ip-neighbor/ip_neighbor.api_types.h> + +#include <vlibmemory/api.h> + +/** + * Database of registered watchers + * The key for a watcher is {type, sw_if_index, addreess} + * interface=~0 / address=all-zeros imples any. + */ +typedef struct ip_neighbor_watch_db_t_ +{ + mhash_t ipnwdb_hash; +} ip_neighbor_watch_db_t; + +static ip_neighbor_watch_db_t ipnw_db; + +static uword +ip_neighbor_event_process (vlib_main_t * vm, + vlib_node_runtime_t * rt, vlib_frame_t * f) +{ + ip_neighbor_event_t *ipne, *ipnes = NULL; + uword event_type = ~0; + + while (1) + { + vlib_process_wait_for_event (vm); + + ipnes = vlib_process_get_event_data (vm, &event_type); + + switch (event_type) + { + default: + vec_foreach (ipne, ipnes) ip_neighbor_handle_event (ipne); + break; + + case ~0: + /* timeout - */ + break; + } + + vec_reset_length (ipnes); + } + return 0; +} + +/* *INDENT-OFF* */ +VLIB_REGISTER_NODE (ip_neighbor_event_process_node) = { + .function = ip_neighbor_event_process, + .type = VLIB_NODE_TYPE_PROCESS, + .name = "ip-neighbor-event", +}; +/* *INDENT-ON* */ + + +static clib_error_t * +want_ip_neighbor_events_reaper (u32 client_index) +{ + ip_neighbor_key_t *key, *empty_keys = NULL; + ip_neighbor_watcher_t *watchers; + uword *v; + i32 pos; + + /* walk the entire IP neighbour DB and removes the client's registrations */ + /* *INDENT-OFF* */ + mhash_foreach(key, v, &ipnw_db.ipnwdb_hash, + ({ + watchers = (ip_neighbor_watcher_t*) *v; + + vec_foreach_index_backwards (pos, watchers) { + if (watchers[pos].ipw_client == client_index) + vec_del1(watchers, pos); + } + + if (vec_len(watchers) == 0) + vec_add1 (empty_keys, *key); + })); + /* *INDENT-OFF* */ + + vec_foreach (key, empty_keys) + mhash_unset (&ipnw_db.ipnwdb_hash, key, NULL); + vec_free (empty_keys); + return (NULL); +} + +VL_MSG_API_REAPER_FUNCTION (want_ip_neighbor_events_reaper); + +static int +ip_neighbor_watch_cmp (const ip_neighbor_watcher_t * w1, + const ip_neighbor_watcher_t * w2) +{ + return (0 == clib_memcmp (w1, w2, sizeof(*w1))); +} + +void +ip_neighbor_watch (const ip46_address_t * ip, + ip46_type_t type, + u32 sw_if_index, + const ip_neighbor_watcher_t * watch) +{ + ip_neighbor_key_t key = { + .ipnk_ip = *ip, + .ipnk_sw_if_index = (sw_if_index == 0 ? ~0 : sw_if_index), + .ipnk_type = type, + }; + ip_neighbor_watcher_t *ipws = NULL; + uword *p; + + p = mhash_get (&ipnw_db.ipnwdb_hash, &key); + + if (p) + { + ipws = (ip_neighbor_watcher_t*) p[0]; + + if (~0 != vec_search_with_function (ipws, watch, + ip_neighbor_watch_cmp)) + /* duplicate */ + return; + } + + vec_add1 (ipws, *watch); + + mhash_set (&ipnw_db.ipnwdb_hash, &key, (uword) ipws, NULL); +} + +void +ip_neighbor_unwatch (const ip46_address_t * ip, + ip46_type_t type, + u32 sw_if_index, + const ip_neighbor_watcher_t * watch) +{ + ip_neighbor_key_t key = { + .ipnk_ip = *ip, + .ipnk_sw_if_index = (sw_if_index == 0 ? ~0 : sw_if_index), + .ipnk_type = type, + }; + ip_neighbor_watcher_t *ipws = NULL; + uword *p; + u32 pos; + + p = mhash_get (&ipnw_db.ipnwdb_hash, &key); + + if (!p) + return; + + ipws = (ip_neighbor_watcher_t*) p[0]; + + pos = vec_search_with_function (ipws, watch, ip_neighbor_watch_cmp); + + if (~0 == pos) + return; + + vec_del1 (ipws, pos); + + if (vec_len(ipws) == 0) + mhash_unset (&ipnw_db.ipnwdb_hash, &key, NULL); +} + +static void +ip_neighbor_signal (ip_neighbor_watcher_t *watchers, index_t ipni) +{ + ip_neighbor_watcher_t *watcher; + + vec_foreach (watcher, watchers) { + ip_neighbor_event_t *ipne; + + ipne = vlib_process_signal_event_data (vlib_get_main(), + ip_neighbor_event_process_node.index, + 0, 1, sizeof(*ipne)); + ipne->ipne_watch = *watcher; + ipne->ipne_index = ipni; + } +} + +void +ip_neighbor_publish (index_t ipni) +{ + const ip_neighbor_t *ipn; + ip_neighbor_key_t key; + uword *p; + + ipn = ip_neighbor_get (ipni); + + clib_memcpy (&key, ipn->ipn_key, sizeof (key)); + + /* Search the DB from longest to shortest key */ + p = mhash_get (&ipnw_db.ipnwdb_hash, &key); + + if (p) { + ip_neighbor_signal ((ip_neighbor_watcher_t*) p[0], ipni); + } + + ip46_address_reset (&key.ipnk_ip); + p = mhash_get (&ipnw_db.ipnwdb_hash, &key); + + if (p) { + ip_neighbor_signal ((ip_neighbor_watcher_t*) p[0], ipni); + } + + key.ipnk_sw_if_index = ~0; + p = mhash_get (&ipnw_db.ipnwdb_hash, &key); + + if (p) { + ip_neighbor_signal ((ip_neighbor_watcher_t*) p[0], ipni); + } +} + +static clib_error_t * +ip_neighbor_watchers_show (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + ip_neighbor_watcher_t *watchers, *watcher; + ip_neighbor_key_t *key; + uword *v; + + /* *INDENT-OFF* */ + mhash_foreach(key, v, &ipnw_db.ipnwdb_hash, + ({ + watchers = (ip_neighbor_watcher_t*) *v; + + ASSERT(vec_len(watchers)); + vlib_cli_output (vm, "Key: %U", format_ip_neighbor_key, key); + + vec_foreach (watcher, watchers) + vlib_cli_output (vm, " %U", format_ip_neighbor_watcher, watcher); + })); + /* *INDENT-ON* */ + return (NULL); +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (show_ip_neighbor_watchers_cmd_node, static) = { + .path = "show ip neighbor-watcher", + .function = ip_neighbor_watchers_show, + .short_help = "show ip neighbors-watcher", +}; +/* *INDENT-ON* */ + +static clib_error_t * +ip_neighbor_watch_init (vlib_main_t * vm) +{ + mhash_init (&ipnw_db.ipnwdb_hash, + sizeof (ip_neighbor_watcher_t *), sizeof (ip_neighbor_key_t)); + return (NULL); +} + +/* *INDENT-OFF* */ +VLIB_INIT_FUNCTION (ip_neighbor_watch_init) = +{ + .runs_after = VLIB_INITS("ip_neighbor_init"), +}; +/* *INDENT-ON* */ + + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vnet/ip-neighbor/ip_neighbor_watch.h b/src/vnet/ip-neighbor/ip_neighbor_watch.h new file mode 100644 index 00000000000..91d9f6fe12f --- /dev/null +++ b/src/vnet/ip-neighbor/ip_neighbor_watch.h @@ -0,0 +1,42 @@ +/* + * ip_neighboor_watch.h: ip neighbor event handling + * + * 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_NEIGHBOR_WATCH_H__ +#define __IP_NEIGHBOR_WATCH_H__ + +#include <vnet/ip-neighbor/ip_neighbor_types.h> + +extern void ip_neighbor_watch (const ip46_address_t * ip, + ip46_type_t type, + u32 sw_if_index, + const ip_neighbor_watcher_t * watch); +extern void ip_neighbor_unwatch (const ip46_address_t * ip, + ip46_type_t type, + u32 sw_if_index, + const ip_neighbor_watcher_t * watch); + +extern void ip_neighbor_publish (index_t ipni); + +#endif + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ |