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