|
1
|
|
|
"""For the controller to send a packet out through the datapath.""" |
|
2
|
1 |
|
from copy import deepcopy |
|
3
|
|
|
|
|
4
|
1 |
|
from pyof.foundation.base import GenericMessage |
|
5
|
1 |
|
from pyof.foundation.basic_types import BinaryData, Pad, UBInt16, UBInt32 |
|
6
|
1 |
|
from pyof.foundation.constants import UBINT32_MAX_VALUE |
|
7
|
1 |
|
from pyof.foundation.exceptions import PackException, ValidationError |
|
8
|
1 |
|
from pyof.v0x04.common.action import ListOfActions |
|
9
|
1 |
|
from pyof.v0x04.common.header import Header, Type |
|
10
|
1 |
|
from pyof.v0x04.common.port import PortNo |
|
11
|
|
|
|
|
12
|
1 |
|
__all__ = ('PacketOut',) |
|
13
|
|
|
|
|
14
|
|
|
# Classes |
|
15
|
|
|
|
|
16
|
|
|
#: in_port valid virtual port values, for validation |
|
17
|
1 |
|
_VIRT_IN_PORTS = (PortNo.OFPP_LOCAL, PortNo.OFPP_CONTROLLER, PortNo.OFPP_ANY) |
|
18
|
|
|
|
|
19
|
|
|
|
|
20
|
1 |
View Code Duplication |
class PacketOut(GenericMessage): |
|
|
|
|
|
|
21
|
|
|
"""Send packet (controller -> datapath).""" |
|
22
|
|
|
|
|
23
|
|
|
#: Openflow :class:`~pyof.v0x04.common.header.Header` |
|
24
|
1 |
|
header = Header(message_type=Type.OFPT_PACKET_OUT) |
|
25
|
|
|
#: ID assigned by datapath (OFP_NO_BUFFER if none). |
|
26
|
1 |
|
buffer_id = UBInt32() |
|
27
|
|
|
#: Packet’s input port or OFPP_CONTROLLER. |
|
28
|
1 |
|
in_port = UBInt32() |
|
29
|
|
|
#: Size of action array in bytes. |
|
30
|
1 |
|
actions_len = UBInt16() |
|
31
|
|
|
#: Padding |
|
32
|
1 |
|
pad = Pad(6) |
|
33
|
|
|
#: Action List. |
|
34
|
1 |
|
actions = ListOfActions() |
|
35
|
|
|
#: Packet data. The length is inferred from the length field in the header. |
|
36
|
|
|
#: (Only meaningful if buffer_id == -1.) |
|
37
|
1 |
|
data = BinaryData() |
|
38
|
|
|
|
|
39
|
1 |
|
def __init__(self, xid=None, buffer_id=UBINT32_MAX_VALUE, |
|
40
|
|
|
in_port=PortNo.OFPP_CONTROLLER, actions=None, |
|
41
|
|
|
data=b''): |
|
42
|
|
|
"""Create a PacketOut with the optional parameters below. |
|
43
|
|
|
|
|
44
|
|
|
Args: |
|
45
|
|
|
xid (int): xid of the message header. |
|
46
|
|
|
buffer_id (int): ID assigned by datapath (-1 if none). In this case |
|
47
|
|
|
UBINT32_MAX_VALUE is -1 for the field. |
|
48
|
|
|
in_port (:class:`int`, :class:`~pyof.v0x04.common.port.Port`): |
|
49
|
|
|
Packet's input port (:attr:`Port.OFPP_NONE` if none). |
|
50
|
|
|
Virtual ports OFPP_IN_PORT, OFPP_TABLE, OFPP_NORMAL, |
|
51
|
|
|
OFPP_FLOOD, and OFPP_ALL cannot be used as input port. |
|
52
|
|
|
actions (:class:`~pyof.v0x04.common.action.ListOfActions`): |
|
53
|
|
|
List of Action instances. |
|
54
|
|
|
data (bytes): Packet data. The length is inferred from the length |
|
55
|
|
|
field in the header. (Only meaningful if ``buffer_id`` == -1). |
|
56
|
|
|
""" |
|
57
|
1 |
|
super().__init__(xid) |
|
58
|
1 |
|
self.buffer_id = buffer_id |
|
59
|
1 |
|
self.in_port = in_port |
|
60
|
1 |
|
self.actions = [] if actions is None else actions |
|
61
|
1 |
|
self.data = data |
|
62
|
|
|
|
|
63
|
1 |
|
def validate(self): |
|
64
|
|
|
"""Validate the entire message.""" |
|
65
|
1 |
|
if not super().is_valid(): |
|
66
|
|
|
raise ValidationError() |
|
67
|
1 |
|
self._validate_in_port() |
|
68
|
|
|
|
|
69
|
1 |
|
def is_valid(self): |
|
70
|
|
|
"""Answer if this message is valid.""" |
|
71
|
1 |
|
try: |
|
72
|
1 |
|
self.validate() |
|
73
|
1 |
|
return True |
|
74
|
|
|
except ValidationError: |
|
75
|
|
|
return False |
|
76
|
|
|
|
|
77
|
1 |
|
def pack(self, value=None): |
|
78
|
|
|
"""Update the action_len attribute and call super's pack.""" |
|
79
|
|
|
if value is None: |
|
80
|
|
|
self._update_actions_len() |
|
81
|
|
|
return super().pack() |
|
82
|
|
|
elif isinstance(value, type(self)): |
|
83
|
|
|
return value.pack() |
|
84
|
|
|
else: |
|
85
|
|
|
msg = "{} is not an instance of {}".format(value, |
|
86
|
|
|
type(self).__name__) |
|
87
|
|
|
raise PackException(msg) |
|
88
|
|
|
|
|
89
|
1 |
|
def unpack(self, buff, offset=0): |
|
90
|
|
|
"""Unpack a binary message into this object's attributes. |
|
91
|
|
|
|
|
92
|
|
|
Unpack the binary value *buff* and update this object attributes based |
|
93
|
|
|
on the results. It is an inplace method and it receives the binary data |
|
94
|
|
|
of the message **without the header**. |
|
95
|
|
|
|
|
96
|
|
|
This class' unpack method is like the :meth:`.GenericMessage.unpack` |
|
97
|
|
|
one, except for the ``actions`` attribute which has a length determined |
|
98
|
|
|
by the ``actions_len`` attribute. |
|
99
|
|
|
|
|
100
|
|
|
Args: |
|
101
|
|
|
buff (bytes): Binary data package to be unpacked, without the |
|
102
|
|
|
header. |
|
103
|
|
|
offset (int): Where to begin unpacking. |
|
104
|
|
|
""" |
|
105
|
|
|
begin = offset |
|
106
|
|
|
for attribute_name, class_attribute in self.get_class_attributes(): |
|
107
|
|
|
if type(class_attribute).__name__ != "Header": |
|
108
|
|
|
attribute = deepcopy(class_attribute) |
|
109
|
|
|
if attribute_name == 'actions': |
|
110
|
|
|
length = self.actions_len.value |
|
111
|
|
|
attribute.unpack(buff[begin:begin+length]) |
|
112
|
|
|
else: |
|
113
|
|
|
attribute.unpack(buff, begin) |
|
114
|
|
|
setattr(self, attribute_name, attribute) |
|
115
|
|
|
begin += attribute.get_size() |
|
116
|
|
|
|
|
117
|
1 |
|
def _update_actions_len(self): |
|
118
|
|
|
"""Update the actions_len field based on actions value.""" |
|
119
|
|
|
if isinstance(self.actions, ListOfActions): |
|
120
|
|
|
self.actions_len = self.actions.get_size() |
|
121
|
|
|
else: |
|
122
|
|
|
self.actions_len = ListOfActions(self.actions).get_size() |
|
123
|
|
|
|
|
124
|
1 |
|
def _validate_in_port(self): |
|
125
|
1 |
|
is_valid_range = self.in_port > 0 and self.in_port <= PortNo.OFPP_MAX |
|
126
|
1 |
|
is_valid_virtual_in_ports = self.in_port in _VIRT_IN_PORTS |
|
127
|
|
|
|
|
128
|
1 |
|
if (is_valid_range or is_valid_virtual_in_ports) is False: |
|
129
|
|
|
raise ValidationError(f'{self.in_port} is not a valid input port.') |
|
130
|
|
|
|