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