Statistics
| Branch: | Tag: | Revision:

mininet / mininet / mininet.py @ 1095628b

History | View | Annotate | Download (31.1 KB)

1
#!/usr/bin/python
2

    
3
"""
4
Mininet: A simple networking testbed for OpenFlow!
5

6
Mininet creates scalable OpenFlow test networks by using
7
process-based virtualization and network namespaces. 
8

9
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

13
Each host has:
14
   A virtual console (pipes to a shell)
15
   A virtual interfaces (half of a veth pair)
16
   A parent shell (and possibly some child processes) in a namespace
17
   
18
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

21
This version supports both the kernel and user space datapaths
22
from the OpenFlow reference implementation.
23

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

27
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
controller via the loopback interface.
31

32
In user datapath mode, the controller and switches are full-service
33
nodes that live in their own network namespaces and have management
34
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
several switch interfaces, halves of veth pairs whose other halves
39
reside in the host nodes that the switches are connected to.
40

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
   OpenFlowVMS spec files, but I haven't done so yet.
50
   For the moment, specifying configurations and tests in Python
51
   is straightforward and relatively concise.
52
   Soon, we may want to split the various subsystems (core,
53
   topology/network, cli, tests, etc.) into multiple modules.
54
   Currently nox support is in nox.py.
55
   We'd like to support OpenVSwitch as well as the reference
56
   implementation.
57
   
58
Bob Lantz
59
rlantz@cs.stanford.edu
60

61
History:
62
11/19/09 Initial revision (user datapath only)
63
11/19/09 Mininet demo at OpenFlow SWAI meeting
64
12/08/09 Kernel datapath support complete
65
12/09/09 Moved controller and switch routines into classes
66
12/12/09 Added subdivided network driver workflow
67
12/13/09 Added support for custom controller and switch classes
68
"""
69

    
70
from subprocess import call, check_call, Popen, PIPE, STDOUT
71
from time import sleep
72
import os, re, signal, sys, select
73
flush = sys.stdout.flush
74
from resource import setrlimit, RLIMIT_NPROC, RLIMIT_NOFILE
75

    
76
DATAPATHS = ['user', 'kernel']
77

    
78
# Utility routines to make it easier to run commands
79

    
80
def run( cmd ):
81
   "Simple interface to subprocess.call()"
82
   return call( cmd.split( ' ' ) )
83

    
84
def checkRun( cmd ):
85
   "Simple interface to subprocess.check_call()"
86
   check_call( cmd.split( ' ' ) )
87
   
88
def quietRun( cmd ):
89
   "Run a command, routing stderr to stdout, and return the output."
90
   if isinstance( cmd, str ): cmd = cmd.split( ' ' )
91
   popen = Popen( cmd, stdout=PIPE, stderr=STDOUT)
92
   # We can't use Popen.communicate() because it uses 
93
   # select(), which can't handle
94
   # high file descriptor numbers! poll() can, however.
95
   output = ''
96
   readable = select.poll()
97
   readable.register( popen.stdout )
98
   while True:
99
      while readable.poll(): 
100
         data = popen.stdout.read( 1024 )
101
         if len( data ) == 0: break
102
         output += data
103
      popen.poll()
104
      if popen.returncode != None: break
105
   return output
106
   
107
class Node( object ):
108
   """A virtual network node is simply a shell in a network namespace.
109
      We communicate with it using pipes."""
110
   inToNode = {}
111
   outToNode = {}
112
   def __init__( self, name, inNamespace=True ):
113
      self.name = name
114
      closeFds = False # speed vs. memory use
115
      # xpg_echo is needed so we can echo our sentinel in sendCmd
116
      cmd = [ '/bin/bash', '-O', 'xpg_echo' ]
117
      self.inNamespace = inNamespace
118
      if self.inNamespace: cmd = [ 'netns' ] + cmd
119
      self.shell = Popen( cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT,
120
         close_fds=closeFds )
121
      self.stdin = self.shell.stdin
122
      self.stdout = self.shell.stdout
123
      self.pollOut = select.poll() 
124
      self.pollOut.register( self.stdout )
125
      # Maintain mapping between file descriptors and nodes
126
      # This could be useful for monitoring multiple nodes
127
      # using select.poll()
128
      self.outToNode[ self.stdout.fileno() ] = self
129
      self.inToNode[ self.stdin.fileno() ] = self
130
      self.pid = self.shell.pid
131
      self.intfCount = 0
132
      self.intfs = []
133
      self.ips = {}
134
      self.connection = {}
135
      self.waiting = False
136
      self.execed = False
137
   def fdToNode( self, f ):
138
      node = self.outToNode.get( f )
139
      return node or self.inToNode.get( f )
140
   def cleanup( self ):
141
      # Help python collect its garbage
142
      self.shell = None
143
   # Subshell I/O, commands and control
144
   def read( self, max ): return os.read( self.stdout.fileno(), max )
145
   def write( self, data ): os.write( self.stdin.fileno(), data )
146
   def terminate( self ):
147
      os.kill( self.pid, signal.SIGKILL )
148
      self.cleanup()
149
   def stop( self ): self.terminate()
150
   def waitReadable( self ): self.pollOut.poll()
151
   def sendCmd( self, cmd ):
152
      """Send a command, followed by a command to echo a sentinel,
153
         and return without waiting for the command to complete."""
154
      assert not self.waiting
155
      if cmd[ -1 ] == '&':
156
         separator = '&'
157
         cmd = cmd[ : -1 ]
158
      else: separator = ';'
159
      if isinstance( cmd, list): cmd = ' '.join( cmd )
160
      self.write( cmd + separator + " echo -n '\\0177' \n")
161
      self.waiting = True
162
   def monitor( self ):
163
      "Monitor a command's output, returning (done, data)."
164
      assert self.waiting
165
      self.waitReadable()
166
      data = self.read( 1024 )
167
      if len( data ) > 0 and data[ -1 ] == chr( 0177 ):
168
         self.waiting = False
169
         return True, data[ : -1 ]
170
      else:
171
         return False, data
172
   def sendInt( self ):
173
      "Send ^C, hopefully interrupting a running subprocess."
174
      self.write( chr( 3 ) )
175
   def waitOutput( self ):
176
      """Wait for a command to complete (signaled by a sentinel
177
      character, ASCII(127) appearing in the output stream) and return
178
      the output, including trailing newline."""
179
      assert self.waiting
180
      output = ""
181
      while True:
182
         self.waitReadable()
183
         data = self.read( 1024 )
184
         if len(data) > 0  and data[ -1 ] == chr( 0177 ): 
185
            output += data[ : -1 ]
186
            break
187
         else: output += data
188
      self.waiting = False
189
      return output
190
   def cmd( self, cmd ):
191
      "Send a command, wait for output, and return it."
192
      self.sendCmd( cmd )
193
      return self.waitOutput()
194
   def cmdPrint( self, cmd ):
195
      "Call cmd, printing the command and output"
196
      print "***", self.name, ":", cmd
197
      result = self.cmd( cmd )
198
      print result,
199
      return result
200
   # Interface management, configuration, and routing
201
   def intfName( self, n):
202
      "Construct a canonical interface name node-intf for interface N."
203
      return self.name + '-eth' + `n`
204
   def newIntf( self ):
205
      "Reserve and return a new interface name for this node."
206
      intfName = self.intfName( self.intfCount)
207
      self.intfCount += 1
208
      self.intfs += [ intfName ]
209
      return intfName
210
   def setIP( self, intf, ip, bits ):
211
      "Set an interface's IP address."
212
      result = self.cmd( [ 'ifconfig', intf, ip + bits, 'up' ] )
213
      self.ips[ intf ] = ip
214
      return result
215
   def setHostRoute( self, ip, intf ):
216
      "Add a route to the given IP address via intf."
217
      return self.cmd( 'route add -host ' + ip + ' dev ' + intf )
218
   def setDefaultRoute( self, intf ):
219
      "Set the default route to go through intf."
220
      self.cmd( 'ip route flush' )
221
      return self.cmd( 'route add default ' + intf )
222
   def IP( self ):
223
      "Return IP address of first interface"
224
      if len( self.intfs ) > 0:
225
         return self.ips.get( self.intfs[ 0 ], None )
226
   def intfIsUp( self, intf ):
227
      "Check if one of our interfaces is up."
228
      return 'UP' in self.cmd( 'ifconfig ' + self.intfs[ 0 ] )
229
   # Other methods  
230
   def __str__( self ): 
231
      result = self.name
232
      result += ": IP=" + self.IP() + " intfs=" + ','.join( self.intfs )
233
      result += " waiting=" +  `self.waiting`
234
      return result
235

    
236

    
237

    
238
class Host( Node ):
239
   """A host is simply a Node."""
240
   pass
241
      
242
class Controller( Node ):
243
   """A Controller is a Node that is running (or has execed) an 
244
      OpenFlow controller."""
245
   def __init__( self, name, kernel=True, controller='controller',
246
      cargs='-v ptcp:', cdir=None ):
247
      self.controller = controller
248
      self.cargs = cargs
249
      self.cdir = cdir
250
      Node.__init__( self, name, inNamespace=( not kernel ) )
251
   def start( self ):
252
      "Start <controller> <args> on controller, logging to /tmp/cN.log"
253
      cout = '/tmp/' + self.name + '.log'
254
      if self.cdir is not None:
255
         self.cmdPrint( 'cd ' + self.cdir )
256
      self.cmdPrint( self.controller + ' ' + self.cargs + 
257
         ' 1> ' + cout + ' 2> ' + cout + ' &' )
258
      self.execed = False # XXX Until I fix it
259
   def stop( self, controller='controller' ):
260
      "Stop controller cprog on controller"
261
      self.cmd( "kill %" + controller )  
262
      self.terminate()
263
         
264
class Switch( Node ):
265
   """A Switch is a Node that is running (or has execed)
266
      an OpenFlow switch."""
267
   def __init__( self, name, datapath=None ):
268
      self.dp = datapath
269
      Node.__init__( self, name, inNamespace=( datapath == None ) )
270
   def startUserDatapath( self, controller ):
271
      """Start OpenFlow reference user datapath, 
272
         logging to /tmp/sN-{ofd,ofp}.log"""
273
      ofdlog = '/tmp/' + self.name + '-ofd.log'
274
      ofplog = '/tmp/' + self.name + '-ofp.log'
275
      self.cmd( 'ifconfig lo up' )
276
      intfs = self.intfs[ 1 : ] # 0 is mgmt interface
277
      self.cmdPrint( 'ofdatapath -i ' + ','.join( intfs ) +
278
       ' ptcp: 1> ' + ofdlog + ' 2> '+ ofdlog + ' &' )
279
      self.cmdPrint( 'ofprotocol tcp:' + controller.IP() +
280
         ' tcp:localhost --fail=closed 1> ' + ofplog + ' 2>' + ofplog + ' &' )
281
   def stopUserDatapath( self ):
282
      "Stop OpenFlow reference user datapath."
283
      self.cmd( "kill %ofdatapath" )
284
      self.cmd( "kill %ofprotocol" )
285
   def startKernelDatapath( self, controller):
286
      "Start up switch using OpenFlow reference kernel datapath."
287
      ofplog = '/tmp/' + self.name + '-ofp.log'
288
      quietRun( 'ifconfig lo up' )
289
      # Delete local datapath if it exists;
290
      # then create a new one monitoring the given interfaces
291
      quietRun( 'dpctl deldp ' + self.dp )
292
      self.cmdPrint( 'dpctl adddp ' + self.dp )
293
      self.cmdPrint( 'dpctl addif ' + self.dp + ' ' + ' '.join( self.intfs ) )
294
      # Run protocol daemon
295
      self.cmdPrint( 'ofprotocol' +
296
         ' ' + self.dp + ' tcp:127.0.0.1 ' + 
297
         ' --fail=closed 1> ' + ofplog + ' 2>' + ofplog + ' &' )
298
      self.execed = False # XXX until I fix it
299
   def stopKernelDatapath( self ):
300
      "Terminate a switch using OpenFlow reference kernel datapath."
301
      quietRun( 'dpctl deldp ' + self.dp )
302
      # In theory the interfaces should go away after we shut down.
303
      # However, this takes time, so we're better off to remove them
304
      # explicitly so that we won't get errors if we run before they
305
      # have been removed by the kernel. Unfortunately this is very slow.
306
      self.cmd( 'kill %ofprotocol')
307
      for intf in self.intfs:
308
         quietRun( 'ip link del ' + intf )
309
         sys.stdout.write( '.' ) ; flush()
310
   def start( self, controller ): 
311
      if self.dp is None: self.startUserDatapath( controller )
312
      else: self.startKernelDatapath( controller )
313
   def stop( self ):
314
      if self.dp is None: self.stopUserDatapath()
315
      else: self.stopKernelDatapath()
316
   def sendCmd( self, cmd ):
317
      if not self.execed: return Node.sendCmd( self, cmd )
318
      else: print "*** Error:", self.name, "has execed and cannot accept commands"
319
   def monitor( self ):
320
      if not self.execed: return Node.monitor( self )
321
      else: return True, ''
322
         
323
# Interface management
324
# 
325
# Interfaces are managed as strings which are simply the
326
# interface names, of the form "nodeN-ethM".
327
#
328
# To connect nodes, we create a pair of veth interfaces, and then place them
329
# in the pair of nodes that we want to communicate. We then update the node's
330
# list of interfaces and connectivity map.
331
#
332
# For the kernel datapath, switch interfaces
333
# live in the root namespace and thus do not have to be
334
# explicitly moved.
335

    
336
MOVEINTF_DELAY = 0.0001
337

    
338
def makeIntfPair( intf1, intf2 ):
339
   "Make a veth pair of intf1 and intf2."
340
   # Delete any old interfaces with the same names
341
   quietRun( 'ip link del ' + intf1 )
342
   quietRun( 'ip link del ' + intf2 )
343
   # Create new pair
344
   cmd = 'ip link add name ' + intf1 + ' type veth peer name ' + intf2
345
   return checkRun( cmd )
346
   
347
def moveIntf( intf, node, print_error = False ):
348
   "Move intf to node."
349
   cmd = 'ip link set ' + intf + ' netns ' + `node.pid`
350
   quietRun( cmd )
351
   links = node.cmd( 'ip link show' )
352
   if not intf in links:
353
      if print_error:
354
          print "*** Error: moveIntf:", intf, "not successfully moved to",
355
          print node.name,":"
356
      return False
357
   return True
358

    
359
def retry( n, retry_delay, fn, *args):
360
   "Try something N times before giving up."
361
   tries = 0
362
   while not apply( fn, args ) and tries < n:
363
      sleep( retry_delay )
364
      tries += 1
365
   if tries >= n: 
366
      print "*** gave up after %i retries" % tries; flush()
367
      exit( 1 )
368
   
369
def createLink( node1, node2 ):
370
   "Create a link node1-intf1 <---> node2-intf2."
371
   intf1 = node1.newIntf()
372
   intf2 = node2.newIntf()
373
   makeIntfPair( intf1, intf2 )
374
   if node1.inNamespace: retry( 3, MOVEINTF_DELAY, moveIntf, intf1, node1 )
375
   if node2.inNamespace: retry( 3, MOVEINTF_DELAY, moveIntf, intf2, node2 )
376
   node1.connection[ intf1 ] = ( node2, intf2 )
377
   node2.connection[ intf2 ] = ( node1, intf1 )
378
   return intf1, intf2
379

    
380
# Handy utilities
381
 
382
def createNodes( name, count ):
383
   "Create and return a list of nodes."
384
   nodes = [ Node( name + `i` ) for i in range( 0, count ) ]
385
   # print "*** CreateNodes: created:", nodes
386
   return nodes
387
     
388
def dumpNodes( nodes ):
389
   "Dump ifconfig of each node."
390
   for node in nodes:
391
      print "*** Dumping node", node.name
392
      print node.cmd( 'ip link show' )
393
      print node.cmd( 'route' )
394
   
395
def ipGen( A, B, c, d ):
396
   "Generate next IP class B IP address, starting at A.B.c.d"
397
   while True:
398
      yield '%d.%d.%d.%d' % ( A, B, c, d )
399
      d += 1
400
      if d > 254:
401
         d = 1
402
         c += 1
403
         if c > 254: break
404

    
405
def nameGen( prefix ):
406
   "Generate names starting with prefix."
407
   i = 0
408
   while True: yield prefix + `i`; i += 1
409
      
410
# Control network support:
411
#
412
# Create an explicit control network. Currently this is only
413
# used by the user datapath configuration.
414
#
415
# Notes:
416
#
417
# 1. If the controller and switches are in the same (e.g. root)
418
#    namespace, they can just use the loopback connection.
419
#    We may wish to do this for the user datapath as well as the
420
#    kernel datapath.
421
#
422
# 2. If we can get unix domain sockets to work, we can use them
423
#    instead of an explicit control network.
424
#
425
# 3. Instead of routing, we could bridge or use "in-band" control.
426
#
427
# 4. Even if we dispense with this in general, it could still be
428
#    useful for people who wish to simulate a separate control
429
#    network (since real networks may need one!)
430

    
431
def configureRoutedControlNetwork( controller, switches, ips):
432
   """Configure a routed control network on controller and switches,
433
      for use with the user datapath."""
434
   cip = ips.next()
435
   print controller.name, '<->',
436
   for switch in switches:
437
      print switch.name, ; flush()
438
      sip = ips.next()
439
      sintf = switch.intfs[ 0 ]
440
      node, cintf = switch.connection[ sintf ]
441
      if node != controller:
442
         print "*** Error: switch", switch.name, 
443
         print "not connected to correct controller"
444
         exit( 1 )
445
      controller.setIP( cintf, cip,  '/24' )
446
      switch.setIP( sintf, sip, '/24' )
447
      controller.setHostRoute( sip, cintf )
448
      switch.setHostRoute( cip, sintf )
449
   print
450
   print "*** Testing control network"
451
   while not controller.intfIsUp( controller.intfs[ 0 ] ):
452
      print "*** Waiting for ", controller.intfs[ 0 ], "to come up"
453
      sleep( 1 )
454
   for switch in switches:
455
      while not switch.intfIsUp( switch.intfs[ 0 ] ):
456
         print "*** Waiting for ", switch.intfs[ 0 ], "to come up"
457
         sleep( 1 )
458
      if pingTest( hosts=[ switch, controller ] ) != 0:
459
         print "*** Error: control network test failed"
460
         exit( 1 )
461

    
462
def configHosts( hosts, ips ):
463
   "Configure a set of hosts, starting at IP address a.b.c.d"
464
   for host in hosts:
465
      hintf = host.intfs[ 0 ]
466
      host.setIP( hintf, ips.next(), '/24' )
467
      host.setDefaultRoute( hintf )
468
      # You're low priority, dude!
469
      quietRun( 'renice +18 -p ' + `host.pid` )
470
      print host.name, ; flush()
471
   print
472
 
473
# Test driver and topologies
474

    
475
class Network( object ):
476
   "Network topology (and test driver) base class."
477
   def __init__( self,
478
      kernel=True, 
479
      Controller=Controller, Switch=Switch, 
480
      hostIpGen=ipGen, hostIpStart=( 192, 168, 123, 1 ) ):
481
      self.kernel = kernel
482
      self.Controller = Controller
483
      self.Switch = Switch
484
      self.hostIps = apply( hostIpGen, hostIpStart )
485
      # Check for kernel modules
486
      modules = quietRun( 'lsmod' )
487
      if not kernel and 'tun' not in modules:
488
         print "*** Error: kernel module tun not loaded:",
489
         print " user datapath not supported"
490
         exit( 1 )
491
      if kernel and 'ofdatapath' not in modules:
492
         print "*** Error: kernel module ofdatapath not loaded:",
493
         print " kernel datapath not supported"
494
         exit( 1 )
495
      # Create network, but don't start things up yet!
496
      self.prepareNet()
497
   def configureControlNetwork( self,
498
      ipGen=ipGen, ipStart = (10, 0, 123, 1 ) ):
499
      ips = apply( ipGen, ipStart )
500
      configureRoutedControlNetwork( self.controllers[ 0 ],
501
         self.switches, ips = ips)
502
   def configHosts( self ):
503
      configHosts( self.hosts, self.hostIps )
504
   def prepareNet( self ):
505
      """Create a network by calling makeNet as follows: 
506
         (switches, hosts ) = makeNet()
507
         Create a controller here as well."""
508
      kernel = self.kernel
509
      if kernel: print "*** Using kernel datapath"
510
      else: print "*** Using user datapath"
511
      print "*** Creating controller"
512
      self.controller = self.Controller( 'c0', kernel=kernel )
513
      self.controllers = [ self.controller ]
514
      print "*** Creating network"
515
      self.switches, self.hosts = self.makeNet( self.controller )
516
      print
517
      if not kernel:
518
         print "*** Configuring control network"
519
         self.configureControlNetwork()
520
      print "*** Configuring hosts"
521
      self.configHosts()
522
   def start( self ):
523
      "Start controller and switches"
524
      print "*** Starting controller"
525
      for controller in self.controllers:
526
         controller.start()
527
      print "*** Starting", len( self.switches ), "switches"
528
      for switch in self.switches:
529
         switch.start( self.controllers[ 0 ] )
530
   def stop( self ):
531
      "Stop the controller(s), switches and hosts"
532
      print "*** Stopping hosts"
533
      for host in self.hosts: 
534
         host.terminate()
535
      print "*** Stopping switches"
536
      for switch in self.switches:
537
         print switch.name, ; flush()
538
         switch.stop()
539
      print
540
      print "*** Stopping controller"
541
      for controller in self.controllers:
542
         controller.stop();
543
      print
544
      print "*** Test complete"
545
   def runTest( self, test ):
546
      "Run a given test, called as test( controllers, switches, hosts)"
547
      return test( self.controllers, self.switches, self.hosts )
548
   def run( self, test ):
549
      """Perform a complete start/test/stop cycle; test is of the form
550
         test( controllers, switches, hosts )"""
551
      self.start()
552
      print "*** Running test"
553
      result = self.runTest( test )
554
      self.stop()
555
      return result
556
   def interact( self ):
557
      "Create a network and run our simple CLI."
558
      self.run( self, Cli )
559
   
560
def defaultNames( snames=None, hnames=None, dpnames=None ):
561
   "Reinitialize default names from generators, if necessary."
562
   if snames is None: snames = nameGen( 's' )
563
   if hnames is None: hnames = nameGen( 'h' )
564
   if dpnames is None: dpnames = nameGen( 'nl:' )
565
   return snames, hnames, dpnames
566

    
567
# Tree network
568

    
569
class TreeNet( Network ):
570
   "A tree-structured network with the specified depth and fanout"
571
   def __init__( self, depth, fanout, **kwargs):
572
      self.depth, self.fanout = depth, fanout
573
      Network.__init__( self, **kwargs )
574
   def treeNet( self, controller, depth, fanout, snames=None,
575
      hnames=None, dpnames=None ):
576
      """Return a tree network of the given depth and fanout as a triple:
577
         ( root, switches, hosts ), using the given switch, host and
578
         datapath name generators, with the switches connected to the given
579
         controller. If kernel=True, use the kernel datapath; otherwise the
580
         user datapath will be used."""
581
      # Ugly, but necessary (?) since defaults are only evaluated once
582
      snames, hnames, dpnames = defaultNames( snames, hnames, dpnames )
583
      if ( depth == 0 ):
584
         host = Host( hnames.next() )
585
         print host.name, ; flush()
586
         return host, [], [ host ]
587
      dp = dpnames.next() if self.kernel else None
588
      switch = Switch( snames.next(), dp )
589
      if not self.kernel: createLink( switch, controller )
590
      print switch.name, ; flush()
591
      switches, hosts = [ switch ], []
592
      for i in range( 0, fanout ):
593
         child, slist, hlist = self.treeNet( controller, 
594
            depth - 1, fanout, snames, hnames, dpnames )
595
         createLink( switch, child )
596
         switches += slist
597
         hosts += hlist
598
      return switch, switches, hosts
599
   def makeNet( self, controller ):
600
      root, switches, hosts = self.treeNet( controller,
601
         self.depth, self.fanout )
602
      return switches, hosts
603
   
604
# Grid network
605

    
606
class GridNet( Network ):
607
   """An N x M grid/mesh network of switches, with hosts at the edges.
608
      This class also demonstrates creating a somewhat complicated
609
      topology."""
610
   def __init__( self, n, m, linear=False, **kwargs ):
611
      self.n, self.m, self.linear = n, m, linear and m == 1
612
      Network.__init__( self, **kwargs )
613
   def makeNet( self, controller ):
614
      snames, hnames, dpnames = defaultNames()
615
      n, m = self.n, self.m
616
      hosts = []
617
      switches = []
618
      kernel = self.kernel
619
      rows = []
620
      if not self.linear:
621
         print "*** gridNet: creating", n, "x", m, "grid of switches" ; flush()
622
      for y in range( 0, m ):
623
         row = []
624
         for x in range( 0, n ):
625
            dp = dpnames.next() if kernel else None
626
            switch = Switch( snames.next(), dp )
627
            if not kernel: createLink( switch, controller )
628
            row.append( switch )
629
            switches += [ switch ]
630
            print switch.name, ; flush()
631
         rows += [ row ]
632
      # Hook up rows
633
      for row in rows:
634
         previous = None
635
         for switch in row:
636
            if previous is not None:
637
               createLink( switch, previous )
638
            previous = switch
639
         h1, h2 = Host( hnames.next() ), Host( hnames.next() )
640
         createLink( h1, row[ 0 ] )
641
         createLink( h2, row[ -1 ] )
642
         hosts += [ h1, h2 ]
643
         print h1.name, h2.name, ; flush()
644
      # Return here if we're using this to make a linear network
645
      if self.linear: return switches, hosts
646
      # Hook up columns
647
      for x in range( 0, n ):
648
         previous = None
649
         for y in range( 0, m ):
650
            switch = rows[ y ][ x ]
651
            if previous is not None:
652
               createLink( switch, previous )
653
            previous = switch
654
         h1, h2 = Host( hnames.next() ), Host( hnames.next() )
655
         createLink( h1, rows[ 0 ][ x ] )
656
         createLink( h2, rows[ -1 ][ x ] )
657
         hosts += [ h1, h2 ]
658
         print h1.name, h2.name, ; flush()
659
      return switches, hosts
660

    
661
class LinearNet( GridNet ):
662
   "A network consisting of two hosts connected by a string of switches."
663
   def __init__( self, switchCount, **kwargs ):
664
      self.switchCount = switchCount
665
      GridNet.__init__( self, switchCount, 1, linear=True, **kwargs )
666
      
667
# Tests
668

    
669
def parsePing( pingOutput ):
670
   "Parse ping output and return packets sent, received."
671
   r = r'(\d+) packets transmitted, (\d+) received'
672
   m = re.search( r, pingOutput )
673
   if m == None:
674
      print "*** Error: could not parse ping output:", pingOutput
675
      exit( 1 )
676
   sent, received  = int( m.group( 1 ) ), int( m.group( 2 ) )
677
   return sent, received
678
   
679
def pingTest( controllers=[], switches=[], hosts=[], verbose=False ):
680
   "Test that each host can reach every other host."
681
   packets = 0 ; lost = 0
682
   for node in hosts:
683
      if verbose: 
684
         print node.name, "->", ; flush()
685
      for dest in hosts: 
686
         if node != dest:
687
            result = node.cmd( 'ping -c1 ' + dest.IP() )
688
            sent, received = parsePing( result )
689
            packets += sent
690
            if received > sent:
691
               print "*** Error: received too many packets"
692
               print result
693
               node.cmdPrint( 'route' )
694
               exit( 1 )
695
            lost += sent - received
696
            if verbose: 
697
               print ( dest.name if received else "X" ), ; flush()
698
      if verbose: print
699
   ploss = 100 * lost/packets
700
   if verbose:
701
      print "%d%% packet loss (%d/%d lost)" % ( ploss, lost, packets )
702
      flush()
703
   return ploss
704

    
705
def pingTestVerbose( controllers, switches, hosts ):
706
   return "%d %% packet loss" % \
707
      pingTest( controllers, switches, hosts, verbose=True )
708
 
709
def parseIperf( iperfOutput ):
710
   "Parse iperf output and return bandwidth."
711
   r = r'([\d\.]+ \w+/sec)'
712
   m = re.search( r, iperfOutput )
713
   return m.group( 1 ) if m is not None else "could not parse iperf output"
714
    
715
def iperf( hosts, verbose=False ):
716
   "Run iperf between two hosts."
717
   assert len( hosts ) == 2
718
   host1, host2 = hosts[ 0 ], hosts[ 1 ]
719
   host1.cmd( 'killall -9 iperf') # XXX shouldn't be global killall
720
   server = host1.cmd( 'iperf -s &' )
721
   if verbose: print server ; flush()
722
   client = host2.cmd( 'iperf -t 5 -c ' + host1.IP() )
723
   if verbose: print client ; flush()
724
   server = host1.cmd( 'kill -9 %iperf' )
725
   if verbose: print server; flush()
726
   return [ parseIperf( server ), parseIperf( client ) ]
727
   
728
def iperfTest( controllers, switches, hosts, verbose=False ):
729
   "Simple iperf test between two hosts."
730
   if verbose: print "*** Starting ping test"   
731
   h0, hN = hosts[ 0 ], hosts[ -1 ]
732
   print "*** iperfTest: Testing bandwidth between", 
733
   print h0.name, "and", hN.name
734
   result = iperf( [ h0, hN], verbose )
735
   print "*** result:", result
736
   return result
737

    
738
# Simple CLI
739

    
740
class Cli( object ):
741
   "Simple command-line interface to talk to nodes."
742
   cmds = [ '?', 'help', 'nodes', 'sh', 'pingtest', 'iperf', 'net', 'exit' ]
743
   def __init__( self, controllers, switches, hosts ):
744
      self.controllers = controllers
745
      self.switches = switches
746
      self.hosts = hosts
747
      self.nodemap = {}
748
      self.nodelist = controllers + switches + hosts
749
      for node in self.nodelist:
750
         self.nodemap[ node.name ] = node
751
      self.run()
752
   # Commands
753
   def help( self, args ):
754
      "Semi-useful help for CLI"
755
      print "Available commands are:", self.cmds
756
      print
757
      print "You may also send a command to a node using:"
758
      print "  <node> command {args}"
759
      print "For example:"
760
      print "  mininet> h0 ifconfig"
761
      print
762
      print "The interpreter automatically substitutes IP addresses"
763
      print "for node names, so commands like"
764
      print "  mininet> h0 ping -c1 h1"
765
      print "should work."
766
      print
767
      print "Interactive commands are not really supported yet,"
768
      print "so please limit commands to ones that do not"
769
      print "require user interaction and will terminate"
770
      print "after a reasonable amount of time."
771
   def nodes( self, args ):
772
      "List available nodes"
773
      print "available nodes are:", [ node.name for node in self.nodelist]
774
   def sh( self, args ):
775
      "Run an external shell command"
776
      call( [ 'sh', '-c' ] + args )
777
   def pingtest( self, args ):
778
      pingTest( self.controllers, self.switches, self.hosts, verbose=True )
779
   def net( self, args ):
780
      for switch in self.switches:
781
         print switch.name, "<->",
782
         for intf in switch.intfs:
783
            node, remoteIntf = switch.connection[ intf ]
784
            print node.name,
785
         print
786
   def iperf( self, args ):
787
      if len( args ) != 2:
788
         print "usage: iperf <h1> <h2>"
789
         return
790
      for host in args:
791
         if host not in self.nodemap:
792
            print "iperf: cannot find host:", host
793
            return
794
      iperf( [ self.nodemap[ h ] for h in args ], verbose=True )
795
   # Interpreter
796
   def run( self ):
797
      "Read and execute commands."
798
      print "*** cli: starting"
799
      while True:
800
         print "mininet> ", ; flush()
801
         input = sys.stdin.readline()
802
         if input == '': break
803
         if input[ -1 ] == '\n': input = input[ : -1 ]
804
         cmd = input.split( ' ' )
805
         first = cmd[ 0 ]
806
         rest = cmd[ 1: ]
807
         if first in self.cmds and hasattr( self, first ):
808
            getattr( self, first )( rest )
809
         elif first in self.nodemap and rest != []:
810
            node = self.nodemap[ first ]
811
            # Substitute IP addresses for node names in command
812
            rest = [ self.nodemap[ arg ].IP() if arg in self.nodemap else arg
813
               for arg in rest ]
814
            rest = ' '.join( rest )
815
            # Interactive commands don't work yet, and
816
            # there are still issues with control-c
817
            print "***", node.name, ": running", rest
818
            node.sendCmd( rest )
819
            while True:
820
               try:
821
                  done, data = node.monitor()
822
                  print data,
823
                  if done: break
824
               except KeyboardInterrupt: node.sendInt()
825
            print
826
         elif first == '': pass
827
         elif first in [ 'exit', 'quit' ]: break
828
         elif first == '?': self.help( rest )
829
         else: print "cli: unknown node or command: <", first, ">"
830
      print "*** cli: exiting"
831
   
832
def fixLimits():
833
   "Fix ridiculously small resource limits."
834
   setrlimit( RLIMIT_NPROC, ( 4096, 8192 ) )
835
   setrlimit( RLIMIT_NOFILE, ( 16384, 32768 ) )
836

    
837
def init():
838
   "Initialize Mininet."
839
   if os.getuid() != 0: 
840
      # Note: this script must be run as root 
841
      # Perhaps we should do so automatically!
842
      print "*** Mininet must run as root."; exit( 1 )
843
   # If which produces no output, then netns is not in the path.
844
   # May want to loosen this to handle netns in the current dir.
845
   if not quietRun( [ 'which', 'netns' ] ):
846
       raise Exception( "Could not find netns; see INSTALL" )
847
   fixLimits()
848

    
849
if __name__ == '__main__':
850
   init()
851
   results = {}
852
   print "*** Welcome to Mininet!"
853
   print "*** Look in examples/ for more examples\n"
854
   print "*** Testing Mininet with kernel and user datapath"
855
   for datapath in [ 'kernel', 'user' ]:
856
      k = datapath == 'kernel'
857
      network = TreeNet( depth=2, fanout=4, kernel=k)
858
      result = network.run( pingTestVerbose )
859
      results[ datapath ] = result
860
   print "*** Test results:", results