blob: 8cda9323dfa039e349e1939fa95100cd4b5c5ae0 [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
Rich Lanecd97d3d2013-01-07 18:50:06 -080021import select
22import logging
Dan Talayco3087a462010-02-13 14:01:47 -080023from threading import Thread
24from threading import Lock
Dan Talaycoe226eb12010-02-18 23:06:30 -080025from threading import Condition
Rich Lanecd97d3d2013-01-07 18:50:06 -080026import ofutils
27import netutils
Rich Lane472aaea2013-08-27 09:27:38 -070028from pcap_writer import PcapWriter
Dan Talayco3087a462010-02-13 14:01:47 -080029
Rich Laneb42a31c2012-10-05 17:54:17 -070030have_pypcap = False
31try:
32 import pcap
Ed Swierkab0bab32012-11-30 13:31:00 -080033 if hasattr(pcap, "pcap"):
34 # the incompatible pylibpcap library masquerades as pcap
35 have_pypcap = True
Rich Laneb42a31c2012-10-05 17:54:17 -070036except:
37 pass
38
Ed Swierk506614a2012-03-29 08:16:59 -070039def match_exp_pkt(exp_pkt, pkt):
40 """
41 Compare the string value of pkt with the string value of exp_pkt,
42 and return True iff they are identical. If the length of exp_pkt is
43 less than the minimum Ethernet frame size (60 bytes), then padding
44 bytes in pkt are ignored.
45 """
46 e = str(exp_pkt)
47 p = str(pkt)
48 if len(e) < 60:
49 p = p[:len(e)]
50 return e == p
51
Jeffrey Townsend0e8b0922012-07-11 11:37:46 -070052
Rich Lanee1b8da92012-12-26 22:47:13 -080053class DataPlanePort:
Dan Talayco3087a462010-02-13 14:01:47 -080054 """
Rich Lanee1b8da92012-12-26 22:47:13 -080055 Uses raw sockets to capture and send packets on a network interface.
Dan Talayco3087a462010-02-13 14:01:47 -080056 """
57
Rich Lanee1b8da92012-12-26 22:47:13 -080058 RCV_SIZE_DEFAULT = 4096
59 ETH_P_ALL = 0x03
60 RCV_TIMEOUT = 10000
61
62 def __init__(self, interface_name, port_number):
Dan Talayco3087a462010-02-13 14:01:47 -080063 """
Dan Talayco3087a462010-02-13 14:01:47 -080064 @param interface_name The name of the physical interface like eth1
Dan Talayco3087a462010-02-13 14:01:47 -080065 """
Dan Talayco3087a462010-02-13 14:01:47 -080066 self.interface_name = interface_name
Rich Lanee1b8da92012-12-26 22:47:13 -080067 self.socket = socket.socket(socket.AF_PACKET, socket.SOCK_RAW,
68 socket.htons(self.ETH_P_ALL))
69 self.socket.bind((interface_name, 0))
70 netutils.set_promisc(self.socket, interface_name)
71 self.socket.settimeout(self.RCV_TIMEOUT)
Dan Talayco1b3f6902010-02-15 14:14:19 -080072
Rich Lanee1b8da92012-12-26 22:47:13 -080073 def __del__(self):
74 if self.socket:
Dan Talayco1b3f6902010-02-15 14:14:19 -080075 self.socket.close()
Dan Talayco1b3f6902010-02-15 14:14:19 -080076
Rich Lanee1b8da92012-12-26 22:47:13 -080077 def fileno(self):
Dan Talayco3087a462010-02-13 14:01:47 -080078 """
Rich Lanee1b8da92012-12-26 22:47:13 -080079 Return an integer file descriptor that can be passed to select(2).
Dan Talayco3087a462010-02-13 14:01:47 -080080 """
Rich Lanee1b8da92012-12-26 22:47:13 -080081 return self.socket.fileno()
Dan Talayco3087a462010-02-13 14:01:47 -080082
Rich Lanee1b8da92012-12-26 22:47:13 -080083 def recv(self):
Dan Talayco3087a462010-02-13 14:01:47 -080084 """
Rich Lanee1b8da92012-12-26 22:47:13 -080085 Receive a packet from this port.
86 @retval (packet data, timestamp)
Dan Talayco3087a462010-02-13 14:01:47 -080087 """
Rich Lanee1b8da92012-12-26 22:47:13 -080088 pkt = self.socket.recv(self.RCV_SIZE_DEFAULT)
89 return (pkt, time.time())
Dan Talayco3087a462010-02-13 14:01:47 -080090
91 def send(self, packet):
92 """
Rich Lanee1b8da92012-12-26 22:47:13 -080093 Send a packet out this port.
Dan Talayco3087a462010-02-13 14:01:47 -080094 @param packet The packet data to send to the port
95 @retval The number of bytes sent
96 """
97 return self.socket.send(packet)
98
Rich Lanee1b8da92012-12-26 22:47:13 -080099 def down(self):
Dan Talayco3087a462010-02-13 14:01:47 -0800100 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800101 Bring the physical link down.
Dan Talayco3087a462010-02-13 14:01:47 -0800102 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800103 os.system("ifconfig down %s" % self.interface_name)
Dan Talayco3087a462010-02-13 14:01:47 -0800104
Rich Lanee1b8da92012-12-26 22:47:13 -0800105 def up(self):
ShreyaPanditacd8e1cf2012-11-28 11:44:42 -0500106 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800107 Bring the physical link up.
ShreyaPanditacd8e1cf2012-11-28 11:44:42 -0500108 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800109 os.system("ifconfig up %s" % self.interface_name)
ShreyaPanditacd8e1cf2012-11-28 11:44:42 -0500110
111
Rich Lanee1b8da92012-12-26 22:47:13 -0800112class DataPlanePortPcap:
Rich Laneb42a31c2012-10-05 17:54:17 -0700113 """
114 Alternate port implementation using libpcap. This is required for recent
115 versions of Linux (such as Linux 3.2 included in Ubuntu 12.04) which
116 offload the VLAN tag, so it isn't in the data returned from a read on a raw
117 socket. libpcap understands how to read the VLAN tag from the kernel.
118 """
119
Rich Lanee1b8da92012-12-26 22:47:13 -0800120 def __init__(self, interface_name, port_number):
Rich Laneb42a31c2012-10-05 17:54:17 -0700121 self.pcap = pcap.pcap(interface_name)
122 self.pcap.setnonblock()
Rich Lanee1b8da92012-12-26 22:47:13 -0800123
124 def fileno(self):
Rich Laneb42a31c2012-10-05 17:54:17 -0700125 return self.pcap.fileno()
126
Rich Lanee1b8da92012-12-26 22:47:13 -0800127 def recv(self):
128 (timestamp, pkt) = next(self.pcap)
Rich Lane0415fd72014-02-25 22:04:17 -0800129 return (pkt[:], timestamp)
Rich Laneb42a31c2012-10-05 17:54:17 -0700130
131 def send(self, packet):
Rich Lane0306fd32015-03-11 15:52:21 -0700132 if hasattr(self.pcap, "inject"):
133 return self.pcap.inject(packet, len(packet))
134 else:
135 return self.pcap.sendpacket(packet)
Dan Talayco34089522010-02-07 23:07:41 -0800136
Rich Lanee1b8da92012-12-26 22:47:13 -0800137 def down(self):
138 pass
139
140 def up(self):
141 pass
142
143class DataPlane(Thread):
Dan Talayco34089522010-02-07 23:07:41 -0800144 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800145 This class provides methods to send and receive packets on the dataplane.
146 It uses the DataPlanePort class, or an alternative implementation of that
147 interface, to do IO on a particular port. A background thread is used to
148 read packets from the dataplane ports and enqueue them to be read by the
149 test. The kill() method must be called to shutdown this thread.
Dan Talayco34089522010-02-07 23:07:41 -0800150 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800151
152 MAX_QUEUE_LEN = 100
153
Jeffrey Townsend0e8b0922012-07-11 11:37:46 -0700154 def __init__(self, config=None):
Rich Lanee1b8da92012-12-26 22:47:13 -0800155 Thread.__init__(self)
156
157 # dict from port number to port object
158 self.ports = {}
159
160 # dict from port number to list of (timestamp, packet)
161 self.packet_queues = {}
162
163 # cvar serves double duty as a regular top level lock and
Dan Talaycoe226eb12010-02-18 23:06:30 -0800164 # as a condition variable
Rich Lanee1b8da92012-12-26 22:47:13 -0800165 self.cvar = Condition()
166
167 # Used to wake up the event loop from another thread
Rich Lanecd97d3d2013-01-07 18:50:06 -0800168 self.waker = ofutils.EventDescriptor()
Rich Lanee1b8da92012-12-26 22:47:13 -0800169 self.killed = False
Dan Talaycoe226eb12010-02-18 23:06:30 -0800170
Dan Talayco48370102010-03-03 15:17:33 -0800171 self.logger = logging.getLogger("dataplane")
Rich Lane472aaea2013-08-27 09:27:38 -0700172 self.pcap_writer = None
Dan Talayco34089522010-02-07 23:07:41 -0800173
Jeffrey Townsend0e8b0922012-07-11 11:37:46 -0700174 if config is None:
175 self.config = {}
176 else:
177 self.config = config;
178
179 ############################################################
180 #
Jeffrey Townsend0e8b0922012-07-11 11:37:46 -0700181 # The platform/config can provide a custom DataPlanePort class
182 # here if you have a custom implementation with different
183 # behavior.
184 #
185 # Set config.dataplane.portclass = MyDataPlanePortClass
186 # where MyDataPlanePortClass has the same interface as the class
187 # DataPlanePort defined here.
188 #
Rich Lanefb718302012-12-26 21:02:31 -0800189 if "dataplane" in self.config and "portclass" in self.config["dataplane"]:
190 self.dppclass = self.config["dataplane"]["portclass"]
191 elif have_pypcap:
192 self.dppclass = DataPlanePortPcap
193 else:
194 self.logger.warning("Missing pypcap, VLAN tests may fail. See README for installation instructions.")
195 self.dppclass = DataPlanePort
Jeffrey Townsend0e8b0922012-07-11 11:37:46 -0700196
Rich Lanee1b8da92012-12-26 22:47:13 -0800197 self.start()
198
199 def run(self):
200 """
201 Activity function for class
202 """
203 while not self.killed:
204 sockets = [self.waker] + self.ports.values()
205 try:
206 sel_in, sel_out, sel_err = select.select(sockets, [], [], 1)
207 except:
208 print sys.exc_info()
209 self.logger.error("Select error, exiting")
210 break
211
212 with self.cvar:
213 for port in sel_in:
214 if port == self.waker:
215 self.waker.wait()
216 continue
217 else:
218 # Enqueue packet
219 pkt, timestamp = port.recv()
220 port_number = port._port_number
221 self.logger.debug("Pkt len %d in on port %d",
222 len(pkt), port_number)
Rich Lane472aaea2013-08-27 09:27:38 -0700223 if self.pcap_writer:
224 self.pcap_writer.write(pkt, timestamp, port_number)
Rich Lanee1b8da92012-12-26 22:47:13 -0800225 queue = self.packet_queues[port_number]
226 if len(queue) >= self.MAX_QUEUE_LEN:
227 # Queue full, throw away oldest
228 queue.pop(0)
229 self.logger.debug("Discarding oldest packet to make room")
230 queue.append((pkt, timestamp))
231 self.cvar.notify_all()
232
233 self.logger.info("Thread exit")
234
Dan Talayco34089522010-02-07 23:07:41 -0800235 def port_add(self, interface_name, port_number):
236 """
237 Add a port to the dataplane
Dan Talayco34089522010-02-07 23:07:41 -0800238 @param interface_name The name of the physical interface like eth1
239 @param port_number The port number used to refer to the port
Rich Lanee1b8da92012-12-26 22:47:13 -0800240 Stashes the port number on the created port object.
Dan Talayco34089522010-02-07 23:07:41 -0800241 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800242 self.ports[port_number] = self.dppclass(interface_name, port_number)
243 self.ports[port_number]._port_number = port_number
244 self.packet_queues[port_number] = []
245 # Need to wake up event loop to change the sockets being selected on.
246 self.waker.notify()
Dan Talayco34089522010-02-07 23:07:41 -0800247
248 def send(self, port_number, packet):
249 """
250 Send a packet to the given port
251 @param port_number The port to send the data to
252 @param packet Raw packet data to send to port
253 """
Dan Talayco11c26e72010-03-07 22:03:57 -0800254 self.logger.debug("Sending %d bytes to port %d" %
255 (len(packet), port_number))
Rich Lane472aaea2013-08-27 09:27:38 -0700256 if self.pcap_writer:
257 self.pcap_writer.write(packet, time.time(), port_number)
Rich Lanee1b8da92012-12-26 22:47:13 -0800258 bytes = self.ports[port_number].send(packet)
Dan Talayco34089522010-02-07 23:07:41 -0800259 if bytes != len(packet):
Dan Talayco48370102010-03-03 15:17:33 -0800260 self.logger.error("Unhandled send error, length mismatch %d != %d" %
Dan Talayco1b3f6902010-02-15 14:14:19 -0800261 (bytes, len(packet)))
Dan Talayco34089522010-02-07 23:07:41 -0800262 return bytes
263
Rich Lanee1b8da92012-12-26 22:47:13 -0800264 def oldest_port_number(self):
265 """
266 Returns the port number with the oldest packet, or
267 None if no packets are queued.
268 """
269 min_port_number = None
Rich Lanedb9d8662012-07-26 18:04:24 -0700270 min_time = float('inf')
Rich Lanee1b8da92012-12-26 22:47:13 -0800271 for (port_number, queue) in self.packet_queues.items():
272 if queue and queue[0][1] < min_time:
273 min_time = queue[0][1]
274 min_port_number = port_number
275 return min_port_number
Rich Lanedb9d8662012-07-26 18:04:24 -0700276
277 # Dequeues and yields packets in the order they were received.
Rich Lanee1b8da92012-12-26 22:47:13 -0800278 # Yields (port number, packet, received time).
Rich Lanedb9d8662012-07-26 18:04:24 -0700279 # If port_number is not specified yields packets from all ports.
280 def packets(self, port_number=None):
281 while True:
Rich Lanee1b8da92012-12-26 22:47:13 -0800282 rcv_port_number = port_number or self.oldest_port_number()
Rich Lanedb9d8662012-07-26 18:04:24 -0700283
Rich Lanee1b8da92012-12-26 22:47:13 -0800284 if rcv_port_number == None:
285 self.logger.debug("Out of packets on all ports")
Rich Lanedb9d8662012-07-26 18:04:24 -0700286 break
287
Rich Lanee1b8da92012-12-26 22:47:13 -0800288 queue = self.packet_queues[rcv_port_number]
289
290 if len(queue) == 0:
291 self.logger.debug("Out of packets on port %d", rcv_port_number)
292 break
293
294 pkt, time = queue.pop(0)
295 yield (rcv_port_number, pkt, time)
Rich Lanedb9d8662012-07-26 18:04:24 -0700296
Rich Lane8806bc42012-07-26 19:18:37 -0700297 def poll(self, port_number=None, timeout=-1, exp_pkt=None):
Dan Talaycoe226eb12010-02-18 23:06:30 -0800298 """
299 Poll one or all dataplane ports for a packet
300
301 If port_number is given, get the oldest packet from that port.
302 Otherwise, find the port with the oldest packet and return
303 that packet.
Dan Talayco1729fdb2012-05-03 09:35:56 -0700304
305 If exp_pkt is true, discard all packets until that one is found
306
Dan Talaycoe226eb12010-02-18 23:06:30 -0800307 @param port_number If set, get packet from this port
Dan Talayco11c26e72010-03-07 22:03:57 -0800308 @param timeout If positive and no packet is available, block
Dan Talaycoe226eb12010-02-18 23:06:30 -0800309 until a packet is received or for this many seconds
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700310 @param exp_pkt If not None, look for this packet and ignore any
Dan Talayco1729fdb2012-05-03 09:35:56 -0700311 others received. Note that if port_number is None, all packets
312 from all ports will be discarded until the exp_pkt is found
Dan Talaycoe226eb12010-02-18 23:06:30 -0800313 @return The triple port_number, packet, pkt_time where packet
314 is received from port_number at time pkt_time. If a timeout
315 occurs, return None, None, None
316 """
317
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700318 if exp_pkt and not port_number:
Dan Talayco1729fdb2012-05-03 09:35:56 -0700319 self.logger.warn("Dataplane poll with exp_pkt but no port number")
320
Rich Lanedb9d8662012-07-26 18:04:24 -0700321 # Retrieve the packet. Returns (port number, packet, time).
322 def grab():
323 self.logger.debug("Grabbing packet")
Rich Lanee1b8da92012-12-26 22:47:13 -0800324 for (rcv_port_number, pkt, time) in self.packets(port_number):
325 self.logger.debug("Checking packet from port %d", rcv_port_number)
Dan Talayco1729fdb2012-05-03 09:35:56 -0700326 if not exp_pkt or match_exp_pkt(exp_pkt, pkt):
Rich Lanee1b8da92012-12-26 22:47:13 -0800327 return (rcv_port_number, pkt, time)
Rich Lanedb9d8662012-07-26 18:04:24 -0700328 self.logger.debug("Did not find packet")
329 return None
Dan Talaycoe226eb12010-02-18 23:06:30 -0800330
Rich Lanee1b8da92012-12-26 22:47:13 -0800331 with self.cvar:
Rich Lanecd97d3d2013-01-07 18:50:06 -0800332 ret = ofutils.timed_wait(self.cvar, grab, timeout=timeout)
Dan Talayco34089522010-02-07 23:07:41 -0800333
Rich Lanedb9d8662012-07-26 18:04:24 -0700334 if ret != None:
Rich Lanee1b8da92012-12-26 22:47:13 -0800335 return ret
Rich Lanedb9d8662012-07-26 18:04:24 -0700336 else:
337 self.logger.debug("Poll time out, no packet from " + str(port_number))
338 return (None, None, None)
Dan Talayco34089522010-02-07 23:07:41 -0800339
Rich Lanee1b8da92012-12-26 22:47:13 -0800340 def kill(self):
Dan Talayco1b3f6902010-02-15 14:14:19 -0800341 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800342 Stop the dataplane thread.
Dan Talayco1b3f6902010-02-15 14:14:19 -0800343 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800344 self.killed = True
345 self.waker.notify()
346 self.join()
347 # Explicitly release ports to ensure we don't run out of sockets
348 # even if someone keeps holding a reference to the dataplane.
349 del self.ports
Rich Lane4d46dbd2012-12-22 19:26:19 -0800350
Rich Lanee1b8da92012-12-26 22:47:13 -0800351 def port_down(self, port_number):
ShreyaPanditacd8e1cf2012-11-28 11:44:42 -0500352 """Brings the specified port down"""
Rich Lanee1b8da92012-12-26 22:47:13 -0800353 self.ports[port_number].down()
ShreyaPanditacd8e1cf2012-11-28 11:44:42 -0500354
Rich Lanee1b8da92012-12-26 22:47:13 -0800355 def port_up(self, port_number):
ShreyaPanditacd8e1cf2012-11-28 11:44:42 -0500356 """Brings the specified port up"""
Rich Lanee1b8da92012-12-26 22:47:13 -0800357 self.ports[port_number].up()
Rich Lane2c7812c2012-12-27 17:52:23 -0800358
359 def flush(self):
360 """
361 Drop any queued packets.
362 """
363 for port_number in self.packet_queues.keys():
364 self.packet_queues[port_number] = []
Rich Lane472aaea2013-08-27 09:27:38 -0700365
366 def start_pcap(self, filename):
367 assert(self.pcap_writer == None)
368 self.pcap_writer = PcapWriter(filename)
369
370 def stop_pcap(self):
371 if self.pcap_writer:
372 self.pcap_writer.close()
373 self.pcap_writer = None