Add port forwarding using iptables

By default, we now forward the following ports from
port on eth0 to port+N on onosN:

8101 (KarafPort)
8181 (GUIPort) (also REST)
6643 (OpenFlowPort)

Note: this will not work if your default interface is
called something other than eth0.

Also: added null topology so we can use onos.py to control
an external/hardware network.

Bugs: it seems that iptables isn't cleaned up completely -
Mininet's NAT element may be to blame.

Change-Id: I2197194100a77ebfddd0d38ad5194ad7569ceba3
diff --git a/tools/dev/mininet/onos.py b/tools/dev/mininet/onos.py
index 9640837..6e66f26 100755
--- a/tools/dev/mininet/onos.py
+++ b/tools/dev/mininet/onos.py
@@ -20,7 +20,7 @@
 mn --custom onos.py --controller onos,3 \
    --switch onosuser --topo torus,4,4
 
-Currently you meed to specify a custom switch class
+Currently you meed to use a custom switch class
 because Mininet's Switch() class does't (yet?) handle
 controllers with multiple IP addresses directly.
 
@@ -59,7 +59,9 @@
 
 ### ONOS Environment
 
-KarafPort = 8101  # ssh port indicating karaf is running
+KarafPort = 8101	# ssh port indicating karaf is running
+GUIPort = 8181		# GUI/REST port
+OpenFlowPort = 6653 	# OpenFlow port
 
 def defaultUser():
     "Return a reasonable default user"
@@ -71,7 +73,6 @@
         user = 'nobody'
     return user
 
-
 # Module vars, initialized below
 HOME = ONOS_ROOT = KARAF_ROOT = ONOS_HOME = ONOS_USER = None
 ONOS_APPS = ONOS_WEB_USER = ONOS_WEB_PASS = ONOS_TAR = None
@@ -239,13 +240,13 @@
             info( '.' )
             time.sleep( 1 )
         info( ' ssh-port' )
-        waitListening( client=self, server=self, port=8101 )
+        waitListening( server=self, port=KarafPort )
         info( ' openflow-port' )
-        waitListening( server=self, port=6653 )
+        waitListening( server=self, port=OpenFlowPort )
         info( ' client' )
         while True:
-            result = quietRun( 'echo apps -a | %s -h %s' % ( self.client, self.IP() ),
-                               shell=True )
+            result = quietRun( 'echo apps -a | %s -h %s' %
+                               ( self.client, self.IP() ), shell=True )
             if 'openflow' in result:
                 break
             info( '.' )
@@ -265,6 +266,7 @@
         """name: (first parameter)
            *args: topology class parameters
            ipBase: IP range for ONOS nodes
+           forward: default port forwarding list,
            topo: topology class or instance
            **kwargs: additional topology parameters"""
         args = list( args )
@@ -277,11 +279,13 @@
                 args = ( 1, )
         if not isinstance( topo, Topo ):
             topo = RenamedTopo( topo, *args, hnew='onos', **kwargs )
-        ipBase = kwargs.pop( 'ipBase', '192.168.123.0/24' )
+        self.ipBase = kwargs.pop( 'ipBase', '192.168.123.0/24' )
+        self.forward = kwargs.pop( 'forward',
+                                   [ KarafPort, GUIPort, OpenFlowPort ] )
         super( ONOSCluster, self ).__init__( name, inNamespace=False )
         fixIPTables()
         self.env = initONOSEnv()
-        self.net = Mininet( topo=topo, ipBase=ipBase,
+        self.net = Mininet( topo=topo, ipBase=self.ipBase,
                             host=ONOSNode, switch=LinuxBridge,
                             controller=None )
         self.net.addNAT().configDefault()
@@ -296,6 +300,7 @@
         for node in self.nodes():
             node.start( self.env )
         info( '\n' )
+        self.configPortForwarding( ports=self.forward, action='A' )
         self.waitStarted()
         return
 
@@ -305,10 +310,12 @@
         for node in self.nodes():
             info( node )
             node.waitStarted()
-        info( '*** Waited %.2f seconds for ONOS startup' % ( time.time() - startTime ) )
+        info( '*** Waited %.2f seconds for ONOS startup' %
+              ( time.time() - startTime ) )
 
     def stop( self ):
         "Shut down ONOS cluster"
+        self.configPortForwarding( ports=self.forward, action='D' )
         for node in self.nodes():
             node.stop()
         self.net.stop()
@@ -317,6 +324,18 @@
         "Return list of ONOS nodes"
         return [ h for h in self.net.hosts if isinstance( h, ONOSNode ) ]
 
+    def configPortForwarding( self, ports=[], intf='eth0', action='A' ):
+        """Start or stop ports on intf to all nodes
+           action: A=add/start, D=delete/stop (default: A)"""
+        for port in ports:
+            for index, node in enumerate( self.nodes() ):
+                ip, inport = node.IP(), port + index
+                # Configure a destination NAT rule
+                cmd = ( 'iptables -t nat -{action} PREROUTING -t nat '
+                        '-i {intf} -p tcp --dport {inport} '
+                        '-j DNAT --to-destination {ip}:{port}' )
+                self.cmd( cmd.format( **locals() ) )
+
 
 class ONOSSwitchMixin( object ):
     "Mixin for switches that connect to an ONOSCluster"
@@ -413,6 +432,9 @@
              'onosuser': ONOSUserSwitch,
              'default': ONOSOVSSwitch }
 
+# Null topology so we can control an external/hardware network
+topos = { 'none': Topo }
+
 if __name__ == '__main__':
     if len( argv ) != 2:
         test( 3 )