First commit
diff --git a/mininet/cli.py b/mininet/cli.py
new file mode 100644
index 0000000..efb1808
--- /dev/null
+++ b/mininet/cli.py
@@ -0,0 +1,366 @@
+"""
+A simple command-line interface for Mininet.
+
+The Mininet CLI provides a simple control console which
+makes it easy to talk to nodes. For example, the command
+
+mininet> h27 ifconfig
+
+runs 'ifconfig' on host h27.
+
+Having a single console rather than, for example, an xterm for each
+node is particularly convenient for networks of any reasonable
+size.
+
+The CLI automatically substitutes IP addresses for node names,
+so commands like
+
+mininet> h2 ping h3
+
+should work correctly and allow host h2 to ping host h3
+
+Several useful commands are provided, including the ability to
+list all nodes ('nodes'), to print out the network topology
+('net') and to check connectivity ('pingall', 'pingpair')
+and bandwidth ('iperf'.)
+"""
+
+from subprocess import call
+from cmd import Cmd
+from os import isatty
+from select import poll, POLLIN
+import sys
+import time
+
+from mininet.log import info, output, error
+from mininet.term import makeTerms
+from mininet.util import quietRun, isShellBuiltin, dumpNodeConnections
+
+class CLI( Cmd ):
+    "Simple command-line interface to talk to nodes."
+
+    prompt = 'mininet> '
+
+    def __init__( self, mininet, stdin=sys.stdin, script=None ):
+        self.mn = mininet
+        self.nodelist = self.mn.controllers + self.mn.switches + self.mn.hosts
+        self.nodemap = {}  # map names to Node objects
+        for node in self.nodelist:
+            self.nodemap[ node.name ] = node
+        # Local variable bindings for py command
+        self.locals = { 'net': mininet }
+        self.locals.update( self.nodemap )
+        # Attempt to handle input
+        self.stdin = stdin
+        self.inPoller = poll()
+        self.inPoller.register( stdin )
+        self.inputFile = script
+        Cmd.__init__( self )
+        info( '*** Starting CLI:\n' )
+        if self.inputFile:
+            self.do_source( self.inputFile )
+            return
+        while True:
+            try:
+                # Make sure no nodes are still waiting
+                for node in self.nodelist:
+                    while node.waiting:
+                        node.sendInt()
+                        node.monitor()
+                if self.isatty():
+                    quietRun( 'stty sane' )
+                self.cmdloop()
+                break
+            except KeyboardInterrupt:
+                output( '\nInterrupt\n' )
+
+    def emptyline( self ):
+        "Don't repeat last command when you hit return."
+        pass
+
+    # Disable pylint "Unused argument: 'arg's'" messages, as well as
+    # "method could be a function" warning, since each CLI function
+    # must have the same interface
+    # pylint: disable-msg=R0201
+
+    helpStr = (
+        'You may also send a command to a node using:\n'
+        '  <node> command {args}\n'
+        'For example:\n'
+        '  mininet> h1 ifconfig\n'
+        '\n'
+        'The interpreter automatically substitutes IP addresses\n'
+        'for node names when a node is the first arg, so commands\n'
+        'like\n'
+        '  mininet> h2 ping h3\n'
+        'should work.\n'
+        '\n'
+        'Some character-oriented interactive commands require\n'
+        'noecho:\n'
+        '  mininet> noecho h2 vi foo.py\n'
+        'However, starting up an xterm/gterm is generally better:\n'
+        '  mininet> xterm h2\n\n'
+    )
+
+    def do_help( self, line ):
+        "Describe available CLI commands."
+        Cmd.do_help( self, line )
+        if line is '':
+            output( self.helpStr )
+
+    def do_nodes( self, _line ):
+        "List all nodes."
+        nodes = ' '.join( [ node.name for node in sorted( self.nodelist ) ] )
+        output( 'available nodes are: \n%s\n' % nodes )
+
+    def do_net( self, _line ):
+        "List network connections."
+        dumpNodeConnections( self.nodelist )
+
+    def do_sh( self, line ):
+        "Run an external shell command"
+        call( line, shell=True )
+
+    # do_py() needs to catch any exception during eval()
+    # pylint: disable-msg=W0703
+
+    def do_py( self, line ):
+        """Evaluate a Python expression.
+           Node names may be used, e.g.: h1.cmd('ls')"""
+        try:
+            result = eval( line, globals(), self.locals )
+            if not result:
+                return
+            elif isinstance( result, str ):
+                output( result + '\n' )
+            else:
+                output( repr( result ) + '\n' )
+        except Exception, e:
+            output( str( e ) + '\n' )
+
+    # pylint: enable-msg=W0703
+
+    def do_pingall( self, _line ):
+        "Ping between all hosts."
+        self.mn.pingAll()
+
+    def do_pingpair( self, _line ):
+        "Ping between first two hosts, useful for testing."
+        self.mn.pingPair()
+
+    def do_pingallfull( self, _line ):
+        "Ping between first two hosts, returns all ping results."
+        self.mn.pingAllFull()
+
+    def do_pingpairfull( self, _line ):
+        "Ping between first two hosts, returns all ping results."
+        self.mn.pingPairFull()
+
+    def do_iperf( self, line ):
+        "Simple iperf TCP test between two (optionally specified) hosts."
+        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.nodemap:
+                    err = True
+                    error( "node '%s' not in network\n" % arg )
+                else:
+                    hosts.append( self.nodemap[ 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 TCP test between two (optionally specified) hosts."
+        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.nodemap:
+                    err = True
+                    error( "node '%s' not in network\n" % arg )
+                else:
+                    hosts.append( self.nodemap[ 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_intfs( self, _line ):
+        "List interfaces."
+        for node in self.nodelist:
+            output( '%s: %s\n' %
+                    ( node.name, ','.join( node.intfNames() ) ) )
+
+    def do_dump( self, _line ):
+        "Dump node info."
+        for node in self.nodelist:
+            output( '%s\n' % repr( node ) )
+
+    def do_link( self, line ):
+        "Bring link(s) between two nodes up or 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_xterm( self, line, term='xterm' ):
+        "Spawn xterm(s) for the given node(s)."
+        args = line.split()
+        if not args:
+            error( 'usage: %s node1 node2 ...\n' % term )
+        else:
+            for arg in args:
+                if arg not in self.nodemap:
+                    error( "node '%s' not in network\n" % arg )
+                else:
+                    node = self.nodemap[ arg ]
+                    self.mn.terms += makeTerms( [ node ], term = term )
+
+    def do_gterm( self, line ):
+        "Spawn gnome-terminal(s) for the given node(s)."
+        self.do_xterm( line, term='gterm' )
+
+    def do_exit( self, _line ):
+        "Exit"
+        return 'exited by user command'
+
+    def do_quit( self, line ):
+        "Exit"
+        return self.do_exit( line )
+
+    def do_EOF( self, line ):
+        "Exit"
+        output( '\n' )
+        return self.do_exit( line )
+
+    def isatty( self ):
+        "Is our standard input a tty?"
+        return isatty( self.stdin.fileno() )
+
+    def do_noecho( self, line ):
+        "Run an interactive command with echoing turned off."
+        if self.isatty():
+            quietRun( 'stty -echo' )
+        self.default( line )
+        if self.isatty():
+            quietRun( 'stty echo' )
+
+    def do_source( self, line ):
+        "Read commands from an input 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 = None
+
+    def do_dpctl( self, line ):
+        "Run dpctl command on all switches."
+        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_time( self, line ):
+        "Measure time taken for any command in Mininet."
+        start = time.time()
+        self.onecmd(line)
+        elapsed = time.time() - start
+        self.stdout.write("*** Elapsed time: %0.6f secs\n" % elapsed)
+
+    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 not args:
+            return
+        if args and len(args) > 0 and args[ -1 ] == '\n':
+            args = args[ :-1 ]
+        rest = args.split( ' ' )
+
+        if first in self.nodemap:
+            node = self.nodemap[ first ]
+            # Substitute IP addresses for node names in command
+            rest = [ self.nodemap[ arg ].IP()
+                     if arg in self.nodemap else arg
+                     for arg in rest ]
+            rest = ' '.join( rest )
+            # Run cmd on node:
+            builtin = isShellBuiltin( first )
+            node.sendCmd( rest, printPid=( not builtin ) )
+            self.waitForNode( node )
+        else:
+            error( '*** Unknown command: %s\n' % first )
+
+    # pylint: enable-msg=R0201
+
+    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:
+                node.sendInt()
+
+# Helper functions
+
+def isReadable( poller ):
+    "Check whether a Poll object has a readable fd."
+    for fdmask in poller.poll( 0 ):
+        mask = fdmask[ 1 ]
+        if mask & POLLIN:
+            return True