Statistics
| Branch: | Tag: | Revision:

mininet / mininet / net.py @ 496b5f9e

History | View | Annotate | Download (18.1 KB)

1
#!/usr/bin/python
2
"""
3

4
    Mininet: A simple networking testbed for OpenFlow!
5

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

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

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

16
Each host has:
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.
26

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

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

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

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

44
Naming:
45

46
    Host nodes are named h1-hN
47
    Switch nodes are named s0-sN
48
    Interfaces are named { nodename }-eth0 .. { nodename }-ethN
49

50
"""
51

    
52
import os
53
import re
54
import signal
55
from time import sleep
56

    
57
from mininet.cli import CLI
58
from mininet.log import lg
59
from mininet.node import KernelSwitch, OVSKernelSwitch
60
from mininet.util import quietRun, fixLimits
61
from mininet.util import makeIntfPair, moveIntf
62
from mininet.xterm import cleanUpScreens, makeXterms
63

    
64
DATAPATHS = [ 'kernel' ] #[ 'user', 'kernel' ]
65

    
66
def init():
67
    "Initialize Mininet."
68
    if os.getuid() != 0:
69
        # Note: this script must be run as root
70
        # Perhaps we should do so automatically!
71
        print "*** Mininet must run as root."
72
        exit( 1 )
73
    # If which produces no output, then netns is not in the path.
74
    # May want to loosen this to handle netns in the current dir.
75
    if not quietRun( [ 'which', 'netns' ] ):
76
        raise Exception( "Could not find netns; see INSTALL" )
77
    fixLimits()
78

    
79
class Mininet( object ):
80
    "Network emulation with hosts spawned in network namespaces."
81

    
82
    def __init__( self, topo, switch, host, controller, cparams,
83
                 build=True, xterms=False, cleanup=False,
84
                 inNamespace=False,
85
                 autoSetMacs=False, autoStaticArp=False ):
86
        """Create Mininet object.
87
           topo: Topo object
88
           switch: Switch class
89
           host: Host class
90
           controller: Controller class
91
           cparams: ControllerParams object
92
           now: build now?
93
           xterms: if build now, spawn xterms?
94
           cleanup: if build now, cleanup before creating?
95
           inNamespace: spawn switches and controller in net namespaces?
96
           autoSetMacs: set MAC addrs to DPIDs?
97
           autoStaticArp: set all-pairs static MAC addrs?"""
98
        self.topo = topo
99
        self.switch = switch
100
        self.host = host
101
        self.controller = controller
102
        self.cparams = cparams
103
        self.nodes = {} # dpid to Node{ Host, Switch } objects
104
        self.controllers = {} # controller name to Controller objects
105
        self.dps = 0 # number of created kernel datapaths
106
        self.inNamespace = inNamespace
107
        self.xterms = xterms
108
        self.cleanup = cleanup
109
        self.autoSetMacs = autoSetMacs
110
        self.autoStaticArp = autoStaticArp
111

    
112
        self.terms = [] # list of spawned xterm processes
113

    
114
        if build:
115
            self.build()
116

    
117
    def _addHost( self, dpid ):
118
        """Add host.
119
           dpid: DPID of host to add"""
120
        host = self.host( 'h_' + self.topo.name( dpid ) )
121
        # for now, assume one interface per host.
122
        host.intfs.append( 'h_' + self.topo.name( dpid ) + '-eth0' )
123
        self.nodes[ dpid ] = host
124
        #lg.info( '%s ' % host.name )
125

    
126
    def _addSwitch( self, dpid ):
127
        """Add switch.
128
           dpid: DPID of switch to add"""
129
        sw = None
130
        swDpid = None
131
        if self.autoSetMacs:
132
            swDpid = dpid
133
        if self.switch is KernelSwitch or self.switch is OVSKernelSwitch:
134
            sw = self.switch( 's_' + self.topo.name( dpid ), dp = self.dps,
135
                             dpid = swDpid )
136
            self.dps += 1
137
        else:
138
            sw = self.switch( 's_' + self.topo.name( dpid ) )
139
        self.nodes[ dpid ] = sw
140

    
141
    def _addLink( self, src, dst ):
142
        """Add link.
143
           src: source DPID
144
           dst: destination DPID"""
145
        srcPort, dstPort = self.topo.port( src, dst )
146
        srcNode = self.nodes[ src ]
147
        dstNode = self.nodes[ dst ]
148
        srcIntf = srcNode.intfName( srcPort )
149
        dstIntf = dstNode.intfName( dstPort )
150
        makeIntfPair( srcIntf, dstIntf )
151
        srcNode.intfs.append( srcIntf )
152
        dstNode.intfs.append( dstIntf )
153
        srcNode.ports[ srcPort ] = srcIntf
154
        dstNode.ports[ dstPort ] = dstIntf
155
        #lg.info( '\n' )
156
        #lg.info( 'added intf %s to src node %x\n' % ( srcIntf, src ) )
157
        #lg.info( 'added intf %s to dst node %x\n' % ( dstIntf, dst ) )
158
        if srcNode.inNamespace:
159
            #lg.info( 'moving src w/inNamespace set\n' )
160
            moveIntf( srcIntf, srcNode )
161
        if dstNode.inNamespace:
162
            #lg.info( 'moving dst w/inNamespace set\n' )
163
            moveIntf( dstIntf, dstNode )
164
        srcNode.connection[ srcIntf ] = ( dstNode, dstIntf )
165
        dstNode.connection[ dstIntf ] = ( srcNode, srcIntf )
166

    
167
    def _addController( self, controller ):
168
        """Add controller.
169
           controller: Controller class"""
170
        controller = self.controller( 'c0', self.inNamespace )
171
        if controller: # allow controller-less setups
172
            self.controllers[ 'c0' ] = controller
173

    
174
    # Control network support:
175
    #
176
    # Create an explicit control network. Currently this is only
177
    # used by the user datapath configuration.
178
    #
179
    # Notes:
180
    #
181
    # 1. If the controller and switches are in the same ( e.g. root )
182
    #    namespace, they can just use the loopback connection.
183
    #    We may wish to do this for the user datapath as well as the
184
    #    kernel datapath.
185
    #
186
    # 2. If we can get unix domain sockets to work, we can use them
187
    #    instead of an explicit control network.
188
    #
189
    # 3. Instead of routing, we could bridge or use 'in-band' control.
190
    #
191
    # 4. Even if we dispense with this in general, it could still be
192
    #    useful for people who wish to simulate a separate control
193
    #    network ( since real networks may need one! )
194

    
195
    def _configureControlNetwork( self ):
196
        "Configure control network."
197
        self._configureRoutedControlNetwork()
198

    
199
    def _configureRoutedControlNetwork( self ):
200
        """Configure a routed control network on controller and switches.
201
           For use with the user datapath only right now.
202
           TODO( brandonh ) test this code!
203
           """
204

    
205
        # params were: controller, switches, ips
206

    
207
        controller = self.controllers[ 'c0' ]
208
        lg.info( '%s <-> ' % controller.name )
209
        for switchDpid in self.topo.switches():
210
            switch = self.nodes[ switchDpid ]
211
            lg.info( '%s ' % switch.name )
212
            sip = self.topo.ip( switchDpid )#ips.next()
213
            sintf = switch.intfs[ 0 ]
214
            node, cintf = switch.connection[ sintf ]
215
            if node != controller:
216
                lg.error( '*** Error: switch %s not connected to correct'
217
                         'controller' %
218
                         switch.name )
219
                exit( 1 )
220
            controller.setIP( cintf, self.cparams.ip, '/' +
221
                             self.cparams.subnetSize )
222
            switch.setIP( sintf, sip, '/' + self.cparams.subnetSize )
223
            controller.setHostRoute( sip, cintf )
224
            switch.setHostRoute( self.cparams.ip, sintf )
225
        lg.info( '\n' )
226
        lg.info( '*** Testing control network\n' )
227
        while not controller.intfIsUp( controller.intfs[ 0 ] ):
228
            lg.info( '*** Waiting for %s to come up\n',
229
                controller.intfs[ 0 ] )
230
            sleep( 1 )
231
        for switchDpid in self.topo.switches():
232
            switch = self.nodes[ switchDpid ]
233
            while not switch.intfIsUp( switch.intfs[ 0 ] ):
234
                lg.info( '*** Waiting for %s to come up\n' %
235
                    switch.intfs[ 0 ] )
236
                sleep( 1 )
237
            if self.ping( hosts=[ switch, controller ] ) != 0:
238
                lg.error( '*** Error: control network test failed\n' )
239
                exit( 1 )
240
        lg.info( '\n' )
241

    
242
    def _configHosts( self ):
243
        "Configure a set of hosts."
244
        # params were: hosts, ips
245
        for hostDpid in self.topo.hosts():
246
            host = self.nodes[ hostDpid ]
247
            hintf = host.intfs[ 0 ]
248
            host.setIP( hintf, self.topo.ip( hostDpid ),
249
                       '/' + str( self.cparams.subnetSize ) )
250
            host.setDefaultRoute( hintf )
251
            # You're low priority, dude!
252
            quietRun( 'renice +18 -p ' + repr( host.pid ) )
253
            lg.info( '%s ', host.name )
254
        lg.info( '\n' )
255

    
256
    def build( self ):
257
        """Build mininet.
258
           At the end of this function, everything should be connected
259
           and up."""
260
        if self.cleanup:
261
            pass # cleanup
262
        # validate topo?
263
        lg.info( '*** Adding controller\n' )
264
        self._addController( self.controller )
265
        lg.info( '*** Creating network\n' )
266
        lg.info( '*** Adding hosts:\n' )
267
        for host in sorted( self.topo.hosts() ):
268
            self._addHost( host )
269
            lg.info( '0x%x ' % host )
270
        lg.info( '\n*** Adding switches:\n' )
271
        for switch in sorted( self.topo.switches() ):
272
            self._addSwitch( switch )
273
            lg.info( '0x%x ' % switch )
274
        lg.info( '\n*** Adding edges:\n' )
275
        for src, dst in sorted( self.topo.edges() ):
276
            self._addLink( src, dst )
277
            lg.info( '(0x%x, 0x%x) ' % ( src, dst ) )
278
        lg.info( '\n' )
279

    
280
        if self.inNamespace:
281
            lg.info( '*** Configuring control network\n' )
282
            self._configureControlNetwork()
283

    
284
        lg.info( '*** Configuring hosts\n' )
285
        self._configHosts()
286

    
287
        if self.xterms:
288
            self.startXterms()
289
        if self.autoSetMacs:
290
            self.setMacs()
291
        if self.autoStaticArp:
292
            self.staticArp()
293

    
294
    def switchNodes( self ):
295
        "Return switch nodes."
296
        return [ self.nodes[ dpid ] for dpid in self.topo.switches() ]
297

    
298
    def hostNodes( self ):
299
        "Return host nodes."
300
        return [ self.nodes[ dpid ] for dpid in self.topo.hosts() ]
301

    
302
    def startXterms( self ):
303
        "Start an xterm for each node in the topo."
304
        lg.info( "*** Running xterms on %s\n" % os.environ[ 'DISPLAY' ] )
305
        cleanUpScreens()
306
        self.terms += makeXterms( self.controllers.values(), 'controller' )
307
        self.terms += makeXterms( self.switchNodes(), 'switch' )
308
        self.terms += makeXterms( self.hostNodes(), 'host' )
309

    
310
    def stopXterms( self ):
311
        "Kill each xterm."
312
        # Kill xterms
313
        for term in self.terms:
314
            os.kill( term.pid, signal.SIGKILL )
315
        cleanUpScreens()
316

    
317
    def setMacs( self ):
318
        """Set MAC addrs to correspond to datapath IDs on hosts.
319
           Assume that the host only has one interface."""
320
        for dpid in self.topo.hosts():
321
            hostNode = self.nodes[ dpid ]
322
            hostNode.setMAC( hostNode.intfs[ 0 ], dpid )
323

    
324
    def staticArp( self ):
325
        "Add all-pairs ARP entries to remove the need to handle broadcast."
326
        for src in self.topo.hosts():
327
            srcNode = self.nodes[ src ]
328
            for dst in self.topo.hosts():
329
                if src != dst:
330
                    srcNode.setARP( dst, dst )
331

    
332
    def start( self ):
333
        "Start controller and switches\n"
334
        lg.info( '*** Starting controller\n' )
335
        for cnode in self.controllers.values():
336
            cnode.start()
337
        lg.info( '*** Starting %s switches\n' % len( self.topo.switches() ) )
338
        for switchDpid in self.topo.switches():
339
            switch = self.nodes[ switchDpid ]
340
            #lg.info( 'switch = %s' % switch )
341
            lg.info( '0x%x ' % switchDpid )
342
            switch.start( self.controllers )
343
        lg.info( '\n' )
344

    
345
    def stop( self ):
346
        "Stop the controller(s), switches and hosts\n"
347
        if self.terms:
348
            lg.info( '*** Stopping %i terms\n' % len( self.terms ) )
349
            self.stopXterms()
350
        lg.info( '*** Stopping %i hosts\n' % len( self.topo.hosts() ) )
351
        for hostDpid in self.topo.hosts():
352
            host = self.nodes[ hostDpid ]
353
            lg.info( '%s ' % host.name )
354
            host.terminate()
355
        lg.info( '\n' )
356
        lg.info( '*** Stopping %i switches\n' % len( self.topo.switches() ) )
357
        for switchDpid in self.topo.switches():
358
            switch = self.nodes[ switchDpid ]
359
            lg.info( '%s' % switch.name )
360
            switch.stop()
361
        lg.info( '\n' )
362
        lg.info( '*** Stopping controller\n' )
363
        for cnode in self.controllers.values():
364
            cnode.stop()
365
        lg.info( '*** Test complete\n' )
366

    
367
    def run( self, test, **params ):
368
        "Perform a complete start/test/stop cycle."
369
        self.start()
370
        lg.info( '*** Running test\n' )
371
        result = getattr( self, test )( **params )
372
        self.stop()
373
        return result
374

    
375
    @staticmethod
376
    def _parsePing( pingOutput ):
377
        "Parse ping output and return packets sent, received."
378
        r = r'(\d+) packets transmitted, (\d+) received'
379
        m = re.search( r, pingOutput )
380
        if m == None:
381
            lg.error( '*** Error: could not parse ping output: %s\n' %
382
                     pingOutput )
383
            exit( 1 )
384
        sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
385
        return sent, received
386

    
387
    def ping( self, hosts=None ):
388
        """Ping between all specified hosts.
389
           hosts: list of host DPIDs
390
           returns: ploss packet loss percentage"""
391
        #self.start()
392
        # check if running - only then, start?
393
        packets = 0
394
        lost = 0
395
        ploss = None
396
        if not hosts:
397
            hosts = self.topo.hosts()
398
        lg.info( '*** Ping: testing ping reachability\n' )
399
        for nodeDpid in hosts:
400
            node = self.nodes[ nodeDpid ]
401
            lg.info( '%s -> ' % node.name )
402
            for destDpid in hosts:
403
                dest = self.nodes[ destDpid ]
404
                if node != dest:
405
                    result = node.cmd( 'ping -c1 ' + dest.IP() )
406
                    sent, received = self._parsePing( result )
407
                    packets += sent
408
                    if received > sent:
409
                        lg.error( '*** Error: received too many packets' )
410
                        lg.error( '%s' % result )
411
                        node.cmdPrint( 'route' )
412
                        exit( 1 )
413
                    lost += sent - received
414
                    lg.info( ( '%s ' % dest.name ) if received else 'X ' )
415
            lg.info( '\n' )
416
            ploss = 100 * lost / packets
417
        lg.info( "*** Results: %i%% dropped (%d/%d lost)\n" %
418
                ( ploss, lost, packets ) )
419
        return ploss
420

    
421
    def pingAll( self ):
422
        """Ping between all hosts.
423
           returns: ploss packet loss percentage"""
424
        return self.ping()
425

    
426
    def pingPair( self ):
427
        """Ping between first two hosts, useful for testing.
428
           returns: ploss packet loss percentage"""
429
        hostsSorted = sorted( self.topo.hosts() )
430
        hosts = [ hostsSorted[ 0 ], hostsSorted[ 1 ] ]
431
        return self.ping( hosts=hosts )
432

    
433
    @staticmethod
434
    def _parseIperf( iperfOutput ):
435
        """Parse iperf output and return bandwidth.
436
           iperfOutput: string
437
           returns: result string"""
438
        r = r'([\d\.]+ \w+/sec)'
439
        m = re.search( r, iperfOutput )
440
        if m:
441
            return m.group( 1 )
442
        else:
443
            raise Exception( 'could not parse iperf output' )
444

    
445
    def iperf( self, hosts=None, l4Type='TCP', udpBw='10M',
446
              verbose=False ):
447
        """Run iperf between two hosts.
448
           hosts: list of host DPIDs; if None, uses opposite hosts
449
           l4Type: string, one of [ TCP, UDP ]
450
           verbose: verbose printing
451
           returns: results two-element array of server and client speeds"""
452
        if not hosts:
453
            hostsSorted = sorted( self.topo.hosts() )
454
            hosts = [ hostsSorted[ 0 ], hostsSorted[ -1 ] ]
455
        else:
456
            assert len( hosts ) == 2
457
        host0 = self.nodes[ hosts[ 0 ] ]
458
        host1 = self.nodes[ hosts[ 1 ] ]
459
        lg.info( '*** Iperf: testing ' + l4Type + ' bandwidth between ' )
460
        lg.info( "%s and %s\n" % ( host0.name, host1.name ) )
461
        host0.cmd( 'killall -9 iperf' )
462
        iperfArgs = 'iperf '
463
        bwArgs = ''
464
        if l4Type == 'UDP':
465
            iperfArgs += '-u '
466
            bwArgs = '-b ' + udpBw + ' '
467
        elif l4Type != 'TCP':
468
            raise Exception( 'Unexpected l4 type: %s' % l4Type )
469
        server = host0.cmd( iperfArgs + '-s &' )
470
        if verbose:
471
            lg.info( '%s\n' % server )
472
        client = host1.cmd( iperfArgs + '-t 5 -c ' + host0.IP() + ' ' +
473
                           bwArgs )
474
        if verbose:
475
            lg.info( '%s\n' % client )
476
        server = host0.cmd( 'killall -9 iperf' )
477
        if verbose:
478
            lg.info( '%s\n' % server )
479
        result = [ self._parseIperf( server ), self._parseIperf( client ) ]
480
        if l4Type == 'UDP':
481
            result.insert( 0, udpBw )
482
        lg.info( '*** Results: %s\n' % result )
483
        return result
484

    
485
    def iperfUdp( self, udpBw='10M' ):
486
        "Run iperf UDP test."
487
        return self.iperf( l4Type='UDP', udpBw=udpBw )
488

    
489
    def interact( self ):
490
        "Start network and run our simple CLI."
491
        self.start()
492
        result = CLI( self )
493
        self.stop()
494
        return result