blob: 81e5929fc0848a4753843db15182789dae9f6f30 [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
Rich Lane2e860482015-03-18 10:22:43 -070032else:
Rich Laneb42a31c2012-10-05 17:54:17 -070033 import pcap
Rich Laneb42a31c2012-10-05 17:54:17 -070034
Ed Swierk506614a2012-03-29 08:16:59 -070035def match_exp_pkt(exp_pkt, pkt):
36 """
37 Compare the string value of pkt with the string value of exp_pkt,
38 and return True iff they are identical. If the length of exp_pkt is
39 less than the minimum Ethernet frame size (60 bytes), then padding
40 bytes in pkt are ignored.
41 """
42 e = str(exp_pkt)
43 p = str(pkt)
44 if len(e) < 60:
45 p = p[:len(e)]
46 return e == p
47
Jeffrey Townsend0e8b0922012-07-11 11:37:46 -070048
Rich Lane2e860482015-03-18 10:22:43 -070049class DataPlanePortLinux:
Dan Talayco3087a462010-02-13 14:01:47 -080050 """
Rich Lanee1b8da92012-12-26 22:47:13 -080051 Uses raw sockets to capture and send packets on a network interface.
Dan Talayco3087a462010-02-13 14:01:47 -080052 """
53
Rich Lanee1b8da92012-12-26 22:47:13 -080054 RCV_SIZE_DEFAULT = 4096
55 ETH_P_ALL = 0x03
56 RCV_TIMEOUT = 10000
57
58 def __init__(self, interface_name, port_number):
Dan Talayco3087a462010-02-13 14:01:47 -080059 """
Dan Talayco3087a462010-02-13 14:01:47 -080060 @param interface_name The name of the physical interface like eth1
Dan Talayco3087a462010-02-13 14:01:47 -080061 """
Dan Talayco3087a462010-02-13 14:01:47 -080062 self.interface_name = interface_name
Rich Lanee1b8da92012-12-26 22:47:13 -080063 self.socket = socket.socket(socket.AF_PACKET, socket.SOCK_RAW,
64 socket.htons(self.ETH_P_ALL))
Rich Lane1b737262015-03-18 10:13:56 -070065 afpacket.enable_auxdata(self.socket)
Rich Lanee1b8da92012-12-26 22:47:13 -080066 self.socket.bind((interface_name, 0))
67 netutils.set_promisc(self.socket, interface_name)
68 self.socket.settimeout(self.RCV_TIMEOUT)
Dan Talayco1b3f6902010-02-15 14:14:19 -080069
Rich Lanee1b8da92012-12-26 22:47:13 -080070 def __del__(self):
71 if self.socket:
Dan Talayco1b3f6902010-02-15 14:14:19 -080072 self.socket.close()
Dan Talayco1b3f6902010-02-15 14:14:19 -080073
Rich Lanee1b8da92012-12-26 22:47:13 -080074 def fileno(self):
Dan Talayco3087a462010-02-13 14:01:47 -080075 """
Rich Lanee1b8da92012-12-26 22:47:13 -080076 Return an integer file descriptor that can be passed to select(2).
Dan Talayco3087a462010-02-13 14:01:47 -080077 """
Rich Lanee1b8da92012-12-26 22:47:13 -080078 return self.socket.fileno()
Dan Talayco3087a462010-02-13 14:01:47 -080079
Rich Lanee1b8da92012-12-26 22:47:13 -080080 def recv(self):
Dan Talayco3087a462010-02-13 14:01:47 -080081 """
Rich Lanee1b8da92012-12-26 22:47:13 -080082 Receive a packet from this port.
83 @retval (packet data, timestamp)
Dan Talayco3087a462010-02-13 14:01:47 -080084 """
Rich Lane1b737262015-03-18 10:13:56 -070085 pkt = afpacket.recv(self.socket, self.RCV_SIZE_DEFAULT)
Rich Lanee1b8da92012-12-26 22:47:13 -080086 return (pkt, time.time())
Dan Talayco3087a462010-02-13 14:01:47 -080087
88 def send(self, packet):
89 """
Rich Lanee1b8da92012-12-26 22:47:13 -080090 Send a packet out this port.
Dan Talayco3087a462010-02-13 14:01:47 -080091 @param packet The packet data to send to the port
92 @retval The number of bytes sent
93 """
94 return self.socket.send(packet)
95
Rich Lanee1b8da92012-12-26 22:47:13 -080096 def down(self):
Dan Talayco3087a462010-02-13 14:01:47 -080097 """
Rich Lanee1b8da92012-12-26 22:47:13 -080098 Bring the physical link down.
Dan Talayco3087a462010-02-13 14:01:47 -080099 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800100 os.system("ifconfig down %s" % self.interface_name)
Dan Talayco3087a462010-02-13 14:01:47 -0800101
Rich Lanee1b8da92012-12-26 22:47:13 -0800102 def up(self):
ShreyaPanditacd8e1cf2012-11-28 11:44:42 -0500103 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800104 Bring the physical link up.
ShreyaPanditacd8e1cf2012-11-28 11:44:42 -0500105 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800106 os.system("ifconfig up %s" % self.interface_name)
ShreyaPanditacd8e1cf2012-11-28 11:44:42 -0500107
108
Rich Lanee1b8da92012-12-26 22:47:13 -0800109class DataPlanePortPcap:
Rich Laneb42a31c2012-10-05 17:54:17 -0700110 """
Rich Lane2e860482015-03-18 10:22:43 -0700111 Alternate port implementation using libpcap. This is used by non-Linux
112 operating systems.
Rich Laneb42a31c2012-10-05 17:54:17 -0700113 """
114
Rich Lanee1b8da92012-12-26 22:47:13 -0800115 def __init__(self, interface_name, port_number):
Rich Laneb42a31c2012-10-05 17:54:17 -0700116 self.pcap = pcap.pcap(interface_name)
117 self.pcap.setnonblock()
Rich Lanee1b8da92012-12-26 22:47:13 -0800118
119 def fileno(self):
Rich Laneb42a31c2012-10-05 17:54:17 -0700120 return self.pcap.fileno()
121
Rich Lanee1b8da92012-12-26 22:47:13 -0800122 def recv(self):
123 (timestamp, pkt) = next(self.pcap)
Rich Lane0415fd72014-02-25 22:04:17 -0800124 return (pkt[:], timestamp)
Rich Laneb42a31c2012-10-05 17:54:17 -0700125
126 def send(self, packet):
Rich Lane0306fd32015-03-11 15:52:21 -0700127 if hasattr(self.pcap, "inject"):
128 return self.pcap.inject(packet, len(packet))
129 else:
130 return self.pcap.sendpacket(packet)
Dan Talayco34089522010-02-07 23:07:41 -0800131
Rich Lanee1b8da92012-12-26 22:47:13 -0800132 def down(self):
133 pass
134
135 def up(self):
136 pass
137
138class DataPlane(Thread):
Dan Talayco34089522010-02-07 23:07:41 -0800139 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800140 This class provides methods to send and receive packets on the dataplane.
141 It uses the DataPlanePort class, or an alternative implementation of that
142 interface, to do IO on a particular port. A background thread is used to
143 read packets from the dataplane ports and enqueue them to be read by the
144 test. The kill() method must be called to shutdown this thread.
Dan Talayco34089522010-02-07 23:07:41 -0800145 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800146
147 MAX_QUEUE_LEN = 100
148
Jeffrey Townsend0e8b0922012-07-11 11:37:46 -0700149 def __init__(self, config=None):
Rich Lanee1b8da92012-12-26 22:47:13 -0800150 Thread.__init__(self)
151
152 # dict from port number to port object
153 self.ports = {}
154
155 # dict from port number to list of (timestamp, packet)
156 self.packet_queues = {}
157
158 # cvar serves double duty as a regular top level lock and
Dan Talaycoe226eb12010-02-18 23:06:30 -0800159 # as a condition variable
Rich Lanee1b8da92012-12-26 22:47:13 -0800160 self.cvar = Condition()
161
162 # Used to wake up the event loop from another thread
Rich Lanecd97d3d2013-01-07 18:50:06 -0800163 self.waker = ofutils.EventDescriptor()
Rich Lanee1b8da92012-12-26 22:47:13 -0800164 self.killed = False
Dan Talaycoe226eb12010-02-18 23:06:30 -0800165
Dan Talayco48370102010-03-03 15:17:33 -0800166 self.logger = logging.getLogger("dataplane")
Rich Lane472aaea2013-08-27 09:27:38 -0700167 self.pcap_writer = None
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"]
Rich Lane2e860482015-03-18 10:22:43 -0700186 elif "linux" in sys.platform:
187 self.dppclass = DataPlanePortLinux
Rich Lanefb718302012-12-26 21:02:31 -0800188 else:
Rich Lane2e860482015-03-18 10:22:43 -0700189 self.dppclass = DataPlanePortPcap
Jeffrey Townsend0e8b0922012-07-11 11:37:46 -0700190
Rich Lanee1b8da92012-12-26 22:47:13 -0800191 self.start()
192
193 def run(self):
194 """
195 Activity function for class
196 """
197 while not self.killed:
198 sockets = [self.waker] + self.ports.values()
199 try:
200 sel_in, sel_out, sel_err = select.select(sockets, [], [], 1)
201 except:
202 print sys.exc_info()
203 self.logger.error("Select error, exiting")
204 break
205
206 with self.cvar:
207 for port in sel_in:
208 if port == self.waker:
209 self.waker.wait()
210 continue
211 else:
212 # Enqueue packet
213 pkt, timestamp = port.recv()
214 port_number = port._port_number
215 self.logger.debug("Pkt len %d in on port %d",
216 len(pkt), port_number)
Rich Lane472aaea2013-08-27 09:27:38 -0700217 if self.pcap_writer:
218 self.pcap_writer.write(pkt, timestamp, port_number)
Rich Lanee1b8da92012-12-26 22:47:13 -0800219 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 Lane472aaea2013-08-27 09:27:38 -0700250 if self.pcap_writer:
251 self.pcap_writer.write(packet, time.time(), port_number)
Rich Lanee1b8da92012-12-26 22:47:13 -0800252 bytes = self.ports[port_number].send(packet)
Dan Talayco34089522010-02-07 23:07:41 -0800253 if bytes != len(packet):
Dan Talayco48370102010-03-03 15:17:33 -0800254 self.logger.error("Unhandled send error, length mismatch %d != %d" %
Dan Talayco1b3f6902010-02-15 14:14:19 -0800255 (bytes, len(packet)))
Dan Talayco34089522010-02-07 23:07:41 -0800256 return bytes
257
Rich Lanee1b8da92012-12-26 22:47:13 -0800258 def oldest_port_number(self):
259 """
260 Returns the port number with the oldest packet, or
261 None if no packets are queued.
262 """
263 min_port_number = None
Rich Lanedb9d8662012-07-26 18:04:24 -0700264 min_time = float('inf')
Rich Lanee1b8da92012-12-26 22:47:13 -0800265 for (port_number, queue) in self.packet_queues.items():
266 if queue and queue[0][1] < min_time:
267 min_time = queue[0][1]
268 min_port_number = port_number
269 return min_port_number
Rich Lanedb9d8662012-07-26 18:04:24 -0700270
271 # Dequeues and yields packets in the order they were received.
Rich Lanee1b8da92012-12-26 22:47:13 -0800272 # Yields (port number, packet, received time).
Rich Lanedb9d8662012-07-26 18:04:24 -0700273 # If port_number is not specified yields packets from all ports.
274 def packets(self, port_number=None):
275 while True:
Rich Lanee1b8da92012-12-26 22:47:13 -0800276 rcv_port_number = port_number or self.oldest_port_number()
Rich Lanedb9d8662012-07-26 18:04:24 -0700277
Rich Lanee1b8da92012-12-26 22:47:13 -0800278 if rcv_port_number == None:
279 self.logger.debug("Out of packets on all ports")
Rich Lanedb9d8662012-07-26 18:04:24 -0700280 break
281
Rich Lanee1b8da92012-12-26 22:47:13 -0800282 queue = self.packet_queues[rcv_port_number]
283
284 if len(queue) == 0:
285 self.logger.debug("Out of packets on port %d", rcv_port_number)
286 break
287
288 pkt, time = queue.pop(0)
289 yield (rcv_port_number, pkt, time)
Rich Lanedb9d8662012-07-26 18:04:24 -0700290
Rich Lane8806bc42012-07-26 19:18:37 -0700291 def poll(self, port_number=None, timeout=-1, exp_pkt=None):
Dan Talaycoe226eb12010-02-18 23:06:30 -0800292 """
293 Poll one or all dataplane ports for a packet
294
295 If port_number is given, get the oldest packet from that port.
296 Otherwise, find the port with the oldest packet and return
297 that packet.
Dan Talayco1729fdb2012-05-03 09:35:56 -0700298
299 If exp_pkt is true, discard all packets until that one is found
300
Dan Talaycoe226eb12010-02-18 23:06:30 -0800301 @param port_number If set, get packet from this port
Dan Talayco11c26e72010-03-07 22:03:57 -0800302 @param timeout If positive and no packet is available, block
Dan Talaycoe226eb12010-02-18 23:06:30 -0800303 until a packet is received or for this many seconds
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700304 @param exp_pkt If not None, look for this packet and ignore any
Dan Talayco1729fdb2012-05-03 09:35:56 -0700305 others received. Note that if port_number is None, all packets
306 from all ports will be discarded until the exp_pkt is found
Dan Talaycoe226eb12010-02-18 23:06:30 -0800307 @return The triple port_number, packet, pkt_time where packet
308 is received from port_number at time pkt_time. If a timeout
309 occurs, return None, None, None
310 """
311
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700312 if exp_pkt and not port_number:
Dan Talayco1729fdb2012-05-03 09:35:56 -0700313 self.logger.warn("Dataplane poll with exp_pkt but no port number")
314
Rich Lanedb9d8662012-07-26 18:04:24 -0700315 # Retrieve the packet. Returns (port number, packet, time).
316 def grab():
317 self.logger.debug("Grabbing packet")
Rich Lanee1b8da92012-12-26 22:47:13 -0800318 for (rcv_port_number, pkt, time) in self.packets(port_number):
319 self.logger.debug("Checking packet from port %d", rcv_port_number)
Dan Talayco1729fdb2012-05-03 09:35:56 -0700320 if not exp_pkt or match_exp_pkt(exp_pkt, pkt):
Rich Lanee1b8da92012-12-26 22:47:13 -0800321 return (rcv_port_number, pkt, time)
Rich Lanedb9d8662012-07-26 18:04:24 -0700322 self.logger.debug("Did not find packet")
323 return None
Dan Talaycoe226eb12010-02-18 23:06:30 -0800324
Rich Lanee1b8da92012-12-26 22:47:13 -0800325 with self.cvar:
Rich Lanecd97d3d2013-01-07 18:50:06 -0800326 ret = ofutils.timed_wait(self.cvar, grab, timeout=timeout)
Dan Talayco34089522010-02-07 23:07:41 -0800327
Rich Lanedb9d8662012-07-26 18:04:24 -0700328 if ret != None:
Rich Lanee1b8da92012-12-26 22:47:13 -0800329 return ret
Rich Lanedb9d8662012-07-26 18:04:24 -0700330 else:
331 self.logger.debug("Poll time out, no packet from " + str(port_number))
332 return (None, None, None)
Dan Talayco34089522010-02-07 23:07:41 -0800333
Rich Lanee1b8da92012-12-26 22:47:13 -0800334 def kill(self):
Dan Talayco1b3f6902010-02-15 14:14:19 -0800335 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800336 Stop the dataplane thread.
Dan Talayco1b3f6902010-02-15 14:14:19 -0800337 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800338 self.killed = True
339 self.waker.notify()
340 self.join()
341 # Explicitly release ports to ensure we don't run out of sockets
342 # even if someone keeps holding a reference to the dataplane.
343 del self.ports
Rich Lane4d46dbd2012-12-22 19:26:19 -0800344
Rich Lanee1b8da92012-12-26 22:47:13 -0800345 def port_down(self, port_number):
ShreyaPanditacd8e1cf2012-11-28 11:44:42 -0500346 """Brings the specified port down"""
Rich Lanee1b8da92012-12-26 22:47:13 -0800347 self.ports[port_number].down()
ShreyaPanditacd8e1cf2012-11-28 11:44:42 -0500348
Rich Lanee1b8da92012-12-26 22:47:13 -0800349 def port_up(self, port_number):
ShreyaPanditacd8e1cf2012-11-28 11:44:42 -0500350 """Brings the specified port up"""
Rich Lanee1b8da92012-12-26 22:47:13 -0800351 self.ports[port_number].up()
Rich Lane2c7812c2012-12-27 17:52:23 -0800352
353 def flush(self):
354 """
355 Drop any queued packets.
356 """
357 for port_number in self.packet_queues.keys():
358 self.packet_queues[port_number] = []
Rich Lane472aaea2013-08-27 09:27:38 -0700359
360 def start_pcap(self, filename):
361 assert(self.pcap_writer == None)
362 self.pcap_writer = PcapWriter(filename)
363
364 def stop_pcap(self):
365 if self.pcap_writer:
366 self.pcap_writer.close()
367 self.pcap_writer = None