Statistics
| Branch: | Tag: | Revision:

mininet / mininet.py @ fb7658c8

History | View | Annotate | Download (29.5 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
   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
11/19/09 Mininet demo at OpenFlow SWAI meeting
65
12/08/09 Kernel datapath support complete
66
12/09/09 Moved controller and switch routines into classes
67
12/12/09 Added subdivided network driver workflow
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
# Utility routines to make it easier to run commands
77

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

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

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

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

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

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

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

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

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

    
522
# Tree network
523

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

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

    
616
class LinearNet( GridNet ):
617
   def __init__( self, switchCount, kernel=True ):
618
      self.switchCount = switchCount
619
      GridNet.__init__( self, switchCount, 1, kernel, linear=True )
620
      
621
# Tests
622

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

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

    
693
# Simple CLI
694

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

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

    
801
if __name__ == '__main__':
802
   init()
803
   results = {}
804
   print "*** Welcome to Mininet!"
805
   print "*** Look in examples/ for more examples\n"
806
   print "*** Testing Mininet with kernel and user datapath"
807
   for datapath in [ 'kernel', 'user' ]:
808
      k = datapath == 'kernel'
809
      network = TreeNet( depth=2, fanout=4, kernel=k)
810
      result = network.run( pingTestVerbose )
811
      results[ datapath ] = result
812
   print "*** Test results:", results