Add: Dot1Q + L2BD + GBP
[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 PapiSocketExecutor
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 PapiSocketExecutor(node) as papi_exec:
293             reply = papi_exec.add(cmd, **args).get_reply(err_msg)
294
295         return int(reply["new_table_index"]), int(reply["skip_n_vectors"]),\
296             int(reply["match_n_vectors"])
297
298     @staticmethod
299     def _classify_add_del_session(node, is_add, table_index, match,
300                                   opaque_index=0xFFFFFFFF,
301                                   hit_next_index=0xFFFFFFFF, advance=0,
302                                   action=0, metadata=0):
303         """Add or delete a classify session.
304
305         :param node: VPP node to create classify session.
306         :param is_add: If 1 the session is added, if 0 the session is deleted.
307         :param table_index: Index of the table to add/del the session.
308         :param match: For add, match value for session, required, needs to
309             include bytes in front with length of skip_n_vectors of target table
310             times sizeof (u32x4) (values of those bytes will be ignored).
311         :param opaque_index: For add, opaque_index of new session.
312             (Default value = 0xFFFFFFFF)
313         :param hit_next_index: For add, hit_next_index of new session.
314             (Default value = 0xFFFFFFFF)
315         :param advance: For add, advance value for session. (Default value = 0)
316         :param action: 0: No action (by default) metadata is not used.
317             1: Classified IP packets will be looked up from the specified ipv4
318                fib table (configured by metadata as VRF id).
319                Only valid for L3 input ACL node
320             2: Classified IP packets will be looked up from the specified ipv6
321                fib table (configured by metadata as VRF id).
322                Only valid for L3 input ACL node
323             3: Classified packet will be steered to source routig policy of
324                given index (in metadata).
325                This is only valid for IPv6 packets redirected to a source
326                routing node.
327         :param metadata: Valid only if action != 0
328             VRF id if action is 1 or 2. SR policy index if action is 3.
329             (Default value = 0)
330         :type node: dict
331         :type is_add: int
332         :type table_index: int
333         :type match: str
334         :type opaque_index: int
335         :type hit_next_index: int
336         :type advance: int
337         :type action: int
338         :type metadata: int
339         """
340
341         match_len = ((len(match) - 1) / 16 + 1) * 16
342         match = match + '\0' * (match_len - len(match))
343         args = dict(
344             is_add=is_add,
345             table_index=table_index,
346             hit_next_index=hit_next_index,
347             opaque_index=opaque_index,
348             advance=advance,
349             action=action,
350             metadata=metadata,
351             match_len=match_len,
352             match=match
353         )
354         cmd = 'classify_add_del_session'
355         err_msg = "Failed to create a classify session on host {host}".format(
356             host=node['host'])
357
358         with PapiSocketExecutor(node) as papi_exec:
359             papi_exec.add(cmd, **args).get_reply(err_msg)
360
361     @staticmethod
362     def _macip_acl_add(node, rules, tag=""):
363         """Add MACIP ACL.
364
365         :param node: VPP node to add MACIP ACL.
366         :param rules: List of rules for given ACL.
367         :param tag: ACL tag.
368         :type node: dict
369         :type rules: list
370         :type tag: str
371         """
372         cmd = "macip_acl_add"
373         args = dict(
374             r=rules,
375             count=len(rules),
376             tag=tag
377         )
378
379         err_msg = "Failed to create a classify session on host {host}".format(
380             host=node['host'])
381
382         with PapiSocketExecutor(node) as papi_exec:
383             papi_exec.add(cmd, **args).get_reply(err_msg)
384
385     @staticmethod
386     def _acl_interface_set_acl_list(node, sw_if_index, acl_type, acls):
387         """Set ACL list for interface.
388
389         :param node: VPP node to set ACL list for interface.
390         :param sw_if_index: sw_if_index of the used interface.
391         :param acl_type: Type of ACL(s) - input or output.
392         :param acls: List of ACLs.
393         :type node: dict
394         :type sw_if_index: int
395         :type acl_type: str
396         :type acls: list
397         """
398         cmd = "acl_interface_set_acl_list"
399         n_input = len(acls) if acl_type == "input" else 0
400         args = dict(
401             sw_if_index=sw_if_index,
402             acls=acls,
403             n_input=n_input,
404             count=len(acls)
405         )
406
407         err_msg = "Failed to set acl list for interface {idx} on host {host}".\
408             format(idx=sw_if_index, host=node['host'])
409
410         with PapiSocketExecutor(node) as papi_exec:
411             papi_exec.add(cmd, **args).get_reply(err_msg)
412
413     @staticmethod
414     def _acl_add_replace(node, acl_idx, rules, tag=""):
415         """ Add/replace ACLs.
416
417         :param node: VPP node to add MACIP ACL.
418         :param acl_idx: ACL index.
419         :param rules: List of rules for given ACL.
420         :param tag: ACL tag.
421         :type node: dict
422         :type acl_idx: int
423         :type rules: list
424         :type tag: str
425         """
426         cmd = "acl_add_replace"
427         args = dict(
428             tag=tag.encode("utf-8"),
429             acl_index=4294967295 if acl_idx is None else acl_idx,
430             count=len(rules),
431             r=rules
432         )
433
434         err_msg = "Failed to add/replace acls on host {host}".format(
435             host=node['host'])
436
437         with PapiSocketExecutor(node) as papi_exec:
438             papi_exec.add(cmd, **args).get_reply(err_msg)
439
440     @staticmethod
441     def vpp_creates_classify_table_l3(node, ip_version, direction, ip_addr):
442         """Create classify table for IP address filtering.
443
444         :param node: VPP node to create classify table.
445         :param ip_version: Version of IP protocol.
446         :param direction: Direction of traffic - src/dst.
447         :param ip_addr: IPv4 or Ipv6 (depending on the parameter 'ip_version')
448             address.
449         :type node: dict
450         :type ip_version: str
451         :type direction: str
452         :type ip_addr: str
453         :returns: (table_index, skip_n, match_n)
454             table_index: Classify table index.
455             skip_n: Number of skip vectors.
456             match_n: Number of match vectors.
457         :rtype: tuple(int, int, int)
458         :raises ValueError: If the parameters 'ip_version' or 'direction' have
459             incorrect values.
460         """
461         mask_f = dict(
462             ip4=Classify._build_ip_mask,
463             ip6=Classify._build_ip6_mask
464         )
465         if ip_version == "ip4" or ip_version == "ip6":
466             ip_addr = binascii.hexlify(ip_address(unicode(ip_addr)).packed)
467         else:
468             raise ValueError("IP version {ver} is not supported.".
469                              format(ver=ip_version))
470
471         if direction == "src":
472             mask = mask_f[ip_version](src_ip=ip_addr)
473         elif direction == "dst":
474             mask = mask_f[ip_version](dst_ip=ip_addr)
475         else:
476             raise ValueError("Direction {dir} is not supported.".
477                              format(dir=direction))
478
479         return Classify._classify_add_del_table(
480             node,
481             is_add=1,
482             mask=binascii.unhexlify(mask),
483             match_n_vectors=(len(mask) - 1) // 32 + 1
484         )
485
486     @staticmethod
487     def vpp_creates_classify_table_l2(node, direction, mac=""):
488         """Create classify table for MAC address filtering.
489
490         :param node: VPP node to create classify table.
491         :param direction: Direction of traffic - src/dst.
492         :param mac: Source or destination (depending on the parameter
493             'direction') MAC address.
494         :type node: dict
495         :type direction: str
496         :type mac: str
497         :returns: (table_index, skip_n, match_n)
498             table_index: Classify table index.
499             skip_n: Number of skip vectors.
500             match_n: Number of match vectors.
501         :rtype: tuple(int, int, int)
502         :raises ValueError: If the parameter 'direction' has incorrect value.
503         """
504         if direction == "src":
505             mask = Classify._build_mac_mask(src_mac=mac)
506         elif direction == "dst":
507             mask = Classify._build_mac_mask(dst_mac=mac)
508         else:
509             raise ValueError("Direction {dir} is not supported.".
510                              format(dir=direction))
511
512         return Classify._classify_add_del_table(
513             node,
514             is_add=1,
515             mask=binascii.unhexlify(mask),
516             match_n_vectors=(len(mask) - 1) // 32 + 1
517         )
518
519     @staticmethod
520     def vpp_creates_classify_table_hex(node, hex_mask):
521         """Create classify table with hex mask.
522
523         :param node: VPP node to create classify table based on hex mask.
524         :param hex_mask: Classify hex mask.
525         :type node: dict
526         :type hex_mask: str
527         :returns: (table_index, skip_n, match_n)
528             table_index: Classify table index.
529             skip_n: Number of skip vectors.
530             match_n: Number of match vectors.
531         :rtype: tuple(int, int, int)
532         """
533         return Classify._classify_add_del_table(
534             node,
535             is_add=1,
536             mask=binascii.unhexlify(hex_mask),
537             match_n_vectors=(len(hex_mask) - 1) // 32 + 1
538         )
539
540     @staticmethod
541     def vpp_configures_classify_session_l3(node, acl_method, table_index,
542                                            ip_version, direction, address):
543         """Configuration of classify session for IP address filtering.
544
545         :param node: VPP node to setup classify session.
546         :param acl_method: ACL method - deny/permit.
547         :param table_index: Classify table index.
548         :param ip_version: Version of IP protocol.
549         :param direction: Direction of traffic - src/dst.
550         :param address: IPv4 or IPv6 address.
551         :type node: dict
552         :type acl_method: str
553         :type table_index: int
554         :type ip_version: str
555         :type direction: str
556         :type address: str
557         :raises ValueError: If the parameter 'direction' has incorrect value.
558         """
559         match_f = dict(
560             ip4=Classify._build_ip_match,
561             ip6=Classify._build_ip6_match
562         )
563         if direction == "src":
564             match = match_f[ip_version](src_ip=address)
565         elif direction == "dst":
566             match = match_f[ip_version](dst_ip=address)
567         else:
568             raise ValueError("Direction {dir} is not supported.".
569                              format(dir=direction))
570         action = dict(
571             permit=0,
572             deny=1
573         )
574         Classify._classify_add_del_session(
575             node,
576             is_add=1,
577             table_index=table_index,
578             match=binascii.unhexlify(match),
579             action=action[acl_method])
580
581     @staticmethod
582     def vpp_configures_classify_session_l2(node, acl_method, table_index,
583                                            direction, address):
584         """Configuration of classify session for MAC address filtering.
585
586         :param node: VPP node to setup classify session.
587         :param acl_method: ACL method - deny/permit.
588         :param table_index: Classify table index.
589         :param direction: Direction of traffic - src/dst.
590         :param address: MAC address.
591         :type node: dict
592         :type acl_method: str
593         :type table_index: int
594         :type direction: str
595         :type address: str
596         :raises ValueError: If the parameter 'direction' has incorrect value.
597         """
598         if direction == "src":
599             match = Classify._build_mac_match(src_mac=address)
600         elif direction == "dst":
601             match = Classify._build_mac_match(dst_mac=address)
602         else:
603             raise ValueError("Direction {dir} is not supported.".
604                              format(dir=direction))
605         action = dict(
606             permit=0,
607             deny=1
608         )
609         Classify._classify_add_del_session(
610             node,
611             is_add=1,
612             table_index=table_index,
613             match=binascii.unhexlify(match),
614             action=action[acl_method])
615
616     @staticmethod
617     def vpp_configures_classify_session_hex(node, acl_method, table_index,
618                                             hex_value):
619         """Configuration of classify session with hex value.
620
621         :param node: VPP node to setup classify session.
622         :param acl_method: ACL method - deny/permit.
623         :param table_index: Classify table index.
624         :param hex_value: Classify hex value.
625         :type node: dict
626         :type acl_method: str
627         :type table_index: int
628         :type hex_value: str
629         """
630         action = dict(
631             permit=0,
632             deny=1
633         )
634         Classify._classify_add_del_session(
635             node,
636             is_add=1,
637             table_index=table_index,
638             match=binascii.unhexlify(hex_value),
639             action=action[acl_method])
640
641     @staticmethod
642     def compute_classify_hex_mask(ip_version, protocol, direction):
643         """Compute classify hex mask for TCP or UDP packet matching.
644
645         :param ip_version: Version of IP protocol.
646         :param protocol: Type of protocol.
647         :param direction: Traffic direction.
648         :type ip_version: str
649         :type protocol: str
650         :type direction: str
651         :returns: Classify hex mask.
652         :rtype: str
653         :raises ValueError: If protocol is not TCP or UDP.
654         :raises ValueError: If direction is not source or destination or
655             source + destination.
656         """
657         if protocol in ('TCP', 'UDP'):
658             base_mask = Classify._compute_base_mask(ip_version)
659
660             if direction == 'source':
661                 return base_mask + 'FFFF0000'
662             elif direction == 'destination':
663                 return base_mask + '0000FFFF'
664             elif direction == 'source + destination':
665                 return base_mask + 'FFFFFFFF'
666             else:
667                 raise ValueError("Invalid direction!")
668         else:
669             raise ValueError("Invalid protocol!")
670
671     @staticmethod
672     def compute_classify_hex_value(hex_mask, source_port, destination_port):
673         """Compute classify hex value for TCP or UDP packet matching.
674
675         :param hex_mask: Classify hex mask.
676         :param source_port: Source TCP/UDP port.
677         :param destination_port: Destination TCP/UDP port.
678         :type hex_mask: str
679         :type source_port: str
680         :type destination_port: str
681         :returns: Classify hex value.
682         :rtype: str
683         """
684         source_port_hex = Classify._port_convert(source_port)
685         destination_port_hex = Classify._port_convert(destination_port)
686
687         return hex_mask[:-8] + source_port_hex + destination_port_hex
688
689     @staticmethod
690     def _port_convert(port):
691         """Convert port number for classify hex table format.
692
693         :param port: TCP/UDP port number.
694         :type port: str
695         :returns: TCP/UDP port number in 4-digit hexadecimal format.
696         :rtype: str
697         """
698         return '{0:04x}'.format(int(port))
699
700     @staticmethod
701     def _compute_base_mask(ip_version):
702         """Compute base classify hex mask based on IP version.
703
704         :param ip_version: Version of IP protocol.
705         :type ip_version: str
706         :returns: Base hex mask.
707         :rtype: str
708         """
709         if ip_version == 'ip4':
710             return 68 * '0'
711             # base value of classify hex table for IPv4 TCP/UDP ports
712         elif ip_version == 'ip6':
713             return 108 * '0'
714             # base value of classify hex table for IPv6 TCP/UDP ports
715         else:
716             raise ValueError("Invalid IP version!")
717
718     @staticmethod
719     def get_classify_table_data(node, table_index):
720         """Retrieve settings for classify table by ID.
721
722         :param node: VPP node to retrieve classify data from.
723         :param table_index: Index of a specific classify table.
724         :type node: dict
725         :type table_index: int
726         :returns: Classify table settings.
727         :rtype: dict
728         """
729         cmd = 'classify_table_info'
730         err_msg = "Failed to get 'classify_table_info' on host {host}".format(
731             host=node['host'])
732         args = dict(
733             table_id=int(table_index)
734         )
735         with PapiSocketExecutor(node) as papi_exec:
736             reply = papi_exec.add(cmd, **args).get_reply(err_msg)
737         return reply
738
739     @staticmethod
740     def get_classify_session_data(node, table_index):
741         """Retrieve settings for all classify sessions in a table.
742
743         :param node: VPP node to retrieve classify data from.
744         :param table_index: Index of a classify table.
745         :type node: dict
746         :type table_index: int
747         :returns: List of classify session settings.
748         :rtype: list or dict
749         """
750         cmd = "classify_session_dump"
751         args = dict(
752             table_id=int(table_index)
753         )
754         with PapiSocketExecutor(node) as papi_exec:
755             details = papi_exec.add(cmd, **args).get_details()
756
757         return details
758
759     @staticmethod
760     def vpp_log_plugin_acl_settings(node):
761         """Retrieve configured settings from the ACL plugin and write to robot
762         log.
763
764         :param node: VPP node.
765         :type node: dict
766         """
767         PapiSocketExecutor.dump_and_log(node, ["acl_dump", ])
768
769     @staticmethod
770     def vpp_log_plugin_acl_interface_assignment(node):
771         """Retrieve interface assignment from the ACL plugin and write to robot
772         log.
773
774         :param node: VPP node.
775         :type node: dict
776         """
777         PapiSocketExecutor.dump_and_log(node, ["acl_interface_list_dump", ])
778
779     @staticmethod
780     def set_acl_list_for_interface(node, interface, acl_type, acl_idx=None):
781         """Set the list of input or output ACLs applied to the interface. It
782         unapplies any previously applied ACLs.
783
784         :param node: VPP node to set ACL on.
785         :param interface: Interface name or sw_if_index.
786         :param acl_type: Type of ACL(s) - input or output.
787         :param acl_idx: Index(ies) of ACLs to be applied on the interface.
788         :type node: dict
789         :type interface: str or int
790         :type acl_type: str
791         :type acl_idx: list
792         """
793         if isinstance(interface, basestring):
794             sw_if_index = Topology.get_interface_sw_index(node, interface)
795         else:
796             sw_if_index = int(interface)
797
798         acls = acl_idx if isinstance(acl_idx, list) else list()
799
800         Classify._acl_interface_set_acl_list(node=node,
801                                              sw_if_index=sw_if_index,
802                                              acl_type=acl_type,
803                                              acls=acls)
804
805     @staticmethod
806     def add_replace_acl_multi_entries(node, acl_idx=None, rules=None, tag=""):
807         """Add a new ACL or replace the existing one. To replace an existing
808         ACL, pass the ID of this ACL.
809
810         :param node: VPP node to set ACL on.
811         :param acl_idx: ID of ACL. (Optional)
812         :param rules: Required rules. (Optional)
813         :param tag: ACL tag (Optional).
814         :type node: dict
815         :type acl_idx: int
816         :type rules: str
817         :type tag: str
818         """
819         reg_ex_src_ip = re.compile(r'(src [0-9a-fA-F.:/\d{1,2}]*)')
820         reg_ex_dst_ip = re.compile(r'(dst [0-9a-fA-F.:/\d{1,2}]*)')
821         reg_ex_sport = re.compile(r'(sport \d{1,5})')
822         reg_ex_dport = re.compile(r'(dport \d{1,5})')
823         reg_ex_proto = re.compile(r'(proto \d{1,5})')
824
825         acl_rules = list()
826         for rule in rules.split(", "):
827             acl_rule = dict()
828             acl_rule["is_permit"] = 1 if "permit" in rule else 0
829             acl_rule["is_ipv6"] = 1 if "ipv6" in rule else 0
830
831             groups = re.search(reg_ex_src_ip, rule)
832             if groups:
833                 grp = groups.group(1).split(' ')[1].split('/')
834                 acl_rule["src_ip_addr"] = ip_address(unicode(grp[0])).packed
835                 acl_rule["src_ip_prefix_len"] = int(grp[1])
836
837             groups = re.search(reg_ex_dst_ip, rule)
838             if groups:
839                 grp = groups.group(1).split(' ')[1].split('/')
840                 acl_rule["dst_ip_addr"] = ip_address(unicode(grp[0])).packed
841                 acl_rule["dst_ip_prefix_len"] = int(grp[1])
842
843             groups = re.search(reg_ex_sport, rule)
844             if groups:
845                 port = int(groups.group(1).split(' ')[1])
846                 acl_rule["srcport_or_icmptype_first"] = port
847                 acl_rule["srcport_or_icmptype_last"] = port
848             else:
849                 acl_rule["srcport_or_icmptype_first"] = 0
850                 acl_rule["srcport_or_icmptype_last"] = 65535
851
852             groups = re.search(reg_ex_dport, rule)
853             if groups:
854                 port = int(groups.group(1).split(' ')[1])
855                 acl_rule["dstport_or_icmpcode_first"] = port
856                 acl_rule["dstport_or_icmpcode_last"] = port
857             else:
858                 acl_rule["dstport_or_icmpcode_first"] = 0
859                 acl_rule["dstport_or_icmpcode_last"] = 65535
860
861             groups = re.search(reg_ex_proto, rule)
862             if groups:
863                 proto = int(groups.group(1).split(' ')[1])
864                 acl_rule["proto"] = proto
865             else:
866                 acl_rule["proto"] = 0
867
868             acl_rules.append(acl_rule)
869
870         Classify._acl_add_replace(
871             node, acl_idx=acl_idx, rules=acl_rules, tag=tag)
872
873     @staticmethod
874     def add_macip_acl_multi_entries(node, rules=""):
875         """Add a new MACIP ACL.
876
877         :param node: VPP node to set MACIP ACL on.
878         :param rules: Required MACIP rules.
879         :type node: dict
880         :type rules: str
881         """
882         reg_ex_ip = re.compile(r'(ip [0-9a-fA-F.:/\d{1,2}]*)')
883         reg_ex_mac = re.compile(r'(mac \S\S:\S\S:\S\S:\S\S:\S\S:\S\S)')
884         reg_ex_mask = re.compile(r'(mask \S\S:\S\S:\S\S:\S\S:\S\S:\S\S)')
885
886         acl_rules = list()
887         for rule in rules.split(", "):
888             acl_rule = dict()
889             acl_rule["is_permit"] = 1 if "permit" in rule else 0
890             acl_rule["is_ipv6"] = 1 if "ipv6" in rule else 0
891
892             groups = re.search(reg_ex_mac, rule)
893             if groups:
894                 mac = groups.group(1).split(' ')[1].replace(':', '')
895                 acl_rule["src_mac"] = unicode(mac)
896
897             groups = re.search(reg_ex_mask, rule)
898             if groups:
899                 mask = groups.group(1).split(' ')[1].replace(':', '')
900                 acl_rule["src_mac_mask"] = unicode(mask)
901
902             groups = re.search(reg_ex_ip, rule)
903             if groups:
904                 grp = groups.group(1).split(' ')[1].split('/')
905                 acl_rule["src_ip_addr"] = ip_address(unicode(grp[0])).packed
906                 acl_rule["src_ip_prefix_len"] = int(grp[1])
907
908             acl_rules.append(acl_rule)
909
910         Classify._macip_acl_add(node=node, rules=acl_rules)
911
912     @staticmethod
913     def vpp_log_macip_acl_settings(node):
914         """Retrieve configured MACIP settings from the ACL plugin and write to
915         robot log.
916
917         :param node: VPP node.
918         :type node: dict
919         """
920         PapiSocketExecutor.dump_and_log(node, ["macip_acl_dump", ])
921
922     @staticmethod
923     def add_del_macip_acl_interface(node, interface, action, acl_idx):
924         """Apply/un-apply the MACIP ACL to/from a given interface.
925
926         :param node: VPP node to set MACIP ACL on.
927         :param interface: Interface name or sw_if_index.
928         :param action: Required action - add or del.
929         :param acl_idx: ACL index to be applied on the interface.
930         :type node: dict
931         :type interface: str or int
932         :type action: str
933         :type acl_idx: str or int
934         :raises RuntimeError: If unable to set MACIP ACL for the interface.
935         """
936         if isinstance(interface, basestring):
937             sw_if_index = Topology.get_interface_sw_index(node, interface)
938         else:
939             sw_if_index = interface
940
941         is_add = 1 if action == "add" else 0
942
943         cmd = 'macip_acl_interface_add_del'
944         err_msg = "Failed to get 'macip_acl_interface' on host {host}".format(
945             host=node['host'])
946         args = dict(
947             is_add=is_add,
948             sw_if_index=int(sw_if_index),
949             acl_index=int(acl_idx)
950         )
951         with PapiSocketExecutor(node) as papi_exec:
952             papi_exec.add(cmd, **args).get_reply(err_msg)
953
954     @staticmethod
955     def vpp_log_macip_acl_interface_assignment(node):
956         """Get interface list and associated MACIP ACLs and write to robot log.
957
958         :param node: VPP node.
959         :type node: dict
960         """
961         cmd = 'macip_acl_interface_get'
962         err_msg = "Failed to get 'macip_acl_interface' on host {host}".format(
963             host=node['host'])
964         with PapiSocketExecutor(node) as papi_exec:
965             reply = papi_exec.add(cmd).get_reply(err_msg)
966         logger.info(reply)