From eddef947a467f7655c901e6230416d8685557e1d Mon Sep 17 00:00:00 2001
From: Bob Lantz <openflow@debian.localdomain>
Date: Wed, 9 Dec 2009 21:13:17 -0800
Subject: [PATCH] Organized routines into classes:

Node -> { Host, Switch, Controller }
Network -> { TreeNet, GridNet -> LinearNet }

Modified cleanup to clean up kernel datapaths.
---
 cleanup    |   4 +
 mininet.py | 748 ++++++++++++++++++++++++++++++-----------------------
 2 files changed, 434 insertions(+), 318 deletions(-)

diff --git a/cleanup b/cleanup
index ba031eda..2375e848 100755
--- a/cleanup
+++ b/cleanup
@@ -11,6 +11,10 @@ done
 echo "Removing excess controllers/ofprotocols/ofdatapaths/pings"
 killall -9 controller ofprotocol ofdatapath ping 2> /dev/null
 
+echo "Removing excess kernel datapath processes"
+ps ax | grep 'dp[0-9]' | awk '{print $1;}' | xargs kill
+
 echo "Removing vconn junk in /tmp"
 rm -f /tmp/vconn* /tmp/vlogs* /tmp/*.out /tmp/*.log
 
+
diff --git a/mininet.py b/mininet.py
index f5a25181..fe509153 100755
--- a/mininet.py
+++ b/mininet.py
@@ -1,10 +1,9 @@
 #!/usr/bin/python
 
 """
-
 Mininet: A simple networking testbed for OpenFlow!
 
-Mininet creates a simple test network for OpenFlow by using
+Mininet creates simple OpenFlow test networks by using
 process-based virtualization and network namespaces. 
 
 This file supports use of either the kernel or user space datapath
@@ -12,35 +11,34 @@
 supported using the kernel datapath, and 512 (or more) switches are
 supported via the user datapath.
 
-Simulated hosts are created as processes in
-separate network namespaces. This allows a complete OpenFlow
-network to be simulated on top of a single Linux kernel.
+Simulated hosts are created as processes in separate network
+namespaces. This allows a complete OpenFlow network to be simulated on
+top of a single Linux kernel.
 
 Each host has:
    A virtual console (pipes to a shell)
    A virtual interfaces (half of a veth pair)
-   A namespaced parent shell (and possibly some child processes)
+   A parent shell (and possibly some child processes) in a namespace
    
-Hosts have a network interface which is
-configured via ifconfig/ip link/etc. with data network IP
-addresses (e.g. 192.168.123.2 )
+Hosts have a network interface which is configured via ifconfig/ip
+link/etc. with data network IP addresses (e.g. 192.168.123.2 )
 
 In kernel datapath mode, the controller and switches are simply
 processes in the root namespace.
 
-Kernel OpenFlow datapaths are instantiated using dpctl, and are
-attached to the one side of a veth pair; the other side resides in
-the host namespace. In this mode, switch processes can simply
-connect to the controller via the loopback interface.
+Kernel OpenFlow datapaths are instantiated using dpctl(8), and are attached
+to the one side of a veth pair; the other side resides in the host
+namespace. In this mode, switch processes can simply connect to the
+controller via the loopback interface.
 
-In user datapath mode, the controller and switch are full-service
-nodes that live in their own network namespace and have management
+In user datapath mode, the controller and switches are full-service
+nodes that live in their own network namespaces and have management
 interfaces and IP addresses on a control network (e.g. 10.0.123.1,
 currently routed although it could be bridged.)
 
 In addition to a management interface, user mode switches also have
-several switch interfaces, halves of veth pairs whose other halves
-reside in the host nodes that the switches are connected to.
+several switch interfaces, halves of veth pairs whose other halves reside
+in the host nodes that the switches are connected to.
 
 Naming:
    Host nodes are named h1-hN
@@ -65,16 +63,13 @@
 
 History:
 11/19/09 Initial revision (user datapath only)
-12/8/08  Kernel datapath support complete
-
+12/08/09 Kernel datapath support complete
+12/09/09 Moved controller and switch routines into classes
 """
 
-# Note: this script must be run as root 
-# Perhaps we should do so automatically!
-
 from subprocess import call, check_call, Popen, PIPE, STDOUT
 from time import sleep
-import re, os, signal, sys, select
+import os, re, signal, sys, select
 flush = sys.stdout.flush
 from resource import setrlimit, RLIMIT_NPROC, RLIMIT_NOFILE
 
@@ -106,11 +101,6 @@ def quietRun( cmd ):
       if popen.returncode != None: break
    return output
    
-# Command paths
-netns = "/usr/local/bin/netns"
-bash = "/bin/bash"
-ifconfig = "/sbin/ifconfig"
-
 class Node( object ):
    """A virtual network node is simply a shell in a network namespace.
       We communicate with it using pipes."""
@@ -118,9 +108,9 @@ def __init__( self, name, inNamespace=True ):
       self.name = name
       closeFds = False # speed vs. memory use
       # xpg_echo is needed so we can echo our sentinel in sendCmd
-      cmd = [ bash, '-O', 'xpg_echo' ]
+      cmd = [ '/bin/bash', '-O', 'xpg_echo' ]
       self.inNamespace = inNamespace
-      if self.inNamespace: cmd = [ netns ] + cmd
+      if self.inNamespace: cmd = [ 'netns' ] + cmd
       self.shell = Popen( cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT,
          close_fds=closeFds )
       self.stdin = self.shell.stdin
@@ -135,8 +125,7 @@ def __init__( self, name, inNamespace=True ):
       self.ips = {}
       self.connection = {}
       self.waiting = False
-      # For sanity check, try bringing up loopback interface
-      # self.cmd( "ifconfig lo 127.0.0.1 up" )
+   # Subshell I/O, commands and control
    def read( self, max ): return os.read( self.stdout.fileno(), max )
    def write( self, data ): os.write( self.stdin.fileno(), data )
    def terminate( self ): os.kill( self.pid, signal.SIGKILL )
@@ -190,6 +179,7 @@ def cmdPrint( self, cmd ):
       result = self.cmd( cmd )
       print result,
       return result
+   # Interface management, configuration, and routing
    def intfName( self, n):
       "Construct a canonical interface name node-intf for interface N."
       return self.name + '-eth' + `n`
@@ -201,7 +191,7 @@ def newIntf( self ):
       return intfName
    def setIP( self, intf, ip, bits ):
       "Set an interface's IP address."
-      result = self.cmd( [ ifconfig, intf, ip + bits, 'up' ] )
+      result = self.cmd( [ 'ifconfig', intf, ip + bits, 'up' ] )
       self.ips[ intf ] = ip
       return result
    def setHostRoute( self, ip, intf ):
@@ -213,13 +203,17 @@ def setDefaultRoute( self, intf ):
       return self.cmd( 'route add default ' + intf )
    def IP( self ):
       "Return IP address of first interface"
-      return self.ips[ self.intfs[ 0 ] ]    
+      return self.ips[ self.intfs[ 0 ] ]
+   def intfIsUp( self, intf ):
+      "Check if one of our interfaces is up."
+      return 'UP' in self.cmd( 'ifconfig ' + self.intfs[ 0 ] )
+   # Other methods  
    def __str__( self ): 
       result = self.name
       result += ": IP=" + self.IP() + " intfs=" + self.intfs
       result += " waiting=", self.waiting
       return result
- 
+
 # Maintain mapping between i/o pipes and nodes
 # This could be useful for monitoring multiple nodes
 # using select.poll()
@@ -233,20 +227,93 @@ def nodeFromFile( f ):
    node = outToNode.get( f )
    return node or inToNode.get( f )
 
-def createNodes( name, count ):
-   "Create and return a list of nodes."
-   nodes = [ Node( name + `i` ) for i in range( 0, count ) ]
-   # print "*** CreateNodes: created:", nodes
-   return nodes
-
+class Host( Node ):
+   """A host is simply a Node."""
+   pass
+      
+class Controller( Node ):
+   """A Controller is a Node that is running (or has execed) an 
+      OpenFlow controller."""
+   def __init__( self, name, kernel=True ):
+      Node.__init__( self, name, inNamespace=( not kernel ) )
+   def start( self, cprog='controller', cargs='ptcp:' ):
+      "Start <cprog cargs> on controller, logging to /tmp/cN.log"
+      cout = '/tmp/' + self.name + '.log'
+      self.cmdPrint( cprog + ' ' + cargs + 
+         ' 1> ' + cout + ' 2> ' + cout + ' &' )
+   def stop( self, cprog='controller' ):
+      "Stop controller cprog on controller"
+      self.cmd( "kill %" + cprog )  
+         
+class Switch( Node ):
+   """A Switch is a Node that is running (or has execed)
+      an OpenFlow switch."""
+   def __init__( self, name, datapath=None ):
+      self.dp = datapath
+      self.execed = False
+      Node.__init__( self, name, inNamespace=( datapath == None ) )
+   def startUserDatapath( self, controller ):
+      """Start OpenFlow reference user datapath, 
+         logging to /tmp/sN-{ofd,ofp}.log"""
+      ofdlog = '/tmp/' + self.name + '-ofd.log'
+      ofplog = '/tmp/' + self.name + '-ofp.log'
+      self.cmd( 'ifconfig lo up' )
+      intfs = self.intfs[ 1 : ] # 0 is mgmt interface
+      self.cmdPrint( 'ofdatapath -i ' + ','.join( intfs ) +
+       ' ptcp: 1> ' + ofdlog + ' 2> '+ ofdlog + ' &' )
+      self.cmdPrint( 'ofprotocol tcp:' + controller.IP() +
+         ' tcp:localhost 1> ' + ofplog + ' 2>' + ofplog + ' &' )
+   def stopUserDatapath( self ):
+      "Stop OpenFlow reference user datapath."
+      self.cmd( "kill %ofdatapath" )
+      self.cmd( "kill %ofprotocol" )
+   def startKernelDatapath( self, controller):
+      "Start up switch using OpenFlow reference kernel datapath."
+      ofplog = '/tmp/' + self.name + '-ofp.log'
+      quietRun( 'ifconfig lo up' )
+      # Delete local datapath if it exists;
+      # then create a new one monitoring the given interfaces
+      quietRun( 'dpctl deldp ' + self.dp )
+      self.cmdPrint( 'dpctl adddp ' + self.dp )
+      self.cmdPrint( 'dpctl addif ' + self.dp + ' ' + ' '.join( self.intfs ) )
+      # Become protocol daemon
+      self.cmdPrint( 'exec ofprotocol' +
+         ' ' + self.dp + ' tcp:127.0.0.1 1> ' + ofplog + ' 2>' + ofplog + ' &' )
+      self.execed = True
+   def stopKernelDatapath( self ):
+      "Terminate a switch using OpenFlow reference kernel datapath."
+      quietRun( 'dpctl deldp ' + self.dp )
+      for intf in self.intfs: quietRun( 'ip link del ' + intf )
+      self.terminate()
+   def start( self, controller ): 
+      if self.dp is None: self.startUserDatapath( controller )
+      else: self.startKernelDatapath( controller )
+   def stop( self ):
+      if self.dp is None: self.stopUserDatapath()
+      else: self.stopKernelDatapath()
+   # Handle non-interaction if we've execed
+   def sendCmd( self, cmd ):
+      if not self.execed: return Node.sendCmd( self, cmd )
+      else: print "*** Error:", self.name, "has execed and cannot accept commands"
+   def monitor( self ):
+      if not self.execed: return Node.monitor( self )
+      else: return True, ''
+         
 # Interface management
 # 
-# We connect nodes by creating a pair of veth interfaces,
-# and then placing them in the pair of nodes that we want
-# to communicate. Interfaces are named nodeN-ethM
+# Interfaces are managed as strings which are simply the
+# interface names, of the form "nodeN-ethM".
+#
+# To connect nodes, we create a pair of veth interfaces, and then place them
+# in the pair of nodes that we want to communicate. We then update the node's
+# list of interfaces and connectivity map.
+#
+# For the kernel datapath, switch interfaces
+# live in the root namespace and thus do not have to be
+# explicitly moved.
 
 def makeIntfPair( intf1, intf2 ):
-   "Make a veth pair of intf1 and intf2"
+   "Make a veth pair of intf1 and intf2."
    # Delete any old interfaces with the same names
    quietRun( 'ip link del ' + intf1 )
    quietRun( 'ip link del ' + intf2 )
@@ -255,7 +322,7 @@ def makeIntfPair( intf1, intf2 ):
    return checkRun( cmd )
    
 def moveIntf( intf, node ):
-   "Move intf to node"
+   "Move intf to node."
    cmd = 'ip link set ' + intf + ' netns ' + `node.pid`
    checkRun( cmd )
    links = node.cmd( 'ip link show' )
@@ -277,50 +344,20 @@ def createLink( node1, node2 ):
    return intf1, intf2
 
 # Handy utilities
-
-def parsePing( pingOutput ):
-   "Parse ping output and return packets sent, received"
-   r = r'(\d+) packets transmitted, (\d+) received'
-   m = re.search( r, pingOutput )
-   if m == None:
-      print "*** Error: could not parse ping output:", pingOutput
-      exit( 1 )
-   sent, received  = int( m.group( 1 ) ), int( m.group( 2 ) )
-   return sent, received
-
-def pingTest( hosts, verbose=False ):
-   "Test that each host can reach every other host."
-   packets = 0 ; lost = 0
-   for node in hosts:
-      if verbose: 
-         print node.name, "->", ; flush()
-      for dest in hosts: 
-         if node != dest:
-            result = node.cmd( 'ping -c1 ' + dest.IP() )
-            sent, received = parsePing( result )
-            packets += sent
-            if received > sent:
-               print "*** Error: received too many packets"
-               print result
-               node.cmdPrint( 'route' )
-               exit( 1 )
-            lost += sent - received
-            if verbose: 
-               print ( dest.name if received else "X" ), ; flush()
-      if verbose: print
-   ploss = 100 * lost/packets
-   if verbose:
-      print "%d%% packet loss (%d/%d lost)" % ( ploss, lost, packets )
-      flush()
-   return ploss
-   
+ 
+def createNodes( name, count ):
+   "Create and return a list of nodes."
+   nodes = [ Node( name + `i` ) for i in range( 0, count ) ]
+   # print "*** CreateNodes: created:", nodes
+   return nodes
+     
 def dumpNodes( nodes ):
    "Dump ifconfig of each node."
    for node in nodes:
       print "*** Dumping node", node.name
       print node.cmd( 'ip link show' )
       print node.cmd( 'route' )
-
+   
 def ipGen( A, B, c, d ):
    "Generate next IP class B IP address, starting at A.B.c.d"
    while True:
@@ -334,45 +371,43 @@ def ipGen( A, B, c, d ):
 def nameGen( prefix ):
    "Generate names starting with prefix."
    i = 0
-   while True: 
-      yield prefix + `i`
-      i += 1
-
+   while True: yield prefix + `i`; i += 1
+      
 # Control network support
-# Instead of routing, we could bridge or use "in-band" control
-
-def checkUp( node ):
-   "Make sure node's first interface is up."
-   return 'UP' in node.cmd( 'ifconfig ' + node.intfs[ 0 ] )
+# For the user datapath, we create an explicit control network.
+# Note: Instead of routing, we could bridge or use "in-band" control
    
-def configRoutedControlNetwork( controller, switches ):
-   "Configure a routed control network on controller and switches."
-   cip = '10.0.0.1'
-   sips = ipGen( 10, 123, 0, 1)
+def configRoutedControlNetwork( controller, switches, 
+   startAddr=( 10, 123, 0, 1 ) ):
+   """Configure a routed control network on controller and switches,
+      for use with the user datapath."""
+   ips = apply( ipGen, startAddr )
+   cip = ips.next()
    print controller.name, '<->',
    for switch in switches:
       print switch.name, ; flush()
-      sip = sips.next()
+      sip = ips.next()
       sintf = switch.intfs[ 0 ]
       node, cintf = switch.connection[ sintf ]
-      assert node == controller
-      controller.setIP( cintf, cip,  '/24')
+      if node != controller:
+         print "*** Error: switch", switch.name, 
+         print "not connected to correct controller"
+         exit( 1 )
+      controller.setIP( cintf, cip,  '/24' )
       switch.setIP( sintf, sip, '/24' )
       controller.setHostRoute( sip, cintf )
       switch.setHostRoute( cip, sintf )
    print
    print "*** Testing control network"
-   while not checkUp( controller ):
+   while not controller.intfIsUp( controller.intfs[ 0 ] ):
       print "*** Waiting for ", controller.intfs[ 0 ], "to come up"
       sleep( 1 )
    for switch in switches:
-      while not checkUp( switch ):
-         print "*** Waiting for ", controller.intfs[ 0 ], "to come up"
+      while not switch.intfIsUp( switch.intfs[ 0 ] ):
+         print "*** Waiting for ", switch.intfs[ 0 ], "to come up"
          sleep( 1 )
-      if pingTest( [ switch, controller ] ) != 0:
-         print "*** Error: control network test failed"
-      else:
-         return
+   if pingTest( [ switch, controller ] ) != 0:
+      print "*** Error: control network test failed"
       exit( 1 )
 
 def configHosts( hosts, ( a, b, c, d ) ):
@@ -386,236 +421,313 @@ def configHosts( hosts, ( a, b, c, d ) ):
       quietRun( 'renice +18 -p ' + `host.pid` )
       print host.name, ; flush()
    print
+ 
+# Test driver and topologies
+
+class Network( object ):
+   "Network topology (and test driver) base class."
+   def __init__( self, kernel=True, startAddr=( 192, 168, 123, 1) ):
+      self.kernel, self.startAddr = kernel, startAddr
+   # In progress: we probably want to decouple creating/starting/stopping
+   # the network and running tests, since we might wish to run
+   # multiple tests on the same network. It's not clear if the network
+   # should always be started/stopped for each test or not. Probably
+   # not...
+   def run( self, test ):
+      """Create a network by calling makeNet as follows: 
+         (switches, hosts ) = makeNet()
+         and then run test( controller, switches, hosts ) on it."""
+      kernel = self.kernel
+      if kernel: print "*** Using kernel datapath"
+      else: print "*** Using user datapath"
+      print "*** Creating controller"
+      controller = Controller( 'c0', kernel )
+      print "*** Creating network"
+      switches, hosts = self.makeNet()
+      print
+      if not kernel:
+         print "*** Configuring control network"
+         configRoutedControlNetwork( controller, switches )
+      print "*** Configuring hosts"
+      configHosts( hosts, self.startAddr )
+      print "*** Starting reference controller"
+      controller.start()
+      print "*** Starting switches"
+      for switch in switches:
+         switch.start( controller )
+      print "*** Running test"
+      test( [ controller ], switches, hosts )
+      print "*** Stopping controller"
+      controller.stop()
+      print "*** Stopping switches"
+      for switch in switches:
+         switch.stop()
+      print "*** Test complete"
+   def interact( self ):
+      "Create a network and run our simple CLI."
+      self.run( self, Cli )
+   
+def defaultNames( snames=None, hnames=None, dpnames=None ):
+   "Reinitialize default names from generators, if necessary."
+   if snames is None: snames = nameGen( 's' )
+   if hnames is None: hnames = nameGen( 'h' )
+   if dpnames is None: dpnames = nameGen( 'nl:' )
+   return snames, hnames, dpnames
+
+# Tree network
+
+class TreeNet( Network ):
+   "A tree-structured network of the given depth and fanout"
+   def __init__( self, depth, fanout, kernel=True):
+      self.depth, self.fanout = depth, fanout
+      Network.__init__( self, kernel )
+   def treeNet( self, depth, fanout, kernel=True, snames=None,
+      hnames=None, dpnames=None ):
+      """Return a tree network of the given depth and fanout as a triple:
+         ( root, switches, hosts ), using the given switch, host and
+         datapath name generators, with the switches connected to the given
+         controller. If kernel=True, use the kernel datapath; otherwise the
+         user datapath will be used."""
+      # Ugly, but necessary (?) since defaults are only evaluated once
+      snames, hnames, dpnames = defaultNames( snames, hnames, dpnames )
+      if ( depth == 0 ):
+         host = Host( hnames.next() )
+         print host.name, ; flush()
+         return host, [], [ host ]
+      dp = dpnames.next() if kernel else None
+      switch = Switch( snames.next(), dp )
+      if not kernel: createLink( switch, controller )
+      print switch.name, ; flush()
+      switches, hosts = [ switch ], []
+      for i in range( 0, fanout ):
+         child, slist, hlist = self.treeNet( 
+            depth - 1, fanout, kernel, snames, hnames, dpnames )
+         createLink( switch, child )
+         switches += slist
+         hosts += hlist
+      return switch, switches, hosts
+   def makeNet( self ):
+      root, switches, hosts = self.treeNet( 
+         self.depth, self.fanout, self.kernel)
+      return switches, hosts
+   
+# Grid network
+
+class GridNet( Network ):
+   "An n x m grid/mesh network of switches, with hosts at the edges."
+   def __init__( self, n, m, kernel=True, linear=False ):
+      self.n, self.m, self.linear = n, m, linear and m == 1
+      print "m=",m
+      Network.__init__( self, kernel )
+   def makeNet( self ):
+      snames, hnames, dpnames = defaultNames()
+      n, m = self.n, self.m
+      hosts = []
+      switches = []
+      kernel = self.kernel
+      rows = []
+      if not self.linear:
+         print "*** gridNet: creating", n, "x", m, "grid of switches" ; flush()
+      for y in range( 0, m ):
+         row = []
+         for x in range( 0, n ):
+            dp = dpnames.next() if kernel else None
+            switch = Switch( snames.next(), dp )
+            if not kernel: createLink( switch, controller )
+            row.append( switch )
+            switches += [ switch ]
+            print switch.name, ; flush()
+         rows += [ row ]
+      # Hook up rows
+      for row in rows:
+         previous = None
+         for switch in row:
+            if previous is not None:
+               createLink( switch, previous )
+            previous = switch
+         h1, h2 = Host( hnames.next() ), Host( hnames.next() )
+         createLink( h1, row[ 0 ] )
+         createLink( h2, row[ -1 ] )
+         hosts += [ h1, h2 ]
+         print h1.name, h2.name, ; flush()
+      # Return here if we're using this to make a linear network
+      if self.linear: 
+         print "returning linear network"
+         return switches, hosts
+      # Hook up columns
+      for x in range( 0, n ):
+         previous = None
+         for y in range( 0, m ):
+            switch = rows[ y ][ x ]
+            if previous is not None:
+               createLink( switch, previous )
+            previous = switch
+         h1, h2 = Host( hnames.next() ), Host( hnames.next() )
+         createLink( h1, rows[ 0 ][ x ] )
+         createLink( h2, rows[ -1 ][ x ] )
+         hosts += [ h1, h2 ]
+         print h1.name, h2.name, ; flush()
+      return switches, hosts
+
+class LinearNet( GridNet ):
+   def __init__( self, switchCount, kernel=True ):
+      self.switchCount = switchCount
+      GridNet.__init__( self, switchCount, 1, kernel, linear=True )
+      
+# Tests
 
-def startController( controller, cprog='controller', cargs='ptcp:' ):
-   "Start <cprog cargs> on controller, logging to /tmp/cN.log"
-   cout = '/tmp/' + controller.name + '.log'
-   controller.cmdPrint( cprog + ' ' + cargs + 
-      ' 1> ' + cout + ' 2> ' + cout + ' &' )
+def parsePing( pingOutput ):
+   "Parse ping output and return packets sent, received."
+   r = r'(\d+) packets transmitted, (\d+) received'
+   m = re.search( r, pingOutput )
+   if m == None:
+      print "*** Error: could not parse ping output:", pingOutput
+      exit( 1 )
+   sent, received  = int( m.group( 1 ) ), int( m.group( 2 ) )
+   return sent, received
 
-def stopController( controller, cprog='controller' ):
-   "Stop controller cprog on controller"
-   controller.cmd( "kill %" + cprog )
-   
-def startOpenFlowU( switch, controller ):
-   """Start OpenFlow reference user datapath on a switch, 
-      logging to /tmp/sN-{ofd,ofp}.log"""
-   ofdlog = '/tmp/' + switch.name + '-ofd.log'
-   ofplog = '/tmp/' + switch.name + '-ofp.log'
-   switch.cmd( 'ifconfig lo up' )
-   intfs = switch.intfs[ 1 : ] # 0 is mgmt interface
-   switch.cmdPrint( 'ofdatapath -i ' + ','.join( intfs ) +
-    ' ptcp: 1> ' + ofdlog + ' 2> '+ ofdlog + ' &' )
-   switch.cmdPrint( 'ofprotocol tcp:' + controller.IP() +
-      ' tcp:localhost 1> ' + ofplog + ' 2>' + ofplog + '&' )
-
-def stopOpenFlowU( switch ):
-   "Stop OpenFlow reference user datapath on a switch."
-   switch.cmd( "kill %ofdatapath" )
-   switch.cmd( "kill %ofprotocol" )   
-
-def dpgen():
-   "Generator for OpenFlow kernel datapath names."
-   dpCount = 0
-   while True:
-      yield 'nl:' + `dpCount`
-      dpCount += 1
-
-def startOpenFlowK( switch, dp, controller):
-   "Start up a switch connected to an OpenFlow reference kernel datapath."
-   ofplog = '/tmp/' + switch.name + '-ofp.log'
-   switch.cmd( 'ifconfig lo up' )
-   # Delete local datapath if it exists;
-   # then create a new one monitoring the given interfaces
-   quietRun( 'dpctl deldp ' + dp )
-   switch.cmdPrint( 'dpctl adddp ' + dp )
-   switch.cmdPrint( 'dpctl addif ' + dp + ' ' + ' '.join( switch.intfs ) )
-   switch.dp = dp
-   # Become protocol daemon
-   switch.cmdPrint( 'exec ofprotocol' +
-      ' ' + dp + ' tcp:127.0.0.1 1> ' + ofplog + ' 2>' + ofplog + '&' )
-
-def stopOpenFlowK( switch ):
-   "Terminate a switch using OpenFlow reference kernel datapath."
-   quietRun( 'dpctl deldp ' + switch.dp )
-   for intf in switch.intfs: quietRun( 'ip link del ' + intf )
-   switch.terminate()
-
-def stopOpenFlow( switch ):
-   if hasattr(switch, 'dp' ): stopOpenFlowK( switch )
-   else: stopOpenFlowU( switch )
-
-# Test scenarios and topologies
-
-def treeNet( controller, depth, fanout, snames=nameGen( 's' ), 
-   hnames=nameGen( 'h' ), kernel=True ):
-   """Return a tree network of the given depth and fanout as a triple:
-      ( root, switches, hosts ), using the given switch and host
-      name generators, with the switches connected to the given
-      controller."""
-   if ( depth == 0 ):
-      host = Node( hnames.next() )
-      print host.name, ; flush()
-      return host, [], [ host ]
-   switch = Node( snames.next(), inNamespace=(not kernel) )
-   if not kernel: createLink( switch, controller )
-   print switch.name, ; flush()
-   switches, hosts = [ switch ], []
-   for i in range( 0, fanout ):
-      child, slist, hlist = treeNet( 
-         controller, depth - 1, fanout, snames, hnames, kernel )
-      createLink( switch, child )
-      switches += slist
-      hosts += hlist
-   return switch, switches, hosts
-
-def treeNetTest( depth, fanout, test, kernel=True ):
-   """Create a tree network of the given depth and fanout, and
-      run test( controller, root, switches, hosts ) on it."""
-   if kernel: print "*** Using kernel datapath"
-   else: print "*** Using user datapath"
-   print "*** Creating controller"
-   controller = Node( 'c0', inNamespace=( not kernel ) )
-   print "*** Creating tree network depth:", depth, "fanout:", fanout
-   root, switches, hosts = treeNet( controller, depth, fanout, kernel=kernel )
-   print
-   if not kernel:
-      print "*** Configuring control network"
-      configRoutedControlNetwork( controller, switches )
-   else: dp = dpgen()
-   print "*** Configuring hosts"
-   configHosts( hosts, ( 192, 168, 123, 1 ) )
-   print "*** Starting reference controller"
-   startController( controller )
-   print "*** Starting switches"
-   for switch in switches:
-      if kernel: startOpenFlowK( switch, dp.next(), controller )
-      else: startOpenFlowU( switch, controller )
-   print "*** Running test"
-   test( controller, root, switches, hosts )
-   print "*** Stopping controller"
-   stopController( controller )
-   print "*** Stopping switches"
-   for switch in switches:
-      stopOpenFlow( switch )
+def pingTest( controllers, switches, hosts, verbose=False ):
+   "Test that each host can reach every other host."
+   packets = 0 ; lost = 0
+   for node in hosts:
+      if verbose: 
+         print node.name, "->", ; flush()
+      for dest in hosts: 
+         if node != dest:
+            result = node.cmd( 'ping -c1 ' + dest.IP() )
+            sent, received = parsePing( result )
+            packets += sent
+            if received > sent:
+               print "*** Error: received too many packets"
+               print result
+               node.cmdPrint( 'route' )
+               exit( 1 )
+            lost += sent - received
+            if verbose: 
+               print ( dest.name if received else "X" ), ; flush()
+      if verbose: print
+   ploss = 100 * lost/packets
+   if verbose:
+      print "%d%% packet loss (%d/%d lost)" % ( ploss, lost, packets )
+      flush()
+   return ploss
 
-def treePingTest( depth, fanout, kernel=True ):
-   "Run a ping test on a tree network with the given depth and fanout."
-   test = lambda c, r, s, hosts : pingTest( hosts, verbose=True )
-   treeNetTest( depth, fanout, test, kernel)
+def pingTestVerbose( controllers, switches, hosts ):
+   return pingTest( controllers, switches, hosts, verbose=True )
    
 def iperf( hosts ):
    "Run iperf between two hosts."
    assert len( hosts ) == 2
    host1, host2 = hosts[ 0 ], hosts[ 1 ]
-   dumpNodes( [ host1, host2 ] )
-   host1.cmdPrint( 'killall -9 iperf')
+   # dumpNodes( [ host1, host2 ] )
+   host1.cmdPrint( 'killall -9 iperf') # XXX shouldn't be global killall
    host1.cmdPrint( 'iperf -s &' )
    host2.cmdPrint( 'iperf -t 5 -c ' + host1.IP() )
    host1.cmdPrint( 'kill -9 %iperf' )
  
-def iperfTest( depth=1, fanout=2, kernel=True ):
+def iperfTest( controllers, switches, hosts ):
    "Simple iperf test between two hosts."
-   def test( c, r, s, hosts ):
-      h0, hN = hosts[ 0 ], hosts[ -1 ]
-      print "*** iperfTest: Testing bandwidth between", 
-      print h0.name, "and", hN.name
-      return iperf( [ h0, hN] )
-   treeNetTest( depth, fanout, test, kernel )
+   h0, hN = hosts[ 0 ], hosts[ -1 ]
+   print "*** iperfTest: Testing bandwidth between", 
+   print h0.name, "and", hN.name
+   return iperf( [ h0, hN] )
+   
+# Simple CLI
+
+class Cli( object ):
+   "Simple command-line interface to talk to nodes."
+   cmds = [ '?', 'help', 'nodes', 'sh', 'pingtest', 'iperf', 'net', 'exit' ]
+   def __init__( self, controllers, switches, hosts ):
+      self.controllers = controllers
+      self.switches = switches
+      self.hosts = hosts
+      self.nodemap = {}
+      self.nodelist = controllers + switches + hosts
+      for node in self.nodelist:
+         self.nodemap[ node.name ] = node
+      self.run()
+   # Commands
+   def help( self, args ):
+      "Semi-useful help for CLI"
+      print "available commands are:", self.cmds
+   def nodes( self, args ):
+      "List available nodes"
+      print "available nodes are:", [ node.name for node in self.nodelist]
+   def sh( self, args ):
+      "Run an external shell command"
+      call( [ 'sh', '-c' ] + args )
+   def pingtest( self, args ):
+      pingTest( self.controllers, self.switches, self.hosts, verbose=True )
+   def net( self, args ):
+      for switch in self.switches:
+         print switch.name, "<->",
+         for intf in switch.intfs:
+            node, rintf = switch.connection[ intf ]
+            print node.name,
+         print
+   def iperf( self, args ):
+      print "iperf: got args", args
+      if len( args ) != 2:
+         print "usage: iperf <h1> <h2>"
+         return
+      for host in args:
+         if host not in self.nodemap:
+            print "iperf: cannot find host:", host
+            return
+      iperf( [ self.nodemap[ h ] for h in args ] )
+   # Interpreter
+   def run( self ):
+      "Read and execute commands."
+      print "*** cli: starting"
+      while True:
+         print "mininet> ", ; flush()
+         input = sys.stdin.readline()
+         if input == '': break
+         if input[ -1 ] == '\n': input = input[ : -1 ]
+         cmd = input.split( ' ' )
+         first = cmd[ 0 ]
+         rest = cmd[ 1: ]
+         if first in self.cmds and hasattr( self, first ):
+            getattr( self, first )( rest )
+         elif first in self.nodemap and rest != []:
+            node = self.nodemap[ first ]
+            # Substitute IP addresses for node names in command
+            rest = [ self.nodemap[ arg ].IP() if arg in self.nodemap else arg
+               for arg in rest ]
+            rest = ' '.join( rest )
+            # Interactive commands don't work yet, and
+            # there are still issues with control-c
+            print "***", node.name, ": running", rest
+            node.sendCmd( rest )
+            while True:
+               try:
+                  done, data = node.monitor()
+                  print data,
+                  if done: break
+               except KeyboardInterrupt: node.sendInt()
+            print
+         elif first == '': pass
+         elif first in [ 'exit', 'quit' ]: break
+         elif first == '?': self.help( rest )
+         else: print "cli: unknown node or command: <", first, ">"
+      print "*** cli: exiting"
    
 def fixLimits():
    "Fix ridiculously small resource limits."
    setrlimit( RLIMIT_NPROC, ( 4096, 8192 ) )
    setrlimit( RLIMIT_NOFILE, ( 16384, 32768 ) )
 
-# Simple CLI
-
-def cliHelp( nodemap, c, s, h, args  ):
-   "Semi-useful help for CLI"
-   print "available commands are:", cliCmds.keys()
-
-def cliNodes( nodemap, c, s, h, args ):
-   "List available nodes"
-   print "available nodes are:", nodemap.keys()
-      
-def cliSh( nodemap, c, s, h, args ):
-   "Run an external shell command"
-   call( [ bash, '-c', args ] )
-
-def cliPingTest( map, c, s, hosts, args ):
-   pingTest( hosts, verbose=True )
-
-def cliNet( map, c, switches, h, args ):
-   for switch in switches:
-      print switch.name, "<->",
-      for intf in switch.intfs:
-         node, rintf = switch.connection[ intf ]
-         print node.name,
-      print
-
-def cliIperf( map, c, switches, h, args ):
-   print "iperf: got args", args
-   if len( args ) != 2:
-      print "usage: iperf <h1> <h2>"
-      return
-   for host in args:
-      if host not in map:
-         print "iperf: cannot find host:", host
-         return
-   iperf( [ map[ h ] for h in args ] )
-
-cliCmds = { '?': cliHelp, 'help': cliHelp, 'net': cliNet, 'nodes': cliNodes, 
-         'pingtest': cliPingTest, 'iperf': cliIperf, 'sh': cliSh, 
-         'exit': None }
-
-def cli( controllers, switches, hosts ):
-   "Simple command-line interface to talk to nodes."
-   print "*** cli: starting"
-   nodemap = {}
-   nodes = controllers + switches + hosts
-   for node in nodes:
-      nodemap[ node.name ] = node
-   while True:
-      print "mininet> ", ; flush()
-      input = sys.stdin.readline()
-      if input == '': break
-      if input[ -1 ] == '\n': input = input[ : -1 ]
-      cmd = input.split( ' ' )
-      first = cmd[ 0 ]
-      rest = cmd[ 1: ]
-      if first in cliCmds: cliCmds[ first ]( 
-         nodemap, controllers, switches, hosts, rest )
-      elif first in nodemap and rest != []:
-         node = nodemap[ first ]
-         # Substitute IP addresses for node names in command
-         rest = [ nodemap[ arg ].IP() if arg in nodemap else arg
-            for arg in rest ]
-         rest = ' '.join( rest )
-         # Interactive commands don't work yet, and
-         # there are still issues with control-c
-         print "***", node.name, ": running", rest
-         node.sendCmd( rest )
-         while True:
-            try:
-               done, data = node.monitor()
-               print data,
-               if done: break
-            except KeyboardInterrupt: node.sendInt()
-         print
-      elif first == '': pass
-      elif first in [ 'exit', 'quit' ]: break
-      else: print "cli: unknown node or command: <", first, ">"
-   print "*** cli: exiting"
-      
-def treeInteract( depth, fanout, kernel=True ):
-   "Create a tree network and start the CLI."
-   interact = lambda c, r, s, h : cli( [ c ], s, h )
-   treeNetTest( depth, fanout, interact, kernel )
+def init():
+   # Note: this script must be run as root 
+   # Perhaps we should do so automatically!
+   if os.getuid() != 0: 
+      print "*** Mininet must run as root."; exit( 1 )
+   fixLimits()
    
 if __name__ == '__main__':
-   fixLimits()
+   init()
    # for kernel in [ False, True ]:
-   #   treePingTest( depth=3, fanout=4, kernel=kernel )
-   # treeInteract( depth=1, fanout=2, kernel=False )
-   #  iperfTest( depth=1, fanout=2, kernel=kernel )
-   treeInteract( depth=1, fanout=2, kernel=False )
\ No newline at end of file
+   #   TreeNet( depth=3, fanout=4, kernel=kernel).run( pingTest )
+   TreeNet( depth=2, fanout=32).run( Cli )
+   # LinearNet( switchCount=100 ).run( iperfTest)
+   # GridNet( 2, 2 ).run( Cli )
\ No newline at end of file
-- 
GitLab