blob: 232aadf65a1344e0a4bd1580f9a4532331bebaa9 [file] [log] [blame]
Matteo Scandoloa229eca2017-08-08 13:05:28 -07001
2# Copyright 2017-present Open Networking Foundation
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16
Dan Talayco34089522010-02-07 23:07:41 -080017"""
18OpenFlow Test Framework
19
Dan Talayco3087a462010-02-13 14:01:47 -080020DataPlane and DataPlanePort classes
Dan Talayco34089522010-02-07 23:07:41 -080021
22Provide the interface to the control the set of ports being used
23to stimulate the switch under test.
24
25See the class dataplaneport for more details. This class wraps
Dan Talaycoe226eb12010-02-18 23:06:30 -080026a set of those objects allowing general calls and parsing
Dan Talayco34089522010-02-07 23:07:41 -080027configuration.
28
Dan Talayco3087a462010-02-13 14:01:47 -080029@todo Add "filters" for matching packets. Actions supported
30for filters should include a callback or a counter
Dan Talayco34089522010-02-07 23:07:41 -080031"""
32
Dan Talayco3087a462010-02-13 14:01:47 -080033import sys
34import os
35import socket
36import time
Rich Lanecd97d3d2013-01-07 18:50:06 -080037import select
38import logging
Dan Talayco3087a462010-02-13 14:01:47 -080039from threading import Thread
40from threading import Lock
Dan Talaycoe226eb12010-02-18 23:06:30 -080041from threading import Condition
Rich Lanecd97d3d2013-01-07 18:50:06 -080042import ofutils
43import netutils
Rich Lane472aaea2013-08-27 09:27:38 -070044from pcap_writer import PcapWriter
Dan Talayco3087a462010-02-13 14:01:47 -080045
Rich Lane1b737262015-03-18 10:13:56 -070046if "linux" in sys.platform:
47 import afpacket
Rich Lane2e860482015-03-18 10:22:43 -070048else:
Rich Laneb42a31c2012-10-05 17:54:17 -070049 import pcap
Rich Laneb42a31c2012-10-05 17:54:17 -070050
macauleydf8f5fc2015-07-16 17:27:55 +080051def match_exp_pkt(self, exp_pkt, pkt):
Ed Swierk506614a2012-03-29 08:16:59 -070052 """
53 Compare the string value of pkt with the string value of exp_pkt,
54 and return True iff they are identical. If the length of exp_pkt is
55 less than the minimum Ethernet frame size (60 bytes), then padding
56 bytes in pkt are ignored.
57 """
58 e = str(exp_pkt)
59 p = str(pkt)
60 if len(e) < 60:
61 p = p[:len(e)]
macauleydf8f5fc2015-07-16 17:27:55 +080062
63 #return e == p
64 #some nic card have capature problem, will have more bytes capatured.
65 if pkt.find(exp_pkt) >=0:
66 return True
Charles Chanc85f1562018-01-18 15:44:29 -080067 else:
macauleydf8f5fc2015-07-16 17:27:55 +080068 if self.config["dump_packet"]:
Charles Chanc85f1562018-01-18 15:44:29 -080069 self.logger.debug("rx pkt ->"+(" ".join("{:02x}".format(ord(c)) for c in pkt)))
70 self.logger.debug("expect pkt->"+(" ".join("{:02x}".format(ord(c)) for c in exp_pkt)))
macauleydf8f5fc2015-07-16 17:27:55 +080071
72 return False
Ed Swierk506614a2012-03-29 08:16:59 -070073
Jeffrey Townsend0e8b0922012-07-11 11:37:46 -070074
Rich Lane2e860482015-03-18 10:22:43 -070075class DataPlanePortLinux:
Dan Talayco3087a462010-02-13 14:01:47 -080076 """
Rich Lanee1b8da92012-12-26 22:47:13 -080077 Uses raw sockets to capture and send packets on a network interface.
Dan Talayco3087a462010-02-13 14:01:47 -080078 """
79
Rich Lanee1b8da92012-12-26 22:47:13 -080080 RCV_SIZE_DEFAULT = 4096
81 ETH_P_ALL = 0x03
82 RCV_TIMEOUT = 10000
83
84 def __init__(self, interface_name, port_number):
Dan Talayco3087a462010-02-13 14:01:47 -080085 """
Dan Talayco3087a462010-02-13 14:01:47 -080086 @param interface_name The name of the physical interface like eth1
Dan Talayco3087a462010-02-13 14:01:47 -080087 """
Dan Talayco3087a462010-02-13 14:01:47 -080088 self.interface_name = interface_name
Rich Lane3e7d28a2015-03-19 00:18:47 -070089 self.socket = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, 0)
Rich Lane1b737262015-03-18 10:13:56 -070090 afpacket.enable_auxdata(self.socket)
Rich Lane3e7d28a2015-03-19 00:18:47 -070091 self.socket.bind((interface_name, self.ETH_P_ALL))
Rich Lanee1b8da92012-12-26 22:47:13 -080092 netutils.set_promisc(self.socket, interface_name)
93 self.socket.settimeout(self.RCV_TIMEOUT)
Dan Talayco1b3f6902010-02-15 14:14:19 -080094
Rich Lanee1b8da92012-12-26 22:47:13 -080095 def __del__(self):
96 if self.socket:
Dan Talayco1b3f6902010-02-15 14:14:19 -080097 self.socket.close()
Dan Talayco1b3f6902010-02-15 14:14:19 -080098
Rich Lanee1b8da92012-12-26 22:47:13 -080099 def fileno(self):
Dan Talayco3087a462010-02-13 14:01:47 -0800100 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800101 Return an integer file descriptor that can be passed to select(2).
Dan Talayco3087a462010-02-13 14:01:47 -0800102 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800103 return self.socket.fileno()
Dan Talayco3087a462010-02-13 14:01:47 -0800104
Rich Lanee1b8da92012-12-26 22:47:13 -0800105 def recv(self):
Dan Talayco3087a462010-02-13 14:01:47 -0800106 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800107 Receive a packet from this port.
108 @retval (packet data, timestamp)
Dan Talayco3087a462010-02-13 14:01:47 -0800109 """
Rich Lane1b737262015-03-18 10:13:56 -0700110 pkt = afpacket.recv(self.socket, self.RCV_SIZE_DEFAULT)
Rich Lanee1b8da92012-12-26 22:47:13 -0800111 return (pkt, time.time())
Dan Talayco3087a462010-02-13 14:01:47 -0800112
113 def send(self, packet):
114 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800115 Send a packet out this port.
Dan Talayco3087a462010-02-13 14:01:47 -0800116 @param packet The packet data to send to the port
117 @retval The number of bytes sent
118 """
119 return self.socket.send(packet)
120
Rich Lanee1b8da92012-12-26 22:47:13 -0800121 def down(self):
Dan Talayco3087a462010-02-13 14:01:47 -0800122 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800123 Bring the physical link down.
Dan Talayco3087a462010-02-13 14:01:47 -0800124 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800125 os.system("ifconfig down %s" % self.interface_name)
Dan Talayco3087a462010-02-13 14:01:47 -0800126
Rich Lanee1b8da92012-12-26 22:47:13 -0800127 def up(self):
ShreyaPanditacd8e1cf2012-11-28 11:44:42 -0500128 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800129 Bring the physical link up.
ShreyaPanditacd8e1cf2012-11-28 11:44:42 -0500130 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800131 os.system("ifconfig up %s" % self.interface_name)
ShreyaPanditacd8e1cf2012-11-28 11:44:42 -0500132
133
Rich Lanee1b8da92012-12-26 22:47:13 -0800134class DataPlanePortPcap:
Rich Laneb42a31c2012-10-05 17:54:17 -0700135 """
Rich Lane2e860482015-03-18 10:22:43 -0700136 Alternate port implementation using libpcap. This is used by non-Linux
137 operating systems.
Rich Laneb42a31c2012-10-05 17:54:17 -0700138 """
139
Rich Lanee1b8da92012-12-26 22:47:13 -0800140 def __init__(self, interface_name, port_number):
Rich Laneb42a31c2012-10-05 17:54:17 -0700141 self.pcap = pcap.pcap(interface_name)
142 self.pcap.setnonblock()
Rich Lanee1b8da92012-12-26 22:47:13 -0800143
144 def fileno(self):
Rich Laneb42a31c2012-10-05 17:54:17 -0700145 return self.pcap.fileno()
146
Rich Lanee1b8da92012-12-26 22:47:13 -0800147 def recv(self):
148 (timestamp, pkt) = next(self.pcap)
Rich Lane0415fd72014-02-25 22:04:17 -0800149 return (pkt[:], timestamp)
Rich Laneb42a31c2012-10-05 17:54:17 -0700150
151 def send(self, packet):
Rich Lane0306fd32015-03-11 15:52:21 -0700152 if hasattr(self.pcap, "inject"):
153 return self.pcap.inject(packet, len(packet))
154 else:
155 return self.pcap.sendpacket(packet)
Dan Talayco34089522010-02-07 23:07:41 -0800156
Rich Lanee1b8da92012-12-26 22:47:13 -0800157 def down(self):
158 pass
159
160 def up(self):
161 pass
162
163class DataPlane(Thread):
Dan Talayco34089522010-02-07 23:07:41 -0800164 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800165 This class provides methods to send and receive packets on the dataplane.
166 It uses the DataPlanePort class, or an alternative implementation of that
167 interface, to do IO on a particular port. A background thread is used to
168 read packets from the dataplane ports and enqueue them to be read by the
169 test. The kill() method must be called to shutdown this thread.
Dan Talayco34089522010-02-07 23:07:41 -0800170 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800171
172 MAX_QUEUE_LEN = 100
173
Jeffrey Townsend0e8b0922012-07-11 11:37:46 -0700174 def __init__(self, config=None):
Rich Lanee1b8da92012-12-26 22:47:13 -0800175 Thread.__init__(self)
176
177 # dict from port number to port object
178 self.ports = {}
179
180 # dict from port number to list of (timestamp, packet)
181 self.packet_queues = {}
182
183 # cvar serves double duty as a regular top level lock and
Dan Talaycoe226eb12010-02-18 23:06:30 -0800184 # as a condition variable
Rich Lanee1b8da92012-12-26 22:47:13 -0800185 self.cvar = Condition()
186
187 # Used to wake up the event loop from another thread
Rich Lanecd97d3d2013-01-07 18:50:06 -0800188 self.waker = ofutils.EventDescriptor()
Rich Lanee1b8da92012-12-26 22:47:13 -0800189 self.killed = False
Dan Talaycoe226eb12010-02-18 23:06:30 -0800190
Dan Talayco48370102010-03-03 15:17:33 -0800191 self.logger = logging.getLogger("dataplane")
Rich Lane472aaea2013-08-27 09:27:38 -0700192 self.pcap_writer = None
Dan Talayco34089522010-02-07 23:07:41 -0800193
Jeffrey Townsend0e8b0922012-07-11 11:37:46 -0700194 if config is None:
195 self.config = {}
196 else:
Charles Chanc85f1562018-01-18 15:44:29 -0800197 self.config = config;
Jeffrey Townsend0e8b0922012-07-11 11:37:46 -0700198
199 ############################################################
200 #
Jeffrey Townsend0e8b0922012-07-11 11:37:46 -0700201 # The platform/config can provide a custom DataPlanePort class
202 # here if you have a custom implementation with different
Charles Chanc85f1562018-01-18 15:44:29 -0800203 # behavior.
Jeffrey Townsend0e8b0922012-07-11 11:37:46 -0700204 #
205 # Set config.dataplane.portclass = MyDataPlanePortClass
206 # where MyDataPlanePortClass has the same interface as the class
Charles Chanc85f1562018-01-18 15:44:29 -0800207 # DataPlanePort defined here.
Jeffrey Townsend0e8b0922012-07-11 11:37:46 -0700208 #
Rich Lanefb718302012-12-26 21:02:31 -0800209 if "dataplane" in self.config and "portclass" in self.config["dataplane"]:
210 self.dppclass = self.config["dataplane"]["portclass"]
Rich Lane2e860482015-03-18 10:22:43 -0700211 elif "linux" in sys.platform:
212 self.dppclass = DataPlanePortLinux
Rich Lanefb718302012-12-26 21:02:31 -0800213 else:
Rich Lane2e860482015-03-18 10:22:43 -0700214 self.dppclass = DataPlanePortPcap
Jeffrey Townsend0e8b0922012-07-11 11:37:46 -0700215
Rich Lanee1b8da92012-12-26 22:47:13 -0800216 self.start()
217
218 def run(self):
219 """
220 Activity function for class
221 """
222 while not self.killed:
223 sockets = [self.waker] + self.ports.values()
224 try:
225 sel_in, sel_out, sel_err = select.select(sockets, [], [], 1)
226 except:
227 print sys.exc_info()
228 self.logger.error("Select error, exiting")
229 break
230
231 with self.cvar:
232 for port in sel_in:
233 if port == self.waker:
234 self.waker.wait()
235 continue
236 else:
237 # Enqueue packet
238 pkt, timestamp = port.recv()
239 port_number = port._port_number
240 self.logger.debug("Pkt len %d in on port %d",
241 len(pkt), port_number)
Rich Lane472aaea2013-08-27 09:27:38 -0700242 if self.pcap_writer:
243 self.pcap_writer.write(pkt, timestamp, port_number)
Rich Lanee1b8da92012-12-26 22:47:13 -0800244 queue = self.packet_queues[port_number]
245 if len(queue) >= self.MAX_QUEUE_LEN:
246 # Queue full, throw away oldest
247 queue.pop(0)
248 self.logger.debug("Discarding oldest packet to make room")
249 queue.append((pkt, timestamp))
250 self.cvar.notify_all()
251
252 self.logger.info("Thread exit")
253
Dan Talayco34089522010-02-07 23:07:41 -0800254 def port_add(self, interface_name, port_number):
255 """
256 Add a port to the dataplane
Dan Talayco34089522010-02-07 23:07:41 -0800257 @param interface_name The name of the physical interface like eth1
258 @param port_number The port number used to refer to the port
Rich Lanee1b8da92012-12-26 22:47:13 -0800259 Stashes the port number on the created port object.
Dan Talayco34089522010-02-07 23:07:41 -0800260 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800261 self.ports[port_number] = self.dppclass(interface_name, port_number)
262 self.ports[port_number]._port_number = port_number
263 self.packet_queues[port_number] = []
264 # Need to wake up event loop to change the sockets being selected on.
265 self.waker.notify()
Dan Talayco34089522010-02-07 23:07:41 -0800266
267 def send(self, port_number, packet):
268 """
269 Send a packet to the given port
270 @param port_number The port to send the data to
271 @param packet Raw packet data to send to port
272 """
Dan Talayco11c26e72010-03-07 22:03:57 -0800273 self.logger.debug("Sending %d bytes to port %d" %
274 (len(packet), port_number))
Rich Lane472aaea2013-08-27 09:27:38 -0700275 if self.pcap_writer:
276 self.pcap_writer.write(packet, time.time(), port_number)
Rich Lanee1b8da92012-12-26 22:47:13 -0800277 bytes = self.ports[port_number].send(packet)
Dan Talayco34089522010-02-07 23:07:41 -0800278 if bytes != len(packet):
Dan Talayco48370102010-03-03 15:17:33 -0800279 self.logger.error("Unhandled send error, length mismatch %d != %d" %
Dan Talayco1b3f6902010-02-15 14:14:19 -0800280 (bytes, len(packet)))
Dan Talayco34089522010-02-07 23:07:41 -0800281 return bytes
282
Rich Lanee1b8da92012-12-26 22:47:13 -0800283 def oldest_port_number(self):
284 """
285 Returns the port number with the oldest packet, or
286 None if no packets are queued.
287 """
288 min_port_number = None
Rich Lanedb9d8662012-07-26 18:04:24 -0700289 min_time = float('inf')
Rich Lanee1b8da92012-12-26 22:47:13 -0800290 for (port_number, queue) in self.packet_queues.items():
291 if queue and queue[0][1] < min_time:
292 min_time = queue[0][1]
293 min_port_number = port_number
294 return min_port_number
Rich Lanedb9d8662012-07-26 18:04:24 -0700295
296 # Dequeues and yields packets in the order they were received.
Rich Lanee1b8da92012-12-26 22:47:13 -0800297 # Yields (port number, packet, received time).
Rich Lanedb9d8662012-07-26 18:04:24 -0700298 # If port_number is not specified yields packets from all ports.
299 def packets(self, port_number=None):
300 while True:
Rich Lanee1b8da92012-12-26 22:47:13 -0800301 rcv_port_number = port_number or self.oldest_port_number()
Rich Lanedb9d8662012-07-26 18:04:24 -0700302
Rich Lanee1b8da92012-12-26 22:47:13 -0800303 if rcv_port_number == None:
304 self.logger.debug("Out of packets on all ports")
Rich Lanedb9d8662012-07-26 18:04:24 -0700305 break
306
Rich Lanee1b8da92012-12-26 22:47:13 -0800307 queue = self.packet_queues[rcv_port_number]
308
309 if len(queue) == 0:
310 self.logger.debug("Out of packets on port %d", rcv_port_number)
311 break
312
313 pkt, time = queue.pop(0)
314 yield (rcv_port_number, pkt, time)
Rich Lanedb9d8662012-07-26 18:04:24 -0700315
Rich Lane8806bc42012-07-26 19:18:37 -0700316 def poll(self, port_number=None, timeout=-1, exp_pkt=None):
Dan Talaycoe226eb12010-02-18 23:06:30 -0800317 """
318 Poll one or all dataplane ports for a packet
319
320 If port_number is given, get the oldest packet from that port.
321 Otherwise, find the port with the oldest packet and return
322 that packet.
Dan Talayco1729fdb2012-05-03 09:35:56 -0700323
324 If exp_pkt is true, discard all packets until that one is found
325
Dan Talaycoe226eb12010-02-18 23:06:30 -0800326 @param port_number If set, get packet from this port
Dan Talayco11c26e72010-03-07 22:03:57 -0800327 @param timeout If positive and no packet is available, block
Dan Talaycoe226eb12010-02-18 23:06:30 -0800328 until a packet is received or for this many seconds
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700329 @param exp_pkt If not None, look for this packet and ignore any
Dan Talayco1729fdb2012-05-03 09:35:56 -0700330 others received. Note that if port_number is None, all packets
331 from all ports will be discarded until the exp_pkt is found
Dan Talaycoe226eb12010-02-18 23:06:30 -0800332 @return The triple port_number, packet, pkt_time where packet
333 is received from port_number at time pkt_time. If a timeout
334 occurs, return None, None, None
335 """
336
Dan Talaycocf26b7a2011-08-05 10:15:35 -0700337 if exp_pkt and not port_number:
Dan Talayco1729fdb2012-05-03 09:35:56 -0700338 self.logger.warn("Dataplane poll with exp_pkt but no port number")
339
Rich Lanedb9d8662012-07-26 18:04:24 -0700340 # Retrieve the packet. Returns (port number, packet, time).
341 def grab():
342 self.logger.debug("Grabbing packet")
Rich Lanee1b8da92012-12-26 22:47:13 -0800343 for (rcv_port_number, pkt, time) in self.packets(port_number):
344 self.logger.debug("Checking packet from port %d", rcv_port_number)
macauleydf8f5fc2015-07-16 17:27:55 +0800345 if not exp_pkt or match_exp_pkt(self, exp_pkt, pkt):
Rich Lanee1b8da92012-12-26 22:47:13 -0800346 return (rcv_port_number, pkt, time)
Rich Lanedb9d8662012-07-26 18:04:24 -0700347 self.logger.debug("Did not find packet")
348 return None
Dan Talaycoe226eb12010-02-18 23:06:30 -0800349
Rich Lanee1b8da92012-12-26 22:47:13 -0800350 with self.cvar:
Rich Lanecd97d3d2013-01-07 18:50:06 -0800351 ret = ofutils.timed_wait(self.cvar, grab, timeout=timeout)
Dan Talayco34089522010-02-07 23:07:41 -0800352
Rich Lanedb9d8662012-07-26 18:04:24 -0700353 if ret != None:
Rich Lanee1b8da92012-12-26 22:47:13 -0800354 return ret
Rich Lanedb9d8662012-07-26 18:04:24 -0700355 else:
356 self.logger.debug("Poll time out, no packet from " + str(port_number))
357 return (None, None, None)
Dan Talayco34089522010-02-07 23:07:41 -0800358
Rich Lanee1b8da92012-12-26 22:47:13 -0800359 def kill(self):
Dan Talayco1b3f6902010-02-15 14:14:19 -0800360 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800361 Stop the dataplane thread.
Dan Talayco1b3f6902010-02-15 14:14:19 -0800362 """
Rich Lanee1b8da92012-12-26 22:47:13 -0800363 self.killed = True
364 self.waker.notify()
365 self.join()
366 # Explicitly release ports to ensure we don't run out of sockets
367 # even if someone keeps holding a reference to the dataplane.
368 del self.ports
Rich Lane4d46dbd2012-12-22 19:26:19 -0800369
Rich Lanee1b8da92012-12-26 22:47:13 -0800370 def port_down(self, port_number):
ShreyaPanditacd8e1cf2012-11-28 11:44:42 -0500371 """Brings the specified port down"""
Rich Lanee1b8da92012-12-26 22:47:13 -0800372 self.ports[port_number].down()
ShreyaPanditacd8e1cf2012-11-28 11:44:42 -0500373
Rich Lanee1b8da92012-12-26 22:47:13 -0800374 def port_up(self, port_number):
ShreyaPanditacd8e1cf2012-11-28 11:44:42 -0500375 """Brings the specified port up"""
Rich Lanee1b8da92012-12-26 22:47:13 -0800376 self.ports[port_number].up()
Rich Lane2c7812c2012-12-27 17:52:23 -0800377
378 def flush(self):
379 """
380 Drop any queued packets.
381 """
382 for port_number in self.packet_queues.keys():
383 self.packet_queues[port_number] = []
Rich Lane472aaea2013-08-27 09:27:38 -0700384
385 def start_pcap(self, filename):
386 assert(self.pcap_writer == None)
387 self.pcap_writer = PcapWriter(filename)
388
389 def stop_pcap(self):
390 if self.pcap_writer:
391 self.pcap_writer.close()
392 self.pcap_writer = None