tcp: retransmit and multi-buffer segment fixes and improvements
[vpp.git] / src / vnet / tcp / tcp_input.c
index a2e6dad..95f9ade 100644 (file)
@@ -206,8 +206,8 @@ tcp_options_parse (tcp_header_t * th, tcp_options_t * to)
          vec_reset_length (to->sacks);
          for (j = 0; j < to->n_sack_blocks; j++)
            {
-             b.start = clib_net_to_host_u32 (*(u32 *) (data + 2 + 4 * j));
-             b.end = clib_net_to_host_u32 (*(u32 *) (data + 6 + 4 * j));
+             b.start = clib_net_to_host_u32 (*(u32 *) (data + 2 + 8 * j));
+             b.end = clib_net_to_host_u32 (*(u32 *) (data + 6 + 8 * j));
              vec_add1 (to->sacks, b);
            }
          break;
@@ -251,6 +251,7 @@ tcp_update_timestamp (tcp_connection_t * tc, u32 seq, u32 seq_end)
   if (tcp_opts_tstamp (&tc->rcv_opts) && tc->tsval_recent
       && seq_leq (seq, tc->rcv_las) && seq_leq (tc->rcv_las, seq_end))
     {
+      ASSERT (timestamp_leq (tc->tsval_recent, tc->rcv_opts.tsval));
       tc->tsval_recent = tc->rcv_opts.tsval;
       tc->tsval_recent_age = tcp_time_now ();
     }
@@ -348,7 +349,10 @@ tcp_segment_validate (vlib_main_t * vm, tcp_connection_t * tc0,
   /* 4th: check the SYN bit */
   if (tcp_syn (th0))
     {
-      tcp_send_reset (b0, tc0->c_is_ip4);
+      /* TODO implement RFC 5961 */
+      tcp_make_ack (tc0, b0);
+      *next0 = tcp_next_output (tc0->c_is_ip4);
+      TCP_EVT_DBG (TCP_EVT_SYN_RCVD, tc0);
       return -1;
     }
 
@@ -383,12 +387,9 @@ tcp_estimate_rtt (tcp_connection_t * tc, u32 mrtt)
   if (tc->srtt != 0)
     {
       err = mrtt - tc->srtt;
-//      tc->srtt += err >> 3;
 
       /* XXX Drop in RTT results in RTTVAR increase and bigger RTO.
        * The increase should be bound */
-//      tc->rttvar += ((int) clib_abs (err) - (int) tc->rttvar) >> 2;
-
       tc->srtt = clib_max ((int) tc->srtt + (err >> 3), 1);
       diff = (clib_abs (err) - (int) tc->rttvar) >> 2;
       tc->rttvar = clib_max ((int) tc->rttvar + diff, 1);
@@ -503,7 +504,7 @@ tcp_ack_is_cc_event (tcp_connection_t * tc, vlib_buffer_t * b,
   *is_dack = tc->sack_sb.last_sacked_bytes
     || tcp_ack_is_dupack (tc, b, prev_snd_wnd, prev_snd_una);
 
-  return (*is_dack || tcp_in_cong_recovery (tc));
+  return ((*is_dack || tcp_in_cong_recovery (tc)) && !tcp_is_lost_fin (tc));
 }
 
 void
@@ -534,6 +535,10 @@ scoreboard_remove_hole (sack_scoreboard_t * sb, sack_scoreboard_hole_t * hole)
   if (scoreboard_hole_index (sb, hole) == sb->cur_rxt_hole)
     sb->cur_rxt_hole = TCP_INVALID_SACK_HOLE_INDEX;
 
+  /* Poison the entry */
+  if (CLIB_DEBUG > 0)
+    memset (hole, 0xfe, sizeof (*hole));
+
   pool_put (sb->holes, hole);
 }
 
@@ -549,7 +554,7 @@ scoreboard_insert_hole (sack_scoreboard_t * sb, u32 prev_index,
 
   hole->start = start;
   hole->end = end;
-  hole_index = hole - sb->holes;
+  hole_index = scoreboard_hole_index (sb, hole);
 
   prev = scoreboard_get_hole (sb, prev_index);
   if (prev)
@@ -674,12 +679,30 @@ scoreboard_next_rxt_hole (sack_scoreboard_t * sb,
 }
 
 void
-scoreboard_init_high_rxt (sack_scoreboard_t * sb)
+scoreboard_init_high_rxt (sack_scoreboard_t * sb, u32 seq)
 {
   sack_scoreboard_hole_t *hole;
   hole = scoreboard_first_hole (sb);
-  sb->high_rxt = hole->start;
-  sb->cur_rxt_hole = sb->head;
+  if (hole)
+    {
+      seq = seq_gt (seq, hole->start) ? seq : hole->start;
+      sb->cur_rxt_hole = sb->head;
+    }
+  sb->high_rxt = seq;
+}
+
+/**
+ * Test that scoreboard is sane after recovery
+ *
+ * Returns 1 if scoreboard is empty or if first hole beyond
+ * snd_una.
+ */
+u8
+tcp_scoreboard_is_sane_post_recovery (tcp_connection_t * tc)
+{
+  sack_scoreboard_hole_t *hole;
+  hole = scoreboard_first_hole (&tc->sack_sb);
+  return (!hole || seq_geq (hole->start, tc->snd_una));
 }
 
 void
@@ -706,7 +729,7 @@ tcp_rcv_sacks (tcp_connection_t * tc, u32 ack)
     {
       if (seq_lt (blk->start, blk->end)
          && seq_gt (blk->start, tc->snd_una)
-         && seq_gt (blk->start, ack) && seq_leq (blk->end, tc->snd_nxt))
+         && seq_gt (blk->start, ack) && seq_leq (blk->end, tc->snd_una_max))
        {
          blk++;
          continue;
@@ -725,6 +748,8 @@ tcp_rcv_sacks (tcp_connection_t * tc, u32 ack)
   if (vec_len (tc->rcv_opts.sacks) == 0)
     return;
 
+  tcp_scoreboard_trace_add (tc, ack);
+
   /* Make sure blocks are ordered */
   for (i = 0; i < vec_len (tc->rcv_opts.sacks); i++)
     for (j = i + 1; j < vec_len (tc->rcv_opts.sacks); j++)
@@ -750,10 +775,20 @@ tcp_rcv_sacks (tcp_connection_t * tc, u32 ack)
        * last hole end */
       tmp = tc->rcv_opts.sacks[vec_len (tc->rcv_opts.sacks) - 1];
       last_hole = scoreboard_last_hole (sb);
-      if (seq_gt (tc->snd_una_max, sb->high_sacked)
-         && seq_gt (tc->snd_una_max, last_hole->end))
-       last_hole->end = tc->snd_una_max;
-      /* keep track of max byte sacked for when the last hole
+      if (seq_gt (tc->snd_una_max, last_hole->end))
+       {
+         if (seq_geq (last_hole->start, sb->high_sacked))
+           {
+             last_hole->end = tc->snd_una_max;
+           }
+         /* New hole after high sacked block */
+         else if (seq_lt (sb->high_sacked, tc->snd_una_max))
+           {
+             scoreboard_insert_hole (sb, sb->tail, sb->high_sacked,
+                                     tc->snd_una_max);
+           }
+       }
+      /* Keep track of max byte sacked for when the last hole
        * is acked */
       if (seq_gt (tmp.end, sb->high_sacked))
        sb->high_sacked = tmp.end;
@@ -764,7 +799,6 @@ tcp_rcv_sacks (tcp_connection_t * tc, u32 ack)
   while (hole && blk_index < vec_len (tc->rcv_opts.sacks))
     {
       blk = &tc->rcv_opts.sacks[blk_index];
-
       if (seq_leq (blk->start, hole->start))
        {
          /* Block covers hole. Remove hole */
@@ -782,8 +816,9 @@ tcp_rcv_sacks (tcp_connection_t * tc, u32 ack)
                      sb->last_bytes_delivered +=
                        next_hole->start - hole->end;
                    }
-                 else if (!next_hole)
+                 else
                    {
+                     ASSERT (seq_geq (sb->high_sacked, ack));
                      sb->snd_una_adv = sb->high_sacked - ack;
                      sb->last_bytes_delivered += sb->high_sacked - hole->end;
                    }
@@ -808,18 +843,19 @@ tcp_rcv_sacks (tcp_connection_t * tc, u32 ack)
          if (seq_lt (blk->end, hole->end))
            {
              hole_index = scoreboard_hole_index (sb, hole);
-             scoreboard_insert_hole (sb, hole_index, blk->end, hole->end);
+             next_hole = scoreboard_insert_hole (sb, hole_index, blk->end,
+                                                 hole->end);
 
              /* Pool might've moved */
              hole = scoreboard_get_hole (sb, hole_index);
              hole->end = blk->start;
              blk_index++;
+             ASSERT (hole->next == scoreboard_hole_index (sb, next_hole));
            }
          else if (seq_lt (blk->start, hole->end))
            {
              hole->end = blk->start;
            }
-
          hole = scoreboard_next_hole (sb, hole);
        }
     }
@@ -827,10 +863,13 @@ tcp_rcv_sacks (tcp_connection_t * tc, u32 ack)
   scoreboard_update_bytes (tc, sb);
   sb->last_sacked_bytes = sb->sacked_bytes
     - (old_sacked_bytes - sb->last_bytes_delivered);
+  ASSERT (sb->last_sacked_bytes <= sb->sacked_bytes);
   ASSERT (sb->sacked_bytes == 0
          || sb->sacked_bytes < tc->snd_una_max - seq_max (tc->snd_una, ack));
   ASSERT (sb->last_sacked_bytes + sb->lost_bytes <= tc->snd_una_max
          - seq_max (tc->snd_una, ack));
+  ASSERT (sb->head == TCP_INVALID_SACK_HOLE_INDEX || tcp_in_recovery (tc)
+         || sb->holes[sb->head].start == ack + sb->snd_una_adv);
 }
 
 /**
@@ -916,7 +955,8 @@ tcp_cc_congestion_undo (tcp_connection_t * tc)
 static u8
 tcp_cc_is_spurious_retransmit (tcp_connection_t * tc)
 {
-  return (tc->snd_rxt_ts
+  return (tcp_in_recovery (tc)
+         && tc->snd_rxt_ts
          && tcp_opts_tstamp (&tc->rcv_opts)
          && timestamp_lt (tc->rcv_opts.tsecr, tc->snd_rxt_ts));
 }
@@ -938,7 +978,7 @@ tcp_cc_recover (tcp_connection_t * tc)
 
   ASSERT (tc->rto_boff == 0);
   ASSERT (!tcp_in_cong_recovery (tc));
-
+  ASSERT (tcp_scoreboard_is_sane_post_recovery (tc));
   TCP_EVT_DBG (TCP_EVT_CC_EVT, tc, 3);
   return 0;
 }
@@ -946,7 +986,7 @@ tcp_cc_recover (tcp_connection_t * tc)
 static void
 tcp_cc_update (tcp_connection_t * tc, vlib_buffer_t * b)
 {
-  ASSERT (!tcp_in_cong_recovery (tc));
+  ASSERT (!tcp_in_cong_recovery (tc) || tcp_is_lost_fin (tc));
 
   /* Congestion avoidance */
   tc->cc_algo->rcv_ack (tc);
@@ -994,6 +1034,7 @@ tcp_cc_handle_event (tcp_connection_t * tc, u32 is_dack)
     {
       ASSERT (tc->snd_una != tc->snd_una_max
              || tc->sack_sb.last_sacked_bytes);
+
       tc->rcv_dupacks++;
 
       if (tc->rcv_dupacks > TCP_DUPACK_THRESHOLD && !tc->bytes_acked)
@@ -1012,17 +1053,20 @@ tcp_cc_handle_event (tcp_connection_t * tc, u32 is_dack)
              goto partial_ack_test;
            }
 
-         /* If of of the two conditions lower hold, reset dupacks
-          * 1) Cumulative ack does not cover more than congestion threshold,
-          *    and the following doesn't hold: the congestion window is
-          *    greater than SMSS bytes and the difference between highest_ack
-          *    and prev_highest_ack is at most 4*SMSS bytes (XXX)
-          * 2) RFC6582 heuristic to avoid multiple fast retransmits
+         /* If of of the two conditions lower hold, reset dupacks because
+          * we're probably after timeout (RFC6582 heuristics).
+          * If Cumulative ack does not cover more than congestion threshold,
+          * and:
+          * 1) The following doesn't hold: The congestion window is greater
+          *    than SMSS bytes and the difference between highest_ack
+          *    and prev_highest_ack is at most 4*SMSS bytes
+          * 2) Echoed timestamp in the last non-dup ack does not equal the
+          *    stored timestamp
           */
-         if ((seq_gt (tc->snd_una, tc->snd_congestion)
-              || !(tc->cwnd > tc->snd_mss
-                   && tc->bytes_acked <= 4 * tc->snd_mss))
-             || tc->rcv_opts.tsecr != tc->tsecr_last_ack)
+         if (seq_leq (tc->snd_una, tc->snd_congestion)
+             && ((!(tc->cwnd > tc->snd_mss
+                    && tc->bytes_acked <= 4 * tc->snd_mss))
+                 || (tc->rcv_opts.tsecr != tc->tsecr_last_ack)))
            {
              tc->rcv_dupacks = 0;
              return;
@@ -1038,12 +1082,13 @@ tcp_cc_handle_event (tcp_connection_t * tc, u32 is_dack)
           * three segments that have left the network and should've been
           * buffered at the receiver XXX */
          tc->cwnd = tc->ssthresh + tc->rcv_dupacks * tc->snd_mss;
+         ASSERT (tc->cwnd >= tc->snd_mss);
 
          /* If cwnd allows, send more data */
-         if (tcp_opts_sack_permitted (&tc->rcv_opts)
-             && scoreboard_first_hole (&tc->sack_sb))
+         if (tcp_opts_sack_permitted (&tc->rcv_opts))
            {
-             scoreboard_init_high_rxt (&tc->sack_sb);
+             scoreboard_init_high_rxt (&tc->sack_sb,
+                                       tc->snd_una + tc->snd_mss);
              tcp_fast_retransmit_sack (tc);
            }
          else
@@ -1109,13 +1154,15 @@ partial_ack:
 
   /* Remove retransmitted bytes that have been delivered */
   ASSERT (tc->bytes_acked + tc->sack_sb.snd_una_adv
-         >= tc->sack_sb.last_bytes_delivered);
-  rxt_delivered = tc->bytes_acked + tc->sack_sb.snd_una_adv
-    - tc->sack_sb.last_bytes_delivered;
-  if (rxt_delivered && seq_gt (tc->sack_sb.high_rxt, tc->snd_una))
+         >= tc->sack_sb.last_bytes_delivered
+         || (tc->flags & TCP_CONN_FINSNT));
+
+  if (seq_lt (tc->snd_una, tc->sack_sb.high_rxt))
     {
       /* If we have sacks and we haven't gotten an ack beyond high_rxt,
        * remove sacked bytes delivered */
+      rxt_delivered = tc->bytes_acked + tc->sack_sb.snd_una_adv
+       - tc->sack_sb.last_bytes_delivered;
       ASSERT (tc->snd_rxt_bytes >= rxt_delivered);
       tc->snd_rxt_bytes -= rxt_delivered;
     }
@@ -1195,8 +1242,6 @@ tcp_rcv_ack (tcp_connection_t * tc, vlib_buffer_t * b,
    * Looks okay, process feedback
    */
 
-  TCP_EVT_DBG (TCP_EVT_ACK_RCVD, tc);
-
   if (tcp_opts_sack_permitted (&tc->rcv_opts))
     tcp_rcv_sacks (tc, vnet_buffer (b)->tcp.ack_number);
 
@@ -1212,6 +1257,8 @@ tcp_rcv_ack (tcp_connection_t * tc, vlib_buffer_t * b,
   if (tc->bytes_acked)
     tcp_dequeue_acked (tc, vnet_buffer (b)->tcp.ack_number);
 
+  TCP_EVT_DBG (TCP_EVT_ACK_RCVD, tc);
+
   /*
    * Check if we have congestion event
    */
@@ -1219,6 +1266,8 @@ tcp_rcv_ack (tcp_connection_t * tc, vlib_buffer_t * b,
   if (tcp_ack_is_cc_event (tc, b, prev_snd_wnd, prev_snd_una, &is_dack))
     {
       tcp_cc_handle_event (tc, is_dack);
+      if (!tcp_in_cong_recovery (tc))
+       return 0;
       *error = TCP_ERROR_ACK_DUP;
       TCP_EVT_DBG (TCP_EVT_DUPACK_RCVD, tc, 1);
       return vnet_buffer (b)->tcp.data_len ? 0 : -1;
@@ -1232,6 +1281,18 @@ tcp_rcv_ack (tcp_connection_t * tc, vlib_buffer_t * b,
   return 0;
 }
 
+static u8
+tcp_sack_vector_is_sane (sack_block_t * sacks)
+{
+  int i;
+  for (i = 1; i < vec_len (sacks); i++)
+    {
+      if (sacks[i - 1].end == sacks[i].start)
+       return 0;
+    }
+  return 1;
+}
+
 /**
  * Build SACK list as per RFC2018.
  *
@@ -1292,6 +1353,9 @@ tcp_update_sack_list (tcp_connection_t * tc, u32 start, u32 end)
   /* Replace old vector with new one */
   vec_free (tc->snd_sacks);
   tc->snd_sacks = new_list;
+
+  /* Segments should not 'touch' */
+  ASSERT (tcp_sack_vector_is_sane (tc->snd_sacks));
 }
 
 /** Enqueue data for delivery to application */
@@ -1299,12 +1363,13 @@ always_inline int
 tcp_session_enqueue_data (tcp_connection_t * tc, vlib_buffer_t * b,
                          u16 data_len)
 {
-  int written;
+  int written, error = TCP_ERROR_ENQUEUED;
+
+  ASSERT (seq_geq (vnet_buffer (b)->tcp.seq_number, tc->rcv_nxt));
 
   /* Pure ACK. Update rcv_nxt and be done. */
   if (PREDICT_FALSE (data_len == 0))
     {
-      tc->rcv_nxt = vnet_buffer (b)->tcp.seq_end;
       return TCP_ERROR_PURE_ACK;
     }
 
@@ -1316,12 +1381,12 @@ tcp_session_enqueue_data (tcp_connection_t * tc, vlib_buffer_t * b,
   /* Update rcv_nxt */
   if (PREDICT_TRUE (written == data_len))
     {
-      tc->rcv_nxt = vnet_buffer (b)->tcp.seq_end;
+      tc->rcv_nxt += written;
     }
   /* If more data written than expected, account for out-of-order bytes. */
   else if (written > data_len)
     {
-      tc->rcv_nxt = vnet_buffer (b)->tcp.seq_end + written - data_len;
+      tc->rcv_nxt += written;
 
       /* Send ACK confirming the update */
       tc->flags |= TCP_CONN_SNDACK;
@@ -1335,7 +1400,7 @@ tcp_session_enqueue_data (tcp_connection_t * tc, vlib_buffer_t * b,
        * not be enqueued. Inform peer */
       tc->flags |= TCP_CONN_SNDACK;
 
-      return TCP_ERROR_PARTIALLY_ENQUEUED;
+      error = TCP_ERROR_PARTIALLY_ENQUEUED;
     }
   else
     {
@@ -1350,7 +1415,7 @@ tcp_session_enqueue_data (tcp_connection_t * tc, vlib_buffer_t * b,
       tcp_update_sack_list (tc, tc->rcv_nxt, tc->rcv_nxt);
     }
 
-  return TCP_ERROR_ENQUEUED;
+  return error;
 }
 
 /** Enqueue out-of-order data */
@@ -1359,7 +1424,7 @@ tcp_session_enqueue_ooo (tcp_connection_t * tc, vlib_buffer_t * b,
                         u16 data_len)
 {
   stream_session_t *s0;
-  int rv;
+  int rv, offset;
 
   ASSERT (seq_gt (vnet_buffer (b)->tcp.seq_number, tc->rcv_nxt));
 
@@ -1395,12 +1460,12 @@ tcp_session_enqueue_ooo (tcp_connection_t * tc, vlib_buffer_t * b,
       newest = svm_fifo_newest_ooo_segment (s0->server_rx_fifo);
       if (newest)
        {
-         start =
-           tc->rcv_nxt + ooo_segment_offset (s0->server_rx_fifo, newest);
+         offset = ooo_segment_offset (s0->server_rx_fifo, newest);
+         ASSERT (offset <= vnet_buffer (b)->tcp.seq_number - tc->rcv_nxt);
+         start = tc->rcv_nxt + offset;
          end = start + ooo_segment_length (s0->server_rx_fifo, newest);
          tcp_update_sack_list (tc, start, end);
-
-         ASSERT (seq_gt (start, tc->rcv_nxt));
+         svm_fifo_newest_ooo_segment_reset (s0->server_rx_fifo);
        }
     }
 
@@ -1427,11 +1492,43 @@ tcp_can_delack (tcp_connection_t * tc)
   return 1;
 }
 
+static int
+tcp_buffer_discard_bytes (vlib_buffer_t * b, u32 n_bytes_to_drop)
+{
+  u32 discard, first = b->current_length;
+  vlib_main_t *vm = vlib_get_main ();
+
+  /* Handle multi-buffer segments */
+  if (n_bytes_to_drop > b->current_length)
+    {
+      if (!(b->flags & VLIB_BUFFER_NEXT_PRESENT))
+       return -1;
+      do
+       {
+         discard = clib_min (n_bytes_to_drop, b->current_length);
+         vlib_buffer_advance (b, discard);
+         b = vlib_get_buffer (vm, b->next_buffer);
+         n_bytes_to_drop -= discard;
+       }
+      while (n_bytes_to_drop);
+      if (n_bytes_to_drop > first)
+       b->total_length_not_including_first_buffer -= n_bytes_to_drop - first;
+    }
+  else
+    vlib_buffer_advance (b, n_bytes_to_drop);
+  vnet_buffer (b)->tcp.data_len -= n_bytes_to_drop;
+  return 0;
+}
+
 static int
 tcp_segment_rcv (tcp_main_t * tm, tcp_connection_t * tc, vlib_buffer_t * b,
-                u16 n_data_bytes, u32 * next0)
+                u32 * next0)
 {
-  u32 error = 0, n_bytes_to_drop;
+  u32 error = 0, n_bytes_to_drop, n_data_bytes;
+
+  vlib_buffer_advance (b, vnet_buffer (b)->tcp.data_offset);
+  n_data_bytes = vnet_buffer (b)->tcp.data_len;
+  ASSERT (n_data_bytes);
 
   /* Handle out-of-order data */
   if (PREDICT_FALSE (vnet_buffer (b)->tcp.seq_number != tc->rcv_nxt))
@@ -1445,12 +1542,19 @@ tcp_segment_rcv (tcp_main_t * tm, tcp_connection_t * tc, vlib_buffer_t * b,
 
          /* Completely in the past (possible retransmit) */
          if (seq_leq (vnet_buffer (b)->tcp.seq_end, tc->rcv_nxt))
-           goto done;
+           {
+             /* Ack retransmissions since we may not have any data to send */
+             tcp_make_ack (tc, b);
+             *next0 = tcp_next_output (tc->c_is_ip4);
+             goto done;
+           }
 
          /* Chop off the bytes in the past */
          n_bytes_to_drop = tc->rcv_nxt - vnet_buffer (b)->tcp.seq_number;
          n_data_bytes -= n_bytes_to_drop;
-         vlib_buffer_advance (b, n_bytes_to_drop);
+         vnet_buffer (b)->tcp.seq_number = tc->rcv_nxt;
+         if (tcp_buffer_discard_bytes (b, n_bytes_to_drop))
+           goto done;
 
          goto in_order;
        }
@@ -1482,12 +1586,6 @@ in_order:
    * segments can be enqueued after fifo tail offset changes. */
   error = tcp_session_enqueue_data (tc, b, n_data_bytes);
 
-  if (n_data_bytes == 0)
-    {
-      *next0 = TCP_NEXT_DROP;
-      goto done;
-    }
-
   /* Check if ACK can be delayed */
   if (tcp_can_delack (tc))
     {
@@ -1612,7 +1710,9 @@ tcp46_established_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
            }
 
          th0 = tcp_buffer_hdr (b0);
-         is_fin = (th0->flags & TCP_FLAG_FIN) != 0;
+         /* N.B. buffer is rewritten if segment is ooo. Thus, th0 becomes a
+          * dangling reference. */
+         is_fin = tcp_is_fin (th0);
 
          /* SYNs, FINs and data consume sequence numbers */
          vnet_buffer (b0)->tcp.seq_end = vnet_buffer (b0)->tcp.seq_number
@@ -1632,31 +1732,29 @@ tcp46_established_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
 
          /* 5: check the ACK field  */
          if (tcp_rcv_ack (tc0, b0, th0, &next0, &error0))
-           {
-             goto done;
-           }
+           goto done;
 
          /* 6: check the URG bit TODO */
 
          /* 7: process the segment text */
-
-         vlib_buffer_advance (b0, vnet_buffer (b0)->tcp.data_offset);
-         error0 = tcp_segment_rcv (tm, tc0, b0,
-                                   vnet_buffer (b0)->tcp.data_len, &next0);
-
-         /* N.B. buffer is rewritten if segment is ooo. Thus, th0 becomes a
-          * dangling reference. */
+         if (vnet_buffer (b0)->tcp.data_len)
+           error0 = tcp_segment_rcv (tm, tc0, b0, &next0);
 
          /* 8: check the FIN bit */
-         if (is_fin)
+         if (PREDICT_FALSE (is_fin))
            {
              /* Enter CLOSE-WAIT and notify session. Don't send ACK, instead
               * wait for session to call close. To avoid lingering
               * in CLOSE-WAIT, set timer (reuse WAITCLOSE). */
              tc0->state = TCP_STATE_CLOSE_WAIT;
              TCP_EVT_DBG (TCP_EVT_FIN_RCVD, tc0);
+             if (vnet_buffer (b0)->tcp.data_len == 0)
+               {
+                 tc0->rcv_nxt += 1;
+                 next0 = TCP_ESTABLISHED_NEXT_DROP;
+               }
              stream_session_disconnect_notify (&tc0->connection);
-             tcp_timer_set (tc0, TCP_TIMER_WAITCLOSE, TCP_CLOSEWAIT_TIME);
+             tcp_timer_update (tc0, TCP_TIMER_WAITCLOSE, TCP_CLOSEWAIT_TIME);
            }
 
        done:
@@ -1677,6 +1775,8 @@ tcp46_established_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
 
   errors = session_manager_flush_enqueue_events (my_thread_index);
   tcp_established_inc_counter (vm, is_ip4, TCP_ERROR_EVENT_FIFO_FULL, errors);
+  tcp_flush_frame_to_output (vm, my_thread_index, is_ip4);
+
   return from_frame->n_vectors;
 }
 
@@ -1749,7 +1849,6 @@ tcp46_syn_sent_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
   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;
-  u8 sst = is_ip4 ? SESSION_TYPE_IP4_TCP : SESSION_TYPE_IP6_TCP;
 
   from = vlib_frame_vector_args (from_frame);
   n_left_from = from_frame->n_vectors;
@@ -1783,6 +1882,7 @@ tcp46_syn_sent_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
          tc0 =
            tcp_half_open_connection_get (vnet_buffer (b0)->
                                          tcp.connection_index);
+         ASSERT (tc0);
 
          ack0 = vnet_buffer (b0)->tcp.ack_number;
          seq0 = vnet_buffer (b0)->tcp.seq_number;
@@ -1813,8 +1913,7 @@ tcp46_syn_sent_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
              if (ack0 <= tc0->iss || ack0 > tc0->snd_nxt)
                {
                  if (!tcp_rst (tcp0))
-                   tcp_send_reset (b0, is_ip4);
-
+                   tcp_send_reset_w_pkt (tc0, b0, is_ip4);
                  goto drop;
                }
 
@@ -1832,11 +1931,7 @@ tcp46_syn_sent_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
              /* If ACK is acceptable, signal client that peer is not
               * willing to accept connection and drop connection*/
              if (tcp_ack (tcp0))
-               {
-                 stream_session_connect_notify (&tc0->connection, sst,
-                                                1 /* fail */ );
-                 tcp_connection_cleanup (tc0);
-               }
+               tcp_connection_reset (tc0);
              goto drop;
            }
 
@@ -1852,27 +1947,26 @@ tcp46_syn_sent_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
          if (!tcp_syn (tcp0))
            goto drop;
 
-         /* Stop connection establishment and retransmit timers */
-         tcp_timer_reset (tc0, TCP_TIMER_ESTABLISH);
-         tcp_timer_reset (tc0, TCP_TIMER_RETRANSMIT_SYN);
+         /* Parse options */
+         if (tcp_options_parse (tcp0, &tc0->rcv_opts))
+           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_thread_index = my_thread_index;
          new_tc0->c_c_index = new_tc0 - tm->connections[my_thread_index];
-
-         /* Cleanup half-open connection XXX lock */
-         pool_put (tm->half_open_connections, tc0);
-
+         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;
 
-         /* Parse options */
-         if (tcp_options_parse (tcp0, &new_tc0->rcv_opts))
-           goto drop;
+         /* 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))
            {
@@ -1891,7 +1985,7 @@ tcp46_syn_sent_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
          tcp_connection_init_vars (new_tc0);
 
          /* SYN-ACK: See if we can switch to ESTABLISHED state */
-         if (tcp_ack (tcp0))
+         if (PREDICT_TRUE (tcp_ack (tcp0)))
            {
              /* Our SYN is ACKed: we have iss < ack = snd_una */
 
@@ -1904,19 +1998,20 @@ tcp46_syn_sent_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
 
              /* Notify app that we have connection. If session layer can't
               * allocate session send reset */
-             if (stream_session_connect_notify (&new_tc0->connection, sst,
-                                                0))
+             if (stream_session_connect_notify (&new_tc0->connection, 0))
                {
+                 tcp_send_reset_w_pkt (new_tc0, b0, is_ip4);
                  tcp_connection_cleanup (new_tc0);
-                 tcp_send_reset (b0, is_ip4);
                  goto drop;
                }
 
-             stream_session_init_fifos_pointers (&new_tc0->connection,
-                                                 new_tc0->irs + 1,
-                                                 new_tc0->iss + 1);
              /* Make sure after data segment processing ACK is sent */
              new_tc0->flags |= TCP_CONN_SNDACK;
+
+             /* Update rtt with the syn-ack sample */
+             new_tc0->bytes_acked = 1;
+             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
@@ -1924,17 +2019,15 @@ tcp46_syn_sent_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
              new_tc0->state = TCP_STATE_SYN_RCVD;
 
              /* Notify app that we have connection */
-             if (stream_session_connect_notify
-                 (&new_tc0->connection, sst, 0))
+             if (stream_session_connect_notify (&new_tc0->connection, 0))
                {
                  tcp_connection_cleanup (new_tc0);
-                 tcp_send_reset (b0, is_ip4);
+                 tcp_send_reset_w_pkt (tc0, b0, is_ip4);
+                 TCP_EVT_DBG (TCP_EVT_RST_SENT, tc0);
                  goto drop;
                }
 
-             stream_session_init_fifos_pointers (&new_tc0->connection,
-                                                 new_tc0->irs + 1,
-                                                 new_tc0->iss + 1);
+             tc0->rtt_ts = 0;
              tcp_make_synack (new_tc0, b0);
              next0 = tcp_next_output (is_ip4);
 
@@ -1942,12 +2035,10 @@ tcp46_syn_sent_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
            }
 
          /* Read data, if any */
-         if (vnet_buffer (b0)->tcp.data_len)
+         if (PREDICT_FALSE (vnet_buffer (b0)->tcp.data_len))
            {
-             vlib_buffer_advance (b0, vnet_buffer (b0)->tcp.data_offset);
-             error0 = tcp_segment_rcv (tm, new_tc0, b0,
-                                       vnet_buffer (b0)->tcp.data_len,
-                                       &next0);
+             ASSERT (0);
+             error0 = tcp_segment_rcv (tm, new_tc0, b0, &next0);
              if (error0 == TCP_ERROR_PURE_ACK)
                error0 = TCP_ERROR_SYN_ACKS_RCVD;
            }
@@ -2046,6 +2137,7 @@ VLIB_REGISTER_NODE (tcp6_syn_sent_node) =
 /* *INDENT-ON* */
 
 VLIB_NODE_FUNCTION_MULTIARCH (tcp6_syn_sent_node, tcp6_syn_sent_rcv);
+
 /**
  * Handles reception for all states except LISTEN, SYN-SENT and ESTABLISHED
  * as per RFC793 p. 64
@@ -2134,7 +2226,7 @@ tcp46_rcv_process_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
               */
              if (!tcp_rcv_ack_is_acceptable (tc0, b0))
                {
-                 tcp_send_reset (b0, is_ip4);
+                 tcp_send_reset_w_pkt (tc0, b0, is_ip4);
                  goto drop;
                }
 
@@ -2151,12 +2243,11 @@ tcp46_rcv_process_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
                << tc0->rcv_opts.wscale;
              tc0->snd_wl1 = vnet_buffer (b0)->tcp.seq_number;
              tc0->snd_wl2 = vnet_buffer (b0)->tcp.ack_number;
-
-             /* Shoulder tap the server */
              stream_session_accept_notify (&tc0->connection);
 
-             /* Reset SYN-ACK retransmit timer */
+             /* Reset SYN-ACK retransmit and SYN_RCV establish timers */
              tcp_retransmit_timer_reset (tc0);
+             tcp_timer_reset (tc0, TCP_TIMER_ESTABLISH);
              break;
            case TCP_STATE_ESTABLISHED:
              /* We can get packets in established state here because they
@@ -2172,12 +2263,31 @@ tcp46_rcv_process_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
              if (tcp_rcv_ack (tc0, b0, tcp0, &next0, &error0))
                goto drop;
 
+             /* Still have to send the FIN */
+             if (tc0->flags & TCP_CONN_FINPNDG)
+               {
+                 /* TX fifo finally drained */
+                 if (!stream_session_tx_fifo_max_dequeue (&tc0->connection))
+                   tcp_send_fin (tc0);
+               }
              /* If FIN is ACKed */
-             if (tc0->snd_una == tc0->snd_una_max)
+             else if (tc0->snd_una == tc0->snd_una_max)
                {
+                 tc0->rcv_nxt += 1;
                  tc0->state = TCP_STATE_FIN_WAIT_2;
-                 /* Stop all timers, 2MSL will be set lower */
-                 tcp_connection_timers_reset (tc0);
+                 TCP_EVT_DBG (TCP_EVT_STATE_CHANGE, tc0);
+
+                 if (tcp_fin (tcp0))
+                   {
+                     /* Stop all timers, 2MSL will be set lower */
+                     tcp_connection_timers_reset (tc0);
+                   }
+                 else
+                   {
+                     /* Wait for peer to finish sending its data */
+                     tcp_timer_update (tc0, TCP_TIMER_WAITCLOSE,
+                                       TCP_2MSL_TIME);
+                   }
                }
              break;
            case TCP_STATE_FIN_WAIT_2:
@@ -2186,7 +2296,6 @@ tcp46_rcv_process_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
               * acknowledged ("ok") but do not delete the TCB. */
              if (tcp_rcv_ack (tc0, b0, tcp0, &next0, &error0))
                goto drop;
-             /* check if rtx queue is empty and ack CLOSE TODO */
              break;
            case TCP_STATE_CLOSE_WAIT:
              /* Do the same processing as for the ESTABLISHED state. */
@@ -2200,8 +2309,9 @@ tcp46_rcv_process_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
              if (tcp_rcv_ack (tc0, b0, tcp0, &next0, &error0))
                goto drop;
 
-             /* XXX test that send queue empty */
              tc0->state = TCP_STATE_TIME_WAIT;
+             TCP_EVT_DBG (TCP_EVT_STATE_CHANGE, tc0);
+             tcp_timer_update (tc0, TCP_TIMER_WAITCLOSE, TCP_2MSL_TIME);
              goto drop;
 
              break;
@@ -2222,6 +2332,7 @@ tcp46_rcv_process_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
                }
 
              tc0->state = TCP_STATE_CLOSED;
+             TCP_EVT_DBG (TCP_EVT_STATE_CHANGE, tc0);
 
              /* Don't delete the connection/session yet. Instead, wait a
               * reasonable amount of time until the pipes are cleared. In
@@ -2262,10 +2373,8 @@ tcp46_rcv_process_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
            case TCP_STATE_ESTABLISHED:
            case TCP_STATE_FIN_WAIT_1:
            case TCP_STATE_FIN_WAIT_2:
-             vlib_buffer_advance (b0, vnet_buffer (b0)->tcp.data_offset);
-             error0 = tcp_segment_rcv (tm, tc0, b0,
-                                       vnet_buffer (b0)->tcp.data_len,
-                                       &next0);
+             if (vnet_buffer (b0)->tcp.data_len)
+               error0 = tcp_segment_rcv (tm, tc0, b0, &next0);
              break;
            case TCP_STATE_CLOSE_WAIT:
            case TCP_STATE_CLOSING:
@@ -2290,6 +2399,7 @@ tcp46_rcv_process_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
              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:
@@ -2297,17 +2407,21 @@ tcp46_rcv_process_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
              /* move along .. */
              break;
            case TCP_STATE_FIN_WAIT_1:
-             tc0->state = TCP_STATE_TIME_WAIT;
-             tcp_connection_timers_reset (tc0);
-             tcp_timer_set (tc0, TCP_TIMER_WAITCLOSE, TCP_2MSL_TIME);
+             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! */
              tc0->state = TCP_STATE_TIME_WAIT;
              tcp_connection_timers_reset (tc0);
-             tcp_timer_set (tc0, TCP_TIMER_WAITCLOSE, TCP_CLOSEWAIT_TIME);
+             tcp_timer_update (tc0, TCP_TIMER_WAITCLOSE, TCP_CLOSEWAIT_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 2 MSL time-wait
@@ -2419,7 +2533,6 @@ tcp46_listen_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
 {
   u32 n_left_from, next_index, *from, *to_next;
   u32 my_thread_index = vm->thread_index;
-  tcp_main_t *tm = vnet_get_tcp_main ();
   u8 sst = is_ip4 ? SESSION_TYPE_IP4_TCP : SESSION_TYPE_IP6_TCP;
 
   from = vlib_frame_vector_args (from_frame);
@@ -2482,14 +2595,10 @@ tcp46_listen_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
          /* 3. check for a SYN (did that already) */
 
          /* Create child session and send SYN-ACK */
-         pool_get (tm->connections[my_thread_index], child0);
-         memset (child0, 0, sizeof (*child0));
-
-         child0->c_c_index = child0 - tm->connections[my_thread_index];
+         child0 = tcp_connection_new (my_thread_index);
          child0->c_lcl_port = lc0->c_lcl_port;
          child0->c_rmt_port = th0->src_port;
          child0->c_is_ip4 = is_ip4;
-         child0->c_thread_index = my_thread_index;
          child0->state = TCP_STATE_SYN_RCVD;
 
          if (is_ip4)
@@ -2538,17 +2647,13 @@ tcp46_listen_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
          child0->snd_wl2 = vnet_buffer (b0)->tcp.ack_number;
 
          tcp_connection_init_vars (child0);
-
          TCP_EVT_DBG (TCP_EVT_SYN_RCVD, child0);
 
          /* 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);
 
-         /* Init fifo pointers after we have iss */
-         stream_session_init_fifos_pointers (&child0->connection,
-                                             child0->irs + 1,
-                                             child0->iss + 1);
        drop:
          if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED))
            {
@@ -2659,6 +2764,34 @@ typedef enum _tcp_input_next
 
 #define filter_flags (TCP_FLAG_SYN|TCP_FLAG_ACK|TCP_FLAG_RST|TCP_FLAG_FIN)
 
+static u8
+tcp_lookup_is_valid (tcp_connection_t * tc, tcp_header_t * hdr)
+{
+  transport_connection_t *tmp;
+  if (!tc)
+    return 1;
+
+  u8 is_valid = (tc->c_lcl_port == hdr->dst_port
+                && (tc->state == TCP_STATE_LISTEN
+                    || tc->c_rmt_port == hdr->src_port));
+
+  if (!is_valid)
+    {
+      if ((tmp =
+          stream_session_half_open_lookup (&tc->c_lcl_ip, &tc->c_rmt_ip,
+                                           tc->c_lcl_port, tc->c_rmt_port,
+                                           tc->c_transport_proto)))
+       {
+         if (tmp->lcl_port == hdr->dst_port
+             && tmp->rmt_port == hdr->src_port)
+           {
+             clib_warning ("half-open is valid!");
+           }
+       }
+    }
+  return is_valid;
+}
+
 always_inline uword
 tcp46_input_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
                    vlib_frame_t * from_frame, int is_ip4)
@@ -2669,8 +2802,8 @@ tcp46_input_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
 
   from = vlib_frame_vector_args (from_frame);
   n_left_from = from_frame->n_vectors;
-
   next_index = node->cached_next_index;
+  tcp_set_time_now (my_thread_index);
 
   while (n_left_from > 0)
     {
@@ -2711,15 +2844,15 @@ tcp46_input_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
              n_data_bytes0 = clib_net_to_host_u16 (ip40->length)
                - n_advance_bytes0;
 
-             /* lookup session */
              tc0 =
                (tcp_connection_t *)
-               stream_session_lookup_transport4 (&ip40->dst_address,
-                                                 &ip40->src_address,
-                                                 tcp0->dst_port,
-                                                 tcp0->src_port,
-                                                 SESSION_TYPE_IP4_TCP,
-                                                 my_thread_index);
+               stream_session_lookup_transport_wt4 (&ip40->dst_address,
+                                                    &ip40->src_address,
+                                                    tcp0->dst_port,
+                                                    tcp0->src_port,
+                                                    SESSION_TYPE_IP4_TCP,
+                                                    my_thread_index);
+             ASSERT (tcp_lookup_is_valid (tc0, tcp0));
            }
          else
            {
@@ -2732,12 +2865,13 @@ tcp46_input_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
 
              tc0 =
                (tcp_connection_t *)
-               stream_session_lookup_transport6 (&ip60->src_address,
-                                                 &ip60->dst_address,
-                                                 tcp0->src_port,
-                                                 tcp0->dst_port,
-                                                 SESSION_TYPE_IP6_TCP,
-                                                 my_thread_index);
+               stream_session_lookup_transport_wt6 (&ip60->dst_address,
+                                                    &ip60->src_address,
+                                                    tcp0->dst_port,
+                                                    tcp0->src_port,
+                                                    SESSION_TYPE_IP6_TCP,
+                                                    my_thread_index);
+             ASSERT (tcp_lookup_is_valid (tc0, tcp0));
            }
 
          /* Length check */
@@ -2886,9 +3020,12 @@ do {                                                             \
   _(LISTEN, TCP_FLAG_SYN, TCP_INPUT_NEXT_LISTEN, TCP_ERROR_NONE);
   _(LISTEN, TCP_FLAG_ACK, TCP_INPUT_NEXT_RESET, TCP_ERROR_NONE);
   _(LISTEN, TCP_FLAG_RST, TCP_INPUT_NEXT_DROP, TCP_ERROR_NONE);
+  _(LISTEN, TCP_FLAG_FIN | TCP_FLAG_ACK, TCP_INPUT_NEXT_RESET,
+    TCP_ERROR_NONE);
   /* ACK for for a SYN-ACK -> tcp-rcv-process. */
   _(SYN_RCVD, TCP_FLAG_ACK, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE);
   _(SYN_RCVD, TCP_FLAG_RST, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE);
+  _(SYN_RCVD, TCP_FLAG_SYN, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE);
   /* SYN-ACK for a SYN */
   _(SYN_SENT, TCP_FLAG_SYN | TCP_FLAG_ACK, TCP_INPUT_NEXT_SYN_SENT,
     TCP_ERROR_NONE);
@@ -2905,12 +3042,16 @@ do {                                                            \
   _(ESTABLISHED, TCP_FLAG_RST, TCP_INPUT_NEXT_ESTABLISHED, TCP_ERROR_NONE);
   _(ESTABLISHED, TCP_FLAG_RST | TCP_FLAG_ACK, TCP_INPUT_NEXT_ESTABLISHED,
     TCP_ERROR_NONE);
+  _(ESTABLISHED, TCP_FLAG_SYN, TCP_INPUT_NEXT_ESTABLISHED, TCP_ERROR_NONE);
+  _(ESTABLISHED, TCP_FLAG_SYN | TCP_FLAG_ACK, TCP_INPUT_NEXT_ESTABLISHED,
+    TCP_ERROR_NONE);
   /* ACK or FIN-ACK to our FIN */
   _(FIN_WAIT_1, TCP_FLAG_ACK, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE);
   _(FIN_WAIT_1, TCP_FLAG_ACK | TCP_FLAG_FIN, TCP_INPUT_NEXT_RCV_PROCESS,
     TCP_ERROR_NONE);
   /* FIN in reply to our FIN from the other side */
   _(FIN_WAIT_1, TCP_FLAG_FIN, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE);
+  _(FIN_WAIT_1, TCP_FLAG_RST, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE);
   /* FIN confirming that the peer (app) has closed */
   _(FIN_WAIT_2, TCP_FLAG_FIN, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE);
   _(FIN_WAIT_2, TCP_FLAG_ACK, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE);
@@ -2927,8 +3068,11 @@ do {                                                             \
   _(TIME_WAIT, TCP_FLAG_FIN, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE);
   _(TIME_WAIT, TCP_FLAG_FIN | TCP_FLAG_ACK, TCP_INPUT_NEXT_RCV_PROCESS,
     TCP_ERROR_NONE);
+  _(TIME_WAIT, TCP_FLAG_RST, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE);
   _(CLOSED, TCP_FLAG_ACK, TCP_INPUT_NEXT_RESET, TCP_ERROR_CONNECTION_CLOSED);
   _(CLOSED, TCP_FLAG_RST, TCP_INPUT_NEXT_DROP, TCP_ERROR_CONNECTION_CLOSED);
+  _(CLOSED, TCP_FLAG_FIN | TCP_FLAG_ACK, TCP_INPUT_NEXT_RESET,
+    TCP_ERROR_CONNECTION_CLOSED);
 #undef _
 }