From d44a5843d35ef0800a8e75f8b23f4da8514885d0 Mon Sep 17 00:00:00 2001 From: Bob Lantz <rlantz@cs.stanford.edu> Date: Tue, 9 Mar 2010 21:48:14 -0800 Subject: [PATCH] Reinstantiated inNamespace and routed control network. We need to figure out how to specify the IP addresses for the routed control network. For now I'm going back to 192.168.12x.y Also changed controller params to use IP strings rather than numbers. However, we still need to clarify what ControllerParams is actually for. --- mininet/net.py | 112 ++++++++++++++++++++------------------ mininet/node.py | 111 ++++++++++++++++++++++++------------- mininet/test/test_nets.py | 6 +- mininet/util.py | 41 ++++++++------ 4 files changed, 157 insertions(+), 113 deletions(-) diff --git a/mininet/net.py b/mininet/net.py index 47b0f6cd..a4ed8a57 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 99fcdfcf..e0cad50f 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 c5b842f3..b5e32a34 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 366e653b..9c0e4af2 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 ) -- GitLab