Matteo Scandolo | a229eca | 2017-08-08 13:05:28 -0700 | [diff] [blame] | 1 | |
| 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 Talayco | 3408952 | 2010-02-07 23:07:41 -0800 | [diff] [blame] | 17 | """ |
| 18 | OpenFlow Test Framework |
| 19 | |
Dan Talayco | 3087a46 | 2010-02-13 14:01:47 -0800 | [diff] [blame] | 20 | DataPlane and DataPlanePort classes |
Dan Talayco | 3408952 | 2010-02-07 23:07:41 -0800 | [diff] [blame] | 21 | |
| 22 | Provide the interface to the control the set of ports being used |
| 23 | to stimulate the switch under test. |
| 24 | |
| 25 | See the class dataplaneport for more details. This class wraps |
Dan Talayco | e226eb1 | 2010-02-18 23:06:30 -0800 | [diff] [blame] | 26 | a set of those objects allowing general calls and parsing |
Dan Talayco | 3408952 | 2010-02-07 23:07:41 -0800 | [diff] [blame] | 27 | configuration. |
| 28 | |
Dan Talayco | 3087a46 | 2010-02-13 14:01:47 -0800 | [diff] [blame] | 29 | @todo Add "filters" for matching packets. Actions supported |
| 30 | for filters should include a callback or a counter |
Dan Talayco | 3408952 | 2010-02-07 23:07:41 -0800 | [diff] [blame] | 31 | """ |
| 32 | |
Dan Talayco | 3087a46 | 2010-02-13 14:01:47 -0800 | [diff] [blame] | 33 | import sys |
| 34 | import os |
| 35 | import socket |
| 36 | import time |
Rich Lane | cd97d3d | 2013-01-07 18:50:06 -0800 | [diff] [blame] | 37 | import select |
| 38 | import logging |
Dan Talayco | 3087a46 | 2010-02-13 14:01:47 -0800 | [diff] [blame] | 39 | from threading import Thread |
| 40 | from threading import Lock |
Dan Talayco | e226eb1 | 2010-02-18 23:06:30 -0800 | [diff] [blame] | 41 | from threading import Condition |
Rich Lane | cd97d3d | 2013-01-07 18:50:06 -0800 | [diff] [blame] | 42 | import ofutils |
| 43 | import netutils |
Rich Lane | 472aaea | 2013-08-27 09:27:38 -0700 | [diff] [blame] | 44 | from pcap_writer import PcapWriter |
Dan Talayco | 3087a46 | 2010-02-13 14:01:47 -0800 | [diff] [blame] | 45 | |
Rich Lane | 1b73726 | 2015-03-18 10:13:56 -0700 | [diff] [blame] | 46 | if "linux" in sys.platform: |
| 47 | import afpacket |
Rich Lane | 2e86048 | 2015-03-18 10:22:43 -0700 | [diff] [blame] | 48 | else: |
Rich Lane | b42a31c | 2012-10-05 17:54:17 -0700 | [diff] [blame] | 49 | import pcap |
Rich Lane | b42a31c | 2012-10-05 17:54:17 -0700 | [diff] [blame] | 50 | |
macauley | df8f5fc | 2015-07-16 17:27:55 +0800 | [diff] [blame] | 51 | def match_exp_pkt(self, exp_pkt, pkt): |
Ed Swierk | 506614a | 2012-03-29 08:16:59 -0700 | [diff] [blame] | 52 | """ |
| 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)] |
macauley | df8f5fc | 2015-07-16 17:27:55 +0800 | [diff] [blame] | 62 | |
| 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 Chan | c85f156 | 2018-01-18 15:44:29 -0800 | [diff] [blame] | 67 | else: |
macauley | df8f5fc | 2015-07-16 17:27:55 +0800 | [diff] [blame] | 68 | if self.config["dump_packet"]: |
Charles Chan | c85f156 | 2018-01-18 15:44:29 -0800 | [diff] [blame] | 69 | 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))) |
macauley | df8f5fc | 2015-07-16 17:27:55 +0800 | [diff] [blame] | 71 | |
| 72 | return False |
Ed Swierk | 506614a | 2012-03-29 08:16:59 -0700 | [diff] [blame] | 73 | |
Jeffrey Townsend | 0e8b092 | 2012-07-11 11:37:46 -0700 | [diff] [blame] | 74 | |
Rich Lane | 2e86048 | 2015-03-18 10:22:43 -0700 | [diff] [blame] | 75 | class DataPlanePortLinux: |
Dan Talayco | 3087a46 | 2010-02-13 14:01:47 -0800 | [diff] [blame] | 76 | """ |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 77 | Uses raw sockets to capture and send packets on a network interface. |
Dan Talayco | 3087a46 | 2010-02-13 14:01:47 -0800 | [diff] [blame] | 78 | """ |
| 79 | |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 80 | RCV_SIZE_DEFAULT = 4096 |
| 81 | ETH_P_ALL = 0x03 |
| 82 | RCV_TIMEOUT = 10000 |
| 83 | |
| 84 | def __init__(self, interface_name, port_number): |
Dan Talayco | 3087a46 | 2010-02-13 14:01:47 -0800 | [diff] [blame] | 85 | """ |
Dan Talayco | 3087a46 | 2010-02-13 14:01:47 -0800 | [diff] [blame] | 86 | @param interface_name The name of the physical interface like eth1 |
Dan Talayco | 3087a46 | 2010-02-13 14:01:47 -0800 | [diff] [blame] | 87 | """ |
Dan Talayco | 3087a46 | 2010-02-13 14:01:47 -0800 | [diff] [blame] | 88 | self.interface_name = interface_name |
Rich Lane | 3e7d28a | 2015-03-19 00:18:47 -0700 | [diff] [blame] | 89 | self.socket = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, 0) |
Rich Lane | 1b73726 | 2015-03-18 10:13:56 -0700 | [diff] [blame] | 90 | afpacket.enable_auxdata(self.socket) |
Rich Lane | 3e7d28a | 2015-03-19 00:18:47 -0700 | [diff] [blame] | 91 | self.socket.bind((interface_name, self.ETH_P_ALL)) |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 92 | netutils.set_promisc(self.socket, interface_name) |
| 93 | self.socket.settimeout(self.RCV_TIMEOUT) |
Dan Talayco | 1b3f690 | 2010-02-15 14:14:19 -0800 | [diff] [blame] | 94 | |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 95 | def __del__(self): |
| 96 | if self.socket: |
Dan Talayco | 1b3f690 | 2010-02-15 14:14:19 -0800 | [diff] [blame] | 97 | self.socket.close() |
Dan Talayco | 1b3f690 | 2010-02-15 14:14:19 -0800 | [diff] [blame] | 98 | |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 99 | def fileno(self): |
Dan Talayco | 3087a46 | 2010-02-13 14:01:47 -0800 | [diff] [blame] | 100 | """ |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 101 | Return an integer file descriptor that can be passed to select(2). |
Dan Talayco | 3087a46 | 2010-02-13 14:01:47 -0800 | [diff] [blame] | 102 | """ |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 103 | return self.socket.fileno() |
Dan Talayco | 3087a46 | 2010-02-13 14:01:47 -0800 | [diff] [blame] | 104 | |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 105 | def recv(self): |
Dan Talayco | 3087a46 | 2010-02-13 14:01:47 -0800 | [diff] [blame] | 106 | """ |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 107 | Receive a packet from this port. |
| 108 | @retval (packet data, timestamp) |
Dan Talayco | 3087a46 | 2010-02-13 14:01:47 -0800 | [diff] [blame] | 109 | """ |
Rich Lane | 1b73726 | 2015-03-18 10:13:56 -0700 | [diff] [blame] | 110 | pkt = afpacket.recv(self.socket, self.RCV_SIZE_DEFAULT) |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 111 | return (pkt, time.time()) |
Dan Talayco | 3087a46 | 2010-02-13 14:01:47 -0800 | [diff] [blame] | 112 | |
| 113 | def send(self, packet): |
| 114 | """ |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 115 | Send a packet out this port. |
Dan Talayco | 3087a46 | 2010-02-13 14:01:47 -0800 | [diff] [blame] | 116 | @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 Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 121 | def down(self): |
Dan Talayco | 3087a46 | 2010-02-13 14:01:47 -0800 | [diff] [blame] | 122 | """ |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 123 | Bring the physical link down. |
Dan Talayco | 3087a46 | 2010-02-13 14:01:47 -0800 | [diff] [blame] | 124 | """ |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 125 | os.system("ifconfig down %s" % self.interface_name) |
Dan Talayco | 3087a46 | 2010-02-13 14:01:47 -0800 | [diff] [blame] | 126 | |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 127 | def up(self): |
ShreyaPandita | cd8e1cf | 2012-11-28 11:44:42 -0500 | [diff] [blame] | 128 | """ |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 129 | Bring the physical link up. |
ShreyaPandita | cd8e1cf | 2012-11-28 11:44:42 -0500 | [diff] [blame] | 130 | """ |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 131 | os.system("ifconfig up %s" % self.interface_name) |
ShreyaPandita | cd8e1cf | 2012-11-28 11:44:42 -0500 | [diff] [blame] | 132 | |
| 133 | |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 134 | class DataPlanePortPcap: |
Rich Lane | b42a31c | 2012-10-05 17:54:17 -0700 | [diff] [blame] | 135 | """ |
Rich Lane | 2e86048 | 2015-03-18 10:22:43 -0700 | [diff] [blame] | 136 | Alternate port implementation using libpcap. This is used by non-Linux |
| 137 | operating systems. |
Rich Lane | b42a31c | 2012-10-05 17:54:17 -0700 | [diff] [blame] | 138 | """ |
| 139 | |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 140 | def __init__(self, interface_name, port_number): |
Rich Lane | b42a31c | 2012-10-05 17:54:17 -0700 | [diff] [blame] | 141 | self.pcap = pcap.pcap(interface_name) |
| 142 | self.pcap.setnonblock() |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 143 | |
| 144 | def fileno(self): |
Rich Lane | b42a31c | 2012-10-05 17:54:17 -0700 | [diff] [blame] | 145 | return self.pcap.fileno() |
| 146 | |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 147 | def recv(self): |
| 148 | (timestamp, pkt) = next(self.pcap) |
Rich Lane | 0415fd7 | 2014-02-25 22:04:17 -0800 | [diff] [blame] | 149 | return (pkt[:], timestamp) |
Rich Lane | b42a31c | 2012-10-05 17:54:17 -0700 | [diff] [blame] | 150 | |
| 151 | def send(self, packet): |
Rich Lane | 0306fd3 | 2015-03-11 15:52:21 -0700 | [diff] [blame] | 152 | if hasattr(self.pcap, "inject"): |
| 153 | return self.pcap.inject(packet, len(packet)) |
| 154 | else: |
| 155 | return self.pcap.sendpacket(packet) |
Dan Talayco | 3408952 | 2010-02-07 23:07:41 -0800 | [diff] [blame] | 156 | |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 157 | def down(self): |
| 158 | pass |
| 159 | |
| 160 | def up(self): |
| 161 | pass |
| 162 | |
| 163 | class DataPlane(Thread): |
Dan Talayco | 3408952 | 2010-02-07 23:07:41 -0800 | [diff] [blame] | 164 | """ |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 165 | 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 Talayco | 3408952 | 2010-02-07 23:07:41 -0800 | [diff] [blame] | 170 | """ |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 171 | |
| 172 | MAX_QUEUE_LEN = 100 |
| 173 | |
Jeffrey Townsend | 0e8b092 | 2012-07-11 11:37:46 -0700 | [diff] [blame] | 174 | def __init__(self, config=None): |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 175 | 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 Talayco | e226eb1 | 2010-02-18 23:06:30 -0800 | [diff] [blame] | 184 | # as a condition variable |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 185 | self.cvar = Condition() |
| 186 | |
| 187 | # Used to wake up the event loop from another thread |
Rich Lane | cd97d3d | 2013-01-07 18:50:06 -0800 | [diff] [blame] | 188 | self.waker = ofutils.EventDescriptor() |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 189 | self.killed = False |
Dan Talayco | e226eb1 | 2010-02-18 23:06:30 -0800 | [diff] [blame] | 190 | |
Dan Talayco | 4837010 | 2010-03-03 15:17:33 -0800 | [diff] [blame] | 191 | self.logger = logging.getLogger("dataplane") |
Rich Lane | 472aaea | 2013-08-27 09:27:38 -0700 | [diff] [blame] | 192 | self.pcap_writer = None |
Dan Talayco | 3408952 | 2010-02-07 23:07:41 -0800 | [diff] [blame] | 193 | |
Jeffrey Townsend | 0e8b092 | 2012-07-11 11:37:46 -0700 | [diff] [blame] | 194 | if config is None: |
| 195 | self.config = {} |
| 196 | else: |
Charles Chan | c85f156 | 2018-01-18 15:44:29 -0800 | [diff] [blame] | 197 | self.config = config; |
Jeffrey Townsend | 0e8b092 | 2012-07-11 11:37:46 -0700 | [diff] [blame] | 198 | |
| 199 | ############################################################ |
| 200 | # |
Jeffrey Townsend | 0e8b092 | 2012-07-11 11:37:46 -0700 | [diff] [blame] | 201 | # The platform/config can provide a custom DataPlanePort class |
| 202 | # here if you have a custom implementation with different |
Charles Chan | c85f156 | 2018-01-18 15:44:29 -0800 | [diff] [blame] | 203 | # behavior. |
Jeffrey Townsend | 0e8b092 | 2012-07-11 11:37:46 -0700 | [diff] [blame] | 204 | # |
| 205 | # Set config.dataplane.portclass = MyDataPlanePortClass |
| 206 | # where MyDataPlanePortClass has the same interface as the class |
Charles Chan | c85f156 | 2018-01-18 15:44:29 -0800 | [diff] [blame] | 207 | # DataPlanePort defined here. |
Jeffrey Townsend | 0e8b092 | 2012-07-11 11:37:46 -0700 | [diff] [blame] | 208 | # |
Rich Lane | fb71830 | 2012-12-26 21:02:31 -0800 | [diff] [blame] | 209 | if "dataplane" in self.config and "portclass" in self.config["dataplane"]: |
| 210 | self.dppclass = self.config["dataplane"]["portclass"] |
Rich Lane | 2e86048 | 2015-03-18 10:22:43 -0700 | [diff] [blame] | 211 | elif "linux" in sys.platform: |
| 212 | self.dppclass = DataPlanePortLinux |
Rich Lane | fb71830 | 2012-12-26 21:02:31 -0800 | [diff] [blame] | 213 | else: |
Rich Lane | 2e86048 | 2015-03-18 10:22:43 -0700 | [diff] [blame] | 214 | self.dppclass = DataPlanePortPcap |
Jeffrey Townsend | 0e8b092 | 2012-07-11 11:37:46 -0700 | [diff] [blame] | 215 | |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 216 | 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 Lane | 472aaea | 2013-08-27 09:27:38 -0700 | [diff] [blame] | 242 | if self.pcap_writer: |
| 243 | self.pcap_writer.write(pkt, timestamp, port_number) |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 244 | 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 Talayco | 3408952 | 2010-02-07 23:07:41 -0800 | [diff] [blame] | 254 | def port_add(self, interface_name, port_number): |
| 255 | """ |
| 256 | Add a port to the dataplane |
Dan Talayco | 3408952 | 2010-02-07 23:07:41 -0800 | [diff] [blame] | 257 | @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 Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 259 | Stashes the port number on the created port object. |
Dan Talayco | 3408952 | 2010-02-07 23:07:41 -0800 | [diff] [blame] | 260 | """ |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 261 | 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 Talayco | 3408952 | 2010-02-07 23:07:41 -0800 | [diff] [blame] | 266 | |
| 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 Talayco | 11c26e7 | 2010-03-07 22:03:57 -0800 | [diff] [blame] | 273 | self.logger.debug("Sending %d bytes to port %d" % |
| 274 | (len(packet), port_number)) |
Rich Lane | 472aaea | 2013-08-27 09:27:38 -0700 | [diff] [blame] | 275 | if self.pcap_writer: |
| 276 | self.pcap_writer.write(packet, time.time(), port_number) |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 277 | bytes = self.ports[port_number].send(packet) |
Dan Talayco | 3408952 | 2010-02-07 23:07:41 -0800 | [diff] [blame] | 278 | if bytes != len(packet): |
Dan Talayco | 4837010 | 2010-03-03 15:17:33 -0800 | [diff] [blame] | 279 | self.logger.error("Unhandled send error, length mismatch %d != %d" % |
Dan Talayco | 1b3f690 | 2010-02-15 14:14:19 -0800 | [diff] [blame] | 280 | (bytes, len(packet))) |
Dan Talayco | 3408952 | 2010-02-07 23:07:41 -0800 | [diff] [blame] | 281 | return bytes |
| 282 | |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 283 | 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 Lane | db9d866 | 2012-07-26 18:04:24 -0700 | [diff] [blame] | 289 | min_time = float('inf') |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 290 | 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 Lane | db9d866 | 2012-07-26 18:04:24 -0700 | [diff] [blame] | 295 | |
| 296 | # Dequeues and yields packets in the order they were received. |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 297 | # Yields (port number, packet, received time). |
Rich Lane | db9d866 | 2012-07-26 18:04:24 -0700 | [diff] [blame] | 298 | # If port_number is not specified yields packets from all ports. |
| 299 | def packets(self, port_number=None): |
| 300 | while True: |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 301 | rcv_port_number = port_number or self.oldest_port_number() |
Rich Lane | db9d866 | 2012-07-26 18:04:24 -0700 | [diff] [blame] | 302 | |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 303 | if rcv_port_number == None: |
| 304 | self.logger.debug("Out of packets on all ports") |
Rich Lane | db9d866 | 2012-07-26 18:04:24 -0700 | [diff] [blame] | 305 | break |
| 306 | |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 307 | 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 Lane | db9d866 | 2012-07-26 18:04:24 -0700 | [diff] [blame] | 315 | |
Rich Lane | 8806bc4 | 2012-07-26 19:18:37 -0700 | [diff] [blame] | 316 | def poll(self, port_number=None, timeout=-1, exp_pkt=None): |
Dan Talayco | e226eb1 | 2010-02-18 23:06:30 -0800 | [diff] [blame] | 317 | """ |
| 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 Talayco | 1729fdb | 2012-05-03 09:35:56 -0700 | [diff] [blame] | 323 | |
| 324 | If exp_pkt is true, discard all packets until that one is found |
| 325 | |
Dan Talayco | e226eb1 | 2010-02-18 23:06:30 -0800 | [diff] [blame] | 326 | @param port_number If set, get packet from this port |
Dan Talayco | 11c26e7 | 2010-03-07 22:03:57 -0800 | [diff] [blame] | 327 | @param timeout If positive and no packet is available, block |
Dan Talayco | e226eb1 | 2010-02-18 23:06:30 -0800 | [diff] [blame] | 328 | until a packet is received or for this many seconds |
Dan Talayco | cf26b7a | 2011-08-05 10:15:35 -0700 | [diff] [blame] | 329 | @param exp_pkt If not None, look for this packet and ignore any |
Dan Talayco | 1729fdb | 2012-05-03 09:35:56 -0700 | [diff] [blame] | 330 | 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 Talayco | e226eb1 | 2010-02-18 23:06:30 -0800 | [diff] [blame] | 332 | @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 Talayco | cf26b7a | 2011-08-05 10:15:35 -0700 | [diff] [blame] | 337 | if exp_pkt and not port_number: |
Dan Talayco | 1729fdb | 2012-05-03 09:35:56 -0700 | [diff] [blame] | 338 | self.logger.warn("Dataplane poll with exp_pkt but no port number") |
| 339 | |
Rich Lane | db9d866 | 2012-07-26 18:04:24 -0700 | [diff] [blame] | 340 | # Retrieve the packet. Returns (port number, packet, time). |
| 341 | def grab(): |
| 342 | self.logger.debug("Grabbing packet") |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 343 | for (rcv_port_number, pkt, time) in self.packets(port_number): |
| 344 | self.logger.debug("Checking packet from port %d", rcv_port_number) |
macauley | df8f5fc | 2015-07-16 17:27:55 +0800 | [diff] [blame] | 345 | if not exp_pkt or match_exp_pkt(self, exp_pkt, pkt): |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 346 | return (rcv_port_number, pkt, time) |
Rich Lane | db9d866 | 2012-07-26 18:04:24 -0700 | [diff] [blame] | 347 | self.logger.debug("Did not find packet") |
| 348 | return None |
Dan Talayco | e226eb1 | 2010-02-18 23:06:30 -0800 | [diff] [blame] | 349 | |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 350 | with self.cvar: |
Rich Lane | cd97d3d | 2013-01-07 18:50:06 -0800 | [diff] [blame] | 351 | ret = ofutils.timed_wait(self.cvar, grab, timeout=timeout) |
Dan Talayco | 3408952 | 2010-02-07 23:07:41 -0800 | [diff] [blame] | 352 | |
Rich Lane | db9d866 | 2012-07-26 18:04:24 -0700 | [diff] [blame] | 353 | if ret != None: |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 354 | return ret |
Rich Lane | db9d866 | 2012-07-26 18:04:24 -0700 | [diff] [blame] | 355 | else: |
| 356 | self.logger.debug("Poll time out, no packet from " + str(port_number)) |
| 357 | return (None, None, None) |
Dan Talayco | 3408952 | 2010-02-07 23:07:41 -0800 | [diff] [blame] | 358 | |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 359 | def kill(self): |
Dan Talayco | 1b3f690 | 2010-02-15 14:14:19 -0800 | [diff] [blame] | 360 | """ |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 361 | Stop the dataplane thread. |
Dan Talayco | 1b3f690 | 2010-02-15 14:14:19 -0800 | [diff] [blame] | 362 | """ |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 363 | 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 Lane | 4d46dbd | 2012-12-22 19:26:19 -0800 | [diff] [blame] | 369 | |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 370 | def port_down(self, port_number): |
ShreyaPandita | cd8e1cf | 2012-11-28 11:44:42 -0500 | [diff] [blame] | 371 | """Brings the specified port down""" |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 372 | self.ports[port_number].down() |
ShreyaPandita | cd8e1cf | 2012-11-28 11:44:42 -0500 | [diff] [blame] | 373 | |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 374 | def port_up(self, port_number): |
ShreyaPandita | cd8e1cf | 2012-11-28 11:44:42 -0500 | [diff] [blame] | 375 | """Brings the specified port up""" |
Rich Lane | e1b8da9 | 2012-12-26 22:47:13 -0800 | [diff] [blame] | 376 | self.ports[port_number].up() |
Rich Lane | 2c7812c | 2012-12-27 17:52:23 -0800 | [diff] [blame] | 377 | |
| 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 Lane | 472aaea | 2013-08-27 09:27:38 -0700 | [diff] [blame] | 384 | |
| 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 |