Merge pull request #353 from mininet/devel/nat
Adding NAT class to NodeLib
This commit is contained in:
@@ -200,6 +200,9 @@ class MininetRunner( object ):
|
|||||||
opts.add_option( '--pin', action='store_true',
|
opts.add_option( '--pin', action='store_true',
|
||||||
default=False, help="pin hosts to CPU cores "
|
default=False, help="pin hosts to CPU cores "
|
||||||
"(requires --host cfs or --host rt)" )
|
"(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 )
|
opts.add_option( '--version', action='callback', callback=version )
|
||||||
|
|
||||||
self.options, self.args = opts.parse_args()
|
self.options, self.args = opts.parse_args()
|
||||||
@@ -257,6 +260,10 @@ class MininetRunner( object ):
|
|||||||
autoStaticArp=arp, autoPinCpus=pin,
|
autoStaticArp=arp, autoPinCpus=pin,
|
||||||
listenPort=listenPort )
|
listenPort=listenPort )
|
||||||
|
|
||||||
|
if self.options.nat:
|
||||||
|
nat = mn.addNAT()
|
||||||
|
nat.configDefault()
|
||||||
|
|
||||||
if self.options.pre:
|
if self.options.pre:
|
||||||
CLI( mn, script=self.options.pre )
|
CLI( mn, script=self.options.pre )
|
||||||
|
|
||||||
|
|||||||
Executable
+70
@@ -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()
|
||||||
|
|
||||||
@@ -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()
|
||||||
+16
-1
@@ -97,6 +97,7 @@ from itertools import chain, groupby
|
|||||||
from mininet.cli import CLI
|
from mininet.cli import CLI
|
||||||
from mininet.log import info, error, debug, output, warn
|
from mininet.log import info, error, debug, output, warn
|
||||||
from mininet.node import Host, OVSKernelSwitch, DefaultController, Controller
|
from mininet.node import Host, OVSKernelSwitch, DefaultController, Controller
|
||||||
|
from mininet.nodelib import NAT
|
||||||
from mininet.link import Link, Intf
|
from mininet.link import Link, Intf
|
||||||
from mininet.util import quietRun, fixLimits, numCores, ensureRoot
|
from mininet.util import quietRun, fixLimits, numCores, ensureRoot
|
||||||
from mininet.util import macColonHex, ipStr, ipParse, netParse, ipAdd
|
from mininet.util import macColonHex, ipStr, ipParse, netParse, ipAdd
|
||||||
@@ -208,7 +209,7 @@ class Mininet( object ):
|
|||||||
prefixLen=self.prefixLen ) +
|
prefixLen=self.prefixLen ) +
|
||||||
'/%s' % self.prefixLen }
|
'/%s' % self.prefixLen }
|
||||||
if self.autoSetMacs:
|
if self.autoSetMacs:
|
||||||
defaults[ 'mac'] = macColonHex( self.nextIP )
|
defaults[ 'mac' ] = macColonHex( self.nextIP )
|
||||||
if self.autoPinCpus:
|
if self.autoPinCpus:
|
||||||
defaults[ 'cores' ] = self.nextCore
|
defaults[ 'cores' ] = self.nextCore
|
||||||
self.nextCore = ( self.nextCore + 1 ) % self.numCores
|
self.nextCore = ( self.nextCore + 1 ) % self.numCores
|
||||||
@@ -260,6 +261,20 @@ class Mininet( object ):
|
|||||||
self.nameToNode[ name ] = controller_new
|
self.nameToNode[ name ] = controller_new
|
||||||
return 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
|
# BL: We now have four ways to look up nodes
|
||||||
# This may (should?) be cleaned up in the future.
|
# This may (should?) be cleaned up in the future.
|
||||||
def getNodeByName( self, *args ):
|
def getNodeByName( self, *args ):
|
||||||
|
|||||||
@@ -1358,7 +1358,6 @@ class RemoteController( Controller ):
|
|||||||
warn( "Unable to contact the remote controller"
|
warn( "Unable to contact the remote controller"
|
||||||
" at %s:%d\n" % ( self.ip, self.port ) )
|
" at %s:%d\n" % ( self.ip, self.port ) )
|
||||||
|
|
||||||
|
|
||||||
def DefaultController( name, order=[ Controller, OVSController ], **kwargs ):
|
def DefaultController( name, order=[ Controller, OVSController ], **kwargs ):
|
||||||
"find any controller that is available and run it"
|
"find any controller that is available and run it"
|
||||||
for controller in order:
|
for controller in order:
|
||||||
|
|||||||
+69
-5
@@ -1,15 +1,12 @@
|
|||||||
"""
|
"""
|
||||||
Node Library for Mininet
|
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.node import Node, Switch
|
||||||
from mininet.topo import Topo
|
|
||||||
from mininet.node import Switch
|
|
||||||
from mininet.log import setLogLevel, info
|
from mininet.log import setLogLevel, info
|
||||||
|
|
||||||
|
|
||||||
class LinuxBridge( Switch ):
|
class LinuxBridge( Switch ):
|
||||||
"Linux Bridge (with optional spanning tree)"
|
"Linux Bridge (with optional spanning tree)"
|
||||||
|
|
||||||
@@ -49,3 +46,70 @@ class LinuxBridge( Switch ):
|
|||||||
self.cmd( 'ifconfig', self, 'down' )
|
self.cmd( 'ifconfig', self, 'down' )
|
||||||
self.cmd( 'brctl delbr', self )
|
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()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user