/* * 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 <vnet/ip/ip.h> #include <vnet/dpo/lookup_dpo.h> #include <vnet/dpo/load_balance_map.h> #include <vnet/mpls/mpls_lookup.h> #include <vnet/fib/fib_table.h> #include <vnet/fib/ip4_fib.h> #include <vnet/fib/ip6_fib.h> #include <vnet/fib/mpls_fib.h> #include <vnet/mfib/mfib_table.h> #include <vnet/mfib/ip4_mfib.h> #include <vnet/mfib/ip6_mfib.h> static const char *const lookup_input_names[] = LOOKUP_INPUTS; static const char *const lookup_cast_names[] = LOOKUP_CASTS; /** * If a packet encounters a lookup DPO more than the many times * then we assume there is a loop in the forward graph and drop the packet */ #define MAX_LUKPS_PER_PACKET 4 /** * @brief Enumeration of the lookup subtypes */ typedef enum lookup_sub_type_t_ { LOOKUP_SUB_TYPE_SRC, LOOKUP_SUB_TYPE_DST, LOOKUP_SUB_TYPE_DST_MCAST, LOOKUP_SUB_TYPE_DST_TABLE_FROM_INTERFACE, } lookup_sub_type_t; #define LOOKUP_SUB_TYPE_NUM (LOOKUP_SUB_TYPE_DST_TABLE_FROM_INTERFACE+1) #define FOR_EACH_LOOKUP_SUB_TYPE(_st) \ for (_st = LOOKUP_SUB_TYPE_IP4_SRC; _st < LOOKUP_SUB_TYPE_NUM; _st++) /** * @brief pool of all MPLS Label DPOs */ lookup_dpo_t *lookup_dpo_pool; /** * @brief An array of registered DPO type values for the sub-types */ static dpo_type_t lookup_dpo_sub_types[LOOKUP_SUB_TYPE_NUM]; static lookup_dpo_t * lookup_dpo_alloc (void) { lookup_dpo_t *lkd; vlib_main_t *vm; u8 did_barrier_sync; dpo_pool_barrier_sync (vm, lookup_dpo_pool, did_barrier_sync); pool_get_aligned(lookup_dpo_pool, lkd, CLIB_CACHE_LINE_BYTES); dpo_pool_barrier_release (vm, did_barrier_sync); return (lkd); } static index_t lookup_dpo_get_index (lookup_dpo_t *lkd) { return (lkd - lookup_dpo_pool); } static void lookup_dpo_add_or_lock_i (fib_node_index_t fib_index, dpo_proto_t proto, lookup_cast_t cast, lookup_input_t input, lookup_table_t table_config, dpo_id_t *dpo) { lookup_dpo_t *lkd; dpo_type_t type; lkd = lookup_dpo_alloc(); lkd->lkd_fib_index = fib_index; lkd->lkd_proto = proto; lkd->lkd_input = input; lkd->lkd_table = table_config; lkd->lkd_cast = cast; /* * use the input type to select the lookup sub-type */ type = 0; switch (input) { case LOOKUP_INPUT_SRC_ADDR: type = lookup_dpo_sub_types[LOOKUP_SUB_TYPE_SRC]; break; case LOOKUP_INPUT_DST_ADDR: switch (table_config) { case LOOKUP_TABLE_FROM_INPUT_INTERFACE: type = lookup_dpo_sub_types[LOOKUP_SUB_TYPE_DST_TABLE_FROM_INTERFACE]; break; case LOOKUP_TABLE_FROM_CONFIG: type = lookup_dpo_sub_types[LOOKUP_SUB_TYPE_DST]; break; } if (LOOKUP_MULTICAST == cast) { type = lookup_dpo_sub_types[LOOKUP_SUB_TYPE_DST_MCAST]; } } if (0 == type) { dpo_reset(dpo); } else { dpo_set(dpo, type, proto, lookup_dpo_get_index(lkd)); } } void lookup_dpo_add_or_lock_w_fib_index (fib_node_index_t fib_index, dpo_proto_t proto, lookup_cast_t cast, lookup_input_t input, lookup_table_t table_config, dpo_id_t *dpo) { if (LOOKUP_TABLE_FROM_CONFIG == table_config) { if (LOOKUP_UNICAST == cast) { fib_table_lock(fib_index, dpo_proto_to_fib(proto), FIB_SOURCE_RR); } else { mfib_table_lock(fib_index, dpo_proto_to_fib(proto), MFIB_SOURCE_RR); } } lookup_dpo_add_or_lock_i(fib_index, proto, cast, input, table_config, dpo); } void lookup_dpo_add_or_lock_w_table_id (u32 table_id, dpo_proto_t proto, lookup_cast_t cast, lookup_input_t input, lookup_table_t table_config, dpo_id_t *dpo) { fib_node_index_t fib_index = FIB_NODE_INDEX_INVALID; if (LOOKUP_TABLE_FROM_CONFIG == table_config) { if (LOOKUP_UNICAST == cast) { fib_index = fib_table_find_or_create_and_lock(dpo_proto_to_fib(proto), table_id, FIB_SOURCE_RR); } else { fib_index = mfib_table_find_or_create_and_lock(dpo_proto_to_fib(proto), table_id, MFIB_SOURCE_RR); } } ASSERT(FIB_NODE_INDEX_INVALID != fib_index); lookup_dpo_add_or_lock_i(fib_index, proto, cast, input, table_config, dpo); } u8* format_lookup_dpo (u8 *s, va_list *args) { index_t index = va_arg (*args, index_t); lookup_dpo_t *lkd; lkd = lookup_dpo_get(index); if (LOOKUP_TABLE_FROM_INPUT_INTERFACE == lkd->lkd_table) { s = format(s, "%s,%s lookup in interface's %U table", lookup_input_names[lkd->lkd_input], lookup_cast_names[lkd->lkd_cast], format_dpo_proto, lkd->lkd_proto); } else { if (LOOKUP_UNICAST == lkd->lkd_cast) { s = format(s, "%s,%s lookup in %U", lookup_input_names[lkd->lkd_input], lookup_cast_names[lkd->lkd_cast], format_fib_table_name, lkd->lkd_fib_index, dpo_proto_to_fib(lkd->lkd_proto)); } else { s = format(s, "%s,%s lookup in %U", lookup_input_names[lkd->lkd_input], lookup_cast_names[lkd->lkd_cast], format_mfib_table_name, lkd->lkd_fib_index, dpo_proto_to_fib(lkd->lkd_proto)); } } return (s); } static void lookup_dpo_lock (dpo_id_t *dpo) { lookup_dpo_t *lkd; lkd = lookup_dpo_get(dpo->dpoi_index); lkd->lkd_locks++; } static void lookup_dpo_unlock (dpo_id_t *dpo) { lookup_dpo_t *lkd; lkd = lookup_dpo_get(dpo->dpoi_index); lkd->lkd_locks--; if (0 == lkd->lkd_locks) { if (LOOKUP_TABLE_FROM_CONFIG == lkd->lkd_table) { if (LOOKUP_UNICAST == lkd->lkd_cast) { fib_table_unlock(lkd->lkd_fib_index, dpo_proto_to_fib(lkd->lkd_proto), FIB_SOURCE_RR); } else { mfib_table_unlock(lkd->lkd_fib_index, dpo_proto_to_fib(lkd->lkd_proto), MFIB_SOURCE_RR); } } pool_put(lookup_dpo_pool, lkd); } } always_inline void ip4_src_fib_lookup_one (u32 src_fib_index0, const ip4_address_t * addr0, u32 * src_adj_index0) { ip4_fib_mtrie_leaf_t leaf0; ip4_fib_mtrie_t * mtrie0; mtrie0 = &ip4_fib_get (src_fib_index0)->mtrie; leaf0 = ip4_fib_mtrie_lookup_step_one (mtrie0, addr0); leaf0 = ip4_fib_mtrie_lookup_step (mtrie0, leaf0, addr0, 2); leaf0 = ip4_fib_mtrie_lookup_step (mtrie0, leaf0, addr0, 3); src_adj_index0[0] = ip4_fib_mtrie_leaf_get_adj_index (leaf0); } always_inline void ip4_src_fib_lookup_two (u32 src_fib_index0, u32 src_fib_index1, const ip4_address_t * addr0, const ip4_address_t * addr1, u32 * src_adj_index0, u32 * src_adj_index1) { ip4_fib_mtrie_leaf_t leaf0, leaf1; ip4_fib_mtrie_t * mtrie0, * mtrie1; mtrie0 = &ip4_fib_get (src_fib_index0)->mtrie; mtrie1 = &ip4_fib_get (src_fib_index1)->mtrie; leaf0 = ip4_fib_mtrie_lookup_step_one (mtrie0, addr0); leaf1 = ip4_fib_mtrie_lookup_step_one (mtrie1, addr1); leaf0 = ip4_fib_mtrie_lookup_step (mtrie0, leaf0, addr0, 2); leaf1 = ip4_fib_mtrie_lookup_step (mtrie1, leaf1, addr1, 2); leaf0 = ip4_fib_mtrie_lookup_step (mtrie0, leaf0, addr0, 3); leaf1 = ip4_fib_mtrie_lookup_step (mtrie1, leaf1, addr1, 3); src_adj_index0[0] = ip4_fib_mtrie_leaf_get_adj_index (leaf0); src_adj_index1[0] = ip4_fib_mtrie_leaf_get_adj_index (leaf1); } /** * @brief Lookup trace data */ typedef struct lookup_trace_t_ { union { ip46_address_t addr; mpls_unicast_header_t hdr; }; fib_node_index_t fib_index; index_t lbi; } lookup_trace_t; always_inline uword lookup_dpo_ip4_inline (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * from_frame, int input_src_addr, int table_from_interface) { u32 n_left_from, next_index, * from, * to_next; u32 thread_index = vlib_get_thread_index(); vlib_combined_counter_main_t * cm = &load_balance_main.lbm_to_counters; from = vlib_frame_vector_args (from_frame); n_left_from = from_frame->n_vectors; next_index = node->cached_next_index; 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 >= 4 && n_left_to_next > 2) { u32 bi0, lkdi0, lbi0, fib_index0, next0, hash_c0; flow_hash_config_t flow_hash_config0; const ip4_address_t *input_addr0; const load_balance_t *lb0; const lookup_dpo_t * lkd0; const ip4_header_t * ip0; const dpo_id_t *dpo0; vlib_buffer_t * b0; u32 bi1, lkdi1, lbi1, fib_index1, next1, hash_c1; flow_hash_config_t flow_hash_config1; const ip4_address_t *input_addr1; const load_balance_t *lb1; const lookup_dpo_t * lkd1; const ip4_header_t * ip1; const dpo_id_t *dpo1; vlib_buffer_t * b1; /* 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, LOAD); vlib_prefetch_buffer_header (p3, LOAD); CLIB_PREFETCH (p2->data, CLIB_CACHE_LINE_BYTES, STORE); CLIB_PREFETCH (p3->data, CLIB_CACHE_LINE_BYTES, STORE); } bi0 = from[0]; to_next[0] = bi0; bi1 = from[1]; to_next[1] = bi1; from += 2; to_next += 2; n_left_from -= 2; n_left_to_next -= 2; b0 = vlib_get_buffer (vm, bi0); ip0 = vlib_buffer_get_current (b0); b1 = vlib_get_buffer (vm, bi1); ip1 = vlib_buffer_get_current (b1); /* dst lookup was done by ip4 lookup */ lkdi0 = vnet_buffer(b0)->ip.adj_index[VLIB_TX]; lkdi1 = vnet_buffer(b1)->ip.adj_index[VLIB_TX]; lkd0 = lookup_dpo_get(lkdi0); lkd1 = lookup_dpo_get(lkdi1); /* * choose between a lookup using the fib index in the DPO * or getting the FIB index from the interface. */ if (table_from_interface) { fib_index0 = ip4_fib_table_get_index_for_sw_if_index( vnet_buffer(b0)->sw_if_index[VLIB_RX]); fib_index1 = ip4_fib_table_get_index_for_sw_if_index( vnet_buffer(b1)->sw_if_index[VLIB_RX]); } else { fib_index0 = lkd0->lkd_fib_index; fib_index1 = lkd1->lkd_fib_index; } /* * choose between a source or destination address lookup in the table */ if (input_src_addr) { input_addr0 = &ip0->src_address; input_addr1 = &ip1->src_address; } else { input_addr0 = &ip0->dst_address; input_addr1 = &ip1->dst_address; } /* do lookup */ ip4_src_fib_lookup_two (fib_index0, fib_index1, input_addr0, input_addr1, &lbi0, &lbi1); lb0 = load_balance_get(lbi0); lb1 = load_balance_get(lbi1); vnet_buffer(b0)->sw_if_index[VLIB_TX] = fib_index0; vnet_buffer(b1)->sw_if_index[VLIB_TX] = fib_index1; /* Use flow hash to compute multipath adjacency. */ hash_c0 = vnet_buffer (b0)->ip.flow_hash = 0; hash_c1 = vnet_buffer (b1)->ip.flow_hash = 0; if (PREDICT_FALSE (lb0->lb_n_buckets > 1)) { flow_hash_config0 = lb0->lb_hash_config; hash_c0 = vnet_buffer (b0)->ip.flow_hash = ip4_compute_flow_hash (ip0, flow_hash_config0); } if (PREDICT_FALSE (lb1->lb_n_buckets > 1)) { flow_hash_config1 = lb1->lb_hash_config; hash_c1 = vnet_buffer (b1)->ip.flow_hash = ip4_compute_flow_hash (ip1, flow_hash_config1); } dpo0 = load_balance_get_bucket_i(lb0, (hash_c0 & (lb0->lb_n_buckets_minus_1))); dpo1 = load_balance_get_bucket_i(lb1, (hash_c1 & (lb1->lb_n_buckets_minus_1))); next0 = dpo0->dpoi_next_node; next1 = dpo1->dpoi_next_node; vnet_buffer(b0)->ip.adj_index[VLIB_TX] = dpo0->dpoi_index; vnet_buffer(b1)->ip.adj_index[VLIB_TX] = dpo1->dpoi_index; vlib_increment_combined_counter (cm, thread_index, lbi0, 1, vlib_buffer_length_in_chain (vm, b0)); vlib_increment_combined_counter (cm, thread_index, lbi1, 1, vlib_buffer_length_in_chain (vm, b1)); if (!(b0->flags & VNET_BUFFER_F_LOOP_COUNTER_VALID)) { vnet_buffer2(b0)->loop_counter = 0; b0->flags |= VNET_BUFFER_F_LOOP_COUNTER_VALID; } if (!(b1->flags & VNET_BUFFER_F_LOOP_COUNTER_VALID)) { vnet_buffer2(b1)->loop_counter = 0; b1->flags |= VNET_BUFFER_F_LOOP_COUNTER_VALID; } vnet_buffer2(b0)->loop_counter++; vnet_buffer2(b1)->loop_counter++; if (PREDICT_FALSE(vnet_buffer2(b0)->loop_counter > MAX_LUKPS_PER_PACKET)) next0 = IP_LOOKUP_NEXT_DROP; if (PREDICT_FALSE(vnet_buffer2(b1)->loop_counter > MAX_LUKPS_PER_PACKET)) next1 = IP_LOOKUP_NEXT_DROP; if (PREDICT_FALSE(b0->flags & VLIB_BUFFER_IS_TRACED)) { lookup_trace_t *tr = vlib_add_trace (vm, node, b0, sizeof (*tr)); tr->fib_index = fib_index0; tr->lbi = lbi0; tr->addr.ip4 = *input_addr0; } if (PREDICT_FALSE(b1->flags & VLIB_BUFFER_IS_TRACED)) { lookup_trace_t *tr = vlib_add_trace (vm, node, b1, sizeof (*tr)); tr->fib_index = fib_index1; tr->lbi = lbi1; tr->addr.ip4 = *input_addr1; } vlib_validate_buffer_enqueue_x2 (vm, node, next_index, to_next, n_left_to_next, bi0, bi1, next0, next1); } while (n_left_from > 0 && n_left_to_next > 0) { u32 bi0, lkdi0, lbi0, fib_index0, next0, hash_c0; flow_hash_config_t flow_hash_config0; const ip4_address_t *input_addr; const load_balance_t *lb0; const lookup_dpo_t * lkd0; const ip4_header_t * ip0; const dpo_id_t *dpo0; vlib_buffer_t * b0; bi0 = from[0]; to_next[0] = bi0; from += 1; to_next += 1; n_left_from -= 1; n_left_to_next -= 1; b0 = vlib_get_buffer (vm, bi0); ip0 = vlib_buffer_get_current (b0); /* dst lookup was done by ip4 lookup */ lkdi0 = vnet_buffer(b0)->ip.adj_index[VLIB_TX]; lkd0 = lookup_dpo_get(lkdi0); /* * choose between a lookup using the fib index in the DPO * or getting the FIB index from the interface. */ if (table_from_interface) { fib_index0 = ip4_fib_table_get_index_for_sw_if_index( vnet_buffer(b0)->sw_if_index[VLIB_RX]); } else { fib_index0 = lkd0->lkd_fib_index; } /* * choose between a source or destination address lookup in the table */ if (input_src_addr) { input_addr = &ip0->src_address; } else { input_addr = &ip0->dst_address; } /* do lookup */ ip4_src_fib_lookup_one (fib_index0, input_addr, &lbi0); lb0 = load_balance_get(lbi0); vnet_buffer(b0)->sw_if_index[VLIB_TX] = fib_index0; /* Use flow hash to compute multipath adjacency. */ hash_c0 = vnet_buffer (b0)->ip.flow_hash = 0; if (PREDICT_FALSE (lb0->lb_n_buckets > 1)) { flow_hash_config0 = lb0->lb_hash_config; hash_c0 = vnet_buffer (b0)->ip.flow_hash = ip4_compute_flow_hash (ip0, flow_hash_config0); } dpo0 = load_balance_get_bucket_i(lb0, (hash_c0 & (lb0->lb_n_buckets_minus_1))); next0 = dpo0->dpoi_next_node; vnet_buffer(b0)->ip.adj_index[VLIB_TX] = dpo0->dpoi_index; vlib_increment_combined_counter (cm, thread_index, lbi0, 1, vlib_buffer_length_in_chain (vm, b0)); if (!(b0->flags & VNET_BUFFER_F_LOOP_COUNTER_VALID)) { vnet_buffer2(b0)->loop_counter = 0; b0->flags |= VNET_BUFFER_F_LOOP_COUNTER_VALID; } vnet_buffer2(b0)->loop_counter++; if (PREDICT_FALSE(vnet_buffer2(b0)->loop_counter > MAX_LUKPS_PER_PACKET)) next0 = IP_LOOKUP_NEXT_DROP; if (PREDICT_FALSE(b0->flags & VLIB_BUFFER_IS_TRACED)) { lookup_trace_t *tr = vlib_add_trace (vm, node, b0, sizeof (*tr)); tr->fib_index = fib_index0; tr->lbi = lbi0; tr->addr.ip4 = *input_addr; } vlib_validate_buffer_enqueue_x1(vm, node, next_index, to_next, n_left_to_next, bi0, next0); } vlib_put_next_frame (vm, node, next_index, n_left_to_next); } return from_frame->n_vectors; } static u8 * format_lookup_trace (u8 * s, va_list * args) { CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *); CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *); lookup_trace_t * t = va_arg (*args, lookup_trace_t *); u32 indent = format_get_indent (s); s = format (s, "%U fib-index:%d addr:%U load-balance:%d", format_white_space, indent, t->fib_index, format_ip46_address, &t->addr, IP46_TYPE_ANY, t->lbi); return s; } VLIB_NODE_FN (lookup_ip4_dst_node) (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * from_frame) { return (lookup_dpo_ip4_inline(vm, node, from_frame, 0, 0)); } VLIB_REGISTER_NODE (lookup_ip4_dst_node) = { .name = "lookup-ip4-dst", .vector_size = sizeof (u32), .sibling_of = "ip4-lookup", .format_trace = format_lookup_trace, }; VLIB_NODE_FN (lookup_ip4_dst_itf_node) (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * from_frame) { return (lookup_dpo_ip4_inline(vm, node, from_frame, 0, 1)); } VLIB_REGISTER_NODE (lookup_ip4_dst_itf_node) = { .name = "lookup-ip4-dst-itf", .vector_size = sizeof (u32), .sibling_of = "ip4-lookup", .format_trace = format_lookup_trace, }; VLIB_NODE_FN (lookup_ip4_src_node) (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * from_frame) { return (lookup_dpo_ip4_inline(vm, node, from_frame, 1, 0)); } VLIB_REGISTER_NODE (lookup_ip4_src_node) = { .name = "lookup-ip4-src", .vector_size = sizeof (u32), .format_trace = format_lookup_trace, .sibling_of = "ip4-lookup", }; always_inline uword lookup_dpo_ip6_inline (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * from_frame, int input_src_addr, int table_from_interface) { vlib_combined_counter_main_t * cm = &load_balance_main.lbm_to_counters; u32 n_left_from, next_index, * from, * to_next; u32 thread_index = vlib_get_thread_index(); from = vlib_frame_vector_args (from_frame); n_left_from = from_frame->n_vectors; next_index = node->cached_next_index; 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 >= 4 && n_left_to_next > 2) { u32 bi0, lkdi0, lbi0, fib_index0, next0, hash_c0; flow_hash_config_t flow_hash_config0; const ip6_address_t *input_addr0; const load_balance_t *lb0; const lookup_dpo_t * lkd0; const ip6_header_t * ip0; const dpo_id_t *dpo0; vlib_buffer_t * b0; u32 bi1, lkdi1, lbi1, fib_index1, next1, hash_c1; flow_hash_config_t flow_hash_config1; const ip6_address_t *input_addr1; const load_balance_t *lb1; const lookup_dpo_t * lkd1; const ip6_header_t * ip1; const dpo_id_t *dpo1; vlib_buffer_t * b1; /* 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, LOAD); vlib_prefetch_buffer_header (p3, LOAD); CLIB_PREFETCH (p2->data, CLIB_CACHE_LINE_BYTES, STORE); CLIB_PREFETCH (p3->data, CLIB_CACHE_LINE_BYTES, STORE); } bi0 = from[0]; to_next[0] = bi0; bi1 = from[1]; to_next[1] = bi1; from += 2; to_next += 2; n_left_from -= 2; n_left_to_next -= 2; b0 = vlib_get_buffer (vm, bi0); ip0 = vlib_buffer_get_current (b0); b1 = vlib_get_buffer (vm, bi1); ip1 = vlib_buffer_get_current (b1); /* dst lookup was done by ip6 lookup */ lkdi0 = vnet_buffer(b0)->ip.adj_index[VLIB_TX]; lkdi1 = vnet_buffer(b1)->ip.adj_index[VLIB_TX]; lkd0 = lookup_dpo_get(lkdi0); lkd1 = lookup_dpo_get(lkdi1); /* * choose between a lookup using the fib index in the DPO * or getting the FIB index from the interface. */ if (table_from_interface) { fib_index0 = ip6_fib_table_get_index_for_sw_if_index( vnet_buffer(b0)->sw_if_index[VLIB_RX]); fib_index1 = ip6_fib_table_get_index_for_sw_if_index( vnet_buffer(b1)->sw_if_index[VLIB_RX]); } else { fib_index0 = lkd0->lkd_fib_index; fib_index1 = lkd1->lkd_fib_index; } /* * choose between a source or destination address lookup in the table */ if (input_src_addr) { input_addr0 = &ip0->src_address; input_addr1 = &ip1->src_address; } else { input_addr0 = &ip0->dst_address; input_addr1 = &ip1->dst_address; } /* do src lookup */ lbi0 = ip6_fib_table_fwding_lookup( fib_index0, input_addr0); lbi1 = ip6_fib_table_fwding_lookup( fib_index1, input_addr1); lb0 = load_balance_get(lbi0); lb1 = load_balance_get(lbi1); vnet_buffer(b0)->sw_if_index[VLIB_TX] = fib_index0; vnet_buffer(b1)->sw_if_index[VLIB_TX] = fib_index1; /* Use flow hash to compute multipath adjacency. */ hash_c0 = vnet_buffer (b0)->ip.flow_hash = 0; hash_c1 = vnet_buffer (b1)->ip.flow_hash = 0; if (!(b0->flags & VNET_BUFFER_F_LOOP_COUNTER_VALID)) { vnet_buffer2(b0)->loop_counter = 0; b0->flags |= VNET_BUFFER_F_LOOP_COUNTER_VALID; } if (!(b1->flags & VNET_BUFFER_F_LOOP_COUNTER_VALID)) { vnet_buffer2(b1)->loop_counter = 0; b1->flags |= VNET_BUFFER_F_LOOP_COUNTER_VALID; } vnet_buffer2(b0)->loop_counter++; vnet_buffer2(b1)->loop_counter++; if (PREDICT_FALSE(vnet_buffer2(b0)->loop_counter > MAX_LUKPS_PER_PACKET)) next0 = IP_LOOKUP_NEXT_DROP; if (PREDICT_FALSE(vnet_buffer2(b1)->loop_counter > MAX_LUKPS_PER_PACKET)) next1 = IP_LOOKUP_NEXT_DROP; if (PREDICT_FALSE (lb0->lb_n_buckets > 1)) { flow_hash_config0 = lb0->lb_hash_config; hash_c0 = vnet_buffer (b0)->ip.flow_hash = ip6_compute_flow_hash (ip0, flow_hash_config0); } if (PREDICT_FALSE (lb1->lb_n_buckets > 1)) { flow_hash_config1 = lb1->lb_hash_config; hash_c1 = vnet_buffer (b1)->ip.flow_hash = ip6_compute_flow_hash (ip1, flow_hash_config1); } dpo0 = load_balance_get_bucket_i(lb0, (hash_c0 & (lb0->lb_n_buckets_minus_1))); dpo1 = load_balance_get_bucket_i(lb1, (hash_c1 & (lb1->lb_n_buckets_minus_1))); next0 = dpo0->dpoi_next_node; next1 = dpo1->dpoi_next_node; vnet_buffer(b0)->ip.adj_index[VLIB_TX] = dpo0->dpoi_index; vnet_buffer(b1)->ip.adj_index[VLIB_TX] = dpo1->dpoi_index; vlib_increment_combined_counter (cm, thread_index, lbi0, 1, vlib_buffer_length_in_chain (vm, b0)); vlib_increment_combined_counter (cm, thread_index, lbi1, 1, vlib_buffer_length_in_chain (vm, b1)); if (PREDICT_FALSE(b0->flags & VLIB_BUFFER_IS_TRACED)) { lookup_trace_t *tr = vlib_add_trace (vm, node, b0, sizeof (*tr)); tr->fib_index = fib_index0; tr->lbi = lbi0; tr->addr.ip6 = *input_addr0; } if (PREDICT_FALSE(b1->flags & VLIB_BUFFER_IS_TRACED)) { lookup_trace_t *tr = vlib_add_trace (vm, node, b1, sizeof (*tr)); tr->fib_index = fib_index1; tr->lbi = lbi1; tr->addr.ip6 = *input_addr1; } vlib_validate_buffer_enqueue_x2(vm, node, next_index, to_next, n_left_to_next, bi0, bi1, next0, next1); } while (n_left_from > 0 && n_left_to_next > 0) { u32 bi0, lkdi0, lbi0, fib_index0, next0, hash_c0; flow_hash_config_t flow_hash_config0; const ip6_address_t *input_addr0; const load_balance_t *lb0; const lookup_dpo_t * lkd0; const ip6_header_t * ip0; const dpo_id_t *dpo0; vlib_buffer_t * b0; bi0 = from[0]; to_next[0] = bi0; from += 1; to_next += 1; n_left_from -= 1; n_left_to_next -= 1; b0 = vlib_get_buffer (vm, bi0); ip0 = vlib_buffer_get_current (b0); /* dst lookup was done by ip6 lookup */ lkdi0 = vnet_buffer(b0)->ip.adj_index[VLIB_TX]; lkd0 = lookup_dpo_get(lkdi0); /* * choose between a lookup using the fib index in the DPO * or getting the FIB index from the interface. */ if (table_from_interface) { fib_index0 = ip6_fib_table_get_index_for_sw_if_index( vnet_buffer(b0)->sw_if_index[VLIB_RX]); } else { fib_index0 = lkd0->lkd_fib_index; } /* * choose between a source or destination address lookup in the table */ if (input_src_addr) { input_addr0 = &ip0->src_address; } else { input_addr0 = &ip0->dst_address; } /* do src lookup */ lbi0 = ip6_fib_table_fwding_lookup( fib_index0, input_addr0); lb0 = load_balance_get(lbi0); vnet_buffer(b0)->sw_if_index[VLIB_TX] = fib_index0; /* Use flow hash to compute multipath adjacency. */ hash_c0 = vnet_buffer (b0)->ip.flow_hash = 0; if (PREDICT_FALSE (lb0->lb_n_buckets > 1)) { flow_hash_config0 = lb0->lb_hash_config; hash_c0 = vnet_buffer (b0)->ip.flow_hash = ip6_compute_flow_hash (ip0, flow_hash_config0); } dpo0 = load_balance_get_bucket_i(lb0, (hash_c0 & (lb0->lb_n_buckets_minus_1))); next0 = dpo0->dpoi_next_node; vnet_buffer(b0)->ip.adj_index[VLIB_TX] = dpo0->dpoi_index; if (!(b0->flags & VNET_BUFFER_F_LOOP_COUNTER_VALID)) { vnet_buffer2(b0)->loop_counter = 0; b0->flags |= VNET_BUFFER_F_LOOP_COUNTER_VALID; } vnet_buffer2(b0)->loop_counter++; if (PREDICT_FALSE(vnet_buffer2(b0)->loop_counter > MAX_LUKPS_PER_PACKET)) next0 = IP_LOOKUP_NEXT_DROP; vlib_increment_combined_counter (cm, thread_index, lbi0, 1, vlib_buffer_length_in_chain (vm, b0)); if (PREDICT_FALSE(b0->flags & VLIB_BUFFER_IS_TRACED)) { lookup_trace_t *tr = vlib_add_trace (vm, node, b0, sizeof (*tr)); tr->fib_index = fib_index0; tr->lbi = lbi0; tr->addr.ip6 = *input_addr0; } vlib_validate_buffer_enqueue_x1(vm, node, next_index, to_next, n_left_to_next, bi0, next0); } vlib_put_next_frame (vm, node, next_index, n_left_to_next); } return from_frame->n_vectors; } VLIB_NODE_FN (lookup_ip6_dst_node) (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * from_frame) { return (lookup_dpo_ip6_inline(vm, node, from_frame, 0 /*use src*/, 0)); } VLIB_REGISTER_NODE (lookup_ip6_dst_node) = { .name = "lookup-ip6-dst", .vector_size = sizeof (u32), .format_trace = format_lookup_trace, .sibling_of = "ip6-lookup", }; VLIB_NODE_FN (lookup_ip6_dst_itf_node) (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * from_frame) { return (lookup_dpo_ip6_inline(vm, node, from_frame, 0 /*use src*/, 1)); } VLIB_REGISTER_NODE (lookup_ip6_dst_itf_node) = { .name = "lookup-ip6-dst-itf", .vector_size = sizeof (u32), .format_trace = format_lookup_trace, .sibling_of = "ip6-lookup", }; VLIB_NODE_FN (lookup_ip6_src_node) (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * from_frame) { return (lookup_dpo_ip6_inline(vm, node, from_frame, 1, 0)); } VLIB_REGISTER_NODE (lookup_ip6_src_node) = { .name = "lookup-ip6-src", .vector_size = sizeof (u32), .format_trace = format_lookup_trace, .sibling_of = "ip6-lookup", }; always_inline uword lookup_dpo_mpls_inline (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * from_frame, int table_from_interface) { u32 n_left_from, next_index, * from, * to_next; u32 thread_index = vlib_get_thread_index(); vlib_combined_counter_main_t * cm = &load_balance_main.lbm_to_counters; from = vlib_frame_vector_args (from_frame); n_left_from = from_frame->n_vectors; next_index = node->cached_next_index; 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 >= 4 && n_left_to_next >= 2) */ /* } */ while (n_left_from > 0 && n_left_to_next > 0) { u32 bi0, lkdi0, lbi0, fib_index0, next0, hash0; const mpls_unicast_header_t * hdr0; const load_balance_t *lb0; const lookup_dpo_t * lkd0; const dpo_id_t *dpo0; vlib_buffer_t * b0; bi0 = from[0]; to_next[0] = bi0; from += 1; to_next += 1; n_left_from -= 1; n_left_to_next -= 1; b0 = vlib_get_buffer (vm, bi0); hdr0 = vlib_buffer_get_current (b0); /* dst lookup was done by mpls lookup */ lkdi0 = vnet_buffer(b0)->ip.adj_index[VLIB_TX]; lkd0 = lookup_dpo_get(lkdi0); /* * choose between a lookup using the fib index in the DPO * or getting the FIB index from the interface. */ if (table_from_interface) { fib_index0 = mpls_fib_table_get_index_for_sw_if_index( vnet_buffer(b0)->sw_if_index[VLIB_RX]); } else { fib_index0 = lkd0->lkd_fib_index; } /* do lookup */ lbi0 = mpls_fib_table_forwarding_lookup (fib_index0, hdr0); lb0 = load_balance_get(lbi0); dpo0 = load_balance_get_bucket_i(lb0, 0); next0 = dpo0->dpoi_next_node; vnet_buffer(b0)->ip.adj_index[VLIB_TX] = dpo0->dpoi_index; if (MPLS_IS_REPLICATE & lbi0) { next0 = mpls_lookup_to_replicate_edge; vnet_buffer (b0)->ip.adj_index[VLIB_TX] = (lbi0 & ~MPLS_IS_REPLICATE); } else { lb0 = load_balance_get(lbi0); ASSERT (lb0->lb_n_buckets > 0); ASSERT (is_pow2 (lb0->lb_n_buckets)); if (PREDICT_FALSE(lb0->lb_n_buckets > 1)) { hash0 = vnet_buffer (b0)->ip.flow_hash = mpls_compute_flow_hash(hdr0, lb0->lb_hash_config); dpo0 = load_balance_get_fwd_bucket (lb0, (hash0 & (lb0->lb_n_buckets_minus_1))); } else { dpo0 = load_balance_get_bucket_i (lb0, 0); } next0 = dpo0->dpoi_next_node; vnet_buffer (b0)->ip.adj_index[VLIB_TX] = dpo0->dpoi_index; vlib_increment_combined_counter (cm, thread_index, lbi0, 1, vlib_buffer_length_in_chain (vm, b0)); } vnet_buffer (b0)->mpls.ttl = ((char*)hdr0)[3]; vnet_buffer (b0)->mpls.exp = (((char*)hdr0)[2] & 0xe) >> 1; vnet_buffer (b0)->mpls.first = 1; vlib_buffer_advance(b0, sizeof(*hdr0)); if (!(b0->flags & VNET_BUFFER_F_LOOP_COUNTER_VALID)) { vnet_buffer2(b0)->loop_counter = 0; b0->flags |= VNET_BUFFER_F_LOOP_COUNTER_VALID; } vnet_buffer2(b0)->loop_counter++; if (PREDICT_FALSE(vnet_buffer2(b0)->loop_counter > MAX_LUKPS_PER_PACKET)) next0 = MPLS_LOOKUP_NEXT_DROP; if (PREDICT_FALSE(b0->flags & VLIB_BUFFER_IS_TRACED)) { lookup_trace_t *tr = vlib_add_trace (vm, node, b0, sizeof (*tr)); tr->fib_index = fib_index0; tr->lbi = lbi0; tr->hdr = *hdr0; } vlib_validate_buffer_enqueue_x1(vm, node, next_index, to_next, n_left_to_next, bi0, next0); } vlib_put_next_frame (vm, node, next_index, n_left_to_next); } return from_frame->n_vectors; } static u8 * format_lookup_mpls_trace (u8 * s, va_list * args) { CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *); CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *); lookup_trace_t * t = va_arg (*args, lookup_trace_t *); u32 indent = format_get_indent (s); mpls_unicast_header_t hdr; hdr.label_exp_s_ttl = clib_net_to_host_u32(t->hdr.label_exp_s_ttl); s = format (s, "%U fib-index:%d hdr:%U load-balance:%d", format_white_space, indent, t->fib_index, format_mpls_header, hdr, t->lbi); return s; } VLIB_NODE_FN (lookup_mpls_dst_node) (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * from_frame) { return (lookup_dpo_mpls_inline(vm, node, from_frame, 0)); } VLIB_REGISTER_NODE (lookup_mpls_dst_node) = { .name = "lookup-mpls-dst", .vector_size = sizeof (u32), .sibling_of = "mpls-lookup", .format_trace = format_lookup_mpls_trace, .n_next_nodes = 0, }; VLIB_NODE_FN (lookup_mpls_dst_itf_node) (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * from_frame) { return (lookup_dpo_mpls_inline(vm, node, from_frame, 1)); } VLIB_REGISTER_NODE (lookup_mpls_dst_itf_node) = { .name = "lookup-mpls-dst-itf", .vector_size = sizeof (u32), .sibling_of = "mpls-lookup", .format_trace = format_lookup_mpls_trace, .n_next_nodes = 0, }; typedef enum lookup_ip_dst_mcast_next_t_ { LOOKUP_IP_DST_MCAST_NEXT_DROP, LOOKUP_IP_DST_MCAST_NEXT_RPF, LOOKUP_IP_DST_MCAST_N_NEXT, } mfib_forward_lookup_next_t; always_inline uword lookup_dpo_ip_dst_mcast_inline (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * from_frame, int is_v4) { u32 n_left_from, next_index, * from, * to_next; from = vlib_frame_vector_args (from_frame); n_left_from = from_frame->n_vectors; next_index = LOOKUP_IP_DST_MCAST_NEXT_RPF; 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 >= 4 && n_left_to_next >= 2) */ /* } */ while (n_left_from > 0 && n_left_to_next > 0) { u32 bi0, lkdi0, fib_index0, next0; const lookup_dpo_t * lkd0; fib_node_index_t mfei0; vlib_buffer_t * b0; bi0 = from[0]; to_next[0] = bi0; from += 1; to_next += 1; n_left_from -= 1; n_left_to_next -= 1; b0 = vlib_get_buffer (vm, bi0); /* dst lookup was done by mpls lookup */ lkdi0 = vnet_buffer(b0)->ip.adj_index[VLIB_TX]; lkd0 = lookup_dpo_get(lkdi0); fib_index0 = lkd0->lkd_fib_index; next0 = LOOKUP_IP_DST_MCAST_NEXT_RPF; if (is_v4) { ip4_header_t * ip0; ip0 = vlib_buffer_get_current (b0); mfei0 = ip4_mfib_table_lookup(ip4_mfib_get(fib_index0), &ip0->src_address, &ip0->dst_address, 64); if (PREDICT_FALSE(b0->flags & VLIB_BUFFER_IS_TRACED)) { lookup_trace_t *tr = vlib_add_trace (vm, node, b0, sizeof (*tr)); tr->fib_index = fib_index0; tr->lbi = mfei0; tr->addr.ip4 = ip0->dst_address; } } else { ip6_header_t * ip0; ip0 = vlib_buffer_get_current (b0); mfei0 = ip6_mfib_table_fwd_lookup(ip6_mfib_get(fib_index0), &ip0->src_address, &ip0->dst_address); if (PREDICT_FALSE(b0->flags & VLIB_BUFFER_IS_TRACED)) { lookup_trace_t *tr = vlib_add_trace (vm, node, b0, sizeof (*tr)); tr->fib_index = fib_index0; tr->lbi = mfei0; tr->addr.ip6 = ip0->dst_address; } } vnet_buffer (b0)->ip.adj_index[VLIB_TX] = mfei0; if (!(b0->flags & VNET_BUFFER_F_LOOP_COUNTER_VALID)) { vnet_buffer2(b0)->loop_counter = 0; b0->flags |= VNET_BUFFER_F_LOOP_COUNTER_VALID; } vnet_buffer2(b0)->loop_counter++; if (PREDICT_FALSE(vnet_buffer2(b0)->loop_counter > MAX_LUKPS_PER_PACKET)) next0 = LOOKUP_IP_DST_MCAST_NEXT_DROP; vlib_validate_buffer_enqueue_x1(vm, node, next_index, to_next, n_left_to_next, bi0, next0); } vlib_put_next_frame (vm, node, next_index, n_left_to_next); } return from_frame->n_vectors; } VLIB_NODE_FN (lookup_ip4_dst_mcast_node) (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * from_frame) { return (lookup_dpo_ip_dst_mcast_inline(vm, node, from_frame, 1)); } VLIB_REGISTER_NODE (lookup_ip4_dst_mcast_node) = { .name = "lookup-ip4-dst-mcast", .vector_size = sizeof (u32), .format_trace = format_lookup_trace, .n_next_nodes = LOOKUP_IP_DST_MCAST_N_NEXT, .next_nodes = { [LOOKUP_IP_DST_MCAST_NEXT_DROP] = "ip4-drop", [LOOKUP_IP_DST_MCAST_NEXT_RPF] = "ip4-mfib-forward-rpf", }, }; VLIB_NODE_FN (lookup_ip6_dst_mcast_node) (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * from_frame) { return (lookup_dpo_ip_dst_mcast_inline(vm, node, from_frame, 0)); } VLIB_REGISTER_NODE (lookup_ip6_dst_mcast_node) = { .name = "lookup-ip6-dst-mcast", .vector_size = sizeof (u32), .format_trace = format_lookup_trace, .n_next_nodes = LOOKUP_IP_DST_MCAST_N_NEXT, .next_nodes = { [LOOKUP_IP_DST_MCAST_NEXT_DROP] = "ip6-drop", [LOOKUP_IP_DST_MCAST_NEXT_RPF] = "ip6-mfib-forward-rpf", }, }; static void lookup_dpo_mem_show (void) { fib_show_memory_usage("Lookup", pool_elts(lookup_dpo_pool), pool_len(lookup_dpo_pool), sizeof(lookup_dpo_t)); } const static dpo_vft_t lkd_vft = { .dv_lock = lookup_dpo_lock, .dv_unlock = lookup_dpo_unlock, .dv_format = format_lookup_dpo, }; const static dpo_vft_t lkd_vft_w_mem_show = { .dv_lock = lookup_dpo_lock, .dv_unlock = lookup_dpo_unlock, .dv_format = format_lookup_dpo, .dv_mem_show = lookup_dpo_mem_show, }; const static char* const lookup_src_ip4_nodes[] = { "lookup-ip4-src", NULL, }; const static char* const lookup_src_ip6_nodes[] = { "lookup-ip6-src", NULL, }; const static char* const * const lookup_src_nodes[DPO_PROTO_NUM] = { [DPO_PROTO_IP4] = lookup_src_ip4_nodes, [DPO_PROTO_IP6] = lookup_src_ip6_nodes, [DPO_PROTO_MPLS] = NULL, }; const static char* const lookup_dst_ip4_nodes[] = { "lookup-ip4-dst", NULL, }; const static char* const lookup_dst_ip6_nodes[] = { "lookup-ip6-dst", NULL, }; const static char* const lookup_dst_mpls_nodes[] = { "lookup-mpls-dst", NULL, }; const static char* const * const lookup_dst_nodes[DPO_PROTO_NUM] = { [DPO_PROTO_IP4] = lookup_dst_ip4_nodes, [DPO_PROTO_IP6] = lookup_dst_ip6_nodes, [DPO_PROTO_MPLS] = lookup_dst_mpls_nodes, }; const static char* const lookup_dst_mcast_ip4_nodes[] = { "lookup-ip4-dst-mcast", NULL, }; const static char* const lookup_dst_mcast_ip6_nodes[] = { "lookup-ip6-dst-mcast", NULL, }; const static char* const * const lookup_dst_mcast_nodes[DPO_PROTO_NUM] = { [DPO_PROTO_IP4] = lookup_dst_mcast_ip4_nodes, [DPO_PROTO_IP6] = lookup_dst_mcast_ip6_nodes, }; const static char* const lookup_dst_from_interface_ip4_nodes[] = { "lookup-ip4-dst-itf", NULL, }; const static char* const lookup_dst_from_interface_ip6_nodes[] = { "lookup-ip6-dst-itf", NULL, }; const static char* const lookup_dst_from_interface_mpls_nodes[] = { "lookup-mpls-dst-itf", NULL, }; const static char* const * const lookup_dst_from_interface_nodes[DPO_PROTO_NUM] = { [DPO_PROTO_IP4] = lookup_dst_from_interface_ip4_nodes, [DPO_PROTO_IP6] = lookup_dst_from_interface_ip6_nodes, [DPO_PROTO_MPLS] = lookup_dst_from_interface_mpls_nodes, }; static clib_error_t * lookup_dpo_show (vlib_main_t * vm, unformat_input_t * input, vlib_cli_command_t * cmd) { index_t lkdi = INDEX_INVALID; while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) { if (unformat (input, "%d", &lkdi)) ; else break; } if (INDEX_INVALID != lkdi) { if (pool_is_free_index(lookup_dpo_pool, lkdi)) vlib_cli_output (vm, "no such index %d", lkdi); else vlib_cli_output (vm, "%U", format_lookup_dpo, lkdi); } else { lookup_dpo_t *lkd; pool_foreach (lkd, lookup_dpo_pool) { vlib_cli_output (vm, "[@%d] %U", lookup_dpo_get_index(lkd), format_lookup_dpo, lookup_dpo_get_index(lkd)); } } return 0; } VLIB_CLI_COMMAND (replicate_show_command, static) = { .path = "show lookup-dpo", .short_help = "show lookup-dpo [<index>]", .function = lookup_dpo_show, }; void lookup_dpo_module_init (void) { dpo_register(DPO_LOOKUP, &lkd_vft_w_mem_show, NULL); /* * There are various sorts of lookup; src or dst addr v4 /v6 etc. * there isn't an object type for each (there is only the lookup_dpo_t), * but, for performance reasons, there is a data plane function, and hence * VLIB node for each. VLIB graph node construction is based on DPO types * so we create sub-types. */ lookup_dpo_sub_types[LOOKUP_SUB_TYPE_SRC] = dpo_register_new_type(&lkd_vft, lookup_src_nodes); lookup_dpo_sub_types[LOOKUP_SUB_TYPE_DST] = dpo_register_new_type(&lkd_vft, lookup_dst_nodes); lookup_dpo_sub_types[LOOKUP_SUB_TYPE_DST_MCAST] = dpo_register_new_type(&lkd_vft, lookup_dst_mcast_nodes); lookup_dpo_sub_types[LOOKUP_SUB_TYPE_DST_TABLE_FROM_INTERFACE] = dpo_register_new_type(&lkd_vft, lookup_dst_from_interface_nodes); }