MAP: Use explicit address/prefix types in API
[vpp.git] / test / test_nat.py
index aeeb5aa..524301f 100644 (file)
@@ -7,10 +7,10 @@ import StringIO
 import random
 
 from framework import VppTestCase, VppTestRunner, running_extended_tests
 import random
 
 from framework import VppTestCase, VppTestRunner, running_extended_tests
-from vpp_ip_route import VppIpRoute, VppRoutePath, DpoProto
 from scapy.layers.inet import IP, TCP, UDP, ICMP
 from scapy.layers.inet import IPerror, TCPerror, UDPerror, ICMPerror
 from scapy.layers.inet import IP, TCP, UDP, ICMP
 from scapy.layers.inet import IPerror, TCPerror, UDPerror, ICMPerror
-from scapy.layers.inet6 import IPv6, ICMPv6EchoRequest, ICMPv6EchoReply
+from scapy.layers.inet6 import IPv6, ICMPv6EchoRequest, ICMPv6EchoReply, \
+    ICMPv6ND_NS, ICMPv6ND_NA, ICMPv6NDOptDstLLAddr
 from scapy.layers.inet6 import ICMPv6DestUnreach, IPerror6, IPv6ExtHdrFragment
 from scapy.layers.l2 import Ether, ARP, GRE
 from scapy.data import IP_PROTOS
 from scapy.layers.inet6 import ICMPv6DestUnreach, IPerror6, IPv6ExtHdrFragment
 from scapy.layers.l2 import Ether, ARP, GRE
 from scapy.data import IP_PROTOS
@@ -26,138 +26,205 @@ from util import mactobinary
 class MethodHolder(VppTestCase):
     """ NAT create capture and verify method holder """
 
 class MethodHolder(VppTestCase):
     """ NAT create capture and verify method holder """
 
-    @classmethod
-    def setUpClass(cls):
-        super(MethodHolder, cls).setUpClass()
+    def clear_nat44(self):
+        """
+        Clear NAT44 configuration.
+        """
+        if hasattr(self, 'pg7') and hasattr(self, 'pg8'):
+            # I found no elegant way to do this
+            self.vapi.ip_add_del_route(
+                dst_address=self.pg7.remote_ip4n,
+                dst_address_length=32,
+                next_hop_address=self.pg7.remote_ip4n,
+                next_hop_sw_if_index=self.pg7.sw_if_index,
+                is_add=0)
+            self.vapi.ip_add_del_route(
+                dst_address=self.pg8.remote_ip4n,
+                dst_address_length=32,
+                next_hop_address=self.pg8.remote_ip4n,
+                next_hop_sw_if_index=self.pg8.sw_if_index,
+                is_add=0)
 
 
-    def tearDown(self):
-        super(MethodHolder, self).tearDown()
+            for intf in [self.pg7, self.pg8]:
+                neighbors = self.vapi.ip_neighbor_dump(intf.sw_if_index)
+                for n in neighbors:
+                    self.vapi.ip_neighbor_add_del(intf.sw_if_index,
+                                                  n.mac_address,
+                                                  n.ip_address,
+                                                  is_add=0)
 
 
-    def check_ip_checksum(self, pkt):
-        """
-        Check IP checksum of the packet
+            if self.pg7.has_ip4_config:
+                self.pg7.unconfig_ip4()
 
 
-        :param pkt: Packet to check IP checksum
-        """
-        new = pkt.__class__(str(pkt))
-        del new['IP'].chksum
-        new = new.__class__(str(new))
-        self.assertEqual(new['IP'].chksum, pkt['IP'].chksum)
+        self.vapi.nat44_forwarding_enable_disable(0)
 
 
-    def check_tcp_checksum(self, pkt):
-        """
-        Check TCP checksum in IP packet
+        interfaces = self.vapi.nat44_interface_addr_dump()
+        for intf in interfaces:
+            self.vapi.nat44_add_interface_addr(intf.sw_if_index,
+                                               twice_nat=intf.twice_nat,
+                                               is_add=0)
 
 
-        :param pkt: Packet to check TCP checksum
-        """
-        new = pkt.__class__(str(pkt))
-        del new['TCP'].chksum
-        new = new.__class__(str(new))
-        self.assertEqual(new['TCP'].chksum, pkt['TCP'].chksum)
+        self.vapi.nat_ipfix(enable=0, src_port=self.ipfix_src_port,
+                            domain_id=self.ipfix_domain_id)
+        self.ipfix_src_port = 4739
+        self.ipfix_domain_id = 1
 
 
-    def check_udp_checksum(self, pkt):
-        """
-        Check UDP checksum in IP packet
+        interfaces = self.vapi.nat44_interface_dump()
+        for intf in interfaces:
+            if intf.is_inside > 1:
+                self.vapi.nat44_interface_add_del_feature(intf.sw_if_index,
+                                                          0,
+                                                          is_add=0)
+            self.vapi.nat44_interface_add_del_feature(intf.sw_if_index,
+                                                      intf.is_inside,
+                                                      is_add=0)
 
 
-        :param pkt: Packet to check UDP checksum
-        """
-        new = pkt.__class__(str(pkt))
-        del new['UDP'].chksum
-        new = new.__class__(str(new))
-        self.assertEqual(new['UDP'].chksum, pkt['UDP'].chksum)
+        interfaces = self.vapi.nat44_interface_output_feature_dump()
+        for intf in interfaces:
+            self.vapi.nat44_interface_add_del_output_feature(intf.sw_if_index,
+                                                             intf.is_inside,
+                                                             is_add=0)
 
 
-    def check_icmp_errror_embedded(self, pkt):
-        """
-        Check ICMP error embeded packet checksum
+        static_mappings = self.vapi.nat44_static_mapping_dump()
+        for sm in static_mappings:
+            self.vapi.nat44_add_del_static_mapping(
+                sm.local_ip_address,
+                sm.external_ip_address,
+                local_port=sm.local_port,
+                external_port=sm.external_port,
+                addr_only=sm.addr_only,
+                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)
 
 
-        :param pkt: Packet to check ICMP error embeded packet checksum
-        """
-        if pkt.haslayer(IPerror):
-            new = pkt.__class__(str(pkt))
-            del new['IPerror'].chksum
-            new = new.__class__(str(new))
-            self.assertEqual(new['IPerror'].chksum, pkt['IPerror'].chksum)
-
-        if pkt.haslayer(TCPerror):
-            new = pkt.__class__(str(pkt))
-            del new['TCPerror'].chksum
-            new = new.__class__(str(new))
-            self.assertEqual(new['TCPerror'].chksum, pkt['TCPerror'].chksum)
-
-        if pkt.haslayer(UDPerror):
-            if pkt['UDPerror'].chksum != 0:
-                new = pkt.__class__(str(pkt))
-                del new['UDPerror'].chksum
-                new = new.__class__(str(new))
-                self.assertEqual(new['UDPerror'].chksum,
-                                 pkt['UDPerror'].chksum)
-
-        if pkt.haslayer(ICMPerror):
-            del new['ICMPerror'].chksum
-            new = new.__class__(str(new))
-            self.assertEqual(new['ICMPerror'].chksum, pkt['ICMPerror'].chksum)
-
-    def check_icmp_checksum(self, pkt):
+        lb_static_mappings = self.vapi.nat44_lb_static_mapping_dump()
+        for lb_sm in lb_static_mappings:
+            self.vapi.nat44_add_del_lb_static_mapping(
+                lb_sm.external_addr,
+                lb_sm.external_port,
+                lb_sm.protocol,
+                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=[])
+
+        identity_mappings = self.vapi.nat44_identity_mapping_dump()
+        for id_m in identity_mappings:
+            self.vapi.nat44_add_del_identity_mapping(
+                addr_only=id_m.addr_only,
+                ip=id_m.ip_address,
+                port=id_m.port,
+                sw_if_index=id_m.sw_if_index,
+                vrf_id=id_m.vrf_id,
+                protocol=id_m.protocol,
+                is_add=0)
+
+        adresses = self.vapi.nat44_address_dump()
+        for addr in adresses:
+            self.vapi.nat44_add_del_address_range(addr.ip_address,
+                                                  addr.ip_address,
+                                                  twice_nat=addr.twice_nat,
+                                                  is_add=0)
+
+        self.vapi.nat_set_reass()
+        self.vapi.nat_set_reass(is_ip6=1)
+        self.verify_no_nat44_user()
+        self.vapi.nat_set_timeouts()
+        self.vapi.nat_set_addr_and_port_alloc_alg()
+        self.vapi.nat_set_mss_clamping()
+
+    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, self_twice_nat=0,
+                                 out2in_only=0, tag=""):
         """
         """
-        Check ICMP checksum in IPv4 packet
+        Add/delete NAT44 static mapping
 
 
-        :param pkt: Packet to check ICMP checksum
+        :param local_ip: Local IP address
+        :param external_ip: External IP address
+        :param local_port: Local port number (Optional)
+        :param external_port: External port number (Optional)
+        :param vrf_id: VRF ID (Default 0)
+        :param is_add: 1 if add, 0 if delete (Default add)
+        :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
         """
         """
-        new = pkt.__class__(str(pkt))
-        del new['ICMP'].chksum
-        new = new.__class__(str(new))
-        self.assertEqual(new['ICMP'].chksum, pkt['ICMP'].chksum)
-        if pkt.haslayer(IPerror):
-            self.check_icmp_errror_embedded(pkt)
-
-    def check_icmpv6_checksum(self, pkt):
+        addr_only = 1
+        if local_port and external_port:
+            addr_only = 0
+        l_ip = socket.inet_pton(socket.AF_INET, local_ip)
+        e_ip = socket.inet_pton(socket.AF_INET, external_ip)
+        self.vapi.nat44_add_del_static_mapping(
+            l_ip,
+            e_ip,
+            external_sw_if_index,
+            local_port,
+            external_port,
+            addr_only,
+            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):
         """
         """
-        Check ICMPv6 checksum in IPv4 packet
+        Add/delete NAT44 address
 
 
-        :param pkt: Packet to check ICMPv6 checksum
+        :param ip: IP address
+        :param is_add: 1 if add, 0 if delete (Default add)
+        :param twice_nat: twice NAT address for extenal hosts
         """
         """
-        new = pkt.__class__(str(pkt))
-        if pkt.haslayer(ICMPv6DestUnreach):
-            del new['ICMPv6DestUnreach'].cksum
-            new = new.__class__(str(new))
-            self.assertEqual(new['ICMPv6DestUnreach'].cksum,
-                             pkt['ICMPv6DestUnreach'].cksum)
-            self.check_icmp_errror_embedded(pkt)
-        if pkt.haslayer(ICMPv6EchoRequest):
-            del new['ICMPv6EchoRequest'].cksum
-            new = new.__class__(str(new))
-            self.assertEqual(new['ICMPv6EchoRequest'].cksum,
-                             pkt['ICMPv6EchoRequest'].cksum)
-        if pkt.haslayer(ICMPv6EchoReply):
-            del new['ICMPv6EchoReply'].cksum
-            new = new.__class__(str(new))
-            self.assertEqual(new['ICMPv6EchoReply'].cksum,
-                             pkt['ICMPv6EchoReply'].cksum)
+        nat_addr = socket.inet_pton(socket.AF_INET, ip)
+        self.vapi.nat44_add_del_address_range(nat_addr, nat_addr, is_add,
+                                              vrf_id=vrf_id,
+                                              twice_nat=twice_nat)
 
 
-    def create_stream_in(self, in_if, out_if, ttl=64):
+    def create_stream_in(self, in_if, out_if, dst_ip=None, ttl=64):
         """
         Create packet stream for inside network
 
         :param in_if: Inside interface
         :param out_if: Outside interface
         """
         Create packet stream for inside network
 
         :param in_if: Inside interface
         :param out_if: Outside interface
+        :param dst_ip: Destination address
         :param ttl: TTL of generated packets
         """
         :param ttl: TTL of generated packets
         """
+        if dst_ip is None:
+            dst_ip = out_if.remote_ip4
+
         pkts = []
         # TCP
         p = (Ether(dst=in_if.local_mac, src=in_if.remote_mac) /
         pkts = []
         # TCP
         p = (Ether(dst=in_if.local_mac, src=in_if.remote_mac) /
-             IP(src=in_if.remote_ip4, dst=out_if.remote_ip4, ttl=ttl) /
+             IP(src=in_if.remote_ip4, dst=dst_ip, ttl=ttl) /
              TCP(sport=self.tcp_port_in, dport=20))
         pkts.append(p)
 
         # UDP
         p = (Ether(dst=in_if.local_mac, src=in_if.remote_mac) /
              TCP(sport=self.tcp_port_in, dport=20))
         pkts.append(p)
 
         # UDP
         p = (Ether(dst=in_if.local_mac, src=in_if.remote_mac) /
-             IP(src=in_if.remote_ip4, dst=out_if.remote_ip4, ttl=ttl) /
+             IP(src=in_if.remote_ip4, dst=dst_ip, ttl=ttl) /
              UDP(sport=self.udp_port_in, dport=20))
         pkts.append(p)
 
         # ICMP
         p = (Ether(dst=in_if.local_mac, src=in_if.remote_mac) /
              UDP(sport=self.udp_port_in, dport=20))
         pkts.append(p)
 
         # ICMP
         p = (Ether(dst=in_if.local_mac, src=in_if.remote_mac) /
-             IP(src=in_if.remote_ip4, dst=out_if.remote_ip4, ttl=ttl) /
+             IP(src=in_if.remote_ip4, dst=dst_ip, ttl=ttl) /
              ICMP(id=self.icmp_id_in, type='echo-request'))
         pkts.append(p)
 
              ICMP(id=self.icmp_id_in, type='echo-request'))
         pkts.append(p)
 
@@ -206,6 +273,48 @@ class MethodHolder(VppTestCase):
             pref_n[15] = ip4_n[3]
         return socket.inet_ntop(socket.AF_INET6, ''.join(pref_n))
 
             pref_n[15] = ip4_n[3]
         return socket.inet_ntop(socket.AF_INET6, ''.join(pref_n))
 
+    def extract_ip4(self, ip6, plen):
+        """
+        Extract IPv4 address embedded in IPv6 addresses
+
+        :param ip6: IPv6 address
+        :param plen: IPv6 prefix length
+        :returns: extracted IPv4 address
+        """
+        ip6_n = list(socket.inet_pton(socket.AF_INET6, ip6))
+        ip4_n = [None] * 4
+        if plen == 32:
+            ip4_n[0] = ip6_n[4]
+            ip4_n[1] = ip6_n[5]
+            ip4_n[2] = ip6_n[6]
+            ip4_n[3] = ip6_n[7]
+        elif plen == 40:
+            ip4_n[0] = ip6_n[5]
+            ip4_n[1] = ip6_n[6]
+            ip4_n[2] = ip6_n[7]
+            ip4_n[3] = ip6_n[9]
+        elif plen == 48:
+            ip4_n[0] = ip6_n[6]
+            ip4_n[1] = ip6_n[7]
+            ip4_n[2] = ip6_n[9]
+            ip4_n[3] = ip6_n[10]
+        elif plen == 56:
+            ip4_n[0] = ip6_n[7]
+            ip4_n[1] = ip6_n[9]
+            ip4_n[2] = ip6_n[10]
+            ip4_n[3] = ip6_n[11]
+        elif plen == 64:
+            ip4_n[0] = ip6_n[9]
+            ip4_n[1] = ip6_n[10]
+            ip4_n[2] = ip6_n[11]
+            ip4_n[3] = ip6_n[12]
+        elif plen == 96:
+            ip4_n[0] = ip6_n[12]
+            ip4_n[1] = ip6_n[13]
+            ip4_n[2] = ip6_n[14]
+            ip4_n[3] = ip6_n[15]
+        return socket.inet_ntop(socket.AF_INET, ''.join(ip4_n))
+
     def create_stream_in_ip6(self, in_if, out_if, hlim=64, pref=None, plen=0):
         """
         Create IPv6 packet stream for inside network
     def create_stream_in_ip6(self, in_if, out_if, hlim=64, pref=None, plen=0):
         """
         Create IPv6 packet stream for inside network
@@ -284,26 +393,61 @@ class MethodHolder(VppTestCase):
 
         return pkts
 
 
         return pkts
 
+    def create_stream_out_ip6(self, out_if, src_ip, dst_ip, hl=64):
+        """
+        Create packet stream for outside network
+
+        :param out_if: Outside interface
+        :param dst_ip: Destination IP address (Default use global NAT address)
+        :param hl: HL of generated packets
+        """
+        pkts = []
+        # TCP
+        p = (Ether(dst=out_if.local_mac, src=out_if.remote_mac) /
+             IPv6(src=src_ip, dst=dst_ip, hlim=hl) /
+             TCP(dport=self.tcp_port_out, sport=20))
+        pkts.append(p)
+
+        # UDP
+        p = (Ether(dst=out_if.local_mac, src=out_if.remote_mac) /
+             IPv6(src=src_ip, dst=dst_ip, hlim=hl) /
+             UDP(dport=self.udp_port_out, sport=20))
+        pkts.append(p)
+
+        # ICMP
+        p = (Ether(dst=out_if.local_mac, src=out_if.remote_mac) /
+             IPv6(src=src_ip, dst=dst_ip, hlim=hl) /
+             ICMPv6EchoReply(id=self.icmp_id_out))
+        pkts.append(p)
+
+        return pkts
+
     def verify_capture_out(self, capture, nat_ip=None, same_port=False,
     def verify_capture_out(self, capture, nat_ip=None, same_port=False,
-                           packet_num=3, dst_ip=None):
+                           dst_ip=None, is_ip6=False):
         """
         Verify captured packets on outside network
 
         :param capture: Captured packets
         :param nat_ip: Translated IP address (Default use global NAT address)
         :param same_port: Sorce port number is not translated (Default False)
         """
         Verify captured packets on outside network
 
         :param capture: Captured packets
         :param nat_ip: Translated IP address (Default use global NAT address)
         :param same_port: Sorce port number is not translated (Default False)
-        :param packet_num: Expected number of packets (Default 3)
         :param dst_ip: Destination IP address (Default do not verify)
         :param dst_ip: Destination IP address (Default do not verify)
+        :param is_ip6: If L3 protocol is IPv6 (Default False)
         """
         """
+        if is_ip6:
+            IP46 = IPv6
+            ICMP46 = ICMPv6EchoRequest
+        else:
+            IP46 = IP
+            ICMP46 = ICMP
         if nat_ip is None:
             nat_ip = self.nat_addr
         if nat_ip is None:
             nat_ip = self.nat_addr
-        self.assertEqual(packet_num, len(capture))
         for packet in capture:
             try:
         for packet in capture:
             try:
-                self.check_ip_checksum(packet)
-                self.assertEqual(packet[IP].src, nat_ip)
+                if not is_ip6:
+                    self.assert_packet_checksums_valid(packet)
+                self.assertEqual(packet[IP46].src, nat_ip)
                 if dst_ip is not None:
                 if dst_ip is not None:
-                    self.assertEqual(packet[IP].dst, dst_ip)
+                    self.assertEqual(packet[IP46].dst, dst_ip)
                 if packet.haslayer(TCP):
                     if same_port:
                         self.assertEqual(packet[TCP].sport, self.tcp_port_in)
                 if packet.haslayer(TCP):
                     if same_port:
                         self.assertEqual(packet[TCP].sport, self.tcp_port_in)
@@ -311,7 +455,7 @@ class MethodHolder(VppTestCase):
                         self.assertNotEqual(
                             packet[TCP].sport, self.tcp_port_in)
                     self.tcp_port_out = packet[TCP].sport
                         self.assertNotEqual(
                             packet[TCP].sport, self.tcp_port_in)
                     self.tcp_port_out = packet[TCP].sport
-                    self.check_tcp_checksum(packet)
+                    self.assert_packet_checksums_valid(packet)
                 elif packet.haslayer(UDP):
                     if same_port:
                         self.assertEqual(packet[UDP].sport, self.udp_port_in)
                 elif packet.haslayer(UDP):
                     if same_port:
                         self.assertEqual(packet[UDP].sport, self.udp_port_in)
@@ -321,66 +465,71 @@ class MethodHolder(VppTestCase):
                     self.udp_port_out = packet[UDP].sport
                 else:
                     if same_port:
                     self.udp_port_out = packet[UDP].sport
                 else:
                     if same_port:
-                        self.assertEqual(packet[ICMP].id, self.icmp_id_in)
+                        self.assertEqual(packet[ICMP46].id, self.icmp_id_in)
                     else:
                     else:
-                        self.assertNotEqual(packet[ICMP].id, self.icmp_id_in)
-                    self.icmp_id_out = packet[ICMP].id
-                    self.check_icmp_checksum(packet)
+                        self.assertNotEqual(packet[ICMP46].id, self.icmp_id_in)
+                    self.icmp_id_out = packet[ICMP46].id
+                    self.assert_packet_checksums_valid(packet)
             except:
                 self.logger.error(ppp("Unexpected or invalid packet "
                                       "(outside network):", packet))
                 raise
 
             except:
                 self.logger.error(ppp("Unexpected or invalid packet "
                                       "(outside network):", packet))
                 raise
 
-    def verify_capture_in(self, capture, in_if, packet_num=3):
+    def verify_capture_out_ip6(self, capture, nat_ip, same_port=False,
+                               dst_ip=None):
+        """
+        Verify captured packets on outside network
+
+        :param capture: Captured packets
+        :param nat_ip: Translated IP address
+        :param same_port: Sorce port number is not translated (Default False)
+        :param dst_ip: Destination IP address (Default do not verify)
+        """
+        return self.verify_capture_out(capture, nat_ip, same_port, dst_ip,
+                                       True)
+
+    def verify_capture_in(self, capture, in_if):
         """
         Verify captured packets on inside network
 
         :param capture: Captured packets
         :param in_if: Inside interface
         """
         Verify captured packets on inside network
 
         :param capture: Captured packets
         :param in_if: Inside interface
-        :param packet_num: Expected number of packets (Default 3)
         """
         """
-        self.assertEqual(packet_num, len(capture))
         for packet in capture:
             try:
         for packet in capture:
             try:
-                self.check_ip_checksum(packet)
+                self.assert_packet_checksums_valid(packet)
                 self.assertEqual(packet[IP].dst, in_if.remote_ip4)
                 if packet.haslayer(TCP):
                     self.assertEqual(packet[TCP].dport, self.tcp_port_in)
                 self.assertEqual(packet[IP].dst, in_if.remote_ip4)
                 if packet.haslayer(TCP):
                     self.assertEqual(packet[TCP].dport, self.tcp_port_in)
-                    self.check_tcp_checksum(packet)
                 elif packet.haslayer(UDP):
                     self.assertEqual(packet[UDP].dport, self.udp_port_in)
                 else:
                     self.assertEqual(packet[ICMP].id, self.icmp_id_in)
                 elif packet.haslayer(UDP):
                     self.assertEqual(packet[UDP].dport, self.udp_port_in)
                 else:
                     self.assertEqual(packet[ICMP].id, self.icmp_id_in)
-                    self.check_icmp_checksum(packet)
             except:
                 self.logger.error(ppp("Unexpected or invalid packet "
                                       "(inside network):", packet))
                 raise
 
             except:
                 self.logger.error(ppp("Unexpected or invalid packet "
                                       "(inside network):", packet))
                 raise
 
-    def verify_capture_in_ip6(self, capture, src_ip, dst_ip, packet_num=3):
+    def verify_capture_in_ip6(self, capture, src_ip, dst_ip):
         """
         Verify captured IPv6 packets on inside network
 
         :param capture: Captured packets
         :param src_ip: Source IP
         :param dst_ip: Destination IP address
         """
         Verify captured IPv6 packets on inside network
 
         :param capture: Captured packets
         :param src_ip: Source IP
         :param dst_ip: Destination IP address
-        :param packet_num: Expected number of packets (Default 3)
         """
         """
-        self.assertEqual(packet_num, len(capture))
         for packet in capture:
             try:
                 self.assertEqual(packet[IPv6].src, src_ip)
                 self.assertEqual(packet[IPv6].dst, dst_ip)
         for packet in capture:
             try:
                 self.assertEqual(packet[IPv6].src, src_ip)
                 self.assertEqual(packet[IPv6].dst, dst_ip)
+                self.assert_packet_checksums_valid(packet)
                 if packet.haslayer(TCP):
                     self.assertEqual(packet[TCP].dport, self.tcp_port_in)
                 if packet.haslayer(TCP):
                     self.assertEqual(packet[TCP].dport, self.tcp_port_in)
-                    self.check_tcp_checksum(packet)
                 elif packet.haslayer(UDP):
                     self.assertEqual(packet[UDP].dport, self.udp_port_in)
                 elif packet.haslayer(UDP):
                     self.assertEqual(packet[UDP].dport, self.udp_port_in)
-                    self.check_udp_checksum(packet)
                 else:
                     self.assertEqual(packet[ICMPv6EchoReply].id,
                                      self.icmp_id_in)
                 else:
                     self.assertEqual(packet[ICMPv6EchoReply].id,
                                      self.icmp_id_in)
-                    self.check_icmpv6_checksum(packet)
             except:
                 self.logger.error(ppp("Unexpected or invalid packet "
                                       "(inside network):", packet))
             except:
                 self.logger.error(ppp("Unexpected or invalid packet "
                                       "(inside network):", packet))
@@ -410,20 +559,18 @@ class MethodHolder(VppTestCase):
                 raise
 
     def verify_capture_out_with_icmp_errors(self, capture, src_ip=None,
                 raise
 
     def verify_capture_out_with_icmp_errors(self, capture, src_ip=None,
-                                            packet_num=3, icmp_type=11):
+                                            icmp_type=11):
         """
         Verify captured packets with ICMP errors on outside network
 
         :param capture: Captured packets
         :param src_ip: Translated IP address or IP address of VPP
                        (Default use global NAT address)
         """
         Verify captured packets with ICMP errors on outside network
 
         :param capture: Captured packets
         :param src_ip: Translated IP address or IP address of VPP
                        (Default use global NAT address)
-        :param packet_num: Expected number of packets (Default 3)
         :param icmp_type: Type of error ICMP packet
                           we are expecting (Default 11)
         """
         if src_ip is None:
             src_ip = self.nat_addr
         :param icmp_type: Type of error ICMP packet
                           we are expecting (Default 11)
         """
         if src_ip is None:
             src_ip = self.nat_addr
-        self.assertEqual(packet_num, len(capture))
         for packet in capture:
             try:
                 self.assertEqual(packet[IP].src, src_ip)
         for packet in capture:
             try:
                 self.assertEqual(packet[IP].src, src_ip)
@@ -445,18 +592,15 @@ class MethodHolder(VppTestCase):
                                       "(outside network):", packet))
                 raise
 
                                       "(outside network):", packet))
                 raise
 
-    def verify_capture_in_with_icmp_errors(self, capture, in_if, packet_num=3,
-                                           icmp_type=11):
+    def verify_capture_in_with_icmp_errors(self, capture, in_if, icmp_type=11):
         """
         Verify captured packets with ICMP errors on inside network
 
         :param capture: Captured packets
         :param in_if: Inside interface
         """
         Verify captured packets with ICMP errors on inside network
 
         :param capture: Captured packets
         :param in_if: Inside interface
-        :param packet_num: Expected number of packets (Default 3)
         :param icmp_type: Type of error ICMP packet
                           we are expecting (Default 11)
         """
         :param icmp_type: Type of error ICMP packet
                           we are expecting (Default 11)
         """
-        self.assertEqual(packet_num, len(capture))
         for packet in capture:
             try:
                 self.assertEqual(packet[IP].dst, in_if.remote_ip4)
         for packet in capture:
             try:
                 self.assertEqual(packet[IP].dst, in_if.remote_ip4)
@@ -478,38 +622,64 @@ class MethodHolder(VppTestCase):
                                       "(inside network):", packet))
                 raise
 
                                       "(inside network):", packet))
                 raise
 
-    def create_stream_frag(self, src_if, dst, sport, dport, data):
+    def create_stream_frag(self, src_if, dst, sport, dport, data,
+                           proto=IP_PROTOS.tcp, echo_reply=False):
         """
         Create fragmented packet stream
 
         :param src_if: Source interface
         :param dst: Destination IPv4 address
         """
         Create fragmented packet stream
 
         :param src_if: Source interface
         :param dst: Destination IPv4 address
-        :param sport: Source TCP port
-        :param dport: Destination TCP port
+        :param sport: Source port
+        :param dport: Destination port
         :param data: Payload data
         :param data: Payload data
+        :param proto: protocol (TCP, UDP, ICMP)
+        :param echo_reply: use echo_reply if protocol is ICMP
         :returns: Fragmets
         """
         :returns: Fragmets
         """
+        if proto == IP_PROTOS.tcp:
+            p = (IP(src=src_if.remote_ip4, dst=dst) /
+                 TCP(sport=sport, dport=dport) /
+                 Raw(data))
+            p = p.__class__(str(p))
+            chksum = p['TCP'].chksum
+            proto_header = TCP(sport=sport, dport=dport, chksum=chksum)
+        elif proto == IP_PROTOS.udp:
+            proto_header = UDP(sport=sport, dport=dport)
+        elif proto == IP_PROTOS.icmp:
+            if not echo_reply:
+                proto_header = ICMP(id=sport, type='echo-request')
+            else:
+                proto_header = ICMP(id=sport, type='echo-reply')
+        else:
+            raise Exception("Unsupported protocol")
         id = random.randint(0, 65535)
         id = random.randint(0, 65535)
-        p = (IP(src=src_if.remote_ip4, dst=dst) /
-             TCP(sport=sport, dport=dport) /
-             Raw(data))
-        p = p.__class__(str(p))
-        chksum = p['TCP'].chksum
         pkts = []
         pkts = []
+        if proto == IP_PROTOS.tcp:
+            raw = Raw(data[0:4])
+        else:
+            raw = Raw(data[0:16])
         p = (Ether(src=src_if.remote_mac, dst=src_if.local_mac) /
              IP(src=src_if.remote_ip4, dst=dst, flags="MF", frag=0, id=id) /
         p = (Ether(src=src_if.remote_mac, dst=src_if.local_mac) /
              IP(src=src_if.remote_ip4, dst=dst, flags="MF", frag=0, id=id) /
-             TCP(sport=sport, dport=dport, chksum=chksum) /
-             Raw(data[0:4]))
+             proto_header /
+             raw)
         pkts.append(p)
         pkts.append(p)
+        if proto == IP_PROTOS.tcp:
+            raw = Raw(data[4:20])
+        else:
+            raw = Raw(data[16:32])
         p = (Ether(src=src_if.remote_mac, dst=src_if.local_mac) /
              IP(src=src_if.remote_ip4, dst=dst, flags="MF", frag=3, id=id,
         p = (Ether(src=src_if.remote_mac, dst=src_if.local_mac) /
              IP(src=src_if.remote_ip4, dst=dst, flags="MF", frag=3, id=id,
-                proto=IP_PROTOS.tcp) /
-             Raw(data[4:20]))
+                proto=proto) /
+             raw)
         pkts.append(p)
         pkts.append(p)
+        if proto == IP_PROTOS.tcp:
+            raw = Raw(data[20:])
+        else:
+            raw = Raw(data[32:])
         p = (Ether(src=src_if.remote_mac, dst=src_if.local_mac) /
         p = (Ether(src=src_if.remote_mac, dst=src_if.local_mac) /
-             IP(src=src_if.remote_ip4, dst=dst, frag=5, proto=IP_PROTOS.tcp,
+             IP(src=src_if.remote_ip4, dst=dst, frag=5, proto=proto,
                 id=id) /
                 id=id) /
-             Raw(data[20:]))
+             raw)
         pkts.append(p)
         return pkts
 
         pkts.append(p)
         return pkts
 
@@ -555,17 +725,19 @@ class MethodHolder(VppTestCase):
         for p in frags:
             self.assertEqual(p[IP].src, src)
             self.assertEqual(p[IP].dst, dst)
         for p in frags:
             self.assertEqual(p[IP].src, src)
             self.assertEqual(p[IP].dst, dst)
-            self.check_ip_checksum(p)
+            self.assert_ip_checksum_valid(p)
             buffer.seek(p[IP].frag * 8)
             buffer.write(p[IP].payload)
             buffer.seek(p[IP].frag * 8)
             buffer.write(p[IP].payload)
-        ip = frags[0].getlayer(IP)
         ip = IP(src=frags[0][IP].src, dst=frags[0][IP].dst,
                 proto=frags[0][IP].proto)
         if ip.proto == IP_PROTOS.tcp:
             p = (ip / TCP(buffer.getvalue()))
         ip = IP(src=frags[0][IP].src, dst=frags[0][IP].dst,
                 proto=frags[0][IP].proto)
         if ip.proto == IP_PROTOS.tcp:
             p = (ip / TCP(buffer.getvalue()))
-            self.check_tcp_checksum(p)
+            self.assert_tcp_checksum_valid(p)
         elif ip.proto == IP_PROTOS.udp:
         elif ip.proto == IP_PROTOS.udp:
-            p = (ip / UDP(buffer.getvalue()))
+            p = (ip / UDP(buffer.getvalue()[:8]) /
+                 Raw(buffer.getvalue()[8:]))
+        elif ip.proto == IP_PROTOS.icmp:
+            p = (ip / ICMP(buffer.getvalue()))
         return p
 
     def reass_frags_and_verify_ip6(self, frags, src, dst):
         return p
 
     def reass_frags_and_verify_ip6(self, frags, src, dst):
@@ -588,11 +760,55 @@ class MethodHolder(VppTestCase):
                   nh=frags[0][IPv6ExtHdrFragment].nh)
         if ip.nh == IP_PROTOS.tcp:
             p = (ip / TCP(buffer.getvalue()))
                   nh=frags[0][IPv6ExtHdrFragment].nh)
         if ip.nh == IP_PROTOS.tcp:
             p = (ip / TCP(buffer.getvalue()))
-            self.check_tcp_checksum(p)
         elif ip.nh == IP_PROTOS.udp:
             p = (ip / UDP(buffer.getvalue()))
         elif ip.nh == IP_PROTOS.udp:
             p = (ip / UDP(buffer.getvalue()))
+        self.assert_packet_checksums_valid(p)
         return p
 
         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
     def verify_ipfix_nat44_ses(self, data):
         """
         Verify IPFIX NAT44 session create/delete event
@@ -649,28 +865,541 @@ class MethodHolder(VppTestCase):
         # natPoolID
         self.assertEqual(struct.pack("!I", 0), record[283])
 
         # natPoolID
         self.assertEqual(struct.pack("!I", 0), record[283])
 
+    def verify_ipfix_max_sessions(self, data, limit):
+        """
+        Verify IPFIX maximum session entries exceeded event
 
 
-class TestNAT44(MethodHolder):
-    """ NAT44 Test Cases """
-
-    @classmethod
-    def setUpClass(cls):
-        super(TestNAT44, cls).setUpClass()
-
-        try:
-            cls.tcp_port_in = 6303
-            cls.tcp_port_out = 6303
-            cls.udp_port_in = 6304
-            cls.udp_port_out = 6304
-            cls.icmp_id_in = 6305
-            cls.icmp_id_out = 6305
-            cls.nat_addr = '10.0.0.3'
-            cls.nat_addr_n = socket.inet_pton(socket.AF_INET, cls.nat_addr)
-            cls.ipfix_src_port = 4739
-            cls.ipfix_domain_id = 1
+        :param data: Decoded IPFIX data records
+        :param limit: Number of maximum session entries that can be created.
+        """
+        self.assertEqual(1, len(data))
+        record = data[0]
+        # natEvent
+        self.assertEqual(ord(record[230]), 13)
+        # natQuotaExceededEvent
+        self.assertEqual(struct.pack("I", 1), record[466])
+        # maxSessionEntries
+        self.assertEqual(struct.pack("I", limit), record[471])
 
 
-            cls.create_pg_interfaces(range(10))
-            cls.interfaces = list(cls.pg_interfaces[0:4])
+    def verify_ipfix_max_bibs(self, data, limit):
+        """
+        Verify IPFIX maximum BIB entries exceeded event
+
+        :param data: Decoded IPFIX data records
+        :param limit: Number of maximum BIB entries that can be created.
+        """
+        self.assertEqual(1, len(data))
+        record = data[0]
+        # natEvent
+        self.assertEqual(ord(record[230]), 13)
+        # natQuotaExceededEvent
+        self.assertEqual(struct.pack("I", 2), record[466])
+        # maxBIBEntries
+        self.assertEqual(struct.pack("I", limit), record[472])
+
+    def verify_ipfix_max_fragments_ip6(self, data, limit, src_addr):
+        """
+        Verify IPFIX maximum IPv6 fragments pending reassembly exceeded event
+
+        :param data: Decoded IPFIX data records
+        :param limit: Number of maximum fragments pending reassembly
+        :param src_addr: IPv6 source address
+        """
+        self.assertEqual(1, len(data))
+        record = data[0]
+        # natEvent
+        self.assertEqual(ord(record[230]), 13)
+        # natQuotaExceededEvent
+        self.assertEqual(struct.pack("I", 5), record[466])
+        # maxFragmentsPendingReassembly
+        self.assertEqual(struct.pack("I", limit), record[475])
+        # sourceIPv6Address
+        self.assertEqual(src_addr, record[27])
+
+    def verify_ipfix_max_fragments_ip4(self, data, limit, src_addr):
+        """
+        Verify IPFIX maximum IPv4 fragments pending reassembly exceeded event
+
+        :param data: Decoded IPFIX data records
+        :param limit: Number of maximum fragments pending reassembly
+        :param src_addr: IPv4 source address
+        """
+        self.assertEqual(1, len(data))
+        record = data[0]
+        # natEvent
+        self.assertEqual(ord(record[230]), 13)
+        # natQuotaExceededEvent
+        self.assertEqual(struct.pack("I", 5), record[466])
+        # maxFragmentsPendingReassembly
+        self.assertEqual(struct.pack("I", limit), record[475])
+        # sourceIPv4Address
+        self.assertEqual(src_addr, record[8])
+
+    def verify_ipfix_bib(self, data, is_create, src_addr):
+        """
+        Verify IPFIX NAT64 BIB create and delete events
+
+        :param data: Decoded IPFIX data records
+        :param is_create: Create event if nonzero value otherwise delete event
+        :param src_addr: IPv6 source address
+        """
+        self.assertEqual(1, len(data))
+        record = data[0]
+        # natEvent
+        if is_create:
+            self.assertEqual(ord(record[230]), 10)
+        else:
+            self.assertEqual(ord(record[230]), 11)
+        # sourceIPv6Address
+        self.assertEqual(src_addr, record[27])
+        # postNATSourceIPv4Address
+        self.assertEqual(self.nat_addr_n, record[225])
+        # protocolIdentifier
+        self.assertEqual(IP_PROTOS.tcp, ord(record[4]))
+        # ingressVRFID
+        self.assertEqual(struct.pack("!I", 0), record[234])
+        # sourceTransportPort
+        self.assertEqual(struct.pack("!H", self.tcp_port_in), record[7])
+        # postNAPTSourceTransportPort
+        self.assertEqual(struct.pack("!H", self.tcp_port_out), record[227])
+
+    def verify_ipfix_nat64_ses(self, data, is_create, src_addr, dst_addr,
+                               dst_port):
+        """
+        Verify IPFIX NAT64 session create and delete events
+
+        :param data: Decoded IPFIX data records
+        :param is_create: Create event if nonzero value otherwise delete event
+        :param src_addr: IPv6 source address
+        :param dst_addr: IPv4 destination address
+        :param dst_port: destination TCP port
+        """
+        self.assertEqual(1, len(data))
+        record = data[0]
+        # natEvent
+        if is_create:
+            self.assertEqual(ord(record[230]), 6)
+        else:
+            self.assertEqual(ord(record[230]), 7)
+        # sourceIPv6Address
+        self.assertEqual(src_addr, record[27])
+        # destinationIPv6Address
+        self.assertEqual(socket.inet_pton(socket.AF_INET6,
+                                          self.compose_ip6(dst_addr,
+                                                           '64:ff9b::',
+                                                           96)),
+                         record[28])
+        # postNATSourceIPv4Address
+        self.assertEqual(self.nat_addr_n, record[225])
+        # postNATDestinationIPv4Address
+        self.assertEqual(socket.inet_pton(socket.AF_INET, dst_addr),
+                         record[226])
+        # protocolIdentifier
+        self.assertEqual(IP_PROTOS.tcp, ord(record[4]))
+        # ingressVRFID
+        self.assertEqual(struct.pack("!I", 0), record[234])
+        # sourceTransportPort
+        self.assertEqual(struct.pack("!H", self.tcp_port_in), record[7])
+        # postNAPTSourceTransportPort
+        self.assertEqual(struct.pack("!H", self.tcp_port_out), record[227])
+        # destinationTransportPort
+        self.assertEqual(struct.pack("!H", dst_port), record[11])
+        # postNAPTDestinationTransportPort
+        self.assertEqual(struct.pack("!H", dst_port), record[228])
+
+    def verify_no_nat44_user(self):
+        """ Verify that there is no NAT44 user """
+        users = self.vapi.nat44_user_dump()
+        self.assertEqual(len(users), 0)
+
+    def verify_ipfix_max_entries_per_user(self, data, limit, src_addr):
+        """
+        Verify IPFIX maximum entries per user exceeded event
+
+        :param data: Decoded IPFIX data records
+        :param limit: Number of maximum entries per user
+        :param src_addr: IPv4 source address
+        """
+        self.assertEqual(1, len(data))
+        record = data[0]
+        # natEvent
+        self.assertEqual(ord(record[230]), 13)
+        # natQuotaExceededEvent
+        self.assertEqual(struct.pack("I", 3), record[466])
+        # maxEntriesPerUser
+        self.assertEqual(struct.pack("I", limit), record[473])
+        # sourceIPv4Address
+        self.assertEqual(src_addr, record[8])
+
+    def verify_mss_value(self, pkt, mss):
+        """
+        Verify TCP MSS value
+
+        :param pkt:
+        :param mss:
+        """
+        if not pkt.haslayer(IP) or not pkt.haslayer(TCP):
+            raise TypeError("Not a TCP/IP packet")
+
+        for option in pkt[TCP].options:
+            if option[0] == 'MSS':
+                self.assertEqual(option[1], mss)
+                self.assert_tcp_checksum_valid(pkt)
+
+    @staticmethod
+    def proto2layer(proto):
+        if proto == IP_PROTOS.tcp:
+            return TCP
+        elif proto == IP_PROTOS.udp:
+            return UDP
+        elif proto == IP_PROTOS.icmp:
+            return ICMP
+        else:
+            raise Exception("Unsupported protocol")
+
+    def frag_in_order(self, proto=IP_PROTOS.tcp, dont_translate=False):
+        layer = self.proto2layer(proto)
+
+        if proto == IP_PROTOS.tcp:
+            data = "A" * 4 + "B" * 16 + "C" * 3
+        else:
+            data = "A" * 16 + "B" * 16 + "C" * 3
+        self.port_in = random.randint(1025, 65535)
+
+        reass = self.vapi.nat_reass_dump()
+        reass_n_start = len(reass)
+
+        # in2out
+        pkts = self.create_stream_frag(self.pg0,
+                                       self.pg1.remote_ip4,
+                                       self.port_in,
+                                       20,
+                                       data,
+                                       proto)
+        self.pg0.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        frags = self.pg1.get_capture(len(pkts))
+        if not dont_translate:
+            p = self.reass_frags_and_verify(frags,
+                                            self.nat_addr,
+                                            self.pg1.remote_ip4)
+        else:
+            p = self.reass_frags_and_verify(frags,
+                                            self.pg0.remote_ip4,
+                                            self.pg1.remote_ip4)
+        if proto != IP_PROTOS.icmp:
+            if not dont_translate:
+                self.assertEqual(p[layer].dport, 20)
+                self.assertNotEqual(p[layer].sport, self.port_in)
+            else:
+                self.assertEqual(p[layer].sport, self.port_in)
+        else:
+            if not dont_translate:
+                self.assertNotEqual(p[layer].id, self.port_in)
+            else:
+                self.assertEqual(p[layer].id, self.port_in)
+        self.assertEqual(data, p[Raw].load)
+
+        # out2in
+        if not dont_translate:
+            dst_addr = self.nat_addr
+        else:
+            dst_addr = self.pg0.remote_ip4
+        if proto != IP_PROTOS.icmp:
+            sport = 20
+            dport = p[layer].sport
+        else:
+            sport = p[layer].id
+            dport = 0
+        pkts = self.create_stream_frag(self.pg1,
+                                       dst_addr,
+                                       sport,
+                                       dport,
+                                       data,
+                                       proto,
+                                       echo_reply=True)
+        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)
+        if proto != IP_PROTOS.icmp:
+            self.assertEqual(p[layer].sport, 20)
+            self.assertEqual(p[layer].dport, self.port_in)
+        else:
+            self.assertEqual(p[layer].id, self.port_in)
+        self.assertEqual(data, p[Raw].load)
+
+        reass = self.vapi.nat_reass_dump()
+        reass_n_end = len(reass)
+
+        self.assertEqual(reass_n_end - reass_n_start, 2)
+
+    def frag_in_order_in_plus_out(self, proto=IP_PROTOS.tcp):
+        layer = self.proto2layer(proto)
+
+        if proto == IP_PROTOS.tcp:
+            data = "A" * 4 + "B" * 16 + "C" * 3
+        else:
+            data = "A" * 16 + "B" * 16 + "C" * 3
+        self.port_in = random.randint(1025, 65535)
+
+        for i in range(2):
+            reass = self.vapi.nat_reass_dump()
+            reass_n_start = len(reass)
+
+            # out2in
+            pkts = self.create_stream_frag(self.pg0,
+                                           self.server_out_addr,
+                                           self.port_in,
+                                           self.server_out_port,
+                                           data,
+                                           proto)
+            self.pg0.add_stream(pkts)
+            self.pg_enable_capture(self.pg_interfaces)
+            self.pg_start()
+            frags = self.pg1.get_capture(len(pkts))
+            p = self.reass_frags_and_verify(frags,
+                                            self.pg0.remote_ip4,
+                                            self.server_in_addr)
+            if proto != IP_PROTOS.icmp:
+                self.assertEqual(p[layer].sport, self.port_in)
+                self.assertEqual(p[layer].dport, self.server_in_port)
+            else:
+                self.assertEqual(p[layer].id, self.port_in)
+            self.assertEqual(data, p[Raw].load)
+
+            # in2out
+            if proto != IP_PROTOS.icmp:
+                pkts = self.create_stream_frag(self.pg1,
+                                               self.pg0.remote_ip4,
+                                               self.server_in_port,
+                                               p[layer].sport,
+                                               data,
+                                               proto)
+            else:
+                pkts = self.create_stream_frag(self.pg1,
+                                               self.pg0.remote_ip4,
+                                               p[layer].id,
+                                               0,
+                                               data,
+                                               proto,
+                                               echo_reply=True)
+            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.server_out_addr,
+                                            self.pg0.remote_ip4)
+            if proto != IP_PROTOS.icmp:
+                self.assertEqual(p[layer].sport, self.server_out_port)
+                self.assertEqual(p[layer].dport, self.port_in)
+            else:
+                self.assertEqual(p[layer].id, self.port_in)
+            self.assertEqual(data, p[Raw].load)
+
+            reass = self.vapi.nat_reass_dump()
+            reass_n_end = len(reass)
+
+            self.assertEqual(reass_n_end - reass_n_start, 2)
+
+    def reass_hairpinning(self, proto=IP_PROTOS.tcp):
+        layer = self.proto2layer(proto)
+
+        if proto == IP_PROTOS.tcp:
+            data = "A" * 4 + "B" * 16 + "C" * 3
+        else:
+            data = "A" * 16 + "B" * 16 + "C" * 3
+
+        # send packet from host to server
+        pkts = self.create_stream_frag(self.pg0,
+                                       self.nat_addr,
+                                       self.host_in_port,
+                                       self.server_out_port,
+                                       data,
+                                       proto)
+        self.pg0.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.nat_addr,
+                                        self.server.ip4)
+        if proto != IP_PROTOS.icmp:
+            self.assertNotEqual(p[layer].sport, self.host_in_port)
+            self.assertEqual(p[layer].dport, self.server_in_port)
+        else:
+            self.assertNotEqual(p[layer].id, self.host_in_port)
+        self.assertEqual(data, p[Raw].load)
+
+    def frag_out_of_order(self, proto=IP_PROTOS.tcp, dont_translate=False):
+        layer = self.proto2layer(proto)
+
+        if proto == IP_PROTOS.tcp:
+            data = "A" * 4 + "B" * 16 + "C" * 3
+        else:
+            data = "A" * 16 + "B" * 16 + "C" * 3
+        self.port_in = random.randint(1025, 65535)
+
+        for i in range(2):
+            # in2out
+            pkts = self.create_stream_frag(self.pg0,
+                                           self.pg1.remote_ip4,
+                                           self.port_in,
+                                           20,
+                                           data,
+                                           proto)
+            pkts.reverse()
+            self.pg0.add_stream(pkts)
+            self.pg_enable_capture(self.pg_interfaces)
+            self.pg_start()
+            frags = self.pg1.get_capture(len(pkts))
+            if not dont_translate:
+                p = self.reass_frags_and_verify(frags,
+                                                self.nat_addr,
+                                                self.pg1.remote_ip4)
+            else:
+                p = self.reass_frags_and_verify(frags,
+                                                self.pg0.remote_ip4,
+                                                self.pg1.remote_ip4)
+            if proto != IP_PROTOS.icmp:
+                if not dont_translate:
+                    self.assertEqual(p[layer].dport, 20)
+                    self.assertNotEqual(p[layer].sport, self.port_in)
+                else:
+                    self.assertEqual(p[layer].sport, self.port_in)
+            else:
+                if not dont_translate:
+                    self.assertNotEqual(p[layer].id, self.port_in)
+                else:
+                    self.assertEqual(p[layer].id, self.port_in)
+            self.assertEqual(data, p[Raw].load)
+
+            # out2in
+            if not dont_translate:
+                dst_addr = self.nat_addr
+            else:
+                dst_addr = self.pg0.remote_ip4
+            if proto != IP_PROTOS.icmp:
+                sport = 20
+                dport = p[layer].sport
+            else:
+                sport = p[layer].id
+                dport = 0
+            pkts = self.create_stream_frag(self.pg1,
+                                           dst_addr,
+                                           sport,
+                                           dport,
+                                           data,
+                                           proto,
+                                           echo_reply=True)
+            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)
+            if proto != IP_PROTOS.icmp:
+                self.assertEqual(p[layer].sport, 20)
+                self.assertEqual(p[layer].dport, self.port_in)
+            else:
+                self.assertEqual(p[layer].id, self.port_in)
+            self.assertEqual(data, p[Raw].load)
+
+    def frag_out_of_order_in_plus_out(self, proto=IP_PROTOS.tcp):
+        layer = self.proto2layer(proto)
+
+        if proto == IP_PROTOS.tcp:
+            data = "A" * 4 + "B" * 16 + "C" * 3
+        else:
+            data = "A" * 16 + "B" * 16 + "C" * 3
+        self.port_in = random.randint(1025, 65535)
+
+        for i in range(2):
+            # out2in
+            pkts = self.create_stream_frag(self.pg0,
+                                           self.server_out_addr,
+                                           self.port_in,
+                                           self.server_out_port,
+                                           data,
+                                           proto)
+            pkts.reverse()
+            self.pg0.add_stream(pkts)
+            self.pg_enable_capture(self.pg_interfaces)
+            self.pg_start()
+            frags = self.pg1.get_capture(len(pkts))
+            p = self.reass_frags_and_verify(frags,
+                                            self.pg0.remote_ip4,
+                                            self.server_in_addr)
+            if proto != IP_PROTOS.icmp:
+                self.assertEqual(p[layer].dport, self.server_in_port)
+                self.assertEqual(p[layer].sport, self.port_in)
+                self.assertEqual(p[layer].dport, self.server_in_port)
+            else:
+                self.assertEqual(p[layer].id, self.port_in)
+            self.assertEqual(data, p[Raw].load)
+
+            # in2out
+            if proto != IP_PROTOS.icmp:
+                pkts = self.create_stream_frag(self.pg1,
+                                               self.pg0.remote_ip4,
+                                               self.server_in_port,
+                                               p[layer].sport,
+                                               data,
+                                               proto)
+            else:
+                pkts = self.create_stream_frag(self.pg1,
+                                               self.pg0.remote_ip4,
+                                               p[layer].id,
+                                               0,
+                                               data,
+                                               proto,
+                                               echo_reply=True)
+            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.server_out_addr,
+                                            self.pg0.remote_ip4)
+            if proto != IP_PROTOS.icmp:
+                self.assertEqual(p[layer].sport, self.server_out_port)
+                self.assertEqual(p[layer].dport, self.port_in)
+            else:
+                self.assertEqual(p[layer].id, self.port_in)
+            self.assertEqual(data, p[Raw].load)
+
+
+class TestNAT44(MethodHolder):
+    """ NAT44 Test Cases """
+
+    @classmethod
+    def setUpClass(cls):
+        super(TestNAT44, cls).setUpClass()
+        cls.vapi.cli("set log class nat level debug")
+
+        try:
+            cls.tcp_port_in = 6303
+            cls.tcp_port_out = 6303
+            cls.udp_port_in = 6304
+            cls.udp_port_out = 6304
+            cls.icmp_id_in = 6305
+            cls.icmp_id_out = 6305
+            cls.nat_addr = '10.0.0.3'
+            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])
 
             for i in cls.interfaces:
                 i.admin_up()
 
             for i in cls.interfaces:
                 i.admin_up()
@@ -680,6 +1409,9 @@ class TestNAT44(MethodHolder):
             cls.pg0.generate_remote_hosts(3)
             cls.pg0.configure_ipv4_neighbors()
 
             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)
             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)
@@ -720,184 +1452,37 @@ class TestNAT44(MethodHolder):
             super(TestNAT44, cls).tearDownClass()
             raise
 
             super(TestNAT44, cls).tearDownClass()
             raise
 
-    def clear_nat44(self):
-        """
-        Clear NAT44 configuration.
-        """
-        # I found no elegant way to do this
-        self.vapi.ip_add_del_route(dst_address=self.pg7.remote_ip4n,
-                                   dst_address_length=32,
-                                   next_hop_address=self.pg7.remote_ip4n,
-                                   next_hop_sw_if_index=self.pg7.sw_if_index,
-                                   is_add=0)
-        self.vapi.ip_add_del_route(dst_address=self.pg8.remote_ip4n,
-                                   dst_address_length=32,
-                                   next_hop_address=self.pg8.remote_ip4n,
-                                   next_hop_sw_if_index=self.pg8.sw_if_index,
-                                   is_add=0)
-
-        for intf in [self.pg7, self.pg8]:
-            neighbors = self.vapi.ip_neighbor_dump(intf.sw_if_index)
-            for n in neighbors:
-                self.vapi.ip_neighbor_add_del(intf.sw_if_index,
-                                              n.mac_address,
-                                              n.ip_address,
-                                              is_add=0)
+    def test_dynamic(self):
+        """ NAT44 dynamic translation test """
 
 
-        if self.pg7.has_ip4_config:
-            self.pg7.unconfig_ip4()
+        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)
 
 
-        interfaces = self.vapi.nat44_interface_addr_dump()
-        for intf in interfaces:
-            self.vapi.nat44_add_interface_addr(intf.sw_if_index,
-                                               twice_nat=intf.twice_nat,
-                                               is_add=0)
+        # in2out
+        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)
 
 
-        self.vapi.nat_ipfix(enable=0, src_port=self.ipfix_src_port,
-                            domain_id=self.ipfix_domain_id)
-        self.ipfix_src_port = 4739
-        self.ipfix_domain_id = 1
+        # out2in
+        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)
 
 
-        interfaces = self.vapi.nat44_interface_dump()
-        for intf in interfaces:
-            if intf.is_inside > 1:
-                self.vapi.nat44_interface_add_del_feature(intf.sw_if_index,
-                                                          0,
-                                                          is_add=0)
-            self.vapi.nat44_interface_add_del_feature(intf.sw_if_index,
-                                                      intf.is_inside,
-                                                      is_add=0)
+    def test_dynamic_icmp_errors_in2out_ttl_1(self):
+        """ NAT44 handling of client packets with TTL=1 """
 
 
-        interfaces = self.vapi.nat44_interface_output_feature_dump()
-        for intf in interfaces:
-            self.vapi.nat44_interface_add_del_output_feature(intf.sw_if_index,
-                                                             intf.is_inside,
-                                                             is_add=0)
-
-        static_mappings = self.vapi.nat44_static_mapping_dump()
-        for sm in static_mappings:
-            self.vapi.nat44_add_del_static_mapping(
-                sm.local_ip_address,
-                sm.external_ip_address,
-                local_port=sm.local_port,
-                external_port=sm.external_port,
-                addr_only=sm.addr_only,
-                vrf_id=sm.vrf_id,
-                protocol=sm.protocol,
-                twice_nat=sm.twice_nat,
-                is_add=0)
-
-        lb_static_mappings = self.vapi.nat44_lb_static_mapping_dump()
-        for lb_sm in lb_static_mappings:
-            self.vapi.nat44_add_del_lb_static_mapping(
-                lb_sm.external_addr,
-                lb_sm.external_port,
-                lb_sm.protocol,
-                vrf_id=lb_sm.vrf_id,
-                twice_nat=lb_sm.twice_nat,
-                is_add=0,
-                local_num=0,
-                locals=[])
-
-        identity_mappings = self.vapi.nat44_identity_mapping_dump()
-        for id_m in identity_mappings:
-            self.vapi.nat44_add_del_identity_mapping(
-                addr_only=id_m.addr_only,
-                ip=id_m.ip_address,
-                port=id_m.port,
-                sw_if_index=id_m.sw_if_index,
-                vrf_id=id_m.vrf_id,
-                protocol=id_m.protocol,
-                is_add=0)
-
-        adresses = self.vapi.nat44_address_dump()
-        for addr in adresses:
-            self.vapi.nat44_add_del_address_range(addr.ip_address,
-                                                  addr.ip_address,
-                                                  twice_nat=addr.twice_nat,
-                                                  is_add=0)
-
-        self.vapi.nat_set_reass()
-        self.vapi.nat_set_reass(is_ip6=1)
-
-    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):
-        """
-        Add/delete NAT44 static mapping
-
-        :param local_ip: Local IP address
-        :param external_ip: External IP address
-        :param local_port: Local port number (Optional)
-        :param external_port: External port number (Optional)
-        :param vrf_id: VRF ID (Default 0)
-        :param is_add: 1 if add, 0 if delete (Default add)
-        :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
-        """
-        addr_only = 1
-        if local_port and external_port:
-            addr_only = 0
-        l_ip = socket.inet_pton(socket.AF_INET, local_ip)
-        e_ip = socket.inet_pton(socket.AF_INET, external_ip)
-        self.vapi.nat44_add_del_static_mapping(
-            l_ip,
-            e_ip,
-            external_sw_if_index,
-            local_port,
-            external_port,
-            addr_only,
-            vrf_id,
-            proto,
-            twice_nat,
-            is_add)
-
-    def nat44_add_address(self, ip, is_add=1, vrf_id=0xFFFFFFFF, twice_nat=0):
-        """
-        Add/delete NAT44 address
-
-        :param ip: IP address
-        :param is_add: 1 if add, 0 if delete (Default add)
-        :param twice_nat: twice NAT address for extenal hosts
-        """
-        nat_addr = socket.inet_pton(socket.AF_INET, ip)
-        self.vapi.nat44_add_del_address_range(nat_addr, nat_addr, is_add,
-                                              vrf_id=vrf_id,
-                                              twice_nat=twice_nat)
-
-    def test_dynamic(self):
-        """ NAT44 dynamic translation test """
-
-        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)
-
-        # in2out
-        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)
-
-        # out2in
-        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_dynamic_icmp_errors_in2out_ttl_1(self):
-        """ NAT44 handling of client packets with TTL=1 """
-
-        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.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)
 
         # Client side - generate traffic
         pkts = self.create_stream_in(self.pg0, self.pg1, ttl=1)
 
         # Client side - generate traffic
         pkts = self.create_stream_in(self.pg0, self.pg1, ttl=1)
@@ -1014,7 +1599,6 @@ class TestNAT44(MethodHolder):
         self.pg_enable_capture(self.pg_interfaces)
         self.pg_start()
         capture = self.pg1.get_capture(len(pkts))
         self.pg_enable_capture(self.pg_interfaces)
         self.pg_start()
         capture = self.pg1.get_capture(len(pkts))
-        self.assertEqual(1, len(capture))
         packet = capture[0]
         try:
             self.assertEqual(packet[IP].src, self.pg1.local_ip4)
         packet = capture[0]
         try:
             self.assertEqual(packet[IP].src, self.pg1.local_ip4)
@@ -1042,7 +1626,7 @@ class TestNAT44(MethodHolder):
         self.pg_enable_capture(self.pg_interfaces)
         self.pg_start()
         capture = self.pg0.get_capture(1)
         self.pg_enable_capture(self.pg_interfaces)
         self.pg_start()
         capture = self.pg0.get_capture(1)
-        self.verify_capture_in(capture, self.pg0, packet_num=1)
+        self.verify_capture_in(capture, self.pg0)
         self.assert_equal(capture[0][IP].proto, IP_PROTOS.icmp)
 
         # in2out
         self.assert_equal(capture[0][IP].proto, IP_PROTOS.icmp)
 
         # in2out
@@ -1053,7 +1637,7 @@ class TestNAT44(MethodHolder):
         self.pg_enable_capture(self.pg_interfaces)
         self.pg_start()
         capture = self.pg1.get_capture(1)
         self.pg_enable_capture(self.pg_interfaces)
         self.pg_start()
         capture = self.pg1.get_capture(1)
-        self.verify_capture_out(capture, same_port=True, packet_num=1)
+        self.verify_capture_out(capture, same_port=True)
         self.assert_equal(capture[0][IP].proto, IP_PROTOS.icmp)
 
     def test_forwarding(self):
         self.assert_equal(capture[0][IP].proto, IP_PROTOS.icmp)
 
     def test_forwarding(self):
@@ -1070,7 +1654,7 @@ class TestNAT44(MethodHolder):
                                                external_ip=alias_ip)
 
         try:
                                                external_ip=alias_ip)
 
         try:
-            # in2out - static mapping match
+            # static mapping match
 
             pkts = self.create_stream_out(self.pg1)
             self.pg1.add_stream(pkts)
 
             pkts = self.create_stream_out(self.pg1)
             self.pg1.add_stream(pkts)
@@ -1086,7 +1670,7 @@ class TestNAT44(MethodHolder):
             capture = self.pg1.get_capture(len(pkts))
             self.verify_capture_out(capture, same_port=True)
 
             capture = self.pg1.get_capture(len(pkts))
             self.verify_capture_out(capture, same_port=True)
 
-            # in2out - no static mapping match
+            # no static mapping match
 
             host0 = self.pg0.remote_hosts[0]
             self.pg0.remote_hosts[0] = self.pg0.remote_hosts[1]
 
             host0 = self.pg0.remote_hosts[0]
             self.pg0.remote_hosts[0] = self.pg0.remote_hosts[1]
@@ -1128,6 +1712,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)
         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)
 
         # in2out
         pkts = self.create_stream_in(self.pg0, self.pg1)
@@ -1152,11 +1742,15 @@ class TestNAT44(MethodHolder):
         self.tcp_port_out = 6303
         self.udp_port_out = 6304
         self.icmp_id_out = 6305
         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)
         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)
 
         # out2in
         pkts = self.create_stream_out(self.pg1, nat_ip)
@@ -1282,125 +1876,71 @@ class TestNAT44(MethodHolder):
         self.pg_start()
         self.pg3.assert_nothing_captured()
 
         self.pg_start()
         self.pg3.assert_nothing_captured()
 
-    def test_identity_nat(self):
-        """ Identity NAT """
+    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.vapi.nat44_add_del_identity_mapping(ip=self.pg0.remote_ip4n)
+        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.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.pg0.remote_ip4) /
-             TCP(sport=12345, dport=56789))
-        self.pg1.add_stream(p)
+        # 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()
         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, self.pg1.remote_ip4)
-            self.assertEqual(tcp.dport, 56789)
-            self.assertEqual(tcp.sport, 12345)
-            self.check_tcp_checksum(p)
-            self.check_ip_checksum(p)
-        except:
-            self.logger.error(ppp("Unexpected or invalid packet:", p))
-            raise
+        capture = self.pg1.get_capture(len(pkts))
+        self.verify_capture_out(capture)
 
 
-    def test_static_lb(self):
-        """ NAT44 local service load balancing """
-        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]
+        # 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)
 
 
-        locals = [{'addr': server1.ip4n,
-                   'port': local_port,
-                   'probability': 70},
-                  {'addr': server2.ip4n,
-                   'port': local_port,
-                   'probability': 30}]
+    def test_identity_nat(self):
+        """ Identity NAT """
 
 
-        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_add_del_identity_mapping(ip=self.pg0.remote_ip4n)
         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.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) /
         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))
+             IP(src=self.pg1.remote_ip4, dst=self.pg0.remote_ip4) /
+             TCP(sport=12345, dport=56789))
         self.pg1.add_stream(p)
         self.pg_enable_capture(self.pg_interfaces)
         self.pg_start()
         capture = self.pg0.get_capture(1)
         p = capture[0]
         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.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 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.pg1.get_capture(1)
-        p = capture[0]
         try:
             ip = p[IP]
             tcp = p[TCP]
         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)
+            self.assertEqual(ip.dst, self.pg0.remote_ip4)
+            self.assertEqual(ip.src, self.pg1.remote_ip4)
+            self.assertEqual(tcp.dport, 56789)
+            self.assertEqual(tcp.sport, 12345)
+            self.assert_packet_checksums_valid(p)
         except:
             self.logger.error(ppp("Unexpected or invalid packet:", p))
             raise
 
         except:
             self.logger.error(ppp("Unexpected or invalid packet:", p))
             raise
 
-        # multiple clients
-        server1_n = 0
-        server2_n = 0
-        clients = ip4_range(self.pg1.remote_ip4, 10, 20)
-        pkts = []
-        for client in clients:
-            p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
-                 IP(src=client, dst=self.nat_addr) /
-                 TCP(sport=12345, dport=external_port))
-            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 p in capture:
-            if p[IP].dst == server1.ip4:
-                server1_n += 1
-            else:
-                server2_n += 1
-        self.assertTrue(server1_n > server2_n)
+        sessions = self.vapi.nat44_user_session_dump(self.pg0.remote_ip4n, 0)
+        self.assertEqual(len(sessions), 0)
+        self.vapi.nat44_add_del_identity_mapping(ip=self.pg0.remote_ip4n,
+                                                 vrf_id=1)
+        identity_mappings = self.vapi.nat44_identity_mapping_dump()
+        self.assertEqual(len(identity_mappings), 2)
 
     def test_multiple_inside_interfaces(self):
         """ NAT44 multiple non-overlapping address space inside interfaces """
 
     def test_multiple_inside_interfaces(self):
         """ NAT44 multiple non-overlapping address space inside interfaces """
@@ -1583,6 +2123,7 @@ class TestNAT44(MethodHolder):
                 self.assertTrue(session.protocol in
                                 [IP_PROTOS.tcp, IP_PROTOS.udp,
                                  IP_PROTOS.icmp])
                 self.assertTrue(session.protocol in
                                 [IP_PROTOS.tcp, IP_PROTOS.udp,
                                  IP_PROTOS.icmp])
+                self.assertFalse(session.ext_host_valid)
 
         # pg4 session dump
         sessions = self.vapi.nat44_user_session_dump(self.pg4.remote_ip4n, 10)
 
         # pg4 session dump
         sessions = self.vapi.nat44_user_session_dump(self.pg4.remote_ip4n, 10)
@@ -1642,7 +2183,7 @@ class TestNAT44(MethodHolder):
             self.assertEqual(ip.dst, server.ip4)
             self.assertNotEqual(tcp.sport, host_in_port)
             self.assertEqual(tcp.dport, server_in_port)
             self.assertEqual(ip.dst, server.ip4)
             self.assertNotEqual(tcp.sport, host_in_port)
             self.assertEqual(tcp.dport, server_in_port)
-            self.check_tcp_checksum(p)
+            self.assert_packet_checksums_valid(p)
             host_out_port = tcp.sport
         except:
             self.logger.error(ppp("Unexpected or invalid packet:", p))
             host_out_port = tcp.sport
         except:
             self.logger.error(ppp("Unexpected or invalid packet:", p))
@@ -1664,9 +2205,9 @@ class TestNAT44(MethodHolder):
             self.assertEqual(ip.dst, host.ip4)
             self.assertEqual(tcp.sport, server_out_port)
             self.assertEqual(tcp.dport, host_in_port)
             self.assertEqual(ip.dst, host.ip4)
             self.assertEqual(tcp.sport, server_out_port)
             self.assertEqual(tcp.dport, host_in_port)
-            self.check_tcp_checksum(p)
+            self.assert_packet_checksums_valid(p)
         except:
         except:
-            self.logger.error(ppp("Unexpected or invalid packet:"), p)
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
             raise
 
     def test_hairpinning2(self):
             raise
 
     def test_hairpinning2(self):
@@ -1715,7 +2256,7 @@ class TestNAT44(MethodHolder):
                     self.assertNotEqual(packet[TCP].sport, self.tcp_port_in)
                     self.assertEqual(packet[TCP].dport, server_tcp_port)
                     self.tcp_port_out = packet[TCP].sport
                     self.assertNotEqual(packet[TCP].sport, self.tcp_port_in)
                     self.assertEqual(packet[TCP].dport, server_tcp_port)
                     self.tcp_port_out = packet[TCP].sport
-                    self.check_tcp_checksum(packet)
+                    self.assert_packet_checksums_valid(packet)
                 elif packet.haslayer(UDP):
                     self.assertNotEqual(packet[UDP].sport, self.udp_port_in)
                     self.assertEqual(packet[UDP].dport, server_udp_port)
                 elif packet.haslayer(UDP):
                     self.assertNotEqual(packet[UDP].sport, self.udp_port_in)
                     self.assertEqual(packet[UDP].dport, server_udp_port)
@@ -1752,7 +2293,7 @@ class TestNAT44(MethodHolder):
                 if packet.haslayer(TCP):
                     self.assertEqual(packet[TCP].dport, self.tcp_port_in)
                     self.assertEqual(packet[TCP].sport, server_tcp_port)
                 if packet.haslayer(TCP):
                     self.assertEqual(packet[TCP].dport, self.tcp_port_in)
                     self.assertEqual(packet[TCP].sport, server_tcp_port)
-                    self.check_tcp_checksum(packet)
+                    self.assert_packet_checksums_valid(packet)
                 elif packet.haslayer(UDP):
                     self.assertEqual(packet[UDP].dport, self.udp_port_in)
                     self.assertEqual(packet[UDP].sport, server_udp_port)
                 elif packet.haslayer(UDP):
                     self.assertEqual(packet[UDP].dport, self.udp_port_in)
                     self.assertEqual(packet[UDP].sport, server_udp_port)
@@ -1788,7 +2329,7 @@ class TestNAT44(MethodHolder):
                     self.assertEqual(packet[TCP].sport, self.tcp_port_in)
                     self.assertEqual(packet[TCP].dport, server_tcp_port)
                     self.tcp_port_out = packet[TCP].sport
                     self.assertEqual(packet[TCP].sport, self.tcp_port_in)
                     self.assertEqual(packet[TCP].dport, server_tcp_port)
                     self.tcp_port_out = packet[TCP].sport
-                    self.check_tcp_checksum(packet)
+                    self.assert_packet_checksums_valid(packet)
                 elif packet.haslayer(UDP):
                     self.assertEqual(packet[UDP].sport, self.udp_port_in)
                     self.assertEqual(packet[UDP].dport, server_udp_port)
                 elif packet.haslayer(UDP):
                     self.assertEqual(packet[UDP].sport, self.udp_port_in)
                     self.assertEqual(packet[UDP].dport, server_udp_port)
@@ -1825,7 +2366,7 @@ class TestNAT44(MethodHolder):
                 if packet.haslayer(TCP):
                     self.assertEqual(packet[TCP].dport, self.tcp_port_in)
                     self.assertEqual(packet[TCP].sport, server_tcp_port)
                 if packet.haslayer(TCP):
                     self.assertEqual(packet[TCP].dport, self.tcp_port_in)
                     self.assertEqual(packet[TCP].sport, server_tcp_port)
-                    self.check_tcp_checksum(packet)
+                    self.assert_packet_checksums_valid(packet)
                 elif packet.haslayer(UDP):
                     self.assertEqual(packet[UDP].dport, self.udp_port_in)
                     self.assertEqual(packet[UDP].sport, server_udp_port)
                 elif packet.haslayer(UDP):
                     self.assertEqual(packet[UDP].dport, self.udp_port_in)
                     self.assertEqual(packet[UDP].sport, server_udp_port)
@@ -1861,6 +2402,31 @@ class TestNAT44(MethodHolder):
         # verify number of translated packet
         self.pg1.get_capture(pkts_num)
 
         # verify number of translated packet
         self.pg1.get_capture(pkts_num)
 
+        users = self.vapi.nat44_user_dump()
+        for user in users:
+            if user.ip_address == self.pg0.remote_ip4n:
+                self.assertEqual(user.nsessions,
+                                 nat44_config.max_translations_per_user)
+                self.assertEqual(user.nstaticsessions, 0)
+
+        tcp_port = 22
+        self.nat44_add_static_mapping(self.pg0.remote_ip4, self.nat_addr,
+                                      tcp_port, tcp_port,
+                                      proto=IP_PROTOS.tcp)
+        p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+             IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+             TCP(sport=tcp_port))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        self.pg1.get_capture(1)
+        users = self.vapi.nat44_user_dump()
+        for user in users:
+            if user.ip_address == self.pg0.remote_ip4n:
+                self.assertEqual(user.nsessions,
+                                 nat44_config.max_translations_per_user - 1)
+                self.assertEqual(user.nstaticsessions, 1)
+
     def test_interface_addr(self):
         """ Acquire NAT44 addresses from interface """
         self.vapi.nat44_add_interface_addr(self.pg7.sw_if_index)
     def test_interface_addr(self):
         """ Acquire NAT44 addresses from interface """
         self.vapi.nat44_add_interface_addr(self.pg7.sw_if_index)
@@ -1882,35 +2448,69 @@ class TestNAT44(MethodHolder):
 
     def test_interface_addr_static_mapping(self):
         """ Static mapping with addresses from interface """
 
     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',
         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)
 
         # 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()
 
         # 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()
 
         # remove interface address and check static mappings
         self.pg7.unconfig_ip4()
         static_mappings = self.vapi.nat44_static_mapping_dump()
-        self.assertEqual(0, len(static_mappings))
-
-    def test_interface_addr_identity_nat(self):
-        """ Identity NAT with addresses from interface """
+        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)
 
 
-        port = 53053
-        self.vapi.nat44_add_interface_addr(self.pg7.sw_if_index)
+        # 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):
+        """ Identity NAT with addresses from interface """
+
+        port = 53053
+        self.vapi.nat44_add_interface_addr(self.pg7.sw_if_index)
         self.vapi.nat44_add_del_identity_mapping(
             sw_if_index=self.pg7.sw_if_index,
             port=port,
         self.vapi.nat44_add_del_identity_mapping(
             sw_if_index=self.pg7.sw_if_index,
             port=port,
@@ -1926,17 +2526,23 @@ class TestNAT44(MethodHolder):
         # configure interface address and check identity mappings
         self.pg7.config_ip4()
         identity_mappings = self.vapi.nat44_identity_mapping_dump()
         # 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()
 
         # 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 """
 
     def test_ipfix_nat44_sess(self):
         """ IPFIX logging NAT44 session created/delted """
@@ -1964,7 +2570,7 @@ class TestNAT44(MethodHolder):
         self.verify_capture_out(capture)
         self.nat44_add_address(self.nat_addr, is_add=0)
         self.vapi.cli("ipfix flush")  # FIXME this should be an API call
         self.verify_capture_out(capture)
         self.nat44_add_address(self.nat_addr, is_add=0)
         self.vapi.cli("ipfix flush")  # FIXME this should be an API call
-        capture = self.pg3.get_capture(3)
+        capture = self.pg3.get_capture(9)
         ipfix = IPFIXDecoder()
         # first load template
         for p in capture:
         ipfix = IPFIXDecoder()
         # first load template
         for p in capture:
@@ -2001,9 +2607,10 @@ class TestNAT44(MethodHolder):
         self.pg0.add_stream(p)
         self.pg_enable_capture(self.pg_interfaces)
         self.pg_start()
         self.pg0.add_stream(p)
         self.pg_enable_capture(self.pg_interfaces)
         self.pg_start()
-        capture = self.pg1.get_capture(0)
+        self.pg1.assert_nothing_captured()
+        sleep(1)
         self.vapi.cli("ipfix flush")  # FIXME this should be an API call
         self.vapi.cli("ipfix flush")  # FIXME this should be an API call
-        capture = self.pg3.get_capture(3)
+        capture = self.pg3.get_capture(9)
         ipfix = IPFIXDecoder()
         # first load template
         for p in capture:
         ipfix = IPFIXDecoder()
         # first load template
         for p in capture:
@@ -2022,6 +2629,64 @@ class TestNAT44(MethodHolder):
                 data = ipfix.decode_data_set(p.getlayer(Set))
                 self.verify_ipfix_addr_exhausted(data)
 
                 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)
+        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)
+
+        nat44_config = self.vapi.nat_show_config()
+        max_sessions = 10 * nat44_config.translation_buckets
+
+        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) /
+                 TCP(sport=1025))
+            pkts.append(p)
+        self.pg0.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+
+        self.pg1.get_capture(max_sessions)
+        self.vapi.set_ipfix_exporter(collector_address=self.pg3.remote_ip4n,
+                                     src_address=self.pg3.local_ip4n,
+                                     path_mtu=512,
+                                     template_interval=10)
+        self.vapi.nat_ipfix(domain_id=self.ipfix_domain_id,
+                            src_port=self.ipfix_src_port)
+
+        p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+             IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+             TCP(sport=1025))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        self.pg1.assert_nothing_captured()
+        sleep(1)
+        self.vapi.cli("ipfix flush")  # FIXME this should be an API call
+        capture = self.pg3.get_capture(9)
+        ipfix = IPFIXDecoder()
+        # first load template
+        for p in capture:
+            self.assertTrue(p.haslayer(IPFIX))
+            self.assertEqual(p[IP].src, self.pg3.local_ip4)
+            self.assertEqual(p[IP].dst, self.pg3.remote_ip4)
+            self.assertEqual(p[UDP].sport, self.ipfix_src_port)
+            self.assertEqual(p[UDP].dport, 4739)
+            self.assertEqual(p[IPFIX].observationDomainID,
+                             self.ipfix_domain_id)
+            if p.haslayer(Template):
+                ipfix.add_template(p.getlayer(Template))
+        # verify events in data set
+        for p in capture:
+            if p.haslayer(Data):
+                data = ipfix.decode_data_set(p.getlayer(Set))
+                self.verify_ipfix_max_sessions(data, max_sessions)
+
     def test_pool_addr_fib(self):
         """ NAT44 add pool addresses to FIB """
         static_addr = '10.0.0.10'
     def test_pool_addr_fib(self):
         """ NAT44 add pool addresses to FIB """
         static_addr = '10.0.0.10'
@@ -2060,7 +2725,7 @@ class TestNAT44(MethodHolder):
         self.pg2.add_stream(p)
         self.pg_enable_capture(self.pg_interfaces)
         self.pg_start()
         self.pg2.add_stream(p)
         self.pg_enable_capture(self.pg_interfaces)
         self.pg_start()
-        capture = self.pg1.get_capture(0)
+        self.pg1.assert_nothing_captured()
 
         # remove addresses and verify
         self.nat44_add_address(self.nat_addr, is_add=0)
 
         # remove addresses and verify
         self.nat44_add_address(self.nat_addr, is_add=0)
@@ -2073,7 +2738,7 @@ class TestNAT44(MethodHolder):
         self.pg1.add_stream(p)
         self.pg_enable_capture(self.pg_interfaces)
         self.pg_start()
         self.pg1.add_stream(p)
         self.pg_enable_capture(self.pg_interfaces)
         self.pg_start()
-        capture = self.pg1.get_capture(0)
+        self.pg1.assert_nothing_captured()
 
         p = (Ether(src=self.pg1.remote_mac, dst='ff:ff:ff:ff:ff:ff') /
              ARP(op=ARP.who_has, pdst=static_addr,
 
         p = (Ether(src=self.pg1.remote_mac, dst='ff:ff:ff:ff:ff:ff') /
              ARP(op=ARP.who_has, pdst=static_addr,
@@ -2081,7 +2746,7 @@ class TestNAT44(MethodHolder):
         self.pg1.add_stream(p)
         self.pg_enable_capture(self.pg_interfaces)
         self.pg_start()
         self.pg1.add_stream(p)
         self.pg_enable_capture(self.pg_interfaces)
         self.pg_start()
-        capture = self.pg1.get_capture(0)
+        self.pg1.assert_nothing_captured()
 
     def test_vrf_mode(self):
         """ NAT44 tenant VRF aware address pool mode """
 
     def test_vrf_mode(self):
         """ NAT44 tenant VRF aware address pool mode """
@@ -2099,6 +2764,8 @@ class TestNAT44(MethodHolder):
         self.pg1.set_table_ip4(vrf_id2)
         self.pg0.config_ip4()
         self.pg1.config_ip4()
         self.pg1.set_table_ip4(vrf_id2)
         self.pg0.config_ip4()
         self.pg1.config_ip4()
+        self.pg0.resolve_arp()
+        self.pg1.resolve_arp()
 
         self.nat44_add_address(nat_ip1, vrf_id=vrf_id1)
         self.nat44_add_address(nat_ip2, vrf_id=vrf_id2)
 
         self.nat44_add_address(nat_ip1, vrf_id=vrf_id1)
         self.nat44_add_address(nat_ip2, vrf_id=vrf_id2)
@@ -2107,28 +2774,34 @@ class TestNAT44(MethodHolder):
         self.vapi.nat44_interface_add_del_feature(self.pg2.sw_if_index,
                                                   is_inside=0)
 
         self.vapi.nat44_interface_add_del_feature(self.pg2.sw_if_index,
                                                   is_inside=0)
 
-        # first VRF
-        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_out(capture, nat_ip1)
+        try:
+            # first VRF
+            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_out(capture, nat_ip1)
 
 
-        # second VRF
-        pkts = self.create_stream_in(self.pg1, self.pg2)
-        self.pg1.add_stream(pkts)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        capture = self.pg2.get_capture(len(pkts))
-        self.verify_capture_out(capture, nat_ip2)
+            # second VRF
+            pkts = self.create_stream_in(self.pg1, self.pg2)
+            self.pg1.add_stream(pkts)
+            self.pg_enable_capture(self.pg_interfaces)
+            self.pg_start()
+            capture = self.pg2.get_capture(len(pkts))
+            self.verify_capture_out(capture, nat_ip2)
 
 
-        self.pg0.unconfig_ip4()
-        self.pg1.unconfig_ip4()
-        self.pg0.set_table_ip4(0)
-        self.pg1.set_table_ip4(0)
-        self.vapi.ip_table_add_del(vrf_id1, is_add=0)
-        self.vapi.ip_table_add_del(vrf_id2, is_add=0)
+        finally:
+            self.pg0.unconfig_ip4()
+            self.pg1.unconfig_ip4()
+            self.pg0.set_table_ip4(0)
+            self.pg1.set_table_ip4(0)
+            self.pg0.config_ip4()
+            self.pg1.config_ip4()
+            self.pg0.resolve_arp()
+            self.pg1.resolve_arp()
+            self.vapi.ip_table_add_del(vrf_id1, is_add=0)
+            self.vapi.ip_table_add_del(vrf_id2, is_add=0)
 
     def test_vrf_feature_independent(self):
         """ NAT44 tenant VRF independent address pool mode """
 
     def test_vrf_feature_independent(self):
         """ NAT44 tenant VRF independent address pool mode """
@@ -2321,7 +2994,7 @@ class TestNAT44(MethodHolder):
             self.assertEqual(packet[IP].src, nat_ip)
             self.assertEqual(packet[IP].dst, self.pg1.remote_ip4)
             self.assertTrue(packet.haslayer(GRE))
             self.assertEqual(packet[IP].src, nat_ip)
             self.assertEqual(packet[IP].dst, self.pg1.remote_ip4)
             self.assertTrue(packet.haslayer(GRE))
-            self.check_ip_checksum(packet)
+            self.assert_packet_checksums_valid(packet)
         except:
             self.logger.error(ppp("Unexpected or invalid packet:", packet))
             raise
         except:
             self.logger.error(ppp("Unexpected or invalid packet:", packet))
             raise
@@ -2341,7 +3014,7 @@ class TestNAT44(MethodHolder):
             self.assertEqual(packet[IP].src, self.pg1.remote_ip4)
             self.assertEqual(packet[IP].dst, self.pg0.remote_ip4)
             self.assertTrue(packet.haslayer(GRE))
             self.assertEqual(packet[IP].src, self.pg1.remote_ip4)
             self.assertEqual(packet[IP].dst, self.pg0.remote_ip4)
             self.assertTrue(packet.haslayer(GRE))
-            self.check_ip_checksum(packet)
+            self.assert_packet_checksums_valid(packet)
         except:
             self.logger.error(ppp("Unexpected or invalid packet:", packet))
             raise
         except:
             self.logger.error(ppp("Unexpected or invalid packet:", packet))
             raise
@@ -2376,7 +3049,7 @@ class TestNAT44(MethodHolder):
             self.assertEqual(packet[IP].src, host_nat_ip)
             self.assertEqual(packet[IP].dst, server.ip4)
             self.assertTrue(packet.haslayer(GRE))
             self.assertEqual(packet[IP].src, host_nat_ip)
             self.assertEqual(packet[IP].dst, server.ip4)
             self.assertTrue(packet.haslayer(GRE))
-            self.check_ip_checksum(packet)
+            self.assert_packet_checksums_valid(packet)
         except:
             self.logger.error(ppp("Unexpected or invalid packet:", packet))
             raise
         except:
             self.logger.error(ppp("Unexpected or invalid packet:", packet))
             raise
@@ -2396,128 +3069,7 @@ class TestNAT44(MethodHolder):
             self.assertEqual(packet[IP].src, server_nat_ip)
             self.assertEqual(packet[IP].dst, host.ip4)
             self.assertTrue(packet.haslayer(GRE))
             self.assertEqual(packet[IP].src, server_nat_ip)
             self.assertEqual(packet[IP].dst, host.ip4)
             self.assertTrue(packet.haslayer(GRE))
-            self.check_ip_checksum(packet)
-        except:
-            self.logger.error(ppp("Unexpected or invalid packet:", packet))
-            raise
-
-    def test_unknown_proto(self):
-        """ NAT44 translate packet with unknown protocol """
-        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)
-
-        # in2out
-        p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
-             IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
-             TCP(sport=self.tcp_port_in, dport=20))
-        self.pg0.add_stream(p)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        p = self.pg1.get_capture(1)
-
-        p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
-             IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
-             GRE() /
-             IP(src=self.pg2.remote_ip4, dst=self.pg3.remote_ip4) /
-             TCP(sport=1234, dport=1234))
-        self.pg0.add_stream(p)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        p = self.pg1.get_capture(1)
-        packet = p[0]
-        try:
-            self.assertEqual(packet[IP].src, self.nat_addr)
-            self.assertEqual(packet[IP].dst, self.pg1.remote_ip4)
-            self.assertTrue(packet.haslayer(GRE))
-            self.check_ip_checksum(packet)
-        except:
-            self.logger.error(ppp("Unexpected or invalid packet:", packet))
-            raise
-
-        # out2in
-        p = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
-             IP(src=self.pg1.remote_ip4, dst=self.nat_addr) /
-             GRE() /
-             IP(src=self.pg3.remote_ip4, dst=self.pg2.remote_ip4) /
-             TCP(sport=1234, dport=1234))
-        self.pg1.add_stream(p)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        p = self.pg0.get_capture(1)
-        packet = p[0]
-        try:
-            self.assertEqual(packet[IP].src, self.pg1.remote_ip4)
-            self.assertEqual(packet[IP].dst, self.pg0.remote_ip4)
-            self.assertTrue(packet.haslayer(GRE))
-            self.check_ip_checksum(packet)
-        except:
-            self.logger.error(ppp("Unexpected or invalid packet:", packet))
-            raise
-
-    def test_hairpinning_unknown_proto(self):
-        """ NAT44 translate packet with unknown protocol - hairpinning """
-        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
-        server_nat_ip = "10.0.0.11"
-
-        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)
-
-        # add static mapping for server
-        self.nat44_add_static_mapping(server.ip4, server_nat_ip)
-
-        # host to server
-        p = (Ether(src=host.mac, dst=self.pg0.local_mac) /
-             IP(src=host.ip4, dst=server_nat_ip) /
-             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 = (Ether(dst=self.pg0.local_mac, src=host.mac) /
-             IP(src=host.ip4, dst=server_nat_ip) /
-             GRE() /
-             IP(src=self.pg2.remote_ip4, dst=self.pg3.remote_ip4) /
-             TCP(sport=1234, dport=1234))
-        self.pg0.add_stream(p)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        p = self.pg0.get_capture(1)
-        packet = p[0]
-        try:
-            self.assertEqual(packet[IP].src, self.nat_addr)
-            self.assertEqual(packet[IP].dst, server.ip4)
-            self.assertTrue(packet.haslayer(GRE))
-            self.check_ip_checksum(packet)
-        except:
-            self.logger.error(ppp("Unexpected or invalid packet:", packet))
-            raise
-
-        # server to host
-        p = (Ether(dst=self.pg0.local_mac, src=server.mac) /
-             IP(src=server.ip4, dst=self.nat_addr) /
-             GRE() /
-             IP(src=self.pg3.remote_ip4, dst=self.pg2.remote_ip4) /
-             TCP(sport=1234, dport=1234))
-        self.pg0.add_stream(p)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        p = self.pg0.get_capture(1)
-        packet = p[0]
-        try:
-            self.assertEqual(packet[IP].src, server_nat_ip)
-            self.assertEqual(packet[IP].dst, host.ip4)
-            self.assertTrue(packet.haslayer(GRE))
-            self.check_ip_checksum(packet)
+            self.assert_packet_checksums_valid(packet)
         except:
             self.logger.error(ppp("Unexpected or invalid packet:", packet))
             raise
         except:
             self.logger.error(ppp("Unexpected or invalid packet:", packet))
             raise
@@ -2644,7 +3196,7 @@ class TestNAT44(MethodHolder):
             self.assertEqual(ip.dst, server.ip4)
             self.assertNotEqual(tcp.sport, host_in_port)
             self.assertEqual(tcp.dport, server_in_port)
             self.assertEqual(ip.dst, server.ip4)
             self.assertNotEqual(tcp.sport, host_in_port)
             self.assertEqual(tcp.dport, server_in_port)
-            self.check_tcp_checksum(p)
+            self.assert_packet_checksums_valid(p)
             host_out_port = tcp.sport
         except:
             self.logger.error(ppp("Unexpected or invalid packet:", p))
             host_out_port = tcp.sport
         except:
             self.logger.error(ppp("Unexpected or invalid packet:", p))
@@ -2666,9 +3218,9 @@ class TestNAT44(MethodHolder):
             self.assertEqual(ip.dst, host.ip4)
             self.assertEqual(tcp.sport, server_out_port)
             self.assertEqual(tcp.dport, host_in_port)
             self.assertEqual(ip.dst, host.ip4)
             self.assertEqual(tcp.sport, server_out_port)
             self.assertEqual(tcp.dport, host_in_port)
-            self.check_tcp_checksum(p)
+            self.assert_packet_checksums_valid(p)
         except:
         except:
-            self.logger.error(ppp("Unexpected or invalid packet:"), p)
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
             raise
 
     def test_one_armed_nat44(self):
             raise
 
     def test_one_armed_nat44(self):
@@ -2699,8 +3251,7 @@ class TestNAT44(MethodHolder):
             self.assertNotEqual(tcp.sport, 12345)
             external_port = tcp.sport
             self.assertEqual(tcp.dport, 80)
             self.assertNotEqual(tcp.sport, 12345)
             external_port = tcp.sport
             self.assertEqual(tcp.dport, 80)
-            self.check_tcp_checksum(p)
-            self.check_ip_checksum(p)
+            self.assert_packet_checksums_valid(p)
         except:
             self.logger.error(ppp("Unexpected or invalid packet:", p))
             raise
         except:
             self.logger.error(ppp("Unexpected or invalid packet:", p))
             raise
@@ -2721,8 +3272,7 @@ class TestNAT44(MethodHolder):
             self.assertEqual(ip.dst, local_host.ip4)
             self.assertEqual(tcp.sport, 80)
             self.assertEqual(tcp.dport, 12345)
             self.assertEqual(ip.dst, local_host.ip4)
             self.assertEqual(tcp.sport, 80)
             self.assertEqual(tcp.dport, 12345)
-            self.check_tcp_checksum(p)
-            self.check_ip_checksum(p)
+            self.assert_packet_checksums_valid(p)
         except:
             self.logger.error(ppp("Unexpected or invalid packet:", p))
             raise
         except:
             self.logger.error(ppp("Unexpected or invalid packet:", p))
             raise
@@ -2738,7 +3288,7 @@ class TestNAT44(MethodHolder):
         self.pg0.add_stream(pkts)
         self.pg_enable_capture(self.pg_interfaces)
         self.pg_start()
         self.pg0.add_stream(pkts)
         self.pg_enable_capture(self.pg_interfaces)
         self.pg_start()
-        capture = self.pg1.get_capture(len(pkts))
+        self.pg1.get_capture(len(pkts))
 
         sessions = self.vapi.nat44_user_session_dump(self.pg0.remote_ip4n, 0)
         nsessions = len(sessions)
 
         sessions = self.vapi.nat44_user_session_dump(self.pg0.remote_ip4n, 0)
         nsessions = len(sessions)
@@ -2754,6 +3304,12 @@ class TestNAT44(MethodHolder):
         sessions = self.vapi.nat44_user_session_dump(self.pg0.remote_ip4n, 0)
         self.assertEqual(nsessions - len(sessions), 2)
 
         sessions = self.vapi.nat44_user_session_dump(self.pg0.remote_ip4n, 0)
         self.assertEqual(nsessions - len(sessions), 2)
 
+        self.vapi.nat44_del_session(sessions[0].inside_ip_address,
+                                    sessions[0].inside_port,
+                                    sessions[0].protocol)
+
+        self.verify_no_nat44_user()
+
     def test_set_get_reass(self):
         """ NAT44 set/get virtual fragmentation reassembly """
         reas_cfg1 = self.vapi.nat_get_reass()
     def test_set_get_reass(self):
         """ NAT44 set/get virtual fragmentation reassembly """
         reas_cfg1 = self.vapi.nat_get_reass()
@@ -2773,41 +3329,31 @@ class TestNAT44(MethodHolder):
 
     def test_frag_in_order(self):
         """ NAT44 translate fragments arriving in order """
 
     def test_frag_in_order(self):
         """ NAT44 translate fragments arriving in order """
+
         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.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)
 
-        data = "A" * 4 + "B" * 16 + "C" * 3
-        self.tcp_port_in = random.randint(1025, 65535)
-
-        reass = self.vapi.nat_reass_dump()
-        reass_n_start = len(reass)
+        self.frag_in_order(proto=IP_PROTOS.tcp)
+        self.frag_in_order(proto=IP_PROTOS.udp)
+        self.frag_in_order(proto=IP_PROTOS.icmp)
 
 
-        # in2out
-        pkts = self.create_stream_frag(self.pg0,
-                                       self.pg1.remote_ip4,
-                                       self.tcp_port_in,
-                                       20,
-                                       data)
-        self.pg0.add_stream(pkts)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        frags = self.pg1.get_capture(len(pkts))
-        p = self.reass_frags_and_verify(frags,
-                                        self.nat_addr,
-                                        self.pg1.remote_ip4)
-        self.assertEqual(p[TCP].dport, 20)
-        self.assertNotEqual(p[TCP].sport, self.tcp_port_in)
-        self.tcp_port_out = p[TCP].sport
-        self.assertEqual(data, p[Raw].load)
+    def test_frag_forwarding(self):
+        """ NAT44 forwarding fragment test """
+        self.vapi.nat44_add_interface_addr(self.pg1.sw_if_index)
+        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.nat44_forwarding_enable_disable(1)
 
 
-        # out2in
+        data = "A" * 16 + "B" * 16 + "C" * 3
         pkts = self.create_stream_frag(self.pg1,
         pkts = self.create_stream_frag(self.pg1,
-                                       self.nat_addr,
-                                       20,
-                                       self.tcp_port_out,
-                                       data)
+                                       self.pg0.remote_ip4,
+                                       4789,
+                                       4789,
+                                       data,
+                                       proto=IP_PROTOS.udp)
         self.pg1.add_stream(pkts)
         self.pg_enable_capture(self.pg_interfaces)
         self.pg_start()
         self.pg1.add_stream(pkts)
         self.pg_enable_capture(self.pg_interfaces)
         self.pg_start()
@@ -2815,97 +3361,48 @@ class TestNAT44(MethodHolder):
         p = self.reass_frags_and_verify(frags,
                                         self.pg1.remote_ip4,
                                         self.pg0.remote_ip4)
         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(p[UDP].sport, 4789)
+        self.assertEqual(p[UDP].dport, 4789)
         self.assertEqual(data, p[Raw].load)
 
         self.assertEqual(data, p[Raw].load)
 
-        reass = self.vapi.nat_reass_dump()
-        reass_n_end = len(reass)
-
-        self.assertEqual(reass_n_end - reass_n_start, 2)
-
     def test_reass_hairpinning(self):
         """ NAT44 fragments hairpinning """
     def test_reass_hairpinning(self):
         """ NAT44 fragments hairpinning """
-        host = self.pg0.remote_hosts[0]
-        server = self.pg0.remote_hosts[1]
-        host_in_port = random.randint(1025, 65535)
-        host_out_port = 0
-        server_in_port = random.randint(1025, 65535)
-        server_out_port = random.randint(1025, 65535)
-        data = "A" * 4 + "B" * 16 + "C" * 3
+
+        self.server = self.pg0.remote_hosts[1]
+        self.host_in_port = random.randint(1025, 65535)
+        self.server_in_port = random.randint(1025, 65535)
+        self.server_out_port = random.randint(1025, 65535)
 
         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)
         # add static mapping for server
 
         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)
         # add static mapping for server
-        self.nat44_add_static_mapping(server.ip4, self.nat_addr,
-                                      server_in_port, server_out_port,
+        self.nat44_add_static_mapping(self.server.ip4, self.nat_addr,
+                                      self.server_in_port,
+                                      self.server_out_port,
                                       proto=IP_PROTOS.tcp)
                                       proto=IP_PROTOS.tcp)
+        self.nat44_add_static_mapping(self.server.ip4, self.nat_addr,
+                                      self.server_in_port,
+                                      self.server_out_port,
+                                      proto=IP_PROTOS.udp)
+        self.nat44_add_static_mapping(self.server.ip4, self.nat_addr)
 
 
-        # send packet from host to server
-        pkts = self.create_stream_frag(self.pg0,
-                                       self.nat_addr,
-                                       host_in_port,
-                                       server_out_port,
-                                       data)
-        self.pg0.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.nat_addr,
-                                        server.ip4)
-        self.assertNotEqual(p[TCP].sport, host_in_port)
-        self.assertEqual(p[TCP].dport, server_in_port)
-        self.assertEqual(data, p[Raw].load)
+        self.reass_hairpinning(proto=IP_PROTOS.tcp)
+        self.reass_hairpinning(proto=IP_PROTOS.udp)
+        self.reass_hairpinning(proto=IP_PROTOS.icmp)
 
     def test_frag_out_of_order(self):
         """ NAT44 translate fragments arriving out of order """
 
     def test_frag_out_of_order(self):
         """ NAT44 translate fragments arriving out of order """
+
         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.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)
 
-        data = "A" * 4 + "B" * 16 + "C" * 3
-        random.randint(1025, 65535)
-
-        # in2out
-        pkts = self.create_stream_frag(self.pg0,
-                                       self.pg1.remote_ip4,
-                                       self.tcp_port_in,
-                                       20,
-                                       data)
-        pkts.reverse()
-        self.pg0.add_stream(pkts)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        frags = self.pg1.get_capture(len(pkts))
-        p = self.reass_frags_and_verify(frags,
-                                        self.nat_addr,
-                                        self.pg1.remote_ip4)
-        self.assertEqual(p[TCP].dport, 20)
-        self.assertNotEqual(p[TCP].sport, self.tcp_port_in)
-        self.tcp_port_out = p[TCP].sport
-        self.assertEqual(data, p[Raw].load)
-
-        # out2in
-        pkts = self.create_stream_frag(self.pg1,
-                                       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)
+        self.frag_out_of_order(proto=IP_PROTOS.tcp)
+        self.frag_out_of_order(proto=IP_PROTOS.udp)
+        self.frag_out_of_order(proto=IP_PROTOS.icmp)
 
     def test_port_restricted(self):
         """ Port restricted NAT44 (MAP-E CE) """
 
     def test_port_restricted(self):
         """ Port restricted NAT44 (MAP-E CE) """
@@ -2913,8 +3410,10 @@ 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)
         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")
+        self.vapi.nat_set_addr_and_port_alloc_alg(alg=1,
+                                                  psid_offset=6,
+                                                  psid_length=6,
+                                                  psid=10)
 
         p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
              IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
 
         p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
              IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
@@ -2932,177 +3431,2502 @@ class TestNAT44(MethodHolder):
             self.assertEqual(tcp.dport, 22)
             self.assertNotEqual(tcp.sport, 4567)
             self.assertEqual((tcp.sport >> 6) & 63, 10)
             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)
+            self.assert_packet_checksums_valid(p)
         except:
             self.logger.error(ppp("Unexpected or invalid packet:", p))
             raise
 
         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
+    def test_port_range(self):
+        """ External address port range """
         self.nat44_add_address(self.nat_addr)
         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)
         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.nat_set_addr_and_port_alloc_alg(alg=2,
+                                                  start_port=1025,
+                                                  end_port=1027)
 
 
-        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)
+        pkts = []
+        for port in range(0, 5):
+            p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+                 IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+                 TCP(sport=1125 + port))
+            pkts.append(p)
+        self.pg0.add_stream(pkts)
         self.pg_enable_capture(self.pg_interfaces)
         self.pg_start()
         self.pg_enable_capture(self.pg_interfaces)
         self.pg_start()
-        capture = self.pg0.get_capture(1)
-        p = capture[0]
-        try:
-            ip = p[IP]
+        capture = self.pg1.get_capture(3)
+        for p in capture:
             tcp = p[TCP]
             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
+            self.assertGreaterEqual(tcp.sport, 1025)
+            self.assertLessEqual(tcp.sport, 1027)
 
 
-        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))
-        self.pg0.add_stream(p)
+    def test_ipfix_max_frags(self):
+        """ IPFIX logging maximum fragments pending reassembly exceeded """
+        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.nat_set_reass(max_frag=1)
+        self.vapi.set_ipfix_exporter(collector_address=self.pg3.remote_ip4n,
+                                     src_address=self.pg3.local_ip4n,
+                                     path_mtu=512,
+                                     template_interval=10)
+        self.vapi.nat_ipfix(domain_id=self.ipfix_domain_id,
+                            src_port=self.ipfix_src_port)
+
+        data = "A" * 4 + "B" * 16 + "C" * 3
+        self.tcp_port_in = random.randint(1025, 65535)
+        pkts = self.create_stream_frag(self.pg0,
+                                       self.pg1.remote_ip4,
+                                       self.tcp_port_in,
+                                       20,
+                                       data)
+        pkts.reverse()
+        self.pg0.add_stream(pkts)
         self.pg_enable_capture(self.pg_interfaces)
         self.pg_start()
         self.pg_enable_capture(self.pg_interfaces)
         self.pg_start()
-        capture = self.pg1.get_capture(1)
-        p = capture[0]
+        self.pg1.assert_nothing_captured()
+        sleep(1)
+        self.vapi.cli("ipfix flush")  # FIXME this should be an API call
+        capture = self.pg3.get_capture(9)
+        ipfix = IPFIXDecoder()
+        # first load template
+        for p in capture:
+            self.assertTrue(p.haslayer(IPFIX))
+            self.assertEqual(p[IP].src, self.pg3.local_ip4)
+            self.assertEqual(p[IP].dst, self.pg3.remote_ip4)
+            self.assertEqual(p[UDP].sport, self.ipfix_src_port)
+            self.assertEqual(p[UDP].dport, 4739)
+            self.assertEqual(p[IPFIX].observationDomainID,
+                             self.ipfix_domain_id)
+            if p.haslayer(Template):
+                ipfix.add_template(p.getlayer(Template))
+        # verify events in data set
+        for p in capture:
+            if p.haslayer(Data):
+                data = ipfix.decode_data_set(p.getlayer(Set))
+                self.verify_ipfix_max_fragments_ip4(data, 1,
+                                                    self.pg0.remote_ip4n)
+
+    def test_multiple_outside_vrf(self):
+        """ Multiple outside VRF """
+        vrf_id1 = 1
+        vrf_id2 = 2
+
+        self.pg1.unconfig_ip4()
+        self.pg2.unconfig_ip4()
+        self.vapi.ip_table_add_del(vrf_id1, is_add=1)
+        self.vapi.ip_table_add_del(vrf_id2, is_add=1)
+        self.pg1.set_table_ip4(vrf_id1)
+        self.pg2.set_table_ip4(vrf_id2)
+        self.pg1.config_ip4()
+        self.pg2.config_ip4()
+        self.pg1.resolve_arp()
+        self.pg2.resolve_arp()
+
+        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.nat44_interface_add_del_feature(self.pg2.sw_if_index,
+                                                  is_inside=0)
+
         try:
         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, 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
+            # first VRF
+            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, self.nat_addr)
 
 
-    def test_twice_nat_lb(self):
-        """ Twice NAT44 local service load balancing """
-        external_addr_n = socket.inet_pton(socket.AF_INET, self.nat_addr)
-        twice_nat_addr = '10.0.1.3'
-        local_port = 8080
-        external_port = 80
-        eh_port_out = 4567
-        eh_port_in = 0
-        server1 = self.pg0.remote_hosts[0]
-        server2 = self.pg0.remote_hosts[1]
+            pkts = self.create_stream_out(self.pg1, self.nat_addr)
+            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)
 
 
-        locals = [{'addr': server1.ip4n,
-                   'port': local_port,
-                   'probability': 50},
-                  {'addr': server2.ip4n,
-                   'port': local_port,
-                   'probability': 50}]
+            self.tcp_port_in = 60303
+            self.udp_port_in = 60304
+            self.icmp_id_in = 60305
+
+            # second VRF
+            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_out(capture, self.nat_addr)
+
+            pkts = self.create_stream_out(self.pg2, self.nat_addr)
+            self.pg2.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)
+
+        finally:
+            self.pg1.unconfig_ip4()
+            self.pg2.unconfig_ip4()
+            self.pg1.set_table_ip4(0)
+            self.pg2.set_table_ip4(0)
+            self.pg1.config_ip4()
+            self.pg2.config_ip4()
+            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)
         self.nat44_add_address(self.nat_addr)
-        self.nat44_add_address(twice_nat_addr, 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)
+        self.vapi.nat_set_timeouts(udp=5)
 
 
-        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)
+        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)
         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.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=external_port))
-        self.pg1.add_stream(p)
+        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="S", options=[('MSS', 1400)]))
+
+        self.vapi.nat_set_mss_clamping(enable=1, mss_value=1000)
+        self.pg0.add_stream(p)
         self.pg_enable_capture(self.pg_interfaces)
         self.pg_start()
         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.src, twice_nat_addr)
-            self.assertIn(ip.dst, [server1.ip4, server2.ip4])
-            if ip.dst == server1.ip4:
-                server = server1
-            else:
-                server = server2
-            self.assertNotEqual(tcp.sport, eh_port_out)
-            eh_port_in = tcp.sport
-            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
+        capture = self.pg1.get_capture(1)
+        # Negotiated MSS value greater than configured - changed
+        self.verify_mss_value(capture[0], 1000)
 
 
-        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.vapi.nat_set_mss_clamping(enable=0)
         self.pg0.add_stream(p)
         self.pg_enable_capture(self.pg_interfaces)
         self.pg_start()
         capture = self.pg1.get_capture(1)
         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(ip.dst, self.pg1.remote_ip4)
-            self.assertEqual(tcp.sport, external_port)
-            self.assertEqual(tcp.dport, eh_port_out)
-            self.check_tcp_checksum(p)
-            self.check_ip_checksum(p)
-        except:
-            self.logger.error(ppp("Unexpected or invalid packet:", p))
+        # MSS clamping disabled - negotiated MSS unchanged
+        self.verify_mss_value(capture[0], 1400)
+
+        self.vapi.nat_set_mss_clamping(enable=1, mss_value=1500)
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(1)
+        # Negotiated MSS value smaller than configured - unchanged
+        self.verify_mss_value(capture[0], 1400)
+
+    def tearDown(self):
+        super(TestNAT44, self).tearDown()
+        if not self.vpp_dead:
+            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.logger.info(self.vapi.cli("show nat44 hash tables detail"))
+            self.logger.info(self.vapi.cli("show nat timeouts"))
+            self.logger.info(
+                self.vapi.cli("show nat addr-port-assignment-alg"))
+            self.clear_nat44()
+            self.vapi.cli("clear logging")
+
+
+class TestNAT44EndpointDependent(MethodHolder):
+    """ Endpoint-Dependent mapping and filtering test cases """
+
+    @classmethod
+    def setUpConstants(cls):
+        super(TestNAT44EndpointDependent, cls).setUpConstants()
+        cls.vpp_cmdline.extend(["nat", "{", "endpoint-dependent", "}"])
+
+    @classmethod
+    def setUpClass(cls):
+        super(TestNAT44EndpointDependent, cls).setUpClass()
+        cls.vapi.cli("set log class nat level debug")
+        try:
+            cls.tcp_port_in = 6303
+            cls.tcp_port_out = 6303
+            cls.udp_port_in = 6304
+            cls.udp_port_out = 6304
+            cls.icmp_id_in = 6305
+            cls.icmp_id_out = 6305
+            cls.nat_addr = '10.0.0.3'
+            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(7))
+            cls.interfaces = list(cls.pg_interfaces[0:3])
+
+            for i in cls.interfaces:
+                i.admin_up()
+                i.config_ip4()
+                i.resolve_arp()
+
+            cls.pg0.generate_remote_hosts(3)
+            cls.pg0.configure_ipv4_neighbors()
+
+            cls.pg3.admin_up()
+
+            cls.pg4.generate_remote_hosts(2)
+            cls.pg4.config_ip4()
+            ip_addr_n = socket.inet_pton(socket.AF_INET, "10.0.0.1")
+            cls.vapi.sw_interface_add_del_address(cls.pg4.sw_if_index,
+                                                  ip_addr_n,
+                                                  24)
+            cls.pg4.admin_up()
+            cls.pg4.resolve_arp()
+            cls.pg4._remote_hosts[1]._ip4 = cls.pg4._remote_hosts[0]._ip4
+            cls.pg4.resolve_arp()
+
+            zero_ip4n = socket.inet_pton(socket.AF_INET, "0.0.0.0")
+            cls.vapi.ip_table_add_del(1, is_add=1)
+
+            cls.pg5._local_ip4 = "10.1.1.1"
+            cls.pg5._local_ip4n = socket.inet_pton(socket.AF_INET,
+                                                   cls.pg5.local_ip4)
+            cls.pg5._remote_hosts[0]._ip4 = "10.1.1.2"
+            cls.pg5._remote_hosts[0]._ip4n = socket.inet_pton(
+                socket.AF_INET, cls.pg5.remote_ip4)
+            cls.pg5.set_table_ip4(1)
+            cls.pg5.config_ip4()
+            cls.pg5.admin_up()
+            cls.vapi.ip_add_del_route(dst_address=cls.pg5.remote_ip4n,
+                                      dst_address_length=32,
+                                      table_id=1,
+                                      next_hop_sw_if_index=cls.pg5.sw_if_index,
+                                      next_hop_address=zero_ip4n)
+
+            cls.pg6._local_ip4 = "10.1.2.1"
+            cls.pg6._local_ip4n = socket.inet_pton(socket.AF_INET,
+                                                   cls.pg6.local_ip4)
+            cls.pg6._remote_hosts[0]._ip4 = "10.1.2.2"
+            cls.pg6._remote_hosts[0]._ip4n = socket.inet_pton(
+                socket.AF_INET, cls.pg6.remote_ip4)
+            cls.pg6.set_table_ip4(1)
+            cls.pg6.config_ip4()
+            cls.pg6.admin_up()
+            cls.vapi.ip_add_del_route(dst_address=cls.pg6.remote_ip4n,
+                                      dst_address_length=32,
+                                      table_id=1,
+                                      next_hop_sw_if_index=cls.pg6.sw_if_index,
+                                      next_hop_address=zero_ip4n)
+
+            cls.vapi.ip_add_del_route(dst_address=cls.pg6.remote_ip4n,
+                                      dst_address_length=16,
+                                      next_hop_address=zero_ip4n,
+                                      table_id=0,
+                                      next_hop_table_id=1)
+            cls.vapi.ip_add_del_route(dst_address=zero_ip4n,
+                                      dst_address_length=0,
+                                      next_hop_address=zero_ip4n,
+                                      table_id=1,
+                                      next_hop_table_id=0)
+            cls.vapi.ip_add_del_route(dst_address=zero_ip4n,
+                                      dst_address_length=0,
+                                      table_id=0,
+                                      next_hop_sw_if_index=cls.pg1.sw_if_index,
+                                      next_hop_address=cls.pg1.local_ip4n)
+
+            cls.pg5.resolve_arp()
+            cls.pg6.resolve_arp()
+
+        except Exception:
+            super(TestNAT44EndpointDependent, cls).tearDownClass()
+            raise
+
+    def test_frag_in_order(self):
+        """ NAT44 translate fragments arriving in order """
+        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.frag_in_order(proto=IP_PROTOS.tcp)
+        self.frag_in_order(proto=IP_PROTOS.udp)
+        self.frag_in_order(proto=IP_PROTOS.icmp)
+
+    def test_frag_in_order_dont_translate(self):
+        """ NAT44 don't translate fragments arriving in order """
+        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.nat44_forwarding_enable_disable(enable=True)
+        self.frag_in_order(proto=IP_PROTOS.tcp, dont_translate=True)
+
+    def test_frag_out_of_order(self):
+        """ NAT44 translate fragments arriving out of order """
+        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.frag_out_of_order(proto=IP_PROTOS.tcp)
+        self.frag_out_of_order(proto=IP_PROTOS.udp)
+        self.frag_out_of_order(proto=IP_PROTOS.icmp)
+
+    def test_frag_out_of_order_dont_translate(self):
+        """ NAT44 don't translate fragments arriving out of order """
+        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.nat44_forwarding_enable_disable(enable=True)
+        self.frag_out_of_order(proto=IP_PROTOS.tcp, dont_translate=True)
+
+    def test_frag_in_order_in_plus_out(self):
+        """ in+out interface fragments in order """
+        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_feature(self.pg1.sw_if_index)
+        self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+                                                  is_inside=0)
+
+        self.server = self.pg1.remote_hosts[0]
+
+        self.server_in_addr = self.server.ip4
+        self.server_out_addr = '11.11.11.11'
+        self.server_in_port = random.randint(1025, 65535)
+        self.server_out_port = random.randint(1025, 65535)
+
+        self.nat44_add_address(self.server_out_addr)
+
+        # add static mappings for server
+        self.nat44_add_static_mapping(self.server_in_addr,
+                                      self.server_out_addr,
+                                      self.server_in_port,
+                                      self.server_out_port,
+                                      proto=IP_PROTOS.tcp)
+        self.nat44_add_static_mapping(self.server_in_addr,
+                                      self.server_out_addr,
+                                      self.server_in_port,
+                                      self.server_out_port,
+                                      proto=IP_PROTOS.udp)
+        self.nat44_add_static_mapping(self.server_in_addr,
+                                      self.server_out_addr,
+                                      proto=IP_PROTOS.icmp)
+
+        self.vapi.nat_set_reass(timeout=10)
+
+        self.frag_in_order_in_plus_out(proto=IP_PROTOS.tcp)
+        self.frag_in_order_in_plus_out(proto=IP_PROTOS.udp)
+        self.frag_in_order_in_plus_out(proto=IP_PROTOS.icmp)
+
+    def test_frag_out_of_order_in_plus_out(self):
+        """ in+out interface fragments out of order """
+        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_feature(self.pg1.sw_if_index)
+        self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+                                                  is_inside=0)
+
+        self.server = self.pg1.remote_hosts[0]
+
+        self.server_in_addr = self.server.ip4
+        self.server_out_addr = '11.11.11.11'
+        self.server_in_port = random.randint(1025, 65535)
+        self.server_out_port = random.randint(1025, 65535)
+
+        self.nat44_add_address(self.server_out_addr)
+
+        # add static mappings for server
+        self.nat44_add_static_mapping(self.server_in_addr,
+                                      self.server_out_addr,
+                                      self.server_in_port,
+                                      self.server_out_port,
+                                      proto=IP_PROTOS.tcp)
+        self.nat44_add_static_mapping(self.server_in_addr,
+                                      self.server_out_addr,
+                                      self.server_in_port,
+                                      self.server_out_port,
+                                      proto=IP_PROTOS.udp)
+        self.nat44_add_static_mapping(self.server_in_addr,
+                                      self.server_out_addr,
+                                      proto=IP_PROTOS.icmp)
+
+        self.vapi.nat_set_reass(timeout=10)
+
+        self.frag_out_of_order_in_plus_out(proto=IP_PROTOS.tcp)
+        self.frag_out_of_order_in_plus_out(proto=IP_PROTOS.udp)
+        self.frag_out_of_order_in_plus_out(proto=IP_PROTOS.icmp)
+
+    def test_reass_hairpinning(self):
+        """ NAT44 fragments hairpinning """
+        self.server = self.pg0.remote_hosts[1]
+        self.host_in_port = random.randint(1025, 65535)
+        self.server_in_port = random.randint(1025, 65535)
+        self.server_out_port = random.randint(1025, 65535)
+
+        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)
+        # add static mapping for server
+        self.nat44_add_static_mapping(self.server.ip4, self.nat_addr,
+                                      self.server_in_port,
+                                      self.server_out_port,
+                                      proto=IP_PROTOS.tcp)
+        self.nat44_add_static_mapping(self.server.ip4, self.nat_addr,
+                                      self.server_in_port,
+                                      self.server_out_port,
+                                      proto=IP_PROTOS.udp)
+        self.nat44_add_static_mapping(self.server.ip4, self.nat_addr)
+
+        self.reass_hairpinning(proto=IP_PROTOS.tcp)
+        self.reass_hairpinning(proto=IP_PROTOS.udp)
+        self.reass_hairpinning(proto=IP_PROTOS.icmp)
+
+    def test_dynamic(self):
+        """ NAT44 dynamic translation test """
+
+        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)
+
+        nat_config = self.vapi.nat_show_config()
+        self.assertEqual(1, nat_config.endpoint_dependent)
+
+        # in2out
+        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)
+
+        # out2in
+        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_forwarding(self):
+        """ NAT44 forwarding test """
+
+        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.nat44_forwarding_enable_disable(1)
+
+        real_ip = self.pg0.remote_ip4n
+        alias_ip = self.nat_addr_n
+        self.vapi.nat44_add_del_static_mapping(local_ip=real_ip,
+                                               external_ip=alias_ip)
+
+        try:
+            # in2out - static mapping match
+
+            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)
+
+            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, same_port=True)
+
+            # in2out - no static mapping match
+
+            host0 = self.pg0.remote_hosts[0]
+            self.pg0.remote_hosts[0] = self.pg0.remote_hosts[1]
+            try:
+                pkts = self.create_stream_out(self.pg1,
+                                              dst_ip=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)
+            finally:
+                self.pg0.remote_hosts[0] = host0
+
+            user = self.pg0.remote_hosts[1]
+            sessions = self.vapi.nat44_user_session_dump(user.ip4n, 0)
+            self.assertEqual(len(sessions), 3)
+            self.assertTrue(sessions[0].ext_host_valid)
+            self.vapi.nat44_del_session(
+                sessions[0].inside_ip_address,
+                sessions[0].inside_port,
+                sessions[0].protocol,
+                ext_host_address=sessions[0].ext_host_address,
+                ext_host_port=sessions[0].ext_host_port)
+            sessions = self.vapi.nat44_user_session_dump(user.ip4n, 0)
+            self.assertEqual(len(sessions), 2)
+
+        finally:
+            self.vapi.nat44_forwarding_enable_disable(0)
+            self.vapi.nat44_add_del_static_mapping(local_ip=real_ip,
+                                                   external_ip=alias_ip,
+                                                   is_add=0)
+
+    def test_static_lb(self):
+        """ NAT44 local service load balancing """
+        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': 70,
+                   'vrf_id': 0},
+                  {'addr': server2.ip4n,
+                   'port': local_port,
+                   'probability': 30,
+                   'vrf_id': 0}]
+
+        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)
+
+        # 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.assertIn(ip.dst, [server1.ip4, server2.ip4])
+            if ip.dst == server1.ip4:
+                server = server1
+            else:
+                server = server2
+            self.assertEqual(tcp.dport, local_port)
+            self.assert_packet_checksums_valid(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        # 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.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.assert_packet_checksums_valid(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        sessions = self.vapi.nat44_user_session_dump(server.ip4n, 0)
+        self.assertEqual(len(sessions), 1)
+        self.assertTrue(sessions[0].ext_host_valid)
+        self.vapi.nat44_del_session(
+            sessions[0].inside_ip_address,
+            sessions[0].inside_port,
+            sessions[0].protocol,
+            ext_host_address=sessions[0].ext_host_address,
+            ext_host_port=sessions[0].ext_host_port)
+        sessions = self.vapi.nat44_user_session_dump(server.ip4n, 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"""
+
+        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,
+                   'vrf_id': 0},
+                  {'addr': server2.ip4n,
+                   'port': local_port,
+                   'probability': 10,
+                   'vrf_id': 0}]
+
+        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, 50)
+        pkts = []
+        for client in clients:
+            p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
+                 IP(src=client, dst=self.nat_addr) /
+                 TCP(sport=12345, dport=external_port))
+            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 p in capture:
+            if p[IP].dst == server1.ip4:
+                server1_n += 1
+            else:
+                server2_n += 1
+        self.assertTrue(server1_n > server2_n)
+
+    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]
+
+        locals = [{'addr': server1.ip4n,
+                   'port': local_port,
+                   'probability': 70,
+                   'vrf_id': 0},
+                  {'addr': server2.ip4n,
+                   'port': local_port,
+                   'probability': 30,
+                   'vrf_id': 0}]
+
+        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,
+                                                  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.assertIn(ip.dst, [server1.ip4, server2.ip4])
+            if ip.dst == server1.ip4:
+                server = server1
+            else:
+                server = server2
+            self.assertEqual(tcp.dport, local_port)
+            self.assert_packet_checksums_valid(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        # 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.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.assert_packet_checksums_valid(p)
+        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=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.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.assert_packet_checksums_valid(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.assert_packet_checksums_valid(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+    def test_lb_affinity(self):
+        """ NAT44 local service load balancing affinity """
+        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': 50,
+                   'vrf_id': 0},
+                  {'addr': server2.ip4n,
+                   'port': local_port,
+                   'probability': 50,
+                   'vrf_id': 0}]
+
+        self.nat44_add_address(self.nat_addr)
+        self.vapi.nat44_add_del_lb_static_mapping(external_addr_n,
+                                                  external_port,
+                                                  IP_PROTOS.tcp,
+                                                  affinity=10800,
+                                                  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)
+
+        p = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
+             IP(src=self.pg1.remote_ip4, dst=self.nat_addr) /
+             TCP(sport=1025, dport=external_port))
+        self.pg1.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg0.get_capture(1)
+        backend = capture[0][IP].dst
+
+        sessions = self.vapi.nat44_user_session_dump(
+            socket.inet_pton(socket.AF_INET, backend), 0)
+        self.assertEqual(len(sessions), 1)
+        self.assertTrue(sessions[0].ext_host_valid)
+        self.vapi.nat44_del_session(
+            sessions[0].inside_ip_address,
+            sessions[0].inside_port,
+            sessions[0].protocol,
+            ext_host_address=sessions[0].ext_host_address,
+            ext_host_port=sessions[0].ext_host_port)
+
+        pkts = []
+        for port in range(1030, 1100):
+            p = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
+                 IP(src=self.pg1.remote_ip4, dst=self.nat_addr) /
+                 TCP(sport=port, dport=external_port))
+            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 p in capture:
+            self.assertEqual(p[IP].dst, backend)
+
+    def test_unknown_proto(self):
+        """ NAT44 translate packet with unknown protocol """
+        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)
+
+        # in2out
+        p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+             IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+             TCP(sport=self.tcp_port_in, dport=20))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        p = self.pg1.get_capture(1)
+
+        p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+             IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+             GRE() /
+             IP(src=self.pg2.remote_ip4, dst=self.pg2.remote_ip4) /
+             TCP(sport=1234, dport=1234))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        p = self.pg1.get_capture(1)
+        packet = p[0]
+        try:
+            self.assertEqual(packet[IP].src, self.nat_addr)
+            self.assertEqual(packet[IP].dst, self.pg1.remote_ip4)
+            self.assertTrue(packet.haslayer(GRE))
+            self.assert_packet_checksums_valid(packet)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", packet))
+            raise
+
+        # out2in
+        p = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
+             IP(src=self.pg1.remote_ip4, dst=self.nat_addr) /
+             GRE() /
+             IP(src=self.pg2.remote_ip4, dst=self.pg2.remote_ip4) /
+             TCP(sport=1234, dport=1234))
+        self.pg1.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        p = self.pg0.get_capture(1)
+        packet = p[0]
+        try:
+            self.assertEqual(packet[IP].src, self.pg1.remote_ip4)
+            self.assertEqual(packet[IP].dst, self.pg0.remote_ip4)
+            self.assertTrue(packet.haslayer(GRE))
+            self.assert_packet_checksums_valid(packet)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", packet))
+            raise
+
+    def test_hairpinning_unknown_proto(self):
+        """ NAT44 translate packet with unknown protocol - hairpinning """
+        host = self.pg0.remote_hosts[0]
+        server = self.pg0.remote_hosts[1]
+        host_in_port = 1234
+        server_out_port = 8765
+        server_nat_ip = "10.0.0.11"
+
+        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)
+
+        # add static mapping for server
+        self.nat44_add_static_mapping(server.ip4, server_nat_ip)
+
+        # host to server
+        p = (Ether(src=host.mac, dst=self.pg0.local_mac) /
+             IP(src=host.ip4, dst=server_nat_ip) /
+             TCP(sport=host_in_port, dport=server_out_port))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        self.pg0.get_capture(1)
+
+        p = (Ether(dst=self.pg0.local_mac, src=host.mac) /
+             IP(src=host.ip4, dst=server_nat_ip) /
+             GRE() /
+             IP(src=self.pg2.remote_ip4, dst=self.pg2.remote_ip4) /
+             TCP(sport=1234, dport=1234))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        p = self.pg0.get_capture(1)
+        packet = p[0]
+        try:
+            self.assertEqual(packet[IP].src, self.nat_addr)
+            self.assertEqual(packet[IP].dst, server.ip4)
+            self.assertTrue(packet.haslayer(GRE))
+            self.assert_packet_checksums_valid(packet)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", packet))
+            raise
+
+        # server to host
+        p = (Ether(dst=self.pg0.local_mac, src=server.mac) /
+             IP(src=server.ip4, dst=self.nat_addr) /
+             GRE() /
+             IP(src=self.pg2.remote_ip4, dst=self.pg2.remote_ip4) /
+             TCP(sport=1234, dport=1234))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        p = self.pg0.get_capture(1)
+        packet = p[0]
+        try:
+            self.assertEqual(packet[IP].src, server_nat_ip)
+            self.assertEqual(packet[IP].dst, host.ip4)
+            self.assertTrue(packet.haslayer(GRE))
+            self.assert_packet_checksums_valid(packet)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", packet))
+            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.vapi.nat44_add_del_identity_mapping(ip=self.pg1.remote_ip4n)
+        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_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)
+
+        # 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]
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.dst, self.pg0.remote_ip4)
+            self.assertEqual(tcp.dport, local_port)
+            self.assert_packet_checksums_valid(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.assert_packet_checksums_valid(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)
+
+        # session initiaded from remote host - do not translate
+        self.tcp_port_in = 60303
+        self.udp_port_in = 60304
+        self.icmp_id_in = 60305
+        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.assert_packet_checksums_valid(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.assert_packet_checksums_valid(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+    def test_next_src_nat(self):
+        """ On way back forward packet to nat44-in2out node. """
+        twice_nat_addr = '10.0.1.3'
+        external_port = 80
+        local_port = 8080
+        post_twice_nat_port = 0
+
+        self.vapi.nat44_forwarding_enable_disable(1)
+        self.nat44_add_address(twice_nat_addr, twice_nat=1)
+        self.nat44_add_static_mapping(self.pg6.remote_ip4, self.pg1.remote_ip4,
+                                      local_port, external_port,
+                                      proto=IP_PROTOS.tcp, out2in_only=1,
+                                      self_twice_nat=1, vrf_id=1)
+        self.vapi.nat44_interface_add_del_feature(self.pg6.sw_if_index,
+                                                  is_inside=0)
+
+        p = (Ether(src=self.pg6.remote_mac, dst=self.pg6.local_mac) /
+             IP(src=self.pg6.remote_ip4, dst=self.pg1.remote_ip4) /
+             TCP(sport=12345, dport=external_port))
+        self.pg6.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg6.get_capture(1)
+        p = capture[0]
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.src, twice_nat_addr)
+            self.assertNotEqual(tcp.sport, 12345)
+            post_twice_nat_port = tcp.sport
+            self.assertEqual(ip.dst, self.pg6.remote_ip4)
+            self.assertEqual(tcp.dport, local_port)
+            self.assert_packet_checksums_valid(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        p = (Ether(src=self.pg6.remote_mac, dst=self.pg6.local_mac) /
+             IP(src=self.pg6.remote_ip4, dst=twice_nat_addr) /
+             TCP(sport=local_port, dport=post_twice_nat_port))
+        self.pg6.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg6.get_capture(1)
+        p = capture[0]
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.src, self.pg1.remote_ip4)
+            self.assertEqual(tcp.sport, external_port)
+            self.assertEqual(ip.dst, self.pg6.remote_ip4)
+            self.assertEqual(tcp.dport, 12345)
+            self.assert_packet_checksums_valid(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+    def twice_nat_common(self, self_twice_nat=False, same_pg=False, lb=False,
+                         client_id=None):
+        twice_nat_addr = '10.0.1.3'
+
+        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
+
+        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
+
+        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)
+        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,
+                       'vrf_id': 0},
+                      {'addr': server2.ip4n,
+                       'port': port_in2,
+                       'probability': 50,
+                       'vrf_id': 0}]
+            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)
+
+        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 = pg0.get_capture(1)
+        p = capture[0]
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            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:
+                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
+            saved_port_in = tcp.dport
+            self.assert_packet_checksums_valid(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        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 = 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(tcp.dport, eh_port_out)
+            self.assertEqual(tcp.sport, port_out)
+            self.assert_packet_checksums_valid(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        if eh_translate:
+            sessions = self.vapi.nat44_user_session_dump(server.ip4n, 0)
+            self.assertEqual(len(sessions), 1)
+            self.assertTrue(sessions[0].ext_host_valid)
+            self.assertTrue(sessions[0].is_twicenat)
+            self.vapi.nat44_del_session(
+                sessions[0].inside_ip_address,
+                sessions[0].inside_port,
+                sessions[0].protocol,
+                ext_host_address=sessions[0].ext_host_nat_address,
+                ext_host_port=sessions[0].ext_host_nat_port)
+            sessions = self.vapi.nat44_user_session_dump(server.ip4n, 0)
+            self.assertEqual(len(sessions), 0)
+
+    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.pg3.sw_if_index, twice_nat=1)
+
+        # no address in NAT pool
+        adresses = self.vapi.nat44_address_dump()
+        self.assertEqual(0, len(adresses))
+
+        # configure interface address and check NAT address pool
+        self.pg3.config_ip4()
+        adresses = self.vapi.nat44_address_dump()
+        self.assertEqual(1, len(adresses))
+        self.assertEqual(adresses[0].ip_address[0:4], self.pg3.local_ip4n)
+        self.assertEqual(adresses[0].twice_nat, 1)
+
+        # remove interface address and check NAT address pool
+        self.pg3.unconfig_ip4()
+        adresses = self.vapi.nat44_address_dump()
+        self.assertEqual(0, len(adresses))
+
+    def test_tcp_close(self):
+        """ Close TCP session from inside network - output feature """
+        self.vapi.nat44_forwarding_enable_disable(1)
+        self.nat44_add_address(self.pg1.local_ip4)
+        twice_nat_addr = '10.0.1.3'
+        service_ip = '192.168.16.150'
+        self.nat44_add_address(twice_nat_addr, twice_nat=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)
+        self.nat44_add_static_mapping(self.pg0.remote_ip4,
+                                      service_ip,
+                                      80,
+                                      80,
+                                      proto=IP_PROTOS.tcp,
+                                      out2in_only=1,
+                                      twice_nat=1)
+        sessions = self.vapi.nat44_user_session_dump(self.pg0.remote_ip4n, 0)
+        start_sessnum = len(sessions)
+
+        # SYN packet out->in
+        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()
+        capture = self.pg0.get_capture(1)
+        p = capture[0]
+        tcp_port = p[TCP].sport
+
+        # SYN + ACK packet in->out
+        p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+             IP(src=self.pg0.remote_ip4, dst=twice_nat_addr) /
+             TCP(sport=80, dport=tcp_port, flags="SA"))
+        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=service_ip) /
+             TCP(sport=33898, dport=80, flags="A"))
+        self.pg1.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        self.pg0.get_capture(1)
+
+        # FIN packet in -> out
+        p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+             IP(src=self.pg0.remote_ip4, dst=twice_nat_addr) /
+             TCP(sport=80, dport=tcp_port, flags="FA", seq=100, ack=300))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        self.pg1.get_capture(1)
+
+        # FIN+ACK packet out -> in
+        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()
+        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=twice_nat_addr) /
+             TCP(sport=80, dport=tcp_port, flags="A", seq=101, ack=301))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        self.pg1.get_capture(1)
+
+        sessions = self.vapi.nat44_user_session_dump(self.pg0.remote_ip4n,
+                                                     0)
+        self.assertEqual(len(sessions) - start_sessnum, 0)
+
+    def test_tcp_session_close_in(self):
+        """ Close TCP session from inside network """
+        self.tcp_port_out = 10505
+        self.nat44_add_address(self.nat_addr)
+        self.nat44_add_static_mapping(self.pg0.remote_ip4,
+                                      self.nat_addr,
+                                      self.tcp_port_in,
+                                      self.tcp_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)
+
+        sessions = self.vapi.nat44_user_session_dump(self.pg0.remote_ip4n, 0)
+        start_sessnum = len(sessions)
+
+        self.initiate_tcp_session(self.pg0, self.pg1)
+
+        # 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", seq=100, ack=300))
+        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", seq=300, ack=101))
+        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", seq=300, ack=101))
+        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", seq=101, ack=301))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        self.pg1.get_capture(1)
+
+        sessions = self.vapi.nat44_user_session_dump(self.pg0.remote_ip4n,
+                                                     0)
+        self.assertEqual(len(sessions) - start_sessnum, 0)
+
+    def test_tcp_session_close_out(self):
+        """ Close TCP session from outside network """
+        self.tcp_port_out = 10505
+        self.nat44_add_address(self.nat_addr)
+        self.nat44_add_static_mapping(self.pg0.remote_ip4,
+                                      self.nat_addr,
+                                      self.tcp_port_in,
+                                      self.tcp_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)
+
+        sessions = self.vapi.nat44_user_session_dump(self.pg0.remote_ip4n, 0)
+        start_sessnum = len(sessions)
+
+        self.initiate_tcp_session(self.pg0, self.pg1)
+
+        # 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", seq=100, ack=300))
+        self.pg1.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        self.pg0.get_capture(1)
+
+        # FIN+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", seq=300, ack=101))
+
+        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", seq=101, ack=301))
+        self.pg1.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        self.pg0.get_capture(1)
+
+        sessions = self.vapi.nat44_user_session_dump(self.pg0.remote_ip4n,
+                                                     0)
+        self.assertEqual(len(sessions) - start_sessnum, 0)
+
+    def test_tcp_session_close_simultaneous(self):
+        """ Close TCP session from inside network """
+        self.tcp_port_out = 10505
+        self.nat44_add_address(self.nat_addr)
+        self.nat44_add_static_mapping(self.pg0.remote_ip4,
+                                      self.nat_addr,
+                                      self.tcp_port_in,
+                                      self.tcp_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)
+
+        sessions = self.vapi.nat44_user_session_dump(self.pg0.remote_ip4n, 0)
+        start_sessnum = len(sessions)
+
+        self.initiate_tcp_session(self.pg0, self.pg1)
+
+        # 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", seq=100, ack=300))
+        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", seq=300, ack=100))
+        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", seq=101, ack=301))
+        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", seq=301, ack=101))
+        self.pg1.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        self.pg0.get_capture(1)
+
+        sessions = self.vapi.nat44_user_session_dump(self.pg0.remote_ip4n,
+                                                     0)
+        self.assertEqual(len(sessions) - start_sessnum, 0)
+
+    def test_one_armed_nat44_static(self):
+        """ One armed NAT44 and 1:1 NAPT asymmetrical rule """
+        remote_host = self.pg4.remote_hosts[0]
+        local_host = self.pg4.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.pg4.sw_if_index)
+        self.vapi.nat44_interface_add_del_feature(self.pg4.sw_if_index,
+                                                  is_inside=0)
+
+        # from client to service
+        p = (Ether(src=self.pg4.remote_mac, dst=self.pg4.local_mac) /
+             IP(src=remote_host.ip4, dst=self.nat_addr) /
+             TCP(sport=12345, dport=external_port))
+        self.pg4.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg4.get_capture(1)
+        p = capture[0]
+        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.assert_packet_checksums_valid(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        # from service back to client
+        p = (Ether(src=self.pg4.remote_mac, dst=self.pg4.local_mac) /
+             IP(src=local_host.ip4, dst=self.nat_addr) /
+             TCP(sport=local_port, dport=eh_port_in))
+        self.pg4.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg4.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.assert_packet_checksums_valid(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+    def test_static_with_port_out2(self):
+        """ 1:1 NAPT asymmetrical 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]
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.dst, self.pg0.remote_ip4)
+            self.assertEqual(tcp.dport, local_port)
+            self.assert_packet_checksums_valid(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.assert_packet_checksums_valid(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]
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.dst, self.pg0.remote_ip4)
+            self.assertEqual(tcp.dport, local_port)
+            self.assert_packet_checksums_valid(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.assert_packet_checksums_valid(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+    def test_output_feature(self):
+        """ NAT44 interface output feature (in2out postrouting) """
+        self.vapi.nat44_forwarding_enable_disable(1)
+        self.nat44_add_address(self.nat_addr)
+        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)
+
+        # in2out
+        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)
+
+        # out2in
+        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_multiple_vrf(self):
+        """ Multiple VRF setup """
+        external_addr = '1.2.3.4'
+        external_port = 80
+        local_port = 8080
+        port = 0
+
+        self.vapi.nat44_forwarding_enable_disable(1)
+        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.pg0.sw_if_index,
+                                                  is_inside=0)
+        self.vapi.nat44_interface_add_del_output_feature(self.pg1.sw_if_index,
+                                                         is_inside=0)
+        self.vapi.nat44_interface_add_del_feature(self.pg5.sw_if_index)
+        self.vapi.nat44_interface_add_del_feature(self.pg5.sw_if_index,
+                                                  is_inside=0)
+        self.vapi.nat44_interface_add_del_feature(self.pg6.sw_if_index,
+                                                  is_inside=0)
+        self.nat44_add_static_mapping(self.pg5.remote_ip4, external_addr,
+                                      local_port, external_port, vrf_id=1,
+                                      proto=IP_PROTOS.tcp, out2in_only=1)
+        self.nat44_add_static_mapping(
+             self.pg0.remote_ip4, external_sw_if_index=self.pg0.sw_if_index,
+             local_port=local_port, vrf_id=0, external_port=external_port,
+             proto=IP_PROTOS.tcp, out2in_only=1)
+
+        # from client to service (both VRF1)
+        p = (Ether(src=self.pg6.remote_mac, dst=self.pg6.local_mac) /
+             IP(src=self.pg6.remote_ip4, dst=external_addr) /
+             TCP(sport=12345, dport=external_port))
+        self.pg6.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg5.get_capture(1)
+        p = capture[0]
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.dst, self.pg5.remote_ip4)
+            self.assertEqual(tcp.dport, local_port)
+            self.assert_packet_checksums_valid(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        # from service back to client (both VRF1)
+        p = (Ether(src=self.pg5.remote_mac, dst=self.pg5.local_mac) /
+             IP(src=self.pg5.remote_ip4, dst=self.pg6.remote_ip4) /
+             TCP(sport=local_port, dport=12345))
+        self.pg5.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg6.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.assert_packet_checksums_valid(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        # dynamic NAT from VRF1 to VRF0 (output-feature)
+        p = (Ether(src=self.pg5.remote_mac, dst=self.pg5.local_mac) /
+             IP(src=self.pg5.remote_ip4, dst=self.pg1.remote_ip4) /
+             TCP(sport=2345, dport=22))
+        self.pg5.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.assertNotEqual(tcp.sport, 2345)
+            self.assert_packet_checksums_valid(p)
+            port = tcp.sport
+        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.nat_addr) /
+             TCP(sport=22, dport=port))
+        self.pg1.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg5.get_capture(1)
+        p = capture[0]
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.dst, self.pg5.remote_ip4)
+            self.assertEqual(tcp.dport, 2345)
+            self.assert_packet_checksums_valid(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        # from client VRF1 to service VRF0
+        p = (Ether(src=self.pg6.remote_mac, dst=self.pg6.local_mac) /
+             IP(src=self.pg6.remote_ip4, dst=self.pg0.local_ip4) /
+             TCP(sport=12346, dport=external_port))
+        self.pg6.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(tcp.dport, local_port)
+            self.assert_packet_checksums_valid(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        # from service VRF0 back to client VRF1
+        p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+             IP(src=self.pg0.remote_ip4, dst=self.pg6.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.pg6.get_capture(1)
+        p = capture[0]
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.src, self.pg0.local_ip4)
+            self.assertEqual(tcp.sport, external_port)
+            self.assert_packet_checksums_valid(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        # from client VRF0 to service VRF1
+        p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+             IP(src=self.pg0.remote_ip4, dst=external_addr) /
+             TCP(sport=12347, dport=external_port))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg5.get_capture(1)
+        p = capture[0]
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.dst, self.pg5.remote_ip4)
+            self.assertEqual(tcp.dport, local_port)
+            self.assert_packet_checksums_valid(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        # from service VRF1 back to client VRF0
+        p = (Ether(src=self.pg5.remote_mac, dst=self.pg5.local_mac) /
+             IP(src=self.pg5.remote_ip4, dst=self.pg0.remote_ip4) /
+             TCP(sport=local_port, dport=12347))
+        self.pg5.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.assert_packet_checksums_valid(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        # from client to server (both VRF1, no translation)
+        p = (Ether(src=self.pg6.remote_mac, dst=self.pg6.local_mac) /
+             IP(src=self.pg6.remote_ip4, dst=self.pg5.remote_ip4) /
+             TCP(sport=12348, dport=local_port))
+        self.pg6.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg5.get_capture(1)
+        p = capture[0]
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.dst, self.pg5.remote_ip4)
+            self.assertEqual(tcp.dport, local_port)
+            self.assert_packet_checksums_valid(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        # from server back to client (both VRF1, no translation)
+        p = (Ether(src=self.pg5.remote_mac, dst=self.pg5.local_mac) /
+             IP(src=self.pg5.remote_ip4, dst=self.pg6.remote_ip4) /
+             TCP(sport=local_port, dport=12348))
+        self.pg5.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg6.get_capture(1)
+        p = capture[0]
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.src, self.pg5.remote_ip4)
+            self.assertEqual(tcp.sport, local_port)
+            self.assert_packet_checksums_valid(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        # from client VRF1 to server VRF0 (no translation)
+        p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+             IP(src=self.pg0.remote_ip4, dst=self.pg6.remote_ip4) /
+             TCP(sport=local_port, dport=12349))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg6.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.assert_packet_checksums_valid(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        # from server VRF0 back to client VRF1 (no translation)
+        p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+             IP(src=self.pg0.remote_ip4, dst=self.pg6.remote_ip4) /
+             TCP(sport=local_port, dport=12349))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg6.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.assert_packet_checksums_valid(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        # from client VRF0 to server VRF1 (no translation)
+        p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+             IP(src=self.pg0.remote_ip4, dst=self.pg5.remote_ip4) /
+             TCP(sport=12344, dport=local_port))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg5.get_capture(1)
+        p = capture[0]
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.dst, self.pg5.remote_ip4)
+            self.assertEqual(tcp.dport, local_port)
+            self.assert_packet_checksums_valid(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        # from server VRF1 back to client VRF0 (no translation)
+        p = (Ether(src=self.pg5.remote_mac, dst=self.pg5.local_mac) /
+             IP(src=self.pg5.remote_ip4, dst=self.pg0.remote_ip4) /
+             TCP(sport=local_port, dport=12344))
+        self.pg5.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.pg5.remote_ip4)
+            self.assertEqual(tcp.sport, local_port)
+            self.assert_packet_checksums_valid(p)
+        except:
+            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)
+        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.nat_set_timeouts(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.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=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)
+        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.nat_set_timeouts(tcp_transitory=5)
+
+        nat44_config = self.vapi.nat_show_config()
+
+        self.initiate_tcp_session(self.pg0, self.pg1)
+        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="R"))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        self.pg1.get_capture(1)
+
+        pkts_num = nat44_config.max_translations_per_user - 1
+        pkts = []
+        for i in range(0, pkts_num):
+            p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+                 IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+                 UDP(sport=1025 + i, dport=53))
+            pkts.append(p)
+        self.pg0.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        self.pg1.get_capture(pkts_num)
+
+        sleep(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=self.tcp_port_in + 1, dport=self.tcp_external_port + 1,
+                 flags="S"))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        self.pg1.get_capture(1)
+
+        nsessions = 0
+        users = self.vapi.nat44_user_dump()
+        self.assertEqual(len(users), 1)
+        self.assertEqual(users[0].ip_address, self.pg0.remote_ip4n)
+        self.assertEqual(users[0].nsessions,
+                         nat44_config.max_translations_per_user)
+
+    @unittest.skipUnless(running_extended_tests(), "part of extended tests")
+    def test_session_limit_per_user(self):
+        """ Maximum sessions per user limit """
+        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.set_ipfix_exporter(collector_address=self.pg2.remote_ip4n,
+                                     src_address=self.pg2.local_ip4n,
+                                     path_mtu=512,
+                                     template_interval=10)
+        self.vapi.nat_set_timeouts(udp=5)
+
+        # get maximum number of translations per user
+        nat44_config = self.vapi.nat_show_config()
+
+        pkts = []
+        for port in range(0, nat44_config.max_translations_per_user):
+            p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+                 IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+                 UDP(sport=1025 + port, dport=1025 + port))
+            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))
+
+        self.vapi.nat_ipfix(domain_id=self.ipfix_domain_id,
+                            src_port=self.ipfix_src_port)
+
+        p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+             IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+             UDP(sport=3001, dport=3002))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.assert_nothing_captured()
+
+        # verify IPFIX logging
+        self.vapi.cli("ipfix flush")  # FIXME this should be an API call
+        sleep(1)
+        capture = self.pg2.get_capture(10)
+        ipfix = IPFIXDecoder()
+        # first load template
+        for p in capture:
+            self.assertTrue(p.haslayer(IPFIX))
+            if p.haslayer(Template):
+                ipfix.add_template(p.getlayer(Template))
+        # verify events in data set
+        for p in capture:
+            if p.haslayer(Data):
+                data = ipfix.decode_data_set(p.getlayer(Set))
+                self.verify_ipfix_max_entries_per_user(
+                    data,
+                    nat44_config.max_translations_per_user,
+                    self.pg0.remote_ip4n)
+
+        sleep(6)
+        p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+             IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+             UDP(sport=3001, dport=3002))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        self.pg1.get_capture(1)
+
+    def tearDown(self):
+        super(TestNAT44EndpointDependent, self).tearDown()
+        if not self.vpp_dead:
+            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 nat44 hash tables detail"))
+            self.logger.info(self.vapi.cli("show nat timeouts"))
+            self.clear_nat44()
+            self.vapi.cli("clear logging")
+
+
+class TestNAT44Out2InDPO(MethodHolder):
+    """ NAT44 Test Cases using out2in DPO """
+
+    @classmethod
+    def setUpConstants(cls):
+        super(TestNAT44Out2InDPO, cls).setUpConstants()
+        cls.vpp_cmdline.extend(["nat", "{", "out2in dpo", "}"])
+
+    @classmethod
+    def setUpClass(cls):
+        super(TestNAT44Out2InDPO, cls).setUpClass()
+        cls.vapi.cli("set log class nat level debug")
+
+        try:
+            cls.tcp_port_in = 6303
+            cls.tcp_port_out = 6303
+            cls.udp_port_in = 6304
+            cls.udp_port_out = 6304
+            cls.icmp_id_in = 6305
+            cls.icmp_id_out = 6305
+            cls.nat_addr = '10.0.0.3'
+            cls.nat_addr_n = socket.inet_pton(socket.AF_INET, cls.nat_addr)
+            cls.dst_ip4 = '192.168.70.1'
+
+            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.resolve_ndp()
+
+            cls.vapi.ip_add_del_route(is_ipv6=True, dst_address='\x00'*16,
+                                      dst_address_length=0,
+                                      next_hop_address=cls.pg1.remote_ip6n,
+                                      next_hop_sw_if_index=cls.pg1.sw_if_index)
+
+        except Exception:
+            super(TestNAT44Out2InDPO, cls).tearDownClass()
             raise
 
             raise
 
-    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)
+    def configure_xlat(self):
+        self.dst_ip6_pfx = '1:2:3::'
+        self.dst_ip6_pfx_n = socket.inet_pton(socket.AF_INET6,
+                                              self.dst_ip6_pfx)
+        self.dst_ip6_pfx_len = 96
+        self.src_ip6_pfx = '4:5:6::'
+        self.src_ip6_pfx_n = socket.inet_pton(socket.AF_INET6,
+                                              self.src_ip6_pfx)
+        self.src_ip6_pfx_len = 96
+        self.vapi.map_add_domain(self.dst_ip6_pfx_n, self.dst_ip6_pfx_len,
+                                 self.src_ip6_pfx_n, self.src_ip6_pfx_len,
+                                 '\x00\x00\x00\x00', 0, is_translation=1,
+                                 is_rfc6052=1)
+
+    @unittest.skip('Temporary disabled')
+    def test_464xlat_ce(self):
+        """ Test 464XLAT CE with NAT44 """
+
+        nat_config = self.vapi.nat_show_config()
+        self.assertEqual(1, nat_config.out2in_dpo)
+
+        self.configure_xlat()
+
+        self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+        self.vapi.nat44_add_del_address_range(self.nat_addr_n, self.nat_addr_n)
+
+        out_src_ip6 = self.compose_ip6(self.dst_ip4, self.dst_ip6_pfx,
+                                       self.dst_ip6_pfx_len)
+        out_dst_ip6 = self.compose_ip6(self.nat_addr, self.src_ip6_pfx,
+                                       self.src_ip6_pfx_len)
+
+        try:
+            pkts = self.create_stream_in(self.pg0, self.pg1, self.dst_ip4)
+            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_ip6(capture, nat_ip=out_dst_ip6,
+                                        dst_ip=out_src_ip6)
+
+            pkts = self.create_stream_out_ip6(self.pg1, out_src_ip6,
+                                              out_dst_ip6)
+            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)
+        finally:
+            self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index,
+                                                      is_add=0)
+            self.vapi.nat44_add_del_address_range(self.nat_addr_n,
+                                                  self.nat_addr_n, is_add=0)
 
 
-        # no address in NAT pool
-        adresses = self.vapi.nat44_address_dump()
-        self.assertEqual(0, len(adresses))
+    @unittest.skip('Temporary disabled')
+    def test_464xlat_ce_no_nat(self):
+        """ Test 464XLAT CE without NAT44 """
 
 
-        # configure interface address and check NAT address pool
-        self.pg7.config_ip4()
-        adresses = self.vapi.nat44_address_dump()
-        self.assertEqual(1, len(adresses))
-        self.assertEqual(adresses[0].ip_address[0:4], self.pg7.local_ip4n)
-        self.assertEqual(adresses[0].twice_nat, 1)
+        self.configure_xlat()
 
 
-        # remove interface address and check NAT address pool
-        self.pg7.unconfig_ip4()
-        adresses = self.vapi.nat44_address_dump()
-        self.assertEqual(0, len(adresses))
+        out_src_ip6 = self.compose_ip6(self.dst_ip4, self.dst_ip6_pfx,
+                                       self.dst_ip6_pfx_len)
+        out_dst_ip6 = self.compose_ip6(self.pg0.remote_ip4, self.src_ip6_pfx,
+                                       self.src_ip6_pfx_len)
 
 
-    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 nat virtual-reassembly"))
-            self.vapi.cli("nat addr-port-assignment-alg default")
-            self.clear_nat44()
+        pkts = self.create_stream_in(self.pg0, self.pg1, self.dst_ip4)
+        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_ip6(capture, dst_ip=out_src_ip6,
+                                    nat_ip=out_dst_ip6, same_port=True)
+
+        pkts = self.create_stream_out_ip6(self.pg1, out_src_ip6, out_dst_ip6)
+        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)
 
 
 class TestDeterministicNAT(MethodHolder):
 
 
 class TestDeterministicNAT(MethodHolder):
@@ -3116,6 +5940,7 @@ class TestDeterministicNAT(MethodHolder):
     @classmethod
     def setUpClass(cls):
         super(TestDeterministicNAT, cls).setUpClass()
     @classmethod
     def setUpClass(cls):
         super(TestDeterministicNAT, cls).setUpClass()
+        cls.vapi.cli("set log class nat level debug")
 
         try:
             cls.tcp_port_in = 6303
 
         try:
             cls.tcp_port_in = 6303
@@ -3200,18 +6025,16 @@ class TestDeterministicNAT(MethodHolder):
 
         return pkts
 
 
         return pkts
 
-    def verify_capture_out(self, capture, nat_ip=None, packet_num=3):
+    def verify_capture_out(self, capture, nat_ip=None):
         """
         Verify captured packets on outside network
 
         :param capture: Captured packets
         :param nat_ip: Translated IP address (Default use global NAT address)
         :param same_port: Sorce port number is not translated (Default False)
         """
         Verify captured packets on outside network
 
         :param capture: Captured packets
         :param nat_ip: Translated IP address (Default use global NAT address)
         :param same_port: Sorce port number is not translated (Default False)
-        :param packet_num: Expected number of packets (Default 3)
         """
         if nat_ip is None:
             nat_ip = self.nat_addr
         """
         if nat_ip is None:
             nat_ip = self.nat_addr
-        self.assertEqual(packet_num, len(capture))
         for packet in capture:
             try:
                 self.assertEqual(packet[IP].src, nat_ip)
         for packet in capture:
             try:
                 self.assertEqual(packet[IP].src, nat_ip)
@@ -3226,65 +6049,6 @@ class TestDeterministicNAT(MethodHolder):
                                       "(outside network):", packet))
                 raise
 
                                       "(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
-
-        :param data: Decoded IPFIX data records
-        """
-        self.assertEqual(1, len(data))
-        record = data[0]
-        # natEvent
-        self.assertEqual(ord(record[230]), 13)
-        # natQuotaExceededEvent
-        self.assertEqual('\x03\x00\x00\x00', record[466])
-        # sourceIPv4Address
-        self.assertEqual(self.pg0.remote_ip4n, record[8])
-
     def test_deterministic_mode(self):
         """ NAT plugin run deterministic mode """
         in_addr = '172.16.255.0'
     def test_deterministic_mode(self):
         """ NAT plugin run deterministic mode """
         in_addr = '172.16.255.0'
@@ -3320,14 +6084,14 @@ class TestDeterministicNAT(MethodHolder):
 
     def test_set_timeouts(self):
         """ Set deterministic NAT timeouts """
 
     def test_set_timeouts(self):
         """ Set deterministic NAT timeouts """
-        timeouts_before = self.vapi.nat_det_get_timeouts()
+        timeouts_before = self.vapi.nat_get_timeouts()
 
 
-        self.vapi.nat_det_set_timeouts(timeouts_before.udp + 10,
-                                       timeouts_before.tcp_established + 10,
-                                       timeouts_before.tcp_transitory + 10,
-                                       timeouts_before.icmp + 10)
+        self.vapi.nat_set_timeouts(timeouts_before.udp + 10,
+                                   timeouts_before.tcp_established + 10,
+                                   timeouts_before.tcp_transitory + 10,
+                                   timeouts_before.icmp + 10)
 
 
-        timeouts_after = self.vapi.nat_det_get_timeouts()
+        timeouts_after = self.vapi.nat_get_timeouts()
 
         self.assertNotEqual(timeouts_before.udp, timeouts_after.udp)
         self.assertNotEqual(timeouts_before.icmp, timeouts_after.icmp)
 
         self.assertNotEqual(timeouts_before.udp, timeouts_after.udp)
         self.assertNotEqual(timeouts_before.icmp, timeouts_after.icmp)
@@ -3642,7 +6406,7 @@ class TestDeterministicNAT(MethodHolder):
                                                   is_inside=0)
 
         self.initiate_tcp_session(self.pg0, self.pg1)
                                                   is_inside=0)
 
         self.initiate_tcp_session(self.pg0, self.pg1)
-        self.vapi.nat_det_set_timeouts(5, 5, 5, 5)
+        self.vapi.nat_set_timeouts(5, 5, 5, 5)
         pkts = self.create_stream_in(self.pg0, self.pg1)
         self.pg0.add_stream(pkts)
         self.pg_enable_capture(self.pg_interfaces)
         pkts = self.create_stream_in(self.pg0, self.pg1)
         self.pg0.add_stream(pkts)
         self.pg_enable_capture(self.pg_interfaces)
@@ -3719,14 +6483,16 @@ class TestDeterministicNAT(MethodHolder):
         for p in capture:
             if p.haslayer(Data):
                 data = ipfix.decode_data_set(p.getlayer(Set))
         for p in capture:
             if p.haslayer(Data):
                 data = ipfix.decode_data_set(p.getlayer(Set))
-                self.verify_ipfix_max_entries_per_user(data)
+                self.verify_ipfix_max_entries_per_user(data,
+                                                       1000,
+                                                       self.pg0.remote_ip4n)
 
     def clear_nat_det(self):
         """
         Clear deterministic NAT configuration.
         """
         self.vapi.nat_ipfix(enable=0)
 
     def clear_nat_det(self):
         """
         Clear deterministic NAT configuration.
         """
         self.vapi.nat_ipfix(enable=0)
-        self.vapi.nat_det_set_timeouts()
+        self.vapi.nat_set_timeouts()
         deterministic_mappings = self.vapi.nat_det_map_dump()
         for dsm in deterministic_mappings:
             self.vapi.nat_det_add_del_map(dsm.in_addr,
         deterministic_mappings = self.vapi.nat_det_map_dump()
         for dsm in deterministic_mappings:
             self.vapi.nat_det_add_del_map(dsm.in_addr,
@@ -3744,13 +6510,24 @@ class TestDeterministicNAT(MethodHolder):
     def tearDown(self):
         super(TestDeterministicNAT, self).tearDown()
         if not self.vpp_dead:
     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 nat timeouts"))
+            self.logger.info(
+                self.vapi.cli("show nat44 deterministic mappings"))
+            self.logger.info(
+                self.vapi.cli("show nat44 deterministic sessions"))
             self.clear_nat_det()
 
 
 class TestNAT64(MethodHolder):
     """ NAT64 Test Cases """
 
             self.clear_nat_det()
 
 
 class TestNAT64(MethodHolder):
     """ NAT64 Test Cases """
 
+    @classmethod
+    def setUpConstants(cls):
+        super(TestNAT64, cls).setUpConstants()
+        cls.vpp_cmdline.extend(["nat", "{", "nat64 bib hash buckets 128",
+                                "nat64 st hash buckets 256", "}"])
+
     @classmethod
     def setUpClass(cls):
         super(TestNAT64, cls).setUpClass()
     @classmethod
     def setUpClass(cls):
         super(TestNAT64, cls).setUpClass()
@@ -3768,8 +6545,10 @@ class TestNAT64(MethodHolder):
             cls.vrf1_nat_addr = '10.0.10.3'
             cls.vrf1_nat_addr_n = socket.inet_pton(socket.AF_INET,
                                                    cls.vrf1_nat_addr)
             cls.vrf1_nat_addr = '10.0.10.3'
             cls.vrf1_nat_addr_n = socket.inet_pton(socket.AF_INET,
                                                    cls.vrf1_nat_addr)
+            cls.ipfix_src_port = 4739
+            cls.ipfix_domain_id = 1
 
 
-            cls.create_pg_interfaces(range(5))
+            cls.create_pg_interfaces(range(6))
             cls.ip6_interfaces = list(cls.pg_interfaces[0:1])
             cls.ip6_interfaces.append(cls.pg_interfaces[2])
             cls.ip4_interfaces = list(cls.pg_interfaces[1:2])
             cls.ip6_interfaces = list(cls.pg_interfaces[0:1])
             cls.ip6_interfaces.append(cls.pg_interfaces[2])
             cls.ip4_interfaces = list(cls.pg_interfaces[1:2])
@@ -3796,10 +6575,65 @@ class TestNAT64(MethodHolder):
             cls.pg3.config_ip6()
             cls.pg3.configure_ipv6_neighbors()
 
             cls.pg3.config_ip6()
             cls.pg3.configure_ipv6_neighbors()
 
+            cls.pg5.admin_up()
+            cls.pg5.config_ip6()
+
         except Exception:
             super(TestNAT64, cls).tearDownClass()
             raise
 
         except Exception:
             super(TestNAT64, cls).tearDownClass()
             raise
 
+    def test_nat64_inside_interface_handles_neighbor_advertisement(self):
+        """ NAT64 inside interface handles Neighbor Advertisement """
+
+        self.vapi.nat64_add_del_interface(self.pg5.sw_if_index)
+
+        # Try to send ping
+        ping = (Ether(dst=self.pg5.local_mac, src=self.pg5.remote_mac) /
+                IPv6(src=self.pg5.remote_ip6, dst=self.pg5.local_ip6) /
+                ICMPv6EchoRequest())
+        pkts = [ping]
+        self.pg5.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+
+        # Wait for Neighbor Solicitation
+        capture = self.pg5.get_capture(len(pkts))
+        packet = capture[0]
+        try:
+            self.assertEqual(packet[IPv6].src, self.pg5.local_ip6)
+            self.assertTrue(packet.haslayer(ICMPv6ND_NS))
+            tgt = packet[ICMPv6ND_NS].tgt
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", packet))
+            raise
+
+        # Send Neighbor Advertisement
+        p = (Ether(dst=self.pg5.local_mac, src=self.pg5.remote_mac) /
+             IPv6(src=self.pg5.remote_ip6, dst=self.pg5.local_ip6) /
+             ICMPv6ND_NA(tgt=tgt) /
+             ICMPv6NDOptDstLLAddr(lladdr=self.pg5.remote_mac))
+        pkts = [p]
+        self.pg5.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+
+        # Try to send ping again
+        pkts = [ping]
+        self.pg5.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+
+        # Wait for ping reply
+        capture = self.pg5.get_capture(len(pkts))
+        packet = capture[0]
+        try:
+            self.assertEqual(packet[IPv6].src, self.pg5.local_ip6)
+            self.assertEqual(packet[IPv6].dst, self.pg5.remote_ip6)
+            self.assertTrue(packet.haslayer(ICMPv6EchoReply))
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", packet))
+            raise
+
     def test_pool(self):
         """ Add/delete address to NAT64 pool """
         nat_addr = socket.inet_pton(socket.AF_INET, '1.2.3.4')
     def test_pool(self):
         """ Add/delete address to NAT64 pool """
         nat_addr = socket.inet_pton(socket.AF_INET, '1.2.3.4')
@@ -3886,22 +6720,20 @@ class TestNAT64(MethodHolder):
     def test_set_timeouts(self):
         """ Set NAT64 timeouts """
         # verify default values
     def test_set_timeouts(self):
         """ Set NAT64 timeouts """
         # verify default values
-        timeouts = self.vapi.nat64_get_timeouts()
+        timeouts = self.vapi.nat_get_timeouts()
         self.assertEqual(timeouts.udp, 300)
         self.assertEqual(timeouts.icmp, 60)
         self.assertEqual(timeouts.udp, 300)
         self.assertEqual(timeouts.icmp, 60)
-        self.assertEqual(timeouts.tcp_trans, 240)
-        self.assertEqual(timeouts.tcp_est, 7440)
-        self.assertEqual(timeouts.tcp_incoming_syn, 6)
+        self.assertEqual(timeouts.tcp_transitory, 240)
+        self.assertEqual(timeouts.tcp_established, 7440)
 
         # set and verify custom values
 
         # set and verify custom values
-        self.vapi.nat64_set_timeouts(udp=200, icmp=30, tcp_trans=250,
-                                     tcp_est=7450, tcp_incoming_syn=10)
-        timeouts = self.vapi.nat64_get_timeouts()
+        self.vapi.nat_set_timeouts(udp=200, icmp=30, tcp_transitory=250,
+                                   tcp_established=7450)
+        timeouts = self.vapi.nat_get_timeouts()
         self.assertEqual(timeouts.udp, 200)
         self.assertEqual(timeouts.icmp, 30)
         self.assertEqual(timeouts.udp, 200)
         self.assertEqual(timeouts.icmp, 30)
-        self.assertEqual(timeouts.tcp_trans, 250)
-        self.assertEqual(timeouts.tcp_est, 7450)
-        self.assertEqual(timeouts.tcp_incoming_syn, 10)
+        self.assertEqual(timeouts.tcp_transitory, 250)
+        self.assertEqual(timeouts.tcp_established, 7450)
 
     def test_dynamic(self):
         """ NAT64 dynamic translation test """
 
     def test_dynamic(self):
         """ NAT64 dynamic translation test """
@@ -4038,7 +6870,7 @@ class TestNAT64(MethodHolder):
                                                 self.nat_addr_n)
         self.vapi.nat64_add_del_interface(self.pg0.sw_if_index)
         self.vapi.nat64_add_del_interface(self.pg1.sw_if_index, is_inside=0)
                                                 self.nat_addr_n)
         self.vapi.nat64_add_del_interface(self.pg0.sw_if_index)
         self.vapi.nat64_add_del_interface(self.pg1.sw_if_index, is_inside=0)
-        self.vapi.nat64_set_timeouts(icmp=5)
+        self.vapi.nat_set_timeouts(icmp=5, tcp_transitory=5, tcp_established=5)
 
         pkts = self.create_stream_in_ip6(self.pg0, self.pg1)
         self.pg0.add_stream(pkts)
 
         pkts = self.create_stream_in_ip6(self.pg0, self.pg1)
         self.pg0.add_stream(pkts)
@@ -4050,9 +6882,9 @@ class TestNAT64(MethodHolder):
 
         sleep(15)
 
 
         sleep(15)
 
-        # ICMP session after timeout
+        # ICMP and TCP session after timeout
         ses_num_after_timeout = self.nat64_get_ses_num()
         ses_num_after_timeout = self.nat64_get_ses_num()
-        self.assertNotEqual(ses_num_before_timeout, ses_num_after_timeout)
+        self.assertEqual(ses_num_before_timeout - ses_num_after_timeout, 2)
 
     def test_icmp_error(self):
         """ NAT64 ICMP Error message translation """
 
     def test_icmp_error(self):
         """ NAT64 ICMP Error message translation """
@@ -4060,8 +6892,6 @@ class TestNAT64(MethodHolder):
         self.udp_port_in = 6304
         self.icmp_id_in = 6305
 
         self.udp_port_in = 6304
         self.icmp_id_in = 6305
 
-        ses_num_start = self.nat64_get_ses_num()
-
         self.vapi.nat64_add_del_pool_addr_range(self.nat_addr_n,
                                                 self.nat_addr_n)
         self.vapi.nat64_add_del_interface(self.pg0.sw_if_index)
         self.vapi.nat64_add_del_pool_addr_range(self.nat_addr_n,
                                                 self.nat_addr_n)
         self.vapi.nat64_add_del_interface(self.pg0.sw_if_index)
@@ -4104,7 +6934,7 @@ class TestNAT64(MethodHolder):
                 inner = packet[IPerror]
                 self.assertEqual(inner.src, self.pg1.remote_ip4)
                 self.assertEqual(inner.dst, self.nat_addr)
                 inner = packet[IPerror]
                 self.assertEqual(inner.src, self.pg1.remote_ip4)
                 self.assertEqual(inner.dst, self.nat_addr)
-                self.check_icmp_checksum(packet)
+                self.assert_packet_checksums_valid(packet)
                 if inner.haslayer(TCPerror):
                     self.assertEqual(inner[TCPerror].dport, self.tcp_port_out)
                 elif inner.haslayer(UDPerror):
                 if inner.haslayer(TCPerror):
                     self.assertEqual(inner[TCPerror].dport, self.tcp_port_out)
                 elif inner.haslayer(UDPerror):
@@ -4133,7 +6963,7 @@ class TestNAT64(MethodHolder):
                 inner = icmp[IPerror6]
                 self.assertEqual(inner.src, self.pg0.remote_ip6)
                 self.assertEqual(inner.dst, ip.src)
                 inner = icmp[IPerror6]
                 self.assertEqual(inner.src, self.pg0.remote_ip6)
                 self.assertEqual(inner.dst, ip.src)
-                self.check_icmpv6_checksum(packet)
+                self.assert_icmpv6_checksum_valid(packet)
                 if inner.haslayer(TCPerror):
                     self.assertEqual(inner[TCPerror].sport, self.tcp_port_in)
                 elif inner.haslayer(UDPerror):
                 if inner.haslayer(TCPerror):
                     self.assertEqual(inner[TCPerror].sport, self.tcp_port_in)
                 elif inner.haslayer(UDPerror):
@@ -4195,15 +7025,14 @@ class TestNAT64(MethodHolder):
             try:
                 self.assertEqual(packet[IPv6].src, nat_addr_ip6)
                 self.assertEqual(packet[IPv6].dst, server.ip6)
             try:
                 self.assertEqual(packet[IPv6].src, nat_addr_ip6)
                 self.assertEqual(packet[IPv6].dst, server.ip6)
+                self.assert_packet_checksums_valid(packet)
                 if packet.haslayer(TCP):
                     self.assertNotEqual(packet[TCP].sport, client_tcp_in_port)
                     self.assertEqual(packet[TCP].dport, server_tcp_in_port)
                 if packet.haslayer(TCP):
                     self.assertNotEqual(packet[TCP].sport, client_tcp_in_port)
                     self.assertEqual(packet[TCP].dport, server_tcp_in_port)
-                    self.check_tcp_checksum(packet)
                     client_tcp_out_port = packet[TCP].sport
                 else:
                     self.assertNotEqual(packet[UDP].sport, client_udp_in_port)
                     self.assertEqual(packet[UDP].dport, server_udp_in_port)
                     client_tcp_out_port = packet[TCP].sport
                 else:
                     self.assertNotEqual(packet[UDP].sport, client_udp_in_port)
                     self.assertEqual(packet[UDP].dport, server_udp_in_port)
-                    self.check_udp_checksum(packet)
                     client_udp_out_port = packet[UDP].sport
             except:
                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
                     client_udp_out_port = packet[UDP].sport
             except:
                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
@@ -4227,14 +7056,13 @@ class TestNAT64(MethodHolder):
             try:
                 self.assertEqual(packet[IPv6].src, nat_addr_ip6)
                 self.assertEqual(packet[IPv6].dst, client.ip6)
             try:
                 self.assertEqual(packet[IPv6].src, nat_addr_ip6)
                 self.assertEqual(packet[IPv6].dst, client.ip6)
+                self.assert_packet_checksums_valid(packet)
                 if packet.haslayer(TCP):
                     self.assertEqual(packet[TCP].sport, server_tcp_out_port)
                     self.assertEqual(packet[TCP].dport, client_tcp_in_port)
                 if packet.haslayer(TCP):
                     self.assertEqual(packet[TCP].sport, server_tcp_out_port)
                     self.assertEqual(packet[TCP].dport, client_tcp_in_port)
-                    self.check_tcp_checksum(packet)
                 else:
                     self.assertEqual(packet[UDP].sport, server_udp_out_port)
                     self.assertEqual(packet[UDP].dport, client_udp_in_port)
                 else:
                     self.assertEqual(packet[UDP].sport, server_udp_out_port)
                     self.assertEqual(packet[UDP].dport, client_udp_in_port)
-                    self.check_udp_checksum(packet)
             except:
                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
                 raise
             except:
                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
                 raise
@@ -4258,7 +7086,7 @@ class TestNAT64(MethodHolder):
                 inner = icmp[IPerror6]
                 self.assertEqual(inner.src, server.ip6)
                 self.assertEqual(inner.dst, nat_addr_ip6)
                 inner = icmp[IPerror6]
                 self.assertEqual(inner.src, server.ip6)
                 self.assertEqual(inner.dst, nat_addr_ip6)
-                self.check_icmpv6_checksum(packet)
+                self.assert_packet_checksums_valid(packet)
                 if inner.haslayer(TCPerror):
                     self.assertEqual(inner[TCPerror].sport, server_tcp_in_port)
                     self.assertEqual(inner[TCPerror].dport,
                 if inner.haslayer(TCPerror):
                     self.assertEqual(inner[TCPerror].sport, server_tcp_in_port)
                     self.assertEqual(inner[TCPerror].dport,
@@ -4381,7 +7209,7 @@ class TestNAT64(MethodHolder):
             self.assertEqual(packet[IP].src, self.nat_addr)
             self.assertEqual(packet[IP].dst, self.pg1.remote_ip4)
             self.assertTrue(packet.haslayer(GRE))
             self.assertEqual(packet[IP].src, self.nat_addr)
             self.assertEqual(packet[IP].dst, self.pg1.remote_ip4)
             self.assertTrue(packet.haslayer(GRE))
-            self.check_ip_checksum(packet)
+            self.assert_packet_checksums_valid(packet)
         except:
             self.logger.error(ppp("Unexpected or invalid packet:", packet))
             raise
         except:
             self.logger.error(ppp("Unexpected or invalid packet:", packet))
             raise
@@ -4519,8 +7347,7 @@ class TestNAT64(MethodHolder):
             self.assertNotEqual(tcp.sport, 12345)
             external_port = tcp.sport
             self.assertEqual(tcp.dport, 80)
             self.assertNotEqual(tcp.sport, 12345)
             external_port = tcp.sport
             self.assertEqual(tcp.dport, 80)
-            self.check_tcp_checksum(p)
-            self.check_ip_checksum(p)
+            self.assert_packet_checksums_valid(p)
         except:
             self.logger.error(ppp("Unexpected or invalid packet:", p))
             raise
         except:
             self.logger.error(ppp("Unexpected or invalid packet:", p))
             raise
@@ -4541,7 +7368,7 @@ class TestNAT64(MethodHolder):
             self.assertEqual(ip.dst, self.pg3.remote_ip6)
             self.assertEqual(tcp.sport, 80)
             self.assertEqual(tcp.dport, 12345)
             self.assertEqual(ip.dst, self.pg3.remote_ip6)
             self.assertEqual(tcp.sport, 80)
             self.assertEqual(tcp.dport, 12345)
-            self.check_tcp_checksum(p)
+            self.assert_packet_checksums_valid(p)
         except:
             self.logger.error(ppp("Unexpected or invalid packet:", p))
             raise
         except:
             self.logger.error(ppp("Unexpected or invalid packet:", p))
             raise
@@ -4599,7 +7426,6 @@ class TestNAT64(MethodHolder):
     def test_reass_hairpinning(self):
         """ NAT64 fragments hairpinning """
         data = 'a' * 200
     def test_reass_hairpinning(self):
         """ NAT64 fragments hairpinning """
         data = 'a' * 200
-        client = self.pg0.remote_hosts[0]
         server = self.pg0.remote_hosts[1]
         server_in_port = random.randint(1025, 65535)
         server_out_port = random.randint(1025, 65535)
         server = self.pg0.remote_hosts[1]
         server_in_port = random.randint(1025, 65535)
         server_out_port = random.randint(1025, 65535)
@@ -4678,24 +7504,242 @@ class TestNAT64(MethodHolder):
         self.assertEqual(p[TCP].dport, self.tcp_port_in)
         self.assertEqual(data, p[Raw].load)
 
         self.assertEqual(p[TCP].dport, self.tcp_port_in)
         self.assertEqual(data, p[Raw].load)
 
-    def test_interface_addr(self):
-        """ Acquire NAT64 pool addresses from interface """
-        self.vapi.nat64_add_interface_addr(self.pg4.sw_if_index)
+    def test_interface_addr(self):
+        """ Acquire NAT64 pool addresses from interface """
+        self.vapi.nat64_add_interface_addr(self.pg4.sw_if_index)
+
+        # no address in NAT64 pool
+        adresses = self.vapi.nat44_address_dump()
+        self.assertEqual(0, len(adresses))
+
+        # configure interface address and check NAT64 address pool
+        self.pg4.config_ip4()
+        addresses = self.vapi.nat64_pool_addr_dump()
+        self.assertEqual(len(addresses), 1)
+        self.assertEqual(addresses[0].address, self.pg4.local_ip4n)
+
+        # remove interface address and check NAT64 address pool
+        self.pg4.unconfig_ip4()
+        addresses = self.vapi.nat64_pool_addr_dump()
+        self.assertEqual(0, len(adresses))
+
+    @unittest.skipUnless(running_extended_tests(), "part of extended tests")
+    def test_ipfix_max_bibs_sessions(self):
+        """ IPFIX logging maximum session and BIB entries exceeded """
+        max_bibs = 1280
+        max_sessions = 2560
+        remote_host_ip6 = self.compose_ip6(self.pg1.remote_ip4,
+                                           '64:ff9b::',
+                                           96)
+
+        self.vapi.nat64_add_del_pool_addr_range(self.nat_addr_n,
+                                                self.nat_addr_n)
+        self.vapi.nat64_add_del_interface(self.pg0.sw_if_index)
+        self.vapi.nat64_add_del_interface(self.pg1.sw_if_index, is_inside=0)
+
+        pkts = []
+        src = ""
+        for i in range(0, max_bibs):
+            src = "fd01:aa::%x" % (i)
+            p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+                 IPv6(src=src, dst=remote_host_ip6) /
+                 TCP(sport=12345, dport=80))
+            pkts.append(p)
+            p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+                 IPv6(src=src, dst=remote_host_ip6) /
+                 TCP(sport=12345, dport=22))
+            pkts.append(p)
+        self.pg0.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        self.pg1.get_capture(max_sessions)
+
+        self.vapi.set_ipfix_exporter(collector_address=self.pg3.remote_ip4n,
+                                     src_address=self.pg3.local_ip4n,
+                                     path_mtu=512,
+                                     template_interval=10)
+        self.vapi.nat_ipfix(domain_id=self.ipfix_domain_id,
+                            src_port=self.ipfix_src_port)
+
+        p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+             IPv6(src=src, dst=remote_host_ip6) /
+             TCP(sport=12345, dport=25))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        self.pg1.assert_nothing_captured()
+        sleep(1)
+        self.vapi.cli("ipfix flush")  # FIXME this should be an API call
+        capture = self.pg3.get_capture(9)
+        ipfix = IPFIXDecoder()
+        # first load template
+        for p in capture:
+            self.assertTrue(p.haslayer(IPFIX))
+            self.assertEqual(p[IP].src, self.pg3.local_ip4)
+            self.assertEqual(p[IP].dst, self.pg3.remote_ip4)
+            self.assertEqual(p[UDP].sport, self.ipfix_src_port)
+            self.assertEqual(p[UDP].dport, 4739)
+            self.assertEqual(p[IPFIX].observationDomainID,
+                             self.ipfix_domain_id)
+            if p.haslayer(Template):
+                ipfix.add_template(p.getlayer(Template))
+        # verify events in data set
+        for p in capture:
+            if p.haslayer(Data):
+                data = ipfix.decode_data_set(p.getlayer(Set))
+                self.verify_ipfix_max_sessions(data, max_sessions)
+
+        p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+             IPv6(src=self.pg0.remote_ip6, dst=remote_host_ip6) /
+             TCP(sport=12345, dport=80))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        self.pg1.assert_nothing_captured()
+        sleep(1)
+        self.vapi.cli("ipfix flush")  # FIXME this should be an API call
+        capture = self.pg3.get_capture(1)
+        # verify events in data set
+        for p in capture:
+            self.assertTrue(p.haslayer(IPFIX))
+            self.assertEqual(p[IP].src, self.pg3.local_ip4)
+            self.assertEqual(p[IP].dst, self.pg3.remote_ip4)
+            self.assertEqual(p[UDP].sport, self.ipfix_src_port)
+            self.assertEqual(p[UDP].dport, 4739)
+            self.assertEqual(p[IPFIX].observationDomainID,
+                             self.ipfix_domain_id)
+            if p.haslayer(Data):
+                data = ipfix.decode_data_set(p.getlayer(Set))
+                self.verify_ipfix_max_bibs(data, max_bibs)
+
+    def test_ipfix_max_frags(self):
+        """ IPFIX logging maximum fragments pending reassembly exceeded """
+        self.vapi.nat64_add_del_pool_addr_range(self.nat_addr_n,
+                                                self.nat_addr_n)
+        self.vapi.nat64_add_del_interface(self.pg0.sw_if_index)
+        self.vapi.nat64_add_del_interface(self.pg1.sw_if_index, is_inside=0)
+        self.vapi.nat_set_reass(max_frag=1, is_ip6=1)
+        self.vapi.set_ipfix_exporter(collector_address=self.pg3.remote_ip4n,
+                                     src_address=self.pg3.local_ip4n,
+                                     path_mtu=512,
+                                     template_interval=10)
+        self.vapi.nat_ipfix(domain_id=self.ipfix_domain_id,
+                            src_port=self.ipfix_src_port)
+
+        data = 'a' * 200
+        pkts = self.create_stream_frag_ip6(self.pg0, self.pg1.remote_ip4,
+                                           self.tcp_port_in, 20, data)
+        pkts.reverse()
+        self.pg0.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        self.pg1.assert_nothing_captured()
+        sleep(1)
+        self.vapi.cli("ipfix flush")  # FIXME this should be an API call
+        capture = self.pg3.get_capture(9)
+        ipfix = IPFIXDecoder()
+        # first load template
+        for p in capture:
+            self.assertTrue(p.haslayer(IPFIX))
+            self.assertEqual(p[IP].src, self.pg3.local_ip4)
+            self.assertEqual(p[IP].dst, self.pg3.remote_ip4)
+            self.assertEqual(p[UDP].sport, self.ipfix_src_port)
+            self.assertEqual(p[UDP].dport, 4739)
+            self.assertEqual(p[IPFIX].observationDomainID,
+                             self.ipfix_domain_id)
+            if p.haslayer(Template):
+                ipfix.add_template(p.getlayer(Template))
+        # verify events in data set
+        for p in capture:
+            if p.haslayer(Data):
+                data = ipfix.decode_data_set(p.getlayer(Set))
+                self.verify_ipfix_max_fragments_ip6(data, 1,
+                                                    self.pg0.remote_ip6n)
+
+    def test_ipfix_bib_ses(self):
+        """ IPFIX logging NAT64 BIB/session create and delete events """
+        self.tcp_port_in = random.randint(1025, 65535)
+        remote_host_ip6 = self.compose_ip6(self.pg1.remote_ip4,
+                                           '64:ff9b::',
+                                           96)
 
 
-        # no address in NAT64 pool
-        adresses = self.vapi.nat44_address_dump()
-        self.assertEqual(0, len(adresses))
+        self.vapi.nat64_add_del_pool_addr_range(self.nat_addr_n,
+                                                self.nat_addr_n)
+        self.vapi.nat64_add_del_interface(self.pg0.sw_if_index)
+        self.vapi.nat64_add_del_interface(self.pg1.sw_if_index, is_inside=0)
+        self.vapi.set_ipfix_exporter(collector_address=self.pg3.remote_ip4n,
+                                     src_address=self.pg3.local_ip4n,
+                                     path_mtu=512,
+                                     template_interval=10)
+        self.vapi.nat_ipfix(domain_id=self.ipfix_domain_id,
+                            src_port=self.ipfix_src_port)
 
 
-        # configure interface address and check NAT64 address pool
-        self.pg4.config_ip4()
-        addresses = self.vapi.nat64_pool_addr_dump()
-        self.assertEqual(len(addresses), 1)
-        self.assertEqual(addresses[0].address, self.pg4.local_ip4n)
+        # Create
+        p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+             IPv6(src=self.pg0.remote_ip6, dst=remote_host_ip6) /
+             TCP(sport=self.tcp_port_in, dport=25))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        p = self.pg1.get_capture(1)
+        self.tcp_port_out = p[0][TCP].sport
+        self.vapi.cli("ipfix flush")  # FIXME this should be an API call
+        capture = self.pg3.get_capture(10)
+        ipfix = IPFIXDecoder()
+        # first load template
+        for p in capture:
+            self.assertTrue(p.haslayer(IPFIX))
+            self.assertEqual(p[IP].src, self.pg3.local_ip4)
+            self.assertEqual(p[IP].dst, self.pg3.remote_ip4)
+            self.assertEqual(p[UDP].sport, self.ipfix_src_port)
+            self.assertEqual(p[UDP].dport, 4739)
+            self.assertEqual(p[IPFIX].observationDomainID,
+                             self.ipfix_domain_id)
+            if p.haslayer(Template):
+                ipfix.add_template(p.getlayer(Template))
+        # verify events in data set
+        for p in capture:
+            if p.haslayer(Data):
+                data = ipfix.decode_data_set(p.getlayer(Set))
+                if ord(data[0][230]) == 10:
+                    self.verify_ipfix_bib(data, 1, self.pg0.remote_ip6n)
+                elif ord(data[0][230]) == 6:
+                    self.verify_ipfix_nat64_ses(data,
+                                                1,
+                                                self.pg0.remote_ip6n,
+                                                self.pg1.remote_ip4,
+                                                25)
+                else:
+                    self.logger.error(ppp("Unexpected or invalid packet: ", p))
 
 
-        # remove interface address and check NAT64 address pool
-        self.pg4.unconfig_ip4()
-        addresses = self.vapi.nat64_pool_addr_dump()
-        self.assertEqual(0, len(adresses))
+        # Delete
+        self.pg_enable_capture(self.pg_interfaces)
+        self.vapi.nat64_add_del_pool_addr_range(self.nat_addr_n,
+                                                self.nat_addr_n,
+                                                is_add=0)
+        self.vapi.cli("ipfix flush")  # FIXME this should be an API call
+        capture = self.pg3.get_capture(2)
+        # verify events in data set
+        for p in capture:
+            self.assertTrue(p.haslayer(IPFIX))
+            self.assertEqual(p[IP].src, self.pg3.local_ip4)
+            self.assertEqual(p[IP].dst, self.pg3.remote_ip4)
+            self.assertEqual(p[UDP].sport, self.ipfix_src_port)
+            self.assertEqual(p[UDP].dport, 4739)
+            self.assertEqual(p[IPFIX].observationDomainID,
+                             self.ipfix_domain_id)
+            if p.haslayer(Data):
+                data = ipfix.decode_data_set(p.getlayer(Set))
+                if ord(data[0][230]) == 11:
+                    self.verify_ipfix_bib(data, 0, self.pg0.remote_ip6n)
+                elif ord(data[0][230]) == 7:
+                    self.verify_ipfix_nat64_ses(data,
+                                                0,
+                                                self.pg0.remote_ip6n,
+                                                self.pg1.remote_ip4,
+                                                25)
+                else:
+                    self.logger.error(ppp("Unexpected or invalid packet: ", p))
 
     def nat64_get_ses_num(self):
         """
 
     def nat64_get_ses_num(self):
         """
@@ -4708,7 +7752,12 @@ class TestNAT64(MethodHolder):
         """
         Clear NAT64 configuration.
         """
         """
         Clear NAT64 configuration.
         """
-        self.vapi.nat64_set_timeouts()
+        self.vapi.nat_ipfix(enable=0, src_port=self.ipfix_src_port,
+                            domain_id=self.ipfix_domain_id)
+        self.ipfix_src_port = 4739
+        self.ipfix_domain_id = 1
+
+        self.vapi.nat_set_timeouts()
 
         interfaces = self.vapi.nat64_interface_dump()
         for intf in interfaces:
 
         interfaces = self.vapi.nat64_interface_dump()
         for intf in interfaces:
@@ -4720,29 +7769,7 @@ class TestNAT64(MethodHolder):
                                               intf.is_inside,
                                               is_add=0)
 
                                               intf.is_inside,
                                               is_add=0)
 
-        bib = self.vapi.nat64_bib_dump(IP_PROTOS.tcp)
-        for bibe in bib:
-            if bibe.is_static:
-                self.vapi.nat64_add_del_static_bib(bibe.i_addr,
-                                                   bibe.o_addr,
-                                                   bibe.i_port,
-                                                   bibe.o_port,
-                                                   bibe.proto,
-                                                   bibe.vrf_id,
-                                                   is_add=0)
-
-        bib = self.vapi.nat64_bib_dump(IP_PROTOS.udp)
-        for bibe in bib:
-            if bibe.is_static:
-                self.vapi.nat64_add_del_static_bib(bibe.i_addr,
-                                                   bibe.o_addr,
-                                                   bibe.i_port,
-                                                   bibe.o_port,
-                                                   bibe.proto,
-                                                   bibe.vrf_id,
-                                                   is_add=0)
-
-        bib = self.vapi.nat64_bib_dump(IP_PROTOS.icmp)
+        bib = self.vapi.nat64_bib_dump(255)
         for bibe in bib:
             if bibe.is_static:
                 self.vapi.nat64_add_del_static_bib(bibe.i_addr,
         for bibe in bib:
             if bibe.is_static:
                 self.vapi.nat64_add_del_static_bib(bibe.i_addr,
@@ -4805,6 +7832,9 @@ class TestDSlite(MethodHolder):
 
     def test_dslite(self):
         """ Test DS-Lite """
 
     def test_dslite(self):
         """ Test DS-Lite """
+        nat_config = self.vapi.nat_show_config()
+        self.assertEqual(0, nat_config.dslite_ce)
+
         self.vapi.dslite_add_del_pool_addr_range(self.nat_addr_n,
                                                  self.nat_addr_n)
         aftr_ip4 = '192.0.0.1'
         self.vapi.dslite_add_del_pool_addr_range(self.nat_addr_n,
                                                  self.nat_addr_n)
         aftr_ip4 = '192.0.0.1'
@@ -4828,7 +7858,7 @@ class TestDSlite(MethodHolder):
         self.assertEqual(capture[IP].dst, self.pg0.remote_ip4)
         self.assertNotEqual(capture[UDP].sport, 20000)
         self.assertEqual(capture[UDP].dport, 10000)
         self.assertEqual(capture[IP].dst, self.pg0.remote_ip4)
         self.assertNotEqual(capture[UDP].sport, 20000)
         self.assertEqual(capture[UDP].dport, 10000)
-        self.check_ip_checksum(capture)
+        self.assert_packet_checksums_valid(capture)
         out_port = capture[UDP].sport
 
         p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
         out_port = capture[UDP].sport
 
         p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
@@ -4845,7 +7875,7 @@ class TestDSlite(MethodHolder):
         self.assertEqual(capture[IP].dst, '192.168.1.1')
         self.assertEqual(capture[UDP].sport, 10000)
         self.assertEqual(capture[UDP].dport, 20000)
         self.assertEqual(capture[IP].dst, '192.168.1.1')
         self.assertEqual(capture[UDP].sport, 10000)
         self.assertEqual(capture[UDP].dport, 20000)
-        self.check_ip_checksum(capture)
+        self.assert_packet_checksums_valid(capture)
 
         # TCP
         p = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
 
         # TCP
         p = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
@@ -4862,8 +7892,7 @@ class TestDSlite(MethodHolder):
         self.assertEqual(capture[IP].dst, self.pg0.remote_ip4)
         self.assertNotEqual(capture[TCP].sport, 20001)
         self.assertEqual(capture[TCP].dport, 10001)
         self.assertEqual(capture[IP].dst, self.pg0.remote_ip4)
         self.assertNotEqual(capture[TCP].sport, 20001)
         self.assertEqual(capture[TCP].dport, 10001)
-        self.check_ip_checksum(capture)
-        self.check_tcp_checksum(capture)
+        self.assert_packet_checksums_valid(capture)
         out_port = capture[TCP].sport
 
         p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
         out_port = capture[TCP].sport
 
         p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
@@ -4880,8 +7909,7 @@ class TestDSlite(MethodHolder):
         self.assertEqual(capture[IP].dst, '192.168.1.1')
         self.assertEqual(capture[TCP].sport, 10001)
         self.assertEqual(capture[TCP].dport, 20001)
         self.assertEqual(capture[IP].dst, '192.168.1.1')
         self.assertEqual(capture[TCP].sport, 10001)
         self.assertEqual(capture[TCP].dport, 20001)
-        self.check_ip_checksum(capture)
-        self.check_tcp_checksum(capture)
+        self.assert_packet_checksums_valid(capture)
 
         # ICMP
         p = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
 
         # ICMP
         p = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
@@ -4897,8 +7925,7 @@ class TestDSlite(MethodHolder):
         self.assertEqual(capture[IP].src, self.nat_addr)
         self.assertEqual(capture[IP].dst, self.pg0.remote_ip4)
         self.assertNotEqual(capture[ICMP].id, 4000)
         self.assertEqual(capture[IP].src, self.nat_addr)
         self.assertEqual(capture[IP].dst, self.pg0.remote_ip4)
         self.assertNotEqual(capture[ICMP].id, 4000)
-        self.check_ip_checksum(capture)
-        self.check_icmp_checksum(capture)
+        self.assert_packet_checksums_valid(capture)
         out_id = capture[ICMP].id
 
         p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
         out_id = capture[ICMP].id
 
         p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
@@ -4914,8 +7941,7 @@ class TestDSlite(MethodHolder):
         self.assertEqual(capture[IP].src, self.pg0.remote_ip4)
         self.assertEqual(capture[IP].dst, '192.168.1.1')
         self.assertEqual(capture[ICMP].id, 4000)
         self.assertEqual(capture[IP].src, self.pg0.remote_ip4)
         self.assertEqual(capture[IP].dst, '192.168.1.1')
         self.assertEqual(capture[ICMP].id, 4000)
-        self.check_ip_checksum(capture)
-        self.check_icmp_checksum(capture)
+        self.assert_packet_checksums_valid(capture)
 
         # ping DS-Lite AFTR tunnel endpoint address
         p = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
 
         # ping DS-Lite AFTR tunnel endpoint address
         p = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
@@ -4925,7 +7951,6 @@ class TestDSlite(MethodHolder):
         self.pg_enable_capture(self.pg_interfaces)
         self.pg_start()
         capture = self.pg1.get_capture(1)
         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, aftr_ip6)
         self.assertEqual(capture[IPv6].dst, self.pg1.remote_hosts[1].ip6)
         capture = capture[0]
         self.assertEqual(capture[IPv6].src, aftr_ip6)
         self.assertEqual(capture[IPv6].dst, self.pg1.remote_hosts[1].ip6)
@@ -4939,5 +7964,256 @@ class TestDSlite(MethodHolder):
                 self.vapi.cli("show dslite aftr-tunnel-endpoint-address"))
             self.logger.info(self.vapi.cli("show dslite sessions"))
 
                 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 """
+
+        nat_config = self.vapi.nat_show_config()
+        self.assertEqual(1, nat_config.dslite_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.assert_packet_checksums_valid(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.assert_packet_checksums_valid(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)
+        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)
+                self.assert_packet_checksums_valid(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)
+                self.assert_packet_checksums_valid(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)
 if __name__ == '__main__':
     unittest.main(testRunner=VppTestRunner)