blob: 221fc67d2049af9877eb902207aa441e93fd0d43 [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
Dan Talayco8a64e332012-03-28 14:53:20 -0700458def flow_msg_install(parent, request):
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)
Dan Talayco551befa2010-07-15 17:05:32 -0700468 if clear_table:
469 parent.logger.debug("Clear flow table")
470 rc = delete_all_flows(parent.controller, parent.logger)
471 parent.assertEqual(rc, 0, "Failed to delete all flows")
472 do_barrier(parent.controller)
473
474 parent.logger.debug("Insert flow")
475 rv = parent.controller.message_send(request)
476 parent.assertTrue(rv != -1, "Error installing flow mod")
477 do_barrier(parent.controller)
478
Dan Talaycof6e76c02012-03-23 10:56:12 -0700479def flow_match_test_port_pair(parent, ing_port, egr_ports, wildcards=0,
Dan Talayco551befa2010-07-15 17:05:32 -0700480 dl_vlan=-1, pkt=None, exp_pkt=None,
481 action_list=None, check_expire=False):
482 """
483 Flow match test on single TCP packet
Dan Talaycof6e76c02012-03-23 10:56:12 -0700484 @param egr_ports A single port or list of ports
Dan Talayco551befa2010-07-15 17:05:32 -0700485
486 Run test with packet through switch from ing_port to egr_port
487 See flow_match_test for parameter descriptions
488 """
489
Dan Talaycof6e76c02012-03-23 10:56:12 -0700490 parent.logger.info("Pkt match test: " + str(ing_port) + " to " +
491 str(egr_ports))
Dan Talayco551befa2010-07-15 17:05:32 -0700492 parent.logger.debug(" WC: " + hex(wildcards) + " vlan: " + str(dl_vlan) +
Dan Talayco98fada92010-07-17 00:36:21 -0700493 " expire: " + str(check_expire))
Dan Talayco551befa2010-07-15 17:05:32 -0700494 if pkt is None:
495 pkt = simple_tcp_packet(dl_vlan_enable=(dl_vlan >= 0), dl_vlan=dl_vlan)
496
497 request = flow_msg_create(parent, pkt, ing_port=ing_port,
Dan Talaycof6e76c02012-03-23 10:56:12 -0700498 wildcards=wildcards, egr_ports=egr_ports,
Dan Talayco551befa2010-07-15 17:05:32 -0700499 action_list=action_list)
500
501 flow_msg_install(parent, request)
502
Dan Talaycof6e76c02012-03-23 10:56:12 -0700503 parent.logger.debug("Send packet: " + str(ing_port) + " to " +
504 str(egr_ports))
Dan Talayco551befa2010-07-15 17:05:32 -0700505 parent.dataplane.send(ing_port, str(pkt))
506
507 if exp_pkt is None:
508 exp_pkt = pkt
Dan Talaycoc948d0b2012-03-23 12:17:54 -0700509 receive_pkt_verify(parent, egr_ports, exp_pkt, ing_port)
Dan Talayco551befa2010-07-15 17:05:32 -0700510
511 if check_expire:
512 #@todo Not all HW supports both pkt and byte counters
513 flow_removed_verify(parent, request, pkt_count=1, byte_count=len(pkt))
514
Dan Talaycof6e76c02012-03-23 10:56:12 -0700515def get_egr_list(parent, of_ports, how_many, exclude_list=[]):
516 """
517 Generate a list of ports avoiding those in the exclude list
518 @param parent Supplies logger
519 @param of_ports List of OF port numbers
520 @param how_many Number of ports to be added to the list
521 @param exclude_list List of ports not to be used
522 @returns An empty list if unable to find enough ports
523 """
524
Dan Talaycoc948d0b2012-03-23 12:17:54 -0700525 if how_many == 0:
526 return []
527
Dan Talaycof6e76c02012-03-23 10:56:12 -0700528 count = 0
529 egr_ports = []
530 for egr_idx in range(len(of_ports)):
531 if of_ports[egr_idx] not in exclude_list:
532 egr_ports.append(of_ports[egr_idx])
533 count += 1
534 if count >= how_many:
535 return egr_ports
536 parent.logger.debug("Could not generate enough egress ports for test")
537 return []
538
Dan Talayco551befa2010-07-15 17:05:32 -0700539def flow_match_test(parent, port_map, wildcards=0, dl_vlan=-1, pkt=None,
540 exp_pkt=None, action_list=None, check_expire=False,
Dan Talaycoc948d0b2012-03-23 12:17:54 -0700541 max_test=0, egr_count=1, ing_port=False):
Dan Talayco551befa2010-07-15 17:05:32 -0700542 """
543 Run flow_match_test_port_pair on all port pairs
544
545 @param max_test If > 0 no more than this number of tests are executed.
546 @param parent Must implement controller, dataplane, assertTrue, assertEqual
547 and logger
548 @param pkt If not None, use this packet for ingress
549 @param wildcards For flow match entry
Dan Talayco79184222010-11-01 12:24:29 -0700550 @param dl_vlan If not -1, and pkt is None, create a pkt w/ VLAN tag
Dan Talayco551befa2010-07-15 17:05:32 -0700551 @param exp_pkt If not None, use this as the expected output pkt; els use pkt
552 @param action_list Additional actions to add to flow mod
553 @param check_expire Check for flow expiration message
Dan Talaycocfa172f2012-03-23 12:03:00 -0700554 @param egr_count Number of egress ports; -1 means get from config w/ dflt 2
Dan Talayco551befa2010-07-15 17:05:32 -0700555 """
556 of_ports = port_map.keys()
557 of_ports.sort()
558 parent.assertTrue(len(of_ports) > 1, "Not enough ports for test")
559 test_count = 0
560
Dan Talaycocfa172f2012-03-23 12:03:00 -0700561 if egr_count == -1:
562 egr_count = test_param_get(parent.config, 'egr_count', default=2)
563
Dan Talayco551befa2010-07-15 17:05:32 -0700564 for ing_idx in range(len(of_ports)):
565 ingress_port = of_ports[ing_idx]
Dan Talaycof6e76c02012-03-23 10:56:12 -0700566 egr_ports = get_egr_list(parent, of_ports, egr_count,
567 exclude_list=[ingress_port])
Dan Talaycoc948d0b2012-03-23 12:17:54 -0700568 if ing_port:
569 egr_ports.append(ofp.OFPP_IN_PORT)
Dan Talaycof6e76c02012-03-23 10:56:12 -0700570 if len(egr_ports) == 0:
571 parent.assertTrue(0, "Failed to generate egress port list")
572
573 flow_match_test_port_pair(parent, ingress_port, egr_ports,
574 wildcards=wildcards, dl_vlan=dl_vlan,
575 pkt=pkt, exp_pkt=exp_pkt,
576 action_list=action_list,
577 check_expire=check_expire)
578 test_count += 1
579 if (max_test > 0) and (test_count > max_test):
580 parent.logger.info("Ran " + str(test_count) + " tests; exiting")
581 return
Dan Talayco551befa2010-07-15 17:05:32 -0700582
Dan Talayco4b2bee62010-07-20 14:10:05 -0700583def test_param_get(config, key, default=None):
584 """
585 Return value passed via test-params if present
586
587 @param config The configuration structure for OFTest
588 @param key The lookup key
589 @param default Default value to use if not found
590
591 If the pair 'key=val' appeared in the string passed to --test-params
592 on the command line, return val (as interpreted by exec). Otherwise
593 return default value.
Dan Talaycof6e76c02012-03-23 10:56:12 -0700594
595 WARNING: TEST PARAMETERS MUST BE PYTHON IDENTIFIERS;
596 eg egr_count, not egr-count.
Dan Talayco4b2bee62010-07-20 14:10:05 -0700597 """
598 try:
599 exec config["test_params"]
600 except:
601 return default
602
603 s = "val = " + str(key)
604 try:
605 exec s
606 return val
607 except:
608 return default
609
610def action_generate(parent, field_to_mod, mod_field_vals):
611 """
612 Create an action to modify the field indicated in field_to_mod
613
614 @param parent Must implement, assertTrue
615 @param field_to_mod The field to modify as a string name
616 @param mod_field_vals Hash of values to use for modified values
617 """
618
619 act = None
620
621 if field_to_mod in ['pktlen']:
622 return None
623
624 if field_to_mod == 'dl_dst':
625 act = action.action_set_dl_dst()
626 act.dl_addr = parse.parse_mac(mod_field_vals['dl_dst'])
627 elif field_to_mod == 'dl_src':
628 act = action.action_set_dl_src()
629 act.dl_addr = parse.parse_mac(mod_field_vals['dl_src'])
630 elif field_to_mod == 'dl_vlan_enable':
631 if not mod_field_vals['dl_vlan_enable']: # Strip VLAN tag
632 act = action.action_strip_vlan()
633 # Add VLAN tag is handled by dl_vlan field
634 # Will return None in this case
635 elif field_to_mod == 'dl_vlan':
636 act = action.action_set_vlan_vid()
637 act.vlan_vid = mod_field_vals['dl_vlan']
638 elif field_to_mod == 'dl_vlan_pcp':
639 act = action.action_set_vlan_pcp()
640 act.vlan_pcp = mod_field_vals['dl_vlan_pcp']
641 elif field_to_mod == 'ip_src':
642 act = action.action_set_nw_src()
643 act.nw_addr = parse.parse_ip(mod_field_vals['ip_src'])
644 elif field_to_mod == 'ip_dst':
645 act = action.action_set_nw_dst()
646 act.nw_addr = parse.parse_ip(mod_field_vals['ip_dst'])
647 elif field_to_mod == 'ip_tos':
648 act = action.action_set_nw_tos()
649 act.nw_tos = mod_field_vals['ip_tos']
650 elif field_to_mod == 'tcp_sport':
651 act = action.action_set_tp_src()
652 act.tp_port = mod_field_vals['tcp_sport']
653 elif field_to_mod == 'tcp_dport':
654 act = action.action_set_tp_dst()
655 act.tp_port = mod_field_vals['tcp_dport']
656 else:
657 parent.assertTrue(0, "Unknown field to modify: " + str(field_to_mod))
658
659 return act
660
661def pkt_action_setup(parent, start_field_vals={}, mod_field_vals={},
662 mod_fields={}, check_test_params=False):
663 """
664 Set up the ingress and expected packet and action list for a test
665
666 @param parent Must implement, assertTrue, config hash and logger
667 @param start_field_values Field values to use for ingress packet (optional)
668 @param mod_field_values Field values to use for modified packet (optional)
669 @param mod_fields The list of fields to be modified by the switch in the test.
670 @params check_test_params If True, will check the parameters vid, add_vlan
671 and strip_vlan from the command line.
672
673 Returns a triple: pkt-to-send, expected-pkt, action-list
674 """
675
676 new_actions = []
677
Dan Talayco4b2bee62010-07-20 14:10:05 -0700678 base_pkt_params = {}
679 base_pkt_params['pktlen'] = 100
680 base_pkt_params['dl_dst'] = '00:DE:F0:12:34:56'
681 base_pkt_params['dl_src'] = '00:23:45:67:89:AB'
682 base_pkt_params['dl_vlan_enable'] = False
683 base_pkt_params['dl_vlan'] = 2
684 base_pkt_params['dl_vlan_pcp'] = 0
685 base_pkt_params['ip_src'] = '192.168.0.1'
686 base_pkt_params['ip_dst'] = '192.168.0.2'
687 base_pkt_params['ip_tos'] = 0
688 base_pkt_params['tcp_sport'] = 1234
689 base_pkt_params['tcp_dport'] = 80
690 for keyname in start_field_vals.keys():
691 base_pkt_params[keyname] = start_field_vals[keyname]
692
693 mod_pkt_params = {}
694 mod_pkt_params['pktlen'] = 100
695 mod_pkt_params['dl_dst'] = '00:21:0F:ED:CB:A9'
696 mod_pkt_params['dl_src'] = '00:ED:CB:A9:87:65'
697 mod_pkt_params['dl_vlan_enable'] = False
698 mod_pkt_params['dl_vlan'] = 3
699 mod_pkt_params['dl_vlan_pcp'] = 7
700 mod_pkt_params['ip_src'] = '10.20.30.40'
701 mod_pkt_params['ip_dst'] = '50.60.70.80'
702 mod_pkt_params['ip_tos'] = 0xf0
703 mod_pkt_params['tcp_sport'] = 4321
704 mod_pkt_params['tcp_dport'] = 8765
705 for keyname in mod_field_vals.keys():
706 mod_pkt_params[keyname] = mod_field_vals[keyname]
707
708 # Check for test param modifications
709 strip = False
710 if check_test_params:
711 add_vlan = test_param_get(parent.config, 'add_vlan')
712 strip_vlan = test_param_get(parent.config, 'strip_vlan')
713 vid = test_param_get(parent.config, 'vid')
714
715 if add_vlan and strip_vlan:
716 parent.assertTrue(0, "Add and strip VLAN both specified")
717
718 if vid:
719 base_pkt_params['dl_vlan_enable'] = True
720 base_pkt_params['dl_vlan'] = vid
721 if 'dl_vlan' in mod_fields:
722 mod_pkt_params['dl_vlan'] = vid + 1
723
724 if add_vlan:
725 base_pkt_params['dl_vlan_enable'] = False
726 mod_pkt_params['dl_vlan_enable'] = True
727 mod_pkt_params['pktlen'] = base_pkt_params['pktlen'] + 4
728 mod_fields.append('pktlen')
729 mod_fields.append('dl_vlan_enable')
730 if 'dl_vlan' not in mod_fields:
731 mod_fields.append('dl_vlan')
732 elif strip_vlan:
733 base_pkt_params['dl_vlan_enable'] = True
734 mod_pkt_params['dl_vlan_enable'] = False
735 mod_pkt_params['pktlen'] = base_pkt_params['pktlen'] - 4
736 mod_fields.append('dl_vlan_enable')
737 mod_fields.append('pktlen')
738
739 # Build the ingress packet
740 ingress_pkt = simple_tcp_packet(**base_pkt_params)
741
742 # Build the expected packet, modifying the indicated fields
743 for item in mod_fields:
744 base_pkt_params[item] = mod_pkt_params[item]
745 act = action_generate(parent, item, mod_pkt_params)
746 if act:
747 new_actions.append(act)
748
749 expected_pkt = simple_tcp_packet(**base_pkt_params)
750
751 return (ingress_pkt, expected_pkt, new_actions)
Dan Talayco677c0b72011-08-23 22:53:38 -0700752
753# Generate a simple "drop" flow mod
754# If in_band is true, then only drop from first test port
755def flow_mod_gen(port_map, in_band):
756 request = message.flow_mod()
757 request.match.wildcards = ofp.OFPFW_ALL
758 if in_band:
759 request.match.wildcards = ofp.OFPFW_ALL - ofp.OFPFW_IN_PORT
760 for of_port, ifname in port_map.items(): # Grab first port
761 break
762 request.match.in_port = of_port
763 request.buffer_id = 0xffffffff
764 return request
Dan Talaycoba3745c2010-07-21 21:51:08 -0700765
766def skip_message_emit(parent, s):
767 """
768 Print out a 'skipped' message to stderr
769
770 @param s The string to print out to the log file
771 @param parent Must implement config and logger objects
772 """
773 global skipped_test_count
774
775 skipped_test_count += 1
776 parent.logger.info("Skipping: " + s)
777 if parent.config["dbg_level"] < logging.WARNING:
778 sys.stderr.write("(skipped) ")
779 else:
780 sys.stderr.write("(S)")
Dan Talayco677c0b72011-08-23 22:53:38 -0700781
Dan Talayco8a64e332012-03-28 14:53:20 -0700782
783def all_stats_get(parent):
784 """
785 Get the aggregate stats for all flows in the table
786 @param parent Test instance with controller connection and assert
787 @returns dict with keys flows, packets, bytes, active (flows),
788 lookups, matched
789 """
790 stat_req = message.aggregate_stats_request()
791 stat_req.match = ofp.ofp_match()
792 stat_req.match.wildcards = ofp.OFPFW_ALL
793 stat_req.table_id = 0xff
794 stat_req.out_port = ofp.OFPP_NONE
795
796 rv = {}
797
798 (reply, pkt) = parent.controller.transact(stat_req, timeout=2)
799 parent.assertTrue(len(reply.stats) == 1, "Did not receive flow stats reply")
800
801 for obj in reply.stats:
802 (rv["flows"], rv["packets"], rv["bytes"]) = (obj.flow_count,
803 obj.packet_count, obj.byte_count)
804 break
805
806 request = message.table_stats_request()
807 (reply , pkt) = parent.controller.transact(request, timeout=2)
808
809
810 (rv["active"], rv["lookups"], rv["matched"]) = (0,0,0)
811 for obj in reply.stats:
812 rv["active"] += obj.active_count
813 rv["lookups"] += obj.lookup_count
814 rv["matched"] += obj.matched_count
815
816 return rv