Statistics
| Branch: | Tag: | Revision:

mininet / mininet.py @ 05cce994

History | View | Annotate | Download (31 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
   quietRun( 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
      return False
355
   return True
356

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

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

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

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

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

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

    
563
# Tree network
564

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

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

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

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

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

    
735
# Simple CLI
736

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

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

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