CSIT-1471: Policer - VAT API to PAPI
[csit.git] / resources / libraries / python / Classify.py
index 6f05a61..6d4b84c 100644 (file)
@@ -20,6 +20,7 @@ from ipaddress import ip_address
 
 from robot.api import logger
 
+from resources.libraries.python.Constants import Constants
 from resources.libraries.python.topology import Topology
 from resources.libraries.python.PapiExecutor import PapiSocketExecutor
 
@@ -40,16 +41,10 @@ class Classify(object):
         :returns MAC ACL mask in hexstring format.
         :rtype: str
         """
-        if ether_type:
-            end = 28
-        elif src_mac:
-            end = 24
-        else:
-            end = 12
 
         return ('{!s:0>12}{!s:0>12}{!s:0>4}'.format(
             dst_mac.replace(':', ''), src_mac.replace(':', ''),
-            ether_type))[0:end]
+            ether_type)).decode('hex').rstrip('\0')
 
     @staticmethod
     def _build_ip_mask(proto='', src_ip='', dst_ip='', src_port='',
@@ -69,19 +64,10 @@ class Classify(object):
         :returns: IP mask in hexstring format.
         :rtype: str
         """
-        if dst_port:
-            end = 48
-        elif src_port:
-            end = 44
-        elif dst_ip:
-            end = 40
-        elif src_ip:
-            end = 32
-        else:
-            end = 20
 
         return ('{!s:0>20}{!s:0>12}{!s:0>8}{!s:0>4}{!s:0>4}'.format(
-            proto, src_ip, dst_ip, src_port, dst_port))[0:end]
+            proto, src_ip, dst_ip, src_port, dst_port)).decode('hex').\
+            rstrip('\0')
 
     @staticmethod
     def _build_ip6_mask(next_hdr='', src_ip='', dst_ip='', src_port='',
@@ -101,19 +87,10 @@ class Classify(object):
         :returns: IPv6 ACL mask in hexstring format.
         :rtype: str
         """
-        if dst_port:
-            end = 88
-        elif src_port:
-            end = 84
-        elif dst_ip:
-            end = 80
-        elif src_ip:
-            end = 48
-        else:
-            end = 14
 
         return ('{!s:0>14}{!s:0>34}{!s:0>32}{!s:0>4}{!s:0>4}'.format(
-            next_hdr, src_ip, dst_ip, src_port, dst_port))[0:end]
+            next_hdr, src_ip, dst_ip, src_port, dst_port)).decode('hex').\
+            rstrip('\0')
 
     @staticmethod
     def _build_mac_match(dst_mac='', src_mac='', ether_type=''):
@@ -128,24 +105,18 @@ class Classify(object):
         :returns: MAC ACL match data in hexstring format.
         :rtype: str
         """
-        if ether_type:
-            end = 28
-        elif src_mac:
-            end = 24
-        else:
-            end = 12
 
         return ('{!s:0>12}{!s:0>12}{!s:0>4}'.format(
             dst_mac.replace(':', ''), src_mac.replace(':', ''),
-            ether_type))[0:end]
+            ether_type)).decode('hex').rstrip('\0')
 
     @staticmethod
     def _build_ip_match(proto=0, src_ip='', dst_ip='', src_port=0, dst_port=0):
-        """Build IP ACL match data in hexstring format.
+        """Build IP ACL match data in byte-string format.
 
         :param proto: Protocol number with valid option "x".
-        :param src_ip: Source ip address with format of "x.x.x.x".
-        :param dst_ip: Destination ip address with format of "x.x.x.x".
+        :param src_ip: Source ip address in packed format.
+        :param dst_ip: Destination ip address in packed format.
         :param src_port: Source port number "x".
         :param dst_port: Destination port number "x".
         :type proto: int
@@ -153,37 +124,22 @@ class Classify(object):
         :type dst_ip: str
         :type src_port: int
         :type dst_port: int
-        :returns: IP ACL match data in hexstring format.
+        :returns: IP ACL match data in byte-string format.
         :rtype: str
         """
-        if src_ip:
-            src_ip = binascii.hexlify(ip_address(unicode(src_ip)).packed)
-        if dst_ip:
-            dst_ip = binascii.hexlify(ip_address(unicode(dst_ip)).packed)
-        if dst_port:
-            end = 48
-        elif src_port:
-            end = 44
-        elif dst_ip:
-            end = 40
-        elif src_ip:
-            end = 32
-        else:
-            end = 20
 
         return ('{!s:0>20}{!s:0>12}{!s:0>8}{!s:0>4}{!s:0>4}'.format(
             hex(proto)[2:], src_ip, dst_ip, hex(src_port)[2:],
-            hex(dst_port)[2:]))[0:end]
+            hex(dst_port)[2:])).decode('hex').rstrip('\0')
 
     @staticmethod
     def _build_ip6_match(next_hdr=0, src_ip='', dst_ip='', src_port=0,
                          dst_port=0):
-        """Build IPv6 ACL match data in hexstring format.
+        """Build IPv6 ACL match data in byte-string format.
 
         :param next_hdr: Next header number with valid option "x".
-        :param src_ip: Source ip6 address with format of "xxxx:xxxx::xxxx".
-        :param dst_ip: Destination ip6 address with format of
-            "xxxx:xxxx::xxxx".
+        :param src_ip: Source ip6 address in packed format.
+        :param dst_ip: Destination ip6 address in packed format.
         :param src_port: Source port number "x".
         :param dst_port: Destination port number "x".
         :type next_hdr: int
@@ -191,52 +147,36 @@ class Classify(object):
         :type dst_ip: str
         :type src_port: int
         :type dst_port: int
-        :returns: IPv6 ACL match data in hexstring format.
+        :returns: IPv6 ACL match data in byte-string format.
         :rtype: str
         """
-        if src_ip:
-            src_ip = binascii.hexlify(ip_address(unicode(src_ip)).packed)
-        if dst_ip:
-            dst_ip = binascii.hexlify(ip_address(unicode(dst_ip)).packed)
-        if dst_port:
-            end = 88
-        elif src_port:
-            end = 84
-        elif dst_ip:
-            end = 80
-        elif src_ip:
-            end = 48
-        else:
-            end = 14
 
         return ('{!s:0>14}{!s:0>34}{!s:0>32}{!s:0>4}{!s:0>4}'.format(
             hex(next_hdr)[2:], src_ip, dst_ip, hex(src_port)[2:],
-            hex(dst_port)[2:]))[0:end]
+            hex(dst_port)[2:])).decode('hex').rstrip('\0')
 
     @staticmethod
-    def _classify_add_del_table(node, is_add, mask, match_n_vectors=1,
-                                table_index=0xFFFFFFFF, nbuckets=2,
-                                memory_size=2097152, skip_n_vectors=0,
-                                next_table_index=0xFFFFFFFF,
-                                miss_next_index=0xFFFFFFFF, current_data_flag=0,
-                                current_data_offset=0):
+    def _classify_add_del_table(
+            node, is_add, mask, match_n_vectors=Constants.BITWISE_NON_ZERO,
+            table_index=Constants.BITWISE_NON_ZERO, nbuckets=2,
+            memory_size=2097152, skip_n_vectors=Constants.BITWISE_NON_ZERO,
+            next_table_index=Constants.BITWISE_NON_ZERO,
+            miss_next_index=Constants.BITWISE_NON_ZERO,
+            current_data_flag=0, current_data_offset=0):
         """Add or delete a classify table.
 
         :param node: VPP node to create classify table.
         :param is_add: If 1 the table is added, if 0 the table is deleted.
         :param mask: ACL mask in hexstring format.
-        :param match_n_vectors: Number of vectors to match (Default value = 1).
-        :param table_index: Index of the classify table.
-            (Default value = 0xFFFFFFFF)
+        :param match_n_vectors: Number of vectors to match (Default value = ~0).
+        :param table_index: Index of the classify table. (Default value = ~0)
         :param nbuckets: Number of buckets when adding a table.
             (Default value = 2)
         :param memory_size: Memory size when adding a table.
             (Default value = 2097152)
-        :param skip_n_vectors: Number of skip vectors (Default value = 0).
-        :param next_table_index: Index of next table.
-            (Default value = 0xFFFFFFFF)
-        :param miss_next_index: Index of miss table.
-            (Default value = 0xFFFFFFFF)
+        :param skip_n_vectors: Number of skip vectors (Default value = ~0).
+        :param next_table_index: Index of next table. (Default value = ~0)
+        :param miss_next_index: Index of miss table. (Default value = ~0)
         :param current_data_flag: Option to use current node's packet payload
             as the starting point from where packets are classified.
             This option is only valid for L2/L3 input ACL for now.
@@ -267,9 +207,7 @@ class Classify(object):
             match_n: Number of match vectors.
         :rtype: tuple(int, int, int)
         """
-        mask_len = ((len(mask) - 1) / 16 + 1) * 16
-        mask = mask + '\0' * (mask_len - len(mask))
-
+        cmd = 'classify_add_del_table'
         args = dict(
             is_add=is_add,
             table_index=table_index,
@@ -281,11 +219,9 @@ class Classify(object):
             miss_next_index=miss_next_index,
             current_data_flag=current_data_flag,
             current_data_offset=current_data_offset,
-            mask_len=mask_len,
+            mask_len=len(mask),
             mask=mask
         )
-
-        cmd = 'classify_add_del_table'
         err_msg = "Failed to create a classify table on host {host}".format(
             host=node['host'])
 
@@ -296,10 +232,11 @@ class Classify(object):
             int(reply["match_n_vectors"])
 
     @staticmethod
-    def _classify_add_del_session(node, is_add, table_index, match,
-                                  opaque_index=0xFFFFFFFF,
-                                  hit_next_index=0xFFFFFFFF, advance=0,
-                                  action=0, metadata=0):
+    def _classify_add_del_session(
+            node, is_add, table_index, match,
+            opaque_index=Constants.BITWISE_NON_ZERO,
+            hit_next_index=Constants.BITWISE_NON_ZERO, advance=0,
+            action=0, metadata=0):
         """Add or delete a classify session.
 
         :param node: VPP node to create classify session.
@@ -309,9 +246,9 @@ class Classify(object):
             include bytes in front with length of skip_n_vectors of target table
             times sizeof (u32x4) (values of those bytes will be ignored).
         :param opaque_index: For add, opaque_index of new session.
-            (Default value = 0xFFFFFFFF)
+            (Default value = ~0)
         :param hit_next_index: For add, hit_next_index of new session.
-            (Default value = 0xFFFFFFFF)
+            (Default value = ~0)
         :param advance: For add, advance value for session. (Default value = 0)
         :param action: 0: No action (by default) metadata is not used.
             1: Classified IP packets will be looked up from the specified ipv4
@@ -337,9 +274,7 @@ class Classify(object):
         :type action: int
         :type metadata: int
         """
-
-        match_len = ((len(match) - 1) / 16 + 1) * 16
-        match = match + '\0' * (match_len - len(match))
+        cmd = 'classify_add_del_session'
         args = dict(
             is_add=is_add,
             table_index=table_index,
@@ -348,10 +283,9 @@ class Classify(object):
             advance=advance,
             action=action,
             metadata=metadata,
-            match_len=match_len,
+            match_len=len(match),
             match=match
         )
-        cmd = 'classify_add_del_session'
         err_msg = "Failed to create a classify session on host {host}".format(
             host=node['host'])
 
@@ -438,18 +372,18 @@ class Classify(object):
             papi_exec.add(cmd, **args).get_reply(err_msg)
 
     @staticmethod
-    def vpp_creates_classify_table_l3(node, ip_version, direction, ip_addr):
+    def vpp_creates_classify_table_l3(node, ip_version, direction, netmask):
         """Create classify table for IP address filtering.
 
         :param node: VPP node to create classify table.
         :param ip_version: Version of IP protocol.
         :param direction: Direction of traffic - src/dst.
-        :param ip_addr: IPv4 or Ipv6 (depending on the parameter 'ip_version')
-            address.
+        :param netmask: IPv4 or Ipv6 (depending on the parameter 'ip_version')
+            netmask (decimal, e.g. 255.255.255.255).
         :type node: dict
         :type ip_version: str
         :type direction: str
-        :type ip_addr: str
+        :type netmask: str
         :returns: (table_index, skip_n, match_n)
             table_index: Classify table index.
             skip_n: Number of skip vectors.
@@ -462,181 +396,114 @@ class Classify(object):
             ip4=Classify._build_ip_mask,
             ip6=Classify._build_ip6_mask
         )
+
         if ip_version == "ip4" or ip_version == "ip6":
-            ip_addr = binascii.hexlify(ip_address(unicode(ip_addr)).packed)
+            netmask = binascii.hexlify(ip_address(unicode(netmask)).packed)
         else:
-            raise ValueError("IP version {ver} is not supported.".
-                             format(ver=ip_version))
+            raise ValueError("IP version {ver} is not supported.".format(
+                ver=ip_version))
 
         if direction == "src":
-            mask = mask_f[ip_version](src_ip=ip_addr)
+            mask = mask_f[ip_version](src_ip=netmask)
         elif direction == "dst":
-            mask = mask_f[ip_version](dst_ip=ip_addr)
+            mask = mask_f[ip_version](dst_ip=netmask)
         else:
-            raise ValueError("Direction {dir} is not supported.".
-                             format(dir=direction))
+            raise ValueError("Direction {dir} is not supported.".format(
+                dir=direction))
 
-        return Classify._classify_add_del_table(
-            node,
-            is_add=1,
-            mask=binascii.unhexlify(mask),
-            match_n_vectors=(len(mask) - 1) // 32 + 1
-        )
+        # Add l2 ethernet header to mask
+        mask = 14 * '\0' + mask
 
-    @staticmethod
-    def vpp_creates_classify_table_l2(node, direction, mac=""):
-        """Create classify table for MAC address filtering.
+        # Get index of the first significant mask octet
+        i = len(mask) - len(mask.lstrip('\0'))
 
-        :param node: VPP node to create classify table.
-        :param direction: Direction of traffic - src/dst.
-        :param mac: Source or destination (depending on the parameter
-            'direction') MAC address.
-        :type node: dict
-        :type direction: str
-        :type mac: str
-        :returns: (table_index, skip_n, match_n)
-            table_index: Classify table index.
-            skip_n: Number of skip vectors.
-            match_n: Number of match vectors.
-        :rtype: tuple(int, int, int)
-        :raises ValueError: If the parameter 'direction' has incorrect value.
-        """
-        if direction == "src":
-            mask = Classify._build_mac_mask(src_mac=mac)
-        elif direction == "dst":
-            mask = Classify._build_mac_mask(dst_mac=mac)
-        else:
-            raise ValueError("Direction {dir} is not supported.".
-                             format(dir=direction))
-
-        return Classify._classify_add_del_table(
-            node,
-            is_add=1,
-            mask=binascii.unhexlify(mask),
-            match_n_vectors=(len(mask) - 1) // 32 + 1
-        )
-
-    @staticmethod
-    def vpp_creates_classify_table_hex(node, hex_mask):
-        """Create classify table with hex mask.
+        # Compute skip_n parameter
+        skip_n = i // 16
+        # Remove octets to be skipped from the mask
+        mask = mask[skip_n*16:]
+        # Pad mask to an even multiple of the vector size
+        mask = mask + (16 - len(mask) % 16 if len(mask) % 16 else 0) * '\0'
+        # Compute match_n parameter
+        match_n = len(mask) // 16
 
-        :param node: VPP node to create classify table based on hex mask.
-        :param hex_mask: Classify hex mask.
-        :type node: dict
-        :type hex_mask: str
-        :returns: (table_index, skip_n, match_n)
-            table_index: Classify table index.
-            skip_n: Number of skip vectors.
-            match_n: Number of match vectors.
-        :rtype: tuple(int, int, int)
-        """
         return Classify._classify_add_del_table(
             node,
             is_add=1,
-            mask=binascii.unhexlify(hex_mask),
-            match_n_vectors=(len(hex_mask) - 1) // 32 + 1
+            mask=mask,
+            match_n_vectors=match_n,
+            skip_n_vectors=skip_n
         )
 
     @staticmethod
-    def vpp_configures_classify_session_l3(node, acl_method, table_index,
-                                           ip_version, direction, address):
+    def vpp_configures_classify_session_l3(
+            node, acl_method, table_index, skip_n, match_n, ip_version,
+            direction, address, hit_next_index=Constants.BITWISE_NON_ZERO,
+            opaque_index=Constants.BITWISE_NON_ZERO):
         """Configuration of classify session for IP address filtering.
 
         :param node: VPP node to setup classify session.
         :param acl_method: ACL method - deny/permit.
         :param table_index: Classify table index.
+        :param skip_n: Number of skip vectors.
+        :param match_n: Number of vectors to match.
         :param ip_version: Version of IP protocol.
         :param direction: Direction of traffic - src/dst.
         :param address: IPv4 or IPv6 address.
+        :param hit_next_index: hit_next_index of new session.
+            (Default value = ~0)
+        :param opaque_index: opaque_index of new session. (Default value = ~0)
         :type node: dict
         :type acl_method: str
         :type table_index: int
+        :type skip_n: int
+        :type match_n: int
         :type ip_version: str
         :type direction: str
         :type address: str
+        :type hit_next_index: int
+        :type opaque_index: int
         :raises ValueError: If the parameter 'direction' has incorrect value.
         """
         match_f = dict(
             ip4=Classify._build_ip_match,
             ip6=Classify._build_ip6_match
         )
-        if direction == "src":
-            match = match_f[ip_version](src_ip=address)
-        elif direction == "dst":
-            match = match_f[ip_version](dst_ip=address)
-        else:
-            raise ValueError("Direction {dir} is not supported.".
-                             format(dir=direction))
         action = dict(
             permit=0,
             deny=1
         )
-        Classify._classify_add_del_session(
-            node,
-            is_add=1,
-            table_index=table_index,
-            match=binascii.unhexlify(match),
-            action=action[acl_method])
 
-    @staticmethod
-    def vpp_configures_classify_session_l2(node, acl_method, table_index,
-                                           direction, address):
-        """Configuration of classify session for MAC address filtering.
+        if ip_version == "ip4" or ip_version == "ip6":
+            address = binascii.hexlify(ip_address(unicode(address)).packed)
+        else:
+            raise ValueError("IP version {ver} is not supported.".format(
+                ver=ip_version))
 
-        :param node: VPP node to setup classify session.
-        :param acl_method: ACL method - deny/permit.
-        :param table_index: Classify table index.
-        :param direction: Direction of traffic - src/dst.
-        :param address: MAC address.
-        :type node: dict
-        :type acl_method: str
-        :type table_index: int
-        :type direction: str
-        :type address: str
-        :raises ValueError: If the parameter 'direction' has incorrect value.
-        """
         if direction == "src":
-            match = Classify._build_mac_match(src_mac=address)
+            match = match_f[ip_version](src_ip=address)
         elif direction == "dst":
-            match = Classify._build_mac_match(dst_mac=address)
+            match = match_f[ip_version](dst_ip=address)
         else:
-            raise ValueError("Direction {dir} is not supported.".
-                             format(dir=direction))
-        action = dict(
-            permit=0,
-            deny=1
-        )
-        Classify._classify_add_del_session(
-            node,
-            is_add=1,
-            table_index=table_index,
-            match=binascii.unhexlify(match),
-            action=action[acl_method])
+            raise ValueError("Direction {dir} is not supported.".format(
+                dir=direction))
 
-    @staticmethod
-    def vpp_configures_classify_session_hex(node, acl_method, table_index,
-                                            hex_value):
-        """Configuration of classify session with hex value.
+        # Prepend match with l2 ethernet header part
+        match = 14 * '\0' + match
+
+        # Pad match to match skip_n_vector + match_n_vector size
+        match = match + ((match_n + skip_n) * 16 - len(match)
+                         if len(match) < (match_n + skip_n) * 16
+                         else 0) * '\0'
 
-        :param node: VPP node to setup classify session.
-        :param acl_method: ACL method - deny/permit.
-        :param table_index: Classify table index.
-        :param hex_value: Classify hex value.
-        :type node: dict
-        :type acl_method: str
-        :type table_index: int
-        :type hex_value: str
-        """
-        action = dict(
-            permit=0,
-            deny=1
-        )
         Classify._classify_add_del_session(
             node,
             is_add=1,
             table_index=table_index,
-            match=binascii.unhexlify(hex_value),
-            action=action[acl_method])
+            hit_next_index=hit_next_index,
+            opaque_index=opaque_index,
+            match=match,
+            action=action[acl_method]
+        )
 
     @staticmethod
     def compute_classify_hex_mask(ip_version, protocol, direction):
@@ -756,6 +623,18 @@ class Classify(object):
 
         return details
 
+    @staticmethod
+    def show_classify_tables_verbose(node):
+        """Show classify tables verbose.
+
+        :param node: Topology node.
+        :type node: dict
+        :returns: Classify tables verbose data.
+        :rtype: str
+        """
+        return PapiSocketExecutor.run_cli_cmd(
+            node, "show classify tables verbose")
+
     @staticmethod
     def vpp_log_plugin_acl_settings(node):
         """Retrieve configured settings from the ACL plugin and write to robot