Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ ec7b211c

History | View | Annotate | Download (24 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
    # This is a symmetric operation, but it makes sense to put
265
    # the code here since it is tightly coupled to routines in
266
    # this class. For a more symmetric API, you can use
267
    # mininet.util.createLink()
268

    
269
    def linkTo( self, node2, port1=None, port2=None ):
270
        """Create link to another node, making two new interfaces.
271
           node2: Node to link us to
272
           port1: our port number (optional)
273
           port2: node2 port number (optional)
274
           returns: intf1 name, intf2 name"""
275
        node1 = self
276
        if port1 is None:
277
            port1 = node1.newPort()
278
        if port2 is None:
279
            port2 = node2.newPort()
280
        intf1 = node1.intfName( port1 )
281
        intf2 = node2.intfName( port2 )
282
        makeIntfPair( intf1, intf2 )
283
        node1.addIntf( intf1, port1 )
284
        node2.addIntf( intf2, port2 )
285
        node1.registerIntf( intf1, node2, intf2 )
286
        node2.registerIntf( intf2, node1, intf1 )
287
        return intf1, intf2
288

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

    
302
    def setMAC( self, intf, mac ):
303
        """Set the MAC address for an interface.
304
           mac: MAC address as string"""
305
        result = self.cmd( [ 'ifconfig', intf, 'down' ] )
306
        result += self.cmd( [ 'ifconfig', intf, 'hw', 'ether', mac ] )
307
        result += self.cmd( [ 'ifconfig', intf, 'up' ] )
308
        return result
309

    
310
    def setARP( self, ip, mac ):
311
        """Add an ARP entry.
312
           ip: IP address as string
313
           mac: MAC address as string"""
314
        result = self.cmd( [ 'arp', '-s', ip, mac ] )
315
        return result
316

    
317
    def setIP( self, intf, ip, prefixLen=8 ):
318
        """Set the IP address for an interface.
319
           intf: interface name
320
           ip: IP address as a string
321
           prefixLen: prefix length, e.g. 8 for /8 or 16M addrs"""
322
        ipSub = '%s/%d' % ( ip, prefixLen )
323
        result = self.cmd( [ 'ifconfig', intf, ipSub, 'up' ] )
324
        self.ips[ intf ] = ip
325
        return result
326

    
327
    def setHostRoute( self, ip, intf ):
328
        """Add route to host.
329
           ip: IP address as dotted decimal
330
           intf: string, interface name"""
331
        return self.cmd( 'route add -host ' + ip + ' dev ' + intf )
332

    
333
    def setDefaultRoute( self, intf ):
334
        """Set the default route to go through intf.
335
           intf: string, interface name"""
336
        self.cmd( 'ip route flush root 0/0' )
337
        return self.cmd( 'route add default ' + intf )
338

    
339
    def IP( self, intf=None ):
340
        "Return IP address of a node or specific interface."
341
        if len( self.ips ) == 1:
342
            return self.ips.values()[ 0 ]
343
        if intf:
344
            return self.ips.get( intf, None )
345

    
346
    def MAC( self, intf=None ):
347
        "Return MAC address of a node or specific interface."
348
        if intf is None and len( self.intfs ) == 1:
349
            intf = self.intfs.values()[ 0 ]
350
        ifconfig = self.cmd( 'ifconfig ' + intf )
351
        macs = re.findall( '..:..:..:..:..:..', ifconfig )
352
        if len( macs ) > 0:
353
            return macs[ 0 ]
354

    
355
    def intfIsUp( self, intf ):
356
        "Check if an interface is up."
357
        return 'UP' in self.cmd( 'ifconfig ' + intf )
358

    
359
    def modIntfs( self, action ):
360
        """Bring all interfaces up or down.
361
           action: string to pass to ifconfig"""
362
        for intf in self.intfs.values():
363
            self.cmd( [ 'ifconfig', intf, action ] )
364

    
365
    # Other methods
366
    def __str__( self ):
367
        result = self.name + ':'
368
        result += ' IP=' + str( self.IP() )
369
        result += ' intfs=' + ','.join( sorted( self.intfs.values() ) )
370
        result += ' waiting=' + str( self.waiting )
371
        result += ' pid=' + str( self.pid )
372
        return result
373

    
374

    
375
class Host( Node ):
376
    "A host is simply a Node."
377

    
378
    # Ideally, pausing a host would pause the bash process corresponding to
379
    # that host.  However, when one tries to run a command on a paused host,
380
    # it leads to an exception later.  For now, disable interfaces to "pause"
381
    # the host.
382

    
383
    def pause( self ):
384
        "Disable interfaces."
385
        self.modIntfs('down')
386

    
387
    def resume( self ):
388
        "Re-enable interfaces"
389
        self.modIntfs('up')
390

    
391
class Switch( Node ):
392
    """A Switch is a Node that is running (or has execed?)
393
       an OpenFlow switch."""
394

    
395
    def sendCmd( self, cmd, printPid=False):
396
        """Send command to Node.
397
           cmd: string"""
398
        if not self.execed:
399
            return Node.sendCmd( self, cmd, printPid )
400
        else:
401
            error( '*** Error: %s has execed and cannot accept commands' %
402
                     self.name )
403

    
404
    def monitor( self ):
405
        "Monitor node."
406
        if not self.execed:
407
            return Node.monitor( self )
408
        else:
409
            return True, ''
410

    
411

    
412
class UserSwitch( Switch ):
413
    "User-space switch."
414

    
415
    def __init__( self, name, *args, **kwargs ):
416
        """Init.
417
           name: name for the switch"""
418
        Switch.__init__( self, name, **kwargs )
419

    
420
    @staticmethod
421
    def setup():
422
        "Ensure any dependencies are loaded; if not, try to load them."
423
        moduleDeps( add = TUN )
424

    
425
    def start( self, controllers ):
426
        """Start OpenFlow reference user datapath.
427
           Log to /tmp/sN-{ofd,ofp}.log.
428
           controllers: list of controller objects"""
429
        controller = controllers[ 0 ]
430
        ofdlog = '/tmp/' + self.name + '-ofd.log'
431
        ofplog = '/tmp/' + self.name + '-ofp.log'
432
        self.cmd( 'ifconfig lo up' )
433
        intfs = sorted( self.intfs.values() )
434
        if self.inNamespace:
435
            intfs = intfs[ :-1 ]
436
        self.cmd( 'ofdatapath -i ' + ','.join( intfs ) +
437
            ' punix:/tmp/' + self.name +
438
            ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' )
439
        self.cmd( 'ofprotocol unix:/tmp/' + self.name +
440
            ' tcp:' + controller.IP() + ' --fail=closed' +
441
            ' 1> ' + ofplog + ' 2>' + ofplog + ' &' )
442

    
443
    def stop( self ):
444
        "Stop OpenFlow reference user datapath."
445
        self.cmd( 'kill %ofdatapath' )
446
        self.cmd( 'kill %ofprotocol' )
447
        self.deleteIntfs()
448

    
449
    def pause( self ):
450
        "Pause ofprotocol and ofdatapath."
451
        self.cmd( 'kill -STOP %ofdatapath' )
452
        self.cmd( 'kill -STOP %ofprotocol' )
453

    
454
    def resume( self ):
455
        "Resume ofprotocol and datapath."
456
        self.cmd( 'kill -CONT %ofdatapath' )
457
        self.cmd( 'kill -CONT %ofprotocol' )
458

    
459

    
460
class KernelSwitch( Switch ):
461
    """Kernel-space switch.
462
       Currently only works in root namespace."""
463

    
464
    def __init__( self, name, dp=None, **kwargs ):
465
        """Init.
466
           name: name for switch
467
           dp: netlink id (0, 1, 2, ...)
468
           defaultMAC: default MAC as string; random value if None"""
469
        Switch.__init__( self, name, **kwargs )
470
        self.dp = dp
471
        if self.inNamespace:
472
            error( "KernelSwitch currently only works"
473
                " in the root namespace." )
474
            exit( 1 )
475

    
476
    @staticmethod
477
    def setup():
478
        "Ensure any dependencies are loaded; if not, try to load them."
479
        moduleDeps( subtract = OVS_KMOD, add = OF_KMOD )
480

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

    
506
    def stop( self ):
507
        "Terminate kernel datapath."
508
        quietRun( 'dpctl deldp nl:%i' % self.dp )
509
        self.cmd( 'kill %ofprotocol' )
510
        self.deleteIntfs()
511

    
512
    # Since kernel threads cannot receive signals like user-space processes,
513
    # disabling the interfaces and ofdatapath is our workaround.
514

    
515
    def pause( self ):
516
        "Disable interfaces and pause ofprotocol."
517
        self.cmd( 'kill -STOP %ofprotocol' )
518
        self.modIntfs('down')
519

    
520
    def resume( self ):
521
        "Re-enable interfaces and resume ofprotocol."
522
        self.cmd( 'kill -CONT %ofprotocol' )
523
        self.modIntfs('up')
524

    
525
class OVSKernelSwitch( Switch ):
526
    """Open VSwitch kernel-space switch.
527
       Currently only works in the root namespace."""
528

    
529
    def __init__( self, name, dp=None, **kwargs ):
530
        """Init.
531
           name: name for switch
532
           dp: netlink id (0, 1, 2, ...)
533
           defaultMAC: default MAC as unsigned int; random value if None"""
534
        Switch.__init__( self, name, **kwargs )
535
        self.dp = dp
536
        if self.inNamespace:
537
            error( "OVSKernelSwitch currently only works"
538
                " in the root namespace." )
539
            exit( 1 )
540

    
541
    @staticmethod
542
    def setup():
543
        "Ensure any dependencies are loaded; if not, try to load them."
544
        moduleDeps( subtract = OF_KMOD, add = OVS_KMOD )
545

    
546
    def start( self, controllers ):
547
        "Start up kernel datapath."
548
        ofplog = '/tmp/' + self.name + '-ofp.log'
549
        quietRun( 'ifconfig lo up' )
550
        # Delete local datapath if it exists;
551
        # then create a new one monitoring the given interfaces
552
        quietRun( 'ovs-dpctl del-dp dp%i' % self.dp )
553
        self.cmd( 'ovs-dpctl add-dp dp%i' % self.dp )
554
        if self.defaultMAC:
555
            intf = 'dp%i' % self.dp
556
            mac = self.defaultMAC
557
            self.cmd( [ 'ifconfig', intf, 'hw', 'ether', mac ] )
558

    
559
        if len( self.intfs ) != max( self.intfs ) + 1:
560
            raise Exception( 'only contiguous, zero-indexed port ranges'
561
                            'supported: %s' % self.intfs )
562
        intfs = [ self.intfs[ port ] for port in sorted( self.intfs.keys() ) ]
563
        self.cmd( 'ovs-dpctl add-if dp' + str( self.dp ) + ' ' +
564
                      ' '.join( intfs ) )
565
        # Run protocol daemon
566
        controller = controllers[ 0 ]
567
        self.cmd( 'ovs-openflowd dp' + str( self.dp ) + ' tcp:' +
568
                      controller.IP() + ':' +
569
                      ' --fail=closed 1> ' + ofplog + ' 2>' + ofplog + ' &' )
570
        self.execed = False
571

    
572
    def stop( self ):
573
        "Terminate kernel datapath."
574
        quietRun( 'ovs-dpctl del-dp dp%i' % self.dp )
575
        self.cmd( 'kill %ovs-openflowd' )
576
        self.deleteIntfs()
577

    
578
    # Since kernel threads cannot receive signals like user-space processes,
579
    # disabling the interfaces and ovs-openflowd is our workaround.
580

    
581
    def pause( self ):
582
        "Disable interfaces and pause ovs-openflowd."
583
        self.cmd( 'kill -STOP %ovs-openflowd' )
584
        self.modIntfs('down')
585

    
586
    def resume( self ):
587
        "Re-enable interfaces and resume ovs-openflowd."
588
        self.cmd( 'kill -CONT %ovs-openflowd' )
589
        self.modIntfs('up')
590

    
591

    
592
class Controller( Node ):
593
    """A Controller is a Node that is running (or has execed?) an
594
       OpenFlow controller."""
595

    
596
    def __init__( self, name, inNamespace=False, controller='controller',
597
                 cargs='-v ptcp:', cdir=None, defaultIP="127.0.0.1",
598
                 port=6633 ):
599
        self.controller = controller
600
        self.cargs = cargs
601
        self.cdir = cdir
602
        self.port = port
603
        Node.__init__( self, name, inNamespace=inNamespace,
604
            defaultIP=defaultIP )
605

    
606
    def start( self ):
607
        """Start <controller> <args> on controller.
608
           Log to /tmp/cN.log"""
609
        cout = '/tmp/' + self.name + '.log'
610
        if self.cdir is not None:
611
            self.cmd( 'cd ' + self.cdir )
612
        self.cmd( self.controller + ' ' + self.cargs +
613
            ' 1> ' + cout + ' 2> ' + cout + ' &' )
614
        self.execed = False
615

    
616
    def stop( self ):
617
        "Stop controller."
618
        self.cmd( 'kill %' + self.controller )
619
        self.terminate()
620

    
621
    def pause( self ):
622
        "Pause controller."
623
        self.cmd( 'kill -STOP %' + self.controller )
624

    
625
    def resume( self ):
626
        "Resume controller."
627
        self.cmd( 'kill -CONT %' + self.controller )
628

    
629
    def IP( self, intf=None ):
630
        "Return IP address of the Controller"
631
        ip = Node.IP( self, intf=intf )
632
        if ip is None:
633
            ip = self.defaultIP
634
        return ip
635

    
636

    
637
class ControllerParams( object ):
638
    "Container for controller IP parameters."
639

    
640
    def __init__( self, ip, prefixLen ):
641
        """Init.
642
           ip: string, controller IP address
643
           prefixLen: prefix length, e.g. 8 for /8, covering 16M"""
644
        self.ip = ip
645
        self.prefixLen = prefixLen
646

    
647

    
648
class NOX( Controller ):
649
    "Controller to run a NOX application."
650

    
651
    def __init__( self, name, inNamespace=False, noxArgs=None, **kwargs ):
652
        """Init.
653
           name: name to give controller
654
           noxArgs: list of args, or single arg, to pass to NOX"""
655
        if type( noxArgs ) != list:
656
            noxArgs = [ noxArgs ]
657
        if not noxArgs:
658
            noxArgs = [ 'packetdump' ]
659

    
660
        if 'NOX_CORE_DIR' not in os.environ:
661
            exit( 'exiting; please set missing NOX_CORE_DIR env var' )
662
        noxCoreDir = os.environ[ 'NOX_CORE_DIR' ]
663

    
664
        Controller.__init__( self, name,
665
            controller=noxCoreDir + '/nox_core',
666
            cargs='--libdir=/usr/local/lib -v -i ptcp: ' +
667
                    ' '.join( noxArgs ),
668
            cdir = noxCoreDir, **kwargs )
669

    
670

    
671
class RemoteController( Controller ):
672
    "Controller running outside of Mininet's control."
673

    
674
    def __init__( self, name, inNamespace=False, defaultIP='127.0.0.1',
675
                 port=6633 ):
676
        """Init.
677
           name: name to give controller
678
           defaultIP: the IP address where the remote controller is
679
           listening
680
           port: the port where the remote controller is listening"""
681
        Controller.__init__( self, name, defaultIP=defaultIP, port=port )
682

    
683
    def start( self ):
684
        "Overridden to do nothing."
685
        return
686

    
687
    def stop( self ):
688
        "Overridden to do nothing."
689
        return