def waitListening( client=None, server='127.0.0.1', port=80, timeout=None ): """Wait until server is listening on port. returns True if server is listening""" runCmd = ( client.cmd if client else partial( quietRun, shell=True ) ) if not runCmd( 'which telnet' ): raise Exception('Could not find telnet' ) # pylint: disable=maybe-no-member serverIP = server if isinstance( server, BaseString ) else server.IP() cmd = ( 'echo A | telnet -e A %s %s' % ( serverIP, port ) ) time = 0 result = runCmd( cmd ) while 'Connected' not in result: if 'No route' in result: rtable = runCmd( 'route' ) error( 'no route to %s:\n%s' % ( server, rtable ) ) return False if timeout and time >= timeout: error( 'could not connect to %s on port %d\n' % ( server, port ) ) return False debug( 'waiting for', server, 'to listen on port', port, '\n' ) info( '.' ) sleep( .5 ) time += .5 result = runCmd( cmd ) return True
def tunnelX11(node, display=None): """Create an X11 tunnel from node:6000 to the root host display: display on root host (optional) returns: node $DISPLAY, Popen object for tunnel""" if display is None and 'DISPLAY' in environ: display = environ['DISPLAY'] if display is None: error("Error: Cannot connect to display\n") return None, None host, screen = display.split(':') # Unix sockets should work if not host or host == 'unix': # GDM3 doesn't put credentials in .Xauthority, # so allow root to just connect quietRun('xhost +si:localuser:root') return display, None else: # Create a tunnel for the TCP connection port = 6000 + int(float(screen)) connection = r'TCP\:%s\:%s' % (host, port) cmd = [ "socat", "TCP-LISTEN:%d,fork,reuseaddr" % port, "EXEC:'mnexec -a 1 socat STDIO %s'" % connection ] return 'localhost:' + screen, node.popen(cmd)
def default(self, line): """Called on an input line when the command prefix is not recognized. Overridden to run shell commands when a node is the first CLI argument. Past the first CLI argument, node names are automatically replaced with corresponding IP addrs.""" first, args, line = self.parseline(line) if first in self.mn: if not args: error('*** Please enter a command for node: %s <cmd>\n' % first) return node = self.mn[first] rest = args.split(' ') # Substitute IP addresses for node names in command # If updateIP() returns None, then use node name rest = [ self.mn[arg].defaultIntf().updateIP() or arg if arg in self.mn else arg for arg in rest ] rest = ' '.join(rest) # Run cmd on node: node.sendCmd(rest) self.waitForNode(node) else: error('*** Unknown command: %s\n' % line)
def delayCmds(parent, delay=None, jitter=None, loss=None, max_queue_size=None): "Internal method: return tc commands for delay and loss" cmds = [] # if delay and float(delay) < 0: # error( 'Negative delay', delay, '\n' ) # elif jitter and float(jitter) < 0: # error( 'Negative jitter', jitter, '\n' ) # elif loss and ( float(loss) < 0 or float(loss) > 100 ): if loss and (loss < 0 or loss > 100): error('Bad loss percentage', loss, '%%\n') else: # Delay/jitter/loss/max queue size netemargs = '%s%s%s%s' % ( 'delay %s ' % delay if delay is not None else '', '%s ' % jitter if jitter is not None else '', 'loss %.5f ' % loss if (loss is not None and loss > 0) else '', 'limit %d' % max_queue_size if max_queue_size is not None else '') if netemargs: cmds = [ '%s qdisc add dev %s ' + parent + ' handle 10: netem ' + netemargs ] parent = ' parent 10:1 ' return cmds, parent
def ensureRoot(): """Ensure that we are running as root. Probably we should only sudo when needed as per Big Switch's patch. """ if os.getuid() != 0: error( '*** Mininet must run as root.\n' ) exit( 1 ) return
def pathCheck( *args, **kwargs ): "Make sure each program in *args can be found in $PATH." moduleName = kwargs.get( 'moduleName', 'it' ) for arg in args: if not quietRun( 'which ' + arg ): error( 'Cannot find required executable %s.\n' % arg + 'Please make sure that %s is installed ' % moduleName + 'and available in your $PATH:\n(%s)\n' % environ[ 'PATH' ] ) exit( 1 )
def do_dpctl(self, line): """Run dpctl (or ovs-ofctl) command on all switches. Usage: dpctl command [arg1] [arg2] ...""" args = line.split() if len(args) < 1: error('usage: dpctl command [arg1] [arg2] ...\n') return for sw in self.mn.switches: output('*** ' + sw.name + ' ' + ('-' * 72) + '\n') output(sw.dpctl(*args))
def do_link(self, line): """Bring link(s) between two nodes up or down. Usage: link node1 node2 [up/down]""" args = line.split() if len(args) != 3: error('invalid number of args: link end1 end2 [up down]\n') elif args[2] not in ['up', 'down']: error('invalid type: link end1 end2 [up down]\n') else: self.mn.configLinkStatus(*args)
def makeTerm(node, title='Node', term='xterm', display=None, cmd='bash'): """Create an X11 tunnel to the node and start up a terminal. node: Node object title: base title term: 'xterm' or 'gterm' returns: two Popen objects, tunnel and terminal""" title = '"%s: %s"' % (title, node.name) if not node.inNamespace: title += ' (root)' cmds = { 'xterm': ['xterm', '-title', title, '-display'], 'gterm': ['gnome-terminal', '--title', title, '--display'] } if term not in cmds: error('invalid terminal type: %s' % term) return # Docker Hosts don't have DISPLAY. So instead of # X11 tunnel, we use terminals from outside Docker from mininet.node import Docker if isinstance(node, Docker): if not node._is_container_running(): return [] if display is None: cmds[term] = cmds[term][:-1] else: cmds[term].append(display) cmds[term].append('-e') from subprocess import Popen, PIPE term = Popen(cmds[term] + [ 'env TERM=ansi docker exec -it %s.%s %s' % (node.dnameprefix, node.name, cmd) ], stdout=PIPE, stdin=PIPE, stderr=PIPE) if term: return [term] # Failover alternative for Docker # host = None --> Needs docker interface # port = 6000 # from mininet.log import warn # warn('Warning: Docker Exec Failed. Trying TCP Connection for Terminal\n') # pipe = node.popen( [ 'socat', 'TCP-LISTEN:%d,fork,reuseaddr' % port, # 'EXEC:\'%s\',pty,stderr,setsid,sigint,sane' % cmd ] ); # term = Popen( cmds[ term ] + # [ 'socat FILE:`tty`,raw,echo=0 TCP:%s:%s' % ( # host, port ) ], stdout=PIPE, stdin=PIPE, stderr=PIPE ) # return [ pipe, term ] if pipe else [ term ] return [] display, tunnel = tunnelX11(node, display) if display is None: return [] term = node.popen(cmds[term] + [display, '-e', 'env TERM=ansi %s' % cmd]) return [tunnel, term] if tunnel else [term]
def do_x(self, line): """Create an X11 tunnel to the given node, optionally starting a client. Usage: x node [cmd args]""" args = line.split() if not args: error('usage: x node [cmd args]...\n') else: node = self.mn[args[0]] cmd = args[1:] self.mn.terms += runX11(node, cmd)
def isUp(self, setUp=False): "Return whether interface is up" if setUp: cmdOutput = self.ifconfig('up') # no output / command output indicates success if (len(cmdOutput) > 0 and "ifconfig" not in cmdOutput): error("Error setting %s up: %s " % (self.name, cmdOutput)) return False else: return True else: return "UP" in self.ifconfig()
def retry( retries, delaySecs, fn, *args, **keywords ): """Try something several times before giving up. n: number of times to retry delaySecs: wait this long between tries fn: function to call args: args to apply to function call""" tries = 0 while not fn( *args, **keywords ) and tries < retries: sleep( delaySecs ) tries += 1 if tries >= retries: error( "*** gave up after %i retries\n" % tries ) exit( 1 )
def do_xterm(self, line, term='xterm'): """Spawn xterm(s) for the given node(s). Usage: xterm node1 node2 ...""" args = line.split() if not args: error('usage: %s node1 node2 ...\n' % term) else: for arg in args: if arg not in self.mn: error("node '%s' not in network\n" % arg) else: node = self.mn[arg] self.mn.terms += makeTerms([node], term=term)
def moduleDeps( subtract=None, add=None ): """Handle module dependencies. subtract: string or list of module names to remove, if already loaded add: string or list of module names to add, if not already loaded""" subtract = subtract if subtract is not None else [] add = add if add is not None else [] if isinstance( subtract, BaseString ): subtract = [ subtract ] if isinstance( add, BaseString ): add = [ add ] for mod in subtract: if mod in lsmod(): info( '*** Removing ' + mod + '\n' ) rmmodOutput = rmmod( mod ) if rmmodOutput: error( 'Error removing ' + mod + ': "%s">\n' % rmmodOutput ) exit( 1 ) if mod in lsmod(): error( 'Failed to remove ' + mod + '; still there!\n' ) exit( 1 ) for mod in add: if mod not in lsmod(): info( '*** Loading ' + mod + '\n' ) modprobeOutput = modprobe( mod ) if modprobeOutput: error( 'Error inserting ' + mod + ' - is it installed and available via modprobe?\n' + 'Error was: "%s"\n' % modprobeOutput ) if mod not in lsmod(): error( 'Failed to insert ' + mod + ' - quitting.\n' ) exit( 1 ) else: debug( '*** ' + mod + ' already loaded\n' )
def waitForNode(self, node): "Wait for a node to finish, and print its output." # Pollers nodePoller = poll() nodePoller.register(node.stdout) bothPoller = poll() bothPoller.register(self.stdin, POLLIN) bothPoller.register(node.stdout, POLLIN) if self.isatty(): # Buffer by character, so that interactive # commands sort of work quietRun('stty -isig -icanon min 1') while node.shell: try: bothPoller.poll() # XXX BL: this doesn't quite do what we want. if False and self.inputFile: key = self.inputFile.read(1) if key is not '': node.write(key) else: self.inputFile = None if isReadable(self.inPoller): key = self.stdin.read(1) node.write(key) if isReadable(nodePoller): data = node.monitor() output(data) if not node.waiting: quietRun('stty isig') break except KeyboardInterrupt: # There is an at least one race condition here, since # it's possible to interrupt ourselves after we've # read data but before it has been printed. node.sendInt() """ #TODO THERE ARE YOU NEED TO SEND OWN INTERRUPT """ except select.error as e: # pylint: disable=unpacking-non-sequence errno_, errmsg = e.args # pylint: enable=unpacking-non-sequence if errno_ != errno.EINTR: error("select.error: %d, %s" % (errno_, errmsg)) node.sendInt()
def moveIntfNoRetry( intf, dstNode, printError=False ): """Move interface to node, without retrying. intf: string, interface dstNode: destination Node printError: if true, print error""" intf = str( intf ) cmd = 'ip link set %s netns %s' % ( intf, dstNode.pid ) cmdOutput = quietRun( cmd ) # If ip link set does not produce any output, then we can assume # that the link has been moved successfully. if cmdOutput: if printError: error( '*** Error: moveIntf: ' + intf + ' not successfully moved to ' + dstNode.name + ':\n', cmdOutput ) return False return True
def do_source(self, line): """Read commands from an input file. Usage: source <file>""" args = line.split() if len(args) != 1: error('usage: source <file>\n') return try: self.inputFile = open(args[0]) while True: line = self.inputFile.readline() if len(line) > 0: self.onecmd(line) else: break except IOError: error('error reading file %s\n' % args[0]) self.inputFile.close() self.inputFile = None
def do_iperf(self, line): """Simple iperf TCP test between two (optionally specified) hosts. Usage: iperf node1 node2""" args = line.split() if not args: self.mn.iperf() elif len(args) == 2: hosts = [] err = False for arg in args: if arg not in self.mn: err = True error("node '%s' not in network\n" % arg) else: hosts.append(self.mn[arg]) if not err: self.mn.iperf(hosts) else: error('invalid number of args: iperf src dst\n')
def do_iperfudp(self, line): """Simple iperf UDP test between two (optionally specified) hosts. Usage: iperfudp bw node1 node2""" args = line.split() if not args: self.mn.iperf(l4Type='UDP') elif len(args) == 3: udpBw = args[0] hosts = [] err = False for arg in args[1:3]: if arg not in self.mn: err = True error("node '%s' not in network\n" % arg) else: hosts.append(self.mn[arg]) if not err: self.mn.iperf(hosts, l4Type='UDP', udpBw=udpBw) else: error('invalid number of args: iperfudp bw src dst\n' + 'bw examples: 10M\n')
def do_switch(self, line): "Starts or stops a switch" args = line.split() if len(args) != 2: error('invalid number of args: switch <switch name>' '{start, stop}\n') return sw = args[0] command = args[1] if sw not in self.mn or self.mn.get(sw) not in self.mn.switches: error('invalid switch: %s\n' % args[1]) else: sw = args[0] command = args[1] if command == 'start': self.mn.get(sw).start(self.mn.controllers) elif command == 'stop': self.mn.get(sw).stop(deleteIntfs=False) else: error('invalid command: ' 'switch <switch name> {start, stop}\n')
def config(self, bw=None, delay=None, jitter=None, loss=None, gro=False, txo=True, rxo=True, speedup=0, use_hfsc=False, use_tbf=False, latency_ms=None, enable_ecn=False, enable_red=False, max_queue_size=None, **params): """Configure the port and set its properties. bw: bandwidth in b/s (e.g. '10m') delay: transmit delay (e.g. '1ms' ) jitter: jitter (e.g. '1ms') loss: loss (e.g. '1%' ) gro: enable GRO (False) txo: enable transmit checksum offload (True) rxo: enable receive checksum offload (True) speedup: experimental switch-side bw option use_hfsc: use HFSC scheduling use_tbf: use TBF scheduling latency_ms: TBF latency parameter enable_ecn: enable ECN (False) enable_red: enable RED (False) max_queue_size: queue limit parameter for netem""" # Support old names for parameters gro = not params.pop('disable_gro', not gro) result = Intf.config(self, **params) def on(isOn): "Helper method: bool -> 'on'/'off'" return 'on' if isOn else 'off' # Set offload parameters with ethool self.cmd('ethtool -K', self, 'gro', on(gro), 'tx', on(txo), 'rx', on(rxo)) # Optimization: return if nothing else to configure # Question: what happens if we want to reset things? if (bw is None and not delay and not loss and max_queue_size is None): return # Clear existing configuration tcoutput = self.tc('%s qdisc show dev %s') if "priomap" not in tcoutput and "noqueue" not in tcoutput: cmds = ['%s qdisc del dev %s root'] else: cmds = [] # Bandwidth limits via various methods bwcmds, parent = self.bwCmds(bw=bw, speedup=speedup, use_hfsc=use_hfsc, use_tbf=use_tbf, latency_ms=latency_ms, enable_ecn=enable_ecn, enable_red=enable_red) cmds += bwcmds # Delay/jitter/loss/max_queue_size using netem delaycmds, parent = self.delayCmds(delay=delay, jitter=jitter, loss=loss, max_queue_size=max_queue_size, parent=parent) cmds += delaycmds # Ugly but functional: display configuration info stuff = ((['%.2fMbit' % bw] if bw is not None else []) + (['%s delay' % delay] if delay is not None else []) + (['%s jitter' % jitter] if jitter is not None else []) + (['%.5f%% loss' % loss] if loss is not None else []) + (['ECN'] if enable_ecn else ['RED'] if enable_red else [])) info('(' + ' '.join(stuff) + ') ') # Execute all the commands in our node debug("at map stage w/cmds: %s\n" % cmds) tcoutputs = [self.tc(cmd) for cmd in cmds] for output in tcoutputs: if output != '' and output != 'RTNETLINK answers: No such file or directory\r\n': error("*** Error: %s" % output) debug("cmds:", cmds, '\n') debug("outputs:", tcoutputs, '\n') result['tcoutputs'] = tcoutputs result['parent'] = parent return result
def bwCmds(self, bw=None, speedup=0, use_hfsc=False, use_tbf=False, latency_ms=None, enable_ecn=False, enable_red=False): "Return tc commands to set bandwidth" cmds, parent = [], ' root ' if bw and (bw < 0 or bw > self.bwParamMax): error('Bandwidth limit', bw, 'is outside supported range 0..%d' % self.bwParamMax, '- ignoring\n') elif bw is not None: # BL: this seems a bit brittle... if (speedup > 0 and self.node.name[0:1] == 's'): bw = speedup # This may not be correct - we should look more closely # at the semantics of burst (and cburst) to make sure we # are specifying the correct sizes. For now I have used # the same settings we had in the mininet-hifi code. if use_hfsc: cmds += [ '%s qdisc add dev %s root handle 5:0 hfsc default 1', '%s class add dev %s parent 5:0 classid 5:1 hfsc sc ' + 'rate %fMbit ul rate %fMbit' % (bw, bw) ] elif use_tbf: if latency_ms is None: latency_ms = 15.0 * 8 / bw cmds += [ '%s qdisc add dev %s root handle 5: tbf ' + 'rate %fMbit burst 15000 latency %fms' % (bw, latency_ms) ] else: cmds += [ '%s qdisc add dev %s root handle 5:0 htb default 1', '%s class add dev %s parent 5:0 classid 5:1 htb ' + 'rate %fMbit burst 15k' % bw ] parent = ' parent 5:1 ' # ECN or RED if enable_ecn: cmds += [ '%s qdisc add dev %s' + parent + 'handle 6: red limit 1000000 ' + 'min 30000 max 35000 avpkt 1500 ' + 'burst 20 ' + 'bandwidth %fmbit probability 1 ecn' % bw ] parent = ' parent 6: ' elif enable_red: cmds += [ '%s qdisc add dev %s' + parent + 'handle 6: red limit 1000000 ' + 'min 30000 max 35000 avpkt 1500 ' + 'burst 20 ' + 'bandwidth %fmbit probability 1' % bw ] parent = ' parent 6: ' return cmds, parent