blob: 1cb04b8cff9a5108e994bd20d565f05b38ac0e16 [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 Lane1b737262015-03-18 10:13:56 -070030if "linux" in sys.platform:
31 import afpacket
32
Rich Laneb42a31c2012-10-05 17:54:17 -070033have_pypcap = False
34try:
35 import pcap
Ed Swierkab0bab32012-11-30 13:31:00 -080036 if hasattr(pcap, "pcap"):
37 # the incompatible pylibpcap library masquerades as pcap
38 have_pypcap = True
Rich Laneb42a31c2012-10-05 17:54:17 -070039except:
40 pass
41
Ed Swierk506614a2012-03-29 08:16:59 -070042def match_exp_pkt(exp_pkt, pkt):
43 """
44 Compare the string value of pkt with the string value of exp_pkt,
45 and return True iff they are identical. If the length of exp_pkt is
46 less than the minimum Ethernet frame size (60 bytes), then padding
47 bytes in pkt are ignored.
48 """
49 e = str(exp_pkt)
50 p = str(pkt)
51 if len(e) < 60:
52 p = p[:len(e)]
53 return e == p
54
Jeffrey Townsend0e8b0922012-07-11 11:37:46 -070055
Rich Lanee1b8da92012-12-26 22:47:13 -080056class DataPlanePort:
Dan Talayco3087a462010-02-13 14:01:47 -080057 """
Rich Lanee1b8da92012-12-26 22:47:13 -080058 Uses raw sockets to capture and send packets on a network interface.
Dan Talayco3087a462010-02-13 14:01:47 -080059 """
60
Rich Lanee1b8da92012-12-26 22:47:13 -080061 RCV_SIZE_DEFAULT = 4096
62 ETH_P_ALL = 0x03
63 RCV_TIMEOUT = 10000
64
65 def __init__(self, interface_name, port_number):
Dan Talayco3087a462010-02-13 14:01:47 -080066 """
Dan Talayco3087a462010-02-13 14:01:47 -080067 @param interface_name The name of the physical interface like eth1
Dan Talayco3087a462010-02-13 14:01:47 -080068 """
Dan Talayco3087a462010-02-13 14:01:47 -080069 self.interface_name = interface_name
Rich Lanee1b8da92012-12-26 22:47:13 -080070 self.socket = socket.socket(socket.AF_PACKET, socket.SOCK_RAW,
71 socket.htons(self.ETH_P_ALL))
Rich Lane1b737262015-03-18 10:13:56 -070072 afpacket.enable_auxdata(self.socket)
Rich Lanee1b8da92012-12-26 22:47:13 -080073 self.socket.bind((interface_name, 0))
74 netutils.set_promisc(self.socket, interface_name)
75 self.socket.settimeout(self.RCV_TIMEOUT)
Dan Talayco1b3f6902010-02-15 14:14:19 -080076
Rich Lanee1b8da92012-12-26 22:47:13 -080077 def __del__(self):
78 if self.socket:
Dan Talayco1b3f6902010-02-15 14:14:19 -080079 self.socket.close()
Dan Talayco1b3f6902010-02-15 14:14:19 -080080
Rich Lanee1b8da92012-12-26 22:47:13 -080081 def fileno(self):
Dan Talayco3087a462010-02-13 14:01:47 -080082 """
Rich Lanee1b8da92012-12-26 22:47:13 -080083 Return an integer file descriptor that can be passed to select(2).
Dan Talayco3087a462010-02-13 14:01:47 -080084 """
Rich Lanee1b8da92012-12-26 22:47:13 -080085 return self.socket.fileno()
Dan Talayco3087a462010-02-13 14:01:47 -080086
Rich Lanee1b8da92012-12-26 22:47:13 -080087 def recv(self):
Dan Talayco3087a462010-02-13 14:01:47 -080088 """
Rich Lanee1b8da92012-12-26 22:47:13 -080089 Receive a packet from this port.
90 @retval (packet data, timestamp)
Dan Talayco3087a462010-02-13 14:01:47 -080091 """
Rich Lane1b737262015-03-18 10:13:56 -070092 pkt = afpacket.recv(self.socket, self.RCV_SIZE_DEFAULT)
Rich Lanee1b8da92012-12-26 22:47:13 -080093 return (pkt, time.time())
Dan Talayco3087a462010-02-13 14:01:47 -080094
95 def send(self, packet):
96 """
Rich Lanee1b8da92012-12-26 22:47:13 -080097 Send a packet out this port.
Dan Talayco3087a462010-02-13 14:01:47 -080098 @param packet The packet data to send to the port
99 @retval The number of bytes sent
100 """
101 return self.socket.send(packet)
102
Rich Lanee1b8da92012-12-26 22:47:13 -0800103 def down(self):
Dan Talayco3087a462010-02-13 14:01:47 -0800104 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800105 Bring the physical link down.
Dan Talayco3087a462010-02-13 14:01:47 -0800106 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800107 os.system("ifconfig down %s" % self.interface_name)
Dan Talayco3087a462010-02-13 14:01:47 -0800108
Rich Lanee1b8da92012-12-26 22:47:13 -0800109 def up(self):
ShreyaPanditacd8e1cf2012-11-28 11:44:42 -0500110 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800111 Bring the physical link up.
ShreyaPanditacd8e1cf2012-11-28 11:44:42 -0500112 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800113 os.system("ifconfig up %s" % self.interface_name)
ShreyaPanditacd8e1cf2012-11-28 11:44:42 -0500114
115
Rich Lanee1b8da92012-12-26 22:47:13 -0800116class DataPlanePortPcap:
Rich Laneb42a31c2012-10-05 17:54:17 -0700117 """
118 Alternate port implementation using libpcap. This is required for recent
119 versions of Linux (such as Linux 3.2 included in Ubuntu 12.04) which
120 offload the VLAN tag, so it isn't in the data returned from a read on a raw
121 socket. libpcap understands how to read the VLAN tag from the kernel.
122 """
123
Rich Lanee1b8da92012-12-26 22:47:13 -0800124 def __init__(self, interface_name, port_number):
Rich Laneb42a31c2012-10-05 17:54:17 -0700125 self.pcap = pcap.pcap(interface_name)
126 self.pcap.setnonblock()
Rich Lanee1b8da92012-12-26 22:47:13 -0800127
128 def fileno(self):
Rich Laneb42a31c2012-10-05 17:54:17 -0700129 return self.pcap.fileno()
130
Rich Lanee1b8da92012-12-26 22:47:13 -0800131 def recv(self):
132 (timestamp, pkt) = next(self.pcap)
Rich Lane0415fd72014-02-25 22:04:17 -0800133 return (pkt[:], timestamp)
Rich Laneb42a31c2012-10-05 17:54:17 -0700134
135 def send(self, packet):
Rich Lane0306fd32015-03-11 15:52:21 -0700136 if hasattr(self.pcap, "inject"):
137 return self.pcap.inject(packet, len(packet))
138 else:
139 return self.pcap.sendpacket(packet)
Dan Talayco34089522010-02-07 23:07:41 -0800140
Rich Lanee1b8da92012-12-26 22:47:13 -0800141 def down(self):
142 pass
143
144 def up(self):
145 pass
146
147class DataPlane(Thread):
Dan Talayco34089522010-02-07 23:07:41 -0800148 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800149 This class provides methods to send and receive packets on the dataplane.
150 It uses the DataPlanePort class, or an alternative implementation of that
151 interface, to do IO on a particular port. A background thread is used to
152 read packets from the dataplane ports and enqueue them to be read by the
153 test. The kill() method must be called to shutdown this thread.
Dan Talayco34089522010-02-07 23:07:41 -0800154 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800155
156 MAX_QUEUE_LEN = 100
157
Jeffrey Townsend0e8b0922012-07-11 11:37:46 -0700158 def __init__(self, config=None):
Rich Lanee1b8da92012-12-26 22:47:13 -0800159 Thread.__init__(self)
160
161 # dict from port number to port object
162 self.ports = {}
163
164 # dict from port number to list of (timestamp, packet)
165 self.packet_queues = {}
166
167 # cvar serves double duty as a regular top level lock and
Dan Talaycoe226eb12010-02-18 23:06:30 -0800168 # as a condition variable
Rich Lanee1b8da92012-12-26 22:47:13 -0800169 self.cvar = Condition()
170
171 # Used to wake up the event loop from another thread
Rich Lanecd97d3d2013-01-07 18:50:06 -0800172 self.waker = ofutils.EventDescriptor()
Rich Lanee1b8da92012-12-26 22:47:13 -0800173 self.killed = False
Dan Talaycoe226eb12010-02-18 23:06:30 -0800174
Dan Talayco48370102010-03-03 15:17:33 -0800175 self.logger = logging.getLogger("dataplane")
Rich Lane472aaea2013-08-27 09:27:38 -0700176 self.pcap_writer = None
Dan Talayco34089522010-02-07 23:07:41 -0800177
Jeffrey Townsend0e8b0922012-07-11 11:37:46 -0700178 if config is None:
179 self.config = {}
180 else:
181 self.config = config;
182
183 ############################################################
184 #
Jeffrey Townsend0e8b0922012-07-11 11:37:46 -0700185 # The platform/config can provide a custom DataPlanePort class
186 # here if you have a custom implementation with different
187 # behavior.
188 #
189 # Set config.dataplane.portclass = MyDataPlanePortClass
190 # where MyDataPlanePortClass has the same interface as the class
191 # DataPlanePort defined here.
192 #
Rich Lanefb718302012-12-26 21:02:31 -0800193 if "dataplane" in self.config and "portclass" in self.config["dataplane"]:
194 self.dppclass = self.config["dataplane"]["portclass"]
195 elif have_pypcap:
196 self.dppclass = DataPlanePortPcap
197 else:
Rich Lanefb718302012-12-26 21:02:31 -0800198 self.dppclass = DataPlanePort
Jeffrey Townsend0e8b0922012-07-11 11:37:46 -0700199
Rich Lanee1b8da92012-12-26 22:47:13 -0800200 self.start()
201
202 def run(self):
203 """
204 Activity function for class
205 """
206 while not self.killed:
207 sockets = [self.waker] + self.ports.values()
208 try:
209 sel_in, sel_out, sel_err = select.select(sockets, [], [], 1)
210 except:
211 print sys.exc_info()
212 self.logger.error("Select error, exiting")
213 break
214
215 with self.cvar:
216 for port in sel_in:
217 if port == self.waker:
218 self.waker.wait()
219 continue
220 else:
221 # Enqueue packet
222 pkt, timestamp = port.recv()
223 port_number = port._port_number
224 self.logger.debug("Pkt len %d in on port %d",
225 len(pkt), port_number)
Rich Lane472aaea2013-08-27 09:27:38 -0700226 if self.pcap_writer:
227 self.pcap_writer.write(pkt, timestamp, port_number)
Rich Lanee1b8da92012-12-26 22:47:13 -0800228 queue = self.packet_queues[port_number]
229 if len(queue) >= self.MAX_QUEUE_LEN:
230 # Queue full, throw away oldest
231 queue.pop(0)
232 self.logger.debug("Discarding oldest packet to make room")
233 queue.append((pkt, timestamp))
234 self.cvar.notify_all()
235
236 self.logger.info("Thread exit")
237
Dan Talayco34089522010-02-07 23:07:41 -0800238 def port_add(self, interface_name, port_number):
239 """
240 Add a port to the dataplane
Dan Talayco34089522010-02-07 23:07:41 -0800241 @param interface_name The name of the physical interface like eth1
242 @param port_number The port number used to refer to the port
Rich Lanee1b8da92012-12-26 22:47:13 -0800243 Stashes the port number on the created port object.
Dan Talayco34089522010-02-07 23:07:41 -0800244 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800245 self.ports[port_number] = self.dppclass(interface_name, port_number)
246 self.ports[port_number]._port_number = port_number
247 self.packet_queues[port_number] = []
248 # Need to wake up event loop to change the sockets being selected on.
249 self.waker.notify()
Dan Talayco34089522010-02-07 23:07:41 -0800250
251 def send(self, port_number, packet):
252 """
253 Send a packet to the given port
254 @param port_number The port to send the data to
255 @param packet Raw packet data to send to port
256 """
Dan Talayco11c26e72010-03-07 22:03:57 -0800257 self.logger.debug("Sending %d bytes to port %d" %
258 (len(packet), port_number))
Rich Lane472aaea2013-08-27 09:27:38 -0700259 if self.pcap_writer:
260 self.pcap_writer.write(packet, time.time(), port_number)
Rich Lanee1b8da92012-12-26 22:47:13 -0800261 bytes = self.ports[port_number].send(packet)
Dan Talayco34089522010-02-07 23:07:41 -0800262 if bytes != len(packet):
Dan Talayco48370102010-03-03 15:17:33 -0800263 self.logger.error("Unhandled send error, length mismatch %d != %d" %
Dan Talayco1b3f6902010-02-15 14:14:19 -0800264 (bytes, len(packet)))
Dan Talayco34089522010-02-07 23:07:41 -0800265 return bytes
266
Rich Lanee1b8da92012-12-26 22:47:13 -0800267 def oldest_port_number(self):
268 """
269 Returns the port number with the oldest packet, or
270 None if no packets are queued.
271 """
272 min_port_number = None
Rich Lanedb9d8662012-07-26 18:04:24 -0700273 min_time = float('inf')
Rich Lanee1b8da92012-12-26 22:47:13 -0800274 for (port_number, queue) in self.packet_queues.items():
275 if queue and queue[0][1] < min_time:
276 min_time = queue[0][1]
277 min_port_number = port_number
278 return min_port_number
Rich Lanedb9d8662012-07-26 18:04:24 -0700279
280 # Dequeues and yields packets in the order they were received.
Rich Lanee1b8da92012-12-26 22:47:13 -0800281 # Yields (port number, packet, received time).
Rich Lanedb9d8662012-07-26 18:04:24 -0700282 # If port_number is not specified yields packets from all ports.
283 def packets(self, port_number=None):
284 while True:
Rich Lanee1b8da92012-12-26 22:47:13 -0800285 rcv_port_number = port_number or self.oldest_port_number()
Rich Lanedb9d8662012-07-26 18:04:24 -0700286
Rich Lanee1b8da92012-12-26 22:47:13 -0800287 if rcv_port_number == None:
288 self.logger.debug("Out of packets on all ports")
Rich Lanedb9d8662012-07-26 18:04:24 -0700289 break
290
Rich Lanee1b8da92012-12-26 22:47:13 -0800291 queue = self.packet_queues[rcv_port_number]
292
293 if len(queue) == 0:
294 self.logger.debug("Out of packets on port %d", rcv_port_number)
295 break
296
297 pkt, time = queue.pop(0)
298 yield (rcv_port_number, pkt, time)
Rich Lanedb9d8662012-07-26 18:04:24 -0700299
Rich Lane8806bc42012-07-26 19:18:37 -0700300 def poll(self, port_number=None, timeout=-1, exp_pkt=None):
Dan Talaycoe226eb12010-02-18 23:06:30 -0800301 """
302 Poll one or all dataplane ports for a packet
303
304 If port_number is given, get the oldest packet from that port.
305 Otherwise, find the port with the oldest packet and return
306 that packet.
Dan Talayco1729fdb2012-05-03 09:35:56 -0700307
308 If exp_pkt is true, discard all packets until that one is found
309
Dan Talaycoe226eb12010-02-18 23:06:30 -0800310 @param port_number If set, get packet from this port
Dan Talayco11c26e72010-03-07 22:03:57 -0800311 @param timeout If positive and no packet is available, block
Dan Talaycoe226eb12010-02-18 23:06:30 -0800312 until a packet is received or for this many seconds
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700313 @param exp_pkt If not None, look for this packet and ignore any
Dan Talayco1729fdb2012-05-03 09:35:56 -0700314 others received. Note that if port_number is None, all packets
315 from all ports will be discarded until the exp_pkt is found
Dan Talaycoe226eb12010-02-18 23:06:30 -0800316 @return The triple port_number, packet, pkt_time where packet
317 is received from port_number at time pkt_time. If a timeout
318 occurs, return None, None, None
319 """
320
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700321 if exp_pkt and not port_number:
Dan Talayco1729fdb2012-05-03 09:35:56 -0700322 self.logger.warn("Dataplane poll with exp_pkt but no port number")
323
Rich Lanedb9d8662012-07-26 18:04:24 -0700324 # Retrieve the packet. Returns (port number, packet, time).
325 def grab():
326 self.logger.debug("Grabbing packet")
Rich Lanee1b8da92012-12-26 22:47:13 -0800327 for (rcv_port_number, pkt, time) in self.packets(port_number):
328 self.logger.debug("Checking packet from port %d", rcv_port_number)
Dan Talayco1729fdb2012-05-03 09:35:56 -0700329 if not exp_pkt or match_exp_pkt(exp_pkt, pkt):
Rich Lanee1b8da92012-12-26 22:47:13 -0800330 return (rcv_port_number, pkt, time)
Rich Lanedb9d8662012-07-26 18:04:24 -0700331 self.logger.debug("Did not find packet")
332 return None
Dan Talaycoe226eb12010-02-18 23:06:30 -0800333
Rich Lanee1b8da92012-12-26 22:47:13 -0800334 with self.cvar:
Rich Lanecd97d3d2013-01-07 18:50:06 -0800335 ret = ofutils.timed_wait(self.cvar, grab, timeout=timeout)
Dan Talayco34089522010-02-07 23:07:41 -0800336
Rich Lanedb9d8662012-07-26 18:04:24 -0700337 if ret != None:
Rich Lanee1b8da92012-12-26 22:47:13 -0800338 return ret
Rich Lanedb9d8662012-07-26 18:04:24 -0700339 else:
340 self.logger.debug("Poll time out, no packet from " + str(port_number))
341 return (None, None, None)
Dan Talayco34089522010-02-07 23:07:41 -0800342
Rich Lanee1b8da92012-12-26 22:47:13 -0800343 def kill(self):
Dan Talayco1b3f6902010-02-15 14:14:19 -0800344 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800345 Stop the dataplane thread.
Dan Talayco1b3f6902010-02-15 14:14:19 -0800346 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800347 self.killed = True
348 self.waker.notify()
349 self.join()
350 # Explicitly release ports to ensure we don't run out of sockets
351 # even if someone keeps holding a reference to the dataplane.
352 del self.ports
Rich Lane4d46dbd2012-12-22 19:26:19 -0800353
Rich Lanee1b8da92012-12-26 22:47:13 -0800354 def port_down(self, port_number):
ShreyaPanditacd8e1cf2012-11-28 11:44:42 -0500355 """Brings the specified port down"""
Rich Lanee1b8da92012-12-26 22:47:13 -0800356 self.ports[port_number].down()
ShreyaPanditacd8e1cf2012-11-28 11:44:42 -0500357
Rich Lanee1b8da92012-12-26 22:47:13 -0800358 def port_up(self, port_number):
ShreyaPanditacd8e1cf2012-11-28 11:44:42 -0500359 """Brings the specified port up"""
Rich Lanee1b8da92012-12-26 22:47:13 -0800360 self.ports[port_number].up()
Rich Lane2c7812c2012-12-27 17:52:23 -0800361
362 def flush(self):
363 """
364 Drop any queued packets.
365 """
366 for port_number in self.packet_queues.keys():
367 self.packet_queues[port_number] = []
Rich Lane472aaea2013-08-27 09:27:38 -0700368
369 def start_pcap(self, filename):
370 assert(self.pcap_writer == None)
371 self.pcap_writer = PcapWriter(filename)
372
373 def stop_pcap(self):
374 if self.pcap_writer:
375 self.pcap_writer.close()
376 self.pcap_writer = None