Statistics
| Branch: | Tag: | Revision:

mininet / mininet / net.py @ 54037995

History | View | Annotate | Download (22.5 KB)

1 98d4f189 Bob Lantz
#!/usr/bin/python
2 8f20b95d Brandon Heller
"""Mininet: A simple networking testbed for OpenFlow!
3 98d4f189 Bob Lantz

4 8f20b95d Brandon Heller
@author Bob Lantz (rlantz@cs.stanford.edu)
5
@author Brandon Heller (brandonh@stanford.edu)
6 98d4f189 Bob Lantz

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

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

14
Each host has:
15
   A virtual console (pipes to a shell)
16
   A virtual interfaces (half of a veth pair)
17 eddef947 Bob Lantz
   A parent shell (and possibly some child processes) in a namespace
18 98d4f189 Bob Lantz
   
19 eddef947 Bob Lantz
Hosts have a network interface which is configured via ifconfig/ip
20 8f20b95d Brandon Heller
link/etc.
21 98d4f189 Bob Lantz

22 55dd9368 Bob Lantz
This version supports both the kernel and user space datapaths
23 08cef003 Bob Lantz
from the OpenFlow reference implementation.
24

25 98d4f189 Bob Lantz
In kernel datapath mode, the controller and switches are simply
26
processes in the root namespace.
27

28 55dd9368 Bob Lantz
Kernel OpenFlow datapaths are instantiated using dpctl(8), and are
29
attached to the one side of a veth pair; the other side resides in the
30
host namespace. In this mode, switch processes can simply connect to the
31 eddef947 Bob Lantz
controller via the loopback interface.
32 98d4f189 Bob Lantz

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

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

42
Naming:
43
   Host nodes are named h1-hN
44
   Switch nodes are named s0-sN
45
   Interfaces are named {nodename}-eth0 .. {nodename}-ethN,
46

47
"""
48 8b5062a3 Brandon Heller
import os
49
import re
50 8a034f4f Brandon Heller
import signal
51 8b5062a3 Brandon Heller
from subprocess import call
52
import sys
53 98d4f189 Bob Lantz
from time import sleep
54
55 8f20b95d Brandon Heller
from mininet.logging_mod import lg
56 54037995 Brandon Heller
from mininet.node import KernelSwitch
57 8b5062a3 Brandon Heller
from mininet.util import quietRun, fixLimits
58
from mininet.util import make_veth_pair, move_intf, retry, MOVEINTF_DELAY
59 8a034f4f Brandon Heller
from mininet.xterm import cleanUpScreens, makeXterms
60 8b5062a3 Brandon Heller
61
DATAPATHS = ['kernel'] #['user', 'kernel']
62 e3621eb0 Brandon Heller
63 98d4f189 Bob Lantz
64 eddef947 Bob Lantz
def init():
65 8b5062a3 Brandon Heller
    "Initialize Mininet."
66
    if os.getuid() != 0:
67
        # Note: this script must be run as root
68
        # Perhaps we should do so automatically!
69
        print "*** Mininet must run as root."
70
        exit(1)
71
    # If which produces no output, then netns is not in the path.
72
    # May want to loosen this to handle netns in the current dir.
73
    if not quietRun(['which', 'netns']):
74
        raise Exception("Could not find netns; see INSTALL")
75
    fixLimits()
76
77
78
class Mininet(object):
79
    '''Network emulation with hosts spawned in network namespaces.'''
80
81
    def __init__(self, topo, switch, host, controller, cparams,
82
                 build = True, xterms = False, cleanup = False,
83 54037995 Brandon Heller
                 in_namespace = False,
84 376bcba4 Brandon Heller
                 auto_set_macs = False, auto_static_arp = False):
85 8b5062a3 Brandon Heller
        '''Create Mininet object.
86

87
        @param topo Topo object
88
        @param switch Switch class
89
        @param host Host class
90
        @param controller Controller class
91
        @param cparams ControllerParams object
92
        @param now build now?
93
        @param xterms if build now, spawn xterms?
94
        @param cleanup if build now, cleanup before creating?
95
        @param in_namespace spawn switches and hosts in their own namespace?
96 376bcba4 Brandon Heller
        @param auto_set_macs set MAC addrs to DPIDs?
97
        @param auto_static_arp set all-pairs static MAC addrs?
98 8b5062a3 Brandon Heller
        '''
99
        self.topo = topo
100
        self.switch = switch
101
        self.host = host
102
        self.controller = controller
103
        self.cparams = cparams
104
        self.nodes = {} # dpid to Node{Host, Switch} objects
105
        self.controllers = {} # controller name to Controller objects
106
        self.dps = 0 # number of created kernel datapaths
107
        self.in_namespace = in_namespace
108 376bcba4 Brandon Heller
        self.xterms = xterms
109
        self.cleanup = cleanup
110
        self.auto_set_macs = auto_set_macs
111
        self.auto_static_arp = auto_static_arp
112 8b5062a3 Brandon Heller
113 8a034f4f Brandon Heller
        self.terms = [] # list of spawned xterm processes
114
115 8b5062a3 Brandon Heller
        self.kernel = True #temporary!
116
117
        if build:
118 376bcba4 Brandon Heller
            self.build()
119 8b5062a3 Brandon Heller
120
    def _add_host(self, dpid):
121
        '''Add host.
122

123
        @param dpid DPID of host to add
124
        '''
125
        host = self.host('h_' + self.topo.name(dpid))
126
        # for now, assume one interface per host.
127
        host.intfs.append('h_' + self.topo.name(dpid) + '-eth0')
128
        self.nodes[dpid] = host
129
        #lg.info('%s ' % host.name)
130
131
    def _add_switch(self, dpid):
132 54037995 Brandon Heller
        '''Add switch.
133

134 8b5062a3 Brandon Heller
        @param dpid DPID of switch to add
135
        '''
136
        sw = None
137 54037995 Brandon Heller
        sw_dpid = None
138
        if self.auto_set_macs:
139
            sw_dpid = dpid
140
        if self.switch is KernelSwitch:
141
            sw = self.switch('s_' + self.topo.name(dpid), dp = self.dps,
142
                             dpid = sw_dpid)
143 8b5062a3 Brandon Heller
            self.dps += 1
144
        else:
145
            sw = self.switch('s_' + self.topo.name(dpid))
146
        self.nodes[dpid] = sw
147
148
    def _add_link(self, src, dst):
149
        '''Add link.
150

151
        @param src source DPID
152
        @param dst destination DPID
153
        '''
154
        src_port, dst_port = self.topo.port(src, dst)
155
        src_node = self.nodes[src]
156
        dst_node = self.nodes[dst]
157
        src_intf = src_node.intfName(src_port)
158
        dst_intf = dst_node.intfName(dst_port)
159
        make_veth_pair(src_intf, dst_intf)
160
        src_node.intfs.append(src_intf)
161
        dst_node.intfs.append(dst_intf)
162
        #lg.info('\n')
163
        #lg.info('added intf %s to src node %x\n' % (src_intf, src))
164
        #lg.info('added intf %s to dst node %x\n' % (dst_intf, dst))
165
        if src_node.inNamespace:
166
            #lg.info('moving src w/inNamespace set\n')
167
            retry(3, MOVEINTF_DELAY, move_intf, src_intf, src_node)
168
        if dst_node.inNamespace:
169
            #lg.info('moving dst w/inNamespace set\n')
170
            retry(3, MOVEINTF_DELAY, move_intf, dst_intf, dst_node)
171
        src_node.connection[src_intf] = (dst_node, dst_intf)
172
        dst_node.connection[dst_intf] = (src_node, src_intf)
173
174
    def _add_controller(self, controller):
175
        '''Add controller.
176

177
        @param controller Controller class
178
        '''
179 4804237f Brandon Heller
        controller = self.controller('c0', not self.kernel)
180 16c57ddb Brandon Heller
        if controller: # allow controller-less setups
181
            self.controllers['c0'] = controller
182 8b5062a3 Brandon Heller
183
    # Control network support:
184
    #
185
    # Create an explicit control network. Currently this is only
186
    # used by the user datapath configuration.
187
    #
188
    # Notes:
189
    #
190
    # 1. If the controller and switches are in the same (e.g. root)
191
    #    namespace, they can just use the loopback connection.
192
    #    We may wish to do this for the user datapath as well as the
193
    #    kernel datapath.
194
    #
195
    # 2. If we can get unix domain sockets to work, we can use them
196
    #    instead of an explicit control network.
197
    #
198
    # 3. Instead of routing, we could bridge or use 'in-band' control.
199
    #
200
    # 4. Even if we dispense with this in general, it could still be
201
    #    useful for people who wish to simulate a separate control
202
    #    network (since real networks may need one!)
203
204
    def _configureControlNetwork(self):
205
        '''Configure control network.'''
206
        self._configureRoutedControlNetwork()
207
208
    def _configureRoutedControlNetwork(self):
209
        '''Configure a routed control network on controller and switches.
210

211
        For use with the user datapath only right now.
212

213
        @todo(brandonh) Test this code and verify that user-space works!
214
        '''
215
        # params were: controller, switches, ips
216
217
        controller = self.controllers['c0']
218
        lg.info('%s <-> ' % controller.name)
219
        for switch_dpid in self.topo.switches():
220
            switch = self.nodes[switch_dpid]
221
            lg.info('%s ' % switch.name)
222
            sip = self.topo.ip(switch_dpid)#ips.next()
223
            sintf = switch.intfs[0]
224
            node, cintf = switch.connection[sintf]
225
            if node != controller:
226
                lg.error('*** Error: switch %s not connected to correct'
227
                         'controller' %
228
                         switch.name)
229
                exit(1)
230
            controller.setIP(cintf, self.cparams.ip, '/' +
231
                             self.cparams.subnet_size)
232
            switch.setIP(sintf, sip, '/' + self.cparams.subnet_size)
233
            controller.setHostRoute(sip, cintf)
234
            switch.setHostRoute(self.cparams.ip, sintf)
235
        lg.info('\n')
236
        lg.info('*** Testing control network\n')
237
        while not controller.intfIsUp(controller.intfs[0]):
238
            lg.info('*** Waiting for %s to come up\n', controller.intfs[0])
239
            sleep(1)
240
        for switch_dpid in self.topo.switches():
241
            switch = self.nodes[switch_dpid]
242
            while not switch.intfIsUp(switch.intfs[0]):
243
                lg.info('*** Waiting for %s to come up\n' % switch.intfs[0])
244
                sleep(1)
245
            if self.ping_test(hosts=[switch, controller]) != 0:
246
                lg.error('*** Error: control network test failed\n')
247
                exit(1)
248
        lg.info('\n')
249
250
    def _config_hosts( self ):
251
        '''Configure a set of hosts.'''
252
        # params were: hosts, ips
253
        for host_dpid in self.topo.hosts():
254
            host = self.nodes[host_dpid]
255
            hintf = host.intfs[0]
256
            host.setIP(hintf, self.topo.ip(host_dpid),
257
                       '/' + str(self.cparams.subnet_size))
258
            host.setDefaultRoute(hintf)
259
            # You're low priority, dude!
260
            quietRun('renice +18 -p ' + repr(host.pid))
261
            lg.info('%s ', host.name)
262
        lg.info('\n')
263
264 376bcba4 Brandon Heller
    def build(self):
265 8b5062a3 Brandon Heller
        '''Build mininet.
266

267
        At the end of this function, everything should be connected and up.
268
        '''
269 376bcba4 Brandon Heller
        if self.cleanup:
270 8b5062a3 Brandon Heller
            pass # cleanup
271
        # validate topo?
272
        kernel = self.kernel
273
        if kernel:
274
            lg.info('*** Using kernel datapath\n')
275
        else:
276
            lg.info('*** Using user datapath\n')
277
        lg.info('*** Adding controller\n')
278
        self._add_controller(self.controller)
279
        lg.info('*** Creating network\n')
280
        lg.info('*** Adding hosts:\n')
281
        for host in sorted(self.topo.hosts()):
282
            self._add_host(host)
283
            lg.info('0x%x ' % host)
284
        lg.info('\n*** Adding switches:\n')
285
        for switch in sorted(self.topo.switches()):
286
            self._add_switch(switch)
287
            lg.info('0x%x ' % switch)
288
        lg.info('\n*** Adding edges: ')
289
        for src, dst in sorted(self.topo.edges()):
290
            self._add_link(src, dst)
291
            lg.info('(0x%x, 0x%x) ' % (src, dst))
292
        lg.info('\n')
293
294
        if not kernel:
295
            lg.info('*** Configuring control network\n')
296
            self._configureControlNetwork()
297
298
        lg.info('*** Configuring hosts\n')
299
        self._config_hosts()
300
301 376bcba4 Brandon Heller
        if self.xterms:
302 8a034f4f Brandon Heller
            self.start_xterms()
303 376bcba4 Brandon Heller
        if self.auto_set_macs:
304
            self.set_macs()
305
        if self.auto_static_arp:
306
            self.static_arp()
307 8a034f4f Brandon Heller
308
    def switch_nodes(self):
309
        '''Return switch nodes.'''
310
        return [self.nodes[dpid] for dpid in self.topo.switches()]
311
312
    def host_nodes(self):
313
        '''Return host nodes.'''
314
        return [self.nodes[dpid] for dpid in self.topo.hosts()]
315
316
    def start_xterms(self):
317
        '''Start an xterm for each node in the topo.'''
318
        lg.info("*** Running xterms on %s\n" % os.environ['DISPLAY'])
319
        cleanUpScreens()
320
        self.terms += makeXterms(self.controllers.values(), 'controller')
321
        self.terms += makeXterms(self.switch_nodes(), 'switch')
322
        self.terms += makeXterms(self.host_nodes(), 'host')
323
324
    def stop_xterms(self):
325
        '''Kill each xterm.'''
326
        # Kill xterms
327
        for term in self.terms:
328
            os.kill(term.pid, signal.SIGKILL)
329
        cleanUpScreens()
330 8b5062a3 Brandon Heller
331 376bcba4 Brandon Heller
    def set_macs(self):
332
        '''Set MAC addrs to correspond to datapath IDs on hosts.
333

334
        Assume that the host only has one interface.
335
        '''
336
        for dpid in self.topo.hosts():
337
            host_node = self.nodes[dpid]
338
            host_node.setMAC(host_node.intfs[0], dpid)
339
340
    def static_arp(self):
341
        '''Add all-pairs ARP entries to remove the need to handle broadcast.'''
342
        for src in self.topo.hosts():
343
            src_node = self.nodes[src]
344
            for dst in self.topo.hosts():
345
                if src != dst:
346
                    src_node.setARP(dst, dst)
347
348 8b5062a3 Brandon Heller
    def start(self):
349
        '''Start controller and switches\n'''
350
        lg.info('*** Starting controller\n')
351 16c57ddb Brandon Heller
        for cname, cnode in self.controllers.iteritems():
352
            cnode.start()
353 8b5062a3 Brandon Heller
        lg.info('*** Starting %s switches\n' % len(self.topo.switches()))
354
        for switch_dpid in self.topo.switches():
355
            switch = self.nodes[switch_dpid]
356
            #lg.info('switch = %s' % switch)
357
            lg.info('0x%x ' % switch_dpid)
358 16c57ddb Brandon Heller
            switch.start(self.controllers)
359 8b5062a3 Brandon Heller
        lg.info('\n')
360
361
    def stop(self):
362
        '''Stop the controller(s), switches and hosts\n'''
363 8a034f4f Brandon Heller
        if self.terms:
364
            lg.info('*** Stopping %i terms\n' % len(self.terms))
365
            self.stop_xterms()
366 8b5062a3 Brandon Heller
        lg.info('*** Stopping %i hosts\n' % len(self.topo.hosts()))
367
        for host_dpid in self.topo.hosts():
368
            host = self.nodes[host_dpid]
369
            lg.info('%s ' % host.name)
370
            host.terminate()
371
        lg.info('\n')
372
        lg.info('*** Stopping %i switches\n' % len(self.topo.switches()))
373
        for switch_dpid in self.topo.switches():
374
            switch = self.nodes[switch_dpid]
375
            lg.info('%s' % switch.name)
376
            switch.stop()
377
        lg.info('\n')
378
        lg.info('*** Stopping controller\n')
379 16c57ddb Brandon Heller
        for cname, cnode in self.controllers.iteritems():
380
            cnode.stop()
381 8b5062a3 Brandon Heller
        lg.info('*** Test complete\n')
382
383
    def run(self, test, **params):
384
        '''Perform a complete start/test/stop cycle.'''
385
        self.start()
386
        lg.info('*** Running test\n')
387 eeb9cb3c Brandon Heller
        result = getattr(self, test)(**params)
388 8b5062a3 Brandon Heller
        self.stop()
389
        return result
390
391
    @staticmethod
392
    def _parse_ping(pingOutput):
393
        '''Parse ping output and return packets sent, received.'''
394
        r = r'(\d+) packets transmitted, (\d+) received'
395
        m = re.search( r, pingOutput )
396
        if m == None:
397
            lg.error('*** Error: could not parse ping output: %s\n' %
398
                     pingOutput)
399
            exit(1)
400
        sent, received = int(m.group(1)), int(m.group(2))
401
        return sent, received
402
403 eeb9cb3c Brandon Heller
    def ping(self, hosts = None):
404 8b5062a3 Brandon Heller
        '''Ping between all specified hosts.
405

406
        @param hosts list of host DPIDs
407
        @return ploss packet loss percentage
408
        '''
409
        #self.start()
410
        # check if running - only then, start?
411
        packets = 0
412
        lost = 0
413
        ploss = None
414
        if not hosts:
415
            hosts = self.topo.hosts()
416 eeb9cb3c Brandon Heller
        lg.info('*** Ping: testing ping reachability\n')
417 8b5062a3 Brandon Heller
        for node_dpid in hosts:
418
            node = self.nodes[node_dpid]
419 eeb9cb3c Brandon Heller
            lg.info('%s -> ' % node.name)
420 8b5062a3 Brandon Heller
            for dest_dpid in hosts:
421
                dest = self.nodes[dest_dpid]
422
                if node != dest:
423
                    result = node.cmd('ping -c1 ' + dest.IP())
424
                    sent, received = self._parse_ping(result)
425
                    packets += sent
426
                    if received > sent:
427
                        lg.error('*** Error: received too many packets')
428
                        lg.error('%s' % result)
429
                        node.cmdPrint('route')
430
                        exit( 1 )
431
                    lost += sent - received
432 eeb9cb3c Brandon Heller
                    lg.info(('%s ' % dest.name) if received else 'X ')
433
            lg.info('\n')
434 8b5062a3 Brandon Heller
            ploss = 100 * lost / packets
435 eeb9cb3c Brandon Heller
        lg.info("*** Results: %i%% dropped (%d/%d lost)\n" %
436
                (ploss, lost, packets))
437 8b5062a3 Brandon Heller
        return ploss
438
439 eeb9cb3c Brandon Heller
    def ping_all(self):
440 1bb4412f Brandon Heller
        '''Ping between all hosts.
441

442
        @return ploss packet loss percentage
443
        '''
444
        return self.ping()
445 eeb9cb3c Brandon Heller
446
    def ping_pair(self):
447 1bb4412f Brandon Heller
        '''Ping between first two hosts, useful for testing.
448

449
        @return ploss packet loss percentage
450
        '''
451 eeb9cb3c Brandon Heller
        hosts_sorted = sorted(self.topo.hosts())
452
        hosts = [hosts_sorted[0], hosts_sorted[1]]
453 1bb4412f Brandon Heller
        return self.ping(hosts = hosts)
454 eeb9cb3c Brandon Heller
455
    @staticmethod
456
    def _parseIperf(iperfOutput):
457
        '''Parse iperf output and return bandwidth.
458

459
        @param iperfOutput string
460
        @return result string
461
        '''
462
        r = r'([\d\.]+ \w+/sec)'
463
        m = re.search(r, iperfOutput)
464
        if m:
465
            return m.group(1)
466
        else:
467
            raise Exception('could not parse iperf output')
468
469 0cd489a7 Brandon Heller
    def iperf(self, hosts = None, l4_type = 'TCP', udp_bw = '10M',
470 fcf6a16f Brandon Heller
              verbose = False):
471 eeb9cb3c Brandon Heller
        '''Run iperf between two hosts.
472

473
        @param hosts list of host DPIDs; if None, uses opposite hosts
474 0cd489a7 Brandon Heller
        @param l4_type string, one of [TCP, UDP]
475 eeb9cb3c Brandon Heller
        @param verbose verbose printing
476
        @return results two-element array of server and client speeds
477
        '''
478
        if not hosts:
479
            hosts_sorted = sorted(self.topo.hosts())
480
            hosts = [hosts_sorted[0], hosts_sorted[-1]]
481
        else:
482
            assert len(hosts) == 2
483
        host0 = self.nodes[hosts[0]]
484
        host1 = self.nodes[hosts[1]]
485 0cd489a7 Brandon Heller
        lg.info('*** Iperf: testing ' + l4_type + ' bandwidth between ')
486 eeb9cb3c Brandon Heller
        lg.info("%s and %s\n" % (host0.name, host1.name))
487
        host0.cmd('killall -9 iperf')
488 0cd489a7 Brandon Heller
        iperf_args = 'iperf '
489
        bw_args = ''
490
        if l4_type == 'UDP':
491
            iperf_args += '-u '
492
            bw_args = '-b ' + udp_bw + ' '
493
        elif l4_type != 'TCP':
494
            raise Exception('Unexpected l4 type: %s' % l4_type)
495
        server = host0.cmd(iperf_args + '-s &')
496 eeb9cb3c Brandon Heller
        if verbose:
497
            lg.info('%s\n' % server)
498 0cd489a7 Brandon Heller
        client = host1.cmd(iperf_args + '-t 5 -c ' + host0.IP() + ' ' + bw_args)
499 eeb9cb3c Brandon Heller
        if verbose:
500
            lg.info('%s\n' % client)
501
        server = host0.cmd('killall -9 iperf')
502
        if verbose:
503
            lg.info('%s\n' % server)
504
        result = [self._parseIperf(server), self._parseIperf(client)]
505 0cd489a7 Brandon Heller
        if l4_type == 'UDP':
506
            result.insert(0, udp_bw)
507 eeb9cb3c Brandon Heller
        lg.info('*** Results: %s\n' % result)
508
        return result
509
510 0cd489a7 Brandon Heller
    def iperf_udp(self, udp_bw = '10M'):
511
        '''Run iperf UDP test.'''
512
        return self.iperf(l4_type = 'UDP', udp_bw = udp_bw)
513
514 8b5062a3 Brandon Heller
    def interact(self):
515
        '''Start network and run our simple CLI.'''
516 eeb9cb3c Brandon Heller
        self.start()
517
        result = MininetCLI(self)
518
        self.stop()
519
        return result
520 8b5062a3 Brandon Heller
521
522
class MininetCLI(object):
523
    '''Simple command-line interface to talk to nodes.'''
524
    cmds = ['?', 'help', 'nodes', 'net', 'sh', 'ping_all', 'exit', \
525 0cd489a7 Brandon Heller
            'ping_pair', 'iperf', 'iperf_udp']
526 8b5062a3 Brandon Heller
527
    def __init__(self, mininet):
528
        self.mn = mininet
529
        self.nodemap = {} # map names to Node objects
530
        for node in self.mn.nodes.values():
531
            self.nodemap[node.name] = node
532 16c57ddb Brandon Heller
        for cname, cnode in self.mn.controllers.iteritems():
533
            self.nodemap[cname] = cnode
534 8b5062a3 Brandon Heller
        self.nodelist = self.nodemap.values()
535
        self.run()
536
537
    # Commands
538
    def help(self, args):
539
        '''Semi-useful help for CLI.'''
540
        help_str = 'Available commands are:' + str(self.cmds) + '\n' + \
541
                   'You may also send a command to a node using:\n' + \
542
                   '  <node> command {args}\n' + \
543
                   'For example:\n' + \
544
                   '  mininet> h0 ifconfig\n' + \
545
                   '\n' + \
546
                   'The interpreter automatically substitutes IP ' + \
547
                   'addresses\n' + \
548
                   'for node names, so commands like\n' + \
549
                   '  mininet> h0 ping -c1 h1\n' + \
550
                   'should work.\n' + \
551
                   '\n\n' + \
552
                   'Interactive commands are not really supported yet,\n' + \
553
                   'so please limit commands to ones that do not\n' + \
554
                   'require user interaction and will terminate\n' + \
555
                   'after a reasonable amount of time.\n'
556
        print(help_str)
557
558
    def nodes(self, args):
559
        '''List all nodes.'''
560
        lg.info('available nodes are: \n%s\n',
561
                ' '.join([node.name for node in sorted(self.nodelist)]))
562
563
    def net(self, args):
564
        '''List network connection.'''
565
        for switch_dpid in self.mn.topo.switches():
566
            switch = self.mn.nodes[switch_dpid]
567
            lg.info('%s <->', switch.name)
568
            for intf in switch.intfs:
569
                node, remoteIntf = switch.connection[intf]
570
                lg.info(' %s' % node.name)
571
            lg.info('\n')
572
573
    def sh(self, args):
574
        '''Run an external shell command'''
575 eeb9cb3c Brandon Heller
        call(['sh', '-c'] + args)
576 8b5062a3 Brandon Heller
577
    def ping_all(self, args):
578
        '''Ping between all hosts.'''
579 eeb9cb3c Brandon Heller
        self.mn.ping_all()
580 8b5062a3 Brandon Heller
581
    def ping_pair(self, args):
582
        '''Ping between first two hosts, useful for testing.'''
583 eeb9cb3c Brandon Heller
        self.mn.ping_pair()
584
585
    def iperf(self, args):
586 0cd489a7 Brandon Heller
        '''Simple iperf TCP test between two hosts.'''
587 eeb9cb3c Brandon Heller
        self.mn.iperf()
588 8b5062a3 Brandon Heller
589 0cd489a7 Brandon Heller
    def iperf_udp(self, args):
590
        '''Simple iperf UDP test between two hosts.'''
591
        udp_bw = args[0] if len(args) else '10M'
592
        self.mn.iperf_udp(udp_bw)
593
594 8b5062a3 Brandon Heller
    def run(self):
595
        '''Read and execute commands.'''
596
        lg.warn('*** Starting CLI:\n')
597
        while True:
598
            lg.warn('mininet> ')
599
            input = sys.stdin.readline()
600
            if input == '':
601
                break
602
            if input[-1] == '\n':
603
                input = input[:-1]
604
            cmd = input.split(' ')
605
            first = cmd[0]
606
            rest = cmd[1:]
607
            if first in self.cmds and hasattr(self, first):
608
                getattr(self, first)(rest)
609
            elif first in self.nodemap and rest != []:
610
                node = self.nodemap[first]
611
                # Substitute IP addresses for node names in command
612
                rest = [self.nodemap[arg].IP() if arg in self.nodemap else arg
613
                        for arg in rest]
614
                rest = ' '.join(rest)
615
                # Interactive commands don't work yet, and
616
                # there are still issues with control-c
617
                lg.warn('*** %s: running %s\n' % (node.name, rest))
618
                node.sendCmd(rest)
619
                while True:
620
                    try:
621
                        done, data = node.monitor()
622
                        lg.info('%s\n' % data)
623
                        if done:
624
                            break
625
                    except KeyboardInterrupt:
626
                        node.sendInt()
627
            elif first == '':
628
                pass
629
            elif first in ['exit', 'quit']:
630
                break
631
            elif first == '?':
632
                self.help( rest )
633
            else:
634
                lg.error('CLI: unknown node or command: < %s >\n' % first)
635
            #lg.info('*** CLI: command complete\n')
636 8f20b95d Brandon Heller
        return 'exited by user command'