blob: 0d2dd0953465d76ecc0ae067603b7dbbb14dfdb8 [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 Talayco73f84012012-10-02 09:23:18 -070021import time
Dan Talaycoc901f4d2010-03-07 21:55:45 -080022
Dan Talaycoba3745c2010-07-21 21:51:08 -070023global skipped_test_count
24skipped_test_count = 0
25
Dan Talayco551befa2010-07-15 17:05:32 -070026# Some useful defines
27IP_ETHERTYPE = 0x800
28TCP_PROTOCOL = 0x6
29UDP_PROTOCOL = 0x11
30
Jeffrey Townsend5cb7ed32012-08-17 18:11:01 +000031MINSIZE = 0
32
Dan Talayco98fada92010-07-17 00:36:21 -070033def clear_switch(parent, port_list, logger):
34 """
35 Clear the switch configuration
36
37 @param parent Object implementing controller and assert equal
38 @param logger Logging object
39 """
40 for port in port_list:
41 clear_port_config(parent, port, logger)
42 delete_all_flows(parent.controller, logger)
43
Dan Talayco41eae8b2010-03-10 13:57:06 -080044def delete_all_flows(ctrl, logger):
45 """
46 Delete all flows on the switch
47 @param ctrl The controller object for the test
48 @param logger Logging object
49 """
50
Dan Talaycoc901f4d2010-03-07 21:55:45 -080051 logger.info("Deleting all flows")
52 msg = message.flow_mod()
53 msg.match.wildcards = ofp.OFPFW_ALL
Dan Talayco41eae8b2010-03-10 13:57:06 -080054 msg.out_port = ofp.OFPP_NONE
Dan Talaycoc901f4d2010-03-07 21:55:45 -080055 msg.command = ofp.OFPFC_DELETE
56 msg.buffer_id = 0xffffffff
Dan Talayco41eae8b2010-03-10 13:57:06 -080057 return ctrl.message_send(msg)
58
Dan Talayco98fada92010-07-17 00:36:21 -070059def clear_port_config(parent, port, logger):
60 """
61 Clear the port configuration (currently only no flood setting)
62
63 @param parent Object implementing controller and assert equal
64 @param logger Logging object
65 """
66 rv = port_config_set(parent.controller, port,
67 0, ofp.OFPPC_NO_FLOOD, logger)
68 self.assertEqual(rv, 0, "Failed to reset port config")
69
Ed Swierk99a74de2012-08-22 06:40:54 -070070def required_wildcards(parent):
71 w = test_param_get(parent.config, 'required_wildcards', default='default')
72 if w == 'l3-l4':
73 return (ofp.OFPFW_NW_SRC_ALL | ofp.OFPFW_NW_DST_ALL | ofp.OFPFW_NW_TOS
74 | ofp.OFPFW_NW_PROTO | ofp.OFPFW_TP_SRC | ofp.OFPFW_TP_DST)
75 else:
76 return 0
77
Dan Talayco41eae8b2010-03-10 13:57:06 -080078def simple_tcp_packet(pktlen=100,
79 dl_dst='00:01:02:03:04:05',
80 dl_src='00:06:07:08:09:0a',
Tatsuya Yabe460321e2010-05-25 17:50:49 -070081 dl_vlan_enable=False,
82 dl_vlan=0,
83 dl_vlan_pcp=0,
Dan Talayco551befa2010-07-15 17:05:32 -070084 dl_vlan_cfi=0,
Dan Talayco41eae8b2010-03-10 13:57:06 -080085 ip_src='192.168.0.1',
86 ip_dst='192.168.0.2',
Tatsuya Yabe460321e2010-05-25 17:50:49 -070087 ip_tos=0,
Dan Talayco41eae8b2010-03-10 13:57:06 -080088 tcp_sport=1234,
89 tcp_dport=80
90 ):
91 """
92 Return a simple dataplane TCP packet
93
94 Supports a few parameters:
95 @param len Length of packet in bytes w/o CRC
96 @param dl_dst Destinatino MAC
97 @param dl_src Source MAC
Tatsuya Yabe460321e2010-05-25 17:50:49 -070098 @param dl_vlan_enable True if the packet is with vlan, False otherwise
99 @param dl_vlan VLAN ID
100 @param dl_vlan_pcp VLAN priority
Dan Talayco41eae8b2010-03-10 13:57:06 -0800101 @param ip_src IP source
102 @param ip_dst IP destination
Tatsuya Yabe460321e2010-05-25 17:50:49 -0700103 @param ip_tos IP ToS
Dan Talayco41eae8b2010-03-10 13:57:06 -0800104 @param tcp_dport TCP destination port
105 @param ip_sport TCP source port
106
107 Generates a simple TCP request. Users
108 shouldn't assume anything about this packet other than that
109 it is a valid ethernet/IP/TCP frame.
110 """
Jeffrey Townsend5cb7ed32012-08-17 18:11:01 +0000111
112 if MINSIZE > pktlen:
113 pktlen = MINSIZE
114
Dan Talayco551befa2010-07-15 17:05:32 -0700115 # Note Dot1Q.id is really CFI
Tatsuya Yabe460321e2010-05-25 17:50:49 -0700116 if (dl_vlan_enable):
117 pkt = scapy.Ether(dst=dl_dst, src=dl_src)/ \
Dan Talayco551befa2010-07-15 17:05:32 -0700118 scapy.Dot1Q(prio=dl_vlan_pcp, id=dl_vlan_cfi, vlan=dl_vlan)/ \
Tatsuya Yabe460321e2010-05-25 17:50:49 -0700119 scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos)/ \
120 scapy.TCP(sport=tcp_sport, dport=tcp_dport)
121 else:
122 pkt = scapy.Ether(dst=dl_dst, src=dl_src)/ \
123 scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos)/ \
124 scapy.TCP(sport=tcp_sport, dport=tcp_dport)
125
Dan Talayco41eae8b2010-03-10 13:57:06 -0800126 pkt = pkt/("D" * (pktlen - len(pkt)))
127
128 return pkt
129
Tatsuya Yabeb8fb3c32010-06-14 15:48:36 -0700130def simple_icmp_packet(pktlen=60,
131 dl_dst='00:01:02:03:04:05',
132 dl_src='00:06:07:08:09:0a',
133 dl_vlan_enable=False,
134 dl_vlan=0,
135 dl_vlan_pcp=0,
136 ip_src='192.168.0.1',
137 ip_dst='192.168.0.2',
138 ip_tos=0,
139 icmp_type=8,
140 icmp_code=0
141 ):
142 """
143 Return a simple ICMP packet
144
145 Supports a few parameters:
146 @param len Length of packet in bytes w/o CRC
147 @param dl_dst Destinatino MAC
148 @param dl_src Source MAC
149 @param dl_vlan_enable True if the packet is with vlan, False otherwise
150 @param dl_vlan VLAN ID
151 @param dl_vlan_pcp VLAN priority
152 @param ip_src IP source
153 @param ip_dst IP destination
154 @param ip_tos IP ToS
155 @param icmp_type ICMP type
156 @param icmp_code ICMP code
157
158 Generates a simple ICMP ECHO REQUEST. Users
159 shouldn't assume anything about this packet other than that
160 it is a valid ethernet/ICMP frame.
161 """
Jeffrey Townsend5cb7ed32012-08-17 18:11:01 +0000162
163 if MINSIZE > pktlen:
164 pktlen = MINSIZE
165
Tatsuya Yabeb8fb3c32010-06-14 15:48:36 -0700166 if (dl_vlan_enable):
167 pkt = scapy.Ether(dst=dl_dst, src=dl_src)/ \
168 scapy.Dot1Q(prio=dl_vlan_pcp, id=0, vlan=dl_vlan)/ \
169 scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos)/ \
170 scapy.ICMP(type=icmp_type, code=icmp_code)
171 else:
172 pkt = scapy.Ether(dst=dl_dst, src=dl_src)/ \
173 scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos)/ \
174 scapy.ICMP(type=icmp_type, code=icmp_code)
175
176 pkt = pkt/("0" * (pktlen - len(pkt)))
177
178 return pkt
179
Ed Swierk0aeff8c2012-03-23 20:27:18 -0700180def simple_eth_packet(pktlen=60,
181 dl_dst='00:01:02:03:04:05',
182 dl_src='01:80:c2:00:00:00',
183 dl_type=0x88cc):
Jeffrey Townsend5cb7ed32012-08-17 18:11:01 +0000184
185 if MINSIZE > pktlen:
186 pktlen = MINSIZE
187
Ed Swierk0aeff8c2012-03-23 20:27:18 -0700188 pkt = scapy.Ether(dst=dl_dst, src=dl_src, type=dl_type)
189
190 pkt = pkt/("0" * (pktlen - len(pkt)))
191
192 return pkt
193
Dan Talayco41eae8b2010-03-10 13:57:06 -0800194def do_barrier(ctrl):
Dan Talayco0fc08bd2012-04-09 16:56:18 -0700195 """
196 Do a barrier command
197 Return 0 on success, -1 on error
198 """
Dan Talayco41eae8b2010-03-10 13:57:06 -0800199 b = message.barrier_request()
Dan Talaycof6b94832012-04-12 21:50:57 -0700200 (resp, pkt) = ctrl.transact(b)
201 # We'll trust the transaction processing in the controller that xid matched
Dan Talayco0fc08bd2012-04-09 16:56:18 -0700202 if not resp:
203 return -1
204 return 0
Dan Talayco92c99122010-06-03 13:53:18 -0700205
206def port_config_get(controller, port_no, logger):
207 """
208 Get a port's configuration
209
210 Gets the switch feature configuration and grabs one port's
211 configuration
212
213 @returns (hwaddr, config, advert) The hwaddress, configuration and
214 advertised values
215 """
216 request = message.features_request()
Rich Lanec8aaa3e2012-07-26 19:28:02 -0700217 reply, pkt = controller.transact(request)
Dan Talayco92c99122010-06-03 13:53:18 -0700218 logger.debug(reply.show())
219 if reply is None:
220 logger.warn("Get feature request failed")
221 return None, None, None
222 for idx in range(len(reply.ports)):
223 if reply.ports[idx].port_no == port_no:
224 return (reply.ports[idx].hw_addr, reply.ports[idx].config,
225 reply.ports[idx].advertised)
226
227 logger.warn("Did not find port number for port config")
228 return None, None, None
229
230def port_config_set(controller, port_no, config, mask, logger):
231 """
232 Set the port configuration according the given parameters
233
234 Gets the switch feature configuration and updates one port's
235 configuration value according to config and mask
236 """
237 logger.info("Setting port " + str(port_no) + " to config " + str(config))
238 request = message.features_request()
Rich Lanec8aaa3e2012-07-26 19:28:02 -0700239 reply, pkt = controller.transact(request)
Dan Talayco92c99122010-06-03 13:53:18 -0700240 if reply is None:
241 return -1
242 logger.debug(reply.show())
243 for idx in range(len(reply.ports)):
244 if reply.ports[idx].port_no == port_no:
245 break
246 if idx >= len(reply.ports):
247 return -1
248 mod = message.port_mod()
249 mod.port_no = port_no
250 mod.hw_addr = reply.ports[idx].hw_addr
251 mod.config = config
252 mod.mask = mask
253 mod.advertise = reply.ports[idx].advertised
254 rv = controller.message_send(mod)
255 return rv
256
Ken Chiang1bf01602012-04-04 10:48:23 -0700257def receive_pkt_check(dp, pkt, yes_ports, no_ports, assert_if, logger,
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700258 config):
Dan Talayco92c99122010-06-03 13:53:18 -0700259 """
260 Check for proper receive packets across all ports
Ken Chiang1bf01602012-04-04 10:48:23 -0700261 @param dp The dataplane object
Dan Talayco92c99122010-06-03 13:53:18 -0700262 @param pkt Expected packet; may be None if yes_ports is empty
263 @param yes_ports Set or list of ports that should recieve packet
264 @param no_ports Set or list of ports that should not receive packet
265 @param assert_if Object that implements assertXXX
266 """
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700267 exp_pkt_arg = None
268 if config and config["relax"]:
269 exp_pkt_arg = pkt
270
Dan Talayco92c99122010-06-03 13:53:18 -0700271 for ofport in yes_ports:
272 logger.debug("Checking for pkt on port " + str(ofport))
Ken Chiang1bf01602012-04-04 10:48:23 -0700273 (rcv_port, rcv_pkt, pkt_time) = dp.poll(
Rich Lanec8aaa3e2012-07-26 19:28:02 -0700274 port_number=ofport, exp_pkt=exp_pkt_arg)
Dan Talayco92c99122010-06-03 13:53:18 -0700275 assert_if.assertTrue(rcv_pkt is not None,
276 "Did not receive pkt on " + str(ofport))
Ken Chiang1bf01602012-04-04 10:48:23 -0700277 if not dataplane.match_exp_pkt(pkt, rcv_pkt):
278 logger.debug("Sent %s" % format_packet(pkt))
279 logger.debug("Resp %s" % format_packet(rcv_pkt))
280 assert_if.assertTrue(dataplane.match_exp_pkt(pkt, rcv_pkt),
281 "Response packet does not match send packet " +
282 "on port " + str(ofport))
Dan Talayco73f84012012-10-02 09:23:18 -0700283 if len(no_ports) > 0:
284 time.sleep(1)
Dan Talayco92c99122010-06-03 13:53:18 -0700285 for ofport in no_ports:
286 logger.debug("Negative check for pkt on port " + str(ofport))
Ken Chiang1bf01602012-04-04 10:48:23 -0700287 (rcv_port, rcv_pkt, pkt_time) = dp.poll(
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700288 port_number=ofport, timeout=1, exp_pkt=exp_pkt_arg)
Dan Talayco92c99122010-06-03 13:53:18 -0700289 assert_if.assertTrue(rcv_pkt is None,
290 "Unexpected pkt on port " + str(ofport))
Dan Talayco551befa2010-07-15 17:05:32 -0700291
292
Dan Talaycoc948d0b2012-03-23 12:17:54 -0700293def receive_pkt_verify(parent, egr_ports, exp_pkt, ing_port):
Dan Talayco551befa2010-07-15 17:05:32 -0700294 """
295 Receive a packet and verify it matches an expected value
Dan Talaycof6e76c02012-03-23 10:56:12 -0700296 @param egr_port A single port or list of ports
Dan Talayco551befa2010-07-15 17:05:32 -0700297
298 parent must implement dataplane, assertTrue and assertEqual
299 """
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700300 exp_pkt_arg = None
301 if parent.config["relax"]:
302 exp_pkt_arg = exp_pkt
303
Dan Talaycof6e76c02012-03-23 10:56:12 -0700304 if type(egr_ports) == type([]):
305 egr_port_list = egr_ports
306 else:
307 egr_port_list = [egr_ports]
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700308
Dan Talaycof6e76c02012-03-23 10:56:12 -0700309 # Expect a packet from each port on egr port list
310 for egr_port in egr_port_list:
Dan Talaycod8ae7582012-03-23 12:24:56 -0700311 check_port = egr_port
Dan Talaycoc948d0b2012-03-23 12:17:54 -0700312 if egr_port == ofp.OFPP_IN_PORT:
Dan Talaycod8ae7582012-03-23 12:24:56 -0700313 check_port = ing_port
Dan Talaycof6e76c02012-03-23 10:56:12 -0700314 (rcv_port, rcv_pkt, pkt_time) = parent.dataplane.poll(
Rich Lanec8aaa3e2012-07-26 19:28:02 -0700315 port_number=check_port, exp_pkt=exp_pkt_arg)
Dan Talayco551befa2010-07-15 17:05:32 -0700316
Dan Talaycof6e76c02012-03-23 10:56:12 -0700317 if rcv_pkt is None:
Dan Talaycod8ae7582012-03-23 12:24:56 -0700318 parent.logger.error("ERROR: No packet received from " +
319 str(check_port))
Dan Talayco551befa2010-07-15 17:05:32 -0700320
Dan Talaycof6e76c02012-03-23 10:56:12 -0700321 parent.assertTrue(rcv_pkt is not None,
Dan Talaycod8ae7582012-03-23 12:24:56 -0700322 "Did not receive packet port " + str(check_port))
Dan Talaycof6e76c02012-03-23 10:56:12 -0700323 parent.logger.debug("Packet len " + str(len(rcv_pkt)) + " in on " +
324 str(rcv_port))
325
326 if str(exp_pkt) != str(rcv_pkt):
327 parent.logger.error("ERROR: Packet match failed.")
328 parent.logger.debug("Expected len " + str(len(exp_pkt)) + ": "
329 + str(exp_pkt).encode('hex'))
330 parent.logger.debug("Received len " + str(len(rcv_pkt)) + ": "
331 + str(rcv_pkt).encode('hex'))
332 parent.assertEqual(str(exp_pkt), str(rcv_pkt),
Dan Talaycod8ae7582012-03-23 12:24:56 -0700333 "Packet match error on port " + str(check_port))
Dan Talaycof6e76c02012-03-23 10:56:12 -0700334
Dan Talayco551befa2010-07-15 17:05:32 -0700335def match_verify(parent, req_match, res_match):
336 """
337 Verify flow matches agree; if they disagree, report where
338
339 parent must implement assertEqual
340 Use str() to ensure content is compared and not pointers
341 """
342
343 parent.assertEqual(req_match.wildcards, res_match.wildcards,
344 'Match failed: wildcards: ' + hex(req_match.wildcards) +
345 " != " + hex(res_match.wildcards))
346 parent.assertEqual(req_match.in_port, res_match.in_port,
347 'Match failed: in_port: ' + str(req_match.in_port) +
348 " != " + str(res_match.in_port))
349 parent.assertEqual(str(req_match.dl_src), str(res_match.dl_src),
350 'Match failed: dl_src: ' + str(req_match.dl_src) +
351 " != " + str(res_match.dl_src))
352 parent.assertEqual(str(req_match.dl_dst), str(res_match.dl_dst),
353 'Match failed: dl_dst: ' + str(req_match.dl_dst) +
354 " != " + str(res_match.dl_dst))
355 parent.assertEqual(req_match.dl_vlan, res_match.dl_vlan,
356 'Match failed: dl_vlan: ' + str(req_match.dl_vlan) +
357 " != " + str(res_match.dl_vlan))
358 parent.assertEqual(req_match.dl_vlan_pcp, res_match.dl_vlan_pcp,
359 'Match failed: dl_vlan_pcp: ' +
360 str(req_match.dl_vlan_pcp) + " != " +
361 str(res_match.dl_vlan_pcp))
362 parent.assertEqual(req_match.dl_type, res_match.dl_type,
363 'Match failed: dl_type: ' + str(req_match.dl_type) +
364 " != " + str(res_match.dl_type))
365
366 if (not(req_match.wildcards & ofp.OFPFW_DL_TYPE)
367 and (req_match.dl_type == IP_ETHERTYPE)):
368 parent.assertEqual(req_match.nw_tos, res_match.nw_tos,
369 'Match failed: nw_tos: ' + str(req_match.nw_tos) +
370 " != " + str(res_match.nw_tos))
371 parent.assertEqual(req_match.nw_proto, res_match.nw_proto,
372 'Match failed: nw_proto: ' + str(req_match.nw_proto) +
373 " != " + str(res_match.nw_proto))
374 parent.assertEqual(req_match.nw_src, res_match.nw_src,
375 'Match failed: nw_src: ' + str(req_match.nw_src) +
376 " != " + str(res_match.nw_src))
377 parent.assertEqual(req_match.nw_dst, res_match.nw_dst,
378 'Match failed: nw_dst: ' + str(req_match.nw_dst) +
379 " != " + str(res_match.nw_dst))
380
381 if (not(req_match.wildcards & ofp.OFPFW_NW_PROTO)
382 and ((req_match.nw_proto == TCP_PROTOCOL)
383 or (req_match.nw_proto == UDP_PROTOCOL))):
384 parent.assertEqual(req_match.tp_src, res_match.tp_src,
385 'Match failed: tp_src: ' +
386 str(req_match.tp_src) +
387 " != " + str(res_match.tp_src))
388 parent.assertEqual(req_match.tp_dst, res_match.tp_dst,
389 'Match failed: tp_dst: ' +
390 str(req_match.tp_dst) +
391 " != " + str(res_match.tp_dst))
392
393def flow_removed_verify(parent, request=None, pkt_count=-1, byte_count=-1):
394 """
395 Receive a flow removed msg and verify it matches expected
396
397 @params parent Must implement controller, assertEqual
398 @param pkt_count If >= 0, verify packet count
399 @param byte_count If >= 0, verify byte count
400 """
401 (response, raw) = parent.controller.poll(ofp.OFPT_FLOW_REMOVED, 2)
402 parent.assertTrue(response is not None, 'No flow removed message received')
403
404 if request is None:
405 return
406
407 parent.assertEqual(request.cookie, response.cookie,
408 "Flow removed cookie error: " +
409 hex(request.cookie) + " != " + hex(response.cookie))
410
411 req_match = request.match
412 res_match = response.match
413 verifyMatchField(req_match, res_match)
414
415 if (req_match.wildcards != 0):
416 parent.assertEqual(request.priority, response.priority,
417 'Flow remove prio mismatch: ' +
418 str(request,priority) + " != " +
419 str(response.priority))
420 parent.assertEqual(response.reason, ofp.OFPRR_HARD_TIMEOUT,
421 'Flow remove reason is not HARD TIMEOUT:' +
422 str(response.reason))
423 if pkt_count >= 0:
424 parent.assertEqual(response.packet_count, pkt_count,
425 'Flow removed failed, packet count: ' +
426 str(response.packet_count) + " != " +
427 str(pkt_count))
428 if byte_count >= 0:
429 parent.assertEqual(response.byte_count, byte_count,
430 'Flow removed failed, byte count: ' +
431 str(response.byte_count) + " != " +
432 str(byte_count))
433
Ed Swierk99a74de2012-08-22 06:40:54 -0700434def packet_to_flow_match(parent, packet):
435 match = parse.packet_to_flow_match(packet)
436 match.wildcards |= required_wildcards(parent)
437 return match
438
439def flow_msg_create(parent, pkt, ing_port=None, action_list=None, wildcards=None,
Dan Talaycof6e76c02012-03-23 10:56:12 -0700440 egr_ports=None, egr_queue=None, check_expire=False, in_band=False):
Dan Talayco551befa2010-07-15 17:05:32 -0700441 """
442 Create a flow message
443
444 Match on packet with given wildcards.
445 See flow_match_test for other parameter descriptoins
446 @param egr_queue if not None, make the output an enqueue action
Dan Talayco677c0b72011-08-23 22:53:38 -0700447 @param in_band if True, do not wildcard ingress port
Dan Talaycof6e76c02012-03-23 10:56:12 -0700448 @param egr_ports None (drop), single port or list of ports
Dan Talayco551befa2010-07-15 17:05:32 -0700449 """
450 match = parse.packet_to_flow_match(pkt)
451 parent.assertTrue(match is not None, "Flow match from pkt failed")
Ed Swierk99a74de2012-08-22 06:40:54 -0700452 if wildcards is None:
453 wildcards = required_wildcards(parent)
Dan Talayco677c0b72011-08-23 22:53:38 -0700454 if in_band:
455 wildcards &= ~ofp.OFPFW_IN_PORT
Dan Talayco551befa2010-07-15 17:05:32 -0700456 match.wildcards = wildcards
457 match.in_port = ing_port
458
Dan Talaycof6e76c02012-03-23 10:56:12 -0700459 if type(egr_ports) == type([]):
460 egr_port_list = egr_ports
461 else:
462 egr_port_list = [egr_ports]
463
Dan Talayco551befa2010-07-15 17:05:32 -0700464 request = message.flow_mod()
465 request.match = match
466 request.buffer_id = 0xffffffff
467 if check_expire:
468 request.flags |= ofp.OFPFF_SEND_FLOW_REM
469 request.hard_timeout = 1
470
471 if action_list is not None:
472 for act in action_list:
473 parent.logger.debug("Adding action " + act.show())
474 rv = request.actions.add(act)
475 parent.assertTrue(rv, "Could not add action" + act.show())
476
477 # Set up output/enqueue action if directed
478 if egr_queue is not None:
Dan Talaycof6e76c02012-03-23 10:56:12 -0700479 parent.assertTrue(egr_ports is not None, "Egress port not set")
Dan Talayco551befa2010-07-15 17:05:32 -0700480 act = action.action_enqueue()
Dan Talaycof6e76c02012-03-23 10:56:12 -0700481 for egr_port in egr_port_list:
482 act.port = egr_port
483 act.queue_id = egr_queue
484 rv = request.actions.add(act)
485 parent.assertTrue(rv, "Could not add enqueue action " +
486 str(egr_port) + " Q: " + str(egr_queue))
487 elif egr_ports is not None:
488 for egr_port in egr_port_list:
489 act = action.action_output()
490 act.port = egr_port
491 rv = request.actions.add(act)
492 parent.assertTrue(rv, "Could not add output action " +
493 str(egr_port))
Dan Talayco551befa2010-07-15 17:05:32 -0700494
495 parent.logger.debug(request.show())
496
497 return request
498
Jeffrey Townsendf3eae9c2012-03-28 18:23:21 -0700499def flow_msg_install(parent, request, clear_table_override=None):
Dan Talayco551befa2010-07-15 17:05:32 -0700500 """
501 Install a flow mod message in the switch
502
503 @param parent Must implement controller, assertEqual, assertTrue
504 @param request The request, all set to go
505 @param clear_table If true, clear the flow table before installing
506 """
Dan Talayco8a64e332012-03-28 14:53:20 -0700507
508 clear_table = test_param_get(parent.config, 'clear_table', default=True)
Jeffrey Townsendf3eae9c2012-03-28 18:23:21 -0700509 if(clear_table_override != None):
510 clear_table = clear_table_override
511
512 if clear_table:
Dan Talayco551befa2010-07-15 17:05:32 -0700513 parent.logger.debug("Clear flow table")
514 rc = delete_all_flows(parent.controller, parent.logger)
515 parent.assertEqual(rc, 0, "Failed to delete all flows")
Dan Talayco0fc08bd2012-04-09 16:56:18 -0700516 parent.assertEqual(do_barrier(parent.controller), 0, "Barrier failed")
Dan Talayco551befa2010-07-15 17:05:32 -0700517
518 parent.logger.debug("Insert flow")
519 rv = parent.controller.message_send(request)
520 parent.assertTrue(rv != -1, "Error installing flow mod")
Dan Talayco0fc08bd2012-04-09 16:56:18 -0700521 parent.assertEqual(do_barrier(parent.controller), 0, "Barrier failed")
Dan Talayco551befa2010-07-15 17:05:32 -0700522
Ed Swierk99a74de2012-08-22 06:40:54 -0700523def flow_match_test_port_pair(parent, ing_port, egr_ports, wildcards=None,
Dan Talayco551befa2010-07-15 17:05:32 -0700524 dl_vlan=-1, pkt=None, exp_pkt=None,
525 action_list=None, check_expire=False):
526 """
527 Flow match test on single TCP packet
Dan Talaycof6e76c02012-03-23 10:56:12 -0700528 @param egr_ports A single port or list of ports
Dan Talayco551befa2010-07-15 17:05:32 -0700529
530 Run test with packet through switch from ing_port to egr_port
531 See flow_match_test for parameter descriptions
532 """
533
Ed Swierk99a74de2012-08-22 06:40:54 -0700534 if wildcards is None:
535 wildcards = required_wildcards(parent)
Dan Talaycof6e76c02012-03-23 10:56:12 -0700536 parent.logger.info("Pkt match test: " + str(ing_port) + " to " +
537 str(egr_ports))
Dan Talayco551befa2010-07-15 17:05:32 -0700538 parent.logger.debug(" WC: " + hex(wildcards) + " vlan: " + str(dl_vlan) +
Dan Talayco98fada92010-07-17 00:36:21 -0700539 " expire: " + str(check_expire))
Dan Talayco551befa2010-07-15 17:05:32 -0700540 if pkt is None:
541 pkt = simple_tcp_packet(dl_vlan_enable=(dl_vlan >= 0), dl_vlan=dl_vlan)
542
543 request = flow_msg_create(parent, pkt, ing_port=ing_port,
Dan Talaycof6e76c02012-03-23 10:56:12 -0700544 wildcards=wildcards, egr_ports=egr_ports,
Dan Talayco551befa2010-07-15 17:05:32 -0700545 action_list=action_list)
546
547 flow_msg_install(parent, request)
548
Dan Talaycof6e76c02012-03-23 10:56:12 -0700549 parent.logger.debug("Send packet: " + str(ing_port) + " to " +
550 str(egr_ports))
Dan Talayco551befa2010-07-15 17:05:32 -0700551 parent.dataplane.send(ing_port, str(pkt))
552
553 if exp_pkt is None:
554 exp_pkt = pkt
Dan Talaycoc948d0b2012-03-23 12:17:54 -0700555 receive_pkt_verify(parent, egr_ports, exp_pkt, ing_port)
Dan Talayco551befa2010-07-15 17:05:32 -0700556
557 if check_expire:
558 #@todo Not all HW supports both pkt and byte counters
559 flow_removed_verify(parent, request, pkt_count=1, byte_count=len(pkt))
560
Dan Talaycof6e76c02012-03-23 10:56:12 -0700561def get_egr_list(parent, of_ports, how_many, exclude_list=[]):
562 """
563 Generate a list of ports avoiding those in the exclude list
564 @param parent Supplies logger
565 @param of_ports List of OF port numbers
566 @param how_many Number of ports to be added to the list
567 @param exclude_list List of ports not to be used
568 @returns An empty list if unable to find enough ports
569 """
570
Dan Talaycoc948d0b2012-03-23 12:17:54 -0700571 if how_many == 0:
572 return []
573
Dan Talaycof6e76c02012-03-23 10:56:12 -0700574 count = 0
575 egr_ports = []
576 for egr_idx in range(len(of_ports)):
577 if of_ports[egr_idx] not in exclude_list:
578 egr_ports.append(of_ports[egr_idx])
579 count += 1
580 if count >= how_many:
581 return egr_ports
582 parent.logger.debug("Could not generate enough egress ports for test")
583 return []
584
Ed Swierk99a74de2012-08-22 06:40:54 -0700585def flow_match_test(parent, port_map, wildcards=None, dl_vlan=-1, pkt=None,
Dan Talayco551befa2010-07-15 17:05:32 -0700586 exp_pkt=None, action_list=None, check_expire=False,
Dan Talaycoc948d0b2012-03-23 12:17:54 -0700587 max_test=0, egr_count=1, ing_port=False):
Dan Talayco551befa2010-07-15 17:05:32 -0700588 """
589 Run flow_match_test_port_pair on all port pairs
590
591 @param max_test If > 0 no more than this number of tests are executed.
592 @param parent Must implement controller, dataplane, assertTrue, assertEqual
593 and logger
594 @param pkt If not None, use this packet for ingress
595 @param wildcards For flow match entry
Dan Talayco79184222010-11-01 12:24:29 -0700596 @param dl_vlan If not -1, and pkt is None, create a pkt w/ VLAN tag
Dan Talayco551befa2010-07-15 17:05:32 -0700597 @param exp_pkt If not None, use this as the expected output pkt; els use pkt
598 @param action_list Additional actions to add to flow mod
599 @param check_expire Check for flow expiration message
Dan Talaycocfa172f2012-03-23 12:03:00 -0700600 @param egr_count Number of egress ports; -1 means get from config w/ dflt 2
Dan Talayco551befa2010-07-15 17:05:32 -0700601 """
Ed Swierk99a74de2012-08-22 06:40:54 -0700602 if wildcards is None:
603 wildcards = required_wildcards(parent)
Dan Talayco551befa2010-07-15 17:05:32 -0700604 of_ports = port_map.keys()
605 of_ports.sort()
606 parent.assertTrue(len(of_ports) > 1, "Not enough ports for test")
607 test_count = 0
608
Dan Talaycocfa172f2012-03-23 12:03:00 -0700609 if egr_count == -1:
610 egr_count = test_param_get(parent.config, 'egr_count', default=2)
611
Dan Talayco551befa2010-07-15 17:05:32 -0700612 for ing_idx in range(len(of_ports)):
613 ingress_port = of_ports[ing_idx]
Dan Talaycof6e76c02012-03-23 10:56:12 -0700614 egr_ports = get_egr_list(parent, of_ports, egr_count,
615 exclude_list=[ingress_port])
Dan Talaycoc948d0b2012-03-23 12:17:54 -0700616 if ing_port:
617 egr_ports.append(ofp.OFPP_IN_PORT)
Dan Talaycof6e76c02012-03-23 10:56:12 -0700618 if len(egr_ports) == 0:
619 parent.assertTrue(0, "Failed to generate egress port list")
620
621 flow_match_test_port_pair(parent, ingress_port, egr_ports,
622 wildcards=wildcards, dl_vlan=dl_vlan,
623 pkt=pkt, exp_pkt=exp_pkt,
624 action_list=action_list,
625 check_expire=check_expire)
626 test_count += 1
627 if (max_test > 0) and (test_count > max_test):
628 parent.logger.info("Ran " + str(test_count) + " tests; exiting")
629 return
Dan Talayco551befa2010-07-15 17:05:32 -0700630
Dan Talayco4b2bee62010-07-20 14:10:05 -0700631def test_param_get(config, key, default=None):
632 """
633 Return value passed via test-params if present
634
635 @param config The configuration structure for OFTest
636 @param key The lookup key
637 @param default Default value to use if not found
638
639 If the pair 'key=val' appeared in the string passed to --test-params
640 on the command line, return val (as interpreted by exec). Otherwise
641 return default value.
Dan Talaycof6e76c02012-03-23 10:56:12 -0700642
643 WARNING: TEST PARAMETERS MUST BE PYTHON IDENTIFIERS;
644 eg egr_count, not egr-count.
Dan Talayco4b2bee62010-07-20 14:10:05 -0700645 """
646 try:
647 exec config["test_params"]
648 except:
649 return default
650
651 s = "val = " + str(key)
652 try:
653 exec s
654 return val
655 except:
656 return default
657
658def action_generate(parent, field_to_mod, mod_field_vals):
659 """
660 Create an action to modify the field indicated in field_to_mod
661
662 @param parent Must implement, assertTrue
663 @param field_to_mod The field to modify as a string name
664 @param mod_field_vals Hash of values to use for modified values
665 """
666
667 act = None
668
669 if field_to_mod in ['pktlen']:
670 return None
671
672 if field_to_mod == 'dl_dst':
673 act = action.action_set_dl_dst()
674 act.dl_addr = parse.parse_mac(mod_field_vals['dl_dst'])
675 elif field_to_mod == 'dl_src':
676 act = action.action_set_dl_src()
677 act.dl_addr = parse.parse_mac(mod_field_vals['dl_src'])
678 elif field_to_mod == 'dl_vlan_enable':
679 if not mod_field_vals['dl_vlan_enable']: # Strip VLAN tag
680 act = action.action_strip_vlan()
681 # Add VLAN tag is handled by dl_vlan field
682 # Will return None in this case
683 elif field_to_mod == 'dl_vlan':
684 act = action.action_set_vlan_vid()
685 act.vlan_vid = mod_field_vals['dl_vlan']
686 elif field_to_mod == 'dl_vlan_pcp':
687 act = action.action_set_vlan_pcp()
688 act.vlan_pcp = mod_field_vals['dl_vlan_pcp']
689 elif field_to_mod == 'ip_src':
690 act = action.action_set_nw_src()
691 act.nw_addr = parse.parse_ip(mod_field_vals['ip_src'])
692 elif field_to_mod == 'ip_dst':
693 act = action.action_set_nw_dst()
694 act.nw_addr = parse.parse_ip(mod_field_vals['ip_dst'])
695 elif field_to_mod == 'ip_tos':
696 act = action.action_set_nw_tos()
697 act.nw_tos = mod_field_vals['ip_tos']
698 elif field_to_mod == 'tcp_sport':
699 act = action.action_set_tp_src()
700 act.tp_port = mod_field_vals['tcp_sport']
701 elif field_to_mod == 'tcp_dport':
702 act = action.action_set_tp_dst()
703 act.tp_port = mod_field_vals['tcp_dport']
704 else:
705 parent.assertTrue(0, "Unknown field to modify: " + str(field_to_mod))
706
707 return act
708
709def pkt_action_setup(parent, start_field_vals={}, mod_field_vals={},
710 mod_fields={}, check_test_params=False):
711 """
712 Set up the ingress and expected packet and action list for a test
713
714 @param parent Must implement, assertTrue, config hash and logger
715 @param start_field_values Field values to use for ingress packet (optional)
716 @param mod_field_values Field values to use for modified packet (optional)
717 @param mod_fields The list of fields to be modified by the switch in the test.
718 @params check_test_params If True, will check the parameters vid, add_vlan
719 and strip_vlan from the command line.
720
721 Returns a triple: pkt-to-send, expected-pkt, action-list
722 """
723
724 new_actions = []
725
Dan Talayco4b2bee62010-07-20 14:10:05 -0700726 base_pkt_params = {}
727 base_pkt_params['pktlen'] = 100
728 base_pkt_params['dl_dst'] = '00:DE:F0:12:34:56'
729 base_pkt_params['dl_src'] = '00:23:45:67:89:AB'
730 base_pkt_params['dl_vlan_enable'] = False
731 base_pkt_params['dl_vlan'] = 2
732 base_pkt_params['dl_vlan_pcp'] = 0
733 base_pkt_params['ip_src'] = '192.168.0.1'
734 base_pkt_params['ip_dst'] = '192.168.0.2'
735 base_pkt_params['ip_tos'] = 0
736 base_pkt_params['tcp_sport'] = 1234
737 base_pkt_params['tcp_dport'] = 80
738 for keyname in start_field_vals.keys():
739 base_pkt_params[keyname] = start_field_vals[keyname]
740
741 mod_pkt_params = {}
742 mod_pkt_params['pktlen'] = 100
743 mod_pkt_params['dl_dst'] = '00:21:0F:ED:CB:A9'
744 mod_pkt_params['dl_src'] = '00:ED:CB:A9:87:65'
745 mod_pkt_params['dl_vlan_enable'] = False
746 mod_pkt_params['dl_vlan'] = 3
747 mod_pkt_params['dl_vlan_pcp'] = 7
748 mod_pkt_params['ip_src'] = '10.20.30.40'
749 mod_pkt_params['ip_dst'] = '50.60.70.80'
750 mod_pkt_params['ip_tos'] = 0xf0
751 mod_pkt_params['tcp_sport'] = 4321
752 mod_pkt_params['tcp_dport'] = 8765
753 for keyname in mod_field_vals.keys():
754 mod_pkt_params[keyname] = mod_field_vals[keyname]
755
756 # Check for test param modifications
757 strip = False
758 if check_test_params:
759 add_vlan = test_param_get(parent.config, 'add_vlan')
760 strip_vlan = test_param_get(parent.config, 'strip_vlan')
761 vid = test_param_get(parent.config, 'vid')
762
763 if add_vlan and strip_vlan:
764 parent.assertTrue(0, "Add and strip VLAN both specified")
765
766 if vid:
767 base_pkt_params['dl_vlan_enable'] = True
768 base_pkt_params['dl_vlan'] = vid
769 if 'dl_vlan' in mod_fields:
770 mod_pkt_params['dl_vlan'] = vid + 1
771
772 if add_vlan:
773 base_pkt_params['dl_vlan_enable'] = False
774 mod_pkt_params['dl_vlan_enable'] = True
775 mod_pkt_params['pktlen'] = base_pkt_params['pktlen'] + 4
776 mod_fields.append('pktlen')
777 mod_fields.append('dl_vlan_enable')
778 if 'dl_vlan' not in mod_fields:
779 mod_fields.append('dl_vlan')
780 elif strip_vlan:
781 base_pkt_params['dl_vlan_enable'] = True
782 mod_pkt_params['dl_vlan_enable'] = False
783 mod_pkt_params['pktlen'] = base_pkt_params['pktlen'] - 4
784 mod_fields.append('dl_vlan_enable')
785 mod_fields.append('pktlen')
786
787 # Build the ingress packet
788 ingress_pkt = simple_tcp_packet(**base_pkt_params)
789
790 # Build the expected packet, modifying the indicated fields
791 for item in mod_fields:
792 base_pkt_params[item] = mod_pkt_params[item]
793 act = action_generate(parent, item, mod_pkt_params)
794 if act:
795 new_actions.append(act)
796
797 expected_pkt = simple_tcp_packet(**base_pkt_params)
798
799 return (ingress_pkt, expected_pkt, new_actions)
Dan Talayco677c0b72011-08-23 22:53:38 -0700800
801# Generate a simple "drop" flow mod
802# If in_band is true, then only drop from first test port
803def flow_mod_gen(port_map, in_band):
804 request = message.flow_mod()
805 request.match.wildcards = ofp.OFPFW_ALL
806 if in_band:
807 request.match.wildcards = ofp.OFPFW_ALL - ofp.OFPFW_IN_PORT
808 for of_port, ifname in port_map.items(): # Grab first port
809 break
810 request.match.in_port = of_port
811 request.buffer_id = 0xffffffff
812 return request
Dan Talaycoba3745c2010-07-21 21:51:08 -0700813
814def skip_message_emit(parent, s):
815 """
816 Print out a 'skipped' message to stderr
817
818 @param s The string to print out to the log file
819 @param parent Must implement config and logger objects
820 """
821 global skipped_test_count
822
823 skipped_test_count += 1
824 parent.logger.info("Skipping: " + s)
825 if parent.config["dbg_level"] < logging.WARNING:
826 sys.stderr.write("(skipped) ")
827 else:
828 sys.stderr.write("(S)")
Dan Talayco677c0b72011-08-23 22:53:38 -0700829
Dan Talayco8a64e332012-03-28 14:53:20 -0700830
831def all_stats_get(parent):
832 """
833 Get the aggregate stats for all flows in the table
834 @param parent Test instance with controller connection and assert
835 @returns dict with keys flows, packets, bytes, active (flows),
836 lookups, matched
837 """
838 stat_req = message.aggregate_stats_request()
839 stat_req.match = ofp.ofp_match()
840 stat_req.match.wildcards = ofp.OFPFW_ALL
841 stat_req.table_id = 0xff
842 stat_req.out_port = ofp.OFPP_NONE
843
844 rv = {}
845
Rich Lanec8aaa3e2012-07-26 19:28:02 -0700846 (reply, pkt) = parent.controller.transact(stat_req)
Dan Talayco8a64e332012-03-28 14:53:20 -0700847 parent.assertTrue(len(reply.stats) == 1, "Did not receive flow stats reply")
848
849 for obj in reply.stats:
850 (rv["flows"], rv["packets"], rv["bytes"]) = (obj.flow_count,
851 obj.packet_count, obj.byte_count)
852 break
853
854 request = message.table_stats_request()
Rich Lanec8aaa3e2012-07-26 19:28:02 -0700855 (reply , pkt) = parent.controller.transact(request)
Dan Talayco8a64e332012-03-28 14:53:20 -0700856
857
858 (rv["active"], rv["lookups"], rv["matched"]) = (0,0,0)
859 for obj in reply.stats:
860 rv["active"] += obj.active_count
861 rv["lookups"] += obj.lookup_count
862 rv["matched"] += obj.matched_count
863
864 return rv
Dan Talayco2baf8b52012-03-30 09:55:42 -0700865
866FILTER=''.join([(len(repr(chr(x)))==3) and chr(x) or '.'
867 for x in range(256)])
868
869def hex_dump_buffer(src, length=16):
870 """
871 Convert src to a hex dump string and return the string
872 @param src The source buffer
873 @param length The number of bytes shown in each line
874 @returns A string showing the hex dump
875 """
Dan Talaycoc516fa02012-04-12 22:28:43 -0700876 result = ["\n"]
Dan Talayco2baf8b52012-03-30 09:55:42 -0700877 for i in xrange(0, len(src), length):
878 chars = src[i:i+length]
879 hex = ' '.join(["%02x" % ord(x) for x in chars])
880 printable = ''.join(["%s" % ((ord(x) <= 127 and
881 FILTER[ord(x)]) or '.') for x in chars])
882 result.append("%04x %-*s %s\n" % (i, length*3, hex, printable))
883 return ''.join(result)
884
885def format_packet(pkt):
886 return "Packet length %d \n%s" % (len(str(pkt)),
887 hex_dump_buffer(str(pkt)))