tcp: consume incoming buffers instead of reusing 33/15733/12
authorFlorin Coras <fcoras@cisco.com>
Mon, 5 Nov 2018 23:57:21 +0000 (15:57 -0800)
committerDamjan Marion <dmarion@me.com>
Wed, 7 Nov 2018 13:26:12 +0000 (13:26 +0000)
Instead of reusing buffers for acking, consume all buffers and program
output for (dup)ack generation. This implicitly fixes the drop counters
that were artificially inflated by both data and feedback traffic.

Moreover, the patch also significantly reduces the ack traffic as we now
only generate an ack per frame, unless duplicate acks need to be sent.

Because of the reduced feedback traffic, a sender's rx path and a
receiver's tx path are now significantly less loaded. In particular, a
sender can overwhelm a 40Gbps NIC and generate tx drop bursts for low
rtts. Consequently, tx pacing is now enforced by default.

Change-Id: I619c29a8945bf26c093f8f9e197e3c6d5d43868e
Signed-off-by: Florin Coras <fcoras@cisco.com>
src/vnet/session/session.h
src/vnet/session/session_node.c
src/vnet/session/transport.c
src/vnet/tcp/tcp.c
src/vnet/tcp/tcp.h
src/vnet/tcp/tcp_input.c
src/vnet/tcp/tcp_output.c

index 3bdf6c9..35c8fb4 100644 (file)
@@ -524,6 +524,13 @@ transport_tx_fifo_size (transport_connection_t * tc)
   return s->server_tx_fifo->nitems;
 }
 
+always_inline u8
+transport_rx_fifo_has_ooo_data (transport_connection_t * tc)
+{
+  stream_session_t *s = session_get (tc->c_index, tc->thread_index);
+  return svm_fifo_has_ooo_data (s->server_rx_fifo);
+}
+
 always_inline f64
 transport_dispatch_period (u32 thread_index)
 {
index edc518e..64c873c 100644 (file)
@@ -751,10 +751,9 @@ static void
 session_update_dispatch_period (session_manager_worker_t * wrk, f64 now,
                                u32 thread_index)
 {
-  if (wrk->last_tx_packets > 8)
+  if (wrk->last_tx_packets > 1)
     {
       f64 sample = now - wrk->last_vlib_time;
-      sample = (sample * wrk->last_tx_packets) / VLIB_FRAME_SIZE;
       wrk->dispatch_period = (wrk->dispatch_period + sample) * 0.5;
     }
   wrk->last_vlib_time = now;
index 42149eb..06c541a 100644 (file)
@@ -47,7 +47,8 @@ static clib_spinlock_t local_endpoints_lock;
  */
 static double transport_pacer_period;
 
-#define TRANSPORT_PACER_MIN_MSS 1460
+#define TRANSPORT_PACER_MIN_MSS        1460
+#define TRANSPORT_PACER_MIN_BURST      TRANSPORT_PACER_MIN_MSS
 
 u8 *
 format_transport_proto (u8 * s, va_list * args)
@@ -518,7 +519,8 @@ spacer_update_bucket (spacer_t * pacer, u32 bytes)
 static inline void
 spacer_update_max_burst_size (spacer_t * pacer, u32 max_burst_bytes)
 {
-  pacer->max_burst_size = clib_max (max_burst_bytes, TRANSPORT_PACER_MIN_MSS);
+  pacer->max_burst_size = clib_max (max_burst_bytes,
+                                   TRANSPORT_PACER_MIN_BURST);
 }
 
 static inline void
@@ -561,9 +563,8 @@ void
 transport_connection_tx_pacer_update (transport_connection_t * tc,
                                      u64 bytes_per_sec)
 {
-  u32 burst_size;
-
-  burst_size = bytes_per_sec * transport_dispatch_period (tc->thread_index);
+  f64 dispatch_period = transport_dispatch_period (tc->thread_index);
+  u32 burst_size = 1.1 * bytes_per_sec * dispatch_period;
   spacer_set_pace_rate (&tc->pacer, bytes_per_sec);
   spacer_update_max_burst_size (&tc->pacer, burst_size);
 }
index a466e3c..ea350dd 100644 (file)
@@ -1105,6 +1105,7 @@ tcp_update_time (f64 now, u8 thread_index)
   tcp_set_time_now (wrk);
   tw_timer_expire_timers_16t_2w_512sl (&wrk->timer_wheel, now);
   tcp_do_fastretransmits (wrk);
+  tcp_send_acks (wrk);
   tcp_flush_frames_to_output (wrk);
 }
 
@@ -1143,13 +1144,16 @@ void
 tcp_connection_tx_pacer_update (tcp_connection_t * tc)
 {
   f64 srtt;
+  u64 rate;
 
   if (!transport_connection_is_tx_paced (&tc->connection))
     return;
 
   srtt = clib_min ((f64) tc->srtt * TCP_TICK, tc->mrtt_us);
-  transport_connection_tx_pacer_update (&tc->connection,
-                                       ((f64) tc->cwnd) / srtt);
+  /* TODO should constrain to interface's max throughput but
+   * we don't have link speeds for sw ifs ..*/
+  rate = tc->cwnd / srtt;
+  transport_connection_tx_pacer_update (&tc->connection, rate);
 }
 
 void
@@ -1326,10 +1330,12 @@ tcp_main_enable (vlib_main_t * vm)
       vec_validate (tm->wrk_ctx[thread].ongoing_fast_rxt, 255);
       vec_validate (tm->wrk_ctx[thread].postponed_fast_rxt, 255);
       vec_validate (tm->wrk_ctx[thread].pending_deq_acked, 255);
+      vec_validate (tm->wrk_ctx[thread].pending_acks, 255);
       vec_reset_length (tm->wrk_ctx[thread].pending_fast_rxt);
       vec_reset_length (tm->wrk_ctx[thread].ongoing_fast_rxt);
       vec_reset_length (tm->wrk_ctx[thread].postponed_fast_rxt);
       vec_reset_length (tm->wrk_ctx[thread].pending_deq_acked);
+      vec_reset_length (tm->wrk_ctx[thread].pending_acks);
       tm->wrk_ctx[thread].vm = vlib_mains[thread];
 
       /*
@@ -1417,6 +1423,7 @@ tcp_init (vlib_main_t * vm)
                               FIB_PROTOCOL_IP6, tcp6_output_node.index);
 
   tcp_api_reference ();
+  tm->tx_pacing = 1;
   return 0;
 }
 
@@ -1441,8 +1448,8 @@ tcp_config_fn (vlib_main_t * vm, unformat_input_t * input)
       else if (unformat (input, "max-rx-fifo %U", unformat_memory_size,
                         &tm->max_rx_fifo))
        ;
-      else if (unformat (input, "tx-pacing"))
-       tm->tx_pacing = 1;
+      else if (unformat (input, "no-tx-pacing"))
+       tm->tx_pacing = 0;
       else
        return clib_error_return (0, "unknown input `%U'",
                                  format_unformat_error, input);
index 480b924..5a3a965 100644 (file)
@@ -300,7 +300,7 @@ typedef struct _tcp_connection
   sack_scoreboard_t sack_sb;   /**< SACK "scoreboard" that tracks holes */
 
   u16 rcv_dupacks;     /**< Number of DUPACKs received */
-  u8 snt_dupacks;      /**< Number of DUPACKs sent in a burst */
+  u8 pending_dupacks;  /**< Number of DUPACKs to be sent */
 
   /* Congestion control */
   u32 cwnd;            /**< Congestion window */
@@ -395,6 +395,7 @@ typedef struct tcp_worker_ctx_
   u32 *postponed_fast_rxt;                     /**< vector of connections
                                                     that will do fast rxt */
   u32 *pending_deq_acked;
+  u32 *pending_acks;
   vlib_main_t *vm;                             /**< pointer to vm */
 
     CLIB_CACHE_LINE_ALIGN_MARK (cacheline1);
@@ -564,6 +565,7 @@ void tcp_send_reset_w_pkt (tcp_connection_t * tc, vlib_buffer_t * pkt,
                           u8 is_ip4);
 void tcp_send_reset (tcp_connection_t * tc);
 void tcp_send_syn (tcp_connection_t * tc);
+void tcp_send_synack (tcp_connection_t * tc);
 void tcp_send_fin (tcp_connection_t * tc);
 void tcp_init_mss (tcp_connection_t * tc);
 void tcp_update_burst_snd_vars (tcp_connection_t * tc);
@@ -574,6 +576,10 @@ void tcp_program_fastretransmit (tcp_worker_ctx_t * wrk,
                                 tcp_connection_t * tc);
 void tcp_do_fastretransmits (tcp_worker_ctx_t * wrk);
 
+void tcp_program_ack (tcp_worker_ctx_t * wrk, tcp_connection_t * tc);
+void tcp_program_dupack (tcp_worker_ctx_t * wrk, tcp_connection_t * tc);
+void tcp_send_acks (tcp_worker_ctx_t * wrk);
+
 always_inline u32
 tcp_end_seq (tcp_header_t * th, u32 len)
 {
@@ -750,14 +756,6 @@ tcp_cc_rcv_ack (tcp_connection_t * tc)
   tcp_connection_tx_pacer_update (tc);
 }
 
-always_inline void
-tcp_connection_force_ack (tcp_connection_t * tc, vlib_buffer_t * b)
-{
-  /* Reset flags, make sure ack is sent */
-  tc->flags = TCP_CONN_SNDACK;
-  vnet_buffer (b)->tcp.flags &= ~TCP_BUF_FLAG_DUPACK;
-}
-
 always_inline void
 tcp_timer_set (tcp_connection_t * tc, u8 timer_id, u32 interval)
 {
index 9fc601b..9c303eb 100644 (file)
@@ -272,29 +272,28 @@ tcp_update_timestamp (tcp_connection_t * tc, u32 seq, u32 seq_end)
  * @return 0 if segments passes validation.
  */
 static int
-tcp_segment_validate (vlib_main_t * vm, tcp_connection_t * tc0,
-                     vlib_buffer_t * b0, tcp_header_t * th0,
-                     u32 * next0, u32 * error0)
+tcp_segment_validate (tcp_worker_ctx_t * wrk, tcp_connection_t * tc0,
+                     vlib_buffer_t * b0, tcp_header_t * th0, u32 * error0)
 {
   /* We could get a burst of RSTs interleaved with acks */
   if (PREDICT_FALSE (tc0->state == TCP_STATE_CLOSED))
     {
       tcp_send_reset (tc0);
       *error0 = TCP_ERROR_CONNECTION_CLOSED;
-      goto drop;
+      goto error;
     }
 
   if (PREDICT_FALSE (!tcp_ack (th0) && !tcp_rst (th0) && !tcp_syn (th0)))
     {
       *error0 = TCP_ERROR_SEGMENT_INVALID;
-      goto drop;
+      goto error;
     }
 
   if (PREDICT_FALSE (tcp_options_parse (th0, &tc0->rcv_opts)))
     {
       clib_warning ("options parse error");
       *error0 = TCP_ERROR_OPTIONS;
-      goto drop;
+      goto error;
     }
 
   if (PREDICT_FALSE (tcp_segment_check_paws (tc0)))
@@ -319,12 +318,11 @@ tcp_segment_validate (vlib_main_t * vm, tcp_connection_t * tc0,
          /* Drop after ack if not rst */
          if (!tcp_rst (th0))
            {
-             tcp_make_ack (tc0, b0);
+             tcp_program_ack (wrk, tc0);
              TCP_EVT_DBG (TCP_EVT_DUPACK_SENT, tc0, vnet_buffer (b0)->tcp);
-             goto error;
            }
        }
-      goto drop;
+      goto error;
     }
 
   /* 1st: check sequence number */
@@ -340,11 +338,10 @@ tcp_segment_validate (vlib_main_t * vm, tcp_connection_t * tc0,
          /* If not RST, send dup ack */
          if (!tcp_rst (th0))
            {
-             tcp_make_ack (tc0, b0);
+             tcp_program_dupack (wrk, tc0);
              TCP_EVT_DBG (TCP_EVT_DUPACK_SENT, tc0, vnet_buffer (b0)->tcp);
-             goto error;
            }
-         goto drop;
+         goto error;
        }
     }
 
@@ -353,7 +350,7 @@ tcp_segment_validate (vlib_main_t * vm, tcp_connection_t * tc0,
     {
       tcp_connection_reset (tc0);
       *error0 = TCP_ERROR_RST_RCVD;
-      goto drop;
+      goto error;
     }
 
   /* 3rd: check security and precedence (skip) */
@@ -364,12 +361,12 @@ tcp_segment_validate (vlib_main_t * vm, tcp_connection_t * tc0,
       /* TODO implement RFC 5961 */
       if (tc0->state == TCP_STATE_SYN_RCVD)
        {
-         tcp_make_synack (tc0, b0);
+         tcp_send_synack (tc0);
          TCP_EVT_DBG (TCP_EVT_SYN_RCVD, tc0, 0);
        }
       else
        {
-         tcp_make_ack (tc0, b0);
+         tcp_program_ack (wrk, tc0);
          TCP_EVT_DBG (TCP_EVT_SYNACK_RCVD, tc0);
        }
       goto error;
@@ -380,11 +377,7 @@ tcp_segment_validate (vlib_main_t * vm, tcp_connection_t * tc0,
                        vnet_buffer (b0)->tcp.seq_end);
   return 0;
 
-drop:
-  *next0 = tcp_next_drop (tc0->c_is_ip4);
-  return -1;
 error:
-  *next0 = tcp_next_output (tc0->c_is_ip4);
   return -1;
 }
 
@@ -1476,7 +1469,7 @@ partial_ack:
  */
 static int
 tcp_rcv_ack (tcp_worker_ctx_t * wrk, tcp_connection_t * tc, vlib_buffer_t * b,
-            tcp_header_t * th, u32 * next, u32 * error)
+            tcp_header_t * th, u32 * error)
 {
   u32 prev_snd_wnd, prev_snd_una;
   u8 is_dack;
@@ -1502,8 +1495,7 @@ tcp_rcv_ack (tcp_worker_ctx_t * wrk, tcp_connection_t * tc, vlib_buffer_t * b,
        * drop it */
       if (seq_gt (vnet_buffer (b)->tcp.ack_number, tc->snd_una_max))
        {
-         tcp_make_ack (tc, b);
-         *next = tcp_next_output (tc->c_is_ip4);
+         tcp_program_ack (wrk, tc);
          *error = TCP_ERROR_ACK_FUTURE;
          TCP_EVT_DBG (TCP_EVT_ACK_RCV_ERR, tc, 0,
                       vnet_buffer (b)->tcp.ack_number);
@@ -1684,25 +1676,16 @@ tcp_session_enqueue_data (tcp_connection_t * tc, vlib_buffer_t * b,
   else if (written > data_len)
     {
       tc->rcv_nxt += written;
-
-      /* Send ACK confirming the update */
-      tc->flags |= TCP_CONN_SNDACK;
       TCP_EVT_DBG (TCP_EVT_CC_INPUT, tc, data_len, written);
     }
   else if (written > 0)
     {
       /* We've written something but FIFO is probably full now */
       tc->rcv_nxt += written;
-
-      /* Depending on how fast the app is, all remaining buffers in burst will
-       * not be enqueued. Inform peer */
-      tc->flags |= TCP_CONN_SNDACK;
-
       error = TCP_ERROR_PARTIALLY_ENQUEUED;
     }
   else
     {
-      tc->flags |= TCP_CONN_SNDACK;
       return TCP_ERROR_FIFO_FULL;
     }
 
@@ -1821,7 +1804,8 @@ tcp_buffer_discard_bytes (vlib_buffer_t * b, u32 n_bytes_to_drop)
  * It handles both in order or out-of-order data.
  */
 static int
-tcp_segment_rcv (tcp_connection_t * tc, vlib_buffer_t * b, u32 * next0)
+tcp_segment_rcv (tcp_worker_ctx_t * wrk, tcp_connection_t * tc,
+                vlib_buffer_t * b)
 {
   u32 error, n_bytes_to_drop, n_data_bytes;
 
@@ -1840,9 +1824,8 @@ tcp_segment_rcv (tcp_connection_t * tc, vlib_buffer_t * b, u32 * next0)
           * retransmissions since we may not have any data to send */
          if (seq_leq (vnet_buffer (b)->tcp.seq_end, tc->rcv_nxt))
            {
-             tcp_make_ack (tc, b);
+             tcp_program_ack (wrk, tc);
              error = TCP_ERROR_SEGMENT_OLD;
-             *next0 = tcp_next_output (tc->c_is_ip4);
              goto done;
            }
 
@@ -1854,7 +1837,6 @@ tcp_segment_rcv (tcp_connection_t * tc, vlib_buffer_t * b, u32 * next0)
          if (tcp_buffer_discard_bytes (b, n_bytes_to_drop))
            {
              error = TCP_ERROR_SEGMENT_OLD;
-             *next0 = tcp_next_drop (tc->c_is_ip4);
              goto done;
            }
          goto in_order;
@@ -1862,9 +1844,7 @@ tcp_segment_rcv (tcp_connection_t * tc, vlib_buffer_t * b, u32 * next0)
 
       /* RFC2581: Enqueue and send DUPACK for fast retransmit */
       error = tcp_session_enqueue_ooo (tc, b, n_data_bytes);
-      *next0 = tcp_next_output (tc->c_is_ip4);
-      tcp_make_ack (tc, b);
-      vnet_buffer (b)->tcp.flags = TCP_BUF_FLAG_DUPACK;
+      tcp_program_dupack (wrk, tc);
       TCP_EVT_DBG (TCP_EVT_DUPACK_SENT, tc, vnet_buffer (b)->tcp);
       goto done;
     }
@@ -1876,14 +1856,12 @@ in_order:
   error = tcp_session_enqueue_data (tc, b, n_data_bytes);
   if (tcp_can_delack (tc))
     {
-      *next0 = tcp_next_drop (tc->c_is_ip4);
       if (!tcp_timer_is_active (tc, TCP_TIMER_DELACK))
        tcp_timer_set (tc, TCP_TIMER_DELACK, TCP_DELACK_TIME);
       goto done;
     }
 
-  *next0 = tcp_next_output (tc->c_is_ip4);
-  tcp_make_ack (tc, b);
+  tcp_program_ack (wrk, tc);
 
 done:
   return error;
@@ -2019,116 +1997,91 @@ tcp46_established_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
 {
   u32 thread_index = vm->thread_index, errors = 0;
   tcp_worker_ctx_t *wrk = tcp_get_worker (thread_index);
-  u32 n_left_from, next_index, *from, *to_next;
+  u32 n_left_from, *from, *first_buffer;
   u16 err_counters[TCP_N_ERROR] = { 0 };
   u8 is_fin = 0;
 
   if (node->flags & VLIB_NODE_FLAG_TRACE)
     tcp_established_trace_frame (vm, node, frame, is_ip4);
 
-  from = vlib_frame_vector_args (frame);
+  first_buffer = from = vlib_frame_vector_args (frame);
   n_left_from = frame->n_vectors;
-  next_index = node->cached_next_index;
 
   while (n_left_from > 0)
     {
-      u32 n_left_to_next;
+      u32 bi0, error0 = TCP_ERROR_ACK_OK;
+      vlib_buffer_t *b0;
+      tcp_header_t *th0 = 0;
+      tcp_connection_t *tc0;
 
-      vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);
-      while (n_left_from > 0 && n_left_to_next > 0)
+      if (n_left_from > 1)
        {
-         u32 bi0;
-         vlib_buffer_t *b0;
-         tcp_header_t *th0 = 0;
-         tcp_connection_t *tc0;
-         u32 next0 = tcp_next_drop (is_ip4), error0 = TCP_ERROR_ACK_OK;
-
-         if (n_left_from > 1)
-           {
-             vlib_buffer_t *pb;
-             pb = vlib_get_buffer (vm, from[1]);
-             vlib_prefetch_buffer_header (pb, LOAD);
-             CLIB_PREFETCH (pb->data, 2 * CLIB_CACHE_LINE_BYTES, LOAD);
-           }
-
-         bi0 = from[0];
-         to_next[0] = bi0;
-         from += 1;
-         to_next += 1;
-         n_left_from -= 1;
-         n_left_to_next -= 1;
+         vlib_buffer_t *pb;
+         pb = vlib_get_buffer (vm, from[1]);
+         vlib_prefetch_buffer_header (pb, LOAD);
+         CLIB_PREFETCH (pb->data, 2 * CLIB_CACHE_LINE_BYTES, LOAD);
+       }
 
-         b0 = vlib_get_buffer (vm, bi0);
-         tc0 = tcp_connection_get (vnet_buffer (b0)->tcp.connection_index,
-                                   thread_index);
+      bi0 = from[0];
+      from += 1;
+      n_left_from -= 1;
 
-         if (PREDICT_FALSE (tc0 == 0))
-           {
-             error0 = TCP_ERROR_INVALID_CONNECTION;
-             goto done;
-           }
+      b0 = vlib_get_buffer (vm, bi0);
+      tc0 = tcp_connection_get (vnet_buffer (b0)->tcp.connection_index,
+                               thread_index);
 
-         th0 = tcp_buffer_hdr (b0);
-         /* N.B. buffer is rewritten if segment is ooo. Thus, th0 becomes a
-          * dangling reference. */
-         is_fin = tcp_is_fin (th0);
+      if (PREDICT_FALSE (tc0 == 0))
+       {
+         error0 = TCP_ERROR_INVALID_CONNECTION;
+         goto done;
+       }
 
-         /* SYNs, FINs and data consume sequence numbers */
-         vnet_buffer (b0)->tcp.seq_end = vnet_buffer (b0)->tcp.seq_number
-           + tcp_is_syn (th0) + is_fin + vnet_buffer (b0)->tcp.data_len;
+      th0 = tcp_buffer_hdr (b0);
+      /* N.B. buffer is rewritten if segment is ooo. Thus, th0 becomes a
+       * dangling reference. */
+      is_fin = tcp_is_fin (th0);
 
-         /* TODO header prediction fast path */
+      /* SYNs, FINs and data consume sequence numbers */
+      vnet_buffer (b0)->tcp.seq_end = vnet_buffer (b0)->tcp.seq_number
+       + tcp_is_syn (th0) + is_fin + vnet_buffer (b0)->tcp.data_len;
 
-         /* 1-4: check SEQ, RST, SYN */
-         if (PREDICT_FALSE (tcp_segment_validate (vm, tc0, b0, th0, &next0,
-                                                  &error0)))
-           {
-             tcp_maybe_inc_err_counter (err_counters, error0);
-             TCP_EVT_DBG (TCP_EVT_SEG_INVALID, tc0, vnet_buffer (b0)->tcp);
-             goto done;
-           }
+      /* TODO header prediction fast path */
 
-         /* 5: check the ACK field  */
-         if (PREDICT_FALSE (tcp_rcv_ack (wrk, tc0, b0, th0, &next0,
-                                         &error0)))
-           {
-             tcp_maybe_inc_err_counter (err_counters, error0);
-             goto done;
-           }
+      /* 1-4: check SEQ, RST, SYN */
+      if (PREDICT_FALSE (tcp_segment_validate (wrk, tc0, b0, th0, &error0)))
+       {
+         TCP_EVT_DBG (TCP_EVT_SEG_INVALID, tc0, vnet_buffer (b0)->tcp);
+         goto done;
+       }
 
-         /* 6: check the URG bit TODO */
+      /* 5: check the ACK field  */
+      if (PREDICT_FALSE (tcp_rcv_ack (wrk, tc0, b0, th0, &error0)))
+       goto done;
 
-         /* 7: process the segment text */
-         if (vnet_buffer (b0)->tcp.data_len)
-           {
-             error0 = tcp_segment_rcv (tc0, b0, &next0);
-             tcp_maybe_inc_err_counter (err_counters, error0);
-           }
+      /* 6: check the URG bit TODO */
 
-         /* 8: check the FIN bit */
-         if (PREDICT_FALSE (is_fin))
-           {
-             /* Enter CLOSE-WAIT and notify session. To avoid lingering
-              * in CLOSE-WAIT, set timer (reuse WAITCLOSE). */
-             /* Account for the FIN if nothing else was received */
-             if (vnet_buffer (b0)->tcp.data_len == 0)
-               tc0->rcv_nxt += 1;
-             tcp_make_ack (tc0, b0);
-             next0 = tcp_next_output (tc0->c_is_ip4);
-             tc0->state = TCP_STATE_CLOSE_WAIT;
-             stream_session_disconnect_notify (&tc0->connection);
-             tcp_timer_update (tc0, TCP_TIMER_WAITCLOSE, TCP_CLOSEWAIT_TIME);
-             TCP_EVT_DBG (TCP_EVT_FIN_RCVD, tc0);
-             tcp_inc_err_counter (err_counters, TCP_ERROR_FIN_RCVD, 1);
-           }
+      /* 7: process the segment text */
+      if (vnet_buffer (b0)->tcp.data_len)
+       error0 = tcp_segment_rcv (wrk, tc0, b0);
 
-       done:
-         b0->error = node->errors[error0];
-         vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next,
-                                          n_left_to_next, bi0, next0);
+      /* 8: check the FIN bit */
+      if (PREDICT_FALSE (is_fin))
+       {
+         /* Enter CLOSE-WAIT and notify session. To avoid lingering
+          * in CLOSE-WAIT, set timer (reuse WAITCLOSE). */
+         /* Account for the FIN if nothing else was received */
+         if (vnet_buffer (b0)->tcp.data_len == 0)
+           tc0->rcv_nxt += 1;
+         tcp_program_ack (wrk, tc0);
+         tc0->state = TCP_STATE_CLOSE_WAIT;
+         stream_session_disconnect_notify (&tc0->connection);
+         tcp_timer_update (tc0, TCP_TIMER_WAITCLOSE, TCP_CLOSEWAIT_TIME);
+         TCP_EVT_DBG (TCP_EVT_FIN_RCVD, tc0);
+         error0 = TCP_ERROR_FIN_RCVD;
        }
 
-      vlib_put_next_frame (vm, node, next_index, n_left_to_next);
+    done:
+      tcp_inc_err_counter (err_counters, error0, 1);
     }
 
   errors = session_manager_flush_enqueue_events (TRANSPORT_PROTO_TCP,
@@ -2136,7 +2089,7 @@ tcp46_established_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
   err_counters[TCP_ERROR_EVENT_FIFO_FULL] = errors;
   tcp_store_err_counters (established, err_counters);
   tcp_handle_postponed_dequeues (wrk);
-  tcp_flush_frame_to_output (wrk, is_ip4);
+  vlib_buffer_free (vm, first_buffer, frame->n_vectors);
 
   return frame->n_vectors;
 }
@@ -2287,262 +2240,247 @@ tcp46_syn_sent_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
                       vlib_frame_t * from_frame, int is_ip4)
 {
   tcp_main_t *tm = vnet_get_tcp_main ();
-  u32 n_left_from, next_index, *from, *to_next;
-  u32 my_thread_index = vm->thread_index, errors = 0;
+  u32 n_left_from, *from, *first_buffer, errors = 0;
+  u32 my_thread_index = vm->thread_index;
+  tcp_worker_ctx_t *wrk = tcp_get_worker (my_thread_index);
 
-  from = vlib_frame_vector_args (from_frame);
+  from = first_buffer = 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;
+      u32 bi0, ack0, seq0, error0 = TCP_ERROR_NONE;
+      tcp_connection_t *tc0, *new_tc0;
+      tcp_header_t *tcp0 = 0;
+      tcp_rx_trace_t *t0;
+      vlib_buffer_t *b0;
 
-      vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);
+      bi0 = from[0];
+      from += 1;
+      n_left_from -= 1;
 
-      while (n_left_from > 0 && n_left_to_next > 0)
+      b0 = vlib_get_buffer (vm, bi0);
+      tc0 =
+       tcp_half_open_connection_get (vnet_buffer (b0)->tcp.connection_index);
+      if (PREDICT_FALSE (tc0 == 0))
        {
-         u32 bi0, ack0, seq0;
-         vlib_buffer_t *b0;
-         tcp_rx_trace_t *t0;
-         tcp_header_t *tcp0 = 0;
-         tcp_connection_t *tc0;
-         tcp_connection_t *new_tc0;
-         u32 next0 = tcp_next_drop (is_ip4), error0 = TCP_ERROR_ENQUEUED;
-
-         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);
-         tc0 =
-           tcp_half_open_connection_get (vnet_buffer (b0)->
-                                         tcp.connection_index);
-         if (PREDICT_FALSE (tc0 == 0))
-           {
-             error0 = TCP_ERROR_INVALID_CONNECTION;
-             goto drop;
-           }
-
-         /* Half-open completed recently but the connection was't removed
-          * yet by the owning thread */
-         if (PREDICT_FALSE (tc0->flags & TCP_CONN_HALF_OPEN_DONE))
-           {
-             /* Make sure the connection actually exists */
-             ASSERT (tcp_lookup_connection (tc0->c_fib_index, b0,
-                                            my_thread_index, is_ip4));
-             goto drop;
-           }
-
-         ack0 = vnet_buffer (b0)->tcp.ack_number;
-         seq0 = vnet_buffer (b0)->tcp.seq_number;
-         tcp0 = tcp_buffer_hdr (b0);
-
-         /* Crude check to see if the connection handle does not match
-          * the packet. Probably connection just switched to established */
-         if (PREDICT_FALSE (tcp0->dst_port != tc0->c_lcl_port
-                            || tcp0->src_port != tc0->c_rmt_port))
-           goto drop;
-
-         if (PREDICT_FALSE
-             (!tcp_ack (tcp0) && !tcp_rst (tcp0) && !tcp_syn (tcp0)))
-           goto drop;
-
-         /* SYNs, FINs and data consume sequence numbers */
-         vnet_buffer (b0)->tcp.seq_end = seq0 + tcp_is_syn (tcp0)
-           + tcp_is_fin (tcp0) + vnet_buffer (b0)->tcp.data_len;
-
-         /*
-          *  1. check the ACK bit
-          */
+         error0 = TCP_ERROR_INVALID_CONNECTION;
+         goto drop;
+       }
 
-         /*
-          *   If the ACK bit is set
-          *     If SEG.ACK =< ISS, or SEG.ACK > SND.NXT, send a reset (unless
-          *     the RST bit is set, if so drop the segment and return)
-          *       <SEQ=SEG.ACK><CTL=RST>
-          *     and discard the segment.  Return.
-          *     If SND.UNA =< SEG.ACK =< SND.NXT then the ACK is acceptable.
-          */
-         if (tcp_ack (tcp0))
-           {
-             if (seq_leq (ack0, tc0->iss) || seq_gt (ack0, tc0->snd_nxt))
-               {
-                 clib_warning ("ack not in rcv wnd");
-                 if (!tcp_rst (tcp0))
-                   tcp_send_reset_w_pkt (tc0, b0, is_ip4);
-                 goto drop;
-               }
+      /* Half-open completed recently but the connection was't removed
+       * yet by the owning thread */
+      if (PREDICT_FALSE (tc0->flags & TCP_CONN_HALF_OPEN_DONE))
+       {
+         /* Make sure the connection actually exists */
+         ASSERT (tcp_lookup_connection (tc0->c_fib_index, b0,
+                                        my_thread_index, is_ip4));
+         goto drop;
+       }
 
-             /* Make sure ACK is valid */
-             if (seq_gt (tc0->snd_una, ack0))
-               {
-                 clib_warning ("ack invalid");
-                 goto drop;
-               }
-           }
+      ack0 = vnet_buffer (b0)->tcp.ack_number;
+      seq0 = vnet_buffer (b0)->tcp.seq_number;
+      tcp0 = tcp_buffer_hdr (b0);
 
-         /*
-          * 2. check the RST bit
-          */
+      /* Crude check to see if the connection handle does not match
+       * the packet. Probably connection just switched to established */
+      if (PREDICT_FALSE (tcp0->dst_port != tc0->c_lcl_port
+                        || tcp0->src_port != tc0->c_rmt_port))
+       {
+         error0 = TCP_ERROR_INVALID_CONNECTION;
+         goto drop;
+       }
 
-         if (tcp_rst (tcp0))
-           {
-             /* If ACK is acceptable, signal client that peer is not
-              * willing to accept connection and drop connection*/
-             if (tcp_ack (tcp0))
-               tcp_connection_reset (tc0);
-             goto drop;
-           }
+      if (PREDICT_FALSE (!tcp_ack (tcp0) && !tcp_rst (tcp0)
+                        && !tcp_syn (tcp0)))
+       {
+         error0 = TCP_ERROR_SEGMENT_INVALID;
+         goto drop;
+       }
 
-         /*
-          * 3. check the security and precedence (skipped)
-          */
+      /* SYNs, FINs and data consume sequence numbers */
+      vnet_buffer (b0)->tcp.seq_end =
+       seq0 + tcp_is_syn (tcp0) + tcp_is_fin (tcp0) +
+       vnet_buffer (b0)->tcp.data_len;
 
-         /*
-          * 4. check the SYN bit
-          */
+      /*
+       *  1. check the ACK bit
+       */
 
-         /* No SYN flag. Drop. */
-         if (!tcp_syn (tcp0))
+      /*
+       *   If the ACK bit is set
+       *     If SEG.ACK =< ISS, or SEG.ACK > SND.NXT, send a reset (unless
+       *     the RST bit is set, if so drop the segment and return)
+       *       <SEQ=SEG.ACK><CTL=RST>
+       *     and discard the segment.  Return.
+       *     If SND.UNA =< SEG.ACK =< SND.NXT then the ACK is acceptable.
+       */
+      if (tcp_ack (tcp0))
+       {
+         if (seq_leq (ack0, tc0->iss) || seq_gt (ack0, tc0->snd_nxt))
            {
-             clib_warning ("not synack");
+             if (!tcp_rst (tcp0))
+               tcp_send_reset_w_pkt (tc0, b0, is_ip4);
+             error0 = TCP_ERROR_RCV_WND;
              goto drop;
            }
 
-         /* Parse options */
-         if (tcp_options_parse (tcp0, &tc0->rcv_opts))
+         /* Make sure ACK is valid */
+         if (seq_gt (tc0->snd_una, ack0))
            {
-             clib_warning ("options parse fail");
+             error0 = TCP_ERROR_ACK_INVALID;
              goto drop;
            }
+       }
 
-         /* Valid SYN or SYN-ACK. Move connection from half-open pool to
-          * current thread pool. */
-         pool_get (tm->connections[my_thread_index], new_tc0);
-         clib_memcpy (new_tc0, tc0, sizeof (*new_tc0));
-         new_tc0->c_c_index = new_tc0 - tm->connections[my_thread_index];
-         new_tc0->c_thread_index = my_thread_index;
-         new_tc0->rcv_nxt = vnet_buffer (b0)->tcp.seq_end;
-         new_tc0->irs = seq0;
-         new_tc0->timers[TCP_TIMER_ESTABLISH] = TCP_TIMER_HANDLE_INVALID;
-         new_tc0->timers[TCP_TIMER_RETRANSMIT_SYN] =
-           TCP_TIMER_HANDLE_INVALID;
-         new_tc0->sw_if_index = vnet_buffer (b0)->sw_if_index[VLIB_RX];
-
-         /* If this is not the owning thread, wait for syn retransmit to
-          * expire and cleanup then */
-         if (tcp_half_open_connection_cleanup (tc0))
-           tc0->flags |= TCP_CONN_HALF_OPEN_DONE;
-
-         if (tcp_opts_tstamp (&new_tc0->rcv_opts))
-           {
-             new_tc0->tsval_recent = new_tc0->rcv_opts.tsval;
-             new_tc0->tsval_recent_age = tcp_time_now ();
-           }
+      /*
+       * 2. check the RST bit
+       */
 
-         if (tcp_opts_wscale (&new_tc0->rcv_opts))
-           new_tc0->snd_wscale = new_tc0->rcv_opts.wscale;
+      if (tcp_rst (tcp0))
+       {
+         /* If ACK is acceptable, signal client that peer is not
+          * willing to accept connection and drop connection*/
+         if (tcp_ack (tcp0))
+           tcp_connection_reset (tc0);
+         error0 = TCP_ERROR_RST_RCVD;
+         goto drop;
+       }
 
-         new_tc0->snd_wnd = clib_net_to_host_u16 (tcp0->window)
-           << new_tc0->snd_wscale;
-         new_tc0->snd_wl1 = seq0;
-         new_tc0->snd_wl2 = ack0;
+      /*
+       * 3. check the security and precedence (skipped)
+       */
 
-         tcp_connection_init_vars (new_tc0);
+      /*
+       * 4. check the SYN bit
+       */
 
-         /* SYN-ACK: See if we can switch to ESTABLISHED state */
-         if (PREDICT_TRUE (tcp_ack (tcp0)))
-           {
-             /* Our SYN is ACKed: we have iss < ack = snd_una */
+      /* No SYN flag. Drop. */
+      if (!tcp_syn (tcp0))
+       {
+         clib_warning ("not synack");
+         error0 = TCP_ERROR_SEGMENT_INVALID;
+         goto drop;
+       }
 
-             /* TODO Dequeue acknowledged segments if we support Fast Open */
-             new_tc0->snd_una = ack0;
-             new_tc0->state = TCP_STATE_ESTABLISHED;
+      /* Parse options */
+      if (tcp_options_parse (tcp0, &tc0->rcv_opts))
+       {
+         clib_warning ("options parse fail");
+         error0 = TCP_ERROR_OPTIONS;
+         goto drop;
+       }
 
-             /* Make sure las is initialized for the wnd computation */
-             new_tc0->rcv_las = new_tc0->rcv_nxt;
+      /* Valid SYN or SYN-ACK. Move connection from half-open pool to
+       * current thread pool. */
+      pool_get (tm->connections[my_thread_index], new_tc0);
+      clib_memcpy (new_tc0, tc0, sizeof (*new_tc0));
+      new_tc0->c_c_index = new_tc0 - tm->connections[my_thread_index];
+      new_tc0->c_thread_index = my_thread_index;
+      new_tc0->rcv_nxt = vnet_buffer (b0)->tcp.seq_end;
+      new_tc0->irs = seq0;
+      new_tc0->timers[TCP_TIMER_ESTABLISH] = TCP_TIMER_HANDLE_INVALID;
+      new_tc0->timers[TCP_TIMER_RETRANSMIT_SYN] = TCP_TIMER_HANDLE_INVALID;
+      new_tc0->sw_if_index = vnet_buffer (b0)->sw_if_index[VLIB_RX];
+
+      /* If this is not the owning thread, wait for syn retransmit to
+       * expire and cleanup then */
+      if (tcp_half_open_connection_cleanup (tc0))
+       tc0->flags |= TCP_CONN_HALF_OPEN_DONE;
+
+      if (tcp_opts_tstamp (&new_tc0->rcv_opts))
+       {
+         new_tc0->tsval_recent = new_tc0->rcv_opts.tsval;
+         new_tc0->tsval_recent_age = tcp_time_now ();
+       }
 
-             /* Notify app that we have connection. If session layer can't
-              * allocate session send reset */
-             if (session_stream_connect_notify (&new_tc0->connection, 0))
-               {
-                 clib_warning ("connect notify fail");
-                 tcp_send_reset_w_pkt (new_tc0, b0, is_ip4);
-                 tcp_connection_cleanup (new_tc0);
-                 goto drop;
-               }
+      if (tcp_opts_wscale (&new_tc0->rcv_opts))
+       new_tc0->snd_wscale = new_tc0->rcv_opts.wscale;
 
-             /* Make sure after data segment processing ACK is sent */
-             new_tc0->flags |= TCP_CONN_SNDACK;
+      new_tc0->snd_wnd = clib_net_to_host_u16 (tcp0->window)
+       << new_tc0->snd_wscale;
+      new_tc0->snd_wl1 = seq0;
+      new_tc0->snd_wl2 = ack0;
 
-             /* Update rtt with the syn-ack sample */
-             tcp_update_rtt (new_tc0, vnet_buffer (b0)->tcp.ack_number);
-             TCP_EVT_DBG (TCP_EVT_SYNACK_RCVD, new_tc0);
-           }
-         /* SYN: Simultaneous open. Change state to SYN-RCVD and send SYN-ACK */
-         else
-           {
-             new_tc0->state = TCP_STATE_SYN_RCVD;
+      tcp_connection_init_vars (new_tc0);
 
-             /* Notify app that we have connection */
-             if (session_stream_connect_notify (&new_tc0->connection, 0))
-               {
-                 tcp_connection_cleanup (new_tc0);
-                 tcp_send_reset_w_pkt (tc0, b0, is_ip4);
-                 TCP_EVT_DBG (TCP_EVT_RST_SENT, tc0);
-                 goto drop;
-               }
+      /* SYN-ACK: See if we can switch to ESTABLISHED state */
+      if (PREDICT_TRUE (tcp_ack (tcp0)))
+       {
+         /* Our SYN is ACKed: we have iss < ack = snd_una */
 
-             tc0->rtt_ts = 0;
-             tcp_init_snd_vars (tc0);
-             tcp_make_synack (new_tc0, b0);
-             next0 = tcp_next_output (is_ip4);
+         /* TODO Dequeue acknowledged segments if we support Fast Open */
+         new_tc0->snd_una = ack0;
+         new_tc0->state = TCP_STATE_ESTABLISHED;
 
-             goto drop;
-           }
+         /* Make sure las is initialized for the wnd computation */
+         new_tc0->rcv_las = new_tc0->rcv_nxt;
 
-         /* Read data, if any */
-         if (PREDICT_FALSE (vnet_buffer (b0)->tcp.data_len))
+         /* Notify app that we have connection. If session layer can't
+          * allocate session send reset */
+         if (session_stream_connect_notify (&new_tc0->connection, 0))
            {
-             clib_warning ("rcvd data in syn-sent");
-             error0 = tcp_segment_rcv (new_tc0, b0, &next0);
-             if (error0 == TCP_ERROR_ACK_OK)
-               error0 = TCP_ERROR_SYN_ACKS_RCVD;
-             tcp_maybe_inc_counter (syn_sent, error0, 1);
-           }
-         else
-           {
-             tcp_make_ack (new_tc0, b0);
-             next0 = tcp_next_output (new_tc0->c_is_ip4);
+             clib_warning ("connect notify fail");
+             tcp_send_reset_w_pkt (new_tc0, b0, is_ip4);
+             tcp_connection_cleanup (new_tc0);
+             goto drop;
            }
 
-       drop:
+         /* Update rtt with the syn-ack sample */
+         tcp_update_rtt (new_tc0, vnet_buffer (b0)->tcp.ack_number);
+         TCP_EVT_DBG (TCP_EVT_SYNACK_RCVD, new_tc0);
+         error0 = TCP_ERROR_SYN_ACKS_RCVD;
+       }
+      /* SYN: Simultaneous open. Change state to SYN-RCVD and send SYN-ACK */
+      else
+       {
+         new_tc0->state = TCP_STATE_SYN_RCVD;
 
-         b0->error = error0 ? node->errors[error0] : 0;
-         if (PREDICT_FALSE
-             ((b0->flags & VLIB_BUFFER_IS_TRACED) && tcp0 != 0))
+         /* Notify app that we have connection */
+         if (session_stream_connect_notify (&new_tc0->connection, 0))
            {
-             t0 = vlib_add_trace (vm, node, b0, sizeof (*t0));
-             clib_memcpy (&t0->tcp_header, tcp0, sizeof (t0->tcp_header));
-             clib_memcpy (&t0->tcp_connection, tc0,
-                          sizeof (t0->tcp_connection));
+             tcp_connection_cleanup (new_tc0);
+             tcp_send_reset_w_pkt (tc0, b0, is_ip4);
+             TCP_EVT_DBG (TCP_EVT_RST_SENT, tc0);
+             goto drop;
            }
 
-         vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next,
-                                          n_left_to_next, bi0, next0);
+         tc0->rtt_ts = 0;
+         tcp_init_snd_vars (tc0);
+         tcp_send_synack (new_tc0);
+         error0 = TCP_ERROR_SYNS_RCVD;
+         goto drop;
        }
 
-      vlib_put_next_frame (vm, node, next_index, n_left_to_next);
+      /* Read data, if any */
+      if (PREDICT_FALSE (vnet_buffer (b0)->tcp.data_len))
+       {
+         clib_warning ("rcvd data in syn-sent");
+         error0 = tcp_segment_rcv (wrk, new_tc0, b0);
+         if (error0 == TCP_ERROR_ACK_OK)
+           error0 = TCP_ERROR_SYN_ACKS_RCVD;
+       }
+      else
+       {
+         tcp_program_ack (wrk, new_tc0);
+       }
+
+    drop:
+
+      tcp_inc_counter (syn_sent, error0, 1);
+      if (PREDICT_FALSE ((b0->flags & VLIB_BUFFER_IS_TRACED) && tcp0 != 0))
+       {
+         t0 = vlib_add_trace (vm, node, b0, sizeof (*t0));
+         clib_memcpy (&t0->tcp_header, tcp0, sizeof (t0->tcp_header));
+         clib_memcpy (&t0->tcp_connection, tc0, sizeof (t0->tcp_connection));
+       }
     }
 
   errors = session_manager_flush_enqueue_events (TRANSPORT_PROTO_TCP,
                                                 my_thread_index);
   tcp_inc_counter (syn_sent, TCP_ERROR_EVENT_FIFO_FULL, errors);
+  vlib_buffer_free (vm, first_buffer, from_frame->n_vectors);
+
   return from_frame->n_vectors;
 }
 
@@ -2615,359 +2553,310 @@ always_inline uword
 tcp46_rcv_process_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
                          vlib_frame_t * from_frame, int is_ip4)
 {
-  u32 n_left_from, next_index, *from, *to_next, n_fins = 0;
-  u32 my_thread_index = vm->thread_index, errors = 0;
-  tcp_worker_ctx_t *wrk = tcp_get_worker (my_thread_index);
+  u32 thread_index = vm->thread_index, errors = 0, *first_buffer;
+  tcp_worker_ctx_t *wrk = tcp_get_worker (thread_index);
+  u32 n_left_from, *from;
 
-  from = vlib_frame_vector_args (from_frame);
+  from = first_buffer = 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;
+      u32 bi0, error0 = TCP_ERROR_NONE;
+      tcp_header_t *tcp0 = 0;
+      tcp_connection_t *tc0;
+      vlib_buffer_t *b0;
+      u8 is_fin0;
 
-      vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);
+      bi0 = from[0];
+      from += 1;
+      n_left_from -= 1;
 
-      while (n_left_from > 0 && n_left_to_next > 0)
+      b0 = vlib_get_buffer (vm, bi0);
+      tc0 = tcp_connection_get (vnet_buffer (b0)->tcp.connection_index,
+                               thread_index);
+      if (PREDICT_FALSE (tc0 == 0))
        {
-         u32 bi0;
-         vlib_buffer_t *b0;
-         tcp_header_t *tcp0 = 0;
-         tcp_connection_t *tc0;
-         u32 next0 = tcp_next_drop (is_ip4), error0 = TCP_ERROR_NONE;
-         u8 is_fin0;
+         error0 = TCP_ERROR_INVALID_CONNECTION;
+         goto drop;
+       }
 
-         bi0 = from[0];
-         to_next[0] = bi0;
-         from += 1;
-         to_next += 1;
-         n_left_from -= 1;
-         n_left_to_next -= 1;
+      tcp0 = tcp_buffer_hdr (b0);
+      is_fin0 = tcp_is_fin (tcp0);
 
-         b0 = vlib_get_buffer (vm, bi0);
-         tc0 = tcp_connection_get (vnet_buffer (b0)->tcp.connection_index,
-                                   my_thread_index);
-         if (PREDICT_FALSE (tc0 == 0))
+      /* SYNs, FINs and data consume sequence numbers */
+      vnet_buffer (b0)->tcp.seq_end = vnet_buffer (b0)->tcp.seq_number
+       + tcp_is_syn (tcp0) + is_fin0 + vnet_buffer (b0)->tcp.data_len;
+
+      if (CLIB_DEBUG)
+       {
+         tcp_connection_t *tmp;
+         tmp = tcp_lookup_connection (tc0->c_fib_index, b0, thread_index,
+                                      is_ip4);
+         if (tmp->state != tc0->state)
            {
-             error0 = TCP_ERROR_INVALID_CONNECTION;
+             clib_warning ("state changed");
              goto drop;
            }
+       }
 
-         tcp0 = tcp_buffer_hdr (b0);
-         is_fin0 = tcp_is_fin (tcp0);
+      /*
+       * Special treatment for CLOSED
+       */
+      if (PREDICT_FALSE (tc0->state == TCP_STATE_CLOSED))
+       {
+         error0 = TCP_ERROR_CONNECTION_CLOSED;
+         goto drop;
+       }
 
-         /* SYNs, FINs and data consume sequence numbers */
-         vnet_buffer (b0)->tcp.seq_end = vnet_buffer (b0)->tcp.seq_number
-           + tcp_is_syn (tcp0) + is_fin0 + vnet_buffer (b0)->tcp.data_len;
+      /*
+       * For all other states (except LISTEN)
+       */
 
-         if (CLIB_DEBUG)
-           {
-             tcp_connection_t *tmp;
-             tmp = tcp_lookup_connection (tc0->c_fib_index, b0,
-                                          my_thread_index, is_ip4);
-             if (tmp->state != tc0->state)
-               {
-                 clib_warning ("state changed");
-                 goto drop;
-               }
-           }
+      /* 1-4: check SEQ, RST, SYN */
+      if (PREDICT_FALSE (tcp_segment_validate (wrk, tc0, b0, tcp0, &error0)))
+       goto drop;
 
+      /* 5: check the ACK field  */
+      switch (tc0->state)
+       {
+       case TCP_STATE_SYN_RCVD:
          /*
-          * Special treatment for CLOSED
+          * If the segment acknowledgment is not acceptable, form a
+          * reset segment,
+          *  <SEQ=SEG.ACK><CTL=RST>
+          * and send it.
           */
-         if (PREDICT_FALSE (tc0->state == TCP_STATE_CLOSED))
+         if (!tcp_rcv_ack_is_acceptable (tc0, b0))
            {
-             error0 = TCP_ERROR_CONNECTION_CLOSED;
+             TCP_DBG ("connection not accepted");
+             tcp_send_reset_w_pkt (tc0, b0, is_ip4);
+             error0 = TCP_ERROR_ACK_INVALID;
              goto drop;
            }
 
-         /*
-          * For all other states (except LISTEN)
-          */
+         /* Update rtt and rto */
+         tcp_update_rtt (tc0, vnet_buffer (b0)->tcp.ack_number);
+
+         /* Switch state to ESTABLISHED */
+         tc0->state = TCP_STATE_ESTABLISHED;
+         TCP_EVT_DBG (TCP_EVT_STATE_CHANGE, tc0);
+
+         /* Initialize session variables */
+         tc0->snd_una = vnet_buffer (b0)->tcp.ack_number;
+         tc0->snd_wnd = clib_net_to_host_u16 (tcp0->window)
+           << tc0->rcv_opts.wscale;
+         tc0->snd_wl1 = vnet_buffer (b0)->tcp.seq_number;
+         tc0->snd_wl2 = vnet_buffer (b0)->tcp.ack_number;
+
+         /* Reset SYN-ACK retransmit and SYN_RCV establish timers */
+         tcp_retransmit_timer_reset (tc0);
+         tcp_timer_reset (tc0, TCP_TIMER_ESTABLISH);
+         stream_session_accept_notify (&tc0->connection);
+         error0 = TCP_ERROR_ACK_OK;
+         break;
+       case TCP_STATE_ESTABLISHED:
+         /* We can get packets in established state here because they
+          * were enqueued before state change */
+         if (tcp_rcv_ack (wrk, tc0, b0, tcp0, &error0))
+           goto drop;
+
+         break;
+       case TCP_STATE_FIN_WAIT_1:
+         /* In addition to the processing for the ESTABLISHED state, if
+          * our FIN is now acknowledged then enter FIN-WAIT-2 and
+          * continue processing in that state. */
+         if (tcp_rcv_ack (wrk, tc0, b0, tcp0, &error0))
+           goto drop;
 
-         /* 1-4: check SEQ, RST, SYN */
-         if (PREDICT_FALSE (tcp_segment_validate (vm, tc0, b0, tcp0,
-                                                  &next0, &error0)))
+         /* Still have to send the FIN */
+         if (tc0->flags & TCP_CONN_FINPNDG)
            {
-             tcp_maybe_inc_counter (rcv_process, error0, 1);
-             goto drop;
+             /* TX fifo finally drained */
+             if (!session_tx_fifo_max_dequeue (&tc0->connection))
+               tcp_send_fin (tc0);
            }
-
-         /* 5: check the ACK field  */
-         switch (tc0->state)
+         /* If FIN is ACKed */
+         else if (tc0->snd_una == tc0->snd_una_max)
            {
-           case TCP_STATE_SYN_RCVD:
-             /*
-              * If the segment acknowledgment is not acceptable, form a
-              * reset segment,
-              *  <SEQ=SEG.ACK><CTL=RST>
-              * and send it.
-              */
-             if (!tcp_rcv_ack_is_acceptable (tc0, b0))
-               {
-                 TCP_DBG ("connection not accepted");
-                 tcp_send_reset_w_pkt (tc0, b0, is_ip4);
-                 error0 = TCP_ERROR_ACK_INVALID;
-                 goto drop;
-               }
-
-             /* Update rtt and rto */
-             tcp_update_rtt (tc0, vnet_buffer (b0)->tcp.ack_number);
-
-             /* Switch state to ESTABLISHED */
-             tc0->state = TCP_STATE_ESTABLISHED;
+             tc0->state = TCP_STATE_FIN_WAIT_2;
              TCP_EVT_DBG (TCP_EVT_STATE_CHANGE, tc0);
 
-             /* Initialize session variables */
-             tc0->snd_una = vnet_buffer (b0)->tcp.ack_number;
-             tc0->snd_wnd = clib_net_to_host_u16 (tcp0->window)
-               << tc0->rcv_opts.wscale;
-             tc0->snd_wl1 = vnet_buffer (b0)->tcp.seq_number;
-             tc0->snd_wl2 = vnet_buffer (b0)->tcp.ack_number;
-
-             /* Reset SYN-ACK retransmit and SYN_RCV establish timers */
-             tcp_retransmit_timer_reset (tc0);
-             tcp_timer_reset (tc0, TCP_TIMER_ESTABLISH);
-             stream_session_accept_notify (&tc0->connection);
-             error0 = TCP_ERROR_ACK_OK;
-             break;
-           case TCP_STATE_ESTABLISHED:
-             /* We can get packets in established state here because they
-              * were enqueued before state change */
-             if (tcp_rcv_ack (wrk, tc0, b0, tcp0, &next0, &error0))
-               {
-                 tcp_maybe_inc_counter (rcv_process, error0, 1);
-                 goto drop;
-               }
-
-             break;
-           case TCP_STATE_FIN_WAIT_1:
-             /* In addition to the processing for the ESTABLISHED state, if
-              * our FIN is now acknowledged then enter FIN-WAIT-2 and
-              * continue processing in that state. */
-             if (tcp_rcv_ack (wrk, tc0, b0, tcp0, &next0, &error0))
-               {
-                 tcp_maybe_inc_counter (rcv_process, error0, 1);
-                 goto drop;
-               }
+             /* Stop all retransmit timers because we have nothing more
+              * to send. Enable waitclose though because we're willing to
+              * wait for peer's FIN but not indefinitely. */
+             tcp_connection_timers_reset (tc0);
+             tcp_timer_update (tc0, TCP_TIMER_WAITCLOSE, TCP_2MSL_TIME);
+           }
+         break;
+       case TCP_STATE_FIN_WAIT_2:
+         /* In addition to the processing for the ESTABLISHED state, if
+          * the retransmission queue is empty, the user's CLOSE can be
+          * acknowledged ("ok") but do not delete the TCB. */
+         if (tcp_rcv_ack (wrk, tc0, b0, tcp0, &error0))
+           goto drop;
+         break;
+       case TCP_STATE_CLOSE_WAIT:
+         /* Do the same processing as for the ESTABLISHED state. */
+         if (tcp_rcv_ack (wrk, tc0, b0, tcp0, &error0))
+           goto drop;
 
-             /* Still have to send the FIN */
-             if (tc0->flags & TCP_CONN_FINPNDG)
-               {
-                 /* TX fifo finally drained */
-                 if (!session_tx_fifo_max_dequeue (&tc0->connection))
-                   tcp_send_fin (tc0);
-               }
-             /* If FIN is ACKed */
-             else if (tc0->snd_una == tc0->snd_una_max)
+         if (tc0->flags & TCP_CONN_FINPNDG)
+           {
+             /* TX fifo finally drained */
+             if (!session_tx_fifo_max_dequeue (&tc0->connection))
                {
-                 tc0->state = TCP_STATE_FIN_WAIT_2;
-                 TCP_EVT_DBG (TCP_EVT_STATE_CHANGE, tc0);
-
-                 /* Stop all retransmit timers because we have nothing more
-                  * to send. Enable waitclose though because we're willing to
-                  * wait for peer's FIN but not indefinitely. */
+                 tcp_send_fin (tc0);
                  tcp_connection_timers_reset (tc0);
+                 tc0->state = TCP_STATE_LAST_ACK;
                  tcp_timer_update (tc0, TCP_TIMER_WAITCLOSE, TCP_2MSL_TIME);
                }
-             break;
-           case TCP_STATE_FIN_WAIT_2:
-             /* In addition to the processing for the ESTABLISHED state, if
-              * the retransmission queue is empty, the user's CLOSE can be
-              * acknowledged ("ok") but do not delete the TCB. */
-             if (tcp_rcv_ack (wrk, tc0, b0, tcp0, &next0, &error0))
-               {
-                 tcp_maybe_inc_counter (rcv_process, error0, 1);
-                 goto drop;
-               }
-             break;
-           case TCP_STATE_CLOSE_WAIT:
-             /* Do the same processing as for the ESTABLISHED state. */
-             if (tcp_rcv_ack (wrk, tc0, b0, tcp0, &next0, &error0))
-               {
-                 tcp_maybe_inc_counter (rcv_process, error0, 1);
-                 goto drop;
-               }
-             if (tc0->flags & TCP_CONN_FINPNDG)
-               {
-                 /* TX fifo finally drained */
-                 if (!session_tx_fifo_max_dequeue (&tc0->connection))
-                   {
-                     tcp_send_fin (tc0);
-                     tcp_connection_timers_reset (tc0);
-                     tc0->state = TCP_STATE_LAST_ACK;
-                     tcp_timer_update (tc0, TCP_TIMER_WAITCLOSE,
-                                       TCP_2MSL_TIME);
-                   }
-               }
-             break;
-           case TCP_STATE_CLOSING:
-             /* In addition to the processing for the ESTABLISHED state, if
-              * the ACK acknowledges our FIN then enter the TIME-WAIT state,
-              * otherwise ignore the segment. */
-             if (tcp_rcv_ack (wrk, tc0, b0, tcp0, &next0, &error0))
-               {
-                 tcp_maybe_inc_counter (rcv_process, error0, 1);
-                 goto drop;
-               }
-
-             tc0->state = TCP_STATE_TIME_WAIT;
-             TCP_EVT_DBG (TCP_EVT_STATE_CHANGE, tc0);
-             tcp_timer_update (tc0, TCP_TIMER_WAITCLOSE, TCP_TIMEWAIT_TIME);
-             goto drop;
-
-             break;
-           case TCP_STATE_LAST_ACK:
-             /* The only thing that [should] arrive in this state is an
-              * acknowledgment of our FIN. If our FIN is now acknowledged,
-              * delete the TCB, enter the CLOSED state, and return. */
-
-             if (!tcp_rcv_ack_is_acceptable (tc0, b0))
-               {
-                 error0 = TCP_ERROR_ACK_INVALID;
-                 goto drop;
-               }
-             error0 = TCP_ERROR_ACK_OK;
-             tc0->snd_una = vnet_buffer (b0)->tcp.ack_number;
-             /* Apparently our ACK for the peer's FIN was lost */
-             if (is_fin0 && tc0->snd_una != tc0->snd_una_max)
-               {
-                 tcp_send_fin (tc0);
-                 goto drop;
-               }
+           }
+         break;
+       case TCP_STATE_CLOSING:
+         /* In addition to the processing for the ESTABLISHED state, if
+          * the ACK acknowledges our FIN then enter the TIME-WAIT state,
+          * otherwise ignore the segment. */
+         if (tcp_rcv_ack (wrk, tc0, b0, tcp0, &error0))
+           goto drop;
 
-             tc0->state = TCP_STATE_CLOSED;
-             TCP_EVT_DBG (TCP_EVT_STATE_CHANGE, tc0);
+         tc0->state = TCP_STATE_TIME_WAIT;
+         TCP_EVT_DBG (TCP_EVT_STATE_CHANGE, tc0);
+         tcp_timer_update (tc0, TCP_TIMER_WAITCLOSE, TCP_TIMEWAIT_TIME);
+         goto drop;
 
-             /* Don't free the connection from the data path since
-              * we can't ensure that we have no packets already enqueued
-              * to output. Rely instead on the waitclose timer */
-             tcp_connection_timers_reset (tc0);
-             tcp_timer_update (tc0, TCP_TIMER_WAITCLOSE, 1);
+         break;
+       case TCP_STATE_LAST_ACK:
+         /* The only thing that [should] arrive in this state is an
+          * acknowledgment of our FIN. If our FIN is now acknowledged,
+          * delete the TCB, enter the CLOSED state, and return. */
 
+         if (!tcp_rcv_ack_is_acceptable (tc0, b0))
+           {
+             error0 = TCP_ERROR_ACK_INVALID;
+             goto drop;
+           }
+         error0 = TCP_ERROR_ACK_OK;
+         tc0->snd_una = vnet_buffer (b0)->tcp.ack_number;
+         /* Apparently our ACK for the peer's FIN was lost */
+         if (is_fin0 && tc0->snd_una != tc0->snd_una_max)
+           {
+             tcp_send_fin (tc0);
              goto drop;
+           }
 
-             break;
-           case TCP_STATE_TIME_WAIT:
-             /* The only thing that can arrive in this state is a
-              * retransmission of the remote FIN. Acknowledge it, and restart
-              * the 2 MSL timeout. */
+         tc0->state = TCP_STATE_CLOSED;
+         TCP_EVT_DBG (TCP_EVT_STATE_CHANGE, tc0);
 
-             if (tcp_rcv_ack (wrk, tc0, b0, tcp0, &next0, &error0))
-               {
-                 tcp_maybe_inc_counter (rcv_process, error0, 1);
-                 goto drop;
-               }
+         /* Don't free the connection from the data path since
+          * we can't ensure that we have no packets already enqueued
+          * to output. Rely instead on the waitclose timer */
+         tcp_connection_timers_reset (tc0);
+         tcp_timer_update (tc0, TCP_TIMER_WAITCLOSE, 1);
 
-             tcp_make_ack (tc0, b0);
-             next0 = tcp_next_output (is_ip4);
-             tcp_timer_update (tc0, TCP_TIMER_WAITCLOSE, TCP_TIMEWAIT_TIME);
-             goto drop;
+         goto drop;
 
-             break;
-           default:
-             ASSERT (0);
-           }
+         break;
+       case TCP_STATE_TIME_WAIT:
+         /* The only thing that can arrive in this state is a
+          * retransmission of the remote FIN. Acknowledge it, and restart
+          * the 2 MSL timeout. */
 
-         /* 6: check the URG bit TODO */
+         if (tcp_rcv_ack (wrk, tc0, b0, tcp0, &error0))
+           goto drop;
 
-         /* 7: process the segment text */
-         switch (tc0->state)
-           {
-           case TCP_STATE_ESTABLISHED:
-           case TCP_STATE_FIN_WAIT_1:
-           case TCP_STATE_FIN_WAIT_2:
-             if (vnet_buffer (b0)->tcp.data_len)
-               {
-                 error0 = tcp_segment_rcv (tc0, b0, &next0);
-                 tcp_maybe_inc_counter (rcv_process, error0, 1);
-               }
-             else if (is_fin0)
-               tc0->rcv_nxt += 1;
-             break;
-           case TCP_STATE_CLOSE_WAIT:
-           case TCP_STATE_CLOSING:
-           case TCP_STATE_LAST_ACK:
-           case TCP_STATE_TIME_WAIT:
-             /* This should not occur, since a FIN has been received from the
-              * remote side.  Ignore the segment text. */
-             break;
-           }
+         tcp_program_ack (wrk, tc0);
+         tcp_timer_update (tc0, TCP_TIMER_WAITCLOSE, TCP_TIMEWAIT_TIME);
+         goto drop;
 
-         /* 8: check the FIN bit */
-         if (!is_fin0)
-           goto drop;
+         break;
+       default:
+         ASSERT (0);
+       }
 
-         switch (tc0->state)
-           {
-           case TCP_STATE_ESTABLISHED:
-           case TCP_STATE_SYN_RCVD:
-             /* Send FIN-ACK notify app and enter CLOSE-WAIT */
-             tcp_connection_timers_reset (tc0);
-             tcp_make_fin (tc0, b0);
-             tc0->snd_nxt += 1;
-             tc0->snd_una_max = tc0->snd_nxt;
-             tcp_retransmit_timer_set (tc0);
-             next0 = tcp_next_output (tc0->c_is_ip4);
-             stream_session_disconnect_notify (&tc0->connection);
-             tc0->state = TCP_STATE_CLOSE_WAIT;
-             TCP_EVT_DBG (TCP_EVT_STATE_CHANGE, tc0);
-             break;
-           case TCP_STATE_CLOSE_WAIT:
-           case TCP_STATE_CLOSING:
-           case TCP_STATE_LAST_ACK:
-             /* move along .. */
-             break;
-           case TCP_STATE_FIN_WAIT_1:
-             tc0->state = TCP_STATE_CLOSING;
-             tcp_make_ack (tc0, b0);
-             next0 = tcp_next_output (is_ip4);
-             TCP_EVT_DBG (TCP_EVT_STATE_CHANGE, tc0);
-             /* Wait for ACK but not forever */
-             tcp_timer_update (tc0, TCP_TIMER_WAITCLOSE, TCP_2MSL_TIME);
-             break;
-           case TCP_STATE_FIN_WAIT_2:
-             /* Got FIN, send ACK! Be more aggressive with resource cleanup */
-             tc0->state = TCP_STATE_TIME_WAIT;
-             tcp_connection_timers_reset (tc0);
-             tcp_timer_update (tc0, TCP_TIMER_WAITCLOSE, TCP_TIMEWAIT_TIME);
-             tcp_make_ack (tc0, b0);
-             next0 = tcp_next_output (is_ip4);
-             TCP_EVT_DBG (TCP_EVT_STATE_CHANGE, tc0);
-             break;
-           case TCP_STATE_TIME_WAIT:
-             /* Remain in the TIME-WAIT state. Restart the time-wait
-              * timeout.
-              */
-             tcp_timer_update (tc0, TCP_TIMER_WAITCLOSE, TCP_TIMEWAIT_TIME);
-             break;
-           }
-         TCP_EVT_DBG (TCP_EVT_FIN_RCVD, tc0);
-         n_fins += 1;
+      /* 6: check the URG bit TODO */
 
-       drop:
-         b0->error = error0 ? node->errors[error0] : 0;
+      /* 7: process the segment text */
+      switch (tc0->state)
+       {
+       case TCP_STATE_ESTABLISHED:
+       case TCP_STATE_FIN_WAIT_1:
+       case TCP_STATE_FIN_WAIT_2:
+         if (vnet_buffer (b0)->tcp.data_len)
+           error0 = tcp_segment_rcv (wrk, tc0, b0);
+         else if (is_fin0)
+           tc0->rcv_nxt += 1;
+         break;
+       case TCP_STATE_CLOSE_WAIT:
+       case TCP_STATE_CLOSING:
+       case TCP_STATE_LAST_ACK:
+       case TCP_STATE_TIME_WAIT:
+         /* This should not occur, since a FIN has been received from the
+          * remote side.  Ignore the segment text. */
+         break;
+       }
 
-         if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED))
-           {
-             tcp_rx_trace_t *t0 =
-               vlib_add_trace (vm, node, b0, sizeof (*t0));
-             tcp_set_rx_trace_data (t0, tc0, tcp0, b0, is_ip4);
-           }
+      /* 8: check the FIN bit */
+      if (!is_fin0)
+       goto drop;
 
-         vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next,
-                                          n_left_to_next, bi0, next0);
+      switch (tc0->state)
+       {
+       case TCP_STATE_ESTABLISHED:
+       case TCP_STATE_SYN_RCVD:
+         /* Send FIN-ACK notify app and enter CLOSE-WAIT */
+         tcp_connection_timers_reset (tc0);
+         tcp_send_fin (tc0);
+         stream_session_disconnect_notify (&tc0->connection);
+         tc0->state = TCP_STATE_CLOSE_WAIT;
+         TCP_EVT_DBG (TCP_EVT_STATE_CHANGE, tc0);
+         break;
+       case TCP_STATE_CLOSE_WAIT:
+       case TCP_STATE_CLOSING:
+       case TCP_STATE_LAST_ACK:
+         /* move along .. */
+         break;
+       case TCP_STATE_FIN_WAIT_1:
+         tc0->state = TCP_STATE_CLOSING;
+         tcp_program_ack (wrk, tc0);
+         TCP_EVT_DBG (TCP_EVT_STATE_CHANGE, tc0);
+         /* Wait for ACK but not forever */
+         tcp_timer_update (tc0, TCP_TIMER_WAITCLOSE, TCP_2MSL_TIME);
+         break;
+       case TCP_STATE_FIN_WAIT_2:
+         /* Got FIN, send ACK! Be more aggressive with resource cleanup */
+         tc0->state = TCP_STATE_TIME_WAIT;
+         tcp_connection_timers_reset (tc0);
+         tcp_timer_update (tc0, TCP_TIMER_WAITCLOSE, TCP_TIMEWAIT_TIME);
+         tcp_program_ack (wrk, tc0);
+         TCP_EVT_DBG (TCP_EVT_STATE_CHANGE, tc0);
+         break;
+       case TCP_STATE_TIME_WAIT:
+         /* Remain in the TIME-WAIT state. Restart the time-wait
+          * timeout.
+          */
+         tcp_timer_update (tc0, TCP_TIMER_WAITCLOSE, TCP_TIMEWAIT_TIME);
+         break;
        }
+      error0 = TCP_ERROR_FIN_RCVD;
+      TCP_EVT_DBG (TCP_EVT_FIN_RCVD, tc0);
+
+    drop:
 
-      vlib_put_next_frame (vm, node, next_index, n_left_to_next);
+      tcp_inc_counter (rcv_process, error0, 1);
+      if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED))
+       {
+         tcp_rx_trace_t *t0 = vlib_add_trace (vm, node, b0, sizeof (*t0));
+         tcp_set_rx_trace_data (t0, tc0, tcp0, b0, is_ip4);
+       }
     }
 
   errors = session_manager_flush_enqueue_events (TRANSPORT_PROTO_TCP,
-                                                my_thread_index);
+                                                thread_index);
   tcp_inc_counter (rcv_process, TCP_ERROR_EVENT_FIFO_FULL, errors);
-  tcp_inc_counter (rcv_process, TCP_ERROR_FIN_RCVD, n_fins);
   tcp_handle_postponed_dequeues (wrk);
+  vlib_buffer_free (vm, first_buffer, from_frame->n_vectors);
+
   return from_frame->n_vectors;
 }
 
@@ -3039,162 +2928,146 @@ always_inline uword
 tcp46_listen_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
                     vlib_frame_t * from_frame, int is_ip4)
 {
-  u32 n_left_from, next_index, *from, *to_next, n_syns = 0;
+  u32 n_left_from, *from, n_syns = 0, *first_buffer;
   u32 my_thread_index = vm->thread_index;
 
-  from = vlib_frame_vector_args (from_frame);
+  from = first_buffer = 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 > 0 && n_left_to_next > 0)
-       {
-         u32 bi0;
-         vlib_buffer_t *b0;
-         tcp_rx_trace_t *t0;
-         tcp_header_t *th0 = 0;
-         tcp_connection_t *lc0;
-         ip4_header_t *ip40;
-         ip6_header_t *ip60;
-         tcp_connection_t *child0;
-         u32 error0 = TCP_ERROR_NONE, next0 = tcp_next_drop (is_ip4);
-
-         bi0 = from[0];
-         to_next[0] = bi0;
-         from += 1;
-         to_next += 1;
-         n_left_from -= 1;
-         n_left_to_next -= 1;
+      u32 bi0;
+      vlib_buffer_t *b0;
+      tcp_rx_trace_t *t0;
+      tcp_header_t *th0 = 0;
+      tcp_connection_t *lc0;
+      ip4_header_t *ip40;
+      ip6_header_t *ip60;
+      tcp_connection_t *child0;
+      u32 error0 = TCP_ERROR_NONE;
 
-         b0 = vlib_get_buffer (vm, bi0);
-         lc0 = tcp_listener_get (vnet_buffer (b0)->tcp.connection_index);
+      bi0 = from[0];
+      from += 1;
+      n_left_from -= 1;
 
-         if (is_ip4)
-           {
-             ip40 = vlib_buffer_get_current (b0);
-             th0 = ip4_next_header (ip40);
-           }
-         else
-           {
-             ip60 = vlib_buffer_get_current (b0);
-             th0 = ip6_next_header (ip60);
-           }
+      b0 = vlib_get_buffer (vm, bi0);
+      lc0 = tcp_listener_get (vnet_buffer (b0)->tcp.connection_index);
 
-         /* Create child session. For syn-flood protection use filter */
+      if (is_ip4)
+       {
+         ip40 = vlib_buffer_get_current (b0);
+         th0 = ip4_next_header (ip40);
+       }
+      else
+       {
+         ip60 = vlib_buffer_get_current (b0);
+         th0 = ip6_next_header (ip60);
+       }
 
-         /* 1. first check for an RST: handled in dispatch */
-         /* if (tcp_rst (th0))
-            goto drop; */
+      /* Create child session. For syn-flood protection use filter */
 
-         /* 2. second check for an ACK: handled in dispatch */
-         /* if (tcp_ack (th0))
-            {
-            tcp_send_reset (b0, is_ip4);
-            goto drop;
-            } */
+      /* 1. first check for an RST: handled in dispatch */
+      /* if (tcp_rst (th0))
+         goto drop;
+       */
 
-         /* 3. check for a SYN (did that already) */
+      /* 2. second check for an ACK: handled in dispatch */
+      /* if (tcp_ack (th0))
+         {
+         tcp_send_reset (b0, is_ip4);
+         goto drop;
+         }
+       */
 
-         /* Make sure connection wasn't just created */
-         child0 = tcp_lookup_connection (lc0->c_fib_index, b0,
-                                         my_thread_index, is_ip4);
-         if (PREDICT_FALSE (child0->state != TCP_STATE_LISTEN))
-           {
-             error0 = TCP_ERROR_CREATE_EXISTS;
-             goto drop;
-           }
+      /* 3. check for a SYN (did that already) */
 
-         /* Create child session and send SYN-ACK */
-         child0 = tcp_connection_new (my_thread_index);
-         child0->c_lcl_port = th0->dst_port;
-         child0->c_rmt_port = th0->src_port;
-         child0->c_is_ip4 = is_ip4;
-         child0->state = TCP_STATE_SYN_RCVD;
-         child0->c_fib_index = lc0->c_fib_index;
+      /* Make sure connection wasn't just created */
+      child0 = tcp_lookup_connection (lc0->c_fib_index, b0, my_thread_index,
+                                     is_ip4);
+      if (PREDICT_FALSE (child0->state != TCP_STATE_LISTEN))
+       {
+         error0 = TCP_ERROR_CREATE_EXISTS;
+         goto drop;
+       }
 
-         if (is_ip4)
-           {
-             child0->c_lcl_ip4.as_u32 = ip40->dst_address.as_u32;
-             child0->c_rmt_ip4.as_u32 = ip40->src_address.as_u32;
-           }
-         else
-           {
-             clib_memcpy (&child0->c_lcl_ip6, &ip60->dst_address,
-                          sizeof (ip6_address_t));
-             clib_memcpy (&child0->c_rmt_ip6, &ip60->src_address,
-                          sizeof (ip6_address_t));
-           }
+      /* Create child session and send SYN-ACK */
+      child0 = tcp_connection_new (my_thread_index);
+      child0->c_lcl_port = th0->dst_port;
+      child0->c_rmt_port = th0->src_port;
+      child0->c_is_ip4 = is_ip4;
+      child0->state = TCP_STATE_SYN_RCVD;
+      child0->c_fib_index = lc0->c_fib_index;
 
-         if (tcp_options_parse (th0, &child0->rcv_opts))
-           {
-             clib_warning ("options parse fail");
-             goto drop;
-           }
+      if (is_ip4)
+       {
+         child0->c_lcl_ip4.as_u32 = ip40->dst_address.as_u32;
+         child0->c_rmt_ip4.as_u32 = ip40->src_address.as_u32;
+       }
+      else
+       {
+         clib_memcpy (&child0->c_lcl_ip6, &ip60->dst_address,
+                      sizeof (ip6_address_t));
+         clib_memcpy (&child0->c_rmt_ip6, &ip60->src_address,
+                      sizeof (ip6_address_t));
+       }
 
-         child0->irs = vnet_buffer (b0)->tcp.seq_number;
-         child0->rcv_nxt = vnet_buffer (b0)->tcp.seq_number + 1;
-         child0->rcv_las = child0->rcv_nxt;
-         child0->sw_if_index = vnet_buffer (b0)->sw_if_index[VLIB_RX];
+      if (tcp_options_parse (th0, &child0->rcv_opts))
+       {
+         clib_warning ("options parse fail");
+         goto drop;
+       }
 
-         /* RFC1323: TSval timestamps sent on {SYN} and {SYN,ACK}
-          * segments are used to initialize PAWS. */
-         if (tcp_opts_tstamp (&child0->rcv_opts))
-           {
-             child0->tsval_recent = child0->rcv_opts.tsval;
-             child0->tsval_recent_age = tcp_time_now ();
-           }
+      child0->irs = vnet_buffer (b0)->tcp.seq_number;
+      child0->rcv_nxt = vnet_buffer (b0)->tcp.seq_number + 1;
+      child0->rcv_las = child0->rcv_nxt;
+      child0->sw_if_index = vnet_buffer (b0)->sw_if_index[VLIB_RX];
 
-         if (tcp_opts_wscale (&child0->rcv_opts))
-           child0->snd_wscale = child0->rcv_opts.wscale;
+      /* RFC1323: TSval timestamps sent on {SYN} and {SYN,ACK}
+       * segments are used to initialize PAWS. */
+      if (tcp_opts_tstamp (&child0->rcv_opts))
+       {
+         child0->tsval_recent = child0->rcv_opts.tsval;
+         child0->tsval_recent_age = tcp_time_now ();
+       }
 
-         child0->snd_wnd = clib_net_to_host_u16 (th0->window)
-           << child0->snd_wscale;
-         child0->snd_wl1 = vnet_buffer (b0)->tcp.seq_number;
-         child0->snd_wl2 = vnet_buffer (b0)->tcp.ack_number;
+      if (tcp_opts_wscale (&child0->rcv_opts))
+       child0->snd_wscale = child0->rcv_opts.wscale;
 
-         tcp_connection_init_vars (child0);
-         TCP_EVT_DBG (TCP_EVT_SYN_RCVD, child0, 1);
+      child0->snd_wnd = clib_net_to_host_u16 (th0->window)
+       << child0->snd_wscale;
+      child0->snd_wl1 = vnet_buffer (b0)->tcp.seq_number;
+      child0->snd_wl2 = vnet_buffer (b0)->tcp.ack_number;
 
-         if (stream_session_accept (&child0->connection, lc0->c_s_index,
-                                    0 /* notify */ ))
-           {
-             clib_warning ("session accept fail");
-             tcp_connection_cleanup (child0);
-             error0 = TCP_ERROR_CREATE_SESSION_FAIL;
-             goto drop;
-           }
+      tcp_connection_init_vars (child0);
+      TCP_EVT_DBG (TCP_EVT_SYN_RCVD, child0, 1);
 
-         /* Reuse buffer to make syn-ack and send */
-         tcp_make_synack (child0, b0);
-         next0 = tcp_next_output (is_ip4);
-         tcp_timer_set (child0, TCP_TIMER_ESTABLISH, TCP_SYN_RCVD_TIME);
+      if (stream_session_accept (&child0->connection, lc0->c_s_index,
+                                0 /* notify */ ))
+       {
+         clib_warning ("session accept fail");
+         tcp_connection_cleanup (child0);
+         error0 = TCP_ERROR_CREATE_SESSION_FAIL;
+         goto drop;
+       }
 
-       drop:
-         if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED))
-           {
-             t0 = vlib_add_trace (vm, node, b0, sizeof (*t0));
-             clib_memcpy (&t0->tcp_header, th0, sizeof (t0->tcp_header));
-             clib_memcpy (&t0->tcp_connection, lc0,
-                          sizeof (t0->tcp_connection));
-           }
+      tcp_send_synack (child0);
+      tcp_timer_set (child0, TCP_TIMER_ESTABLISH, TCP_SYN_RCVD_TIME);
 
-         n_syns += (error0 == TCP_ERROR_NONE);
-         b0->error = node->errors[error0];
+    drop:
 
-         vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next,
-                                          n_left_to_next, bi0, next0);
+      if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED))
+       {
+         t0 = vlib_add_trace (vm, node, b0, sizeof (*t0));
+         clib_memcpy (&t0->tcp_header, th0, sizeof (t0->tcp_header));
+         clib_memcpy (&t0->tcp_connection, lc0, sizeof (t0->tcp_connection));
        }
 
-      vlib_put_next_frame (vm, node, next_index, n_left_to_next);
+      n_syns += (error0 == TCP_ERROR_NONE);
     }
 
   tcp_inc_counter (listen, TCP_ERROR_SYNS_RCVD, n_syns);
+  vlib_buffer_free (vm, first_buffer, from_frame->n_vectors);
+
   return from_frame->n_vectors;
 }
 
index 29a919b..089f85a 100644 (file)
@@ -559,7 +559,6 @@ tcp_make_ack (tcp_connection_t * tc, vlib_buffer_t * b)
   tcp_reuse_buffer (vm, b);
   tcp_make_ack_i (tc, b, TCP_STATE_ESTABLISHED, TCP_FLAG_ACK);
   TCP_EVT_DBG (TCP_EVT_ACK_SENT, tc);
-  vnet_buffer (b)->tcp.flags = TCP_BUF_FLAG_ACK;
   tc->rcv_las = tc->rcv_nxt;
 }
 
@@ -631,7 +630,6 @@ tcp_make_synack (tcp_connection_t * tc, vlib_buffer_t * b)
   tcp_options_write ((u8 *) (th + 1), snd_opts);
 
   vnet_buffer (b)->tcp.connection_index = tc->c_c_index;
-  vnet_buffer (b)->tcp.flags = TCP_BUF_FLAG_ACK;
 
   /* Init retransmit timer. Use update instead of set because of
    * retransmissions */
@@ -1011,6 +1009,23 @@ tcp_send_syn (tcp_connection_t * tc)
   TCP_EVT_DBG (TCP_EVT_SYN_SENT, tc);
 }
 
+void
+tcp_send_synack (tcp_connection_t * tc)
+{
+  tcp_worker_ctx_t *wrk = tcp_get_worker (tc->c_thread_index);
+  vlib_main_t *vm = wrk->vm;
+  vlib_buffer_t *b;
+  u32 bi;
+
+  /* Get buffer */
+  if (PREDICT_FALSE (tcp_get_free_buffer_index (wrk, &bi)))
+    return;
+
+  b = vlib_get_buffer (vm, bi);
+  tcp_make_synack (tc, b);
+  tcp_enqueue_to_output (wrk, b, bi, tc->c_is_ip4);
+}
+
 /**
  * Flush tx frame populated by retransmits and timer pops
  */
@@ -1223,6 +1238,56 @@ tcp_send_ack (tcp_connection_t * tc)
   tcp_enqueue_to_output (wrk, b, bi, tc->c_is_ip4);
 }
 
+void
+tcp_program_ack (tcp_worker_ctx_t * wrk, tcp_connection_t * tc)
+{
+  if (!(tc->flags & TCP_CONN_SNDACK))
+    {
+      vec_add1 (wrk->pending_acks, tc->c_c_index);
+      tc->flags |= TCP_CONN_SNDACK;
+    }
+}
+
+void
+tcp_program_dupack (tcp_worker_ctx_t * wrk, tcp_connection_t * tc)
+{
+  if (!(tc->flags & TCP_CONN_SNDACK))
+    {
+      vec_add1 (wrk->pending_acks, tc->c_c_index);
+      tc->flags |= TCP_CONN_SNDACK;
+    }
+  if (tc->pending_dupacks < 255)
+    tc->pending_dupacks += 1;
+}
+
+void
+tcp_send_acks (tcp_worker_ctx_t * wrk)
+{
+  u32 thread_index, *pending_acks;
+  tcp_connection_t *tc;
+  int i, j, n_acks;
+
+  if (!vec_len (wrk->pending_acks))
+    return;
+
+  thread_index = wrk->vm->thread_index;
+  pending_acks = wrk->pending_acks;
+  for (i = 0; i < vec_len (pending_acks); i++)
+    {
+      tc = tcp_connection_get (pending_acks[i], thread_index);
+      tc->flags &= ~TCP_CONN_SNDACK;
+      n_acks = clib_max (1, tc->pending_dupacks);
+      /* If we're supposed to send dupacks but have no ooo data
+       * send only one ack */
+      if (tc->pending_dupacks && !vec_len (tc->snd_sacks))
+       n_acks = 1;
+      for (j = 0; j < n_acks; j++)
+       tcp_send_ack (tc);
+      tc->pending_dupacks = 0;
+    }
+  _vec_len (wrk->pending_acks) = 0;
+}
+
 /**
  * Delayed ack timer handler
  *
@@ -1944,13 +2009,6 @@ tcp_fast_retransmit (tcp_worker_ctx_t * wrk, tcp_connection_t * tc,
     return tcp_fast_retransmit_no_sack (wrk, tc, burst_size);
 }
 
-static u32
-tcp_session_has_ooo_data (tcp_connection_t * tc)
-{
-  stream_session_t *s = session_get (tc->c_s_index, tc->c_thread_index);
-  return svm_fifo_has_ooo_data (s->server_rx_fifo);
-}
-
 static void
 tcp_output_handle_link_local (tcp_connection_t * tc0, vlib_buffer_t * b0,
                              u16 * next0, u32 * error0)
@@ -2055,25 +2113,6 @@ tcp_output_handle_packet (tcp_connection_t * tc0, vlib_buffer_t * b0,
        tcp_output_handle_link_local (tc0, b0, next0, error0);
     }
 
-  /* Filter out DUPACKs if there are no OOO segments left */
-  if (PREDICT_FALSE (vnet_buffer (b0)->tcp.flags & TCP_BUF_FLAG_DUPACK))
-    {
-      /* N.B. Should not filter burst of dupacks. Two issues:
-       * 1) dupacks open cwnd on remote peer when congested
-       * 2) acks leaving should have the latest rcv_wnd since the
-       *    burst may have eaten up all of it, so only the old ones
-       *     could be filtered.
-       */
-      if (!tcp_session_has_ooo_data (tc0))
-       {
-         *error0 = TCP_ERROR_FILTERED_DUPACKS;
-         *next0 = TCP_OUTPUT_NEXT_DROP;
-         return;
-       }
-    }
-
-  /* Stop DELACK timer and fix flags */
-  tc0->flags &= ~(TCP_CONN_SNDACK);
   if (!TCP_ALWAYS_ACK)
     tcp_timer_reset (tc0, TCP_TIMER_DELACK);
 }