23293b6dc668e4db1d06e31cdc17f9ee913a28d0
[csit.git] / resources / libraries / python / FlowUtil.py
1 # copyright (c) 2022 Intel 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 """Flow Utilities Library."""
15
16 from enum import IntEnum
17 from ipaddress import ip_address
18
19 from resources.libraries.python.topology import Topology
20 from resources.libraries.python.ssh import exec_cmd_no_error
21 from resources.libraries.python.PapiExecutor import PapiSocketExecutor
22
23 class FlowType(IntEnum):
24     """Flow types."""
25     FLOW_TYPE_ETHERNET = 1
26     FLOW_TYPE_IP4 = 2
27     FLOW_TYPE_IP6 = 3
28     FLOW_TYPE_IP4_L2TPV3OIP = 4
29     FLOW_TYPE_IP4_IPSEC_ESP = 5
30     FLOW_TYPE_IP4_IPSEC_AH = 6
31     FLOW_TYPE_IP4_N_TUPLE = 7
32     FLOW_TYPE_IP6_N_TUPLE = 8
33     FLOW_TYPE_IP4_VXLAN = 11
34     FLOW_TYPE_IP6_VXLAN = 12
35     FLOW_TYPE_IP4_GTPU = 14
36
37 class FlowProto(IntEnum):
38     """Flow protocols."""
39     IP_API_PROTO_TCP = 6
40     IP_API_PROTO_UDP = 17
41     IP_API_PROTO_ESP = 50
42     IP_API_PROTO_AH = 51
43     IP_API_PROTO_L2TP = 115
44
45 class FlowAction(IntEnum):
46     """Flow actions."""
47     FLOW_ACTION_MARK = 2
48     FLOW_ACTION_REDIRECT_TO_QUEUE = 16
49     FLOW_ACTION_DROP = 64
50
51 class FlowUtil:
52     """Utilities for flow configuration."""
53
54     @staticmethod
55     def vpp_create_ip4_n_tuple_flow(
56             node, src_ip, dst_ip, src_port, dst_port,
57             proto, action, value=0):
58         """Create IP4_N_TUPLE flow.
59
60         :param node: DUT node.
61         :param src_ip: Source IP4 address.
62         :param dst_ip: Destination IP4 address.
63         :param src_port: Source port.
64         :param dst_port: Destination port.
65         :param proto: TCP or UDP.
66         :param action: Mark, drop or redirect-to-queue.
67         :param value: Action value.
68
69         :type node: dict
70         :type src_ip: str
71         :type dst_ip: str
72         :type src_port: int
73         :type dst_port: int
74         :type proto: str
75         :type action: str
76         :type value: int
77         :returns: flow_index.
78         :rtype: int
79         """
80         flow = u"ip4_n_tuple"
81         flow_type = FlowType.FLOW_TYPE_IP4_N_TUPLE
82
83         if proto == u"TCP":
84             flow_proto = FlowProto.IP_API_PROTO_TCP
85         elif proto == u"UDP":
86             flow_proto = FlowProto.IP_API_PROTO_UDP
87         else:
88             raise ValueError(f"proto error: {proto}")
89
90         pattern = {
91             u'src_addr': {u'addr': src_ip, u'mask': u"255.255.255.255"},
92             u'dst_addr': {u'addr': dst_ip, u'mask': u"255.255.255.255"},
93             u'src_port': {u'port': src_port, u'mask': 0xFFFF},
94             u'dst_port': {u'port': dst_port, u'mask': 0xFFFF},
95             u'protocol': {u'prot': flow_proto}
96         }
97
98         flow_index = FlowUtil.vpp_flow_add(
99             node, flow, flow_type, pattern, action, value)
100
101         return flow_index
102
103     @staticmethod
104     def vpp_create_ip6_n_tuple_flow(
105             node, src_ip, dst_ip, src_port, dst_port,
106             proto, action, value=0):
107         """Create IP6_N_TUPLE flow.
108
109         :param node: DUT node.
110         :param src_ip: Source IP6 address.
111         :param dst_ip: Destination IP6 address.
112         :param src_port: Source port.
113         :param dst_port: Destination port.
114         :param proto: TCP or UDP.
115         :param action: Mark, drop or redirect-to-queue.
116         :param value: Action value.
117
118         :type node: dict
119         :type src_ip: str
120         :type dst_ip: str
121         :type src_port: int
122         :type dst_port: int
123         :type proto: str
124         :type action: str
125         :type value: int
126         :returns: flow_index.
127         :rtype: int
128         """
129         flow = u"ip6_n_tuple"
130         flow_type = FlowType.FLOW_TYPE_IP6_N_TUPLE
131
132         if proto == u"TCP":
133             flow_proto = FlowProto.IP_API_PROTO_TCP
134         elif proto == u"UDP":
135             flow_proto = FlowProto.IP_API_PROTO_UDP
136         else:
137             raise ValueError(f"proto error: {proto}")
138
139         pattern = {
140             u'src_addr': {u'addr': src_ip, \
141                 u'mask': u"FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF"},
142             u'dst_addr': {u'addr': dst_ip, \
143                 u'mask': u"FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF"},
144             u'src_port': {u'port': src_port, u'mask': 0xFFFF},
145             u'dst_port': {u'port': dst_port, u'mask': 0xFFFF},
146             u'protocol': {u'prot': flow_proto}
147         }
148
149         flow_index = FlowUtil.vpp_flow_add(
150             node, flow, flow_type, pattern, action, value)
151
152         return flow_index
153
154     @staticmethod
155     def vpp_create_ip4_flow(
156             node, src_ip, dst_ip, proto, action, value=0):
157         """Create IP4 flow.
158
159         :param node: DUT node.
160         :param src_ip: Source IP4 address.
161         :param dst_ip: Destination IP4 address.
162         :param proto: TCP or UDP.
163         :param action: Mark, drop or redirect-to-queue.
164         :param value: Action value.
165
166         :type node: dict
167         :type src_ip: str
168         :type dst_ip: str
169         :type proto: str
170         :type action: str
171         :type value: int
172         :returns: flow_index.
173         :rtype: int
174         """
175         flow = u"ip4"
176         flow_type = FlowType.FLOW_TYPE_IP4
177
178         if proto == u"TCP":
179             flow_proto = FlowProto.IP_API_PROTO_TCP
180         elif proto == u"UDP":
181             flow_proto = FlowProto.IP_API_PROTO_UDP
182         else:
183             raise ValueError(f"proto error: {proto}")
184
185         pattern = {
186             u'src_addr': {u'addr': src_ip, u'mask': u"255.255.255.255"},
187             u'dst_addr': {u'addr': dst_ip, u'mask': u"255.255.255.255"},
188             u'protocol': {u'prot': flow_proto}
189         }
190
191         flow_index = FlowUtil.vpp_flow_add(
192             node, flow, flow_type, pattern, action, value)
193
194         return flow_index
195
196     @staticmethod
197     def vpp_create_ip6_flow(
198             node, src_ip, dst_ip, proto, action, value=0):
199         """Create IP6 flow.
200
201         :param node: DUT node.
202         :param src_ip: Source IP6 address.
203         :param dst_ip: Destination IP6 address.
204         :param proto: TCP or UDP.
205         :param action: Mark, drop or redirect-to-queue.
206         :param value: Action value.
207
208         :type node: dict
209         :type src_ip: str
210         :type dst_ip: str
211         :type proto: str
212         :type action: str
213         :type value: int
214         :returns: flow_index.
215         :rtype: int
216         """
217         flow = u"ip6"
218         flow_type = FlowType.FLOW_TYPE_IP6
219
220         if proto == u"TCP":
221             flow_proto = FlowProto.IP_API_PROTO_TCP
222         elif proto == u"UDP":
223             flow_proto = FlowProto.IP_API_PROTO_UDP
224         else:
225             raise ValueError(f"proto error: {proto}")
226
227         pattern = {
228             u'src_addr': {u'addr': src_ip, \
229                 u'mask': u"FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF"},
230             u'dst_addr': {'addr': dst_ip, \
231                 u'mask': u"FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF"},
232             u'protocol': {u'prot': flow_proto}
233         }
234
235         flow_index = FlowUtil.vpp_flow_add(
236             node, flow, flow_type, pattern, action, value)
237
238         return flow_index
239
240     @staticmethod
241     def vpp_create_ip4_gtpu_flow(
242             node, src_ip, dst_ip, teid, action, value=0):
243         """Create IP4_GTPU flow.
244
245         :param node: DUT node.
246         :param src_ip: Source IP4 address.
247         :param dst_ip: Destination IP4 address.
248         :param teid: Tunnel endpoint identifier.
249         :param action: Mark, drop or redirect-to-queue.
250         :param value: Action value.
251
252         :type node: dict
253         :type src_ip: str
254         :type dst_ip: str
255         :type teid: int
256         :type action: str
257         :type value: int
258         :returns: flow_index.
259         :rtype: int
260         """
261         flow = u"ip4_gtpu"
262         flow_type = FlowType.FLOW_TYPE_IP4_GTPU
263         flow_proto = FlowProto.IP_API_PROTO_UDP
264
265         pattern = {
266             u'src_addr': {u'addr': src_ip, u'mask': u"255.255.255.255"},
267             u'dst_addr': {u'addr': dst_ip, u'mask': u"255.255.255.255"},
268             u'protocol': {u'prot': flow_proto},
269             u'teid': teid
270         }
271
272         flow_index = FlowUtil.vpp_flow_add(
273             node, flow, flow_type, pattern, action, value)
274
275         return flow_index
276
277     @staticmethod
278     def vpp_create_ip4_ipsec_flow(node, proto, spi, action, value=0):
279         """Create IP4_IPSEC flow.
280
281         :param node: DUT node.
282         :param proto: TCP or UDP.
283         :param spi: Security Parameters Index.
284         :param action: Mark, drop or redirect-to-queue.
285         :param value: Action value.
286
287         :type node: dict
288         :type proto: str
289         :type spi: int
290         :type action: str
291         :type value: int
292         :returns: flow_index.
293         :rtype: int
294         """
295         if proto == u"ESP":
296             flow = u"ip4_ipsec_esp"
297             flow_proto = FlowProto.IP_API_PROTO_ESP
298             flow_type = FlowType.FLOW_TYPE_IP4_IPSEC_ESP
299         elif proto == u"AH":
300             flow = u"ip4_ipsec_ah"
301             flow_proto = FlowProto.IP_API_PROTO_AH
302             flow_type = FlowType.FLOW_TYPE_IP4_IPSEC_AH
303         else:
304             raise ValueError(f"proto error: {proto}")
305
306         pattern = {
307             u'protocol': {u'prot': flow_proto},
308             u'spi': spi
309         }
310
311         flow_index = FlowUtil.vpp_flow_add(
312             node, flow, flow_type, pattern, action, value)
313
314         return flow_index
315
316     @staticmethod
317     def vpp_create_ip4_l2tp_flow(node, session_id, action, value=0):
318         """Create IP4_L2TPV3OIP flow.
319
320         :param node: DUT node.
321         :param session_id: PPPoE session ID
322         :param action: Mark, drop or redirect-to-queue.
323         :param value: Action value.
324
325         :type node: dict
326         :type session_id: int
327         :type action: str
328         :type value: int
329         :returns: flow_index.
330         :rtype: int
331         """
332         flow = u"ip4_l2tpv3oip"
333         flow_proto = FlowProto.IP_API_PROTO_L2TP
334         flow_type = FlowType.FLOW_TYPE_IP4_L2TPV3OIP
335
336         pattern = {
337             u'protocol': {u'prot': flow_proto},
338             u'session_id': session_id
339         }
340
341         flow_index = FlowUtil.vpp_flow_add(
342             node, flow, flow_type, pattern, action, value)
343
344         return flow_index
345
346     @staticmethod
347     def vpp_create_ip4_vxlan_flow(node, src_ip, dst_ip, vni, action, value=0):
348         """Create IP4_VXLAN flow.
349
350         :param node: DUT node.
351         :param src_ip: Source IP4 address.
352         :param dst_ip: Destination IP4 address.
353         :param vni: Virtual network instance.
354         :param action: Mark, drop or redirect-to-queue.
355         :param value: Action value.
356
357         :type node: dict
358         :type src_ip: str
359         :type dst_ip: str
360         :type vni: int
361         :type action: str
362         :type value: int
363         :returns: flow_index.
364         """
365         flow = u"ip4_vxlan"
366         flow_type = FlowType.FLOW_TYPE_IP4_VXLAN
367         flow_proto = FlowProto.IP_API_PROTO_UDP
368
369         pattern = {
370             u'src_addr': {u'addr': src_ip, u'mask': u"255.255.255.255"},
371             u'dst_addr': {u'addr': dst_ip, u'mask': u"255.255.255.255"},
372             u'dst_port': {u'port': 4789, 'mask': 0xFFFF},
373             u'protocol': {u'prot': flow_proto},
374             u'vni': vni
375         }
376
377         flow_index = FlowUtil.vpp_flow_add(
378             node, flow, flow_type, pattern, action, value)
379
380         return flow_index
381
382     @staticmethod
383     def vpp_flow_add(node, flow, flow_type, pattern, action, value=0):
384         """Flow add.
385
386         :param node: DUT node.
387         :param flow: Name of flow.
388         :param flow_type: Type of flow.
389         :param pattern: Pattern of flow.
390         :param action: Mark, drop or redirect-to-queue.
391         :param value: Action value.
392
393         :type node: dict
394         :type node: str
395         :type flow_type: str
396         :type pattern: dict
397         :type action: str
398         :type value: int
399         :returns: flow_index.
400         :rtype: int
401         :raises ValueError: If action type is not supported.
402         """
403         cmd = u"flow_add"
404
405         if action == u"redirect-to-queue":
406             flow_rule = {
407                 u'type': flow_type,
408                 u'actions': FlowAction.FLOW_ACTION_REDIRECT_TO_QUEUE,
409                 u'redirect_queue': value,
410                 u'flow': {flow : pattern}
411             }
412         elif action == u"mark":
413             flow_rule = {
414                 u'type': flow_type,
415                 u'actions': FlowAction.FLOW_ACTION_MARK,
416                 u'mark_flow_id': value,
417                 u'flow': {flow : pattern}
418             }
419         elif action == u"drop":
420             flow_rule = {
421                 u'type': flow_type,
422                 u'actions': FlowAction.FLOW_ACTION_DROP,
423                 u'flow': {flow : pattern}
424             }
425         else:
426             raise ValueError(f"Unsupported action type: {action}")
427
428         err_msg = f"Failed to create {flow} flow on host {node[u'host']}."
429         args = dict(flow=flow_rule)
430         flow_index = -1
431         with PapiSocketExecutor(node) as papi_exec:
432             reply = papi_exec.add(cmd, **args).get_reply(err_msg)
433             flow_index = reply[u"flow_index"]
434
435         return flow_index
436
437     @staticmethod
438     def vpp_flow_enable(node, interface, flow_index=0):
439         """Flow enable.
440
441         :param node: DUT node.
442         :param interface: Interface sw_if_index.
443         :param flow_index: Flow index.
444
445         :type node: dict
446         :type interface: int
447         :type flow_index: int
448         :returns: Nothing.
449         """
450         cmd = u"flow_enable"
451         sw_if_index = Topology.get_interface_sw_index(node, interface)
452         args = dict(
453             flow_index=int(flow_index),
454             hw_if_index=int(sw_if_index)
455         )
456
457         err_msg = u"Failed to enable flow on host {node[u'host']}"
458         with PapiSocketExecutor(node) as papi_exec:
459             papi_exec.add(cmd, **args).get_reply(err_msg)
460
461     @staticmethod
462     def vpp_flow_disable(node, interface, flow_index=0):
463         """Flow disable.
464
465         :param node: DUT node.
466         :param interface: Interface sw_if_index.
467         :param flow_index: Flow index.
468
469         :type node: dict
470         :type interface: int
471         :type flow_index: int
472         :returns: Nothing.
473         """
474         cmd = u"flow_disable"
475         sw_if_index = Topology.get_interface_sw_index(node, interface)
476         args = dict(
477             flow_index=int(flow_index),
478             hw_if_index=int(sw_if_index)
479         )
480
481         err_msg = u"Failed to disable flow on host {node[u'host']}"
482         with PapiSocketExecutor(node) as papi_exec:
483             papi_exec.add(cmd, **args).get_reply(err_msg)
484
485     @staticmethod
486     def vpp_flow_del(node, flow_index=0):
487         """Flow delete.
488
489         :param node: DUT node.
490         :param flow_index: Flow index.
491
492         :type node: dict
493         :type flow_index: int
494         :returns: Nothing.
495         """
496         cmd = u"flow_del"
497         args = dict(
498             flow_index=int(flow_index)
499         )
500
501         err_msg = u"Failed to delete flow on host {node[u'host']}"
502         with PapiSocketExecutor(node) as papi_exec:
503             papi_exec.add(cmd, **args).get_reply(err_msg)
504
505     @staticmethod
506     def vpp_show_flow_entry(node):
507         """Show flow entry.
508
509         :param node: DUT node.
510
511         :type node: dict
512         :returns: flow entry.
513         :rtype: str
514         """
515         cmd = u"vppctl show flow entry"
516
517         err_msg = u"Failed to show flow on host {node[u'host']}"
518         stdout, _ = exec_cmd_no_error(
519             node, cmd, sudo=False, message=err_msg, retries=120
520             )
521
522         return stdout.strip()
523
524     @staticmethod
525     def vpp_verify_flow_action(
526             node, action, value,
527             src_mac=u"11:22:33:44:55:66", dst_mac=u"11:22:33:44:55:66",
528             src_ip=None, dst_ip=None):
529         """Verify the correctness of the flow action.
530
531         :param node: DUT node.
532         :param action: Action.
533         :param value: Action value.
534         :param src_mac: Source mac address.
535         :param dst_mac: Destination mac address.
536         :param src_ip: Source IP address.
537         :param dst_ip: Destination IP address.
538
539         :type node: dict
540         :type action: str
541         :type value: int
542         :type src_mac: str
543         :type dst_mac: str
544         :type src_ip: str
545         :type dst_ip: str
546         :returns: Nothing.
547         :raises RuntimeError: If the verification of flow action fails.
548         :raises ValueError: If action type is not supported.
549         """
550         err_msg = f"Failed to show trace on host {node[u'host']}"
551         cmd = u"vppctl show trace"
552         stdout, _ = exec_cmd_no_error(
553             node, cmd, sudo=False, message=err_msg, retries=120
554         )
555
556         err_info = f"Verify flow {action} failed"
557
558         if src_ip is None:
559             expected_str = f"{src_mac} -> {dst_mac}"
560         else:
561             src_ip = ip_address(src_ip)
562             dst_ip = ip_address(dst_ip)
563             expected_str = f"{src_ip} -> {dst_ip}"
564
565         if action == u"drop":
566             if expected_str in stdout:
567                 raise RuntimeError(err_info)
568         elif action == u"redirect-to-queue":
569             if f"queue {value}" not in stdout \
570                     and f"qid {value}" not in stdout:
571                 raise RuntimeError(err_info)
572             if expected_str not in stdout:
573                 raise RuntimeError(err_info)
574         elif action == u"mark":
575             if u"PKT_RX_FDIR" not in stdout and  u"flow-id 1" not in stdout:
576                 raise RuntimeError(err_info)
577             if expected_str not in stdout:
578                 raise RuntimeError(err_info)
579         else:
580             raise ValueError(f"Unsupported action type: {action}")