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