diff --git a/INSTALL b/INSTALL index 9168bd7..d3e4695 100644 --- a/INSTALL +++ b/INSTALL @@ -1,117 +1,123 @@ Mininet Installation/Configuration Notes +---------------------------------------- Mininet 2.0.0 - --- -The supported installation methods for Mininet are 1) using -a pre-built VM image, and 2) native installation on Ubuntu. You -can also easily create your own Mininet VM image (3). +The supported installation methods for Mininet are 1) using a +pre-built VM image, and 2) native installation on Ubuntu. You can also +easily create your own Mininet VM image (4). (Other distributions may be supported in the future - if you would like to contribute an installation script, we would welcome it!) 1. Easiest "installation" - use our pre-built VM image! -The easiest way to get Mininet running is to start with one of our pre-built -virtual machine images from http://openflow.org/mininet + The easiest way to get Mininet running is to start with one of our + pre-built virtual machine images from -Boot up the VM image, log in, and follow the instructions on the wiki page. + Boot up the VM image, log in, and follow the instructions on the + Mininet web site. -One advantage of using the VM image is that it doesn't mess with -your native OS installation or damage it in any way. + One advantage of using the VM image is that it doesn't mess with + your native OS installation or damage it in any way. -Although a single Mininet instance can simulate multiple networks with -multiple controllers, only one Mininet instance may currently be run at -a time, and Mininet requires root access in the machine it's running on. -Therefore, if you have a multiuser system, you may wish to consider -running Mininet in a VM. + Although a single Mininet instance can simulate multiple networks + with multiple controllers, only one Mininet instance may currently + be run at a time, and Mininet requires root access in the machine + it's running on. Therefore, if you have a multiuser system, you + may wish to consider running Mininet in a VM. 2. Next-easiest option: use our Ubuntu package! -To install Mininet itself (i.e. mn and the Python API) on Ubuntu 12.10+ + To install Mininet itself (i.e. `mn` and the Python API) on Ubuntu + 12.10+: - sudo apt-get install mininet + sudo apt-get install mininet -Note: if you are upgrading from an older version of Mininet, make sure you -remove the old OVS from /usr/local: + Note: if you are upgrading from an older version of Mininet, make + sure you remove the old OVS from `/usr/local`: - sudo rm /usr/local/bin/ovs* - sudo rm /usr/local/sbin/ovs* + sudo rm /usr/local/bin/ovs* + sudo rm /usr/local/sbin/ovs* 3. Native installation from source on Ubuntu 11.10+ -If you're reading this, you've probably already done so, but the command to -download the Mininet source code is; - - git clone git://openflow.org/mininet.git + If you're reading this, you've probably already done so, but the + command to download the Mininet source code is: -If you are running Ubuntu, you may be able to use our handy install.sh script, -which is in mininet/util. + git clone git://github.com/mininet/mininet.git -WARNING: USE AT YOUR OWN RISK! + If you are running Ubuntu, you may be able to use our handy + `install.sh` script, which is in `mininet/util`. -install.sh is a bit intrusive and may possibly damage your OS and/or -home directory, by creating/modifying several directories such as -mininet, openflow, openvswitch and noxcore. Although we hope it won't -do anything completely terrible, you may want to look at the script -before you run it, and you should make sure your system and home -directory are backed up just in case! + *WARNING: USE AT YOUR OWN RISK!* -To install Mininet itself, the OpenFlow reference implementation, and -Open vSwitch, you may use: + `install.sh` is a bit intrusive and may possibly damage your OS + and/or home directory, by creating/modifying several directories + such as `mininet`, `openflow`, `oftest`, `pox`, or `noxcosre`. + Although we hope it won't do anything completely terrible, you may + want to look at the script before you run it, and you should make + sure your system and home directory are backed up just in case! -$ mininet/util/install.sh -fnv + To install Mininet itself, the OpenFlow reference implementation, and + Open vSwitch, you may use: -This should be reasonably quick and the following command should work -after the installation: + mininet/util/install.sh -fnv -$ sudo mn --test pingall + This should be reasonably quick, and the following command should + work after the installation: -To install ALL of the software which we use for OpenFlow tutorials, -including NOX classic, the OpenFlow WireShark dissector, the oftest -framework, and other potentially useful software (and to add some stuff -to /etc/sysctl.conf which may or may not be useful) you may use + sudo mn --test pingall -$ mininet/util/install.sh -a + To install ALL of the software which we use for OpenFlow tutorials, + including POX, the OpenFlow WireShark dissector, the `oftest` + framework, and other potentially useful software (and to add some + stuff to `/etc/sysctl.conf` which may or may not be useful) you may + use: -This takes about 20 minutes on our test system. + mininet/util/install.sh -a + + This takes about 4 minutes on our test system. 4. Creating your own Mininet/OpenFlow tutorial VM -Creating your own Ubuntu Mininet VM for use with the OpenFlow tutorial -is easy! First, create a new Ubuntu VM. Then, run + Creating your own Ubuntu Mininet VM for use with the OpenFlow tutorial + is easy! First, create a new Ubuntu VM. Next, run two commands in it: -$ wget https://raw.github.com/mininet/mininet/util/vm/install-mininet-vm.sh -$ time install-mininet-vm.sh + wget https://raw.github.com/mininet/mininet/master/util/vm/install-mininet-vm.sh + time install-mininet-vm.sh + + Finally, verify that Mininet is installed and working in the VM: + + sudo mn --test pingall 5. Installation on other Linux distributions -Although we don't support other Linux distributions directly, it should be -possible to install and run Mininet with some degree of manual effort. + Although we don't support other Linux distributions directly, it + should be possible to install and run Mininet with some degree of + manual effort. -In general, you must have: + In general, you must have: -* A Linux kernel compiled with network namespace support enabled + * A Linux kernel compiled with network namespace support enabled -* An OpenFlow implementation (either the reference user or kernel - space implementations, or Open vSwitch.) Appropriate kernel modules - (e.g. tun and ofdatapath for the reference kernel implementation) must - be loaded. + * An OpenFlow implementation (either the reference user or kernel + space implementations, or Open vSwitch.) Appropriate kernel + modules (e.g. tun and ofdatapath for the reference kernel + implementation) must be loaded. -* Python, `bash`, `ping`, `iperf`, etc. + * Python, `bash`, `ping`, `iperf`, etc.` -* Root privileges (required for network device access) + * Root privileges (required for network device access) + + We encourage contribution of patches to the `install.sh` script to + support other Linux distributions. -We encourage contribution of patches to the `install.sh` script to support -other Linux distributions. Good luck! Mininet Team --- - - - diff --git a/bin/mn b/bin/mn index 0fb5bae..bba2353 100755 --- a/bin/mn +++ b/bin/mn @@ -71,8 +71,7 @@ TESTS = [ 'cli', 'build', 'pingall', 'pingpair', 'iperf', 'all', 'iperfudp', ALTSPELLING = { 'pingall': 'pingAll', 'pingpair': 'pingPair', 'iperfudp': 'iperfUdp', - 'iperfUDP': 'iperfUdp', - 'prefixlen': 'prefixLen' } + 'iperfUDP': 'iperfUdp' } def addDictOption( opts, choicesDict, default, name, helpStr=None ): @@ -190,9 +189,6 @@ class MininetRunner( object ): help='CLI script to run before tests' ) opts.add_option( '--post', type='string', default=None, help='CLI script to run after tests' ) - opts.add_option( '--prefixlen', type='int', default=8, - help='prefix length (e.g. /8) for automatic ' - 'network configuration' ) opts.add_option( '--pin', action='store_true', default=False, help="pin hosts to CPU cores " "(requires --host cfs or --host rt)" ) @@ -200,6 +196,11 @@ class MininetRunner( object ): self.options, self.args = opts.parse_args() + # We don't accept extra arguments after the options + if self.args: + opts.print_help() + exit() + def setup( self ): "Setup and validate environment." diff --git a/debian/control b/debian/control index fb289dc..e2297ae 100644 --- a/debian/control +++ b/debian/control @@ -9,20 +9,18 @@ Build-Depends: help2man, python-dev, python-pkg-resources, - python-setuptools, - python-networkx + python-setuptools Homepage: http://openflow.org/mininet Package: mininet Architecture: any Depends: openvswitch-switch, - python-networkx, telnet, ${misc:Depends}, ${python:Depends}, ${shlibs:Depends} -Recommends: iperf, openvswitch-controller +Recommends: iperf, openvswitch-controller, socat Description: Process-based network emulator Mininet is a network emulator which uses lightweight virtualization to create virtual networks for rapid diff --git a/examples/README b/examples/README index dd71bea..841486e 100644 --- a/examples/README +++ b/examples/README @@ -19,7 +19,19 @@ graphical monitoring. controllers.py: -This example creates a network and adds multiple controllers to it. +This example creates a network with multiple controllers, by +using a custom Switch() subclass. + +controllers2.py: + +This example creates a network with multiple controllers by +creating an empty network, adding nodes to it, and manually +starting the switches. + +controlnet.py: + +This examples shows how you can model the control network as well +as the data network, by actually creating two Mininet objects. cpu.py: @@ -61,6 +73,12 @@ multitest.py: This example creates a network and runs multiple tests on it. +nat.py: + +This example shows how to connect a Mininet network to the Internet +using NAT. It also answers the eternal question "why can't I ping +google?" + popen.py: This example monitors a number of hosts using host.popen() and diff --git a/examples/controllers.py b/examples/controllers.py index 6eeef0e..ac86429 100755 --- a/examples/controllers.py +++ b/examples/controllers.py @@ -1,64 +1,35 @@ #!/usr/bin/python """ -This example creates a multi-controller network from -semi-scratch; note a topo object could also be used and -would be passed into the Mininet() constructor. +Create a network where different switches are connected to +different controllers, by creating a custom Switch() subclass. """ from mininet.net import Mininet -from mininet.node import Controller, OVSKernelSwitch -from mininet.cli import CLI +from mininet.node import OVSSwitch, Controller, RemoteController +from mininet.topolib import TreeTopo from mininet.log import setLogLevel +from mininet.cli import CLI -Switch = OVSKernelSwitch +setLogLevel( 'info' ) -def addHost( net, N ): - "Create host hN and add to net." - name = 'h%d' % N - ip = '10.0.0.%d' % N - return net.addHost( name, ip=ip ) +# Two local and one "external" controller (which is actually c0) +# Ignore the warning message that the remote isn't (yet) running +c0 = Controller( 'c0' ) +c1 = Controller( 'c1' ) +c2 = RemoteController( 'c2', ip='127.0.0.1' ) -def multiControllerNet(): - "Create a network with multiple controllers." +cmap = { 's1': c0, 's2': c1, 's3': c2 } - net = Mininet( controller=Controller, switch=Switch) +class MultiSwitch( OVSSwitch ): + "Custom Switch() subclass that connects to different controllers" + def start( self, controllers ): + return OVSSwitch.start( self, [ cmap[ self.name ] ] ) - print "*** Creating controllers" - c1 = net.addController( 'c1', port=6633 ) - c2 = net.addController( 'c2', port=6634 ) - - print "*** Creating switches" - s1 = net.addSwitch( 's1' ) - s2 = net.addSwitch( 's2' ) - - print "*** Creating hosts" - hosts1 = [ addHost( net, n ) for n in 3, 4 ] - hosts2 = [ addHost( net, n ) for n in 5, 6 ] - - print "*** Creating links" - for h in hosts1: - s1.linkTo( h ) - for h in hosts2: - s2.linkTo( h ) - s1.linkTo( s2 ) - - print "*** Starting network" - net.build() - c1.start() - c2.start() - s1.start( [ c1 ] ) - s2.start( [ c2 ] ) - - print "*** Testing network" - net.pingAll() - - print "*** Running CLI" - CLI( net ) - - print "*** Stopping network" - net.stop() - -if __name__ == '__main__': - setLogLevel( 'info' ) # for CLI output - multiControllerNet() +topo = TreeTopo( depth=2, fanout=2 ) +net = Mininet( topo=topo, switch=MultiSwitch, build=False ) +net.controllers = [ c0, c1 ] +net.build() +net.start() +CLI( net ) +net.stop() diff --git a/examples/controllers2.py b/examples/controllers2.py new file mode 100755 index 0000000..0836533 --- /dev/null +++ b/examples/controllers2.py @@ -0,0 +1,61 @@ +#!/usr/bin/python + +""" +This example creates a multi-controller network from semi-scratch by +using the net.add*() API and manually starting the switches and controllers. + +This is the "mid-level" API, which is an alternative to the "high-level" +Topo() API which supports parametrized topology classes. + +Note that one could also create a custom switch class and pass it into +the Mininet() constructor. +""" + +from mininet.net import Mininet +from mininet.node import Controller, OVSSwitch +from mininet.cli import CLI +from mininet.log import setLogLevel + +def multiControllerNet(): + "Create a network from semi-scratch with multiple controllers." + + net = Mininet( controller=Controller, switch=OVSSwitch, build=False ) + + print "*** Creating (reference) controllers" + c1 = net.addController( 'c1', port=6633 ) + c2 = net.addController( 'c2', port=6634 ) + + print "*** Creating switches" + s1 = net.addSwitch( 's1' ) + s2 = net.addSwitch( 's2' ) + + print "*** Creating hosts" + hosts1 = [ net.addHost( 'h%d' % n ) for n in 3, 4 ] + hosts2 = [ net.addHost( 'h%d' % n ) for n in 5, 6 ] + + print "*** Creating links" + for h in hosts1: + net.addLink( s1, h ) + for h in hosts2: + net.addLink( s2, h ) + net.addLink( s1, s2 ) + + print "*** Starting network" + net.build() + c1.start() + c2.start() + s1.start( [ c1 ] ) + s2.start( [ c2 ] ) + + print "*** Testing network" + net.pingAll() + + print "*** Running CLI" + CLI( net ) + + print "*** Stopping network" + net.stop() + +if __name__ == '__main__': + setLogLevel( 'info' ) # for CLI output + multiControllerNet() diff --git a/examples/controlnet.py b/examples/controlnet.py new file mode 100755 index 0000000..3d5dd9d --- /dev/null +++ b/examples/controlnet.py @@ -0,0 +1,118 @@ +#!/usr/bin/python + +""" +controlnet.py: Mininet with a custom control network + +We create two Mininet() networks, a control network +and a data network, running four DataControllers on the +control network to control the data network. + +Since we're using UserSwitch on the data network, +it should correctly fail over to a backup controller. + +We also hack/subclass the CLI slightly so it can talk to +both the control and data networks. +""" + +from mininet.net import Mininet +from mininet.node import Controller, UserSwitch +from mininet.cli import CLI +from mininet.topo import Topo +from mininet.topolib import TreeTopo +from mininet.log import setLogLevel, info + +# Some minor hacks + +class DataController( Controller ): + """Data Network Controller. + patched to avoid checkListening error""" + def checkListening( self ): + "Ignore spurious error" + pass + + +class CLI2( CLI ): + "CLI that can talk to two networks" + + def __init__( self, *args, **kwargs ): + "cnet: second network" + self.cnet = kwargs.pop( 'cnet' ) + CLI.__init__( self, *args, **kwargs ) + + def updateVars( self ): + "Update variables to include cnet" + cnet = self.cnet + nodes2 = cnet.controllers + cnet.switches + cnet.hosts + self.nodelist += nodes2 + for node in nodes2: + self.nodemap[ node.name ] = node + self.locals[ 'cnet' ] = cnet + self.locals.update( self.nodemap ) + + def cmdloop( self, *args, **kwargs ): + "Patch to add cnet if needed" + if 'cnet' not in self.locals: + self.updateVars() + CLI.cmdloop( self, *args, **kwargs ) + + +# A real control network! + +class ControlNetwork( Topo ): + "Control Network Topology" + def __init__( self, n, dataController=DataController, **kwargs ): + """n: number of data network controller nodes + dataController: class for data network controllers""" + Topo.__init__( self, **kwargs ) + # Connect everything to a single switch + cs0 = self.addSwitch( 'cs0' ) + # Add hosts which will serve as data network controllers + for i in range( 0, n ): + c = self.addHost( 'c%s' % i, cls=dataController, + inNamespace=True ) + self.addLink( c, cs0 ) + # Connect switch to root namespace so that data network + # switches will be able to talk to us + root = self.addHost( 'root', inNamespace=False ) + self.addLink( root, cs0 ) + + +# Make it Happen!! + +setLogLevel( 'info' ) + +info( '* Creating Control Network\n' ) +ctopo = ControlNetwork( n=4, dataController=DataController ) +cnet = Mininet( topo=ctopo, ipBase='192.168.123.0/24', build=False ) +info( '* Adding Control Network Controller\n') +cnet.addController( 'cc0' ) +info( '* Starting Control Network\n') +cnet.build() +cnet.start() +dataControllers = cnet.hosts[ : -1 ] # ignore 'root' node + +info( '* Creating Data Network\n' ) +topo = TreeTopo( depth=2, fanout=2 ) +# UserSwitch so we can easily test failover +net = Mininet( topo=topo, switch=UserSwitch, build=False ) +info( '* Adding Controllers to Data Network\n' ) +net.controllers = dataControllers +net.build() +info( '* Starting Data Network\n') +net.start() + +CLI2( net, cnet=cnet ) + +info( '* Stopping Data Network\n' ) +net.stop() + +info( '* Stopping Control Network\n' ) +# dataControllers have already been stopped +cnet.hosts = list( set( cnet.hosts ) - set( dataControllers ) ) +cnet.stop() + + + + + + diff --git a/examples/nat.py b/examples/nat.py new file mode 100755 index 0000000..fcad85c --- /dev/null +++ b/examples/nat.py @@ -0,0 +1,112 @@ +#!/usr/bin/python + +""" +Example to create a Mininet topology and connect it to the internet via NAT +through eth0 on the host. + +Glen Gibb, February 2011 + +(slight modifications by BL, 5/13) +""" + +from mininet.cli import CLI +from mininet.log import lg +from mininet.node import Node +from mininet.topolib import TreeNet + +################################# +def startNAT( root, inetIntf='eth0', subnet='10.0/8' ): + """Start NAT/forwarding between Mininet and external network + root: node to access iptables from + inetIntf: interface for internet access + subnet: Mininet subnet (default 10.0/8)=""" + + # Identify the interface connecting to the mininet network + localIntf = root.defaultIntf() + + # Flush any currently active rules + root.cmd( 'iptables -F' ) + root.cmd( 'iptables -t nat -F' ) + + # Create default entries for unmatched traffic + root.cmd( 'iptables -P INPUT ACCEPT' ) + root.cmd( 'iptables -P OUTPUT ACCEPT' ) + root.cmd( 'iptables -P FORWARD DROP' ) + + # Configure NAT + root.cmd( 'iptables -I FORWARD -i', localIntf, '-d', subnet, '-j DROP' ) + root.cmd( 'iptables -A FORWARD -i', localIntf, '-s', subnet, '-j ACCEPT' ) + root.cmd( 'iptables -A FORWARD -i', inetIntf, '-d', subnet, '-j ACCEPT' ) + root.cmd( 'iptables -t nat -A POSTROUTING -o ', inetIntf, '-j MASQUERADE' ) + + # Instruct the kernel to perform forwarding + root.cmd( 'sysctl net.ipv4.ip_forward=1' ) + +def stopNAT( root ): + """Stop NAT/forwarding between Mininet and external network""" + # Flush any currently active rules + root.cmd( 'iptables -F' ) + root.cmd( 'iptables -t nat -F' ) + + # Instruct the kernel to stop forwarding + root.cmd( 'sysctl net.ipv4.ip_forward=0' ) + +def fixNetworkManager( root, intf ): + """Prevent network-manager from messing with our interface, + by specifying manual configuration in /etc/network/interfaces + root: a node in the root namespace (for running commands) + intf: interface name""" + cfile = '/etc/network/interfaces' + line = '\niface %s inet manual\n' % intf + config = open( cfile ).read() + if ( line ) not in config: + print '*** Adding', line.strip(), 'to', cfile + with open( cfile, 'a' ) as f: + f.write( line ) + # Probably need to restart network-manager to be safe - + # hopefully this won't disconnect you + root.cmd( 'service network-manager restart' ) + +def connectToInternet( network, switch='s1', rootip='10.254', subnet='10.0/8'): + """Connect the network to the internet + switch: switch to connect to root namespace + rootip: address for interface in root namespace + subnet: Mininet subnet""" + switch = network.get( switch ) + prefixLen = subnet.split( '/' )[ 1 ] + + # Create a node in root namespace + root = Node( 'root', inNamespace=False ) + + # Prevent network-manager from interfering with our interface + fixNetworkManager( root, 'root-eth0' ) + + # Create link between root NS and switch + link = network.addLink( root, switch ) + link.intf1.setIP( rootip, prefixLen ) + + # Start network that now includes link to root namespace + network.start() + + # Start NAT and establish forwarding + startNAT( root ) + + # Establish routes from end hosts + for host in network.hosts: + host.cmd( 'ip route flush root 0/0' ) + host.cmd( 'route add -net', subnet, 'dev', host.defaultIntf() ) + host.cmd( 'route add default gw', rootip ) + + return root + +if __name__ == '__main__': + lg.setLogLevel( 'info') + net = TreeNet( depth=1, fanout=4 ) + # Configure and start NATted connectivity + rootnode = connectToInternet( net ) + print "*** Hosts are running and should have internet connectivity" + print "*** Type 'exit' or control-D to shut down network" + CLI( net ) + # Shut down NAT + stopNAT( rootnode ) + net.stop() diff --git a/mininet/clean.py b/mininet/clean.py index eac8fda..c5ef1ab 100755 --- a/mininet/clean.py +++ b/mininet/clean.py @@ -27,16 +27,19 @@ def cleanup(): info("*** Removing excess controllers/ofprotocols/ofdatapaths/pings/noxes" "\n") zombies = 'controller ofprotocol ofdatapath ping nox_core lt-nox_core ' - zombies += 'ovs-openflowd udpbwtest' + zombies += 'ovs-openflowd ovs-controller udpbwtest mnexec' # 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' ) + # And kill off sudo mnexec + sh( 'pkill -9 -f "sudo mnexec"') + info( "*** Removing junk from /tmp\n" ) sh( 'rm -f /tmp/vconn* /tmp/vlogs* /tmp/*.out /tmp/*.log' ) - info( "*** Removing old screen sessions\n" ) + info( "*** Removing old X11 tunnels\n" ) cleanUpScreens() info( "*** Removing excess kernel datapaths\n" ) @@ -52,7 +55,7 @@ def cleanup(): 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' ) + links = sh( r"ip link show | egrep -o '(\w+-eth\w+)'" ).split( '\n' ) for link in links: if link != '': sh( "ip link del " + link ) diff --git a/mininet/cli.py b/mininet/cli.py index efb1808..a89051d 100644 --- a/mininet/cli.py +++ b/mininet/cli.py @@ -33,7 +33,7 @@ import sys import time from mininet.log import info, output, error -from mininet.term import makeTerms +from mininet.term import makeTerms, runX11 from mininet.util import quietRun, isShellBuiltin, dumpNodeConnections class CLI( Cmd ): @@ -121,12 +121,12 @@ class CLI( Cmd ): "Run an external shell command" call( line, shell=True ) - # do_py() needs to catch any exception during eval() + # do_py() and do_px() need to catch any exception during eval()/exec() # pylint: disable-msg=W0703 def do_py( self, line ): """Evaluate a Python expression. - Node names may be used, e.g.: h1.cmd('ls')""" + Node names may be used, e.g.: py h1.cmd('ls')""" try: result = eval( line, globals(), self.locals ) if not result: @@ -138,7 +138,18 @@ class CLI( Cmd ): except Exception, e: output( str( e ) + '\n' ) - # pylint: enable-msg=W0703 + # We are in fact using the exec() pseudo-function + # pylint: disable-msg=W0122 + + def do_px( self, line ): + """Execute a Python statement. + Node names may be used, e.g.: px print h1.cmd('ls')""" + try: + exec( line, globals(), self.locals ) + except Exception, e: + output( str( e ) + '\n' ) + + # pylint: enable-msg=W0703,W0122 def do_pingall( self, _line ): "Ping between all hosts." @@ -230,6 +241,17 @@ class CLI( Cmd ): node = self.nodemap[ arg ] self.mn.terms += makeTerms( [ node ], term = term ) + def do_x( self, line ): + """Create an X11 tunnel to the given node, + optionally starting a client.""" + args = line.split() + if not args: + error( 'usage: x node [cmd args]...\n' ) + else: + node = self.mn[ args[ 0 ] ] + cmd = args[ 1: ] + self.mn.terms += runX11( node, cmd ) + def do_gterm( self, line ): "Spawn gnome-terminal(s) for the given node(s)." self.do_xterm( line, term='gterm' ) @@ -278,7 +300,7 @@ class CLI( Cmd ): self.inputFile = None def do_dpctl( self, line ): - "Run dpctl command on all switches." + "Run dpctl (or ovs-ofctl) command on all switches." args = line.split() if len(args) < 1: error( 'usage: dpctl command [arg1] [arg2] ...\n' ) @@ -310,7 +332,7 @@ class CLI( Cmd ): if first in self.nodemap: node = self.nodemap[ first ] # Substitute IP addresses for node names in command - rest = [ self.nodemap[ arg ].IP() + rest = [ self.nodemap[ arg ].defaultIntf().updateIP() if arg in self.nodemap else arg for arg in rest ] rest = ' '.join( rest ) diff --git a/mininet/link.py b/mininet/link.py index 21e18ba..be73d52 100644 --- a/mininet/link.py +++ b/mininet/link.py @@ -25,8 +25,7 @@ Link: basic link class for creating veth pairs """ from mininet.log import info, error, debug -from mininet.util import makeIntfPair -from time import sleep +from mininet.util import makeIntfPair, quietRun import re class Intf( object ): @@ -162,8 +161,9 @@ class Intf( object ): def delete( self ): "Delete interface" self.cmd( 'ip link del ' + self.name ) - # Does it help to sleep to let things run? - sleep( 0.001 ) + if self.node.inNamespace: + # Link may have been dumped into root NS + quietRun( 'ip link del ' + self.name ) def __repr__( self ): return '<%s %s>' % ( self.__class__.__name__, self.name ) @@ -196,36 +196,36 @@ class TCIntf( Intf ): # 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 ' + cmds += [ '%s qdisc add dev %s root handle 5:0 hfsc default 1', + '%s class add dev %s parent 5:0 classid 5: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 ' + + cmds += [ '%s qdisc add dev %s root handle 5: 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 ' + + cmds += [ '%s qdisc add dev %s root handle 5:0 htb default 1', + '%s class add dev %s parent 5:0 classid 5:1 htb ' + 'rate %fMbit burst 15k' % bw ] - parent = ' parent 1:1 ' + parent = ' parent 5:1 ' # ECN or RED if enable_ecn: cmds += [ '%s qdisc add dev %s' + parent + - 'handle 10: red limit 1000000 ' + + 'handle 6: red limit 1000000 ' + 'min 30000 max 35000 avpkt 1500 ' + 'burst 20 ' + 'bandwidth %fmbit probability 1 ecn' % bw ] - parent = ' parent 10: ' + parent = ' parent 6: ' elif enable_red: cmds += [ '%s qdisc add dev %s' + parent + - 'handle 10: red limit 1000000 ' + + 'handle 6: red limit 1000000 ' + 'min 30000 max 35000 avpkt 1500 ' + 'burst 20 ' + 'bandwidth %fmbit probability 1' % bw ] - parent = ' parent 10: ' + parent = ' parent 6: ' return cmds, parent @staticmethod @@ -251,7 +251,8 @@ class TCIntf( Intf ): cmds = [ '%s qdisc add dev %s ' + parent + ' handle 10: netem ' + netemargs ] - return cmds + parent = ' parent 10:1 ' + return cmds, parent def tc( self, cmd, tc='tc' ): "Execute tc command for our interface" @@ -289,9 +290,10 @@ class TCIntf( Intf ): 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, + delaycmds, parent = self.delayCmds( delay=delay, jitter=jitter, + loss=loss, max_queue_size=max_queue_size, parent=parent ) + cmds += delaycmds # Ugly but functional: display configuration info stuff = ( ( [ '%.2fMbit' % bw ] if bw is not None else [] ) + @@ -308,6 +310,7 @@ class TCIntf( Intf ): debug( "cmds:", cmds, '\n' ) debug( "outputs:", tcoutputs, '\n' ) result[ 'tcoutputs'] = tcoutputs + result[ 'parent' ] = parent return result diff --git a/mininet/log.py b/mininet/log.py index cd00821..a046f50 100644 --- a/mininet/log.py +++ b/mininet/log.py @@ -60,18 +60,16 @@ class Singleton( type ): See http://en.wikipedia.org/wiki/SingletonPattern#Python Intended to be used as a __metaclass_ param, as shown for the class - below. + below.""" - Changed cls first args to mcs to satisfy pylint.""" + def __init__( cls, name, bases, dict_ ): + super( Singleton, cls ).__init__( name, bases, dict_ ) + cls.instance = None - 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 + def __call__( cls, *args, **kw ): + if cls.instance is None: + cls.instance = super( Singleton, cls ).__call__( *args, **kw ) + return cls.instance class MininetLogger( Logger, object ): diff --git a/mininet/net.py b/mininet/net.py index b31a10c..db594c3 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -91,6 +91,7 @@ import re import select import signal from time import sleep +from itertools import chain from mininet.cli import CLI from mininet.log import info, error, debug, output @@ -216,8 +217,8 @@ class Mininet( object ): self.nameToNode[ name ] = controller_new return controller_new - # BL: is this better than just using nameToNode[] ? - # Should it have a better name? + # BL: We now have four ways to look up nodes + # This may (should?) be cleaned up in the future. def getNodeByName( self, *args ): "Return node(s) with given name(s)" if len( args ) == 1: @@ -228,6 +229,15 @@ class Mininet( object ): "Convenience alias for getNodeByName" return self.getNodeByName( *args ) + # Even more convenient syntax for node lookup and iteration + def __getitem__( self, *args ): + """net [ name ] operator: Return node(s) with given name(s)""" + return self.getNodeByName( *args ) + + def __iter__( self ): + "return iterator over nodes" + return chain( self.hosts, self.switches, self.controllers ) + def addLink( self, node1, node2, port1=None, port2=None, cls=None, **params ): """"Add a link from node1 to node2 @@ -250,7 +260,7 @@ class Mininet( object ): info( host.name + ' ' ) intf = host.defaultIntf() if intf: - host.configDefault( defaultRoute=intf ) + host.configDefault() else: # Don't configure nonexistent intf host.configDefault( ip=None, mac=None ) @@ -362,16 +372,16 @@ class Mininet( object ): 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 hosts\n' % len( self.hosts ) ) + for host in self.hosts: + info( host.name + ' ' ) + host.terminate() + info( '\n' ) info( '*** Stopping %i controllers\n' % len( self.controllers ) ) for controller in self.controllers: info( controller.name + ' ' ) @@ -458,9 +468,13 @@ class Mininet( object ): lost += sent - received output( ( '%s ' % dest.name ) if received else 'X ' ) output( '\n' ) + if packets > 0: ploss = 100 * lost / packets - output( "*** Results: %i%% dropped (%d/%d lost)\n" % - ( ploss, lost, packets ) ) + output( "*** Results: %i%% dropped (%d/%d lost)\n" % + ( ploss, lost, packets ) ) + else: + ploss = 0 + output( "*** Warning: No packets sent\n" ) return ploss @staticmethod diff --git a/mininet/node.py b/mininet/node.py index 023c1e1..639bae2 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -49,6 +49,7 @@ import re import signal import select from subprocess import Popen, PIPE, STDOUT +from operator import or_ from mininet.log import info, error, warn, debug from mininet.util import ( quietRun, errRun, errFail, moveIntf, isShellBuiltin, @@ -118,7 +119,8 @@ class Node( object ): if self.inNamespace: opts += 'n' # bash -m: enable job control - cmd = [ 'mnexec', opts, 'bash', '-m' ] + # -s: pass $* to shell, and make process easy to find in ps + cmd = [ 'mnexec', opts, 'bash', '-ms', 'mininet:' + self.name ] self.shell = Popen( cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True ) self.stdin = self.shell.stdin @@ -139,10 +141,10 @@ class Node( object ): 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 ) + # Intfs may end up in root NS + for intfName in self.intfNames(): + if self.name in intfName: + quietRun( 'ip link del ' + intfName ) self.shell = None # Subshell I/O, commands and control @@ -290,7 +292,7 @@ class Node( object ): kwargs: Popen() keyword args""" defaults = { 'stdout': PIPE, 'stderr': PIPE, 'mncmd': - [ 'mnexec', '-a', str( self.pid ) ] } + [ 'mnexec', '-da', str( self.pid ) ] } defaults.update( kwargs ) if len( args ) == 1: if type( args[ 0 ] ) is list: @@ -370,7 +372,7 @@ class Node( object ): """ if not intf: return self.defaultIntf() - elif type( intf) is str: + elif type( intf ) is str: return self.nameToIntf[ intf ] else: return intf @@ -389,16 +391,19 @@ class Node( object ): connections += [ ( intf, link.intf1 ) ] return connections - def deleteIntfs( self ): - "Delete all of our interfaces." + def deleteIntfs( self, checkName=True ): + """Delete all of our interfaces. + checkName: only delete interfaces that contain our name""" # 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( '.' ) + # Protect against deleting hardware interfaces + if ( self.name in intf.name ) or ( not checkName ): + intf.delete() + info( '.' ) # Routing support @@ -417,11 +422,14 @@ class Node( object ): 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 ) + intf: Intf or {dev via ...}""" + # Note setParam won't call us if intf is none + if type( intf ) is str and ' ' in intf: + params = intf + else: + params = 'dev %s' % intf + self.cmd( 'ip route del default' ) + return self.cmd( 'ip route add default', params ) # Convenience and configuration methods @@ -447,7 +455,7 @@ class Node( object ): def MAC( self, intf=None ): "Return MAC address of a node or specific interface." - return self.intf( intf ).IP() + return self.intf( intf ).MAC() def intfIsUp( self, intf=None ): "Check if an interface is up." @@ -492,7 +500,7 @@ class Node( object ): r = {} self.setParam( r, 'setMAC', mac=mac ) self.setParam( r, 'setIP', ip=ip ) - self.setParam( r, 'defaultRoute', defaultRoute=defaultRoute ) + self.setParam( r, 'setDefaultRoute', defaultRoute=defaultRoute ) # This should be examined self.cmd( 'ifconfig lo ' + lo ) return r @@ -604,14 +612,15 @@ class CPULimitedHost( Host ): 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 ), + mncmd = [ 'mnexec', '-da', 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" + "Clean up Node, then clean up our cgroup" + super( CPULimitedHost, self ).cleanup() retry( retries=3, delaySecs=1, fn=self.cgroupDel ) def chrt( self ): @@ -754,7 +763,7 @@ class Switch( Node ): def defaultDpid( self ): "Derive dpid from switch name, s1 -> 1" try: - dpid = int( re.findall( '\d+', self.name )[ 0 ] ) + dpid = int( re.findall( r'\d+', self.name )[ 0 ] ) dpid = hex( dpid )[ 2: ] dpid = '0' * ( self.dpidLen - len( dpid ) ) + dpid return dpid @@ -780,6 +789,10 @@ class Switch( Node ): error( '*** Error: %s has execed and cannot accept commands' % self.name ) + def connected( self ): + "Is the switch connected to a controller? (override this method)" + return False and self # satisfy pylint + def __repr__( self ): "More informative string representation" intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() ) @@ -792,15 +805,17 @@ class UserSwitch( Switch ): dpidLen = 12 - def __init__( self, name, **kwargs ): + def __init__( self, name, dpopts='--no-slicing', **kwargs ): """Init. - name: name for the switch""" + name: name for the switch + dpopts: additional arguments to ofdatapath (--no-slicing)""" 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 + self.dpopts = dpopts @classmethod def setup( cls ): @@ -815,6 +830,10 @@ class UserSwitch( Switch ): return self.cmd( 'dpctl ' + ' '.join( args ) + ' tcp:127.0.0.1:%i' % self.listenPort ) + def connected( self ): + "Is the switch connected to a controller?" + return 'remote.is-connected=true' in self.dpctl( 'status' ) + def start( self, controllers ): """Start OpenFlow reference user datapath. Log to /tmp/sN-{ofd,ofp}.log. @@ -827,7 +846,8 @@ class UserSwitch( Switch ): 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 + + ' punix:/tmp/' + self.name + ' -d %s ' % self.dpid + + self.dpopts + ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' ) self.cmd( 'ofprotocol unix:/tmp/' + self.name + ' ' + clist + @@ -895,12 +915,14 @@ class OVSLegacyKernelSwitch( Switch ): class OVSSwitch( Switch ): "Open vSwitch switch. Depends on ovs-vsctl." - def __init__( self, name, failMode='secure', **params ): + def __init__( self, name, failMode='secure', datapath='kernel', **params ): """Init. name: name for switch - failMode: controller loss behavior (secure|open)""" + failMode: controller loss behavior (secure|open) + datapath: userspace or kernel mode (kernel|user)""" Switch.__init__( self, name, **params ) self.failMode = failMode + self.datapath = datapath @classmethod def setup( cls ): @@ -923,8 +945,8 @@ class OVSSwitch( Switch ): exit( 1 ) def dpctl( self, *args ): - "Run ovs-dpctl command" - return self.cmd( 'ovs-dpctl', args[ 0 ], self, *args[ 1: ] ) + "Run ovs-ofctl command" + return self.cmd( 'ovs-ofctl', args[ 0 ], self, *args[ 1: ] ) @staticmethod def TCReapply( intf ): @@ -944,6 +966,23 @@ class OVSSwitch( Switch ): "Disconnect a data port" self.cmd( 'ovs-vsctl del-port', self, intf ) + def controllerUUIDs( self ): + "Return ovsdb UUIDs for our controllers" + uuids = [] + controllers = self.cmd( 'ovs-vsctl -- get Bridge', self, + 'Controller' ).strip() + if controllers.startswith( '[' ) and controllers.endswith( ']' ): + controllers = controllers[ 1 : -1 ] + uuids = [ c.strip() for c in controllers.split( ',' ) ] + return uuids + + def connected( self ): + "Are we connected to at least one of our controllers?" + results = [ 'true' in self.cmd( 'ovs-vsctl -- get Controller', + uuid, 'is_connected' ) + for uuid in self.controllerUUIDs() ] + return reduce( or_, results, False ) + def start( self, controllers ): "Start up a new OVS OpenFlow switch using ovs-vsctl" if self.inNamespace: @@ -955,6 +994,9 @@ class OVSSwitch( Switch ): # Annoyingly, --if-exists option seems not to work self.cmd( 'ovs-vsctl del-br', self ) self.cmd( 'ovs-vsctl add-br', self ) + if self.datapath == 'user': + self.cmd( 'ovs-vsctl set bridge', self,'datapath_type=netdev' ) + int( self.dpid, 16 ) # DPID must be a hex string self.cmd( 'ovs-vsctl -- set Bridge', self, 'other_config:datapath-id=' + self.dpid ) self.cmd( 'ovs-vsctl set-fail-mode', self, self.failMode ) @@ -967,6 +1009,14 @@ class OVSSwitch( Switch ): if self.listenPort: clist += ' ptcp:%s' % self.listenPort self.cmd( 'ovs-vsctl set-controller', self, clist ) + # Reconnect quickly to controllers (1s vs. 15s max_backoff) + for uuid in self.controllerUUIDs(): + if uuid.count( '-' ) != 4: + # Doesn't look like a UUID + continue + uuid = uuid.strip() + self.cmd( 'ovs-vsctl set Controller', uuid, + 'max_backoff=1000' ) def stop( self ): "Terminate OVS switch." diff --git a/mininet/term.py b/mininet/term.py index 3cd70f2..5857fa8 100644 --- a/mininet/term.py +++ b/mininet/term.py @@ -1,60 +1,73 @@ """ Terminal creation and cleanup. -Utility functions to run a term (connected via screen(1)) on each host. +Utility functions to run a terminal (connected via socat(1)) on each host. -Requires GNU screen(1) and xterm(1). +Requires socat(1) and xterm(1). Optionally uses gnome-terminal. """ -import re -from subprocess import Popen +from os import environ from mininet.log import error -from mininet.util import quietRun +from mininet.util import quietRun, errRun -def quoteArg( arg ): - "Quote an argument if it contains spaces." - return repr( arg ) if ' ' in arg else arg +def tunnelX11( node, display=None): + """Create an X11 tunnel from node:6000 to the root host + display: display on root host (optional) + returns: node $DISPLAY, Popen object for tunnel""" + if display is None: + display = environ[ 'DISPLAY' ] + host, screen = display.split( ':' ) + # Unix sockets should work + if not host or host == 'unix': + # GDM3 doesn't put credentials in .Xauthority, + # so allow root to just connect + quietRun( 'xhost +si:localuser:root' ) + return display, None + else: + # Create a tunnel for the TCP connection + port = 6000 + int( float( screen ) ) + connection = r'TCP\:%s\:%s' % ( host, port ) + cmd = [ "socat", "TCP-LISTEN:%d,fork,reuseaddr" % port, + "EXEC:'mnexec -a 1 socat STDIO %s'" % connection ] + return 'localhost:' + screen, node.popen( cmd ) -def makeTerm( node, title='Node', term='xterm' ): - """Run screen on a node, and hook up a terminal. +def makeTerm( node, title='Node', term='xterm', display=None ): + """Create an X11 tunnel to the node and start up a terminal. node: Node object title: base title term: 'xterm' or 'gterm' - returns: process created""" + returns: two Popen objects, tunnel and terminal""" title += ': ' + node.name if not node.inNamespace: title += ' (root)' cmds = { - 'xterm': [ 'xterm', '-title', title, '-e' ], - 'gterm': [ 'gnome-terminal', '--title', title, '-e' ] + 'xterm': [ 'xterm', '-title', title, '-display' ], + 'gterm': [ 'gnome-terminal', '--title', title, '--display' ] } 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 ) + display, tunnel = tunnelX11( node, display ) + term = node.popen( cmds[ term ] + [ display, '-e', 'env TERM=ansi bash'] ) + return [ tunnel, term ] if tunnel else [ term ] + +def runX11( node, cmd ): + "Run an X11 client on a node" + _display, tunnel = tunnelX11( node ) + popen = node.popen( cmd ) + return [ tunnel, popen ] 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' ) + "Remove moldy socat X11 tunnels." + errRun( "pkill -9 -f mnexec.*socat" ) 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 ] + returns: list of created tunnel/terminal processes""" + terms = [] + for node in nodes: + terms += makeTerm( node, title, term ) + return terms diff --git a/mininet/topo.py b/mininet/topo.py index fff9604..fa20e19 100644 --- a/mininet/topo.py +++ b/mininet/topo.py @@ -11,13 +11,41 @@ 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 Graph( object ): + "Utility class to track nodes and edges - replaces networkx.Graph" + + def __init__( self ): + self.data = {} + + def add_node( self, node ): + "Add node to graph" + if node not in self.data.keys(): + self.data[ node ] = [] + + def add_edge( self, src, dest ): + "Add edge to graph" + self.add_node( src ) + self.add_node( dest ) + self.data[ src ].append( dest ) + self.data[ dest ].append( src ) + + def nodes( self ): + "Return list of graph nodes" + return self.data.keys() + + def edges( self ): + "Iterator: return graph edges" + for src in self.data.keys(): + for dest in self.data[ src ]: + yield ( src, dest ) + + def __getitem__( self, node ): + "Return link dict for the given node" + return self.data[node] + + class Topo(object): "Data center network representation for structured multi-trees." diff --git a/mininet/util.py b/mininet/util.py index ff75b10..9cb1a53 100644 --- a/mininet/util.py +++ b/mininet/util.py @@ -4,7 +4,7 @@ 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 select import poll, POLLIN, POLLHUP from subprocess import call, check_call, Popen, PIPE, STDOUT import re from fcntl import fcntl, F_GETFL, F_SETFL @@ -171,27 +171,35 @@ def retry( retries, delaySecs, fn, *args, **keywords ): error( "*** gave up after %i retries\n" % tries ) exit( 1 ) -def moveIntfNoRetry( intf, node, printError=False ): +def moveIntfNoRetry( intf, dstNode, srcNode=None, 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' ) + dstNode: destination Node + srcNode: source Node or None (default) for root ns + printError: if true, print error""" + intf = str( intf ) + cmd = 'ip link set %s netns %s' % ( intf, dstNode.pid ) + if srcNode: + srcNode.cmd( cmd ) + else: + quietRun( cmd ) + links = dstNode.cmd( 'ip link show' ) if not ( ' %s:' % intf ) in links: if printError: error( '*** Error: moveIntf: ' + intf + - ' not successfully moved to ' + node.name + '\n' ) + ' not successfully moved to ' + dstNode.name + '\n' ) return False return True -def moveIntf( intf, node, printError=False, retries=3, delaySecs=0.001 ): +def moveIntf( intf, dstNode, srcNode=None, printError=False, + retries=3, delaySecs=0.001 ): """Move interface to node, retrying on failure. intf: string, interface - node: Node object + dstNode: destination Node + srcNode: source Node or None (default) for root ns printError: if true, print error""" - retry( retries, delaySecs, moveIntfNoRetry, intf, node, printError ) + retry( retries, delaySecs, moveIntfNoRetry, intf, dstNode, + srcNode=srcNode, printError=printError ) # Support for dumping network @@ -242,9 +250,8 @@ def macColonHex( mac ): 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""" + returns: ip address string w.x.y.z""" w = ( ip >> 24 ) & 0xff - w = 10 if w == 0 else w x = ( ip >> 16 ) & 0xff y = ( ip >> 8 ) & 0xff z = ip & 0xff @@ -261,10 +268,10 @@ def ipAdd( i, prefixLen=8, ipBaseNum=0x0a000000 ): 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 ) + imax = 0xffffffff >> prefixLen + assert i <= imax + mask = 0xffffffff ^ imax + ipnum = ( ipBaseNum & mask ) + i return ipStr( ipnum ) def ipParse( ip ): @@ -326,27 +333,24 @@ def pmonitor(popens, timeoutms=500, readline=True, # Use non-blocking reads flags = fcntl( fd, F_GETFL ) fcntl( fd, F_SETFL, flags | O_NONBLOCK ) - while True: + while popens: fds = poller.poll( timeoutms ) if fds: - for fd, _event in 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 + if event & POLLIN: + 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 + elif event & POLLHUP: + poller.unregister( fd ) + del popens[ host ] else: yield None, '' @@ -419,7 +423,7 @@ def splitArgs( argstr ): 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( '=' ) + key, val = s.split( '=', 1 ) kwargs[ key ] = makeNumeric( val ) return fn, args, kwargs diff --git a/mnexec.c b/mnexec.c index 42a9cf6..bd7b1b1 100644 --- a/mnexec.c +++ b/mnexec.c @@ -46,7 +46,7 @@ void usage(char *name) int setns(int fd, int nstype) { - return syscall(308, fd, nstype); + return syscall(__NR_setns, fd, nstype); } /* Validate alphanumeric path foo1/bar2/baz */ diff --git a/setup.py b/setup.py index 3f9f0eb..9cee655 100644 --- a/setup.py +++ b/setup.py @@ -37,8 +37,7 @@ setup( keywords='networking emulator protocol Internet OpenFlow SDN', license='BSD', install_requires=[ - 'setuptools', - 'networkx' + 'setuptools' ], scripts=scripts, ) diff --git a/util/install.sh b/util/install.sh index 8bd7cac..424d0ac 100755 --- a/util/install.sh +++ b/util/install.sh @@ -9,6 +9,18 @@ set -e # Fail on unset var usage set -o nounset +# Get directory containing mininet folder +MININET_DIR="$( cd -P "$( dirname "${BASH_SOURCE[0]}" )/../.." && pwd )" + +# Set up build directory, which by default is the working directory +# unless the working directory is a subdirectory of mininet, +# in which case we use the directory containing mininet +BUILD_DIR=$PWD +case $PWD in + $MININET_DIR/*) BUILD_DIR=$MININET_DIR;; # currect directory is a subdirectory + *) BUILD_DIR=$PWD;; +esac + # Location of CONFIG_NET_NS-enabled kernel(s) KERNEL_LOC=http://www.openflow.org/downloads/mininet @@ -72,7 +84,7 @@ OVS_RELEASE=1.4.0 OVS_PACKAGE_LOC=https://github.com/downloads/mininet/mininet OVS_BUILDSUFFIX=-ignore # was -2 OVS_PACKAGE_NAME=ovs-$OVS_RELEASE-core-$DIST_LC-$RELEASE-$ARCH$OVS_BUILDSUFFIX.tar -OVS_SRC=~/openvswitch +OVS_SRC=$BUILD_DIR/openvswitch OVS_TAG=v$OVS_RELEASE OVS_BUILD=$OVS_SRC/build-$KERNEL_NAME OVS_KMODS=($OVS_BUILD/datapath/linux/{openvswitch_mod.ko,brcompat_mod.ko}) @@ -116,30 +128,27 @@ function kernel_clean { fi # Also remove downloaded packages: - rm -f ~/linux-headers-* ~/linux-image-* + rm -f $HOME/linux-headers-* $HOME/linux-image-* } # Install Mininet deps function mn_deps { echo "Installing Mininet dependencies" - $install gcc make screen psmisc xterm ssh iperf iproute telnet \ - python-setuptools python-networkx cgroup-bin ethtool help2man \ + $install gcc make socat psmisc xterm ssh iperf iproute telnet \ + python-setuptools cgroup-bin ethtool help2man \ pyflakes pylint pep8 - if [ "$DIST" = "Ubuntu" ] && [ "$RELEASE" = "10.04" ]; then - echo "Upgrading networkx to avoid deprecation warning" - sudo easy_install --upgrade networkx - fi - # Add sysctl parameters as noted in the INSTALL file to increase kernel # limits to support larger setups: - sudo su -c "cat $HOME/mininet/util/sysctl_addon >> /etc/sysctl.conf" - + if ! grep Mininet /etc/sysctl.conf; then + echo "Adding Mininet sysctl settings" + sudo su -c "cat $MININET_DIR/mininet/util/sysctl_addon >> /etc/sysctl.conf" + fi # Load new sysctl settings: sudo sysctl -p echo "Installing Mininet core" - pushd ~/mininet + pushd $MININET_DIR/mininet sudo make install popd } @@ -151,45 +160,66 @@ function mn_deps { # ... modified to use Debian Lenny rather than unstable. function of { echo "Installing OpenFlow reference implementation..." - cd ~/ + cd $BUILD_DIR/ $install git-core autoconf automake autotools-dev pkg-config \ make gcc libtool libc6-dev git clone git://openflowswitch.org/openflow.git - cd ~/openflow + cd $BUILD_DIR/openflow # Patch controller to handle more than 16 switches - patch -p1 < ~/mininet/util/openflow-patches/controller.patch + patch -p1 < $MININET_DIR/mininet/util/openflow-patches/controller.patch # Resume the install: ./boot.sh ./configure make sudo make install + cd $BUILD_DIR +} - # Remove avahi-daemon, which may cause unwanted discovery packets to be - # sent during tests, near link status changes: - $remove avahi-daemon +function of13 { + echo "Installing OpenFlow 1.3 soft switch implementation..." + cd $BUILD_DIR/ + $install git-core autoconf automake autotools-dev pkg-config \ + make gcc g++ libtool libc6-dev cmake libpcap-dev libxerces-c2-dev \ + unzip libpcre3-dev flex bison libboost-dev - # Disable IPv6. Add to /etc/modprobe.d/blacklist: - if [ "$DIST" = "Ubuntu" ]; then - BLACKLIST=/etc/modprobe.d/blacklist.conf - else - BLACKLIST=/etc/modprobe.d/blacklist + if [ ! -d "ofsoftswitch13" ]; then + git clone https://github.com/CPqD/ofsoftswitch13.git fi - sudo sh -c "echo 'blacklist net-pf-10\nblacklist ipv6' >> $BLACKLIST" - cd ~ + + # Install netbee + NBEESRC="nbeesrc-jan-10-2013" + NBEEURL=${NBEEURL:-http://www.nbee.org/download/} + wget -nc ${NBEEURL}${NBEESRC}.zip + unzip ${NBEESRC}.zip + cd ${NBEESRC}/src + cmake . + make + cd $BUILD_DIR/ + sudo cp ${NBEESRC}/bin/libn*.so /usr/local/lib + sudo ldconfig + sudo cp -R ${NBEESRC}/include/ /usr/ + + # Resume the install: + cd $BUILD_DIR/ofsoftswitch13 + ./boot.sh + ./configure + make + sudo make install + cd $BUILD_DIR } function wireshark { echo "Installing Wireshark dissector..." - sudo apt-get install -y wireshark libgtk2.0-dev + sudo apt-get install -y wireshark tshark libgtk2.0-dev if [ "$DIST" = "Ubuntu" ] && [ "$RELEASE" != "10.04" ]; then # Install newer version sudo apt-get install -y scons mercurial libglib2.0-dev sudo apt-get install -y libwiretap-dev libwireshark-dev - cd ~ + cd $BUILD_DIR hg clone https://bitbucket.org/barnstorm/of-dissector cd of-dissector/src export WIRESHARK=/usr/include/wireshark @@ -201,14 +231,14 @@ function wireshark { echo "Copied openflow plugin to $WSPLUGDIR" else # Install older version from reference source - cd ~/openflow/utilities/wireshark_dissectors/openflow + cd $BUILD_DIR/openflow/utilities/wireshark_dissectors/openflow make sudo make install fi # Copy coloring rules: OF is white-on-blue: - mkdir -p ~/.wireshark - cp ~/mininet/util/colorfilters ~/.wireshark + mkdir -p $HOME/.wireshark + cp $MININET_DIR/mininet/util/colorfilters $HOME/.wireshark } @@ -266,7 +296,7 @@ function ovs { if [ $ovspresent = 1 ]; then echo "Done (hopefully) installing packages" - cd ~ + cd $BUILD_DIR return fi @@ -289,7 +319,7 @@ function ovs { fi # Install OVS from release - cd ~/ + cd $BUILD_DIR/ git clone git://openvswitch.org/openvswitch $OVS_SRC cd $OVS_SRC git checkout $OVS_TAG @@ -348,7 +378,7 @@ function nox { $install libsqlite3-dev python-simplejson # Fetch NOX destiny - cd ~/ + cd $BUILD_DIR/ git clone https://github.com/noxrepo/nox-classic.git noxcore cd noxcore if ! git checkout -b destiny remotes/origin/destiny ; then @@ -357,9 +387,9 @@ function nox { # Apply patches git checkout -b tutorial-destiny - git am ~/mininet/util/nox-patches/*tutorial-port-nox-destiny*.patch + git am $MININET_DIR/mininet/util/nox-patches/*tutorial-port-nox-destiny*.patch if [ "$DIST" = "Ubuntu" ] && [ `expr $RELEASE '>=' 12.04` = 1 ]; then - git am ~/mininet/util/nox-patches/*nox-ubuntu12-hacks.patch + git am $MININET_DIR/mininet/util/nox-patches/*nox-ubuntu12-hacks.patch fi # Build @@ -371,17 +401,51 @@ function nox { #make check # Add NOX_CORE_DIR env var: - sed -i -e 's|# for examples$|&\nexport NOX_CORE_DIR=~/noxcore/build/src|' ~/.bashrc + sed -i -e 's|# for examples$|&\nexport NOX_CORE_DIR=$BUILD_DIR/noxcore/build/src|' ~/.bashrc # To verify this install: #cd ~/noxcore/build/src #./nox_core -v -i ptcp: } +# Install NOX Classic/Zaku for OpenFlow 1.3 +function nox13 { + echo "Installing NOX w/tutorial files..." + + # Install NOX deps: + $install autoconf automake g++ libtool python python-twisted \ + swig libssl-dev make + if [ "$DIST" = "Debian" ]; then + $install libboost1.35-dev + elif [ "$DIST" = "Ubuntu" ]; then + $install python-dev libboost-dev + $install libboost-filesystem-dev + $install libboost-test-dev + fi + + # Fetch NOX destiny + cd $BUILD_DIR/ + git clone https://github.com/CPqD/nox13oflib.git + cd nox13oflib + + # Build + ./boot.sh + mkdir build + cd build + ../configure + make -j3 + #make check + + # To verify this install: + #cd ~/nox13oflib/build/src + #./nox_core -v -i ptcp: +} + + # "Install" POX function pox { - echo "Installing POX into $HOME/pox..." - cd ~ + echo "Installing POX into $BUILD_DIR/pox..." + cd $BUILD_DIR git clone https://github.com/noxrepo/pox.git } @@ -393,11 +457,8 @@ function oftest { $install tcpdump python-scapy # Install oftest: - cd ~/ + cd $BUILD_DIR/ git clone git://github.com/floodlight/oftest - cd oftest - cd tools/munger - sudo make install } # Install cbench @@ -405,25 +466,56 @@ function cbench { echo "Installing cbench..." $install libsnmp-dev libpcap-dev libconfig-dev - cd ~/ + cd $BUILD_DIR/ git clone git://openflow.org/oflops.git cd oflops sh boot.sh || true # possible error in autoreconf, so run twice sh boot.sh - ./configure --with-openflow-src-dir=$HOME/openflow + ./configure --with-openflow-src-dir=$BUILD_DIR/openflow make sudo make install || true # make install fails; force past this } -function other { - echo "Doing other setup tasks..." +function vm_other { + echo "Doing other Mininet VM setup tasks..." + + # Remove avahi-daemon, which may cause unwanted discovery packets to be + # sent during tests, near link status changes: + echo "Removing avahi-daemon" + $remove avahi-daemon + + # was: Disable IPv6. Add to /etc/modprobe.d/blacklist: + #echo "Attempting to disable IPv6" + #if [ "$DIST" = "Ubuntu" ]; then + # BLACKLIST=/etc/modprobe.d/blacklist.conf + #else + # BLACKLIST=/etc/modprobe.d/blacklist + #fi + #sudo sh -c "echo 'blacklist net-pf-10\nblacklist ipv6' >> $BLACKLIST" + + # Disable IPv6 + if ! grep 'disable IPv6' /etc/sysctl.conf; then + echo 'Disabling IPv6' + echo ' +# Mininet: disable IPv6 +net.ipv6.conf.all.disable_ipv6 = 1 +net.ipv6.conf.default.disable_ipv6 = 1 +net.ipv6.conf.lo.disable_ipv6 = 1' | sudo tee -a /etc/sysctl.conf > /dev/null + fi + # Disabling IPv6 breaks X11 forwarding via ssh + line='AddressFamily inet' + file='/etc/ssh/sshd_config' + echo "Adding $line to $file" + if ! grep "$line" $file > /dev/null; then + echo "$line" | sudo tee -a $file > /dev/null + fi # Enable command auto completion using sudo; modify ~/.bashrc: sed -i -e 's|# for examples$|&\ncomplete -cf sudo|' ~/.bashrc - # Install tcpdump and tshark, cmd-line packet dump tools. Also install gitk, + # Install tcpdump, cmd-line packet dump tool. Also install gitk, # a graphical git history viewer. - $install tcpdump tshark gitk + $install tcpdump gitk # Install common text editors $install vim nano emacs @@ -471,8 +563,6 @@ function all { pox oftest cbench - other - echo "Please reboot, then run ./mininet/util/install.sh -c to remove unneeded packages." echo "Enjoy Mininet!" } @@ -480,6 +570,7 @@ function all { function vm_clean { echo "Cleaning VM..." sudo apt-get clean + sudo apt-get autoremove sudo rm -rf /tmp/* sudo rm -rf openvswitch*.tar.gz @@ -500,16 +591,20 @@ function vm_clean { git config --global user.name "None" git config --global user.email "None" - # Remove mininet install script - rm -f install-mininet.sh + # Note: you can shrink the .vmdk in vmware using + # vmware-vdiskmanager -k *.vmdk + echo "Zeroing out disk blocks for efficient compaction..." + time sudo dd if=/dev/zero of=/tmp/zero bs=1M + sync ; sleep 1 ; sync ; sudo rm -f /tmp/zero + } function usage { - printf 'Usage: %s [-acdfhkmntvxy]\n\n' $(basename $0) >&2 + printf '\nUsage: %s [-abcdfhkmnprtvwx03]\n\n' $(basename $0) >&2 printf 'This install script attempts to install useful packages\n' >&2 - printf 'for Mininet. It should (hopefully) work on Ubuntu 10.04, 11.10\n' >&2 - printf 'and Debian 5.0 (Lenny). If you run into trouble, try\n' >&2 + printf 'for Mininet. It should (hopefully) work on Ubuntu 11.10+\n' >&2 + printf 'If you run into trouble, try\n' >&2 printf 'installing one thing at a time, and looking at the \n' >&2 printf 'specific installation function in this script.\n\n' >&2 @@ -523,38 +618,55 @@ function usage { printf -- ' -k: install new (K)ernel\n' >&2 printf -- ' -m: install Open vSwitch kernel (M)odule from source dir\n' >&2 printf -- ' -n: install mini(N)et dependencies + core files\n' >&2 + printf -- ' -p: install (P)OX OpenFlow Controller\n' >&2 printf -- ' -r: remove existing Open vSwitch packages\n' >&2 - printf -- ' -t: install o(T)her stuff\n' >&2 + printf -- ' -t: complete o(T)her Mininet VM setup tasks\n' >&2 printf -- ' -v: install open (V)switch\n' >&2 printf -- ' -w: install OpenFlow (w)ireshark dissector\n' >&2 - printf -- ' -x: install NO(X) OpenFlow controller\n' >&2 - printf -- ' -y: install (A)ll packages\n' >&2 - + printf -- ' -x: install NO(X) Classic OpenFlow controller\n' >&2 + printf -- ' -0: (default) -0[fx] installs OpenFlow 1.0 versions\n' >&2 + printf -- ' -3: -3[fx] installs OpenFlow 1.3 versions\n' >&2 + printf -- ' -i < directory >: sets the (I)nstallation directory for Mininet dependencies\n' >&2 exit 2 } +OF_VERSION=1.0 + if [ $# -eq 0 ] then all else - while getopts 'abcdfhkmnprtvwx' OPTION + while getopts 'abcdfhkmnprtvwx03i:' OPTION do case $OPTION in a) all;; b) cbench;; c) kernel_clean;; d) vm_clean;; - f) of;; + f) case $OF_VERSION in + 1.0) of;; + 1.3) of13;; + *) echo "Invalid OpenFlow version $OF_VERSION";; + esac;; h) usage;; k) kernel;; m) modprobe;; n) mn_deps;; p) pox;; r) remove_ovs;; - t) other;; + t) vm_other;; v) ovs;; w) wireshark;; - x) nox;; + x) case $OF_VERSION in + 1.0) nox;; + 1.3) nox13;; + *) echo "Invalid OpenFlow version $OF_VERSION";; + esac;; + 0) OF_VERSION=1.0;; + 3) OF_VERSION=1.3;; + i) mkdir -p $OPTARG; # ensure the directory is created + BUILD_DIR="$( cd -P "$OPTARG" && pwd )"; # get the full path of the directory + echo "Dependency installation directory: $BUILD_DIR";; ?) usage;; esac done diff --git a/util/m b/util/m new file mode 100755 index 0000000..786dcce --- /dev/null +++ b/util/m @@ -0,0 +1,36 @@ +#!/bin/bash + +# Attach to a Mininet host and run a command + +if [ -z $1 ]; then + echo "usage: $0 host cmd [args...]" + exit 1 +else + host=$1 +fi + +pid=`ps ax | grep "mininet:$host$" | grep bash | awk '{print $1};'` + +if echo $pid | grep -q ' '; then + echo "Error: found multiple mininet:$host processes" + exit 2 +fi + +if [ "$pid" == "" ]; then + echo "Could not find Mininet host $host" + exit 3 +fi + +if [ -z $2 ]; then + cmd='bash' +else + shift + cmd=$* +fi + +cgroup=/sys/fs/cgroup/cpu/$host +if [ -d "$cgroup" ]; then + cg="-g $host" +fi + +exec sudo mnexec -a $pid $cg $cmd diff --git a/util/sysctl_addon b/util/sysctl_addon index e26eecc..d413964 100644 --- a/util/sysctl_addon +++ b/util/sysctl_addon @@ -15,3 +15,4 @@ net.ipv4.neigh.default.gc_thresh3 = 16384 # Mininet: increase routing table size net.ipv4.route.max_size=32768 + diff --git a/util/vm/install-mininet-vm.sh b/util/vm/install-mininet-vm.sh index 382945e..aa93de4 100755 --- a/util/vm/install-mininet-vm.sh +++ b/util/vm/install-mininet-vm.sh @@ -4,15 +4,19 @@ # a brand-new Ubuntu virtual machine, # to create a fully usable "tutorial" VM. set -e -echo `whoami` ALL=NOPASSWD: ALL | sudo tee -a /etc/sudoers +echo `whoami` ALL=NOPASSWD: ALL | sudo tee -a /etc/sudoers > /dev/null sudo sed -i -e 's/Default/#Default/' /etc/sudoers -sudo sed -i -e 's/ubuntu/mininet-vm/' /etc/hostname +echo mininet-vm | sudo tee /etc/hostname > /dev/null sudo sed -i -e 's/ubuntu/mininet-vm/g' /etc/hosts sudo hostname `cat /etc/hostname` sudo sed -i -e 's/quiet splash/text/' /etc/default/grub sudo update-grub +# 12.10 and earlier sudo sed -i -e 's/us.archive.ubuntu.com/mirrors.kernel.org/' \ /etc/apt/sources.list +# 13.04 and later +sudo sed -i -e 's/\/archive.ubuntu.com/\/mirrors.kernel.org/' \ + /etc/apt/sources.list sudo apt-get update # Clean up vmware easy install junk if present if [ -e /etc/issue.backup ]; then @@ -27,14 +31,11 @@ git clone git://github.com/mininet/mininet cd mininet cd time mininet/util/install.sh +# Finalize VM +time mininet/util/install.sh -tc # Ignoring this since NOX classic is deprecated #if ! grep NOX_CORE_DIR .bashrc; then # echo "export NOX_CORE_DIR=~/noxcore/build/src/" >> .bashrc #fi -echo <