Skip to content
Snippets Groups Projects
Commit 98d4f189 authored by Bob Lantz's avatar Bob Lantz
Browse files

Initial commit - first development version of Mininet

parents
No related branches found
No related tags found
No related merge requests found
cleanup 0 → 100755
#!/bin/bash
echo "Removing all links of the pattern foo-ethX"
for f in `ip link show | egrep -o '(\w+-eth\w+)' ` ; do
cmd="ip link del $f"
echo $smd
$cmd
done
echo "Removing excess controllers/ofprotocols/ofdatapaths/pings"
killall -9 controller ofprotocol ofdatapath ping 2> /dev/null
echo "Removing vconn junk in /tmp"
rm -f /tmp/vconn* /tmp/vlogs* /tmp/*.out /tmp/*.log
#!/usr/bin/python
"""
Mininet: A simple networking testbed for OpenFlow!
Mininet creates a simple test network for OpenFlow by using
process-based virtualization and network namespaces.
This file supports use of either the kernel or user space datapath
from the OpenFlow reference implementation. Up to 32 switches are
supported using the kernel datapath, and 512 (or more) switches are
supported via the user datapath.
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 namespaced parent shell (and possibly some child processes)
Hosts have a network interface which is
configured via ifconfig/ip link/etc. with data network IP
addresses (e.g. 192.168.123.2 )
In kernel datapath mode, the controller and switches are simply
processes in the root namespace.
Kernel OpenFlow datapaths are instantiated using dpctl, 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 switch are full-service
nodes that live in their own network namespace 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,
Thoughts/TBD:
It should be straightforward to add a function to read
OpenFlowVMS spec files, but I haven't done so yet.
For the moment, specifying configurations and tests in Python
is straightforward and concise.
Soon, we'll want to split the various subsystems (core,
cli, tests, etc.) into multiple modules.
We may be able to get better performance by using the kernel
datapath (using its multiple datapath feature on multiple
interfaces.) This would eliminate the ofdatapath user processes.
OpenVSwitch would still run at user level.
Bob Lantz
rlantz@cs.stanford.edu
History:
11/19/09 Initial revision (user datapath only)
12/8/08 Kernel datapath support complete
"""
# Note: this script must be run as root
# Perhaps we should do so automatically!
from subprocess import call, check_call, Popen, PIPE, STDOUT
from time import sleep
import re, os, signal, sys, select
flush = sys.stdout.flush
from resource import setrlimit, RLIMIT_NPROC, RLIMIT_NOFILE
# Utility routines to make it easier to run commands
def run( cmd ):
"Simple interface to subprocess.call()"
return call( cmd.split( ' ' ) )
def checkRun( cmd ):
"Simple interface to subprocess.check_call()"
check_call( cmd.split( ' ' ) )
def quietRun( cmd ):
"Run a command, routing stderr to stdout, and return the output."
popen = Popen( cmd.split( ' '), stdout=PIPE, stderr=STDOUT)
# We can't use Popen.communicate() because it uses
# select(), which can't handle
# high file descriptor numbers! poll() can, however.
output = ''
readable = select.poll()
readable.register( popen.stdout )
while True:
while readable.poll():
data = popen.stdout.read( 1024 )
if len( data ) == 0: break
output += data
popen.poll()
if popen.returncode != None: break
return output
# Command paths
netns = "/usr/local/bin/netns"
bash = "/bin/bash"
ifconfig = "/sbin/ifconfig"
class Node( object ):
"""A virtual network node is simply a shell in a network namespace.
We communicate with it using pipes."""
def __init__( self, name, inNamespace=True ):
self.name = name
closeFds = False # speed vs. memory use
# xpg_echo is needed so we can echo our sentinel in sendCmd
cmd = [ bash, '-O', 'xpg_echo' ]
self.inNamespace = inNamespace
if self.inNamespace: cmd = [ netns ] + cmd
self.shell = Popen( cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT,
close_fds=closeFds )
self.stdin = self.shell.stdin
self.stdout = self.shell.stdout
self.pollOut = select.poll()
self.pollOut.register( self.stdout )
outToNode[ self.stdout ] = self
inToNode[ self.stdin ] = self
self.pid = self.shell.pid
self.intfCount = 0
self.intfs = []
self.ips = {}
self.connection = {}
self.waiting = False
# For sanity check, try bringing up loopback interface
# self.cmd( "ifconfig lo 127.0.0.1 up" )
def read( self, max ): return os.read( self.stdout.fileno(), max )
def write( self, data ): os.write( self.stdin.fileno(), data )
def terminate( self ): os.kill( self.pid, signal.SIGKILL )
def waitReadable( self ): self.pollOut.poll()
def sendCmd( self, cmd ):
"""Send a command, followed by a command to echo a sentinel,
and return without waiting for the command to complete."""
assert not self.waiting
if cmd[ -1 ] == '&':
separator = '&'
cmd = cmd[ : -1 ]
else: separator = ';'
if isinstance( cmd, list): cmd = ' '.join( cmd )
self.write( cmd + separator + " echo -n '\\0177' \n")
self.waiting = True
def monitor( self ):
"Monitor a command's output, returning (done, data)."
assert self.waiting
self.waitReadable()
data = self.read( 1024 )
if len( data ) > 0 and data[ -1 ] == chr( 0177 ):
self.waiting = False
return True, data[ : -1 ]
else:
return False, data
def sendInt( self ):
"Send ^C, hopefully interrupting a running subprocess."
self.write( chr( 3 ) )
def waitOutput( self ):
"""Wait for a command to complete (signaled by a sentinel
character, ASCII(127) appearing in the output stream) and return
the output, including trailing newline."""
assert self.waiting
output = ""
while True:
self.waitReadable()
data = self.read( 1024 )
if len(data) > 0 and data[ -1 ] == chr( 0177 ):
output += data[ : -1 ]
break
else: output += data
self.waiting = False
return output
def cmd( self, cmd ):
"Send a command, wait for output, and return it."
self.sendCmd( cmd )
return self.waitOutput()
def cmdPrint( self, cmd ):
"Call cmd, printing the command and output"
print "***", self.name, ":", cmd
result = self.cmd( cmd )
print result,
return result
def intfName( self, n):
"Construct a canonical interface name node-intf for interface N."
return self.name + '-eth' + `n`
def newIntf( self ):
"Reserve and return a new interface name for this node."
intfName = self.intfName( self.intfCount)
self.intfCount += 1
self.intfs += [ intfName ]
return intfName
def setIP( self, intf, ip, bits ):
"Set an interface's IP address."
result = self.cmd( [ ifconfig, intf, ip + bits, 'up' ] )
self.ips[ intf ] = ip
return result
def setHostRoute( self, ip, intf ):
"Add a route to the given IP address via intf."
return self.cmd( 'route add -host ' + ip + ' dev ' + intf )
def setDefaultRoute( self, intf ):
"Set the default route to go through intf."
self.cmd( 'ip route flush' )
return self.cmd( 'route add default ' + intf )
def IP( self ):
"Return IP address of first interface"
return self.ips[ self.intfs[ 0 ] ]
def __str__( self ):
result = self.name
result += ": IP=" + self.IP() + " intfs=" + self.intfs
result += " waiting=", self.waiting
return result
# Maintain mapping between i/o pipes and nodes
# This could be useful for monitoring multiple nodes
# using select.poll()
inToNode = {}
outToNode = {}
def outputs(): return outToNode.keys()
def nodes(): return outToNode.values()
def inputs(): return [ node.stdin for node in nodes() ]
def nodeFromFile( f ):
node = outToNode.get( f )
return node or inToNode.get( f )
def createNodes( name, count ):
"Create and return a list of nodes."
nodes = [ Node( name + `i` ) for i in range( 0, count ) ]
# print "*** CreateNodes: created:", nodes
return nodes
# Interface management
#
# We connect nodes by creating a pair of veth interfaces,
# and then placing them in the pair of nodes that we want
# to communicate. Interfaces are named nodeN-ethM
def makeIntfPair( intf1, intf2 ):
"Make a veth pair of intf1 and intf2"
# Delete any old interfaces with the same names
quietRun( 'ip link del ' + intf1 )
quietRun( 'ip link del ' + intf2 )
# Create new pair
cmd = 'ip link add name ' + intf1 + ' type veth peer name ' + intf2
return checkRun( cmd )
def moveIntf( intf, node ):
"Move intf to node"
cmd = 'ip link set ' + intf + ' netns ' + `node.pid`
checkRun( cmd )
links = node.cmd( 'ip link show' )
if not intf in links:
print "*** Error: moveIntf:", intf, "not successfully moved to",
print node.name,":"
exit( 1 )
return
def createLink( node1, node2 ):
"Create a link node1-intf1 <---> node2-intf2."
intf1 = node1.newIntf()
intf2 = node2.newIntf()
makeIntfPair( intf1, intf2 )
if node1.inNamespace: moveIntf( intf1, node1 )
if node2.inNamespace: moveIntf( intf2, node2 )
node1.connection[ intf1 ] = ( node2, intf2 )
node2.connection[ intf2 ] = ( node1, intf1 )
return intf1, intf2
# Handy utilities
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:
print "*** Error: could not parse ping output:", pingOutput
exit( 1 )
sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
return sent, received
def pingTest( hosts, verbose=False ):
"Test that each host can reach every other host."
packets = 0 ; lost = 0
for node in hosts:
if verbose:
print node.name, "->", ; flush()
for dest in hosts:
if node != dest:
result = node.cmd( 'ping -c1 ' + dest.IP() )
sent, received = parsePing( result )
packets += sent
if received > sent:
print "*** Error: received too many packets"
print result
node.cmdPrint( 'route' )
exit( 1 )
lost += sent - received
if verbose:
print ( dest.name if received else "X" ), ; flush()
if verbose: print
ploss = 100 * lost/packets
if verbose:
print "%d%% packet loss (%d/%d lost)" % ( ploss, lost, packets )
flush()
return ploss
def dumpNodes( nodes ):
"Dump ifconfig of each node."
for node in nodes:
print "*** Dumping node", node.name
print node.cmd( 'ip link show' )
print node.cmd( 'route' )
def ipGen( A, B, c, d ):
"Generate next IP class B IP address, starting at A.B.c.d"
while True:
yield '%d.%d.%d.%d' % ( A, B, c, d )
d += 1
if d > 254:
d = 1
c += 1
if c > 254: break
def nameGen( prefix ):
"Generate names starting with prefix."
i = 0
while True:
yield prefix + `i`
i += 1
# Control network support
# Instead of routing, we could bridge or use "in-band" control
def checkUp( node ):
"Make sure node's first interface is up."
return 'UP' in node.cmd( 'ifconfig ' + node.intfs[ 0 ] )
def configRoutedControlNetwork( controller, switches ):
"Configure a routed control network on controller and switches."
cip = '10.0.0.1'
sips = ipGen( 10, 123, 0, 1)
print controller.name, '<->',
for switch in switches:
print switch.name, ; flush()
sip = sips.next()
sintf = switch.intfs[ 0 ]
node, cintf = switch.connection[ sintf ]
assert node == controller
controller.setIP( cintf, cip, '/24')
switch.setIP( sintf, sip, '/24' )
controller.setHostRoute( sip, cintf )
switch.setHostRoute( cip, sintf )
print
print "*** Testing control network"
while not checkUp( controller ):
print "*** Waiting for ", controller.intfs[ 0 ], "to come up"
sleep( 1 )
for switch in switches:
while not checkUp( switch ):
print "*** Waiting for ", controller.intfs[ 0 ], "to come up"
sleep( 1 )
if pingTest( [ switch, controller ] ) != 0:
print "*** Error: control network test failed"
else:
return
exit( 1 )
def configHosts( hosts, ( a, b, c, d ) ):
"Configure a set of hosts, starting at IP address a.b.c.d"
ips = ipGen( a, b, c, d )
for host in hosts:
hintf = host.intfs[ 0 ]
host.setIP( hintf, ips.next(), '/24' )
host.setDefaultRoute( hintf )
# You're low priority, dude!
quietRun( 'renice +18 -p ' + `host.pid` )
print host.name, ; flush()
print
def startController( controller, cprog='controller', cargs='ptcp:' ):
"Start <cprog cargs> on controller, logging to /tmp/cN.log"
cout = '/tmp/' + controller.name + '.log'
controller.cmdPrint( cprog + ' ' + cargs +
' 1> ' + cout + ' 2> ' + cout + ' &' )
def stopController( controller, cprog='controller' ):
"Stop controller cprog on controller"
controller.cmd( "kill %" + cprog )
def startOpenFlowU( switch, controller ):
"""Start OpenFlow reference user datapath on a switch,
logging to /tmp/sN-{ofd,ofp}.log"""
ofdlog = '/tmp/' + switch.name + '-ofd.log'
ofplog = '/tmp/' + switch.name + '-ofp.log'
switch.cmd( 'ifconfig lo up' )
intfs = switch.intfs[ 1 : ] # 0 is mgmt interface
switch.cmdPrint( 'ofdatapath -i ' + ','.join( intfs ) +
' ptcp: 1> ' + ofdlog + ' 2> '+ ofdlog + ' &' )
switch.cmdPrint( 'ofprotocol tcp:' + controller.IP() +
' tcp:localhost 1> ' + ofplog + ' 2>' + ofplog + '&' )
def stopOpenFlowU( switch ):
"Stop OpenFlow reference user datapath on a switch."
switch.cmd( "kill %ofdatapath" )
switch.cmd( "kill %ofprotocol" )
def dpgen():
"Generator for OpenFlow kernel datapath names."
dpCount = 0
while True:
yield 'nl:' + `dpCount`
dpCount += 1
def startOpenFlowK( switch, dp, controller):
"Start up a switch connected to an OpenFlow reference kernel datapath."
ofplog = '/tmp/' + switch.name + '-ofp.log'
switch.cmd( 'ifconfig lo up' )
# Delete local datapath if it exists;
# then create a new one monitoring the given interfaces
quietRun( 'dpctl deldp ' + dp )
switch.cmdPrint( 'dpctl adddp ' + dp )
switch.cmdPrint( 'dpctl addif ' + dp + ' ' + ' '.join( switch.intfs ) )
switch.dp = dp
# Become protocol daemon
switch.cmdPrint( 'exec ofprotocol' +
' ' + dp + ' tcp:127.0.0.1 1> ' + ofplog + ' 2>' + ofplog + '&' )
def stopOpenFlowK( switch ):
"Terminate a switch using OpenFlow reference kernel datapath."
quietRun( 'dpctl deldp ' + switch.dp )
for intf in switch.intfs: quietRun( 'ip link del ' + intf )
switch.terminate()
def stopOpenFlow( switch ):
if hasattr(switch, 'dp' ): stopOpenFlowK( switch )
else: stopOpenFlowU( switch )
# Test scenarios and topologies
def treeNet( controller, depth, fanout, snames=nameGen( 's' ),
hnames=nameGen( 'h' ), kernel=True ):
"""Return a tree network of the given depth and fanout as a triple:
( root, switches, hosts ), using the given switch and host
name generators, with the switches connected to the given
controller."""
if ( depth == 0 ):
host = Node( hnames.next() )
print host.name, ; flush()
return host, [], [ host ]
switch = Node( snames.next(), inNamespace=(not kernel) )
if not kernel: createLink( switch, controller )
print switch.name, ; flush()
switches, hosts = [ switch ], []
for i in range( 0, fanout ):
child, slist, hlist = treeNet(
controller, depth - 1, fanout, snames, hnames, kernel )
createLink( switch, child )
switches += slist
hosts += hlist
return switch, switches, hosts
def treeNetTest( depth, fanout, test, kernel=True ):
"""Create a tree network of the given depth and fanout, and
run test( controller, root, switches, hosts ) on it."""
if kernel: print "*** Using kernel datapath"
else: print "*** Using user datapath"
print "*** Creating controller"
controller = Node( 'c0', inNamespace=( not kernel ) )
print "*** Creating tree network depth:", depth, "fanout:", fanout
root, switches, hosts = treeNet( controller, depth, fanout, kernel=kernel )
print
if not kernel:
print "*** Configuring control network"
configRoutedControlNetwork( controller, switches )
else: dp = dpgen()
print "*** Configuring hosts"
configHosts( hosts, ( 192, 168, 123, 1 ) )
print "*** Starting reference controller"
startController( controller )
print "*** Starting switches"
for switch in switches:
if kernel: startOpenFlowK( switch, dp.next(), controller )
else: startOpenFlowU( switch, controller )
print "*** Running test"
test( controller, root, switches, hosts )
print "*** Stopping controller"
stopController( controller )
print "*** Stopping switches"
for switch in switches:
stopOpenFlow( switch )
def treePingTest( depth, fanout, kernel=True ):
"Run a ping test on a tree network with the given depth and fanout."
test = lambda c, r, s, hosts : pingTest( hosts, verbose=True )
treeNetTest( depth, fanout, test, kernel)
def iperf( hosts ):
"Run iperf between two hosts."
assert len( hosts ) == 2
host1, host2 = hosts[ 0 ], hosts[ 1 ]
dumpNodes( [ host1, host2 ] )
host1.cmdPrint( 'killall -9 iperf')
host1.cmdPrint( 'iperf -s &' )
host2.cmdPrint( 'iperf -t 5 -c ' + host1.IP() )
host1.cmdPrint( 'kill -9 %iperf' )
def iperfTest( depth=1, fanout=2, kernel=True ):
"Simple iperf test between two hosts."
def test( c, r, s, hosts ):
h0, hN = hosts[ 0 ], hosts[ -1 ]
print "*** iperfTest: Testing bandwidth between",
print h0.name, "and", hN.name
return iperf( [ h0, hN] )
treeNetTest( depth, fanout, test, kernel )
def fixLimits():
"Fix ridiculously small resource limits."
setrlimit( RLIMIT_NPROC, ( 4096, 8192 ) )
setrlimit( RLIMIT_NOFILE, ( 16384, 32768 ) )
# Simple CLI
def cliHelp( nodemap, c, s, h, args ):
"Semi-useful help for CLI"
print "available commands are:", cliCmds.keys()
def cliNodes( nodemap, c, s, h, args ):
"List available nodes"
print "available nodes are:", nodemap.keys()
def cliSh( nodemap, c, s, h, args ):
"Run an external shell command"
call( [ bash, '-c', args ] )
def cliPingTest( map, c, s, hosts, args ):
pingTest( hosts, verbose=True )
def cliNet( map, c, switches, h, args ):
for switch in switches:
print switch.name, "<->",
for intf in switch.intfs:
node, rintf = switch.connection[ intf ]
print node.name,
print
def cliIperf( map, c, switches, h, args ):
print "iperf: got args", args
if len( args ) != 2:
print "usage: iperf <h1> <h2>"
return
for host in args:
if host not in map:
print "iperf: cannot find host:", host
return
iperf( [ map[ h ] for h in args ] )
cliCmds = { '?': cliHelp, 'help': cliHelp, 'net': cliNet, 'nodes': cliNodes,
'pingtest': cliPingTest, 'iperf': cliIperf, 'sh': cliSh,
'exit': None }
def cli( controllers, switches, hosts ):
"Simple command-line interface to talk to nodes."
print "*** cli: starting"
nodemap = {}
nodes = controllers + switches + hosts
for node in nodes:
nodemap[ node.name ] = node
while True:
print "mininet> ", ; flush()
input = sys.stdin.readline()
if input == '': break
if input[ -1 ] == '\n': input = input[ : -1 ]
cmd = input.split( ' ' )
first = cmd[ 0 ]
rest = cmd[ 1: ]
if first in cliCmds: cliCmds[ first ](
nodemap, controllers, switches, hosts, rest )
elif first in nodemap and rest != []:
node = nodemap[ first ]
# Substitute IP addresses for node names in command
rest = [ nodemap[ arg ].IP() if arg in nodemap else arg
for arg in rest ]
rest = ' '.join( rest )
# Interactive commands don't work yet, and
# there are still issues with control-c
print "***", node.name, ": running", rest
node.sendCmd( rest )
while True:
try:
done, data = node.monitor()
print data,
if done: break
except KeyboardInterrupt: node.sendInt()
print
elif first == '': pass
elif first in [ 'exit', 'quit' ]: break
else: print "cli: unknown node or command: <", first, ">"
print "*** cli: exiting"
def treeInteract( depth, fanout, kernel=True ):
"Create a tree network and start the CLI."
interact = lambda c, r, s, h : cli( [ c ], s, h )
treeNetTest( depth, fanout, interact, kernel )
if __name__ == '__main__':
fixLimits()
# for kernel in [ False, True ]:
# treePingTest( depth=3, fanout=4, kernel=kernel )
# treeInteract( depth=1, fanout=2, kernel=False )
# iperfTest( depth=1, fanout=2, kernel=kernel )
treeInteract( depth=1, fanout=2, kernel=False )
\ No newline at end of file
netns.c 0 → 100644
/* netns: run a command in a network namespace.
* Simplified from netunshare.c on lxc.sf.net
*/
#include <stdio.h>
#include <sched.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
if (unshare(CLONE_NEWNET) == -1) {
perror("unshare");
return 1;
}
if (argc) {
execve(argv[1], &argv[1], __environ);
perror("execve");
return 1;
}
return 0;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment