1 | """Defines actions that may be associated with flows packets.""" |
||
2 | # System imports |
||
3 | 1 | from enum import IntEnum |
|
4 | 1 | from math import ceil |
|
5 | |||
6 | # Local source tree imports |
||
7 | 1 | from pyof.foundation.base import GenericStruct |
|
8 | 1 | from pyof.foundation.basic_types import ( |
|
9 | BinaryData, FixedTypeList, Pad, UBInt8, UBInt16, UBInt32) |
||
10 | 1 | from pyof.v0x04.common.flow_match import OxmTLV |
|
11 | |||
12 | # Third-party imports |
||
13 | |||
14 | 1 | __all__ = ('ActionExperimenter', 'ActionGroup', 'ActionHeader', |
|
15 | 'ActionCopyTTLIn', 'ActionCopyTTLOut', 'ActionDecMPLSTTL', |
||
16 | 'ActionSetMPLSTTL', 'ActionDecNWTTL', 'ActionSetNWTTL', |
||
17 | 'ActionOutput', 'ActionPopMPLS', 'ActionPopPBB', 'ActionPopVLAN', |
||
18 | 'ActionPush', 'ActionSetField', 'ActionSetQueue', 'ActionType', |
||
19 | 'ControllerMaxLen', 'ListOfActions') |
||
20 | |||
21 | # Enums |
||
22 | |||
23 | |||
24 | 1 | class ActionType(IntEnum): |
|
25 | """Actions associated with flows and packets.""" |
||
26 | |||
27 | #: Output to switch port. |
||
28 | 1 | OFPAT_OUTPUT = 0 |
|
29 | #: Copy TTL "outwards" -- from next-to-outermost to outermost |
||
30 | 1 | OFPAT_COPY_TTL_OUT = 11 |
|
31 | #: Copy TTL "inwards" -- from outermost to next-to-outermost |
||
32 | 1 | OFPAT_COPY_TTL_IN = 12 |
|
33 | #: MPLS TTL |
||
34 | 1 | OFPAT_SET_MPLS_TTL = 15 |
|
35 | #: Decrement MPLS TTL |
||
36 | 1 | OFPAT_DEC_MPLS_TTL = 16 |
|
37 | #: Push a new VLAN tag |
||
38 | 1 | OFPAT_PUSH_VLAN = 17 |
|
39 | #: Pop the outer VLAN tag |
||
40 | 1 | OFPAT_POP_VLAN = 18 |
|
41 | #: Push a new MPLS tag |
||
42 | 1 | OFPAT_PUSH_MPLS = 19 |
|
43 | #: Pop the outer MPLS tag |
||
44 | 1 | OFPAT_POP_MPLS = 20 |
|
45 | #: Set queue id when outputting to a port |
||
46 | 1 | OFPAT_SET_QUEUE = 21 |
|
47 | #: Apply group. |
||
48 | 1 | OFPAT_GROUP = 22 |
|
49 | #: IP TTL. |
||
50 | 1 | OFPAT_SET_NW_TTL = 23 |
|
51 | #: Decrement IP TTL. |
||
52 | 1 | OFPAT_DEC_NW_TTL = 24 |
|
53 | #: Set a header field using OXM TLV format. |
||
54 | 1 | OFPAT_SET_FIELD = 25 |
|
55 | #: Push a new PBB service tag (I-TAG) |
||
56 | 1 | OFPAT_PUSH_PBB = 26 |
|
57 | #: Pop the outer PBB service tag (I-TAG) |
||
58 | 1 | OFPAT_POP_PBB = 27 |
|
59 | #: Experimenter type |
||
60 | 1 | OFPAT_EXPERIMENTER = 0xffff |
|
61 | |||
62 | |||
63 | 1 | class ControllerMaxLen(IntEnum): |
|
64 | """A max_len of OFPCML_NO_BUFFER means not to buffer. |
||
65 | |||
66 | The packet should be sent. |
||
67 | """ |
||
68 | |||
69 | #: maximum max_len value which can be used to request a specific byte |
||
70 | #: length. |
||
71 | 1 | OFPCML_MAX = 0xffe5 |
|
72 | #: indicates that no buffering should be applied and the whole packet is to |
||
73 | #: be sent to the controller. |
||
74 | 1 | OFPCML_NO_BUFFER = 0xffff |
|
75 | |||
76 | |||
77 | # Classes |
||
78 | |||
79 | |||
80 | 1 | class ActionHeader(GenericStruct): |
|
81 | """Action header that is common to all actions. |
||
82 | |||
83 | The length includes the header and any padding used to make the action |
||
84 | 64-bit aligned. |
||
85 | NB: The length of an action *must* always be a multiple of eight. |
||
86 | """ |
||
87 | |||
88 | #: One of OFPAT_*. |
||
89 | 1 | action_type = UBInt16(enum_ref=ActionType) |
|
90 | #: Length of action, including this header. This is the length of actions, |
||
91 | #: including any padding to make it 64-bit aligned. |
||
92 | 1 | length = UBInt16() |
|
93 | # Pad for 64-bit alignment. |
||
94 | # This should not be implemented, as each action type has its own padding. |
||
95 | # pad = Pad(4) |
||
96 | |||
97 | 1 | _allowed_types = () |
|
98 | |||
99 | 1 | def __init__(self, action_type=None, length=None): |
|
100 | """Create an ActionHeader with the optional parameters below. |
||
101 | |||
102 | Args: |
||
103 | action_type (~pyof.v0x04.common.action.ActionType): |
||
104 | The type of the action. |
||
105 | length (int): Length of action, including this header. |
||
106 | """ |
||
107 | 1 | super().__init__() |
|
108 | 1 | self.action_type = action_type |
|
109 | 1 | self.length = length |
|
110 | |||
111 | 1 | def get_size(self, value=None): |
|
112 | """Return the action length including the padding (multiple of 8).""" |
||
113 | 1 | if isinstance(value, ActionHeader): |
|
114 | return value.get_size() |
||
115 | 1 | if value is None: |
|
116 | 1 | current_size = super().get_size() |
|
117 | 1 | return ceil(current_size / 8) * 8 |
|
118 | raise ValueError(f'Invalid value "{value}" for Action*.get_size()') |
||
119 | |||
120 | 1 | View Code Duplication | def unpack(self, buff, offset=0): |
0 ignored issues
–
show
Duplication
introduced
by
![]() |
|||
121 | """Unpack a binary message into this object's attributes. |
||
122 | |||
123 | Unpack the binary value *buff* and update this object attributes based |
||
124 | on the results. |
||
125 | |||
126 | Args: |
||
127 | buff (bytes): Binary data package to be unpacked. |
||
128 | offset (int): Where to begin unpacking. |
||
129 | |||
130 | Raises: |
||
131 | Exception: If there is a struct unpacking error. |
||
132 | |||
133 | """ |
||
134 | 1 | self.action_type = UBInt16(enum_ref=ActionType) |
|
135 | 1 | self.action_type.unpack(buff, offset) |
|
136 | |||
137 | 1 | self.length = UBInt16() |
|
138 | 1 | self.length.unpack(buff, offset=offset+2) |
|
139 | |||
140 | 1 | for cls in ActionHeader.__subclasses__(): |
|
141 | 1 | if self.action_type.value in cls.get_allowed_types(): |
|
142 | 1 | self.__class__ = cls |
|
143 | 1 | break |
|
144 | |||
145 | 1 | super().unpack(buff[:offset+self.length], offset) |
|
146 | |||
147 | 1 | @classmethod |
|
148 | def get_allowed_types(cls): |
||
149 | """Return allowed types for the class.""" |
||
150 | 1 | return cls._allowed_types |
|
151 | |||
152 | |||
153 | 1 | class ActionExperimenter(ActionHeader): |
|
154 | """Action structure for OFPAT_EXPERIMENTER.""" |
||
155 | |||
156 | 1 | experimenter = UBInt32() |
|
157 | 1 | body = BinaryData() |
|
158 | |||
159 | 1 | _allowed_types = (ActionType.OFPAT_EXPERIMENTER,) |
|
160 | |||
161 | 1 | def __init__(self, length=None, experimenter=None, body=None): |
|
162 | """Create ActionExperimenterHeader with the optional parameters below. |
||
163 | |||
164 | Args: |
||
165 | experimenter (int): The experimenter field is the Experimenter ID, |
||
166 | which takes the same form as in struct ofp_experimenter. |
||
167 | body(bytes): The body of the experimenter. It is vendor-defined, |
||
168 | so it is left as it is. |
||
169 | """ |
||
170 | 1 | super().__init__(action_type=ActionType.OFPAT_EXPERIMENTER) |
|
171 | 1 | self.length = length |
|
172 | 1 | self.experimenter = experimenter |
|
173 | 1 | self.body = body |
|
174 | |||
175 | |||
176 | 1 | class ActionGroup(ActionHeader): |
|
177 | """Action structure for OFPAT_GROUP.""" |
||
178 | |||
179 | 1 | group_id = UBInt32() |
|
180 | |||
181 | 1 | _allowed_types = (ActionType.OFPAT_GROUP,) |
|
182 | |||
183 | 1 | def __init__(self, group_id=None): |
|
184 | """Create an ActionGroup with the optional parameters below. |
||
185 | |||
186 | Args: |
||
187 | group_id (int): The group_id indicates the group used to process |
||
188 | this packet. The set of buckets to apply depends on the group |
||
189 | type. |
||
190 | """ |
||
191 | super().__init__(action_type=ActionType.OFPAT_GROUP, length=8) |
||
192 | self.group_id = group_id |
||
193 | |||
194 | |||
195 | 1 | class ActionDecMPLSTTL(ActionHeader): |
|
196 | """Action structure for OFPAT_DEC_MPLS_TTL.""" |
||
197 | |||
198 | 1 | pad = Pad(4) |
|
199 | |||
200 | 1 | _allowed_types = (ActionType.OFPAT_DEC_MPLS_TTL,) |
|
201 | |||
202 | 1 | def __init__(self): |
|
203 | """Create an ActionDecMPLSTTL.""" |
||
204 | super().__init__(action_type=ActionType.OFPAT_DEC_MPLS_TTL, length=8) |
||
205 | |||
206 | |||
207 | 1 | class ActionSetMPLSTTL(ActionHeader): |
|
208 | """Action structure for OFPAT_SET_MPLS_TTL.""" |
||
209 | |||
210 | 1 | mpls_ttl = UBInt8() |
|
211 | 1 | pad = Pad(3) |
|
212 | |||
213 | 1 | _allowed_types = (ActionType.OFPAT_SET_MPLS_TTL,) |
|
214 | |||
215 | 1 | def __init__(self, mpls_ttl=None): |
|
216 | """Create an ActionSetMPLSTTL with the optional parameters below. |
||
217 | |||
218 | Args: |
||
219 | mpls_ttl (int): The mpls_ttl field is the MPLS TTL to set. |
||
220 | """ |
||
221 | super().__init__(action_type=ActionType.OFPAT_SET_MPLS_TTL, length=8) |
||
222 | self.mpls_ttl = mpls_ttl |
||
223 | |||
224 | |||
225 | 1 | class ActionCopyTTLIn(ActionHeader): |
|
226 | """Action structure for OFPAT_COPY_TTL_IN.""" |
||
227 | |||
228 | 1 | pad = Pad(4) |
|
229 | |||
230 | 1 | _allowed_types = (ActionType.OFPAT_COPY_TTL_IN,) |
|
231 | |||
232 | 1 | def __init__(self): |
|
233 | """Create an ActionCopyTTLIn.""" |
||
234 | super().__init__(action_type=ActionType.OFPAT_COPY_TTL_IN, length=8) |
||
235 | |||
236 | |||
237 | 1 | class ActionCopyTTLOut(ActionHeader): |
|
238 | """Action structure for OFPAT_COPY_TTL_OUT.""" |
||
239 | |||
240 | 1 | pad = Pad(4) |
|
241 | |||
242 | 1 | _allowed_types = (ActionType.OFPAT_COPY_TTL_OUT,) |
|
243 | |||
244 | 1 | def __init__(self): |
|
245 | """Create an ActionCopyTTLOut.""" |
||
246 | super().__init__(action_type=ActionType.OFPAT_COPY_TTL_OUT, length=8) |
||
247 | |||
248 | |||
249 | 1 | class ActionPopVLAN(ActionHeader): |
|
250 | """Action structure for OFPAT_POP_VLAN.""" |
||
251 | |||
252 | 1 | pad = Pad(4) |
|
253 | |||
254 | 1 | _allowed_types = (ActionType.OFPAT_POP_VLAN,) |
|
255 | |||
256 | 1 | def __init__(self): |
|
257 | """Create an ActionPopVLAN.""" |
||
258 | super().__init__(action_type=ActionType.OFPAT_POP_VLAN, length=8) |
||
259 | |||
260 | |||
261 | 1 | class ActionPopPBB(ActionHeader): |
|
262 | """Action structure for OFPAT_POP_PBB.""" |
||
263 | |||
264 | 1 | pad = Pad(4) |
|
265 | |||
266 | 1 | _allowed_types = (ActionType.OFPAT_POP_PBB,) |
|
267 | |||
268 | 1 | def __init__(self): |
|
269 | """Create an ActionPopPBB.""" |
||
270 | super().__init__(action_type=ActionType.OFPAT_POP_PBB, length=8) |
||
271 | |||
272 | |||
273 | 1 | class ActionDecNWTTL(ActionHeader): |
|
274 | """Action structure for OFPAT_DEC_NW_TTL.""" |
||
275 | |||
276 | 1 | pad = Pad(4) |
|
277 | |||
278 | 1 | _allowed_types = (ActionType.OFPAT_DEC_NW_TTL,) |
|
279 | |||
280 | 1 | def __init__(self): |
|
281 | """Create a ActionDecNWTTL.""" |
||
282 | super().__init__(action_type=ActionType.OFPAT_DEC_NW_TTL, length=8) |
||
283 | |||
284 | |||
285 | 1 | class ActionSetNWTTL(ActionHeader): |
|
286 | """Action structure for OFPAT_SET_NW_TTL.""" |
||
287 | |||
288 | 1 | nw_ttl = UBInt8() |
|
289 | 1 | pad = Pad(3) |
|
290 | |||
291 | 1 | _allowed_types = (ActionType.OFPAT_SET_NW_TTL,) |
|
292 | |||
293 | 1 | def __init__(self, nw_ttl=None): |
|
294 | """Create an ActionSetNWTTL with the optional parameters below. |
||
295 | |||
296 | Args: |
||
297 | nw_ttl (int): the TTL address to set in the IP header. |
||
298 | """ |
||
299 | super().__init__(action_type=ActionType.OFPAT_SET_NW_TTL, length=8) |
||
300 | self.nw_ttl = nw_ttl |
||
301 | |||
302 | |||
303 | 1 | class ActionOutput(ActionHeader): |
|
304 | """Defines the actions output. |
||
305 | |||
306 | Action structure for :attr:`ActionType.OFPAT_OUTPUT`, which sends packets |
||
307 | out :attr:`port`. When the :attr:`port` is the |
||
308 | :attr:`.Port.OFPP_CONTROLLER`, :attr:`max_length` indicates the max number |
||
309 | of bytes to send. A :attr:`max_length` of zero means no bytes of the packet |
||
310 | should be sent. |
||
311 | """ |
||
312 | |||
313 | 1 | port = UBInt32() |
|
314 | 1 | max_length = UBInt16() |
|
315 | 1 | pad = Pad(6) |
|
316 | |||
317 | 1 | _allowed_types = (ActionType.OFPAT_OUTPUT,) |
|
318 | |||
319 | 1 | def __init__(self, port=None, |
|
320 | max_length=ControllerMaxLen.OFPCML_NO_BUFFER): |
||
321 | """Create a ActionOutput with the optional parameters below. |
||
322 | |||
323 | Args: |
||
324 | port (:class:`Port` or :class:`int`): Output port. |
||
325 | max_length (int): Max length to send to controller. |
||
326 | """ |
||
327 | 1 | super().__init__(action_type=ActionType.OFPAT_OUTPUT, length=16) |
|
328 | 1 | self.port = port |
|
329 | 1 | self.max_length = max_length |
|
330 | |||
331 | 1 | def __repr__(self): |
|
332 | return f"{type(self).__name__}(port={self.port})" |
||
333 | |||
334 | |||
335 | 1 | class ActionPopMPLS(ActionHeader): |
|
336 | """Action structure for OFPAT_POP_MPLS.""" |
||
337 | |||
338 | 1 | ethertype = UBInt16() |
|
339 | 1 | pad = Pad(2) |
|
340 | |||
341 | 1 | _allowed_types = (ActionType.OFPAT_POP_MPLS,) |
|
342 | |||
343 | 1 | def __init__(self, ethertype=None): |
|
344 | """Create an ActionPopMPLS with the optional parameters below. |
||
345 | |||
346 | Args: |
||
347 | ethertype (int): indicates the Ethertype of the payload. |
||
348 | """ |
||
349 | super().__init__(action_type=ActionType.OFPAT_POP_MPLS) |
||
350 | self.ethertype = ethertype |
||
351 | |||
352 | |||
353 | 1 | class ActionPush(ActionHeader): |
|
354 | """Action structure for OFPAT_PUSH_[VLAN/MPLS/PBB].""" |
||
355 | |||
356 | 1 | ethertype = UBInt16() |
|
357 | 1 | pad = Pad(2) |
|
358 | |||
359 | 1 | _allowed_types = (ActionType.OFPAT_PUSH_VLAN, ActionType.OFPAT_PUSH_MPLS, |
|
360 | ActionType.OFPAT_PUSH_PBB) |
||
361 | |||
362 | 1 | def __init__(self, action_type=None, ethertype=None): |
|
363 | """Create a ActionPush with the optional parameters below. |
||
364 | |||
365 | Args: |
||
366 | action_type (:class:`ActionType`): indicates which tag will be |
||
367 | pushed (VLAN, MPLS, PBB). |
||
368 | ethertype (int): indicates the Ethertype of the new tag. |
||
369 | """ |
||
370 | super().__init__(action_type, length=8) |
||
371 | self.ethertype = ethertype |
||
372 | |||
373 | |||
374 | 1 | class ActionSetField(ActionHeader): |
|
375 | """Action structure for OFPAT_SET_FIELD.""" |
||
376 | |||
377 | 1 | field = OxmTLV() |
|
378 | |||
379 | 1 | _allowed_types = (ActionType.OFPAT_SET_FIELD,) |
|
380 | |||
381 | 1 | def __init__(self, field=None): |
|
382 | """Create a ActionSetField with the optional parameters below. |
||
383 | |||
384 | Args: |
||
385 | length (int): length padded to 64 bits, followed by exactly |
||
386 | oxm_len bytes containing a single OXM TLV, then |
||
387 | exactly ((oxm_len + 4) + 7)/8*8 - (oxm_len + 4) |
||
388 | (between 0 and 7) bytes of all-zero bytes |
||
389 | field (:class:`OxmTLV`): OXM field and value. |
||
390 | """ |
||
391 | 1 | super().__init__(action_type=ActionType.OFPAT_SET_FIELD) |
|
392 | 1 | self.field = OxmTLV() if field is None else field |
|
393 | |||
394 | 1 | def __repr__(self): |
|
395 | return (f"{type(self).__name__}({self.field.oxm_field!s}, " |
||
396 | f"{self.field.oxm_value})") |
||
397 | |||
398 | 1 | def pack(self, value=None): |
|
399 | """Pack this structure updating the length and padding it.""" |
||
400 | 1 | self._update_length() |
|
401 | 1 | packet = super().pack() |
|
402 | 1 | return self._complete_last_byte(packet) |
|
403 | |||
404 | 1 | def _update_length(self): |
|
405 | """Update the length field of the struct.""" |
||
406 | 1 | action_length = 4 + len(self.field.pack()) |
|
407 | 1 | overflow = action_length % 8 |
|
408 | 1 | self.length = action_length |
|
409 | 1 | if overflow: |
|
410 | self.length = action_length + 8 - overflow |
||
411 | |||
412 | 1 | def _complete_last_byte(self, packet): |
|
413 | """Pad until the packet length is a multiple of 8 (bytes).""" |
||
414 | 1 | padded_size = self.length |
|
415 | 1 | padding_bytes = padded_size - len(packet) |
|
416 | 1 | if padding_bytes > 0: |
|
417 | packet += Pad(padding_bytes).pack() |
||
418 | 1 | return packet |
|
419 | |||
420 | |||
421 | 1 | class ActionSetQueue(ActionHeader): |
|
422 | """Action structure for OFPAT_SET_QUEUE.""" |
||
423 | |||
424 | 1 | queue_id = UBInt32() |
|
425 | |||
426 | 1 | _allowed_types = (ActionType.OFPAT_SET_QUEUE,) |
|
427 | |||
428 | 1 | def __init__(self, queue_id=None): |
|
429 | """Create an ActionSetQueue with the optional parameters below. |
||
430 | |||
431 | Args: |
||
432 | queue_id (int): The queue_id send packets to given queue on port. |
||
433 | """ |
||
434 | super().__init__(action_type=ActionType.OFPAT_SET_QUEUE, length=8) |
||
435 | self.queue_id = queue_id |
||
436 | |||
437 | |||
438 | 1 | class ListOfActions(FixedTypeList): |
|
439 | """List of actions. |
||
440 | |||
441 | Represented by instances of ActionHeader and used on ActionHeader objects. |
||
442 | """ |
||
443 | |||
444 | 1 | def __init__(self, items=None): |
|
445 | """Create a ListOfActions with the optional parameters below. |
||
446 | |||
447 | Args: |
||
448 | items (~pyof.v0x04.common.action.ActionHeader): |
||
449 | Instance or a list of instances. |
||
450 | """ |
||
451 | super().__init__(pyof_class=ActionHeader, items=items) |
||
452 |