Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ 28f46c8d

History | View | Annotate | Download (25.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, 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, maxbytes=1024 ):
120
        """Buffered read from node, non-blocking.
121
           maxbytes: maximum number of bytes to return"""
122
        count = len( self.readbuf )
123
        if count < maxbytes:
124
            data = os.read( self.stdout.fileno(), maxbytes - count )
125
            self.readbuf += data
126
        if maxbytes >= len( self.readbuf ):
127
            result = self.readbuf
128
            self.readbuf = ''
129
        else:
130
            result = self.readbuf[ :maxbytes ]
131
            self.readbuf = self.readbuf[ maxbytes: ]
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
        self.waitReadable( timeoutms )
204
        data = self.read( 1024 )
205
        # Look for PID
206
        marker = chr( 1 ) + r'\d+\n'
207
        if chr( 1 ) in data:
208
            markers = re.findall( marker, data )
209
            if markers:
210
                self.lastPid = int( markers[ 0 ][ 1: ] )
211
                data = re.sub( marker, '', data )
212
        # Look for sentinel/EOF
213
        if len( data ) > 0 and data[ -1 ] == chr( 127 ):
214
            self.waiting = False
215
            data = data[ :-1 ]
216
        elif chr( 127 ) in data:
217
            self.waiting = False
218
            data = data.replace( chr( 127 ), '' )
219
        return data
220

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

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

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

    
249
    # Interface management, configuration, and routing
250

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
425

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

    
429

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

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

    
436
    def __init__( self, name, opts='', listenPort=None, **kwargs):
437
        Node.__init__( self, name, **kwargs )
438
        self.opts = opts
439
        self.listenPort = listenPort
440
        if self.listenPort:
441
            self.opts += ' --listen=ptcp:%i ' % self.listenPort
442

    
443
    def defaultIntf( self ):
444
        "Return interface for HIGHEST port"
445
        ports = self.intfs.keys()
446
        if ports:
447
            intf = self.intfs[ max( ports ) ]
448
        return intf
449

    
450
    def sendCmd( self, *cmd, **kwargs ):
451
        """Send command to Node.
452
           cmd: string"""
453
        kwargs.setdefault( 'printPid', False )
454
        if not self.execed:
455
            return Node.sendCmd( self, *cmd, **kwargs )
456
        else:
457
            error( '*** Error: %s has execed and cannot accept commands' %
458
                     self.name )
459

    
460
class UserSwitch( Switch ):
461
    "User-space switch."
462

    
463
    def __init__( self, name, **kwargs ):
464
        """Init.
465
           name: name for the switch"""
466
        Switch.__init__( self, name, **kwargs )
467
        pathCheck( 'ofdatapath', 'ofprotocol',
468
            moduleName='the OpenFlow reference user switch (openflow.org)' )
469

    
470
    @staticmethod
471
    def setup():
472
        "Ensure any dependencies are loaded; if not, try to load them."
473
        if not os.path.exists( '/dev/net/tun' ):
474
            moduleDeps( add=TUN )
475

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

    
499
    def stop( self ):
500
        "Stop OpenFlow reference user datapath."
501
        self.cmd( 'kill %ofdatapath' )
502
        self.cmd( 'kill %ofprotocol' )
503
        self.deleteIntfs()
504

    
505
class KernelSwitch( Switch ):
506
    """Kernel-space switch.
507
       Currently only works in root namespace."""
508

    
509
    def __init__( self, name, dp=None, **kwargs ):
510
        """Init.
511
           name: name for switch
512
           dp: netlink id (0, 1, 2, ...)
513
           defaultMAC: default MAC as string; random value if None"""
514
        Switch.__init__( self, name, **kwargs )
515
        self.dp = 'nl:%i' % dp
516
        self.intf = 'of%i' % dp
517
        if self.inNamespace:
518
            error( "KernelSwitch currently only works"
519
                " in the root namespace." )
520
            exit( 1 )
521

    
522
    @staticmethod
523
    def setup():
524
        "Ensure any dependencies are loaded; if not, try to load them."
525
        pathCheck( 'ofprotocol',
526
            moduleName='the OpenFlow reference kernel switch'
527
            ' (openflow.org) (NOTE: not available in OpenFlow 1.0!)' )
528
        moduleDeps( subtract=OVS_KMOD, add=OF_KMOD )
529

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

    
554
    def stop( self ):
555
        "Terminate kernel datapath."
556
        quietRun( 'dpctl deldp ' + self.dp )
557
        self.cmd( 'kill %ofprotocol' )
558
        self.deleteIntfs()
559

    
560

    
561
class OVSKernelSwitch( Switch ):
562
    """Open VSwitch kernel-space switch.
563
       Currently only works in the root namespace."""
564

    
565
    def __init__( self, name, dp=None, **kwargs ):
566
        """Init.
567
           name: name for switch
568
           dp: netlink id (0, 1, 2, ...)
569
           defaultMAC: default MAC as unsigned int; random value if None"""
570
        Switch.__init__( self, name, **kwargs )
571
        self.dp = 'dp%i' % dp
572
        self.intf = self.dp
573
        if self.inNamespace:
574
            error( "OVSKernelSwitch currently only works"
575
                " in the root namespace.\n" )
576
            exit( 1 )
577

    
578
    @staticmethod
579
    def setup():
580
        "Ensure any dependencies are loaded; if not, try to load them."
581
        pathCheck( 'ovs-dpctl', 'ovs-openflowd',
582
            moduleName='Open vSwitch (openvswitch.org)')
583
        moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
584

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

    
613
    def stop( self ):
614
        "Terminate kernel datapath."
615
        quietRun( 'ovs-dpctl del-dp ' + self.dp )
616
        self.cmd( 'kill %ovs-openflowd' )
617
        self.deleteIntfs()
618

    
619

    
620
class Controller( Node ):
621
    """A Controller is a Node that is running (or has execed?) an
622
       OpenFlow controller."""
623

    
624
    def __init__( self, name, inNamespace=False, command='controller',
625
                 cargs='-v ptcp:%d', cdir=None, defaultIP="127.0.0.1",
626
                 port=6633 ):
627
        self.command = command
628
        self.cargs = cargs
629
        self.cdir = cdir
630
        self.port = port
631
        Node.__init__( self, name, inNamespace=inNamespace,
632
            defaultIP=defaultIP )
633

    
634
    def start( self ):
635
        """Start <controller> <args> on controller.
636
           Log to /tmp/cN.log"""
637
        pathCheck( self.command )
638
        cout = '/tmp/' + self.name + '.log'
639
        if self.cdir is not None:
640
            self.cmd( 'cd ' + self.cdir )
641
        self.cmd( self.command + ' ' + self.cargs % self.port +
642
            ' 1>' + cout + ' 2>' + cout + '&' )
643
        self.execed = False
644

    
645
    def stop( self ):
646
        "Stop controller."
647
        self.cmd( 'kill %' + self.command )
648
        self.terminate()
649

    
650
    def IP( self, intf=None ):
651
        "Return IP address of the Controller"
652
        ip = Node.IP( self, intf=intf )
653
        if ip is None:
654
            ip = self.defaultIP
655
        return ip
656

    
657
class ControllerParams( object ):
658
    "Container for controller IP parameters."
659

    
660
    def __init__( self, ip, prefixLen ):
661
        """Init.
662
           ip: string, controller IP address
663
           prefixLen: prefix length, e.g. 8 for /8, covering 16M"""
664
        self.ip = ip
665
        self.prefixLen = prefixLen
666

    
667

    
668
class NOX( Controller ):
669
    "Controller to run a NOX application."
670

    
671
    def __init__( self, name, noxArgs=None, **kwargs ):
672
        """Init.
673
           name: name to give controller
674
           noxArgs: list of args, or single arg, to pass to NOX"""
675
        if not noxArgs:
676
            noxArgs = [ 'packetdump' ]
677
        elif type( noxArgs ) != list:
678
            noxArgs = [ noxArgs ]
679

    
680
        if 'NOX_CORE_DIR' not in os.environ:
681
            exit( 'exiting; please set missing NOX_CORE_DIR env var' )
682
        noxCoreDir = os.environ[ 'NOX_CORE_DIR' ]
683

    
684
        Controller.__init__( self, name,
685
            command=noxCoreDir + '/nox_core',
686
            cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' +
687
                    ' '.join( noxArgs ),
688
            cdir=noxCoreDir, **kwargs )
689

    
690

    
691
class RemoteController( Controller ):
692
    "Controller running outside of Mininet's control."
693

    
694
    def __init__( self, name, defaultIP='127.0.0.1',
695
                 port=6633, **kwargs):
696
        """Init.
697
           name: name to give controller
698
           defaultIP: the IP address where the remote controller is
699
           listening
700
           port: the port where the remote controller is listening"""
701
        Controller.__init__( self, name, defaultIP=defaultIP, port=port,
702
            **kwargs )
703

    
704
    def start( self ):
705
        "Overridden to do nothing."
706
        return
707

    
708
    def stop( self ):
709
        "Overridden to do nothing."
710
        return