First commit
diff --git a/mininet/net.py b/mininet/net.py
new file mode 100644
index 0000000..1255e12
--- /dev/null
+++ b/mininet/net.py
@@ -0,0 +1,770 @@
+"""
+
+ Mininet: A simple networking testbed for OpenFlow/SDN!
+
+author: Bob Lantz (rlantz@cs.stanford.edu)
+author: Brandon Heller (brandonh@stanford.edu)
+
+Mininet creates scalable OpenFlow test networks by using
+process-based virtualization and network namespaces.
+
+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 parent shell (and possibly some child processes) in a namespace
+
+Hosts have a network interface which is configured via ifconfig/ip
+link/etc.
+
+This version supports both the kernel and user space datapaths
+from the OpenFlow reference implementation (openflowswitch.org)
+as well as OpenVSwitch (openvswitch.org.)
+
+In kernel datapath mode, the controller and switches are simply
+processes in the root namespace.
+
+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 switches can be full-service
+nodes that live in their own network namespaces and have management
+interfaces and IP addresses on a control network (e.g. 192.168.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.
+
+Consistent, straightforward naming is important in order to easily
+identify hosts, switches and controllers, both from the CLI and
+from program code. Interfaces are named to make it easy to identify
+which interfaces belong to which node.
+
+The basic naming scheme is as follows:
+
+ Host nodes are named h1-hN
+ Switch nodes are named s1-sN
+ Controller nodes are named c0-cN
+ Interfaces are named {nodename}-eth0 .. {nodename}-ethN
+
+Note: If the network topology is created using mininet.topo, then
+node numbers are unique among hosts and switches (e.g. we have
+h1..hN and SN..SN+M) and also correspond to their default IP addresses
+of 10.x.y.z/8 where x.y.z is the base-256 representation of N for
+hN. This mapping allows easy determination of a node's IP
+address from its name, e.g. h1 -> 10.0.0.1, h257 -> 10.0.1.1.
+
+Note also that 10.0.0.1 can often be written as 10.1 for short, e.g.
+"ping 10.1" is equivalent to "ping 10.0.0.1".
+
+Currently we wrap the entire network in a 'mininet' object, which
+constructs a simulated network based on a network topology created
+using a topology object (e.g. LinearTopo) from mininet.topo or
+mininet.topolib, and a Controller which the switches will connect
+to. Several configuration options are provided for functions such as
+automatically setting MAC addresses, populating the ARP table, or
+even running a set of terminals to allow direct interaction with nodes.
+
+After the network is created, it can be started using start(), and a
+variety of useful tasks maybe performed, including basic connectivity
+and bandwidth tests and running the mininet CLI.
+
+Once the network is up and running, test code can easily get access
+to host and switch objects which can then be used for arbitrary
+experiments, typically involving running a series of commands on the
+hosts.
+
+After all desired tests or activities have been completed, the stop()
+method may be called to shut down the network.
+
+"""
+
+import os
+import re
+import select
+import signal
+from time import sleep
+
+from mininet.cli import CLI
+from mininet.log import info, error, debug, output
+from mininet.node import Host, OVSKernelSwitch, Controller
+from mininet.link import Link, Intf
+from mininet.util import quietRun, fixLimits, numCores, ensureRoot
+from mininet.util import macColonHex, ipStr, ipParse, netParse, ipAdd, nextCCNnet
+from mininet.term import cleanUpScreens, makeTerms
+
+# Mininet version: should be consistent with README and LICENSE
+VERSION = "2.0.0"
+
+class Mininet( object ):
+ "Network emulation with hosts spawned in network namespaces."
+
+ def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host,
+ controller=Controller, link=Link, intf=Intf,
+ build=True, xterms=False, cleanup=False, ipBase='10.0.0.0/8',
+ inNamespace=False,
+ autoSetMacs=False, autoStaticArp=False, autoPinCpus=False,
+ listenPort=None ):
+ """Create Mininet object.
+ topo: Topo (topology) object or None
+ switch: default Switch class
+ host: default Host class/constructor
+ controller: default Controller class/constructor
+ link: default Link class/constructor
+ intf: default Intf class/constructor
+ ipBase: base IP address for hosts,
+ build: build now from topo?
+ xterms: if build now, spawn xterms?
+ cleanup: if build now, cleanup before creating?
+ inNamespace: spawn switches and controller in net namespaces?
+ autoSetMacs: set MAC addrs automatically like IP addresses?
+ autoStaticArp: set all-pairs static MAC addrs?
+ autoPinCpus: pin hosts to (real) cores (requires CPULimitedHost)?
+ listenPort: base listening port to open; will be incremented for
+ each additional switch in the net if inNamespace=False"""
+ self.topo = topo
+ self.switch = switch
+ self.host = host
+ self.controller = controller
+ self.link = link
+ self.intf = intf
+ self.ipBase = ipBase
+ self.ipBaseNum, self.prefixLen = netParse( self.ipBase )
+ self.nextIP = 1 # start for address allocation
+ self.ccnNetBase = '1.0.0.0'
+ self.inNamespace = inNamespace
+ self.xterms = xterms
+ self.cleanup = cleanup
+ self.autoSetMacs = autoSetMacs
+ self.autoStaticArp = autoStaticArp
+ self.autoPinCpus = autoPinCpus
+ self.numCores = numCores()
+ self.nextCore = 0 # next core for pinning hosts to CPUs
+ self.listenPort = listenPort
+
+ self.hosts = []
+ self.switches = []
+ self.controllers = []
+
+ self.nameToNode = {} # name to Node (Host/Switch) objects
+
+ self.terms = [] # list of spawned xterm processes
+
+ Mininet.init() # Initialize Mininet if necessary
+
+ self.built = False
+ if topo and build:
+ self.build()
+
+ def isCCNhost(self, node):
+ if 'fib' in node.params:
+ return True
+ else:
+ return False
+
+ def addHost( self, name, cls=None, **params ):
+ """Add host.
+ name: name of host to add
+ cls: custom host class/constructor (optional)
+ params: parameters for host
+ returns: added host"""
+ # Default IP and MAC addresses
+
+ defaults = { 'ip': ipAdd( self.nextIP,
+ ipBaseNum=self.ipBaseNum,
+ prefixLen=self.prefixLen ) +
+ '/%s' % self.prefixLen }
+ if self.autoSetMacs:
+ defaults[ 'mac'] = macColonHex( self.nextIP )
+ if self.autoPinCpus:
+ defaults[ 'cores' ] = self.nextCore
+ self.nextCore = ( self.nextCore + 1 ) % self.numCores
+ self.nextIP += 1
+ defaults.update( params )
+ if not cls:
+ cls = self.host
+ h = cls( name, **defaults )
+ self.hosts.append( h )
+ self.nameToNode[ name ] = h
+ return h
+
+ def addSwitch( self, name, cls=None, **params ):
+ """Add switch.
+ name: name of switch to add
+ cls: custom switch class/constructor (optional)
+ returns: added switch
+ side effect: increments listenPort ivar ."""
+ defaults = { 'listenPort': self.listenPort,
+ 'inNamespace': self.inNamespace }
+ defaults.update( params )
+ if not cls:
+ cls = self.switch
+ sw = cls( name, **defaults )
+ if not self.inNamespace and self.listenPort:
+ self.listenPort += 1
+ self.switches.append( sw )
+ self.nameToNode[ name ] = sw
+ return sw
+
+ def addController( self, name='c0', controller=None, **params ):
+ """Add controller.
+ controller: Controller class"""
+ if not controller:
+ controller = self.controller
+ controller_new = controller( name, **params )
+ if controller_new: # allow controller-less setups
+ self.controllers.append( controller_new )
+ self.nameToNode[ name ] = controller_new
+ return controller_new
+
+ # BL: is this better than just using nameToNode[] ?
+ # Should it have a better name?
+ def getNodeByName( self, *args ):
+ "Return node(s) with given name(s)"
+ if len( args ) == 1:
+ return self.nameToNode[ args[ 0 ] ]
+ return [ self.nameToNode[ n ] for n in args ]
+
+ def get( self, *args ):
+ "Convenience alias for getNodeByName"
+ return self.getNodeByName( *args )
+
+ def addLink( self, node1, node2, port1=None, port2=None,
+ cls=None, **params ):
+ """"Add a link from node1 to node2
+ node1: source node
+ node2: dest node
+ port1: source port
+ port2: dest port
+ returns: link object"""
+ defaults = { 'port1': port1,
+ 'port2': port2,
+ 'intf': self.intf }
+ defaults.update( params )
+ if not cls:
+ cls = self.link
+ return cls( node1, node2, **defaults )
+
+ def configHosts( self ):
+ "Configure a set of hosts."
+ for host in self.hosts:
+ info( host.name + ' ' )
+ intf = host.defaultIntf()
+ if self.isCCNhost(host):
+ host.configCCN()
+ host.configDefault(ip=None,mac=None)
+ elif intf:
+ host.configDefault( defaultRoute=intf )
+ else:
+ # Don't configure nonexistent intf
+ host.configDefault( ip=None, mac=None )
+ # You're low priority, dude!
+ # BL: do we want to do this here or not?
+ # May not make sense if we have CPU lmiting...
+ # quietRun( 'renice +18 -p ' + repr( host.pid ) )
+ # This may not be the right place to do this, but
+ # it needs to be done somewhere.
+ host.cmd( 'ifconfig lo up' )
+ info( '\n' )
+
+ def buildFromTopo( self, topo=None ):
+ """Build mininet from a topology object
+ At the end of this function, everything should be connected
+ and up."""
+
+ # Possibly we should clean up here and/or validate
+ # the topo
+ if self.cleanup:
+ pass
+
+ info( '*** Creating network\n' )
+
+ if not self.controllers:
+ # Add a default controller
+ info( '*** Adding controller\n' )
+ classes = self.controller
+ if type( classes ) is not list:
+ classes = [ classes ]
+ for i, cls in enumerate( classes ):
+ self.addController( 'c%d' % i, cls )
+
+ info( '*** Adding hosts:\n' )
+ for hostName in topo.hosts():
+ self.addHost( hostName, **topo.nodeInfo( hostName ) )
+ info( hostName + ' ' )
+
+ info( '\n*** Adding switches:\n' )
+ for switchName in topo.switches():
+ self.addSwitch( switchName, **topo.nodeInfo( switchName) )
+ info( switchName + ' ' )
+
+ info( '\n*** Adding links:\n' )
+ for srcName, dstName in topo.links(sort=True):
+ src, dst = self.nameToNode[ srcName ], self.nameToNode[ dstName ]
+ params = topo.linkInfo( srcName, dstName )
+ srcPort, dstPort = topo.port( srcName, dstName )
+ self.addLink( src, dst, srcPort, dstPort, **params )
+ if self.isCCNhost(src):
+ src.setIP(ipStr(ipParse(self.ccnNetBase) + 1) + '/30', intf=src.name + '-eth' + str(srcPort))
+ dst.setIP(ipStr(ipParse(self.ccnNetBase) + 2) + '/30', intf=dst.name + '-eth' + str(dstPort))
+ self.ccnNetBase=nextCCNnet(self.ccnNetBase)
+
+ info( '(%s, %s) ' % ( src.name, dst.name ) )
+
+ info( '\n' )
+
+
+ def configureControlNetwork( self ):
+ "Control net config hook: override in subclass"
+ raise Exception( 'configureControlNetwork: '
+ 'should be overriden in subclass', self )
+
+ def build( self ):
+ "Build mininet."
+ if self.topo:
+ self.buildFromTopo( self.topo )
+ if ( self.inNamespace ):
+ self.configureControlNetwork()
+ info( '*** Configuring hosts\n' )
+ self.configHosts()
+ if self.xterms:
+ self.startTerms()
+ if self.autoStaticArp:
+ self.staticArp()
+ self.built = True
+
+ def startTerms( self ):
+ "Start a terminal for each node."
+ info( "*** Running terms on %s\n" % os.environ[ 'DISPLAY' ] )
+ cleanUpScreens()
+ self.terms += makeTerms( self.controllers, 'controller' )
+ self.terms += makeTerms( self.switches, 'switch' )
+ self.terms += makeTerms( self.hosts, 'host' )
+
+ def stopXterms( self ):
+ "Kill each xterm."
+ for term in self.terms:
+ os.kill( term.pid, signal.SIGKILL )
+ cleanUpScreens()
+
+ def staticArp( self ):
+ "Add all-pairs ARP entries to remove the need to handle broadcast."
+ for src in self.hosts:
+ for dst in self.hosts:
+ if src != dst:
+ src.setARP( ip=dst.IP(), mac=dst.MAC() )
+
+ def start( self ):
+ "Start controller and switches."
+ if not self.built:
+ self.build()
+ info( '*** Starting controller\n' )
+ for controller in self.controllers:
+ controller.start()
+ info( '*** Starting %s switches\n' % len( self.switches ) )
+ for switch in self.switches:
+ info( switch.name + ' ')
+ switch.start( self.controllers )
+ info( '\n' )
+
+ def stop( self ):
+ "Stop the controller(s), switches and hosts"
+ if self.terms:
+ info( '*** Stopping %i terms\n' % len( self.terms ) )
+ self.stopXterms()
+ info( '*** Stopping %i hosts\n' % len( self.hosts ) )
+ for host in self.hosts:
+ info( host.name + ' ' )
+ host.terminate()
+ info( '\n' )
+ info( '*** Stopping %i switches\n' % len( self.switches ) )
+ for switch in self.switches:
+ info( switch.name + ' ' )
+ switch.stop()
+ info( '\n' )
+ info( '*** Stopping %i controllers\n' % len( self.controllers ) )
+ for controller in self.controllers:
+ info( controller.name + ' ' )
+ controller.stop()
+ info( '\n*** Done\n' )
+
+ def run( self, test, *args, **kwargs ):
+ "Perform a complete start/test/stop cycle."
+ self.start()
+ info( '*** Running test\n' )
+ result = test( *args, **kwargs )
+ self.stop()
+ return result
+
+ def monitor( self, hosts=None, timeoutms=-1 ):
+ """Monitor a set of hosts (or all hosts by default),
+ and return their output, a line at a time.
+ hosts: (optional) set of hosts to monitor
+ timeoutms: (optional) timeout value in ms
+ returns: iterator which returns host, line"""
+ if hosts is None:
+ hosts = self.hosts
+ poller = select.poll()
+ Node = hosts[ 0 ] # so we can call class method fdToNode
+ for host in hosts:
+ poller.register( host.stdout )
+ while True:
+ ready = poller.poll( timeoutms )
+ for fd, event in ready:
+ host = Node.fdToNode( fd )
+ if event & select.POLLIN:
+ line = host.readline()
+ if line is not None:
+ yield host, line
+ # Return if non-blocking
+ if not ready and timeoutms >= 0:
+ yield None, None
+
+ # XXX These test methods should be moved out of this class.
+ # Probably we should create a tests.py for them
+
+ @staticmethod
+ def _parsePing( pingOutput ):
+ "Parse ping output and return packets sent, received."
+ # Check for downed link
+ if 'connect: Network is unreachable' in pingOutput:
+ return (1, 0)
+ r = r'(\d+) packets transmitted, (\d+) received'
+ m = re.search( r, pingOutput )
+ if m is None:
+ error( '*** Error: could not parse ping output: %s\n' %
+ pingOutput )
+ return (1, 0)
+ sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
+ return sent, received
+
+ def ping( self, hosts=None, timeout=None ):
+ """Ping between all specified hosts.
+ hosts: list of hosts
+ timeout: time to wait for a response, as string
+ returns: ploss packet loss percentage"""
+ # should we check if running?
+ packets = 0
+ lost = 0
+ ploss = None
+ if not hosts:
+ hosts = self.hosts
+ output( '*** Ping: testing ping reachability\n' )
+ for node in hosts:
+ output( '%s -> ' % node.name )
+ for dest in hosts:
+ if node != dest:
+ opts = ''
+ if timeout:
+ opts = '-W %s' % timeout
+ result = node.cmd( 'ping -c1 %s %s' % (opts, dest.IP()) )
+ sent, received = self._parsePing( result )
+ packets += sent
+ if received > sent:
+ error( '*** Error: received too many packets' )
+ error( '%s' % result )
+ node.cmdPrint( 'route' )
+ exit( 1 )
+ lost += sent - received
+ output( ( '%s ' % dest.name ) if received else 'X ' )
+ output( '\n' )
+ ploss = 100 * lost / packets
+ output( "*** Results: %i%% dropped (%d/%d lost)\n" %
+ ( ploss, lost, packets ) )
+ return ploss
+
+ @staticmethod
+ def _parsePingFull( pingOutput ):
+ "Parse ping output and return all data."
+ # Check for downed link
+ if 'connect: Network is unreachable' in pingOutput:
+ return (1, 0)
+ r = r'(\d+) packets transmitted, (\d+) received'
+ m = re.search( r, pingOutput )
+ if m is None:
+ error( '*** Error: could not parse ping output: %s\n' %
+ pingOutput )
+ return (1, 0, 0, 0, 0, 0)
+ sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
+ r = r'rtt min/avg/max/mdev = '
+ r += r'(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+) ms'
+ m = re.search( r, pingOutput )
+ rttmin = float( m.group( 1 ) )
+ rttavg = float( m.group( 2 ) )
+ rttmax = float( m.group( 3 ) )
+ rttdev = float( m.group( 4 ) )
+ return sent, received, rttmin, rttavg, rttmax, rttdev
+
+ def pingFull( self, hosts=None, timeout=None ):
+ """Ping between all specified hosts and return all data.
+ hosts: list of hosts
+ timeout: time to wait for a response, as string
+ returns: all ping data; see function body."""
+ # should we check if running?
+ # Each value is a tuple: (src, dsd, [all ping outputs])
+ all_outputs = []
+ if not hosts:
+ hosts = self.hosts
+ output( '*** Ping: testing ping reachability\n' )
+ for node in hosts:
+ output( '%s -> ' % node.name )
+ for dest in hosts:
+ if node != dest:
+ opts = ''
+ if timeout:
+ opts = '-W %s' % timeout
+ result = node.cmd( 'ping -c1 %s %s' % (opts, dest.IP()) )
+ outputs = self._parsePingFull( result )
+ sent, received, rttmin, rttavg, rttmax, rttdev = outputs
+ all_outputs.append( (node, dest, outputs) )
+ output( ( '%s ' % dest.name ) if received else 'X ' )
+ output( '\n' )
+ output( "*** Results: \n" )
+ for outputs in all_outputs:
+ src, dest, ping_outputs = outputs
+ sent, received, rttmin, rttavg, rttmax, rttdev = ping_outputs
+ output( " %s->%s: %s/%s, " % (src, dest, sent, received ) )
+ output( "rtt min/avg/max/mdev %0.3f/%0.3f/%0.3f/%0.3f ms\n" %
+ (rttmin, rttavg, rttmax, rttdev) )
+ return all_outputs
+
+ def pingAll( self ):
+ """Ping between all hosts.
+ returns: ploss packet loss percentage"""
+ return self.ping()
+
+ def pingPair( self ):
+ """Ping between first two hosts, useful for testing.
+ returns: ploss packet loss percentage"""
+ hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
+ return self.ping( hosts=hosts )
+
+ def pingAllFull( self ):
+ """Ping between all hosts.
+ returns: ploss packet loss percentage"""
+ return self.pingFull()
+
+ def pingPairFull( self ):
+ """Ping between first two hosts, useful for testing.
+ returns: ploss packet loss percentage"""
+ hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
+ return self.pingFull( hosts=hosts )
+
+ @staticmethod
+ def _parseIperf( iperfOutput ):
+ """Parse iperf output and return bandwidth.
+ iperfOutput: string
+ returns: result string"""
+ r = r'([\d\.]+ \w+/sec)'
+ m = re.findall( r, iperfOutput )
+ if m:
+ return m[-1]
+ else:
+ # was: raise Exception(...)
+ error( 'could not parse iperf output: ' + iperfOutput )
+ return ''
+
+ # XXX This should be cleaned up
+
+ def iperf( self, hosts=None, l4Type='TCP', udpBw='10M' ):
+ """Run iperf between two hosts.
+ hosts: list of hosts; if None, uses opposite hosts
+ l4Type: string, one of [ TCP, UDP ]
+ returns: results two-element array of server and client speeds"""
+ if not quietRun( 'which telnet' ):
+ error( 'Cannot find telnet in $PATH - required for iperf test' )
+ return
+ if not hosts:
+ hosts = [ self.hosts[ 0 ], self.hosts[ -1 ] ]
+ else:
+ assert len( hosts ) == 2
+ client, server = hosts
+ output( '*** Iperf: testing ' + l4Type + ' bandwidth between ' )
+ output( "%s and %s\n" % ( client.name, server.name ) )
+ server.cmd( 'killall -9 iperf' )
+ iperfArgs = 'iperf '
+ bwArgs = ''
+ if l4Type == 'UDP':
+ iperfArgs += '-u '
+ bwArgs = '-b ' + udpBw + ' '
+ elif l4Type != 'TCP':
+ raise Exception( 'Unexpected l4 type: %s' % l4Type )
+ server.sendCmd( iperfArgs + '-s', printPid=True )
+ servout = ''
+ while server.lastPid is None:
+ servout += server.monitor()
+ if l4Type == 'TCP':
+ while 'Connected' not in client.cmd(
+ 'sh -c "echo A | telnet -e A %s 5001"' % server.IP()):
+ output('waiting for iperf to start up...')
+ sleep(.5)
+ cliout = client.cmd( iperfArgs + '-t 5 -c ' + server.IP() + ' ' +
+ bwArgs )
+ debug( 'Client output: %s\n' % cliout )
+ server.sendInt()
+ servout += server.waitOutput()
+ debug( 'Server output: %s\n' % servout )
+ result = [ self._parseIperf( servout ), self._parseIperf( cliout ) ]
+ if l4Type == 'UDP':
+ result.insert( 0, udpBw )
+ output( '*** Results: %s\n' % result )
+ return result
+
+ def runCpuLimitTest( self, cpu, duration=5 ):
+ """run CPU limit test with 'while true' processes.
+ cpu: desired CPU fraction of each host
+ duration: test duration in seconds
+ returns a single list of measured CPU fractions as floats.
+ """
+ pct = cpu * 100
+ info('*** Testing CPU %.0f%% bandwidth limit\n' % pct)
+ hosts = self.hosts
+ for h in hosts:
+ h.cmd( 'while true; do a=1; done &' )
+ pids = [h.cmd( 'echo $!' ).strip() for h in hosts]
+ pids_str = ",".join(["%s" % pid for pid in pids])
+ cmd = 'ps -p %s -o pid,%%cpu,args' % pids_str
+ # It's a shame that this is what pylint prefers
+ outputs = []
+ for _ in range( duration ):
+ sleep( 1 )
+ outputs.append( quietRun( cmd ).strip() )
+ for h in hosts:
+ h.cmd( 'kill %1' )
+ cpu_fractions = []
+ for test_output in outputs:
+ # Split by line. Ignore first line, which looks like this:
+ # PID %CPU COMMAND\n
+ for line in test_output.split('\n')[1:]:
+ r = r'\d+\s*(\d+\.\d+)'
+ m = re.search( r, line )
+ if m is None:
+ error( '*** Error: could not extract CPU fraction: %s\n' %
+ line )
+ return None
+ cpu_fractions.append( float( m.group( 1 ) ) )
+ output( '*** Results: %s\n' % cpu_fractions )
+ return cpu_fractions
+
+ # BL: I think this can be rewritten now that we have
+ # a real link class.
+ def configLinkStatus( self, src, dst, status ):
+ """Change status of src <-> dst links.
+ src: node name
+ dst: node name
+ status: string {up, down}"""
+ if src not in self.nameToNode:
+ error( 'src not in network: %s\n' % src )
+ elif dst not in self.nameToNode:
+ error( 'dst not in network: %s\n' % dst )
+ else:
+ if type( src ) is str:
+ src = self.nameToNode[ src ]
+ if type( dst ) is str:
+ dst = self.nameToNode[ dst ]
+ connections = src.connectionsTo( dst )
+ if len( connections ) == 0:
+ error( 'src and dst not connected: %s %s\n' % ( src, dst) )
+ for srcIntf, dstIntf in connections:
+ result = srcIntf.ifconfig( status )
+ if result:
+ error( 'link src status change failed: %s\n' % result )
+ result = dstIntf.ifconfig( status )
+ if result:
+ error( 'link dst status change failed: %s\n' % result )
+
+ def interact( self ):
+ "Start network and run our simple CLI."
+ self.start()
+ result = CLI( self )
+ self.stop()
+ return result
+
+ inited = False
+
+ @classmethod
+ def init( cls ):
+ "Initialize Mininet"
+ if cls.inited:
+ return
+ ensureRoot()
+ fixLimits()
+ cls.inited = True
+
+
+class MininetWithControlNet( Mininet ):
+
+ """Control network support:
+
+ Create an explicit control network. Currently this is only
+ used/usable with the user datapath.
+
+ Notes:
+
+ 1. If the controller and switches are in the same (e.g. root)
+ namespace, they can just use the loopback connection.
+
+ 2. If we can get unix domain sockets to work, we can use them
+ instead of an explicit control network.
+
+ 3. Instead of routing, we could bridge or use 'in-band' control.
+
+ 4. Even if we dispense with this in general, it could still be
+ useful for people who wish to simulate a separate control
+ network (since real networks may need one!)
+
+ 5. Basically nobody ever used this code, so it has been moved
+ into its own class.
+
+ 6. Ultimately we may wish to extend this to allow us to create a
+ control network which every node's control interface is
+ attached to."""
+
+ def configureControlNetwork( self ):
+ "Configure control network."
+ self.configureRoutedControlNetwork()
+
+ # We still need to figure out the right way to pass
+ # in the control network location.
+
+ def configureRoutedControlNetwork( self, ip='192.168.123.1',
+ prefixLen=16 ):
+ """Configure a routed control network on controller and switches.
+ For use with the user datapath only right now."""
+ controller = self.controllers[ 0 ]
+ info( controller.name + ' <->' )
+ cip = ip
+ snum = ipParse( ip )
+ for switch in self.switches:
+ info( ' ' + switch.name )
+ link = self.link( switch, controller, port1=0 )
+ sintf, cintf = link.intf1, link.intf2
+ switch.controlIntf = sintf
+ snum += 1
+ while snum & 0xff in [ 0, 255 ]:
+ snum += 1
+ sip = ipStr( snum )
+ cintf.setIP( cip, prefixLen )
+ sintf.setIP( sip, prefixLen )
+ controller.setHostRoute( sip, cintf )
+ switch.setHostRoute( cip, sintf )
+ info( '\n' )
+ info( '*** Testing control network\n' )
+ while not cintf.isUp():
+ info( '*** Waiting for', cintf, 'to come up\n' )
+ sleep( 1 )
+ for switch in self.switches:
+ while not sintf.isUp():
+ info( '*** Waiting for', sintf, 'to come up\n' )
+ sleep( 1 )
+ if self.ping( hosts=[ switch, controller ] ) != 0:
+ error( '*** Error: control network test failed\n' )
+ exit( 1 )
+ info( '\n' )