92001b4965515c365cb20741e42f87a65a817c80
[vsorcdistro/.git] / mininet / mininet / net.py
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 import random
94
95 from time import sleep
96 from itertools import chain, groupby
97 from math import ceil
98
99 from mininet.cli import CLI
100 from mininet.log import info, error, debug, output, warn
101 from mininet.node import ( Node, Host, OVSKernelSwitch, DefaultController,
102                            Controller )
103 from mininet.nodelib import NAT
104 from mininet.link import Link, Intf
105 from mininet.util import ( quietRun, fixLimits, numCores, ensureRoot,
106                            macColonHex, ipStr, ipParse, netParse, ipAdd,
107                            waitListening, BaseString )
108 from mininet.term import cleanUpScreens, makeTerms
109
110 # Mininet version: should be consistent with README and LICENSE
111 VERSION = "2.3.0d5"
112
113 class Mininet( object ):
114     "Network emulation with hosts spawned in network namespaces."
115
116     def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host,
117                   controller=DefaultController, link=Link, intf=Intf,
118                   build=True, xterms=False, cleanup=False, ipBase='10.0.0.0/8',
119                   inNamespace=False,
120                   autoSetMacs=False, autoStaticArp=False, autoPinCpus=False,
121                   listenPort=None, waitConnected=False ):
122         """Create Mininet object.
123            topo: Topo (topology) object or None
124            switch: default Switch class
125            host: default Host class/constructor
126            controller: default Controller class/constructor
127            link: default Link class/constructor
128            intf: default Intf class/constructor
129            ipBase: base IP address for hosts,
130            build: build now from topo?
131            xterms: if build now, spawn xterms?
132            cleanup: if build now, cleanup before creating?
133            inNamespace: spawn switches and controller in net namespaces?
134            autoSetMacs: set MAC addrs automatically like IP addresses?
135            autoStaticArp: set all-pairs static MAC addrs?
136            autoPinCpus: pin hosts to (real) cores (requires CPULimitedHost)?
137            listenPort: base listening port to open; will be incremented for
138                each additional switch in the net if inNamespace=False"""
139         self.topo = topo
140         self.switch = switch
141         self.host = host
142         self.controller = controller
143         self.link = link
144         self.intf = intf
145         self.ipBase = ipBase
146         self.ipBaseNum, self.prefixLen = netParse( self.ipBase )
147         hostIP = ( 0xffffffff >> self.prefixLen ) & self.ipBaseNum
148         # Start for address allocation
149         self.nextIP = hostIP if hostIP > 0 else 1
150         self.inNamespace = inNamespace
151         self.xterms = xterms
152         self.cleanup = cleanup
153         self.autoSetMacs = autoSetMacs
154         self.autoStaticArp = autoStaticArp
155         self.autoPinCpus = autoPinCpus
156         self.numCores = numCores()
157         self.nextCore = 0  # next core for pinning hosts to CPUs
158         self.listenPort = listenPort
159         self.waitConn = waitConnected
160
161         self.hosts = []
162         self.switches = []
163         self.controllers = []
164         self.links = []
165
166         self.nameToNode = {}  # name to Node (Host/Switch) objects
167
168         self.terms = []  # list of spawned xterm processes
169
170         Mininet.init()  # Initialize Mininet if necessary
171
172         self.built = False
173         if topo and build:
174             self.build()
175
176     def waitConnected( self, timeout=None, delay=.5 ):
177         """wait for each switch to connect to a controller,
178            up to 5 seconds
179            timeout: time to wait, or None to wait indefinitely
180            delay: seconds to sleep per iteration
181            returns: True if all switches are connected"""
182         info( '*** Waiting for switches to connect\n' )
183         time = 0
184         remaining = list( self.switches )
185         while True:
186             for switch in tuple( remaining ):
187                 if switch.connected():
188                     info( '%s ' % switch )
189                     remaining.remove( switch )
190             if not remaining:
191                 info( '\n' )
192                 return True
193             if timeout is not None and time > timeout:
194                 break
195             sleep( delay )
196             time += delay
197         warn( 'Timed out after %d seconds\n' % time )
198         for switch in remaining:
199             if not switch.connected():
200                 warn( 'Warning: %s is not connected to a controller\n'
201                       % switch.name )
202             else:
203                 remaining.remove( switch )
204         return not remaining
205
206     def addHost( self, name, cls=None, **params ):
207         """Add host.
208            name: name of host to add
209            cls: custom host class/constructor (optional)
210            params: parameters for host
211            returns: added host"""
212         # Default IP and MAC addresses
213         defaults = { 'ip': ipAdd( self.nextIP,
214                                   ipBaseNum=self.ipBaseNum,
215                                   prefixLen=self.prefixLen ) +
216                                   '/%s' % self.prefixLen }
217         if self.autoSetMacs:
218             defaults[ 'mac' ] = macColonHex( self.nextIP )
219         if self.autoPinCpus:
220             defaults[ 'cores' ] = self.nextCore
221             self.nextCore = ( self.nextCore + 1 ) % self.numCores
222         self.nextIP += 1
223         defaults.update( params )
224         if not cls:
225             cls = self.host
226         h = cls( name, **defaults )
227         self.hosts.append( h )
228         self.nameToNode[ name ] = h
229         return h
230
231     def delNode( self, node, nodes=None):
232         """Delete node
233            node: node to delete
234            nodes: optional list to delete from (e.g. self.hosts)"""
235         if nodes is None:
236             nodes = ( self.hosts if node in self.hosts else
237                       ( self.switches if node in self.switches else
238                         ( self.controllers if node in self.controllers else
239                           [] ) ) )
240         node.stop( deleteIntfs=True )
241         node.terminate()
242         nodes.remove( node )
243         del self.nameToNode[ node.name ]
244
245     def delHost( self, host ):
246         "Delete a host"
247         self.delNode( host, nodes=self.hosts )
248
249     def addSwitch( self, name, cls=None, **params ):
250         """Add switch.
251            name: name of switch to add
252            cls: custom switch class/constructor (optional)
253            returns: added switch
254            side effect: increments listenPort ivar ."""
255         defaults = { 'listenPort': self.listenPort,
256                      'inNamespace': self.inNamespace }
257         defaults.update( params )
258         if not cls:
259             cls = self.switch
260         sw = cls( name, **defaults )
261         if not self.inNamespace and self.listenPort:
262             self.listenPort += 1
263         self.switches.append( sw )
264         self.nameToNode[ name ] = sw
265         return sw
266
267     def delSwitch( self, switch ):
268         "Delete a switch"
269         self.delNode( switch, nodes=self.switches )
270
271     def addController( self, name='c0', controller=None, **params ):
272         """Add controller.
273            controller: Controller class"""
274         # Get controller class
275         if not controller:
276             controller = self.controller
277         # Construct new controller if one is not given
278         if isinstance( name, Controller ):
279             controller_new = name
280             # Pylint thinks controller is a str()
281             # pylint: disable=maybe-no-member
282             name = controller_new.name
283             # pylint: enable=maybe-no-member
284         else:
285             controller_new = controller( name, **params )
286         # Add new controller to net
287         if controller_new:  # allow controller-less setups
288             self.controllers.append( controller_new )
289             self.nameToNode[ name ] = controller_new
290         return controller_new
291
292     def delController( self, controller ):
293         """Delete a controller
294            Warning - does not reconfigure switches, so they
295            may still attempt to connect to it!"""
296         self.delNode( controller )
297
298     def addNAT( self, name='nat0', connect=True, inNamespace=False,
299                 **params):
300         """Add a NAT to the Mininet network
301            name: name of NAT node
302            connect: switch to connect to | True (s1) | None
303            inNamespace: create in a network namespace
304            params: other NAT node params, notably:
305                ip: used as default gateway address"""
306         nat = self.addHost( name, cls=NAT, inNamespace=inNamespace,
307                             subnet=self.ipBase, **params )
308         # find first switch and create link
309         if connect:
310             if not isinstance( connect, Node ):
311                 # Use first switch if not specified
312                 connect = self.switches[ 0 ]
313             # Connect the nat to the switch
314             self.addLink( nat, connect )
315             # Set the default route on hosts
316             natIP = nat.params[ 'ip' ].split('/')[ 0 ]
317             for host in self.hosts:
318                 if host.inNamespace:
319                     host.setDefaultRoute( 'via %s' % natIP )
320         return nat
321
322     # BL: We now have four ways to look up nodes
323     # This may (should?) be cleaned up in the future.
324     def getNodeByName( self, *args ):
325         "Return node(s) with given name(s)"
326         if len( args ) == 1:
327             return self.nameToNode[ args[ 0 ] ]
328         return [ self.nameToNode[ n ] for n in args ]
329
330     def get( self, *args ):
331         "Convenience alias for getNodeByName"
332         return self.getNodeByName( *args )
333
334     # Even more convenient syntax for node lookup and iteration
335     def __getitem__( self, key ):
336         "net[ name ] operator: Return node with given name"
337         return self.nameToNode[ key ]
338
339     def __delitem__( self, key ):
340         "del net[ name ] operator - delete node with given name"
341         self.delNode( self.nameToNode[ key ] )
342
343     def __iter__( self ):
344         "return iterator over node names"
345         for node in chain( self.hosts, self.switches, self.controllers ):
346             yield node.name
347
348     def __len__( self ):
349         "returns number of nodes in net"
350         return ( len( self.hosts ) + len( self.switches ) +
351                  len( self.controllers ) )
352
353     def __contains__( self, item ):
354         "returns True if net contains named node"
355         return item in self.nameToNode
356
357     def keys( self ):
358         "return a list of all node names or net's keys"
359         return list( self )
360
361     def values( self ):
362         "return a list of all nodes or net's values"
363         return [ self[name] for name in self ]
364
365     def items( self ):
366         "return (key,value) tuple list for every node in net"
367         return zip( self.keys(), self.values() )
368
369     @staticmethod
370     def randMac():
371         "Return a random, non-multicast MAC address"
372         return macColonHex( random.randint(1, 2**48 - 1) & 0xfeffffffffff |
373                             0x020000000000 )
374
375     def addLink( self, node1, node2, port1=None, port2=None,
376                  cls=None, **params ):
377         """"Add a link from node1 to node2
378             node1: source node (or name)
379             node2: dest node (or name)
380             port1: source port (optional)
381             port2: dest port (optional)
382             cls: link class (optional)
383             params: additional link params (optional)
384             returns: link object"""
385         # Accept node objects or names
386         node1 = node1 if not isinstance( node1, BaseString ) else self[ node1 ]
387         node2 = node2 if not isinstance( node2, BaseString ) else self[ node2 ]
388         options = dict( params )
389         # Port is optional
390         if port1 is not None:
391             options.setdefault( 'port1', port1 )
392         if port2 is not None:
393             options.setdefault( 'port2', port2 )
394         if self.intf is not None:
395             options.setdefault( 'intf', self.intf )
396         # Set default MAC - this should probably be in Link
397         options.setdefault( 'addr1', self.randMac() )
398         options.setdefault( 'addr2', self.randMac() )
399         cls = self.link if cls is None else cls
400         link = cls( node1, node2, **options )
401         self.links.append( link )
402         return link
403
404     def delLink( self, link ):
405         "Remove a link from this network"
406         link.delete()
407         self.links.remove( link )
408
409     def linksBetween( self, node1, node2 ):
410         "Return Links between node1 and node2"
411         return [ link for link in self.links
412                  if ( node1, node2 ) in (
413                     ( link.intf1.node, link.intf2.node ),
414                     ( link.intf2.node, link.intf1.node ) ) ]
415
416     def delLinkBetween( self, node1, node2, index=0, allLinks=False ):
417         """Delete link(s) between node1 and node2
418            index: index of link to delete if multiple links (0)
419            allLinks: ignore index and delete all such links (False)
420            returns: deleted link(s)"""
421         links = self.linksBetween( node1, node2 )
422         if not allLinks:
423             links = [ links[ index ] ]
424         for link in links:
425             self.delLink( link )
426         return links
427
428     def configHosts( self ):
429         "Configure a set of hosts."
430         for host in self.hosts:
431             info( host.name + ' ' )
432             intf = host.defaultIntf()
433             if intf:
434                 host.configDefault()
435             else:
436                 # Don't configure nonexistent intf
437                 host.configDefault( ip=None, mac=None )
438             # You're low priority, dude!
439             # BL: do we want to do this here or not?
440             # May not make sense if we have CPU lmiting...
441             # quietRun( 'renice +18 -p ' + repr( host.pid ) )
442             # This may not be the right place to do this, but
443             # it needs to be done somewhere.
444         info( '\n' )
445
446     def buildFromTopo( self, topo=None ):
447         """Build mininet from a topology object
448            At the end of this function, everything should be connected
449            and up."""
450
451         # Possibly we should clean up here and/or validate
452         # the topo
453         if self.cleanup:
454             pass
455
456         info( '*** Creating network...(toy en mininet.net buildfromtopo)\n' )
457
458         if not self.controllers and self.controller:
459             # Add a default controller
460             info( '*** Adding controller\n' )
461             classes = self.controller
462             if not isinstance( classes, list ):
463                 classes = [ classes ]
464             for i, cls in enumerate( classes ):
465                 # Allow Controller objects because nobody understands partial()
466                 if isinstance( cls, Controller ):
467                     self.addController( cls )
468                 else:
469                     self.addController( 'c%d' % i, cls )
470
471         info( '*** Adding hosts:\n' )
472         for hostName in topo.hosts():
473             self.addHost( hostName, **topo.nodeInfo( hostName ) )
474             info( hostName + ' ' )
475             info('dentro de adding host\n')
476         info('sali de adding host')
477
478         info( '\n*** Adding switches:\n' )
479         for switchName in topo.switches():
480             # A bit ugly: add batch parameter if appropriate
481             params = topo.nodeInfo( switchName)
482             cls = params.get( 'cls', self.switch )
483             if hasattr( cls, 'batchStartup' ):
484                 params.setdefault( 'batch', True )
485             self.addSwitch( switchName, **params )
486             info( switchName + ' ' )
487
488         info( '\n*** Adding links:\n' )
489         for srcName, dstName, params in topo.links(
490                 sort=True, withInfo=True ):
491             self.addLink( **params )
492             info( '(%s, %s) ' % ( srcName, dstName ) )
493
494         info( '\n' )
495
496     def configureControlNetwork( self ):
497         "Control net config hook: override in subclass"
498         raise Exception( 'configureControlNetwork: '
499                          'should be overriden in subclass', self )
500
501     def build( self ):
502         "Build mininet."
503         if self.topo:
504             self.buildFromTopo( self.topo )
505         if self.inNamespace:
506             self.configureControlNetwork()
507         info( '*** Configuring hosts\n' )
508         self.configHosts()
509         if self.xterms:
510             self.startTerms()
511         if self.autoStaticArp:
512             self.staticArp()
513         self.built = True
514
515     def startTerms( self ):
516         "Start a terminal for each node."
517         if 'DISPLAY' not in os.environ:
518             error( "Error starting terms: Cannot connect to display\n" )
519             return
520         info( "*** Running terms on %s\n" % os.environ[ 'DISPLAY' ] )
521         cleanUpScreens()
522         self.terms += makeTerms( self.controllers, 'controller' )
523         self.terms += makeTerms( self.switches, 'switch' )
524         self.terms += makeTerms( self.hosts, 'host' )
525
526     def stopXterms( self ):
527         "Kill each xterm."
528         for term in self.terms:
529             os.kill( term.pid, signal.SIGKILL )
530         cleanUpScreens()
531
532     def staticArp( self ):
533         "Add all-pairs ARP entries to remove the need to handle broadcast."
534         for src in self.hosts:
535             for dst in self.hosts:
536                 if src != dst:
537                     src.setARP( ip=dst.IP(), mac=dst.MAC() )
538
539     def start( self ):
540         "Start controller and switches."
541         if not self.built:
542             self.build()
543         info( '*** Starting controller\n' )
544         for controller in self.controllers:
545             info( controller.name + ' ')
546             controller.start()
547         info( '\n' )
548         info( '*** Starting %s switches\n' % len( self.switches ) )
549         for switch in self.switches:
550             info( switch.name + ' ')
551             switch.start( self.controllers )
552         started = {}
553         for swclass, switches in groupby(
554                 sorted( self.switches,
555                         key=lambda s: str( type( s ) ) ), type ):
556             switches = tuple( switches )
557             if hasattr( swclass, 'batchStartup' ):
558                 success = swclass.batchStartup( switches )
559                 started.update( { s: s for s in success } )
560         info( '\n' )
561         if self.waitConn:
562             self.waitConnected()
563
564     def stop( self ):
565         "Stop the controller(s), switches and hosts"
566         info( '*** Stopping %i controllers\n' % len( self.controllers ) )
567         for controller in self.controllers:
568             info( controller.name + ' ' )
569             controller.stop()
570         info( '\n' )
571         if self.terms:
572             info( '*** Stopping %i terms\n' % len( self.terms ) )
573             self.stopXterms()
574         info( '*** Stopping %i links\n' % len( self.links ) )
575         for link in self.links:
576             info( '.' )
577             link.stop()
578         info( '\n' )
579         info( '*** Stopping %i switches\n' % len( self.switches ) )
580         stopped = {}
581         for swclass, switches in groupby(
582                 sorted( self.switches,
583                         key=lambda s: str( type( s ) ) ), type ):
584             switches = tuple( switches )
585             if hasattr( swclass, 'batchShutdown' ):
586                 success = swclass.batchShutdown( switches )
587                 stopped.update( { s: s for s in success } )
588         for switch in self.switches:
589             info( switch.name + ' ' )
590             if switch not in stopped:
591                 switch.stop()
592             switch.terminate()
593         info( '\n' )
594         info( '*** Stopping %i hosts\n' % len( self.hosts ) )
595         for host in self.hosts:
596             info( host.name + ' ' )
597             host.terminate()
598         info( '\n*** Done\n' )
599
600     def run( self, test, *args, **kwargs ):
601         "Perform a complete start/test/stop cycle."
602         self.start()
603         info( '*** Running test\n' )
604         result = test( *args, **kwargs )
605         self.stop()
606         return result
607
608     def monitor( self, hosts=None, timeoutms=-1 ):
609         """Monitor a set of hosts (or all hosts by default),
610            and return their output, a line at a time.
611            hosts: (optional) set of hosts to monitor
612            timeoutms: (optional) timeout value in ms
613            returns: iterator which returns host, line"""
614         if hosts is None:
615             hosts = self.hosts
616         poller = select.poll()
617         h1 = hosts[ 0 ]  # so we can call class method fdToNode
618         for host in hosts:
619             poller.register( host.stdout )
620         while True:
621             ready = poller.poll( timeoutms )
622             for fd, event in ready:
623                 host = h1.fdToNode( fd )
624                 if event & select.POLLIN:
625                     line = host.readline()
626                     if line is not None:
627                         yield host, line
628             # Return if non-blocking
629             if not ready and timeoutms >= 0:
630                 yield None, None
631
632     # XXX These test methods should be moved out of this class.
633     # Probably we should create a tests.py for them
634
635     @staticmethod
636     def _parsePing( pingOutput ):
637         "Parse ping output and return packets sent, received."
638         # Check for downed link
639         if 'connect: Network is unreachable' in pingOutput:
640             return 1, 0
641         r = r'(\d+) packets transmitted, (\d+)( packets)? received'
642         m = re.search( r, pingOutput )
643         if m is None:
644             error( '*** Error: could not parse ping output: %s\n' %
645                    pingOutput )
646             return 1, 0
647         sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
648         return sent, received
649
650     def ping( self, hosts=None, timeout=None ):
651         """Ping between all specified hosts.
652            hosts: list of hosts
653            timeout: time to wait for a response, as string
654            returns: ploss packet loss percentage"""
655         # should we check if running?
656         packets = 0
657         lost = 0
658         ploss = None
659         if not hosts:
660             hosts = self.hosts
661             output( '*** Ping: testing ping reachability\n' )
662         for node in hosts:
663             output( '%s -> ' % node.name )
664             for dest in hosts:
665                 if node != dest:
666                     opts = ''
667                     if timeout:
668                         opts = '-W %s' % timeout
669                     if dest.intfs:
670                         result = node.cmd( 'ping -c1 %s %s' %
671                                            (opts, dest.IP()) )
672                         sent, received = self._parsePing( result )
673                     else:
674                         sent, received = 0, 0
675                     packets += sent
676                     if received > sent:
677                         error( '*** Error: received too many packets' )
678                         error( '%s' % result )
679                         node.cmdPrint( 'route' )
680                         exit( 1 )
681                     lost += sent - received
682                     output( ( '%s ' % dest.name ) if received else 'X ' )
683             output( '\n' )
684         if packets > 0:
685             ploss = 100.0 * lost / packets
686             received = packets - lost
687             output( "*** Results: %i%% dropped (%d/%d received)\n" %
688                     ( ploss, received, packets ) )
689         else:
690             ploss = 0
691             output( "*** Warning: No packets sent\n" )
692         return ploss
693
694     @staticmethod
695     def _parsePingFull( pingOutput ):
696         "Parse ping output and return all data."
697         errorTuple = (1, 0, 0, 0, 0, 0)
698         # Check for downed link
699         r = r'[uU]nreachable'
700         m = re.search( r, pingOutput )
701         if m is not None:
702             return errorTuple
703         r = r'(\d+) packets transmitted, (\d+)( packets)? received'
704         m = re.search( r, pingOutput )
705         if m is None:
706             error( '*** Error: could not parse ping output: %s\n' %
707                    pingOutput )
708             return errorTuple
709         sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
710         r = r'rtt min/avg/max/mdev = '
711         r += r'(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+) ms'
712         m = re.search( r, pingOutput )
713         if m is None:
714             if received == 0:
715                 return errorTuple
716             error( '*** Error: could not parse ping output: %s\n' %
717                    pingOutput )
718             return errorTuple
719         rttmin = float( m.group( 1 ) )
720         rttavg = float( m.group( 2 ) )
721         rttmax = float( m.group( 3 ) )
722         rttdev = float( m.group( 4 ) )
723         return sent, received, rttmin, rttavg, rttmax, rttdev
724
725     def pingFull( self, hosts=None, timeout=None ):
726         """Ping between all specified hosts and return all data.
727            hosts: list of hosts
728            timeout: time to wait for a response, as string
729            returns: all ping data; see function body."""
730         # should we check if running?
731         # Each value is a tuple: (src, dsd, [all ping outputs])
732         all_outputs = []
733         if not hosts:
734             hosts = self.hosts
735             output( '*** Ping: testing ping reachability\n' )
736         for node in hosts:
737             output( '%s -> ' % node.name )
738             for dest in hosts:
739                 if node != dest:
740                     opts = ''
741                     if timeout:
742                         opts = '-W %s' % timeout
743                     result = node.cmd( 'ping -c1 %s %s' % (opts, dest.IP()) )
744                     outputs = self._parsePingFull( result )
745                     sent, received, rttmin, rttavg, rttmax, rttdev = outputs
746                     all_outputs.append( (node, dest, outputs) )
747                     output( ( '%s ' % dest.name ) if received else 'X ' )
748             output( '\n' )
749         output( "*** Results: \n" )
750         for outputs in all_outputs:
751             src, dest, ping_outputs = outputs
752             sent, received, rttmin, rttavg, rttmax, rttdev = ping_outputs
753             output( " %s->%s: %s/%s, " % (src, dest, sent, received ) )
754             output( "rtt min/avg/max/mdev %0.3f/%0.3f/%0.3f/%0.3f ms\n" %
755                     (rttmin, rttavg, rttmax, rttdev) )
756         return all_outputs
757
758     def pingAll( self, timeout=None ):
759         """Ping between all hosts.
760            returns: ploss packet loss percentage"""
761         return self.ping( timeout=timeout )
762
763     def pingPair( self ):
764         """Ping between first two hosts, useful for testing.
765            returns: ploss packet loss percentage"""
766         hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
767         return self.ping( hosts=hosts )
768
769     def pingAllFull( self ):
770         """Ping between all hosts.
771            returns: ploss packet loss percentage"""
772         return self.pingFull()
773
774     def pingPairFull( self ):
775         """Ping between first two hosts, useful for testing.
776            returns: ploss packet loss percentage"""
777         hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
778         return self.pingFull( hosts=hosts )
779
780     @staticmethod
781     def _parseIperf( iperfOutput ):
782         """Parse iperf output and return bandwidth.
783            iperfOutput: string
784            returns: result string"""
785         r = r'([\d\.]+ \w+/sec)'
786         m = re.findall( r, iperfOutput )
787         if m:
788             return m[-1]
789         else:
790             # was: raise Exception(...)
791             error( 'could not parse iperf output: ' + iperfOutput )
792             return ''
793
794     # XXX This should be cleaned up
795
796     def iperf( self, hosts=None, l4Type='TCP', udpBw='10M', fmt=None,
797                seconds=5, port=5001):
798         """Run iperf between two hosts.
799            hosts: list of hosts; if None, uses first and last hosts
800            l4Type: string, one of [ TCP, UDP ]
801            udpBw: bandwidth target for UDP test
802            fmt: iperf format argument if any
803            seconds: iperf time to transmit
804            port: iperf port
805            returns: two-element array of [ server, client ] speeds
806            note: send() is buffered, so client rate can be much higher than
807            the actual transmission rate; on an unloaded system, server
808            rate should be much closer to the actual receive rate"""
809         hosts = hosts or [ self.hosts[ 0 ], self.hosts[ -1 ] ]
810         assert len( hosts ) == 2
811         client, server = hosts
812         output( '*** Iperf: testing', l4Type, 'bandwidth between',
813                 client, 'and', server, '\n' )
814         server.cmd( 'killall -9 iperf' )
815         iperfArgs = 'iperf -p %d ' % port
816         bwArgs = ''
817         if l4Type == 'UDP':
818             iperfArgs += '-u '
819             bwArgs = '-b ' + udpBw + ' '
820         elif l4Type != 'TCP':
821             raise Exception( 'Unexpected l4 type: %s' % l4Type )
822         if fmt:
823             iperfArgs += '-f %s ' % fmt
824         server.sendCmd( iperfArgs + '-s' )
825         if l4Type == 'TCP':
826             if not waitListening( client, server.IP(), port ):
827                 raise Exception( 'Could not connect to iperf on port %d'
828                                  % port )
829         cliout = client.cmd( iperfArgs + '-t %d -c ' % seconds +
830                              server.IP() + ' ' + bwArgs )
831         debug( 'Client output: %s\n' % cliout )
832         servout = ''
833         # We want the last *b/sec from the iperf server output
834         # for TCP, there are two of them because of waitListening
835         count = 2 if l4Type == 'TCP' else 1
836         while len( re.findall( '/sec', servout ) ) < count:
837             servout += server.monitor( timeoutms=5000 )
838         server.sendInt()
839         servout += server.waitOutput()
840         debug( 'Server output: %s\n' % servout )
841         result = [ self._parseIperf( servout ), self._parseIperf( cliout ) ]
842         if l4Type == 'UDP':
843             result.insert( 0, udpBw )
844         output( '*** Results: %s\n' % result )
845         return result
846
847     def runCpuLimitTest( self, cpu, duration=5 ):
848         """run CPU limit test with 'while true' processes.
849         cpu: desired CPU fraction of each host
850         duration: test duration in seconds (integer)
851         returns a single list of measured CPU fractions as floats.
852         """
853         pct = cpu * 100
854         info( '*** Testing CPU %.0f%% bandwidth limit\n' % pct )
855         hosts = self.hosts
856         cores = int( quietRun( 'nproc' ) )
857         # number of processes to run a while loop on per host
858         num_procs = int( ceil( cores * cpu ) )
859         pids = {}
860         for h in hosts:
861             pids[ h ] = []
862             for _core in range( num_procs ):
863                 h.cmd( 'while true; do a=1; done &' )
864                 pids[ h ].append( h.cmd( 'echo $!' ).strip() )
865         outputs = {}
866         time = {}
867         # get the initial cpu time for each host
868         for host in hosts:
869             outputs[ host ] = []
870             with open( '/sys/fs/cgroup/cpuacct/%s/cpuacct.usage' %
871                        host, 'r' ) as f:
872                 time[ host ] = float( f.read() )
873         for _ in range( duration ):
874             sleep( 1 )
875             for host in hosts:
876                 with open( '/sys/fs/cgroup/cpuacct/%s/cpuacct.usage' %
877                            host, 'r' ) as f:
878                     readTime = float( f.read() )
879                 outputs[ host ].append( ( ( readTime - time[ host ] )
880                                         / 1000000000 ) / cores * 100 )
881                 time[ host ] = readTime
882         for h, pids in pids.items():
883             for pid in pids:
884                 h.cmd( 'kill -9 %s' % pid )
885         cpu_fractions = []
886         for _host, outputs in outputs.items():
887             for pct in outputs:
888                 cpu_fractions.append( pct )
889         output( '*** Results: %s\n' % cpu_fractions )
890         return cpu_fractions
891
892     # BL: I think this can be rewritten now that we have
893     # a real link class.
894     def configLinkStatus( self, src, dst, status ):
895         """Change status of src <-> dst links.
896            src: node name
897            dst: node name
898            status: string {up, down}"""
899         if src not in self.nameToNode:
900             error( 'src not in network: %s\n' % src )
901         elif dst not in self.nameToNode:
902             error( 'dst not in network: %s\n' % dst )
903         else:
904             src = self.nameToNode[ src ]
905             dst = self.nameToNode[ dst ]
906             connections = src.connectionsTo( dst )
907             if len( connections ) == 0:
908                 error( 'src and dst not connected: %s %s\n' % ( src, dst) )
909             for srcIntf, dstIntf in connections:
910                 result = srcIntf.ifconfig( status )
911                 if result:
912                     error( 'link src status change failed: %s\n' % result )
913                 result = dstIntf.ifconfig( status )
914                 if result:
915                     error( 'link dst status change failed: %s\n' % result )
916
917     def interact( self ):
918         "Start network and run our simple CLI."
919         self.start()
920         result = CLI( self )
921         self.stop()
922         return result
923
924     inited = False
925
926     @classmethod
927     def init( cls ):
928         "Initialize Mininet"
929         if cls.inited:
930             return
931         ensureRoot()
932         fixLimits()
933         cls.inited = True
934
935
936 class MininetWithControlNet( Mininet ):
937
938     """Control network support:
939
940        Create an explicit control network. Currently this is only
941        used/usable with the user datapath.
942
943        Notes:
944
945        1. If the controller and switches are in the same (e.g. root)
946           namespace, they can just use the loopback connection.
947
948        2. If we can get unix domain sockets to work, we can use them
949           instead of an explicit control network.
950
951        3. Instead of routing, we could bridge or use 'in-band' control.
952
953        4. Even if we dispense with this in general, it could still be
954           useful for people who wish to simulate a separate control
955           network (since real networks may need one!)
956
957        5. Basically nobody ever used this code, so it has been moved
958           into its own class.
959
960        6. Ultimately we may wish to extend this to allow us to create a
961           control network which every node's control interface is
962           attached to."""
963
964     def configureControlNetwork( self ):
965         "Configure control network."
966         self.configureRoutedControlNetwork()
967
968     # We still need to figure out the right way to pass
969     # in the control network location.
970
971     def configureRoutedControlNetwork( self, ip='192.168.123.1',
972                                        prefixLen=16 ):
973         """Configure a routed control network on controller and switches.
974            For use with the user datapath only right now."""
975         controller = self.controllers[ 0 ]
976         info( controller.name + ' <->' )
977         cip = ip
978         snum = ipParse( ip )
979         for switch in self.switches:
980             info( ' ' + switch.name )
981             link = self.link( switch, controller, port1=0 )
982             sintf, cintf = link.intf1, link.intf2
983             switch.controlIntf = sintf
984             snum += 1
985             while snum & 0xff in [ 0, 255 ]:
986                 snum += 1
987             sip = ipStr( snum )
988             cintf.setIP( cip, prefixLen )
989             sintf.setIP( sip, prefixLen )
990             controller.setHostRoute( sip, cintf )
991             switch.setHostRoute( cip, sintf )
992         info( '\n' )
993         info( '*** Testing control network\n' )
994         while not cintf.isUp():
995             info( '*** Waiting for', cintf, 'to come up\n' )
996             sleep( 1 )
997         for switch in self.switches:
998             while not sintf.isUp():
999                 info( '*** Waiting for', sintf, 'to come up\n' )
1000                 sleep( 1 )
1001             if self.ping( hosts=[ switch, controller ] ) != 0:
1002                 error( '*** Error: control network test failed\n' )
1003                 exit( 1 )
1004         info( '\n' )