Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ 47acf539

History | View | Annotate | Download (24.3 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
        cmd = [ 'mnexec', opts, 'bash', '-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
        self.readbuf = ''
97
        self.waiting = False
98

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
229
    # Interface management, configuration, and routing
230

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
383

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

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

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

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

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

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

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

    
420

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

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

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

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

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

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

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

    
468

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
600

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

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

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

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

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

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

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

    
645

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

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

    
656

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

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

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

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

    
679

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

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

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

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