Statistics
| Branch: | Tag: | Revision:

mininet / mininet.py @ 55dd9368

History | View | Annotate | Download (29.3 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 waitReadable( self ): self.pollOut.poll()
140
   def sendCmd( self, cmd ):
141
      """Send a command, followed by a command to echo a sentinel,
142
         and return without waiting for the command to complete."""
143
      assert not self.waiting
144
      if cmd[ -1 ] == '&':
145
         separator = '&'
146
         cmd = cmd[ : -1 ]
147
      else: separator = ';'
148
      if isinstance( cmd, list): cmd = ' '.join( cmd )
149
      self.write( cmd + separator + " echo -n '\\0177' \n")
150
      self.waiting = True
151
   def monitor( self ):
152
      "Monitor a command's output, returning (done, data)."
153
      assert self.waiting
154
      self.waitReadable()
155
      data = self.read( 1024 )
156
      if len( data ) > 0 and data[ -1 ] == chr( 0177 ):
157
         self.waiting = False
158
         return True, data[ : -1 ]
159
      else:
160
         return False, data
161
   def sendInt( self ):
162
      "Send ^C, hopefully interrupting a running subprocess."
163
      self.write( chr( 3 ) )
164
   def waitOutput( self ):
165
      """Wait for a command to complete (signaled by a sentinel
166
      character, ASCII(127) appearing in the output stream) and return
167
      the output, including trailing newline."""
168
      assert self.waiting
169
      output = ""
170
      while True:
171
         self.waitReadable()
172
         data = self.read( 1024 )
173
         if len(data) > 0  and data[ -1 ] == chr( 0177 ): 
174
            output += data[ : -1 ]
175
            break
176
         else: output += data
177
      self.waiting = False
178
      return output
179
   def cmd( self, cmd ):
180
      "Send a command, wait for output, and return it."
181
      self.sendCmd( cmd )
182
      return self.waitOutput()
183
   def cmdPrint( self, cmd ):
184
      "Call cmd, printing the command and output"
185
      print "***", self.name, ":", cmd
186
      result = self.cmd( cmd )
187
      print result,
188
      return result
189
   # Interface management, configuration, and routing
190
   def intfName( self, n):
191
      "Construct a canonical interface name node-intf for interface N."
192
      return self.name + '-eth' + `n`
193
   def newIntf( self ):
194
      "Reserve and return a new interface name for this node."
195
      intfName = self.intfName( self.intfCount)
196
      self.intfCount += 1
197
      self.intfs += [ intfName ]
198
      return intfName
199
   def setIP( self, intf, ip, bits ):
200
      "Set an interface's IP address."
201
      result = self.cmd( [ 'ifconfig', intf, ip + bits, 'up' ] )
202
      self.ips[ intf ] = ip
203
      return result
204
   def setHostRoute( self, ip, intf ):
205
      "Add a route to the given IP address via intf."
206
      return self.cmd( 'route add -host ' + ip + ' dev ' + intf )
207
   def setDefaultRoute( self, intf ):
208
      "Set the default route to go through intf."
209
      self.cmd( 'ip route flush' )
210
      return self.cmd( 'route add default ' + intf )
211
   def IP( self ):
212
      "Return IP address of first interface"
213
      return self.ips[ self.intfs[ 0 ] ]
214
   def intfIsUp( self, intf ):
215
      "Check if one of our interfaces is up."
216
      return 'UP' in self.cmd( 'ifconfig ' + self.intfs[ 0 ] )
217
   # Other methods  
218
   def __str__( self ): 
219
      result = self.name
220
      result += ": IP=" + self.IP() + " intfs=" + self.intfs
221
      result += " waiting=", self.waiting
222
      return result
223

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

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

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

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

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

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

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

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

    
519
# Tree network
520

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

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

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

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

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

    
690
# Simple CLI
691

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

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

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