From 47b9466fad7d2751be0487f720679c0bfbe9cf0e Mon Sep 17 00:00:00 2001 From: Brian O'Connor Date: Tue, 16 Jul 2013 17:10:12 -0700 Subject: [PATCH] Adding NAT class Includes automatic NAT feature (mn --nat) and addNAT convenience method for topologies fixes #111 --- bin/mn | 5 ++++ mininet/net.py | 43 +++++++++++++++++++++++++++++---- mininet/node.py | 64 +++++++++++++++++++++++++++++++++++++++++++++++++ mininet/topo.py | 10 ++++++++ 4 files changed, 118 insertions(+), 4 deletions(-) diff --git a/bin/mn b/bin/mn index 71c76ec..0424d35 100755 --- a/bin/mn +++ b/bin/mn @@ -193,6 +193,9 @@ class MininetRunner( object ): opts.add_option( '--pin', action='store_true', default=False, help="pin hosts to CPU cores " "(requires --host cfs or --host rt)" ) + opts.add_option( '--nat', action='store_true', + default=False, help="adds a NAT to the topology " + "that connects Mininet to the physical network" ) opts.add_option( '--version', action='callback', callback=version ) self.options, self.args = opts.parse_args() @@ -223,6 +226,8 @@ class MininetRunner( object ): start = time.time() topo = buildTopo( TOPOS, self.options.topo ) + if self.options.nat: + topo.addNAT() switch = customConstructor( SWITCHES, self.options.switch ) host = customConstructor( HOSTS, self.options.host ) controller = customConstructor( CONTROLLERS, self.options.controller ) diff --git a/mininet/net.py b/mininet/net.py index 4994f9e..87f7b47 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -95,7 +95,7 @@ from itertools import chain from mininet.cli import CLI from mininet.log import info, error, debug, output -from mininet.node import Host, OVSKernelSwitch, Controller +from mininet.node import Host, OVSKernelSwitch, 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 @@ -112,7 +112,8 @@ class Mininet( object ): build=True, xterms=False, cleanup=False, ipBase='10.0.0.0/8', inNamespace=False, autoSetMacs=False, autoStaticArp=False, autoPinCpus=False, - listenPort=None ): + listenPort=None, + gateway=None ): """Create Mininet object. topo: Topo (topology) object or None switch: default Switch class @@ -129,7 +130,8 @@ class Mininet( object ): autoStaticArp: set all-pairs static MAC addrs? autoPinCpus: pin hosts to (real) cores (requires CPULimitedHost)? listenPort: base listening port to open; will be incremented for - each additional switch in the net if inNamespace=False""" + each additional switch in the net if inNamespace=False + gateway: node that provides connectivity to the Internet""" self.topo = topo self.switch = switch self.host = host @@ -148,6 +150,7 @@ class Mininet( object ): self.numCores = numCores() self.nextCore = 0 # next core for pinning hosts to CPUs self.listenPort = listenPort + self.gateway = gateway self.hosts = [] self.switches = [] @@ -163,6 +166,9 @@ class Mininet( object ): if topo and build: self.build() + # list of Node subclasses that provide gateway service + gateways = [ NAT ] + def addHost( self, name, cls=None, **params ): """Add host. name: name of host to add @@ -175,17 +181,22 @@ class Mininet( object ): prefixLen=self.prefixLen ) + '/%s' % self.prefixLen } if self.autoSetMacs: - defaults[ 'mac'] = macColonHex( self.nextIP ) + defaults[ 'mac' ] = macColonHex( self.nextIP ) if self.autoPinCpus: defaults[ 'cores' ] = self.nextCore self.nextCore = ( self.nextCore + 1 ) % self.numCores self.nextIP += 1 defaults.update( params ) + # TODO: clean this up + if params.get( 'isNAT', False ): + cls = NAT if not cls: cls = self.host h = cls( name, **defaults ) self.hosts.append( h ) self.nameToNode[ name ] = h + if cls in self.gateways: + self.gateway = h return h def addSwitch( self, name, cls=None, **params ): @@ -227,6 +238,16 @@ class Mininet( object ): self.nameToNode[ name ] = controller_new return controller_new + # TODO: incomplete + def addNAT( self, name='nat0', connect=True, **params ): + nat = self.addHost( name, cls=NAT, **params ) + # find first switch and create link + print "net/addNAT" + if connect: + #connect the nat to the first switch + self.addLink( nat, self.switches[ 0 ] ) + return nat + # BL: We now have four ways to look up nodes # This may (should?) be cleaned up in the future. def getNodeByName( self, *args ): @@ -305,6 +326,19 @@ class Mininet( object ): host.cmd( 'ifconfig lo up' ) info( '\n' ) + def configGateway( self ): + """Add gateway routes to all hosts if the networks has a gateway.""" + if self.gateway: + gatewayIP = self.gateway.defaultIntf().IP() + for host in self.hosts: + if host.inNamespace and self.gateway: + host.cmd( 'ip route flush root 0/0' ) + host.cmd( 'route add -net', self.ipBase, 'dev', host.defaultIntf() ) + host.cmd( 'route add default gw', gatewayIP ) + else: + # Don't mess with hosts in the root namespace + pass + def buildFromTopo( self, topo=None ): """Build mininet from a topology object At the end of this function, everything should be connected @@ -363,6 +397,7 @@ class Mininet( object ): self.startTerms() if self.autoStaticArp: self.staticArp() + self.configGateway() self.built = True def startTerms( self ): diff --git a/mininet/node.py b/mininet/node.py index 9e93957..17e153c 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -1243,3 +1243,67 @@ class RemoteController( Controller ): warn( "Unable to contact the remote controller" " at %s:%d\n" % ( self.ip, self.port ) ) +class NAT( Node ): + """NAT: Provides connectivity to external network""" + + def __init__( self, name, inetIntf='eth0', subnet='10.0/8', **params): + super( NAT, self ).__init__( name, **params ) + + """Start NAT/forwarding between Mininet and external network + inetIntf: interface for internet access + subnet: Mininet subnet (default 10.0/8)=""" + self.inetIntf = inetIntf + self.subnet = subnet #TODO: get subnet from Mininet directly + + def config( self, inetIntf='eth0', subnet='10.0/8', **params ): + super( NAT, self).config( **params ) + """Configure the NAT and iptables""" + + # Identify the interface connecting to the mininet network + localIntf = self.defaultIntf() + self.cmd( 'sysctl net.ipv4.ip_forward=0' ) + + # Flush any currently active rules + # TODO: is this safe? + self.cmd( 'iptables -F' ) + self.cmd( 'iptables -t nat -F' ) + + # Create default entries for unmatched traffic + self.cmd( 'iptables -P INPUT ACCEPT' ) + self.cmd( 'iptables -P OUTPUT ACCEPT' ) + self.cmd( 'iptables -P FORWARD DROP' ) + + # Configure NAT + self.cmd( 'iptables -I FORWARD -i', localIntf, '-d', self.subnet, '-j DROP' ) + self.cmd( 'iptables -A FORWARD -i', localIntf, '-s', self.subnet, '-j ACCEPT' ) + self.cmd( 'iptables -A FORWARD -i', self.inetIntf, '-d', self.subnet, '-j ACCEPT' ) + self.cmd( 'iptables -t nat -A POSTROUTING -o ', self.inetIntf, '-j MASQUERADE' ) + + # Instruct the kernel to perform forwarding + self.cmd( 'sysctl net.ipv4.ip_forward=1' ) + + # Prevent network-manager from messing with our interface + # by specifying manual configuration in /etc/network/interfaces + intf = localIntf + cfile = '/etc/network/interfaces' + line = '\niface %s inet manual\n' % intf + config = open( cfile ).read() + if ( line ) not in config: + info( '*** 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 + self.cmd( 'service network-manager restart' ) + + def terminate( self ): + """Stop NAT/forwarding between Mininet and external network""" + # Flush any currently active rules + # TODO: is this safe? + self.cmd( 'iptables -F' ) + self.cmd( 'iptables -t nat -F' ) + + # Instruct the kernel to stop forwarding + self.cmd( 'sysctl net.ipv4.ip_forward=0' ) + + super( NAT, self ).terminate() diff --git a/mininet/topo.py b/mininet/topo.py index 2ab455c..9de6eff 100644 --- a/mininet/topo.py +++ b/mininet/topo.py @@ -89,6 +89,16 @@ class Topo(object): result = self.addNode(name, isSwitch=True, **opts) return result + def addNAT(self, name='nat', connect=True, **opts): + """Convenience method: Add NAT to graph. + name: NAT name + connect: True will automatically connect to the first switch""" + nat = self.addNode(name, isNAT=True, inNamespace=False) + if connect: + # connect the NAT to the first switch + self.addLink(name, self.switches()[ 0 ]) + return nat + def addLink(self, node1, node2, port1=None, port2=None, **opts): """node1, node2: nodes to link together