blob: 3b2dc434afc0823da3683fbde3698edca1a8f3f0 [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 Chiang74be4722012-12-21 13:07:03 -0800447 with self.packets_cv:
448 self.packets = []
Ken Chiange875baf2012-10-09 15:24:40 -0700449 with self.connect_cv:
450 self.connect_cv.notifyAll()
Ken Chiangadc950f2012-10-05 13:50:03 -0700451
452 def wait_disconnected(self, timeout=-1):
453 """
454 @param timeout Block for up to timeout seconds. Pass -1 for the default.
455 @return Boolean, True if disconnected
456 """
457
Ken Chiange875baf2012-10-09 15:24:40 -0700458 with self.connect_cv:
459 timed_wait(self.connect_cv,
Ken Chiangadc950f2012-10-05 13:50:03 -0700460 lambda: True if not self.switch_socket else None,
461 timeout=timeout)
462 return self.switch_socket is None
463
Dan Talayco710438c2010-02-18 15:16:07 -0800464 def kill(self):
465 """
466 Force the controller thread to quit
467
468 Just sets the active state variable to false and expects
469 the select timeout to kick in
470 """
471 self.active = False
Dan Talayco21c75c72010-02-12 22:59:24 -0800472
Dan Talayco1b3f6902010-02-15 14:14:19 -0800473 def shutdown(self):
Dan Talayco21c75c72010-02-12 22:59:24 -0800474 """
Dan Talayco1b3f6902010-02-15 14:14:19 -0800475 Shutdown the controller closing all sockets
Dan Talayco21c75c72010-02-12 22:59:24 -0800476
Dan Talayco1b3f6902010-02-15 14:14:19 -0800477 @todo Might want to synchronize shutdown with self.sync...
478 """
Dan Talaycof8de5182012-04-12 22:38:41 -0700479
Dan Talayco710438c2010-02-18 15:16:07 -0800480 self.active = False
481 try:
482 self.switch_socket.shutdown(socket.SHUT_RDWR)
483 except:
Dan Talayco48370102010-03-03 15:17:33 -0800484 self.logger.info("Ignoring switch soc shutdown error")
Dan Talayco710438c2010-02-18 15:16:07 -0800485 self.switch_socket = None
Dan Talayco1b3f6902010-02-15 14:14:19 -0800486
Dan Talayco710438c2010-02-18 15:16:07 -0800487 try:
488 self.listen_socket.shutdown(socket.SHUT_RDWR)
489 except:
Dan Talayco48370102010-03-03 15:17:33 -0800490 self.logger.info("Ignoring listen soc shutdown error")
Dan Talayco710438c2010-02-18 15:16:07 -0800491 self.listen_socket = None
Dan Talaycof8de5182012-04-12 22:38:41 -0700492
Rich Laneee3586c2012-07-11 17:26:02 -0700493 # Wakeup condition variables on which controller may be wait
494 with self.xid_cv:
495 self.xid_cv.notifyAll()
Dan Talaycof8de5182012-04-12 22:38:41 -0700496
Rich Laneee3586c2012-07-11 17:26:02 -0700497 with self.connect_cv:
498 self.connect_cv.notifyAll()
Dan Talaycof8de5182012-04-12 22:38:41 -0700499
Dan Talayco710438c2010-02-18 15:16:07 -0800500 self.dbg_state = "down"
501
Dan Talayco34089522010-02-07 23:07:41 -0800502 def register(self, msg_type, handler):
503 """
504 Register a callback to receive a specific message type.
505
506 Only one handler may be registered for a given message type.
Dan Talaycod12b6612010-03-07 22:00:46 -0800507
508 WARNING: A lock is held during the handler call back, so
509 the handler should not make any blocking calls
510
Dan Talayco34089522010-02-07 23:07:41 -0800511 @param msg_type The type of message to receive. May be DEFAULT
Dan Talayco21c75c72010-02-12 22:59:24 -0800512 for all non-handled packets. The special type, the string "all"
513 will send all packets to the handler.
Dan Talayco34089522010-02-07 23:07:41 -0800514 @param handler The function to call when a message of the given
515 type is received.
516 """
Dan Talayco21c75c72010-02-12 22:59:24 -0800517 # Should check type is valid
518 if not handler and msg_type in self.handlers.keys():
519 del self.handlers[msg_type]
520 return
521 self.handlers[msg_type] = handler
Dan Talayco34089522010-02-07 23:07:41 -0800522
Rich Laneb64ce3d2012-07-26 15:37:57 -0700523 def poll(self, exp_msg=None, timeout=-1):
Dan Talayco34089522010-02-07 23:07:41 -0800524 """
525 Wait for the next OF message received from the switch.
526
527 @param exp_msg If set, return only when this type of message
Dan Talayco48370102010-03-03 15:17:33 -0800528 is received (unless timeout occurs).
Rich Laneb64ce3d2012-07-26 15:37:57 -0700529
530 @param timeout Maximum number of seconds to wait for the message.
531 Pass -1 for the default timeout.
Dan Talayco34089522010-02-07 23:07:41 -0800532
Dan Talaycod7e2dbe2010-02-13 21:51:15 -0800533 @retval A pair (msg, pkt) where msg is a message object and pkt
534 the string representing the packet as received from the socket.
535 This allows additional parsing by the receiver if necessary.
536
Dan Talayco34089522010-02-07 23:07:41 -0800537 The data members in the message are in host endian order.
Dan Talayco48370102010-03-03 15:17:33 -0800538 If an error occurs, (None, None) is returned
Dan Talayco34089522010-02-07 23:07:41 -0800539 """
Dan Talayco34089522010-02-07 23:07:41 -0800540
Ken Chiang77173992012-10-30 15:44:39 -0700541 if exp_msg is not None:
Ed Swierk9e55e282012-08-22 06:57:28 -0700542 self.logger.debug("Poll for %s" % ofp_type_map[exp_msg])
543 else:
544 self.logger.debug("Poll for any OF message")
Rich Laneb64ce3d2012-07-26 15:37:57 -0700545
546 # Take the packet from the queue
Rich Lanec4f071b2012-07-11 17:25:57 -0700547 def grab():
548 if len(self.packets) > 0:
Ken Chiang77173992012-10-30 15:44:39 -0700549 if exp_msg is None:
Rich Lanec4f071b2012-07-11 17:25:57 -0700550 self.logger.debug("Looking for any packet")
551 (msg, pkt) = self.packets.pop(0)
552 return (msg, pkt)
553 else:
554 self.logger.debug("Looking for %s" % ofp_type_map[exp_msg])
555 for i in range(len(self.packets)):
556 msg = self.packets[i][0]
557 self.logger.debug("Checking packets[%d] (%s)" % (i, ofp_type_map[msg.header.type]))
558 if msg.header.type == exp_msg:
559 (msg, pkt) = self.packets.pop(i)
560 return (msg, pkt)
561 # Not found
562 self.logger.debug("Packet not in queue")
Rich Laneb64ce3d2012-07-26 15:37:57 -0700563 return None
Dan Talayco21c75c72010-02-12 22:59:24 -0800564
Rich Lanec4f071b2012-07-11 17:25:57 -0700565 with self.packets_cv:
Rich Lane8806bc42012-07-26 19:18:37 -0700566 ret = timed_wait(self.packets_cv, grab, timeout=timeout)
Rich Lanec4f071b2012-07-11 17:25:57 -0700567
Rich Laneb64ce3d2012-07-26 15:37:57 -0700568 if ret != None:
569 (msg, pkt) = ret
570 self.logger.debug("Got message %s" % str(msg))
571 return (msg, pkt)
572 else:
573 return (None, None)
Dan Talayco1b3f6902010-02-15 14:14:19 -0800574
Dan Talayco0fc08bd2012-04-09 16:56:18 -0700575 def transact(self, msg, timeout=-1, zero_xid=False):
Dan Talayco34089522010-02-07 23:07:41 -0800576 """
Dan Talayco21c75c72010-02-12 22:59:24 -0800577 Run a message transaction with the switch
Dan Talaycoe37999f2010-02-09 15:27:12 -0800578
579 Send the message in msg and wait for a reply with a matching
Dan Talayco21c75c72010-02-12 22:59:24 -0800580 transaction id. Transactions have the highest priority in
581 received message handling.
Dan Talaycoe37999f2010-02-09 15:27:12 -0800582
Dan Talayco21c75c72010-02-12 22:59:24 -0800583 @param msg The message object to send; must not be a string
Rich Lanee1da7ea2012-07-26 15:58:45 -0700584 @param timeout The timeout in seconds; if -1 use default.
Dan Talaycod7e2dbe2010-02-13 21:51:15 -0800585 @param zero_xid Normally, if the XID is 0 an XID will be generated
Ed Swierk9e55e282012-08-22 06:57:28 -0700586 for the message. Set zero_xid to override this behavior
Dan Talayco21c75c72010-02-12 22:59:24 -0800587 @return The matching message object or None if unsuccessful
Dan Talaycoe37999f2010-02-09 15:27:12 -0800588
Dan Talayco34089522010-02-07 23:07:41 -0800589 """
Dan Talayco21c75c72010-02-12 22:59:24 -0800590
Dan Talaycod7e2dbe2010-02-13 21:51:15 -0800591 if not zero_xid and msg.header.xid == 0:
592 msg.header.xid = gen_xid()
593
Dan Talayco0fc08bd2012-04-09 16:56:18 -0700594 self.logger.debug("Running transaction %d" % msg.header.xid)
Dan Talayco21c75c72010-02-12 22:59:24 -0800595
Rich Lane9aca1992012-07-11 17:26:31 -0700596 with self.xid_cv:
597 if self.xid:
598 self.logger.error("Can only run one transaction at a time")
599 return (None, None)
Dan Talaycof8de5182012-04-12 22:38:41 -0700600
Rich Lane9aca1992012-07-11 17:26:31 -0700601 self.xid = msg.header.xid
Dan Talaycod12b6612010-03-07 22:00:46 -0800602 self.xid_response = None
Rich Lane9aca1992012-07-11 17:26:31 -0700603 if self.message_send(msg.pack()) < 0:
604 self.logger.error("Error sending pkt for transaction %d" %
605 msg.header.xid)
606 return (None, None)
607
Rich Lane8806bc42012-07-26 19:18:37 -0700608 self.logger.debug("Waiting for transaction %d" % msg.header.xid)
Rich Lanee1da7ea2012-07-26 15:58:45 -0700609 timed_wait(self.xid_cv, lambda: self.xid_response, timeout=timeout)
Rich Lane9aca1992012-07-11 17:26:31 -0700610
611 if self.xid_response:
612 (resp, pkt) = self.xid_response
613 self.xid_response = None
614 else:
615 (resp, pkt) = (None, None)
616
Dan Talayco09c2c592010-05-13 14:21:52 -0700617 if resp is None:
618 self.logger.warning("No response for xid " + str(self.xid))
619 return (resp, pkt)
Dan Talayco34089522010-02-07 23:07:41 -0800620
Dan Talayco710438c2010-02-18 15:16:07 -0800621 def message_send(self, msg, zero_xid=False):
Dan Talayco34089522010-02-07 23:07:41 -0800622 """
623 Send the message to the switch
Dan Talaycoe37999f2010-02-09 15:27:12 -0800624
Dan Talayco11c26e72010-03-07 22:03:57 -0800625 @param msg A string or OpenFlow message object to be forwarded to
626 the switch.
627 @param zero_xid If msg is an OpenFlow object (not a string) and if
Dan Talayco710438c2010-02-18 15:16:07 -0800628 the XID in the header is 0, then an XID will be generated
Ed Swierk9e55e282012-08-22 06:57:28 -0700629 for the message. Set zero_xid to override this behavior (and keep an
Dan Talayco710438c2010-02-18 15:16:07 -0800630 existing 0 xid)
Dan Talaycoe37999f2010-02-09 15:27:12 -0800631
Ed Swierk9e55e282012-08-22 06:57:28 -0700632 @return 0 on success
Dan Talayco34089522010-02-07 23:07:41 -0800633
Dan Talayco21c75c72010-02-12 22:59:24 -0800634 """
635
Dan Talayco1b3f6902010-02-15 14:14:19 -0800636 if not self.switch_socket:
637 # Sending a string indicates the message is ready to go
Ed Swierk9e55e282012-08-22 06:57:28 -0700638 raise Exception("no socket")
Dan Talayco710438c2010-02-18 15:16:07 -0800639 #@todo If not string, try to pack
Dan Talayco21c75c72010-02-12 22:59:24 -0800640 if type(msg) != type(""):
Ed Swierk9e55e282012-08-22 06:57:28 -0700641 if msg.header.xid == 0 and not zero_xid:
642 msg.header.xid = gen_xid()
643 outpkt = msg.pack()
Dan Talayco710438c2010-02-18 15:16:07 -0800644 else:
645 outpkt = msg
Dan Talayco21c75c72010-02-12 22:59:24 -0800646
Dan Talayco48370102010-03-03 15:17:33 -0800647 self.logger.debug("Sending pkt of len " + str(len(outpkt)))
Ed Swierk9e55e282012-08-22 06:57:28 -0700648 if self.switch_socket.sendall(outpkt) is not None:
649 raise Exception("unknown error on sendall")
Dan Talayco710438c2010-02-18 15:16:07 -0800650
Ed Swierk9e55e282012-08-22 06:57:28 -0700651 return 0
Dan Talayco21c75c72010-02-12 22:59:24 -0800652
653 def __str__(self):
654 string = "Controller:\n"
Dan Talaycod7e2dbe2010-02-13 21:51:15 -0800655 string += " state " + self.dbg_state + "\n"
Dan Talayco21c75c72010-02-12 22:59:24 -0800656 string += " switch_addr " + str(self.switch_addr) + "\n"
657 string += " pending pkts " + str(len(self.packets)) + "\n"
658 string += " total pkts " + str(self.packets_total) + "\n"
659 string += " expired pkts " + str(self.packets_expired) + "\n"
660 string += " handled pkts " + str(self.packets_handled) + "\n"
Dan Talaycoe226eb12010-02-18 23:06:30 -0800661 string += " poll discards " + str(self.poll_discards) + "\n"
Dan Talayco21c75c72010-02-12 22:59:24 -0800662 string += " parse errors " + str(self.parse_errors) + "\n"
663 string += " sock errrors " + str(self.socket_errors) + "\n"
664 string += " max pkts " + str(self.max_pkts) + "\n"
Dan Talayco69ca4d62012-11-15 11:50:22 -0800665 string += " target switch " + str(self.switch) + "\n"
Dan Talayco21c75c72010-02-12 22:59:24 -0800666 string += " host " + str(self.host) + "\n"
667 string += " port " + str(self.port) + "\n"
668 string += " keep_alive " + str(self.keep_alive) + "\n"
Dan Talaycof8de5182012-04-12 22:38:41 -0700669 string += " pkt_in_run " + str(self.pkt_in_run) + "\n"
670 string += " pkt_in_dropped " + str(self.pkt_in_dropped) + "\n"
Dan Talayco21c75c72010-02-12 22:59:24 -0800671 return string
672
673 def show(self):
674 print str(self)
675
676def sample_handler(controller, msg, pkt):
677 """
678 Sample message handler
679
680 This is the prototype for functions registered with the controller
681 class for packet reception
682
683 @param controller The controller calling the handler
684 @param msg The parsed message object
685 @param pkt The raw packet that was received on the socket. This is
686 in case the packet contains extra unparsed data.
687 @returns Boolean value indicating if the packet was handled. If
688 not handled, the packet is placed in the queue for pollers to received
689 """
690 pass