CSIT-1597 API cleanup: classify
[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 True the table is added, if False 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: bool
198         :type mask: str
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             del_chain=False,
218             table_index=table_index,
219             nbuckets=nbuckets,
220             memory_size=memory_size,
221             skip_n_vectors=skip_n_vectors,
222             match_n_vectors=match_n_vectors,
223             next_table_index=next_table_index,
224             miss_next_index=miss_next_index,
225             current_data_flag=current_data_flag,
226             current_data_offset=current_data_offset,
227             mask_len=len(mask),
228             mask=mask
229         )
230         err_msg = f"Failed to create a classify table on host {node[u'host']}"
231
232         with PapiSocketExecutor(node) as papi_exec:
233             reply = papi_exec.add(cmd, **args).get_reply(err_msg)
234
235         return int(reply[u"new_table_index"]), int(reply[u"skip_n_vectors"]),\
236             int(reply[u"match_n_vectors"])
237
238     @staticmethod
239     def _classify_add_del_session(
240             node, is_add, table_index, match,
241             opaque_index=Constants.BITWISE_NON_ZERO,
242             hit_next_index=Constants.BITWISE_NON_ZERO, advance=0,
243             action=0, metadata=0):
244         """Add or delete a classify session.
245
246         :param node: VPP node to create classify session.
247         :param is_add: If True the session is added, if False the session
248             is deleted.
249         :param table_index: Index of the table to add/del the session.
250         :param match: For add, match value for session, required, needs to
251             include bytes in front with length of skip_n_vectors of target table
252             times sizeof (u32x4) (values of those bytes will be ignored).
253         :param opaque_index: For add, opaque_index of new session.
254             (Default value = ~0)
255         :param hit_next_index: For add, hit_next_index of new session.
256             (Default value = ~0)
257         :param advance: For add, advance value for session. (Default value = 0)
258         :param action: 0: No action (by default) metadata is not used.
259             1: Classified IP packets will be looked up from the specified ipv4
260                fib table (configured by metadata as VRF id).
261                Only valid for L3 input ACL node
262             2: Classified IP packets will be looked up from the specified ipv6
263                fib table (configured by metadata as VRF id).
264                Only valid for L3 input ACL node
265             3: Classified packet will be steered to source routing policy of
266                given index (in metadata).
267                This is only valid for IPv6 packets redirected to a source
268                routing node.
269         :param metadata: Valid only if action != 0. VRF id if action is 1 or 2.
270             SR policy index if action is 3. (Default value = 0)
271         :type node: dict
272         :type is_add: bool
273         :type table_index: int
274         :type match: bytes
275         :type opaque_index: int
276         :type hit_next_index: int
277         :type advance: int
278         :type action: int
279         :type metadata: int
280         """
281         cmd = u"classify_add_del_session"
282         args = dict(
283             is_add=is_add,
284             table_index=table_index,
285             hit_next_index=hit_next_index,
286             opaque_index=opaque_index,
287             advance=advance,
288             action=action,
289             metadata=metadata,
290             match_len=len(match),
291             match=match
292         )
293         err_msg = f"Failed to create a classify session on host {node[u'host']}"
294
295         with PapiSocketExecutor(node) as papi_exec:
296             papi_exec.add(cmd, **args).get_reply(err_msg)
297
298     @staticmethod
299     def _macip_acl_add(node, rules, tag=""):
300         """Add MACIP ACL.
301
302         :param node: VPP node to add MACIP ACL.
303         :param rules: List of rules for given ACL.
304         :param tag: ACL tag.
305         :type node: dict
306         :type rules: list
307         :type tag: str
308         """
309         cmd = u"macip_acl_add"
310         args = dict(
311             r=rules,
312             count=len(rules),
313             tag=tag
314         )
315
316         err_msg = f"Failed to add MACIP ACL on host {node[u'host']}"
317
318         with PapiSocketExecutor(node) as papi_exec:
319             papi_exec.add(cmd, **args).get_reply(err_msg)
320
321     @staticmethod
322     def _acl_interface_set_acl_list(node, sw_if_index, acl_type, acls):
323         """Set ACL list for interface.
324
325         :param node: VPP node to set ACL list for interface.
326         :param sw_if_index: sw_if_index of the used interface.
327         :param acl_type: Type of ACL(s) - input or output.
328         :param acls: List of ACLs.
329         :type node: dict
330         :type sw_if_index: int
331         :type acl_type: str
332         :type acls: list
333         """
334         cmd = u"acl_interface_set_acl_list"
335         n_input = len(acls) if acl_type == u"input" else 0
336         args = dict(
337             sw_if_index=sw_if_index,
338             acls=acls,
339             n_input=n_input,
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.encode("utf-8"),
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         if isinstance(interface, str):
610             sw_if_index = Topology.get_interface_sw_index(node, interface)
611         else:
612             sw_if_index = int(interface)
613
614         acls = acl_idx if isinstance(acl_idx, list) else list()
615
616         Classify._acl_interface_set_acl_list(
617             node=node, sw_if_index=sw_if_index, acl_type=acl_type, acls=acls
618         )
619
620     @staticmethod
621     def add_replace_acl_multi_entries(node, acl_idx=None, rules=None, tag=u""):
622         """Add a new ACL or replace the existing one. To replace an existing
623         ACL, pass the ID of this ACL.
624
625         :param node: VPP node to set ACL on.
626         :param acl_idx: ID of ACL. (Optional)
627         :param rules: Required rules. (Optional)
628         :param tag: ACL tag (Optional).
629         :type node: dict
630         :type acl_idx: int
631         :type rules: str
632         :type tag: str
633         """
634         reg_ex_src_ip = re.compile(r"(src [0-9a-fA-F.:/\d{1,2}]*)")
635         reg_ex_dst_ip = re.compile(r"(dst [0-9a-fA-F.:/\d{1,2}]*)")
636         reg_ex_sport = re.compile(r"(sport \d{1,5})")
637         reg_ex_dport = re.compile(r"(dport \d{1,5})")
638         reg_ex_proto = re.compile(r"(proto \d{1,5})")
639
640         acl_rules = list()
641         for rule in rules.split(u", "):
642             acl_rule = dict()
643             acl_rule[u"is_permit"] = 1 if u"permit" in rule else 0
644             acl_rule[u"is_ipv6"] = 1 if u"ipv6" in rule else 0
645
646             groups = re.search(reg_ex_src_ip, rule)
647             if groups:
648                 grp = groups.group(1).split(u" ")[1].split(u"/")
649                 acl_rule[u"src_ip_addr"] = ip_address(grp[0]).packed
650                 acl_rule[u"src_ip_prefix_len"] = int(grp[1])
651
652             groups = re.search(reg_ex_dst_ip, rule)
653             if groups:
654                 grp = groups.group(1).split(u" ")[1].split(u"/")
655                 acl_rule[u"dst_ip_addr"] = ip_address(grp[0]).packed
656                 acl_rule[u"dst_ip_prefix_len"] = int(grp[1])
657
658             groups = re.search(reg_ex_sport, rule)
659             if groups:
660                 port = int(groups.group(1).split(u" ")[1])
661                 acl_rule[u"srcport_or_icmptype_first"] = port
662                 acl_rule[u"srcport_or_icmptype_last"] = port
663             else:
664                 acl_rule[u"srcport_or_icmptype_first"] = 0
665                 acl_rule[u"srcport_or_icmptype_last"] = 65535
666
667             groups = re.search(reg_ex_dport, rule)
668             if groups:
669                 port = int(groups.group(1).split(u" ")[1])
670                 acl_rule[u"dstport_or_icmpcode_first"] = port
671                 acl_rule[u"dstport_or_icmpcode_last"] = port
672             else:
673                 acl_rule[u"dstport_or_icmpcode_first"] = 0
674                 acl_rule[u"dstport_or_icmpcode_last"] = 65535
675
676             groups = re.search(reg_ex_proto, rule)
677             if groups:
678                 proto = int(groups.group(1).split(' ')[1])
679                 acl_rule[u"proto"] = proto
680             else:
681                 acl_rule[u"proto"] = 0
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     @staticmethod
689     def add_macip_acl_multi_entries(node, rules=u""):
690         """Add a new MACIP ACL.
691
692         :param node: VPP node to set MACIP ACL on.
693         :param rules: Required MACIP rules.
694         :type node: dict
695         :type rules: str
696         """
697         reg_ex_ip = re.compile(r"(ip [0-9a-fA-F.:/\d{1,2}]*)")
698         reg_ex_mac = re.compile(r"(mac \S\S:\S\S:\S\S:\S\S:\S\S:\S\S)")
699         reg_ex_mask = re.compile(r"(mask \S\S:\S\S:\S\S:\S\S:\S\S:\S\S)")
700
701         acl_rules = list()
702         for rule in rules.split(u", "):
703             acl_rule = dict()
704             acl_rule[u"is_permit"] = 1 if u"permit" in rule else 0
705             acl_rule[u"is_ipv6"] = 1 if u"ipv6" in rule else 0
706
707             groups = re.search(reg_ex_mac, rule)
708             if groups:
709                 mac = groups.group(1).split(u" ")[1].replace(u":", u"")
710                 acl_rule[u"src_mac"] = bytes.fromhex(mac)
711
712             groups = re.search(reg_ex_mask, rule)
713             if groups:
714                 mask = groups.group(1).split(u" ")[1].replace(u":", u"")
715                 acl_rule[u"src_mac_mask"] = bytes.fromhex(mask)
716
717             groups = re.search(reg_ex_ip, rule)
718             if groups:
719                 grp = groups.group(1).split(u" ")[1].split(u"/")
720                 acl_rule[u"src_ip_addr"] = ip_address((grp[0])).packed
721                 acl_rule[u"src_ip_prefix_len"] = int(grp[1])
722
723             acl_rules.append(acl_rule)
724
725         Classify._macip_acl_add(node=node, rules=acl_rules)
726
727     @staticmethod
728     def vpp_log_macip_acl_settings(node):
729         """Retrieve configured MACIP settings from the ACL plugin and write to
730         robot log.
731
732         :param node: VPP node.
733         :type node: dict
734         """
735         PapiSocketExecutor.dump_and_log(node, [u"macip_acl_dump", ])
736
737     @staticmethod
738     def add_del_macip_acl_interface(node, interface, action, acl_idx):
739         """Apply/un-apply the MACIP ACL to/from a given interface.
740
741         :param node: VPP node to set MACIP ACL on.
742         :param interface: Interface name or sw_if_index.
743         :param action: Required action - add or del.
744         :param acl_idx: ACL index to be applied on the interface.
745         :type node: dict
746         :type interface: str or int
747         :type action: str
748         :type acl_idx: str or int
749         :raises RuntimeError: If unable to set MACIP ACL for the interface.
750         """
751         if isinstance(interface, str):
752             sw_if_index = Topology.get_interface_sw_index(node, interface)
753         else:
754             sw_if_index = interface
755
756         is_add = 1 if action == u"add" else 0
757
758         cmd = u"macip_acl_interface_add_del"
759         err_msg = f"Failed to get 'macip_acl_interface' on host {node[u'host']}"
760         args = dict(
761             is_add=is_add,
762             sw_if_index=int(sw_if_index),
763             acl_index=int(acl_idx)
764         )
765         with PapiSocketExecutor(node) as papi_exec:
766             papi_exec.add(cmd, **args).get_reply(err_msg)
767
768     @staticmethod
769     def vpp_log_macip_acl_interface_assignment(node):
770         """Get interface list and associated MACIP ACLs and write to robot log.
771
772         :param node: VPP node.
773         :type node: dict
774         """
775         cmd = u"macip_acl_interface_get"
776         err_msg = f"Failed to get 'macip_acl_interface' on host {node[u'host']}"
777         with PapiSocketExecutor(node) as papi_exec:
778             reply = papi_exec.add(cmd).get_reply(err_msg)
779         logger.info(reply)