Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9bd75bdfb6 | |||
| fd97ab4462 | |||
| 5ee31199c1 | |||
| 2ecc637ff4 | |||
| 62d1121be7 | |||
| c522ba2cee | |||
| a598baf943 | |||
| 79aa54096e | |||
| 5175bc7b9a | |||
| 8badfe4879 | |||
| 98bd9ad2f2 | |||
| 21506f909c | |||
| 72c1275078 | |||
| e3ac746a39 | |||
| a114eb78a0 | |||
| 7924b15331 | |||
| 48131b13b8 | |||
| 277e4d2e09 | |||
| 81da901b6b | |||
| 5916830268 | |||
| 6f00369dff | |||
| 7c83f036df | |||
| b340488fdc | |||
| fc462e3bdd | |||
| 76f3db36a2 | |||
| 160b665d09 | |||
| 29f15487ea | |||
| a9c3844830 | |||
| eea5d0b602 | |||
| 1687605cf0 | |||
| 0a5c1f6026 | |||
| c46608710f | |||
| 020f276d10 | |||
| 43aa659e08 | |||
| 034c16d95a | |||
| d30f7bd931 | |||
| 90281b1d44 | |||
| b141594a29 | |||
| 9b86b5b7de | |||
| 1d2ce39a01 | |||
| 7dee9ce563 | |||
| 24dd285b1a | |||
| 2b7eb2ed99 | |||
| 7dae4e4b44 | |||
| 5960e4ef89 | |||
| 4d4d4719ff | |||
| e67c9d9ea7 | |||
| 4c70429587 | |||
| d830b2331b | |||
| c616476c33 | |||
| 1c1fa3af96 | |||
| 0a9a5af67b | |||
| 57ba8bcd2e | |||
| c447d17362 | |||
| 7ff615acea | |||
| 7263c16e89 |
@@ -29,11 +29,11 @@ from mininet.node import ( Host, CPULimitedHost, Controller, OVSController,
|
||||
DefaultController, NullController,
|
||||
UserSwitch, OVSSwitch, OVSBridge,
|
||||
IVSSwitch )
|
||||
from mininet.nodelib import LinuxBridge
|
||||
from mininet.nodelib import LinuxBridge, Server
|
||||
from mininet.link import Link, TCLink, TCULink, OVSLink
|
||||
from mininet.topo import ( SingleSwitchTopo, LinearTopo,
|
||||
SingleSwitchReversedTopo, MinimalTopo )
|
||||
from mininet.topolib import TreeTopo, TorusTopo
|
||||
import mininet.topolib
|
||||
from mininet.util import customClass, specialClass, splitArgs
|
||||
from mininet.util import buildTopo
|
||||
|
||||
@@ -53,9 +53,8 @@ TOPODEF = 'minimal'
|
||||
TOPOS = { 'minimal': MinimalTopo,
|
||||
'linear': LinearTopo,
|
||||
'reversed': SingleSwitchReversedTopo,
|
||||
'single': SingleSwitchTopo,
|
||||
'tree': TreeTopo,
|
||||
'torus': TorusTopo }
|
||||
'single': SingleSwitchTopo }
|
||||
TOPOS.update( mininet.topolib.topos)
|
||||
|
||||
SWITCHDEF = 'default'
|
||||
SWITCHES = { 'user': UserSwitch,
|
||||
@@ -69,6 +68,7 @@ SWITCHES = { 'user': UserSwitch,
|
||||
|
||||
HOSTDEF = 'proc'
|
||||
HOSTS = { 'proc': Host,
|
||||
'server': Server,
|
||||
'rt': specialClass( CPULimitedHost, defaults=dict( sched='rt' ) ),
|
||||
'cfs': specialClass( CPULimitedHost, defaults=dict( sched='cfs' ) ) }
|
||||
|
||||
|
||||
+11
-7
@@ -157,7 +157,7 @@ class RemoteMixin( object ):
|
||||
'-o', 'ForwardAgent=yes', '-tt' ]
|
||||
|
||||
def __init__( self, name, server='localhost', user=None, serverIP=None,
|
||||
controlPath=False, splitInit=False, **kwargs):
|
||||
controlPath=True, splitInit=False, **kwargs):
|
||||
"""Instantiate a remote node
|
||||
name: name of remote node
|
||||
server: remote server (optional)
|
||||
@@ -213,7 +213,7 @@ class RemoteMixin( object ):
|
||||
def startShell( self, *args, **kwargs ):
|
||||
"Start a shell process for running commands"
|
||||
if self.isRemote:
|
||||
kwargs.update( mnopts='-c' )
|
||||
kwargs.update( mnopts='-cp' )
|
||||
super( RemoteMixin, self ).startShell( *args, **kwargs )
|
||||
# Optional split initialization
|
||||
self.sendCmd( 'echo $$' )
|
||||
@@ -386,7 +386,8 @@ class RemoteLink( Link ):
|
||||
if server1 == server2:
|
||||
# Link within same server
|
||||
return Link.makeIntfPair( intfname1, intfname2, addr1, addr2,
|
||||
node1, node2, deleteIntfs=deleteIntfs )
|
||||
node1, node2, deleteIntfs=deleteIntfs,
|
||||
runCmd=None )
|
||||
# Otherwise, make a tunnel
|
||||
self.tunnel = self.makeTunnel( node1, node2, intfname1, intfname2,
|
||||
addr1, addr2 )
|
||||
@@ -844,10 +845,13 @@ class MininetCluster( Mininet ):
|
||||
"Patch to update IP address to global IP address"
|
||||
controller = Mininet.addController( self, *args, **kwargs )
|
||||
# Update IP address for controller that may not be local
|
||||
if ( isinstance( controller, Controller)
|
||||
and controller.IP() == '127.0.0.1'
|
||||
and ' eth0:' in controller.cmd( 'ip link show' ) ):
|
||||
Intf( 'eth0', node=controller ).updateIP()
|
||||
if ( isinstance( controller, Controller )
|
||||
and controller.IP() == '127.0.0.1' ):
|
||||
links = controller.cmd( 'ip link show' )
|
||||
eth0 = re.findall( ' (.*eth0):', links )
|
||||
if not eth0:
|
||||
raise Exception( 'Cannot find IP address for controller eth0' )
|
||||
Intf( eth0[ 0 ], node=controller ).updateIP()
|
||||
return controller
|
||||
|
||||
def buildFromTopo( self, *args, **kwargs ):
|
||||
|
||||
Executable
+86
@@ -0,0 +1,86 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
fakecluster.py: a fake cluster for testing Mininet cluster edition!!!
|
||||
|
||||
We are going to self-host Mininet by creating a virtual cluster
|
||||
for cluster edition.
|
||||
|
||||
Note: ssh is kind of a mess - you end up having to do things
|
||||
like h1 sudo -E -u openflow ssh 10.2
|
||||
"""
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.nodelib import LinuxBridge, Server
|
||||
from mininet.cli import CLI
|
||||
from mininet.topo import Topo, SingleSwitchTopo
|
||||
from mininet.log import setLogLevel, warn
|
||||
from mininet.util import errRun, quietRun
|
||||
from mininet.link import Link
|
||||
|
||||
from functools import partial
|
||||
from sys import argv
|
||||
|
||||
class MininetServer( Server ):
|
||||
"A server (for nested Mininet) that runs ssh and ovs"
|
||||
|
||||
privateDirs = [ '/var/run/sshd', '/etc/openvswitch',
|
||||
'/var/run/openvswitch', '/var/log/openvswitch' ]
|
||||
|
||||
def __init__( self, *args, **kwargs ):
|
||||
"Turn on ovs by default"
|
||||
kwargs.setdefault( 'ovs', True )
|
||||
super( MininetServer, self ).__init__( *args, **kwargs )
|
||||
|
||||
def config( self, **kwargs ):
|
||||
"""Configure/start sshd and other stuff
|
||||
ovs: start Open vSwitch?"""
|
||||
self.ovs = kwargs.get( 'ovs' )
|
||||
super( MininetServer, self ).config( **kwargs )
|
||||
if self.ovs:
|
||||
self.service( 'openvswitch-switch start' )
|
||||
|
||||
def terminate( self, *args, **kwargs ):
|
||||
"Shut down services and terminate server"
|
||||
if self.ovs:
|
||||
self.service( 'openvswitch-switch stop' )
|
||||
super( MininetServer, self ).terminate( *args, **kwargs )
|
||||
|
||||
|
||||
class ServerLink( Link ):
|
||||
def intfName( self, node, n ):
|
||||
"Override to avoid destruction by cleanup!"
|
||||
# This is kind of ugly... for some reason 'eth0' fails so
|
||||
# we just use 'm1eth0'; however, this should nest reasonably.
|
||||
return ( node.name + 'eth' + repr( n ) if isinstance( node, Server )
|
||||
else node.name + '-eth' + repr( n ) )
|
||||
def makeIntfPair( self, *args, **kwargs ):
|
||||
"Override to use quietRun"
|
||||
kwargs.update( runCmd=quietRun )
|
||||
super( ServerLink, self ).makeIntfPair( *args, **kwargs )
|
||||
|
||||
class ClusterTopo( Topo ):
|
||||
"Cluster topology: m1..mN"
|
||||
def build( self, n ):
|
||||
ms1 = self.addSwitch( 'ms1' )
|
||||
for i in range( 1, n + 1 ):
|
||||
h = self.addHost( 'm%d' % i )
|
||||
self.addLink( h, ms1, cls=ServerLink )
|
||||
|
||||
|
||||
def test( serverCount ):
|
||||
"Test this setup"
|
||||
setLogLevel( 'info' )
|
||||
topo = ClusterTopo( serverCount )
|
||||
host = partial( MininetServer, ssh=True, ovs=True)
|
||||
net = Mininet( topo=topo, host=host, switch=LinuxBridge, ipBase='10.0/24' )
|
||||
MininetServer.updateHostsFiles( net.hosts )
|
||||
# addNAT().configDefault() also connects root namespace to Mininet
|
||||
net.addNAT().configDefault()
|
||||
net.start()
|
||||
CLI( net )
|
||||
net.stop()
|
||||
|
||||
if __name__ == '__main__':
|
||||
n = 8 if len( argv ) != 2 else int( argv[ 1 ] )
|
||||
test( n )
|
||||
+5
-5
@@ -25,15 +25,15 @@ def sh( cmd ):
|
||||
|
||||
def killprocs( pattern ):
|
||||
"Reliably terminate processes matching a pattern (including args)"
|
||||
sh( 'pkill -9 -f %s' % pattern )
|
||||
# Make sure they are gone
|
||||
while True:
|
||||
try:
|
||||
pids = co( [ 'pgrep', '-f', pattern ] )
|
||||
pids = co( [ 'pgrep', '-f', pattern ] ).split( '\n' )
|
||||
except CalledProcessError:
|
||||
pids = ''
|
||||
pids = []
|
||||
# Don't kill init
|
||||
pids = [ pid for pid in pids if pid and pid != '1' ]
|
||||
if pids:
|
||||
sh( 'pkill -9 -f %s' % pattern )
|
||||
sh( "kill -9 %s" % ' '.join( pids ) )
|
||||
time.sleep( .5 )
|
||||
else:
|
||||
break
|
||||
|
||||
+6
-3
@@ -25,7 +25,9 @@ Link: basic link class for creating veth pairs
|
||||
"""
|
||||
|
||||
from mininet.log import info, error, debug
|
||||
from mininet.util import makeIntfPair
|
||||
from mininet.util import makeIntfPair, quietRun
|
||||
import mininet.node
|
||||
|
||||
import re
|
||||
|
||||
class Intf( object ):
|
||||
@@ -479,7 +481,8 @@ class Link( object ):
|
||||
|
||||
@classmethod
|
||||
def makeIntfPair( cls, intfname1, intfname2, addr1=None, addr2=None,
|
||||
node1=None, node2=None, deleteIntfs=True ):
|
||||
node1=None, node2=None, deleteIntfs=True,
|
||||
runCmd=quietRun ):
|
||||
"""Create pair of interfaces
|
||||
intfname1: name for interface 1
|
||||
intfname2: name for interface 2
|
||||
@@ -492,7 +495,7 @@ class Link( object ):
|
||||
# Leave this as a class method for now
|
||||
assert cls
|
||||
return makeIntfPair( intfname1, intfname2, addr1, addr2, node1, node2,
|
||||
deleteIntfs=deleteIntfs )
|
||||
deleteIntfs=deleteIntfs, runCmd=runCmd )
|
||||
|
||||
def delete( self ):
|
||||
"Delete this link"
|
||||
|
||||
+2
-2
@@ -89,7 +89,7 @@ method may be called to shut down the network.
|
||||
import os
|
||||
import re
|
||||
import select
|
||||
import signal
|
||||
from signal import SIGKILL
|
||||
import random
|
||||
|
||||
from time import sleep
|
||||
@@ -524,7 +524,7 @@ class Mininet( object ):
|
||||
def stopXterms( self ):
|
||||
"Kill each xterm."
|
||||
for term in self.terms:
|
||||
os.kill( term.pid, signal.SIGKILL )
|
||||
term.send_signal( SIGKILL )
|
||||
cleanUpScreens()
|
||||
|
||||
def staticArp( self ):
|
||||
|
||||
+99
-33
@@ -74,10 +74,11 @@ class Node( object ):
|
||||
|
||||
portBase = 0 # Nodes always start with eth0/port0, even in OF 1.0
|
||||
|
||||
def __init__( self, name, inNamespace=True, **params ):
|
||||
def __init__( self, name, **params ):
|
||||
"""name: name of node
|
||||
inNamespace: in network namespace?
|
||||
ns: private namespaces to use ['net','mnt']
|
||||
privateDirs: list of private directory strings or tuples
|
||||
overlayDirs: list of overlay directory strings or tuples
|
||||
params: Node parameters (see config() for details)"""
|
||||
|
||||
# Make sure class actually works
|
||||
@@ -85,7 +86,14 @@ class Node( object ):
|
||||
|
||||
self.name = params.get( 'name', name )
|
||||
self.privateDirs = params.get( 'privateDirs', [] )
|
||||
self.inNamespace = params.get( 'inNamespace', inNamespace )
|
||||
self.overlayDirs = params.get( 'overlayDirs', [] )
|
||||
|
||||
# Support old inNamespace param
|
||||
self.ns = params.get( 'ns', ( 'net', 'mnt' ) )
|
||||
inNamespace = params.get( 'inNamespace', True )
|
||||
if not inNamespace:
|
||||
self.ns = []
|
||||
self.inNamespace = 'net' in self.ns
|
||||
|
||||
# Stash configuration parameters for future reference
|
||||
self.params = params
|
||||
@@ -102,8 +110,9 @@ class Node( object ):
|
||||
self.waiting = False
|
||||
self.readbuf = ''
|
||||
|
||||
# Start command interpreter shell
|
||||
# Start command interpreter shell and mount any local dirs
|
||||
self.startShell()
|
||||
self.mountOverlayDirs()
|
||||
self.mountPrivateDirs()
|
||||
|
||||
# File descriptor to node mapping support
|
||||
@@ -120,17 +129,21 @@ class Node( object ):
|
||||
node = cls.outToNode.get( fd )
|
||||
return node or cls.inToNode.get( fd )
|
||||
|
||||
_marker = re.compile( chr( 1 ) + r'(\d+)\r\n' )
|
||||
|
||||
# Command support via shell process in namespace
|
||||
def startShell( self, mnopts=None ):
|
||||
"Start a shell process for running commands"
|
||||
if self.shell:
|
||||
error( "%s: shell is already running\n" % self.name )
|
||||
return
|
||||
# mnexec: (c)lose descriptors, (d)etach from tty,
|
||||
# (p)rint pid, and run in (n)amespace
|
||||
opts = '-cd' if mnopts is None else mnopts
|
||||
if self.inNamespace:
|
||||
opts += 'n'
|
||||
# mnexec: (c)lose descriptors
|
||||
# (p)rint pid, and run in (n)etwork and (m)ount namespace
|
||||
opts = '-cdp' if mnopts is None else mnopts
|
||||
# Handle additional namespaces if specified
|
||||
nsmap = { 'pid': 'P', 'mnt': 'm', 'net': 'n', 'uts': 'u' }
|
||||
chars = [ nsmap.get( ns, '' ) for ns in self.ns ]
|
||||
opts += ''.join( chars )
|
||||
# bash -i: force interactive
|
||||
# -s: pass $* to shell, and make process easy to find in ps
|
||||
# prompt is set to sentinel chr( 127 )
|
||||
@@ -157,17 +170,19 @@ class Node( object ):
|
||||
self.lastPid = None
|
||||
self.readbuf = ''
|
||||
# Wait for prompt
|
||||
while True:
|
||||
data = self.read( 1024 )
|
||||
if data[ -1 ] == chr( 127 ):
|
||||
break
|
||||
self.pollOut.poll()
|
||||
self.waiting = False
|
||||
self.waiting = True
|
||||
self.waitOutput()
|
||||
if 'P' in opts:
|
||||
assert self.lastPid is not None
|
||||
self.pid = self.lastPid
|
||||
# +m: disable job control notification
|
||||
self.cmd( 'unset HISTFILE; stty -echo; set +m' )
|
||||
initcmd = 'unset HISTFILE; stty -echo; set +m'
|
||||
if 'uts' in self.ns:
|
||||
initcmd += '; hostname ' + self.name
|
||||
self.cmd( initcmd )
|
||||
|
||||
def mountPrivateDirs( self ):
|
||||
"mount private directories"
|
||||
"Mount private directories"
|
||||
# Avoid expanding a string into a list of chars
|
||||
assert not isinstance( self.privateDirs, basestring )
|
||||
for directory in self.privateDirs:
|
||||
@@ -185,13 +200,61 @@ class Node( object ):
|
||||
self.cmd( 'mount -n -t tmpfs tmpfs %s' % directory )
|
||||
|
||||
def unmountPrivateDirs( self ):
|
||||
"mount private directories"
|
||||
"Unmount private and overlay directories"
|
||||
for directory in self.privateDirs:
|
||||
if isinstance( directory, tuple ):
|
||||
self.cmd( 'umount ', directory[ 0 ] )
|
||||
else:
|
||||
self.cmd( 'umount ', directory )
|
||||
|
||||
# XXX We should make overlayDirs as consistent as possible
|
||||
# with privateDirs.
|
||||
|
||||
def _overlayFrom( self, entry ):
|
||||
"Helper function: return mountpaint, overlay, tmpfs from entry"
|
||||
if type( entry ) is str:
|
||||
# '/mountpoint'
|
||||
mountpoint, overlay = entry, None
|
||||
elif len( entry ) is 1:
|
||||
# [ '/mountpoint' ]
|
||||
mountpoint, overlay = entry[ 0 ], None
|
||||
else:
|
||||
# [ '/mountpoint', '/overlay' ]
|
||||
mountpoint, overlay = entry
|
||||
tmpfs = None if overlay else '/tmp/%s/%s' % ( self, mountpoint )
|
||||
return mountpoint, overlay, tmpfs
|
||||
|
||||
def mountOverlayDirs( self ):
|
||||
"""Mount overlay directories. Overlay directories are similar
|
||||
to private directories except they are copy-on-write copies
|
||||
of directories in the host file system.
|
||||
overlayDirs is of the form ((mountpoint,overlaydir), ...)
|
||||
much like privateDirs. If overlaydir doesn't exist, we
|
||||
mount a tmpfs at the specified mount point."""
|
||||
# Avoid expanding a string into a list of chars
|
||||
assert not isinstance( self.overlayDirs, basestring )
|
||||
for entry in self.overlayDirs:
|
||||
mountpoint, overlay, tmpfs = self._overlayFrom( entry )
|
||||
# Create tmpfs if overlay dir is not specified
|
||||
if not overlay:
|
||||
overlay = tmpfs
|
||||
self.cmd( 'mkdir -p', overlay )
|
||||
self.cmd( 'mount -t tmpfs tmpfs', overlay )
|
||||
# Mount overlay dir at mount point
|
||||
self.cmd( ( 'mount -t overlayfs -o upperdir=%s,lowerdir=%s'
|
||||
' overlayfs %s' ) % ( overlay, mountpoint, mountpoint ) )
|
||||
|
||||
def unmountOverlayDirs( self ):
|
||||
"Unmount overlay directories"
|
||||
for entry in self.overlayDirs:
|
||||
mountpoint, overlay, tmpfs = self._overlayFrom( entry )
|
||||
# Unfortunately these umounts can fail if the mount point
|
||||
# is in use, possibly leaving tmpfs garbage in the root
|
||||
# mount namespace / file system
|
||||
self.cmd( 'umount', mountpoint )
|
||||
if not overlay:
|
||||
self.cmd( 'umount', tmpfs )
|
||||
|
||||
def _popen( self, cmd, **params ):
|
||||
"""Internal method: spawn and return a process
|
||||
cmd: command to run (list)
|
||||
@@ -245,9 +308,10 @@ class Node( object ):
|
||||
def terminate( self ):
|
||||
"Send kill signal to Node and clean up after it."
|
||||
self.unmountPrivateDirs()
|
||||
self.unmountOverlayDirs()
|
||||
if self.shell:
|
||||
if self.shell.poll() is None:
|
||||
os.killpg( self.shell.pid, signal.SIGHUP )
|
||||
os.killpg( self.pid, signal.SIGHUP )
|
||||
self.cleanup()
|
||||
|
||||
def stop( self, deleteIntfs=False ):
|
||||
@@ -294,10 +358,10 @@ class Node( object ):
|
||||
self.lastPid = None
|
||||
self.waiting = True
|
||||
|
||||
def sendInt( self, intr=chr( 3 ) ):
|
||||
def sendInt( self, signal=signal.SIGINT ):
|
||||
"Interrupt running command."
|
||||
debug( 'sendInt: writing chr(%d)\n' % ord( intr ) )
|
||||
self.write( intr )
|
||||
debug( "sending signal %d to pgrp %d" % ( signal, self.pid ) )
|
||||
os.killpg( self.pid, signal )
|
||||
|
||||
def monitor( self, timeoutms=None, findPid=True ):
|
||||
"""Monitor and return the output of a command.
|
||||
@@ -310,18 +374,18 @@ class Node( object ):
|
||||
data = self.read( 1024 )
|
||||
pidre = r'\[\d+\] \d+\r\n'
|
||||
# Look for PID
|
||||
marker = chr( 1 ) + r'\d+\r\n'
|
||||
if findPid and chr( 1 ) in data:
|
||||
# suppress the job and PID of a backgrounded command
|
||||
if re.findall( pidre, data ):
|
||||
data = re.sub( pidre, '', data )
|
||||
# Marker can be read in chunks; continue until all of it is read
|
||||
while not re.findall( marker, data ):
|
||||
while True:
|
||||
markers = self._marker.findall( data )
|
||||
if markers:
|
||||
self.lastPid = int( markers[ -1 ] )
|
||||
data = self._marker.sub( '', data )
|
||||
break
|
||||
data += self.read( 1024 )
|
||||
markers = re.findall( marker, data )
|
||||
if markers:
|
||||
self.lastPid = int( markers[ 0 ][ 1: ] )
|
||||
data = re.sub( marker, '', data )
|
||||
# Look for sentinel/EOF
|
||||
if len( data ) > 0 and data[ -1 ] == chr( 127 ):
|
||||
self.waiting = False
|
||||
@@ -387,6 +451,7 @@ class Node( object ):
|
||||
# Shell requires a string, not a list!
|
||||
if defaults.get( 'shell', False ):
|
||||
cmd = ' '.join( cmd )
|
||||
debug( cmd, defaults )
|
||||
popen = self._popen( cmd, **defaults )
|
||||
return popen
|
||||
|
||||
@@ -642,6 +707,8 @@ class Node( object ):
|
||||
def setup( cls ):
|
||||
"Make sure our class dependencies are available"
|
||||
pathCheck( 'mnexec', 'ifconfig', moduleName='Mininet')
|
||||
if '-m:' not in quietRun( 'mnexec -h' ):
|
||||
raise Exception( 'Please update mnexec (e.g. make install)' )
|
||||
|
||||
class Host( Node ):
|
||||
"A host is simply a Node"
|
||||
@@ -872,7 +939,7 @@ class Switch( Node ):
|
||||
self.dpid = self.defaultDpid( dpid )
|
||||
self.opts = opts
|
||||
self.listenPort = listenPort
|
||||
if not self.inNamespace:
|
||||
if 'net' not in self.ns:
|
||||
self.controlIntf = Intf( 'lo', self, port=0 )
|
||||
|
||||
def defaultDpid( self, dpid=None ):
|
||||
@@ -1078,10 +1145,9 @@ class OVSSwitch( Switch ):
|
||||
version = quietRun( 'ovs-vsctl --version' )
|
||||
cls.OVSVersion = findall( r'\d+\.\d+', version )[ 0 ]
|
||||
|
||||
@classmethod
|
||||
def isOldOVS( cls ):
|
||||
def isOldOVS( self ):
|
||||
"Is OVS ersion < 1.10?"
|
||||
return ( StrictVersion( cls.OVSVersion ) <
|
||||
return ( StrictVersion( self.OVSVersion ) <
|
||||
StrictVersion( '1.10' ) )
|
||||
|
||||
def dpctl( self, *args ):
|
||||
@@ -1167,7 +1233,7 @@ class OVSSwitch( Switch ):
|
||||
"Start up a new OVS OpenFlow switch using ovs-vsctl"
|
||||
if self.inNamespace:
|
||||
raise Exception(
|
||||
'OVS kernel switch does not work in a namespace' )
|
||||
'OVS kernel switch does not work in a network namespace' )
|
||||
int( self.dpid, 16 ) # DPID must be a hex string
|
||||
# Command to add interfaces
|
||||
intfs = ''.join( ' -- add-port %s %s' % ( self, intf ) +
|
||||
|
||||
+86
-1
@@ -4,11 +4,14 @@ Node Library for Mininet
|
||||
This contains additional Node types which you may find to be useful.
|
||||
"""
|
||||
|
||||
from mininet.node import Node, Switch
|
||||
from mininet.node import Node, Host, Switch
|
||||
from mininet.log import info, warn
|
||||
from mininet.moduledeps import pathCheck
|
||||
from mininet.util import quietRun
|
||||
|
||||
import re
|
||||
from tempfile import NamedTemporaryFile
|
||||
|
||||
|
||||
class LinuxBridge( Switch ):
|
||||
"Linux Bridge (with optional spanning tree)"
|
||||
@@ -143,3 +146,85 @@ class NAT( Node ):
|
||||
# Put the forwarding state back to what it was
|
||||
self.cmd( 'sysctl net.ipv4.ip_forward=%s' % self.forwardState )
|
||||
super( NAT, self ).terminate()
|
||||
|
||||
|
||||
class Server( Host ):
|
||||
"Run sshd in a net/mnt/pid/uts namespace, with private /etc/hosts"
|
||||
|
||||
ns = [ 'net', 'mnt', 'pid', 'uts' ]
|
||||
overlayDirs = [ '/etc', '/var/run', '/var/log' ]
|
||||
privateDirs = [ '/var/run/sshd', ]
|
||||
|
||||
def __init__( self, *args, **kwargs ):
|
||||
"""Add overlay dirs and private dirs, and change permissions
|
||||
ssh: run sshd? (True)"""
|
||||
kwargs.setdefault( 'inNamespace', True )
|
||||
kwargs.setdefault( 'ns', self.ns )
|
||||
kwargs.setdefault( 'privateDirs', self.privateDirs )
|
||||
kwargs.setdefault( 'overlayDirs', self.overlayDirs )
|
||||
kwargs.setdefault( 'ssh', True )
|
||||
super( Server, self ).__init__( *args, **kwargs )
|
||||
# Change permissions, mainly for ssh
|
||||
for pdir in self.privateDirs:
|
||||
self.cmd( 'chown root:root', pdir )
|
||||
self.cmd( 'chmod 755', pdir )
|
||||
|
||||
@staticmethod
|
||||
def updateHostsFiles( servers, tmpdirs=[ '/tmp', '/var/tmp' ] ):
|
||||
"""Update local hosts files on a list of servers
|
||||
servers: list of servers
|
||||
tmpdir: tmp dir shared between mn and servers"""
|
||||
# For large configurations it will be more efficient
|
||||
# to use a DNS server
|
||||
hosts = ( '# Mininet hosts file\n'
|
||||
'127.0.0.1 localhost %s\n' +
|
||||
''.join( '%s %s\n' % ( t.IP(), t )
|
||||
for t in servers ) )
|
||||
for s in servers:
|
||||
dirs = ( getattr( s, 'overlayDirs', [] ) +
|
||||
getattr( s, 'privateDirs', [] ) )
|
||||
if '/etc' in dirs:
|
||||
tmpdirs = [ d for d in tmpdirs if d not in dirs ]
|
||||
if tmpdirs:
|
||||
with NamedTemporaryFile( dir=tmpdirs[ 0 ] ) as tmpfile:
|
||||
tmpfile.write( hosts % s )
|
||||
tmpfile.flush()
|
||||
s.cmd( 'cp', tmpfile.name, '/etc/hosts' )
|
||||
else:
|
||||
warn( 'not updating hosts file on %s\n' % s )
|
||||
|
||||
def service( self, cmd ):
|
||||
"""Start or stop a service
|
||||
usage: service( 'ssh stop' )"""
|
||||
self.cmd( '/etc/init.d/%s' % cmd )
|
||||
|
||||
def motd( self ):
|
||||
"Return login message as a string"
|
||||
return 'Welcome to Mininet host %s at %s' % ( self, self.IP() )
|
||||
|
||||
def startSSH( self, motdPath='/var/run/motd.dynamic' ):
|
||||
"Update motd, Clear out utmp/wtmp/btmp, and start sshd"
|
||||
# Note: /var/run and /var/log must be overlays!
|
||||
assert ( '/var/run' in ( self.overlayDirs + self.privateDirs ) and
|
||||
'/var/log' in ( self.overlayDirs + self.privateDirs ) )
|
||||
self.cmd( "echo '%s' > %s" % ( self.motd(), motdPath ) )
|
||||
self.cmd( 'truncate -s0 /var/run/utmp /var/log/wtmp* /var/log/btmp*' )
|
||||
# sshd.pid should really be in /var/run/sshd instead of /var/run
|
||||
self.cmd( 'rm /var/run/sshd.pid' )
|
||||
self.cmd( '/etc/init.d/ssh start' )
|
||||
|
||||
def config( self, **kwargs ):
|
||||
"""Configure/start sshd and other stuff
|
||||
ssh: start sshd? (True )"""
|
||||
super( Server, self ).config( **kwargs )
|
||||
self.ssh = kwargs.get( 'ssh' )
|
||||
if self.ssh:
|
||||
self.startSSH()
|
||||
if 'uts' in self.ns:
|
||||
self.cmd( 'hostname', self )
|
||||
|
||||
def terminate( self, *args, **kwargs ):
|
||||
"Shut down services and terminate server"
|
||||
if self.ssh:
|
||||
self.service( 'ssh stop' )
|
||||
super( Server, self ).terminate( *args, **kwargs )
|
||||
|
||||
+64
-13
@@ -5,13 +5,43 @@ Utility functions to run a terminal (connected via socat(1)) on each host.
|
||||
Requires socat(1) and xterm(1).
|
||||
Optionally uses gnome-terminal.
|
||||
"""
|
||||
|
||||
from os import environ
|
||||
|
||||
from mininet.log import error
|
||||
from mininet.util import quietRun, errRun
|
||||
|
||||
def tunnelX11( node, display=None):
|
||||
from os import environ, getpid, path, setsid
|
||||
from subprocess import Popen, PIPE, STDOUT
|
||||
from tempfile import NamedTemporaryFile
|
||||
|
||||
def getAuthX11( display ):
|
||||
"Return X11 credentials for display"
|
||||
host, screen = display.split( ':' )
|
||||
host = host.split( '/' )[ 0 ]
|
||||
hostname = quietRun( 'hostname' ).strip()
|
||||
# First, try hostname:display
|
||||
if host == 'localhost':
|
||||
host = hostname
|
||||
result = quietRun( 'xauth list %s:%s' % ( host, screen ) )
|
||||
# Otherwise, try hostname/unix:display
|
||||
if not result:
|
||||
result = quietRun( 'xauth list %s/unix:%s' % ( host, screen ) )
|
||||
items = result.strip().split()
|
||||
if len( items ) != 3:
|
||||
raise Exception( "getAuthX11: could not fetch credentials for " +
|
||||
display )
|
||||
return items
|
||||
|
||||
# This is tricky with uts and pid namespaces
|
||||
# For uts namespaces, we create and use a private $XAUTHORITY
|
||||
# and add credentials for the node's hostname.
|
||||
# To enable pid namespaces to work, we proxy the X11
|
||||
# socket twice using socat - first with a shared socket in /tmp, and
|
||||
# second with a TCP listener in the host network namespace.
|
||||
# Note that this will fail if /tmp is not shared - we should
|
||||
# probably think about this some more. We could potentially
|
||||
# specify a globally shared directory somehow if /tmp is
|
||||
# private.
|
||||
|
||||
def tunnelX11( node, display=None ):
|
||||
"""Create an X11 tunnel from node:6000 to the root host
|
||||
display: display on root host (optional)
|
||||
returns: node $DISPLAY, Popen object for tunnel"""
|
||||
@@ -28,11 +58,30 @@ def tunnelX11( node, display=None):
|
||||
quietRun( 'xhost +si:localuser:root' )
|
||||
return display, None
|
||||
else:
|
||||
# Create a tunnel for the TCP connection
|
||||
hostname = quietRun( 'hostname' ).strip()
|
||||
port = 6000 + int( float( screen ) )
|
||||
connection = r'TCP\:%s\:%s' % ( host, port )
|
||||
cmd = [ "socat", "TCP-LISTEN:%d,fork,reuseaddr" % port,
|
||||
"EXEC:'mnexec -a 1 socat STDIO %s'" % connection ]
|
||||
if 'uts' in node.ns and ( hostname in display or
|
||||
'localhost' in display ):
|
||||
# Use private xauth file, and add credentials
|
||||
# for this hostname
|
||||
if not hasattr( node, 'xauthFile' ):
|
||||
node.xauthFile = NamedTemporaryFile()
|
||||
_display, proto, cookie = getAuthX11( display )
|
||||
creds = '%s %s %s' % ( '%s/unix:%s' % ( node.name, screen ),
|
||||
proto, cookie )
|
||||
node.cmd( 'export XAUTHORITY=' + node.xauthFile.name )
|
||||
node.cmd( 'xauth -f $XAUTHORITY add ' + creds )
|
||||
# Create a shared unix socket in /tmp
|
||||
# This can conflict if we are running nested Mininet
|
||||
# in a pid namespace, and it will also fail if /tmp is not
|
||||
# shared
|
||||
socket = '/tmp/mininet.x11.%d' % getpid()
|
||||
if not hasattr( tunnelX11, 'socket' ):
|
||||
cmd = ( 'socat unix-listen:%s,fork tcp:localhost:%d' %
|
||||
( socket, port ) ).split()
|
||||
tunnelX11.socket = Popen( cmd )
|
||||
# Create a tunnel for the TCP connection
|
||||
cmd = 'socat tcp-listen:%d,fork,reuseaddr unix:%s' % ( port, socket )
|
||||
return 'localhost:' + screen, node.popen( cmd )
|
||||
|
||||
def makeTerm( node, title='Node', term='xterm', display=None, cmd='bash'):
|
||||
@@ -45,8 +94,8 @@ def makeTerm( node, title='Node', term='xterm', display=None, cmd='bash'):
|
||||
if not node.inNamespace:
|
||||
title += ' (root)'
|
||||
cmds = {
|
||||
'xterm': [ 'xterm', '-title', title, '-display' ],
|
||||
'gterm': [ 'gnome-terminal', '--title', title, '--display' ]
|
||||
'xterm': [ 'xterm', '-title', title ],
|
||||
'gterm': [ 'gnome-terminal', '--title', title ]
|
||||
}
|
||||
if term not in cmds:
|
||||
error( 'invalid terminal type: %s' % term )
|
||||
@@ -54,8 +103,10 @@ def makeTerm( node, title='Node', term='xterm', display=None, cmd='bash'):
|
||||
display, tunnel = tunnelX11( node, display )
|
||||
if display is None:
|
||||
return []
|
||||
term = node.popen( cmds[ term ] +
|
||||
[ display, '-e', 'env TERM=ansi %s' % cmd ] )
|
||||
env = [ 'env', 'TERM=ansi', 'DISPLAY=%s' % display ]
|
||||
if hasattr( node, 'xauthFile' ):
|
||||
env += [ 'XAUTHORITY=%s' % node.xauthFile.name ]
|
||||
term = node.popen( env + cmds[ term ] + [ '-e', cmd ] )
|
||||
return [ tunnel, term ] if tunnel else [ term ]
|
||||
|
||||
def runX11( node, cmd ):
|
||||
@@ -68,7 +119,7 @@ def runX11( node, cmd ):
|
||||
|
||||
def cleanUpScreens():
|
||||
"Remove moldy socat X11 tunnels."
|
||||
errRun( "pkill -9 -f mnexec.*socat" )
|
||||
errRun( "pkill -9 -f socat.*mininet" )
|
||||
|
||||
def makeTerms( nodes, title='Node', term='xterm' ):
|
||||
"""Create terminals.
|
||||
|
||||
+33
-10
@@ -45,10 +45,11 @@ class TorusTopo( Topo ):
|
||||
without STP turned on! It can be used with STP, e.g.:
|
||||
# mn --topo torus,3,3 --switch lxbr,stp=1 --test pingall"""
|
||||
|
||||
def build( self, x, y, n=1 ):
|
||||
"""x: dimension of torus in x-direction
|
||||
y: dimension of torus in y-direction
|
||||
n: number of hosts per switch"""
|
||||
def build( self, x, y, n=1, wrap=True ):
|
||||
"""x: number of switches per row
|
||||
y: number of rows
|
||||
n: number of hosts per switch
|
||||
wrap: torus rather than grid? (True)"""
|
||||
if x < 3 or y < 3:
|
||||
raise Exception( 'Please use 3x3 or greater for compatibility '
|
||||
'with 2.1' )
|
||||
@@ -72,11 +73,33 @@ class TorusTopo( Topo ):
|
||||
self.addLink( host, switch )
|
||||
# Connect switches
|
||||
for i in range( 0, x ):
|
||||
for j in range( 0, y ):
|
||||
sw1 = switches[ i, j ]
|
||||
sw2 = switches[ i, ( j + 1 ) % y ]
|
||||
sw3 = switches[ ( i + 1 ) % x, j ]
|
||||
self.addLink( sw1, sw2 )
|
||||
self.addLink( sw1, sw3 )
|
||||
for j in range( 0, y):
|
||||
sw = switches[ i, j ]
|
||||
right = switches[ ( i + 1 ) % x, j ]
|
||||
down = switches[ i, ( j + 1 ) % y ]
|
||||
if wrap or i + 1 < x:
|
||||
self.addLink( sw, right )
|
||||
if wrap or j + 1 < y:
|
||||
self.addLink( sw, down )
|
||||
|
||||
|
||||
class GridTopo( TorusTopo ):
|
||||
"""2-D Grid topology
|
||||
WARNING: this topology has LOOPS and WILL NOT WORK
|
||||
with the default controller or any Ethernet bridge
|
||||
without STP turned on! It can be used with STP, e.g.:
|
||||
# mn --topo grid,3,3 --switch lxbr,stp=1 --test pingall"""
|
||||
|
||||
def build( self, x, y, n=1, wrap=False ):
|
||||
"""x: number of switches per row
|
||||
y: number of rows
|
||||
n: number of hosts per switch
|
||||
wrap: torus rather than grid (False)"""
|
||||
super( GridTopo, self ).build( x, y, n, wrap )
|
||||
|
||||
|
||||
topos = { 'tree': TreeTopo,
|
||||
'torus': TorusTopo,
|
||||
'grid': GridTopo }
|
||||
|
||||
# pylint: enable=arguments-differ
|
||||
|
||||
+37
-15
@@ -178,18 +178,19 @@ def makeIntfPair( intf1, intf2, addr1=None, addr2=None, node1=None, node2=None,
|
||||
runCmd( 'ip link del ' + intf1 )
|
||||
runCmd2( 'ip link del ' + intf2 )
|
||||
# Create new pair
|
||||
netns = 1 if not node2 else node2.pid
|
||||
ns1 = 1 if not node1 else node1.pid
|
||||
ns2 = 1 if not node2 else node2.pid
|
||||
if addr1 is None and addr2 is None:
|
||||
cmdOutput = runCmd( 'ip link add name %s '
|
||||
cmdOutput = runCmd( 'ip link add name %s netns %s '
|
||||
'type veth peer name %s '
|
||||
'netns %s' % ( intf1, intf2, netns ) )
|
||||
'netns %s' % ( intf1, ns1, intf2, ns2 ) )
|
||||
else:
|
||||
cmdOutput = runCmd( 'ip link add name %s '
|
||||
'address %s '
|
||||
'address %s netns %s '
|
||||
'type veth peer name %s '
|
||||
'address %s '
|
||||
'netns %s' %
|
||||
( intf1, addr1, intf2, addr2, netns ) )
|
||||
( intf1, addr1, ns1, intf2, addr2, ns2 ) )
|
||||
if cmdOutput:
|
||||
raise Exception( "Error creating interface pair (%s,%s): %s " %
|
||||
( intf1, intf2, cmdOutput ) )
|
||||
@@ -508,21 +509,42 @@ def custom( cls, **params ):
|
||||
customized.__name__ = 'custom(%s,%s)' % ( cls, params )
|
||||
return customized
|
||||
|
||||
def splitArgs( argstr ):
|
||||
"""Split argument string into usable python arguments
|
||||
argstr: argument string with format fn,arg2,kw1=arg3...
|
||||
returns: fn, args, kwargs"""
|
||||
split = argstr.split( ',' )
|
||||
fn = split[ 0 ]
|
||||
params = split[ 1: ]
|
||||
|
||||
|
||||
def parseArgs( argstr ):
|
||||
"""Parse argument string
|
||||
returns args, kwargs"""
|
||||
# One step at a time: support param=[a,b,c]
|
||||
paramre = r'\[[^\]]*\]|[^,\[\]]+'
|
||||
paramre = r'\w+=\[[^\]]*\]|\w+\[^,\[\]]+' + paramre
|
||||
params = re.findall( paramre, argstr )
|
||||
# Parse lists
|
||||
for i, arg in enumerate( params ):
|
||||
if arg.startswith( '[' ) and arg.endswith( ']' ):
|
||||
arg = arg[ 1 : -1 ]
|
||||
print "recurse on", arg
|
||||
params[ i ] = parseArgs( args )
|
||||
# Convert int and float args; removes the need for function
|
||||
# to be flexible with input arg formats.
|
||||
args = [ makeNumeric( s ) for s in params if '=' not in s ]
|
||||
kwargs = {}
|
||||
for s in [ p for p in params if '=' in p ]:
|
||||
key, val = s.split( '=', 1 )
|
||||
kwargs[ key ] = makeNumeric( val )
|
||||
return fn, args, kwargs
|
||||
key, arg = s.split( '=', 1 )
|
||||
if arg.startswith( '[' ) and arg.endswith( ']' ):
|
||||
arg = arg[ 1 : -1 ]
|
||||
print 'recurse on', arg
|
||||
arg = parseArgs( arg )[ 0 ]
|
||||
else:
|
||||
arg = makeNumeric( arg )
|
||||
kwargs[ key ] = arg
|
||||
return args, kwargs
|
||||
|
||||
def splitArgs( argstr ):
|
||||
"""Split argument string into usable python arguments
|
||||
argstr: argument string with format fn,arg2,kw1=arg3...
|
||||
returns: fn, args, kwargs"""
|
||||
args, kwargs = parseArgs( argstr )
|
||||
return args[ 0 ], args[ 1: ], kwargs
|
||||
|
||||
def customClass( classes, argStr ):
|
||||
"""Return customized class based on argStr
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
*
|
||||
* - closing all file descriptors except stdin/out/error
|
||||
* - detaching from a controlling tty using setsid
|
||||
* - running in network and mount namespaces
|
||||
* - running in network and other namespaces
|
||||
* - printing out the pid of a process so we can identify it later
|
||||
* - attaching to a namespace and cgroup
|
||||
* - attaching to namespace(s) and cgroup
|
||||
* - setting RT scheduling
|
||||
*
|
||||
* Partially based on public domain setsid(1)
|
||||
@@ -24,21 +24,29 @@
|
||||
#include <sched.h>
|
||||
#include <ctype.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/wait.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#if !defined(VERSION)
|
||||
#define VERSION "(devel)"
|
||||
#endif
|
||||
|
||||
|
||||
void usage(char *name)
|
||||
{
|
||||
printf("Execution utility for Mininet\n\n"
|
||||
"Usage: %s [-cdnp] [-a pid] [-g group] [-r rtprio] cmd args...\n\n"
|
||||
"Usage: %s [-cdmnPpu] [-a pid] [-g group] [-r rtprio] cmd args...\n\n"
|
||||
"Options:\n"
|
||||
" -c: close all file descriptors except stdin/out/error\n"
|
||||
" -d: detach from tty by calling setsid()\n"
|
||||
" -n: run in new network and mount namespaces\n"
|
||||
" -m: run in a new mount namespace\n"
|
||||
" -n: run in a new network namespace\n"
|
||||
" -P: run in a new pid namespace (implies -m)\n"
|
||||
" -u: run in a new UTS (ipc, hostname) namespace\n"
|
||||
" -p: print ^A + pid\n"
|
||||
" -a pid: attach to pid's network and mount namespaces\n"
|
||||
" -a pid: attach to pid's namespaces\n"
|
||||
" -g group: add to cgroup\n"
|
||||
" -r rtprio: run with SCHED_RR (usually requires -g)\n"
|
||||
" -v: print version\n",
|
||||
@@ -46,10 +54,13 @@ void usage(char *name)
|
||||
}
|
||||
|
||||
|
||||
#if !defined(setns)
|
||||
int setns(int fd, int nstype)
|
||||
{
|
||||
return syscall(__NR_setns, fd, nstype);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/* Validate alphanumeric path foo1/bar2/baz */
|
||||
void validate(char *path)
|
||||
@@ -63,6 +74,7 @@ void validate(char *path)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Add our pid to cgroup */
|
||||
void cgroup(char *gname)
|
||||
{
|
||||
@@ -92,117 +104,193 @@ void cgroup(char *gname)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Attach to ns 'name' if present; return 1 if pidns */
|
||||
int attachns( pid_t pid, char *name ) {
|
||||
char path[PATH_MAX];
|
||||
int nsid, err;
|
||||
int pidns = 0;
|
||||
|
||||
snprintf(path, PATH_MAX, "/proc/%d/ns/%s", pid, name) ;
|
||||
|
||||
if ((nsid = open(path, O_RDONLY)) < 0)
|
||||
return nsid;
|
||||
|
||||
if (strcmp(name, "pid") == 0) {
|
||||
struct stat buf1, buf2;
|
||||
int stat1 = stat(path, &buf1);
|
||||
int stat2 = stat("/proc/self/ns/pid", &buf2);
|
||||
/* Don't reattach to the same pid ns */
|
||||
if (stat1 == 0 && stat2 == 0 &&
|
||||
buf1.st_dev == buf2.st_dev &&
|
||||
buf1.st_ino == buf2.st_ino)
|
||||
return 0;
|
||||
pidns = 1;
|
||||
}
|
||||
|
||||
if ((err = setns(nsid, 0)) < 0) {
|
||||
perror("setns");
|
||||
return err;
|
||||
}
|
||||
|
||||
return pidns;
|
||||
}
|
||||
|
||||
/* Attach to pid's namespaces - returns 1 if pidns */
|
||||
int attach(int pid) {
|
||||
|
||||
char *cwd = get_current_dir_name();
|
||||
char path[PATH_MAX];
|
||||
int pidns = 0;
|
||||
|
||||
attachns(pid, "net");
|
||||
attachns(pid, "uts");
|
||||
if (attachns(pid, "pid") == 1)
|
||||
pidns = 1;
|
||||
|
||||
if (attachns(pid, "mnt") != 0) {
|
||||
/* Plan B: chroot into pid's root file system */
|
||||
sprintf(path, "/proc/%d/root", pid);
|
||||
if (chroot(path) < 0) {
|
||||
perror(path);
|
||||
}
|
||||
}
|
||||
|
||||
/* chdir to correct working directory */
|
||||
if (chdir(cwd) != 0)
|
||||
perror(cwd);
|
||||
|
||||
return pidns;
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int c;
|
||||
int fd;
|
||||
char path[PATH_MAX];
|
||||
int nsid;
|
||||
int pid;
|
||||
char *cwd = get_current_dir_name();
|
||||
|
||||
static struct sched_param sp;
|
||||
while ((c = getopt(argc, argv, "+cdnpa:g:r:vh")) != -1)
|
||||
/* Argument flags */
|
||||
int flags = 0;
|
||||
int closefds = 0;
|
||||
int attachpid = 0;
|
||||
char *cgrouparg = NULL;
|
||||
int detachtty = 0;
|
||||
int printpid = 0;
|
||||
int rtprio = 0;
|
||||
int pidns = 0;
|
||||
int dofork = 0;
|
||||
|
||||
while ((c = getopt(argc, argv, "+cdmnPpa:g:r:uvh")) != -1)
|
||||
switch(c) {
|
||||
case 'c':
|
||||
/* close file descriptors except stdin/out/error */
|
||||
for (fd = getdtablesize(); fd > 2; fd--)
|
||||
close(fd);
|
||||
break;
|
||||
case 'd':
|
||||
/* detach from tty */
|
||||
if (getpgrp() == getpid()) {
|
||||
switch(fork()) {
|
||||
case -1:
|
||||
perror("fork");
|
||||
return 1;
|
||||
case 0: /* child */
|
||||
break;
|
||||
default: /* parent */
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
setsid();
|
||||
break;
|
||||
case 'n':
|
||||
/* run in network and mount namespaces */
|
||||
if (unshare(CLONE_NEWNET|CLONE_NEWNS) == -1) {
|
||||
perror("unshare");
|
||||
return 1;
|
||||
}
|
||||
case 'c': closefds = 1; break;
|
||||
case 'd': detachtty = 1; break;
|
||||
case 'm': flags |= CLONE_NEWNS; break;
|
||||
case 'n': flags |= CLONE_NEWNET; break;
|
||||
case 'p': printpid = 1; break;
|
||||
case 'P': flags |= CLONE_NEWPID | CLONE_NEWNS; break;
|
||||
case 'a': attachpid = atoi(optarg);break;
|
||||
case 'g': cgrouparg = optarg ; break;
|
||||
case 'r': rtprio = atoi(optarg); break;
|
||||
case 'u': flags |= CLONE_NEWUTS; break;
|
||||
case 'v': printf("%s\n", VERSION); exit(0);
|
||||
case 'h': usage(argv[0]); exit(0);
|
||||
default: usage(argv[0]); exit(1);
|
||||
}
|
||||
|
||||
/* Mark our whole hierarchy recursively as private, so that our
|
||||
* mounts do not propagate to other processes.
|
||||
*/
|
||||
if (closefds) {
|
||||
/* close file descriptors except stdin/out/error */
|
||||
int fd;
|
||||
for (fd = getdtablesize(); fd > 2; fd--) close(fd);
|
||||
}
|
||||
|
||||
if (mount("none", "/", NULL, MS_REC|MS_PRIVATE, NULL) == -1) {
|
||||
perror("remount");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* mount sysfs to pick up the new network namespace */
|
||||
if (mount("sysfs", "/sys", "sysfs", MS_MGC_VAL, NULL) == -1) {
|
||||
perror("mount");
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
case 'p':
|
||||
/* print pid */
|
||||
printf("\001%d\n", getpid());
|
||||
fflush(stdout);
|
||||
break;
|
||||
case 'a':
|
||||
/* Attach to pid's network namespace and mount namespace */
|
||||
pid = atoi(optarg);
|
||||
sprintf(path, "/proc/%d/ns/net", pid);
|
||||
nsid = open(path, O_RDONLY);
|
||||
if (nsid < 0) {
|
||||
perror(path);
|
||||
return 1;
|
||||
}
|
||||
if (setns(nsid, 0) != 0) {
|
||||
perror("setns");
|
||||
return 1;
|
||||
}
|
||||
/* Plan A: call setns() to attach to mount namespace */
|
||||
sprintf(path, "/proc/%d/ns/mnt", pid);
|
||||
nsid = open(path, O_RDONLY);
|
||||
if (nsid < 0 || setns(nsid, 0) != 0) {
|
||||
/* Plan B: chroot/chdir into pid's root file system */
|
||||
sprintf(path, "/proc/%d/root", pid);
|
||||
if (chroot(path) < 0) {
|
||||
perror(path);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
/* chdir to correct working directory */
|
||||
if (chdir(cwd) != 0) {
|
||||
perror(cwd);
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
case 'g':
|
||||
/* Attach to cgroup */
|
||||
cgroup(optarg);
|
||||
break;
|
||||
case 'r':
|
||||
/* Set RT scheduling priority */
|
||||
sp.sched_priority = atoi(optarg);
|
||||
if (sched_setscheduler(getpid(), SCHED_RR, &sp) < 0) {
|
||||
perror("sched_setscheduler");
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
case 'v':
|
||||
printf("%s\n", VERSION);
|
||||
exit(0);
|
||||
case 'h':
|
||||
usage(argv[0]);
|
||||
exit(0);
|
||||
default:
|
||||
usage(argv[0]);
|
||||
exit(1);
|
||||
if (attachpid)
|
||||
/* Attach to existing namespace(s) */
|
||||
pidns = attach(attachpid);
|
||||
else {
|
||||
/* Create new namespace(s) */
|
||||
if (unshare(flags) == -1) {
|
||||
perror("unshare");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (flags & CLONE_NEWPID || pidns)
|
||||
/* pidns requires fork/wait; child will be pid 1 */
|
||||
dofork = 1;
|
||||
|
||||
if (detachtty && getpgrp() == getpid())
|
||||
/* Fork so that we will no longer be pgroup leader */
|
||||
dofork = 1;
|
||||
else
|
||||
/* We don't need a new session, only a new pgroup */
|
||||
detachtty = 0;
|
||||
|
||||
if (detachtty)
|
||||
/* Create a new session - and by implication a new process group */
|
||||
setsid();
|
||||
else
|
||||
/* Use a new process group (in the current session)
|
||||
* so Mininet can use killpg without unintended effects */
|
||||
setpgid(0, 0);
|
||||
|
||||
if (dofork) {
|
||||
/* Fork and then wait if necessary */
|
||||
pid_t pid = fork();
|
||||
switch(pid) {
|
||||
int status;
|
||||
case -1:
|
||||
perror("fork");
|
||||
return 1;
|
||||
case 0:
|
||||
/* Child continues below */
|
||||
break;
|
||||
default:
|
||||
/* We print the *child pid* in *parent's pidns* if needed */
|
||||
if (printpid) {
|
||||
printf("\001%d\n", pid);
|
||||
fflush(stdout);
|
||||
}
|
||||
/* For pid namespace, we need to fork and wait for child ;-( */
|
||||
if (flags & CLONE_NEWPID || pidns) {
|
||||
wait(&status);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (printpid && !dofork) {
|
||||
/* Print child pid if we didn't fork/aren't in a pidns */
|
||||
assert(!pidns);
|
||||
printf("\001%d\n", getpid());
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
if (flags & CLONE_NEWPID) {
|
||||
/* Child remounts /proc for ps */
|
||||
if (mount("proc", "/proc", "proc", MS_MGC_VAL, NULL) == -1) {
|
||||
perror("mountproc");
|
||||
}
|
||||
}
|
||||
|
||||
/* Attach to cgroup if necessary */
|
||||
if (cgrouparg) cgroup(cgrouparg);
|
||||
|
||||
if (flags & CLONE_NEWNET & CLONE_NEWNS) {
|
||||
/* Mount sysfs to pick up the new network namespace */
|
||||
if (mount("sysfs", "/sys", "sysfs", MS_MGC_VAL, NULL) == -1) {
|
||||
perror("mount");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (rtprio != 0) {
|
||||
/* Set RT scheduling priority */
|
||||
static struct sched_param sp;
|
||||
sp.sched_priority = atoi(optarg);
|
||||
if (sched_setscheduler(getpid(), SCHED_RR, &sp) < 0) {
|
||||
perror("sched_setscheduler");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (optind < argc) {
|
||||
execvp(argv[optind], &argv[optind]);
|
||||
|
||||
@@ -33,12 +33,5 @@ if [ -d "$cgroup" ]; then
|
||||
cg="-g $host"
|
||||
fi
|
||||
|
||||
# Check whether host should be running in a chroot dir
|
||||
rootdir="/var/run/mn/$host/root"
|
||||
if [ -d $rootdir -a -x $rootdir/bin/bash ]; then
|
||||
cmd="'cd `pwd`; exec $cmd'"
|
||||
cmd="chroot $rootdir /bin/bash -c $cmd"
|
||||
fi
|
||||
|
||||
cmd="exec sudo mnexec $cg -a $pid $cmd"
|
||||
cmd="exec sudo -E mnexec $cg -a $pid $cmd"
|
||||
eval $cmd
|
||||
|
||||
Reference in New Issue
Block a user