Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ 9de7873b

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
SWITCH_PORT_BASE = 1  # For OF > 0.9, switch ports start at 1 rather than zero
56

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

    
61
    inToNode = {}  # mapping of input fds to nodes
62
    outToNode = {}  # mapping of output fds to nodes
63

    
64
    portBase = 0  # Nodes always start with eth0/port0, even in OF 1.0
65

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

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

    
114
    def cleanup( self ):
115
        "Help python collect its garbage."
116
        self.shell = None
117

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

    
134
    def readline( self ):
135
        """Buffered readline from node, non-blocking.
136
           returns: line (minus newline) or None"""
137
        self.readbuf += self.read( 1024 )
138
        if '\n' not in self.readbuf:
139
            return None
140
        pos = self.readbuf.find( '\n' )
141
        line = self.readbuf[ 0 : pos ]
142
        self.readbuf = self.readbuf[ pos + 1: ]
143
        return line
144

    
145
    def write( self, data ):
146
        """Write data to node.
147
           data: string"""
148
        os.write( self.stdin.fileno(), data )
149

    
150
    def terminate( self ):
151
        "Send kill signal to Node and clean up after it."
152
        os.kill( self.pid, signal.SIGKILL )
153
        self.cleanup()
154

    
155
    def stop( self ):
156
        "Stop node."
157
        self.terminate()
158

    
159
    def waitReadable( self, timeoutms=None ):
160
        """Wait until node's output is readable.
161
           timeoutms: timeout in ms or None to wait indefinitely."""
162
        if len( self.readbuf ) == 0:
163
            self.pollOut.poll( timeoutms )
164

    
165
    def sendCmd( self, *args, **kwargs ):
166
        """Send a command, followed by a command to echo a sentinel,
167
           and return without waiting for the command to complete.
168
           args: command and arguments, or string
169
           printPid: print command's PID?"""
170
        assert not self.waiting
171
        printPid = kwargs.get( 'printPid', True )
172
        if len( args ) > 0:
173
            cmd = args
174
        if not isinstance( cmd, str ):
175
            cmd = ' '.join( cmd )
176
        if not re.search( r'\w', cmd ):
177
            # Replace empty commands with something harmless
178
            cmd = 'echo -n'
179
        if len( cmd ) > 0 and cmd[ -1 ] == '&':
180
            separator = '&'
181
            cmd = cmd[ :-1 ]
182
        else:
183
            separator = ';'
184
            if printPid and not isShellBuiltin( cmd ):
185
                cmd = 'mnexec -p ' + cmd
186
        self.write( cmd + separator + ' printf "\\177" \n' )
187
        self.lastCmd = cmd
188
        self.lastPid = None
189
        self.waiting = True
190

    
191
    def sendInt( self, sig=signal.SIGINT ):
192
        "Interrupt running command."
193
        if self.lastPid:
194
            try:
195
                os.kill( self.lastPid, sig )
196
            except OSError:
197
                pass
198

    
199
    def monitor( self, timeoutms=None ):
200
        """Monitor and return the output of a command.
201
           Set self.waiting to False if command has completed.
202
           timeoutms: timeout in ms or None to wait indefinitely."""
203
        assert self.waiting
204
        self.waitReadable( timeoutms )
205
        data = self.read( 1024 )
206
        # Look for PID
207
        marker = chr( 1 ) + r'\d+\n'
208
        if chr( 1 ) in data:
209
            markers = re.findall( marker, data )
210
            if markers:
211
                self.lastPid = int( markers[ 0 ][ 1: ] )
212
                data = re.sub( marker, '', data )
213
        # Look for sentinel/EOF
214
        if len( data ) > 0 and data[ -1 ] == chr( 127 ):
215
            self.waiting = False
216
            data = data[ :-1 ]
217
        elif chr( 127 ) in data:
218
            self.waiting = False
219
            data = data.replace( chr( 127 ), '' )
220
        return data
221

    
222
    def waitOutput( self, verbose=False ):
223
        """Wait for a command to complete.
224
           Completion is signaled by a sentinel character, ASCII(127)
225
           appearing in the output stream.  Wait for the sentinel and return
226
           the output, including trailing newline.
227
           verbose: print output interactively"""
228
        log = info if verbose else debug
229
        output = ''
230
        while self.waiting:
231
            data = self.monitor()
232
            output += data
233
            log( data )
234
        return output
235

    
236
    def cmd( self, *args, **kwargs ):
237
        """Send a command, wait for output, and return it.
238
           cmd: string"""
239
        verbose = kwargs.get( 'verbose', False )
240
        log = info if verbose else debug
241
        log( '*** %s : %s\n' % ( self.name, args ) )
242
        self.sendCmd( *args, **kwargs )
243
        return self.waitOutput( verbose )
244

    
245
    def cmdPrint( self, *args):
246
        """Call cmd and printing its output
247
           cmd: string"""
248
        return self.cmd( *args, **{ 'verbose': True } )
249

    
250
    # Interface management, configuration, and routing
251

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

    
258
    def intfName( self, n ):
259
        "Construct a canonical interface name node-ethN for interface n."
260
        return self.name + '-eth' + repr( n )
261

    
262
    def newPort( self ):
263
        "Return the next port number to allocate."
264
        if len( self.ports ) > 0:
265
            return max( self.ports.values() ) + 1
266
        return self.portBase
267

    
268
    def addIntf( self, intf, port=None ):
269
        """Add an interface.
270
           intf: interface name (e.g. nodeN-ethM)
271
           port: port number (optional, typically OpenFlow port number)"""
272
        if port is None:
273
            port = self.newPort()
274
        self.intfs[ port ] = intf
275
        self.ports[ intf ] = port
276
        #info( '\n' )
277
        #info( 'added intf %s:%d to node %s\n' % ( intf,port, self.name ) )
278
        if self.inNamespace:
279
            #info( 'moving w/inNamespace set\n' )
280
            moveIntf( intf, self )
281

    
282
    def registerIntf( self, intf, dstNode, dstIntf ):
283
        "Register connection of intf to dstIntf on dstNode."
284
        self.connection[ intf ] = ( dstNode, dstIntf )
285

    
286
    def connectionsTo( self, node):
287
        "Return [(srcIntf, dstIntf)..] for connections to dstNode."
288
        # We could optimize this if it is important
289
        connections = []
290
        for intf in self.connection.keys():
291
            dstNode, dstIntf = self.connection[ intf ]
292
            if dstNode == node:
293
                connections.append( ( intf, dstIntf ) )
294
        return connections
295

    
296
    # This is a symmetric operation, but it makes sense to put
297
    # the code here since it is tightly coupled to routines in
298
    # this class. For a more symmetric API, you can use
299
    # mininet.util.createLink()
300

    
301
    def linkTo( self, node2, port1=None, port2=None ):
302
        """Create link to another node, making two new interfaces.
303
           node2: Node to link us to
304
           port1: our port number (optional)
305
           port2: node2 port number (optional)
306
           returns: intf1 name, intf2 name"""
307
        node1 = self
308
        if port1 is None:
309
            port1 = node1.newPort()
310
        if port2 is None:
311
            port2 = node2.newPort()
312
        intf1 = node1.intfName( port1 )
313
        intf2 = node2.intfName( port2 )
314
        makeIntfPair( intf1, intf2 )
315
        node1.addIntf( intf1, port1 )
316
        node2.addIntf( intf2, port2 )
317
        node1.registerIntf( intf1, node2, intf2 )
318
        node2.registerIntf( intf2, node1, intf1 )
319
        return intf1, intf2
320

    
321
    def deleteIntfs( self ):
322
        "Delete all of our interfaces."
323
        # In theory the interfaces should go away after we shut down.
324
        # However, this takes time, so we're better off removing them
325
        # explicitly so that we won't get errors if we run before they
326
        # have been removed by the kernel. Unfortunately this is very slow,
327
        # at least with Linux kernels before 2.6.33
328
        for intf in self.intfs.values():
329
            quietRun( 'ip link del ' + intf )
330
            info( '.' )
331
            # Does it help to sleep to let things run?
332
            sleep( 0.001 )
333

    
334
    def setMAC( self, intf, mac ):
335
        """Set the MAC address for an interface.
336
           mac: MAC address as string"""
337
        result = self.cmd( 'ifconfig', intf, 'down' )
338
        result += self.cmd( 'ifconfig', intf, 'hw', 'ether', mac )
339
        result += self.cmd( 'ifconfig', intf, 'up' )
340
        return result
341

    
342
    def setARP( self, ip, mac ):
343
        """Add an ARP entry.
344
           ip: IP address as string
345
           mac: MAC address as string"""
346
        result = self.cmd( 'arp', '-s', ip, mac )
347
        return result
348

    
349
    def setIP( self, intf, ip, prefixLen=8 ):
350
        """Set the IP address for an interface.
351
           intf: interface name
352
           ip: IP address as a string
353
           prefixLen: prefix length, e.g. 8 for /8 or 16M addrs"""
354
        ipSub = '%s/%d' % ( ip, prefixLen )
355
        result = self.cmd( 'ifconfig', intf, ipSub, 'up' )
356
        self.ips[ intf ] = ip
357
        return result
358

    
359
    def setHostRoute( self, ip, intf ):
360
        """Add route to host.
361
           ip: IP address as dotted decimal
362
           intf: string, interface name"""
363
        return self.cmd( 'route add -host ' + ip + ' dev ' + intf )
364

    
365
    def setDefaultRoute( self, intf ):
366
        """Set the default route to go through intf.
367
           intf: string, interface name"""
368
        self.cmd( 'ip route flush root 0/0' )
369
        return self.cmd( 'route add default ' + intf )
370

    
371
    def defaultIntf( self ):
372
        "Return interface for lowest port"
373
        ports = self.intfs.keys()
374
        if ports:
375
            return self.intfs[ min( ports ) ]
376

    
377
    _ipMatchRegex = re.compile( r'\d+\.\d+\.\d+\.\d+' )
378
    _macMatchRegex = re.compile( r'..:..:..:..:..:..' )
379

    
380
    def IP( self, intf=None ):
381
        "Return IP address of a node or specific interface."
382
        if intf is None:
383
            intf = self.defaultIntf()
384
        if intf and not self.waiting:
385
            self.updateIP( intf )
386
        return self.ips.get( intf, None )
387

    
388
    def MAC( self, intf=None ):
389
        "Return MAC address of a node or specific interface."
390
        if intf is None:
391
            intf = self.defaultIntf()
392
        if intf and not self.waiting:
393
            self.updateMAC( intf )
394
        return self.macs.get( intf, None )
395

    
396
    def updateIP( self, intf ):
397
        "Update IP address for an interface"
398
        assert not self.waiting
399
        ifconfig = self.cmd( 'ifconfig ' + intf )
400
        ips = self._ipMatchRegex.findall( ifconfig )
401
        if ips:
402
            self.ips[ intf ] = ips[ 0 ]
403
        else:
404
            self.ips[ intf ] = None
405

    
406
    def updateMAC( self, intf ):
407
        "Update MAC address for an interface"
408
        assert not self.waiting
409
        ifconfig = self.cmd( 'ifconfig ' + intf )
410
        macs = self._macMatchRegex.findall( ifconfig )
411
        if macs:
412
            self.macs[ intf ] = macs[ 0 ]
413
        else:
414
            self.macs[ intf ] = None
415

    
416
    def intfIsUp( self, intf ):
417
        "Check if an interface is up."
418
        return 'UP' in self.cmd( 'ifconfig ' + intf )
419

    
420
    # Other methods
421
    def __str__( self ):
422
        intfs = sorted( self.intfs.values() )
423
        return '%s: IP=%s intfs=%s pid=%s' % (
424
            self.name, self.IP(), ','.join( intfs ), self.pid )
425

    
426

    
427
class Host( Node ):
428
    "A host is simply a Node."
429

    
430

    
431
class Switch( Node ):
432
    """A Switch is a Node that is running (or has execed?)
433
       an OpenFlow switch."""
434

    
435
    portBase = SWITCH_PORT_BASE  # 0 for OF < 1.0, 1 for OF >= 1.0
436

    
437
    def __init__( self, name, opts='', **kwargs):
438
        Node.__init__( self, name, **kwargs )
439
        self.opts = opts
440

    
441
    def sendCmd( self, *cmd, **kwargs ):
442
        """Send command to Node.
443
           cmd: string"""
444
        kwargs.setdefault( 'printPid', False )
445
        if not self.execed:
446
            return Node.sendCmd( self, *cmd, **kwargs )
447
        else:
448
            error( '*** Error: %s has execed and cannot accept commands' %
449
                     self.name )
450

    
451
class UserSwitch( Switch ):
452
    "User-space switch."
453

    
454
    def __init__( self, name, **kwargs ):
455
        """Init.
456
           name: name for the switch"""
457
        Switch.__init__( self, name, **kwargs )
458

    
459
    @staticmethod
460
    def setup():
461
        "Ensure any dependencies are loaded; if not, try to load them."
462
        moduleDeps( add = TUN )
463

    
464
    def start( self, controllers ):
465
        """Start OpenFlow reference user datapath.
466
           Log to /tmp/sN-{ofd,ofp}.log.
467
           controllers: list of controller objects"""
468
        controller = controllers[ 0 ]
469
        ofdlog = '/tmp/' + self.name + '-ofd.log'
470
        ofplog = '/tmp/' + self.name + '-ofp.log'
471
        self.cmd( 'ifconfig lo up' )
472
        mac_str = ''
473
        if self.defaultMAC:
474
            # ofdatapath expects a string of hex digits with no colons.
475
            mac_str = ' -d ' + ''.join( self.defaultMAC.split( ':' ) )
476
        intfs = sorted( self.intfs.values() )
477
        if self.inNamespace:
478
            intfs = intfs[ :-1 ]
479
        self.cmd( 'ofdatapath -i ' + ','.join( intfs ) +
480
            ' punix:/tmp/' + self.name + mac_str +
481
            ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' )
482
        self.cmd( 'ofprotocol unix:/tmp/' + self.name +
483
            ' tcp:%s:%d' % ( controller.IP(), controller.port ) +
484
            ' --fail=closed ' + self.opts +
485
            ' 1> ' + ofplog + ' 2>' + ofplog + ' &' )
486

    
487
    def stop( self ):
488
        "Stop OpenFlow reference user datapath."
489
        self.cmd( 'kill %ofdatapath' )
490
        self.cmd( 'kill %ofprotocol' )
491
        self.deleteIntfs()
492

    
493
class KernelSwitch( Switch ):
494
    """Kernel-space switch.
495
       Currently only works in root namespace."""
496

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

    
510
    @staticmethod
511
    def setup():
512
        "Ensure any dependencies are loaded; if not, try to load them."
513
        moduleDeps( subtract = OVS_KMOD, add = OF_KMOD )
514

    
515
    def start( self, controllers ):
516
        "Start up reference kernel datapath."
517
        ofplog = '/tmp/' + self.name + '-ofp.log'
518
        quietRun( 'ifconfig lo up' )
519
        # Delete local datapath if it exists;
520
        # then create a new one monitoring the given interfaces
521
        quietRun( 'dpctl deldp ' + self.dp )
522
        self.cmd( 'dpctl adddp ' + self.dp )
523
        if self.defaultMAC:
524
            self.cmd( 'ifconfig', self.intf, 'hw', 'ether', self.defaultMAC )
525
        ports = sorted( self.ports.values() )
526
        if len( ports ) != ports[ -1 ] + 1:
527
            raise Exception( 'only contiguous, zero-indexed port ranges'
528
                            'supported: %s' % ports )
529
        intfs = [ self.intfs[ port ] for port in ports ]
530
        self.cmd( 'dpctl', 'addif', self.dp, ' '.join( intfs ) )
531
        # Run protocol daemon
532
        controller = controllers[ 0 ]
533
        self.cmd( 'ofprotocol ' + self.dp +
534
            ' tcp:%s:%d' % ( controller.IP(), controller.port ) +
535
            ' --fail=closed ' + self.opts +
536
            ' 1> ' + ofplog + ' 2>' + ofplog + ' &' )
537
        self.execed = False
538

    
539
    def stop( self ):
540
        "Terminate kernel datapath."
541
        quietRun( 'dpctl deldp ' + self.dp )
542
        self.cmd( 'kill %ofprotocol' )
543
        self.deleteIntfs()
544

    
545

    
546
class OVSKernelSwitch( Switch ):
547
    """Open VSwitch kernel-space switch.
548
       Currently only works in the root namespace."""
549

    
550
    def __init__( self, name, dp=None, **kwargs ):
551
        """Init.
552
           name: name for switch
553
           dp: netlink id (0, 1, 2, ...)
554
           defaultMAC: default MAC as unsigned int; random value if None"""
555
        Switch.__init__( self, name, **kwargs )
556
        self.dp = 'dp%i' % dp
557
        self.intf = self.dp
558
        if self.inNamespace:
559
            error( "OVSKernelSwitch currently only works"
560
                " in the root namespace." )
561
            exit( 1 )
562

    
563
    @staticmethod
564
    def setup():
565
        "Ensure any dependencies are loaded; if not, try to load them."
566
        moduleDeps( subtract = OF_KMOD, add = OVS_KMOD )
567

    
568
    def start( self, controllers ):
569
        "Start up kernel datapath."
570
        ofplog = '/tmp/' + self.name + '-ofp.log'
571
        quietRun( 'ifconfig lo up' )
572
        # Delete local datapath if it exists;
573
        # then create a new one monitoring the given interfaces
574
        quietRun( 'ovs-dpctl del-dp ' + self.dp )
575
        self.cmd( 'ovs-dpctl add-dp ' + self.dp )
576
        mac_str = ''
577
        if self.defaultMAC:
578
            # ovs-openflowd expects a string of exactly 16 hex digits with no
579
            # colons.
580
            mac_str = ' --datapath-id=0000' + \
581
                      ''.join( self.defaultMAC.split( ':' ) ) + ' '
582
        ports = sorted( self.ports.values() )
583
        if len( ports ) != ports[ -1 ]:
584
            raise Exception( 'only contiguous, one-indexed port ranges '
585
                            'supported: %s' % self.intfs )
586
        intfs = [ self.intfs[ port ] for port in ports ]
587
        self.cmd( 'ovs-dpctl', 'add-if', self.dp, ' '.join( intfs ) )
588
        # Run protocol daemon
589
        controller = controllers[ 0 ]
590
        self.cmd( 'ovs-openflowd ' + self.dp +
591
            ' tcp:%s:%d' % ( controller.IP(), controller.port ) +
592
            ' --fail=secure ' + self.opts + mac_str +
593
            ' 1>' + ofplog + ' 2>' + ofplog + '&' )
594
        self.execed = False
595

    
596
    def stop( self ):
597
        "Terminate kernel datapath."
598
        quietRun( 'ovs-dpctl del-dp ' + self.dp )
599
        self.cmd( 'kill %ovs-openflowd' )
600
        self.deleteIntfs()
601

    
602

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

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

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

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

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

    
639
class ControllerParams( object ):
640
    "Container for controller IP parameters."
641

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

    
649

    
650
class NOX( Controller ):
651
    "Controller to run a NOX application."
652

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

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

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

    
672

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

    
676
    def __init__( self, name, defaultIP='127.0.0.1',
677
                 port=6633, **kwargs):
678
        """Init.
679
           name: name to give controller
680
           defaultIP: the IP address where the remote controller is
681
           listening
682
           port: the port where the remote controller is listening"""
683
        Controller.__init__( self, name, defaultIP=defaultIP, port=port,
684
            **kwargs )
685

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

    
690
    def stop( self ):
691
        "Overridden to do nothing."
692
        return