blob: 9b9940e15399d48bca31538810e59b4062e37624 [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):
Dan Talayco0fc08bd2012-04-09 16:56:18 -0700172 """
173 Do a barrier command
174 Return 0 on success, -1 on error
175 """
Dan Talayco41eae8b2010-03-10 13:57:06 -0800176 b = message.barrier_request()
Dan Talaycof6b94832012-04-12 21:50:57 -0700177 (resp, pkt) = ctrl.transact(b)
178 # We'll trust the transaction processing in the controller that xid matched
Dan Talayco0fc08bd2012-04-09 16:56:18 -0700179 if not resp:
180 return -1
181 return 0
Dan Talayco92c99122010-06-03 13:53:18 -0700182
183def port_config_get(controller, port_no, logger):
184 """
185 Get a port's configuration
186
187 Gets the switch feature configuration and grabs one port's
188 configuration
189
190 @returns (hwaddr, config, advert) The hwaddress, configuration and
191 advertised values
192 """
193 request = message.features_request()
Rich Lanec8aaa3e2012-07-26 19:28:02 -0700194 reply, pkt = controller.transact(request)
Dan Talayco92c99122010-06-03 13:53:18 -0700195 logger.debug(reply.show())
196 if reply is None:
197 logger.warn("Get feature request failed")
198 return None, None, None
199 for idx in range(len(reply.ports)):
200 if reply.ports[idx].port_no == port_no:
201 return (reply.ports[idx].hw_addr, reply.ports[idx].config,
202 reply.ports[idx].advertised)
203
204 logger.warn("Did not find port number for port config")
205 return None, None, None
206
207def port_config_set(controller, port_no, config, mask, logger):
208 """
209 Set the port configuration according the given parameters
210
211 Gets the switch feature configuration and updates one port's
212 configuration value according to config and mask
213 """
214 logger.info("Setting port " + str(port_no) + " to config " + str(config))
215 request = message.features_request()
Rich Lanec8aaa3e2012-07-26 19:28:02 -0700216 reply, pkt = controller.transact(request)
Dan Talayco92c99122010-06-03 13:53:18 -0700217 if reply is None:
218 return -1
219 logger.debug(reply.show())
220 for idx in range(len(reply.ports)):
221 if reply.ports[idx].port_no == port_no:
222 break
223 if idx >= len(reply.ports):
224 return -1
225 mod = message.port_mod()
226 mod.port_no = port_no
227 mod.hw_addr = reply.ports[idx].hw_addr
228 mod.config = config
229 mod.mask = mask
230 mod.advertise = reply.ports[idx].advertised
231 rv = controller.message_send(mod)
232 return rv
233
Ken Chiang1bf01602012-04-04 10:48:23 -0700234def receive_pkt_check(dp, pkt, yes_ports, no_ports, assert_if, logger,
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700235 config):
Dan Talayco92c99122010-06-03 13:53:18 -0700236 """
237 Check for proper receive packets across all ports
Ken Chiang1bf01602012-04-04 10:48:23 -0700238 @param dp The dataplane object
Dan Talayco92c99122010-06-03 13:53:18 -0700239 @param pkt Expected packet; may be None if yes_ports is empty
240 @param yes_ports Set or list of ports that should recieve packet
241 @param no_ports Set or list of ports that should not receive packet
242 @param assert_if Object that implements assertXXX
243 """
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700244 exp_pkt_arg = None
245 if config and config["relax"]:
246 exp_pkt_arg = pkt
247
Dan Talayco92c99122010-06-03 13:53:18 -0700248 for ofport in yes_ports:
249 logger.debug("Checking for pkt on port " + str(ofport))
Ken Chiang1bf01602012-04-04 10:48:23 -0700250 (rcv_port, rcv_pkt, pkt_time) = dp.poll(
Rich Lanec8aaa3e2012-07-26 19:28:02 -0700251 port_number=ofport, exp_pkt=exp_pkt_arg)
Dan Talayco92c99122010-06-03 13:53:18 -0700252 assert_if.assertTrue(rcv_pkt is not None,
253 "Did not receive pkt on " + str(ofport))
Ken Chiang1bf01602012-04-04 10:48:23 -0700254 if not dataplane.match_exp_pkt(pkt, rcv_pkt):
255 logger.debug("Sent %s" % format_packet(pkt))
256 logger.debug("Resp %s" % format_packet(rcv_pkt))
257 assert_if.assertTrue(dataplane.match_exp_pkt(pkt, rcv_pkt),
258 "Response packet does not match send packet " +
259 "on port " + str(ofport))
Dan Talayco92c99122010-06-03 13:53:18 -0700260
261 for ofport in no_ports:
262 logger.debug("Negative check for pkt on port " + str(ofport))
Ken Chiang1bf01602012-04-04 10:48:23 -0700263 (rcv_port, rcv_pkt, pkt_time) = dp.poll(
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700264 port_number=ofport, timeout=1, exp_pkt=exp_pkt_arg)
Dan Talayco92c99122010-06-03 13:53:18 -0700265 assert_if.assertTrue(rcv_pkt is None,
266 "Unexpected pkt on port " + str(ofport))
Dan Talayco551befa2010-07-15 17:05:32 -0700267
268
Dan Talaycoc948d0b2012-03-23 12:17:54 -0700269def receive_pkt_verify(parent, egr_ports, exp_pkt, ing_port):
Dan Talayco551befa2010-07-15 17:05:32 -0700270 """
271 Receive a packet and verify it matches an expected value
Dan Talaycof6e76c02012-03-23 10:56:12 -0700272 @param egr_port A single port or list of ports
Dan Talayco551befa2010-07-15 17:05:32 -0700273
274 parent must implement dataplane, assertTrue and assertEqual
275 """
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700276 exp_pkt_arg = None
277 if parent.config["relax"]:
278 exp_pkt_arg = exp_pkt
279
Dan Talaycof6e76c02012-03-23 10:56:12 -0700280 if type(egr_ports) == type([]):
281 egr_port_list = egr_ports
282 else:
283 egr_port_list = [egr_ports]
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700284
Dan Talaycof6e76c02012-03-23 10:56:12 -0700285 # Expect a packet from each port on egr port list
286 for egr_port in egr_port_list:
Dan Talaycod8ae7582012-03-23 12:24:56 -0700287 check_port = egr_port
Dan Talaycoc948d0b2012-03-23 12:17:54 -0700288 if egr_port == ofp.OFPP_IN_PORT:
Dan Talaycod8ae7582012-03-23 12:24:56 -0700289 check_port = ing_port
Dan Talaycof6e76c02012-03-23 10:56:12 -0700290 (rcv_port, rcv_pkt, pkt_time) = parent.dataplane.poll(
Rich Lanec8aaa3e2012-07-26 19:28:02 -0700291 port_number=check_port, exp_pkt=exp_pkt_arg)
Dan Talayco551befa2010-07-15 17:05:32 -0700292
Dan Talaycof6e76c02012-03-23 10:56:12 -0700293 if rcv_pkt is None:
Dan Talaycod8ae7582012-03-23 12:24:56 -0700294 parent.logger.error("ERROR: No packet received from " +
295 str(check_port))
Dan Talayco551befa2010-07-15 17:05:32 -0700296
Dan Talaycof6e76c02012-03-23 10:56:12 -0700297 parent.assertTrue(rcv_pkt is not None,
Dan Talaycod8ae7582012-03-23 12:24:56 -0700298 "Did not receive packet port " + str(check_port))
Dan Talaycof6e76c02012-03-23 10:56:12 -0700299 parent.logger.debug("Packet len " + str(len(rcv_pkt)) + " in on " +
300 str(rcv_port))
301
302 if str(exp_pkt) != str(rcv_pkt):
303 parent.logger.error("ERROR: Packet match failed.")
304 parent.logger.debug("Expected len " + str(len(exp_pkt)) + ": "
305 + str(exp_pkt).encode('hex'))
306 parent.logger.debug("Received len " + str(len(rcv_pkt)) + ": "
307 + str(rcv_pkt).encode('hex'))
308 parent.assertEqual(str(exp_pkt), str(rcv_pkt),
Dan Talaycod8ae7582012-03-23 12:24:56 -0700309 "Packet match error on port " + str(check_port))
Dan Talaycof6e76c02012-03-23 10:56:12 -0700310
Dan Talayco551befa2010-07-15 17:05:32 -0700311def match_verify(parent, req_match, res_match):
312 """
313 Verify flow matches agree; if they disagree, report where
314
315 parent must implement assertEqual
316 Use str() to ensure content is compared and not pointers
317 """
318
319 parent.assertEqual(req_match.wildcards, res_match.wildcards,
320 'Match failed: wildcards: ' + hex(req_match.wildcards) +
321 " != " + hex(res_match.wildcards))
322 parent.assertEqual(req_match.in_port, res_match.in_port,
323 'Match failed: in_port: ' + str(req_match.in_port) +
324 " != " + str(res_match.in_port))
325 parent.assertEqual(str(req_match.dl_src), str(res_match.dl_src),
326 'Match failed: dl_src: ' + str(req_match.dl_src) +
327 " != " + str(res_match.dl_src))
328 parent.assertEqual(str(req_match.dl_dst), str(res_match.dl_dst),
329 'Match failed: dl_dst: ' + str(req_match.dl_dst) +
330 " != " + str(res_match.dl_dst))
331 parent.assertEqual(req_match.dl_vlan, res_match.dl_vlan,
332 'Match failed: dl_vlan: ' + str(req_match.dl_vlan) +
333 " != " + str(res_match.dl_vlan))
334 parent.assertEqual(req_match.dl_vlan_pcp, res_match.dl_vlan_pcp,
335 'Match failed: dl_vlan_pcp: ' +
336 str(req_match.dl_vlan_pcp) + " != " +
337 str(res_match.dl_vlan_pcp))
338 parent.assertEqual(req_match.dl_type, res_match.dl_type,
339 'Match failed: dl_type: ' + str(req_match.dl_type) +
340 " != " + str(res_match.dl_type))
341
342 if (not(req_match.wildcards & ofp.OFPFW_DL_TYPE)
343 and (req_match.dl_type == IP_ETHERTYPE)):
344 parent.assertEqual(req_match.nw_tos, res_match.nw_tos,
345 'Match failed: nw_tos: ' + str(req_match.nw_tos) +
346 " != " + str(res_match.nw_tos))
347 parent.assertEqual(req_match.nw_proto, res_match.nw_proto,
348 'Match failed: nw_proto: ' + str(req_match.nw_proto) +
349 " != " + str(res_match.nw_proto))
350 parent.assertEqual(req_match.nw_src, res_match.nw_src,
351 'Match failed: nw_src: ' + str(req_match.nw_src) +
352 " != " + str(res_match.nw_src))
353 parent.assertEqual(req_match.nw_dst, res_match.nw_dst,
354 'Match failed: nw_dst: ' + str(req_match.nw_dst) +
355 " != " + str(res_match.nw_dst))
356
357 if (not(req_match.wildcards & ofp.OFPFW_NW_PROTO)
358 and ((req_match.nw_proto == TCP_PROTOCOL)
359 or (req_match.nw_proto == UDP_PROTOCOL))):
360 parent.assertEqual(req_match.tp_src, res_match.tp_src,
361 'Match failed: tp_src: ' +
362 str(req_match.tp_src) +
363 " != " + str(res_match.tp_src))
364 parent.assertEqual(req_match.tp_dst, res_match.tp_dst,
365 'Match failed: tp_dst: ' +
366 str(req_match.tp_dst) +
367 " != " + str(res_match.tp_dst))
368
369def flow_removed_verify(parent, request=None, pkt_count=-1, byte_count=-1):
370 """
371 Receive a flow removed msg and verify it matches expected
372
373 @params parent Must implement controller, assertEqual
374 @param pkt_count If >= 0, verify packet count
375 @param byte_count If >= 0, verify byte count
376 """
377 (response, raw) = parent.controller.poll(ofp.OFPT_FLOW_REMOVED, 2)
378 parent.assertTrue(response is not None, 'No flow removed message received')
379
380 if request is None:
381 return
382
383 parent.assertEqual(request.cookie, response.cookie,
384 "Flow removed cookie error: " +
385 hex(request.cookie) + " != " + hex(response.cookie))
386
387 req_match = request.match
388 res_match = response.match
389 verifyMatchField(req_match, res_match)
390
391 if (req_match.wildcards != 0):
392 parent.assertEqual(request.priority, response.priority,
393 'Flow remove prio mismatch: ' +
394 str(request,priority) + " != " +
395 str(response.priority))
396 parent.assertEqual(response.reason, ofp.OFPRR_HARD_TIMEOUT,
397 'Flow remove reason is not HARD TIMEOUT:' +
398 str(response.reason))
399 if pkt_count >= 0:
400 parent.assertEqual(response.packet_count, pkt_count,
401 'Flow removed failed, packet count: ' +
402 str(response.packet_count) + " != " +
403 str(pkt_count))
404 if byte_count >= 0:
405 parent.assertEqual(response.byte_count, byte_count,
406 'Flow removed failed, byte count: ' +
407 str(response.byte_count) + " != " +
408 str(byte_count))
409
410def flow_msg_create(parent, pkt, ing_port=None, action_list=None, wildcards=0,
Dan Talaycof6e76c02012-03-23 10:56:12 -0700411 egr_ports=None, egr_queue=None, check_expire=False, in_band=False):
Dan Talayco551befa2010-07-15 17:05:32 -0700412 """
413 Create a flow message
414
415 Match on packet with given wildcards.
416 See flow_match_test for other parameter descriptoins
417 @param egr_queue if not None, make the output an enqueue action
Dan Talayco677c0b72011-08-23 22:53:38 -0700418 @param in_band if True, do not wildcard ingress port
Dan Talaycof6e76c02012-03-23 10:56:12 -0700419 @param egr_ports None (drop), single port or list of ports
Dan Talayco551befa2010-07-15 17:05:32 -0700420 """
421 match = parse.packet_to_flow_match(pkt)
422 parent.assertTrue(match is not None, "Flow match from pkt failed")
Dan Talayco677c0b72011-08-23 22:53:38 -0700423 if in_band:
424 wildcards &= ~ofp.OFPFW_IN_PORT
Dan Talayco551befa2010-07-15 17:05:32 -0700425 match.wildcards = wildcards
426 match.in_port = ing_port
427
Dan Talaycof6e76c02012-03-23 10:56:12 -0700428 if type(egr_ports) == type([]):
429 egr_port_list = egr_ports
430 else:
431 egr_port_list = [egr_ports]
432
Dan Talayco551befa2010-07-15 17:05:32 -0700433 request = message.flow_mod()
434 request.match = match
435 request.buffer_id = 0xffffffff
436 if check_expire:
437 request.flags |= ofp.OFPFF_SEND_FLOW_REM
438 request.hard_timeout = 1
439
440 if action_list is not None:
441 for act in action_list:
442 parent.logger.debug("Adding action " + act.show())
443 rv = request.actions.add(act)
444 parent.assertTrue(rv, "Could not add action" + act.show())
445
446 # Set up output/enqueue action if directed
447 if egr_queue is not None:
Dan Talaycof6e76c02012-03-23 10:56:12 -0700448 parent.assertTrue(egr_ports is not None, "Egress port not set")
Dan Talayco551befa2010-07-15 17:05:32 -0700449 act = action.action_enqueue()
Dan Talaycof6e76c02012-03-23 10:56:12 -0700450 for egr_port in egr_port_list:
451 act.port = egr_port
452 act.queue_id = egr_queue
453 rv = request.actions.add(act)
454 parent.assertTrue(rv, "Could not add enqueue action " +
455 str(egr_port) + " Q: " + str(egr_queue))
456 elif egr_ports is not None:
457 for egr_port in egr_port_list:
458 act = action.action_output()
459 act.port = egr_port
460 rv = request.actions.add(act)
461 parent.assertTrue(rv, "Could not add output action " +
462 str(egr_port))
Dan Talayco551befa2010-07-15 17:05:32 -0700463
464 parent.logger.debug(request.show())
465
466 return request
467
Jeffrey Townsendf3eae9c2012-03-28 18:23:21 -0700468def flow_msg_install(parent, request, clear_table_override=None):
Dan Talayco551befa2010-07-15 17:05:32 -0700469 """
470 Install a flow mod message in the switch
471
472 @param parent Must implement controller, assertEqual, assertTrue
473 @param request The request, all set to go
474 @param clear_table If true, clear the flow table before installing
475 """
Dan Talayco8a64e332012-03-28 14:53:20 -0700476
477 clear_table = test_param_get(parent.config, 'clear_table', default=True)
Jeffrey Townsendf3eae9c2012-03-28 18:23:21 -0700478 if(clear_table_override != None):
479 clear_table = clear_table_override
480
481 if clear_table:
Dan Talayco551befa2010-07-15 17:05:32 -0700482 parent.logger.debug("Clear flow table")
483 rc = delete_all_flows(parent.controller, parent.logger)
484 parent.assertEqual(rc, 0, "Failed to delete all flows")
Dan Talayco0fc08bd2012-04-09 16:56:18 -0700485 parent.assertEqual(do_barrier(parent.controller), 0, "Barrier failed")
Dan Talayco551befa2010-07-15 17:05:32 -0700486
487 parent.logger.debug("Insert flow")
488 rv = parent.controller.message_send(request)
489 parent.assertTrue(rv != -1, "Error installing flow mod")
Dan Talayco0fc08bd2012-04-09 16:56:18 -0700490 parent.assertEqual(do_barrier(parent.controller), 0, "Barrier failed")
Dan Talayco551befa2010-07-15 17:05:32 -0700491
Dan Talaycof6e76c02012-03-23 10:56:12 -0700492def flow_match_test_port_pair(parent, ing_port, egr_ports, wildcards=0,
Dan Talayco551befa2010-07-15 17:05:32 -0700493 dl_vlan=-1, pkt=None, exp_pkt=None,
494 action_list=None, check_expire=False):
495 """
496 Flow match test on single TCP packet
Dan Talaycof6e76c02012-03-23 10:56:12 -0700497 @param egr_ports A single port or list of ports
Dan Talayco551befa2010-07-15 17:05:32 -0700498
499 Run test with packet through switch from ing_port to egr_port
500 See flow_match_test for parameter descriptions
501 """
502
Dan Talaycof6e76c02012-03-23 10:56:12 -0700503 parent.logger.info("Pkt match test: " + str(ing_port) + " to " +
504 str(egr_ports))
Dan Talayco551befa2010-07-15 17:05:32 -0700505 parent.logger.debug(" WC: " + hex(wildcards) + " vlan: " + str(dl_vlan) +
Dan Talayco98fada92010-07-17 00:36:21 -0700506 " expire: " + str(check_expire))
Dan Talayco551befa2010-07-15 17:05:32 -0700507 if pkt is None:
508 pkt = simple_tcp_packet(dl_vlan_enable=(dl_vlan >= 0), dl_vlan=dl_vlan)
509
510 request = flow_msg_create(parent, pkt, ing_port=ing_port,
Dan Talaycof6e76c02012-03-23 10:56:12 -0700511 wildcards=wildcards, egr_ports=egr_ports,
Dan Talayco551befa2010-07-15 17:05:32 -0700512 action_list=action_list)
513
514 flow_msg_install(parent, request)
515
Dan Talaycof6e76c02012-03-23 10:56:12 -0700516 parent.logger.debug("Send packet: " + str(ing_port) + " to " +
517 str(egr_ports))
Dan Talayco551befa2010-07-15 17:05:32 -0700518 parent.dataplane.send(ing_port, str(pkt))
519
520 if exp_pkt is None:
521 exp_pkt = pkt
Dan Talaycoc948d0b2012-03-23 12:17:54 -0700522 receive_pkt_verify(parent, egr_ports, exp_pkt, ing_port)
Dan Talayco551befa2010-07-15 17:05:32 -0700523
524 if check_expire:
525 #@todo Not all HW supports both pkt and byte counters
526 flow_removed_verify(parent, request, pkt_count=1, byte_count=len(pkt))
527
Dan Talaycof6e76c02012-03-23 10:56:12 -0700528def get_egr_list(parent, of_ports, how_many, exclude_list=[]):
529 """
530 Generate a list of ports avoiding those in the exclude list
531 @param parent Supplies logger
532 @param of_ports List of OF port numbers
533 @param how_many Number of ports to be added to the list
534 @param exclude_list List of ports not to be used
535 @returns An empty list if unable to find enough ports
536 """
537
Dan Talaycoc948d0b2012-03-23 12:17:54 -0700538 if how_many == 0:
539 return []
540
Dan Talaycof6e76c02012-03-23 10:56:12 -0700541 count = 0
542 egr_ports = []
543 for egr_idx in range(len(of_ports)):
544 if of_ports[egr_idx] not in exclude_list:
545 egr_ports.append(of_ports[egr_idx])
546 count += 1
547 if count >= how_many:
548 return egr_ports
549 parent.logger.debug("Could not generate enough egress ports for test")
550 return []
551
Dan Talayco551befa2010-07-15 17:05:32 -0700552def flow_match_test(parent, port_map, wildcards=0, dl_vlan=-1, pkt=None,
553 exp_pkt=None, action_list=None, check_expire=False,
Dan Talaycoc948d0b2012-03-23 12:17:54 -0700554 max_test=0, egr_count=1, ing_port=False):
Dan Talayco551befa2010-07-15 17:05:32 -0700555 """
556 Run flow_match_test_port_pair on all port pairs
557
558 @param max_test If > 0 no more than this number of tests are executed.
559 @param parent Must implement controller, dataplane, assertTrue, assertEqual
560 and logger
561 @param pkt If not None, use this packet for ingress
562 @param wildcards For flow match entry
Dan Talayco79184222010-11-01 12:24:29 -0700563 @param dl_vlan If not -1, and pkt is None, create a pkt w/ VLAN tag
Dan Talayco551befa2010-07-15 17:05:32 -0700564 @param exp_pkt If not None, use this as the expected output pkt; els use pkt
565 @param action_list Additional actions to add to flow mod
566 @param check_expire Check for flow expiration message
Dan Talaycocfa172f2012-03-23 12:03:00 -0700567 @param egr_count Number of egress ports; -1 means get from config w/ dflt 2
Dan Talayco551befa2010-07-15 17:05:32 -0700568 """
569 of_ports = port_map.keys()
570 of_ports.sort()
571 parent.assertTrue(len(of_ports) > 1, "Not enough ports for test")
572 test_count = 0
573
Dan Talaycocfa172f2012-03-23 12:03:00 -0700574 if egr_count == -1:
575 egr_count = test_param_get(parent.config, 'egr_count', default=2)
576
Dan Talayco551befa2010-07-15 17:05:32 -0700577 for ing_idx in range(len(of_ports)):
578 ingress_port = of_ports[ing_idx]
Dan Talaycof6e76c02012-03-23 10:56:12 -0700579 egr_ports = get_egr_list(parent, of_ports, egr_count,
580 exclude_list=[ingress_port])
Dan Talaycoc948d0b2012-03-23 12:17:54 -0700581 if ing_port:
582 egr_ports.append(ofp.OFPP_IN_PORT)
Dan Talaycof6e76c02012-03-23 10:56:12 -0700583 if len(egr_ports) == 0:
584 parent.assertTrue(0, "Failed to generate egress port list")
585
586 flow_match_test_port_pair(parent, ingress_port, egr_ports,
587 wildcards=wildcards, dl_vlan=dl_vlan,
588 pkt=pkt, exp_pkt=exp_pkt,
589 action_list=action_list,
590 check_expire=check_expire)
591 test_count += 1
592 if (max_test > 0) and (test_count > max_test):
593 parent.logger.info("Ran " + str(test_count) + " tests; exiting")
594 return
Dan Talayco551befa2010-07-15 17:05:32 -0700595
Dan Talayco4b2bee62010-07-20 14:10:05 -0700596def test_param_get(config, key, default=None):
597 """
598 Return value passed via test-params if present
599
600 @param config The configuration structure for OFTest
601 @param key The lookup key
602 @param default Default value to use if not found
603
604 If the pair 'key=val' appeared in the string passed to --test-params
605 on the command line, return val (as interpreted by exec). Otherwise
606 return default value.
Dan Talaycof6e76c02012-03-23 10:56:12 -0700607
608 WARNING: TEST PARAMETERS MUST BE PYTHON IDENTIFIERS;
609 eg egr_count, not egr-count.
Dan Talayco4b2bee62010-07-20 14:10:05 -0700610 """
611 try:
612 exec config["test_params"]
613 except:
614 return default
615
616 s = "val = " + str(key)
617 try:
618 exec s
619 return val
620 except:
621 return default
622
623def action_generate(parent, field_to_mod, mod_field_vals):
624 """
625 Create an action to modify the field indicated in field_to_mod
626
627 @param parent Must implement, assertTrue
628 @param field_to_mod The field to modify as a string name
629 @param mod_field_vals Hash of values to use for modified values
630 """
631
632 act = None
633
634 if field_to_mod in ['pktlen']:
635 return None
636
637 if field_to_mod == 'dl_dst':
638 act = action.action_set_dl_dst()
639 act.dl_addr = parse.parse_mac(mod_field_vals['dl_dst'])
640 elif field_to_mod == 'dl_src':
641 act = action.action_set_dl_src()
642 act.dl_addr = parse.parse_mac(mod_field_vals['dl_src'])
643 elif field_to_mod == 'dl_vlan_enable':
644 if not mod_field_vals['dl_vlan_enable']: # Strip VLAN tag
645 act = action.action_strip_vlan()
646 # Add VLAN tag is handled by dl_vlan field
647 # Will return None in this case
648 elif field_to_mod == 'dl_vlan':
649 act = action.action_set_vlan_vid()
650 act.vlan_vid = mod_field_vals['dl_vlan']
651 elif field_to_mod == 'dl_vlan_pcp':
652 act = action.action_set_vlan_pcp()
653 act.vlan_pcp = mod_field_vals['dl_vlan_pcp']
654 elif field_to_mod == 'ip_src':
655 act = action.action_set_nw_src()
656 act.nw_addr = parse.parse_ip(mod_field_vals['ip_src'])
657 elif field_to_mod == 'ip_dst':
658 act = action.action_set_nw_dst()
659 act.nw_addr = parse.parse_ip(mod_field_vals['ip_dst'])
660 elif field_to_mod == 'ip_tos':
661 act = action.action_set_nw_tos()
662 act.nw_tos = mod_field_vals['ip_tos']
663 elif field_to_mod == 'tcp_sport':
664 act = action.action_set_tp_src()
665 act.tp_port = mod_field_vals['tcp_sport']
666 elif field_to_mod == 'tcp_dport':
667 act = action.action_set_tp_dst()
668 act.tp_port = mod_field_vals['tcp_dport']
669 else:
670 parent.assertTrue(0, "Unknown field to modify: " + str(field_to_mod))
671
672 return act
673
674def pkt_action_setup(parent, start_field_vals={}, mod_field_vals={},
675 mod_fields={}, check_test_params=False):
676 """
677 Set up the ingress and expected packet and action list for a test
678
679 @param parent Must implement, assertTrue, config hash and logger
680 @param start_field_values Field values to use for ingress packet (optional)
681 @param mod_field_values Field values to use for modified packet (optional)
682 @param mod_fields The list of fields to be modified by the switch in the test.
683 @params check_test_params If True, will check the parameters vid, add_vlan
684 and strip_vlan from the command line.
685
686 Returns a triple: pkt-to-send, expected-pkt, action-list
687 """
688
689 new_actions = []
690
Dan Talayco4b2bee62010-07-20 14:10:05 -0700691 base_pkt_params = {}
692 base_pkt_params['pktlen'] = 100
693 base_pkt_params['dl_dst'] = '00:DE:F0:12:34:56'
694 base_pkt_params['dl_src'] = '00:23:45:67:89:AB'
695 base_pkt_params['dl_vlan_enable'] = False
696 base_pkt_params['dl_vlan'] = 2
697 base_pkt_params['dl_vlan_pcp'] = 0
698 base_pkt_params['ip_src'] = '192.168.0.1'
699 base_pkt_params['ip_dst'] = '192.168.0.2'
700 base_pkt_params['ip_tos'] = 0
701 base_pkt_params['tcp_sport'] = 1234
702 base_pkt_params['tcp_dport'] = 80
703 for keyname in start_field_vals.keys():
704 base_pkt_params[keyname] = start_field_vals[keyname]
705
706 mod_pkt_params = {}
707 mod_pkt_params['pktlen'] = 100
708 mod_pkt_params['dl_dst'] = '00:21:0F:ED:CB:A9'
709 mod_pkt_params['dl_src'] = '00:ED:CB:A9:87:65'
710 mod_pkt_params['dl_vlan_enable'] = False
711 mod_pkt_params['dl_vlan'] = 3
712 mod_pkt_params['dl_vlan_pcp'] = 7
713 mod_pkt_params['ip_src'] = '10.20.30.40'
714 mod_pkt_params['ip_dst'] = '50.60.70.80'
715 mod_pkt_params['ip_tos'] = 0xf0
716 mod_pkt_params['tcp_sport'] = 4321
717 mod_pkt_params['tcp_dport'] = 8765
718 for keyname in mod_field_vals.keys():
719 mod_pkt_params[keyname] = mod_field_vals[keyname]
720
721 # Check for test param modifications
722 strip = False
723 if check_test_params:
724 add_vlan = test_param_get(parent.config, 'add_vlan')
725 strip_vlan = test_param_get(parent.config, 'strip_vlan')
726 vid = test_param_get(parent.config, 'vid')
727
728 if add_vlan and strip_vlan:
729 parent.assertTrue(0, "Add and strip VLAN both specified")
730
731 if vid:
732 base_pkt_params['dl_vlan_enable'] = True
733 base_pkt_params['dl_vlan'] = vid
734 if 'dl_vlan' in mod_fields:
735 mod_pkt_params['dl_vlan'] = vid + 1
736
737 if add_vlan:
738 base_pkt_params['dl_vlan_enable'] = False
739 mod_pkt_params['dl_vlan_enable'] = True
740 mod_pkt_params['pktlen'] = base_pkt_params['pktlen'] + 4
741 mod_fields.append('pktlen')
742 mod_fields.append('dl_vlan_enable')
743 if 'dl_vlan' not in mod_fields:
744 mod_fields.append('dl_vlan')
745 elif strip_vlan:
746 base_pkt_params['dl_vlan_enable'] = True
747 mod_pkt_params['dl_vlan_enable'] = False
748 mod_pkt_params['pktlen'] = base_pkt_params['pktlen'] - 4
749 mod_fields.append('dl_vlan_enable')
750 mod_fields.append('pktlen')
751
752 # Build the ingress packet
753 ingress_pkt = simple_tcp_packet(**base_pkt_params)
754
755 # Build the expected packet, modifying the indicated fields
756 for item in mod_fields:
757 base_pkt_params[item] = mod_pkt_params[item]
758 act = action_generate(parent, item, mod_pkt_params)
759 if act:
760 new_actions.append(act)
761
762 expected_pkt = simple_tcp_packet(**base_pkt_params)
763
764 return (ingress_pkt, expected_pkt, new_actions)
Dan Talayco677c0b72011-08-23 22:53:38 -0700765
766# Generate a simple "drop" flow mod
767# If in_band is true, then only drop from first test port
768def flow_mod_gen(port_map, in_band):
769 request = message.flow_mod()
770 request.match.wildcards = ofp.OFPFW_ALL
771 if in_band:
772 request.match.wildcards = ofp.OFPFW_ALL - ofp.OFPFW_IN_PORT
773 for of_port, ifname in port_map.items(): # Grab first port
774 break
775 request.match.in_port = of_port
776 request.buffer_id = 0xffffffff
777 return request
Dan Talaycoba3745c2010-07-21 21:51:08 -0700778
779def skip_message_emit(parent, s):
780 """
781 Print out a 'skipped' message to stderr
782
783 @param s The string to print out to the log file
784 @param parent Must implement config and logger objects
785 """
786 global skipped_test_count
787
788 skipped_test_count += 1
789 parent.logger.info("Skipping: " + s)
790 if parent.config["dbg_level"] < logging.WARNING:
791 sys.stderr.write("(skipped) ")
792 else:
793 sys.stderr.write("(S)")
Dan Talayco677c0b72011-08-23 22:53:38 -0700794
Dan Talayco8a64e332012-03-28 14:53:20 -0700795
796def all_stats_get(parent):
797 """
798 Get the aggregate stats for all flows in the table
799 @param parent Test instance with controller connection and assert
800 @returns dict with keys flows, packets, bytes, active (flows),
801 lookups, matched
802 """
803 stat_req = message.aggregate_stats_request()
804 stat_req.match = ofp.ofp_match()
805 stat_req.match.wildcards = ofp.OFPFW_ALL
806 stat_req.table_id = 0xff
807 stat_req.out_port = ofp.OFPP_NONE
808
809 rv = {}
810
Rich Lanec8aaa3e2012-07-26 19:28:02 -0700811 (reply, pkt) = parent.controller.transact(stat_req)
Dan Talayco8a64e332012-03-28 14:53:20 -0700812 parent.assertTrue(len(reply.stats) == 1, "Did not receive flow stats reply")
813
814 for obj in reply.stats:
815 (rv["flows"], rv["packets"], rv["bytes"]) = (obj.flow_count,
816 obj.packet_count, obj.byte_count)
817 break
818
819 request = message.table_stats_request()
Rich Lanec8aaa3e2012-07-26 19:28:02 -0700820 (reply , pkt) = parent.controller.transact(request)
Dan Talayco8a64e332012-03-28 14:53:20 -0700821
822
823 (rv["active"], rv["lookups"], rv["matched"]) = (0,0,0)
824 for obj in reply.stats:
825 rv["active"] += obj.active_count
826 rv["lookups"] += obj.lookup_count
827 rv["matched"] += obj.matched_count
828
829 return rv
Dan Talayco2baf8b52012-03-30 09:55:42 -0700830
831FILTER=''.join([(len(repr(chr(x)))==3) and chr(x) or '.'
832 for x in range(256)])
833
834def hex_dump_buffer(src, length=16):
835 """
836 Convert src to a hex dump string and return the string
837 @param src The source buffer
838 @param length The number of bytes shown in each line
839 @returns A string showing the hex dump
840 """
Dan Talaycoc516fa02012-04-12 22:28:43 -0700841 result = ["\n"]
Dan Talayco2baf8b52012-03-30 09:55:42 -0700842 for i in xrange(0, len(src), length):
843 chars = src[i:i+length]
844 hex = ' '.join(["%02x" % ord(x) for x in chars])
845 printable = ''.join(["%s" % ((ord(x) <= 127 and
846 FILTER[ord(x)]) or '.') for x in chars])
847 result.append("%04x %-*s %s\n" % (i, length*3, hex, printable))
848 return ''.join(result)
849
850def format_packet(pkt):
851 return "Packet length %d \n%s" % (len(str(pkt)),
852 hex_dump_buffer(str(pkt)))