Add special Twice-NAT feature (VPP-1221)
[vpp.git] / test / test_nat.py
index 7194704..47f3b8c 100644 (file)
@@ -922,6 +922,9 @@ class TestNAT44(MethodHolder):
             cls.pg0.generate_remote_hosts(3)
             cls.pg0.configure_ipv4_neighbors()
 
+            cls.pg1.generate_remote_hosts(1)
+            cls.pg1.configure_ipv4_neighbors()
+
             cls.overlapping_interfaces = list(list(cls.pg_interfaces[4:7]))
             cls.vapi.ip_table_add_del(10, is_add=1)
             cls.vapi.ip_table_add_del(20, is_add=1)
@@ -989,6 +992,8 @@ class TestNAT44(MethodHolder):
         if self.pg7.has_ip4_config:
             self.pg7.unconfig_ip4()
 
+        self.vapi.nat44_forwarding_enable_disable(0)
+
         interfaces = self.vapi.nat44_interface_addr_dump()
         for intf in interfaces:
             self.vapi.nat44_add_interface_addr(intf.sw_if_index,
@@ -1027,6 +1032,10 @@ class TestNAT44(MethodHolder):
                 vrf_id=sm.vrf_id,
                 protocol=sm.protocol,
                 twice_nat=sm.twice_nat,
+                self_twice_nat=sm.self_twice_nat,
+                out2in_only=sm.out2in_only,
+                tag=sm.tag,
+                external_sw_if_index=sm.external_sw_if_index,
                 is_add=0)
 
         lb_static_mappings = self.vapi.nat44_lb_static_mapping_dump()
@@ -1037,6 +1046,9 @@ class TestNAT44(MethodHolder):
                 lb_sm.protocol,
                 vrf_id=lb_sm.vrf_id,
                 twice_nat=lb_sm.twice_nat,
+                self_twice_nat=lb_sm.self_twice_nat,
+                out2in_only=lb_sm.out2in_only,
+                tag=lb_sm.tag,
                 is_add=0,
                 local_num=0,
                 locals=[])
@@ -1065,7 +1077,8 @@ class TestNAT44(MethodHolder):
     def nat44_add_static_mapping(self, local_ip, external_ip='0.0.0.0',
                                  local_port=0, external_port=0, vrf_id=0,
                                  is_add=1, external_sw_if_index=0xFFFFFFFF,
-                                 proto=0, twice_nat=0):
+                                 proto=0, twice_nat=0, self_twice_nat=0,
+                                 out2in_only=0, tag=""):
         """
         Add/delete NAT44 static mapping
 
@@ -1078,6 +1091,11 @@ class TestNAT44(MethodHolder):
         :param external_sw_if_index: External interface instead of IP address
         :param proto: IP protocol (Mandatory if port specified)
         :param twice_nat: 1 if translate external host address and port
+        :param self_twice_nat: 1 if translate external host address and port
+                               whenever external host address equals
+                               local address of internal host
+        :param out2in_only: if 1 rule is matching only out2in direction
+        :param tag: Opaque string tag
         """
         addr_only = 1
         if local_port and external_port:
@@ -1094,6 +1112,9 @@ class TestNAT44(MethodHolder):
             vrf_id,
             proto,
             twice_nat,
+            self_twice_nat,
+            out2in_only,
+            tag,
             is_add)
 
     def nat44_add_address(self, ip, is_add=1, vrf_id=0xFFFFFFFF, twice_nat=0):
@@ -1370,6 +1391,12 @@ class TestNAT44(MethodHolder):
         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)
+        sm = self.vapi.nat44_static_mapping_dump()
+        self.assertEqual(len(sm), 1)
+        self.assertEqual((sm[0].tag).split('\0', 1)[0], '')
+        self.assertEqual(sm[0].protocol, 0)
+        self.assertEqual(sm[0].local_port, 0)
+        self.assertEqual(sm[0].external_port, 0)
 
         # in2out
         pkts = self.create_stream_in(self.pg0, self.pg1)
@@ -1394,11 +1421,15 @@ class TestNAT44(MethodHolder):
         self.tcp_port_out = 6303
         self.udp_port_out = 6304
         self.icmp_id_out = 6305
+        tag = "testTAG"
 
-        self.nat44_add_static_mapping(self.pg0.remote_ip4, nat_ip)
+        self.nat44_add_static_mapping(self.pg0.remote_ip4, nat_ip, tag=tag)
         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)
+        sm = self.vapi.nat44_static_mapping_dump()
+        self.assertEqual(len(sm), 1)
+        self.assertEqual((sm[0].tag).split('\0', 1)[0], tag)
 
         # out2in
         pkts = self.create_stream_out(self.pg1, nat_ip)
@@ -1490,6 +1521,138 @@ class TestNAT44(MethodHolder):
         capture = self.pg1.get_capture(len(pkts))
         self.verify_capture_out(capture)
 
+    def test_static_with_port_out2(self):
+        """ 1:1 NAPT symmetrical rule """
+
+        external_port = 80
+        local_port = 8080
+
+        self.vapi.nat44_forwarding_enable_disable(1)
+        self.nat44_add_static_mapping(self.pg0.remote_ip4, self.nat_addr,
+                                      local_port, external_port,
+                                      proto=IP_PROTOS.tcp, out2in_only=1)
+        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)
+
+        # from client to service
+        p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
+             IP(src=self.pg1.remote_ip4, dst=self.nat_addr) /
+             TCP(sport=12345, dport=external_port))
+        self.pg1.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg0.get_capture(1)
+        p = capture[0]
+        server = None
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.dst, self.pg0.remote_ip4)
+            self.assertEqual(tcp.dport, local_port)
+            self.check_tcp_checksum(p)
+            self.check_ip_checksum(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        # ICMP error
+        p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+             IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+             ICMP(type=11) / capture[0][IP])
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(1)
+        p = capture[0]
+        try:
+            self.assertEqual(p[IP].src, self.nat_addr)
+            inner = p[IPerror]
+            self.assertEqual(inner.dst, self.nat_addr)
+            self.assertEqual(inner[TCPerror].dport, external_port)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        # from service back to client
+        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=local_port, dport=12345))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(1)
+        p = capture[0]
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.src, self.nat_addr)
+            self.assertEqual(tcp.sport, external_port)
+            self.check_tcp_checksum(p)
+            self.check_ip_checksum(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        # ICMP error
+        p = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
+             IP(src=self.pg1.remote_ip4, dst=self.nat_addr) /
+             ICMP(type=11) / capture[0][IP])
+        self.pg1.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg0.get_capture(1)
+        p = capture[0]
+        try:
+            self.assertEqual(p[IP].dst, self.pg0.remote_ip4)
+            inner = p[IPerror]
+            self.assertEqual(inner.src, self.pg0.remote_ip4)
+            self.assertEqual(inner[TCPerror].sport, local_port)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        # from client to server (no translation)
+        p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
+             IP(src=self.pg1.remote_ip4, dst=self.pg0.remote_ip4) /
+             TCP(sport=12346, dport=local_port))
+        self.pg1.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg0.get_capture(1)
+        p = capture[0]
+        server = None
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.dst, self.pg0.remote_ip4)
+            self.assertEqual(tcp.dport, local_port)
+            self.check_tcp_checksum(p)
+            self.check_ip_checksum(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        # from service back to client (no translation)
+        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=local_port, dport=12346))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(1)
+        p = capture[0]
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.src, self.pg0.remote_ip4)
+            self.assertEqual(tcp.sport, local_port)
+            self.check_tcp_checksum(p)
+            self.check_ip_checksum(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
     def test_static_vrf_aware(self):
         """ 1:1 NAT VRF awareness """
 
@@ -1524,6 +1687,37 @@ class TestNAT44(MethodHolder):
         self.pg_start()
         self.pg3.assert_nothing_captured()
 
+    def test_dynamic_to_static(self):
+        """ Switch from dynamic translation to 1:1NAT """
+        nat_ip = "10.0.0.10"
+        self.tcp_port_out = 6303
+        self.udp_port_out = 6304
+        self.icmp_id_out = 6305
+
+        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)
+
+        # dynamic
+        pkts = self.create_stream_in(self.pg0, self.pg1)
+        self.pg0.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(len(pkts))
+        self.verify_capture_out(capture)
+
+        # 1:1NAT
+        self.nat44_add_static_mapping(self.pg0.remote_ip4, nat_ip)
+        sessions = self.vapi.nat44_user_session_dump(self.pg0.remote_ip4n, 0)
+        self.assertEqual(len(sessions), 0)
+        pkts = self.create_stream_in(self.pg0, self.pg1)
+        self.pg0.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(len(pkts))
+        self.verify_capture_out(capture, nat_ip, True)
+
     def test_identity_nat(self):
         """ Identity NAT """
 
@@ -1623,10 +1817,36 @@ class TestNAT44(MethodHolder):
             self.logger.error(ppp("Unexpected or invalid packet:", p))
             raise
 
-        # multiple clients
+    @unittest.skipUnless(running_extended_tests(), "part of extended tests")
+    def test_static_lb_multi_clients(self):
+        """ NAT44 local service load balancing - multiple clients"""
+
+        external_addr_n = socket.inet_pton(socket.AF_INET, self.nat_addr)
+        external_port = 80
+        local_port = 8080
+        server1 = self.pg0.remote_hosts[0]
+        server2 = self.pg0.remote_hosts[1]
+
+        locals = [{'addr': server1.ip4n,
+                   'port': local_port,
+                   'probability': 90},
+                  {'addr': server2.ip4n,
+                   'port': local_port,
+                   'probability': 10}]
+
+        self.nat44_add_address(self.nat_addr)
+        self.vapi.nat44_add_del_lb_static_mapping(external_addr_n,
+                                                  external_port,
+                                                  IP_PROTOS.tcp,
+                                                  local_num=len(locals),
+                                                  locals=locals)
+        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)
+
         server1_n = 0
         server2_n = 0
-        clients = ip4_range(self.pg1.remote_ip4, 10, 20)
+        clients = ip4_range(self.pg1.remote_ip4, 10, 50)
         pkts = []
         for client in clients:
             p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
@@ -1644,38 +1864,150 @@ class TestNAT44(MethodHolder):
                 server2_n += 1
         self.assertTrue(server1_n > server2_n)
 
-    def test_multiple_inside_interfaces(self):
-        """ NAT44 multiple non-overlapping address space inside interfaces """
+    def test_static_lb_2(self):
+        """ NAT44 local service load balancing (asymmetrical rule) """
+        external_addr_n = socket.inet_pton(socket.AF_INET, self.nat_addr)
+        external_port = 80
+        local_port = 8080
+        server1 = self.pg0.remote_hosts[0]
+        server2 = self.pg0.remote_hosts[1]
 
-        self.nat44_add_address(self.nat_addr)
+        locals = [{'addr': server1.ip4n,
+                   'port': local_port,
+                   'probability': 70},
+                  {'addr': server2.ip4n,
+                   'port': local_port,
+                   'probability': 30}]
+
+        self.vapi.nat44_forwarding_enable_disable(1)
+        self.vapi.nat44_add_del_lb_static_mapping(external_addr_n,
+                                                  external_port,
+                                                  IP_PROTOS.tcp,
+                                                  out2in_only=1,
+                                                  local_num=len(locals),
+                                                  locals=locals)
         self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
-        self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index)
-        self.vapi.nat44_interface_add_del_feature(self.pg3.sw_if_index,
+        self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
                                                   is_inside=0)
 
-        # between two NAT44 inside interfaces (no translation)
-        pkts = self.create_stream_in(self.pg0, self.pg1)
-        self.pg0.add_stream(pkts)
+        # from client to service
+        p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
+             IP(src=self.pg1.remote_ip4, dst=self.nat_addr) /
+             TCP(sport=12345, dport=external_port))
+        self.pg1.add_stream(p)
         self.pg_enable_capture(self.pg_interfaces)
         self.pg_start()
-        capture = self.pg1.get_capture(len(pkts))
-        self.verify_capture_no_translation(capture, self.pg0, self.pg1)
+        capture = self.pg0.get_capture(1)
+        p = capture[0]
+        server = None
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertIn(ip.dst, [server1.ip4, server2.ip4])
+            if ip.dst == server1.ip4:
+                server = server1
+            else:
+                server = server2
+            self.assertEqual(tcp.dport, local_port)
+            self.check_tcp_checksum(p)
+            self.check_ip_checksum(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
 
-        # from NAT44 inside to interface without NAT44 feature (no translation)
-        pkts = self.create_stream_in(self.pg0, self.pg2)
-        self.pg0.add_stream(pkts)
+        # from service back to client
+        p = (Ether(src=server.mac, dst=self.pg0.local_mac) /
+             IP(src=server.ip4, dst=self.pg1.remote_ip4) /
+             TCP(sport=local_port, dport=12345))
+        self.pg0.add_stream(p)
         self.pg_enable_capture(self.pg_interfaces)
         self.pg_start()
-        capture = self.pg2.get_capture(len(pkts))
-        self.verify_capture_no_translation(capture, self.pg0, self.pg2)
+        capture = self.pg1.get_capture(1)
+        p = capture[0]
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.src, self.nat_addr)
+            self.assertEqual(tcp.sport, external_port)
+            self.check_tcp_checksum(p)
+            self.check_ip_checksum(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
 
-        # in2out 1st interface
-        pkts = self.create_stream_in(self.pg0, self.pg3)
-        self.pg0.add_stream(pkts)
+        # from client to server (no translation)
+        p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
+             IP(src=self.pg1.remote_ip4, dst=server1.ip4) /
+             TCP(sport=12346, dport=local_port))
+        self.pg1.add_stream(p)
         self.pg_enable_capture(self.pg_interfaces)
         self.pg_start()
-        capture = self.pg3.get_capture(len(pkts))
-        self.verify_capture_out(capture)
+        capture = self.pg0.get_capture(1)
+        p = capture[0]
+        server = None
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.dst, server1.ip4)
+            self.assertEqual(tcp.dport, local_port)
+            self.check_tcp_checksum(p)
+            self.check_ip_checksum(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        # from service back to client (no translation)
+        p = (Ether(src=server1.mac, dst=self.pg0.local_mac) /
+             IP(src=server1.ip4, dst=self.pg1.remote_ip4) /
+             TCP(sport=local_port, dport=12346))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(1)
+        p = capture[0]
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.src, server1.ip4)
+            self.assertEqual(tcp.sport, local_port)
+            self.check_tcp_checksum(p)
+            self.check_ip_checksum(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+    def test_multiple_inside_interfaces(self):
+        """ NAT44 multiple non-overlapping address space inside interfaces """
+
+        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)
+        self.vapi.nat44_interface_add_del_feature(self.pg3.sw_if_index,
+                                                  is_inside=0)
+
+        # between two NAT44 inside interfaces (no translation)
+        pkts = self.create_stream_in(self.pg0, self.pg1)
+        self.pg0.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(len(pkts))
+        self.verify_capture_no_translation(capture, self.pg0, self.pg1)
+
+        # from NAT44 inside to interface without NAT44 feature (no translation)
+        pkts = self.create_stream_in(self.pg0, self.pg2)
+        self.pg0.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg2.get_capture(len(pkts))
+        self.verify_capture_no_translation(capture, self.pg0, self.pg2)
+
+        # in2out 1st interface
+        pkts = self.create_stream_in(self.pg0, self.pg3)
+        self.pg0.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg3.get_capture(len(pkts))
+        self.verify_capture_out(capture)
 
         # out2in 1st interface
         pkts = self.create_stream_out(self.pg3)
@@ -1908,7 +2240,7 @@ class TestNAT44(MethodHolder):
             self.assertEqual(tcp.dport, host_in_port)
             self.check_tcp_checksum(p)
         except:
-            self.logger.error(ppp("Unexpected or invalid packet:"), p)
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
             raise
 
     def test_hairpinning2(self):
@@ -2124,28 +2456,62 @@ class TestNAT44(MethodHolder):
 
     def test_interface_addr_static_mapping(self):
         """ Static mapping with addresses from interface """
+        tag = "testTAG"
+
         self.vapi.nat44_add_interface_addr(self.pg7.sw_if_index)
         self.nat44_add_static_mapping(
             '1.2.3.4',
-            external_sw_if_index=self.pg7.sw_if_index)
+            external_sw_if_index=self.pg7.sw_if_index,
+            tag=tag)
 
         # static mappings with external interface
         static_mappings = self.vapi.nat44_static_mapping_dump()
         self.assertEqual(1, len(static_mappings))
         self.assertEqual(self.pg7.sw_if_index,
                          static_mappings[0].external_sw_if_index)
+        self.assertEqual((static_mappings[0].tag).split('\0', 1)[0], tag)
 
         # configure interface address and check static mappings
         self.pg7.config_ip4()
         static_mappings = self.vapi.nat44_static_mapping_dump()
-        self.assertEqual(1, len(static_mappings))
-        self.assertEqual(static_mappings[0].external_ip_address[0:4],
-                         self.pg7.local_ip4n)
-        self.assertEqual(0xFFFFFFFF, static_mappings[0].external_sw_if_index)
+        self.assertEqual(2, len(static_mappings))
+        resolved = False
+        for sm in static_mappings:
+            if sm.external_sw_if_index == 0xFFFFFFFF:
+                self.assertEqual(sm.external_ip_address[0:4],
+                                 self.pg7.local_ip4n)
+                self.assertEqual((sm.tag).split('\0', 1)[0], tag)
+                resolved = True
+        self.assertTrue(resolved)
 
         # remove interface address and check static mappings
         self.pg7.unconfig_ip4()
         static_mappings = self.vapi.nat44_static_mapping_dump()
+        self.assertEqual(1, len(static_mappings))
+        self.assertEqual(self.pg7.sw_if_index,
+                         static_mappings[0].external_sw_if_index)
+        self.assertEqual((static_mappings[0].tag).split('\0', 1)[0], tag)
+
+        # configure interface address again and check static mappings
+        self.pg7.config_ip4()
+        static_mappings = self.vapi.nat44_static_mapping_dump()
+        self.assertEqual(2, len(static_mappings))
+        resolved = False
+        for sm in static_mappings:
+            if sm.external_sw_if_index == 0xFFFFFFFF:
+                self.assertEqual(sm.external_ip_address[0:4],
+                                 self.pg7.local_ip4n)
+                self.assertEqual((sm.tag).split('\0', 1)[0], tag)
+                resolved = True
+        self.assertTrue(resolved)
+
+        # remove static mapping
+        self.nat44_add_static_mapping(
+            '1.2.3.4',
+            external_sw_if_index=self.pg7.sw_if_index,
+            tag=tag,
+            is_add=0)
+        static_mappings = self.vapi.nat44_static_mapping_dump()
         self.assertEqual(0, len(static_mappings))
 
     def test_interface_addr_identity_nat(self):
@@ -2168,17 +2534,23 @@ class TestNAT44(MethodHolder):
         # configure interface address and check identity mappings
         self.pg7.config_ip4()
         identity_mappings = self.vapi.nat44_identity_mapping_dump()
-        self.assertEqual(1, len(identity_mappings))
-        self.assertEqual(identity_mappings[0].ip_address,
-                         self.pg7.local_ip4n)
-        self.assertEqual(0xFFFFFFFF, identity_mappings[0].sw_if_index)
-        self.assertEqual(port, identity_mappings[0].port)
-        self.assertEqual(IP_PROTOS.tcp, identity_mappings[0].protocol)
+        resolved = False
+        self.assertEqual(2, len(identity_mappings))
+        for sm in identity_mappings:
+            if sm.sw_if_index == 0xFFFFFFFF:
+                self.assertEqual(identity_mappings[0].ip_address,
+                                 self.pg7.local_ip4n)
+                self.assertEqual(port, identity_mappings[0].port)
+                self.assertEqual(IP_PROTOS.tcp, identity_mappings[0].protocol)
+                resolved = True
+        self.assertTrue(resolved)
 
         # remove interface address and check identity mappings
         self.pg7.unconfig_ip4()
         identity_mappings = self.vapi.nat44_identity_mapping_dump()
-        self.assertEqual(0, len(identity_mappings))
+        self.assertEqual(1, len(identity_mappings))
+        self.assertEqual(self.pg7.sw_if_index,
+                         identity_mappings[0].sw_if_index)
 
     def test_ipfix_nat44_sess(self):
         """ IPFIX logging NAT44 session created/delted """
@@ -2967,7 +3339,188 @@ class TestNAT44(MethodHolder):
             self.assertEqual(tcp.dport, host_in_port)
             self.check_tcp_checksum(p)
         except:
-            self.logger.error(ppp("Unexpected or invalid packet:"), p)
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+    def test_output_feature_and_service(self):
+        """ NAT44 interface output feature and services """
+        external_addr = '1.2.3.4'
+        external_port = 80
+        local_port = 8080
+
+        self.vapi.nat44_forwarding_enable_disable(1)
+        self.nat44_add_address(self.nat_addr)
+        self.nat44_add_static_mapping(self.pg0.remote_ip4, external_addr,
+                                      local_port, external_port,
+                                      proto=IP_PROTOS.tcp, out2in_only=1)
+        self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+        self.vapi.nat44_interface_add_del_output_feature(self.pg1.sw_if_index,
+                                                         is_inside=0)
+
+        # from client to service
+        p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
+             IP(src=self.pg1.remote_ip4, dst=external_addr) /
+             TCP(sport=12345, dport=external_port))
+        self.pg1.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg0.get_capture(1)
+        p = capture[0]
+        server = None
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.dst, self.pg0.remote_ip4)
+            self.assertEqual(tcp.dport, local_port)
+            self.check_tcp_checksum(p)
+            self.check_ip_checksum(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        # from service back to client
+        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=local_port, dport=12345))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(1)
+        p = capture[0]
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.src, external_addr)
+            self.assertEqual(tcp.sport, external_port)
+            self.check_tcp_checksum(p)
+            self.check_ip_checksum(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        # from local network host to external network
+        pkts = self.create_stream_in(self.pg0, self.pg1)
+        self.pg0.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(len(pkts))
+        self.verify_capture_out(capture)
+        pkts = self.create_stream_in(self.pg0, self.pg1)
+        self.pg0.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(len(pkts))
+        self.verify_capture_out(capture)
+
+        # from external network back to local network host
+        pkts = self.create_stream_out(self.pg1)
+        self.pg1.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg0.get_capture(len(pkts))
+        self.verify_capture_in(capture, self.pg0)
+
+    def test_output_feature_and_service2(self):
+        """ NAT44 interface output feature and service host direct access """
+        self.vapi.nat44_forwarding_enable_disable(1)
+        self.nat44_add_address(self.nat_addr)
+        self.vapi.nat44_interface_add_del_output_feature(self.pg1.sw_if_index,
+                                                         is_inside=0)
+
+        # session initiaded from service host - translate
+        pkts = self.create_stream_in(self.pg0, self.pg1)
+        self.pg0.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(len(pkts))
+        self.verify_capture_out(capture)
+
+        pkts = self.create_stream_out(self.pg1)
+        self.pg1.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg0.get_capture(len(pkts))
+        self.verify_capture_in(capture, self.pg0)
+
+        tcp_port_out = self.tcp_port_out
+        udp_port_out = self.udp_port_out
+        icmp_id_out = self.icmp_id_out
+
+        # session initiaded from remote host - do not translate
+        pkts = self.create_stream_out(self.pg1,
+                                      self.pg0.remote_ip4,
+                                      use_inside_ports=True)
+        self.pg1.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg0.get_capture(len(pkts))
+        self.verify_capture_in(capture, self.pg0)
+
+        pkts = self.create_stream_in(self.pg0, self.pg1)
+        self.pg0.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(len(pkts))
+        self.verify_capture_out(capture, nat_ip=self.pg0.remote_ip4,
+                                same_port=True)
+
+    def test_output_feature_and_service3(self):
+        """ NAT44 interface output feature and DST NAT """
+        external_addr = '1.2.3.4'
+        external_port = 80
+        local_port = 8080
+
+        self.vapi.nat44_forwarding_enable_disable(1)
+        self.nat44_add_address(self.nat_addr)
+        self.nat44_add_static_mapping(self.pg1.remote_ip4, external_addr,
+                                      local_port, external_port,
+                                      proto=IP_PROTOS.tcp, out2in_only=1)
+        self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+        self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index,
+                                                  is_inside=0)
+        self.vapi.nat44_interface_add_del_output_feature(self.pg1.sw_if_index,
+                                                         is_inside=0)
+
+        p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+             IP(src=self.pg0.remote_ip4, dst=external_addr) /
+             TCP(sport=12345, dport=external_port))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(1)
+        p = capture[0]
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.src, self.pg0.remote_ip4)
+            self.assertEqual(tcp.sport, 12345)
+            self.assertEqual(ip.dst, self.pg1.remote_ip4)
+            self.assertEqual(tcp.dport, local_port)
+            self.check_tcp_checksum(p)
+            self.check_ip_checksum(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
+             IP(src=self.pg1.remote_ip4, dst=self.pg0.remote_ip4) /
+             TCP(sport=local_port, dport=12345))
+        self.pg1.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, external_addr)
+            self.assertEqual(tcp.sport, external_port)
+            self.assertEqual(ip.dst, self.pg0.remote_ip4)
+            self.assertEqual(tcp.dport, 12345)
+            self.check_tcp_checksum(p)
+            self.check_ip_checksum(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
             raise
 
     def test_one_armed_nat44(self):
@@ -3026,6 +3579,70 @@ class TestNAT44(MethodHolder):
             self.logger.error(ppp("Unexpected or invalid packet:", p))
             raise
 
+    def test_one_armed_nat44_static(self):
+        """ One armed NAT44 and 1:1 NAPT symmetrical rule """
+        remote_host = self.pg9.remote_hosts[0]
+        local_host = self.pg9.remote_hosts[1]
+        external_port = 80
+        local_port = 8080
+        eh_port_in = 0
+
+        self.vapi.nat44_forwarding_enable_disable(1)
+        self.nat44_add_address(self.nat_addr, twice_nat=1)
+        self.nat44_add_static_mapping(local_host.ip4, self.nat_addr,
+                                      local_port, external_port,
+                                      proto=IP_PROTOS.tcp, out2in_only=1,
+                                      twice_nat=1)
+        self.vapi.nat44_interface_add_del_feature(self.pg9.sw_if_index)
+        self.vapi.nat44_interface_add_del_feature(self.pg9.sw_if_index,
+                                                  is_inside=0)
+
+        # from client to service
+        p = (Ether(src=self.pg9.remote_mac, dst=self.pg9.local_mac) /
+             IP(src=remote_host.ip4, dst=self.nat_addr) /
+             TCP(sport=12345, dport=external_port))
+        self.pg9.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg9.get_capture(1)
+        p = capture[0]
+        server = None
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.dst, local_host.ip4)
+            self.assertEqual(ip.src, self.nat_addr)
+            self.assertEqual(tcp.dport, local_port)
+            self.assertNotEqual(tcp.sport, 12345)
+            eh_port_in = tcp.sport
+            self.check_tcp_checksum(p)
+            self.check_ip_checksum(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        # from service back to client
+        p = (Ether(src=self.pg9.remote_mac, dst=self.pg9.local_mac) /
+             IP(src=local_host.ip4, dst=self.nat_addr) /
+             TCP(sport=local_port, dport=eh_port_in))
+        self.pg9.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg9.get_capture(1)
+        p = capture[0]
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.src, self.nat_addr)
+            self.assertEqual(ip.dst, remote_host.ip4)
+            self.assertEqual(tcp.sport, external_port)
+            self.assertEqual(tcp.dport, 12345)
+            self.check_tcp_checksum(p)
+            self.check_ip_checksum(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
     def test_del_session(self):
         """ Delete NAT44 session """
         self.nat44_add_address(self.nat_addr)
@@ -3193,91 +3810,31 @@ class TestNAT44(MethodHolder):
                                        self.nat_addr,
                                        20,
                                        self.tcp_port_out,
-                                       data)
-        pkts.reverse()
-        self.pg1.add_stream(pkts)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        frags = self.pg0.get_capture(len(pkts))
-        p = self.reass_frags_and_verify(frags,
-                                        self.pg1.remote_ip4,
-                                        self.pg0.remote_ip4)
-        self.assertEqual(p[TCP].sport, 20)
-        self.assertEqual(p[TCP].dport, self.tcp_port_in)
-        self.assertEqual(data, p[Raw].load)
-
-    def test_port_restricted(self):
-        """ Port restricted NAT44 (MAP-E CE) """
-        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)
-        self.vapi.cli("nat addr-port-assignment-alg map-e psid 10 "
-                      "psid-offset 6 psid-len 6")
-
-        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=4567, dport=22))
-        self.pg0.add_stream(p)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        capture = self.pg1.get_capture(1)
-        p = capture[0]
-        try:
-            ip = p[IP]
-            tcp = p[TCP]
-            self.assertEqual(ip.dst, self.pg1.remote_ip4)
-            self.assertEqual(ip.src, self.nat_addr)
-            self.assertEqual(tcp.dport, 22)
-            self.assertNotEqual(tcp.sport, 4567)
-            self.assertEqual((tcp.sport >> 6) & 63, 10)
-            self.check_tcp_checksum(p)
-            self.check_ip_checksum(p)
-        except:
-            self.logger.error(ppp("Unexpected or invalid packet:", p))
-            raise
-
-    def test_twice_nat(self):
-        """ Twice NAT44 """
-        twice_nat_addr = '10.0.1.3'
-        port_in = 8080
-        port_out = 80
-        eh_port_out = 4567
-        eh_port_in = 0
-        self.nat44_add_address(self.nat_addr)
-        self.nat44_add_address(twice_nat_addr, twice_nat=1)
-        self.nat44_add_static_mapping(self.pg0.remote_ip4, self.nat_addr,
-                                      port_in, port_out, proto=IP_PROTOS.tcp,
-                                      twice_nat=1)
-        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)
-
-        p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
-             IP(src=self.pg1.remote_ip4, dst=self.nat_addr) /
-             TCP(sport=eh_port_out, dport=port_out))
-        self.pg1.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.dst, self.pg0.remote_ip4)
-            self.assertEqual(ip.src, twice_nat_addr)
-            self.assertEqual(tcp.dport, port_in)
-            self.assertNotEqual(tcp.sport, eh_port_out)
-            eh_port_in = tcp.sport
-            self.check_tcp_checksum(p)
-            self.check_ip_checksum(p)
-        except:
-            self.logger.error(ppp("Unexpected or invalid packet:", p))
-            raise
+                                       data)
+        pkts.reverse()
+        self.pg1.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        frags = self.pg0.get_capture(len(pkts))
+        p = self.reass_frags_and_verify(frags,
+                                        self.pg1.remote_ip4,
+                                        self.pg0.remote_ip4)
+        self.assertEqual(p[TCP].sport, 20)
+        self.assertEqual(p[TCP].dport, self.tcp_port_in)
+        self.assertEqual(data, p[Raw].load)
+
+    def test_port_restricted(self):
+        """ Port restricted NAT44 (MAP-E CE) """
+        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)
+        self.vapi.cli("nat addr-port-assignment-alg map-e psid 10 "
+                      "psid-offset 6 psid-len 6")
 
         p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
-             IP(src=self.pg0.remote_ip4, dst=twice_nat_addr) /
-             TCP(sport=port_in, dport=eh_port_in))
+             IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+             TCP(sport=4567, dport=22))
         self.pg0.add_stream(p)
         self.pg_enable_capture(self.pg_interfaces)
         self.pg_start()
@@ -3288,93 +3845,172 @@ class TestNAT44(MethodHolder):
             tcp = p[TCP]
             self.assertEqual(ip.dst, self.pg1.remote_ip4)
             self.assertEqual(ip.src, self.nat_addr)
-            self.assertEqual(tcp.dport, eh_port_out)
-            self.assertEqual(tcp.sport, port_out)
+            self.assertEqual(tcp.dport, 22)
+            self.assertNotEqual(tcp.sport, 4567)
+            self.assertEqual((tcp.sport >> 6) & 63, 10)
             self.check_tcp_checksum(p)
             self.check_ip_checksum(p)
         except:
             self.logger.error(ppp("Unexpected or invalid packet:", p))
             raise
 
-    def test_twice_nat_lb(self):
-        """ Twice NAT44 local service load balancing """
-        external_addr_n = socket.inet_pton(socket.AF_INET, self.nat_addr)
+    def twice_nat_common(self, self_twice_nat=False, same_pg=False, lb=False,
+                         client_id=None):
         twice_nat_addr = '10.0.1.3'
-        local_port = 8080
-        external_port = 80
+
+        port_in = 8080
+        if lb:
+            if not same_pg:
+                port_in1 = port_in
+                port_in2 = port_in
+            else:
+                port_in1 = port_in+1
+                port_in2 = port_in+2
+
+        port_out = 80
         eh_port_out = 4567
-        eh_port_in = 0
+
         server1 = self.pg0.remote_hosts[0]
         server2 = self.pg0.remote_hosts[1]
+        if lb and same_pg:
+            server2 = server1
+        if not lb:
+            server = server1
+
+        pg0 = self.pg0
+        if same_pg:
+            pg1 = self.pg0
+        else:
+            pg1 = self.pg1
 
-        locals = [{'addr': server1.ip4n,
-                   'port': local_port,
-                   'probability': 50},
-                  {'addr': server2.ip4n,
-                   'port': local_port,
-                   'probability': 50}]
+        eh_translate = ((not self_twice_nat) or (not lb and same_pg) or
+                        client_id == 1)
 
         self.nat44_add_address(self.nat_addr)
         self.nat44_add_address(twice_nat_addr, twice_nat=1)
-
-        self.vapi.nat44_add_del_lb_static_mapping(external_addr_n,
-                                                  external_port,
-                                                  IP_PROTOS.tcp,
-                                                  twice_nat=1,
-                                                  local_num=len(locals),
-                                                  locals=locals)
-        self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
-        self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+        if not lb:
+            self.nat44_add_static_mapping(pg0.remote_ip4, self.nat_addr,
+                                          port_in, port_out,
+                                          proto=IP_PROTOS.tcp,
+                                          twice_nat=int(not self_twice_nat),
+                                          self_twice_nat=int(self_twice_nat))
+        else:
+            locals = [{'addr': server1.ip4n,
+                       'port': port_in1,
+                       'probability': 50},
+                      {'addr': server2.ip4n,
+                       'port': port_in2,
+                       'probability': 50}]
+            out_addr_n = socket.inet_pton(socket.AF_INET, self.nat_addr)
+            self.vapi.nat44_add_del_lb_static_mapping(out_addr_n,
+                                                      port_out,
+                                                      IP_PROTOS.tcp,
+                                                      twice_nat=int(
+                                                          not self_twice_nat),
+                                                      self_twice_nat=int(
+                                                          self_twice_nat),
+                                                      local_num=len(locals),
+                                                      locals=locals)
+        self.vapi.nat44_interface_add_del_feature(pg0.sw_if_index)
+        self.vapi.nat44_interface_add_del_feature(pg1.sw_if_index,
                                                   is_inside=0)
 
-        p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
-             IP(src=self.pg1.remote_ip4, dst=self.nat_addr) /
-             TCP(sport=eh_port_out, dport=external_port))
-        self.pg1.add_stream(p)
+        if same_pg:
+            if not lb:
+                client = server
+            else:
+                assert client_id is not None
+                if client_id == 1:
+                    client = self.pg0.remote_hosts[0]
+                elif client_id == 2:
+                    client = self.pg0.remote_hosts[1]
+        else:
+            client = pg1.remote_hosts[0]
+        p = (Ether(src=pg1.remote_mac, dst=pg1.local_mac) /
+             IP(src=client.ip4, dst=self.nat_addr) /
+             TCP(sport=eh_port_out, dport=port_out))
+        pg1.add_stream(p)
         self.pg_enable_capture(self.pg_interfaces)
         self.pg_start()
-        capture = self.pg0.get_capture(1)
+        capture = pg0.get_capture(1)
         p = capture[0]
-        server = None
         try:
             ip = p[IP]
             tcp = p[TCP]
-            self.assertEqual(ip.src, twice_nat_addr)
-            self.assertIn(ip.dst, [server1.ip4, server2.ip4])
-            if ip.dst == server1.ip4:
-                server = server1
+            if lb:
+                if ip.dst == server1.ip4:
+                    server = server1
+                    port_in = port_in1
+                else:
+                    server = server2
+                    port_in = port_in2
+            self.assertEqual(ip.dst, server.ip4)
+            if lb and same_pg:
+                self.assertIn(tcp.dport, [port_in1, port_in2])
             else:
-                server = server2
-            self.assertNotEqual(tcp.sport, eh_port_out)
+                self.assertEqual(tcp.dport, port_in)
+            if eh_translate:
+                self.assertEqual(ip.src, twice_nat_addr)
+                self.assertNotEqual(tcp.sport, eh_port_out)
+            else:
+                self.assertEqual(ip.src, client.ip4)
+                self.assertEqual(tcp.sport, eh_port_out)
+            eh_addr_in = ip.src
             eh_port_in = tcp.sport
-            self.assertEqual(tcp.dport, local_port)
+            saved_port_in = tcp.dport
             self.check_tcp_checksum(p)
             self.check_ip_checksum(p)
         except:
             self.logger.error(ppp("Unexpected or invalid packet:", p))
             raise
 
-        p = (Ether(src=server.mac, dst=self.pg0.local_mac) /
-             IP(src=server.ip4, dst=twice_nat_addr) /
-             TCP(sport=local_port, dport=eh_port_in))
-        self.pg0.add_stream(p)
+        p = (Ether(src=server.mac, dst=pg0.local_mac) /
+             IP(src=server.ip4, dst=eh_addr_in) /
+             TCP(sport=saved_port_in, dport=eh_port_in))
+        pg0.add_stream(p)
         self.pg_enable_capture(self.pg_interfaces)
         self.pg_start()
-        capture = self.pg1.get_capture(1)
+        capture = pg1.get_capture(1)
         p = capture[0]
         try:
             ip = p[IP]
             tcp = p[TCP]
+            self.assertEqual(ip.dst, client.ip4)
             self.assertEqual(ip.src, self.nat_addr)
-            self.assertEqual(ip.dst, self.pg1.remote_ip4)
-            self.assertEqual(tcp.sport, external_port)
             self.assertEqual(tcp.dport, eh_port_out)
+            self.assertEqual(tcp.sport, port_out)
             self.check_tcp_checksum(p)
             self.check_ip_checksum(p)
         except:
             self.logger.error(ppp("Unexpected or invalid packet:", p))
             raise
 
+    def test_twice_nat(self):
+        """ Twice NAT44 """
+        self.twice_nat_common()
+
+    def test_self_twice_nat_positive(self):
+        """ Self Twice NAT44 (positive test) """
+        self.twice_nat_common(self_twice_nat=True, same_pg=True)
+
+    def test_self_twice_nat_negative(self):
+        """ Self Twice NAT44 (negative test) """
+        self.twice_nat_common(self_twice_nat=True)
+
+    def test_twice_nat_lb(self):
+        """ Twice NAT44 local service load balancing """
+        self.twice_nat_common(lb=True)
+
+    def test_self_twice_nat_lb_positive(self):
+        """ Self Twice NAT44 local service load balancing (positive test) """
+        self.twice_nat_common(lb=True, self_twice_nat=True, same_pg=True,
+                              client_id=1)
+
+    def test_self_twice_nat_lb_negative(self):
+        """ Self Twice NAT44 local service load balancing (negative test) """
+        self.twice_nat_common(lb=True, self_twice_nat=True, same_pg=True,
+                              client_id=2)
+
     def test_twice_nat_interface_addr(self):
         """ Acquire twice NAT44 addresses from interface """
         self.vapi.nat44_add_interface_addr(self.pg7.sw_if_index, twice_nat=1)
@@ -3444,7 +4080,11 @@ class TestNAT44(MethodHolder):
     def tearDown(self):
         super(TestNAT44, self).tearDown()
         if not self.vpp_dead:
-            self.logger.info(self.vapi.cli("show nat44 verbose"))
+            self.logger.info(self.vapi.cli("show nat44 addresses"))
+            self.logger.info(self.vapi.cli("show nat44 interfaces"))
+            self.logger.info(self.vapi.cli("show nat44 static mappings"))
+            self.logger.info(self.vapi.cli("show nat44 interface address"))
+            self.logger.info(self.vapi.cli("show nat44 sessions detail"))
             self.logger.info(self.vapi.cli("show nat virtual-reassembly"))
             self.vapi.cli("nat addr-port-assignment-alg default")
             self.clear_nat44()
@@ -4208,7 +4848,13 @@ class TestDeterministicNAT(MethodHolder):
     def tearDown(self):
         super(TestDeterministicNAT, self).tearDown()
         if not self.vpp_dead:
-            self.logger.info(self.vapi.cli("show nat44 detail"))
+            self.logger.info(self.vapi.cli("show nat44 interfaces"))
+            self.logger.info(
+                self.vapi.cli("show nat44 deterministic mappings"))
+            self.logger.info(
+                self.vapi.cli("show nat44 deterministic timeouts"))
+            self.logger.info(
+                self.vapi.cli("show nat44 deterministic sessions"))
             self.clear_nat_det()
 
 
@@ -5608,5 +6254,263 @@ class TestDSlite(MethodHolder):
                 self.vapi.cli("show dslite aftr-tunnel-endpoint-address"))
             self.logger.info(self.vapi.cli("show dslite sessions"))
 
+
+class TestDSliteCE(MethodHolder):
+    """ DS-Lite CE Test Cases """
+
+    @classmethod
+    def setUpConstants(cls):
+        super(TestDSliteCE, cls).setUpConstants()
+        cls.vpp_cmdline.extend(["nat", "{", "dslite ce", "}"])
+
+    @classmethod
+    def setUpClass(cls):
+        super(TestDSliteCE, cls).setUpClass()
+
+        try:
+            cls.create_pg_interfaces(range(2))
+            cls.pg0.admin_up()
+            cls.pg0.config_ip4()
+            cls.pg0.resolve_arp()
+            cls.pg1.admin_up()
+            cls.pg1.config_ip6()
+            cls.pg1.generate_remote_hosts(1)
+            cls.pg1.configure_ipv6_neighbors()
+
+        except Exception:
+            super(TestDSliteCE, cls).tearDownClass()
+            raise
+
+    def test_dslite_ce(self):
+        """ Test DS-Lite CE """
+
+        b4_ip4 = '192.0.0.2'
+        b4_ip4_n = socket.inet_pton(socket.AF_INET, b4_ip4)
+        b4_ip6 = '2001:db8:62aa::375e:f4c1:1'
+        b4_ip6_n = socket.inet_pton(socket.AF_INET6, b4_ip6)
+        self.vapi.dslite_set_b4_addr(b4_ip6_n, b4_ip4_n)
+
+        aftr_ip4 = '192.0.0.1'
+        aftr_ip4_n = socket.inet_pton(socket.AF_INET, aftr_ip4)
+        aftr_ip6 = '2001:db8:85a3::8a2e:370:1'
+        aftr_ip6_n = socket.inet_pton(socket.AF_INET6, aftr_ip6)
+        self.vapi.dslite_set_aftr_addr(aftr_ip6_n, aftr_ip4_n)
+
+        self.vapi.ip_add_del_route(dst_address=aftr_ip6_n,
+                                   dst_address_length=128,
+                                   next_hop_address=self.pg1.remote_ip6n,
+                                   next_hop_sw_if_index=self.pg1.sw_if_index,
+                                   is_ipv6=1)
+
+        # UDP encapsulation
+        p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+             IP(dst=self.pg1.remote_ip4, src=self.pg0.remote_ip4) /
+             UDP(sport=10000, dport=20000))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(1)
+        capture = capture[0]
+        self.assertEqual(capture[IPv6].src, b4_ip6)
+        self.assertEqual(capture[IPv6].dst, aftr_ip6)
+        self.assertEqual(capture[IP].src, self.pg0.remote_ip4)
+        self.assertEqual(capture[IP].dst, self.pg1.remote_ip4)
+        self.assertEqual(capture[UDP].sport, 10000)
+        self.assertEqual(capture[UDP].dport, 20000)
+        self.check_ip_checksum(capture)
+
+        # UDP decapsulation
+        p = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
+             IPv6(dst=b4_ip6, src=aftr_ip6) /
+             IP(dst=self.pg0.remote_ip4, src=self.pg1.remote_ip4) /
+             UDP(sport=20000, dport=10000))
+        self.pg1.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg0.get_capture(1)
+        capture = capture[0]
+        self.assertFalse(capture.haslayer(IPv6))
+        self.assertEqual(capture[IP].src, self.pg1.remote_ip4)
+        self.assertEqual(capture[IP].dst, self.pg0.remote_ip4)
+        self.assertEqual(capture[UDP].sport, 20000)
+        self.assertEqual(capture[UDP].dport, 10000)
+        self.check_ip_checksum(capture)
+
+        # ping DS-Lite B4 tunnel endpoint address
+        p = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
+             IPv6(src=self.pg1.remote_hosts[0].ip6, dst=b4_ip6) /
+             ICMPv6EchoRequest())
+        self.pg1.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(1)
+        self.assertEqual(1, len(capture))
+        capture = capture[0]
+        self.assertEqual(capture[IPv6].src, b4_ip6)
+        self.assertEqual(capture[IPv6].dst, self.pg1.remote_hosts[0].ip6)
+        self.assertTrue(capture.haslayer(ICMPv6EchoReply))
+
+    def tearDown(self):
+        super(TestDSliteCE, self).tearDown()
+        if not self.vpp_dead:
+            self.logger.info(
+                self.vapi.cli("show dslite aftr-tunnel-endpoint-address"))
+            self.logger.info(
+                self.vapi.cli("show dslite b4-tunnel-endpoint-address"))
+
+
+class TestNAT66(MethodHolder):
+    """ NAT66 Test Cases """
+
+    @classmethod
+    def setUpClass(cls):
+        super(TestNAT66, cls).setUpClass()
+
+        try:
+            cls.nat_addr = 'fd01:ff::2'
+            cls.nat_addr_n = socket.inet_pton(socket.AF_INET6, cls.nat_addr)
+
+            cls.create_pg_interfaces(range(2))
+            cls.interfaces = list(cls.pg_interfaces)
+
+            for i in cls.interfaces:
+                i.admin_up()
+                i.config_ip6()
+                i.configure_ipv6_neighbors()
+
+        except Exception:
+            super(TestNAT66, cls).tearDownClass()
+            raise
+
+    def test_static(self):
+        """ 1:1 NAT66 test """
+        self.vapi.nat66_add_del_interface(self.pg0.sw_if_index)
+        self.vapi.nat66_add_del_interface(self.pg1.sw_if_index, is_inside=0)
+        self.vapi.nat66_add_del_static_mapping(self.pg0.remote_ip6n,
+                                               self.nat_addr_n)
+
+        # in2out
+        pkts = []
+        p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+             IPv6(src=self.pg0.remote_ip6, dst=self.pg1.remote_ip6) /
+             TCP())
+        pkts.append(p)
+        p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+             IPv6(src=self.pg0.remote_ip6, dst=self.pg1.remote_ip6) /
+             UDP())
+        pkts.append(p)
+        p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+             IPv6(src=self.pg0.remote_ip6, dst=self.pg1.remote_ip6) /
+             ICMPv6EchoRequest())
+        pkts.append(p)
+        p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+             IPv6(src=self.pg0.remote_ip6, dst=self.pg1.remote_ip6) /
+             GRE() / IP() / TCP())
+        pkts.append(p)
+        self.pg0.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(len(pkts))
+        for packet in capture:
+            try:
+                self.assertEqual(packet[IPv6].src, self.nat_addr)
+                self.assertEqual(packet[IPv6].dst, self.pg1.remote_ip6)
+                if packet.haslayer(TCP):
+                    self.check_tcp_checksum(packet)
+                elif packet.haslayer(UDP):
+                    self.check_udp_checksum(packet)
+                elif packet.haslayer(ICMPv6EchoRequest):
+                    self.check_icmpv6_checksum(packet)
+            except:
+                self.logger.error(ppp("Unexpected or invalid packet:", packet))
+                raise
+
+        # out2in
+        pkts = []
+        p = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
+             IPv6(src=self.pg1.remote_ip6, dst=self.nat_addr) /
+             TCP())
+        pkts.append(p)
+        p = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
+             IPv6(src=self.pg1.remote_ip6, dst=self.nat_addr) /
+             UDP())
+        pkts.append(p)
+        p = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
+             IPv6(src=self.pg1.remote_ip6, dst=self.nat_addr) /
+             ICMPv6EchoReply())
+        pkts.append(p)
+        p = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
+             IPv6(src=self.pg1.remote_ip6, dst=self.nat_addr) /
+             GRE() / IP() / TCP())
+        pkts.append(p)
+        self.pg1.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg0.get_capture(len(pkts))
+        for packet in capture:
+            try:
+                self.assertEqual(packet[IPv6].src, self.pg1.remote_ip6)
+                self.assertEqual(packet[IPv6].dst, self.pg0.remote_ip6)
+                if packet.haslayer(TCP):
+                    self.check_tcp_checksum(packet)
+                elif packet.haslayer(UDP):
+                    self.check_udp_checksum(packet)
+                elif packet.haslayer(ICMPv6EchoReply):
+                    self.check_icmpv6_checksum(packet)
+            except:
+                self.logger.error(ppp("Unexpected or invalid packet:", packet))
+                raise
+
+        sm = self.vapi.nat66_static_mapping_dump()
+        self.assertEqual(len(sm), 1)
+        self.assertEqual(sm[0].total_pkts, 8)
+
+    def test_check_no_translate(self):
+        """ NAT66 translate only when egress interface is outside interface """
+        self.vapi.nat66_add_del_interface(self.pg0.sw_if_index)
+        self.vapi.nat66_add_del_interface(self.pg1.sw_if_index)
+        self.vapi.nat66_add_del_static_mapping(self.pg0.remote_ip6n,
+                                               self.nat_addr_n)
+
+        # in2out
+        p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+             IPv6(src=self.pg0.remote_ip6, dst=self.pg1.remote_ip6) /
+             UDP())
+        self.pg0.add_stream([p])
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(1)
+        packet = capture[0]
+        try:
+            self.assertEqual(packet[IPv6].src, self.pg0.remote_ip6)
+            self.assertEqual(packet[IPv6].dst, self.pg1.remote_ip6)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", packet))
+            raise
+
+    def clear_nat66(self):
+        """
+        Clear NAT66 configuration.
+        """
+        interfaces = self.vapi.nat66_interface_dump()
+        for intf in interfaces:
+            self.vapi.nat66_add_del_interface(intf.sw_if_index,
+                                              intf.is_inside,
+                                              is_add=0)
+
+        static_mappings = self.vapi.nat66_static_mapping_dump()
+        for sm in static_mappings:
+            self.vapi.nat66_add_del_static_mapping(sm.local_ip_address,
+                                                   sm.external_ip_address,
+                                                   sm.vrf_id,
+                                                   is_add=0)
+
+    def tearDown(self):
+        super(TestNAT66, self).tearDown()
+        if not self.vpp_dead:
+            self.logger.info(self.vapi.cli("show nat66 interfaces"))
+            self.logger.info(self.vapi.cli("show nat66 static mappings"))
+            self.clear_nat66()
+
 if __name__ == '__main__':
     unittest.main(testRunner=VppTestRunner)