Statistics
| Branch: | Tag: | Revision:

mininet / mininet / net.py @ 149a1f56

History | View | Annotate | Download (23.4 KB)

1
"""
2

3
    Mininet: A simple networking testbed for OpenFlow/SDN!
4

5
author: Bob Lantz (rlantz@cs.stanford.edu)
6
author: Brandon Heller (brandonh@stanford.edu)
7

8
Mininet creates scalable OpenFlow test networks by using
9
process-based virtualization and network namespaces.
10

11
Simulated hosts are created as processes in separate network
12
namespaces. This allows a complete OpenFlow network to be simulated on
13
top of a single Linux kernel.
14

15
Each host has:
16

17
A virtual console (pipes to a shell)
18
A virtual interfaces (half of a veth pair)
19
A parent shell (and possibly some child processes) in a namespace
20

21
Hosts have a network interface which is configured via ifconfig/ip
22
link/etc.
23

24
This version supports both the kernel and user space datapaths
25
from the OpenFlow reference implementation (openflowswitch.org)
26
as well as OpenVSwitch (openvswitch.org.)
27

28
In kernel datapath mode, the controller and switches are simply
29
processes in the root namespace.
30

31
Kernel OpenFlow datapaths are instantiated using dpctl(8), and are
32
attached to the one side of a veth pair; the other side resides in the
33
host namespace. In this mode, switch processes can simply connect to the
34
controller via the loopback interface.
35

36
In user datapath mode, the controller and switches can be full-service
37
nodes that live in their own network namespaces and have management
38
interfaces and IP addresses on a control network (e.g. 192.168.123.1,
39
currently routed although it could be bridged.)
40

41
In addition to a management interface, user mode switches also have
42
several switch interfaces, halves of veth pairs whose other halves
43
reside in the host nodes that the switches are connected to.
44

45
Consistent, straightforward naming is important in order to easily
46
identify hosts, switches and controllers, both from the CLI and
47
from program code. Interfaces are named to make it easy to identify
48
which interfaces belong to which node.
49

50
The basic naming scheme is as follows:
51

52
    Host nodes are named h1-hN
53
    Switch nodes are named s1-sN
54
    Controller nodes are named c0-cN
55
    Interfaces are named {nodename}-eth0 .. {nodename}-ethN
56

57
Note: If the network topology is created using mininet.topo, then
58
node numbers are unique among hosts and switches (e.g. we have
59
h1..hN and SN..SN+M) and also correspond to their default IP addresses
60
of 10.x.y.z/8 where x.y.z is the base-256 representation of N for
61
hN. This mapping allows easy determination of a node's IP
62
address from its name, e.g. h1 -> 10.0.0.1, h257 -> 10.0.1.1.
63

64
Note also that 10.0.0.1 can often be written as 10.1 for short, e.g.
65
"ping 10.1" is equivalent to "ping 10.0.0.1".
66

67
Currently we wrap the entire network in a 'mininet' object, which
68
constructs a simulated network based on a network topology created
69
using a topology object (e.g. LinearTopo) from mininet.topo or
70
mininet.topolib, and a Controller which the switches will connect
71
to. Several configuration options are provided for functions such as
72
automatically setting MAC addresses, populating the ARP table, or
73
even running a set of terminals to allow direct interaction with nodes.
74

75
After the network is created, it can be started using start(), and a
76
variety of useful tasks maybe performed, including basic connectivity
77
and bandwidth tests and running the mininet CLI.
78

79
Once the network is up and running, test code can easily get access
80
to host and switch objects which can then be used for arbitrary
81
experiments, typically involving running a series of commands on the
82
hosts.
83

84
After all desired tests or activities have been completed, the stop()
85
method may be called to shut down the network.
86

87
"""
88

    
89
import os
90
import re
91
import select
92
import signal
93
from time import sleep
94

    
95
from mininet.cli import CLI
96
from mininet.log import info, error, debug, output
97
from mininet.node import Host, OVSKernelSwitch, Controller
98
from mininet.link import Link, Intf
99
from mininet.util import quietRun, fixLimits, numCores
100
from mininet.util import macColonHex, ipStr, ipParse, netParse, ipAdd
101
from mininet.term import cleanUpScreens, makeTerms
102

    
103
class Mininet( object ):
104
    "Network emulation with hosts spawned in network namespaces."
105

    
106
    def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host,
107
                 controller=Controller, link=Link, intf=Intf,
108
                 build=True, xterms=False, cleanup=False, ipBase='10.0.0.0/8',
109
                 inNamespace=False,
110
                 autoSetMacs=False, autoStaticArp=False, autoPinCpus=False,
111
                 listenPort=None ):
112
        """Create Mininet object.
113
           topo: Topo (topology) object or None
114
           switch: default Switch class
115
           host: default Host class/constructor
116
           controller: default Controller class/constructor
117
           link: default Link class/constructor
118
           intf: default Intf class/constructor
119
           ipBase: base IP address for hosts,
120
           build: build now from topo?
121
           xterms: if build now, spawn xterms?
122
           cleanup: if build now, cleanup before creating?
123
           inNamespace: spawn switches and controller in net namespaces?
124
           autoSetMacs: set MAC addrs automatically like IP addresses?
125
           autoStaticArp: set all-pairs static MAC addrs?
126
           autoPinCpus: pin hosts to (real) cores (requires CPULimitedHost)?
127
           listenPort: base listening port to open; will be incremented for
128
               each additional switch in the net if inNamespace=False"""
129
        self.topo = topo
130
        self.switch = switch
131
        self.host = host
132
        self.controller = controller
133
        self.link = link
134
        self.intf = intf
135
        self.ipBase = ipBase
136
        self.ipBaseNum, self.prefixLen = netParse( self.ipBase )
137
        self.nextIP = 1  # start for address allocation
138
        self.inNamespace = inNamespace
139
        self.xterms = xterms
140
        self.cleanup = cleanup
141
        self.autoSetMacs = autoSetMacs
142
        self.autoStaticArp = autoStaticArp
143
        self.autoPinCpus = autoPinCpus
144
        self.numCores = numCores()
145
        self.nextCore = 0  # next core for pinning hosts to CPUs
146
        self.listenPort = listenPort
147

    
148
        self.hosts = []
149
        self.switches = []
150
        self.controllers = []
151

    
152
        self.nameToNode = {}  # name to Node (Host/Switch) objects
153

    
154
        self.terms = []  # list of spawned xterm processes
155

    
156
        Mininet.init()  # Initialize Mininet if necessary
157

    
158
        self.built = False
159
        if topo and build:
160
            self.build()
161

    
162
    def addHost( self, name, cls=None, **params ):
163
        """Add host.
164
           name: name of host to add
165
           cls: custom host class/constructor (optional)
166
           params: parameters for host
167
           returns: added host"""
168
        # Default IP and MAC addresses
169
        defaults = { 'ip': ipAdd( self.nextIP,
170
                                  ipBaseNum=self.ipBaseNum,
171
                                  prefixLen=self.prefixLen ) }
172
        if self.autoSetMacs:
173
            defaults[ 'mac'] = macColonHex( self.nextIP )
174
        if self.autoPinCpus:
175
            defaults[ 'cores' ] = self.nextCore
176
            self.nextCore = ( self.nextCore + 1 ) % self.numCores
177
        self.nextIP += 1
178
        defaults.update( params )
179
        if not cls:
180
            cls = self.host
181
        h = cls( name, **defaults )
182
        self.hosts.append( h )
183
        self.nameToNode[ name ] = h
184
        return h
185

    
186
    def addSwitch( self, name, cls=None, **params ):
187
        """Add switch.
188
           name: name of switch to add
189
           cls: custom switch class/constructor (optional)
190
           returns: added switch
191
           side effect: increments listenPort ivar ."""
192
        defaults = { 'listenPort': self.listenPort,
193
                     'inNamespace': self.inNamespace }
194
        defaults.update( params )
195
        if not cls:
196
            cls = self.switch
197
        sw = cls( name, **defaults )
198
        if not self.inNamespace and self.listenPort:
199
            self.listenPort += 1
200
        self.switches.append( sw )
201
        self.nameToNode[ name ] = sw
202
        return sw
203

    
204
    def addController( self, name='c0', controller=None, **params ):
205
        """Add controller.
206
           controller: Controller class"""
207
        if not controller:
208
            controller = self.controller
209
        controller_new = controller( name, **params )
210
        if controller_new:  # allow controller-less setups
211
            self.controllers.append( controller_new )
212
            self.nameToNode[ name ] = controller_new
213
        return controller_new
214

    
215
    # BL: is this better than just using nameToNode[] ?
216
    # Should it have a better name?
217
    def getNodeByName( self, nodeName ):
218
        "Return node with given name"
219
        return self.nameToNode[ nodeName ]
220

    
221
    def addLink( self, node1, node2, port1=None, port2=None,
222
                 cls=None, **params ):
223
        """"Add a link from node1 to node2
224
            node1: source node
225
            node2: dest node
226
            port1: source port
227
            port2: dest port
228
            returns: link object"""
229
        defaults = { 'port1': port1,
230
                     'port2': port2,
231
                     'intf': self.intf }
232
        defaults.update( params )
233
        if not cls:
234
            cls = self.link
235
        return cls( node1, node2, **defaults )
236

    
237
    def configHosts( self ):
238
        "Configure a set of hosts."
239
        for host in self.hosts:
240
            info( host.name + ' ' )
241
            host.configDefault( defaultRoute=host.defaultIntf() )
242
            # You're low priority, dude!
243
            # BL: do we want to do this here or not?
244
            # May not make sense if we have CPU lmiting...
245
            # quietRun( 'renice +18 -p ' + repr( host.pid ) )
246
            # This may not be the right place to do this, but
247
            # it needs to be done somewhere.
248
            host.cmd( 'ifconfig lo up' )
249
        info( '\n' )
250

    
251
    def buildFromTopo( self, topo=None ):
252
        """Build mininet from a topology object
253
           At the end of this function, everything should be connected
254
           and up."""
255

    
256
        # Possibly we should clean up here and/or validate
257
        # the topo
258
        if self.cleanup:
259
            pass
260

    
261
        info( '*** Creating network\n' )
262

    
263
        if not self.controllers:
264
            # Add a default controller
265
            info( '*** Adding controller\n' )
266
            self.addController( 'c0' )
267

    
268
        info( '*** Adding hosts:\n' )
269
        for hostName in topo.hosts():
270
            self.addHost( hostName, **topo.nodeInfo( hostName ) )
271
            info( hostName + ' ' )
272

    
273
        info( '\n*** Adding switches:\n' )
274
        for switchName in topo.switches():
275
            self.addSwitch( switchName, **topo.nodeInfo( switchName) )
276
            info( switchName + ' ' )
277

    
278
        info( '\n*** Adding links:\n' )
279
        for srcName, dstName in topo.links(sort=True):
280
            src, dst = self.nameToNode[ srcName ], self.nameToNode[ dstName ]
281
            params = topo.linkInfo( srcName, dstName )
282
            srcPort, dstPort = topo.port( srcName, dstName )
283
            self.addLink( src, dst, srcPort, dstPort, **params )
284
            info( '(%s, %s) ' % ( src.name, dst.name ) )
285

    
286
        info( '\n' )
287

    
288
    def configureControlNetwork( self ):
289
        "Control net config hook: override in subclass"
290
        raise Exception( 'configureControlNetwork: '
291
               'should be overriden in subclass', self )
292

    
293
    def build( self ):
294
        "Build mininet."
295
        if self.topo:
296
            self.buildFromTopo( self.topo )
297
        if ( self.inNamespace ):
298
            self.configureControlNetwork()
299
        info( '*** Configuring hosts\n' )
300
        self.configHosts()
301
        if self.xterms:
302
            self.startTerms()
303
        if self.autoStaticArp:
304
            self.staticArp()
305
        self.built = True
306

    
307
    def startTerms( self ):
308
        "Start a terminal for each node."
309
        info( "*** Running terms on %s\n" % os.environ[ 'DISPLAY' ] )
310
        cleanUpScreens()
311
        self.terms += makeTerms( self.controllers, 'controller' )
312
        self.terms += makeTerms( self.switches, 'switch' )
313
        self.terms += makeTerms( self.hosts, 'host' )
314

    
315
    def stopXterms( self ):
316
        "Kill each xterm."
317
        for term in self.terms:
318
            os.kill( term.pid, signal.SIGKILL )
319
        cleanUpScreens()
320

    
321
    def staticArp( self ):
322
        "Add all-pairs ARP entries to remove the need to handle broadcast."
323
        for src in self.hosts:
324
            for dst in self.hosts:
325
                if src != dst:
326
                    src.setARP( ip=dst.IP(), mac=dst.MAC() )
327

    
328
    def start( self ):
329
        "Start controller and switches."
330
        if not self.built:
331
            self.build()
332
        info( '*** Starting controller\n' )
333
        for controller in self.controllers:
334
            controller.start()
335
        info( '*** Starting %s switches\n' % len( self.switches ) )
336
        for switch in self.switches:
337
            info( switch.name + ' ')
338
            switch.start( self.controllers )
339
        info( '\n' )
340

    
341
    def stop( self ):
342
        "Stop the controller(s), switches and hosts"
343
        if self.terms:
344
            info( '*** Stopping %i terms\n' % len( self.terms ) )
345
            self.stopXterms()
346
        info( '*** Stopping %i hosts\n' % len( self.hosts ) )
347
        for host in self.hosts:
348
            info( host.name + ' ' )
349
            host.terminate()
350
        info( '\n' )
351
        info( '*** Stopping %i switches\n' % len( self.switches ) )
352
        for switch in self.switches:
353
            info( switch.name + ' ' )
354
            switch.stop()
355
        info( '\n' )
356
        info( '*** Stopping %i controllers\n' % len( self.controllers ) )
357
        for controller in self.controllers:
358
            info( controller.name + ' ' )
359
            controller.stop()
360
        info( '\n*** Done\n' )
361

    
362
    def run( self, test, *args, **kwargs ):
363
        "Perform a complete start/test/stop cycle."
364
        self.start()
365
        info( '*** Running test\n' )
366
        result = test( *args, **kwargs )
367
        self.stop()
368
        return result
369

    
370
    def monitor( self, hosts=None, timeoutms=-1 ):
371
        """Monitor a set of hosts (or all hosts by default),
372
           and return their output, a line at a time.
373
           hosts: (optional) set of hosts to monitor
374
           timeoutms: (optional) timeout value in ms
375
           returns: iterator which returns host, line"""
376
        if hosts is None:
377
            hosts = self.hosts
378
        poller = select.poll()
379
        Node = hosts[ 0 ]  # so we can call class method fdToNode
380
        for host in hosts:
381
            poller.register( host.stdout )
382
        while True:
383
            ready = poller.poll( timeoutms )
384
            for fd, event in ready:
385
                host = Node.fdToNode( fd )
386
                if event & select.POLLIN:
387
                    line = host.readline()
388
                    if line is not None:
389
                        yield host, line
390
            # Return if non-blocking
391
            if not ready and timeoutms >= 0:
392
                yield None, None
393

    
394
    # XXX These test methods should be moved out of this class.
395
    # Probably we should create a tests.py for them
396

    
397
    @staticmethod
398
    def _parsePing( pingOutput ):
399
        "Parse ping output and return packets sent, received."
400
        # Check for downed link
401
        if 'connect: Network is unreachable' in pingOutput:
402
            return (1, 0)
403
        r = r'(\d+) packets transmitted, (\d+) received'
404
        m = re.search( r, pingOutput )
405
        if m == None:
406
            error( '*** Error: could not parse ping output: %s\n' %
407
                     pingOutput )
408
            return (1, 0)
409
        sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
410
        return sent, received
411

    
412
    def ping( self, hosts=None ):
413
        """Ping between all specified hosts.
414
           hosts: list of hosts
415
           returns: ploss packet loss percentage"""
416
        # should we check if running?
417
        packets = 0
418
        lost = 0
419
        ploss = None
420
        if not hosts:
421
            hosts = self.hosts
422
            output( '*** Ping: testing ping reachability\n' )
423
        for node in hosts:
424
            output( '%s -> ' % node.name )
425
            for dest in hosts:
426
                if node != dest:
427
                    result = node.cmd( 'ping -c1 ' + dest.IP() )
428
                    sent, received = self._parsePing( result )
429
                    packets += sent
430
                    if received > sent:
431
                        error( '*** Error: received too many packets' )
432
                        error( '%s' % result )
433
                        node.cmdPrint( 'route' )
434
                        exit( 1 )
435
                    lost += sent - received
436
                    output( ( '%s ' % dest.name ) if received else 'X ' )
437
            output( '\n' )
438
            ploss = 100 * lost / packets
439
        output( "*** Results: %i%% dropped (%d/%d lost)\n" %
440
                ( ploss, lost, packets ) )
441
        return ploss
442

    
443
    def pingAll( self ):
444
        """Ping between all hosts.
445
           returns: ploss packet loss percentage"""
446
        return self.ping()
447

    
448
    def pingPair( self ):
449
        """Ping between first two hosts, useful for testing.
450
           returns: ploss packet loss percentage"""
451
        hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
452
        return self.ping( hosts=hosts )
453

    
454
    @staticmethod
455
    def _parseIperf( iperfOutput ):
456
        """Parse iperf output and return bandwidth.
457
           iperfOutput: string
458
           returns: result string"""
459
        r = r'([\d\.]+ \w+/sec)'
460
        m = re.findall( r, iperfOutput )
461
        if m:
462
            return m[-1]
463
        else:
464
            # was: raise Exception(...)
465
            error( 'could not parse iperf output: ' + iperfOutput )
466
            return ''
467

    
468
    # XXX This should be cleaned up
469

    
470
    def iperf( self, hosts=None, l4Type='TCP', udpBw='10M' ):
471
        """Run iperf between two hosts.
472
           hosts: list of hosts; if None, uses opposite hosts
473
           l4Type: string, one of [ TCP, UDP ]
474
           returns: results two-element array of server and client speeds"""
475
        if not quietRun( 'which telnet' ):
476
            error( 'Cannot find telnet in $PATH - required for iperf test' )
477
            return
478
        if not hosts:
479
            hosts = [ self.hosts[ 0 ], self.hosts[ -1 ] ]
480
        else:
481
            assert len( hosts ) == 2
482
        client, server = hosts
483
        output( '*** Iperf: testing ' + l4Type + ' bandwidth between ' )
484
        output( "%s and %s\n" % ( client.name, server.name ) )
485
        server.cmd( 'killall -9 iperf' )
486
        iperfArgs = 'iperf '
487
        bwArgs = ''
488
        if l4Type == 'UDP':
489
            iperfArgs += '-u '
490
            bwArgs = '-b ' + udpBw + ' '
491
        elif l4Type != 'TCP':
492
            raise Exception( 'Unexpected l4 type: %s' % l4Type )
493
        server.sendCmd( iperfArgs + '-s', printPid=True )
494
        servout = ''
495
        while server.lastPid is None:
496
            servout += server.monitor()
497
        if l4Type == 'TCP':
498
            while 'Connected' not in client.cmd(
499
                'sh -c "echo A | telnet -e A %s 5001"' % server.IP()):
500
                output('waiting for iperf to start up...')
501
                sleep(.5)
502
        cliout = client.cmd( iperfArgs + '-t 5 -c ' + server.IP() + ' ' +
503
                           bwArgs )
504
        debug( 'Client output: %s\n' % cliout )
505
        server.sendInt()
506
        servout += server.waitOutput()
507
        debug( 'Server output: %s\n' % servout )
508
        result = [ self._parseIperf( servout ), self._parseIperf( cliout ) ]
509
        if l4Type == 'UDP':
510
            result.insert( 0, udpBw )
511
        output( '*** Results: %s\n' % result )
512
        return result
513

    
514
    # BL: I think this can be rewritten now that we have
515
    # a real link class.
516
    def configLinkStatus( self, src, dst, status ):
517
        """Change status of src <-> dst links.
518
           src: node name
519
           dst: node name
520
           status: string {up, down}"""
521
        if src not in self.nameToNode:
522
            error( 'src not in network: %s\n' % src )
523
        elif dst not in self.nameToNode:
524
            error( 'dst not in network: %s\n' % dst )
525
        else:
526
            if type( src ) is str:
527
                src = self.nameToNode[ src ]
528
            if type( dst ) is str:
529
                dst = self.nameToNode[ dst ]
530
            connections = src.connectionsTo( dst )
531
            if len( connections ) == 0:
532
                error( 'src and dst not connected: %s %s\n' % ( src, dst) )
533
            for srcIntf, dstIntf in connections:
534
                result = srcIntf.ifconfig( status )
535
                if result:
536
                    error( 'link src status change failed: %s\n' % result )
537
                result = dstIntf.ifconfig( status )
538
                if result:
539
                    error( 'link dst status change failed: %s\n' % result )
540

    
541
    def interact( self ):
542
        "Start network and run our simple CLI."
543
        self.start()
544
        result = CLI( self )
545
        self.stop()
546
        return result
547

    
548
    inited = False
549

    
550
    @classmethod
551
    def init( cls ):
552
        "Initialize Mininet"
553
        if cls.inited:
554
            return
555
        if os.getuid() != 0:
556
            # Note: this script must be run as root
557
            # Probably we should only sudo when we need
558
            # to as per Big Switch's patch
559
            print "*** Mininet must run as root."
560
            exit( 1 )
561
        fixLimits()
562
        cls.inited = True
563

    
564

    
565
class MininetWithControlNet( Mininet ):
566

    
567
    """Control network support:
568

569
       Create an explicit control network. Currently this is only
570
       used/usable with the user datapath.
571

572
       Notes:
573

574
       1. If the controller and switches are in the same (e.g. root)
575
          namespace, they can just use the loopback connection.
576

577
       2. If we can get unix domain sockets to work, we can use them
578
          instead of an explicit control network.
579

580
       3. Instead of routing, we could bridge or use 'in-band' control.
581

582
       4. Even if we dispense with this in general, it could still be
583
          useful for people who wish to simulate a separate control
584
          network (since real networks may need one!)
585

586
       5. Basically nobody ever used this code, so it has been moved
587
          into its own class.
588

589
       6. Ultimately we may wish to extend this to allow us to create a
590
          control network which every node's control interface is
591
          attached to."""
592

    
593
    def configureControlNetwork( self ):
594
        "Configure control network."
595
        self.configureRoutedControlNetwork()
596

    
597
    # We still need to figure out the right way to pass
598
    # in the control network location.
599

    
600
    def configureRoutedControlNetwork( self, ip='192.168.123.1',
601
        prefixLen=16 ):
602
        """Configure a routed control network on controller and switches.
603
           For use with the user datapath only right now."""
604
        controller = self.controllers[ 0 ]
605
        info( controller.name + ' <->' )
606
        cip = ip
607
        snum = ipParse( ip )
608
        for switch in self.switches:
609
            info( ' ' + switch.name )
610
            link = self.link( switch, controller, port1=0 )
611
            sintf, cintf = link.intf1, link.intf2
612
            switch.controlIntf = sintf
613
            snum += 1
614
            while snum & 0xff in [ 0, 255 ]:
615
                snum += 1
616
            sip = ipStr( snum )
617
            cintf.setIP( cip, prefixLen )
618
            sintf.setIP( sip, prefixLen )
619
            controller.setHostRoute( sip, cintf )
620
            switch.setHostRoute( cip, sintf )
621
        info( '\n' )
622
        info( '*** Testing control network\n' )
623
        while not cintf.isUp():
624
            info( '*** Waiting for', cintf, 'to come up\n' )
625
            sleep( 1 )
626
        for switch in self.switches:
627
            while not sintf.isUp():
628
                info( '*** Waiting for', sintf, 'to come up\n' )
629
                sleep( 1 )
630
            if self.ping( hosts=[ switch, controller ] ) != 0:
631
                error( '*** Error: control network test failed\n' )
632
                exit( 1 )
633
        info( '\n' )