Files
mininet/mininet/net.py
T

496 lines
18 KiB
Python
Executable File

#!/usr/bin/python
"""
Mininet: A simple networking testbed for OpenFlow!
author: Bob Lantz (rlantz@cs.stanford.edu)
author: Brandon Heller (brandonh@stanford.edu)
Mininet creates scalable OpenFlow test networks by using
process-based virtualization and network namespaces.
Simulated hosts are created as processes in separate network
namespaces. This allows a complete OpenFlow network to be simulated on
top of a single Linux kernel.
Each host has:
A virtual console (pipes to a shell)
A virtual interfaces (half of a veth pair)
A parent shell (and possibly some child processes) in a namespace
Hosts have a network interface which is configured via ifconfig/ip
link/etc.
This version supports both the kernel and user space datapaths
from the OpenFlow reference implementation.
In kernel datapath mode, the controller and switches are simply
processes in the root namespace.
Kernel OpenFlow datapaths are instantiated using dpctl(8), and are
attached to the one side of a veth pair; the other side resides in the
host namespace. In this mode, switch processes can simply connect to the
controller via the loopback interface.
In user datapath mode, the controller and switches are full-service
nodes that live in their own network namespaces and have management
interfaces and IP addresses on a control network (e.g. 10.0.123.1,
currently routed although it could be bridged.)
In addition to a management interface, user mode switches also have
several switch interfaces, halves of veth pairs whose other halves
reside in the host nodes that the switches are connected to.
Naming:
Host nodes are named h1-hN
Switch nodes are named s0-sN
Interfaces are named { nodename }-eth0 .. { nodename }-ethN
"""
import os
import re
import signal
from time import sleep
from mininet.cli import CLI
from mininet.log import lg
from mininet.node import KernelSwitch, OVSKernelSwitch
from mininet.util import quietRun, fixLimits
from mininet.util import makeIntfPair, moveIntf
from mininet.xterm import cleanUpScreens, makeXterms
DATAPATHS = [ 'kernel' ] #[ 'user', 'kernel' ]
def init():
"Initialize Mininet."
if os.getuid() != 0:
# Note: this script must be run as root
# Perhaps we should do so automatically!
print "*** Mininet must run as root."
exit( 1 )
# If which produces no output, then netns is not in the path.
# May want to loosen this to handle netns in the current dir.
if not quietRun( [ 'which', 'netns' ] ):
raise Exception( "Could not find netns; see INSTALL" )
fixLimits()
class Mininet( object ):
"Network emulation with hosts spawned in network namespaces."
def __init__( self, topo, switch, host, controller, cparams,
build=True, xterms=False, cleanup=False,
inNamespace=False,
autoSetMacs=False, autoStaticArp=False ):
"""Create Mininet object.
topo: Topo object
switch: Switch class
host: Host class
controller: Controller class
cparams: ControllerParams object
now: build now?
xterms: if build now, spawn xterms?
cleanup: if build now, cleanup before creating?
inNamespace: spawn switches and controller in net namespaces?
autoSetMacs: set MAC addrs to DPIDs?
autoStaticArp: set all-pairs static MAC addrs?"""
self.topo = topo
self.switch = switch
self.host = host
self.controller = controller
self.cparams = cparams
self.nodes = {} # dpid to Node{ Host, Switch } objects
self.controllers = {} # controller name to Controller objects
self.dps = 0 # number of created kernel datapaths
self.inNamespace = inNamespace
self.xterms = xterms
self.cleanup = cleanup
self.autoSetMacs = autoSetMacs
self.autoStaticArp = autoStaticArp
self.terms = [] # list of spawned xterm processes
if build:
self.build()
def _addHost( self, dpid ):
"""Add host.
dpid: DPID of host to add"""
host = self.host( 'h_' + self.topo.name( dpid ) )
# for now, assume one interface per host.
host.intfs.append( 'h_' + self.topo.name( dpid ) + '-eth0' )
self.nodes[ dpid ] = host
#lg.info( '%s ' % host.name )
def _addSwitch( self, dpid ):
"""Add switch.
dpid: DPID of switch to add"""
sw = None
swDpid = None
if self.autoSetMacs:
swDpid = dpid
if self.switch is KernelSwitch or self.switch is OVSKernelSwitch:
sw = self.switch( 's_' + self.topo.name( dpid ), dp = self.dps,
dpid = swDpid )
self.dps += 1
else:
sw = self.switch( 's_' + self.topo.name( dpid ) )
self.nodes[ dpid ] = sw
def _addLink( self, src, dst ):
"""Add link.
src: source DPID
dst: destination DPID"""
srcPort, dstPort = self.topo.port( src, dst )
srcNode = self.nodes[ src ]
dstNode = self.nodes[ dst ]
srcIntf = srcNode.intfName( srcPort )
dstIntf = dstNode.intfName( dstPort )
makeIntfPair( srcIntf, dstIntf )
srcNode.intfs.append( srcIntf )
dstNode.intfs.append( dstIntf )
srcNode.ports[ srcPort ] = srcIntf
dstNode.ports[ dstPort ] = dstIntf
#lg.info( '\n' )
#lg.info( 'added intf %s to src node %x\n' % ( srcIntf, src ) )
#lg.info( 'added intf %s to dst node %x\n' % ( dstIntf, dst ) )
if srcNode.inNamespace:
#lg.info( 'moving src w/inNamespace set\n' )
moveIntf( srcIntf, srcNode )
if dstNode.inNamespace:
#lg.info( 'moving dst w/inNamespace set\n' )
moveIntf( dstIntf, dstNode )
srcNode.connection[ srcIntf ] = ( dstNode, dstIntf )
dstNode.connection[ dstIntf ] = ( srcNode, srcIntf )
def _addController( self, controller ):
"""Add controller.
controller: Controller class"""
controller = self.controller( 'c0', self.inNamespace )
if controller: # allow controller-less setups
self.controllers[ 'c0' ] = controller
# Control network support:
#
# Create an explicit control network. Currently this is only
# used by the user datapath configuration.
#
# Notes:
#
# 1. If the controller and switches are in the same ( e.g. root )
# namespace, they can just use the loopback connection.
# We may wish to do this for the user datapath as well as the
# kernel datapath.
#
# 2. If we can get unix domain sockets to work, we can use them
# instead of an explicit control network.
#
# 3. Instead of routing, we could bridge or use 'in-band' control.
#
# 4. Even if we dispense with this in general, it could still be
# useful for people who wish to simulate a separate control
# network ( since real networks may need one! )
def _configureControlNetwork( self ):
"Configure control network."
self._configureRoutedControlNetwork()
def _configureRoutedControlNetwork( self ):
"""Configure a routed control network on controller and switches.
For use with the user datapath only right now.
TODO( brandonh ) test this code!
"""
# params were: controller, switches, ips
controller = self.controllers[ 'c0' ]
lg.info( '%s <-> ' % controller.name )
for switchDpid in self.topo.switches():
switch = self.nodes[ switchDpid ]
lg.info( '%s ' % switch.name )
sip = self.topo.ip( switchDpid )#ips.next()
sintf = switch.intfs[ 0 ]
node, cintf = switch.connection[ sintf ]
if node != controller:
lg.error( '*** Error: switch %s not connected to correct'
'controller' %
switch.name )
exit( 1 )
controller.setIP( cintf, self.cparams.ip, '/' +
self.cparams.subnetSize )
switch.setIP( sintf, sip, '/' + self.cparams.subnetSize )
controller.setHostRoute( sip, cintf )
switch.setHostRoute( self.cparams.ip, sintf )
lg.info( '\n' )
lg.info( '*** Testing control network\n' )
while not controller.intfIsUp( controller.intfs[ 0 ] ):
lg.info( '*** Waiting for %s to come up\n',
controller.intfs[ 0 ] )
sleep( 1 )
for switchDpid in self.topo.switches():
switch = self.nodes[ switchDpid ]
while not switch.intfIsUp( switch.intfs[ 0 ] ):
lg.info( '*** Waiting for %s to come up\n' %
switch.intfs[ 0 ] )
sleep( 1 )
if self.ping( hosts=[ switch, controller ] ) != 0:
lg.error( '*** Error: control network test failed\n' )
exit( 1 )
lg.info( '\n' )
def _configHosts( self ):
"Configure a set of hosts."
# params were: hosts, ips
for hostDpid in self.topo.hosts():
host = self.nodes[ hostDpid ]
hintf = host.intfs[ 0 ]
host.setIP( hintf, self.topo.ip( hostDpid ),
'/' + str( self.cparams.subnetSize ) )
host.setDefaultRoute( hintf )
# You're low priority, dude!
quietRun( 'renice +18 -p ' + repr( host.pid ) )
lg.info( '%s ', host.name )
lg.info( '\n' )
def build( self ):
"""Build mininet.
At the end of this function, everything should be connected
and up."""
if self.cleanup:
pass # cleanup
# validate topo?
lg.info( '*** Adding controller\n' )
self._addController( self.controller )
lg.info( '*** Creating network\n' )
lg.info( '*** Adding hosts:\n' )
for host in sorted( self.topo.hosts() ):
self._addHost( host )
lg.info( '0x%x ' % host )
lg.info( '\n*** Adding switches:\n' )
for switch in sorted( self.topo.switches() ):
self._addSwitch( switch )
lg.info( '0x%x ' % switch )
lg.info( '\n*** Adding edges:\n' )
for src, dst in sorted( self.topo.edges() ):
self._addLink( src, dst )
lg.info( '(0x%x, 0x%x) ' % ( src, dst ) )
lg.info( '\n' )
if self.inNamespace:
lg.info( '*** Configuring control network\n' )
self._configureControlNetwork()
lg.info( '*** Configuring hosts\n' )
self._configHosts()
if self.xterms:
self.startXterms()
if self.autoSetMacs:
self.setMacs()
if self.autoStaticArp:
self.staticArp()
def switchNodes( self ):
"Return switch nodes."
return [ self.nodes[ dpid ] for dpid in self.topo.switches() ]
def hostNodes( self ):
"Return host nodes."
return [ self.nodes[ dpid ] for dpid in self.topo.hosts() ]
def startXterms( self ):
"Start an xterm for each node in the topo."
lg.info( "*** Running xterms on %s\n" % os.environ[ 'DISPLAY' ] )
cleanUpScreens()
self.terms += makeXterms( self.controllers.values(), 'controller' )
self.terms += makeXterms( self.switchNodes(), 'switch' )
self.terms += makeXterms( self.hostNodes(), 'host' )
def stopXterms( self ):
"Kill each xterm."
# Kill xterms
for term in self.terms:
os.kill( term.pid, signal.SIGKILL )
cleanUpScreens()
def setMacs( self ):
"""Set MAC addrs to correspond to datapath IDs on hosts.
Assume that the host only has one interface."""
for dpid in self.topo.hosts():
hostNode = self.nodes[ dpid ]
hostNode.setMAC( hostNode.intfs[ 0 ], dpid )
def staticArp( self ):
"Add all-pairs ARP entries to remove the need to handle broadcast."
for src in self.topo.hosts():
srcNode = self.nodes[ src ]
for dst in self.topo.hosts():
if src != dst:
srcNode.setARP( dst, dst )
def start( self ):
"Start controller and switches\n"
lg.info( '*** Starting controller\n' )
for cnode in self.controllers.values():
cnode.start()
lg.info( '*** Starting %s switches\n' % len( self.topo.switches() ) )
for switchDpid in self.topo.switches():
switch = self.nodes[ switchDpid ]
#lg.info( 'switch = %s' % switch )
lg.info( '0x%x ' % switchDpid )
switch.start( self.controllers )
lg.info( '\n' )
def stop( self ):
"Stop the controller(s), switches and hosts\n"
if self.terms:
lg.info( '*** Stopping %i terms\n' % len( self.terms ) )
self.stopXterms()
lg.info( '*** Stopping %i hosts\n' % len( self.topo.hosts() ) )
for hostDpid in self.topo.hosts():
host = self.nodes[ hostDpid ]
lg.info( '%s ' % host.name )
host.terminate()
lg.info( '\n' )
lg.info( '*** Stopping %i switches\n' % len( self.topo.switches() ) )
for switchDpid in self.topo.switches():
switch = self.nodes[ switchDpid ]
lg.info( '%s' % switch.name )
switch.stop()
lg.info( '\n' )
lg.info( '*** Stopping controller\n' )
for cnode in self.controllers.values():
cnode.stop()
lg.info( '*** Test complete\n' )
def run( self, test, **params ):
"Perform a complete start/test/stop cycle."
self.start()
lg.info( '*** Running test\n' )
result = getattr( self, test )( **params )
self.stop()
return result
@staticmethod
def _parsePing( pingOutput ):
"Parse ping output and return packets sent, received."
r = r'(\d+) packets transmitted, (\d+) received'
m = re.search( r, pingOutput )
if m == None:
lg.error( '*** Error: could not parse ping output: %s\n' %
pingOutput )
exit( 1 )
sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
return sent, received
def ping( self, hosts=None ):
"""Ping between all specified hosts.
hosts: list of host DPIDs
returns: ploss packet loss percentage"""
#self.start()
# check if running - only then, start?
packets = 0
lost = 0
ploss = None
if not hosts:
hosts = self.topo.hosts()
lg.info( '*** Ping: testing ping reachability\n' )
for nodeDpid in hosts:
node = self.nodes[ nodeDpid ]
lg.info( '%s -> ' % node.name )
for destDpid in hosts:
dest = self.nodes[ destDpid ]
if node != dest:
result = node.cmd( 'ping -c1 ' + dest.IP() )
sent, received = self._parsePing( result )
packets += sent
if received > sent:
lg.error( '*** Error: received too many packets' )
lg.error( '%s' % result )
node.cmdPrint( 'route' )
exit( 1 )
lost += sent - received
lg.info( ( '%s ' % dest.name ) if received else 'X ' )
lg.info( '\n' )
ploss = 100 * lost / packets
lg.info( "*** Results: %i%% dropped (%d/%d lost)\n" %
( ploss, lost, packets ) )
return ploss
def pingAll( self ):
"""Ping between all hosts.
returns: ploss packet loss percentage"""
return self.ping()
def pingPair( self ):
"""Ping between first two hosts, useful for testing.
returns: ploss packet loss percentage"""
hostsSorted = sorted( self.topo.hosts() )
hosts = [ hostsSorted[ 0 ], hostsSorted[ 1 ] ]
return self.ping( hosts=hosts )
@staticmethod
def _parseIperf( iperfOutput ):
"""Parse iperf output and return bandwidth.
iperfOutput: string
returns: result string"""
r = r'([\d\.]+ \w+/sec)'
m = re.search( r, iperfOutput )
if m:
return m.group( 1 )
else:
raise Exception( 'could not parse iperf output' )
def iperf( self, hosts=None, l4Type='TCP', udpBw='10M',
verbose=False ):
"""Run iperf between two hosts.
hosts: list of host DPIDs; if None, uses opposite hosts
l4Type: string, one of [ TCP, UDP ]
verbose: verbose printing
returns: results two-element array of server and client speeds"""
if not hosts:
hostsSorted = sorted( self.topo.hosts() )
hosts = [ hostsSorted[ 0 ], hostsSorted[ -1 ] ]
else:
assert len( hosts ) == 2
host0 = self.nodes[ hosts[ 0 ] ]
host1 = self.nodes[ hosts[ 1 ] ]
lg.info( '*** Iperf: testing ' + l4Type + ' bandwidth between ' )
lg.info( "%s and %s\n" % ( host0.name, host1.name ) )
host0.cmd( 'killall -9 iperf' )
iperfArgs = 'iperf '
bwArgs = ''
if l4Type == 'UDP':
iperfArgs += '-u '
bwArgs = '-b ' + udpBw + ' '
elif l4Type != 'TCP':
raise Exception( 'Unexpected l4 type: %s' % l4Type )
server = host0.cmd( iperfArgs + '-s &' )
if verbose:
lg.info( '%s\n' % server )
client = host1.cmd( iperfArgs + '-t 5 -c ' + host0.IP() + ' ' +
bwArgs )
if verbose:
lg.info( '%s\n' % client )
server = host0.cmd( 'killall -9 iperf' )
if verbose:
lg.info( '%s\n' % server )
result = [ self._parseIperf( server ), self._parseIperf( client ) ]
if l4Type == 'UDP':
result.insert( 0, udpBw )
lg.info( '*** Results: %s\n' % result )
return result
def iperfUdp( self, udpBw='10M' ):
"Run iperf UDP test."
return self.iperf( l4Type='UDP', udpBw=udpBw )
def interact( self ):
"Start network and run our simple CLI."
self.start()
result = CLI( self )
self.stop()
return result