nat: TCP state tracking based on RFC 7857/RFC 6146
[vpp.git] / src / plugins / nat / nat44-ed / nat44_ed_inlines.h
index cb41896..a13f250 100644 (file)
@@ -24,6 +24,7 @@
 #include <vnet/fib/ip4_fib.h>
 
 #include <nat/lib/log.h>
+#include <nat/lib/ipfix_logging.h>
 #include <nat/nat44-ed/nat44_ed.h>
 
 always_inline void
@@ -171,6 +172,26 @@ nat_get_icmp_session_lookup_values (vlib_buffer_t *b, ip4_header_t *ip0,
   return 0;
 }
 
+always_inline int
+nat44_ed_tcp_is_established (nat44_ed_tcp_state_e state)
+{
+  static int lookup[] = {
+    [NAT44_ED_TCP_STATE_CLOSED] = 0,
+    [NAT44_ED_TCP_STATE_SYN_I2O] = 0,
+    [NAT44_ED_TCP_STATE_SYN_O2I] = 0,
+    [NAT44_ED_TCP_STATE_ESTABLISHED] = 1,
+    [NAT44_ED_TCP_STATE_FIN_I2O] = 1,
+    [NAT44_ED_TCP_STATE_FIN_O2I] = 1,
+    [NAT44_ED_TCP_STATE_RST_TRANS] = 0,
+    [NAT44_ED_TCP_STATE_FIN_TRANS] = 0,
+    [NAT44_ED_TCP_STATE_FIN_REOPEN_SYN_I2O] = 0,
+    [NAT44_ED_TCP_STATE_FIN_REOPEN_SYN_O2I] = 0,
+    [NAT44_ED_TCP_N_STATE] = 0,
+  };
+  ASSERT (state <= ARRAY_LEN (lookup));
+  return lookup[state];
+}
+
 always_inline u32
 nat44_session_get_timeout (snat_main_t *sm, snat_session_t *s)
 {
@@ -184,10 +205,10 @@ nat44_session_get_timeout (snat_main_t *sm, snat_session_t *s)
       return sm->timeouts.udp;
     case IP_PROTOCOL_TCP:
       {
-       if (s->state)
-         return sm->timeouts.tcp.transitory;
-       else
+       if (nat44_ed_tcp_is_established (s->tcp_state))
          return sm->timeouts.tcp.established;
+       else
+         return sm->timeouts.tcp.transitory;
       }
     default:
       return sm->timeouts.udp;
@@ -340,8 +361,7 @@ nat_lru_free_one_with_head (snat_main_t *sm, int thread_index, f64 now,
 
       sess_timeout_time =
        s->last_heard + (f64) nat44_session_get_timeout (sm, s);
-      if (now >= sess_timeout_time ||
-         (s->tcp_closed_timestamp && now >= s->tcp_closed_timestamp))
+      if (now >= sess_timeout_time)
        {
          nat44_ed_free_session_data (sm, s, thread_index, 0);
          nat_ed_session_delete (sm, s, thread_index, 0);
@@ -701,101 +721,303 @@ is_interface_addr (snat_main_t *sm, vlib_node_runtime_t *node,
 }
 
 always_inline void
-nat44_set_tcp_session_state_i2o (snat_main_t *sm, f64 now, snat_session_t *ses,
-                                vlib_buffer_t *b, u32 thread_index)
+nat44_ed_session_reopen (u32 thread_index, snat_session_t *s)
 {
-  snat_main_per_thread_data_t *tsm = &sm->per_thread_data[thread_index];
-  u8 tcp_flags = vnet_buffer (b)->ip.reass.icmp_type_or_tcp_flags;
-  u32 tcp_ack_number = vnet_buffer (b)->ip.reass.tcp_ack_number;
-  u32 tcp_seq_number = vnet_buffer (b)->ip.reass.tcp_seq_number;
-  if ((ses->state == 0) && (tcp_flags & TCP_FLAG_RST))
-    ses->state = NAT44_SES_RST;
-  if ((ses->state == NAT44_SES_RST) && !(tcp_flags & TCP_FLAG_RST))
-    ses->state = 0;
-  if ((tcp_flags & TCP_FLAG_ACK) && (ses->state & NAT44_SES_I2O_SYN) &&
-      (ses->state & NAT44_SES_O2I_SYN))
-    ses->state = 0;
-  if (tcp_flags & TCP_FLAG_SYN)
-    ses->state |= NAT44_SES_I2O_SYN;
-  if (tcp_flags & TCP_FLAG_FIN)
-    {
-      ses->i2o_fin_seq = clib_net_to_host_u32 (tcp_seq_number);
-      ses->state |= NAT44_SES_I2O_FIN;
-    }
-  if ((tcp_flags & TCP_FLAG_ACK) && (ses->state & NAT44_SES_O2I_FIN))
+  nat_syslog_nat44_sdel (0, s->in2out.fib_index, &s->in2out.addr,
+                        s->in2out.port, &s->ext_host_nat_addr,
+                        s->ext_host_nat_port, &s->out2in.addr, s->out2in.port,
+                        &s->ext_host_addr, s->ext_host_port, s->proto,
+                        nat44_ed_is_twice_nat_session (s));
+
+  nat_ipfix_logging_nat44_ses_delete (
+    thread_index, s->in2out.addr.as_u32, s->out2in.addr.as_u32, s->proto,
+    s->in2out.port, s->out2in.port, s->in2out.fib_index);
+  nat_ipfix_logging_nat44_ses_create (
+    thread_index, s->in2out.addr.as_u32, s->out2in.addr.as_u32, s->proto,
+    s->in2out.port, s->out2in.port, s->in2out.fib_index);
+
+  nat_syslog_nat44_sadd (0, s->in2out.fib_index, &s->in2out.addr,
+                        s->in2out.port, &s->ext_host_nat_addr,
+                        s->ext_host_nat_port, &s->out2in.addr, s->out2in.port,
+                        &s->ext_host_addr, s->ext_host_port, s->proto, 0);
+  s->total_pkts = 0;
+  s->total_bytes = 0;
+}
+
+always_inline void
+nat44_ed_init_tcp_state_stable (snat_main_t *sm)
+{
+  /* first make sure whole table is initialised in a way where state
+   * is not changed, then define special cases */
+  nat44_ed_tcp_state_e s;
+  for (s = 0; s < NAT44_ED_TCP_N_STATE; ++s)
     {
-      if (clib_net_to_host_u32 (tcp_ack_number) > ses->o2i_fin_seq)
+      int i;
+      for (i = 0; i < NAT44_ED_N_DIR; ++i)
        {
-         ses->state |= NAT44_SES_O2I_FIN_ACK;
-         if (nat44_is_ses_closed (ses))
-           { // if session is now closed, save the timestamp
-             ses->tcp_closed_timestamp = now + sm->timeouts.tcp.transitory;
-             ses->last_lru_update = now;
+         int j = 0;
+         for (j = 0; j < NAT44_ED_TCP_N_FLAG; ++j)
+           {
+             sm->tcp_state_change_table[s][i][j] = s;
            }
        }
     }
 
-  // move the session to proper LRU
-  if (ses->state)
-    {
-      ses->lru_head_index = tsm->tcp_trans_lru_head_index;
-    }
-  else
-    {
-      ses->lru_head_index = tsm->tcp_estab_lru_head_index;
-    }
-  clib_dlist_remove (tsm->lru_pool, ses->lru_index);
-  clib_dlist_addtail (tsm->lru_pool, ses->lru_head_index, ses->lru_index);
+  /* CLOSED and any kind of SYN -> HALF-OPEN */
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_CLOSED][NAT44_ED_DIR_I2O]
+                           [NAT44_ED_TCP_FLAG_SYN] =
+    NAT44_ED_TCP_STATE_SYN_I2O;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_CLOSED][NAT44_ED_DIR_O2I]
+                           [NAT44_ED_TCP_FLAG_SYN] =
+    NAT44_ED_TCP_STATE_SYN_O2I;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_CLOSED][NAT44_ED_DIR_I2O]
+                           [NAT44_ED_TCP_FLAG_SYNFIN] =
+    NAT44_ED_TCP_STATE_SYN_I2O;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_CLOSED][NAT44_ED_DIR_O2I]
+                           [NAT44_ED_TCP_FLAG_SYNFIN] =
+    NAT44_ED_TCP_STATE_SYN_O2I;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_CLOSED][NAT44_ED_DIR_I2O]
+                           [NAT44_ED_TCP_FLAG_SYNFINRST] =
+    NAT44_ED_TCP_STATE_SYN_I2O;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_CLOSED][NAT44_ED_DIR_O2I]
+                           [NAT44_ED_TCP_FLAG_SYNFINRST] =
+    NAT44_ED_TCP_STATE_SYN_O2I;
+
+  /* HALF-OPEN and any kind of SYN in right direction -> ESTABLISHED */
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_SYN_I2O][NAT44_ED_DIR_O2I]
+                           [NAT44_ED_TCP_FLAG_SYN] =
+    NAT44_ED_TCP_STATE_ESTABLISHED;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_SYN_O2I][NAT44_ED_DIR_I2O]
+                           [NAT44_ED_TCP_FLAG_SYN] =
+    NAT44_ED_TCP_STATE_ESTABLISHED;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_SYN_I2O][NAT44_ED_DIR_O2I]
+                           [NAT44_ED_TCP_FLAG_SYNFIN] =
+    NAT44_ED_TCP_STATE_ESTABLISHED;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_SYN_O2I][NAT44_ED_DIR_I2O]
+                           [NAT44_ED_TCP_FLAG_SYNFIN] =
+    NAT44_ED_TCP_STATE_ESTABLISHED;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_SYN_I2O][NAT44_ED_DIR_O2I]
+                           [NAT44_ED_TCP_FLAG_SYNFINRST] =
+    NAT44_ED_TCP_STATE_ESTABLISHED;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_SYN_O2I][NAT44_ED_DIR_I2O]
+                           [NAT44_ED_TCP_FLAG_SYNFINRST] =
+    NAT44_ED_TCP_STATE_ESTABLISHED;
+
+  /* ESTABLISHED and any kind of RST -> RST_TRANS */
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_ESTABLISHED][NAT44_ED_DIR_I2O]
+                           [NAT44_ED_TCP_FLAG_RST] =
+    NAT44_ED_TCP_STATE_RST_TRANS;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_ESTABLISHED][NAT44_ED_DIR_O2I]
+                           [NAT44_ED_TCP_FLAG_RST] =
+    NAT44_ED_TCP_STATE_RST_TRANS;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_ESTABLISHED][NAT44_ED_DIR_I2O]
+                           [NAT44_ED_TCP_FLAG_SYNRST] =
+    NAT44_ED_TCP_STATE_RST_TRANS;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_ESTABLISHED][NAT44_ED_DIR_O2I]
+                           [NAT44_ED_TCP_FLAG_SYNRST] =
+    NAT44_ED_TCP_STATE_RST_TRANS;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_ESTABLISHED][NAT44_ED_DIR_I2O]
+                           [NAT44_ED_TCP_FLAG_FINRST] =
+    NAT44_ED_TCP_STATE_RST_TRANS;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_ESTABLISHED][NAT44_ED_DIR_O2I]
+                           [NAT44_ED_TCP_FLAG_FINRST] =
+    NAT44_ED_TCP_STATE_RST_TRANS;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_ESTABLISHED][NAT44_ED_DIR_I2O]
+                           [NAT44_ED_TCP_FLAG_SYNFINRST] =
+    NAT44_ED_TCP_STATE_RST_TRANS;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_ESTABLISHED][NAT44_ED_DIR_O2I]
+                           [NAT44_ED_TCP_FLAG_SYNFINRST] =
+    NAT44_ED_TCP_STATE_RST_TRANS;
+
+  /* ESTABLISHED and any kind of FIN without RST -> HALF-CLOSED */
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_ESTABLISHED][NAT44_ED_DIR_I2O]
+                           [NAT44_ED_TCP_FLAG_FIN] =
+    NAT44_ED_TCP_STATE_FIN_I2O;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_ESTABLISHED][NAT44_ED_DIR_O2I]
+                           [NAT44_ED_TCP_FLAG_FIN] =
+    NAT44_ED_TCP_STATE_FIN_O2I;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_ESTABLISHED][NAT44_ED_DIR_I2O]
+                           [NAT44_ED_TCP_FLAG_SYNFIN] =
+    NAT44_ED_TCP_STATE_FIN_I2O;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_ESTABLISHED][NAT44_ED_DIR_O2I]
+                           [NAT44_ED_TCP_FLAG_SYNFIN] =
+    NAT44_ED_TCP_STATE_FIN_O2I;
+
+  /* HALF-CLOSED and any kind of FIN -> FIN_TRANS */
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_FIN_I2O][NAT44_ED_DIR_O2I]
+                           [NAT44_ED_TCP_FLAG_FIN] =
+    NAT44_ED_TCP_STATE_FIN_TRANS;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_FIN_O2I][NAT44_ED_DIR_I2O]
+                           [NAT44_ED_TCP_FLAG_FIN] =
+    NAT44_ED_TCP_STATE_FIN_TRANS;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_FIN_I2O][NAT44_ED_DIR_O2I]
+                           [NAT44_ED_TCP_FLAG_SYNFIN] =
+    NAT44_ED_TCP_STATE_FIN_TRANS;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_FIN_O2I][NAT44_ED_DIR_I2O]
+                           [NAT44_ED_TCP_FLAG_SYNFIN] =
+    NAT44_ED_TCP_STATE_FIN_TRANS;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_FIN_I2O][NAT44_ED_DIR_O2I]
+                           [NAT44_ED_TCP_FLAG_FINRST] =
+    NAT44_ED_TCP_STATE_FIN_TRANS;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_FIN_O2I][NAT44_ED_DIR_I2O]
+                           [NAT44_ED_TCP_FLAG_FINRST] =
+    NAT44_ED_TCP_STATE_FIN_TRANS;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_FIN_I2O][NAT44_ED_DIR_O2I]
+                           [NAT44_ED_TCP_FLAG_SYNFINRST] =
+    NAT44_ED_TCP_STATE_FIN_TRANS;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_FIN_O2I][NAT44_ED_DIR_I2O]
+                           [NAT44_ED_TCP_FLAG_SYNFINRST] =
+    NAT44_ED_TCP_STATE_FIN_TRANS;
+
+  /* RST_TRANS and anything non-RST -> ESTABLISHED */
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_RST_TRANS][NAT44_ED_DIR_O2I]
+                           [NAT44_ED_TCP_FLAG_NONE] =
+    NAT44_ED_TCP_STATE_ESTABLISHED;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_RST_TRANS][NAT44_ED_DIR_I2O]
+                           [NAT44_ED_TCP_FLAG_NONE] =
+    NAT44_ED_TCP_STATE_ESTABLISHED;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_RST_TRANS][NAT44_ED_DIR_O2I]
+                           [NAT44_ED_TCP_FLAG_SYN] =
+    NAT44_ED_TCP_STATE_ESTABLISHED;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_RST_TRANS][NAT44_ED_DIR_I2O]
+                           [NAT44_ED_TCP_FLAG_SYN] =
+    NAT44_ED_TCP_STATE_ESTABLISHED;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_RST_TRANS][NAT44_ED_DIR_O2I]
+                           [NAT44_ED_TCP_FLAG_FIN] =
+    NAT44_ED_TCP_STATE_ESTABLISHED;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_RST_TRANS][NAT44_ED_DIR_I2O]
+                           [NAT44_ED_TCP_FLAG_FIN] =
+    NAT44_ED_TCP_STATE_ESTABLISHED;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_RST_TRANS][NAT44_ED_DIR_O2I]
+                           [NAT44_ED_TCP_FLAG_SYNFIN] =
+    NAT44_ED_TCP_STATE_ESTABLISHED;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_RST_TRANS][NAT44_ED_DIR_I2O]
+                           [NAT44_ED_TCP_FLAG_SYNFIN] =
+    NAT44_ED_TCP_STATE_ESTABLISHED;
+
+  /* FIN_TRANS and any kind of SYN -> HALF-REOPEN */
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_FIN_TRANS][NAT44_ED_DIR_I2O]
+                           [NAT44_ED_TCP_FLAG_SYN] =
+    NAT44_ED_TCP_STATE_FIN_REOPEN_SYN_I2O;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_FIN_TRANS][NAT44_ED_DIR_O2I]
+                           [NAT44_ED_TCP_FLAG_SYN] =
+    NAT44_ED_TCP_STATE_FIN_REOPEN_SYN_O2I;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_FIN_TRANS][NAT44_ED_DIR_I2O]
+                           [NAT44_ED_TCP_FLAG_SYNRST] =
+    NAT44_ED_TCP_STATE_FIN_REOPEN_SYN_I2O;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_FIN_TRANS][NAT44_ED_DIR_O2I]
+                           [NAT44_ED_TCP_FLAG_SYNRST] =
+    NAT44_ED_TCP_STATE_FIN_REOPEN_SYN_O2I;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_FIN_TRANS][NAT44_ED_DIR_I2O]
+                           [NAT44_ED_TCP_FLAG_SYNFIN] =
+    NAT44_ED_TCP_STATE_FIN_REOPEN_SYN_I2O;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_FIN_TRANS][NAT44_ED_DIR_O2I]
+                           [NAT44_ED_TCP_FLAG_SYNFIN] =
+    NAT44_ED_TCP_STATE_FIN_REOPEN_SYN_O2I;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_FIN_TRANS][NAT44_ED_DIR_I2O]
+                           [NAT44_ED_TCP_FLAG_SYNFINRST] =
+    NAT44_ED_TCP_STATE_FIN_REOPEN_SYN_I2O;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_FIN_TRANS][NAT44_ED_DIR_O2I]
+                           [NAT44_ED_TCP_FLAG_SYNFINRST] =
+    NAT44_ED_TCP_STATE_FIN_REOPEN_SYN_O2I;
+
+  /* HALF-REOPEN and any kind of SYN in right direction -> ESTABLISHED */
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_FIN_REOPEN_SYN_I2O]
+                           [NAT44_ED_DIR_O2I][NAT44_ED_TCP_FLAG_SYN] =
+    NAT44_ED_TCP_STATE_ESTABLISHED;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_FIN_REOPEN_SYN_O2I]
+                           [NAT44_ED_DIR_I2O][NAT44_ED_TCP_FLAG_SYN] =
+    NAT44_ED_TCP_STATE_ESTABLISHED;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_FIN_REOPEN_SYN_I2O]
+                           [NAT44_ED_DIR_O2I][NAT44_ED_TCP_FLAG_SYNRST] =
+    NAT44_ED_TCP_STATE_ESTABLISHED;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_FIN_REOPEN_SYN_O2I]
+                           [NAT44_ED_DIR_I2O][NAT44_ED_TCP_FLAG_SYNRST] =
+    NAT44_ED_TCP_STATE_ESTABLISHED;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_FIN_REOPEN_SYN_I2O]
+                           [NAT44_ED_DIR_O2I][NAT44_ED_TCP_FLAG_SYNFIN] =
+    NAT44_ED_TCP_STATE_ESTABLISHED;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_FIN_REOPEN_SYN_O2I]
+                           [NAT44_ED_DIR_I2O][NAT44_ED_TCP_FLAG_SYNFIN] =
+    NAT44_ED_TCP_STATE_ESTABLISHED;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_FIN_REOPEN_SYN_I2O]
+                           [NAT44_ED_DIR_O2I][NAT44_ED_TCP_FLAG_SYNFINRST] =
+    NAT44_ED_TCP_STATE_ESTABLISHED;
+  sm->tcp_state_change_table[NAT44_ED_TCP_STATE_FIN_REOPEN_SYN_O2I]
+                           [NAT44_ED_DIR_I2O][NAT44_ED_TCP_FLAG_SYNFINRST] =
+    NAT44_ED_TCP_STATE_ESTABLISHED;
 }
 
+/* TCP state tracking according to RFC 7857 (and RFC 6146, which is referenced
+ * by RFC 7857). Our implementation also goes beyond by supporting creation of
+ * a new session while old session is in transitory timeout after seeing FIN
+ * packets from both sides. */
 always_inline void
-nat44_set_tcp_session_state_o2i (snat_main_t *sm, f64 now, snat_session_t *ses,
-                                u8 tcp_flags, u32 tcp_ack_number,
-                                u32 tcp_seq_number, u32 thread_index)
+nat44_set_tcp_session_state (snat_main_t *sm, f64 now, snat_session_t *ses,
+                            u8 tcp_flags, u32 thread_index,
+                            nat44_ed_dir_e dir)
 {
   snat_main_per_thread_data_t *tsm = &sm->per_thread_data[thread_index];
-  if ((ses->state == 0) && (tcp_flags & TCP_FLAG_RST))
-    ses->state = NAT44_SES_RST;
-  if ((ses->state == NAT44_SES_RST) && !(tcp_flags & TCP_FLAG_RST))
-    ses->state = 0;
-  if ((tcp_flags & TCP_FLAG_ACK) && (ses->state & NAT44_SES_I2O_SYN) &&
-      (ses->state & NAT44_SES_O2I_SYN))
-    ses->state = 0;
-  if (tcp_flags & TCP_FLAG_SYN)
-    ses->state |= NAT44_SES_O2I_SYN;
-  if (tcp_flags & TCP_FLAG_FIN)
-    {
-      ses->o2i_fin_seq = clib_net_to_host_u32 (tcp_seq_number);
-      ses->state |= NAT44_SES_O2I_FIN;
-    }
-  if ((tcp_flags & TCP_FLAG_ACK) && (ses->state & NAT44_SES_I2O_FIN))
+  nat44_ed_tcp_flag_e flags =
+    tcp_flags & (TCP_FLAG_SYN | TCP_FLAG_FIN | TCP_FLAG_RST);
+
+  u8 old_state = ses->tcp_state;
+  ses->tcp_state = sm->tcp_state_change_table[ses->tcp_state][dir][flags];
+
+  if (old_state != ses->tcp_state)
     {
-      if (clib_net_to_host_u32 (tcp_ack_number) > ses->i2o_fin_seq)
-       ses->state |= NAT44_SES_I2O_FIN_ACK;
-      if (nat44_is_ses_closed (ses))
-       { // if session is now closed, save the timestamp
-         ses->tcp_closed_timestamp = now + sm->timeouts.tcp.transitory;
-         ses->last_lru_update = now;
+      if (nat44_ed_tcp_is_established (ses->tcp_state))
+       {
+         if (NAT44_ED_TCP_STATE_FIN_REOPEN_SYN_I2O == old_state ||
+             NAT44_ED_TCP_STATE_FIN_REOPEN_SYN_O2I == old_state)
+           {
+             nat44_ed_session_reopen (thread_index, ses);
+           }
+         ses->lru_head_index = tsm->tcp_estab_lru_head_index;
        }
+      else
+       {
+         if (NAT44_ED_TCP_STATE_ESTABLISHED == old_state)
+           { // need to update last heard otherwise session might get
+             // immediately timed out if it has been idle longer than
+             // transitory timeout
+             ses->last_heard = now;
+           }
+         ses->lru_head_index = tsm->tcp_trans_lru_head_index;
+       }
+      ses->last_lru_update = now;
+      clib_dlist_remove (tsm->lru_pool, ses->lru_index);
+      clib_dlist_addtail (tsm->lru_pool, ses->lru_head_index, ses->lru_index);
     }
-  // move the session to proper LRU
-  if (ses->state)
-    {
-      ses->lru_head_index = tsm->tcp_trans_lru_head_index;
-    }
-  else
-    {
-      ses->lru_head_index = tsm->tcp_estab_lru_head_index;
-    }
-  clib_dlist_remove (tsm->lru_pool, ses->lru_index);
-  clib_dlist_addtail (tsm->lru_pool, ses->lru_head_index, ses->lru_index);
+}
+
+always_inline void
+nat44_set_tcp_session_state_i2o (snat_main_t *sm, f64 now, snat_session_t *ses,
+                                u8 tcp_flags, u32 thread_index)
+{
+  return nat44_set_tcp_session_state (sm, now, ses, tcp_flags, thread_index,
+                                     NAT44_ED_DIR_I2O);
+}
+
+always_inline void
+nat44_set_tcp_session_state_o2i (snat_main_t *sm, f64 now, snat_session_t *ses,
+                                u8 tcp_flags, u32 thread_index)
+{
+  return nat44_set_tcp_session_state (sm, now, ses, tcp_flags, thread_index,
+                                     NAT44_ED_DIR_O2I);
 }
 
 always_inline void
 nat44_session_update_counters (snat_session_t *s, f64 now, uword bytes,
                               u32 thread_index)
 {
-  s->last_heard = now;
+  if (NAT44_ED_TCP_STATE_RST_TRANS != s->tcp_state &&
+      NAT44_ED_TCP_STATE_FIN_TRANS != s->tcp_state &&
+      NAT44_ED_TCP_STATE_FIN_REOPEN_SYN_I2O != s->tcp_state &&
+      NAT44_ED_TCP_STATE_FIN_REOPEN_SYN_O2I != s->tcp_state)
+    {
+      s->last_heard = now;
+    }
   s->total_pkts++;
   s->total_bytes += bytes;
 }