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