diff --git a/mininet/net.py b/mininet/net.py index 47b0f6cdae67cf85ae721f51187cdd50cc1dca6e..a4ed8a57f07680462359e01172c8ea7554e1f36c 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -90,10 +90,10 @@ from mininet.cli import CLI from mininet.log import info, error, debug -from mininet.node import Host, KernelSwitch, OVSKernelSwitch, Controller +from mininet.node import Host, UserSwitch, KernelSwitch, Controller from mininet.node import ControllerParams from mininet.util import quietRun, fixLimits -from mininet.util import createLink, macColonHex +from mininet.util import createLink, macColonHex, ipStr, ipParse from mininet.xterm import cleanUpScreens, makeXterms DATAPATHS = [ 'kernel' ] #[ 'user', 'kernel' ] @@ -130,7 +130,7 @@ def __init__( self, topo, switch=KernelSwitch, host=Host, xterms: if build now, spawn xterms? cleanup: if build now, cleanup before creating? inNamespace: spawn switches and controller in net namespaces? - autoSetMacs: set MAC addrs to DPIDs? + autoSetMacs: set MAC addrs from topo? autoStaticArp: set all-pairs static MAC addrs?""" self.switch = switch self.host = host @@ -151,8 +151,8 @@ def __init__( self, topo, switch=KernelSwitch, host=Host, self.dps = 0 # number of created kernel datapaths self.terms = [] # list of spawned xterm processes - if topo and build: - self.buildFromTopo( self.topo ) + if build: + self.build() def addHost( self, name, mac=None, ip=None ): """Add host. @@ -165,16 +165,18 @@ def addHost( self, name, mac=None, ip=None ): self.nameToNode[ name ] = host return host - def addSwitch( self, name, mac=None ): + def addSwitch( self, name, mac=None, ip=None ): """Add switch. name: name of switch to add mac: default MAC address for kernel/OVS switch intf 0 returns: added switch""" - if self.switch is KernelSwitch or self.switch is OVSKernelSwitch: - sw = self.switch( name, dp=self.dps, defaultMAC=mac ) - self.dps += 1 + if self.switch == UserSwitch: + sw = self.switch( name, defaultMAC=mac, defaultIP=ip, + inNamespace=self.inNamespace ) else: - sw = self.switch( name ) + sw = self.switch( name, defaultMAC=mac, defaultIP=ip, dp=self.dps, + inNamespace=self.inNamespace ) + self.dps += 1 self.switches.append( sw ) self.nameToNode[ name ] = sw return sw @@ -206,43 +208,41 @@ def addController( self, controller ): # useful for people who wish to simulate a separate control # network (since real networks may need one!) - def _configureControlNetwork( self ): + def configureControlNetwork( self ): "Configure control network." - self._configureRoutedControlNetwork() + self.configureRoutedControlNetwork() + + # We still need to figure out the right way to pass + # in the control network location. - def _configureRoutedControlNetwork( self ): + def configureRoutedControlNetwork( self, ip='192.168.123.1', + prefixLen=16 ): """Configure a routed control network on controller and switches. For use with the user datapath only right now. - TODO( brandonh ) test this code! """ - # params were: controller, switches, ips - controller = self.controllers[ 0 ] - info( '%s <-> ' % controller.name ) + info( controller.name + ' <->' ) + cip = ip + snum = ipParse( ip ) for switch in self.switches: - info( '%s ' % switch.name ) - sip = switch.defaultIP - sintf = switch.intfs[ 0 ] - node, cintf = switch.connection[ sintf ] - if node != controller: - error( '*** Error: switch %s not connected to correct' - 'controller' % - switch.name ) - exit( 1 ) - controller.setIP( cintf, self.cparams.ip, self.cparams.prefixLen ) - switch.setIP( sintf, sip, self.cparams.prefixLen ) + info( ' ' + switch.name ) + sintf, cintf = createLink( switch, controller ) + snum += 1 + while snum & 0xff in [ 0, 255 ]: + snum += 1 + sip = ipStr( snum ) + controller.setIP( cintf, cip, prefixLen ) + switch.setIP( sintf, sip, prefixLen ) controller.setHostRoute( sip, cintf ) - switch.setHostRoute( self.cparams.ip, sintf ) + switch.setHostRoute( cip, sintf ) info( '\n' ) info( '*** Testing control network\n' ) - while not controller.intfIsUp( controller.intfs[ 0 ] ): - info( '*** Waiting for %s to come up\n', - controller.intfs[ 0 ] ) + while not controller.intfIsUp( cintf ): + info( '*** Waiting for', cintf, 'to come up\n' ) sleep( 1 ) for switch in self.switches: - while not switch.intfIsUp( switch.intfs[ 0 ] ): - info( '*** Waiting for %s to come up\n' % - switch.intfs[ 0 ] ) + while not switch.intfIsUp( sintf ): + info( '*** Waiting for', sintf, 'to come up\n' ) sleep( 1 ) if self.ping( hosts=[ switch, controller ] ) != 0: error( '*** Error: control network test failed\n' ) @@ -265,42 +265,47 @@ def buildFromTopo( self, topo ): """Build mininet from a topology object At the end of this function, everything should be connected and up.""" + + def addNode( prefix, addMethod, nodeId ): + "Add a host or a switch." + name = prefix + topo.name( nodeId ) + mac = macColonHex( nodeId ) if self.setMacs else None + ip = topo.ip( nodeId ) + node = addMethod( name, mac=mac, ip=ip ) + self.idToNode[ nodeId ] = node + info( name + ' ' ) + + # Possibly we should clean up here and/or validate + # the topo if self.cleanup: - pass # cleanup - # validate topo? + pass + info( '*** Adding controller\n' ) self.addController( self.controller ) info( '*** Creating network\n' ) info( '*** Adding hosts:\n' ) for hostId in sorted( topo.hosts() ): - name = 'h' + topo.name( hostId ) - mac = macColonHex( hostId ) if self.setMacs else None - ip = topo.ip( hostId ) - host = self.addHost( name, ip=ip, mac=mac ) - self.idToNode[ hostId ] = host - info( name + ' ' ) + addNode( 'h', self.addHost, hostId ) info( '\n*** Adding switches:\n' ) for switchId in sorted( topo.switches() ): - name = 's' + topo.name( switchId ) - mac = macColonHex( switchId) if self.setMacs else None - switch = self.addSwitch( name, mac=mac ) - self.idToNode[ switchId ] = switch - info( name + ' ' ) + addNode( 's', self.addSwitch, switchId ) info( '\n*** Adding edges:\n' ) for srcId, dstId in sorted( topo.edges() ): src, dst = self.idToNode[ srcId ], self.idToNode[ dstId ] srcPort, dstPort = topo.port( srcId, dstId ) - createLink( src, srcPort, dst, dstPort ) + createLink( src, dst, srcPort, dstPort ) info( '(%s, %s) ' % ( src.name, dst.name ) ) info( '\n' ) + def build( self ): + "Build mininet." + if self.topo: + self.buildFromTopo( self.topo ) if self.inNamespace: info( '*** Configuring control network\n' ) - self._configureControlNetwork() - + self.configureControlNetwork() info( '*** Configuring hosts\n' ) self.configHosts() - if self.xterms: self.startXterms() if self.autoSetMacs: @@ -391,8 +396,7 @@ def ping( self, hosts=None ): """Ping between all specified hosts. hosts: list of hosts returns: ploss packet loss percentage""" - #self.start() - # check if running - only then, start? + # should we check if running? packets = 0 lost = 0 ploss = None diff --git a/mininet/node.py b/mininet/node.py index 99fcdfcf1af749144e724b7809c42040d5e0d858..e0cad50ff382f9427bbedd825aa5bd254b52ec2b 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -49,8 +49,7 @@ from time import sleep from mininet.log import info, error, debug -from mininet.util import quietRun, moveIntf - +from mininet.util import quietRun, makeIntfPair, moveIntf class Node( object ): """A virtual network node is simply a shell in a network namespace. @@ -84,7 +83,6 @@ def __init__( self, name, inNamespace=True, self.outToNode[ self.stdout.fileno() ] = self self.inToNode[ self.stdin.fileno() ] = self self.pid = self.shell.pid - self.intfCount = 0 self.intfs = {} # dict of port numbers to interface names self.ports = {} # dict of interface names to port numbers # replace with Port objects, eventually ? @@ -206,11 +204,11 @@ def intfName( self, n ): "Construct a canonical interface name node-ethN for interface n." return self.name + '-eth' + repr( n ) - def newIntf( self ): - "Reserve and return a new interface name." - intfName = self.intfName( self.intfCount ) - self.intfCount += 1 - return intfName + def newPort( self ): + "Return the next port number to allocate." + if len( self.ports ) > 0: + return max( self.ports.values() ) + 1 + return 0 def addIntf( self, intf, port ): """Add an interface. @@ -219,21 +217,47 @@ def addIntf( self, intf, port ): self.intfs[ port ] = intf self.ports[ intf ] = port #info( '\n' ) - #info( 'added intf %s to node %x\n' % ( srcIntf, src ) ) + #info( 'added intf %s:%d to node %s\n' % ( intf,port, self.name ) ) if self.inNamespace: #info( 'moving w/inNamespace set\n' ) moveIntf( intf, self ) - def connect( self, intf, dstNode, dstIntf ): + def registerIntf( self, intf, dstNode, dstIntf ): "Register connection of intf to dstIntf on dstNode." self.connection[ intf ] = ( dstNode, dstIntf ) + # This is a symmetric operation, but it makes sense to put + # the code here since it is tightly coupled to routines in + # this class. For a more symmetric API, you can use + # mininet.util.createLink() + + def linkTo( self, node2, port1=None, port2=None ): + """Create link to another node, making two new interfaces. + node2: Node to link us to + port1: our port number (optional) + port2: node2 port number (optional) + returns: intf1 name, intf2 name""" + node1 = self + if port1 is None: + port1 = node1.newPort() + if port2 is None: + port2 = node2.newPort() + intf1 = node1.intfName( port1 ) + intf2 = node2.intfName( port2 ) + makeIntfPair( intf1, intf2 ) + node1.addIntf( intf1, port1 ) + node2.addIntf( intf2, port2 ) + node1.registerIntf( intf1, node2, intf2 ) + node2.registerIntf( intf2, node1, intf1 ) + return intf1, intf2 + def deleteIntfs( self ): "Delete all of our interfaces." # In theory the interfaces should go away after we shut down. # However, this takes time, so we're better off removing them # explicitly so that we won't get errors if we run before they - # have been removed by the kernel. Unfortunately this is very slow. + # 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(): quietRun( 'ip link del ' + intf ) info( '.' ) @@ -255,7 +279,7 @@ def setARP( self, ip, mac ): result = self.cmd( [ 'arp', '-s', ip, mac ] ) return result - def setIP( self, intf, ip, prefixLen ): + def setIP( self, intf, ip, prefixLen=8 ): """Set the IP address for an interface. intf: interface name ip: IP address as a string @@ -277,23 +301,25 @@ def setDefaultRoute( self, intf ): self.cmd( 'ip route flush' ) return self.cmd( 'route add default ' + intf ) - def IP( self ): - "Return IP address of interface 0" - return self.ips.get( self.intfs.get( 0 , None ), None ) - - def MAC( self ): - "Return MAC address of interface 0" - ifconfig = self.cmd( 'ifconfig ' + self.intfs[ 0 ] ) + def IP( self, intf=None ): + "Return IP address of a node or specific interface." + if len( self.ips ) == 1: + return self.ips.values()[ 0 ] + if intf: + return self.ips.get( intf, None ) + + def MAC( self, intf=None ): + "Return MAC address of a node or specific interface." + if intf is None and len( self.intfs ) == 1: + intf = self.intfs.values()[ 0 ] + ifconfig = self.cmd( 'ifconfig ' + intf ) macs = re.findall( '..:..:..:..:..:..', ifconfig ) if len( macs ) > 0: return macs[ 0 ] - else: - return None - def intfIsUp( self, port ): - """Check if interface for a given port number is up. - port: port number""" - return 'UP' in self.cmd( 'ifconfig ' + self.intfs[ port ] ) + def intfIsUp( self, intf ): + "Check if an interface is up." + return 'UP' in self.cmd( 'ifconfig ' + intf ) # Other methods def __str__( self ): @@ -331,13 +357,12 @@ def monitor( self ): class UserSwitch( Switch ): - """User-space switch. - Currently only works in the root namespace.""" + "User-space switch." def __init__( self, name, *args, **kwargs ): """Init. name: name for the switch""" - Switch.__init__( self, name, inNamespace=False, **kwargs ) + Switch.__init__( self, name, **kwargs ) def start( self, controllers ): """Start OpenFlow reference user datapath. @@ -348,7 +373,8 @@ def start( self, controllers ): ofplog = '/tmp/' + self.name + '-ofp.log' self.cmd( 'ifconfig lo up' ) intfs = sorted( self.intfs.values() ) - + if self.inNamespace: + intfs = intfs[ :-1 ] self.cmd( 'ofdatapath -i ' + ','.join( intfs ) + ' punix:/tmp/' + self.name + ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' ) @@ -364,15 +390,19 @@ def stop( self ): class KernelSwitch( Switch ): """Kernel-space switch. - Currently only works in the root namespace.""" + Currently only works in root namespace.""" def __init__( self, name, dp=None, **kwargs ): """Init. name: dp: netlink id (0, 1, 2, ...) defaultMAC: default MAC as string; random value if None""" - Switch.__init__( self, name, inNamespace=False, **kwargs ) + Switch.__init__( self, name, **kwargs ) self.dp = dp + if self.inNamespace: + error( "KernelSwitch currently only works" + " in the root namespace." ) + exit( 1 ) def start( self, controllers ): "Start up reference kernel datapath." @@ -385,7 +415,6 @@ def start( self, controllers ): if self.defaultMAC: intf = 'of%i' % self.dp self.cmd( [ 'ifconfig', intf, 'hw', 'ether', self.defaultMAC ] ) - if len( self.intfs ) != max( self.intfs ) + 1: raise Exception( 'only contiguous, zero-indexed port ranges' 'supported: %s' % self.intfs ) @@ -412,11 +441,15 @@ class OVSKernelSwitch( Switch ): def __init__( self, name, dp=None, **kwargs ): """Init. - name: + name: name of switch dp: netlink id (0, 1, 2, ...) - dpid: datapath ID as unsigned int; random value if None""" - Switch.__init__( self, name, inNamespace=False, **kwargs ) + defaultMAC: default MAC as unsigned int; random value if None""" + Switch.__init__( self, name, **kwargs ) self.dp = dp + if self.inNamespace: + error( "OVSKernelSwitch currently only works" + " in the root namespace." ) + exit( 1 ) def start( self, controllers ): "Start up kernel datapath." @@ -480,10 +513,12 @@ def stop( self ): self.cmd( 'kill %' + self.controller ) self.terminate() - def IP( self ): + def IP( self, intf=None ): "Return IP address of the Controller" - return self.defaultIP - + ip = Node.IP( self, intf=intf ) + if ip is None: + ip = self.defaultIP + return ip class ControllerParams( object ): "Container for controller IP parameters." diff --git a/mininet/test/test_nets.py b/mininet/test/test_nets.py index c5b842f3ed0acf0f5f49e149c613077bf6f628e8..b5e32a349978955933b4384ac0f65c2b6ab119eb 100755 --- a/mininet/test/test_nets.py +++ b/mininet/test/test_nets.py @@ -20,7 +20,7 @@ def testMinimal( self ): "Ping test with both datapaths on minimal topology" init() for switch in SWITCHES.values(): - controllerParams = ControllerParams( 0x0a000000, 8 ) # 10.0.0.0/8 + controllerParams = ControllerParams( '10.0.0.0', 8 ) mn = Mininet( SingleSwitchTopo(), switch, Host, Controller, controllerParams ) dropped = mn.run( 'ping' ) @@ -30,7 +30,7 @@ def testSingle5( self ): "Ping test with both datapaths on 5-host single-switch topology" init() for switch in SWITCHES.values(): - controllerParams = ControllerParams( 0x0a000000, 8 ) # 10.0.0.0/8 + controllerParams = ControllerParams( '10.0.0.0', 8 ) mn = Mininet( SingleSwitchTopo( k=5 ), switch, Host, Controller, controllerParams ) dropped = mn.run( 'ping' ) @@ -44,7 +44,7 @@ def testLinear5( self ): "Ping test with both datapaths on a 5-switch topology" init() for switch in SWITCHES.values(): - controllerParams = ControllerParams( 0x0a000000, 8 ) # 10.0.0.0/8 + controllerParams = ControllerParams( '10.0.0.0', 8 ) mn = Mininet( LinearTopo( k=5 ), switch, Host, Controller, controllerParams ) dropped = mn.run( 'ping' ) diff --git a/mininet/util.py b/mininet/util.py index 366e653b4b8d390e97c549a290635ddf78ae6ed4..9c0e4af25bb2ac82ef3f1902bf0708ececccb3f3 100644 --- a/mininet/util.py +++ b/mininet/util.py @@ -101,21 +101,14 @@ def moveIntf( intf, node, printError=False, retries=3, delaySecs=0.001 ): printError: if true, print error""" retry( retries, delaySecs, moveIntfNoRetry, intf, node, printError ) -def createLink( node1, port1, node2, port2 ): +def createLink( node1, node2, port1=None, port2=None ): """Create a link between nodes, making an interface for each. node1: Node object - port1: node1 port number node2: Node object - port2: node2 port number + port1: node1 port number (optional) + port2: node2 port number (optional) returns: intf1 name, intf2 name""" - intf1 = node1.intfName( port1 ) - intf2 = node2.intfName( port2 ) - makeIntfPair( intf1, intf2 ) - node1.addIntf( intf1, port1 ) - node2.addIntf( intf2, port2 ) - node1.connect( intf1, node2, intf2 ) - node2.connect( intf2, node1, intf1 ) - return intf1, intf2 + return node1.linkTo( node2, port1, port2 ) def fixLimits(): "Fix ridiculously small resource limits." @@ -141,10 +134,22 @@ def macColonHex( mac ): return _colonHex( mac, 6 ) def ipStr( ip ): - """Generate IP address string from an unsigned int - ip: unsigned int of form x << 16 | y << 8 | z - returns: ip address string 10.x.y.z """ - hi = ( ip & 0xff0000 ) >> 16 - mid = ( ip & 0xff00 ) >> 8 - lo = ip & 0xff - return "10.%i.%i.%i" % ( hi, mid, lo ) + """Generate IP address string from an unsigned int. + ip: unsigned int of form w << 24 | x << 16 | y << 8 | z + returns: ip address string w.x.y.z, or 10.x.y.z if w==0""" + w = ( ip & 0xff000000 ) >> 24 + w = 10 if w == 0 else w + x = ( ip & 0xff0000 ) >> 16 + y = ( ip & 0xff00 ) >> 8 + z = ip & 0xff + return "%i.%i.%i.%i" % ( w, x, y, z ) + +def ipNum( w, x, y, z ): + """Generate unsigned int from components ofIP address + returns: w << 24 | x << 16 | y << 8 | z""" + return ( w << 24 ) | ( x << 16 ) | ( y << 8 ) | z + +def ipParse( ip ): + "Parse an IP address and return an unsigned int." + args = [ int( arg ) for arg in ip.split( '.' ) ] + return ipNum( *args )