blob: f1459bf41969807652d154709981d08edf7b99dd [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
Dan Talayco4b2bee62010-07-20 14:10:05 -070020import types
Dan Talaycoc901f4d2010-03-07 21:55:45 -080021
Dan Talaycoba3745c2010-07-21 21:51:08 -070022global skipped_test_count
23skipped_test_count = 0
24
Dan Talayco551befa2010-07-15 17:05:32 -070025# Some useful defines
26IP_ETHERTYPE = 0x800
27TCP_PROTOCOL = 0x6
28UDP_PROTOCOL = 0x11
29
Dan Talayco98fada92010-07-17 00:36:21 -070030def clear_switch(parent, port_list, logger):
31 """
32 Clear the switch configuration
33
34 @param parent Object implementing controller and assert equal
35 @param logger Logging object
36 """
37 for port in port_list:
38 clear_port_config(parent, port, logger)
39 delete_all_flows(parent.controller, logger)
40
Dan Talayco41eae8b2010-03-10 13:57:06 -080041def delete_all_flows(ctrl, logger):
42 """
43 Delete all flows on the switch
44 @param ctrl The controller object for the test
45 @param logger Logging object
46 """
47
Dan Talaycoc901f4d2010-03-07 21:55:45 -080048 logger.info("Deleting all flows")
49 msg = message.flow_mod()
50 msg.match.wildcards = ofp.OFPFW_ALL
Dan Talayco41eae8b2010-03-10 13:57:06 -080051 msg.out_port = ofp.OFPP_NONE
Dan Talaycoc901f4d2010-03-07 21:55:45 -080052 msg.command = ofp.OFPFC_DELETE
53 msg.buffer_id = 0xffffffff
Dan Talayco41eae8b2010-03-10 13:57:06 -080054 return ctrl.message_send(msg)
55
Dan Talayco98fada92010-07-17 00:36:21 -070056def clear_port_config(parent, port, logger):
57 """
58 Clear the port configuration (currently only no flood setting)
59
60 @param parent Object implementing controller and assert equal
61 @param logger Logging object
62 """
63 rv = port_config_set(parent.controller, port,
64 0, ofp.OFPPC_NO_FLOOD, logger)
65 self.assertEqual(rv, 0, "Failed to reset port config")
66
Dan Talayco41eae8b2010-03-10 13:57:06 -080067def simple_tcp_packet(pktlen=100,
68 dl_dst='00:01:02:03:04:05',
69 dl_src='00:06:07:08:09:0a',
Tatsuya Yabe460321e2010-05-25 17:50:49 -070070 dl_vlan_enable=False,
71 dl_vlan=0,
72 dl_vlan_pcp=0,
Dan Talayco551befa2010-07-15 17:05:32 -070073 dl_vlan_cfi=0,
Dan Talayco41eae8b2010-03-10 13:57:06 -080074 ip_src='192.168.0.1',
75 ip_dst='192.168.0.2',
Tatsuya Yabe460321e2010-05-25 17:50:49 -070076 ip_tos=0,
Dan Talayco41eae8b2010-03-10 13:57:06 -080077 tcp_sport=1234,
78 tcp_dport=80
79 ):
80 """
81 Return a simple dataplane TCP packet
82
83 Supports a few parameters:
84 @param len Length of packet in bytes w/o CRC
85 @param dl_dst Destinatino MAC
86 @param dl_src Source MAC
Tatsuya Yabe460321e2010-05-25 17:50:49 -070087 @param dl_vlan_enable True if the packet is with vlan, False otherwise
88 @param dl_vlan VLAN ID
89 @param dl_vlan_pcp VLAN priority
Dan Talayco41eae8b2010-03-10 13:57:06 -080090 @param ip_src IP source
91 @param ip_dst IP destination
Tatsuya Yabe460321e2010-05-25 17:50:49 -070092 @param ip_tos IP ToS
Dan Talayco41eae8b2010-03-10 13:57:06 -080093 @param tcp_dport TCP destination port
94 @param ip_sport TCP source port
95
96 Generates a simple TCP request. Users
97 shouldn't assume anything about this packet other than that
98 it is a valid ethernet/IP/TCP frame.
99 """
Dan Talayco551befa2010-07-15 17:05:32 -0700100 # Note Dot1Q.id is really CFI
Tatsuya Yabe460321e2010-05-25 17:50:49 -0700101 if (dl_vlan_enable):
102 pkt = scapy.Ether(dst=dl_dst, src=dl_src)/ \
Dan Talayco551befa2010-07-15 17:05:32 -0700103 scapy.Dot1Q(prio=dl_vlan_pcp, id=dl_vlan_cfi, vlan=dl_vlan)/ \
Tatsuya Yabe460321e2010-05-25 17:50:49 -0700104 scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos)/ \
105 scapy.TCP(sport=tcp_sport, dport=tcp_dport)
106 else:
107 pkt = scapy.Ether(dst=dl_dst, src=dl_src)/ \
108 scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos)/ \
109 scapy.TCP(sport=tcp_sport, dport=tcp_dport)
110
Dan Talayco41eae8b2010-03-10 13:57:06 -0800111 pkt = pkt/("D" * (pktlen - len(pkt)))
112
113 return pkt
114
Tatsuya Yabeb8fb3c32010-06-14 15:48:36 -0700115def simple_icmp_packet(pktlen=60,
116 dl_dst='00:01:02:03:04:05',
117 dl_src='00:06:07:08:09:0a',
118 dl_vlan_enable=False,
119 dl_vlan=0,
120 dl_vlan_pcp=0,
121 ip_src='192.168.0.1',
122 ip_dst='192.168.0.2',
123 ip_tos=0,
124 icmp_type=8,
125 icmp_code=0
126 ):
127 """
128 Return a simple ICMP packet
129
130 Supports a few parameters:
131 @param len Length of packet in bytes w/o CRC
132 @param dl_dst Destinatino MAC
133 @param dl_src Source MAC
134 @param dl_vlan_enable True if the packet is with vlan, False otherwise
135 @param dl_vlan VLAN ID
136 @param dl_vlan_pcp VLAN priority
137 @param ip_src IP source
138 @param ip_dst IP destination
139 @param ip_tos IP ToS
140 @param icmp_type ICMP type
141 @param icmp_code ICMP code
142
143 Generates a simple ICMP ECHO REQUEST. Users
144 shouldn't assume anything about this packet other than that
145 it is a valid ethernet/ICMP frame.
146 """
147 if (dl_vlan_enable):
148 pkt = scapy.Ether(dst=dl_dst, src=dl_src)/ \
149 scapy.Dot1Q(prio=dl_vlan_pcp, id=0, vlan=dl_vlan)/ \
150 scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos)/ \
151 scapy.ICMP(type=icmp_type, code=icmp_code)
152 else:
153 pkt = scapy.Ether(dst=dl_dst, src=dl_src)/ \
154 scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos)/ \
155 scapy.ICMP(type=icmp_type, code=icmp_code)
156
157 pkt = pkt/("0" * (pktlen - len(pkt)))
158
159 return pkt
160
Ed Swierk0aeff8c2012-03-23 20:27:18 -0700161def simple_eth_packet(pktlen=60,
162 dl_dst='00:01:02:03:04:05',
163 dl_src='01:80:c2:00:00:00',
164 dl_type=0x88cc):
165 pkt = scapy.Ether(dst=dl_dst, src=dl_src, type=dl_type)
166
167 pkt = pkt/("0" * (pktlen - len(pkt)))
168
169 return pkt
170
Dan Talayco41eae8b2010-03-10 13:57:06 -0800171def do_barrier(ctrl):
172 b = message.barrier_request()
173 ctrl.transact(b)
Dan Talayco92c99122010-06-03 13:53:18 -0700174
175
176def port_config_get(controller, port_no, logger):
177 """
178 Get a port's configuration
179
180 Gets the switch feature configuration and grabs one port's
181 configuration
182
183 @returns (hwaddr, config, advert) The hwaddress, configuration and
184 advertised values
185 """
186 request = message.features_request()
187 reply, pkt = controller.transact(request, timeout=2)
188 logger.debug(reply.show())
189 if reply is None:
190 logger.warn("Get feature request failed")
191 return None, None, None
192 for idx in range(len(reply.ports)):
193 if reply.ports[idx].port_no == port_no:
194 return (reply.ports[idx].hw_addr, reply.ports[idx].config,
195 reply.ports[idx].advertised)
196
197 logger.warn("Did not find port number for port config")
198 return None, None, None
199
200def port_config_set(controller, port_no, config, mask, logger):
201 """
202 Set the port configuration according the given parameters
203
204 Gets the switch feature configuration and updates one port's
205 configuration value according to config and mask
206 """
207 logger.info("Setting port " + str(port_no) + " to config " + str(config))
208 request = message.features_request()
209 reply, pkt = controller.transact(request, timeout=2)
210 if reply is None:
211 return -1
212 logger.debug(reply.show())
213 for idx in range(len(reply.ports)):
214 if reply.ports[idx].port_no == port_no:
215 break
216 if idx >= len(reply.ports):
217 return -1
218 mod = message.port_mod()
219 mod.port_no = port_no
220 mod.hw_addr = reply.ports[idx].hw_addr
221 mod.config = config
222 mod.mask = mask
223 mod.advertise = reply.ports[idx].advertised
224 rv = controller.message_send(mod)
225 return rv
226
Ken Chiang1bf01602012-04-04 10:48:23 -0700227def receive_pkt_check(dp, pkt, yes_ports, no_ports, assert_if, logger,
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700228 config):
Dan Talayco92c99122010-06-03 13:53:18 -0700229 """
230 Check for proper receive packets across all ports
Ken Chiang1bf01602012-04-04 10:48:23 -0700231 @param dp The dataplane object
Dan Talayco92c99122010-06-03 13:53:18 -0700232 @param pkt Expected packet; may be None if yes_ports is empty
233 @param yes_ports Set or list of ports that should recieve packet
234 @param no_ports Set or list of ports that should not receive packet
235 @param assert_if Object that implements assertXXX
236 """
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700237 exp_pkt_arg = None
238 if config and config["relax"]:
239 exp_pkt_arg = pkt
240
Dan Talayco92c99122010-06-03 13:53:18 -0700241 for ofport in yes_ports:
242 logger.debug("Checking for pkt on port " + str(ofport))
Ken Chiang1bf01602012-04-04 10:48:23 -0700243 (rcv_port, rcv_pkt, pkt_time) = dp.poll(
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700244 port_number=ofport, timeout=1, exp_pkt=exp_pkt_arg)
Dan Talayco92c99122010-06-03 13:53:18 -0700245 assert_if.assertTrue(rcv_pkt is not None,
246 "Did not receive pkt on " + str(ofport))
Ken Chiang1bf01602012-04-04 10:48:23 -0700247 if not dataplane.match_exp_pkt(pkt, rcv_pkt):
248 logger.debug("Sent %s" % format_packet(pkt))
249 logger.debug("Resp %s" % format_packet(rcv_pkt))
250 assert_if.assertTrue(dataplane.match_exp_pkt(pkt, rcv_pkt),
251 "Response packet does not match send packet " +
252 "on port " + str(ofport))
Dan Talayco92c99122010-06-03 13:53:18 -0700253
254 for ofport in no_ports:
255 logger.debug("Negative check for pkt on port " + str(ofport))
Ken Chiang1bf01602012-04-04 10:48:23 -0700256 (rcv_port, rcv_pkt, pkt_time) = dp.poll(
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700257 port_number=ofport, timeout=1, exp_pkt=exp_pkt_arg)
Dan Talayco92c99122010-06-03 13:53:18 -0700258 assert_if.assertTrue(rcv_pkt is None,
259 "Unexpected pkt on port " + str(ofport))
Dan Talayco551befa2010-07-15 17:05:32 -0700260
261
Dan Talaycoc948d0b2012-03-23 12:17:54 -0700262def receive_pkt_verify(parent, egr_ports, exp_pkt, ing_port):
Dan Talayco551befa2010-07-15 17:05:32 -0700263 """
264 Receive a packet and verify it matches an expected value
Dan Talaycof6e76c02012-03-23 10:56:12 -0700265 @param egr_port A single port or list of ports
Dan Talayco551befa2010-07-15 17:05:32 -0700266
267 parent must implement dataplane, assertTrue and assertEqual
268 """
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700269 exp_pkt_arg = None
270 if parent.config["relax"]:
271 exp_pkt_arg = exp_pkt
272
Dan Talaycof6e76c02012-03-23 10:56:12 -0700273 if type(egr_ports) == type([]):
274 egr_port_list = egr_ports
275 else:
276 egr_port_list = [egr_ports]
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700277
Dan Talaycof6e76c02012-03-23 10:56:12 -0700278 # Expect a packet from each port on egr port list
279 for egr_port in egr_port_list:
Dan Talaycod8ae7582012-03-23 12:24:56 -0700280 check_port = egr_port
Dan Talaycoc948d0b2012-03-23 12:17:54 -0700281 if egr_port == ofp.OFPP_IN_PORT:
Dan Talaycod8ae7582012-03-23 12:24:56 -0700282 check_port = ing_port
Dan Talaycof6e76c02012-03-23 10:56:12 -0700283 (rcv_port, rcv_pkt, pkt_time) = parent.dataplane.poll(
Dan Talaycod8ae7582012-03-23 12:24:56 -0700284 port_number=check_port, timeout=1, exp_pkt=exp_pkt_arg)
Dan Talayco551befa2010-07-15 17:05:32 -0700285
Dan Talaycof6e76c02012-03-23 10:56:12 -0700286 if rcv_pkt is None:
Dan Talaycod8ae7582012-03-23 12:24:56 -0700287 parent.logger.error("ERROR: No packet received from " +
288 str(check_port))
Dan Talayco551befa2010-07-15 17:05:32 -0700289
Dan Talaycof6e76c02012-03-23 10:56:12 -0700290 parent.assertTrue(rcv_pkt is not None,
Dan Talaycod8ae7582012-03-23 12:24:56 -0700291 "Did not receive packet port " + str(check_port))
Dan Talaycof6e76c02012-03-23 10:56:12 -0700292 parent.logger.debug("Packet len " + str(len(rcv_pkt)) + " in on " +
293 str(rcv_port))
294
295 if str(exp_pkt) != str(rcv_pkt):
296 parent.logger.error("ERROR: Packet match failed.")
297 parent.logger.debug("Expected len " + str(len(exp_pkt)) + ": "
298 + str(exp_pkt).encode('hex'))
299 parent.logger.debug("Received len " + str(len(rcv_pkt)) + ": "
300 + str(rcv_pkt).encode('hex'))
301 parent.assertEqual(str(exp_pkt), str(rcv_pkt),
Dan Talaycod8ae7582012-03-23 12:24:56 -0700302 "Packet match error on port " + str(check_port))
Dan Talaycof6e76c02012-03-23 10:56:12 -0700303
Dan Talayco551befa2010-07-15 17:05:32 -0700304def match_verify(parent, req_match, res_match):
305 """
306 Verify flow matches agree; if they disagree, report where
307
308 parent must implement assertEqual
309 Use str() to ensure content is compared and not pointers
310 """
311
312 parent.assertEqual(req_match.wildcards, res_match.wildcards,
313 'Match failed: wildcards: ' + hex(req_match.wildcards) +
314 " != " + hex(res_match.wildcards))
315 parent.assertEqual(req_match.in_port, res_match.in_port,
316 'Match failed: in_port: ' + str(req_match.in_port) +
317 " != " + str(res_match.in_port))
318 parent.assertEqual(str(req_match.dl_src), str(res_match.dl_src),
319 'Match failed: dl_src: ' + str(req_match.dl_src) +
320 " != " + str(res_match.dl_src))
321 parent.assertEqual(str(req_match.dl_dst), str(res_match.dl_dst),
322 'Match failed: dl_dst: ' + str(req_match.dl_dst) +
323 " != " + str(res_match.dl_dst))
324 parent.assertEqual(req_match.dl_vlan, res_match.dl_vlan,
325 'Match failed: dl_vlan: ' + str(req_match.dl_vlan) +
326 " != " + str(res_match.dl_vlan))
327 parent.assertEqual(req_match.dl_vlan_pcp, res_match.dl_vlan_pcp,
328 'Match failed: dl_vlan_pcp: ' +
329 str(req_match.dl_vlan_pcp) + " != " +
330 str(res_match.dl_vlan_pcp))
331 parent.assertEqual(req_match.dl_type, res_match.dl_type,
332 'Match failed: dl_type: ' + str(req_match.dl_type) +
333 " != " + str(res_match.dl_type))
334
335 if (not(req_match.wildcards & ofp.OFPFW_DL_TYPE)
336 and (req_match.dl_type == IP_ETHERTYPE)):
337 parent.assertEqual(req_match.nw_tos, res_match.nw_tos,
338 'Match failed: nw_tos: ' + str(req_match.nw_tos) +
339 " != " + str(res_match.nw_tos))
340 parent.assertEqual(req_match.nw_proto, res_match.nw_proto,
341 'Match failed: nw_proto: ' + str(req_match.nw_proto) +
342 " != " + str(res_match.nw_proto))
343 parent.assertEqual(req_match.nw_src, res_match.nw_src,
344 'Match failed: nw_src: ' + str(req_match.nw_src) +
345 " != " + str(res_match.nw_src))
346 parent.assertEqual(req_match.nw_dst, res_match.nw_dst,
347 'Match failed: nw_dst: ' + str(req_match.nw_dst) +
348 " != " + str(res_match.nw_dst))
349
350 if (not(req_match.wildcards & ofp.OFPFW_NW_PROTO)
351 and ((req_match.nw_proto == TCP_PROTOCOL)
352 or (req_match.nw_proto == UDP_PROTOCOL))):
353 parent.assertEqual(req_match.tp_src, res_match.tp_src,
354 'Match failed: tp_src: ' +
355 str(req_match.tp_src) +
356 " != " + str(res_match.tp_src))
357 parent.assertEqual(req_match.tp_dst, res_match.tp_dst,
358 'Match failed: tp_dst: ' +
359 str(req_match.tp_dst) +
360 " != " + str(res_match.tp_dst))
361
362def flow_removed_verify(parent, request=None, pkt_count=-1, byte_count=-1):
363 """
364 Receive a flow removed msg and verify it matches expected
365
366 @params parent Must implement controller, assertEqual
367 @param pkt_count If >= 0, verify packet count
368 @param byte_count If >= 0, verify byte count
369 """
370 (response, raw) = parent.controller.poll(ofp.OFPT_FLOW_REMOVED, 2)
371 parent.assertTrue(response is not None, 'No flow removed message received')
372
373 if request is None:
374 return
375
376 parent.assertEqual(request.cookie, response.cookie,
377 "Flow removed cookie error: " +
378 hex(request.cookie) + " != " + hex(response.cookie))
379
380 req_match = request.match
381 res_match = response.match
382 verifyMatchField(req_match, res_match)
383
384 if (req_match.wildcards != 0):
385 parent.assertEqual(request.priority, response.priority,
386 'Flow remove prio mismatch: ' +
387 str(request,priority) + " != " +
388 str(response.priority))
389 parent.assertEqual(response.reason, ofp.OFPRR_HARD_TIMEOUT,
390 'Flow remove reason is not HARD TIMEOUT:' +
391 str(response.reason))
392 if pkt_count >= 0:
393 parent.assertEqual(response.packet_count, pkt_count,
394 'Flow removed failed, packet count: ' +
395 str(response.packet_count) + " != " +
396 str(pkt_count))
397 if byte_count >= 0:
398 parent.assertEqual(response.byte_count, byte_count,
399 'Flow removed failed, byte count: ' +
400 str(response.byte_count) + " != " +
401 str(byte_count))
402
403def flow_msg_create(parent, pkt, ing_port=None, action_list=None, wildcards=0,
Dan Talaycof6e76c02012-03-23 10:56:12 -0700404 egr_ports=None, egr_queue=None, check_expire=False, in_band=False):
Dan Talayco551befa2010-07-15 17:05:32 -0700405 """
406 Create a flow message
407
408 Match on packet with given wildcards.
409 See flow_match_test for other parameter descriptoins
410 @param egr_queue if not None, make the output an enqueue action
Dan Talayco677c0b72011-08-23 22:53:38 -0700411 @param in_band if True, do not wildcard ingress port
Dan Talaycof6e76c02012-03-23 10:56:12 -0700412 @param egr_ports None (drop), single port or list of ports
Dan Talayco551befa2010-07-15 17:05:32 -0700413 """
414 match = parse.packet_to_flow_match(pkt)
415 parent.assertTrue(match is not None, "Flow match from pkt failed")
Dan Talayco677c0b72011-08-23 22:53:38 -0700416 if in_band:
417 wildcards &= ~ofp.OFPFW_IN_PORT
Dan Talayco551befa2010-07-15 17:05:32 -0700418 match.wildcards = wildcards
419 match.in_port = ing_port
420
Dan Talaycof6e76c02012-03-23 10:56:12 -0700421 if type(egr_ports) == type([]):
422 egr_port_list = egr_ports
423 else:
424 egr_port_list = [egr_ports]
425
Dan Talayco551befa2010-07-15 17:05:32 -0700426 request = message.flow_mod()
427 request.match = match
428 request.buffer_id = 0xffffffff
429 if check_expire:
430 request.flags |= ofp.OFPFF_SEND_FLOW_REM
431 request.hard_timeout = 1
432
433 if action_list is not None:
434 for act in action_list:
435 parent.logger.debug("Adding action " + act.show())
436 rv = request.actions.add(act)
437 parent.assertTrue(rv, "Could not add action" + act.show())
438
439 # Set up output/enqueue action if directed
440 if egr_queue is not None:
Dan Talaycof6e76c02012-03-23 10:56:12 -0700441 parent.assertTrue(egr_ports is not None, "Egress port not set")
Dan Talayco551befa2010-07-15 17:05:32 -0700442 act = action.action_enqueue()
Dan Talaycof6e76c02012-03-23 10:56:12 -0700443 for egr_port in egr_port_list:
444 act.port = egr_port
445 act.queue_id = egr_queue
446 rv = request.actions.add(act)
447 parent.assertTrue(rv, "Could not add enqueue action " +
448 str(egr_port) + " Q: " + str(egr_queue))
449 elif egr_ports is not None:
450 for egr_port in egr_port_list:
451 act = action.action_output()
452 act.port = egr_port
453 rv = request.actions.add(act)
454 parent.assertTrue(rv, "Could not add output action " +
455 str(egr_port))
Dan Talayco551befa2010-07-15 17:05:32 -0700456
457 parent.logger.debug(request.show())
458
459 return request
460
Jeffrey Townsendf3eae9c2012-03-28 18:23:21 -0700461def flow_msg_install(parent, request, clear_table_override=None):
Dan Talayco551befa2010-07-15 17:05:32 -0700462 """
463 Install a flow mod message in the switch
464
465 @param parent Must implement controller, assertEqual, assertTrue
466 @param request The request, all set to go
467 @param clear_table If true, clear the flow table before installing
468 """
Dan Talayco8a64e332012-03-28 14:53:20 -0700469
470 clear_table = test_param_get(parent.config, 'clear_table', default=True)
Jeffrey Townsendf3eae9c2012-03-28 18:23:21 -0700471 if(clear_table_override != None):
472 clear_table = clear_table_override
473
474 if clear_table:
Dan Talayco551befa2010-07-15 17:05:32 -0700475 parent.logger.debug("Clear flow table")
476 rc = delete_all_flows(parent.controller, parent.logger)
477 parent.assertEqual(rc, 0, "Failed to delete all flows")
478 do_barrier(parent.controller)
479
480 parent.logger.debug("Insert flow")
481 rv = parent.controller.message_send(request)
482 parent.assertTrue(rv != -1, "Error installing flow mod")
483 do_barrier(parent.controller)
484
Dan Talaycof6e76c02012-03-23 10:56:12 -0700485def flow_match_test_port_pair(parent, ing_port, egr_ports, wildcards=0,
Dan Talayco551befa2010-07-15 17:05:32 -0700486 dl_vlan=-1, pkt=None, exp_pkt=None,
487 action_list=None, check_expire=False):
488 """
489 Flow match test on single TCP packet
Dan Talaycof6e76c02012-03-23 10:56:12 -0700490 @param egr_ports A single port or list of ports
Dan Talayco551befa2010-07-15 17:05:32 -0700491
492 Run test with packet through switch from ing_port to egr_port
493 See flow_match_test for parameter descriptions
494 """
495
Dan Talaycof6e76c02012-03-23 10:56:12 -0700496 parent.logger.info("Pkt match test: " + str(ing_port) + " to " +
497 str(egr_ports))
Dan Talayco551befa2010-07-15 17:05:32 -0700498 parent.logger.debug(" WC: " + hex(wildcards) + " vlan: " + str(dl_vlan) +
Dan Talayco98fada92010-07-17 00:36:21 -0700499 " expire: " + str(check_expire))
Dan Talayco551befa2010-07-15 17:05:32 -0700500 if pkt is None:
501 pkt = simple_tcp_packet(dl_vlan_enable=(dl_vlan >= 0), dl_vlan=dl_vlan)
502
503 request = flow_msg_create(parent, pkt, ing_port=ing_port,
Dan Talaycof6e76c02012-03-23 10:56:12 -0700504 wildcards=wildcards, egr_ports=egr_ports,
Dan Talayco551befa2010-07-15 17:05:32 -0700505 action_list=action_list)
506
507 flow_msg_install(parent, request)
508
Dan Talaycof6e76c02012-03-23 10:56:12 -0700509 parent.logger.debug("Send packet: " + str(ing_port) + " to " +
510 str(egr_ports))
Dan Talayco551befa2010-07-15 17:05:32 -0700511 parent.dataplane.send(ing_port, str(pkt))
512
513 if exp_pkt is None:
514 exp_pkt = pkt
Dan Talaycoc948d0b2012-03-23 12:17:54 -0700515 receive_pkt_verify(parent, egr_ports, exp_pkt, ing_port)
Dan Talayco551befa2010-07-15 17:05:32 -0700516
517 if check_expire:
518 #@todo Not all HW supports both pkt and byte counters
519 flow_removed_verify(parent, request, pkt_count=1, byte_count=len(pkt))
520
Dan Talaycof6e76c02012-03-23 10:56:12 -0700521def get_egr_list(parent, of_ports, how_many, exclude_list=[]):
522 """
523 Generate a list of ports avoiding those in the exclude list
524 @param parent Supplies logger
525 @param of_ports List of OF port numbers
526 @param how_many Number of ports to be added to the list
527 @param exclude_list List of ports not to be used
528 @returns An empty list if unable to find enough ports
529 """
530
Dan Talaycoc948d0b2012-03-23 12:17:54 -0700531 if how_many == 0:
532 return []
533
Dan Talaycof6e76c02012-03-23 10:56:12 -0700534 count = 0
535 egr_ports = []
536 for egr_idx in range(len(of_ports)):
537 if of_ports[egr_idx] not in exclude_list:
538 egr_ports.append(of_ports[egr_idx])
539 count += 1
540 if count >= how_many:
541 return egr_ports
542 parent.logger.debug("Could not generate enough egress ports for test")
543 return []
544
Dan Talayco551befa2010-07-15 17:05:32 -0700545def flow_match_test(parent, port_map, wildcards=0, dl_vlan=-1, pkt=None,
546 exp_pkt=None, action_list=None, check_expire=False,
Dan Talaycoc948d0b2012-03-23 12:17:54 -0700547 max_test=0, egr_count=1, ing_port=False):
Dan Talayco551befa2010-07-15 17:05:32 -0700548 """
549 Run flow_match_test_port_pair on all port pairs
550
551 @param max_test If > 0 no more than this number of tests are executed.
552 @param parent Must implement controller, dataplane, assertTrue, assertEqual
553 and logger
554 @param pkt If not None, use this packet for ingress
555 @param wildcards For flow match entry
Dan Talayco79184222010-11-01 12:24:29 -0700556 @param dl_vlan If not -1, and pkt is None, create a pkt w/ VLAN tag
Dan Talayco551befa2010-07-15 17:05:32 -0700557 @param exp_pkt If not None, use this as the expected output pkt; els use pkt
558 @param action_list Additional actions to add to flow mod
559 @param check_expire Check for flow expiration message
Dan Talaycocfa172f2012-03-23 12:03:00 -0700560 @param egr_count Number of egress ports; -1 means get from config w/ dflt 2
Dan Talayco551befa2010-07-15 17:05:32 -0700561 """
562 of_ports = port_map.keys()
563 of_ports.sort()
564 parent.assertTrue(len(of_ports) > 1, "Not enough ports for test")
565 test_count = 0
566
Dan Talaycocfa172f2012-03-23 12:03:00 -0700567 if egr_count == -1:
568 egr_count = test_param_get(parent.config, 'egr_count', default=2)
569
Dan Talayco551befa2010-07-15 17:05:32 -0700570 for ing_idx in range(len(of_ports)):
571 ingress_port = of_ports[ing_idx]
Dan Talaycof6e76c02012-03-23 10:56:12 -0700572 egr_ports = get_egr_list(parent, of_ports, egr_count,
573 exclude_list=[ingress_port])
Dan Talaycoc948d0b2012-03-23 12:17:54 -0700574 if ing_port:
575 egr_ports.append(ofp.OFPP_IN_PORT)
Dan Talaycof6e76c02012-03-23 10:56:12 -0700576 if len(egr_ports) == 0:
577 parent.assertTrue(0, "Failed to generate egress port list")
578
579 flow_match_test_port_pair(parent, ingress_port, egr_ports,
580 wildcards=wildcards, dl_vlan=dl_vlan,
581 pkt=pkt, exp_pkt=exp_pkt,
582 action_list=action_list,
583 check_expire=check_expire)
584 test_count += 1
585 if (max_test > 0) and (test_count > max_test):
586 parent.logger.info("Ran " + str(test_count) + " tests; exiting")
587 return
Dan Talayco551befa2010-07-15 17:05:32 -0700588
Dan Talayco4b2bee62010-07-20 14:10:05 -0700589def test_param_get(config, key, default=None):
590 """
591 Return value passed via test-params if present
592
593 @param config The configuration structure for OFTest
594 @param key The lookup key
595 @param default Default value to use if not found
596
597 If the pair 'key=val' appeared in the string passed to --test-params
598 on the command line, return val (as interpreted by exec). Otherwise
599 return default value.
Dan Talaycof6e76c02012-03-23 10:56:12 -0700600
601 WARNING: TEST PARAMETERS MUST BE PYTHON IDENTIFIERS;
602 eg egr_count, not egr-count.
Dan Talayco4b2bee62010-07-20 14:10:05 -0700603 """
604 try:
605 exec config["test_params"]
606 except:
607 return default
608
609 s = "val = " + str(key)
610 try:
611 exec s
612 return val
613 except:
614 return default
615
616def action_generate(parent, field_to_mod, mod_field_vals):
617 """
618 Create an action to modify the field indicated in field_to_mod
619
620 @param parent Must implement, assertTrue
621 @param field_to_mod The field to modify as a string name
622 @param mod_field_vals Hash of values to use for modified values
623 """
624
625 act = None
626
627 if field_to_mod in ['pktlen']:
628 return None
629
630 if field_to_mod == 'dl_dst':
631 act = action.action_set_dl_dst()
632 act.dl_addr = parse.parse_mac(mod_field_vals['dl_dst'])
633 elif field_to_mod == 'dl_src':
634 act = action.action_set_dl_src()
635 act.dl_addr = parse.parse_mac(mod_field_vals['dl_src'])
636 elif field_to_mod == 'dl_vlan_enable':
637 if not mod_field_vals['dl_vlan_enable']: # Strip VLAN tag
638 act = action.action_strip_vlan()
639 # Add VLAN tag is handled by dl_vlan field
640 # Will return None in this case
641 elif field_to_mod == 'dl_vlan':
642 act = action.action_set_vlan_vid()
643 act.vlan_vid = mod_field_vals['dl_vlan']
644 elif field_to_mod == 'dl_vlan_pcp':
645 act = action.action_set_vlan_pcp()
646 act.vlan_pcp = mod_field_vals['dl_vlan_pcp']
647 elif field_to_mod == 'ip_src':
648 act = action.action_set_nw_src()
649 act.nw_addr = parse.parse_ip(mod_field_vals['ip_src'])
650 elif field_to_mod == 'ip_dst':
651 act = action.action_set_nw_dst()
652 act.nw_addr = parse.parse_ip(mod_field_vals['ip_dst'])
653 elif field_to_mod == 'ip_tos':
654 act = action.action_set_nw_tos()
655 act.nw_tos = mod_field_vals['ip_tos']
656 elif field_to_mod == 'tcp_sport':
657 act = action.action_set_tp_src()
658 act.tp_port = mod_field_vals['tcp_sport']
659 elif field_to_mod == 'tcp_dport':
660 act = action.action_set_tp_dst()
661 act.tp_port = mod_field_vals['tcp_dport']
662 else:
663 parent.assertTrue(0, "Unknown field to modify: " + str(field_to_mod))
664
665 return act
666
667def pkt_action_setup(parent, start_field_vals={}, mod_field_vals={},
668 mod_fields={}, check_test_params=False):
669 """
670 Set up the ingress and expected packet and action list for a test
671
672 @param parent Must implement, assertTrue, config hash and logger
673 @param start_field_values Field values to use for ingress packet (optional)
674 @param mod_field_values Field values to use for modified packet (optional)
675 @param mod_fields The list of fields to be modified by the switch in the test.
676 @params check_test_params If True, will check the parameters vid, add_vlan
677 and strip_vlan from the command line.
678
679 Returns a triple: pkt-to-send, expected-pkt, action-list
680 """
681
682 new_actions = []
683
Dan Talayco4b2bee62010-07-20 14:10:05 -0700684 base_pkt_params = {}
685 base_pkt_params['pktlen'] = 100
686 base_pkt_params['dl_dst'] = '00:DE:F0:12:34:56'
687 base_pkt_params['dl_src'] = '00:23:45:67:89:AB'
688 base_pkt_params['dl_vlan_enable'] = False
689 base_pkt_params['dl_vlan'] = 2
690 base_pkt_params['dl_vlan_pcp'] = 0
691 base_pkt_params['ip_src'] = '192.168.0.1'
692 base_pkt_params['ip_dst'] = '192.168.0.2'
693 base_pkt_params['ip_tos'] = 0
694 base_pkt_params['tcp_sport'] = 1234
695 base_pkt_params['tcp_dport'] = 80
696 for keyname in start_field_vals.keys():
697 base_pkt_params[keyname] = start_field_vals[keyname]
698
699 mod_pkt_params = {}
700 mod_pkt_params['pktlen'] = 100
701 mod_pkt_params['dl_dst'] = '00:21:0F:ED:CB:A9'
702 mod_pkt_params['dl_src'] = '00:ED:CB:A9:87:65'
703 mod_pkt_params['dl_vlan_enable'] = False
704 mod_pkt_params['dl_vlan'] = 3
705 mod_pkt_params['dl_vlan_pcp'] = 7
706 mod_pkt_params['ip_src'] = '10.20.30.40'
707 mod_pkt_params['ip_dst'] = '50.60.70.80'
708 mod_pkt_params['ip_tos'] = 0xf0
709 mod_pkt_params['tcp_sport'] = 4321
710 mod_pkt_params['tcp_dport'] = 8765
711 for keyname in mod_field_vals.keys():
712 mod_pkt_params[keyname] = mod_field_vals[keyname]
713
714 # Check for test param modifications
715 strip = False
716 if check_test_params:
717 add_vlan = test_param_get(parent.config, 'add_vlan')
718 strip_vlan = test_param_get(parent.config, 'strip_vlan')
719 vid = test_param_get(parent.config, 'vid')
720
721 if add_vlan and strip_vlan:
722 parent.assertTrue(0, "Add and strip VLAN both specified")
723
724 if vid:
725 base_pkt_params['dl_vlan_enable'] = True
726 base_pkt_params['dl_vlan'] = vid
727 if 'dl_vlan' in mod_fields:
728 mod_pkt_params['dl_vlan'] = vid + 1
729
730 if add_vlan:
731 base_pkt_params['dl_vlan_enable'] = False
732 mod_pkt_params['dl_vlan_enable'] = True
733 mod_pkt_params['pktlen'] = base_pkt_params['pktlen'] + 4
734 mod_fields.append('pktlen')
735 mod_fields.append('dl_vlan_enable')
736 if 'dl_vlan' not in mod_fields:
737 mod_fields.append('dl_vlan')
738 elif strip_vlan:
739 base_pkt_params['dl_vlan_enable'] = True
740 mod_pkt_params['dl_vlan_enable'] = False
741 mod_pkt_params['pktlen'] = base_pkt_params['pktlen'] - 4
742 mod_fields.append('dl_vlan_enable')
743 mod_fields.append('pktlen')
744
745 # Build the ingress packet
746 ingress_pkt = simple_tcp_packet(**base_pkt_params)
747
748 # Build the expected packet, modifying the indicated fields
749 for item in mod_fields:
750 base_pkt_params[item] = mod_pkt_params[item]
751 act = action_generate(parent, item, mod_pkt_params)
752 if act:
753 new_actions.append(act)
754
755 expected_pkt = simple_tcp_packet(**base_pkt_params)
756
757 return (ingress_pkt, expected_pkt, new_actions)
Dan Talayco677c0b72011-08-23 22:53:38 -0700758
759# Generate a simple "drop" flow mod
760# If in_band is true, then only drop from first test port
761def flow_mod_gen(port_map, in_band):
762 request = message.flow_mod()
763 request.match.wildcards = ofp.OFPFW_ALL
764 if in_band:
765 request.match.wildcards = ofp.OFPFW_ALL - ofp.OFPFW_IN_PORT
766 for of_port, ifname in port_map.items(): # Grab first port
767 break
768 request.match.in_port = of_port
769 request.buffer_id = 0xffffffff
770 return request
Dan Talaycoba3745c2010-07-21 21:51:08 -0700771
772def skip_message_emit(parent, s):
773 """
774 Print out a 'skipped' message to stderr
775
776 @param s The string to print out to the log file
777 @param parent Must implement config and logger objects
778 """
779 global skipped_test_count
780
781 skipped_test_count += 1
782 parent.logger.info("Skipping: " + s)
783 if parent.config["dbg_level"] < logging.WARNING:
784 sys.stderr.write("(skipped) ")
785 else:
786 sys.stderr.write("(S)")
Dan Talayco677c0b72011-08-23 22:53:38 -0700787
Dan Talayco8a64e332012-03-28 14:53:20 -0700788
789def all_stats_get(parent):
790 """
791 Get the aggregate stats for all flows in the table
792 @param parent Test instance with controller connection and assert
793 @returns dict with keys flows, packets, bytes, active (flows),
794 lookups, matched
795 """
796 stat_req = message.aggregate_stats_request()
797 stat_req.match = ofp.ofp_match()
798 stat_req.match.wildcards = ofp.OFPFW_ALL
799 stat_req.table_id = 0xff
800 stat_req.out_port = ofp.OFPP_NONE
801
802 rv = {}
803
804 (reply, pkt) = parent.controller.transact(stat_req, timeout=2)
805 parent.assertTrue(len(reply.stats) == 1, "Did not receive flow stats reply")
806
807 for obj in reply.stats:
808 (rv["flows"], rv["packets"], rv["bytes"]) = (obj.flow_count,
809 obj.packet_count, obj.byte_count)
810 break
811
812 request = message.table_stats_request()
813 (reply , pkt) = parent.controller.transact(request, timeout=2)
814
815
816 (rv["active"], rv["lookups"], rv["matched"]) = (0,0,0)
817 for obj in reply.stats:
818 rv["active"] += obj.active_count
819 rv["lookups"] += obj.lookup_count
820 rv["matched"] += obj.matched_count
821
822 return rv
Dan Talayco2baf8b52012-03-30 09:55:42 -0700823
824FILTER=''.join([(len(repr(chr(x)))==3) and chr(x) or '.'
825 for x in range(256)])
826
827def hex_dump_buffer(src, length=16):
828 """
829 Convert src to a hex dump string and return the string
830 @param src The source buffer
831 @param length The number of bytes shown in each line
832 @returns A string showing the hex dump
833 """
834 result = []
835 for i in xrange(0, len(src), length):
836 chars = src[i:i+length]
837 hex = ' '.join(["%02x" % ord(x) for x in chars])
838 printable = ''.join(["%s" % ((ord(x) <= 127 and
839 FILTER[ord(x)]) or '.') for x in chars])
840 result.append("%04x %-*s %s\n" % (i, length*3, hex, printable))
841 return ''.join(result)
842
843def format_packet(pkt):
844 return "Packet length %d \n%s" % (len(str(pkt)),
845 hex_dump_buffer(str(pkt)))