blob: a07433bcdfd70ce10f730e06ffd1cf1e2232b2ee [file] [log] [blame]
Dan Talayco34089522010-02-07 23:07:41 -08001"""
2OpenFlow Test Framework
3
4Controller class
5
6Provide the interface to the control channel to the switch under test.
7
8Class inherits from thread so as to run in background allowing
9asynchronous callbacks (if needed, not required). Also supports
10polling.
11
12The controller thread maintains a queue. Incoming messages that
13are not handled by a callback function are placed in this queue for
14poll calls.
15
16Callbacks and polling support specifying the message type
17
18@todo Support transaction semantics via xid
Dan Talayco1b3f6902010-02-15 14:14:19 -080019@todo Support select and listen on an administrative socket (or
20use a timeout to support clean shutdown).
21
22Currently only one connection is accepted during the life of
23the controller. There seems
24to be no clean way to interrupt an accept call. Using select that also listens
25on an administrative socket and can shut down the socket might work.
26
Dan Talayco34089522010-02-07 23:07:41 -080027"""
28
Dan Talayco34089522010-02-07 23:07:41 -080029import os
30import socket
31import time
Dan Talayco34089522010-02-07 23:07:41 -080032from threading import Thread
33from threading import Lock
Dan Talayco21c75c72010-02-12 22:59:24 -080034from threading import Condition
Dan Talayco34089522010-02-07 23:07:41 -080035from message import *
Dan Talaycoe37999f2010-02-09 15:27:12 -080036from parse import *
Dan Talaycod7e2dbe2010-02-13 21:51:15 -080037from ofutils import *
Dan Talayco710438c2010-02-18 15:16:07 -080038# For some reason, it seems select to be last (or later).
39# Otherwise get an attribute error when calling select.select
40import select
Dan Talayco48370102010-03-03 15:17:33 -080041import logging
42
Dan Talaycof8de5182012-04-12 22:38:41 -070043
44FILTER=''.join([(len(repr(chr(x)))==3) and chr(x) or '.'
45 for x in range(256)])
46
47def hex_dump_buffer(src, length=16):
48 """
49 Convert src to a hex dump string and return the string
50 @param src The source buffer
51 @param length The number of bytes shown in each line
52 @returns A string showing the hex dump
53 """
54 result = ["\n"]
55 for i in xrange(0, len(src), length):
56 chars = src[i:i+length]
57 hex = ' '.join(["%02x" % ord(x) for x in chars])
58 printable = ''.join(["%s" % ((ord(x) <= 127 and
59 FILTER[ord(x)]) or '.') for x in chars])
60 result.append("%04x %-*s %s\n" % (i, length*3, hex, printable))
61 return ''.join(result)
62
Dan Talayco48370102010-03-03 15:17:33 -080063##@todo Find a better home for these identifiers (controller)
Glen Gibb741b1182010-07-08 16:43:58 -070064RCV_SIZE_DEFAULT = 32768
Dan Talayco48370102010-03-03 15:17:33 -080065LISTEN_QUEUE_SIZE = 1
Dan Talayco34089522010-02-07 23:07:41 -080066
67class Controller(Thread):
68 """
69 Class abstracting the control interface to the switch.
70
71 For receiving messages, two mechanism will be implemented. First,
72 query the interface with poll. Second, register to have a
73 function called by message type. The callback is passed the
74 message type as well as the raw packet (or message object)
75
76 One of the main purposes of this object is to translate between network
77 and host byte order. 'Above' this object, things should be in host
78 byte order.
Dan Talayco21c75c72010-02-12 22:59:24 -080079
80 @todo Consider using SocketServer for listening socket
81 @todo Test transaction code
82
83 @var rcv_size The receive size to use for receive calls
84 @var max_pkts The max size of the receive queue
85 @var keep_alive If true, listen for echo requests and respond w/
86 echo replies
Dan Talayco710438c2010-02-18 15:16:07 -080087 @var initial_hello If true, will send a hello message immediately
88 upon connecting to the switch
Dan Talayco69ca4d62012-11-15 11:50:22 -080089 @var switch If not None, do an active connection to the switch
Dan Talayco21c75c72010-02-12 22:59:24 -080090 @var host The host to use for connect
91 @var port The port to connect on
92 @var packets_total Total number of packets received
93 @var packets_expired Number of packets popped from queue as queue full
94 @var packets_handled Number of packets handled by something
Dan Talaycod7e2dbe2010-02-13 21:51:15 -080095 @var dbg_state Debug indication of state
Dan Talayco34089522010-02-07 23:07:41 -080096 """
97
Dan Talayco69ca4d62012-11-15 11:50:22 -080098 def __init__(self, switch=None, host='127.0.0.1', port=6633, max_pkts=1024):
Dan Talayco21c75c72010-02-12 22:59:24 -080099 Thread.__init__(self)
Dan Talayco1b3f6902010-02-15 14:14:19 -0800100 # Socket related
Dan Talayco21c75c72010-02-12 22:59:24 -0800101 self.rcv_size = RCV_SIZE_DEFAULT
Dan Talayco1b3f6902010-02-15 14:14:19 -0800102 self.listen_socket = None
103 self.switch_socket = None
104 self.switch_addr = None
Dan Talayco710438c2010-02-18 15:16:07 -0800105 self.connect_cv = Condition()
Dan Talaycoe226eb12010-02-18 23:06:30 -0800106 self.message_cv = Condition()
Dan Talayco1b3f6902010-02-15 14:14:19 -0800107
Rich Lane4dfd5e12012-12-22 19:48:01 -0800108 # Used to wake up the event loop from another thread
109 self.waker = EventDescriptor()
Rich Lane32797542012-12-22 17:46:05 -0800110
Dan Talayco1b3f6902010-02-15 14:14:19 -0800111 # Counters
Dan Talayco21c75c72010-02-12 22:59:24 -0800112 self.socket_errors = 0
113 self.parse_errors = 0
Dan Talayco21c75c72010-02-12 22:59:24 -0800114 self.packets_total = 0
Dan Talayco1b3f6902010-02-15 14:14:19 -0800115 self.packets_expired = 0
116 self.packets_handled = 0
Dan Talaycoe226eb12010-02-18 23:06:30 -0800117 self.poll_discards = 0
Dan Talayco1b3f6902010-02-15 14:14:19 -0800118
119 # State
Dan Talayco21c75c72010-02-12 22:59:24 -0800120 self.sync = Lock()
121 self.handlers = {}
122 self.keep_alive = False
Dan Talayco710438c2010-02-18 15:16:07 -0800123 self.active = True
124 self.initial_hello = True
Dan Talayco1b3f6902010-02-15 14:14:19 -0800125
Rich Lanec4f071b2012-07-11 17:25:57 -0700126 # OpenFlow message/packet queue
127 # Protected by the packets_cv lock / condition variable
128 self.packets = []
129 self.packets_cv = Condition()
130
Dan Talayco1b3f6902010-02-15 14:14:19 -0800131 # Settings
132 self.max_pkts = max_pkts
Dan Talayco69ca4d62012-11-15 11:50:22 -0800133 self.switch = switch
134 self.passive = not self.switch
Dan Talayco48370102010-03-03 15:17:33 -0800135 self.host = host
136 self.port = port
Dan Talaycod7e2dbe2010-02-13 21:51:15 -0800137 self.dbg_state = "init"
Dan Talayco48370102010-03-03 15:17:33 -0800138 self.logger = logging.getLogger("controller")
Dan Talaycof8de5182012-04-12 22:38:41 -0700139 self.filter_packet_in = False # Drop "excessive" packet ins
140 self.pkt_in_run = 0 # Count on run of packet ins
141 self.pkt_in_filter_limit = 50 # Count on run of packet ins
142 self.pkt_in_dropped = 0 # Total dropped packet ins
143 self.transact_to = 15 # Transact timeout default value; add to config
Dan Talayco1b3f6902010-02-15 14:14:19 -0800144
Dan Talaycoe226eb12010-02-18 23:06:30 -0800145 # Transaction and message type waiting variables
146 # xid_cv: Condition variable (semaphore) for packet waiters
Dan Talayco21c75c72010-02-12 22:59:24 -0800147 # xid: Transaction ID being waited on
148 # xid_response: Transaction response message
149 self.xid_cv = Condition()
150 self.xid = None
151 self.xid_response = None
152
Rob Sherwoode3e452a2012-03-06 09:24:26 -0800153 self.buffered_input = ""
Dan Talaycoe226eb12010-02-18 23:06:30 -0800154
Rich Lane207502e2012-12-31 14:29:12 -0800155 # Create listen socket
156 if self.passive:
157 self.logger.info("Create/listen at " + self.host + ":" +
158 str(self.port))
159 self.listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
160 self.listen_socket.setsockopt(socket.SOL_SOCKET,
161 socket.SO_REUSEADDR, 1)
162 self.listen_socket.bind((self.host, self.port))
163 self.listen_socket.listen(LISTEN_QUEUE_SIZE)
164
Dan Talaycof8de5182012-04-12 22:38:41 -0700165 def filter_packet(self, rawmsg, hdr):
166 """
167 Check if packet should be filtered
168
169 Currently filters packet in messages
170 @return Boolean, True if packet should be dropped
171 """
172 # Add check for packet in and rate limit
173 if self.filter_packet_in:
Rich Lanec4f071b2012-07-11 17:25:57 -0700174 # If we were dropping packets, report number dropped
175 # TODO dont drop expected packet ins
176 if self.pkt_in_run > self.pkt_in_filter_limit:
177 self.logger.debug("Dropped %d packet ins (%d total)"
178 % ((self.pkt_in_run -
179 self.pkt_in_filter_limit),
180 self.pkt_in_dropped))
181 self.pkt_in_run = 0
Dan Talaycof8de5182012-04-12 22:38:41 -0700182
183 return False
184
Dan Talaycod12b6612010-03-07 22:00:46 -0800185 def _pkt_handle(self, pkt):
186 """
187 Check for all packet handling conditions
188
189 Parse and verify message
190 Check if XID matches something waiting
191 Check if message is being expected for a poll operation
192 Check if keep alive is on and message is an echo request
193 Check if any registered handler wants the packet
194 Enqueue if none of those conditions is met
195
196 an echo request in case keep_alive is true, followed by
197 registered message handlers.
Glen Gibb6d467062010-07-08 16:15:08 -0700198 @param pkt The raw packet (string) which may contain multiple OF msgs
Dan Talaycod12b6612010-03-07 22:00:46 -0800199 """
Rob Sherwoode3e452a2012-03-06 09:24:26 -0800200
201 # snag any left over data from last read()
202 pkt = self.buffered_input + pkt
203 self.buffered_input = ""
204
Glen Gibb6d467062010-07-08 16:15:08 -0700205 # Process each of the OF msgs inside the pkt
206 offset = 0
207 while offset < len(pkt):
208 # Parse the header to get type
209 hdr = of_header_parse(pkt[offset:])
Dan Talaycof8de5182012-04-12 22:38:41 -0700210 if not hdr or hdr.length == 0:
211 self.logger.error("Could not parse header")
212 self.logger.error("pkt len %d." % len(pkt))
213 if hdr:
214 self.logger.error("hdr len %d." % hdr.length)
215 self.logger.error("%s" % hex_dump_buffer(pkt[:200]))
216 self.kill()
Dan Talaycod12b6612010-03-07 22:00:46 -0800217 return
218
Glen Gibb6d467062010-07-08 16:15:08 -0700219 # Extract the raw message bytes
Ed Swierk836e5bd2012-03-20 11:08:53 -0700220 if (offset + hdr.length) > len(pkt):
Rob Sherwoode3e452a2012-03-06 09:24:26 -0800221 break
Glen Gibb6d467062010-07-08 16:15:08 -0700222 rawmsg = pkt[offset : offset + hdr.length]
Dan Talayco4306d3e2011-09-07 09:42:26 -0700223 offset += hdr.length
Dan Talaycof8de5182012-04-12 22:38:41 -0700224
225 if self.filter_packet(rawmsg, hdr):
226 continue
227
228 self.logger.debug("Msg in: buf len %d. hdr.type %s. hdr.len %d" %
229 (len(pkt), ofp_type_map[hdr.type], hdr.length))
Glen Gibb6d467062010-07-08 16:15:08 -0700230 if hdr.version != OFP_VERSION:
231 self.logger.error("Version %d does not match OFTest version %d"
232 % (hdr.version, OFP_VERSION))
233 print "Version %d does not match OFTest version %d" % \
234 (hdr.version, OFP_VERSION)
Ken Chiangadc950f2012-10-05 13:50:03 -0700235 self.disconnect()
Dan Talaycof8de5182012-04-12 22:38:41 -0700236 return
Dan Talaycod12b6612010-03-07 22:00:46 -0800237
Glen Gibb6d467062010-07-08 16:15:08 -0700238 msg = of_message_parse(rawmsg)
239 if not msg:
240 self.parse_errors += 1
241 self.logger.warn("Could not parse message")
242 continue
243
Rich Lanec4f071b2012-07-11 17:25:57 -0700244 with self.sync:
245 # Check if transaction is waiting
246 with self.xid_cv:
247 if self.xid and hdr.xid == self.xid:
248 self.logger.debug("Matched expected XID " + str(hdr.xid))
249 self.xid_response = (msg, rawmsg)
250 self.xid = None
251 self.xid_cv.notify()
252 continue
Glen Gibb6d467062010-07-08 16:15:08 -0700253
Rich Lanec4f071b2012-07-11 17:25:57 -0700254 # Check if keep alive is set; if so, respond to echo requests
255 if self.keep_alive:
256 if hdr.type == OFPT_ECHO_REQUEST:
257 self.logger.debug("Responding to echo request")
258 rep = echo_reply()
259 rep.header.xid = hdr.xid
260 # Ignoring additional data
261 if self.message_send(rep.pack(), zero_xid=True) < 0:
262 self.logger.error("Error sending echo reply")
263 continue
Glen Gibb6d467062010-07-08 16:15:08 -0700264
Rich Lanec4f071b2012-07-11 17:25:57 -0700265 # Now check for message handlers; preference is given to
266 # handlers for a specific packet
267 handled = False
268 if hdr.type in self.handlers.keys():
269 handled = self.handlers[hdr.type](self, msg, rawmsg)
270 if not handled and ("all" in self.handlers.keys()):
271 handled = self.handlers["all"](self, msg, rawmsg)
Glen Gibb6d467062010-07-08 16:15:08 -0700272
Rich Lanec4f071b2012-07-11 17:25:57 -0700273 if not handled: # Not handled, enqueue
274 self.logger.debug("Enqueuing pkt type " + ofp_type_map[hdr.type])
275 with self.packets_cv:
276 if len(self.packets) >= self.max_pkts:
277 self.packets.pop(0)
278 self.packets_expired += 1
279 self.packets.append((msg, rawmsg))
280 self.packets_cv.notify_all()
281 self.packets_total += 1
282 else:
283 self.packets_handled += 1
284 self.logger.debug("Message handled by callback")
Glen Gibb6d467062010-07-08 16:15:08 -0700285
Rob Sherwoode3e452a2012-03-06 09:24:26 -0800286 # end of 'while offset < len(pkt)'
287 # note that if offset = len(pkt), this is
288 # appends a harmless empty string
289 self.buffered_input += pkt[offset:]
Dan Talaycod12b6612010-03-07 22:00:46 -0800290
Dan Talayco710438c2010-02-18 15:16:07 -0800291 def _socket_ready_handle(self, s):
292 """
293 Handle an input-ready socket
Dan Talaycof8de5182012-04-12 22:38:41 -0700294
Dan Talayco710438c2010-02-18 15:16:07 -0800295 @param s The socket object that is ready
Dan Talaycof8de5182012-04-12 22:38:41 -0700296 @returns 0 on success, -1 on error
Dan Talayco710438c2010-02-18 15:16:07 -0800297 """
298
Dan Talayco69ca4d62012-11-15 11:50:22 -0800299 if self.passive and s and s == self.listen_socket:
Dan Talayco710438c2010-02-18 15:16:07 -0800300 if self.switch_socket:
Rich Lanee1da7ea2012-07-26 15:58:45 -0700301 self.logger.warning("Ignoring incoming connection; already connected to switch")
Rich Laneb4f8ecb2012-09-25 09:36:26 -0700302 (sock, addr) = self.listen_socket.accept()
303 sock.close()
Rich Lanee1da7ea2012-07-26 15:58:45 -0700304 return 0
Dan Talayco710438c2010-02-18 15:16:07 -0800305
Ken Chiange875baf2012-10-09 15:24:40 -0700306 try:
307 (sock, addr) = self.listen_socket.accept()
308 except:
309 self.logger.warning("Error on listen socket accept")
310 return -1
Ken Chiang77173992012-10-30 15:44:39 -0700311 self.logger.info(self.host+":"+str(self.port)+": Incoming connection from "+str(addr))
Rich Lanee1da7ea2012-07-26 15:58:45 -0700312
Rich Laneee3586c2012-07-11 17:26:02 -0700313 with self.connect_cv:
Rich Lanee1da7ea2012-07-26 15:58:45 -0700314 (self.switch_socket, self.switch_addr) = (sock, addr)
Rich Lane82ef1832012-12-22 17:04:35 -0800315 self.switch_socket.setsockopt(socket.IPPROTO_TCP,
316 socket.TCP_NODELAY, True)
Rich Lane1a8d5aa2012-10-08 15:40:03 -0700317 if self.initial_hello:
318 self.message_send(hello())
Rich Lanee1da7ea2012-07-26 15:58:45 -0700319 self.connect_cv.notify() # Notify anyone waiting
Dan Talaycof8de5182012-04-12 22:38:41 -0700320 elif s and s == self.switch_socket:
321 for idx in range(3): # debug: try a couple of times
322 try:
323 pkt = self.switch_socket.recv(self.rcv_size)
324 except:
325 self.logger.warning("Error on switch read")
326 return -1
327
328 if not self.active:
329 return 0
330
331 if len(pkt) == 0:
332 self.logger.warning("Zero-length switch read, %d" % idx)
333 else:
334 break
Dan Talayco710438c2010-02-18 15:16:07 -0800335
Dan Talaycof8de5182012-04-12 22:38:41 -0700336 if len(pkt) == 0: # Still no packet
Dan Talayco0fc08bd2012-04-09 16:56:18 -0700337 self.logger.warning("Zero-length switch read; closing cxn")
Dan Talaycof8de5182012-04-12 22:38:41 -0700338 self.logger.info(str(self))
339 return -1
Dan Talayco710438c2010-02-18 15:16:07 -0800340
Dan Talaycod12b6612010-03-07 22:00:46 -0800341 self._pkt_handle(pkt)
Rich Lane4dfd5e12012-12-22 19:48:01 -0800342 elif s and s == self.waker:
343 self.waker.wait()
Dan Talayco710438c2010-02-18 15:16:07 -0800344 else:
Dan Talayco48370102010-03-03 15:17:33 -0800345 self.logger.error("Unknown socket ready: " + str(s))
Dan Talaycof8de5182012-04-12 22:38:41 -0700346 return -1
Dan Talayco710438c2010-02-18 15:16:07 -0800347
Dan Talaycof8de5182012-04-12 22:38:41 -0700348 return 0
Dan Talayco710438c2010-02-18 15:16:07 -0800349
Dan Talayco69ca4d62012-11-15 11:50:22 -0800350 def active_connect(self):
351 """
352 Actively connect to a switch IP addr
353 """
354 try:
355 self.logger.info("Trying active connection to %s" % self.switch)
356 soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
357 soc.connect((self.switch, self.port))
358 self.logger.info("Connected to " + self.switch + " on " +
359 str(self.port))
Rich Lane82ef1832012-12-22 17:04:35 -0800360 soc.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, True)
Dan Talayco69ca4d62012-11-15 11:50:22 -0800361 self.switch_addr = (self.switch, self.port)
362 return soc
363 except (StandardError, socket.error), e:
364 self.logger.error("Could not connect to %s at %d:: %s" %
365 (self.switch, self.port, str(e)))
366 return None
367
Rich Lane32797542012-12-22 17:46:05 -0800368 def wakeup(self):
369 """
370 Wake up the event loop, presumably from another thread.
371 """
Rich Lane4dfd5e12012-12-22 19:48:01 -0800372 self.waker.notify()
Rich Lane32797542012-12-22 17:46:05 -0800373
374 def sockets(self):
375 """
376 Return list of sockets to select on.
377 """
Rich Lane4dfd5e12012-12-22 19:48:01 -0800378 socs = [self.listen_socket, self.switch_socket, self.waker]
Rich Lane32797542012-12-22 17:46:05 -0800379 return [x for x in socs if x]
380
Dan Talayco1b3f6902010-02-15 14:14:19 -0800381 def run(self):
Dan Talayco21c75c72010-02-12 22:59:24 -0800382 """
Dan Talayco1b3f6902010-02-15 14:14:19 -0800383 Activity function for class
Dan Talayco21c75c72010-02-12 22:59:24 -0800384
Dan Talayco1b3f6902010-02-15 14:14:19 -0800385 Assumes connection to switch already exists. Listens on
386 switch_socket for messages until an error (or zero len pkt)
387 occurs.
Dan Talayco21c75c72010-02-12 22:59:24 -0800388
Dan Talayco1b3f6902010-02-15 14:14:19 -0800389 When there is a message on the socket, check for handlers; queue the
390 packet if no one handles the packet.
391
392 See note for controller describing the limitation of a single
393 connection for now.
394 """
395
Rich Lane207502e2012-12-31 14:29:12 -0800396 self.dbg_state = "running"
Ken Chiangadc950f2012-10-05 13:50:03 -0700397
Dan Talayco710438c2010-02-18 15:16:07 -0800398 while self.active:
Dan Talayco710438c2010-02-18 15:16:07 -0800399 try:
400 sel_in, sel_out, sel_err = \
Rich Lane32797542012-12-22 17:46:05 -0800401 select.select(self.sockets(), [], self.sockets(), 1)
Dan Talayco710438c2010-02-18 15:16:07 -0800402 except:
403 print sys.exc_info()
Ken Chiangadc950f2012-10-05 13:50:03 -0700404 self.logger.error("Select error, disconnecting")
405 self.disconnect()
Dan Talayco1b3f6902010-02-15 14:14:19 -0800406
Dan Talayco710438c2010-02-18 15:16:07 -0800407 for s in sel_err:
Ken Chiangadc950f2012-10-05 13:50:03 -0700408 self.logger.error("Got socket error on: " + str(s) + ", disconnecting")
409 self.disconnect()
Dan Talaycof8de5182012-04-12 22:38:41 -0700410
411 for s in sel_in:
412 if self._socket_ready_handle(s) == -1:
Ken Chiangadc950f2012-10-05 13:50:03 -0700413 self.disconnect()
Dan Talayco710438c2010-02-18 15:16:07 -0800414
Dan Talayco710438c2010-02-18 15:16:07 -0800415 # End of main loop
416 self.dbg_state = "closing"
Dan Talayco48370102010-03-03 15:17:33 -0800417 self.logger.info("Exiting controller thread")
Dan Talayco710438c2010-02-18 15:16:07 -0800418 self.shutdown()
419
Rich Lane8806bc42012-07-26 19:18:37 -0700420 def connect(self, timeout=-1):
Dan Talayco710438c2010-02-18 15:16:07 -0800421 """
422 Connect to the switch
423
Rich Lane8806bc42012-07-26 19:18:37 -0700424 @param timeout Block for up to timeout seconds. Pass -1 for the default.
Dan Talayco710438c2010-02-18 15:16:07 -0800425 @return Boolean, True if connected
426 """
427
Dan Talayco69ca4d62012-11-15 11:50:22 -0800428 if not self.passive: # Do active connection now
429 self.logger.info("Attempting to connect to %s on port %s" %
430 (self.switch, str(self.port)))
431 soc = self.active_connect()
432 if soc:
433 self.logger.info("Connected to %s", self.switch)
Dan Talayco69ca4d62012-11-15 11:50:22 -0800434 self.dbg_state = "running"
435 self.switch_socket = soc
Rich Lane32797542012-12-22 17:46:05 -0800436 self.wakeup()
Dan Talayco69ca4d62012-11-15 11:50:22 -0800437 with self.connect_cv:
438 if self.initial_hello:
439 self.message_send(hello())
440 self.connect_cv.notify() # Notify anyone waiting
441 else:
442 self.logger.error("Could not actively connect to switch %s",
443 self.switch)
444 self.active = False
445 else:
446 with self.connect_cv:
447 timed_wait(self.connect_cv, lambda: self.switch_socket,
448 timeout=timeout)
449
Dan Talayco710438c2010-02-18 15:16:07 -0800450 return self.switch_socket is not None
451
Ken Chiangadc950f2012-10-05 13:50:03 -0700452 def disconnect(self, timeout=-1):
453 """
454 If connected to a switch, disconnect.
455 """
456 if self.switch_socket:
Ken Chiangadc950f2012-10-05 13:50:03 -0700457 self.switch_socket.close()
458 self.switch_socket = None
459 self.switch_addr = None
Ken Chiang74be4722012-12-21 13:07:03 -0800460 with self.packets_cv:
461 self.packets = []
Ken Chiange875baf2012-10-09 15:24:40 -0700462 with self.connect_cv:
463 self.connect_cv.notifyAll()
Ken Chiangadc950f2012-10-05 13:50:03 -0700464
465 def wait_disconnected(self, timeout=-1):
466 """
467 @param timeout Block for up to timeout seconds. Pass -1 for the default.
468 @return Boolean, True if disconnected
469 """
470
Ken Chiange875baf2012-10-09 15:24:40 -0700471 with self.connect_cv:
472 timed_wait(self.connect_cv,
Ken Chiangadc950f2012-10-05 13:50:03 -0700473 lambda: True if not self.switch_socket else None,
474 timeout=timeout)
475 return self.switch_socket is None
476
Dan Talayco710438c2010-02-18 15:16:07 -0800477 def kill(self):
478 """
479 Force the controller thread to quit
480
481 Just sets the active state variable to false and expects
482 the select timeout to kick in
483 """
484 self.active = False
Rich Lane32797542012-12-22 17:46:05 -0800485 self.wakeup()
Dan Talayco21c75c72010-02-12 22:59:24 -0800486
Dan Talayco1b3f6902010-02-15 14:14:19 -0800487 def shutdown(self):
Dan Talayco21c75c72010-02-12 22:59:24 -0800488 """
Dan Talayco1b3f6902010-02-15 14:14:19 -0800489 Shutdown the controller closing all sockets
Dan Talayco21c75c72010-02-12 22:59:24 -0800490
Dan Talayco1b3f6902010-02-15 14:14:19 -0800491 @todo Might want to synchronize shutdown with self.sync...
492 """
Dan Talaycof8de5182012-04-12 22:38:41 -0700493
Dan Talayco710438c2010-02-18 15:16:07 -0800494 self.active = False
495 try:
496 self.switch_socket.shutdown(socket.SHUT_RDWR)
497 except:
Dan Talayco48370102010-03-03 15:17:33 -0800498 self.logger.info("Ignoring switch soc shutdown error")
Dan Talayco710438c2010-02-18 15:16:07 -0800499 self.switch_socket = None
Dan Talayco1b3f6902010-02-15 14:14:19 -0800500
Dan Talayco710438c2010-02-18 15:16:07 -0800501 try:
502 self.listen_socket.shutdown(socket.SHUT_RDWR)
503 except:
Dan Talayco48370102010-03-03 15:17:33 -0800504 self.logger.info("Ignoring listen soc shutdown error")
Dan Talayco710438c2010-02-18 15:16:07 -0800505 self.listen_socket = None
Dan Talaycof8de5182012-04-12 22:38:41 -0700506
Rich Laneee3586c2012-07-11 17:26:02 -0700507 # Wakeup condition variables on which controller may be wait
508 with self.xid_cv:
509 self.xid_cv.notifyAll()
Dan Talaycof8de5182012-04-12 22:38:41 -0700510
Rich Laneee3586c2012-07-11 17:26:02 -0700511 with self.connect_cv:
512 self.connect_cv.notifyAll()
Dan Talaycof8de5182012-04-12 22:38:41 -0700513
Rich Lane32797542012-12-22 17:46:05 -0800514 self.wakeup()
Dan Talayco710438c2010-02-18 15:16:07 -0800515 self.dbg_state = "down"
516
Dan Talayco34089522010-02-07 23:07:41 -0800517 def register(self, msg_type, handler):
518 """
519 Register a callback to receive a specific message type.
520
521 Only one handler may be registered for a given message type.
Dan Talaycod12b6612010-03-07 22:00:46 -0800522
523 WARNING: A lock is held during the handler call back, so
524 the handler should not make any blocking calls
525
Dan Talayco34089522010-02-07 23:07:41 -0800526 @param msg_type The type of message to receive. May be DEFAULT
Dan Talayco21c75c72010-02-12 22:59:24 -0800527 for all non-handled packets. The special type, the string "all"
528 will send all packets to the handler.
Dan Talayco34089522010-02-07 23:07:41 -0800529 @param handler The function to call when a message of the given
530 type is received.
531 """
Dan Talayco21c75c72010-02-12 22:59:24 -0800532 # Should check type is valid
533 if not handler and msg_type in self.handlers.keys():
534 del self.handlers[msg_type]
535 return
536 self.handlers[msg_type] = handler
Dan Talayco34089522010-02-07 23:07:41 -0800537
Rich Laneb64ce3d2012-07-26 15:37:57 -0700538 def poll(self, exp_msg=None, timeout=-1):
Dan Talayco34089522010-02-07 23:07:41 -0800539 """
540 Wait for the next OF message received from the switch.
541
542 @param exp_msg If set, return only when this type of message
Dan Talayco48370102010-03-03 15:17:33 -0800543 is received (unless timeout occurs).
Rich Laneb64ce3d2012-07-26 15:37:57 -0700544
545 @param timeout Maximum number of seconds to wait for the message.
546 Pass -1 for the default timeout.
Dan Talayco34089522010-02-07 23:07:41 -0800547
Dan Talaycod7e2dbe2010-02-13 21:51:15 -0800548 @retval A pair (msg, pkt) where msg is a message object and pkt
549 the string representing the packet as received from the socket.
550 This allows additional parsing by the receiver if necessary.
551
Dan Talayco34089522010-02-07 23:07:41 -0800552 The data members in the message are in host endian order.
Dan Talayco48370102010-03-03 15:17:33 -0800553 If an error occurs, (None, None) is returned
Dan Talayco34089522010-02-07 23:07:41 -0800554 """
Dan Talayco34089522010-02-07 23:07:41 -0800555
Ken Chiang77173992012-10-30 15:44:39 -0700556 if exp_msg is not None:
Ed Swierk9e55e282012-08-22 06:57:28 -0700557 self.logger.debug("Poll for %s" % ofp_type_map[exp_msg])
558 else:
559 self.logger.debug("Poll for any OF message")
Rich Laneb64ce3d2012-07-26 15:37:57 -0700560
561 # Take the packet from the queue
Rich Lanec4f071b2012-07-11 17:25:57 -0700562 def grab():
563 if len(self.packets) > 0:
Ken Chiang77173992012-10-30 15:44:39 -0700564 if exp_msg is None:
Rich Lanec4f071b2012-07-11 17:25:57 -0700565 self.logger.debug("Looking for any packet")
566 (msg, pkt) = self.packets.pop(0)
567 return (msg, pkt)
568 else:
569 self.logger.debug("Looking for %s" % ofp_type_map[exp_msg])
570 for i in range(len(self.packets)):
571 msg = self.packets[i][0]
572 self.logger.debug("Checking packets[%d] (%s)" % (i, ofp_type_map[msg.header.type]))
573 if msg.header.type == exp_msg:
574 (msg, pkt) = self.packets.pop(i)
575 return (msg, pkt)
576 # Not found
577 self.logger.debug("Packet not in queue")
Rich Laneb64ce3d2012-07-26 15:37:57 -0700578 return None
Dan Talayco21c75c72010-02-12 22:59:24 -0800579
Rich Lanec4f071b2012-07-11 17:25:57 -0700580 with self.packets_cv:
Rich Lane8806bc42012-07-26 19:18:37 -0700581 ret = timed_wait(self.packets_cv, grab, timeout=timeout)
Rich Lanec4f071b2012-07-11 17:25:57 -0700582
Rich Laneb64ce3d2012-07-26 15:37:57 -0700583 if ret != None:
584 (msg, pkt) = ret
585 self.logger.debug("Got message %s" % str(msg))
586 return (msg, pkt)
587 else:
588 return (None, None)
Dan Talayco1b3f6902010-02-15 14:14:19 -0800589
Dan Talayco0fc08bd2012-04-09 16:56:18 -0700590 def transact(self, msg, timeout=-1, zero_xid=False):
Dan Talayco34089522010-02-07 23:07:41 -0800591 """
Dan Talayco21c75c72010-02-12 22:59:24 -0800592 Run a message transaction with the switch
Dan Talaycoe37999f2010-02-09 15:27:12 -0800593
594 Send the message in msg and wait for a reply with a matching
Dan Talayco21c75c72010-02-12 22:59:24 -0800595 transaction id. Transactions have the highest priority in
596 received message handling.
Dan Talaycoe37999f2010-02-09 15:27:12 -0800597
Dan Talayco21c75c72010-02-12 22:59:24 -0800598 @param msg The message object to send; must not be a string
Rich Lanee1da7ea2012-07-26 15:58:45 -0700599 @param timeout The timeout in seconds; if -1 use default.
Dan Talaycod7e2dbe2010-02-13 21:51:15 -0800600 @param zero_xid Normally, if the XID is 0 an XID will be generated
Ed Swierk9e55e282012-08-22 06:57:28 -0700601 for the message. Set zero_xid to override this behavior
Dan Talayco21c75c72010-02-12 22:59:24 -0800602 @return The matching message object or None if unsuccessful
Dan Talaycoe37999f2010-02-09 15:27:12 -0800603
Dan Talayco34089522010-02-07 23:07:41 -0800604 """
Dan Talayco21c75c72010-02-12 22:59:24 -0800605
Dan Talaycod7e2dbe2010-02-13 21:51:15 -0800606 if not zero_xid and msg.header.xid == 0:
607 msg.header.xid = gen_xid()
608
Dan Talayco0fc08bd2012-04-09 16:56:18 -0700609 self.logger.debug("Running transaction %d" % msg.header.xid)
Dan Talayco21c75c72010-02-12 22:59:24 -0800610
Rich Lane9aca1992012-07-11 17:26:31 -0700611 with self.xid_cv:
612 if self.xid:
613 self.logger.error("Can only run one transaction at a time")
614 return (None, None)
Dan Talaycof8de5182012-04-12 22:38:41 -0700615
Rich Lane9aca1992012-07-11 17:26:31 -0700616 self.xid = msg.header.xid
Dan Talaycod12b6612010-03-07 22:00:46 -0800617 self.xid_response = None
Rich Lane9aca1992012-07-11 17:26:31 -0700618 if self.message_send(msg.pack()) < 0:
619 self.logger.error("Error sending pkt for transaction %d" %
620 msg.header.xid)
621 return (None, None)
622
Rich Lane8806bc42012-07-26 19:18:37 -0700623 self.logger.debug("Waiting for transaction %d" % msg.header.xid)
Rich Lanee1da7ea2012-07-26 15:58:45 -0700624 timed_wait(self.xid_cv, lambda: self.xid_response, timeout=timeout)
Rich Lane9aca1992012-07-11 17:26:31 -0700625
626 if self.xid_response:
627 (resp, pkt) = self.xid_response
628 self.xid_response = None
629 else:
630 (resp, pkt) = (None, None)
631
Dan Talayco09c2c592010-05-13 14:21:52 -0700632 if resp is None:
633 self.logger.warning("No response for xid " + str(self.xid))
634 return (resp, pkt)
Dan Talayco34089522010-02-07 23:07:41 -0800635
Dan Talayco710438c2010-02-18 15:16:07 -0800636 def message_send(self, msg, zero_xid=False):
Dan Talayco34089522010-02-07 23:07:41 -0800637 """
638 Send the message to the switch
Dan Talaycoe37999f2010-02-09 15:27:12 -0800639
Dan Talayco11c26e72010-03-07 22:03:57 -0800640 @param msg A string or OpenFlow message object to be forwarded to
641 the switch.
642 @param zero_xid If msg is an OpenFlow object (not a string) and if
Dan Talayco710438c2010-02-18 15:16:07 -0800643 the XID in the header is 0, then an XID will be generated
Ed Swierk9e55e282012-08-22 06:57:28 -0700644 for the message. Set zero_xid to override this behavior (and keep an
Dan Talayco710438c2010-02-18 15:16:07 -0800645 existing 0 xid)
Dan Talaycoe37999f2010-02-09 15:27:12 -0800646
Ed Swierk9e55e282012-08-22 06:57:28 -0700647 @return 0 on success
Dan Talayco34089522010-02-07 23:07:41 -0800648
Dan Talayco21c75c72010-02-12 22:59:24 -0800649 """
650
Dan Talayco1b3f6902010-02-15 14:14:19 -0800651 if not self.switch_socket:
652 # Sending a string indicates the message is ready to go
Ed Swierk9e55e282012-08-22 06:57:28 -0700653 raise Exception("no socket")
Dan Talayco710438c2010-02-18 15:16:07 -0800654 #@todo If not string, try to pack
Dan Talayco21c75c72010-02-12 22:59:24 -0800655 if type(msg) != type(""):
Ed Swierk9e55e282012-08-22 06:57:28 -0700656 if msg.header.xid == 0 and not zero_xid:
657 msg.header.xid = gen_xid()
658 outpkt = msg.pack()
Dan Talayco710438c2010-02-18 15:16:07 -0800659 else:
660 outpkt = msg
Dan Talayco21c75c72010-02-12 22:59:24 -0800661
Dan Talayco48370102010-03-03 15:17:33 -0800662 self.logger.debug("Sending pkt of len " + str(len(outpkt)))
Ed Swierk9e55e282012-08-22 06:57:28 -0700663 if self.switch_socket.sendall(outpkt) is not None:
664 raise Exception("unknown error on sendall")
Dan Talayco710438c2010-02-18 15:16:07 -0800665
Ed Swierk9e55e282012-08-22 06:57:28 -0700666 return 0
Dan Talayco21c75c72010-02-12 22:59:24 -0800667
668 def __str__(self):
669 string = "Controller:\n"
Dan Talaycod7e2dbe2010-02-13 21:51:15 -0800670 string += " state " + self.dbg_state + "\n"
Dan Talayco21c75c72010-02-12 22:59:24 -0800671 string += " switch_addr " + str(self.switch_addr) + "\n"
672 string += " pending pkts " + str(len(self.packets)) + "\n"
673 string += " total pkts " + str(self.packets_total) + "\n"
674 string += " expired pkts " + str(self.packets_expired) + "\n"
675 string += " handled pkts " + str(self.packets_handled) + "\n"
Dan Talaycoe226eb12010-02-18 23:06:30 -0800676 string += " poll discards " + str(self.poll_discards) + "\n"
Dan Talayco21c75c72010-02-12 22:59:24 -0800677 string += " parse errors " + str(self.parse_errors) + "\n"
678 string += " sock errrors " + str(self.socket_errors) + "\n"
679 string += " max pkts " + str(self.max_pkts) + "\n"
Dan Talayco69ca4d62012-11-15 11:50:22 -0800680 string += " target switch " + str(self.switch) + "\n"
Dan Talayco21c75c72010-02-12 22:59:24 -0800681 string += " host " + str(self.host) + "\n"
682 string += " port " + str(self.port) + "\n"
683 string += " keep_alive " + str(self.keep_alive) + "\n"
Dan Talaycof8de5182012-04-12 22:38:41 -0700684 string += " pkt_in_run " + str(self.pkt_in_run) + "\n"
685 string += " pkt_in_dropped " + str(self.pkt_in_dropped) + "\n"
Dan Talayco21c75c72010-02-12 22:59:24 -0800686 return string
687
688 def show(self):
689 print str(self)
690
691def sample_handler(controller, msg, pkt):
692 """
693 Sample message handler
694
695 This is the prototype for functions registered with the controller
696 class for packet reception
697
698 @param controller The controller calling the handler
699 @param msg The parsed message object
700 @param pkt The raw packet that was received on the socket. This is
701 in case the packet contains extra unparsed data.
702 @returns Boolean value indicating if the packet was handled. If
703 not handled, the packet is placed in the queue for pollers to received
704 """
705 pass