diff --git a/bin/mn b/bin/mn
index b2323891b1205c4239d23ef5b9549fa11bd70459..7eab6c9125fd4fe472dc5e3308af78145d2b2ee5 100755
--- a/bin/mn
+++ b/bin/mn
@@ -20,11 +20,27 @@ from mininet.clean import cleanup
 from mininet.cli import CLI
 from mininet.log import lg, LEVELS, info
 from mininet.net import Mininet, init
-from mininet.node import Host, Controller, ControllerParams, NOX
+from mininet.node import Host, CPULimitedHost, Controller, NOX
 from mininet.node import RemoteController, UserSwitch, OVSKernelSwitch
+from mininet.link import Intf, TCIntf
 from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo
 from mininet.topolib import TreeTopo
-from mininet.util import makeNumeric
+from mininet.util import makeNumeric, custom
+
+def customNode( constructors, argStr ):
+    "Return custom Node constructor based on argStr"
+    cname, noargs, kwargs = splitArgs( argStr )
+    constructor = constructors.get( cname, None )
+    if noargs:
+        raise Exception( "please specify keyword  arguments for " + cname )
+    if not constructor:
+        raise Exception( "error: %s is unknown - please specify one of %s" %
+               ( cname, constructors.keys() ) )
+    def custom( *args, **params ):
+        params.update( kwargs )
+        print 'CONSTRUCTOR', constructor, 'ARGS', args, 'PARAMS', params
+        return constructor( *args, **params )
+    return custom
 
 # built in topologies, created only when run
 TOPODEF = 'minimal'
@@ -38,17 +54,22 @@ SWITCHDEF = 'ovsk'
 SWITCHES = { 'user': UserSwitch,
             'ovsk': OVSKernelSwitch }
 
-HOSTDEF = 'process'
-HOSTS = { 'process': Host }
+HOSTDEF = 'proc'
+HOSTS = { 'proc': Host,
+          'rt': custom( CPULimitedHost, sched='rt' ),
+          'cfs': custom( CPULimitedHost, sched='cfs' ) }
 
 CONTROLLERDEF = 'ref'
-# a and b are the name and inNamespace params.
 CONTROLLERS = { 'ref': Controller,
-               'nox_dump': lambda name: NOX( name, 'packetdump' ),
-               'nox_pysw': lambda name: NOX( name, 'pyswitch' ),
-               'remote': lambda name: None,
+               'nox': NOX, 
+               'remote': RemoteController,
                'none': lambda name: None }
 
+INTFDEF = 'default'
+INTFS = { 'default': Intf,
+          'tc': TCIntf }
+
+
 # optional tests to run
 TESTS = [ 'cli', 'build', 'pingall', 'pingpair', 'iperf', 'all', 'iperfudp',
          'none' ]
@@ -56,24 +77,31 @@ TESTS = [ 'cli', 'build', 'pingall', 'pingpair', 'iperf', 'all', 'iperfudp',
 ALTSPELLING = { 'pingall': 'pingAll', 'pingpair': 'pingPair',
     'iperfudp': 'iperfUdp', 'iperfUDP': 'iperfUdp', 'prefixlen': 'prefixLen' }
 
-def buildTopo( topo ):
-    "Create topology from string with format (object, arg1, arg2,...)."
-    topo_split = topo.split( ',' )
-    topo_name = topo_split[ 0 ]
-    topo_params = topo_split[ 1: ]
-
-    # Convert int and float args; removes the need for every topology to
-    # be flexible with input arg formats.
-    topo_seq_params = [ s for s in topo_params if '=' not in s ]
-    topo_seq_params = [ makeNumeric( s ) for s in topo_seq_params ]
-    topo_kw_params = {}
-    for s in [ p for p in topo_params if '=' in p ]:
+
+def splitArgs( argstr ):
+    """Split argument string into usable python arguments
+       argstr: argument string with format fn,arg2,kw1=arg3...
+       returns: fn, args, kwargs"""
+    split = argstr.split( ',' )
+    fn = split[ 0 ]
+    params = split[ 1: ]
+    # Convert int and float args; removes the need for function
+    # to be flexible with input arg formats.
+    args = [ s for s in params if '=' not in s ]
+    args = map( makeNumeric, args )
+    kwargs = {}
+    for s in [ p for p in params if '=' in p ]:
         key, val = s.split( '=' )
-        topo_kw_params[ key ] = makeNumeric( val )
+        kwargs[ key ] = makeNumeric( val )
+    return fn, args, kwargs
 
-    if topo_name not in TOPOS.keys():
-        raise Exception( 'Invalid topo_name %s' % topo_name )
-    return TOPOS[ topo_name ]( *topo_seq_params, **topo_kw_params )
+
+def buildTopo( topoStr ):
+    "Create topology from string with format (object, arg1, arg2,...)."
+    topo, args, kwargs = splitArgs( topoStr )
+    if topo not in TOPOS:
+        raise Exception( 'Invalid topo name %s' % topo )
+    return TOPOS[ topo ]( *args, **kwargs )
 
 
 def addDictOption( opts, choicesDict, default, name, helpStr=None ):
@@ -87,10 +115,9 @@ def addDictOption( opts, choicesDict, default, name, helpStr=None ):
         raise Exception( 'Invalid  default %s for choices dict: %s' %
                         ( default, name ) )
     if not helpStr:
-        helpStr = '[' + ' '.join( choicesDict.keys() ) + ']'
+        helpStr = '|'.join( sorted( choicesDict.keys() ) ) + '[,param=value...]'
     opts.add_option( '--' + name,
-                    type='choice',
-                    choices=choicesDict.keys(),
+                    type='string',
                     default = default,
                     help = helpStr )
 
@@ -135,7 +162,6 @@ class MininetRunner( object ):
         """Parse command-line args and return options object.
            returns: opts parse options dict"""
         if '--custom' in sys.argv:
-            print "custom in sys.argv"
             index = sys.argv.index( '--custom' )
             if len( sys.argv ) > index + 1:
                 custom = sys.argv[ index + 1 ]
@@ -147,46 +173,41 @@ class MininetRunner( object ):
         addDictOption( opts, SWITCHES, SWITCHDEF, 'switch' )
         addDictOption( opts, HOSTS, HOSTDEF, 'host' )
         addDictOption( opts, CONTROLLERS, CONTROLLERDEF, 'controller' )
+        addDictOption( opts, INTFS, INTFDEF, 'intf' )
+        addDictOption( opts, TOPOS, TOPODEF, 'topo' )
 
-        opts.add_option( '--topo', type='string', default=TOPODEF,
-                        help='[' + ' '.join( TOPOS.keys() ) + '],arg1,arg2,'
-                        '...argN')
         opts.add_option( '--clean', '-c', action='store_true',
                         default=False, help='clean and exit' )
         opts.add_option( '--custom', type='string', default=None,
                         help='read custom topo and node params from .py file' )
         opts.add_option( '--test', type='choice', choices=TESTS,
                         default=TESTS[ 0 ],
-                        help='[' + ' '.join( TESTS ) + ']' )
+                        help='|'.join( TESTS ) )
         opts.add_option( '--xterms', '-x', action='store_true',
                         default=False, help='spawn xterms for each node' )
         opts.add_option( '--mac', action='store_true',
-                        default=False, help='set MACs equal to DPIDs' )
+                        default=False, help='automatically set host MACs' )
         opts.add_option( '--arp', action='store_true',
                         default=False, help='set all-pairs ARP entries' )
         opts.add_option( '--verbosity', '-v', type='choice',
                         choices=LEVELS.keys(), default = 'info',
-                        help = '[' + ' '.join( LEVELS.keys() ) + ']' )
+                        help = '|'.join( LEVELS.keys() )  )
         opts.add_option( '--ip', type='string', default='127.0.0.1',
-                        help='[ip address as a dotted decimal string for a'
-                        'remote controller]' )
-        opts.add_option( '--port', type='int', default=6633,
-                        help='[port integer for a listening remote'
-                        ' controller]' )
+                        help='ip address as a dotted decimal string for a'
+                        'remote controller' )
         opts.add_option( '--innamespace', action='store_true',
                         default=False, help='sw and ctrl in namespace?' )
         opts.add_option( '--listenport', type='int', default=6634,
-                        help='[base port for passive switch listening'
-                        ' controller]' )
+                         help='base port for passive switch listening' )
         opts.add_option( '--nolistenport', action='store_true',
                         default=False, help="don't use passive listening port")
         opts.add_option( '--pre', type='string', default=None,
-                        help='[CLI script to run before tests]' )
+                        help='CLI script to run before tests' )
         opts.add_option( '--post', type='string', default=None,
-                        help='[CLI script to run after tests]' )
+                        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]' )
+                        help='prefix length (e.g. /8) for automatic '
+                        'network configuration' )
 
         self.options, self.args = opts.parse_args()
 
@@ -214,23 +235,14 @@ class MininetRunner( object ):
         start = time.time()
 
         topo = buildTopo( self.options.topo )
-        switch = SWITCHES[ self.options.switch ]
-        host = HOSTS[ self.options.host ]
-        controller = CONTROLLERS[ self.options.controller ]
-        if self.options.controller == 'remote':
-            controller = lambda a: RemoteController( a,
-                             defaultIP=self.options.ip,
-                             port=self.options.port )
+        switch = customNode( SWITCHES,  self.options.switch )
+        host = customNode( HOSTS, self.options.host )
+        controller = customNode( CONTROLLERS, self.options.controller )
+        intf = customNode( INTFS, self.options.intf )
 
         if self.validate:
             self.validate( self.options )
 
-        # We should clarify what this is actually for...
-        # It seems like it should be default values for the
-        # *data* network, so it may be misnamed.
-        controllerParams = ControllerParams( '10.0.0.0',
-            self.options.prefixlen)
-
         inNamespace = self.options.innamespace
         xterms = self.options.xterms
         mac = self.options.mac
@@ -238,10 +250,12 @@ class MininetRunner( object ):
         listenPort = None
         if not self.options.nolistenport:
             listenPort = self.options.listenport
-        mn = Mininet( topo, switch, host, controller, controllerParams,
-                     inNamespace=inNamespace,
-                     xterms=xterms, autoSetMacs=mac,
-                     autoStaticArp=arp, listenPort=listenPort )
+        mn = Mininet( topo=topo, 
+                      switch=switch, host=host, controller=controller, 
+                      intf=intf,
+                      inNamespace=inNamespace,
+                      xterms=xterms, autoSetMacs=mac,
+                      autoStaticArp=arp, listenPort=listenPort )
 
         if self.options.pre:
             CLI( mn, script=self.options.pre )
diff --git a/examples/limit.py b/examples/limit.py
index 58e2ff78babca4045e52be756ab06e2b5821fc5c..801159af6c0989f6149d4babd6d651f7cc7902f4 100755
--- a/examples/limit.py
+++ b/examples/limit.py
@@ -5,19 +5,20 @@
 """
 
 from mininet.net import Mininet
-from mininet.link import TCIntf, Link
+from mininet.link import TCIntf
 from mininet.node import CPULimitedHost
 from mininet.topolib import TreeTopo
 from mininet.util import custom, quietRun
 from mininet.log import setLogLevel
 from time import sleep
 
-def testLinkLimit( net ):
-    print '*** Testing network bandwidth limit'
-    net.iperf()
+def testLinkLimit( net, bw ):
+    print '*** Testing network %.2f Mbps bandwidth limit' % bw
+    net.iperf( )
 
-def testCpuLimit( net ):
-    print '*** Testing CPU bandwidth limit'
+def testCpuLimit( net, cpu ):
+    pct = cpu * 100
+    print '*** Testing CPU %.0f%% bandwidth limit' % pct
     h1, h2 = net.hosts
     h1.cmd( 'while true; do a=1; done &' )
     h2.cmd( 'while true; do a=1; done &' )
@@ -26,26 +27,24 @@ def testCpuLimit( net ):
     cmd = 'ps -p %s,%s -o pid,%%cpu,args' % ( pid1, pid2 )
     for i in range( 0, 5):
         sleep( 1 ) 
-        print quietRun( cmd )
+        print quietRun( cmd ).strip()
     h1.cmd( 'kill %1')
     h2.cmd( 'kill %1')
 
-def limit():
-    "Example/test of link and CPU bandwidth limits"
-    # 1 Mbps interfaces limited using tc
-    intf1Mbps = custom( TCIntf, bw=1 )
-    # Links consisting of two 10 Mbps interfaces
-    link1Mbps = custom( Link, intf=intf1Mbps, cls2=TCIntf )
-    # Hosts with 30% of system bandwidth
-    host30pct = custom( CPULimitedHost, cpu=.3 )
+def limit( bw=1, cpu=.3 ):
+    """Example/test of link and CPU bandwidth limits
+       bw: interface bandwidth limit in Mbps
+       cpu: cpu limit as fraction of overall CPU time"""
+    intf = custom( TCIntf, bw=1 )
     myTopo = TreeTopo( depth=1, fanout=2 )
-    net = Mininet( topo=myTopo,
-                   link=link1Mbps, 
-                   host=host30pct )
-    net.start()
-    testLinkLimit( net )
-    testCpuLimit( net )
-    net.stop()
+    for sched in 'rt', 'cfs':
+        print '*** Testing with', sched, 'bandwidth limiting'
+        host = custom( CPULimitedHost, sched=sched, cpu=cpu )
+        net = Mininet( topo=myTopo, intf=intf, host=host )
+        net.start()
+        testLinkLimit( net, bw=bw )
+        testCpuLimit( net, cpu=cpu )
+        net.stop()
 
 if __name__ == '__main__':
     setLogLevel( 'info' )
diff --git a/mininet/net.py b/mininet/net.py
index 18d03633630508a491c4561bc8c5798eb8b2dd33..953a5bc65e727b2b41a1fb4bd8880e4c20300251 100755
--- a/mininet/net.py
+++ b/mininet/net.py
@@ -104,7 +104,7 @@ class Mininet( object ):
     "Network emulation with hosts spawned in network namespaces."
 
     def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host,
-                 controller=Controller, link=Link,
+                 controller=Controller, link=Link, intf=None, 
                  build=True, xterms=False, cleanup=False,
                  inNamespace=False,
                  autoSetMacs=False, autoStaticArp=False, listenPort=None ):
@@ -114,6 +114,7 @@ def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host,
            host: default Host class/constructor
            controller: default Controller class/constructor
            link: default Link class/constructor
+           intf: default Intf class/constructor
            ipBase: base IP address for hosts,
            build: build now from topo?
            xterms: if build now, spawn xterms?
@@ -123,11 +124,12 @@ def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host,
            autoStaticArp: set all-pairs static MAC addrs?
            listenPort: base listening port to open; will be incremented for
                each additional switch in the net if inNamespace=False"""
+        self.topo = topo
         self.switch = switch
         self.host = host
         self.controller = controller
         self.link = link
-        self.topo = topo
+        self.intf = intf
         self.inNamespace = inNamespace
         self.xterms = xterms
         self.cleanup = cleanup
@@ -199,12 +201,12 @@ def addController( self, name='c0', controller=None, **params ):
     def configHosts( self ):
         "Configure a set of hosts."
         for host in self.hosts:
+            info( host.name + ' ' )
             host.configDefault( defaultRoute=host.defaultIntf )
             # You're low priority, dude!
             # BL: do we want to do this here or not?
             # May not make sense if we have CPU lmiting...
             # quietRun( 'renice +18 -p ' + repr( host.pid ) )
-            info( host.name + ' ' )
         info( '\n' )
 
     def buildFromTopo( self, topo=None ):
@@ -235,6 +237,8 @@ def addLink( srcId, dstId, link=None ):
             ei = topo.edgeInfo( srcId, dstId )
             link = getattr( ei, 'cls', link )
             params = ei.params
+            if self.intf and not 'intf' in params:
+                params[ 'intf' ] = self.intf
             if not link:
                 link = self.link
             info( '(%s, %s) ' % ( src.name, dst.name ) )
@@ -447,6 +451,8 @@ def _parseIperf( iperfOutput ):
             error( 'could not parse iperf output: ' + iperfOutput )
             return ''
 
+    # XXX This should be cleaned up
+
     def iperf( self, hosts=None, l4Type='TCP', udpBw='10M' ):
         """Run iperf between two hosts.
            hosts: list of hosts; if None, uses opposite hosts
diff --git a/mininet/node.py b/mininet/node.py
index 68a843ed6c2c8c9dfd89379240fcce32731a8b79..f78ebe97d841091c46329009f244f3930650b13e 100644
--- a/mininet/node.py
+++ b/mininet/node.py
@@ -127,9 +127,12 @@ def startShell( self ):
         if self.shell:
             error( "%s: shell is already running" )
             return
+        # mnexec: (c)lose descriptors, (d)etach from tty,
+        # (p)rint pid, and run in (n)amespace 
         opts = '-cdp'
         if self.inNamespace:
             opts += 'n'
+        # bash -m: enable job control
         cmd = [ 'mnexec', opts, 'bash', '-m' ]
         self.shell = Popen( cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT,
             close_fds=True )
@@ -405,10 +408,14 @@ def intfIsUp( self, intf=None ):
     # annoying, but at least the information is there!
 
     def setParam( self, results, method, **param ):
-        """Internal method: configure single parameter"""
+        """Internal method: configure a *single* parameter
+           results: dict of results to update
+           method: config method name
+           param: arg=value (ignore if value=None)
+           value may also be list or dict"""
         name, value = param.items()[ 0 ]
         f = getattr( self, method, None )
-        if not value or not f:
+        if not f or value is None:
             return
         if type( value ) is list:
             result = f( *value )
@@ -417,6 +424,7 @@ def setParam( self, results, method, **param ):
         else:
             result = f( value )
         results[ name ] = result
+        return result
 
     def config( self, mac=None, ip=None, ifconfig=None, 
                 defaultRoute=None, **params):
@@ -462,7 +470,12 @@ def __str__( self ):
             self.name, self.IP(), ','.join( self.intfNames() ), self.pid )
 
 
-class CPULimitedHost( Node ):
+class Host( Node ):
+    "A host is simply a Node"
+    pass
+
+
+class CPULimitedHost( Host ):
 
     "CPU limited host"
 
@@ -472,9 +485,8 @@ def __init__( self, *args, **kwargs ):
         cgroup = 'cpu,cpuacct:/' + self.name
         errFail( 'cgcreate -g ' + cgroup )
         errFail( 'cgclassify -g %s %s' % ( cgroup, self.pid ) )
-        self.sched = 'rt'
-        self.period_us = 10000
-        self.rtset = False
+        self.period_us = kwargs.get( 'period_us', 10000 )
+        self.sched = kwargs.get( 'sched', 'rt' )
 
     def cgroupSet( self, param, value, resource='cpu' ):
         "Set a cgroup parameter and return its value"
@@ -495,40 +507,73 @@ def chrt( self, prio=20 ):
         lastword = firstline.split( ' ' )[ -1 ]
         return lastword
 
-    def setCPUFrac( self, f=-1 ):
-        "Set overall CPU fraction for this host"
-        if ( f < 0 or f is None):
+    # BL comment:
+    # This may not be the right API, 
+    # since it doesn't specify CPU bandwidth in "absolute"
+    # units the way link bandwidth is specified.
+    # We should use MIPS or SPECINT or something instead.
+    # Alternatively, we should change from system fraction
+    # to CPU seconds per second, essentially assuming that
+    # all CPUs are the same.
+    
+    def setCPUFrac( self, f=-1, sched=None):
+        """Set overall CPU fraction for this host
+           f: CPU bandwidth limit (fraction)
+           sched: 'rt' or 'cfs'
+           Note 'cfs' requires CONFIG_CFS_BANDWIDTH"""
+        if not f:
+            return
+        if not sched:
+            sched = self.sched
+        period = self.period_us
+        if sched == 'rt':
+            pstr, qstr = 'rt_period_us', 'rt_runtime_us'
+            # RT uses system time for period and quota
+            quota = int( period * f * numCores() )
+        elif sched == 'cfs':
+            pstr, qstr = 'cfs_period_us', 'cfs_quota_us'
+            # CFS uses wall clock time for period and CPU time for quota.
+            quota = int( self.period_us * f * numCores() )
+            if f > 0 and quota < 1000:
+                info( '*** setCPUFrac: quota too small - adjusting period\n' )
+                quota = 1000
+                period = int( quota / f / numCores() )
+        else:
+            return
+        if quota < 0:
             # Reset to unlimited
-            f = -1
-        # Set new period and quota
-        pstr, qstr = 'rt_period_us', 'rt_runtime_us'
-        quota = int( self.period_us * f * numCores() )
-        self.cgroupSet( pstr, self.period_us )
+            quota = -1
+        # Set cgroup's period and quota
+        self.cgroupSet( pstr, period )
         nquota = int ( self.cgroupGet( qstr ) )
         self.cgroupSet( qstr, quota )
         nperiod = int( self.cgroupGet( pstr ) )
-        # Set RT priority
-        nchrt = self.chrt( prio=20 )
-        # Check to make sure it worked
-        if 'SCHED_RR' not in nchrt:
-            error( '*** error: could not assign SCHED_RR to %s\n' % self.name )
+        # Make sure it worked
         if nperiod != self.period_us:
             error( '*** error: period is %s rather than %s\n' % (
                     nperiod, self.period_us ) )
         if nquota != quota:
             error( '*** error: quota is %s rather than %s\n' % (
                     nquota, quota ) )
+        if sched == 'rt':
+            # Set RT priority if necessary
+            nchrt = self.chrt( prio=20 )
+            # Nake sure it worked
+            if sched == 'SCHED_RR' not in nchrt:
+                error( '*** error: could not assign SCHED_RR to %s\n' % self.name )
+            info( '( period', nperiod, 'quota', nquota, nchrt, ') ' )
+        else:
+            info( '( period', nperiod, 'quota', nquota, ') ' )
 
-    def config( self, cpu=None, **params ):
+    def config( self, cpu=None, sched=None, **params ):
         """cpu: desired overall system CPU fraction
            params: parameters for Node.config()"""
         r = Node.config( self, **params )
+        # Was considering cpu={'cpu': cpu , 'sched': sched}, but
+        # that seems redundant
         self.setParam( r, 'setCPUFrac', cpu=cpu )
         return r
 
-Host = CPULimitedHost
-
-
 # Some important things to note:
 #
 # The "IP" address which we assign to the switch is not
@@ -805,7 +850,7 @@ def __init__( self, ip, prefixLen ):
 class NOX( Controller ):
     "Controller to run a NOX application."
 
-    def __init__( self, name, noxArgs=None, **kwargs ):
+    def __init__( self, name, noxArgs=[], **kwargs ):
         """Init.
            name: name to give controller
            noxArgs: list of args, or single arg, to pass to NOX"""