import OpenFlow 1.2 protocol module and basic test cases from CPqD/oftest12
For now these tests will live in a separate directory. The goal is to merge
them where possible but this has to wait for an OpenFlow protocol module that
supports all versions of OpenFlow.
diff --git a/src/python/of12/parse.py b/src/python/of12/parse.py
new file mode 100644
index 0000000..262ca53
--- /dev/null
+++ b/src/python/of12/parse.py
@@ -0,0 +1,338 @@
+"""
+OpenFlow message parsing functions
+"""
+
+import sys
+import logging
+import message
+from match_list import match_list
+import match
+#from error import *
+#from action import *
+#from action_list import action_list
+import cstruct as ofp
+
+
+
+
+try:
+ logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
+ from scapy.all import *
+ #load_contrib("mpls")
+ #TODO This should really be in scapy!
+ #bind_layers(MPLS, MPLS, s=0)
+except ImportError:
+ sys.exit("Need to install scapy for packet parsing")
+
+"""
+of_message.py
+Contains wrapper functions and classes for the of_message namespace
+that are generated by hand. It includes the rest of the wrapper
+function information into the of_message namespace
+"""
+
+parse_logger = logging.getLogger("parse")
+#parse_logger.setLevel(logging.DEBUG)
+
+# These message types are subclassed
+msg_type_subclassed = [
+ ofp.OFPT_STATS_REQUEST,
+ ofp.OFPT_STATS_REPLY,
+ ofp.OFPT_ERROR
+]
+
+# Maps from sub-types to classes
+stats_reply_to_class_map = {
+ ofp.OFPST_DESC : message.desc_stats_reply,
+ ofp.OFPST_FLOW : message.flow_stats_reply,
+ ofp.OFPST_AGGREGATE : message.aggregate_stats_reply,
+ ofp.OFPST_TABLE : message.table_stats_reply,
+ ofp.OFPST_PORT : message.port_stats_reply,
+ ofp.OFPST_QUEUE : message.queue_stats_reply,
+ ofp.OFPST_GROUP : message.group_stats_reply,
+ ofp.OFPST_GROUP_DESC : message.group_desc_stats_reply
+# ofp.OFPST_EXPERIMENTER
+}
+
+stats_request_to_class_map = {
+ ofp.OFPST_DESC : message.desc_stats_request,
+ ofp.OFPST_FLOW : message.flow_stats_request,
+ ofp.OFPST_AGGREGATE : message.aggregate_stats_request,
+ ofp.OFPST_TABLE : message.table_stats_request,
+ ofp.OFPST_PORT : message.port_stats_request,
+ ofp.OFPST_QUEUE : message.queue_stats_request,
+ ofp.OFPST_GROUP : message.group_stats_request,
+ ofp.OFPST_GROUP_DESC : message.group_desc_stats_request
+# ofp.OFPST_EXPERIMENTER
+}
+
+error_to_class_map = {
+ ofp.OFPET_HELLO_FAILED : message.hello_failed_error_msg,
+ ofp.OFPET_BAD_REQUEST : message.bad_request_error_msg,
+ ofp.OFPET_BAD_ACTION : message.bad_action_error_msg,
+ ofp.OFPET_BAD_INSTRUCTION : message.bad_instruction_error_msg,
+ ofp.OFPET_BAD_MATCH : message.bad_match_error_msg,
+ ofp.OFPET_FLOW_MOD_FAILED : message.flow_mod_failed_error_msg,
+ ofp.OFPET_GROUP_MOD_FAILED : message.group_mod_failed_error_msg,
+ ofp.OFPET_PORT_MOD_FAILED : message.port_mod_failed_error_msg,
+ ofp.OFPET_TABLE_MOD_FAILED : message.table_mod_failed_error_msg,
+ ofp.OFPET_QUEUE_OP_FAILED : message.queue_op_failed_error_msg,
+ ofp.OFPET_SWITCH_CONFIG_FAILED : message.switch_config_failed_error_msg
+}
+
+# Map from header type value to the underlieing message class
+msg_type_to_class_map = {
+ ofp.OFPT_HELLO : message.hello,
+ ofp.OFPT_ERROR : message.error,
+ ofp.OFPT_ECHO_REQUEST : message.echo_request,
+ ofp.OFPT_ECHO_REPLY : message.echo_reply,
+ ofp.OFPT_EXPERIMENTER : message.experimenter,
+ ofp.OFPT_FEATURES_REQUEST : message.features_request,
+ ofp.OFPT_FEATURES_REPLY : message.features_reply,
+ ofp.OFPT_GET_CONFIG_REQUEST : message.get_config_request,
+ ofp.OFPT_GET_CONFIG_REPLY : message.get_config_reply,
+ ofp.OFPT_SET_CONFIG : message.set_config,
+ ofp.OFPT_PACKET_IN : message.packet_in,
+ ofp.OFPT_FLOW_REMOVED : message.flow_removed,
+ ofp.OFPT_PORT_STATUS : message.port_status,
+ ofp.OFPT_PACKET_OUT : message.packet_out,
+ ofp.OFPT_FLOW_MOD : message.flow_mod,
+ ofp.OFPT_GROUP_MOD : message.group_mod,
+ ofp.OFPT_PORT_MOD : message.port_mod,
+ ofp.OFPT_TABLE_MOD : message.table_mod,
+ ofp.OFPT_STATS_REQUEST : message.stats_request,
+ ofp.OFPT_STATS_REPLY : message.stats_reply,
+ ofp.OFPT_BARRIER_REQUEST : message.barrier_request,
+ ofp.OFPT_BARRIER_REPLY : message.barrier_reply,
+ ofp.OFPT_QUEUE_GET_CONFIG_REQUEST : message.queue_get_config_request,
+ ofp.OFPT_QUEUE_GET_CONFIG_REPLY : message.queue_get_config_reply,
+}
+
+def _of_message_to_object(binary_string):
+ """
+ Map a binary string to the corresponding class.
+
+ Appropriately resolves subclasses
+ """
+ hdr = ofp.ofp_header()
+ hdr.unpack(binary_string)
+ # FIXME: Add error detection
+ if not hdr.type in msg_type_subclassed:
+ return msg_type_to_class_map[hdr.type]()
+ if hdr.type == ofp.OFPT_STATS_REQUEST:
+ sub_hdr = ofp.ofp_stats_request()
+ sub_hdr.unpack(binary_string[ofp.OFP_HEADER_BYTES:])
+ try:
+ obj = stats_request_to_class_map[sub_hdr.type]()
+ except LookupError:
+ obj = None
+ return obj
+ elif hdr.type == ofp.OFPT_STATS_REPLY:
+ sub_hdr = ofp.ofp_stats_reply()
+ sub_hdr.unpack(binary_string[ofp.OFP_HEADER_BYTES:])
+ try:
+ obj = stats_reply_to_class_map[sub_hdr.type]()
+ except LookupError:
+ obj = None
+ return obj
+ elif hdr.type == ofp.OFPT_ERROR:
+ sub_hdr = ofp.ofp_error_msg()
+ sub_hdr.unpack(binary_string[ofp.OFP_HEADER_BYTES:])
+ return error_to_class_map[sub_hdr.type]()
+ else:
+ parse_logger.error("Cannot parse pkt to message")
+ return None
+
+def of_message_parse(binary_string, raw=False):
+ """
+ Parse an OpenFlow packet
+
+ Parses a raw OpenFlow packet into a Python class, with class
+ members fully populated.
+
+ @param binary_string The packet (string) to be parsed
+ @param raw If true, interpret the packet as an L2 packet. Not
+ yet supported.
+ @return An object of some message class or None if fails
+ Note that any data beyond that parsed is not returned
+
+ """
+
+ if raw:
+ parse_logger.error("raw packet message parsing not supported")
+ return None
+
+ obj = _of_message_to_object(binary_string)
+ if obj:
+ obj.unpack(binary_string)
+ return obj
+
+
+def of_header_parse(binary_string, raw=False):
+ """
+ Parse only the header from an OpenFlow packet
+
+ Parses the header from a raw OpenFlow packet into a
+ an ofp_header Python class.
+
+ @param binary_string The packet (string) to be parsed
+ @param raw If true, interpret the packet as an L2 packet. Not
+ yet supported.
+ @return An ofp_header object
+
+ """
+
+ if raw:
+ parse_logger.error("raw packet message parsing not supported")
+ return None
+
+ hdr = ofp.ofp_header()
+ hdr.unpack(binary_string)
+
+ return hdr
+
+map_wc_field_to_match_member = {
+ 'OFPFW_DL_VLAN' : 'dl_vlan',
+ 'OFPFW_DL_SRC' : 'dl_src',
+ 'OFPFW_DL_DST' : 'dl_dst',
+ 'OFPFW_DL_TYPE' : 'dl_type',
+ 'OFPFW_NW_PROTO' : 'nw_proto',
+ 'OFPFW_TP_SRC' : 'tp_src',
+ 'OFPFW_TP_DST' : 'tp_dst',
+ 'OFPFW_NW_SRC_SHIFT' : 'nw_src_shift',
+ 'OFPFW_NW_SRC_BITS' : 'nw_src_bits',
+ 'OFPFW_NW_SRC_MASK' : 'nw_src_mask',
+ 'OFPFW_NW_SRC_ALL' : 'nw_src_all',
+ 'OFPFW_NW_DST_SHIFT' : 'nw_dst_shift',
+ 'OFPFW_NW_DST_BITS' : 'nw_dst_bits',
+ 'OFPFW_NW_DST_MASK' : 'nw_dst_mask',
+ 'OFPFW_NW_DST_ALL' : 'nw_dst_all',
+ 'OFPFW_DL_VLAN_PCP' : 'dl_vlan_pcp',
+ 'OFPFW_NW_TOS' : 'nw_tos'
+}
+
+
+def parse_mac(mac_str):
+ """
+ Parse a MAC address
+
+ Parse a MAC address ':' separated string of hex digits to an
+ array of integer values. '00:d0:05:5d:24:00' => [0, 208, 5, 93, 36, 0]
+ @param mac_str The string to convert
+ @return Array of 6 integer values
+ """
+ return map(lambda val:eval("0x" + val), mac_str.split(":"))
+
+def parse_ip(ip_str):
+ """
+ Parse an IP address
+
+ Parse an IP address '.' separated string of decimal digits to an
+ host ordered integer. '172.24.74.77' =>
+ @param ip_str The string to convert
+ @return Integer value
+ """
+ array = map(lambda val:eval(val),ip_str.split("."))
+ val = 0
+ for a in array:
+ val <<= 8
+ val += a
+ return val
+
+def packet_to_flow_match(packet):
+ """
+ Create a flow match that matches packet with the given wildcards
+
+ @param packet The packet to use as a flow template
+ @param pkt_format Currently only L2 is supported. Will indicate the
+ overall packet type for parsing
+ @return An ofp_match object if successful. None if format is not
+ recognized. The wildcards of the match will be cleared for the
+ values extracted from the packet.
+
+ @todo check min length of packet
+ @todo Check if packet is other than L2 format
+ @todo implement other fields covered by OpenFlow 1.2
+ """
+ match_ls = match_list()
+
+ if Ether in packet:
+ ether = packet[Ether]
+ eth_type = match.eth_type(ether.type)
+ eth_dst = match.eth_dst(parse_mac(ether.dst))
+ eth_src = match.eth_src(parse_mac(ether.src))
+ match_ls.add(eth_type)
+ match_ls.add(eth_dst)
+ match_ls.add(eth_src)
+ else:
+ return match_ls
+
+ if Dot1Q in packet:
+ #TODO: nicer way to get last vlan tag?
+ vlan = packet[Dot1Q:0]
+ vlan_vid = match.vlan_vid(vlan.vlan)
+ vlan_pcp = match.vlan_pcp(vlan.prio)
+ match_ls.add(vlan_vid)
+ match_ls.add(vlan_pcp)
+ vlan_pl = vlan.payload
+ while vlan_pl is not None and vlan_pl.name == Dot1Q.name:
+ vlan = vlan_pl
+ vlan_pl = vlan.payload
+ #We need to overwrite the already
+ # inserted eth_type
+ eth_index = match.tlvs.index()
+ eth_type = match.eth_type(vlan.type)
+ match_ls.tlvs.insert(vlan.type,eth_index)
+ #TODO ARP
+
+ if MPLS in packet:
+ mpls = packet[MPLS:0]
+ mpls_label = match.mpls_label(mpls.label)
+ mpls_tc = match.mpls_tc(mpls.cos)
+ match_ls.add(mpls_label)
+ match_ls.add(mpls_tc)
+ return match_ls
+
+ if IP in packet:
+ ip = packet[IP]
+ ipv4_src = match.ipv4_src(parse_ip(ip.src))
+ ipv4_dst = match.ipv4_dst(parse_ip(ip.dst))
+ ip_dscp = match.ip_dscp(ip.tos >> 2)
+ ip_ecn = match.ip_ecn(ip.tos & 0x03)
+ match_ls.add(ipv4_src)
+ match_ls.add(ipv4_dst)
+ match_ls.add(ip_dscp)
+ match_ls.add(ip_ecn)
+ else:
+ return match_ls
+
+ if TCP in packet:
+ tcp = packet[TCP]
+ ip_proto = match.ip_proto(6)
+ tcp_src = match.tcp_src(tcp.sport)
+ tcp_dst = match.tcp_dst(tcp.dport)
+ match_ls.add(ip_proto)
+ match_ls.add(tcp_src)
+ match_ls.add(tcp_dst)
+ return match_ls
+
+ if UDP in packet:
+ udp = packet[UDP]
+ ip_proto = match.ip_proto(17)
+ udp_src = match.tcp_src(udp.sport)
+ udp_dst = match.tcp_dst(udp.dport)
+ match_ls.add(ip_proto)
+ match_ls.add(udp_src)
+ match_ls.add(udp_dst)
+ returnmatch_ls
+
+ if ICMP in packet:
+ icmp = packet[ICMP]
+ ip_proto = match.ip_proto(1)
+ icmp_type = match.icmp_type(icmp.type)
+ icmp_code = match.icmp_code(icmp.code)
+ match_ls.add(icmp_type)
+ match_ls.add(icmp_code)
+ return match_ls
+
+ return match_ls