diff --git a/INSTALL b/INSTALL index fc556c80e..a03af195b 100644 --- a/INSTALL +++ b/INSTALL @@ -1,7 +1,7 @@ -Preliminary Mininet Installation/Configuration Notes +Mininet Installation/Configuration Notes -Mininet 1.0rc0 +Mininet 1.0.0 --- @@ -20,20 +20,22 @@ Boot up the VM image, log in, and follow the instructions on the wiki page. An additional advantage of using the VM image is that it doesn't mess with your native OS installation or damage it in any way. -2. Native installation (experimental!) for Ubuntu and Debian 5 +2. Native installation (experimental!) for Ubuntu 10.04 LTS -If you are running Ubuntu or Debian 5, you may be able to use our handy -install.sh script, which is in mininet/util. +If you are running Ubuntu 10.04 LTS (or possibly Debian 5), you may be +able to use our handy install.sh script, which is in mininet/util. WARNING: USE AT YOUR OWN RISK! -install.sh is a bit intrusive and may possibly damage your OS -and/or home directory. 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! +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! -To install ALL of the software which we use for OpenFlow tutorials, you may -use +To install ALL of the software which we use for OpenFlow tutorials, +you may use $ mininet/util/install.sh @@ -43,9 +45,9 @@ Alternately, you can install just the pieces you need. We recommend the following steps, in order: -a) On Debian 5, first install a Mininet-compatible kernel: +[a) On Debian 5, first install a Mininet-compatible kernel: $ mininet/util/install.sh -k - Reboot and run 'uname -r' to make sure you're running the new kernel. + Reboot and run 'uname -r' to make sure you're running the new kernel.] b) Install mininet and its dependencies: $ mininet/util/install.sh -n @@ -59,6 +61,10 @@ d) Install Open vSwitch and its kernel module e) If you wish to install the version of NOX we use in the tutorial: $ mininet/util/install.sh -x + Note: NOX development is progressing over time, so after you complete + the tutorial you may wish to install the latest and greatest NOX from + noxrepo.org. + Good luck! Some additional installation notes are provided below, for the brave and/or Linux-savvy, or those who are trying to understand what is installed and why. diff --git a/LICENSE b/LICENSE index d81e2d328..7546c7f58 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ -Mininet Pre-Beta License +Mininet 1.0.0 License -Copyright (c) 2009-2010 Bob Lantz and Brandon Heller +Copyright (c) 2009-2011 Bob Lantz and Brandon Heller We are making Mininet available for public use and benefit with the expectation that others will use, modify and enhance the Software and diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 000000000..8753c415d --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +include mnexec.c +exclude bin/mnexec diff --git a/README b/README index 336b6693d..dc8d25f5d 100644 --- a/README +++ b/README @@ -1,9 +1,9 @@ - Mininet: A Simple Virtual Testbed for OpenFlow + Mininet: A Simple Virtual Testbed for OpenFlow/SDN or How to Squeeze a 1024-node OpenFlow Network onto your Laptop -Mininet 1.0rc0 +Mininet 1.0.0 --- Welcome to Mininet! @@ -74,9 +74,10 @@ Batteries are not included (yet!) However, some preliminary installation notes are included in the INSTALL file. -Additionally, much useful information is available on the Mininet wiki: +Additionally, much useful information, including a Mininet tutorial, +is available on the Mininet wiki: -http://www.openflowswitch.org/foswiki/bin/view/OpenFlow/Mininet +http://openflow.org/mininet Enjoy, and good luck! diff --git a/bin/mn b/bin/mn index 1a63737ad..eac21815b 100755 --- a/bin/mn +++ b/bin/mn @@ -22,6 +22,7 @@ from mininet.log import lg, LEVELS, info from mininet.net import Mininet, init from mininet.node import KernelSwitch, Host, Controller, ControllerParams, NOX from mininet.node import RemoteController, UserSwitch, OVSKernelSwitch +from mininet.node import OVSUserSwitch from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo from mininet.topolib import TreeTopo from mininet.util import makeNumeric @@ -37,7 +38,8 @@ TOPOS = { 'minimal': lambda: SingleSwitchTopo( k=2 ), SWITCHDEF = 'ovsk' SWITCHES = { 'kernel': KernelSwitch, 'user': UserSwitch, - 'ovsk': OVSKernelSwitch } + 'ovsk': OVSKernelSwitch, + 'ovsu': OVSUserSwitch } HOSTDEF = 'process' HOSTS = { 'process': Host } @@ -45,6 +47,7 @@ HOSTS = { 'process': Host } CONTROLLERDEF = 'ref' # a and b are the name and inNamespace params. CONTROLLERS = { 'ref': Controller, + 'ovs': lambda name: Controller( name, command='ovs-controller' ), 'nox_dump': lambda name: NOX( name, 'packetdump' ), 'nox_pysw': lambda name: NOX( name, 'pyswitch' ), 'remote': lambda name: None, diff --git a/mininet/clean.py b/mininet/clean.py index 3052e979e..2cd453425 100755 --- a/mininet/clean.py +++ b/mininet/clean.py @@ -24,10 +24,10 @@ 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" + info("*** Removing excess controllers/switches/pings/noxes" "\n") zombies = 'controller ofprotocol ofdatapath ping nox_core lt-nox_core ' - zombies += 'ovs-openflowd udpbwtest' + zombies += 'ovs-openflowd udpbwtest ovs-openflowd ovs-controller' # 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. @@ -42,9 +42,14 @@ def cleanup(): 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 != '': + if dp: sh( 'dpctl deldp ' + dp ) + ovsdps = sh( "ovs-dpctl show | egrep '\w+@\w+:'" ).split( '\n' ) + for dp in dps: + if dp: + sh( 'ovs-dpctl del-dp ' + 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: diff --git a/mininet/cli.py b/mininet/cli.py index 3fbedd19b..cf6b5e8ba 100644 --- a/mininet/cli.py +++ b/mininet/cli.py @@ -212,6 +212,22 @@ def do_link( self, line ): else: self.mn.configLinkStatus( *args ) + def do_attach( self, line ): + "Create new link between a host and a switch" + args = line.split() + if len(args) != 2: + error( 'invalid number of args: attach host switch\n' ) + else: + self.mn.attachHost(*args) + + def do_detach( self, line ): + "Detach a host from a switch" + args = line.split() + if len(args) < 1 or len(args) > 2: + error( 'invalid number of args: detach host [switch]\n' ) + else: + self.mn.detachHost(*args) + def do_xterm( self, line, term='xterm' ): "Spawn xterm(s) for the given node(s)." args = line.split() @@ -300,9 +316,17 @@ def default( self, line ): 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 ] + for index in range(len(rest)): + arg = rest[index] + if arg in self.nodemap: + ip = self.nodemap[arg].IP() + if not ip: + error('%s is an unreachable, detached host\n' % arg) + return + rest[index] = ip + #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 ) @@ -321,10 +345,6 @@ def waitForNode( self, node ): bothPoller = poll() bothPoller.register( self.stdin ) bothPoller.register( node.stdout ) - if self.isatty(): - # Buffer by character, so that interactive - # commands sort of work - quietRun( 'stty -icanon min 1' ) while True: try: bothPoller.poll() diff --git a/mininet/moduledeps.py b/mininet/moduledeps.py index 15b575dc4..fcb03275e 100644 --- a/mininet/moduledeps.py +++ b/mininet/moduledeps.py @@ -22,7 +22,7 @@ def modprobe( mod ): OVS_KMOD = 'openvswitch_mod' TUN = 'tun' -def moduleDeps( subtract=None, add=None ): +def moduleDeps( subtract=None, add=None, moduleName='it' ): """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""" @@ -47,9 +47,10 @@ def moduleDeps( subtract=None, add=None ): 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 ) + error( 'Error inserting ' + mod + '\n' + 'Is %s installed and available via modprobe?\n' % + moduleName + + 'Error was: "%s"\n' % modprobeOutput.strip() ) if mod not in lsmod(): error( 'Failed to insert ' + mod + ' - quitting.\n' ) exit( 1 ) diff --git a/mininet/net.py b/mininet/net.py index 973a694c8..29f6c36f5 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -94,8 +94,8 @@ from mininet.cli import CLI from mininet.log import info, error, debug, output -from mininet.node import Host, UserSwitch, OVSKernelSwitch, Controller -from mininet.node import ControllerParams +from mininet.node import Host, Switch, UserSwitch, OVSKernelSwitch, RemoteSwitch +from mininet.node import Controller, ControllerParams from mininet.util import quietRun, fixLimits from mininet.util import createLink, macColonHex, ipStr, ipParse from mininet.term import cleanUpScreens, makeTerms @@ -181,6 +181,16 @@ def addSwitch( self, name, mac=None, ip=None ): self.nameToNode[ name ] = sw return sw + def addRemoteSwitch( self, name, remotePorts, dpid=None ): + """Add a RemoteSwitch. + name: name of switch to add + remotePorts: kernel interface name for each switch port + returns: added switch""" + sw = RemoteSwitch( name, dpid=dpid, remotePorts=remotePorts ) + self.switches.append( sw ) + self.nameToNode[ name ] = sw + return sw + def addController( self, name='c0', controller=None, **kwargs ): """Add controller. controller: Controller class""" @@ -546,6 +556,70 @@ def configLinkStatus( self, src, dst, status ): if result: error( 'link dst status change failed: %s\n' % result ) + def attachHost( self, hostName, switchName ): + if hostName not in self.nameToNode: + error( 'host not in network: %s\n' % hostName ) + return + + if switchName not in self.nameToNode: + error( 'switch not in network: %s\n' % switchName ) + return + + host = self.nameToNode[hostName] + if not isinstance(host, Host): + error('%s is not a host' % hostName) + return + + sw = self.nameToNode[switchName] + if not isinstance(sw, Switch): + error('%s is not a switch' % switchName) + return + + if not isinstance(sw, OVSKernelSwitch): + error('attachHost only works with OVS kernel switches') + return + + hostIntf, swIntf = host.linkTo(sw) + + host.setIP( hostIntf, host.defaultIP, self.cparams.prefixLen ) + host.setDefaultRoute( hostIntf ) + if self.autoSetMacs: + host.setMAC( hostIntf, host.defaultMAC ) + #if self.autoStaticArp: + # for h in self.hosts: + # if h != host: + # host.setARP( ip=h.IP(), mac=h.MAC() ) + # h.setARP( ip=host.IP(), mac=host.MAC() ) + + + def detachHost( self, hostName, switchName=None ): + if hostName not in self.nameToNode: + error( 'host not in network: %s\n' % hostName ) + return + + host = self.nameToNode[hostName] + if not isinstance(host, Host): + error('%s is not a host' % hostName) + return + + if switchName: + if switchName not in self.nameToNode: + error( 'switch not in network: %s\n' % switchName ) + return + + sw = self.nameToNode[switchName] + if not isinstance(sw, Switch): + error('%s is not a switch' % switchName) + return + + if not isinstance(sw, OVSKernelSwitch): + error('attachHost only works with OVS kernel switches') + return + else: + sw = None + + host.unlinkFrom(sw) + def interact( self ): "Start network and run our simple CLI." self.start() @@ -561,16 +635,11 @@ def init(): "Initialize Mininet." if init.inited: return - if os.getuid() != 0: - # Note: this script must be run as root - # Perhaps we should do so automatically! - print "*** Mininet must run as root." - exit( 1 ) # If which produces no output, then mnexec is not in the path. # May want to loosen this to handle mnexec in the current dir. if not quietRun( 'which mnexec' ): raise Exception( "Could not find mnexec - check $PATH" ) - fixLimits() + #fixLimits() init.inited = True init.inited = False diff --git a/mininet/node.py b/mininet/node.py index 828646661..c9bf2b507 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -42,6 +42,7 @@ """ import os +import pty import re import signal import select @@ -76,12 +77,16 @@ def __init__( self, name, inNamespace=True, opts = '-cdp' if self.inNamespace: opts += 'n' - cmd = [ 'mnexec', opts, 'bash', '-m' ] - self.shell = Popen( cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT, + cmd = [ 'sudo', '-E', 'env', 'PATH=%s' % os.environ['PATH'], + 'PS1=' + chr( 127 ), 'mnexec', opts, 'bash', '--norc' ] + # Spawn a shell subprocess in a pseudo-tty, to disable buffering + # in the subprocess and insulate it from signals (e.g. SIGINT) + # received by the parent + master, slave = pty.openpty() + self.shell = Popen( cmd, stdin=slave, stdout=slave, stderr=slave, close_fds=False ) - self.stdin = self.shell.stdin - self.stdout = self.shell.stdout - self.pid = self.shell.pid + self.stdin = os.fdopen( master ) + self.stdout = self.stdin self.pollOut = select.poll() self.pollOut.register( self.stdout ) # Maintain mapping between file descriptors and nodes @@ -102,6 +107,12 @@ def __init__( self, name, inNamespace=True, self.waiting = False # Stash additional information as desired self.args = kwargs + x = "" + while "\n" not in x: + self.waitReadable() + x += self.read(1) + self.pid = int(x[1:-1]) + self.serial = 0 @classmethod def fdToNode( cls, fd ): @@ -149,7 +160,7 @@ def write( self, data ): def terminate( self ): "Send kill signal to Node and clean up after it." - os.kill( self.pid, signal.SIGKILL ) + quietRun( 'kill ' + str( self.pid ) ) self.cleanup() def stop( self ): @@ -163,11 +174,25 @@ def waitReadable( self, timeoutms=None ): 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. + """Send a command, and return without waiting for the command + to complete. args: command and arguments, or string printPid: print command's PID?""" assert not self.waiting + self.serial += 1 + self.write( 'echo __ %s __\n' % self.serial ) + match = '__ %s __' % self.serial + buf = '' + while True: + i = buf.find( match ) + if i >= 0: + buf = buf[ i + len( match ): ] + break + buf += self.read( 1024 ) + while True: + if chr( 127 ) in buf: + break + buf += self.read( 1024 ) printPid = kwargs.get( 'printPid', True ) if len( args ) > 0: cmd = args @@ -176,25 +201,16 @@ def sendCmd( self, *args, **kwargs ): if not re.search( r'\w', cmd ): # Replace empty commands with something harmless cmd = 'echo -n' - if len( cmd ) > 0 and cmd[ -1 ] == '&': - separator = '&' - cmd = cmd[ :-1 ] - else: - separator = ';' - if printPid and not isShellBuiltin( cmd ): - cmd = 'mnexec -p ' + cmd - self.write( cmd + separator + ' printf "\\177" \n' ) + if printPid and not isShellBuiltin( cmd ): + cmd = 'mnexec -p ' + cmd + self.write( cmd + '\n' ) self.lastCmd = cmd 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 + self.write( chr( 3 ) ) def monitor( self, timeoutms=None ): """Monitor and return the output of a command. @@ -203,7 +219,7 @@ def monitor( self, timeoutms=None ): self.waitReadable( timeoutms ) data = self.read( 1024 ) # Look for PID - marker = chr( 1 ) + r'\d+\n' + marker = chr( 1 ) + r'\d+\r\n' if chr( 1 ) in data: markers = re.findall( marker, data ) if markers: @@ -218,15 +234,17 @@ def monitor( self, timeoutms=None ): data = data.replace( chr( 127 ), '' ) return data - def waitOutput( self, verbose=False ): - """Wait for a command to complete. + def waitOutput( self, verbose=False, pattern=None ): + """Wait for a command to complete or generate certain output. 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""" + appearing in the output stream. Wait for the sentinel or + output matched by a certain pattern, and return the output. + verbose: print output interactively + pattern: compiled regexp or None""" log = info if verbose else debug output = '' - while self.waiting: + while self.waiting and (pattern is None or + not pattern.search(output)): data = self.monitor() output += data log( data ) @@ -258,6 +276,12 @@ def intfName( self, n ): "Construct a canonical interface name node-ethN for interface n." return self.name + '-eth' + repr( n ) + def intfToPort(self, intf): + index = intf.rfind('-eth') + if index < 0: + return None + return int(intf[index + len('-eth'):]) + def newPort( self ): "Return the next port number to allocate." if len( self.ports ) > 0: @@ -317,6 +341,51 @@ def linkTo( self, node2, port1=None, port2=None ): node2.registerIntf( intf2, node1, intf1 ) return intf1, intf2 + def unlinkFrom( self, node2=None ): + if node2: + unlinkList = [node2] + else: + unlinkList = [c[0] for c in self.connection.values()] + + for node in unlinkList: + self.deleteIntfsToNode(node) + node.deleteIntfsToNode(self) + + def deleteIntfsToNode( self, dstNode, dstPort=None ): + + dstIntf = dstNode.intfName(dstPort) if dstPort else None + intfs = [] + for intf in self.connection.keys(): + nextNode, nextIntf = self.connection[intf] + if dstIntf: + if nextIntf == dstIntf: + intfs.append(intf) + elif nextNode == dstNode: + intfs.append(intf) + + #connections = self.connectionsTo(dstNode) + #if dstPort: + # dstIntf = dstNode.intfName(dstPort) + # intfs = [connection[0] for connection in connections if connection[1] == dstIntf] + #else: + # intfs = [connection[0] for connection in connections] + + for intf in intfs: + self.deleteIntf(intf) + + def deleteIntf(self, intf): + del self.connection[intf] + port = self.intfToPort(intf) + if port is not None: + del self.intfs[port] + del self.ports[intf] + + quietRun( 'ip link del ' + intf ) + sleep( 0.001 ) + + def deletePort(self, port): + self.deleteIntf(self.intfName(port)) + def deleteIntfs( self ): "Delete all of our interfaces." # In theory the interfaces should go away after we shut down. @@ -433,12 +502,18 @@ class Switch( Node ): portBase = SWITCH_PORT_BASE # 0 for OF < 1.0, 1 for OF >= 1.0 - def __init__( self, name, opts='', listenPort=None, **kwargs): + def __init__( self, name, opts='', listenPort=None, dpid=None, **kwargs): Node.__init__( self, name, **kwargs ) self.opts = opts self.listenPort = listenPort if self.listenPort: self.opts += ' --listen=ptcp:%i ' % self.listenPort + if dpid: + self.dpid = dpid + elif self.defaultMAC: + self.dpid = "00:00:" + self.defaultMAC + else: + self.dpid = None def defaultIntf( self ): "Return interface for HIGHEST port" @@ -447,6 +522,12 @@ def defaultIntf( self ): intf = self.intfs[ max( ports ) ] return intf + def startIntfs( self ): + "Default function to start interfaces" + self.cmd("ifconfig lo up") + for intf in self.intfs.values(): + self.cmd("ifconfig " + intf + " up" ) + def sendCmd( self, *cmd, **kwargs ): """Send command to Node. cmd: string""" @@ -466,6 +547,8 @@ def __init__( self, name, **kwargs ): Switch.__init__( self, name, **kwargs ) pathCheck( 'ofdatapath', 'ofprotocol', moduleName='the OpenFlow reference user switch (openflow.org)' ) + self.cmd( 'kill %ofdatapath' ) + self.cmd( 'kill %ofprotocol' ) @staticmethod def setup(): @@ -480,7 +563,7 @@ def start( self, controllers ): controller = controllers[ 0 ] ofdlog = '/tmp/' + self.name + '-ofd.log' ofplog = '/tmp/' + self.name + '-ofp.log' - self.cmd( 'ifconfig lo up' ) + self.startIntfs() mac_str = '' if self.defaultMAC: # ofdatapath expects a string of hex digits with no colons. @@ -522,15 +605,15 @@ def __init__( self, name, dp=None, **kwargs ): @staticmethod def setup(): "Ensure any dependencies are loaded; if not, try to load them." - pathCheck( 'ofprotocol', - moduleName='the OpenFlow reference kernel switch' - ' (openflow.org) (NOTE: not available in OpenFlow 1.0!)' ) - moduleDeps( subtract=OVS_KMOD, add=OF_KMOD ) + moduleName=('the OpenFlow reference kernel switch' + ' (openflow.org) (NOTE: not available in OpenFlow 1.0+!)' ) + pathCheck( 'ofprotocol', moduleName=moduleName) + moduleDeps( subtract=OVS_KMOD, add=OF_KMOD, moduleName=moduleName ) def start( self, controllers ): "Start up reference kernel datapath." ofplog = '/tmp/' + self.name + '-ofp.log' - quietRun( 'ifconfig lo up' ) + self.startIntfs() # Delete local datapath if it exists; # then create a new one monitoring the given interfaces quietRun( 'dpctl deldp ' + self.dp ) @@ -578,14 +661,14 @@ def __init__( self, name, dp=None, **kwargs ): @staticmethod def setup(): "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 ) + moduleName='Open vSwitch (openvswitch.org)' + pathCheck( 'ovs-dpctl', 'ovs-openflowd', moduleName=moduleName ) + moduleDeps( subtract=OF_KMOD, add=OVS_KMOD, moduleName=moduleName ) def start( self, controllers ): "Start up kernel datapath." ofplog = '/tmp/' + self.name + '-ofp.log' - quietRun( 'ifconfig lo up' ) + self.startIntfs() # Delete local datapath if it exists; # then create a new one monitoring the given interfaces quietRun( 'ovs-dpctl del-dp ' + self.dp ) @@ -616,6 +699,89 @@ def stop( self ): self.cmd( 'kill %ovs-openflowd' ) self.deleteIntfs() + def addIntf( self, intf, port ): + super(OVSKernelSwitch, self).addIntf(intf, port) + self.cmd( 'ovs-dpctl', 'add-if', self.dp, intf ) + + def deleteIntf( self, intf ): + super(OVSKernelSwitch, self).deleteIntf(intf) + self.cmd( 'ovs-dpctl', 'del-if', self.dp, intf ) + +class OVSUserSwitch( Switch ): + """Open VSwitch kernel-space switch. + 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 = 'netdev@dp%i' % dp + self.intf = self.dp + if self.inNamespace: + error( "OVSUserSwitch currently only works" + " in the root namespace.\n" ) + exit( 1 ) + + @staticmethod + def setup(): + "Ensure any dependencies are loaded; if not, try to load them." + pathCheck( 'ovs-dpctl', 'ovs-openflowd', + moduleName='Open vSwitch (openvswitch.org)') + if not os.path.exists( '/dev/net/tun' ): + moduleDeps( add=TUN ) + + def start( self, controllers ): + "Start up kernel datapath." + ofplog = '/tmp/' + self.name + '-ofp.log' + self.startIntfs() + mac_str = '' + if self.defaultMAC: + # ovs-openflowd expects a string of exactly 16 hex digits with no + # colons. + mac_str = ' --datapath-id=0000' + \ + ''.join( self.defaultMAC.split( ':' ) ) + ' ' + ports = sorted( self.ports.values() ) + if len( ports ) != ports[ -1 ] + 1 - self.portBase: + raise Exception( 'only contiguous, one-indexed port ranges ' + 'supported: %s' % self.intfs ) + intfs = [ self.intfs[ port ] for port in ports ] + # self.cmd( 'ovs-dpctl', 'add-if', self.dp, ' '.join( intfs ) ) + # Run protocol daemon + controller = controllers[ 0 ] + self.cmd( 'ovs-openflowd -v ' + self.dp + + ' --ports=' + ','.join(intfs) + + ' tcp:%s:%d' % ( controller.IP(), controller.port ) + + ' --fail=secure ' + self.opts + mac_str + + ' 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 RemoteSwitch( Switch ): + "Switch created outside mininet." + + def __init__( self, name, remotePorts, **kwargs ): + Switch.__init__( self, name, inNamespace=False, **kwargs ) + self.remotePorts = remotePorts + + @staticmethod + def setup(): + pass + + def start( self, controllers ): + self.startIntfs() + for port, intf in self.intfs.items(): + self.cmd( 'brctl', 'addif', self.remotePorts[ port ], intf ) + + def stop(self): + self.deleteIntfs() + class Controller( Node ): """A Controller is a Node that is running (or has execed?) an diff --git a/mininet/util.py b/mininet/util.py index 75065ead8..a543501f7 100644 --- a/mininet/util.py +++ b/mininet/util.py @@ -4,6 +4,7 @@ from resource import setrlimit, RLIMIT_NPROC, RLIMIT_NOFILE import select from subprocess import call, check_call, Popen, PIPE, STDOUT +import os from mininet.log import error @@ -29,6 +30,7 @@ def quietRun( *cmd ): cmd = cmd[ 0 ] if isinstance( cmd, str ): cmd = cmd.split( ' ' ) + cmd = ["sudo", "-E", "env", "PATH=%s" % os.environ["PATH"]] + cmd popen = Popen( cmd, stdout=PIPE, stderr=STDOUT ) # We can't use Popen.communicate() because it uses # select(), which can't handle diff --git a/setup.py b/setup.py index 0394245a3..302bb9e05 100644 --- a/setup.py +++ b/setup.py @@ -2,24 +2,31 @@ "Setuptools params" -from setuptools import setup, find_packages -from os.path import join - -scripts = [ join( 'bin', filename ) for filename in [ - 'mn', 'mnexec' ] ] +import os, setuptools +import distutils.command.build_scripts +import distutils.command.clean +import distutils.ccompiler modname = distname = 'mininet' -setup( +class build_scripts(distutils.command.build_scripts.build_scripts): + def run(self): + distutils.ccompiler.new_compiler().link_executable(["mnexec.c"], "bin/mnexec") + distutils.command.build_scripts.build_scripts.run(self) + +class clean(distutils.command.clean.clean): + def run(self): + if os.path.exists("bin/mnexec"): + os.unlink("bin/mnexec") + distutils.command.clean.clean.run(self) + +setuptools.setup( name=distname, version='0.0.0', description='Process-based OpenFlow emulator', author='Bob Lantz', author_email='rlantz@cs.stanford.edu', - packages=find_packages(exclude='test'), - long_description=""" -Insert longer description here. - """, + packages=setuptools.find_packages(exclude='test'), classifiers=[ "License :: OSI Approved :: GNU General Public License (GPL)", "Programming Language :: Python", @@ -33,5 +40,10 @@ 'setuptools', 'networkx' ], - scripts=scripts, + scripts=[ + 'bin/mn', + 'bin/mnexec' + ], + cmdclass={"build_scripts": build_scripts, + "clean": clean} ) diff --git a/util/install.sh b/util/install.sh index 103660385..e15c42624 100755 --- a/util/install.sh +++ b/util/install.sh @@ -35,9 +35,8 @@ KERNEL_IMAGE_OLD=linux-image-2.6.26-2-686 DRIVERS_DIR=/lib/modules/${KERNEL_NAME}/kernel/drivers/net -#OVS_RELEASE=openvswitch-1.0.1 -OVS_RELEASE=openvswitch # release 1.0.1 doesn't work with veth pairs. -OVS_DIR=~/$OVS_RELEASE +OVS_RELEASE=v1.1.1 +OVS_DIR=~/openvswitch OVS_KMOD=openvswitch_mod.ko function kernel { @@ -85,6 +84,11 @@ function mn_deps { sudo aptitude install -y gcc make screen psmisc xterm ssh iperf iproute \ python-setuptools python-networkx + if [ "$DIST" = "Ubuntu" ] && [ "`lsb_release -sr`" = "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" @@ -111,6 +115,9 @@ function of { git clone git://openflowswitch.org/openflow.git cd ~/openflow + # Patch controller to handle more than 16 switches + patch -p1 < ~/mininet/util/openflow-patches/controller.patch + # Resume the install: ./boot.sh ./configure @@ -161,7 +168,8 @@ function ovs { #Install OVS from release cd ~/ git clone git://openvswitch.org/openvswitch - cd $OVS_RELEASE + cd $OVS_DIR + git checkout $OVS_RELEASE ./boot.sh BUILDDIR=/lib/modules/${KERNEL_NAME}/build if [ ! -e $BUILDDIR ]; then @@ -236,8 +244,9 @@ function cbench { sudo apt-get install -y libsnmp-dev libpcap-dev cd ~/ - git clone git://www.openflow.org/oflops.git + 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 make @@ -345,6 +354,7 @@ function usage { printf 'options:\n' >&2 printf -- ' -a: (default) install (A)ll packages - good luck!\n' >&2 + printf -- ' -b: install controller (B)enchmark (oflops)\n' >&2 printf -- ' -c: (C)lean up after kernel install\n' >&2 printf -- ' -d: (D)elete some sensitive files from a VM image\n' >&2 printf -- ' -f: install open(F)low\n' >&2 @@ -364,10 +374,11 @@ if [ $# -eq 0 ] then all else - while getopts 'acdfhkmntvx' OPTION + while getopts 'abcdfhkmntvx' OPTION do case $OPTION in - a) all;; + a) all;; + b) cbench;; c) kernel_clean;; d) vm_clean;; f) of;;