Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ ccca871a

History | View | Annotate | Download (24.8 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, pathCheck, 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='', listenPort=None, **kwargs):
438
        Node.__init__( self, name, **kwargs )
439
        self.opts = opts
440
        self.listenPort = listenPort
441
        if self.listenPort:
442
            self.opts += ' --listen=ptcp:%i ' % self.listenPort
443

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

    
454
class UserSwitch( Switch ):
455
    "User-space switch."
456

    
457
    def __init__( self, name, **kwargs ):
458
        """Init.
459
           name: name for the switch"""
460
        Switch.__init__( self, name, **kwargs )
461
        pathCheck( 'ofdatapath', 'ofprotocol' )
462

    
463
    @staticmethod
464
    def setup():
465
        "Ensure any dependencies are loaded; if not, try to load them."
466
        moduleDeps( add=TUN )
467

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

    
491
    def stop( self ):
492
        "Stop OpenFlow reference user datapath."
493
        self.cmd( 'kill %ofdatapath' )
494
        self.cmd( 'kill %ofprotocol' )
495
        self.deleteIntfs()
496

    
497
class KernelSwitch( Switch ):
498
    """Kernel-space switch.
499
       Currently only works in root namespace."""
500

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

    
514
    @staticmethod
515
    def setup():
516
        "Ensure any dependencies are loaded; if not, try to load them."
517
        moduleDeps( subtract=OVS_KMOD, add=OF_KMOD )
518
        pathCheck( 'ofprotocol' )
519

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

    
544
    def stop( self ):
545
        "Terminate kernel datapath."
546
        quietRun( 'dpctl deldp ' + self.dp )
547
        self.cmd( 'kill %ofprotocol' )
548
        self.deleteIntfs()
549

    
550

    
551
class OVSKernelSwitch( Switch ):
552
    """Open VSwitch kernel-space switch.
553
       Currently only works in the root namespace."""
554

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

    
568
    @staticmethod
569
    def setup():
570
        "Ensure any dependencies are loaded; if not, try to load them."
571
        moduleDeps( subtract = OF_KMOD, add = OVS_KMOD )
572
        pathCheck( 'ovs-dpctl', 'ovs-openflowd' )
573

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

    
602
    def stop( self ):
603
        "Terminate kernel datapath."
604
        quietRun( 'ovs-dpctl del-dp ' + self.dp )
605
        self.cmd( 'kill %ovs-openflowd' )
606
        self.deleteIntfs()
607

    
608

    
609
class Controller( Node ):
610
    """A Controller is a Node that is running (or has execed?) an
611
       OpenFlow controller."""
612

    
613
    def __init__( self, name, inNamespace=False, controller='controller',
614
                 cargs='-v ptcp:', cdir=None, defaultIP="127.0.0.1",
615
                 port=6633 ):
616
        self.controller = controller
617
        self.cargs = cargs
618
        self.cdir = cdir
619
        self.port = port
620
        Node.__init__( self, name, inNamespace=inNamespace,
621
            defaultIP=defaultIP )
622

    
623
    def start( self ):
624
        """Start <controller> <args> on controller.
625
           Log to /tmp/cN.log"""
626
        pathCheck( self.controller )
627
        cout = '/tmp/' + self.name + '.log'
628
        if self.cdir is not None:
629
            self.cmd( 'cd ' + self.cdir )
630
        self.cmd( self.controller + ' ' + self.cargs +
631
            ' 1>' + cout + ' 2>' + cout + '&' )
632
        self.execed = False
633

    
634
    def stop( self ):
635
        "Stop controller."
636
        self.cmd( 'kill %' + self.controller )
637
        self.terminate()
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
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 not noxArgs:
665
            noxArgs = [ 'packetdump' ]
666
        elif type( noxArgs ) != list:
667
            noxArgs = [ noxArgs ]
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