Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ 3f2355a3

History | View | Annotate | Download (48.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
CPULimitedHost: a virtual host whose CPU bandwidth is limited by
17
    RT or CFS bandwidth limiting.
18

19
Switch: superclass for switch nodes.
20

21
UserSwitch: a switch using the user-space switch from the OpenFlow
22
    reference implementation.
23

24
KernelSwitch: a switch using the kernel switch from the OpenFlow reference
25
    implementation.
26

27
OVSSwitch: a switch using the OpenVSwitch OpenFlow-compatible switch
28
    implementation (openvswitch.org).
29

30
Controller: superclass for OpenFlow controllers. The default controller
31
    is controller(8) from the reference implementation.
32

33
NOXController: a controller node using NOX (noxrepo.org).
34

35
RemoteController: a remote controller node, which may use any
36
    arbitrary OpenFlow-compatible controller, and which is not
37
    created or managed by mininet.
38

39
Future enhancements:
40

41
- Possibly make Node, Switch and Controller more abstract so that
42
  they can be used for both local and remote nodes
43

44
- Create proxy objects for remote nodes (Mininet: Cluster Edition)
45
"""
46

    
47
import os
48
import re
49
import signal
50
import select
51
from subprocess import Popen, PIPE, STDOUT
52
from operator import or_
53
from time import sleep
54

    
55
from mininet.log import info, error, warn, debug
56
from mininet.util import ( quietRun, errRun, errFail, moveIntf, isShellBuiltin,
57
                           numCores, retry, mountCgroups )
58
from mininet.moduledeps import moduleDeps, pathCheck, OVS_KMOD, OF_KMOD, TUN
59
from mininet.link import Link, Intf, TCIntf
60

    
61
class Node( object ):
62
    """A virtual network node is simply a shell in a network namespace.
63
       We communicate with it using pipes."""
64

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

    
67
    def __init__( self, name, inNamespace=True, **params ):
68
        """name: name of node
69
           inNamespace: in network namespace?
70
           params: Node parameters (see config() for details)"""
71

    
72
        # Make sure class actually works
73
        self.checkSetup()
74

    
75
        self.name = name
76
        self.inNamespace = inNamespace
77

    
78
        # Stash configuration parameters for future reference
79
        self.params = params
80

    
81
        self.intfs = {}  # dict of port numbers to interfaces
82
        self.ports = {}  # dict of interfaces to port numbers
83
                         # replace with Port objects, eventually ?
84
        self.nameToIntf = {}  # dict of interface names to Intfs
85

    
86
        # Make pylint happy
87
        ( self.shell, self.execed, self.pid, self.stdin, self.stdout,
88
            self.lastPid, self.lastCmd, self.pollOut ) = (
89
                None, None, None, None, None, None, None, None )
90
        self.waiting = False
91
        self.readbuf = ''
92

    
93
        # Start command interpreter shell
94
        self.startShell()
95

    
96
    # File descriptor to node mapping support
97
    # Class variables and methods
98

    
99
    inToNode = {}  # mapping of input fds to nodes
100
    outToNode = {}  # mapping of output fds to nodes
101

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

    
110
    # Command support via shell process in namespace
111

    
112
    def startShell( self ):
113
        "Start a shell process for running commands"
114
        if self.shell:
115
            error( "%s: shell is already running" )
116
            return
117
        # mnexec: (c)lose descriptors, (d)etach from tty,
118
        # (p)rint pid, and run in (n)amespace
119
        opts = '-cdp'
120
        if self.inNamespace:
121
            opts += 'n'
122
        # bash -m: enable job control
123
        # -s: pass $* to shell, and make process easy to find in ps
124
        cmd = [ 'mnexec', opts, 'bash', '-ms', 'mininet:' + self.name ]
125
        self.shell = Popen( cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT,
126
                            close_fds=True )
127
        self.stdin = self.shell.stdin
128
        self.stdout = self.shell.stdout
129
        self.pid = self.shell.pid
130
        self.pollOut = select.poll()
131
        self.pollOut.register( self.stdout )
132
        # Maintain mapping between file descriptors and nodes
133
        # This is useful for monitoring multiple nodes
134
        # using select.poll()
135
        self.outToNode[ self.stdout.fileno() ] = self
136
        self.inToNode[ self.stdin.fileno() ] = self
137
        self.execed = False
138
        self.lastCmd = None
139
        self.lastPid = None
140
        self.readbuf = ''
141
        self.waiting = False
142

    
143
    def cleanup( self ):
144
        "Help python collect its garbage."
145
        # Intfs may end up in root NS
146
        for intfName in self.intfNames():
147
            if self.name in intfName:
148
                quietRun( 'ip link del ' + intfName )
149
        self.shell = None
150

    
151
    # Subshell I/O, commands and control
152

    
153
    def read( self, maxbytes=1024 ):
154
        """Buffered read from node, non-blocking.
155
           maxbytes: maximum number of bytes to return"""
156
        count = len( self.readbuf )
157
        if count < maxbytes:
158
            data = os.read( self.stdout.fileno(), maxbytes - count )
159
            self.readbuf += data
160
        if maxbytes >= len( self.readbuf ):
161
            result = self.readbuf
162
            self.readbuf = ''
163
        else:
164
            result = self.readbuf[ :maxbytes ]
165
            self.readbuf = self.readbuf[ maxbytes: ]
166
        return result
167

    
168
    def readline( self ):
169
        """Buffered readline from node, non-blocking.
170
           returns: line (minus newline) or None"""
171
        self.readbuf += self.read( 1024 )
172
        if '\n' not in self.readbuf:
173
            return None
174
        pos = self.readbuf.find( '\n' )
175
        line = self.readbuf[ 0: pos ]
176
        self.readbuf = self.readbuf[ pos + 1: ]
177
        return line
178

    
179
    def write( self, data ):
180
        """Write data to node.
181
           data: string"""
182
        os.write( self.stdin.fileno(), data )
183

    
184
    def terminate( self ):
185
        "Send kill signal to Node and clean up after it."
186
        if self.shell:
187
            os.kill( self.pid, signal.SIGKILL )
188
        self.cleanup()
189

    
190
    def stop( self ):
191
        "Stop node."
192
        self.terminate()
193

    
194
    def waitReadable( self, timeoutms=None ):
195
        """Wait until node's output is readable.
196
           timeoutms: timeout in ms or None to wait indefinitely."""
197
        if len( self.readbuf ) == 0:
198
            self.pollOut.poll( timeoutms )
199

    
200
    def sendCmd( self, *args, **kwargs ):
201
        """Send a command, followed by a command to echo a sentinel,
202
           and return without waiting for the command to complete.
203
           args: command and arguments, or string
204
           printPid: print command's PID?"""
205
        assert not self.waiting
206
        printPid = kwargs.get( 'printPid', True )
207
        # Allow sendCmd( [ list ] )
208
        if len( args ) == 1 and type( args[ 0 ] ) is list:
209
            cmd = args[ 0 ]
210
        # Allow sendCmd( cmd, arg1, arg2... )
211
        elif len( args ) > 0:
212
            cmd = args
213
        # Convert to string
214
        if not isinstance( cmd, str ):
215
            cmd = ' '.join( [ str( c ) for c in cmd ] )
216
        if not re.search( r'\w', cmd ):
217
            # Replace empty commands with something harmless
218
            cmd = 'echo -n'
219
        self.lastCmd = cmd
220
        printPid = printPid and not isShellBuiltin( cmd )
221
        if len( cmd ) > 0 and cmd[ -1 ] == '&':
222
            # print ^A{pid}\n{sentinel}
223
            cmd += ' printf "\\001%d\n\\177" $! \n'
224
        else:
225
            # print sentinel
226
            cmd += '; printf "\\177"'
227
            if printPid and not isShellBuiltin( cmd ):
228
                cmd = 'mnexec -p ' + cmd
229
        self.write( cmd + '\n' )
230
        self.lastPid = None
231
        self.waiting = True
232

    
233
    def sendInt( self, sig=signal.SIGINT ):
234
        "Interrupt running command."
235
        if self.lastPid:
236
            try:
237
                os.kill( self.lastPid, sig )
238
            except OSError:
239
                pass
240

    
241
    def monitor( self, timeoutms=None ):
242
        """Monitor and return the output of a command.
243
           Set self.waiting to False if command has completed.
244
           timeoutms: timeout in ms or None to wait indefinitely."""
245
        self.waitReadable( timeoutms )
246
        data = self.read( 1024 )
247
        # Look for PID
248
        marker = chr( 1 ) + r'\d+\n'
249
        if chr( 1 ) in data:
250
            markers = re.findall( marker, data )
251
            if markers:
252
                self.lastPid = int( markers[ 0 ][ 1: ] )
253
                data = re.sub( marker, '', data )
254
        # Look for sentinel/EOF
255
        if len( data ) > 0 and data[ -1 ] == chr( 127 ):
256
            self.waiting = False
257
            data = data[ :-1 ]
258
        elif chr( 127 ) in data:
259
            self.waiting = False
260
            data = data.replace( chr( 127 ), '' )
261
        return data
262

    
263
    def waitOutput( self, verbose=False ):
264
        """Wait for a command to complete.
265
           Completion is signaled by a sentinel character, ASCII(127)
266
           appearing in the output stream.  Wait for the sentinel and return
267
           the output, including trailing newline.
268
           verbose: print output interactively"""
269
        log = info if verbose else debug
270
        output = ''
271
        while self.waiting:
272
            data = self.monitor()
273
            output += data
274
            log( data )
275
        return output
276

    
277
    def cmd( self, *args, **kwargs ):
278
        """Send a command, wait for output, and return it.
279
           cmd: string"""
280
        verbose = kwargs.get( 'verbose', False )
281
        log = info if verbose else debug
282
        log( '*** %s : %s\n' % ( self.name, args ) )
283
        self.sendCmd( *args, **kwargs )
284
        return self.waitOutput( verbose )
285

    
286
    def cmdPrint( self, *args):
287
        """Call cmd and printing its output
288
           cmd: string"""
289
        return self.cmd( *args, **{ 'verbose': True } )
290

    
291
    def popen( self, *args, **kwargs ):
292
        """Return a Popen() object in our namespace
293
           args: Popen() args, single list, or string
294
           kwargs: Popen() keyword args"""
295
        defaults = { 'stdout': PIPE, 'stderr': PIPE,
296
                     'mncmd':
297
                     [ 'mnexec', '-da', str( self.pid ) ] }
298
        defaults.update( kwargs )
299
        if len( args ) == 1:
300
            if type( args[ 0 ] ) is list:
301
                # popen([cmd, arg1, arg2...])
302
                cmd = args[ 0 ]
303
            elif type( args[ 0 ] ) is str:
304
                # popen("cmd arg1 arg2...")
305
                cmd = args[ 0 ].split()
306
            else:
307
                raise Exception( 'popen() requires a string or list' )
308
        elif len( args ) > 0:
309
            # popen( cmd, arg1, arg2... )
310
            cmd = list( args )
311
        # Attach to our namespace  using mnexec -a
312
        mncmd = defaults[ 'mncmd' ]
313
        del defaults[ 'mncmd' ]
314
        cmd = mncmd + cmd
315
        # Shell requires a string, not a list!
316
        if defaults.get( 'shell', False ):
317
            cmd = ' '.join( cmd )
318
        return Popen( cmd, **defaults )
319

    
320
    def pexec( self, *args, **kwargs ):
321
        """Execute a command using popen
322
           returns: out, err, exitcode"""
323
        popen = self.popen( *args, **kwargs)
324
        out, err = popen.communicate()
325
        exitcode = popen.wait()
326
        return out, err, exitcode
327

    
328
    # Interface management, configuration, and routing
329

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

    
336
    def newPort( self ):
337
        "Return the next port number to allocate."
338
        if len( self.ports ) > 0:
339
            return max( self.ports.values() ) + 1
340
        return self.portBase
341

    
342
    def addIntf( self, intf, port=None ):
343
        """Add an interface.
344
           intf: interface
345
           port: port number (optional, typically OpenFlow port number)"""
346
        if port is None:
347
            port = self.newPort()
348
        self.intfs[ port ] = intf
349
        self.ports[ intf ] = port
350
        self.nameToIntf[ intf.name ] = intf
351
        debug( '\n' )
352
        debug( 'added intf %s:%d to node %s\n' % ( intf, port, self.name ) )
353
        if self.inNamespace:
354
            debug( 'moving', intf, 'into namespace for', self.name, '\n' )
355
            moveIntf( intf.name, self )
356

    
357
    def defaultIntf( self ):
358
        "Return interface for lowest port"
359
        ports = self.intfs.keys()
360
        if ports:
361
            return self.intfs[ min( ports ) ]
362
        else:
363
            warn( '*** defaultIntf: warning:', self.name,
364
                  'has no interfaces\n' )
365

    
366
    def intf( self, intf='' ):
367
        """Return our interface object with given string name,
368
           default intf if name is falsy (None, empty string, etc).
369
           or the input intf arg.
370

371
        Having this fcn return its arg for Intf objects makes it
372
        easier to construct functions with flexible input args for
373
        interfaces (those that accept both string names and Intf objects).
374
        """
375
        if not intf:
376
            return self.defaultIntf()
377
        elif type( intf ) is str:
378
            return self.nameToIntf[ intf ]
379
        else:
380
            return intf
381

    
382
    def connectionsTo( self, node):
383
        "Return [ intf1, intf2... ] for all intfs that connect self to node."
384
        # We could optimize this if it is important
385
        connections = []
386
        for intf in self.intfList():
387
            link = intf.link
388
            if link:
389
                node1, node2 = link.intf1.node, link.intf2.node
390
                if node1 == self and node2 == node:
391
                    connections += [ ( intf, link.intf2 ) ]
392
                elif node1 == node and node2 == self:
393
                    connections += [ ( intf, link.intf1 ) ]
394
        return connections
395

    
396
    def deleteIntfs( self, checkName=True ):
397
        """Delete all of our interfaces.
398
           checkName: only delete interfaces that contain our name"""
399
        # In theory the interfaces should go away after we shut down.
400
        # However, this takes time, so we're better off removing them
401
        # explicitly so that we won't get errors if we run before they
402
        # have been removed by the kernel. Unfortunately this is very slow,
403
        # at least with Linux kernels before 2.6.33
404
        for intf in self.intfs.values():
405
            # Protect against deleting hardware interfaces
406
            if ( self.name in intf.name ) or ( not checkName ):
407
                intf.delete()
408
                info( '.' )
409

    
410
    # Routing support
411

    
412
    def setARP( self, ip, mac ):
413
        """Add an ARP entry.
414
           ip: IP address as string
415
           mac: MAC address as string"""
416
        result = self.cmd( 'arp', '-s', ip, mac )
417
        return result
418

    
419
    def setHostRoute( self, ip, intf ):
420
        """Add route to host.
421
           ip: IP address as dotted decimal
422
           intf: string, interface name"""
423
        return self.cmd( 'route add -host', ip, 'dev', intf )
424

    
425
    def setDefaultRoute( self, intf=None ):
426
        """Set the default route to go through intf.
427
           intf: Intf or {dev <intfname> via <gw-ip> ...}"""
428
        # Note setParam won't call us if intf is none
429
        if type( intf ) is str and ' ' in intf:
430
            params = intf
431
        else:
432
            params = 'dev %s' % intf
433
        self.cmd( 'ip route del default' )
434
        return self.cmd( 'ip route add default', params )
435

    
436
    # Convenience and configuration methods
437

    
438
    def setMAC( self, mac, intf=None ):
439
        """Set the MAC address for an interface.
440
           intf: intf or intf name
441
           mac: MAC address as string"""
442
        return self.intf( intf ).setMAC( mac )
443

    
444
    def setIP( self, ip, prefixLen=8, intf=None ):
445
        """Set the IP address for an interface.
446
           intf: intf or intf name
447
           ip: IP address as a string
448
           prefixLen: prefix length, e.g. 8 for /8 or 16M addrs"""
449
        # This should probably be rethought
450
        if '/' not in ip:
451
            ip = '%s/%s' % ( ip, prefixLen )
452
        return self.intf( intf ).setIP( ip )
453

    
454
    def IP( self, intf=None ):
455
        "Return IP address of a node or specific interface."
456
        return self.intf( intf ).IP()
457

    
458
    def MAC( self, intf=None ):
459
        "Return MAC address of a node or specific interface."
460
        return self.intf( intf ).MAC()
461

    
462
    def intfIsUp( self, intf=None ):
463
        "Check if an interface is up."
464
        return self.intf( intf ).isUp()
465

    
466
    # The reason why we configure things in this way is so
467
    # That the parameters can be listed and documented in
468
    # the config method.
469
    # Dealing with subclasses and superclasses is slightly
470
    # annoying, but at least the information is there!
471

    
472
    def setParam( self, results, method, **param ):
473
        """Internal method: configure a *single* parameter
474
           results: dict of results to update
475
           method: config method name
476
           param: arg=value (ignore if value=None)
477
           value may also be list or dict"""
478
        name, value = param.items()[ 0 ]
479
        f = getattr( self, method, None )
480
        if not f or value is None:
481
            return
482
        if type( value ) is list:
483
            result = f( *value )
484
        elif type( value ) is dict:
485
            result = f( **value )
486
        else:
487
            result = f( value )
488
        results[ name ] = result
489
        return result
490

    
491
    def config( self, mac=None, ip=None,
492
                defaultRoute=None, lo='up', **_params ):
493
        """Configure Node according to (optional) parameters:
494
           mac: MAC address for default interface
495
           ip: IP address for default interface
496
           ifconfig: arbitrary interface configuration
497
           Subclasses should override this method and call
498
           the parent class's config(**params)"""
499
        # If we were overriding this method, we would call
500
        # the superclass config method here as follows:
501
        # r = Parent.config( **_params )
502
        r = {}
503
        self.setParam( r, 'setMAC', mac=mac )
504
        self.setParam( r, 'setIP', ip=ip )
505
        self.setParam( r, 'setDefaultRoute', defaultRoute=defaultRoute )
506
        # This should be examined
507
        self.cmd( 'ifconfig lo ' + lo )
508
        return r
509

    
510
    def configDefault( self, **moreParams ):
511
        "Configure with default parameters"
512
        self.params.update( moreParams )
513
        self.config( **self.params )
514

    
515
    # This is here for backward compatibility
516
    def linkTo( self, node, link=Link ):
517
        """(Deprecated) Link to another node
518
           replace with Link( node1, node2)"""
519
        return link( self, node )
520

    
521
    # Other methods
522

    
523
    def intfList( self ):
524
        "List of our interfaces sorted by port number"
525
        return [ self.intfs[ p ] for p in sorted( self.intfs.iterkeys() ) ]
526

    
527
    def intfNames( self ):
528
        "The names of our interfaces sorted by port number"
529
        return [ str( i ) for i in self.intfList() ]
530

    
531
    def __repr__( self ):
532
        "More informative string representation"
533
        intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
534
                              for i in self.intfList() ] ) )
535
        return '<%s %s: %s pid=%s> ' % (
536
            self.__class__.__name__, self.name, intfs, self.pid )
537

    
538
    def __str__( self ):
539
        "Abbreviated string representation"
540
        return self.name
541

    
542
    # Automatic class setup support
543

    
544
    isSetup = False
545

    
546
    @classmethod
547
    def checkSetup( cls ):
548
        "Make sure our class and superclasses are set up"
549
        while cls and not getattr( cls, 'isSetup', True ):
550
            cls.setup()
551
            cls.isSetup = True
552
            # Make pylint happy
553
            cls = getattr( type( cls ), '__base__', None )
554

    
555
    @classmethod
556
    def setup( cls ):
557
        "Make sure our class dependencies are available"
558
        pathCheck( 'mnexec', 'ifconfig', moduleName='Mininet')
559

    
560

    
561
class Host( Node ):
562
    "A host is simply a Node"
563
    pass
564

    
565

    
566
class CPULimitedHost( Host ):
567

    
568
    "CPU limited host"
569

    
570
    def __init__( self, name, sched='cfs', **kwargs ):
571
        Host.__init__( self, name, **kwargs )
572
        # Initialize class if necessary
573
        if not CPULimitedHost.inited:
574
            CPULimitedHost.init()
575
        # Create a cgroup and move shell into it
576
        self.cgroup = 'cpu,cpuacct,cpuset:/' + self.name
577
        errFail( 'cgcreate -g ' + self.cgroup )
578
        # We don't add ourselves to a cpuset because you must
579
        # specify the cpu and memory placement first
580
        errFail( 'cgclassify -g cpu,cpuacct:/%s %s' % ( self.name, self.pid ) )
581
        # BL: Setting the correct period/quota is tricky, particularly
582
        # for RT. RT allows very small quotas, but the overhead
583
        # seems to be high. CFS has a mininimum quota of 1 ms, but
584
        # still does better with larger period values.
585
        self.period_us = kwargs.get( 'period_us', 100000 )
586
        self.sched = sched
587
        self.rtprio = 20
588

    
589
    def cgroupSet( self, param, value, resource='cpu' ):
590
        "Set a cgroup parameter and return its value"
591
        cmd = 'cgset -r %s.%s=%s /%s' % (
592
            resource, param, value, self.name )
593
        quietRun( cmd )
594
        nvalue = int( self.cgroupGet( param, resource ) )
595
        if nvalue != value:
596
            error( '*** error: cgroupSet: %s set to %s instead of %s\n'
597
                   % ( param, nvalue, value ) )
598
        return nvalue
599

    
600
    def cgroupGet( self, param, resource='cpu' ):
601
        "Return value of cgroup parameter"
602
        cmd = 'cgget -r %s.%s /%s' % (
603
            resource, param, self.name )
604
        return int( quietRun( cmd ).split()[ -1 ] )
605

    
606
    def cgroupDel( self ):
607
        "Clean up our cgroup"
608
        # info( '*** deleting cgroup', self.cgroup, '\n' )
609
        _out, _err, exitcode = errRun( 'cgdelete -r ' + self.cgroup )
610
        return exitcode != 0
611

    
612
    def popen( self, *args, **kwargs ):
613
        """Return a Popen() object in node's namespace
614
           args: Popen() args, single list, or string
615
           kwargs: Popen() keyword args"""
616
        # Tell mnexec to execute command in our cgroup
617
        mncmd = [ 'mnexec', '-da', str( self.pid ),
618
                  '-g', self.name ]
619
        if self.sched == 'rt':
620
            mncmd += [ '-r', str( self.rtprio ) ]
621
        return Host.popen( self, *args, mncmd=mncmd, **kwargs )
622

    
623
    def cleanup( self ):
624
        "Clean up Node, then clean up our cgroup"
625
        super( CPULimitedHost, self ).cleanup()
626
        retry( retries=3, delaySecs=1, fn=self.cgroupDel )
627

    
628
    def chrt( self ):
629
        "Set RT scheduling priority"
630
        quietRun( 'chrt -p %s %s' % ( self.rtprio, self.pid ) )
631
        result = quietRun( 'chrt -p %s' % self.pid )
632
        firstline = result.split( '\n' )[ 0 ]
633
        lastword = firstline.split( ' ' )[ -1 ]
634
        if lastword != 'SCHED_RR':
635
            error( '*** error: could not assign SCHED_RR to %s\n' % self.name )
636
        return lastword
637

    
638
    def rtInfo( self, f ):
639
        "Internal method: return parameters for RT bandwidth"
640
        pstr, qstr = 'rt_period_us', 'rt_runtime_us'
641
        # RT uses wall clock time for period and quota
642
        quota = int( self.period_us * f * numCores() )
643
        return pstr, qstr, self.period_us, quota
644

    
645
    def cfsInfo( self, f):
646
        "Internal method: return parameters for CFS bandwidth"
647
        pstr, qstr = 'cfs_period_us', 'cfs_quota_us'
648
        # CFS uses wall clock time for period and CPU time for quota.
649
        quota = int( self.period_us * f * numCores() )
650
        period = self.period_us
651
        if f > 0 and quota < 1000:
652
            debug( '(cfsInfo: increasing default period) ' )
653
            quota = 1000
654
            period = int( quota / f / numCores() )
655
        return pstr, qstr, period, quota
656

    
657
    # BL comment:
658
    # This may not be the right API,
659
    # since it doesn't specify CPU bandwidth in "absolute"
660
    # units the way link bandwidth is specified.
661
    # We should use MIPS or SPECINT or something instead.
662
    # Alternatively, we should change from system fraction
663
    # to CPU seconds per second, essentially assuming that
664
    # all CPUs are the same.
665

    
666
    def setCPUFrac( self, f=-1, sched=None):
667
        """Set overall CPU fraction for this host
668
           f: CPU bandwidth limit (fraction)
669
           sched: 'rt' or 'cfs'
670
           Note 'cfs' requires CONFIG_CFS_BANDWIDTH"""
671
        if not f:
672
            return
673
        if not sched:
674
            sched = self.sched
675
        if sched == 'rt':
676
            pstr, qstr, period, quota = self.rtInfo( f )
677
        elif sched == 'cfs':
678
            pstr, qstr, period, quota = self.cfsInfo( f )
679
        else:
680
            return
681
        if quota < 0:
682
            # Reset to unlimited
683
            quota = -1
684
        # Set cgroup's period and quota
685
        self.cgroupSet( pstr, period )
686
        self.cgroupSet( qstr, quota )
687
        if sched == 'rt':
688
            # Set RT priority if necessary
689
            self.chrt()
690
        info( '(%s %d/%dus) ' % ( sched, quota, period ) )
691

    
692
    def setCPUs( self, cores, mems=0 ):
693
        "Specify (real) cores that our cgroup can run on"
694
        if type( cores ) is list:
695
            cores = ','.join( [ str( c ) for c in cores ] )
696
        self.cgroupSet( resource='cpuset', param='cpus',
697
                        value=cores )
698
        # Memory placement is probably not relevant, but we
699
        # must specify it anyway
700
        self.cgroupSet( resource='cpuset', param='mems',
701
                        value=mems)
702
        # We have to do this here after we've specified
703
        # cpus and mems
704
        errFail( 'cgclassify -g cpuset:/%s %s' % (
705
                 self.name, self.pid ) )
706

    
707
    def config( self, cpu=None, cores=None, **params ):
708
        """cpu: desired overall system CPU fraction
709
           cores: (real) core(s) this host can run on
710
           params: parameters for Node.config()"""
711
        r = Node.config( self, **params )
712
        # Was considering cpu={'cpu': cpu , 'sched': sched}, but
713
        # that seems redundant
714
        self.setParam( r, 'setCPUFrac', cpu=cpu )
715
        self.setParam( r, 'setCPUs', cores=cores )
716
        return r
717

    
718
    inited = False
719

    
720
    @classmethod
721
    def init( cls ):
722
        "Initialization for CPULimitedHost class"
723
        mountCgroups()
724
        cls.inited = True
725

    
726

    
727
# Some important things to note:
728
#
729
# The "IP" address which setIP() assigns to the switch is not
730
# an "IP address for the switch" in the sense of IP routing.
731
# Rather, it is the IP address for the control interface,
732
# on the control network, and it is only relevant to the
733
# controller. If you are running in the root namespace
734
# (which is the only way to run OVS at the moment), the
735
# control interface is the loopback interface, and you
736
# normally never want to change its IP address!
737
#
738
# In general, you NEVER want to attempt to use Linux's
739
# network stack (i.e. ifconfig) to "assign" an IP address or
740
# MAC address to a switch data port. Instead, you "assign"
741
# the IP and MAC addresses in the controller by specifying
742
# packets that you want to receive or send. The "MAC" address
743
# reported by ifconfig for a switch data port is essentially
744
# meaningless. It is important to understand this if you
745
# want to create a functional router using OpenFlow.
746

    
747
class Switch( Node ):
748
    """A Switch is a Node that is running (or has execed?)
749
       an OpenFlow switch."""
750

    
751
    portBase = 1  # Switches start with port 1 in OpenFlow
752
    dpidLen = 16  # digits in dpid passed to switch
753

    
754
    def __init__( self, name, dpid=None, opts='', listenPort=None, **params):
755
        """dpid: dpid for switch (or None to derive from name, e.g. s1 -> 1)
756
           opts: additional switch options
757
           listenPort: port to listen on for dpctl connections"""
758
        Node.__init__( self, name, **params )
759
        self.dpid = dpid if dpid else self.defaultDpid()
760
        self.opts = opts
761
        self.listenPort = listenPort
762
        if not self.inNamespace:
763
            self.controlIntf = Intf( 'lo', self, port=0 )
764

    
765
    def defaultDpid( self ):
766
        "Derive dpid from switch name, s1 -> 1"
767
        try:
768
            dpid = int( re.findall( r'\d+', self.name )[ 0 ] )
769
            dpid = hex( dpid )[ 2: ]
770
            dpid = '0' * ( self.dpidLen - len( dpid ) ) + dpid
771
            return dpid
772
        except IndexError:
773
            raise Exception( 'Unable to derive default datapath ID - '
774
                             'please either specify a dpid or use a '
775
                             'canonical switch name such as s23.' )
776

    
777
    def defaultIntf( self ):
778
        "Return control interface"
779
        if self.controlIntf:
780
            return self.controlIntf
781
        else:
782
            return Node.defaultIntf( self )
783

    
784
    def sendCmd( self, *cmd, **kwargs ):
785
        """Send command to Node.
786
           cmd: string"""
787
        kwargs.setdefault( 'printPid', False )
788
        if not self.execed:
789
            return Node.sendCmd( self, *cmd, **kwargs )
790
        else:
791
            error( '*** Error: %s has execed and cannot accept commands' %
792
                   self.name )
793

    
794
    def connected( self ):
795
        "Is the switch connected to a controller? (override this method)"
796
        return False and self  # satisfy pylint
797

    
798
    def __repr__( self ):
799
        "More informative string representation"
800
        intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
801
                              for i in self.intfList() ] ) )
802
        return '<%s %s: %s pid=%s> ' % (
803
            self.__class__.__name__, self.name, intfs, self.pid )
804

    
805
class UserSwitch( Switch ):
806
    "User-space switch."
807

    
808
    dpidLen = 12
809

    
810
    def __init__( self, name, dpopts='--no-slicing', **kwargs ):
811
        """Init.
812
           name: name for the switch
813
           dpopts: additional arguments to ofdatapath (--no-slicing)"""
814
        Switch.__init__( self, name, **kwargs )
815
        pathCheck( 'ofdatapath', 'ofprotocol',
816
                   moduleName='the OpenFlow reference user switch' +
817
                              '(openflow.org)' )
818
        if self.listenPort:
819
            self.opts += ' --listen=ptcp:%i ' % self.listenPort
820
        self.dpopts = dpopts
821

    
822
    @classmethod
823
    def setup( cls ):
824
        "Ensure any dependencies are loaded; if not, try to load them."
825
        if not os.path.exists( '/dev/net/tun' ):
826
            moduleDeps( add=TUN )
827

    
828
    def dpctl( self, *args ):
829
        "Run dpctl command"
830
        if not self.listenPort:
831
            return "can't run dpctl without passive listening port"
832
        return self.cmd( 'dpctl ' + ' '.join( args ) +
833
                         ' tcp:127.0.0.1:%i' % self.listenPort )
834

    
835
    def connected( self ):
836
        "Is the switch connected to a controller?"
837
        return 'remote.is-connected=true' in self.dpctl( 'status' )
838

    
839
    @staticmethod
840
    def TCReapply( intf ):
841
        """Unfortunately user switch and Mininet are fighting
842
           over tc queuing disciplines. To resolve the conflict,
843
           we re-create the user switch's configuration, but as a
844
           leaf of the TCIntf-created configuration."""
845
        if type( intf ) is TCIntf:
846
            ifspeed = 10000000000 # 10 Gbps
847
            minspeed = ifspeed * 0.001
848

    
849
            res = intf.config( **intf.params )
850
            parent = res['parent']
851

    
852
            # Re-add qdisc, root, and default classes user switch created, but
853
            # with new parent, as setup by Mininet's TCIntf
854
            intf.tc( "%s qdisc add dev %s " + parent +
855
                     " handle 1: htb default 0xfffe" )
856
            intf.tc( "%s class add dev %s classid 1:0xffff parent 1: htb rate "
857
                     + str(ifspeed) )
858
            intf.tc( "%s class add dev %s classid 1:0xfffe parent 1:0xffff " +
859
                     "htb rate " + str(minspeed) + " ceil " + str(ifspeed) )
860

    
861
    def start( self, controllers ):
862
        """Start OpenFlow reference user datapath.
863
           Log to /tmp/sN-{ofd,ofp}.log.
864
           controllers: list of controller objects"""
865
        # Add controllers
866
        clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
867
                            for c in controllers ] )
868
        ofdlog = '/tmp/' + self.name + '-ofd.log'
869
        ofplog = '/tmp/' + self.name + '-ofp.log'
870
        self.cmd( 'ifconfig lo up' )
871
        intfs = [ str( i ) for i in self.intfList() if not i.IP() ]
872
        self.cmd( 'ofdatapath -i ' + ','.join( intfs ) +
873
                  ' punix:/tmp/' + self.name + ' -d %s ' % self.dpid +
874
                  self.dpopts +
875
                  ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' )
876
        self.cmd( 'ofprotocol unix:/tmp/' + self.name +
877
                  ' ' + clist +
878
                  ' --fail=closed ' + self.opts +
879
                  ' 1> ' + ofplog + ' 2>' + ofplog + ' &' )
880
        if "no-slicing" not in self.dpopts:
881
            # Only TCReapply if slicing is enable
882
            sleep(1) # Allow ofdatapath to start before re-arranging qdisc's
883
            for intf in self.intfList():
884
                if not intf.IP():
885
                    self.TCReapply( intf )
886

    
887
    def stop( self ):
888
        "Stop OpenFlow reference user datapath."
889
        self.cmd( 'kill %ofdatapath' )
890
        self.cmd( 'kill %ofprotocol' )
891
        self.deleteIntfs()
892

    
893

    
894
class OVSLegacyKernelSwitch( Switch ):
895
    """Open VSwitch legacy kernel-space switch using ovs-openflowd.
896
       Currently only works in the root namespace."""
897

    
898
    def __init__( self, name, dp=None, **kwargs ):
899
        """Init.
900
           name: name for switch
901
           dp: netlink id (0, 1, 2, ...)
902
           defaultMAC: default MAC as unsigned int; random value if None"""
903
        Switch.__init__( self, name, **kwargs )
904
        self.dp = dp if dp else self.name
905
        self.intf = self.dp
906
        if self.inNamespace:
907
            error( "OVSKernelSwitch currently only works"
908
                   " in the root namespace.\n" )
909
            exit( 1 )
910

    
911
    @classmethod
912
    def setup( cls ):
913
        "Ensure any dependencies are loaded; if not, try to load them."
914
        pathCheck( 'ovs-dpctl', 'ovs-openflowd',
915
                   moduleName='Open vSwitch (openvswitch.org)')
916
        moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
917

    
918
    def start( self, controllers ):
919
        "Start up kernel datapath."
920
        ofplog = '/tmp/' + self.name + '-ofp.log'
921
        quietRun( 'ifconfig lo up' )
922
        # Delete local datapath if it exists;
923
        # then create a new one monitoring the given interfaces
924
        self.cmd( 'ovs-dpctl del-dp ' + self.dp )
925
        self.cmd( 'ovs-dpctl add-dp ' + self.dp )
926
        intfs = [ str( i ) for i in self.intfList() if not i.IP() ]
927
        self.cmd( 'ovs-dpctl', 'add-if', self.dp, ' '.join( intfs ) )
928
        # Run protocol daemon
929
        clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
930
                            for c in controllers ] )
931
        self.cmd( 'ovs-openflowd ' + self.dp +
932
                  ' ' + clist +
933
                  ' --fail=secure ' + self.opts +
934
                  ' --datapath-id=' + self.dpid +
935
                  ' 1>' + ofplog + ' 2>' + ofplog + '&' )
936
        self.execed = False
937

    
938
    def stop( self ):
939
        "Terminate kernel datapath."
940
        quietRun( 'ovs-dpctl del-dp ' + self.dp )
941
        self.cmd( 'kill %ovs-openflowd' )
942
        self.deleteIntfs()
943

    
944

    
945
class OVSSwitch( Switch ):
946
    "Open vSwitch switch. Depends on ovs-vsctl."
947

    
948
    def __init__( self, name, failMode='secure', datapath='kernel', **params ):
949
        """Init.
950
           name: name for switch
951
           failMode: controller loss behavior (secure|open)
952
           datapath: userspace or kernel mode (kernel|user)"""
953
        Switch.__init__( self, name, **params )
954
        self.failMode = failMode
955
        self.datapath = datapath
956

    
957
    @classmethod
958
    def setup( cls ):
959
        "Make sure Open vSwitch is installed and working"
960
        pathCheck( 'ovs-vsctl',
961
                   moduleName='Open vSwitch (openvswitch.org)')
962
        # This should no longer be needed, and it breaks
963
        # with OVS 1.7 which has renamed the kernel module:
964
        #  moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
965
        out, err, exitcode = errRun( 'ovs-vsctl -t 1 show' )
966
        if exitcode:
967
            error( out + err +
968
                   'ovs-vsctl exited with code %d\n' % exitcode +
969
                   '*** Error connecting to ovs-db with ovs-vsctl\n'
970
                   'Make sure that Open vSwitch is installed, '
971
                   'that ovsdb-server is running, and that\n'
972
                   '"ovs-vsctl show" works correctly.\n'
973
                   'You may wish to try '
974
                   '"service openvswitch-switch start".\n' )
975
            exit( 1 )
976

    
977
    def dpctl( self, *args ):
978
        "Run ovs-ofctl command"
979
        return self.cmd( 'ovs-ofctl', args[ 0 ], self, *args[ 1: ] )
980

    
981
    @staticmethod
982
    def TCReapply( intf ):
983
        """Unfortunately OVS and Mininet are fighting
984
           over tc queuing disciplines. As a quick hack/
985
           workaround, we clear OVS's and reapply our own."""
986
        if type( intf ) is TCIntf:
987
            intf.config( **intf.params )
988

    
989
    def attach( self, intf ):
990
        "Connect a data port"
991
        self.cmd( 'ovs-vsctl add-port', self, intf )
992
        self.cmd( 'ifconfig', intf, 'up' )
993
        self.TCReapply( intf )
994

    
995
    def detach( self, intf ):
996
        "Disconnect a data port"
997
        self.cmd( 'ovs-vsctl del-port', self, intf )
998

    
999
    def controllerUUIDs( self ):
1000
        "Return ovsdb UUIDs for our controllers"
1001
        uuids = []
1002
        controllers = self.cmd( 'ovs-vsctl -- get Bridge', self,
1003
                               'Controller' ).strip()
1004
        if controllers.startswith( '[' ) and controllers.endswith( ']' ):
1005
            controllers = controllers[ 1 : -1 ]
1006
            uuids = [ c.strip() for c in controllers.split( ',' ) ]
1007
        return uuids
1008

    
1009
    def connected( self ):
1010
        "Are we connected to at least one of our controllers?"
1011
        results = [ 'true' in self.cmd( 'ovs-vsctl -- get Controller',
1012
                                         uuid, 'is_connected' )
1013
                    for uuid in self.controllerUUIDs() ]
1014
        return reduce( or_, results, False )
1015

    
1016
    def start( self, controllers ):
1017
        "Start up a new OVS OpenFlow switch using ovs-vsctl"
1018
        if self.inNamespace:
1019
            raise Exception(
1020
                'OVS kernel switch does not work in a namespace' )
1021
        # We should probably call config instead, but this
1022
        # requires some rethinking...
1023
        self.cmd( 'ifconfig lo up' )
1024
        # Annoyingly, --if-exists option seems not to work
1025
        self.cmd( 'ovs-vsctl del-br', self )
1026
        self.cmd( 'ovs-vsctl add-br', self )
1027
        if self.datapath == 'user':
1028
            self.cmd( 'ovs-vsctl set bridge', self,'datapath_type=netdev' )
1029
        int( self.dpid, 16 ) # DPID must be a hex string
1030
        self.cmd( 'ovs-vsctl -- set Bridge', self,
1031
                  'other_config:datapath-id=' + self.dpid )
1032
        self.cmd( 'ovs-vsctl set-fail-mode', self, self.failMode )
1033
        for intf in self.intfList():
1034
            if not intf.IP():
1035
                self.attach( intf )
1036
        # Add controllers
1037
        clist = ' '.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
1038
                            for c in controllers ] )
1039
        if self.listenPort:
1040
            clist += ' ptcp:%s' % self.listenPort
1041
        self.cmd( 'ovs-vsctl set-controller', self, clist )
1042
        # Reconnect quickly to controllers (1s vs. 15s max_backoff)
1043
        for uuid in self.controllerUUIDs():
1044
            if uuid.count( '-' ) != 4:
1045
                # Doesn't look like a UUID
1046
                continue
1047
            uuid = uuid.strip()
1048
            self.cmd( 'ovs-vsctl set Controller', uuid,
1049
                      'max_backoff=1000' )
1050

    
1051
    def stop( self ):
1052
        "Terminate OVS switch."
1053
        self.cmd( 'ovs-vsctl del-br', self )
1054
        if self.datapath == 'user':
1055
            self.cmd( 'ip link del', self )
1056
        self.deleteIntfs()
1057

    
1058
OVSKernelSwitch = OVSSwitch
1059

    
1060

    
1061
class IVSSwitch(Switch):
1062
    """IVS virtual switch"""
1063

    
1064
    def __init__( self, name, **kwargs ):
1065
        Switch.__init__( self, name, **kwargs )
1066

    
1067
    @classmethod
1068
    def setup( cls ):
1069
        "Make sure IVS is installed"
1070
        pathCheck( 'ivs-ctl', 'ivs',
1071
                   moduleName="Indigo Virtual Switch (projectfloodlight.org)" )
1072
        out, err, exitcode = errRun( 'ivs-ctl show' )
1073
        if exitcode:
1074
            error( out + err +
1075
                   'ivs-ctl exited with code %d\n' % exitcode +
1076
                   '*** The openvswitch kernel module might '
1077
                   'not be loaded. Try modprobe openvswitch.\n' )
1078
            exit( 1 )
1079

    
1080
    def start( self, controllers ):
1081
        "Start up a new IVS switch"
1082
        args = ['ivs']
1083
        args.extend( ['--name', self.name] )
1084
        args.extend( ['--dpid', self.dpid] )
1085
        args.extend( ['--verbose'] )
1086
        for intf in self.intfs.values():
1087
            if not intf.IP():
1088
                args.extend( ['-i', intf.name] )
1089
        for c in controllers:
1090
            args.extend( ['-c', '%s:%d' % (c.IP(), c.port)] )
1091
        if self.listenPort:
1092
            args.extend( ['--listen', '127.0.0.1:%i' % self.listenPort] )
1093
        args.append( self.opts )
1094

    
1095
        logfile = '/tmp/ivs.%s.log' % self.name
1096

    
1097
        self.cmd( ' '.join(args) + ' >' + logfile + ' 2>&1 </dev/null &' )
1098

    
1099
    def stop( self ):
1100
        "Terminate IVS switch."
1101
        self.cmd( 'kill %ivs' )
1102
        self.deleteIntfs()
1103

    
1104
    def attach( self, intf ):
1105
        "Connect a data port"
1106
        self.cmd( 'ivs-ctl', 'add-port', '--datapath', self.name, intf )
1107

    
1108
    def detach( self, intf ):
1109
        "Disconnect a data port"
1110
        self.cmd( 'ivs-ctl', 'del-port', '--datapath', self.name, intf )
1111

    
1112
    def dpctl( self, *args ):
1113
        "Run dpctl command"
1114
        if not self.listenPort:
1115
            return "can't run dpctl without passive listening port"
1116
        return self.cmd( 'ovs-ofctl ' + ' '.join( args ) +
1117
                         ' tcp:127.0.0.1:%i' % self.listenPort )
1118

    
1119

    
1120
class Controller( Node ):
1121
    """A Controller is a Node that is running (or has execed?) an
1122
       OpenFlow controller."""
1123

    
1124
    def __init__( self, name, inNamespace=False, command='controller',
1125
                  cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1",
1126
                  port=6633, **params ):
1127
        self.command = command
1128
        self.cargs = cargs
1129
        self.cdir = cdir
1130
        self.ip = ip
1131
        self.port = port
1132
        Node.__init__( self, name, inNamespace=inNamespace,
1133
                       ip=ip, **params  )
1134
        self.cmd( 'ifconfig lo up' )  # Shouldn't be necessary
1135
        self.checkListening()
1136

    
1137
    def checkListening( self ):
1138
        "Make sure no controllers are running on our port"
1139
        # Verify that Telnet is installed first:
1140
        out, _err, returnCode = errRun( "which telnet" )
1141
        if 'telnet' not in out or returnCode != 0:
1142
            raise Exception( "Error running telnet to check for listening "
1143
                             "controllers; please check that it is "
1144
                             "installed." )
1145
        listening = self.cmd( "echo A | telnet -e A %s %d" %
1146
                              ( self.ip, self.port ) )
1147
        if 'Connected' in listening:
1148
            servers = self.cmd( 'netstat -atp' ).split( '\n' )
1149
            pstr = ':%d ' % self.port
1150
            clist = servers[ 0:1 ] + [ s for s in servers if pstr in s ]
1151
            raise Exception( "Please shut down the controller which is"
1152
                             " running on port %d:\n" % self.port +
1153
                             '\n'.join( clist ) )
1154

    
1155
    def start( self ):
1156
        """Start <controller> <args> on controller.
1157
           Log to /tmp/cN.log"""
1158
        pathCheck( self.command )
1159
        cout = '/tmp/' + self.name + '.log'
1160
        if self.cdir is not None:
1161
            self.cmd( 'cd ' + self.cdir )
1162
        self.cmd( self.command + ' ' + self.cargs % self.port +
1163
                  ' 1>' + cout + ' 2>' + cout + '&' )
1164
        self.execed = False
1165

    
1166
    def stop( self ):
1167
        "Stop controller."
1168
        self.cmd( 'kill %' + self.command )
1169
        self.terminate()
1170

    
1171
    def IP( self, intf=None ):
1172
        "Return IP address of the Controller"
1173
        if self.intfs:
1174
            ip = Node.IP( self, intf )
1175
        else:
1176
            ip = self.ip
1177
        return ip
1178

    
1179
    def __repr__( self ):
1180
        "More informative string representation"
1181
        return '<%s %s: %s:%s pid=%s> ' % (
1182
            self.__class__.__name__, self.name,
1183
            self.IP(), self.port, self.pid )
1184

    
1185

    
1186
class OVSController( Controller ):
1187
    "Open vSwitch controller"
1188
    def __init__( self, name, command='ovs-controller', **kwargs ):
1189
        Controller.__init__( self, name, command=command, **kwargs )
1190

    
1191

    
1192
class NOX( Controller ):
1193
    "Controller to run a NOX application."
1194

    
1195
    def __init__( self, name, *noxArgs, **kwargs ):
1196
        """Init.
1197
           name: name to give controller
1198
           noxArgs: arguments (strings) to pass to NOX"""
1199
        if not noxArgs:
1200
            warn( 'warning: no NOX modules specified; '
1201
                  'running packetdump only\n' )
1202
            noxArgs = [ 'packetdump' ]
1203
        elif type( noxArgs ) not in ( list, tuple ):
1204
            noxArgs = [ noxArgs ]
1205

    
1206
        if 'NOX_CORE_DIR' not in os.environ:
1207
            exit( 'exiting; please set missing NOX_CORE_DIR env var' )
1208
        noxCoreDir = os.environ[ 'NOX_CORE_DIR' ]
1209

    
1210
        Controller.__init__( self, name,
1211
                             command=noxCoreDir + '/nox_core',
1212
                             cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' +
1213
                             ' '.join( noxArgs ),
1214
                             cdir=noxCoreDir,
1215
                             **kwargs )
1216

    
1217

    
1218
class RemoteController( Controller ):
1219
    "Controller running outside of Mininet's control."
1220

    
1221
    def __init__( self, name, ip='127.0.0.1',
1222
                  port=6633, **kwargs):
1223
        """Init.
1224
           name: name to give controller
1225
           ip: the IP address where the remote controller is
1226
           listening
1227
           port: the port where the remote controller is listening"""
1228
        Controller.__init__( self, name, ip=ip, port=port, **kwargs )
1229

    
1230
    def start( self ):
1231
        "Overridden to do nothing."
1232
        return
1233

    
1234
    def stop( self ):
1235
        "Overridden to do nothing."
1236
        return
1237

    
1238
    def checkListening( self ):
1239
        "Warn if remote controller is not accessible"
1240
        listening = self.cmd( "echo A | telnet -e A %s %d" %
1241
                              ( self.ip, self.port ) )
1242
        if 'Connected' not in listening:
1243
            warn( "Unable to contact the remote controller"
1244
                  " at %s:%d\n" % ( self.ip, self.port ) )
1245

    
1246
class NAT( Node ):
1247
    """NAT: Provides connectivity to external network"""
1248

    
1249
    def __init__( self, name, inetIntf='eth0', subnet='10.0/8', localIntf=None, **params):
1250
        super( NAT, self ).__init__( name, **params )
1251

    
1252
        """Start NAT/forwarding between Mininet and external network
1253
        inetIntf: interface for internet access
1254
        subnet: Mininet subnet (default 10.0/8)="""
1255
        self.inetIntf = inetIntf
1256
        self.subnet = subnet #TODO: get subnet from Mininet directly
1257
        self.localIntf = localIntf
1258

    
1259
    def config( self, **params ):
1260
        super( NAT, self).config( **params )
1261
        """Configure the NAT and iptables"""
1262

    
1263
        if not self.localIntf:
1264
            self.localIntf =  self.defaultIntf()
1265

    
1266
        #-------------------------
1267
        print "inetIntf:", self.inetIntf
1268
        print "subnet:", self.subnet
1269
        # Identify the interface connecting to the mininet network
1270
        print "LocalIntf:", self.localIntf
1271
        #-------------------------
1272

    
1273
        self.cmd( 'sysctl net.ipv4.ip_forward=0' )
1274

    
1275
        # Flush any currently active rules
1276
        # TODO: is this safe?
1277
        self.cmd( 'iptables -F' )
1278
        self.cmd( 'iptables -t nat -F' )
1279

    
1280
        # Create default entries for unmatched traffic
1281
        self.cmd( 'iptables -P INPUT ACCEPT' )
1282
        self.cmd( 'iptables -P OUTPUT ACCEPT' )
1283
        self.cmd( 'iptables -P FORWARD DROP' )
1284

    
1285
        # Configure NAT
1286
        self.cmd( 'iptables -I FORWARD -i', self.localIntf, '-d', self.subnet, '-j DROP' )
1287
        self.cmd( 'iptables -A FORWARD -i', self.localIntf, '-s', self.subnet, '-j ACCEPT' )
1288
        self.cmd( 'iptables -A FORWARD -i', self.inetIntf, '-d', self.subnet, '-j ACCEPT' )
1289
        self.cmd( 'iptables -t nat -A POSTROUTING -o ', self.inetIntf, '-j MASQUERADE' )
1290

    
1291
        # Instruct the kernel to perform forwarding
1292
        self.cmd( 'sysctl net.ipv4.ip_forward=1' )
1293

    
1294
        # Prevent network-manager from messing with our interface
1295
        # by specifying manual configuration in /etc/network/interfaces
1296
        intf = self.localIntf
1297
        cfile = '/etc/network/interfaces'
1298
        line = '\niface %s inet manual\n' % intf
1299
        config = open( cfile ).read()
1300
        if ( line ) not in config:
1301
            info( '*** Adding "' + line.strip() + '" to ' + cfile )
1302
            with open( cfile, 'a' ) as f:
1303
                f.write( line )
1304
        # Probably need to restart network-manager to be safe -
1305
        # hopefully this won't disconnect you
1306
        self.cmd( 'service network-manager restart' )
1307

    
1308
    def terminate( self ):
1309
        """Stop NAT/forwarding between Mininet and external network"""
1310
        # Flush any currently active rules
1311
        # TODO: is this safe?
1312
        self.cmd( 'iptables -F' )
1313
        self.cmd( 'iptables -t nat -F' )
1314

    
1315
        # Instruct the kernel to stop forwarding
1316
        self.cmd( 'sysctl net.ipv4.ip_forward=0' )
1317

    
1318
        super( NAT, self ).terminate()
1319