blob: daa8ce1eb727ad717edeeea56ebf9dae22768f16 [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
Jeffrey Townsend5cb7ed32012-08-17 18:11:01 +000030MINSIZE = 0
31
Dan Talayco98fada92010-07-17 00:36:21 -070032def clear_switch(parent, port_list, logger):
33 """
34 Clear the switch configuration
35
36 @param parent Object implementing controller and assert equal
37 @param logger Logging object
38 """
39 for port in port_list:
40 clear_port_config(parent, port, logger)
41 delete_all_flows(parent.controller, logger)
42
Dan Talayco41eae8b2010-03-10 13:57:06 -080043def delete_all_flows(ctrl, logger):
44 """
45 Delete all flows on the switch
46 @param ctrl The controller object for the test
47 @param logger Logging object
48 """
49
Dan Talaycoc901f4d2010-03-07 21:55:45 -080050 logger.info("Deleting all flows")
51 msg = message.flow_mod()
52 msg.match.wildcards = ofp.OFPFW_ALL
Dan Talayco41eae8b2010-03-10 13:57:06 -080053 msg.out_port = ofp.OFPP_NONE
Dan Talaycoc901f4d2010-03-07 21:55:45 -080054 msg.command = ofp.OFPFC_DELETE
55 msg.buffer_id = 0xffffffff
Dan Talayco41eae8b2010-03-10 13:57:06 -080056 return ctrl.message_send(msg)
57
Dan Talayco98fada92010-07-17 00:36:21 -070058def clear_port_config(parent, port, logger):
59 """
60 Clear the port configuration (currently only no flood setting)
61
62 @param parent Object implementing controller and assert equal
63 @param logger Logging object
64 """
65 rv = port_config_set(parent.controller, port,
66 0, ofp.OFPPC_NO_FLOOD, logger)
67 self.assertEqual(rv, 0, "Failed to reset port config")
68
Dan Talayco41eae8b2010-03-10 13:57:06 -080069def simple_tcp_packet(pktlen=100,
70 dl_dst='00:01:02:03:04:05',
71 dl_src='00:06:07:08:09:0a',
Tatsuya Yabe460321e2010-05-25 17:50:49 -070072 dl_vlan_enable=False,
73 dl_vlan=0,
74 dl_vlan_pcp=0,
Dan Talayco551befa2010-07-15 17:05:32 -070075 dl_vlan_cfi=0,
Dan Talayco41eae8b2010-03-10 13:57:06 -080076 ip_src='192.168.0.1',
77 ip_dst='192.168.0.2',
Tatsuya Yabe460321e2010-05-25 17:50:49 -070078 ip_tos=0,
Dan Talayco41eae8b2010-03-10 13:57:06 -080079 tcp_sport=1234,
80 tcp_dport=80
81 ):
82 """
83 Return a simple dataplane TCP packet
84
85 Supports a few parameters:
86 @param len Length of packet in bytes w/o CRC
87 @param dl_dst Destinatino MAC
88 @param dl_src Source MAC
Tatsuya Yabe460321e2010-05-25 17:50:49 -070089 @param dl_vlan_enable True if the packet is with vlan, False otherwise
90 @param dl_vlan VLAN ID
91 @param dl_vlan_pcp VLAN priority
Dan Talayco41eae8b2010-03-10 13:57:06 -080092 @param ip_src IP source
93 @param ip_dst IP destination
Tatsuya Yabe460321e2010-05-25 17:50:49 -070094 @param ip_tos IP ToS
Dan Talayco41eae8b2010-03-10 13:57:06 -080095 @param tcp_dport TCP destination port
96 @param ip_sport TCP source port
97
98 Generates a simple TCP request. Users
99 shouldn't assume anything about this packet other than that
100 it is a valid ethernet/IP/TCP frame.
101 """
Jeffrey Townsend5cb7ed32012-08-17 18:11:01 +0000102
103 if MINSIZE > pktlen:
104 pktlen = MINSIZE
105
Dan Talayco551befa2010-07-15 17:05:32 -0700106 # Note Dot1Q.id is really CFI
Tatsuya Yabe460321e2010-05-25 17:50:49 -0700107 if (dl_vlan_enable):
108 pkt = scapy.Ether(dst=dl_dst, src=dl_src)/ \
Dan Talayco551befa2010-07-15 17:05:32 -0700109 scapy.Dot1Q(prio=dl_vlan_pcp, id=dl_vlan_cfi, vlan=dl_vlan)/ \
Tatsuya Yabe460321e2010-05-25 17:50:49 -0700110 scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos)/ \
111 scapy.TCP(sport=tcp_sport, dport=tcp_dport)
112 else:
113 pkt = scapy.Ether(dst=dl_dst, src=dl_src)/ \
114 scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos)/ \
115 scapy.TCP(sport=tcp_sport, dport=tcp_dport)
116
Dan Talayco41eae8b2010-03-10 13:57:06 -0800117 pkt = pkt/("D" * (pktlen - len(pkt)))
118
119 return pkt
120
Tatsuya Yabeb8fb3c32010-06-14 15:48:36 -0700121def simple_icmp_packet(pktlen=60,
122 dl_dst='00:01:02:03:04:05',
123 dl_src='00:06:07:08:09:0a',
124 dl_vlan_enable=False,
125 dl_vlan=0,
126 dl_vlan_pcp=0,
127 ip_src='192.168.0.1',
128 ip_dst='192.168.0.2',
129 ip_tos=0,
130 icmp_type=8,
131 icmp_code=0
132 ):
133 """
134 Return a simple ICMP packet
135
136 Supports a few parameters:
137 @param len Length of packet in bytes w/o CRC
138 @param dl_dst Destinatino MAC
139 @param dl_src Source MAC
140 @param dl_vlan_enable True if the packet is with vlan, False otherwise
141 @param dl_vlan VLAN ID
142 @param dl_vlan_pcp VLAN priority
143 @param ip_src IP source
144 @param ip_dst IP destination
145 @param ip_tos IP ToS
146 @param icmp_type ICMP type
147 @param icmp_code ICMP code
148
149 Generates a simple ICMP ECHO REQUEST. Users
150 shouldn't assume anything about this packet other than that
151 it is a valid ethernet/ICMP frame.
152 """
Jeffrey Townsend5cb7ed32012-08-17 18:11:01 +0000153
154 if MINSIZE > pktlen:
155 pktlen = MINSIZE
156
Tatsuya Yabeb8fb3c32010-06-14 15:48:36 -0700157 if (dl_vlan_enable):
158 pkt = scapy.Ether(dst=dl_dst, src=dl_src)/ \
159 scapy.Dot1Q(prio=dl_vlan_pcp, id=0, vlan=dl_vlan)/ \
160 scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos)/ \
161 scapy.ICMP(type=icmp_type, code=icmp_code)
162 else:
163 pkt = scapy.Ether(dst=dl_dst, src=dl_src)/ \
164 scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos)/ \
165 scapy.ICMP(type=icmp_type, code=icmp_code)
166
167 pkt = pkt/("0" * (pktlen - len(pkt)))
168
169 return pkt
170
Ed Swierk0aeff8c2012-03-23 20:27:18 -0700171def simple_eth_packet(pktlen=60,
172 dl_dst='00:01:02:03:04:05',
173 dl_src='01:80:c2:00:00:00',
174 dl_type=0x88cc):
Jeffrey Townsend5cb7ed32012-08-17 18:11:01 +0000175
176 if MINSIZE > pktlen:
177 pktlen = MINSIZE
178
Ed Swierk0aeff8c2012-03-23 20:27:18 -0700179 pkt = scapy.Ether(dst=dl_dst, src=dl_src, type=dl_type)
180
181 pkt = pkt/("0" * (pktlen - len(pkt)))
182
183 return pkt
184
Dan Talayco41eae8b2010-03-10 13:57:06 -0800185def do_barrier(ctrl):
Dan Talayco0fc08bd2012-04-09 16:56:18 -0700186 """
187 Do a barrier command
188 Return 0 on success, -1 on error
189 """
Dan Talayco41eae8b2010-03-10 13:57:06 -0800190 b = message.barrier_request()
Dan Talaycof6b94832012-04-12 21:50:57 -0700191 (resp, pkt) = ctrl.transact(b)
192 # We'll trust the transaction processing in the controller that xid matched
Dan Talayco0fc08bd2012-04-09 16:56:18 -0700193 if not resp:
194 return -1
195 return 0
Dan Talayco92c99122010-06-03 13:53:18 -0700196
197def port_config_get(controller, port_no, logger):
198 """
199 Get a port's configuration
200
201 Gets the switch feature configuration and grabs one port's
202 configuration
203
204 @returns (hwaddr, config, advert) The hwaddress, configuration and
205 advertised values
206 """
207 request = message.features_request()
Rich Lanec8aaa3e2012-07-26 19:28:02 -0700208 reply, pkt = controller.transact(request)
Dan Talayco92c99122010-06-03 13:53:18 -0700209 logger.debug(reply.show())
210 if reply is None:
211 logger.warn("Get feature request failed")
212 return None, None, None
213 for idx in range(len(reply.ports)):
214 if reply.ports[idx].port_no == port_no:
215 return (reply.ports[idx].hw_addr, reply.ports[idx].config,
216 reply.ports[idx].advertised)
217
218 logger.warn("Did not find port number for port config")
219 return None, None, None
220
221def port_config_set(controller, port_no, config, mask, logger):
222 """
223 Set the port configuration according the given parameters
224
225 Gets the switch feature configuration and updates one port's
226 configuration value according to config and mask
227 """
228 logger.info("Setting port " + str(port_no) + " to config " + str(config))
229 request = message.features_request()
Rich Lanec8aaa3e2012-07-26 19:28:02 -0700230 reply, pkt = controller.transact(request)
Dan Talayco92c99122010-06-03 13:53:18 -0700231 if reply is None:
232 return -1
233 logger.debug(reply.show())
234 for idx in range(len(reply.ports)):
235 if reply.ports[idx].port_no == port_no:
236 break
237 if idx >= len(reply.ports):
238 return -1
239 mod = message.port_mod()
240 mod.port_no = port_no
241 mod.hw_addr = reply.ports[idx].hw_addr
242 mod.config = config
243 mod.mask = mask
244 mod.advertise = reply.ports[idx].advertised
245 rv = controller.message_send(mod)
246 return rv
247
Ken Chiang1bf01602012-04-04 10:48:23 -0700248def receive_pkt_check(dp, pkt, yes_ports, no_ports, assert_if, logger,
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700249 config):
Dan Talayco92c99122010-06-03 13:53:18 -0700250 """
251 Check for proper receive packets across all ports
Ken Chiang1bf01602012-04-04 10:48:23 -0700252 @param dp The dataplane object
Dan Talayco92c99122010-06-03 13:53:18 -0700253 @param pkt Expected packet; may be None if yes_ports is empty
254 @param yes_ports Set or list of ports that should recieve packet
255 @param no_ports Set or list of ports that should not receive packet
256 @param assert_if Object that implements assertXXX
257 """
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700258 exp_pkt_arg = None
259 if config and config["relax"]:
260 exp_pkt_arg = pkt
261
Dan Talayco92c99122010-06-03 13:53:18 -0700262 for ofport in yes_ports:
263 logger.debug("Checking for pkt on port " + str(ofport))
Ken Chiang1bf01602012-04-04 10:48:23 -0700264 (rcv_port, rcv_pkt, pkt_time) = dp.poll(
Rich Lanec8aaa3e2012-07-26 19:28:02 -0700265 port_number=ofport, exp_pkt=exp_pkt_arg)
Dan Talayco92c99122010-06-03 13:53:18 -0700266 assert_if.assertTrue(rcv_pkt is not None,
267 "Did not receive pkt on " + str(ofport))
Ken Chiang1bf01602012-04-04 10:48:23 -0700268 if not dataplane.match_exp_pkt(pkt, rcv_pkt):
269 logger.debug("Sent %s" % format_packet(pkt))
270 logger.debug("Resp %s" % format_packet(rcv_pkt))
271 assert_if.assertTrue(dataplane.match_exp_pkt(pkt, rcv_pkt),
272 "Response packet does not match send packet " +
273 "on port " + str(ofport))
Dan Talayco92c99122010-06-03 13:53:18 -0700274
275 for ofport in no_ports:
276 logger.debug("Negative check for pkt on port " + str(ofport))
Ken Chiang1bf01602012-04-04 10:48:23 -0700277 (rcv_port, rcv_pkt, pkt_time) = dp.poll(
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700278 port_number=ofport, timeout=1, exp_pkt=exp_pkt_arg)
Dan Talayco92c99122010-06-03 13:53:18 -0700279 assert_if.assertTrue(rcv_pkt is None,
280 "Unexpected pkt on port " + str(ofport))
Dan Talayco551befa2010-07-15 17:05:32 -0700281
282
Dan Talaycoc948d0b2012-03-23 12:17:54 -0700283def receive_pkt_verify(parent, egr_ports, exp_pkt, ing_port):
Dan Talayco551befa2010-07-15 17:05:32 -0700284 """
285 Receive a packet and verify it matches an expected value
Dan Talaycof6e76c02012-03-23 10:56:12 -0700286 @param egr_port A single port or list of ports
Dan Talayco551befa2010-07-15 17:05:32 -0700287
288 parent must implement dataplane, assertTrue and assertEqual
289 """
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700290 exp_pkt_arg = None
291 if parent.config["relax"]:
292 exp_pkt_arg = exp_pkt
293
Dan Talaycof6e76c02012-03-23 10:56:12 -0700294 if type(egr_ports) == type([]):
295 egr_port_list = egr_ports
296 else:
297 egr_port_list = [egr_ports]
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700298
Dan Talaycof6e76c02012-03-23 10:56:12 -0700299 # Expect a packet from each port on egr port list
300 for egr_port in egr_port_list:
Dan Talaycod8ae7582012-03-23 12:24:56 -0700301 check_port = egr_port
Dan Talaycoc948d0b2012-03-23 12:17:54 -0700302 if egr_port == ofp.OFPP_IN_PORT:
Dan Talaycod8ae7582012-03-23 12:24:56 -0700303 check_port = ing_port
Dan Talaycof6e76c02012-03-23 10:56:12 -0700304 (rcv_port, rcv_pkt, pkt_time) = parent.dataplane.poll(
Rich Lanec8aaa3e2012-07-26 19:28:02 -0700305 port_number=check_port, exp_pkt=exp_pkt_arg)
Dan Talayco551befa2010-07-15 17:05:32 -0700306
Dan Talaycof6e76c02012-03-23 10:56:12 -0700307 if rcv_pkt is None:
Dan Talaycod8ae7582012-03-23 12:24:56 -0700308 parent.logger.error("ERROR: No packet received from " +
309 str(check_port))
Dan Talayco551befa2010-07-15 17:05:32 -0700310
Dan Talaycof6e76c02012-03-23 10:56:12 -0700311 parent.assertTrue(rcv_pkt is not None,
Dan Talaycod8ae7582012-03-23 12:24:56 -0700312 "Did not receive packet port " + str(check_port))
Dan Talaycof6e76c02012-03-23 10:56:12 -0700313 parent.logger.debug("Packet len " + str(len(rcv_pkt)) + " in on " +
314 str(rcv_port))
315
316 if str(exp_pkt) != str(rcv_pkt):
317 parent.logger.error("ERROR: Packet match failed.")
318 parent.logger.debug("Expected len " + str(len(exp_pkt)) + ": "
319 + str(exp_pkt).encode('hex'))
320 parent.logger.debug("Received len " + str(len(rcv_pkt)) + ": "
321 + str(rcv_pkt).encode('hex'))
322 parent.assertEqual(str(exp_pkt), str(rcv_pkt),
Dan Talaycod8ae7582012-03-23 12:24:56 -0700323 "Packet match error on port " + str(check_port))
Dan Talaycof6e76c02012-03-23 10:56:12 -0700324
Dan Talayco551befa2010-07-15 17:05:32 -0700325def match_verify(parent, req_match, res_match):
326 """
327 Verify flow matches agree; if they disagree, report where
328
329 parent must implement assertEqual
330 Use str() to ensure content is compared and not pointers
331 """
332
333 parent.assertEqual(req_match.wildcards, res_match.wildcards,
334 'Match failed: wildcards: ' + hex(req_match.wildcards) +
335 " != " + hex(res_match.wildcards))
336 parent.assertEqual(req_match.in_port, res_match.in_port,
337 'Match failed: in_port: ' + str(req_match.in_port) +
338 " != " + str(res_match.in_port))
339 parent.assertEqual(str(req_match.dl_src), str(res_match.dl_src),
340 'Match failed: dl_src: ' + str(req_match.dl_src) +
341 " != " + str(res_match.dl_src))
342 parent.assertEqual(str(req_match.dl_dst), str(res_match.dl_dst),
343 'Match failed: dl_dst: ' + str(req_match.dl_dst) +
344 " != " + str(res_match.dl_dst))
345 parent.assertEqual(req_match.dl_vlan, res_match.dl_vlan,
346 'Match failed: dl_vlan: ' + str(req_match.dl_vlan) +
347 " != " + str(res_match.dl_vlan))
348 parent.assertEqual(req_match.dl_vlan_pcp, res_match.dl_vlan_pcp,
349 'Match failed: dl_vlan_pcp: ' +
350 str(req_match.dl_vlan_pcp) + " != " +
351 str(res_match.dl_vlan_pcp))
352 parent.assertEqual(req_match.dl_type, res_match.dl_type,
353 'Match failed: dl_type: ' + str(req_match.dl_type) +
354 " != " + str(res_match.dl_type))
355
356 if (not(req_match.wildcards & ofp.OFPFW_DL_TYPE)
357 and (req_match.dl_type == IP_ETHERTYPE)):
358 parent.assertEqual(req_match.nw_tos, res_match.nw_tos,
359 'Match failed: nw_tos: ' + str(req_match.nw_tos) +
360 " != " + str(res_match.nw_tos))
361 parent.assertEqual(req_match.nw_proto, res_match.nw_proto,
362 'Match failed: nw_proto: ' + str(req_match.nw_proto) +
363 " != " + str(res_match.nw_proto))
364 parent.assertEqual(req_match.nw_src, res_match.nw_src,
365 'Match failed: nw_src: ' + str(req_match.nw_src) +
366 " != " + str(res_match.nw_src))
367 parent.assertEqual(req_match.nw_dst, res_match.nw_dst,
368 'Match failed: nw_dst: ' + str(req_match.nw_dst) +
369 " != " + str(res_match.nw_dst))
370
371 if (not(req_match.wildcards & ofp.OFPFW_NW_PROTO)
372 and ((req_match.nw_proto == TCP_PROTOCOL)
373 or (req_match.nw_proto == UDP_PROTOCOL))):
374 parent.assertEqual(req_match.tp_src, res_match.tp_src,
375 'Match failed: tp_src: ' +
376 str(req_match.tp_src) +
377 " != " + str(res_match.tp_src))
378 parent.assertEqual(req_match.tp_dst, res_match.tp_dst,
379 'Match failed: tp_dst: ' +
380 str(req_match.tp_dst) +
381 " != " + str(res_match.tp_dst))
382
383def flow_removed_verify(parent, request=None, pkt_count=-1, byte_count=-1):
384 """
385 Receive a flow removed msg and verify it matches expected
386
387 @params parent Must implement controller, assertEqual
388 @param pkt_count If >= 0, verify packet count
389 @param byte_count If >= 0, verify byte count
390 """
391 (response, raw) = parent.controller.poll(ofp.OFPT_FLOW_REMOVED, 2)
392 parent.assertTrue(response is not None, 'No flow removed message received')
393
394 if request is None:
395 return
396
397 parent.assertEqual(request.cookie, response.cookie,
398 "Flow removed cookie error: " +
399 hex(request.cookie) + " != " + hex(response.cookie))
400
401 req_match = request.match
402 res_match = response.match
403 verifyMatchField(req_match, res_match)
404
405 if (req_match.wildcards != 0):
406 parent.assertEqual(request.priority, response.priority,
407 'Flow remove prio mismatch: ' +
408 str(request,priority) + " != " +
409 str(response.priority))
410 parent.assertEqual(response.reason, ofp.OFPRR_HARD_TIMEOUT,
411 'Flow remove reason is not HARD TIMEOUT:' +
412 str(response.reason))
413 if pkt_count >= 0:
414 parent.assertEqual(response.packet_count, pkt_count,
415 'Flow removed failed, packet count: ' +
416 str(response.packet_count) + " != " +
417 str(pkt_count))
418 if byte_count >= 0:
419 parent.assertEqual(response.byte_count, byte_count,
420 'Flow removed failed, byte count: ' +
421 str(response.byte_count) + " != " +
422 str(byte_count))
423
424def flow_msg_create(parent, pkt, ing_port=None, action_list=None, wildcards=0,
Dan Talaycof6e76c02012-03-23 10:56:12 -0700425 egr_ports=None, egr_queue=None, check_expire=False, in_band=False):
Dan Talayco551befa2010-07-15 17:05:32 -0700426 """
427 Create a flow message
428
429 Match on packet with given wildcards.
430 See flow_match_test for other parameter descriptoins
431 @param egr_queue if not None, make the output an enqueue action
Dan Talayco677c0b72011-08-23 22:53:38 -0700432 @param in_band if True, do not wildcard ingress port
Dan Talaycof6e76c02012-03-23 10:56:12 -0700433 @param egr_ports None (drop), single port or list of ports
Dan Talayco551befa2010-07-15 17:05:32 -0700434 """
435 match = parse.packet_to_flow_match(pkt)
436 parent.assertTrue(match is not None, "Flow match from pkt failed")
Dan Talayco677c0b72011-08-23 22:53:38 -0700437 if in_band:
438 wildcards &= ~ofp.OFPFW_IN_PORT
Dan Talayco551befa2010-07-15 17:05:32 -0700439 match.wildcards = wildcards
440 match.in_port = ing_port
441
Dan Talaycof6e76c02012-03-23 10:56:12 -0700442 if type(egr_ports) == type([]):
443 egr_port_list = egr_ports
444 else:
445 egr_port_list = [egr_ports]
446
Dan Talayco551befa2010-07-15 17:05:32 -0700447 request = message.flow_mod()
448 request.match = match
449 request.buffer_id = 0xffffffff
450 if check_expire:
451 request.flags |= ofp.OFPFF_SEND_FLOW_REM
452 request.hard_timeout = 1
453
454 if action_list is not None:
455 for act in action_list:
456 parent.logger.debug("Adding action " + act.show())
457 rv = request.actions.add(act)
458 parent.assertTrue(rv, "Could not add action" + act.show())
459
460 # Set up output/enqueue action if directed
461 if egr_queue is not None:
Dan Talaycof6e76c02012-03-23 10:56:12 -0700462 parent.assertTrue(egr_ports is not None, "Egress port not set")
Dan Talayco551befa2010-07-15 17:05:32 -0700463 act = action.action_enqueue()
Dan Talaycof6e76c02012-03-23 10:56:12 -0700464 for egr_port in egr_port_list:
465 act.port = egr_port
466 act.queue_id = egr_queue
467 rv = request.actions.add(act)
468 parent.assertTrue(rv, "Could not add enqueue action " +
469 str(egr_port) + " Q: " + str(egr_queue))
470 elif egr_ports is not None:
471 for egr_port in egr_port_list:
472 act = action.action_output()
473 act.port = egr_port
474 rv = request.actions.add(act)
475 parent.assertTrue(rv, "Could not add output action " +
476 str(egr_port))
Dan Talayco551befa2010-07-15 17:05:32 -0700477
478 parent.logger.debug(request.show())
479
480 return request
481
Jeffrey Townsendf3eae9c2012-03-28 18:23:21 -0700482def flow_msg_install(parent, request, clear_table_override=None):
Dan Talayco551befa2010-07-15 17:05:32 -0700483 """
484 Install a flow mod message in the switch
485
486 @param parent Must implement controller, assertEqual, assertTrue
487 @param request The request, all set to go
488 @param clear_table If true, clear the flow table before installing
489 """
Dan Talayco8a64e332012-03-28 14:53:20 -0700490
491 clear_table = test_param_get(parent.config, 'clear_table', default=True)
Jeffrey Townsendf3eae9c2012-03-28 18:23:21 -0700492 if(clear_table_override != None):
493 clear_table = clear_table_override
494
495 if clear_table:
Dan Talayco551befa2010-07-15 17:05:32 -0700496 parent.logger.debug("Clear flow table")
497 rc = delete_all_flows(parent.controller, parent.logger)
498 parent.assertEqual(rc, 0, "Failed to delete all flows")
Dan Talayco0fc08bd2012-04-09 16:56:18 -0700499 parent.assertEqual(do_barrier(parent.controller), 0, "Barrier failed")
Dan Talayco551befa2010-07-15 17:05:32 -0700500
501 parent.logger.debug("Insert flow")
502 rv = parent.controller.message_send(request)
503 parent.assertTrue(rv != -1, "Error installing flow mod")
Dan Talayco0fc08bd2012-04-09 16:56:18 -0700504 parent.assertEqual(do_barrier(parent.controller), 0, "Barrier failed")
Dan Talayco551befa2010-07-15 17:05:32 -0700505
Dan Talaycof6e76c02012-03-23 10:56:12 -0700506def flow_match_test_port_pair(parent, ing_port, egr_ports, wildcards=0,
Dan Talayco551befa2010-07-15 17:05:32 -0700507 dl_vlan=-1, pkt=None, exp_pkt=None,
508 action_list=None, check_expire=False):
509 """
510 Flow match test on single TCP packet
Dan Talaycof6e76c02012-03-23 10:56:12 -0700511 @param egr_ports A single port or list of ports
Dan Talayco551befa2010-07-15 17:05:32 -0700512
513 Run test with packet through switch from ing_port to egr_port
514 See flow_match_test for parameter descriptions
515 """
516
Dan Talaycof6e76c02012-03-23 10:56:12 -0700517 parent.logger.info("Pkt match test: " + str(ing_port) + " to " +
518 str(egr_ports))
Dan Talayco551befa2010-07-15 17:05:32 -0700519 parent.logger.debug(" WC: " + hex(wildcards) + " vlan: " + str(dl_vlan) +
Dan Talayco98fada92010-07-17 00:36:21 -0700520 " expire: " + str(check_expire))
Dan Talayco551befa2010-07-15 17:05:32 -0700521 if pkt is None:
522 pkt = simple_tcp_packet(dl_vlan_enable=(dl_vlan >= 0), dl_vlan=dl_vlan)
523
524 request = flow_msg_create(parent, pkt, ing_port=ing_port,
Dan Talaycof6e76c02012-03-23 10:56:12 -0700525 wildcards=wildcards, egr_ports=egr_ports,
Dan Talayco551befa2010-07-15 17:05:32 -0700526 action_list=action_list)
527
528 flow_msg_install(parent, request)
529
Dan Talaycof6e76c02012-03-23 10:56:12 -0700530 parent.logger.debug("Send packet: " + str(ing_port) + " to " +
531 str(egr_ports))
Dan Talayco551befa2010-07-15 17:05:32 -0700532 parent.dataplane.send(ing_port, str(pkt))
533
534 if exp_pkt is None:
535 exp_pkt = pkt
Dan Talaycoc948d0b2012-03-23 12:17:54 -0700536 receive_pkt_verify(parent, egr_ports, exp_pkt, ing_port)
Dan Talayco551befa2010-07-15 17:05:32 -0700537
538 if check_expire:
539 #@todo Not all HW supports both pkt and byte counters
540 flow_removed_verify(parent, request, pkt_count=1, byte_count=len(pkt))
541
Dan Talaycof6e76c02012-03-23 10:56:12 -0700542def get_egr_list(parent, of_ports, how_many, exclude_list=[]):
543 """
544 Generate a list of ports avoiding those in the exclude list
545 @param parent Supplies logger
546 @param of_ports List of OF port numbers
547 @param how_many Number of ports to be added to the list
548 @param exclude_list List of ports not to be used
549 @returns An empty list if unable to find enough ports
550 """
551
Dan Talaycoc948d0b2012-03-23 12:17:54 -0700552 if how_many == 0:
553 return []
554
Dan Talaycof6e76c02012-03-23 10:56:12 -0700555 count = 0
556 egr_ports = []
557 for egr_idx in range(len(of_ports)):
558 if of_ports[egr_idx] not in exclude_list:
559 egr_ports.append(of_ports[egr_idx])
560 count += 1
561 if count >= how_many:
562 return egr_ports
563 parent.logger.debug("Could not generate enough egress ports for test")
564 return []
565
Dan Talayco551befa2010-07-15 17:05:32 -0700566def flow_match_test(parent, port_map, wildcards=0, dl_vlan=-1, pkt=None,
567 exp_pkt=None, action_list=None, check_expire=False,
Dan Talaycoc948d0b2012-03-23 12:17:54 -0700568 max_test=0, egr_count=1, ing_port=False):
Dan Talayco551befa2010-07-15 17:05:32 -0700569 """
570 Run flow_match_test_port_pair on all port pairs
571
572 @param max_test If > 0 no more than this number of tests are executed.
573 @param parent Must implement controller, dataplane, assertTrue, assertEqual
574 and logger
575 @param pkt If not None, use this packet for ingress
576 @param wildcards For flow match entry
Dan Talayco79184222010-11-01 12:24:29 -0700577 @param dl_vlan If not -1, and pkt is None, create a pkt w/ VLAN tag
Dan Talayco551befa2010-07-15 17:05:32 -0700578 @param exp_pkt If not None, use this as the expected output pkt; els use pkt
579 @param action_list Additional actions to add to flow mod
580 @param check_expire Check for flow expiration message
Dan Talaycocfa172f2012-03-23 12:03:00 -0700581 @param egr_count Number of egress ports; -1 means get from config w/ dflt 2
Dan Talayco551befa2010-07-15 17:05:32 -0700582 """
583 of_ports = port_map.keys()
584 of_ports.sort()
585 parent.assertTrue(len(of_ports) > 1, "Not enough ports for test")
586 test_count = 0
587
Dan Talaycocfa172f2012-03-23 12:03:00 -0700588 if egr_count == -1:
589 egr_count = test_param_get(parent.config, 'egr_count', default=2)
590
Dan Talayco551befa2010-07-15 17:05:32 -0700591 for ing_idx in range(len(of_ports)):
592 ingress_port = of_ports[ing_idx]
Dan Talaycof6e76c02012-03-23 10:56:12 -0700593 egr_ports = get_egr_list(parent, of_ports, egr_count,
594 exclude_list=[ingress_port])
Dan Talaycoc948d0b2012-03-23 12:17:54 -0700595 if ing_port:
596 egr_ports.append(ofp.OFPP_IN_PORT)
Dan Talaycof6e76c02012-03-23 10:56:12 -0700597 if len(egr_ports) == 0:
598 parent.assertTrue(0, "Failed to generate egress port list")
599
600 flow_match_test_port_pair(parent, ingress_port, egr_ports,
601 wildcards=wildcards, dl_vlan=dl_vlan,
602 pkt=pkt, exp_pkt=exp_pkt,
603 action_list=action_list,
604 check_expire=check_expire)
605 test_count += 1
606 if (max_test > 0) and (test_count > max_test):
607 parent.logger.info("Ran " + str(test_count) + " tests; exiting")
608 return
Dan Talayco551befa2010-07-15 17:05:32 -0700609
Dan Talayco4b2bee62010-07-20 14:10:05 -0700610def test_param_get(config, key, default=None):
611 """
612 Return value passed via test-params if present
613
614 @param config The configuration structure for OFTest
615 @param key The lookup key
616 @param default Default value to use if not found
617
618 If the pair 'key=val' appeared in the string passed to --test-params
619 on the command line, return val (as interpreted by exec). Otherwise
620 return default value.
Dan Talaycof6e76c02012-03-23 10:56:12 -0700621
622 WARNING: TEST PARAMETERS MUST BE PYTHON IDENTIFIERS;
623 eg egr_count, not egr-count.
Dan Talayco4b2bee62010-07-20 14:10:05 -0700624 """
625 try:
626 exec config["test_params"]
627 except:
628 return default
629
630 s = "val = " + str(key)
631 try:
632 exec s
633 return val
634 except:
635 return default
636
637def action_generate(parent, field_to_mod, mod_field_vals):
638 """
639 Create an action to modify the field indicated in field_to_mod
640
641 @param parent Must implement, assertTrue
642 @param field_to_mod The field to modify as a string name
643 @param mod_field_vals Hash of values to use for modified values
644 """
645
646 act = None
647
648 if field_to_mod in ['pktlen']:
649 return None
650
651 if field_to_mod == 'dl_dst':
652 act = action.action_set_dl_dst()
653 act.dl_addr = parse.parse_mac(mod_field_vals['dl_dst'])
654 elif field_to_mod == 'dl_src':
655 act = action.action_set_dl_src()
656 act.dl_addr = parse.parse_mac(mod_field_vals['dl_src'])
657 elif field_to_mod == 'dl_vlan_enable':
658 if not mod_field_vals['dl_vlan_enable']: # Strip VLAN tag
659 act = action.action_strip_vlan()
660 # Add VLAN tag is handled by dl_vlan field
661 # Will return None in this case
662 elif field_to_mod == 'dl_vlan':
663 act = action.action_set_vlan_vid()
664 act.vlan_vid = mod_field_vals['dl_vlan']
665 elif field_to_mod == 'dl_vlan_pcp':
666 act = action.action_set_vlan_pcp()
667 act.vlan_pcp = mod_field_vals['dl_vlan_pcp']
668 elif field_to_mod == 'ip_src':
669 act = action.action_set_nw_src()
670 act.nw_addr = parse.parse_ip(mod_field_vals['ip_src'])
671 elif field_to_mod == 'ip_dst':
672 act = action.action_set_nw_dst()
673 act.nw_addr = parse.parse_ip(mod_field_vals['ip_dst'])
674 elif field_to_mod == 'ip_tos':
675 act = action.action_set_nw_tos()
676 act.nw_tos = mod_field_vals['ip_tos']
677 elif field_to_mod == 'tcp_sport':
678 act = action.action_set_tp_src()
679 act.tp_port = mod_field_vals['tcp_sport']
680 elif field_to_mod == 'tcp_dport':
681 act = action.action_set_tp_dst()
682 act.tp_port = mod_field_vals['tcp_dport']
683 else:
684 parent.assertTrue(0, "Unknown field to modify: " + str(field_to_mod))
685
686 return act
687
688def pkt_action_setup(parent, start_field_vals={}, mod_field_vals={},
689 mod_fields={}, check_test_params=False):
690 """
691 Set up the ingress and expected packet and action list for a test
692
693 @param parent Must implement, assertTrue, config hash and logger
694 @param start_field_values Field values to use for ingress packet (optional)
695 @param mod_field_values Field values to use for modified packet (optional)
696 @param mod_fields The list of fields to be modified by the switch in the test.
697 @params check_test_params If True, will check the parameters vid, add_vlan
698 and strip_vlan from the command line.
699
700 Returns a triple: pkt-to-send, expected-pkt, action-list
701 """
702
703 new_actions = []
704
Dan Talayco4b2bee62010-07-20 14:10:05 -0700705 base_pkt_params = {}
706 base_pkt_params['pktlen'] = 100
707 base_pkt_params['dl_dst'] = '00:DE:F0:12:34:56'
708 base_pkt_params['dl_src'] = '00:23:45:67:89:AB'
709 base_pkt_params['dl_vlan_enable'] = False
710 base_pkt_params['dl_vlan'] = 2
711 base_pkt_params['dl_vlan_pcp'] = 0
712 base_pkt_params['ip_src'] = '192.168.0.1'
713 base_pkt_params['ip_dst'] = '192.168.0.2'
714 base_pkt_params['ip_tos'] = 0
715 base_pkt_params['tcp_sport'] = 1234
716 base_pkt_params['tcp_dport'] = 80
717 for keyname in start_field_vals.keys():
718 base_pkt_params[keyname] = start_field_vals[keyname]
719
720 mod_pkt_params = {}
721 mod_pkt_params['pktlen'] = 100
722 mod_pkt_params['dl_dst'] = '00:21:0F:ED:CB:A9'
723 mod_pkt_params['dl_src'] = '00:ED:CB:A9:87:65'
724 mod_pkt_params['dl_vlan_enable'] = False
725 mod_pkt_params['dl_vlan'] = 3
726 mod_pkt_params['dl_vlan_pcp'] = 7
727 mod_pkt_params['ip_src'] = '10.20.30.40'
728 mod_pkt_params['ip_dst'] = '50.60.70.80'
729 mod_pkt_params['ip_tos'] = 0xf0
730 mod_pkt_params['tcp_sport'] = 4321
731 mod_pkt_params['tcp_dport'] = 8765
732 for keyname in mod_field_vals.keys():
733 mod_pkt_params[keyname] = mod_field_vals[keyname]
734
735 # Check for test param modifications
736 strip = False
737 if check_test_params:
738 add_vlan = test_param_get(parent.config, 'add_vlan')
739 strip_vlan = test_param_get(parent.config, 'strip_vlan')
740 vid = test_param_get(parent.config, 'vid')
741
742 if add_vlan and strip_vlan:
743 parent.assertTrue(0, "Add and strip VLAN both specified")
744
745 if vid:
746 base_pkt_params['dl_vlan_enable'] = True
747 base_pkt_params['dl_vlan'] = vid
748 if 'dl_vlan' in mod_fields:
749 mod_pkt_params['dl_vlan'] = vid + 1
750
751 if add_vlan:
752 base_pkt_params['dl_vlan_enable'] = False
753 mod_pkt_params['dl_vlan_enable'] = True
754 mod_pkt_params['pktlen'] = base_pkt_params['pktlen'] + 4
755 mod_fields.append('pktlen')
756 mod_fields.append('dl_vlan_enable')
757 if 'dl_vlan' not in mod_fields:
758 mod_fields.append('dl_vlan')
759 elif strip_vlan:
760 base_pkt_params['dl_vlan_enable'] = True
761 mod_pkt_params['dl_vlan_enable'] = False
762 mod_pkt_params['pktlen'] = base_pkt_params['pktlen'] - 4
763 mod_fields.append('dl_vlan_enable')
764 mod_fields.append('pktlen')
765
766 # Build the ingress packet
767 ingress_pkt = simple_tcp_packet(**base_pkt_params)
768
769 # Build the expected packet, modifying the indicated fields
770 for item in mod_fields:
771 base_pkt_params[item] = mod_pkt_params[item]
772 act = action_generate(parent, item, mod_pkt_params)
773 if act:
774 new_actions.append(act)
775
776 expected_pkt = simple_tcp_packet(**base_pkt_params)
777
778 return (ingress_pkt, expected_pkt, new_actions)
Dan Talayco677c0b72011-08-23 22:53:38 -0700779
780# Generate a simple "drop" flow mod
781# If in_band is true, then only drop from first test port
782def flow_mod_gen(port_map, in_band):
783 request = message.flow_mod()
784 request.match.wildcards = ofp.OFPFW_ALL
785 if in_band:
786 request.match.wildcards = ofp.OFPFW_ALL - ofp.OFPFW_IN_PORT
787 for of_port, ifname in port_map.items(): # Grab first port
788 break
789 request.match.in_port = of_port
790 request.buffer_id = 0xffffffff
791 return request
Dan Talaycoba3745c2010-07-21 21:51:08 -0700792
793def skip_message_emit(parent, s):
794 """
795 Print out a 'skipped' message to stderr
796
797 @param s The string to print out to the log file
798 @param parent Must implement config and logger objects
799 """
800 global skipped_test_count
801
802 skipped_test_count += 1
803 parent.logger.info("Skipping: " + s)
804 if parent.config["dbg_level"] < logging.WARNING:
805 sys.stderr.write("(skipped) ")
806 else:
807 sys.stderr.write("(S)")
Dan Talayco677c0b72011-08-23 22:53:38 -0700808
Dan Talayco8a64e332012-03-28 14:53:20 -0700809
810def all_stats_get(parent):
811 """
812 Get the aggregate stats for all flows in the table
813 @param parent Test instance with controller connection and assert
814 @returns dict with keys flows, packets, bytes, active (flows),
815 lookups, matched
816 """
817 stat_req = message.aggregate_stats_request()
818 stat_req.match = ofp.ofp_match()
819 stat_req.match.wildcards = ofp.OFPFW_ALL
820 stat_req.table_id = 0xff
821 stat_req.out_port = ofp.OFPP_NONE
822
823 rv = {}
824
Rich Lanec8aaa3e2012-07-26 19:28:02 -0700825 (reply, pkt) = parent.controller.transact(stat_req)
Dan Talayco8a64e332012-03-28 14:53:20 -0700826 parent.assertTrue(len(reply.stats) == 1, "Did not receive flow stats reply")
827
828 for obj in reply.stats:
829 (rv["flows"], rv["packets"], rv["bytes"]) = (obj.flow_count,
830 obj.packet_count, obj.byte_count)
831 break
832
833 request = message.table_stats_request()
Rich Lanec8aaa3e2012-07-26 19:28:02 -0700834 (reply , pkt) = parent.controller.transact(request)
Dan Talayco8a64e332012-03-28 14:53:20 -0700835
836
837 (rv["active"], rv["lookups"], rv["matched"]) = (0,0,0)
838 for obj in reply.stats:
839 rv["active"] += obj.active_count
840 rv["lookups"] += obj.lookup_count
841 rv["matched"] += obj.matched_count
842
843 return rv
Dan Talayco2baf8b52012-03-30 09:55:42 -0700844
845FILTER=''.join([(len(repr(chr(x)))==3) and chr(x) or '.'
846 for x in range(256)])
847
848def hex_dump_buffer(src, length=16):
849 """
850 Convert src to a hex dump string and return the string
851 @param src The source buffer
852 @param length The number of bytes shown in each line
853 @returns A string showing the hex dump
854 """
Dan Talaycoc516fa02012-04-12 22:28:43 -0700855 result = ["\n"]
Dan Talayco2baf8b52012-03-30 09:55:42 -0700856 for i in xrange(0, len(src), length):
857 chars = src[i:i+length]
858 hex = ' '.join(["%02x" % ord(x) for x in chars])
859 printable = ''.join(["%s" % ((ord(x) <= 127 and
860 FILTER[ord(x)]) or '.') for x in chars])
861 result.append("%04x %-*s %s\n" % (i, length*3, hex, printable))
862 return ''.join(result)
863
864def format_packet(pkt):
865 return "Packet length %d \n%s" % (len(str(pkt)),
866 hex_dump_buffer(str(pkt)))