blob: 044e5c885791befcad1cdd1ced3b12752891dfde [file] [log] [blame]
Dan Talaycoc901f4d2010-03-07 21:55:45 -08001
Dan Talaycod2ca1032010-03-10 14:40:26 -08002import sys
Dan Talayco92c99122010-06-03 13:53:18 -07003import copy
Dan Talaycod2ca1032010-03-10 14:40:26 -08004
5try:
6 import scapy.all as scapy
7except:
8 try:
9 import scapy as scapy
10 except:
11 sys.exit("Need to install scapy for packet parsing")
Dan Talayco41eae8b2010-03-10 13:57:06 -080012
Dan Talaycoc901f4d2010-03-07 21:55:45 -080013import oftest.controller as controller
14import oftest.cstruct as ofp
15import oftest.message as message
16import oftest.dataplane as dataplane
17import oftest.action as action
Dan Talayco41eae8b2010-03-10 13:57:06 -080018import oftest.parse as parse
Dan Talaycoc901f4d2010-03-07 21:55:45 -080019import logging
20
Dan Talayco551befa2010-07-15 17:05:32 -070021# Some useful defines
22IP_ETHERTYPE = 0x800
23TCP_PROTOCOL = 0x6
24UDP_PROTOCOL = 0x11
25
Dan Talayco41eae8b2010-03-10 13:57:06 -080026def delete_all_flows(ctrl, logger):
27 """
28 Delete all flows on the switch
29 @param ctrl The controller object for the test
30 @param logger Logging object
31 """
32
Dan Talaycoc901f4d2010-03-07 21:55:45 -080033 logger.info("Deleting all flows")
34 msg = message.flow_mod()
35 msg.match.wildcards = ofp.OFPFW_ALL
Dan Talayco41eae8b2010-03-10 13:57:06 -080036 msg.out_port = ofp.OFPP_NONE
Dan Talaycoc901f4d2010-03-07 21:55:45 -080037 msg.command = ofp.OFPFC_DELETE
38 msg.buffer_id = 0xffffffff
Dan Talayco41eae8b2010-03-10 13:57:06 -080039 return ctrl.message_send(msg)
40
41def simple_tcp_packet(pktlen=100,
42 dl_dst='00:01:02:03:04:05',
43 dl_src='00:06:07:08:09:0a',
Tatsuya Yabe460321e2010-05-25 17:50:49 -070044 dl_vlan_enable=False,
45 dl_vlan=0,
46 dl_vlan_pcp=0,
Dan Talayco551befa2010-07-15 17:05:32 -070047 dl_vlan_cfi=0,
Dan Talayco41eae8b2010-03-10 13:57:06 -080048 ip_src='192.168.0.1',
49 ip_dst='192.168.0.2',
Tatsuya Yabe460321e2010-05-25 17:50:49 -070050 ip_tos=0,
Dan Talayco41eae8b2010-03-10 13:57:06 -080051 tcp_sport=1234,
52 tcp_dport=80
53 ):
54 """
55 Return a simple dataplane TCP packet
56
57 Supports a few parameters:
58 @param len Length of packet in bytes w/o CRC
59 @param dl_dst Destinatino MAC
60 @param dl_src Source MAC
Tatsuya Yabe460321e2010-05-25 17:50:49 -070061 @param dl_vlan_enable True if the packet is with vlan, False otherwise
62 @param dl_vlan VLAN ID
63 @param dl_vlan_pcp VLAN priority
Dan Talayco41eae8b2010-03-10 13:57:06 -080064 @param ip_src IP source
65 @param ip_dst IP destination
Tatsuya Yabe460321e2010-05-25 17:50:49 -070066 @param ip_tos IP ToS
Dan Talayco41eae8b2010-03-10 13:57:06 -080067 @param tcp_dport TCP destination port
68 @param ip_sport TCP source port
69
70 Generates a simple TCP request. Users
71 shouldn't assume anything about this packet other than that
72 it is a valid ethernet/IP/TCP frame.
73 """
Dan Talayco551befa2010-07-15 17:05:32 -070074 # Note Dot1Q.id is really CFI
Tatsuya Yabe460321e2010-05-25 17:50:49 -070075 if (dl_vlan_enable):
76 pkt = scapy.Ether(dst=dl_dst, src=dl_src)/ \
Dan Talayco551befa2010-07-15 17:05:32 -070077 scapy.Dot1Q(prio=dl_vlan_pcp, id=dl_vlan_cfi, vlan=dl_vlan)/ \
Tatsuya Yabe460321e2010-05-25 17:50:49 -070078 scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos)/ \
79 scapy.TCP(sport=tcp_sport, dport=tcp_dport)
80 else:
81 pkt = scapy.Ether(dst=dl_dst, src=dl_src)/ \
82 scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos)/ \
83 scapy.TCP(sport=tcp_sport, dport=tcp_dport)
84
Dan Talayco41eae8b2010-03-10 13:57:06 -080085 pkt = pkt/("D" * (pktlen - len(pkt)))
86
87 return pkt
88
Tatsuya Yabeb8fb3c32010-06-14 15:48:36 -070089def simple_icmp_packet(pktlen=60,
90 dl_dst='00:01:02:03:04:05',
91 dl_src='00:06:07:08:09:0a',
92 dl_vlan_enable=False,
93 dl_vlan=0,
94 dl_vlan_pcp=0,
95 ip_src='192.168.0.1',
96 ip_dst='192.168.0.2',
97 ip_tos=0,
98 icmp_type=8,
99 icmp_code=0
100 ):
101 """
102 Return a simple ICMP packet
103
104 Supports a few parameters:
105 @param len Length of packet in bytes w/o CRC
106 @param dl_dst Destinatino MAC
107 @param dl_src Source MAC
108 @param dl_vlan_enable True if the packet is with vlan, False otherwise
109 @param dl_vlan VLAN ID
110 @param dl_vlan_pcp VLAN priority
111 @param ip_src IP source
112 @param ip_dst IP destination
113 @param ip_tos IP ToS
114 @param icmp_type ICMP type
115 @param icmp_code ICMP code
116
117 Generates a simple ICMP ECHO REQUEST. Users
118 shouldn't assume anything about this packet other than that
119 it is a valid ethernet/ICMP frame.
120 """
121 if (dl_vlan_enable):
122 pkt = scapy.Ether(dst=dl_dst, src=dl_src)/ \
123 scapy.Dot1Q(prio=dl_vlan_pcp, id=0, vlan=dl_vlan)/ \
124 scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos)/ \
125 scapy.ICMP(type=icmp_type, code=icmp_code)
126 else:
127 pkt = scapy.Ether(dst=dl_dst, src=dl_src)/ \
128 scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos)/ \
129 scapy.ICMP(type=icmp_type, code=icmp_code)
130
131 pkt = pkt/("0" * (pktlen - len(pkt)))
132
133 return pkt
134
Dan Talayco41eae8b2010-03-10 13:57:06 -0800135def do_barrier(ctrl):
136 b = message.barrier_request()
137 ctrl.transact(b)
Dan Talayco92c99122010-06-03 13:53:18 -0700138
139
140def port_config_get(controller, port_no, logger):
141 """
142 Get a port's configuration
143
144 Gets the switch feature configuration and grabs one port's
145 configuration
146
147 @returns (hwaddr, config, advert) The hwaddress, configuration and
148 advertised values
149 """
150 request = message.features_request()
151 reply, pkt = controller.transact(request, timeout=2)
152 logger.debug(reply.show())
153 if reply is None:
154 logger.warn("Get feature request failed")
155 return None, None, None
156 for idx in range(len(reply.ports)):
157 if reply.ports[idx].port_no == port_no:
158 return (reply.ports[idx].hw_addr, reply.ports[idx].config,
159 reply.ports[idx].advertised)
160
161 logger.warn("Did not find port number for port config")
162 return None, None, None
163
164def port_config_set(controller, port_no, config, mask, logger):
165 """
166 Set the port configuration according the given parameters
167
168 Gets the switch feature configuration and updates one port's
169 configuration value according to config and mask
170 """
171 logger.info("Setting port " + str(port_no) + " to config " + str(config))
172 request = message.features_request()
173 reply, pkt = controller.transact(request, timeout=2)
174 if reply is None:
175 return -1
176 logger.debug(reply.show())
177 for idx in range(len(reply.ports)):
178 if reply.ports[idx].port_no == port_no:
179 break
180 if idx >= len(reply.ports):
181 return -1
182 mod = message.port_mod()
183 mod.port_no = port_no
184 mod.hw_addr = reply.ports[idx].hw_addr
185 mod.config = config
186 mod.mask = mask
187 mod.advertise = reply.ports[idx].advertised
188 rv = controller.message_send(mod)
189 return rv
190
191def receive_pkt_check(dataplane, pkt, yes_ports, no_ports, assert_if, logger):
192 """
193 Check for proper receive packets across all ports
194 @param dataplane The dataplane object
195 @param pkt Expected packet; may be None if yes_ports is empty
196 @param yes_ports Set or list of ports that should recieve packet
197 @param no_ports Set or list of ports that should not receive packet
198 @param assert_if Object that implements assertXXX
199 """
200 for ofport in yes_ports:
201 logger.debug("Checking for pkt on port " + str(ofport))
202 (rcv_port, rcv_pkt, pkt_time) = dataplane.poll(
203 port_number=ofport, timeout=1)
204 assert_if.assertTrue(rcv_pkt is not None,
205 "Did not receive pkt on " + str(ofport))
206 assert_if.assertEqual(str(pkt), str(rcv_pkt),
207 "Response packet does not match send packet " +
208 "on port " + str(ofport))
209
210 for ofport in no_ports:
211 logger.debug("Negative check for pkt on port " + str(ofport))
212 (rcv_port, rcv_pkt, pkt_time) = dataplane.poll(
213 port_number=ofport, timeout=1)
214 assert_if.assertTrue(rcv_pkt is None,
215 "Unexpected pkt on port " + str(ofport))
Dan Talayco551befa2010-07-15 17:05:32 -0700216
217
218def receive_pkt_verify(parent, egr_port, exp_pkt):
219 """
220 Receive a packet and verify it matches an expected value
221
222 parent must implement dataplane, assertTrue and assertEqual
223 """
224 (rcv_port, rcv_pkt, pkt_time) = parent.dataplane.poll(port_number=egr_port,
225 timeout=1)
226 if rcv_pkt is None:
227 parent.logger.error("ERROR: No packet received from " + str(egr_port))
228
229 parent.assertTrue(rcv_pkt is not None,
230 "Did not receive packet port " + str(egr_port))
231 parent.logger.debug("Packet len " + str(len(rcv_pkt)) + " in on " +
232 str(rcv_port))
233
234 if str(exp_pkt) != str(rcv_pkt):
235 parent.logger.error("ERROR: Packet match failed.")
236 parent.logger.debug("Expected len " + str(len(exp_pkt)) + ": "
237 + str(exp_pkt).encode('hex'))
238 parent.logger.debug("Received len " + str(len(rcv_pkt)) + ": "
239 + str(rcv_pkt).encode('hex'))
240 parent.assertEqual(str(exp_pkt), str(rcv_pkt),
241 "Packet match error on port " + str(egr_port))
242
243def match_verify(parent, req_match, res_match):
244 """
245 Verify flow matches agree; if they disagree, report where
246
247 parent must implement assertEqual
248 Use str() to ensure content is compared and not pointers
249 """
250
251 parent.assertEqual(req_match.wildcards, res_match.wildcards,
252 'Match failed: wildcards: ' + hex(req_match.wildcards) +
253 " != " + hex(res_match.wildcards))
254 parent.assertEqual(req_match.in_port, res_match.in_port,
255 'Match failed: in_port: ' + str(req_match.in_port) +
256 " != " + str(res_match.in_port))
257 parent.assertEqual(str(req_match.dl_src), str(res_match.dl_src),
258 'Match failed: dl_src: ' + str(req_match.dl_src) +
259 " != " + str(res_match.dl_src))
260 parent.assertEqual(str(req_match.dl_dst), str(res_match.dl_dst),
261 'Match failed: dl_dst: ' + str(req_match.dl_dst) +
262 " != " + str(res_match.dl_dst))
263 parent.assertEqual(req_match.dl_vlan, res_match.dl_vlan,
264 'Match failed: dl_vlan: ' + str(req_match.dl_vlan) +
265 " != " + str(res_match.dl_vlan))
266 parent.assertEqual(req_match.dl_vlan_pcp, res_match.dl_vlan_pcp,
267 'Match failed: dl_vlan_pcp: ' +
268 str(req_match.dl_vlan_pcp) + " != " +
269 str(res_match.dl_vlan_pcp))
270 parent.assertEqual(req_match.dl_type, res_match.dl_type,
271 'Match failed: dl_type: ' + str(req_match.dl_type) +
272 " != " + str(res_match.dl_type))
273
274 if (not(req_match.wildcards & ofp.OFPFW_DL_TYPE)
275 and (req_match.dl_type == IP_ETHERTYPE)):
276 parent.assertEqual(req_match.nw_tos, res_match.nw_tos,
277 'Match failed: nw_tos: ' + str(req_match.nw_tos) +
278 " != " + str(res_match.nw_tos))
279 parent.assertEqual(req_match.nw_proto, res_match.nw_proto,
280 'Match failed: nw_proto: ' + str(req_match.nw_proto) +
281 " != " + str(res_match.nw_proto))
282 parent.assertEqual(req_match.nw_src, res_match.nw_src,
283 'Match failed: nw_src: ' + str(req_match.nw_src) +
284 " != " + str(res_match.nw_src))
285 parent.assertEqual(req_match.nw_dst, res_match.nw_dst,
286 'Match failed: nw_dst: ' + str(req_match.nw_dst) +
287 " != " + str(res_match.nw_dst))
288
289 if (not(req_match.wildcards & ofp.OFPFW_NW_PROTO)
290 and ((req_match.nw_proto == TCP_PROTOCOL)
291 or (req_match.nw_proto == UDP_PROTOCOL))):
292 parent.assertEqual(req_match.tp_src, res_match.tp_src,
293 'Match failed: tp_src: ' +
294 str(req_match.tp_src) +
295 " != " + str(res_match.tp_src))
296 parent.assertEqual(req_match.tp_dst, res_match.tp_dst,
297 'Match failed: tp_dst: ' +
298 str(req_match.tp_dst) +
299 " != " + str(res_match.tp_dst))
300
301def flow_removed_verify(parent, request=None, pkt_count=-1, byte_count=-1):
302 """
303 Receive a flow removed msg and verify it matches expected
304
305 @params parent Must implement controller, assertEqual
306 @param pkt_count If >= 0, verify packet count
307 @param byte_count If >= 0, verify byte count
308 """
309 (response, raw) = parent.controller.poll(ofp.OFPT_FLOW_REMOVED, 2)
310 parent.assertTrue(response is not None, 'No flow removed message received')
311
312 if request is None:
313 return
314
315 parent.assertEqual(request.cookie, response.cookie,
316 "Flow removed cookie error: " +
317 hex(request.cookie) + " != " + hex(response.cookie))
318
319 req_match = request.match
320 res_match = response.match
321 verifyMatchField(req_match, res_match)
322
323 if (req_match.wildcards != 0):
324 parent.assertEqual(request.priority, response.priority,
325 'Flow remove prio mismatch: ' +
326 str(request,priority) + " != " +
327 str(response.priority))
328 parent.assertEqual(response.reason, ofp.OFPRR_HARD_TIMEOUT,
329 'Flow remove reason is not HARD TIMEOUT:' +
330 str(response.reason))
331 if pkt_count >= 0:
332 parent.assertEqual(response.packet_count, pkt_count,
333 'Flow removed failed, packet count: ' +
334 str(response.packet_count) + " != " +
335 str(pkt_count))
336 if byte_count >= 0:
337 parent.assertEqual(response.byte_count, byte_count,
338 'Flow removed failed, byte count: ' +
339 str(response.byte_count) + " != " +
340 str(byte_count))
341
342def flow_msg_create(parent, pkt, ing_port=None, action_list=None, wildcards=0,
343 egr_port=None, egr_queue=None, check_expire=False):
344 """
345 Create a flow message
346
347 Match on packet with given wildcards.
348 See flow_match_test for other parameter descriptoins
349 @param egr_queue if not None, make the output an enqueue action
350 """
351 match = parse.packet_to_flow_match(pkt)
352 parent.assertTrue(match is not None, "Flow match from pkt failed")
353 match.wildcards = wildcards
354 match.in_port = ing_port
355
356 request = message.flow_mod()
357 request.match = match
358 request.buffer_id = 0xffffffff
359 if check_expire:
360 request.flags |= ofp.OFPFF_SEND_FLOW_REM
361 request.hard_timeout = 1
362
363 if action_list is not None:
364 for act in action_list:
365 parent.logger.debug("Adding action " + act.show())
366 rv = request.actions.add(act)
367 parent.assertTrue(rv, "Could not add action" + act.show())
368
369 # Set up output/enqueue action if directed
370 if egr_queue is not None:
371 parent.assertTrue(egr_port is not None, "Egress port not set")
372 act = action.action_enqueue()
373 act.port = egr_port
374 act.queue_id = egr_queue
375 rv = request.actions.add(act)
376 parent.assertTrue(rv, "Could not add enqueue action " +
377 str(egr_port) + " Q: " + str(egr_queue))
378 elif egr_port is not None:
379 act = action.action_output()
380 act.port = egr_port
381 rv = request.actions.add(act)
382 parent.assertTrue(rv, "Could not add output action " + str(egr_port))
383
384 parent.logger.debug(request.show())
385
386 return request
387
388def flow_msg_install(parent, request, clear_table=True):
389 """
390 Install a flow mod message in the switch
391
392 @param parent Must implement controller, assertEqual, assertTrue
393 @param request The request, all set to go
394 @param clear_table If true, clear the flow table before installing
395 """
396 if clear_table:
397 parent.logger.debug("Clear flow table")
398 rc = delete_all_flows(parent.controller, parent.logger)
399 parent.assertEqual(rc, 0, "Failed to delete all flows")
400 do_barrier(parent.controller)
401
402 parent.logger.debug("Insert flow")
403 rv = parent.controller.message_send(request)
404 parent.assertTrue(rv != -1, "Error installing flow mod")
405 do_barrier(parent.controller)
406
407def flow_match_test_port_pair(parent, ing_port, egr_port, wildcards=0,
408 dl_vlan=-1, pkt=None, exp_pkt=None,
409 action_list=None, check_expire=False):
410 """
411 Flow match test on single TCP packet
412
413 Run test with packet through switch from ing_port to egr_port
414 See flow_match_test for parameter descriptions
415 """
416
417 parent.logger.info("Pkt match test: " + str(ing_port) + " to " + str(egr_port))
418 parent.logger.debug(" WC: " + hex(wildcards) + " vlan: " + str(dl_vlan) +
419 " exp: " + str(check_expire))
420 if pkt is None:
421 pkt = simple_tcp_packet(dl_vlan_enable=(dl_vlan >= 0), dl_vlan=dl_vlan)
422
423 request = flow_msg_create(parent, pkt, ing_port=ing_port,
424 wildcards=wildcards, egr_port=egr_port,
425 action_list=action_list)
426
427 flow_msg_install(parent, request)
428
429 parent.logger.debug("Send packet: " + str(ing_port) + " to " + str(egr_port))
430 parent.dataplane.send(ing_port, str(pkt))
431
432 if exp_pkt is None:
433 exp_pkt = pkt
434 receive_pkt_verify(parent, egr_port, exp_pkt)
435
436 if check_expire:
437 #@todo Not all HW supports both pkt and byte counters
438 flow_removed_verify(parent, request, pkt_count=1, byte_count=len(pkt))
439
440def flow_match_test(parent, port_map, wildcards=0, dl_vlan=-1, pkt=None,
441 exp_pkt=None, action_list=None, check_expire=False,
442 max_test=0):
443 """
444 Run flow_match_test_port_pair on all port pairs
445
446 @param max_test If > 0 no more than this number of tests are executed.
447 @param parent Must implement controller, dataplane, assertTrue, assertEqual
448 and logger
449 @param pkt If not None, use this packet for ingress
450 @param wildcards For flow match entry
451 @param dl_vlan If not -1, and pkt is not None, create a pkt w/ VLAN tag
452 @param exp_pkt If not None, use this as the expected output pkt; els use pkt
453 @param action_list Additional actions to add to flow mod
454 @param check_expire Check for flow expiration message
455 """
456 of_ports = port_map.keys()
457 of_ports.sort()
458 parent.assertTrue(len(of_ports) > 1, "Not enough ports for test")
459 test_count = 0
460
461 for ing_idx in range(len(of_ports)):
462 ingress_port = of_ports[ing_idx]
463 for egr_idx in range(len(of_ports)):
464 if egr_idx == ing_idx:
465 continue
466 egress_port = of_ports[egr_idx]
467 flow_match_test_port_pair(parent, ingress_port, egress_port,
468 dl_vlan=dl_vlan, pkt=pkt,
469 exp_pkt=exp_pkt, action_list=action_list,
470 check_expire=check_expire)
471 test_count += 1
472 if (max_test > 0) and (test_count > max_test):
473 parent.logger.info("Ran " + str(test_count) + " tests; exiting")
474 return
475