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 )