blob: aa434a78c36f7e6231e75a0deb6671534cd35423 [file] [log] [blame]
Dan Talayco34089522010-02-07 23:07:41 -08001"""
2OpenFlow Test Framework
3
Dan Talayco3087a462010-02-13 14:01:47 -08004DataPlane and DataPlanePort classes
Dan Talayco34089522010-02-07 23:07:41 -08005
6Provide the interface to the control the set of ports being used
7to stimulate the switch under test.
8
9See the class dataplaneport for more details. This class wraps
Dan Talaycoe226eb12010-02-18 23:06:30 -080010a set of those objects allowing general calls and parsing
Dan Talayco34089522010-02-07 23:07:41 -080011configuration.
12
Dan Talayco3087a462010-02-13 14:01:47 -080013@todo Add "filters" for matching packets. Actions supported
14for filters should include a callback or a counter
Dan Talayco34089522010-02-07 23:07:41 -080015"""
16
Dan Talayco3087a462010-02-13 14:01:47 -080017import sys
18import os
19import socket
20import time
Dan Talaycod7e2dbe2010-02-13 21:51:15 -080021import netutils
Dan Talayco3087a462010-02-13 14:01:47 -080022from threading import Thread
23from threading import Lock
Dan Talaycoe226eb12010-02-18 23:06:30 -080024from threading import Condition
Dan Talayco710438c2010-02-18 15:16:07 -080025import select
Dan Talayco48370102010-03-03 15:17:33 -080026import logging
27from oft_assert import oft_assert
Rich Lanedb9d8662012-07-26 18:04:24 -070028from ofutils import *
Dan Talayco3087a462010-02-13 14:01:47 -080029
Dan Talayco48370102010-03-03 15:17:33 -080030##@todo Find a better home for these identifiers (dataplane)
31RCV_SIZE_DEFAULT = 4096
Dan Talayco3087a462010-02-13 14:01:47 -080032ETH_P_ALL = 0x03
33RCV_TIMEOUT = 10000
Dan Talayco3087a462010-02-13 14:01:47 -080034
Ed Swierk506614a2012-03-29 08:16:59 -070035def match_exp_pkt(exp_pkt, pkt):
36 """
37 Compare the string value of pkt with the string value of exp_pkt,
38 and return True iff they are identical. If the length of exp_pkt is
39 less than the minimum Ethernet frame size (60 bytes), then padding
40 bytes in pkt are ignored.
41 """
42 e = str(exp_pkt)
43 p = str(pkt)
44 if len(e) < 60:
45 p = p[:len(e)]
46 return e == p
47
Jeffrey Townsend0e8b0922012-07-11 11:37:46 -070048
Dan Talayco3087a462010-02-13 14:01:47 -080049class DataPlanePort(Thread):
50 """
51 Class defining a port monitoring object.
52
53 Control a dataplane port connected to the switch under test.
54 Creates a promiscuous socket on a physical interface.
55 Queues the packets received on that interface with time stamps.
56 Inherits from Thread class as meant to run in background. Also
57 supports polling.
Dan Talaycoe226eb12010-02-18 23:06:30 -080058
59 Currently assumes a controlling 'parent' which maintains a
60 common Lock object and a total packet-pending count. May want
61 to decouple that some day.
Dan Talayco3087a462010-02-13 14:01:47 -080062 """
63
Dan Talaycoe226eb12010-02-18 23:06:30 -080064 def __init__(self, interface_name, port_number, parent, max_pkts=1024):
Dan Talayco3087a462010-02-13 14:01:47 -080065 """
66 Set up a port monitor object
67 @param interface_name The name of the physical interface like eth1
Dan Talayco4d065972010-02-18 23:11:32 -080068 @param port_number The port number associated with this port
Dan Talaycoe226eb12010-02-18 23:06:30 -080069 @param parent The controlling dataplane object; for pkt wait CV
Dan Talayco3087a462010-02-13 14:01:47 -080070 @param max_pkts Maximum number of pkts to keep in queue
71 """
72 Thread.__init__(self)
73 self.interface_name = interface_name
74 self.max_pkts = max_pkts
Dan Talayco3087a462010-02-13 14:01:47 -080075 self.packets_total = 0
76 self.packets = []
Dan Talayco1b3f6902010-02-15 14:14:19 -080077 self.packets_discarded = 0
Dan Talaycoe226eb12010-02-18 23:06:30 -080078 self.port_number = port_number
Dan Talayco48370102010-03-03 15:17:33 -080079 logname = "dp-" + interface_name
80 self.logger = logging.getLogger(logname)
Dan Talayco0db53eb2010-03-10 14:00:02 -080081 try:
82 self.socket = self.interface_open(interface_name)
83 except:
84 self.logger.info("Could not open socket")
85 sys.exit(1)
Dan Talayco48370102010-03-03 15:17:33 -080086 self.logger.info("Openned port monitor socket")
Dan Talaycoe226eb12010-02-18 23:06:30 -080087 self.parent = parent
Dan Talayco1b3f6902010-02-15 14:14:19 -080088
Dan Talayco3087a462010-02-13 14:01:47 -080089 def interface_open(self, interface_name):
90 """
91 Open a socket in a promiscuous mode for a data connection.
92 @param interface_name port name as a string such as 'eth1'
93 @retval s socket
94 """
Dan Talaycoe226eb12010-02-18 23:06:30 -080095 s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW,
Dan Talayco3087a462010-02-13 14:01:47 -080096 socket.htons(ETH_P_ALL))
97 s.bind((interface_name, 0))
Dan Talayco1b3f6902010-02-15 14:14:19 -080098 netutils.set_promisc(s, interface_name)
Dan Talayco3087a462010-02-13 14:01:47 -080099 s.settimeout(RCV_TIMEOUT)
100 return s
101
Dan Talayco3087a462010-02-13 14:01:47 -0800102 def run(self):
103 """
104 Activity function for class
105 """
106 self.running = True
Dan Talayco710438c2010-02-18 15:16:07 -0800107 self.socs = [self.socket]
108 error_warned = False # Have we warned about error?
Dan Talayco3087a462010-02-13 14:01:47 -0800109 while self.running:
110 try:
Dan Talayco710438c2010-02-18 15:16:07 -0800111 sel_in, sel_out, sel_err = \
112 select.select(self.socs, [], [], 1)
113 except:
114 print sys.exc_info()
Dan Talayco48370102010-03-03 15:17:33 -0800115 self.logger.error("Select error, exiting")
116 break
Dan Talayco710438c2010-02-18 15:16:07 -0800117
118 if not self.running:
119 break
120
Dan Talayco48370102010-03-03 15:17:33 -0800121 if (sel_in is None) or (len(sel_in) == 0):
Dan Talayco710438c2010-02-18 15:16:07 -0800122 continue
123
124 try:
Dan Talayco48370102010-03-03 15:17:33 -0800125 rcvmsg = self.socket.recv(RCV_SIZE_DEFAULT)
Dan Talayco3087a462010-02-13 14:01:47 -0800126 except socket.error:
Dan Talayco710438c2010-02-18 15:16:07 -0800127 if not error_warned:
Dan Talayco48370102010-03-03 15:17:33 -0800128 self.logger.info("Socket error on recv")
Dan Talayco710438c2010-02-18 15:16:07 -0800129 error_warned = True
Dan Talayco1b3f6902010-02-15 14:14:19 -0800130 continue
Dan Talayco710438c2010-02-18 15:16:07 -0800131
Dan Talayco1b3f6902010-02-15 14:14:19 -0800132 if len(rcvmsg) == 0:
Dan Talayco48370102010-03-03 15:17:33 -0800133 self.logger.info("Zero len pkt rcvd")
Dan Talayco1b3f6902010-02-15 14:14:19 -0800134 self.kill()
Dan Talayco3087a462010-02-13 14:01:47 -0800135 break
136
Dan Talayco1b3f6902010-02-15 14:14:19 -0800137 rcvtime = time.clock()
Dan Talayco48370102010-03-03 15:17:33 -0800138 self.logger.debug("Pkt len " + str(len(rcvmsg)) +
Ed Swierk4e200302012-03-19 14:53:31 -0700139 " in at " + str(rcvtime) + " on port " +
140 str(self.port_number))
Dan Talayco1b3f6902010-02-15 14:14:19 -0800141
Dan Talaycoe226eb12010-02-18 23:06:30 -0800142 # Enqueue packet
Rich Lanedb9d8662012-07-26 18:04:24 -0700143 with self.parent.pkt_sync:
144 if len(self.packets) >= self.max_pkts:
145 # Queue full, throw away oldest
146 self.packets.pop(0)
147 self.packets_discarded += 1
148 self.logger.debug("Discarding oldest packet to make room")
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700149 self.packets.append((rcvmsg, rcvtime))
150 self.packets_total += 1
Rich Lanedb9d8662012-07-26 18:04:24 -0700151 self.parent.pkt_sync.notify_all()
Dan Talayco1b3f6902010-02-15 14:14:19 -0800152
Rich Lanedb9d8662012-07-26 18:04:24 -0700153 self.logger.info("Thread exit")
Dan Talayco1b3f6902010-02-15 14:14:19 -0800154
155 def kill(self):
156 """
157 Terminate the running thread
158 """
Dan Talayco48370102010-03-03 15:17:33 -0800159 self.logger.debug("Port monitor kill")
Dan Talayco1b3f6902010-02-15 14:14:19 -0800160 self.running = False
161 try:
162 self.socket.close()
163 except:
Dan Talayco48370102010-03-03 15:17:33 -0800164 self.logger.info("Ignoring dataplane soc shutdown error")
Dan Talayco1b3f6902010-02-15 14:14:19 -0800165
Dan Talayco3087a462010-02-13 14:01:47 -0800166 def timestamp_head(self):
167 """
168 Return the timestamp of the head of queue or None if empty
169 """
Dan Talayco710438c2010-02-18 15:16:07 -0800170 rv = None
Dan Talaycoe226eb12010-02-18 23:06:30 -0800171 try:
Dan Talayco710438c2010-02-18 15:16:07 -0800172 rv = self.packets[0][1]
Dan Talaycoe226eb12010-02-18 23:06:30 -0800173 except:
174 rv = None
Dan Talayco710438c2010-02-18 15:16:07 -0800175 return rv
Dan Talayco3087a462010-02-13 14:01:47 -0800176
177 def flush(self):
178 """
179 Clear the packet queue
180 """
Rich Lanedb9d8662012-07-26 18:04:24 -0700181 with self.parent.pkt_sync:
182 self.packets_discarded += len(self.packets)
183 self.packets = []
Dan Talayco3087a462010-02-13 14:01:47 -0800184
185 def send(self, packet):
186 """
187 Send a packet to the dataplane port
188 @param packet The packet data to send to the port
189 @retval The number of bytes sent
190 """
191 return self.socket.send(packet)
192
193
194 def register(self, handler):
195 """
196 Register a callback function to receive packets from this
Dan Talaycoe226eb12010-02-18 23:06:30 -0800197 port. The callback will be passed the packet, the
198 interface name and the port number (if set) on which the
Dan Talayco3087a462010-02-13 14:01:47 -0800199 packet was received.
200
201 To be implemented
202 """
203 pass
204
Dan Talayco1b3f6902010-02-15 14:14:19 -0800205 def show(self, prefix=''):
206 print prefix + "Name: " + self.interface_name
Dan Talayco710438c2010-02-18 15:16:07 -0800207 print prefix + "Pkts pending: " + str(len(self.packets))
Dan Talayco1b3f6902010-02-15 14:14:19 -0800208 print prefix + "Pkts total: " + str(self.packets_total)
209 print prefix + "socket: " + str(self.socket)
Dan Talaycoe226eb12010-02-18 23:06:30 -0800210
Dan Talayco34089522010-02-07 23:07:41 -0800211
212class DataPlane:
213 """
214 Class defining access primitives to the data plane
215 Controls a list of DataPlanePort objects
216 """
Jeffrey Townsend0e8b0922012-07-11 11:37:46 -0700217 def __init__(self, config=None):
Dan Talayco34089522010-02-07 23:07:41 -0800218 self.port_list = {}
Dan Talaycoe226eb12010-02-18 23:06:30 -0800219 # pkt_sync serves double duty as a regular top level lock and
220 # as a condition variable
221 self.pkt_sync = Condition()
222
223 # These are used to signal async pkt arrival for polling
224 self.want_pkt = False
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700225 self.exp_pkt = None
Dan Talaycoe226eb12010-02-18 23:06:30 -0800226 self.want_pkt_port = None # What port required (or None)
227 self.got_pkt_port = None # On what port received?
228 self.packets_pending = 0 # Total pkts in all port queues
Dan Talayco48370102010-03-03 15:17:33 -0800229 self.logger = logging.getLogger("dataplane")
Dan Talayco34089522010-02-07 23:07:41 -0800230
Jeffrey Townsend0e8b0922012-07-11 11:37:46 -0700231 if config is None:
232 self.config = {}
233 else:
234 self.config = config;
235
236 ############################################################
237 #
238 # We use the DataPlanePort class defined here by
239 # default for all port traffic:
240 #
241 self.dppclass = DataPlanePort
242
243 ############################################################
244 #
245 # The platform/config can provide a custom DataPlanePort class
246 # here if you have a custom implementation with different
247 # behavior.
248 #
249 # Set config.dataplane.portclass = MyDataPlanePortClass
250 # where MyDataPlanePortClass has the same interface as the class
251 # DataPlanePort defined here.
252 #
253 if "dataplane" in self.config:
254 if "portclass" in self.config["dataplane"]:
255 self.dppclass = self.config["dataplane"]["portclass"]
256
257 if self.dppclass == None:
258 raise Exception("Problem determining DataPlanePort class.")
259
260
Dan Talayco34089522010-02-07 23:07:41 -0800261 def port_add(self, interface_name, port_number):
262 """
263 Add a port to the dataplane
264 TBD: Max packets for queue?
265 @param interface_name The name of the physical interface like eth1
266 @param port_number The port number used to refer to the port
267 """
Dan Talaycoe226eb12010-02-18 23:06:30 -0800268
Jeffrey Townsend0e8b0922012-07-11 11:37:46 -0700269 self.port_list[port_number] = self.dppclass(interface_name,
270 port_number, self);
271
Dan Talayco34089522010-02-07 23:07:41 -0800272 self.port_list[port_number].start()
273
Jeffrey Townsend0e8b0922012-07-11 11:37:46 -0700274
275
Dan Talayco34089522010-02-07 23:07:41 -0800276 def send(self, port_number, packet):
277 """
278 Send a packet to the given port
279 @param port_number The port to send the data to
280 @param packet Raw packet data to send to port
281 """
Dan Talayco11c26e72010-03-07 22:03:57 -0800282 self.logger.debug("Sending %d bytes to port %d" %
283 (len(packet), port_number))
Dan Talayco34089522010-02-07 23:07:41 -0800284 bytes = self.port_list[port_number].send(packet)
285 if bytes != len(packet):
Dan Talayco48370102010-03-03 15:17:33 -0800286 self.logger.error("Unhandled send error, length mismatch %d != %d" %
Dan Talayco1b3f6902010-02-15 14:14:19 -0800287 (bytes, len(packet)))
Dan Talayco34089522010-02-07 23:07:41 -0800288 return bytes
289
290 def flood(self, packet):
291 """
292 Send a packet to all ports
293 @param packet Raw packet data to send to port
294 """
295 for port_number in self.port_list.keys():
296 bytes = self.port_list[port_number].send(packet)
297 if bytes != len(packet):
Dan Talayco48370102010-03-03 15:17:33 -0800298 self.logger.error("Unhandled send error" +
Dan Talayco1b3f6902010-02-15 14:14:19 -0800299 ", port %d, length mismatch %d != %d" %
300 (port_number, bytes, len(packet)))
Dan Talayco34089522010-02-07 23:07:41 -0800301
Dan Talaycoe226eb12010-02-18 23:06:30 -0800302 def _oldest_packet_find(self):
Dan Talayco34089522010-02-07 23:07:41 -0800303 # Find port with oldest packet
Dan Talaycoe226eb12010-02-18 23:06:30 -0800304 oft_assert(min_port != -1, "Could not find port when pkts pending")
Dan Talayco34089522010-02-07 23:07:41 -0800305
Dan Talaycoe226eb12010-02-18 23:06:30 -0800306 return min_port
307
Rich Lanedb9d8662012-07-26 18:04:24 -0700308 # Returns the port with the oldest packet, or None if no packets are queued.
309 def oldest_port(self):
310 min_port = None
311 min_time = float('inf')
312 for port in self.port_list.values():
313 ptime = port.timestamp_head()
314 if ptime and ptime < min_time:
315 min_time = ptime
316 min_port = port
317 return min_port
318
319 # Dequeues and yields packets in the order they were received.
320 # Yields (port, packet, received time).
321 # If port_number is not specified yields packets from all ports.
322 def packets(self, port_number=None):
323 while True:
324 if port_number == None:
325 port = self.oldest_port()
326 else:
327 port = self.port_list[port_number]
328
329 if port == None or len(port.packets) == 0:
330 self.logger.debug("Out of packets for port %s" % str(port_number))
331 # Out of packets
332 break
333
334 pkt, time = port.packets.pop(0)
335 yield (port, pkt, time)
336
Rich Lane8806bc42012-07-26 19:18:37 -0700337 def poll(self, port_number=None, timeout=-1, exp_pkt=None):
Dan Talaycoe226eb12010-02-18 23:06:30 -0800338 """
339 Poll one or all dataplane ports for a packet
340
341 If port_number is given, get the oldest packet from that port.
342 Otherwise, find the port with the oldest packet and return
343 that packet.
Dan Talayco1729fdb2012-05-03 09:35:56 -0700344
345 If exp_pkt is true, discard all packets until that one is found
346
Dan Talaycoe226eb12010-02-18 23:06:30 -0800347 @param port_number If set, get packet from this port
Dan Talayco11c26e72010-03-07 22:03:57 -0800348 @param timeout If positive and no packet is available, block
Dan Talaycoe226eb12010-02-18 23:06:30 -0800349 until a packet is received or for this many seconds
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700350 @param exp_pkt If not None, look for this packet and ignore any
Dan Talayco1729fdb2012-05-03 09:35:56 -0700351 others received. Note that if port_number is None, all packets
352 from all ports will be discarded until the exp_pkt is found
Dan Talaycoe226eb12010-02-18 23:06:30 -0800353 @return The triple port_number, packet, pkt_time where packet
354 is received from port_number at time pkt_time. If a timeout
355 occurs, return None, None, None
356 """
357
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700358 if exp_pkt and not port_number:
Dan Talayco1729fdb2012-05-03 09:35:56 -0700359 self.logger.warn("Dataplane poll with exp_pkt but no port number")
360
Rich Lanedb9d8662012-07-26 18:04:24 -0700361 # Retrieve the packet. Returns (port number, packet, time).
362 def grab():
363 self.logger.debug("Grabbing packet")
364 for (port, pkt, time) in self.packets(port_number):
365 self.logger.debug("Checking packet from port %d" % port.port_number)
Dan Talayco1729fdb2012-05-03 09:35:56 -0700366 if not exp_pkt or match_exp_pkt(exp_pkt, pkt):
Rich Lanedb9d8662012-07-26 18:04:24 -0700367 return (port, pkt, time)
368 self.logger.debug("Did not find packet")
369 return None
Dan Talaycoe226eb12010-02-18 23:06:30 -0800370
Rich Lanedb9d8662012-07-26 18:04:24 -0700371 with self.pkt_sync:
372 ret = timed_wait(self.pkt_sync, grab, timeout=timeout)
Dan Talayco34089522010-02-07 23:07:41 -0800373
Rich Lanedb9d8662012-07-26 18:04:24 -0700374 if ret != None:
375 (port, pkt, time) = ret
376 return (port.port_number, pkt, time)
377 else:
378 self.logger.debug("Poll time out, no packet from " + str(port_number))
379 return (None, None, None)
Dan Talayco34089522010-02-07 23:07:41 -0800380
Dan Talayco48370102010-03-03 15:17:33 -0800381 def kill(self, join_threads=True):
Dan Talayco1b3f6902010-02-15 14:14:19 -0800382 """
383 Close all sockets for dataplane
Dan Talayco710438c2010-02-18 15:16:07 -0800384 @param join_threads If True call join on each thread
Dan Talayco1b3f6902010-02-15 14:14:19 -0800385 """
Dan Talayco34089522010-02-07 23:07:41 -0800386 for port_number in self.port_list.keys():
387 self.port_list[port_number].kill()
Dan Talayco1b3f6902010-02-15 14:14:19 -0800388 if join_threads:
Dan Talayco48370102010-03-03 15:17:33 -0800389 self.logger.debug("Joining " + str(port_number))
Dan Talayco1b3f6902010-02-15 14:14:19 -0800390 self.port_list[port_number].join()
Dan Talayco34089522010-02-07 23:07:41 -0800391
Dan Talayco48370102010-03-03 15:17:33 -0800392 self.logger.info("DataPlane shutdown")
Dan Talayco1b3f6902010-02-15 14:14:19 -0800393
394 def show(self, prefix=''):
395 print prefix + "Dataplane Controller"
Dan Talaycoe226eb12010-02-18 23:06:30 -0800396 print prefix + "Packets pending" + str(self.packets_pending)
Dan Talayco1b3f6902010-02-15 14:14:19 -0800397 for pnum, port in self.port_list.items():
398 print prefix + "OpenFlow Port Number " + str(pnum)
399 port.show(prefix + ' ')
400