blob: d53e223ca20aee98fadf168ecb5628505e06fbdd [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
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)
129 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
165 self.waker = EventDescriptor()
166 self.killed = False
Dan Talaycoe226eb12010-02-18 23:06:30 -0800167
Dan Talayco48370102010-03-03 15:17:33 -0800168 self.logger = logging.getLogger("dataplane")
Dan Talayco34089522010-02-07 23:07:41 -0800169
Jeffrey Townsend0e8b0922012-07-11 11:37:46 -0700170 if config is None:
171 self.config = {}
172 else:
173 self.config = config;
174
175 ############################################################
176 #
Jeffrey Townsend0e8b0922012-07-11 11:37:46 -0700177 # The platform/config can provide a custom DataPlanePort class
178 # here if you have a custom implementation with different
179 # behavior.
180 #
181 # Set config.dataplane.portclass = MyDataPlanePortClass
182 # where MyDataPlanePortClass has the same interface as the class
183 # DataPlanePort defined here.
184 #
Rich Lanefb718302012-12-26 21:02:31 -0800185 if "dataplane" in self.config and "portclass" in self.config["dataplane"]:
186 self.dppclass = self.config["dataplane"]["portclass"]
187 elif have_pypcap:
188 self.dppclass = DataPlanePortPcap
189 else:
190 self.logger.warning("Missing pypcap, VLAN tests may fail. See README for installation instructions.")
191 self.dppclass = DataPlanePort
Jeffrey Townsend0e8b0922012-07-11 11:37:46 -0700192
Rich Lanee1b8da92012-12-26 22:47:13 -0800193 self.start()
194
195 def run(self):
196 """
197 Activity function for class
198 """
199 while not self.killed:
200 sockets = [self.waker] + self.ports.values()
201 try:
202 sel_in, sel_out, sel_err = select.select(sockets, [], [], 1)
203 except:
204 print sys.exc_info()
205 self.logger.error("Select error, exiting")
206 break
207
208 with self.cvar:
209 for port in sel_in:
210 if port == self.waker:
211 self.waker.wait()
212 continue
213 else:
214 # Enqueue packet
215 pkt, timestamp = port.recv()
216 port_number = port._port_number
217 self.logger.debug("Pkt len %d in on port %d",
218 len(pkt), port_number)
219 queue = self.packet_queues[port_number]
220 if len(queue) >= self.MAX_QUEUE_LEN:
221 # Queue full, throw away oldest
222 queue.pop(0)
223 self.logger.debug("Discarding oldest packet to make room")
224 queue.append((pkt, timestamp))
225 self.cvar.notify_all()
226
227 self.logger.info("Thread exit")
228
Dan Talayco34089522010-02-07 23:07:41 -0800229 def port_add(self, interface_name, port_number):
230 """
231 Add a port to the dataplane
Dan Talayco34089522010-02-07 23:07:41 -0800232 @param interface_name The name of the physical interface like eth1
233 @param port_number The port number used to refer to the port
Rich Lanee1b8da92012-12-26 22:47:13 -0800234 Stashes the port number on the created port object.
Dan Talayco34089522010-02-07 23:07:41 -0800235 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800236 self.ports[port_number] = self.dppclass(interface_name, port_number)
237 self.ports[port_number]._port_number = port_number
238 self.packet_queues[port_number] = []
239 # Need to wake up event loop to change the sockets being selected on.
240 self.waker.notify()
Dan Talayco34089522010-02-07 23:07:41 -0800241
242 def send(self, port_number, packet):
243 """
244 Send a packet to the given port
245 @param port_number The port to send the data to
246 @param packet Raw packet data to send to port
247 """
Dan Talayco11c26e72010-03-07 22:03:57 -0800248 self.logger.debug("Sending %d bytes to port %d" %
249 (len(packet), port_number))
Rich Lanee1b8da92012-12-26 22:47:13 -0800250 bytes = self.ports[port_number].send(packet)
Dan Talayco34089522010-02-07 23:07:41 -0800251 if bytes != len(packet):
Dan Talayco48370102010-03-03 15:17:33 -0800252 self.logger.error("Unhandled send error, length mismatch %d != %d" %
Dan Talayco1b3f6902010-02-15 14:14:19 -0800253 (bytes, len(packet)))
Dan Talayco34089522010-02-07 23:07:41 -0800254 return bytes
255
Rich Lanee1b8da92012-12-26 22:47:13 -0800256 def oldest_port_number(self):
257 """
258 Returns the port number with the oldest packet, or
259 None if no packets are queued.
260 """
261 min_port_number = None
Rich Lanedb9d8662012-07-26 18:04:24 -0700262 min_time = float('inf')
Rich Lanee1b8da92012-12-26 22:47:13 -0800263 for (port_number, queue) in self.packet_queues.items():
264 if queue and queue[0][1] < min_time:
265 min_time = queue[0][1]
266 min_port_number = port_number
267 return min_port_number
Rich Lanedb9d8662012-07-26 18:04:24 -0700268
269 # Dequeues and yields packets in the order they were received.
Rich Lanee1b8da92012-12-26 22:47:13 -0800270 # Yields (port number, packet, received time).
Rich Lanedb9d8662012-07-26 18:04:24 -0700271 # If port_number is not specified yields packets from all ports.
272 def packets(self, port_number=None):
273 while True:
Rich Lanee1b8da92012-12-26 22:47:13 -0800274 rcv_port_number = port_number or self.oldest_port_number()
Rich Lanedb9d8662012-07-26 18:04:24 -0700275
Rich Lanee1b8da92012-12-26 22:47:13 -0800276 if rcv_port_number == None:
277 self.logger.debug("Out of packets on all ports")
Rich Lanedb9d8662012-07-26 18:04:24 -0700278 break
279
Rich Lanee1b8da92012-12-26 22:47:13 -0800280 queue = self.packet_queues[rcv_port_number]
281
282 if len(queue) == 0:
283 self.logger.debug("Out of packets on port %d", rcv_port_number)
284 break
285
286 pkt, time = queue.pop(0)
287 yield (rcv_port_number, pkt, time)
Rich Lanedb9d8662012-07-26 18:04:24 -0700288
Rich Lane8806bc42012-07-26 19:18:37 -0700289 def poll(self, port_number=None, timeout=-1, exp_pkt=None):
Dan Talaycoe226eb12010-02-18 23:06:30 -0800290 """
291 Poll one or all dataplane ports for a packet
292
293 If port_number is given, get the oldest packet from that port.
294 Otherwise, find the port with the oldest packet and return
295 that packet.
Dan Talayco1729fdb2012-05-03 09:35:56 -0700296
297 If exp_pkt is true, discard all packets until that one is found
298
Dan Talaycoe226eb12010-02-18 23:06:30 -0800299 @param port_number If set, get packet from this port
Dan Talayco11c26e72010-03-07 22:03:57 -0800300 @param timeout If positive and no packet is available, block
Dan Talaycoe226eb12010-02-18 23:06:30 -0800301 until a packet is received or for this many seconds
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700302 @param exp_pkt If not None, look for this packet and ignore any
Dan Talayco1729fdb2012-05-03 09:35:56 -0700303 others received. Note that if port_number is None, all packets
304 from all ports will be discarded until the exp_pkt is found
Dan Talaycoe226eb12010-02-18 23:06:30 -0800305 @return The triple port_number, packet, pkt_time where packet
306 is received from port_number at time pkt_time. If a timeout
307 occurs, return None, None, None
308 """
309
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700310 if exp_pkt and not port_number:
Dan Talayco1729fdb2012-05-03 09:35:56 -0700311 self.logger.warn("Dataplane poll with exp_pkt but no port number")
312
Rich Lanedb9d8662012-07-26 18:04:24 -0700313 # Retrieve the packet. Returns (port number, packet, time).
314 def grab():
315 self.logger.debug("Grabbing packet")
Rich Lanee1b8da92012-12-26 22:47:13 -0800316 for (rcv_port_number, pkt, time) in self.packets(port_number):
317 self.logger.debug("Checking packet from port %d", rcv_port_number)
Dan Talayco1729fdb2012-05-03 09:35:56 -0700318 if not exp_pkt or match_exp_pkt(exp_pkt, pkt):
Rich Lanee1b8da92012-12-26 22:47:13 -0800319 return (rcv_port_number, pkt, time)
Rich Lanedb9d8662012-07-26 18:04:24 -0700320 self.logger.debug("Did not find packet")
321 return None
Dan Talaycoe226eb12010-02-18 23:06:30 -0800322
Rich Lanee1b8da92012-12-26 22:47:13 -0800323 with self.cvar:
324 ret = timed_wait(self.cvar, grab, timeout=timeout)
Dan Talayco34089522010-02-07 23:07:41 -0800325
Rich Lanedb9d8662012-07-26 18:04:24 -0700326 if ret != None:
Rich Lanee1b8da92012-12-26 22:47:13 -0800327 return ret
Rich Lanedb9d8662012-07-26 18:04:24 -0700328 else:
329 self.logger.debug("Poll time out, no packet from " + str(port_number))
330 return (None, None, None)
Dan Talayco34089522010-02-07 23:07:41 -0800331
Rich Lanee1b8da92012-12-26 22:47:13 -0800332 def kill(self):
Dan Talayco1b3f6902010-02-15 14:14:19 -0800333 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800334 Stop the dataplane thread.
Dan Talayco1b3f6902010-02-15 14:14:19 -0800335 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800336 self.killed = True
337 self.waker.notify()
338 self.join()
339 # Explicitly release ports to ensure we don't run out of sockets
340 # even if someone keeps holding a reference to the dataplane.
341 del self.ports
Rich Lane4d46dbd2012-12-22 19:26:19 -0800342
Rich Lanee1b8da92012-12-26 22:47:13 -0800343 def port_down(self, port_number):
ShreyaPanditacd8e1cf2012-11-28 11:44:42 -0500344 """Brings the specified port down"""
Rich Lanee1b8da92012-12-26 22:47:13 -0800345 self.ports[port_number].down()
ShreyaPanditacd8e1cf2012-11-28 11:44:42 -0500346
Rich Lanee1b8da92012-12-26 22:47:13 -0800347 def port_up(self, port_number):
ShreyaPanditacd8e1cf2012-11-28 11:44:42 -0500348 """Brings the specified port up"""
Rich Lanee1b8da92012-12-26 22:47:13 -0800349 self.ports[port_number].up()