1
|
|
|
"""Utility functions to be used in this Napp""" |
2
|
|
|
# pylint: disable=consider-using-join |
3
|
1 |
|
import ipaddress |
4
|
|
|
|
5
|
1 |
|
import httpx |
6
|
1 |
|
from kytos.core.retry import before_sleep |
7
|
1 |
|
from napps.amlight.sdntrace_cp import settings |
8
|
1 |
|
from tenacity import (retry, retry_if_exception_type, stop_after_attempt, |
9
|
|
|
wait_random) |
10
|
|
|
|
11
|
|
|
|
12
|
1 |
|
@retry( |
13
|
|
|
stop=stop_after_attempt(3), |
14
|
|
|
wait=wait_random(min=0.1, max=0.2), |
15
|
|
|
before_sleep=before_sleep, |
16
|
|
|
retry=retry_if_exception_type((httpx.RequestError, ConnectionError))) |
17
|
1 |
|
def get_stored_flows(dpids: list = None, state: str = "installed"): |
18
|
|
|
"""Get stored flows from flow_manager napps.""" |
19
|
1 |
|
api_url = f'{settings.FLOW_MANAGER_URL}/stored_flows' |
20
|
1 |
|
if dpids: |
21
|
|
|
str_dpids = '' |
22
|
|
|
for dpid in dpids: |
23
|
|
|
str_dpids += f'&dpid={dpid}' |
24
|
|
|
api_url += '/?'+str_dpids[1:] |
25
|
1 |
|
if state: |
26
|
1 |
|
char = '&' if dpids else '/?' |
27
|
1 |
|
api_url += char+f'state={state}' |
28
|
1 |
|
result = httpx.get(api_url, timeout=20) |
29
|
1 |
|
flows_from_manager = result.json() |
30
|
1 |
|
return flows_from_manager |
31
|
|
|
|
32
|
|
|
|
33
|
1 |
|
def convert_entries(entries): |
34
|
|
|
""" Transform entries dictionary in a plain dictionary suitable for |
35
|
|
|
matching |
36
|
|
|
|
37
|
|
|
:param entries: dict |
38
|
|
|
:return: plain dict |
39
|
|
|
""" |
40
|
1 |
|
new_entries = {} |
41
|
1 |
|
for entry in entries['trace'].values(): |
42
|
1 |
|
for field, value in entry.items(): |
43
|
1 |
|
new_entries[field] = value |
44
|
1 |
|
if 'dl_vlan' in new_entries: |
45
|
1 |
|
new_entries['dl_vlan'] = [new_entries['dl_vlan']] |
46
|
1 |
|
return new_entries |
47
|
|
|
|
48
|
|
|
|
49
|
1 |
|
def convert_list_entries(entries): |
50
|
|
|
""" Transform a list of entries dictionary in a list |
51
|
|
|
of plain dictionary suitable for matching |
52
|
|
|
:param entries: list(dict) |
53
|
|
|
:return: list(plain dict) |
54
|
|
|
""" |
55
|
1 |
|
new_entries = [] |
56
|
1 |
|
for entry in entries: |
57
|
1 |
|
new_entry = convert_entries(entry) |
58
|
1 |
|
if new_entry: |
59
|
1 |
|
new_entries.append(new_entry) |
60
|
1 |
|
return new_entries |
61
|
|
|
|
62
|
|
|
|
63
|
1 |
|
def find_endpoint(switch, port): |
64
|
|
|
""" Find where switch/port is connected. If it is another switch, |
65
|
|
|
returns the interface it is connected to, otherwise returns None """ |
66
|
|
|
|
67
|
1 |
|
interface = switch.get_interface_by_port_no(port) |
68
|
1 |
|
if not interface: |
69
|
|
|
return None |
70
|
1 |
|
if interface and interface.link: |
71
|
1 |
|
if interface == interface.link.endpoint_a: |
72
|
1 |
|
return {'endpoint': interface.link.endpoint_b} |
73
|
1 |
|
return {'endpoint': interface.link.endpoint_a} |
74
|
1 |
|
return {'endpoint': None} |
75
|
|
|
|
76
|
|
|
|
77
|
1 |
|
def _prepare_json(trace_result): |
78
|
|
|
"""Auxiliar function to return the json for REST call.""" |
79
|
1 |
|
result = [] |
80
|
1 |
|
for trace_step in trace_result: |
81
|
1 |
|
result.append(trace_step['in']) |
82
|
1 |
|
if result: |
83
|
1 |
|
result[-1]["out"] = trace_result[-1].get("out") |
84
|
1 |
|
return result |
85
|
|
|
|
86
|
|
|
|
87
|
1 |
|
def prepare_json(trace_result): |
88
|
|
|
"""Prepare return json for REST call.""" |
89
|
1 |
|
result = [] |
90
|
1 |
|
if trace_result and isinstance(trace_result[0], list): |
91
|
1 |
|
for trace in trace_result: |
92
|
1 |
|
result.append(_prepare_json(trace)) |
93
|
|
|
else: |
94
|
1 |
|
result = _prepare_json(trace_result) |
95
|
1 |
|
return {'result': result} |
96
|
|
|
|
97
|
|
|
|
98
|
|
|
# pylint: disable=too-many-return-statements |
99
|
1 |
|
def _compare_endpoints(endpoint1, endpoint2): |
100
|
1 |
|
if endpoint1['dpid'] != endpoint2['dpid']: |
101
|
1 |
|
return False |
102
|
1 |
|
if ( |
103
|
|
|
'in_port' not in endpoint1 |
104
|
|
|
or 'out_port' not in endpoint2 |
105
|
|
|
or endpoint1['in_port'] != endpoint2['out_port'] |
106
|
|
|
): |
107
|
1 |
|
return False |
108
|
1 |
|
if 'in_vlan' in endpoint1 and 'out_vlan' in endpoint2: |
109
|
1 |
|
if endpoint1['in_vlan'] != endpoint2['out_vlan']: |
110
|
1 |
|
return False |
111
|
1 |
|
elif 'in_vlan' in endpoint1 or 'out_vlan' in endpoint2: |
112
|
1 |
|
return False |
113
|
1 |
|
if 'out_vlan' in endpoint1 and 'in_vlan' in endpoint2: |
114
|
1 |
|
if endpoint1['out_vlan'] != endpoint2['in_vlan']: |
115
|
1 |
|
return False |
116
|
1 |
|
elif 'out_vlan' in endpoint1 or 'in_vlan' in endpoint2: |
117
|
1 |
|
return False |
118
|
1 |
|
return True |
119
|
|
|
|
120
|
|
|
|
121
|
1 |
|
def convert_vlan(value): |
122
|
|
|
"""Auxiliar function to calculate dl_vlan""" |
123
|
1 |
|
if isinstance(value, int): |
124
|
1 |
|
return value, 4095 |
125
|
1 |
|
value, mask = map(int, value.split('/')) |
126
|
1 |
|
return value, mask |
127
|
|
|
|
128
|
|
|
|
129
|
1 |
|
def match_field_dl_vlan(value, field_flow): |
130
|
|
|
""" Verify match in dl_vlan. |
131
|
|
|
value only takes an int in range [1,4095]. |
132
|
|
|
0 is not allowed for value. """ |
133
|
1 |
|
if not value: |
134
|
1 |
|
return field_flow == 0 |
135
|
1 |
|
value = value[-1] |
136
|
1 |
|
value_flow, mask_flow = convert_vlan(field_flow) |
137
|
1 |
|
return value & (mask_flow & 4095) == value_flow & (mask_flow & 4095) |
138
|
|
|
|
139
|
|
|
|
140
|
1 |
|
def match_field_ip(field, field_flow): |
141
|
|
|
"Verify match in ip fields" |
142
|
1 |
|
packet_address = ipaddress.ip_address(field) |
143
|
1 |
|
flow_network = ipaddress.ip_network(field_flow, strict=False) |
144
|
|
|
return packet_address in flow_network |
145
|
|
|
|