blob: 0380ade02ae371b0e630c18f1512c6bc4dd2ccab [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
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700227def receive_pkt_check(dataplane, pkt, yes_ports, no_ports, assert_if, logger,
228 config):
Dan Talayco92c99122010-06-03 13:53:18 -0700229 """
230 Check for proper receive packets across all ports
231 @param dataplane The dataplane object
232 @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))
243 (rcv_port, rcv_pkt, pkt_time) = dataplane.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))
247 assert_if.assertEqual(str(pkt), str(rcv_pkt),
248 "Response packet does not match send packet " +
249 "on port " + str(ofport))
250
251 for ofport in no_ports:
252 logger.debug("Negative check for pkt on port " + str(ofport))
253 (rcv_port, rcv_pkt, pkt_time) = dataplane.poll(
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700254 port_number=ofport, timeout=1, exp_pkt=exp_pkt_arg)
Dan Talayco92c99122010-06-03 13:53:18 -0700255 assert_if.assertTrue(rcv_pkt is None,
256 "Unexpected pkt on port " + str(ofport))
Dan Talayco551befa2010-07-15 17:05:32 -0700257
258
Dan Talaycoc948d0b2012-03-23 12:17:54 -0700259def receive_pkt_verify(parent, egr_ports, exp_pkt, ing_port):
Dan Talayco551befa2010-07-15 17:05:32 -0700260 """
261 Receive a packet and verify it matches an expected value
Dan Talaycof6e76c02012-03-23 10:56:12 -0700262 @param egr_port A single port or list of ports
Dan Talayco551befa2010-07-15 17:05:32 -0700263
264 parent must implement dataplane, assertTrue and assertEqual
265 """
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700266 exp_pkt_arg = None
267 if parent.config["relax"]:
268 exp_pkt_arg = exp_pkt
269
Dan Talaycof6e76c02012-03-23 10:56:12 -0700270 if type(egr_ports) == type([]):
271 egr_port_list = egr_ports
272 else:
273 egr_port_list = [egr_ports]
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700274
Dan Talaycof6e76c02012-03-23 10:56:12 -0700275 # Expect a packet from each port on egr port list
276 for egr_port in egr_port_list:
Dan Talaycod8ae7582012-03-23 12:24:56 -0700277 check_port = egr_port
Dan Talaycoc948d0b2012-03-23 12:17:54 -0700278 if egr_port == ofp.OFPP_IN_PORT:
Dan Talaycod8ae7582012-03-23 12:24:56 -0700279 check_port = ing_port
Dan Talaycof6e76c02012-03-23 10:56:12 -0700280 (rcv_port, rcv_pkt, pkt_time) = parent.dataplane.poll(
Dan Talaycod8ae7582012-03-23 12:24:56 -0700281 port_number=check_port, timeout=1, exp_pkt=exp_pkt_arg)
Dan Talayco551befa2010-07-15 17:05:32 -0700282
Dan Talaycof6e76c02012-03-23 10:56:12 -0700283 if rcv_pkt is None:
Dan Talaycod8ae7582012-03-23 12:24:56 -0700284 parent.logger.error("ERROR: No packet received from " +
285 str(check_port))
Dan Talayco551befa2010-07-15 17:05:32 -0700286
Dan Talaycof6e76c02012-03-23 10:56:12 -0700287 parent.assertTrue(rcv_pkt is not None,
Dan Talaycod8ae7582012-03-23 12:24:56 -0700288 "Did not receive packet port " + str(check_port))
Dan Talaycof6e76c02012-03-23 10:56:12 -0700289 parent.logger.debug("Packet len " + str(len(rcv_pkt)) + " in on " +
290 str(rcv_port))
291
292 if str(exp_pkt) != str(rcv_pkt):
293 parent.logger.error("ERROR: Packet match failed.")
294 parent.logger.debug("Expected len " + str(len(exp_pkt)) + ": "
295 + str(exp_pkt).encode('hex'))
296 parent.logger.debug("Received len " + str(len(rcv_pkt)) + ": "
297 + str(rcv_pkt).encode('hex'))
298 parent.assertEqual(str(exp_pkt), str(rcv_pkt),
Dan Talaycod8ae7582012-03-23 12:24:56 -0700299 "Packet match error on port " + str(check_port))
Dan Talaycof6e76c02012-03-23 10:56:12 -0700300
Dan Talayco551befa2010-07-15 17:05:32 -0700301def match_verify(parent, req_match, res_match):
302 """
303 Verify flow matches agree; if they disagree, report where
304
305 parent must implement assertEqual
306 Use str() to ensure content is compared and not pointers
307 """
308
309 parent.assertEqual(req_match.wildcards, res_match.wildcards,
310 'Match failed: wildcards: ' + hex(req_match.wildcards) +
311 " != " + hex(res_match.wildcards))
312 parent.assertEqual(req_match.in_port, res_match.in_port,
313 'Match failed: in_port: ' + str(req_match.in_port) +
314 " != " + str(res_match.in_port))
315 parent.assertEqual(str(req_match.dl_src), str(res_match.dl_src),
316 'Match failed: dl_src: ' + str(req_match.dl_src) +
317 " != " + str(res_match.dl_src))
318 parent.assertEqual(str(req_match.dl_dst), str(res_match.dl_dst),
319 'Match failed: dl_dst: ' + str(req_match.dl_dst) +
320 " != " + str(res_match.dl_dst))
321 parent.assertEqual(req_match.dl_vlan, res_match.dl_vlan,
322 'Match failed: dl_vlan: ' + str(req_match.dl_vlan) +
323 " != " + str(res_match.dl_vlan))
324 parent.assertEqual(req_match.dl_vlan_pcp, res_match.dl_vlan_pcp,
325 'Match failed: dl_vlan_pcp: ' +
326 str(req_match.dl_vlan_pcp) + " != " +
327 str(res_match.dl_vlan_pcp))
328 parent.assertEqual(req_match.dl_type, res_match.dl_type,
329 'Match failed: dl_type: ' + str(req_match.dl_type) +
330 " != " + str(res_match.dl_type))
331
332 if (not(req_match.wildcards & ofp.OFPFW_DL_TYPE)
333 and (req_match.dl_type == IP_ETHERTYPE)):
334 parent.assertEqual(req_match.nw_tos, res_match.nw_tos,
335 'Match failed: nw_tos: ' + str(req_match.nw_tos) +
336 " != " + str(res_match.nw_tos))
337 parent.assertEqual(req_match.nw_proto, res_match.nw_proto,
338 'Match failed: nw_proto: ' + str(req_match.nw_proto) +
339 " != " + str(res_match.nw_proto))
340 parent.assertEqual(req_match.nw_src, res_match.nw_src,
341 'Match failed: nw_src: ' + str(req_match.nw_src) +
342 " != " + str(res_match.nw_src))
343 parent.assertEqual(req_match.nw_dst, res_match.nw_dst,
344 'Match failed: nw_dst: ' + str(req_match.nw_dst) +
345 " != " + str(res_match.nw_dst))
346
347 if (not(req_match.wildcards & ofp.OFPFW_NW_PROTO)
348 and ((req_match.nw_proto == TCP_PROTOCOL)
349 or (req_match.nw_proto == UDP_PROTOCOL))):
350 parent.assertEqual(req_match.tp_src, res_match.tp_src,
351 'Match failed: tp_src: ' +
352 str(req_match.tp_src) +
353 " != " + str(res_match.tp_src))
354 parent.assertEqual(req_match.tp_dst, res_match.tp_dst,
355 'Match failed: tp_dst: ' +
356 str(req_match.tp_dst) +
357 " != " + str(res_match.tp_dst))
358
359def flow_removed_verify(parent, request=None, pkt_count=-1, byte_count=-1):
360 """
361 Receive a flow removed msg and verify it matches expected
362
363 @params parent Must implement controller, assertEqual
364 @param pkt_count If >= 0, verify packet count
365 @param byte_count If >= 0, verify byte count
366 """
367 (response, raw) = parent.controller.poll(ofp.OFPT_FLOW_REMOVED, 2)
368 parent.assertTrue(response is not None, 'No flow removed message received')
369
370 if request is None:
371 return
372
373 parent.assertEqual(request.cookie, response.cookie,
374 "Flow removed cookie error: " +
375 hex(request.cookie) + " != " + hex(response.cookie))
376
377 req_match = request.match
378 res_match = response.match
379 verifyMatchField(req_match, res_match)
380
381 if (req_match.wildcards != 0):
382 parent.assertEqual(request.priority, response.priority,
383 'Flow remove prio mismatch: ' +
384 str(request,priority) + " != " +
385 str(response.priority))
386 parent.assertEqual(response.reason, ofp.OFPRR_HARD_TIMEOUT,
387 'Flow remove reason is not HARD TIMEOUT:' +
388 str(response.reason))
389 if pkt_count >= 0:
390 parent.assertEqual(response.packet_count, pkt_count,
391 'Flow removed failed, packet count: ' +
392 str(response.packet_count) + " != " +
393 str(pkt_count))
394 if byte_count >= 0:
395 parent.assertEqual(response.byte_count, byte_count,
396 'Flow removed failed, byte count: ' +
397 str(response.byte_count) + " != " +
398 str(byte_count))
399
400def flow_msg_create(parent, pkt, ing_port=None, action_list=None, wildcards=0,
Dan Talaycof6e76c02012-03-23 10:56:12 -0700401 egr_ports=None, egr_queue=None, check_expire=False, in_band=False):
Dan Talayco551befa2010-07-15 17:05:32 -0700402 """
403 Create a flow message
404
405 Match on packet with given wildcards.
406 See flow_match_test for other parameter descriptoins
407 @param egr_queue if not None, make the output an enqueue action
Dan Talayco677c0b72011-08-23 22:53:38 -0700408 @param in_band if True, do not wildcard ingress port
Dan Talaycof6e76c02012-03-23 10:56:12 -0700409 @param egr_ports None (drop), single port or list of ports
Dan Talayco551befa2010-07-15 17:05:32 -0700410 """
411 match = parse.packet_to_flow_match(pkt)
412 parent.assertTrue(match is not None, "Flow match from pkt failed")
Dan Talayco677c0b72011-08-23 22:53:38 -0700413 if in_band:
414 wildcards &= ~ofp.OFPFW_IN_PORT
Dan Talayco551befa2010-07-15 17:05:32 -0700415 match.wildcards = wildcards
416 match.in_port = ing_port
417
Dan Talaycof6e76c02012-03-23 10:56:12 -0700418 if type(egr_ports) == type([]):
419 egr_port_list = egr_ports
420 else:
421 egr_port_list = [egr_ports]
422
Dan Talayco551befa2010-07-15 17:05:32 -0700423 request = message.flow_mod()
424 request.match = match
425 request.buffer_id = 0xffffffff
426 if check_expire:
427 request.flags |= ofp.OFPFF_SEND_FLOW_REM
428 request.hard_timeout = 1
429
430 if action_list is not None:
431 for act in action_list:
432 parent.logger.debug("Adding action " + act.show())
433 rv = request.actions.add(act)
434 parent.assertTrue(rv, "Could not add action" + act.show())
435
436 # Set up output/enqueue action if directed
437 if egr_queue is not None:
Dan Talaycof6e76c02012-03-23 10:56:12 -0700438 parent.assertTrue(egr_ports is not None, "Egress port not set")
Dan Talayco551befa2010-07-15 17:05:32 -0700439 act = action.action_enqueue()
Dan Talaycof6e76c02012-03-23 10:56:12 -0700440 for egr_port in egr_port_list:
441 act.port = egr_port
442 act.queue_id = egr_queue
443 rv = request.actions.add(act)
444 parent.assertTrue(rv, "Could not add enqueue action " +
445 str(egr_port) + " Q: " + str(egr_queue))
446 elif egr_ports is not None:
447 for egr_port in egr_port_list:
448 act = action.action_output()
449 act.port = egr_port
450 rv = request.actions.add(act)
451 parent.assertTrue(rv, "Could not add output action " +
452 str(egr_port))
Dan Talayco551befa2010-07-15 17:05:32 -0700453
454 parent.logger.debug(request.show())
455
456 return request
457
Jeffrey Townsendf3eae9c2012-03-28 18:23:21 -0700458def flow_msg_install(parent, request, clear_table_override=None):
Dan Talayco551befa2010-07-15 17:05:32 -0700459 """
460 Install a flow mod message in the switch
461
462 @param parent Must implement controller, assertEqual, assertTrue
463 @param request The request, all set to go
464 @param clear_table If true, clear the flow table before installing
465 """
Dan Talayco8a64e332012-03-28 14:53:20 -0700466
467 clear_table = test_param_get(parent.config, 'clear_table', default=True)
Jeffrey Townsendf3eae9c2012-03-28 18:23:21 -0700468 if(clear_table_override != None):
469 clear_table = clear_table_override
470
471 if clear_table:
Dan Talayco551befa2010-07-15 17:05:32 -0700472 parent.logger.debug("Clear flow table")
473 rc = delete_all_flows(parent.controller, parent.logger)
474 parent.assertEqual(rc, 0, "Failed to delete all flows")
475 do_barrier(parent.controller)
476
477 parent.logger.debug("Insert flow")
478 rv = parent.controller.message_send(request)
479 parent.assertTrue(rv != -1, "Error installing flow mod")
480 do_barrier(parent.controller)
481
Dan Talaycof6e76c02012-03-23 10:56:12 -0700482def flow_match_test_port_pair(parent, ing_port, egr_ports, wildcards=0,
Dan Talayco551befa2010-07-15 17:05:32 -0700483 dl_vlan=-1, pkt=None, exp_pkt=None,
484 action_list=None, check_expire=False):
485 """
486 Flow match test on single TCP packet
Dan Talaycof6e76c02012-03-23 10:56:12 -0700487 @param egr_ports A single port or list of ports
Dan Talayco551befa2010-07-15 17:05:32 -0700488
489 Run test with packet through switch from ing_port to egr_port
490 See flow_match_test for parameter descriptions
491 """
492
Dan Talaycof6e76c02012-03-23 10:56:12 -0700493 parent.logger.info("Pkt match test: " + str(ing_port) + " to " +
494 str(egr_ports))
Dan Talayco551befa2010-07-15 17:05:32 -0700495 parent.logger.debug(" WC: " + hex(wildcards) + " vlan: " + str(dl_vlan) +
Dan Talayco98fada92010-07-17 00:36:21 -0700496 " expire: " + str(check_expire))
Dan Talayco551befa2010-07-15 17:05:32 -0700497 if pkt is None:
498 pkt = simple_tcp_packet(dl_vlan_enable=(dl_vlan >= 0), dl_vlan=dl_vlan)
499
500 request = flow_msg_create(parent, pkt, ing_port=ing_port,
Dan Talaycof6e76c02012-03-23 10:56:12 -0700501 wildcards=wildcards, egr_ports=egr_ports,
Dan Talayco551befa2010-07-15 17:05:32 -0700502 action_list=action_list)
503
504 flow_msg_install(parent, request)
505
Dan Talaycof6e76c02012-03-23 10:56:12 -0700506 parent.logger.debug("Send packet: " + str(ing_port) + " to " +
507 str(egr_ports))
Dan Talayco551befa2010-07-15 17:05:32 -0700508 parent.dataplane.send(ing_port, str(pkt))
509
510 if exp_pkt is None:
511 exp_pkt = pkt
Dan Talaycoc948d0b2012-03-23 12:17:54 -0700512 receive_pkt_verify(parent, egr_ports, exp_pkt, ing_port)
Dan Talayco551befa2010-07-15 17:05:32 -0700513
514 if check_expire:
515 #@todo Not all HW supports both pkt and byte counters
516 flow_removed_verify(parent, request, pkt_count=1, byte_count=len(pkt))
517
Dan Talaycof6e76c02012-03-23 10:56:12 -0700518def get_egr_list(parent, of_ports, how_many, exclude_list=[]):
519 """
520 Generate a list of ports avoiding those in the exclude list
521 @param parent Supplies logger
522 @param of_ports List of OF port numbers
523 @param how_many Number of ports to be added to the list
524 @param exclude_list List of ports not to be used
525 @returns An empty list if unable to find enough ports
526 """
527
Dan Talaycoc948d0b2012-03-23 12:17:54 -0700528 if how_many == 0:
529 return []
530
Dan Talaycof6e76c02012-03-23 10:56:12 -0700531 count = 0
532 egr_ports = []
533 for egr_idx in range(len(of_ports)):
534 if of_ports[egr_idx] not in exclude_list:
535 egr_ports.append(of_ports[egr_idx])
536 count += 1
537 if count >= how_many:
538 return egr_ports
539 parent.logger.debug("Could not generate enough egress ports for test")
540 return []
541
Dan Talayco551befa2010-07-15 17:05:32 -0700542def flow_match_test(parent, port_map, wildcards=0, dl_vlan=-1, pkt=None,
543 exp_pkt=None, action_list=None, check_expire=False,
Dan Talaycoc948d0b2012-03-23 12:17:54 -0700544 max_test=0, egr_count=1, ing_port=False):
Dan Talayco551befa2010-07-15 17:05:32 -0700545 """
546 Run flow_match_test_port_pair on all port pairs
547
548 @param max_test If > 0 no more than this number of tests are executed.
549 @param parent Must implement controller, dataplane, assertTrue, assertEqual
550 and logger
551 @param pkt If not None, use this packet for ingress
552 @param wildcards For flow match entry
Dan Talayco79184222010-11-01 12:24:29 -0700553 @param dl_vlan If not -1, and pkt is None, create a pkt w/ VLAN tag
Dan Talayco551befa2010-07-15 17:05:32 -0700554 @param exp_pkt If not None, use this as the expected output pkt; els use pkt
555 @param action_list Additional actions to add to flow mod
556 @param check_expire Check for flow expiration message
Dan Talaycocfa172f2012-03-23 12:03:00 -0700557 @param egr_count Number of egress ports; -1 means get from config w/ dflt 2
Dan Talayco551befa2010-07-15 17:05:32 -0700558 """
559 of_ports = port_map.keys()
560 of_ports.sort()
561 parent.assertTrue(len(of_ports) > 1, "Not enough ports for test")
562 test_count = 0
563
Dan Talaycocfa172f2012-03-23 12:03:00 -0700564 if egr_count == -1:
565 egr_count = test_param_get(parent.config, 'egr_count', default=2)
566
Dan Talayco551befa2010-07-15 17:05:32 -0700567 for ing_idx in range(len(of_ports)):
568 ingress_port = of_ports[ing_idx]
Dan Talaycof6e76c02012-03-23 10:56:12 -0700569 egr_ports = get_egr_list(parent, of_ports, egr_count,
570 exclude_list=[ingress_port])
Dan Talaycoc948d0b2012-03-23 12:17:54 -0700571 if ing_port:
572 egr_ports.append(ofp.OFPP_IN_PORT)
Dan Talaycof6e76c02012-03-23 10:56:12 -0700573 if len(egr_ports) == 0:
574 parent.assertTrue(0, "Failed to generate egress port list")
575
576 flow_match_test_port_pair(parent, ingress_port, egr_ports,
577 wildcards=wildcards, dl_vlan=dl_vlan,
578 pkt=pkt, exp_pkt=exp_pkt,
579 action_list=action_list,
580 check_expire=check_expire)
581 test_count += 1
582 if (max_test > 0) and (test_count > max_test):
583 parent.logger.info("Ran " + str(test_count) + " tests; exiting")
584 return
Dan Talayco551befa2010-07-15 17:05:32 -0700585
Dan Talayco4b2bee62010-07-20 14:10:05 -0700586def test_param_get(config, key, default=None):
587 """
588 Return value passed via test-params if present
589
590 @param config The configuration structure for OFTest
591 @param key The lookup key
592 @param default Default value to use if not found
593
594 If the pair 'key=val' appeared in the string passed to --test-params
595 on the command line, return val (as interpreted by exec). Otherwise
596 return default value.
Dan Talaycof6e76c02012-03-23 10:56:12 -0700597
598 WARNING: TEST PARAMETERS MUST BE PYTHON IDENTIFIERS;
599 eg egr_count, not egr-count.
Dan Talayco4b2bee62010-07-20 14:10:05 -0700600 """
601 try:
602 exec config["test_params"]
603 except:
604 return default
605
606 s = "val = " + str(key)
607 try:
608 exec s
609 return val
610 except:
611 return default
612
613def action_generate(parent, field_to_mod, mod_field_vals):
614 """
615 Create an action to modify the field indicated in field_to_mod
616
617 @param parent Must implement, assertTrue
618 @param field_to_mod The field to modify as a string name
619 @param mod_field_vals Hash of values to use for modified values
620 """
621
622 act = None
623
624 if field_to_mod in ['pktlen']:
625 return None
626
627 if field_to_mod == 'dl_dst':
628 act = action.action_set_dl_dst()
629 act.dl_addr = parse.parse_mac(mod_field_vals['dl_dst'])
630 elif field_to_mod == 'dl_src':
631 act = action.action_set_dl_src()
632 act.dl_addr = parse.parse_mac(mod_field_vals['dl_src'])
633 elif field_to_mod == 'dl_vlan_enable':
634 if not mod_field_vals['dl_vlan_enable']: # Strip VLAN tag
635 act = action.action_strip_vlan()
636 # Add VLAN tag is handled by dl_vlan field
637 # Will return None in this case
638 elif field_to_mod == 'dl_vlan':
639 act = action.action_set_vlan_vid()
640 act.vlan_vid = mod_field_vals['dl_vlan']
641 elif field_to_mod == 'dl_vlan_pcp':
642 act = action.action_set_vlan_pcp()
643 act.vlan_pcp = mod_field_vals['dl_vlan_pcp']
644 elif field_to_mod == 'ip_src':
645 act = action.action_set_nw_src()
646 act.nw_addr = parse.parse_ip(mod_field_vals['ip_src'])
647 elif field_to_mod == 'ip_dst':
648 act = action.action_set_nw_dst()
649 act.nw_addr = parse.parse_ip(mod_field_vals['ip_dst'])
650 elif field_to_mod == 'ip_tos':
651 act = action.action_set_nw_tos()
652 act.nw_tos = mod_field_vals['ip_tos']
653 elif field_to_mod == 'tcp_sport':
654 act = action.action_set_tp_src()
655 act.tp_port = mod_field_vals['tcp_sport']
656 elif field_to_mod == 'tcp_dport':
657 act = action.action_set_tp_dst()
658 act.tp_port = mod_field_vals['tcp_dport']
659 else:
660 parent.assertTrue(0, "Unknown field to modify: " + str(field_to_mod))
661
662 return act
663
664def pkt_action_setup(parent, start_field_vals={}, mod_field_vals={},
665 mod_fields={}, check_test_params=False):
666 """
667 Set up the ingress and expected packet and action list for a test
668
669 @param parent Must implement, assertTrue, config hash and logger
670 @param start_field_values Field values to use for ingress packet (optional)
671 @param mod_field_values Field values to use for modified packet (optional)
672 @param mod_fields The list of fields to be modified by the switch in the test.
673 @params check_test_params If True, will check the parameters vid, add_vlan
674 and strip_vlan from the command line.
675
676 Returns a triple: pkt-to-send, expected-pkt, action-list
677 """
678
679 new_actions = []
680
Dan Talayco4b2bee62010-07-20 14:10:05 -0700681 base_pkt_params = {}
682 base_pkt_params['pktlen'] = 100
683 base_pkt_params['dl_dst'] = '00:DE:F0:12:34:56'
684 base_pkt_params['dl_src'] = '00:23:45:67:89:AB'
685 base_pkt_params['dl_vlan_enable'] = False
686 base_pkt_params['dl_vlan'] = 2
687 base_pkt_params['dl_vlan_pcp'] = 0
688 base_pkt_params['ip_src'] = '192.168.0.1'
689 base_pkt_params['ip_dst'] = '192.168.0.2'
690 base_pkt_params['ip_tos'] = 0
691 base_pkt_params['tcp_sport'] = 1234
692 base_pkt_params['tcp_dport'] = 80
693 for keyname in start_field_vals.keys():
694 base_pkt_params[keyname] = start_field_vals[keyname]
695
696 mod_pkt_params = {}
697 mod_pkt_params['pktlen'] = 100
698 mod_pkt_params['dl_dst'] = '00:21:0F:ED:CB:A9'
699 mod_pkt_params['dl_src'] = '00:ED:CB:A9:87:65'
700 mod_pkt_params['dl_vlan_enable'] = False
701 mod_pkt_params['dl_vlan'] = 3
702 mod_pkt_params['dl_vlan_pcp'] = 7
703 mod_pkt_params['ip_src'] = '10.20.30.40'
704 mod_pkt_params['ip_dst'] = '50.60.70.80'
705 mod_pkt_params['ip_tos'] = 0xf0
706 mod_pkt_params['tcp_sport'] = 4321
707 mod_pkt_params['tcp_dport'] = 8765
708 for keyname in mod_field_vals.keys():
709 mod_pkt_params[keyname] = mod_field_vals[keyname]
710
711 # Check for test param modifications
712 strip = False
713 if check_test_params:
714 add_vlan = test_param_get(parent.config, 'add_vlan')
715 strip_vlan = test_param_get(parent.config, 'strip_vlan')
716 vid = test_param_get(parent.config, 'vid')
717
718 if add_vlan and strip_vlan:
719 parent.assertTrue(0, "Add and strip VLAN both specified")
720
721 if vid:
722 base_pkt_params['dl_vlan_enable'] = True
723 base_pkt_params['dl_vlan'] = vid
724 if 'dl_vlan' in mod_fields:
725 mod_pkt_params['dl_vlan'] = vid + 1
726
727 if add_vlan:
728 base_pkt_params['dl_vlan_enable'] = False
729 mod_pkt_params['dl_vlan_enable'] = True
730 mod_pkt_params['pktlen'] = base_pkt_params['pktlen'] + 4
731 mod_fields.append('pktlen')
732 mod_fields.append('dl_vlan_enable')
733 if 'dl_vlan' not in mod_fields:
734 mod_fields.append('dl_vlan')
735 elif strip_vlan:
736 base_pkt_params['dl_vlan_enable'] = True
737 mod_pkt_params['dl_vlan_enable'] = False
738 mod_pkt_params['pktlen'] = base_pkt_params['pktlen'] - 4
739 mod_fields.append('dl_vlan_enable')
740 mod_fields.append('pktlen')
741
742 # Build the ingress packet
743 ingress_pkt = simple_tcp_packet(**base_pkt_params)
744
745 # Build the expected packet, modifying the indicated fields
746 for item in mod_fields:
747 base_pkt_params[item] = mod_pkt_params[item]
748 act = action_generate(parent, item, mod_pkt_params)
749 if act:
750 new_actions.append(act)
751
752 expected_pkt = simple_tcp_packet(**base_pkt_params)
753
754 return (ingress_pkt, expected_pkt, new_actions)
Dan Talayco677c0b72011-08-23 22:53:38 -0700755
756# Generate a simple "drop" flow mod
757# If in_band is true, then only drop from first test port
758def flow_mod_gen(port_map, in_band):
759 request = message.flow_mod()
760 request.match.wildcards = ofp.OFPFW_ALL
761 if in_band:
762 request.match.wildcards = ofp.OFPFW_ALL - ofp.OFPFW_IN_PORT
763 for of_port, ifname in port_map.items(): # Grab first port
764 break
765 request.match.in_port = of_port
766 request.buffer_id = 0xffffffff
767 return request
Dan Talaycoba3745c2010-07-21 21:51:08 -0700768
769def skip_message_emit(parent, s):
770 """
771 Print out a 'skipped' message to stderr
772
773 @param s The string to print out to the log file
774 @param parent Must implement config and logger objects
775 """
776 global skipped_test_count
777
778 skipped_test_count += 1
779 parent.logger.info("Skipping: " + s)
780 if parent.config["dbg_level"] < logging.WARNING:
781 sys.stderr.write("(skipped) ")
782 else:
783 sys.stderr.write("(S)")
Dan Talayco677c0b72011-08-23 22:53:38 -0700784
Dan Talayco8a64e332012-03-28 14:53:20 -0700785
786def all_stats_get(parent):
787 """
788 Get the aggregate stats for all flows in the table
789 @param parent Test instance with controller connection and assert
790 @returns dict with keys flows, packets, bytes, active (flows),
791 lookups, matched
792 """
793 stat_req = message.aggregate_stats_request()
794 stat_req.match = ofp.ofp_match()
795 stat_req.match.wildcards = ofp.OFPFW_ALL
796 stat_req.table_id = 0xff
797 stat_req.out_port = ofp.OFPP_NONE
798
799 rv = {}
800
801 (reply, pkt) = parent.controller.transact(stat_req, timeout=2)
802 parent.assertTrue(len(reply.stats) == 1, "Did not receive flow stats reply")
803
804 for obj in reply.stats:
805 (rv["flows"], rv["packets"], rv["bytes"]) = (obj.flow_count,
806 obj.packet_count, obj.byte_count)
807 break
808
809 request = message.table_stats_request()
810 (reply , pkt) = parent.controller.transact(request, timeout=2)
811
812
813 (rv["active"], rv["lookups"], rv["matched"]) = (0,0,0)
814 for obj in reply.stats:
815 rv["active"] += obj.active_count
816 rv["lookups"] += obj.lookup_count
817 rv["matched"] += obj.matched_count
818
819 return rv
Dan Talayco2baf8b52012-03-30 09:55:42 -0700820
821FILTER=''.join([(len(repr(chr(x)))==3) and chr(x) or '.'
822 for x in range(256)])
823
824def hex_dump_buffer(src, length=16):
825 """
826 Convert src to a hex dump string and return the string
827 @param src The source buffer
828 @param length The number of bytes shown in each line
829 @returns A string showing the hex dump
830 """
831 result = []
832 for i in xrange(0, len(src), length):
833 chars = src[i:i+length]
834 hex = ' '.join(["%02x" % ord(x) for x in chars])
835 printable = ''.join(["%s" % ((ord(x) <= 127 and
836 FILTER[ord(x)]) or '.') for x in chars])
837 result.append("%04x %-*s %s\n" % (i, length*3, hex, printable))
838 return ''.join(result)
839
840def format_packet(pkt):
841 return "Packet length %d \n%s" % (len(str(pkt)),
842 hex_dump_buffer(str(pkt)))