1 | """of_core utility functions and classes.""" |
||
2 | |||
3 | 1 | import struct |
|
4 | 1 | from collections import OrderedDict |
|
5 | |||
6 | 1 | from pyof.foundation.exceptions import PackException, UnpackException |
|
7 | 1 | from pyof.v0x01.common.header import Type as OFPTYPE |
|
8 | |||
9 | 1 | from kytos.core import KytosEvent |
|
10 | |||
11 | |||
12 | 1 | def of_slicer(remaining_data): |
|
13 | """Slice a raw `bytes` instance into OpenFlow packets.""" |
||
14 | 1 | data_len = len(remaining_data) |
|
15 | 1 | pkts = [] |
|
16 | 1 | while data_len > 3: |
|
17 | 1 | length_field = struct.unpack('!H', remaining_data[2:4])[0] |
|
18 | 1 | if data_len >= length_field: |
|
19 | 1 | pkts.append(remaining_data[:length_field]) |
|
20 | 1 | remaining_data = remaining_data[length_field:] |
|
21 | 1 | data_len = len(remaining_data) |
|
22 | else: |
||
23 | break |
||
24 | 1 | return pkts, remaining_data |
|
25 | |||
26 | |||
27 | 1 | def _unpack_int(packet, offset=0, size=None): |
|
28 | 1 | if size is None: |
|
29 | 1 | if isinstance(packet, int): |
|
30 | return packet |
||
31 | 1 | size = len(packet) |
|
32 | 1 | return int.from_bytes(packet[offset:offset + size], byteorder='big') |
|
33 | |||
34 | |||
35 | 1 | def _emit_message(controller, connection, message, direction): |
|
36 | """Emit a KytosEvent for every incoming or outgoing message.""" |
||
37 | 1 | if direction == 'in': |
|
38 | 1 | address_type = 'source' |
|
39 | 1 | message_buffer = controller.buffers.msg_in |
|
40 | 1 | elif direction == 'out': |
|
41 | 1 | address_type = 'destination' |
|
42 | 1 | message_buffer = controller.buffers.msg_out |
|
43 | else: |
||
44 | raise Exception("direction must be 'in' or 'out'") |
||
45 | |||
46 | 1 | name = message.header.message_type.name.lower() |
|
47 | 1 | hex_version = 'v0x%0.2x' % (message.header.version + 0) |
|
48 | 1 | of_event = KytosEvent( |
|
49 | name=f"kytos/of_core.{hex_version}.messages.{direction}.{name}", |
||
50 | content={'message': message, |
||
51 | address_type: connection}) |
||
52 | 1 | message_buffer.put(of_event) |
|
53 | |||
54 | |||
55 | 1 | def emit_message_in(controller, connection, message): |
|
56 | """Emit a KytosEvent for every incoming message.""" |
||
57 | 1 | _emit_message(controller, connection, message, 'in') |
|
58 | |||
59 | |||
60 | 1 | def emit_message_out(controller, connection, message): |
|
61 | """Emit a KytosEvent for every outgoing message.""" |
||
62 | 1 | _emit_message(controller, connection, message, 'out') |
|
63 | |||
64 | |||
65 | 1 | class GenericHello: |
|
66 | """Version agnostic OpenFlow Hello Message.""" |
||
67 | |||
68 | 1 | header_sizes = OrderedDict( |
|
69 | version=1, |
||
70 | type=1, |
||
71 | length=2, |
||
72 | xid=4) |
||
73 | |||
74 | 1 | elem_type_size = 2 |
|
75 | 1 | elem_len_size = 2 |
|
76 | |||
77 | 1 | OFPHET_VERSIONBITMAP = 1 |
|
78 | |||
79 | 1 | class GenericHeader: |
|
80 | """Generic header for the OpenFlow message.""" |
||
81 | |||
82 | 1 | xid = None |
|
83 | 1 | type = None |
|
84 | 1 | length = None |
|
85 | |||
86 | 1 | def __init__(self, *, packet=None, versions=None, xid=None): |
|
87 | """Initialize from a binary packet or from initial versions and xid. |
||
88 | |||
89 | Parameters: |
||
90 | packet: binary packet (bytes) to be unpacked and used to |
||
91 | initialize the message |
||
92 | versions: list of versions used to build the version bitmap |
||
93 | xid: xid to be used in the message |
||
94 | |||
95 | """ |
||
96 | 1 | self.header = self.GenericHeader() |
|
97 | 1 | self.header.message_type = OFPTYPE.OFPT_HELLO |
|
98 | 1 | if not any((packet, versions)): |
|
99 | raise Exception('either packet or versions must be set.') |
||
100 | |||
101 | 1 | if packet is not None: |
|
102 | 1 | self.unpack(packet) |
|
103 | else: |
||
104 | if xid is None: |
||
105 | self.header.xid = 0 |
||
106 | |||
107 | 1 | if versions is not None: |
|
108 | 1 | self.versions = versions |
|
109 | 1 | self.header.version = max(versions) |
|
110 | 1 | if xid is not None: |
|
111 | self.header.xid = xid |
||
112 | |||
113 | 1 | def pack(self): |
|
114 | """Encode OpenFlow packet.""" |
||
115 | 1 | versions = self.versions |
|
116 | 1 | xid = self.header.xid |
|
117 | 1 | packet_version = max(versions) |
|
118 | 1 | if packet_version > 31: |
|
119 | raise PackException |
||
120 | 1 | versions_value = 0 |
|
121 | 1 | for version in versions: |
|
122 | 1 | versions_value += (1 << version) |
|
123 | 1 | bitmap = versions_value.to_bytes(4, byteorder='big') |
|
124 | 1 | version_byte = packet_version.to_bytes(self.header_sizes['version'], |
|
125 | byteorder='big') |
||
126 | 1 | xid_byte = xid.to_bytes(self.header_sizes['xid'], |
|
127 | byteorder='big') |
||
128 | 1 | packet = version_byte + b'\x00\x00\x10' + xid_byte + \ |
|
129 | b'\x00\x01\x00\x08' + bitmap |
||
130 | 1 | return packet |
|
131 | |||
132 | 1 | def unpack(self, packet): |
|
133 | """Decode OpenFlow packet.""" |
||
134 | 1 | offset = 0 |
|
135 | # self.header = self.generic_Header() |
||
136 | 1 | for key, size in self.header_sizes.items(): |
|
137 | 1 | setattr(self.header, key, _unpack_int(packet, offset, size)) |
|
138 | 1 | offset += size |
|
139 | |||
140 | 1 | if self.header.type != 0: |
|
141 | raise UnpackException |
||
142 | |||
143 | 1 | elements = {} |
|
144 | 1 | try: |
|
145 | 1 | while offset < self.header.length: |
|
146 | 1 | elem_type = _unpack_int( |
|
147 | packet, offset, self.elem_type_size) |
||
148 | |||
149 | 1 | offset += self.elem_type_size |
|
150 | 1 | elem_length = _unpack_int( |
|
151 | packet, offset, self.elem_len_size) |
||
152 | |||
153 | 1 | offset += self.elem_len_size |
|
154 | 1 | elem_header_size = self.elem_type_size - self.elem_len_size |
|
155 | 1 | elem_value_size = elem_length - elem_header_size |
|
156 | 1 | elem_value = _unpack_int( |
|
157 | packet, offset, elem_value_size) |
||
158 | |||
159 | 1 | elements[elem_type] = elem_value |
|
160 | except IndexError: |
||
161 | raise UnpackException |
||
162 | |||
163 | 1 | self.elements = elements |
|
164 | |||
165 | 1 | if self.OFPHET_VERSIONBITMAP in self.elements: |
|
166 | 1 | bitmap = self.elements[self.OFPHET_VERSIONBITMAP] |
|
167 | 1 | versions = [] |
|
168 | 1 | for i in range(32): |
|
169 | 1 | if ((1 << i) & bitmap) != 0: |
|
170 | 1 | versions.append(i) |
|
171 | 1 | self.versions = versions |
|
172 | 1 | self.version_bitmap = bitmap |
|
173 | else: |
||
174 | 1 | self.versions = None |
|
175 | |||
176 | |||
177 | 1 | class NegotiationException(Exception): |
|
178 | """Exception raised when OpenFlow version negotiation failed.""" |
||
179 | |||
180 | 1 | def __str__(self): |
|
181 | return "OF version negotiation failed: " + super().__str__() |
||
182 |