Statistics
| Branch: | Tag: | Revision:

mininet / mininet.py @ 08cef003

History | View | Annotate | Download (29.4 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 or 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 attached
28
to the one side of a veth pair; the other side resides in the host
29
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 reside
39
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
   cli, tests, etc.) into multiple modules.
54
   We don't support nox nicely just yet - you have to hack this file
55
   or subclass things aggressively.
56
   We'd like to support OpenVSwitch as well as the reference
57
   implementation.
58
   
59
Bob Lantz
60
rlantz@cs.stanford.edu
61

62
History:
63
11/19/09 Initial revision (user datapath only)
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
"""
68

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

    
75
# Utility routines to make it easier to run commands
76

    
77
def run( cmd ):
78
   "Simple interface to subprocess.call()"
79
   return call( cmd.split( ' ' ) )
80

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

    
223
# Maintain mapping between i/o pipes and nodes
224
# This could be useful for monitoring multiple nodes
225
# using select.poll()
226

    
227
inToNode = {}
228
outToNode = {}
229
def outputs(): return outToNode.keys()
230
def nodes(): return outToNode.values()
231
def inputs(): return [ node.stdin for node in nodes() ]
232
def nodeFromFile( f ):
233
   node = outToNode.get( f )
234
   return node or inToNode.get( f )
235

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

    
326
def makeIntfPair( intf1, intf2 ):
327
   "Make a veth pair of intf1 and intf2."
328
   # Delete any old interfaces with the same names
329
   quietRun( 'ip link del ' + intf1 )
330
   quietRun( 'ip link del ' + intf2 )
331
   # Create new pair
332
   cmd = 'ip link add name ' + intf1 + ' type veth peer name ' + intf2
333
   return checkRun( cmd )
334
   
335
def moveIntf( intf, node ):
336
   "Move intf to node."
337
   cmd = 'ip link set ' + intf + ' netns ' + `node.pid`
338
   checkRun( cmd )
339
   links = node.cmd( 'ip link show' )
340
   if not intf in links:
341
      print "*** Error: moveIntf:", intf, "not successfully moved to",
342
      print node.name,":"
343
      exit( 1 )
344
   return
345
   
346
def createLink( node1, node2 ):
347
   "Create a link node1-intf1 <---> node2-intf2."
348
   intf1 = node1.newIntf()
349
   intf2 = node2.newIntf()
350
   makeIntfPair( intf1, intf2 )
351
   if node1.inNamespace: moveIntf( intf1, node1 )
352
   if node2.inNamespace: moveIntf( intf2, node2 )
353
   node1.connection[ intf1 ] = ( node2, intf2 )
354
   node2.connection[ intf2 ] = ( node1, intf1 )
355
   return intf1, intf2
356

    
357
# Handy utilities
358
 
359
def createNodes( name, count ):
360
   "Create and return a list of nodes."
361
   nodes = [ Node( name + `i` ) for i in range( 0, count ) ]
362
   # print "*** CreateNodes: created:", nodes
363
   return nodes
364
     
365
def dumpNodes( nodes ):
366
   "Dump ifconfig of each node."
367
   for node in nodes:
368
      print "*** Dumping node", node.name
369
      print node.cmd( 'ip link show' )
370
      print node.cmd( 'route' )
371
   
372
def ipGen( A, B, c, d ):
373
   "Generate next IP class B IP address, starting at A.B.c.d"
374
   while True:
375
      yield '%d.%d.%d.%d' % ( A, B, c, d )
376
      d += 1
377
      if d > 254:
378
         d = 1
379
         c += 1
380
         if c > 254: break
381

    
382
def nameGen( prefix ):
383
   "Generate names starting with prefix."
384
   i = 0
385
   while True: yield prefix + `i`; i += 1
386
      
387
# Control network support
388
# For the user datapath, we create an explicit control network.
389
# Note: Instead of routing, we could bridge or use "in-band" control
390
   
391
def configRoutedControlNetwork( controller, switches, 
392
   startAddr=( 10, 123, 0, 1 ) ):
393
   """Configure a routed control network on controller and switches,
394
      for use with the user datapath."""
395
   ips = apply( ipGen, startAddr )
396
   cip = ips.next()
397
   print controller.name, '<->',
398
   for switch in switches:
399
      print switch.name, ; flush()
400
      sip = ips.next()
401
      sintf = switch.intfs[ 0 ]
402
      node, cintf = switch.connection[ sintf ]
403
      if node != controller:
404
         print "*** Error: switch", switch.name, 
405
         print "not connected to correct controller"
406
         exit( 1 )
407
      controller.setIP( cintf, cip,  '/24' )
408
      switch.setIP( sintf, sip, '/24' )
409
      controller.setHostRoute( sip, cintf )
410
      switch.setHostRoute( cip, sintf )
411
   print
412
   print "*** Testing control network"
413
   while not controller.intfIsUp( controller.intfs[ 0 ] ):
414
      print "*** Waiting for ", controller.intfs[ 0 ], "to come up"
415
      sleep( 1 )
416
   for switch in switches:
417
      while not switch.intfIsUp( switch.intfs[ 0 ] ):
418
         print "*** Waiting for ", switch.intfs[ 0 ], "to come up"
419
         sleep( 1 )
420
   if pingTest( hosts=[ switch, controller ] ) != 0:
421
      print "*** Error: control network test failed"
422
      exit( 1 )
423

    
424
def configHosts( hosts, ( a, b, c, d ) ):
425
   "Configure a set of hosts, starting at IP address a.b.c.d"
426
   ips = ipGen( a, b, c, d )
427
   for host in hosts:
428
      hintf = host.intfs[ 0 ]
429
      host.setIP( hintf, ips.next(), '/24' )
430
      host.setDefaultRoute( hintf )
431
      # You're low priority, dude!
432
      quietRun( 'renice +18 -p ' + `host.pid` )
433
      print host.name, ; flush()
434
   print
435
 
436
# Test driver and topologies
437

    
438
class Network( object ):
439
   "Network topology (and test driver) base class."
440
   def __init__( self, kernel=True, startAddr=( 192, 168, 123, 1) ):
441
      self.kernel, self.startAddr = kernel, startAddr
442
      # Check for kernel modules
443
      modules = quietRun( 'lsmod' )
444
      if not kernel and 'tun' not in modules:
445
         print "*** Error: kernel module tun not loaded:",
446
         print " user datapath not supported"
447
         exit( 1 )
448
      if kernel and 'ofdatapath' not in modules:
449
         print "*** Error: kernel module ofdatapath not loaded:",
450
         print " kernel datapath not supported"
451
         exit( 1 )
452
      # Create network, but don't start things up yet!
453
      self.prepareNet()
454
   def prepareNet( self ):
455
      """Create a network by calling makeNet as follows: 
456
         (switches, hosts ) = makeNet()
457
         Create a controller here as well."""
458
      kernel = self.kernel
459
      if kernel: print "*** Using kernel datapath"
460
      else: print "*** Using user datapath"
461
      print "*** Creating controller"
462
      controller = Controller( 'c0', kernel )
463
      print "*** Creating network"
464
      switches, hosts = self.makeNet( controller )
465
      print
466
      if not kernel:
467
         print "*** Configuring control network"
468
         configRoutedControlNetwork( controller, switches )
469
      print "*** Configuring hosts"
470
      configHosts( hosts, self.startAddr )
471
      self.controllers = [ controller ]
472
      self.switches = switches
473
      self.hosts = hosts
474
   def start( self ):
475
      "Start controller and switches"
476
      print "*** Starting reference controller"
477
      for controller in self.controllers:
478
         controller.start()
479
      print "*** Starting", len( self.switches ), "switches"
480
      for switch in self.switches:
481
         switch.start( self.controllers[ 0 ] )
482
   def stop( self ):
483
      "Stop the controller(s), switches and hosts"
484
      print "*** Stopping controller"
485
      for controller in self.controllers:
486
         controller.stop(); controller.terminate()
487
      print "*** Stopping switches"
488
      for switch in self.switches:
489
         print switch.name, ; flush()
490
         switch.stop() ; switch.terminate()
491
      print
492
      print "*** Stopping hosts"
493
      for host in self.hosts: 
494
         host.terminate()
495
      print "*** Test complete"
496
   def runTest( self, test ):
497
      "Run a given test, called as test( controllers, switches, hosts)"
498
      return test( self.controllers, self.switches, self.hosts )
499
   def run( self, test ):
500
      """Perform a complete start/test/stop cycle; test is of the form
501
         test( controllers, switches, hosts )"""
502
      self.start()
503
      print "*** Running test"
504
      result = self.runTest( test )
505
      self.stop()
506
      return result
507
   def interact( self ):
508
      "Create a network and run our simple CLI."
509
      self.run( self, Cli )
510
   
511
def defaultNames( snames=None, hnames=None, dpnames=None ):
512
   "Reinitialize default names from generators, if necessary."
513
   if snames is None: snames = nameGen( 's' )
514
   if hnames is None: hnames = nameGen( 'h' )
515
   if dpnames is None: dpnames = nameGen( 'nl:' )
516
   return snames, hnames, dpnames
517

    
518
# Tree network
519

    
520
class TreeNet( Network ):
521
   "A tree-structured network with the specified depth and fanout"
522
   def __init__( self, depth, fanout, kernel=True):
523
      self.depth, self.fanout = depth, fanout
524
      Network.__init__( self, kernel )
525
   def treeNet( self, controller, depth, fanout, kernel=True, snames=None,
526
      hnames=None, dpnames=None ):
527
      """Return a tree network of the given depth and fanout as a triple:
528
         ( root, switches, hosts ), using the given switch, host and
529
         datapath name generators, with the switches connected to the given
530
         controller. If kernel=True, use the kernel datapath; otherwise the
531
         user datapath will be used."""
532
      # Ugly, but necessary (?) since defaults are only evaluated once
533
      snames, hnames, dpnames = defaultNames( snames, hnames, dpnames )
534
      if ( depth == 0 ):
535
         host = Host( hnames.next() )
536
         print host.name, ; flush()
537
         return host, [], [ host ]
538
      dp = dpnames.next() if kernel else None
539
      switch = Switch( snames.next(), dp )
540
      if not kernel: createLink( switch, controller )
541
      print switch.name, ; flush()
542
      switches, hosts = [ switch ], []
543
      for i in range( 0, fanout ):
544
         child, slist, hlist = self.treeNet( controller, 
545
            depth - 1, fanout, kernel, snames, hnames, dpnames )
546
         createLink( switch, child )
547
         switches += slist
548
         hosts += hlist
549
      return switch, switches, hosts
550
   def makeNet( self, controller ):
551
      root, switches, hosts = self.treeNet( controller,
552
         self.depth, self.fanout, self.kernel)
553
      return switches, hosts
554
   
555
# Grid network
556

    
557
class GridNet( Network ):
558
   """An N x M grid/mesh network of switches, with hosts at the edges.
559
      This class also demonstrates creating a somewhat complicated
560
      topology."""
561
   def __init__( self, n, m, kernel=True, linear=False ):
562
      self.n, self.m, self.linear = n, m, linear and m == 1
563
      Network.__init__( self, kernel )
564
   def makeNet( self, controller ):
565
      snames, hnames, dpnames = defaultNames()
566
      n, m = self.n, self.m
567
      hosts = []
568
      switches = []
569
      kernel = self.kernel
570
      rows = []
571
      if not self.linear:
572
         print "*** gridNet: creating", n, "x", m, "grid of switches" ; flush()
573
      for y in range( 0, m ):
574
         row = []
575
         for x in range( 0, n ):
576
            dp = dpnames.next() if kernel else None
577
            switch = Switch( snames.next(), dp )
578
            if not kernel: createLink( switch, controller )
579
            row.append( switch )
580
            switches += [ switch ]
581
            print switch.name, ; flush()
582
         rows += [ row ]
583
      # Hook up rows
584
      for row in rows:
585
         previous = None
586
         for switch in row:
587
            if previous is not None:
588
               createLink( switch, previous )
589
            previous = switch
590
         h1, h2 = Host( hnames.next() ), Host( hnames.next() )
591
         createLink( h1, row[ 0 ] )
592
         createLink( h2, row[ -1 ] )
593
         hosts += [ h1, h2 ]
594
         print h1.name, h2.name, ; flush()
595
      # Return here if we're using this to make a linear network
596
      if self.linear: return switches, hosts
597
      # Hook up columns
598
      for x in range( 0, n ):
599
         previous = None
600
         for y in range( 0, m ):
601
            switch = rows[ y ][ x ]
602
            if previous is not None:
603
               createLink( switch, previous )
604
            previous = switch
605
         h1, h2 = Host( hnames.next() ), Host( hnames.next() )
606
         createLink( h1, rows[ 0 ][ x ] )
607
         createLink( h2, rows[ -1 ][ x ] )
608
         hosts += [ h1, h2 ]
609
         print h1.name, h2.name, ; flush()
610
      return switches, hosts
611

    
612
class LinearNet( GridNet ):
613
   def __init__( self, switchCount, kernel=True ):
614
      self.switchCount = switchCount
615
      GridNet.__init__( self, switchCount, 1, kernel, linear=True )
616
      
617
# Tests
618

    
619
def parsePing( pingOutput ):
620
   "Parse ping output and return packets sent, received."
621
   r = r'(\d+) packets transmitted, (\d+) received'
622
   m = re.search( r, pingOutput )
623
   if m == None:
624
      print "*** Error: could not parse ping output:", pingOutput
625
      exit( 1 )
626
   sent, received  = int( m.group( 1 ) ), int( m.group( 2 ) )
627
   return sent, received
628
   
629
def pingTest( controllers=[], switches=[], hosts=[], verbose=False ):
630
   "Test that each host can reach every other host."
631
   packets = 0 ; lost = 0
632
   for node in hosts:
633
      if verbose: 
634
         print node.name, "->", ; flush()
635
      for dest in hosts: 
636
         if node != dest:
637
            result = node.cmd( 'ping -c1 ' + dest.IP() )
638
            sent, received = parsePing( result )
639
            packets += sent
640
            if received > sent:
641
               print "*** Error: received too many packets"
642
               print result
643
               node.cmdPrint( 'route' )
644
               exit( 1 )
645
            lost += sent - received
646
            if verbose: 
647
               print ( dest.name if received else "X" ), ; flush()
648
      if verbose: print
649
   ploss = 100 * lost/packets
650
   if verbose:
651
      print "%d%% packet loss (%d/%d lost)" % ( ploss, lost, packets )
652
      flush()
653
   return ploss
654

    
655
def pingTestVerbose( controllers, switches, hosts ):
656
   return "%d %% packet loss" % \
657
      pingTest( controllers, switches, hosts, verbose=True )
658
 
659
def parseIperf( iperfOutput ):
660
   "Parse iperf output and return bandwidth."
661
   r = r'([\d\.]+ \w+/sec)'
662
   m = re.search( r, iperfOutput )
663
   return m.group( 1 ) if m is not None else "could not parse iperf output"
664
    
665
def iperf( hosts, verbose=False ):
666
   "Run iperf between two hosts."
667
   assert len( hosts ) == 2
668
   host1, host2 = hosts[ 0 ], hosts[ 1 ]
669
   # dumpNodes( [ host1, host2 ] )
670
   host1.cmd( 'killall -9 iperf') # XXX shouldn't be global killall
671
   server = host1.cmd( 'iperf -s &' )
672
   if verbose: print server ; flush()
673
   client = host2.cmd( 'iperf -t 5 -c ' + host1.IP() )
674
   if verbose: print client ; flush()
675
   server = host1.cmd( 'kill -9 %iperf' )
676
   if verbose: print server; flush()
677
   return [ parseIperf( server ), parseIperf( client ) ]
678
   
679
def iperfTest( controllers, switches, hosts, verbose=False ):
680
   "Simple iperf test between two hosts."
681
   if verbose: print "*** Starting ping test"   
682
   h0, hN = hosts[ 0 ], hosts[ -1 ]
683
   print "*** iperfTest: Testing bandwidth between", 
684
   print h0.name, "and", hN.name
685
   result = iperf( [ h0, hN], verbose )
686
   print "*** result:", result
687
   return result
688

    
689
# Simple CLI
690

    
691
class Cli( object ):
692
   "Simple command-line interface to talk to nodes."
693
   cmds = [ '?', 'help', 'nodes', 'sh', 'pingtest', 'iperf', 'net', 'exit' ]
694
   def __init__( self, controllers, switches, hosts ):
695
      self.controllers = controllers
696
      self.switches = switches
697
      self.hosts = hosts
698
      self.nodemap = {}
699
      self.nodelist = controllers + switches + hosts
700
      for node in self.nodelist:
701
         self.nodemap[ node.name ] = node
702
      self.run()
703
   # Commands
704
   def help( self, args ):
705
      "Semi-useful help for CLI"
706
      print "Available commands are:", self.cmds
707
      print
708
      print "You may also send a command to a node using:"
709
      print "  <node> command {args}"
710
      print "For example:"
711
      print "  mininet> h0 ifconfig"
712
      print
713
      print "The interpreter automatically substitutes IP addresses"
714
      print "for node names, so commands like"
715
      print "  mininet> h0 ping -c1 h1"
716
      print "should work."
717
      print
718
      print "Interactive commands are not really supported yet,"
719
      print "so please limit commands to ones that do not"
720
      print "require user interaction and will terminate"
721
      print "after a reasonable amount of time."
722
   def nodes( self, args ):
723
      "List available nodes"
724
      print "available nodes are:", [ node.name for node in self.nodelist]
725
   def sh( self, args ):
726
      "Run an external shell command"
727
      call( [ 'sh', '-c' ] + args )
728
   def pingtest( self, args ):
729
      pingTest( self.controllers, self.switches, self.hosts, verbose=True )
730
   def net( self, args ):
731
      for switch in self.switches:
732
         print switch.name, "<->",
733
         for intf in switch.intfs:
734
            node, remoteIntf = switch.connection[ intf ]
735
            print node.name,
736
         print
737
   def iperf( self, args ):
738
      print "iperf: got args", args
739
      if len( args ) != 2:
740
         print "usage: iperf <h1> <h2>"
741
         return
742
      for host in args:
743
         if host not in self.nodemap:
744
            print "iperf: cannot find host:", host
745
            return
746
      iperf( [ self.nodemap[ h ] for h in args ] )
747
   # Interpreter
748
   def run( self ):
749
      "Read and execute commands."
750
      print "*** cli: starting"
751
      while True:
752
         print "mininet> ", ; flush()
753
         input = sys.stdin.readline()
754
         if input == '': break
755
         if input[ -1 ] == '\n': input = input[ : -1 ]
756
         cmd = input.split( ' ' )
757
         first = cmd[ 0 ]
758
         rest = cmd[ 1: ]
759
         if first in self.cmds and hasattr( self, first ):
760
            getattr( self, first )( rest )
761
         elif first in self.nodemap and rest != []:
762
            node = self.nodemap[ first ]
763
            # Substitute IP addresses for node names in command
764
            rest = [ self.nodemap[ arg ].IP() if arg in self.nodemap else arg
765
               for arg in rest ]
766
            rest = ' '.join( rest )
767
            # Interactive commands don't work yet, and
768
            # there are still issues with control-c
769
            print "***", node.name, ": running", rest
770
            node.sendCmd( rest )
771
            while True:
772
               try:
773
                  done, data = node.monitor()
774
                  print data,
775
                  if done: break
776
               except KeyboardInterrupt: node.sendInt()
777
            print
778
         elif first == '': pass
779
         elif first in [ 'exit', 'quit' ]: break
780
         elif first == '?': self.help( rest )
781
         else: print "cli: unknown node or command: <", first, ">"
782
      print "*** cli: exiting"
783
   
784
def fixLimits():
785
   "Fix ridiculously small resource limits."
786
   setrlimit( RLIMIT_NPROC, ( 4096, 8192 ) )
787
   setrlimit( RLIMIT_NOFILE, ( 16384, 32768 ) )
788

    
789
def init():
790
   "Initialize Mininet."
791
   # Note: this script must be run as root 
792
   # Perhaps we should do so automatically!
793
   if os.getuid() != 0: 
794
      print "*** Mininet must run as root."; exit( 1 )
795
   fixLimits()
796

    
797
if __name__ == '__main__':
798
   init()
799
   results = {}
800
   exit( 1 )
801
   print "*** Testing Mininet with kernel and user datapath"
802
   for datapath in [ 'kernel', 'user' ]:
803
      k = datapath == 'kernel'
804
      # results += [ TreeNet( depth=2, fanout=2, kernel=k ).
805
      #   run( pingTestVerbose ) ]
806
      results[ datapath ] = []
807
      for switchCount in range( 1, 4 ):
808
         results[ datapath ]  += [ ( switchCount,
809
            LinearNet( switchCount, k).run( iperfTest ) ) ]
810
      # GridNet( 2, 2 ).run( Cli )
811
   print "*** Test results:", results