blob: 0cdbaa88655fe2704521c3afedc47e66b2efa78c [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 Lane3e7d28a2015-03-19 00:18:47 -070063 self.socket = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, 0)
Rich Lane1b737262015-03-18 10:13:56 -070064 afpacket.enable_auxdata(self.socket)
Rich Lane3e7d28a2015-03-19 00:18:47 -070065 self.socket.bind((interface_name, self.ETH_P_ALL))
Rich Lanee1b8da92012-12-26 22:47:13 -080066 netutils.set_promisc(self.socket, interface_name)
67 self.socket.settimeout(self.RCV_TIMEOUT)
Dan Talayco1b3f6902010-02-15 14:14:19 -080068
Rich Lanee1b8da92012-12-26 22:47:13 -080069 def __del__(self):
70 if self.socket:
Dan Talayco1b3f6902010-02-15 14:14:19 -080071 self.socket.close()
Dan Talayco1b3f6902010-02-15 14:14:19 -080072
Rich Lanee1b8da92012-12-26 22:47:13 -080073 def fileno(self):
Dan Talayco3087a462010-02-13 14:01:47 -080074 """
Rich Lanee1b8da92012-12-26 22:47:13 -080075 Return an integer file descriptor that can be passed to select(2).
Dan Talayco3087a462010-02-13 14:01:47 -080076 """
Rich Lanee1b8da92012-12-26 22:47:13 -080077 return self.socket.fileno()
Dan Talayco3087a462010-02-13 14:01:47 -080078
Rich Lanee1b8da92012-12-26 22:47:13 -080079 def recv(self):
Dan Talayco3087a462010-02-13 14:01:47 -080080 """
Rich Lanee1b8da92012-12-26 22:47:13 -080081 Receive a packet from this port.
82 @retval (packet data, timestamp)
Dan Talayco3087a462010-02-13 14:01:47 -080083 """
Rich Lane1b737262015-03-18 10:13:56 -070084 pkt = afpacket.recv(self.socket, self.RCV_SIZE_DEFAULT)
Rich Lanee1b8da92012-12-26 22:47:13 -080085 return (pkt, time.time())
Dan Talayco3087a462010-02-13 14:01:47 -080086
87 def send(self, packet):
88 """
Rich Lanee1b8da92012-12-26 22:47:13 -080089 Send a packet out this port.
Dan Talayco3087a462010-02-13 14:01:47 -080090 @param packet The packet data to send to the port
91 @retval The number of bytes sent
92 """
93 return self.socket.send(packet)
94
Rich Lanee1b8da92012-12-26 22:47:13 -080095 def down(self):
Dan Talayco3087a462010-02-13 14:01:47 -080096 """
Rich Lanee1b8da92012-12-26 22:47:13 -080097 Bring the physical link down.
Dan Talayco3087a462010-02-13 14:01:47 -080098 """
Rich Lanee1b8da92012-12-26 22:47:13 -080099 os.system("ifconfig down %s" % self.interface_name)
Dan Talayco3087a462010-02-13 14:01:47 -0800100
Rich Lanee1b8da92012-12-26 22:47:13 -0800101 def up(self):
ShreyaPanditacd8e1cf2012-11-28 11:44:42 -0500102 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800103 Bring the physical link up.
ShreyaPanditacd8e1cf2012-11-28 11:44:42 -0500104 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800105 os.system("ifconfig up %s" % self.interface_name)
ShreyaPanditacd8e1cf2012-11-28 11:44:42 -0500106
107
Rich Lanee1b8da92012-12-26 22:47:13 -0800108class DataPlanePortPcap:
Rich Laneb42a31c2012-10-05 17:54:17 -0700109 """
Rich Lane2e860482015-03-18 10:22:43 -0700110 Alternate port implementation using libpcap. This is used by non-Linux
111 operating systems.
Rich Laneb42a31c2012-10-05 17:54:17 -0700112 """
113
Rich Lanee1b8da92012-12-26 22:47:13 -0800114 def __init__(self, interface_name, port_number):
Rich Laneb42a31c2012-10-05 17:54:17 -0700115 self.pcap = pcap.pcap(interface_name)
116 self.pcap.setnonblock()
Rich Lanee1b8da92012-12-26 22:47:13 -0800117
118 def fileno(self):
Rich Laneb42a31c2012-10-05 17:54:17 -0700119 return self.pcap.fileno()
120
Rich Lanee1b8da92012-12-26 22:47:13 -0800121 def recv(self):
122 (timestamp, pkt) = next(self.pcap)
Rich Lane0415fd72014-02-25 22:04:17 -0800123 return (pkt[:], timestamp)
Rich Laneb42a31c2012-10-05 17:54:17 -0700124
125 def send(self, packet):
Rich Lane0306fd32015-03-11 15:52:21 -0700126 if hasattr(self.pcap, "inject"):
127 return self.pcap.inject(packet, len(packet))
128 else:
129 return self.pcap.sendpacket(packet)
Dan Talayco34089522010-02-07 23:07:41 -0800130
Rich Lanee1b8da92012-12-26 22:47:13 -0800131 def down(self):
132 pass
133
134 def up(self):
135 pass
136
137class DataPlane(Thread):
Dan Talayco34089522010-02-07 23:07:41 -0800138 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800139 This class provides methods to send and receive packets on the dataplane.
140 It uses the DataPlanePort class, or an alternative implementation of that
141 interface, to do IO on a particular port. A background thread is used to
142 read packets from the dataplane ports and enqueue them to be read by the
143 test. The kill() method must be called to shutdown this thread.
Dan Talayco34089522010-02-07 23:07:41 -0800144 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800145
146 MAX_QUEUE_LEN = 100
147
Jeffrey Townsend0e8b0922012-07-11 11:37:46 -0700148 def __init__(self, config=None):
Rich Lanee1b8da92012-12-26 22:47:13 -0800149 Thread.__init__(self)
150
151 # dict from port number to port object
152 self.ports = {}
153
154 # dict from port number to list of (timestamp, packet)
155 self.packet_queues = {}
156
157 # cvar serves double duty as a regular top level lock and
Dan Talaycoe226eb12010-02-18 23:06:30 -0800158 # as a condition variable
Rich Lanee1b8da92012-12-26 22:47:13 -0800159 self.cvar = Condition()
160
161 # Used to wake up the event loop from another thread
Rich Lanecd97d3d2013-01-07 18:50:06 -0800162 self.waker = ofutils.EventDescriptor()
Rich Lanee1b8da92012-12-26 22:47:13 -0800163 self.killed = False
Dan Talaycoe226eb12010-02-18 23:06:30 -0800164
Dan Talayco48370102010-03-03 15:17:33 -0800165 self.logger = logging.getLogger("dataplane")
Rich Lane472aaea2013-08-27 09:27:38 -0700166 self.pcap_writer = None
Dan Talayco34089522010-02-07 23:07:41 -0800167
Jeffrey Townsend0e8b0922012-07-11 11:37:46 -0700168 if config is None:
169 self.config = {}
170 else:
171 self.config = config;
172
173 ############################################################
174 #
Jeffrey Townsend0e8b0922012-07-11 11:37:46 -0700175 # The platform/config can provide a custom DataPlanePort class
176 # here if you have a custom implementation with different
177 # behavior.
178 #
179 # Set config.dataplane.portclass = MyDataPlanePortClass
180 # where MyDataPlanePortClass has the same interface as the class
181 # DataPlanePort defined here.
182 #
Rich Lanefb718302012-12-26 21:02:31 -0800183 if "dataplane" in self.config and "portclass" in self.config["dataplane"]:
184 self.dppclass = self.config["dataplane"]["portclass"]
Rich Lane2e860482015-03-18 10:22:43 -0700185 elif "linux" in sys.platform:
186 self.dppclass = DataPlanePortLinux
Rich Lanefb718302012-12-26 21:02:31 -0800187 else:
Rich Lane2e860482015-03-18 10:22:43 -0700188 self.dppclass = DataPlanePortPcap
Jeffrey Townsend0e8b0922012-07-11 11:37:46 -0700189
Rich Lanee1b8da92012-12-26 22:47:13 -0800190 self.start()
191
192 def run(self):
193 """
194 Activity function for class
195 """
196 while not self.killed:
197 sockets = [self.waker] + self.ports.values()
198 try:
199 sel_in, sel_out, sel_err = select.select(sockets, [], [], 1)
200 except:
201 print sys.exc_info()
202 self.logger.error("Select error, exiting")
203 break
204
205 with self.cvar:
206 for port in sel_in:
207 if port == self.waker:
208 self.waker.wait()
209 continue
210 else:
211 # Enqueue packet
212 pkt, timestamp = port.recv()
213 port_number = port._port_number
214 self.logger.debug("Pkt len %d in on port %d",
215 len(pkt), port_number)
Rich Lane472aaea2013-08-27 09:27:38 -0700216 if self.pcap_writer:
217 self.pcap_writer.write(pkt, timestamp, port_number)
Rich Lanee1b8da92012-12-26 22:47:13 -0800218 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 Lane472aaea2013-08-27 09:27:38 -0700249 if self.pcap_writer:
250 self.pcap_writer.write(packet, time.time(), port_number)
Rich Lanee1b8da92012-12-26 22:47:13 -0800251 bytes = self.ports[port_number].send(packet)
Dan Talayco34089522010-02-07 23:07:41 -0800252 if bytes != len(packet):
Dan Talayco48370102010-03-03 15:17:33 -0800253 self.logger.error("Unhandled send error, length mismatch %d != %d" %
Dan Talayco1b3f6902010-02-15 14:14:19 -0800254 (bytes, len(packet)))
Dan Talayco34089522010-02-07 23:07:41 -0800255 return bytes
256
Rich Lanee1b8da92012-12-26 22:47:13 -0800257 def oldest_port_number(self):
258 """
259 Returns the port number with the oldest packet, or
260 None if no packets are queued.
261 """
262 min_port_number = None
Rich Lanedb9d8662012-07-26 18:04:24 -0700263 min_time = float('inf')
Rich Lanee1b8da92012-12-26 22:47:13 -0800264 for (port_number, queue) in self.packet_queues.items():
265 if queue and queue[0][1] < min_time:
266 min_time = queue[0][1]
267 min_port_number = port_number
268 return min_port_number
Rich Lanedb9d8662012-07-26 18:04:24 -0700269
270 # Dequeues and yields packets in the order they were received.
Rich Lanee1b8da92012-12-26 22:47:13 -0800271 # Yields (port number, packet, received time).
Rich Lanedb9d8662012-07-26 18:04:24 -0700272 # If port_number is not specified yields packets from all ports.
273 def packets(self, port_number=None):
274 while True:
Rich Lanee1b8da92012-12-26 22:47:13 -0800275 rcv_port_number = port_number or self.oldest_port_number()
Rich Lanedb9d8662012-07-26 18:04:24 -0700276
Rich Lanee1b8da92012-12-26 22:47:13 -0800277 if rcv_port_number == None:
278 self.logger.debug("Out of packets on all ports")
Rich Lanedb9d8662012-07-26 18:04:24 -0700279 break
280
Rich Lanee1b8da92012-12-26 22:47:13 -0800281 queue = self.packet_queues[rcv_port_number]
282
283 if len(queue) == 0:
284 self.logger.debug("Out of packets on port %d", rcv_port_number)
285 break
286
287 pkt, time = queue.pop(0)
288 yield (rcv_port_number, pkt, time)
Rich Lanedb9d8662012-07-26 18:04:24 -0700289
Rich Lane8806bc42012-07-26 19:18:37 -0700290 def poll(self, port_number=None, timeout=-1, exp_pkt=None):
Dan Talaycoe226eb12010-02-18 23:06:30 -0800291 """
292 Poll one or all dataplane ports for a packet
293
294 If port_number is given, get the oldest packet from that port.
295 Otherwise, find the port with the oldest packet and return
296 that packet.
Dan Talayco1729fdb2012-05-03 09:35:56 -0700297
298 If exp_pkt is true, discard all packets until that one is found
299
Dan Talaycoe226eb12010-02-18 23:06:30 -0800300 @param port_number If set, get packet from this port
Dan Talayco11c26e72010-03-07 22:03:57 -0800301 @param timeout If positive and no packet is available, block
Dan Talaycoe226eb12010-02-18 23:06:30 -0800302 until a packet is received or for this many seconds
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700303 @param exp_pkt If not None, look for this packet and ignore any
Dan Talayco1729fdb2012-05-03 09:35:56 -0700304 others received. Note that if port_number is None, all packets
305 from all ports will be discarded until the exp_pkt is found
Dan Talaycoe226eb12010-02-18 23:06:30 -0800306 @return The triple port_number, packet, pkt_time where packet
307 is received from port_number at time pkt_time. If a timeout
308 occurs, return None, None, None
309 """
310
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700311 if exp_pkt and not port_number:
Dan Talayco1729fdb2012-05-03 09:35:56 -0700312 self.logger.warn("Dataplane poll with exp_pkt but no port number")
313
Rich Lanedb9d8662012-07-26 18:04:24 -0700314 # Retrieve the packet. Returns (port number, packet, time).
315 def grab():
316 self.logger.debug("Grabbing packet")
Rich Lanee1b8da92012-12-26 22:47:13 -0800317 for (rcv_port_number, pkt, time) in self.packets(port_number):
318 self.logger.debug("Checking packet from port %d", rcv_port_number)
Dan Talayco1729fdb2012-05-03 09:35:56 -0700319 if not exp_pkt or match_exp_pkt(exp_pkt, pkt):
Rich Lanee1b8da92012-12-26 22:47:13 -0800320 return (rcv_port_number, pkt, time)
Rich Lanedb9d8662012-07-26 18:04:24 -0700321 self.logger.debug("Did not find packet")
322 return None
Dan Talaycoe226eb12010-02-18 23:06:30 -0800323
Rich Lanee1b8da92012-12-26 22:47:13 -0800324 with self.cvar:
Rich Lanecd97d3d2013-01-07 18:50:06 -0800325 ret = ofutils.timed_wait(self.cvar, grab, timeout=timeout)
Dan Talayco34089522010-02-07 23:07:41 -0800326
Rich Lanedb9d8662012-07-26 18:04:24 -0700327 if ret != None:
Rich Lanee1b8da92012-12-26 22:47:13 -0800328 return ret
Rich Lanedb9d8662012-07-26 18:04:24 -0700329 else:
330 self.logger.debug("Poll time out, no packet from " + str(port_number))
331 return (None, None, None)
Dan Talayco34089522010-02-07 23:07:41 -0800332
Rich Lanee1b8da92012-12-26 22:47:13 -0800333 def kill(self):
Dan Talayco1b3f6902010-02-15 14:14:19 -0800334 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800335 Stop the dataplane thread.
Dan Talayco1b3f6902010-02-15 14:14:19 -0800336 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800337 self.killed = True
338 self.waker.notify()
339 self.join()
340 # Explicitly release ports to ensure we don't run out of sockets
341 # even if someone keeps holding a reference to the dataplane.
342 del self.ports
Rich Lane4d46dbd2012-12-22 19:26:19 -0800343
Rich Lanee1b8da92012-12-26 22:47:13 -0800344 def port_down(self, port_number):
ShreyaPanditacd8e1cf2012-11-28 11:44:42 -0500345 """Brings the specified port down"""
Rich Lanee1b8da92012-12-26 22:47:13 -0800346 self.ports[port_number].down()
ShreyaPanditacd8e1cf2012-11-28 11:44:42 -0500347
Rich Lanee1b8da92012-12-26 22:47:13 -0800348 def port_up(self, port_number):
ShreyaPanditacd8e1cf2012-11-28 11:44:42 -0500349 """Brings the specified port up"""
Rich Lanee1b8da92012-12-26 22:47:13 -0800350 self.ports[port_number].up()
Rich Lane2c7812c2012-12-27 17:52:23 -0800351
352 def flush(self):
353 """
354 Drop any queued packets.
355 """
356 for port_number in self.packet_queues.keys():
357 self.packet_queues[port_number] = []
Rich Lane472aaea2013-08-27 09:27:38 -0700358
359 def start_pcap(self, filename):
360 assert(self.pcap_writer == None)
361 self.pcap_writer = PcapWriter(filename)
362
363 def stop_pcap(self):
364 if self.pcap_writer:
365 self.pcap_writer.close()
366 self.pcap_writer = None