blob: b4c3ac5e0db820005a0c5d92b7d898db92f72765 [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.socs = []
106 self.connect_cv = Condition()
Dan Talaycoe226eb12010-02-18 23:06:30 -0800107 self.message_cv = Condition()
Dan Talayco1b3f6902010-02-15 14:14:19 -0800108
109 # Counters
Dan Talayco21c75c72010-02-12 22:59:24 -0800110 self.socket_errors = 0
111 self.parse_errors = 0
Dan Talayco21c75c72010-02-12 22:59:24 -0800112 self.packets_total = 0
Dan Talayco1b3f6902010-02-15 14:14:19 -0800113 self.packets_expired = 0
114 self.packets_handled = 0
Dan Talaycoe226eb12010-02-18 23:06:30 -0800115 self.poll_discards = 0
Dan Talayco1b3f6902010-02-15 14:14:19 -0800116
117 # State
Dan Talayco21c75c72010-02-12 22:59:24 -0800118 self.sync = Lock()
119 self.handlers = {}
120 self.keep_alive = False
Dan Talayco710438c2010-02-18 15:16:07 -0800121 self.active = True
122 self.initial_hello = True
Dan Talayco1b3f6902010-02-15 14:14:19 -0800123
Rich Lanec4f071b2012-07-11 17:25:57 -0700124 # OpenFlow message/packet queue
125 # Protected by the packets_cv lock / condition variable
126 self.packets = []
127 self.packets_cv = Condition()
128
Dan Talayco1b3f6902010-02-15 14:14:19 -0800129 # Settings
130 self.max_pkts = max_pkts
Dan Talayco69ca4d62012-11-15 11:50:22 -0800131 self.switch = switch
132 self.passive = not self.switch
Dan Talayco48370102010-03-03 15:17:33 -0800133 self.host = host
134 self.port = port
Dan Talaycod7e2dbe2010-02-13 21:51:15 -0800135 self.dbg_state = "init"
Dan Talayco48370102010-03-03 15:17:33 -0800136 self.logger = logging.getLogger("controller")
Dan Talaycof8de5182012-04-12 22:38:41 -0700137 self.filter_packet_in = False # Drop "excessive" packet ins
138 self.pkt_in_run = 0 # Count on run of packet ins
139 self.pkt_in_filter_limit = 50 # Count on run of packet ins
140 self.pkt_in_dropped = 0 # Total dropped packet ins
141 self.transact_to = 15 # Transact timeout default value; add to config
Dan Talayco1b3f6902010-02-15 14:14:19 -0800142
Dan Talaycoe226eb12010-02-18 23:06:30 -0800143 # Transaction and message type waiting variables
144 # xid_cv: Condition variable (semaphore) for packet waiters
Dan Talayco21c75c72010-02-12 22:59:24 -0800145 # xid: Transaction ID being waited on
146 # xid_response: Transaction response message
147 self.xid_cv = Condition()
148 self.xid = None
149 self.xid_response = None
150
Rob Sherwoode3e452a2012-03-06 09:24:26 -0800151 self.buffered_input = ""
Dan Talaycoe226eb12010-02-18 23:06:30 -0800152
Dan Talaycof8de5182012-04-12 22:38:41 -0700153 def filter_packet(self, rawmsg, hdr):
154 """
155 Check if packet should be filtered
156
157 Currently filters packet in messages
158 @return Boolean, True if packet should be dropped
159 """
160 # Add check for packet in and rate limit
161 if self.filter_packet_in:
Rich Lanec4f071b2012-07-11 17:25:57 -0700162 # If we were dropping packets, report number dropped
163 # TODO dont drop expected packet ins
164 if self.pkt_in_run > self.pkt_in_filter_limit:
165 self.logger.debug("Dropped %d packet ins (%d total)"
166 % ((self.pkt_in_run -
167 self.pkt_in_filter_limit),
168 self.pkt_in_dropped))
169 self.pkt_in_run = 0
Dan Talaycof8de5182012-04-12 22:38:41 -0700170
171 return False
172
Dan Talaycod12b6612010-03-07 22:00:46 -0800173 def _pkt_handle(self, pkt):
174 """
175 Check for all packet handling conditions
176
177 Parse and verify message
178 Check if XID matches something waiting
179 Check if message is being expected for a poll operation
180 Check if keep alive is on and message is an echo request
181 Check if any registered handler wants the packet
182 Enqueue if none of those conditions is met
183
184 an echo request in case keep_alive is true, followed by
185 registered message handlers.
Glen Gibb6d467062010-07-08 16:15:08 -0700186 @param pkt The raw packet (string) which may contain multiple OF msgs
Dan Talaycod12b6612010-03-07 22:00:46 -0800187 """
Rob Sherwoode3e452a2012-03-06 09:24:26 -0800188
189 # snag any left over data from last read()
190 pkt = self.buffered_input + pkt
191 self.buffered_input = ""
192
Glen Gibb6d467062010-07-08 16:15:08 -0700193 # Process each of the OF msgs inside the pkt
194 offset = 0
195 while offset < len(pkt):
196 # Parse the header to get type
197 hdr = of_header_parse(pkt[offset:])
Dan Talaycof8de5182012-04-12 22:38:41 -0700198 if not hdr or hdr.length == 0:
199 self.logger.error("Could not parse header")
200 self.logger.error("pkt len %d." % len(pkt))
201 if hdr:
202 self.logger.error("hdr len %d." % hdr.length)
203 self.logger.error("%s" % hex_dump_buffer(pkt[:200]))
204 self.kill()
Dan Talaycod12b6612010-03-07 22:00:46 -0800205 return
206
Glen Gibb6d467062010-07-08 16:15:08 -0700207 # Extract the raw message bytes
Ed Swierk836e5bd2012-03-20 11:08:53 -0700208 if (offset + hdr.length) > len(pkt):
Rob Sherwoode3e452a2012-03-06 09:24:26 -0800209 break
Glen Gibb6d467062010-07-08 16:15:08 -0700210 rawmsg = pkt[offset : offset + hdr.length]
Dan Talayco4306d3e2011-09-07 09:42:26 -0700211 offset += hdr.length
Dan Talaycof8de5182012-04-12 22:38:41 -0700212
213 if self.filter_packet(rawmsg, hdr):
214 continue
215
216 self.logger.debug("Msg in: buf len %d. hdr.type %s. hdr.len %d" %
217 (len(pkt), ofp_type_map[hdr.type], hdr.length))
Glen Gibb6d467062010-07-08 16:15:08 -0700218 if hdr.version != OFP_VERSION:
219 self.logger.error("Version %d does not match OFTest version %d"
220 % (hdr.version, OFP_VERSION))
221 print "Version %d does not match OFTest version %d" % \
222 (hdr.version, OFP_VERSION)
Ken Chiangadc950f2012-10-05 13:50:03 -0700223 self.disconnect()
Dan Talaycof8de5182012-04-12 22:38:41 -0700224 return
Dan Talaycod12b6612010-03-07 22:00:46 -0800225
Glen Gibb6d467062010-07-08 16:15:08 -0700226 msg = of_message_parse(rawmsg)
227 if not msg:
228 self.parse_errors += 1
229 self.logger.warn("Could not parse message")
230 continue
231
Rich Lanec4f071b2012-07-11 17:25:57 -0700232 with self.sync:
233 # Check if transaction is waiting
234 with self.xid_cv:
235 if self.xid and hdr.xid == self.xid:
236 self.logger.debug("Matched expected XID " + str(hdr.xid))
237 self.xid_response = (msg, rawmsg)
238 self.xid = None
239 self.xid_cv.notify()
240 continue
Glen Gibb6d467062010-07-08 16:15:08 -0700241
Rich Lanec4f071b2012-07-11 17:25:57 -0700242 # Check if keep alive is set; if so, respond to echo requests
243 if self.keep_alive:
244 if hdr.type == OFPT_ECHO_REQUEST:
245 self.logger.debug("Responding to echo request")
246 rep = echo_reply()
247 rep.header.xid = hdr.xid
248 # Ignoring additional data
249 if self.message_send(rep.pack(), zero_xid=True) < 0:
250 self.logger.error("Error sending echo reply")
251 continue
Glen Gibb6d467062010-07-08 16:15:08 -0700252
Rich Lanec4f071b2012-07-11 17:25:57 -0700253 # Now check for message handlers; preference is given to
254 # handlers for a specific packet
255 handled = False
256 if hdr.type in self.handlers.keys():
257 handled = self.handlers[hdr.type](self, msg, rawmsg)
258 if not handled and ("all" in self.handlers.keys()):
259 handled = self.handlers["all"](self, msg, rawmsg)
Glen Gibb6d467062010-07-08 16:15:08 -0700260
Rich Lanec4f071b2012-07-11 17:25:57 -0700261 if not handled: # Not handled, enqueue
262 self.logger.debug("Enqueuing pkt type " + ofp_type_map[hdr.type])
263 with self.packets_cv:
264 if len(self.packets) >= self.max_pkts:
265 self.packets.pop(0)
266 self.packets_expired += 1
267 self.packets.append((msg, rawmsg))
268 self.packets_cv.notify_all()
269 self.packets_total += 1
270 else:
271 self.packets_handled += 1
272 self.logger.debug("Message handled by callback")
Glen Gibb6d467062010-07-08 16:15:08 -0700273
Rob Sherwoode3e452a2012-03-06 09:24:26 -0800274 # end of 'while offset < len(pkt)'
275 # note that if offset = len(pkt), this is
276 # appends a harmless empty string
277 self.buffered_input += pkt[offset:]
Dan Talaycod12b6612010-03-07 22:00:46 -0800278
Dan Talayco710438c2010-02-18 15:16:07 -0800279 def _socket_ready_handle(self, s):
280 """
281 Handle an input-ready socket
Dan Talaycof8de5182012-04-12 22:38:41 -0700282
Dan Talayco710438c2010-02-18 15:16:07 -0800283 @param s The socket object that is ready
Dan Talaycof8de5182012-04-12 22:38:41 -0700284 @returns 0 on success, -1 on error
Dan Talayco710438c2010-02-18 15:16:07 -0800285 """
286
Dan Talayco69ca4d62012-11-15 11:50:22 -0800287 if self.passive and s and s == self.listen_socket:
Dan Talayco710438c2010-02-18 15:16:07 -0800288 if self.switch_socket:
Rich Lanee1da7ea2012-07-26 15:58:45 -0700289 self.logger.warning("Ignoring incoming connection; already connected to switch")
Rich Laneb4f8ecb2012-09-25 09:36:26 -0700290 (sock, addr) = self.listen_socket.accept()
291 sock.close()
Rich Lanee1da7ea2012-07-26 15:58:45 -0700292 return 0
Dan Talayco710438c2010-02-18 15:16:07 -0800293
Ken Chiange875baf2012-10-09 15:24:40 -0700294 try:
295 (sock, addr) = self.listen_socket.accept()
296 except:
297 self.logger.warning("Error on listen socket accept")
298 return -1
Rich Lanee1da7ea2012-07-26 15:58:45 -0700299 self.socs.append(sock)
Ken Chiang77173992012-10-30 15:44:39 -0700300 self.logger.info(self.host+":"+str(self.port)+": Incoming connection from "+str(addr))
Rich Lanee1da7ea2012-07-26 15:58:45 -0700301
Rich Laneee3586c2012-07-11 17:26:02 -0700302 with self.connect_cv:
Rich Lanee1da7ea2012-07-26 15:58:45 -0700303 (self.switch_socket, self.switch_addr) = (sock, addr)
Rich Lane1a8d5aa2012-10-08 15:40:03 -0700304 if self.initial_hello:
305 self.message_send(hello())
Rich Lanee1da7ea2012-07-26 15:58:45 -0700306 self.connect_cv.notify() # Notify anyone waiting
Dan Talaycof8de5182012-04-12 22:38:41 -0700307 elif s and s == self.switch_socket:
308 for idx in range(3): # debug: try a couple of times
309 try:
310 pkt = self.switch_socket.recv(self.rcv_size)
311 except:
312 self.logger.warning("Error on switch read")
313 return -1
314
315 if not self.active:
316 return 0
317
318 if len(pkt) == 0:
319 self.logger.warning("Zero-length switch read, %d" % idx)
320 else:
321 break
Dan Talayco710438c2010-02-18 15:16:07 -0800322
Dan Talaycof8de5182012-04-12 22:38:41 -0700323 if len(pkt) == 0: # Still no packet
Dan Talayco0fc08bd2012-04-09 16:56:18 -0700324 self.logger.warning("Zero-length switch read; closing cxn")
Dan Talaycof8de5182012-04-12 22:38:41 -0700325 self.logger.info(str(self))
326 return -1
Dan Talayco710438c2010-02-18 15:16:07 -0800327
Dan Talaycod12b6612010-03-07 22:00:46 -0800328 self._pkt_handle(pkt)
Dan Talayco710438c2010-02-18 15:16:07 -0800329 else:
Dan Talayco48370102010-03-03 15:17:33 -0800330 self.logger.error("Unknown socket ready: " + str(s))
Dan Talaycof8de5182012-04-12 22:38:41 -0700331 return -1
Dan Talayco710438c2010-02-18 15:16:07 -0800332
Dan Talaycof8de5182012-04-12 22:38:41 -0700333 return 0
Dan Talayco710438c2010-02-18 15:16:07 -0800334
Dan Talayco69ca4d62012-11-15 11:50:22 -0800335 def active_connect(self):
336 """
337 Actively connect to a switch IP addr
338 """
339 try:
340 self.logger.info("Trying active connection to %s" % self.switch)
341 soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
342 soc.connect((self.switch, self.port))
343 self.logger.info("Connected to " + self.switch + " on " +
344 str(self.port))
345 self.switch_addr = (self.switch, self.port)
346 return soc
347 except (StandardError, socket.error), e:
348 self.logger.error("Could not connect to %s at %d:: %s" %
349 (self.switch, self.port, str(e)))
350 return None
351
Dan Talayco1b3f6902010-02-15 14:14:19 -0800352 def run(self):
Dan Talayco21c75c72010-02-12 22:59:24 -0800353 """
Dan Talayco1b3f6902010-02-15 14:14:19 -0800354 Activity function for class
Dan Talayco21c75c72010-02-12 22:59:24 -0800355
Dan Talayco1b3f6902010-02-15 14:14:19 -0800356 Assumes connection to switch already exists. Listens on
357 switch_socket for messages until an error (or zero len pkt)
358 occurs.
Dan Talayco21c75c72010-02-12 22:59:24 -0800359
Dan Talayco1b3f6902010-02-15 14:14:19 -0800360 When there is a message on the socket, check for handlers; queue the
361 packet if no one handles the packet.
362
363 See note for controller describing the limitation of a single
364 connection for now.
365 """
366
Dan Talayco710438c2010-02-18 15:16:07 -0800367 self.dbg_state = "starting"
Dan Talayco1b3f6902010-02-15 14:14:19 -0800368
Dan Talayco710438c2010-02-18 15:16:07 -0800369 # Create listen socket
Dan Talayco69ca4d62012-11-15 11:50:22 -0800370 if self.passive:
371 self.logger.info("Create/listen at " + self.host + ":" +
372 str(self.port))
373 self.listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
374 self.listen_socket.setsockopt(socket.SOL_SOCKET,
375 socket.SO_REUSEADDR, 1)
376 self.listen_socket.bind((self.host, self.port))
377 self.dbg_state = "listening"
378 self.listen_socket.listen(LISTEN_QUEUE_SIZE)
Dan Talayco21c75c72010-02-12 22:59:24 -0800379
Dan Talayco69ca4d62012-11-15 11:50:22 -0800380 self.logger.info("Listening for switch connection")
381 self.socs = [self.listen_socket]
382 self.dbg_state = "running"
Ken Chiangadc950f2012-10-05 13:50:03 -0700383
Dan Talayco710438c2010-02-18 15:16:07 -0800384 while self.active:
Dan Talayco710438c2010-02-18 15:16:07 -0800385 try:
386 sel_in, sel_out, sel_err = \
387 select.select(self.socs, [], self.socs, 1)
388 except:
389 print sys.exc_info()
Ken Chiangadc950f2012-10-05 13:50:03 -0700390 self.logger.error("Select error, disconnecting")
391 self.disconnect()
Dan Talayco1b3f6902010-02-15 14:14:19 -0800392
Dan Talayco710438c2010-02-18 15:16:07 -0800393 for s in sel_err:
Ken Chiangadc950f2012-10-05 13:50:03 -0700394 self.logger.error("Got socket error on: " + str(s) + ", disconnecting")
395 self.disconnect()
Dan Talaycof8de5182012-04-12 22:38:41 -0700396
397 for s in sel_in:
398 if self._socket_ready_handle(s) == -1:
Ken Chiangadc950f2012-10-05 13:50:03 -0700399 self.disconnect()
Dan Talayco710438c2010-02-18 15:16:07 -0800400
Dan Talayco710438c2010-02-18 15:16:07 -0800401 # End of main loop
402 self.dbg_state = "closing"
Dan Talayco48370102010-03-03 15:17:33 -0800403 self.logger.info("Exiting controller thread")
Dan Talayco710438c2010-02-18 15:16:07 -0800404 self.shutdown()
405
Rich Lane8806bc42012-07-26 19:18:37 -0700406 def connect(self, timeout=-1):
Dan Talayco710438c2010-02-18 15:16:07 -0800407 """
408 Connect to the switch
409
Rich Lane8806bc42012-07-26 19:18:37 -0700410 @param timeout Block for up to timeout seconds. Pass -1 for the default.
Dan Talayco710438c2010-02-18 15:16:07 -0800411 @return Boolean, True if connected
412 """
413
Dan Talayco69ca4d62012-11-15 11:50:22 -0800414 if not self.passive: # Do active connection now
415 self.logger.info("Attempting to connect to %s on port %s" %
416 (self.switch, str(self.port)))
417 soc = self.active_connect()
418 if soc:
419 self.logger.info("Connected to %s", self.switch)
420 self.socs = [soc]
421 self.dbg_state = "running"
422 self.switch_socket = soc
423 with self.connect_cv:
424 if self.initial_hello:
425 self.message_send(hello())
426 self.connect_cv.notify() # Notify anyone waiting
427 else:
428 self.logger.error("Could not actively connect to switch %s",
429 self.switch)
430 self.active = False
431 else:
432 with self.connect_cv:
433 timed_wait(self.connect_cv, lambda: self.switch_socket,
434 timeout=timeout)
435
Dan Talayco710438c2010-02-18 15:16:07 -0800436 return self.switch_socket is not None
437
Ken Chiangadc950f2012-10-05 13:50:03 -0700438 def disconnect(self, timeout=-1):
439 """
440 If connected to a switch, disconnect.
441 """
442 if self.switch_socket:
443 self.socs.remove(self.switch_socket)
444 self.switch_socket.close()
445 self.switch_socket = None
446 self.switch_addr = None
Ken Chiange875baf2012-10-09 15:24:40 -0700447 with self.connect_cv:
448 self.connect_cv.notifyAll()
Ken Chiangadc950f2012-10-05 13:50:03 -0700449
450 def wait_disconnected(self, timeout=-1):
451 """
452 @param timeout Block for up to timeout seconds. Pass -1 for the default.
453 @return Boolean, True if disconnected
454 """
455
Ken Chiange875baf2012-10-09 15:24:40 -0700456 with self.connect_cv:
457 timed_wait(self.connect_cv,
Ken Chiangadc950f2012-10-05 13:50:03 -0700458 lambda: True if not self.switch_socket else None,
459 timeout=timeout)
460 return self.switch_socket is None
461
Dan Talayco710438c2010-02-18 15:16:07 -0800462 def kill(self):
463 """
464 Force the controller thread to quit
465
466 Just sets the active state variable to false and expects
467 the select timeout to kick in
468 """
469 self.active = False
Dan Talayco21c75c72010-02-12 22:59:24 -0800470
Dan Talayco1b3f6902010-02-15 14:14:19 -0800471 def shutdown(self):
Dan Talayco21c75c72010-02-12 22:59:24 -0800472 """
Dan Talayco1b3f6902010-02-15 14:14:19 -0800473 Shutdown the controller closing all sockets
Dan Talayco21c75c72010-02-12 22:59:24 -0800474
Dan Talayco1b3f6902010-02-15 14:14:19 -0800475 @todo Might want to synchronize shutdown with self.sync...
476 """
Dan Talaycof8de5182012-04-12 22:38:41 -0700477
Dan Talayco710438c2010-02-18 15:16:07 -0800478 self.active = False
479 try:
480 self.switch_socket.shutdown(socket.SHUT_RDWR)
481 except:
Dan Talayco48370102010-03-03 15:17:33 -0800482 self.logger.info("Ignoring switch soc shutdown error")
Dan Talayco710438c2010-02-18 15:16:07 -0800483 self.switch_socket = None
Dan Talayco1b3f6902010-02-15 14:14:19 -0800484
Dan Talayco710438c2010-02-18 15:16:07 -0800485 try:
486 self.listen_socket.shutdown(socket.SHUT_RDWR)
487 except:
Dan Talayco48370102010-03-03 15:17:33 -0800488 self.logger.info("Ignoring listen soc shutdown error")
Dan Talayco710438c2010-02-18 15:16:07 -0800489 self.listen_socket = None
Dan Talaycof8de5182012-04-12 22:38:41 -0700490
Rich Laneee3586c2012-07-11 17:26:02 -0700491 # Wakeup condition variables on which controller may be wait
492 with self.xid_cv:
493 self.xid_cv.notifyAll()
Dan Talaycof8de5182012-04-12 22:38:41 -0700494
Rich Laneee3586c2012-07-11 17:26:02 -0700495 with self.connect_cv:
496 self.connect_cv.notifyAll()
Dan Talaycof8de5182012-04-12 22:38:41 -0700497
Dan Talayco710438c2010-02-18 15:16:07 -0800498 self.dbg_state = "down"
499
Dan Talayco34089522010-02-07 23:07:41 -0800500 def register(self, msg_type, handler):
501 """
502 Register a callback to receive a specific message type.
503
504 Only one handler may be registered for a given message type.
Dan Talaycod12b6612010-03-07 22:00:46 -0800505
506 WARNING: A lock is held during the handler call back, so
507 the handler should not make any blocking calls
508
Dan Talayco34089522010-02-07 23:07:41 -0800509 @param msg_type The type of message to receive. May be DEFAULT
Dan Talayco21c75c72010-02-12 22:59:24 -0800510 for all non-handled packets. The special type, the string "all"
511 will send all packets to the handler.
Dan Talayco34089522010-02-07 23:07:41 -0800512 @param handler The function to call when a message of the given
513 type is received.
514 """
Dan Talayco21c75c72010-02-12 22:59:24 -0800515 # Should check type is valid
516 if not handler and msg_type in self.handlers.keys():
517 del self.handlers[msg_type]
518 return
519 self.handlers[msg_type] = handler
Dan Talayco34089522010-02-07 23:07:41 -0800520
Rich Laneb64ce3d2012-07-26 15:37:57 -0700521 def poll(self, exp_msg=None, timeout=-1):
Dan Talayco34089522010-02-07 23:07:41 -0800522 """
523 Wait for the next OF message received from the switch.
524
525 @param exp_msg If set, return only when this type of message
Dan Talayco48370102010-03-03 15:17:33 -0800526 is received (unless timeout occurs).
Rich Laneb64ce3d2012-07-26 15:37:57 -0700527
528 @param timeout Maximum number of seconds to wait for the message.
529 Pass -1 for the default timeout.
Dan Talayco34089522010-02-07 23:07:41 -0800530
Dan Talaycod7e2dbe2010-02-13 21:51:15 -0800531 @retval A pair (msg, pkt) where msg is a message object and pkt
532 the string representing the packet as received from the socket.
533 This allows additional parsing by the receiver if necessary.
534
Dan Talayco34089522010-02-07 23:07:41 -0800535 The data members in the message are in host endian order.
Dan Talayco48370102010-03-03 15:17:33 -0800536 If an error occurs, (None, None) is returned
Dan Talayco34089522010-02-07 23:07:41 -0800537 """
Dan Talayco34089522010-02-07 23:07:41 -0800538
Ken Chiang77173992012-10-30 15:44:39 -0700539 if exp_msg is not None:
Ed Swierk9e55e282012-08-22 06:57:28 -0700540 self.logger.debug("Poll for %s" % ofp_type_map[exp_msg])
541 else:
542 self.logger.debug("Poll for any OF message")
Rich Laneb64ce3d2012-07-26 15:37:57 -0700543
544 # Take the packet from the queue
Rich Lanec4f071b2012-07-11 17:25:57 -0700545 def grab():
546 if len(self.packets) > 0:
Ken Chiang77173992012-10-30 15:44:39 -0700547 if exp_msg is None:
Rich Lanec4f071b2012-07-11 17:25:57 -0700548 self.logger.debug("Looking for any packet")
549 (msg, pkt) = self.packets.pop(0)
550 return (msg, pkt)
551 else:
552 self.logger.debug("Looking for %s" % ofp_type_map[exp_msg])
553 for i in range(len(self.packets)):
554 msg = self.packets[i][0]
555 self.logger.debug("Checking packets[%d] (%s)" % (i, ofp_type_map[msg.header.type]))
556 if msg.header.type == exp_msg:
557 (msg, pkt) = self.packets.pop(i)
558 return (msg, pkt)
559 # Not found
560 self.logger.debug("Packet not in queue")
Rich Laneb64ce3d2012-07-26 15:37:57 -0700561 return None
Dan Talayco21c75c72010-02-12 22:59:24 -0800562
Rich Lanec4f071b2012-07-11 17:25:57 -0700563 with self.packets_cv:
Rich Lane8806bc42012-07-26 19:18:37 -0700564 ret = timed_wait(self.packets_cv, grab, timeout=timeout)
Rich Lanec4f071b2012-07-11 17:25:57 -0700565
Rich Laneb64ce3d2012-07-26 15:37:57 -0700566 if ret != None:
567 (msg, pkt) = ret
568 self.logger.debug("Got message %s" % str(msg))
569 return (msg, pkt)
570 else:
571 return (None, None)
Dan Talayco1b3f6902010-02-15 14:14:19 -0800572
Dan Talayco0fc08bd2012-04-09 16:56:18 -0700573 def transact(self, msg, timeout=-1, zero_xid=False):
Dan Talayco34089522010-02-07 23:07:41 -0800574 """
Dan Talayco21c75c72010-02-12 22:59:24 -0800575 Run a message transaction with the switch
Dan Talaycoe37999f2010-02-09 15:27:12 -0800576
577 Send the message in msg and wait for a reply with a matching
Dan Talayco21c75c72010-02-12 22:59:24 -0800578 transaction id. Transactions have the highest priority in
579 received message handling.
Dan Talaycoe37999f2010-02-09 15:27:12 -0800580
Dan Talayco21c75c72010-02-12 22:59:24 -0800581 @param msg The message object to send; must not be a string
Rich Lanee1da7ea2012-07-26 15:58:45 -0700582 @param timeout The timeout in seconds; if -1 use default.
Dan Talaycod7e2dbe2010-02-13 21:51:15 -0800583 @param zero_xid Normally, if the XID is 0 an XID will be generated
Ed Swierk9e55e282012-08-22 06:57:28 -0700584 for the message. Set zero_xid to override this behavior
Dan Talayco21c75c72010-02-12 22:59:24 -0800585 @return The matching message object or None if unsuccessful
Dan Talaycoe37999f2010-02-09 15:27:12 -0800586
Dan Talayco34089522010-02-07 23:07:41 -0800587 """
Dan Talayco21c75c72010-02-12 22:59:24 -0800588
Dan Talaycod7e2dbe2010-02-13 21:51:15 -0800589 if not zero_xid and msg.header.xid == 0:
590 msg.header.xid = gen_xid()
591
Dan Talayco0fc08bd2012-04-09 16:56:18 -0700592 self.logger.debug("Running transaction %d" % msg.header.xid)
Dan Talayco21c75c72010-02-12 22:59:24 -0800593
Rich Lane9aca1992012-07-11 17:26:31 -0700594 with self.xid_cv:
595 if self.xid:
596 self.logger.error("Can only run one transaction at a time")
597 return (None, None)
Dan Talaycof8de5182012-04-12 22:38:41 -0700598
Rich Lane9aca1992012-07-11 17:26:31 -0700599 self.xid = msg.header.xid
Dan Talaycod12b6612010-03-07 22:00:46 -0800600 self.xid_response = None
Rich Lane9aca1992012-07-11 17:26:31 -0700601 if self.message_send(msg.pack()) < 0:
602 self.logger.error("Error sending pkt for transaction %d" %
603 msg.header.xid)
604 return (None, None)
605
Rich Lane8806bc42012-07-26 19:18:37 -0700606 self.logger.debug("Waiting for transaction %d" % msg.header.xid)
Rich Lanee1da7ea2012-07-26 15:58:45 -0700607 timed_wait(self.xid_cv, lambda: self.xid_response, timeout=timeout)
Rich Lane9aca1992012-07-11 17:26:31 -0700608
609 if self.xid_response:
610 (resp, pkt) = self.xid_response
611 self.xid_response = None
612 else:
613 (resp, pkt) = (None, None)
614
Dan Talayco09c2c592010-05-13 14:21:52 -0700615 if resp is None:
616 self.logger.warning("No response for xid " + str(self.xid))
617 return (resp, pkt)
Dan Talayco34089522010-02-07 23:07:41 -0800618
Dan Talayco710438c2010-02-18 15:16:07 -0800619 def message_send(self, msg, zero_xid=False):
Dan Talayco34089522010-02-07 23:07:41 -0800620 """
621 Send the message to the switch
Dan Talaycoe37999f2010-02-09 15:27:12 -0800622
Dan Talayco11c26e72010-03-07 22:03:57 -0800623 @param msg A string or OpenFlow message object to be forwarded to
624 the switch.
625 @param zero_xid If msg is an OpenFlow object (not a string) and if
Dan Talayco710438c2010-02-18 15:16:07 -0800626 the XID in the header is 0, then an XID will be generated
Ed Swierk9e55e282012-08-22 06:57:28 -0700627 for the message. Set zero_xid to override this behavior (and keep an
Dan Talayco710438c2010-02-18 15:16:07 -0800628 existing 0 xid)
Dan Talaycoe37999f2010-02-09 15:27:12 -0800629
Ed Swierk9e55e282012-08-22 06:57:28 -0700630 @return 0 on success
Dan Talayco34089522010-02-07 23:07:41 -0800631
Dan Talayco21c75c72010-02-12 22:59:24 -0800632 """
633
Dan Talayco1b3f6902010-02-15 14:14:19 -0800634 if not self.switch_socket:
635 # Sending a string indicates the message is ready to go
Ed Swierk9e55e282012-08-22 06:57:28 -0700636 raise Exception("no socket")
Dan Talayco710438c2010-02-18 15:16:07 -0800637 #@todo If not string, try to pack
Dan Talayco21c75c72010-02-12 22:59:24 -0800638 if type(msg) != type(""):
Ed Swierk9e55e282012-08-22 06:57:28 -0700639 if msg.header.xid == 0 and not zero_xid:
640 msg.header.xid = gen_xid()
641 outpkt = msg.pack()
Dan Talayco710438c2010-02-18 15:16:07 -0800642 else:
643 outpkt = msg
Dan Talayco21c75c72010-02-12 22:59:24 -0800644
Dan Talayco48370102010-03-03 15:17:33 -0800645 self.logger.debug("Sending pkt of len " + str(len(outpkt)))
Ed Swierk9e55e282012-08-22 06:57:28 -0700646 if self.switch_socket.sendall(outpkt) is not None:
647 raise Exception("unknown error on sendall")
Dan Talayco710438c2010-02-18 15:16:07 -0800648
Ed Swierk9e55e282012-08-22 06:57:28 -0700649 return 0
Dan Talayco21c75c72010-02-12 22:59:24 -0800650
651 def __str__(self):
652 string = "Controller:\n"
Dan Talaycod7e2dbe2010-02-13 21:51:15 -0800653 string += " state " + self.dbg_state + "\n"
Dan Talayco21c75c72010-02-12 22:59:24 -0800654 string += " switch_addr " + str(self.switch_addr) + "\n"
655 string += " pending pkts " + str(len(self.packets)) + "\n"
656 string += " total pkts " + str(self.packets_total) + "\n"
657 string += " expired pkts " + str(self.packets_expired) + "\n"
658 string += " handled pkts " + str(self.packets_handled) + "\n"
Dan Talaycoe226eb12010-02-18 23:06:30 -0800659 string += " poll discards " + str(self.poll_discards) + "\n"
Dan Talayco21c75c72010-02-12 22:59:24 -0800660 string += " parse errors " + str(self.parse_errors) + "\n"
661 string += " sock errrors " + str(self.socket_errors) + "\n"
662 string += " max pkts " + str(self.max_pkts) + "\n"
Dan Talayco69ca4d62012-11-15 11:50:22 -0800663 string += " target switch " + str(self.switch) + "\n"
Dan Talayco21c75c72010-02-12 22:59:24 -0800664 string += " host " + str(self.host) + "\n"
665 string += " port " + str(self.port) + "\n"
666 string += " keep_alive " + str(self.keep_alive) + "\n"
Dan Talaycof8de5182012-04-12 22:38:41 -0700667 string += " pkt_in_run " + str(self.pkt_in_run) + "\n"
668 string += " pkt_in_dropped " + str(self.pkt_in_dropped) + "\n"
Dan Talayco21c75c72010-02-12 22:59:24 -0800669 return string
670
671 def show(self):
672 print str(self)
673
674def sample_handler(controller, msg, pkt):
675 """
676 Sample message handler
677
678 This is the prototype for functions registered with the controller
679 class for packet reception
680
681 @param controller The controller calling the handler
682 @param msg The parsed message object
683 @param pkt The raw packet that was received on the socket. This is
684 in case the packet contains extra unparsed data.
685 @returns Boolean value indicating if the packet was handled. If
686 not handled, the packet is placed in the queue for pollers to received
687 """
688 pass