blob: 740e6aaee166439ca413974ae93260944db59d78 [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 Laneb42a31c2012-10-05 17:54:17 -0700132 return self.pcap.inject(packet, len(packet))
Dan Talayco34089522010-02-07 23:07:41 -0800133
Rich Lanee1b8da92012-12-26 22:47:13 -0800134 def down(self):
135 pass
136
137 def up(self):
138 pass
139
140class DataPlane(Thread):
Dan Talayco34089522010-02-07 23:07:41 -0800141 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800142 This class provides methods to send and receive packets on the dataplane.
143 It uses the DataPlanePort class, or an alternative implementation of that
144 interface, to do IO on a particular port. A background thread is used to
145 read packets from the dataplane ports and enqueue them to be read by the
146 test. The kill() method must be called to shutdown this thread.
Dan Talayco34089522010-02-07 23:07:41 -0800147 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800148
149 MAX_QUEUE_LEN = 100
150
Jeffrey Townsend0e8b0922012-07-11 11:37:46 -0700151 def __init__(self, config=None):
Rich Lanee1b8da92012-12-26 22:47:13 -0800152 Thread.__init__(self)
153
154 # dict from port number to port object
155 self.ports = {}
156
157 # dict from port number to list of (timestamp, packet)
158 self.packet_queues = {}
159
160 # cvar serves double duty as a regular top level lock and
Dan Talaycoe226eb12010-02-18 23:06:30 -0800161 # as a condition variable
Rich Lanee1b8da92012-12-26 22:47:13 -0800162 self.cvar = Condition()
163
164 # Used to wake up the event loop from another thread
Rich Lanecd97d3d2013-01-07 18:50:06 -0800165 self.waker = ofutils.EventDescriptor()
Rich Lanee1b8da92012-12-26 22:47:13 -0800166 self.killed = False
Dan Talaycoe226eb12010-02-18 23:06:30 -0800167
Dan Talayco48370102010-03-03 15:17:33 -0800168 self.logger = logging.getLogger("dataplane")
Rich Lane472aaea2013-08-27 09:27:38 -0700169 self.pcap_writer = None
Dan Talayco34089522010-02-07 23:07:41 -0800170
Jeffrey Townsend0e8b0922012-07-11 11:37:46 -0700171 if config is None:
172 self.config = {}
173 else:
174 self.config = config;
175
176 ############################################################
177 #
Jeffrey Townsend0e8b0922012-07-11 11:37:46 -0700178 # The platform/config can provide a custom DataPlanePort class
179 # here if you have a custom implementation with different
180 # behavior.
181 #
182 # Set config.dataplane.portclass = MyDataPlanePortClass
183 # where MyDataPlanePortClass has the same interface as the class
184 # DataPlanePort defined here.
185 #
Rich Lanefb718302012-12-26 21:02:31 -0800186 if "dataplane" in self.config and "portclass" in self.config["dataplane"]:
187 self.dppclass = self.config["dataplane"]["portclass"]
188 elif have_pypcap:
189 self.dppclass = DataPlanePortPcap
190 else:
191 self.logger.warning("Missing pypcap, VLAN tests may fail. See README for installation instructions.")
192 self.dppclass = DataPlanePort
Jeffrey Townsend0e8b0922012-07-11 11:37:46 -0700193
Rich Lanee1b8da92012-12-26 22:47:13 -0800194 self.start()
195
196 def run(self):
197 """
198 Activity function for class
199 """
200 while not self.killed:
201 sockets = [self.waker] + self.ports.values()
202 try:
203 sel_in, sel_out, sel_err = select.select(sockets, [], [], 1)
204 except:
205 print sys.exc_info()
206 self.logger.error("Select error, exiting")
207 break
208
209 with self.cvar:
210 for port in sel_in:
211 if port == self.waker:
212 self.waker.wait()
213 continue
214 else:
215 # Enqueue packet
216 pkt, timestamp = port.recv()
217 port_number = port._port_number
218 self.logger.debug("Pkt len %d in on port %d",
219 len(pkt), port_number)
Rich Lane472aaea2013-08-27 09:27:38 -0700220 if self.pcap_writer:
221 self.pcap_writer.write(pkt, timestamp, port_number)
Rich Lanee1b8da92012-12-26 22:47:13 -0800222 queue = self.packet_queues[port_number]
223 if len(queue) >= self.MAX_QUEUE_LEN:
224 # Queue full, throw away oldest
225 queue.pop(0)
226 self.logger.debug("Discarding oldest packet to make room")
227 queue.append((pkt, timestamp))
228 self.cvar.notify_all()
229
230 self.logger.info("Thread exit")
231
Dan Talayco34089522010-02-07 23:07:41 -0800232 def port_add(self, interface_name, port_number):
233 """
234 Add a port to the dataplane
Dan Talayco34089522010-02-07 23:07:41 -0800235 @param interface_name The name of the physical interface like eth1
236 @param port_number The port number used to refer to the port
Rich Lanee1b8da92012-12-26 22:47:13 -0800237 Stashes the port number on the created port object.
Dan Talayco34089522010-02-07 23:07:41 -0800238 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800239 self.ports[port_number] = self.dppclass(interface_name, port_number)
240 self.ports[port_number]._port_number = port_number
241 self.packet_queues[port_number] = []
242 # Need to wake up event loop to change the sockets being selected on.
243 self.waker.notify()
Dan Talayco34089522010-02-07 23:07:41 -0800244
245 def send(self, port_number, packet):
246 """
247 Send a packet to the given port
248 @param port_number The port to send the data to
249 @param packet Raw packet data to send to port
250 """
Dan Talayco11c26e72010-03-07 22:03:57 -0800251 self.logger.debug("Sending %d bytes to port %d" %
252 (len(packet), port_number))
Rich Lane472aaea2013-08-27 09:27:38 -0700253 if self.pcap_writer:
254 self.pcap_writer.write(packet, time.time(), port_number)
Rich Lanee1b8da92012-12-26 22:47:13 -0800255 bytes = self.ports[port_number].send(packet)
Dan Talayco34089522010-02-07 23:07:41 -0800256 if bytes != len(packet):
Dan Talayco48370102010-03-03 15:17:33 -0800257 self.logger.error("Unhandled send error, length mismatch %d != %d" %
Dan Talayco1b3f6902010-02-15 14:14:19 -0800258 (bytes, len(packet)))
Dan Talayco34089522010-02-07 23:07:41 -0800259 return bytes
260
Rich Lanee1b8da92012-12-26 22:47:13 -0800261 def oldest_port_number(self):
262 """
263 Returns the port number with the oldest packet, or
264 None if no packets are queued.
265 """
266 min_port_number = None
Rich Lanedb9d8662012-07-26 18:04:24 -0700267 min_time = float('inf')
Rich Lanee1b8da92012-12-26 22:47:13 -0800268 for (port_number, queue) in self.packet_queues.items():
269 if queue and queue[0][1] < min_time:
270 min_time = queue[0][1]
271 min_port_number = port_number
272 return min_port_number
Rich Lanedb9d8662012-07-26 18:04:24 -0700273
274 # Dequeues and yields packets in the order they were received.
Rich Lanee1b8da92012-12-26 22:47:13 -0800275 # Yields (port number, packet, received time).
Rich Lanedb9d8662012-07-26 18:04:24 -0700276 # If port_number is not specified yields packets from all ports.
277 def packets(self, port_number=None):
278 while True:
Rich Lanee1b8da92012-12-26 22:47:13 -0800279 rcv_port_number = port_number or self.oldest_port_number()
Rich Lanedb9d8662012-07-26 18:04:24 -0700280
Rich Lanee1b8da92012-12-26 22:47:13 -0800281 if rcv_port_number == None:
282 self.logger.debug("Out of packets on all ports")
Rich Lanedb9d8662012-07-26 18:04:24 -0700283 break
284
Rich Lanee1b8da92012-12-26 22:47:13 -0800285 queue = self.packet_queues[rcv_port_number]
286
287 if len(queue) == 0:
288 self.logger.debug("Out of packets on port %d", rcv_port_number)
289 break
290
291 pkt, time = queue.pop(0)
292 yield (rcv_port_number, pkt, time)
Rich Lanedb9d8662012-07-26 18:04:24 -0700293
Rich Lane8806bc42012-07-26 19:18:37 -0700294 def poll(self, port_number=None, timeout=-1, exp_pkt=None):
Dan Talaycoe226eb12010-02-18 23:06:30 -0800295 """
296 Poll one or all dataplane ports for a packet
297
298 If port_number is given, get the oldest packet from that port.
299 Otherwise, find the port with the oldest packet and return
300 that packet.
Dan Talayco1729fdb2012-05-03 09:35:56 -0700301
302 If exp_pkt is true, discard all packets until that one is found
303
Dan Talaycoe226eb12010-02-18 23:06:30 -0800304 @param port_number If set, get packet from this port
Dan Talayco11c26e72010-03-07 22:03:57 -0800305 @param timeout If positive and no packet is available, block
Dan Talaycoe226eb12010-02-18 23:06:30 -0800306 until a packet is received or for this many seconds
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700307 @param exp_pkt If not None, look for this packet and ignore any
Dan Talayco1729fdb2012-05-03 09:35:56 -0700308 others received. Note that if port_number is None, all packets
309 from all ports will be discarded until the exp_pkt is found
Dan Talaycoe226eb12010-02-18 23:06:30 -0800310 @return The triple port_number, packet, pkt_time where packet
311 is received from port_number at time pkt_time. If a timeout
312 occurs, return None, None, None
313 """
314
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700315 if exp_pkt and not port_number:
Dan Talayco1729fdb2012-05-03 09:35:56 -0700316 self.logger.warn("Dataplane poll with exp_pkt but no port number")
317
Rich Lanedb9d8662012-07-26 18:04:24 -0700318 # Retrieve the packet. Returns (port number, packet, time).
319 def grab():
320 self.logger.debug("Grabbing packet")
Rich Lanee1b8da92012-12-26 22:47:13 -0800321 for (rcv_port_number, pkt, time) in self.packets(port_number):
322 self.logger.debug("Checking packet from port %d", rcv_port_number)
Dan Talayco1729fdb2012-05-03 09:35:56 -0700323 if not exp_pkt or match_exp_pkt(exp_pkt, pkt):
Rich Lanee1b8da92012-12-26 22:47:13 -0800324 return (rcv_port_number, pkt, time)
Rich Lanedb9d8662012-07-26 18:04:24 -0700325 self.logger.debug("Did not find packet")
326 return None
Dan Talaycoe226eb12010-02-18 23:06:30 -0800327
Rich Lanee1b8da92012-12-26 22:47:13 -0800328 with self.cvar:
Rich Lanecd97d3d2013-01-07 18:50:06 -0800329 ret = ofutils.timed_wait(self.cvar, grab, timeout=timeout)
Dan Talayco34089522010-02-07 23:07:41 -0800330
Rich Lanedb9d8662012-07-26 18:04:24 -0700331 if ret != None:
Rich Lanee1b8da92012-12-26 22:47:13 -0800332 return ret
Rich Lanedb9d8662012-07-26 18:04:24 -0700333 else:
334 self.logger.debug("Poll time out, no packet from " + str(port_number))
335 return (None, None, None)
Dan Talayco34089522010-02-07 23:07:41 -0800336
Rich Lanee1b8da92012-12-26 22:47:13 -0800337 def kill(self):
Dan Talayco1b3f6902010-02-15 14:14:19 -0800338 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800339 Stop the dataplane thread.
Dan Talayco1b3f6902010-02-15 14:14:19 -0800340 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800341 self.killed = True
342 self.waker.notify()
343 self.join()
344 # Explicitly release ports to ensure we don't run out of sockets
345 # even if someone keeps holding a reference to the dataplane.
346 del self.ports
Rich Lane4d46dbd2012-12-22 19:26:19 -0800347
Rich Lanee1b8da92012-12-26 22:47:13 -0800348 def port_down(self, port_number):
ShreyaPanditacd8e1cf2012-11-28 11:44:42 -0500349 """Brings the specified port down"""
Rich Lanee1b8da92012-12-26 22:47:13 -0800350 self.ports[port_number].down()
ShreyaPanditacd8e1cf2012-11-28 11:44:42 -0500351
Rich Lanee1b8da92012-12-26 22:47:13 -0800352 def port_up(self, port_number):
ShreyaPanditacd8e1cf2012-11-28 11:44:42 -0500353 """Brings the specified port up"""
Rich Lanee1b8da92012-12-26 22:47:13 -0800354 self.ports[port_number].up()
Rich Lane2c7812c2012-12-27 17:52:23 -0800355
356 def flush(self):
357 """
358 Drop any queued packets.
359 """
360 for port_number in self.packet_queues.keys():
361 self.packet_queues[port_number] = []
Rich Lane472aaea2013-08-27 09:27:38 -0700362
363 def start_pcap(self, filename):
364 assert(self.pcap_writer == None)
365 self.pcap_writer = PcapWriter(filename)
366
367 def stop_pcap(self):
368 if self.pcap_writer:
369 self.pcap_writer.close()
370 self.pcap_writer = None