blob: 1255e12c7d13102e8c710b880d76821dfcf5ba3e [file] [log] [blame]
carlosmscabralf40ecd12013-02-01 18:15:58 -02001"""
2
3 Mininet: A simple networking testbed for OpenFlow/SDN!
4
5author: Bob Lantz (rlantz@cs.stanford.edu)
6author: Brandon Heller (brandonh@stanford.edu)
7
8Mininet creates scalable OpenFlow test networks by using
9process-based virtualization and network namespaces.
10
11Simulated hosts are created as processes in separate network
12namespaces. This allows a complete OpenFlow network to be simulated on
13top of a single Linux kernel.
14
15Each host has:
16
17A virtual console (pipes to a shell)
18A virtual interfaces (half of a veth pair)
19A parent shell (and possibly some child processes) in a namespace
20
21Hosts have a network interface which is configured via ifconfig/ip
22link/etc.
23
24This version supports both the kernel and user space datapaths
25from the OpenFlow reference implementation (openflowswitch.org)
26as well as OpenVSwitch (openvswitch.org.)
27
28In kernel datapath mode, the controller and switches are simply
29processes in the root namespace.
30
31Kernel OpenFlow datapaths are instantiated using dpctl(8), and are
32attached to the one side of a veth pair; the other side resides in the
33host namespace. In this mode, switch processes can simply connect to the
34controller via the loopback interface.
35
36In user datapath mode, the controller and switches can be full-service
37nodes that live in their own network namespaces and have management
38interfaces and IP addresses on a control network (e.g. 192.168.123.1,
39currently routed although it could be bridged.)
40
41In addition to a management interface, user mode switches also have
42several switch interfaces, halves of veth pairs whose other halves
43reside in the host nodes that the switches are connected to.
44
45Consistent, straightforward naming is important in order to easily
46identify hosts, switches and controllers, both from the CLI and
47from program code. Interfaces are named to make it easy to identify
48which interfaces belong to which node.
49
50The 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
57Note: If the network topology is created using mininet.topo, then
58node numbers are unique among hosts and switches (e.g. we have
59h1..hN and SN..SN+M) and also correspond to their default IP addresses
60of 10.x.y.z/8 where x.y.z is the base-256 representation of N for
61hN. This mapping allows easy determination of a node's IP
62address from its name, e.g. h1 -> 10.0.0.1, h257 -> 10.0.1.1.
63
64Note 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
67Currently we wrap the entire network in a 'mininet' object, which
68constructs a simulated network based on a network topology created
69using a topology object (e.g. LinearTopo) from mininet.topo or
70mininet.topolib, and a Controller which the switches will connect
71to. Several configuration options are provided for functions such as
72automatically setting MAC addresses, populating the ARP table, or
73even running a set of terminals to allow direct interaction with nodes.
74
75After the network is created, it can be started using start(), and a
76variety of useful tasks maybe performed, including basic connectivity
77and bandwidth tests and running the mininet CLI.
78
79Once the network is up and running, test code can easily get access
80to host and switch objects which can then be used for arbitrary
81experiments, typically involving running a series of commands on the
82hosts.
83
84After all desired tests or activities have been completed, the stop()
85method may be called to shut down the network.
86
87"""
88
89import os
90import re
91import select
92import signal
93from time import sleep
94
95from mininet.cli import CLI
96from mininet.log import info, error, debug, output
97from mininet.node import Host, OVSKernelSwitch, Controller
98from mininet.link import Link, Intf
99from mininet.util import quietRun, fixLimits, numCores, ensureRoot
100from mininet.util import macColonHex, ipStr, ipParse, netParse, ipAdd, nextCCNnet
101from mininet.term import cleanUpScreens, makeTerms
102
103# Mininet version: should be consistent with README and LICENSE
104VERSION = "2.0.0"
105
106class Mininet( object ):
107 "Network emulation with hosts spawned in network namespaces."
108
109 def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host,
110 controller=Controller, link=Link, intf=Intf,
111 build=True, xterms=False, cleanup=False, ipBase='10.0.0.0/8',
112 inNamespace=False,
113 autoSetMacs=False, autoStaticArp=False, autoPinCpus=False,
114 listenPort=None ):
115 """Create Mininet object.
116 topo: Topo (topology) object or None
117 switch: default Switch class
118 host: default Host class/constructor
119 controller: default Controller class/constructor
120 link: default Link class/constructor
121 intf: default Intf class/constructor
122 ipBase: base IP address for hosts,
123 build: build now from topo?
124 xterms: if build now, spawn xterms?
125 cleanup: if build now, cleanup before creating?
126 inNamespace: spawn switches and controller in net namespaces?
127 autoSetMacs: set MAC addrs automatically like IP addresses?
128 autoStaticArp: set all-pairs static MAC addrs?
129 autoPinCpus: pin hosts to (real) cores (requires CPULimitedHost)?
130 listenPort: base listening port to open; will be incremented for
131 each additional switch in the net if inNamespace=False"""
132 self.topo = topo
133 self.switch = switch
134 self.host = host
135 self.controller = controller
136 self.link = link
137 self.intf = intf
138 self.ipBase = ipBase
139 self.ipBaseNum, self.prefixLen = netParse( self.ipBase )
140 self.nextIP = 1 # start for address allocation
141 self.ccnNetBase = '1.0.0.0'
142 self.inNamespace = inNamespace
143 self.xterms = xterms
144 self.cleanup = cleanup
145 self.autoSetMacs = autoSetMacs
146 self.autoStaticArp = autoStaticArp
147 self.autoPinCpus = autoPinCpus
148 self.numCores = numCores()
149 self.nextCore = 0 # next core for pinning hosts to CPUs
150 self.listenPort = listenPort
151
152 self.hosts = []
153 self.switches = []
154 self.controllers = []
155
156 self.nameToNode = {} # name to Node (Host/Switch) objects
157
158 self.terms = [] # list of spawned xterm processes
159
160 Mininet.init() # Initialize Mininet if necessary
161
162 self.built = False
163 if topo and build:
164 self.build()
165
166 def isCCNhost(self, node):
167 if 'fib' in node.params:
168 return True
169 else:
170 return False
171
172 def addHost( self, name, cls=None, **params ):
173 """Add host.
174 name: name of host to add
175 cls: custom host class/constructor (optional)
176 params: parameters for host
177 returns: added host"""
178 # Default IP and MAC addresses
179
180 defaults = { 'ip': ipAdd( self.nextIP,
181 ipBaseNum=self.ipBaseNum,
182 prefixLen=self.prefixLen ) +
183 '/%s' % self.prefixLen }
184 if self.autoSetMacs:
185 defaults[ 'mac'] = macColonHex( self.nextIP )
186 if self.autoPinCpus:
187 defaults[ 'cores' ] = self.nextCore
188 self.nextCore = ( self.nextCore + 1 ) % self.numCores
189 self.nextIP += 1
190 defaults.update( params )
191 if not cls:
192 cls = self.host
193 h = cls( name, **defaults )
194 self.hosts.append( h )
195 self.nameToNode[ name ] = h
196 return h
197
198 def addSwitch( self, name, cls=None, **params ):
199 """Add switch.
200 name: name of switch to add
201 cls: custom switch class/constructor (optional)
202 returns: added switch
203 side effect: increments listenPort ivar ."""
204 defaults = { 'listenPort': self.listenPort,
205 'inNamespace': self.inNamespace }
206 defaults.update( params )
207 if not cls:
208 cls = self.switch
209 sw = cls( name, **defaults )
210 if not self.inNamespace and self.listenPort:
211 self.listenPort += 1
212 self.switches.append( sw )
213 self.nameToNode[ name ] = sw
214 return sw
215
216 def addController( self, name='c0', controller=None, **params ):
217 """Add controller.
218 controller: Controller class"""
219 if not controller:
220 controller = self.controller
221 controller_new = controller( name, **params )
222 if controller_new: # allow controller-less setups
223 self.controllers.append( controller_new )
224 self.nameToNode[ name ] = controller_new
225 return controller_new
226
227 # BL: is this better than just using nameToNode[] ?
228 # Should it have a better name?
229 def getNodeByName( self, *args ):
230 "Return node(s) with given name(s)"
231 if len( args ) == 1:
232 return self.nameToNode[ args[ 0 ] ]
233 return [ self.nameToNode[ n ] for n in args ]
234
235 def get( self, *args ):
236 "Convenience alias for getNodeByName"
237 return self.getNodeByName( *args )
238
239 def addLink( self, node1, node2, port1=None, port2=None,
240 cls=None, **params ):
241 """"Add a link from node1 to node2
242 node1: source node
243 node2: dest node
244 port1: source port
245 port2: dest port
246 returns: link object"""
247 defaults = { 'port1': port1,
248 'port2': port2,
249 'intf': self.intf }
250 defaults.update( params )
251 if not cls:
252 cls = self.link
253 return cls( node1, node2, **defaults )
254
255 def configHosts( self ):
256 "Configure a set of hosts."
257 for host in self.hosts:
258 info( host.name + ' ' )
259 intf = host.defaultIntf()
260 if self.isCCNhost(host):
261 host.configCCN()
262 host.configDefault(ip=None,mac=None)
263 elif intf:
264 host.configDefault( defaultRoute=intf )
265 else:
266 # Don't configure nonexistent intf
267 host.configDefault( ip=None, mac=None )
268 # You're low priority, dude!
269 # BL: do we want to do this here or not?
270 # May not make sense if we have CPU lmiting...
271 # quietRun( 'renice +18 -p ' + repr( host.pid ) )
272 # This may not be the right place to do this, but
273 # it needs to be done somewhere.
274 host.cmd( 'ifconfig lo up' )
275 info( '\n' )
276
277 def buildFromTopo( self, topo=None ):
278 """Build mininet from a topology object
279 At the end of this function, everything should be connected
280 and up."""
281
282 # Possibly we should clean up here and/or validate
283 # the topo
284 if self.cleanup:
285 pass
286
287 info( '*** Creating network\n' )
288
289 if not self.controllers:
290 # Add a default controller
291 info( '*** Adding controller\n' )
292 classes = self.controller
293 if type( classes ) is not list:
294 classes = [ classes ]
295 for i, cls in enumerate( classes ):
296 self.addController( 'c%d' % i, cls )
297
298 info( '*** Adding hosts:\n' )
299 for hostName in topo.hosts():
300 self.addHost( hostName, **topo.nodeInfo( hostName ) )
301 info( hostName + ' ' )
302
303 info( '\n*** Adding switches:\n' )
304 for switchName in topo.switches():
305 self.addSwitch( switchName, **topo.nodeInfo( switchName) )
306 info( switchName + ' ' )
307
308 info( '\n*** Adding links:\n' )
309 for srcName, dstName in topo.links(sort=True):
310 src, dst = self.nameToNode[ srcName ], self.nameToNode[ dstName ]
311 params = topo.linkInfo( srcName, dstName )
312 srcPort, dstPort = topo.port( srcName, dstName )
313 self.addLink( src, dst, srcPort, dstPort, **params )
314 if self.isCCNhost(src):
315 src.setIP(ipStr(ipParse(self.ccnNetBase) + 1) + '/30', intf=src.name + '-eth' + str(srcPort))
316 dst.setIP(ipStr(ipParse(self.ccnNetBase) + 2) + '/30', intf=dst.name + '-eth' + str(dstPort))
317 self.ccnNetBase=nextCCNnet(self.ccnNetBase)
318
319 info( '(%s, %s) ' % ( src.name, dst.name ) )
320
321 info( '\n' )
322
323
324 def configureControlNetwork( self ):
325 "Control net config hook: override in subclass"
326 raise Exception( 'configureControlNetwork: '
327 'should be overriden in subclass', self )
328
329 def build( self ):
330 "Build mininet."
331 if self.topo:
332 self.buildFromTopo( self.topo )
333 if ( self.inNamespace ):
334 self.configureControlNetwork()
335 info( '*** Configuring hosts\n' )
336 self.configHosts()
337 if self.xterms:
338 self.startTerms()
339 if self.autoStaticArp:
340 self.staticArp()
341 self.built = True
342
343 def startTerms( self ):
344 "Start a terminal for each node."
345 info( "*** Running terms on %s\n" % os.environ[ 'DISPLAY' ] )
346 cleanUpScreens()
347 self.terms += makeTerms( self.controllers, 'controller' )
348 self.terms += makeTerms( self.switches, 'switch' )
349 self.terms += makeTerms( self.hosts, 'host' )
350
351 def stopXterms( self ):
352 "Kill each xterm."
353 for term in self.terms:
354 os.kill( term.pid, signal.SIGKILL )
355 cleanUpScreens()
356
357 def staticArp( self ):
358 "Add all-pairs ARP entries to remove the need to handle broadcast."
359 for src in self.hosts:
360 for dst in self.hosts:
361 if src != dst:
362 src.setARP( ip=dst.IP(), mac=dst.MAC() )
363
364 def start( self ):
365 "Start controller and switches."
366 if not self.built:
367 self.build()
368 info( '*** Starting controller\n' )
369 for controller in self.controllers:
370 controller.start()
371 info( '*** Starting %s switches\n' % len( self.switches ) )
372 for switch in self.switches:
373 info( switch.name + ' ')
374 switch.start( self.controllers )
375 info( '\n' )
376
377 def stop( self ):
378 "Stop the controller(s), switches and hosts"
379 if self.terms:
380 info( '*** Stopping %i terms\n' % len( self.terms ) )
381 self.stopXterms()
382 info( '*** Stopping %i hosts\n' % len( self.hosts ) )
383 for host in self.hosts:
384 info( host.name + ' ' )
385 host.terminate()
386 info( '\n' )
387 info( '*** Stopping %i switches\n' % len( self.switches ) )
388 for switch in self.switches:
389 info( switch.name + ' ' )
390 switch.stop()
391 info( '\n' )
392 info( '*** Stopping %i controllers\n' % len( self.controllers ) )
393 for controller in self.controllers:
394 info( controller.name + ' ' )
395 controller.stop()
396 info( '\n*** Done\n' )
397
398 def run( self, test, *args, **kwargs ):
399 "Perform a complete start/test/stop cycle."
400 self.start()
401 info( '*** Running test\n' )
402 result = test( *args, **kwargs )
403 self.stop()
404 return result
405
406 def monitor( self, hosts=None, timeoutms=-1 ):
407 """Monitor a set of hosts (or all hosts by default),
408 and return their output, a line at a time.
409 hosts: (optional) set of hosts to monitor
410 timeoutms: (optional) timeout value in ms
411 returns: iterator which returns host, line"""
412 if hosts is None:
413 hosts = self.hosts
414 poller = select.poll()
415 Node = hosts[ 0 ] # so we can call class method fdToNode
416 for host in hosts:
417 poller.register( host.stdout )
418 while True:
419 ready = poller.poll( timeoutms )
420 for fd, event in ready:
421 host = Node.fdToNode( fd )
422 if event & select.POLLIN:
423 line = host.readline()
424 if line is not None:
425 yield host, line
426 # Return if non-blocking
427 if not ready and timeoutms >= 0:
428 yield None, None
429
430 # XXX These test methods should be moved out of this class.
431 # Probably we should create a tests.py for them
432
433 @staticmethod
434 def _parsePing( pingOutput ):
435 "Parse ping output and return packets sent, received."
436 # Check for downed link
437 if 'connect: Network is unreachable' in pingOutput:
438 return (1, 0)
439 r = r'(\d+) packets transmitted, (\d+) received'
440 m = re.search( r, pingOutput )
441 if m is None:
442 error( '*** Error: could not parse ping output: %s\n' %
443 pingOutput )
444 return (1, 0)
445 sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
446 return sent, received
447
448 def ping( self, hosts=None, timeout=None ):
449 """Ping between all specified hosts.
450 hosts: list of hosts
451 timeout: time to wait for a response, as string
452 returns: ploss packet loss percentage"""
453 # should we check if running?
454 packets = 0
455 lost = 0
456 ploss = None
457 if not hosts:
458 hosts = self.hosts
459 output( '*** Ping: testing ping reachability\n' )
460 for node in hosts:
461 output( '%s -> ' % node.name )
462 for dest in hosts:
463 if node != dest:
464 opts = ''
465 if timeout:
466 opts = '-W %s' % timeout
467 result = node.cmd( 'ping -c1 %s %s' % (opts, dest.IP()) )
468 sent, received = self._parsePing( result )
469 packets += sent
470 if received > sent:
471 error( '*** Error: received too many packets' )
472 error( '%s' % result )
473 node.cmdPrint( 'route' )
474 exit( 1 )
475 lost += sent - received
476 output( ( '%s ' % dest.name ) if received else 'X ' )
477 output( '\n' )
478 ploss = 100 * lost / packets
479 output( "*** Results: %i%% dropped (%d/%d lost)\n" %
480 ( ploss, lost, packets ) )
481 return ploss
482
483 @staticmethod
484 def _parsePingFull( pingOutput ):
485 "Parse ping output and return all data."
486 # Check for downed link
487 if 'connect: Network is unreachable' in pingOutput:
488 return (1, 0)
489 r = r'(\d+) packets transmitted, (\d+) received'
490 m = re.search( r, pingOutput )
491 if m is None:
492 error( '*** Error: could not parse ping output: %s\n' %
493 pingOutput )
494 return (1, 0, 0, 0, 0, 0)
495 sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
496 r = r'rtt min/avg/max/mdev = '
497 r += r'(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+) ms'
498 m = re.search( r, pingOutput )
499 rttmin = float( m.group( 1 ) )
500 rttavg = float( m.group( 2 ) )
501 rttmax = float( m.group( 3 ) )
502 rttdev = float( m.group( 4 ) )
503 return sent, received, rttmin, rttavg, rttmax, rttdev
504
505 def pingFull( self, hosts=None, timeout=None ):
506 """Ping between all specified hosts and return all data.
507 hosts: list of hosts
508 timeout: time to wait for a response, as string
509 returns: all ping data; see function body."""
510 # should we check if running?
511 # Each value is a tuple: (src, dsd, [all ping outputs])
512 all_outputs = []
513 if not hosts:
514 hosts = self.hosts
515 output( '*** Ping: testing ping reachability\n' )
516 for node in hosts:
517 output( '%s -> ' % node.name )
518 for dest in hosts:
519 if node != dest:
520 opts = ''
521 if timeout:
522 opts = '-W %s' % timeout
523 result = node.cmd( 'ping -c1 %s %s' % (opts, dest.IP()) )
524 outputs = self._parsePingFull( result )
525 sent, received, rttmin, rttavg, rttmax, rttdev = outputs
526 all_outputs.append( (node, dest, outputs) )
527 output( ( '%s ' % dest.name ) if received else 'X ' )
528 output( '\n' )
529 output( "*** Results: \n" )
530 for outputs in all_outputs:
531 src, dest, ping_outputs = outputs
532 sent, received, rttmin, rttavg, rttmax, rttdev = ping_outputs
533 output( " %s->%s: %s/%s, " % (src, dest, sent, received ) )
534 output( "rtt min/avg/max/mdev %0.3f/%0.3f/%0.3f/%0.3f ms\n" %
535 (rttmin, rttavg, rttmax, rttdev) )
536 return all_outputs
537
538 def pingAll( self ):
539 """Ping between all hosts.
540 returns: ploss packet loss percentage"""
541 return self.ping()
542
543 def pingPair( self ):
544 """Ping between first two hosts, useful for testing.
545 returns: ploss packet loss percentage"""
546 hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
547 return self.ping( hosts=hosts )
548
549 def pingAllFull( self ):
550 """Ping between all hosts.
551 returns: ploss packet loss percentage"""
552 return self.pingFull()
553
554 def pingPairFull( self ):
555 """Ping between first two hosts, useful for testing.
556 returns: ploss packet loss percentage"""
557 hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
558 return self.pingFull( hosts=hosts )
559
560 @staticmethod
561 def _parseIperf( iperfOutput ):
562 """Parse iperf output and return bandwidth.
563 iperfOutput: string
564 returns: result string"""
565 r = r'([\d\.]+ \w+/sec)'
566 m = re.findall( r, iperfOutput )
567 if m:
568 return m[-1]
569 else:
570 # was: raise Exception(...)
571 error( 'could not parse iperf output: ' + iperfOutput )
572 return ''
573
574 # XXX This should be cleaned up
575
576 def iperf( self, hosts=None, l4Type='TCP', udpBw='10M' ):
577 """Run iperf between two hosts.
578 hosts: list of hosts; if None, uses opposite hosts
579 l4Type: string, one of [ TCP, UDP ]
580 returns: results two-element array of server and client speeds"""
581 if not quietRun( 'which telnet' ):
582 error( 'Cannot find telnet in $PATH - required for iperf test' )
583 return
584 if not hosts:
585 hosts = [ self.hosts[ 0 ], self.hosts[ -1 ] ]
586 else:
587 assert len( hosts ) == 2
588 client, server = hosts
589 output( '*** Iperf: testing ' + l4Type + ' bandwidth between ' )
590 output( "%s and %s\n" % ( client.name, server.name ) )
591 server.cmd( 'killall -9 iperf' )
592 iperfArgs = 'iperf '
593 bwArgs = ''
594 if l4Type == 'UDP':
595 iperfArgs += '-u '
596 bwArgs = '-b ' + udpBw + ' '
597 elif l4Type != 'TCP':
598 raise Exception( 'Unexpected l4 type: %s' % l4Type )
599 server.sendCmd( iperfArgs + '-s', printPid=True )
600 servout = ''
601 while server.lastPid is None:
602 servout += server.monitor()
603 if l4Type == 'TCP':
604 while 'Connected' not in client.cmd(
605 'sh -c "echo A | telnet -e A %s 5001"' % server.IP()):
606 output('waiting for iperf to start up...')
607 sleep(.5)
608 cliout = client.cmd( iperfArgs + '-t 5 -c ' + server.IP() + ' ' +
609 bwArgs )
610 debug( 'Client output: %s\n' % cliout )
611 server.sendInt()
612 servout += server.waitOutput()
613 debug( 'Server output: %s\n' % servout )
614 result = [ self._parseIperf( servout ), self._parseIperf( cliout ) ]
615 if l4Type == 'UDP':
616 result.insert( 0, udpBw )
617 output( '*** Results: %s\n' % result )
618 return result
619
620 def runCpuLimitTest( self, cpu, duration=5 ):
621 """run CPU limit test with 'while true' processes.
622 cpu: desired CPU fraction of each host
623 duration: test duration in seconds
624 returns a single list of measured CPU fractions as floats.
625 """
626 pct = cpu * 100
627 info('*** Testing CPU %.0f%% bandwidth limit\n' % pct)
628 hosts = self.hosts
629 for h in hosts:
630 h.cmd( 'while true; do a=1; done &' )
631 pids = [h.cmd( 'echo $!' ).strip() for h in hosts]
632 pids_str = ",".join(["%s" % pid for pid in pids])
633 cmd = 'ps -p %s -o pid,%%cpu,args' % pids_str
634 # It's a shame that this is what pylint prefers
635 outputs = []
636 for _ in range( duration ):
637 sleep( 1 )
638 outputs.append( quietRun( cmd ).strip() )
639 for h in hosts:
640 h.cmd( 'kill %1' )
641 cpu_fractions = []
642 for test_output in outputs:
643 # Split by line. Ignore first line, which looks like this:
644 # PID %CPU COMMAND\n
645 for line in test_output.split('\n')[1:]:
646 r = r'\d+\s*(\d+\.\d+)'
647 m = re.search( r, line )
648 if m is None:
649 error( '*** Error: could not extract CPU fraction: %s\n' %
650 line )
651 return None
652 cpu_fractions.append( float( m.group( 1 ) ) )
653 output( '*** Results: %s\n' % cpu_fractions )
654 return cpu_fractions
655
656 # BL: I think this can be rewritten now that we have
657 # a real link class.
658 def configLinkStatus( self, src, dst, status ):
659 """Change status of src <-> dst links.
660 src: node name
661 dst: node name
662 status: string {up, down}"""
663 if src not in self.nameToNode:
664 error( 'src not in network: %s\n' % src )
665 elif dst not in self.nameToNode:
666 error( 'dst not in network: %s\n' % dst )
667 else:
668 if type( src ) is str:
669 src = self.nameToNode[ src ]
670 if type( dst ) is str:
671 dst = self.nameToNode[ dst ]
672 connections = src.connectionsTo( dst )
673 if len( connections ) == 0:
674 error( 'src and dst not connected: %s %s\n' % ( src, dst) )
675 for srcIntf, dstIntf in connections:
676 result = srcIntf.ifconfig( status )
677 if result:
678 error( 'link src status change failed: %s\n' % result )
679 result = dstIntf.ifconfig( status )
680 if result:
681 error( 'link dst status change failed: %s\n' % result )
682
683 def interact( self ):
684 "Start network and run our simple CLI."
685 self.start()
686 result = CLI( self )
687 self.stop()
688 return result
689
690 inited = False
691
692 @classmethod
693 def init( cls ):
694 "Initialize Mininet"
695 if cls.inited:
696 return
697 ensureRoot()
698 fixLimits()
699 cls.inited = True
700
701
702class MininetWithControlNet( Mininet ):
703
704 """Control network support:
705
706 Create an explicit control network. Currently this is only
707 used/usable with the user datapath.
708
709 Notes:
710
711 1. If the controller and switches are in the same (e.g. root)
712 namespace, they can just use the loopback connection.
713
714 2. If we can get unix domain sockets to work, we can use them
715 instead of an explicit control network.
716
717 3. Instead of routing, we could bridge or use 'in-band' control.
718
719 4. Even if we dispense with this in general, it could still be
720 useful for people who wish to simulate a separate control
721 network (since real networks may need one!)
722
723 5. Basically nobody ever used this code, so it has been moved
724 into its own class.
725
726 6. Ultimately we may wish to extend this to allow us to create a
727 control network which every node's control interface is
728 attached to."""
729
730 def configureControlNetwork( self ):
731 "Configure control network."
732 self.configureRoutedControlNetwork()
733
734 # We still need to figure out the right way to pass
735 # in the control network location.
736
737 def configureRoutedControlNetwork( self, ip='192.168.123.1',
738 prefixLen=16 ):
739 """Configure a routed control network on controller and switches.
740 For use with the user datapath only right now."""
741 controller = self.controllers[ 0 ]
742 info( controller.name + ' <->' )
743 cip = ip
744 snum = ipParse( ip )
745 for switch in self.switches:
746 info( ' ' + switch.name )
747 link = self.link( switch, controller, port1=0 )
748 sintf, cintf = link.intf1, link.intf2
749 switch.controlIntf = sintf
750 snum += 1
751 while snum & 0xff in [ 0, 255 ]:
752 snum += 1
753 sip = ipStr( snum )
754 cintf.setIP( cip, prefixLen )
755 sintf.setIP( sip, prefixLen )
756 controller.setHostRoute( sip, cintf )
757 switch.setHostRoute( cip, sintf )
758 info( '\n' )
759 info( '*** Testing control network\n' )
760 while not cintf.isUp():
761 info( '*** Waiting for', cintf, 'to come up\n' )
762 sleep( 1 )
763 for switch in self.switches:
764 while not sintf.isUp():
765 info( '*** Waiting for', sintf, 'to come up\n' )
766 sleep( 1 )
767 if self.ping( hosts=[ switch, controller ] ) != 0:
768 error( '*** Error: control network test failed\n' )
769 exit( 1 )
770 info( '\n' )