diff --git a/bin/mn b/bin/mn index edd4d98423263ae1e8ca4955504ba83a180bac11..f4b16eff4cd947b6db628987de98632fc86233eb 100755 --- a/bin/mn +++ b/bin/mn @@ -200,6 +200,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() @@ -257,6 +260,10 @@ class MininetRunner( object ): autoStaticArp=arp, autoPinCpus=pin, listenPort=listenPort ) + if self.options.nat: + nat = mn.addNAT() + nat.configDefault() + if self.options.pre: CLI( mn, script=self.options.pre ) diff --git a/examples/natnet.py b/examples/natnet.py new file mode 100755 index 0000000000000000000000000000000000000000..4305d1fc232d090c4488ce73a2b2dbd224d92386 --- /dev/null +++ b/examples/natnet.py @@ -0,0 +1,70 @@ +#!/usr/bin/python + +""" +natnet.py: Example network with NATs + + + h0 + | + s0 + | + ---------------- + | | + nat1 nat2 + | | + s1 s2 + | | + h1 h2 + +""" + +from mininet.topo import Topo +from mininet.net import Mininet +from mininet.nodelib import NAT +from mininet.log import setLogLevel +from mininet.cli import CLI +from mininet.util import irange + +class InternetTopo(Topo): + "Single switch connected to n hosts." + def __init__(self, n=2, h=1, **opts): + Topo.__init__(self, **opts) + + # set up inet switch + inetSwitch = self.addSwitch('s0') + # add inet host + inetHost = self.addHost('h0') + self.addLink(inetSwitch, inetHost) + + # add local nets + for i in irange(1, n): + inetIntf = 'nat%d-eth0' % i + localIntf = 'nat%d-eth1' % i + localIP = '192.168.%d.1' % i + localSubnet = '192.168.%d.0/24' % i + natParams = { 'ip' : '%s/24' % localIP } + # add NAT to topology + nat = self.addNode('nat%d' % i, cls=NAT, subnet=localSubnet, + inetIntf=inetIntf, localIntf=localIntf) + switch = self.addSwitch('s%d' % i) + # connect NAT to inet and local switches + self.addLink(nat, inetSwitch, intfName1=inetIntf) + self.addLink(nat, switch, intfName1=localIntf, params1=natParams) + # add host and connect to local switch + host = self.addHost('h%d' % i, + ip='192.168.%d.100/24' % i, + defaultRoute='via %s' % localIP) + self.addLink(host, switch) + +def run(): + "Create network and run the CLI" + topo = InternetTopo() + net = Mininet(topo=topo) + net.start() + CLI(net) + net.stop() + +if __name__ == '__main__': + setLogLevel('info') + run() + \ No newline at end of file diff --git a/examples/test/test_natnet.py b/examples/test/test_natnet.py new file mode 100644 index 0000000000000000000000000000000000000000..3addc92e25e7f912887a90d5fbb77a55f491a201 --- /dev/null +++ b/examples/test/test_natnet.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python + +""" +Test for natnet.py +""" + +import unittest +import pexpect +from mininet.util import quietRun + +class testNATNet( unittest.TestCase ): + + prompt = 'mininet>' + + def setUp( self ): + self.net = pexpect.spawn( 'python -m mininet.examples.natnet' ) + self.net.expect( self.prompt ) + + def testPublicPing( self ): + "Attempt to ping the public server (h0) from h1 and h2" + self.net.sendline( 'h1 ping -c 1 h0' ) + self.net.expect ( '(\d+)% packet loss' ) + percent = int( self.net.match.group( 1 ) ) if self.net.match else -1 + self.assertEqual( percent, 0 ) + self.net.expect( self.prompt ) + + self.net.sendline( 'h2 ping -c 1 h0' ) + self.net.expect ( '(\d+)% packet loss' ) + percent = int( self.net.match.group( 1 ) ) if self.net.match else -1 + self.assertEqual( percent, 0 ) + self.net.expect( self.prompt ) + + def testPrivatePing( self ): + "Attempt to ping h1 and h2 from public server" + self.net.sendline( 'h0 ping -c 1 -t 1 h1' ) + result = self.net.expect ( [ 'unreachable', 'loss' ] ) + self.assertEqual( result, 0 ) + self.net.expect( self.prompt ) + + self.net.sendline( 'h0 ping -c 1 -t 1 h2' ) + result = self.net.expect ( [ 'unreachable', 'loss' ] ) + self.assertEqual( result, 0 ) + self.net.expect( self.prompt ) + + def testPrivateToPrivatePing( self ): + "Attempt to ping from NAT'ed host h1 to NAT'ed host h2" + self.net.sendline( 'h1 ping -c 1 -t 1 h2' ) + result = self.net.expect ( [ '[Uu]nreachable', 'loss' ] ) + self.assertEqual( result, 0 ) + self.net.expect( self.prompt ) + + def tearDown( self ): + self.net.sendline( 'exit' ) + self.net.wait() + +if __name__ == '__main__': + unittest.main() diff --git a/mininet/net.py b/mininet/net.py index 80654f5c37583cd341da02fb25375e6bb3c23f45..880780401847226905b183a4ddf1a791c3b70f0e 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -97,6 +97,7 @@ from mininet.cli import CLI from mininet.log import info, error, debug, output, warn from mininet.node import Host, OVSKernelSwitch, DefaultController, Controller +from mininet.nodelib import NAT from mininet.link import Link, Intf from mininet.util import quietRun, fixLimits, numCores, ensureRoot from mininet.util import macColonHex, ipStr, ipParse, netParse, ipAdd @@ -208,7 +209,7 @@ def addHost( self, name, cls=None, **params ): 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 @@ -260,6 +261,20 @@ def addController( self, name='c0', controller=None, **params ): self.nameToNode[ name ] = controller_new return controller_new + def addNAT( self, name='nat0', connect=True, inNamespace=False, **params ): + nat = self.addHost( name, cls=NAT, inNamespace=inNamespace, + subnet=self.ipBase, **params ) + # find first switch and create link + if connect: + # connect the nat to the first switch + self.addLink( nat, self.switches[ 0 ] ) + # set the default route on hosts + natIP = nat.params[ 'ip' ].split('/')[ 0 ] + for host in self.hosts: + if host.inNamespace: + host.setDefaultRoute( 'via %s' % natIP ) + 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 ): diff --git a/mininet/node.py b/mininet/node.py index 55d353104ad1cefecb38da4ae45e03ebffdaf3bc..7eb632217774fa76b0979d560569e1826b2c9814 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -1358,7 +1358,6 @@ 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: diff --git a/mininet/nodelib.py b/mininet/nodelib.py index 2eb80465cd005c28c9a2aaa86aa920b2cc04c02a..1760c7b30b761cf143e1f04d71bc3b6714ea9ec4 100644 --- a/mininet/nodelib.py +++ b/mininet/nodelib.py @@ -1,15 +1,12 @@ """ Node Library for Mininet -This contains additional Node types which you may find to be useful +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.node import Node, Switch from mininet.log import setLogLevel, info - class LinuxBridge( Switch ): "Linux Bridge (with optional spanning tree)" @@ -49,3 +46,70 @@ def stop( self ): self.cmd( 'ifconfig', self, 'down' ) self.cmd( 'brctl delbr', self ) +class NAT( Node ): + """NAT: Provides connectivity to external network""" + + def __init__( self, name, inetIntf='eth0', subnet='10.0/8', localIntf=None, **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 + self.localIntf = localIntf + + def config( self, **params ): + super( NAT, self).config( **params ) + """Configure the NAT and iptables""" + + if not self.localIntf: + self.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', self.localIntf, '-d', self.subnet, '-j DROP' ) + self.cmd( 'iptables -A FORWARD -i', self.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 = self.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() +