/* * 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 #include #include #include #include /** * @file * @brief IPv4 ARP. * * This file contains code to manage the IPv4 ARP tables (IP Address * to MAC Address lookup). */ void vl_api_rpc_call_main_thread (void *fp, u8 * data, u32 data_length); /** * @brief Per-interface ARP configuration and state */ typedef struct ethernet_arp_interface_t_ { /** * Hash table of ARP entries. * Since this hash table is per-interface, the key is only the IPv4 address. */ uword *arp_entries; } ethernet_arp_interface_t; typedef struct { ip4_address_t lo_addr; ip4_address_t hi_addr; u32 fib_index; } ethernet_proxy_arp_t; typedef struct { u32 next_index; uword node_index; uword type_opaque; uword data; /* Used for arp event notification only */ arp_change_event_cb_t data_callback; u32 pid; } pending_resolution_t; typedef struct { /* Hash tables mapping name to opcode. */ uword *opcode_by_name; /* lite beer "glean" adjacency handling */ uword *pending_resolutions_by_address; pending_resolution_t *pending_resolutions; /* Mac address change notification */ uword *mac_changes_by_address; pending_resolution_t *mac_changes; ethernet_arp_ip4_entry_t *ip4_entry_pool; /* ARP attack mitigation */ u32 arp_delete_rotor; u32 limit_arp_cache_size; /** Per interface state */ ethernet_arp_interface_t *ethernet_arp_by_sw_if_index; /* Proxy arp vector */ ethernet_proxy_arp_t *proxy_arps; uword wc_ip4_arp_publisher_node; uword wc_ip4_arp_publisher_et; } ethernet_arp_main_t; static ethernet_arp_main_t ethernet_arp_main; typedef struct { u32 sw_if_index; ip4_address_t ip4; mac_address_t mac; ip_neighbor_flags_t nbr_flags; u32 flags; #define ETHERNET_ARP_ARGS_REMOVE (1<<0) #define ETHERNET_ARP_ARGS_FLUSH (1<<1) #define ETHERNET_ARP_ARGS_POPULATE (1<<2) #define ETHERNET_ARP_ARGS_WC_PUB (1<<3) } vnet_arp_set_ip4_over_ethernet_rpc_args_t; static const u8 vrrp_prefix[] = { 0x00, 0x00, 0x5E, 0x00, 0x01 }; /* Node index for send_garp_na_process */ u32 send_garp_na_process_node_index; static void set_ip4_over_ethernet_rpc_callback (vnet_arp_set_ip4_over_ethernet_rpc_args_t * a); static u8 * format_ethernet_arp_hardware_type (u8 * s, va_list * va) { ethernet_arp_hardware_type_t h = va_arg (*va, ethernet_arp_hardware_type_t); char *t = 0; switch (h) { #define _(n,f) case n: t = #f; break; foreach_ethernet_arp_hardware_type; #undef _ default: return format (s, "unknown 0x%x", h); } return format (s, "%s", t); } static u8 * format_ethernet_arp_opcode (u8 * s, va_list * va) { ethernet_arp_opcode_t o = va_arg (*va, ethernet_arp_opcode_t); char *t = 0; switch (o) { #define _(f) case ETHERNET_ARP_OPCODE_##f: t = #f; break; foreach_ethernet_arp_opcode; #undef _ default: return format (s, "unknown 0x%x", o); } return format (s, "%s", t); } 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; } static u8 * format_ethernet_arp_header (u8 * s, va_list * va) { ethernet_arp_header_t *a = va_arg (*va, ethernet_arp_header_t *); u32 max_header_bytes = va_arg (*va, u32); u32 indent; u16 l2_type, l3_type; if (max_header_bytes != 0 && sizeof (a[0]) > max_header_bytes) return format (s, "ARP header truncated"); l2_type = clib_net_to_host_u16 (a->l2_type); l3_type = clib_net_to_host_u16 (a->l3_type); indent = format_get_indent (s); s = format (s, "%U, type %U/%U, address size %d/%d", format_ethernet_arp_opcode, clib_net_to_host_u16 (a->opcode), format_ethernet_arp_hardware_type, l2_type, format_ethernet_type, l3_type, a->n_l2_address_bytes, a->n_l3_address_bytes); if (l2_type == ETHERNET_ARP_HARDWARE_TYPE_ethernet && l3_type == ETHERNET_TYPE_IP4) { s = format (s, "\n%U%U/%U -> %U/%U", format_white_space, indent, format_mac_address_t, &a->ip4_over_ethernet[0].mac, format_ip4_address, &a->ip4_over_ethernet[0].ip4, format_mac_address_t, &a->ip4_over_ethernet[1].mac, format_ip4_address, &a->ip4_over_ethernet[1].ip4); } else { uword n2 = a->n_l2_address_bytes; uword n3 = a->n_l3_address_bytes; s = format (s, "\n%U%U/%U -> %U/%U", format_white_space, indent, format_hex_bytes, a->data + 0 * n2 + 0 * n3, n2, format_hex_bytes, a->data + 1 * n2 + 0 * n3, n3, format_hex_bytes, a->data + 1 * n2 + 1 * n3, n2, format_hex_bytes, a->data + 2 * n2 + 1 * n3, n3); } return s; } u8 * format_ethernet_arp_ip4_entry (u8 * s, va_list * va) { vnet_main_t *vnm = va_arg (*va, vnet_main_t *); ethernet_arp_ip4_entry_t *e = va_arg (*va, ethernet_arp_ip4_entry_t *); vnet_sw_interface_t *si; u8 *flags = 0; if (!e) return format (s, "%=12s%=16s%=6s%=20s%=24s", "Time", "IP4", "Flags", "Ethernet", "Interface"); si = vnet_get_sw_interface (vnm, e->sw_if_index); if (e->flags & IP_NEIGHBOR_FLAG_STATIC) flags = format (flags, "S"); if (e->flags & IP_NEIGHBOR_FLAG_DYNAMIC) flags = format (flags, "D"); if (e->flags & IP_NEIGHBOR_FLAG_NO_FIB_ENTRY) flags = format (flags, "N"); s = format (s, "%=12U%=16U%=6s%=20U%U", format_vlib_time, vnm->vlib_main, e->time_last_updated, format_ip4_address, &e->ip4_address, flags ? (char *) flags : "", format_mac_address_t, &e->mac, format_vnet_sw_interface_name, vnm, si); vec_free (flags); return s; } 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 u8 * format_arp_term_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 *); /* arp-term trace data saved is either arp or ip6/icmp6 packet: - for arp, the 1st 16-bit field is hw type of value of 0x0001. - for ip6, the first nibble has value of 6. */ s = format (s, "%U", t->packet_data[0] == 0 ? format_ethernet_arp_header : format_ip6_header, t->packet_data, sizeof (t->packet_data)); return s; } static void arp_nbr_probe (ip_adjacency_t * adj) { vnet_main_t *vnm = vnet_get_main (); ip4_main_t *im = &ip4_main; ip_interface_address_t *ia; ethernet_arp_header_t *h; vnet_hw_interface_t *hi; vnet_sw_interface_t *si; ip4_address_t *src; vlib_buffer_t *b; vlib_main_t *vm; u32 bi = 0; vm = vlib_get_main (); si = vnet_get_sw_interface (vnm, adj->rewrite_header.sw_if_index); if (!(si->flags & VNET_SW_INTERFACE_FLAG_ADMIN_UP)) { return; } src = ip4_interface_address_matching_destination (im, &adj->sub_type.nbr.next_hop. ip4, adj->rewrite_header. sw_if_index, &ia); if (!src) { return; } h = vlib_packet_template_get_packet (vm, &im->ip4_arp_request_packet_template, &bi); if (!h) return; hi = vnet_get_sup_hw_interface (vnm, adj->rewrite_header.sw_if_index); mac_address_from_bytes (&h->ip4_over_ethernet[0].mac, hi->hw_address); h->ip4_over_ethernet[0].ip4 = src[0]; h->ip4_over_ethernet[1].ip4 = adj->sub_type.nbr.next_hop.ip4; b = vlib_get_buffer (vm, bi); vnet_buffer (b)->sw_if_index[VLIB_RX] = vnet_buffer (b)->sw_if_index[VLIB_TX] = adj->rewrite_header.sw_if_index; /* Add encapsulation string for software interface (e.g. ethernet header). */ vnet_rewrite_one_header (adj[0], h, sizeof (ethernet_header_t)); vlib_buffer_advance (b, -adj->rewrite_header.data_bytes); { 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); } } static void arp_mk_complete (adj_index_t ai, ethernet_arp_ip4_entry_t * e) { adj_nbr_update_rewrite (ai, ADJ_NBR_REWRITE_FLAG_COMPLETE, ethernet_build_rewrite (vnet_get_main (), e->sw_if_index, adj_get_link_type (ai), &e->mac)); } static void arp_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, VNET_LINK_ARP, VNET_REWRITE_FOR_SW_INTERFACE_ADDRESS_BROADCAST)); } static ethernet_arp_ip4_entry_t * arp_entry_find (ethernet_arp_interface_t * eai, const ip4_address_t * addr) { ethernet_arp_main_t *am = ðernet_arp_main; ethernet_arp_ip4_entry_t *e = NULL; uword *p; if (NULL != eai->arp_entries) { p = hash_get (eai->arp_entries, addr->as_u32); if (!p) return (NULL); e = pool_elt_at_index (am->ip4_entry_pool, p[0]); } return (e); } static adj_walk_rc_t arp_mk_complete_walk (adj_index_t ai, void *ctx) { ethernet_arp_ip4_entry_t *e = ctx; arp_mk_complete (ai, e); return (ADJ_WALK_RC_CONTINUE); } static adj_walk_rc_t arp_mk_incomplete_walk (adj_index_t ai, void *ctx) { arp_mk_incomplete (ai); return (ADJ_WALK_RC_CONTINUE); } void arp_update_adjacency (vnet_main_t * vnm, u32 sw_if_index, u32 ai) { ethernet_arp_main_t *am = ðernet_arp_main; ethernet_arp_interface_t *arp_int; ethernet_arp_ip4_entry_t *e; ip_adjacency_t *adj; adj = adj_get (ai); vec_validate (am->ethernet_arp_by_sw_if_index, sw_if_index); arp_int = &am->ethernet_arp_by_sw_if_index[sw_if_index]; e = arp_entry_find (arp_int, &adj->sub_type.nbr.next_hop.ip4); switch (adj->lookup_next_index) { case IP_LOOKUP_NEXT_GLEAN: adj_glean_update_rewrite (ai); break; case IP_LOOKUP_NEXT_ARP: if (NULL != e) { adj_nbr_walk_nh4 (sw_if_index, &e->ip4_address, arp_mk_complete_walk, e); } 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, 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. */ arp_nbr_probe (adj); } break; case IP_LOOKUP_NEXT_BCAST: adj_nbr_update_rewrite (ai, ADJ_NBR_REWRITE_FLAG_COMPLETE, ethernet_build_rewrite (vnm, sw_if_index, VNET_LINK_IP4, VNET_REWRITE_FOR_SW_INTERFACE_ADDRESS_BROADCAST)); break; case IP_LOOKUP_NEXT_MCAST: { /* * Construct a partial rewrite from the known ethernet mcast dest MAC */ u8 *rewrite; u8 offset; rewrite = ethernet_build_rewrite (vnm, sw_if_index, adj->ia_link, ethernet_ip4_mcast_dst_addr ()); offset = vec_len (rewrite) - 2; /* * Complete the remaining fields of the adj's rewrite to direct the * complete of the rewrite at switch time by copying in the IP * dst address's bytes. * Ofset is 2 bytes into the MAC desintation address. */ adj_mcast_update_rewrite (ai, rewrite, offset); break; } 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; } } static void arp_adj_fib_add (ethernet_arp_ip4_entry_t * e, u32 fib_index) { fib_prefix_t pfx = { .fp_len = 32, .fp_proto = FIB_PROTOCOL_IP4, .fp_addr.ip4 = e->ip4_address, }; e->fib_entry_index = fib_table_entry_path_add (fib_index, &pfx, FIB_SOURCE_ADJ, FIB_ENTRY_FLAG_ATTACHED, DPO_PROTO_IP4, &pfx.fp_addr, e->sw_if_index, ~0, 1, NULL, FIB_ROUTE_PATH_FLAG_NONE); fib_table_lock (fib_index, FIB_PROTOCOL_IP4, FIB_SOURCE_ADJ); } static void arp_adj_fib_remove (ethernet_arp_ip4_entry_t * e, u32 fib_index) { if (FIB_NODE_INDEX_INVALID != e->fib_entry_index) { fib_prefix_t pfx = { .fp_len = 32, .fp_proto = FIB_PROTOCOL_IP4, .fp_addr.ip4 = e->ip4_address, }; u32 fib_index; fib_index = ip4_fib_table_get_index_for_sw_if_index (e->sw_if_index); fib_table_entry_path_remove (fib_index, &pfx, FIB_SOURCE_ADJ, DPO_PROTO_IP4, &pfx.fp_addr, e->sw_if_index, ~0, 1, FIB_ROUTE_PATH_FLAG_NONE); fib_table_unlock (fib_index, FIB_PROTOCOL_IP4, FIB_SOURCE_ADJ); } } static ethernet_arp_ip4_entry_t * force_reuse_arp_entry (void) { ethernet_arp_ip4_entry_t *e; ethernet_arp_main_t *am = ðernet_arp_main; u32 count = 0; u32 index = pool_next_index (am->ip4_entry_pool, am->arp_delete_rotor); if (index == ~0) /* Try again from elt 0 */ index = pool_next_index (am->ip4_entry_pool, index); /* Find a non-static random entry to free up for reuse */ do { if ((count++ == 100) || (index == ~0)) return NULL; /* give up after 100 entries */ e = pool_elt_at_index (am->ip4_entry_pool, index); am->arp_delete_rotor = index; index = pool_next_index (am->ip4_entry_pool, index); } while (e->flags & IP_NEIGHBOR_FLAG_STATIC); /* Remove ARP entry from its interface and update fib */ hash_unset (am->ethernet_arp_by_sw_if_index[e->sw_if_index].arp_entries, e->ip4_address.as_u32); arp_adj_fib_remove (e, ip4_fib_table_get_index_for_sw_if_index (e->sw_if_index)); adj_nbr_walk_nh4 (e->sw_if_index, &e->ip4_address, arp_mk_incomplete_walk, e); return e; } static int vnet_arp_set_ip4_over_ethernet_internal (vnet_main_t * vnm, vnet_arp_set_ip4_over_ethernet_rpc_args_t * args) { ethernet_arp_ip4_entry_t *e = 0; ethernet_arp_main_t *am = ðernet_arp_main; vlib_main_t *vm = vlib_get_main (); int make_new_arp_cache_entry = 1; uword *p; pending_resolution_t *pr, *mc; ethernet_arp_interface_t *arp_int; u32 sw_if_index = args->sw_if_index; vec_validate (am->ethernet_arp_by_sw_if_index, sw_if_index); arp_int = &am->ethernet_arp_by_sw_if_index[sw_if_index]; if (NULL != arp_int->arp_entries) { p = hash_get (arp_int->arp_entries, args->ip4.as_u32); if (p) { e = pool_elt_at_index (am->ip4_entry_pool, p[0]); /* Refuse to over-write static arp. */ if (!(args->nbr_flags & IP_NEIGHBOR_FLAG_STATIC) && (e->flags & IP_NEIGHBOR_FLAG_STATIC)) { /* if MAC address match, still check to send event */ if (mac_address_equal (&e->mac, &args->mac)) goto check_customers; return -2; } make_new_arp_cache_entry = 0; } } if (make_new_arp_cache_entry) { if (am->limit_arp_cache_size && pool_elts (am->ip4_entry_pool) >= am->limit_arp_cache_size) { e = force_reuse_arp_entry (); if (NULL == e) return -2; } else pool_get (am->ip4_entry_pool, e); if (NULL == arp_int->arp_entries) arp_int->arp_entries = hash_create (0, sizeof (u32)); hash_set (arp_int->arp_entries, args->ip4.as_u32, e - am->ip4_entry_pool); e->sw_if_index = sw_if_index; e->ip4_address = args->ip4; e->fib_entry_index = FIB_NODE_INDEX_INVALID; mac_address_copy (&e->mac, &args->mac); if (!(args->nbr_flags & IP_NEIGHBOR_FLAG_NO_FIB_ENTRY)) { arp_adj_fib_add (e, ip4_fib_table_get_index_for_sw_if_index (e->sw_if_index)); } else { e->flags |= IP_NEIGHBOR_FLAG_NO_FIB_ENTRY; } } else { /* * prevent a DoS attack from the data-plane that * spams us with no-op updates to the MAC address */ if (mac_address_equal (&e->mac, &args->mac)) { e->time_last_updated = vlib_time_now (vm); goto check_customers; } /* Update ethernet address. */ mac_address_copy (&e->mac, &args->mac); } /* Update time stamp and flags. */ e->time_last_updated = vlib_time_now (vm); if (args->nbr_flags & IP_NEIGHBOR_FLAG_STATIC) { e->flags &= ~IP_NEIGHBOR_FLAG_DYNAMIC; e->flags |= IP_NEIGHBOR_FLAG_STATIC; } else { e->flags &= ~IP_NEIGHBOR_FLAG_STATIC; e->flags |= IP_NEIGHBOR_FLAG_DYNAMIC; } adj_nbr_walk_nh4 (sw_if_index, &e->ip4_address, arp_mk_complete_walk, e); check_customers: /* Customer(s) waiting for this address to be resolved? */ p = hash_get (am->pending_resolutions_by_address, args->ip4.as_u32); if (p) { u32 next_index; next_index = p[0]; while (next_index != (u32) ~ 0) { pr = pool_elt_at_index (am->pending_resolutions, next_index); vlib_process_signal_event (vm, pr->node_index, pr->type_opaque, pr->data); next_index = pr->next_index; pool_put (am->pending_resolutions, pr); } hash_unset (am->pending_resolutions_by_address, args->ip4.as_u32); } /* Customer(s) requesting ARP event for this address? */ p = hash_get (am->mac_changes_by_address, args->ip4.as_u32); if (p) { u32 next_index; next_index = p[0]; while (next_index != (u32) ~ 0) { int rv = 1; mc = pool_elt_at_index (am->mac_changes, next_index); /* Call the user's data callback, return 1 to suppress dup events */ if (mc->data_callback) rv = (mc->data_callback) (mc->data, &args->mac, sw_if_index, 0); /* * Signal the resolver process, as long as the user * says they want to be notified */ if (rv == 0) vlib_process_signal_event (vm, mc->node_index, mc->type_opaque, mc->data); next_index = mc->next_index; } } return 0; } void vnet_register_ip4_arp_resolution_event (vnet_main_t * vnm, void *address_arg, uword node_index, uword type_opaque, uword data) { ethernet_arp_main_t *am = ðernet_arp_main; ip4_address_t *address = address_arg; uword *p; pending_resolution_t *pr; pool_get (am->pending_resolutions, pr); pr->next_index = ~0; pr->node_index = node_index; pr->type_opaque = type_opaque; pr->data = data; pr->data_callback = 0; p = hash_get (am->pending_resolutions_by_address, address->as_u32); if (p) { /* Insert new resolution at the head of the list */ pr->next_index = p[0]; hash_unset (am->pending_resolutions_by_address, address->as_u32); } hash_set (am->pending_resolutions_by_address, address->as_u32, pr - am->pending_resolutions); } int vnet_add_del_ip4_arp_change_event (vnet_main_t * vnm, arp_change_event_cb_t data_callback, u32 pid, void *address_arg, uword node_index, uword type_opaque, uword data, int is_add) { ethernet_arp_main_t *am = ðernet_arp_main; ip4_address_t *address = address_arg; /* Try to find an existing entry */ u32 *first = (u32 *) hash_get (am->mac_changes_by_address, address->as_u32); u32 *p = first; pending_resolution_t *mc; while (p && *p != ~0) { mc = pool_elt_at_index (am->mac_changes, *p); if (mc->node_index == node_index && mc->type_opaque == type_opaque && mc->pid == pid) break; p = &mc->next_index; } int found = p && *p != ~0; if (is_add) { if (found) return VNET_API_ERROR_ENTRY_ALREADY_EXISTS; pool_get (am->mac_changes, mc); /* *INDENT-OFF* */ *mc = (pending_resolution_t) { .next_index = ~0, .node_index = node_index, .type_opaque = type_opaque, .data = data, .data_callback = data_callback, .pid = pid, }; /* *INDENT-ON* */ /* Insert new resolution at the end of the list */ u32 new_idx = mc - am->mac_changes; if (p) p[0] = new_idx; else hash_set (am->mac_changes_by_address, address->as_u32, new_idx); } else { if (!found) return VNET_API_ERROR_NO_SUCH_ENTRY; /* Clients may need to clean up pool entries, too */ if (data_callback) /* no new mac addrs */ (data_callback) (mc->data, NULL, ~0, NULL); /* Remove the entry from the list and delete the entry */ *p = mc->next_index; pool_put (am->mac_changes, mc); /* Remove from hash if we deleted the last entry */ if (*p == ~0 && p == first) hash_unset (am->mac_changes_by_address, address->as_u32); } return 0; } /* Either we drop the packet or we send a reply to the sender. */ typedef enum { ARP_INPUT_NEXT_DROP, ARP_INPUT_NEXT_REPLY_TX, ARP_INPUT_N_NEXT, } arp_input_next_t; #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_input_error_t; 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; } static u32 arp_learn (vnet_main_t * vnm, ethernet_arp_main_t * am, u32 sw_if_index, const ethernet_arp_ip4_over_ethernet_address_t * addr) { vnet_arp_set_ip4_over_ethernet (vnm, sw_if_index, addr, 0); return (ETHERNET_ARP_ERROR_l3_src_address_learned); } static uword arp_input (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * frame) { ethernet_arp_main_t *am = ðernet_arp_main; vnet_main_t *vnm = vnet_get_main (); ip4_main_t *im4 = &ip4_main; u32 n_left_from, next_index, *from, *to_next; u32 n_replies_sent = 0, n_proxy_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; vnet_hw_interface_t *hw_if0; ethernet_arp_header_t *arp0; ethernet_header_t *eth_rx, *eth_tx; const ip4_address_t *if_addr0; ip4_address_t proxy_src; u32 pi0, error0, next0, sw_if_index0, conn_sw_if_index0, fib_index0; u8 is_request0, dst_is_local0, is_unnum0, is_vrrp_reply0; ethernet_proxy_arp_t *pa; fib_node_index_t dst_fei, src_fei; const fib_prefix_t *pfx0; fib_entry_flag_t src_flags, dst_flags; u8 *rewrite0, rewrite0_len; pi0 = from[0]; to_next[0] = pi0; from += 1; to_next += 1; n_left_from -= 1; n_left_to_next -= 1; pa = 0; 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; 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); sw_if_index0 = vnet_buffer (p0)->sw_if_index[VLIB_RX]; /* not playing the ARP game if the interface is not IPv4 enabled */ error0 = (im4->ip_enabled_by_sw_if_index[sw_if_index0] == 0 ? ETHERNET_ARP_ERROR_interface_not_ip_enabled : error0); if (error0) goto drop2; /* 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 drop2; } dst_fei = ip4_fib_table_lookup (ip4_fib_get (fib_index0), &arp0->ip4_over_ethernet[1].ip4, 32); dst_flags = fib_entry_get_flags (dst_fei); conn_sw_if_index0 = fib_entry_get_resolving_interface (dst_fei); /* Honor unnumbered interface, if any */ is_unnum0 = sw_if_index0 != conn_sw_if_index0; { /* * 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 drop1; } /* 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 drop2; } } if (fib_entry_is_sourced (dst_fei, FIB_SOURCE_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 (vnm, am, sw_if_index0, &arp0->ip4_over_ethernet[0]); goto drop2; } else if (!(FIB_ENTRY_FLAG_CONNECTED & dst_flags)) { error0 = ETHERNET_ARP_ERROR_l3_dst_address_not_local; goto drop1; } if (sw_if_index0 != fib_entry_get_resolving_interface (src_fei)) { /* * The interface the ARP was received on is not the interface * on which the covering prefix is configured. Maybe this is a * case for unnumbered. */ is_unnum0 = 1; } 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 drop2; } /* 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 (vnm, am, 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 drop1; } else if (arp0->opcode == clib_host_to_net_u16 (ETHERNET_ARP_OPCODE_request) && (dst_is_local0 == 0)) { goto drop1; } send_reply: /* 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_INPUT_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); if (NULL == pa) { if (is_unnum0) { if (!arp_unnumbered (p0, sw_if_index0, conn_sw_if_index0)) { error0 = ETHERNET_ARP_ERROR_unnumbered_mismatch; goto drop2; } } } /* We are going to reply to this request, so, in the absence of errors, learn the sender */ if (!error0) error0 = arp_learn (vnm, am, sw_if_index0, &arp0->ip4_over_ethernet[1]); vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next, n_left_to_next, pi0, next0); n_replies_sent += 1; continue; drop1: if (arp0->ip4_over_ethernet[0].ip4.as_u32 == arp0->ip4_over_ethernet[1].ip4.as_u32) { error0 = ETHERNET_ARP_ERROR_gratuitous_arp; goto drop2; } /* See if proxy arp is configured for the address */ if (is_request0) { vnet_sw_interface_t *si; u32 this_addr = clib_net_to_host_u32 (arp0->ip4_over_ethernet[1].ip4.as_u32); u32 fib_index0; si = vnet_get_sw_interface (vnm, sw_if_index0); if (!(si->flags & VNET_SW_INTERFACE_FLAG_PROXY_ARP)) goto drop2; fib_index0 = vec_elt (im4->fib_index_by_sw_if_index, sw_if_index0); 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 */ if_addr0 = &proxy_src; is_unnum0 = 0; n_proxy_arp_replies_sent++; goto send_reply; } } } drop2: next0 = ARP_INPUT_NEXT_DROP; p0->error = node->errors[error0]; vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to
# 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.

"""Algorithms to generate tables.
"""


import logging
import csv

from string import replace
from collections import OrderedDict
from numpy import nan, isnan
from xml.etree import ElementTree as ET

from errors import PresentationError
from utils import mean, stdev, relative_change, classify_anomalies, \
    convert_csv_to_pretty_txt


def generate_tables(spec, data):
    """Generate all tables specified in the specification file.

    :param spec: Specification read from the specification file.
    :param data: Data to process.
    :type spec: Specification
    :type data: InputData
    """

    logging.info("Generating the tables ...")
    for table in spec.tables:
        try:
            eval(table["algorithm"])(table, data)
        except NameError as err:
            logging.error("Probably algorithm '{alg}' is not defined: {err}".
                          format(alg=table["algorithm"], err=repr(err)))
    logging.info("Done.")


def table_details(table, input_data):
    """Generate the table(s) with algorithm: table_detailed_test_results
    specified in the specification file.

    :param table: Table to generate.
    :param input_data: Data to process.
    :type table: pandas.Series
    :type input_data: InputData
    """

    logging.info("  Generating the table {0} ...".
                 format(table.get("title", "")))

    # Transform the data
    logging.info("    Creating the data set for the {0} '{1}'.".
                 format(table.get("type", ""), table.get("title", "")))
    data = input_data.filter_data(table)

    # Prepare the header of the tables
    header = list()
    for column in table["columns"]:
        header.append('"{0}"'.format(str(column["title"]).replace('"', '""')))

    # Generate the data for the table according to the model in the table
    # specification
    job = table["data"].keys()[0]
    build = str(table["data"][job][0])
    try:
        suites = input_data.suites(job, build)
    except KeyError:
        logging.error("    No data available. The table will not be generated.")
        return

    for suite_longname, suite in suites.iteritems():
        # Generate data
        suite_name = suite["name"]
        table_lst = list()
        for test in data[job][build].keys():
            if data[job][build][test]["parent"] in suite_name:
                row_lst = list()
                for column in table["columns"]:
                    try:
                        col_data = str(data[job][build][test][column["data"].
                                       split(" ")[1]]).replace('"', '""')
                        if column["data"].split(" ")[1] in ("vat-history",
                                                            "show-run"):
                            col_data = replace(col_data, " |br| ", "",
                                               maxreplace=1)
                            col_data = " |prein| {0} |preout| ".\
                                format(col_data[:-5])
                        row_lst.append('"{0}"'.format(col_data))
                    except KeyError:
                        row_lst.append("No data")
                table_lst.append(row_lst)

        # Write the data to file
        if table_lst:
            file_name = "{0}_{1}{2}".format(table["output-file"], suite_name,
                                            table["output-file-ext"])
            logging.info("      Writing file: '{}'".format(file_name))
            with open(file_name, "w") as file_handler:
                file_handler.write(",".join(header) + "\n")
                for item in table_lst:
                    file_handler.write(",".join(item) + "\n")

    logging.info("  Done.")


def table_merged_details(table, input_data):
    """Generate the table(s) with algorithm: table_merged_details
    specified in the specification file.

    :param table: Table to generate.
    :param input_data: Data to process.
    :type table: pandas.Series
    :type input_data: InputData
    """

    logging.info("  Generating the table {0} ...".
                 format(table.get("title", "")))

    # Transform the data
    logging.info("    Creating the data set for the {0} '{1}'.".
                 format(table.get("type", ""), table.get("title", "")))
    data = input_data.filter_data(table)
    data = input_data.merge_data(data)
    data.sort_index(inplace=True)

    logging.info("    Creating the data set for the {0} '{1}'.".
                 format(table.get("type", ""), table.get("title", "")))
    suites = input_data.filter_data(table, data_set="suites")
    suites = input_data.merge_data(suites)

    # Prepare the header of the tables
    header = list()
    for column in table["columns"]:
        header.append('"{0}"'.format(str(column["title"]).replace('"', '""')))

    for _, suite in suites.iteritems():
        # Generate data
        suite_name = suite["name"]
        table_lst = list()
        for test in data.keys():
            if data[test]["parent"] in suite_name:
                row_lst = list()
                for column in table["columns"]:
                    try:
                        col_data = str(data[test][column["data"].
                                       split(" ")[1]]).replace('"', '""')
                        if column["data"].split(" ")[1] in ("vat-history",
                                                            "show-run"):
                            col_data = replace(col_data, " |br| ", "",
                                               maxreplace=1)
                            col_data = " |prein| {0} |preout| ".\
                                format(col_data[:-5])
                        row_lst.append('"{0}"'.format(col_data))
                    except KeyError:
                        row_lst.append("No data")
                table_lst.append(row_lst)

        # Write the data to file
        if table_lst:
            file_name = "{0}_{1}{2}".format(table["output-file"], suite_name,
                                            table["output-file-ext"])
            <