File: packet_out.py

package info (click to toggle)
python-openflow 2021.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,224 kB
  • sloc: python: 6,906; sh: 4; makefile: 4
file content (127 lines) | stat: -rw-r--r-- 5,087 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
"""For the controller to send a packet out through the datapath."""
from copy import deepcopy

from pyof.foundation.base import GenericMessage
from pyof.foundation.basic_types import BinaryData, Pad, UBInt16, UBInt32
from pyof.foundation.constants import UBINT32_MAX_VALUE
from pyof.foundation.exceptions import PackException, ValidationError
from pyof.v0x04.common.action import ListOfActions
from pyof.v0x04.common.header import Header, Type
from pyof.v0x04.common.port import PortNo

__all__ = ('PacketOut',)

# Classes

#: in_port valid virtual port values, for validation
_VIRT_IN_PORTS = (PortNo.OFPP_LOCAL, PortNo.OFPP_CONTROLLER, PortNo.OFPP_ANY)


class PacketOut(GenericMessage):
    """Send packet (controller -> datapath)."""

    #: Openflow :class:`~pyof.v0x04.common.header.Header`
    header = Header(message_type=Type.OFPT_PACKET_OUT)
    #: ID assigned by datapath (OFP_NO_BUFFER if none).
    buffer_id = UBInt32()
    #: Packet’s input port or OFPP_CONTROLLER.
    in_port = UBInt32()
    #: Size of action array in bytes.
    actions_len = UBInt16()
    #: Padding
    pad = Pad(6)
    #: Action List.
    actions = ListOfActions()
    #: Packet data. The length is inferred from the length field in the header.
    #:    (Only meaningful if buffer_id == -1.)
    data = BinaryData()

    def __init__(self, xid=None, buffer_id=UBINT32_MAX_VALUE,
                 in_port=PortNo.OFPP_CONTROLLER, actions=None,
                 data=b''):
        """Create a PacketOut with the optional parameters below.

        Args:
            xid (int): xid of the message header.
            buffer_id (int): ID assigned by datapath (-1 if none). In this case
                UBINT32_MAX_VALUE is -1 for the field.
            in_port (:class:`int`, :class:`~pyof.v0x04.common.port.Port`):
                Packet's input port (:attr:`Port.OFPP_NONE` if none).
                Virtual ports OFPP_IN_PORT, OFPP_TABLE, OFPP_NORMAL,
                OFPP_FLOOD, and OFPP_ALL cannot be used as input port.
            actions (:class:`~pyof.v0x04.common.action.ListOfActions`):
                List of Action instances.
            data (bytes): Packet data. The length is inferred from the length
                field in the header. (Only meaningful if ``buffer_id`` == -1).
        """
        super().__init__(xid)
        self.buffer_id = buffer_id
        self.in_port = in_port
        self.actions = [] if actions is None else actions
        self.data = data

    def validate(self):
        """Validate the entire message."""
        if not super().is_valid():
            raise ValidationError()
        self._validate_in_port()

    def is_valid(self):
        """Answer if this message is valid."""
        try:
            self.validate()
            return True
        except ValidationError:
            return False

    def pack(self, value=None):
        """Update the action_len attribute and call super's pack."""
        if value is None:
            self._update_actions_len()
            return super().pack()
        if isinstance(value, type(self)):
            return value.pack()
        msg = "{} is not an instance of {}".format(value, type(self).__name__)
        raise PackException(msg)

    def unpack(self, buff, offset=0):
        """Unpack a binary message into this object's attributes.

        Unpack the binary value *buff* and update this object attributes based
        on the results. It is an inplace method and it receives the binary data
        of the message **without the header**.

        This class' unpack method is like the :meth:`.GenericMessage.unpack`
        one, except for the ``actions`` attribute which has a length determined
        by the ``actions_len`` attribute.

        Args:
            buff (bytes): Binary data package to be unpacked, without the
                header.
            offset (int): Where to begin unpacking.
        """
        begin = offset
        for attribute_name, class_attribute in self.get_class_attributes():
            if type(class_attribute).__name__ != "Header":
                attribute = deepcopy(class_attribute)
                if attribute_name == 'actions':
                    length = self.actions_len.value
                    attribute.unpack(buff[begin:begin+length])
                else:
                    attribute.unpack(buff, begin)
                setattr(self, attribute_name, attribute)
                begin += attribute.get_size()

    def _update_actions_len(self):
        """Update the actions_len field based on actions value."""
        if isinstance(self.actions, ListOfActions):
            self.actions_len = self.actions.get_size()
        else:
            self.actions_len = ListOfActions(self.actions).get_size()

    def _validate_in_port(self):
        is_valid_range = self.in_port > 0 and self.in_port <= PortNo.OFPP_MAX
        is_valid_virtual_in_ports = self.in_port in _VIRT_IN_PORTS

        if (is_valid_range or is_valid_virtual_in_ports) is False:
            raise ValidationError(f'{self.in_port} is not a valid input port.')