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