diff --git a/pyof/v0x05/__init__.py b/pyof/v0x05/__init__.py new file mode 100644 index 000000000..f29833e00 --- /dev/null +++ b/pyof/v0x05/__init__.py @@ -0,0 +1 @@ +"""The ofx parser package - spec version 0x05 (1.4.1).""" diff --git a/pyof/v0x05/asynchronous/__init__.py b/pyof/v0x05/asynchronous/__init__.py new file mode 100644 index 000000000..9a2270440 --- /dev/null +++ b/pyof/v0x05/asynchronous/__init__.py @@ -0,0 +1 @@ +"""Asynchronous messages.""" diff --git a/pyof/v0x05/asynchronous/packet_in.py b/pyof/v0x05/asynchronous/packet_in.py new file mode 100644 index 000000000..2300f3edc --- /dev/null +++ b/pyof/v0x05/asynchronous/packet_in.py @@ -0,0 +1,107 @@ +"""For packets received by the datapath and sent to the controller.""" + +# System imports + +from pyof.foundation.base import Enum, GenericMessage +from pyof.foundation.basic_types import ( + BinaryData, UBInt8, UBInt16, UBInt32, UBInt64) +from pyof.v0x05.common.flow_match import OPFMatch, OPFOxmOfbMatchField +from pyof.v0x05.common.header import Header, Type + +# Third-party imports + + +__all__ = ('OPFPacketIn', 'OPFPacketInReason') + +# Enums + + +class OPFPacketInReason(Enum): + """Reason why this packet is being sent to the controller.""" + + #: No matching flow (table- miss flow entry). + OFPR_TABLE_MISS = 0 + #: Output to controller in apply-actions. + OFPR_APPLY_ACTION = 1 + #: Packet has invalid TTL. + OFPR_INVALID_TTL = 2 + #: Output to controller in action set. + OFPR_ACTION_SET = 3 + #: Output to controller in group bucket. + OFPR_GROUP = 4 + #: Output to controller in packet-out. + OFPR_PACKET_OUT = 5 + + +# Classes + + +class OPFPacketIn(GenericMessage): + """Packet received on port (datapath -> controller).""" + + #: :class:`~pyof.v0x05.common.header.Header`: OpenFlow Header + header = Header(message_type=Type.OFPT_PACKET_IN) + #: ID assigned by datapath. + buffer_id = UBInt32() + #: Full length of frame. + total_len = UBInt16() + #: Reason packet is being sent (one of OFPR_*), + reason = UBInt8(enum_ref=OPFPacketInReason) + #: ID of the table that was looked up. + table_id = UBInt8() + #: Cookie of the flow entry that was looked up. + cookie = UBInt64() + #: Packet metadata. Variable size. + match = OPFMatch() + + #: Align to 64 bit + 16 bit + #: pad = Pad(2) + #: Ethernet frame whose length is inferred from header.length. + #: The padding bytes preceding the Ethernet frame ensure that the IP + #: header (if any) following the Ethernet header is 32-bit aligned. + data = BinaryData() + + def __init__(self, xid=None, buffer_id=None, total_len=None, reason=None, + table_id=None, cookie=None, match=None, data=b''): + """Assign parameters to object attributes. + + Args: + xid (int): Header's xid. + buffer_id (int): ID assigned by datapath. + total_len (int): Full length of frame. + reason (~pyof.v0x05.asynchronous.packet_in.PacketInReason): + The reason why the packet is being sent + table_id (int): ID of the table that was looked up + cookie (int): Cookie of the flow entry that was looked up + match (:class:`~pyof.v0x05.common.flow_match.Match`): + Packet metadata with variable size. + data (bytes): Ethernet frame, halfway through 32-bit word, so the + IP header is 32-bit aligned. The amount of data is inferred + from the length field in the header. Because of padding, + offsetof(struct ofp_packet_in, data) == + sizeof(struct ofp_packet_in) - 2. + """ + super().__init__(xid) + self.buffer_id = buffer_id + self.total_len = total_len + self.reason = reason + self.table_id = table_id + self.cookie = cookie + self.match = match + self.data = data + + @property + def in_port(self): + """Retrieve the 'in_port' that generated the PacketIn. + + This method will look for the OXM_TLV with type OFPXMT_OFB_IN_PORT on + the `oxm_match_fields` field from `match` field and return its value, + if the OXM exists. + + Returns: + The integer number of the 'in_port' that generated the PacketIn if + it exists. Otherwise return None. + + """ + in_port = self.match.get_field(OPFOxmOfbMatchField.OFPXMT_OFB_IN_PORT) + return int.from_bytes(in_port, 'big') diff --git a/pyof/v0x05/asynchronous/request_forward.py b/pyof/v0x05/asynchronous/request_forward.py new file mode 100644 index 000000000..c7fdad323 --- /dev/null +++ b/pyof/v0x05/asynchronous/request_forward.py @@ -0,0 +1,46 @@ +"""Defines a Request Forward Header.""" + +#: System imports. + +from pyof.foundation.base import Enum, GenericStruct +from pyof.v0x05.common.header import Header, Type + +#: Third-party imports. + + +__all__ = ('RequestForwardHeader', 'RequestForwardReason') + + +# Enums + +class RequestForwardReason(Enum): + """Request forward reason.""" + + #: Forward group mod requests. + OFPRFR_GROUP_MOD = 0 + #: Forward meter mod requests. + OFPRFR_METER_MOD = 1 + + +class RequestForwardHeader(GenericStruct): + """Group/Meter request forwarding.""" + + # :class:`~.header.Header`: OpenFlow Header + # :class:`~.header.Type`: OpenFlow Type + header = Header(Type.OFPT_REQUESTFORWARD) + # :class:`~.header.Header`: OpenFlow Header + request = Header() + + def __init__(self, header=Header(Type.OFPT_REQUESTFORWARD), + request=Header): + """Create an instance of the header. + + Args: + header (:class: `~pyof.v0x05.common.header.Header`): + :class: `~pyof.v0x05.common.header.Type` OFPT_REQUESTFORWARD. + request (:class: `~pyof.v0x05.common.header.Header`): + Request being forwarded. + """ + super().__init__() + self.header = header + self.request = request diff --git a/pyof/v0x05/common/__init__.py b/pyof/v0x05/common/__init__.py new file mode 100644 index 000000000..fe900bde7 --- /dev/null +++ b/pyof/v0x05/common/__init__.py @@ -0,0 +1 @@ +"""Common structures used on OpenFlow Protocol.""" diff --git a/pyof/v0x05/common/action.py b/pyof/v0x05/common/action.py new file mode 100644 index 000000000..83fc34f03 --- /dev/null +++ b/pyof/v0x05/common/action.py @@ -0,0 +1,482 @@ +"""Defines actions that may be associated with flows packets.""" +# System imports +from enum import IntEnum +from math import ceil + +# Local source tree imports +from pyof.foundation.base import GenericStruct +from pyof.foundation.basic_types import ( + FixedTypeList, Pad, UBInt8, UBInt16, UBInt32) +from pyof.v0x05.common.flow_match import OPFOxmTLV + +# Third-party imports + +__all__ = ('OPFActionExperimenterHeader', 'OPFActionGroup', 'OPFActionHeader', + 'OPFActionCopyTTLIn', 'OPFActionCopyTTLOut', 'OPFActionDecMPLSTTL', + 'OPFActionSetMPLSTTL', 'OPFActionDecNWTTL', 'OPFActionSetNWTTL', + 'OPFActionOutput', 'OPFActionPopMPLS', 'OPFActionPopPBB', + 'OPFActionPopVLAN', 'OPFActionPush', 'OPFActionSetField', + 'OPFActionSetQueue', 'OPFActionType', 'ControllerMaxLen', + 'ListOfActions') + +# Enums + + +class OPFActionType(IntEnum): + """Actions associated with flows and packets.""" + + #: Output to switch port. + OFPAT_OUTPUT = 0 + #: Copy TTL "outwards" -- from next-to-outermost to outermost + OFPAT_COPY_TTL_OUT = 11 + #: Copy TTL "inwards" -- from outermost to next-to-outermost + OFPAT_COPY_TTL_IN = 12 + #: MPLS TTL + OFPAT_SET_MPLS_TTL = 15 + #: Decrement MPLS TTL + OFPAT_DEC_MPLS_TTL = 16 + #: Push a new VLAN tag + OFPAT_PUSH_VLAN = 17 + #: Pop the outer VLAN tag + OFPAT_POP_VLAN = 18 + #: Push a new MPLS tag + OFPAT_PUSH_MPLS = 19 + #: Pop the outer MPLS tag + OFPAT_POP_MPLS = 20 + #: Set queue id when outputting to a port + OFPAT_SET_QUEUE = 21 + #: Apply group. + OFPAT_GROUP = 22 + #: IP TTL. + OFPAT_SET_NW_TTL = 23 + #: Decrement IP TTL. + OFPAT_DEC_NW_TTL = 24 + #: Set a header field using OXM TLV format. + OFPAT_SET_FIELD = 25 + #: Push a new PBB service tag (I-TAG) + OFPAT_PUSH_PBB = 26 + #: Pop the outer PBB service tag (I-TAG) + OFPAT_POP_PBB = 27 + #: Experimenter type + OFPAT_EXPERIMENTER = 0xffff + + +class ControllerMaxLen(IntEnum): + """A max_len of OFPCML_NO_BUFFER means not to buffer. + + The packet should be sent. + """ + + #: Maximum max_len value which can be used to request a specific byte + #: length. + OFPCML_MAX = 0xffe5 + #: indicates that no buffering should be applied and the whole packet is + #: to be sent to the controller. + OFPCML_NO_BUFFER = 0xffff + + +# Classes + + +class OPFActionHeader(GenericStruct): + """Action header that is common to all actions. + + The length includes the header and any padding used to make the action + 64-bit aligned. + NB: The length of an action *must* always be a multiple of eight. + """ + + #: One of OFPAT_*. + + action_type = UBInt16(enum_ref=OPFActionType) + #: Length of action, including this header. This is the length of actions, + #: including any padding to make it 64-bit aligned. + length = UBInt16() + + _allowed_types = () + + def __init__(self, action_type=None, length=None): + """Create an ActionHeader with the optional parameters below. + + Args: + action_type (~pyof.v0x05.common.action.ActionType): + The type of the action. + length (int): Length of action, including this header. + """ + super().__init__() + self.action_type = action_type + self.length = length + + def get_size(self, value=None): + """Return the action length including the padding (multiple of 8).""" + if isinstance(value, OPFActionHeader): + return value.get_size() + elif value is None: + current_size = super().get_size() + return ceil(current_size / 8) * 8 + raise ValueError(f'Invalid value "{value}" for Action*.get_size()') + + 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. + + Args: + buff (bytes): Binary data package to be unpacked. + offset (int): Where to begin unpacking. + + Raises: + Exception: If there is a struct unpacking error. + + """ + self.action_type = UBInt16(enum_ref=OPFActionType) + self.action_type.unpack(buff, offset) + + for cls in OPFActionHeader.__subclasses__(): + if self.action_type.value in cls.get_allowed_types(): + self.__class__ = cls + break + + super().unpack(buff, offset) + + @classmethod + def get_allowed_types(cls): + """Return allowed types for the class.""" + return cls._allowed_types + + +class OPFActionExperimenterHeader(OPFActionHeader): + """Action structure for OFPAT_EXPERIMENTER.""" + + # Experimenter ID + experimenter = UBInt32() + + _allowed_types = (OPFActionType.OFPAT_EXPERIMENTER, ) + + def __init__(self, length=None, experimenter=None): + """Create ActionExperimenterHeader with the optional parameters below. + + Args: + experimenter (int): The experimenter field is the Experimenter ID, + which takes the same form as in struct ofp_experimenter. + """ + super().__init__(action_type=OPFActionType.OFPAT_EXPERIMENTER) + self.length = length + self.experimenter = experimenter + + +class OPFExperimenterStruct(GenericStruct): + """Typical Experimenter structure.""" + + # Experimenter ID: + # - MSB 0: low-order bytes are IEEE OUI + # - MSB != 0: defined by ONF + experimenter = UBInt32() + # Experimenter defined + exp_type = UBInt32() + + experimenter_data = UBInt8() + + +class OPFActionGroup(OPFActionHeader): + """Action structure for OFPAT_GROUP.""" + + # Group identifier. + + group_id = UBInt32() + + _allowed_types = (OPFActionType.OFPAT_GROUP, ) + + def __init__(self, group_id=None): + """Create an ActionGroup with the optional parameters below. + + Args: + group_id (int): The group_id indicates the group used to process + this packet. The set of buckets to apply depends on the group + type. + """ + super().__init__(action_type=OPFActionType.OFPAT_GROUP, length=8) + self.group_id = group_id + + +class OPFActionDecMPLSTTL(OPFActionHeader): + """Action structure for OFPAT_DEC_MPLS_TTL.""" + + # Pad to 64 bits + + pad = Pad(4) + + _allowed_types = (OPFActionType.OFPAT_DEC_MPLS_TTL, ) + + def __init__(self): + """Create an ActionDecMPLSTTL.""" + super().__init__(action_type=OPFActionType.OFPAT_DEC_MPLS_TTL, + length=8) + + +class OPFActionSetMPLSTTL(OPFActionHeader): + """Action structure for OFPAT_SET_MPLS_TTL.""" + + # MPLS TTL + mpls_ttl = UBInt8() + + pad = Pad(3) + + _allowed_types = (OPFActionType.OFPAT_SET_MPLS_TTL, ) + + def __init__(self, mpls_ttl=None): + """Create an ActionSetMPLSTTL with the optional parameters below. + + Args: + mpls_ttl (int): The mpls_ttl field is the MPLS TTL to set. + """ + super().__init__(action_type=OPFActionType.OFPAT_SET_MPLS_TTL, + length=8) + self.mpls_ttl = mpls_ttl + + +class OPFActionCopyTTLIn(OPFActionHeader): + """Action structure for OFPAT_COPY_TTL_IN.""" + + pad = Pad(4) + + _allowed_types = (OPFActionType.OFPAT_COPY_TTL_IN, ) + + def __init__(self): + """Create an ActionCopyTTLIn.""" + super().__init__(action_type=OPFActionType.OFPAT_COPY_TTL_IN, + length=8) + + +class OPFActionCopyTTLOut(OPFActionHeader): + """Action structure for OFPAT_COPY_TTL_OUT.""" + + pad = Pad(4) + + _allowed_types = (OPFActionType.OFPAT_COPY_TTL_OUT, ) + + def __init__(self): + """Create an ActionCopyTTLOut.""" + super().__init__(action_type=OPFActionType.OFPAT_COPY_TTL_OUT, + length=8) + + +class OPFActionPopVLAN(OPFActionHeader): + """Action structure for OFPAT_POP_VLAN.""" + + pad = Pad(4) + + _allowed_types = (OPFActionType.OFPAT_POP_VLAN, ) + + def __init__(self): + """Create an ActionPopVLAN.""" + super().__init__(action_type=OPFActionType.OFPAT_POP_VLAN, length=8) + + +class OPFActionPopPBB(OPFActionHeader): + """Action structure for OFPAT_POP_PBB.""" + + pad = Pad(4) + + _allowed_types = (OPFActionType.OFPAT_POP_PBB, ) + + def __init__(self): + """Create an ActionPopPBB.""" + super().__init__(action_type=OPFActionType.OFPAT_POP_PBB, length=8) + + +class OPFActionDecNWTTL(OPFActionHeader): + """Action structure for OFPAT_DEC_NW_TTL.""" + + pad = Pad(4) + + _allowed_types = (OPFActionType.OFPAT_DEC_NW_TTL, ) + + def __init__(self): + """Create a ActionDecNWTTL.""" + super().__init__(action_type=OPFActionType.OFPAT_DEC_NW_TTL, length=8) + + +class OPFActionSetNWTTL(OPFActionHeader): + """Action structure for OFPAT_SET_NW_TTL.""" + + # IP TTL + nw_ttl = UBInt8() + + pad = Pad(3) + + _allowed_types = (OPFActionType.OFPAT_SET_NW_TTL, ) + + def __init__(self, nw_ttl=None): + """Create an ActionSetNWTTL with the optional parameters below. + + Args: + nw_ttl (int): the TTL address to set in the IP header. + + """ + super().__init__(action_type=OPFActionType.OFPAT_SET_NW_TTL, length=8) + self.nw_ttl = nw_ttl + + +class OPFActionOutput(OPFActionHeader): + """Action structure for OFPAT_OUTPUT. + + When the ’port’ is the OFPP_CONTROLLER, ’max_len’ indicates the max + number of bytes to send. A ’max_len’ of zero means no bytes of the + packet should be sent. A ’max_len’ of OFPCML_NO_BUFFER means that + the packet is not buffered and the complete packet is to be sent to + the controller. + + """ + + # Extend the ActionHeader. + # Output port. + port = UBInt32() + # Max length to send to controller. + max_length = UBInt16() + # Pad to 64 bits. + + pad = Pad(6) + + _allowed_types = (OPFActionType.OFPAT_OUTPUT, ) + + def __init__(self, port=None, + max_length=ControllerMaxLen.OFPCML_NO_BUFFER): + """Create a ActionOutput with the optional parameters below. + + Args: + port (:class:`Port` or :class:`int`): Output port. + max_length (int): Max length to send to controller. + """ + super().__init__(action_type=OPFActionType.OFPAT_OUTPUT, length=16) + self.port = port + self.max_length = max_length + + +class OPFActionPopMPLS(OPFActionHeader): + """Action structure for OFPAT_POP_MPLS.""" + + # Ethertype + ethertype = UBInt16() + + pad = Pad(2) + + _allowed_types = (OPFActionType.OFPAT_POP_MPLS, ) + + def __init__(self, ethertype=None): + """Create an ActionPopMPLS with the optional parameters below. + + Args: + ethertype (int): indicates the Ethertype of the payload. + """ + super().__init__(action_type=OPFActionType.OFPAT_POP_MPLS) + self.ethertype = ethertype + + +class OPFActionPush(OPFActionHeader): + """Action structure for OFPAT_PUSH_[VLAN/MPLS/PBB].""" + + # Ethertype + ethertype = UBInt16() + + pad = Pad(2) + + _allowed_types = (OPFActionType. OFPAT_PUSH_VLAN, + OPFActionType.OFPAT_PUSH_MPLS, + OPFActionType.OFPAT_PUSH_PBB, ) + + def __init__(self, action_type=None, ethertype=None): + """Create a ActionPush with the optional parameters below. + + Args: + action_type (:class:`ActionType`): indicates which tag will be + pushed (VLAN, MPLS, PBB). + ethertype (int): indicates the Ethertype of the new tag. + """ + super().__init__(action_type, length=8) + self.ethertype = ethertype + + +class OPFActionSetField(OPFActionHeader): + """Action structure for OFPAT_SET_FIELD.""" + + # + # + + # OXM TLV - Make compiler happy + + field = OPFOxmTLV() + + _allowed_types = (OPFActionType.OFPAT_SET_FIELD, ) + + def __init__(self, length=None, field=None): + """Create a ActionSetField with the optional parameters below. + + Args: + length (int): length padded to 64 bits, followed by exactly + oxm_len bytes containing a single OXM TLV, then + exactly ((oxm_len + 4) + 7)/8*8 - (oxm_len + 4) + (between 0 and 7) bytes of all-zero bytes + field (:class:`OxmTLV`): OXM field and value. + """ + super().__init__(action_type=OPFActionType.OFPAT_SET_FIELD, + length=length) + self.field = OPFOxmTLV() if field is None else field + + def pack(self, value=None): + """Pack this structure updating the length and padding it.""" + self._update_length() + packet = super().pack() + return self._complete_last_byte(packet) + + def _update_length(self): + """Update the length field of the struct.""" + action_length = 4 + len(self.field.pack()) + overflow = action_length % 8 + self.length = action_length + if overflow: + self.length = action_length + 8 - overflow + + def _complete_last_byte(self, packet): + """Pad until the packet length is a multiple of 8 (bytes).""" + padded_size = self.length + padding_bytes = padded_size - len(packet) + if padding_bytes > 0: + packet += Pad(padding_bytes).pack() + return packet + + +class OPFActionSetQueue(OPFActionHeader): + """Action structure for OFPAT_SET_QUEUE.""" + + # Queue id for packets. + + queue_id = UBInt32() + + _allowed_types = (OPFActionType.OFPAT_SET_QUEUE, ) + + def __init__(self, queue_id=None): + """Create an ActionSetQueue with the optional parameters below. + + Args: + queue_id (int): The queue_id send packets to given queue on port. + """ + super().__init__(action_type=OPFActionType.OFPAT_SET_QUEUE, length=8) + self.queue_id = queue_id + + +class ListOfActions(FixedTypeList): + """List of actions. + + Represented by instances of ActionHeader and used on ActionHeader objects. + """ + + def __init__(self, items=None): + """Create a ListOfActions with the optional parameters below. + + Args: + items (~pyof.v0x05.common.action.ActionHeader): + Instance or a list of instances. + """ + super().__init__(pyof_class=OPFActionHeader, items=items) diff --git a/pyof/v0x05/common/constants.py b/pyof/v0x05/common/constants.py new file mode 100644 index 000000000..c6f62ed78 --- /dev/null +++ b/pyof/v0x05/common/constants.py @@ -0,0 +1,4 @@ +"""Here we have the constants related to v0x05 version.""" + +OFP_VERSION = 0x05 +OFP_NO_BUFFER = 0xffffffff diff --git a/pyof/v0x05/common/flow_match.py b/pyof/v0x05/common/flow_match.py new file mode 100644 index 000000000..36472b5bc --- /dev/null +++ b/pyof/v0x05/common/flow_match.py @@ -0,0 +1,465 @@ +"""Match strucutre and related enums. + +An OpenFlow match is composed of a flow match header and a sequence of zero or +more flow match fields. +""" +# System imports +from enum import IntEnum +from math import ceil + +# Local source tree imports +from pyof.foundation.base import GenericBitMask, GenericStruct +from pyof.foundation.basic_types import ( + BinaryData, FixedTypeList, Pad, UBInt8, UBInt16, UBInt32) +from pyof.foundation.exceptions import PackException, UnpackException + +__all__ = ('Ipv6ExtHdrFlags', 'ListOfOxmHeader', 'OPFMatch', 'OPFMatchType', + 'OPFOxmClass', 'OxmExperimenterHeader', 'OPFOxmMatchFields', + 'OPFOxmOfbMatchField', 'OPFOxmTLV', 'VlanId') + + +class Ipv6ExtHdrFlags(GenericBitMask): + """Bit definitions for IPv6 Extension Header pseudo-field.""" + + #: "No next header" encountered. + OFPIEH_NONEXT = 1 << 0 + #: Encrypted Sec Payload header present. + OFPIEH_ESP = 1 << 1 + #: Authentication header present. + OFPIEH_AUTH = 1 << 2 + #: 1 or 2 dest headers present. + OFPIEH_DEST = 1 << 3 + #: Fragment header present. + OFPIEH_FRAG = 1 << 4 + #: Router header present. + OFPIEH_ROUTER = 1 << 5 + #: Hop-by-hop header present. + OFPIEH_HOP = 1 << 6 + #: Unexpected repeats encountered. + OFPIEH_UNREP = 1 << 7 + #: Unexpected sequencing encountered. + OFPIEH_UNSEQ = 1 << 8 + + +class OPFOxmOfbMatchField(IntEnum): + """OXM Flow match field types for OpenFlow basic class. + + A switch is not required to support all match field types, just those + listed in the Table 10. Those required match fields don’t need to be + implemented in the same table lookup. The controller can query the switch + about which other fields it supports. + """ + + #: Switch input port. + OFPXMT_OFB_IN_PORT = 0 + #: Switch physical input port. + OFPXMT_OFB_IN_PHY_PORT = 1 + #: Metadata passed between tables. + OFPXMT_OFB_METADATA = 2 + #: Ethernet destination address. + OFPXMT_OFB_ETH_DST = 3 + #: Ethernet source address. + OFPXMT_OFB_ETH_SRC = 4 + #: Ethernet frame type. + OFPXMT_OFB_ETH_TYPE = 5 + #: VLAN id. + OFPXMT_OFB_VLAN_VID = 6 + #: VLAN priority. + OFPXMT_OFB_VLAN_PCP = 7 + #: IP DSCP (6 bits in ToS field). + OFPXMT_OFB_IP_DSCP = 8 + #: IP ECN (2 bits in ToS field). + OFPXMT_OFB_IP_ECN = 9 + #: IP protocol. + OFPXMT_OFB_IP_PROTO = 10 + #: IPv4 source address. + OFPXMT_OFB_IPV4_SRC = 11 + #: IPv4 destination address. + OFPXMT_OFB_IPV4_DST = 12 + #: TCP source port. + OFPXMT_OFB_TCP_SRC = 13 + #: TCP destination port. + OFPXMT_OFB_TCP_DST = 14 + #: UDP source port. + OFPXMT_OFB_UDP_SRC = 15 + #: UDP destination port. + OFPXMT_OFB_UDP_DST = 16 + #: SCTP source port. + OFPXMT_OFB_SCTP_SRC = 17 + #: SCTP destination port. + OFPXMT_OFB_SCTP_DST = 18 + #: ICMP type. + OFPXMT_OFB_ICMPV4_TYPE = 19 + #: ICMP code. + OFPXMT_OFB_ICMPV4_CODE = 20 + #: ARP opcode. + OFPXMT_OFB_ARP_OP = 21 + #: ARP source IPv4 address. + OFPXMT_OFB_ARP_SPA = 22 + #: ARP target IPv4 address. + OFPXMT_OFB_ARP_TPA = 23 + #: ARP source hardware address. + OFPXMT_OFB_ARP_SHA = 24 + #: ARP target hardware address. + OFPXMT_OFB_ARP_THA = 25 + #: IPv6 source address. + OFPXMT_OFB_IPV6_SRC = 26 + #: IPv6 destination address. + OFPXMT_OFB_IPV6_DST = 27 + #: IPv6 Flow Label + OFPXMT_OFB_IPV6_FLABEL = 28 + #: ICMPv6 type. + OFPXMT_OFB_ICMPV6_TYPE = 29 + #: ICMPv6 code. + OFPXMT_OFB_ICMPV6_CODE = 30 + #: Target address for ND. + OFPXMT_OFB_IPV6_ND_TARGET = 31 + #: Source link-layer for ND. + OFPXMT_OFB_IPV6_ND_SLL = 32 + #: Target link-layer for ND. + OFPXMT_OFB_IPV6_ND_TLL = 33 + #: MPLS label. + OFPXMT_OFB_MPLS_LABEL = 34 + #: MPLS TC. + OFPXMT_OFB_MPLS_TC = 35 + #: MPLS BoS bit. + OFPXMT_OFP_MPLS_BOS = 36 + #: PBB I-SID. + OFPXMT_OFB_PBB_ISID = 37 + #: Logical Port Metadata. + OFPXMT_OFB_TUNNEL_ID = 38 + #: IPv6 Extension Header pseudo-field + OFPXMT_OFB_IPV6_EXTHDR = 39 + + #: PBB UCA header field. + OFPXMT_OFB_PBB_UCA = 41 + + +class OPFMatchType(IntEnum): + """Indicates the match structure in use. + + The match type is placed in the type field at the beginning of all match + structures. The "OpenFlow Extensible Match" type corresponds to OXM TLV + format described below and must be supported by all OpenFlow switches. + Extensions that define other match types may be published on the ONF wiki. + Support for extensions is optional + """ + + #: Deprecated + OFPMT_STANDARD = 0 + #: OpenFlow Extensible Match + OFPMT_OXM = 1 + + +class OPFOxmClass(IntEnum): + """OpenFlow Extensible Match (OXM) Class IDs. + + The high order bit differentiate reserved classes from member classes. + Classes 0x0000 to 0x7FFF are member classes, allocated by ONF. + Classes 0x8000 to 0xFFFE are reserved classes, reserved for + standardisation. + """ + + #: Backward compatibility with NXM + OFPXMC_NXM_0 = 0x0000 + #: Backward compatibility with NXM + OFPXMC_NXM_1 = 0x0001 + #: Basic class for OpenFlow + OFPXMC_OPENFLOW_BASIC = 0x8000 + #: Experimenter class + OFPXMC_EXPERIMENTER = 0xFFFF + + +class VlanId(IntEnum): + """Indicates conditions of the Vlan. + + The VLAN id is 12-bits, so we can use the entire 16 bits to indicate + special conditions. + """ + + #: Bit that indicate that a VLAN id is set. + OFPVID_PRESENT = 0x1000 + #: No VLAN id was set + OFPVID_NONE = 0x0000 + + +# Classes + +class OPFOxmTLV(GenericStruct): + """Oxm (OpenFlow Extensible Match) TLV.""" + + oxm_class = UBInt16(enum_ref=OPFOxmClass) + oxm_field_and_mask = UBInt8() + oxm_length = UBInt8() + oxm_value = BinaryData() + + def __init__(self, oxm_class=OPFOxmClass.OFPXMC_OPENFLOW_BASIC, + oxm_field=None, oxm_hasmask=False, oxm_value=None): + """Create an OXM TLV struct with the optional parameters below. + + Args: + oxm_class (OxmClass): Match class: member class or reserved class + oxm_field (OxmMatchFields, OxmOfbMatchField): Match field within + the class + oxm_hasmask (bool): Set if OXM include a bitmask in payload + oxm_value (bytes): OXM Payload + + """ + super().__init__() + self.oxm_class = oxm_class + self.oxm_field_and_mask = None + self.oxm_length = None + self.oxm_value = oxm_value + # Attributes that are not packed + self.oxm_field = oxm_field + self.oxm_hasmask = oxm_hasmask + + def unpack(self, buff, offset=0): + """Unpack the buffer into a OxmTLV. + + Args: + buff (bytes): The binary data to be unpacked. + offset (int): If we need to shift the beginning of the data. + + """ + super().unpack(buff, offset) + # Recover field from field_and_hasmask. + try: + self.oxm_field = self._unpack_oxm_field() + except ValueError as exception: + raise UnpackException(exception) + + # The last bit of field_and_mask is oxm_hasmask + self.oxm_hasmask = (self.oxm_field_and_mask & 1) == 1 # as boolean + + # Unpack oxm_value that has oxm_length bytes + start = offset + 4 # 4 bytes: class, field_and_mask and length + end = start + self.oxm_length + self.oxm_value = buff[start:end] + + def _unpack_oxm_field(self): + """Unpack oxm_field from oxm_field_and_mask. + + Returns: + :class:`OxmOfbMatchField`, int: oxm_field from oxm_field_and_mask. + + Raises: + ValueError: If oxm_class is OFPXMC_OPENFLOW_BASIC but + :class:`OxmOfbMatchField` has no such integer value. + + """ + field_int = self.oxm_field_and_mask >> 1 + # We know that the class below requires a subset of the ofb enum + if self.oxm_class == OPFOxmClass.OFPXMC_OPENFLOW_BASIC: + return OPFOxmOfbMatchField(field_int) + return field_int + + def _update_length(self): + """Update length field. + + Update the oxm_length field with the packed payload length. + + """ + payload = type(self).oxm_value.pack(self.oxm_value) + self.oxm_length = len(payload) + + def pack(self, value=None): + """Join oxm_hasmask bit and 7-bit oxm_field.""" + if value is not None: + return value.pack() + + # Set oxm_field_and_mask instance attribute + # 1. Move field integer one bit to the left + try: + field_int = self._get_oxm_field_int() + except ValueError as exception: + raise PackException(exception) + field_bits = field_int << 1 + # 2. hasmask bit + hasmask_bit = self.oxm_hasmask & 1 + # 3. Add hasmask bit to field value + self.oxm_field_and_mask = field_bits + hasmask_bit + + self._update_length() + return super().pack(value) + + def _get_oxm_field_int(self): + """Return a valid integer value for oxm_field. + + Used while packing. + + Returns: + int: valid oxm_field value. + + Raises: + ValueError: If :attribute:`oxm_field` is bigger than 7 bits or + should be :class:`OxmOfbMatchField` and the enum has no such + value. + + """ + if self.oxm_class == OPFOxmClass.OFPXMC_OPENFLOW_BASIC: + return OPFOxmOfbMatchField(self.oxm_field).value + elif not isinstance(self.oxm_field, int) or self.oxm_field > 127: + raise ValueError('oxm_field above 127: "{self.oxm_field}".') + return self.oxm_field + + +class OPFOxmMatchFields(FixedTypeList): + """Generic Openflow EXtensible Match header. + + Abstract class that can be instantiated as Match or OxmExperimenterHeader. + + """ + + def __init__(self, items=None): + """Initialize ``items`` attribute. + + Args: + items (OxmHeader): Instance or a list of instances. + """ + super().__init__(pyof_class=OPFOxmTLV, items=items) + + +class OPFMatch(GenericStruct): + """Describes the flow match header structure. + + These are the fields to match against flows. + + The :attr:`~type` field is set to :attr:`~MatchType.OFPMT_OXM` and + + :attr:`length` field is set to the actual length of match structure + including all match fields. The payload of the OpenFlow match is a set of + OXM Flow match fields. + + """ + + # One of OFPMT_* + match_type = UBInt16(enum_ref=OPFMatchType) + # Length of Match (excluding padding) + length = UBInt16() + # 0 or more OXM match fields. + oxm_match_fields = OPFOxmMatchFields() + # Zero bytes - see above for sizing + pad = Pad(4) + + def __init__(self, match_type=OPFMatchType.OFPMT_OXM, + length=None, oxm_match_fields=None): + """Describe the flow match header structure. + + Args: + match_type (MatchType): One of OFPMT_* (MatchType) items. + length (int): Length of Match (excluding padding) followed by + Exactly (length - 4) (possibly 0) bytes containing + OXM TLVs, then exactly ((length + 7)/8*8 - length) + (between 0 and 7) bytes of all-zero bytes. + oxm_match_fields (OxmMatchFields): Sample description. + + """ + super().__init__() + self.match_type = match_type + self.length = length + self.oxm_match_fields = oxm_match_fields or OPFOxmMatchFields() + self._update_match_length() + + def _update_match_length(self): + """Update the match length field.""" + self.length = super().get_size() + + def pack(self, value=None): + """Pack and complete the last byte by padding.""" + if isinstance(value, OPFMatch): + return value.pack() + elif value is None: + self._update_match_length() + packet = super().pack() + return self._complete_last_byte(packet) + raise PackException(f'Match can\'t unpack "{value}".') + + def _complete_last_byte(self, packet): + """Pad until the packet length is a multiple of 8 (bytes).""" + padded_size = self.get_size() + padding_bytes = padded_size - len(packet) + if padding_bytes > 0: + packet += Pad(padding_bytes).pack() + return packet + + def get_size(self, value=None): + """Return the packet length including the padding (multiple of 8).""" + if isinstance(value, OPFMatch): + return value.get_size() + elif value is None: + current_size = super().get_size() + return ceil(current_size / 8) * 8 + raise ValueError(f'Invalid value "{value}" for Match.get_size()') + + def unpack(self, buff, offset=0): + """Discard padding bytes using the unpacked length attribute.""" + begin = offset + size = 0 + for name, value in list(self.get_class_attributes())[:-1]: + size = self._unpack_attribute(name, value, buff, begin) + begin += size + self._unpack_attribute('oxm_match_fields', + type(self).oxm_match_fields, + buff[:offset+self.length - len(Pad(4))], + begin - size) + + def get_field(self, field_type): + """Return the value for the 'field_type' field in oxm_match_fields. + + Args: + + field_type (~pyof.v0x05.common.flow_match.OxmOfbMatchField, + ~pyof.v0x05.common.flow_match.OxmMatchFields): + + The type of the OXM field you want the value. + + Returns: + The integer number of the 'field_type' if it exists. Otherwise + return None. + + """ + for field in self.oxm_match_fields: + if field.oxm_field == field_type: + return field.oxm_value + + return None + + +class OxmExperimenterHeader(GenericStruct): + """Header for OXM experimenter match fields.""" + + #: oxm_class = OFPXMC_EXPERIMENTER + oxm_header = UBInt32(OPFOxmClass.OFPXMC_EXPERIMENTER, + enum_ref=OPFOxmClass) + #: Experimenter ID which takes the same form as in struct + #: ofp_experimenter_header + experimenter = UBInt32() + + def __init__(self, experimenter=None): + """Initialize ``experimenter`` attribute. + + Args: + experimenter (int): Experimenter ID which takes the same form as + in struct ofp_experimenter_header + + """ + super().__init__() + self.experimenter = experimenter + + +class ListOfOxmHeader(FixedTypeList): + """List of Openflow Extensible Match header instances. + + Represented by instances of OxmHeader. + + """ + + def __init__(self, items=None): + """Initialize ``items`` attribute. + + Args: + items (OxmHeader): Instance or a list of instances. + + """ + super().__init__(pyof_class=OPFOxmTLV, items=items) diff --git a/pyof/v0x05/common/header.py b/pyof/v0x05/common/header.py new file mode 100644 index 000000000..1acf849a5 --- /dev/null +++ b/pyof/v0x05/common/header.py @@ -0,0 +1,122 @@ +"""Defines Header classes and related items.""" + +# System imports +from enum import IntEnum + +# Local source tree imports +from pyof.foundation.base import GenericStruct +from pyof.foundation.basic_types import UBInt8, UBInt16, UBInt32 +from pyof.v0x05.common.constants import OFP_VERSION + +# Third-party imports + +__all__ = ('Header', 'Type') + +# Enums + + +class Type(IntEnum): + """Enumeration of Message Types.""" + + # Symmetric/Immutable messages + + OFPT_HELLO = 0 + OFPT_ERROR = 1 + OFPT_ECHO_REQUEST = 2 + OFPT_ECHO_REPLY = 3 + OFPT_EXPERIMENTER = 4 + + # Switch configuration messages + # Controller/Switch messages + OFPT_FEATURES_REQUEST = 5 + OFPT_FEATURES_REPLY = 6 + OFPT_GET_CONFIG_REQUEST = 7 + OFPT_GET_CONFIG_REPLY = 8 + OFPT_SET_CONFIG = 9 + + # Async messages + OFPT_PACKET_IN = 10 + OFPT_FLOW_REMOVED = 11 + OFPT_PORT_STATUS = 12 + + # Controller command messages + # Controller/Switch message + OFPT_PACKET_OUT = 13 + OFPT_FLOW_MOD = 14 + OFPT_GROUP_MOD = 15 + OFPT_PORT_MOD = 16 + OFPT_TABLE_MOD = 17 + + # Multipart messages. + # Controller/Switch message + OFPT_MULTIPART_REQUEST = 18 + OFPT_MULTIPART_REPLY = 19 + + # Barrier messages + # Controller/Switch message + OFPT_BARRIER_REQUEST = 20 + OFPT_BARRIER_REPLY = 21 + + # Queue Configuration messages + # Controller/Switch message + + # OFPT_QUEUE_GET_CONFIG_REQUEST = 22 + # OFPT_QUEUE_GET_CONFIG_REPLY = 23 + + # Controller role change request message + # Controller/Switch message + OFPT_ROLE_REQUEST = 24 + OFPT_ROLE_REPLY = 25 + + # Asynchronous message configuration + # Controller/Switch message + OFPT_GET_ASYNC_REQUEST = 26 + OFPT_GET_ASYNC_REPLY = 27 + OFPT_SET_ASYNC = 28 + + # Meters and rate limiters configuration messages + # Controller/Switch message + OFPT_METER_MOD = 29 + + # Controller role change event messages + # Async message + OFPT_ROLE_STATUS = 30 + + # Asynchronous message + # Async message + OFPT_TABLE_STATUS = 31 + + # Request forwarding by switch + # Async message + OFPT_REQUESTFORWARD = 32 + + # Bundle operations (Multiple messages as a single operation) + # Controller/Switch message + OFPT_BUNDLE_CONTROL = 33 + OFPT_BUNDLE_ADD_MESSAGE = 34 + + +# Classes + +class Header(GenericStruct): + """Representation of an OpenFlow message Header.""" + + version = UBInt8(OFP_VERSION) + message_type = UBInt8(enum_ref=Type) + length = UBInt16() + xid = UBInt32() + + def __init__(self, message_type=None, length=None, xid=None): + """Create a Header with the optional parameters below. + + Args: + message_type (~pyof.v0x05.common.header.Type): + One of the OFPT_* constants. + length (int): Length including this ofp_header. + xid (int): Transaction id associated with this packet. Replies use + the same id as was in the request to facilitate pairing. + """ + super().__init__() + self.message_type = message_type + self.length = length + self.xid = xid diff --git a/pyof/v0x05/common/port.py b/pyof/v0x05/common/port.py new file mode 100644 index 000000000..fe47cf5e6 --- /dev/null +++ b/pyof/v0x05/common/port.py @@ -0,0 +1,429 @@ +"""Defines physical port classes and related items.""" + +# System imports + +# Local source tree imports +from pyof.foundation.base import Enum, GenericBitMask, GenericStruct +from pyof.foundation.basic_types import ( + Char, FixedTypeList, HWAddress, Pad, UBInt16, UBInt32) +from pyof.foundation.constants import OFP_MAX_PORT_NAME_LEN + +# Third-party imports + +__all__ = ('ListOfPortDescProperties', 'ListOfPorts', 'OPFPort', + 'OPFPortConfig', 'OPFPortFeatures', 'OPFPortNo', + 'OPFPortState') + + +class OPFPortNo(Enum): + """Port numbering. + + Ports are numbered starting from 1. + """ + + #: Maximum number of physical and logical switch ports. + OFPP_MAX = 0xffffff00 + # Reserved OpenFlow port (fake output "ports") + #: Send the packet out the input port. This reserved port must be + #: explicitly used in order to send back out of the input port. + OFPP_IN_PORT = 0xfffffff8 + #: Submit the packet to the first flow table + #: NB: This destination port can only be used in packet-out messages. + OFPP_TABLE = 0xfffffff9 + #: Process with normal L2/L3 switching. + OFPP_NORMAL = 0xfffffffa + #: All physical ports in VLAN, except input port and thos blocked or link + #: down. + OFPP_FLOOD = 0xfffffffb + #: All physical ports except input port + OFPP_ALL = 0xfffffffc + #: Send to controller + OFPP_CONTROLLER = 0xfffffffd + #: Local openflow "port" + OFPP_LOCAL = 0xfffffffe + #: Wildcard port used only for flow mod (delete) and flow stats requests. + #: Selects all flows regardless of output port (including flows with no + #: output port). + OFPP_ANY = 0xffffffff + + +class OPFPortDescPropType(Enum): + """Port description property types.""" + + # Ethernet property + OFPPDPT_ETHERNET = 0 + # Optical property + OFPPDPT_OPTICAL = 1 + # Experimenter property + OFPPDPT_EXPERIMENTER = 0xfff + + +class OPFOpticalPortFeatures(GenericBitMask): + """Features of optical ports available in switch.""" + + # Receiver is tunable. + OFPOPF_RX_TUNE = 1 << 0 + # Transmit is tunable. + OFPOPF_TX_TUNE = 1 << 1 + # Power is configurable. + OFPOPF_TX_PWR = 1 << 2 + # Use Frequency, not wavelength + OFPOPF_USE_FREQ = 1 << 3 + + +class OPFPortConfig(GenericBitMask): + """Flags to indicate behavior of the physical port. + + These flags are used in :class:`Port` to describe the current + configuration. They are used in the + :class:`~pyof.v0x05.controller2switch.port_mod.PortMod` + message to configure the port's behavior. + + The :attr:`OFPPC_PORT_DOWN` bit indicates that the port has been + administratively brought down and should not be used by OpenFlow. The + :attr:`~OFPPC_NO_RECV` bit indicates that packets received on that port + should be ignored. The :attr:`OFPPC_NO_FWD` bit indicates that OpenFlow + should not send packets to that port. The :attr:`OFPPC_NO_PACKET_IN` bit + indicates that packets on that port that generate a table miss should never + trigger a packet-in message to the controller. + + In general, the port config bits are set by the controller and not changed + by the switch. Those bits may be useful for the controller to implement + protocols such as STP or BFD. If the port config bits are changed by the + switch through another administrative interface, the switch sends an + :attr:`OFPT_PORT_STATUS` message to notify the controller of the change. + """ + + #: Port is administratively down. + OFPPC_PORT_DOWN = 1 << 0 + #: Drop all packets received by port. + OFPPC_NO_RECV = 1 << 2 + #: Drop packets forwarded to port. + OFPPC_NO_FWD = 1 << 5 + #: Do not send packet-in msgs for port. + OFPPC_NO_PACKET_IN = 1 << 6 + + +class OPFPortFeatures(GenericBitMask): + """Physical ports features. + + The curr, advertised, supported, and peer fields indicate link modes + (speed and duplexity), link type (copper/fiber) and link features + (autonegotiation and pause). + + Multiple of these flags may be set simultaneously. If none of the port + speed flags are set, the max_speed or curr_speed are used. + + The curr_speed and max_speed fields indicate the current and maximum bit + rate (raw transmission speed) of the link in kbps. The number should be + rounded to match common usage. For example, an optical 10 Gb Ethernet port + should have this field set to 10000000 (instead of 10312500), and an OC-192 + port should have this field set to 10000000 (instead of 9953280). + + The max_speed fields indicate the maximum configured capacity of the link, + whereas the curr_speed indicates the current capacity. If the port is a LAG + with 3 links of 1Gb/s capacity, with one of the ports of the LAG being + down, one port auto-negotiated at 1Gb/s and 1 port auto-negotiated at + 100Mb/s, the max_speed is 3 Gb/s and the curr_speed is 1.1 Gb/s. + """ + + #: 10 Mb half-duplex rate support. + OFPPF_10MB_HD = 1 << 0 + #: 10 Mb full-duplex rate support. + OFPPF_10MB_FD = 1 << 1 + #: 100 Mb half-duplex rate support. + OFPPF_100MB_HD = 1 << 2 + #: 100 Mb full-duplex rate support. + OFPPF_100MB_FD = 1 << 3 + #: 1 Gb half-duplex rate support. + OFPPF_1GB_HD = 1 << 4 + #: 1 Gb full-duplex rate support. + OFPPF_1GB_FD = 1 << 5 + #: 10 Gb full-duplex rate support. + OFPPF_10GB_FD = 1 << 6 + #: 40 Gb full-duplex rate support. + OFPPF_40GB_FD = 1 << 7 + #: 100 Gb full-duplex rate support. + OFPPF_100GB_FD = 1 << 8 + #: 1 Tb full-duplex rate support. + OFPPF_1TB_FD = 1 << 9 + #: Other rate, not in the list + OFPPF_OTHER = 1 << 10 + + #: Copper medium. + OFPPF_COPPER = 1 << 11 + #: Fiber medium. + OFPPF_FIBER = 1 << 12 + #: Auto-negotiation. + OFPPF_AUTONEG = 1 << 13 + #: Pause. + OFPPF_PAUSE = 1 << 14 + #: Asymmetric pause. + OFPPF_PAUSE_ASYM = 1 << 15 + + +class OPFPortState(GenericBitMask): + """Current state of the physical port. + + These are not configurable from the controller. + + The port state bits represent the state of the physical link or switch + protocols outside of OpenFlow. The :attr:`~PortConfig.OFPPS_LINK_DOWN` bit + indicates the the physical link is not present. The + :attr:`~PortConfig.OFPPS_BLOCKED` bit indicates that a switch protocol + outside of OpenFlow, such as 802.1D Spanning Tree, is preventing the use of + that port with :attr:`~PortConfig.OFPP_FLOOD`. + + All port state bits are read-only and cannot be changed by the controller. + When the port flags are changed, the switch sends an + :attr:`v0x05.common.header.Type.OFPT_PORT_STATUS` message to notify the + controller of the change. + """ + + #: Not physical link present. + OFPPS_LINK_DOWN = 1 << 0 + #: Port is blocked. + OFPPS_BLOCKED = 1 << 1 + #: Live for Fast Failover Group. + OFPPS_LIVE = 1 << 2 + + +# Classes +class OPFPortDescPropHeader(GenericStruct): + """Common header for all port description properties.""" + + # One of OFPPDPT_* + port_desc_type = UBInt16() + # Length in bytes of this property + length = UBInt16() + + def __init__(self, port_desc_type=None, length=None): + """Create the Header for Port Description Properties. + + Args: + port_desc_type (int): The Port Description property type. + length (int): The property's length. + """ + super().__init__() + self.port_desc_type = port_desc_type + self.length = length + + +class ListOfPortDescProperties(FixedTypeList): + """List of Port Description Properties. + + Represented by instances of PortDescPropHeader objects. + """ + + def __init__(self, items=None): + """Create a ListOfActions with the optional parameters below. + + Args: + items (~pyof.v0x05.common.action.ActionHeader): + Instance or a list of instances. + """ + super().__init__(pyof_class=OPFPortDescPropHeader, items=items) + + +class OPFPort(GenericStruct): + """Description of a port. + + The port_no field uniquely identifies a port within a switch. The hw_addr + field typically is the MAC address for the port; + :data:`.OFP_MAX_ETH_ALEN` is 6. The name field is a null-terminated string + containing a human-readable name for the interface. + The value of :data:`.OFP_MAX_PORT_NAME_LEN` is 16. + + :attr:`curr`, :attr:`advertised`, :attr:`supported` and :attr:`peer` fields + indicate link modes (speed and duplexity), link type (copper/fiber) and + link features (autonegotiation and pause). They are bitmaps of + :class:`PortFeatures` enum values that describe features. + Multiple of these flags may be set simultaneously. If none of the port + speed flags are set, the :attr:`max_speed` or :attr:`curr_speed` are used. + """ + + port_no = UBInt32() + + length = UBInt16() + pad = Pad(2) + hw_addr = HWAddress() + pad2 = Pad(2) # Align to 64 bits + name = Char(length=OFP_MAX_PORT_NAME_LEN) # Null terminated + config = UBInt32(enum_ref=OPFPortConfig) # Bitmap of OFPPC_* flags + state = UBInt32(enum_ref=OPFPortState) # Bitmap of OFPPS_* flags + + properties = ListOfPortDescProperties() + + def __init__(self, port_no=None, hw_addr=None, name=None, config=None, + state=None, properties=ListOfPortDescProperties): + """Create a Port with the optional parameters below. + + Args: + port_no (int): Port number. + hw_addr (HWAddress): Hardware address. + name (str): Null-terminated name. + + config (~pyof.v0x05.common.port.PortConfig): + Bitmap of OFPPC* flags. + state (~pyof.v0x05.common.port.PortState): Bitmap of OFPPS* flags. + properties (ListOfPortDescProperties): Port description property + list - 0 or more properties. + + """ + super().__init__() + self.port_no = port_no + self.hw_addr = hw_addr + self.name = name + self.config = config + self.state = state + self.properties = properties + self.length = UBInt16(self.__sizeof__()) + + +class OPFPortDescPropEthernet(OPFPortDescPropHeader): + """Ethernet port description property.""" + + # Align to 64 bits + pad4 = Pad(4) + # Current features. + curr = UBInt32(enum_ref=OPFPortFeatures) + # Feature being advertised by port. + advertised = UBInt32(enum_ref=OPFPortFeatures) + # Features supported by the port. + supported = UBInt32(enum_ref=OPFPortFeatures) + # Features advertised by peer. + peer = UBInt32(enum_ref=OPFPortFeatures) + # Current port bitrate in kbps. + curr_speed = UBInt32() + # Max port bitrate in kbps. + max_speed = UBInt32() + + def __init__(self, curr=OPFPortFeatures, advertised=OPFPortFeatures, + supported=OPFPortFeatures, peer=OPFPortFeatures, + curr_speed=None, max_speed=None): + """Create the Port Description Property for Ethernet. + + Args: + curr (int): Current features. + advertised (int): Feature being advertised by port. + supported (int): Features supported by the port. + peer (int): Features advertised by peer. + curr_speed (int): Current port bitrate in kbps. + max_speed (int): Max port bitrate in kbps. + """ + super().__init__(OPFPortDescPropType.OFPPDPT_ETHERNET) + self.curr = curr + self.advertised = advertised + self.supported = supported + self.peer = peer + self.curr_speed = curr_speed + self.max_speed = max_speed + self.length = UBInt16(self.__sizeof__()) + + +class PortDescPropOptical(OPFPortDescPropHeader): + """Optical port description property.""" + + # Align to 64 bits. + pad4 = Pad(4) + + # Features supported by the port. + supported = UBInt32() + # Minimum TX Frequency/Wavelength. + tx_min_freq_lmda = UBInt32() + # Maximum TX Frequency/Wavelength. + tx_max_freq_lmda = UBInt32() + # TX Grid Spacing Frequency/Wavelength. + tx_grid_freq_lmda = UBInt32() + # Minimum RX Frequency/Wavelength. + rx_min_freq_lmda = UBInt32() + # Maximum RX Frequency/Wavelength. + rx_max_freq_lmda = UBInt32() + # RX Grid Spacing Frequency/Wavelength + rx_grid_freq_lmda = UBInt32() + # Minimum TX power + tx_pwr_min = UBInt16() + # Maximun TX power + tx_pwr_max = UBInt16() + + def __init__(self, supported=None, tx_min_freq_lmda=None, + tx_max_freq_lmda=None, tx_grid_freq_lmda=None, + rx_min_freq_lmda=None, rx_max_freq_lmda=None, + rx_grid_freq_lmda=None, tx_pwr_min=None, tx_pwr_max=None): + """Create the Port Description Property for Optical. + + Args: + supported (int): Features supported by the port. + tx_min_freq_lmda (int): Minimum TX Frequency/Wavelength. + tx_max_freq_lmda (int): Maximum TX Frequency/Wavelength. + tx_grid_freq_lmda (int): TX Grid Spacing Frequency/Wavelength. + rx_min_freq_lmda (int): Minimum RX Frequency/Wavelength. + rx_max_freq_lmda (int): Maximum RX Frequency/Wavelength. + rx_grid_freq_lmda (int): RX Grid Spacing Frequency/Wavelength. + tx_pwr_min (int): Minimum TX power. + tx_pwr_max (int): Maximun TX power. + """ + super().__init__(OPFPortDescPropType.OFPPDPT_OPTICAL) + self.supported = supported + self.tx_min_freq_lmda = tx_min_freq_lmda + self.tx_max_freq_lmda = tx_max_freq_lmda + self.tx_grid_freq_lmda = tx_grid_freq_lmda + self.rx_grid_freq_lmda = rx_grid_freq_lmda + self.rx_min_freq_lmda = rx_min_freq_lmda + self.rx_max_freq_lmda = rx_max_freq_lmda + self.tx_pwr_min = tx_pwr_min + self.tx_pwr_max = tx_pwr_max + self.length = UBInt16(self.__sizeof__()) + + +class PortDescPropExperimenter(OPFPortDescPropHeader): + """Experimenter port description property.""" + + # Experimenter ID which takes the same form as in ExperimenterHeader. + experimenter = UBInt16() + # Experimenter defined. + exp_type = UBInt16() + experimenter_data = UBInt32() + + def __init__(self, experimenter=None, exp_type=None, + experimenter_data=None): + """Create the Port Description Property for Experimenter. + + Args: + experimenter (int): Experimenter ID which takes the same + form as in ExperimenterHeader. + exp_type (int): Experimenter defined. + experimenter_data (int): Experimenter Data. + Followed by: + - Exactly (length - 12) bytes containing the experimenter + data, then + - Exactly (length + 7) / 8 * 8 - (length) (between 0 and 7) + bytes of all-zero bytes. + """ + super().__init__(OPFPortDescPropType.OFPPDPT_EXPERIMENTER) + self.experimenter = experimenter + self.exp_type = exp_type + self.experimenter_data = experimenter_data + + +class ListOfPorts(FixedTypeList): + """List of Ports. + + Represented by instances of :class:`Port` and used on + + :class:`~pyof.v0x05.controller2switch.features_reply.FeaturesReply`/ + :class:`~pyof.v0x05.controller2switch.features_reply.SwitchFeatures` + objects. + """ + + def __init__(self, items=None): + """Create a ListOfPort with the optional parameters below. + + Args: + + items (:class:`list`, :class:`~pyof.v0x05.common.port.Port`): + One :class:`~pyof.v0x04.common.port.Port` instance or list. + + """ + super().__init__(pyof_class=OPFPort, + items=items) diff --git a/pyof/v0x05/symmetric/__init__.py b/pyof/v0x05/symmetric/__init__.py new file mode 100644 index 000000000..6b4d142c0 --- /dev/null +++ b/pyof/v0x05/symmetric/__init__.py @@ -0,0 +1 @@ +"""Symmetric Messages.""" diff --git a/pyof/v0x05/symmetric/echo_reply.py b/pyof/v0x05/symmetric/echo_reply.py new file mode 100644 index 000000000..3392770a3 --- /dev/null +++ b/pyof/v0x05/symmetric/echo_reply.py @@ -0,0 +1,34 @@ +"""Defines Echo Reply message during the handshake.""" + +# System imports + +# Third-party imports + +from pyof.foundation.base import GenericMessage +from pyof.foundation.basic_types import BinaryData +from pyof.v0x05.common.header import Header, Type + +__all__ = ('EchoReply',) + +# Classes + + +class EchoReply(GenericMessage): + """OpenFlow Reply message. + + This message does not contain a body beyond the OpenFlow Header. + """ + + header = Header(message_type=Type.OFPT_ECHO_REPLY, length=8) + + data = BinaryData() + + def __init__(self, xid=None, data=b''): + """Create a EchoReply with the optional parameters below. + + Args: + xid (int): xid to be used on the message header. + data (bytes): arbitrary-length data field. + """ + super().__init__(xid) + self.data = data diff --git a/pyof/v0x05/symmetric/echo_request.py b/pyof/v0x05/symmetric/echo_request.py new file mode 100644 index 000000000..6ef1d5de4 --- /dev/null +++ b/pyof/v0x05/symmetric/echo_request.py @@ -0,0 +1,33 @@ +"""Defines Echo Request message during the handshake.""" + +# System imports + +# Third-party imports + +from pyof.foundation.base import GenericMessage +from pyof.foundation.basic_types import BinaryData +from pyof.v0x05.common.header import Header, Type + +__all__ = ('EchoRequest',) + +# Classes + + +class EchoRequest(GenericMessage): + """OpenFlow Reply message. + + This message does not contain a body after the OpenFlow Header. + """ + + header = Header(message_type=Type.OFPT_ECHO_REQUEST, length=8) + data = BinaryData() + + def __init__(self, xid=None, data=b''): + """Create a EchoRequest with the optional parameters below. + + Args: + xid (int): xid to be used on the message header. + data (bytes): arbitrary-length data field. + """ + super().__init__(xid) + self.data = data diff --git a/pyof/v0x05/symmetric/error_msg.py b/pyof/v0x05/symmetric/error_msg.py new file mode 100644 index 000000000..c36450cfe --- /dev/null +++ b/pyof/v0x05/symmetric/error_msg.py @@ -0,0 +1,648 @@ +"""Defines an Error Message.""" + +# System imports + +from pyof.foundation import exceptions +from pyof.foundation.base import Enum, GenericMessage +from pyof.foundation.basic_types import BinaryData, UBInt16, UBInt32 +from pyof.v0x05.common.header import Header, Type + +# Third-party imports + + +__all__ = ('OPFBadActionCode', 'OPFBadInstructionCode', 'OPFBadMatchCode', + 'OPFErrorType', 'OPFFlowModFailedCode', 'OPFGroupModFailedCode', + 'OPFHelloFailedCode', 'OPFMeterModFailedCode', + 'OPFPortModFailedCode', 'OPFQueueOpFailedCode', + 'OPFRoleRequestFailedCode', 'OPFSwitchConfigFailedCode', + 'OPFTableFeaturesFailedCode', 'OPFTableModFailedCode', + 'OPFGenericFailedCode', 'OPFBadPropertyCode', + 'OPFAsyncConfigFailedCode', 'OPFFlowMonitorFailedCode', + 'OPFBundleFailedCode') + +# Enums + + +class OPFGenericFailedCode(Enum): + """Error_msg 'code' values for OFPET_BAD_ACTION. + + 'data' contains at least the first 64 bytes of the failed request. + """ + + #: Unknown error + GENERIC_ERROR = 0 + + +class OPFBadActionCode(Enum): + """Error_msg 'code' values for OFPET_BAD_ACTION. + + 'data' contains at least the first 64 bytes of the failed request. + """ + + #: Unknown action type. + OFPBAC_BAD_TYPE = 0 + #: Length problem in actions. + OFPBAC_BAD_LEN = 1 + #: Unknown experimenter id specified. + OFPBAC_BAD_EXPERIMENTER = 2 + #: Unknown action for experimenter id. + OFPBAC_BAD_EXP_TYPE = 3 + #: Problem validating output port. + OFPBAC_BAD_OUT_PORT = 4 + #: Bad action argument. + OFPBAC_BAD_ARGUMENT = 5 + #: Permissions error. + OFPBAC_EPERM = 6 + #: Can’t handle this many actions. + OFPBAC_TOO_MANY = 7 + #: Problem validating output queue. + OFPBAC_BAD_QUEUE = 8 + #: Invalid group id in forward action. + OFPBAC_BAD_OUT_GROUP = 9 + #: Action can’t apply for this match, or Set-Field missing prerequisite. + OFPBAC_MATCH_INCONSISTENT = 10 + #: Action order is unsupported for the action list in an Apply-Actions + #: instruction. + OFPBAC_UNSUPPORTED_ORDER = 11 + #: Actions uses an unsupported tag/encap. + OFPBAC_BAD_TAG = 12 + #: Unsupported type in SET_FIELD action. + OFPBAC_BAD_SET_TYPE = 13 + #: Length problem in SET_FIELD action. + OFPBAC_BAD_SET_LEN = 14 + #: Bad argument in SET_FIELD action. + OFPBAC_BAD_SET_ARGUMENT = 15 + + +class OPFBadInstructionCode(Enum): + """Error_msg 'code' values for OFPET_BAD_INSTRUCTION. + + 'data' contains at least the first 64 bytes of the failed request. + """ + + #: Unknown instruction. + OFPBIC_UNKNOWN_INST = 0 + #: Switch or table does not support the instruction. + OFPBIC_UNSUP_INST = 1 + #: Invalid Table-ID specified. + OFPBIC_BAD_TABLE_ID = 2 + #: Metadata value unsupported by datapath. + OFPBIC_UNSUP_METADATA = 3 + #: Metadata mask value unsupported by datapath. + OFPBIC_UNSUP_METADATA_MASK = 4 + #: Unknown experimenter id specified. + OFPBIC_BAD_EXPERIMENTER = 5 + #: Unknown instruction for experimenter id. + OFPBIC_BAD_EXP_TYPE = 6 + #: Length problem in instructions. + OFPBIC_BAD_LEN = 7 + #: Permissions error. + OFPBIC_EPERM = 8 + #: Duplicate instruction. + OFPBIC_DUP_INST = 9 + + +class OPFBadMatchCode(Enum): + """Error_msg 'code' values for OFPET_BAD_MATCH. + + 'data' contains at least the first 64 bytes of the failed request. + """ + + #: Unsupported match type specified by the match + OFPBMC_BAD_TYPE = 0 + #: Length problem in match. + OFPBMC_BAD_LEN = 1 + #: Match uses an unsupported tag/encap. + OFPBMC_BAD_TAG = 2 + #: Unsupported datalink addr mask - switch does not support arbitrary + #: datalink address mask. + OFPBMC_BAD_DL_ADDR_MASK = 3 + #: Unsupported network addr mask - switch does not support arbitrary + #: network address mask. + OFPBMC_BAD_NW_ADDR_MASK = 4 + #: Unsupported combination of fields masked or omitted in the match. + OFPBMC_BAD_WILDCARDS = 5 + #: Unsupported field type in the match. + OFPBMC_BAD_FIELD = 6 + #: Unsupported value in a match field. + OFPBMC_BAD_VALUE = 7 + #: Unsupported mask specified in the match, field is not dl-address or + #: nw-address. + OFPBMC_BAD_MASK = 8 + #: A prerequisite was not met. + OFPBMC_BAD_PREREQ = 9 + #: A field type was duplicated. + OFPBMC_DUP_FIELD = 10 + #: Permissions error. + OFPBMC_EPERM = 11 + + +class OPFBadRequestCode(Enum): + """Error_msg 'code' values for OFPET_BAD_REQUEST. + + 'data' contains at least the first 64 bytes of the failed request. + """ + + #: ofp_header.version not supported. + OFPBRC_BAD_VERSION = 0 + #: ofp_header.type not supported. + OFPBRC_BAD_TYPE = 1 + #: ofp_multipart_request.type not supported. + OFPBRC_BAD_MULTIPART = 2 + #: Experimenter id not supported (in ofp_experimenter_header or + #: ofp_multipart_request or * ofp_multipart_reply). + OFPBRC_BAD_EXPERIMENTER = 3 + #: Experimenter type not supported. + OFPBRC_BAD_EXP_TYPE = 4 + #: Permissions error. + OFPBRC_EPERM = 5 + #: Wrong request length for type. + OFPBRC_BAD_LEN = 6 + #: Specified buffer has already been used. + OFPBRC_BUFFER_EMPTY = 7 + #: Specified buffer does not exist. + OFPBRC_BUFFER_UNKNOWN = 8 + #: Specified table-id invalid or does not * exist. + OFPBRC_BAD_TABLE_ID = 9 + #: Denied because controller is slave. + OFPBRC_IS_SLAVE = 10 + #: Invalid port. + OFPBRC_BAD_PORT = 11 + #: Invalid packet in packet-out. + OFPBRC_BAD_PACKET = 12 + #: ofp_multipart_request overflowed the assigned buffer. + OFPBRC_MULTIPART_BUFFER_OVERFLOW = 13 # pylint: disable=invalid-name + #: Timeout during multipart request. + OFPBRC_MULTIPART_REQUEST_TIMEOUT = 14 # pylint: disable=invalid-name + #: Timeout during multipart reply + OFPBRC_MULTIPART_REPLY_TIMEOUT = 15 # pylint: disable=invalid-name + + +class OPFErrorType(Enum): + """Values for ’type’ in ofp_error_message. + + These values are immutable: they will not change in future versions of the + protocol (although new values may be added). + """ + + #: Hello protocol failed + OFPET_HELLO_FAILED = 0 + #: Request was not understood + OFPET_BAD_REQUEST = 1 + #: Error in action description + OFPET_BAD_ACTION = 2 + #: Error in instruction list. + OFPET_BAD_INSTRUCTION = 3 + #: Error in match. + OFPET_BAD_MATCH = 4 + #: Problem modifying flow entry. + OFPET_FLOW_MOD_FAILED = 5 + #: Problem modifying group entry. + OFPET_GROUP_MOD_FAILED = 6 + #: Port mod request failed. + OFPET_PORT_MOD_FAILED = 7 + #: Table mod request failed. + OFPET_TABLE_MOD_FAILED = 8 + #: Queue operation failed. + OFPET_QUEUE_OP_FAILED = 9 + #: Switch config request failed. + OFPET_SWITCH_CONFIG_FAILED = 10 + #: Controller Role request failed. + OFPET_ROLE_REQUEST_FAILED = 11 + #: Error in meter. + OFPET_METER_MOD_FAILED = 12 + #: Setting table features failed. + OFPET_TABLE_FEATURES_FAILED = 13 + #: Some property is invalid. + OFPET_BAD_PROPERTY = 14 + #: Asynchronous config request failed + OFPET_ASYNC_CONFIG_FAILED = 15 + #: Setting flow monitor failed. + OFPET_FLOW_MONITOR_FAILED = 16 + #: Bundle operation failed + OFPET_BUNDLE_FAILED = 17 + #: Experimenter error messages. + OFPET_EXPERIMENTER = 0xffff + + def get_class(self): + """Return a Code class based on current ErrorType value. + + Returns: + enum.IntEnum: class referenced by current error type. + + """ + classes = {'OFPET_HELLO_FAILED': OPFHelloFailedCode, + 'OFPET_BAD_REQUEST': OPFBadRequestCode, + 'OFPET_BAD_ACTION': OPFBadActionCode, + 'OFPET_BAD_INSTRUCTION': OPFBadInstructionCode, + 'OFPET_BAD_MATCH': OPFBadMatchCode, + 'OFPET_FLOW_MOD_FAILED': OPFFlowModFailedCode, + 'OFPET_GROUP_MOD_FAILED': OPFGroupModFailedCode, + 'OFPET_PORT_MOD_FAILED': OPFPortModFailedCode, + 'OFPET_QUEUE_OP_FAILED': OPFQueueOpFailedCode, + 'OFPET_SWITCH_CONFIG_FAILED': OPFSwitchConfigFailedCode, + 'OFPET_ROLE_REQUEST_FAILED': OPFRoleRequestFailedCode, + 'OFPET_METER_MOD_FAILED': OPFMeterModFailedCode, + 'OFPET_TABLE_MOD_FAILED': OPFTableModFailedCode, + 'OFPET_TABLE_FEATURES_FAILED': OPFTableFeaturesFailedCode, + 'OFPET_BAD_PROPERTY': OPFBadPropertyCode, + 'OFPET_ASYNC_CONFIG_FAILED': OPFAsyncConfigFailedCode, + 'OFPET_FLOW_MONITOR_FAILED': OPFFlowMonitorFailedCode, + 'OFPET_BUNDLE_FAILED': OPFBundleFailedCode} + return classes.get(self.name, OPFGenericFailedCode) + + +class OPFFlowModFailedCode(Enum): + """Error_msg 'code' values for OFPET_FLOW_MOD_FAILED. + + 'data' contains at least the first 64 bytes of the failed request. + """ + + #: Unspecified error. + OFPFMFC_UNKNOWN = 0 + #: Flow not added because table was full. + OFPFMFC_TABLE_FULL = 1 + #: Table does not exist + OFPFMFC_BAD_TABLE_ID = 2 + #: Attempted to add overlapping flow with CHECK_OVERLAP flag set. + OFPFMFC_OVERLAP = 3 + #: Permissions error. + OFPFMFC_EPERM = 4 + #: Flow not added because of unsupported idle/hard timeout. + OFPFMFC_BAD_TIMEOUT = 5 + #: Unsupported or unknown command. + OFPFMFC_BAD_COMMAND = 6 + #: Unsupported or unknown flags. + OFPFMFC_BAD_FLAGS = 7 + #: Problem in table synchronisation + OFPFMFC_CANT_SYNC = 8 + #: Unsupported priority value + OFPFMFC_BAD_PRIORITY = 9 + #: Synchronised flow entry is read only. + OFPFMFC_IS_SYNC = 10 + + +class OPFGroupModFailedCode(Enum): + """Error_msg 'code' values for OFPET_GROUP_MOD_FAILED. + + 'data' contains at least the first 64 bytes of the failed request. + """ + + #: Group not added because a group ADD attempted to replace an + #: already-present group. + FPGMFC_GROUP_EXISTS = 0 + #: Group not added because Group specified is invalid. + OFPGMFC_INVALID_GROUP = 1 + #: Switch does not support unequal load sharing with select groups. + OFPGMFC_WEIGHT_UNSUPPORTED = 2 + #: The group table is full. + OFPGMFC_OUT_OF_GROUPS = 3 + #: The maximum number of action buckets for a group has been exceeded. + OFPGMFC_OUT_OF_BUCKETS = 4 + #: Switch does not support groups that forward to groups. + OFPGMFC_CHAINING_UNSUPPORTED = 5 + #: This group cannot watch the watch_port or watch_group specified. + OFPGMFC_WATCH_UNSUPPORTED = 6 + #: Group entry would cause a loop. + OFPGMFC_LOOP = 7 + #: Group not modified because a group MODIFY attempted to modify a + #: non-existent group. + OFPGMFC_UNKNOWN_GROUP = 8 + #: Group not deleted because another group is forwarding to it. + OFPGMFC_CHAINED_GROUP = 9 + #: Unsupported or unknown group type. + OFPGMFC_BAD_TYPE = 10 + #: Unsupported or unknown command. + OFPGMFC_BAD_COMMAND = 11 + #: Error in bucket. + OFPGMFC_BAD_BUCKET = 12 + #: Error in watch port/group. + OFPGMFC_BAD_WATCH = 13 + #: Permissions error. + OFPGMFC_EPERM = 14 + + +class OPFHelloFailedCode(Enum): + """Error_msg 'code' values for OFPET_HELLO_FAILED. + + 'data' contains an ASCII text string that may give failure details. + """ + + #: No compatible version + OFPHFC_INCOMPATIBLE = 0 + #: Permissions error + OFPHFC_EPERM = 1 + + +class OPFMeterModFailedCode(Enum): + """Error msg 'code' values for OFPET_METER_MOD_FAILED. + + 'data' contains at least the first 64 bytes of the failed request. + """ + + #: Unspecified error. + OFPMMFC_UNKNOWN = 0 + #: Meter not added because a Meter ADD * attempted to replace an existing + #: Meter. + OFPMMFC_METER_EXISTS = 1 + #: Meter not added because Meter specified * is invalid. + OFPMMFC_INVALID_METER = 2 + #: Meter not modified because a Meter MODIFY attempted to modify a + #: non-existent Meter. + OFPMMFC_UNKNOWN_METER = 3 + #: Unsupported or unknown command. + OFPMMFC_BAD_COMMAND = 4 + #: Flag configuration unsupported. + OFPMMFC_BAD_FLAGS = 5 + #: Rate unsupported. + OFPMMFC_BAD_RATE = 6 + #: Burst size unsupported. + OFPMMFC_BAD_BURST = 7 + #: Band unsupported. + OFPMMFC_BAD_BAND = 8 + #: Band value unsupported. + OFPMMFC_BAD_BAND_VALUE = 9 + #: No more meters available. + OFPMMFC_OUT_OF_METERS = 10 + #: The maximum number of properties * for a meter has been exceeded. + OFPMMFC_OUT_OF_BANDS = 11 + + +class OPFPortModFailedCode(Enum): + """Error_msg 'code' values for OFPET_PORT_MOD_FAILED. + + 'data' contains at least the first 64 bytes of the failed request. + """ + + #: Specified port number does not exist. + OFPPMFC_BAD_PORT = 0 + #: Specified hardware address does not * match the port number. + OFPPMFC_BAD_HW_ADDR = 1 + #: Specified config is invalid. + OFPPMFC_BAD_CONFIG = 2 + #: Specified advertise is invalid. + OFPPMFC_BAD_ADVERTISE = 3 + #: Permissions error. + OFPPMFC_EPERM = 4 + + +class OPFQueueOpFailedCode(Enum): + """Error msg 'code' values for OFPET_QUEUE_OP_FAILED. + + 'data' contains at least the first 64 bytes of the failed request. + """ + + #: Invalid port (or port does not exist) + OFPQOFC_BAD_PORT = 0 + #: Queue does not exist + OFPQOFC_BAD_QUEUE = 1 + #: Permissions error + OFPQOFC_EPERM = 2 + + +class OPFRoleRequestFailedCode(Enum): + """Error msg 'code' values for OFPET_ROLE_REQUEST_FAILED. + + 'data' contains at least the first 64 bytes of the failed request. + """ + + #: Stale Message: old generation_id. + OFPRRFC_STALE = 0 + #: Controller role change unsupported. + OFPRRFC_UNSUP = 1 + #: Invalid role. + OFPRRFC_BAD_ROLE = 2 + + +class OPFSwitchConfigFailedCode(Enum): + """Error msg 'code' values for OFPET_SWITCH_CONFIG_FAILED. + + 'data' contains at least the first 64 bytes of the failed request. + """ + + #: Specified flags is invalid. + OFPSCFC_BAD_FLAGS = 0 + #: Specified len is invalid. + OFPSCFC_BAD_LEN = 1 + #: Permissions error. + OFPQCFC_EPERM = 2 + + +class OPFTableFeaturesFailedCode(Enum): + """Error msg 'code' values for OFPET_TABLE_FEATURES_FAILED. + + 'data' contains at least the first 64 bytes of the failed request. + """ + + #: Specified table does not exist. + OFPTFFC_BAD_TABLE = 0 + #: Invalid metadata mask. + OFPTFFC_BAD_METADATA = 1 + #: Permissions error. + OFPTFFC_EPERM = 5 + + +class OPFTableModFailedCode(Enum): + """Error_msg 'code' values for OFPET_TABLE_MOD_FAILED. + + 'data' contains at least the first 64 bytes of the failed request. + """ + + #: Specified table does not exist. + OFPTMFC_BAD_TABLE = 0 + #: Specified config is invalid. + OFPTMFC_BAD_CONFIG = 1 + #: Permissions error. + OFPTMFC_EPERM = 2 + + +class OPFBadPropertyCode(Enum): + """Error_msg 'code' values for OFPET_BAD_PROPERTY. + + 'data' contains at least the first 64 bytes of the failed request. + """ + + #: Unknown property type. + OFPBPC_BAD_TYPE = 0 + #: Length problem in property. + OFPBPC_BAD_LEN = 1 + #: Unsupported property value. + OFPBPC_BAD_VALUE = 2 + #: Can't handle this many properties. + OFPBPC_TOO_MANY = 3 + #: A property type was duplicated. + OFPBPC_DUP_TYPE = 4 + #: Unknown experimenter id specified. + OFPBPC_BAD_EXPERIMENTER = 5 + #: Unknown exp_type for experimenter id. + OFPBPC_BAD_EXP_TYPE = 6 + #: Unknown value for experimenter id. + OFPBPC_BAD_EXP_VALUE = 7 + #: Permissions error + OFPBPC_EPERM = 8 + + +class OPFAsyncConfigFailedCode(Enum): + """Error_msg 'code' values for OFPET_ASYNC_CONFIG_FAILED. + + 'data' contains at least the first 64 bytes of the failed request. + """ + + #: One mask is invalid. + OFPACFC_INVALID = 0 + #: Requested configuration not supported. + OFPACFC_UNSUPPORTED = 1 + #: Permissions error. + OFPACFC_EPERM = 2 + + +class OPFFlowMonitorFailedCode(Enum): + """Error_msg 'code' values for OFPET_FLOW_MONITOR_FAILED. + + 'data' contains at least the first 64 bytes of the failed request. + """ + + #: Unspecified error. + OFPMOFC_UNKNOWN = 0 + #: Monitor not added because a Monitor ADD attempted to replace + #: and existing Monitor. + OFPMOFC_MONITOR_EXISTS = 1 + #: Monitor not added because Monitor specified is invalid. + OFPMOFC_INVALID_MONITOR = 2 + #: Monitor not modified because a Monitor MODIFY attempted to modify + # a non-existent Monitor. + OFPMOFC_UNKNOWN_MONITOR = 3 + #: Unsupported or unknown command. + OFPMOFC_BAD_COMMAND = 4 + #: Flag configuration unsupported. + OFPMOFC_BAD_FLAGS = 5 + #: Specified table does not exist. + OFPMOFC_BAD_TABLE_ID = 6 + #: Error in output port/group. + OFPMOFC_BAD_OUT = 7 + + +class OPFBundleFailedCode(Enum): + """Error_msg 'code' values for OFPET_BUNDLE_FAILED. + + 'data' contains at least the first 64 bytes of the failed request. + """ + + #: Unspecified error. + OFPBFC_UNKNOWN = 0 + #: Permissions error. + OFPBFC_EPERM = 1 + #: Bundle ID doesn't exist. + OFPBFC_BAD_ID = 2 + #: Bundle ID already exist. + OFPBFC_BUNDLE_EXIST = 3 + #: Bundle ID is closed. + OFPBFC_BUNDLE_CLOSED = 4 + #: Too many bundles IDs. + OFPBFC_OUT_OF_BUNDLES = 5 + #: Unsupported or unknown message control type + OFPBFC_BAD_TYPE = 6 + #: Unsupported, unknown, or inconsistent flags. + OFPBFC_BAD_FLAGS = 7 + #: Length problem in included message. + OFPBFC_MSG_BAD_LEN = 8 + #: Inconsistent or duplicate XID. + OFPBFC_MSG_BAD_XID = 9 + #: Unsupported message in this bundle. + OFPBFC_MSG_UNSUP = 10 + #: Unsupported message combination in this bundle. + OFPBFC_MSG_CONFLICT = 11 + #: Can't handle this many messages in bundle. + OFPBFC__MSG_TOO_MANY = 12 + #: One message in bundle failed. + OFPBFC_MSG_FAILED = 13 + #: Bundle is taking too long. + OFPBFC_TIMEOUT = 14 + #: Bundle is locking the resources. + OFPBFC_BUNDLE_IN_PROGRESS = 15 + + +# Classes + +class OPFErrorMsg(GenericMessage): + """OpenFlow Error Message. + + This message does not contain a body in addition to the OpenFlow Header. + """ + + #: :class:`~.header.Header`: OpenFlow Header + header = Header(message_type=Type.OFPT_ERROR) + #: ErrorType enum item + error_type = UBInt16(enum_ref=OPFErrorType) + #: Error code associated with ErrorType + code = UBInt16() + #: Variable-length data interpreted based on the type and code. + # No padding. + data = BinaryData() + + def __init__(self, xid=None, error_type=None, code=None, data=b''): + """Assign parameters to object attributes. + + Args: + xid (int): To be included in the message header. + error_type (ErrorType): Error type. + code (Enum): Error code. + data: Its content is specified in the error code documentation. + Unless specified otherwise, the data field contains at least + 64 bytes of the failed request that caused the error message + to be generated, if the failed request is shorter than 64 + bytes it should be the full request without any padding. + """ + super().__init__(xid) + self.error_type = error_type + self.code = code + self.data = data + + def unpack(self, buff, offset=0): + """Unpack binary data into python object.""" + super().unpack(buff, offset) + code_class = OPFErrorType(self.error_type).get_class() + self.code = code_class(self.code) + + +class OPFErrorExperimenterMsg(GenericMessage): + """OFPET_EXPERIMENTER: Error message (datapath -> controller). + + The experimenter field is the Experimenter ID, which takes the same form + as in :class:`~.symmetric.experimenter.ExperimenterHeader + """ + + # :class:`~.header.Header`: OpenFlow Header + header = Header(message_type=Type.OFPT_ERROR) + #: OFPET_EXPERIMENTER. + error_type = UBInt16(OPFErrorType.OFPET_EXPERIMENTER, + enum_ref=OPFErrorType) + #: Experimenter Defined + exp_type = UBInt16() + #: Experimenter ID which takes the same form as in + #: :class:`~.symmetric.experimenter.ExperimenterHeader`. + experimenter = UBInt32() + #: Variable-length data interpreted based on the type and code. + # No padding. + data = BinaryData() + + def __init__(self, xid=None, exp_type=None, experimenter=None, data=b''): + """Assign parameters to object attributes. + + Args: + xid (int): To be included in the message header. + exp_type (int): Experimenter defined. + experimenter (int): Experimenter ID which takes the same form as + in :class:`~.symmetric.experimenter.ExperimenterHeader`. + data: Variable-length data interpreted based on the type and code. + No padding. + """ + super().__init__(xid) + self.exp_type = exp_type + self.experimenter = experimenter + self.data = data + + def unpack(self, buff, offset=0): + """Unpack binary data into python object.""" + raise exceptions.MethodNotImplemented("'Unpack' method not " + "implemented on ErrorMsg class") diff --git a/pyof/v0x05/symmetric/experimenter.py b/pyof/v0x05/symmetric/experimenter.py new file mode 100644 index 000000000..b45d531d5 --- /dev/null +++ b/pyof/v0x05/symmetric/experimenter.py @@ -0,0 +1,55 @@ +"""Defines Experimenter message.""" + +# System imports + +# Third-party imports + +from pyof.foundation.base import GenericMessage +from pyof.foundation.basic_types import BinaryData, UBInt32 +from pyof.v0x05.common.header import Header, Type + +__all__ = ('OPFExperimenterHeader',) + +# Classes + + +class OPFExperimenterHeader(GenericMessage): + """OpenFlow Experimenter message. + + The experimenter field is a 32-bit value that uniquely identifies the + experimenter. If the most significant byte is zero, the next three bytes + are the experimenter’s IEEE OUI. If the most significant byte is not zero, + it is a value allocated by the Open Networking Foundation. If experimenter + does not have (or wish to use) their OUI, they should contact the Open + Networking Foundation to obtain an unique experimenter ID. + + The rest of the body is uninterpreted by standard OpenFlow processing and + is arbitrarily defined by the corresponding experimenter. + + If a switch does not understand an experimenter extension, it must send an + OFPT_ERROR message with a OFPBRC_BAD_EXPERIMENTER error code and + OFPET_BAD_REQUEST error type. + """ + + header = Header(message_type=Type.OFPT_EXPERIMENTER) + experimenter = UBInt32() + exp_type = UBInt32() + experimenter_data = BinaryData() + + def __init__(self, xid=None, experimenter=None, exp_type=None, + experimenter_data=b''): + """Create a ExperimenterHeader with the optional parameters below. + + Args: + xid (int): xid to be used on the message header. + experimenter (int): Vendor ID: + MSB 0: low-order bytes are IEEE OUI. + MSB != 0: defined by ONF. + exp_type (int): Experimenter defined. + experimenter_data (Binary): Experimenter-defined arbitrary + additional data. + """ + super().__init__(xid) + self.experimenter = experimenter + self.exp_type = exp_type + self.experimenter_data = experimenter_data diff --git a/pyof/v0x05/symmetric/hello.py b/pyof/v0x05/symmetric/hello.py new file mode 100644 index 000000000..90372aac7 --- /dev/null +++ b/pyof/v0x05/symmetric/hello.py @@ -0,0 +1,155 @@ +"""Defines Hello message.""" + +# System imports + +from pyof.foundation.base import Enum, GenericMessage, GenericStruct +from pyof.foundation.basic_types import BinaryData, FixedTypeList, UBInt16 +from pyof.foundation.exceptions import PackException +from pyof.v0x05.common.header import Header, Type + +# Third-party imports + +__all__ = ('Hello', 'HelloElemType', + 'HelloElemVersionBitmap', 'ListOfHelloElements', + 'OPFHelloElemHeader') + +# Enums + + +class HelloElemType(Enum): + """Hello element types.""" + + #: Bitmap of version supported. + OFPHET_VERSIONBITMAP = 1 + + +# Classes + +class OPFHelloElemHeader(GenericStruct): + """Common header for all Hello Elements.""" + + hello_element_type = UBInt16() + # Length in bytes of element, including this header, excluding padding. + length = UBInt16() + # This variable does NOT appear in 1.4 specification + # content = BinaryData() + + def __init__(self, hello_element_type=None, length=None): + """Create a HelloElemHeader with the optional parameters below. + + Args: + hello_element_type: One of OFPHET_*. + length: Length in bytes of the element, including this header, + excluding padding. + """ + super().__init__() + self.hello_element_type = hello_element_type + self.length = length + + def pack(self, value=None): + """Update the length and pack the message into binary data. + + Returns: + bytes: A binary data that represents the Message. + + Raises: + Exception: If there are validation errors. + + """ + if value is None: + self.update_length() + return super().pack() + elif isinstance(value, type(self)): + return value.pack() + else: + msg = "{} is not an instance of {}".format(value, + type(self).__name__) + raise PackException(msg) + + def update_length(self): + """Update length attribute.""" + self.length = self.get_size() + + def unpack(self, buff=None, offset=0): + """Unpack *buff* into this object. + + This method will convert a binary data into a readable value according + to the attribute format. + + Args: + buff (bytes): Binary buffer. + offset (int): Where to begin unpacking. + + Raises: + :exc:`~.exceptions.UnpackException`: If unpack fails. + + """ + # length = UBInt16() + self.length.unpack(buff, offset=offset+2) + + super().unpack(buff[:offset+self.length.value], offset) + + +class ListOfHelloElements(FixedTypeList): + """List of Hello elements. + + Represented by instances of HelloElemHeader and used on Hello + objects. + """ + + def __init__(self, items=None): + """Create a ListOfHelloElements with the optional parameters below. + + Args: + items (HelloElemHeader): Instance or a list of instances. + """ + super().__init__(pyof_class=OPFHelloElemHeader, items=items) + + +class Hello(GenericMessage): + """OpenFlow Hello Message OFPT_HELLO. + + This message includes zero or more hello elements having variable size. + Unknown element types must be ignored/skipped, to allow for future + extensions. + """ + + header = Header(Type.OFPT_HELLO) + + #: Hello element list + elements = ListOfHelloElements() + + def __init__(self, xid=None, elements=None): + """Create a Hello with the optional parameters below. + + Args: + xid (int): xid to be used on the message header. + elements: List of elements - 0 or more + """ + super().__init__(xid) + self.elements = elements + + +class HelloElemVersionBitmap(OPFHelloElemHeader): + """Version bitmap Hello Element. + + The bitmaps field indicates the set of versions + of the OpenFlow switch protocol a device supports, + and may be used during version negotiation. + """ + + bitmaps = BinaryData() + + def __init__(self, length=None, bitmaps=None): + """Initialize the class with the needed values. + + Args: + length(int): Followed by: + - Exactly (length - 4) bytes containing the bitmaps, then + - Exactly (length + 7) / 8 * 8 - (length) (Between 0 and 7) + bytes of all-zero bytes. + + bitmaps(binary): List of bitmaps - supported versions. + """ + super().__init__(HelloElemType.OFPHET_VERSIONBITMAP, length) + self.bitmaps = bitmaps