blob: 262ca53fb4e1de73873b3c350f7fec9cf409dae6 [file] [log] [blame]
Rich Lane629393f2013-01-10 15:37:33 -08001"""
2OpenFlow message parsing functions
3"""
4
5import sys
6import logging
7import message
8from match_list import match_list
9import match
10#from error import *
11#from action import *
12#from action_list import action_list
13import cstruct as ofp
14
15
16
17
18try:
19 logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
20 from scapy.all import *
21 #load_contrib("mpls")
22 #TODO This should really be in scapy!
23 #bind_layers(MPLS, MPLS, s=0)
24except ImportError:
25 sys.exit("Need to install scapy for packet parsing")
26
27"""
28of_message.py
29Contains wrapper functions and classes for the of_message namespace
30that are generated by hand. It includes the rest of the wrapper
31function information into the of_message namespace
32"""
33
34parse_logger = logging.getLogger("parse")
35#parse_logger.setLevel(logging.DEBUG)
36
37# These message types are subclassed
38msg_type_subclassed = [
39 ofp.OFPT_STATS_REQUEST,
40 ofp.OFPT_STATS_REPLY,
41 ofp.OFPT_ERROR
42]
43
44# Maps from sub-types to classes
45stats_reply_to_class_map = {
46 ofp.OFPST_DESC : message.desc_stats_reply,
47 ofp.OFPST_FLOW : message.flow_stats_reply,
48 ofp.OFPST_AGGREGATE : message.aggregate_stats_reply,
49 ofp.OFPST_TABLE : message.table_stats_reply,
50 ofp.OFPST_PORT : message.port_stats_reply,
51 ofp.OFPST_QUEUE : message.queue_stats_reply,
52 ofp.OFPST_GROUP : message.group_stats_reply,
53 ofp.OFPST_GROUP_DESC : message.group_desc_stats_reply
54# ofp.OFPST_EXPERIMENTER
55}
56
57stats_request_to_class_map = {
58 ofp.OFPST_DESC : message.desc_stats_request,
59 ofp.OFPST_FLOW : message.flow_stats_request,
60 ofp.OFPST_AGGREGATE : message.aggregate_stats_request,
61 ofp.OFPST_TABLE : message.table_stats_request,
62 ofp.OFPST_PORT : message.port_stats_request,
63 ofp.OFPST_QUEUE : message.queue_stats_request,
64 ofp.OFPST_GROUP : message.group_stats_request,
65 ofp.OFPST_GROUP_DESC : message.group_desc_stats_request
66# ofp.OFPST_EXPERIMENTER
67}
68
69error_to_class_map = {
70 ofp.OFPET_HELLO_FAILED : message.hello_failed_error_msg,
71 ofp.OFPET_BAD_REQUEST : message.bad_request_error_msg,
72 ofp.OFPET_BAD_ACTION : message.bad_action_error_msg,
73 ofp.OFPET_BAD_INSTRUCTION : message.bad_instruction_error_msg,
74 ofp.OFPET_BAD_MATCH : message.bad_match_error_msg,
75 ofp.OFPET_FLOW_MOD_FAILED : message.flow_mod_failed_error_msg,
76 ofp.OFPET_GROUP_MOD_FAILED : message.group_mod_failed_error_msg,
77 ofp.OFPET_PORT_MOD_FAILED : message.port_mod_failed_error_msg,
78 ofp.OFPET_TABLE_MOD_FAILED : message.table_mod_failed_error_msg,
79 ofp.OFPET_QUEUE_OP_FAILED : message.queue_op_failed_error_msg,
80 ofp.OFPET_SWITCH_CONFIG_FAILED : message.switch_config_failed_error_msg
81}
82
83# Map from header type value to the underlieing message class
84msg_type_to_class_map = {
85 ofp.OFPT_HELLO : message.hello,
86 ofp.OFPT_ERROR : message.error,
87 ofp.OFPT_ECHO_REQUEST : message.echo_request,
88 ofp.OFPT_ECHO_REPLY : message.echo_reply,
89 ofp.OFPT_EXPERIMENTER : message.experimenter,
90 ofp.OFPT_FEATURES_REQUEST : message.features_request,
91 ofp.OFPT_FEATURES_REPLY : message.features_reply,
92 ofp.OFPT_GET_CONFIG_REQUEST : message.get_config_request,
93 ofp.OFPT_GET_CONFIG_REPLY : message.get_config_reply,
94 ofp.OFPT_SET_CONFIG : message.set_config,
95 ofp.OFPT_PACKET_IN : message.packet_in,
96 ofp.OFPT_FLOW_REMOVED : message.flow_removed,
97 ofp.OFPT_PORT_STATUS : message.port_status,
98 ofp.OFPT_PACKET_OUT : message.packet_out,
99 ofp.OFPT_FLOW_MOD : message.flow_mod,
100 ofp.OFPT_GROUP_MOD : message.group_mod,
101 ofp.OFPT_PORT_MOD : message.port_mod,
102 ofp.OFPT_TABLE_MOD : message.table_mod,
103 ofp.OFPT_STATS_REQUEST : message.stats_request,
104 ofp.OFPT_STATS_REPLY : message.stats_reply,
105 ofp.OFPT_BARRIER_REQUEST : message.barrier_request,
106 ofp.OFPT_BARRIER_REPLY : message.barrier_reply,
107 ofp.OFPT_QUEUE_GET_CONFIG_REQUEST : message.queue_get_config_request,
108 ofp.OFPT_QUEUE_GET_CONFIG_REPLY : message.queue_get_config_reply,
109}
110
111def _of_message_to_object(binary_string):
112 """
113 Map a binary string to the corresponding class.
114
115 Appropriately resolves subclasses
116 """
117 hdr = ofp.ofp_header()
118 hdr.unpack(binary_string)
119 # FIXME: Add error detection
120 if not hdr.type in msg_type_subclassed:
121 return msg_type_to_class_map[hdr.type]()
122 if hdr.type == ofp.OFPT_STATS_REQUEST:
123 sub_hdr = ofp.ofp_stats_request()
124 sub_hdr.unpack(binary_string[ofp.OFP_HEADER_BYTES:])
125 try:
126 obj = stats_request_to_class_map[sub_hdr.type]()
127 except LookupError:
128 obj = None
129 return obj
130 elif hdr.type == ofp.OFPT_STATS_REPLY:
131 sub_hdr = ofp.ofp_stats_reply()
132 sub_hdr.unpack(binary_string[ofp.OFP_HEADER_BYTES:])
133 try:
134 obj = stats_reply_to_class_map[sub_hdr.type]()
135 except LookupError:
136 obj = None
137 return obj
138 elif hdr.type == ofp.OFPT_ERROR:
139 sub_hdr = ofp.ofp_error_msg()
140 sub_hdr.unpack(binary_string[ofp.OFP_HEADER_BYTES:])
141 return error_to_class_map[sub_hdr.type]()
142 else:
143 parse_logger.error("Cannot parse pkt to message")
144 return None
145
146def of_message_parse(binary_string, raw=False):
147 """
148 Parse an OpenFlow packet
149
150 Parses a raw OpenFlow packet into a Python class, with class
151 members fully populated.
152
153 @param binary_string The packet (string) to be parsed
154 @param raw If true, interpret the packet as an L2 packet. Not
155 yet supported.
156 @return An object of some message class or None if fails
157 Note that any data beyond that parsed is not returned
158
159 """
160
161 if raw:
162 parse_logger.error("raw packet message parsing not supported")
163 return None
164
165 obj = _of_message_to_object(binary_string)
166 if obj:
167 obj.unpack(binary_string)
168 return obj
169
170
171def of_header_parse(binary_string, raw=False):
172 """
173 Parse only the header from an OpenFlow packet
174
175 Parses the header from a raw OpenFlow packet into a
176 an ofp_header Python class.
177
178 @param binary_string The packet (string) to be parsed
179 @param raw If true, interpret the packet as an L2 packet. Not
180 yet supported.
181 @return An ofp_header object
182
183 """
184
185 if raw:
186 parse_logger.error("raw packet message parsing not supported")
187 return None
188
189 hdr = ofp.ofp_header()
190 hdr.unpack(binary_string)
191
192 return hdr
193
194map_wc_field_to_match_member = {
195 'OFPFW_DL_VLAN' : 'dl_vlan',
196 'OFPFW_DL_SRC' : 'dl_src',
197 'OFPFW_DL_DST' : 'dl_dst',
198 'OFPFW_DL_TYPE' : 'dl_type',
199 'OFPFW_NW_PROTO' : 'nw_proto',
200 'OFPFW_TP_SRC' : 'tp_src',
201 'OFPFW_TP_DST' : 'tp_dst',
202 'OFPFW_NW_SRC_SHIFT' : 'nw_src_shift',
203 'OFPFW_NW_SRC_BITS' : 'nw_src_bits',
204 'OFPFW_NW_SRC_MASK' : 'nw_src_mask',
205 'OFPFW_NW_SRC_ALL' : 'nw_src_all',
206 'OFPFW_NW_DST_SHIFT' : 'nw_dst_shift',
207 'OFPFW_NW_DST_BITS' : 'nw_dst_bits',
208 'OFPFW_NW_DST_MASK' : 'nw_dst_mask',
209 'OFPFW_NW_DST_ALL' : 'nw_dst_all',
210 'OFPFW_DL_VLAN_PCP' : 'dl_vlan_pcp',
211 'OFPFW_NW_TOS' : 'nw_tos'
212}
213
214
215def parse_mac(mac_str):
216 """
217 Parse a MAC address
218
219 Parse a MAC address ':' separated string of hex digits to an
220 array of integer values. '00:d0:05:5d:24:00' => [0, 208, 5, 93, 36, 0]
221 @param mac_str The string to convert
222 @return Array of 6 integer values
223 """
224 return map(lambda val:eval("0x" + val), mac_str.split(":"))
225
226def parse_ip(ip_str):
227 """
228 Parse an IP address
229
230 Parse an IP address '.' separated string of decimal digits to an
231 host ordered integer. '172.24.74.77' =>
232 @param ip_str The string to convert
233 @return Integer value
234 """
235 array = map(lambda val:eval(val),ip_str.split("."))
236 val = 0
237 for a in array:
238 val <<= 8
239 val += a
240 return val
241
242def packet_to_flow_match(packet):
243 """
244 Create a flow match that matches packet with the given wildcards
245
246 @param packet The packet to use as a flow template
247 @param pkt_format Currently only L2 is supported. Will indicate the
248 overall packet type for parsing
249 @return An ofp_match object if successful. None if format is not
250 recognized. The wildcards of the match will be cleared for the
251 values extracted from the packet.
252
253 @todo check min length of packet
254 @todo Check if packet is other than L2 format
255 @todo implement other fields covered by OpenFlow 1.2
256 """
257 match_ls = match_list()
258
259 if Ether in packet:
260 ether = packet[Ether]
261 eth_type = match.eth_type(ether.type)
262 eth_dst = match.eth_dst(parse_mac(ether.dst))
263 eth_src = match.eth_src(parse_mac(ether.src))
264 match_ls.add(eth_type)
265 match_ls.add(eth_dst)
266 match_ls.add(eth_src)
267 else:
268 return match_ls
269
270 if Dot1Q in packet:
271 #TODO: nicer way to get last vlan tag?
272 vlan = packet[Dot1Q:0]
273 vlan_vid = match.vlan_vid(vlan.vlan)
274 vlan_pcp = match.vlan_pcp(vlan.prio)
275 match_ls.add(vlan_vid)
276 match_ls.add(vlan_pcp)
277 vlan_pl = vlan.payload
278 while vlan_pl is not None and vlan_pl.name == Dot1Q.name:
279 vlan = vlan_pl
280 vlan_pl = vlan.payload
281 #We need to overwrite the already
282 # inserted eth_type
283 eth_index = match.tlvs.index()
284 eth_type = match.eth_type(vlan.type)
285 match_ls.tlvs.insert(vlan.type,eth_index)
286 #TODO ARP
287
288 if MPLS in packet:
289 mpls = packet[MPLS:0]
290 mpls_label = match.mpls_label(mpls.label)
291 mpls_tc = match.mpls_tc(mpls.cos)
292 match_ls.add(mpls_label)
293 match_ls.add(mpls_tc)
294 return match_ls
295
296 if IP in packet:
297 ip = packet[IP]
298 ipv4_src = match.ipv4_src(parse_ip(ip.src))
299 ipv4_dst = match.ipv4_dst(parse_ip(ip.dst))
300 ip_dscp = match.ip_dscp(ip.tos >> 2)
301 ip_ecn = match.ip_ecn(ip.tos & 0x03)
302 match_ls.add(ipv4_src)
303 match_ls.add(ipv4_dst)
304 match_ls.add(ip_dscp)
305 match_ls.add(ip_ecn)
306 else:
307 return match_ls
308
309 if TCP in packet:
310 tcp = packet[TCP]
311 ip_proto = match.ip_proto(6)
312 tcp_src = match.tcp_src(tcp.sport)
313 tcp_dst = match.tcp_dst(tcp.dport)
314 match_ls.add(ip_proto)
315 match_ls.add(tcp_src)
316 match_ls.add(tcp_dst)
317 return match_ls
318
319 if UDP in packet:
320 udp = packet[UDP]
321 ip_proto = match.ip_proto(17)
322 udp_src = match.tcp_src(udp.sport)
323 udp_dst = match.tcp_dst(udp.dport)
324 match_ls.add(ip_proto)
325 match_ls.add(udp_src)
326 match_ls.add(udp_dst)
327 returnmatch_ls
328
329 if ICMP in packet:
330 icmp = packet[ICMP]
331 ip_proto = match.ip_proto(1)
332 icmp_type = match.icmp_type(icmp.type)
333 icmp_code = match.icmp_code(icmp.code)
334 match_ls.add(icmp_type)
335 match_ls.add(icmp_code)
336 return match_ls
337
338 return match_ls