Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ 07aad110

History | View | Annotate | Download (22.7 KB)

1
"""
2
Node objects for Mininet.
3

4
Nodes provide a simple abstraction for interacting with hosts, switches
5
and controllers. Local nodes are simply one or more processes on the local
6
machine.
7

8
Node: superclass for all (primarily local) network nodes.
9

10
Host: a virtual host. By default, a host is simply a shell; commands
11
    may be sent using Cmd (which waits for output), or using sendCmd(),
12
    which returns immediately, allowing subsequent monitoring using
13
    monitor(). Examples of how to run experiments using this
14
    functionality are provided in the examples/ directory.
15

16
Switch: superclass for switch nodes.
17

18
UserSwitch: a switch using the user-space switch from the OpenFlow
19
    reference implementation.
20

21
KernelSwitch: a switch using the kernel switch from the OpenFlow reference
22
    implementation.
23

24
OVSSwitch: a switch using the OpenVSwitch OpenFlow-compatible switch
25
    implementation (openvswitch.org).
26

27
Controller: superclass for OpenFlow controllers. The default controller
28
    is controller(8) from the reference implementation.
29

30
NOXController: a controller node using NOX (noxrepo.org).
31

32
RemoteController: a remote controller node, which may use any
33
    arbitrary OpenFlow-compatible controller, and which is not
34
    created or managed by mininet.
35

36
Future enhancements:
37

38
- Possibly make Node, Switch and Controller more abstract so that
39
  they can be used for both local and remote nodes
40

41
- Create proxy objects for remote nodes (Mininet: Cluster Edition)
42
"""
43

    
44
import os
45
import re
46
import signal
47
import select
48
from subprocess import Popen, PIPE, STDOUT
49
from time import sleep
50

    
51
from mininet.log import info, error, debug
52
from mininet.util import quietRun, makeIntfPair, moveIntf, isShellBuiltin
53

    
54
class Node( object ):
55
    """A virtual network node is simply a shell in a network namespace.
56
       We communicate with it using pipes."""
57

    
58
    inToNode = {} # mapping of input fds to nodes
59
    outToNode = {} # mapping of output fds to nodes
60

    
61
    def __init__( self, name, inNamespace=True,
62
        defaultMAC=None, defaultIP=None ):
63
        """name: name of node
64
           inNamespace: in network namespace?
65
           defaultMAC: default MAC address for intf 0
66
           defaultIP: default IP address for intf 0"""
67
        self.name = name
68
        opts = '-cdp'
69
        self.inNamespace = inNamespace
70
        if self.inNamespace:
71
            opts += '-n'
72
        # xpg_echo is needed so we can echo our sentinel in sendCmd
73
        cmd = [ 'mnexec', opts, 'bash', '-O', 'xpg_echo', '-m' ]
74
        self.shell = Popen( cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT,
75
            close_fds=False )
76
        self.stdin = self.shell.stdin
77
        self.stdout = self.shell.stdout
78
        self.pollOut = select.poll()
79
        self.pollOut.register( self.stdout )
80
        # Maintain mapping between file descriptors and nodes
81
        # This is useful for monitoring multiple nodes
82
        # using select.poll()
83
        self.outToNode[ self.stdout.fileno() ] = self
84
        self.inToNode[ self.stdin.fileno() ] = self
85
        self.pid = self.shell.pid
86
        self.intfs = {} # dict of port numbers to interface names
87
        self.ports = {} # dict of interface names to port numbers
88
                        # replace with Port objects, eventually ?
89
        self.ips = {} # dict of interfaces to ip addresses as strings
90
        self.connection = {} # remote node connected to each interface
91
        self.execed = False
92
        self.defaultIP = defaultIP
93
        self.defaultMAC = defaultMAC
94
        self.lastCmd = None
95
        self.lastPid = None
96
        # Grab PID
97
        self.waiting = True
98
        while self.lastPid is None:
99
            self.monitor()
100
        self.pid = self.lastPid
101
        self.waiting = False
102

    
103
    @classmethod
104
    def fdToNode( cls, fd ):
105
        """Return node corresponding to given file descriptor.
106
           fd: file descriptor
107
           returns: node"""
108
        node = Node.outToNode.get( fd )
109
        return node or Node.inToNode.get( fd )
110

    
111
    def cleanup( self ):
112
        "Help python collect its garbage."
113
        self.shell = None
114

    
115
    # Subshell I/O, commands and control
116
    def read( self, bytes ):
117
        """Read from a node.
118
           bytes: maximum number of bytes to read"""
119
        return os.read( self.stdout.fileno(), bytes )
120

    
121
    def write( self, data ):
122
        """Write data to node.
123
           data: string"""
124
        os.write( self.stdin.fileno(), data )
125

    
126
    def terminate( self ):
127
        "Send kill signal to Node and clean up after it."
128
        os.kill( self.pid, signal.SIGKILL )
129
        self.cleanup()
130

    
131
    def stop( self ):
132
        "Stop node."
133
        self.terminate()
134

    
135
    def waitReadable( self ):
136
        "Wait until node's output is readable."
137
        self.pollOut.poll()
138

    
139
    def sendCmd( self, cmd, printPid=False ):
140
        """Send a command, followed by a command to echo a sentinel,
141
           and return without waiting for the command to complete."""
142
        assert not self.waiting
143
        if isinstance( cmd, list ):
144
            cmd = ' '.join( cmd )
145
        if cmd[ -1 ] == '&':
146
            separator = '&'
147
            cmd = cmd[ :-1 ]
148
        else:
149
            separator = ';'
150
            if printPid and not isShellBuiltin( cmd ):
151
                cmd = 'mnexec -p ' + cmd
152
        self.write( cmd + separator + ' echo -n "\\0177" \n' )
153
        self.lastCmd = cmd
154
        self.lastPid = None
155
        self.waiting = True
156

    
157
    def sendInt( self ):
158
        "Interrupt running command."
159
        if self.lastPid:
160
            os.kill( self.lastPid, signal.SIGINT )
161

    
162
    def monitor( self ):
163
        "Monitor the output of a command, returning (done?, data)."
164
        assert self.waiting
165
        self.waitReadable()
166
        data = self.read( 1024 )
167
        # Look for PID
168
        marker = chr( 1 ) + r'\d+\n'
169
        if chr( 1 ) in data:
170
            markers = re.findall( marker, data )
171
            if markers:
172
                self.lastPid = int( markers[ 0 ][ 1: ] )
173
                data = re.sub( marker, '', data )
174
        # Look for sentinel/EOF
175
        if len( data ) > 0 and data[ -1 ] == chr( 127 ):
176
            self.waiting = False
177
            return True, data[ :-1 ]
178
        elif chr( 127 ) in data:
179
            self.waiting = False
180
            return True, data.replace( chr( 127 ), '' )
181
        return False, data
182

    
183
    def waitOutput( self, verbose=False ):
184
        """Wait for a command to complete.
185
           Completion is signaled by a sentinel character, ASCII(127)
186
           appearing in the output stream.  Wait for the sentinel and return
187
           the output, including trailing newline.
188
           verbose: print output interactively"""
189
        log = info if verbose else debug
190
        output = ''
191
        done = False
192
        while not done:
193
            done, data = self.monitor()
194
            output += data
195
            log( data )
196
        return output
197

    
198
    def cmd( self, cmd, verbose=False ):
199
        """Send a command, wait for output, and return it.
200
           cmd: string"""
201
        log = info if verbose else debug
202
        log( '*** %s : %s\n' % ( self.name, cmd ) )
203
        self.sendCmd( cmd )
204
        return self.waitOutput( verbose )
205

    
206
    def cmdPrint( self, cmd ):
207
        """Call cmd and printing its output
208
           cmd: string"""
209
        return self.cmd( cmd, verbose=True )
210

    
211
    # Interface management, configuration, and routing
212

    
213
    # BL notes: This might be a bit redundant or over-complicated.
214
    # However, it does allow a bit of specialization, including
215
    # changing the canonical interface names. It's also tricky since
216
    # the real interfaces are created as veth pairs, so we can't
217
    # make a single interface at a time.
218

    
219
    def intfName( self, n ):
220
        "Construct a canonical interface name node-ethN for interface n."
221
        return self.name + '-eth' + repr( n )
222

    
223
    def newPort( self ):
224
        "Return the next port number to allocate."
225
        if len( self.ports ) > 0:
226
            return max( self.ports.values() ) + 1
227
        return 0
228

    
229
    def addIntf( self, intf, port ):
230
        """Add an interface.
231
           intf: interface name (nodeN-ethM)
232
           port: port number (typically OpenFlow port number)"""
233
        self.intfs[ port ] = intf
234
        self.ports[ intf ] = port
235
        #info( '\n' )
236
        #info( 'added intf %s:%d to node %s\n' % ( intf,port, self.name ) )
237
        if self.inNamespace:
238
            #info( 'moving w/inNamespace set\n' )
239
            moveIntf( intf, self )
240

    
241
    def registerIntf( self, intf, dstNode, dstIntf ):
242
        "Register connection of intf to dstIntf on dstNode."
243
        self.connection[ intf ] = ( dstNode, dstIntf )
244

    
245
    # This is a symmetric operation, but it makes sense to put
246
    # the code here since it is tightly coupled to routines in
247
    # this class. For a more symmetric API, you can use
248
    # mininet.util.createLink()
249

    
250
    def linkTo( self, node2, port1=None, port2=None ):
251
        """Create link to another node, making two new interfaces.
252
           node2: Node to link us to
253
           port1: our port number (optional)
254
           port2: node2 port number (optional)
255
           returns: intf1 name, intf2 name"""
256
        node1 = self
257
        if port1 is None:
258
            port1 = node1.newPort()
259
        if port2 is None:
260
            port2 = node2.newPort()
261
        intf1 = node1.intfName( port1 )
262
        intf2 = node2.intfName( port2 )
263
        makeIntfPair( intf1, intf2 )
264
        node1.addIntf( intf1, port1 )
265
        node2.addIntf( intf2, port2 )
266
        node1.registerIntf( intf1, node2, intf2 )
267
        node2.registerIntf( intf2, node1, intf1 )
268
        return intf1, intf2
269

    
270
    def deleteIntfs( self ):
271
        "Delete all of our interfaces."
272
        # In theory the interfaces should go away after we shut down.
273
        # However, this takes time, so we're better off removing them
274
        # explicitly so that we won't get errors if we run before they
275
        # have been removed by the kernel. Unfortunately this is very slow,
276
        # at least with Linux kernels before 2.6.33
277
        for intf in self.intfs.values():
278
            quietRun( 'ip link del ' + intf )
279
            info( '.' )
280
            # Does it help to sleep to let things run?
281
            sleep( 0.001 )
282

    
283
    def setMAC( self, intf, mac ):
284
        """Set the MAC address for an interface.
285
           mac: MAC address as string"""
286
        result = self.cmd( [ 'ifconfig', intf, 'down' ] )
287
        result += self.cmd( [ 'ifconfig', intf, 'hw', 'ether', mac ] )
288
        result += self.cmd( [ 'ifconfig', intf, 'up' ] )
289
        return result
290

    
291
    def setARP( self, ip, mac ):
292
        """Add an ARP entry.
293
           ip: IP address as string
294
           mac: MAC address as string"""
295
        result = self.cmd( [ 'arp', '-s', ip, mac ] )
296
        return result
297

    
298
    def setIP( self, intf, ip, prefixLen=8 ):
299
        """Set the IP address for an interface.
300
           intf: interface name
301
           ip: IP address as a string
302
           prefixLen: prefix length, e.g. 8 for /8 or 16M addrs"""
303
        ipSub = '%s/%d' % ( ip, prefixLen )
304
        result = self.cmd( [ 'ifconfig', intf, ipSub, 'up' ] )
305
        self.ips[ intf ] = ip
306
        return result
307

    
308
    def setHostRoute( self, ip, intf ):
309
        """Add route to host.
310
           ip: IP address as dotted decimal
311
           intf: string, interface name"""
312
        return self.cmd( 'route add -host ' + ip + ' dev ' + intf )
313

    
314
    def setDefaultRoute( self, intf ):
315
        """Set the default route to go through intf.
316
           intf: string, interface name"""
317
        self.cmd( 'ip route flush' )
318
        return self.cmd( 'route add default ' + intf )
319

    
320
    def IP( self, intf=None ):
321
        "Return IP address of a node or specific interface."
322
        if len( self.ips ) == 1:
323
            return self.ips.values()[ 0 ]
324
        if intf:
325
            return self.ips.get( intf, None )
326

    
327
    def MAC( self, intf=None ):
328
        "Return MAC address of a node or specific interface."
329
        if intf is None and len( self.intfs ) == 1:
330
            intf = self.intfs.values()[ 0 ]
331
        ifconfig = self.cmd( 'ifconfig ' + intf )
332
        macs = re.findall( '..:..:..:..:..:..', ifconfig )
333
        if len( macs ) > 0:
334
            return macs[ 0 ]
335

    
336
    def intfIsUp( self, intf ):
337
        "Check if an interface is up."
338
        return 'UP' in self.cmd( 'ifconfig ' + intf )
339

    
340
    def modIntfs( self, action ):
341
        """Bring all interfaces up or down.
342
           action: string to pass to ifconfig"""
343
        for intf in self.intfs.values():
344
            result = self.cmd( [ 'ifconfig', intf, action ] )
345

    
346
    # Other methods
347
    def __str__( self ):
348
        result = self.name + ':'
349
        result += ' IP=' + str( self.IP() )
350
        result += ' intfs=' + ','.join( sorted( self.intfs.values() ) )
351
        result += ' waiting=' + str( self.waiting )
352
        result += ' pid=' + str( self.pid )
353
        return result
354

    
355

    
356
class Host( Node ):
357
    "A host is simply a Node."
358
    pass
359

    
360
    # Ideally, pausing a host would pause the process.  However, when one
361
    # tries to run a command on a paused host, it leads to an exception later.
362
    # For now, disable interfaces to "pause" the host.
363
    def pause( self ):
364
        "Disable interfaces."
365
        self.modIntfs('down')
366

    
367
    def resume( self ):
368
        "Re-enable interfaces"
369
        self.modIntfs('up')
370

    
371
class Switch( Node ):
372
    """A Switch is a Node that is running (or has execed?)
373
       an OpenFlow switch."""
374

    
375
    def sendCmd( self, cmd, printPid=False):
376
        """Send command to Node.
377
           cmd: string"""
378
        if not self.execed:
379
            return Node.sendCmd( self, cmd, printPid )
380
        else:
381
            error( '*** Error: %s has execed and cannot accept commands' %
382
                     self.name )
383

    
384
    def monitor( self ):
385
        "Monitor node."
386
        if not self.execed:
387
            return Node.monitor( self )
388
        else:
389
            return True, ''
390

    
391

    
392
class UserSwitch( Switch ):
393
    "User-space switch."
394

    
395
    def __init__( self, name, *args, **kwargs ):
396
        """Init.
397
           name: name for the switch"""
398
        Switch.__init__( self, name, **kwargs )
399

    
400
    def start( self, controllers ):
401
        """Start OpenFlow reference user datapath.
402
           Log to /tmp/sN-{ofd,ofp}.log.
403
           controllers: list of controller objects"""
404
        controller = controllers[ 0 ]
405
        ofdlog = '/tmp/' + self.name + '-ofd.log'
406
        ofplog = '/tmp/' + self.name + '-ofp.log'
407
        self.cmd( 'ifconfig lo up' )
408
        intfs = sorted( self.intfs.values() )
409
        if self.inNamespace:
410
            intfs = intfs[ :-1 ]
411
        self.cmd( 'ofdatapath -i ' + ','.join( intfs ) +
412
            ' punix:/tmp/' + self.name +
413
            ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' )
414
        self.cmd( 'ofprotocol unix:/tmp/' + self.name +
415
            ' tcp:' + controller.IP() + ' --fail=closed' +
416
            ' 1> ' + ofplog + ' 2>' + ofplog + ' &' )
417

    
418
    def stop( self ):
419
        "Stop OpenFlow reference user datapath."
420
        self.cmd( 'kill %ofdatapath' )
421
        self.cmd( 'kill %ofprotocol' )
422
        self.deleteIntfs()
423

    
424
    def pause( self ):
425
        "Pause ofprotocol and ofdatapath."
426
        self.cmd( 'kill -STOP %ofdatapath' )
427
        self.cmd( 'kill -STOP %ofprotocol' )
428

    
429
    def resume( self ):
430
        "Resume ofprotocol and datapath."
431
        self.cmd( 'kill -CONT %ofdatapath' )
432
        self.cmd( 'kill -CONT %ofprotocol' )
433

    
434

    
435
class KernelSwitch( Switch ):
436
    """Kernel-space switch.
437
       Currently only works in root namespace."""
438

    
439
    def __init__( self, name, dp=None, **kwargs ):
440
        """Init.
441
           name: name for switch
442
           dp: netlink id (0, 1, 2, ...)
443
           defaultMAC: default MAC as string; random value if None"""
444
        Switch.__init__( self, name, **kwargs )
445
        self.dp = dp
446
        if self.inNamespace:
447
            error( "KernelSwitch currently only works"
448
                " in the root namespace." )
449
            exit( 1 )
450

    
451
    def start( self, controllers ):
452
        "Start up reference kernel datapath."
453
        ofplog = '/tmp/' + self.name + '-ofp.log'
454
        quietRun( 'ifconfig lo up' )
455
        # Delete local datapath if it exists;
456
        # then create a new one monitoring the given interfaces
457
        quietRun( 'dpctl deldp nl:%i' % self.dp )
458
        self.cmd( 'dpctl adddp nl:%i' % self.dp )
459
        if self.defaultMAC:
460
            intf = 'of%i' % self.dp
461
            self.cmd( [ 'ifconfig', intf, 'hw', 'ether', self.defaultMAC ] )
462
        if len( self.intfs ) != max( self.intfs ) + 1:
463
            raise Exception( 'only contiguous, zero-indexed port ranges'
464
                            'supported: %s' % self.intfs )
465
        intfs = [ self.intfs[ port ] for port in sorted( self.intfs.keys() ) ]
466
        self.cmd( 'dpctl addif nl:' + str( self.dp ) + ' ' +
467
            ' '.join( intfs ) )
468
        # Run protocol daemon
469
        controller = controllers[ 0 ]
470
        self.cmd( 'ofprotocol nl:' + str( self.dp ) + ' tcp:' +
471
                      controller.IP() + ':' +
472
                      str( controller.port ) +
473
                      ' --fail=closed 1> ' + ofplog + ' 2>' + ofplog + ' &' )
474
        self.execed = False
475

    
476
    def stop( self ):
477
        "Terminate kernel datapath."
478
        quietRun( 'dpctl deldp nl:%i' % self.dp )
479
        self.cmd( 'kill %ofprotocol' )
480
        self.deleteIntfs()
481

    
482
    # Since kernel threads cannot receive signals like user-space processes,
483
    # disabling the interfaces and ofdatapath is our workaround.
484
    def pause( self ):
485
        "Disable interfaces and pause ofprotocol."
486
        self.cmd( 'kill -STOP %ofprotocol' )
487
        self.modIntfs('down')
488

    
489
    def resume( self ):
490
        "Re-enable interfaces and resume ofprotocol."
491
        self.cmd( 'kill -CONT %ofprotocol' )
492
        self.modIntfs('up')
493

    
494
class OVSKernelSwitch( Switch ):
495
    """Open VSwitch kernel-space switch.
496
       Currently only works in the root namespace."""
497

    
498
    def __init__( self, name, dp=None, **kwargs ):
499
        """Init.
500
           name: name for switch
501
           dp: netlink id (0, 1, 2, ...)
502
           defaultMAC: default MAC as unsigned int; random value if None"""
503
        Switch.__init__( self, name, **kwargs )
504
        self.dp = dp
505
        if self.inNamespace:
506
            error( "OVSKernelSwitch currently only works"
507
                " in the root namespace." )
508
            exit( 1 )
509

    
510
    def start( self, controllers ):
511
        "Start up kernel datapath."
512
        ofplog = '/tmp/' + self.name + '-ofp.log'
513
        quietRun( 'ifconfig lo up' )
514
        # Delete local datapath if it exists;
515
        # then create a new one monitoring the given interfaces
516
        quietRun( 'ovs-dpctl del-dp dp%i' % self.dp )
517
        self.cmd( 'ovs-dpctl add-dp dp%i' % self.dp )
518
        if self.defaultMAC:
519
            intf = 'dp%i' % self.dp
520
            mac = self.defaultMAC
521
            self.cmd( [ 'ifconfig', intf, 'hw', 'ether', mac ] )
522

    
523
        if len( self.intfs ) != max( self.intfs ) + 1:
524
            raise Exception( 'only contiguous, zero-indexed port ranges'
525
                            'supported: %s' % self.intfs )
526
        intfs = [ self.intfs[ port ] for port in sorted( self.intfs.keys() ) ]
527
        self.cmd( 'ovs-dpctl add-if dp' + str( self.dp ) + ' ' +
528
                      ' '.join( intfs ) )
529
        # Run protocol daemon
530
        controller = controllers[ 0 ]
531
        self.cmd( 'ovs-openflowd dp' + str( self.dp ) + ' tcp:' +
532
                      controller.IP() + ':' +
533
                      ' --fail=closed 1> ' + ofplog + ' 2>' + ofplog + ' &' )
534
        self.execed = False
535

    
536
    def stop( self ):
537
        "Terminate kernel datapath."
538
        quietRun( 'ovs-dpctl del-dp dp%i' % self.dp )
539
        self.cmd( 'kill %ovs-openflowd' )
540
        self.deleteIntfs()
541

    
542
    # Since kernel threads cannot receive signals like user-space processes,
543
    # disabling the interfaces and ofdatapath is our workaround.
544
    def pause( self ):
545
        "Disable interfaces and pause ovs-openflowd."
546
        self.cmd( 'kill -STOP %ovs-openflowd' )
547
        self.modIntfs('down')
548

    
549
    def resume( self ):
550
        "Re-enable interfaces and resume ovs-openflowd."
551
        self.cmd( 'kill -CONT %ovs-openflowd' )
552
        self.modIntfs('up')
553

    
554

    
555
class Controller( Node ):
556
    """A Controller is a Node that is running (or has execed?) an
557
       OpenFlow controller."""
558

    
559
    def __init__( self, name, inNamespace=False, controller='controller',
560
                 cargs='-v ptcp:', cdir=None, defaultIP="127.0.0.1",
561
                 port=6633 ):
562
        self.controller = controller
563
        self.cargs = cargs
564
        self.cdir = cdir
565
        self.port = port
566
        Node.__init__( self, name, inNamespace=inNamespace,
567
            defaultIP=defaultIP )
568

    
569
    def start( self ):
570
        """Start <controller> <args> on controller.
571
           Log to /tmp/cN.log"""
572
        cout = '/tmp/' + self.name + '.log'
573
        if self.cdir is not None:
574
            self.cmd( 'cd ' + self.cdir )
575
        self.cmd( self.controller + ' ' + self.cargs +
576
            ' 1> ' + cout + ' 2> ' + cout + ' &' )
577
        self.execed = False
578

    
579
    def stop( self ):
580
        "Stop controller."
581
        self.cmd( 'kill %' + self.controller )
582
        self.terminate()
583

    
584
    def pause( self ):
585
        "Pause controller."
586
        self.cmd( 'kill -STOP %' + self.controller )
587

    
588
    def resume( self ):
589
        "Resume controller."
590
        self.cmd( 'kill -CONT %' + self.controller )
591

    
592
    def IP( self, intf=None ):
593
        "Return IP address of the Controller"
594
        ip = Node.IP( self, intf=intf )
595
        if ip is None:
596
            ip = self.defaultIP
597
        return ip
598

    
599

    
600
class ControllerParams( object ):
601
    "Container for controller IP parameters."
602

    
603
    def __init__( self, ip, prefixLen ):
604
        """Init.
605
           ip: string, controller IP address
606
           prefixLen: prefix length, e.g. 8 for /8, covering 16M"""
607
        self.ip = ip
608
        self.prefixLen = prefixLen
609

    
610

    
611
class NOX( Controller ):
612
    "Controller to run a NOX application."
613

    
614
    def __init__( self, name, inNamespace=False, noxArgs=None, **kwargs ):
615
        """Init.
616
           name: name to give controller
617
           noxArgs: list of args, or single arg, to pass to NOX"""
618
        if type( noxArgs ) != list:
619
            noxArgs = [ noxArgs ]
620
        if not noxArgs:
621
            noxArgs = [ 'packetdump' ]
622

    
623
        if 'NOX_CORE_DIR' not in os.environ:
624
            exit( 'exiting; please set missing NOX_CORE_DIR env var' )
625
        noxCoreDir = os.environ[ 'NOX_CORE_DIR' ]
626

    
627
        Controller.__init__( self, name,
628
            controller=noxCoreDir + '/nox_core',
629
            cargs='--libdir=/usr/local/lib -v -i ptcp: ' +
630
                    ' '.join( noxArgs ),
631
            cdir = noxCoreDir, **kwargs )
632

    
633

    
634
class RemoteController( Controller ):
635
    "Controller running outside of Mininet's control."
636

    
637
    def __init__( self, name, inNamespace=False, defaultIP='127.0.0.1',
638
                 port=6633 ):
639
        """Init.
640
           name: name to give controller
641
           defaultIP: the IP address where the remote controller is
642
           listening
643
           port: the port where the remote controller is listening"""
644
        Controller.__init__( self, name, defaultIP=defaultIP, port=port )
645

    
646
    def start( self ):
647
        "Overridden to do nothing."
648
        return
649

    
650
    def stop( self ):
651
        "Overridden to do nothing."
652
        return