Statistics
| Branch: | Tag: | Revision:

mininet / mininet.py @ 696a619d

History | View | Annotate | Download (30.8 KB)

1
#!/usr/bin/python
2

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

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

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

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

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

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

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

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

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

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

46
Thoughts/TBD:
47

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

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

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

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

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

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

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

    
419
def configureRoutedControlNetwork( controller, switches, ips):
420
   """Configure a routed control network on controller and switches,
421
      for use with the user datapath."""
422
   cip = ips.next()
423
   print controller.name, '<->',
424
   for switch in switches:
425
      print switch.name, ; flush()
426
      sip = ips.next()
427
      sintf = switch.intfs[ 0 ]
428
      node, cintf = switch.connection[ sintf ]
429
      if node != controller:
430
         print "*** Error: switch", switch.name, 
431
         print "not connected to correct controller"
432
         exit( 1 )
433
      controller.setIP( cintf, cip,  '/24' )
434
      switch.setIP( sintf, sip, '/24' )
435
      controller.setHostRoute( sip, cintf )
436
      switch.setHostRoute( cip, sintf )
437
   print
438
   print "*** Testing control network"
439
   while not controller.intfIsUp( controller.intfs[ 0 ] ):
440
      print "*** Waiting for ", controller.intfs[ 0 ], "to come up"
441
      sleep( 1 )
442
   for switch in switches:
443
      while not switch.intfIsUp( switch.intfs[ 0 ] ):
444
         print "*** Waiting for ", switch.intfs[ 0 ], "to come up"
445
         sleep( 1 )
446
   if pingTest( hosts=[ switch, controller ] ) != 0:
447
      print "*** Error: control network test failed"
448
      exit( 1 )
449

    
450
def configHosts( hosts, ips ):
451
   "Configure a set of hosts, starting at IP address a.b.c.d"
452
   for host in hosts:
453
      hintf = host.intfs[ 0 ]
454
      host.setIP( hintf, ips.next(), '/24' )
455
      host.setDefaultRoute( hintf )
456
      # You're low priority, dude!
457
      quietRun( 'renice +18 -p ' + `host.pid` )
458
      print host.name, ; flush()
459
   print
460
 
461
# Test driver and topologies
462

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

    
554
# Tree network
555

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

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

    
648
class LinearNet( GridNet ):
649
   "A network consisting of two hosts connected by a string of switches."
650
   def __init__( self, switchCount, **kwargs ):
651
      self.switchCount = switchCount
652
      GridNet.__init__( self, switchCount, 1, linear=True, **kwargs )
653
      
654
# Tests
655

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

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

    
726
# Simple CLI
727

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

    
825
def init():
826
   "Initialize Mininet."
827
   if os.getuid() != 0: 
828
      # Note: this script must be run as root 
829
      # Perhaps we should do so automatically!
830
      print "*** Mininet must run as root."; exit( 1 )
831
   # If which produces no output, then netns is not in the path.
832
   # May want to loosen this to handle netns in the current dir.
833
   if not quietRun( [ 'which', 'netns' ] ):
834
       raise Exception( "Could not find netns; see INSTALL" )
835
   fixLimits()
836

    
837
if __name__ == '__main__':
838
   init()
839
   results = {}
840
   print "*** Welcome to Mininet!"
841
   print "*** Look in examples/ for more examples\n"
842
   print "*** Testing Mininet with kernel and user datapath"
843
   for datapath in [ 'kernel', 'user' ]:
844
      k = datapath == 'kernel'
845
      network = TreeNet( depth=2, fanout=4, kernel=k)
846
      result = network.run( pingTestVerbose )
847
      results[ datapath ] = result
848
   print "*** Test results:", results