diff --git a/INSTALL b/INSTALL
index 494638c19e75cfbe686ebcdffbfba1f7c3a843a1..6d6337fde7175ae5c562b936322f30bf50a6f4f8 100644
--- a/INSTALL
+++ b/INSTALL
@@ -2,7 +2,7 @@
 Mininet Installation/Configuration Notes
 ----------------------------------------
 
-Mininet 2.1.0
+Mininet 2.1.0+
 ---
 
 The supported installation methods for Mininet are 1) using a
@@ -51,6 +51,13 @@ like to contribute an installation script, we would welcome it!)
 
         git clone git://github.com/mininet/mininet.git
 
+   Note that the above git command will check out the latest and greatest
+   Mininet (which we recommend!) If you want to run the last tagged/released
+   version of Mininet, use:
+
+        git clone git://github.com/mininet/mininet
+        git checkout -b 2.1.0 2.1.0
+
    If you are running Ubuntu, you may be able to use our handy
    `install.sh` script, which is in `mininet/util`.
 
diff --git a/LICENSE b/LICENSE
index de9b391c03946ca70c90cee1cfcb5d133ac96c18..0cb01dd0c9e9bf1a4aca5ab18f9939ef5fb0d550 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Mininet 2.1.0 License
+Mininet 2.1.0+ License
 
 Copyright (c) 2013 Open Networking Laboratory
 Copyright (c) 2009-2012 Bob Lantz and The Board of Trustees of
diff --git a/Makefile b/Makefile
index c989e61c1a22b50eb4a55b8b71aeb35da621b700..74da5aca145bc3a054fa33fe5b42b93dfdc1b6f0 100644
--- a/Makefile
+++ b/Makefile
@@ -12,6 +12,8 @@ MANDIR = /usr/share/man/man1
 DOCDIRS = doc/html doc/latex
 PDF = doc/latex/refman.pdf
 
+CFLAGS += -Wall -Wextra
+
 all: codecheck test
 
 clean:
diff --git a/README.md b/README.md
index 785dd1554f2f29448e34d548e104ac0ec0e52ad8..4c668ae85682a8c98a28b3124c3ce2003a79e478 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@ Mininet: Rapid Prototyping for Software Defined Networks
 
 *The best way to emulate almost any network on your laptop!*
 
-Version 2.1.0
+Version 2.1.0+
 
 ### What is Mininet?
 
@@ -66,9 +66,9 @@ Mininet includes:
 
   `mn -c`
 
-### New features in 2.1.0
+### New features in 2.1.0+
 
-Mininet 2.1.0 provides a number of bug fixes as well as
+Mininet 2.1.0+ provides a number of bug fixes as well as
 several new features, including:
 
 * Convenient access to `Mininet()` as a dict of nodes
@@ -127,7 +127,7 @@ Mininet to change the networking world!
 
 ### Credits
 
-The Mininet 2.1.0 Team:
+The Mininet 2.1.0+ Team:
 
 * Bob Lantz
 * Brian O'Connor
diff --git a/bin/mn b/bin/mn
index 5bab076a5813bae2d3a87d8648d907413474e036..f4b16eff4cd947b6db628987de98632fc86233eb 100755
--- a/bin/mn
+++ b/bin/mn
@@ -25,11 +25,13 @@ from mininet.cli import CLI
 from mininet.log import lg, LEVELS, info, debug, error
 from mininet.net import Mininet, MininetWithControlNet, VERSION
 from mininet.node import ( Host, CPULimitedHost, Controller, OVSController,
-                           NOX, RemoteController, UserSwitch, OVSKernelSwitch,
+                           NOX, RemoteController, DefaultController,
+                           UserSwitch, OVSSwitch,
                            OVSLegacyKernelSwitch, IVSSwitch )
+from mininet.nodelib import LinuxBridge
 from mininet.link import Link, TCLink
 from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo
-from mininet.topolib import TreeTopo
+from mininet.topolib import TreeTopo, TorusTopo
 from mininet.util import custom, customConstructor
 from mininet.util import buildTopo
 
@@ -40,24 +42,29 @@ TOPOS = { 'minimal': lambda: SingleSwitchTopo( k=2 ),
           'linear': LinearTopo,
           'reversed': SingleSwitchReversedTopo,
           'single': SingleSwitchTopo,
-          'tree': TreeTopo }
+          'tree': TreeTopo,
+          'torus': TorusTopo }
 
 SWITCHDEF = 'ovsk'
 SWITCHES = { 'user': UserSwitch,
-             'ovsk': OVSKernelSwitch,
+             'ovs':  OVSSwitch,
+             # Keep ovsk for compatibility with 2.0
+             'ovsk': OVSSwitch,
              'ovsl': OVSLegacyKernelSwitch,
-             'ivs': IVSSwitch }
+             'ivs': IVSSwitch,
+             'lxbr': LinuxBridge }
 
 HOSTDEF = 'proc'
 HOSTS = { 'proc': Host,
           'rt': custom( CPULimitedHost, sched='rt' ),
           'cfs': custom( CPULimitedHost, sched='cfs' ) }
 
-CONTROLLERDEF = 'ovsc'
+CONTROLLERDEF = 'default'
 CONTROLLERS = { 'ref': Controller,
                 'ovsc': OVSController,
                 'nox': NOX,
                 'remote': RemoteController,
+                'default': DefaultController,
                 'none': lambda name: None }
 
 LINKDEF = 'default'
@@ -268,12 +275,14 @@ class MininetRunner( object ):
         if test == 'none':
             pass
         elif test == 'all':
+            mn.waitConnected()
             mn.start()
             mn.ping()
             mn.iperf()
         elif test == 'cli':
             CLI( mn )
         elif test != 'build':
+            mn.waitConnected()
             getattr( mn, test )()
 
         if self.options.post:
diff --git a/examples/README.md b/examples/README.md
index 4be55642493702d3562d32ab8f6e4a43e5359a28..96e30d75c6f7d69c062fee50bbd1080db4029cf3 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -117,3 +117,8 @@ memory and `sysctl` configuration (see `INSTALL`.)
 
 This example creates a 64-host tree network, and attempts to check full
 connectivity using `ping`, for different switch/datapath types.
+
+#### numberedports.py
+
+This example verifies the mininet ofport numbers match up to the ovs port numbers.
+It also verifies that the port numbers match up to the interface numbers
diff --git a/examples/bind.py b/examples/bind.py
index 2c317cf19543e8a151325762aca0e2e061f16995..287c97abfff8a03e726891acf2e420ddb0ea417e 100755
--- a/examples/bind.py
+++ b/examples/bind.py
@@ -1,197 +1,72 @@
 #!/usr/bin/python
 
 """
-bind.py: Bind mount prototype
+bind.py: Bind mount example
 
-This creates hosts with private directories as desired.
+This creates hosts with private directories that the user specifies.
+These hosts may have persistent directories that will be available
+across multiple mininet session, or temporary directories that will
+only last for one mininet session. To specify a persistent
+directory, add a tuple to a list of private directories:
+
+    [ ( 'directory to be mounted on', 'directory to be mounted' ) ]
+
+String expansion may be used to create a directory template for
+each host. To do this, add a %(name)s in place of the host name
+when creating your list of directories:
+
+    [ ( '/var/run', '/tmp/%(name)s/var/run' ) ]
+
+If no persistent directory is specified, the directories will default
+to temporary private directories. To do this, simply create a list of
+directories to be made private. A tmpfs will then be mounted on them.
+
+You may use both temporary and persistent directories at the same
+time. In the following privateDirs string, each host will have a 
+persistent directory in the root filesystem at
+"/tmp/(hostname)/var/run" mounted on "/var/run". Each host will also
+have a temporary private directory mounted on "/var/log".
+
+    [ ( '/var/run', '/tmp/%(name)s/var/run' ), '/var/log' ]
+
+This example has both persistent directories mounted on '/var/log'
+and '/var/run'. It also has a temporary private directory mounted
+on '/var/mn'
 """
 
 from mininet.net import Mininet
-from mininet.node import Host
+from mininet.node import Host, HostWithPrivateDirs
 from mininet.cli import CLI
-from mininet.util import errFail, quietRun, errRun
 from mininet.topo import SingleSwitchTopo
 from mininet.log import setLogLevel, info, debug
 
-from os.path import realpath
 from functools import partial
 
 
-# Utility functions for unmounting a tree
-
-MNRUNDIR = realpath( '/var/run/mn' )
-
-def mountPoints():
-    "Return list of mounted file systems"
-    mtab, _err, _ret = errFail( 'cat /proc/mounts' )
-    lines = mtab.split( '\n' )
-    mounts = []
-    for line in lines:
-        if not line:
-            continue
-        fields = line.split( ' ')
-        mount = fields[ 1 ]
-        mounts.append( mount )
-    return mounts
-
-def unmountAll( rootdir=MNRUNDIR ):
-    "Unmount all mounts under a directory tree"
-    rootdir = realpath( rootdir )
-    # Find all mounts below rootdir
-    # This is subtle because /foo is not
-    # a parent of /foot
-    dirslash = rootdir + '/'
-    mounts = [ m for m in mountPoints()
-              if m == dir or m.find( dirslash ) == 0 ]
-    # Unmount them from bottom to top
-    mounts.sort( reverse=True )
-    for mount in mounts:
-        debug( 'Unmounting', mount, '\n' )
-        _out, err, code = errRun( 'umount', mount )
-        if code != 0:
-            info( '*** Warning: failed to umount', mount, '\n' )
-            info( err )
-
-
-class HostWithPrivateDirs( Host ):
-    "Host with private directories"
-
-    mnRunDir = MNRUNDIR
-
-    def __init__(self, name, *args, **kwargs ):
-        """privateDirs: list of private directories
-           remounts: dirs to remount
-           unmount: unmount dirs in cleanup? (True)
-           Note: if unmount is False, you must call unmountAll()
-           manually."""
-        self.privateDirs = kwargs.pop( 'privateDirs', [] )
-        self.remounts = kwargs.pop( 'remounts', [] )
-        self.unmount = kwargs.pop( 'unmount', True )
-        Host.__init__( self, name, *args, **kwargs )
-        self.rundir = '%s/%s' % ( self.mnRunDir, name )
-        self.root, self.private = None, None  # set in createBindMounts
-        if self.privateDirs:
-            self.privateDirs = [ realpath( d ) for d in self.privateDirs ]
-            self.createBindMounts()
-        # These should run in the namespace before we chroot,
-        # in order to put the right entries in /etc/mtab
-        # Eventually this will allow a local pid space
-        # Now we chroot and cd to wherever we were before.
-        pwd = self.cmd( 'pwd' ).strip()
-        self.sendCmd( 'exec chroot', self.root, 'bash -ms mininet:'
-                       + self.name )
-        self.waiting = False
-        self.cmd( 'cd', pwd )
-        # In order for many utilities to work,
-        # we need to remount /proc and /sys
-        self.cmd( 'mount /proc' )
-        self.cmd( 'mount /sys' )
-
-    def mountPrivateDirs( self ):
-        "Create and bind mount private dirs"
-        for dir_ in self.privateDirs:
-            privateDir = self.private + dir_
-            errFail( 'mkdir -p ' + privateDir )
-            mountPoint = self.root + dir_
-            errFail( 'mount -B %s %s' %
-                           ( privateDir, mountPoint) )
-
-    def mountDirs( self, dirs ):
-        "Mount a list of directories"
-        for dir_ in dirs:
-            mountpoint = self.root + dir_
-            errFail( 'mount -B %s %s' %
-                     ( dir_, mountpoint ) )
-
-    @classmethod
-    def findRemounts( cls, fstypes=None ):
-        """Identify mount points in /proc/mounts to remount
-           fstypes: file system types to match"""
-        if fstypes is None:
-            fstypes = [ 'nfs' ]
-        dirs = quietRun( 'cat /proc/mounts' ).strip().split( '\n' )
-        remounts = []
-        for dir_ in dirs:
-            line = dir_.split()
-            mountpoint, fstype = line[ 1 ], line[ 2 ]
-            # Don't re-remount directories!!!
-            if mountpoint.find( cls.mnRunDir ) == 0:
-                continue
-            if fstype in fstypes:
-                remounts.append( mountpoint )
-        return remounts
-
-    def createBindMounts( self ):
-        """Create a chroot directory structure,
-           with self.privateDirs as private dirs"""
-        errFail( 'mkdir -p '+ self.rundir )
-        unmountAll( self.rundir )
-        # Create /root and /private directories
-        self.root = self.rundir + '/root'
-        self.private = self.rundir + '/private'
-        errFail( 'mkdir -p ' + self.root )
-        errFail( 'mkdir -p ' + self.private )
-        # Recursively mount / in private doort
-        # note we'll remount /sys and /proc later
-        errFail( 'mount -B / ' + self.root )
-        self.mountDirs( self.remounts )
-        self.mountPrivateDirs()
-
-    def unmountBindMounts( self ):
-        "Unmount all of our bind mounts"
-        unmountAll( self.rundir )
-
-    def popen( self, *args, **kwargs ):
-        "Popen with chroot support"
-        chroot = kwargs.pop( 'chroot', True )
-        mncmd = kwargs.get( 'mncmd',
-                           [ 'mnexec', '-a', str( self.pid ) ] )
-        if chroot:
-            mncmd = [ 'chroot', self.root ] + mncmd
-            kwargs[ 'mncmd' ] = mncmd
-        return Host.popen( self, *args, **kwargs )
-
-    def cleanup( self ):
-        """Clean up, then unmount bind mounts
-           unmount: actually unmount bind mounts?"""
-        # Wait for process to actually terminate
-        self.shell.wait()
-        Host.cleanup( self )
-        if self.unmount:
-            self.unmountBindMounts()
-            errFail( 'rmdir ' + self.root )
-
-
-# Convenience aliases
-
-findRemounts = HostWithPrivateDirs.findRemounts
-
-
 # Sample usage
 
 def testHostWithPrivateDirs():
     "Test bind mounts"
     topo = SingleSwitchTopo( 10 )
-    remounts = findRemounts( fstypes=[ 'nfs' ] )
-    privateDirs = [ '/var/log', '/var/run' ]
-    host = partial( HostWithPrivateDirs, remounts=remounts,
-                    privateDirs=privateDirs, unmount=False )
+    privateDirs = [ ( '/var/log', '/tmp/%(name)s/var/log' ), 
+                    ( '/var/run', '/tmp/%(name)s/var/run' ), 
+                      '/var/mn' ]
+    host = partial( HostWithPrivateDirs,
+                    privateDirs=privateDirs )
     net = Mininet( topo=topo, host=host )
     net.start()
-    info( 'Private Directories:', privateDirs, '\n' )
+    directories = []
+    for directory in privateDirs:
+        directories.append( directory[ 0 ]
+                            if isinstance( directory, tuple )
+                            else directory )
+    info( 'Private Directories:',  directories, '\n' )
     CLI( net )
     net.stop()
-    # We do this all at once to save a bit of time
-    info( 'Unmounting host bind mounts...\n' )
-    unmountAll()
-
 
 if __name__ == '__main__':
-    unmountAll()
     setLogLevel( 'info' )
     testHostWithPrivateDirs()
     info( 'Done.\n')
 
 
-
-
diff --git a/examples/controllers2.py b/examples/controllers2.py
index 0836533c94fb395a8e961ea6f970099d270fcb23..628a2f1cf8f8d20a8085ed75b04d1cb0cc17cf84 100755
--- a/examples/controllers2.py
+++ b/examples/controllers2.py
@@ -19,7 +19,7 @@
 def multiControllerNet():
     "Create a network from semi-scratch with multiple controllers."
 
-    net = Mininet( controller=Controller, switch=OVSSwitch, build=False )
+    net = Mininet( controller=Controller, switch=OVSSwitch )
 
     print "*** Creating (reference) controllers"
     c1 = net.addController( 'c1', port=6633 )
diff --git a/examples/linearbandwidth.py b/examples/linearbandwidth.py
index 3fd06c757db8730d10ccee8345ebd9de458f0ce9..dee5490cf28a34376a7cc93bb8a92be181badc36 100755
--- a/examples/linearbandwidth.py
+++ b/examples/linearbandwidth.py
@@ -24,7 +24,7 @@
 """
 
 from mininet.net import Mininet
-from mininet.node import UserSwitch, OVSKernelSwitch
+from mininet.node import UserSwitch, OVSKernelSwitch, Controller
 from mininet.topo import Topo
 from mininet.log import lg
 from mininet.util import irange
@@ -76,7 +76,7 @@ def linearBandwidthTest( lengths ):
         print "*** testing", datapath, "datapath"
         Switch = switches[ datapath ]
         results[ datapath ] = []
-        net = Mininet( topo=topo, switch=Switch )
+        net = Mininet( topo=topo, switch=Switch, controller=Controller, waitConnected=True )
         net.start()
         print "*** testing basic connectivity"
         for n in lengths:
diff --git a/examples/miniedit.py b/examples/miniedit.py
index 2883c1cee7c2a4dd0421f1bcdd35bd824913295a..472c0bc80421c0f8a55f143ea1bb36b47ebc9b71 100755
--- a/examples/miniedit.py
+++ b/examples/miniedit.py
@@ -6,30 +6,1011 @@
 This is a simple demonstration of how one might build a
 GUI application using Mininet as the network model.
 
-Development version - not entirely functional!
-
 Bob Lantz, April 2010
+Gregory Gee, July 2013
+
+Controller icon from http://semlabs.co.uk/
+OpenFlow icon from https://www.opennetworking.org/
 """
 
-from Tkinter import Frame, Button, Label, Scrollbar, Canvas
-from Tkinter import Menu, BitmapImage, PhotoImage, Wm, Toplevel
+MINIEDIT_VERSION = '2.1.0.8.1'
+
+from optparse import OptionParser
+from Tkinter import *
+from tkMessageBox import showinfo, showerror, showwarning
+from subprocess import call
+import tkFont
+import csv
+import tkFileDialog
+import tkSimpleDialog
+import re
+import json
+from distutils.version import StrictVersion
+import os
+import sys
+
+if 'PYTHONPATH' in os.environ:
+    sys.path = os.environ[ 'PYTHONPATH' ].split( ':' ) + sys.path
 
 # someday: from ttk import *
 
-from mininet.log import setLogLevel
-from mininet.net import Mininet
-from mininet.util import ipStr
+from mininet.log import info, error, debug, output, setLogLevel
+from mininet.net import Mininet, VERSION
+from mininet.util import ipStr, netParse, ipAdd, quietRun
+from mininet.util import buildTopo
 from mininet.term import makeTerm, cleanUpScreens
+from mininet.node import Controller, RemoteController, NOX, OVSController
+from mininet.node import CPULimitedHost, Host, Node
+from mininet.node import OVSKernelSwitch, OVSSwitch, UserSwitch
+from mininet.link import TCLink, Intf, Link
+from mininet.cli import CLI
+from mininet.moduledeps import moduleDeps, pathCheck
+from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo
+from mininet.topolib import TreeTopo
+
+print 'MiniEdit running against MiniNet '+VERSION
+MININET_VERSION = re.sub(r'[^\d\.]', '', VERSION)
+if StrictVersion(MININET_VERSION) > StrictVersion('2.0'):
+    from mininet.node import IVSSwitch
+
+TOPODEF = 'none'
+TOPOS = { 'minimal': lambda: SingleSwitchTopo( k=2 ),
+          'linear': LinearTopo,
+          'reversed': SingleSwitchReversedTopo,
+          'single': SingleSwitchTopo,
+          'none': None,
+          'tree': TreeTopo }
+CONTROLLERDEF = 'ref'
+CONTROLLERS = { 'ref': Controller,
+                'ovsc': OVSController,
+                'nox': NOX,
+                'remote': RemoteController,
+                'none': lambda name: None }
+
+class InbandController( RemoteController ):
+
+    def checkListening( self ):
+        "Overridden to do nothing."
+        return
+
+class CustomUserSwitch(UserSwitch):
+    def __init__( self, name, dpopts='--no-slicing', **kwargs ):
+        UserSwitch.__init__( self, name, **kwargs )
+        self.switchIP = None
+
+    def getSwitchIP(self):
+        return self.switchIP
+
+    def setSwitchIP(self, ip):
+        self.switchIP = ip
+
+    def start( self, controllers ):
+        # Call superclass constructor
+        UserSwitch.start( self, controllers )
+        # Set Switch IP address
+        if (self.switchIP is not None):
+            if not self.inNamespace:
+                self.cmd( 'ifconfig', self, self.switchIP )
+            else:
+                self.cmd( 'ifconfig lo', self.switchIP )
+
+class LegacyRouter( Node ):
+
+    def __init__( self, name, inNamespace=True, **params ):
+        Node.__init__( self, name, inNamespace, **params )
+
+    def config( self, **_params ):
+        if self.intfs:
+            self.setParam( _params, 'setIP', ip='0.0.0.0' )
+        r = Node.config( self, **_params )
+        self.cmd('sysctl -w net.ipv4.ip_forward=1')
+        return r
+
+class LegacySwitch(OVSSwitch):
+
+    def __init__( self, name, **params ):
+        OVSSwitch.__init__( self, name, failMode='standalone', **params )
+        self.switchIP = None
+
+class customOvs(OVSSwitch):
+
+    def __init__( self, name, failMode='secure', datapath='kernel', **params ):
+        OVSSwitch.__init__( self, name, failMode=failMode, datapath=datapath, **params )
+        self.switchIP = None
+
+    def getSwitchIP(self):
+        return self.switchIP
+
+    def setSwitchIP(self, ip):
+        self.switchIP = ip
+
+    def getOpenFlowVersion(self):
+        return self.openFlowVersions
+
+    def setOpenFlowVersion(self, versions):
+        self.openFlowVersions = []
+        if versions['ovsOf10'] == '1':
+            self.openFlowVersions.append('OpenFlow10')
+        if versions['ovsOf11'] == '1':
+            self.openFlowVersions.append('OpenFlow11')
+        if versions['ovsOf12'] == '1':
+            self.openFlowVersions.append('OpenFlow12')
+        if versions['ovsOf13'] == '1':
+            self.openFlowVersions.append('OpenFlow13')
+
+    def configureOpenFlowVersion(self):
+        if not ( 'OpenFlow11' in self.openFlowVersions or
+                 'OpenFlow12' in self.openFlowVersions or
+                 'OpenFlow13' in self.openFlowVersions ):
+            return
+
+        protoList = ",".join(self.openFlowVersions)
+        print 'Configuring OpenFlow to '+protoList
+        self.cmd( 'ovs-vsctl -- set bridge', self, 'protocols='+protoList)
+
+    def start( self, controllers ):
+        # Call superclass constructor
+        OVSSwitch.start( self, controllers )
+        # Set OpenFlow Versions
+        self.configureOpenFlowVersion()
+        # Set Switch IP address
+        if (self.switchIP is not None):
+            self.cmd( 'ifconfig', self, self.switchIP )
+
+class PrefsDialog(tkSimpleDialog.Dialog):
+
+        def __init__(self, parent, title, prefDefaults):
+
+            self.prefValues = prefDefaults
+
+            tkSimpleDialog.Dialog.__init__(self, parent, title)
+
+        def body(self, master):
+            self.rootFrame = master
+            self.leftfieldFrame = Frame(self.rootFrame, padx=5, pady=5)
+            self.leftfieldFrame.grid(row=0, column=0, sticky='nswe', columnspan=2)
+            self.rightfieldFrame = Frame(self.rootFrame, padx=5, pady=5)
+            self.rightfieldFrame.grid(row=0, column=2, sticky='nswe', columnspan=2)
+
+
+            # Field for Base IP
+            Label(self.leftfieldFrame, text="IP Base:").grid(row=0, sticky=E)
+            self.ipEntry = Entry(self.leftfieldFrame)
+            self.ipEntry.grid(row=0, column=1)
+            ipBase =  self.prefValues['ipBase']
+            self.ipEntry.insert(0, ipBase)
+
+            # Selection of terminal type
+            Label(self.leftfieldFrame, text="Default Terminal:").grid(row=1, sticky=E)
+            self.terminalVar = StringVar(self.leftfieldFrame)
+            self.terminalOption = OptionMenu(self.leftfieldFrame, self.terminalVar, "xterm", "gterm")
+            self.terminalOption.grid(row=1, column=1, sticky=W)
+            terminalType = self.prefValues['terminalType']
+            self.terminalVar.set(terminalType)
+
+            # Field for CLI
+            Label(self.leftfieldFrame, text="Start CLI:").grid(row=2, sticky=E)
+            self.cliStart = IntVar()
+            self.cliButton = Checkbutton(self.leftfieldFrame, variable=self.cliStart)
+            self.cliButton.grid(row=2, column=1, sticky=W)
+            if self.prefValues['startCLI'] == '0':
+                self.cliButton.deselect()
+            else:
+                self.cliButton.select()
+
+            # Selection of switch type
+            Label(self.leftfieldFrame, text="Default Switch:").grid(row=3, sticky=E)
+            self.switchType = StringVar(self.leftfieldFrame)
+            self.switchTypeMenu = OptionMenu(self.leftfieldFrame, self.switchType, "Open vSwitch", "Indigo Virtual Switch", "Userspace Switch", "Userspace Switch inNamespace")
+            self.switchTypeMenu.grid(row=3, column=1, sticky=W)
+            switchTypePref = self.prefValues['switchType']
+            if switchTypePref == 'ivs':
+                self.switchType.set("Indigo Virtual Switch")
+            elif switchTypePref == 'userns':
+                self.switchType.set("Userspace Switch inNamespace")
+            elif switchTypePref == 'user':
+                self.switchType.set("Userspace Switch")
+            else:
+                self.switchType.set("Open vSwitch")
+
+
+            # Fields for OVS OpenFlow version
+            ovsFrame= LabelFrame(self.leftfieldFrame, text='Open vSwitch', padx=5, pady=5)
+            ovsFrame.grid(row=4, column=0, columnspan=2, sticky=EW)
+            Label(ovsFrame, text="OpenFlow 1.0:").grid(row=0, sticky=E)
+            Label(ovsFrame, text="OpenFlow 1.1:").grid(row=1, sticky=E)
+            Label(ovsFrame, text="OpenFlow 1.2:").grid(row=2, sticky=E)
+            Label(ovsFrame, text="OpenFlow 1.3:").grid(row=3, sticky=E)
+
+            self.ovsOf10 = IntVar()
+            self.covsOf10 = Checkbutton(ovsFrame, variable=self.ovsOf10)
+            self.covsOf10.grid(row=0, column=1, sticky=W)
+            if self.prefValues['openFlowVersions']['ovsOf10'] == '0':
+                self.covsOf10.deselect()
+            else:
+                self.covsOf10.select()
+
+            self.ovsOf11 = IntVar()
+            self.covsOf11 = Checkbutton(ovsFrame, variable=self.ovsOf11)
+            self.covsOf11.grid(row=1, column=1, sticky=W)
+            if self.prefValues['openFlowVersions']['ovsOf11'] == '0':
+                self.covsOf11.deselect()
+            else:
+                self.covsOf11.select()
+
+            self.ovsOf12 = IntVar()
+            self.covsOf12 = Checkbutton(ovsFrame, variable=self.ovsOf12)
+            self.covsOf12.grid(row=2, column=1, sticky=W)
+            if self.prefValues['openFlowVersions']['ovsOf12'] == '0':
+                self.covsOf12.deselect()
+            else:
+                self.covsOf12.select()
+
+            self.ovsOf13 = IntVar()
+            self.covsOf13 = Checkbutton(ovsFrame, variable=self.ovsOf13)
+            self.covsOf13.grid(row=3, column=1, sticky=W)
+            if self.prefValues['openFlowVersions']['ovsOf13'] == '0':
+                self.covsOf13.deselect()
+            else:
+                self.covsOf13.select()
+
+            # Field for DPCTL listen port
+            Label(self.leftfieldFrame, text="dpctl port:").grid(row=5, sticky=E)
+            self.dpctlEntry = Entry(self.leftfieldFrame)
+            self.dpctlEntry.grid(row=5, column=1)
+            if 'dpctl' in self.prefValues:
+                self.dpctlEntry.insert(0, self.prefValues['dpctl'])
+
+            # sFlow
+            sflowValues = self.prefValues['sflow']
+            self.sflowFrame= LabelFrame(self.rightfieldFrame, text='sFlow Profile for Open vSwitch', padx=5, pady=5)
+            self.sflowFrame.grid(row=0, column=0, columnspan=2, sticky=EW)
+
+            Label(self.sflowFrame, text="Target:").grid(row=0, sticky=E)
+            self.sflowTarget = Entry(self.sflowFrame)
+            self.sflowTarget.grid(row=0, column=1)
+            self.sflowTarget.insert(0, sflowValues['sflowTarget'])
+
+            Label(self.sflowFrame, text="Sampling:").grid(row=1, sticky=E)
+            self.sflowSampling = Entry(self.sflowFrame)
+            self.sflowSampling.grid(row=1, column=1)
+            self.sflowSampling.insert(0, sflowValues['sflowSampling'])
+
+            Label(self.sflowFrame, text="Header:").grid(row=2, sticky=E)
+            self.sflowHeader = Entry(self.sflowFrame)
+            self.sflowHeader.grid(row=2, column=1)
+            self.sflowHeader.insert(0, sflowValues['sflowHeader'])
+
+            Label(self.sflowFrame, text="Polling:").grid(row=3, sticky=E)
+            self.sflowPolling = Entry(self.sflowFrame)
+            self.sflowPolling.grid(row=3, column=1)
+            self.sflowPolling.insert(0, sflowValues['sflowPolling'])
+
+            # NetFlow
+            nflowValues = self.prefValues['netflow']
+            self.nFrame= LabelFrame(self.rightfieldFrame, text='NetFlow Profile for Open vSwitch', padx=5, pady=5)
+            self.nFrame.grid(row=1, column=0, columnspan=2, sticky=EW)
+
+            Label(self.nFrame, text="Target:").grid(row=0, sticky=E)
+            self.nflowTarget = Entry(self.nFrame)
+            self.nflowTarget.grid(row=0, column=1)
+            self.nflowTarget.insert(0, nflowValues['nflowTarget'])
+
+            Label(self.nFrame, text="Active Timeout:").grid(row=1, sticky=E)
+            self.nflowTimeout = Entry(self.nFrame)
+            self.nflowTimeout.grid(row=1, column=1)
+            self.nflowTimeout.insert(0, nflowValues['nflowTimeout'])
+
+            Label(self.nFrame, text="Add ID to Interface:").grid(row=2, sticky=E)
+            self.nflowAddId = IntVar()
+            self.nflowAddIdButton = Checkbutton(self.nFrame, variable=self.nflowAddId)
+            self.nflowAddIdButton.grid(row=2, column=1, sticky=W)
+            if nflowValues['nflowAddId'] == '0':
+                self.nflowAddIdButton.deselect()
+            else:
+                self.nflowAddIdButton.select()
+
+            # initial focus
+            return self.ipEntry
+
+        def apply(self):
+            ipBase = self.ipEntry.get()
+            terminalType = self.terminalVar.get()
+            startCLI = str(self.cliStart.get())
+            sw = self.switchType.get()
+            dpctl = self.dpctlEntry.get()
+
+            ovsOf10 = str(self.ovsOf10.get())
+            ovsOf11 = str(self.ovsOf11.get())
+            ovsOf12 = str(self.ovsOf12.get())
+            ovsOf13 = str(self.ovsOf13.get())
+
+            sflowValues = {'sflowTarget':self.sflowTarget.get(),
+                           'sflowSampling':self.sflowSampling.get(),
+                           'sflowHeader':self.sflowHeader.get(),
+                           'sflowPolling':self.sflowPolling.get()}
+            nflowvalues = {'nflowTarget':self.nflowTarget.get(),
+                           'nflowTimeout':self.nflowTimeout.get(),
+                           'nflowAddId':str(self.nflowAddId.get())}
+            self.result = {'ipBase':ipBase,
+                           'terminalType':terminalType,
+                           'dpctl':dpctl,
+                           'sflow':sflowValues,
+                           'netflow':nflowvalues,
+                           'startCLI':startCLI}
+            if sw == 'Indigo Virtual Switch':
+                self.result['switchType'] = 'ivs'
+                if StrictVersion(MININET_VERSION) < StrictVersion('2.1'):
+                    self.ovsOk = False
+                    showerror(title="Error",
+                              message='MiniNet version 2.1+ required. You have '+VERSION+'.')
+            elif sw == 'Userspace Switch':
+                self.result['switchType'] = 'user'
+            elif sw == 'Userspace Switch inNamespace':
+                self.result['switchType'] = 'userns'
+            else:
+                self.result['switchType'] = 'ovs'
+
+            self.ovsOk = True
+            if ovsOf11 == "1":
+                ovsVer = self.getOvsVersion()
+                if StrictVersion(ovsVer) < StrictVersion('2.0'):
+                    self.ovsOk = False
+                    showerror(title="Error",
+                              message='Open vSwitch version 2.0+ required. You have '+ovsVer+'.')
+            if ovsOf12 == "1" or ovsOf13 == "1":
+                ovsVer = self.getOvsVersion()
+                if StrictVersion(ovsVer) < StrictVersion('1.10'):
+                    self.ovsOk = False
+                    showerror(title="Error",
+                              message='Open vSwitch version 1.10+ required. You have '+ovsVer+'.')
+
+            if self.ovsOk:
+                self.result['openFlowVersions']={'ovsOf10':ovsOf10,
+                                                 'ovsOf11':ovsOf11,
+                                                 'ovsOf12':ovsOf12,
+                                                 'ovsOf13':ovsOf13}
+            else:
+                self.result = None
+
+        def getOvsVersion(self):
+            outp = quietRun("ovs-vsctl show")
+            r = r'ovs_version: "(.*)"'
+            m = re.search(r, outp)
+            if m is None:
+                print 'Version check failed'
+                return None
+            else:
+                print 'Open vSwitch version is '+m.group(1)
+                return m.group(1)
+
+
+class CustomDialog(object):
+
+        # TODO: Fix button placement and Title and window focus lock
+        def __init__(self, master, title):
+            self.top=Toplevel(master)
+
+            self.bodyFrame = Frame(self.top)
+            self.bodyFrame.grid(row=0, column=0, sticky='nswe')
+            self.body(self.bodyFrame)
+
+            #return self.b # initial focus
+            buttonFrame = Frame(self.top, relief='ridge', bd=3, bg='lightgrey')
+            buttonFrame.grid(row=1 , column=0, sticky='nswe')
+
+            okButton = Button(buttonFrame, width=8, text='OK', relief='groove',
+                       bd=4, command=self.okAction)
+            okButton.grid(row=0, column=0, sticky=E)
+
+            canlceButton = Button(buttonFrame, width=8, text='Cancel', relief='groove',
+                        bd=4, command=self.cancelAction)
+            canlceButton.grid(row=0, column=1, sticky=W)
+
+        def body(self, master):
+            self.rootFrame = master
+
+        def apply(self):
+            self.top.destroy()
+
+        def cancelAction(self):
+            self.top.destroy()
+
+        def okAction(self):
+            self.apply()
+            self.top.destroy()
+
+class HostDialog(CustomDialog):
+
+        def __init__(self, master, title, prefDefaults):
+
+            self.prefValues = prefDefaults
+            self.result = None
+
+            CustomDialog.__init__(self, master, title)
+
+        def body(self, master):
+            self.rootFrame = master
+            self.leftfieldFrame = Frame(self.rootFrame)
+            self.leftfieldFrame.grid(row=0, column=0, sticky='nswe', columnspan=2)
+            self.rightfieldFrame = Frame(self.rootFrame)
+            self.rightfieldFrame.grid(row=0, column=2, sticky='nswe', columnspan=2)
+
+            # Field for Hostname
+            Label(self.leftfieldFrame, text="Hostname:").grid(row=0, sticky=E)
+            self.hostnameEntry = Entry(self.leftfieldFrame)
+            self.hostnameEntry.grid(row=0, column=1)
+            if 'hostname' in self.prefValues:
+                self.hostnameEntry.insert(0, self.prefValues['hostname'])
+
+            # Field for Switch IP
+            Label(self.leftfieldFrame, text="IP Address:").grid(row=1, sticky=E)
+            self.ipEntry = Entry(self.leftfieldFrame)
+            self.ipEntry.grid(row=1, column=1)
+            if 'ip' in self.prefValues:
+                self.ipEntry.insert(0, self.prefValues['ip'])
+
+            # Field for default route
+            Label(self.leftfieldFrame, text="Default Route:").grid(row=2, sticky=E)
+            self.routeEntry = Entry(self.leftfieldFrame)
+            self.routeEntry.grid(row=2, column=1)
+            if 'defaultRoute' in self.prefValues:
+                self.routeEntry.insert(0, self.prefValues['defaultRoute'])
+
+            # Field for CPU
+            Label(self.rightfieldFrame, text="Amount CPU:").grid(row=0, sticky=E)
+            self.cpuEntry = Entry(self.rightfieldFrame)
+            self.cpuEntry.grid(row=0, column=1)
+            if 'cpu' in self.prefValues:
+                self.cpuEntry.insert(0, str(self.prefValues['cpu']))
+            # Selection of Scheduler
+            if 'sched' in self.prefValues:
+                sched =  self.prefValues['sched']
+            else:
+                sched = 'host'
+            self.schedVar = StringVar(self.rightfieldFrame)
+            self.schedOption = OptionMenu(self.rightfieldFrame, self.schedVar, "host", "cfs", "rt")
+            self.schedOption.grid(row=0, column=2, sticky=W)
+            self.schedVar.set(sched)
+
+            # Selection of Cores
+            Label(self.rightfieldFrame, text="Cores:").grid(row=1, sticky=E)
+            self.coreEntry = Entry(self.rightfieldFrame)
+            self.coreEntry.grid(row=1, column=1)
+            if 'cores' in self.prefValues:
+                self.coreEntry.insert(1, self.prefValues['cores'])
+
+            # External Interfaces
+            self.externalInterfaces = 0
+            Label(self.rootFrame, text="External Interface:").grid(row=1, column=0, sticky=E)
+            self.b = Button( self.rootFrame, text='Add', command=self.addInterface)
+            self.b.grid(row=1, column=1)
+
+            self.interfaceFrame = VerticalScrolledTable(self.rootFrame, rows=0, columns=1, title='External Interfaces')
+            self.interfaceFrame.grid(row=2, column=0, sticky='nswe', columnspan=2)
+            self.tableFrame = self.interfaceFrame.interior
+            self.tableFrame.addRow(value=['Interface Name'], readonly=True)
+
+            # Add defined interfaces
+            externalInterfaces = []
+            if 'externalInterfaces' in self.prefValues:
+                externalInterfaces = self.prefValues['externalInterfaces']
+
+            for externalInterface in externalInterfaces:
+                self.tableFrame.addRow(value=[externalInterface])
+
+            # VLAN Interfaces
+            self.vlanInterfaces = 0
+            Label(self.rootFrame, text="VLAN Interface:").grid(row=1, column=2, sticky=E)
+            self.vlanButton = Button( self.rootFrame, text='Add', command=self.addVlanInterface)
+            self.vlanButton.grid(row=1, column=3)
+
+            self.vlanFrame = VerticalScrolledTable(self.rootFrame, rows=0, columns=2, title='VLAN Interfaces')
+            self.vlanFrame.grid(row=2, column=2, sticky='nswe', columnspan=2)
+            self.vlanTableFrame = self.vlanFrame.interior
+            self.vlanTableFrame.addRow(value=['IP Address','VLAN ID'], readonly=True)
+
+            vlanInterfaces = []
+            if 'vlanInterfaces' in self.prefValues:
+                vlanInterfaces = self.prefValues['vlanInterfaces']
+            for vlanInterface in vlanInterfaces:
+                self.vlanTableFrame.addRow(value=vlanInterface)
+
+        def addVlanInterface( self ):
+            self.vlanTableFrame.addRow()
+
+        def addInterface( self ):
+            self.tableFrame.addRow()
+
+        def apply(self):
+            externalInterfaces = []
+            for row in range(self.tableFrame.rows):
+                if (len(self.tableFrame.get(row, 0)) > 0 and
+                    row > 0):
+                    externalInterfaces.append(self.tableFrame.get(row, 0))
+            vlanInterfaces = []
+            for row in range(self.vlanTableFrame.rows):
+                if (len(self.vlanTableFrame.get(row, 0)) > 0 and
+                    len(self.vlanTableFrame.get(row, 1)) > 0 and
+                    row > 0):
+                    vlanInterfaces.append([self.vlanTableFrame.get(row, 0), self.vlanTableFrame.get(row, 1)])
+
+            results = {'cpu': self.cpuEntry.get(),
+                       'cores':self.coreEntry.get(),
+                       'sched':self.schedVar.get(),
+                       'hostname':self.hostnameEntry.get(),
+                       'ip':self.ipEntry.get(),
+                       'defaultRoute':self.routeEntry.get(),
+                       'externalInterfaces':externalInterfaces,
+                       'vlanInterfaces':vlanInterfaces}
+            self.result = results
+
+class SwitchDialog(CustomDialog):
+
+        def __init__(self, master, title, prefDefaults):
+
+            self.prefValues = prefDefaults
+            self.result = None
+            CustomDialog.__init__(self, master, title)
+
+        def body(self, master):
+            self.rootFrame = master
+
+            rowCount = 0
+            externalInterfaces = []
+            if 'externalInterfaces' in self.prefValues:
+                externalInterfaces = self.prefValues['externalInterfaces']
+
+            self.fieldFrame = Frame(self.rootFrame)
+            self.fieldFrame.grid(row=0, column=0, sticky='nswe')
+
+            # Field for Hostname
+            Label(self.fieldFrame, text="Hostname:").grid(row=rowCount, sticky=E)
+            self.hostnameEntry = Entry(self.fieldFrame)
+            self.hostnameEntry.grid(row=rowCount, column=1)
+            self.hostnameEntry.insert(0, self.prefValues['hostname'])
+            rowCount+=1
+
+            # Field for DPID
+            Label(self.fieldFrame, text="DPID:").grid(row=rowCount, sticky=E)
+            self.dpidEntry = Entry(self.fieldFrame)
+            self.dpidEntry.grid(row=rowCount, column=1)
+            if 'dpid' in self.prefValues:
+                self.dpidEntry.insert(0, self.prefValues['dpid'])
+            rowCount+=1
+
+            # Field for Netflow
+            Label(self.fieldFrame, text="Enable NetFlow:").grid(row=rowCount, sticky=E)
+            self.nflow = IntVar()
+            self.nflowButton = Checkbutton(self.fieldFrame, variable=self.nflow)
+            self.nflowButton.grid(row=rowCount, column=1, sticky=W)
+            if 'netflow' in self.prefValues:
+                if self.prefValues['netflow'] == '0':
+                    self.nflowButton.deselect()
+                else:
+                    self.nflowButton.select()
+            else:
+                self.nflowButton.deselect()
+            rowCount+=1
+
+            # Field for sflow
+            Label(self.fieldFrame, text="Enable sFlow:").grid(row=rowCount, sticky=E)
+            self.sflow = IntVar()
+            self.sflowButton = Checkbutton(self.fieldFrame, variable=self.sflow)
+            self.sflowButton.grid(row=rowCount, column=1, sticky=W)
+            if 'sflow' in self.prefValues:
+                if self.prefValues['sflow'] == '0':
+                    self.sflowButton.deselect()
+                else:
+                    self.sflowButton.select()
+            else:
+                self.sflowButton.deselect()
+            rowCount+=1
+
+            # Selection of switch type
+            Label(self.fieldFrame, text="Switch Type:").grid(row=rowCount, sticky=E)
+            self.switchType = StringVar(self.fieldFrame)
+            self.switchTypeMenu = OptionMenu(self.fieldFrame, self.switchType, "Default", "Open vSwitch", "Indigo Virtual Switch", "Userspace Switch", "Userspace Switch inNamespace")
+            self.switchTypeMenu.grid(row=rowCount, column=1, sticky=W)
+            if 'switchType' in self.prefValues:
+                switchTypePref = self.prefValues['switchType']
+                if switchTypePref == 'ivs':
+                    self.switchType.set("Indigo Virtual Switch")
+                elif switchTypePref == 'userns':
+                    self.switchType.set("Userspace Switch inNamespace")
+                elif switchTypePref == 'user':
+                    self.switchType.set("Userspace Switch")
+                elif switchTypePref == 'ovs':
+                    self.switchType.set("Open vSwitch")
+                else:
+                    self.switchType.set("Default")
+            else:
+                self.switchType.set("Default")
+            rowCount+=1
+
+            # Field for Switch IP
+            Label(self.fieldFrame, text="IP Address:").grid(row=rowCount, sticky=E)
+            self.ipEntry = Entry(self.fieldFrame)
+            self.ipEntry.grid(row=rowCount, column=1)
+            if 'switchIP' in self.prefValues:
+                self.ipEntry.insert(0, self.prefValues['switchIP'])
+            rowCount+=1
+
+            # Field for DPCTL port
+            Label(self.fieldFrame, text="DPCTL port:").grid(row=rowCount, sticky=E)
+            self.dpctlEntry = Entry(self.fieldFrame)
+            self.dpctlEntry.grid(row=rowCount, column=1)
+            if 'dpctl' in self.prefValues:
+                self.dpctlEntry.insert(0, self.prefValues['dpctl'])
+            rowCount+=1
+
+            # External Interfaces
+            Label(self.fieldFrame, text="External Interface:").grid(row=rowCount, sticky=E)
+            self.b = Button( self.fieldFrame, text='Add', command=self.addInterface)
+            self.b.grid(row=rowCount, column=1)
+
+            self.interfaceFrame = VerticalScrolledTable(self.rootFrame, rows=0, columns=1, title='External Interfaces')
+            self.interfaceFrame.grid(row=2, column=0, sticky='nswe')
+            self.tableFrame = self.interfaceFrame.interior
+
+            # Add defined interfaces
+            for externalInterface in externalInterfaces:
+                self.tableFrame.addRow(value=[externalInterface])
+            rowCount+=1
+
+        def addInterface( self ):
+            self.tableFrame.addRow()
+
+        def defaultDpid( self ,name):
+            "Derive dpid from switch name, s1 -> 1"
+            try:
+                dpid = int( re.findall( r'\d+', name )[ 0 ] )
+                dpid = hex( dpid )[ 2: ]
+                return dpid
+            except IndexError:
+                return None
+                #raise Exception( 'Unable to derive default datapath ID - '
+                #                 'please either specify a dpid or use a '
+                #                 'canonical switch name such as s23.' )
+
+        def apply(self):
+            externalInterfaces = []
+            for row in range(self.tableFrame.rows):
+                #print 'Interface is ' + self.tableFrame.get(row, 0)
+                if (len(self.tableFrame.get(row, 0)) > 0):
+                    externalInterfaces.append(self.tableFrame.get(row, 0))
+
+            dpid = self.dpidEntry.get()
+            if (self.defaultDpid(self.hostnameEntry.get()) is None
+               and len(dpid) == 0):
+                showerror(title="Error",
+                              message= 'Unable to derive default datapath ID - '
+                                 'please either specify a DPID or use a '
+                                 'canonical switch name such as s23.' )
+
+            
+            results = {'externalInterfaces':externalInterfaces,
+                       'hostname':self.hostnameEntry.get(),
+                       'dpid':dpid,
+                       'sflow':str(self.sflow.get()),
+                       'netflow':str(self.nflow.get()),
+                       'dpctl':self.dpctlEntry.get(),
+                       'switchIP':self.ipEntry.get()}
+            sw = self.switchType.get()
+            if sw == 'Indigo Virtual Switch':
+                results['switchType'] = 'ivs'
+                if StrictVersion(MININET_VERSION) < StrictVersion('2.1'):
+                    self.ovsOk = False
+                    showerror(title="Error",
+                              message='MiniNet version 2.1+ required. You have '+VERSION+'.')
+            elif sw == 'Userspace Switch inNamespace':
+                results['switchType'] = 'userns'
+            elif sw == 'Userspace Switch':
+                results['switchType'] = 'user'
+            elif sw == 'Open vSwitch':
+                results['switchType'] = 'ovs'
+            else:
+                results['switchType'] = 'default'
+            self.result = results
+
+
+class VerticalScrolledTable(LabelFrame):
+    """A pure Tkinter scrollable frame that actually works!
+
+    * Use the 'interior' attribute to place widgets inside the scrollable frame
+    * Construct and pack/place/grid normally
+    * This frame only allows vertical scrolling
+    
+    """
+    def __init__(self, parent, rows=2, columns=2, title=None, *args, **kw):
+        LabelFrame.__init__(self, parent, text=title, padx=5, pady=5, *args, **kw)            
+
+        # create a canvas object and a vertical scrollbar for scrolling it
+        vscrollbar = Scrollbar(self, orient=VERTICAL)
+        vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE)
+        canvas = Canvas(self, bd=0, highlightthickness=0,
+                        yscrollcommand=vscrollbar.set)
+        canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)
+        vscrollbar.config(command=canvas.yview)
+
+        # reset the view
+        canvas.xview_moveto(0)
+        canvas.yview_moveto(0)
+
+        # create a frame inside the canvas which will be scrolled with it
+        self.interior = interior = TableFrame(canvas, rows=rows, columns=columns)
+        interior_id = canvas.create_window(0, 0, window=interior,
+                                           anchor=NW)
+
+        # track changes to the canvas and frame width and sync them,
+        # also updating the scrollbar
+        def _configure_interior(event):
+            # update the scrollbars to match the size of the inner frame
+            size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
+            canvas.config(scrollregion="0 0 %s %s" % size)
+            if interior.winfo_reqwidth() != canvas.winfo_width():
+                # update the canvas's width to fit the inner frame
+                canvas.config(width=interior.winfo_reqwidth())
+        interior.bind('<Configure>', _configure_interior)
+
+        def _configure_canvas(event):
+            if interior.winfo_reqwidth() != canvas.winfo_width():
+                # update the inner frame's width to fill the canvas
+                canvas.itemconfigure(interior_id, width=canvas.winfo_width())
+        canvas.bind('<Configure>', _configure_canvas)
+
+        return
+
+class TableFrame(Frame):
+    def __init__(self, parent, rows=2, columns=2):
+
+        Frame.__init__(self, parent, background="black")
+        self._widgets = []
+        self.rows = rows
+        self.columns = columns
+        for row in range(rows):
+            current_row = []
+            for column in range(columns):
+                label = Entry(self, borderwidth=0)
+                label.grid(row=row, column=column, sticky="wens", padx=1, pady=1)
+                current_row.append(label)
+            self._widgets.append(current_row)
+
+    def set(self, row, column, value):
+        widget = self._widgets[row][column]
+        widget.insert(0, value)
+
+    def get(self, row, column):
+        widget = self._widgets[row][column]
+        return widget.get()
+
+    def addRow( self, value=None, readonly=False ):
+        #print "Adding row " + str(self.rows +1)
+        current_row = []
+        for column in range(self.columns):
+            label = Entry(self, borderwidth=0)
+            label.grid(row=self.rows, column=column, sticky="wens", padx=1, pady=1)
+            if value is not None:
+                label.insert(0, value[column])
+            if (readonly == True):
+                label.configure(state='readonly')
+            current_row.append(label)
+        self._widgets.append(current_row)
+        self.update_idletasks()
+        self.rows += 1
+
+class LinkDialog(tkSimpleDialog.Dialog):
+
+        def __init__(self, parent, title, linkDefaults):
+
+            self.linkValues = linkDefaults
+
+            tkSimpleDialog.Dialog.__init__(self, parent, title)
+
+        def body(self, master):
+
+            self.var = StringVar(master)
+            Label(master, text="Bandwidth:").grid(row=0, sticky=E)
+            self.e1 = Entry(master)
+            self.e1.grid(row=0, column=1)
+            Label(master, text="Mbit").grid(row=0, column=2, sticky=W)
+            if 'bw' in self.linkValues:
+                self.e1.insert(0,str(self.linkValues['bw']))
+
+            Label(master, text="Delay:").grid(row=1, sticky=E)
+            self.e2 = Entry(master)
+            self.e2.grid(row=1, column=1)
+            if 'delay' in self.linkValues:
+                self.e2.insert(0, self.linkValues['delay'])
+
+            Label(master, text="Loss:").grid(row=2, sticky=E)
+            self.e3 = Entry(master)
+            self.e3.grid(row=2, column=1)
+            Label(master, text="%").grid(row=2, column=2, sticky=W)
+            if 'loss' in self.linkValues:
+                self.e3.insert(0, str(self.linkValues['loss']))
+
+            Label(master, text="Max Queue size:").grid(row=3, sticky=E)
+            self.e4 = Entry(master)
+            self.e4.grid(row=3, column=1)
+            if 'max_queue_size' in self.linkValues:
+                self.e4.insert(0, str(self.linkValues['max_queue_size']))
+
+            Label(master, text="Jitter:").grid(row=4, sticky=E)
+            self.e5 = Entry(master)
+            self.e5.grid(row=4, column=1)
+            if 'jitter' in self.linkValues:
+                self.e5.insert(0, self.linkValues['jitter'])
+
+            Label(master, text="Speedup:").grid(row=5, sticky=E)
+            self.e6 = Entry(master)
+            self.e6.grid(row=5, column=1)
+            if 'speedup' in self.linkValues:
+                self.e6.insert(0, str(self.linkValues['speedup']))
+
+            return self.e1 # initial focus
+
+        def apply(self):
+            self.result = {}
+            if (len(self.e1.get()) > 0):
+                self.result['bw'] = int(self.e1.get())
+            if (len(self.e2.get()) > 0):
+                self.result['delay'] = self.e2.get()
+            if (len(self.e3.get()) > 0):
+                self.result['loss'] = int(self.e3.get())
+            if (len(self.e4.get()) > 0):
+                self.result['max_queue_size'] = int(self.e4.get())
+            if (len(self.e5.get()) > 0):
+                self.result['jitter'] = self.e5.get()
+            if (len(self.e6.get()) > 0):
+                self.result['speedup'] = int(self.e6.get())
+
+class ControllerDialog(tkSimpleDialog.Dialog):
+
+        def __init__(self, parent, title, ctrlrDefaults=None):
+
+            if ctrlrDefaults:
+                self.ctrlrValues = ctrlrDefaults
+
+            tkSimpleDialog.Dialog.__init__(self, parent, title)
+
+        def body(self, master):
+
+            self.var = StringVar(master)
+
+            rowCount=0
+            # Field for Hostname
+            Label(master, text="Name:").grid(row=rowCount, sticky=E)
+            self.hostnameEntry = Entry(master)
+            self.hostnameEntry.grid(row=rowCount, column=1)
+            self.hostnameEntry.insert(0, self.ctrlrValues['hostname'])
+            rowCount+=1
+
+            # Field for Remove Controller Port
+            Label(master, text="Controller Port:").grid(row=rowCount, sticky=E)
+            self.e2 = Entry(master)
+            self.e2.grid(row=rowCount, column=1)
+            self.e2.insert(0, self.ctrlrValues['remotePort'])
+            rowCount+=1
+
+            # Field for Controller Type
+            Label(master, text="Controller Type:").grid(row=rowCount, sticky=E)
+            controllerType = self.ctrlrValues['controllerType']
+            self.o1 = OptionMenu(master, self.var, "Remote Controller", "In-Band Controller", "OpenFlow Reference", "OVS Controller")
+            self.o1.grid(row=rowCount, column=1, sticky=W)
+            if controllerType == 'ref':
+                self.var.set("OpenFlow Reference")
+            elif controllerType == 'inband':
+                self.var.set("In-Band Controller")
+            elif controllerType == 'remote':
+                self.var.set("Remote Controller")
+            else:
+                self.var.set("OVS Controller")
+            rowCount+=1
+
+            # Field for Remove Controller IP
+            remoteFrame= LabelFrame(master, text='Remote/In-Band Controller', padx=5, pady=5)
+            remoteFrame.grid(row=rowCount, column=0, columnspan=2, sticky=W)
+
+            Label(remoteFrame, text="IP Address:").grid(row=0, sticky=E)
+            self.e1 = Entry(remoteFrame)
+            self.e1.grid(row=0, column=1)
+            self.e1.insert(0, self.ctrlrValues['remoteIP'])
+            rowCount+=1
+
+            return self.hostnameEntry # initial focus
+
+        def apply(self):
+            hostname = self.hostnameEntry.get()
+            controllerType = self.var.get()
+            remoteIP = self.e1.get()
+            controllerPort = int(self.e2.get())
+            self.result = { 'hostname': hostname,
+                            'remoteIP': remoteIP,
+                            'remotePort': controllerPort}
+
+            if controllerType == 'Remote Controller':
+                self.result['controllerType'] = 'remote'
+            elif controllerType == 'In-Band Controller':
+                self.result['controllerType'] = 'inband'
+            elif controllerType == 'OpenFlow Reference':
+                self.result['controllerType'] = 'ref'
+            else:
+                self.result['controllerType'] = 'ovsc'
+
+class ToolTip(object):
+
+    def __init__(self, widget):
+        self.widget = widget
+        self.tipwindow = None
+        self.id = None
+        self.x = self.y = 0
+
+    def showtip(self, text):
+        "Display text in tooltip window"
+        self.text = text
+        if self.tipwindow or not self.text:
+            return
+        x, y, cx, cy = self.widget.bbox("insert")
+        x = x + self.widget.winfo_rootx() + 27
+        y = y + cy + self.widget.winfo_rooty() +27
+        self.tipwindow = tw = Toplevel(self.widget)
+        tw.wm_overrideredirect(1)
+        tw.wm_geometry("+%d+%d" % (x, y))
+        try:
+            # For Mac OS
+            tw.tk.call("::tk::unsupported::MacWindowStyle",
+                       "style", tw._w,
+                       "help", "noActivates")
+        except TclError:
+            pass
+        label = Label(tw, text=self.text, justify=LEFT,
+                      background="#ffffe0", relief=SOLID, borderwidth=1,
+                      font=("tahoma", "8", "normal"))
+        label.pack(ipadx=1)
+
+    def hidetip(self):
+        tw = self.tipwindow
+        self.tipwindow = None
+        if tw:
+            tw.destroy()
 
 class MiniEdit( Frame ):
 
     "A simple network editor for Mininet."
 
-    def __init__( self, parent=None, cheight=200, cwidth=500 ):
+    def __init__( self, parent=None, cheight=600, cwidth=1000 ):
+
+        self.defaultIpBase='10.0.0.0/8'
+
+        self.nflowDefaults = {'nflowTarget':'',
+                              'nflowTimeout':'600',
+                              'nflowAddId':'0'}
+        self.sflowDefaults = {'sflowTarget':'',
+                              'sflowSampling':'400',
+                              'sflowHeader':'128',
+                              'sflowPolling':'30'}
+
+        self.appPrefs={
+            "ipBase": self.defaultIpBase,
+            "startCLI": "0",
+            "terminalType": 'xterm',
+            "switchType": 'ovs',
+            "dpctl": '',
+            'sflow':self.sflowDefaults,
+            'netflow':self.nflowDefaults,
+            'openFlowVersions':{'ovsOf10':'1',
+                                'ovsOf11':'0',
+                                'ovsOf12':'0',
+                                'ovsOf13':'0'}
+
+        }
+
 
         Frame.__init__( self, parent )
         self.action = None
         self.appName = 'MiniEdit'
+        self.fixedFont = tkFont.Font ( family="DejaVu Sans Mono", size="14" )
 
         # Style
         self.font = ( 'Geneva', 9 )
@@ -47,11 +1028,14 @@ def __init__( self, parent=None, cheight=200, cwidth=500 ):
         self.cheight, self.cwidth = cheight, cwidth
         self.cframe, self.canvas = self.createCanvas()
 
+        # Toolbar
+        self.controllers = {}
+
         # Toolbar
         self.images = miniEditImages()
         self.buttons = {}
         self.active = None
-        self.tools = ( 'Select', 'Host', 'Switch', 'Link' )
+        self.tools = ( 'Select', 'Host', 'Switch', 'LegacySwitch', 'LegacyRouter', 'NetLink', 'Controller' )
         self.customColors = { 'Switch': 'darkGreen', 'Host': 'blue' }
         self.toolbar = self.createToolbar()
 
@@ -67,7 +1051,7 @@ def __init__( self, parent=None, cheight=200, cwidth=500 ):
 
         # Initialize node data
         self.nodeBindings = self.createNodeBindings()
-        self.nodePrefixes = { 'Switch': 's', 'Host': 'h' }
+        self.nodePrefixes = { 'LegacyRouter': 'r', 'LegacySwitch': 's', 'Switch': 's', 'Host': 'h' , 'Controller': 'c'}
         self.widgetToItem = {}
         self.itemToWidget = {}
 
@@ -83,13 +1067,59 @@ def __init__( self, parent=None, cheight=200, cwidth=500 ):
         self.bind( '<KeyPress-BackSpace>', self.deleteSelection )
         self.focus()
 
+        self.hostPopup = Menu(self.top, tearoff=0)
+        self.hostPopup.add_command(label='Host Options', font=self.font)
+        self.hostPopup.add_separator()
+        self.hostPopup.add_command(label='Properties', font=self.font, command=self.hostDetails )
+
+        self.hostRunPopup = Menu(self.top, tearoff=0)
+        self.hostRunPopup.add_command(label='Host Options', font=self.font)
+        self.hostRunPopup.add_separator()
+        self.hostRunPopup.add_command(label='Terminal', font=self.font, command=self.xterm )
+
+        self.legacyRouterRunPopup = Menu(self.top, tearoff=0)
+        self.legacyRouterRunPopup.add_command(label='Router Options', font=self.font)
+        self.legacyRouterRunPopup.add_separator()
+        self.legacyRouterRunPopup.add_command(label='Terminal', font=self.font, command=self.xterm )
+
+        self.switchPopup = Menu(self.top, tearoff=0)
+        self.switchPopup.add_command(label='Switch Options', font=self.font)
+        self.switchPopup.add_separator()
+        self.switchPopup.add_command(label='Properties', font=self.font, command=self.switchDetails )
+
+        self.switchRunPopup = Menu(self.top, tearoff=0)
+        self.switchRunPopup.add_command(label='Switch Options', font=self.font)
+        self.switchRunPopup.add_separator()
+        self.switchRunPopup.add_command(label='List bridge details', font=self.font, command=self.listBridge )
+
+        self.linkPopup = Menu(self.top, tearoff=0)
+        self.linkPopup.add_command(label='Link Options', font=self.font)
+        self.linkPopup.add_separator()
+        self.linkPopup.add_command(label='Properties', font=self.font, command=self.linkDetails )
+
+        self.linkRunPopup = Menu(self.top, tearoff=0)
+        self.linkRunPopup.add_command(label='Link Options', font=self.font)
+        self.linkRunPopup.add_separator()
+        self.linkRunPopup.add_command(label='Link Up', font=self.font, command=self.linkUp )
+        self.linkRunPopup.add_command(label='Link Down', font=self.font, command=self.linkDown )
+
+        self.controllerPopup = Menu(self.top, tearoff=0)
+        self.controllerPopup.add_command(label='Controller Options', font=self.font)
+        self.controllerPopup.add_separator()
+        self.controllerPopup.add_command(label='Properties', font=self.font, command=self.controllerDetails )
+
+
         # Event handling initalization
         self.linkx = self.linky = self.linkItem = None
         self.lastSelection = None
 
         # Model initialization
         self.links = {}
-        self.nodeCount = 0
+        self.hostOpts = {}
+        self.switchOpts = {}
+        self.hostCount = 0
+        self.switchCount = 0
+        self.controllerCount = 0
         self.net = None
 
         # Close window gracefully
@@ -108,34 +1138,35 @@ def createMenubar( self ):
         mbar = Menu( self.top, font=font )
         self.top.configure( menu=mbar )
 
-        # Application menu
-        appMenu = Menu( mbar, tearoff=False )
-        mbar.add_cascade( label=self.appName, font=font, menu=appMenu )
-        appMenu.add_command( label='About MiniEdit', command=self.about,
-                             font=font)
-        appMenu.add_separator()
-        appMenu.add_command( label='Quit', command=self.quit, font=font )
 
-        #fileMenu = Menu( mbar, tearoff=False )
-        #mbar.add_cascade( label="File", font=font, menu=fileMenu )
-        #fileMenu.add_command( label="Load...", font=font )
-        #fileMenu.add_separator()
-        #fileMenu.add_command( label="Save", font=font )
-        #fileMenu.add_separator()
-        #fileMenu.add_command( label="Print", font=font )
+        fileMenu = Menu( mbar, tearoff=False )
+        mbar.add_cascade( label="File", font=font, menu=fileMenu )
+        fileMenu.add_command( label="New", font=font, command=self.newTopology )
+        fileMenu.add_command( label="Open", font=font, command=self.loadTopology )
+        fileMenu.add_command( label="Save", font=font, command=self.saveTopology )
+        fileMenu.add_command( label="Export", font=font, command=self.exportTopology )
+        fileMenu.add_separator()
+        fileMenu.add_command( label='Quit', command=self.quit, font=font )
 
         editMenu = Menu( mbar, tearoff=False )
         mbar.add_cascade( label="Edit", font=font, menu=editMenu )
         editMenu.add_command( label="Cut", font=font,
                               command=lambda: self.deleteSelection( None ) )
+        editMenu.add_command( label="Preferences", font=font, command=self.prefDetails)
 
         runMenu = Menu( mbar, tearoff=False )
         mbar.add_cascade( label="Run", font=font, menu=runMenu )
         runMenu.add_command( label="Run", font=font, command=self.doRun )
         runMenu.add_command( label="Stop", font=font, command=self.doStop )
-        runMenu.add_separator()
-        runMenu.add_command( label='Xterm', font=font, command=self.xterm )
+        fileMenu.add_separator()
+        runMenu.add_command( label='Show OVS Summary', font=font, command=self.ovsShow )
+        runMenu.add_command( label='Root Terminal', font=font, command=self.rootTerminal )
 
+        # Application menu
+        appMenu = Menu( mbar, tearoff=False )
+        mbar.add_cascade( label="Help", font=font, menu=appMenu )
+        appMenu.add_command( label='About MiniEdit', command=self.about,
+                             font=font)
     # Canvas
 
     def createCanvas( self ):
@@ -200,6 +1231,16 @@ def activate( self, toolName ):
         # Activate dynamic bindings
         self.active = toolName
 
+
+    def createToolTip(self, widget, text):
+        toolTip = ToolTip(widget)
+        def enter(event):
+            toolTip.showtip(text)
+        def leave(event):
+            toolTip.hidetip()
+        widget.bind('<Enter>', enter)
+        widget.bind('<Leave>', leave)
+
     def createToolbar( self ):
         "Create and return our toolbar frame."
 
@@ -211,6 +1252,7 @@ def createToolbar( self ):
             b = Button( toolbar, text=tool, font=self.smallFont, command=cmd)
             if tool in self.images:
                 b.config( height=35, image=self.images[ tool ] )
+                self.createToolTip(b, str(tool))
                 # b.config( compound='top' )
             b.pack( fill='x' )
             self.buttons[ tool ] = b
@@ -241,6 +1283,559 @@ def doStop( self ):
         for tool in self.tools:
             self.buttons[ tool ].config( state='normal' )
 
+    def addNode( self, node, nodeNum, x, y, name=None):
+        "Add a new node to our canvas."
+        if 'Switch' == node:
+            self.switchCount += 1
+        if 'Host' == node:
+            self.hostCount += 1
+        if 'Controller' == node:
+            self.controllerCount += 1
+        if name is None:
+            name = self.nodePrefixes[ node ] + nodeNum
+        self.addNamedNode(node, name, x, y)
+
+    def addNamedNode( self, node, name, x, y):
+        "Add a new node to our canvas."
+        c = self.canvas
+        icon = self.nodeIcon( node, name )
+        item = self.canvas.create_window( x, y, anchor='c', window=icon,
+                                          tags=node )
+        self.widgetToItem[ icon ] = item
+        self.itemToWidget[ item ] = icon
+        icon.links = {}
+
+    def loadTopology( self ):
+        "Load command."
+        c = self.canvas
+
+        myFormats = [
+            ('Mininet Topology','*.mn'),
+            ('All Files','*'),
+        ]
+        f = tkFileDialog.askopenfile(filetypes=myFormats, mode='rb')
+        if f == None:
+            return
+        self.newTopology()
+        loadedTopology = eval(f.read())
+
+        # Load application preferences
+        if 'application' in loadedTopology:
+            self.appPrefs = dict(self.appPrefs.items() + loadedTopology['application'].items())
+            if "ovsOf10" not in self.appPrefs["openFlowVersions"]:
+                self.appPrefs["openFlowVersions"]["ovsOf10"] = '0'
+            if "ovsOf11" not in self.appPrefs["openFlowVersions"]:
+                self.appPrefs["openFlowVersions"]["ovsOf11"] = '0'
+            if "ovsOf12" not in self.appPrefs["openFlowVersions"]:
+                self.appPrefs["openFlowVersions"]["ovsOf12"] = '0'
+            if "ovsOf13" not in self.appPrefs["openFlowVersions"]:
+                self.appPrefs["openFlowVersions"]["ovsOf13"] = '0'
+            if "sflow" not in self.appPrefs:
+                self.appPrefs["sflow"] = self.sflowDefaults
+            if "netflow" not in self.appPrefs:
+                self.appPrefs["netflow"] = self.nflowDefaults
+
+        # Load controllers
+        if ('controllers' in loadedTopology):
+            if (loadedTopology['version'] == '1'):
+                # This is old location of controller info
+                hostname = 'c0'
+                self.controllers = {}
+                self.controllers[hostname] = loadedTopology['controllers']['c0']
+                self.controllers[hostname]['hostname'] = hostname
+                self.addNode('Controller', 0, float(30), float(30), name=hostname)
+                icon = self.findWidgetByName(hostname)
+                icon.bind('<Button-3>', self.do_controllerPopup )
+            else:
+                controllers = loadedTopology['controllers']
+                for controller in controllers:
+                    hostname = controller['opts']['hostname']
+                    x = controller['x']
+                    y = controller['y']
+                    self.addNode('Controller', 0, float(x), float(y), name=hostname)
+                    self.controllers[hostname] = controller['opts']
+                    icon = self.findWidgetByName(hostname)
+                    icon.bind('<Button-3>', self.do_controllerPopup )
+
+
+        # Load hosts
+        hosts = loadedTopology['hosts']
+        for host in hosts:
+            nodeNum = host['number']
+            hostname = 'h'+nodeNum
+            if 'hostname' in host['opts']:
+                hostname = host['opts']['hostname']
+            else:
+                host['opts']['hostname'] = hostname
+            if 'nodeNum' not in host['opts']:
+                host['opts']['nodeNum'] = int(nodeNum)
+            x = host['x']
+            y = host['y']
+            self.addNode('Host', nodeNum, float(x), float(y), name=hostname)
+            self.hostOpts[hostname] = host['opts']
+            icon = self.findWidgetByName(hostname)
+            icon.bind('<Button-3>', self.do_hostPopup )
+
+        # Load switches
+        switches = loadedTopology['switches']
+        for switch in switches:
+            nodeNum = switch['number']
+            hostname = 's'+nodeNum
+            if 'controllers' not in switch['opts']:
+                switch['opts']['controllers'] = []
+            if 'switchType' not in switch['opts']:
+                switch['opts']['switchType'] = 'default'
+            if 'hostname' in switch['opts']:
+                hostname = switch['opts']['hostname']
+            else:
+                switch['opts']['hostname'] = hostname
+            if 'nodeNum' not in switch['opts']:
+                switch['opts']['nodeNum'] = int(nodeNum)
+            x = switch['x']
+            y = switch['y']
+            if switch['opts']['switchType'] == "legacyRouter":
+                self.addNode('LegacyRouter', nodeNum, float(x), float(y), name=hostname)
+                icon = self.findWidgetByName(hostname)
+                icon.bind('<Button-3>', self.do_legacyRouterPopup )
+            elif switch['opts']['switchType'] == "legacySwitch":
+                self.addNode('LegacySwitch', nodeNum, float(x), float(y), name=hostname)
+                icon = self.findWidgetByName(hostname)
+                icon.bind('<Button-3>', self.do_legacySwitchPopup )
+            else:
+                self.addNode('Switch', nodeNum, float(x), float(y), name=hostname)
+                icon = self.findWidgetByName(hostname)
+                icon.bind('<Button-3>', self.do_switchPopup )
+            self.switchOpts[hostname] = switch['opts']
+
+            # create links to controllers
+            if (int(loadedTopology['version']) > 1):
+                controllers = self.switchOpts[hostname]['controllers']
+                for controller in controllers:
+                    dest = self.findWidgetByName(controller)
+                    dx, dy = self.canvas.coords( self.widgetToItem[ dest ] )
+                    self.link = self.canvas.create_line(float(x),
+                                                        float(y),
+                                                        dx,
+                                                        dy,
+                                                        width=4,
+                                                        fill='red',
+                                                        dash=(6, 4, 2, 4),
+                                                        tag='link' )
+                    c.itemconfig(self.link, tags=c.gettags(self.link)+('control',))
+                    self.addLink( icon, dest, linktype='control' )
+                    self.createControlLinkBindings()
+                    self.link = self.linkWidget = None
+            else:
+                dest = self.findWidgetByName('c0')
+                dx, dy = self.canvas.coords( self.widgetToItem[ dest ] )
+                self.link = self.canvas.create_line(float(x),
+                                                    float(y),
+                                                    dx,
+                                                    dy,
+                                                    width=4,
+                                                    fill='red',
+                                                    dash=(6, 4, 2, 4),
+                                                    tag='link' )
+                c.itemconfig(self.link, tags=c.gettags(self.link)+('control',))
+                self.addLink( icon, dest, linktype='control' )
+                self.createControlLinkBindings()
+                self.link = self.linkWidget = None
+
+        # Load links
+        links = loadedTopology['links']
+        for link in links:
+            srcNode = link['src']
+            src = self.findWidgetByName(srcNode)
+            sx, sy = self.canvas.coords( self.widgetToItem[ src ] )
+
+            destNode = link['dest']
+            dest = self.findWidgetByName(destNode)
+            dx, dy = self.canvas.coords( self.widgetToItem[ dest]  )
+
+            self.link = self.canvas.create_line( sx, sy, dx, dy, width=4,
+                                             fill='blue', tag='link' )
+            c.itemconfig(self.link, tags=c.gettags(self.link)+('data',))
+            self.addLink( src, dest, linkopts=link['opts'] )
+            self.createDataLinkBindings()
+            self.link = self.linkWidget = None
+
+        f.close
+
+    def findWidgetByName( self, name ):
+        for widget in self.widgetToItem:
+            if name ==  widget[ 'text' ]:
+                return widget
+
+    def newTopology( self ):
+        "New command."
+        for widget in self.widgetToItem.keys():
+            self.deleteItem( self.widgetToItem[ widget ] )
+        self.hostCount = 0
+        self.switchCount = 0
+        self.controllerCount = 0
+        self.links = {}
+        self.hostOpts = {}
+        self.switchOpts = {}
+        self.controllers = {}
+        self.appPrefs["ipBase"]= self.defaultIpBase
+
+    def saveTopology( self ):
+        "Save command."
+        myFormats = [
+            ('Mininet Topology','*.mn'),
+            ('All Files','*'),
+        ]
+
+        savingDictionary = {}
+        fileName = tkFileDialog.asksaveasfilename(filetypes=myFormats ,title="Save the topology as...")
+        if len(fileName ) > 0:
+            # Save Application preferences
+            savingDictionary['version'] = '2'
+
+            # Save Switches and Hosts
+            hostsToSave = []
+            switchesToSave = []
+            controllersToSave = []
+            for widget in self.widgetToItem:
+                name = widget[ 'text' ]
+                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
+                x1, y1 = self.canvas.coords( self.widgetToItem[ widget ] )
+                if 'Switch' in tags or 'LegacySwitch' in tags or 'LegacyRouter' in tags:
+                    nodeNum = self.switchOpts[name]['nodeNum']
+                    nodeToSave = {'number':str(nodeNum),
+                                  'x':str(x1),
+                                  'y':str(y1),
+                                  'opts':self.switchOpts[name] }
+                    switchesToSave.append(nodeToSave)
+                elif 'Host' in tags:
+                    nodeNum = self.hostOpts[name]['nodeNum']
+                    nodeToSave = {'number':str(nodeNum),
+                                  'x':str(x1),
+                                  'y':str(y1),
+                                  'opts':self.hostOpts[name] }
+                    hostsToSave.append(nodeToSave)
+                elif 'Controller' in tags:
+                    nodeToSave = {'x':str(x1),
+                                  'y':str(y1),
+                                  'opts':self.controllers[name] }
+                    controllersToSave.append(nodeToSave)
+                else:
+                    raise Exception( "Cannot create mystery node: " + name )
+            savingDictionary['hosts'] = hostsToSave
+            savingDictionary['switches'] = switchesToSave
+            savingDictionary['controllers'] = controllersToSave
+
+            # Save Links
+            linksToSave = []
+            for link in self.links.values():
+                src = link['src']
+                dst = link['dest']
+                linkopts = link['linkOpts']
+
+                srcName, dstName = src[ 'text' ], dst[ 'text' ]
+                linkToSave = {'src':srcName,
+                              'dest':dstName,
+                              'opts':linkopts}
+                if link['type'] == 'data':
+                    linksToSave.append(linkToSave)
+            savingDictionary['links'] = linksToSave
+
+            # Save Application preferences
+            savingDictionary['application'] = self.appPrefs
+
+            try:
+                f = open(fileName, 'wb')
+                #f.write(str(savingDictionary))
+                f.write(json.dumps(savingDictionary, sort_keys=True, indent=4, separators=(',', ': ')))
+            except Exception as er:
+                print er
+            finally:
+                f.close()
+
+    def exportTopology( self ):
+        "Export command."
+        myFormats = [
+            ('Mininet Custom Topology','*.py'),
+            ('All Files','*'),
+        ]
+
+        fileName = tkFileDialog.asksaveasfilename(filetypes=myFormats ,title="Export the topology as...")
+        if len(fileName ) > 0:
+            #print "Now saving under %s" % fileName
+            f = open(fileName, 'wb')
+
+            f.write("#!/usr/bin/python\n")
+            f.write("\n")
+            f.write("from mininet.net import Mininet\n")
+            f.write("from mininet.node import Controller, RemoteController, OVSController\n")
+            f.write("from mininet.node import CPULimitedHost, Host, Node\n")
+            f.write("from mininet.node import OVSKernelSwitch, UserSwitch\n")
+            if StrictVersion(MININET_VERSION) > StrictVersion('2.0'):
+                f.write("from mininet.node import IVSSwitch\n")
+            f.write("from mininet.cli import CLI\n")
+            f.write("from mininet.log import setLogLevel, info\n")
+            f.write("from mininet.link import TCLink, Intf\n")
+
+            inBandCtrl = False
+            hasLegacySwitch = False
+            for widget in self.widgetToItem:
+                name = widget[ 'text' ]
+                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
+
+                if 'Controller' in tags:
+                    opts = self.controllers[name]
+                    controllerType = opts['controllerType']
+                    if controllerType == 'inband':
+                        inBandCtrl = True
+
+            if inBandCtrl == True:
+                f.write("\n")
+                f.write("class InbandController( RemoteController ):\n")
+                f.write("\n")
+                f.write("    def checkListening( self ):\n")
+                f.write("        \"Overridden to do nothing.\"\n")
+                f.write("        return\n")
+
+            f.write("\n")
+            f.write("def myNetwork():\n")
+            f.write("\n")
+            f.write("    net = Mininet( topo=None,\n")
+            if len(self.appPrefs['dpctl']) > 0:
+                f.write("                   listenPort="+self.appPrefs['dpctl']+",\n")
+            f.write("                   build=False,\n")
+            f.write("                   ipBase='"+self.appPrefs['ipBase']+"')\n")
+            f.write("\n")
+            f.write("    info( '*** Adding controller\\n' )\n")
+            for widget in self.widgetToItem:
+                name = widget[ 'text' ]
+                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
+    
+                if 'Controller' in tags:
+                    opts = self.controllers[name]
+                    controllerType = opts['controllerType']
+                    controllerIP = opts['remoteIP']
+                    controllerPort = opts['remotePort']
+
+    
+                    f.write("    "+name+"=net.addController(name='"+name+"',\n")
+        
+                    if controllerType == 'remote':
+                        f.write("                      controller=RemoteController,\n")
+                        f.write("                      ip='"+controllerIP+"',\n")
+                    elif controllerType == 'inband':
+                        f.write("                      controller=InbandController,\n")
+                        f.write("                      ip='"+controllerIP+"',\n")
+                    elif controllerType == 'ovsc':
+                        f.write("                      controller=OVSController,\n")
+                    else:
+                        f.write("                      controller=Controller,\n")
+        
+                    f.write("                      port="+str(controllerPort)+")\n")
+                    f.write("\n")
+
+            # Save Switches and Hosts
+            f.write("    info( '*** Add switches\\n')\n")
+            for widget in self.widgetToItem:
+                name = widget[ 'text' ]
+                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
+                if 'LegacyRouter' in tags:
+                    f.write("    "+name+" = net.addHost('"+name+"', cls=Node, ip='0.0.0.0')\n")
+                    f.write("    "+name+".cmd('sysctl -w net.ipv4.ip_forward=1')\n")
+                if 'LegacySwitch' in tags:
+                    f.write("    "+name+" = net.addSwitch('"+name+"', cls=OVSKernelSwitch, failMode='standalone')\n")
+                if 'Switch' in tags:
+                    opts = self.switchOpts[name]
+                    nodeNum = opts['nodeNum']
+                    f.write("    "+name+" = net.addSwitch('"+name+"'")
+                    if opts['switchType'] == 'default':
+                        if self.appPrefs['switchType'] == 'ivs':
+                            f.write(", cls=IVSSwitch")
+                        elif self.appPrefs['switchType'] == 'user':
+                            f.write(", cls=UserSwitch")
+                        elif self.appPrefs['switchType'] == 'userns':
+                            f.write(", cls=UserSwitch, inNamespace=True")
+                        else:
+                            f.write(", cls=OVSKernelSwitch")
+                    elif opts['switchType'] == 'ivs':
+                        f.write(", cls=IVSSwitch")
+                    elif opts['switchType'] == 'user':
+                        f.write(", cls=UserSwitch")
+                    elif opts['switchType'] == 'userns':
+                        f.write(", cls=UserSwitch, inNamespace=True")
+                    else:
+                        f.write(", cls=OVSKernelSwitch")
+                    if 'dpctl' in opts:
+                        f.write(", listenPort="+opts['dpctl'])
+                    if 'dpid' in opts:
+                        f.write(", dpid='"+opts['dpid']+"'")
+                    f.write(")\n")
+                    if ('externalInterfaces' in opts):
+                        for extInterface in opts['externalInterfaces']:
+                            f.write("    Intf( '"+extInterface+"', node="+name+" )\n")
+
+            f.write("\n")
+            f.write("    info( '*** Add hosts\\n')\n")
+            for widget in self.widgetToItem:
+                name = widget[ 'text' ]
+                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
+                if 'Host' in tags:
+                    opts = self.hostOpts[name]
+                    ip = None
+                    defaultRoute = None
+                    if 'defaultRoute' in opts and len(opts['defaultRoute']) > 0:
+                        defaultRoute = "'via "+opts['defaultRoute']+"'"
+                    else:
+                        defaultRoute = 'None'
+                    if 'ip' in opts and len(opts['ip']) > 0:
+                        ip = opts['ip']
+                    else:
+                        nodeNum = self.hostOpts[name]['nodeNum']
+                        ipBaseNum, prefixLen = netParse( self.appPrefs['ipBase'] )
+                        ip = ipAdd(i=nodeNum, prefixLen=prefixLen, ipBaseNum=ipBaseNum)
+
+                    if 'cores' in opts or 'cpu' in opts:
+                        f.write("    "+name+" = net.addHost('"+name+"', cls=CPULimitedHost, ip='"+ip+"', defaultRoute="+defaultRoute+")\n")
+                        if 'cores' in opts:
+                            f.write("    "+name+".setCPUs(cores='"+opts['cores']+"')\n")
+                        if 'cpu' in opts:
+                            f.write("    "+name+".setCPUFrac(f="+str(opts['cpu'])+", sched='"+opts['sched']+"')\n")
+                    else:
+                        f.write("    "+name+" = net.addHost('"+name+"', cls=Host, ip='"+ip+"', defaultRoute="+defaultRoute+")\n")
+                    if ('externalInterfaces' in opts):
+                        for extInterface in opts['externalInterfaces']:
+                            f.write("    Intf( '"+extInterface+"', node="+name+" )\n")
+            f.write("\n")
+
+            # Save Links
+            f.write("    info( '*** Add links\\n')\n")
+            for key,linkDetail in self.links.iteritems():
+              tags = self.canvas.gettags(key)
+              if 'data' in tags:
+                optsExist = False
+                src = linkDetail['src']
+                dst = linkDetail['dest']
+                linkopts = linkDetail['linkOpts']
+                srcName, dstName = src[ 'text' ], dst[ 'text' ]
+                bw = ''
+                delay = ''
+                loss = ''
+                max_queue_size = ''
+                linkOpts = "{"
+                if 'bw' in linkopts:
+                    bw =  linkopts['bw']
+                    linkOpts = linkOpts + "'bw':"+str(bw)
+                    optsExist = True
+                if 'delay' in linkopts:
+                    delay =  linkopts['delay']
+                    if optsExist:
+                        linkOpts = linkOpts + ","
+                    linkOpts = linkOpts + "'delay':'"+linkopts['delay']+"'"
+                    optsExist = True
+                if 'loss' in linkopts:
+                    if optsExist:
+                        linkOpts = linkOpts + ","
+                    linkOpts = linkOpts + "'loss':"+str(linkopts['loss'])
+                    optsExist = True
+                if 'max_queue_size' in linkopts:
+                    if optsExist:
+                        linkOpts = linkOpts + ","
+                    linkOpts = linkOpts + "'max_queue_size':"+str(linkopts['max_queue_size'])
+                    optsExist = True
+                if 'jitter' in linkopts:
+                    if optsExist:
+                        linkOpts = linkOpts + ","
+                    linkOpts = linkOpts + "'jitter':'"+linkopts['jitter']+"'"
+                    optsExist = True
+                if 'speedup' in linkopts:
+                    if optsExist:
+                        linkOpts = linkOpts + ","
+                    linkOpts = linkOpts + "'speedup':"+str(linkopts['speedup'])
+                    optsExist = True
+
+                linkOpts = linkOpts + "}"
+                if optsExist:
+                    f.write("    "+srcName+dstName+" = "+linkOpts+"\n")
+                f.write("    net.addLink("+srcName+", "+dstName)
+                if optsExist:
+                    f.write(", link=TCLink , **"+srcName+dstName)
+                f.write(")\n")
+
+            f.write("\n")
+            f.write("    info( '*** Starting network\\n')\n")
+            f.write("    net.build()\n")
+
+            f.write("    info( '*** Starting controllers\\n')\n")
+            f.write("    for controller in net.controllers:\n")
+            f.write("        controller.start()\n")
+            f.write("\n")
+
+            f.write("    info( '*** Starting switches\\n')\n")
+            for widget in self.widgetToItem:
+                name = widget[ 'text' ]
+                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
+                if 'Switch' in tags or 'LegacySwitch' in tags:
+                    opts = self.switchOpts[name]
+                    ctrlList = ",".join(opts['controllers'])
+                    f.write("    net.get('"+name+"').start(["+ctrlList+"])\n")
+
+            f.write("\n")
+
+            f.write("    info( '*** Configuring switches\\n')\n")
+            for widget in self.widgetToItem:
+                name = widget[ 'text' ]
+                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
+                if 'Switch' in tags:
+                    opts = self.switchOpts[name]
+                    if opts['switchType'] == 'default':
+                        if self.appPrefs['switchType'] == 'user':
+                            if ('switchIP' in opts):
+                                if (len(opts['switchIP'])>0):
+                                    f.write("    "+name+".cmd('ifconfig "+name+" "+opts['switchIP']+"')\n")
+                        elif self.appPrefs['switchType'] == 'userns':
+                            if ('switchIP' in opts):
+                                if (len(opts['switchIP'])>0):
+                                    f.write("    "+name+".cmd('ifconfig lo "+opts['switchIP']+"')\n")
+                        elif self.appPrefs['switchType'] == 'ovs':
+                            if ('switchIP' in opts):
+                                if (len(opts['switchIP'])>0):
+                                    f.write("    "+name+".cmd('ifconfig "+name+" "+opts['switchIP']+"')\n")
+                    elif opts['switchType'] == 'user':
+                        if ('switchIP' in opts):
+                            if (len(opts['switchIP'])>0):
+                                f.write("    "+name+".cmd('ifconfig "+name+" "+opts['switchIP']+"')\n")
+                    elif opts['switchType'] == 'userns':
+                        if ('switchIP' in opts):
+                            if (len(opts['switchIP'])>0):
+                                f.write("    "+name+".cmd('ifconfig lo "+opts['switchIP']+"')\n")
+                    elif opts['switchType'] == 'ovs':
+                        if ('switchIP' in opts):
+                            if (len(opts['switchIP'])>0):
+                                f.write("    "+name+".cmd('ifconfig "+name+" "+opts['switchIP']+"')\n")
+            for widget in self.widgetToItem:
+                name = widget[ 'text' ]
+                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
+                if 'Host' in tags:
+                    opts = self.hostOpts[name]
+                    # Attach vlan interfaces
+                    if ('vlanInterfaces' in opts):
+                        for vlanInterface in opts['vlanInterfaces']:
+                            f.write("    "+name+".cmd('vconfig add "+name+"-eth0 "+vlanInterface[1]+"')\n")
+                            f.write("    "+name+".cmd('ifconfig "+name+"-eth0."+vlanInterface[1]+" "+vlanInterface[0]+"')\n")
+
+
+            f.write("\n")
+            f.write("    CLI(net)\n")
+            f.write("    net.stop()\n")
+            f.write("\n")
+            f.write("if __name__ == '__main__':\n")
+            f.write("    setLogLevel( 'info' )\n")
+            f.write("    myNetwork()\n")
+            f.write("\n")
+
+
+            f.close()
+
+
     # Generic canvas handler
     #
     # We could have used bindtags, as in nodeIcon, but
@@ -319,8 +1914,46 @@ def newNode( self, node, event ):
         "Add a new node to our canvas."
         c = self.canvas
         x, y = c.canvasx( event.x ), c.canvasy( event.y )
-        self.nodeCount += 1
-        name = self.nodePrefixes[ node ] + str( self.nodeCount )
+        name = self.nodePrefixes[ node ]
+        if 'Switch' == node:
+            self.switchCount += 1
+            name = self.nodePrefixes[ node ] + str( self.switchCount )
+            self.switchOpts[name] = {}
+            self.switchOpts[name]['nodeNum']=self.switchCount
+            self.switchOpts[name]['hostname']=name
+            self.switchOpts[name]['switchType']='default'
+            self.switchOpts[name]['controllers']=[]
+        if 'LegacyRouter' == node:
+            self.switchCount += 1
+            name = self.nodePrefixes[ node ] + str( self.switchCount )
+            self.switchOpts[name] = {}
+            self.switchOpts[name]['nodeNum']=self.switchCount
+            self.switchOpts[name]['hostname']=name
+            self.switchOpts[name]['switchType']='legacyRouter'
+        if 'LegacySwitch' == node:
+            self.switchCount += 1
+            name = self.nodePrefixes[ node ] + str( self.switchCount )
+            self.switchOpts[name] = {}
+            self.switchOpts[name]['nodeNum']=self.switchCount
+            self.switchOpts[name]['hostname']=name
+            self.switchOpts[name]['switchType']='legacySwitch'
+            self.switchOpts[name]['controllers']=[]
+        if 'Host' == node:
+            self.hostCount += 1
+            name = self.nodePrefixes[ node ] + str( self.hostCount )
+            self.hostOpts[name] = {'sched':'host'}
+            self.hostOpts[name]['nodeNum']=self.hostCount
+            self.hostOpts[name]['hostname']=name
+        if 'Controller' == node:
+            name = self.nodePrefixes[ node ] + str( self.controllerCount )
+            ctrlr = { 'controllerType': 'ref',
+                      'hostname': name,
+                      'remoteIP': '127.0.0.1',
+                      'remotePort': 6633}
+            self.controllers[name] = ctrlr
+            # We want to start controller count at 0
+            self.controllerCount += 1
+
         icon = self.nodeIcon( node, name )
         item = self.canvas.create_window( x, y, anchor='c', window=icon,
                                           tags=node )
@@ -328,16 +1961,38 @@ def newNode( self, node, event ):
         self.itemToWidget[ item ] = icon
         self.selectItem( item )
         icon.links = {}
+        if 'Switch' == node:
+            icon.bind('<Button-3>', self.do_switchPopup )
+        if 'LegacyRouter' == node:
+            icon.bind('<Button-3>', self.do_legacyRouterPopup )
+        if 'LegacySwitch' == node:
+            icon.bind('<Button-3>', self.do_legacySwitchPopup )
+        if 'Host' == node:
+            icon.bind('<Button-3>', self.do_hostPopup )
+        if 'Controller' == node:
+            icon.bind('<Button-3>', self.do_controllerPopup )
+
+    def clickController( self, event ):
+        "Add a new Controller to our canvas."
+        self.newNode( 'Controller', event )
 
     def clickHost( self, event ):
         "Add a new host to our canvas."
         self.newNode( 'Host', event )
 
+    def clickLegacyRouter( self, event ):
+        "Add a new switch to our canvas."
+        self.newNode( 'LegacyRouter', event )
+
+    def clickLegacySwitch( self, event ):
+        "Add a new switch to our canvas."
+        self.newNode( 'LegacySwitch', event )
+
     def clickSwitch( self, event ):
         "Add a new switch to our canvas."
         self.newNode( 'Switch', event )
 
-    def dragLink( self, event ):
+    def dragNetLink( self, event ):
         "Drag a link's endpoint to another node."
         if self.link is None:
             return
@@ -347,7 +2002,7 @@ def dragLink( self, event ):
         c = self.canvas
         c.coords( self.link, self.linkx, self.linky, x, y )
 
-    def releaseLink( self, _event ):
+    def releaseNetLink( self, _event ):
         "Give up on the current link."
         if self.link is not None:
             self.canvas.delete( self.link )
@@ -362,8 +2017,7 @@ def createNodeBindings( self ):
             '<B1-Motion>': self.dragNode,
             '<ButtonRelease-1>': self.releaseNode,
             '<Enter>': self.enterNode,
-            '<Leave>': self.leaveNode,
-            '<Double-ButtonPress-1>': self.xterm
+            '<Leave>': self.leaveNode
         }
         l = Label()  # lightweight-ish owner for bindings
         for event, binding in bindings.items():
@@ -385,7 +2039,7 @@ def leaveNode( self, _event ):
 
     def clickNode( self, event ):
         "Node click handler."
-        if self.active is 'Link':
+        if self.active is 'NetLink':
             self.startLink( event )
         else:
             self.selectNode( event )
@@ -393,14 +2047,14 @@ def clickNode( self, event ):
 
     def dragNode( self, event ):
         "Node drag handler."
-        if self.active is 'Link':
-            self.dragLink( event )
+        if self.active is 'NetLink':
+            self.dragNetLink( event )
         else:
             self.dragNodeAround( event )
 
     def releaseNode( self, event ):
         "Node release handler."
-        if self.active is 'Link':
+        if self.active is 'NetLink':
             self.finishLink( event )
 
     # Specific node handlers
@@ -427,21 +2081,34 @@ def dragNodeAround( self, event ):
             item = self.widgetToItem[ dest ]
             x1, y1 = c.coords( item )
             c.coords( link, x, y, x1, y1 )
+        self.updateScrollRegion()
 
-    def startLink( self, event ):
-        "Start a new link."
-        if event.widget not in self.widgetToItem:
-            # Didn't click on a node
-            return
-        w = event.widget
-        item = self.widgetToItem[ w ]
-        x, y = self.canvas.coords( item )
-        self.link = self.canvas.create_line( x, y, x, y, width=4,
-                                             fill='blue', tag='link' )
-        self.linkx, self.linky = x, y
-        self.linkWidget = w
-        self.linkItem = item
+    def createControlLinkBindings( self ):
+        "Create a set of bindings for nodes."
+        # Link bindings
+        # Selection still needs a bit of work overall
+        # Callbacks ignore event
+
+        def select( _event, link=self.link ):
+            "Select item on mouse entry."
+            self.selectItem( link )
+
+        def highlight( _event, link=self.link ):
+            "Highlight item on mouse entry."
+            self.selectItem( link )
+            self.canvas.itemconfig( link, fill='green' )
+
+        def unhighlight( _event, link=self.link ):
+            "Unhighlight item on mouse exit."
+            self.canvas.itemconfig( link, fill='red' )
+            #self.selectItem( None )
+
+        self.canvas.tag_bind( self.link, '<Enter>', highlight )
+        self.canvas.tag_bind( self.link, '<Leave>', unhighlight )
+        self.canvas.tag_bind( self.link, '<ButtonPress-1>', select )
 
+    def createDataLinkBindings( self ):
+        "Create a set of bindings for nodes."
         # Link bindings
         # Selection still needs a bit of work overall
         # Callbacks ignore event
@@ -452,17 +2119,35 @@ def select( _event, link=self.link ):
 
         def highlight( _event, link=self.link ):
             "Highlight item on mouse entry."
-            # self.selectItem( link )
+            self.selectItem( link )
             self.canvas.itemconfig( link, fill='green' )
 
         def unhighlight( _event, link=self.link ):
             "Unhighlight item on mouse exit."
             self.canvas.itemconfig( link, fill='blue' )
-            # self.selectItem( None )
+            #self.selectItem( None )
 
         self.canvas.tag_bind( self.link, '<Enter>', highlight )
         self.canvas.tag_bind( self.link, '<Leave>', unhighlight )
         self.canvas.tag_bind( self.link, '<ButtonPress-1>', select )
+        self.canvas.tag_bind( self.link, '<Button-3>', self.do_linkPopup )
+
+
+    def startLink( self, event ):
+        "Start a new link."
+        if event.widget not in self.widgetToItem:
+            # Didn't click on a node
+            return
+
+        w = event.widget
+        item = self.widgetToItem[ w ]
+        x, y = self.canvas.coords( item )
+        self.link = self.canvas.create_line( x, y, x, y, width=4,
+                                             fill='blue', tag='link' )
+        self.linkx, self.linky = x, y
+        self.linkWidget = w
+        self.linkItem = item
+
 
     def finishLink( self, event ):
         "Finish creating a link"
@@ -476,17 +2161,48 @@ def finishLink( self, event ):
         dest = self.itemToWidget.get( target, None )
         if ( source is None or dest is None or source == dest
                 or dest in source.links or source in dest.links ):
-            self.releaseLink( event )
+            self.releaseNetLink( event )
             return
         # For now, don't allow hosts to be directly linked
         stags = self.canvas.gettags( self.widgetToItem[ source ] )
         dtags = self.canvas.gettags( target )
-        if 'Host' in stags and 'Host' in dtags:
-            self.releaseLink( event )
+        if (('Host' in stags and 'Host' in dtags) or
+           ('Controller' in dtags and 'LegacyRouter' in stags) or
+           ('Controller' in stags and 'LegacyRouter' in dtags) or
+           ('Controller' in dtags and 'LegacySwitch' in stags) or
+           ('Controller' in stags and 'LegacySwitch' in dtags) or
+           ('Controller' in dtags and 'Host' in stags) or
+           ('Controller' in stags and 'Host' in dtags) or
+           ('Controller' in stags and 'Controller' in dtags)):
+            self.releaseNetLink( event )
             return
+
+        # Set link type
+        linkType='data'
+        if 'Controller' in stags or 'Controller' in dtags:
+            linkType='control'
+            c.itemconfig(self.link, dash=(6, 4, 2, 4), fill='red')
+            self.createControlLinkBindings()
+        else:
+            linkType='data'
+            self.createDataLinkBindings()
+        c.itemconfig(self.link, tags=c.gettags(self.link)+(linkType,))
+
         x, y = c.coords( target )
         c.coords( self.link, self.linkx, self.linky, x, y )
-        self.addLink( source, dest )
+        self.addLink( source, dest, linktype=linkType )
+        if linkType == 'control':
+            controllerName = ''
+            switchName = ''
+            if 'Controller' in stags:
+                controllerName = source[ 'text' ]
+                switchName = dest[ 'text' ]
+            else:
+                controllerName = dest[ 'text' ]
+                switchName = source[ 'text' ]
+
+            self.switchOpts[switchName]['controllers'].append(controllerName)
+
         # We're done
         self.link = self.linkWidget = None
 
@@ -500,14 +2216,22 @@ def about( self ):
             about = Toplevel( bg='white' )
             about.title( 'About' )
             info = self.appName + ': a simple network editor for MiniNet'
-            warning = 'Development version - not entirely functional!'
-            author = 'Bob Lantz <rlantz@cs>, April 2010'
+            version = 'MiniEdit '+MINIEDIT_VERSION
+            author = 'Originally by: Bob Lantz <rlantz@cs>, April 2010'
+            enhancements = 'Enhancements by: Gregory Gee, Since July 2013'
+            www = 'http://gregorygee.wordpress.com/category/miniedit/'
             line1 = Label( about, text=info, font='Helvetica 10 bold', bg=bg )
-            line2 = Label( about, text=warning, font='Helvetica 9', bg=bg )
+            line2 = Label( about, text=version, font='Helvetica 9', bg=bg )
             line3 = Label( about, text=author, font='Helvetica 9', bg=bg )
+            line4 = Label( about, text=enhancements, font='Helvetica 9', bg=bg )
+            line5 = Entry( about, font='Helvetica 9', bg=bg, width=len(www), justify=CENTER )
+            line5.insert(0, www)
+            line5.configure(state='readonly')
             line1.pack( padx=20, pady=10 )
             line2.pack(pady=10 )
             line3.pack(pady=10 )
+            line4.pack(pady=10 )
+            line5.pack(pady=10 )
             hide = ( lambda about=about: about.withdraw() )
             self.aboutBox = about
             # Hide on close rather than destroying window
@@ -518,73 +2242,542 @@ def about( self ):
     def createToolImages( self ):
         "Create toolbar (and icon) images."
 
+    def checkIntf( self, intf ):
+        "Make sure intf exists and is not configured."
+        if ( ' %s:' % intf ) not in quietRun( 'ip link show' ):
+            showerror(title="Error",
+                      message='External interface ' +intf + ' does not exist! Skipping.')
+            return False
+        ips = re.findall( r'\d+\.\d+\.\d+\.\d+', quietRun( 'ifconfig ' + intf ) )
+        if ips:
+            showerror(title="Error",
+                      message= intf + ' has an IP address and is probably in use! Skipping.' )
+            return False
+        return True
+
+    def hostDetails( self, _ignore=None ):
+        if ( self.selection is None or
+             self.net is not None or
+             self.selection not in self.itemToWidget ):
+            return
+        widget = self.itemToWidget[ self.selection ]
+        name = widget[ 'text' ]
+        tags = self.canvas.gettags( self.selection )
+        if 'Host' not in tags:
+            return
+
+        prefDefaults = self.hostOpts[name]
+        hostBox = HostDialog(self, title='Host Details', prefDefaults=prefDefaults)
+        self.master.wait_window(hostBox.top)
+        if hostBox.result:
+            newHostOpts = {'nodeNum':self.hostOpts[name]['nodeNum']}
+            newHostOpts['sched'] = hostBox.result['sched']
+            if len(hostBox.result['cpu']) > 0:
+                newHostOpts['cpu'] = float(hostBox.result['cpu'])
+            if len(hostBox.result['cores']) > 0:
+                newHostOpts['cores'] = hostBox.result['cores']
+            if len(hostBox.result['hostname']) > 0:
+                newHostOpts['hostname'] = hostBox.result['hostname']
+                name = hostBox.result['hostname']
+                widget[ 'text' ] = name
+            if len(hostBox.result['defaultRoute']) > 0:
+                newHostOpts['defaultRoute'] = hostBox.result['defaultRoute']
+            if len(hostBox.result['ip']) > 0:
+                newHostOpts['ip'] = hostBox.result['ip']
+            if len(hostBox.result['externalInterfaces']) > 0:
+                newHostOpts['externalInterfaces'] = hostBox.result['externalInterfaces']
+            if len(hostBox.result['vlanInterfaces']) > 0:
+                newHostOpts['vlanInterfaces'] = hostBox.result['vlanInterfaces']
+            self.hostOpts[name] = newHostOpts
+            print 'New host details for ' + name + ' = ' + str(newHostOpts)
+
+    def switchDetails( self, _ignore=None ):
+        if ( self.selection is None or
+             self.net is not None or
+             self.selection not in self.itemToWidget ):
+            return
+        widget = self.itemToWidget[ self.selection ]
+        name = widget[ 'text' ]
+        tags = self.canvas.gettags( self.selection )
+        if 'Switch' not in tags:
+            return
+
+        prefDefaults = self.switchOpts[name]
+        switchBox = SwitchDialog(self, title='Switch Details', prefDefaults=prefDefaults)
+        self.master.wait_window(switchBox.top)
+        if switchBox.result:
+            newSwitchOpts = {'nodeNum':self.switchOpts[name]['nodeNum']}
+            newSwitchOpts['switchType'] = switchBox.result['switchType']
+            newSwitchOpts['controllers'] = self.switchOpts[name]['controllers']
+            if len(switchBox.result['dpctl']) > 0:
+                newSwitchOpts['dpctl'] = switchBox.result['dpctl']
+            if len(switchBox.result['dpid']) > 0:
+                newSwitchOpts['dpid'] = switchBox.result['dpid']
+            if len(switchBox.result['hostname']) > 0:
+                newSwitchOpts['hostname'] = switchBox.result['hostname']
+                name = switchBox.result['hostname']
+                widget[ 'text' ] = name
+            if len(switchBox.result['externalInterfaces']) > 0:
+                newSwitchOpts['externalInterfaces'] = switchBox.result['externalInterfaces']
+            newSwitchOpts['switchIP'] = switchBox.result['switchIP']
+            newSwitchOpts['sflow'] = switchBox.result['sflow']
+            newSwitchOpts['netflow'] = switchBox.result['netflow']
+            self.switchOpts[name] = newSwitchOpts
+            print 'New switch details for ' + name + ' = ' + str(newSwitchOpts)
+
+    def linkUp( self ):
+        if ( self.selection is None or
+             self.net is None):
+            return
+        link = self.selection
+        linkDetail =  self.links[link]
+        src = linkDetail['src']
+        dst = linkDetail['dest']
+        srcName, dstName = src[ 'text' ], dst[ 'text' ]
+        self.net.configLinkStatus(srcName, dstName, 'up')
+        self.canvas.itemconfig(link, dash=())
+
+    def linkDown( self ):
+        if ( self.selection is None or
+             self.net is None):
+            return
+        link = self.selection
+        linkDetail =  self.links[link]
+        src = linkDetail['src']
+        dst = linkDetail['dest']
+        srcName, dstName = src[ 'text' ], dst[ 'text' ]
+        self.net.configLinkStatus(srcName, dstName, 'down')
+        self.canvas.itemconfig(link, dash=(4, 4))
+
+    def linkDetails( self, _ignore=None ):
+        if ( self.selection is None or
+             self.net is not None):
+            return
+        link = self.selection
+
+        linkDetail =  self.links[link]
+        src = linkDetail['src']
+        dest = linkDetail['dest']
+        linkopts = linkDetail['linkOpts']
+        linkBox = LinkDialog(self, title='Link Details', linkDefaults=linkopts)
+        if linkBox.result is not None:
+            linkDetail['linkOpts'] = linkBox.result
+            print 'New link details = ' + str(linkBox.result)
+
+    def prefDetails( self ):
+        prefDefaults = self.appPrefs
+        prefBox = PrefsDialog(self, title='Preferences', prefDefaults=prefDefaults)
+        print 'New Prefs = ' + str(prefBox.result)
+        if prefBox.result:
+            self.appPrefs = prefBox.result
+
+
+    def controllerDetails( self ):
+        if ( self.selection is None or
+             self.net is not None or
+             self.selection not in self.itemToWidget ):
+            return
+        widget = self.itemToWidget[ self.selection ]
+        name = widget[ 'text' ]
+        tags = self.canvas.gettags( self.selection )
+        oldName = name
+        if 'Controller' not in tags:
+            return
+
+        ctrlrBox = ControllerDialog(self, title='Controller Details', ctrlrDefaults=self.controllers[name])
+        if ctrlrBox.result:
+            #print 'Controller is ' + ctrlrBox.result[0]
+            if len(ctrlrBox.result['hostname']) > 0:
+                name = ctrlrBox.result['hostname']
+                widget[ 'text' ] = name
+            else:
+                ctrlrBox.result['hostname'] = name
+            self.controllers[name] = ctrlrBox.result
+            print 'New controller details for ' + name + ' = ' + str(self.controllers[name])
+            # Find references to controller and change name
+            if oldName != name:
+                for widget in self.widgetToItem:
+                    switchName = widget[ 'text' ]
+                    tags = self.canvas.gettags( self.widgetToItem[ widget ] )
+                    if 'Switch' in tags:
+                        switch = self.switchOpts[switchName]
+                        if oldName in switch['controllers']:
+                            switch['controllers'].remove(oldName)
+                            switch['controllers'].append(name)
+
+
+    def listBridge( self, _ignore=None ):
+        if ( self.selection is None or
+             self.net is None or
+             self.selection not in self.itemToWidget ):
+            return
+        name = self.itemToWidget[ self.selection ][ 'text' ]
+        tags = self.canvas.gettags( self.selection )
+
+        if name not in self.net.nameToNode:
+            return
+        if 'Switch' in tags or 'LegacySwitch' in tags:
+           call(["xterm -T 'Bridge Details' -sb -sl 2000 -e 'ovs-vsctl list bridge " + name + "; read -p \"Press Enter to close\"' &"], shell=True)
+
+    def ovsShow( self, _ignore=None ):
+        call(["xterm -T 'OVS Summary' -sb -sl 2000 -e 'ovs-vsctl show; read -p \"Press Enter to close\"' &"], shell=True)
+
+    def rootTerminal( self, _ignore=None ):
+        call(["xterm -T 'Root Terminal' -sb -sl 2000 &"], shell=True)
+
     # Model interface
     #
     # Ultimately we will either want to use a topo or
     # mininet object here, probably.
 
-    def addLink( self, source, dest ):
+    def addLink( self, source, dest, linktype='data', linkopts={} ):
         "Add link to model."
         source.links[ dest ] = self.link
         dest.links[ source ] = self.link
-        self.links[ self.link ] = ( source, dest )
+        self.links[ self.link ] = {'type' :linktype,
+                                   'src':source,
+                                   'dest':dest,
+                                   'linkOpts':linkopts}
 
     def deleteLink( self, link ):
         "Delete link from model."
         pair = self.links.get( link, None )
         if pair is not None:
-            source, dest = pair
+            source=pair['src']
+            dest=pair['dest']
             del source.links[ dest ]
             del dest.links[ source ]
+            stags = self.canvas.gettags( self.widgetToItem[ source ] )
+            dtags = self.canvas.gettags( self.widgetToItem[ dest ] )
+            ltags = self.canvas.gettags( link )
+
+            if 'control' in ltags:
+                controllerName = ''
+                switchName = ''
+                if 'Controller' in stags:
+                    controllerName = source[ 'text' ]
+                    switchName = dest[ 'text' ]
+                else:
+                    controllerName = dest[ 'text' ]
+                    switchName = source[ 'text' ]
+    
+                if controllerName in self.switchOpts[switchName]['controllers']:
+                    self.switchOpts[switchName]['controllers'].remove(controllerName)
+
+
         if link is not None:
             del self.links[ link ]
 
     def deleteNode( self, item ):
         "Delete node (and its links) from model."
+
         widget = self.itemToWidget[ item ]
+        tags = self.canvas.gettags(item)
+        if 'Controller' in tags:
+            # remove from switch controller lists
+            for serachwidget in self.widgetToItem:
+                name = serachwidget[ 'text' ]
+                tags = self.canvas.gettags( self.widgetToItem[ serachwidget ] )
+                if 'Switch' in tags:
+                    if widget['text'] in self.switchOpts[name]['controllers']:
+                        self.switchOpts[name]['controllers'].remove(widget['text'])
+            
         for link in widget.links.values():
             # Delete from view and model
             self.deleteItem( link )
         del self.itemToWidget[ item ]
         del self.widgetToItem[ widget ]
 
-    def build( self ):
-        "Build network based on our topology."
-
-        net = Mininet( topo=None )
-
-        # Make controller
-        net.addController( 'c0' )
+    def buildNodes( self, net):
         # Make nodes
+        print "Getting Hosts and Switches."
         for widget in self.widgetToItem:
             name = widget[ 'text' ]
             tags = self.canvas.gettags( self.widgetToItem[ widget ] )
-            nodeNum = int( name[ 1: ] )
+            #print name+' has '+str(tags)
+
             if 'Switch' in tags:
-                net.addSwitch( name )
+                opts = self.switchOpts[name]
+
+                # Create the correct switch class
+                switchClass = customOvs
+                switchParms={}
+                if 'dpctl' in opts:
+                    switchParms['listenPort']=int(opts['dpctl'])
+                if 'dpid' in opts:
+                    switchParms['dpid']=opts['dpid']
+                if opts['switchType'] == 'default':
+                    if self.appPrefs['switchType'] == 'ivs':
+                        switchClass = IVSSwitch
+                    elif self.appPrefs['switchType'] == 'user':
+                        switchClass = CustomUserSwitch
+                    elif self.appPrefs['switchType'] == 'userns':
+                        switchParms['inNamespace'] = True
+                        switchClass = CustomUserSwitch
+                    else:
+                        switchClass = customOvs
+                elif opts['switchType'] == 'user':
+                    switchClass = CustomUserSwitch
+                elif opts['switchType'] == 'userns':
+                    switchClass = CustomUserSwitch
+                    switchParms['inNamespace'] = True
+                elif opts['switchType'] == 'ivs':
+                    switchClass = IVSSwitch
+                else:
+                    switchClass = customOvs
+                newSwitch = net.addSwitch( name , cls=switchClass, **switchParms)
+                if switchClass == CustomUserSwitch:
+                    if ('switchIP' in opts):
+                        if (len(opts['switchIP']) > 0):
+                            newSwitch.setSwitchIP(opts['switchIP'])
+                if switchClass == customOvs:
+                    newSwitch.setOpenFlowVersion(self.appPrefs['openFlowVersions'])
+                    if ('switchIP' in opts):
+                        if (len(opts['switchIP']) > 0):
+                            newSwitch.setSwitchIP(opts['switchIP'])
+
+                # Attach external interfaces
+                if ('externalInterfaces' in opts):
+                    for extInterface in opts['externalInterfaces']:
+                        if self.checkIntf(extInterface):
+                           Intf( extInterface, node=newSwitch )
+
+            elif 'LegacySwitch' in tags:
+                newSwitch = net.addSwitch( name , cls=LegacySwitch)
+            elif 'LegacyRouter' in tags:
+                newSwitch = net.addHost( name , cls=LegacyRouter)
             elif 'Host' in tags:
-                #Generate IP adddress in the 10.0/8 block
-                ipAddr = ( 10 << 24 ) + nodeNum
-                net.addHost( name, ip=ipStr( ipAddr ) )
+                opts = self.hostOpts[name]
+                ip = None
+                defaultRoute = None
+                if 'defaultRoute' in opts and len(opts['defaultRoute']) > 0:
+                    defaultRoute = 'via '+opts['defaultRoute']
+                if 'ip' in opts and len(opts['ip']) > 0:
+                    ip = opts['ip']
+                else:
+                    nodeNum = self.hostOpts[name]['nodeNum']
+                    ipBaseNum, prefixLen = netParse( self.appPrefs['ipBase'] )
+                    ip = ipAdd(i=nodeNum, prefixLen=prefixLen, ipBaseNum=ipBaseNum)
+
+                # Create the correct host class
+                hostCls = Host
+                if 'cores' in opts or 'cpu' in opts:
+                    hostCls=CPULimitedHost
+                newHost = net.addHost( name,
+                                       cls=hostCls,
+                                       ip=ip,
+                                       defaultRoute=defaultRoute
+                                      )
+
+                # Set the CPULimitedHost specific options
+                if 'cores' in opts:
+                    newHost.setCPUs(cores = opts['cores'])
+                if 'cpu' in opts:
+                    newHost.setCPUFrac(f=opts['cpu'], sched=opts['sched'])
+
+                # Attach external interfaces
+                if ('externalInterfaces' in opts):
+                    for extInterface in opts['externalInterfaces']:
+                        if self.checkIntf(extInterface):
+                           Intf( extInterface, node=newHost )
+                if ('vlanInterfaces' in opts):
+                    if len(opts['vlanInterfaces']) > 0:
+                        print 'Checking that OS is VLAN prepared'
+                        self.pathCheck('vconfig', moduleName='vlan package')
+                        moduleDeps( add='8021q' )
+            elif 'Controller' in tags:
+                opts = self.controllers[name]
+
+                # Get controller info from panel
+                controllerType = opts['controllerType']
+
+                # Make controller
+                print 'Getting controller selection:'+controllerType
+                controllerIP = opts['remoteIP']
+                controllerPort = opts['remotePort']
+                if controllerType == 'remote':
+                    net.addController(name=name,
+                                      controller=RemoteController,
+                                      ip=controllerIP,
+                                      port=controllerPort)
+                elif controllerType == 'inband':
+                    net.addController(name=name,
+                                      controller=InbandController,
+                                      ip=controllerIP,
+                                      port=controllerPort)
+                elif controllerType == 'ovsc':
+                    net.addController(name=name,
+                                      controller=OVSController,
+                                      port=controllerPort)
+                else:
+                    net.addController(name=name,
+                                      controller=Controller,
+                                      port=controllerPort)
+
             else:
                 raise Exception( "Cannot create mystery node: " + name )
+
+    def pathCheck( self, *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 ):
+                showerror(title="Error",
+                      message= 'Cannot find required executable %s.\n' % arg +
+                       'Please make sure that %s is installed ' % moduleName +
+                       'and available in your $PATH.' )
+
+    def buildLinks( self, net):
         # Make links
-        for link in self.links.values():
-            ( src, dst ) = link
-            srcName, dstName = src[ 'text' ], dst[ 'text' ]
-            src, dst = net.nameToNode[ srcName ], net.nameToNode[ dstName ]
-            src.linkTo( dst )
+        print "Getting Links."
+        for key,link in self.links.iteritems():
+            tags = self.canvas.gettags(key)
+            if 'data' in tags:
+                src=link['src']
+                dst=link['dest']
+                linkopts=link['linkOpts']
+                srcName, dstName = src[ 'text' ], dst[ 'text' ]
+                src, dst = net.nameToNode[ srcName ], net.nameToNode[ dstName ]
+                if linkopts:
+                    net.addLink(src, dst, cls=TCLink, **linkopts)
+                else:
+                    net.addLink(src, dst)
+                self.canvas.itemconfig(key, dash=())
+
+
+    def build( self ):
+        print "Build network based on our topology."
+
+        dpctl = None
+        if len(self.appPrefs['dpctl']) > 0:
+            dpctl = int(self.appPrefs['dpctl'])
+        net = Mininet( topo=None,
+                       listenPort=dpctl,
+                       build=False,
+                       ipBase=self.appPrefs['ipBase'] )
+
+        self.buildNodes(net)
+        self.buildLinks(net)
 
         # Build network (we have to do this separately at the moment )
         net.build()
 
         return net
 
+
+    def postStartSetup( self ):
+
+        # Setup host VLAN subinterfaces
+        for widget in self.widgetToItem:
+            name = widget[ 'text' ]
+            tags = self.canvas.gettags( self.widgetToItem[ widget ] )
+            if 'Host' in tags:
+                opts = self.hostOpts[name]
+                # Attach vlan interfaces
+                if ('vlanInterfaces' in opts):
+                    for vlanInterface in opts['vlanInterfaces']:
+                        print 'adding vlan interface '+vlanInterface[1]
+                        newHost = self.net.get(name)
+                        newHost.cmdPrint('vconfig add '+name+'-eth0 '+vlanInterface[1])
+                        newHost.cmdPrint('ifconfig '+name+'-eth0.'+vlanInterface[1]+' '+vlanInterface[0])
+
+        # Configure NetFlow
+        nflowValues = self.appPrefs['netflow']
+        if len(nflowValues['nflowTarget']) > 0:
+            nflowEnabled = False
+            nflowSwitches = ''
+            for widget in self.widgetToItem:
+                name = widget[ 'text' ]
+                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
+    
+                if 'Switch' in tags:
+                    opts = self.switchOpts[name]
+                    if 'netflow' in opts:
+                        if opts['netflow'] == '1':
+                            print name+' has Netflow enabled'
+                            nflowSwitches = nflowSwitches+' -- set Bridge '+name+' netflow=@MiniEditNF'
+                            nflowEnabled=True
+            if nflowEnabled:
+                nflowCmd = 'ovs-vsctl -- --id=@MiniEditNF create NetFlow '+ 'target=\\\"'+nflowValues['nflowTarget']+'\\\" '+ 'active-timeout='+nflowValues['nflowTimeout']
+                if nflowValues['nflowAddId'] == '1':
+                    nflowCmd = nflowCmd + ' add_id_to_interface=true'
+                else:
+                    nflowCmd = nflowCmd + ' add_id_to_interface=false'
+                print 'cmd = '+nflowCmd+nflowSwitches
+                call(nflowCmd+nflowSwitches, shell=True)
+
+            else:
+                print 'No switches with Netflow'
+        else:
+            print 'No NetFlow targets specified.'
+
+        # Configure sFlow
+        sflowValues = self.appPrefs['sflow']
+        if len(sflowValues['sflowTarget']) > 0:
+            sflowEnabled = False
+            sflowSwitches = ''
+            for widget in self.widgetToItem:
+                name = widget[ 'text' ]
+                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
+    
+                if 'Switch' in tags:
+                    opts = self.switchOpts[name]
+                    if 'sflow' in opts:
+                        if opts['sflow'] == '1':
+                            print name+' has sflow enabled'
+                            sflowSwitches = sflowSwitches+' -- set Bridge '+name+' sflow=@MiniEditSF'
+                            sflowEnabled=True
+            if sflowEnabled:
+                sflowCmd = 'ovs-vsctl -- --id=@MiniEditSF create sFlow '+ 'target=\\\"'+sflowValues['sflowTarget']+'\\\" '+ 'header='+sflowValues['sflowHeader']+' '+ 'sampling='+sflowValues['sflowSampling']+' '+ 'polling='+sflowValues['sflowPolling']
+                print 'cmd = '+sflowCmd+sflowSwitches
+                call(sflowCmd+sflowSwitches, shell=True)
+
+            else:
+                print 'No switches with sflow'
+        else:
+            print 'No sFlow targets specified.'
+
+        ## NOTE: MAKE SURE THIS IS LAST THING CALLED
+        # Start the CLI if enabled
+        if self.appPrefs['startCLI'] == '1':
+            info( "\n\n NOTE: PLEASE REMEMBER TO EXIT THE CLI BEFORE YOU PRESS THE STOP BUTTON. Not exiting will prevent MiniEdit from quitting and will prevent you from starting the network again during this sessoin.\n\n")
+            CLI(self.net)
+
     def start( self ):
         "Start network."
         if self.net is None:
             self.net = self.build()
-            self.net.start()
+
+            # Since I am going to inject per switch controllers.
+            # I can't call net.start().  I have to replicate what it
+            # does and add the controller options.
+            #self.net.start()
+            info( '**** Starting %s controllers\n' % len( self.net.controllers ) )
+            for controller in self.net.controllers:
+                info( str(controller) + ' ')
+                controller.start()
+            info('\n')
+            info( '**** Starting %s switches\n' % len( self.net.switches ) )
+            #for switch in self.net.switches:
+            #    info( switch.name + ' ')
+            #    switch.start( self.net.controllers )
+            for widget in self.widgetToItem:
+                name = widget[ 'text' ]
+                tags = self.canvas.gettags( self.widgetToItem[ widget ] )
+                if 'Switch' in tags:
+                    opts = self.switchOpts[name]
+                    switchControllers = []
+                    for ctrl in opts['controllers']:
+                        switchControllers.append(self.net.get(ctrl))
+                    info( name + ' ')
+                    # Figure out what controllers will manage this switch
+                    self.net.get(name).start( switchControllers )
+                if 'LegacySwitch' in tags:
+                    self.net.get(name).start( [] )
+                    info( name + ' ')
+            info('\n')
+
+            self.postStartSetup()
 
     def stop( self ):
         "Stop network."
@@ -593,6 +2786,78 @@ def stop( self ):
         cleanUpScreens()
         self.net = None
 
+    def do_linkPopup(self, event):
+        # display the popup menu
+        if ( self.net is None ):
+            try:
+                self.linkPopup.tk_popup(event.x_root, event.y_root, 0)
+            finally:
+                # make sure to release the grab (Tk 8.0a1 only)
+                self.linkPopup.grab_release()
+        else:
+            try:
+                self.linkRunPopup.tk_popup(event.x_root, event.y_root, 0)
+            finally:
+                # make sure to release the grab (Tk 8.0a1 only)
+                self.linkRunPopup.grab_release()
+
+    def do_controllerPopup(self, event):
+        # display the popup menu
+        if ( self.net is None ):
+            try:
+                self.controllerPopup.tk_popup(event.x_root, event.y_root, 0)
+            finally:
+                # make sure to release the grab (Tk 8.0a1 only)
+                self.controllerPopup.grab_release()
+
+    def do_legacyRouterPopup(self, event):
+        # display the popup menu
+        if ( self.net is not None ):
+            try:
+                self.legacyRouterRunPopup.tk_popup(event.x_root, event.y_root, 0)
+            finally:
+                # make sure to release the grab (Tk 8.0a1 only)
+                self.legacyRouterRunPopup.grab_release()
+
+    def do_hostPopup(self, event):
+        # display the popup menu
+        if ( self.net is None ):
+            try:
+                self.hostPopup.tk_popup(event.x_root, event.y_root, 0)
+            finally:
+                # make sure to release the grab (Tk 8.0a1 only)
+                self.hostPopup.grab_release()
+        else:
+            try:
+                self.hostRunPopup.tk_popup(event.x_root, event.y_root, 0)
+            finally:
+                # make sure to release the grab (Tk 8.0a1 only)
+                self.hostRunPopup.grab_release()
+
+    def do_legacySwitchPopup(self, event):
+        # display the popup menu
+        if ( self.net is not None ):
+            try:
+                self.switchRunPopup.tk_popup(event.x_root, event.y_root, 0)
+            finally:
+                # make sure to release the grab (Tk 8.0a1 only)
+                self.switchRunPopup.grab_release()
+
+    def do_switchPopup(self, event):
+        # display the popup menu
+        if ( self.net is None ):
+            try:
+                self.switchPopup.tk_popup(event.x_root, event.y_root, 0)
+            finally:
+                # make sure to release the grab (Tk 8.0a1 only)
+                self.switchPopup.grab_release()
+        else:
+            try:
+                self.switchRunPopup.tk_popup(event.x_root, event.y_root, 0)
+            finally:
+                # make sure to release the grab (Tk 8.0a1 only)
+                self.switchRunPopup.grab_release()
+
     def xterm( self, _ignore=None ):
         "Make an xterm when a button is pressed."
         if ( self.selection is None or
@@ -602,8 +2867,193 @@ def xterm( self, _ignore=None ):
         name = self.itemToWidget[ self.selection ][ 'text' ]
         if name not in self.net.nameToNode:
             return
-        term = makeTerm( self.net.nameToNode[ name ], 'Host' )
-        self.net.terms += term
+        term = makeTerm( self.net.nameToNode[ name ], 'Host', term=self.appPrefs['terminalType'] )
+        if StrictVersion(MININET_VERSION) > StrictVersion('2.0'):
+            self.net.terms += term
+        else:
+            self.net.terms.append(term)
+
+    def iperf( self, _ignore=None ):
+        "Make an xterm when a button is pressed."
+        if ( self.selection is None or
+             self.net is None or
+             self.selection not in self.itemToWidget ):
+            return
+        name = self.itemToWidget[ self.selection ][ 'text' ]
+        if name not in self.net.nameToNode:
+            return
+        self.net.nameToNode[ name ].cmd( 'iperf -s -p 5001 &' )
+
+    """ BELOW HERE IS THE TOPOLOGY IMPORT CODE """
+
+    def parseArgs( self ):
+        """Parse command-line args and return options object.
+           returns: opts parse options dict"""
+
+        if '--custom' in sys.argv:
+            index = sys.argv.index( '--custom' )
+            if len( sys.argv ) > index + 1:
+                filename = sys.argv[ index + 1 ]
+                self.parseCustomFile( filename )
+            else:
+                raise Exception( 'Custom file name not found' )
+
+        desc = ( "The %prog utility creates Mininet network from the\n"
+                 "command line. It can create parametrized topologies,\n"
+                 "invoke the Mininet CLI, and run tests." )
+
+        usage = ( '%prog [options]\n'
+                  '(type %prog -h for details)' )
+
+        opts = OptionParser( description=desc, usage=usage )
+
+        addDictOption( opts, TOPOS, TOPODEF, 'topo' )
+        opts.add_option( '--custom', type='string', default=None,
+                         help='read custom topo and node params from .py' +
+                         'file' )
+
+        self.options, self.args = opts.parse_args()
+        # We don't accept extra arguments after the options
+        if self.args:
+            opts.print_help()
+            exit()
+
+    def setCustom( self, name, value ):
+        "Set custom parameters for MininetRunner."
+        if name in ( 'topos', 'switches', 'hosts', 'controllers' ):
+            # Update dictionaries
+            param = name.upper()
+            globals()[ param ].update( value )
+        elif name == 'validate':
+            # Add custom validate function
+            self.validate = value
+        else:
+            # Add or modify global variable or class
+            globals()[ name ] = value
+
+    def parseCustomFile( self, fileName ):
+        "Parse custom file and add params before parsing cmd-line options."
+        customs = {}
+        if os.path.isfile( fileName ):
+            execfile( fileName, customs, customs )
+            for name, val in customs.iteritems():
+                self.setCustom( name, val )
+        else:
+            raise Exception( 'could not find custom file: %s' % fileName )
+
+    def importTopo( self ):
+        print 'topo='+self.options.topo
+        if self.options.topo == 'none':
+            return
+        self.newTopology()
+        topo = buildTopo( TOPOS, self.options.topo )
+        importNet = Mininet(topo=topo, build=False)
+        importNet.build()
+
+        c = self.canvas
+        rowIncrement = 100
+        currentY = 100
+
+        # Add Controllers
+        print 'controllers:'+str(len(importNet.controllers))
+        for controller in importNet.controllers:
+            name = controller.name
+            x = self.controllerCount*100+100
+            self.addNode('Controller', self.controllerCount,
+                 float(x), float(currentY), name=name)
+            icon = self.findWidgetByName(name)
+            icon.bind('<Button-3>', self.do_controllerPopup )
+            ctrlr = { 'controllerType': 'ref',
+                      'hostname': name,
+                      'remoteIP': controller.ip,
+                      'remotePort': controller.port}
+            self.controllers[name] = ctrlr
+
+
+
+        currentY = currentY + rowIncrement
+
+        # Add switches
+        print 'switches:'+str(len(importNet.switches))
+        columnCount = 0
+        for switch in importNet.switches:
+            name = switch.name
+            self.switchOpts[name] = {}
+            self.switchOpts[name]['nodeNum']=self.switchCount
+            self.switchOpts[name]['hostname']=name
+            self.switchOpts[name]['switchType']='default'
+            self.switchOpts[name]['controllers']=[]
+
+            x = columnCount*100+100
+            self.addNode('Switch', self.switchCount,
+                 float(x), float(currentY), name=name)
+            icon = self.findWidgetByName(name)
+            icon.bind('<Button-3>', self.do_switchPopup )
+            # Now link to controllers
+            for controller in importNet.controllers:
+                self.switchOpts[name]['controllers'].append(controller.name)
+                dest = self.findWidgetByName(controller.name)
+                dx, dy = c.coords( self.widgetToItem[ dest ] )
+                self.link = c.create_line(float(x),
+                                          float(currentY),
+                                          dx,
+                                          dy,
+                                          width=4,
+                                          fill='red',
+                                          dash=(6, 4, 2, 4),
+                                          tag='link' )
+                c.itemconfig(self.link, tags=c.gettags(self.link)+('control',))
+                self.addLink( icon, dest, linktype='control' )
+                self.createControlLinkBindings()
+                self.link = self.linkWidget = None
+            if columnCount == 9:
+                columnCount = 0
+                currentY = currentY + rowIncrement
+            else:
+                columnCount =columnCount+1
+
+
+        currentY = currentY + rowIncrement
+        # Add hosts
+        print 'hosts:'+str(len(importNet.hosts))
+        columnCount = 0
+        for host in importNet.hosts:
+            name = host.name
+            self.hostOpts[name] = {'sched':'host'}
+            self.hostOpts[name]['nodeNum']=self.hostCount
+            self.hostOpts[name]['hostname']=name
+            self.hostOpts[name]['ip']=host.IP()
+
+            x = columnCount*100+100
+            self.addNode('Host', self.hostCount,
+                 float(x), float(currentY), name=name)
+            icon = self.findWidgetByName(name)
+            icon.bind('<Button-3>', self.do_hostPopup )
+            if columnCount == 9:
+                columnCount = 0
+                currentY = currentY + rowIncrement
+            else:
+                columnCount =columnCount+1
+
+        print 'links:'+str(len(topo.links()))
+        #[('h1', 's3'), ('h2', 's4'), ('s3', 's4')]
+        for link in topo.links():
+            srcNode = link[0]
+            src = self.findWidgetByName(srcNode)
+            sx, sy = self.canvas.coords( self.widgetToItem[ src ] )
+
+            destNode = link[1]
+            dest = self.findWidgetByName(destNode)
+            dx, dy = self.canvas.coords( self.widgetToItem[ dest]  )
+
+            self.link = self.canvas.create_line( sx, sy, dx, dy, width=4,
+                                             fill='blue', tag='link' )
+            c.itemconfig(self.link, tags=c.gettags(self.link)+('data',))
+            self.addLink( src, dest )
+            self.createDataLinkBindings()
+            self.link = self.linkWidget = None
+
+        importNet.stop()
 
 def miniEditImages():
     "Create and return images for MiniEdit."
@@ -616,6 +3066,133 @@ def miniEditImages():
         'Select': BitmapImage(
             file='/usr/include/X11/bitmaps/left_ptr' ),
 
+        'Switch': PhotoImage( data=r"""
+R0lGODlhLgAgAPcAAB2ZxGq61imex4zH3RWWwmK41tzd3vn9/jCiyfX7/Q6SwFay0gBlmtnZ2snJ
+yr+2tAuMu6rY6D6kyfHx8XO/2Uqszjmly6DU5uXz+JLN4uz3+kSrzlKx0ZeZm2K21BuYw67a6QB9
+r+Xl5rW2uHW61On1+UGpzbrf6xiXwny9166vsMLCwgBdlAmHt8TFxgBwpNTs9C2hyO7t7ZnR5L/B
+w0yv0NXV1gBimKGjpABtoQBuoqKkpiaUvqWmqHbB2/j4+Pf39729vgB/sN7w9obH3hSMugCAsonJ
+4M/q8wBglgB6rCCaxLO0tX7C2wBqniGMuABzpuPl5f3+/v39/fr6+r7i7vP6/ABonV621LLc6zWk
+yrq6uq6wskGlyUaszp6gohmYw8HDxKaoqn3E3LGztWGuzcnLzKmrrOnp6gB1qCaex1q001ewz+Dg
+4QB3qrCxstHS09LR0dHR0s7Oz8zNzsfIyQaJuQB0pozL4YzI3re4uAGFtYDG3hOUwb+/wQB5rOvr
+6wB2qdju9TWfxgBpniOcxeLj48vn8dvc3VKuzwB2qp6fos/Q0aXV6D+jxwB7rsXHyLu8vb27vCSc
+xSGZwxyZxH3A2RuUv0+uzz+ozCedxgCDtABnnABroKutr/7+/n2/2LTd6wBvo9bX2OLo6lGv0C6d
+xS6avjmmzLTR2uzr6m651RuXw4jF3CqfxySaxSadyAuRv9bd4cPExRiMuDKjyUWevNPS0sXl8BeY
+xKytr8G/wABypXvC23vD3O73+3vE3cvU2PH5+7S1t7q7vCGVwO/v8JfM3zymyyyZwrWys+Hy90Ki
+xK6qqg+TwBKXxMvMzaWtsK7U4jemzLXEygBxpW++2aCho97Z18bP0/T09fX29vb19ViuzdDR0crf
+51qz01y00ujo6Onq6hCDs2Gpw3i71CqWv3S71nO92M/h52m207bJ0AN6rPPz9Nrh5Nvo7K/b6oTI
+37Td7ABqneHi4yScxo/M4RiWwRqVwcro8n3B2lGoylStzszMzAAAACH5BAEAAP8ALAAAAAAuACAA
+Bwj/AP8JHEjw3wEkEY74WOjrQhUNBSNKnCjRSoYKCOwJcKWpEAACBFBRGEKxZMkDjRAg2OBlQyYL
+WhDEcOWxDwofv0zqHIhhDYIFC2p4MYFMS62ZaiYVWlJJAYIqO00KMlEjABYOQokaRbp0CYBKffpE
+iDpxSKYC1gqswToUmYVaCFyp6QrgwwcCscaSJZhgQYBeAdRyqFBhgwWkGyct8WoXRZ8Ph/YOxMOB
+CIUAHsBxwGQBAII1YwpMI5Brcd0PKFA4Q2ZFMgYteZqkwxyu1KQNJzQc+CdFCrxypyqdRoEPX6x7
+ki/n2TfbAxtNRHYTVCWpWTRbuRoX7yMgZ9QSFQa0/7LU/BXygjIWXVOBTR2sxp7BxGpENgKbY+PR
+reqyIOKnOh0M445AjTjDCgrPSBNFKt9w8wMVU5g0Bg8kDAAKOutQAkNEQNBwDRAEeVEcAV6w84Ay
+KowQSRhmzNGAASIAYow2IP6DySPk8ANKCv1wINE2cpjxCUEgOIOPAKicQMMbKnhyhhg97HDNF4vs
+IEYkNkzwjwSP/PHIE2VIgIdEnxjAiBwNGIKGDKS8I0sw2VAzApNOQimGLlyMAIkDw2yhZTF/KKGE
+lxCEMtEPBtDhACQurLDCLkFIsoUeZLyRpx8OmEGHN3AEcU0HkFAhUDFulDroJvOU5M44iDjgDTQO
+1P/hzRw2IFJPGw3AAY0LI/SAwxc7jEKQI2mkEUipRoxp0g821AMIGlG0McockMzihx5c1LkDDmSg
+UVAiafACRbGPVKDTFG3MYUYdLoThRxDE6DEMGUww8eQONGwTER9piFINFOPasaFJVIjTwC1xzOGP
+A3HUKoIMDTwJR4QRgdBOJzq8UM0Lj5QihU5ZdGMOCSSYUwYzAwwkDhNtUKTBOZ10koMOoohihDwm
+HZKPEDwb4fMe9An0g5Yl+SDKFTHnkMMLLQAjXUTxUCLEIyH0bIQAwuxVQhEMcEIIIUmHUEsWGCQg
+xQEaIFGAHV0+QnUIIWwyg2T/3MPLDQwwcAUhTjiswYsQl1SAxQKmbBJCIMe6ISjVmXwsWQKJEJJE
+3l1/TY8O4wZyh8ZQ3IF4qX9cggTdAmEwCAMs3IB311fsDfbMGv97BxSBQBAP6QMN0QUhLCSRhOp5
+e923zDpk/EIaRdyO+0C/eHBHEiz0vjrrfMfciSKD4LJ8RBEk88IN0ff+O/CEVEPLGK1tH1ECM7Dx
+RDWdcMLJFTpUQ44jfCyjvlShZNDE/0QAgT6ypr6AAAA7
+            """),
+
+        'LegacySwitch': PhotoImage( data=r"""
+R0lGODlhMgAYAPcAAAEBAXmDjbe4uAE5cjF7xwFWq2Sa0S9biSlrrdTW1k2Ly02a5xUvSQFHjmep
+6bfI2Q5SlQIYLwFfvj6M3Jaan8fHyDuFzwFp0Vah60uU3AEiRhFgrgFRogFr10N9uTFrpytHYQFM
+mGWt9wIwX+bm5kaT4gtFgR1cnJPF9yt80CF0yAIMGHmp2c/P0AEoUb/P4Fei7qK4zgpLjgFkyQlf
+t1mf5jKD1WWJrQ86ZwFAgBhYmVOa4MPV52uv8y+A0iR3ywFbtUyX5ECI0Q1UmwIcOUGQ3RBXoQI0
+aRJbpr3BxVeJvQUJDafH5wIlS2aq7xBmv52lr7fH12el5Wml3097ph1ru7vM3HCz91Ke6lid40KQ
+4GSQvgQGClFnfwVJjszMzVCX3hljrdPT1AFLlBRnutPf6yd5zjeI2QE9eRBdrBNVl+3v70mV4ydf
+lwMVKwErVlul8AFChTGB1QE3bsTFxQImTVmAp0FjiUSM1k+b6QQvWQ1SlxMgLgFixEqU3xJhsgFT
+pn2Xs5OluZ+1yz1Xb6HN+Td9wy1zuYClykV5r0x2oeDh4qmvt8LDwxhuxRlLfyRioo2124mft9bi
+71mDr7fT79nl8Z2hpQs9b7vN4QMQIOPj5XOPrU2Jx32z6xtvwzeBywFFikFnjwcPFa29yxJjuFmP
+xQFv3qGxwRc/Z8vb6wsRGBNqwqmpqTdvqQIbNQFPngMzZAEfP0mQ13mHlQFYsAFnznOXu2mPtQxj
+vQ1Vn4Ot1+/x8my0/CJgnxNNh8DT5CdJaWyx+AELFWmt8QxPkxBZpwMFB015pgFduGCNuyx7zdnZ
+2WKm6h1xyOPp8aW70QtPkUmM0LrCyr/FyztljwFPm0OJzwFny7/L1xFjswE/e12i50iR2VR8o2Gf
+3xszS2eTvz2BxSlloQdJiwMHDzF3u7bJ3T2I1WCp8+Xt80FokQFJklef6mORw2ap7SJ1y77Q47nN
+3wFfu1Kb5cXJyxdhrdDR0wlNkTSF11Oa4yp4yQEuW0WQ3QIDBQI7dSH5BAEAAAAALAAAAAAyABgA
+Bwj/AAEIHDjKF6SDvhImPMHwhA6HOiLqUENRDYSLEIplxBcNHz4Z5GTI8BLKS5OBA1Ply2fDhxwf
+PlLITGFmmRkzP+DlVKHCmU9nnz45csSqKKsn9gileZKrVC4aRFACOGZu5UobNuRohRkzhc2b+36o
+qCaqrFmzZEV1ERBg3BOmMl5JZTBhwhm7ZyycYZnvJdeuNl21qkCHTiPDhxspTtKoQgUKCJ6wehMV
+5QctWupeo6TkjOd8e1lmdQkTGbTTMaDFiDGINeskX6YhEicUiQa5A/kUKaFFwQ0oXzjZ8Tbcm3Hj
+irwpMtTSgg9QMJf5WEZ9375AiED19ImpSQSUB4Kw/8HFSMyiRWJaqG/xhf2X91+oCbmq1e/MFD/2
+EcApVkWVJhp8J9AqsywQxDfAbLJJPAy+kMkL8shjxTkUnhOJZ5+JVp8cKfhwxwdf4fQLgG4MFAwW
+KOZRAxM81EAPPQvoE0QQfrDhx4399OMBMjz2yCMVivCoCAWXKLKMTPvoUYcsKwi0RCcwYCAlFjU0
+A6OBM4pXAhsl8FYELYWFWZhiZCbRQgIC2AGTLy408coxAoEDx5wwtGPALTVg0E4NKC7gp4FsBKoA
+Ki8U+oIVmVih6DnZPMBMAlGwIARWOLiggSYC+ZNIOulwY4AkSZCyxaikbqHMqaeaIp4+rAaxQxBg
+2P+IozuRzvLZIS4syYVAfMAhwhSC1EPCGoskIIYY9yS7Hny75OFnEIAGyiVvWkjjRxF11fXIG3WU
+KNA6wghDTCW88PKMJZOkm24Z7LarSjPtoIjFn1lKyyVmmBVhwRtvaDDMgFL0Eu4VhaiDwhXCXNFD
+D8QQw7ATEDsBw8RSxotFHs7CKJ60XWrRBj91EOGPQCA48c7J7zTjSTPctOzynjVkkYU+O9S8Axg4
+Z6BzBt30003Ps+AhNB5C4PCGC5gKJMMTZJBRytOl/CH1HxvQkMbVVxujtdZGGKGL17rsEfYQe+xR
+zNnFcGQCv7LsKlAtp8R9Sgd0032BLXjPoPcMffTd3YcEgAMOxOBA1GJ4AYgXAMjiHDTgggveCgRI
+3RfcnffefgcOeDKEG3444osDwgEspMNiTQhx5FoOShxcrrfff0uQjOycD+554qFzMHrpp4cwBju/
+5+CmVNbArnntndeCO+O689777+w0IH0o1P/TRJMohRA4EJwn47nyiocOSOmkn/57COxE3wD11Mfh
+fg45zCGyVF4Ufvvyze8ewv5jQK9++6FwXxzglwM0GPAfR8AeSo4gwAHCbxsQNCAa/kHBAVhwAHPI
+4BE2eIRYeHAEIBwBP0Y4Qn41YWRSCQgAOw==
+            """),
+
+        'LegacyRouter': PhotoImage( data=r"""
+R0lGODlhMgAYAPcAAAEBAXZ8gQNAgL29vQNctjl/xVSa4j1dfCF+3QFq1DmL3wJMmAMzZZW11dnZ
+2SFrtyNdmTSO6gIZMUKa8gJVqEOHzR9Pf5W74wFjxgFx4jltn+np6Eyi+DuT6qKiohdtwwUPGWiq
+6ymF4LHH3Rh11CV81kKT5AMoUA9dq1ap/mV0gxdXlytRdR1ptRNPjTt9vwNgvwJZsX+69gsXJQFH
+jTtjizF0tvHx8VOm9z2V736Dhz2N3QM2acPZ70qe8gFo0HS19wVRnTiR6hMpP0eP1i6J5iNlqAtg
+tktjfQFu3TNxryx4xAMTIzOE1XqAh1uf5SWC4AcfNy1XgQJny93n8a2trRh312Gt+VGm/AQIDTmB
+yAF37QJasydzvxM/ayF3zhdLf8zLywFdu4i56gFlyi2J4yV/1w8wUo2/8j+X8D2Q5Eee9jeR7Uia
+7DpeggFt2QNPm97e3jRong9bpziH2DuT7aipqQoVICmG45vI9R5720eT4Q1hs1er/yVVhwJJktPh
+70tfdbHP7Xev5xs5V7W1sz9jhz11rUVZcQ9WoCVVhQk7cRdtwWuw9QYOFyFHbSBnr0dznxtWkS18
+zKfP9wwcLAMHCwFFiS5UeqGtuRNNiwMfPS1hlQMtWRE5XzGM5yhxusLCwCljnwMdOFWh7cve8pG/
+7Tlxp+Tr8g9bpXF3f0lheStrrYu13QEXLS1ppTV3uUuR1RMjNTF3vU2X4TZupwRSolNne4nB+T+L
+2YGz4zJ/zYe99YGHjRdDcT95sx09XQldsgMLEwMrVc/X3yN3yQ1JhTRbggsdMQNfu9HPz6WlpW2t
+7RctQ0GFyeHh4dvl8SBZklCb5kOO2kWR3Vmt/zdjkQIQHi90uvPz8wIVKBp42SV5zbfT7wtXpStV
+fwFWrBVvyTt3swFz5kGBv2+1/QlbrVFjdQM7d1+j54i67UmX51qn9i1vsy+D2TuR5zddhQsjOR1t
+u0GV6ghbsDVZf4+76RRisent8Xd9hQFBgwFNmwJLlcPDwwFr1z2T5yH5BAEAAAAALAAAAAAyABgA
+Bwj/AAEIHEiQYJY7Qwg9UsTplRIbENuxEiXJgpcz8e5YKsixY8Essh7JcbbOBwcOa1JOmJAmTY4c
+HeoIabJrCShI0XyB8YRso0eOjoAdWpciBZajJ1GuWcnSZY46Ed5N8hPATqEBoRB9gVJsxRlhPwHI
+0kDkVywcRpGe9LF0adOnMpt8CxDnxg1o9lphKoEACoIvmlxxvHOKVg0n/Tzku2WoVoU2J1P6WNkS
+rtwADuxCG/MOjwgRUEIjGG3FhaOBzaThiDSCil27G8Isc3LLjZwXsA6YYJmDjhTMmseoKQIFDx7R
+oxHo2abnwygAlUj1mV6tWjlelEpRwfd6gzI7VeJQ/2vZoVaDUqigqftXpH0R46H9Kl++zUo4JnKq
+9dGvv09RHFhcIUMe0NiFDyql0OJUHWywMc87TXRhhCRGiHAccvNZUR8JxpDTH38p9HEUFhxgMSAv
+jbBjQge8PSXEC6uo0IsHA6gAAShmgCbffNtsQwIJifhRHX/TpUUiSijlUk8AqgQixSwdNBjCa7CF
+oVggmEgCyRf01WcFCYvYUgB104k4YlK5HONEXXfpokYdMrXRAzMhmNINNNzB9p0T57AgyZckpKKP
+GFNgw06ZWKR10jTw6MAmFWj4AJcQQkQQwSefvFeGCemMIQggeaJywSQ/wgHOAmJskQEfWqBlFBEH
+1P/QaGY3QOpDZXA2+A6m7hl3IRQKGDCIAj6iwE8yGKC6xbJv8IHNHgACQQybN2QiTi5NwdlBpZdi
+isd7vyanByOJ7CMGGRhgwE+qyy47DhnBPLDLEzLIAEQjBtChRmVPNWgpr+Be+Nc9icARww9TkIEu
+DAsQ0O7DzGIQzD2QdDEJHTsIAROc3F7qWQncyHPPHN5QQAAG/vjzw8oKp8sPPxDH3O44/kwBQzLB
+xBCMOTzzHEMMBMBARgJvZJBBEm/4k0ACKydMBgwYoKNNEjJXbTXE42Q9jtFIp8z0Dy1jQMA1AGzi
+z9VoW7310V0znYDTGMQgwUDXLDBO2nhvoTXbbyRk/XXL+pxWkAT8UJ331WsbnbTSK8MggDZhCTOM
+LQkcjvXeSPedAAw0nABWWARZIgEDfyTzxt15Z53BG1PEcEknrvgEelhZMDHKCTwI8EcQFHBBAAFc
+gGPLHwLwcMIo12Qxu0ABAQA7
+            """),
+
+        'Controller': PhotoImage( data=r"""
+            R0lGODlhMAAwAPcAAAEBAWfNAYWFhcfHx+3t6/f390lJUaWlpfPz8/Hx72lpaZGRke/v77m5uc0B
+            AeHh4e/v7WNjY3t7e5eXlyMjI4mJidPT0+3t7f///09PT7Ozs/X19fHx8ZWTk8HBwX9/fwAAAAAA
+            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAAALAAAAAAwADAA
+            Bwj/AAEIHEiwoMGDCBMqXMiwocOHECNKnEixosWLGAEIeMCxo8ePHwVkBGABg8mTKFOmtDByAIYN
+            MGPCRCCzQIENNzEMGOkBAwIKQIMKpYCgKAIHCDB4GNkAA4OnUJ9++CDhQ1QGFzA0GKkBA4GvYMOK
+            BYtBA1cNaNOqXcuWq8q3b81m7Cqzbk2bMMu6/Tl0qFEEAZLKxdj1KlSqVA3rnet1rOOwiwmznUzZ
+            LdzLJgdfpIv3pmebN2Pm1GyRbocNp1PLNMDaAM3Im1/alQk4gO28pCt2RdCBt+/eRg8IP1AUdmmf
+            f5MrL56bYlcOvaP7Xo6Ag3HdGDho3869u/YE1507t+3AgLz58ujPMwg/sTBUCAzgy49PH0LW5u0x
+            XFiwvz////5dcJ9bjxVIAHsSdUXAAgs2yOCDDn6FYEQaFGDgYxNCpEFfHHKIX4IDhCjiiCSS+CGF
+            FlCmogYpcnVABTDGKGOMAlRQYwUHnKjhAjX2aOOPN8LImgAL6PiQBhLMqCSNAThQgQRGOqRBBD1W
+            aaOVAggnQARRNqRBBxmEKeaYZIrZQZcMKbDiigqM5OabcMYp55x01ilnQAA7
+            """),
+
         'Host': PhotoImage( data=r"""
             R0lGODlhIAAYAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M
             mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m
@@ -644,7 +3221,7 @@ def miniEditImages():
             C8cSBBAQADs=
         """ ),
 
-        'Switch': PhotoImage( data=r"""
+        'OldSwitch': PhotoImage( data=r"""
             R0lGODlhIAAYAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M
             mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m
             Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A
@@ -671,7 +3248,7 @@ def miniEditImages():
             6saLWLNq3cq1q9evYB0GBAA7
         """ ),
 
-        'Link': PhotoImage( data=r"""
+        'NetLink': PhotoImage( data=r"""
             R0lGODlhFgAWAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M
             mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m
             Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A
@@ -698,7 +3275,29 @@ def miniEditImages():
         """ )
     }
 
+def addDictOption( opts, choicesDict, default, name, helpStr=None ):
+    """Convenience function to add choices dicts to OptionParser.
+       opts: OptionParser instance
+       choicesDict: dictionary of valid choices, must include default
+       default: default choice key
+       name: long option name
+       help: string"""
+    if default not in choicesDict:
+        raise Exception( 'Invalid  default %s for choices dict: %s' %
+                         ( default, name ) )
+    if not helpStr:
+        helpStr = ( '|'.join( sorted( choicesDict.keys() ) ) +
+                    '[,param=value...]' )
+    opts.add_option( '--' + name,
+                     type='string',
+                     default = default,
+                     help = helpStr )
+
 if __name__ == '__main__':
     setLogLevel( 'info' )
     app = MiniEdit()
+    """ import topology if specified """
+    app.parseArgs()
+    app.importTopo()
+
     app.mainloop()
diff --git a/examples/nat.py b/examples/nat.py
index fcad85c209e0055af67837171a01d2f66a269e99..dada3638f05c82bfd40388ed966038342ca9b1a2 100755
--- a/examples/nat.py
+++ b/examples/nat.py
@@ -59,13 +59,13 @@ def fixNetworkManager( root, intf ):
     cfile = '/etc/network/interfaces'
     line = '\niface %s inet manual\n' % intf
     config = open( cfile ).read()
-    if ( line ) not in config:
+    if line not in config:
         print '*** Adding', line.strip(), 'to', cfile
         with open( cfile, 'a' ) as f:
             f.write( line )
-    # Probably need to restart network-manager to be safe -
-    # hopefully this won't disconnect you
-    root.cmd( 'service network-manager restart' )
+        # Probably need to restart network-manager to be safe -
+        # hopefully this won't disconnect you
+        root.cmd( 'service network-manager restart' )
 
 def connectToInternet( network, switch='s1', rootip='10.254', subnet='10.0/8'):
     """Connect the network to the internet
diff --git a/examples/numberedports.py b/examples/numberedports.py
new file mode 100755
index 0000000000000000000000000000000000000000..82205506ca36416b292af72136a3a16a07104518
--- /dev/null
+++ b/examples/numberedports.py
@@ -0,0 +1,76 @@
+#!/usr/bin/python
+
+"""
+Create a network with 5 hosts, numbered 1-4 and 9. 
+Validate that the port numbers match to the interface name,
+and that the ovs ports match the mininet ports.
+"""
+
+from mininet.net import Mininet
+from mininet.node import Controller
+from mininet.log import setLogLevel, info, warn
+from mininet.node import Node
+
+def validatePort( switch, intf ):
+    "Validate intf's OF port number"
+    ofport = int( switch.cmd( 'ovs-vsctl get Interface', intf,
+                          'ofport' ) )
+    if ofport != switch.ports[ intf ]:
+        warn( 'WARNING: ofport for', intf, 'is actually', ofport, '\n' )
+        return 0
+    else:
+        return 1
+
+def net():
+
+    "Create a network with 5 hosts."
+
+    net = Mininet( controller=Controller )
+
+    info( '*** Adding controller\n' )
+    net.addController( 'c0' )
+
+    info( '*** Adding hosts\n' )
+    h1 = net.addHost( 'h1', ip='10.0.0.1' )
+    h2 = net.addHost( 'h2', ip='10.0.0.2' )
+    h3 = net.addHost( 'h3', ip='10.0.0.3' )
+    h4 = net.addHost( 'h4', ip='10.0.0.4' )
+    h5 = net.addHost( 'h5', ip='10.0.0.5' )
+
+    info( '*** Adding switch\n' )
+    s1 = net.addSwitch( 's1' )
+
+    info( '*** Creating links\n' )
+    # host 1-4 connect to ports 1-4 on the switch
+    net.addLink( h1, s1 )
+    net.addLink( h2, s1 )
+    net.addLink( h3, s1 )
+    net.addLink( h4, s1 )
+    net.addLink( h5, s1, port1 = 1, port2 = 9 ) # specify a different port to connect host 5 to on the switch.
+
+    root = Node( 'root', inNamespace=False )
+    info( '*** Starting network\n' )
+    net.start()
+
+    # print the interfaces and their port numbers
+    info( '\n*** printing and validating the ports running on each interface\n' )
+    for intfs in s1.intfList():
+        if not intfs.name == "lo":
+            info( intfs, ': ', s1.ports[intfs], 
+            '\n' )
+            info ( 'Validating that', intfs, 'is actually on port', s1.ports[intfs], '... ' )
+            if validatePort( s1, intfs ):
+                info( 'Validated.\n' )
+    print '\n'
+        
+    # test the network with pingall
+    net.pingAll()
+    print '\n'
+
+    info( '*** Stopping network' )
+    net.stop()
+
+if __name__ == '__main__':
+    setLogLevel( 'info' )
+    net()
+
diff --git a/examples/sshd.py b/examples/sshd.py
index cef1d87b097cce693f5d6c1f5da7d2f3c6381ea2..27166cb909837a80221e920614b430e559584943 100755
--- a/examples/sshd.py
+++ b/examples/sshd.py
@@ -55,7 +55,7 @@ def sshd( network, cmd='/usr/sbin/sshd', opts='-D',
     if not switch:
         switch = network[ 's1' ]  # switch to use
     if not routes:
-         routes = [ '10.0.0.0/24' ]
+        routes = [ '10.0.0.0/24' ]
     connectToRootNS( network, switch, ip, routes )
     for host in network.hosts:
         host.cmd( cmd + ' ' + opts + '&' )
diff --git a/examples/test/test_hwintf.py b/examples/test/test_hwintf.py
index 20d08d319c2d769ebea6d55d764926e35d75084f..aafd643963852b316652179acf7a1027ae5e7919 100755
--- a/examples/test/test_hwintf.py
+++ b/examples/test/test_hwintf.py
@@ -5,12 +5,14 @@
 """
 
 import unittest
-import pexpect
 import re
+
+import pexpect
+
 from mininet.log import setLogLevel
-from mininet.net import Mininet
 from mininet.node import Node
-from mininet.link import Link, Intf
+from mininet.link import Link
+
 
 class testHwintf( unittest.TestCase ):
 
diff --git a/examples/test/test_numberedports.py b/examples/test/test_numberedports.py
new file mode 100755
index 0000000000000000000000000000000000000000..b565d3ed79d6ea7f35e400c963f35e6a69c0dfa4
--- /dev/null
+++ b/examples/test/test_numberedports.py
@@ -0,0 +1,52 @@
+#!/usr/bin/env python
+
+"""
+Test for numberedports.py
+"""
+
+import unittest
+import pexpect
+from collections import defaultdict
+from mininet.node import OVSSwitch
+
+class testNumberedports( unittest.TestCase ):
+
+    @unittest.skipIf( OVSSwitch.setup() or OVSSwitch.isOldOVS(), "old version of OVS" )
+    def testConsistency( self ):
+        """verify consistency between mininet and ovs ports"""
+        p = pexpect.spawn( 'python -m mininet.examples.numberedports' )
+        opts = [ 'Validating that s1-eth\d is actually on port \d ... Validated.', 
+                 'Validating that s1-eth\d is actually on port \d ... WARNING', 
+                 pexpect.EOF ]
+        correct_ports = True
+        count = 0
+        while True:
+            index = p.expect( opts )
+            if index == 0:
+                count += 1
+            elif index == 1:
+                correct_ports = False
+            elif index == 2:
+                self.assertNotEqual( 0, count )
+                break
+        self.assertTrue( correct_ports )
+
+    def testNumbering( self ):
+        """verify that all of the port numbers are printed correctly and consistent with their interface"""
+        p = pexpect.spawn( 'python -m mininet.examples.numberedports' )
+        opts = [ 's1-eth(\d+) :  (\d+)', 
+                 pexpect.EOF ]
+        count_intfs = 0
+        while True:
+            index = p.expect( opts )
+            if index == 0:
+                count_intfs += 1
+                intfport = p.match.group( 1 )
+                ofport = p.match.group( 2 )
+                self.assertEqual( intfport, ofport )
+            elif index == 1:
+                break
+                self.assertNotEqual( 0, count_intfs )
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/examples/test/test_simpleperf.py b/examples/test/test_simpleperf.py
index 7d44c9c7961da57bfe0b2e123f915fc5083a71f1..d88da673381f6903883a6c5c895c071e2f3741b6 100755
--- a/examples/test/test_simpleperf.py
+++ b/examples/test/test_simpleperf.py
@@ -24,7 +24,7 @@ def testE2E( self ):
         # check ping results
         p.expect( "Results: (\d+)% dropped", timeout=120 )
         loss = int( p.match.group( 1 ) )
-        self.assertTrue( loss > 0 and loss < 100 )
+        self.assertTrue( 0 < loss < 100 )
         # check iperf results
         p.expect( "Results: \['([\d\.]+) .bits/sec", timeout=480 )
         bw = float( p.match.group( 1 ) )
@@ -46,7 +46,7 @@ def testTopo( self ):
         m = re.search( expectStr, output )
         loss = int( m.group( 3 ) )
         net.stop()
-        self.assertTrue( loss > 0 and loss < 100 )
+        self.assertTrue( 0 < loss < 100 )
 
 if __name__ == '__main__':
     setLogLevel( 'warning' )
diff --git a/examples/test/test_sshd.py b/examples/test/test_sshd.py
index eae65e8eddfa5b796f65e517cb997ea1720b62ee..9cfad6d9db4c436a1f8bd9015fcde29e3c43f87c 100755
--- a/examples/test/test_sshd.py
+++ b/examples/test/test_sshd.py
@@ -6,7 +6,6 @@
 
 import unittest
 import pexpect
-from time import sleep
 from mininet.clean import sh
 
 class testSSHD( unittest.TestCase ):
diff --git a/mininet/clean.py b/mininet/clean.py
index 5162bf378fefcb98af186ce812f5470d2e4b84fa..49e32eaf24bd267e0bd889f6564e6dfe6e00f5fc 100755
--- a/mininet/clean.py
+++ b/mininet/clean.py
@@ -10,7 +10,7 @@
 nothing irreplaceable!
 """
 
-from subprocess import Popen, PIPE
+from subprocess import Popen, PIPE, check_output as co
 import time
 
 from mininet.log import info
@@ -47,21 +47,40 @@ def cleanup():
     cleanUpScreens()
 
     info( "*** Removing excess kernel datapaths\n" )
-    dps = sh( "ps ax | egrep -o 'dp[0-9]+' | sed 's/dp/nl:/'" ).split( '\n' )
+    dps = sh( "ps ax | egrep -o 'dp[0-9]+' | sed 's/dp/nl:/'" ).splitlines()
     for dp in dps:
-        if dp != '':
+        if dp:
             sh( 'dpctl deldp ' + dp )
 
     info( "***  Removing OVS datapaths" )
-    dps = sh("ovs-vsctl --timeout=1 list-br").split( '\n' )
+    dps = sh("ovs-vsctl --timeout=1 list-br").strip().splitlines()
+    if dps:
+        sh( "ovs-vsctl " + " -- ".join( "--if-exists del-br " + dp
+                                       for dp in dps if dp ) )
+    # And in case the above didn't work...
+    dps = sh("ovs-vsctl --timeout=1 list-br").strip().splitlines()
     for dp in dps:
-        if dp:
-            sh( 'ovs-vsctl del-br ' + dp )
+        sh( 'ovs-vsctl del-br ' + dp )
 
     info( "*** Removing all links of the pattern foo-ethX\n" )
-    links = sh( r"ip link show | egrep -o '(\w+-eth\w+)'" ).split( '\n' )
+    links = sh( "ip link show | "
+                "egrep -o '([-_.[:alnum:]]+-eth[[:digit:]]+)'" ).splitlines()
     for link in links:
-        if link != '':
+        if link:
             sh( "ip link del " + link )
 
+    info( "*** Killing stale mininet node processes\n" )
+    sh( 'pkill -9 -f mininet:' )
+    # Make sure they are gone
+    while True:
+        try:
+            pids = co( 'pgrep -f mininet:'.split() )
+        except:
+            pids = ''
+        if pids:
+            sh( 'pkill -f 9 mininet:' )
+            sleep( .5 )
+        else:
+            break
+
     info( "*** Cleanup complete.\n" )
diff --git a/mininet/cli.py b/mininet/cli.py
index 3ca190f722d487436464aac231f0519ac5634251..42a4d3d7f1d6724d916475d3f17d6766098802a7 100644
--- a/mininet/cli.py
+++ b/mininet/cli.py
@@ -31,6 +31,8 @@
 from select import poll, POLLIN
 import sys
 import time
+import os
+import atexit
 
 from mininet.log import info, output, error
 from mininet.term import makeTerms, runX11
@@ -52,6 +54,18 @@ def __init__( self, mininet, stdin=sys.stdin, script=None ):
         self.inputFile = script
         Cmd.__init__( self )
         info( '*** Starting CLI:\n' )
+
+        # Set up history if readline is available
+        try:
+            import readline
+        except ImportError:
+            pass
+        else:
+            history_path = os.path.expanduser('~/.mininet_history')
+            if os.path.isfile(history_path):
+                readline.read_history_file(history_path)
+            atexit.register(lambda: readline.write_history_file(history_path))
+
         if self.inputFile:
             self.do_source( self.inputFile )
             return
@@ -63,7 +77,7 @@ def __init__( self, mininet, stdin=sys.stdin, script=None ):
                         node.sendInt()
                         node.monitor()
                 if self.isatty():
-                    quietRun( 'stty sane' )
+                    quietRun( 'stty echo sane intr "^C"' )
                 self.cmdloop()
                 break
             except KeyboardInterrupt:
@@ -151,16 +165,16 @@ def do_px( self, line ):
 
     # pylint: enable-msg=W0703,W0122
 
-    def do_pingall( self, _line ):
+    def do_pingall( self, line ):
         "Ping between all hosts."
-        self.mn.pingAll()
+        self.mn.pingAll( line )
 
     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."
+        "Ping between all hosts, returns all ping results."
         self.mn.pingAllFull()
 
     def do_pingpairfull( self, _line ):
@@ -187,7 +201,7 @@ def do_iperf( self, line ):
             error( 'invalid number of args: iperf src dst\n' )
 
     def do_iperfudp( self, line ):
-        "Simple iperf TCP test between two (optionally specified) hosts."
+        "Simple iperf UDP test between two (optionally specified) hosts."
         args = line.split()
         if not args:
             self.mn.iperf( l4Type='UDP' )
@@ -297,6 +311,7 @@ def do_source( self, line ):
                     break
         except IOError:
             error( 'error reading file %s\n' % args[ 0 ] )
+        self.inputFile.close()
         self.inputFile = None
 
     def do_dpctl( self, line ):
@@ -331,13 +346,13 @@ def default( self, line ):
             node = self.mn[ first ]
             rest = args.split( ' ' )
             # Substitute IP addresses for node names in command
-            rest = [ self.mn[ arg ].defaultIntf().updateIP()
+            # 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:
-            builtin = isShellBuiltin( first )
-            node.sendCmd( rest, printPid=( not builtin ) )
+            node.sendCmd( rest )
             self.waitForNode( node )
         else:
             error( '*** Unknown command: %s\n' % line )
@@ -345,7 +360,7 @@ def default( self, line ):
     # pylint: enable-msg=R0201
 
     def waitForNode( self, node ):
-        "Wait for a node to finish, and  print its output."
+        "Wait for a node to finish, and print its output."
         # Pollers
         nodePoller = poll()
         nodePoller.register( node.stdout )
@@ -363,7 +378,7 @@ def waitForNode( self, node ):
                 if False and self.inputFile:
                     key = self.inputFile.read( 1 )
                     if key is not '':
-                        node.write(key)
+                        node.write( key )
                     else:
                         self.inputFile = None
                 if isReadable( self.inPoller ):
@@ -375,8 +390,12 @@ def waitForNode( self, node ):
                 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()
 
+
 # Helper functions
 
 def isReadable( poller ):
diff --git a/mininet/log.py b/mininet/log.py
index a046f50f019ef178cc22600bab1c28c6017ef235..5f96569e8312516795de614579a90c20f4400d14 100644
--- a/mininet/log.py
+++ b/mininet/log.py
@@ -57,7 +57,7 @@ def emit( self, record ):
 
 class Singleton( type ):
     """Singleton pattern from Wikipedia
-       See http://en.wikipedia.org/wiki/SingletonPattern#Python
+       See http://en.wikipedia.org/wiki/Singleton_Pattern
 
        Intended to be used as a __metaclass_ param, as shown for the class
        below."""
@@ -69,7 +69,7 @@ def __init__( cls, name, bases, dict_ ):
     def __call__( cls, *args, **kw ):
         if cls.instance is None:
             cls.instance = super( Singleton, cls ).__call__( *args, **kw )
-            return cls.instance
+        return cls.instance
 
 
 class MininetLogger( Logger, object ):
diff --git a/mininet/net.py b/mininet/net.py
index 1912fba6ec81f8b011da5bf359e662402cb83e99..42ab0a70447a93acac30f4e0352cf6035e671c7b 100755
--- a/mininet/net.py
+++ b/mininet/net.py
@@ -90,29 +90,30 @@
 import re
 import select
 import signal
+import copy
 from time import sleep
-from itertools import chain
+from itertools import chain, groupby
 
 from mininet.cli import CLI
-from mininet.log import info, error, debug, output
-from mininet.node import Host, OVSKernelSwitch, Controller, NAT
+from mininet.log import info, error, debug, output, warn
+from mininet.node import Host, OVSKernelSwitch, DefaultController, Controller, NAT
 from mininet.link import Link, Intf
 from mininet.util import quietRun, fixLimits, numCores, ensureRoot
 from mininet.util import macColonHex, ipStr, ipParse, netParse, ipAdd
 from mininet.term import cleanUpScreens, makeTerms
 
 # Mininet version: should be consistent with README and LICENSE
-VERSION = "2.1.0"
+VERSION = "2.1.0+"
 
 class Mininet( object ):
     "Network emulation with hosts spawned in network namespaces."
 
     def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host,
-                  controller=Controller, link=Link, intf=Intf,
+                  controller=DefaultController, link=Link, intf=Intf,
                   build=True, xterms=False, cleanup=False, ipBase='10.0.0.0/8',
                   inNamespace=False,
                   autoSetMacs=False, autoStaticArp=False, autoPinCpus=False,
-                  listenPort=None ):
+                  listenPort=None, waitConnected=False ):
         """Create Mininet object.
            topo: Topo (topology) object or None
            switch: default Switch class
@@ -148,6 +149,7 @@ def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host,
         self.numCores = numCores()
         self.nextCore = 0  # next core for pinning hosts to CPUs
         self.listenPort = listenPort
+        self.waitConn = waitConnected
 
         self.hosts = []
         self.switches = []
@@ -163,6 +165,37 @@ def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host,
         if topo and build:
             self.build()
 
+
+    def waitConnected( self, timeout=None, delay=.5 ):
+        """wait for each switch to connect to a controller,
+           up to 5 seconds
+           timeout: time to wait, or None to wait indefinitely
+           delay: seconds to sleep per iteration
+           returns: True if all switches are connected"""
+        info( '*** Waiting for switches to connect\n' )
+        time = 0
+        remaining = list( self.switches )
+        while True:
+            for switch in tuple( remaining ):
+                if switch.connected():
+                    info( '%s ' % switch )
+                    remaining.remove( switch )
+            if not remaining:
+                info( '\n' )
+                return True
+            if time > timeout and timeout is not None:
+                break
+            sleep( delay )
+            time += delay
+        warn( 'Timed out after %d seconds\n' % time )
+        for switch in remaining:
+            if not switch.connected():
+                warn( 'Warning: %s is not connected to a controller\n'
+                      % switch.name )
+            else:
+                remaining.remove( switch )
+        return not remaining
+
     def addHost( self, name, cls=None, **params ):
         """Add host.
            name: name of host to add
@@ -213,7 +246,7 @@ def addController( self, name='c0', controller=None, **params ):
         if not controller:
             controller = self.controller
         # Construct new controller if one is not given
-        if isinstance(name, Controller):
+        if isinstance( name, Controller ):
             controller_new = name
             # Pylint thinks controller is a str()
             # pylint: disable=E1103
@@ -222,7 +255,7 @@ def addController( self, name='c0', controller=None, **params ):
         else:
             controller_new = controller( name, **params )
         # Add new controller to net
-        if controller_new:  # allow controller-less setups
+        if controller_new: # allow controller-less setups
             self.controllers.append( controller_new )
             self.nameToNode[ name ] = controller_new
         return controller_new
@@ -338,7 +371,11 @@ def buildFromTopo( self, topo=None ):
             if type( classes ) is not list:
                 classes = [ classes ]
             for i, cls in enumerate( classes ):
-                self.addController( 'c%d' % i, cls )
+                # Allow Controller objects because nobody understands currying
+                if isinstance( cls, Controller ):
+                    self.addController( cls )
+                else:
+                    self.addController( 'c%d' % i, cls )
 
         info( '*** Adding hosts:\n' )
         for hostName in topo.hosts():
@@ -369,7 +406,7 @@ def build( self ):
         "Build mininet."
         if self.topo:
             self.buildFromTopo( self.topo )
-        if ( self.inNamespace ):
+        if self.inNamespace:
             self.configureControlNetwork()
         info( '*** Configuring hosts\n' )
         self.configHosts()
@@ -415,13 +452,23 @@ def start( self ):
             info( switch.name + ' ')
             switch.start( self.controllers )
         info( '\n' )
+        if self.waitConn:
+            self.waitConnected()
 
     def stop( self ):
         "Stop the controller(s), switches and hosts"
+        info( '*** Stopping %i controllers\n' % len( self.controllers ) )
+        for controller in self.controllers:
+            info( controller.name + ' ' )
+            controller.stop()
+        info( '\n' )
         if self.terms:
             info( '*** Stopping %i terms\n' % len( self.terms ) )
             self.stopXterms()
         info( '*** Stopping %i switches\n' % len( self.switches ) )
+        for swclass, switches in groupby( sorted( self.switches, key=type ), type ):
+            if hasattr( swclass, 'batchShutdown' ):
+                swclass.batchShutdown( switches )
         for switch in self.switches:
             info( switch.name + ' ' )
             switch.stop()
@@ -430,11 +477,6 @@ def stop( self ):
         for host in self.hosts:
             info( host.name + ' ' )
             host.terminate()
-        info( '\n' )
-        info( '*** Stopping %i controllers\n' % len( self.controllers ) )
-        for controller in self.controllers:
-            info( controller.name + ' ' )
-            controller.stop()
         info( '\n*** Done\n' )
 
     def run( self, test, *args, **kwargs ):
@@ -477,13 +519,13 @@ 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)
+            return 1, 0
         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 (1, 0)
+            return 1, 0
         sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
         return sent, received
 
@@ -518,7 +560,7 @@ def ping( self, hosts=None, timeout=None ):
                     output( ( '%s ' % dest.name ) if received else 'X ' )
             output( '\n' )
         if packets > 0:
-            ploss = 100 * lost / packets
+            ploss = 100.0 * lost / packets
             received = packets - lost
             output( "*** Results: %i%% dropped (%d/%d received)\n" %
                     ( ploss, received, packets ) )
@@ -589,10 +631,10 @@ def pingFull( self, hosts=None, timeout=None ):
                     (rttmin, rttavg, rttmax, rttdev) )
         return all_outputs
 
-    def pingAll( self ):
+    def pingAll( self, timeout=None ):
         """Ping between all hosts.
            returns: ploss packet loss percentage"""
-        return self.ping()
+        return self.ping( timeout=timeout )
 
     def pingPair( self ):
         """Ping between first two hosts, useful for testing.
@@ -627,7 +669,7 @@ def _parseIperf( iperfOutput ):
 
     # XXX This should be cleaned up
 
-    def iperf( self, hosts=None, l4Type='TCP', udpBw='10M' ):
+    def iperf( self, hosts=None, l4Type='TCP', udpBw='10M', format=None ):
         """Run iperf between two hosts.
            hosts: list of hosts; if None, uses opposite hosts
            l4Type: string, one of [ TCP, UDP ]
@@ -650,6 +692,8 @@ def iperf( self, hosts=None, l4Type='TCP', udpBw='10M' ):
             bwArgs = '-b ' + udpBw + ' '
         elif l4Type != 'TCP':
             raise Exception( 'Unexpected l4 type: %s' % l4Type )
+        if format:
+          iperfArgs += '-f %s ' %format
         server.sendCmd( iperfArgs + '-s', printPid=True )
         servout = ''
         while server.lastPid is None:
@@ -657,7 +701,7 @@ def iperf( self, hosts=None, l4Type='TCP', udpBw='10M' ):
         if l4Type == 'TCP':
             while 'Connected' not in client.cmd(
                     'sh -c "echo A | telnet -e A %s 5001"' % server.IP()):
-                output('waiting for iperf to start up...')
+                info( 'Waiting for iperf to start up...' )
                 sleep(.5)
         cliout = client.cmd( iperfArgs + '-t 5 -c ' + server.IP() + ' ' +
                              bwArgs )
diff --git a/mininet/node.py b/mininet/node.py
index dba383d4d370c788bfb070ebb869c8c97e755bb4..7f8125dc4c3133d824aa80ac11497b4d415350ea 100644
--- a/mininet/node.py
+++ b/mininet/node.py
@@ -16,6 +16,11 @@
 CPULimitedHost: a virtual host whose CPU bandwidth is limited by
     RT or CFS bandwidth limiting.
 
+HostWithPrivateDirs: a virtual host that has user-specified private
+    directories. These may be temporary directories stored as a tmpfs,
+    or persistent directories that are mounted from another directory in
+    the root filesystem.
+
 Switch: superclass for switch nodes.
 
 UserSwitch: a switch using the user-space switch from the OpenFlow
@@ -47,6 +52,7 @@
 """
 
 import os
+import pty
 import re
 import signal
 import select
@@ -59,6 +65,8 @@
                            numCores, retry, mountCgroups )
 from mininet.moduledeps import moduleDeps, pathCheck, OVS_KMOD, OF_KMOD, TUN
 from mininet.link import Link, Intf, TCIntf
+from re import findall
+from distutils.version import StrictVersion
 
 class Node( object ):
     """A virtual network node is simply a shell in a network namespace.
@@ -118,16 +126,22 @@ def startShell( self ):
             return
         # mnexec: (c)lose descriptors, (d)etach from tty,
         # (p)rint pid, and run in (n)amespace
-        opts = '-cdp'
+        opts = '-cd'
         if self.inNamespace:
             opts += 'n'
-        # bash -m: enable job control
+        # bash -m: enable job control, i: force interactive
         # -s: pass $* to shell, and make process easy to find in ps
-        cmd = [ 'mnexec', opts, 'bash', '-ms', 'mininet:' + self.name ]
-        self.shell = Popen( cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT,
-                            close_fds=True )
-        self.stdin = self.shell.stdin
-        self.stdout = self.shell.stdout
+        # prompt is set to sentinel chr( 127 )
+        os.environ[ 'PS1' ] = chr( 127 )
+        cmd = [ 'mnexec', opts, 'bash', '--norc', '-mis', 'mininet:' + self.name ]
+        # Spawn a shell subprocess in a pseudo-tty, to disable buffering
+        # in the subprocess and insulate it from signals (e.g. SIGINT)
+        # received by the parent
+        master, slave = pty.openpty()
+        self.shell = Popen( cmd, stdin=slave, stdout=slave, stderr=slave,
+                                  close_fds=False )
+        self.stdin = os.fdopen( master )
+        self.stdout = self.stdin
         self.pid = self.shell.pid
         self.pollOut = select.poll()
         self.pollOut.register( self.stdout )
@@ -140,7 +154,14 @@ def startShell( self ):
         self.lastCmd = None
         self.lastPid = None
         self.readbuf = ''
+        # Wait for prompt
+        while True:
+            data = self.read( 1024 )
+            if data[ -1 ] == chr( 127 ):
+                break
+            self.pollOut.poll()
         self.waiting = False
+        self.cmd( 'stty -echo' )
 
     def cleanup( self ):
         "Help python collect its garbage."
@@ -186,7 +207,7 @@ def write( self, data ):
     def terminate( self ):
         "Send kill signal to Node and clean up after it."
         if self.shell:
-            os.kill( self.pid, signal.SIGKILL )
+            os.killpg( self.pid, signal.SIGKILL )
         self.cleanup()
 
     def stop( self ):
@@ -219,36 +240,29 @@ def sendCmd( self, *args, **kwargs ):
             # Replace empty commands with something harmless
             cmd = 'echo -n'
         self.lastCmd = cmd
-        printPid = printPid and not isShellBuiltin( cmd )
-        if len( cmd ) > 0 and cmd[ -1 ] == '&':
-            # print ^A{pid}\n{sentinel}
-            cmd += ' printf "\\001%d\n\\177" $! \n'
-        else:
-            # print sentinel
-            cmd += '; printf "\\177"'
-            if printPid and not isShellBuiltin( cmd ):
+        if printPid and not isShellBuiltin( cmd ):
+            if len( cmd ) > 0 and cmd[ -1 ] == '&':
+                # print ^A{pid}\n so monitor() can set lastPid
+                cmd += ' printf "\\001%d\n" $! \n'
+            else:
                 cmd = 'mnexec -p ' + cmd
         self.write( cmd + '\n' )
         self.lastPid = None
         self.waiting = True
 
-    def sendInt( self, sig=signal.SIGINT ):
+    def sendInt( self, intr=chr( 3 ) ):
         "Interrupt running command."
-        if self.lastPid:
-            try:
-                os.kill( self.lastPid, sig )
-            except OSError:
-                pass
+        self.write( intr )
 
-    def monitor( self, timeoutms=None ):
+    def monitor( self, timeoutms=None, findPid=True ):
         """Monitor and return the output of a command.
            Set self.waiting to False if command has completed.
            timeoutms: timeout in ms or None to wait indefinitely."""
         self.waitReadable( timeoutms )
         data = self.read( 1024 )
         # Look for PID
-        marker = chr( 1 ) + r'\d+\n'
-        if chr( 1 ) in data:
+        marker = chr( 1 ) + r'\d+\r\n'
+        if findPid and chr( 1 ) in data:
             markers = re.findall( marker, data )
             if markers:
                 self.lastPid = int( markers[ 0 ][ 1: ] )
@@ -725,6 +739,33 @@ def init( cls ):
         mountCgroups()
         cls.inited = True
 
+class HostWithPrivateDirs( Host ):
+    "Host with private directories"
+
+    def __init__( self, name, *args, **kwargs ):
+        "privateDirs: list of private directory strings or tuples"
+        self.name = name
+        self.privateDirs = kwargs.pop( 'privateDirs', [] )
+        Host.__init__( self, name, *args, **kwargs )
+        self.mountPrivateDirs()
+
+    def mountPrivateDirs( self ):
+        "mount private directories"
+        for directory in self.privateDirs:
+            if isinstance( directory, tuple ):
+                # mount given private directory
+                privateDir = directory[ 1 ] % self.__dict__ 
+                mountPoint = directory[ 0 ]
+                self.cmd( 'mkdir -p %s' % privateDir )
+                self.cmd( 'mkdir -p %s' % mountPoint )
+                self.cmd( 'mount --bind %s %s' %
+                               ( privateDir, mountPoint ) )
+            else:
+                # mount temporary filesystem on directory
+                self.cmd( 'mkdir -p %s' % directory ) 
+                self.cmd( 'mount -n -t tmpfs tmpfs %s' % directory )
+
+
 
 # Some important things to note:
 #
@@ -754,27 +795,32 @@ class Switch( Node ):
     dpidLen = 16  # digits in dpid passed to switch
 
     def __init__( self, name, dpid=None, opts='', listenPort=None, **params):
-        """dpid: dpid for switch (or None to derive from name, e.g. s1 -> 1)
+        """dpid: dpid hex string (or None to derive from name, e.g. s1 -> 1)
            opts: additional switch options
            listenPort: port to listen on for dpctl connections"""
         Node.__init__( self, name, **params )
-        self.dpid = dpid if dpid else self.defaultDpid()
+        self.dpid = self.defaultDpid( dpid )
         self.opts = opts
         self.listenPort = listenPort
         if not self.inNamespace:
             self.controlIntf = Intf( 'lo', self, port=0 )
 
-    def defaultDpid( self ):
-        "Derive dpid from switch name, s1 -> 1"
-        try:
-            dpid = int( re.findall( r'\d+', self.name )[ 0 ] )
-            dpid = hex( dpid )[ 2: ]
-            dpid = '0' * ( self.dpidLen - len( dpid ) ) + dpid
-            return dpid
-        except IndexError:
-            raise Exception( 'Unable to derive default datapath ID - '
-                             'please either specify a dpid or use a '
-                             'canonical switch name such as s23.' )
+    def defaultDpid( self, dpid=None ):
+        "Return correctly formatted dpid from dpid or switch name (s1 -> 1)"
+        if dpid:
+            # Remove any colons and make sure it's a good hex number
+            dpid = dpid.translate( None, ':' )
+            assert len( dpid ) <= self.dpidLen and int( dpid, 16 ) >= 0
+        else:
+            # Use hex of the first number in the switch name
+            nums = re.findall( r'\d+', self.name )
+            if nums:
+                dpid = hex( int( nums[ 0 ] ) )[ 2: ]
+            else:
+                raise Exception( 'Unable to derive default datapath ID - '
+                                 'please either specify a dpid or use a '
+                                 'canonical switch name such as s23.' )
+        return '0' * ( self.dpidLen - len( dpid ) ) + dpid
 
     def defaultIntf( self ):
         "Return control interface"
@@ -819,6 +865,8 @@ def __init__( self, name, dpopts='--no-slicing', **kwargs ):
                               '(openflow.org)' )
         if self.listenPort:
             self.opts += ' --listen=ptcp:%i ' % self.listenPort
+        else:
+            self.opts += ' --listen=punix:/tmp/%s.listen' % self.name
         self.dpopts = dpopts
 
     @classmethod
@@ -829,10 +877,13 @@ def setup( cls ):
 
     def dpctl( self, *args ):
         "Run dpctl command"
+        listenAddr = None
         if not self.listenPort:
-            return "can't run dpctl without passive listening port"
+            listenAddr = 'unix:/tmp/%s.listen' % self.name
+        else:
+            listenAddr = 'tcp:127.0.0.1:%i' % self.listenPort
         return self.cmd( 'dpctl ' + ' '.join( args ) +
-                         ' tcp:127.0.0.1:%i' % self.listenPort )
+                         ' ' + listenAddr )
 
     def connected( self ):
         "Is the switch connected to a controller?"
@@ -849,10 +900,13 @@ def TCReapply( intf ):
             minspeed = ifspeed * 0.001
 
             res = intf.config( **intf.params )
-            parent = res['parent']
+
+            if res is None: # link may not have TC parameters
+                return
 
             # Re-add qdisc, root, and default classes user switch created, but
             # with new parent, as setup by Mininet's TCIntf
+            parent = res['parent']
             intf.tc( "%s qdisc add dev %s " + parent +
                      " handle 1: htb default 0xfffe" )
             intf.tc( "%s class add dev %s classid 1:0xffff parent 1: htb rate "
@@ -947,14 +1001,17 @@ def stop( self ):
 class OVSSwitch( Switch ):
     "Open vSwitch switch. Depends on ovs-vsctl."
 
-    def __init__( self, name, failMode='secure', datapath='kernel', **params ):
+    def __init__( self, name, failMode='secure', datapath='kernel',
+                 inband=False, **params ):
         """Init.
            name: name for switch
            failMode: controller loss behavior (secure|open)
-           datapath: userspace or kernel mode (kernel|user)"""
+           datapath: userspace or kernel mode (kernel|user)
+           inband: use in-band control (False)"""
         Switch.__init__( self, name, **params )
         self.failMode = failMode
         self.datapath = datapath
+        self.inband = inband
 
     @classmethod
     def setup( cls ):
@@ -975,6 +1032,20 @@ def setup( cls ):
                    'You may wish to try '
                    '"service openvswitch-switch start".\n' )
             exit( 1 )
+        info = quietRun( 'ovs-vsctl --version' )
+        cls.OVSVersion =  findall( '\d+\.\d+', info )[ 0 ]
+
+    @classmethod
+    def isOldOVS( cls ):
+        return ( StrictVersion( cls.OVSVersion ) <
+             StrictVersion( '1.10' ) )
+
+    @classmethod
+    def batchShutdown( cls, switches ):
+        "Call ovs-vsctl del-br on all OVSSwitches in a list"
+        quietRun( 'ovs-vsctl ' +
+                  ' -- '.join( '--if-exists del-br %s' % s
+                               for s in switches ) )
 
     def dpctl( self, *args ):
         "Run ovs-ofctl command"
@@ -1025,30 +1096,51 @@ def start( self, controllers ):
         self.cmd( 'ifconfig lo up' )
         # Annoyingly, --if-exists option seems not to work
         self.cmd( 'ovs-vsctl del-br', self )
-        self.cmd( 'ovs-vsctl add-br', self )
-        if self.datapath == 'user':
-            self.cmd( 'ovs-vsctl set bridge', self,'datapath_type=netdev' )
         int( self.dpid, 16 ) # DPID must be a hex string
-        self.cmd( 'ovs-vsctl -- set Bridge', self,
-                  'other_config:datapath-id=' + self.dpid )
-        self.cmd( 'ovs-vsctl set-fail-mode', self, self.failMode )
-        for intf in self.intfList():
-            if not intf.IP():
-                self.attach( intf )
-        # Add controllers
-        clist = ' '.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
-                            for c in controllers ] )
+        # Interfaces and controllers
+        intfs = ' '.join( '-- add-port %s %s ' % ( self, intf ) +
+                          '-- set Interface %s ' % intf +
+                          'ofport_request=%s ' % self.ports[ intf ]
+                         for intf in self.intfList() if not intf.IP() )
+        clist = ' '.join( '%s:%s:%d' % ( c.protocol, c.IP(), c.port )
+                         for c in controllers )
         if self.listenPort:
             clist += ' ptcp:%s' % self.listenPort
-        self.cmd( 'ovs-vsctl set-controller', self, clist )
+        # Construct big ovs-vsctl command for new versions of OVS
+        if not self.isOldOVS():
+            cmd = ( 'ovs-vsctl add-br %s ' % self +
+                    '-- set Bridge %s ' % self +
+                    'other_config:datapath-id=%s ' % self.dpid +
+                    '-- set-fail-mode %s %s ' % ( self, self.failMode ) +
+                    intfs +
+                    '-- set-controller %s %s ' % ( self, clist ) )
+        # Construct ovs-vsctl commands for old versions of OVS
+        else:
+            self.cmd( 'ovs-vsctl add-br', self )
+            for intf in self.intfList():
+                if not intf.IP():
+                    self.cmd( 'ovs-vsctl add-port', self, intf )
+            cmd = ( 'ovs-vsctl set Bridge %s ' % self +
+                    'other_config:datapath-id=%s ' % self.dpid +
+                    '-- set-fail-mode %s %s ' % ( self, self.failMode ) +
+                    '-- set-controller %s %s ' % ( self, clist ) )
+        if not self.inband:
+            cmd += ( '-- set bridge %s '
+                     'other-config:disable-in-band=true ' % self )
+        if self.datapath == 'user':
+            cmd += '-- set bridge %s datapath_type=netdev ' % self
         # Reconnect quickly to controllers (1s vs. 15s max_backoff)
         for uuid in self.controllerUUIDs():
             if uuid.count( '-' ) != 4:
                 # Doesn't look like a UUID
                 continue
             uuid = uuid.strip()
-            self.cmd( 'ovs-vsctl set Controller', uuid,
-                      'max_backoff=1000' )
+            cmd += '-- set Controller %smax_backoff=1000 ' % uuid
+        # Do it!!
+        self.cmd( cmd )
+        for intf in self.intfList():
+            self.TCReapply( intf )
+
 
     def stop( self ):
         "Terminate OVS switch."
@@ -1063,8 +1155,9 @@ def stop( self ):
 class IVSSwitch(Switch):
     """IVS virtual switch"""
 
-    def __init__( self, name, **kwargs ):
+    def __init__( self, name, verbose=True, **kwargs ):
         Switch.__init__( self, name, **kwargs )
+        self.verbose = verbose
 
     @classmethod
     def setup( cls ):
@@ -1079,12 +1172,19 @@ def setup( cls ):
                    'not be loaded. Try modprobe openvswitch.\n' )
             exit( 1 )
 
+    @classmethod
+    def batchShutdown( cls, switches ):
+        "Kill each IVS switch, to be waited on later in stop()"
+        for switch in switches:
+            switch.cmd( 'kill %ivs' )
+
     def start( self, controllers ):
         "Start up a new IVS switch"
         args = ['ivs']
         args.extend( ['--name', self.name] )
         args.extend( ['--dpid', self.dpid] )
-        args.extend( ['--verbose'] )
+        if self.verbose:
+            args.extend( ['--verbose'] )
         for intf in self.intfs.values():
             if not intf.IP():
                 args.extend( ['-i', intf.name] )
@@ -1096,11 +1196,13 @@ def start( self, controllers ):
 
         logfile = '/tmp/ivs.%s.log' % self.name
 
+        self.cmd( 'ifconfig lo up' )
         self.cmd( ' '.join(args) + ' >' + logfile + ' 2>&1 </dev/null &' )
 
     def stop( self ):
         "Terminate IVS switch."
         self.cmd( 'kill %ivs' )
+        self.cmd( 'wait' )
         self.deleteIntfs()
 
     def attach( self, intf ):
@@ -1125,12 +1227,13 @@ class Controller( Node ):
 
     def __init__( self, name, inNamespace=False, command='controller',
                   cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1",
-                  port=6633, **params ):
+                  port=6633, protocol='tcp', **params ):
         self.command = command
         self.cargs = cargs
         self.cdir = cdir
         self.ip = ip
         self.port = port
+        self.protocol = protocol
         Node.__init__( self, name, inNamespace=inNamespace,
                        ip=ip, **params  )
         self.cmd( 'ifconfig lo up' )  # Shouldn't be necessary
@@ -1147,7 +1250,7 @@ def checkListening( self ):
         listening = self.cmd( "echo A | telnet -e A %s %d" %
                               ( self.ip, self.port ) )
         if 'Connected' in listening:
-            servers = self.cmd( 'netstat -atp' ).split( '\n' )
+            servers = self.cmd( 'netstat -natp' ).split( '\n' )
             pstr = ':%d ' % self.port
             clist = servers[ 0:1 ] + [ s for s in servers if pstr in s ]
             raise Exception( "Please shut down the controller which is"
@@ -1183,13 +1286,19 @@ def __repr__( self ):
         return '<%s %s: %s:%s pid=%s> ' % (
             self.__class__.__name__, self.name,
             self.IP(), self.port, self.pid )
-
+    @classmethod
+    def isAvailable( self ):
+        return quietRun( 'which controller' )
 
 class OVSController( Controller ):
     "Open vSwitch controller"
     def __init__( self, name, command='ovs-controller', **kwargs ):
+        if quietRun( 'which test-controller' ):
+            command = 'test-controller'
         Controller.__init__( self, name, command=command, **kwargs )
-
+    @classmethod
+    def isAvailable( self ):
+        return quietRun( 'which ovs-controller' ) or quietRun( 'which test-controller' )
 
 class NOX( Controller ):
     "Controller to run a NOX application."
@@ -1245,6 +1354,12 @@ def checkListening( self ):
             warn( "Unable to contact the remote controller"
                   " at %s:%d\n" % ( self.ip, self.port ) )
 
+def DefaultController( name, order=[ Controller, OVSController ], **kwargs ):
+    "find any controller that is available and run it"
+    for controller in order:
+        if controller.isAvailable():
+            return controller( name, **kwargs )
+
 class NAT( Node ):
     """NAT: Provides connectivity to external network"""
 
@@ -1311,3 +1426,4 @@ def terminate( self ):
         self.cmd( 'sysctl net.ipv4.ip_forward=0' )
 
         super( NAT, self ).terminate()
+
diff --git a/mininet/nodelib.py b/mininet/nodelib.py
new file mode 100644
index 0000000000000000000000000000000000000000..2eb80465cd005c28c9a2aaa86aa920b2cc04c02a
--- /dev/null
+++ b/mininet/nodelib.py
@@ -0,0 +1,51 @@
+"""
+Node Library for Mininet
+
+This contains additional Node types which you may find to be useful
+"""
+
+from mininet.net import Mininet
+from mininet.topo import Topo
+from mininet.node import Switch
+from mininet.log import setLogLevel, info
+
+
+class LinuxBridge( Switch ):
+    "Linux Bridge (with optional spanning tree)"
+
+    nextPrio = 100  # next bridge priority for spanning tree
+
+    def __init__( self, name, stp=False, prio=None, **kwargs ):
+        """stp: use spanning tree protocol? (default False)
+           prio: optional explicit bridge priority for STP"""
+        self.stp = stp
+        if prio:
+            self.prio = prio
+        else:
+            self.prio = LinuxBridge.nextPrio
+            LinuxBridge.nextPrio += 1
+        Switch.__init__( self, name, **kwargs )
+
+    def connected( self ):
+        "Are we forwarding yet?"
+        if self.stp:
+            return 'forwarding' in self.cmd( 'brctl showstp', self )
+        else:
+            return True
+    
+    def start( self, controllers ):
+        self.cmd( 'ifconfig', self, 'down' )
+        self.cmd( 'brctl delbr', self )
+        self.cmd( 'brctl addbr', self )
+        if self.stp:
+            self.cmd( 'brctl setbridgeprio', self.prio )
+            self.cmd( 'brctl stp', self, 'on' )
+        for i in self.intfList():
+            if self.name in i.name:
+                self.cmd( 'brctl addif', self, i )
+        self.cmd( 'ifconfig', self, 'up' )
+
+    def stop( self ):
+        self.cmd( 'ifconfig', self, 'down' )
+        self.cmd( 'brctl delbr', self )
+
diff --git a/mininet/test/test_hifi.py b/mininet/test/test_hifi.py
index c9d288b3c1cdcfca87d4ad788871d305ad38ae8b..c888e29620a4edfd4aea6468feff43be11c44ba8 100755
--- a/mininet/test/test_hifi.py
+++ b/mininet/test/test_hifi.py
@@ -53,8 +53,11 @@ def assertWithinTolerance(self, measured, expected, tolerance_frac):
         """Check that a given value is within a tolerance of expected
         tolerance_frac: less-than-1.0 value; 0.8 would yield 20% tolerance.
         """
-        self.assertTrue( float(measured) >= float(expected) * tolerance_frac )
-        self.assertTrue( float(measured) >= float(expected) * tolerance_frac )
+        self.assertGreaterEqual( float(measured),
+                                 float(expected) * tolerance_frac )
+        self.assertLessEqual( float( measured ),
+                                 float(expected) + (1-tolerance_frac)
+                                 * float( expected ) )
 
     def testCPULimits( self ):
         "Verify topology creation with CPU limits set for both schedulers."
@@ -68,19 +71,20 @@ def testCPULimits( self ):
         mn.start()
         results = mn.runCpuLimitTest( cpu=CPU_FRACTION )
         mn.stop()
-        for cpu in results:
-            self.assertWithinTolerance( cpu, CPU_FRACTION, CPU_TOLERANCE )
+        for pct in results:
+            #divide cpu by 100 to convert from percentage to fraction
+            self.assertWithinTolerance( pct/100, CPU_FRACTION, CPU_TOLERANCE )
 
     def testLinkBandwidth( self ):
         "Verify that link bandwidths are accurate within a bound."
-        BW = 5  # Mbps
+        BW = .5  # Mbps
         BW_TOLERANCE = 0.8  # BW fraction below which test should fail
         # Verify ability to create limited-link topo first;
         lopts = { 'bw': BW, 'use_htb': True }
         # Also verify correctness of limit limitng within a bound.
         mn = Mininet( SingleSwitchOptionsTopo( n=N, lopts=lopts ),
                       link=TCLink, switch=self.switchClass )
-        bw_strs = mn.run( mn.iperf )
+        bw_strs = mn.run( mn.iperf, format='m' )
         for bw_str in bw_strs:
             bw = float( bw_str.split(' ')[0] )
             self.assertWithinTolerance( bw, BW, BW_TOLERANCE )
@@ -91,7 +95,7 @@ def testLinkDelay( self ):
         DELAY_TOLERANCE = 0.8  # Delay fraction below which test should fail
         lopts = { 'delay': '%sms' % DELAY_MS, 'use_htb': True }
         mn = Mininet( SingleSwitchOptionsTopo( n=N, lopts=lopts ),
-                      link=TCLink, switch=self.switchClass )
+                      link=TCLink, switch=self.switchClass, autoStaticArp=True )
         ping_delays = mn.run( mn.pingFull )
         test_outputs = ping_delays[0]
         # Ignore unused variables below
@@ -102,9 +106,10 @@ def testLinkDelay( self ):
         # pylint: enable-msg=W0612
         for rttval in [rttmin, rttavg, rttmax]:
             # Multiply delay by 4 to cover there & back on two links
-            self.assertWithinTolerance( rttval, DELAY_MS * 4.0,
+            self.assertWithinTolerance( rttval, DELAY_MS * 4.0, 
                                         DELAY_TOLERANCE)
 
+
     def testLinkLoss( self ):
         "Verify that we see packet drops with a high configured loss rate."
         LOSS_PERCENT = 99
@@ -120,7 +125,7 @@ def testLinkLoss( self ):
         for _ in range(REPS):
             dropped_total += mn.ping(timeout='1')
         mn.stop()
-        self.assertTrue(dropped_total > 0)
+        self.assertGreater( dropped_total, 0 )
 
     def testMostOptions( self ):
         "Verify topology creation with most link options and CPU limits."
diff --git a/mininet/test/test_nets.py b/mininet/test/test_nets.py
index 159ba348c46831fe4e48f020f07bbd79d40d4744..330e9a88a6c69e336f95467bf99e2b9bb9b31e69 100755
--- a/mininet/test/test_nets.py
+++ b/mininet/test/test_nets.py
@@ -66,7 +66,7 @@ class testLinearCommon( object ):
 
     def testLinear5( self ):
         "Ping test on a 5-switch topology"
-        mn = Mininet( LinearTopo( k=5 ), self.switchClass, Host, Controller )
+        mn = Mininet( LinearTopo( k=5 ), self.switchClass, Host, Controller, waitConnected=True )
         dropped = mn.run( mn.ping )
         self.assertEqual( dropped, 0 )
 
diff --git a/mininet/test/test_walkthrough.py b/mininet/test/test_walkthrough.py
index 6fa1ce07f7d07326b6787d557249ad39dcdc2296..2c2d6b0d9e74f2f34bf85202ec8b790b9dade983 100755
--- a/mininet/test/test_walkthrough.py
+++ b/mininet/test/test_walkthrough.py
@@ -9,7 +9,6 @@
 import unittest
 import pexpect
 import os
-from time import sleep
 from mininet.util import quietRun
 
 class testWalkthrough( unittest.TestCase ):
diff --git a/mininet/topo.py b/mininet/topo.py
index 2ab455cd5cbf6395e1a454b3053f656a13dc8d94..cc458fd87602e53dc0afab9616d098ef514f4167 100644
--- a/mininet/topo.py
+++ b/mininet/topo.py
@@ -48,18 +48,25 @@ def __getitem__( self, node ):
 class Topo(object):
     "Data center network representation for structured multi-trees."
 
-    def __init__(self, hopts=None, sopts=None, lopts=None):
-        """Topo object:
+    def __init__(self, *args, **params):
+        """Topo object. 
+           Optional named parameters:
            hinfo: default host options
            sopts: default switch options
-           lopts: default link options"""
+           lopts: default link options
+           calls build()"""
         self.g = MultiGraph()
         self.node_info = {}
         self.link_info = {}  # (src, dst) tuples hash to EdgeInfo objects
-        self.hopts = {} if hopts is None else hopts
-        self.sopts = {} if sopts is None else sopts
-        self.lopts = {} if lopts is None else lopts
+        self.hopts = params.pop( 'hopts', {} )
+        self.sopts = params.pop( 'sopts', {} )
+        self.lopts = params.pop( 'lopts', {} )
         self.ports = {}  # ports[src][dst] is port on src that connects to dst
+        self.build( *args, **params )
+
+    def build( self, *args, **params ):
+        "Override this method to build your topology."
+        pass
 
     def addNode(self, name, **opts):
         """Add Node to graph.
@@ -168,7 +175,7 @@ def port(self, src, dst):
         '''
         if src in self.ports and dst in self.ports[src]:
             assert dst in self.ports and src in self.ports[dst]
-            return (self.ports[src][dst], self.ports[dst][src])
+            return self.ports[src][dst], self.ports[dst][src]
 
     def linkInfo( self, src, dst ):
         "Return link metadata"
@@ -194,76 +201,56 @@ def sorted( items ):
         "Items sorted in natural (i.e. alphabetical) order"
         return sorted(items, key=natural)
 
-class SingleSwitchTopo(Topo):
-    '''Single switch connected to k hosts.'''
-
-    def __init__(self, k=2, **opts):
-        '''Init.
 
-        @param k number of hosts
-        @param enable_all enables all nodes and switches?
-        '''
-        super(SingleSwitchTopo, self).__init__(**opts)
+class SingleSwitchTopo( Topo ):
+    "Single switch connected to k hosts."
 
+    def build( self, k=2, **opts ):
+        "k: number of hosts"
         self.k = k
+        switch = self.addSwitch( 's1' )
+        for h in irange( 1, k ):
+            host = self.addHost( 'h%s' % h )
+            self.addLink( host, switch )
 
-        switch = self.addSwitch('s1')
-        for h in irange(1, k):
-            host = self.addHost('h%s' % h)
-            self.addLink(host, switch)
-
-
-class SingleSwitchReversedTopo(Topo):
-    '''Single switch connected to k hosts, with reversed ports.
 
-    The lowest-numbered host is connected to the highest-numbered port.
+class SingleSwitchReversedTopo( Topo ):
+    """Single switch connected to k hosts, with reversed ports.
+       The lowest-numbered host is connected to the highest-numbered port.
+       Useful to verify that Mininet properly handles custom port numberings."""
 
-    Useful to verify that Mininet properly handles custom port numberings.
-    '''
-    def __init__(self, k=2, **opts):
-        '''Init.
-
-        @param k number of hosts
-        @param enable_all enables all nodes and switches?
-        '''
-        super(SingleSwitchReversedTopo, self).__init__(**opts)
+    def build( self, k=2 ):
+        "k: number of hosts"
         self.k = k
-        switch = self.addSwitch('s1')
-        for h in irange(1, k):
-            host = self.addHost('h%s' % h)
-            self.addLink(host, switch,
-                         port1=0, port2=(k - h + 1))
+        switch = self.addSwitch( 's1' )
+        for h in irange( 1, k ):
+            host = self.addHost( 'h%s' % h )
+            self.addLink( host, switch,
+                          port1=0, port2=( k - h + 1 ) )
 
-class LinearTopo(Topo):
+class LinearTopo( Topo ):
     "Linear topology of k switches, with n hosts per switch."
 
-    def __init__(self, k=2, n=1, **opts):
-        """Init.
-           k: number of switches
-           n: number of hosts per switch
-           hconf: host configuration options
-           lconf: link configuration options"""
-
-        super(LinearTopo, self).__init__(**opts)
-
+    def build( self, k=2, n=1, **opts):
+        """k: number of switches
+           n: number of hosts per switch"""
         self.k = k
         self.n = n
 
         if n == 1:
             genHostName = lambda i, j: 'h%s' % i
         else:
-            genHostName = lambda i, j: 'h%ss%d' % (j, i)
-
+            genHostName = lambda i, j: 'h%ss%d' % ( j, i )
 
         lastSwitch = None
-        for i in irange(1, k):
+        for i in irange( 1, k ):
             # Add switch
-            switch = self.addSwitch('s%s' % i)
+            switch = self.addSwitch( 's%s' % i )
             # Add hosts to switch
-            for j in irange(1, n):
-                host = self.addHost(genHostName(i, j))
-                self.addLink(host, switch)
+            for j in irange( 1, n ):
+                host = self.addHost( genHostName( i, j ) )
+                self.addLink( host, switch )
             # Connect switch to previous
             if lastSwitch:
-                self.addLink(switch, lastSwitch)
+                self.addLink( switch, lastSwitch )
             lastSwitch = switch
diff --git a/mininet/topolib.py b/mininet/topolib.py
index 63ba36deb3d51e5a246545e7bfe8782ed4a3848e..6ee6f0cdb58215b1d23c9046db7e58742c5b5809 100644
--- a/mininet/topolib.py
+++ b/mininet/topolib.py
@@ -6,8 +6,7 @@
 class TreeTopo( Topo ):
     "Topology for a tree network with a given depth and fanout."
 
-    def __init__( self, depth=1, fanout=2 ):
-        super( TreeTopo, self ).__init__()
+    def build( self, depth=1, fanout=2 ):
         # Numbering:  h1..N, s1..M
         self.hostNum = 1
         self.switchNum = 1
@@ -34,3 +33,37 @@ def TreeNet( depth=1, fanout=2, **kwargs ):
     "Convenience function for creating tree networks."
     topo = TreeTopo( depth, fanout )
     return Mininet( topo, **kwargs )
+
+
+class TorusTopo( Topo ):
+    """2-D Torus topology
+       WARNING: this topology has LOOPS and WILL NOT WORK
+       with the default controller or any Ethernet bridge
+       without STP turned on! It can be used with STP, e.g.:
+       # mn --topo torus,3,3 --switch lxbr,stp=1 --test pingall"""
+    
+    def build( self, x, y ):
+        if x < 3 or y < 3:
+            raise Exception( 'Please use 3x3 or greater for compatibility '
+                            'with 2.1' )
+        hosts, switches, dpid = {}, {}, 0
+        # Create and wire interior
+        for i in range( 0, x ):
+            for j in range( 0, y ):
+                loc = '%dx%d' % ( i + 1, j + 1 )
+                # dpid cannot be zero for OVS
+                dpid = ( i + 1 ) * 256 + ( j + 1 )
+                switch = switches[ i, j ] = self.addSwitch( 's' + loc, dpid='%016x' % dpid )
+                host = hosts[ i, j ] = self.addHost( 'h' + loc )
+                self.addLink( host, switch )
+        # Connect switches
+        for i in range( 0, x ):
+            for j in range( 0, y ):
+                sw1 = switches[ i, j ]
+                sw2 = switches[ i, ( j + 1 ) % y ]
+                sw3 = switches[ ( i + 1 ) % x, j ]
+                self.addLink( sw1, sw2 )
+                self.addLink( sw1, sw3 )
+
+    
+
diff --git a/mininet/util.py b/mininet/util.py
index 1b6440bbd33ee1384de547c62e61841023597330..5cb27f449d5ecad2dc62691c218307ab97ae18d0 100644
--- a/mininet/util.py
+++ b/mininet/util.py
@@ -155,7 +155,12 @@ def makeIntfPair( intf1, intf2 ):
     quietRun( 'ip link del ' + intf2 )
     # Create new pair
     cmd = 'ip link add name ' + intf1 + ' type veth peer name ' + intf2
-    return quietRun( cmd )
+    cmdOutput = quietRun( cmd )
+    if cmdOutput == '':
+        return True
+    else:
+        error( "Error creating interface pair: %s " % cmdOutput )
+        return False
 
 def retry( retries, delaySecs, fn, *args, **keywords ):
     """Try something several times before giving up.
@@ -183,8 +188,7 @@ def moveIntfNoRetry( intf, dstNode, srcNode=None, printError=False ):
         srcNode.cmd( cmd )
     else:
         quietRun( cmd )
-    links = dstNode.cmd( 'ip link show' )
-    if not ( ' %s:' % intf ) in links:
+    if ( ' %s:' % intf ) not in dstNode.cmd( 'ip link show', intf ):
         if printError:
             error( '*** Error: moveIntf: ' + intf +
                    ' not successfully moved to ' + dstNode.name + '\n' )
@@ -269,7 +273,7 @@ def ipAdd( i, prefixLen=8, ipBaseNum=0x0a000000 ):
        ipBaseNum: option base IP address as int
        returns IP address as string"""
     imax = 0xffffffff >> prefixLen
-    assert i <= imax
+    assert i <= imax, 'Not enough IP addresses in the subnet'
     mask = 0xffffffff ^ imax
     ipnum = ( ipBaseNum & mask ) + i
     return ipStr( ipnum )
@@ -277,6 +281,8 @@ def ipAdd( i, prefixLen=8, ipBaseNum=0x0a000000 ):
 def ipParse( ip ):
     "Parse an IP address and return an unsigned int."
     args = [ int( arg ) for arg in ip.split( '.' ) ]
+    while ( len(args) < 4 ):
+        args.append( 0 )
     return ipNum( *args )
 
 def netParse( ipstr ):
@@ -286,6 +292,10 @@ def netParse( ipstr ):
     if '/' in ipstr:
         ip, pf = ipstr.split( '/' )
         prefixLen = int( pf )
+    #if no prefix is specified, set the prefix to 24
+    else:
+        ip = ipstr
+        prefixLen = 24
     return ipParse( ip ), prefixLen
 
 def checkInt( s ):
diff --git a/mnexec.c b/mnexec.c
index a56349950b5b8f9b231f409709e1793a830b63b4..c7103d4670d72e82364d8eef349cd1bc89f0b556 100644
--- a/mnexec.c
+++ b/mnexec.c
@@ -5,7 +5,7 @@
  *
  *  - closing all file descriptors except stdin/out/error
  *  - detaching from a controlling tty using setsid
- *  - running in a network namespace
+ *  - running in network and mount namespaces
  *  - printing out the pid of a process so we can identify it later
  *  - attaching to a namespace and cgroup
  *  - setting RT scheduling
@@ -13,6 +13,7 @@
  * Partially based on public domain setsid(1)
 */
 
+#define _GNU_SOURCE
 #include <stdio.h>
 #include <linux/sched.h>
 #include <unistd.h>
@@ -20,8 +21,9 @@
 #include <syscall.h>
 #include <fcntl.h>
 #include <stdlib.h>
-#include <limits.h>
 #include <sched.h>
+#include <ctype.h>
+#include <sys/mount.h>
 
 #if !defined(VERSION)
 #define VERSION "(devel)"
@@ -34,9 +36,9 @@ void usage(char *name)
            "Options:\n"
            "  -c: close all file descriptors except stdin/out/error\n"
            "  -d: detach from tty by calling setsid()\n"
-           "  -n: run in new network namespace\n"
+           "  -n: run in new network and mount namespaces\n"
            "  -p: print ^A + pid\n"
-           "  -a pid: attach to pid's network namespace\n"
+           "  -a pid: attach to pid's network and mount namespaces\n"
            "  -g group: add to cgroup\n"
            "  -r rtprio: run with SCHED_RR (usually requires -g)\n"
            "  -v: print version\n",
@@ -62,7 +64,7 @@ void validate(char *path)
 }
 
 /* Add our pid to cgroup */
-int cgroup(char *gname)
+void cgroup(char *gname)
 {
     static char path[PATH_MAX];
     static char *groups[] = {
@@ -121,11 +123,16 @@ int main(int argc, char *argv[])
             setsid();
             break;
         case 'n':
-            /* run in network namespace */
-            if (unshare(CLONE_NEWNET) == -1) {
+            /* run in network and mount namespaces */
+            if (unshare(CLONE_NEWNET|CLONE_NEWNS) == -1) {
                 perror("unshare");
                 return 1;
             }
+            /* mount sysfs to pick up the new network namespace */
+            if (mount("sysfs", "/sys", "sysfs", MS_MGC_VAL, NULL) == -1) {
+                perror("mount");
+                return 1;
+            }
             break;
         case 'p':
             /* print pid */
@@ -133,7 +140,7 @@ int main(int argc, char *argv[])
             fflush(stdout);
             break;
         case 'a':
-            /* Attach to pid's network namespace */
+            /* Attach to pid's network namespace and mount namespace*/
             pid = atoi(optarg);
             sprintf(path, "/proc/%d/ns/net", pid );
             nsid = open(path, O_RDONLY);
@@ -145,6 +152,16 @@ int main(int argc, char *argv[])
                 perror("setns");
                 return 1;
             }
+            sprintf(path, "/proc/%d/ns/mnt", pid );
+            nsid = open(path, O_RDONLY);
+            if (nsid < 0) {
+                perror(path);
+                return 1;
+            }
+            if (setns(nsid, 0) != 0) {
+                perror("setns");
+                return 1;
+            }
             break;
         case 'g':
             /* Attach to cgroup */
@@ -176,4 +193,6 @@ int main(int argc, char *argv[])
     }
     
     usage(argv[0]);
+
+    return 0;
 }
diff --git a/util/install.sh b/util/install.sh
index 59e9bbf49ab0c1afb3448ffab0ac8c5b9ae74276..b1df4a1ea2e7d8bdc37a6a51dcfa9a1b7fca30a8 100755
--- a/util/install.sh
+++ b/util/install.sh
@@ -63,22 +63,11 @@ echo "Detected Linux distribution: $DIST $RELEASE $CODENAME $ARCH"
 
 # Kernel params
 
-if [ "$DIST" = "Ubuntu" ]; then
-    if [ "$RELEASE" = "10.04" ]; then
-        KERNEL_NAME='3.0.0-15-generic'
-    else
-        KERNEL_NAME=`uname -r`
-    fi
-    KERNEL_HEADERS=linux-headers-${KERNEL_NAME}
-elif [ "$DIST" = "Debian" ] && [ "$ARCH" = "i386" ] && [ "$CODENAME" = "lenny" ]; then
-    KERNEL_NAME=2.6.33.1-mininet
-    KERNEL_HEADERS=linux-headers-${KERNEL_NAME}_${KERNEL_NAME}-10.00.Custom_i386.deb
-    KERNEL_IMAGE=linux-image-${KERNEL_NAME}_${KERNEL_NAME}-10.00.Custom_i386.deb
-elif [ "$DIST" = "Fedora" ]; then
-    KERNEL_NAME=`uname -r`
-    KERNEL_HEADERS=kernel-headers-${KERNEL_NAME}
-else
-    echo "Install.sh currently only supports Ubuntu, Debian Lenny i386 and Fedora."
+KERNEL_NAME=`uname -r`
+KERNEL_HEADERS=kernel-headers-${KERNEL_NAME}
+
+if ! echo $DIST | egrep 'Ubuntu|Debian|Fedora'; then
+    echo "Install.sh currently only supports Ubuntu, Debian and Fedora."
     exit 1
 fi
 
@@ -96,8 +85,6 @@ OVS_BUILDSUFFIX=-ignore # was -2
 OVS_PACKAGE_NAME=ovs-$OVS_RELEASE-core-$DIST_LC-$RELEASE-$ARCH$OVS_BUILDSUFFIX.tar
 OVS_TAG=v$OVS_RELEASE
 
-IVS_TAG=v0.3
-
 # Command-line versions overrides that simplify custom VM creation
 # To use, pass in the vars on the cmd line before install.sh, e.g.
 # WS_DISSECTOR_REV=pre-ws-1.10.0 install.sh -w
@@ -108,31 +95,7 @@ OF13_SWITCH_REV=${OF13_SWITCH_REV:-""}
 function kernel {
     echo "Install Mininet-compatible kernel if necessary"
     sudo apt-get update
-    if [ "$DIST" = "Ubuntu" ] &&  [ "$RELEASE" = "10.04" ]; then
-        $install linux-image-$KERNEL_NAME
-    elif [ "$DIST" = "Debian" ]; then
-        # The easy approach: download pre-built linux-image and linux-headers packages:
-        wget -c $KERNEL_LOC/$KERNEL_HEADERS
-        wget -c $KERNEL_LOC/$KERNEL_IMAGE
-
-        # Install custom linux headers and image:
-        $pkginst $KERNEL_IMAGE $KERNEL_HEADERS
-
-        # The next two steps are to work around a bug in newer versions of
-        # kernel-package, which fails to add initrd images with the latest kernels.
-        # See http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=525032
-        # Generate initrd image if the .deb didn't install it:
-        if ! test -e /boot/initrd.img-${KERNEL_NAME}; then
-            sudo update-initramfs -c -k ${KERNEL_NAME}
-        fi
-
-        # Ensure /boot/grub/menu.lst boots with initrd image:
-        sudo update-grub
-
-        # The default should be the new kernel. Otherwise, you may need to modify
-        # /boot/grub/menu.lst to set the default to the entry corresponding to the
-        # kernel you just installed.
-    fi
+    $install linux-image-$KERNEL_NAME
 }
 
 function kernel_clean {
@@ -176,10 +139,9 @@ function mn_dev {
 # -user switch
 # The instructions below are an abbreviated version from
 # http://www.openflowswitch.org/wk/index.php/Debian_Install
-# ... modified to use Debian Lenny rather than unstable.
 function of {
     echo "Installing OpenFlow reference implementation..."
-    cd $BUILD_DIR/
+    cd $BUILD_DIR
     $install autoconf automake libtool make gcc
     if [ "$DIST" = "Fedora" ]; then
         $install git pkgconfig glibc-devel
@@ -300,111 +262,95 @@ function wireshark {
 }
 
 
-# Install Open vSwitch
-# Instructions derived from OVS INSTALL, INSTALL.OpenFlow and README files.
-
-function ovs {
-    echo "Installing Open vSwitch..."
-
-    if [ "$DIST" = "Fedora" ]; then
-        # Just install Fedora's openvswitch RPMS
-        $install openvswitch openvswitch-controller
-        return
-    fi
+# Install Open vSwitch specific version Ubuntu package
+function ubuntuOvs {
+    echo "Creating and Installing Open vSwitch packages..."
 
     OVS_SRC=$BUILD_DIR/openvswitch
-    OVS_BUILD=$OVS_SRC/build-$KERNEL_NAME
-    OVS_KMODS=($OVS_BUILD/datapath/linux/{openvswitch_mod.ko,brcompat_mod.ko})
-
-    # Required for module build/dkms install
-    $install $KERNEL_HEADERS
-
-    ovspresent=0
-
-    # First see if we have packages
-    # XXX wget -c seems to fail from github/amazon s3
-    cd /tmp
-    if wget $OVS_PACKAGE_LOC/$OVS_PACKAGE_NAME 2> /dev/null; then
-	$install patch dkms fakeroot python-argparse
-        tar xf $OVS_PACKAGE_NAME
-        orig=`tar tf $OVS_PACKAGE_NAME`
-        # Now install packages in reasonable dependency order
-        order='dkms common pki openvswitch-switch brcompat controller'
-        pkgs=""
-        for p in $order; do
-            pkg=`echo "$orig" | grep $p`
-	    # Annoyingly, things seem to be missing without this flag
-            $pkginst --force-confmiss $pkg
-        done
-        ovspresent=1
-    fi
+    OVS_TARBALL_LOC=http://openvswitch.org/releases
 
-    # Otherwise try distribution's OVS packages
-    if [ "$DIST" = "Ubuntu" ] && [ `expr $RELEASE '>=' 11.10` = 1 ]; then
-        if ! dpkg --get-selections | grep openvswitch-datapath; then
-            # If you've already installed a datapath, assume you
-            # know what you're doing and don't need dkms datapath.
-            # Otherwise, install it.
-            $install openvswitch-datapath-dkms
+    if [ "$DIST" = "Ubuntu" ] && [ `expr $RELEASE '>=' 12.04` = 1 ]; then
+        rm -rf $OVS_SRC
+        mkdir -p $OVS_SRC
+        cd $OVS_SRC
+
+        if wget $OVS_TARBALL_LOC/openvswitch-$OVS_RELEASE.tar.gz 2> /dev/null; then
+            tar xzf openvswitch-$OVS_RELEASE.tar.gz
+        else
+            echo "Failed to find OVS at $OVS_TARBALL_LOC/openvswitch-$OVS_RELEASE.tar.gz"
+            cd $BUILD_DIR
+            return
         fi
-	if $install openvswitch-switch openvswitch-controller; then
+
+        # Remove any old packages
+        $remove openvswitch-common openvswitch-datapath-dkms openvswitch-controller \
+                openvswitch-pki openvswitch-switch
+
+        # Get build deps
+        $install build-essential fakeroot debhelper autoconf automake libssl-dev \
+                 pkg-config bzip2 openssl python-all procps python-qt4 \
+                 python-zopeinterface python-twisted-conch dkms
+
+        # Build OVS
+        cd $BUILD_DIR/openvswitch/openvswitch-$OVS_RELEASE
+                DEB_BUILD_OPTIONS='parallel=2 nocheck' fakeroot debian/rules binary
+        cd ..
+        $pkginst openvswitch-common_$OVS_RELEASE*.deb openvswitch-datapath-dkms_$OVS_RELEASE*.deb \
+                 openvswitch-pki_$OVS_RELEASE*.deb openvswitch-switch_$OVS_RELEASE*.deb
+        if $pkginst openvswitch-controller_$OVS_RELEASE*.deb; then
             echo "Ignoring error installing openvswitch-controller"
         fi
-        ovspresent=1
-    fi
 
-    # Switch can run on its own, but
-    # Mininet should control the controller
-    if [ -e /etc/init.d/openvswitch-controller ]; then
+        modinfo openvswitch
+        sudo ovs-vsctl show
+        # Switch can run on its own, but
+        # Mininet should control the controller
+        # This appears to only be an issue on Ubuntu/Debian
         if sudo service openvswitch-controller stop; then
             echo "Stopped running controller"
         fi
-        sudo update-rc.d openvswitch-controller disable
+        if [ -e /etc/init.d/openvswitch-controller ]; then
+            sudo update-rc.d openvswitch-controller disable
+        fi
+    else
+        echo "Failed to install Open vSwitch.  OS must be Ubuntu >= 12.04"
+            cd $BUILD_DIR
+            return
     fi
+}
 
-    if [ $ovspresent = 1 ]; then
-        echo "Done (hopefully) installing packages"
-        cd $BUILD_DIR
-        return
-    fi
 
-    # Otherwise attempt to install from source
+# Install Open vSwitch
 
-    $install pkg-config gcc make python-dev libssl-dev libtool
+function ovs {
+    echo "Installing Open vSwitch..."
 
-    if [ "$DIST" = "Debian" ]; then
-        if [ "$CODENAME" = "lenny" ]; then
-            $install git-core
-            # Install Autoconf 2.63+ backport from Debian Backports repo:
-            # Instructions from http://backports.org/dokuwiki/doku.php?id=instructions
-            sudo su -c "echo 'deb http://www.backports.org/debian lenny-backports main contrib non-free' >> /etc/apt/sources.list"
-            sudo apt-get update
-            sudo apt-get -y --force-yes install debian-backports-keyring
-            sudo apt-get -y --force-yes -t lenny-backports install autoconf
-        fi
-    else
-        $install git
+    if [ "$DIST" == "Fedora" ]; then
+        $install openvswitch openvswitch-controller
+        return
     fi
 
-    # Install OVS from release
-    cd $BUILD_DIR/
-    git clone git://openvswitch.org/openvswitch $OVS_SRC
-    cd $OVS_SRC
-    git checkout $OVS_TAG
-    ./boot.sh
-    BUILDDIR=/lib/modules/${KERNEL_NAME}/build
-    if [ ! -e $BUILDDIR ]; then
-        echo "Creating build sdirectory $BUILDDIR"
-        sudo mkdir -p $BUILDDIR
+    # Manually installing openvswitch-datapath may be necessary
+    # for manually built kernel .debs using Debian's defective kernel
+    # packaging, which doesn't yield usable headers.
+    if ! dpkg --get-selections | grep openvswitch-datapath; then
+        # If you've already installed a datapath, assume you
+        # know what you're doing and don't need dkms datapath.
+        # Otherwise, install it.
+        $install openvswitch-datapath-dkms
     fi
-    opts="--with-linux=$BUILDDIR"
-    mkdir -p $OVS_BUILD
-    cd $OVS_BUILD
-    ../configure $opts
-    make
-    sudo make install
 
-    modprobe
+    $install openvswitch-switch openvswitch-controller
+
+    # Switch can run on its own, but
+    # Mininet should control the controller
+    # This appears to only be an issue on Ubuntu/Debian
+    if sudo service openvswitch-controller stop; then
+        echo "Stopped running controller"
+    fi
+    if [ -e /etc/init.d/openvswitch-controller ]; then
+        sudo update-rc.d openvswitch-controller disable
+    fi
 }
 
 function remove_ovs {
@@ -438,7 +384,7 @@ function ivs {
 
     # Install IVS from source
     cd $BUILD_DIR
-    git clone git://github.com/floodlight/ivs $IVS_SRC -b $IVS_TAG --recursive
+    git clone git://github.com/floodlight/ivs $IVS_SRC --recursive
     cd $IVS_SRC
     make
     sudo make install
@@ -555,7 +501,7 @@ function cbench {
         $install libsnmp-dev libpcap-dev libconfig-dev
     fi
     cd $BUILD_DIR/
-    git clone git://openflow.org/oflops.git
+    git clone git://gitosis.stanford.edu/oflops.git
     cd oflops
     sh boot.sh || true # possible error in autoreconf, so run twice
     sh boot.sh
@@ -706,7 +652,7 @@ function vm_clean {
 }
 
 function usage {
-    printf '\nUsage: %s [-abcdfhikmnprtvwx03]\n\n' $(basename $0) >&2
+    printf '\nUsage: %s [-abcdfhikmnprtvVwx03]\n\n' $(basename $0) >&2
 
     printf 'This install script attempts to install useful packages\n' >&2
     printf 'for Mininet. It should (hopefully) work on Ubuntu 11.10+\n' >&2
@@ -731,6 +677,7 @@ function usage {
     printf -- ' -s <dir>: place dependency (S)ource/build trees in <dir>\n' >&2
     printf -- ' -t: complete o(T)her Mininet VM setup tasks\n' >&2
     printf -- ' -v: install Open (V)switch\n' >&2
+    printf -- ' -V <version>: install a particular version of Open (V)switch on Ubuntu\n' >&2
     printf -- ' -w: install OpenFlow (W)ireshark dissector\n' >&2
     printf -- ' -x: install NO(X) Classic OpenFlow controller\n' >&2
     printf -- ' -0: (default) -0[fx] installs OpenFlow 1.0 versions\n' >&2
@@ -744,7 +691,7 @@ if [ $# -eq 0 ]
 then
     all
 else
-    while getopts 'abcdefhikmnprs:tvwx03' OPTION
+    while getopts 'abcdefhikmnprs:tvV:wx03' OPTION
     do
       case $OPTION in
       a)    all;;
@@ -769,6 +716,8 @@ else
             echo "Dependency installation directory: $BUILD_DIR";;
       t)    vm_other;;
       v)    ovs;;
+      V)    OVS_RELEASE=$OPTARG;
+            ubuntuOvs;;
       w)    wireshark;;
       x)    case $OF_VERSION in
             1.0) nox;;
diff --git a/util/versioncheck.py b/util/versioncheck.py
index d9e54831062e2e0192ca68fdb29d62867ed673f1..7180f4935f56a5f622990839e1b44e30e6cc47a5 100755
--- a/util/versioncheck.py
+++ b/util/versioncheck.py
@@ -8,7 +8,7 @@
 version = version.strip()
 
 # Find all Mininet path references
-lines = co( "grep -or 'Mininet \w\.\w\.\w\w*' *", shell=True )
+lines = co( "grep -or 'Mininet \w\+\.\w\+\.\w\+[+]*' *", shell=True )
 
 error = False
 
diff --git a/util/vm/build.py b/util/vm/build.py
index eda70e451f82fa629c4ce6e6f1e7c9b49adbea3e..ad454f0ddfbd3df163202cd352ebad1f480a3abc 100755
--- a/util/vm/build.py
+++ b/util/vm/build.py
@@ -83,6 +83,18 @@
     'saucy64server':
     'http://mirrors.kernel.org/ubuntu-releases/13.10/'
     'ubuntu-13.10-server-amd64.iso',
+    'trusty32server':
+    'http://mirrors.kernel.org/ubuntu-releases/14.04/'
+    'ubuntu-14.04-server-i386.iso',
+    'trusty64server':
+    'http://mirrors.kernel.org/ubuntu-releases/14.04/'
+    'ubuntu-14.04-server-amd64.iso',
+    'utopic32server':
+    'http://mirrors.kernel.org/ubuntu-releases/14.10/'
+    'ubuntu-14.10-server-i386.iso',
+    'utopic64server':
+    'http://mirrors.kernel.org/ubuntu-releases/14.10/'
+    'ubuntu-14.10-server-amd64.iso',
 }
 
 
@@ -91,6 +103,18 @@ def OSVersion( flavor ):
     urlbase = path.basename( isoURLs.get( flavor, 'unknown' ) )
     return path.splitext( urlbase )[ 0 ]
 
+def OVFOSNameID( flavor ):
+    "Return OVF-specified ( OS Name, ID ) for flavor"
+    version = OSVersion( flavor )
+    arch = archFor( flavor )
+    if 'ubuntu' in version:
+        map = { 'i386': ( 'Ubuntu', 93 ),
+                'x86_64': ( 'Ubuntu 64-bit', 94 ) }
+    else:
+        map = { 'i386': ( 'Linux', 36 ),
+                'x86_64': ( 'Linux 64-bit', 101 ) }
+    osname, osid = map[ arch ]
+    return osname, osid
 
 LogStartTime = time()
 LogFile = None
@@ -140,8 +164,7 @@ def depend():
     run( 'sudo apt-get -y update' )
     run( 'sudo apt-get install -y'
          ' kvm cloud-utils genisoimage qemu-kvm qemu-utils'
-         ' e2fsprogs '
-         ' landscape-client'
+         ' e2fsprogs dnsmasq curl'
          ' python-setuptools mtools zip' )
     run( 'sudo easy_install pexpect' )
 
@@ -440,16 +463,16 @@ def boot( cow, kernel, initrd, logfile, memory=1024 ):
     return vm
 
 
-def login( vm ):
+def login( vm, user='mininet', password='mininet' ):
     "Log in to vm (pexpect object)"
     log( '* Waiting for login prompt' )
     vm.expect( 'login: ' )
     log( '* Logging in' )
-    vm.sendline( 'mininet' )
+    vm.sendline( user )
     log( '* Waiting for password prompt' )
     vm.expect( 'Password: ' )
     log( '* Sending password' )
-    vm.sendline( 'mininet' )
+    vm.sendline( password )
     log( '* Waiting for login...' )
 
 
@@ -485,6 +508,10 @@ def coreTest( vm, prompt=Prompt ):
             log( '* Test', test, 'output:' )
             log( vm.before )
 
+def noneTest( vm ):
+    "This test does nothing"
+    vm.sendline( 'echo' )
+
 def examplesquickTest( vm, prompt=Prompt ):
     "Quick test of mininet examples"
     vm.sendline( 'sudo apt-get install python-pexpect' )
@@ -507,8 +534,13 @@ def walkthroughTest( vm, prompt=Prompt ):
 
 
 def checkOutBranch( vm, branch, prompt=Prompt ):
-    vm.sendline( 'cd ~/mininet; git fetch; git pull --rebase; git checkout '
-                 + branch )
+    # This is a bit subtle; it will check out an existing branch (e.g. master)
+    # if it exists; otherwise it will create a detached branch.
+    # The branch will be rebased to its parent on origin.
+    # This probably doesn't matter since we're running on a COW disk
+    # anyway.
+    vm.sendline( 'cd ~/mininet; git fetch --all; git checkout '
+                 + branch + '; git pull --rebase origin ' + branch )
     vm.expect( prompt )
     vm.sendline( 'sudo make install' )
 
@@ -523,12 +555,16 @@ def interact( vm, tests, pre='', post='', prompt=Prompt ):
     log( '* Waiting for output' )
     vm.expect( prompt )
     log( '* Fetching Mininet VM install script' )
+    branch = Branch if Branch else 'master'
     vm.sendline( 'wget '
-                 'https://raw.github.com/mininet/mininet/master/util/vm/'
-                 'install-mininet-vm.sh' )
+                 'https://raw.github.com/mininet/mininet/%s/util/vm/'
+                 'install-mininet-vm.sh' % branch )
     vm.expect( prompt )
     log( '* Running VM install script' )
-    vm.sendline( 'bash install-mininet-vm.sh' )
+    installcmd = 'bash install-mininet-vm.sh'
+    if Branch:
+        installcmd += ' ' + Branch
+    vm.sendline( installcmd )
     vm.expect ( 'password for mininet: ' )
     vm.sendline( 'mininet' )
     log( '* Waiting for script to complete... ' )
@@ -541,6 +577,13 @@ def interact( vm, tests, pre='', post='', prompt=Prompt ):
     log( '* Mininet version: ', version )
     log( '* Testing Mininet' )
     runTests( vm, tests=tests, pre=pre, post=post )
+    # Ubuntu adds this because we install via a serial console,
+    # but we want the VM to boot via the VM console. Otherwise
+    # we get the message 'error: terminal "serial" not found'
+    log( '* Disabling serial console' )
+    vm.sendline( "sudo sed -i -e 's/^GRUB_TERMINAL=serial/#GRUB_TERMINAL=serial/' "
+                "/etc/default/grub; sudo update-grub" )
+    vm.expect( prompt )
     log( '* Shutting down' )
     vm.sendline( 'sync; sudo shutdown -h now' )
     log( '* Waiting for EOF/shutdown' )
@@ -577,13 +620,13 @@ def convert( cow, basename ):
     xmlns:vssd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 <References>
-<File ovf:href="%s" ovf:id="file1" ovf:size="%d"/>
+<File ovf:href="%(diskname)s" ovf:id="file1" ovf:size="%(filesize)d"/>
 </References>
 <DiskSection>
 <Info>Virtual disk information</Info>
-<Disk ovf:capacity="%d" ovf:capacityAllocationUnits="byte" 
-    ovf:diskId="vmdisk1" ovf:fileRef="file1" 
-    ovf:format="http://www.vmware.com/interfaces/specifications/vmdk.html"/>
+<Disk ovf:capacity="%(disksize)d" ovf:capacityAllocationUnits="byte"
+    ovf:diskId="vmdisk1" ovf:fileRef="file1"
+    ovf:format="http://www.vmware.com/interfaces/specifications/vmdk.html#streamOptimized"/>
 </DiskSection>
 <NetworkSection>
 <Info>The list of logical networks</Info>
@@ -591,26 +634,30 @@ def convert( cow, basename ):
 <Description>The nat  network</Description>
 </Network>
 </NetworkSection>
-<VirtualSystem ovf:id="Mininet-VM">
-<Info>A Mininet Virtual Machine (%s)</Info>
-<Name>mininet-vm</Name>
+<VirtualSystem ovf:id="%(vmname)s">
+<Info>%(vminfo)s (%(name)s)</Info>
+<Name>%(vmname)s</Name>
+<OperatingSystemSection ovf:id="%(osid)d">
+<Info>The kind of installed guest operating system</Info>
+<Description>%(osname)s</Description>
+</OperatingSystemSection>
 <VirtualHardwareSection>
 <Info>Virtual hardware requirements</Info>
 <Item>
 <rasd:AllocationUnits>hertz * 10^6</rasd:AllocationUnits>
 <rasd:Description>Number of Virtual CPUs</rasd:Description>
-<rasd:ElementName>1 virtual CPU(s)</rasd:ElementName>
+<rasd:ElementName>%(cpus)s virtual CPU(s)</rasd:ElementName>
 <rasd:InstanceID>1</rasd:InstanceID>
 <rasd:ResourceType>3</rasd:ResourceType>
-<rasd:VirtualQuantity>1</rasd:VirtualQuantity>
+<rasd:VirtualQuantity>%(cpus)s</rasd:VirtualQuantity>
 </Item>
 <Item>
 <rasd:AllocationUnits>byte * 2^20</rasd:AllocationUnits>
 <rasd:Description>Memory Size</rasd:Description>
-<rasd:ElementName>%dMB of memory</rasd:ElementName>
+<rasd:ElementName>%(mem)dMB of memory</rasd:ElementName>
 <rasd:InstanceID>2</rasd:InstanceID>
 <rasd:ResourceType>4</rasd:ResourceType>
-<rasd:VirtualQuantity>%d</rasd:VirtualQuantity>
+<rasd:VirtualQuantity>%(mem)d</rasd:VirtualQuantity>
 </Item>
 <Item>
 <rasd:Address>0</rasd:Address>
@@ -653,16 +700,24 @@ def convert( cow, basename ):
 """
 
 
-def generateOVF( name, diskname, disksize, mem=1024 ):
+def generateOVF( name, osname, osid, diskname, disksize, mem=1024, cpus=1,
+                 vmname='Mininet-VM', vminfo='A Mininet Virtual Machine' ):
     """Generate (and return) OVF file "name.ovf"
        name: root name of OVF file to generate
+       osname: OS name for OVF (Ubuntu | Ubuntu 64-bit)
+       osid: OS ID for OVF (93 | 94 )
        diskname: name of disk file
        disksize: size of virtual disk in bytes
-       mem: VM memory size in MB"""
+       mem: VM memory size in MB
+       cpus: # of virtual CPUs
+       vmname: Name for VM (default name when importing)
+       vmimfo: Brief description of VM for OVF"""
     ovf = name + '.ovf'
     filesize = stat( diskname )[ ST_SIZE ]
-    # OVFTemplate uses the memory size twice in a row
-    xmltext = OVFTemplate % ( diskname, filesize, disksize, name, mem, mem )
+    params = dict( osname=osname, osid=osid, diskname=diskname,
+                   filesize=filesize, disksize=disksize, name=name,
+                   mem=mem, cpus=cpus, vmname=vmname, vminfo=vminfo )
+    xmltext = OVFTemplate % params
     with open( ovf, 'w+' ) as f:
         f.write( xmltext )
     return ovf
@@ -689,6 +744,8 @@ def build( flavor='raring32server', tests=None, pre='', post='', memory=1024 ):
     date = strftime( '%y%m%d-%H-%M-%S', lstart)
     ovfdate = strftime( '%y%m%d', lstart )
     dir = 'mn-%s-%s' % ( flavor, date )
+    if Branch:
+        dir = 'mn-%s-%s-%s' % ( Branch, flavor, date )
     try:
         os.mkdir( dir )
     except:
@@ -710,13 +767,16 @@ def build( flavor='raring32server', tests=None, pre='', post='', memory=1024 ):
     vm = boot( volume, kernel, initrd, logfile, memory=memory )
     version = interact( vm, tests=tests, pre=pre, post=post )
     size = qcow2size( volume )
-    vmdk = convert( volume, basename='mininet-vm-' + archFor( flavor ) )
+    arch = archFor( flavor )
+    vmdk = convert( volume, basename='mininet-vm-' + arch )
     if not SaveQCOW2:
         log( '* Removing qcow2 volume', volume )
         os.remove( volume )
     log( '* Converted VM image stored as', abspath( vmdk ) )
     ovfname = 'mininet-%s-%s-%s' % ( version, ovfdate, OSVersion( flavor ) )
-    ovf = generateOVF( diskname=vmdk, disksize=size, name=ovfname )
+    osname, osid = OVFOSNameID( flavor )
+    ovf = generateOVF( name=ovfname, osname=osname, osid=osid,
+                       diskname=vmdk, disksize=size )
     log( '* Generated OVF descriptor file', ovf )
     if Zip:
         log( '* Generating .zip file' )
@@ -731,6 +791,9 @@ def build( flavor='raring32server', tests=None, pre='', post='', memory=1024 ):
 
 def runTests( vm, tests=None, pre='', post='', prompt=Prompt ):
     "Run tests (list) in vm (pexpect object)"
+    if Branch:
+        checkOutBranch( vm, branch=Branch )
+        vm.expect( prompt )
     if not tests:
         tests = []
     if pre:
@@ -761,8 +824,8 @@ def getMininetVersion( vm ):
     return version
 
 
-def bootAndRunTests( image, tests=None, pre='', post='', prompt=Prompt,
-                     memory=1024 ):
+def bootAndRun( image, prompt=Prompt, memory=1024, outputFile=None, 
+                runFunction=None, **runArgs ):
     """Boot and test VM
        tests: list of tests to run
        pre: command line to run in VM before tests
@@ -790,15 +853,16 @@ def bootAndRunTests( image, tests=None, pre='', post='', prompt=Prompt,
     login( vm )
     log( '* Waiting for prompt after login' )
     vm.expect( prompt )
-    if Branch:
-        checkOutBranch( vm, branch=Branch )
-        vm.expect( prompt )
-    runTests( vm, tests=tests, pre=pre, post=post )
-    # runTests eats its last prompt, but maybe it shouldn't...
+    # runFunction should begin with sendline and should eat its last prompt
+    if runFunction:
+        runFunction( vm, **runArgs )
     log( '* Shutting down' )
     vm.sendline( 'sudo shutdown -h now ' )
     log( '* Waiting for shutdown' )
     vm.wait()
+    if outputFile:
+        log( '* Saving temporary image to %s' % outputFile )
+        convert( cow, outputFile )
     log( '* Removing temporary dir', tmpdir )
     srun( 'rm -rf ' + tmpdir )
     elapsed = time() - bootTestStart
@@ -859,12 +923,13 @@ def parseArgs():
     parser.add_argument( '-p', '--post', metavar='cmd', default='',
                          help='specify a command line to run after tests' )
     parser.add_argument( '-b', '--branch', metavar='branch',
-                         help='For an existing VM image, check out and install'
-                         ' this branch before testing' )
+                         help='branch to install and/or check out and test' )
     parser.add_argument( 'flavor', nargs='*',
                          help='VM flavor(s) to build (e.g. raring32server)' )
     parser.add_argument( '-z', '--zip', action='store_true',
                          help='archive .ovf and .vmdk into .zip file' )
+    parser.add_argument( '-o', '--out',
+                         help='output file for test image (vmdk)' )
     args = parser.parse_args()
     if args.depend:
         depend()
@@ -896,8 +961,8 @@ def parseArgs():
             log( '* BUILD FAILED with exception: ', e )
             exit( 1 )
     for image in args.image:
-        bootAndRunTests( image, tests=args.test, pre=args.run,
-                         post=args.post, memory=args.memory)
+        bootAndRun( image, runFunction=runTests, tests=args.test, pre=args.run,
+                    post=args.post, memory=args.memory, outputFile=args.out )
     if not ( args.depend or args.list or args.clean or args.flavor
              or args.image ):
         parser.print_help()
diff --git a/util/vm/install-mininet-vm.sh b/util/vm/install-mininet-vm.sh
index aa93de4739c236c9900fd9d37d24ca55c3079179..3c82e75db2bc5c7dd9d1db648f388e1355d9b3d9 100755
--- a/util/vm/install-mininet-vm.sh
+++ b/util/vm/install-mininet-vm.sh
@@ -3,6 +3,8 @@
 # This script is intended to install Mininet into
 # a brand-new Ubuntu virtual machine,
 # to create a fully usable "tutorial" VM.
+#
+# optional argument: Mininet branch to install
 set -e
 echo `whoami` ALL=NOPASSWD: ALL | sudo tee -a /etc/sudoers > /dev/null
 sudo sed -i -e 's/Default/#Default/' /etc/sudoers
@@ -25,11 +27,16 @@ fi
 if [ -e /etc/rc.local.backup ]; then
     sudo mv /etc/rc.local.backup /etc/rc.local
 fi
-# Install Mininet
+# Fetch Mininet
 sudo apt-get -y install git-core openssh-server
 git clone git://github.com/mininet/mininet
-cd mininet
-cd
+# Optionally check out branch
+if [ "$1" != "" ]; then
+  pushd mininet
+  git checkout -b $1 $1
+  popd
+fi
+# Install Mininet
 time mininet/util/install.sh
 # Finalize VM
 time mininet/util/install.sh -tc
@@ -38,4 +45,3 @@ time mininet/util/install.sh -tc
 #  echo "export NOX_CORE_DIR=~/noxcore/build/src/" >> .bashrc
 #fi
 echo "Done preparing Mininet VM."
-