blob: b60823abe64c0de2a5164365cbf48736c808f317 [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]))
Rich Lane376bb402012-12-31 15:20:16 -0800216 self.shutdown()
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
Dan Talayco710438c2010-02-18 15:16:07 -0800480 """
481 self.active = False
Rich Lane32797542012-12-22 17:46:05 -0800482 self.wakeup()
Rich Lane376bb402012-12-31 15:20:16 -0800483 self.join()
Dan Talayco21c75c72010-02-12 22:59:24 -0800484
Dan Talayco1b3f6902010-02-15 14:14:19 -0800485 def shutdown(self):
Dan Talayco21c75c72010-02-12 22:59:24 -0800486 """
Dan Talayco1b3f6902010-02-15 14:14:19 -0800487 Shutdown the controller closing all sockets
Dan Talayco21c75c72010-02-12 22:59:24 -0800488
Dan Talayco1b3f6902010-02-15 14:14:19 -0800489 @todo Might want to synchronize shutdown with self.sync...
490 """
Dan Talaycof8de5182012-04-12 22:38:41 -0700491
Dan Talayco710438c2010-02-18 15:16:07 -0800492 self.active = False
493 try:
494 self.switch_socket.shutdown(socket.SHUT_RDWR)
495 except:
Dan Talayco48370102010-03-03 15:17:33 -0800496 self.logger.info("Ignoring switch soc shutdown error")
Dan Talayco710438c2010-02-18 15:16:07 -0800497 self.switch_socket = None
Dan Talayco1b3f6902010-02-15 14:14:19 -0800498
Dan Talayco710438c2010-02-18 15:16:07 -0800499 try:
500 self.listen_socket.shutdown(socket.SHUT_RDWR)
501 except:
Dan Talayco48370102010-03-03 15:17:33 -0800502 self.logger.info("Ignoring listen soc shutdown error")
Dan Talayco710438c2010-02-18 15:16:07 -0800503 self.listen_socket = None
Dan Talaycof8de5182012-04-12 22:38:41 -0700504
Rich Laneee3586c2012-07-11 17:26:02 -0700505 # Wakeup condition variables on which controller may be wait
506 with self.xid_cv:
507 self.xid_cv.notifyAll()
Dan Talaycof8de5182012-04-12 22:38:41 -0700508
Rich Laneee3586c2012-07-11 17:26:02 -0700509 with self.connect_cv:
510 self.connect_cv.notifyAll()
Dan Talaycof8de5182012-04-12 22:38:41 -0700511
Rich Lane32797542012-12-22 17:46:05 -0800512 self.wakeup()
Dan Talayco710438c2010-02-18 15:16:07 -0800513 self.dbg_state = "down"
514
Dan Talayco34089522010-02-07 23:07:41 -0800515 def register(self, msg_type, handler):
516 """
517 Register a callback to receive a specific message type.
518
519 Only one handler may be registered for a given message type.
Dan Talaycod12b6612010-03-07 22:00:46 -0800520
521 WARNING: A lock is held during the handler call back, so
522 the handler should not make any blocking calls
523
Dan Talayco34089522010-02-07 23:07:41 -0800524 @param msg_type The type of message to receive. May be DEFAULT
Dan Talayco21c75c72010-02-12 22:59:24 -0800525 for all non-handled packets. The special type, the string "all"
526 will send all packets to the handler.
Dan Talayco34089522010-02-07 23:07:41 -0800527 @param handler The function to call when a message of the given
528 type is received.
529 """
Dan Talayco21c75c72010-02-12 22:59:24 -0800530 # Should check type is valid
531 if not handler and msg_type in self.handlers.keys():
532 del self.handlers[msg_type]
533 return
534 self.handlers[msg_type] = handler
Dan Talayco34089522010-02-07 23:07:41 -0800535
Rich Laneb64ce3d2012-07-26 15:37:57 -0700536 def poll(self, exp_msg=None, timeout=-1):
Dan Talayco34089522010-02-07 23:07:41 -0800537 """
538 Wait for the next OF message received from the switch.
539
540 @param exp_msg If set, return only when this type of message
Dan Talayco48370102010-03-03 15:17:33 -0800541 is received (unless timeout occurs).
Rich Laneb64ce3d2012-07-26 15:37:57 -0700542
543 @param timeout Maximum number of seconds to wait for the message.
544 Pass -1 for the default timeout.
Dan Talayco34089522010-02-07 23:07:41 -0800545
Dan Talaycod7e2dbe2010-02-13 21:51:15 -0800546 @retval A pair (msg, pkt) where msg is a message object and pkt
547 the string representing the packet as received from the socket.
548 This allows additional parsing by the receiver if necessary.
549
Dan Talayco34089522010-02-07 23:07:41 -0800550 The data members in the message are in host endian order.
Dan Talayco48370102010-03-03 15:17:33 -0800551 If an error occurs, (None, None) is returned
Dan Talayco34089522010-02-07 23:07:41 -0800552 """
Dan Talayco34089522010-02-07 23:07:41 -0800553
Ken Chiang77173992012-10-30 15:44:39 -0700554 if exp_msg is not None:
Ed Swierk9e55e282012-08-22 06:57:28 -0700555 self.logger.debug("Poll for %s" % ofp_type_map[exp_msg])
556 else:
557 self.logger.debug("Poll for any OF message")
Rich Laneb64ce3d2012-07-26 15:37:57 -0700558
559 # Take the packet from the queue
Rich Lanec4f071b2012-07-11 17:25:57 -0700560 def grab():
561 if len(self.packets) > 0:
Ken Chiang77173992012-10-30 15:44:39 -0700562 if exp_msg is None:
Rich Lanec4f071b2012-07-11 17:25:57 -0700563 self.logger.debug("Looking for any packet")
564 (msg, pkt) = self.packets.pop(0)
565 return (msg, pkt)
566 else:
567 self.logger.debug("Looking for %s" % ofp_type_map[exp_msg])
568 for i in range(len(self.packets)):
569 msg = self.packets[i][0]
570 self.logger.debug("Checking packets[%d] (%s)" % (i, ofp_type_map[msg.header.type]))
571 if msg.header.type == exp_msg:
572 (msg, pkt) = self.packets.pop(i)
573 return (msg, pkt)
574 # Not found
575 self.logger.debug("Packet not in queue")
Rich Laneb64ce3d2012-07-26 15:37:57 -0700576 return None
Dan Talayco21c75c72010-02-12 22:59:24 -0800577
Rich Lanec4f071b2012-07-11 17:25:57 -0700578 with self.packets_cv:
Rich Lane8806bc42012-07-26 19:18:37 -0700579 ret = timed_wait(self.packets_cv, grab, timeout=timeout)
Rich Lanec4f071b2012-07-11 17:25:57 -0700580
Rich Laneb64ce3d2012-07-26 15:37:57 -0700581 if ret != None:
582 (msg, pkt) = ret
583 self.logger.debug("Got message %s" % str(msg))
584 return (msg, pkt)
585 else:
586 return (None, None)
Dan Talayco1b3f6902010-02-15 14:14:19 -0800587
Dan Talayco0fc08bd2012-04-09 16:56:18 -0700588 def transact(self, msg, timeout=-1, zero_xid=False):
Dan Talayco34089522010-02-07 23:07:41 -0800589 """
Dan Talayco21c75c72010-02-12 22:59:24 -0800590 Run a message transaction with the switch
Dan Talaycoe37999f2010-02-09 15:27:12 -0800591
592 Send the message in msg and wait for a reply with a matching
Dan Talayco21c75c72010-02-12 22:59:24 -0800593 transaction id. Transactions have the highest priority in
594 received message handling.
Dan Talaycoe37999f2010-02-09 15:27:12 -0800595
Dan Talayco21c75c72010-02-12 22:59:24 -0800596 @param msg The message object to send; must not be a string
Rich Lanee1da7ea2012-07-26 15:58:45 -0700597 @param timeout The timeout in seconds; if -1 use default.
Dan Talaycod7e2dbe2010-02-13 21:51:15 -0800598 @param zero_xid Normally, if the XID is 0 an XID will be generated
Ed Swierk9e55e282012-08-22 06:57:28 -0700599 for the message. Set zero_xid to override this behavior
Dan Talayco21c75c72010-02-12 22:59:24 -0800600 @return The matching message object or None if unsuccessful
Dan Talaycoe37999f2010-02-09 15:27:12 -0800601
Dan Talayco34089522010-02-07 23:07:41 -0800602 """
Dan Talayco21c75c72010-02-12 22:59:24 -0800603
Dan Talaycod7e2dbe2010-02-13 21:51:15 -0800604 if not zero_xid and msg.header.xid == 0:
605 msg.header.xid = gen_xid()
606
Dan Talayco0fc08bd2012-04-09 16:56:18 -0700607 self.logger.debug("Running transaction %d" % msg.header.xid)
Dan Talayco21c75c72010-02-12 22:59:24 -0800608
Rich Lane9aca1992012-07-11 17:26:31 -0700609 with self.xid_cv:
610 if self.xid:
611 self.logger.error("Can only run one transaction at a time")
612 return (None, None)
Dan Talaycof8de5182012-04-12 22:38:41 -0700613
Rich Lane9aca1992012-07-11 17:26:31 -0700614 self.xid = msg.header.xid
Dan Talaycod12b6612010-03-07 22:00:46 -0800615 self.xid_response = None
Rich Lane9aca1992012-07-11 17:26:31 -0700616 if self.message_send(msg.pack()) < 0:
617 self.logger.error("Error sending pkt for transaction %d" %
618 msg.header.xid)
619 return (None, None)
620
Rich Lane8806bc42012-07-26 19:18:37 -0700621 self.logger.debug("Waiting for transaction %d" % msg.header.xid)
Rich Lanee1da7ea2012-07-26 15:58:45 -0700622 timed_wait(self.xid_cv, lambda: self.xid_response, timeout=timeout)
Rich Lane9aca1992012-07-11 17:26:31 -0700623
624 if self.xid_response:
625 (resp, pkt) = self.xid_response
626 self.xid_response = None
627 else:
628 (resp, pkt) = (None, None)
629
Dan Talayco09c2c592010-05-13 14:21:52 -0700630 if resp is None:
631 self.logger.warning("No response for xid " + str(self.xid))
632 return (resp, pkt)
Dan Talayco34089522010-02-07 23:07:41 -0800633
Dan Talayco710438c2010-02-18 15:16:07 -0800634 def message_send(self, msg, zero_xid=False):
Dan Talayco34089522010-02-07 23:07:41 -0800635 """
636 Send the message to the switch
Dan Talaycoe37999f2010-02-09 15:27:12 -0800637
Dan Talayco11c26e72010-03-07 22:03:57 -0800638 @param msg A string or OpenFlow message object to be forwarded to
639 the switch.
640 @param zero_xid If msg is an OpenFlow object (not a string) and if
Dan Talayco710438c2010-02-18 15:16:07 -0800641 the XID in the header is 0, then an XID will be generated
Ed Swierk9e55e282012-08-22 06:57:28 -0700642 for the message. Set zero_xid to override this behavior (and keep an
Dan Talayco710438c2010-02-18 15:16:07 -0800643 existing 0 xid)
Dan Talaycoe37999f2010-02-09 15:27:12 -0800644
Ed Swierk9e55e282012-08-22 06:57:28 -0700645 @return 0 on success
Dan Talayco34089522010-02-07 23:07:41 -0800646
Dan Talayco21c75c72010-02-12 22:59:24 -0800647 """
648
Dan Talayco1b3f6902010-02-15 14:14:19 -0800649 if not self.switch_socket:
650 # Sending a string indicates the message is ready to go
Ed Swierk9e55e282012-08-22 06:57:28 -0700651 raise Exception("no socket")
Dan Talayco710438c2010-02-18 15:16:07 -0800652 #@todo If not string, try to pack
Dan Talayco21c75c72010-02-12 22:59:24 -0800653 if type(msg) != type(""):
Ed Swierk9e55e282012-08-22 06:57:28 -0700654 if msg.header.xid == 0 and not zero_xid:
655 msg.header.xid = gen_xid()
656 outpkt = msg.pack()
Dan Talayco710438c2010-02-18 15:16:07 -0800657 else:
658 outpkt = msg
Dan Talayco21c75c72010-02-12 22:59:24 -0800659
Rich Lanef18980d2012-12-31 17:11:41 -0800660 msg_version, msg_type, msg_len, msg_xid = struct.unpack_from("!BBHL", outpkt)
661 self.logger.debug("Msg out: buf len %d. hdr.type %s. hdr.len %d",
662 len(outpkt),
663 ofp_type_map.get(msg_type, "unknown (%d)" % msg_type),
664 msg_len)
Ed Swierk9e55e282012-08-22 06:57:28 -0700665 if self.switch_socket.sendall(outpkt) is not None:
666 raise Exception("unknown error on sendall")
Dan Talayco710438c2010-02-18 15:16:07 -0800667
Ed Swierk9e55e282012-08-22 06:57:28 -0700668 return 0
Dan Talayco21c75c72010-02-12 22:59:24 -0800669
670 def __str__(self):
671 string = "Controller:\n"
Dan Talaycod7e2dbe2010-02-13 21:51:15 -0800672 string += " state " + self.dbg_state + "\n"
Dan Talayco21c75c72010-02-12 22:59:24 -0800673 string += " switch_addr " + str(self.switch_addr) + "\n"
674 string += " pending pkts " + str(len(self.packets)) + "\n"
675 string += " total pkts " + str(self.packets_total) + "\n"
676 string += " expired pkts " + str(self.packets_expired) + "\n"
677 string += " handled pkts " + str(self.packets_handled) + "\n"
Dan Talaycoe226eb12010-02-18 23:06:30 -0800678 string += " poll discards " + str(self.poll_discards) + "\n"
Dan Talayco21c75c72010-02-12 22:59:24 -0800679 string += " parse errors " + str(self.parse_errors) + "\n"
680 string += " sock errrors " + str(self.socket_errors) + "\n"
681 string += " max pkts " + str(self.max_pkts) + "\n"
Dan Talayco69ca4d62012-11-15 11:50:22 -0800682 string += " target switch " + str(self.switch) + "\n"
Dan Talayco21c75c72010-02-12 22:59:24 -0800683 string += " host " + str(self.host) + "\n"
684 string += " port " + str(self.port) + "\n"
685 string += " keep_alive " + str(self.keep_alive) + "\n"
Dan Talaycof8de5182012-04-12 22:38:41 -0700686 string += " pkt_in_run " + str(self.pkt_in_run) + "\n"
687 string += " pkt_in_dropped " + str(self.pkt_in_dropped) + "\n"
Dan Talayco21c75c72010-02-12 22:59:24 -0800688 return string
689
690 def show(self):
691 print str(self)
692
693def sample_handler(controller, msg, pkt):
694 """
695 Sample message handler
696
697 This is the prototype for functions registered with the controller
698 class for packet reception
699
700 @param controller The controller calling the handler
701 @param msg The parsed message object
702 @param pkt The raw packet that was received on the socket. This is
703 in case the packet contains extra unparsed data.
704 @returns Boolean value indicating if the packet was handled. If
705 not handled, the packet is placed in the queue for pollers to received
706 """
707 pass