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