Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ 35341142

History | View | Annotate | Download (24.4 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
from mininet.moduledeps import moduleDeps, OVS_KMOD, OF_KMOD, TUN
54

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

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

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

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

    
108
    def cleanup( self ):
109
        "Help python collect its garbage."
110
        self.shell = None
111

    
112
    # Subshell I/O, commands and control
113
    def read( self, bytes ):
114
        """Buffered read from node, non-blocking.
115
           bytes: maximum number of bytes to return"""
116
        count = len( self.readbuf )
117
        if count < bytes:
118
            data = os.read( self.stdout.fileno(), bytes - count )
119
            self.readbuf += data
120
        if bytes >= len( self.readbuf ):
121
            result = self.readbuf
122
            self.readbuf = ''
123
        else:
124
            result = self.readbuf[ :bytes ]
125
            self.readbuf = self.readbuf[ bytes: ]
126
        return result
127

    
128
    def readline( self ):
129
        """Buffered readline from node, non-blocking.
130
           returns: line (minus newline) or None"""
131
        self.readbuf += self.read( 1024 )
132
        if '\n' not in self.readbuf:
133
            return None
134
        pos = self.readbuf.find( '\n' )
135
        line = self.readbuf[ 0 : pos ]
136
        self.readbuf = self.readbuf[ pos + 1: ]
137
        return line
138

    
139
    def write( self, data ):
140
        """Write data to node.
141
           data: string"""
142
        os.write( self.stdin.fileno(), data )
143

    
144
    def terminate( self ):
145
        "Send kill signal to Node and clean up after it."
146
        os.kill( self.pid, signal.SIGKILL )
147
        self.cleanup()
148

    
149
    def stop( self ):
150
        "Stop node."
151
        self.terminate()
152

    
153
    def waitReadable( self ):
154
        "Wait until node's output is readable."
155
        if len( self.readbuf ) == 0:
156
            self.pollOut.poll()
157

    
158
    def sendCmd( self, cmd, printPid=False ):
159
        """Send a command, followed by a command to echo a sentinel,
160
           and return without waiting for the command to complete."""
161
        assert not self.waiting
162
        if isinstance( cmd, list ):
163
            cmd = ' '.join( cmd )
164
        if cmd[ -1 ] == '&':
165
            separator = '&'
166
            cmd = cmd[ :-1 ]
167
        else:
168
            separator = ';'
169
            if printPid and not isShellBuiltin( cmd ):
170
                cmd = 'mnexec -p ' + cmd
171
        self.write( cmd + separator + ' echo -n "\\0177" \n' )
172
        self.lastCmd = cmd
173
        self.lastPid = None
174
        self.waiting = True
175

    
176
    def sendInt( self, sig=signal.SIGINT ):
177
        "Interrupt running command."
178
        if self.lastPid:
179
            os.kill( self.lastPid, sig )
180

    
181
    def monitor( self ):
182
        """Monitor and return the output of a command.
183
           Set self.waiting to False if command has completed."""
184
        assert self.waiting
185
        self.waitReadable()
186
        data = self.read( 1024 )
187
        # Look for PID
188
        marker = chr( 1 ) + r'\d+\n'
189
        if chr( 1 ) in data:
190
            markers = re.findall( marker, data )
191
            if markers:
192
                self.lastPid = int( markers[ 0 ][ 1: ] )
193
                data = re.sub( marker, '', data )
194
        # Look for sentinel/EOF
195
        if len( data ) > 0 and data[ -1 ] == chr( 127 ):
196
            self.waiting = False
197
            data = data[ :-1 ]
198
        elif chr( 127 ) in data:
199
            self.waiting = False
200
            data = data.replace( chr( 127 ), '' )
201
        return data
202

    
203
    def waitOutput( self, verbose=False ):
204
        """Wait for a command to complete.
205
           Completion is signaled by a sentinel character, ASCII(127)
206
           appearing in the output stream.  Wait for the sentinel and return
207
           the output, including trailing newline.
208
           verbose: print output interactively"""
209
        log = info if verbose else debug
210
        output = ''
211
        while self.waiting:
212
            data = self.monitor()
213
            output += data
214
            log( data )
215
        return output
216

    
217
    def cmd( self, cmd, verbose=False ):
218
        """Send a command, wait for output, and return it.
219
           cmd: string"""
220
        log = info if verbose else debug
221
        log( '*** %s : %s\n' % ( self.name, cmd ) )
222
        self.sendCmd( cmd )
223
        return self.waitOutput( verbose )
224

    
225
    def cmdPrint( self, cmd ):
226
        """Call cmd and printing its output
227
           cmd: string"""
228
        return self.cmd( cmd, verbose=True )
229

    
230
    # Interface management, configuration, and routing
231

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

    
238
    def intfName( self, n ):
239
        "Construct a canonical interface name node-ethN for interface n."
240
        return self.name + '-eth' + repr( n )
241

    
242
    def newPort( self ):
243
        "Return the next port number to allocate."
244
        if len( self.ports ) > 0:
245
            return max( self.ports.values() ) + 1
246
        return 0
247

    
248
    def addIntf( self, intf, port ):
249
        """Add an interface.
250
           intf: interface name (nodeN-ethM)
251
           port: port number (typically OpenFlow port number)"""
252
        self.intfs[ port ] = intf
253
        self.ports[ intf ] = port
254
        #info( '\n' )
255
        #info( 'added intf %s:%d to node %s\n' % ( intf,port, self.name ) )
256
        if self.inNamespace:
257
            #info( 'moving w/inNamespace set\n' )
258
            moveIntf( intf, self )
259

    
260
    def registerIntf( self, intf, dstNode, dstIntf ):
261
        "Register connection of intf to dstIntf on dstNode."
262
        self.connection[ intf ] = ( dstNode, dstIntf )
263

    
264
    def connectionsTo( self, node):
265
        "Return [(srcIntf, dstIntf)..] for connections to dstNode."
266
        # We could optimize this if it is important
267
        connections = []
268
        for intf in self.connection.keys():
269
            dstNode, dstIntf = self.connection[ intf ]
270
            if dstNode == node:
271
                connections.append( ( intf, dstIntf ) )
272
        return connections
273

    
274
    # This is a symmetric operation, but it makes sense to put
275
    # the code here since it is tightly coupled to routines in
276
    # this class. For a more symmetric API, you can use
277
    # mininet.util.createLink()
278

    
279
    def linkTo( self, node2, port1=None, port2=None ):
280
        """Create link to another node, making two new interfaces.
281
           node2: Node to link us to
282
           port1: our port number (optional)
283
           port2: node2 port number (optional)
284
           returns: intf1 name, intf2 name"""
285
        node1 = self
286
        if port1 is None:
287
            port1 = node1.newPort()
288
        if port2 is None:
289
            port2 = node2.newPort()
290
        intf1 = node1.intfName( port1 )
291
        intf2 = node2.intfName( port2 )
292
        makeIntfPair( intf1, intf2 )
293
        node1.addIntf( intf1, port1 )
294
        node2.addIntf( intf2, port2 )
295
        node1.registerIntf( intf1, node2, intf2 )
296
        node2.registerIntf( intf2, node1, intf1 )
297
        return intf1, intf2
298

    
299
    def deleteIntfs( self ):
300
        "Delete all of our interfaces."
301
        # In theory the interfaces should go away after we shut down.
302
        # However, this takes time, so we're better off removing them
303
        # explicitly so that we won't get errors if we run before they
304
        # have been removed by the kernel. Unfortunately this is very slow,
305
        # at least with Linux kernels before 2.6.33
306
        for intf in self.intfs.values():
307
            quietRun( 'ip link del ' + intf )
308
            info( '.' )
309
            # Does it help to sleep to let things run?
310
            sleep( 0.001 )
311

    
312
    def setMAC( self, intf, mac ):
313
        """Set the MAC address for an interface.
314
           mac: MAC address as string"""
315
        result = self.cmd( [ 'ifconfig', intf, 'down' ] )
316
        result += self.cmd( [ 'ifconfig', intf, 'hw', 'ether', mac ] )
317
        result += self.cmd( [ 'ifconfig', intf, 'up' ] )
318
        return result
319

    
320
    def setARP( self, ip, mac ):
321
        """Add an ARP entry.
322
           ip: IP address as string
323
           mac: MAC address as string"""
324
        result = self.cmd( [ 'arp', '-s', ip, mac ] )
325
        return result
326

    
327
    def setIP( self, intf, ip, prefixLen=8 ):
328
        """Set the IP address for an interface.
329
           intf: interface name
330
           ip: IP address as a string
331
           prefixLen: prefix length, e.g. 8 for /8 or 16M addrs"""
332
        ipSub = '%s/%d' % ( ip, prefixLen )
333
        result = self.cmd( [ 'ifconfig', intf, ipSub, 'up' ] )
334
        self.ips[ intf ] = ip
335
        return result
336

    
337
    def setHostRoute( self, ip, intf ):
338
        """Add route to host.
339
           ip: IP address as dotted decimal
340
           intf: string, interface name"""
341
        return self.cmd( 'route add -host ' + ip + ' dev ' + intf )
342

    
343
    def setDefaultRoute( self, intf ):
344
        """Set the default route to go through intf.
345
           intf: string, interface name"""
346
        self.cmd( 'ip route flush root 0/0' )
347
        return self.cmd( 'route add default ' + intf )
348

    
349
    def IP( self, intf=None ):
350
        "Return IP address of a node or specific interface."
351
        if len( self.ips ) == 1:
352
            return self.ips.values()[ 0 ]
353
        if intf:
354
            return self.ips.get( intf, None )
355

    
356
    def MAC( self, intf=None ):
357
        "Return MAC address of a node or specific interface."
358
        if intf is None and len( self.intfs ) == 1:
359
            intf = self.intfs.values()[ 0 ]
360
        ifconfig = self.cmd( 'ifconfig ' + intf )
361
        macs = re.findall( '..:..:..:..:..:..', ifconfig )
362
        if len( macs ) > 0:
363
            return macs[ 0 ]
364

    
365
    def intfIsUp( self, intf ):
366
        "Check if an interface is up."
367
        return 'UP' in self.cmd( 'ifconfig ' + intf )
368

    
369
    def modIntfs( self, action ):
370
        """Bring all interfaces up or down.
371
           action: string to pass to ifconfig"""
372
        for intf in self.intfs.values():
373
            self.cmd( [ 'ifconfig', intf, action ] )
374

    
375
    # Other methods
376
    def __str__( self ):
377
        result = self.name + ':'
378
        result += ' IP=' + str( self.IP() )
379
        result += ' intfs=' + ','.join( sorted( self.intfs.values() ) )
380
        result += ' waiting=' + str( self.waiting )
381
        result += ' pid=' + str( self.pid )
382
        return result
383

    
384

    
385
class Host( Node ):
386
    "A host is simply a Node."
387

    
388
    # Ideally, pausing a host would pause the bash process corresponding to
389
    # that host.  However, when one tries to run a command on a paused host,
390
    # it leads to an exception later.  For now, disable interfaces to "pause"
391
    # the host.
392

    
393
    def pause( self ):
394
        "Disable interfaces."
395
        self.modIntfs('down')
396

    
397
    def resume( self ):
398
        "Re-enable interfaces"
399
        self.modIntfs('up')
400

    
401
class Switch( Node ):
402
    """A Switch is a Node that is running (or has execed?)
403
       an OpenFlow switch."""
404

    
405
    def sendCmd( self, cmd, printPid=False):
406
        """Send command to Node.
407
           cmd: string"""
408
        if not self.execed:
409
            return Node.sendCmd( self, cmd, printPid )
410
        else:
411
            error( '*** Error: %s has execed and cannot accept commands' %
412
                     self.name )
413

    
414
    def monitor( self ):
415
        "Monitor node."
416
        if not self.execed:
417
            return Node.monitor( self )
418
        else:
419
            return True, ''
420

    
421

    
422
class UserSwitch( Switch ):
423
    "User-space switch."
424

    
425
    def __init__( self, name, *args, **kwargs ):
426
        """Init.
427
           name: name for the switch"""
428
        Switch.__init__( self, name, **kwargs )
429

    
430
    @staticmethod
431
    def setup():
432
        "Ensure any dependencies are loaded; if not, try to load them."
433
        moduleDeps( add = TUN )
434

    
435
    def start( self, controllers ):
436
        """Start OpenFlow reference user datapath.
437
           Log to /tmp/sN-{ofd,ofp}.log.
438
           controllers: list of controller objects"""
439
        controller = controllers[ 0 ]
440
        ofdlog = '/tmp/' + self.name + '-ofd.log'
441
        ofplog = '/tmp/' + self.name + '-ofp.log'
442
        self.cmd( 'ifconfig lo up' )
443
        intfs = sorted( self.intfs.values() )
444
        if self.inNamespace:
445
            intfs = intfs[ :-1 ]
446
        self.cmd( 'ofdatapath -i ' + ','.join( intfs ) +
447
            ' punix:/tmp/' + self.name +
448
            ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' )
449
        self.cmd( 'ofprotocol unix:/tmp/' + self.name +
450
            ' tcp:' + controller.IP() + ' --fail=closed' +
451
            ' 1> ' + ofplog + ' 2>' + ofplog + ' &' )
452

    
453
    def stop( self ):
454
        "Stop OpenFlow reference user datapath."
455
        self.cmd( 'kill %ofdatapath' )
456
        self.cmd( 'kill %ofprotocol' )
457
        self.deleteIntfs()
458

    
459
    def pause( self ):
460
        "Pause ofprotocol and ofdatapath."
461
        self.cmd( 'kill -STOP %ofdatapath' )
462
        self.cmd( 'kill -STOP %ofprotocol' )
463

    
464
    def resume( self ):
465
        "Resume ofprotocol and datapath."
466
        self.cmd( 'kill -CONT %ofdatapath' )
467
        self.cmd( 'kill -CONT %ofprotocol' )
468

    
469

    
470
class KernelSwitch( Switch ):
471
    """Kernel-space switch.
472
       Currently only works in root namespace."""
473

    
474
    def __init__( self, name, dp=None, **kwargs ):
475
        """Init.
476
           name: name for switch
477
           dp: netlink id (0, 1, 2, ...)
478
           defaultMAC: default MAC as string; random value if None"""
479
        Switch.__init__( self, name, **kwargs )
480
        self.dp = dp
481
        if self.inNamespace:
482
            error( "KernelSwitch currently only works"
483
                " in the root namespace." )
484
            exit( 1 )
485

    
486
    @staticmethod
487
    def setup():
488
        "Ensure any dependencies are loaded; if not, try to load them."
489
        moduleDeps( subtract = OVS_KMOD, add = OF_KMOD )
490

    
491
    def start( self, controllers ):
492
        "Start up reference kernel datapath."
493
        ofplog = '/tmp/' + self.name + '-ofp.log'
494
        quietRun( 'ifconfig lo up' )
495
        # Delete local datapath if it exists;
496
        # then create a new one monitoring the given interfaces
497
        quietRun( 'dpctl deldp nl:%i' % self.dp )
498
        self.cmd( 'dpctl adddp nl:%i' % self.dp )
499
        if self.defaultMAC:
500
            intf = 'of%i' % self.dp
501
            self.cmd( [ 'ifconfig', intf, 'hw', 'ether', self.defaultMAC ] )
502
        if len( self.intfs ) != max( self.intfs ) + 1:
503
            raise Exception( 'only contiguous, zero-indexed port ranges'
504
                            'supported: %s' % self.intfs )
505
        intfs = [ self.intfs[ port ] for port in sorted( self.intfs.keys() ) ]
506
        self.cmd( 'dpctl addif nl:' + str( self.dp ) + ' ' +
507
            ' '.join( intfs ) )
508
        # Run protocol daemon
509
        controller = controllers[ 0 ]
510
        self.cmd( 'ofprotocol nl:' + str( self.dp ) + ' tcp:' +
511
                      controller.IP() + ':' +
512
                      str( controller.port ) +
513
                      ' --fail=closed 1> ' + ofplog + ' 2>' + ofplog + ' &' )
514
        self.execed = False
515

    
516
    def stop( self ):
517
        "Terminate kernel datapath."
518
        quietRun( 'dpctl deldp nl:%i' % self.dp )
519
        self.cmd( 'kill %ofprotocol' )
520
        self.deleteIntfs()
521

    
522
    # Since kernel threads cannot receive signals like user-space processes,
523
    # disabling the interfaces and ofdatapath is our workaround.
524

    
525
    def pause( self ):
526
        "Disable interfaces and pause ofprotocol."
527
        self.cmd( 'kill -STOP %ofprotocol' )
528
        self.modIntfs('down')
529

    
530
    def resume( self ):
531
        "Re-enable interfaces and resume ofprotocol."
532
        self.cmd( 'kill -CONT %ofprotocol' )
533
        self.modIntfs('up')
534

    
535
class OVSKernelSwitch( Switch ):
536
    """Open VSwitch kernel-space switch.
537
       Currently only works in the root namespace."""
538

    
539
    def __init__( self, name, dp=None, **kwargs ):
540
        """Init.
541
           name: name for switch
542
           dp: netlink id (0, 1, 2, ...)
543
           defaultMAC: default MAC as unsigned int; random value if None"""
544
        Switch.__init__( self, name, **kwargs )
545
        self.dp = dp
546
        if self.inNamespace:
547
            error( "OVSKernelSwitch currently only works"
548
                " in the root namespace." )
549
            exit( 1 )
550

    
551
    @staticmethod
552
    def setup():
553
        "Ensure any dependencies are loaded; if not, try to load them."
554
        moduleDeps( subtract = OF_KMOD, add = OVS_KMOD )
555

    
556
    def start( self, controllers ):
557
        "Start up kernel datapath."
558
        ofplog = '/tmp/' + self.name + '-ofp.log'
559
        quietRun( 'ifconfig lo up' )
560
        # Delete local datapath if it exists;
561
        # then create a new one monitoring the given interfaces
562
        quietRun( 'ovs-dpctl del-dp dp%i' % self.dp )
563
        self.cmd( 'ovs-dpctl add-dp dp%i' % self.dp )
564
        if self.defaultMAC:
565
            intf = 'dp%i' % self.dp
566
            mac = self.defaultMAC
567
            self.cmd( [ 'ifconfig', intf, 'hw', 'ether', mac ] )
568

    
569
        if len( self.intfs ) != max( self.intfs ) + 1:
570
            raise Exception( 'only contiguous, zero-indexed port ranges'
571
                            'supported: %s' % self.intfs )
572
        intfs = [ self.intfs[ port ] for port in sorted( self.intfs.keys() ) ]
573
        self.cmd( 'ovs-dpctl add-if dp' + str( self.dp ) + ' ' +
574
                      ' '.join( intfs ) )
575
        # Run protocol daemon
576
        controller = controllers[ 0 ]
577
        self.cmd( 'ovs-openflowd dp' + str( self.dp ) + ' tcp:' +
578
                      controller.IP() + ':' +
579
                      ' --fail=closed 1> ' + ofplog + ' 2>' + ofplog + ' &' )
580
        self.execed = False
581

    
582
    def stop( self ):
583
        "Terminate kernel datapath."
584
        quietRun( 'ovs-dpctl del-dp dp%i' % self.dp )
585
        self.cmd( 'kill %ovs-openflowd' )
586
        self.deleteIntfs()
587

    
588
    # Since kernel threads cannot receive signals like user-space processes,
589
    # disabling the interfaces and ovs-openflowd is our workaround.
590

    
591
    def pause( self ):
592
        "Disable interfaces and pause ovs-openflowd."
593
        self.cmd( 'kill -STOP %ovs-openflowd' )
594
        self.modIntfs('down')
595

    
596
    def resume( self ):
597
        "Re-enable interfaces and resume ovs-openflowd."
598
        self.cmd( 'kill -CONT %ovs-openflowd' )
599
        self.modIntfs('up')
600

    
601

    
602
class Controller( Node ):
603
    """A Controller is a Node that is running (or has execed?) an
604
       OpenFlow controller."""
605

    
606
    def __init__( self, name, inNamespace=False, controller='controller',
607
                 cargs='-v ptcp:', cdir=None, defaultIP="127.0.0.1",
608
                 port=6633 ):
609
        self.controller = controller
610
        self.cargs = cargs
611
        self.cdir = cdir
612
        self.port = port
613
        Node.__init__( self, name, inNamespace=inNamespace,
614
            defaultIP=defaultIP )
615

    
616
    def start( self ):
617
        """Start <controller> <args> on controller.
618
           Log to /tmp/cN.log"""
619
        cout = '/tmp/' + self.name + '.log'
620
        if self.cdir is not None:
621
            self.cmd( 'cd ' + self.cdir )
622
        self.cmd( self.controller + ' ' + self.cargs +
623
            ' 1> ' + cout + ' 2> ' + cout + ' &' )
624
        self.execed = False
625

    
626
    def stop( self ):
627
        "Stop controller."
628
        self.cmd( 'kill %' + self.controller )
629
        self.terminate()
630

    
631
    def pause( self ):
632
        "Pause controller."
633
        self.cmd( 'kill -STOP %' + self.controller )
634

    
635
    def resume( self ):
636
        "Resume controller."
637
        self.cmd( 'kill -CONT %' + self.controller )
638

    
639
    def IP( self, intf=None ):
640
        "Return IP address of the Controller"
641
        ip = Node.IP( self, intf=intf )
642
        if ip is None:
643
            ip = self.defaultIP
644
        return ip
645

    
646

    
647
class ControllerParams( object ):
648
    "Container for controller IP parameters."
649

    
650
    def __init__( self, ip, prefixLen ):
651
        """Init.
652
           ip: string, controller IP address
653
           prefixLen: prefix length, e.g. 8 for /8, covering 16M"""
654
        self.ip = ip
655
        self.prefixLen = prefixLen
656

    
657

    
658
class NOX( Controller ):
659
    "Controller to run a NOX application."
660

    
661
    def __init__( self, name, inNamespace=False, noxArgs=None, **kwargs ):
662
        """Init.
663
           name: name to give controller
664
           noxArgs: list of args, or single arg, to pass to NOX"""
665
        if type( noxArgs ) != list:
666
            noxArgs = [ noxArgs ]
667
        if not noxArgs:
668
            noxArgs = [ 'packetdump' ]
669

    
670
        if 'NOX_CORE_DIR' not in os.environ:
671
            exit( 'exiting; please set missing NOX_CORE_DIR env var' )
672
        noxCoreDir = os.environ[ 'NOX_CORE_DIR' ]
673

    
674
        Controller.__init__( self, name,
675
            controller=noxCoreDir + '/nox_core',
676
            cargs='--libdir=/usr/local/lib -v -i ptcp: ' +
677
                    ' '.join( noxArgs ),
678
            cdir = noxCoreDir, **kwargs )
679

    
680

    
681
class RemoteController( Controller ):
682
    "Controller running outside of Mininet's control."
683

    
684
    def __init__( self, name, inNamespace=False, defaultIP='127.0.0.1',
685
                 port=6633 ):
686
        """Init.
687
           name: name to give controller
688
           defaultIP: the IP address where the remote controller is
689
           listening
690
           port: the port where the remote controller is listening"""
691
        Controller.__init__( self, name, defaultIP=defaultIP, port=port )
692

    
693
    def start( self ):
694
        "Overridden to do nothing."
695
        return
696

    
697
    def stop( self ):
698
        "Overridden to do nothing."
699
        return