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 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 _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+) 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 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: print "*** Enter a command for node: %s <cmd>" % 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 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 precheck( self ): """Pre-check to make sure connection works and that we can call sudo without a password""" result = 0 info( '*** Checking servers\n' ) for server in self.servers: ip = self.serverIP[ server ] if not server or server == 'localhost': continue info( server, '' ) dest = '%s@%s' % ( self.user, ip ) cmd = [ 'sudo', '-E', '-u', self.user ] cmd += self.sshcmd + [ '-n', dest, 'sudo true' ] debug( ' '.join( cmd ), '\n' ) _out, _err, code = errRun( cmd ) if code != 0: error( '\nstartConnection: server connection check failed ' 'to %s using command:\n%s\n' % ( server, ' '.join( cmd ) ) ) result |= code if result: error( '*** Server precheck failed.\n' '*** Make sure that the above ssh command works' ' correctly.\n' '*** You may also need to run mn -c on all nodes, and/or\n' '*** use sudo -E.\n' ) sys.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 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_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 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_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_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 checkIntf( intf ): "Make sure intf exists and is not configured." config = quietRun( 'ifconfig %s 2>/dev/null' % intf, shell=True ) if not config: error( 'Error:', intf, 'does not exist!\n' ) exit( 1 ) ips = re.findall( r'\d+\.\d+\.\d+\.\d+', config ) if ips: error( 'Error:', intf, 'has an IP address,' 'and is probably in use!\n' ) exit( 1 )
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 src.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 src.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 _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 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 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 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 _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 do_plot( self, _line ): "Plot topology colored by node placement" # Import networkx if needed global nx, plt if not nx: try: # pylint: disable=import-error import networkx nx = networkx # satisfy pylint from matplotlib import pyplot plt = pyplot # satisfiy pylint import pygraphviz assert pygraphviz # silence pyflakes # pylint: enable=import-error except ImportError: error( 'plot requires networkx, matplotlib and pygraphviz - ' 'please install them and try again\n' ) return # Make a networkx Graph g = nx.Graph() mn = self.mn servers, hosts, switches = mn.servers, mn.hosts, mn.switches nodes = hosts + switches g.add_nodes_from( nodes ) links = [ ( link.intf1.node, link.intf2.node ) for link in self.mn.links ] g.add_edges_from( links ) # Pick some shapes and colors # shapes = hlen * [ 's' ] + slen * [ 'o' ] color = dict( zip( servers, self.colorsFor( servers ) ) ) # Plot it! pos = nx.graphviz_layout( g ) opts = { 'ax': None, 'font_weight': 'bold', 'width': 2, 'edge_color': 'darkblue' } hcolors = [ color[ getattr( h, 'server', 'localhost' ) ] for h in hosts ] scolors = [ color[ getattr( s, 'server', 'localhost' ) ] for s in switches ] nx.draw_networkx( g, pos=pos, nodelist=hosts, node_size=800, label='host', node_color=hcolors, node_shape='s', **opts ) nx.draw_networkx( g, pos=pos, nodelist=switches, node_size=1000, node_color=scolors, node_shape='o', **opts ) # Get rid of axes, add title, and show fig = plt.gcf() ax = plt.gca() ax.get_xaxis().set_visible( False ) ax.get_yaxis().set_visible( False ) fig.canvas.set_window_title( 'Mininet') plt.title( 'Node Placement', fontweight='bold' ) plt.show()
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 * 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 ping(self, hosts=None, timeout=None, manualdestip=None): """Ping between all specified hosts. hosts: list of hosts timeout: time to wait for a response, as string manualdestip: sends pings from each h in hosts to manualdestip 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) if manualdestip is not None: opts = '' if timeout: opts = '-W %s' % timeout result = node.cmd('ping -c1 %s %s' % (opts, manualdestip)) sent, received = self._parsePing(result) packets += sent if received > sent: error('*** Error: received too many packets') error('%s' % result) node.cmdPrint('route') exit(1) lost += sent - received output(('%s ' % manualdestip) if received else 'X ') else: 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 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 moveIntf( intf, node, printError=True ): """Move remote interface from root ns to node intf: string, interface dstNode: destination Node srcNode: source Node or None (default) for root ns printError: if true, print error""" intf = str( intf ) cmd = 'ip link set %s netns %s' % ( intf, node.pid ) node.rcmd( cmd ) links = node.cmd( 'ip link show' ) if not ' %s:' % intf in links: if printError: error( '*** Error: RemoteLink.moveIntf: ' + intf + ' not successfully moved to ' + node.name + '\n' ) return False return True
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_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_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 removeHost(self, name, **params): """ Remove a host from the network at runtime. """ if not isinstance(name, basestring) and name is not None: name = name.name # if we get a host object try: h = self.get(name) except: error("Host: %s not found. Cannot remove it.\n" % name) return False if h is not None: if h in self.hosts: self.hosts.remove(h) if name in self.nameToNode: del self.nameToNode[name] h.stop(deleteIntfs=True) debug("Removed: %s\n" % name) return True return False
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 removeLink(self, link=None, node1=None, node2=None): """ Removes a link. Can either be specified by link object, or the nodes the link connects. """ if link is None: if (isinstance(node1, basestring) and isinstance(node2, basestring)): try: node1 = self.get(node1) except: error("Host: %s not found.\n" % node1) try: node2 = self.get(node2) except: error("Host: %s not found.\n" % node2) # try to find link by nodes for l in self.links: if l.intf1.node == node1 and l.intf2.node == node2: link = l break if l.intf1.node == node2 and l.intf2.node == node1: link = l break if link is None: error("Couldn't find link to be removed.\n") return # tear down the link link.delete() self.links.remove(link)
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: if isinstance(src, basestring): src = self.nameToNode[src] if isinstance(dst, basestring): 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)