First commit
diff --git a/mininet/__init__.py b/mininet/__init__.py
new file mode 100644
index 0000000..c15ea6a
--- /dev/null
+++ b/mininet/__init__.py
@@ -0,0 +1 @@
+"Docstring to silence pylint; ignores --ignore option for __init__.py"
diff --git a/mininet/clean.py b/mininet/clean.py
new file mode 100644
index 0000000..eac8fda
--- /dev/null
+++ b/mininet/clean.py
@@ -0,0 +1,60 @@
+"""
+Mininet Cleanup
+author: Bob Lantz (rlantz@cs.stanford.edu)
+
+Unfortunately, Mininet and OpenFlow (and the Linux kernel)
+don't always clean up properly after themselves. Until they do
+(or until cleanup functionality is integrated into the Python
+code), this script may be used to get rid of unwanted garbage.
+It may also get rid of 'false positives', but hopefully
+nothing irreplaceable!
+"""
+
+from subprocess import Popen, PIPE
+
+from mininet.log import info
+from mininet.term import cleanUpScreens
+
+def sh( cmd ):
+ "Print a command and send it to the shell"
+ info( cmd + '\n' )
+ return Popen( [ '/bin/sh', '-c', cmd ], stdout=PIPE ).communicate()[ 0 ]
+
+def cleanup():
+ """Clean up junk which might be left over from old runs;
+ do fast stuff before slow dp and link removal!"""
+
+ info("*** Removing excess controllers/ofprotocols/ofdatapaths/pings/noxes"
+ "\n")
+ zombies = 'controller ofprotocol ofdatapath ping nox_core lt-nox_core '
+ zombies += 'ovs-openflowd udpbwtest'
+ # Note: real zombie processes can't actually be killed, since they
+ # are already (un)dead. Then again,
+ # you can't connect to them either, so they're mostly harmless.
+ sh( 'killall -9 ' + zombies + ' 2> /dev/null' )
+
+ info( "*** Removing junk from /tmp\n" )
+ sh( 'rm -f /tmp/vconn* /tmp/vlogs* /tmp/*.out /tmp/*.log' )
+
+ info( "*** Removing old screen sessions\n" )
+ cleanUpScreens()
+
+ info( "*** Removing excess kernel datapaths\n" )
+ dps = sh( "ps ax | egrep -o 'dp[0-9]+' | sed 's/dp/nl:/'" ).split( '\n' )
+ for dp in dps:
+ if dp != '':
+ sh( 'dpctl deldp ' + dp )
+
+ info( "*** Removing OVS datapaths" )
+ dps = sh("ovs-vsctl list-br").split( '\n' )
+ for dp in dps:
+ if dp:
+ sh( 'ovs-vsctl del-br ' + dp )
+
+ info( "*** Removing all links of the pattern foo-ethX\n" )
+ links = sh( "ip link show | egrep -o '(\w+-eth\w+)'" ).split( '\n' )
+ for link in links:
+ if link != '':
+ sh( "ip link del " + link )
+
+ info( "*** Cleanup complete.\n" )
diff --git a/mininet/cli.py b/mininet/cli.py
new file mode 100644
index 0000000..efb1808
--- /dev/null
+++ b/mininet/cli.py
@@ -0,0 +1,366 @@
+"""
+A simple command-line interface for Mininet.
+
+The Mininet CLI provides a simple control console which
+makes it easy to talk to nodes. For example, the command
+
+mininet> h27 ifconfig
+
+runs 'ifconfig' on host h27.
+
+Having a single console rather than, for example, an xterm for each
+node is particularly convenient for networks of any reasonable
+size.
+
+The CLI automatically substitutes IP addresses for node names,
+so commands like
+
+mininet> h2 ping h3
+
+should work correctly and allow host h2 to ping host h3
+
+Several useful commands are provided, including the ability to
+list all nodes ('nodes'), to print out the network topology
+('net') and to check connectivity ('pingall', 'pingpair')
+and bandwidth ('iperf'.)
+"""
+
+from subprocess import call
+from cmd import Cmd
+from os import isatty
+from select import poll, POLLIN
+import sys
+import time
+
+from mininet.log import info, output, error
+from mininet.term import makeTerms
+from mininet.util import quietRun, isShellBuiltin, dumpNodeConnections
+
+class CLI( Cmd ):
+ "Simple command-line interface to talk to nodes."
+
+ prompt = 'mininet> '
+
+ def __init__( self, mininet, stdin=sys.stdin, script=None ):
+ self.mn = mininet
+ self.nodelist = self.mn.controllers + self.mn.switches + self.mn.hosts
+ self.nodemap = {} # map names to Node objects
+ for node in self.nodelist:
+ self.nodemap[ node.name ] = node
+ # Local variable bindings for py command
+ self.locals = { 'net': mininet }
+ self.locals.update( self.nodemap )
+ # Attempt to handle input
+ self.stdin = stdin
+ self.inPoller = poll()
+ self.inPoller.register( stdin )
+ self.inputFile = script
+ Cmd.__init__( self )
+ info( '*** Starting CLI:\n' )
+ if self.inputFile:
+ self.do_source( self.inputFile )
+ return
+ while True:
+ try:
+ # Make sure no nodes are still waiting
+ for node in self.nodelist:
+ while node.waiting:
+ node.sendInt()
+ node.monitor()
+ if self.isatty():
+ quietRun( 'stty sane' )
+ self.cmdloop()
+ break
+ except KeyboardInterrupt:
+ output( '\nInterrupt\n' )
+
+ def emptyline( self ):
+ "Don't repeat last command when you hit return."
+ pass
+
+ # Disable pylint "Unused argument: 'arg's'" messages, as well as
+ # "method could be a function" warning, since each CLI function
+ # must have the same interface
+ # pylint: disable-msg=R0201
+
+ helpStr = (
+ 'You may also send a command to a node using:\n'
+ ' <node> command {args}\n'
+ 'For example:\n'
+ ' mininet> h1 ifconfig\n'
+ '\n'
+ 'The interpreter automatically substitutes IP addresses\n'
+ 'for node names when a node is the first arg, so commands\n'
+ 'like\n'
+ ' mininet> h2 ping h3\n'
+ 'should work.\n'
+ '\n'
+ 'Some character-oriented interactive commands require\n'
+ 'noecho:\n'
+ ' mininet> noecho h2 vi foo.py\n'
+ 'However, starting up an xterm/gterm is generally better:\n'
+ ' mininet> xterm h2\n\n'
+ )
+
+ def do_help( self, line ):
+ "Describe available CLI commands."
+ Cmd.do_help( self, line )
+ if line is '':
+ output( self.helpStr )
+
+ def do_nodes( self, _line ):
+ "List all nodes."
+ nodes = ' '.join( [ node.name for node in sorted( self.nodelist ) ] )
+ output( 'available nodes are: \n%s\n' % nodes )
+
+ def do_net( self, _line ):
+ "List network connections."
+ dumpNodeConnections( self.nodelist )
+
+ def do_sh( self, line ):
+ "Run an external shell command"
+ call( line, shell=True )
+
+ # do_py() needs to catch any exception during eval()
+ # pylint: disable-msg=W0703
+
+ def do_py( self, line ):
+ """Evaluate a Python expression.
+ Node names may be used, e.g.: h1.cmd('ls')"""
+ try:
+ result = eval( line, globals(), self.locals )
+ if not result:
+ return
+ elif isinstance( result, str ):
+ output( result + '\n' )
+ else:
+ output( repr( result ) + '\n' )
+ except Exception, e:
+ output( str( e ) + '\n' )
+
+ # pylint: enable-msg=W0703
+
+ def do_pingall( self, _line ):
+ "Ping between all hosts."
+ self.mn.pingAll()
+
+ def do_pingpair( self, _line ):
+ "Ping between first two hosts, useful for testing."
+ self.mn.pingPair()
+
+ def do_pingallfull( self, _line ):
+ "Ping between first two hosts, returns all ping results."
+ self.mn.pingAllFull()
+
+ def do_pingpairfull( self, _line ):
+ "Ping between first two hosts, returns all ping results."
+ self.mn.pingPairFull()
+
+ def do_iperf( self, line ):
+ "Simple iperf TCP test between two (optionally specified) hosts."
+ args = line.split()
+ if not args:
+ self.mn.iperf()
+ elif len(args) == 2:
+ hosts = []
+ err = False
+ for arg in args:
+ if arg not in self.nodemap:
+ err = True
+ error( "node '%s' not in network\n" % arg )
+ else:
+ hosts.append( self.nodemap[ arg ] )
+ if not err:
+ self.mn.iperf( hosts )
+ else:
+ error( 'invalid number of args: iperf src dst\n' )
+
+ def do_iperfudp( self, line ):
+ "Simple iperf TCP test between two (optionally specified) hosts."
+ args = line.split()
+ if not args:
+ self.mn.iperf( l4Type='UDP' )
+ elif len(args) == 3:
+ udpBw = args[ 0 ]
+ hosts = []
+ err = False
+ for arg in args[ 1:3 ]:
+ if arg not in self.nodemap:
+ err = True
+ error( "node '%s' not in network\n" % arg )
+ else:
+ hosts.append( self.nodemap[ arg ] )
+ if not err:
+ self.mn.iperf( hosts, l4Type='UDP', udpBw=udpBw )
+ else:
+ error( 'invalid number of args: iperfudp bw src dst\n' +
+ 'bw examples: 10M\n' )
+
+ def do_intfs( self, _line ):
+ "List interfaces."
+ for node in self.nodelist:
+ output( '%s: %s\n' %
+ ( node.name, ','.join( node.intfNames() ) ) )
+
+ def do_dump( self, _line ):
+ "Dump node info."
+ for node in self.nodelist:
+ output( '%s\n' % repr( node ) )
+
+ def do_link( self, line ):
+ "Bring link(s) between two nodes up or down."
+ args = line.split()
+ if len(args) != 3:
+ error( 'invalid number of args: link end1 end2 [up down]\n' )
+ elif args[ 2 ] not in [ 'up', 'down' ]:
+ error( 'invalid type: link end1 end2 [up down]\n' )
+ else:
+ self.mn.configLinkStatus( *args )
+
+ def do_xterm( self, line, term='xterm' ):
+ "Spawn xterm(s) for the given node(s)."
+ args = line.split()
+ if not args:
+ error( 'usage: %s node1 node2 ...\n' % term )
+ else:
+ for arg in args:
+ if arg not in self.nodemap:
+ error( "node '%s' not in network\n" % arg )
+ else:
+ node = self.nodemap[ arg ]
+ self.mn.terms += makeTerms( [ node ], term = term )
+
+ def do_gterm( self, line ):
+ "Spawn gnome-terminal(s) for the given node(s)."
+ self.do_xterm( line, term='gterm' )
+
+ def do_exit( self, _line ):
+ "Exit"
+ return 'exited by user command'
+
+ def do_quit( self, line ):
+ "Exit"
+ return self.do_exit( line )
+
+ def do_EOF( self, line ):
+ "Exit"
+ output( '\n' )
+ return self.do_exit( line )
+
+ def isatty( self ):
+ "Is our standard input a tty?"
+ return isatty( self.stdin.fileno() )
+
+ def do_noecho( self, line ):
+ "Run an interactive command with echoing turned off."
+ if self.isatty():
+ quietRun( 'stty -echo' )
+ self.default( line )
+ if self.isatty():
+ quietRun( 'stty echo' )
+
+ def do_source( self, line ):
+ "Read commands from an input file."
+ args = line.split()
+ if len(args) != 1:
+ error( 'usage: source <file>\n' )
+ return
+ try:
+ self.inputFile = open( args[ 0 ] )
+ while True:
+ line = self.inputFile.readline()
+ if len( line ) > 0:
+ self.onecmd( line )
+ else:
+ break
+ except IOError:
+ error( 'error reading file %s\n' % args[ 0 ] )
+ self.inputFile = None
+
+ def do_dpctl( self, line ):
+ "Run dpctl command on all switches."
+ args = line.split()
+ if len(args) < 1:
+ error( 'usage: dpctl command [arg1] [arg2] ...\n' )
+ return
+ for sw in self.mn.switches:
+ output( '*** ' + sw.name + ' ' + ('-' * 72) + '\n' )
+ output( sw.dpctl( *args ) )
+
+ def do_time( self, line ):
+ "Measure time taken for any command in Mininet."
+ start = time.time()
+ self.onecmd(line)
+ elapsed = time.time() - start
+ self.stdout.write("*** Elapsed time: %0.6f secs\n" % elapsed)
+
+ def default( self, line ):
+ """Called on an input line when the command prefix is not recognized.
+ Overridden to run shell commands when a node is the first CLI argument.
+ Past the first CLI argument, node names are automatically replaced with
+ corresponding IP addrs."""
+
+ first, args, line = self.parseline( line )
+ if not args:
+ return
+ if args and len(args) > 0 and args[ -1 ] == '\n':
+ args = args[ :-1 ]
+ rest = args.split( ' ' )
+
+ if first in self.nodemap:
+ 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 )
+ # Run cmd on node:
+ builtin = isShellBuiltin( first )
+ node.sendCmd( rest, printPid=( not builtin ) )
+ self.waitForNode( node )
+ else:
+ error( '*** Unknown command: %s\n' % first )
+
+ # pylint: enable-msg=R0201
+
+ def waitForNode( self, node ):
+ "Wait for a node to finish, and print its output."
+ # Pollers
+ nodePoller = poll()
+ nodePoller.register( node.stdout )
+ bothPoller = poll()
+ bothPoller.register( self.stdin, POLLIN )
+ bothPoller.register( node.stdout, POLLIN )
+ if self.isatty():
+ # Buffer by character, so that interactive
+ # commands sort of work
+ quietRun( 'stty -icanon min 1' )
+ while True:
+ try:
+ bothPoller.poll()
+ # XXX BL: this doesn't quite do what we want.
+ if False and self.inputFile:
+ key = self.inputFile.read( 1 )
+ if key is not '':
+ node.write(key)
+ else:
+ self.inputFile = None
+ if isReadable( self.inPoller ):
+ key = self.stdin.read( 1 )
+ node.write( key )
+ if isReadable( nodePoller ):
+ data = node.monitor()
+ output( data )
+ if not node.waiting:
+ break
+ except KeyboardInterrupt:
+ node.sendInt()
+
+# Helper functions
+
+def isReadable( poller ):
+ "Check whether a Poll object has a readable fd."
+ for fdmask in poller.poll( 0 ):
+ mask = fdmask[ 1 ]
+ if mask & POLLIN:
+ return True
diff --git a/mininet/conf_parser.py b/mininet/conf_parser.py
new file mode 100644
index 0000000..68792ac
--- /dev/null
+++ b/mininet/conf_parser.py
@@ -0,0 +1,108 @@
+import ConfigParser
+
+class confCCNHost():
+
+ def __init__(self, name, app='', uri_tuples=''):
+ self.name = name
+ self.app = app
+ self.uri_tuples = uri_tuples
+
+ def __repr__(self):
+ return 'Name: ' + self.name + ' App: ' + self.app + ' URIS: ' + str(self.uri_tuples)
+
+class confCCNLink():
+
+ def __init__(self,h1,h2,linkDict=None):
+ self.h1 = h1
+ self.h2 = h2
+ self.linkDict = linkDict
+
+ def __repr__(self):
+ return 'h1: ' + self.h1 + ' h2: ' + self.h2 + ' params: ' + str(self.linkDict)
+
+def extrai_hosts(conf_arq):
+ 'Extrai hosts da secao hosts do arquivo de configuracao'
+ config = ConfigParser.RawConfigParser()
+ config.read(conf_arq)
+
+ hosts = []
+
+ items = config.items('hosts')
+
+ for item in items:
+
+ name = item[0]
+
+ rest = item[1].split()
+
+ app = rest.pop(0)
+
+ uris = rest
+ uri_list=[]
+ for uri in uris:
+ uri_list.append((uri.split(',')[0],uri.split(',')[1]))
+
+ hosts.append(confCCNHost(name , app, uri_list))
+
+ return hosts
+
+def extrai_routers(conf_arq):
+ 'Extrai routers da secao routers do arquivo de configuracao'
+ config = ConfigParser.RawConfigParser()
+ config.read(conf_arq)
+
+ routers = []
+
+ items = config.items('routers')
+
+ for item in items:
+ name = item[0]
+
+ rest = item[1].split()
+
+ uris = rest
+ uri_list=[]
+ for uri in uris:
+ uri_list.append((uri.split(',')[0],uri.split(',')[1]))
+
+ routers.append(confCCNHost(name=name , uri_tuples=uri_list))
+
+ return routers
+
+def extrai_links(conf_arq):
+ 'Extrai links da secao links do arquivo de configuracao'
+ arq = open(conf_arq,'r')
+
+ links = []
+
+ while True:
+ line = arq.readline()
+ if line == '[links]\n':
+ break
+
+ while True:
+ line = arq.readline()
+ if line == '':
+ break
+
+ args = line.split()
+ h1, h2 = args.pop(0).split(':')
+
+ link_dict = {}
+
+ for arg in args:
+ arg_name, arg_value = arg.split('=')
+ key = arg_name
+ value = arg_value
+ if key in ['loss','bw','jitter']:
+ value = int(value)
+
+ link_dict[key] = value
+
+ links.append(confCCNLink(h1,h2,link_dict))
+
+
+ return links
+
+
+
diff --git a/mininet/link.py b/mininet/link.py
new file mode 100644
index 0000000..21e18ba
--- /dev/null
+++ b/mininet/link.py
@@ -0,0 +1,397 @@
+"""
+link.py: interface and link abstractions for mininet
+
+It seems useful to bundle functionality for interfaces into a single
+class.
+
+Also it seems useful to enable the possibility of multiple flavors of
+links, including:
+
+- simple veth pairs
+- tunneled links
+- patchable links (which can be disconnected and reconnected via a patchbay)
+- link simulators (e.g. wireless)
+
+Basic division of labor:
+
+ Nodes: know how to execute commands
+ Intfs: know how to configure themselves
+ Links: know how to connect nodes together
+
+Intf: basic interface object that can configure itself
+TCIntf: interface with bandwidth limiting and delay via tc
+
+Link: basic link class for creating veth pairs
+"""
+
+from mininet.log import info, error, debug
+from mininet.util import makeIntfPair
+from time import sleep
+import re
+
+class Intf( object ):
+
+ "Basic interface object that can configure itself."
+
+ def __init__( self, name, node=None, port=None, link=None, **params ):
+ """name: interface name (e.g. h1-eth0)
+ node: owning node (where this intf most likely lives)
+ link: parent link if we're part of a link
+ other arguments are passed to config()"""
+ self.node = node
+ self.name = name
+ self.link = link
+ self.mac, self.ip, self.prefixLen = None, None, None
+ # Add to node (and move ourselves if necessary )
+ node.addIntf( self, port=port )
+ # Save params for future reference
+ self.params = params
+ self.config( **params )
+
+ def cmd( self, *args, **kwargs ):
+ "Run a command in our owning node"
+ return self.node.cmd( *args, **kwargs )
+
+ def ifconfig( self, *args ):
+ "Configure ourselves using ifconfig"
+ return self.cmd( 'ifconfig', self.name, *args )
+
+ def setIP( self, ipstr, prefixLen=None ):
+ """Set our IP address"""
+ # This is a sign that we should perhaps rethink our prefix
+ # mechanism and/or the way we specify IP addresses
+ if '/' in ipstr:
+ self.ip, self.prefixLen = ipstr.split( '/' )
+ return self.ifconfig( ipstr, 'up' )
+ else:
+ self.ip, self.prefixLen = ipstr, prefixLen
+ return self.ifconfig( '%s/%s' % ( ipstr, prefixLen ) )
+
+ def setMAC( self, macstr ):
+ """Set the MAC address for an interface.
+ macstr: MAC address as string"""
+ self.mac = macstr
+ return ( self.ifconfig( 'down' ) +
+ self.ifconfig( 'hw', 'ether', macstr ) +
+ self.ifconfig( 'up' ) )
+
+ _ipMatchRegex = re.compile( r'\d+\.\d+\.\d+\.\d+' )
+ _macMatchRegex = re.compile( r'..:..:..:..:..:..' )
+
+ def updateIP( self ):
+ "Return updated IP address based on ifconfig"
+ ifconfig = self.ifconfig()
+ ips = self._ipMatchRegex.findall( ifconfig )
+ self.ip = ips[ 0 ] if ips else None
+ return self.ip
+
+ def updateMAC( self ):
+ "Return updated MAC address based on ifconfig"
+ ifconfig = self.ifconfig()
+ macs = self._macMatchRegex.findall( ifconfig )
+ self.mac = macs[ 0 ] if macs else None
+ return self.mac
+
+ def IP( self ):
+ "Return IP address"
+ return self.ip
+
+ def MAC( self ):
+ "Return MAC address"
+ return self.mac
+
+ def isUp( self, setUp=False ):
+ "Return whether interface is up"
+ if setUp:
+ self.ifconfig( 'up' )
+ return "UP" in self.ifconfig()
+
+ def rename( self, newname ):
+ "Rename interface"
+ self.ifconfig( 'down' )
+ result = self.cmd( 'ip link set', self.name, 'name', newname )
+ self.name = newname
+ self.ifconfig( 'up' )
+ return result
+
+ # The reason why we configure things in this way is so
+ # That the parameters can be listed and documented in
+ # the config method.
+ # Dealing with subclasses and superclasses is slightly
+ # annoying, but at least the information is there!
+
+ def setParam( self, results, method, **param ):
+ """Internal method: configure a *single* parameter
+ results: dict of results to update
+ method: config method name
+ param: arg=value (ignore if value=None)
+ value may also be list or dict"""
+ name, value = param.items()[ 0 ]
+ f = getattr( self, method, None )
+ if not f or value is None:
+ return
+ if type( value ) is list:
+ result = f( *value )
+ elif type( value ) is dict:
+ result = f( **value )
+ else:
+ result = f( value )
+ results[ name ] = result
+ return result
+
+ def config( self, mac=None, ip=None, ifconfig=None,
+ up=True, **_params ):
+ """Configure Node according to (optional) parameters:
+ mac: MAC address
+ ip: IP address
+ ifconfig: arbitrary interface configuration
+ Subclasses should override this method and call
+ the parent class's config(**params)"""
+ # If we were overriding this method, we would call
+ # the superclass config method here as follows:
+ # r = Parent.config( **params )
+ r = {}
+ self.setParam( r, 'setMAC', mac=mac )
+ self.setParam( r, 'setIP', ip=ip )
+ self.setParam( r, 'isUp', up=up )
+ self.setParam( r, 'ifconfig', ifconfig=ifconfig )
+ self.updateIP()
+ self.updateMAC()
+ return r
+
+ def delete( self ):
+ "Delete interface"
+ self.cmd( 'ip link del ' + self.name )
+ # Does it help to sleep to let things run?
+ sleep( 0.001 )
+
+ def __repr__( self ):
+ return '<%s %s>' % ( self.__class__.__name__, self.name )
+
+ def __str__( self ):
+ return self.name
+
+
+class TCIntf( Intf ):
+ """Interface customized by tc (traffic control) utility
+ Allows specification of bandwidth limits (various methods)
+ as well as delay, loss and max queue length"""
+
+ def bwCmds( self, bw=None, speedup=0, use_hfsc=False, use_tbf=False,
+ latency_ms=None, enable_ecn=False, enable_red=False ):
+ "Return tc commands to set bandwidth"
+
+ cmds, parent = [], ' root '
+
+ if bw and ( bw < 0 or bw > 1000 ):
+ error( 'Bandwidth', bw, 'is outside range 0..1000 Mbps\n' )
+
+ elif bw is not None:
+ # BL: this seems a bit brittle...
+ if ( speedup > 0 and
+ self.node.name[0:1] == 's' ):
+ bw = speedup
+ # This may not be correct - we should look more closely
+ # at the semantics of burst (and cburst) to make sure we
+ # are specifying the correct sizes. For now I have used
+ # the same settings we had in the mininet-hifi code.
+ if use_hfsc:
+ cmds += [ '%s qdisc add dev %s root handle 1:0 hfsc default 1',
+ '%s class add dev %s parent 1:0 classid 1:1 hfsc sc '
+ + 'rate %fMbit ul rate %fMbit' % ( bw, bw ) ]
+ elif use_tbf:
+ if latency_ms is None:
+ latency_ms = 15 * 8 / bw
+ cmds += [ '%s qdisc add dev %s root handle 1: tbf ' +
+ 'rate %fMbit burst 15000 latency %fms' %
+ ( bw, latency_ms ) ]
+ else:
+ cmds += [ '%s qdisc add dev %s root handle 1:0 htb default 1',
+ '%s class add dev %s parent 1:0 classid 1:1 htb ' +
+ 'rate %fMbit burst 15k' % bw ]
+ parent = ' parent 1:1 '
+
+ # ECN or RED
+ if enable_ecn:
+ cmds += [ '%s qdisc add dev %s' + parent +
+ 'handle 10: red limit 1000000 ' +
+ 'min 30000 max 35000 avpkt 1500 ' +
+ 'burst 20 ' +
+ 'bandwidth %fmbit probability 1 ecn' % bw ]
+ parent = ' parent 10: '
+ elif enable_red:
+ cmds += [ '%s qdisc add dev %s' + parent +
+ 'handle 10: red limit 1000000 ' +
+ 'min 30000 max 35000 avpkt 1500 ' +
+ 'burst 20 ' +
+ 'bandwidth %fmbit probability 1' % bw ]
+ parent = ' parent 10: '
+ return cmds, parent
+
+ @staticmethod
+ def delayCmds( parent, delay=None, jitter=None,
+ loss=None, max_queue_size=None ):
+ "Internal method: return tc commands for delay and loss"
+ cmds = []
+ if delay and delay < 0:
+ error( 'Negative delay', delay, '\n' )
+ elif jitter and jitter < 0:
+ error( 'Negative jitter', jitter, '\n' )
+ elif loss and ( loss < 0 or loss > 100 ):
+ error( 'Bad loss percentage', loss, '%%\n' )
+ else:
+ # Delay/jitter/loss/max queue size
+ netemargs = '%s%s%s%s' % (
+ 'delay %s ' % delay if delay is not None else '',
+ '%s ' % jitter if jitter is not None else '',
+ 'loss %d ' % loss if loss is not None else '',
+ 'limit %d' % max_queue_size if max_queue_size is not None
+ else '' )
+ if netemargs:
+ cmds = [ '%s qdisc add dev %s ' + parent +
+ ' handle 10: netem ' +
+ netemargs ]
+ return cmds
+
+ def tc( self, cmd, tc='tc' ):
+ "Execute tc command for our interface"
+ c = cmd % (tc, self) # Add in tc command and our name
+ debug(" *** executing command: %s\n" % c)
+ return self.cmd( c )
+
+ def config( self, bw=None, delay=None, jitter=None, loss=None,
+ disable_gro=True, speedup=0, use_hfsc=False, use_tbf=False,
+ latency_ms=None, enable_ecn=False, enable_red=False,
+ max_queue_size=None, **params ):
+ "Configure the port and set its properties."
+
+ result = Intf.config( self, **params)
+
+ # Disable GRO
+ if disable_gro:
+ self.cmd( 'ethtool -K %s gro off' % self )
+
+ # Optimization: return if nothing else to configure
+ # Question: what happens if we want to reset things?
+ if ( bw is None and not delay and not loss
+ and max_queue_size is None ):
+ return
+
+ # Clear existing configuration
+ cmds = [ '%s qdisc del dev %s root' ]
+
+ # Bandwidth limits via various methods
+ bwcmds, parent = self.bwCmds( bw=bw, speedup=speedup,
+ use_hfsc=use_hfsc, use_tbf=use_tbf,
+ latency_ms=latency_ms,
+ enable_ecn=enable_ecn,
+ enable_red=enable_red )
+ cmds += bwcmds
+
+ # Delay/jitter/loss/max_queue_size using netem
+ cmds += self.delayCmds( delay=delay, jitter=jitter, loss=loss,
+ max_queue_size=max_queue_size,
+ parent=parent )
+
+ # Ugly but functional: display configuration info
+ stuff = ( ( [ '%.2fMbit' % bw ] if bw is not None else [] ) +
+ ( [ '%s delay' % delay ] if delay is not None else [] ) +
+ ( [ '%s jitter' % jitter ] if jitter is not None else [] ) +
+ ( ['%d%% loss' % loss ] if loss is not None else [] ) +
+ ( [ 'ECN' ] if enable_ecn else [ 'RED' ]
+ if enable_red else [] ) )
+ info( '(' + ' '.join( stuff ) + ') ' )
+
+ # Execute all the commands in our node
+ debug("at map stage w/cmds: %s\n" % cmds)
+ tcoutputs = [ self.tc(cmd) for cmd in cmds ]
+ debug( "cmds:", cmds, '\n' )
+ debug( "outputs:", tcoutputs, '\n' )
+ result[ 'tcoutputs'] = tcoutputs
+
+ return result
+
+
+class Link( object ):
+
+ """A basic link is just a veth pair.
+ Other types of links could be tunnels, link emulators, etc.."""
+
+ def __init__( self, node1, node2, port1=None, port2=None,
+ intfName1=None, intfName2=None,
+ intf=Intf, cls1=None, cls2=None, params1=None,
+ params2=None ):
+ """Create veth link to another node, making two new interfaces.
+ node1: first node
+ node2: second node
+ port1: node1 port number (optional)
+ port2: node2 port number (optional)
+ intf: default interface class/constructor
+ cls1, cls2: optional interface-specific constructors
+ intfName1: node1 interface name (optional)
+ intfName2: node2 interface name (optional)
+ params1: parameters for interface 1
+ params2: parameters for interface 2"""
+ # This is a bit awkward; it seems that having everything in
+ # params would be more orthogonal, but being able to specify
+ # in-line arguments is more convenient!
+ if port1 is None:
+ port1 = node1.newPort()
+ if port2 is None:
+ port2 = node2.newPort()
+ if not intfName1:
+ intfName1 = self.intfName( node1, port1 )
+ if not intfName2:
+ intfName2 = self.intfName( node2, port2 )
+
+ self.makeIntfPair( intfName1, intfName2 )
+
+ if not cls1:
+ cls1 = intf
+ if not cls2:
+ cls2 = intf
+ if not params1:
+ params1 = {}
+ if not params2:
+ params2 = {}
+
+ intf1 = cls1( name=intfName1, node=node1, port=port1,
+ link=self, **params1 )
+ intf2 = cls2( name=intfName2, node=node2, port=port2,
+ link=self, **params2 )
+
+ # All we are is dust in the wind, and our two interfaces
+ self.intf1, self.intf2 = intf1, intf2
+
+ @classmethod
+ def intfName( cls, node, n ):
+ "Construct a canonical interface name node-ethN for interface n."
+ return node.name + '-eth' + repr( n )
+
+ @classmethod
+ def makeIntfPair( cls, intf1, intf2 ):
+ """Create pair of interfaces
+ intf1: name of interface 1
+ intf2: name of interface 2
+ (override this class method [and possibly delete()]
+ to change link type)"""
+ makeIntfPair( intf1, intf2 )
+
+ def delete( self ):
+ "Delete this link"
+ self.intf1.delete()
+ self.intf2.delete()
+
+ def __str__( self ):
+ return '%s<->%s' % ( self.intf1, self.intf2 )
+
+class TCLink( Link ):
+ "Link with symmetric TC interfaces configured via opts"
+ def __init__( self, node1, node2, port1=None, port2=None,
+ intfName1=None, intfName2=None, **params ):
+ Link.__init__( self, node1, node2, port1=port1, port2=port2,
+ intfName1=intfName1, intfName2=intfName2,
+ cls1=TCIntf,
+ cls2=TCIntf,
+ params1=params,
+ params2=params)
diff --git a/mininet/log.py b/mininet/log.py
new file mode 100644
index 0000000..cd00821
--- /dev/null
+++ b/mininet/log.py
@@ -0,0 +1,178 @@
+"Logging functions for Mininet."
+
+import logging
+from logging import Logger
+import types
+
+# Create a new loglevel, 'CLI info', which enables a Mininet user to see only
+# the output of the commands they execute, plus any errors or warnings. This
+# level is in between info and warning. CLI info-level commands should not be
+# printed during regression tests.
+OUTPUT = 25
+
+LEVELS = { 'debug': logging.DEBUG,
+ 'info': logging.INFO,
+ 'output': OUTPUT,
+ 'warning': logging.WARNING,
+ 'error': logging.ERROR,
+ 'critical': logging.CRITICAL }
+
+# change this to logging.INFO to get printouts when running unit tests
+LOGLEVELDEFAULT = OUTPUT
+
+#default: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
+LOGMSGFORMAT = '%(message)s'
+
+
+# Modified from python2.5/__init__.py
+class StreamHandlerNoNewline( logging.StreamHandler ):
+ """StreamHandler that doesn't print newlines by default.
+ Since StreamHandler automatically adds newlines, define a mod to more
+ easily support interactive mode when we want it, or errors-only logging
+ for running unit tests."""
+
+ def emit( self, record ):
+ """Emit a record.
+ If a formatter is specified, it is used to format the record.
+ The record is then written to the stream with a trailing newline
+ [ N.B. this may be removed depending on feedback ]. If exception
+ information is present, it is formatted using
+ traceback.printException and appended to the stream."""
+ try:
+ msg = self.format( record )
+ fs = '%s' # was '%s\n'
+ if not hasattr( types, 'UnicodeType' ): # if no unicode support...
+ self.stream.write( fs % msg )
+ else:
+ try:
+ self.stream.write( fs % msg )
+ except UnicodeError:
+ self.stream.write( fs % msg.encode( 'UTF-8' ) )
+ self.flush()
+ except ( KeyboardInterrupt, SystemExit ):
+ raise
+ except:
+ self.handleError( record )
+
+
+class Singleton( type ):
+ """Singleton pattern from Wikipedia
+ See http://en.wikipedia.org/wiki/SingletonPattern#Python
+
+ Intended to be used as a __metaclass_ param, as shown for the class
+ below.
+
+ Changed cls first args to mcs to satisfy pylint."""
+
+ def __init__( mcs, name, bases, dict_ ):
+ super( Singleton, mcs ).__init__( name, bases, dict_ )
+ mcs.instance = None
+
+ def __call__( mcs, *args, **kw ):
+ if mcs.instance is None:
+ mcs.instance = super( Singleton, mcs ).__call__( *args, **kw )
+ return mcs.instance
+
+
+class MininetLogger( Logger, object ):
+ """Mininet-specific logger
+ Enable each mininet .py file to with one import:
+
+ from mininet.log import [lg, info, error]
+
+ ...get a default logger that doesn't require one newline per logging
+ call.
+
+ Inherit from object to ensure that we have at least one new-style base
+ class, and can then use the __metaclass__ directive, to prevent this
+ error:
+
+ TypeError: Error when calling the metaclass bases
+ a new-style class can't have only classic bases
+
+ If Python2.5/logging/__init__.py defined Filterer as a new-style class,
+ via Filterer( object ): rather than Filterer, we wouldn't need this.
+
+ Use singleton pattern to ensure only one logger is ever created."""
+
+ __metaclass__ = Singleton
+
+ def __init__( self ):
+
+ Logger.__init__( self, "mininet" )
+
+ # create console handler
+ ch = StreamHandlerNoNewline()
+ # create formatter
+ formatter = logging.Formatter( LOGMSGFORMAT )
+ # add formatter to ch
+ ch.setFormatter( formatter )
+ # add ch to lg
+ self.addHandler( ch )
+
+ self.setLogLevel()
+
+ def setLogLevel( self, levelname=None ):
+ """Setup loglevel.
+ Convenience function to support lowercase names.
+ levelName: level name from LEVELS"""
+ level = LOGLEVELDEFAULT
+ if levelname is not None:
+ if levelname not in LEVELS:
+ raise Exception( 'unknown levelname seen in setLogLevel' )
+ else:
+ level = LEVELS.get( levelname, level )
+
+ self.setLevel( level )
+ self.handlers[ 0 ].setLevel( level )
+
+ # pylint: disable-msg=E0202
+ # "An attribute inherited from mininet.log hide this method"
+ # Not sure why this is occurring - this function definitely gets called.
+
+ # See /usr/lib/python2.5/logging/__init__.py; modified from warning()
+ def output( self, msg, *args, **kwargs ):
+ """Log 'msg % args' with severity 'OUTPUT'.
+
+ To pass exception information, use the keyword argument exc_info
+ with a true value, e.g.
+
+ logger.warning("Houston, we have a %s", "cli output", exc_info=1)
+ """
+ if self.manager.disable >= OUTPUT:
+ return
+ if self.isEnabledFor( OUTPUT ):
+ self._log( OUTPUT, msg, args, kwargs )
+
+ # pylint: enable-msg=E0202
+
+lg = MininetLogger()
+
+# Make things a bit more convenient by adding aliases
+# (info, warn, error, debug) and allowing info( 'this', 'is', 'OK' )
+# In the future we may wish to make things more efficient by only
+# doing the join (and calling the function) unless the logging level
+# is high enough.
+
+def makeListCompatible( fn ):
+ """Return a new function allowing fn( 'a 1 b' ) to be called as
+ newfn( 'a', 1, 'b' )"""
+
+ def newfn( *args ):
+ "Generated function. Closure-ish."
+ if len( args ) == 1:
+ return fn( *args )
+ args = ' '.join( [ str( arg ) for arg in args ] )
+ return fn( args )
+
+ # Fix newfn's name and docstring
+ setattr( newfn, '__name__', fn.__name__ )
+ setattr( newfn, '__doc__', fn.__doc__ )
+ return newfn
+
+info, output, warn, error, debug = (
+ lg.info, lg.output, lg.warn, lg.error, lg.debug ) = [
+ makeListCompatible( f ) for f in
+ lg.info, lg.output, lg.warn, lg.error, lg.debug ]
+
+setLogLevel = lg.setLogLevel
diff --git a/mininet/moduledeps.py b/mininet/moduledeps.py
new file mode 100644
index 0000000..862c1f6
--- /dev/null
+++ b/mininet/moduledeps.py
@@ -0,0 +1,68 @@
+"Module dependency utility functions for Mininet."
+
+from mininet.util import quietRun
+from mininet.log import info, error, debug
+from os import environ
+
+def lsmod():
+ "Return output of lsmod."
+ return quietRun( 'lsmod' )
+
+def rmmod( mod ):
+ """Return output of lsmod.
+ mod: module string"""
+ return quietRun( [ 'rmmod', mod ] )
+
+def modprobe( mod ):
+ """Return output of modprobe
+ mod: module string"""
+ return quietRun( [ 'modprobe', mod ] )
+
+OF_KMOD = 'ofdatapath'
+OVS_KMOD = 'openvswitch_mod' # Renamed 'openvswitch' in OVS 1.7+/Linux 3.5+
+TUN = 'tun'
+
+def moduleDeps( subtract=None, add=None ):
+ """Handle module dependencies.
+ subtract: string or list of module names to remove, if already loaded
+ add: string or list of module names to add, if not already loaded"""
+ subtract = subtract if subtract is not None else []
+ add = add if add is not None else []
+ if type( subtract ) is str:
+ subtract = [ subtract ]
+ if type( add ) is str:
+ add = [ add ]
+ for mod in subtract:
+ if mod in lsmod():
+ info( '*** Removing ' + mod + '\n' )
+ rmmodOutput = rmmod( mod )
+ if rmmodOutput:
+ error( 'Error removing ' + mod + ': "%s">\n' % rmmodOutput )
+ exit( 1 )
+ if mod in lsmod():
+ error( 'Failed to remove ' + mod + '; still there!\n' )
+ exit( 1 )
+ for mod in add:
+ if mod not in lsmod():
+ info( '*** Loading ' + mod + '\n' )
+ modprobeOutput = modprobe( mod )
+ if modprobeOutput:
+ error( 'Error inserting ' + mod +
+ ' - is it installed and available via modprobe?\n' +
+ 'Error was: "%s"\n' % modprobeOutput )
+ if mod not in lsmod():
+ error( 'Failed to insert ' + mod + ' - quitting.\n' )
+ exit( 1 )
+ else:
+ debug( '*** ' + mod + ' already loaded\n' )
+
+
+def pathCheck( *args, **kwargs ):
+ "Make sure each program in *args can be found in $PATH."
+ moduleName = kwargs.get( 'moduleName', 'it' )
+ for arg in args:
+ if not quietRun( 'which ' + arg ):
+ error( 'Cannot find required executable %s.\n' % arg +
+ 'Please make sure that %s is installed ' % moduleName +
+ 'and available in your $PATH:\n(%s)\n' % environ[ 'PATH' ] )
+ exit( 1 )
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' )
diff --git a/mininet/node.py b/mininet/node.py
new file mode 100644
index 0000000..4c54d75
--- /dev/null
+++ b/mininet/node.py
@@ -0,0 +1,1235 @@
+"""
+Node objects for Mininet.
+
+Nodes provide a simple abstraction for interacting with hosts, switches
+and controllers. Local nodes are simply one or more processes on the local
+machine.
+
+Node: superclass for all (primarily local) network nodes.
+
+Host: a virtual host. By default, a host is simply a shell; commands
+ may be sent using Cmd (which waits for output), or using sendCmd(),
+ which returns immediately, allowing subsequent monitoring using
+ monitor(). Examples of how to run experiments using this
+ functionality are provided in the examples/ directory.
+
+CPULimitedHost: a virtual host whose CPU bandwidth is limited by
+ RT or CFS bandwidth limiting.
+
+Switch: superclass for switch nodes.
+
+UserSwitch: a switch using the user-space switch from the OpenFlow
+ reference implementation.
+
+KernelSwitch: a switch using the kernel switch from the OpenFlow reference
+ implementation.
+
+OVSSwitch: a switch using the OpenVSwitch OpenFlow-compatible switch
+ implementation (openvswitch.org).
+
+Controller: superclass for OpenFlow controllers. The default controller
+ is controller(8) from the reference implementation.
+
+NOXController: a controller node using NOX (noxrepo.org).
+
+RemoteController: a remote controller node, which may use any
+ arbitrary OpenFlow-compatible controller, and which is not
+ created or managed by mininet.
+
+Future enhancements:
+
+- Possibly make Node, Switch and Controller more abstract so that
+ they can be used for both local and remote nodes
+
+- Create proxy objects for remote nodes (Mininet: Cluster Edition)
+"""
+
+import os
+import re
+import signal
+import select
+from subprocess import Popen, PIPE, STDOUT
+
+from mininet.log import info, error, warn, debug
+from mininet.util import ( quietRun, errRun, errFail, moveIntf, isShellBuiltin,
+ numCores, retry, mountCgroups )
+from mininet.moduledeps import moduleDeps, pathCheck, OVS_KMOD, OF_KMOD, TUN
+from mininet.link import Link, Intf, TCIntf
+
+class Node( object ):
+ """A virtual network node is simply a shell in a network namespace.
+ We communicate with it using pipes."""
+
+ portBase = 0 # Nodes always start with eth0/port0, even in OF 1.0
+
+ def __init__( self, name, inNamespace=True, **params ):
+ """name: name of node
+ inNamespace: in network namespace?
+ params: Node parameters (see config() for details)"""
+
+ # Make sure class actually works
+ self.checkSetup()
+
+ self.name = name
+ self.inNamespace = inNamespace
+
+ # Stash configuration parameters for future reference
+ self.params = params
+
+ self.intfs = {} # dict of port numbers to interfaces
+ self.ports = {} # dict of interfaces to port numbers
+ # replace with Port objects, eventually ?
+ self.nameToIntf = {} # dict of interface names to Intfs
+
+ # Make pylint happy
+ ( self.shell, self.execed, self.pid, self.stdin, self.stdout,
+ self.lastPid, self.lastCmd, self.pollOut ) = (
+ None, None, None, None, None, None, None, None )
+ self.waiting = False
+ self.readbuf = ''
+
+ # Start command interpreter shell
+ self.startShell()
+
+ # File descriptor to node mapping support
+ # Class variables and methods
+
+ inToNode = {} # mapping of input fds to nodes
+ outToNode = {} # mapping of output fds to nodes
+
+ @classmethod
+ def fdToNode( cls, fd ):
+ """Return node corresponding to given file descriptor.
+ fd: file descriptor
+ returns: node"""
+ node = cls.outToNode.get( fd )
+ return node or cls.inToNode.get( fd )
+
+ # Command support via shell process in namespace
+
+ def startShell( self ):
+ "Start a shell process for running commands"
+ if self.shell:
+ error( "%s: shell is already running" )
+ return
+ # mnexec: (c)lose descriptors, (d)etach from tty,
+ # (p)rint pid, and run in (n)amespace
+ opts = '-cdp'
+ if self.inNamespace:
+ opts += 'n'
+ # bash -m: enable job control
+ cmd = [ 'mnexec', opts, 'bash', '-m' ]
+ self.shell = Popen( cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT,
+ close_fds=True )
+ self.stdin = self.shell.stdin
+ self.stdout = self.shell.stdout
+ self.pid = self.shell.pid
+ self.pollOut = select.poll()
+ self.pollOut.register( self.stdout )
+ # Maintain mapping between file descriptors and nodes
+ # This is useful for monitoring multiple nodes
+ # using select.poll()
+ self.outToNode[ self.stdout.fileno() ] = self
+ self.inToNode[ self.stdin.fileno() ] = self
+ self.execed = False
+ self.lastCmd = None
+ self.lastPid = None
+ self.readbuf = ''
+ self.waiting = False
+
+ def cleanup( self ):
+ "Help python collect its garbage."
+ if not self.inNamespace:
+ for intfName in self.intfNames():
+ if self.name in intfName:
+ quietRun( 'ip link del ' + intfName )
+ self.shell = None
+
+ # Subshell I/O, commands and control
+
+ def read( self, maxbytes=1024 ):
+ """Buffered read from node, non-blocking.
+ maxbytes: maximum number of bytes to return"""
+ count = len( self.readbuf )
+ if count < maxbytes:
+ data = os.read( self.stdout.fileno(), maxbytes - count )
+ self.readbuf += data
+ if maxbytes >= len( self.readbuf ):
+ result = self.readbuf
+ self.readbuf = ''
+ else:
+ result = self.readbuf[ :maxbytes ]
+ self.readbuf = self.readbuf[ maxbytes: ]
+ return result
+
+ def readline( self ):
+ """Buffered readline from node, non-blocking.
+ returns: line (minus newline) or None"""
+ self.readbuf += self.read( 1024 )
+ if '\n' not in self.readbuf:
+ return None
+ pos = self.readbuf.find( '\n' )
+ line = self.readbuf[ 0: pos ]
+ self.readbuf = self.readbuf[ pos + 1: ]
+ return line
+
+ def write( self, data ):
+ """Write data to node.
+ data: string"""
+ os.write( self.stdin.fileno(), data )
+
+ def terminate( self ):
+ "Send kill signal to Node and clean up after it."
+ os.kill( self.pid, signal.SIGKILL )
+ self.cleanup()
+
+ def stop( self ):
+ "Stop node."
+ self.terminate()
+
+ def waitReadable( self, timeoutms=None ):
+ """Wait until node's output is readable.
+ timeoutms: timeout in ms or None to wait indefinitely."""
+ if len( self.readbuf ) == 0:
+ self.pollOut.poll( timeoutms )
+
+ def sendCmd( self, *args, **kwargs ):
+ """Send a command, followed by a command to echo a sentinel,
+ and return without waiting for the command to complete.
+ args: command and arguments, or string
+ printPid: print command's PID?"""
+ assert not self.waiting
+ printPid = kwargs.get( 'printPid', True )
+ # Allow sendCmd( [ list ] )
+ if len( args ) == 1 and type( args[ 0 ] ) is list:
+ cmd = args[ 0 ]
+ # Allow sendCmd( cmd, arg1, arg2... )
+ elif len( args ) > 0:
+ cmd = args
+ # Convert to string
+ if not isinstance( cmd, str ):
+ cmd = ' '.join( [ str( c ) for c in cmd ] )
+ if not re.search( r'\w', cmd ):
+ # Replace empty commands with something harmless
+ cmd = 'echo -n'
+ self.lastCmd = cmd
+ printPid = printPid and not isShellBuiltin( cmd )
+ if len( cmd ) > 0 and cmd[ -1 ] == '&':
+ # print ^A{pid}\n{sentinel}
+ cmd += ' printf "\\001%d\n\\177" $! \n'
+ else:
+ # print sentinel
+ cmd += '; printf "\\177"'
+ if printPid and not isShellBuiltin( cmd ):
+ cmd = 'mnexec -p ' + cmd
+ self.write( cmd + '\n' )
+ self.lastPid = None
+ self.waiting = True
+
+ def sendInt( self, sig=signal.SIGINT ):
+ "Interrupt running command."
+ if self.lastPid:
+ try:
+ os.kill( self.lastPid, sig )
+ except OSError:
+ pass
+
+ def monitor( self, timeoutms=None ):
+ """Monitor and return the output of a command.
+ Set self.waiting to False if command has completed.
+ timeoutms: timeout in ms or None to wait indefinitely."""
+ self.waitReadable( timeoutms )
+ data = self.read( 1024 )
+ # Look for PID
+ marker = chr( 1 ) + r'\d+\n'
+ if chr( 1 ) in data:
+ markers = re.findall( marker, data )
+ if markers:
+ self.lastPid = int( markers[ 0 ][ 1: ] )
+ data = re.sub( marker, '', data )
+ # Look for sentinel/EOF
+ if len( data ) > 0 and data[ -1 ] == chr( 127 ):
+ self.waiting = False
+ data = data[ :-1 ]
+ elif chr( 127 ) in data:
+ self.waiting = False
+ data = data.replace( chr( 127 ), '' )
+ return data
+
+ def waitOutput( self, verbose=False ):
+ """Wait for a command to complete.
+ Completion is signaled by a sentinel character, ASCII(127)
+ appearing in the output stream. Wait for the sentinel and return
+ the output, including trailing newline.
+ verbose: print output interactively"""
+ log = info if verbose else debug
+ output = ''
+ while self.waiting:
+ data = self.monitor()
+ output += data
+ log( data )
+ return output
+
+ def cmd( self, *args, **kwargs ):
+ """Send a command, wait for output, and return it.
+ cmd: string"""
+ verbose = kwargs.get( 'verbose', False )
+ log = info if verbose else debug
+ log( '*** %s : %s\n' % ( self.name, args ) )
+ self.sendCmd( *args, **kwargs )
+ return self.waitOutput( verbose )
+
+ def cmdPrint( self, *args):
+ """Call cmd and printing its output
+ cmd: string"""
+ return self.cmd( *args, **{ 'verbose': True } )
+
+ def popen( self, *args, **kwargs ):
+ """Return a Popen() object in our namespace
+ args: Popen() args, single list, or string
+ kwargs: Popen() keyword args"""
+ defaults = { 'stdout': PIPE, 'stderr': PIPE,
+ 'mncmd':
+ [ 'mnexec', '-a', str( self.pid ) ] }
+ defaults.update( kwargs )
+ if len( args ) == 1:
+ if type( args[ 0 ] ) is list:
+ # popen([cmd, arg1, arg2...])
+ cmd = args[ 0 ]
+ elif type( args[ 0 ] ) is str:
+ # popen("cmd arg1 arg2...")
+ cmd = args[ 0 ].split()
+ else:
+ raise Exception( 'popen() requires a string or list' )
+ elif len( args ) > 0:
+ # popen( cmd, arg1, arg2... )
+ cmd = list( args )
+ # Attach to our namespace using mnexec -a
+ mncmd = defaults[ 'mncmd' ]
+ del defaults[ 'mncmd' ]
+ cmd = mncmd + cmd
+ # Shell requires a string, not a list!
+ if defaults.get( 'shell', False ):
+ cmd = ' '.join( cmd )
+ return Popen( cmd, **defaults )
+
+ def pexec( self, *args, **kwargs ):
+ """Execute a command using popen
+ returns: out, err, exitcode"""
+ popen = self.popen( *args, **kwargs)
+ out, err = popen.communicate()
+ exitcode = popen.wait()
+ return out, err, exitcode
+
+ # Interface management, configuration, and routing
+
+ # BL notes: This might be a bit redundant or over-complicated.
+ # However, it does allow a bit of specialization, including
+ # changing the canonical interface names. It's also tricky since
+ # the real interfaces are created as veth pairs, so we can't
+ # make a single interface at a time.
+
+ def newPort( self ):
+ "Return the next port number to allocate."
+ if len( self.ports ) > 0:
+ return max( self.ports.values() ) + 1
+ return self.portBase
+
+ def addIntf( self, intf, port=None ):
+ """Add an interface.
+ intf: interface
+ port: port number (optional, typically OpenFlow port number)"""
+ if port is None:
+ port = self.newPort()
+ self.intfs[ port ] = intf
+ self.ports[ intf ] = port
+ self.nameToIntf[ intf.name ] = intf
+ debug( '\n' )
+ debug( 'added intf %s:%d to node %s\n' % ( intf, port, self.name ) )
+ if self.inNamespace:
+ debug( 'moving', intf, 'into namespace for', self.name, '\n' )
+ moveIntf( intf.name, self )
+
+ def defaultIntf( self ):
+ "Return interface for lowest port"
+ ports = self.intfs.keys()
+ if ports:
+ return self.intfs[ min( ports ) ]
+ else:
+ warn( '*** defaultIntf: warning:', self.name,
+ 'has no interfaces\n' )
+
+ def intf( self, intf='' ):
+ """Return our interface object with given string name,
+ default intf if name is falsy (None, empty string, etc).
+ or the input intf arg.
+
+ Having this fcn return its arg for Intf objects makes it
+ easier to construct functions with flexible input args for
+ interfaces (those that accept both string names and Intf objects).
+ """
+ if not intf:
+ return self.defaultIntf()
+ elif type( intf) is str:
+ return self.nameToIntf[ intf ]
+ else:
+ return intf
+
+ def connectionsTo( self, node):
+ "Return [ intf1, intf2... ] for all intfs that connect self to node."
+ # We could optimize this if it is important
+ connections = []
+ for intf in self.intfList():
+ link = intf.link
+ if link:
+ node1, node2 = link.intf1.node, link.intf2.node
+ if node1 == self and node2 == node:
+ connections += [ ( intf, link.intf2 ) ]
+ elif node1 == node and node2 == self:
+ connections += [ ( intf, link.intf1 ) ]
+ return connections
+
+ def deleteIntfs( self ):
+ "Delete all of our interfaces."
+ # In theory the interfaces should go away after we shut down.
+ # However, this takes time, so we're better off removing them
+ # explicitly so that we won't get errors if we run before they
+ # have been removed by the kernel. Unfortunately this is very slow,
+ # at least with Linux kernels before 2.6.33
+ for intf in self.intfs.values():
+ intf.delete()
+ info( '.' )
+
+ # Routing support
+
+ def setARP( self, ip, mac ):
+ """Add an ARP entry.
+ ip: IP address as string
+ mac: MAC address as string"""
+ result = self.cmd( 'arp', '-s', ip, mac )
+ return result
+
+ def setHostRoute( self, ip, intf ):
+ """Add route to host.
+ ip: IP address as dotted decimal
+ intf: string, interface name"""
+ return self.cmd( 'route add -host', ip, 'dev', intf )
+
+ def setDefaultRoute( self, intf=None ):
+ """Set the default route to go through intf.
+ intf: string, interface name"""
+ if not intf:
+ intf = self.defaultIntf()
+ self.cmd( 'ip route flush root 0/0' )
+ return self.cmd( 'route add default %s' % intf )
+
+ # Convenience and configuration methods
+
+ def setMAC( self, mac, intf=None ):
+ """Set the MAC address for an interface.
+ intf: intf or intf name
+ mac: MAC address as string"""
+ return self.intf( intf ).setMAC( mac )
+
+ def setIP( self, ip, prefixLen=8, intf=None ):
+ """Set the IP address for an interface.
+ intf: intf or intf name
+ ip: IP address as a string
+ prefixLen: prefix length, e.g. 8 for /8 or 16M addrs"""
+ # This should probably be rethought
+ if '/' not in ip:
+ ip = '%s/%s' % ( ip, prefixLen )
+ return self.intf( intf ).setIP( ip )
+
+ def IP( self, intf=None ):
+ "Return IP address of a node or specific interface."
+ return self.intf( intf ).IP()
+
+ def MAC( self, intf=None ):
+ "Return MAC address of a node or specific interface."
+ return self.intf( intf ).MAC()
+
+ def intfIsUp( self, intf=None ):
+ "Check if an interface is up."
+ return self.intf( intf ).isUp()
+
+ # The reason why we configure things in this way is so
+ # That the parameters can be listed and documented in
+ # the config method.
+ # Dealing with subclasses and superclasses is slightly
+ # annoying, but at least the information is there!
+
+ def setParam( self, results, method, **param ):
+ """Internal method: configure a *single* parameter
+ results: dict of results to update
+ method: config method name
+ param: arg=value (ignore if value=None)
+ value may also be list or dict"""
+ name, value = param.items()[ 0 ]
+ f = getattr( self, method, None )
+ if not f or value is None:
+ return
+ if type( value ) is list:
+ result = f( *value )
+ elif type( value ) is dict:
+ result = f( **value )
+ else:
+ result = f( value )
+ results[ name ] = result
+ return result
+
+ def config( self, mac=None, ip=None,
+ defaultRoute=None, lo='up', **_params ):
+ """Configure Node according to (optional) parameters:
+ mac: MAC address for default interface
+ ip: IP address for default interface
+ ifconfig: arbitrary interface configuration
+ Subclasses should override this method and call
+ the parent class's config(**params)"""
+ # If we were overriding this method, we would call
+ # the superclass config method here as follows:
+ # r = Parent.config( **_params )
+ r = {}
+ self.setParam( r, 'setMAC', mac=mac )
+ self.setParam( r, 'setIP', ip=ip )
+ self.setParam( r, 'defaultRoute', defaultRoute=defaultRoute )
+ # This should be examined
+ self.cmd( 'ifconfig lo ' + lo )
+ return r
+
+ def configDefault( self, **moreParams ):
+ "Configure with default parameters"
+ self.params.update( moreParams )
+ self.config( **self.params )
+
+ # This is here for backward compatibility
+ def linkTo( self, node, link=Link ):
+ """(Deprecated) Link to another node
+ replace with Link( node1, node2)"""
+ return link( self, node )
+
+ # Other methods
+
+ def intfList( self ):
+ "List of our interfaces sorted by port number"
+ return [ self.intfs[ p ] for p in sorted( self.intfs.iterkeys() ) ]
+
+ def intfNames( self ):
+ "The names of our interfaces sorted by port number"
+ return [ str( i ) for i in self.intfList() ]
+
+ def __repr__( self ):
+ "More informative string representation"
+ intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
+ for i in self.intfList() ] ) )
+ return '<%s %s: %s pid=%s> ' % (
+ self.__class__.__name__, self.name, intfs, self.pid )
+
+ def __str__( self ):
+ "Abbreviated string representation"
+ return self.name
+
+ # Automatic class setup support
+
+ isSetup = False
+
+ @classmethod
+ def checkSetup( cls ):
+ "Make sure our class and superclasses are set up"
+ while cls and not getattr( cls, 'isSetup', True ):
+ cls.setup()
+ cls.isSetup = True
+ # Make pylint happy
+ cls = getattr( type( cls ), '__base__', None )
+
+ @classmethod
+ def setup( cls ):
+ "Make sure our class dependencies are available"
+ pathCheck( 'mnexec', 'ifconfig', moduleName='Mininet')
+
+
+class Host( Node ):
+ "A host is simply a Node"
+ pass
+
+
+
+class CPULimitedHost( Host ):
+
+ "CPU limited host"
+
+ def __init__( self, name, sched='cfs', **kwargs ):
+ Host.__init__( self, name, **kwargs )
+ # Initialize class if necessary
+ if not CPULimitedHost.inited:
+ CPULimitedHost.init()
+ # Create a cgroup and move shell into it
+ self.cgroup = 'cpu,cpuacct,cpuset:/' + self.name
+ errFail( 'cgcreate -g ' + self.cgroup )
+ # We don't add ourselves to a cpuset because you must
+ # specify the cpu and memory placement first
+ errFail( 'cgclassify -g cpu,cpuacct:/%s %s' % ( self.name, self.pid ) )
+ # BL: Setting the correct period/quota is tricky, particularly
+ # for RT. RT allows very small quotas, but the overhead
+ # seems to be high. CFS has a mininimum quota of 1 ms, but
+ # still does better with larger period values.
+ self.period_us = kwargs.get( 'period_us', 100000 )
+ self.sched = sched
+ self.rtprio = 20
+
+ def cgroupSet( self, param, value, resource='cpu' ):
+ "Set a cgroup parameter and return its value"
+ cmd = 'cgset -r %s.%s=%s /%s' % (
+ resource, param, value, self.name )
+ quietRun( cmd )
+ nvalue = int( self.cgroupGet( param, resource ) )
+ if nvalue != value:
+ error( '*** error: cgroupSet: %s set to %s instead of %s\n'
+ % ( param, nvalue, value ) )
+ return nvalue
+
+ def cgroupGet( self, param, resource='cpu' ):
+ "Return value of cgroup parameter"
+ cmd = 'cgget -r %s.%s /%s' % (
+ resource, param, self.name )
+ return int( quietRun( cmd ).split()[ -1 ] )
+
+ def cgroupDel( self ):
+ "Clean up our cgroup"
+ # info( '*** deleting cgroup', self.cgroup, '\n' )
+ _out, _err, exitcode = errRun( 'cgdelete -r ' + self.cgroup )
+ return exitcode != 0
+
+ def popen( self, *args, **kwargs ):
+ """Return a Popen() object in node's namespace
+ args: Popen() args, single list, or string
+ kwargs: Popen() keyword args"""
+ # Tell mnexec to execute command in our cgroup
+ mncmd = [ 'mnexec', '-a', str( self.pid ),
+ '-g', self.name ]
+ if self.sched == 'rt':
+ mncmd += [ '-r', str( self.rtprio ) ]
+ return Host.popen( self, *args, mncmd=mncmd, **kwargs )
+
+ def cleanup( self ):
+ "Clean up our cgroup"
+ retry( retries=3, delaySecs=1, fn=self.cgroupDel )
+
+ def chrt( self ):
+ "Set RT scheduling priority"
+ quietRun( 'chrt -p %s %s' % ( self.rtprio, self.pid ) )
+ result = quietRun( 'chrt -p %s' % self.pid )
+ firstline = result.split( '\n' )[ 0 ]
+ lastword = firstline.split( ' ' )[ -1 ]
+ if lastword != 'SCHED_RR':
+ error( '*** error: could not assign SCHED_RR to %s\n' % self.name )
+ return lastword
+
+ def rtInfo( self, f ):
+ "Internal method: return parameters for RT bandwidth"
+ pstr, qstr = 'rt_period_us', 'rt_runtime_us'
+ # RT uses wall clock time for period and quota
+ quota = int( self.period_us * f * numCores() )
+ return pstr, qstr, self.period_us, quota
+
+ def cfsInfo( self, f):
+ "Internal method: return parameters for CFS bandwidth"
+ pstr, qstr = 'cfs_period_us', 'cfs_quota_us'
+ # CFS uses wall clock time for period and CPU time for quota.
+ quota = int( self.period_us * f * numCores() )
+ period = self.period_us
+ if f > 0 and quota < 1000:
+ debug( '(cfsInfo: increasing default period) ' )
+ quota = 1000
+ period = int( quota / f / numCores() )
+ return pstr, qstr, period, quota
+
+ # BL comment:
+ # This may not be the right API,
+ # since it doesn't specify CPU bandwidth in "absolute"
+ # units the way link bandwidth is specified.
+ # We should use MIPS or SPECINT or something instead.
+ # Alternatively, we should change from system fraction
+ # to CPU seconds per second, essentially assuming that
+ # all CPUs are the same.
+
+ def setCPUFrac( self, f=-1, sched=None):
+ """Set overall CPU fraction for this host
+ f: CPU bandwidth limit (fraction)
+ sched: 'rt' or 'cfs'
+ Note 'cfs' requires CONFIG_CFS_BANDWIDTH"""
+ if not f:
+ return
+ if not sched:
+ sched = self.sched
+ if sched == 'rt':
+ pstr, qstr, period, quota = self.rtInfo( f )
+ elif sched == 'cfs':
+ pstr, qstr, period, quota = self.cfsInfo( f )
+ else:
+ return
+ if quota < 0:
+ # Reset to unlimited
+ quota = -1
+ # Set cgroup's period and quota
+ self.cgroupSet( pstr, period )
+ self.cgroupSet( qstr, quota )
+ if sched == 'rt':
+ # Set RT priority if necessary
+ self.chrt()
+ info( '(%s %d/%dus) ' % ( sched, quota, period ) )
+
+ def setCPUs( self, cores, mems=0 ):
+ "Specify (real) cores that our cgroup can run on"
+ if type( cores ) is list:
+ cores = ','.join( [ str( c ) for c in cores ] )
+ self.cgroupSet( resource='cpuset', param='cpus',
+ value=cores )
+ # Memory placement is probably not relevant, but we
+ # must specify it anyway
+ self.cgroupSet( resource='cpuset', param='mems',
+ value=mems)
+ # We have to do this here after we've specified
+ # cpus and mems
+ errFail( 'cgclassify -g cpuset:/%s %s' % (
+ self.name, self.pid ) )
+
+ def config( self, cpu=None, cores=None, **params ):
+ """cpu: desired overall system CPU fraction
+ cores: (real) core(s) this host can run on
+ params: parameters for Node.config()"""
+ r = Node.config( self, **params )
+ # Was considering cpu={'cpu': cpu , 'sched': sched}, but
+ # that seems redundant
+
+ self.setParam( r, 'setCPUFrac', cpu=cpu )
+ self.setParam( r, 'setCPUs', cores=cores )
+
+ return r
+
+ inited = False
+
+ @classmethod
+ def init( cls ):
+ "Initialization for CPULimitedHost class"
+ mountCgroups()
+ cls.inited = True
+
+class CCNHost( Host ):
+ "CCNHost is a Host that always runs the ccnd daemon"
+
+ def __init__( self, name, **kwargs ):
+
+
+ Host.__init__( self, name, **kwargs )
+ if not CCNHost.inited:
+ CCNHost.init()
+
+ self.cmd("export CCN_LOCAL_SOCKNAME=/tmp/.sock.ccnx.{0}".format(self.name))
+ self.cmd("ccndstart")
+ self.peerList = {}
+
+ def config( self, fib=None, app=None, **params ):
+
+ r = Node.config( self, **params )
+
+ self.setParam( r, 'app', fib=fib )
+ self.setParam( r, 'fib', app=app)
+
+ return r
+
+ def configCCN(self):
+
+ self.buildPeerIP()
+ self.setFIB()
+
+ def buildPeerIP(self):
+ for iface in self.intfList():
+ link = iface.link
+ if link:
+ node1, node2 = link.intf1.node, link.intf2.node
+ if node1 == self:
+ self.peerList[node2.name] = link.intf2.node.IP(link.intf2)
+ else:
+ self.peerList[node1.name] = link.intf1.node.IP(link.intf1)
+
+
+ def setFIB(self):
+
+ for name in self.params['fib']:
+ if not name:
+ pass
+ else:
+ self.insert_fib(name[0],self.peerList[name[1]])
+
+
+ def insert_fib(self, uri, host):
+ self.cmd('ccndc add {0} tcp {1}'.format(uri,host))
+
+ def terminate( self ):
+ "Stop node."
+ self.cmd('ccndstop')
+ Host.terminate(self)
+
+ inited = False
+
+
+ @classmethod
+ def init( cls ):
+ "Initialization for CCNHost class"
+ cls.inited = True
+
+class CPULimitedCCNHost( CPULimitedHost ):
+ '''CPULimitedCCNHost is a Host that always runs the ccnd daemon and extends CPULimitedHost.
+ It should be used when one wants to limit the resources of CCN routers and hosts '''
+
+
+ def __init__( self, name, sched='cfs', **kwargs ):
+
+ CPULimitedHost.__init__( self, name, sched, **kwargs )
+ if not CCNHost.inited:
+ CCNHost.init()
+
+ self.cmd("export CCN_LOCAL_SOCKNAME=/tmp/.sock.ccnx.{0}".format(self.name))
+ self.cmd("ccndstart")
+ self.peerList = {}
+
+ def config( self, fib=None, app=None, cpu=None, cores=None, **params):
+
+ r = CPULimitedHost.config(self,cpu,cores, **params)
+
+ print 'nozess'
+ self.setParam( r, 'app', fib=fib )
+ self.setParam( r, 'fib', app=app)
+
+ return r
+
+ def configCCN(self):
+
+ self.buildPeerIP()
+ self.setFIB()
+
+ def buildPeerIP(self):
+ for iface in self.intfList():
+ link = iface.link
+ if link:
+ node1, node2 = link.intf1.node, link.intf2.node
+ if node1 == self:
+ self.peerList[node2.name] = link.intf2.node.IP(link.intf2)
+ else:
+ self.peerList[node1.name] = link.intf1.node.IP(link.intf1)
+
+
+ def setFIB(self):
+
+ for name in self.params['fib']:
+ if not name:
+ pass
+ else:
+ self.insert_fib(name[0],self.peerList[name[1]])
+
+
+ def insert_fib(self, uri, host):
+ self.cmd('ccndc add {0} tcp {1}'.format(uri,host))
+
+ def terminate( self ):
+ "Stop node."
+ self.cmd('ccndstop')
+ Host.terminate(self)
+
+ inited = False
+
+
+ @classmethod
+ def init( cls ):
+ "Initialization for CCNHost class"
+ cls.inited = True
+
+# Some important things to note:
+#
+# The "IP" address which setIP() assigns to the switch is not
+# an "IP address for the switch" in the sense of IP routing.
+# Rather, it is the IP address for the control interface,
+# on the control network, and it is only relevant to the
+# controller. If you are running in the root namespace
+# (which is the only way to run OVS at the moment), the
+# control interface is the loopback interface, and you
+# normally never want to change its IP address!
+#
+# In general, you NEVER want to attempt to use Linux's
+# network stack (i.e. ifconfig) to "assign" an IP address or
+# MAC address to a switch data port. Instead, you "assign"
+# the IP and MAC addresses in the controller by specifying
+# packets that you want to receive or send. The "MAC" address
+# reported by ifconfig for a switch data port is essentially
+# meaningless. It is important to understand this if you
+# want to create a functional router using OpenFlow.
+
+class Switch( Node ):
+ """A Switch is a Node that is running (or has execed?)
+ an OpenFlow switch."""
+
+ portBase = 1 # Switches start with port 1 in OpenFlow
+ dpidLen = 16 # digits in dpid passed to switch
+
+ def __init__( self, name, dpid=None, opts='', listenPort=None, **params):
+ """dpid: dpid for switch (or None to derive from name, e.g. s1 -> 1)
+ opts: additional switch options
+ listenPort: port to listen on for dpctl connections"""
+ Node.__init__( self, name, **params )
+ self.dpid = dpid if dpid else self.defaultDpid()
+ self.opts = opts
+ self.listenPort = listenPort
+ if not self.inNamespace:
+ self.controlIntf = Intf( 'lo', self, port=0 )
+
+ def defaultDpid( self ):
+ "Derive dpid from switch name, s1 -> 1"
+ try:
+ dpid = int( re.findall( '\d+', self.name )[ 0 ] )
+ dpid = hex( dpid )[ 2: ]
+ dpid = '0' * ( self.dpidLen - len( dpid ) ) + dpid
+ return dpid
+ except IndexError:
+ raise Exception( 'Unable to derive default datapath ID - '
+ 'please either specify a dpid or use a '
+ 'canonical switch name such as s23.' )
+
+ def defaultIntf( self ):
+ "Return control interface"
+ if self.controlIntf:
+ return self.controlIntf
+ else:
+ return Node.defaultIntf( self )
+
+ def sendCmd( self, *cmd, **kwargs ):
+ """Send command to Node.
+ cmd: string"""
+ kwargs.setdefault( 'printPid', False )
+ if not self.execed:
+ return Node.sendCmd( self, *cmd, **kwargs )
+ else:
+ error( '*** Error: %s has execed and cannot accept commands' %
+ self.name )
+
+ def __repr__( self ):
+ "More informative string representation"
+ intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
+ for i in self.intfList() ] ) )
+ return '<%s %s: %s pid=%s> ' % (
+ self.__class__.__name__, self.name, intfs, self.pid )
+
+class UserSwitch( Switch ):
+ "User-space switch."
+
+ dpidLen = 12
+
+ def __init__( self, name, **kwargs ):
+ """Init.
+ name: name for the switch"""
+ Switch.__init__( self, name, **kwargs )
+ pathCheck( 'ofdatapath', 'ofprotocol',
+ moduleName='the OpenFlow reference user switch' +
+ '(openflow.org)' )
+ if self.listenPort:
+ self.opts += ' --listen=ptcp:%i ' % self.listenPort
+
+ @classmethod
+ def setup( cls ):
+ "Ensure any dependencies are loaded; if not, try to load them."
+ if not os.path.exists( '/dev/net/tun' ):
+ moduleDeps( add=TUN )
+
+ def dpctl( self, *args ):
+ "Run dpctl command"
+ if not self.listenPort:
+ return "can't run dpctl without passive listening port"
+ return self.cmd( 'dpctl ' + ' '.join( args ) +
+ ' tcp:127.0.0.1:%i' % self.listenPort )
+
+ def start( self, controllers ):
+ """Start OpenFlow reference user datapath.
+ Log to /tmp/sN-{ofd,ofp}.log.
+ controllers: list of controller objects"""
+ # Add controllers
+ clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
+ for c in controllers ] )
+ ofdlog = '/tmp/' + self.name + '-ofd.log'
+ ofplog = '/tmp/' + self.name + '-ofp.log'
+ self.cmd( 'ifconfig lo up' )
+ intfs = [ str( i ) for i in self.intfList() if not i.IP() ]
+ self.cmd( 'ofdatapath -i ' + ','.join( intfs ) +
+ ' punix:/tmp/' + self.name + ' -d ' + self.dpid +
+ ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' )
+ self.cmd( 'ofprotocol unix:/tmp/' + self.name +
+ ' ' + clist +
+ ' --fail=closed ' + self.opts +
+ ' 1> ' + ofplog + ' 2>' + ofplog + ' &' )
+
+ def stop( self ):
+ "Stop OpenFlow reference user datapath."
+ self.cmd( 'kill %ofdatapath' )
+ self.cmd( 'kill %ofprotocol' )
+ self.deleteIntfs()
+
+
+class OVSLegacyKernelSwitch( Switch ):
+ """Open VSwitch legacy kernel-space switch using ovs-openflowd.
+ Currently only works in the root namespace."""
+
+ def __init__( self, name, dp=None, **kwargs ):
+ """Init.
+ name: name for switch
+ dp: netlink id (0, 1, 2, ...)
+ defaultMAC: default MAC as unsigned int; random value if None"""
+ Switch.__init__( self, name, **kwargs )
+ self.dp = dp if dp else self.name
+ self.intf = self.dp
+ if self.inNamespace:
+ error( "OVSKernelSwitch currently only works"
+ " in the root namespace.\n" )
+ exit( 1 )
+
+ @classmethod
+ def setup( cls ):
+ "Ensure any dependencies are loaded; if not, try to load them."
+ pathCheck( 'ovs-dpctl', 'ovs-openflowd',
+ moduleName='Open vSwitch (openvswitch.org)')
+ moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
+
+ def start( self, controllers ):
+ "Start up 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
+ self.cmd( 'ovs-dpctl del-dp ' + self.dp )
+ self.cmd( 'ovs-dpctl add-dp ' + self.dp )
+ intfs = [ str( i ) for i in self.intfList() if not i.IP() ]
+ self.cmd( 'ovs-dpctl', 'add-if', self.dp, ' '.join( intfs ) )
+ # Run protocol daemon
+ clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
+ for c in controllers ] )
+ self.cmd( 'ovs-openflowd ' + self.dp +
+ ' ' + clist +
+ ' --fail=secure ' + self.opts +
+ ' --datapath-id=' + self.dpid +
+ ' 1>' + ofplog + ' 2>' + ofplog + '&' )
+ self.execed = False
+
+ def stop( self ):
+ "Terminate kernel datapath."
+ quietRun( 'ovs-dpctl del-dp ' + self.dp )
+ self.cmd( 'kill %ovs-openflowd' )
+ self.deleteIntfs()
+
+
+class OVSSwitch( Switch ):
+ "Open vSwitch switch. Depends on ovs-vsctl."
+
+ def __init__( self, name, failMode='secure', **params ):
+ """Init.
+ name: name for switch
+ failMode: controller loss behavior (secure|open)"""
+ Switch.__init__( self, name, **params )
+ self.failMode = failMode
+
+ @classmethod
+ def setup( cls ):
+ "Make sure Open vSwitch is installed and working"
+ pathCheck( 'ovs-vsctl',
+ moduleName='Open vSwitch (openvswitch.org)')
+ # This should no longer be needed, and it breaks
+ # with OVS 1.7 which has renamed the kernel module:
+ # moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
+ out, err, exitcode = errRun( 'ovs-vsctl -t 1 show' )
+ if exitcode:
+ error( out + err +
+ 'ovs-vsctl exited with code %d\n' % exitcode +
+ '*** Error connecting to ovs-db with ovs-vsctl\n'
+ 'Make sure that Open vSwitch is installed, '
+ 'that ovsdb-server is running, and that\n'
+ '"ovs-vsctl show" works correctly.\n'
+ 'You may wish to try '
+ '"service openvswitch-switch start".\n' )
+ exit( 1 )
+
+ def dpctl( self, *args ):
+ "Run ovs-dpctl command"
+ return self.cmd( 'ovs-dpctl', args[ 0 ], self, *args[ 1: ] )
+
+ @staticmethod
+ def TCReapply( intf ):
+ """Unfortunately OVS and Mininet are fighting
+ over tc queuing disciplines. As a quick hack/
+ workaround, we clear OVS's and reapply our own."""
+ if type( intf ) is TCIntf:
+ intf.config( **intf.params )
+
+ def attach( self, intf ):
+ "Connect a data port"
+ self.cmd( 'ovs-vsctl add-port', self, intf )
+ self.cmd( 'ifconfig', intf, 'up' )
+ self.TCReapply( intf )
+
+ def detach( self, intf ):
+ "Disconnect a data port"
+ self.cmd( 'ovs-vsctl del-port', self, intf )
+
+ def start( self, controllers ):
+ "Start up a new OVS OpenFlow switch using ovs-vsctl"
+ if self.inNamespace:
+ raise Exception(
+ 'OVS kernel switch does not work in a namespace' )
+ # We should probably call config instead, but this
+ # requires some rethinking...
+ self.cmd( 'ifconfig lo up' )
+ # Annoyingly, --if-exists option seems not to work
+ self.cmd( 'ovs-vsctl del-br', self )
+ self.cmd( 'ovs-vsctl add-br', self )
+ self.cmd( 'ovs-vsctl -- set Bridge', self,
+ 'other_config:datapath-id=' + self.dpid )
+ self.cmd( 'ovs-vsctl set-fail-mode', self, self.failMode )
+ for intf in self.intfList():
+ if not intf.IP():
+ self.attach( intf )
+ # Add controllers
+ clist = ' '.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
+ for c in controllers ] )
+ if self.listenPort:
+ clist += ' ptcp:%s' % self.listenPort
+ self.cmd( 'ovs-vsctl set-controller', self, clist )
+
+ def stop( self ):
+ "Terminate OVS switch."
+ self.cmd( 'ovs-vsctl del-br', self )
+ self.deleteIntfs()
+
+OVSKernelSwitch = OVSSwitch
+
+
+class Controller( Node ):
+ """A Controller is a Node that is running (or has execed?) an
+ OpenFlow controller."""
+
+ def __init__( self, name, inNamespace=False, command='controller',
+ cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1",
+ port=6633, **params ):
+ self.command = command
+ self.cargs = cargs
+ self.cdir = cdir
+ self.ip = ip
+ self.port = port
+ Node.__init__( self, name, inNamespace=inNamespace,
+ ip=ip, **params )
+ self.cmd( 'ifconfig lo up' ) # Shouldn't be necessary
+ self.checkListening()
+
+ def checkListening( self ):
+ "Make sure no controllers are running on our port"
+ # Verify that Telnet is installed first:
+ out, _err, returnCode = errRun( "which telnet" )
+ if 'telnet' not in out or returnCode != 0:
+ raise Exception( "Error running telnet to check for listening "
+ "controllers; please check that it is "
+ "installed." )
+ listening = self.cmd( "echo A | telnet -e A %s %d" %
+ ( self.ip, self.port ) )
+ if 'Unable' not in listening:
+ servers = self.cmd( 'netstat -atp' ).split( '\n' )
+ pstr = ':%d ' % self.port
+ clist = servers[ 0:1 ] + [ s for s in servers if pstr in s ]
+ raise Exception( "Please shut down the controller which is"
+ " running on port %d:\n" % self.port +
+ '\n'.join( clist ) )
+
+ def start( self ):
+ """Start <controller> <args> on controller.
+ Log to /tmp/cN.log"""
+ pathCheck( self.command )
+ cout = '/tmp/' + self.name + '.log'
+ if self.cdir is not None:
+ self.cmd( 'cd ' + self.cdir )
+ self.cmd( self.command + ' ' + self.cargs % self.port +
+ ' 1>' + cout + ' 2>' + cout + '&' )
+ self.execed = False
+
+ def stop( self ):
+ "Stop controller."
+ self.cmd( 'kill %' + self.command )
+ self.terminate()
+
+ def IP( self, intf=None ):
+ "Return IP address of the Controller"
+ if self.intfs:
+ ip = Node.IP( self, intf )
+ else:
+ ip = self.ip
+ return ip
+
+ def __repr__( self ):
+ "More informative string representation"
+ return '<%s %s: %s:%s pid=%s> ' % (
+ self.__class__.__name__, self.name,
+ self.IP(), self.port, self.pid )
+
+
+class OVSController( Controller ):
+ "Open vSwitch controller"
+ def __init__( self, name, command='ovs-controller', **kwargs ):
+ Controller.__init__( self, name, command=command, **kwargs )
+
+
+class NOX( Controller ):
+ "Controller to run a NOX application."
+
+ def __init__( self, name, *noxArgs, **kwargs ):
+ """Init.
+ name: name to give controller
+ noxArgs: arguments (strings) to pass to NOX"""
+ if not noxArgs:
+ warn( 'warning: no NOX modules specified; '
+ 'running packetdump only\n' )
+ noxArgs = [ 'packetdump' ]
+ elif type( noxArgs ) not in ( list, tuple ):
+ noxArgs = [ noxArgs ]
+
+ if 'NOX_CORE_DIR' not in os.environ:
+ exit( 'exiting; please set missing NOX_CORE_DIR env var' )
+ noxCoreDir = os.environ[ 'NOX_CORE_DIR' ]
+
+ Controller.__init__( self, name,
+ command=noxCoreDir + '/nox_core',
+ cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' +
+ ' '.join( noxArgs ),
+ cdir=noxCoreDir,
+ **kwargs )
+
+
+class RemoteController( Controller ):
+ "Controller running outside of Mininet's control."
+
+ def __init__( self, name, ip='127.0.0.1',
+ port=6633, **kwargs):
+ """Init.
+ name: name to give controller
+ ip: the IP address where the remote controller is
+ listening
+ port: the port where the remote controller is listening"""
+ Controller.__init__( self, name, ip=ip, port=port, **kwargs )
+
+ def start( self ):
+ "Overridden to do nothing."
+ return
+
+ def stop( self ):
+ "Overridden to do nothing."
+ return
+
+ def checkListening( self ):
+ "Warn if remote controller is not accessible"
+ listening = self.cmd( "echo A | telnet -e A %s %d" %
+ ( self.ip, self.port ) )
+ if 'Unable' in listening:
+ warn( "Unable to contact the remote controller"
+ " at %s:%d\n" % ( self.ip, self.port ) )
diff --git a/mininet/term.py b/mininet/term.py
new file mode 100644
index 0000000..3cd70f2
--- /dev/null
+++ b/mininet/term.py
@@ -0,0 +1,60 @@
+"""
+Terminal creation and cleanup.
+Utility functions to run a term (connected via screen(1)) on each host.
+
+Requires GNU screen(1) and xterm(1).
+Optionally uses gnome-terminal.
+"""
+
+import re
+from subprocess import Popen
+
+from mininet.log import error
+from mininet.util import quietRun
+
+def quoteArg( arg ):
+ "Quote an argument if it contains spaces."
+ return repr( arg ) if ' ' in arg else arg
+
+def makeTerm( node, title='Node', term='xterm' ):
+ """Run screen on a node, and hook up a terminal.
+ node: Node object
+ title: base title
+ term: 'xterm' or 'gterm'
+ returns: process created"""
+ title += ': ' + node.name
+ if not node.inNamespace:
+ title += ' (root)'
+ cmds = {
+ 'xterm': [ 'xterm', '-title', title, '-e' ],
+ 'gterm': [ 'gnome-terminal', '--title', title, '-e' ]
+ }
+ if term not in cmds:
+ error( 'invalid terminal type: %s' % term )
+ return
+ if not node.execed:
+ node.cmd( 'screen -dmS ' + 'mininet.' + node.name)
+ args = [ 'screen', '-D', '-RR', '-S', 'mininet.' + node.name ]
+ else:
+ args = [ 'sh', '-c', 'exec tail -f /tmp/' + node.name + '*.log' ]
+ if term == 'gterm':
+ # Compress these for gnome-terminal, which expects one token
+ # to follow the -e option
+ args = [ ' '.join( [ quoteArg( arg ) for arg in args ] ) ]
+ return Popen( cmds[ term ] + args )
+
+def cleanUpScreens():
+ "Remove moldy old screen sessions."
+ r = r'(\d+\.mininet\.[hsc]\d+)'
+ output = quietRun( 'screen -ls' ).split( '\n' )
+ for line in output:
+ m = re.search( r, line )
+ if m:
+ quietRun( 'screen -S ' + m.group( 1 ) + ' -X quit' )
+
+def makeTerms( nodes, title='Node', term='xterm' ):
+ """Create terminals.
+ nodes: list of Node objects
+ title: base title for each
+ returns: list of created terminal processes"""
+ return [ makeTerm( node, title, term ) for node in nodes ]
diff --git a/mininet/test/test_hifi.py b/mininet/test/test_hifi.py
new file mode 100644
index 0000000..ace7bb5
--- /dev/null
+++ b/mininet/test/test_hifi.py
@@ -0,0 +1,127 @@
+#!/usr/bin/env python
+
+"""Package: mininet
+ Test creation and pings for topologies with link and/or CPU options."""
+
+import unittest
+
+from mininet.net import Mininet
+from mininet.node import OVSKernelSwitch
+from mininet.node import CPULimitedHost
+from mininet.link import TCLink
+from mininet.topo import Topo
+from mininet.log import setLogLevel
+
+
+SWITCH = OVSKernelSwitch
+# Number of hosts for each test
+N = 2
+
+
+class SingleSwitchOptionsTopo(Topo):
+ "Single switch connected to n hosts."
+ def __init__(self, n=2, hopts=None, lopts=None):
+ if not hopts:
+ hopts = {}
+ if not lopts:
+ lopts = {}
+ Topo.__init__(self, hopts=hopts, lopts=lopts)
+ switch = self.addSwitch('s1')
+ for h in range(n):
+ host = self.addHost('h%s' % (h + 1))
+ self.addLink(host, switch)
+
+
+class testOptionsTopo( unittest.TestCase ):
+ "Verify ability to create networks with host and link options."
+
+ def runOptionsTopoTest( self, n, hopts=None, lopts=None ):
+ "Generic topology-with-options test runner."
+ mn = Mininet( topo=SingleSwitchOptionsTopo( n=n, hopts=hopts,
+ lopts=lopts ),
+ host=CPULimitedHost, link=TCLink )
+ dropped = mn.run( mn.ping )
+ self.assertEqual( dropped, 0 )
+
+ def assertWithinTolerance(self, measured, expected, tolerance_frac):
+ """Check that a given value is within a tolerance of expected
+ tolerance_frac: less-than-1.0 value; 0.8 would yield 20% tolerance.
+ """
+ self.assertTrue( float(measured) >= float(expected) * tolerance_frac )
+ self.assertTrue( float(measured) >= float(expected) * tolerance_frac )
+
+ def testCPULimits( self ):
+ "Verify topology creation with CPU limits set for both schedulers."
+ CPU_FRACTION = 0.1
+ CPU_TOLERANCE = 0.8 # CPU fraction below which test should fail
+ hopts = { 'cpu': CPU_FRACTION }
+ #self.runOptionsTopoTest( N, hopts=hopts )
+
+ mn = Mininet( SingleSwitchOptionsTopo( n=N, hopts=hopts ),
+ host=CPULimitedHost )
+ mn.start()
+ results = mn.runCpuLimitTest( cpu=CPU_FRACTION )
+ mn.stop()
+ for cpu in results:
+ self.assertWithinTolerance( cpu, CPU_FRACTION, CPU_TOLERANCE )
+
+ def testLinkBandwidth( self ):
+ "Verify that link bandwidths are accurate within a bound."
+ BW = 5 # Mbps
+ BW_TOLERANCE = 0.8 # BW fraction below which test should fail
+ # Verify ability to create limited-link topo first;
+ lopts = { 'bw': BW, 'use_htb': True }
+ # Also verify correctness of limit limitng within a bound.
+ mn = Mininet( SingleSwitchOptionsTopo( n=N, lopts=lopts ),
+ link=TCLink )
+ bw_strs = mn.run( mn.iperf )
+ for bw_str in bw_strs:
+ bw = float( bw_str.split(' ')[0] )
+ self.assertWithinTolerance( bw, BW, BW_TOLERANCE )
+
+ def testLinkDelay( self ):
+ "Verify that link delays are accurate within a bound."
+ DELAY_MS = 15
+ DELAY_TOLERANCE = 0.8 # Delay fraction below which test should fail
+ lopts = { 'delay': '%sms' % DELAY_MS, 'use_htb': True }
+ mn = Mininet( SingleSwitchOptionsTopo( n=N, lopts=lopts ),
+ link=TCLink )
+ ping_delays = mn.run( mn.pingFull )
+ test_outputs = ping_delays[0]
+ # Ignore unused variables below
+ # pylint: disable-msg=W0612
+ node, dest, ping_outputs = test_outputs
+ sent, received, rttmin, rttavg, rttmax, rttdev = ping_outputs
+ self.assertEqual( sent, received )
+ # pylint: enable-msg=W0612
+ for rttval in [rttmin, rttavg, rttmax]:
+ # Multiply delay by 4 to cover there & back on two links
+ self.assertWithinTolerance( rttval, DELAY_MS * 4.0,
+ DELAY_TOLERANCE)
+
+ def testLinkLoss( self ):
+ "Verify that we see packet drops with a high configured loss rate."
+ LOSS_PERCENT = 99
+ REPS = 1
+ lopts = { 'loss': LOSS_PERCENT, 'use_htb': True }
+ mn = Mininet( topo=SingleSwitchOptionsTopo( n=N, lopts=lopts ),
+ host=CPULimitedHost, link=TCLink )
+ # Drops are probabilistic, but the chance of no dropped packets is
+ # 1 in 100 million with 4 hops for a link w/99% loss.
+ dropped_total = 0
+ mn.start()
+ for _ in range(REPS):
+ dropped_total += mn.ping(timeout='1')
+ mn.stop()
+ self.assertTrue(dropped_total > 0)
+
+ def testMostOptions( self ):
+ "Verify topology creation with most link options and CPU limits."
+ lopts = { 'bw': 10, 'delay': '5ms', 'use_htb': True }
+ hopts = { 'cpu': 0.5 / N }
+ self.runOptionsTopoTest( N, hopts=hopts, lopts=lopts )
+
+
+if __name__ == '__main__':
+ setLogLevel( 'warning' )
+ unittest.main()
diff --git a/mininet/test/test_nets.py b/mininet/test/test_nets.py
new file mode 100644
index 0000000..fde8e87
--- /dev/null
+++ b/mininet/test/test_nets.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python
+
+"""Package: mininet
+ Test creation and all-pairs ping for each included mininet topo type."""
+
+import unittest
+
+from mininet.net import Mininet
+from mininet.node import Host, Controller
+from mininet.node import UserSwitch, OVSKernelSwitch
+from mininet.topo import SingleSwitchTopo, LinearTopo
+from mininet.log import setLogLevel
+
+SWITCHES = { 'user': UserSwitch,
+ 'ovsk': OVSKernelSwitch,
+}
+
+
+class testSingleSwitch( unittest.TestCase ):
+ "For each datapath type, test ping with single switch topologies."
+
+ def testMinimal( self ):
+ "Ping test with both datapaths on minimal topology"
+ for switch in SWITCHES.values():
+ mn = Mininet( SingleSwitchTopo(), switch, Host, Controller )
+ dropped = mn.run( mn.ping )
+ self.assertEqual( dropped, 0 )
+
+ def testSingle5( self ):
+ "Ping test with both datapaths on 5-host single-switch topology"
+ for switch in SWITCHES.values():
+ mn = Mininet( SingleSwitchTopo( k=5 ), switch, Host, Controller )
+ dropped = mn.run( mn.ping )
+ self.assertEqual( dropped, 0 )
+
+
+class testLinear( unittest.TestCase ):
+ "For each datapath type, test all-pairs ping with LinearNet."
+
+ def testLinear5( self ):
+ "Ping test with both datapaths on a 5-switch topology"
+ for switch in SWITCHES.values():
+ mn = Mininet( LinearTopo( k=5 ), switch, Host, Controller )
+ dropped = mn.run( mn.ping )
+ self.assertEqual( dropped, 0 )
+
+
+if __name__ == '__main__':
+ setLogLevel( 'warning' )
+ unittest.main()
diff --git a/mininet/topo.py b/mininet/topo.py
new file mode 100644
index 0000000..fff9604
--- /dev/null
+++ b/mininet/topo.py
@@ -0,0 +1,230 @@
+#!/usr/bin/env python
+'''@package topo
+
+Network topology creation.
+
+@author Brandon Heller (brandonh@stanford.edu)
+
+This package includes code to represent network topologies.
+
+A Topo object can be a topology database for NOX, can represent a physical
+setup for testing, and can even be emulated with the Mininet package.
+'''
+
+# BL: we may have to fix compatibility here.
+# networkx is also a fairly heavyweight dependency
+# from networkx.classes.graph import Graph
+
+from networkx import Graph
+from mininet.util import irange, natural, naturalSeq
+
+class Topo(object):
+ "Data center network representation for structured multi-trees."
+
+ def __init__(self, hopts=None, sopts=None, lopts=None):
+ """Topo object:
+ hinfo: default host options
+ sopts: default switch options
+ lopts: default link options"""
+ self.g = Graph()
+ self.node_info = {}
+ self.link_info = {} # (src, dst) tuples hash to EdgeInfo objects
+ self.hopts = {} if hopts is None else hopts
+ self.sopts = {} if sopts is None else sopts
+ self.lopts = {} if lopts is None else lopts
+ self.ports = {} # ports[src][dst] is port on src that connects to dst
+
+ def addNode(self, name, **opts):
+ """Add Node to graph.
+ name: name
+ opts: node options
+ returns: node name"""
+ self.g.add_node(name)
+ self.node_info[name] = opts
+ return name
+
+ def addHost(self, name, **opts):
+ """Convenience method: Add host to graph.
+ name: host name
+ opts: host options
+ returns: host name"""
+ if not opts and self.hopts:
+ opts = self.hopts
+ return self.addNode(name, **opts)
+
+ def addSwitch(self, name, **opts):
+ """Convenience method: Add switch to graph.
+ name: switch name
+ opts: switch options
+ returns: switch name"""
+ if not opts and self.sopts:
+ opts = self.sopts
+ result = self.addNode(name, isSwitch=True, **opts)
+ return result
+
+ def addLink(self, node1, node2, port1=None, port2=None,
+ **opts):
+ """node1, node2: nodes to link together
+ port1, port2: ports (optional)
+ opts: link options (optional)
+ returns: link info key"""
+ if not opts and self.lopts:
+ opts = self.lopts
+ self.addPort(node1, node2, port1, port2)
+ key = tuple(self.sorted([node1, node2]))
+ self.link_info[key] = opts
+ self.g.add_edge(*key)
+ return key
+
+ def addPort(self, src, dst, sport=None, dport=None):
+ '''Generate port mapping for new edge.
+ @param src source switch name
+ @param dst destination switch name
+ '''
+ self.ports.setdefault(src, {})
+ self.ports.setdefault(dst, {})
+ # New port: number of outlinks + base
+ src_base = 1 if self.isSwitch(src) else 0
+ dst_base = 1 if self.isSwitch(dst) else 0
+ if sport is None:
+ sport = len(self.ports[src]) + src_base
+ if dport is None:
+ dport = len(self.ports[dst]) + dst_base
+ self.ports[src][dst] = sport
+ self.ports[dst][src] = dport
+
+ def nodes(self, sort=True):
+ "Return nodes in graph"
+ if sort:
+ return self.sorted( self.g.nodes() )
+ else:
+ return self.g.nodes()
+
+ def isSwitch(self, n):
+ '''Returns true if node is a switch.'''
+ info = self.node_info[n]
+ return info and info.get('isSwitch', False)
+
+ def switches(self, sort=True):
+ '''Return switches.
+ sort: sort switches alphabetically
+ @return dpids list of dpids
+ '''
+ return [n for n in self.nodes(sort) if self.isSwitch(n)]
+
+ def hosts(self, sort=True):
+ '''Return hosts.
+ sort: sort hosts alphabetically
+ @return dpids list of dpids
+ '''
+ return [n for n in self.nodes(sort) if not self.isSwitch(n)]
+
+ def links(self, sort=True):
+ '''Return links.
+ sort: sort links alphabetically
+ @return links list of name pairs
+ '''
+ if not sort:
+ return self.g.edges()
+ else:
+ links = [tuple(self.sorted(e)) for e in self.g.edges()]
+ return sorted( links, key=naturalSeq )
+
+ def port(self, src, dst):
+ '''Get port number.
+
+ @param src source switch name
+ @param dst destination switch name
+ @return tuple (src_port, dst_port):
+ src_port: port on source switch leading to the destination switch
+ dst_port: port on destination switch leading to the source switch
+ '''
+ if src in self.ports and dst in self.ports[src]:
+ assert dst in self.ports and src in self.ports[dst]
+ return (self.ports[src][dst], self.ports[dst][src])
+
+ def linkInfo( self, src, dst ):
+ "Return link metadata"
+ src, dst = self.sorted([src, dst])
+ return self.link_info[(src, dst)]
+
+ def setlinkInfo( self, src, dst, info ):
+ "Set link metadata"
+ src, dst = self.sorted([src, dst])
+ self.link_info[(src, dst)] = info
+
+ def nodeInfo( self, name ):
+ "Return metadata (dict) for node"
+ info = self.node_info[ name ]
+ return info if info is not None else {}
+
+ def setNodeInfo( self, name, info ):
+ "Set metadata (dict) for node"
+ self.node_info[ name ] = info
+
+ @staticmethod
+ def sorted( items ):
+ "Items sorted in natural (i.e. alphabetical) order"
+ return sorted(items, key=natural)
+
+class SingleSwitchTopo(Topo):
+ '''Single switch connected to k hosts.'''
+
+ def __init__(self, k=2, **opts):
+ '''Init.
+
+ @param k number of hosts
+ @param enable_all enables all nodes and switches?
+ '''
+ super(SingleSwitchTopo, self).__init__(**opts)
+
+ self.k = k
+
+ switch = self.addSwitch('s1')
+ for h in irange(1, k):
+ host = self.addHost('h%s' % h)
+ self.addLink(host, switch)
+
+
+class SingleSwitchReversedTopo(Topo):
+ '''Single switch connected to k hosts, with reversed ports.
+
+ The lowest-numbered host is connected to the highest-numbered port.
+
+ Useful to verify that Mininet properly handles custom port numberings.
+ '''
+ def __init__(self, k=2, **opts):
+ '''Init.
+
+ @param k number of hosts
+ @param enable_all enables all nodes and switches?
+ '''
+ super(SingleSwitchReversedTopo, self).__init__(**opts)
+ self.k = k
+ switch = self.addSwitch('s1')
+ for h in irange(1, k):
+ host = self.addHost('h%s' % h)
+ self.addLink(host, switch,
+ port1=0, port2=(k - h + 1))
+
+class LinearTopo(Topo):
+ "Linear topology of k switches, with one host per switch."
+
+ def __init__(self, k=2, **opts):
+ """Init.
+ k: number of switches (and hosts)
+ hconf: host configuration options
+ lconf: link configuration options"""
+
+ super(LinearTopo, self).__init__(**opts)
+
+ self.k = k
+
+ lastSwitch = None
+ for i in irange(1, k):
+ host = self.addHost('h%s' % i)
+ switch = self.addSwitch('s%s' % i)
+ self.addLink( host, switch)
+ if lastSwitch:
+ self.addLink( switch, lastSwitch)
+ lastSwitch = switch
diff --git a/mininet/topolib.py b/mininet/topolib.py
new file mode 100644
index 0000000..63ba36d
--- /dev/null
+++ b/mininet/topolib.py
@@ -0,0 +1,36 @@
+"Library of potentially useful topologies for Mininet"
+
+from mininet.topo import Topo
+from mininet.net import Mininet
+
+class TreeTopo( Topo ):
+ "Topology for a tree network with a given depth and fanout."
+
+ def __init__( self, depth=1, fanout=2 ):
+ super( TreeTopo, self ).__init__()
+ # Numbering: h1..N, s1..M
+ self.hostNum = 1
+ self.switchNum = 1
+ # Build topology
+ self.addTree( depth, fanout )
+
+ def addTree( self, depth, fanout ):
+ """Add a subtree starting with node n.
+ returns: last node added"""
+ isSwitch = depth > 0
+ if isSwitch:
+ node = self.addSwitch( 's%s' % self.switchNum )
+ self.switchNum += 1
+ for _ in range( fanout ):
+ child = self.addTree( depth - 1, fanout )
+ self.addLink( node, child )
+ else:
+ node = self.addHost( 'h%s' % self.hostNum )
+ self.hostNum += 1
+ return node
+
+
+def TreeNet( depth=1, fanout=2, **kwargs ):
+ "Convenience function for creating tree networks."
+ topo = TreeTopo( depth, fanout )
+ return Mininet( topo, **kwargs )
diff --git a/mininet/util.py b/mininet/util.py
new file mode 100644
index 0000000..6a3dc14
--- /dev/null
+++ b/mininet/util.py
@@ -0,0 +1,473 @@
+"Utility functions for Mininet."
+
+from mininet.log import output, info, error, warn
+
+from time import sleep
+from resource import setrlimit, RLIMIT_NPROC, RLIMIT_NOFILE
+from select import poll, POLLIN
+from subprocess import call, check_call, Popen, PIPE, STDOUT
+import re
+from fcntl import fcntl, F_GETFL, F_SETFL
+from os import O_NONBLOCK
+import os
+
+# Command execution support
+
+def run( cmd ):
+ """Simple interface to subprocess.call()
+ cmd: list of command params"""
+ return call( cmd.split( ' ' ) )
+
+def checkRun( cmd ):
+ """Simple interface to subprocess.check_call()
+ cmd: list of command params"""
+ return check_call( cmd.split( ' ' ) )
+
+# pylint doesn't understand explicit type checking
+# pylint: disable-msg=E1103
+
+def oldQuietRun( *cmd ):
+ """Run a command, routing stderr to stdout, and return the output.
+ cmd: list of command params"""
+ if len( cmd ) == 1:
+ cmd = cmd[ 0 ]
+ if isinstance( cmd, str ):
+ cmd = cmd.split( ' ' )
+ popen = Popen( cmd, stdout=PIPE, stderr=STDOUT )
+ # We can't use Popen.communicate() because it uses
+ # select(), which can't handle
+ # high file descriptor numbers! poll() can, however.
+ out = ''
+ readable = poll()
+ readable.register( popen.stdout )
+ while True:
+ while readable.poll():
+ data = popen.stdout.read( 1024 )
+ if len( data ) == 0:
+ break
+ out += data
+ popen.poll()
+ if popen.returncode is not None:
+ break
+ return out
+
+
+# This is a bit complicated, but it enables us to
+# monitor command output as it is happening
+
+def errRun( *cmd, **kwargs ):
+ """Run a command and return stdout, stderr and return code
+ cmd: string or list of command and args
+ stderr: STDOUT to merge stderr with stdout
+ shell: run command using shell
+ echo: monitor output to console"""
+ # Allow passing in a list or a string
+ if len( cmd ) == 1:
+ cmd = cmd[ 0 ]
+ if isinstance( cmd, str ):
+ cmd = cmd.split( ' ' )
+ cmd = [ str( arg ) for arg in cmd ]
+ # By default we separate stderr, don't run in a shell, and don't echo
+ stderr = kwargs.get( 'stderr', PIPE )
+ shell = kwargs.get( 'shell', False )
+ echo = kwargs.get( 'echo', False )
+ if echo:
+ # cmd goes to stderr, output goes to stdout
+ info( cmd, '\n' )
+ popen = Popen( cmd, stdout=PIPE, stderr=stderr, shell=shell )
+ # We use poll() because select() doesn't work with large fd numbers,
+ # and thus communicate() doesn't work either
+ out, err = '', ''
+ poller = poll()
+ poller.register( popen.stdout, POLLIN )
+ fdtofile = { popen.stdout.fileno(): popen.stdout }
+ outDone, errDone = False, True
+ if popen.stderr:
+ fdtofile[ popen.stderr.fileno() ] = popen.stderr
+ poller.register( popen.stderr, POLLIN )
+ errDone = False
+ while not outDone or not errDone:
+ readable = poller.poll()
+ for fd, _event in readable:
+ f = fdtofile[ fd ]
+ data = f.read( 1024 )
+ if echo:
+ output( data )
+ if f == popen.stdout:
+ out += data
+ if data == '':
+ outDone = True
+ elif f == popen.stderr:
+ err += data
+ if data == '':
+ errDone = True
+ returncode = popen.wait()
+ return out, err, returncode
+
+def errFail( *cmd, **kwargs ):
+ "Run a command using errRun and raise exception on nonzero exit"
+ out, err, ret = errRun( *cmd, **kwargs )
+ if ret:
+ raise Exception( "errFail: %s failed with return code %s: %s"
+ % ( cmd, ret, err ) )
+ return out, err, ret
+
+def quietRun( cmd, **kwargs ):
+ "Run a command and return merged stdout and stderr"
+ return errRun( cmd, stderr=STDOUT, **kwargs )[ 0 ]
+
+# pylint: enable-msg=E1103
+# pylint: disable-msg=E1101
+
+def isShellBuiltin( cmd ):
+ "Return True if cmd is a bash builtin."
+ if isShellBuiltin.builtIns is None:
+ isShellBuiltin.builtIns = quietRun( 'bash -c enable' )
+ space = cmd.find( ' ' )
+ if space > 0:
+ cmd = cmd[ :space]
+ return cmd in isShellBuiltin.builtIns
+
+isShellBuiltin.builtIns = None
+
+# pylint: enable-msg=E1101
+
+# Interface management
+#
+# 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 connecting intf1 and intf2.
+ intf1: string, interface
+ intf2: string, interface
+ returns: success boolean"""
+ # Delete any old interfaces with the same names
+ quietRun( 'ip link del ' + intf1 )
+ quietRun( 'ip link del ' + intf2 )
+ # Create new pair
+ cmd = 'ip link add name ' + intf1 + ' type veth peer name ' + intf2
+ return quietRun( cmd )
+
+def retry( retries, delaySecs, fn, *args, **keywords ):
+ """Try something several times before giving up.
+ n: number of times to retry
+ delaySecs: wait this long between tries
+ fn: function to call
+ args: args to apply to function call"""
+ tries = 0
+ while not fn( *args, **keywords ) and tries < retries:
+ sleep( delaySecs )
+ tries += 1
+ if tries >= retries:
+ error( "*** gave up after %i retries\n" % tries )
+ exit( 1 )
+
+def moveIntfNoRetry( intf, node, printError=False ):
+ """Move interface to node, without retrying.
+ intf: string, interface
+ node: Node object
+ printError: if true, print error"""
+ cmd = 'ip link set ' + intf + ' netns ' + repr( node.pid )
+ quietRun( cmd )
+ links = node.cmd( 'ip link show' )
+ if not ( ' %s:' % intf ) in links:
+ if printError:
+ error( '*** Error: moveIntf: ' + intf +
+ ' not successfully moved to ' + node.name + '\n' )
+ return False
+ return True
+
+def moveIntf( intf, node, printError=False, retries=3, delaySecs=0.001 ):
+ """Move interface to node, retrying on failure.
+ intf: string, interface
+ node: Node object
+ printError: if true, print error"""
+ retry( retries, delaySecs, moveIntfNoRetry, intf, node, printError )
+
+# Support for dumping network
+
+def dumpNodeConnections( nodes ):
+ "Dump connections to/from nodes."
+
+ def dumpConnections( node ):
+ "Helper function: dump connections to node"
+ for intf in node.intfList():
+ output( ' %s:' % intf )
+ if intf.link:
+ intfs = [ intf.link.intf1, intf.link.intf2 ]
+ intfs.remove( intf )
+ output( intfs[ 0 ] )
+ else:
+ output( ' ' )
+
+ for node in nodes:
+ output( node.name )
+ dumpConnections( node )
+ output( '\n' )
+
+def dumpNetConnections( net ):
+ "Dump connections in network"
+ nodes = net.controllers + net.switches + net.hosts
+ dumpNodeConnections( nodes )
+
+# IP and Mac address formatting and parsing
+
+def _colonHex( val, bytecount ):
+ """Generate colon-hex string.
+ val: input as unsigned int
+ bytecount: number of bytes to convert
+ returns: chStr colon-hex string"""
+ pieces = []
+ for i in range( bytecount - 1, -1, -1 ):
+ piece = ( ( 0xff << ( i * 8 ) ) & val ) >> ( i * 8 )
+ pieces.append( '%02x' % piece )
+ chStr = ':'.join( pieces )
+ return chStr
+
+def macColonHex( mac ):
+ """Generate MAC colon-hex string from unsigned int.
+ mac: MAC address as unsigned int
+ returns: macStr MAC colon-hex string"""
+ return _colonHex( mac, 6 )
+
+def ipStr( ip ):
+ """Generate IP address string from an unsigned int.
+ ip: unsigned int of form w << 24 | x << 16 | y << 8 | z
+ returns: ip address string w.x.y.z, or 10.x.y.z if w==0"""
+ w = ( ip >> 24 ) & 0xff
+ w = 10 if w == 0 else w
+ x = ( ip >> 16 ) & 0xff
+ y = ( ip >> 8 ) & 0xff
+ z = ip & 0xff
+ return "%i.%i.%i.%i" % ( w, x, y, z )
+
+def ipNum( w, x, y, z ):
+ """Generate unsigned int from components of IP address
+ returns: w << 24 | x << 16 | y << 8 | z"""
+ return ( w << 24 ) | ( x << 16 ) | ( y << 8 ) | z
+
+def nextCCNnet(curCCNnet):
+ netNum = ipParse(curCCNnet)
+ return ipStr(netNum+4)
+
+def ipAdd( i, prefixLen=8, ipBaseNum=0x0a000000 ):
+ """Return IP address string from ints
+ i: int to be added to ipbase
+ prefixLen: optional IP prefix length
+ ipBaseNum: option base IP address as int
+ returns IP address as string"""
+ # Ugly but functional
+ assert i < ( 1 << ( 32 - prefixLen ) )
+ mask = 0xffffffff ^ ( ( 1 << prefixLen ) - 1 )
+ ipnum = i + ( ipBaseNum & mask )
+ return ipStr( ipnum )
+
+def ipParse( ip ):
+ "Parse an IP address and return an unsigned int."
+ args = [ int( arg ) for arg in ip.split( '.' ) ]
+ return ipNum( *args )
+
+def netParse( ipstr ):
+ """Parse an IP network specification, returning
+ address and prefix len as unsigned ints"""
+ prefixLen = 0
+ if '/' in ipstr:
+ ip, pf = ipstr.split( '/' )
+ prefixLen = int( pf )
+ return ipParse( ip ), prefixLen
+
+def checkInt( s ):
+ "Check if input string is an int"
+ try:
+ int( s )
+ return True
+ except ValueError:
+ return False
+
+def checkFloat( s ):
+ "Check if input string is a float"
+ try:
+ float( s )
+ return True
+ except ValueError:
+ return False
+
+def makeNumeric( s ):
+ "Convert string to int or float if numeric."
+ if checkInt( s ):
+ return int( s )
+ elif checkFloat( s ):
+ return float( s )
+ else:
+ return s
+
+# Popen support
+
+def pmonitor(popens, timeoutms=500, readline=True,
+ readmax=1024 ):
+ """Monitor dict of hosts to popen objects
+ a line at a time
+ timeoutms: timeout for poll()
+ readline: return single line of output
+ yields: host, line/output (if any)
+ terminates: when all EOFs received"""
+ poller = poll()
+ fdToHost = {}
+ for host, popen in popens.iteritems():
+ fd = popen.stdout.fileno()
+ fdToHost[ fd ] = host
+ poller.register( fd, POLLIN )
+ if not readline:
+ # Use non-blocking reads
+ flags = fcntl( fd, F_GETFL )
+ fcntl( fd, F_SETFL, flags | O_NONBLOCK )
+ while True:
+ fds = poller.poll( timeoutms )
+ if fds:
+ for fd, _event in fds:
+ host = fdToHost[ fd ]
+ popen = popens[ host ]
+ if readline:
+ # Attempt to read a line of output
+ # This blocks until we receive a newline!
+ line = popen.stdout.readline()
+ else:
+ line = popen.stdout.read( readmax )
+ yield host, line
+ # Check for EOF
+ if not line:
+ popen.poll()
+ if popen.returncode is not None:
+ poller.unregister( fd )
+ del popens[ host ]
+ if not popens:
+ return
+ else:
+ yield None, ''
+
+# Other stuff we use
+
+def fixLimits():
+ "Fix ridiculously small resource limits."
+ setrlimit( RLIMIT_NPROC, ( 8192, 8192 ) )
+ setrlimit( RLIMIT_NOFILE, ( 16384, 16384 ) )
+
+def mountCgroups():
+ "Make sure cgroups file system is mounted"
+ mounts = quietRun( 'mount' )
+ cgdir = '/sys/fs/cgroup'
+ csdir = cgdir + '/cpuset'
+ if ('cgroup on %s' % cgdir not in mounts and
+ 'cgroups on %s' % cgdir not in mounts):
+ raise Exception( "cgroups not mounted on " + cgdir )
+ if 'cpuset on %s' % csdir not in mounts:
+ errRun( 'mkdir -p ' + csdir )
+ errRun( 'mount -t cgroup -ocpuset cpuset ' + csdir )
+
+def natural( text ):
+ "To sort sanely/alphabetically: sorted( l, key=natural )"
+ def num( s ):
+ "Convert text segment to int if necessary"
+ return int( s ) if s.isdigit() else s
+ return [ num( s ) for s in re.split( r'(\d+)', text ) ]
+
+def naturalSeq( t ):
+ "Natural sort key function for sequences"
+ return [ natural( x ) for x in t ]
+
+def numCores():
+ "Returns number of CPU cores based on /proc/cpuinfo"
+ if hasattr( numCores, 'ncores' ):
+ return numCores.ncores
+ try:
+ numCores.ncores = int( quietRun('grep -c processor /proc/cpuinfo') )
+ except ValueError:
+ return 0
+ return numCores.ncores
+
+def irange(start, end):
+ """Inclusive range from start to end (vs. Python insanity.)
+ irange(1,5) -> 1, 2, 3, 4, 5"""
+ return range( start, end + 1 )
+
+def custom( cls, **params ):
+ "Returns customized constructor for class cls."
+ # Note: we may wish to see if we can use functools.partial() here
+ # and in customConstructor
+ def customized( *args, **kwargs):
+ "Customized constructor"
+ kwargs = kwargs.copy()
+ kwargs.update( params )
+ return cls( *args, **kwargs )
+ customized.__name__ = 'custom(%s,%s)' % ( cls, params )
+ return customized
+
+def splitArgs( argstr ):
+ """Split argument string into usable python arguments
+ argstr: argument string with format fn,arg2,kw1=arg3...
+ returns: fn, args, kwargs"""
+ split = argstr.split( ',' )
+ fn = split[ 0 ]
+ params = split[ 1: ]
+ # Convert int and float args; removes the need for function
+ # to be flexible with input arg formats.
+ args = [ makeNumeric( s ) for s in params if '=' not in s ]
+ kwargs = {}
+ for s in [ p for p in params if '=' in p ]:
+ key, val = s.split( '=' )
+ kwargs[ key ] = makeNumeric( val )
+ return fn, args, kwargs
+
+def customConstructor( constructors, argStr ):
+ """Return custom constructor based on argStr
+ The args and key/val pairs in argsStr will be automatically applied
+ when the generated constructor is later used.
+ """
+ cname, newargs, kwargs = splitArgs( argStr )
+ constructor = constructors.get( cname, None )
+
+ if not constructor:
+ raise Exception( "error: %s is unknown - please specify one of %s" %
+ ( cname, constructors.keys() ) )
+
+ def customized( name, *args, **params ):
+ "Customized constructor, useful for Node, Link, and other classes"
+ params = params.copy()
+ params.update( kwargs )
+ if not newargs:
+ return constructor( name, *args, **params )
+ if args:
+ warn( 'warning: %s replacing %s with %s\n' % (
+ constructor, args, newargs ) )
+ return constructor( name, *newargs, **params )
+
+ customized.__name__ = 'customConstructor(%s)' % argStr
+ return customized
+
+def buildTopo( topos, topoStr ):
+ """Create topology from string with format (object, arg1, arg2,...).
+ input topos is a dict of topo names to constructors, possibly w/args.
+ """
+ topo, args, kwargs = splitArgs( topoStr )
+ if topo not in topos:
+ raise Exception( 'Invalid topo name %s' % topo )
+ return topos[ topo ]( *args, **kwargs )
+
+def ensureRoot():
+ """Ensure that we are running as root.
+
+ Probably we should only sudo when needed as per Big Switch's patch.
+ """
+ if os.getuid() != 0:
+ print "*** Mininet must run as root."
+ exit( 1 )
+ return