Statistics
| Branch: | Tag: | Revision:

mininet / mininet / net.py @ 03dd914e

History | View | Annotate | Download (22.2 KB)

1 281f6e59 Bob Lantz
"""
2

3 a6bcad8f Bob Lantz
    Mininet: A simple networking testbed for OpenFlow/SDN!
4 281f6e59 Bob Lantz

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

8 08cef003 Bob Lantz
Mininet creates scalable OpenFlow test networks by using
9 723d068c Brandon Heller
process-based virtualization and network namespaces.
10 98d4f189 Bob Lantz

11 eddef947 Bob Lantz
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 98d4f189 Bob Lantz

15
Each host has:
16 7d4b7b7f Bob Lantz

17 281f6e59 Bob Lantz
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 723d068c Brandon Heller

21 eddef947 Bob Lantz
Hosts have a network interface which is configured via ifconfig/ip
22 8f20b95d Brandon Heller
link/etc.
23 98d4f189 Bob Lantz

24 55dd9368 Bob Lantz
This version supports both the kernel and user space datapaths
25 1bda2d21 Bob Lantz
from the OpenFlow reference implementation (openflowswitch.org)
26
as well as OpenVSwitch (openvswitch.org.)
27 08cef003 Bob Lantz

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

31 281f6e59 Bob Lantz
Kernel OpenFlow datapaths are instantiated using dpctl(8), and are
32 55dd9368 Bob Lantz
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 eddef947 Bob Lantz
controller via the loopback interface.
35 98d4f189 Bob Lantz

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

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

45 31b43002 Bob Lantz
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 281f6e59 Bob Lantz

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

57 80be5642 Bob Lantz
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 bbe5f8a3 Bob Lantz
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 31b43002 Bob Lantz
Currently we wrap the entire network in a 'mininet' object, which
68
constructs a simulated network based on a network topology created
69 80be5642 Bob Lantz
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 31b43002 Bob Lantz
automatically setting MAC addresses, populating the ARP table, or
73 15b482e3 Brandon Heller
even running a set of terminals to allow direct interaction with nodes.
74 31b43002 Bob Lantz

75 80be5642 Bob Lantz
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 31b43002 Bob Lantz

79
Once the network is up and running, test code can easily get access
80 80be5642 Bob Lantz
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 31b43002 Bob Lantz

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

87
"""
88
89 8b5062a3 Brandon Heller
import os
90
import re
91 ec7b211c Bob Lantz
import select
92 8a034f4f Brandon Heller
import signal
93 98d4f189 Bob Lantz
from time import sleep
94
95 496b5f9e Bob Lantz
from mininet.cli import CLI
96 cdeaca86 Brandon Heller
from mininet.log import info, error, debug, output
97 eaf5888a Bob Lantz
from mininet.node import Host, UserSwitch, OVSKernelSwitch, Controller
98 086ef80e Bob Lantz
from mininet.node import ControllerParams
99 a6bcad8f Bob Lantz
from mininet.link import Link
100 8b5062a3 Brandon Heller
from mininet.util import quietRun, fixLimits
101 d44a5843 Bob Lantz
from mininet.util import createLink, macColonHex, ipStr, ipParse
102 15b482e3 Brandon Heller
from mininet.term import cleanUpScreens, makeTerms
103 8b5062a3 Brandon Heller
104 80a8fa62 Bob Lantz
class Mininet( object ):
105
    "Network emulation with hosts spawned in network namespaces."
106
107 eaf5888a Bob Lantz
    def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host,
108 a6bcad8f Bob Lantz
                 controller=Controller, link=Link,
109 086ef80e Bob Lantz
                 cparams=ControllerParams( '10.0.0.0', 8 ),
110 80a8fa62 Bob Lantz
                 build=True, xterms=False, cleanup=False,
111
                 inNamespace=False,
112 ccca871a Brandon Heller
                 autoSetMacs=False, autoStaticArp=False, listenPort=None ):
113 80a8fa62 Bob Lantz
        """Create Mininet object.
114 019bff82 Bob Lantz
           topo: Topo (topology) object or None
115 a6bcad8f Bob Lantz
           switch: default Switch class
116
           host: default Host class/constructor
117
           controller: default Controller class/constructor
118
           link: default Link class/constructor
119 80a8fa62 Bob Lantz
           cparams: ControllerParams object
120 956bf4f2 Brandon Heller
           build: build now from topo?
121 80a8fa62 Bob Lantz
           xterms: if build now, spawn xterms?
122
           cleanup: if build now, cleanup before creating?
123
           inNamespace: spawn switches and controller in net namespaces?
124 d44a5843 Bob Lantz
           autoSetMacs: set MAC addrs from topo?
125 ccca871a Brandon Heller
           autoStaticArp: set all-pairs static MAC addrs?
126
           listenPort: base listening port to open; will be incremented for
127
               each additional switch in the net if inNamespace=False"""
128 8b5062a3 Brandon Heller
        self.switch = switch
129
        self.host = host
130
        self.controller = controller
131 a6bcad8f Bob Lantz
        self.link = link
132 8b5062a3 Brandon Heller
        self.cparams = cparams
133 019bff82 Bob Lantz
        self.topo = topo
134 80a8fa62 Bob Lantz
        self.inNamespace = inNamespace
135 376bcba4 Brandon Heller
        self.xterms = xterms
136
        self.cleanup = cleanup
137 80a8fa62 Bob Lantz
        self.autoSetMacs = autoSetMacs
138
        self.autoStaticArp = autoStaticArp
139 ccca871a Brandon Heller
        self.listenPort = listenPort
140 8b5062a3 Brandon Heller
141 019bff82 Bob Lantz
        self.hosts = []
142
        self.switches = []
143
        self.controllers = []
144 cf6f6704 Bob Lantz
        self.nameToNode = {}  # name to Node (Host/Switch) objects
145
        self.idToNode = {}  # dpid to Node (Host/Switch) objects
146
        self.dps = 0  # number of created kernel datapaths
147
        self.terms = []  # list of spawned xterm processes
148 8a034f4f Brandon Heller
149 724f1144 Bob Lantz
        init()
150 b055728f Brandon Heller
        switch.setup()
151
152 99c035d9 Bob Lantz
        self.built = False
153
        if topo and build:
154 d44a5843 Bob Lantz
            self.build()
155 8b5062a3 Brandon Heller
156 a6bcad8f Bob Lantz
    # BL Note:
157
    # The specific items for host/switch/etc. should probably be
158
    # handled in the node classes rather than here!!
159
160
    def addHost( self, name, mac=None, ip=None, host=None, **params ):
161 80a8fa62 Bob Lantz
        """Add host.
162 019bff82 Bob Lantz
           name: name of host to add
163 086ef80e Bob Lantz
           mac: default MAC address for intf 0
164
           ip: default IP address for intf 0
165 019bff82 Bob Lantz
           returns: added host"""
166 a6bcad8f Bob Lantz
        if not host:
167
            host = self.host
168
        defaults = { 'defaultMAC': mac, 'defaultIP': ip }
169
        defaults.update( params )
170
        h = host( name, **defaults)
171
        self.hosts.append( h )
172
        self.nameToNode[ name ] = h
173
        return h
174
175
    def addSwitch( self, name, switch=None, **params ):
176 80a8fa62 Bob Lantz
        """Add switch.
177 019bff82 Bob Lantz
           name: name of switch to add
178 ccca871a Brandon Heller
           returns: added switch
179 a6bcad8f Bob Lantz
           side effect: increments listenPort and dps ivars."""
180
        defaults = { 'listenPort': self.listenPort, 
181
                     'inNamespace': self.inNamespace }
182
        if not switch:
183
            switch = self.switch
184
        if switch != UserSwitch:
185
            defaults[ 'dps' ] = self.dps
186
        defaults.update( params )
187
        sw = self.switch( name, **defaults )
188 0a9358c9 Brandon Heller
        if not self.inNamespace and self.listenPort:
189 ccca871a Brandon Heller
            self.listenPort += 1
190 d44a5843 Bob Lantz
        self.dps += 1
191 019bff82 Bob Lantz
        self.switches.append( sw )
192
        self.nameToNode[ name ] = sw
193
        return sw
194 80a8fa62 Bob Lantz
195 a6bcad8f Bob Lantz
    def addController( self, name='c0', controller=None, **params ):
196 80a8fa62 Bob Lantz
        """Add controller.
197 e30f2c99 Bob Lantz
           controller: Controller class"""
198
        if not controller:
199
            controller = self.controller
200 a6bcad8f Bob Lantz
        controller_new = controller( name, **params )
201 f32a5468 Brandon Heller
        if controller_new:  # allow controller-less setups
202
            self.controllers.append( controller_new )
203 82b72072 Bob Lantz
            self.nameToNode[ name ] = controller_new
204 eaf5888a Bob Lantz
        return controller_new
205 8b5062a3 Brandon Heller
206
    # Control network support:
207
    #
208
    # Create an explicit control network. Currently this is only
209
    # used by the user datapath configuration.
210
    #
211
    # Notes:
212
    #
213 80be5642 Bob Lantz
    # 1. If the controller and switches are in the same (e.g. root)
214 8b5062a3 Brandon Heller
    #    namespace, they can just use the loopback connection.
215
    #
216
    # 2. If we can get unix domain sockets to work, we can use them
217
    #    instead of an explicit control network.
218
    #
219
    # 3. Instead of routing, we could bridge or use 'in-band' control.
220
    #
221
    # 4. Even if we dispense with this in general, it could still be
222
    #    useful for people who wish to simulate a separate control
223 019bff82 Bob Lantz
    #    network (since real networks may need one!)
224 a6bcad8f Bob Lantz
    #
225
    # 5. Basically nobody ever uses this method, so perhaps it should be moved
226
    #    out of this core class.
227 8b5062a3 Brandon Heller
228 d44a5843 Bob Lantz
    def configureControlNetwork( self ):
229 80a8fa62 Bob Lantz
        "Configure control network."
230 d44a5843 Bob Lantz
        self.configureRoutedControlNetwork()
231
232
    # We still need to figure out the right way to pass
233
    # in the control network location.
234 8b5062a3 Brandon Heller
235 d44a5843 Bob Lantz
    def configureRoutedControlNetwork( self, ip='192.168.123.1',
236
        prefixLen=16 ):
237 80a8fa62 Bob Lantz
        """Configure a routed control network on controller and switches.
238 a6bcad8f Bob Lantz
           For use with the user datapath only right now."""
239 019bff82 Bob Lantz
        controller = self.controllers[ 0 ]
240 d44a5843 Bob Lantz
        info( controller.name + ' <->' )
241
        cip = ip
242
        snum = ipParse( ip )
243 019bff82 Bob Lantz
        for switch in self.switches:
244 d44a5843 Bob Lantz
            info( ' ' + switch.name )
245
            sintf, cintf = createLink( switch, controller )
246
            snum += 1
247
            while snum & 0xff in [ 0, 255 ]:
248
                snum += 1
249
            sip = ipStr( snum )
250
            controller.setIP( cintf, cip, prefixLen )
251
            switch.setIP( sintf, sip, prefixLen )
252 80a8fa62 Bob Lantz
            controller.setHostRoute( sip, cintf )
253 d44a5843 Bob Lantz
            switch.setHostRoute( cip, sintf )
254 d5886525 Bob Lantz
        info( '\n' )
255
        info( '*** Testing control network\n' )
256 d44a5843 Bob Lantz
        while not controller.intfIsUp( cintf ):
257
            info( '*** Waiting for', cintf, 'to come up\n' )
258 80a8fa62 Bob Lantz
            sleep( 1 )
259 019bff82 Bob Lantz
        for switch in self.switches:
260 d44a5843 Bob Lantz
            while not switch.intfIsUp( sintf ):
261
                info( '*** Waiting for', sintf, 'to come up\n' )
262 80a8fa62 Bob Lantz
                sleep( 1 )
263
            if self.ping( hosts=[ switch, controller ] ) != 0:
264 d5886525 Bob Lantz
                error( '*** Error: control network test failed\n' )
265 80a8fa62 Bob Lantz
                exit( 1 )
266 d5886525 Bob Lantz
        info( '\n' )
267 80a8fa62 Bob Lantz
268 80be5642 Bob Lantz
    def configHosts( self ):
269 80a8fa62 Bob Lantz
        "Configure a set of hosts."
270 8b5062a3 Brandon Heller
        # params were: hosts, ips
271 019bff82 Bob Lantz
        for host in self.hosts:
272 a6bcad8f Bob Lantz
            hintf = host.defaultIntf()
273
            host.setIP( host.defaultIP, self.cparams.prefixLen, hintf )
274 80a8fa62 Bob Lantz
            host.setDefaultRoute( hintf )
275 8b5062a3 Brandon Heller
            # You're low priority, dude!
276 80a8fa62 Bob Lantz
            quietRun( 'renice +18 -p ' + repr( host.pid ) )
277 086ef80e Bob Lantz
            info( host.name + ' ' )
278 d5886525 Bob Lantz
        info( '\n' )
279 80a8fa62 Bob Lantz
280 019bff82 Bob Lantz
    def buildFromTopo( self, topo ):
281
        """Build mininet from a topology object
282 80a8fa62 Bob Lantz
           At the end of this function, everything should be connected
283
           and up."""
284 d44a5843 Bob Lantz
285
        def addNode( prefix, addMethod, nodeId ):
286
            "Add a host or a switch."
287
            name = prefix + topo.name( nodeId )
288 a6bcad8f Bob Lantz
            # MAC and IP should probably be from nodeInfo...
289 d44a5843 Bob Lantz
            mac = macColonHex( nodeId ) if self.setMacs else None
290
            ip = topo.ip( nodeId )
291 a6bcad8f Bob Lantz
            ni = topo.nodeInfo( nodeId )
292
            node = addMethod( name, cls=ni.cls, mac=mac, ip=ip, **ni.params )
293 d44a5843 Bob Lantz
            self.idToNode[ nodeId ] = node
294
            info( name + ' ' )
295
296
        # Possibly we should clean up here and/or validate
297
        # the topo
298 376bcba4 Brandon Heller
        if self.cleanup:
299 d44a5843 Bob Lantz
            pass
300
301 d5886525 Bob Lantz
        info( '*** Adding controller\n' )
302 c26875cb Bob Lantz
        self.addController( 'c0' )
303 d5886525 Bob Lantz
        info( '*** Creating network\n' )
304
        info( '*** Adding hosts:\n' )
305 019bff82 Bob Lantz
        for hostId in sorted( topo.hosts() ):
306 d44a5843 Bob Lantz
            addNode( 'h', self.addHost, hostId )
307 d5886525 Bob Lantz
        info( '\n*** Adding switches:\n' )
308 019bff82 Bob Lantz
        for switchId in sorted( topo.switches() ):
309 a6bcad8f Bob Lantz
            addNode( 's', self.addSwitch, switchId)
310 724f1144 Bob Lantz
        info( '\n*** Adding links:\n' )
311 019bff82 Bob Lantz
        for srcId, dstId in sorted( topo.edges() ):
312
            src, dst = self.idToNode[ srcId ], self.idToNode[ dstId ]
313
            srcPort, dstPort = topo.port( srcId, dstId )
314 a6bcad8f Bob Lantz
            ei = topo.edgeInfo( srcId, dstId )
315
            link, params = ei.cls, ei.params
316
            if not link:
317
                link = self.link
318
            link( src, dst, srcPort, dstPort, **params )
319 019bff82 Bob Lantz
            info( '(%s, %s) ' % ( src.name, dst.name ) )
320 d5886525 Bob Lantz
        info( '\n' )
321 80a8fa62 Bob Lantz
322 d44a5843 Bob Lantz
    def build( self ):
323
        "Build mininet."
324
        if self.topo:
325
            self.buildFromTopo( self.topo )
326 80a8fa62 Bob Lantz
        if self.inNamespace:
327 d5886525 Bob Lantz
            info( '*** Configuring control network\n' )
328 d44a5843 Bob Lantz
            self.configureControlNetwork()
329 d5886525 Bob Lantz
        info( '*** Configuring hosts\n' )
330 80be5642 Bob Lantz
        self.configHosts()
331 376bcba4 Brandon Heller
        if self.xterms:
332 15b482e3 Brandon Heller
            self.startTerms()
333 80a8fa62 Bob Lantz
        if self.autoSetMacs:
334
            self.setMacs()
335
        if self.autoStaticArp:
336
            self.staticArp()
337 99c035d9 Bob Lantz
        self.built = True
338 80a8fa62 Bob Lantz
339 15b482e3 Brandon Heller
    def startTerms( self ):
340
        "Start a terminal for each node."
341
        info( "*** Running terms on %s\n" % os.environ[ 'DISPLAY' ] )
342 8a034f4f Brandon Heller
        cleanUpScreens()
343 15b482e3 Brandon Heller
        self.terms += makeTerms( self.controllers, 'controller' )
344
        self.terms += makeTerms( self.switches, 'switch' )
345
        self.terms += makeTerms( self.hosts, 'host' )
346 8a034f4f Brandon Heller
347 80a8fa62 Bob Lantz
    def stopXterms( self ):
348
        "Kill each xterm."
349 8a034f4f Brandon Heller
        # Kill xterms
350
        for term in self.terms:
351 80a8fa62 Bob Lantz
            os.kill( term.pid, signal.SIGKILL )
352 8a034f4f Brandon Heller
        cleanUpScreens()
353 8b5062a3 Brandon Heller
354 80a8fa62 Bob Lantz
    def setMacs( self ):
355 1bda2d21 Bob Lantz
        """Set MAC addrs to correspond to default MACs on hosts.
356 80a8fa62 Bob Lantz
           Assume that the host only has one interface."""
357 019bff82 Bob Lantz
        for host in self.hosts:
358 086ef80e Bob Lantz
            host.setMAC( host.intfs[ 0 ], host.defaultMAC )
359 376bcba4 Brandon Heller
360 80a8fa62 Bob Lantz
    def staticArp( self ):
361
        "Add all-pairs ARP entries to remove the need to handle broadcast."
362 019bff82 Bob Lantz
        for src in self.hosts:
363
            for dst in self.hosts:
364 376bcba4 Brandon Heller
                if src != dst:
365 1bda2d21 Bob Lantz
                    src.setARP( ip=dst.IP(), mac=dst.MAC() )
366 376bcba4 Brandon Heller
367 80a8fa62 Bob Lantz
    def start( self ):
368 99c035d9 Bob Lantz
        "Start controller and switches."
369
        if not self.built:
370
            self.build()
371 d5886525 Bob Lantz
        info( '*** Starting controller\n' )
372 019bff82 Bob Lantz
        for controller in self.controllers:
373
            controller.start()
374
        info( '*** Starting %s switches\n' % len( self.switches ) )
375
        for switch in self.switches:
376 efc9a01c Bob Lantz
            info( switch.name + ' ')
377 80a8fa62 Bob Lantz
            switch.start( self.controllers )
378 d5886525 Bob Lantz
        info( '\n' )
379 80a8fa62 Bob Lantz
380
    def stop( self ):
381 d5886525 Bob Lantz
        "Stop the controller(s), switches and hosts"
382 8a034f4f Brandon Heller
        if self.terms:
383 d5886525 Bob Lantz
            info( '*** Stopping %i terms\n' % len( self.terms ) )
384 80a8fa62 Bob Lantz
            self.stopXterms()
385 019bff82 Bob Lantz
        info( '*** Stopping %i hosts\n' % len( self.hosts ) )
386
        for host in self.hosts:
387 d5886525 Bob Lantz
            info( '%s ' % host.name )
388 8b5062a3 Brandon Heller
            host.terminate()
389 d5886525 Bob Lantz
        info( '\n' )
390 019bff82 Bob Lantz
        info( '*** Stopping %i switches\n' % len( self.switches ) )
391
        for switch in self.switches:
392 2235f216 Bob Lantz
            info( switch.name )
393 8b5062a3 Brandon Heller
            switch.stop()
394 d5886525 Bob Lantz
        info( '\n' )
395 019bff82 Bob Lantz
        info( '*** Stopping %i controllers\n' % len( self.controllers ) )
396
        for controller in self.controllers:
397
            controller.stop()
398 724f1144 Bob Lantz
        info( '*** Done\n' )
399 8b5062a3 Brandon Heller
400 67516aa4 Bob Lantz
    def run( self, test, *args, **kwargs ):
401 80a8fa62 Bob Lantz
        "Perform a complete start/test/stop cycle."
402 8b5062a3 Brandon Heller
        self.start()
403 d5886525 Bob Lantz
        info( '*** Running test\n' )
404 67516aa4 Bob Lantz
        result = test( *args, **kwargs )
405 8b5062a3 Brandon Heller
        self.stop()
406
        return result
407
408 259d7133 Bob Lantz
    def monitor( self, hosts=None, timeoutms=-1 ):
409 ec7b211c Bob Lantz
        """Monitor a set of hosts (or all hosts by default),
410
           and return their output, a line at a time.
411 259d7133 Bob Lantz
           hosts: (optional) set of hosts to monitor
412
           timeoutms: (optional) timeout value in ms
413
           returns: iterator which returns host, line"""
414 ec7b211c Bob Lantz
        if hosts is None:
415
            hosts = self.hosts
416
        poller = select.poll()
417 cf6f6704 Bob Lantz
        Node = hosts[ 0 ]  # so we can call class method fdToNode
418 ec7b211c Bob Lantz
        for host in hosts:
419
            poller.register( host.stdout )
420
        while True:
421 259d7133 Bob Lantz
            ready = poller.poll( timeoutms )
422 ec7b211c Bob Lantz
            for fd, event in ready:
423
                host = Node.fdToNode( fd )
424
                if event & select.POLLIN:
425
                    line = host.readline()
426 259d7133 Bob Lantz
                    if line is not None:
427 ec7b211c Bob Lantz
                        yield host, line
428 259d7133 Bob Lantz
            # Return if non-blocking
429
            if not ready and timeoutms >= 0:
430
                yield None, None
431 ec7b211c Bob Lantz
432 8b5062a3 Brandon Heller
    @staticmethod
433 80a8fa62 Bob Lantz
    def _parsePing( pingOutput ):
434
        "Parse ping output and return packets sent, received."
435 3fac5a43 Brandon Heller
        # Check for downed link
436
        if 'connect: Network is unreachable' in pingOutput:
437
            return (1, 0)
438 8b5062a3 Brandon Heller
        r = r'(\d+) packets transmitted, (\d+) received'
439 80a8fa62 Bob Lantz
        m = re.search( r, pingOutput )
440 8b5062a3 Brandon Heller
        if m == None:
441 d5886525 Bob Lantz
            error( '*** Error: could not parse ping output: %s\n' %
442 80a8fa62 Bob Lantz
                     pingOutput )
443 3fac5a43 Brandon Heller
            return (1, 0)
444 80a8fa62 Bob Lantz
        sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
445 8b5062a3 Brandon Heller
        return sent, received
446
447 80a8fa62 Bob Lantz
    def ping( self, hosts=None ):
448
        """Ping between all specified hosts.
449 019bff82 Bob Lantz
           hosts: list of hosts
450 80a8fa62 Bob Lantz
           returns: ploss packet loss percentage"""
451 d44a5843 Bob Lantz
        # should we check if running?
452 8b5062a3 Brandon Heller
        packets = 0
453
        lost = 0
454
        ploss = None
455
        if not hosts:
456 019bff82 Bob Lantz
            hosts = self.hosts
457 cdeaca86 Brandon Heller
            output( '*** Ping: testing ping reachability\n' )
458 019bff82 Bob Lantz
        for node in hosts:
459 cdeaca86 Brandon Heller
            output( '%s -> ' % node.name )
460 019bff82 Bob Lantz
            for dest in hosts:
461 8b5062a3 Brandon Heller
                if node != dest:
462 80a8fa62 Bob Lantz
                    result = node.cmd( 'ping -c1 ' + dest.IP() )
463
                    sent, received = self._parsePing( result )
464 8b5062a3 Brandon Heller
                    packets += sent
465
                    if received > sent:
466 d5886525 Bob Lantz
                        error( '*** Error: received too many packets' )
467
                        error( '%s' % result )
468 80a8fa62 Bob Lantz
                        node.cmdPrint( 'route' )
469
                        exit( 1 )
470 8b5062a3 Brandon Heller
                    lost += sent - received
471 cdeaca86 Brandon Heller
                    output( ( '%s ' % dest.name ) if received else 'X ' )
472
            output( '\n' )
473 8b5062a3 Brandon Heller
            ploss = 100 * lost / packets
474 cdeaca86 Brandon Heller
        output( "*** Results: %i%% dropped (%d/%d lost)\n" %
475 80a8fa62 Bob Lantz
                ( ploss, lost, packets ) )
476 8b5062a3 Brandon Heller
        return ploss
477
478 80a8fa62 Bob Lantz
    def pingAll( self ):
479
        """Ping between all hosts.
480
           returns: ploss packet loss percentage"""
481 1bb4412f Brandon Heller
        return self.ping()
482 eeb9cb3c Brandon Heller
483 80a8fa62 Bob Lantz
    def pingPair( self ):
484
        """Ping between first two hosts, useful for testing.
485
           returns: ploss packet loss percentage"""
486 019bff82 Bob Lantz
        hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
487 80a8fa62 Bob Lantz
        return self.ping( hosts=hosts )
488 eeb9cb3c Brandon Heller
489
    @staticmethod
490 80a8fa62 Bob Lantz
    def _parseIperf( iperfOutput ):
491
        """Parse iperf output and return bandwidth.
492
           iperfOutput: string
493
           returns: result string"""
494 eeb9cb3c Brandon Heller
        r = r'([\d\.]+ \w+/sec)'
495 ad2fda25 Bob Lantz
        m = re.findall( r, iperfOutput )
496 eeb9cb3c Brandon Heller
        if m:
497 ad2fda25 Bob Lantz
            return m[-1]
498 eeb9cb3c Brandon Heller
        else:
499 ad2fda25 Bob Lantz
            # was: raise Exception(...)
500
            error( 'could not parse iperf output: ' + iperfOutput )
501
            return ''
502 80a8fa62 Bob Lantz
503 1a40cd04 Brandon Heller
    def iperf( self, hosts=None, l4Type='TCP', udpBw='10M' ):
504 80a8fa62 Bob Lantz
        """Run iperf between two hosts.
505 019bff82 Bob Lantz
           hosts: list of hosts; if None, uses opposite hosts
506 80a8fa62 Bob Lantz
           l4Type: string, one of [ TCP, UDP ]
507
           returns: results two-element array of server and client speeds"""
508 ad2fda25 Bob Lantz
        if not quietRun( 'which telnet' ):
509
            error( 'Cannot find telnet in $PATH - required for iperf test' )
510
            return
511 eeb9cb3c Brandon Heller
        if not hosts:
512 019bff82 Bob Lantz
            hosts = [ self.hosts[ 0 ], self.hosts[ -1 ] ]
513 eeb9cb3c Brandon Heller
        else:
514 80a8fa62 Bob Lantz
            assert len( hosts ) == 2
515 ec7b211c Bob Lantz
        client, server = hosts
516 cdeaca86 Brandon Heller
        output( '*** Iperf: testing ' + l4Type + ' bandwidth between ' )
517 ec7b211c Bob Lantz
        output( "%s and %s\n" % ( client.name, server.name ) )
518
        server.cmd( 'killall -9 iperf' )
519 80a8fa62 Bob Lantz
        iperfArgs = 'iperf '
520
        bwArgs = ''
521
        if l4Type == 'UDP':
522
            iperfArgs += '-u '
523
            bwArgs = '-b ' + udpBw + ' '
524
        elif l4Type != 'TCP':
525
            raise Exception( 'Unexpected l4 type: %s' % l4Type )
526 ec7b211c Bob Lantz
        server.sendCmd( iperfArgs + '-s', printPid=True )
527
        servout = ''
528
        while server.lastPid is None:
529
            servout += server.monitor()
530 ad2fda25 Bob Lantz
        while 'Connected' not in client.cmd(
531
            'sh -c "echo A | telnet -e A %s 5001"' % server.IP()):
532 a6bcad8f Bob Lantz
            output('waiting for iperf to start up...')
533 ad2fda25 Bob Lantz
            sleep(.5)
534 ec7b211c Bob Lantz
        cliout = client.cmd( iperfArgs + '-t 5 -c ' + server.IP() + ' ' +
535 80a8fa62 Bob Lantz
                           bwArgs )
536 ec7b211c Bob Lantz
        debug( 'Client output: %s\n' % cliout )
537
        server.sendInt()
538
        servout += server.waitOutput()
539
        debug( 'Server output: %s\n' % servout )
540
        result = [ self._parseIperf( servout ), self._parseIperf( cliout ) ]
541 80a8fa62 Bob Lantz
        if l4Type == 'UDP':
542
            result.insert( 0, udpBw )
543 cdeaca86 Brandon Heller
        output( '*** Results: %s\n' % result )
544 eeb9cb3c Brandon Heller
        return result
545
546 c70aab0a Bob Lantz
    def configLinkStatus( self, src, dst, status ):
547
        """Change status of src <-> dst links.
548
           src: node name
549
           dst: node name
550
           status: string {up, down}"""
551 8d3c2859 Brandon Heller
        if src not in self.nameToNode:
552
            error( 'src not in network: %s\n' % src )
553
        elif dst not in self.nameToNode:
554
            error( 'dst not in network: %s\n' % dst )
555
        else:
556 fb2f6523 Bob Lantz
            srcNode, dstNode = self.nameToNode[ src ], self.nameToNode[ dst ]
557
            connections = srcNode.connectionsTo( dstNode )
558
            if len( connections ) == 0:
559 8d3c2859 Brandon Heller
                error( 'src and dst not connected: %s %s\n' % ( src, dst) )
560 fb2f6523 Bob Lantz
            for srcIntf, dstIntf in connections:
561 54dfb243 Bob Lantz
                result = srcNode.cmd( 'ifconfig', srcIntf, status )
562 8d3c2859 Brandon Heller
                if result:
563
                    error( 'link src status change failed: %s\n' % result )
564 54dfb243 Bob Lantz
                result = dstNode.cmd( 'ifconfig', dstIntf, status )
565 8d3c2859 Brandon Heller
                if result:
566
                    error( 'link dst status change failed: %s\n' % result )
567
568 80a8fa62 Bob Lantz
    def interact( self ):
569
        "Start network and run our simple CLI."
570 eeb9cb3c Brandon Heller
        self.start()
571 496b5f9e Bob Lantz
        result = CLI( self )
572 eeb9cb3c Brandon Heller
        self.stop()
573
        return result
574 82b72072 Bob Lantz
575
576
# pylint thinks inited is unused
577
# pylint: disable-msg=W0612
578
579
def init():
580
    "Initialize Mininet."
581
    if init.inited:
582
        return
583
    if os.getuid() != 0:
584
        # Note: this script must be run as root
585
        # Perhaps we should do so automatically!
586
        print "*** Mininet must run as root."
587
        exit( 1 )
588
    # If which produces no output, then mnexec is not in the path.
589
    # May want to loosen this to handle mnexec in the current dir.
590
    if not quietRun( 'which mnexec' ):
591
        raise Exception( "Could not find mnexec - check $PATH" )
592
    fixLimits()
593
    init.inited = True
594
595
init.inited = False
596
597
# pylint: enable-msg=W0612