NAT44: asymmetrical static mapping rule (VPP-1135)
[vpp.git] / test / test_nat.py
index 4ced0af..d2bc456 100644 (file)
@@ -749,6 +749,148 @@ class MethodHolder(VppTestCase):
         # natPoolID
         self.assertEqual(struct.pack("!I", 0), record[283])
 
+    def verify_ipfix_max_sessions(self, data, limit):
+        """
+        Verify IPFIX maximum session entries exceeded event
+
+        :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])
+
+    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])
+
 
 class TestNAT44(MethodHolder):
     """ NAT44 Test Cases """
@@ -847,6 +989,8 @@ class TestNAT44(MethodHolder):
         if self.pg7.has_ip4_config:
             self.pg7.unconfig_ip4()
 
+        self.vapi.nat44_forwarding_enable_disable(0)
+
         interfaces = self.vapi.nat44_interface_addr_dump()
         for intf in interfaces:
             self.vapi.nat44_add_interface_addr(intf.sw_if_index,
@@ -885,6 +1029,7 @@ class TestNAT44(MethodHolder):
                 vrf_id=sm.vrf_id,
                 protocol=sm.protocol,
                 twice_nat=sm.twice_nat,
+                out2in_only=sm.out2in_only,
                 is_add=0)
 
         lb_static_mappings = self.vapi.nat44_lb_static_mapping_dump()
@@ -895,6 +1040,7 @@ class TestNAT44(MethodHolder):
                 lb_sm.protocol,
                 vrf_id=lb_sm.vrf_id,
                 twice_nat=lb_sm.twice_nat,
+                out2in_only=lb_sm.out2in_only,
                 is_add=0,
                 local_num=0,
                 locals=[])
@@ -923,7 +1069,7 @@ class TestNAT44(MethodHolder):
     def nat44_add_static_mapping(self, local_ip, external_ip='0.0.0.0',
                                  local_port=0, external_port=0, vrf_id=0,
                                  is_add=1, external_sw_if_index=0xFFFFFFFF,
-                                 proto=0, twice_nat=0):
+                                 proto=0, twice_nat=0, out2in_only=0):
         """
         Add/delete NAT44 static mapping
 
@@ -936,6 +1082,7 @@ class TestNAT44(MethodHolder):
         :param external_sw_if_index: External interface instead of IP address
         :param proto: IP protocol (Mandatory if port specified)
         :param twice_nat: 1 if translate external host address and port
+        :param out2in_only: if 1 rule is matching only out2in direction
         """
         addr_only = 1
         if local_port and external_port:
@@ -952,6 +1099,7 @@ class TestNAT44(MethodHolder):
             vrf_id,
             proto,
             twice_nat,
+            out2in_only,
             is_add)
 
     def nat44_add_address(self, ip, is_add=1, vrf_id=0xFFFFFFFF, twice_nat=0):
@@ -1348,6 +1496,102 @@ class TestNAT44(MethodHolder):
         capture = self.pg1.get_capture(len(pkts))
         self.verify_capture_out(capture)
 
+    def test_static_with_port_out2(self):
+        """ 1:1 NAPT symmetrical rule """
+
+        external_port = 80
+        local_port = 8080
+
+        self.vapi.nat44_forwarding_enable_disable(1)
+        self.nat44_add_static_mapping(self.pg0.remote_ip4, self.nat_addr,
+                                      local_port, external_port,
+                                      proto=IP_PROTOS.tcp, out2in_only=1)
+        self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+        self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+                                                  is_inside=0)
+
+        # from client to service
+        p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
+             IP(src=self.pg1.remote_ip4, dst=self.nat_addr) /
+             TCP(sport=12345, dport=external_port))
+        self.pg1.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg0.get_capture(1)
+        p = capture[0]
+        server = None
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.dst, self.pg0.remote_ip4)
+            self.assertEqual(tcp.dport, local_port)
+            self.check_tcp_checksum(p)
+            self.check_ip_checksum(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        # from service back to client
+        p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+             IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+             TCP(sport=local_port, dport=12345))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(1)
+        p = capture[0]
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.src, self.nat_addr)
+            self.assertEqual(tcp.sport, external_port)
+            self.check_tcp_checksum(p)
+            self.check_ip_checksum(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        # from client to server (no translation)
+        p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
+             IP(src=self.pg1.remote_ip4, dst=self.pg0.remote_ip4) /
+             TCP(sport=12346, dport=local_port))
+        self.pg1.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg0.get_capture(1)
+        p = capture[0]
+        server = None
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.dst, self.pg0.remote_ip4)
+            self.assertEqual(tcp.dport, local_port)
+            self.check_tcp_checksum(p)
+            self.check_ip_checksum(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        # from service back to client (no translation)
+        p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+             IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+             TCP(sport=local_port, dport=12346))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(1)
+        p = capture[0]
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.src, self.pg0.remote_ip4)
+            self.assertEqual(tcp.sport, local_port)
+            self.check_tcp_checksum(p)
+            self.check_ip_checksum(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
     def test_static_vrf_aware(self):
         """ 1:1 NAT VRF awareness """
 
@@ -1502,6 +1746,118 @@ class TestNAT44(MethodHolder):
                 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},
+                  {'addr': server2.ip4n,
+                   'port': local_port,
+                   'probability': 30}]
+
+        self.vapi.nat44_forwarding_enable_disable(1)
+        self.vapi.nat44_add_del_lb_static_mapping(external_addr_n,
+                                                  external_port,
+                                                  IP_PROTOS.tcp,
+                                                  out2in_only=1,
+                                                  local_num=len(locals),
+                                                  locals=locals)
+        self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+        self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+                                                  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.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]
+            self.assertEqual(ip.src, self.nat_addr)
+            self.assertEqual(tcp.sport, external_port)
+            self.check_tcp_checksum(p)
+            self.check_ip_checksum(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        # 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.check_tcp_checksum(p)
+            self.check_ip_checksum(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
+        # from service back to client (no translation)
+        p = (Ether(src=server1.mac, dst=self.pg0.local_mac) /
+             IP(src=server1.ip4, dst=self.pg1.remote_ip4) /
+             TCP(sport=local_port, dport=12346))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(1)
+        p = capture[0]
+        try:
+            ip = p[IP]
+            tcp = p[TCP]
+            self.assertEqual(ip.src, server1.ip4)
+            self.assertEqual(tcp.sport, local_port)
+            self.check_tcp_checksum(p)
+            self.check_ip_checksum(p)
+        except:
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
+            raise
+
     def test_multiple_inside_interfaces(self):
         """ NAT44 multiple non-overlapping address space inside interfaces """
 
@@ -1766,7 +2122,7 @@ class TestNAT44(MethodHolder):
             self.assertEqual(tcp.dport, host_in_port)
             self.check_tcp_checksum(p)
         except:
-            self.logger.error(ppp("Unexpected or invalid packet:"), p)
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
             raise
 
     def test_hairpinning2(self):
@@ -2064,7 +2420,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
-        capture = self.pg3.get_capture(3)
+        capture = self.pg3.get_capture(9)
         ipfix = IPFIXDecoder()
         # first load template
         for p in capture:
@@ -2103,7 +2459,7 @@ class TestNAT44(MethodHolder):
         self.pg_start()
         capture = self.pg1.get_capture(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:
@@ -2122,6 +2478,63 @@ class TestNAT44(MethodHolder):
                 data = ipfix.decode_data_set(p.getlayer(Set))
                 self.verify_ipfix_addr_exhausted(data)
 
+    @unittest.skipUnless(running_extended_tests(), "part of extended tests")
+    def test_ipfix_max_sessions(self):
+        """ IPFIX logging maximum session entries exceeded """
+        self.nat44_add_address(self.nat_addr)
+        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.get_capture(0)
+        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'
@@ -2768,7 +3181,7 @@ class TestNAT44(MethodHolder):
             self.assertEqual(tcp.dport, host_in_port)
             self.check_tcp_checksum(p)
         except:
-            self.logger.error(ppp("Unexpected or invalid packet:"), p)
+            self.logger.error(ppp("Unexpected or invalid packet:", p))
             raise
 
     def test_one_armed_nat44(self):
@@ -3196,6 +3609,52 @@ class TestNAT44(MethodHolder):
         adresses = self.vapi.nat44_address_dump()
         self.assertEqual(0, len(adresses))
 
+    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=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)
+
+        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)
+        self.pg0.add_stream(pkts[-1])
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        frags = self.pg1.get_capture(0)
+        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, 0,
+                                                    self.pg0.remote_ip4n)
+
     def tearDown(self):
         super(TestNAT44, self).tearDown()
         if not self.vpp_dead:
@@ -3499,6 +3958,8 @@ class TestDeterministicNAT(MethodHolder):
         self.assertEqual(ord(record[230]), 13)
         # natQuotaExceededEvent
         self.assertEqual('\x03\x00\x00\x00', record[466])
+        # maxEntriesPerUser
+        self.assertEqual('\xe8\x03\x00\x00', record[473])
         # sourceIPv4Address
         self.assertEqual(self.pg0.remote_ip4n, record[8])
 
@@ -3968,6 +4429,12 @@ class TestDeterministicNAT(MethodHolder):
 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()
@@ -3985,6 +4452,8 @@ 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.ipfix_src_port = 4739
+            cls.ipfix_domain_id = 1
 
             cls.create_pg_interfaces(range(5))
             cls.ip6_interfaces = list(cls.pg_interfaces[0:1])
@@ -4914,6 +5383,220 @@ class TestNAT64(MethodHolder):
         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.get_capture(0)
+        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.get_capture(0)
+        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=0, 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)
+        self.pg0.add_stream(pkts[-1])
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        self.pg1.get_capture(0)
+        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, 0,
+                                                    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)
+
+        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)
+
+        # 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))
+
+        # 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):
         """
         Return number of active NAT64 sessions.
@@ -4925,6 +5608,11 @@ class TestNAT64(MethodHolder):
         """
         Clear NAT64 configuration.
         """
+        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.nat64_set_timeouts()
 
         interfaces = self.vapi.nat64_interface_dump()
@@ -4937,29 +5625,7 @@ class TestNAT64(MethodHolder):
                                               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,
@@ -5156,5 +5822,109 @@ class TestDSlite(MethodHolder):
                 self.vapi.cli("show dslite aftr-tunnel-endpoint-address"))
             self.logger.info(self.vapi.cli("show dslite sessions"))
 
+
+class TestDSliteCE(MethodHolder):
+    """ DS-Lite CE Test Cases """
+
+    @classmethod
+    def setUpConstants(cls):
+        super(TestDSliteCE, cls).setUpConstants()
+        cls.vpp_cmdline.extend(["nat", "{", "dslite ce", "}"])
+
+    @classmethod
+    def setUpClass(cls):
+        super(TestDSliteCE, cls).setUpClass()
+
+        try:
+            cls.create_pg_interfaces(range(2))
+            cls.pg0.admin_up()
+            cls.pg0.config_ip4()
+            cls.pg0.resolve_arp()
+            cls.pg1.admin_up()
+            cls.pg1.config_ip6()
+            cls.pg1.generate_remote_hosts(1)
+            cls.pg1.configure_ipv6_neighbors()
+
+        except Exception:
+            super(TestDSliteCE, cls).tearDownClass()
+            raise
+
+    def test_dslite_ce(self):
+        """ Test DS-Lite CE """
+
+        b4_ip4 = '192.0.0.2'
+        b4_ip4_n = socket.inet_pton(socket.AF_INET, b4_ip4)
+        b4_ip6 = '2001:db8:62aa::375e:f4c1:1'
+        b4_ip6_n = socket.inet_pton(socket.AF_INET6, b4_ip6)
+        self.vapi.dslite_set_b4_addr(b4_ip6_n, b4_ip4_n)
+
+        aftr_ip4 = '192.0.0.1'
+        aftr_ip4_n = socket.inet_pton(socket.AF_INET, aftr_ip4)
+        aftr_ip6 = '2001:db8:85a3::8a2e:370:1'
+        aftr_ip6_n = socket.inet_pton(socket.AF_INET6, aftr_ip6)
+        self.vapi.dslite_set_aftr_addr(aftr_ip6_n, aftr_ip4_n)
+
+        self.vapi.ip_add_del_route(dst_address=aftr_ip6_n,
+                                   dst_address_length=128,
+                                   next_hop_address=self.pg1.remote_ip6n,
+                                   next_hop_sw_if_index=self.pg1.sw_if_index,
+                                   is_ipv6=1)
+
+        # UDP encapsulation
+        p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+             IP(dst=self.pg1.remote_ip4, src=self.pg0.remote_ip4) /
+             UDP(sport=10000, dport=20000))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(1)
+        capture = capture[0]
+        self.assertEqual(capture[IPv6].src, b4_ip6)
+        self.assertEqual(capture[IPv6].dst, aftr_ip6)
+        self.assertEqual(capture[IP].src, self.pg0.remote_ip4)
+        self.assertEqual(capture[IP].dst, self.pg1.remote_ip4)
+        self.assertEqual(capture[UDP].sport, 10000)
+        self.assertEqual(capture[UDP].dport, 20000)
+        self.check_ip_checksum(capture)
+
+        # UDP decapsulation
+        p = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
+             IPv6(dst=b4_ip6, src=aftr_ip6) /
+             IP(dst=self.pg0.remote_ip4, src=self.pg1.remote_ip4) /
+             UDP(sport=20000, dport=10000))
+        self.pg1.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg0.get_capture(1)
+        capture = capture[0]
+        self.assertFalse(capture.haslayer(IPv6))
+        self.assertEqual(capture[IP].src, self.pg1.remote_ip4)
+        self.assertEqual(capture[IP].dst, self.pg0.remote_ip4)
+        self.assertEqual(capture[UDP].sport, 20000)
+        self.assertEqual(capture[UDP].dport, 10000)
+        self.check_ip_checksum(capture)
+
+        # ping DS-Lite B4 tunnel endpoint address
+        p = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
+             IPv6(src=self.pg1.remote_hosts[0].ip6, dst=b4_ip6) /
+             ICMPv6EchoRequest())
+        self.pg1.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(1)
+        self.assertEqual(1, len(capture))
+        capture = capture[0]
+        self.assertEqual(capture[IPv6].src, b4_ip6)
+        self.assertEqual(capture[IPv6].dst, self.pg1.remote_hosts[0].ip6)
+        self.assertTrue(capture.haslayer(ICMPv6EchoReply))
+
+    def tearDown(self):
+        super(TestDSliteCE, self).tearDown()
+        if not self.vpp_dead:
+            self.logger.info(
+                self.vapi.cli("show dslite aftr-tunnel-endpoint-address"))
+            self.logger.info(
+                self.vapi.cli("show dslite b4-tunnel-endpoint-address"))
+
 if __name__ == '__main__':
     unittest.main(testRunner=VppTestRunner)