CSIT-1471: Policer - VAT API to PAPI
[csit.git] / resources / libraries / python / Classify.py
1 # Copyright (c) 2019 Cisco and/or its affiliates.
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at:
5 #
6 #     http://www.apache.org/licenses/LICENSE-2.0
7 #
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
13
14 """Classify utilities library."""
15
16 import binascii
17 import re
18
19 from ipaddress import ip_address
20
21 from robot.api import logger
22
23 from resources.libraries.python.Constants import Constants
24 from resources.libraries.python.topology import Topology
25 from resources.libraries.python.PapiExecutor import PapiSocketExecutor
26
27
28 class Classify(object):
29     """Classify utilities."""
30
31     @staticmethod
32     def _build_mac_mask(dst_mac='', src_mac='', ether_type=''):
33         """Build MAC ACL mask data in hexstring format.
34
35         :param dst_mac: Source MAC address <0-ffffffffffff>.
36         :param src_mac: Destination MAC address <0-ffffffffffff>.
37         :param ether_type: Ethernet type <0-ffff>.
38         :type dst_mac: str
39         :type src_mac: str
40         :type ether_type: str
41         :returns MAC ACL mask in hexstring format.
42         :rtype: str
43         """
44
45         return ('{!s:0>12}{!s:0>12}{!s:0>4}'.format(
46             dst_mac.replace(':', ''), src_mac.replace(':', ''),
47             ether_type)).decode('hex').rstrip('\0')
48
49     @staticmethod
50     def _build_ip_mask(proto='', src_ip='', dst_ip='', src_port='',
51                        dst_port=''):
52         """Build IP ACL mask data in hexstring format.
53
54         :param proto: Protocol number <0-ff>.
55         :param src_ip: Source ip address <0-ffffffff>.
56         :param dst_ip: Destination ip address <0-ffffffff>.
57         :param src_port: Source port number <0-ffff>.
58         :param str dst_port: Destination port number <0-ffff>.
59         :type proto: str
60         :type src_ip: str
61         :type dst_ip: str
62         :type src_port: str
63         :type dst_port:src
64         :returns: IP mask in hexstring format.
65         :rtype: str
66         """
67
68         return ('{!s:0>20}{!s:0>12}{!s:0>8}{!s:0>4}{!s:0>4}'.format(
69             proto, src_ip, dst_ip, src_port, dst_port)).decode('hex').\
70             rstrip('\0')
71
72     @staticmethod
73     def _build_ip6_mask(next_hdr='', src_ip='', dst_ip='', src_port='',
74                         dst_port=''):
75         """Build IPv6 ACL mask data in hexstring format.
76
77         :param next_hdr: Next header number <0-ff>.
78         :param src_ip: Source ip address <0-ffffffff>.
79         :param dst_ip: Destination ip address <0-ffffffff>.
80         :param src_port: Source port number <0-ffff>.
81         :param dst_port: Destination port number <0-ffff>.
82         :type next_hdr: str
83         :type src_ip: str
84         :type dst_ip: str
85         :type src_port: str
86         :type dst_port: str
87         :returns: IPv6 ACL mask in hexstring format.
88         :rtype: str
89         """
90
91         return ('{!s:0>14}{!s:0>34}{!s:0>32}{!s:0>4}{!s:0>4}'.format(
92             next_hdr, src_ip, dst_ip, src_port, dst_port)).decode('hex').\
93             rstrip('\0')
94
95     @staticmethod
96     def _build_mac_match(dst_mac='', src_mac='', ether_type=''):
97         """Build MAC ACL match data in  hexstring format.
98
99         :param dst_mac: Source MAC address <x:x:x:x:x:x>.
100         :param src_mac: Destination MAC address <x:x:x:x:x:x>.
101         :param ether_type: Ethernet type <0-ffff>.
102         :type dst_mac: str
103         :type src_mac: str
104         :type ether_type: str
105         :returns: MAC ACL match data in hexstring format.
106         :rtype: str
107         """
108
109         return ('{!s:0>12}{!s:0>12}{!s:0>4}'.format(
110             dst_mac.replace(':', ''), src_mac.replace(':', ''),
111             ether_type)).decode('hex').rstrip('\0')
112
113     @staticmethod
114     def _build_ip_match(proto=0, src_ip='', dst_ip='', src_port=0, dst_port=0):
115         """Build IP ACL match data in byte-string format.
116
117         :param proto: Protocol number with valid option "x".
118         :param src_ip: Source ip address in packed format.
119         :param dst_ip: Destination ip address in packed format.
120         :param src_port: Source port number "x".
121         :param dst_port: Destination port number "x".
122         :type proto: int
123         :type src_ip: str
124         :type dst_ip: str
125         :type src_port: int
126         :type dst_port: int
127         :returns: IP ACL match data in byte-string format.
128         :rtype: str
129         """
130
131         return ('{!s:0>20}{!s:0>12}{!s:0>8}{!s:0>4}{!s:0>4}'.format(
132             hex(proto)[2:], src_ip, dst_ip, hex(src_port)[2:],
133             hex(dst_port)[2:])).decode('hex').rstrip('\0')
134
135     @staticmethod
136     def _build_ip6_match(next_hdr=0, src_ip='', dst_ip='', src_port=0,
137                          dst_port=0):
138         """Build IPv6 ACL match data in byte-string format.
139
140         :param next_hdr: Next header number with valid option "x".
141         :param src_ip: Source ip6 address in packed format.
142         :param dst_ip: Destination ip6 address in packed format.
143         :param src_port: Source port number "x".
144         :param dst_port: Destination port number "x".
145         :type next_hdr: int
146         :type src_ip: str
147         :type dst_ip: str
148         :type src_port: int
149         :type dst_port: int
150         :returns: IPv6 ACL match data in byte-string format.
151         :rtype: str
152         """
153
154         return ('{!s:0>14}{!s:0>34}{!s:0>32}{!s:0>4}{!s:0>4}'.format(
155             hex(next_hdr)[2:], src_ip, dst_ip, hex(src_port)[2:],
156             hex(dst_port)[2:])).decode('hex').rstrip('\0')
157
158     @staticmethod
159     def _classify_add_del_table(
160             node, is_add, mask, match_n_vectors=Constants.BITWISE_NON_ZERO,
161             table_index=Constants.BITWISE_NON_ZERO, nbuckets=2,
162             memory_size=2097152, skip_n_vectors=Constants.BITWISE_NON_ZERO,
163             next_table_index=Constants.BITWISE_NON_ZERO,
164             miss_next_index=Constants.BITWISE_NON_ZERO,
165             current_data_flag=0, current_data_offset=0):
166         """Add or delete a classify table.
167
168         :param node: VPP node to create classify table.
169         :param is_add: If 1 the table is added, if 0 the table is deleted.
170         :param mask: ACL mask in hexstring format.
171         :param match_n_vectors: Number of vectors to match (Default value = ~0).
172         :param table_index: Index of the classify table. (Default value = ~0)
173         :param nbuckets: Number of buckets when adding a table.
174             (Default value = 2)
175         :param memory_size: Memory size when adding a table.
176             (Default value = 2097152)
177         :param skip_n_vectors: Number of skip vectors (Default value = ~0).
178         :param next_table_index: Index of next table. (Default value = ~0)
179         :param miss_next_index: Index of miss table. (Default value = ~0)
180         :param current_data_flag: Option to use current node's packet payload
181             as the starting point from where packets are classified.
182             This option is only valid for L2/L3 input ACL for now.
183             0: by default, classify data from the buffer's start location
184             1: classify packets from VPP node's current data pointer.
185         :param current_data_offset: A signed value to shift the start location
186             of the packet to be classified.
187             For example, if input IP ACL node is used, L2 header's first byte
188             can be accessible by configuring current_data_offset to -14
189             if there is no vlan tag.
190             This is valid only if current_data_flag is set to 1.
191             (Default value = 0)
192         :type node: dict
193         :type is_add: int
194         :type mask: str
195         :type match_n_vectors: int
196         :type table_index: int
197         :type nbuckets: int
198         :type memory_size: int
199         :type skip_n_vectors: int
200         :type next_table_index: int
201         :type miss_next_index: int
202         :type current_data_flag: int
203         :type current_data_offset: int
204         :returns: (table_index, skip_n, match_n)
205             table_index: Classify table index.
206             skip_n: Number of skip vectors.
207             match_n: Number of match vectors.
208         :rtype: tuple(int, int, int)
209         """
210         cmd = 'classify_add_del_table'
211         args = dict(
212             is_add=is_add,
213             table_index=table_index,
214             nbuckets=nbuckets,
215             memory_size=memory_size,
216             skip_n_vectors=skip_n_vectors,
217             match_n_vectors=match_n_vectors,
218             next_table_index=next_table_index,
219             miss_next_index=miss_next_index,
220             current_data_flag=current_data_flag,
221             current_data_offset=current_data_offset,
222             mask_len=len(mask),
223             mask=mask
224         )
225         err_msg = "Failed to create a classify table on host {host}".format(
226             host=node['host'])
227
228         with PapiSocketExecutor(node) as papi_exec:
229             reply = papi_exec.add(cmd, **args).get_reply(err_msg)
230
231         return int(reply["new_table_index"]), int(reply["skip_n_vectors"]),\
232             int(reply["match_n_vectors"])
233
234     @staticmethod
235     def _classify_add_del_session(
236             node, is_add, table_index, match,
237             opaque_index=Constants.BITWISE_NON_ZERO,
238             hit_next_index=Constants.BITWISE_NON_ZERO, advance=0,
239             action=0, metadata=0):
240         """Add or delete a classify session.
241
242         :param node: VPP node to create classify session.
243         :param is_add: If 1 the session is added, if 0 the session is deleted.
244         :param table_index: Index of the table to add/del the session.
245         :param match: For add, match value for session, required, needs to
246             include bytes in front with length of skip_n_vectors of target table
247             times sizeof (u32x4) (values of those bytes will be ignored).
248         :param opaque_index: For add, opaque_index of new session.
249             (Default value = ~0)
250         :param hit_next_index: For add, hit_next_index of new session.
251             (Default value = ~0)
252         :param advance: For add, advance value for session. (Default value = 0)
253         :param action: 0: No action (by default) metadata is not used.
254             1: Classified IP packets will be looked up from the specified ipv4
255                fib table (configured by metadata as VRF id).
256                Only valid for L3 input ACL node
257             2: Classified IP packets will be looked up from the specified ipv6
258                fib table (configured by metadata as VRF id).
259                Only valid for L3 input ACL node
260             3: Classified packet will be steered to source routig policy of
261                given index (in metadata).
262                This is only valid for IPv6 packets redirected to a source
263                routing node.
264         :param metadata: Valid only if action != 0
265             VRF id if action is 1 or 2. SR policy index if action is 3.
266             (Default value = 0)
267         :type node: dict
268         :type is_add: int
269         :type table_index: int
270         :type match: str
271         :type opaque_index: int
272         :type hit_next_index: int
273         :type advance: int
274         :type action: int
275         :type metadata: int
276         """
277         cmd = 'classify_add_del_session'
278         args = dict(
279             is_add=is_add,
280             table_index=table_index,
281             hit_next_index=hit_next_index,
282             opaque_index=opaque_index,
283             advance=advance,
284             action=action,
285             metadata=metadata,
286             match_len=len(match),
287             match=match
288         )
289         err_msg = "Failed to create a classify session on host {host}".format(
290             host=node['host'])
291
292         with PapiSocketExecutor(node) as papi_exec:
293             papi_exec.add(cmd, **args).get_reply(err_msg)
294
295     @staticmethod
296     def _macip_acl_add(node, rules, tag=""):
297         """Add MACIP ACL.
298
299         :param node: VPP node to add MACIP ACL.
300         :param rules: List of rules for given ACL.
301         :param tag: ACL tag.
302         :type node: dict
303         :type rules: list
304         :type tag: str
305         """
306         cmd = "macip_acl_add"
307         args = dict(
308             r=rules,
309             count=len(rules),
310             tag=tag
311         )
312
313         err_msg = "Failed to create a classify session on host {host}".format(
314             host=node['host'])
315
316         with PapiSocketExecutor(node) as papi_exec:
317             papi_exec.add(cmd, **args).get_reply(err_msg)
318
319     @staticmethod
320     def _acl_interface_set_acl_list(node, sw_if_index, acl_type, acls):
321         """Set ACL list for interface.
322
323         :param node: VPP node to set ACL list for interface.
324         :param sw_if_index: sw_if_index of the used interface.
325         :param acl_type: Type of ACL(s) - input or output.
326         :param acls: List of ACLs.
327         :type node: dict
328         :type sw_if_index: int
329         :type acl_type: str
330         :type acls: list
331         """
332         cmd = "acl_interface_set_acl_list"
333         n_input = len(acls) if acl_type == "input" else 0
334         args = dict(
335             sw_if_index=sw_if_index,
336             acls=acls,
337             n_input=n_input,
338             count=len(acls)
339         )
340
341         err_msg = "Failed to set acl list for interface {idx} on host {host}".\
342             format(idx=sw_if_index, host=node['host'])
343
344         with PapiSocketExecutor(node) as papi_exec:
345             papi_exec.add(cmd, **args).get_reply(err_msg)
346
347     @staticmethod
348     def _acl_add_replace(node, acl_idx, rules, tag=""):
349         """ Add/replace ACLs.
350
351         :param node: VPP node to add MACIP ACL.
352         :param acl_idx: ACL index.
353         :param rules: List of rules for given ACL.
354         :param tag: ACL tag.
355         :type node: dict
356         :type acl_idx: int
357         :type rules: list
358         :type tag: str
359         """
360         cmd = "acl_add_replace"
361         args = dict(
362             tag=tag.encode("utf-8"),
363             acl_index=4294967295 if acl_idx is None else acl_idx,
364             count=len(rules),
365             r=rules
366         )
367
368         err_msg = "Failed to add/replace acls on host {host}".format(
369             host=node['host'])
370
371         with PapiSocketExecutor(node) as papi_exec:
372             papi_exec.add(cmd, **args).get_reply(err_msg)
373
374     @staticmethod
375     def vpp_creates_classify_table_l3(node, ip_version, direction, netmask):
376         """Create classify table for IP address filtering.
377
378         :param node: VPP node to create classify table.
379         :param ip_version: Version of IP protocol.
380         :param direction: Direction of traffic - src/dst.
381         :param netmask: IPv4 or Ipv6 (depending on the parameter 'ip_version')
382             netmask (decimal, e.g. 255.255.255.255).
383         :type node: dict
384         :type ip_version: str
385         :type direction: str
386         :type netmask: str
387         :returns: (table_index, skip_n, match_n)
388             table_index: Classify table index.
389             skip_n: Number of skip vectors.
390             match_n: Number of match vectors.
391         :rtype: tuple(int, int, int)
392         :raises ValueError: If the parameters 'ip_version' or 'direction' have
393             incorrect values.
394         """
395         mask_f = dict(
396             ip4=Classify._build_ip_mask,
397             ip6=Classify._build_ip6_mask
398         )
399
400         if ip_version == "ip4" or ip_version == "ip6":
401             netmask = binascii.hexlify(ip_address(unicode(netmask)).packed)
402         else:
403             raise ValueError("IP version {ver} is not supported.".format(
404                 ver=ip_version))
405
406         if direction == "src":
407             mask = mask_f[ip_version](src_ip=netmask)
408         elif direction == "dst":
409             mask = mask_f[ip_version](dst_ip=netmask)
410         else:
411             raise ValueError("Direction {dir} is not supported.".format(
412                 dir=direction))
413
414         # Add l2 ethernet header to mask
415         mask = 14 * '\0' + mask
416
417         # Get index of the first significant mask octet
418         i = len(mask) - len(mask.lstrip('\0'))
419
420         # Compute skip_n parameter
421         skip_n = i // 16
422         # Remove octets to be skipped from the mask
423         mask = mask[skip_n*16:]
424         # Pad mask to an even multiple of the vector size
425         mask = mask + (16 - len(mask) % 16 if len(mask) % 16 else 0) * '\0'
426         # Compute match_n parameter
427         match_n = len(mask) // 16
428
429         return Classify._classify_add_del_table(
430             node,
431             is_add=1,
432             mask=mask,
433             match_n_vectors=match_n,
434             skip_n_vectors=skip_n
435         )
436
437     @staticmethod
438     def vpp_configures_classify_session_l3(
439             node, acl_method, table_index, skip_n, match_n, ip_version,
440             direction, address, hit_next_index=Constants.BITWISE_NON_ZERO,
441             opaque_index=Constants.BITWISE_NON_ZERO):
442         """Configuration of classify session for IP address filtering.
443
444         :param node: VPP node to setup classify session.
445         :param acl_method: ACL method - deny/permit.
446         :param table_index: Classify table index.
447         :param skip_n: Number of skip vectors.
448         :param match_n: Number of vectors to match.
449         :param ip_version: Version of IP protocol.
450         :param direction: Direction of traffic - src/dst.
451         :param address: IPv4 or IPv6 address.
452         :param hit_next_index: hit_next_index of new session.
453             (Default value = ~0)
454         :param opaque_index: opaque_index of new session. (Default value = ~0)
455         :type node: dict
456         :type acl_method: str
457         :type table_index: int
458         :type skip_n: int
459         :type match_n: int
460         :type ip_version: str
461         :type direction: str
462         :type address: str
463         :type hit_next_index: int
464         :type opaque_index: int
465         :raises ValueError: If the parameter 'direction' has incorrect value.
466         """
467         match_f = dict(
468             ip4=Classify._build_ip_match,
469             ip6=Classify._build_ip6_match
470         )
471         action = dict(
472             permit=0,
473             deny=1
474         )
475
476         if ip_version == "ip4" or ip_version == "ip6":
477             address = binascii.hexlify(ip_address(unicode(address)).packed)
478         else:
479             raise ValueError("IP version {ver} is not supported.".format(
480                 ver=ip_version))
481
482         if direction == "src":
483             match = match_f[ip_version](src_ip=address)
484         elif direction == "dst":
485             match = match_f[ip_version](dst_ip=address)
486         else:
487             raise ValueError("Direction {dir} is not supported.".format(
488                 dir=direction))
489
490         # Prepend match with l2 ethernet header part
491         match = 14 * '\0' + match
492
493         # Pad match to match skip_n_vector + match_n_vector size
494         match = match + ((match_n + skip_n) * 16 - len(match)
495                          if len(match) < (match_n + skip_n) * 16
496                          else 0) * '\0'
497
498         Classify._classify_add_del_session(
499             node,
500             is_add=1,
501             table_index=table_index,
502             hit_next_index=hit_next_index,
503             opaque_index=opaque_index,
504             match=match,
505             action=action[acl_method]
506         )
507
508     @staticmethod
509     def compute_classify_hex_mask(ip_version, protocol, direction):
510         """Compute classify hex mask for TCP or UDP packet matching.
511
512         :param ip_version: Version of IP protocol.
513         :param protocol: Type of protocol.
514         :param direction: Traffic direction.
515         :type ip_version: str
516         :type protocol: str
517         :type direction: str
518         :returns: Classify hex mask.
519         :rtype: str
520         :raises ValueError: If protocol is not TCP or UDP.
521         :raises ValueError: If direction is not source or destination or
522             source + destination.
523         """
524         if protocol in ('TCP', 'UDP'):
525             base_mask = Classify._compute_base_mask(ip_version)
526
527             if direction == 'source':
528                 return base_mask + 'FFFF0000'
529             elif direction == 'destination':
530                 return base_mask + '0000FFFF'
531             elif direction == 'source + destination':
532                 return base_mask + 'FFFFFFFF'
533             else:
534                 raise ValueError("Invalid direction!")
535         else:
536             raise ValueError("Invalid protocol!")
537
538     @staticmethod
539     def compute_classify_hex_value(hex_mask, source_port, destination_port):
540         """Compute classify hex value for TCP or UDP packet matching.
541
542         :param hex_mask: Classify hex mask.
543         :param source_port: Source TCP/UDP port.
544         :param destination_port: Destination TCP/UDP port.
545         :type hex_mask: str
546         :type source_port: str
547         :type destination_port: str
548         :returns: Classify hex value.
549         :rtype: str
550         """
551         source_port_hex = Classify._port_convert(source_port)
552         destination_port_hex = Classify._port_convert(destination_port)
553
554         return hex_mask[:-8] + source_port_hex + destination_port_hex
555
556     @staticmethod
557     def _port_convert(port):
558         """Convert port number for classify hex table format.
559
560         :param port: TCP/UDP port number.
561         :type port: str
562         :returns: TCP/UDP port number in 4-digit hexadecimal format.
563         :rtype: str
564         """
565         return '{0:04x}'.format(int(port))
566
567     @staticmethod
568     def _compute_base_mask(ip_version):
569         """Compute base classify hex mask based on IP version.
570
571         :param ip_version: Version of IP protocol.
572         :type ip_version: str
573         :returns: Base hex mask.
574         :rtype: str
575         """
576         if ip_version == 'ip4':
577             return 68 * '0'
578             # base value of classify hex table for IPv4 TCP/UDP ports
579         elif ip_version == 'ip6':
580             return 108 * '0'
581             # base value of classify hex table for IPv6 TCP/UDP ports
582         else:
583             raise ValueError("Invalid IP version!")
584
585     @staticmethod
586     def get_classify_table_data(node, table_index):
587         """Retrieve settings for classify table by ID.
588
589         :param node: VPP node to retrieve classify data from.
590         :param table_index: Index of a specific classify table.
591         :type node: dict
592         :type table_index: int
593         :returns: Classify table settings.
594         :rtype: dict
595         """
596         cmd = 'classify_table_info'
597         err_msg = "Failed to get 'classify_table_info' on host {host}".format(
598             host=node['host'])
599         args = dict(
600             table_id=int(table_index)
601         )
602         with PapiSocketExecutor(node) as papi_exec:
603             reply = papi_exec.add(cmd, **args).get_reply(err_msg)
604         return reply
605
606     @staticmethod
607     def get_classify_session_data(node, table_index):
608         """Retrieve settings for all classify sessions in a table.
609
610         :param node: VPP node to retrieve classify data from.
611         :param table_index: Index of a classify table.
612         :type node: dict
613         :type table_index: int
614         :returns: List of classify session settings.
615         :rtype: list or dict
616         """
617         cmd = "classify_session_dump"
618         args = dict(
619             table_id=int(table_index)
620         )
621         with PapiSocketExecutor(node) as papi_exec:
622             details = papi_exec.add(cmd, **args).get_details()
623
624         return details
625
626     @staticmethod
627     def show_classify_tables_verbose(node):
628         """Show classify tables verbose.
629
630         :param node: Topology node.
631         :type node: dict
632         :returns: Classify tables verbose data.
633         :rtype: str
634         """
635         return PapiSocketExecutor.run_cli_cmd(
636             node, "show classify tables verbose")
637
638     @staticmethod
639     def vpp_log_plugin_acl_settings(node):
640         """Retrieve configured settings from the ACL plugin and write to robot
641         log.
642
643         :param node: VPP node.
644         :type node: dict
645         """
646         PapiSocketExecutor.dump_and_log(node, ["acl_dump", ])
647
648     @staticmethod
649     def vpp_log_plugin_acl_interface_assignment(node):
650         """Retrieve interface assignment from the ACL plugin and write to robot
651         log.
652
653         :param node: VPP node.
654         :type node: dict
655         """
656         PapiSocketExecutor.dump_and_log(node, ["acl_interface_list_dump", ])
657
658     @staticmethod
659     def set_acl_list_for_interface(node, interface, acl_type, acl_idx=None):
660         """Set the list of input or output ACLs applied to the interface. It
661         unapplies any previously applied ACLs.
662
663         :param node: VPP node to set ACL on.
664         :param interface: Interface name or sw_if_index.
665         :param acl_type: Type of ACL(s) - input or output.
666         :param acl_idx: Index(ies) of ACLs to be applied on the interface.
667         :type node: dict
668         :type interface: str or int
669         :type acl_type: str
670         :type acl_idx: list
671         """
672         if isinstance(interface, basestring):
673             sw_if_index = Topology.get_interface_sw_index(node, interface)
674         else:
675             sw_if_index = int(interface)
676
677         acls = acl_idx if isinstance(acl_idx, list) else list()
678
679         Classify._acl_interface_set_acl_list(node=node,
680                                              sw_if_index=sw_if_index,
681                                              acl_type=acl_type,
682                                              acls=acls)
683
684     @staticmethod
685     def add_replace_acl_multi_entries(node, acl_idx=None, rules=None, tag=""):
686         """Add a new ACL or replace the existing one. To replace an existing
687         ACL, pass the ID of this ACL.
688
689         :param node: VPP node to set ACL on.
690         :param acl_idx: ID of ACL. (Optional)
691         :param rules: Required rules. (Optional)
692         :param tag: ACL tag (Optional).
693         :type node: dict
694         :type acl_idx: int
695         :type rules: str
696         :type tag: str
697         """
698         reg_ex_src_ip = re.compile(r'(src [0-9a-fA-F.:/\d{1,2}]*)')
699         reg_ex_dst_ip = re.compile(r'(dst [0-9a-fA-F.:/\d{1,2}]*)')
700         reg_ex_sport = re.compile(r'(sport \d{1,5})')
701         reg_ex_dport = re.compile(r'(dport \d{1,5})')
702         reg_ex_proto = re.compile(r'(proto \d{1,5})')
703
704         acl_rules = list()
705         for rule in rules.split(", "):
706             acl_rule = dict()
707             acl_rule["is_permit"] = 1 if "permit" in rule else 0
708             acl_rule["is_ipv6"] = 1 if "ipv6" in rule else 0
709
710             groups = re.search(reg_ex_src_ip, rule)
711             if groups:
712                 grp = groups.group(1).split(' ')[1].split('/')
713                 acl_rule["src_ip_addr"] = ip_address(unicode(grp[0])).packed
714                 acl_rule["src_ip_prefix_len"] = int(grp[1])
715
716             groups = re.search(reg_ex_dst_ip, rule)
717             if groups:
718                 grp = groups.group(1).split(' ')[1].split('/')
719                 acl_rule["dst_ip_addr"] = ip_address(unicode(grp[0])).packed
720                 acl_rule["dst_ip_prefix_len"] = int(grp[1])
721
722             groups = re.search(reg_ex_sport, rule)
723             if groups:
724                 port = int(groups.group(1).split(' ')[1])
725                 acl_rule["srcport_or_icmptype_first"] = port
726                 acl_rule["srcport_or_icmptype_last"] = port
727             else:
728                 acl_rule["srcport_or_icmptype_first"] = 0
729                 acl_rule["srcport_or_icmptype_last"] = 65535
730
731             groups = re.search(reg_ex_dport, rule)
732             if groups:
733                 port = int(groups.group(1).split(' ')[1])
734                 acl_rule["dstport_or_icmpcode_first"] = port
735                 acl_rule["dstport_or_icmpcode_last"] = port
736             else:
737                 acl_rule["dstport_or_icmpcode_first"] = 0
738                 acl_rule["dstport_or_icmpcode_last"] = 65535
739
740             groups = re.search(reg_ex_proto, rule)
741             if groups:
742                 proto = int(groups.group(1).split(' ')[1])
743                 acl_rule["proto"] = proto
744             else:
745                 acl_rule["proto"] = 0
746
747             acl_rules.append(acl_rule)
748
749         Classify._acl_add_replace(
750             node, acl_idx=acl_idx, rules=acl_rules, tag=tag)
751
752     @staticmethod
753     def add_macip_acl_multi_entries(node, rules=""):
754         """Add a new MACIP ACL.
755
756         :param node: VPP node to set MACIP ACL on.
757         :param rules: Required MACIP rules.
758         :type node: dict
759         :type rules: str
760         """
761         reg_ex_ip = re.compile(r'(ip [0-9a-fA-F.:/\d{1,2}]*)')
762         reg_ex_mac = re.compile(r'(mac \S\S:\S\S:\S\S:\S\S:\S\S:\S\S)')
763         reg_ex_mask = re.compile(r'(mask \S\S:\S\S:\S\S:\S\S:\S\S:\S\S)')
764
765         acl_rules = list()
766         for rule in rules.split(", "):
767             acl_rule = dict()
768             acl_rule["is_permit"] = 1 if "permit" in rule else 0
769             acl_rule["is_ipv6"] = 1 if "ipv6" in rule else 0
770
771             groups = re.search(reg_ex_mac, rule)
772             if groups:
773                 mac = groups.group(1).split(' ')[1].replace(':', '')
774                 acl_rule["src_mac"] = binascii.unhexlify(unicode(mac))
775
776             groups = re.search(reg_ex_mask, rule)
777             if groups:
778                 mask = groups.group(1).split(' ')[1].replace(':', '')
779                 acl_rule["src_mac_mask"] = binascii.unhexlify(unicode(mask))
780
781             groups = re.search(reg_ex_ip, rule)
782             if groups:
783                 grp = groups.group(1).split(' ')[1].split('/')
784                 acl_rule["src_ip_addr"] = ip_address(unicode(grp[0])).packed
785                 acl_rule["src_ip_prefix_len"] = int(grp[1])
786
787             acl_rules.append(acl_rule)
788
789         Classify._macip_acl_add(node=node, rules=acl_rules)
790
791     @staticmethod
792     def vpp_log_macip_acl_settings(node):
793         """Retrieve configured MACIP settings from the ACL plugin and write to
794         robot log.
795
796         :param node: VPP node.
797         :type node: dict
798         """
799         PapiSocketExecutor.dump_and_log(node, ["macip_acl_dump", ])
800
801     @staticmethod
802     def add_del_macip_acl_interface(node, interface, action, acl_idx):
803         """Apply/un-apply the MACIP ACL to/from a given interface.
804
805         :param node: VPP node to set MACIP ACL on.
806         :param interface: Interface name or sw_if_index.
807         :param action: Required action - add or del.
808         :param acl_idx: ACL index to be applied on the interface.
809         :type node: dict
810         :type interface: str or int
811         :type action: str
812         :type acl_idx: str or int
813         :raises RuntimeError: If unable to set MACIP ACL for the interface.
814         """
815         if isinstance(interface, basestring):
816             sw_if_index = Topology.get_interface_sw_index(node, interface)
817         else:
818             sw_if_index = interface
819
820         is_add = 1 if action == "add" else 0
821
822         cmd = 'macip_acl_interface_add_del'
823         err_msg = "Failed to get 'macip_acl_interface' on host {host}".format(
824             host=node['host'])
825         args = dict(
826             is_add=is_add,
827             sw_if_index=int(sw_if_index),
828             acl_index=int(acl_idx)
829         )
830         with PapiSocketExecutor(node) as papi_exec:
831             papi_exec.add(cmd, **args).get_reply(err_msg)
832
833     @staticmethod
834     def vpp_log_macip_acl_interface_assignment(node):
835         """Get interface list and associated MACIP ACLs and write to robot log.
836
837         :param node: VPP node.
838         :type node: dict
839         """
840         cmd = 'macip_acl_interface_get'
841         err_msg = "Failed to get 'macip_acl_interface' on host {host}".format(
842             host=node['host'])
843         with PapiSocketExecutor(node) as papi_exec:
844             reply = papi_exec.add(cmd).get_reply(err_msg)
845         logger.info(reply)