/* * 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 #include #include /* for ethernet_header_t */ #include /* for ethernet_arp_header_t */ #include #include /* for srp_hw_interface_class */ #include /* for API error numbers */ #include /* for FIB table and entry creation */ #include /* for FIB table and entry creation */ #include /* for FIB uRPF check */ #include #include #include #include #include /* for mFIB table and entry creation */ /** * @file * @brief IPv4 Forwarding. * * This file contains the source code for IPv4 forwarding. */ void ip4_forward_next_trace (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * frame, vlib_rx_or_tx_t which_adj_index); always_inline uword ip4_lookup_inline (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * frame, int lookup_for_responses_to_locally_received_packets) { ip4_main_t *im = &ip4_main; vlib_combined_counter_main_t *cm = &load_balance_main.lbm_to_counters; u32 n_left_from, n_left_to_next, *from, *to_next; ip_lookup_next_t next; u32 thread_index = vlib_get_thread_index (); from = vlib_frame_vector_args (frame); n_left_from = frame->n_vectors; next = node->cached_next_index; while (n_left_from > 0) { vlib_get_next_frame (vm, node, next, to_next, n_left_to_next); while (n_left_from >= 8 && n_left_to_next >= 4) { vlib_buffer_t *p0, *p1, *p2, *p3; ip4_header_t *ip0, *ip1, *ip2, *ip3; ip_lookup_next_t next0, next1, next2, next3; const load_balance_t *lb0, *lb1, *lb2, *lb3; ip4_fib_mtrie_t *mtrie0, *mtrie1, *mtrie2, *mtrie3; ip4_fib_mtrie_leaf_t leaf0, leaf1, leaf2, leaf3; ip4_address_t *dst_addr0, *dst_addr1, *dst_addr2, *dst_addr3; u32 pi0, fib_index0, lb_index0; u32 pi1, fib_index1, lb_index1; u32 pi2, fib_index2, lb_index2; u32 pi3, fib_index3, lb_index3; flow_hash_config_t flow_hash_config0, flow_hash_config1; flow_hash_config_t flow_hash_config2, flow_hash_config3; u32 hash_c0, hash_c1, hash_c2, hash_c3; const dpo_id_t *dpo0, *dpo1, *dpo2, *dpo3; /* Prefetch next iteration. */ { vlib_buffer_t *p4, *p5, *p6, *p7; p4 = vlib_get_buffer (vm, from[4]); p5 = vlib_get_buffer (vm, from[5]); p6 = vlib_get_buffer (vm, from[6]); p7 = vlib_get_buffer (vm, from[7]); vlib_prefetch_buffer_header (p4, LOAD); vlib_prefetch_buffer_header (p5, LOAD); vlib_prefetch_buffer_header (p6, LOAD); vlib_prefetch_buffer_header (p7, LOAD); CLIB_PREFETCH (p4->data, sizeof (ip0[0]), LOAD); CLIB_PREFETCH (p5->data, sizeof (ip0[0]), LOAD); CLIB_PREFETCH (p6->data, sizeof (ip0[0]), LOAD); CLIB_PREFETCH (p7->data, sizeof (ip0[0]), LOAD); } pi0 = to_next[0] = from[0]; pi1 = to_next[1] = from[1]; pi2 = to_next[2] = from[2]; pi3 = to_next[3] = from[3]; from += 4; to_next += 4; n_left_to_next -= 4; n_left_from -= 4; p0 = vlib_get_buffer (vm, pi0); p1 = vlib_get_buffer (vm, pi1); p2 = vlib_get_buffer (vm, pi2); p3 = vlib_get_buffer (vm, pi3); ip0 = vlib_buffer_get_current (p0); ip1 = vlib_buffer_get_current (p1); ip2 = vlib_buffer_get_current (p2); ip3 = vlib_buffer_get_current (p3); dst_addr0 = &ip0->dst_address; dst_addr1 = &ip1->dst_address; dst_addr2 = &ip2->dst_address; dst_addr3 = &ip3->dst_address; fib_index0 = vec_elt (im->fib_index_by_sw_if_index, vnet_buffer (p0)->sw_if_index[VLIB_RX]); fib_index1 = vec_elt (im->fib_index_by_sw_if_index, vnet_buffer (p1)->sw_if_index[VLIB_RX]); fib_index2 = vec_elt (im->fib_index_by_sw_if_index, vnet_buffer (p2)->sw_if_index[VLIB_RX]); fib_index3 = vec_elt (im->fib_index_by_sw_if_index, vnet_buffer (p3)->sw_if_index[VLIB_RX]); fib_index0 = (vnet_buffer (p0)->sw_if_index[VLIB_TX] == (u32) ~ 0) ? fib_index0 : vnet_buffer (p0)->sw_if_index[VLIB_TX]; fib_index1 = (vnet_buffer (p1)->sw_if_index[VLIB_TX] == (u32) ~ 0) ? fib_index1 : vnet_buffer (p1)->sw_if_index[VLIB_TX]; fib_index2 = (vnet_buffer (p2)->sw_if_index[VLIB_TX] == (u32) ~ 0) ? fib_index2 : vnet_buffer (p2)->sw_if_index[VLIB_TX]; fib_index3 = (vnet_buffer (p3)->sw_if_index[VLIB_TX] == (u32) ~ 0) ? fib_index3 : vnet_buffer (p3)->sw_if_index[VLIB_TX]; if (!lookup_for_responses_to_locally_received_packets) { mtrie0 = &ip4_fib_get (fib_index0)->mtrie; mtrie1 = &ip4_fib_get (fib_index1)->mtrie; mtrie2 = &ip4_fib_get (fib_index2)->mtrie; mtrie3 = &ip4_fib_get (fib_index3)->mtrie; leaf0 = ip4_fib_mtrie_lookup_step_one (mtrie0, dst_addr0); leaf1 = ip4_fib_mtrie_lookup_step_one (mtrie1, dst_addr1); leaf2 = ip4_fib_mtrie_lookup_step_one (mtrie2, dst_addr2); leaf3 = ip4_fib_mtrie_lookup_step_one (mtrie3, dst_addr3); } if (!lookup_for_responses_to_locally_received_packets) { leaf0 = ip4_fib_mtrie_lookup_step (mtrie0, leaf0, dst_addr0, 2); leaf1 = ip4_fib_mtrie_lookup_step (mtrie1, leaf1, dst_addr1, 2); leaf2 = ip4_fib_mtrie_lookup_step (mtrie2, leaf2, dst_addr2, 2); leaf3 = ip4_fib_mtrie_lookup_step (mtrie3, leaf3, dst_addr3, 2); } if (!lookup_for_responses_to_locally_received_packets) { leaf0 = ip4_fib_mtrie_lookup_step (mtrie0, leaf0, dst_addr0, 3); leaf1 = ip4_fib_mtrie_lookup_step (mtrie1, leaf1, dst_addr1, 3); leaf2 = ip4_fib_mtrie_lookup_step (mtrie2, leaf2, dst_addr2, 3); leaf3 = ip4_fib_mtrie_lookup_step (mtrie3, leaf3, dst_addr3, 3); } if (lookup_for_responses_to_locally_received_packets) { lb_index0 = vnet_buffer (p0)->ip.adj_index[VLIB_RX]; lb_index1 = vnet_buffer (p1)->ip.adj_index[VLIB_RX]; lb_index2 = vnet_buffer (p2)->ip.adj_index[VLIB_RX]; lb_index3 = vnet_buffer (p3)->ip.adj_index[VLIB_RX]; } else { lb_index0 = ip4_fib_mtrie_leaf_get_adj_index (leaf0); lb_index1 = ip4_fib_mtrie_leaf_get_adj_index (leaf1); lb_index2 = ip4_fib_mtrie_leaf_get_adj_index (leaf2); lb_index3 = ip4_fib_mtrie_leaf_get_adj_index (leaf3); } ASSERT (lb_index0 && lb_index1 && lb_index2 && lb_index3); lb0 = load_balance_get (lb_index0); lb1 = load_balance_get (lb_index1); lb2 = load_balance_get (lb_index2); lb3 = load_balance_get (lb_index3); ASSERT (lb0->lb_n_buckets > 0); ASSERT (is_pow2 (lb0->lb_n_buckets)); ASSERT (lb1->lb_n_buckets > 0); ASSERT (is_pow2 (lb1->lb_n_buckets)); ASSERT (lb2->lb_n_buckets > 0); ASSERT (is_pow2 (lb2->lb_n_buckets)); ASSERT (lb3->lb_n_buckets > 0); ASSERT (is_pow2 (lb3->lb_n_buckets)); /* Use flow hash to compute multipath adjacency. */ hash_c0 = vnet_buffer (p0)->ip.flow_hash = 0; hash_c1 = vnet_buffer (p1)->ip.flow_hash = 0; hash_c2 = vnet_buffer (p2)->ip.flow_hash = 0; hash_c3 = vnet_buffer (p3)->ip.flow_hash = 0; if (PREDICT_FALSE (lb0->lb_n_buckets > 1)) { flow_hash_config0 = lb0->lb_hash_config; hash_c0 = vnet_buffer (p0)->ip.flow_hash = ip4_compute_flow_hash (ip0, flow_hash_config0); dpo0 = load_balance_get_fwd_bucket (lb0, (hash_c0 & (lb0->lb_n_buckets_minus_1))); } else { dpo0 = load_balance_get_bucket_i (lb0, 0); } if (PREDICT_FALSE (lb1->lb_n_buckets > 1)) { flow_hash_config1 = lb1->lb_hash_config; hash_c1 = vnet_buffer (p1)->ip.flow_hash = ip4_compute_flow_hash (ip1, flow_hash_config1); dpo1 = load_balance_get_fwd_bucket (lb1, (hash_c1 & (lb1->lb_n_buckets_minus_1))); } else { dpo1 = load_balance_get_bucket_i (lb1, 0); } if (PREDICT_FALSE (lb2->lb_n_buckets > 1)) { flow_hash_config2 = lb2->lb_hash_config; hash_c2 = vnet_buffer (p2)->ip.flow_hash = ip4_compute_flow_hash (ip2, flow_hash_config2); dpo2 = load_balance_get_fwd_bucket (lb2, (hash_c2 & (lb2->lb_n_buckets_minus_1))); } else { dpo2 = load_balance_get_bucket_i (lb2, 0); } if (PREDICT_FALSE (lb3->lb_n_buckets > 1)) { flow_hash_config3 = lb3->lb_hash_config; hash_c3 = vnet_buffer (p3)->ip.flow_hash = ip4_compute_flow_hash (ip3, flow_hash_config3); dpo3 = load_balance_get_fwd_bucket (lb3, (hash_c3 & (lb3->lb_n_buckets_minus_1))); } else { dpo3 = load_balance_get_bucket_i (lb3, 0); } next0 = dpo0->dpoi_next_node; vnet_buffer (p0)->ip.adj_index[VLIB_TX] = dpo0->dpoi_index; next1 = dpo1->dpoi_next_node; vnet_buffer (p1)->ip.adj_index[VLIB_TX] = dpo1->dpoi_index; next2 = dpo2->dpoi_next_node; vnet_buffer (p2)->ip.adj_index[VLIB_TX] = dpo2->dpoi_index; next3 = dpo3->dpoi_next_node; vnet_buffer (p3)->ip.adj_index[VLIB_TX] = dpo3->dpoi_index; vlib_increment_combined_counter (cm, thread_index, lb_index0, 1, vlib_buffer_length_in_chain (vm, p0)); vlib_increment_combined_counter (cm, thread_index, lb_index1, 1, vlib_buffer_length_in_chain (vm, p1)); vlib_increment_combined_counter (cm, thread_index, lb_index2, 1, vlib_buffer_length_in_chain (vm, p2)); vlib_increment_combined_counter (cm, thread_index, lb_index3, 1, vlib_buffer_length_in_chain (vm, p3)); vlib_validate_buffer_enqueue_x4 (vm, node, next, to_next, n_left_to_next, pi0, pi1, pi2, pi3, next0, next1, next2, next3); } while (n_left_from > 0 && n_left_to_next > 0) { vlib_buffer_t *p0; ip4_header_t *ip0; ip_lookup_next_t next0; const load_balance_t *lb0; ip4_fib_mtrie_t *mtrie0; ip4_fib_mtrie_leaf_t leaf0; ip4_address_t *dst_addr0; u32 pi0, fib_index0, lbi0; flow_hash_config_t flow_hash_config0; const dpo_id_t *dpo0; u32 hash_c0; pi0 = from[0]; to_next[0] = pi0; p0 = vlib_get_buffer (vm, pi0); ip0 = vlib_buffer_get_current (p0); dst_addr0 = &ip0->dst_address; fib_index0 = vec_elt (im->fib_index_by_sw_if_index, vnet_buffer (p0)->sw_if_index[VLIB_RX]); fib_index0 = (vnet_buffer (p0)->sw_if_index[VLIB_TX] == (u32) ~ 0) ? fib_index0 : vnet_buffer (p0)->sw_if_index[VLIB_TX]; if (!lookup_for_responses_to_locally_received_packets) { mtrie0 = &ip4_fib_get (fib_index0)->mtrie; leaf0 = ip4_fib_mtrie_lookup_step_one (mtrie0, dst_addr0); } if (!lookup_for_responses_to_locally_received_packets) leaf0 = ip4_fib_mtrie_lookup_step (mtrie0, leaf0, dst_addr0, 2); if (!lookup_for_responses_to_locally_received_packets) leaf0 = ip4_fib_mtrie_lookup_step (mtrie0, leaf0, dst_addr0, 3); if (lookup_for_responses_to_locally_received_packets) lbi0 = vnet_buffer (p0)->ip.adj_index[VLIB_RX]; else { /* Handle default route. */ lbi0 = ip4_fib_mtrie_leaf_get_adj_index (leaf0); } ASSERT (lbi0); lb0 = load_balance_get (lbi0); ASSERT (lb0->lb_n_buckets > 0); ASSERT (is_pow2 (lb0->lb_n_buckets)); /* Use flow hash to compute multipath adjacency. */ hash_c0 = vnet_buffer (p0)->ip.flow_hash = 0; if (PREDICT_FALSE (lb0->lb_n_buckets > 1)) { flow_hash_config0 = lb0->lb_hash_config; hash_c0 = vnet_buffer (p0)->ip.flow_hash = ip4_compute_flow_hash (ip0, flow_hash_config0); dpo0 = load_balance_get_fwd_bucket (lb0, (hash_c0 & (lb0->lb_n_buckets_minus_1))); } else { dpo0 = load_balance_get_bucket_i (lb0, 0); } next0 = dpo0->dpoi_next_node; vnet_buffer (p0)->ip.adj_index[VLIB_TX] = dpo0->dpoi_index; vlib_increment_combined_counter (cm, thread_index, lbi0, 1, vlib_buffer_length_in_chain (vm, p0)); from += 1; to_next += 1; n_left_to_next -= 1; n_left_from -= 1; if (PREDICT_FALSE (next0 != next)) { n_left_to_next += 1; vlib_put_next_frame (vm, node, next, n_left_to_next); next = next0; vlib_get_next_frame (vm, node, next, to_next, n_left_to_next); to_next[0] = pi0; to_next += 1; n_left_to_next -= 1; } } vlib_put_next_frame (vm, node, next, n_left_to_next); } if (node->flags & VLIB_NODE_FLAG_TRACE) ip4_forward_next_trace (vm, node, frame, VLIB_TX); return frame->n_vectors; } /** @brief IPv4 lookup node. @node ip4-lookup This is the main IPv4 lookup dispatch node. @param vm vlib_main_t corresponding to the current thread @param node vlib_node_runtime_t @param frame vlib_frame_t whose contents should be dispatched @par Graph mechanics: buffer metadata, next index usage @em Uses: - vnet_buffer(b)->sw_if_index[VLIB_RX] - Indicates the @c sw_if_index value of the interface that the packet was received on. - vnet_buffer(b)->sw_if_index[VLIB_TX] - When the value is @c ~0 then the node performs a longest prefix match (LPM) for the packet destination address in the FIB attached to the receive interface. - Otherwise perform LPM for the packet destination address in the indicated FIB. In this case [VLIB_TX] is a FIB index value (0, 1, ...) and not a VRF id. @em Sets: - vnet_buffer(b)->ip.adj_index[VLIB_TX] - The lookup result adjacency index. Next Index: - Dispatches the packet to the node index found in ip_adjacency_t @c adj->lookup_next_index (where @c adj is the lookup result adjacency). */ static uword ip4_lookup (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * frame) { return ip4_lookup_inline (vm, node, frame, /* lookup_for_responses_to_locally_received_packets */ 0); } static u8 *format_ip4_lookup_trace (u8 * s, va_list * args); VLIB_REGISTER_NODE (ip4_lookup_node) = { .function = ip4_lookup,.name = "ip4-lookup",.vector_size = sizeof (u32),.format_trace = format_ip4_lookup_trace,.n_next_nodes = IP_LOOKUP_N_NEXT,.next_nodes = IP4_LOOKUP_NEXT_NODES,}; VLIB_NODE_FUNCTION_MULTIARCH (ip4_lookup_node, ip4_lookup); always_inline uword ip4_load_balance (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * frame) { vlib_combined_counter_main_t *cm = &load_balance_main.lbm_via_counters; u32 n_left_from, n_left_to_next, *from, *to_next; ip_lookup_next_t next; u32 thread_index = vlib_get_thread_index (); from = vlib_frame_vector_args (frame); n_left_from = frame->n_vectors; next = node->cached_next_index; if (node->flags & VLIB_NODE_FLAG_TRACE) ip4_forward_next_trace (vm, node, frame, VLIB_TX); while (n_left_from > 0) { vlib_get_next_frame (vm, node, next, to_next, n_left_to_next); while (n_left_from >= 4 && n_left_to_next >= 2) { ip_lookup_next_t next0, next1; const load_balance_t *lb0, *lb1; vlib_buffer_t *p0, *p1; u32 pi0, lbi0, hc0, pi1, lbi1, hc1; const ip4_header_t *ip0, *ip1; const dpo_id_t *dpo0, *dpo1; /* Prefetch next iteration. */ { vlib_buffer_t *p2, *p3; p2 = vlib_get_buffer (vm, from[2]); p3 = vlib_get_buffer (vm, from[3]); vlib_prefetch_buffer_header (p2, STORE); vlib_prefetch_buffer_header (p3, STORE); CLIB_PREFETCH (p2->data, sizeof (ip0[0]), STORE); CLIB_PREFETCH (p3->data, sizeof (ip0[0]), STORE); } pi0 = to_next[0] = from[0]; pi1 = to_next[1] = from[1]; from += 2; n_left_from -= 2; to_next += 2; n_left_to_next -= 2; p0 = vlib_get_buffer (vm, pi0); p1 = vlib_get_buffer (vm, pi1); ip0 = vlib_buffer_get_current (p0); ip1 = vlib_buffer_get_current (p1); lbi0 = vnet_buffer (p0)->ip.adj_index[VLIB_TX]; lbi1 = vnet_buffer (p1)->ip.adj_index[VLIB_TX]; lb0 = load_balance_get (lbi0); lb1 = load_balance_get (lbi1); /* * this node is for via FIBs we can re-use the hash value from the * to node if present. * We don't want to use the same hash value at each level in the recursion * graph as that would lead to polarisation */ hc0 = hc1 = 0; if (PREDICT_FALSE (lb0->lb_n_buckets > 1)) { if (PREDICT_TRUE (vnet_buffer (p0)->ip.flow_hash)) { hc0 = vnet_buffer (p0)->ip.flow_hash = vnet_buffer (p0)->ip.flow_hash >> 1; } else { hc0 = vnet_buffer (p0)->ip.flow_hash = ip4_compute_flow_hash (ip0, lb0->lb_hash_config); } dpo0 = load_balance_get_fwd_bucket (lb0, (hc0 & (lb0->lb_n_buckets_minus_1))); } else { dpo0 = load_balance_get_bucket_i (lb0, 0); } if (PREDICT_FALSE (lb1->lb_n_buckets > 1)) { if (PREDICT_TRUE (vnet_buffer (p1)->ip.flow_hash)) { hc1 = vnet_buffer (p1)->ip.flow_hash = vnet_buffer (p1)->ip.flow_hash >> 1; } else { hc1 = vnet_buffer (p1)->ip.flow_hash = ip4_compute_flow_hash (ip1, lb1->lb_hash_config); } dpo1 = load_balance_get_fwd_bucket (lb1, (hc1 & (lb1->lb_n_buckets_minus_1))); } else { dpo1 = load_balance_get_bucket_i (lb1, 0); } next0 = dpo0->dpoi_next_node; next1 = dpo1->dpoi_next_node; vnet_buffer (p0)->ip.adj_index[VLIB_TX] = dpo0->dpoi_index; vnet_buffer (p1)->ip.adj_index[VLIB_TX] = dpo1->dpoi_index; vlib_increment_combined_counter (cm, thread_index, lbi0, 1, vlib_buffer_length_in_chain (vm, p0)); vlib_increment_combined_counter (cm, thread_index, lbi1, 1, vlib_buffer_length_in_chain (vm, p1)); vlib_validate_buffer_enqueue_x2 (vm, node, next, to_next, n_left_to_next, pi0, pi1, next0, next1); } while (n_left_from > 0 && n_left_to_next > 0) { ip_lookup_next_t next0; const load_balance_t *lb0; vlib_buffer_t *p0; u32 pi0, lbi0, hc0; const ip4_header_t *ip0; const dpo_id_t *dpo0; pi0 = from[0]; to_next[0] = pi0; from += 1; to_next += 1; n_left_to_next -= 1; n_left_from -= 1; p0 = vlib_get_buffer (vm, pi0); ip0 = vlib_buffer_get_current (p0); lbi0 = vnet_buffer (p0)->ip.adj_index[VLIB_TX]; lb0 = load_balance_get (lbi0); hc0 = 0; if (PREDICT_FALSE (lb0->lb_n_buckets > 1)) { if (PREDICT_TRUE (vnet_buffer (p0)->ip.flow_hash)) { hc0 = vnet_buffer (p0)->ip.flow_hash = vnet_buffer (p0)->ip.flow_hash >> 1; } else { hc0 = vnet_buffer (p0)->ip.flow_hash = ip4_compute_flow_hash (ip0, lb0->lb_hash_config); } dpo0 = load_balance_get_fwd_bucket (lb0, (hc0 & (lb0->lb_n_buckets_minus_1))); } else { dpo0 = load_balance_get_bucket_i (lb0, 0); } next0 = dpo0->dpoi_next_node; vnet_buffer (p0)->ip.adj_index[VLIB_TX] = dpo0->dpoi_index; vlib_increment_combined_counter (cm, thread_index, lbi0, 1, vlib_buffer_length_in_chain (vm, p0)); vlib_validate_buffer_enqueue_x1 (vm, node, next, to_next, n_left_to_next, pi0, next0); } vlib_put_next_frame (vm, node, next, n_left_to_next); } return frame->n_vectors; } VLIB_REGISTER_NODE (ip4_load_balance_node) = { .function = ip4_load_balance,.name = "ip4-load-balance",.vector_size = sizeof (u32),.sibling_of = "ip4-lookup",.format_trace = format_ip4_lookup_trace,}; VLIB_NODE_FUNCTION_MULTIARCH (ip4_load_balance_node, ip4_load_balance); /* get first interface address */ ip4_address_t * ip4_interface_first_address (ip4_main_t * im, u32 sw_if_index, ip_interface_address_t ** result_ia) { ip_lookup_main_t *lm = &im->lookup_main; ip_interface_address_t *ia = 0; ip4_address_t *result = 0; /* *INDENT-OFF* */ foreach_ip_interface_address (lm, ia, sw_if_index, 1 /* honor unnumbered */ , ({ ip4_address_t * a = ip_interface_address_get_address (lm, ia); result = a; break; })); /* *INDENT-OFF* */ if (result_ia) *result_ia = result ? ia : 0; return result; } static void ip4_add_interface_routes (u32 sw_if_index, ip4_main_t * im, u32 fib_index, ip_interface_address_t * a) { ip_lookup_main_t *lm = &im->lookup_main; ip4_address_t *address = ip_interface_address_get_address (lm, a); fib_prefix_t pfx = { .fp_len = a->address_length, .fp_proto = FIB_PROTOCOL_IP4, .fp_addr.ip4 = *address, }; if (pfx.fp_len <= 30) { /* a /30 or shorter - add a glean for the network address */ fib_table_entry_update_one_path (fib_index, &pfx, FIB_SOURCE_INTERFACE, (FIB_ENTRY_FLAG_CONNECTED | FIB_ENTRY_FLAG_ATTACHED), FIB_PROTOCOL_IP4, /* No next-hop address */ NULL, sw_if_index, // invalid FIB index ~0, 1, // no out-label stack NULL, FIB_ROUTE_PATH_FLAG_NONE); /* Add the two broadcast addresses as drop */ fib_prefix_t net_pfx = { .fp_len = 32, .fp_proto = FIB_PROTOCOL_IP4, .fp_addr.ip4.as_u32 = address->as_u32 & im->fib_masks[pfx.fp_len], }; if (net_pfx.fp_addr.ip4.as_u32 != pfx.fp_addr.ip4.as_u32) fib_table_entry_special_add(fib_index, &net_pfx, FIB_SOURCE_INTERFACE, (FIB_ENTRY_FLAG_DROP | FIB_ENTRY_FLAG_LOOSE_URPF_EXEMPT)); net_pfx.fp_addr.ip4.as_u32 |= ~im->fib_masks[pfx.fp_len]; if (net_pfx.fp_addr.ip4.as_u32 != pfx.fp_addr.ip4.as_u32) fib_table_entry_special_add(fib_index, &net_pfx, FIB_SOURCE_INTERFACE, (FIB_ENTRY_FLAG_DROP | FIB_ENTRY_FLAG_LOOSE_URPF_EXEMPT)); } else if (pfx.fp_len == 31) { u32 mask = clib_host_to_net_u32(1); fib_prefix_t net_pfx = pfx; net_pfx.fp_len = 32; net_pfx.fp_addr.ip4.as_u32 ^= mask; /* a /31 - add the other end as an attached host */ fib_table_entry_update_one_path (fib_index, &net_pfx, FIB_SOURCE_INTERFACE, (FIB_ENTRY_FLAG_ATTACHED), FIB_PROTOCOL_IP4, &net_pfx.fp_addr, sw_if_index, // invalid FIB index ~0, 1, NULL, FIB_ROUTE_PATH_FLAG_NONE); } pfx.fp_len = 32; if (sw_if_index < vec_len (lm->classify_table_index_by_sw_if_index)) { u32 classify_table_index = lm->classify_table_index_by_sw_if_index[sw_if_index]; if (classify_table_index != (u32) ~ 0) { dpo_id_t dpo = DPO_INVALID; dpo_set (&dpo, DPO_CLASSIFY, DPO_PROTO_IP4, classify_dpo_create (DPO_PROTO_IP4, classify_table_index)); fib_table_entry_special_dpo_add (fib_index, &pfx, FIB_SOURCE_CLASSIFY, FIB_ENTRY_FLAG_NONE, &dpo); dpo_reset (&dpo); } } fib_table_entry_update_one_path (fib_index, &pfx, FIB_SOURCE_INTERFACE, (FIB_ENTRY_FLAG_CONNECTED | FIB_ENTRY_FLAG_LOCAL), FIB_PROTOCOL_IP4, &pfx.fp_addr, sw_if_index, // invalid FIB index ~0, 1, NULL, FIB_ROUTE_PATH_FLAG_NONE); } static void ip4_del_interface_routes (ip4_main_t * im, u32 fib_index, ip4_address_t * address, u32 address_length) { fib_prefix_t pfx = { .fp_len = address_length, .fp_proto = FIB_PROTOCOL_IP4, .fp_addr.ip4 = *address, }; if (pfx.fp_len <= 30) { fib_prefix_t net_pfx = { .fp_len = 32, .fp_proto = FIB_PROTOCOL_IP4, .fp_addr.ip4.as_u32 = address->as_u32 & im->fib_masks[pfx.fp_len], }; if (net_pfx.fp_addr.ip4.as_u32 != pfx.fp_addr.ip4.as_u32) fib_table_entry_special_remove(fib_index, &net_pfx, FIB_SOURCE_INTERFACE); net_pfx.fp_addr.ip4.as_u32 |= ~im->fib_masks[pfx.fp_len]; if (net_pfx.fp_addr.ip4.as_u32 != pfx.fp_addr.ip4.as_u32) fib_table_entry_special_remove(fib_index, &net_pfx, FIB_SOURCE_INTERFACE); fib_table_entry_delete (fib_index, &pfx, FIB_SOURCE_INTERFACE); } else if (pfx.fp_len == 31) { u32 mask = clib_host_to_net_u32(1); fib_prefix_t net_pfx = pfx; net_pfx.fp_len = 32; net_pfx.fp_addr.ip4.as_u32 ^= mask; fib_table_entry_delete (fib_index, &net_pfx, FIB_SOURCE_INTERFACE); } pfx.fp_len = 32
#!/usr/bin/env python
""" Classifier-based L2 ACL Test Case HLD:
"""

import unittest
import random
import binascii
import socket


from scapy.packet import Raw
from scapy.data import ETH_P_IP
from scapy.layers.l2 import Ether
from scapy.layers.inet import IP, TCP, UDP, ICMP
from scapy.layers.inet6 import IPv6, ICMPv6EchoRequest
from scapy.layers.inet6 import IPv6ExtHdrFragment
from framework import VppTestCase, VppTestRunner
from util import Host, ppp


class TestClassifyAcl(VppTestCase):
    """ Classifier-based L2 input and output ACL Test Case """

    # traffic types
    IP = 0
    ICMP = 1

    # IP version
    IPRANDOM = -1
    IPV4 = 0
    IPV6 = 1

    # rule types
    DENY = 0
    PERMIT = 1

    # supported protocols
    proto = [[6, 17], [1, 58]]
    proto_map = {1: 'ICMP', 58: 'ICMPv6EchoRequest', 6: 'TCP', 17: 'UDP'}
    ICMPv4 = 0
    ICMPv6 = 1
    TCP = 0
    UDP = 1
    PROTO_ALL = 0

    # port ranges
    PORTS_ALL = -1
    PORTS_RANGE = 0
    PORTS_RANGE_2 = 1
    udp_sport_from = 10
    udp_sport_to = udp_sport_from + 5
    udp_dport_from = 20000
    udp_dport_to = udp_dport_from + 5000
    tcp_sport_from = 30
    tcp_sport_to = tcp_sport_from + 5
    tcp_dport_from = 40000
    tcp_dport_to = tcp_dport_from + 5000

    udp_sport_from_2 = 90
    udp_sport_to_2 = udp_sport_from_2 + 5
    udp_dport_from_2 = 30000
    udp_dport_to_2 = udp_dport_from_2 + 5000
    tcp_sport_from_2 = 130
    tcp_sport_to_2 = tcp_sport_from_2 + 5
    tcp_dport_from_2 = 20000
    tcp_dport_to_2 = tcp_dport_from_2 + 5000

    icmp4_type = 8  # echo request
    icmp4_code = 3
    icmp6_type = 128  # echo request
    icmp6_code = 3

    icmp4_type_2 = 8
    icmp4_code_from_2 = 5
    icmp4_code_to_2 = 20
    icmp6_type_2 = 128
    icmp6_code_from_2 = 8
    icmp6_code_to_2 = 42

    # Test variables
    bd_id = 1

    @classmethod
    def setUpClass(cls):
        """
        Perform standard class setup (defined by class method setUpClass in
        class VppTestCase) before running the test case, set test case related
        variables and configure VPP.
        """
        super(TestClassifyAcl, cls).setUpClass()

        try:
            # Create 2 pg interfaces
            cls.create_pg_interfaces(range(2))

            # Packet flows mapping pg0 -> pg1, pg2 etc.
            cls.flows = dict()
            cls.flows[cls.pg0] = [cls.pg1]

            # Packet sizes
            cls.pg_if_packet_sizes = [64, 512, 1518, 9018]

            # Create BD with MAC learning and unknown unicast flooding disabled
            # and put interfaces to this BD
            cls.vapi.bridge_domain_add_del(bd_id=cls.bd_id, uu_flood=1,
                                           learn=1)
            for pg_if in cls.pg_interfaces:
                cls.vapi.sw_interface_set_l2_bridge(
                    rx_sw_if_index=pg_if.sw_if_index, bd_id=cls.bd_id)

            # Set up all interfaces
            for i in cls.pg_interfaces:
                i.admin_up()

            # Mapping between packet-generator index and lists of test hosts
            cls.hosts_by_pg_idx = dict()
            for pg_if in cls.pg_interfaces:
                cls.hosts_by_pg_idx[pg_if.sw_if_index] = []

            # Create list of deleted hosts
            cls.deleted_hosts_by_pg_idx = dict()
            for pg_if in cls.pg_interfaces:
                cls.deleted_hosts_by_pg_idx[pg_if.sw_if_index] = []

            # warm-up the mac address tables
            # self.warmup_test()

            # Holder of the active classify table key
            cls.acl_active_table = ''

        except Exception:
            super(TestClassifyAcl, cls).tearDownClass()
            raise

    @classmethod
    def tearDownClass(cls):
        super(TestClassifyAcl, cls).tearDownClass()

    def setUp(self):
        super(TestClassifyAcl, self).setUp()

        self.acl_tbl_idx = {}
        self.reset_packet_infos()

    def tearDown(self):
        """
        Show various debug prints after each test.
        """
        if not self.vpp_dead:
            if self.acl_active_table == 'mac_inout':
                self.output_acl_set_interface(
                    self.pg1, self.acl_tbl_idx.get(self.acl_active_table), 0)
                self.input_acl_set_interface(
                    self.pg0, self.acl_tbl_idx.get(self.acl_active_table), 0)
                self.acl_active_table = ''
            elif self.acl_active_table == 'mac_out':
                self.output_acl_set_interface(
                    self.pg1, self.acl_tbl_idx.get(self.acl_active_table), 0)
                self.acl_active_table = ''
            elif self.acl_active_table == 'mac_in':
                self.input_acl_set_interface(
                    self.pg0, self.acl_tbl_idx.get(self.acl_active_table), 0)
                self.acl_active_table = ''

        super(TestClassifyAcl, self).tearDown()

    def show_commands_at_teardown(self):
        self.logger.info(self.vapi.ppcli("show inacl type l2"))
        self.logger.info(self.vapi.ppcli("show outacl type l2"))
        self.logger.info(self.vapi.ppcli("show classify tables verbose"))
        self.logger.info(self.vapi.ppcli("show bridge-domain %s detail"
                                         % self.bd_id))

    @staticmethod
    def build_mac_mask(dst_mac='', src_mac='', ether_type=''):
        """Build MAC ACL mask data with hexstring format

        :param str dst_mac: source MAC address <0-ffffffffffff>
        :param str src_mac: destination MAC address <0-ffffffffffff>
        :param str ether_type: ethernet type <0-ffff>
        """

        return ('{!s:0>12}{!s:0>12}{!s:0>4}'.format(
            dst_mac, src_mac, ether_type)).rstrip('0')

    @staticmethod
    def build_mac_match(dst_mac='', src_mac='', ether_type=''):
        """Build MAC ACL match data with hexstring format

        :param str dst_mac: source MAC address <x:x:x:x:x:x>
        :param str src_mac: destination MAC address <x:x:x:x:x:x>
        :param str ether_type: ethernet type <0-ffff>
        """
        if dst_mac:
            dst_mac = dst_mac.replace(':', '')
        if src_mac:
            src_mac = src_mac.replace(':', '')

        return ('{!s:0>12}{!s:0>12}{!s:0>4}'.format(
            dst_mac, src_mac, ether_type)).rstrip('0')

    def create_classify_table(self, key, mask, data_offset=0, is_add=1):
        """Create Classify Table

        :param str key: key for classify table (ex, ACL name).
        :param str mask: mask value for interested traffic.
        :param int match_n_vectors:
        :param int is_add: option to configure classify table.
            - create(1) or delete(0)
        """
        r = self.vapi.classify_add_del_table(
            is_add,
            binascii.unhexlify(mask),
            match_n_vectors=(len(mask) - 1) // 32 + 1,
            miss_next_index=0,
            current_data_flag=1,
            current_data_offset=data_offset)
        self.assertIsNotNone(r, 'No response msg for add_del_table')
        self.acl_tbl_idx[key] = r.new_table_index

    def create_classify_session(self, intf, table_index, match,
                                hit_next_index=0xffffffff, is_add=1):
        """Create Classify Session

        :param VppInterface intf: Interface to apply classify session.
        :param int table_index: table index to identify classify table.
        :param str match: matched value for interested traffic.
        :param int pbr_action: enable/disable PBR feature.
        :param int vrfid: VRF id.
        :param int is_add: option to configure classify session.
            - create(1) or delete(0)
        """
        r = self.vapi.classify_add_del_session(
            is_add,
            table_index,
            binascii.unhexlify(match),
            hit_next_index=hit_next_index)
        self.assertIsNotNone(r, 'No response msg for add_del_session')

    def input_acl_set_interface(self, intf, table_index, is_add=1):
        """Configure Input ACL interface

        :param VppInterface intf: Interface to apply Input ACL feature.
        :param int table_index: table index to identify classify table.
        :param int is_add: option to configure classify session.
            - enable(1) or disable(0)
        """
        r = self.vapi.input_acl_set_interface(
            is_add,
            intf.sw_if_index,
            l2_table_index=table_index)
        self.assertIsNotNone(r, 'No response msg for acl_set_interface')

    def output_acl_set_interface(self, intf, table_index, is_add=1):
        """Configure Output ACL interface

        :param VppInterface intf: Interface to apply Output ACL feature.
        :param int table_index: table index to identify classify table.
        :param int is_add: option to configure classify session.
            - enable(1) or disable(0)
        """
        r = self.vapi.output_acl_set_interface(
            is_add,
            intf.sw_if_index,
            l2_table_index=table_index)
        self.assertIsNotNone(r, 'No response msg for acl_set_interface')

    def create_hosts(self, count, start=0):
        """
        Create required number of host MAC addresses and distribute them among
        interfaces. Create host IPv4 address for every host MAC address.

        :param int count: Number of hosts to create MAC/IPv4 addresses for.
        :param int start: Number to start numbering from.
        """
        n_int = len(self.pg_interfaces)
        macs_per_if = count / n_int
        i = -1
        for pg_if in self.pg_interfaces:
            i += 1
            start_nr = macs_per_if * i + start
            end_nr = count + start if i == (n_int - 1) \
                else macs_per_if * (i + 1) + start
            hosts = self.hosts_by_pg_idx[pg_if.sw_if_index]
            for j in range(start_nr, end_nr):
                host = Host(
                    "00:00:00:ff:%02x:%02x" % (pg_if.sw_if_index, j),
                    "172.17.1%02x.%u" % (pg_if.sw_if_index, j),
                    "2017:dead:%02x::%u" % (pg_if.sw_if_index, j))
                hosts.append(host)

    def create_upper_layer(self, packet_index, proto, ports=0):
        p = self.proto_map[proto]
        if p == 'UDP':
            if ports == 0:
                return UDP(sport=random.randint(self.udp_sport_from,
                                                self.udp_sport_to),
                           dport=random.randint(self.udp_dport_from,
                                                self.udp_dport_to))
            else:
                return UDP(sport=ports, dport=ports)
        elif p == 'TCP':
            if ports == 0:
                return TCP(sport=random.randint(self.tcp_sport_from,
                                                self.tcp_sport_to),
                           dport=random.randint(self.tcp_dport_from,
                                                self.tcp_dport_to))
            else:
                return TCP(sport=ports, dport=ports)
        return ''

    def create_stream(self, src_if, packet_sizes, traffic_type=0, ipv6=0,
                      proto=-1, ports=0, fragments=False,
                      pkt_raw=True, etype=-1):
        """
        Create input packet stream for defined interface using hosts or
        deleted_hosts list.

        :param object src_if: Interface to create packet stream for.
        :param list packet_sizes: List of required packet sizes.
        :param traffic_type: 1: ICMP packet, 2: IPv6 with EH, 0: otherwise.
        :return: Stream of packets.
        """
        pkts = []
        if self.flows.__contains__(src_if):
            src_hosts = self.hosts_by_pg_idx[src_if.sw_if_index]
            for dst_if in self.flows[src_if]:
                dst_hosts = self.hosts_by_pg_idx[dst_if.sw_if_index]
                n_int = len(dst_hosts) * len(src_hosts)
                for i in range(0, n_int):
                    dst_host = dst_hosts[i / len(src_hosts)]
                    src_host = src_hosts[i % len(src_hosts)]
                    pkt_info = self.create_packet_info(src_if, dst_if)
                    if ipv6 == 1:
                        pkt_info.ip = 1
                    elif ipv6 == 0:
                        pkt_info.ip = 0
                    else:
                        pkt_info.ip = random.choice([0, 1])
                    if proto == -1:
                        pkt_info.proto = random.choice(self.proto[self.IP])
                    else:
                        pkt_info.proto = proto
                    payload = self.info_to_payload(pkt_info)
                    p = Ether(dst=dst_host.mac, src=src_host.mac)
                    if etype > 0:
                        p = Ether(dst=dst_host.mac,
                                  src=src_host.mac,
                                  type=etype)
                    if pkt_info.ip:
                        p /= IPv6(dst=dst_host.ip6, src=src_host.ip6)
                        if fragments:
                            p /= IPv6ExtHdrFragment(offset=64, m=1)
                    else:
                        if fragments:
                            p /= IP(src=src_host.ip4, dst=dst_host.ip4,
                                    flags=1, frag=64)
                        else:
                            p /= IP(src=src_host.ip4, dst=dst_host.ip4)
                    if traffic_type == self.ICMP:
                        if pkt_info.ip:
                            p /= ICMPv6EchoRequest(type=self.icmp6_type,
                                                   code=self.icmp6_code)
                        else:
                            p /= ICMP(type=self.icmp4_type,
                                      code=self.icmp4_code)
                    else:
                        p /= self.create_upper_layer(i, pkt_info.proto, ports)
                    if pkt_raw:
                        p /= Raw(payload)
                        pkt_info.data = p.copy()
                    if pkt_raw:
                        size = random.choice(packet_sizes)
                        self.extend_packet(p, size)
                    pkts.append(p)
        return pkts

    def verify_capture(self, pg_if, capture,
                       traffic_type=0, ip_type=0, etype=-1):
        """
        Verify captured input packet stream for defined interface.

        :param object pg_if: Interface to verify captured packet stream for.
        :param list capture: Captured packet stream.
        :param traffic_type: 1: ICMP packet, 2: IPv6 with EH, 0: otherwise.
        """
        last_info = dict()
        for i in self.pg_interfaces:
            last_info[i.sw_if_index] = None
        dst_sw_if_index = pg_if.sw_if_index
        for packet in capture:
            if etype > 0:
                if packet[Ether].type != etype:
                    self.logger.error(ppp("Unexpected ethertype in packet:",
                                          packet))
                else:
                    continue
            try:
                # Raw data for ICMPv6 are stored in ICMPv6EchoRequest.data
                if traffic_type == self.ICMP and ip_type == self.IPV6:
                    payload_info = self.payload_to_info(
                        packet[ICMPv6EchoRequest].data)
                    payload = packet[ICMPv6EchoRequest]
                else:
                    payload_info = self.payload_to_info(packet[Raw])
                    payload = packet[self.proto_map[payload_info.proto]]
            except:
                self.logger.error(ppp("Unexpected or invalid packet "
                                      "(outside network):", packet))
                raise

            if ip_type != 0:
                self.assertEqual(payload_info.ip, ip_type)
            if traffic_type == self.ICMP:
                try:
                    if payload_info.ip == 0:
                        self.assertEqual(payload.type, self.icmp4_type)
                        self.assertEqual(payload.code, self.icmp4_code)
                    else:
                        self.assertEqual(payload.type, self.icmp6_type)
                        self.assertEqual(payload.code, self.icmp6_code)
                except:
                    self.logger.error(ppp("Unexpected or invalid packet "
                                          "(outside network):", packet))
                    raise
            else:
                try:
                    ip_version = IPv6 if payload_info.ip == 1 else IP

                    ip = packet[ip_version]
                    packet_index = payload_info.index

                    self.assertEqual(payload_info.dst, dst_sw_if_index)
                    self.logger.debug("Got packet on port %s: src=%u (id=%u)" %
                                      (pg_if.name, payload_info.src,
                                       packet_index))
                    next_info = self.get_next_packet_info_for_interface2(
                        payload_info.src, dst_sw_if_index,
                        last_info[payload_info.src])
                    last_info[payload_info.src] = next_info
                    self.assertTrue(next_info is not None)
                    self.assertEqual(packet_index, next_info.index