def _parsePingFull(pingOutput): "Parse ping output and return all data." errorTuple = (1, 0, 0, 0, 0, 0) # Check for downed link r = r'[uU]nreachable' m = re.search(r, pingOutput) if m is not None: return errorTuple r = r'(\d+) packets transmitted, (\d+)( packets)? received' m = re.search(r, pingOutput) if m is None: error('*** Error: could not parse ping output: %s\n' % pingOutput) return errorTuple sent, received = int(m.group(1)), int(m.group(2)) r = r'rtt min/avg/max/mdev = ' r += r'(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+) ms' m = re.search(r, pingOutput) if m is None: if received == 0: return errorTuple error('*** Error: could not parse ping output: %s\n' % pingOutput) return errorTuple rttmin = float(m.group(1)) rttavg = float(m.group(2)) rttmax = float(m.group(3)) rttdev = float(m.group(4)) return sent, received, rttmin, rttavg, rttmax, rttdev
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:'vdlocalmnexec -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 configureRoutedControlNetwork(self, ip='192.168.123.1', prefixLen=16): """Configure a routed control network on controller and switches. For use with the user datapath only right now.""" controller = self.controllers[0] info(controller.name + ' <->') cip = ip snum = ipParse(ip) for switch in self.switches: info(' ' + switch.name) link = self.link(switch, controller, port1=0) sintf, cintf = link.intf1, link.intf2 switch.controlIntf = sintf snum += 1 while snum & 0xff in [0, 255]: snum += 1 sip = ipStr(snum) cintf.setIP(cip, prefixLen) sintf.setIP(sip, prefixLen) controller.setHostRoute(sip, cintf) switch.setHostRoute(cip, sintf) info('\n') info('*** Testing control network\n') while not cintf.isUp(): info('*** Waiting for', cintf, 'to come up\n') sleep(1) for switch in self.switches: while not sintf.isUp(): info('*** Waiting for', sintf, 'to come up\n') sleep(1) if self.ping(hosts=[switch, controller]) != 0: error('*** Error: control network test failed\n') exit(1) info('\n')
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 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 startTerms(self): "Start a terminal for each node." if 'DISPLAY' not in os.environ: error("Error starting terms: Cannot connect to display\n") return info("*** Running terms on %s\n" % os.environ['DISPLAY']) cleanUpScreens() self.terms += makeTerms(self.controllers, 'controller') self.terms += makeTerms(self.switches, 'switch') self.terms += makeTerms(self.hosts, 'host')
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 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 _parseIperf(iperfOutput): """Parse iperf output and return bandwidth. iperfOutput: string returns: result string""" r = r'([\d\.]+ \w+/sec)' m = re.findall(r, iperfOutput) if m: return m[-1] else: # was: raise Exception(...) error('could not parse iperf output: ' + iperfOutput) return ''
def _parsePing(pingOutput): "Parse ping output and return packets sent, received." # Check for downed link if 'connect: Network is unreachable' in pingOutput: return 1, 0 r = r'(\d+) packets transmitted, (\d+)( packets)? received' m = re.search(r, pingOutput) if m is None: error('*** Error: could not parse ping output: %s\n' % pingOutput) return 1, 0 sent, received = int(m.group(1)), int(m.group(2)) return sent, received
def isUp( self, setUp=False ): "Return whether interface is up" if setUp: cmdOutput = self.ifconfig( 'up' ) # no output indicates success if cmdOutput: error( "Error setting %s up: %s " % ( self.name, cmdOutput ) ) return False else: return True else: return "UP" in self.ifconfig()
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 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
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 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 -icanon min 1') while True: 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: 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() 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 ping(self, hosts=None, timeout=None): """Ping between all specified hosts. hosts: list of hosts timeout: time to wait for a response, as string returns: ploss packet loss percentage""" # should we check if running? packets = 0 lost = 0 ploss = None if not hosts: hosts = self.hosts output('*** Ping: testing ping reachability\n') for node in hosts: output('%s -> ' % node.name) for dest in hosts: if node != dest: opts = '' if timeout: opts = '-W %s' % timeout if dest.intfs: result = node.cmd('ping -c1 %s %s' % (opts, dest.IP())) sent, received = self._parsePing(result) else: sent, received = 0, 0 packets += sent if received > sent: error('*** Error: received too many packets') error('%s' % result) node.cmdPrint('route') exit(1) lost += sent - received output(('%s ' % dest.name) if received else 'X ') output('\n') if packets > 0: ploss = 100.0 * lost / packets received = packets - lost output("*** Results: %i%% dropped (%d/%d received)\n" % (ploss, received, packets)) else: ploss = 0 output("*** Warning: No packets sent\n") return ploss
def delayCmds( parent, delay=None, jitter=None, loss=None, max_queue_size=None ): "Internal method: return tc commands for delay and loss" cmds = [] 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 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 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_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 configLinkStatus(self, src, dst, status): """Change status of src <-> dst links. src: node name dst: node name status: string {up, down}""" if src not in self.nameToNode: error('src not in network: %s\n' % src) elif dst not in self.nameToNode: error('dst not in network: %s\n' % dst) else: src = self.nameToNode[src] dst = self.nameToNode[dst] connections = src.connectionsTo(dst) if len(connections) == 0: error('src and dst not connected: %s %s\n' % (src, dst)) for srcIntf, dstIntf in connections: result = srcIntf.ifconfig(status) if result: error('link src status change failed: %s\n' % result) result = dstIntf.ifconfig(status) if result: error('link dst status change failed: %s\n' % result)
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 != '': error( "*** Error: %s" % output ) debug( "cmds:", cmds, '\n' ) debug( "outputs:", tcoutputs, '\n' ) result[ 'tcoutputs'] = tcoutputs result[ 'parent' ] = parent return result