blob: f70de6c3faf2476c44b1445657776f170cdcd9b1 [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
Dan Talayco3087a462010-02-13 14:01:47 -080028
Rich Laneb42a31c2012-10-05 17:54:17 -070029have_pypcap = False
30try:
31 import pcap
Ed Swierkab0bab32012-11-30 13:31:00 -080032 if hasattr(pcap, "pcap"):
33 # the incompatible pylibpcap library masquerades as pcap
34 have_pypcap = True
Rich Laneb42a31c2012-10-05 17:54:17 -070035except:
36 pass
37
Ed Swierk506614a2012-03-29 08:16:59 -070038def match_exp_pkt(exp_pkt, pkt):
39 """
40 Compare the string value of pkt with the string value of exp_pkt,
41 and return True iff they are identical. If the length of exp_pkt is
42 less than the minimum Ethernet frame size (60 bytes), then padding
43 bytes in pkt are ignored.
44 """
45 e = str(exp_pkt)
46 p = str(pkt)
47 if len(e) < 60:
48 p = p[:len(e)]
49 return e == p
50
Jeffrey Townsend0e8b0922012-07-11 11:37:46 -070051
Rich Lanee1b8da92012-12-26 22:47:13 -080052class DataPlanePort:
Dan Talayco3087a462010-02-13 14:01:47 -080053 """
Rich Lanee1b8da92012-12-26 22:47:13 -080054 Uses raw sockets to capture and send packets on a network interface.
Dan Talayco3087a462010-02-13 14:01:47 -080055 """
56
Rich Lanee1b8da92012-12-26 22:47:13 -080057 RCV_SIZE_DEFAULT = 4096
58 ETH_P_ALL = 0x03
59 RCV_TIMEOUT = 10000
60
61 def __init__(self, interface_name, port_number):
Dan Talayco3087a462010-02-13 14:01:47 -080062 """
Dan Talayco3087a462010-02-13 14:01:47 -080063 @param interface_name The name of the physical interface like eth1
Dan Talayco3087a462010-02-13 14:01:47 -080064 """
Dan Talayco3087a462010-02-13 14:01:47 -080065 self.interface_name = interface_name
Rich Lanee1b8da92012-12-26 22:47:13 -080066 self.socket = socket.socket(socket.AF_PACKET, socket.SOCK_RAW,
67 socket.htons(self.ETH_P_ALL))
68 self.socket.bind((interface_name, 0))
69 netutils.set_promisc(self.socket, interface_name)
70 self.socket.settimeout(self.RCV_TIMEOUT)
Dan Talayco1b3f6902010-02-15 14:14:19 -080071
Rich Lanee1b8da92012-12-26 22:47:13 -080072 def __del__(self):
73 if self.socket:
Dan Talayco1b3f6902010-02-15 14:14:19 -080074 self.socket.close()
Dan Talayco1b3f6902010-02-15 14:14:19 -080075
Rich Lanee1b8da92012-12-26 22:47:13 -080076 def fileno(self):
Dan Talayco3087a462010-02-13 14:01:47 -080077 """
Rich Lanee1b8da92012-12-26 22:47:13 -080078 Return an integer file descriptor that can be passed to select(2).
Dan Talayco3087a462010-02-13 14:01:47 -080079 """
Rich Lanee1b8da92012-12-26 22:47:13 -080080 return self.socket.fileno()
Dan Talayco3087a462010-02-13 14:01:47 -080081
Rich Lanee1b8da92012-12-26 22:47:13 -080082 def recv(self):
Dan Talayco3087a462010-02-13 14:01:47 -080083 """
Rich Lanee1b8da92012-12-26 22:47:13 -080084 Receive a packet from this port.
85 @retval (packet data, timestamp)
Dan Talayco3087a462010-02-13 14:01:47 -080086 """
Rich Lanee1b8da92012-12-26 22:47:13 -080087 pkt = self.socket.recv(self.RCV_SIZE_DEFAULT)
88 return (pkt, time.time())
Dan Talayco3087a462010-02-13 14:01:47 -080089
90 def send(self, packet):
91 """
Rich Lanee1b8da92012-12-26 22:47:13 -080092 Send a packet out this port.
Dan Talayco3087a462010-02-13 14:01:47 -080093 @param packet The packet data to send to the port
94 @retval The number of bytes sent
95 """
96 return self.socket.send(packet)
97
Rich Lanee1b8da92012-12-26 22:47:13 -080098 def down(self):
Dan Talayco3087a462010-02-13 14:01:47 -080099 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800100 Bring the physical link down.
Dan Talayco3087a462010-02-13 14:01:47 -0800101 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800102 os.system("ifconfig down %s" % self.interface_name)
Dan Talayco3087a462010-02-13 14:01:47 -0800103
Rich Lanee1b8da92012-12-26 22:47:13 -0800104 def up(self):
ShreyaPanditacd8e1cf2012-11-28 11:44:42 -0500105 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800106 Bring the physical link up.
ShreyaPanditacd8e1cf2012-11-28 11:44:42 -0500107 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800108 os.system("ifconfig up %s" % self.interface_name)
ShreyaPanditacd8e1cf2012-11-28 11:44:42 -0500109
110
Rich Lanee1b8da92012-12-26 22:47:13 -0800111class DataPlanePortPcap:
Rich Laneb42a31c2012-10-05 17:54:17 -0700112 """
113 Alternate port implementation using libpcap. This is required for recent
114 versions of Linux (such as Linux 3.2 included in Ubuntu 12.04) which
115 offload the VLAN tag, so it isn't in the data returned from a read on a raw
116 socket. libpcap understands how to read the VLAN tag from the kernel.
117 """
118
Rich Lanee1b8da92012-12-26 22:47:13 -0800119 def __init__(self, interface_name, port_number):
Rich Laneb42a31c2012-10-05 17:54:17 -0700120 self.pcap = pcap.pcap(interface_name)
121 self.pcap.setnonblock()
Rich Lanee1b8da92012-12-26 22:47:13 -0800122
123 def fileno(self):
Rich Laneb42a31c2012-10-05 17:54:17 -0700124 return self.pcap.fileno()
125
Rich Lanee1b8da92012-12-26 22:47:13 -0800126 def recv(self):
127 (timestamp, pkt) = next(self.pcap)
128 return (pkt, timestamp)
Rich Laneb42a31c2012-10-05 17:54:17 -0700129
130 def send(self, packet):
Rich Laneb42a31c2012-10-05 17:54:17 -0700131 return self.pcap.inject(packet, len(packet))
Dan Talayco34089522010-02-07 23:07:41 -0800132
Rich Lanee1b8da92012-12-26 22:47:13 -0800133 def down(self):
134 pass
135
136 def up(self):
137 pass
138
139class DataPlane(Thread):
Dan Talayco34089522010-02-07 23:07:41 -0800140 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800141 This class provides methods to send and receive packets on the dataplane.
142 It uses the DataPlanePort class, or an alternative implementation of that
143 interface, to do IO on a particular port. A background thread is used to
144 read packets from the dataplane ports and enqueue them to be read by the
145 test. The kill() method must be called to shutdown this thread.
Dan Talayco34089522010-02-07 23:07:41 -0800146 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800147
148 MAX_QUEUE_LEN = 100
149
Jeffrey Townsend0e8b0922012-07-11 11:37:46 -0700150 def __init__(self, config=None):
Rich Lanee1b8da92012-12-26 22:47:13 -0800151 Thread.__init__(self)
152
153 # dict from port number to port object
154 self.ports = {}
155
156 # dict from port number to list of (timestamp, packet)
157 self.packet_queues = {}
158
159 # cvar serves double duty as a regular top level lock and
Dan Talaycoe226eb12010-02-18 23:06:30 -0800160 # as a condition variable
Rich Lanee1b8da92012-12-26 22:47:13 -0800161 self.cvar = Condition()
162
163 # Used to wake up the event loop from another thread
Rich Lanecd97d3d2013-01-07 18:50:06 -0800164 self.waker = ofutils.EventDescriptor()
Rich Lanee1b8da92012-12-26 22:47:13 -0800165 self.killed = False
Dan Talaycoe226eb12010-02-18 23:06:30 -0800166
Dan Talayco48370102010-03-03 15:17:33 -0800167 self.logger = logging.getLogger("dataplane")
Dan Talayco34089522010-02-07 23:07:41 -0800168
Jeffrey Townsend0e8b0922012-07-11 11:37:46 -0700169 if config is None:
170 self.config = {}
171 else:
172 self.config = config;
173
174 ############################################################
175 #
Jeffrey Townsend0e8b0922012-07-11 11:37:46 -0700176 # The platform/config can provide a custom DataPlanePort class
177 # here if you have a custom implementation with different
178 # behavior.
179 #
180 # Set config.dataplane.portclass = MyDataPlanePortClass
181 # where MyDataPlanePortClass has the same interface as the class
182 # DataPlanePort defined here.
183 #
Rich Lanefb718302012-12-26 21:02:31 -0800184 if "dataplane" in self.config and "portclass" in self.config["dataplane"]:
185 self.dppclass = self.config["dataplane"]["portclass"]
186 elif have_pypcap:
187 self.dppclass = DataPlanePortPcap
188 else:
189 self.logger.warning("Missing pypcap, VLAN tests may fail. See README for installation instructions.")
190 self.dppclass = DataPlanePort
Jeffrey Townsend0e8b0922012-07-11 11:37:46 -0700191
Rich Lanee1b8da92012-12-26 22:47:13 -0800192 self.start()
193
194 def run(self):
195 """
196 Activity function for class
197 """
198 while not self.killed:
199 sockets = [self.waker] + self.ports.values()
200 try:
201 sel_in, sel_out, sel_err = select.select(sockets, [], [], 1)
202 except:
203 print sys.exc_info()
204 self.logger.error("Select error, exiting")
205 break
206
207 with self.cvar:
208 for port in sel_in:
209 if port == self.waker:
210 self.waker.wait()
211 continue
212 else:
213 # Enqueue packet
214 pkt, timestamp = port.recv()
215 port_number = port._port_number
216 self.logger.debug("Pkt len %d in on port %d",
217 len(pkt), port_number)
218 queue = self.packet_queues[port_number]
219 if len(queue) >= self.MAX_QUEUE_LEN:
220 # Queue full, throw away oldest
221 queue.pop(0)
222 self.logger.debug("Discarding oldest packet to make room")
223 queue.append((pkt, timestamp))
224 self.cvar.notify_all()
225
226 self.logger.info("Thread exit")
227
Dan Talayco34089522010-02-07 23:07:41 -0800228 def port_add(self, interface_name, port_number):
229 """
230 Add a port to the dataplane
Dan Talayco34089522010-02-07 23:07:41 -0800231 @param interface_name The name of the physical interface like eth1
232 @param port_number The port number used to refer to the port
Rich Lanee1b8da92012-12-26 22:47:13 -0800233 Stashes the port number on the created port object.
Dan Talayco34089522010-02-07 23:07:41 -0800234 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800235 self.ports[port_number] = self.dppclass(interface_name, port_number)
236 self.ports[port_number]._port_number = port_number
237 self.packet_queues[port_number] = []
238 # Need to wake up event loop to change the sockets being selected on.
239 self.waker.notify()
Dan Talayco34089522010-02-07 23:07:41 -0800240
241 def send(self, port_number, packet):
242 """
243 Send a packet to the given port
244 @param port_number The port to send the data to
245 @param packet Raw packet data to send to port
246 """
Dan Talayco11c26e72010-03-07 22:03:57 -0800247 self.logger.debug("Sending %d bytes to port %d" %
248 (len(packet), port_number))
Rich Lanee1b8da92012-12-26 22:47:13 -0800249 bytes = self.ports[port_number].send(packet)
Dan Talayco34089522010-02-07 23:07:41 -0800250 if bytes != len(packet):
Dan Talayco48370102010-03-03 15:17:33 -0800251 self.logger.error("Unhandled send error, length mismatch %d != %d" %
Dan Talayco1b3f6902010-02-15 14:14:19 -0800252 (bytes, len(packet)))
Dan Talayco34089522010-02-07 23:07:41 -0800253 return bytes
254
Rich Lanee1b8da92012-12-26 22:47:13 -0800255 def oldest_port_number(self):
256 """
257 Returns the port number with the oldest packet, or
258 None if no packets are queued.
259 """
260 min_port_number = None
Rich Lanedb9d8662012-07-26 18:04:24 -0700261 min_time = float('inf')
Rich Lanee1b8da92012-12-26 22:47:13 -0800262 for (port_number, queue) in self.packet_queues.items():
263 if queue and queue[0][1] < min_time:
264 min_time = queue[0][1]
265 min_port_number = port_number
266 return min_port_number
Rich Lanedb9d8662012-07-26 18:04:24 -0700267
268 # Dequeues and yields packets in the order they were received.
Rich Lanee1b8da92012-12-26 22:47:13 -0800269 # Yields (port number, packet, received time).
Rich Lanedb9d8662012-07-26 18:04:24 -0700270 # If port_number is not specified yields packets from all ports.
271 def packets(self, port_number=None):
272 while True:
Rich Lanee1b8da92012-12-26 22:47:13 -0800273 rcv_port_number = port_number or self.oldest_port_number()
Rich Lanedb9d8662012-07-26 18:04:24 -0700274
Rich Lanee1b8da92012-12-26 22:47:13 -0800275 if rcv_port_number == None:
276 self.logger.debug("Out of packets on all ports")
Rich Lanedb9d8662012-07-26 18:04:24 -0700277 break
278
Rich Lanee1b8da92012-12-26 22:47:13 -0800279 queue = self.packet_queues[rcv_port_number]
280
281 if len(queue) == 0:
282 self.logger.debug("Out of packets on port %d", rcv_port_number)
283 break
284
285 pkt, time = queue.pop(0)
286 yield (rcv_port_number, pkt, time)
Rich Lanedb9d8662012-07-26 18:04:24 -0700287
Rich Lane8806bc42012-07-26 19:18:37 -0700288 def poll(self, port_number=None, timeout=-1, exp_pkt=None):
Dan Talaycoe226eb12010-02-18 23:06:30 -0800289 """
290 Poll one or all dataplane ports for a packet
291
292 If port_number is given, get the oldest packet from that port.
293 Otherwise, find the port with the oldest packet and return
294 that packet.
Dan Talayco1729fdb2012-05-03 09:35:56 -0700295
296 If exp_pkt is true, discard all packets until that one is found
297
Dan Talaycoe226eb12010-02-18 23:06:30 -0800298 @param port_number If set, get packet from this port
Dan Talayco11c26e72010-03-07 22:03:57 -0800299 @param timeout If positive and no packet is available, block
Dan Talaycoe226eb12010-02-18 23:06:30 -0800300 until a packet is received or for this many seconds
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700301 @param exp_pkt If not None, look for this packet and ignore any
Dan Talayco1729fdb2012-05-03 09:35:56 -0700302 others received. Note that if port_number is None, all packets
303 from all ports will be discarded until the exp_pkt is found
Dan Talaycoe226eb12010-02-18 23:06:30 -0800304 @return The triple port_number, packet, pkt_time where packet
305 is received from port_number at time pkt_time. If a timeout
306 occurs, return None, None, None
307 """
308
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700309 if exp_pkt and not port_number:
Dan Talayco1729fdb2012-05-03 09:35:56 -0700310 self.logger.warn("Dataplane poll with exp_pkt but no port number")
311
Rich Lanedb9d8662012-07-26 18:04:24 -0700312 # Retrieve the packet. Returns (port number, packet, time).
313 def grab():
314 self.logger.debug("Grabbing packet")
Rich Lanee1b8da92012-12-26 22:47:13 -0800315 for (rcv_port_number, pkt, time) in self.packets(port_number):
316 self.logger.debug("Checking packet from port %d", rcv_port_number)
Dan Talayco1729fdb2012-05-03 09:35:56 -0700317 if not exp_pkt or match_exp_pkt(exp_pkt, pkt):
Rich Lanee1b8da92012-12-26 22:47:13 -0800318 return (rcv_port_number, pkt, time)
Rich Lanedb9d8662012-07-26 18:04:24 -0700319 self.logger.debug("Did not find packet")
320 return None
Dan Talaycoe226eb12010-02-18 23:06:30 -0800321
Rich Lanee1b8da92012-12-26 22:47:13 -0800322 with self.cvar:
Rich Lanecd97d3d2013-01-07 18:50:06 -0800323 ret = ofutils.timed_wait(self.cvar, grab, timeout=timeout)
Dan Talayco34089522010-02-07 23:07:41 -0800324
Rich Lanedb9d8662012-07-26 18:04:24 -0700325 if ret != None:
Rich Lanee1b8da92012-12-26 22:47:13 -0800326 return ret
Rich Lanedb9d8662012-07-26 18:04:24 -0700327 else:
328 self.logger.debug("Poll time out, no packet from " + str(port_number))
329 return (None, None, None)
Dan Talayco34089522010-02-07 23:07:41 -0800330
Rich Lanee1b8da92012-12-26 22:47:13 -0800331 def kill(self):
Dan Talayco1b3f6902010-02-15 14:14:19 -0800332 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800333 Stop the dataplane thread.
Dan Talayco1b3f6902010-02-15 14:14:19 -0800334 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800335 self.killed = True
336 self.waker.notify()
337 self.join()
338 # Explicitly release ports to ensure we don't run out of sockets
339 # even if someone keeps holding a reference to the dataplane.
340 del self.ports
Rich Lane4d46dbd2012-12-22 19:26:19 -0800341
Rich Lanee1b8da92012-12-26 22:47:13 -0800342 def port_down(self, port_number):
ShreyaPanditacd8e1cf2012-11-28 11:44:42 -0500343 """Brings the specified port down"""
Rich Lanee1b8da92012-12-26 22:47:13 -0800344 self.ports[port_number].down()
ShreyaPanditacd8e1cf2012-11-28 11:44:42 -0500345
Rich Lanee1b8da92012-12-26 22:47:13 -0800346 def port_up(self, port_number):
ShreyaPanditacd8e1cf2012-11-28 11:44:42 -0500347 """Brings the specified port up"""
Rich Lanee1b8da92012-12-26 22:47:13 -0800348 self.ports[port_number].up()
Rich Lane2c7812c2012-12-27 17:52:23 -0800349
350 def flush(self):
351 """
352 Drop any queued packets.
353 """
354 for port_number in self.packet_queues.keys():
355 self.packet_queues[port_number] = []