diff --git a/scapy/contrib/openflow.py b/scapy/contrib/openflow.py new file mode 100755 index 0000000000000000000000000000000000000000..beab1f13158b812e9e43897728c99703c03679b4 --- /dev/null +++ b/scapy/contrib/openflow.py @@ -0,0 +1,1213 @@ +## This file is part of Scapy +## See http://www.secdev.org/projects/scapy for more information +## Copyright (C) Philippe Biondi <phil@secdev.org> +## This program is published under a GPLv2 license + +## Copyright (C) 2014 Maxence Tury <maxence.tury@ssi.gouv.fr> +## OpenFlow is an open standard used in SDN deployments. +## Based on OpenFlow v1.0.1 +## Specifications can be retrieved from https://www.opennetworking.org/ + +# scapy.contrib.description = openflow v1.0 +# scapy.contrib.status = loads + +import binascii +import struct +from scapy.all import * + +### If prereq_autocomplete is True then match prerequisites will be +### automatically handled. See OFPMatch class. +prereq_autocomplete = False + +##################################################### +################# Predefined values ################# +##################################################### + +ofp_port_no = { 0xfff8: "IN_PORT", + 0xfff9: "TABLE", + 0xfffa: "NORMAL", + 0xfffb: "FLOOD", + 0xfffc: "ALL", + 0xfffd: "CONTROLLER", + 0xfffe: "LOCAL", + 0xffff: "NONE" } + +ofp_table = { 0xff: "ALL" } + +ofp_queue = { 0xffffffff: "ALL" } + +ofp_buffer = { 0xffffffff: "NO_BUFFER" } + +ofp_max_len = { 0xffff: "NO_BUFFER" } + +##################################################### +################# Common structures ################# +##################################################### + +### The following structures will be used in different types +### of OpenFlow messages: ports, matches, actions, queues. + + +##################### Ports ##################### + +ofp_port_config = [ "PORT_DOWN", + "NO_STP", + "NO_RECV", + "NO_RECV_STP", + "NO_FLOOD", + "NO_FWD", + "NO_PACKET_IN" ] + +ofp_port_state = [ "LINK_DOWN" ] + +ofp_port_state_stp = { 0: "OFPPS_STP_LISTEN", + 1: "OFPPS_STP_LEARN", + 2: "OFPPS_STP_FORWARD", + 3: "OFPPS_STP_BLOCK" } + +ofp_port_features = [ "10MB_HD", + "10MB_FD", + "100MB_HD", + "100MB_FD", + "1GB_HD", + "1GB_FD", + "10GB_FD", + "COPPER", + "FIBER", + "AUTONEG", + "PAUSE", + "PAUSE_ASYM" ] + +class OFPPhyPort(Packet): + name = "OFP_PHY_PORT" + fields_desc = [ ShortEnumField("port_no", 0, ofp_port_no), + MACField("hw_addr", "0"), + StrFixedLenField("port_name", "", 16), + FlagsField("config", 0, 32, ofp_port_config), + BitEnumField("stp_state", 0, 24, ofp_port_state), + FlagsField("state", 0, 8, ofp_port_state), + FlagsField("curr", 0, 32, ofp_port_features), + FlagsField("advertised", 0, 32, ofp_port_features), + FlagsField("supported", 0, 32, ofp_port_features), + FlagsField("peer", 0, 32, ofp_port_features) ] + + def extract_padding(self, s): + return "", s + +class OFPMatch(Packet): + name = "OFP_MATCH" + fields_desc= [ FlagsField("wildcards1", None, 12, [ "DL_VLAN_PCP", + "NW_TOS" ]), + BitField("nw_dst_mask", None, 6), + BitField("nw_src_mask", None, 6), + FlagsField("wildcards2", None, 8, [ "IN_PORT", + "DL_VLAN", + "DL_SRC", + "DL_DST", + "DL_TYPE", + "NW_PROTO", + "TP_SRC", + "TP_DST" ]), + ShortEnumField("in_port", None, ofp_port_no), + MACField("dl_src", None), + MACField("dl_dst", None), + ShortField("dl_vlan", None), + ByteField("dl_vlan_pcp", None), + XByteField("pad1", None), + ShortField("dl_type", None), + ByteField("nw_tos", None), + ByteField("nw_proto", None), + XShortField("pad2", None), + IPField("nw_src", "0"), + IPField("nw_dst", "0"), + ShortField("tp_src", None), + ShortField("tp_dst", None) ] + + def extract_padding(self, s): + return "", s + + ### with post_build we create the wildcards field bit by bit + def post_build(self, p, pay): + # first 10 bits of an ofp_match are always set to 0 + l = ["0"*10] + + # when one field has not been declared, it is assumed to be wildcarded + if self.wildcards1 is None: + if self.nw_tos is None: l.append("1") + else: l.append("0") + if self.dl_vlan_pcp is None: l.append("1") + else: l.append("0") + else: + w1 = bin(self.wildcards1)[2:] + l.append("0"*(2-len(w1))) + l.append(w1) + + # ip masks use 6 bits each + if self.nw_dst_mask is None: + if self.nw_dst is "0": l.append("111111") + # 0x100000 would be ok too (32-bit IP mask) + else: l.append("0"*6) + else: + m1 = bin(self.nw_dst_mask)[2:] + l.append("0"*(6-len(m1))) + l.append(m1) + if self.nw_src_mask is None: + if self.nw_src is "0": l.append("111111") + else: l.append("0"*6) + else: + m2 = bin(self.nw_src_mask)[2:] + l.append("0"*(6-len(m2))) + l.append(m2) + + # wildcards2 works the same way as wildcards1 + if self.wildcards2 is None: + if self.tp_dst is None: l.append("1") + else: l.append("0") + if self.tp_src is None: l.append("1") + else: l.append("0") + if self.nw_proto is None: l.append("1") + else: l.append("0") + if self.dl_type is None: l.append("1") + else: l.append("0") + if self.dl_dst is None: l.append("1") + else: l.append("0") + if self.dl_src is None: l.append("1") + else: l.append("0") + if self.dl_vlan is None: l.append("1") + else: l.append("0") + if self.in_port is None: l.append("1") + else: l.append("0") + else: + w2 = bin(self.wildcards2)[2:] + l.append("0"*(8-len(w2))) + l.append(w2) + + ### In order to write OFPMatch compliant with the specifications, + ### if prereq_autocomplete has been set to True + ### we assume ethertype=IP or nwproto=TCP when appropriate subfields are provided. + if prereq_autocomplete: + if self.dl_type is None: + if self.nw_src is not "0" or self.nw_dst is not "0" or self.nw_proto is not None or self.nw_tos is not None: + p = p[:22] + struct.pack("!H", 0x0800) + p[24:] + l[-5] = "0" + if self.nw_proto is None: + if self.tp_src is not None or self.tp_dst is not None: + p = p[:22] + struct.pack("!H", 0x0800) + p[24:] + l[-5] = "0" + p = p[:25] + struct.pack("!B", 0x06) + p[26:] + l[-6] = "0" + + wild = "".join(l) + pad = "" + i = 0 + while i < 32 and wild[i:i+4] == "0000": + pad += "0" + i += 4 + ins = binascii.unhexlify(pad + "%x" % int(wild, 2)) + p = ins + p[4:] + return p + pay + + +###################### Actions ###################### + +class _ofp_action_header(Packet): + name = "Dummy OpenFlow Action Header" + + def post_build(self, p, pay): + if self.len is None: + l = len(p)+len(pay) + p = p[:2] + struct.pack("!H", l) + p[4:] + return p + pay + +ofp_action_types = { 0: "OFPAT_OUTPUT", + 1: "OFPAT_SET_VLAN_VID", + 2: "OFPAT_SET_VLAN_PCP", + 3: "OFPAT_STRIP_VLAN", + 4: "OFPAT_SET_DL_SRC", + 5: "OFPAT_SET_DL_DST", + 6: "OFPAT_SET_NW_SRC", + 7: "OFPAT_SET_NW_DST", + 8: "OFPAT_SET_NW_TOS", + 9: "OFPAT_SET_TP_SRC", + 10: "OFPAT_SET_TP_DST", + 11: "OFPAT_ENQUEUE", + 65535: "OFPAT_VENDOR" } + +class OFPATOutput(_ofp_action_header): + name = "OFPAT_OUTPUT" + fields_desc = [ ShortEnumField("type", 0, ofp_action_types), + ShortField("len", 8), + ShortEnumField("port", 0, ofp_port_no), + ShortEnumField("max_len", "NO_BUFFER", ofp_max_len) ] + +class OFPATSetVLANVID(_ofp_action_header): + name = "OFPAT_SET_VLAN_VID" + fields_desc = [ ShortEnumField("type", 1, ofp_action_types), + ShortField("len", 8), + ShortField("vlan_vid", 0), + XShortField("pad", 0) ] + +class OFPATSetVLANPCP(_ofp_action_header): + name = "OFPAT_SET_VLAN_PCP" + fields_desc = [ ShortEnumField("type", 2, ofp_action_types), + ShortField("len", 8), + ByteField("vlan_pcp", 0), + X3BytesField("pad", 0) ] + +class OFPATStripVLAN(_ofp_action_header): + name = "OFPAT_STRIP_VLAN" + fields_desc = [ ShortEnumField("type", 3, ofp_action_types), + ShortField("len", 8), + XIntField("pad", 0) ] + +class OFPATSetDlSrc(_ofp_action_header): + name = "OFPAT_SET_DL_SRC" + fields_desc = [ ShortEnumField("type", 4, ofp_action_types), + ShortField("len", 16), + MACField("dl_addr", "0"), + XBitField("pad", 0, 48) ] + +class OFPATSetDlDst(_ofp_action_header): + name = "OFPAT_SET_DL_DST" + fields_desc = [ ShortEnumField("type", 5, ofp_action_types), + ShortField("len", 16), + MACField("dl_addr", "0"), + XBitField("pad", 0, 48) ] + +class OFPATSetNwSrc(_ofp_action_header): + name = "OFPAT_SET_NW_SRC" + fields_desc = [ ShortEnumField("type", 6, ofp_action_types), + ShortField("len", 8), + IPField("nw_addr", "0") ] + +class OFPATSetNwDst(_ofp_action_header): + name = "OFPAT_SET_NW_DST" + fields_desc = [ ShortEnumField("type", 7, ofp_action_types), + ShortField("len", 8), + IPField("nw_addr", "0") ] + +class OFPATSetNwToS(_ofp_action_header): + name = "OFPAT_SET_TP_TOS" + fields_desc = [ ShortEnumField("type", 8, ofp_action_types), + ShortField("len", 8), + ByteField("nw_tos", 0), + X3BytesField("pad", 0) ] + +class OFPATSetTpSrc(_ofp_action_header): + name = "OFPAT_SET_TP_SRC" + fields_desc = [ ShortEnumField("type", 9, ofp_action_types), + ShortField("len", 8), + ShortField("tp_port", 0), + XShortField("pad", 0) ] + +class OFPATSetTpDst(_ofp_action_header): + name = "OFPAT_SET_TP_DST" + fields_desc = [ ShortEnumField("type", 10, ofp_action_types), + ShortField("len", 8), + ShortField("tp_port", 0), + XShortField("pad", 0) ] + +class OFPATEnqueue(_ofp_action_header): + name = "OFPAT_ENQUEUE" + fields_desc = [ ShortEnumField("type", 11, ofp_action_types), + ShortField("len", 16), + ShortEnumField("port", 0, ofp_port_no), + XBitField("pad", 0, 48), + IntField("queue_id", 0) ] + +class OFPATVendor(_ofp_action_header): + name = "OFPAT_VENDOR" + fields_desc = [ ShortEnumField("type", 65535, ofp_action_types), + ShortField("len", 8), + IntField("vendor", 0) ] + +ofp_action_cls = { 0: OFPATOutput, + 1: OFPATSetVLANVID, + 2: OFPATSetVLANPCP, + 3: OFPATStripVLAN, + 4: OFPATSetDlSrc, + 5: OFPATSetDlDst, + 6: OFPATSetNwSrc, + 7: OFPATSetNwDst, + 8: OFPATSetNwToS, + 9: OFPATSetTpSrc, + 10: OFPATSetTpDst, + 11: OFPATEnqueue, + 65535: OFPATVendor } + +class ActionPacketListField(PacketListField): + def m2i(self, pkt, s): + t = struct.unpack("!H", s[:2])[0] + return ofp_action_cls.get(t, Raw)(s) + + @staticmethod + def _get_action_length(s): + return struct.unpack("!H", s[2:4])[0] + + def getfield(self, pkt, s): + lst = [] + remain = s + + while remain: + l = ActionPacketListField._get_action_length(remain) + current = remain[:l] + remain = remain[l:] + p = self.m2i(pkt, current) + lst.append(p) + + return remain, lst + + +####################### Queues ###################### + +class _ofp_queue_property_header(Packet): + name = "Dummy OpenFlow Queue Property Header" + + def post_build(self, p, pay): + if self.len is None: + l = len(p)+len(pay) + p = p[:2] + struct.pack("!H", l) + p[4:] + return p + pay + +ofp_queue_property_types = { 0: "OFPQT_NONE", + 1: "OFPQT_MIN_RATE" } + +class OFPQTNone(_ofp_queue_property_header): + name = "OFPQT_NONE" + fields_desc = [ ShortEnumField("type", 0, ofp_queue_property_types), + ShortField("len", 8), + XIntField("pad", 0) ] + +class OFPQTMinRate(_ofp_queue_property_header): + name = "OFPQT_MIN_RATE" + fields_desc = [ ShortEnumField("type", 1, ofp_queue_property_types), + ShortField("len", 16), + XIntField("pad", 0), + ShortField("rate", 0), + XBitField("pad2", 0, 48) ] + +ofp_queue_property_cls = { 0: OFPQTNone, + 1: OFPQTMinRate } + +class QueuePropertyPacketListField(PacketListField): + def m2i(self, pkt, s): + t = struct.unpack("!H", s[:2])[0] + return ofp_queue_property_cls.get(t, Raw)(s) + + @staticmethod + def _get_queue_property_length(s): + return struct.unpack("!H", s[2:4])[0] + + def getfield(self, pkt, s): + lst = [] + l = 0 + ret = "" + remain = s + + while remain: + l = QueuePropertyPacketListField._get_queue_property_length(remain) + current = remain[:l] + remain = remain[l:] + p = self.m2i(pkt, current) + lst.append(p) + + return remain + ret, lst + +class OFPPacketQueue(Packet): + + def extract_padding(self, s): + return "", s + + def post_build(self, p, pay): + if self.properties == []: + p += str(OFPQTNone()) + if self.len is None: + l = len(p)+len(pay) + p = p[:4] + struct.pack("!H", l) + p[6:] + return p + pay + + name = "OFP_PACKET_QUEUE" + fields_desc = [ IntField("queue_id", 0), + ShortField("len", None), + XShortField("pad", 0), + QueuePropertyPacketListField("properties", [], Packet, + length_from=lambda pkt:pkt.len-8) ] + +class QueuePacketListField(PacketListField): + + @staticmethod + def _get_queue_length(s): + return struct.unpack("!H", s[4:6])[0] + + def getfield(self, pkt, s): + lst = [] + l = 0 + ret = "" + remain = s + + while remain: + l = QueuePacketListField._get_queue_length(remain) + current = remain[:l] + remain = remain[l:] + p = OFPPacketQueue(current) + lst.append(p) + + return remain + ret, lst + + +##################################################### +############## OpenFlow 1.0 Messages ################ +##################################################### + +class _ofp_header(Packet): + name = "Dummy OpenFlow Header" + + def post_build(self, p, pay): + if self.len is None: + l = len(p)+len(pay) + p = p[:2] + struct.pack("!H", l) + p[4:] + return p + pay + +ofp_version = { 0x01: "OpenFlow 1.0", + 0x02: "OpenFlow 1.1", + 0x03: "OpenFlow 1.2", + 0x04: "OpenFlow 1.3", + 0x05: "OpenFlow 1.4" } + +ofp_type = { 0: "OFPT_HELLO", + 1: "OFPT_ERROR", + 2: "OFPT_ECHO_REQUEST", + 3: "OFPT_ECHO_REPLY", + 4: "OFPT_VENDOR", + 5: "OFPT_FEATURES_REQUEST", + 6: "OFPT_FEATURES_REPLY", + 7: "OFPT_GET_CONFIG_REQUEST", + 8: "OFPT_GET_CONFIG_REPLY", + 9: "OFPT_SET_CONFIG", + 10: "OFPT_PACKET_IN", + 11: "OFPT_FLOW_REMOVED", + 12: "OFPT_PORT_STATUS", + 13: "OFPT_PACKET_OUT", + 14: "OFPT_FLOW_MOD", + 15: "OFPT_PORT_MOD", + 16: "OFPT_STATS_REQUEST", + 17: "OFPT_STATS_REPLY", + 18: "OFPT_BARRIER_REQUEST", + 19: "OFPT_BARRIER_REPLY", + 20: "OFPT_QUEUE_GET_CONFIG_REQUEST", + 21: "OFPT_QUEUE_GET_CONFIG_REPLY" } + +class OFPTHello(_ofp_header): + name = "OFPT_HELLO" + fields_desc = [ ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 0, ofp_type), + ShortField("len", None), + IntField("xid", 0) ] + overload_fields = {TCP: {"sport": 6653}} + +##################################################### +#################### OFPT_ERROR ##################### +##################################################### + +### this class will be used to display some messages +### sent back by the switch after an error +class OFPacketField(PacketField): + def getfield(self, pkt, s): + try: + l = s[2:4] + l = struct.unpack("!H", l)[0] + ofload = s[:l] + remain = s[l:] + return remain, OpenFlow(None, ofload)(ofload) + except: + return "", Raw(s) + +ofp_error_type = { 0: "OFPET_HELLO_FAILED", + 1: "OFPET_BAD_REQUEST", + 2: "OFPET_BAD_ACTION", + 3: "OFPET_FLOW_MOD_FAILED", + 4: "OFPET_PORT_MOD_FAILED", + 5: "OFPET_QUEUE_OP_FAILED" } + +class OFPETHelloFailed(_ofp_header): + name = "OFPET_HELLO_FAILED" + fields_desc = [ ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 1, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("errtype", 0, ofp_error_type), + ShortEnumField("errcode", 0, { 0: "OFPHFC_INCOMPATIBLE", + 1: "OFPHFC_EPERM" }), + OFPacketField("data", "", Raw) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPETBadRequest(_ofp_header): + name = "OFPET_BAD_REQUEST" + fields_desc = [ ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 1, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("errtype", 1, ofp_error_type), + ShortEnumField("errcode", 0, { 0: "OFPBRC_BAD_VERSION", + 1: "OFPBRC_BAD_TYPE", + 2: "OFPBRC_BAD_STAT", + 3: "OFPBRC_BAD_VENDOR", + 4: "OFPBRC_BAD_SUBTYPE", + 5: "OFPBRC_EPERM", + 6: "OFPBRC_BAD_LEN", + 7: "OFPBRC_BUFFER_EMPTY", + 8: "OFPBRC_BUFFER_UNKNOWN" }), + OFPacketField("data", "", Raw) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPETBadAction(_ofp_header): + name = "OFPET_BAD_ACTION" + fields_desc = [ ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 1, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("errtype", 2, ofp_error_type), + ShortEnumField("errcode", 0, { 0: "OFPBAC_BAD_TYPE", + 1: "OFPBAC_BAD_LEN", + 2: "OFPBAC_BAD_VENDOR", + 3: "OFPBAC_BAD_VENDOR_TYPE", + 4: "OFPBAC_BAD_OUT_PORT", + 5: "OFPBAC_BAD_ARGUMENT", + 6: "OFPBAC_EPERM", + 7: "OFPBAC_TOO_MANY", + 8: "OFPBAC_BAD_QUEUE" }), + OFPacketField("data", "", Raw) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPETFlowModFailed(_ofp_header): + name = "OFPET_FLOW_MOD_FAILED" + fields_desc = [ ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 1, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("errtype", 3, ofp_error_type), + ShortEnumField("errcode", 0, { 0: "OFPFMFC_ALL_TABLES_FULL", + 1: "OFPFMFC_OVERLAP", + 2: "OFPFMFC_EPERM", + 3: "OFPFMFC_BAD_EMERG_TIMEOUT", + 4: "OFPFMFC_BAD_COMMAND", + 5: "OFPFMFC_UNSUPPORTED" }), + OFPacketField("data", "", Raw) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPETPortModFailed(_ofp_header): + name = "OFPET_PORT_MOD_FAILED" + fields_desc = [ ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 1, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("errtype", 4, ofp_error_type), + ShortEnumField("errcode", 0, { 0: "OFPPMFC_BAD_PORT", + 1: "OFPPMFC_BAD_HW_ADDR" }), + OFPacketField("data", "", Raw) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPETQueueOpFailed(_ofp_header): + name = "OFPET_QUEUE_OP_FAILED" + fields_desc = [ ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 1, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("errtype", 5, ofp_error_type), + ShortEnumField("errcode", 0, { 0: "OFPQOFC_BAD_PORT", + 1: "OFPQOFC_BAD_QUEUE", + 2: "OFPQOFC_EPERM" }), + OFPacketField("data", "", Raw) ] + overload_fields = {TCP: {"dport": 6653}} + +# ofp_error_cls allows generic method OpenFlow() to choose the right class for dissection +ofp_error_cls = { 0: OFPETHelloFailed, + 1: OFPETBadRequest, + 2: OFPETBadAction, + 3: OFPETFlowModFailed, + 4: OFPETPortModFailed, + 5: OFPETQueueOpFailed } + +################ end of OFPT_ERRORS ################# + +class OFPTEchoRequest(_ofp_header): + name = "OFPT_ECHO_REQUEST" + fields_desc = [ ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 2, ofp_type), + ShortField("len", None), + IntField("xid", 0) ] + overload_fields = {TCP: {"sport": 6653}} + +class OFPTEchoReply(_ofp_header): + name = "OFPT_ECHO_REPLY" + fields_desc = [ ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 3, ofp_type), + ShortField("len", None), + IntField("xid", 0) ] + overload_fields = {TCP: {"sport": 6653}} + +class OFPTVendor(_ofp_header): + name = "OFPT_VENDOR" + fields_desc = [ ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 4, ofp_type), + ShortField("len", None), + IntField("xid", 0), + IntField("vendor", 0) ] + overload_fields = {TCP: {"sport": 6653}} + +class OFPTFeaturesRequest(_ofp_header): + name = "OFPT_FEATURES_REQUEST" + fields_desc = [ ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 5, ofp_type), + ShortField("len", None), + IntField("xid", 0) ] + overload_fields = {TCP: {"sport": 6653}} + +ofp_action_types_flags = ofp_action_types.values()[:-1] # no ofpat_vendor flag + +class OFPTFeaturesReply(_ofp_header): + name = "OFPT_FEATURES_REPLY" + fields_desc = [ ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 6, ofp_type), + ShortField("len", None), + IntField("xid", 0), + LongField("datapath_id", 0), + IntField("n_buffers", 0), + ByteField("n_tables", 1), + X3BytesField("pad", 0), + FlagsField("capabilities", 0, 32, [ "FLOW_STATS", + "TABLE_STATS", + "PORT_STATS", + "STP", + "RESERVED", + "IP_REASM", + "QUEUE_STATS", + "ARP_MATCH_IP" ]), + FlagsField("actions", 0, 32, ofp_action_types_flags), + PacketListField("ports", None, OFPPhyPort, + length_from=lambda pkt:pkt.len-32) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPTGetConfigRequest(_ofp_header): + name = "OFPT_GET_CONFIG_REQUEST" + fields_desc = [ ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 7, ofp_type), + ShortField("len", None), + IntField("xid", 0) ] + overload_fields = {TCP: {"sport": 6653}} + +class OFPTGetConfigReply(_ofp_header): + name = "OFPT_GET_CONFIG_REPLY" + fields_desc = [ ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 8, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("flags", 0, { 0: "FRAG_NORMAL", + 1: "FRAG_DROP", + 2: "FRAG_REASM", + 3: "FRAG_MASK" }), + ShortField("miss_send_len", 0) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPTSetConfig(_ofp_header): + name = "OFPT_SET_CONFIG" + fields_desc = [ ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 9, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("flags", 0, { 0: "FRAG_NORMAL", + 1: "FRAG_DROP", + 2: "FRAG_REASM", + 3: "FRAG_MASK" }), + ShortField("miss_send_len", 128) ] + overload_fields = {TCP: {"sport": 6653}} + +class OFPTPacketIn(_ofp_header): + name = "OFPT_PACKET_IN" + fields_desc = [ ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 10, ofp_type), + ShortField("len", None), + IntField("xid", 0), + IntEnumField("buffer_id", "NO_BUFFER", ofp_buffer), + ShortField("total_len", 0), + ShortEnumField("in_port", 0, ofp_port_no), + ByteEnumField("reason", 0, { 0: "OFPR_NO_MATCH", + 1: "OFPR_ACTION" }), + XByteField("pad", 0), + PacketField("data", None, Ether) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPTFlowRemoved(_ofp_header): + name = "OFPT_FLOW_REMOVED" + fields_desc = [ ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 11, ofp_type), + ShortField("len", None), + IntField("xid", 0), + PacketField("match", OFPMatch(), OFPMatch), + LongField("cookie", 0), + ShortField("priority", 0), + ByteEnumField("reason", 0, { 0: "OFPRR_IDLE_TIMEOUT", + 1: "OFPRR_HARD_TIMEOUT", + 2: "OFPRR_DELETE" }), + XByteField("pad1", 0), + IntField("duration_sec", 0), + IntField("duration_nsec", 0), + ShortField("idle_timeout", 0), + XShortField("pad2", 0), + LongField("packet_count", 0), + LongField("byte_count", 0) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPTPortStatus(_ofp_header): + name = "OFPT_PORT_STATUS" + fields_desc = [ ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 12, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ByteEnumField("reason", 0, { 0: "OFPPR_ADD", + 1: "OFPPR_DELETE", + 2: "OFPPR_MODIFY" }), + XBitField("pad", 0, 56), + PacketField("desc", OFPPhyPort(), OFPPhyPort) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPTPacketOut(_ofp_header): + name = "OFPT_PACKET_OUT" + fields_desc = [ ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 13, ofp_type), + ShortField("len", None), + IntField("xid", 0), + IntEnumField("buffer_id", "NO_BUFFER", ofp_buffer), + ShortEnumField("in_port", "NONE", ofp_port_no), + FieldLenField("actions_len", None, fmt="H", length_of="actions"), + ActionPacketListField("actions", [], Packet, + length_from=lambda pkt:pkt.actions_len), + PacketField("data", None, Ether) ] + overload_fields = {TCP: {"sport": 6653}} + +class OFPTFlowMod(_ofp_header): + name = "OFPT_FLOW_MOD" + fields_desc = [ ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 14, ofp_type), + ShortField("len", None), + IntField("xid", 0), + PacketField("match", OFPMatch(), OFPMatch), + LongField("cookie", 0), + ShortEnumField("cmd", 0, { 0: "OFPFC_ADD", + 1: "OFPFC_MODIFY", + 2: "OFPFC_MODIFY_STRICT", + 3: "OFPFC_DELETE", + 4: "OFPFC_DELETE_STRICT" }), + ShortField("idle_timeout", 0), + ShortField("hard_timeout", 0), + ShortField("priority", 0), + IntEnumField("buffer_id", "NO_BUFFER", ofp_buffer), + ShortEnumField("out_port", "NONE", ofp_port_no), + FlagsField("flags", 0, 16, [ "SEND_FLOW_REM", + "CHECK_OVERLAP", + "EMERG" ]), + ActionPacketListField("actions", [], Packet, + length_from=lambda pkt:pkt.len-72) ] + overload_fields = {TCP: {"sport": 6653}} + +class OFPTPortMod(_ofp_header): + name = "OFPT_PORT_MOD" + fields_desc = [ ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 15, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("port_no", 0, ofp_port_no), + MACField("hw_addr", "0"), + FlagsField("config", 0, 32, ofp_port_config), + FlagsField("mask", 0, 32, ofp_port_config), + FlagsField("advertise", 0, 32, ofp_port_features), + IntField("pad", 0) ] + overload_fields = {TCP: {"sport": 6653}} + +##################################################### +##################### OFPT_STATS #################### +##################################################### + +ofp_stats_types = { 0: "OFPST_DESC", + 1: "OFPST_FLOW", + 2: "OFPST_AGGREGATE", + 3: "OFPST_TABLE", + 4: "OFPST_PORT", + 5: "OFPST_QUEUE", + 65535: "OFPST_VENDOR" } + +class OFPTStatsRequestDesc(_ofp_header): + name = "OFPST_STATS_REQUEST_DESC" + fields_desc = [ ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 16, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("stats_type", 0, ofp_stats_types), + FlagsField("flags", 0, 16, []) ] + overload_fields = {TCP: {"sport": 6653}} + +class OFPTStatsReplyDesc(_ofp_header): + name = "OFPST_STATS_REPLY_DESC" + fields_desc = [ ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 17, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("stats_type", 0, ofp_stats_types), + FlagsField("flags", 0, 16, []), + StrFixedLenField("mfr_desc", "", 256), + StrFixedLenField("hw_desc", "", 256), + StrFixedLenField("sw_desc", "", 256), + StrFixedLenField("serial_num", "", 32), + StrFixedLenField("dp_desc", "", 256) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPTStatsRequestFlow(_ofp_header): + name = "OFPST_STATS_REQUEST_FLOW" + fields_desc = [ ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 16, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("stats_type", 1, ofp_stats_types), + FlagsField("flags", 0, 16, []), + PacketField("match", OFPMatch(), OFPMatch), + ByteEnumField("table_id", "ALL", ofp_table), + ByteField("pad", 0), + ShortEnumField("out_port", "NONE", ofp_port_no) ] + overload_fields = {TCP: {"sport": 6653}} + +class OFPFlowStats(Packet): + + def post_build(self, p, pay): + if self.length is None: + l = len(p)+len(pay) + p = struct.pack("!H", l) + p[2:] + return p + pay + + name = "OFP_FLOW_STATS" + fields_desc = [ ShortField("length", None), + ByteField("table_id", 0), + XByteField("pad1", 0), + PacketField("match", OFPMatch(), OFPMatch), + IntField("duration_sec", 0), + IntField("duration_nsec", 0), + ShortField("priority", 0), + ShortField("idle_timeout", 0), + ShortField("hard_timeout", 0), + XBitField("pad2", 0, 48), + LongField("cookie", 0), + LongField("packet_count", 0), + LongField("byte_count", 0), + ActionPacketListField("actions", [], Packet, + length_from=lambda pkt:pkt.length-88) ] + +class FlowStatsPacketListField(PacketListField): + + @staticmethod + def _get_flow_stats_length(s): + return struct.unpack("!H", s[:2])[0] + + def getfield(self, pkt, s): + lst = [] + remain = s + + while remain: + l = FlowStatsPacketListField._get_flow_stats_length(remain) + current = remain[:l] + remain = remain[l:] + p = OFPFlowStats(current) + lst.append(p) + + return remain, lst + +class OFPTStatsReplyFlow(_ofp_header): + name = "OFPST_STATS_REPLY_FLOW" + fields_desc = [ ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 17, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("stats_type", 1, ofp_stats_types), + FlagsField("flags", 0, 16, []), + FlowStatsPacketListField("flow_stats", [], Packet, + length_from=lambda pkt:pkt.len-12) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPTStatsRequestAggregate(_ofp_header): + name = "OFPST_STATS_REQUEST_AGGREGATE" + fields_desc = [ ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 16, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("stats_type", 2, ofp_stats_types), + FlagsField("flags", 0, 16, []), + PacketField("match", OFPMatch(), OFPMatch), + ByteEnumField("table_id", "ALL", ofp_table), + ByteField("pad", 0), + ShortEnumField("out_port", "NONE", ofp_port_no) ] + overload_fields = {TCP: {"sport": 6653}} + +class OFPTStatsReplyAggregate(_ofp_header): + name = "OFPST_STATS_REPLY_AGGREGATE" + fields_desc = [ ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 17, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("stats_type", 2, ofp_stats_types), + FlagsField("flags", 0, 16, []), + LongField("packet_count", 0), + LongField("byte_count", 0), + IntField("flow_count", 0), + XIntField("pad", 0) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPTStatsRequestTable(_ofp_header): + name = "OFPST_STATS_REQUEST_TABLE" + fields_desc = [ ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 16, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("stats_type", 3, ofp_stats_types), + FlagsField("flags", 0, 16, []) ] + overload_fields = {TCP: {"sport": 6653}} + +class OFPTableStats(Packet): + + def extract_padding(self, s): + return "", s + + name = "OFP_TABLE_STATS" + fields_desc = [ ByteField("table_id", 0), + X3BytesField("pad", 0), + StrFixedLenField("name", "", 32), + FlagsField("wildcards1", 0x003, 12, [ "DL_VLAN_PCP", + "NW_TOS" ]), + BitField("nw_dst_mask", 63, 6), # 32 would be enough + BitField("nw_src_mask", 63, 6), + FlagsField("wildcards2", 0xff, 8, [ "IN_PORT", + "DL_VLAN", + "DL_SRC", + "DL_DST", + "DL_TYPE", + "NW_PROTO", + "TP_SRC", + "TP_DST" ]), + IntField("max_entries", 0), + IntField("active_count", 0), + LongField("lookup_count", 0), + LongField("matched_count", 0) ] + +class OFPTStatsReplyTable(_ofp_header): + name = "OFPST_STATS_REPLY_TABLE" + fields_desc = [ ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 17, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("stats_type", 3, ofp_stats_types), + FlagsField("flags", 0, 16, []), + PacketListField("table_stats", None, OFPTableStats, + length_from=lambda pkt:pkt.len-12) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPTStatsRequestPort(_ofp_header): + name = "OFPST_STATS_REQUEST_PORT" + fields_desc = [ ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 16, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("stats_type", 4, ofp_stats_types), + FlagsField("flags", 0, 16, []), + ShortEnumField("port_no", "NONE", ofp_port_no), + XBitField("pad", 0, 48) ] + overload_fields = {TCP: {"sport": 6653}} + +class OFPPortStats(Packet): + + def extract_padding(self, s): + return "", s + + name = "OFP_PORT_STATS" + fields_desc = [ ShortEnumField("port_no", 0, ofp_port_no), + XBitField("pad", 0, 48), + LongField("rx_packets", 0), + LongField("tx_packets", 0), + LongField("rx_bytes", 0), + LongField("tx_bytes", 0), + LongField("rx_dropped", 0), + LongField("tx_dropped", 0), + LongField("rx_errors", 0), + LongField("tx_errors", 0), + LongField("rx_frame_err", 0), + LongField("rx_over_err", 0), + LongField("rx_crc_err", 0), + LongField("collisions", 0) ] + +class OFPTStatsReplyPort(_ofp_header): + name = "OFPST_STATS_REPLY_TABLE" + fields_desc = [ ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 17, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("stats_type", 4, ofp_stats_types), + FlagsField("flags", 0, 16, []), + PacketListField("port_stats", None, OFPPortStats, + length_from=lambda pkt:pkt.len-12) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPTStatsRequestQueue(_ofp_header): + name = "OFPST_STATS_REQUEST_QUEUE" + fields_desc = [ ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 16, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("stats_type", 5, ofp_stats_types), + FlagsField("flags", 0, 16, []), + ShortEnumField("port_no", "NONE", ofp_port_no), + XShortField("pad", 0), + IntEnumField("queue_id", "ALL", ofp_queue) ] + overload_fields = {TCP: {"sport": 6653}} + +class OFPTStatsReplyQueue(_ofp_header): + name = "OFPST_STATS_REPLY_QUEUE" + fields_desc = [ ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 17, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("stats_type", 5, ofp_stats_types), + FlagsField("flags", 0, 16, []), + ShortEnumField("port_no", "NONE", ofp_port_no), + XShortField("pad", 0), + IntEnumField("queue_id", "ALL", ofp_queue), + LongField("tx_bytes", 0), + LongField("tx_packets", 0), + LongField("tx_errors", 0) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPTStatsRequestVendor(_ofp_header): + name = "OFPST_STATS_REQUEST_VENDOR" + fields_desc = [ ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 16, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("stats_type", 6, ofp_stats_types), + FlagsField("flags", 0, 16, []), + IntField("vendor", 0) ] + overload_fields = {TCP: {"sport": 6653}} + +class OFPTStatsReplyVendor(_ofp_header): + name = "OFPST_STATS_REPLY_VENDOR" + fields_desc = [ ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 17, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("stats_type", 6, ofp_stats_types), + FlagsField("flags", 0, 16, []), + IntField("vendor", 0) ] + overload_fields = {TCP: {"dport": 6653}} + +# ofp_stats_request/reply_cls allows generic method OpenFlow() (end of script) +# to choose the right class for dissection +ofp_stats_request_cls = { 0: OFPTStatsRequestDesc, + 1: OFPTStatsRequestFlow, + 2: OFPTStatsRequestAggregate, + 3: OFPTStatsRequestTable, + 4: OFPTStatsRequestPort, + 5: OFPTStatsRequestQueue, + 65535: OFPTStatsRequestVendor } + +ofp_stats_reply_cls = { 0: OFPTStatsReplyDesc, + 1: OFPTStatsReplyFlow, + 2: OFPTStatsReplyAggregate, + 3: OFPTStatsReplyTable, + 4: OFPTStatsReplyPort, + 5: OFPTStatsReplyQueue, + 65535: OFPTStatsReplyVendor } + +################ end of OFPT_STATS ################## + +class OFPTBarrierRequest(_ofp_header): + name = "OFPT_BARRIER_REQUEST" + fields_desc = [ ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 18, ofp_type), + ShortField("len", None), + IntField("xid", 0) ] + overload_fields = {TCP: {"sport": 6653}} + +class OFPTBarrierReply(_ofp_header): + name = "OFPT_BARRIER_REPLY" + fields_desc = [ ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 19, ofp_type), + ShortField("len", None), + IntField("xid", 0) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPTQueueGetConfigRequest(_ofp_header): + name = "OFPT_QUEUE_GET_CONFIG_REQUEST" + fields_desc = [ ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 20, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("port", 0, ofp_port_no), + XShortField("pad", 0) ] + overload_fields = {TCP: {"sport": 6653}} + +class OFPTQueueGetConfigReply(_ofp_header): + name = "OFPT_QUEUE_GET_CONFIG_REPLY" + fields_desc = [ ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 21, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("port", 0, ofp_port_no), + XBitField("pad", 0, 48), + QueuePacketListField("queues", [], Packet, + length_from=lambda pkt:pkt.len-16) ] + overload_fields = {TCP: {"dport": 6653}} + +# ofpt_cls allows generic method OpenFlow() to choose the right class for dissection +ofpt_cls = { 0: OFPTHello, + #1: OFPTError, + 2: OFPTEchoRequest, + 3: OFPTEchoReply, + 4: OFPTVendor, + 5: OFPTFeaturesRequest, + 6: OFPTFeaturesReply, + 7: OFPTGetConfigRequest, + 8: OFPTGetConfigReply, + 9: OFPTSetConfig, + 10: OFPTPacketIn, + 11: OFPTFlowRemoved, + 12: OFPTPortStatus, + 13: OFPTPacketOut, + 14: OFPTFlowMod, + 15: OFPTPortMod, + #16: OFPTStatsRequest, + #17: OFPTStatsReply, + 18: OFPTBarrierRequest, + 19: OFPTBarrierReply, + 20: OFPTQueueGetConfigRequest, + 21: OFPTQueueGetConfigReply } + +TCP_guess_payload_class_copy = TCP.guess_payload_class + +def OpenFlow(self, payload): + if self is None or self.dport == 6653 or self.dport == 6633 or self.sport == 6653 or self.sport == 6653: + # port 6653 has been allocated by IANA, port 6633 should no longer be used + # OpenFlow function may be called with None self in OFPPacketField + of_type = ord(payload[1]) + if of_type == 1: + err_type = ord(payload[9]) + # err_type is a short int, but last byte is enough + if err_type == 255: err_type = 65535 + return ofp_error_cls[err_type] + elif of_type == 16: + mp_type = ord(payload[9]) + if mp_type == 255: mp_type = 65535 + return ofp_stats_request_cls[mp_type] + elif of_type == 17: + mp_type = ord(payload[9]) + if mp_type == 255: mp_type = 65535 + return ofp_stats_reply_cls[mp_type] + else: + return ofpt_cls[of_type] + else: + return TCP_guess_payload_class_copy(self, payload) + +TCP.guess_payload_class = OpenFlow diff --git a/scapy/contrib/openflow.uts b/scapy/contrib/openflow.uts new file mode 100755 index 0000000000000000000000000000000000000000..a26af678b819990044b337f2703995ea4db60384 --- /dev/null +++ b/scapy/contrib/openflow.uts @@ -0,0 +1,81 @@ +% Tests for OpenFlow v1.0 with Scapy + ++ Usual OFv1.0 messages + += OFPTHello(), simple hello message +ofm = OFPTHello() +str(ofm) == '\x01\x00\x00\x08\x00\x00\x00\x00' + += OFPTEchoRequest(), echo request +ofm = OFPTEchoRequest() +str(ofm) == '\x01\x02\x00\x08\x00\x00\x00\x00' + += OFPMatch(), check wildcard completion +ofm = OFPMatch(in_port=1, nw_tos=8) +ofm = OFPMatch(str(ofm)) +assert(ofm.wildcards1 == 0x1) +ofm.wildcards2 == 0xfe + += OpenFlow(), generic method test with OFPTEchoRequest() +ofm = OFPTEchoRequest() +s = str(ofm) +isinstance(OpenFlow(None,s)(s), OFPTEchoRequest) + += OFPTFlowMod(), check codes and defaults values +ofm = OFPTFlowMod(cmd='OFPFC_DELETE', out_port='CONTROLLER', flags='CHECK_OVERLAP+EMERG') +assert(ofm.cmd == 3) +assert(ofm.buffer_id == 0xffffffff) +assert(ofm.out_port == 0xfffd) +ofm.flags == 6 + ++ Complex OFv1.3 messages + += OFPTFlowMod(), complex flow_mod +mtc = OFPMatch(dl_vlan=10, nw_src='192.168.42.0', nw_src_mask=8) +act1 = OFPATSetNwSrc(nw_addr='192.168.42.1') +act2 = OFPATOutput(port='CONTROLLER') +act3 = OFPATSetDlSrc(dl_addr='1a:d5:cb:4e:3c:64') +ofm = OFPTFlowMod(priority=1000, match=mtc, flags='CHECK_OVERLAP', actions=[act1,act2,act3]) +str(ofm) +s = '\x01\x0e\x00h\x00\x00\x00\x00\x00?\xc8\xfd\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x00\x00\x00\x00\x00\xc0\xa8*\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xe8\xff\xff\xff\xff\xff\xff\x00\x02\x00\x06\x00\x08\xc0\xa8*\x01\x00\x00\x00\x08\xff\xfd\xff\xff\x00\x04\x00\x10\x1a\xd5\xcbN<d\x00\x00\x00\x00\x00\x00' +str(ofm) == s + += OFPETBadRequest() containing a flow_mod with wrong table_id +flowmod = OFPTFlowMod(actions=OFPATOutput(port='LOCAL')) +ofm = OFPETBadRequest(errcode='OFPBRC_EPERM', data=str(flowmod)) +hexdump(ofm) +s = '\x01\x01\x00\\\x00\x00\x00\x00\x00\x01\x00\x05\x01\x0e\x00P\x00\x00\x00\x00\x00?\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x08\xff\xfe\xff\xff' +str(ofm) == s + += OFPTPacketIn() containing an Ethernet frame +ofm = OFPTPacketIn(data=Ether()/IP()/ICMP()) +p = OFPTPacketIn(str(ofm)) +dat = p.data +assert(isinstance(dat, Ether)) +assert(isinstance(dat.payload, IP)) +isinstance(dat.payload.payload, ICMP) + ++ Layer bindings + += TCP()/OFPTStatsRequestDesc(), check default sport +p = TCP()/OFPTStatsRequestDesc() +p[TCP].sport == 6653 + += TCP()/OFPETHelloFailed(), check default dport +p = TCP()/OFPETHelloFailed() +p[TCP].dport == 6653 + += TCP()/OFPTHello() dissection, check new TCP.guess_payload_class +o = TCP()/OFPTHello() +p = TCP(str(o)) +p[TCP].sport == 6653 +isinstance(p[TCP].payload, OFPTHello) + += complete Ether()/IP()/TCP()/OFPTFeaturesRequest() +ofm = Ether(src='00:11:22:33:44:55',dst='01:23:45:67:89:ab')/IP(src='10.0.0.7',dst='192.168.0.42')/TCP(sport=6633)/OFPTFeaturesRequest(xid=23) +s = '\x01#Eg\x89\xab\x00\x11"3DU\x08\x00E\x00\x000\x00\x01\x00\x00@\x06\xaf\xee\n\x00\x00\x07\xc0\xa8\x00*\x19\xe9\x00P\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xa9\xa4\x00\x00\x01\x05\x00\x08\x00\x00\x00\x17' +assert(str(ofm) == s) +e = Ether(s) +e.show2() +of = OFPTFeaturesRequest(e[TCP].load) +of.xid == 23 diff --git a/scapy/contrib/openflow3.py b/scapy/contrib/openflow3.py new file mode 100755 index 0000000000000000000000000000000000000000..64828722fa890b7beda48287604d49bbde029605 --- /dev/null +++ b/scapy/contrib/openflow3.py @@ -0,0 +1,3369 @@ +## This file is part of Scapy +## See http://www.secdev.org/projects/scapy for more information +## Copyright (C) Philippe Biondi <phil@secdev.org> +## This program is published under a GPLv2 license + +## Copyright (C) 2014 Maxence Tury <maxence.tury@ssi.gouv.fr> +## OpenFlow is an open standard used in SDN deployments. +## Based on OpenFlow v1.3.4 +## Specifications can be retrieved from https://www.opennetworking.org/ + +# scapy.contrib.description = openflow v1.3 +# scapy.contrib.status = loads + +import struct +from scapy.all import * + +### If prereq_autocomplete is True then match prerequisites will be +### automatically handled. See OFPMatch class. +prereq_autocomplete = False + +##################################################### +################# Predefined values ################# +##################################################### + +ofp_port_no = { 0xfffffff8: "IN_PORT", + 0xfffffff9: "TABLE", + 0xfffffffa: "NORMAL", + 0xfffffffb: "FLOOD", + 0xfffffffc: "ALL", + 0xfffffffd: "CONTROLLER", + 0xfffffffe: "LOCAL", + 0xffffffff: "ANY" } + +ofp_group = { 0xffffff00: "MAX", + 0xfffffffc: "ALL", + 0xffffffff: "ANY" } + +ofp_table = { 0xfe: "MAX", + 0xff: "ALL" } + +ofp_queue = { 0xffffffff: "ALL" } + +ofp_meter = { 0xffff0000: "MAX", + 0xfffffffd: "SLOWPATH", + 0xfffffffe: "CONTROLLER", + 0xffffffff: "ALL" } + +ofp_buffer = { 0xffffffff: "NO_BUFFER" } + +ofp_max_len = { 0xffff: "NO_BUFFER" } + + +##################################################### +################# Common structures ################# +##################################################### + +### The following structures will be used in different types +### of OpenFlow messages: ports, matches/OXMs, actions, +### instructions, buckets, queues, meter bands. + + +################## Hello elements ################### + +class _ofp_hello_elem_header(Packet): + name = "Dummy OpenFlow Hello Elem Header" + + def post_build(self, p, pay): + if self.len is None: + l = len(p)+len(pay) + p = p[:2] + struct.pack("!H", l) + p[4:] + return p + pay + +ofp_hello_elem_types = { 1: "OFPHET_VERSIONBITMAP" } + +class OFPHETVersionBitmap(_ofp_hello_elem_header): + name = "OFPHET_VERSIONBITMAP" + fields_desc = [ ShortEnumField("type", 1, ofp_hello_elem_types), + ShortField("len", 8), + FlagsField("bitmap", 0, 32, [ "Type 0", + "OFv1.0", + "OFv1.1", + "OFv1.2", + "OFv1.3", + "OFv1.4" ]) ] + +ofp_hello_elem_cls = { 1: OFPHETVersionBitmap } + +class HelloElemPacketListField(PacketListField): + def m2i(self, pkt, s): + t = struct.unpack("!H", s[:2])[0] + return ofp_hello_elem_cls.get(t, Raw)(s) + + @staticmethod + def _get_hello_elem_length(s): + return struct.unpack("!H", s[2:4])[0] + + def getfield(self, pkt, s): + lst = [] + remain = s + + while remain: + l = HelloElemPacketListField._get_hello_elem_length(remain) + current = remain[:l] + remain = remain[l:] + p = self.m2i(pkt, current) + lst.append(p) + + return remain, lst + + +####################### Ports ####################### + +ofp_port_config = [ "PORT_DOWN", + "NO_STP", # undefined in v1.3 + "NO_RECV", + "NO_RECV_STP", # undefined in v1.3 + "NO_FLOOD", # undefined in v1.3 + "NO_FWD", + "NO_PACKET_IN" ] + +ofp_port_state = [ "LINK_DOWN", + "BLOCKED", + "LIVE" ] + +ofp_port_features = [ "10MB_HD", + "10MB_FD", + "100MB_HD", + "100MB_FD", + "1GB_HD", + "1GB_FD", + "10GB_FD", + "40GB_FD", + "100GB_FD", + "1TB_FD", + "OTHER", + "COPPER", + "FIBER", + "AUTONEG", + "PAUSE", + "PAUSE_ASYM" ] + +class OFPPort(Packet): + name = "OFP_PHY_PORT" + fields_desc = [ IntEnumField("port_no", 0, ofp_port_no), + XIntField("pad1", 0), + MACField("hw_addr", "0"), + XShortField("pad2", 0), + StrFixedLenField("port_name", "", 16), + FlagsField("config", 0, 32, ofp_port_config), + FlagsField("state", 0, 32, ofp_port_state), + FlagsField("curr", 0, 32, ofp_port_features), + FlagsField("advertised", 0, 32, ofp_port_features), + FlagsField("supported", 0, 32, ofp_port_features), + FlagsField("peer", 0, 32, ofp_port_features), + IntField("curr_speed", 0), + IntField("max_speed", 0) ] + + def extract_padding(self, s): + return "", s + # extract_padding is overridden in order for s not to be considered + # as belonging to the same layer (s usually contains other OFPPorts) + + +################### Matches & OXMs ################## + +ofp_oxm_classes = { 0: "OFPXMC_NXM_0", + 1: "OFPXMC_NXM_1", + 0x8000: "OFPXMC_OPENFLOW_BASIC", + 0xffff: "OFPXMC_EXPERIMENTER" } + +ofp_oxm_names = { 0: "OFB_IN_PORT", + 1: "OFB_IN_PHY_PORT", + 2: "OFB_METADATA", + 3: "OFB_ETH_DST", + 4: "OFB_ETH_SRC", + 5: "OFB_ETH_TYPE", + 6: "OFB_VLAN_VID", + 7: "OFB_VLAN_PCP", + 8: "OFB_IP_DSCP", + 9: "OFB_IP_ECN", + 10: "OFB_IP_PROTO", + 11: "OFB_IPV4_SRC", + 12: "OFB_IPV4_DST", + 13: "OFB_TCP_SRC", + 14: "OFB_TCP_DST", + 15: "OFB_UDP_SRC", + 16: "OFB_UDP_DST", + 17: "OFB_SCTP_SRC", + 18: "OFB_SCTP_DST", + 19: "OFB_ICMPV4_TYPE", + 20: "OFB_ICMPV4_CODE", + 21: "OFB_ARP_OP", + 22: "OFB_ARP_SPA", + 23: "OFB_ARP_TPA", + 24: "OFB_ARP_SHA", + 25: "OFB_ARP_THA", + 26: "OFB_IPV6_SRC", + 27: "OFB_IPV6_DST", + 28: "OFB_IPV6_FLABEL", + 29: "OFB_ICMPV6_TYPE", + 30: "OFB_ICMPV6_CODE", + 31: "OFB_IPV6_ND_TARGET", + 32: "OFB_IPV6_ND_SLL", + 33: "OFB_IPV6_ND_TLL", + 34: "OFB_MPLS_LABEL", + 35: "OFB_MPLS_TC", + 36: "OFB_MPLS_BOS", + 37: "OFB_PBB_ISID", + 38: "OFB_TUNNEL_ID", + 39: "OFB_IPV6_EXTHDR" } + +ofp_oxm_constr = { 0: ["OFBInPort", "in_port", 4], + 1: ["OFBInPhyPort", "in_phy_port", 4], + 2: ["OFBMetadata", "metadata", 8], + 3: ["OFBEthDst", "eth_dst", 6], + 4: ["OFBEthSrc", "eth_src", 6], + 5: ["OFBEthType", "eth_type", 2], + 6: ["OFBVLANVID", "vlan_vid", 2], + 7: ["OFBVLANPCP", "vlan_pcp", 1], + 8: ["OFBIPDSCP", "ip_dscp", 1], + 9: ["OFBIPECN", "ip_ecn", 1], + 10: ["OFBIPProto", "ip_proto", 1], + 11: ["OFBIPv4Src", "ipv4_src", 4], + 12: ["OFBIPv4Dst", "ipv4_dst", 4], + 13: ["OFBTCPSrc", "tcp_src", 2], + 14: ["OFBTCPDst", "tcp_dst", 2], + 15: ["OFBUDPSrc", "udp_src", 2], + 16: ["OFBUDPDst", "udp_dst", 2], + 17: ["OFBSCTPSrc", "sctp_src", 2], + 18: ["OFBSCTPDst", "sctp_dst", 2], + 19: ["OFBICMPv4Type", "icmpv4_type", 1], + 20: ["OFBICMPv4Code", "icmpv4_code", 1], + 21: ["OFBARPOP", "arp_op", 2], + 22: ["OFBARPSPA", "arp_spa", 4], + 23: ["OFBARPTPA", "arp_tpa", 4], + 24: ["OFBARPSHA", "arp_sha", 6], + 25: ["OFBARPTHA", "arp_tha", 6], + 26: ["OFBIPv6Src", "ipv6_src", 16], + 27: ["OFBIPv6Dst", "ipv6_dst", 16], + 28: ["OFBIPv6FLabel", "ipv6_flabel", 4], + 29: ["OFBICMPv6Type", "icmpv6_type", 1], + 30: ["OFBICMPv6Code", "icmpv6_code", 1], + 31: ["OFBIPv6NDTarget", "ipv6_nd_target", 16], + 32: ["OFBIPv6NDSLL", "ipv6_sll", 6], + 33: ["OFBIPv6NDTLL", "ipv6_tll", 6], + 34: ["OFBMPLSLabel", "mpls_label", 4], + 35: ["OFBMPLSTC", "mpls_tc", 1], + 36: ["OFBMPLSBoS", "mpls_bos", 1], + 37: ["OFBPBBISID", "pbb_isid", 3], + 38: ["OFBTunnelID", "tunnel_id", 8], + 39: ["OFBIPv6ExtHdr", "ipv6_ext_hdr_flags", 2] } + +# the ipv6flags array is useful only to the OFBIPv6ExtHdr class +ipv6flags = [ "NONEXT", + "ESP", + "AUTH", + "DEST", + "FRAG", + "ROUTER", + "HOP", + "UNREP", + "UNSEQ" ] + +### here we fill ofp_oxm_fields with the fields that will be used +### to generate the various OXM classes +### e.g. the call to add_ofp_oxm_fields(0, ["OFBInPort", "in_port", 4]) +### will add {0: [ShortEnumField("class",..), BitEnumField("field",..),..]} +ofp_oxm_fields = {} +def add_ofp_oxm_fields(i, org): + ofp_oxm_fields[i] = [ ShortEnumField("class", "OFPXMC_OPENFLOW_BASIC", ofp_oxm_classes), + BitEnumField("field", i/2, 7, ofp_oxm_names), + BitField("hasmask", i%2, 1) ] + ofp_oxm_fields[i].append(ByteField("length", org[2]+org[2]*(i%2))) + if i/2 == 0: # OFBInPort + ofp_oxm_fields[i].append(IntEnumField(org[1], 0, ofp_port_no)) + elif i/2 == 3 or i/2 == 4: # OFBEthSrc & OFBEthDst + ofp_oxm_fields[i].append(MACField(org[1], None)) + elif i/2 == 11 or i/2 == 12: # OFBIPv4Src & OFBIPv4Dst + ofp_oxm_fields[i].append(IPField(org[1], "0")) + elif i/2 == 39: # OFBIPv6ExtHdr + ofp_oxm_fields[i].append(FlagsField(org[1], 0, 8*org[2], ipv6flags)) + else: + ofp_oxm_fields[i].append(BitField(org[1], 0, 8*org[2])) + if i%2: + ofp_oxm_fields[i].append(BitField(org[1]+"_mask", 0, 8*org[2])) + +# some HM classes are not supported par OFv1.3 but we will create them anyway +for i,cls in ofp_oxm_constr.items(): + add_ofp_oxm_fields(2*i, cls) + add_ofp_oxm_fields(2*i+1, cls) + +### now we create every OXM class with the same call, +### (except that static variable create_oxm_class.i is each time different) +### and we fill ofp_oxm_cls with them +ofp_oxm_cls = {} +ofp_oxm_id_cls = {} +def create_oxm_cls(): + # static variable initialization + if not hasattr(create_oxm_cls, "i"): + create_oxm_cls.i = 0 + + index = create_oxm_cls.i + cls_name = ofp_oxm_constr[index/4][0] + # we create standard OXM then OXM ID then OXM with mask then OXM-hasmask ID + if index % 4 == 2: + cls_name += "HM" + if index % 2: + cls_name += "ID" + + oxm_name = ofp_oxm_names[index/4] + oxm_fields = ofp_oxm_fields[index/2] + # for ID classes we just want the first 4 fields (no payload) + if index % 2: + oxm_fields = oxm_fields[:4] + + cls = type(cls_name, (Packet,), { "name": oxm_name, "fields_desc": oxm_fields }) + ### the first call to special function type will create the same class as in + ### class OFBInPort(Packet): + ### def __init__(self): + ### self.name = "OFB_IN_PORT" + ### self.fields_desc = [ ShortEnumField("class", 0x8000, ofp_oxm_classes), + ### BitEnumField("field", 0, 7, ofp_oxm_names), + ### BitField("hasmask", 0, 1), + ### ByteField("length", 4), + ### IntEnumField("in_port", 0, ofp_port_no) ] + + if index % 2 == 0: + ofp_oxm_cls[index/2] = cls + else: + ofp_oxm_id_cls[index/2] = cls + create_oxm_cls.i += 1 + return cls + +OFBInPort = create_oxm_cls() +OFBInPortID = create_oxm_cls() +OFBInPortHM = create_oxm_cls() +OFBInPortHMID = create_oxm_cls() +OFBInPhyPort = create_oxm_cls() +OFBInPhyPortID = create_oxm_cls() +OFBInPhyPortHM = create_oxm_cls() +OFBInPhyPortHMID = create_oxm_cls() +OFBMetadata = create_oxm_cls() +OFBMetadataID = create_oxm_cls() +OFBMetadataHM = create_oxm_cls() +OFBMetadataHMID = create_oxm_cls() +OFBEthDst = create_oxm_cls() +OFBEthDstID = create_oxm_cls() +OFBEthDstHM = create_oxm_cls() +OFBEthDstHMID = create_oxm_cls() +OFBEthSrc = create_oxm_cls() +OFBEthSrcID = create_oxm_cls() +OFBEthSrcHM = create_oxm_cls() +OFBEthSrcHMID = create_oxm_cls() +OFBEthType = create_oxm_cls() +OFBEthTypeID = create_oxm_cls() +OFBEthTypeHM = create_oxm_cls() +OFBEthTypeHMID = create_oxm_cls() +OFBVLANVID = create_oxm_cls() +OFBVLANVIDID = create_oxm_cls() +OFBVLANVIDHM = create_oxm_cls() +OFBVLANVIDHMID = create_oxm_cls() +OFBVLANPCP = create_oxm_cls() +OFBVLANPCPID = create_oxm_cls() +OFBVLANPCPHM = create_oxm_cls() +OFBVLANPCPHMID = create_oxm_cls() +OFBIPDSCP = create_oxm_cls() +OFBIPDSCPID = create_oxm_cls() +OFBIPDSCPHM = create_oxm_cls() +OFBIPDSCPHMID = create_oxm_cls() +OFBIPECN = create_oxm_cls() +OFBIPECNID = create_oxm_cls() +OFBIPECNHM = create_oxm_cls() +OFBIPECNHMID = create_oxm_cls() +OFBIPProto = create_oxm_cls() +OFBIPProtoID = create_oxm_cls() +OFBIPProtoHM = create_oxm_cls() +OFBIPProtoHMID = create_oxm_cls() +OFBIPv4Src = create_oxm_cls() +OFBIPv4SrcID = create_oxm_cls() +OFBIPv4SrcHM = create_oxm_cls() +OFBIPv4SrcHMID = create_oxm_cls() +OFBIPv4Dst = create_oxm_cls() +OFBIPv4DstID = create_oxm_cls() +OFBIPv4DstHM = create_oxm_cls() +OFBIPv4DstHMID = create_oxm_cls() +OFBTCPSrc = create_oxm_cls() +OFBTCPSrcID = create_oxm_cls() +OFBTCPSrcHM = create_oxm_cls() +OFBTCPSrcHMID = create_oxm_cls() +OFBTCPDst = create_oxm_cls() +OFBTCPDstID = create_oxm_cls() +OFBTCPDstHM = create_oxm_cls() +OFBTCPDstHMID = create_oxm_cls() +OFBUDPSrc = create_oxm_cls() +OFBUDPSrcID = create_oxm_cls() +OFBUDPSrcHM = create_oxm_cls() +OFBUDPSrcHMID = create_oxm_cls() +OFBUDPDst = create_oxm_cls() +OFBUDPDstID = create_oxm_cls() +OFBUDPDstHM = create_oxm_cls() +OFBUDPDstHMID = create_oxm_cls() +OFBSCTPSrc = create_oxm_cls() +OFBSCTPSrcID = create_oxm_cls() +OFBSCTPSrcHM = create_oxm_cls() +OFBSCTPSrcHMID = create_oxm_cls() +OFBSCTPDst = create_oxm_cls() +OFBSCTPDstID = create_oxm_cls() +OFBSCTPDstHM = create_oxm_cls() +OFBSCTPDstHMID = create_oxm_cls() +OFBICMPv4Type = create_oxm_cls() +OFBICMPv4TypeID = create_oxm_cls() +OFBICMPv4TypeHM = create_oxm_cls() +OFBICMPv4TypeHMID = create_oxm_cls() +OFBICMPv4Code = create_oxm_cls() +OFBICMPv4CodeID = create_oxm_cls() +OFBICMPv4CodeHM = create_oxm_cls() +OFBICMPv4CodeHMID = create_oxm_cls() +OFBARPOP = create_oxm_cls() +OFBARPOPID = create_oxm_cls() +OFBARPOPHM = create_oxm_cls() +OFBARPOPHMID = create_oxm_cls() +OFBARPSPA = create_oxm_cls() +OFBARPSPAID = create_oxm_cls() +OFBARPSPAHM = create_oxm_cls() +OFBARPSPAHMID = create_oxm_cls() +OFBARPTPA = create_oxm_cls() +OFBARPTPAID = create_oxm_cls() +OFBARPTPAHM = create_oxm_cls() +OFBARPTPAHMID = create_oxm_cls() +OFBARPSHA = create_oxm_cls() +OFBARPSHAID = create_oxm_cls() +OFBARPSHAHM = create_oxm_cls() +OFBARPSHAHMID = create_oxm_cls() +OFBARPTHA = create_oxm_cls() +OFBARPTHAID = create_oxm_cls() +OFBARPTHAHM = create_oxm_cls() +OFBARPTHAHMID = create_oxm_cls() +OFBIPv6Src = create_oxm_cls() +OFBIPv6SrcID = create_oxm_cls() +OFBIPv6SrcHM = create_oxm_cls() +OFBIPv6SrcHMID = create_oxm_cls() +OFBIPv6Dst = create_oxm_cls() +OFBIPv6DstID = create_oxm_cls() +OFBIPv6DstHM = create_oxm_cls() +OFBIPv6DstHMID = create_oxm_cls() +OFBIPv6FLabel = create_oxm_cls() +OFBIPv6FLabelID = create_oxm_cls() +OFBIPv6FLabelHM = create_oxm_cls() +OFBIPv6FLabelHMID = create_oxm_cls() +OFBICMPv6Type = create_oxm_cls() +OFBICMPv6TypeID = create_oxm_cls() +OFBICMPv6TypeHM = create_oxm_cls() +OFBICMPv6TypeHMID = create_oxm_cls() +OFBICMPv6Code = create_oxm_cls() +OFBICMPv6CodeID = create_oxm_cls() +OFBICMPv6CodeHM = create_oxm_cls() +OFBICMPv6CodeHMID = create_oxm_cls() +OFBIPv6NDTarget = create_oxm_cls() +OFBIPv6NDTargetID = create_oxm_cls() +OFBIPv6NDTargetHM = create_oxm_cls() +OFBIPv6NDTargetHMID = create_oxm_cls() +OFBIPv6NDSLL = create_oxm_cls() +OFBIPv6NDSLLID = create_oxm_cls() +OFBIPv6NDSLLHM = create_oxm_cls() +OFBIPv6NDSLLHMID = create_oxm_cls() +OFBIPv6NDTLL = create_oxm_cls() +OFBIPv6NDTLLID = create_oxm_cls() +OFBIPv6NDTLLHM = create_oxm_cls() +OFBIPv6NDTLLHMID = create_oxm_cls() +OFBMPLSLabel = create_oxm_cls() +OFBMPLSLabelID = create_oxm_cls() +OFBMPLSLabelHM = create_oxm_cls() +OFBMPLSLabelHMID = create_oxm_cls() +OFBMPLSTC = create_oxm_cls() +OFBMPLSTCID = create_oxm_cls() +OFBMPLSTCHM = create_oxm_cls() +OFBMPLSTCHMID = create_oxm_cls() +OFBMPLSBoS = create_oxm_cls() +OFBMPLSBoSID = create_oxm_cls() +OFBMPLSBoSHM = create_oxm_cls() +OFBMPLSBoSHMID = create_oxm_cls() +OFBPBBISID = create_oxm_cls() +OFBPBBISIDID = create_oxm_cls() +OFBPBBISIDHM = create_oxm_cls() +OFBPBBISIDHMID = create_oxm_cls() +OFBTunnelID = create_oxm_cls() +OFBTunnelIDID = create_oxm_cls() +OFBTunnelIDHM = create_oxm_cls() +OFBTunnelIDHMID = create_oxm_cls() +OFBIPv6ExtHdr = create_oxm_cls() +OFBIPv6ExtHdrID = create_oxm_cls() +OFBIPv6ExtHdrHM = create_oxm_cls() +OFBIPv6ExtHdrHMID = create_oxm_cls() + +### need_prereq holds a list of prerequisites defined in 7.2.3.8 of the specifications +### e.g. if you want to use an OFBTCPSrc instance (code 26) +### you first need to declare an OFBIPProto instance (code 20) with value 6, +### and if you want to use an OFBIPProto instance (still code 20) +### you first need to declare an OFBEthType instance (code 10) with value 0x0800 +### (0x0800 means IPv4 by default, but you might want to use 0x86dd with IPv6) +### need_prereq codes are two times higher than previous oxm classes codes, +### except for 21 which is sort of a proxy for IPv6 (see below) +need_prereq = { 14: [12, 0x1000], + 16: [10, 0x0800], # could be 0x86dd + 18: [10, 0x0800], # could be 0x86dd + 20: [10, 0x0800], # could be 0x86dd + 21: [10, 0x86dd], + 22: [10, 0x0800], + 24: [10, 0x0800], + 26: [20, 6], + 28: [20, 6], + 30: [20, 17], + 32: [20, 17], + 34: [20, 132], + 36: [20, 132], + 38: [20, 1], + 40: [20, 1], + 42: [10, 0x0806], + 44: [10, 0x0806], + 46: [10, 0x0806], + 48: [10, 0x0806], + 50: [10, 0x0806], + 52: [10, 0x86dd], + 54: [10, 0x86dd], + 56: [10, 0x86dd], + 58: [21, 58], ### small trick here, we refer to normally non- + 60: [21, 58], ### existent field 21 to distinguish ipv6 + 62: [58, 135], # could be 136 + 64: [58, 135], + 66: [58, 136], + 68: [10, 0x8847], # could be 0x8848 + 70: [10, 0x8847], # could be 0x8848 + 72: [10, 0x8847], # could be 0x8848 + 74: [10, 0x88e7], + 78: [10, 0x86dd] } + +class OXMPacketListField(PacketListField): + + def __init__(self, name, default, cls, length_from=None, autocomplete=prereq_autocomplete): + PacketListField.__init__(self, name, default, cls, length_from=length_from) + self.autocomplete = autocomplete + self.index = [] + + def i2m(self, pkt, val): + ### this part makes for a faster writing of specs-compliant matches + ### expect some unwanted behaviour if you try incoherent associations + ### you might want to set autocomplete=False in __init__ method + if self.autocomplete: + # val might be modified during the loop so we need a fixed copy + fix_val = copy.deepcopy(val) + for oxm in fix_val: + f = 2*oxm.field + fix_index = list(self.index) + while f in need_prereq.keys(): + # this loop enables a small recursion + # e.g. ipv6_nd<--icmpv6<--ip_proto<--eth_type + prereq = need_prereq[f] + f = prereq[0] + f2 = 20 if f == 21 else f # ipv6 trick... + if f2 not in fix_index: + self.index.insert(0, f2) + prrq = ofp_oxm_cls[f2]() # never HM + setattr(prrq, ofp_oxm_constr[f2/2][1], prereq[1]) + val.insert(0, prrq) + # we could do more complicated stuff to + # make sure prerequisite order is correct + # but it works well when presented with any coherent input + # e.g. you should not mix OFBTCPSrc with OFBICMPv6Code + # and expect to get coherent results... + # you can still go manual by setting prereq_autocomplete=False + return val + + def m2i(self, pkt, s): + t = struct.unpack("!B", s[2])[0] + nrm_t = t - t%2 + if nrm_t not in self.index: + self.index.append(nrm_t) + return ofp_oxm_cls.get(t, Raw)(s) + + @staticmethod + def _get_oxm_length(s): + return struct.unpack("!B", s[3])[0] + + def addfield(self, pkt, s, val): + return s + "".join(map(str,self.i2m(pkt, val))) + + def getfield(self, pkt, s): + lst = [] + lim = self.length_from(pkt) + ret = s[lim:] + remain = s[:lim] + + while remain and len(remain) > 4: + l = OXMPacketListField._get_oxm_length(remain) + 4 + # this could also be done by parsing oxm_fields (fixed lengths) + if l <= 4 or len(remain) < l: + # no incoherent length + break + current = remain[:l] + remain = remain[l:] + p = self.m2i(pkt, current) + lst.append(p) + + self.index = [] + ### since OXMPacketListField is called only twice (when OFPMatch and OFPSetField + ### classes are created) and not when you want to instantiate an OFPMatch, + ### index needs to be reinitialized, otherwise there will be some conflicts + ### e.g. if you create OFPMatch with OFBTCPSrc and then change to OFBTCPDst, + ### index will already be filled with ethertype and nwproto codes, + ### thus the corresponding fields will not be added to the packet + return remain + ret, lst + +class OXMIDPacketListField(PacketListField): + def m2i(self, pkt, s): + t = struct.unpack("!B", s[2])[0] + return ofp_oxm_id_cls.get(t, Raw)(s) + + def getfield(self, pkt, s): + lst = [] + lim = self.length_from(pkt) + ret = s[lim:] + remain = s[:lim] + + while remain and len(remain) >= 4: + # all OXM ID are 32-bit long (no experimenter OXM support here) + current = remain[:4] + remain = remain[4:] + p = self.m2i(pkt, current) + lst.append(p) + + return remain + ret, lst + + +class OFPMatch(Packet): + def post_build(self, p, pay): + l = self.length + if l is None: + l = len(p)+len(pay) + p = p[:2] + struct.pack("!H", l) + p[4:] + zero_bytes = (8 - l%8) % 8 + p += "\x00" * zero_bytes + # message with user-defined length will not be automatically padded + return p + pay + + def extract_padding(self, s): + l = self.length + zero_bytes = (8 - l%8) % 8 + return s[zero_bytes:], s[:zero_bytes] + + name = "OFP_MATCH" + fields_desc= [ ShortEnumField("type", 1, { 0: "OFPMT_STANDARD", + 1: "OFPMT_OXM" }), + ShortField("length", None), + OXMPacketListField("oxm_fields", [], Packet, + length_from=lambda pkt:pkt.length-4) ] + +### ofp_match is no longer a fixed-length structure in v1.3 +### furthermore it may include variable padding +### we introduce to that end a subclass of PacketField +class MatchField(PacketField): + def __init__(self, name): + PacketField.__init__(self, name, OFPMatch(), OFPMatch) + + def getfield(self, pkt, s): + i = self.m2i(pkt, s) + ### i can be <OFPMatch> or <OFPMatch <Padding>> + ### or <OFPMatch <Raw>> or <OFPMatch <Raw <Padding>>> + ### and we want to return "", <OFPMatch> or "", <OFPMatch <Padding>> + ### or str(<Raw>), <OFPMatch> or str(<Raw>), <OFPMatch <Padding>> + if Raw in i: + r = i[Raw] + if Padding in r: + p = r[Padding] + i.payload = p + del(r.payload) + return r.load, i + else: + return "", i + + +###################### Actions ###################### + +class _ofp_action_header(Packet): + name = "Dummy OpenFlow Action Header" + + def post_build(self, p, pay): + if self.len is None: + l = len(p)+len(pay) + p = p[:2] + struct.pack("!H", l) + p[4:] + return p + pay + +ofp_action_types = { 0: "OFPAT_OUTPUT", + 1: "OFPAT_SET_VLAN_VID", + 2: "OFPAT_SET_VLAN_PCP", + 3: "OFPAT_STRIP_VLAN", + 4: "OFPAT_SET_DL_SRC", + 5: "OFPAT_SET_DL_DST", + 6: "OFPAT_SET_NW_SRC", + 7: "OFPAT_SET_NW_DST", + 8: "OFPAT_SET_NW_TOS", + 9: "OFPAT_SET_TP_SRC", + 10: "OFPAT_SET_TP_DST", + #11: "OFPAT_ENQUEUE", + 11: "OFPAT_COPY_TTL_OUT", + 12: "OFPAT_COPY_TTL_IN", + 13: "OFPAT_SET_MPLS_LABEL", + 14: "OFPAT_DEC_MPLS_TC", + 15: "OFPAT_SET_MPLS_TTL", + 16: "OFPAT_DEC_MPLS_TTL", + 17: "OFPAT_PUSH_VLAN", + 18: "OFPAT_POP_VLAN", + 19: "OFPAT_PUSH_MPLS", + 20: "OFPAT_POP_MPLS", + 21: "OFPAT_SET_QUEUE", + 22: "OFPAT_GROUP", + 23: "OFPAT_SET_NW_TTL", + 24: "OFPAT_DEC_NW_TTL", + 25: "OFPAT_SET_FIELD", + 26: "OFPAT_PUSH_PBB", + 27: "OFPAT_POP_PBB", + 65535: "OFPAT_EXPERIMENTER" } + +class OFPATOutput(_ofp_action_header): + name = "OFPAT_OUTPUT" + fields_desc = [ ShortEnumField("type", 0, ofp_action_types), + ShortField("len", 16), + IntEnumField("port", 0, ofp_port_no), + ShortEnumField("max_len", "NO_BUFFER", ofp_max_len), + XBitField("pad", 0, 48) ] + +# the following actions are not supported by OFv1.3 + +class OFPATSetVLANVID(_ofp_action_header): + name = "OFPAT_SET_VLAN_VID" + fields_desc = [ ShortEnumField("type", 1, ofp_action_types), + ShortField("len", 8), + ShortField("vlan_vid", 0), + XShortField("pad", 0) ] + +class OFPATSetVLANPCP(_ofp_action_header): + name = "OFPAT_SET_VLAN_PCP" + fields_desc = [ ShortEnumField("type", 2, ofp_action_types), + ShortField("len", 8), + ByteField("vlan_pcp", 0), + X3BytesField("pad", 0) ] + +class OFPATStripVLAN(_ofp_action_header): + name = "OFPAT_STRIP_VLAN" + fields_desc = [ ShortEnumField("type", 3, ofp_action_types), + ShortField("len", 8), + XIntField("pad", 0) ] + +class OFPATSetDlSrc(_ofp_action_header): + name = "OFPAT_SET_DL_SRC" + fields_desc = [ ShortEnumField("type", 4, ofp_action_types), + ShortField("len", 16), + MACField("dl_addr", "0"), + XBitField("pad", 0, 48) ] + +class OFPATSetDlDst(_ofp_action_header): + name = "OFPAT_SET_DL_DST" + fields_desc = [ ShortEnumField("type", 5, ofp_action_types), + ShortField("len", 16), + MACField("dl_addr", "0"), + XBitField("pad", 0, 48) ] + +class OFPATSetNwSrc(_ofp_action_header): + name = "OFPAT_SET_NW_SRC" + fields_desc = [ ShortEnumField("type", 6, ofp_action_types), + ShortField("len", 8), + IPField("nw_addr", "0") ] + +class OFPATSetNwDst(_ofp_action_header): + name = "OFPAT_SET_NW_DST" + fields_desc = [ ShortEnumField("type", 7, ofp_action_types), + ShortField("len", 8), + IPField("nw_addr", "0") ] + +class OFPATSetNwToS(_ofp_action_header): + name = "OFPAT_SET_TP_TOS" + fields_desc = [ ShortEnumField("type", 8, ofp_action_types), + ShortField("len", 8), + ByteField("nw_tos", 0), + X3BytesField("pad", 0) ] + +class OFPATSetTpSrc(_ofp_action_header): + name = "OFPAT_SET_TP_SRC" + fields_desc = [ ShortEnumField("type", 9, ofp_action_types), + ShortField("len", 8), + ShortField("tp_port", 0), + XShortField("pad", 0) ] + +class OFPATSetTpDst(_ofp_action_header): + name = "OFPAT_SET_TP_DST" + fields_desc = [ ShortEnumField("type", 10, ofp_action_types), + ShortField("len", 8), + ShortField("tp_port", 0), + XShortField("pad", 0) ] + +#class OFPATEnqueue(_ofp_action_header): +# name = "OFPAT_ENQUEUE" +# fields_desc = [ ShortEnumField("type", 11, ofp_action_types), +# ShortField("len", 16), +# ShortField("port", 0), +# XBitField("pad", 0, 48), +# IntEnumField("queue_id", 0, ofp_queue) ] + +class OFPATSetMPLSLabel(_ofp_action_header): + name = "OFPAT_SET_MPLS_LABEL" + fields_desc = [ ShortEnumField("type", 13, ofp_action_types), + ShortField("len", 8), + IntField("mpls_label", 0) ] + +class OFPATSetMPLSTC(_ofp_action_header): + name = "OFPAT_SET_MPLS_TC" + fields_desc = [ ShortEnumField("type", 14, ofp_action_types), + ShortField("len", 8), + ByteField("mpls_tc", 0), + X3BytesField("pad", 0) ] + +# end of unsupported actions + +class OFPATCopyTTLOut(_ofp_action_header): + name = "OFPAT_COPY_TTL_OUT" + fields_desc = [ ShortEnumField("type", 11, ofp_action_types), + ShortField("len", 8), + XIntField("pad", 0) ] + +class OFPATCopyTTLIn(_ofp_action_header): + name = "OFPAT_COPY_TTL_IN" + fields_desc = [ ShortEnumField("type", 12, ofp_action_types), + ShortField("len", 8), + XIntField("pad", 0) ] + +class OFPATSetMPLSTTL(_ofp_action_header): + name = "OFPAT_SET_MPLS_TTL" + fields_desc = [ ShortEnumField("type", 15, ofp_action_types), + ShortField("len", 8), + ByteField("mpls_ttl", 0), + X3BytesField("pad", 0) ] + +class OFPATDecMPLSTTL(_ofp_action_header): + name = "OFPAT_DEC_MPLS_TTL" + fields_desc = [ ShortEnumField("type", 16, ofp_action_types), + ShortField("len", 8), + XIntField("pad", 0) ] + +class OFPATPushVLAN(_ofp_action_header): + name = "OFPAT_PUSH_VLAN" + fields_desc = [ ShortEnumField("type", 17, ofp_action_types), + ShortField("len", 8), + ShortField("ethertype", 0x8100), # or 0x88a8 + XShortField("pad", 0) ] + +class OFPATPopVLAN(_ofp_action_header): + name = "OFPAT_POP_VLAN" + fields_desc = [ ShortEnumField("type", 18, ofp_action_types), + ShortField("len", 8), + XIntField("pad", 0) ] + +class OFPATPushMPLS(_ofp_action_header): + name = "OFPAT_PUSH_MPLS" + fields_desc = [ ShortEnumField("type", 19, ofp_action_types), + ShortField("len", 8), + ShortField("ethertype", 0x8847), # or 0x8848 + XShortField("pad", 0) ] + +class OFPATPopMPLS(_ofp_action_header): + name = "OFPAT_POP_MPLS" + fields_desc = [ ShortEnumField("type", 20, ofp_action_types), + ShortField("len", 8), + ShortField("ethertype", 0x8847), # or 0x8848 + XShortField("pad", 0) ] + +class OFPATSetQueue(_ofp_action_header): + name = "OFPAT_SET_QUEUE" + fields_desc = [ ShortEnumField("type", 21, ofp_action_types), + ShortField("len", 8), + IntEnumField("queue_id", 0, ofp_queue) ] + +class OFPATGroup(_ofp_action_header): + name = "OFPAT_GROUP" + fields_desc = [ ShortEnumField("type", 22, ofp_action_types), + ShortField("len", 8), + IntEnumField("group_id", 0, ofp_group) ] + +class OFPATSetNwTTL(_ofp_action_header): + name = "OFPAT_SET_NW_TTL" + fields_desc = [ ShortEnumField("type", 23, ofp_action_types), + ShortField("len", 8), + ByteField("nw_ttl", 0), + X3BytesField("pad", 0) ] + +class OFPATDecNwTTL(_ofp_action_header): + name = "OFPAT_DEC_NW_TTL" + fields_desc = [ ShortEnumField("type", 24, ofp_action_types), + ShortField("len", 8), + XIntField("pad", 0) ] + +class OFPATSetField(_ofp_action_header): + + def post_build(self, p, pay): + l = self.len + zero_bytes = 0 + if l is None: + l = len(p)+len(pay) + zero_bytes = (8 - l%8) % 8 + l = l + zero_bytes # add padding length + p = p[:2] + struct.pack("!H", l) + p[4:] + else: + zero_bytes = (8 - l%8) % 8 + # every message will be padded correctly + p += "\x00" * zero_bytes + return p + pay + + def extract_padding(self, s): + return "", s + + name = "OFPAT_SET_FIELD" + fields_desc = [ ShortEnumField("type", 25, ofp_action_types), + ShortField("len", None), + # there should not be more than one oxm tlv + OXMPacketListField("field", [], Packet, + length_from=lambda pkt:pkt.len-4, + # /!\ contains padding! + autocomplete=False) ] + +class OFPATPushPBB(_ofp_action_header): + name = "OFPAT_PUSH_PBB" + fields_desc = [ ShortEnumField("type", 26, ofp_action_types), + ShortField("len", 8), + ShortField("ethertype", 0x88e7), + XShortField("pad", 0) ] + +class OFPATPopPBB(_ofp_action_header): + name = "OFPAT_POP_PBB" + fields_desc = [ ShortEnumField("type", 27, ofp_action_types), + ShortField("len", 8), + XIntField("pad", 0) ] + +class OFPATExperimenter(_ofp_action_header): + name = "OFPAT_EXPERIMENTER" + fields_desc = [ ShortEnumField("type", 65535, ofp_action_types), + ShortField("len", 8), + IntField("experimenter", 0) ] + +ofp_action_cls = { 0: OFPATOutput, + 1: OFPATSetVLANVID, + 2: OFPATSetVLANPCP, + 3: OFPATStripVLAN, + 4: OFPATSetDlSrc, + 5: OFPATSetDlDst, + 6: OFPATSetNwSrc, + 7: OFPATSetNwDst, + 8: OFPATSetNwToS, + 9: OFPATSetTpSrc, + 10: OFPATSetTpDst, + #11: OFPATEnqueue, + 11: OFPATCopyTTLOut, + 12: OFPATCopyTTLIn, + 13: OFPATSetMPLSLabel, + 14: OFPATSetMPLSTC, + 15: OFPATSetMPLSTTL, + 16: OFPATDecMPLSTTL, + 17: OFPATPushVLAN, + 18: OFPATPopVLAN, + 19: OFPATPushMPLS, + 20: OFPATPopMPLS, + 21: OFPATSetQueue, + 22: OFPATGroup, + 23: OFPATSetNwTTL, + 24: OFPATDecNwTTL, + 25: OFPATSetField, + 26: OFPATPushPBB, + 27: OFPATPopPBB, + 65535: OFPATExperimenter } + +class ActionPacketListField(PacketListField): + def m2i(self, pkt, s): + t = struct.unpack("!H", s[:2])[0] + return ofp_action_cls.get(t, Raw)(s) + + @staticmethod + def _get_action_length(s): + return struct.unpack("!H", s[2:4])[0] + + def getfield(self, pkt, s): + lst = [] + remain = s + + while remain and len(remain)>=4: + l = ActionPacketListField._get_action_length(remain) + if l < 8 or len(remain) < l: + # length should be at least 8 (non-zero, 64-bit aligned), + # and no incoherent length + break + current = remain[:l] + remain = remain[l:] + p = self.m2i(pkt, current) + lst.append(p) + + return remain, lst + + +##################### Action IDs #################### + +# length is computed as in instruction structures, +# so we reuse _ofp_instruction_header + +class OFPATOutputID(_ofp_action_header): + name = "OFPAT_OUTPUT" + fields_desc = [ ShortEnumField("type", 0, ofp_action_types), + ShortField("len", 4) ] + +# the following actions are not supported by OFv1.3 + +class OFPATSetVLANVIDID(_ofp_action_header): + name = "OFPAT_SET_VLAN_VID" + fields_desc = [ ShortEnumField("type", 1, ofp_action_types), + ShortField("len", 4) ] + +class OFPATSetVLANPCPID(_ofp_action_header): + name = "OFPAT_SET_VLAN_PCP" + fields_desc = [ ShortEnumField("type", 2, ofp_action_types), + ShortField("len", 4) ] + +class OFPATStripVLANID(_ofp_action_header): + name = "OFPAT_STRIP_VLAN" + fields_desc = [ ShortEnumField("type", 3, ofp_action_types), + ShortField("len", 4) ] + +class OFPATSetDlSrcID(_ofp_action_header): + name = "OFPAT_SET_DL_SRC" + fields_desc = [ ShortEnumField("type", 4, ofp_action_types), + ShortField("len", 4) ] + +class OFPATSetDlDstID(_ofp_action_header): + name = "OFPAT_SET_DL_DST" + fields_desc = [ ShortEnumField("type", 5, ofp_action_types), + ShortField("len", 4) ] + +class OFPATSetNwSrcID(_ofp_action_header): + name = "OFPAT_SET_NW_SRC" + fields_desc = [ ShortEnumField("type", 6, ofp_action_types), + ShortField("len", 4) ] + +class OFPATSetNwDstID(_ofp_action_header): + name = "OFPAT_SET_NW_DST" + fields_desc = [ ShortEnumField("type", 7, ofp_action_types), + ShortField("len", 4) ] + +class OFPATSetNwToSID(_ofp_action_header): + name = "OFPAT_SET_TP_TOS" + fields_desc = [ ShortEnumField("type", 8, ofp_action_types), + ShortField("len", 4) ] + +class OFPATSetTpSrcID(_ofp_action_header): + name = "OFPAT_SET_TP_SRC" + fields_desc = [ ShortEnumField("type", 9, ofp_action_types), + ShortField("len", 4) ] + +class OFPATSetTpDstID(_ofp_action_header): + name = "OFPAT_SET_TP_DST" + fields_desc = [ ShortEnumField("type", 10, ofp_action_types), + ShortField("len", 4) ] + +#class OFPATEnqueueID(_ofp_action_header): +# name = "OFPAT_ENQUEUE" +# fields_desc = [ ShortEnumField("type", 11, ofp_action_types), +# ShortField("len", 4) ] + +class OFPATSetMPLSLabelID(_ofp_action_header): + name = "OFPAT_SET_MPLS_LABEL" + fields_desc = [ ShortEnumField("type", 13, ofp_action_types), + ShortField("len", 4) ] + +class OFPATSetMPLSTCID(_ofp_action_header): + name = "OFPAT_SET_MPLS_TC" + fields_desc = [ ShortEnumField("type", 14, ofp_action_types), + ShortField("len", 4) ] + +# end of unsupported actions + +class OFPATCopyTTLOutID(_ofp_action_header): + name = "OFPAT_COPY_TTL_OUT" + fields_desc = [ ShortEnumField("type", 11, ofp_action_types), + ShortField("len", 4) ] + +class OFPATCopyTTLInID(_ofp_action_header): + name = "OFPAT_COPY_TTL_IN" + fields_desc = [ ShortEnumField("type", 12, ofp_action_types), + ShortField("len", 4) ] + +class OFPATSetMPLSTTLID(_ofp_action_header): + name = "OFPAT_SET_MPLS_TTL" + fields_desc = [ ShortEnumField("type", 15, ofp_action_types), + ShortField("len", 4) ] + +class OFPATDecMPLSTTLID(_ofp_action_header): + name = "OFPAT_DEC_MPLS_TTL" + fields_desc = [ ShortEnumField("type", 16, ofp_action_types), + ShortField("len", 4) ] + +class OFPATPushVLANID(_ofp_action_header): + name = "OFPAT_PUSH_VLAN" + fields_desc = [ ShortEnumField("type", 17, ofp_action_types), + ShortField("len", 4) ] + +class OFPATPopVLANID(_ofp_action_header): + name = "OFPAT_POP_VLAN" + fields_desc = [ ShortEnumField("type", 18, ofp_action_types), + ShortField("len", 4) ] + +class OFPATPushMPLSID(_ofp_action_header): + name = "OFPAT_PUSH_MPLS" + fields_desc = [ ShortEnumField("type", 19, ofp_action_types), + ShortField("len", 4) ] + +class OFPATPopMPLSID(_ofp_action_header): + name = "OFPAT_POP_MPLS" + fields_desc = [ ShortEnumField("type", 20, ofp_action_types), + ShortField("len", 4) ] + +class OFPATSetQueueID(_ofp_action_header): + name = "OFPAT_SET_QUEUE" + fields_desc = [ ShortEnumField("type", 21, ofp_action_types), + ShortField("len", 4) ] + +class OFPATGroupID(_ofp_action_header): + name = "OFPAT_GROUP" + fields_desc = [ ShortEnumField("type", 22, ofp_action_types), + ShortField("len", 4) ] + +class OFPATSetNwTTLID(_ofp_action_header): + name = "OFPAT_SET_NW_TTL" + fields_desc = [ ShortEnumField("type", 23, ofp_action_types), + ShortField("len", 4) ] + +class OFPATDecNwTTLID(_ofp_action_header): + name = "OFPAT_DEC_NW_TTL" + fields_desc = [ ShortEnumField("type", 24, ofp_action_types), + ShortField("len", 4) ] + +class OFPATSetFieldID(_ofp_action_header): + name = "OFPAT_SET_FIELD" + fields_desc = [ ShortEnumField("type", 25, ofp_action_types), + ShortField("len", 4) ] + +class OFPATPushPBBID(_ofp_action_header): + name = "OFPAT_PUSH_PBB" + fields_desc = [ ShortEnumField("type", 26, ofp_action_types), + ShortField("len", 4) ] + +class OFPATPopPBBID(_ofp_action_header): + name = "OFPAT_POP_PBB" + fields_desc = [ ShortEnumField("type", 27, ofp_action_types), + ShortField("len", 4) ] + +class OFPATExperimenterID(_ofp_action_header): + name = "OFPAT_EXPERIMENTER" + fields_desc = [ ShortEnumField("type", 65535, ofp_action_types), + ShortField("len", None) ] + +ofp_action_id_cls = { 0: OFPATOutputID, + 1: OFPATSetVLANVIDID, + 2: OFPATSetVLANPCPID, + 3: OFPATStripVLANID, + 4: OFPATSetDlSrcID, + 5: OFPATSetDlDstID, + 6: OFPATSetNwSrcID, + 7: OFPATSetNwDstID, + 8: OFPATSetNwToSID, + 9: OFPATSetTpSrcID, + 10: OFPATSetTpDstID, + #11: OFPATEnqueueID, + 11: OFPATCopyTTLOutID, + 12: OFPATCopyTTLInID, + 13: OFPATSetMPLSLabelID, + 14: OFPATSetMPLSTCID, + 15: OFPATSetMPLSTTLID, + 16: OFPATDecMPLSTTLID, + 17: OFPATPushVLANID, + 18: OFPATPopVLANID, + 19: OFPATPushMPLSID, + 20: OFPATPopMPLSID, + 21: OFPATSetQueueID, + 22: OFPATGroupID, + 23: OFPATSetNwTTLID, + 24: OFPATDecNwTTLID, + 25: OFPATSetFieldID, + 26: OFPATPushPBBID, + 27: OFPATPopPBBID, + 65535: OFPATExperimenterID } + +class ActionIDPacketListField(PacketListField): + def m2i(self, pkt, s): + t = struct.unpack("!H", s[:2])[0] + return ofp_action_id_cls.get(t, Raw)(s) + + @staticmethod + def _get_action_id_length(s): + return struct.unpack("!H", s[2:4])[0] + + def getfield(self, pkt, s): + lst = [] + remain = s + + while remain and len(remain) >= 4: + l = ActionIDPacketListField._get_action_id_length(remain) + if l < 4 or len(remain) < l: + # length is 4 (may be more for experimenter messages), + # and no incoherent length + break + current = remain[:l] + remain = remain[l:] + p = self.m2i(pkt, current) + lst.append(p) + + return remain, lst + + +#################### Instructions ################### + +class _ofp_instruction_header(Packet): + name = "Dummy OpenFlow Instruction Header" + + def post_build(self, p, pay): + if self.len is None: + l = len(p)+len(pay) + p = p[:2] + struct.pack("!H", l) + p[4:] + return p + pay + +ofp_instruction_types = { 1: "OFPIT_GOTO_TABLE", + 2: "OFPIT_WRITE_METADATA", + 3: "OFPIT_WRITE_ACTIONS", + 4: "OFPIT_APPLY_ACTIONS", + 5: "OFPIT_CLEAR_ACTIONS", + 6: "OFPIT_METER", + 65535: "OFPIT_EXPERIMENTER" } + +class OFPITGotoTable(_ofp_instruction_header): + name = "OFPIT_GOTO_TABLE" + fields_desc = [ ShortEnumField("type", 1, ofp_instruction_types), + ShortField("len", 8), + ByteEnumField("table_id", 0, ofp_table), + X3BytesField("pad", 0) ] + +class OFPITWriteMetadata(_ofp_instruction_header): + name = "OFPIT_WRITE_METADATA" + fields_desc = [ ShortEnumField("type", 2, ofp_instruction_types), + ShortField("len", 24), + XIntField("pad", 0), + LongField("metadata", 0), + LongField("metadata_mask", 0) ] + +class OFPITWriteActions(_ofp_instruction_header): + name = "OFPIT_WRITE_ACTIONS" + fields_desc = [ ShortEnumField("type", 3, ofp_instruction_types), + ShortField("len", None), + XIntField("pad", 0), + ActionPacketListField("actions", [], Packet, + length_from=lambda pkt:pkt.len-8) ] + +class OFPITApplyActions(_ofp_instruction_header): + name = "OFPIT_APPLY_ACTIONS" + fields_desc = [ ShortEnumField("type", 4, ofp_instruction_types), + ShortField("len", None), + XIntField("pad", 0), + ActionPacketListField("actions", [], Packet, + length_from=lambda pkt:pkt.len-8) ] + +class OFPITClearActions(_ofp_instruction_header): + name = "OFPIT_CLEAR_ACTIONS" + fields_desc = [ ShortEnumField("type", 5, ofp_instruction_types), + ShortField("len", 8), + XIntField("pad", 0) ] + +class OFPITMeter(_ofp_instruction_header): + name = "OFPIT_METER" + fields_desc = [ ShortEnumField("type", 6, ofp_instruction_types), + ShortField("len", 8), + IntEnumField("meter_id", 1, ofp_meter) ] + +class OFPITExperimenter(_ofp_instruction_header): + name = "OFPIT_EXPERIMENTER" + fields_desc = [ ShortEnumField("type", 65535, ofp_instruction_types), + ShortField("len", None), + IntField("experimenter", 0) ] + +ofp_instruction_cls = { 1: OFPITGotoTable, + 2: OFPITWriteMetadata, + 3: OFPITWriteActions, + 4: OFPITApplyActions, + 5: OFPITClearActions, + 6: OFPITMeter, + 65535: OFPITExperimenter } + +class InstructionPacketListField(PacketListField): + def m2i(self, pkt, s): + t = struct.unpack("!H", s[:2])[0] + return ofp_instruction_cls.get(t, Raw)(s) + + @staticmethod + def _get_instruction_length(s): + return struct.unpack("!H", s[2:4])[0] + + def getfield(self, pkt, s): + lst = [] + remain = s + + while remain and len(remain) > 4: + l = InstructionPacketListField._get_instruction_length(remain) + if l < 8 or len(remain) < l: + # length should be at least 8 (non-zero, 64-bit aligned), + # and no incoherent length + break + current = remain[:l] + remain = remain[l:] + p = self.m2i(pkt, current) + lst.append(p) + + return remain, lst + + +################## Instruction IDs ################## + +# length is computed as in instruction structures, +# so we reuse _ofp_instruction_header + +class OFPITGotoTableID(_ofp_instruction_header): + name = "OFPIT_GOTO_TABLE" + fields_desc = [ ShortEnumField("type", 1, ofp_instruction_types), + ShortField("len", 4) ] + +class OFPITWriteMetadataID(_ofp_instruction_header): + name = "OFPIT_WRITE_METADATA" + fields_desc = [ ShortEnumField("type", 2, ofp_instruction_types), + ShortField("len", 4) ] + +class OFPITWriteActionsID(_ofp_instruction_header): + name = "OFPIT_WRITE_ACTIONS" + fields_desc = [ ShortEnumField("type", 3, ofp_instruction_types), + ShortField("len", 4) ] + +class OFPITApplyActionsID(_ofp_instruction_header): + name = "OFPIT_APPLY_ACTIONS" + fields_desc = [ ShortEnumField("type", 4, ofp_instruction_types), + ShortField("len", 4) ] + +class OFPITClearActionsID(_ofp_instruction_header): + name = "OFPIT_CLEAR_ACTIONS" + fields_desc = [ ShortEnumField("type", 5, ofp_instruction_types), + ShortField("len", 4) ] + +class OFPITMeterID(_ofp_instruction_header): + name = "OFPIT_METER" + fields_desc = [ ShortEnumField("type", 6, ofp_instruction_types), + ShortField("len", 4) ] + +class OFPITExperimenterID(_ofp_instruction_header): + name = "OFPIT_EXPERIMENTER" + fields_desc = [ ShortEnumField("type", 65535, ofp_instruction_types), + ShortField("len", None) ] + +ofp_instruction_id_cls = { 1: OFPITGotoTableID, + 2: OFPITWriteMetadataID, + 3: OFPITWriteActionsID, + 4: OFPITApplyActionsID, + 5: OFPITClearActionsID, + 6: OFPITMeterID, + 65535: OFPITExperimenterID } + +class InstructionIDPacketListField(PacketListField): + def m2i(self, pkt, s): + t = struct.unpack("!H", s[:2])[0] + return ofp_instruction_cls.get(t, Raw)(s) + + @staticmethod + def _get_instruction_id_length(s): + return struct.unpack("!H", s[2:4])[0] + + def getfield(self, pkt, s): + lst = [] + remain = s + + while remain and len(remain) >= 4: + l = InstructionIDPacketListField._get_instruction_id_length(remain) + if l < 4 or len(remain) < l: + # length is 4 (may be more for experimenter messages), + # and no incoherent length + break + current = remain[:l] + remain = remain[l:] + p = self.m2i(pkt, current) + lst.append(p) + + return remain, lst + + +###################### Buckets ###################### + +class OFPBucket(Packet): + + def extract_padding(self, s): + return "", s + + def post_build(self, p, pay): + if self.len is None: + l = len(p)+len(pay) + p = struct.pack("!H", l) + p[2:] + return p + pay + + name = "OFP_BUCKET" + fields_desc = [ ShortField("len", None), + ShortField("weight", 0), + IntEnumField("watch_port", 0, ofp_port_no), + IntEnumField("watch_group", 0, ofp_group), + XIntField("pad", 0), + ActionPacketListField("actions", [], Packet, + length_from=lambda pkt:pkt.len-16) ] + +class BucketPacketListField(PacketListField): + + @staticmethod + def _get_bucket_length(s): + return struct.unpack("!H", s[:2])[0] + + def getfield(self, pkt, s): + lst = [] + remain = s + + while remain: + l = BucketPacketListField._get_bucket_length(remain) + current = remain[:l] + remain = remain[l:] + p = OFPBucket(current) + lst.append(p) + + return remain, lst + + +####################### Queues ###################### + +class _ofp_queue_property_header(Packet): + name = "Dummy OpenFlow Queue Property Header" + + def post_build(self, p, pay): + if self.len is None: + l = len(p)+len(pay) + p = p[:2] + struct.pack("!H", l) + p[4:] + return p + pay + +ofp_queue_property_types = { 0: "OFPQT_NONE", + 1: "OFPQT_MIN_RATE" } + +class OFPQTNone(_ofp_queue_property_header): + name = "OFPQT_NONE" + fields_desc = [ ShortEnumField("type", 0, ofp_queue_property_types), + ShortField("len", 8), + XIntField("pad", 0) ] + +class OFPQTMinRate(_ofp_queue_property_header): + name = "OFPQT_MIN_RATE" + fields_desc = [ ShortEnumField("type", 1, ofp_queue_property_types), + ShortField("len", 16), + XIntField("pad1", 0), + ShortField("rate", 0), + XBitField("pad2", 0, 48) ] + +ofp_queue_property_cls = { 0: OFPQTNone, + 1: OFPQTMinRate } + +class QueuePropertyPacketListField(PacketListField): + def m2i(self, pkt, s): + t = struct.unpack("!H", s[:2])[0] + return ofp_queue_property_cls.get(t, Raw)(s) + + @staticmethod + def _get_queue_property_length(s): + return struct.unpack("!H", s[2:4])[0] + + def getfield(self, pkt, s): + lst = [] + remain = s + + while remain: + l = QueuePropertyPacketListField._get_queue_property_length(remain) + current = remain[:l] + remain = remain[l:] + p = self.m2i(pkt, current) + lst.append(p) + + return remain, lst + +class OFPPacketQueue(Packet): + + def extract_padding(self, s): + return "", s + + def post_build(self, p, pay): + if self.properties == []: + p += str(OFPQTNone()) + if self.len is None: + l = len(p)+len(pay) + p = p[:4] + struct.pack("!H", l) + p[6:] + return p + pay + + name = "OFP_PACKET_QUEUE" + fields_desc = [ IntEnumField("queue_id", 0, ofp_queue), + ShortField("len", None), + XShortField("pad", 0), + QueuePropertyPacketListField("properties", [], Packet, + length_from=lambda pkt:pkt.len-8) ] + +class QueuePacketListField(PacketListField): + + @staticmethod + def _get_queue_length(s): + return struct.unpack("!H", s[4:6])[0] + + def getfield(self, pkt, s): + lst = [] + remain = s + + while remain: + l = QueuePacketListField._get_queue_length(remain) + current = remain[:l] + remain = remain[l:] + p = OFPPacketQueue(current) + lst.append(p) + + return remain, lst + + +#################### Meter bands #################### + +ofp_meter_band_types = { 0: "OFPMBT_DROP", + 1: "OFPMBT_DSCP_REMARK", + 65535: "OFPMBT_EXPERIMENTER" } + +class OFPMBTDrop(Packet): + name = "OFPMBT_DROP" + fields_desc = [ ShortEnumField("type", 0, ofp_queue_property_types), + ShortField("len", 16), + IntField("rate", 0), + IntField("burst_size", 0), + XIntField("pad", 0) ] + +class OFPMBTDSCPRemark(Packet): + name = "OFPMBT_DSCP_REMARK" + fields_desc = [ ShortEnumField("type", 1, ofp_queue_property_types), + ShortField("len", 16), + IntField("rate", 0), + IntField("burst_size", 0), + ByteField("prec_level", 0), + X3BytesField("pad", 0) ] + +class OFPMBTExperimenter(Packet): + name = "OFPMBT_EXPERIMENTER" + fields_desc = [ ShortEnumField("type", 65535, ofp_queue_property_types), + ShortField("len", 16), + IntField("rate", 0), + IntField("burst_size", 0), + IntField("experimenter", 0) ] + +ofp_meter_band_cls = { 0: OFPMBTDrop, + 1: OFPMBTDSCPRemark, + 2: OFPMBTExperimenter } + +class MeterBandPacketListField(PacketListField): + def m2i(self, pkt, s): + t = struct.unpack("!H", s[:2])[0] + return ofp_meter_band_cls.get(t, Raw)(s) + + def getfield(self, pkt, s): + lst = [] + remain = s + + while remain: + current = remain[:16] + remain = remain[16:] + p = self.m2i(pkt, current) + lst.append(p) + + return remain, lst + + +##################################################### +############## OpenFlow 1.3 Messages ################ +##################################################### + +ofp_version = { 0x01: "OpenFlow 1.0", + 0x02: "OpenFlow 1.1", + 0x03: "OpenFlow 1.2", + 0x04: "OpenFlow 1.3", + 0x05: "OpenFlow 1.4" } + +ofp_type = { 0: "OFPT_HELLO", + 1: "OFPT_ERROR", + 2: "OFPT_ECHO_REQUEST", + 3: "OFPT_ECHO_REPLY", + 4: "OFPT_EXPERIMENTER", + 5: "OFPT_FEATURES_REQUEST", + 6: "OFPT_FEATURES_REPLY", + 7: "OFPT_GET_CONFIG_REQUEST", + 8: "OFPT_GET_CONFIG_REPLY", + 9: "OFPT_SET_CONFIG", + 10: "OFPT_PACKET_IN", + 11: "OFPT_FLOW_REMOVED", + 12: "OFPT_PORT_STATUS", + 13: "OFPT_PACKET_OUT", + 14: "OFPT_FLOW_MOD", + 15: "OFPT_GROUP_MOD", + 16: "OFPT_PORT_MOD", + 17: "OFPT_TABLE_MOD", + 18: "OFPT_MULTIPART_REQUEST", + 19: "OFPT_MULTIPART_REPLY", + 20: "OFPT_BARRIER_REQUEST", + 21: "OFPT_BARRIER_REPLY", + 22: "OFPT_QUEUE_GET_CONFIG_REQUEST", + 23: "OFPT_QUEUE_GET_CONFIG_REPLY", + 24: "OFPT_ROLE_REQUEST", + 25: "OFPT_ROLE_REPLY", + 26: "OFPT_GET_ASYNC_REQUEST", + 27: "OFPT_GET_ASYNC_REPLY", + 28: "OFPT_SET_ASYNC", + 29: "OFPT_METER_MOD" } + +class _ofp_header(Packet): + name = "Dummy OpenFlow Header" + + def post_build(self, p, pay): + if self.len is None: + l = len(p)+len(pay) + p = p[:2] + struct.pack("!H", l) + p[4:] + return p + pay + +class OFPTHello(_ofp_header): + name = "OFPT_HELLO" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 0, ofp_type), + ShortField("len", None), + IntField("xid", 0), + HelloElemPacketListField("elements", [], Packet, + length_from=lambda pkt:pkt.len-32) ] + overload_fields = {TCP: {"sport": 6653}} + +##################################################### +##################### OFPT_ERROR #################### +##################################################### + +### this class will be used to display some messages +### sent back by the switch after an error +class OFPacketField(PacketField): + def getfield(self, pkt, s): + try: + l = s[2:4] + l = struct.unpack("!H", l)[0] + ofload = s[:l] + remain = s[l:] + return remain, OpenFlow(None, ofload)(ofload) + except: + return "", Raw(s) + +ofp_error_type = { 0: "OFPET_HELLO_FAILED", + 1: "OFPET_BAD_REQUEST", + 2: "OFPET_BAD_ACTION", + 3: "OFPET_BAD_INSTRUCTION", + 4: "OFPET_BAD_MATCH", + 5: "OFPET_FLOW_MOD_FAILED", + 6: "OFPET_GROUP_MOD_FAILED", + 7: "OFPET_PORT_MOD_FAILED", + 8: "OFPET_TABLE_MOD_FAILED", + 9: "OFPET_QUEUE_OP_FAILED", + 10: "OFPET_SWITCH_CONFIG_FAILED", + 11: "OFPET_ROLE_REQUEST_FAILED", + 12: "OFPET_METER_MOD_FAILED", + 13: "OFPET_TABLE_FEATURES_FAILED", + 65535: "OFPET_EXPERIMENTER" } + +class OFPETHelloFailed(_ofp_header): + name = "OFPET_HELLO_FAILED" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 1, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("errtype", 0, ofp_error_type), + ShortEnumField("errcode", 0, { 0: "OFPHFC_INCOMPATIBLE", + 1: "OFPHFC_EPERM" }), + OFPacketField("data", "", Raw) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPETBadRequest(_ofp_header): + name = "OFPET_BAD_REQUEST" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 1, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("errtype", 1, ofp_error_type), + ShortEnumField("errcode", 0, { 0: "OFPBRC_BAD_VERSION", + 1: "OFPBRC_BAD_TYPE", + 2: "OFPBRC_BAD_MULTIPART", + 3: "OFPBRC_BAD_EXPERIMENTER", + 4: "OFPBRC_BAD_EXP_TYPE", + 5: "OFPBRC_EPERM", + 6: "OFPBRC_BAD_LEN", + 7: "OFPBRC_BUFFER_EMPTY", + 8: "OFPBRC_BUFFER_UNKNOWN", + 9: "OFPBRC_BAD_TABLE_ID", + 10: "OFPBRC_IS_SLAVE", + 11: "OFPBRC_BAD_PORT", + 12: "OFPBRC_BAD_PACKET", + 13: "OFPBRC_MULTIPART_BUFFER_OVERFLOW" }), + OFPacketField("data", "", Raw) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPETBadAction(_ofp_header): + name = "OFPET_BAD_ACTION" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 1, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("errtype", 2, ofp_error_type), + ShortEnumField("errcode", 0, { 0: "OFPBAC_BAD_TYPE", + 1: "OFPBAC_BAD_LEN", + 2: "OFPBAC_BAD_EXPERIMENTER", + 3: "OFPBAC_BAD_EXP_TYPE", + 4: "OFPBAC_BAD_OUT_PORT", + 5: "OFPBAC_BAD_ARGUMENT", + 6: "OFPBAC_EPERM", + 7: "OFPBAC_TOO_MANY", + 8: "OFPBAC_BAD_QUEUE", + 9: "OFPBAC_BAD_OUT_GROUP", + 10: "OFPBAC_MATCH_INCONSISTENT", + 11: "OFPBAC_UNSUPPORTED_ORDER", + 12: "OFPBAC_BAD_TAG", + 13: "OFPBAC_BAD_SET_TYPE", + 14: "OFPBAC_BAD_SET_LEN", + 15: "OFPBAC_BAD_SET_ARGUMENT" }), + OFPacketField("data", "", Raw) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPETBadInstruction(_ofp_header): + name = "OFPET_BAD_INSTRUCTION" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 1, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("errtype", 3, ofp_error_type), + ShortEnumField("errcode", 0, { 0: "OFPBIC_UNKNOWN_INST", + 1: "OFPBIC_UNSUP_INST", + 2: "OFPBIC_BAD_TABLE_ID", + 3: "OFPBIC_UNSUP_METADATA", + 4: "OFPBIC_UNSUP_METADATA_MASK", + 5: "OFPBIC_BAD_EXPERIMENTER", + 6: "OFPBIC_BAD_EXP_TYPE", + 7: "OFPBIC_BAD_LEN", + 8: "OFPBIC_EPERM" }), + OFPacketField("data", "", Raw) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPETBadMatch(_ofp_header): + name = "OFPET_BAD_MATCH" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 1, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("errtype", 4, ofp_error_type), + ShortEnumField("errcode", 0, { 0: "OFPBMC_BAD_TYPE", + 1: "OFPBMC_BAD_LEN", + 2: "OFPBMC_BAD_TAG", + 3: "OFPBMC_BAD_DL_ADDR_MASK", + 4: "OFPBMC_BAD_NW_ADDR_MASK", + 5: "OFPBMC_BAD_WILDCARDS", + 6: "OFPBMC_BAD_FIELD", + 7: "OFPBMC_BAD_VALUE", + 8: "OFPBMC_BAD_MASK", + 9: "OFPBMC_BAD_PREREQ", + 10: "OFPBMC_DUP_FIELD", + 11: "OFPBMC_EPERM" }), + OFPacketField("data", "", Raw) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPETFlowModFailed(_ofp_header): + name = "OFPET_FLOW_MOD_FAILED" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 1, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("errtype", 5, ofp_error_type), + ShortEnumField("errcode", 0, { 0: "OFPFMFC_UNKNOWN", + 1: "OFPFMFC_TABLE_FULL", + 2: "OFPFMFC_BAD_TABLE_ID", + 3: "OFPFMFC_OVERLAP", + 4: "OFPFMFC_EPERM", + 5: "OFPFMFC_BAD_TIMEOUT", + 6: "OFPFMFC_BAD_COMMAND", + 7: "OFPFMFC_BAD_FLAGS" }), + OFPacketField("data", "", Raw) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPETGroupModFailed(_ofp_header): + name = "OFPET_GROUP_MOD_FAILED" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 1, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("errtype", 6, ofp_error_type), + ShortEnumField("errcode", 0, { 0: "OFPGMFC_GROUP_EXISTS", + 1: "OFPGMFC_INVALID_GROUP", + 2: "OFPGMFC_WEIGHT_UNSUPPORTED", + 3: "OFPGMFC_OUT_OF_GROUPS", + 4: "OFPGMFC_OUT_OF_BUCKETS", + 5: "OFPGMFC_CHAINING_UNSUPPORTED", + 6: "OFPGMFC_WATCH_UNSUPPORTED", + 7: "OFPGMFC_LOOP", + 8: "OFPGMFC_UNKNOWN_GROUP", + 9: "OFPGMFC_CHAINED_GROUP", + 10: "OFPGMFC_BAD_TYPE", + 11: "OFPGMFC_BAD_COMMAND", + 12: "OFPGMFC_BAD_BUCKET", + 13: "OFPGMFC_BAD_WATCH", + 14: "OFPFMFC_EPERM" }), + OFPacketField("data", "", Raw) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPETPortModFailed(_ofp_header): + name = "OFPET_PORT_MOD_FAILED" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 1, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("errtype", 7, ofp_error_type), + ShortEnumField("errcode", 0, { 0: "OFPPMFC_BAD_PORT", + 1: "OFPPMFC_BAD_HW_ADDR", + 2: "OFPPMFC_BAD_CONFIG", + 3: "OFPPMFC_BAD_ADVERTISE", + 4: "OFPPMFC_EPERM" }), + OFPacketField("data", "", Raw) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPETTableModFailed(_ofp_header): + name = "OFPET_TABLE_MOD_FAILED" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 1, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("errtype", 8, ofp_error_type), + ShortEnumField("errcode", 0, { 0: "OFPTMFC_BAD_TABLE", + 1: "OFPTMFC_BAD_CONFIG", + 2: "OFPTMFC_EPERM" }), + OFPacketField("data", "", Raw) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPETQueueOpFailed(_ofp_header): + name = "OFPET_QUEUE_OP_FAILED" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 1, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("errtype", 9, ofp_error_type), + ShortEnumField("errcode", 0, { 0: "OFPQOFC_BAD_PORT", + 1: "OFPQOFC_BAD_QUEUE", + 2: "OFPQOFC_EPERM" }), + OFPacketField("data", "", Raw) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPETSwitchConfigFailed(_ofp_header): + name = "OFPET_SWITCH_CONFIG_FAILED" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 1, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("errtype", 10, ofp_error_type), + ShortEnumField("errcode", 0, { 0: "OFPSCFC_BAD_FLAGS", + 1: "OFPSCFC_BAD_LEN", + 2: "OFPSCFC_EPERM" }), + OFPacketField("data", "", Raw) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPETRoleRequestFailed(_ofp_header): + name = "OFPET_ROLE_REQUEST_FAILED" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 1, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("errtype", 11, ofp_error_type), + ShortEnumField("errcode", 0, { 0: "OFPRRFC_STALE", + 1: "OFPRRFC_UNSUP", + 2: "OFPRRFC_BAD_ROLE" }), + OFPacketField("data", "", Raw) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPETMeterModFailed(_ofp_header): + name = "OFPET_METER_MOD_FAILED" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 1, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("errtype", 12, ofp_error_type), + ShortEnumField("errcode", 0, { 0: "OFPMMFC_UNKNOWN", + 1: "OFPMMFC_METER_EXISTS", + 2: "OFPMMFC_INVALID_METER", + 3: "OFPMMFC_UNKNOWN_METER", + 4: "OFPMMFC_BAD_COMMAND", + 5: "OFPMMFC_BAD_FLAGS", + 6: "OFPMMFC_BAD_RATE", + 7: "OFPMMFC_BAD_BURST", + 8: "OFPMMFC_BAD_BAND", + 9: "OFPMMFC_BAD_BAND_VALUE", + 10: "OFPMMFC_OUT_OF_METERS", + 11: "OFPMMFC_OUT_OF_BANDS" }), + OFPacketField("data", "", Raw) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPETTableFeaturesFailed(_ofp_header): + name = "OFPET_TABLE_FEATURES_FAILED" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 1, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("errtype", 13, ofp_error_type), + ShortEnumField("errcode", 0, { 0: "OFPTFFC_BAD_TABLE", + 1: "OFPTFFC_BAD_METADATA", + 2: "OFPTFFC_BAD_TYPE", + 3: "OFPTFFC_BAD_LEN", + 4: "OFPTFFC_BAD_ARGUMENT", + 5: "OFPTFFC_EPERM" }), + OFPacketField("data", "", Raw) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPETExperimenter(_ofp_header): + name = "OFPET_EXPERIMENTER" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 1, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("errtype", "OFPET_EXPERIMENTER", ofp_error_type), + ShortField("exp_type", None), + IntField("experimenter", None), + OFPacketField("data", "", Raw) ] + overload_fields = {TCP: {"dport": 6653}} + +# ofp_error_cls allows generic method OpenFlow() +# to choose the right class for dissection +ofp_error_cls = { 0: OFPETHelloFailed, + 1: OFPETBadRequest, + 2: OFPETBadAction, + 3: OFPETBadInstruction, + 4: OFPETBadMatch, + 5: OFPETFlowModFailed, + 6: OFPETGroupModFailed, + 7: OFPETPortModFailed, + 8: OFPETTableModFailed, + 9: OFPETQueueOpFailed, + 10: OFPETSwitchConfigFailed, + 11: OFPETRoleRequestFailed, + 12: OFPETMeterModFailed, + 13: OFPETTableFeaturesFailed, + 65535: OFPETExperimenter } + +################ end of OFPT_ERRORS ################# + +class OFPTEchoRequest(_ofp_header): + name = "OFPT_ECHO_REQUEST" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 2, ofp_type), + ShortField("len", None), + IntField("xid", 0) ] + overload_fields = {TCP: {"sport": 6653}} + +class OFPTEchoReply(_ofp_header): + name = "OFPT_ECHO_REPLY" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 3, ofp_type), + ShortField("len", None), + IntField("xid", 0) ] + overload_fields = {TCP: {"sport": 6653}} + +class OFPTExperimenter(_ofp_header): + name = "OFPT_EXPERIMENTER" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 4, ofp_type), + ShortField("len", None), + IntField("xid", 0), + IntField("experimenter", 0), + IntField("exp_type", 0) ] + overload_fields = {TCP: {"sport": 6653}} + +class OFPTFeaturesRequest(_ofp_header): + name = "OFPT_FEATURES_REQUEST" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 5, ofp_type), + ShortField("len", None), + IntField("xid", 0) ] + overload_fields = {TCP: {"sport": 6653}} + +class OFPTFeaturesReply(_ofp_header): + name = "OFPT_FEATURES_REPLY" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 6, ofp_type), + ShortField("len", None), + IntField("xid", 0), + LongField("datapath_id", 0), + IntField("n_buffers", 0), + ByteField("n_tables", 1), + ByteField("auxiliary_id", 0), + XShortField("pad", 0), + FlagsField("capabilities", 0, 32, [ "FLOW_STATS", + "TABLE_STATS", + "PORT_STATS", + "GROUP_STATS", + "RESERVED", #undefined + "IP_REASM", + "QUEUE_STATS", + "ARP_MATCH_IP", #undefined + "PORT_BLOCKED"]), + IntField("reserved", 0) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPTGetConfigRequest(_ofp_header): + name = "OFPT_GET_CONFIG_REQUEST" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 7, ofp_type), + ShortField("len", None), + IntField("xid", 0) ] + overload_fields = {TCP: {"sport": 6653}} + +class OFPTGetConfigReply(_ofp_header): + name = "OFPT_GET_CONFIG_REPLY" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 8, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("flags", 0, { 0: "FRAG_NORMAL", + 1: "FRAG_DROP", + 2: "FRAG_REASM", + 3: "FRAG_MASK" }), + ShortField("miss_send_len", 0) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPTSetConfig(_ofp_header): + name = "OFPT_SET_CONFIG" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 9, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("flags", 0, { 0: "FRAG_NORMAL", + 1: "FRAG_DROP", + 2: "FRAG_REASM", + 3: "FRAG_MASK" }), + ShortField("miss_send_len", 128) ] + overload_fields = {TCP: {"sport": 6653}} + +class OFPTPacketIn(_ofp_header): + name = "OFPT_PACKET_IN" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 10, ofp_type), + ShortField("len", None), + IntField("xid", 0), + IntEnumField("buffer_id", "NO_BUFFER", ofp_buffer), + ShortField("total_len", 0), + ByteEnumField("reason", 0, { 0: "OFPR_NO_MATCH", + 1: "OFPR_ACTION", + 2: "OFPR_INVALID_TTL"}), + ByteEnumField("table_id", 0, ofp_table), + LongField("cookie", 0), + MatchField("match"), + XShortField("pad", 0), + PacketField("data", "", Ether) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPTFlowRemoved(_ofp_header): + name = "OFPT_FLOW_REMOVED" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 11, ofp_type), + ShortField("len", None), + IntField("xid", 0), + LongField("cookie", 0), + ShortField("priority", 0), + ByteEnumField("reason", 0, { 0: "OFPRR_IDLE_TIMEOUT", + 1: "OFPRR_HARD_TIMEOUT", + 2: "OFPRR_DELETE", + 3: "OFPRR_GROUP_DELETE"}), + ByteEnumField("table_id", 0, ofp_table), + IntField("duration_sec", 0), + IntField("duration_nsec", 0), + ShortField("idle_timeout", 0), + ShortField("hard_timeout", 0), + LongField("packet_count", 0), + LongField("byte_count", 0), + MatchField("match") ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPTPortStatus(_ofp_header): + name = "OFPT_PORT_STATUS" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 12, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ByteEnumField("reason", 0, { 0: "OFPPR_ADD", + 1: "OFPPR_DELETE", + 2: "OFPPR_MODIFY"}), + XBitField("pad", 0, 56), + PacketField("desc", OFPPort(), OFPPort) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPTPacketOut(_ofp_header): + name = "OFPT_PACKET_OUT" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 13, ofp_type), + ShortField("len", None), + IntField("xid", 0), + IntEnumField("buffer_id", "NO_BUFFER", ofp_buffer), + IntEnumField("in_port", "CONTROLLER", ofp_port_no), + FieldLenField("actions_len", None, fmt="H", length_of="actions"), + XBitField("pad", 0, 48), + ActionPacketListField("actions", [], Packet, + length_from=lambda pkt:pkt.actions_len), + PacketField("data", "", Ether) ] + overload_fields = {TCP: {"sport": 6653}} + +class OFPTFlowMod(_ofp_header): + name = "OFPT_FLOW_MOD" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 14, ofp_type), + ShortField("len", None), + IntField("xid", 0), + LongField("cookie", 0), + LongField("cookie_mask", 0), + ByteEnumField("table_id", 0, ofp_table), + ByteEnumField("cmd", 0, { 0: "OFPFC_ADD", + 1: "OFPFC_MODIFY", + 2: "OFPFC_MODIFY_STRICT", + 3: "OFPFC_DELETE", + 4: "OFPFC_DELETE_STRICT" }), + ShortField("idle_timeout", 0), + ShortField("hard_timeout", 0), + ShortField("priority", 0), + IntEnumField("buffer_id", "NO_BUFFER", ofp_buffer), + IntEnumField("out_port", "ANY", ofp_port_no), + IntEnumField("out_group", "ANY", ofp_group), + FlagsField("flags", 0, 16, [ "SEND_FLOW_REM", + "CHECK_OVERLAP", + "RESET_COUNTS", + "NO_PKT_COUNTS", + "NO_BYT_COUNTS" ]), + XShortField("pad", 0), + MatchField("match"), + InstructionPacketListField("instructions", [], Packet, + length_from=lambda pkt:pkt.len-48-(pkt.match.length+(8-pkt.match.length%8)%8)) ] + # include match padding to match.length + overload_fields = {TCP: {"sport": 6653}} + +class OFPTGroupMod(_ofp_header): + name = "OFPT_GROUP_MOD" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 15, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("cmd", 0, { 0: "OFPGC_ADD", + 1: "OFPGC_MODIFY", + 2: "OFPGC_DELETE" }), + ByteEnumField("group_type", 0, { 0: "OFPGT_ALL", + 1: "OFPGT_SELECT", + 2: "OFPGT_INDIRECT", + 3: "OFPGT_FF" }), + XByteField("pad", 0), + IntEnumField("group_id", 0, ofp_group), + BucketPacketListField("buckets", [], Packet, + length_from=lambda pkt:pkt.len-16) ] + overload_fields = {TCP: {"sport": 6653}} + +class OFPTPortMod(_ofp_header): + name = "OFPT_PORT_MOD" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 16, ofp_type), + ShortField("len", None), + IntField("xid", 0), + IntEnumField("port_no", 0, ofp_port_no), + XIntField("pad1", 0), + MACField("hw_addr", "0"), + XShortField("pad2", 0), + FlagsField("config", 0, 32, ofp_port_config), + FlagsField("mask", 0, 32, ofp_port_config), + FlagsField("advertise", 0, 32, ofp_port_features), + XIntField("pad3", 0) ] + overload_fields = {TCP: {"sport": 6653}} + +class OFPTTableMod(_ofp_header): + name = "OFPT_TABLE_MOD" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 17, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ByteEnumField("table_id", 0, ofp_table), + X3BytesField("pad", 0), + IntEnumField("config", 0, { 3: "OFPTC_DEPRECATED_MASK"}) ] + overload_fields = {TCP: {"sport": 6653}} + +##################################################### +################## OFPT_MULTIPART ################### +##################################################### + +ofp_multipart_types = { 0: "OFPMP_DESC", + 1: "OFPMP_FLOW", + 2: "OFPMP_AGGREGATE", + 3: "OFPMP_TABLE", + 4: "OFPMP_PORT_STATS", + 5: "OFPMP_QUEUE", + 6: "OFPMP_GROUP", + 7: "OFPMP_GROUP_DESC", + 8: "OFPMP_GROUP_FEATURES", + 9: "OFPMP_METER", + 10: "OFPMP_METER_CONFIG", + 11: "OFPMP_METER_FEATURES", + 12: "OFPMP_TABLE_FEATURES", + 13: "OFPMP_PORT_DESC", + 65535: "OFPST_VENDOR" } + +ofpmp_request_flags = [ "REQ_MORE" ] + +ofpmp_reply_flags = [ "REPLY_MORE" ] + +class OFPMPRequestDesc(_ofp_header): + name = "OFPMP_REQUEST_DESC" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 18, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 0, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_request_flags), + XIntField("pad", 0) ] + overload_fields = {TCP: {"sport": 6653}} + +class OFPMPReplyDesc(_ofp_header): + name = "OFPMP_REPLY_DESC" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 19, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 0, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_reply_flags), + XIntField("pad", 0), + StrFixedLenField("mfr_desc", "", 256), + StrFixedLenField("hw_desc", "", 256), + StrFixedLenField("sw_desc", "", 256), + StrFixedLenField("serial_num", "", 32), + StrFixedLenField("dp_desc", "", 256) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPMPRequestFlow(_ofp_header): + name = "OFPMP_REQUEST_FLOW" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 18, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 1, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_request_flags), + XIntField("pad1", 0), + ByteEnumField("table_id", "ALL", ofp_table), + X3BytesField("pad2", 0), + IntEnumField("out_port", "ANY", ofp_port_no), + IntEnumField("out_group", "ANY", ofp_group), + IntField("pad3", 0), + LongField("cookie", 0), + LongField("cookie_mask", 0), + MatchField("match") ] + overload_fields = {TCP: {"sport": 6653}} + +class OFPFlowStats(Packet): + def post_build(self, p, pay): + if self.length is None: + l = len(p)+len(pay) + p = struct.pack("!H", l) + p[2:] + return p + pay + name = "OFP_FLOW_STATS" + fields_desc = [ ShortField("length", None), + ByteEnumField("table_id", 0, ofp_table), + XByteField("pad1", 0), + IntField("duration_sec", 0), + IntField("duration_nsec", 0), + ShortField("priority", 0), + ShortField("idle_timeout", 0), + ShortField("hard_timeout", 0), + FlagsField("flags", 0, 16, [ "SEND_FLOW_REM", + "CHECK_OVERLAP", + "RESET_COUNTS", + "NO_PKT_COUNTS", + "NO_BYT_COUNTS" ]), + IntField("pad2", 0), + LongField("cookie", 0), + LongField("packet_count", 0), + LongField("byte_count", 0), + MatchField("match"), + InstructionPacketListField("instructions", [], Packet, + length_from=lambda pkt:pkt.length-56-pkt.match.length) ] + +class FlowStatsPacketListField(PacketListField): + + @staticmethod + def _get_flow_stats_length(s): + return struct.unpack("!H", s[:2])[0] + + def getfield(self, pkt, s): + lst = [] + remain = s + + while remain: + l = FlowStatsPacketListField._get_flow_stats_length(remain) + current = remain[:l] + remain = remain[l:] + p = OFPFlowStats(current) + lst.append(p) + + return remain, lst + +class OFPMPReplyFlow(_ofp_header): + name = "OFPMP_REPLY_FLOW" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 19, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 1, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_reply_flags), + XIntField("pad1", 0), + FlowStatsPacketListField("flow_stats", [], Packet, + length_from=lambda pkt:pkt.len-16) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPMPRequestAggregate(_ofp_header): + name = "OFPMP_REQUEST_AGGREGATE" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 18, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 2, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_request_flags), + XIntField("pad1", 0), + ByteEnumField("table_id", "ALL", ofp_table), + X3BytesField("pad2", 0), + IntEnumField("out_port", "ANY", ofp_port_no), + IntEnumField("out_group", "ANY", ofp_group), + IntField("pad3", 0), + LongField("cookie", 0), + LongField("cookie_mask", 0), + MatchField("match") ] + overload_fields = {TCP: {"sport": 6653}} + +class OFPMPReplyAggregate(_ofp_header): + name = "OFPMP_REPLY_AGGREGATE" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 19, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 2, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_reply_flags), + XIntField("pad1", 0), + LongField("packet_count", 0), + LongField("byte_count", 0), + IntField("flow_count", 0), + XIntField("pad2", 0) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPMPRequestTable(_ofp_header): + name = "OFPMP_REQUEST_TABLE" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 18, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 3, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_request_flags), + XIntField("pad1", 0) ] + overload_fields = {TCP: {"sport": 6653}} + +class OFPTableStats(Packet): + def extract_padding(self, s): + return "", s + name = "OFP_TABLE_STATS" + fields_desc = [ ByteEnumField("table_id", 0, ofp_table), + X3BytesField("pad1", 0), + IntField("active_count", 0), + LongField("lookup_count", 0), + LongField("matched_count", 0) ] + +class OFPMPReplyTable(_ofp_header): + name = "OFPMP_REPLY_TABLE" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 19, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 3, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_reply_flags), + XIntField("pad1", 0), + PacketListField("table_stats", None, OFPTableStats, + length_from=lambda pkt:pkt.len-16) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPMPRequestPortStats(_ofp_header): + name = "OFPMP_REQUEST_PORT_STATS" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 18, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 4, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_request_flags), + XIntField("pad1", 0), + IntEnumField("port_no", "ANY", ofp_port_no), + XIntField("pad", 0) ] + overload_fields = {TCP: {"sport": 6653}} + +class OFPPortStats(Packet): + def extract_padding(self, s): + return "", s + name = "OFP_PORT_STATS" + fields_desc = [ IntEnumField("port_no", 0, ofp_port_no), + XIntField("pad", 0), + LongField("rx_packets", 0), + LongField("tx_packets", 0), + LongField("rx_bytes", 0), + LongField("tx_bytes", 0), + LongField("rx_dropped", 0), + LongField("tx_dropped", 0), + LongField("rx_errors", 0), + LongField("tx_errors", 0), + LongField("rx_frame_err", 0), + LongField("rx_over_err", 0), + LongField("rx_crc_err", 0), + LongField("collisions", 0), + IntField("duration_sec", 0), + IntField("duration_nsec", 0) ] + +class OFPMPReplyPortStats(_ofp_header): + name = "OFPMP_REPLY_PORT_STATS" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 19, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 4, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_reply_flags), + XIntField("pad1", 0), + PacketListField("port_stats", None, OFPPortStats, + length_from=lambda pkt:pkt.len-16) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPMPRequestQueue(_ofp_header): + name = "OFPMP_REQUEST_QUEUE" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 18, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 5, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_request_flags), + XIntField("pad1", 0), + IntEnumField("port_no", "ANY", ofp_port_no), + IntEnumField("queue_id", "ALL", ofp_queue) ] + overload_fields = {TCP: {"sport": 6653}} + +class OFPQueueStats(Packet): + def extract_padding(self, s): + return "", s + name = "OFP_QUEUE_STATS" + fields_desc = [ IntEnumField("port_no", 0, ofp_port_no), + IntEnumField("queue_id", 0, ofp_queue), + LongField("tx_bytes", 0), + LongField("tx_packets", 0), + LongField("tx_errors", 0), + IntField("duration_sec", 0), + IntField("duration_nsec", 0) ] + +class OFPMPReplyQueue(_ofp_header): + name = "OFPMP_REPLY_QUEUE" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 19, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 5, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_reply_flags), + XIntField("pad1", 0), + PacketListField("queue_stats", None, OFPQueueStats, + length_from=lambda pkt:pkt.len-16) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPMPRequestGroup(_ofp_header): + name = "OFPMP_REQUEST_GROUP" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 18, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 6, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_request_flags), + XIntField("pad1", 0), + IntEnumField("group_id", "ANY", ofp_group), + XIntField("pad2", 0) ] + overload_fields = {TCP: {"sport": 6653}} + +class OFPBucketStats(Packet): + def extract_padding(self, s): + return "", s + name = "OFP_BUCKET_STATS" + fields_desc = [ LongField("packet_count", 0), + LongField("byte_count", 0) ] + +class OFPGroupStats(Packet): + def post_build(self, p, pay): + if self.length is None: + l = len(p)+len(pay) + p = struct.pack("!H", l) + p[2:] + return p + pay + name = "OFP_GROUP_STATS" + fields_desc = [ ShortField("length", None), + XShortField("pad1", 0), + IntEnumField("group_id", 0, ofp_group), + IntField("ref_count", 0), + IntField("pad2", 0), + LongField("packet_count", 0), + LongField("byte_count", 0), + IntField("duration_sec", 0), + IntField("duration_nsec", 0), + PacketListField("bucket_stats", None, OFPBucketStats, + length_from=lambda pkt:pkt.length-40) ] + +class GroupStatsPacketListField(PacketListField): + + @staticmethod + def _get_group_stats_length(s): + return struct.unpack("!H", s[:2])[0] + + def getfield(self, pkt, s): + lst = [] + remain = s + + while remain: + l = GroupStatsPacketListField._get_group_stats_length(remain) + current = remain[:l] + remain = remain[l:] + p = OFPGroupStats(current) + lst.append(p) + + return remain, lst + +class OFPMPReplyGroup(_ofp_header): + name = "OFPMP_REPLY_GROUP" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 19, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 6, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_reply_flags), + XIntField("pad1", 0), + GroupStatsPacketListField("group_stats", [], Packet, + length_from=lambda pkt:pkt.len-16) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPMPRequestGroupDesc(_ofp_header): + name = "OFPMP_REQUEST_GROUP_DESC" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 18, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 7, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_request_flags), + XIntField("pad1", 0) ] + overload_fields = {TCP: {"sport": 6653}} + +class OFPGroupDesc(Packet): + def post_build(self, p, pay): + if self.length is None: + l = len(p)+len(pay) + p = struct.pack("!H", l) + p[2:] + return p + pay + name = "OFP_GROUP_DESC" + fields_desc = [ ShortField("length", None), + ByteEnumField("type", 0, { 0: "OFPGT_ALL", + 1: "OFPGT_SELECT", + 2: "OFPGT_INDIRECT", + 3: "OFPGT_FF" }), + XByteField("pad", 0), + IntEnumField("group_id", 0, ofp_group), + BucketPacketListField("buckets", None, Packet, + length_from=lambda pkt:pkt.length-8) ] + +class GroupDescPacketListField(PacketListField): + + @staticmethod + def _get_group_desc_length(s): + return struct.unpack("!H", s[:2])[0] + + def getfield(self, pkt, s): + lst = [] + remain = s + + while remain: + l = GroupsDescPacketListField._get_group_desc_length(remain) + current = remain[:l] + remain = remain[l:] + p = OFPGroupDesc(current) + lst.append(p) + + return remain, lst + + +class OFPMPReplyGroupDesc(_ofp_header): + name = "OFPMP_REPLY_GROUP_DESC" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 19, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 7, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_reply_flags), + XIntField("pad1", 0), + GroupDescPacketListField("group_descs", [], Packet, + length_from=lambda pkt:pkt.len-16) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPMPRequestGroupFeatures(_ofp_header): + name = "OFPMP_REQUEST_GROUP_FEATURES" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 18, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 8, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_request_flags), + XIntField("pad1", 0) ] + overload_fields = {TCP: {"sport": 6653}} + +ofp_action_types_flags = ofp_action_types.values()[:-1] # no ofpat_experimenter flag +class OFPMPReplyGroupFeatures(_ofp_header): + name = "OFPMP_REPLY_GROUP_FEATURES" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 19, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 8, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_reply_flags), + XIntField("pad1", 0), + FlagsField("types", 0, 32, [ "ALL", + "SELECT", + "INDIRECT", + "FF" ]), + FlagsField("capabilities", 0, 32, [ "SELECT_WEIGHT", + "SELECT_LIVENESS", + "CHAINING", + "CHAINING_CHECKS" ]), + IntField("max_group_all", 0), + IntField("max_group_select", 0), + IntField("max_group_indirect", 0), + IntField("max_group_ff", 0), + # no ofpat_experimenter flag + FlagsField("actions_all", 0, 32, ofp_action_types_flags), + FlagsField("actions_select", 0, 32, ofp_action_types_flags), + FlagsField("actions_indirect", 0, 32, ofp_action_types_flags), + FlagsField("actions_ff", 0, 32, ofp_action_types_flags) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPMPRequestMeter(_ofp_header): + name = "OFPMP_REQUEST_METER" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 18, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 9, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_request_flags), + XIntField("pad1", 0), + IntEnumField("meter_id", "ALL", ofp_meter), + XIntField("pad2", 0) ] + overload_fields = {TCP: {"sport": 6653}} + +class OFPMeterBandStats(Packet): + def extract_padding(self, s): + return "", s + name = "OFP_METER_BAND_STATS" + fields_desc = [ LongField("packet_band_count", 0), + LongField("byte_band_count", 0) ] + +class OFPMeterStats(Packet): + def post_build(self, p, pay): + if self.len is None: + l = len(p)+len(pay) + p = p[:4] + struct.pack("!H", l) + p[6:] + return p + pay + name = "OFP_GROUP_STATS" + fields_desc = [ IntEnumField("meter_id", 1, ofp_meter), + ShortField("len", None), + XBitField("pad", 0, 48), + IntField("flow_count", 0), + LongField("packet_in_count", 0), + LongField("byte_in_count", 0), + IntField("duration_sec", 0), + IntField("duration_nsec", 0), + PacketListField("band_stats", None, OFPMeterBandStats, + length_from=lambda pkt:pkt.len-40) ] + +class MeterStatsPacketListField(PacketListField): + + @staticmethod + def _get_meter_stats_length(s): + return struct.unpack("!H", s[4:6])[0] + + def getfield(self, pkt, s): + lst = [] + l = 0 + ret = "" + remain = s + + while remain: + l = MeterStatsPacketListField._get_meter_stats_length(remain) + current = remain[:l] + remain = remain[l:] + p = OFPMeterStats(current) + lst.append(p) + + return remain + ret, lst + +class OFPMPReplyMeter(_ofp_header): + name = "OFPMP_REPLY_METER" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 19, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 9, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_reply_flags), + XIntField("pad1", 0), + MeterStatsPacketListField("meter_stats", [], Packet, + length_from=lambda pkt:pkt.len-16) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPMPRequestMeterConfig(_ofp_header): + name = "OFPMP_REQUEST_METER_CONFIG" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 18, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 10, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_request_flags), + XIntField("pad1", 0), + IntEnumField("meter_id", "ALL", ofp_meter), + XIntField("pad2", 0) ] + overload_fields = {TCP: {"sport": 6653}} + +class OFPMeterConfig(Packet): + def post_build(self, p, pay): + if self.length is None: + l = len(p)+len(pay) + p = struct.pack("!H", l) + p[2:] + return p + pay + name = "OFP_METER_CONFIG" + fields_desc = [ ShortField("length", None), + FlagsField("flags", 0, 16, [ "KBPS", + "PKTPS", + "BURST", + "STATS" ]), + IntEnumField("meter_id", 1, ofp_meter), + MeterBandPacketListField("bands", [], Packet, + length_from=lambda pkt:pkt.len-8) ] + +class MeterConfigPacketListField(PacketListField): + + @staticmethod + def _get_meter_config_length(s): + return struct.unpack("!H", s[:2])[0] + + def getfield(self, pkt, s): + lst = [] + remain = s + + while remain: + l = MeterConfigPacketListField._get_meter_config_length(remain) + current = remain[:l] + remain = remain[l:] + p = OFPMeterConfig(current) + lst.append(p) + + return remain, lst + +class OFPMPReplyMeterConfig(_ofp_header): + name = "OFPMP_REPLY_METER_CONFIG" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 19, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 10, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_reply_flags), + XIntField("pad1", 0), + MeterConfigPacketListField("meter_configs", [], Packet, + length_from=lambda pkt:pkt.len-16) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPMPRequestMeterFeatures(_ofp_header): + name = "OFPMP_REQUEST_METER_FEATURES" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 18, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 11, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_request_flags), + XIntField("pad1", 0) ] + overload_fields = {TCP: {"sport": 6653}} + +class OFPMPReplyMeterFeatures(_ofp_header): + name = "OFPMP_REPLY_METER_FEATURES" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 19, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 11, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_reply_flags), + XIntField("pad1", 0), + IntField("max_meter", 0), + FlagsField("band_types", 0, 32, [ "DROP", + "DSCP_REMARK", + "EXPERIMENTER" ]), + FlagsField("capabilities", 0, 32, [ "KPBS", + "PKTPS", + "BURST", + "STATS" ]), + ByteField("max_bands", 0), + ByteField("max_color", 0), + XShortField("pad2", 0) ] + overload_fields = {TCP: {"dport": 6653}} + +####### table features for multipart messages ####### + +class _ofp_table_features_prop_header(Packet): + name = "Dummy OpenFlow Table Features Properties Header" + + def post_build(self, p, pay): + l = self.length + if l is None: + l = len(p)+len(pay) + p = p[:2] + struct.pack("!H", l) + p[4:] + # every message will be padded correctly + zero_bytes = (8 - l%8) % 8 + p += "\x00" * zero_bytes + return p + pay + + def extract_padding(self, s): + l = self.length + zero_bytes = (8 - l%8) % 8 + return "", s + + +ofp_table_features_prop_types = { 0: "OFPTFPT_INSTRUCTIONS", + 1: "OFPTFPT_INSTRUCTIONS_MISS", + 2: "OFPTFPT_NEXT_TABLES", + 3: "OFPTFPT_NEXT_TABLES_MISS", + 4: "OFPTFPT_WRITE_ACTIONS", + 5: "OFPTFPT_WRITE_ACTIONS_MISS", + 6: "OFPTFPT_APPLY_ACTIONS", + 7: "OFPTFPT_APPLY_ACTIONS_MISS", + 8: "OFPTFPT_MATCH", + 10: "OFPTFPT_WILDCARDS", + 12: "OFPTFPT_WRITE_SETFIELD", + 13: "OFPTFPT_WRITE_SETFIELD_MISS", + 14: "OFPTFPT_APPLY_SETFIELD", + 15: "OFPTFPT_APPLY_SETFIELD_MISS", + 65534: "OFPTFPT_EXPERIMENTER", + 65535: "OFPTFPT_EXPERIMENTER_MISS" } + +class OFPTFPTInstructions(_ofp_table_features_prop_header): + name = "OFPTFPT_INSTRUCTIONS" + fields_desc = [ ShortField("type", 0), + ShortField("length", None), + InstructionIDPacketListField("instruction_ids", [], Packet, + length_from=lambda pkt:pkt.length-4) ] + +class OFPTFPTInstructionsMiss(_ofp_table_features_prop_header): + name = "OFPTFPT_INSTRUCTIONS_MISS" + fields_desc = [ ShortField("type", 1), + ShortField("length", None), + InstructionIDPacketListField("instruction_ids", [], Packet, + length_from=lambda pkt:pkt.length-4) ] + +class OFPTableID(Packet): + def extract_padding(self, s): + return "", s + name = "OFP_TABLE_ID" + fields_desc = [ ByteEnumField("table_id", 0, ofp_table) ] + +class OFPTFPTNextTables(_ofp_table_features_prop_header): + name = "OFPTFPT_NEXT_TABLES" + fields_desc = [ ShortField("type", 2), + ShortField("length", None), + PacketListField("next_table_ids", None, OFPTableID, + length_from=lambda pkt:pkt.length-4) ] + +class OFPTFPTNextTablesMiss(_ofp_table_features_prop_header): + name = "OFPTFPT_NEXT_TABLES_MISS" + fields_desc = [ ShortField("type", 3), + ShortField("length", None), + PacketListField("next_table_ids", None, OFPTableID, + length_from=lambda pkt:pkt.length-4) ] + +class OFPTFPTWriteActions(_ofp_table_features_prop_header): + name = "OFPTFPT_WRITE_ACTIONS" + fields_desc = [ ShortField("type", 4), + ShortField("length", None), + ActionIDPacketListField("action_ids", [], Packet, + length_from=lambda pkt:pkt.length-4) ] + +class OFPTFPTWriteActionsMiss(_ofp_table_features_prop_header): + name = "OFPTFPT_WRITE_ACTIONS_MISS" + fields_desc = [ ShortField("type", 5), + ShortField("length", None), + ActionIDPacketListField("action_ids", [], Packet, + length_from=lambda pkt:pkt.length-4) ] + +class OFPTFPTApplyActions(_ofp_table_features_prop_header): + name = "OFPTFPT_APPLY_ACTIONS" + fields_desc = [ ShortField("type", 6), + ShortField("length", None), + ActionIDPacketListField("action_ids", [], Packet, + length_from=lambda pkt:pkt.length-4) ] + +class OFPTFPTApplyActionsMiss(_ofp_table_features_prop_header): + name = "OFPTFPT_APPLY_ACTIONS_MISS" + fields_desc = [ ShortField("type", 7), + ShortField("length", None), + ActionIDPacketListField("action_ids", [], Packet, + length_from=lambda pkt:pkt.length-4) ] + +class OFPTFPTMatch(_ofp_table_features_prop_header): + name = "OFPTFPT_MATCH" + fields_desc = [ ShortField("type", 8), + ShortField("length", None), + OXMIDPacketListField("oxm_ids", [], Packet, + length_from=lambda pkt:pkt.length-4) ] + +class OFPTFPTWildcards(_ofp_table_features_prop_header): + name = "OFPTFPT_WILDCARDS" + fields_desc = [ ShortField("type", 10), + ShortField("length", None), + OXMIDPacketListField("oxm_ids", [], Packet, + length_from=lambda pkt:pkt.length-4) ] + +class OFPTFPTWriteSetField(_ofp_table_features_prop_header): + name = "OFPTFPT_WRITE_SETFIELD" + fields_desc = [ ShortField("type", 12), + ShortField("length", None), + OXMIDPacketListField("oxm_ids", [], Packet, + length_from=lambda pkt:pkt.length-4) ] + +class OFPTFPTWriteSetFieldMiss(_ofp_table_features_prop_header): + name = "OFPTFPT_WRITE_SETFIELD_MISS" + fields_desc = [ ShortField("type", 13), + ShortField("length", None), + OXMIDPacketListField("oxm_ids", [], Packet, + length_from=lambda pkt:pkt.length-4) ] + +class OFPTFPTApplySetField(_ofp_table_features_prop_header): + name = "OFPTFPT_APPLY_SETFIELD" + fields_desc = [ ShortField("type", 14), + ShortField("length", None), + OXMIDPacketListField("oxm_ids", [], Packet, + length_from=lambda pkt:pkt.length-4) ] + +class OFPTFPTApplySetFieldMiss(_ofp_table_features_prop_header): + name = "OFPTFPT_APPLY_SETFIELD_MISS" + fields_desc = [ ShortField("type", 15), + ShortField("length", None), + OXMIDPacketListField("oxm_ids", [], Packet, + length_from=lambda pkt:pkt.length-4) ] + +class OFPTFPTExperimenter(_ofp_table_features_prop_header): + name = "OFPTFPT_EXPERIMENTER" + fields_desc = [ ShortField("type", 65534), + ShortField("length", None), + IntField("experimenter", 0), + IntField("exp_type", 0), + PacketField("experimenter_data", None, Raw) ] + +class OFPTFPTExperimenterMiss(_ofp_table_features_prop_header): + name = "OFPTFPT_EXPERIMENTER_MISS" + fields_desc = [ ShortField("type", 65535), + ShortField("length", None), + IntField("experimenter", 0), + IntField("exp_type", 0), + PacketField("experimenter_data", None, Raw) ] + +ofp_table_features_prop_cls = { 0: OFPTFPTInstructions, + 1: OFPTFPTInstructionsMiss, + 2: OFPTFPTNextTables, + 3: OFPTFPTNextTablesMiss, + 4: OFPTFPTWriteActions, + 5: OFPTFPTWriteActionsMiss, + 6: OFPTFPTApplyActions, + 7: OFPTFPTApplyActionsMiss, + 8: OFPTFPTMatch, + 10: OFPTFPTWildcards, + 12: OFPTFPTWriteSetField, + 13: OFPTFPTWriteSetFieldMiss, + 14: OFPTFPTApplySetField, + 15: OFPTFPTApplySetFieldMiss, + 65534: OFPTFPTExperimenter, + 65535: OFPTFPTExperimenterMiss } + +class TableFeaturesPropPacketListField(PacketListField): + + @staticmethod + def _get_table_features_prop_length(s): + return struct.unpack("!H", s[2:4])[0] + + def m2i(self, pkt, s): + t = struct.unpack("!H", s[:2])[0] + return ofp_table_features_prop_cls.get(t, Raw)(s) + + def getfield(self, pkt, s): + lst = [] + remain = s + + while remain and len(remain) >= 4: + l = TableFeaturesPropPacketListField._get_table_features_prop_length(remain) + # add padding ! + lpad = l + (8 - l%8)%8 + if l < 4 or len(remain) < lpad: + # no zero length nor incoherent length + break + current = remain[:lpad] + remain = remain[lpad:] + p = self.m2i(pkt, current) + lst.append(p) + + return remain, lst + +class OFPTableFeatures(Packet): + def post_build(self, p, pay): + if self.length is None: + l = len(p)+len(pay) + p = struct.pack("!H", l) + p[2:] + return p + pay + name = "OFP_TABLE_FEATURES" + fields_desc = [ ShortField("length", None), + ByteEnumField("table_id", 0, ofp_table), + XBitField("pad", 0, 40), + StrFixedLenField("table_name", "", 32), + LongField("metadata_match", 0), + LongField("metadata_write", 0), + IntEnumField("config", 0, { 0: "OFPTC_NO_MASK", + 3: "OFPTC_DEPRECATED_MASK" }), + IntField("max_entries", 0), + TableFeaturesPropPacketListField("properties", [], Packet, + length_from=lambda pkt:pkt.length-64) ] + +class TableFeaturesPacketListField(PacketListField): + + @staticmethod + def _get_table_features_length(s): + return struct.unpack("!H", s[:2])[0] + + def getfield(self, pkt, s): + lst = [] + remain = s + + while remain: + l = TableFeaturesPacketListField._get_table_features_length(remain) + current = remain[:l] + remain = remain[l:] + p = OFPTableFeatures(current) + lst.append(p) + + return remain, lst + +class OFPMPRequestTableFeatures(_ofp_header): + name = "OFPMP_REQUEST_TABLE_FEATURES" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 18, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 12, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_request_flags), + XIntField("pad1", 0), + TableFeaturesPacketListField("table_features", [], Packet, + length_from=lambda pkt:pkt.len-16) ] + overload_fields = {TCP: {"sport": 6653}} + +class OFPMPReplyTableFeatures(_ofp_header): + name = "OFPMP_REPLY_TABLE_FEATURES" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 19, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 12, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_reply_flags), + XIntField("pad1", 0), + TableFeaturesPacketListField("table_features", [], Packet, + length_from=lambda pkt:pkt.len-16) ] + overload_fields = {TCP: {"dport": 6653}} + +############### end of table features ############### + +class OFPMPRequestPortDesc(_ofp_header): + name = "OFPMP_REQUEST_PORT_DESC" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 18, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 13, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_request_flags), + XIntField("pad1", 0), + IntEnumField("port_no", 0, ofp_port_no), + XIntField("pad", 0) ] + overload_fields = {TCP: {"sport": 6653}} + +class OFPMPReplyPortDesc(_ofp_header): + name = "OFPMP_REPLY_PORT_DESC" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 19, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 13, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_reply_flags), + XIntField("pad1", 0), + PacketListField("ports", None, OFPPort, + length_from=lambda pkt:pkt.len-16) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPMPRequestExperimenter(_ofp_header): + name = "OFPST_REQUEST_EXPERIMENTER" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 18, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 65535, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_request_flags), + XIntField("pad1", 0), + IntField("experimenter", 0), + IntField("exp_type", 0) ] + overload_fields = {TCP: {"sport": 6653}} + +class OFPMPReplyExperimenter(_ofp_header): + name = "OFPST_REPLY_EXPERIMENTER" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 19, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 65535, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_reply_flags), + XIntField("pad1", 0), + IntField("experimenter", 0), + IntField("exp_type", 0) ] + overload_fields = {TCP: {"dport": 6653}} + +# ofp_multipart_request/reply_cls allows generic method OpenFlow() +# to choose the right class for dissection +ofp_multipart_request_cls = { 0: OFPMPRequestDesc, + 1: OFPMPRequestFlow, + 2: OFPMPRequestAggregate, + 3: OFPMPRequestTable, + 4: OFPMPRequestPortStats, + 5: OFPMPRequestQueue, + 6: OFPMPRequestGroup, + 7: OFPMPRequestGroupDesc, + 8: OFPMPRequestGroupFeatures, + 9: OFPMPRequestMeter, + 10: OFPMPRequestMeterConfig, + 11: OFPMPRequestMeterFeatures, + 12: OFPMPRequestTableFeatures, + 13: OFPMPRequestPortDesc, + 65535: OFPMPRequestExperimenter } + +ofp_multipart_reply_cls = { 0: OFPMPReplyDesc, + 1: OFPMPReplyFlow, + 2: OFPMPReplyAggregate, + 3: OFPMPReplyTable, + 4: OFPMPReplyPortStats, + 5: OFPMPReplyQueue, + 6: OFPMPReplyGroup, + 7: OFPMPReplyGroupDesc, + 8: OFPMPReplyGroupFeatures, + 9: OFPMPReplyMeter, + 10: OFPMPReplyMeterConfig, + 11: OFPMPReplyMeterFeatures, + 12: OFPMPReplyTableFeatures, + 13: OFPMPReplyPortDesc, + 65535: OFPMPReplyExperimenter } + +############## end of OFPT_MULTIPART ################ + +class OFPTBarrierRequest(_ofp_header): + name = "OFPT_BARRIER_REQUEST" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 20, ofp_type), + ShortField("len", None), + IntField("xid", 0) ] + overload_fields = {TCP: {"sport": 6653}} + +class OFPTBarrierReply(_ofp_header): + name = "OFPT_BARRIER_REPLY" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 21, ofp_type), + ShortField("len", None), + IntField("xid", 0) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPTQueueGetConfigRequest(_ofp_header): + name = "OFPT_QUEUE_GET_CONFIG_REQUEST" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 22, ofp_type), + ShortField("len", None), + IntField("xid", 0), + IntEnumField("port_no", "ANY", ofp_port_no), + XIntField("pad", 0) ] + overload_fields = {TCP: {"sport": 6653}} + +class OFPTQueueGetConfigReply(_ofp_header): + name = "OFPT_QUEUE_GET_CONFIG_REPLY" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 23, ofp_type), + ShortField("len", None), + IntField("xid", 0), + IntEnumField("port", 0, ofp_port_no), + XIntField("pad", 0), + QueuePacketListField("queues", [], Packet, + length_from=lambda pkt:pkt.len-16) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPTRoleRequest(_ofp_header): + name = "OFPT_ROLE_REQUEST" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 24, ofp_type), + ShortField("len", None), + IntField("xid", 0), + IntEnumField("role", 0, { 0: "OFPCR_ROLE_NOCHANGE", + 1: "OFPCR_ROLE_EQUAL", + 2: "OFPCR_ROLE_MASTER", + 3: "OFPCR_ROLE_SLAVE" }), + XIntField("pad", 0), + LongField("generation_id", 0) ] + overload_fields = {TCP: {"sport": 6653}} + +class OFPTRoleReply(_ofp_header): + name = "OFPT_ROLE_REPLY" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 25, ofp_type), + ShortField("len", None), + IntField("xid", 0), + IntEnumField("role", 0, { 0: "OFPCR_ROLE_NOCHANGE", + 1: "OFPCR_ROLE_EQUAL", + 2: "OFPCR_ROLE_MASTER", + 3: "OFPCR_ROLE_SLAVE" }), + XIntField("pad", 0), + LongField("generation_id", 0) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPTGetAsyncRequest(_ofp_header): + name = "OFPT_GET_ASYNC_REQUEST" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 26, ofp_type), + ShortField("len", 8), + IntField("xid", 0) ] + overload_fields = {TCP: {"sport": 6653}} + +ofp_packet_in_reason = [ "NO_MATCH", + "ACTION", + "INVALID_TTL" ] + +ofp_port_reason = [ "ADD", + "DELETE", + "MODIFY" ] + +ofp_flow_removed_reason = [ "IDLE_TIMEOUT", + "HARD_TIMEOUT", + "DELETE", + "GROUP_DELETE" ] + +class OFPTGetAsyncReply(_ofp_header): + name = "OFPT_GET_ASYNC_REPLY" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 27, ofp_type), + ShortField("len", 32), + IntField("xid", 0), + FlagsField("packet_in_mask_master", 0, 32, ofp_packet_in_reason), + FlagsField("packet_in_mask_slave", 0, 32, ofp_packet_in_reason), + FlagsField("port_status_mask_master", 0, 32, ofp_port_reason), + FlagsField("port_status_mask_slave", 0, 32, ofp_port_reason), + FlagsField("flow_removed_mask_master", 0, 32, ofp_flow_removed_reason), + FlagsField("flow_removed_mask_slave", 0, 32, ofp_flow_removed_reason) ] + overload_fields = {TCP: {"dport": 6653}} + +class OFPTSetAsync(_ofp_header): + name = "OFPT_SET_ASYNC" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 28, ofp_type), + ShortField("len", 32), + IntField("xid", 0), + FlagsField("packet_in_mask_master", 0, 32, ofp_packet_in_reason), + FlagsField("packet_in_mask_slave", 0, 32, ofp_packet_in_reason), + FlagsField("port_status_mask_master", 0, 32, ofp_port_reason), + FlagsField("port_status_mask_slave", 0, 32, ofp_port_reason), + FlagsField("flow_removed_mask_master", 0, 32, ofp_flow_removed_reason), + FlagsField("flow_removed_mask_slave", 0, 32, ofp_flow_removed_reason) ] + overload_fields = {TCP: {"sport": 6653}} + +class OFPTMeterMod(_ofp_header): + name = "OFPT_METER_MOD" + fields_desc = [ ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 29, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("cmd", 0, { 0: "OFPMC_ADD", + 1: "OFPMC_MODIFY", + 2: "OFPMC_DELETE" }), + FlagsField("flags", 0, 16, [ "KBPS", + "PKTPS", + "BURST", + "STATS" ]), + IntEnumField("meter_id", 1, ofp_meter), + MeterBandPacketListField("bands", [], Packet, + length_from=lambda pkt:pkt.len-16) ] + overload_fields = {TCP: {"sport": 6653}} + +# ofpt_cls allows generic method OpenFlow() to choose the right class for dissection +ofpt_cls = { 0: OFPTHello, + #1: OFPTError, + 2: OFPTEchoRequest, + 3: OFPTEchoReply, + 4: OFPTExperimenter, + 5: OFPTFeaturesRequest, + 6: OFPTFeaturesReply, + 7: OFPTGetConfigRequest, + 8: OFPTGetConfigReply, + 9: OFPTSetConfig, + 10: OFPTPacketIn, + 11: OFPTFlowRemoved, + 12: OFPTPortStatus, + 13: OFPTPacketOut, + 14: OFPTFlowMod, + 15: OFPTGroupMod, + 16: OFPTPortMod, + 17: OFPTTableMod, + #18: OFPTMultipartRequest, + #19: OFPTMultipartReply, + 20: OFPTBarrierRequest, + 21: OFPTBarrierReply, + 22: OFPTQueueGetConfigRequest, + 23: OFPTQueueGetConfigReply, + 24: OFPTRoleRequest, + 25: OFPTRoleReply, + 26: OFPTGetAsyncRequest, + 27: OFPTGetAsyncReply, + 28: OFPTSetAsync, + 29: OFPTMeterMod } + +TCP_guess_payload_class_copy = TCP.guess_payload_class + +def OpenFlow(self, payload): + if self is None or self.dport == 6653 or self.dport == 6633 or self.sport == 6653 or self.sport == 6653: + # port 6653 has been allocated by IANA, port 6633 should no longer be used + # OpenFlow function may be called with None self in OFPPacketField + of_type = ord(payload[1]) + if of_type == 1: + err_type = ord(payload[9]) + # err_type is a short int, but last byte is enough + if err_type == 255: err_type = 65535 + return ofp_error_cls[err_type] + elif of_type == 16: + mp_type = ord(payload[9]) + if mp_type == 255: mp_type = 65535 + return ofp_multipart_request_cls[mp_type] + elif of_type == 17: + mp_type = ord(payload[9]) + if mp_type == 255: mp_type = 65535 + return ofp_multipart_reply_cls[mp_type] + else: + return ofpt_cls[of_type] + else: + return TCP_guess_payload_class_copy(self, payload) + +TCP.guess_payload_class = OpenFlow diff --git a/scapy/contrib/openflow3.uts b/scapy/contrib/openflow3.uts new file mode 100755 index 0000000000000000000000000000000000000000..7984fa56aa3ba87faf814cb310c1197f1bbabe55 --- /dev/null +++ b/scapy/contrib/openflow3.uts @@ -0,0 +1,83 @@ +% Tests for OpenFlow v1.3 with Scapy + ++ Usual OFv1.3 messages + += OFPTHello(), hello without version bitmap +ofm = OFPTHello() +str(ofm) == '\x04\x00\x00\x08\x00\x00\x00\x00' + += OFPTEchoRequest(), echo request +ofm = OFPTEchoRequest() +str(ofm) == '\x04\x02\x00\x08\x00\x00\x00\x00' + += OFPMatch(), check padding +ofm = OFPMatch(oxm_fields=OFBEthType(eth_type=0x86dd)) +assert(len(str(ofm))%8 == 0) +str(ofm) == '\x00\x01\x00\x0a\x80\x00\x0a\x02\x86\xdd\x00\x00\x00\x00\x00\x00' + += OpenFlow(), generic method test with OFPTEchoRequest() +ofm = OFPTEchoRequest() +s = str(ofm) +isinstance(OpenFlow(None,s)(s), OFPTEchoRequest) + += OFPTFlowMod(), check codes and defaults values +ofm = OFPTFlowMod(cmd='OFPFC_DELETE', out_group='ALL', flags='CHECK_OVERLAP+NO_PKT_COUNTS') +assert(ofm.cmd == 3) +assert(ofm.out_port == 0xffffffff) +assert(ofm.out_group == 0xfffffffc) +ofm.flags == 10 + += OFBIPv6ExtHdrHMID(), check creation of last OXM classes +assert(hasattr(OFBIPv6ExtHdr(), 'ipv6_ext_hdr_flags')) +OFBIPv6ExtHdrHMID().field == 39 + ++ Complex OFv1.3 messages + += OFPTFlowMod(), complex flow_mod +mtc = OFPMatch(oxm_fields=OFBVLANVID(vlan_vid=10)) +ist1 = OFPITApplyActions(actions=[OFPATSetField(field=OFBIPv4Src(ipv4_src='192.168.10.41')),OFPATSetField(field=OFBEthSrc(eth_src='1a:d5:cb:4e:3c:64')),OFPATOutput(port='NORMAL')]) +ist2 = OFPITWriteActions(actions=OFPATOutput(port='CONTROLLER')) +ofm = OFPTFlowMod(table_id=2, match=mtc, instructions=[ist1,ist2]) +hexdump(ofm) +s = '\x04\x0e\x00\x90\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x01\x00\x0a\x80\x00\x0c\x02\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x04\x00\x38\x00\x00\x00\x00\x00\x19\x00\x10\x80\x00\x16\x04\xc0\xa8\x0a\x29\x00\x00\x00\x00\x00\x19\x00\x10\x80\x00\x08\x06\x1a\xd5\xcb\x4e\x3c\x64\x00\x00\x00\x00\x00\x10\xff\xff\xff\xfa\xff\xff\x00\x00\x00\x00\x00\x00\x00\x03\x00\x18\x00\x00\x00\x00\x00\x00\x00\x10\xff\xff\xff\xfd\xff\xff\x00\x00\x00\x00\x00\x00' +str(ofm) == s + += OFPETBadRequest() containing a flow_mod with wrong table_id +flowmod = OFPTFlowMod(instructions=OFPITGotoTable(table_id=0)) +ofm = OFPETBadRequest(errcode='OFPBRC_BAD_TABLE_ID', data=str(flowmod)) +hexdump(ofm) +s = '\x04\x01\x00L\x00\x00\x00\x00\x00\x01\x00\t\x04\x0e\x00@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x01\x00\x04\x00\x00\x00\x00\x00\x01\x00\x08\x00\x00\x00\x00' +str(ofm) == s + += OFPTPacketIn() containing an Ethernet frame +ofm = OFPTPacketIn(data=Ether()/IP()/ICMP()) +p = OFPTPacketIn(str(ofm)) +dat = p.data +assert(isinstance(dat, Ether)) +assert(isinstance(dat.payload, IP)) +isinstance(dat.payload.payload, ICMP) + ++ Layer bindings + += TCP()/OFPMPRequestDesc(), check default sport +p = TCP()/OFPMPRequestDesc() +p[TCP].sport == 6653 + += TCP()/OFPETHelloFailed(), check default dport +p = TCP()/OFPETHelloFailed() +p[TCP].dport == 6653 + += TCP()/OFPTHello() dissection, check new TCP.guess_payload_class +o = TCP()/OFPTHello() +p = TCP(str(o)) +p[TCP].sport == 6653 +isinstance(p[TCP].payload, OFPTHello) + += complete Ether()/IP()/TCP()/OFPTFeaturesRequest() +ofm = Ether(src='00:11:22:33:44:55',dst='01:23:45:67:89:ab')/IP(src='10.0.0.7',dst='192.168.0.42')/TCP(sport=6633)/OFPTFeaturesRequest(xid=23) +s = '\x01#Eg\x89\xab\x00\x11"3DU\x08\x00E\x00\x000\x00\x01\x00\x00@\x06\xaf\xee\n\x00\x00\x07\xc0\xa8\x00*\x19\xe9\x00P\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xa6\xa4\x00\x00\x04\x05\x00\x08\x00\x00\x00\x17' +assert(str(ofm) == s) +e = Ether(s) +e.show2() +of = OFPTFeaturesRequest(e[TCP].load) +of.xid == 23