nat: avoid hairpinning infinite loop problem
[vpp.git] / src / plugins / nat / test / test_nat44.py
index d635abf..0dcf6d5 100644 (file)
@@ -2374,6 +2374,94 @@ class TestNAT44(MethodHolder):
                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
                 raise
 
+    def test_hairpinning_avoid_inf_loop(self):
+        """ NAT44 hairpinning - 1:1 NAPT avoid infinite loop """
+
+        host = self.pg0.remote_hosts[0]
+        server = self.pg0.remote_hosts[1]
+        host_in_port = 1234
+        host_out_port = 0
+        server_in_port = 5678
+        server_out_port = 8765
+
+        self.nat44_add_address(self.nat_addr)
+        flags = self.config_flags.NAT_IS_INSIDE
+        self.vapi.nat44_interface_add_del_feature(
+            sw_if_index=self.pg0.sw_if_index,
+            flags=flags, is_add=1)
+        self.vapi.nat44_interface_add_del_feature(
+            sw_if_index=self.pg1.sw_if_index,
+            is_add=1)
+
+        # add static mapping for server
+        self.nat44_add_static_mapping(server.ip4, self.nat_addr,
+                                      server_in_port, server_out_port,
+                                      proto=IP_PROTOS.tcp)
+
+        # add another static mapping that maps pg0.local_ip4 address to itself
+        self.nat44_add_static_mapping(self.pg0.local_ip4, self.pg0.local_ip4)
+
+        # send packet from host to VPP (the packet should get dropped)
+        p = (Ether(src=host.mac, dst=self.pg0.local_mac) /
+             IP(src=host.ip4, dst=self.pg0.local_ip4) /
+             TCP(sport=host_in_port, dport=server_out_port))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        # Here VPP used to crash due to an infinite loop
+
+        cnt = self.statistics.get_counter('/nat44/hairpinning')[0]
+        # send packet from host to server
+        p = (Ether(src=host.mac, dst=self.pg0.local_mac) /
+             IP(src=host.ip4, dst=self.nat_addr) /
+             TCP(sport=host_in_port, dport=server_out_port))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg0.get_capture(1)
+        p = capture[0]
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.src, self.nat_addr)
+            self.assertEqual(ip.dst, server.ip4)
+            self.assertNotEqual(tcp.sport, host_in_port)
+            self.assertEqual(tcp.dport, server_in_port)
+            self.assert_packet_checksums_valid(p)
+            host_out_port = tcp.sport
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        after = self.statistics.get_counter('/nat44/hairpinning')[0]
+        if_idx = self.pg0.sw_if_index
+        self.assertEqual(after[if_idx] - cnt[if_idx], 1)
+
+        # send reply from server to host
+        p = (Ether(src=server.mac, dst=self.pg0.local_mac) /
+             IP(src=server.ip4, dst=self.nat_addr) /
+             TCP(sport=server_in_port, dport=host_out_port))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg0.get_capture(1)
+        p = capture[0]
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.src, self.nat_addr)
+            self.assertEqual(ip.dst, host.ip4)
+            self.assertEqual(tcp.sport, server_out_port)
+            self.assertEqual(tcp.dport, host_in_port)
+            self.assert_packet_checksums_valid(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        after = self.statistics.get_counter('/nat44/hairpinning')[0]
+        if_idx = self.pg0.sw_if_index
+        self.assertEqual(after[if_idx] - cnt[if_idx], 2)
+
     def test_interface_addr(self):
         """ Acquire NAT44 addresses from interface """
         self.vapi.nat44_add_del_interface_addr(
@@ -2593,7 +2681,6 @@ class TestNAT44(MethodHolder):
                 data = ipfix.decode_data_set(p.getlayer(Set))
                 self.verify_ipfix_addr_exhausted(data)
 
-    @unittest.skipUnless(running_extended_tests, "part of extended tests")
     def test_ipfix_max_sessions(self):
         """ IPFIX logging maximum session entries exceeded """
         self.nat44_add_address(self.nat_addr)
@@ -3582,53 +3669,6 @@ class TestNAT44(MethodHolder):
             self.pg1.resolve_arp()
             self.pg2.resolve_arp()
 
-    @unittest.skipUnless(running_extended_tests, "part of extended tests")
-    def test_session_timeout(self):
-        """ NAT44 session timeouts """
-        self.nat44_add_address(self.nat_addr)
-        flags = self.config_flags.NAT_IS_INSIDE
-        self.vapi.nat44_interface_add_del_feature(
-            sw_if_index=self.pg0.sw_if_index,
-            flags=flags, is_add=1)
-        self.vapi.nat44_interface_add_del_feature(
-            sw_if_index=self.pg1.sw_if_index,
-            is_add=1)
-        self.vapi.nat_set_timeouts(udp=5, tcp_established=7440,
-                                   tcp_transitory=240, icmp=60)
-
-        max_sessions = 1000
-        pkts = []
-        for i in range(0, max_sessions):
-            src = "10.10.%u.%u" % ((i & 0xFF00) >> 8, i & 0xFF)
-            p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
-                 IP(src=src, dst=self.pg1.remote_ip4) /
-                 UDP(sport=1025, dport=53))
-            pkts.append(p)
-        self.pg0.add_stream(pkts)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        self.pg1.get_capture(max_sessions)
-
-        sleep(6)
-
-        pkts = []
-        for i in range(0, max_sessions):
-            src = "10.10.%u.%u" % ((i & 0xFF00) >> 8, i & 0xFF)
-            p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
-                 IP(src=src, dst=self.pg1.remote_ip4) /
-                 UDP(sport=1026, dport=53))
-            pkts.append(p)
-        self.pg0.add_stream(pkts)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        self.pg1.get_capture(max_sessions)
-
-        nsessions = 0
-        users = self.vapi.nat44_user_dump()
-        for user in users:
-            nsessions = nsessions + user.nsessions
-        self.assertLess(nsessions, 2 * max_sessions)
-
     def test_mss_clamping(self):
         """ TCP MSS clamping """
         self.nat44_add_address(self.nat_addr)
@@ -3669,10 +3709,8 @@ class TestNAT44(MethodHolder):
         # Negotiated MSS value smaller than configured - unchanged
         self.verify_mss_value(capture[0], 1400)
 
-    @unittest.skipUnless(running_extended_tests, "part of extended tests")
     def test_ha_send(self):
         """ Send HA session synchronization events (active) """
-        self.nat44_add_address(self.nat_addr)
         flags = self.config_flags.NAT_IS_INSIDE
         self.vapi.nat44_interface_add_del_feature(
             sw_if_index=self.pg0.sw_if_index,
@@ -3680,6 +3718,8 @@ class TestNAT44(MethodHolder):
         self.vapi.nat44_interface_add_del_feature(
             sw_if_index=self.pg1.sw_if_index,
             is_add=1)
+        self.nat44_add_address(self.nat_addr)
+
         self.vapi.nat_ha_set_listener(ip_address=self.pg3.local_ip4,
                                       port=12345,
                                       path_mtu=512)
@@ -4872,7 +4912,6 @@ class TestNAT44EndpointDependent(MethodHolder):
         sessions = self.vapi.nat44_user_session_dump(server.ip4, 0)
         self.assertEqual(len(sessions), 0)
 
-    @unittest.skipUnless(running_extended_tests, "part of extended tests")
     def test_static_lb_multi_clients(self):
         """ NAT44 local service load balancing - multiple clients"""
 
@@ -4892,13 +4931,6 @@ class TestNAT44EndpointDependent(MethodHolder):
                    'probability': 10,
                    'vrf_id': 0}]
 
-        self.nat44_add_address(self.nat_addr)
-        self.vapi.nat44_add_del_lb_static_mapping(is_add=1,
-                                                  external_addr=external_addr,
-                                                  external_port=external_port,
-                                                  protocol=IP_PROTOS.tcp,
-                                                  local_num=len(locals),
-                                                  locals=locals)
         flags = self.config_flags.NAT_IS_INSIDE
         self.vapi.nat44_interface_add_del_feature(
             sw_if_index=self.pg0.sw_if_index,
@@ -4907,6 +4939,14 @@ class TestNAT44EndpointDependent(MethodHolder):
             sw_if_index=self.pg1.sw_if_index,
             is_add=1)
 
+        self.nat44_add_address(self.nat_addr)
+        self.vapi.nat44_add_del_lb_static_mapping(is_add=1,
+                                                  external_addr=external_addr,
+                                                  external_port=external_port,
+                                                  protocol=IP_PROTOS.tcp,
+                                                  local_num=len(locals),
+                                                  locals=locals)
+
         server1_n = 0
         server2_n = 0
         clients = ip4_range(self.pg1.remote_ip4, 10, 50)
@@ -5774,6 +5814,14 @@ class TestNAT44EndpointDependent(MethodHolder):
 
     def test_tcp_close(self):
         """ Close TCP session from inside network - output feature """
+        old_timeouts = self.vapi.nat_get_timeouts()
+        new_transitory = 2
+        self.vapi.nat_set_timeouts(
+                udp=old_timeouts.udp,
+                tcp_established=old_timeouts.tcp_established,
+                icmp=old_timeouts.icmp,
+                tcp_transitory=new_transitory)
+
         self.vapi.nat44_forwarding_enable_disable(enable=1)
         self.nat44_add_address(self.pg1.local_ip4)
         twice_nat_addr = '10.0.1.3'
@@ -5856,9 +5904,34 @@ class TestNAT44EndpointDependent(MethodHolder):
         self.pg_start()
         self.pg1.get_capture(1)
 
-        sessions = self.vapi.nat44_user_session_dump(self.pg0.remote_ip4,
-                                                     0)
+        # session now in transitory timeout
+        # try SYN packet out->in - should be dropped
+        p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
+             IP(src=self.pg1.remote_ip4, dst=service_ip) /
+             TCP(sport=33898, dport=80, flags="S"))
+        self.pg1.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+
+        self.sleep(new_transitory, "wait for transitory timeout")
+        self.pg0.assert_nothing_captured(0)
+
+        # session should still exist
+        sessions = self.vapi.nat44_user_session_dump(self.pg0.remote_ip4, 0)
+        self.assertEqual(len(sessions) - start_sessnum, 1)
+
+        # send FIN+ACK packet out -> in - will cause session to be wiped
+        # but won't create a new session
+        p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
+             IP(src=self.pg1.remote_ip4, dst=service_ip) /
+             TCP(sport=33898, dport=80, flags="FA", seq=300, ack=101))
+        self.pg1.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+
+        sessions = self.vapi.nat44_user_session_dump(self.pg0.remote_ip4, 0)
         self.assertEqual(len(sessions) - start_sessnum, 0)
+        self.pg0.assert_nothing_captured(0)
 
     def test_tcp_session_close_in(self):
         """ Close TCP session from inside network """
@@ -6801,54 +6874,6 @@ class TestNAT44EndpointDependent(MethodHolder):
             self.logger.error(ppp("Unexpected or invalid packet:", p))
             raise
 
-    @unittest.skipUnless(running_extended_tests, "part of extended tests")
-    def test_session_timeout(self):
-        """ NAT44 session timeouts """
-        self.nat44_add_address(self.nat_addr)
-        flags = self.config_flags.NAT_IS_INSIDE
-        self.vapi.nat44_interface_add_del_feature(
-            sw_if_index=self.pg0.sw_if_index,
-            flags=flags, is_add=1)
-        self.vapi.nat44_interface_add_del_feature(
-            sw_if_index=self.pg1.sw_if_index,
-            is_add=1)
-        self.vapi.nat_set_timeouts(udp=300, tcp_established=7440,
-                                   tcp_transitory=240, icmp=5)
-
-        max_sessions = 1000
-        pkts = []
-        for i in range(0, max_sessions):
-            src = "10.10.%u.%u" % ((i & 0xFF00) >> 8, i & 0xFF)
-            p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
-                 IP(src=src, dst=self.pg1.remote_ip4) /
-                 ICMP(id=1025, type='echo-request'))
-            pkts.append(p)
-        self.pg0.add_stream(pkts)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        self.pg1.get_capture(max_sessions)
-
-        sleep(10)
-
-        pkts = []
-        for i in range(0, max_sessions):
-            src = "10.11.%u.%u" % ((i & 0xFF00) >> 8, i & 0xFF)
-            p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
-                 IP(src=src, dst=self.pg1.remote_ip4) /
-                 ICMP(id=1026, type='echo-request'))
-            pkts.append(p)
-        self.pg0.add_stream(pkts)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        self.pg1.get_capture(max_sessions)
-
-        nsessions = 0
-        users = self.vapi.nat44_user_dump()
-        for user in users:
-            nsessions = nsessions + user.nsessions
-        self.assertLess(nsessions, 2 * max_sessions)
-
-    @unittest.skipUnless(running_extended_tests, "part of extended tests")
     def test_session_rst_timeout(self):
         """ NAT44 session RST timeouts """
         self.nat44_add_address(self.nat_addr)