NAT44: TCP connection close detection (VPP-1266) 14/12414/2
authorMatus Fabian <matfabia@cisco.com>
Fri, 4 May 2018 10:57:42 +0000 (03:57 -0700)
committerDamjan Marion <dmarion.lists@gmail.com>
Tue, 8 May 2018 16:03:30 +0000 (16:03 +0000)
Change-Id: Iba1cc1179ee80478e29888790a6476571d1904dc
Signed-off-by: Matus Fabian <matfabia@cisco.com>
src/plugins/nat/in2out.c
src/plugins/nat/nat.api
src/plugins/nat/nat.c
src/plugins/nat/nat.h
src/plugins/nat/nat44_cli.c
src/plugins/nat/nat_api.c
src/plugins/nat/out2in.c
test/test_nat.py

index 3ec65e8..d3369b6 100755 (executable)
@@ -532,6 +532,11 @@ nat_not_translate_output_feature_fwd (snat_main_t * sm, ip4_header_t * ip,
       s = pool_elt_at_index (sm->per_thread_data[thread_index].sessions, value.value);
       if (is_fwd_bypass_session (s))
         {
+          if (ip->protocol == IP_PROTOCOL_TCP)
+            {
+              tcp_header_t *tcp = ip4_next_header(ip);
+              nat44_set_tcp_session_state (sm, s, tcp, thread_index);
+            }
           /* Per-user LRU list maintenance */
           clib_dlist_remove (tsm->list_pool, s->per_user_index);
           clib_dlist_addtail (tsm->list_pool, s->per_user_list_head_index,
@@ -1369,7 +1374,15 @@ snat_in2out_lb (snat_main_t *sm,
     {
       s = pool_elt_at_index (tsm->sessions, s_value.value);
       if (is_fwd_bypass_session (s))
-        return 0;
+        {
+          if (ip->protocol == IP_PROTOCOL_TCP)
+            nat44_set_tcp_session_state (sm, s, tcp, thread_index);
+          /* Per-user LRU list maintenance */
+          clib_dlist_remove (tsm->list_pool, s->per_user_index);
+          clib_dlist_addtail (tsm->list_pool, s->per_user_list_head_index,
+                              s->per_user_index);
+          return 0;
+        }
     }
   else
     {
@@ -1457,6 +1470,7 @@ snat_in2out_lb (snat_main_t *sm,
           ip->dst_address.as_u32 = s->ext_host_addr.as_u32;
         }
       tcp->checksum = ip_csum_fold(sum);
+      nat44_set_tcp_session_state (sm, s, tcp, thread_index);
     }
   else
     {
@@ -1715,6 +1729,7 @@ snat_in2out_node_fn_inline (vlib_main_t * vm,
                                      ip4_header_t /* cheat */,
                                      length /* changed member */);
               tcp0->checksum = ip_csum_fold(sum0);
+              nat44_set_tcp_session_state (sm, s0, tcp0, thread_index);
             }
           else
             {
@@ -1907,6 +1922,7 @@ snat_in2out_node_fn_inline (vlib_main_t * vm,
                                      ip4_header_t /* cheat */,
                                      length /* changed member */);
               tcp1->checksum = ip_csum_fold(sum1);
+              nat44_set_tcp_session_state (sm, s1, tcp1, thread_index);
             }
           else
             {
@@ -2136,6 +2152,7 @@ snat_in2out_node_fn_inline (vlib_main_t * vm,
                                      ip4_header_t /* cheat */,
                                      length /* changed member */);
               tcp0->checksum = ip_csum_fold(sum0);
+              nat44_set_tcp_session_state (sm, s0, tcp0, thread_index);
             }
           else
             {
@@ -2668,6 +2685,7 @@ nat44_in2out_reass_node_fn (vlib_main_t * vm,
                                          ip4_header_t /* cheat */,
                                          length /* changed member */);
                   tcp0->checksum = ip_csum_fold(sum0);
+                  nat44_set_tcp_session_state (sm, s0, tcp0, thread_index);
                 }
               else
                 {
index 23c89ad..24aa5d1 100644 (file)
@@ -13,7 +13,7 @@
  * limitations under the License.
  */
 
-option version = "2.4.1";
+option version = "2.5.0";
 
 /**
  * @file nat.api
@@ -558,6 +558,7 @@ define nat44_user_session_dump {
     @param last_heard - last heard timer
     @param total_bytes - count of bytes sent through session
     @param total_pkts - count of pakets sent through session
+    @param is_closed - 1 if TCP session is closed
 */
 define nat44_user_session_details {
   u32 context;
@@ -570,6 +571,7 @@ define nat44_user_session_details {
   u64 last_heard;
   u64 total_bytes;
   u32 total_pkts;
+  u8 is_closed;
 };
 
 /** \brief NAT44 load-balancing address and port pair
index 8e4d9df..68b43c0 100755 (executable)
@@ -162,7 +162,8 @@ nat_free_session_data (snat_main_t * sm, snat_session_t * s, u32 thread_index)
       ed_key.fib_index = 0;
       ed_kv.key[0] = ed_key.as_u64[0];
       ed_kv.key[1] = ed_key.as_u64[1];
-      if (clib_bihash_add_del_16_8 (&sm->in2out_ed, &ed_kv, 0))
+      if (clib_bihash_add_del_16_8 (&sm->in2out_ed, &ed_kv, 0) &&
+          s->state != SNAT_SESSION_TCP_CLOSED)
         clib_warning ("in2out_ed key del failed");
       return;
     }
@@ -187,7 +188,8 @@ nat_free_session_data (snat_main_t * sm, snat_session_t * s, u32 thread_index)
         }
       ed_kv.key[0] = ed_key.as_u64[0];
       ed_kv.key[1] = ed_key.as_u64[1];
-      if (clib_bihash_add_del_16_8 (&sm->out2in_ed, &ed_kv, 0))
+      if (clib_bihash_add_del_16_8 (&sm->out2in_ed, &ed_kv, 0) &&
+          s->state != SNAT_SESSION_TCP_CLOSED)
         clib_warning ("out2in_ed key del failed");
 
       ed_key.l_addr = s->in2out.addr;
@@ -201,7 +203,8 @@ nat_free_session_data (snat_main_t * sm, snat_session_t * s, u32 thread_index)
         }
       ed_kv.key[0] = ed_key.as_u64[0];
       ed_kv.key[1] = ed_key.as_u64[1];
-      if (clib_bihash_add_del_16_8 (&sm->in2out_ed, &ed_kv, 0))
+      if (clib_bihash_add_del_16_8 (&sm->in2out_ed, &ed_kv, 0) &&
+          s->state != SNAT_SESSION_TCP_CLOSED)
         clib_warning ("in2out_ed key del failed");
     }
 
@@ -217,7 +220,7 @@ nat_free_session_data (snat_main_t * sm, snat_session_t * s, u32 thread_index)
                                       s->in2out.fib_index);
 
   /* Twice NAT address and port for external host */
-  if (is_twice_nat_session (s))
+  if (is_twice_nat_session (s) && s->state != SNAT_SESSION_TCP_CLOSED)
     {
       for (i = 0; i < vec_len (sm->twice_nat_addresses); i++)
         {
@@ -238,16 +241,18 @@ nat_free_session_data (snat_main_t * sm, snat_session_t * s, u32 thread_index)
 
   /* Session lookup tables */
   kv.key = s->in2out.as_u64;
-  if (clib_bihash_add_del_8_8 (&tsm->in2out, &kv, 0))
+  if (clib_bihash_add_del_8_8 (&tsm->in2out, &kv, 0) &&
+      s->state != SNAT_SESSION_TCP_CLOSED)
     clib_warning ("in2out key del failed");
   kv.key = s->out2in.as_u64;
-  if (clib_bihash_add_del_8_8 (&tsm->out2in, &kv, 0))
+  if (clib_bihash_add_del_8_8 (&tsm->out2in, &kv, 0) &&
+      s->state != SNAT_SESSION_TCP_CLOSED)
     clib_warning ("out2in key del failed");
 
   if (snat_is_session_static (s))
     return;
 
-  if (s->outside_address_index != ~0)
+  if (s->outside_address_index != ~0 && s->state != SNAT_SESSION_TCP_CLOSED)
     snat_free_outside_address_and_port (sm->addresses, thread_index,
                                         &s->out2in, s->outside_address_index);
 }
@@ -333,6 +338,11 @@ nat_session_alloc_or_recycle (snat_main_t *sm, snat_user_t *u, u32 thread_index)
       s->flags = 0;
       s->total_bytes = 0;
       s->total_pkts = 0;
+      s->state = 0;
+      s->ext_host_addr.as_u32 = 0;
+      s->ext_host_port = 0;
+      s->ext_host_nat_addr.as_u32 = 0;
+      s->ext_host_nat_port = 0;
     }
   else
     {
@@ -2696,12 +2706,18 @@ u8 * format_snat_session (u8 * s, va_list * args)
   else
     {
       if (sess->ext_host_addr.as_u32)
-          s = format (s, "       external host %U\n",
-                      format_ip4_address, &sess->ext_host_addr);
+          s = format (s, "       external host %U:%u\n",
+                      format_ip4_address, &sess->ext_host_addr,
+                      clib_net_to_host_u16 (sess->ext_host_port));
     }
   s = format (s, "       last heard %.2f\n", sess->last_heard);
   s = format (s, "       total pkts %d, total bytes %lld\n",
               sess->total_pkts, sess->total_bytes);
+  if (sess->in2out.protocol == SNAT_PROTOCOL_TCP)
+    {
+      s = format (s, "       state %s\n",
+                  sess->state == SNAT_SESSION_TCP_CLOSED ? "closed" : "open");
+    }
   if (snat_is_session_static (sess))
     s = format (s, "       static translation\n");
   else
index 775376f..61d26b2 100644 (file)
@@ -115,8 +115,10 @@ typedef enum {
   _(3, TCP_ESTABLISHED, "tcp-established") \
   _(4, TCP_FIN_WAIT, "tcp-fin-wait")       \
   _(5, TCP_CLOSE_WAIT, "tcp-close-wait")   \
-  _(6, TCP_LAST_ACK, "tcp-last-ack")       \
-  _(7, ICMP_ACTIVE, "icmp-active")
+  _(6, TCP_CLOSING, "tcp-closing")         \
+  _(7, TCP_LAST_ACK, "tcp-last-ack")       \
+  _(8, TCP_CLOSED, "tcp-closed")           \
+  _(9, ICMP_ACTIVE, "icmp-active")
 
 typedef enum {
 #define _(v, N, s) SNAT_SESSION_##N = v,
@@ -164,6 +166,9 @@ typedef CLIB_PACKED(struct {
   /* External hos address and port after translation */
   ip4_address_t ext_host_nat_addr; /* 74-77 */
   u16 ext_host_nat_port;           /* 78-79 */
+
+  /* TCP session state */
+  u8 state;
 }) snat_session_t;
 
 
@@ -681,4 +686,25 @@ user_session_increment(snat_main_t *sm, snat_user_t *u, u8 is_static)
     }
 }
 
+always_inline void
+nat44_set_tcp_session_state(snat_main_t * sm, snat_session_t * ses,
+                            tcp_header_t * tcp, u32 thread_index)
+{
+  if (tcp->flags & TCP_FLAG_FIN && ses->state == SNAT_SESSION_UNKNOWN)
+    ses->state = SNAT_SESSION_TCP_FIN_WAIT;
+  else if (tcp->flags & TCP_FLAG_FIN && ses->state == SNAT_SESSION_TCP_FIN_WAIT)
+    ses->state = SNAT_SESSION_TCP_CLOSING;
+  else if (tcp->flags & TCP_FLAG_ACK && ses->state == SNAT_SESSION_TCP_FIN_WAIT)
+    ses->state = SNAT_SESSION_TCP_CLOSE_WAIT;
+  else if (tcp->flags & TCP_FLAG_FIN && ses->state == SNAT_SESSION_TCP_CLOSE_WAIT)
+    ses->state = SNAT_SESSION_TCP_LAST_ACK;
+  else if (tcp->flags & TCP_FLAG_ACK && ses->state == SNAT_SESSION_TCP_CLOSING)
+    ses->state = SNAT_SESSION_TCP_LAST_ACK;
+  else if (tcp->flags & TCP_FLAG_ACK && ses->state == SNAT_SESSION_TCP_LAST_ACK)
+    {
+      nat_free_session_data (sm, ses, thread_index);
+      ses->state = SNAT_SESSION_TCP_CLOSED;
+    }
+}
+
 #endif /* __included_snat_h__ */
index 2f1e9a0..f07b6dd 100644 (file)
@@ -157,6 +157,38 @@ done:
   return error;
 }
 
+static clib_error_t *
+nat44_show_hash_commnad_fn (vlib_main_t * vm, unformat_input_t * input,
+                           vlib_cli_command_t * cmd)
+{
+  snat_main_t *sm = &snat_main;
+  snat_main_per_thread_data_t *tsm;
+  int i;
+  int verbose = 0;
+
+  if (unformat (input, "detail"))
+    verbose = 1;
+  else if (unformat (input, "verbose"))
+    verbose = 2;
+
+  vlib_cli_output (vm, "%U", format_bihash_16_8, &sm->in2out_ed, verbose);
+  vlib_cli_output (vm, "%U", format_bihash_16_8, &sm->out2in_ed, verbose);
+  vlib_cli_output (vm, "%U", format_bihash_8_8, &sm->static_mapping_by_local,
+                  verbose);
+  vlib_cli_output (vm, "%U",
+                  format_bihash_8_8, &sm->static_mapping_by_external,
+                  verbose);
+  vec_foreach_index (i, sm->per_thread_data)
+  {
+    tsm = vec_elt_at_index (sm->per_thread_data, i);
+    vlib_cli_output (vm, "%U", format_bihash_8_8, &tsm->in2out, verbose);
+    vlib_cli_output (vm, "%U", format_bihash_8_8, &tsm->out2in, verbose);
+    vlib_cli_output (vm, "%U", format_bihash_8_8, &tsm->user_hash, verbose);
+  }
+
+  return 0;
+}
+
 static clib_error_t *
 nat44_set_alloc_addr_and_port_alg_command_fn (vlib_main_t * vm,
                                              unformat_input_t * input,
@@ -1488,6 +1520,18 @@ VLIB_CLI_COMMAND (nat44_set_alloc_addr_and_port_alg_command, static) = {
     .function = nat44_set_alloc_addr_and_port_alg_command_fn,
 };
 
+/*?
+ * @cliexpar
+ * @cliexstart{show nat44 hash tables}
+ * Show NAT44 hash tables
+ * @cliexend
+?*/
+VLIB_CLI_COMMAND (nat44_show_hash, static) = {
+  .path = "show nat44 hash tables",
+  .short_help = "show nat44 hash tables [detail|verbose]",
+  .function = nat44_show_hash_commnad_fn,
+};
+
 /*?
  * @cliexpar
  * @cliexstart{nat44 add address}
index 4c815f0..a1d70f8 100644 (file)
@@ -1235,6 +1235,8 @@ send_nat44_user_session_details (snat_session_t * s,
       rmp->inside_port = s->in2out.port;
       rmp->protocol = ntohs (snat_proto_to_ip_proto (s->in2out.protocol));
     }
+  if (s->in2out.protocol == SNAT_PROTOCOL_TCP)
+    rmp->is_closed = s->state == SNAT_SESSION_TCP_CLOSED ? 1 : 0;
 
   vl_api_send_msg (reg, (u8 *) rmp);
 }
index a0128b8..c0f5a3c 100755 (executable)
@@ -407,6 +407,11 @@ create_bypass_for_fwd(snat_main_t * sm, ip4_header_t * ip, u32 rx_fib_index,
         clib_warning ("in2out_ed key add failed");
     }
 
+  if (ip->protocol == IP_PROTOCOL_TCP)
+    {
+      tcp_header_t *tcp = ip4_next_header(ip);
+      nat44_set_tcp_session_state (sm, s, tcp, thread_index);
+    }
   /* Per-user LRU list maintenance */
   clib_dlist_remove (tsm->list_pool, s->per_user_index);
   clib_dlist_addtail (tsm->list_pool, s->per_user_list_head_index,
@@ -1058,6 +1063,7 @@ snat_out2in_lb (snat_main_t *sm,
           ip->src_address.as_u32 = s->ext_host_nat_addr.as_u32;
         }
       tcp->checksum = ip_csum_fold(sum);
+      nat44_set_tcp_session_state (sm, s, tcp, thread_index);
     }
   else
     {
@@ -1300,6 +1306,7 @@ snat_out2in_node_fn (vlib_main_t * vm,
                                      ip4_header_t /* cheat */,
                                      length /* changed member */);
               tcp0->checksum = ip_csum_fold(sum0);
+              nat44_set_tcp_session_state (sm, s0, tcp0, thread_index);
             }
           else
             {
@@ -1478,6 +1485,7 @@ snat_out2in_node_fn (vlib_main_t * vm,
                                      ip4_header_t /* cheat */,
                                      length /* changed member */);
               tcp1->checksum = ip_csum_fold(sum1);
+              nat44_set_tcp_session_state (sm, s1, tcp1, thread_index);
             }
           else
             {
@@ -1692,6 +1700,7 @@ snat_out2in_node_fn (vlib_main_t * vm,
                                      ip4_header_t /* cheat */,
                                      length /* changed member */);
               tcp0->checksum = ip_csum_fold(sum0);
+              nat44_set_tcp_session_state (sm, s0, tcp0, thread_index);
             }
           else
             {
@@ -1960,6 +1969,7 @@ nat44_out2in_reass_node_fn (vlib_main_t * vm,
                                          ip4_header_t /* cheat */,
                                          length /* changed member */);
                   tcp0->checksum = ip_csum_fold(sum0);
+                  nat44_set_tcp_session_state (sm, s0, tcp0, thread_index);
                 }
               else
                 {
index 51a60d1..c4018cf 100644 (file)
@@ -693,6 +693,50 @@ class MethodHolder(VppTestCase):
             p = (ip / UDP(buffer.getvalue()))
         return p
 
+    def initiate_tcp_session(self, in_if, out_if):
+        """
+        Initiates TCP session
+
+        :param in_if: Inside interface
+        :param out_if: Outside interface
+        """
+        try:
+            # SYN packet in->out
+            p = (Ether(src=in_if.remote_mac, dst=in_if.local_mac) /
+                 IP(src=in_if.remote_ip4, dst=out_if.remote_ip4) /
+                 TCP(sport=self.tcp_port_in, dport=self.tcp_external_port,
+                     flags="S"))
+            in_if.add_stream(p)
+            self.pg_enable_capture(self.pg_interfaces)
+            self.pg_start()
+            capture = out_if.get_capture(1)
+            p = capture[0]
+            self.tcp_port_out = p[TCP].sport
+
+            # SYN + ACK packet out->in
+            p = (Ether(src=out_if.remote_mac, dst=out_if.local_mac) /
+                 IP(src=out_if.remote_ip4, dst=self.nat_addr) /
+                 TCP(sport=self.tcp_external_port, dport=self.tcp_port_out,
+                     flags="SA"))
+            out_if.add_stream(p)
+            self.pg_enable_capture(self.pg_interfaces)
+            self.pg_start()
+            in_if.get_capture(1)
+
+            # ACK packet in->out
+            p = (Ether(src=in_if.remote_mac, dst=in_if.local_mac) /
+                 IP(src=in_if.remote_ip4, dst=out_if.remote_ip4) /
+                 TCP(sport=self.tcp_port_in, dport=self.tcp_external_port,
+                     flags="A"))
+            in_if.add_stream(p)
+            self.pg_enable_capture(self.pg_interfaces)
+            self.pg_start()
+            out_if.get_capture(1)
+
+        except:
+            self.logger.error("TCP 3 way handshake failed")
+            raise
+
     def verify_ipfix_nat44_ses(self, data):
         """
         Verify IPFIX NAT44 session create/delete event
@@ -910,6 +954,7 @@ class TestNAT44(MethodHolder):
             cls.nat_addr_n = socket.inet_pton(socket.AF_INET, cls.nat_addr)
             cls.ipfix_src_port = 4739
             cls.ipfix_domain_id = 1
+            cls.tcp_external_port = 80
 
             cls.create_pg_interfaces(range(10))
             cls.interfaces = list(cls.pg_interfaces[0:4])
@@ -4080,6 +4125,194 @@ class TestNAT44(MethodHolder):
                 self.verify_ipfix_max_fragments_ip4(data, 0,
                                                     self.pg0.remote_ip4n)
 
+    def test_tcp_session_close_in(self):
+        """ Close TCP session from inside network """
+        self.nat44_add_address(self.nat_addr)
+        self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+        self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+                                                  is_inside=0)
+
+        sessions = self.vapi.nat44_user_session_dump(self.pg0.remote_ip4n, 0)
+        start_sessnum = len(sessions)
+
+        self.initiate_tcp_session(self.pg0, self.pg1)
+
+        # close the session from inside
+        try:
+            # FIN packet in -> out
+            p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+                 IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+                 TCP(sport=self.tcp_port_in, dport=self.tcp_external_port,
+                     flags="FA"))
+            self.pg0.add_stream(p)
+            self.pg_enable_capture(self.pg_interfaces)
+            self.pg_start()
+            self.pg1.get_capture(1)
+
+            pkts = []
+
+            # ACK packet out -> in
+            p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
+                 IP(src=self.pg1.remote_ip4, dst=self.nat_addr) /
+                 TCP(sport=self.tcp_external_port, dport=self.tcp_port_out,
+                     flags="A"))
+            pkts.append(p)
+
+            # FIN packet out -> in
+            p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
+                 IP(src=self.pg1.remote_ip4, dst=self.nat_addr) /
+                 TCP(sport=self.tcp_external_port, dport=self.tcp_port_out,
+                     flags="FA"))
+            pkts.append(p)
+
+            self.pg1.add_stream(pkts)
+            self.pg_enable_capture(self.pg_interfaces)
+            self.pg_start()
+            self.pg0.get_capture(2)
+
+            # ACK packet in -> out
+            p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+                 IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+                 TCP(sport=self.tcp_port_in, dport=self.tcp_external_port,
+                     flags="A"))
+            self.pg0.add_stream(p)
+            self.pg_enable_capture(self.pg_interfaces)
+            self.pg_start()
+            self.pg1.get_capture(1)
+
+            self.initiate_tcp_session(self.pg0, self.pg1)
+            sessions = self.vapi.nat44_user_session_dump(self.pg0.remote_ip4n,
+                                                         0)
+            self.assertEqual(len(sessions) - start_sessnum, 2)
+        except:
+            self.logger.error("TCP session termination failed")
+            raise
+
+    def test_tcp_session_close_out(self):
+        """ Close TCP session from outside network """
+        self.nat44_add_address(self.nat_addr)
+        self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+        self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+                                                  is_inside=0)
+
+        sessions = self.vapi.nat44_user_session_dump(self.pg0.remote_ip4n, 0)
+        start_sessnum = len(sessions)
+
+        self.initiate_tcp_session(self.pg0, self.pg1)
+
+        # close the session from outside
+        try:
+            # FIN packet out -> in
+            p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
+                 IP(src=self.pg1.remote_ip4, dst=self.nat_addr) /
+                 TCP(sport=self.tcp_external_port, dport=self.tcp_port_out,
+                     flags="FA"))
+            self.pg1.add_stream(p)
+            self.pg_enable_capture(self.pg_interfaces)
+            self.pg_start()
+            self.pg0.get_capture(1)
+
+            pkts = []
+
+            # ACK packet in -> out
+            p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+                 IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+                 TCP(sport=self.tcp_port_in, dport=self.tcp_external_port,
+                     flags="A"))
+            pkts.append(p)
+
+            # ACK packet in -> out
+            p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+                 IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+                 TCP(sport=self.tcp_port_in, dport=self.tcp_external_port,
+                     flags="FA"))
+            pkts.append(p)
+
+            self.pg0.add_stream(pkts)
+            self.pg_enable_capture(self.pg_interfaces)
+            self.pg_start()
+            self.pg1.get_capture(2)
+
+            # ACK packet out -> in
+            p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
+                 IP(src=self.pg1.remote_ip4, dst=self.nat_addr) /
+                 TCP(sport=self.tcp_external_port, dport=self.tcp_port_out,
+                     flags="A"))
+            self.pg1.add_stream(p)
+            self.pg_enable_capture(self.pg_interfaces)
+            self.pg_start()
+            self.pg0.get_capture(1)
+
+            self.initiate_tcp_session(self.pg0, self.pg1)
+            sessions = self.vapi.nat44_user_session_dump(self.pg0.remote_ip4n,
+                                                         0)
+            self.assertEqual(len(sessions) - start_sessnum, 2)
+        except:
+            self.logger.error("TCP session termination failed")
+            raise
+
+    def test_tcp_session_close_simultaneous(self):
+        """ Close TCP session from inside network """
+        self.nat44_add_address(self.nat_addr)
+        self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+        self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+                                                  is_inside=0)
+
+        sessions = self.vapi.nat44_user_session_dump(self.pg0.remote_ip4n, 0)
+        start_sessnum = len(sessions)
+
+        self.initiate_tcp_session(self.pg0, self.pg1)
+
+        # close the session from inside
+        try:
+            # FIN packet in -> out
+            p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+                 IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+                 TCP(sport=self.tcp_port_in, dport=self.tcp_external_port,
+                     flags="FA"))
+            self.pg0.add_stream(p)
+            self.pg_enable_capture(self.pg_interfaces)
+            self.pg_start()
+            self.pg1.get_capture(1)
+
+            # FIN packet out -> in
+            p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
+                 IP(src=self.pg1.remote_ip4, dst=self.nat_addr) /
+                 TCP(sport=self.tcp_external_port, dport=self.tcp_port_out,
+                     flags="FA"))
+            self.pg1.add_stream(p)
+            self.pg_enable_capture(self.pg_interfaces)
+            self.pg_start()
+            self.pg0.get_capture(1)
+
+            # ACK packet in -> out
+            p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+                 IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+                 TCP(sport=self.tcp_port_in, dport=self.tcp_external_port,
+                     flags="A"))
+            self.pg0.add_stream(p)
+            self.pg_enable_capture(self.pg_interfaces)
+            self.pg_start()
+            self.pg1.get_capture(1)
+
+            # ACK packet out -> in
+            p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
+                 IP(src=self.pg1.remote_ip4, dst=self.nat_addr) /
+                 TCP(sport=self.tcp_external_port, dport=self.tcp_port_out,
+                     flags="A"))
+            self.pg1.add_stream(p)
+            self.pg_enable_capture(self.pg_interfaces)
+            self.pg_start()
+            self.pg0.get_capture(1)
+
+            self.initiate_tcp_session(self.pg0, self.pg1)
+            sessions = self.vapi.nat44_user_session_dump(self.pg0.remote_ip4n,
+                                                         0)
+            self.assertEqual(len(sessions) - start_sessnum, 2)
+        except:
+            self.logger.error("TCP session termination failed")
+            raise
+
     def tearDown(self):
         super(TestNAT44, self).tearDown()
         if not self.vpp_dead:
@@ -4331,50 +4564,6 @@ class TestDeterministicNAT(MethodHolder):
                                       "(outside network):", packet))
                 raise
 
-    def initiate_tcp_session(self, in_if, out_if):
-        """
-        Initiates TCP session
-
-        :param in_if: Inside interface
-        :param out_if: Outside interface
-        """
-        try:
-            # SYN packet in->out
-            p = (Ether(src=in_if.remote_mac, dst=in_if.local_mac) /
-                 IP(src=in_if.remote_ip4, dst=out_if.remote_ip4) /
-                 TCP(sport=self.tcp_port_in, dport=self.tcp_external_port,
-                     flags="S"))
-            in_if.add_stream(p)
-            self.pg_enable_capture(self.pg_interfaces)
-            self.pg_start()
-            capture = out_if.get_capture(1)
-            p = capture[0]
-            self.tcp_port_out = p[TCP].sport
-
-            # SYN + ACK packet out->in
-            p = (Ether(src=out_if.remote_mac, dst=out_if.local_mac) /
-                 IP(src=out_if.remote_ip4, dst=self.nat_addr) /
-                 TCP(sport=self.tcp_external_port, dport=self.tcp_port_out,
-                     flags="SA"))
-            out_if.add_stream(p)
-            self.pg_enable_capture(self.pg_interfaces)
-            self.pg_start()
-            in_if.get_capture(1)
-
-            # ACK packet in->out
-            p = (Ether(src=in_if.remote_mac, dst=in_if.local_mac) /
-                 IP(src=in_if.remote_ip4, dst=out_if.remote_ip4) /
-                 TCP(sport=self.tcp_port_in, dport=self.tcp_external_port,
-                     flags="A"))
-            in_if.add_stream(p)
-            self.pg_enable_capture(self.pg_interfaces)
-            self.pg_start()
-            out_if.get_capture(1)
-
-        except:
-            self.logger.error("TCP 3 way handshake failed")
-            raise
-
     def verify_ipfix_max_entries_per_user(self, data):
         """
         Verify IPFIX maximum entries per user exceeded event