From cbe25aab3be72154f2c706c39eeba6a77f34450f Mon Sep 17 00:00:00 2001 From: Neale Ranns Date: Mon, 30 Sep 2019 10:53:31 +0000 Subject: ip: Protocol Independent IP Neighbors Type: feature - ip-neighbour: generic neighbour handling; APIs, DBs, event handling, aging - arp: ARP protocol implementation - ip6-nd; IPv6 neighbor discovery implementation; separate ND, MLD, RA - ip6-link; manage link-local addresses - l2-arp-term; events separated from IP neighbours, since they are not the same. vnet retains just enough education to perform ND/ARP packet construction. arp and ip6-nd to be moved to plugins soon. Change-Id: I88dedd0006b299344f4c7024a0aa5baa6b9a8bbe Signed-off-by: Neale Ranns --- src/vnet/arp/arp.api | 100 +++++ src/vnet/arp/arp.c | 934 ++++++++++++++++++++++++++++++++++++++++++++++ src/vnet/arp/arp.h | 85 +++++ src/vnet/arp/arp_api.c | 190 ++++++++++ src/vnet/arp/arp_packet.h | 91 +++++ src/vnet/arp/arp_proxy.c | 407 ++++++++++++++++++++ src/vnet/arp/arp_test.c | 168 +++++++++ 7 files changed, 1975 insertions(+) create mode 100644 src/vnet/arp/arp.api create mode 100644 src/vnet/arp/arp.c create mode 100644 src/vnet/arp/arp.h create mode 100644 src/vnet/arp/arp_api.c create mode 100644 src/vnet/arp/arp_packet.h create mode 100644 src/vnet/arp/arp_proxy.c create mode 100644 src/vnet/arp/arp_test.c (limited to 'src/vnet/arp') diff --git a/src/vnet/arp/arp.api b/src/vnet/arp/arp.api new file mode 100644 index 00000000000..27bfa3b65c6 --- /dev/null +++ b/src/vnet/arp/arp.api @@ -0,0 +1,100 @@ +/* Hey Emacs use -*- mode: C -*- */ +/* + * 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. + */ + +/** \file + This file defines the vpp control-plane API messages + used to control the ABF plugin +*/ + +option version = "1.0.0"; + +import "vnet/ip/ip_types.api"; +import "vnet/ethernet/ethernet_types.api"; +import "vnet/interface_types.api"; + +/** \brief Proxy ARP configuration type + @param table_id - VRF / Fib table ID + @param low - Low address of the Proxy ARP range + @param hi - High address of the Proxy ARP range +*/ +typedef proxy_arp +{ + u32 table_id; + vl_api_ip4_address_t low; + vl_api_ip4_address_t hi; +}; + +/** \brief Proxy ARP 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 if adding the Proxy ARP range, 0 if deleting + @param proxy - Proxy configuration +*/ +autoreply define proxy_arp_add_del +{ + u32 client_index; + u32 context; + bool is_add; + vl_api_proxy_arp_t proxy; +}; + +/** \brief Proxy ARP dump request + */ +define proxy_arp_dump +{ + u32 client_index; + u32 context; +}; + +/** \brief Proxy ARP dump details reply + * @param proxy - Same data as used to configure + */ +define proxy_arp_details +{ + u32 context; + vl_api_proxy_arp_t proxy; +}; + +/** \brief Proxy ARP add / del interface request + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param sw_if_index - Which interface to enable / disable Proxy Arp on + @param enable - 1 to enable Proxy ARP on interface, 0 to disable +*/ +autoreply define proxy_arp_intfc_enable_disable +{ + u32 client_index; + u32 context; + vl_api_interface_index_t sw_if_index; + bool enable; +}; + +/** \brief Proxy ARP interface dump request + */ +define proxy_arp_intfc_dump +{ + u32 client_index; + u32 context; +}; + +/** \brief Proxy ARP interface dump details reply + * @param sw_if_index The interface on which ARP proxy is enabled. + */ +define proxy_arp_intfc_details +{ + u32 context; + u32 sw_if_index; +}; diff --git a/src/vnet/arp/arp.c b/src/vnet/arp/arp.c new file mode 100644 index 00000000000..14a1ae97d1e --- /dev/null +++ b/src/vnet/arp/arp.c @@ -0,0 +1,934 @@ +/* + * ethernet/arp.c: IP v4 ARP node + * + * Copyright (c) 2010 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include + +/** + * @file + * @brief IPv4 ARP. + * + * This file contains code to manage the IPv4 ARP tables (IP Address + * to MAC Address lookup). + */ + +/** + * @brief Per-interface ARP configuration and state + */ +typedef struct ethernet_arp_interface_t_ +{ + /** + * Is ARP enabled on this interface + */ + u32 enabled; +} ethernet_arp_interface_t; + +typedef struct +{ + /* Hash tables mapping name to opcode. */ + uword *opcode_by_name; + + /** Per interface state */ + ethernet_arp_interface_t *ethernet_arp_by_sw_if_index; + + /* ARP feature arc index */ + u8 feature_arc_index; +} ethernet_arp_main_t; + +static ethernet_arp_main_t ethernet_arp_main; + +static const u8 vrrp_prefix[] = { 0x00, 0x00, 0x5E, 0x00, 0x01 }; + +static uword +unformat_ethernet_arp_opcode_host_byte_order (unformat_input_t * input, + va_list * args) +{ + int *result = va_arg (*args, int *); + ethernet_arp_main_t *am = ðernet_arp_main; + int x, i; + + /* Numeric opcode. */ + if (unformat (input, "0x%x", &x) || unformat (input, "%d", &x)) + { + if (x >= (1 << 16)) + return 0; + *result = x; + return 1; + } + + /* Named type. */ + if (unformat_user (input, unformat_vlib_number_by_name, + am->opcode_by_name, &i)) + { + *result = i; + return 1; + } + + return 0; +} + +static uword +unformat_ethernet_arp_opcode_net_byte_order (unformat_input_t * input, + va_list * args) +{ + int *result = va_arg (*args, int *); + if (!unformat_user + (input, unformat_ethernet_arp_opcode_host_byte_order, result)) + return 0; + + *result = clib_host_to_net_u16 ((u16) * result); + return 1; +} + +typedef struct +{ + u8 packet_data[64]; +} ethernet_arp_input_trace_t; + +static u8 * +format_ethernet_arp_input_trace (u8 * s, va_list * va) +{ + CLIB_UNUSED (vlib_main_t * vm) = va_arg (*va, vlib_main_t *); + CLIB_UNUSED (vlib_node_t * node) = va_arg (*va, vlib_node_t *); + ethernet_arp_input_trace_t *t = va_arg (*va, ethernet_arp_input_trace_t *); + + s = format (s, "%U", + format_ethernet_arp_header, + t->packet_data, sizeof (t->packet_data)); + + return s; +} + +static int +arp_is_enabled (ethernet_arp_main_t * am, u32 sw_if_index) +{ + if (vec_len (am->ethernet_arp_by_sw_if_index) <= sw_if_index) + return 0; + + return (am->ethernet_arp_by_sw_if_index[sw_if_index].enabled); +} + +static void +arp_enable (ethernet_arp_main_t * am, u32 sw_if_index) +{ + if (arp_is_enabled (am, sw_if_index)) + return; + + vec_validate (am->ethernet_arp_by_sw_if_index, sw_if_index); + + am->ethernet_arp_by_sw_if_index[sw_if_index].enabled = 1; + + vnet_feature_enable_disable ("arp", "arp-reply", sw_if_index, 1, NULL, 0); + vnet_feature_enable_disable ("arp", "arp-disabled", sw_if_index, 0, NULL, + 0); +} + +static void +arp_disable (ethernet_arp_main_t * am, u32 sw_if_index) +{ + if (!arp_is_enabled (am, sw_if_index)) + return; + + vnet_feature_enable_disable ("arp", "arp-disabled", sw_if_index, 1, NULL, + 0); + vnet_feature_enable_disable ("arp", "arp-reply", sw_if_index, 0, NULL, 0); + + am->ethernet_arp_by_sw_if_index[sw_if_index].enabled = 0; +} + +static int +arp_unnumbered (vlib_buffer_t * p0, + u32 input_sw_if_index, u32 conn_sw_if_index) +{ + vnet_main_t *vnm = vnet_get_main (); + vnet_interface_main_t *vim = &vnm->interface_main; + vnet_sw_interface_t *si; + + /* verify that the input interface is unnumbered to the connected. + * the connected interface is the interface on which the subnet is + * configured */ + si = &vim->sw_interfaces[input_sw_if_index]; + + if (!(si->flags & VNET_SW_INTERFACE_FLAG_UNNUMBERED && + (si->unnumbered_sw_if_index == conn_sw_if_index))) + { + /* the input interface is not unnumbered to the interface on which + * the sub-net is configured that covers the ARP request. + * So this is not the case for unnumbered.. */ + return 0; + } + + return !0; +} + +always_inline u32 +arp_learn (u32 sw_if_index, + const ethernet_arp_ip4_over_ethernet_address_t * addr) +{ + ip_neighbor_learn_t l = { + .ip.ip4 = addr->ip4, + .type = IP46_TYPE_IP4, + .mac = addr->mac, + .sw_if_index = sw_if_index, + }; + + ip_neighbor_learn_dp (&l); + + return (ETHERNET_ARP_ERROR_l3_src_address_learned); +} + +typedef enum arp_input_next_t_ +{ + ARP_INPUT_NEXT_DROP, + ARP_INPUT_NEXT_DISABLED, + ARP_INPUT_N_NEXT, +} arp_input_next_t; + +static uword +arp_input (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * frame) +{ + u32 n_left_from, next_index, *from, *to_next, n_left_to_next; + ethernet_arp_main_t *am = ðernet_arp_main; + + from = vlib_frame_vector_args (frame); + n_left_from = frame->n_vectors; + next_index = node->cached_next_index; + + if (node->flags & VLIB_NODE_FLAG_TRACE) + vlib_trace_frame_buffers_only (vm, node, from, frame->n_vectors, + /* stride */ 1, + sizeof (ethernet_arp_input_trace_t)); + + while (n_left_from > 0) + { + vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next); + + while (n_left_from > 0 && n_left_to_next > 0) + { + const ethernet_arp_header_t *arp0; + arp_input_next_t next0; + vlib_buffer_t *p0; + u32 pi0, error0; + + pi0 = to_next[0] = from[0]; + from += 1; + to_next += 1; + n_left_from -= 1; + n_left_to_next -= 1; + + p0 = vlib_get_buffer (vm, pi0); + arp0 = vlib_buffer_get_current (p0); + + error0 = ETHERNET_ARP_ERROR_replies_sent; + next0 = ARP_INPUT_NEXT_DROP; + + error0 = + (arp0->l2_type != + clib_net_to_host_u16 (ETHERNET_ARP_HARDWARE_TYPE_ethernet) ? + ETHERNET_ARP_ERROR_l2_type_not_ethernet : error0); + error0 = + (arp0->l3_type != + clib_net_to_host_u16 (ETHERNET_TYPE_IP4) ? + ETHERNET_ARP_ERROR_l3_type_not_ip4 : error0); + error0 = + (0 == arp0->ip4_over_ethernet[0].ip4.as_u32 ? + ETHERNET_ARP_ERROR_l3_dst_address_unset : error0); + + if (ETHERNET_ARP_ERROR_replies_sent == error0) + { + next0 = ARP_INPUT_NEXT_DISABLED; + vnet_feature_arc_start (am->feature_arc_index, + vnet_buffer (p0)->sw_if_index[VLIB_RX], + &next0, p0); + } + else + p0->error = node->errors[error0]; + + vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next, + n_left_to_next, pi0, next0); + } + + vlib_put_next_frame (vm, node, next_index, n_left_to_next); + } + + return frame->n_vectors; +} + +typedef enum arp_disabled_next_t_ +{ + ARP_DISABLED_NEXT_DROP, + ARP_DISABLED_N_NEXT, +} arp_disabled_next_t; + +#define foreach_arp_disabled_error \ + _ (DISABLED, "ARP Disabled on this interface") \ + +typedef enum +{ +#define _(sym,string) ARP_DISABLED_ERROR_##sym, + foreach_arp_disabled_error +#undef _ + ARP_DISABLED_N_ERROR, +} arp_disabled_error_t; + +static char *arp_disabled_error_strings[] = { +#define _(sym,string) string, + foreach_arp_disabled_error +#undef _ +}; + +static uword +arp_disabled (vlib_main_t * vm, + vlib_node_runtime_t * node, vlib_frame_t * frame) +{ + u32 n_left_from, next_index, *from, *to_next, n_left_to_next; + + from = vlib_frame_vector_args (frame); + n_left_from = frame->n_vectors; + next_index = node->cached_next_index; + + if (node->flags & VLIB_NODE_FLAG_TRACE) + vlib_trace_frame_buffers_only (vm, node, from, frame->n_vectors, + /* stride */ 1, + sizeof (ethernet_arp_input_trace_t)); + + while (n_left_from > 0) + { + vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next); + + while (n_left_from > 0 && n_left_to_next > 0) + { + arp_disabled_next_t next0 = ARP_DISABLED_NEXT_DROP; + vlib_buffer_t *p0; + u32 pi0, error0; + + next0 = ARP_DISABLED_NEXT_DROP; + error0 = ARP_DISABLED_ERROR_DISABLED; + + pi0 = to_next[0] = from[0]; + from += 1; + to_next += 1; + n_left_from -= 1; + n_left_to_next -= 1; + + p0 = vlib_get_buffer (vm, pi0); + p0->error = node->errors[error0]; + + vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next, + n_left_to_next, pi0, next0); + } + + vlib_put_next_frame (vm, node, next_index, n_left_to_next); + } + + return frame->n_vectors; +} + +enum arp_dst_fib_type +{ + ARP_DST_FIB_NONE, + ARP_DST_FIB_ADJ, + ARP_DST_FIB_CONN +}; + +/* + * we're looking for FIB sources that indicate the destination + * is attached. There may be interposed DPO prior to the one + * we are looking for + */ +static enum arp_dst_fib_type +arp_dst_fib_check (const fib_node_index_t fei, fib_entry_flag_t * flags) +{ + const fib_entry_t *entry = fib_entry_get (fei); + const fib_entry_src_t *entry_src; + fib_source_t src; + /* *INDENT-OFF* */ + FOR_EACH_SRC_ADDED(entry, entry_src, src, + ({ + *flags = fib_entry_get_flags_for_source (fei, src); + if (fib_entry_is_sourced (fei, FIB_SOURCE_ADJ)) + return ARP_DST_FIB_ADJ; + else if (FIB_ENTRY_FLAG_CONNECTED & *flags) + return ARP_DST_FIB_CONN; + })) + /* *INDENT-ON* */ + + return ARP_DST_FIB_NONE; +} + +static uword +arp_reply (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * frame) +{ + vnet_main_t *vnm = vnet_get_main (); + u32 n_left_from, next_index, *from, *to_next; + u32 n_replies_sent = 0; + + from = vlib_frame_vector_args (frame); + n_left_from = frame->n_vectors; + next_index = node->cached_next_index; + + if (node->flags & VLIB_NODE_FLAG_TRACE) + vlib_trace_frame_buffers_only (vm, node, from, frame->n_vectors, + /* stride */ 1, + sizeof (ethernet_arp_input_trace_t)); + + while (n_left_from > 0) + { + u32 n_left_to_next; + + vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next); + + while (n_left_from > 0 && n_left_to_next > 0) + { + vlib_buffer_t *p0; + ethernet_arp_header_t *arp0; + ethernet_header_t *eth_rx; + const ip4_address_t *if_addr0; + u32 pi0, error0, next0, sw_if_index0, conn_sw_if_index0, fib_index0; + u8 dst_is_local0, is_vrrp_reply0; + fib_node_index_t dst_fei, src_fei; + const fib_prefix_t *pfx0; + fib_entry_flag_t src_flags, dst_flags; + + pi0 = from[0]; + to_next[0] = pi0; + from += 1; + to_next += 1; + n_left_from -= 1; + n_left_to_next -= 1; + + p0 = vlib_get_buffer (vm, pi0); + arp0 = vlib_buffer_get_current (p0); + /* Fill in ethernet header. */ + eth_rx = ethernet_buffer_get_header (p0); + + next0 = ARP_REPLY_NEXT_DROP; + error0 = ETHERNET_ARP_ERROR_replies_sent; + sw_if_index0 = vnet_buffer (p0)->sw_if_index[VLIB_RX]; + + /* Check that IP address is local and matches incoming interface. */ + fib_index0 = ip4_fib_table_get_index_for_sw_if_index (sw_if_index0); + if (~0 == fib_index0) + { + error0 = ETHERNET_ARP_ERROR_interface_no_table; + goto drop; + + } + + { + /* + * we're looking for FIB entries that indicate the source + * is attached. There may be more specific non-attached + * routes that match the source, but these do not influence + * whether we respond to an ARP request, i.e. they do not + * influence whether we are the correct way for the sender + * to reach us, they only affect how we reach the sender. + */ + fib_entry_t *src_fib_entry; + const fib_prefix_t *pfx; + fib_entry_src_t *src; + fib_source_t source; + int attached; + int mask; + + mask = 32; + attached = 0; + + do + { + src_fei = ip4_fib_table_lookup (ip4_fib_get (fib_index0), + &arp0-> + ip4_over_ethernet[0].ip4, + mask); + src_fib_entry = fib_entry_get (src_fei); + + /* + * It's possible that the source that provides the + * flags we need, or the flags we must not have, + * is not the best source, so check then all. + */ + /* *INDENT-OFF* */ + FOR_EACH_SRC_ADDED(src_fib_entry, src, source, + ({ + src_flags = fib_entry_get_flags_for_source (src_fei, source); + + /* Reject requests/replies with our local interface + address. */ + if (FIB_ENTRY_FLAG_LOCAL & src_flags) + { + error0 = ETHERNET_ARP_ERROR_l3_src_address_is_local; + /* + * When VPP has an interface whose address is also + * applied to a TAP interface on the host, then VPP's + * TAP interface will be unnumbered to the 'real' + * interface and do proxy ARP from the host. + * The curious aspect of this setup is that ARP requests + * from the host will come from the VPP's own address. + * So don't drop immediately here, instead go see if this + * is a proxy ARP case. + */ + goto next_feature; + } + /* A Source must also be local to subnet of matching + * interface address. */ + if ((FIB_ENTRY_FLAG_ATTACHED & src_flags) || + (FIB_ENTRY_FLAG_CONNECTED & src_flags)) + { + attached = 1; + break; + } + /* + * else + * The packet was sent from an address that is not + * connected nor attached i.e. it is not from an + * address that is covered by a link's sub-net, + * nor is it a already learned host resp. + */ + })); + /* *INDENT-ON* */ + + /* + * shorter mask lookup for the next iteration. + */ + pfx = fib_entry_get_prefix (src_fei); + mask = pfx->fp_len - 1; + + /* + * continue until we hit the default route or we find + * the attached we are looking for. The most likely + * outcome is we find the attached with the first source + * on the first lookup. + */ + } + while (!attached && + !fib_entry_is_sourced (src_fei, FIB_SOURCE_DEFAULT_ROUTE)); + + if (!attached) + { + /* + * the matching route is a not attached, i.e. it was + * added as a result of routing, rather than interface/ARP + * configuration. If the matching route is not a host route + * (i.e. a /32) + */ + error0 = ETHERNET_ARP_ERROR_l3_src_address_not_local; + goto drop; + } + } + + dst_fei = ip4_fib_table_lookup (ip4_fib_get (fib_index0), + &arp0->ip4_over_ethernet[1].ip4, + 32); + switch (arp_dst_fib_check (dst_fei, &dst_flags)) + { + case ARP_DST_FIB_ADJ: + /* + * We matched an adj-fib on ths source subnet (a /32 previously + * added as a result of ARP). If this request is a gratuitous + * ARP, then learn from it. + * The check for matching an adj-fib, is to prevent hosts + * from spamming us with gratuitous ARPS that might otherwise + * blow our ARP cache + */ + if (arp0->ip4_over_ethernet[0].ip4.as_u32 == + arp0->ip4_over_ethernet[1].ip4.as_u32) + error0 = + arp_learn (sw_if_index0, &arp0->ip4_over_ethernet[0]); + goto drop; + case ARP_DST_FIB_CONN: + /* destination is connected, continue to process */ + break; + case ARP_DST_FIB_NONE: + /* destination is not connected, stop here */ + error0 = ETHERNET_ARP_ERROR_l3_dst_address_not_local; + goto next_feature; + } + + dst_is_local0 = (FIB_ENTRY_FLAG_LOCAL & dst_flags); + pfx0 = fib_entry_get_prefix (dst_fei); + if_addr0 = &pfx0->fp_addr.ip4; + + is_vrrp_reply0 = + ((arp0->opcode == + clib_host_to_net_u16 (ETHERNET_ARP_OPCODE_reply)) + && + (!memcmp + (arp0->ip4_over_ethernet[0].mac.bytes, vrrp_prefix, + sizeof (vrrp_prefix)))); + + /* Trash ARP packets whose ARP-level source addresses do not + match their L2-frame-level source addresses, unless it's + a reply from a VRRP virtual router */ + if (!ethernet_mac_address_equal + (eth_rx->src_address, + arp0->ip4_over_ethernet[0].mac.bytes) && !is_vrrp_reply0) + { + error0 = ETHERNET_ARP_ERROR_l2_address_mismatch; + goto drop; + } + + /* Learn or update sender's mapping only for replies to addresses + * that are local to the subnet */ + if (arp0->opcode == + clib_host_to_net_u16 (ETHERNET_ARP_OPCODE_reply)) + { + if (dst_is_local0) + error0 = + arp_learn (sw_if_index0, &arp0->ip4_over_ethernet[0]); + else + /* a reply for a non-local destination could be a GARP. + * GARPs for hosts we know were handled above, so this one + * we drop */ + error0 = ETHERNET_ARP_ERROR_l3_dst_address_not_local; + + goto next_feature; + } + else if (arp0->opcode == + clib_host_to_net_u16 (ETHERNET_ARP_OPCODE_request) && + (dst_is_local0 == 0)) + { + goto next_feature; + } + + /* Honor unnumbered interface, if any */ + conn_sw_if_index0 = fib_entry_get_resolving_interface (dst_fei); + if (sw_if_index0 != conn_sw_if_index0 || + sw_if_index0 != fib_entry_get_resolving_interface (src_fei)) + { + /* + * The interface the ARP is sent to or was received on is not the + * interface on which the covering prefix is configured. + * Maybe this is a case for unnumbered. + */ + if (!arp_unnumbered (p0, sw_if_index0, conn_sw_if_index0)) + { + error0 = ETHERNET_ARP_ERROR_unnumbered_mismatch; + goto drop; + } + } + if (arp0->ip4_over_ethernet[0].ip4.as_u32 == + arp0->ip4_over_ethernet[1].ip4.as_u32) + { + error0 = ETHERNET_ARP_ERROR_gratuitous_arp; + goto drop; + } + + next0 = arp_mk_reply (vnm, p0, sw_if_index0, + if_addr0, arp0, eth_rx); + + /* We are going to reply to this request, so, in the absence of + errors, learn the sender */ + if (!error0) + error0 = arp_learn (sw_if_index0, &arp0->ip4_over_ethernet[1]); + + n_replies_sent += 1; + goto enqueue; + + next_feature: + vnet_feature_next (&next0, p0); + goto enqueue; + + drop: + p0->error = node->errors[error0]; + + enqueue: + vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next, + n_left_to_next, pi0, next0); + } + + vlib_put_next_frame (vm, node, next_index, n_left_to_next); + } + + vlib_error_count (vm, node->node_index, + ETHERNET_ARP_ERROR_replies_sent, n_replies_sent); + + return frame->n_vectors; +} + + +static char *ethernet_arp_error_strings[] = { +#define _(sym,string) string, + foreach_ethernet_arp_error +#undef _ +}; + +/* *INDENT-OFF* */ + +VLIB_REGISTER_NODE (arp_input_node, static) = +{ + .function = arp_input, + .name = "arp-input", + .vector_size = sizeof (u32), + .n_errors = ETHERNET_ARP_N_ERROR, + .error_strings = ethernet_arp_error_strings, + .n_next_nodes = ARP_INPUT_N_NEXT, + .next_nodes = { + [ARP_INPUT_NEXT_DROP] = "error-drop", + [ARP_INPUT_NEXT_DISABLED] = "arp-disabled", + }, + .format_buffer = format_ethernet_arp_header, + .format_trace = format_ethernet_arp_input_trace, +}; + +VLIB_REGISTER_NODE (arp_disabled_node, static) = +{ + .function = arp_disabled, + .name = "arp-disabled", + .vector_size = sizeof (u32), + .n_errors = ARP_DISABLED_N_ERROR, + .error_strings = arp_disabled_error_strings, + .n_next_nodes = ARP_DISABLED_N_NEXT, + .next_nodes = { + [ARP_INPUT_NEXT_DROP] = "error-drop", + }, + .format_buffer = format_ethernet_arp_header, + .format_trace = format_ethernet_arp_input_trace, +}; + +VLIB_REGISTER_NODE (arp_reply_node, static) = +{ + .function = arp_reply, + .name = "arp-reply", + .vector_size = sizeof (u32), + .n_errors = ETHERNET_ARP_N_ERROR, + .error_strings = ethernet_arp_error_strings, + .n_next_nodes = ARP_REPLY_N_NEXT, + .next_nodes = { + [ARP_REPLY_NEXT_DROP] = "error-drop", + [ARP_REPLY_NEXT_REPLY_TX] = "interface-output", + }, + .format_buffer = format_ethernet_arp_header, + .format_trace = format_ethernet_arp_input_trace, +}; + +/* Built-in ARP rx feature path definition */ +VNET_FEATURE_ARC_INIT (arp_feat, static) = +{ + .arc_name = "arp", + .start_nodes = VNET_FEATURES ("arp-input"), + .last_in_arc = "error-drop", + .arc_index_ptr = ðernet_arp_main.feature_arc_index, +}; + +VNET_FEATURE_INIT (arp_reply_feat_node, static) = +{ + .arc_name = "arp", + .node_name = "arp-reply", + .runs_before = VNET_FEATURES ("arp-disabled"), +}; + +VNET_FEATURE_INIT (arp_proxy_feat_node, static) = +{ + .arc_name = "arp", + .node_name = "arp-proxy", + .runs_after = VNET_FEATURES ("arp-reply"), + .runs_before = VNET_FEATURES ("arp-disabled"), +}; + +VNET_FEATURE_INIT (arp_disabled_feat_node, static) = +{ + .arc_name = "arp", + .node_name = "arp-disabled", + .runs_before = VNET_FEATURES ("error-drop"), +}; + +VNET_FEATURE_INIT (arp_drop_feat_node, static) = +{ + .arc_name = "arp", + .node_name = "error-drop", + .runs_before = 0, /* last feature */ +}; + +/* *INDENT-ON* */ + +typedef struct +{ + pg_edit_t l2_type, l3_type; + pg_edit_t n_l2_address_bytes, n_l3_address_bytes; + pg_edit_t opcode; + struct + { + pg_edit_t mac; + pg_edit_t ip4; + } ip4_over_ethernet[2]; +} pg_ethernet_arp_header_t; + +static inline void +pg_ethernet_arp_header_init (pg_ethernet_arp_header_t * p) +{ + /* Initialize fields that are not bit fields in the IP header. */ +#define _(f) pg_edit_init (&p->f, ethernet_arp_header_t, f); + _(l2_type); + _(l3_type); + _(n_l2_address_bytes); + _(n_l3_address_bytes); + _(opcode); + _(ip4_over_ethernet[0].mac); + _(ip4_over_ethernet[0].ip4); + _(ip4_over_ethernet[1].mac); + _(ip4_over_ethernet[1].ip4); +#undef _ +} + +uword +unformat_pg_arp_header (unformat_input_t * input, va_list * args) +{ + pg_stream_t *s = va_arg (*args, pg_stream_t *); + pg_ethernet_arp_header_t *p; + u32 group_index; + + p = pg_create_edit_group (s, sizeof (p[0]), sizeof (ethernet_arp_header_t), + &group_index); + pg_ethernet_arp_header_init (p); + + /* Defaults. */ + pg_edit_set_fixed (&p->l2_type, ETHERNET_ARP_HARDWARE_TYPE_ethernet); + pg_edit_set_fixed (&p->l3_type, ETHERNET_TYPE_IP4); + pg_edit_set_fixed (&p->n_l2_address_bytes, 6); + pg_edit_set_fixed (&p->n_l3_address_bytes, 4); + + if (!unformat (input, "%U: %U/%U -> %U/%U", + unformat_pg_edit, + unformat_ethernet_arp_opcode_net_byte_order, &p->opcode, + unformat_pg_edit, + unformat_mac_address_t, &p->ip4_over_ethernet[0].mac, + unformat_pg_edit, + unformat_ip4_address, &p->ip4_over_ethernet[0].ip4, + unformat_pg_edit, + unformat_mac_address_t, &p->ip4_over_ethernet[1].mac, + unformat_pg_edit, + unformat_ip4_address, &p->ip4_over_ethernet[1].ip4)) + { + /* Free up any edits we may have added. */ + pg_free_edit_group (s); + return 0; + } + return 1; +} + +/* + * callback when an interface address is added or deleted + */ +static void +arp_enable_disable_interface (ip4_main_t * im, + uword opaque, u32 sw_if_index, u32 is_enable) +{ + ethernet_arp_main_t *am = ðernet_arp_main; + + if (is_enable) + arp_enable (am, sw_if_index); + else + arp_disable (am, sw_if_index); +} + +/* + * Remove any arp entries associated with the specified interface + */ +static clib_error_t * +vnet_arp_add_del_sw_interface (vnet_main_t * vnm, u32 sw_if_index, u32 is_add) +{ + ethernet_arp_main_t *am = ðernet_arp_main; + + if (!is_add && sw_if_index != ~0) + { + arp_disable (am, sw_if_index); + } + else if (is_add) + { + vnet_feature_enable_disable ("arp", "arp-disabled", + sw_if_index, 1, NULL, 0); + } + + return (NULL); +} + +VNET_SW_INTERFACE_ADD_DEL_FUNCTION (vnet_arp_add_del_sw_interface); + +const static ip_neighbor_vft_t arp_vft = { + .inv_proxy4_add = arp_proxy_add, + .inv_proxy4_del = arp_proxy_del, + .inv_proxy4_enable = arp_proxy_disable, + .inv_proxy4_disable = arp_proxy_disable, +}; + +static clib_error_t * +ethernet_arp_init (vlib_main_t * vm) +{ + ethernet_arp_main_t *am = ðernet_arp_main; + ip4_main_t *im = &ip4_main; + pg_node_t *pn; + + ethernet_register_input_type (vm, ETHERNET_TYPE_ARP, arp_input_node.index); + + pn = pg_get_node (arp_input_node.index); + pn->unformat_edit = unformat_pg_arp_header; + + am->opcode_by_name = hash_create_string (0, sizeof (uword)); +#define _(o) hash_set_mem (am->opcode_by_name, #o, ETHERNET_ARP_OPCODE_##o); + foreach_ethernet_arp_opcode; +#undef _ + + /* don't trace ARP error packets */ + { + vlib_node_runtime_t *rt = + vlib_node_get_runtime (vm, arp_input_node.index); + +#define _(a,b) \ + vnet_pcap_drop_trace_filter_add_del \ + (rt->errors[ETHERNET_ARP_ERROR_##a], \ + 1 /* is_add */); + foreach_ethernet_arp_error +#undef _ + } + + { + ip4_enable_disable_interface_callback_t cb = { + .function = arp_enable_disable_interface, + }; + vec_add1 (im->enable_disable_interface_callbacks, cb); + } + + ip_neighbor_register (IP46_TYPE_IP4, &arp_vft); + + return 0; +} + +/* *INDENT-OFF* */ +VLIB_INIT_FUNCTION (ethernet_arp_init) = +{ + .runs_after = VLIB_INITS("ethernet_init", + "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/arp/arp.h b/src/vnet/arp/arp.h new file mode 100644 index 00000000000..7446564b0cf --- /dev/null +++ b/src/vnet/arp/arp.h @@ -0,0 +1,85 @@ +/* + * 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. + */ + +#ifndef __ARP_H__ +#define __ARP_H__ + +#include +#include +#include + +#define foreach_ethernet_arp_error \ + _ (replies_sent, "ARP replies sent") \ + _ (l2_type_not_ethernet, "L2 type not ethernet") \ + _ (l3_type_not_ip4, "L3 type not IP4") \ + _ (l3_src_address_not_local, "IP4 source address not local to subnet") \ + _ (l3_dst_address_not_local, "IP4 destination address not local to subnet") \ + _ (l3_dst_address_unset, "IP4 destination address is unset") \ + _ (l3_src_address_is_local, "IP4 source address matches local interface") \ + _ (l3_src_address_learned, "ARP request IP4 source address learned") \ + _ (replies_received, "ARP replies received") \ + _ (opcode_not_request, "ARP opcode not request") \ + _ (proxy_arp_replies_sent, "Proxy ARP replies sent") \ + _ (l2_address_mismatch, "ARP hw addr does not match L2 frame src addr") \ + _ (gratuitous_arp, "ARP probe or announcement dropped") \ + _ (interface_no_table, "Interface is not mapped to an IP table") \ + _ (interface_not_ip_enabled, "Interface is not IP enabled") \ + _ (unnumbered_mismatch, "RX interface is unnumbered to different subnet") \ + +typedef enum +{ +#define _(sym,string) ETHERNET_ARP_ERROR_##sym, + foreach_ethernet_arp_error +#undef _ + ETHERNET_ARP_N_ERROR, +} ethernet_arp_reply_error_t; + +extern int arp_proxy_add (u32 fib_index, + const ip4_address_t * lo_addr, + const ip4_address_t * hi_addr); +extern int arp_proxy_del (u32 fib_index, + const ip4_address_t * lo_addr, + const ip4_address_t * hi_addr); + +extern int arp_proxy_enable (u32 sw_if_index); +extern int arp_proxy_disable (u32 sw_if_index); + +/** + * call back function when walking the DB of proxy ARPs + * @return 0 to stop the walk !0 to continue + */ +typedef walk_rc_t (proxy_arp_walk_t) (const ip4_address_t * lo_addr, + const ip4_address_t * hi_addr, + u32 fib_index, void *dat); + +extern void proxy_arp_walk (proxy_arp_walk_t cb, void *data); + +/** + * call back function when walking the DB of proxy ARP interface + * @return 0 to stop the walk !0 to continue + */ +typedef walk_rc_t (proxy_arp_intf_walk_t) (u32 sw_if_index, void *data); + +extern void proxy_arp_intfc_walk (proxy_arp_intf_walk_t cb, void *data); + +#endif + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vnet/arp/arp_api.c b/src/vnet/arp/arp_api.c new file mode 100644 index 00000000000..170bace2b0a --- /dev/null +++ b/src/vnet/arp/arp_api.c @@ -0,0 +1,190 @@ +/* + * 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 + +#include + +#include +#include + +#include + +#include +#include + +/* define message IDs */ +#include +#include +#include + +/** + * Base message ID fot the plugin + */ +static u32 arp_base_msg_id; +#define REPLY_MSG_ID_BASE arp_base_msg_id + +#include + +static void +vl_api_proxy_arp_add_del_t_handler (vl_api_proxy_arp_add_del_t * mp) +{ + vl_api_proxy_arp_add_del_reply_t *rmp; + ip4_address_t lo, hi; + u32 fib_index; + int rv; + + fib_index = fib_table_find (FIB_PROTOCOL_IP4, ntohl (mp->proxy.table_id)); + + if (~0 == fib_index) + { + rv = VNET_API_ERROR_NO_SUCH_FIB; + goto out; + } + + ip4_address_decode (mp->proxy.low, &lo); + ip4_address_decode (mp->proxy.hi, &hi); + + if (mp->is_add) + rv = arp_proxy_add (fib_index, &lo, &hi); + else + rv = arp_proxy_del (fib_index, &lo, &hi); + +out: + REPLY_MACRO (VL_API_PROXY_ARP_ADD_DEL_REPLY); +} + +typedef struct proxy_arp_walk_ctx_t_ +{ + vl_api_registration_t *reg; + u32 context; +} proxy_arp_walk_ctx_t; + +static walk_rc_t +send_proxy_arp_details (const ip4_address_t * lo_addr, + const ip4_address_t * hi_addr, + u32 fib_index, void *data) +{ + vl_api_proxy_arp_details_t *mp; + proxy_arp_walk_ctx_t *ctx; + + ctx = data; + + mp = vl_msg_api_alloc (sizeof (*mp)); + clib_memset (mp, 0, sizeof (*mp)); + mp->_vl_msg_id = ntohs (VL_API_PROXY_ARP_DETAILS + REPLY_MSG_ID_BASE); + mp->context = ctx->context; + mp->proxy.table_id = htonl (fib_index); + + ip4_address_encode (lo_addr, mp->proxy.low); + ip4_address_encode (hi_addr, mp->proxy.hi); + + vl_api_send_msg (ctx->reg, (u8 *) mp); + + return (WALK_CONTINUE); +} + +static void +vl_api_proxy_arp_dump_t_handler (vl_api_proxy_arp_dump_t * mp) +{ + vl_api_registration_t *reg; + + reg = vl_api_client_index_to_registration (mp->client_index); + if (!reg) + return; + + proxy_arp_walk_ctx_t wctx = { + .reg = reg, + .context = mp->context, + }; + + proxy_arp_walk (send_proxy_arp_details, &wctx); +} + +static walk_rc_t +send_proxy_arp_intfc_details (u32 sw_if_index, void *data) +{ + vl_api_proxy_arp_intfc_details_t *mp; + proxy_arp_walk_ctx_t *ctx; + + ctx = data; + + mp = vl_msg_api_alloc (sizeof (*mp)); + clib_memset (mp, 0, sizeof (*mp)); + mp->_vl_msg_id = ntohs (VL_API_PROXY_ARP_INTFC_DETAILS + REPLY_MSG_ID_BASE); + mp->context = ctx->context; + mp->sw_if_index = htonl (sw_if_index); + + vl_api_send_msg (ctx->reg, (u8 *) mp); + + return (WALK_CONTINUE); +} + +static void +vl_api_proxy_arp_intfc_dump_t_handler (vl_api_proxy_arp_intfc_dump_t * mp) +{ + vl_api_registration_t *reg; + + reg = vl_api_client_index_to_registration (mp->client_index); + if (!reg) + return; + + proxy_arp_walk_ctx_t wctx = { + .reg = reg, + .context = mp->context, + }; + + proxy_arp_intfc_walk (send_proxy_arp_intfc_details, &wctx); +} + +static void + vl_api_proxy_arp_intfc_enable_disable_t_handler + (vl_api_proxy_arp_intfc_enable_disable_t * mp) +{ + vl_api_proxy_arp_intfc_enable_disable_reply_t *rmp; + int rv; + + VALIDATE_SW_IF_INDEX (mp); + + if (mp->enable) + rv = arp_proxy_enable (ntohl (mp->sw_if_index)); + else + rv = arp_proxy_disable (ntohl (mp->sw_if_index)); + + BAD_SW_IF_INDEX_LABEL; + + REPLY_MACRO (VL_API_PROXY_ARP_INTFC_ENABLE_DISABLE_REPLY); +} + +#include + +static clib_error_t * +arp_api_init (vlib_main_t * vm) +{ + /* Ask for a correctly-sized block of API message decode slots */ + arp_base_msg_id = setup_message_id_table (); + + return 0; +} + +VLIB_INIT_FUNCTION (arp_api_init); + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vnet/arp/arp_packet.h b/src/vnet/arp/arp_packet.h new file mode 100644 index 00000000000..a860c258f75 --- /dev/null +++ b/src/vnet/arp/arp_packet.h @@ -0,0 +1,91 @@ +/* + * ethernet/arp.c: IP v4 ARP node + * + * 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. + */ + +#ifndef __ARP_PACKET_H__ +#define __ARP_PACKET_H__ + +#include + +/* Either we drop the packet or we send a reply to the sender. */ +typedef enum +{ + ARP_REPLY_NEXT_DROP, + ARP_REPLY_NEXT_REPLY_TX, + ARP_REPLY_N_NEXT, +} arp_reply_next_t; + +static_always_inline u32 +arp_mk_reply (vnet_main_t * vnm, + vlib_buffer_t * p0, + u32 sw_if_index0, + const ip4_address_t * if_addr0, + ethernet_arp_header_t * arp0, ethernet_header_t * eth_rx) +{ + vnet_hw_interface_t *hw_if0; + u8 *rewrite0, rewrite0_len; + ethernet_header_t *eth_tx; + u32 next0; + + /* Send a reply. + An adjacency to the sender is not always present, + so we use the interface to build us a rewrite string + which will contain all the necessary tags. */ + rewrite0 = ethernet_build_rewrite (vnm, sw_if_index0, + VNET_LINK_ARP, eth_rx->src_address); + rewrite0_len = vec_len (rewrite0); + + /* Figure out how much to rewind current data from adjacency. */ + vlib_buffer_advance (p0, -rewrite0_len); + eth_tx = vlib_buffer_get_current (p0); + + vnet_buffer (p0)->sw_if_index[VLIB_TX] = sw_if_index0; + hw_if0 = vnet_get_sup_hw_interface (vnm, sw_if_index0); + + /* Send reply back through input interface */ + vnet_buffer (p0)->sw_if_index[VLIB_TX] = sw_if_index0; + next0 = ARP_REPLY_NEXT_REPLY_TX; + + arp0->opcode = clib_host_to_net_u16 (ETHERNET_ARP_OPCODE_reply); + + arp0->ip4_over_ethernet[1] = arp0->ip4_over_ethernet[0]; + + mac_address_from_bytes (&arp0->ip4_over_ethernet[0].mac, + hw_if0->hw_address); + clib_mem_unaligned (&arp0->ip4_over_ethernet[0].ip4.data_u32, u32) = + if_addr0->data_u32; + + /* Hardware must be ethernet-like. */ + ASSERT (vec_len (hw_if0->hw_address) == 6); + + /* the rx nd tx ethernet headers wil overlap in the case + * when we received a tagged VLAN=0 packet, but we are sending + * back untagged */ + clib_memcpy_fast (eth_tx, rewrite0, vec_len (rewrite0)); + vec_free (rewrite0); + + return (next0); +} + +#endif + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vnet/arp/arp_proxy.c b/src/vnet/arp/arp_proxy.c new file mode 100644 index 00000000000..346a21775f8 --- /dev/null +++ b/src/vnet/arp/arp_proxy.c @@ -0,0 +1,407 @@ +/* + * ethernet/arp.c: IP v4 ARP node + * + * Copyright (c) 2010 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include + +typedef struct +{ + ip4_address_t lo_addr; + ip4_address_t hi_addr; + u32 fib_index; +} ethernet_proxy_arp_t; + +typedef struct arp_proxy_main_t_ +{ + /** Per interface state */ + bool *enabled_by_sw_if_index; + + /* Proxy arp vector */ + ethernet_proxy_arp_t *proxy_arps; +} arp_proxy_main_t; + +arp_proxy_main_t arp_proxy_main; + +void +proxy_arp_walk (proxy_arp_walk_t cb, void *data) +{ + arp_proxy_main_t *am = &arp_proxy_main; + ethernet_proxy_arp_t *pa; + + vec_foreach (pa, am->proxy_arps) + { + if (!cb (&pa->lo_addr, &pa->hi_addr, pa->fib_index, data)) + break; + } +} + +int +arp_proxy_disable (u32 sw_if_index) +{ + arp_proxy_main_t *am = &arp_proxy_main; + + vec_validate (am->enabled_by_sw_if_index, sw_if_index); + + if (am->enabled_by_sw_if_index[sw_if_index]) + { + vnet_feature_enable_disable ("arp", "arp-proxy", + sw_if_index, 0, NULL, 0); + } + am->enabled_by_sw_if_index[sw_if_index] = false; + + return (0); +} + +int +arp_proxy_enable (u32 sw_if_index) +{ + arp_proxy_main_t *am = &arp_proxy_main; + + vec_validate (am->enabled_by_sw_if_index, sw_if_index); + + if (!am->enabled_by_sw_if_index[sw_if_index]) + { + vnet_feature_enable_disable ("arp", "arp-proxy", + sw_if_index, 1, NULL, 0); + } + am->enabled_by_sw_if_index[sw_if_index] = true; + + return (0); +} + +static int +vnet_proxy_arp_add_del (const ip4_address_t * lo_addr, + const ip4_address_t * hi_addr, + u32 fib_index, int is_del) +{ + arp_proxy_main_t *am = &arp_proxy_main; + ethernet_proxy_arp_t *pa; + u32 found_at_index = ~0; + + vec_foreach (pa, am->proxy_arps) + { + if (pa->lo_addr.as_u32 == lo_addr->as_u32 && + pa->hi_addr.as_u32 == hi_addr->as_u32 && pa->fib_index == fib_index) + { + found_at_index = pa - am->proxy_arps; + break; + } + } + + if (found_at_index != ~0) + { + /* Delete, otherwise it's already in the table */ + if (is_del) + vec_delete (am->proxy_arps, 1, found_at_index); + return 0; + } + /* delete, no such entry */ + if (is_del) + return VNET_API_ERROR_NO_SUCH_ENTRY; + + /* add, not in table */ + vec_add2 (am->proxy_arps, pa, 1); + pa->lo_addr.as_u32 = lo_addr->as_u32; + pa->hi_addr.as_u32 = hi_addr->as_u32; + pa->fib_index = fib_index; + return 0; +} + +int +arp_proxy_add (u32 fib_index, + const ip4_address_t * lo, const ip4_address_t * hi) +{ + return (vnet_proxy_arp_add_del (lo, hi, fib_index, 0)); +} + +int +arp_proxy_del (u32 fib_index, + const ip4_address_t * lo, const ip4_address_t * hi) +{ + return (vnet_proxy_arp_add_del (lo, hi, fib_index, 1)); +} + +void +proxy_arp_intfc_walk (proxy_arp_intf_walk_t cb, void *data) +{ + arp_proxy_main_t *am = &arp_proxy_main; + bool *enabled; + + vec_foreach (enabled, am->enabled_by_sw_if_index) + { + if (*enabled) + cb (enabled - am->enabled_by_sw_if_index, data); + } +} + +static clib_error_t * +set_int_proxy_arp_command_fn (vlib_main_t * vm, + unformat_input_t * + input, vlib_cli_command_t * cmd) +{ + vnet_main_t *vnm = vnet_get_main (); + u32 sw_if_index; + int enable = 0; + + sw_if_index = ~0; + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (input, "%U", unformat_vnet_sw_interface, + vnm, &sw_if_index)) + ; + else if (unformat (input, "enable") || unformat (input, "on")) + enable = 1; + else if (unformat (input, "disable") || unformat (input, "off")) + enable = 0; + else + break; + } + + if (~0 == sw_if_index) + return clib_error_return (0, "unknown input '%U'", + format_unformat_error, input); + + if (enable) + arp_proxy_enable (sw_if_index); + else + arp_proxy_disable (sw_if_index); + + return 0; +} + +/* *INDENT-OFF* */ +/*? + * Enable proxy-arp on an interface. The vpp stack will answer ARP + * requests for the indicated address range. Multiple proxy-arp + * ranges may be provisioned. + * + * @note Proxy ARP as a technology is infamous for blackholing traffic. + * Also, the underlying implementation has not been performance-tuned. + * Avoid creating an unnecessarily large set of ranges. + * + * @cliexpar + * To enable proxy arp on a range of addresses, use: + * @cliexcmd{set ip arp proxy 6.0.0.1 - 6.0.0.11} + * Append 'del' to delete a range of proxy ARP addresses: + * @cliexcmd{set ip arp proxy 6.0.0.1 - 6.0.0.11 del} + * You must then specifically enable proxy arp on individual interfaces: + * @cliexcmd{set interface proxy-arp GigabitEthernet0/8/0 enable} + * To disable proxy arp on an individual interface: + * @cliexcmd{set interface proxy-arp GigabitEthernet0/8/0 disable} + ?*/ +VLIB_CLI_COMMAND (set_int_proxy_enable_command, static) = { + .path = "set interface proxy-arp", + .short_help = + "set interface proxy-arp [enable|disable]", + .function = set_int_proxy_arp_command_fn, +}; +/* *INDENT-ON* */ + +typedef struct +{ + u8 packet_data[64]; +} ethernet_arp_input_trace_t; + +static u8 * +format_ethernet_arp_input_trace (u8 * s, va_list * va) +{ + CLIB_UNUSED (vlib_main_t * vm) = va_arg (*va, vlib_main_t *); + CLIB_UNUSED (vlib_node_t * node) = va_arg (*va, vlib_node_t *); + ethernet_arp_input_trace_t *t = va_arg (*va, ethernet_arp_input_trace_t *); + + s = format (s, "%U", + format_ethernet_arp_header, + t->packet_data, sizeof (t->packet_data)); + + return s; +} + +static uword +arp_proxy (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * frame) +{ + arp_proxy_main_t *am = &arp_proxy_main; + vnet_main_t *vnm = vnet_get_main (); + u32 n_left_from, next_index, *from, *to_next; + u32 n_arp_replies_sent = 0; + + from = vlib_frame_vector_args (frame); + n_left_from = frame->n_vectors; + next_index = node->cached_next_index; + + if (node->flags & VLIB_NODE_FLAG_TRACE) + vlib_trace_frame_buffers_only (vm, node, from, frame->n_vectors, + /* stride */ 1, + sizeof (ethernet_arp_input_trace_t)); + + while (n_left_from > 0) + { + u32 n_left_to_next; + + vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next); + + while (n_left_from > 0 && n_left_to_next > 0) + { + vlib_buffer_t *p0; + ethernet_arp_header_t *arp0; + ethernet_header_t *eth_rx; + ip4_address_t proxy_src; + u32 pi0, error0, next0, sw_if_index0, fib_index0; + u8 is_request0; + ethernet_proxy_arp_t *pa; + + pi0 = from[0]; + to_next[0] = pi0; + from += 1; + to_next += 1; + n_left_from -= 1; + n_left_to_next -= 1; + + p0 = vlib_get_buffer (vm, pi0); + arp0 = vlib_buffer_get_current (p0); + /* Fill in ethernet header. */ + eth_rx = ethernet_buffer_get_header (p0); + + is_request0 = arp0->opcode + == clib_host_to_net_u16 (ETHERNET_ARP_OPCODE_request); + + error0 = ETHERNET_ARP_ERROR_replies_sent; + sw_if_index0 = vnet_buffer (p0)->sw_if_index[VLIB_RX]; + next0 = ARP_REPLY_NEXT_DROP; + + fib_index0 = ip4_fib_table_get_index_for_sw_if_index (sw_if_index0); + if (~0 == fib_index0) + { + error0 = ETHERNET_ARP_ERROR_interface_no_table; + } + + if (0 == error0 && is_request0) + { + u32 this_addr = clib_net_to_host_u32 + (arp0->ip4_over_ethernet[1].ip4.as_u32); + + vec_foreach (pa, am->proxy_arps) + { + u32 lo_addr = clib_net_to_host_u32 (pa->lo_addr.as_u32); + u32 hi_addr = clib_net_to_host_u32 (pa->hi_addr.as_u32); + + /* an ARP request hit in the proxy-arp table? */ + if ((this_addr >= lo_addr && this_addr <= hi_addr) && + (fib_index0 == pa->fib_index)) + { + proxy_src.as_u32 = + arp0->ip4_over_ethernet[1].ip4.data_u32; + + /* + * change the interface address to the proxied + */ + n_arp_replies_sent++; + + next0 = + arp_mk_reply (vnm, p0, sw_if_index0, &proxy_src, arp0, + eth_rx); + } + } + } + else + { + p0->error = node->errors[error0]; + } + + vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next, + n_left_to_next, pi0, next0); + } + + vlib_put_next_frame (vm, node, next_index, n_left_to_next); + } + + vlib_error_count (vm, node->node_index, + ETHERNET_ARP_ERROR_replies_sent, n_arp_replies_sent); + + return frame->n_vectors; +} + +static char *ethernet_arp_error_strings[] = { +#define _(sym,string) string, + foreach_ethernet_arp_error +#undef _ +}; + +VLIB_REGISTER_NODE (arp_proxy_node, static) = +{ + .function = arp_proxy,.name = "arp-proxy",.vector_size = + sizeof (u32),.n_errors = ETHERNET_ARP_N_ERROR,.error_strings = + ethernet_arp_error_strings,.n_next_nodes = ARP_REPLY_N_NEXT,.next_nodes = + { + [ARP_REPLY_NEXT_DROP] = "error-drop", + [ARP_REPLY_NEXT_REPLY_TX] = "interface-output",} +,.format_buffer = format_ethernet_arp_header,.format_trace = + format_ethernet_arp_input_trace,}; + +static clib_error_t * +show_ip4_arp (vlib_main_t * vm, + unformat_input_t * input, vlib_cli_command_t * cmd) +{ + arp_proxy_main_t *am = &arp_proxy_main; + ethernet_proxy_arp_t *pa; + + if (vec_len (am->proxy_arps)) + { + vlib_cli_output (vm, "Proxy arps enabled for:"); + vec_foreach (pa, am->proxy_arps) + { + vlib_cli_output (vm, "Fib_index %d %U - %U ", + pa->fib_index, + format_ip4_address, &pa->lo_addr, + format_ip4_address, &pa->hi_addr); + } + } + + return (NULL); +} + +/*? + * Display all the IPv4 ARP proxy entries. + * + * @cliexpar + * Example of how to display the IPv4 ARP table: + * @cliexstart{show ip arp} + * 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_ip4_arp_command, static) = { + .path = "show arp proxy", + .function = show_ip4_arp, + .short_help = "show ip arp", +}; +/* *INDENT-ON* */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vnet/arp/arp_test.c b/src/vnet/arp/arp_test.c new file mode 100644 index 00000000000..861d0739406 --- /dev/null +++ b/src/vnet/arp/arp_test.c @@ -0,0 +1,168 @@ +/* + *------------------------------------------------------------------ + * arp_test.c + * + * 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 +#include +#include +#include + +#include + +#include + +typedef struct +{ + /* API message ID base */ + u16 msg_id_base; + u32 ping_id; + vat_main_t *vat_main; +} arp_test_main_t; + +arp_test_main_t arp_test_main; + +#define __plugin_msg_base arp_test_main.msg_id_base +#include +uword unformat_sw_if_index (unformat_input_t * input, va_list * args); + +/* Declare message IDs */ +#include +#include +#include + +static int +api_proxy_arp_dump (vat_main_t * vam) +{ + return -1; +} + +static int +api_proxy_arp_intfc_dump (vat_main_t * vam) +{ + return -1; +} + +static void +vl_api_proxy_arp_details_t_handler (vl_api_proxy_arp_details_t * mp) +{ +} + +static void +vl_api_proxy_arp_intfc_details_t_handler (vl_api_proxy_arp_intfc_details_t * + mp) +{ +} + +static int +api_proxy_arp_add_del (vat_main_t * vam) +{ + unformat_input_t *i = vam->input; + vl_api_proxy_arp_add_del_t *mp; + u32 vrf_id = 0; + u8 is_add = 1; + vl_api_ip4_address_t lo, hi; + u8 range_set = 0; + int ret; + + while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT) + { + if (unformat (i, "vrf %d", &vrf_id)) + ; + else if (unformat (i, "%U - %U", unformat_vl_api_ip4_address, &lo, + unformat_vl_api_ip4_address, &hi)) + range_set = 1; + else if (unformat (i, "del")) + is_add = 0; + else + { + clib_warning ("parse error '%U'", format_unformat_error, i); + return -99; + } + } + + if (range_set == 0) + { + errmsg ("address range not set"); + return -99; + } + + M (PROXY_ARP_ADD_DEL, mp); + + mp->proxy.table_id = ntohl (vrf_id); + mp->is_add = is_add; + clib_memcpy (mp->proxy.low, &lo, sizeof (lo)); + clib_memcpy (mp->proxy.hi, &hi, sizeof (hi)); + + S (mp); + W (ret); + return ret; +} + +static int +api_proxy_arp_intfc_enable_disable (vat_main_t * vam) +{ + unformat_input_t *i = vam->input; + vl_api_proxy_arp_intfc_enable_disable_t *mp; + u32 sw_if_index; + u8 enable = 1; + u8 sw_if_index_set = 0; + int ret; + + while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT) + { + if (unformat (i, "%U", unformat_sw_if_index, vam, &sw_if_index)) + sw_if_index_set = 1; + else if (unformat (i, "sw_if_index %d", &sw_if_index)) + sw_if_index_set = 1; + else if (unformat (i, "enable")) + enable = 1; + else if (unformat (i, "disable")) + enable = 0; + else + { + clib_warning ("parse error '%U'", format_unformat_error, i); + return -99; + } + } + + if (sw_if_index_set == 0) + { + errmsg ("missing interface name or sw_if_index"); + return -99; + } + + M (PROXY_ARP_INTFC_ENABLE_DISABLE, mp); + + mp->sw_if_index = ntohl (sw_if_index); + mp->enable = enable; + + S (mp); + W (ret); + return ret; +} + +#include + + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ -- cgit 1.2.3-korg