Statistics
| Branch: | Tag: | Revision:

mininet / mininet / mininet.py @ 7b804ffb

History | View | Annotate | Download (32.9 KB)

1 98d4f189 Bob Lantz
#!/usr/bin/python
2
3
"""
4
Mininet: A simple networking testbed for OpenFlow!
5

6 08cef003 Bob Lantz
Mininet creates scalable OpenFlow test networks by using
7 98d4f189 Bob Lantz
process-based virtualization and network namespaces. 
8

9 eddef947 Bob Lantz
Simulated hosts are created as processes in separate network
10
namespaces. This allows a complete OpenFlow network to be simulated on
11
top of a single Linux kernel.
12 98d4f189 Bob Lantz

13
Each host has:
14
   A virtual console (pipes to a shell)
15
   A virtual interfaces (half of a veth pair)
16 eddef947 Bob Lantz
   A parent shell (and possibly some child processes) in a namespace
17 98d4f189 Bob Lantz
   
18 eddef947 Bob Lantz
Hosts have a network interface which is configured via ifconfig/ip
19
link/etc. with data network IP addresses (e.g. 192.168.123.2 )
20 98d4f189 Bob Lantz

21 55dd9368 Bob Lantz
This version supports both the kernel and user space datapaths
22 08cef003 Bob Lantz
from the OpenFlow reference implementation.
23

24 98d4f189 Bob Lantz
In kernel datapath mode, the controller and switches are simply
25
processes in the root namespace.
26

27 55dd9368 Bob Lantz
Kernel OpenFlow datapaths are instantiated using dpctl(8), and are
28
attached to the one side of a veth pair; the other side resides in the
29
host namespace. In this mode, switch processes can simply connect to the
30 eddef947 Bob Lantz
controller via the loopback interface.
31 98d4f189 Bob Lantz

32 eddef947 Bob Lantz
In user datapath mode, the controller and switches are full-service
33
nodes that live in their own network namespaces and have management
34 98d4f189 Bob Lantz
interfaces and IP addresses on a control network (e.g. 10.0.123.1,
35
currently routed although it could be bridged.)
36

37
In addition to a management interface, user mode switches also have
38 55dd9368 Bob Lantz
several switch interfaces, halves of veth pairs whose other halves
39
reside in the host nodes that the switches are connected to.
40 98d4f189 Bob Lantz

41
Naming:
42
   Host nodes are named h1-hN
43
   Switch nodes are named s0-sN
44
   Interfaces are named {nodename}-eth0 .. {nodename}-ethN,
45

46
Thoughts/TBD:
47

48
   It should be straightforward to add a function to read
49 08cef003 Bob Lantz
   OpenFlowVMS spec files, but I haven't done so yet.
50 98d4f189 Bob Lantz
   For the moment, specifying configurations and tests in Python
51 08cef003 Bob Lantz
   is straightforward and relatively concise.
52
   Soon, we may want to split the various subsystems (core,
53 696a619d Bob Lantz
   topology/network, cli, tests, etc.) into multiple modules.
54
   Currently nox support is in nox.py.
55 08cef003 Bob Lantz
   We'd like to support OpenVSwitch as well as the reference
56
   implementation.
57 98d4f189 Bob Lantz
   
58
Bob Lantz
59
rlantz@cs.stanford.edu
60

61
History:
62
11/19/09 Initial revision (user datapath only)
63 55dd9368 Bob Lantz
11/19/09 Mininet demo at OpenFlow SWAI meeting
64 eddef947 Bob Lantz
12/08/09 Kernel datapath support complete
65
12/09/09 Moved controller and switch routines into classes
66 08cef003 Bob Lantz
12/12/09 Added subdivided network driver workflow
67 9dbb68df Bob Lantz
12/13/09 Added support for custom controller and switch classes
68 98d4f189 Bob Lantz
"""
69
70
from subprocess import call, check_call, Popen, PIPE, STDOUT
71
from time import sleep
72 eddef947 Bob Lantz
import os, re, signal, sys, select
73 98d4f189 Bob Lantz
flush = sys.stdout.flush
74
from resource import setrlimit, RLIMIT_NPROC, RLIMIT_NOFILE
75
76 7b804ffb Brandon Heller
import logging
77
import sys
78
79
from logging_mod import StreamHandlerNoNewline
80
81 ff43615a Brandon Heller
DATAPATHS = ['user', 'kernel']
82
83 7b804ffb Brandon Heller
LEVELS = {'debug': logging.DEBUG,
84
          'info': logging.INFO,
85
          'warning': logging.WARNING,
86
          'error': logging.ERROR,
87
          'critical': logging.CRITICAL}
88
89
# change this to get printouts when running unit tests
90
LOG_LEVEL_DEFAULT = logging.WARNING
91
92
#default: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
93
LOG_MSG_FORMAT = "%(message)s"
94
95
lg = None
96
97
def setup_logging(loglevel):
98
99
    global lg
100
101
    # create logger
102
    lg = logging.getLogger("mininet")
103
    lg.setLevel(loglevel)
104
    # create console handler and set level to debug
105
    ch = StreamHandlerNoNewline()
106
    ch.setLevel(loglevel)
107
    # create formatter
108
    formatter = logging.Formatter(LOG_MSG_FORMAT)
109
    # add formatter to ch
110
    ch.setFormatter(formatter)
111
    # add ch to lg
112
    lg.addHandler(ch)
113
114
115 98d4f189 Bob Lantz
# Utility routines to make it easier to run commands
116
117
def run( cmd ):
118
   "Simple interface to subprocess.call()"
119
   return call( cmd.split( ' ' ) )
120
121
def checkRun( cmd ):
122
   "Simple interface to subprocess.check_call()"
123
   check_call( cmd.split( ' ' ) )
124
   
125
def quietRun( cmd ):
126
   "Run a command, routing stderr to stdout, and return the output."
127 6bd22292 Bob Lantz
   if isinstance( cmd, str ): cmd = cmd.split( ' ' )
128
   popen = Popen( cmd, stdout=PIPE, stderr=STDOUT)
129 98d4f189 Bob Lantz
   # We can't use Popen.communicate() because it uses 
130
   # select(), which can't handle
131
   # high file descriptor numbers! poll() can, however.
132
   output = ''
133
   readable = select.poll()
134
   readable.register( popen.stdout )
135
   while True:
136
      while readable.poll(): 
137
         data = popen.stdout.read( 1024 )
138
         if len( data ) == 0: break
139
         output += data
140
      popen.poll()
141
      if popen.returncode != None: break
142
   return output
143
   
144
class Node( object ):
145
   """A virtual network node is simply a shell in a network namespace.
146
      We communicate with it using pipes."""
147 b7640209 Bob Lantz
   inToNode = {}
148
   outToNode = {}
149 98d4f189 Bob Lantz
   def __init__( self, name, inNamespace=True ):
150
      self.name = name
151
      closeFds = False # speed vs. memory use
152
      # xpg_echo is needed so we can echo our sentinel in sendCmd
153 eddef947 Bob Lantz
      cmd = [ '/bin/bash', '-O', 'xpg_echo' ]
154 98d4f189 Bob Lantz
      self.inNamespace = inNamespace
155 eddef947 Bob Lantz
      if self.inNamespace: cmd = [ 'netns' ] + cmd
156 98d4f189 Bob Lantz
      self.shell = Popen( cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT,
157
         close_fds=closeFds )
158
      self.stdin = self.shell.stdin
159
      self.stdout = self.shell.stdout
160
      self.pollOut = select.poll() 
161
      self.pollOut.register( self.stdout )
162 b7640209 Bob Lantz
      # Maintain mapping between file descriptors and nodes
163
      # This could be useful for monitoring multiple nodes
164
      # using select.poll()
165
      self.outToNode[ self.stdout.fileno() ] = self
166
      self.inToNode[ self.stdin.fileno() ] = self
167 98d4f189 Bob Lantz
      self.pid = self.shell.pid
168
      self.intfCount = 0
169
      self.intfs = []
170
      self.ips = {}
171
      self.connection = {}
172
      self.waiting = False
173 748e35d5 Bob Lantz
      self.execed = False
174 b7640209 Bob Lantz
   def fdToNode( self, f ):
175
      node = self.outToNode.get( f )
176
      return node or self.inToNode.get( f )
177 748e35d5 Bob Lantz
   def cleanup( self ):
178
      # Help python collect its garbage
179
      self.shell = None
180 eddef947 Bob Lantz
   # Subshell I/O, commands and control
181 98d4f189 Bob Lantz
   def read( self, max ): return os.read( self.stdout.fileno(), max )
182
   def write( self, data ): os.write( self.stdin.fileno(), data )
183 748e35d5 Bob Lantz
   def terminate( self ):
184
      os.kill( self.pid, signal.SIGKILL )
185 b7640209 Bob Lantz
      self.cleanup()
186 98332cb6 Bob Lantz
   def stop( self ): self.terminate()
187 98d4f189 Bob Lantz
   def waitReadable( self ): self.pollOut.poll()
188
   def sendCmd( self, cmd ):
189
      """Send a command, followed by a command to echo a sentinel,
190
         and return without waiting for the command to complete."""
191
      assert not self.waiting
192
      if cmd[ -1 ] == '&':
193
         separator = '&'
194
         cmd = cmd[ : -1 ]
195
      else: separator = ';'
196
      if isinstance( cmd, list): cmd = ' '.join( cmd )
197
      self.write( cmd + separator + " echo -n '\\0177' \n")
198
      self.waiting = True
199
   def monitor( self ):
200
      "Monitor a command's output, returning (done, data)."
201
      assert self.waiting
202
      self.waitReadable()
203
      data = self.read( 1024 )
204
      if len( data ) > 0 and data[ -1 ] == chr( 0177 ):
205
         self.waiting = False
206
         return True, data[ : -1 ]
207
      else:
208
         return False, data
209
   def sendInt( self ):
210
      "Send ^C, hopefully interrupting a running subprocess."
211
      self.write( chr( 3 ) )
212
   def waitOutput( self ):
213
      """Wait for a command to complete (signaled by a sentinel
214
      character, ASCII(127) appearing in the output stream) and return
215
      the output, including trailing newline."""
216
      assert self.waiting
217
      output = ""
218
      while True:
219
         self.waitReadable()
220
         data = self.read( 1024 )
221
         if len(data) > 0  and data[ -1 ] == chr( 0177 ): 
222
            output += data[ : -1 ]
223
            break
224
         else: output += data
225
      self.waiting = False
226
      return output
227
   def cmd( self, cmd ):
228
      "Send a command, wait for output, and return it."
229
      self.sendCmd( cmd )
230
      return self.waitOutput()
231
   def cmdPrint( self, cmd ):
232
      "Call cmd, printing the command and output"
233 7b804ffb Brandon Heller
      lg.info("*** %s : %s", self.name, cmd)
234 98d4f189 Bob Lantz
      result = self.cmd( cmd )
235 7b804ffb Brandon Heller
      lg.info("%s\n", result)
236 98d4f189 Bob Lantz
      return result
237 eddef947 Bob Lantz
   # Interface management, configuration, and routing
238 98d4f189 Bob Lantz
   def intfName( self, n):
239
      "Construct a canonical interface name node-intf for interface N."
240
      return self.name + '-eth' + `n`
241
   def newIntf( self ):
242
      "Reserve and return a new interface name for this node."
243
      intfName = self.intfName( self.intfCount)
244
      self.intfCount += 1
245
      self.intfs += [ intfName ]
246
      return intfName
247
   def setIP( self, intf, ip, bits ):
248
      "Set an interface's IP address."
249 ac75d7cf Bob Lantz
      result = self.cmd( [ 'ifconfig', intf, ip + bits, 'up' ] )
250 98d4f189 Bob Lantz
      self.ips[ intf ] = ip
251
      return result
252
   def setHostRoute( self, ip, intf ):
253
      "Add a route to the given IP address via intf."
254
      return self.cmd( 'route add -host ' + ip + ' dev ' + intf )
255
   def setDefaultRoute( self, intf ):
256
      "Set the default route to go through intf."
257
      self.cmd( 'ip route flush' )
258
      return self.cmd( 'route add default ' + intf )
259
   def IP( self ):
260
      "Return IP address of first interface"
261 b7640209 Bob Lantz
      if len( self.intfs ) > 0:
262
         return self.ips.get( self.intfs[ 0 ], None )
263 eddef947 Bob Lantz
   def intfIsUp( self, intf ):
264
      "Check if one of our interfaces is up."
265
      return 'UP' in self.cmd( 'ifconfig ' + self.intfs[ 0 ] )
266
   # Other methods  
267 98d4f189 Bob Lantz
   def __str__( self ): 
268
      result = self.name
269 b7640209 Bob Lantz
      result += ": IP=" + self.IP() + " intfs=" + ','.join( self.intfs )
270
      result += " waiting=" +  `self.waiting`
271 98d4f189 Bob Lantz
      return result
272 eddef947 Bob Lantz
273 98d4f189 Bob Lantz
274
275 eddef947 Bob Lantz
class Host( Node ):
276
   """A host is simply a Node."""
277
   pass
278
      
279
class Controller( Node ):
280
   """A Controller is a Node that is running (or has execed) an 
281
      OpenFlow controller."""
282 2f534913 Bob Lantz
   def __init__( self, name, kernel=True, controller='controller',
283 cd27f9db Brandon Heller
      cargs='-v ptcp:', cdir=None ):
284 2f534913 Bob Lantz
      self.controller = controller
285
      self.cargs = cargs
286
      self.cdir = cdir
287 eddef947 Bob Lantz
      Node.__init__( self, name, inNamespace=( not kernel ) )
288 2f534913 Bob Lantz
   def start( self ):
289 994c68f6 Bob Lantz
      "Start <controller> <args> on controller, logging to /tmp/cN.log"
290 eddef947 Bob Lantz
      cout = '/tmp/' + self.name + '.log'
291 2f534913 Bob Lantz
      if self.cdir is not None:
292
         self.cmdPrint( 'cd ' + self.cdir )
293
      self.cmdPrint( self.controller + ' ' + self.cargs + 
294 eddef947 Bob Lantz
         ' 1> ' + cout + ' 2> ' + cout + ' &' )
295 fb7658c8 Bob Lantz
      self.execed = False # XXX Until I fix it
296 994c68f6 Bob Lantz
   def stop( self, controller='controller' ):
297 eddef947 Bob Lantz
      "Stop controller cprog on controller"
298 fb7658c8 Bob Lantz
      self.cmd( "kill %" + controller )  
299 98332cb6 Bob Lantz
      self.terminate()
300 eddef947 Bob Lantz
         
301
class Switch( Node ):
302
   """A Switch is a Node that is running (or has execed)
303
      an OpenFlow switch."""
304
   def __init__( self, name, datapath=None ):
305
      self.dp = datapath
306
      Node.__init__( self, name, inNamespace=( datapath == None ) )
307
   def startUserDatapath( self, controller ):
308
      """Start OpenFlow reference user datapath, 
309
         logging to /tmp/sN-{ofd,ofp}.log"""
310
      ofdlog = '/tmp/' + self.name + '-ofd.log'
311
      ofplog = '/tmp/' + self.name + '-ofp.log'
312
      self.cmd( 'ifconfig lo up' )
313
      intfs = self.intfs[ 1 : ] # 0 is mgmt interface
314
      self.cmdPrint( 'ofdatapath -i ' + ','.join( intfs ) +
315
       ' ptcp: 1> ' + ofdlog + ' 2> '+ ofdlog + ' &' )
316
      self.cmdPrint( 'ofprotocol tcp:' + controller.IP() +
317 f939eb56 Bob Lantz
         ' tcp:localhost --fail=closed 1> ' + ofplog + ' 2>' + ofplog + ' &' )
318 eddef947 Bob Lantz
   def stopUserDatapath( self ):
319
      "Stop OpenFlow reference user datapath."
320
      self.cmd( "kill %ofdatapath" )
321
      self.cmd( "kill %ofprotocol" )
322
   def startKernelDatapath( self, controller):
323
      "Start up switch using OpenFlow reference kernel datapath."
324
      ofplog = '/tmp/' + self.name + '-ofp.log'
325
      quietRun( 'ifconfig lo up' )
326
      # Delete local datapath if it exists;
327
      # then create a new one monitoring the given interfaces
328
      quietRun( 'dpctl deldp ' + self.dp )
329
      self.cmdPrint( 'dpctl adddp ' + self.dp )
330
      self.cmdPrint( 'dpctl addif ' + self.dp + ' ' + ' '.join( self.intfs ) )
331 f939eb56 Bob Lantz
      # Run protocol daemon
332 fb7658c8 Bob Lantz
      self.cmdPrint( 'ofprotocol' +
333 f939eb56 Bob Lantz
         ' ' + self.dp + ' tcp:127.0.0.1 ' + 
334
         ' --fail=closed 1> ' + ofplog + ' 2>' + ofplog + ' &' )
335 fb7658c8 Bob Lantz
      self.execed = False # XXX until I fix it
336 eddef947 Bob Lantz
   def stopKernelDatapath( self ):
337
      "Terminate a switch using OpenFlow reference kernel datapath."
338
      quietRun( 'dpctl deldp ' + self.dp )
339 08cef003 Bob Lantz
      # In theory the interfaces should go away after we shut down.
340
      # However, this takes time, so we're better off to remove them
341
      # explicitly so that we won't get errors if we run before they
342
      # have been removed by the kernel. Unfortunately this is very slow.
343 b7640209 Bob Lantz
      self.cmd( 'kill %ofprotocol')
344 08cef003 Bob Lantz
      for intf in self.intfs:
345
         quietRun( 'ip link del ' + intf )
346 7b804ffb Brandon Heller
         lg.info('.')
347 eddef947 Bob Lantz
   def start( self, controller ): 
348
      if self.dp is None: self.startUserDatapath( controller )
349
      else: self.startKernelDatapath( controller )
350
   def stop( self ):
351
      if self.dp is None: self.stopUserDatapath()
352
      else: self.stopKernelDatapath()
353
   def sendCmd( self, cmd ):
354 7b804ffb Brandon Heller
      if not self.execed:
355
          return Node.sendCmd( self, cmd )
356
      else:
357
          lg.error("*** Error: %s has execed and cannot accept commands" %
358
                 self.name)
359 eddef947 Bob Lantz
   def monitor( self ):
360
      if not self.execed: return Node.monitor( self )
361
      else: return True, ''
362
         
363 98d4f189 Bob Lantz
# Interface management
364
# 
365 eddef947 Bob Lantz
# Interfaces are managed as strings which are simply the
366
# interface names, of the form "nodeN-ethM".
367
#
368
# To connect nodes, we create a pair of veth interfaces, and then place them
369
# in the pair of nodes that we want to communicate. We then update the node's
370
# list of interfaces and connectivity map.
371
#
372
# For the kernel datapath, switch interfaces
373
# live in the root namespace and thus do not have to be
374
# explicitly moved.
375 98d4f189 Bob Lantz
376 345bf7cc Brandon Heller
MOVEINTF_DELAY = 0.0001
377
378 98d4f189 Bob Lantz
def makeIntfPair( intf1, intf2 ):
379 eddef947 Bob Lantz
   "Make a veth pair of intf1 and intf2."
380 98d4f189 Bob Lantz
   # Delete any old interfaces with the same names
381
   quietRun( 'ip link del ' + intf1 )
382
   quietRun( 'ip link del ' + intf2 )
383
   # Create new pair
384
   cmd = 'ip link add name ' + intf1 + ' type veth peer name ' + intf2
385
   return checkRun( cmd )
386
   
387 1095628b Brandon Heller
def moveIntf( intf, node, print_error = False ):
388 eddef947 Bob Lantz
   "Move intf to node."
389 98d4f189 Bob Lantz
   cmd = 'ip link set ' + intf + ' netns ' + `node.pid`
390 0a9ea29f Bob Lantz
   quietRun( cmd )
391 98d4f189 Bob Lantz
   links = node.cmd( 'ip link show' )
392
   if not intf in links:
393 1095628b Brandon Heller
      if print_error:
394 7b804ffb Brandon Heller
          lg.error("*** Error: moveIntf: % not successfully moved to %s:\n" %
395
                  (intf, node.name))
396 0a9ea29f Bob Lantz
      return False
397
   return True
398
399 345bf7cc Brandon Heller
def retry( n, retry_delay, fn, *args):
400 0a9ea29f Bob Lantz
   "Try something N times before giving up."
401
   tries = 0
402 05cce994 Bob Lantz
   while not apply( fn, args ) and tries < n:
403 345bf7cc Brandon Heller
      sleep( retry_delay )
404 0a9ea29f Bob Lantz
      tries += 1
405 f939eb56 Bob Lantz
   if tries >= n: 
406 7b804ffb Brandon Heller
      lg.error("*** gave up after %i retries\n" % tries)
407 f939eb56 Bob Lantz
      exit( 1 )
408 98d4f189 Bob Lantz
   
409
def createLink( node1, node2 ):
410
   "Create a link node1-intf1 <---> node2-intf2."
411
   intf1 = node1.newIntf()
412
   intf2 = node2.newIntf()
413
   makeIntfPair( intf1, intf2 )
414 345bf7cc Brandon Heller
   if node1.inNamespace: retry( 3, MOVEINTF_DELAY, moveIntf, intf1, node1 )
415
   if node2.inNamespace: retry( 3, MOVEINTF_DELAY, moveIntf, intf2, node2 )
416 98d4f189 Bob Lantz
   node1.connection[ intf1 ] = ( node2, intf2 )
417
   node2.connection[ intf2 ] = ( node1, intf1 )
418
   return intf1, intf2
419
420
# Handy utilities
421 eddef947 Bob Lantz
 
422
def createNodes( name, count ):
423
   "Create and return a list of nodes."
424
   nodes = [ Node( name + `i` ) for i in range( 0, count ) ]
425
   # print "*** CreateNodes: created:", nodes
426
   return nodes
427
     
428 98d4f189 Bob Lantz
def dumpNodes( nodes ):
429
   "Dump ifconfig of each node."
430
   for node in nodes:
431 7b804ffb Brandon Heller
      lg.info("*** Dumping node %s\n" % node.name)
432
      lg.info("%s\n" % node.cmd( 'ip link show' ))
433
      lg.info("%s\n" % node.cmd( 'route' ))
434
435 98d4f189 Bob Lantz
def ipGen( A, B, c, d ):
436
   "Generate next IP class B IP address, starting at A.B.c.d"
437
   while True:
438
      yield '%d.%d.%d.%d' % ( A, B, c, d )
439
      d += 1
440
      if d > 254:
441
         d = 1
442
         c += 1
443
         if c > 254: break
444
445
def nameGen( prefix ):
446
   "Generate names starting with prefix."
447
   i = 0
448 eddef947 Bob Lantz
   while True: yield prefix + `i`; i += 1
449
      
450 696a619d Bob Lantz
# Control network support:
451
#
452
# Create an explicit control network. Currently this is only
453
# used by the user datapath configuration.
454
#
455
# Notes:
456
#
457
# 1. If the controller and switches are in the same (e.g. root)
458
#    namespace, they can just use the loopback connection.
459
#    We may wish to do this for the user datapath as well as the
460
#    kernel datapath.
461
#
462
# 2. If we can get unix domain sockets to work, we can use them
463
#    instead of an explicit control network.
464
#
465
# 3. Instead of routing, we could bridge or use "in-band" control.
466
#
467
# 4. Even if we dispense with this in general, it could still be
468
#    useful for people who wish to simulate a separate control
469
#    network (since real networks may need one!)
470
471 93519c04 Bob Lantz
def configureRoutedControlNetwork( controller, switches, ips):
472 eddef947 Bob Lantz
   """Configure a routed control network on controller and switches,
473
      for use with the user datapath."""
474
   cip = ips.next()
475 7b804ffb Brandon Heller
   lg.info("%s <-> " % controller.name)
476 98d4f189 Bob Lantz
   for switch in switches:
477 7b804ffb Brandon Heller
      lg.info("%s " % switch.name)
478 eddef947 Bob Lantz
      sip = ips.next()
479 98d4f189 Bob Lantz
      sintf = switch.intfs[ 0 ]
480
      node, cintf = switch.connection[ sintf ]
481 eddef947 Bob Lantz
      if node != controller:
482 7b804ffb Brandon Heller
         lg.error("*** Error: switch %s not connected to correct controller" %
483
                  switch.name)
484 eddef947 Bob Lantz
         exit( 1 )
485
      controller.setIP( cintf, cip,  '/24' )
486 98d4f189 Bob Lantz
      switch.setIP( sintf, sip, '/24' )
487
      controller.setHostRoute( sip, cintf )
488
      switch.setHostRoute( cip, sintf )
489 7b804ffb Brandon Heller
   lg.info("\n")
490
   lg.info("*** Testing control network\n")
491 eddef947 Bob Lantz
   while not controller.intfIsUp( controller.intfs[ 0 ] ):
492 7b804ffb Brandon Heller
      lg.info("*** Waiting for %s to come up\n", controller.intfs[ 0 ])
493 98d4f189 Bob Lantz
      sleep( 1 )
494
   for switch in switches:
495 eddef947 Bob Lantz
      while not switch.intfIsUp( switch.intfs[ 0 ] ):
496 7b804ffb Brandon Heller
         lg.info("*** Waiting for %s to come up\n" % switch.intfs[ 0 ])
497 98d4f189 Bob Lantz
         sleep( 1 )
498 b7640209 Bob Lantz
      if pingTest( hosts=[ switch, controller ] ) != 0:
499 7b804ffb Brandon Heller
         lg.error("*** Error: control network test failed\n")
500 b7640209 Bob Lantz
         exit( 1 )
501 7b804ffb Brandon Heller
   lg.info("\n")
502 98d4f189 Bob Lantz
503 2f534913 Bob Lantz
def configHosts( hosts, ips ):
504 7b804ffb Brandon Heller
   """Configure a set of hosts, starting at IP address a.b.c.d"""
505 98d4f189 Bob Lantz
   for host in hosts:
506
      hintf = host.intfs[ 0 ]
507
      host.setIP( hintf, ips.next(), '/24' )
508
      host.setDefaultRoute( hintf )
509
      # You're low priority, dude!
510
      quietRun( 'renice +18 -p ' + `host.pid` )
511 7b804ffb Brandon Heller
      lg.info("%s ", host.name)
512
   lg.info("\n")
513
514 eddef947 Bob Lantz
# Test driver and topologies
515
516
class Network( object ):
517 7b804ffb Brandon Heller
   """Network topology (and test driver) base class."""
518 2f534913 Bob Lantz
   def __init__( self,
519
      kernel=True, 
520
      Controller=Controller, Switch=Switch, 
521
      hostIpGen=ipGen, hostIpStart=( 192, 168, 123, 1 ) ):
522
      self.kernel = kernel
523
      self.Controller = Controller
524
      self.Switch = Switch
525
      self.hostIps = apply( hostIpGen, hostIpStart )
526 95d9a374 Bob Lantz
      # Check for kernel modules
527 08cef003 Bob Lantz
      modules = quietRun( 'lsmod' )
528
      if not kernel and 'tun' not in modules:
529 7b804ffb Brandon Heller
         lg.error("*** Error: kernel module tun not loaded:\n")
530
         lg.error(" user datapath not supported\n")
531 95d9a374 Bob Lantz
         exit( 1 )
532 08cef003 Bob Lantz
      if kernel and 'ofdatapath' not in modules:
533 7b804ffb Brandon Heller
         lg.error("*** Error: kernel module ofdatapath not loaded:\n")
534
         lg.error(" kernel datapath not supported\n")
535 95d9a374 Bob Lantz
         exit( 1 )
536 42ba5d92 Bob Lantz
      # Create network, but don't start things up yet!
537
      self.prepareNet()
538 93519c04 Bob Lantz
   def configureControlNetwork( self,
539
      ipGen=ipGen, ipStart = (10, 0, 123, 1 ) ):
540
      ips = apply( ipGen, ipStart )
541 2f534913 Bob Lantz
      configureRoutedControlNetwork( self.controllers[ 0 ],
542 93519c04 Bob Lantz
         self.switches, ips = ips)
543 2f534913 Bob Lantz
   def configHosts( self ):
544
      configHosts( self.hosts, self.hostIps )
545 42ba5d92 Bob Lantz
   def prepareNet( self ):
546 eddef947 Bob Lantz
      """Create a network by calling makeNet as follows: 
547
         (switches, hosts ) = makeNet()
548 42ba5d92 Bob Lantz
         Create a controller here as well."""
549 eddef947 Bob Lantz
      kernel = self.kernel
550 7b804ffb Brandon Heller
      if kernel:
551
          lg.info("*** Using kernel datapath\n")
552
      else:
553
          lg.info("*** Using user datapath\n")
554
      lg.info("*** Creating controller\n")
555 2f534913 Bob Lantz
      self.controller = self.Controller( 'c0', kernel=kernel )
556
      self.controllers = [ self.controller ]
557 7b804ffb Brandon Heller
      lg.info("*** Creating network\n")
558 2f534913 Bob Lantz
      self.switches, self.hosts = self.makeNet( self.controller )
559 7b804ffb Brandon Heller
      lg.info("\n")
560 eddef947 Bob Lantz
      if not kernel:
561 7b804ffb Brandon Heller
         lg.info("*** Configuring control network\n")
562 2f534913 Bob Lantz
         self.configureControlNetwork()
563 7b804ffb Brandon Heller
      lg.info("*** Configuring hosts\n")
564 2f534913 Bob Lantz
      self.configHosts()
565 42ba5d92 Bob Lantz
   def start( self ):
566 7b804ffb Brandon Heller
      """Start controller and switches\n"""
567
      lg.info("*** Starting controller\n")
568 42ba5d92 Bob Lantz
      for controller in self.controllers:
569
         controller.start()
570 7b804ffb Brandon Heller
      lg.info("*** Starting %s switches" % len(self.switches))
571 42ba5d92 Bob Lantz
      for switch in self.switches:
572
         switch.start( self.controllers[ 0 ] )
573
   def stop( self ):
574 7b804ffb Brandon Heller
      """Stop the controller(s), switches and hosts\n"""
575
      lg.info("*** Stopping hosts\n")
576 f939eb56 Bob Lantz
      for host in self.hosts: 
577
         host.terminate()
578 7b804ffb Brandon Heller
      lg.info("*** Stopping switches\n")
579 42ba5d92 Bob Lantz
      for switch in self.switches:
580 7b804ffb Brandon Heller
         lg.info("%s" % switch.name)
581 b7640209 Bob Lantz
         switch.stop()
582 7b804ffb Brandon Heller
      lg.info("\n")
583
      lg.info("*** Stopping controller\n")
584 f939eb56 Bob Lantz
      for controller in self.controllers:
585 7c1d7c9f Brandon Heller
         controller.stop();
586 7b804ffb Brandon Heller
      lg.info("*** Test complete\n")
587 42ba5d92 Bob Lantz
   def runTest( self, test ):
588 7b804ffb Brandon Heller
      """Run a given test, called as test( controllers, switches, hosts)"""
589 42ba5d92 Bob Lantz
      return test( self.controllers, self.switches, self.hosts )
590
   def run( self, test ):
591
      """Perform a complete start/test/stop cycle; test is of the form
592
         test( controllers, switches, hosts )"""
593
      self.start()
594 7b804ffb Brandon Heller
      lg.info("*** Running test\n")
595 42ba5d92 Bob Lantz
      result = self.runTest( test )
596
      self.stop()
597 9011e0d2 Bob Lantz
      return result
598 eddef947 Bob Lantz
   def interact( self ):
599
      "Create a network and run our simple CLI."
600
      self.run( self, Cli )
601
   
602
def defaultNames( snames=None, hnames=None, dpnames=None ):
603
   "Reinitialize default names from generators, if necessary."
604
   if snames is None: snames = nameGen( 's' )
605
   if hnames is None: hnames = nameGen( 'h' )
606
   if dpnames is None: dpnames = nameGen( 'nl:' )
607
   return snames, hnames, dpnames
608
609
# Tree network
610
611
class TreeNet( Network ):
612 994c68f6 Bob Lantz
   "A tree-structured network with the specified depth and fanout"
613 2f534913 Bob Lantz
   def __init__( self, depth, fanout, **kwargs):
614 eddef947 Bob Lantz
      self.depth, self.fanout = depth, fanout
615 2f534913 Bob Lantz
      Network.__init__( self, **kwargs )
616 696a619d Bob Lantz
   def treeNet( self, controller, depth, fanout, snames=None,
617 eddef947 Bob Lantz
      hnames=None, dpnames=None ):
618
      """Return a tree network of the given depth and fanout as a triple:
619
         ( root, switches, hosts ), using the given switch, host and
620
         datapath name generators, with the switches connected to the given
621
         controller. If kernel=True, use the kernel datapath; otherwise the
622
         user datapath will be used."""
623
      # Ugly, but necessary (?) since defaults are only evaluated once
624
      snames, hnames, dpnames = defaultNames( snames, hnames, dpnames )
625
      if ( depth == 0 ):
626
         host = Host( hnames.next() )
627 7b804ffb Brandon Heller
         lg.info("%s " % host.name)
628 eddef947 Bob Lantz
         return host, [], [ host ]
629 696a619d Bob Lantz
      dp = dpnames.next() if self.kernel else None
630 eddef947 Bob Lantz
      switch = Switch( snames.next(), dp )
631 696a619d Bob Lantz
      if not self.kernel: createLink( switch, controller )
632 7b804ffb Brandon Heller
      lg.info("%s " % switch.name)
633 eddef947 Bob Lantz
      switches, hosts = [ switch ], []
634
      for i in range( 0, fanout ):
635 9011e0d2 Bob Lantz
         child, slist, hlist = self.treeNet( controller, 
636 696a619d Bob Lantz
            depth - 1, fanout, snames, hnames, dpnames )
637 eddef947 Bob Lantz
         createLink( switch, child )
638
         switches += slist
639
         hosts += hlist
640
      return switch, switches, hosts
641 9011e0d2 Bob Lantz
   def makeNet( self, controller ):
642
      root, switches, hosts = self.treeNet( controller,
643 696a619d Bob Lantz
         self.depth, self.fanout )
644 7b804ffb Brandon Heller
      lg.info("\n")
645 eddef947 Bob Lantz
      return switches, hosts
646
   
647
# Grid network
648
649
class GridNet( Network ):
650 08cef003 Bob Lantz
   """An N x M grid/mesh network of switches, with hosts at the edges.
651
      This class also demonstrates creating a somewhat complicated
652
      topology."""
653 0b084dd5 Bob Lantz
   def __init__( self, n, m, linear=False, **kwargs ):
654 eddef947 Bob Lantz
      self.n, self.m, self.linear = n, m, linear and m == 1
655 0b084dd5 Bob Lantz
      Network.__init__( self, **kwargs )
656 9011e0d2 Bob Lantz
   def makeNet( self, controller ):
657 eddef947 Bob Lantz
      snames, hnames, dpnames = defaultNames()
658
      n, m = self.n, self.m
659
      hosts = []
660
      switches = []
661
      kernel = self.kernel
662
      rows = []
663
      if not self.linear:
664 7b804ffb Brandon Heller
         lg.info("*** gridNet: creating", n, "x", m, "grid of switches")
665 eddef947 Bob Lantz
      for y in range( 0, m ):
666
         row = []
667
         for x in range( 0, n ):
668
            dp = dpnames.next() if kernel else None
669
            switch = Switch( snames.next(), dp )
670
            if not kernel: createLink( switch, controller )
671
            row.append( switch )
672
            switches += [ switch ]
673 7b804ffb Brandon Heller
            lg.info("%s " % switch.name)
674 eddef947 Bob Lantz
         rows += [ row ]
675
      # Hook up rows
676
      for row in rows:
677
         previous = None
678
         for switch in row:
679
            if previous is not None:
680
               createLink( switch, previous )
681
            previous = switch
682
         h1, h2 = Host( hnames.next() ), Host( hnames.next() )
683
         createLink( h1, row[ 0 ] )
684
         createLink( h2, row[ -1 ] )
685
         hosts += [ h1, h2 ]
686 7b804ffb Brandon Heller
         lg.info("%s %s" % (h1.name, h2.name))
687 eddef947 Bob Lantz
      # Return here if we're using this to make a linear network
688 994c68f6 Bob Lantz
      if self.linear: return switches, hosts
689 eddef947 Bob Lantz
      # Hook up columns
690
      for x in range( 0, n ):
691
         previous = None
692
         for y in range( 0, m ):
693
            switch = rows[ y ][ x ]
694
            if previous is not None:
695
               createLink( switch, previous )
696
            previous = switch
697
         h1, h2 = Host( hnames.next() ), Host( hnames.next() )
698
         createLink( h1, rows[ 0 ][ x ] )
699
         createLink( h2, rows[ -1 ][ x ] )
700
         hosts += [ h1, h2 ]
701 7b804ffb Brandon Heller
         lg.info("%s %s" % (h1.name, h2.name))
702 eddef947 Bob Lantz
      return switches, hosts
703
704
class LinearNet( GridNet ):
705 696a619d Bob Lantz
   "A network consisting of two hosts connected by a string of switches."
706
   def __init__( self, switchCount, **kwargs ):
707 eddef947 Bob Lantz
      self.switchCount = switchCount
708 696a619d Bob Lantz
      GridNet.__init__( self, switchCount, 1, linear=True, **kwargs )
709 eddef947 Bob Lantz
      
710
# Tests
711 98d4f189 Bob Lantz
712 eddef947 Bob Lantz
def parsePing( pingOutput ):
713
   "Parse ping output and return packets sent, received."
714
   r = r'(\d+) packets transmitted, (\d+) received'
715
   m = re.search( r, pingOutput )
716
   if m == None:
717 7b804ffb Brandon Heller
      lg.error("*** Error: could not parse ping output: %s\n" % pingOutput)
718 eddef947 Bob Lantz
      exit( 1 )
719
   sent, received  = int( m.group( 1 ) ), int( m.group( 2 ) )
720
   return sent, received
721 994c68f6 Bob Lantz
   
722 9011e0d2 Bob Lantz
def pingTest( controllers=[], switches=[], hosts=[], verbose=False ):
723 eddef947 Bob Lantz
   "Test that each host can reach every other host."
724
   packets = 0 ; lost = 0
725
   for node in hosts:
726 7b804ffb Brandon Heller
      if verbose:
727
         lg.info("%s -> " % node.name)
728 eddef947 Bob Lantz
      for dest in hosts: 
729
         if node != dest:
730
            result = node.cmd( 'ping -c1 ' + dest.IP() )
731
            sent, received = parsePing( result )
732
            packets += sent
733
            if received > sent:
734 7b804ffb Brandon Heller
               lg.error("*** Error: received too many packets")
735
               lg.error("%s" % result)
736 eddef947 Bob Lantz
               node.cmdPrint( 'route' )
737
               exit( 1 )
738
            lost += sent - received
739
            if verbose: 
740 7b804ffb Brandon Heller
               lg.info(("%s " % dest.name) if received else "X ")
741
      if verbose:
742
          lg.info("\n")
743 eddef947 Bob Lantz
   ploss = 100 * lost/packets
744
   if verbose:
745 7b804ffb Brandon Heller
      lg.info("%d%% packet loss (%d/%d lost)\n" % ( ploss, lost, packets ))
746 eddef947 Bob Lantz
      flush()
747
   return ploss
748 98d4f189 Bob Lantz
749 eddef947 Bob Lantz
def pingTestVerbose( controllers, switches, hosts ):
750 994c68f6 Bob Lantz
   return "%d %% packet loss" % \
751
      pingTest( controllers, switches, hosts, verbose=True )
752
 
753
def parseIperf( iperfOutput ):
754
   "Parse iperf output and return bandwidth."
755
   r = r'([\d\.]+ \w+/sec)'
756
   m = re.search( r, iperfOutput )
757
   return m.group( 1 ) if m is not None else "could not parse iperf output"
758
    
759
def iperf( hosts, verbose=False ):
760 98d4f189 Bob Lantz
   "Run iperf between two hosts."
761
   assert len( hosts ) == 2
762
   host1, host2 = hosts[ 0 ], hosts[ 1 ]
763 994c68f6 Bob Lantz
   host1.cmd( 'killall -9 iperf') # XXX shouldn't be global killall
764
   server = host1.cmd( 'iperf -s &' )
765 7b804ffb Brandon Heller
   if verbose:
766
       lg.info("%s" % server)
767 994c68f6 Bob Lantz
   client = host2.cmd( 'iperf -t 5 -c ' + host1.IP() )
768 7b804ffb Brandon Heller
   if verbose:
769
       lg.info("%s" % client)
770 994c68f6 Bob Lantz
   server = host1.cmd( 'kill -9 %iperf' )
771 7b804ffb Brandon Heller
   if verbose:
772
       lg.info("%s" % server)
773 994c68f6 Bob Lantz
   return [ parseIperf( server ), parseIperf( client ) ]
774
   
775
def iperfTest( controllers, switches, hosts, verbose=False ):
776 98d4f189 Bob Lantz
   "Simple iperf test between two hosts."
777 7b804ffb Brandon Heller
   if verbose: 
778
       lg.info("*** Starting ping test\n")
779 eddef947 Bob Lantz
   h0, hN = hosts[ 0 ], hosts[ -1 ]
780 7b804ffb Brandon Heller
   lg.info("*** iperfTest: Testing bandwidth between")
781
   lg.info("%s and %s\n" % (h0.name, hN.name))
782 994c68f6 Bob Lantz
   result = iperf( [ h0, hN], verbose )
783 7b804ffb Brandon Heller
   lg.info("*** result: %s\n" % result)
784 994c68f6 Bob Lantz
   return result
785
786 eddef947 Bob Lantz
# Simple CLI
787
788
class Cli( object ):
789
   "Simple command-line interface to talk to nodes."
790
   cmds = [ '?', 'help', 'nodes', 'sh', 'pingtest', 'iperf', 'net', 'exit' ]
791
   def __init__( self, controllers, switches, hosts ):
792
      self.controllers = controllers
793
      self.switches = switches
794
      self.hosts = hosts
795
      self.nodemap = {}
796
      self.nodelist = controllers + switches + hosts
797
      for node in self.nodelist:
798
         self.nodemap[ node.name ] = node
799
      self.run()
800
   # Commands
801
   def help( self, args ):
802
      "Semi-useful help for CLI"
803 7b804ffb Brandon Heller
      help_str = "Available commands are:" + str(self.cmds) + "\n" + \
804
                 "You may also send a command to a node using:" + \
805
                 "  <node> command {args}" + \
806
                 "For example:" + \
807
                 "  mininet> h0 ifconfig" + \
808
                 "\n" + \
809
                 "The interpreter automatically substitutes IP addresses" + \
810
                 "for node names, so commands like" + \
811
                 "  mininet> h0 ping -c1 h1" + \
812
                 "should work." + \
813
                 "\n" + \
814
                 "Interactive commands are not really supported yet," + \
815
                 "so please limit commands to ones that do not" + \
816
                 "require user interaction and will terminate" + \
817
                 "after a reasonable amount of time."
818
      print(help_str)
819
820 eddef947 Bob Lantz
   def nodes( self, args ):
821
      "List available nodes"
822 7b804ffb Brandon Heller
      lg.info("available nodes are:\n", [ node.name for node in self.nodelist])
823 eddef947 Bob Lantz
   def sh( self, args ):
824
      "Run an external shell command"
825
      call( [ 'sh', '-c' ] + args )
826
   def pingtest( self, args ):
827
      pingTest( self.controllers, self.switches, self.hosts, verbose=True )
828
   def net( self, args ):
829
      for switch in self.switches:
830 7b804ffb Brandon Heller
         lg.info("%s <-> ", switch.name)
831 eddef947 Bob Lantz
         for intf in switch.intfs:
832 08cef003 Bob Lantz
            node, remoteIntf = switch.connection[ intf ]
833 7b804ffb Brandon Heller
            lg.info("%s" % node.name)
834 eddef947 Bob Lantz
   def iperf( self, args ):
835
      if len( args ) != 2:
836 7b804ffb Brandon Heller
         lg.error("usage: iperf <h1> <h2>")
837 eddef947 Bob Lantz
         return
838
      for host in args:
839
         if host not in self.nodemap:
840 7b804ffb Brandon Heller
            lg.error("iperf: cannot find host: %s" % host)
841 eddef947 Bob Lantz
            return
842 9dbb68df Bob Lantz
      iperf( [ self.nodemap[ h ] for h in args ], verbose=True )
843 eddef947 Bob Lantz
   # Interpreter
844
   def run( self ):
845
      "Read and execute commands."
846 7b804ffb Brandon Heller
      lg.info("*** cli: starting")
847 eddef947 Bob Lantz
      while True:
848 7b804ffb Brandon Heller
         lg.info("mininet> ")
849 eddef947 Bob Lantz
         input = sys.stdin.readline()
850
         if input == '': break
851
         if input[ -1 ] == '\n': input = input[ : -1 ]
852
         cmd = input.split( ' ' )
853
         first = cmd[ 0 ]
854
         rest = cmd[ 1: ]
855
         if first in self.cmds and hasattr( self, first ):
856
            getattr( self, first )( rest )
857
         elif first in self.nodemap and rest != []:
858
            node = self.nodemap[ first ]
859
            # Substitute IP addresses for node names in command
860
            rest = [ self.nodemap[ arg ].IP() if arg in self.nodemap else arg
861
               for arg in rest ]
862
            rest = ' '.join( rest )
863
            # Interactive commands don't work yet, and
864
            # there are still issues with control-c
865 7b804ffb Brandon Heller
            lg.error("*** %s: running %s\n" % (node.name, rest))
866 eddef947 Bob Lantz
            node.sendCmd( rest )
867
            while True:
868
               try:
869
                  done, data = node.monitor()
870 7b804ffb Brandon Heller
                  lg.info("%s\n" % data)
871 eddef947 Bob Lantz
                  if done: break
872
               except KeyboardInterrupt: node.sendInt()
873
         elif first == '': pass
874
         elif first in [ 'exit', 'quit' ]: break
875
         elif first == '?': self.help( rest )
876 7b804ffb Brandon Heller
         else:
877
            lg.error("cli: unknown node or command: < %s >\n" % first)
878
      lg.info("*** cli: exiting")
879 98d4f189 Bob Lantz
   
880
def fixLimits():
881
   "Fix ridiculously small resource limits."
882
   setrlimit( RLIMIT_NPROC, ( 4096, 8192 ) )
883
   setrlimit( RLIMIT_NOFILE, ( 16384, 32768 ) )
884
885 eddef947 Bob Lantz
def init():
886 9011e0d2 Bob Lantz
   "Initialize Mininet."
887 eddef947 Bob Lantz
   if os.getuid() != 0: 
888 55dd9368 Bob Lantz
      # Note: this script must be run as root 
889
      # Perhaps we should do so automatically!
890 eddef947 Bob Lantz
      print "*** Mininet must run as root."; exit( 1 )
891 ea7c8795 Brandon Heller
   # If which produces no output, then netns is not in the path.
892
   # May want to loosen this to handle netns in the current dir.
893 9dbb68df Bob Lantz
   if not quietRun( [ 'which', 'netns' ] ):
894
       raise Exception( "Could not find netns; see INSTALL" )
895 eddef947 Bob Lantz
   fixLimits()
896 6bd22292 Bob Lantz
897 98d4f189 Bob Lantz
if __name__ == '__main__':
898 7b804ffb Brandon Heller
   level = logging.INFO
899
   if len(sys.argv) > 1:
900
      level_name = sys.argv[1]
901
      level = LEVELS.get(level_name, level)
902
   setup_logging(level)
903
   #lg.basicConfig(level = level, format = LOG_MSG_FORMAT)
904
905 eddef947 Bob Lantz
   init()
906 994c68f6 Bob Lantz
   results = {}
907 7b804ffb Brandon Heller
   lg.info("*** Welcome to Mininet!\n")
908
   lg.info("*** Look in examples/ for more examples\n\n")
909
   lg.info("*** Testing Mininet with kernel and user datapath\n")
910 994c68f6 Bob Lantz
   for datapath in [ 'kernel', 'user' ]:
911
      k = datapath == 'kernel'
912 55dd9368 Bob Lantz
      network = TreeNet( depth=2, fanout=4, kernel=k)
913
      result = network.run( pingTestVerbose )
914
      results[ datapath ] = result
915 7b804ffb Brandon Heller
   lg.info("*** Test results: %s\n", results)
916
else:
917
   setup_logging(LOG_LEVEL_DEFAULT)
918
   #lg.basicConfig(level = LOG_LEVEL_DEFAULT, format = LOG_MSG_FORMAT)