From 26a101976d6213a01417aa99b35180a79100028b Mon Sep 17 00:00:00 2001 From: =?utf8?q?Beno=C3=AEt=20Ganne?= Date: Thu, 14 Feb 2019 15:32:45 +0100 Subject: [PATCH] Optimize ip6-local MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Optimize IPv6 ip6-local node by rewriting the dual/single loop with prefetch and simpler unrolling. My local, unrepresentative tests for GRE4 termination over IPv6 show a performance improvement of ~40% for ip6-local node alone and ~5% globally. Change-Id: I11e1e86d3838dd3c081aa6be5e25dae16ed6e2d8 Signed-off-by: Benoît Ganne --- src/vnet/ip/ip6_forward.c | 433 ++++++++++++++++++++-------------------------- 1 file changed, 187 insertions(+), 246 deletions(-) diff --git a/src/vnet/ip/ip6_forward.c b/src/vnet/ip/ip6_forward.c index 2c3879b13d1..a7886e630bf 100644 --- a/src/vnet/ip/ip6_forward.c +++ b/src/vnet/ip/ip6_forward.c @@ -1093,316 +1093,257 @@ VNET_FEATURE_ARC_INIT (ip6_local) = }; /* *INDENT-ON* */ -static uword +always_inline uword ip6_local_inline (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * frame, int head_of_feature_arc) { ip6_main_t *im = &ip6_main; ip_lookup_main_t *lm = &im->lookup_main; - ip_local_next_t next_index; - u32 *from, *to_next, n_left_from, n_left_to_next; + u32 *from, n_left_from; vlib_node_runtime_t *error_node = vlib_node_get_runtime (vm, ip6_input_node.index); u8 arc_index = vnet_feat_arc_ip6_local.feature_arc_index; + vlib_buffer_t *bufs[VLIB_FRAME_SIZE], **b; + u16 nexts[VLIB_FRAME_SIZE], *next; from = vlib_frame_vector_args (frame); n_left_from = frame->n_vectors; - next_index = node->cached_next_index; if (node->flags & VLIB_NODE_FLAG_TRACE) ip6_forward_next_trace (vm, node, frame, VLIB_TX); - while (n_left_from > 0) - { - vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next); - - while (n_left_from >= 4 && n_left_to_next >= 2) - { - vlib_buffer_t *p0, *p1; - ip6_header_t *ip0, *ip1; - udp_header_t *udp0, *udp1; - u32 pi0, ip_len0, udp_len0, flags0, next0; - u32 pi1, ip_len1, udp_len1, flags1, next1; - i32 len_diff0, len_diff1; - u8 error0, type0, good_l4_csum0, is_tcp_udp0; - u8 error1, type1, good_l4_csum1, is_tcp_udp1; - u32 udp_offset0, udp_offset1; - - 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; - - error0 = error1 = IP6_ERROR_UNKNOWN_PROTOCOL; - - p0 = vlib_get_buffer (vm, pi0); - p1 = vlib_get_buffer (vm, pi1); - - ip0 = vlib_buffer_get_current (p0); - ip1 = vlib_buffer_get_current (p1); - - if (head_of_feature_arc == 0) - goto skip_checks; + vlib_get_buffers (vm, from, bufs, n_left_from); + b = bufs; + next = nexts; - vnet_buffer (p0)->l3_hdr_offset = p0->current_data; - vnet_buffer (p1)->l3_hdr_offset = p1->current_data; +#define N 2 +#define xN for (int n=0; nbuiltin_protocol_by_ip_protocol[ip0->protocol]; - type1 = lm->builtin_protocol_by_ip_protocol[ip1->protocol]; - - flags0 = p0->flags; - flags1 = p1->flags; + while (n_left_from > N) + { + /* Prefetch next iteration. */ + if (n_left_from >= 3 * N) + { + xN vlib_prefetch_buffer_header (b[2 * N + n], STORE); + xN vlib_prefetch_buffer_data (b[1 * N + n], LOAD); + } - is_tcp_udp0 = ip6_next_proto_is_tcp_udp (p0, ip0, &udp_offset0); - is_tcp_udp1 = ip6_next_proto_is_tcp_udp (p1, ip1, &udp_offset1); + u8 error[N]; + xN error[n] = IP6_ERROR_UNKNOWN_PROTOCOL; - good_l4_csum0 = (flags0 & VNET_BUFFER_F_L4_CHECKSUM_CORRECT - || (flags0 & VNET_BUFFER_F_OFFLOAD_TCP_CKSUM - || flags0 & VNET_BUFFER_F_OFFLOAD_UDP_CKSUM)) - != 0; - good_l4_csum1 = (flags1 & VNET_BUFFER_F_L4_CHECKSUM_CORRECT - || (flags1 & VNET_BUFFER_F_OFFLOAD_TCP_CKSUM - || flags1 & VNET_BUFFER_F_OFFLOAD_UDP_CKSUM)) - != 0; - len_diff0 = 0; - len_diff1 = 0; + ip6_header_t *ip[N]; + xN ip[n] = vlib_buffer_get_current (b[n]); - if (PREDICT_TRUE (is_tcp_udp0)) - { - udp0 = (udp_header_t *) ((u8 *) ip0 + udp_offset0); - /* Don't verify UDP checksum for packets with explicit zero checksum. */ - good_l4_csum0 |= type0 == IP_BUILTIN_PROTOCOL_UDP - && udp0->checksum == 0; - /* Verify UDP length. */ - if (is_tcp_udp0 == IP_PROTOCOL_UDP) - { - ip_len0 = clib_net_to_host_u16 (ip0->payload_length); - udp_len0 = clib_net_to_host_u16 (udp0->length); - len_diff0 = ip_len0 - udp_len0; - } - } - if (PREDICT_TRUE (is_tcp_udp1)) + if (head_of_feature_arc) + { + xN vnet_buffer (b[n])->l3_hdr_offset = b[n]->current_data; + + u8 type[N]; + xN type[n] = lm->builtin_protocol_by_ip_protocol[ip[n]->protocol]; + + u32 flags[N]; + xN flags[n] = b[n]->flags; + + u32 good_l4_csum[N]; + xN good_l4_csum[n] = + flags[n] & (VNET_BUFFER_F_L4_CHECKSUM_CORRECT | + VNET_BUFFER_F_OFFLOAD_TCP_CKSUM | + VNET_BUFFER_F_OFFLOAD_UDP_CKSUM); + + u32 udp_offset[N]; + u8 is_tcp_udp[N]; + xN is_tcp_udp[n] = + ip6_next_proto_is_tcp_udp (b[n], ip[n], &udp_offset[n]); + i16 len_diff[N] = { 0 }; + xN if (PREDICT_TRUE (is_tcp_udp[n])) { - udp1 = (udp_header_t *) ((u8 *) ip1 + udp_offset1); - /* Don't verify UDP checksum for packets with explicit zero checksum. */ - good_l4_csum1 |= type1 == IP_BUILTIN_PROTOCOL_UDP - && udp1->checksum == 0; - /* Verify UDP length. */ - if (is_tcp_udp1 == IP_PROTOCOL_UDP) - { - ip_len1 = clib_net_to_host_u16 (ip1->payload_length); - udp_len1 = clib_net_to_host_u16 (udp1->length); - len_diff1 = ip_len1 - udp_len1; - } + udp_header_t *udp = + (udp_header_t *) ((u8 *) ip[n] + udp_offset[n]); + good_l4_csum[n] |= type[n] == IP_BUILTIN_PROTOCOL_UDP + && udp->checksum == 0; + /* optimistically verify UDP length. */ + u16 ip_len, udp_len; + ip_len = clib_net_to_host_u16 (ip[n]->payload_length); + udp_len = clib_net_to_host_u16 (udp->length); + len_diff[n] = ip_len - udp_len; } - good_l4_csum0 |= type0 == IP_BUILTIN_PROTOCOL_UNKNOWN; - good_l4_csum1 |= type1 == IP_BUILTIN_PROTOCOL_UNKNOWN; + xN good_l4_csum[n] |= type[n] == IP_BUILTIN_PROTOCOL_UNKNOWN; + xN len_diff[n] = + type[n] == IP_BUILTIN_PROTOCOL_UDP ? len_diff[n] : 0; - len_diff0 = type0 == IP_BUILTIN_PROTOCOL_UDP ? len_diff0 : 0; - len_diff1 = type1 == IP_BUILTIN_PROTOCOL_UDP ? len_diff1 : 0; - - if (PREDICT_FALSE (type0 != IP_BUILTIN_PROTOCOL_UNKNOWN - && !good_l4_csum0 - && !(flags0 & - VNET_BUFFER_F_L4_CHECKSUM_COMPUTED))) - { - flags0 = ip6_tcp_udp_icmp_validate_checksum (vm, p0); - good_l4_csum0 = - (flags0 & VNET_BUFFER_F_L4_CHECKSUM_CORRECT) != 0; - } - if (PREDICT_FALSE (type1 != IP_BUILTIN_PROTOCOL_UNKNOWN - && !good_l4_csum1 - && !(flags1 & - VNET_BUFFER_F_L4_CHECKSUM_COMPUTED))) + u8 need_csum[N]; + xN need_csum[n] = type[n] != IP_BUILTIN_PROTOCOL_UNKNOWN + && !good_l4_csum[n] + && !(flags[n] & VNET_BUFFER_F_L4_CHECKSUM_COMPUTED); + xN if (PREDICT_FALSE (need_csum[n])) { - flags1 = ip6_tcp_udp_icmp_validate_checksum (vm, p1); - good_l4_csum1 = - (flags1 & VNET_BUFFER_F_L4_CHECKSUM_CORRECT) != 0; + flags[n] = ip6_tcp_udp_icmp_validate_checksum (vm, b[n]); + good_l4_csum[n] = flags[n] & VNET_BUFFER_F_L4_CHECKSUM_CORRECT; } - error0 = error1 = IP6_ERROR_UNKNOWN_PROTOCOL; - error0 = len_diff0 < 0 ? IP6_ERROR_UDP_LENGTH : error0; - error1 = len_diff1 < 0 ? IP6_ERROR_UDP_LENGTH : error1; + xN error[n] = IP6_ERROR_UNKNOWN_PROTOCOL; + xN error[n] = len_diff[n] < 0 ? IP6_ERROR_UDP_LENGTH : error[n]; - ASSERT (IP6_ERROR_UDP_CHECKSUM + IP_BUILTIN_PROTOCOL_UDP == - IP6_ERROR_UDP_CHECKSUM); - ASSERT (IP6_ERROR_UDP_CHECKSUM + IP_BUILTIN_PROTOCOL_ICMP == - IP6_ERROR_ICMP_CHECKSUM); - error0 = (!good_l4_csum0 ? IP6_ERROR_UDP_CHECKSUM + type0 : error0); - error1 = (!good_l4_csum1 ? IP6_ERROR_UDP_CHECKSUM + type1 : error1); + STATIC_ASSERT (IP6_ERROR_UDP_CHECKSUM + IP_BUILTIN_PROTOCOL_UDP == + IP6_ERROR_UDP_CHECKSUM, + "Wrong IP6 errors constants"); + STATIC_ASSERT (IP6_ERROR_UDP_CHECKSUM + IP_BUILTIN_PROTOCOL_ICMP == + IP6_ERROR_ICMP_CHECKSUM, + "Wrong IP6 errors constants"); + + xN error[n] = + !good_l4_csum[n] ? IP6_ERROR_UDP_CHECKSUM + type[n] : error[n]; /* Drop packets from unroutable hosts. */ /* If this is a neighbor solicitation (ICMP), skip source RPF check */ - if (error0 == IP6_ERROR_UNKNOWN_PROTOCOL && - type0 != IP_BUILTIN_PROTOCOL_ICMP && - !ip6_address_is_link_local_unicast (&ip0->src_address)) + u8 unroutable[N]; + xN unroutable[n] = error[n] == IP6_ERROR_UNKNOWN_PROTOCOL + && type[n] != IP_BUILTIN_PROTOCOL_ICMP + && !ip6_address_is_link_local_unicast (&ip[n]->src_address); + xN if (PREDICT_FALSE (unroutable[n])) { - error0 = (!ip6_urpf_loose_check (im, p0, ip0) - ? IP6_ERROR_SRC_LOOKUP_MISS : error0); + error[n] = + !ip6_urpf_loose_check (im, b[n], + ip[n]) ? IP6_ERROR_SRC_LOOKUP_MISS + : error[n]; } - if (error1 == IP6_ERROR_UNKNOWN_PROTOCOL && - type1 != IP_BUILTIN_PROTOCOL_ICMP && - !ip6_address_is_link_local_unicast (&ip1->src_address)) - { - error1 = (!ip6_urpf_loose_check (im, p1, ip1) - ? IP6_ERROR_SRC_LOOKUP_MISS : error1); - } - - vnet_buffer (p0)->ip.fib_index = - vnet_buffer (p0)->sw_if_index[VLIB_TX] != ~0 ? - vnet_buffer (p0)->sw_if_index[VLIB_TX] : - vnet_buffer (p0)->ip.fib_index; - - vnet_buffer (p1)->ip.fib_index = - vnet_buffer (p1)->sw_if_index[VLIB_TX] != ~0 ? - vnet_buffer (p1)->sw_if_index[VLIB_TX] : - vnet_buffer (p1)->ip.fib_index; - skip_checks: + xN vnet_buffer (b[n])->ip.fib_index = + vnet_buffer (b[n])->sw_if_index[VLIB_TX] != ~0 ? + vnet_buffer (b[n])->sw_if_index[VLIB_TX] : + vnet_buffer (b[n])->ip.fib_index; + } /* head_of_feature_arc */ - next0 = lm->local_next_by_ip_protocol[ip0->protocol]; - next1 = lm->local_next_by_ip_protocol[ip1->protocol]; + xN next[n] = lm->local_next_by_ip_protocol[ip[n]->protocol]; + xN next[n] = + error[n] != IP6_ERROR_UNKNOWN_PROTOCOL ? IP_LOCAL_NEXT_DROP : next[n]; - next0 = - error0 != IP6_ERROR_UNKNOWN_PROTOCOL ? IP_LOCAL_NEXT_DROP : next0; - next1 = - error1 != IP6_ERROR_UNKNOWN_PROTOCOL ? IP_LOCAL_NEXT_DROP : next1; + xN b[n]->error = error_node->errors[n]; - p0->error = error_node->errors[error0]; - p1->error = error_node->errors[error1]; - - if (head_of_feature_arc) + if (head_of_feature_arc) + { + u8 ip6_unknown[N]; + xN ip6_unknown[n] = error[n] == (u8) IP6_ERROR_UNKNOWN_PROTOCOL; + xN if (PREDICT_TRUE (ip6_unknown[n])) { - if (PREDICT_TRUE (error0 == (u8) IP6_ERROR_UNKNOWN_PROTOCOL)) - vnet_feature_arc_start (arc_index, - vnet_buffer (p0)->sw_if_index - [VLIB_RX], &next0, p0); - if (PREDICT_TRUE (error1 == (u8) IP6_ERROR_UNKNOWN_PROTOCOL)) - vnet_feature_arc_start (arc_index, - vnet_buffer (p1)->sw_if_index - [VLIB_RX], &next1, p1); + u32 next32 = next[n]; + vnet_feature_arc_start (arc_index, + vnet_buffer (b[n])->sw_if_index + [VLIB_RX], &next32, b[n]); + next[n] = next32; } - - vlib_validate_buffer_enqueue_x2 (vm, node, next_index, - to_next, n_left_to_next, - pi0, pi1, next0, next1); } - while (n_left_from > 0 && n_left_to_next > 0) - { - vlib_buffer_t *p0; - ip6_header_t *ip0; - udp_header_t *udp0; - u32 pi0, ip_len0, udp_len0, flags0, next0; - i32 len_diff0; - u8 error0, type0, good_l4_csum0; - u32 udp_offset0; - u8 is_tcp_udp0; - - pi0 = to_next[0] = from[0]; - from += 1; - n_left_from -= 1; - to_next += 1; - n_left_to_next -= 1; - - error0 = IP6_ERROR_UNKNOWN_PROTOCOL; - - p0 = vlib_get_buffer (vm, pi0); - ip0 = vlib_buffer_get_current (p0); - - if (head_of_feature_arc == 0) - goto skip_check; - - vnet_buffer (p0)->l3_hdr_offset = p0->current_data; + /* next */ + b += N; + next += N; + n_left_from -= N; + } +#undef xN +#undef N + while (n_left_from) + { + u8 error; + error = IP6_ERROR_UNKNOWN_PROTOCOL; - type0 = lm->builtin_protocol_by_ip_protocol[ip0->protocol]; - flags0 = p0->flags; - is_tcp_udp0 = ip6_next_proto_is_tcp_udp (p0, ip0, &udp_offset0); - good_l4_csum0 = (flags0 & VNET_BUFFER_F_L4_CHECKSUM_CORRECT - || (flags0 & VNET_BUFFER_F_OFFLOAD_TCP_CKSUM - || flags0 & VNET_BUFFER_F_OFFLOAD_UDP_CKSUM)) - != 0; + ip6_header_t *ip; + ip = vlib_buffer_get_current (b[0]); - len_diff0 = 0; - if (PREDICT_TRUE (is_tcp_udp0)) + if (head_of_feature_arc) + { + vnet_buffer (b[0])->l3_hdr_offset = b[0]->current_data; + u8 type = lm->builtin_protocol_by_ip_protocol[ip->protocol]; + + u32 flags = b[0]->flags; + u32 good_l4_csum = + flags & (VNET_BUFFER_F_L4_CHECKSUM_CORRECT | + VNET_BUFFER_F_OFFLOAD_TCP_CKSUM | + VNET_BUFFER_F_OFFLOAD_UDP_CKSUM); + + u32 udp_offset; + i16 len_diff = 0; + u8 is_tcp_udp = ip6_next_proto_is_tcp_udp (b[0], ip, &udp_offset); + if (PREDICT_TRUE (is_tcp_udp)) { - udp0 = (udp_header_t *) ((u8 *) ip0 + udp_offset0); - /* Don't verify UDP checksum for packets with explicit zero - * checksum. */ - good_l4_csum0 |= type0 == IP_BUILTIN_PROTOCOL_UDP - && udp0->checksum == 0; - /* Verify UDP length. */ - if (is_tcp_udp0 == IP_PROTOCOL_UDP) - { - ip_len0 = clib_net_to_host_u16 (ip0->payload_length); - udp_len0 = clib_net_to_host_u16 (udp0->length); - len_diff0 = ip_len0 - udp_len0; - } + udp_header_t *udp = (udp_header_t *) ((u8 *) ip + udp_offset); + /* Don't verify UDP checksum for packets with explicit zero checksum. */ + good_l4_csum |= type == IP_BUILTIN_PROTOCOL_UDP + && udp->checksum == 0; + /* optimistically verify UDP length. */ + u16 ip_len, udp_len; + ip_len = clib_net_to_host_u16 (ip->payload_length); + udp_len = clib_net_to_host_u16 (udp->length); + len_diff = ip_len - udp_len; } - good_l4_csum0 |= type0 == IP_BUILTIN_PROTOCOL_UNKNOWN; - len_diff0 = type0 == IP_BUILTIN_PROTOCOL_UDP ? len_diff0 : 0; + good_l4_csum |= type == IP_BUILTIN_PROTOCOL_UNKNOWN; + len_diff = type == IP_BUILTIN_PROTOCOL_UDP ? len_diff : 0; - if (PREDICT_FALSE (type0 != IP_BUILTIN_PROTOCOL_UNKNOWN - && !good_l4_csum0 - && !(flags0 & - VNET_BUFFER_F_L4_CHECKSUM_COMPUTED))) + u8 need_csum = type != IP_BUILTIN_PROTOCOL_UNKNOWN && !good_l4_csum + && !(flags & VNET_BUFFER_F_L4_CHECKSUM_COMPUTED); + if (PREDICT_FALSE (need_csum)) { - flags0 = ip6_tcp_udp_icmp_validate_checksum (vm, p0); - good_l4_csum0 = - (flags0 & VNET_BUFFER_F_L4_CHECKSUM_CORRECT) != 0; + flags = ip6_tcp_udp_icmp_validate_checksum (vm, b[0]); + good_l4_csum = flags & VNET_BUFFER_F_L4_CHECKSUM_CORRECT; } - error0 = IP6_ERROR_UNKNOWN_PROTOCOL; - error0 = len_diff0 < 0 ? IP6_ERROR_UDP_LENGTH : error0; + error = IP6_ERROR_UNKNOWN_PROTOCOL; + error = len_diff < 0 ? IP6_ERROR_UDP_LENGTH : error; + + STATIC_ASSERT (IP6_ERROR_UDP_CHECKSUM + IP_BUILTIN_PROTOCOL_UDP == + IP6_ERROR_UDP_CHECKSUM, + "Wrong IP6 errors constants"); + STATIC_ASSERT (IP6_ERROR_UDP_CHECKSUM + IP_BUILTIN_PROTOCOL_ICMP == + IP6_ERROR_ICMP_CHECKSUM, + "Wrong IP6 errors constants"); - ASSERT (IP6_ERROR_UDP_CHECKSUM + IP_BUILTIN_PROTOCOL_UDP == - IP6_ERROR_UDP_CHECKSUM); - ASSERT (IP6_ERROR_UDP_CHECKSUM + IP_BUILTIN_PROTOCOL_ICMP == - IP6_ERROR_ICMP_CHECKSUM); - error0 = (!good_l4_csum0 ? IP6_ERROR_UDP_CHECKSUM + type0 : error0); + error = !good_l4_csum ? IP6_ERROR_UDP_CHECKSUM + type : error; - /* If this is a neighbor solicitation (ICMP), skip src RPF check */ - if (error0 == IP6_ERROR_UNKNOWN_PROTOCOL && - type0 != IP_BUILTIN_PROTOCOL_ICMP && - !ip6_address_is_link_local_unicast (&ip0->src_address)) + /* Drop packets from unroutable hosts. */ + /* If this is a neighbor solicitation (ICMP), skip source RPF check */ + u8 unroutable = error == IP6_ERROR_UNKNOWN_PROTOCOL + && type != IP_BUILTIN_PROTOCOL_ICMP + && !ip6_address_is_link_local_unicast (&ip->src_address); + if (PREDICT_FALSE (unroutable)) { - error0 = (!ip6_urpf_loose_check (im, p0, ip0) - ? IP6_ERROR_SRC_LOOKUP_MISS : error0); + error = + !ip6_urpf_loose_check (im, b[0], + ip) ? IP6_ERROR_SRC_LOOKUP_MISS : + error; } - vnet_buffer (p0)->ip.fib_index = - vnet_buffer (p0)->sw_if_index[VLIB_TX] != ~0 ? - vnet_buffer (p0)->sw_if_index[VLIB_TX] : - vnet_buffer (p0)->ip.fib_index; + vnet_buffer (b[0])->ip.fib_index = + vnet_buffer (b[0])->sw_if_index[VLIB_TX] != ~0 ? + vnet_buffer (b[0])->sw_if_index[VLIB_TX] : + vnet_buffer (b[0])->ip.fib_index; + } /* head_of_feature_arc */ - skip_check: + next[0] = lm->local_next_by_ip_protocol[ip->protocol]; + next[0] = + error != IP6_ERROR_UNKNOWN_PROTOCOL ? IP_LOCAL_NEXT_DROP : next[0]; - next0 = lm->local_next_by_ip_protocol[ip0->protocol]; - next0 = - error0 != IP6_ERROR_UNKNOWN_PROTOCOL ? IP_LOCAL_NEXT_DROP : next0; + b[0]->error = error_node->errors[0]; - p0->error = error_node->errors[error0]; - - if (head_of_feature_arc) + if (head_of_feature_arc) + { + if (PREDICT_TRUE (error == (u8) IP6_ERROR_UNKNOWN_PROTOCOL)) { - if (PREDICT_TRUE (error0 == (u8) IP6_ERROR_UNKNOWN_PROTOCOL)) - vnet_feature_arc_start (arc_index, - vnet_buffer (p0)->sw_if_index - [VLIB_RX], &next0, p0); + u32 next32 = next[0]; + vnet_feature_arc_start (arc_index, + vnet_buffer (b[0])->sw_if_index + [VLIB_RX], &next32, b[0]); + next[0] = next32; } - - vlib_validate_buffer_enqueue_x1 (vm, node, next_index, - to_next, n_left_to_next, - pi0, next0); } - vlib_put_next_frame (vm, node, next_index, n_left_to_next); + /* next */ + b += 1; + next += 1; + n_left_from -= 1; } + vlib_buffer_enqueue_to_next (vm, node, from, nexts, frame->n_vectors); return frame->n_vectors; } -- 2.16.6