pax_global_header00006660000000000000000000000064130643112400014504gustar00rootroot0000000000000052 comment=31b1093d865b031b1e55a1b7af3de96692321915 mininet-2.2.2/000077500000000000000000000000001306431124000131525ustar00rootroot00000000000000mininet-2.2.2/.gitattributes000066400000000000000000000000211306431124000160360ustar00rootroot00000000000000*.py diff=python mininet-2.2.2/.gitignore000066400000000000000000000001521306431124000151400ustar00rootroot00000000000000mnexec *.pyc *~ *.1 *.xcodeproj *.xcworkspace \#*\# mininet.egg-info build dist doc/html doc/latex trunk mininet-2.2.2/.pylint000066400000000000000000000220221306431124000144700ustar00rootroot00000000000000# lint Python modules using external checkers. # # This is the main checker controlling the other ones and the reports # generation. It is itself both a raw checker and an astng checker in order # to: # * handle message activation / deactivation at the module level # * handle some basic but necessary stats'data (number of classes, methods...) # [MASTER] # Specify a configuration file. #rcfile= # Python code to execute, usually for sys.path manipulation such as # pygtk.require(). #init-hook= # Profiled execution. profile=no # Add to the black list. It should be a base name, not a # path. You may set this option multiple times. ignore=CVS # Pickle collected data for later comparisons. persistent=yes # List of plugins (as comma separated values of python modules names) to load, # usually to register additional checkers. load-plugins= [MESSAGES CONTROL] # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option # multiple time. #enable= # Disable the message, report, category or checker with the given id(s). You # can either give multiple identifier separated by comma (,) or put this option # multiple time (only on the command line, not in the configuration file where # it should appear only once). disable=pointless-except, invalid-name, super-init-not-called, fixme, star-args, too-many-instance-attributes, too-few-public-methods, too-many-arguments, too-many-locals, too-many-public-methods, duplicate-code, bad-whitespace, locally-disabled [REPORTS] # Set the output format. Available formats are text, parseable, colorized, msvs # (visual studio) and html output-format=colorized msg-template='{path}:{line}: [{msg_id}({symbol}), {obj}] {msg}' # Include message's id in output include-ids=yes # Put messages in a separate file for each module / package specified on the # command line instead of printing them on stdout. Reports (if any) will be # written in a file name "pylint_global.[txt|html]". files-output=no # Tells wether to display a full report or only the messages reports=no # Python expression which should return a note less than 10 (10 is the highes # note). You have access to the variables errors warning, statement which # respectivly contain the number of errors / warnings messages and the total # number of statements analyzed. This is used by the global evaluation repor # (R0004). evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) # Add a comment according to your evaluation note. This is used by the global # evaluation report (R0004). comment=no # Enable the report(s) with the given id(s). #enable-report= # Disable the report(s) with the given id(s). #disable-report= # checks for : # * doc strings # * modules / classes / functions / methods / arguments / variables name # * number of arguments, local variables, branchs, returns and statements in # functions, methods # * required module attributes # * dangerous default values as arguments # * redefinition of function / method / class # * uses of the global statemen # [BASIC] # Required attributes for module, separated by a comma required-attributes= # Regular expression which should only match functions or classes name which do # not require a docstring no-docstring-rgx=__.*__ # Regular expression which should only match correct module names module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ # Regular expression which should only match correct module level names const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ # Regular expression which should only match correct class names class-rgx=[A-Z_][a-zA-Z0-9]+$ # Regular expression which should only match correct function names function-rgx=[a-z_][a-z0-9_]{2,30}$ # Regular expression which should only match correct method names method-rgx=[a-z_][a-z0-9_]{2,30}$ # Regular expression which should only match correct instance attribute names attr-rgx=[a-z_][a-z0-9_]{2,30}$ # Regular expression which should only match correct argument names argument-rgx=[a-z_][a-z0-9_]{2,30}$ # Regular expression which should only match correct variable names variable-rgx=[a-z_][a-z0-9_]{2,30}$ # Regular expression which should only match correct list comprehension / # generator expression variable names inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ # Good variable names which should always be accepted, separated by a comma good-names=i,j,k,ex,Run,_ # Bad variable names which should always be refused, separated by a comma bad-names=foo,bar,baz,toto,tutu,tata # List of builtins function names that should not be used, separated by a comma bad-functions=map,filter,apply,inpu # try to find bugs in the code using type inference # [TYPECHECK] # Tells wether missing members accessed in mixin class should be ignored. A # mixin class is detected if its name ends with "mixin" (case insensitive). ignore-mixin-members=yes # List of classes names for which member attributes should not be checked # (useful for classes with attributes dynamicaly set). ignored-classes=SQLObjec # When zope mode is activated, add a predefined set of Zope acquired attributes # to generated-members. zope=no # List of members which are set dynamically and missed by pylint inference # system, and so shouldn't trigger E0201 when accessed. generated-members=REQUEST,acl_users,aq_paren # checks for # * unused variables / imports # * undefined variables # * redefinition of variable from builtins or from an outer scope # * use of variable before assigmen # [VARIABLES] # Tells wether we should check for unused import in __init__ files. init-import=no # A regular expression matching names used for dummy variables (i.e. not used). dummy-variables-rgx=_|dummy # List of additional names supposed to be defined in builtins. Remember tha # you should avoid to define new builtins when possible. additional-builtins= # checks for : # * methods without self as first argumen # * overridden methods signature # * access only to existant members via self # * attributes not defined in the __init__ method # * supported interfaces implementation # * unreachable code # [CLASSES] # List of interface methods to ignore, separated by a comma. This is used for # instance to not check methods defines in Zope's Interface base class. ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by # List of method names used to declare (i.e. assign) instance attributes. defining-attr-methods=__init__,__new__,setUp,build # checks for sign of poor/misdesign: # * number of methods, attributes, local variables... # * size, complexity of functions, methods # [DESIGN] # Maximum number of arguments for function / method max-args=5 # Maximum number of locals for function / method body max-locals=15 # Maximum number of return / yield for function / method body max-returns=6 # Maximum number of branch for function / method body max-branchs=12 # Maximum number of statements in function / method body max-statements=50 # Maximum number of parents for a class (see R0901). max-parents=7 # Maximum number of attributes for a class (see R0902). max-attributes=7 # Minimum number of public methods for a class (see R0903). min-public-methods=2 # Maximum number of public methods for a class (see R0904). max-public-methods=20 # checks for # * external modules dependencies # * relative / wildcard imports # * cyclic imports # * uses of deprecated modules # [IMPORTS] # Deprecated modules which should not be used, separated by a comma deprecated-modules=regsub,string,TERMIOS,Bastion,rexec # Create a graph of every (i.e. internal and external) dependencies in the # given file (report R0402 must not be disabled) import-graph= # Create a graph of external dependencies in the given file (report R0402 mus # not be disabled) ext-import-graph= # Create a graph of internal dependencies in the given file (report R0402 mus # not be disabled) int-import-graph= # checks for : # * unauthorized constructions # * strict indentation # * line length # * use of <> instead of != # [FORMAT] # Maximum number of characters on a single line. max-line-length=80 # Maximum number of lines in a module # XXX 1500 -> 4000 for miniedit.py max-module-lines=4000 # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 # tab). indent-string=' ' # checks for: # * warning notes in the code like FIXME, XXX # * PEP 263: source code with non ascii character but no encoding declaration # [MISCELLANEOUS] # List of note tags to take in consideration, separated by a comma. notes=FIXME,XXX,TODO # checks for similarities and duplicated code. This computation may be # memory / CPU intensive, so you should disable it if you experiments some # problems. # [SIMILARITIES] # Minimum lines number of a similarity. min-similarity-lines=4 # Ignore comments when computing similarities. ignore-comments=yes # Ignore docstrings when computing similarities. ignore-docstrings=yes mininet-2.2.2/.travis.yml000066400000000000000000000011711306431124000152630ustar00rootroot00000000000000language: python sudo: required matrix: include: - dist: trusty env: dist="14.04 LTS trusty" - dist: xenial env: dist="16.04 LTS xenial" before_install: - sudo apt-get update -qq - sudo apt-get install -qq vlan - sudo util/install.sh -n install: - bash -c "[ `lsb_release -rs` == '14.04' ] && make codecheck" - sudo util/install.sh -fnvw script: - sudo mn --test pingall - sudo python mininet/test/runner.py -v -quick - sudo python examples/test/runner.py -v -quick notifications: email: on_success: never # More details: https://docs.travis-ci.com/user/notifications#Configuring-email-notifications mininet-2.2.2/CONTRIBUTORS000066400000000000000000000017531306431124000150400ustar00rootroot00000000000000Mininet Contributors Mininet is an open source project and we gratefully acknowledge the many contributions to the project! If you have contributed code into the project and are not on this list, please let us know or send a pull request. Contributors include: Mininet Core Team (and alumni) Bob Lantz Brandon Heller Nikhil Handigol Vimal Jeyakumar Brian O'Connor Cody Burkard Additional Mininet Contributors M S Vishwanath Bhat Tomasz Buchert Gustavo Pantuza Coelho Pinto Fernando Cappi Ryan Cox Shaun Crampton David Erickson Glen Gibb Andrew Ferguson Eder Leao Fernandes Gregory Gee Jon Hall Jono Hart Roan Huang Vitaly Ivanov Babis Kaidos Rich Lane Rémy Léone Zi Shen Lim David Mahler Murphy McCauley José Pedro Oliveira James Page Rahman Pujianto Angad Singh Piyush Srivastava Ed Swierk Darshan Thaker Olivier Tilmans Andreas Wundsam Isaku Yamahata Baohua Yang Thanks also to everyone who has submitted issues and pull requests on github, and to our friendly mininet-discuss mailing list! mininet-2.2.2/INSTALL000066400000000000000000000124371306431124000142120ustar00rootroot00000000000000 Mininet Installation/Configuration Notes ---------------------------------------- Mininet 2.2.2 --- The supported installation methods for Mininet are 1) using a pre-built VM image, and 2) native installation on Ubuntu. You can also easily create your own Mininet VM image (4). (Other distributions may be supported in the future - if you would like to contribute an installation script, we would welcome it!) 1. Easiest "installation" - use our pre-built VM image! The easiest way to get Mininet running is to start with one of our pre-built virtual machine images from Boot up the VM image, log in, and follow the instructions on the Mininet web site. One advantage of using the VM image is that it doesn't mess with your native OS installation or damage it in any way. Although a single Mininet instance can simulate multiple networks with multiple controllers, only one Mininet instance may currently be run at a time, and Mininet requires root access in the machine it's running on. Therefore, if you have a multiuser system, you may wish to consider running Mininet in a VM. 2. Next-easiest option: use our Ubuntu package! To install Mininet itself (i.e. `mn` and the Python API) on Ubuntu 12.10+: sudo apt-get install mininet Note: if you are upgrading from an older version of Mininet, make sure you remove the old OVS from `/usr/local`: sudo rm /usr/local/bin/ovs* sudo rm /usr/local/sbin/ovs* 3. Native installation from source 3.1. Native installation from source on Ubuntu 12.04+ If you're reading this, you've probably already done so, but the command to download the Mininet source code is: git clone git://github.com/mininet/mininet.git Note that the above git command will check out the latest and greatest Mininet (which we recommend!) If you want to run the last tagged/released version of Mininet, you can look at the release tags using cd mininet git tag and then git checkout where is the release you want to check out. If you are running Ubuntu, Debian, or Fedora, you may be able to use our handy `install.sh` script, which is in `util/`. *WARNING: USE AT YOUR OWN RISK!* `install.sh` is a bit intrusive and may possibly damage your OS and/or home directory, by creating/modifying several directories such as `mininet`, `openflow`, `oftest`, `pox`, etc.. We recommend trying it in a VM before trying it on a system you use from day to day. Although we hope it won't do anything completely terrible, you may want to look at the script before you run it, and you should make sure your system and home directory are backed up just in case! To install Mininet itself, the OpenFlow reference implementation, and Open vSwitch, you may use: util/install.sh -fnv This should be reasonably quick, and the following command should work after the installation: sudo mn --test pingall To install ALL of the software which we use for OpenFlow tutorials, including POX, the OpenFlow WireShark dissector, the `oftest` framework, and other potentially useful software, you may use: util/install.sh -a This takes about 4 minutes on our test system. You can change the directory where the dependencies are installed using the -s flag. util/install.sh -s -a 3.2. Native installation from source on Fedora 18+. As root execute the following operations: * install git yum install git * create an user account (e.g. mininet) and add it to the wheel group useradd [...] mininet usermod -a -G wheel mininet * change the SElinux setting to permissive. It can be done temporarily with: setenforce 0 then login with the new account (e.g. mininet) and do the following: * clone the Mininet repository git clone git://github.com/mininet/mininet.git * install Mininet, the OpenFlow reference implementation, and Open vSwitch util/install.sh -fnv * enable and start openvswitch sudo systemctl enable openvswitch sudo systemctl start openvswitch * test the mininet installation sudo mn --test pingall 4. Creating your own Mininet/OpenFlow tutorial VM Creating your own Ubuntu Mininet VM for use with the OpenFlow tutorial is easy! First, create a new Ubuntu VM. Next, run two commands in it: wget https://raw.github.com/mininet/mininet/master/util/vm/install-mininet-vm.sh time install-mininet-vm.sh Finally, verify that Mininet is installed and working in the VM: sudo mn --test pingall 5. Installation on other Linux distributions Although we don't support other Linux distributions directly, it should be possible to install and run Mininet with some degree of manual effort. In general, you must have: * A Linux kernel compiled with network namespace support enabled * An compatible software switch such as Open vSwitch or the Linux bridge. * Python, `bash`, `ping`, `iperf`, etc. * Root privileges (required for network device access) We encourage contribution of patches to the `install.sh` script to support other Linux distributions. Good luck! Mininet Team --- mininet-2.2.2/LICENSE000066400000000000000000000032471306431124000141650ustar00rootroot00000000000000Mininet 2.2.2 License Copyright (c) 2013-2017 Open Networking Laboratory Copyright (c) 2009-2012 Bob Lantz and The Board of Trustees of The Leland Stanford Junior University Original authors: Bob Lantz and Brandon Heller We are making Mininet available for public use and benefit with the expectation that others will use, modify and enhance the Software and contribute those enhancements back to the community. However, since we would like to make the Software available for broadest use, with as few restrictions as possible permission is hereby granted, free of charge, to any person obtaining a copy of this Software to deal in the Software under the copyrights without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. The name and trademarks of copyright holder(s) may NOT be used in advertising or publicity pertaining to the Software or any derivatives without specific, written prior permission. mininet-2.2.2/Makefile000066400000000000000000000034111306431124000146110ustar00rootroot00000000000000MININET = mininet/*.py TEST = mininet/test/*.py EXAMPLES = mininet/examples/*.py MN = bin/mn PYMN = python -B bin/mn BIN = $(MN) PYSRC = $(MININET) $(TEST) $(EXAMPLES) $(BIN) MNEXEC = mnexec MANPAGES = mn.1 mnexec.1 P8IGN = E251,E201,E302,E202,E126,E127,E203,E226 BINDIR = /usr/bin MANDIR = /usr/share/man/man1 DOCDIRS = doc/html doc/latex PDF = doc/latex/refman.pdf CFLAGS += -Wall -Wextra all: codecheck test clean: rm -rf build dist *.egg-info *.pyc $(MNEXEC) $(MANPAGES) $(DOCDIRS) codecheck: $(PYSRC) -echo "Running code check" util/versioncheck.py pyflakes $(PYSRC) pylint --rcfile=.pylint $(PYSRC) # Exclude miniedit from pep8 checking for now pep8 --repeat --ignore=$(P8IGN) `ls $(PYSRC) | grep -v miniedit.py` errcheck: $(PYSRC) -echo "Running check for errors only" pyflakes $(PYSRC) pylint -E --rcfile=.pylint $(PYSRC) test: $(MININET) $(TEST) -echo "Running tests" mininet/test/test_nets.py mininet/test/test_hifi.py slowtest: $(MININET) -echo "Running slower tests (walkthrough, examples)" mininet/test/test_walkthrough.py -v mininet/examples/test/runner.py -v mnexec: mnexec.c $(MN) mininet/net.py cc $(CFLAGS) $(LDFLAGS) -DVERSION=\"`PYTHONPATH=. $(PYMN) --version`\" $< -o $@ install: $(MNEXEC) $(MANPAGES) install $(MNEXEC) $(BINDIR) install $(MANPAGES) $(MANDIR) python setup.py install develop: $(MNEXEC) $(MANPAGES) # Perhaps we should link these as well install $(MNEXEC) $(BINDIR) install $(MANPAGES) $(MANDIR) python setup.py develop man: $(MANPAGES) mn.1: $(MN) PYTHONPATH=. help2man -N -n "create a Mininet network." \ --no-discard-stderr "$(PYMN)" -o $@ mnexec.1: mnexec help2man -N -n "execution utility for Mininet." \ -h "-h" -v "-v" --no-discard-stderr ./$< -o $@ .PHONY: doc doc: man doxygen doc/doxygen.cfg make -C doc/latex mininet-2.2.2/README.md000066400000000000000000000107611306431124000144360ustar00rootroot00000000000000Mininet: Rapid Prototyping for Software Defined Networks ======================================================== *The best way to emulate almost any network on your laptop!* Mininet 2.2.2 [![Build Status][1]](https://travis-ci.org/mininet/mininet) ### What is Mininet? Mininet emulates a complete network of hosts, links, and switches on a single machine. To create a sample two-host, one-switch network, just run: `sudo mn` Mininet is useful for interactive development, testing, and demos, especially those using OpenFlow and SDN. OpenFlow-based network controllers prototyped in Mininet can usually be transferred to hardware with minimal changes for full line-rate execution. ### How does it work? Mininet creates virtual networks using process-based virtualization and network namespaces - features that are available in recent Linux kernels. In Mininet, hosts are emulated as `bash` processes running in a network namespace, so any code that would normally run on a Linux server (like a web server or client program) should run just fine within a Mininet "Host". The Mininet "Host" will have its own private network interface and can only see its own processes. Switches in Mininet are software-based switches like Open vSwitch or the OpenFlow reference switch. Links are virtual ethernet pairs, which live in the Linux kernel and connect our emulated switches to emulated hosts (processes). ### Features Mininet includes: * A command-line launcher (`mn`) to instantiate networks. * A handy Python API for creating networks of varying sizes and topologies. * Examples (in the `examples/` directory) to help you get started. * Full API documentation via Python `help()` docstrings, as well as the ability to generate PDF/HTML documentation with `make doc`. * Parametrized topologies (`Topo` subclasses) using the Mininet object. For example, a tree network may be created with the command: `mn --topo tree,depth=2,fanout=3` * A command-line interface (`CLI` class) which provides useful diagnostic commands (like `iperf` and `ping`), as well as the ability to run a command to a node. For example, `mininet> h11 ifconfig -a` tells host h11 to run the command `ifconfig -a` * A "cleanup" command to get rid of junk (interfaces, processes, files in /tmp, etc.) which might be left around by Mininet or Linux. Try this if things stop working! `mn -c` ### New features in this release This is primarily a performance improvement and bug fix release. - Batch startup has been implemented for Open vSwitch, improving startup performance. - OVS patch links have been implemented via OVSLink and --link ovs Warning! These links have *serious limitations* compared to virtual Ethernet pairs: they are not attached to real Linux interfaces so you cannot use tcpdump or wireshark with them; they also cannot be used in long chains - we don't recommend more than 64 OVSLinks, for example --linear,64. However, they can offer significantly better performance than veth pairs, for certain configurations. - You can now easily install Mininet on a Raspberry Pi ;-) - Additional information for this release and previous releases may be found in the release notes on docs.mininet.org ### Installation See `INSTALL` for installation instructions and details. ### Documentation In addition to the API documentation (`make doc`), much useful information, including a Mininet walkthrough and an introduction to the Python API, is available on the [Mininet Web Site](http://mininet.org). There is also a wiki which you are encouraged to read and to contribute to, particularly the Frequently Asked Questions (FAQ.) ### Support Mininet is community-supported. We encourage you to join the Mininet mailing list, `mininet-discuss` at: ### Join Us Thanks again to all of the Mininet contributors! Mininet is an open source project and is currently hosted at . You are encouraged to download the code, examine it, modify it, and submit bug reports, bug fixes, feature requests, new features and other issues and pull requests. Thanks to everyone who has contributed code to the Mininet project (see CONTRIBUTORS for more info!) It is because of everyone's hard work that Mininet continues to grow and improve. ### Enjoy Mininet Best wishes, and we look forward to seeing what you can do with Mininet to change the networking world! Bob Lantz Mininet Core Team [1]: https://travis-ci.org/mininet/mininet.svg?branch=devel/2.2.2 mininet-2.2.2/bin/000077500000000000000000000000001306431124000137225ustar00rootroot00000000000000mininet-2.2.2/bin/mn000077500000000000000000000377551306431124000143030ustar00rootroot00000000000000#!/usr/bin/env python """ Mininet runner author: Brandon Heller (brandonh@stanford.edu) To see options: sudo mn -h Example to pull custom params (topo, switch, etc.) from a file: sudo mn --custom ~/mininet/custom/custom_example.py """ from optparse import OptionParser import os import sys import time # Fix setuptools' evil madness, and open up (more?) security holes if 'PYTHONPATH' in os.environ: sys.path = os.environ[ 'PYTHONPATH' ].split( ':' ) + sys.path from mininet.clean import cleanup from mininet.cli import CLI from mininet.log import lg, LEVELS, info, debug, warn, error from mininet.net import Mininet, MininetWithControlNet, VERSION from mininet.node import ( Host, CPULimitedHost, Controller, OVSController, Ryu, NOX, RemoteController, findController, DefaultController, NullController, UserSwitch, OVSSwitch, OVSBridge, IVSSwitch ) from mininet.nodelib import LinuxBridge from mininet.link import Link, TCLink, TCULink, OVSLink from mininet.topo import ( SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo, MinimalTopo ) from mininet.topolib import TreeTopo, TorusTopo from mininet.util import customClass, specialClass, splitArgs from mininet.util import buildTopo from functools import partial # Experimental! cluster edition prototype from mininet.examples.cluster import ( MininetCluster, RemoteHost, RemoteOVSSwitch, RemoteLink, SwitchBinPlacer, RandomPlacer, ClusterCleanup ) from mininet.examples.clustercli import ClusterCLI PLACEMENT = { 'block': SwitchBinPlacer, 'random': RandomPlacer } # built in topologies, created only when run TOPODEF = 'minimal' TOPOS = { 'minimal': MinimalTopo, 'linear': LinearTopo, 'reversed': SingleSwitchReversedTopo, 'single': SingleSwitchTopo, 'tree': TreeTopo, 'torus': TorusTopo } SWITCHDEF = 'default' SWITCHES = { 'user': UserSwitch, 'ovs': OVSSwitch, 'ovsbr' : OVSBridge, # Keep ovsk for compatibility with 2.0 'ovsk': OVSSwitch, 'ivs': IVSSwitch, 'lxbr': LinuxBridge, 'default': OVSSwitch } HOSTDEF = 'proc' HOSTS = { 'proc': Host, 'rt': specialClass( CPULimitedHost, defaults=dict( sched='rt' ) ), 'cfs': specialClass( CPULimitedHost, defaults=dict( sched='cfs' ) ) } CONTROLLERDEF = 'default' CONTROLLERS = { 'ref': Controller, 'ovsc': OVSController, 'nox': NOX, 'remote': RemoteController, 'ryu': Ryu, 'default': DefaultController, # Note: overridden below 'none': NullController } LINKDEF = 'default' LINKS = { 'default': Link, # Note: overridden below 'tc': TCLink, 'tcu': TCULink, 'ovs': OVSLink } # Names of tests that are Mininet() methods TESTNAMES = [ 'cli', 'build', 'pingall', 'pingpair', 'iperf', 'all', 'iperfudp', 'none' ] # Map to alternate functions and/or spellings of Mininet() methods TESTS = { 'pingall': 'pingAll', 'pingpair': 'pingPair', 'iperfudp': 'iperfUdp', 'iperfUDP': 'iperfUdp' } def addDictOption( opts, choicesDict, default, name, **kwargs ): """Convenience function to add choices dicts to OptionParser. opts: OptionParser instance choicesDict: dictionary of valid choices, must include default default: default choice key name: long option name kwargs: additional arguments to add_option""" helpStr = ( '|'.join( sorted( choicesDict.keys() ) ) + '[,param=value...]' ) helpList = [ '%s=%s' % ( k, v.__name__ ) for k, v in choicesDict.items() ] helpStr += ' ' + ( ' '.join( helpList ) ) params = dict( type='string', default=default, help=helpStr ) params.update( **kwargs ) opts.add_option( '--' + name, **params ) def version( *_args ): "Print Mininet version and exit" print "%s" % VERSION exit() class MininetRunner( object ): "Build, setup, and run Mininet." def __init__( self ): "Init." self.options = None self.args = None # May be used someday for more CLI scripts self.validate = None self.parseArgs() self.setup() self.begin() def custom( self, _option, _opt_str, value, _parser ): """Parse custom file and add params. option: option e.g. --custom opt_str: option string e.g. --custom value: the value the follows the option parser: option parser instance""" files = [] if os.path.isfile( value ): # Accept any single file (including those with commas) files.append( value ) else: # Accept a comma-separated list of filenames files += value.split(',') for fileName in files: customs = {} if os.path.isfile( fileName ): execfile( fileName, customs, customs ) for name, val in customs.iteritems(): self.setCustom( name, val ) else: raise Exception( 'could not find custom file: %s' % fileName ) def setCustom( self, name, value ): "Set custom parameters for MininetRunner." if name in ( 'topos', 'switches', 'hosts', 'controllers', 'links' 'testnames', 'tests' ): # Update dictionaries param = name.upper() globals()[ param ].update( value ) elif name == 'validate': # Add custom validate function self.validate = value else: # Add or modify global variable or class globals()[ name ] = value def setNat( self, _option, opt_str, value, parser ): "Set NAT option(s)" assert self # satisfy pylint parser.values.nat = True # first arg, first char != '-' if parser.rargs and parser.rargs[ 0 ][ 0 ] != '-': value = parser.rargs.pop( 0 ) _, args, kwargs = splitArgs( opt_str + ',' + value ) parser.values.nat_args = args parser.values.nat_kwargs = kwargs else: parser.values.nat_args = [] parser.values.nat_kwargs = {} def parseArgs( self ): """Parse command-line args and return options object. returns: opts parse options dict""" desc = ( "The %prog utility creates Mininet network from the\n" "command line. It can create parametrized topologies,\n" "invoke the Mininet CLI, and run tests." ) usage = ( '%prog [options]\n' '(type %prog -h for details)' ) opts = OptionParser( description=desc, usage=usage ) addDictOption( opts, SWITCHES, SWITCHDEF, 'switch' ) addDictOption( opts, HOSTS, HOSTDEF, 'host' ) addDictOption( opts, CONTROLLERS, [], 'controller', action='append' ) addDictOption( opts, LINKS, LINKDEF, 'link' ) addDictOption( opts, TOPOS, TOPODEF, 'topo' ) opts.add_option( '--clean', '-c', action='store_true', default=False, help='clean and exit' ) opts.add_option( '--custom', action='callback', callback=self.custom, type='string', help='read custom classes or params from .py file(s)' ) testList = TESTNAMES + TESTS.keys() opts.add_option( '--test', default=[], action='append', dest='tests', help='|'.join( testList ) ) opts.add_option( '--xterms', '-x', action='store_true', default=False, help='spawn xterms for each node' ) opts.add_option( '--ipbase', '-i', type='string', default='10.0.0.0/8', help='base IP address for hosts' ) opts.add_option( '--mac', action='store_true', default=False, help='automatically set host MACs' ) opts.add_option( '--arp', action='store_true', default=False, help='set all-pairs ARP entries' ) opts.add_option( '--verbosity', '-v', type='choice', choices=LEVELS.keys(), default = 'info', help = '|'.join( LEVELS.keys() ) ) opts.add_option( '--innamespace', action='store_true', default=False, help='sw and ctrl in namespace?' ) opts.add_option( '--listenport', type='int', default=6654, help='base port for passive switch listening' ) opts.add_option( '--nolistenport', action='store_true', default=False, help="don't use passive listening " + "port") opts.add_option( '--pre', type='string', default=None, help='CLI script to run before tests' ) opts.add_option( '--post', type='string', default=None, help='CLI script to run after tests' ) opts.add_option( '--pin', action='store_true', default=False, help="pin hosts to CPU cores " "(requires --host cfs or --host rt)" ) opts.add_option( '--nat', action='callback', callback=self.setNat, help="[option=val...] adds a NAT to the topology that" " connects Mininet hosts to the physical network." " Warning: This may route any traffic on the machine" " that uses Mininet's" " IP subnet into the Mininet network." " If you need to change" " Mininet's IP subnet, see the --ipbase option." ) opts.add_option( '--version', action='callback', callback=version, help='prints the version and exits' ) opts.add_option( '--cluster', type='string', default=None, metavar='server1,server2...', help=( 'run on multiple servers (experimental!)' ) ) opts.add_option( '--placement', type='choice', choices=PLACEMENT.keys(), default='block', metavar='block|random', help=( 'node placement for --cluster ' '(experimental!) ' ) ) self.options, self.args = opts.parse_args() # We don't accept extra arguments after the options if self.args: opts.print_help() exit() def setup( self ): "Setup and validate environment." # set logging verbosity if LEVELS[self.options.verbosity] > LEVELS['output']: print ( '*** WARNING: selected verbosity level (%s) will hide CLI ' 'output!\n' 'Please restart Mininet with -v [debug, info, output].' % self.options.verbosity ) lg.setLogLevel( self.options.verbosity ) # Maybe we'll reorganize this someday... # pylint: disable=too-many-branches,too-many-statements def begin( self ): "Create and run mininet." if self.options.cluster: servers = self.options.cluster.split( ',' ) for server in servers: ClusterCleanup.add( server ) if self.options.clean: cleanup() exit() start = time.time() if not self.options.controller: # Update default based on available controllers CONTROLLERS[ 'default' ] = findController() self.options.controller = [ 'default' ] if not CONTROLLERS[ 'default' ]: self.options.controller = [ 'none' ] if self.options.switch == 'default': info( '*** No default OpenFlow controller found ' 'for default switch!\n' ) info( '*** Falling back to OVS Bridge\n' ) self.options.switch = 'ovsbr' elif self.options.switch not in ( 'ovsbr', 'lxbr' ): raise Exception( "Could not find a default controller " "for switch %s" % self.options.switch ) topo = buildTopo( TOPOS, self.options.topo ) switch = customClass( SWITCHES, self.options.switch ) host = customClass( HOSTS, self.options.host ) controller = [ customClass( CONTROLLERS, c ) for c in self.options.controller ] if self.options.switch == 'user' and self.options.link == 'default': debug( '*** Using TCULink with UserSwitch\n' ) # Use link configured correctly for UserSwitch self.options.link = 'tcu' link = customClass( LINKS, self.options.link ) if self.validate: self.validate( self.options ) ipBase = self.options.ipbase xterms = self.options.xterms mac = self.options.mac arp = self.options.arp pin = self.options.pin listenPort = None if not self.options.nolistenport: listenPort = self.options.listenport # Handle inNamespace, cluster options inNamespace = self.options.innamespace cluster = self.options.cluster if inNamespace and cluster: print "Please specify --innamespace OR --cluster" exit() Net = MininetWithControlNet if inNamespace else Mininet cli = ClusterCLI if cluster else CLI if cluster: warn( '*** WARNING: Experimental cluster mode!\n' '*** Using RemoteHost, RemoteOVSSwitch, RemoteLink\n' ) host, switch, link = RemoteHost, RemoteOVSSwitch, RemoteLink Net = partial( MininetCluster, servers=servers, placement=PLACEMENT[ self.options.placement ] ) mn = Net( topo=topo, switch=switch, host=host, controller=controller, link=link, ipBase=ipBase, inNamespace=inNamespace, xterms=xterms, autoSetMacs=mac, autoStaticArp=arp, autoPinCpus=pin, listenPort=listenPort ) if self.options.ensure_value( 'nat', False ): nat = mn.addNAT( *self.options.nat_args, **self.options.nat_kwargs ) nat.configDefault() if self.options.pre: cli( mn, script=self.options.pre ) mn.start() if not self.options.tests: cli( mn ) for test in self.options.tests: test = TESTS.get( test, test ) if callable( test ): # user added TESTMAP={'mytest': testfn} test( mn ) elif test == 'none': pass elif test == 'all': mn.waitConnected() mn.start() mn.ping() mn.iperf() elif test == 'cli': cli( mn ) elif test != 'build': mn.waitConnected() getattr( mn, test )() if self.options.post: cli( mn, script=self.options.post ) mn.stop() elapsed = float( time.time() - start ) info( 'completed in %0.3f seconds\n' % elapsed ) if __name__ == "__main__": try: MininetRunner() except KeyboardInterrupt: info( "\n\nKeyboard Interrupt. Shutting down and cleaning up...\n\n") cleanup() except Exception: # Print exception type_, val_, trace_ = sys.exc_info() errorMsg = ( "-"*80 + "\n" + "Caught exception. Cleaning up...\n\n" + "%s: %s\n" % ( type_.__name__, val_ ) + "-"*80 + "\n" ) error( errorMsg ) # Print stack trace to debug log import traceback stackTrace = traceback.format_exc() debug( stackTrace + "\n" ) cleanup() mininet-2.2.2/custom/000077500000000000000000000000001306431124000144645ustar00rootroot00000000000000mininet-2.2.2/custom/README000066400000000000000000000006101306431124000153410ustar00rootroot00000000000000This directory should hold configuration files for custom mininets. See custom_example.py, which loads the default minimal topology. The advantage of defining a mininet in a separate file is that you then use the --custom option in mn to run the CLI or specific tests with it. To start up a mininet with the provided custom topology, do: sudo mn --custom custom_example.py --topo mytopo mininet-2.2.2/custom/topo-2sw-2host.py000066400000000000000000000015761306431124000175760ustar00rootroot00000000000000"""Custom topology example Two directly connected switches plus a host for each switch: host --- switch --- switch --- host Adding the 'topos' dict with a key/value pair to generate our newly defined topology enables one to pass in '--topo=mytopo' from the command line. """ from mininet.topo import Topo class MyTopo( Topo ): "Simple topology example." def __init__( self ): "Create custom topo." # Initialize topology Topo.__init__( self ) # Add hosts and switches leftHost = self.addHost( 'h1' ) rightHost = self.addHost( 'h2' ) leftSwitch = self.addSwitch( 's3' ) rightSwitch = self.addSwitch( 's4' ) # Add links self.addLink( leftHost, leftSwitch ) self.addLink( leftSwitch, rightSwitch ) self.addLink( rightSwitch, rightHost ) topos = { 'mytopo': ( lambda: MyTopo() ) } mininet-2.2.2/debian/000077500000000000000000000000001306431124000143745ustar00rootroot00000000000000mininet-2.2.2/debian/changelog000066400000000000000000000016301306431124000162460ustar00rootroot00000000000000mininet (2.1.0-0ubuntu1) saucy; urgency=low * Add 2.1.0 final packaging -- Bob Lantz Wed, 18 Sep 2013 22:43:47 -0700 mininet (2.1.0~rc1-0ubuntu1) saucy; urgency=low * New upstream release candidate: - d/control: Drop dependency on python-networkx, add iperf, socat and cgroup-bin to Depends. -- James Page Wed, 28 Aug 2013 10:10:20 +0100 mininet (2.0.0-0ubuntu1) raring; urgency=low * New upstream release. -- James Page Wed, 19 Dec 2012 15:48:01 +0000 mininet (2.0.0~rc1-0ubuntu1) quantal; urgency=low * New upstream release. * Update copyright to match upstream release * Fix this message -- Bob Lantz Sun, 18 Nov 2012 00:15:09 -0800 mininet (2.0.0~d4-0ubuntu1) quantal; urgency=low * Initial release. -- Bob Lantz Tue, 07 Aug 2012 14:11:27 -0700 mininet-2.2.2/debian/compat000066400000000000000000000000021306431124000155720ustar00rootroot000000000000009 mininet-2.2.2/debian/control000066400000000000000000000013671306431124000160060ustar00rootroot00000000000000Source: mininet Section: net Priority: extra Maintainer: Ubuntu Developers XSBC-Original-Maintainer: Bob Lantz Standards-Version: 3.9.3 Build-Depends: debhelper (>= 9~), help2man, python-dev, python-pkg-resources, python-setuptools Homepage: http://openflow.org/mininet Package: mininet Architecture: any Depends: openvswitch-switch, telnet, socat, iperf, cgroup-bin, ${misc:Depends}, ${python:Depends}, ${shlibs:Depends} Recommends: openvswitch-controller Description: Process-based network emulator Mininet is a network emulator which uses lightweight virtualization to create virtual networks for rapid prototyping of Software-Defined Network (SDN) designs using OpenFlow. mininet-2.2.2/debian/copyright000066400000000000000000000035771306431124000163430ustar00rootroot00000000000000Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0 Upstream-Name: mininet Source: https://github.com/mininet/mininet Files: * Copyright: 2012-2013 Open Networking Laboratory, 2009-2012 Bob Lantz, 2009-2012 The Board of Trustees of the Leland Stanford Junior University License: Original authors: Bob Lantz and Brandon Heller . We are making Mininet available for public use and benefit with the expectation that others will use, modify and enhance the Software and contribute those enhancements back to the community. However, since we would like to make the Software available for broadest use, with as few restrictions as possible permission is hereby granted, free of charge, to any person obtaining a copy of this Software to deal in the Software under the copyrights without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: . The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. . THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. . The name and trademarks of copyright holder(s) may NOT be used in advertising or publicity pertaining to the Software or any derivatives without specific, written prior permission. mininet-2.2.2/debian/docs000066400000000000000000000000121306431124000152400ustar00rootroot00000000000000README.md mininet-2.2.2/debian/examples000066400000000000000000000000131306431124000161270ustar00rootroot00000000000000examples/* mininet-2.2.2/debian/install000066400000000000000000000000201306431124000157550ustar00rootroot00000000000000mnexec /usr/bin mininet-2.2.2/debian/manpages000066400000000000000000000000041306431124000161040ustar00rootroot00000000000000*.1 mininet-2.2.2/debian/rules000077500000000000000000000002771306431124000154620ustar00rootroot00000000000000#!/usr/bin/make -f %: dh $@ --buildsystem=python_distutils --with=python2 override_dh_auto_build: make man make mnexec dh_auto_build get-orig-source: uscan --force-download --rename mininet-2.2.2/debian/source/000077500000000000000000000000001306431124000156745ustar00rootroot00000000000000mininet-2.2.2/debian/source/format000066400000000000000000000000141306431124000171020ustar00rootroot000000000000003.0 (quilt) mininet-2.2.2/debian/watch000066400000000000000000000002421306431124000154230ustar00rootroot00000000000000version=3 opts=filenamemangle=s/(.*)\/archive/$1/,uversionmangle=s/([abdr].*)\.tar\.gz/~$1/ \ https://github.com/mininet/mininet/tags .*/archive\/(\d.*\.tar\.gz) mininet-2.2.2/doc/000077500000000000000000000000001306431124000137175ustar00rootroot00000000000000mininet-2.2.2/doc/doxygen.cfg000066400000000000000000001640431306431124000160650ustar00rootroot00000000000000# Doxyfile 1.5.6 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project # # All text after a hash (#) is considered a comment and will be ignored # The format is: # TAG = value [value, ...] # For lists items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (" ") #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the config file # that follow. The default is UTF-8 which is also the encoding used for all # text before the first occurrence of this tag. Doxygen uses libiconv (or the # iconv built into libc) for the transcoding. See # http://www.gnu.org/software/libiconv for the list of possible encodings. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or a sequence of words surrounded # by quotes) that should identify the project. PROJECT_NAME = "Mininet Python API Reference Manual" # The PROJECT_NUMBER tag can be used to enter a project or revision number. # This could be handy for archiving the generated documentation or # if some version control system is used. PROJECT_NUMBER = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) # base path where the generated documentation will be put. # If a relative path is entered, it will be relative to the location # where doxygen was started. If left blank the current directory will be used. OUTPUT_DIRECTORY = doc # If the CREATE_SUBDIRS tag is set to YES, then doxygen will create # 4096 sub-directories (in 2 levels) under the output directory of each output # format and will distribute the generated files over these directories. # Enabling this option can be useful when feeding doxygen a huge amount of # source files, where putting all generated files in the same directory would # otherwise cause performance problems for the file system. CREATE_SUBDIRS = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # The default language is English, other supported languages are: # Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, # Croatian, Czech, Danish, Dutch, Farsi, Finnish, French, German, Greek, # Hungarian, Italian, Japanese, Japanese-en (Japanese with English messages), # Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, Polish, # Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, Swedish, # and Ukrainian. OUTPUT_LANGUAGE = English # If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will # include brief member descriptions after the members that are listed in # the file and class documentation (similar to JavaDoc). # Set to NO to disable this. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend # the brief description of a member or function before the detailed description. # Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator # that is used to form the text in various listings. Each string # in this list, if found as the leading text of the brief description, will be # stripped from the text and the result after processing the whole list, is # used as the annotated text. Otherwise, the brief description is used as-is. # If left blank, the following values are used ("$name" is automatically # replaced with the name of the entity): "The $name class" "The $name widget" # "The $name file" "is" "provides" "specifies" "contains" # "represents" "a" "an" "the" ABBREVIATE_BRIEF = # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # Doxygen will generate a detailed section even if there is only a brief # description. ALWAYS_DETAILED_SEC = NO # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full # path before files name in the file list and in the header files. If set # to NO the shortest path that makes the file name unique will be used. FULL_PATH_NAMES = YES # If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag # can be used to strip a user-defined part of the path. Stripping is # only done if one of the specified strings matches the left-hand part of # the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which doxygen is run is used as the # path to strip. STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of # the path mentioned in the documentation of a class, which tells # the reader which header file to include in order to use a class. # If left blank only the name of the header file containing the class # definition is used. Otherwise one should specify the include paths that # are normally passed to the compiler using the -I flag. STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter # (but less readable) file names. This can be useful is your file systems # doesn't support long names like on DOS, Mac, or CD-ROM. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen # will interpret the first line (until the first dot) of a JavaDoc-style # comment as the brief description. If set to NO, the JavaDoc # comments will behave just like regular Qt-style comments # (thus requiring an explicit @brief command for a brief description.) JAVADOC_AUTOBRIEF = NO # If the QT_AUTOBRIEF tag is set to YES then Doxygen will # interpret the first line (until the first dot) of a Qt-style # comment as the brief description. If set to NO, the comments # will behave just like regular Qt-style comments (thus requiring # an explicit \brief command for a brief description.) QT_AUTOBRIEF = NO # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen # treat a multi-line C++ special comment block (i.e. a block of //! or /// # comments) as a brief description. This used to be the default behaviour. # The new default is to treat a multi-line C++ comment block as a detailed # description. Set this tag to YES if you prefer the old behaviour instead. MULTILINE_CPP_IS_BRIEF = NO # If the DETAILS_AT_TOP tag is set to YES then Doxygen # will output the detailed description near the top, like JavaDoc. # If set to NO, the detailed description appears after the member # documentation. DETAILS_AT_TOP = NO # If the INHERIT_DOCS tag is set to YES (the default) then an undocumented # member inherits the documentation from any documented member that it # re-implements. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce # a new page for each member. If set to NO, the documentation of a member will # be part of the file/class/namespace that contains it. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. # Doxygen uses this value to replace tabs by spaces in code fragments. TAB_SIZE = 8 # This tag can be used to specify a number of aliases that acts # as commands in the documentation. An alias has the form "name=value". # For example adding "sideeffect=\par Side Effects:\n" will allow you to # put the command \sideeffect (or @sideeffect) in the documentation, which # will result in a user-defined paragraph with heading "Side Effects:". # You can put \n's in the value part of an alias to insert newlines. ALIASES = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C # sources only. Doxygen will then generate output that is more tailored for C. # For instance, some of the names that are used will be different. The list # of all members will be omitted, etc. OPTIMIZE_OUTPUT_FOR_C = NO # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java # sources only. Doxygen will then generate output that is more tailored for # Java. For instance, namespaces will be presented as packages, qualified # scopes will look different, etc. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran # sources only. Doxygen will then generate output that is more tailored for # Fortran. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL # sources. Doxygen will then generate output that is tailored for # VHDL. OPTIMIZE_OUTPUT_VHDL = NO # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should # set this tag to YES in order to let doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); v.s. # func(std::string) {}). This also make the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. BUILTIN_STL_SUPPORT = NO # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. # Doxygen will parse them like normal C++ but will assume all classes use public # instead of private inheritance when no explicit protection keyword is present. SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate getter # and setter methods for a property. Setting this option to YES (the default) # will make doxygen to replace the get and set methods by a property in the # documentation. This will only work if the methods are indeed getting or # setting a simple type. If this is not the case, or you want to show the # methods anyway, you should set this option to NO. IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES, then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. DISTRIBUTE_GROUP_DOC = NO # Set the SUBGROUPING tag to YES (the default) to allow class member groups of # the same type (for instance a group of public functions) to be put as a # subgroup of that type (e.g. under the Public Functions section). Set it to # NO to prevent subgrouping. Alternatively, this can be done per class using # the \nosubgrouping command. SUBGROUPING = YES # When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum # is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct # with name TypeT. When disabled the typedef will appear as a member of a file, # namespace, or class. And the struct will be named TypeS. This can typically # be useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. TYPEDEF_HIDES_STRUCT = NO #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in # documentation are documented, even if no documentation was available. # Private class members and static file members will be hidden unless # the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES EXTRACT_ALL = NO # If the EXTRACT_PRIVATE tag is set to YES all private members of a class # will be included in the documentation. EXTRACT_PRIVATE = NO # If the EXTRACT_STATIC tag is set to YES all static members of a file # will be included in the documentation. EXTRACT_STATIC = NO # If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) # defined locally in source files will be included in the documentation. # If set to NO only classes defined in header files are included. EXTRACT_LOCAL_CLASSES = YES # This flag is only useful for Objective-C code. When set to YES local # methods, which are defined in the implementation section but not in # the interface are included in the documentation. # If set to NO (the default) only methods in the interface are included. EXTRACT_LOCAL_METHODS = NO # If this flag is set to YES, the members of anonymous namespaces will be # extracted and appear in the documentation as a namespace called # 'anonymous_namespace{file}', where file will be replaced with the base # name of the file that contains the anonymous namespace. By default # anonymous namespace are hidden. EXTRACT_ANON_NSPACES = NO # If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all # undocumented members of documented classes, files or namespaces. # If set to NO (the default) these members will be included in the # various overviews, but no documentation section is generated. # This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. # If set to NO (the default) these classes will be included in the various # overviews. This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all # friend (class|struct|union) declarations. # If set to NO (the default) these declarations will be included in the # documentation. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any # documentation blocks found inside the body of a function. # If set to NO (the default) these blocks will be appended to the # function's detailed documentation block. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation # that is typed after a \internal command is included. If the tag is set # to NO (the default) then the documentation will be excluded. # Set it to YES to include the internal documentation. INTERNAL_DOCS = NO # If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate # file names in lower-case letters. If set to YES upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. CASE_SENSE_NAMES = YES # If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen # will show members with their full class and namespace scopes in the # documentation. If set to YES the scope will be hidden. HIDE_SCOPE_NAMES = NO # If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen # will put a list of the files that are included by a file in the documentation # of that file. SHOW_INCLUDE_FILES = YES # If the INLINE_INFO tag is set to YES (the default) then a tag [inline] # is inserted in the documentation for inline members. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen # will sort the (detailed) documentation of file and class members # alphabetically by member name. If set to NO the members will appear in # declaration order. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the # brief documentation of file, namespace and class members alphabetically # by member name. If set to NO (the default) the members will appear in # declaration order. SORT_BRIEF_DOCS = NO # If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the # hierarchy of group names into alphabetical order. If set to NO (the default) # the group names will appear in their defined order. SORT_GROUP_NAMES = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be # sorted by fully-qualified names, including namespaces. If set to # NO (the default), the class list will be sorted only by class name, # not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the # alphabetical list. SORT_BY_SCOPE_NAME = NO # The GENERATE_TODOLIST tag can be used to enable (YES) or # disable (NO) the todo list. This list is created by putting \todo # commands in the documentation. GENERATE_TODOLIST = YES # The GENERATE_TESTLIST tag can be used to enable (YES) or # disable (NO) the test list. This list is created by putting \test # commands in the documentation. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable (YES) or # disable (NO) the bug list. This list is created by putting \bug # commands in the documentation. GENERATE_BUGLIST = YES # The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or # disable (NO) the deprecated list. This list is created by putting # \deprecated commands in the documentation. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional # documentation sections, marked by \if sectionname ... \endif. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines # the initial value of a variable or define consists of for it to appear in # the documentation. If the initializer consists of more lines than specified # here it will be hidden. Use a value of 0 to hide initializers completely. # The appearance of the initializer of individual variables and defines in the # documentation can be controlled using \showinitializer or \hideinitializer # command in the documentation regardless of this setting. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated # at the bottom of the documentation of classes and structs. If set to YES the # list will mention the files that were used to generate the documentation. SHOW_USED_FILES = YES # If the sources in your project are distributed over multiple directories # then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy # in the documentation. The default is NO. SHOW_DIRECTORIES = NO # Set the SHOW_FILES tag to NO to disable the generation of the Files page. # This will remove the Files entry from the Quick Index and from the # Folder Tree View (if specified). The default is YES. SHOW_FILES = YES # Set the SHOW_NAMESPACES tag to NO to disable the generation of the # Namespaces page. This will remove the Namespaces entry from the Quick Index # and from the Folder Tree View (if specified). The default is YES. SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command , where is the value of # the FILE_VERSION_FILTER tag, and is the name of an input file # provided by doxygen. Whatever the program writes to standard output # is used as the file version. See the manual for examples. FILE_VERSION_FILTER = #--------------------------------------------------------------------------- # configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated # by doxygen. Possible values are YES and NO. If left blank NO is used. QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are # generated by doxygen. Possible values are YES and NO. If left blank # NO is used. WARNINGS = YES # If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings # for undocumented members. If EXTRACT_ALL is set to YES then this flag will # automatically be disabled. WARN_IF_UNDOCUMENTED = YES # If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as not documenting some # parameters in a documented function, or documenting parameters that # don't exist or using markup commands wrongly. WARN_IF_DOC_ERROR = YES # This WARN_NO_PARAMDOC option can be abled to get warnings for # functions that are documented, but have no documentation for their parameters # or return value. If set to NO (the default) doxygen will only warn about # wrong or incomplete parameter documentation, but not about the absence of # documentation. WARN_NO_PARAMDOC = NO # The WARN_FORMAT tag determines the format of the warning messages that # doxygen can produce. The string should contain the $file, $line, and $text # tags, which will be replaced by the file and line number from which the # warning originated and the warning text. Optionally the format may contain # $version, which will be replaced by the version of the file (if it could # be obtained via FILE_VERSION_FILTER) WARN_FORMAT = "$file:$line: $text" # The WARN_LOGFILE tag can be used to specify a file to which warning # and error messages should be written. If left blank the output is written # to stderr. WARN_LOGFILE = #--------------------------------------------------------------------------- # configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag can be used to specify the files and/or directories that contain # documented source files. You may enter file names like "myfile.cpp" or # directories like "/usr/src/myproject". Separate the files or directories # with spaces. INPUT = mininet # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is # also the default input encoding. Doxygen uses libiconv (or the iconv built # into libc) for the transcoding. See http://www.gnu.org/software/libiconv for # the list of possible encodings. INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank the following patterns are tested: # *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx # *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 FILE_PATTERNS = *.py # The RECURSIVE tag can be used to turn specify whether or not subdirectories # should be searched for input files as well. Possible values are YES and NO. # If left blank NO is used. RECURSIVE = NO # The EXCLUDE tag can be used to specify files and/or directories that should # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. EXCLUDE = # The EXCLUDE_SYMLINKS tag can be used select whether or not files or # directories that are symbolic links (a Unix filesystem feature) are excluded # from the input. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. Note that the wildcards are matched # against the file with absolute path, so to exclude all test directories # for example use the pattern */test/* EXCLUDE_PATTERNS = # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # AClass::ANamespace, ANamespace::*Test EXCLUDE_SYMBOLS = # The EXAMPLE_PATH tag can be used to specify one or more files or # directories that contain example code fragments that are included (see # the \include command). EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank all files are included. EXAMPLE_PATTERNS = # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude # commands irrespective of the value of the RECURSIVE tag. # Possible values are YES and NO. If left blank NO is used. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or # directories that contain image that are included in the documentation (see # the \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command , where # is the value of the INPUT_FILTER tag, and is the name of an # input file. Doxygen will then use the output that the filter program writes # to standard output. If FILTER_PATTERNS is specified, this tag will be # ignored. INPUT_FILTER = util/doxify.py # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the # filter if there is a match. The filters are a list of the form: # pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further # info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER # is applied to all files. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will be used to filter the input files when producing source # files to browse (i.e. when SOURCE_BROWSER is set to YES). FILTER_SOURCE_FILES = YES #--------------------------------------------------------------------------- # configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will # be generated. Documented entities will be cross-referenced with these sources. # Note: To get rid of all source code in the generated output, make sure also # VERBATIM_HEADERS is set to NO. SOURCE_BROWSER = NO # Setting the INLINE_SOURCES tag to YES will include the body # of functions and classes directly in the documentation. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct # doxygen to hide any special comment blocks from generated source code # fragments. Normal C and C++ comments will always remain visible. STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES # then for each documented function all documented # functions referencing it will be listed. REFERENCED_BY_RELATION = NO # If the REFERENCES_RELATION tag is set to YES # then for each documented function all documented entities # called/used by that function will be listed. REFERENCES_RELATION = NO # If the REFERENCES_LINK_SOURCE tag is set to YES (the default) # and SOURCE_BROWSER tag is set to YES, then the hyperlinks from # functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will # link to the source code. Otherwise they will link to the documentstion. REFERENCES_LINK_SOURCE = YES # If the USE_HTAGS tag is set to YES then the references to source code # will point to the HTML generated by the htags(1) tool instead of doxygen # built-in source browser. The htags tool is part of GNU's global source # tagging system (see http://www.gnu.org/software/global/global.html). You # will need version 4.8.6 or higher. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen # will generate a verbatim copy of the header file for each class for # which an include is specified. Set to NO to disable this. VERBATIM_HEADERS = YES #--------------------------------------------------------------------------- # configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index # of all compounds will be generated. Enable this if the project # contains a lot of classes, structs, unions or interfaces. ALPHABETICAL_INDEX = NO # If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then # the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns # in which this list will be split (can be a number in the range [1..20]) COLS_IN_ALPHA_INDEX = 5 # In case all classes in a project start with a common prefix, all # classes will be put under the same header in the alphabetical index. # The IGNORE_PREFIX tag can be used to specify one or more prefixes that # should be ignored while generating the index headers. IGNORE_PREFIX = #--------------------------------------------------------------------------- # configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES (the default) Doxygen will # generate HTML output. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `html' will be used as the default path. HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for # each generated HTML page (for example: .htm,.php,.asp). If it is left blank # doxygen will generate files with .html extension. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a personal HTML header for # each generated HTML page. If it is left blank doxygen will generate a # standard header. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a personal HTML footer for # each generated HTML page. If it is left blank doxygen will generate a # standard footer. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading # style sheet that is used by each HTML page. It can be used to # fine-tune the look of the HTML output. If the tag is left blank doxygen # will generate a default style sheet. Note that doxygen will try to copy # the style sheet file to the HTML output directory, so don't put your own # stylesheet in the HTML output directory as well, or it will be erased! HTML_STYLESHEET = # If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, # files or namespaces will be aligned in HTML using tables. If set to # NO a bullet list will be used. HTML_ALIGN_MEMBERS = YES # If the GENERATE_HTMLHELP tag is set to YES, additional index files # will be generated that can be used as input for tools like the # Microsoft HTML help workshop to generate a compiled HTML help file (.chm) # of the generated HTML documentation. GENERATE_HTMLHELP = NO # If the GENERATE_DOCSET tag is set to YES, additional index files # will be generated that can be used as input for Apple's Xcode 3 # integrated development environment, introduced with OSX 10.5 (Leopard). # To create a documentation set, doxygen will generate a Makefile in the # HTML output directory. Running make will produce the docset in that # directory and running "make install" will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find # it at startup. GENERATE_DOCSET = NO # When GENERATE_DOCSET tag is set to YES, this tag determines the name of the # feed. A documentation feed provides an umbrella under which multiple # documentation sets from a single provider (such as a company or product suite) # can be grouped. DOCSET_FEEDNAME = "Doxygen generated docs" # When GENERATE_DOCSET tag is set to YES, this tag specifies a string that # should uniquely identify the documentation set bundle. This should be a # reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen # will append .docset to the name. DOCSET_BUNDLE_ID = org.doxygen.Project # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. For this to work a browser that supports # JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox # Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). HTML_DYNAMIC_SECTIONS = NO # If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can # be used to specify the file name of the resulting .chm file. You # can add a path in front of the file if the result should not be # written to the html output directory. CHM_FILE = # If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can # be used to specify the location (absolute path including file name) of # the HTML help compiler (hhc.exe). If non-empty doxygen will try to run # the HTML help compiler on the generated index.hhp. HHC_LOCATION = # If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag # controls if a separate .chi index file is generated (YES) or that # it should be included in the master .chm file (NO). GENERATE_CHI = NO # If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING # is used to encode HtmlHelp index (hhk), content (hhc) and project file # content. CHM_INDEX_ENCODING = # If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag # controls whether a binary table of contents is generated (YES) or a # normal table of contents (NO) in the .chm file. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members # to the contents of the HTML help documentation and to the tree view. TOC_EXPAND = NO # The DISABLE_INDEX tag can be used to turn on/off the condensed index at # top of each HTML page. The value NO (the default) enables the index and # the value YES disables it. DISABLE_INDEX = NO # This tag can be used to set the number of enum values (range [1..20]) # that doxygen will group on one line in the generated HTML documentation. ENUM_VALUES_PER_LINE = 4 # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. # If the tag value is set to FRAME, a side panel will be generated # containing a tree-like index structure (just like the one that # is generated for HTML Help). For this to work a browser that supports # JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, # Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are # probably better off using the HTML help feature. Other possible values # for this tag are: HIERARCHIES, which will generate the Groups, Directories, # and Class Hiererachy pages using a tree view instead of an ordered list; # ALL, which combines the behavior of FRAME and HIERARCHIES; and NONE, which # disables this behavior completely. For backwards compatibility with previous # releases of Doxygen, the values YES and NO are equivalent to FRAME and NONE # respectively. GENERATE_TREEVIEW = NONE # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be # used to set the initial width (in pixels) of the frame in which the tree # is shown. TREEVIEW_WIDTH = 250 # Use this tag to change the font size of Latex formulas included # as images in the HTML documentation. The default is 10. Note that # when you change the font size after a successful doxygen run you need # to manually remove any form_*.png images from the HTML output directory # to force them to be regenerated. FORMULA_FONTSIZE = 10 #--------------------------------------------------------------------------- # configuration options related to the LaTeX output #--------------------------------------------------------------------------- # If the GENERATE_LATEX tag is set to YES (the default) Doxygen will # generate Latex output. GENERATE_LATEX = YES # The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `latex' will be used as the default path. LATEX_OUTPUT = latex # The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be # invoked. If left blank `latex' will be used as the default command name. LATEX_CMD_NAME = latex # The MAKEINDEX_CMD_NAME tag can be used to specify the command name to # generate index for LaTeX. If left blank `makeindex' will be used as the # default command name. MAKEINDEX_CMD_NAME = makeindex # If the COMPACT_LATEX tag is set to YES Doxygen generates more compact # LaTeX documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_LATEX = NO # The PAPER_TYPE tag can be used to set the paper type that is used # by the printer. Possible values are: a4, a4wide, letter, legal and # executive. If left blank a4wide will be used. PAPER_TYPE = letter # The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX # packages that should be included in the LaTeX output. EXTRA_PACKAGES = # The LATEX_HEADER tag can be used to specify a personal LaTeX header for # the generated latex document. The header should contain everything until # the first chapter. If it is left blank doxygen will generate a # standard header. Notice: only use this tag if you know what you are doing! LATEX_HEADER = # If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated # is prepared for conversion to pdf (using ps2pdf). The pdf file will # contain links (just like the HTML output) instead of page references # This makes the output suitable for online browsing using a pdf viewer. PDF_HYPERLINKS = YES # If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of # plain latex in the generated Makefile. Set this option to YES to get a # higher quality PDF documentation. USE_PDFLATEX = YES # If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. # command to the generated LaTeX files. This will instruct LaTeX to keep # running if errors occur, instead of asking the user for help. # This option is also used when generating formulas in HTML. LATEX_BATCHMODE = NO # If LATEX_HIDE_INDICES is set to YES then doxygen will not # include the index chapters (such as File Index, Compound Index, etc.) # in the output. LATEX_HIDE_INDICES = NO #--------------------------------------------------------------------------- # configuration options related to the RTF output #--------------------------------------------------------------------------- # If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output # The RTF output is optimized for Word 97 and may not look very pretty with # other RTF readers or editors. GENERATE_RTF = NO # The RTF_OUTPUT tag is used to specify where the RTF docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `rtf' will be used as the default path. RTF_OUTPUT = rtf # If the COMPACT_RTF tag is set to YES Doxygen generates more compact # RTF documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_RTF = NO # If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated # will contain hyperlink fields. The RTF file will # contain links (just like the HTML output) instead of page references. # This makes the output suitable for online browsing using WORD or other # programs which support those fields. # Note: wordpad (write) and others do not support links. RTF_HYPERLINKS = NO # Load stylesheet definitions from file. Syntax is similar to doxygen's # config file, i.e. a series of assignments. You only have to provide # replacements, missing definitions are set to their default value. RTF_STYLESHEET_FILE = # Set optional variables used in the generation of an rtf document. # Syntax is similar to doxygen's config file. RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- # configuration options related to the man page output #--------------------------------------------------------------------------- # If the GENERATE_MAN tag is set to YES (the default) Doxygen will # generate man pages GENERATE_MAN = NO # The MAN_OUTPUT tag is used to specify where the man pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `man' will be used as the default path. MAN_OUTPUT = man # The MAN_EXTENSION tag determines the extension that is added to # the generated man pages (default is the subroutine's section .3) MAN_EXTENSION = .3 # If the MAN_LINKS tag is set to YES and Doxygen generates man output, # then it will generate one additional man file for each entity # documented in the real man page(s). These additional files # only source the real man page, but without them the man command # would be unable to find the correct page. The default is NO. MAN_LINKS = NO #--------------------------------------------------------------------------- # configuration options related to the XML output #--------------------------------------------------------------------------- # If the GENERATE_XML tag is set to YES Doxygen will # generate an XML file that captures the structure of # the code including all documentation. GENERATE_XML = NO # The XML_OUTPUT tag is used to specify where the XML pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `xml' will be used as the default path. XML_OUTPUT = xml # The XML_SCHEMA tag can be used to specify an XML schema, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_SCHEMA = # The XML_DTD tag can be used to specify an XML DTD, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_DTD = # If the XML_PROGRAMLISTING tag is set to YES Doxygen will # dump the program listings (including syntax highlighting # and cross-referencing information) to the XML output. Note that # enabling this will significantly increase the size of the XML output. XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- # configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will # generate an AutoGen Definitions (see autogen.sf.net) file # that captures the structure of the code including all # documentation. Note that this feature is still experimental # and incomplete at the moment. GENERATE_AUTOGEN_DEF = NO #--------------------------------------------------------------------------- # configuration options related to the Perl module output #--------------------------------------------------------------------------- # If the GENERATE_PERLMOD tag is set to YES Doxygen will # generate a Perl module file that captures the structure of # the code including all documentation. Note that this # feature is still experimental and incomplete at the # moment. GENERATE_PERLMOD = NO # If the PERLMOD_LATEX tag is set to YES Doxygen will generate # the necessary Makefile rules, Perl scripts and LaTeX code to be able # to generate PDF and DVI output from the Perl module output. PERLMOD_LATEX = NO # If the PERLMOD_PRETTY tag is set to YES the Perl module output will be # nicely formatted so it can be parsed by a human reader. This is useful # if you want to understand what is going on. On the other hand, if this # tag is set to NO the size of the Perl module output will be much smaller # and Perl will parse it just the same. PERLMOD_PRETTY = YES # The names of the make variables in the generated doxyrules.make file # are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. # This is useful so different doxyrules.make files included by the same # Makefile don't overwrite each other's variables. PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor #--------------------------------------------------------------------------- # If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will # evaluate all C-preprocessor directives found in the sources and include # files. ENABLE_PREPROCESSING = YES # If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro # names in the source code. If set to NO (the default) only conditional # compilation will be performed. Macro expansion can be done in a controlled # way by setting EXPAND_ONLY_PREDEF to YES. MACRO_EXPANSION = NO # If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES # then the macro expansion is limited to the macros specified with the # PREDEFINED and EXPAND_AS_DEFINED tags. EXPAND_ONLY_PREDEF = NO # If the SEARCH_INCLUDES tag is set to YES (the default) the includes files # in the INCLUDE_PATH (see below) will be search if a #include is found. SEARCH_INCLUDES = YES # The INCLUDE_PATH tag can be used to specify one or more directories that # contain include files that are not input files but should be processed by # the preprocessor. INCLUDE_PATH = # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard # patterns (like *.h and *.hpp) to filter out the header-files in the # directories. If left blank, the patterns specified with FILE_PATTERNS will # be used. INCLUDE_FILE_PATTERNS = # The PREDEFINED tag can be used to specify one or more macro names that # are defined before the preprocessor is started (similar to the -D option of # gcc). The argument of the tag is a list of macros of the form: name # or name=definition (no spaces). If the definition and the = are # omitted =1 is assumed. To prevent a macro definition from being # undefined via #undef or recursively expanded use the := operator # instead of the = operator. PREDEFINED = # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then # this tag can be used to specify a list of macro names that should be expanded. # The macro definition that is found in the sources will be used. # Use the PREDEFINED tag if you want to use a different macro definition. EXPAND_AS_DEFINED = # If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then # doxygen's preprocessor will remove all function-like macros that are alone # on a line, have an all uppercase name, and do not end with a semicolon. Such # function macros are typically used for boiler-plate code, and will confuse # the parser if not removed. SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- # Configuration::additions related to external references #--------------------------------------------------------------------------- # The TAGFILES option can be used to specify one or more tagfiles. # Optionally an initial location of the external documentation # can be added for each tagfile. The format of a tag file without # this location is as follows: # TAGFILES = file1 file2 ... # Adding location for the tag files is done as follows: # TAGFILES = file1=loc1 "file2 = loc2" ... # where "loc1" and "loc2" can be relative or absolute paths or # URLs. If a location is present for each tag, the installdox tool # does not have to be run to correct the links. # Note that each tag file must have a unique name # (where the name does NOT include the path) # If a tag file is not located in the directory in which doxygen # is run, you must also specify the path to the tagfile here. TAGFILES = # When a file name is specified after GENERATE_TAGFILE, doxygen will create # a tag file that is based on the input files it reads. GENERATE_TAGFILE = # If the ALLEXTERNALS tag is set to YES all external classes will be listed # in the class index. If set to NO only the inherited external classes # will be listed. ALLEXTERNALS = NO # If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed # in the modules index. If set to NO, only the current project's groups will # be listed. EXTERNAL_GROUPS = YES # The PERL_PATH should be the absolute path and name of the perl script # interpreter (i.e. the result of `which perl'). PERL_PATH = /usr/bin/perl #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- # If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will # generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base # or super classes. Setting the tag to NO turns the diagrams off. Note that # this option is superseded by the HAVE_DOT option below. This is only a # fallback. It is recommended to install and use dot, since it yields more # powerful graphs. CLASS_DIAGRAMS = YES # You can define message sequence charts within doxygen comments using the \msc # command. Doxygen will then run the mscgen tool (see # http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the # documentation. The MSCGEN_PATH tag allows you to specify the directory where # the mscgen tool resides. If left empty the tool is assumed to be found in the # default search path. MSCGEN_PATH = # If set to YES, the inheritance and collaboration graphs will hide # inheritance and usage relations if the target is undocumented # or is not a class. HIDE_UNDOC_RELATIONS = YES # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz, a graph visualization # toolkit from AT&T and Lucent Bell Labs. The other options in this section # have no effect if this option is set to NO (the default) HAVE_DOT = NO # By default doxygen will write a font called FreeSans.ttf to the output # directory and reference it in all dot files that doxygen generates. This # font does not include all possible unicode characters however, so when you need # these (or just want a differently looking font) you can specify the font name # using DOT_FONTNAME. You need need to make sure dot is able to find the font, # which can be done by putting it in a standard location or by setting the # DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory # containing the font. DOT_FONTNAME = FreeSans # By default doxygen will tell dot to use the output directory to look for the # FreeSans.ttf font (which doxygen will put there itself). If you specify a # different font using DOT_FONTNAME you can set the path where dot # can find it using this tag. DOT_FONTPATH = # If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect inheritance relations. Setting this tag to YES will force the # the CLASS_DIAGRAMS tag to NO. CLASS_GRAPH = YES # If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect implementation dependencies (inheritance, containment, and # class references variables) of the class with other documented classes. COLLABORATION_GRAPH = YES # If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen # will generate a graph for groups, showing the direct groups dependencies GROUP_GRAPHS = YES # If the UML_LOOK tag is set to YES doxygen will generate inheritance and # collaboration diagrams in a style similar to the OMG's Unified Modeling # Language. UML_LOOK = NO # If set to YES, the inheritance and collaboration graphs will show the # relations between templates and their instances. TEMPLATE_RELATIONS = NO # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT # tags are set to YES then doxygen will generate a graph for each documented # file showing the direct and indirect include dependencies of the file with # other documented files. INCLUDE_GRAPH = YES # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and # HAVE_DOT tags are set to YES then doxygen will generate a graph for each # documented header file showing the documented files that directly or # indirectly include this file. INCLUDED_BY_GRAPH = YES # If the CALL_GRAPH and HAVE_DOT options are set to YES then # doxygen will generate a call dependency graph for every global function # or class method. Note that enabling this option will significantly increase # the time of a run. So in most cases it will be better to enable call graphs # for selected functions only using the \callgraph command. CALL_GRAPH = NO # If the CALLER_GRAPH and HAVE_DOT tags are set to YES then # doxygen will generate a caller dependency graph for every global function # or class method. Note that enabling this option will significantly increase # the time of a run. So in most cases it will be better to enable caller # graphs for selected functions only using the \callergraph command. CALLER_GRAPH = NO # If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen # will graphical hierarchy of all classes instead of a textual one. GRAPHICAL_HIERARCHY = YES # If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES # then doxygen will show the dependencies a directory has on other directories # in a graphical way. The dependency relations are determined by the #include # relations between the files in the directories. DIRECTORY_GRAPH = YES # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. Possible values are png, jpg, or gif # If left blank png will be used. DOT_IMAGE_FORMAT = png # The tag DOT_PATH can be used to specify the path where the dot tool can be # found. If left blank, it is assumed the dot tool can be found in the path. DOT_PATH = # The DOTFILE_DIRS tag can be used to specify one or more directories that # contain dot files that are included in the documentation (see the # \dotfile command). DOTFILE_DIRS = # The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of # nodes that will be shown in the graph. If the number of nodes in a graph # becomes larger than this value, doxygen will truncate the graph, which is # visualized by representing a node as a red box. Note that doxygen if the # number of direct children of the root node in a graph is already larger than # DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note # that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. DOT_GRAPH_MAX_NODES = 50 # The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the # graphs generated by dot. A depth value of 3 means that only nodes reachable # from the root by following a path via at most 3 edges will be shown. Nodes # that lay further from the root node will be omitted. Note that setting this # option to 1 or 2 may greatly reduce the computation time needed for large # code bases. Also note that the size of a graph can be further restricted by # DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. MAX_DOT_GRAPH_DEPTH = 0 # Set the DOT_TRANSPARENT tag to YES to generate images with a transparent # background. This is enabled by default, which results in a transparent # background. Warning: Depending on the platform used, enabling this option # may lead to badly anti-aliased labels on the edges of a graph (i.e. they # become hard to read). DOT_TRANSPARENT = YES # Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output # files in one run (i.e. multiple -o and -T options on the command line). This # makes dot run faster, but since only newer versions of dot (>1.8.10) # support this, this feature is disabled by default. DOT_MULTI_TARGETS = NO # If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will # generate a legend page explaining the meaning of the various boxes and # arrows in the dot generated graphs. GENERATE_LEGEND = YES # If the DOT_CLEANUP tag is set to YES (the default) Doxygen will # remove the intermediate dot files that are used to generate # the various graphs. DOT_CLEANUP = YES #--------------------------------------------------------------------------- # Configuration::additions related to the search engine #--------------------------------------------------------------------------- # The SEARCHENGINE tag specifies whether or not a search engine should be # used. If set to NO the values of all tags below this one will be ignored. SEARCHENGINE = NO mininet-2.2.2/examples/000077500000000000000000000000001306431124000147705ustar00rootroot00000000000000mininet-2.2.2/examples/README.md000066400000000000000000000115451306431124000162550ustar00rootroot00000000000000Mininet Examples ======================================================== These examples are intended to help you get started using Mininet's Python API. ======================================================== #### baresshd.py: This example uses Mininet's medium-level API to create an sshd process running in a namespace. Doesn't use OpenFlow. #### bind.py: This example shows how you can create private directories for each node in a Mininet topology. #### cluster.py: This example contains all of the code for experimental cluster edition. Remote classes and MininetCluster can be imported from here to create a topology with nodes on remote machines. #### clusterSanity.py: This example runs cluster edition locally as a sanity check to test basic functionality. #### clustercli.py: This example contains a CLI for experimental cluster edition. #### clusterdemo.py: This example is a basic demo of cluster edition on 3 servers with a tree topology of depth 3 and fanout 3. #### consoles.py: This example creates a grid of console windows, one for each node, and allows interaction with and monitoring of each console, including graphical monitoring. #### controllers.py: This example creates a network with multiple controllers, by using a custom `Switch()` subclass. #### controllers2.py: This example creates a network with multiple controllers by creating an empty network, adding nodes to it, and manually starting the switches. #### controlnet.py: This examples shows how you can model the control network as well as the data network, by actually creating two Mininet objects. #### cpu.py: This example tests iperf bandwidth for varying CPU limits. #### emptynet.py: This example demonstrates creating an empty network (i.e. with no topology object) and adding nodes to it. #### hwintf.py: This example shows how to add an interface (for example a real hardware interface) to a network after the network is created. #### intfoptions.py: This example reconfigures a TCIntf during runtime with different traffic control commands to test bandwidth, loss, and delay. #### limit.py: This example shows how to use link and CPU limits. #### linearbandwidth.py: This example shows how to create a custom topology programatically by subclassing Topo, and how to run a series of tests on it. #### linuxrouter.py: This example shows how to create and configure a router in Mininet that uses Linux IP forwarding. #### miniedit.py: This example demonstrates creating a network via a graphical editor. #### mobility.py: This example demonstrates detaching an interface from one switch and attaching it another as a basic way to move a host around a network. #### multiLink.py: This example demonstrates the creation of multiple links between nodes using a custom Topology class. #### multiping.py: This example demonstrates one method for monitoring output from multiple hosts, using `node.monitor()`. #### multipoll.py: This example demonstrates monitoring output files from multiple hosts. #### multitest.py: This example creates a network and runs multiple tests on it. #### nat.py: This example shows how to connect a Mininet network to the Internet using NAT. It also answers the eternal question "why can't I ping `google.com`?" #### natnet.py: This example demonstrates how to create a network using a NAT node to connect hosts to the internet. #### numberedports.py: This example verifies the mininet ofport numbers match up to the ovs port numbers. It also verifies that the port numbers match up to the interface numbers #### popen.py: This example monitors a number of hosts using `host.popen()` and `pmonitor()`. #### popenpoll.py: This example demonstrates monitoring output from multiple hosts using the `node.popen()` interface (which returns `Popen` objects) and `pmonitor()`. #### scratchnet.py, scratchnetuser.py: These two examples demonstrate how to create a network by using the lowest- level Mininet functions. Generally the higher-level API is easier to use, but scratchnet shows what is going on behind the scenes. #### simpleperf.py: A simple example of configuring network and CPU bandwidth limits. #### sshd.py: This example shows how to run an `sshd` process in each host, allowing you to log in via `ssh`. This requires connecting the Mininet data network to an interface in the root namespace (generaly the control network already lives in the root namespace, so it does not need to be explicitly connected.) #### tree1024.py: This example attempts to create a 1024-host network, and then runs the CLI on it. It may run into scalability limits, depending on available memory and `sysctl` configuration (see `INSTALL`.) #### treeping64.py: This example creates a 64-host tree network, and attempts to check full connectivity using `ping`, for different switch/datapath types. #### vlanhost.py: An example of how to subclass Host to use a VLAN on its primary interface. mininet-2.2.2/examples/__init__.py000066400000000000000000000000601306431124000170750ustar00rootroot00000000000000""" Mininet Examples See README for details """ mininet-2.2.2/examples/baresshd.py000077500000000000000000000020621306431124000171400ustar00rootroot00000000000000#!/usr/bin/python "This example doesn't use OpenFlow, but attempts to run sshd in a namespace." import sys from mininet.node import Host from mininet.util import ensureRoot, waitListening ensureRoot() timeout = 5 print "*** Creating nodes" h1 = Host( 'h1' ) root = Host( 'root', inNamespace=False ) print "*** Creating links" h1.linkTo( root ) print h1 print "*** Configuring nodes" h1.setIP( '10.0.0.1', 8 ) root.setIP( '10.0.0.2', 8 ) print "*** Creating banner file" f = open( '/tmp/%s.banner' % h1.name, 'w' ) f.write( 'Welcome to %s at %s\n' % ( h1.name, h1.IP() ) ) f.close() print "*** Running sshd" cmd = '/usr/sbin/sshd -o UseDNS=no -u0 -o "Banner /tmp/%s.banner"' % h1.name # add arguments from the command line if len( sys.argv ) > 1: cmd += ' ' + ' '.join( sys.argv[ 1: ] ) h1.cmd( cmd ) listening = waitListening( server=h1, port=22, timeout=timeout ) if listening: print "*** You may now ssh into", h1.name, "at", h1.IP() else: print ( "*** Warning: after %s seconds, %s is not listening on port 22" % ( timeout, h1.name ) ) mininet-2.2.2/examples/bind.py000077500000000000000000000044061306431124000162650ustar00rootroot00000000000000#!/usr/bin/python """ bind.py: Bind mount example This creates hosts with private directories that the user specifies. These hosts may have persistent directories that will be available across multiple mininet session, or temporary directories that will only last for one mininet session. To specify a persistent directory, add a tuple to a list of private directories: [ ( 'directory to be mounted on', 'directory to be mounted' ) ] String expansion may be used to create a directory template for each host. To do this, add a %(name)s in place of the host name when creating your list of directories: [ ( '/var/run', '/tmp/%(name)s/var/run' ) ] If no persistent directory is specified, the directories will default to temporary private directories. To do this, simply create a list of directories to be made private. A tmpfs will then be mounted on them. You may use both temporary and persistent directories at the same time. In the following privateDirs string, each host will have a persistent directory in the root filesystem at "/tmp/(hostname)/var/run" mounted on "/var/run". Each host will also have a temporary private directory mounted on "/var/log". [ ( '/var/run', '/tmp/%(name)s/var/run' ), '/var/log' ] This example has both persistent directories mounted on '/var/log' and '/var/run'. It also has a temporary private directory mounted on '/var/mn' """ from mininet.net import Mininet from mininet.node import Host from mininet.cli import CLI from mininet.topo import SingleSwitchTopo from mininet.log import setLogLevel, info from functools import partial # Sample usage def testHostWithPrivateDirs(): "Test bind mounts" topo = SingleSwitchTopo( 10 ) privateDirs = [ ( '/var/log', '/tmp/%(name)s/var/log' ), ( '/var/run', '/tmp/%(name)s/var/run' ), '/var/mn' ] host = partial( Host, privateDirs=privateDirs ) net = Mininet( topo=topo, host=host ) net.start() directories = [ directory[ 0 ] if isinstance( directory, tuple ) else directory for directory in privateDirs ] info( 'Private Directories:', directories, '\n' ) CLI( net ) net.stop() if __name__ == '__main__': setLogLevel( 'info' ) testHostWithPrivateDirs() info( 'Done.\n') mininet-2.2.2/examples/cluster.py000077500000000000000000001012231306431124000170250ustar00rootroot00000000000000#!/usr/bin/python """ cluster.py: prototyping/experimentation for distributed Mininet, aka Mininet: Cluster Edition Author: Bob Lantz Core classes: RemoteNode: a Node() running on a remote server RemoteOVSSwitch(): an OVSSwitch() running on a remote server RemoteLink: a Link() on a remote server Tunnel: a Link() between a local Node() and a RemoteNode() These are largely interoperable with local objects. - One Mininet to rule them all It is important that the same topologies, APIs, and CLI can be used with minimal or no modification in both local and distributed environments. - Multiple placement models Placement should be as easy as possible. We should provide basic placement support and also allow for explicit placement. Questions: What is the basic communication mechanism? To start with? Probably a single multiplexed ssh connection between each pair of mininet servers that needs to communicate. How are tunnels created? We have several options including ssh, GRE, OF capsulator, socat, VDE, l2tp, etc.. It's not clear what the best one is. For now, we use ssh tunnels since they are encrypted and semi-automatically shared. We will probably want to support GRE as well because it's very easy to set up with OVS. How are tunnels destroyed? They are destroyed when the links are deleted in Mininet.stop() How does RemoteNode.popen() work? It opens a shared ssh connection to the remote server and attaches to the namespace using mnexec -a -g. Is there any value to using Paramiko vs. raw ssh? Maybe, but it doesn't seem to support L2 tunneling. Should we preflight the entire network, including all server-to-server connections? Yes! We don't yet do this with remote server-to-server connections yet. Should we multiplex the link ssh connections? Yes, this is done automatically with ControlMaster=auto. Note on ssh and DNS: Please add UseDNS: no to your /etc/ssh/sshd_config!!! Things to do: - asynchronous/pipelined/parallel startup - ssh debugging/profiling - make connections into real objects - support for other tunneling schemes - tests and benchmarks - hifi support (e.g. delay compensation) """ from mininet.node import Node, Host, OVSSwitch, Controller from mininet.link import Link, Intf from mininet.net import Mininet from mininet.topo import LinearTopo from mininet.topolib import TreeTopo from mininet.util import quietRun, errRun from mininet.examples.clustercli import CLI from mininet.log import setLogLevel, debug, info, error from mininet.clean import addCleanupCallback from signal import signal, SIGINT, SIG_IGN from subprocess import Popen, PIPE, STDOUT import os from random import randrange import sys import re from itertools import groupby from operator import attrgetter from distutils.version import StrictVersion def findUser(): "Try to return logged-in (usually non-root) user" return ( # If we're running sudo os.environ.get( 'SUDO_USER', False ) or # Logged-in user (if we have a tty) ( quietRun( 'who am i' ).split() or [ False ] )[ 0 ] or # Give up and return effective user quietRun( 'whoami' ).strip() ) class ClusterCleanup( object ): "Cleanup callback" inited = False serveruser = {} @classmethod def add( cls, server, user='' ): "Add an entry to server: user dict" if not cls.inited: addCleanupCallback( cls.cleanup ) if not user: user = findUser() cls.serveruser[ server ] = user @classmethod def cleanup( cls ): "Clean up" info( '*** Cleaning up cluster\n' ) for server, user in cls.serveruser.iteritems(): if server == 'localhost': # Handled by mininet.clean.cleanup() continue else: cmd = [ 'su', user, '-c', 'ssh %s@%s sudo mn -c' % ( user, server ) ] info( cmd, '\n' ) info( quietRun( cmd ) ) # BL note: so little code is required for remote nodes, # we will probably just want to update the main Node() # class to enable it for remote access! However, there # are a large number of potential failure conditions with # remote nodes which we may want to detect and handle. # Another interesting point is that we could put everything # in a mix-in class and easily add cluster mode to 2.0. class RemoteMixin( object ): "A mix-in class to turn local nodes into remote nodes" # ssh base command # -q: don't print stupid diagnostic messages # BatchMode yes: don't ask for password # ForwardAgent yes: forward authentication credentials sshbase = [ 'ssh', '-q', '-o', 'BatchMode=yes', '-o', 'ForwardAgent=yes', '-tt' ] def __init__( self, name, server='localhost', user=None, serverIP=None, controlPath=False, splitInit=False, **kwargs): """Instantiate a remote node name: name of remote node server: remote server (optional) user: user on remote server (optional) controlPath: specify shared ssh control path (optional) splitInit: split initialization? **kwargs: see Node()""" # We connect to servers by IP address self.server = server if server else 'localhost' self.serverIP = ( serverIP if serverIP else self.findServerIP( self.server ) ) self.user = user if user else findUser() ClusterCleanup.add( server=server, user=user ) if controlPath is True: # Set a default control path for shared SSH connections controlPath = '/tmp/mn-%r@%h:%p' self.controlPath = controlPath self.splitInit = splitInit if self.user and self.server != 'localhost': self.dest = '%s@%s' % ( self.user, self.serverIP ) self.sshcmd = [ 'sudo', '-E', '-u', self.user ] + self.sshbase if self.controlPath: self.sshcmd += [ '-o', 'ControlPath=' + self.controlPath, '-o', 'ControlMaster=auto', '-o', 'ControlPersist=' + '1' ] self.sshcmd += [ self.dest ] self.isRemote = True else: self.dest = None self.sshcmd = [] self.isRemote = False # Satisfy pylint self.shell, self.pid = None, None super( RemoteMixin, self ).__init__( name, **kwargs ) # Determine IP address of local host _ipMatchRegex = re.compile( r'\d+\.\d+\.\d+\.\d+' ) @classmethod def findServerIP( cls, server ): "Return our server's IP address" # First, check for an IP address ipmatch = cls._ipMatchRegex.findall( server ) if ipmatch: return ipmatch[ 0 ] # Otherwise, look up remote server output = quietRun( 'getent ahostsv4 %s' % server ) ips = cls._ipMatchRegex.findall( output ) ip = ips[ 0 ] if ips else None return ip # Command support via shell process in namespace def startShell( self, *args, **kwargs ): "Start a shell process for running commands" if self.isRemote: kwargs.update( mnopts='-c' ) super( RemoteMixin, self ).startShell( *args, **kwargs ) # Optional split initialization self.sendCmd( 'echo $$' ) if not self.splitInit: self.finishInit() def finishInit( self ): "Wait for split initialization to complete" self.pid = int( self.waitOutput() ) def rpopen( self, *cmd, **opts ): "Return a Popen object on underlying server in root namespace" params = { 'stdin': PIPE, 'stdout': PIPE, 'stderr': STDOUT, 'sudo': True } params.update( opts ) return self._popen( *cmd, **params ) def rcmd( self, *cmd, **opts): """rcmd: run a command on underlying server in root namespace args: string or list of strings returns: stdout and stderr""" popen = self.rpopen( *cmd, **opts ) # print 'RCMD: POPEN:', popen # These loops are tricky to get right. # Once the process exits, we can read # EOF twice if necessary. result = '' while True: poll = popen.poll() result += popen.stdout.read() if poll is not None: break return result @staticmethod def _ignoreSignal(): "Detach from process group to ignore all signals" os.setpgrp() def _popen( self, cmd, sudo=True, tt=True, **params): """Spawn a process on a remote node cmd: remote command to run (list) **params: parameters to Popen() returns: Popen() object""" if type( cmd ) is str: cmd = cmd.split() if self.isRemote: if sudo: cmd = [ 'sudo', '-E' ] + cmd if tt: cmd = self.sshcmd + cmd else: # Hack: remove -tt sshcmd = list( self.sshcmd ) sshcmd.remove( '-tt' ) cmd = sshcmd + cmd else: if self.user and not sudo: # Drop privileges cmd = [ 'sudo', '-E', '-u', self.user ] + cmd params.update( preexec_fn=self._ignoreSignal ) debug( '_popen', cmd, '\n' ) popen = super( RemoteMixin, self )._popen( cmd, **params ) return popen def popen( self, *args, **kwargs ): "Override: disable -tt" return super( RemoteMixin, self).popen( *args, tt=False, **kwargs ) def addIntf( self, *args, **kwargs ): "Override: use RemoteLink.moveIntf" kwargs.update( moveIntfFn=RemoteLink.moveIntf ) return super( RemoteMixin, self).addIntf( *args, **kwargs ) class RemoteNode( RemoteMixin, Node ): "A node on a remote server" pass class RemoteHost( RemoteNode ): "A RemoteHost is simply a RemoteNode" pass class RemoteOVSSwitch( RemoteMixin, OVSSwitch ): "Remote instance of Open vSwitch" OVSVersions = {} def __init__( self, *args, **kwargs ): # No batch startup yet kwargs.update( batch=True ) super( RemoteOVSSwitch, self ).__init__( *args, **kwargs ) def isOldOVS( self ): "Is remote switch using an old OVS version?" cls = type( self ) if self.server not in cls.OVSVersions: # pylint: disable=not-callable vers = self.cmd( 'ovs-vsctl --version' ) # pylint: enable=not-callable cls.OVSVersions[ self.server ] = re.findall( r'\d+\.\d+', vers )[ 0 ] return ( StrictVersion( cls.OVSVersions[ self.server ] ) < StrictVersion( '1.10' ) ) @classmethod def batchStartup( cls, switches, **_kwargs ): "Start up switches in per-server batches" key = attrgetter( 'server' ) for server, switchGroup in groupby( sorted( switches, key=key ), key ): info( '(%s)' % server ) group = tuple( switchGroup ) switch = group[ 0 ] OVSSwitch.batchStartup( group, run=switch.cmd ) return switches @classmethod def batchShutdown( cls, switches, **_kwargs ): "Stop switches in per-server batches" key = attrgetter( 'server' ) for server, switchGroup in groupby( sorted( switches, key=key ), key ): info( '(%s)' % server ) group = tuple( switchGroup ) switch = group[ 0 ] OVSSwitch.batchShutdown( group, run=switch.rcmd ) return switches class RemoteLink( Link ): "A RemoteLink is a link between nodes which may be on different servers" def __init__( self, node1, node2, **kwargs ): """Initialize a RemoteLink see Link() for parameters""" # Create links on remote node self.node1 = node1 self.node2 = node2 self.tunnel = None kwargs.setdefault( 'params1', {} ) kwargs.setdefault( 'params2', {} ) self.cmd = None # satisfy pylint Link.__init__( self, node1, node2, **kwargs ) def stop( self ): "Stop this link" if self.tunnel: self.tunnel.terminate() self.intf1.delete() self.intf2.delete() else: Link.stop( self ) self.tunnel = None def makeIntfPair( self, intfname1, intfname2, addr1=None, addr2=None, node1=None, node2=None, deleteIntfs=True ): """Create pair of interfaces intfname1: name of interface 1 intfname2: name of interface 2 (override this method [and possibly delete()] to change link type)""" node1 = self.node1 if node1 is None else node1 node2 = self.node2 if node2 is None else node2 server1 = getattr( node1, 'server', 'localhost' ) server2 = getattr( node2, 'server', 'localhost' ) if server1 == server2: # Link within same server return Link.makeIntfPair( intfname1, intfname2, addr1, addr2, node1, node2, deleteIntfs=deleteIntfs ) # Otherwise, make a tunnel self.tunnel = self.makeTunnel( node1, node2, intfname1, intfname2, addr1, addr2 ) return self.tunnel @staticmethod def moveIntf( intf, node, printError=True ): """Move remote interface from root ns to node intf: string, interface dstNode: destination Node srcNode: source Node or None (default) for root ns printError: if true, print error""" intf = str( intf ) cmd = 'ip link set %s netns %s' % ( intf, node.pid ) node.rcmd( cmd ) links = node.cmd( 'ip link show' ) if not re.search( r' %s[:@]' % intf, links ): if printError: error( '*** Error: RemoteLink.moveIntf: ' + intf + ' not successfully moved to ' + node.name + '\n' ) return False return True def makeTunnel( self, node1, node2, intfname1, intfname2, addr1=None, addr2=None ): "Make a tunnel across switches on different servers" # We should never try to create a tunnel to ourselves! assert node1.server != 'localhost' or node2.server != 'localhost' # And we can't ssh into this server remotely as 'localhost', # so try again swappping node1 and node2 if node2.server == 'localhost': return self.makeTunnel( node2, node1, intfname2, intfname1, addr2, addr1 ) # 1. Create tap interfaces for node in node1, node2: # For now we are hard-wiring tap9, which we will rename cmd = 'ip tuntap add dev tap9 mode tap user ' + node.user result = node.rcmd( cmd ) if result: raise Exception( 'error creating tap9 on %s: %s' % ( node, result ) ) # 2. Create ssh tunnel between tap interfaces # -n: close stdin dest = '%s@%s' % ( node2.user, node2.serverIP ) cmd = [ 'ssh', '-n', '-o', 'Tunnel=Ethernet', '-w', '9:9', dest, 'echo @' ] self.cmd = cmd tunnel = node1.rpopen( cmd, sudo=False ) # When we receive the character '@', it means that our # tunnel should be set up debug( 'Waiting for tunnel to come up...\n' ) ch = tunnel.stdout.read( 1 ) if ch != '@': raise Exception( 'makeTunnel:\n', 'Tunnel setup failed for', '%s:%s' % ( node1, node1.dest ), 'to', '%s:%s\n' % ( node2, node2.dest ), 'command was:', cmd, '\n' ) # 3. Move interfaces if necessary for node in node1, node2: if not self.moveIntf( 'tap9', node ): raise Exception( 'interface move failed on node %s' % node ) # 4. Rename tap interfaces to desired names for node, intf, addr in ( ( node1, intfname1, addr1 ), ( node2, intfname2, addr2 ) ): if not addr: result = node.cmd( 'ip link set tap9 name', intf ) else: result = node.cmd( 'ip link set tap9 name', intf, 'address', addr ) if result: raise Exception( 'error renaming %s: %s' % ( intf, result ) ) return tunnel def status( self ): "Detailed representation of link" if self.tunnel: if self.tunnel.poll() is not None: status = "Tunnel EXITED %s" % self.tunnel.returncode else: status = "Tunnel Running (%s: %s)" % ( self.tunnel.pid, self.cmd ) else: status = "OK" result = "%s %s" % ( Link.status( self ), status ) return result # Some simple placement algorithms for MininetCluster class Placer( object ): "Node placement algorithm for MininetCluster" def __init__( self, servers=None, nodes=None, hosts=None, switches=None, controllers=None, links=None ): """Initialize placement object servers: list of servers nodes: list of all nodes hosts: list of hosts switches: list of switches controllers: list of controllers links: list of links (all arguments are optional) returns: server""" self.servers = servers or [] self.nodes = nodes or [] self.hosts = hosts or [] self.switches = switches or [] self.controllers = controllers or [] self.links = links or [] def place( self, node ): "Return server for a given node" assert self, node # satisfy pylint # Default placement: run locally return 'localhost' class RandomPlacer( Placer ): "Random placement" def place( self, nodename ): """Random placement function nodename: node name""" assert nodename # please pylint # This may be slow with lots of servers return self.servers[ randrange( 0, len( self.servers ) ) ] class RoundRobinPlacer( Placer ): """Round-robin placement Note this will usually result in cross-server links between hosts and switches""" def __init__( self, *args, **kwargs ): Placer.__init__( self, *args, **kwargs ) self.next = 0 def place( self, nodename ): """Round-robin placement function nodename: node name""" assert nodename # please pylint # This may be slow with lots of servers server = self.servers[ self.next ] self.next = ( self.next + 1 ) % len( self.servers ) return server class SwitchBinPlacer( Placer ): """Place switches (and controllers) into evenly-sized bins, and attempt to co-locate hosts and switches""" def __init__( self, *args, **kwargs ): Placer.__init__( self, *args, **kwargs ) # Easy lookup for servers and node sets self.servdict = dict( enumerate( self.servers ) ) self.hset = frozenset( self.hosts ) self.sset = frozenset( self.switches ) self.cset = frozenset( self.controllers ) # Server and switch placement indices self.placement = self.calculatePlacement() @staticmethod def bin( nodes, servers ): "Distribute nodes evenly over servers" # Calculate base bin size nlen = len( nodes ) slen = len( servers ) # Basic bin size quotient = int( nlen / slen ) binsizes = { server: quotient for server in servers } # Distribute remainder remainder = nlen % slen for server in servers[ 0 : remainder ]: binsizes[ server ] += 1 # Create binsize[ server ] tickets for each server tickets = sum( [ binsizes[ server ] * [ server ] for server in servers ], [] ) # And assign one ticket to each node return { node: ticket for node, ticket in zip( nodes, tickets ) } def calculatePlacement( self ): "Pre-calculate node placement" placement = {} # Create host-switch connectivity map, # associating host with last switch that it's # connected to switchFor = {} for src, dst in self.links: if src in self.hset and dst in self.sset: switchFor[ src ] = dst if dst in self.hset and src in self.sset: switchFor[ dst ] = src # Place switches placement = self.bin( self.switches, self.servers ) # Place controllers and merge into placement dict placement.update( self.bin( self.controllers, self.servers ) ) # Co-locate hosts with their switches for h in self.hosts: if h in placement: # Host is already placed - leave it there continue if h in switchFor: placement[ h ] = placement[ switchFor[ h ] ] else: raise Exception( "SwitchBinPlacer: cannot place isolated host " + h ) return placement def place( self, node ): """Simple placement algorithm: place switches into evenly sized bins, and place hosts near their switches""" return self.placement[ node ] class HostSwitchBinPlacer( Placer ): """Place switches *and hosts* into evenly-sized bins Note that this will usually result in cross-server links between hosts and switches""" def __init__( self, *args, **kwargs ): Placer.__init__( self, *args, **kwargs ) # Calculate bin sizes scount = len( self.servers ) self.hbin = max( int( len( self.hosts ) / scount ), 1 ) self.sbin = max( int( len( self.switches ) / scount ), 1 ) self.cbin = max( int( len( self.controllers ) / scount ), 1 ) info( 'scount:', scount ) info( 'bins:', self.hbin, self.sbin, self.cbin, '\n' ) self.servdict = dict( enumerate( self.servers ) ) self.hset = frozenset( self.hosts ) self.sset = frozenset( self.switches ) self.cset = frozenset( self.controllers ) self.hind, self.sind, self.cind = 0, 0, 0 def place( self, nodename ): """Simple placement algorithm: place nodes into evenly sized bins""" # Place nodes into bins if nodename in self.hset: server = self.servdict[ self.hind / self.hbin ] self.hind += 1 elif nodename in self.sset: server = self.servdict[ self.sind / self.sbin ] self.sind += 1 elif nodename in self.cset: server = self.servdict[ self.cind / self.cbin ] self.cind += 1 else: info( 'warning: unknown node', nodename ) server = self.servdict[ 0 ] return server # The MininetCluster class is not strictly necessary. # However, it has several purposes: # 1. To set up ssh connection sharing/multiplexing # 2. To pre-flight the system so that everything is more likely to work # 3. To allow connection/connectivity monitoring # 4. To support pluggable placement algorithms class MininetCluster( Mininet ): "Cluster-enhanced version of Mininet class" # Default ssh command # BatchMode yes: don't ask for password # ForwardAgent yes: forward authentication credentials sshcmd = [ 'ssh', '-o', 'BatchMode=yes', '-o', 'ForwardAgent=yes' ] def __init__( self, *args, **kwargs ): """servers: a list of servers to use (note: include localhost or None to use local system as well) user: user name for server ssh placement: Placer() subclass""" params = { 'host': RemoteHost, 'switch': RemoteOVSSwitch, 'link': RemoteLink, 'precheck': True } params.update( kwargs ) servers = params.pop( 'servers', [ 'localhost' ] ) servers = [ s if s else 'localhost' for s in servers ] self.servers = servers self.serverIP = params.pop( 'serverIP', {} ) if not self.serverIP: self.serverIP = { server: RemoteMixin.findServerIP( server ) for server in self.servers } self.user = params.pop( 'user', findUser() ) if params.pop( 'precheck' ): self.precheck() self.connections = {} self.placement = params.pop( 'placement', SwitchBinPlacer ) # Make sure control directory exists self.cdir = os.environ[ 'HOME' ] + '/.ssh/mn' errRun( [ 'mkdir', '-p', self.cdir ] ) Mininet.__init__( self, *args, **params ) def popen( self, cmd ): "Popen() for server connections" assert self # please pylint old = signal( SIGINT, SIG_IGN ) conn = Popen( cmd, stdin=PIPE, stdout=PIPE, close_fds=True ) signal( SIGINT, old ) return conn def baddLink( self, *args, **kwargs ): "break addlink for testing" pass def precheck( self ): """Pre-check to make sure connection works and that we can call sudo without a password""" result = 0 info( '*** Checking servers\n' ) for server in self.servers: ip = self.serverIP[ server ] if not server or server == 'localhost': continue info( server, '' ) dest = '%s@%s' % ( self.user, ip ) cmd = [ 'sudo', '-E', '-u', self.user ] cmd += self.sshcmd + [ '-n', dest, 'sudo true' ] debug( ' '.join( cmd ), '\n' ) _out, _err, code = errRun( cmd ) if code != 0: error( '\nstartConnection: server connection check failed ' 'to %s using command:\n%s\n' % ( server, ' '.join( cmd ) ) ) result |= code if result: error( '*** Server precheck failed.\n' '*** Make sure that the above ssh command works' ' correctly.\n' '*** You may also need to run mn -c on all nodes, and/or\n' '*** use sudo -E.\n' ) sys.exit( 1 ) info( '\n' ) def modifiedaddHost( self, *args, **kwargs ): "Slightly modify addHost" assert self # please pylint kwargs[ 'splitInit' ] = True return Mininet.addHost( *args, **kwargs ) def placeNodes( self ): """Place nodes on servers (if they don't have a server), and start shell processes""" if not self.servers or not self.topo: # No shirt, no shoes, no service return nodes = self.topo.nodes() placer = self.placement( servers=self.servers, nodes=self.topo.nodes(), hosts=self.topo.hosts(), switches=self.topo.switches(), links=self.topo.links() ) for node in nodes: config = self.topo.nodeInfo( node ) # keep local server name consistent accross nodes if 'server' in config.keys() and config[ 'server' ] is None: config[ 'server' ] = 'localhost' server = config.setdefault( 'server', placer.place( node ) ) if server: config.setdefault( 'serverIP', self.serverIP[ server ] ) info( '%s:%s ' % ( node, server ) ) key = ( None, server ) _dest, cfile, _conn = self.connections.get( key, ( None, None, None ) ) if cfile: config.setdefault( 'controlPath', cfile ) def addController( self, *args, **kwargs ): "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() return controller def buildFromTopo( self, *args, **kwargs ): "Start network" info( '*** Placing nodes\n' ) self.placeNodes() info( '\n' ) Mininet.buildFromTopo( self, *args, **kwargs ) def testNsTunnels(): "Test tunnels between nodes in namespaces" net = Mininet( host=RemoteHost, link=RemoteLink ) h1 = net.addHost( 'h1' ) h2 = net.addHost( 'h2', server='ubuntu2' ) net.addLink( h1, h2 ) net.start() net.pingAll() net.stop() # Manual topology creation with net.add*() # # This shows how node options may be used to manage # cluster placement using the net.add*() API def testRemoteNet( remote='ubuntu2' ): "Test remote Node classes" print '*** Remote Node Test' net = Mininet( host=RemoteHost, switch=RemoteOVSSwitch, link=RemoteLink ) c0 = net.addController( 'c0' ) # Make sure controller knows its non-loopback address Intf( 'eth0', node=c0 ).updateIP() print "*** Creating local h1" h1 = net.addHost( 'h1' ) print "*** Creating remote h2" h2 = net.addHost( 'h2', server=remote ) print "*** Creating local s1" s1 = net.addSwitch( 's1' ) print "*** Creating remote s2" s2 = net.addSwitch( 's2', server=remote ) print "*** Adding links" net.addLink( h1, s1 ) net.addLink( s1, s2 ) net.addLink( h2, s2 ) net.start() print 'Mininet is running on', quietRun( 'hostname' ).strip() for node in c0, h1, h2, s1, s2: print 'Node', node, 'is running on', node.cmd( 'hostname' ).strip() net.pingAll() CLI( net ) net.stop() # High-level/Topo API example # # This shows how existing Mininet topologies may be used in cluster # mode by creating node placement functions and a controller which # can be accessed remotely. This implements a very compatible version # of cluster edition with a minimum of code! remoteHosts = [ 'h2' ] remoteSwitches = [ 's2' ] remoteServer = 'ubuntu2' def HostPlacer( name, *args, **params ): "Custom Host() constructor which places hosts on servers" if name in remoteHosts: return RemoteHost( name, *args, server=remoteServer, **params ) else: return Host( name, *args, **params ) def SwitchPlacer( name, *args, **params ): "Custom Switch() constructor which places switches on servers" if name in remoteSwitches: return RemoteOVSSwitch( name, *args, server=remoteServer, **params ) else: return RemoteOVSSwitch( name, *args, **params ) def ClusterController( *args, **kwargs): "Custom Controller() constructor which updates its eth0 IP address" controller = Controller( *args, **kwargs ) # Find out its IP address so that cluster switches can connect Intf( 'eth0', node=controller ).updateIP() return controller def testRemoteTopo(): "Test remote Node classes using Mininet()/Topo() API" topo = LinearTopo( 2 ) net = Mininet( topo=topo, host=HostPlacer, switch=SwitchPlacer, link=RemoteLink, controller=ClusterController ) net.start() net.pingAll() net.stop() # Need to test backwards placement, where each host is on # a server other than its switch!! But seriously we could just # do random switch placement rather than completely random # host placement. def testRemoteSwitches(): "Test with local hosts and remote switches" servers = [ 'localhost', 'ubuntu2'] topo = TreeTopo( depth=4, fanout=2 ) net = MininetCluster( topo=topo, servers=servers, placement=RoundRobinPlacer ) net.start() net.pingAll() net.stop() # # For testing and demo purposes it would be nice to draw the # network graph and color it based on server. # The MininetCluster() class integrates pluggable placement # functions, for maximum ease of use. MininetCluster() also # pre-flights and multiplexes server connections. def testMininetCluster(): "Test MininetCluster()" servers = [ 'localhost', 'ubuntu2' ] topo = TreeTopo( depth=3, fanout=3 ) net = MininetCluster( topo=topo, servers=servers, placement=SwitchBinPlacer ) net.start() net.pingAll() net.stop() def signalTest(): "Make sure hosts are robust to signals" h = RemoteHost( 'h0', server='ubuntu1' ) h.shell.send_signal( SIGINT ) h.shell.poll() if h.shell.returncode is None: print 'OK: ', h, 'has not exited' else: print 'FAILURE:', h, 'exited with code', h.shell.returncode h.stop() if __name__ == '__main__': setLogLevel( 'info' ) # testRemoteTopo() # testRemoteNet() # testMininetCluster() # testRemoteSwitches() signalTest() mininet-2.2.2/examples/clusterSanity.py000077500000000000000000000007651306431124000202260ustar00rootroot00000000000000#!/usr/bin/env python ''' A sanity check for cluster edition ''' from mininet.examples.cluster import MininetCluster from mininet.log import setLogLevel from mininet.examples.clustercli import ClusterCLI as CLI from mininet.topo import SingleSwitchTopo def clusterSanity(): "Sanity check for cluster mode" topo = SingleSwitchTopo() net = MininetCluster( topo=topo ) net.start() CLI( net ) net.stop() if __name__ == '__main__': setLogLevel( 'info' ) clusterSanity() mininet-2.2.2/examples/clustercli.py000066400000000000000000000074431306431124000175230ustar00rootroot00000000000000#!/usr/bin/python "CLI for Mininet Cluster Edition prototype demo" from mininet.cli import CLI from mininet.log import output, error # pylint: disable=global-statement nx, graphviz_layout, plt = None, None, None # Will be imported on demand class ClusterCLI( CLI ): "CLI with additional commands for Cluster Edition demo" @staticmethod def colorsFor( seq ): "Return a list of background colors for a sequence" colors = [ 'red', 'lightgreen', 'cyan', 'yellow', 'orange', 'magenta', 'pink', 'grey', 'brown', 'white' ] slen, clen = len( seq ), len( colors ) reps = max( 1, slen / clen ) colors = colors * reps colors = colors[ 0 : slen ] return colors def do_plot( self, _line ): "Plot topology colored by node placement" # Import networkx if needed global nx, plt if not nx: try: # pylint: disable=import-error import networkx nx = networkx # satisfy pylint from matplotlib import pyplot plt = pyplot # satisfiy pylint import pygraphviz assert pygraphviz # silence pyflakes # pylint: enable=import-error except ImportError: error( 'plot requires networkx, matplotlib and pygraphviz - ' 'please install them and try again\n' ) return # Make a networkx Graph g = nx.Graph() mn = self.mn servers, hosts, switches = mn.servers, mn.hosts, mn.switches nodes = hosts + switches g.add_nodes_from( nodes ) links = [ ( link.intf1.node, link.intf2.node ) for link in self.mn.links ] g.add_edges_from( links ) # Pick some shapes and colors # shapes = hlen * [ 's' ] + slen * [ 'o' ] color = dict( zip( servers, self.colorsFor( servers ) ) ) # Plot it! pos = nx.graphviz_layout( g ) opts = { 'ax': None, 'font_weight': 'bold', 'width': 2, 'edge_color': 'darkblue' } hcolors = [ color[ getattr( h, 'server', 'localhost' ) ] for h in hosts ] scolors = [ color[ getattr( s, 'server', 'localhost' ) ] for s in switches ] nx.draw_networkx( g, pos=pos, nodelist=hosts, node_size=800, label='host', node_color=hcolors, node_shape='s', **opts ) nx.draw_networkx( g, pos=pos, nodelist=switches, node_size=1000, node_color=scolors, node_shape='o', **opts ) # Get rid of axes, add title, and show fig = plt.gcf() ax = plt.gca() ax.get_xaxis().set_visible( False ) ax.get_yaxis().set_visible( False ) fig.canvas.set_window_title( 'Mininet') plt.title( 'Node Placement', fontweight='bold' ) plt.show() def do_status( self, _line ): "Report on node shell status" nodes = self.mn.hosts + self.mn.switches for node in nodes: node.shell.poll() exited = [ node for node in nodes if node.shell.returncode is not None ] if exited: for node in exited: output( '%s has exited with code %d\n' % ( node, node.shell.returncode ) ) else: output( 'All nodes are still running.\n' ) def do_placement( self, _line ): "Describe node placement" mn = self.mn nodes = mn.hosts + mn.switches + mn.controllers for server in mn.servers: names = [ n.name for n in nodes if hasattr( n, 'server' ) and n.server == server ] output( '%s: %s\n' % ( server, ' '.join( names ) ) ) mininet-2.2.2/examples/clusterdemo.py000077500000000000000000000011771306431124000177010ustar00rootroot00000000000000#!/usr/bin/python "clusterdemo.py: demo of Mininet Cluster Edition prototype" from mininet.examples.cluster import MininetCluster, SwitchBinPlacer from mininet.topolib import TreeTopo from mininet.log import setLogLevel from mininet.examples.clustercli import ClusterCLI as CLI def demo(): "Simple Demo of Cluster Mode" servers = [ 'localhost', 'ubuntu2', 'ubuntu3' ] topo = TreeTopo( depth=3, fanout=3 ) net = MininetCluster( topo=topo, servers=servers, placement=SwitchBinPlacer ) net.start() CLI( net ) net.stop() if __name__ == '__main__': setLogLevel( 'info' ) demo() mininet-2.2.2/examples/consoles.py000077500000000000000000000363741306431124000172070ustar00rootroot00000000000000#!/usr/bin/python """ consoles.py: bring up a bunch of miniature consoles on a virtual network This demo shows how to monitor a set of nodes by using Node's monitor() and Tkinter's createfilehandler(). We monitor nodes in a couple of ways: - First, each individual node is monitored, and its output is added to its console window - Second, each time a console window gets iperf output, it is parsed and accumulated. Once we have output for all consoles, a bar is added to the bandwidth graph. The consoles also support limited interaction: - Pressing "return" in a console will send a command to it - Pressing the console's title button will open up an xterm Bob Lantz, April 2010 """ import re from Tkinter import Frame, Button, Label, Text, Scrollbar, Canvas, Wm, READABLE from mininet.log import setLogLevel from mininet.topolib import TreeNet from mininet.term import makeTerms, cleanUpScreens from mininet.util import quietRun class Console( Frame ): "A simple console on a host." def __init__( self, parent, net, node, height=10, width=32, title='Node' ): Frame.__init__( self, parent ) self.net = net self.node = node self.prompt = node.name + '# ' self.height, self.width, self.title = height, width, title # Initialize widget styles self.buttonStyle = { 'font': 'Monaco 7' } self.textStyle = { 'font': 'Monaco 7', 'bg': 'black', 'fg': 'green', 'width': self.width, 'height': self.height, 'relief': 'sunken', 'insertbackground': 'green', 'highlightcolor': 'green', 'selectforeground': 'black', 'selectbackground': 'green' } # Set up widgets self.text = self.makeWidgets( ) self.bindEvents() self.sendCmd( 'export TERM=dumb' ) self.outputHook = None def makeWidgets( self ): "Make a label, a text area, and a scroll bar." def newTerm( net=self.net, node=self.node, title=self.title ): "Pop up a new terminal window for a node." net.terms += makeTerms( [ node ], title ) label = Button( self, text=self.node.name, command=newTerm, **self.buttonStyle ) label.pack( side='top', fill='x' ) text = Text( self, wrap='word', **self.textStyle ) ybar = Scrollbar( self, orient='vertical', width=7, command=text.yview ) text.configure( yscrollcommand=ybar.set ) text.pack( side='left', expand=True, fill='both' ) ybar.pack( side='right', fill='y' ) return text def bindEvents( self ): "Bind keyboard and file events." # The text widget handles regular key presses, but we # use special handlers for the following: self.text.bind( '', self.handleReturn ) self.text.bind( '', self.handleInt ) self.text.bind( '', self.handleKey ) # This is not well-documented, but it is the correct # way to trigger a file event handler from Tk's # event loop! self.tk.createfilehandler( self.node.stdout, READABLE, self.handleReadable ) # We're not a terminal (yet?), so we ignore the following # control characters other than [\b\n\r] ignoreChars = re.compile( r'[\x00-\x07\x09\x0b\x0c\x0e-\x1f]+' ) def append( self, text ): "Append something to our text frame." text = self.ignoreChars.sub( '', text ) self.text.insert( 'end', text ) self.text.mark_set( 'insert', 'end' ) self.text.see( 'insert' ) outputHook = lambda x, y: True # make pylint happier if self.outputHook: outputHook = self.outputHook outputHook( self, text ) def handleKey( self, event ): "If it's an interactive command, send it to the node." char = event.char if self.node.waiting: self.node.write( char ) def handleReturn( self, event ): "Handle a carriage return." cmd = self.text.get( 'insert linestart', 'insert lineend' ) # Send it immediately, if "interactive" command if self.node.waiting: self.node.write( event.char ) return # Otherwise send the whole line to the shell pos = cmd.find( self.prompt ) if pos >= 0: cmd = cmd[ pos + len( self.prompt ): ] self.sendCmd( cmd ) # Callback ignores event def handleInt( self, _event=None ): "Handle control-c." self.node.sendInt() def sendCmd( self, cmd ): "Send a command to our node." if not self.node.waiting: self.node.sendCmd( cmd ) def handleReadable( self, _fds, timeoutms=None ): "Handle file readable event." data = self.node.monitor( timeoutms ) self.append( data ) if not self.node.waiting: # Print prompt self.append( self.prompt ) def waiting( self ): "Are we waiting for output?" return self.node.waiting def waitOutput( self ): "Wait for any remaining output." while self.node.waiting: # A bit of a trade-off here... self.handleReadable( self, timeoutms=1000) self.update() def clear( self ): "Clear all of our text." self.text.delete( '1.0', 'end' ) class Graph( Frame ): "Graph that we can add bars to over time." def __init__( self, parent=None, bg = 'white', gheight=200, gwidth=500, barwidth=10, ymax=3.5,): Frame.__init__( self, parent ) self.bg = bg self.gheight = gheight self.gwidth = gwidth self.barwidth = barwidth self.ymax = float( ymax ) self.xpos = 0 # Create everything self.title, self.scale, self.graph = self.createWidgets() self.updateScrollRegions() self.yview( 'moveto', '1.0' ) def createScale( self ): "Create a and return a new canvas with scale markers." height = float( self.gheight ) width = 25 ymax = self.ymax scale = Canvas( self, width=width, height=height, background=self.bg ) opts = { 'fill': 'red' } # Draw scale line scale.create_line( width - 1, height, width - 1, 0, **opts ) # Draw ticks and numbers for y in range( 0, int( ymax + 1 ) ): ypos = height * (1 - float( y ) / ymax ) scale.create_line( width, ypos, width - 10, ypos, **opts ) scale.create_text( 10, ypos, text=str( y ), **opts ) return scale def updateScrollRegions( self ): "Update graph and scale scroll regions." ofs = 20 height = self.gheight + ofs self.graph.configure( scrollregion=( 0, -ofs, self.xpos * self.barwidth, height ) ) self.scale.configure( scrollregion=( 0, -ofs, 0, height ) ) def yview( self, *args ): "Scroll both scale and graph." self.graph.yview( *args ) self.scale.yview( *args ) def createWidgets( self ): "Create initial widget set." # Objects title = Label( self, text='Bandwidth (Gb/s)', bg=self.bg ) width = self.gwidth height = self.gheight scale = self.createScale() graph = Canvas( self, width=width, height=height, background=self.bg) xbar = Scrollbar( self, orient='horizontal', command=graph.xview ) ybar = Scrollbar( self, orient='vertical', command=self.yview ) graph.configure( xscrollcommand=xbar.set, yscrollcommand=ybar.set, scrollregion=(0, 0, width, height ) ) scale.configure( yscrollcommand=ybar.set ) # Layout title.grid( row=0, columnspan=3, sticky='new') scale.grid( row=1, column=0, sticky='nsew' ) graph.grid( row=1, column=1, sticky='nsew' ) ybar.grid( row=1, column=2, sticky='ns' ) xbar.grid( row=2, column=0, columnspan=2, sticky='ew' ) self.rowconfigure( 1, weight=1 ) self.columnconfigure( 1, weight=1 ) return title, scale, graph def addBar( self, yval ): "Add a new bar to our graph." percent = yval / self.ymax c = self.graph x0 = self.xpos * self.barwidth x1 = x0 + self.barwidth y0 = self.gheight y1 = ( 1 - percent ) * self.gheight c.create_rectangle( x0, y0, x1, y1, fill='green' ) self.xpos += 1 self.updateScrollRegions() self.graph.xview( 'moveto', '1.0' ) def clear( self ): "Clear graph contents." self.graph.delete( 'all' ) self.xpos = 0 def test( self ): "Add a bar for testing purposes." ms = 1000 if self.xpos < 10: self.addBar( self.xpos / 10 * self.ymax ) self.after( ms, self.test ) def setTitle( self, text ): "Set graph title" self.title.configure( text=text, font='Helvetica 9 bold' ) class ConsoleApp( Frame ): "Simple Tk consoles for Mininet." menuStyle = { 'font': 'Geneva 7 bold' } def __init__( self, net, parent=None, width=4 ): Frame.__init__( self, parent ) self.top = self.winfo_toplevel() self.top.title( 'Mininet' ) self.net = net self.menubar = self.createMenuBar() cframe = self.cframe = Frame( self ) self.consoles = {} # consoles themselves titles = { 'hosts': 'Host', 'switches': 'Switch', 'controllers': 'Controller' } for name in titles: nodes = getattr( net, name ) frame, consoles = self.createConsoles( cframe, nodes, width, titles[ name ] ) self.consoles[ name ] = Object( frame=frame, consoles=consoles ) self.selected = None self.select( 'hosts' ) self.cframe.pack( expand=True, fill='both' ) cleanUpScreens() # Close window gracefully Wm.wm_protocol( self.top, name='WM_DELETE_WINDOW', func=self.quit ) # Initialize graph graph = Graph( cframe ) self.consoles[ 'graph' ] = Object( frame=graph, consoles=[ graph ] ) self.graph = graph self.graphVisible = False self.updates = 0 self.hostCount = len( self.consoles[ 'hosts' ].consoles ) self.bw = 0 self.pack( expand=True, fill='both' ) def updateGraph( self, _console, output ): "Update our graph." m = re.search( r'(\d+.?\d*) ([KMG]?bits)/sec', output ) if not m: return val, units = float( m.group( 1 ) ), m.group( 2 ) #convert to Gbps if units[0] == 'M': val *= 10 ** -3 elif units[0] == 'K': val *= 10 ** -6 elif units[0] == 'b': val *= 10 ** -9 self.updates += 1 self.bw += val if self.updates >= self.hostCount: self.graph.addBar( self.bw ) self.bw = 0 self.updates = 0 def setOutputHook( self, fn=None, consoles=None ): "Register fn as output hook [on specific consoles.]" if consoles is None: consoles = self.consoles[ 'hosts' ].consoles for console in consoles: console.outputHook = fn def createConsoles( self, parent, nodes, width, title ): "Create a grid of consoles in a frame." f = Frame( parent ) # Create consoles consoles = [] index = 0 for node in nodes: console = Console( f, self.net, node, title=title ) consoles.append( console ) row = index / width column = index % width console.grid( row=row, column=column, sticky='nsew' ) index += 1 f.rowconfigure( row, weight=1 ) f.columnconfigure( column, weight=1 ) return f, consoles def select( self, groupName ): "Select a group of consoles to display." if self.selected is not None: self.selected.frame.pack_forget() self.selected = self.consoles[ groupName ] self.selected.frame.pack( expand=True, fill='both' ) def createMenuBar( self ): "Create and return a menu (really button) bar." f = Frame( self ) buttons = [ ( 'Hosts', lambda: self.select( 'hosts' ) ), ( 'Switches', lambda: self.select( 'switches' ) ), ( 'Controllers', lambda: self.select( 'controllers' ) ), ( 'Graph', lambda: self.select( 'graph' ) ), ( 'Ping', self.ping ), ( 'Iperf', self.iperf ), ( 'Interrupt', self.stop ), ( 'Clear', self.clear ), ( 'Quit', self.quit ) ] for name, cmd in buttons: b = Button( f, text=name, command=cmd, **self.menuStyle ) b.pack( side='left' ) f.pack( padx=4, pady=4, fill='x' ) return f def clear( self ): "Clear selection." for console in self.selected.consoles: console.clear() def waiting( self, consoles=None ): "Are any of our hosts waiting for output?" if consoles is None: consoles = self.consoles[ 'hosts' ].consoles for console in consoles: if console.waiting(): return True return False def ping( self ): "Tell each host to ping the next one." consoles = self.consoles[ 'hosts' ].consoles if self.waiting( consoles ): return count = len( consoles ) i = 0 for console in consoles: i = ( i + 1 ) % count ip = consoles[ i ].node.IP() console.sendCmd( 'ping ' + ip ) def iperf( self ): "Tell each host to iperf to the next one." consoles = self.consoles[ 'hosts' ].consoles if self.waiting( consoles ): return count = len( consoles ) self.setOutputHook( self.updateGraph ) for console in consoles: # Sometimes iperf -sD doesn't return, # so we run it in the background instead console.node.cmd( 'iperf -s &' ) i = 0 for console in consoles: i = ( i + 1 ) % count ip = consoles[ i ].node.IP() console.sendCmd( 'iperf -t 99999 -i 1 -c ' + ip ) def stop( self, wait=True ): "Interrupt all hosts." consoles = self.consoles[ 'hosts' ].consoles for console in consoles: console.handleInt() if wait: for console in consoles: console.waitOutput() self.setOutputHook( None ) # Shut down any iperfs that might still be running quietRun( 'killall -9 iperf' ) def quit( self ): "Stop everything and quit." self.stop( wait=False) Frame.quit( self ) # Make it easier to construct and assign objects def assign( obj, **kwargs ): "Set a bunch of fields in an object." obj.__dict__.update( kwargs ) class Object( object ): "Generic object you can stuff junk into." def __init__( self, **kwargs ): assign( self, **kwargs ) if __name__ == '__main__': setLogLevel( 'info' ) network = TreeNet( depth=2, fanout=4 ) network.start() app = ConsoleApp( network, width=4 ) app.mainloop() network.stop() mininet-2.2.2/examples/controllers.py000077500000000000000000000020451306431124000177140ustar00rootroot00000000000000#!/usr/bin/python """ Create a network where different switches are connected to different controllers, by creating a custom Switch() subclass. """ from mininet.net import Mininet from mininet.node import OVSSwitch, Controller, RemoteController from mininet.topolib import TreeTopo from mininet.log import setLogLevel from mininet.cli import CLI setLogLevel( 'info' ) # Two local and one "external" controller (which is actually c0) # Ignore the warning message that the remote isn't (yet) running c0 = Controller( 'c0', port=6633 ) c1 = Controller( 'c1', port=6634 ) c2 = RemoteController( 'c2', ip='127.0.0.1', port=6633 ) cmap = { 's1': c0, 's2': c1, 's3': c2 } class MultiSwitch( OVSSwitch ): "Custom Switch() subclass that connects to different controllers" def start( self, controllers ): return OVSSwitch.start( self, [ cmap[ self.name ] ] ) topo = TreeTopo( depth=2, fanout=2 ) net = Mininet( topo=topo, switch=MultiSwitch, build=False ) for c in [ c0, c1 ]: net.addController(c) net.build() net.start() CLI( net ) net.stop() mininet-2.2.2/examples/controllers2.py000077500000000000000000000031141306431124000177740ustar00rootroot00000000000000#!/usr/bin/python """ This example creates a multi-controller network from semi-scratch by using the net.add*() API and manually starting the switches and controllers. This is the "mid-level" API, which is an alternative to the "high-level" Topo() API which supports parametrized topology classes. Note that one could also create a custom switch class and pass it into the Mininet() constructor. """ from mininet.net import Mininet from mininet.node import Controller, OVSSwitch from mininet.cli import CLI from mininet.log import setLogLevel def multiControllerNet(): "Create a network from semi-scratch with multiple controllers." net = Mininet( controller=Controller, switch=OVSSwitch ) print "*** Creating (reference) controllers" c1 = net.addController( 'c1', port=6633 ) c2 = net.addController( 'c2', port=6634 ) print "*** Creating switches" s1 = net.addSwitch( 's1' ) s2 = net.addSwitch( 's2' ) print "*** Creating hosts" hosts1 = [ net.addHost( 'h%d' % n ) for n in 3, 4 ] hosts2 = [ net.addHost( 'h%d' % n ) for n in 5, 6 ] print "*** Creating links" for h in hosts1: net.addLink( s1, h ) for h in hosts2: net.addLink( s2, h ) net.addLink( s1, s2 ) print "*** Starting network" net.build() c1.start() c2.start() s1.start( [ c1 ] ) s2.start( [ c2 ] ) print "*** Testing network" net.pingAll() print "*** Running CLI" CLI( net ) print "*** Stopping network" net.stop() if __name__ == '__main__': setLogLevel( 'info' ) # for CLI output multiControllerNet() mininet-2.2.2/examples/controlnet.py000077500000000000000000000115471306431124000175440ustar00rootroot00000000000000#!/usr/bin/python """ controlnet.py: Mininet with a custom control network We create two Mininet() networks, a control network and a data network, running four DataControllers on the control network to control the data network. Since we're using UserSwitch on the data network, it should correctly fail over to a backup controller. We also use a Mininet Facade to talk to both the control and data networks from a single CLI. """ from functools import partial from mininet.net import Mininet from mininet.node import Controller, UserSwitch from mininet.cli import CLI from mininet.topo import Topo from mininet.topolib import TreeTopo from mininet.log import setLogLevel, info # Some minor hacks class DataController( Controller ): """Data Network Controller. patched to avoid checkListening error and to delete intfs""" def checkListening( self ): "Ignore spurious error" pass def stop( self, *args, **kwargs ): "Make sure intfs are deleted" kwargs.update( deleteIntfs=True ) super( DataController, self ).stop( *args, **kwargs ) class MininetFacade( object ): """Mininet object facade that allows a single CLI to talk to one or more networks""" def __init__( self, net, *args, **kwargs ): """Create MininetFacade object. net: Primary Mininet object args: unnamed networks passed as arguments kwargs: named networks passed as arguments""" self.net = net self.nets = [ net ] + list( args ) + kwargs.values() self.nameToNet = kwargs self.nameToNet['net'] = net def __getattr__( self, name ): "returns attribute from Primary Mininet object" return getattr( self.net, name ) def __getitem__( self, key ): "returns primary/named networks or node from any net" #search kwargs for net named key if key in self.nameToNet: return self.nameToNet[ key ] #search each net for node named key for net in self.nets: if key in net: return net[ key ] def __iter__( self ): "Iterate through all nodes in all Mininet objects" for net in self.nets: for node in net: yield node def __len__( self ): "returns aggregate number of nodes in all nets" count = 0 for net in self.nets: count += len(net) return count def __contains__( self, key ): "returns True if node is a member of any net" return key in self.keys() def keys( self ): "returns a list of all node names in all networks" return list( self ) def values( self ): "returns a list of all nodes in all networks" return [ self[ key ] for key in self ] def items( self ): "returns (key,value) tuple list for every node in all networks" return zip( self.keys(), self.values() ) # A real control network! class ControlNetwork( Topo ): "Control Network Topology" def __init__( self, n, dataController=DataController, **kwargs ): """n: number of data network controller nodes dataController: class for data network controllers""" Topo.__init__( self, **kwargs ) # Connect everything to a single switch cs0 = self.addSwitch( 'cs0' ) # Add hosts which will serve as data network controllers for i in range( 0, n ): c = self.addHost( 'c%s' % i, cls=dataController, inNamespace=True ) self.addLink( c, cs0 ) # Connect switch to root namespace so that data network # switches will be able to talk to us root = self.addHost( 'root', inNamespace=False ) self.addLink( root, cs0 ) # Make it Happen!! def run(): "Create control and data networks, and invoke the CLI" info( '* Creating Control Network\n' ) ctopo = ControlNetwork( n=4, dataController=DataController ) cnet = Mininet( topo=ctopo, ipBase='192.168.123.0/24', controller=None ) info( '* Adding Control Network Controller\n') cnet.addController( 'cc0', controller=Controller ) info( '* Starting Control Network\n') cnet.start() info( '* Creating Data Network\n' ) topo = TreeTopo( depth=2, fanout=2 ) # UserSwitch so we can easily test failover sw = partial( UserSwitch, opts='--inactivity-probe=1 --max-backoff=1' ) net = Mininet( topo=topo, switch=sw, controller=None ) info( '* Adding Controllers to Data Network\n' ) for host in cnet.hosts: if isinstance(host, Controller): net.addController( host ) info( '* Starting Data Network\n') net.start() mn = MininetFacade( net, cnet=cnet ) CLI( mn ) info( '* Stopping Data Network\n' ) net.stop() info( '* Stopping Control Network\n' ) cnet.stop() if __name__ == '__main__': setLogLevel( 'info' ) run() mininet-2.2.2/examples/cpu.py000077500000000000000000000072151306431124000161410ustar00rootroot00000000000000#!/usr/bin/python """ cpu.py: test iperf bandwidth for varying cpu limits Since we are limiting the hosts (only), we should expect the iperf processes to be affected, as well as any system processing which is billed to the hosts. We reserve >50% of cycles for system processing; we assume that this is enough for it not to affect results. Hosts are limited to 40% of total cycles, which we assume is enough to make them CPU bound. As CPU performance increases over time, we may have to reduce the overall CPU allocation so that the host processing is still CPU bound. This is perhaps an argument for specifying performance in a more system-independent manner. It would also be nice to have a better handle on limiting packet processing cycles. It's not entirely clear to me how those are billed to user or system processes if we are using OVS with a kernel datapath. With a user datapath, they are easier to account for, but overall performance is usually lower. Although the iperf client uses more CPU and should be CPU bound (?), we measure the received data at the server since the client transmit rate includes buffering. """ from mininet.net import Mininet from mininet.node import CPULimitedHost from mininet.topolib import TreeTopo from mininet.util import custom, waitListening from mininet.log import setLogLevel, info def bwtest( cpuLimits, period_us=100000, seconds=10 ): """Example/test of link and CPU bandwidth limits cpu: cpu limit as fraction of overall CPU time""" topo = TreeTopo( depth=1, fanout=2 ) results = {} for sched in 'rt', 'cfs': info( '*** Testing with', sched, 'bandwidth limiting\n' ) for cpu in cpuLimits: # cpu is the cpu fraction for all hosts, so we divide # it across two hosts host = custom( CPULimitedHost, sched=sched, period_us=period_us, cpu=.5*cpu ) try: net = Mininet( topo=topo, host=host ) # pylint: disable=bare-except except: info( '*** Skipping scheduler %s\n' % sched ) break net.start() net.pingAll() hosts = [ net.getNodeByName( h ) for h in topo.hosts() ] client, server = hosts[ 0 ], hosts[ -1 ] info( '*** Starting iperf with %d%% of CPU allocated to hosts\n' % ( 100.0 * cpu ) ) # We measure at the server because it doesn't include # the client's buffer fill rate popen = server.popen( 'iperf -yc -s -p 5001' ) waitListening( client, server, 5001 ) # ignore empty result from waitListening/telnet popen.stdout.readline() client.cmd( 'iperf -yc -t %s -c %s' % ( seconds, server.IP() ) ) result = popen.stdout.readline().split( ',' ) bps = float( result[ -1 ] ) popen.terminate() net.stop() updated = results.get( sched, [] ) updated += [ ( cpu, bps ) ] results[ sched ] = updated return results def dump( results ): "Dump results" fmt = '%s\t%s\t%s\n' info( '\n' ) info( fmt % ( 'sched', 'cpu', 'received bits/sec' ) ) for sched in sorted( results.keys() ): entries = results[ sched ] for cpu, bps in entries: pct = '%d%%' % ( cpu * 100 ) mbps = '%.2e' % bps info( fmt % ( sched, pct, mbps ) ) if __name__ == '__main__': setLogLevel( 'info' ) # These are the limits for the hosts/iperfs - the # rest is for system processes limits = [ .5, .4, .3, .2, .1 ] out = bwtest( limits ) dump( out ) mininet-2.2.2/examples/emptynet.py000077500000000000000000000017001306431124000172100ustar00rootroot00000000000000#!/usr/bin/python """ This example shows how to create an empty Mininet object (without a topology object) and add nodes to it manually. """ from mininet.net import Mininet from mininet.node import Controller from mininet.cli import CLI from mininet.log import setLogLevel, info def emptyNet(): "Create an empty network and add nodes to it." net = Mininet( controller=Controller ) info( '*** Adding controller\n' ) net.addController( 'c0' ) info( '*** Adding hosts\n' ) h1 = net.addHost( 'h1', ip='10.0.0.1' ) h2 = net.addHost( 'h2', ip='10.0.0.2' ) info( '*** Adding switch\n' ) s3 = net.addSwitch( 's3' ) info( '*** Creating links\n' ) net.addLink( h1, s3 ) net.addLink( h2, s3 ) info( '*** Starting network\n') net.start() info( '*** Running CLI\n' ) CLI( net ) info( '*** Stopping network' ) net.stop() if __name__ == '__main__': setLogLevel( 'info' ) emptyNet() mininet-2.2.2/examples/hwintf.py000077500000000000000000000030151306431124000166430ustar00rootroot00000000000000#!/usr/bin/python """ This example shows how to add an interface (for example a real hardware interface) to a network after the network is created. """ import re import sys from mininet.cli import CLI from mininet.log import setLogLevel, info, error from mininet.net import Mininet from mininet.link import Intf from mininet.topolib import TreeTopo from mininet.util import quietRun def checkIntf( intf ): "Make sure intf exists and is not configured." config = quietRun( 'ifconfig %s 2>/dev/null' % intf, shell=True ) if not config: error( 'Error:', intf, 'does not exist!\n' ) exit( 1 ) ips = re.findall( r'\d+\.\d+\.\d+\.\d+', config ) if ips: error( 'Error:', intf, 'has an IP address,' 'and is probably in use!\n' ) exit( 1 ) if __name__ == '__main__': setLogLevel( 'info' ) # try to get hw intf from the command line; by default, use eth1 intfName = sys.argv[ 1 ] if len( sys.argv ) > 1 else 'eth1' info( '*** Connecting to hw intf: %s' % intfName ) info( '*** Checking', intfName, '\n' ) checkIntf( intfName ) info( '*** Creating network\n' ) net = Mininet( topo=TreeTopo( depth=1, fanout=2 ) ) switch = net.switches[ 0 ] info( '*** Adding hardware interface', intfName, 'to switch', switch.name, '\n' ) _intf = Intf( intfName, node=switch ) info( '*** Note: you may need to reconfigure the interfaces for ' 'the Mininet hosts:\n', net.hosts, '\n' ) net.start() CLI( net ) net.stop() mininet-2.2.2/examples/intfoptions.py000077500000000000000000000024501306431124000177220ustar00rootroot00000000000000#!/usr/bin/python ''' example of using various TCIntf options. reconfigures a single interface using intf.config() to use different traffic control commands to test bandwidth, loss, and delay ''' from mininet.net import Mininet from mininet.log import setLogLevel, info from mininet.link import TCLink def intfOptions(): "run various traffic control commands on a single interface" net = Mininet( autoStaticArp=True ) net.addController( 'c0' ) h1 = net.addHost( 'h1' ) h2 = net.addHost( 'h2' ) s1 = net.addSwitch( 's1' ) link1 = net.addLink( h1, s1, cls=TCLink ) net.addLink( h2, s1 ) net.start() # flush out latency from reactive forwarding delay net.pingAll() info( '\n*** Configuring one intf with bandwidth of 5 Mb\n' ) link1.intf1.config( bw=5 ) info( '\n*** Running iperf to test\n' ) net.iperf() info( '\n*** Configuring one intf with loss of 50%\n' ) link1.intf1.config( loss=50 ) info( '\n' ) net.iperf( ( h1, h2 ), l4Type='UDP' ) info( '\n*** Configuring one intf with delay of 15ms\n' ) link1.intf1.config( delay='15ms' ) info( '\n*** Run a ping to confirm delay\n' ) net.pingPairFull() info( '\n*** Done testing\n' ) net.stop() if __name__ == '__main__': setLogLevel( 'info' ) intfOptions() mininet-2.2.2/examples/limit.py000077500000000000000000000037621306431124000164730ustar00rootroot00000000000000#!/usr/bin/python """ limit.py: example of using link and CPU limits """ from mininet.net import Mininet from mininet.link import TCIntf from mininet.node import CPULimitedHost from mininet.topolib import TreeTopo from mininet.util import custom, quietRun from mininet.log import setLogLevel, info def testLinkLimit( net, bw ): "Run bandwidth limit test" info( '*** Testing network %.2f Mbps bandwidth limit\n' % bw ) net.iperf() def limit( bw=10, cpu=.1 ): """Example/test of link and CPU bandwidth limits bw: interface bandwidth limit in Mbps cpu: cpu limit as fraction of overall CPU time""" intf = custom( TCIntf, bw=bw ) myTopo = TreeTopo( depth=1, fanout=2 ) for sched in 'rt', 'cfs': info( '*** Testing with', sched, 'bandwidth limiting\n' ) if sched == 'rt': release = quietRun( 'uname -r' ).strip('\r\n') output = quietRun( 'grep CONFIG_RT_GROUP_SCHED /boot/config-%s' % release ) if output == '# CONFIG_RT_GROUP_SCHED is not set\n': info( '*** RT Scheduler is not enabled in your kernel. ' 'Skipping this test\n' ) continue host = custom( CPULimitedHost, sched=sched, cpu=cpu ) net = Mininet( topo=myTopo, intf=intf, host=host ) net.start() testLinkLimit( net, bw=bw ) net.runCpuLimitTest( cpu=cpu ) net.stop() def verySimpleLimit( bw=150 ): "Absurdly simple limiting test" intf = custom( TCIntf, bw=bw ) net = Mininet( intf=intf ) h1, h2 = net.addHost( 'h1' ), net.addHost( 'h2' ) net.addLink( h1, h2 ) net.start() net.pingAll() net.iperf() h1.cmdPrint( 'tc -s qdisc ls dev', h1.defaultIntf() ) h2.cmdPrint( 'tc -d class show dev', h2.defaultIntf() ) h1.cmdPrint( 'tc -s qdisc ls dev', h1.defaultIntf() ) h2.cmdPrint( 'tc -d class show dev', h2.defaultIntf() ) net.stop() if __name__ == '__main__': setLogLevel( 'info' ) limit() mininet-2.2.2/examples/linearbandwidth.py000077500000000000000000000077361306431124000205210ustar00rootroot00000000000000#!/usr/bin/python """ Test bandwidth (using iperf) on linear networks of varying size, using both kernel and user datapaths. We construct a network of N hosts and N-1 switches, connected as follows: h1 <-> s1 <-> s2 .. sN-1 | | | h2 h3 hN WARNING: by default, the reference controller only supports 16 switches, so this test WILL NOT WORK unless you have recompiled your controller to support 100 switches (or more.) In addition to testing the bandwidth across varying numbers of switches, this example demonstrates: - creating a custom topology, LinearTestTopo - using the ping() and iperf() tests from Mininet() - testing both the kernel and user switches """ from mininet.net import Mininet from mininet.node import UserSwitch, OVSKernelSwitch, Controller from mininet.topo import Topo from mininet.log import lg, info from mininet.util import irange, quietRun from mininet.link import TCLink from functools import partial import sys flush = sys.stdout.flush class LinearTestTopo( Topo ): "Topology for a string of N hosts and N-1 switches." def __init__( self, N, **params ): # Initialize topology Topo.__init__( self, **params ) # Create switches and hosts hosts = [ self.addHost( 'h%s' % h ) for h in irange( 1, N ) ] switches = [ self.addSwitch( 's%s' % s ) for s in irange( 1, N - 1 ) ] # Wire up switches last = None for switch in switches: if last: self.addLink( last, switch ) last = switch # Wire up hosts self.addLink( hosts[ 0 ], switches[ 0 ] ) for host, switch in zip( hosts[ 1: ], switches ): self.addLink( host, switch ) def linearBandwidthTest( lengths ): "Check bandwidth at various lengths along a switch chain." results = {} switchCount = max( lengths ) hostCount = switchCount + 1 switches = { 'reference user': UserSwitch, 'Open vSwitch kernel': OVSKernelSwitch } # UserSwitch is horribly slow with recent kernels. # We can reinstate it once its performance is fixed del switches[ 'reference user' ] topo = LinearTestTopo( hostCount ) # Select TCP Reno output = quietRun( 'sysctl -w net.ipv4.tcp_congestion_control=reno' ) assert 'reno' in output for datapath in switches.keys(): print "*** testing", datapath, "datapath" Switch = switches[ datapath ] results[ datapath ] = [] link = partial( TCLink, delay='2ms', bw=10 ) net = Mininet( topo=topo, switch=Switch, controller=Controller, waitConnected=True, link=link ) net.start() print "*** testing basic connectivity" for n in lengths: net.ping( [ net.hosts[ 0 ], net.hosts[ n ] ] ) print "*** testing bandwidth" for n in lengths: src, dst = net.hosts[ 0 ], net.hosts[ n ] # Try to prime the pump to reduce PACKET_INs during test # since the reference controller is reactive src.cmd( 'telnet', dst.IP(), '5001' ) info( "testing", src.name, "<->", dst.name, '\n' ) # serverbw = received; _clientbw = buffered serverbw, _clientbw = net.iperf( [ src, dst ], seconds=10 ) info( serverbw, '\n' ) flush() results[ datapath ] += [ ( n, serverbw ) ] net.stop() for datapath in switches.keys(): print print "*** Linear network results for", datapath, "datapath:" print result = results[ datapath ] info( "SwitchCount\tiperf Results\n" ) for switchCount, serverbw in result: info( switchCount, '\t\t' ) info( serverbw, '\n' ) info( '\n') info( '\n' ) if __name__ == '__main__': lg.setLogLevel( 'info' ) sizes = [ 1, 10, 20, 40, 60, 80 ] print "*** Running linearBandwidthTest", sizes linearBandwidthTest( sizes ) mininet-2.2.2/examples/linuxrouter.py000077500000000000000000000054121306431124000177470ustar00rootroot00000000000000#!/usr/bin/python """ linuxrouter.py: Example network with Linux IP router This example converts a Node into a router using IP forwarding already built into Linux. The example topology creates a router and three IP subnets: - 192.168.1.0/24 (r0-eth1, IP: 192.168.1.1) - 172.16.0.0/12 (r0-eth2, IP: 172.16.0.1) - 10.0.0.0/8 (r0-eth3, IP: 10.0.0.1) Each subnet consists of a single host connected to a single switch: r0-eth1 - s1-eth1 - h1-eth0 (IP: 192.168.1.100) r0-eth2 - s2-eth1 - h2-eth0 (IP: 172.16.0.100) r0-eth3 - s3-eth1 - h3-eth0 (IP: 10.0.0.100) The example relies on default routing entries that are automatically created for each router interface, as well as 'defaultRoute' parameters for the host interfaces. Additional routes may be added to the router or hosts by executing 'ip route' or 'route' commands on the router or hosts. """ from mininet.topo import Topo from mininet.net import Mininet from mininet.node import Node from mininet.log import setLogLevel, info from mininet.cli import CLI class LinuxRouter( Node ): "A Node with IP forwarding enabled." def config( self, **params ): super( LinuxRouter, self).config( **params ) # Enable forwarding on the router self.cmd( 'sysctl net.ipv4.ip_forward=1' ) def terminate( self ): self.cmd( 'sysctl net.ipv4.ip_forward=0' ) super( LinuxRouter, self ).terminate() class NetworkTopo( Topo ): "A LinuxRouter connecting three IP subnets" def build( self, **_opts ): defaultIP = '192.168.1.1/24' # IP address for r0-eth1 router = self.addNode( 'r0', cls=LinuxRouter, ip=defaultIP ) s1, s2, s3 = [ self.addSwitch( s ) for s in 's1', 's2', 's3' ] self.addLink( s1, router, intfName2='r0-eth1', params2={ 'ip' : defaultIP } ) # for clarity self.addLink( s2, router, intfName2='r0-eth2', params2={ 'ip' : '172.16.0.1/12' } ) self.addLink( s3, router, intfName2='r0-eth3', params2={ 'ip' : '10.0.0.1/8' } ) h1 = self.addHost( 'h1', ip='192.168.1.100/24', defaultRoute='via 192.168.1.1' ) h2 = self.addHost( 'h2', ip='172.16.0.100/12', defaultRoute='via 172.16.0.1' ) h3 = self.addHost( 'h3', ip='10.0.0.100/8', defaultRoute='via 10.0.0.1' ) for h, s in [ (h1, s1), (h2, s2), (h3, s3) ]: self.addLink( h, s ) def run(): "Test linux router" topo = NetworkTopo() net = Mininet( topo=topo ) # controller is used by s1-s3 net.start() info( '*** Routing Table on Router:\n' ) print net[ 'r0' ].cmd( 'route' ) CLI( net ) net.stop() if __name__ == '__main__': setLogLevel( 'info' ) run() mininet-2.2.2/examples/miniedit.py000077500000000000000000004555571306431124000171740ustar00rootroot00000000000000#!/usr/bin/python """ MiniEdit: a simple network editor for Mininet This is a simple demonstration of how one might build a GUI application using Mininet as the network model. Bob Lantz, April 2010 Gregory Gee, July 2013 Controller icon from http://semlabs.co.uk/ OpenFlow icon from https://www.opennetworking.org/ """ # Miniedit needs some work in order to pass pylint... # pylint: disable=line-too-long,too-many-branches # pylint: disable=too-many-statements,attribute-defined-outside-init # pylint: disable=missing-docstring MINIEDIT_VERSION = '2.2.0.1' from optparse import OptionParser # from Tkinter import * from Tkinter import ( Frame, Label, LabelFrame, Entry, OptionMenu, Checkbutton, Menu, Toplevel, Button, BitmapImage, PhotoImage, Canvas, Scrollbar, Wm, TclError, StringVar, IntVar, E, W, EW, NW, Y, VERTICAL, SOLID, CENTER, RIGHT, LEFT, BOTH, TRUE, FALSE ) from ttk import Notebook from tkMessageBox import showerror from subprocess import call import tkFont import tkFileDialog import tkSimpleDialog import re import json from distutils.version import StrictVersion import os import sys from functools import partial if 'PYTHONPATH' in os.environ: sys.path = os.environ[ 'PYTHONPATH' ].split( ':' ) + sys.path # someday: from ttk import * from mininet.log import info, setLogLevel from mininet.net import Mininet, VERSION from mininet.util import netParse, ipAdd, quietRun from mininet.util import buildTopo from mininet.util import custom, customClass from mininet.term import makeTerm, cleanUpScreens from mininet.node import Controller, RemoteController, NOX, OVSController from mininet.node import CPULimitedHost, Host, Node from mininet.node import OVSSwitch, UserSwitch from mininet.link import TCLink, Intf, Link from mininet.cli import CLI from mininet.moduledeps import moduleDeps from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo from mininet.topolib import TreeTopo print 'MiniEdit running against Mininet '+VERSION MININET_VERSION = re.sub(r'[^\d\.]', '', VERSION) if StrictVersion(MININET_VERSION) > StrictVersion('2.0'): from mininet.node import IVSSwitch TOPODEF = 'none' TOPOS = { 'minimal': lambda: SingleSwitchTopo( k=2 ), 'linear': LinearTopo, 'reversed': SingleSwitchReversedTopo, 'single': SingleSwitchTopo, 'none': None, 'tree': TreeTopo } CONTROLLERDEF = 'ref' CONTROLLERS = { 'ref': Controller, 'ovsc': OVSController, 'nox': NOX, 'remote': RemoteController, 'none': lambda name: None } LINKDEF = 'default' LINKS = { 'default': Link, 'tc': TCLink } HOSTDEF = 'proc' HOSTS = { 'proc': Host, 'rt': custom( CPULimitedHost, sched='rt' ), 'cfs': custom( CPULimitedHost, sched='cfs' ) } class InbandController( RemoteController ): "RemoteController that ignores checkListening" def checkListening( self ): "Overridden to do nothing." return class CustomUserSwitch(UserSwitch): "Customized UserSwitch" def __init__( self, name, dpopts='--no-slicing', **kwargs ): UserSwitch.__init__( self, name, **kwargs ) self.switchIP = None def getSwitchIP(self): "Return management IP address" return self.switchIP def setSwitchIP(self, ip): "Set management IP address" self.switchIP = ip def start( self, controllers ): "Start and set management IP address" # Call superclass constructor UserSwitch.start( self, controllers ) # Set Switch IP address if self.switchIP is not None: if not self.inNamespace: self.cmd( 'ifconfig', self, self.switchIP ) else: self.cmd( 'ifconfig lo', self.switchIP ) class LegacyRouter( Node ): "Simple IP router" def __init__( self, name, inNamespace=True, **params ): Node.__init__( self, name, inNamespace, **params ) def config( self, **_params ): if self.intfs: self.setParam( _params, 'setIP', ip='0.0.0.0' ) r = Node.config( self, **_params ) self.cmd('sysctl -w net.ipv4.ip_forward=1') return r class LegacySwitch(OVSSwitch): "OVS switch in standalone/bridge mode" def __init__( self, name, **params ): OVSSwitch.__init__( self, name, failMode='standalone', **params ) self.switchIP = None class customOvs(OVSSwitch): "Customized OVS switch" def __init__( self, name, failMode='secure', datapath='kernel', **params ): OVSSwitch.__init__( self, name, failMode=failMode, datapath=datapath,**params ) self.switchIP = None def getSwitchIP(self): "Return management IP address" return self.switchIP def setSwitchIP(self, ip): "Set management IP address" self.switchIP = ip def start( self, controllers ): "Start and set management IP address" # Call superclass constructor OVSSwitch.start( self, controllers ) # Set Switch IP address if self.switchIP is not None: self.cmd( 'ifconfig', self, self.switchIP ) class PrefsDialog(tkSimpleDialog.Dialog): "Preferences dialog" def __init__(self, parent, title, prefDefaults): self.prefValues = prefDefaults tkSimpleDialog.Dialog.__init__(self, parent, title) def body(self, master): "Create dialog body" self.rootFrame = master self.leftfieldFrame = Frame(self.rootFrame, padx=5, pady=5) self.leftfieldFrame.grid(row=0, column=0, sticky='nswe', columnspan=2) self.rightfieldFrame = Frame(self.rootFrame, padx=5, pady=5) self.rightfieldFrame.grid(row=0, column=2, sticky='nswe', columnspan=2) # Field for Base IP Label(self.leftfieldFrame, text="IP Base:").grid(row=0, sticky=E) self.ipEntry = Entry(self.leftfieldFrame) self.ipEntry.grid(row=0, column=1) ipBase = self.prefValues['ipBase'] self.ipEntry.insert(0, ipBase) # Selection of terminal type Label(self.leftfieldFrame, text="Default Terminal:").grid(row=1, sticky=E) self.terminalVar = StringVar(self.leftfieldFrame) self.terminalOption = OptionMenu(self.leftfieldFrame, self.terminalVar, "xterm", "gterm") self.terminalOption.grid(row=1, column=1, sticky=W) terminalType = self.prefValues['terminalType'] self.terminalVar.set(terminalType) # Field for CLI Label(self.leftfieldFrame, text="Start CLI:").grid(row=2, sticky=E) self.cliStart = IntVar() self.cliButton = Checkbutton(self.leftfieldFrame, variable=self.cliStart) self.cliButton.grid(row=2, column=1, sticky=W) if self.prefValues['startCLI'] == '0': self.cliButton.deselect() else: self.cliButton.select() # Selection of switch type Label(self.leftfieldFrame, text="Default Switch:").grid(row=3, sticky=E) self.switchType = StringVar(self.leftfieldFrame) self.switchTypeMenu = OptionMenu(self.leftfieldFrame, self.switchType, "Open vSwitch Kernel Mode", "Indigo Virtual Switch", "Userspace Switch", "Userspace Switch inNamespace") self.switchTypeMenu.grid(row=3, column=1, sticky=W) switchTypePref = self.prefValues['switchType'] if switchTypePref == 'ivs': self.switchType.set("Indigo Virtual Switch") elif switchTypePref == 'userns': self.switchType.set("Userspace Switch inNamespace") elif switchTypePref == 'user': self.switchType.set("Userspace Switch") else: self.switchType.set("Open vSwitch Kernel Mode") # Fields for OVS OpenFlow version ovsFrame= LabelFrame(self.leftfieldFrame, text='Open vSwitch', padx=5, pady=5) ovsFrame.grid(row=4, column=0, columnspan=2, sticky=EW) Label(ovsFrame, text="OpenFlow 1.0:").grid(row=0, sticky=E) Label(ovsFrame, text="OpenFlow 1.1:").grid(row=1, sticky=E) Label(ovsFrame, text="OpenFlow 1.2:").grid(row=2, sticky=E) Label(ovsFrame, text="OpenFlow 1.3:").grid(row=3, sticky=E) self.ovsOf10 = IntVar() self.covsOf10 = Checkbutton(ovsFrame, variable=self.ovsOf10) self.covsOf10.grid(row=0, column=1, sticky=W) if self.prefValues['openFlowVersions']['ovsOf10'] == '0': self.covsOf10.deselect() else: self.covsOf10.select() self.ovsOf11 = IntVar() self.covsOf11 = Checkbutton(ovsFrame, variable=self.ovsOf11) self.covsOf11.grid(row=1, column=1, sticky=W) if self.prefValues['openFlowVersions']['ovsOf11'] == '0': self.covsOf11.deselect() else: self.covsOf11.select() self.ovsOf12 = IntVar() self.covsOf12 = Checkbutton(ovsFrame, variable=self.ovsOf12) self.covsOf12.grid(row=2, column=1, sticky=W) if self.prefValues['openFlowVersions']['ovsOf12'] == '0': self.covsOf12.deselect() else: self.covsOf12.select() self.ovsOf13 = IntVar() self.covsOf13 = Checkbutton(ovsFrame, variable=self.ovsOf13) self.covsOf13.grid(row=3, column=1, sticky=W) if self.prefValues['openFlowVersions']['ovsOf13'] == '0': self.covsOf13.deselect() else: self.covsOf13.select() # Field for DPCTL listen port Label(self.leftfieldFrame, text="dpctl port:").grid(row=5, sticky=E) self.dpctlEntry = Entry(self.leftfieldFrame) self.dpctlEntry.grid(row=5, column=1) if 'dpctl' in self.prefValues: self.dpctlEntry.insert(0, self.prefValues['dpctl']) # sFlow sflowValues = self.prefValues['sflow'] self.sflowFrame= LabelFrame(self.rightfieldFrame, text='sFlow Profile for Open vSwitch', padx=5, pady=5) self.sflowFrame.grid(row=0, column=0, columnspan=2, sticky=EW) Label(self.sflowFrame, text="Target:").grid(row=0, sticky=E) self.sflowTarget = Entry(self.sflowFrame) self.sflowTarget.grid(row=0, column=1) self.sflowTarget.insert(0, sflowValues['sflowTarget']) Label(self.sflowFrame, text="Sampling:").grid(row=1, sticky=E) self.sflowSampling = Entry(self.sflowFrame) self.sflowSampling.grid(row=1, column=1) self.sflowSampling.insert(0, sflowValues['sflowSampling']) Label(self.sflowFrame, text="Header:").grid(row=2, sticky=E) self.sflowHeader = Entry(self.sflowFrame) self.sflowHeader.grid(row=2, column=1) self.sflowHeader.insert(0, sflowValues['sflowHeader']) Label(self.sflowFrame, text="Polling:").grid(row=3, sticky=E) self.sflowPolling = Entry(self.sflowFrame) self.sflowPolling.grid(row=3, column=1) self.sflowPolling.insert(0, sflowValues['sflowPolling']) # NetFlow nflowValues = self.prefValues['netflow'] self.nFrame= LabelFrame(self.rightfieldFrame, text='NetFlow Profile for Open vSwitch', padx=5, pady=5) self.nFrame.grid(row=1, column=0, columnspan=2, sticky=EW) Label(self.nFrame, text="Target:").grid(row=0, sticky=E) self.nflowTarget = Entry(self.nFrame) self.nflowTarget.grid(row=0, column=1) self.nflowTarget.insert(0, nflowValues['nflowTarget']) Label(self.nFrame, text="Active Timeout:").grid(row=1, sticky=E) self.nflowTimeout = Entry(self.nFrame) self.nflowTimeout.grid(row=1, column=1) self.nflowTimeout.insert(0, nflowValues['nflowTimeout']) Label(self.nFrame, text="Add ID to Interface:").grid(row=2, sticky=E) self.nflowAddId = IntVar() self.nflowAddIdButton = Checkbutton(self.nFrame, variable=self.nflowAddId) self.nflowAddIdButton.grid(row=2, column=1, sticky=W) if nflowValues['nflowAddId'] == '0': self.nflowAddIdButton.deselect() else: self.nflowAddIdButton.select() # initial focus return self.ipEntry def apply(self): ipBase = self.ipEntry.get() terminalType = self.terminalVar.get() startCLI = str(self.cliStart.get()) sw = self.switchType.get() dpctl = self.dpctlEntry.get() ovsOf10 = str(self.ovsOf10.get()) ovsOf11 = str(self.ovsOf11.get()) ovsOf12 = str(self.ovsOf12.get()) ovsOf13 = str(self.ovsOf13.get()) sflowValues = {'sflowTarget':self.sflowTarget.get(), 'sflowSampling':self.sflowSampling.get(), 'sflowHeader':self.sflowHeader.get(), 'sflowPolling':self.sflowPolling.get()} nflowvalues = {'nflowTarget':self.nflowTarget.get(), 'nflowTimeout':self.nflowTimeout.get(), 'nflowAddId':str(self.nflowAddId.get())} self.result = {'ipBase':ipBase, 'terminalType':terminalType, 'dpctl':dpctl, 'sflow':sflowValues, 'netflow':nflowvalues, 'startCLI':startCLI} if sw == 'Indigo Virtual Switch': self.result['switchType'] = 'ivs' if StrictVersion(MININET_VERSION) < StrictVersion('2.1'): self.ovsOk = False showerror(title="Error", message='MiniNet version 2.1+ required. You have '+VERSION+'.') elif sw == 'Userspace Switch': self.result['switchType'] = 'user' elif sw == 'Userspace Switch inNamespace': self.result['switchType'] = 'userns' else: self.result['switchType'] = 'ovs' self.ovsOk = True if ovsOf11 == "1": ovsVer = self.getOvsVersion() if StrictVersion(ovsVer) < StrictVersion('2.0'): self.ovsOk = False showerror(title="Error", message='Open vSwitch version 2.0+ required. You have '+ovsVer+'.') if ovsOf12 == "1" or ovsOf13 == "1": ovsVer = self.getOvsVersion() if StrictVersion(ovsVer) < StrictVersion('1.10'): self.ovsOk = False showerror(title="Error", message='Open vSwitch version 1.10+ required. You have '+ovsVer+'.') if self.ovsOk: self.result['openFlowVersions']={'ovsOf10':ovsOf10, 'ovsOf11':ovsOf11, 'ovsOf12':ovsOf12, 'ovsOf13':ovsOf13} else: self.result = None @staticmethod def getOvsVersion(): "Return OVS version" outp = quietRun("ovs-vsctl show") r = r'ovs_version: "(.*)"' m = re.search(r, outp) if m is None: print 'Version check failed' return None else: print 'Open vSwitch version is '+m.group(1) return m.group(1) class CustomDialog(object): # TODO: Fix button placement and Title and window focus lock def __init__(self, master, _title): self.top=Toplevel(master) self.bodyFrame = Frame(self.top) self.bodyFrame.grid(row=0, column=0, sticky='nswe') self.body(self.bodyFrame) #return self.b # initial focus buttonFrame = Frame(self.top, relief='ridge', bd=3, bg='lightgrey') buttonFrame.grid(row=1 , column=0, sticky='nswe') okButton = Button(buttonFrame, width=8, text='OK', relief='groove', bd=4, command=self.okAction) okButton.grid(row=0, column=0, sticky=E) canlceButton = Button(buttonFrame, width=8, text='Cancel', relief='groove', bd=4, command=self.cancelAction) canlceButton.grid(row=0, column=1, sticky=W) def body(self, master): self.rootFrame = master def apply(self): self.top.destroy() def cancelAction(self): self.top.destroy() def okAction(self): self.apply() self.top.destroy() class HostDialog(CustomDialog): def __init__(self, master, title, prefDefaults): self.prefValues = prefDefaults self.result = None CustomDialog.__init__(self, master, title) def body(self, master): self.rootFrame = master n = Notebook(self.rootFrame) self.propFrame = Frame(n) self.vlanFrame = Frame(n) self.interfaceFrame = Frame(n) self.mountFrame = Frame(n) n.add(self.propFrame, text='Properties') n.add(self.vlanFrame, text='VLAN Interfaces') n.add(self.interfaceFrame, text='External Interfaces') n.add(self.mountFrame, text='Private Directories') n.pack() ### TAB 1 # Field for Hostname Label(self.propFrame, text="Hostname:").grid(row=0, sticky=E) self.hostnameEntry = Entry(self.propFrame) self.hostnameEntry.grid(row=0, column=1) if 'hostname' in self.prefValues: self.hostnameEntry.insert(0, self.prefValues['hostname']) # Field for Switch IP Label(self.propFrame, text="IP Address:").grid(row=1, sticky=E) self.ipEntry = Entry(self.propFrame) self.ipEntry.grid(row=1, column=1) if 'ip' in self.prefValues: self.ipEntry.insert(0, self.prefValues['ip']) # Field for default route Label(self.propFrame, text="Default Route:").grid(row=2, sticky=E) self.routeEntry = Entry(self.propFrame) self.routeEntry.grid(row=2, column=1) if 'defaultRoute' in self.prefValues: self.routeEntry.insert(0, self.prefValues['defaultRoute']) # Field for CPU Label(self.propFrame, text="Amount CPU:").grid(row=3, sticky=E) self.cpuEntry = Entry(self.propFrame) self.cpuEntry.grid(row=3, column=1) if 'cpu' in self.prefValues: self.cpuEntry.insert(0, str(self.prefValues['cpu'])) # Selection of Scheduler if 'sched' in self.prefValues: sched = self.prefValues['sched'] else: sched = 'host' self.schedVar = StringVar(self.propFrame) self.schedOption = OptionMenu(self.propFrame, self.schedVar, "host", "cfs", "rt") self.schedOption.grid(row=3, column=2, sticky=W) self.schedVar.set(sched) # Selection of Cores Label(self.propFrame, text="Cores:").grid(row=4, sticky=E) self.coreEntry = Entry(self.propFrame) self.coreEntry.grid(row=4, column=1) if 'cores' in self.prefValues: self.coreEntry.insert(1, self.prefValues['cores']) # Start command Label(self.propFrame, text="Start Command:").grid(row=5, sticky=E) self.startEntry = Entry(self.propFrame) self.startEntry.grid(row=5, column=1, sticky='nswe', columnspan=3) if 'startCommand' in self.prefValues: self.startEntry.insert(0, str(self.prefValues['startCommand'])) # Stop command Label(self.propFrame, text="Stop Command:").grid(row=6, sticky=E) self.stopEntry = Entry(self.propFrame) self.stopEntry.grid(row=6, column=1, sticky='nswe', columnspan=3) if 'stopCommand' in self.prefValues: self.stopEntry.insert(0, str(self.prefValues['stopCommand'])) ### TAB 2 # External Interfaces self.externalInterfaces = 0 Label(self.interfaceFrame, text="External Interface:").grid(row=0, column=0, sticky=E) self.b = Button( self.interfaceFrame, text='Add', command=self.addInterface) self.b.grid(row=0, column=1) self.interfaceFrame = VerticalScrolledTable(self.interfaceFrame, rows=0, columns=1, title='External Interfaces') self.interfaceFrame.grid(row=1, column=0, sticky='nswe', columnspan=2) self.tableFrame = self.interfaceFrame.interior self.tableFrame.addRow(value=['Interface Name'], readonly=True) # Add defined interfaces externalInterfaces = [] if 'externalInterfaces' in self.prefValues: externalInterfaces = self.prefValues['externalInterfaces'] for externalInterface in externalInterfaces: self.tableFrame.addRow(value=[externalInterface]) ### TAB 3 # VLAN Interfaces self.vlanInterfaces = 0 Label(self.vlanFrame, text="VLAN Interface:").grid(row=0, column=0, sticky=E) self.vlanButton = Button( self.vlanFrame, text='Add', command=self.addVlanInterface) self.vlanButton.grid(row=0, column=1) self.vlanFrame = VerticalScrolledTable(self.vlanFrame, rows=0, columns=2, title='VLAN Interfaces') self.vlanFrame.grid(row=1, column=0, sticky='nswe', columnspan=2) self.vlanTableFrame = self.vlanFrame.interior self.vlanTableFrame.addRow(value=['IP Address','VLAN ID'], readonly=True) vlanInterfaces = [] if 'vlanInterfaces' in self.prefValues: vlanInterfaces = self.prefValues['vlanInterfaces'] for vlanInterface in vlanInterfaces: self.vlanTableFrame.addRow(value=vlanInterface) ### TAB 4 # Private Directories self.privateDirectories = 0 Label(self.mountFrame, text="Private Directory:").grid(row=0, column=0, sticky=E) self.mountButton = Button( self.mountFrame, text='Add', command=self.addDirectory) self.mountButton.grid(row=0, column=1) self.mountFrame = VerticalScrolledTable(self.mountFrame, rows=0, columns=2, title='Directories') self.mountFrame.grid(row=1, column=0, sticky='nswe', columnspan=2) self.mountTableFrame = self.mountFrame.interior self.mountTableFrame.addRow(value=['Mount','Persistent Directory'], readonly=True) directoryList = [] if 'privateDirectory' in self.prefValues: directoryList = self.prefValues['privateDirectory'] for privateDir in directoryList: if isinstance( privateDir, tuple ): self.mountTableFrame.addRow(value=privateDir) else: self.mountTableFrame.addRow(value=[privateDir,'']) def addDirectory( self ): self.mountTableFrame.addRow() def addVlanInterface( self ): self.vlanTableFrame.addRow() def addInterface( self ): self.tableFrame.addRow() def apply(self): externalInterfaces = [] for row in range(self.tableFrame.rows): if (len(self.tableFrame.get(row, 0)) > 0 and row > 0): externalInterfaces.append(self.tableFrame.get(row, 0)) vlanInterfaces = [] for row in range(self.vlanTableFrame.rows): if (len(self.vlanTableFrame.get(row, 0)) > 0 and len(self.vlanTableFrame.get(row, 1)) > 0 and row > 0): vlanInterfaces.append([self.vlanTableFrame.get(row, 0), self.vlanTableFrame.get(row, 1)]) privateDirectories = [] for row in range(self.mountTableFrame.rows): if len(self.mountTableFrame.get(row, 0)) > 0 and row > 0: if len(self.mountTableFrame.get(row, 1)) > 0: privateDirectories.append((self.mountTableFrame.get(row, 0), self.mountTableFrame.get(row, 1))) else: privateDirectories.append(self.mountTableFrame.get(row, 0)) results = {'cpu': self.cpuEntry.get(), 'cores':self.coreEntry.get(), 'sched':self.schedVar.get(), 'hostname':self.hostnameEntry.get(), 'ip':self.ipEntry.get(), 'defaultRoute':self.routeEntry.get(), 'startCommand':self.startEntry.get(), 'stopCommand':self.stopEntry.get(), 'privateDirectory':privateDirectories, 'externalInterfaces':externalInterfaces, 'vlanInterfaces':vlanInterfaces} self.result = results class SwitchDialog(CustomDialog): def __init__(self, master, title, prefDefaults): self.prefValues = prefDefaults self.result = None CustomDialog.__init__(self, master, title) def body(self, master): self.rootFrame = master self.leftfieldFrame = Frame(self.rootFrame) self.rightfieldFrame = Frame(self.rootFrame) self.leftfieldFrame.grid(row=0, column=0, sticky='nswe') self.rightfieldFrame.grid(row=0, column=1, sticky='nswe') rowCount = 0 externalInterfaces = [] if 'externalInterfaces' in self.prefValues: externalInterfaces = self.prefValues['externalInterfaces'] # Field for Hostname Label(self.leftfieldFrame, text="Hostname:").grid(row=rowCount, sticky=E) self.hostnameEntry = Entry(self.leftfieldFrame) self.hostnameEntry.grid(row=rowCount, column=1) self.hostnameEntry.insert(0, self.prefValues['hostname']) rowCount+=1 # Field for DPID Label(self.leftfieldFrame, text="DPID:").grid(row=rowCount, sticky=E) self.dpidEntry = Entry(self.leftfieldFrame) self.dpidEntry.grid(row=rowCount, column=1) if 'dpid' in self.prefValues: self.dpidEntry.insert(0, self.prefValues['dpid']) rowCount+=1 # Field for Netflow Label(self.leftfieldFrame, text="Enable NetFlow:").grid(row=rowCount, sticky=E) self.nflow = IntVar() self.nflowButton = Checkbutton(self.leftfieldFrame, variable=self.nflow) self.nflowButton.grid(row=rowCount, column=1, sticky=W) if 'netflow' in self.prefValues: if self.prefValues['netflow'] == '0': self.nflowButton.deselect() else: self.nflowButton.select() else: self.nflowButton.deselect() rowCount+=1 # Field for sflow Label(self.leftfieldFrame, text="Enable sFlow:").grid(row=rowCount, sticky=E) self.sflow = IntVar() self.sflowButton = Checkbutton(self.leftfieldFrame, variable=self.sflow) self.sflowButton.grid(row=rowCount, column=1, sticky=W) if 'sflow' in self.prefValues: if self.prefValues['sflow'] == '0': self.sflowButton.deselect() else: self.sflowButton.select() else: self.sflowButton.deselect() rowCount+=1 # Selection of switch type Label(self.leftfieldFrame, text="Switch Type:").grid(row=rowCount, sticky=E) self.switchType = StringVar(self.leftfieldFrame) self.switchTypeMenu = OptionMenu(self.leftfieldFrame, self.switchType, "Default", "Open vSwitch Kernel Mode", "Indigo Virtual Switch", "Userspace Switch", "Userspace Switch inNamespace") self.switchTypeMenu.grid(row=rowCount, column=1, sticky=W) if 'switchType' in self.prefValues: switchTypePref = self.prefValues['switchType'] if switchTypePref == 'ivs': self.switchType.set("Indigo Virtual Switch") elif switchTypePref == 'userns': self.switchType.set("Userspace Switch inNamespace") elif switchTypePref == 'user': self.switchType.set("Userspace Switch") elif switchTypePref == 'ovs': self.switchType.set("Open vSwitch Kernel Mode") else: self.switchType.set("Default") else: self.switchType.set("Default") rowCount+=1 # Field for Switch IP Label(self.leftfieldFrame, text="IP Address:").grid(row=rowCount, sticky=E) self.ipEntry = Entry(self.leftfieldFrame) self.ipEntry.grid(row=rowCount, column=1) if 'switchIP' in self.prefValues: self.ipEntry.insert(0, self.prefValues['switchIP']) rowCount+=1 # Field for DPCTL port Label(self.leftfieldFrame, text="DPCTL port:").grid(row=rowCount, sticky=E) self.dpctlEntry = Entry(self.leftfieldFrame) self.dpctlEntry.grid(row=rowCount, column=1) if 'dpctl' in self.prefValues: self.dpctlEntry.insert(0, self.prefValues['dpctl']) rowCount+=1 # External Interfaces Label(self.rightfieldFrame, text="External Interface:").grid(row=0, sticky=E) self.b = Button( self.rightfieldFrame, text='Add', command=self.addInterface) self.b.grid(row=0, column=1) self.interfaceFrame = VerticalScrolledTable(self.rightfieldFrame, rows=0, columns=1, title='External Interfaces') self.interfaceFrame.grid(row=1, column=0, sticky='nswe', columnspan=2) self.tableFrame = self.interfaceFrame.interior # Add defined interfaces for externalInterface in externalInterfaces: self.tableFrame.addRow(value=[externalInterface]) self.commandFrame = Frame(self.rootFrame) self.commandFrame.grid(row=1, column=0, sticky='nswe', columnspan=2) self.commandFrame.columnconfigure(1, weight=1) # Start command Label(self.commandFrame, text="Start Command:").grid(row=0, column=0, sticky=W) self.startEntry = Entry(self.commandFrame) self.startEntry.grid(row=0, column=1, sticky='nsew') if 'startCommand' in self.prefValues: self.startEntry.insert(0, str(self.prefValues['startCommand'])) # Stop command Label(self.commandFrame, text="Stop Command:").grid(row=1, column=0, sticky=W) self.stopEntry = Entry(self.commandFrame) self.stopEntry.grid(row=1, column=1, sticky='nsew') if 'stopCommand' in self.prefValues: self.stopEntry.insert(0, str(self.prefValues['stopCommand'])) def addInterface( self ): self.tableFrame.addRow() def defaultDpid( self, name): "Derive dpid from switch name, s1 -> 1" assert self # satisfy pylint and allow contextual override try: dpid = int( re.findall( r'\d+', name )[ 0 ] ) dpid = hex( dpid )[ 2: ] return dpid except IndexError: return None #raise Exception( 'Unable to derive default datapath ID - ' # 'please either specify a dpid or use a ' # 'canonical switch name such as s23.' ) def apply(self): externalInterfaces = [] for row in range(self.tableFrame.rows): #print 'Interface is ' + self.tableFrame.get(row, 0) if len(self.tableFrame.get(row, 0)) > 0: externalInterfaces.append(self.tableFrame.get(row, 0)) dpid = self.dpidEntry.get() if (self.defaultDpid(self.hostnameEntry.get()) is None and len(dpid) == 0): showerror(title="Error", message= 'Unable to derive default datapath ID - ' 'please either specify a DPID or use a ' 'canonical switch name such as s23.' ) results = {'externalInterfaces':externalInterfaces, 'hostname':self.hostnameEntry.get(), 'dpid':dpid, 'startCommand':self.startEntry.get(), 'stopCommand':self.stopEntry.get(), 'sflow':str(self.sflow.get()), 'netflow':str(self.nflow.get()), 'dpctl':self.dpctlEntry.get(), 'switchIP':self.ipEntry.get()} sw = self.switchType.get() if sw == 'Indigo Virtual Switch': results['switchType'] = 'ivs' if StrictVersion(MININET_VERSION) < StrictVersion('2.1'): self.ovsOk = False showerror(title="Error", message='MiniNet version 2.1+ required. You have '+VERSION+'.') elif sw == 'Userspace Switch inNamespace': results['switchType'] = 'userns' elif sw == 'Userspace Switch': results['switchType'] = 'user' elif sw == 'Open vSwitch Kernel Mode': results['switchType'] = 'ovs' else: results['switchType'] = 'default' self.result = results class VerticalScrolledTable(LabelFrame): """A pure Tkinter scrollable frame that actually works! * Use the 'interior' attribute to place widgets inside the scrollable frame * Construct and pack/place/grid normally * This frame only allows vertical scrolling """ def __init__(self, parent, rows=2, columns=2, title=None, *args, **kw): LabelFrame.__init__(self, parent, text=title, padx=5, pady=5, *args, **kw) # create a canvas object and a vertical scrollbar for scrolling it vscrollbar = Scrollbar(self, orient=VERTICAL) vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE) canvas = Canvas(self, bd=0, highlightthickness=0, yscrollcommand=vscrollbar.set) canvas.pack(side=LEFT, fill=BOTH, expand=TRUE) vscrollbar.config(command=canvas.yview) # reset the view canvas.xview_moveto(0) canvas.yview_moveto(0) # create a frame inside the canvas which will be scrolled with it self.interior = interior = TableFrame(canvas, rows=rows, columns=columns) interior_id = canvas.create_window(0, 0, window=interior, anchor=NW) # track changes to the canvas and frame width and sync them, # also updating the scrollbar def _configure_interior(_event): # update the scrollbars to match the size of the inner frame size = (interior.winfo_reqwidth(), interior.winfo_reqheight()) canvas.config(scrollregion="0 0 %s %s" % size) if interior.winfo_reqwidth() != canvas.winfo_width(): # update the canvas's width to fit the inner frame canvas.config(width=interior.winfo_reqwidth()) interior.bind('', _configure_interior) def _configure_canvas(_event): if interior.winfo_reqwidth() != canvas.winfo_width(): # update the inner frame's width to fill the canvas canvas.itemconfigure(interior_id, width=canvas.winfo_width()) canvas.bind('', _configure_canvas) return class TableFrame(Frame): def __init__(self, parent, rows=2, columns=2): Frame.__init__(self, parent, background="black") self._widgets = [] self.rows = rows self.columns = columns for row in range(rows): current_row = [] for column in range(columns): label = Entry(self, borderwidth=0) label.grid(row=row, column=column, sticky="wens", padx=1, pady=1) current_row.append(label) self._widgets.append(current_row) def set(self, row, column, value): widget = self._widgets[row][column] widget.insert(0, value) def get(self, row, column): widget = self._widgets[row][column] return widget.get() def addRow( self, value=None, readonly=False ): #print "Adding row " + str(self.rows +1) current_row = [] for column in range(self.columns): label = Entry(self, borderwidth=0) label.grid(row=self.rows, column=column, sticky="wens", padx=1, pady=1) if value is not None: label.insert(0, value[column]) if readonly == True: label.configure(state='readonly') current_row.append(label) self._widgets.append(current_row) self.update_idletasks() self.rows += 1 class LinkDialog(tkSimpleDialog.Dialog): def __init__(self, parent, title, linkDefaults): self.linkValues = linkDefaults tkSimpleDialog.Dialog.__init__(self, parent, title) def body(self, master): self.var = StringVar(master) Label(master, text="Bandwidth:").grid(row=0, sticky=E) self.e1 = Entry(master) self.e1.grid(row=0, column=1) Label(master, text="Mbit").grid(row=0, column=2, sticky=W) if 'bw' in self.linkValues: self.e1.insert(0,str(self.linkValues['bw'])) Label(master, text="Delay:").grid(row=1, sticky=E) self.e2 = Entry(master) self.e2.grid(row=1, column=1) if 'delay' in self.linkValues: self.e2.insert(0, self.linkValues['delay']) Label(master, text="Loss:").grid(row=2, sticky=E) self.e3 = Entry(master) self.e3.grid(row=2, column=1) Label(master, text="%").grid(row=2, column=2, sticky=W) if 'loss' in self.linkValues: self.e3.insert(0, str(self.linkValues['loss'])) Label(master, text="Max Queue size:").grid(row=3, sticky=E) self.e4 = Entry(master) self.e4.grid(row=3, column=1) if 'max_queue_size' in self.linkValues: self.e4.insert(0, str(self.linkValues['max_queue_size'])) Label(master, text="Jitter:").grid(row=4, sticky=E) self.e5 = Entry(master) self.e5.grid(row=4, column=1) if 'jitter' in self.linkValues: self.e5.insert(0, self.linkValues['jitter']) Label(master, text="Speedup:").grid(row=5, sticky=E) self.e6 = Entry(master) self.e6.grid(row=5, column=1) if 'speedup' in self.linkValues: self.e6.insert(0, str(self.linkValues['speedup'])) return self.e1 # initial focus def apply(self): self.result = {} if len(self.e1.get()) > 0: self.result['bw'] = int(self.e1.get()) if len(self.e2.get()) > 0: self.result['delay'] = self.e2.get() if len(self.e3.get()) > 0: self.result['loss'] = int(self.e3.get()) if len(self.e4.get()) > 0: self.result['max_queue_size'] = int(self.e4.get()) if len(self.e5.get()) > 0: self.result['jitter'] = self.e5.get() if len(self.e6.get()) > 0: self.result['speedup'] = int(self.e6.get()) class ControllerDialog(tkSimpleDialog.Dialog): def __init__(self, parent, title, ctrlrDefaults=None): if ctrlrDefaults: self.ctrlrValues = ctrlrDefaults tkSimpleDialog.Dialog.__init__(self, parent, title) def body(self, master): self.var = StringVar(master) self.protcolvar = StringVar(master) rowCount=0 # Field for Hostname Label(master, text="Name:").grid(row=rowCount, sticky=E) self.hostnameEntry = Entry(master) self.hostnameEntry.grid(row=rowCount, column=1) self.hostnameEntry.insert(0, self.ctrlrValues['hostname']) rowCount+=1 # Field for Remove Controller Port Label(master, text="Controller Port:").grid(row=rowCount, sticky=E) self.e2 = Entry(master) self.e2.grid(row=rowCount, column=1) self.e2.insert(0, self.ctrlrValues['remotePort']) rowCount+=1 # Field for Controller Type Label(master, text="Controller Type:").grid(row=rowCount, sticky=E) controllerType = self.ctrlrValues['controllerType'] self.o1 = OptionMenu(master, self.var, "Remote Controller", "In-Band Controller", "OpenFlow Reference", "OVS Controller") self.o1.grid(row=rowCount, column=1, sticky=W) if controllerType == 'ref': self.var.set("OpenFlow Reference") elif controllerType == 'inband': self.var.set("In-Band Controller") elif controllerType == 'remote': self.var.set("Remote Controller") else: self.var.set("OVS Controller") rowCount+=1 # Field for Controller Protcol Label(master, text="Protocol:").grid(row=rowCount, sticky=E) if 'controllerProtocol' in self.ctrlrValues: controllerProtocol = self.ctrlrValues['controllerProtocol'] else: controllerProtocol = 'tcp' self.protcol = OptionMenu(master, self.protcolvar, "TCP", "SSL") self.protcol.grid(row=rowCount, column=1, sticky=W) if controllerProtocol == 'ssl': self.protcolvar.set("SSL") else: self.protcolvar.set("TCP") rowCount+=1 # Field for Remove Controller IP remoteFrame= LabelFrame(master, text='Remote/In-Band Controller', padx=5, pady=5) remoteFrame.grid(row=rowCount, column=0, columnspan=2, sticky=W) Label(remoteFrame, text="IP Address:").grid(row=0, sticky=E) self.e1 = Entry(remoteFrame) self.e1.grid(row=0, column=1) self.e1.insert(0, self.ctrlrValues['remoteIP']) rowCount+=1 return self.hostnameEntry # initial focus def apply(self): self.result = { 'hostname': self.hostnameEntry.get(), 'remoteIP': self.e1.get(), 'remotePort': int(self.e2.get())} controllerType = self.var.get() if controllerType == 'Remote Controller': self.result['controllerType'] = 'remote' elif controllerType == 'In-Band Controller': self.result['controllerType'] = 'inband' elif controllerType == 'OpenFlow Reference': self.result['controllerType'] = 'ref' else: self.result['controllerType'] = 'ovsc' controllerProtocol = self.protcolvar.get() if controllerProtocol == 'SSL': self.result['controllerProtocol'] = 'ssl' else: self.result['controllerProtocol'] = 'tcp' class ToolTip(object): def __init__(self, widget): self.widget = widget self.tipwindow = None self.id = None self.x = self.y = 0 def showtip(self, text): "Display text in tooltip window" self.text = text if self.tipwindow or not self.text: return x, y, _cx, cy = self.widget.bbox("insert") x = x + self.widget.winfo_rootx() + 27 y = y + cy + self.widget.winfo_rooty() +27 self.tipwindow = tw = Toplevel(self.widget) tw.wm_overrideredirect(1) tw.wm_geometry("+%d+%d" % (x, y)) try: # For Mac OS # pylint: disable=protected-access tw.tk.call("::tk::unsupported::MacWindowStyle", "style", tw._w, "help", "noActivates") # pylint: enable=protected-access except TclError: pass label = Label(tw, text=self.text, justify=LEFT, background="#ffffe0", relief=SOLID, borderwidth=1, font=("tahoma", "8", "normal")) label.pack(ipadx=1) def hidetip(self): tw = self.tipwindow self.tipwindow = None if tw: tw.destroy() class MiniEdit( Frame ): "A simple network editor for Mininet." def __init__( self, parent=None, cheight=600, cwidth=1000 ): self.defaultIpBase='10.0.0.0/8' self.nflowDefaults = {'nflowTarget':'', 'nflowTimeout':'600', 'nflowAddId':'0'} self.sflowDefaults = {'sflowTarget':'', 'sflowSampling':'400', 'sflowHeader':'128', 'sflowPolling':'30'} self.appPrefs={ "ipBase": self.defaultIpBase, "startCLI": "0", "terminalType": 'xterm', "switchType": 'ovs', "dpctl": '', 'sflow':self.sflowDefaults, 'netflow':self.nflowDefaults, 'openFlowVersions':{'ovsOf10':'1', 'ovsOf11':'0', 'ovsOf12':'0', 'ovsOf13':'0'} } Frame.__init__( self, parent ) self.action = None self.appName = 'MiniEdit' self.fixedFont = tkFont.Font ( family="DejaVu Sans Mono", size="14" ) # Style self.font = ( 'Geneva', 9 ) self.smallFont = ( 'Geneva', 7 ) self.bg = 'white' # Title self.top = self.winfo_toplevel() self.top.title( self.appName ) # Menu bar self.createMenubar() # Editing canvas self.cheight, self.cwidth = cheight, cwidth self.cframe, self.canvas = self.createCanvas() # Toolbar self.controllers = {} # Toolbar self.images = miniEditImages() self.buttons = {} self.active = None self.tools = ( 'Select', 'Host', 'Switch', 'LegacySwitch', 'LegacyRouter', 'NetLink', 'Controller' ) self.customColors = { 'Switch': 'darkGreen', 'Host': 'blue' } self.toolbar = self.createToolbar() # Layout self.toolbar.grid( column=0, row=0, sticky='nsew') self.cframe.grid( column=1, row=0 ) self.columnconfigure( 1, weight=1 ) self.rowconfigure( 0, weight=1 ) self.pack( expand=True, fill='both' ) # About box self.aboutBox = None # Initialize node data self.nodeBindings = self.createNodeBindings() self.nodePrefixes = { 'LegacyRouter': 'r', 'LegacySwitch': 's', 'Switch': 's', 'Host': 'h' , 'Controller': 'c'} self.widgetToItem = {} self.itemToWidget = {} # Initialize link tool self.link = self.linkWidget = None # Selection support self.selection = None # Keyboard bindings self.bind( '', lambda event: self.quit() ) self.bind( '', self.deleteSelection ) self.bind( '', self.deleteSelection ) self.focus() self.hostPopup = Menu(self.top, tearoff=0) self.hostPopup.add_command(label='Host Options', font=self.font) self.hostPopup.add_separator() self.hostPopup.add_command(label='Properties', font=self.font, command=self.hostDetails ) self.hostRunPopup = Menu(self.top, tearoff=0) self.hostRunPopup.add_command(label='Host Options', font=self.font) self.hostRunPopup.add_separator() self.hostRunPopup.add_command(label='Terminal', font=self.font, command=self.xterm ) self.legacyRouterRunPopup = Menu(self.top, tearoff=0) self.legacyRouterRunPopup.add_command(label='Router Options', font=self.font) self.legacyRouterRunPopup.add_separator() self.legacyRouterRunPopup.add_command(label='Terminal', font=self.font, command=self.xterm ) self.switchPopup = Menu(self.top, tearoff=0) self.switchPopup.add_command(label='Switch Options', font=self.font) self.switchPopup.add_separator() self.switchPopup.add_command(label='Properties', font=self.font, command=self.switchDetails ) self.switchRunPopup = Menu(self.top, tearoff=0) self.switchRunPopup.add_command(label='Switch Options', font=self.font) self.switchRunPopup.add_separator() self.switchRunPopup.add_command(label='List bridge details', font=self.font, command=self.listBridge ) self.linkPopup = Menu(self.top, tearoff=0) self.linkPopup.add_command(label='Link Options', font=self.font) self.linkPopup.add_separator() self.linkPopup.add_command(label='Properties', font=self.font, command=self.linkDetails ) self.linkRunPopup = Menu(self.top, tearoff=0) self.linkRunPopup.add_command(label='Link Options', font=self.font) self.linkRunPopup.add_separator() self.linkRunPopup.add_command(label='Link Up', font=self.font, command=self.linkUp ) self.linkRunPopup.add_command(label='Link Down', font=self.font, command=self.linkDown ) self.controllerPopup = Menu(self.top, tearoff=0) self.controllerPopup.add_command(label='Controller Options', font=self.font) self.controllerPopup.add_separator() self.controllerPopup.add_command(label='Properties', font=self.font, command=self.controllerDetails ) # Event handling initalization self.linkx = self.linky = self.linkItem = None self.lastSelection = None # Model initialization self.links = {} self.hostOpts = {} self.switchOpts = {} self.hostCount = 0 self.switchCount = 0 self.controllerCount = 0 self.net = None # Close window gracefully Wm.wm_protocol( self.top, name='WM_DELETE_WINDOW', func=self.quit ) def quit( self ): "Stop our network, if any, then quit." self.stop() Frame.quit( self ) def createMenubar( self ): "Create our menu bar." font = self.font mbar = Menu( self.top, font=font ) self.top.configure( menu=mbar ) fileMenu = Menu( mbar, tearoff=False ) mbar.add_cascade( label="File", font=font, menu=fileMenu ) fileMenu.add_command( label="New", font=font, command=self.newTopology ) fileMenu.add_command( label="Open", font=font, command=self.loadTopology ) fileMenu.add_command( label="Save", font=font, command=self.saveTopology ) fileMenu.add_command( label="Export Level 2 Script", font=font, command=self.exportScript ) fileMenu.add_separator() fileMenu.add_command( label='Quit', command=self.quit, font=font ) editMenu = Menu( mbar, tearoff=False ) mbar.add_cascade( label="Edit", font=font, menu=editMenu ) editMenu.add_command( label="Cut", font=font, command=lambda: self.deleteSelection( None ) ) editMenu.add_command( label="Preferences", font=font, command=self.prefDetails) runMenu = Menu( mbar, tearoff=False ) mbar.add_cascade( label="Run", font=font, menu=runMenu ) runMenu.add_command( label="Run", font=font, command=self.doRun ) runMenu.add_command( label="Stop", font=font, command=self.doStop ) fileMenu.add_separator() runMenu.add_command( label='Show OVS Summary', font=font, command=self.ovsShow ) runMenu.add_command( label='Root Terminal', font=font, command=self.rootTerminal ) # Application menu appMenu = Menu( mbar, tearoff=False ) mbar.add_cascade( label="Help", font=font, menu=appMenu ) appMenu.add_command( label='About MiniEdit', command=self.about, font=font) # Canvas def createCanvas( self ): "Create and return our scrolling canvas frame." f = Frame( self ) canvas = Canvas( f, width=self.cwidth, height=self.cheight, bg=self.bg ) # Scroll bars xbar = Scrollbar( f, orient='horizontal', command=canvas.xview ) ybar = Scrollbar( f, orient='vertical', command=canvas.yview ) canvas.configure( xscrollcommand=xbar.set, yscrollcommand=ybar.set ) # Resize box resize = Label( f, bg='white' ) # Layout canvas.grid( row=0, column=1, sticky='nsew') ybar.grid( row=0, column=2, sticky='ns') xbar.grid( row=1, column=1, sticky='ew' ) resize.grid( row=1, column=2, sticky='nsew' ) # Resize behavior f.rowconfigure( 0, weight=1 ) f.columnconfigure( 1, weight=1 ) f.grid( row=0, column=0, sticky='nsew' ) f.bind( '', lambda event: self.updateScrollRegion() ) # Mouse bindings canvas.bind( '', self.clickCanvas ) canvas.bind( '', self.dragCanvas ) canvas.bind( '', self.releaseCanvas ) return f, canvas def updateScrollRegion( self ): "Update canvas scroll region to hold everything." bbox = self.canvas.bbox( 'all' ) if bbox is not None: self.canvas.configure( scrollregion=( 0, 0, bbox[ 2 ], bbox[ 3 ] ) ) def canvasx( self, x_root ): "Convert root x coordinate to canvas coordinate." c = self.canvas return c.canvasx( x_root ) - c.winfo_rootx() def canvasy( self, y_root ): "Convert root y coordinate to canvas coordinate." c = self.canvas return c.canvasy( y_root ) - c.winfo_rooty() # Toolbar def activate( self, toolName ): "Activate a tool and press its button." # Adjust button appearance if self.active: self.buttons[ self.active ].configure( relief='raised' ) self.buttons[ toolName ].configure( relief='sunken' ) # Activate dynamic bindings self.active = toolName @staticmethod def createToolTip(widget, text): toolTip = ToolTip(widget) def enter(_event): toolTip.showtip(text) def leave(_event): toolTip.hidetip() widget.bind('', enter) widget.bind('', leave) def createToolbar( self ): "Create and return our toolbar frame." toolbar = Frame( self ) # Tools for tool in self.tools: cmd = ( lambda t=tool: self.activate( t ) ) b = Button( toolbar, text=tool, font=self.smallFont, command=cmd) if tool in self.images: b.config( height=35, image=self.images[ tool ] ) self.createToolTip(b, str(tool)) # b.config( compound='top' ) b.pack( fill='x' ) self.buttons[ tool ] = b self.activate( self.tools[ 0 ] ) # Spacer Label( toolbar, text='' ).pack() # Commands for cmd, color in [ ( 'Stop', 'darkRed' ), ( 'Run', 'darkGreen' ) ]: doCmd = getattr( self, 'do' + cmd ) b = Button( toolbar, text=cmd, font=self.smallFont, fg=color, command=doCmd ) b.pack( fill='x', side='bottom' ) return toolbar def doRun( self ): "Run command." self.activate( 'Select' ) for tool in self.tools: self.buttons[ tool ].config( state='disabled' ) self.start() def doStop( self ): "Stop command." self.stop() for tool in self.tools: self.buttons[ tool ].config( state='normal' ) def addNode( self, node, nodeNum, x, y, name=None): "Add a new node to our canvas." if 'Switch' == node: self.switchCount += 1 if 'Host' == node: self.hostCount += 1 if 'Controller' == node: self.controllerCount += 1 if name is None: name = self.nodePrefixes[ node ] + nodeNum self.addNamedNode(node, name, x, y) def addNamedNode( self, node, name, x, y): "Add a new node to our canvas." icon = self.nodeIcon( node, name ) item = self.canvas.create_window( x, y, anchor='c', window=icon, tags=node ) self.widgetToItem[ icon ] = item self.itemToWidget[ item ] = icon icon.links = {} def convertJsonUnicode(self, text): "Some part of Mininet don't like Unicode" if isinstance(text, dict): return {self.convertJsonUnicode(key): self.convertJsonUnicode(value) for key, value in text.iteritems()} elif isinstance(text, list): return [self.convertJsonUnicode(element) for element in text] elif isinstance(text, unicode): return text.encode('utf-8') else: return text def loadTopology( self ): "Load command." c = self.canvas myFormats = [ ('Mininet Topology','*.mn'), ('All Files','*'), ] f = tkFileDialog.askopenfile(filetypes=myFormats, mode='rb') if f == None: return self.newTopology() loadedTopology = self.convertJsonUnicode(json.load(f)) # Load application preferences if 'application' in loadedTopology: self.appPrefs = dict(self.appPrefs.items() + loadedTopology['application'].items()) if "ovsOf10" not in self.appPrefs["openFlowVersions"]: self.appPrefs["openFlowVersions"]["ovsOf10"] = '0' if "ovsOf11" not in self.appPrefs["openFlowVersions"]: self.appPrefs["openFlowVersions"]["ovsOf11"] = '0' if "ovsOf12" not in self.appPrefs["openFlowVersions"]: self.appPrefs["openFlowVersions"]["ovsOf12"] = '0' if "ovsOf13" not in self.appPrefs["openFlowVersions"]: self.appPrefs["openFlowVersions"]["ovsOf13"] = '0' if "sflow" not in self.appPrefs: self.appPrefs["sflow"] = self.sflowDefaults if "netflow" not in self.appPrefs: self.appPrefs["netflow"] = self.nflowDefaults # Load controllers if 'controllers' in loadedTopology: if loadedTopology['version'] == '1': # This is old location of controller info hostname = 'c0' self.controllers = {} self.controllers[hostname] = loadedTopology['controllers']['c0'] self.controllers[hostname]['hostname'] = hostname self.addNode('Controller', 0, float(30), float(30), name=hostname) icon = self.findWidgetByName(hostname) icon.bind('', self.do_controllerPopup ) else: controllers = loadedTopology['controllers'] for controller in controllers: hostname = controller['opts']['hostname'] x = controller['x'] y = controller['y'] self.addNode('Controller', 0, float(x), float(y), name=hostname) self.controllers[hostname] = controller['opts'] icon = self.findWidgetByName(hostname) icon.bind('', self.do_controllerPopup ) # Load hosts hosts = loadedTopology['hosts'] for host in hosts: nodeNum = host['number'] hostname = 'h'+nodeNum if 'hostname' in host['opts']: hostname = host['opts']['hostname'] else: host['opts']['hostname'] = hostname if 'nodeNum' not in host['opts']: host['opts']['nodeNum'] = int(nodeNum) x = host['x'] y = host['y'] self.addNode('Host', nodeNum, float(x), float(y), name=hostname) # Fix JSON converting tuple to list when saving if 'privateDirectory' in host['opts']: newDirList = [] for privateDir in host['opts']['privateDirectory']: if isinstance( privateDir, list ): newDirList.append((privateDir[0],privateDir[1])) else: newDirList.append(privateDir) host['opts']['privateDirectory'] = newDirList self.hostOpts[hostname] = host['opts'] icon = self.findWidgetByName(hostname) icon.bind('', self.do_hostPopup ) # Load switches switches = loadedTopology['switches'] for switch in switches: nodeNum = switch['number'] hostname = 's'+nodeNum if 'controllers' not in switch['opts']: switch['opts']['controllers'] = [] if 'switchType' not in switch['opts']: switch['opts']['switchType'] = 'default' if 'hostname' in switch['opts']: hostname = switch['opts']['hostname'] else: switch['opts']['hostname'] = hostname if 'nodeNum' not in switch['opts']: switch['opts']['nodeNum'] = int(nodeNum) x = switch['x'] y = switch['y'] if switch['opts']['switchType'] == "legacyRouter": self.addNode('LegacyRouter', nodeNum, float(x), float(y), name=hostname) icon = self.findWidgetByName(hostname) icon.bind('', self.do_legacyRouterPopup ) elif switch['opts']['switchType'] == "legacySwitch": self.addNode('LegacySwitch', nodeNum, float(x), float(y), name=hostname) icon = self.findWidgetByName(hostname) icon.bind('', self.do_legacySwitchPopup ) else: self.addNode('Switch', nodeNum, float(x), float(y), name=hostname) icon = self.findWidgetByName(hostname) icon.bind('', self.do_switchPopup ) self.switchOpts[hostname] = switch['opts'] # create links to controllers if int(loadedTopology['version']) > 1: controllers = self.switchOpts[hostname]['controllers'] for controller in controllers: dest = self.findWidgetByName(controller) dx, dy = self.canvas.coords( self.widgetToItem[ dest ] ) self.link = self.canvas.create_line(float(x), float(y), dx, dy, width=4, fill='red', dash=(6, 4, 2, 4), tag='link' ) c.itemconfig(self.link, tags=c.gettags(self.link)+('control',)) self.addLink( icon, dest, linktype='control' ) self.createControlLinkBindings() self.link = self.linkWidget = None else: dest = self.findWidgetByName('c0') dx, dy = self.canvas.coords( self.widgetToItem[ dest ] ) self.link = self.canvas.create_line(float(x), float(y), dx, dy, width=4, fill='red', dash=(6, 4, 2, 4), tag='link' ) c.itemconfig(self.link, tags=c.gettags(self.link)+('control',)) self.addLink( icon, dest, linktype='control' ) self.createControlLinkBindings() self.link = self.linkWidget = None # Load links links = loadedTopology['links'] for link in links: srcNode = link['src'] src = self.findWidgetByName(srcNode) sx, sy = self.canvas.coords( self.widgetToItem[ src ] ) destNode = link['dest'] dest = self.findWidgetByName(destNode) dx, dy = self.canvas.coords( self.widgetToItem[ dest] ) self.link = self.canvas.create_line( sx, sy, dx, dy, width=4, fill='blue', tag='link' ) c.itemconfig(self.link, tags=c.gettags(self.link)+('data',)) self.addLink( src, dest, linkopts=link['opts'] ) self.createDataLinkBindings() self.link = self.linkWidget = None f.close() def findWidgetByName( self, name ): for widget in self.widgetToItem: if name == widget[ 'text' ]: return widget def newTopology( self ): "New command." for widget in self.widgetToItem.keys(): self.deleteItem( self.widgetToItem[ widget ] ) self.hostCount = 0 self.switchCount = 0 self.controllerCount = 0 self.links = {} self.hostOpts = {} self.switchOpts = {} self.controllers = {} self.appPrefs["ipBase"]= self.defaultIpBase def saveTopology( self ): "Save command." myFormats = [ ('Mininet Topology','*.mn'), ('All Files','*'), ] savingDictionary = {} fileName = tkFileDialog.asksaveasfilename(filetypes=myFormats ,title="Save the topology as...") if len(fileName ) > 0: # Save Application preferences savingDictionary['version'] = '2' # Save Switches and Hosts hostsToSave = [] switchesToSave = [] controllersToSave = [] for widget in self.widgetToItem: name = widget[ 'text' ] tags = self.canvas.gettags( self.widgetToItem[ widget ] ) x1, y1 = self.canvas.coords( self.widgetToItem[ widget ] ) if 'Switch' in tags or 'LegacySwitch' in tags or 'LegacyRouter' in tags: nodeNum = self.switchOpts[name]['nodeNum'] nodeToSave = {'number':str(nodeNum), 'x':str(x1), 'y':str(y1), 'opts':self.switchOpts[name] } switchesToSave.append(nodeToSave) elif 'Host' in tags: nodeNum = self.hostOpts[name]['nodeNum'] nodeToSave = {'number':str(nodeNum), 'x':str(x1), 'y':str(y1), 'opts':self.hostOpts[name] } hostsToSave.append(nodeToSave) elif 'Controller' in tags: nodeToSave = {'x':str(x1), 'y':str(y1), 'opts':self.controllers[name] } controllersToSave.append(nodeToSave) else: raise Exception( "Cannot create mystery node: " + name ) savingDictionary['hosts'] = hostsToSave savingDictionary['switches'] = switchesToSave savingDictionary['controllers'] = controllersToSave # Save Links linksToSave = [] for link in self.links.values(): src = link['src'] dst = link['dest'] linkopts = link['linkOpts'] srcName, dstName = src[ 'text' ], dst[ 'text' ] linkToSave = {'src':srcName, 'dest':dstName, 'opts':linkopts} if link['type'] == 'data': linksToSave.append(linkToSave) savingDictionary['links'] = linksToSave # Save Application preferences savingDictionary['application'] = self.appPrefs try: f = open(fileName, 'wb') f.write(json.dumps(savingDictionary, sort_keys=True, indent=4, separators=(',', ': '))) # pylint: disable=broad-except except Exception as er: print er # pylint: enable=broad-except finally: f.close() def exportScript( self ): "Export command." myFormats = [ ('Mininet Custom Topology','*.py'), ('All Files','*'), ] fileName = tkFileDialog.asksaveasfilename(filetypes=myFormats ,title="Export the topology as...") if len(fileName ) > 0: #print "Now saving under %s" % fileName f = open(fileName, 'wb') f.write("#!/usr/bin/python\n") f.write("\n") f.write("from mininet.net import Mininet\n") f.write("from mininet.node import Controller, RemoteController, OVSController\n") f.write("from mininet.node import CPULimitedHost, Host, Node\n") f.write("from mininet.node import OVSKernelSwitch, UserSwitch\n") if StrictVersion(MININET_VERSION) > StrictVersion('2.0'): f.write("from mininet.node import IVSSwitch\n") f.write("from mininet.cli import CLI\n") f.write("from mininet.log import setLogLevel, info\n") f.write("from mininet.link import TCLink, Intf\n") f.write("from subprocess import call\n") inBandCtrl = False for widget in self.widgetToItem: name = widget[ 'text' ] tags = self.canvas.gettags( self.widgetToItem[ widget ] ) if 'Controller' in tags: opts = self.controllers[name] controllerType = opts['controllerType'] if controllerType == 'inband': inBandCtrl = True if inBandCtrl == True: f.write("\n") f.write("class InbandController( RemoteController ):\n") f.write("\n") f.write(" def checkListening( self ):\n") f.write(" \"Overridden to do nothing.\"\n") f.write(" return\n") f.write("\n") f.write("def myNetwork():\n") f.write("\n") f.write(" net = Mininet( topo=None,\n") if len(self.appPrefs['dpctl']) > 0: f.write(" listenPort="+self.appPrefs['dpctl']+",\n") f.write(" build=False,\n") f.write(" ipBase='"+self.appPrefs['ipBase']+"')\n") f.write("\n") f.write(" info( '*** Adding controller\\n' )\n") for widget in self.widgetToItem: name = widget[ 'text' ] tags = self.canvas.gettags( self.widgetToItem[ widget ] ) if 'Controller' in tags: opts = self.controllers[name] controllerType = opts['controllerType'] if 'controllerProtocol' in opts: controllerProtocol = opts['controllerProtocol'] else: controllerProtocol = 'tcp' controllerIP = opts['remoteIP'] controllerPort = opts['remotePort'] f.write(" "+name+"=net.addController(name='"+name+"',\n") if controllerType == 'remote': f.write(" controller=RemoteController,\n") f.write(" ip='"+controllerIP+"',\n") elif controllerType == 'inband': f.write(" controller=InbandController,\n") f.write(" ip='"+controllerIP+"',\n") elif controllerType == 'ovsc': f.write(" controller=OVSController,\n") else: f.write(" controller=Controller,\n") f.write(" protocol='"+controllerProtocol+"',\n") f.write(" port="+str(controllerPort)+")\n") f.write("\n") # Save Switches and Hosts f.write(" info( '*** Add switches\\n')\n") for widget in self.widgetToItem: name = widget[ 'text' ] tags = self.canvas.gettags( self.widgetToItem[ widget ] ) if 'LegacyRouter' in tags: f.write(" "+name+" = net.addHost('"+name+"', cls=Node, ip='0.0.0.0')\n") f.write(" "+name+".cmd('sysctl -w net.ipv4.ip_forward=1')\n") if 'LegacySwitch' in tags: f.write(" "+name+" = net.addSwitch('"+name+"', cls=OVSKernelSwitch, failMode='standalone')\n") if 'Switch' in tags: opts = self.switchOpts[name] nodeNum = opts['nodeNum'] f.write(" "+name+" = net.addSwitch('"+name+"'") if opts['switchType'] == 'default': if self.appPrefs['switchType'] == 'ivs': f.write(", cls=IVSSwitch") elif self.appPrefs['switchType'] == 'user': f.write(", cls=UserSwitch") elif self.appPrefs['switchType'] == 'userns': f.write(", cls=UserSwitch, inNamespace=True") else: f.write(", cls=OVSKernelSwitch") elif opts['switchType'] == 'ivs': f.write(", cls=IVSSwitch") elif opts['switchType'] == 'user': f.write(", cls=UserSwitch") elif opts['switchType'] == 'userns': f.write(", cls=UserSwitch, inNamespace=True") else: f.write(", cls=OVSKernelSwitch") if 'dpctl' in opts: f.write(", listenPort="+opts['dpctl']) if 'dpid' in opts: f.write(", dpid='"+opts['dpid']+"'") f.write(")\n") if 'externalInterfaces' in opts: for extInterface in opts['externalInterfaces']: f.write(" Intf( '"+extInterface+"', node="+name+" )\n") f.write("\n") f.write(" info( '*** Add hosts\\n')\n") for widget in self.widgetToItem: name = widget[ 'text' ] tags = self.canvas.gettags( self.widgetToItem[ widget ] ) if 'Host' in tags: opts = self.hostOpts[name] ip = None defaultRoute = None if 'defaultRoute' in opts and len(opts['defaultRoute']) > 0: defaultRoute = "'via "+opts['defaultRoute']+"'" else: defaultRoute = 'None' if 'ip' in opts and len(opts['ip']) > 0: ip = opts['ip'] else: nodeNum = self.hostOpts[name]['nodeNum'] ipBaseNum, prefixLen = netParse( self.appPrefs['ipBase'] ) ip = ipAdd(i=nodeNum, prefixLen=prefixLen, ipBaseNum=ipBaseNum) if 'cores' in opts or 'cpu' in opts: f.write(" "+name+" = net.addHost('"+name+"', cls=CPULimitedHost, ip='"+ip+"', defaultRoute="+defaultRoute+")\n") if 'cores' in opts: f.write(" "+name+".setCPUs(cores='"+opts['cores']+"')\n") if 'cpu' in opts: f.write(" "+name+".setCPUFrac(f="+str(opts['cpu'])+", sched='"+opts['sched']+"')\n") else: f.write(" "+name+" = net.addHost('"+name+"', cls=Host, ip='"+ip+"', defaultRoute="+defaultRoute+")\n") if 'externalInterfaces' in opts: for extInterface in opts['externalInterfaces']: f.write(" Intf( '"+extInterface+"', node="+name+" )\n") f.write("\n") # Save Links f.write(" info( '*** Add links\\n')\n") for key,linkDetail in self.links.iteritems(): tags = self.canvas.gettags(key) if 'data' in tags: optsExist = False src = linkDetail['src'] dst = linkDetail['dest'] linkopts = linkDetail['linkOpts'] srcName, dstName = src[ 'text' ], dst[ 'text' ] bw = '' # delay = '' # loss = '' # max_queue_size = '' linkOpts = "{" if 'bw' in linkopts: bw = linkopts['bw'] linkOpts = linkOpts + "'bw':"+str(bw) optsExist = True if 'delay' in linkopts: # delay = linkopts['delay'] if optsExist: linkOpts = linkOpts + "," linkOpts = linkOpts + "'delay':'"+linkopts['delay']+"'" optsExist = True if 'loss' in linkopts: if optsExist: linkOpts = linkOpts + "," linkOpts = linkOpts + "'loss':"+str(linkopts['loss']) optsExist = True if 'max_queue_size' in linkopts: if optsExist: linkOpts = linkOpts + "," linkOpts = linkOpts + "'max_queue_size':"+str(linkopts['max_queue_size']) optsExist = True if 'jitter' in linkopts: if optsExist: linkOpts = linkOpts + "," linkOpts = linkOpts + "'jitter':'"+linkopts['jitter']+"'" optsExist = True if 'speedup' in linkopts: if optsExist: linkOpts = linkOpts + "," linkOpts = linkOpts + "'speedup':"+str(linkopts['speedup']) optsExist = True linkOpts = linkOpts + "}" if optsExist: f.write(" "+srcName+dstName+" = "+linkOpts+"\n") f.write(" net.addLink("+srcName+", "+dstName) if optsExist: f.write(", cls=TCLink , **"+srcName+dstName) f.write(")\n") f.write("\n") f.write(" info( '*** Starting network\\n')\n") f.write(" net.build()\n") f.write(" info( '*** Starting controllers\\n')\n") f.write(" for controller in net.controllers:\n") f.write(" controller.start()\n") f.write("\n") f.write(" info( '*** Starting switches\\n')\n") for widget in self.widgetToItem: name = widget[ 'text' ] tags = self.canvas.gettags( self.widgetToItem[ widget ] ) if 'Switch' in tags or 'LegacySwitch' in tags: opts = self.switchOpts[name] ctrlList = ",".join(opts['controllers']) f.write(" net.get('"+name+"').start(["+ctrlList+"])\n") f.write("\n") f.write(" info( '*** Post configure switches and hosts\\n')\n") for widget in self.widgetToItem: name = widget[ 'text' ] tags = self.canvas.gettags( self.widgetToItem[ widget ] ) if 'Switch' in tags: opts = self.switchOpts[name] if opts['switchType'] == 'default': if self.appPrefs['switchType'] == 'user': if 'switchIP' in opts: if len(opts['switchIP']) > 0: f.write(" "+name+".cmd('ifconfig "+name+" "+opts['switchIP']+"')\n") elif self.appPrefs['switchType'] == 'userns': if 'switchIP' in opts: if len(opts['switchIP']) > 0: f.write(" "+name+".cmd('ifconfig lo "+opts['switchIP']+"')\n") elif self.appPrefs['switchType'] == 'ovs': if 'switchIP' in opts: if len(opts['switchIP']) > 0: f.write(" "+name+".cmd('ifconfig "+name+" "+opts['switchIP']+"')\n") elif opts['switchType'] == 'user': if 'switchIP' in opts: if len(opts['switchIP']) > 0: f.write(" "+name+".cmd('ifconfig "+name+" "+opts['switchIP']+"')\n") elif opts['switchType'] == 'userns': if 'switchIP' in opts: if len(opts['switchIP']) > 0: f.write(" "+name+".cmd('ifconfig lo "+opts['switchIP']+"')\n") elif opts['switchType'] == 'ovs': if 'switchIP' in opts: if len(opts['switchIP']) > 0: f.write(" "+name+".cmd('ifconfig "+name+" "+opts['switchIP']+"')\n") for widget in self.widgetToItem: name = widget[ 'text' ] tags = self.canvas.gettags( self.widgetToItem[ widget ] ) if 'Host' in tags: opts = self.hostOpts[name] # Attach vlan interfaces if 'vlanInterfaces' in opts: for vlanInterface in opts['vlanInterfaces']: f.write(" "+name+".cmd('vconfig add "+name+"-eth0 "+vlanInterface[1]+"')\n") f.write(" "+name+".cmd('ifconfig "+name+"-eth0."+vlanInterface[1]+" "+vlanInterface[0]+"')\n") # Run User Defined Start Command if 'startCommand' in opts: f.write(" "+name+".cmdPrint('"+opts['startCommand']+"')\n") if 'Switch' in tags: opts = self.switchOpts[name] # Run User Defined Start Command if 'startCommand' in opts: f.write(" "+name+".cmdPrint('"+opts['startCommand']+"')\n") # Configure NetFlow nflowValues = self.appPrefs['netflow'] if len(nflowValues['nflowTarget']) > 0: nflowEnabled = False nflowSwitches = '' for widget in self.widgetToItem: name = widget[ 'text' ] tags = self.canvas.gettags( self.widgetToItem[ widget ] ) if 'Switch' in tags: opts = self.switchOpts[name] if 'netflow' in opts: if opts['netflow'] == '1': nflowSwitches = nflowSwitches+' -- set Bridge '+name+' netflow=@MiniEditNF' nflowEnabled=True if nflowEnabled: nflowCmd = 'ovs-vsctl -- --id=@MiniEditNF create NetFlow '+ 'target=\\\"'+nflowValues['nflowTarget']+'\\\" '+ 'active-timeout='+nflowValues['nflowTimeout'] if nflowValues['nflowAddId'] == '1': nflowCmd = nflowCmd + ' add_id_to_interface=true' else: nflowCmd = nflowCmd + ' add_id_to_interface=false' f.write(" \n") f.write(" call('"+nflowCmd+nflowSwitches+"', shell=True)\n") # Configure sFlow sflowValues = self.appPrefs['sflow'] if len(sflowValues['sflowTarget']) > 0: sflowEnabled = False sflowSwitches = '' for widget in self.widgetToItem: name = widget[ 'text' ] tags = self.canvas.gettags( self.widgetToItem[ widget ] ) if 'Switch' in tags: opts = self.switchOpts[name] if 'sflow' in opts: if opts['sflow'] == '1': sflowSwitches = sflowSwitches+' -- set Bridge '+name+' sflow=@MiniEditSF' sflowEnabled=True if sflowEnabled: sflowCmd = 'ovs-vsctl -- --id=@MiniEditSF create sFlow '+ 'target=\\\"'+sflowValues['sflowTarget']+'\\\" '+ 'header='+sflowValues['sflowHeader']+' '+ 'sampling='+sflowValues['sflowSampling']+' '+ 'polling='+sflowValues['sflowPolling'] f.write(" \n") f.write(" call('"+sflowCmd+sflowSwitches+"', shell=True)\n") f.write("\n") f.write(" CLI(net)\n") for widget in self.widgetToItem: name = widget[ 'text' ] tags = self.canvas.gettags( self.widgetToItem[ widget ] ) if 'Host' in tags: opts = self.hostOpts[name] # Run User Defined Stop Command if 'stopCommand' in opts: f.write(" "+name+".cmdPrint('"+opts['stopCommand']+"')\n") if 'Switch' in tags: opts = self.switchOpts[name] # Run User Defined Stop Command if 'stopCommand' in opts: f.write(" "+name+".cmdPrint('"+opts['stopCommand']+"')\n") f.write(" net.stop()\n") f.write("\n") f.write("if __name__ == '__main__':\n") f.write(" setLogLevel( 'info' )\n") f.write(" myNetwork()\n") f.write("\n") f.close() # Generic canvas handler # # We could have used bindtags, as in nodeIcon, but # the dynamic approach used here # may actually require less code. In any case, it's an # interesting introspection-based alternative to bindtags. def canvasHandle( self, eventName, event ): "Generic canvas event handler" if self.active is None: return toolName = self.active handler = getattr( self, eventName + toolName, None ) if handler is not None: handler( event ) def clickCanvas( self, event ): "Canvas click handler." self.canvasHandle( 'click', event ) def dragCanvas( self, event ): "Canvas drag handler." self.canvasHandle( 'drag', event ) def releaseCanvas( self, event ): "Canvas mouse up handler." self.canvasHandle( 'release', event ) # Currently the only items we can select directly are # links. Nodes are handled by bindings in the node icon. def findItem( self, x, y ): "Find items at a location in our canvas." items = self.canvas.find_overlapping( x, y, x, y ) if len( items ) == 0: return None else: return items[ 0 ] # Canvas bindings for Select, Host, Switch and Link tools def clickSelect( self, event ): "Select an item." self.selectItem( self.findItem( event.x, event.y ) ) def deleteItem( self, item ): "Delete an item." # Don't delete while network is running if self.buttons[ 'Select' ][ 'state' ] == 'disabled': return # Delete from model if item in self.links: self.deleteLink( item ) if item in self.itemToWidget: self.deleteNode( item ) # Delete from view self.canvas.delete( item ) def deleteSelection( self, _event ): "Delete the selected item." if self.selection is not None: self.deleteItem( self.selection ) self.selectItem( None ) def nodeIcon( self, node, name ): "Create a new node icon." icon = Button( self.canvas, image=self.images[ node ], text=name, compound='top' ) # Unfortunately bindtags wants a tuple bindtags = [ str( self.nodeBindings ) ] bindtags += list( icon.bindtags() ) icon.bindtags( tuple( bindtags ) ) return icon def newNode( self, node, event ): "Add a new node to our canvas." c = self.canvas x, y = c.canvasx( event.x ), c.canvasy( event.y ) name = self.nodePrefixes[ node ] if 'Switch' == node: self.switchCount += 1 name = self.nodePrefixes[ node ] + str( self.switchCount ) self.switchOpts[name] = {} self.switchOpts[name]['nodeNum']=self.switchCount self.switchOpts[name]['hostname']=name self.switchOpts[name]['switchType']='default' self.switchOpts[name]['controllers']=[] if 'LegacyRouter' == node: self.switchCount += 1 name = self.nodePrefixes[ node ] + str( self.switchCount ) self.switchOpts[name] = {} self.switchOpts[name]['nodeNum']=self.switchCount self.switchOpts[name]['hostname']=name self.switchOpts[name]['switchType']='legacyRouter' if 'LegacySwitch' == node: self.switchCount += 1 name = self.nodePrefixes[ node ] + str( self.switchCount ) self.switchOpts[name] = {} self.switchOpts[name]['nodeNum']=self.switchCount self.switchOpts[name]['hostname']=name self.switchOpts[name]['switchType']='legacySwitch' self.switchOpts[name]['controllers']=[] if 'Host' == node: self.hostCount += 1 name = self.nodePrefixes[ node ] + str( self.hostCount ) self.hostOpts[name] = {'sched':'host'} self.hostOpts[name]['nodeNum']=self.hostCount self.hostOpts[name]['hostname']=name if 'Controller' == node: name = self.nodePrefixes[ node ] + str( self.controllerCount ) ctrlr = { 'controllerType': 'ref', 'hostname': name, 'controllerProtocol': 'tcp', 'remoteIP': '127.0.0.1', 'remotePort': 6633} self.controllers[name] = ctrlr # We want to start controller count at 0 self.controllerCount += 1 icon = self.nodeIcon( node, name ) item = self.canvas.create_window( x, y, anchor='c', window=icon, tags=node ) self.widgetToItem[ icon ] = item self.itemToWidget[ item ] = icon self.selectItem( item ) icon.links = {} if 'Switch' == node: icon.bind('', self.do_switchPopup ) if 'LegacyRouter' == node: icon.bind('', self.do_legacyRouterPopup ) if 'LegacySwitch' == node: icon.bind('', self.do_legacySwitchPopup ) if 'Host' == node: icon.bind('', self.do_hostPopup ) if 'Controller' == node: icon.bind('', self.do_controllerPopup ) def clickController( self, event ): "Add a new Controller to our canvas." self.newNode( 'Controller', event ) def clickHost( self, event ): "Add a new host to our canvas." self.newNode( 'Host', event ) def clickLegacyRouter( self, event ): "Add a new switch to our canvas." self.newNode( 'LegacyRouter', event ) def clickLegacySwitch( self, event ): "Add a new switch to our canvas." self.newNode( 'LegacySwitch', event ) def clickSwitch( self, event ): "Add a new switch to our canvas." self.newNode( 'Switch', event ) def dragNetLink( self, event ): "Drag a link's endpoint to another node." if self.link is None: return # Since drag starts in widget, we use root coords x = self.canvasx( event.x_root ) y = self.canvasy( event.y_root ) c = self.canvas c.coords( self.link, self.linkx, self.linky, x, y ) def releaseNetLink( self, _event ): "Give up on the current link." if self.link is not None: self.canvas.delete( self.link ) self.linkWidget = self.linkItem = self.link = None # Generic node handlers def createNodeBindings( self ): "Create a set of bindings for nodes." bindings = { '': self.clickNode, '': self.dragNode, '': self.releaseNode, '': self.enterNode, '': self.leaveNode } l = Label() # lightweight-ish owner for bindings for event, binding in bindings.items(): l.bind( event, binding ) return l def selectItem( self, item ): "Select an item and remember old selection." self.lastSelection = self.selection self.selection = item def enterNode( self, event ): "Select node on entry." self.selectNode( event ) def leaveNode( self, _event ): "Restore old selection on exit." self.selectItem( self.lastSelection ) def clickNode( self, event ): "Node click handler." if self.active is 'NetLink': self.startLink( event ) else: self.selectNode( event ) return 'break' def dragNode( self, event ): "Node drag handler." if self.active is 'NetLink': self.dragNetLink( event ) else: self.dragNodeAround( event ) def releaseNode( self, event ): "Node release handler." if self.active is 'NetLink': self.finishLink( event ) # Specific node handlers def selectNode( self, event ): "Select the node that was clicked on." item = self.widgetToItem.get( event.widget, None ) self.selectItem( item ) def dragNodeAround( self, event ): "Drag a node around on the canvas." c = self.canvas # Convert global to local coordinates; # Necessary since x, y are widget-relative x = self.canvasx( event.x_root ) y = self.canvasy( event.y_root ) w = event.widget # Adjust node position item = self.widgetToItem[ w ] c.coords( item, x, y ) # Adjust link positions for dest in w.links: link = w.links[ dest ] item = self.widgetToItem[ dest ] x1, y1 = c.coords( item ) c.coords( link, x, y, x1, y1 ) self.updateScrollRegion() def createControlLinkBindings( self ): "Create a set of bindings for nodes." # Link bindings # Selection still needs a bit of work overall # Callbacks ignore event def select( _event, link=self.link ): "Select item on mouse entry." self.selectItem( link ) def highlight( _event, link=self.link ): "Highlight item on mouse entry." self.selectItem( link ) self.canvas.itemconfig( link, fill='green' ) def unhighlight( _event, link=self.link ): "Unhighlight item on mouse exit." self.canvas.itemconfig( link, fill='red' ) #self.selectItem( None ) self.canvas.tag_bind( self.link, '', highlight ) self.canvas.tag_bind( self.link, '', unhighlight ) self.canvas.tag_bind( self.link, '', select ) def createDataLinkBindings( self ): "Create a set of bindings for nodes." # Link bindings # Selection still needs a bit of work overall # Callbacks ignore event def select( _event, link=self.link ): "Select item on mouse entry." self.selectItem( link ) def highlight( _event, link=self.link ): "Highlight item on mouse entry." self.selectItem( link ) self.canvas.itemconfig( link, fill='green' ) def unhighlight( _event, link=self.link ): "Unhighlight item on mouse exit." self.canvas.itemconfig( link, fill='blue' ) #self.selectItem( None ) self.canvas.tag_bind( self.link, '', highlight ) self.canvas.tag_bind( self.link, '', unhighlight ) self.canvas.tag_bind( self.link, '', select ) self.canvas.tag_bind( self.link, '', self.do_linkPopup ) def startLink( self, event ): "Start a new link." if event.widget not in self.widgetToItem: # Didn't click on a node return w = event.widget item = self.widgetToItem[ w ] x, y = self.canvas.coords( item ) self.link = self.canvas.create_line( x, y, x, y, width=4, fill='blue', tag='link' ) self.linkx, self.linky = x, y self.linkWidget = w self.linkItem = item def finishLink( self, event ): "Finish creating a link" if self.link is None: return source = self.linkWidget c = self.canvas # Since we dragged from the widget, use root coords x, y = self.canvasx( event.x_root ), self.canvasy( event.y_root ) target = self.findItem( x, y ) dest = self.itemToWidget.get( target, None ) if ( source is None or dest is None or source == dest or dest in source.links or source in dest.links ): self.releaseNetLink( event ) return # For now, don't allow hosts to be directly linked stags = self.canvas.gettags( self.widgetToItem[ source ] ) dtags = self.canvas.gettags( target ) if (('Host' in stags and 'Host' in dtags) or ('Controller' in dtags and 'LegacyRouter' in stags) or ('Controller' in stags and 'LegacyRouter' in dtags) or ('Controller' in dtags and 'LegacySwitch' in stags) or ('Controller' in stags and 'LegacySwitch' in dtags) or ('Controller' in dtags and 'Host' in stags) or ('Controller' in stags and 'Host' in dtags) or ('Controller' in stags and 'Controller' in dtags)): self.releaseNetLink( event ) return # Set link type linkType='data' if 'Controller' in stags or 'Controller' in dtags: linkType='control' c.itemconfig(self.link, dash=(6, 4, 2, 4), fill='red') self.createControlLinkBindings() else: linkType='data' self.createDataLinkBindings() c.itemconfig(self.link, tags=c.gettags(self.link)+(linkType,)) x, y = c.coords( target ) c.coords( self.link, self.linkx, self.linky, x, y ) self.addLink( source, dest, linktype=linkType ) if linkType == 'control': controllerName = '' switchName = '' if 'Controller' in stags: controllerName = source[ 'text' ] switchName = dest[ 'text' ] else: controllerName = dest[ 'text' ] switchName = source[ 'text' ] self.switchOpts[switchName]['controllers'].append(controllerName) # We're done self.link = self.linkWidget = None # Menu handlers def about( self ): "Display about box." about = self.aboutBox if about is None: bg = 'white' about = Toplevel( bg='white' ) about.title( 'About' ) desc = self.appName + ': a simple network editor for MiniNet' version = 'MiniEdit '+MINIEDIT_VERSION author = 'Originally by: Bob Lantz , April 2010' enhancements = 'Enhancements by: Gregory Gee, Since July 2013' www = 'http://gregorygee.wordpress.com/category/miniedit/' line1 = Label( about, text=desc, font='Helvetica 10 bold', bg=bg ) line2 = Label( about, text=version, font='Helvetica 9', bg=bg ) line3 = Label( about, text=author, font='Helvetica 9', bg=bg ) line4 = Label( about, text=enhancements, font='Helvetica 9', bg=bg ) line5 = Entry( about, font='Helvetica 9', bg=bg, width=len(www), justify=CENTER ) line5.insert(0, www) line5.configure(state='readonly') line1.pack( padx=20, pady=10 ) line2.pack(pady=10 ) line3.pack(pady=10 ) line4.pack(pady=10 ) line5.pack(pady=10 ) hide = ( lambda about=about: about.withdraw() ) self.aboutBox = about # Hide on close rather than destroying window Wm.wm_protocol( about, name='WM_DELETE_WINDOW', func=hide ) # Show (existing) window about.deiconify() def createToolImages( self ): "Create toolbar (and icon) images." @staticmethod def checkIntf( intf ): "Make sure intf exists and is not configured." if ( ' %s:' % intf ) not in quietRun( 'ip link show' ): showerror(title="Error", message='External interface ' +intf + ' does not exist! Skipping.') return False ips = re.findall( r'\d+\.\d+\.\d+\.\d+', quietRun( 'ifconfig ' + intf ) ) if ips: showerror(title="Error", message= intf + ' has an IP address and is probably in use! Skipping.' ) return False return True def hostDetails( self, _ignore=None ): if ( self.selection is None or self.net is not None or self.selection not in self.itemToWidget ): return widget = self.itemToWidget[ self.selection ] name = widget[ 'text' ] tags = self.canvas.gettags( self.selection ) if 'Host' not in tags: return prefDefaults = self.hostOpts[name] hostBox = HostDialog(self, title='Host Details', prefDefaults=prefDefaults) self.master.wait_window(hostBox.top) if hostBox.result: newHostOpts = {'nodeNum':self.hostOpts[name]['nodeNum']} newHostOpts['sched'] = hostBox.result['sched'] if len(hostBox.result['startCommand']) > 0: newHostOpts['startCommand'] = hostBox.result['startCommand'] if len(hostBox.result['stopCommand']) > 0: newHostOpts['stopCommand'] = hostBox.result['stopCommand'] if len(hostBox.result['cpu']) > 0: newHostOpts['cpu'] = float(hostBox.result['cpu']) if len(hostBox.result['cores']) > 0: newHostOpts['cores'] = hostBox.result['cores'] if len(hostBox.result['hostname']) > 0: newHostOpts['hostname'] = hostBox.result['hostname'] name = hostBox.result['hostname'] widget[ 'text' ] = name if len(hostBox.result['defaultRoute']) > 0: newHostOpts['defaultRoute'] = hostBox.result['defaultRoute'] if len(hostBox.result['ip']) > 0: newHostOpts['ip'] = hostBox.result['ip'] if len(hostBox.result['externalInterfaces']) > 0: newHostOpts['externalInterfaces'] = hostBox.result['externalInterfaces'] if len(hostBox.result['vlanInterfaces']) > 0: newHostOpts['vlanInterfaces'] = hostBox.result['vlanInterfaces'] if len(hostBox.result['privateDirectory']) > 0: newHostOpts['privateDirectory'] = hostBox.result['privateDirectory'] self.hostOpts[name] = newHostOpts print 'New host details for ' + name + ' = ' + str(newHostOpts) def switchDetails( self, _ignore=None ): if ( self.selection is None or self.net is not None or self.selection not in self.itemToWidget ): return widget = self.itemToWidget[ self.selection ] name = widget[ 'text' ] tags = self.canvas.gettags( self.selection ) if 'Switch' not in tags: return prefDefaults = self.switchOpts[name] switchBox = SwitchDialog(self, title='Switch Details', prefDefaults=prefDefaults) self.master.wait_window(switchBox.top) if switchBox.result: newSwitchOpts = {'nodeNum':self.switchOpts[name]['nodeNum']} newSwitchOpts['switchType'] = switchBox.result['switchType'] newSwitchOpts['controllers'] = self.switchOpts[name]['controllers'] if len(switchBox.result['startCommand']) > 0: newSwitchOpts['startCommand'] = switchBox.result['startCommand'] if len(switchBox.result['stopCommand']) > 0: newSwitchOpts['stopCommand'] = switchBox.result['stopCommand'] if len(switchBox.result['dpctl']) > 0: newSwitchOpts['dpctl'] = switchBox.result['dpctl'] if len(switchBox.result['dpid']) > 0: newSwitchOpts['dpid'] = switchBox.result['dpid'] if len(switchBox.result['hostname']) > 0: newSwitchOpts['hostname'] = switchBox.result['hostname'] name = switchBox.result['hostname'] widget[ 'text' ] = name if len(switchBox.result['externalInterfaces']) > 0: newSwitchOpts['externalInterfaces'] = switchBox.result['externalInterfaces'] newSwitchOpts['switchIP'] = switchBox.result['switchIP'] newSwitchOpts['sflow'] = switchBox.result['sflow'] newSwitchOpts['netflow'] = switchBox.result['netflow'] self.switchOpts[name] = newSwitchOpts print 'New switch details for ' + name + ' = ' + str(newSwitchOpts) def linkUp( self ): if ( self.selection is None or self.net is None): return link = self.selection linkDetail = self.links[link] src = linkDetail['src'] dst = linkDetail['dest'] srcName, dstName = src[ 'text' ], dst[ 'text' ] self.net.configLinkStatus(srcName, dstName, 'up') self.canvas.itemconfig(link, dash=()) def linkDown( self ): if ( self.selection is None or self.net is None): return link = self.selection linkDetail = self.links[link] src = linkDetail['src'] dst = linkDetail['dest'] srcName, dstName = src[ 'text' ], dst[ 'text' ] self.net.configLinkStatus(srcName, dstName, 'down') self.canvas.itemconfig(link, dash=(4, 4)) def linkDetails( self, _ignore=None ): if ( self.selection is None or self.net is not None): return link = self.selection linkDetail = self.links[link] # src = linkDetail['src'] # dest = linkDetail['dest'] linkopts = linkDetail['linkOpts'] linkBox = LinkDialog(self, title='Link Details', linkDefaults=linkopts) if linkBox.result is not None: linkDetail['linkOpts'] = linkBox.result print 'New link details = ' + str(linkBox.result) def prefDetails( self ): prefDefaults = self.appPrefs prefBox = PrefsDialog(self, title='Preferences', prefDefaults=prefDefaults) print 'New Prefs = ' + str(prefBox.result) if prefBox.result: self.appPrefs = prefBox.result def controllerDetails( self ): if ( self.selection is None or self.net is not None or self.selection not in self.itemToWidget ): return widget = self.itemToWidget[ self.selection ] name = widget[ 'text' ] tags = self.canvas.gettags( self.selection ) oldName = name if 'Controller' not in tags: return ctrlrBox = ControllerDialog(self, title='Controller Details', ctrlrDefaults=self.controllers[name]) if ctrlrBox.result: #print 'Controller is ' + ctrlrBox.result[0] if len(ctrlrBox.result['hostname']) > 0: name = ctrlrBox.result['hostname'] widget[ 'text' ] = name else: ctrlrBox.result['hostname'] = name self.controllers[name] = ctrlrBox.result print 'New controller details for ' + name + ' = ' + str(self.controllers[name]) # Find references to controller and change name if oldName != name: for widget in self.widgetToItem: switchName = widget[ 'text' ] tags = self.canvas.gettags( self.widgetToItem[ widget ] ) if 'Switch' in tags: switch = self.switchOpts[switchName] if oldName in switch['controllers']: switch['controllers'].remove(oldName) switch['controllers'].append(name) def listBridge( self, _ignore=None ): if ( self.selection is None or self.net is None or self.selection not in self.itemToWidget ): return name = self.itemToWidget[ self.selection ][ 'text' ] tags = self.canvas.gettags( self.selection ) if name not in self.net.nameToNode: return if 'Switch' in tags or 'LegacySwitch' in tags: call(["xterm -T 'Bridge Details' -sb -sl 2000 -e 'ovs-vsctl list bridge " + name + "; read -p \"Press Enter to close\"' &"], shell=True) @staticmethod def ovsShow( _ignore=None ): call(["xterm -T 'OVS Summary' -sb -sl 2000 -e 'ovs-vsctl show; read -p \"Press Enter to close\"' &"], shell=True) @staticmethod def rootTerminal( _ignore=None ): call(["xterm -T 'Root Terminal' -sb -sl 2000 &"], shell=True) # Model interface # # Ultimately we will either want to use a topo or # mininet object here, probably. def addLink( self, source, dest, linktype='data', linkopts=None ): "Add link to model." if linkopts is None: linkopts = {} source.links[ dest ] = self.link dest.links[ source ] = self.link self.links[ self.link ] = {'type' :linktype, 'src':source, 'dest':dest, 'linkOpts':linkopts} def deleteLink( self, link ): "Delete link from model." pair = self.links.get( link, None ) if pair is not None: source=pair['src'] dest=pair['dest'] del source.links[ dest ] del dest.links[ source ] stags = self.canvas.gettags( self.widgetToItem[ source ] ) # dtags = self.canvas.gettags( self.widgetToItem[ dest ] ) ltags = self.canvas.gettags( link ) if 'control' in ltags: controllerName = '' switchName = '' if 'Controller' in stags: controllerName = source[ 'text' ] switchName = dest[ 'text' ] else: controllerName = dest[ 'text' ] switchName = source[ 'text' ] if controllerName in self.switchOpts[switchName]['controllers']: self.switchOpts[switchName]['controllers'].remove(controllerName) if link is not None: del self.links[ link ] def deleteNode( self, item ): "Delete node (and its links) from model." widget = self.itemToWidget[ item ] tags = self.canvas.gettags(item) if 'Controller' in tags: # remove from switch controller lists for serachwidget in self.widgetToItem: name = serachwidget[ 'text' ] tags = self.canvas.gettags( self.widgetToItem[ serachwidget ] ) if 'Switch' in tags: if widget['text'] in self.switchOpts[name]['controllers']: self.switchOpts[name]['controllers'].remove(widget['text']) for link in widget.links.values(): # Delete from view and model self.deleteItem( link ) del self.itemToWidget[ item ] del self.widgetToItem[ widget ] def buildNodes( self, net): # Make nodes print "Getting Hosts and Switches." for widget in self.widgetToItem: name = widget[ 'text' ] tags = self.canvas.gettags( self.widgetToItem[ widget ] ) #print name+' has '+str(tags) if 'Switch' in tags: opts = self.switchOpts[name] #print str(opts) # Create the correct switch class switchClass = customOvs switchParms={} if 'dpctl' in opts: switchParms['listenPort']=int(opts['dpctl']) if 'dpid' in opts: switchParms['dpid']=opts['dpid'] if opts['switchType'] == 'default': if self.appPrefs['switchType'] == 'ivs': switchClass = IVSSwitch elif self.appPrefs['switchType'] == 'user': switchClass = CustomUserSwitch elif self.appPrefs['switchType'] == 'userns': switchParms['inNamespace'] = True switchClass = CustomUserSwitch else: switchClass = customOvs elif opts['switchType'] == 'user': switchClass = CustomUserSwitch elif opts['switchType'] == 'userns': switchClass = CustomUserSwitch switchParms['inNamespace'] = True elif opts['switchType'] == 'ivs': switchClass = IVSSwitch else: switchClass = customOvs if switchClass == customOvs: # Set OpenFlow versions self.openFlowVersions = [] if self.appPrefs['openFlowVersions']['ovsOf10'] == '1': self.openFlowVersions.append('OpenFlow10') if self.appPrefs['openFlowVersions']['ovsOf11'] == '1': self.openFlowVersions.append('OpenFlow11') if self.appPrefs['openFlowVersions']['ovsOf12'] == '1': self.openFlowVersions.append('OpenFlow12') if self.appPrefs['openFlowVersions']['ovsOf13'] == '1': self.openFlowVersions.append('OpenFlow13') protoList = ",".join(self.openFlowVersions) switchParms['protocols'] = protoList newSwitch = net.addSwitch( name , cls=switchClass, **switchParms) # Some post startup config if switchClass == CustomUserSwitch: if 'switchIP' in opts: if len(opts['switchIP']) > 0: newSwitch.setSwitchIP(opts['switchIP']) if switchClass == customOvs: if 'switchIP' in opts: if len(opts['switchIP']) > 0: newSwitch.setSwitchIP(opts['switchIP']) # Attach external interfaces if 'externalInterfaces' in opts: for extInterface in opts['externalInterfaces']: if self.checkIntf(extInterface): Intf( extInterface, node=newSwitch ) elif 'LegacySwitch' in tags: newSwitch = net.addSwitch( name , cls=LegacySwitch) elif 'LegacyRouter' in tags: newSwitch = net.addHost( name , cls=LegacyRouter) elif 'Host' in tags: opts = self.hostOpts[name] #print str(opts) ip = None defaultRoute = None if 'defaultRoute' in opts and len(opts['defaultRoute']) > 0: defaultRoute = 'via '+opts['defaultRoute'] if 'ip' in opts and len(opts['ip']) > 0: ip = opts['ip'] else: nodeNum = self.hostOpts[name]['nodeNum'] ipBaseNum, prefixLen = netParse( self.appPrefs['ipBase'] ) ip = ipAdd(i=nodeNum, prefixLen=prefixLen, ipBaseNum=ipBaseNum) # Create the correct host class if 'cores' in opts or 'cpu' in opts: if 'privateDirectory' in opts: hostCls = partial( CPULimitedHost, privateDirs=opts['privateDirectory'] ) else: hostCls=CPULimitedHost else: if 'privateDirectory' in opts: hostCls = partial( Host, privateDirs=opts['privateDirectory'] ) else: hostCls=Host print hostCls newHost = net.addHost( name, cls=hostCls, ip=ip, defaultRoute=defaultRoute ) # Set the CPULimitedHost specific options if 'cores' in opts: newHost.setCPUs(cores = opts['cores']) if 'cpu' in opts: newHost.setCPUFrac(f=opts['cpu'], sched=opts['sched']) # Attach external interfaces if 'externalInterfaces' in opts: for extInterface in opts['externalInterfaces']: if self.checkIntf(extInterface): Intf( extInterface, node=newHost ) if 'vlanInterfaces' in opts: if len(opts['vlanInterfaces']) > 0: print 'Checking that OS is VLAN prepared' self.pathCheck('vconfig', moduleName='vlan package') moduleDeps( add='8021q' ) elif 'Controller' in tags: opts = self.controllers[name] # Get controller info from panel controllerType = opts['controllerType'] if 'controllerProtocol' in opts: controllerProtocol = opts['controllerProtocol'] else: controllerProtocol = 'tcp' opts['controllerProtocol'] = 'tcp' controllerIP = opts['remoteIP'] controllerPort = opts['remotePort'] # Make controller print 'Getting controller selection:'+controllerType if controllerType == 'remote': net.addController(name=name, controller=RemoteController, ip=controllerIP, protocol=controllerProtocol, port=controllerPort) elif controllerType == 'inband': net.addController(name=name, controller=InbandController, ip=controllerIP, protocol=controllerProtocol, port=controllerPort) elif controllerType == 'ovsc': net.addController(name=name, controller=OVSController, protocol=controllerProtocol, port=controllerPort) else: net.addController(name=name, controller=Controller, protocol=controllerProtocol, port=controllerPort) else: raise Exception( "Cannot create mystery node: " + name ) @staticmethod def pathCheck( *args, **kwargs ): "Make sure each program in *args can be found in $PATH." moduleName = kwargs.get( 'moduleName', 'it' ) for arg in args: if not quietRun( 'which ' + arg ): showerror(title="Error", message= 'Cannot find required executable %s.\n' % arg + 'Please make sure that %s is installed ' % moduleName + 'and available in your $PATH.' ) def buildLinks( self, net): # Make links print "Getting Links." for key,link in self.links.iteritems(): tags = self.canvas.gettags(key) if 'data' in tags: src=link['src'] dst=link['dest'] linkopts=link['linkOpts'] srcName, dstName = src[ 'text' ], dst[ 'text' ] srcNode, dstNode = net.nameToNode[ srcName ], net.nameToNode[ dstName ] if linkopts: net.addLink(srcNode, dstNode, cls=TCLink, **linkopts) else: #print str(srcNode) #print str(dstNode) net.addLink(srcNode, dstNode) self.canvas.itemconfig(key, dash=()) def build( self ): print "Build network based on our topology." dpctl = None if len(self.appPrefs['dpctl']) > 0: dpctl = int(self.appPrefs['dpctl']) net = Mininet( topo=None, listenPort=dpctl, build=False, ipBase=self.appPrefs['ipBase'] ) self.buildNodes(net) self.buildLinks(net) # Build network (we have to do this separately at the moment ) net.build() return net def postStartSetup( self ): # Setup host details for widget in self.widgetToItem: name = widget[ 'text' ] tags = self.canvas.gettags( self.widgetToItem[ widget ] ) if 'Host' in tags: newHost = self.net.get(name) opts = self.hostOpts[name] # Attach vlan interfaces if 'vlanInterfaces' in opts: for vlanInterface in opts['vlanInterfaces']: print 'adding vlan interface '+vlanInterface[1] newHost.cmdPrint('ifconfig '+name+'-eth0.'+vlanInterface[1]+' '+vlanInterface[0]) # Run User Defined Start Command if 'startCommand' in opts: newHost.cmdPrint(opts['startCommand']) if 'Switch' in tags: newNode = self.net.get(name) opts = self.switchOpts[name] # Run User Defined Start Command if 'startCommand' in opts: newNode.cmdPrint(opts['startCommand']) # Configure NetFlow nflowValues = self.appPrefs['netflow'] if len(nflowValues['nflowTarget']) > 0: nflowEnabled = False nflowSwitches = '' for widget in self.widgetToItem: name = widget[ 'text' ] tags = self.canvas.gettags( self.widgetToItem[ widget ] ) if 'Switch' in tags: opts = self.switchOpts[name] if 'netflow' in opts: if opts['netflow'] == '1': print name+' has Netflow enabled' nflowSwitches = nflowSwitches+' -- set Bridge '+name+' netflow=@MiniEditNF' nflowEnabled=True if nflowEnabled: nflowCmd = 'ovs-vsctl -- --id=@MiniEditNF create NetFlow '+ 'target=\\\"'+nflowValues['nflowTarget']+'\\\" '+ 'active-timeout='+nflowValues['nflowTimeout'] if nflowValues['nflowAddId'] == '1': nflowCmd = nflowCmd + ' add_id_to_interface=true' else: nflowCmd = nflowCmd + ' add_id_to_interface=false' print 'cmd = '+nflowCmd+nflowSwitches call(nflowCmd+nflowSwitches, shell=True) else: print 'No switches with Netflow' else: print 'No NetFlow targets specified.' # Configure sFlow sflowValues = self.appPrefs['sflow'] if len(sflowValues['sflowTarget']) > 0: sflowEnabled = False sflowSwitches = '' for widget in self.widgetToItem: name = widget[ 'text' ] tags = self.canvas.gettags( self.widgetToItem[ widget ] ) if 'Switch' in tags: opts = self.switchOpts[name] if 'sflow' in opts: if opts['sflow'] == '1': print name+' has sflow enabled' sflowSwitches = sflowSwitches+' -- set Bridge '+name+' sflow=@MiniEditSF' sflowEnabled=True if sflowEnabled: sflowCmd = 'ovs-vsctl -- --id=@MiniEditSF create sFlow '+ 'target=\\\"'+sflowValues['sflowTarget']+'\\\" '+ 'header='+sflowValues['sflowHeader']+' '+ 'sampling='+sflowValues['sflowSampling']+' '+ 'polling='+sflowValues['sflowPolling'] print 'cmd = '+sflowCmd+sflowSwitches call(sflowCmd+sflowSwitches, shell=True) else: print 'No switches with sflow' else: print 'No sFlow targets specified.' ## NOTE: MAKE SURE THIS IS LAST THING CALLED # Start the CLI if enabled if self.appPrefs['startCLI'] == '1': info( "\n\n NOTE: PLEASE REMEMBER TO EXIT THE CLI BEFORE YOU PRESS THE STOP BUTTON. Not exiting will prevent MiniEdit from quitting and will prevent you from starting the network again during this sessoin.\n\n") CLI(self.net) def start( self ): "Start network." if self.net is None: self.net = self.build() # Since I am going to inject per switch controllers. # I can't call net.start(). I have to replicate what it # does and add the controller options. #self.net.start() info( '**** Starting %s controllers\n' % len( self.net.controllers ) ) for controller in self.net.controllers: info( str(controller) + ' ') controller.start() info('\n') info( '**** Starting %s switches\n' % len( self.net.switches ) ) #for switch in self.net.switches: # info( switch.name + ' ') # switch.start( self.net.controllers ) for widget in self.widgetToItem: name = widget[ 'text' ] tags = self.canvas.gettags( self.widgetToItem[ widget ] ) if 'Switch' in tags: opts = self.switchOpts[name] switchControllers = [] for ctrl in opts['controllers']: switchControllers.append(self.net.get(ctrl)) info( name + ' ') # Figure out what controllers will manage this switch self.net.get(name).start( switchControllers ) if 'LegacySwitch' in tags: self.net.get(name).start( [] ) info( name + ' ') info('\n') self.postStartSetup() def stop( self ): "Stop network." if self.net is not None: # Stop host details for widget in self.widgetToItem: name = widget[ 'text' ] tags = self.canvas.gettags( self.widgetToItem[ widget ] ) if 'Host' in tags: newHost = self.net.get(name) opts = self.hostOpts[name] # Run User Defined Stop Command if 'stopCommand' in opts: newHost.cmdPrint(opts['stopCommand']) if 'Switch' in tags: newNode = self.net.get(name) opts = self.switchOpts[name] # Run User Defined Stop Command if 'stopCommand' in opts: newNode.cmdPrint(opts['stopCommand']) self.net.stop() cleanUpScreens() self.net = None def do_linkPopup(self, event): # display the popup menu if self.net is None: try: self.linkPopup.tk_popup(event.x_root, event.y_root, 0) finally: # make sure to release the grab (Tk 8.0a1 only) self.linkPopup.grab_release() else: try: self.linkRunPopup.tk_popup(event.x_root, event.y_root, 0) finally: # make sure to release the grab (Tk 8.0a1 only) self.linkRunPopup.grab_release() def do_controllerPopup(self, event): # display the popup menu if self.net is None: try: self.controllerPopup.tk_popup(event.x_root, event.y_root, 0) finally: # make sure to release the grab (Tk 8.0a1 only) self.controllerPopup.grab_release() def do_legacyRouterPopup(self, event): # display the popup menu if self.net is not None: try: self.legacyRouterRunPopup.tk_popup(event.x_root, event.y_root, 0) finally: # make sure to release the grab (Tk 8.0a1 only) self.legacyRouterRunPopup.grab_release() def do_hostPopup(self, event): # display the popup menu if self.net is None: try: self.hostPopup.tk_popup(event.x_root, event.y_root, 0) finally: # make sure to release the grab (Tk 8.0a1 only) self.hostPopup.grab_release() else: try: self.hostRunPopup.tk_popup(event.x_root, event.y_root, 0) finally: # make sure to release the grab (Tk 8.0a1 only) self.hostRunPopup.grab_release() def do_legacySwitchPopup(self, event): # display the popup menu if self.net is not None: try: self.switchRunPopup.tk_popup(event.x_root, event.y_root, 0) finally: # make sure to release the grab (Tk 8.0a1 only) self.switchRunPopup.grab_release() def do_switchPopup(self, event): # display the popup menu if self.net is None: try: self.switchPopup.tk_popup(event.x_root, event.y_root, 0) finally: # make sure to release the grab (Tk 8.0a1 only) self.switchPopup.grab_release() else: try: self.switchRunPopup.tk_popup(event.x_root, event.y_root, 0) finally: # make sure to release the grab (Tk 8.0a1 only) self.switchRunPopup.grab_release() def xterm( self, _ignore=None ): "Make an xterm when a button is pressed." if ( self.selection is None or self.net is None or self.selection not in self.itemToWidget ): return name = self.itemToWidget[ self.selection ][ 'text' ] if name not in self.net.nameToNode: return term = makeTerm( self.net.nameToNode[ name ], 'Host', term=self.appPrefs['terminalType'] ) if StrictVersion(MININET_VERSION) > StrictVersion('2.0'): self.net.terms += term else: self.net.terms.append(term) def iperf( self, _ignore=None ): "Make an xterm when a button is pressed." if ( self.selection is None or self.net is None or self.selection not in self.itemToWidget ): return name = self.itemToWidget[ self.selection ][ 'text' ] if name not in self.net.nameToNode: return self.net.nameToNode[ name ].cmd( 'iperf -s -p 5001 &' ) ### BELOW HERE IS THE TOPOLOGY IMPORT CODE ### def parseArgs( self ): """Parse command-line args and return options object. returns: opts parse options dict""" if '--custom' in sys.argv: index = sys.argv.index( '--custom' ) if len( sys.argv ) > index + 1: filename = sys.argv[ index + 1 ] self.parseCustomFile( filename ) else: raise Exception( 'Custom file name not found' ) desc = ( "The %prog utility creates Mininet network from the\n" "command line. It can create parametrized topologies,\n" "invoke the Mininet CLI, and run tests." ) usage = ( '%prog [options]\n' '(type %prog -h for details)' ) opts = OptionParser( description=desc, usage=usage ) addDictOption( opts, TOPOS, TOPODEF, 'topo' ) addDictOption( opts, LINKS, LINKDEF, 'link' ) opts.add_option( '--custom', type='string', default=None, help='read custom topo and node params from .py' + 'file' ) self.options, self.args = opts.parse_args() # We don't accept extra arguments after the options if self.args: opts.print_help() exit() def setCustom( self, name, value ): "Set custom parameters for MininetRunner." if name in ( 'topos', 'switches', 'hosts', 'controllers' ): # Update dictionaries param = name.upper() globals()[ param ].update( value ) elif name == 'validate': # Add custom validate function self.validate = value else: # Add or modify global variable or class globals()[ name ] = value def parseCustomFile( self, fileName ): "Parse custom file and add params before parsing cmd-line options." customs = {} if os.path.isfile( fileName ): execfile( fileName, customs, customs ) for name, val in customs.iteritems(): self.setCustom( name, val ) else: raise Exception( 'could not find custom file: %s' % fileName ) def importTopo( self ): print 'topo='+self.options.topo if self.options.topo == 'none': return self.newTopology() topo = buildTopo( TOPOS, self.options.topo ) link = customClass( LINKS, self.options.link ) importNet = Mininet(topo=topo, build=False, link=link) importNet.build() c = self.canvas rowIncrement = 100 currentY = 100 # Add Controllers print 'controllers:'+str(len(importNet.controllers)) for controller in importNet.controllers: name = controller.name x = self.controllerCount*100+100 self.addNode('Controller', self.controllerCount, float(x), float(currentY), name=name) icon = self.findWidgetByName(name) icon.bind('', self.do_controllerPopup ) ctrlr = { 'controllerType': 'ref', 'hostname': name, 'controllerProtocol': controller.protocol, 'remoteIP': controller.ip, 'remotePort': controller.port} self.controllers[name] = ctrlr currentY = currentY + rowIncrement # Add switches print 'switches:'+str(len(importNet.switches)) columnCount = 0 for switch in importNet.switches: name = switch.name self.switchOpts[name] = {} self.switchOpts[name]['nodeNum']=self.switchCount self.switchOpts[name]['hostname']=name self.switchOpts[name]['switchType']='default' self.switchOpts[name]['controllers']=[] x = columnCount*100+100 self.addNode('Switch', self.switchCount, float(x), float(currentY), name=name) icon = self.findWidgetByName(name) icon.bind('', self.do_switchPopup ) # Now link to controllers for controller in importNet.controllers: self.switchOpts[name]['controllers'].append(controller.name) dest = self.findWidgetByName(controller.name) dx, dy = c.coords( self.widgetToItem[ dest ] ) self.link = c.create_line(float(x), float(currentY), dx, dy, width=4, fill='red', dash=(6, 4, 2, 4), tag='link' ) c.itemconfig(self.link, tags=c.gettags(self.link)+('control',)) self.addLink( icon, dest, linktype='control' ) self.createControlLinkBindings() self.link = self.linkWidget = None if columnCount == 9: columnCount = 0 currentY = currentY + rowIncrement else: columnCount =columnCount+1 currentY = currentY + rowIncrement # Add hosts print 'hosts:'+str(len(importNet.hosts)) columnCount = 0 for host in importNet.hosts: name = host.name self.hostOpts[name] = {'sched':'host'} self.hostOpts[name]['nodeNum']=self.hostCount self.hostOpts[name]['hostname']=name self.hostOpts[name]['ip']=host.IP() x = columnCount*100+100 self.addNode('Host', self.hostCount, float(x), float(currentY), name=name) icon = self.findWidgetByName(name) icon.bind('', self.do_hostPopup ) if columnCount == 9: columnCount = 0 currentY = currentY + rowIncrement else: columnCount =columnCount+1 print 'links:'+str(len(topo.links())) #[('h1', 's3'), ('h2', 's4'), ('s3', 's4')] for link in topo.links(): print str(link) srcNode = link[0] src = self.findWidgetByName(srcNode) sx, sy = self.canvas.coords( self.widgetToItem[ src ] ) destNode = link[1] dest = self.findWidgetByName(destNode) dx, dy = self.canvas.coords( self.widgetToItem[ dest] ) params = topo.linkInfo( srcNode, destNode ) print 'Link Parameters='+str(params) self.link = self.canvas.create_line( sx, sy, dx, dy, width=4, fill='blue', tag='link' ) c.itemconfig(self.link, tags=c.gettags(self.link)+('data',)) self.addLink( src, dest, linkopts=params ) self.createDataLinkBindings() self.link = self.linkWidget = None importNet.stop() def miniEditImages(): "Create and return images for MiniEdit." # Image data. Git will be unhappy. However, the alternative # is to keep track of separate binary files, which is also # unappealing. return { 'Select': BitmapImage( file='/usr/include/X11/bitmaps/left_ptr' ), 'Switch': PhotoImage( data=r""" R0lGODlhLgAgAPcAAB2ZxGq61imex4zH3RWWwmK41tzd3vn9/jCiyfX7/Q6SwFay0gBlmtnZ2snJ yr+2tAuMu6rY6D6kyfHx8XO/2Uqszjmly6DU5uXz+JLN4uz3+kSrzlKx0ZeZm2K21BuYw67a6QB9 r+Xl5rW2uHW61On1+UGpzbrf6xiXwny9166vsMLCwgBdlAmHt8TFxgBwpNTs9C2hyO7t7ZnR5L/B w0yv0NXV1gBimKGjpABtoQBuoqKkpiaUvqWmqHbB2/j4+Pf39729vgB/sN7w9obH3hSMugCAsonJ 4M/q8wBglgB6rCCaxLO0tX7C2wBqniGMuABzpuPl5f3+/v39/fr6+r7i7vP6/ABonV621LLc6zWk yrq6uq6wskGlyUaszp6gohmYw8HDxKaoqn3E3LGztWGuzcnLzKmrrOnp6gB1qCaex1q001ewz+Dg 4QB3qrCxstHS09LR0dHR0s7Oz8zNzsfIyQaJuQB0pozL4YzI3re4uAGFtYDG3hOUwb+/wQB5rOvr 6wB2qdju9TWfxgBpniOcxeLj48vn8dvc3VKuzwB2qp6fos/Q0aXV6D+jxwB7rsXHyLu8vb27vCSc xSGZwxyZxH3A2RuUv0+uzz+ozCedxgCDtABnnABroKutr/7+/n2/2LTd6wBvo9bX2OLo6lGv0C6d xS6avjmmzLTR2uzr6m651RuXw4jF3CqfxySaxSadyAuRv9bd4cPExRiMuDKjyUWevNPS0sXl8BeY xKytr8G/wABypXvC23vD3O73+3vE3cvU2PH5+7S1t7q7vCGVwO/v8JfM3zymyyyZwrWys+Hy90Ki xK6qqg+TwBKXxMvMzaWtsK7U4jemzLXEygBxpW++2aCho97Z18bP0/T09fX29vb19ViuzdDR0crf 51qz01y00ujo6Onq6hCDs2Gpw3i71CqWv3S71nO92M/h52m207bJ0AN6rPPz9Nrh5Nvo7K/b6oTI 37Td7ABqneHi4yScxo/M4RiWwRqVwcro8n3B2lGoylStzszMzAAAACH5BAEAAP8ALAAAAAAuACAA Bwj/AP8JHEjw3wEkEY74WOjrQhUNBSNKnCjRSoYKCOwJcKWpEAACBFBRGEKxZMkDjRAg2OBlQyYL WhDEcOWxDwofv0zqHIhhDYIFC2p4MYFMS62ZaiYVWlJJAYIqO00KMlEjABYOQokaRbp0CYBKffpE iDpxSKYC1gqswToUmYVaCFyp6QrgwwcCscaSJZhgQYBeAdRyqFBhgwWkGyct8WoXRZ8Ph/YOxMOB CIUAHsBxwGQBAII1YwpMI5Brcd0PKFA4Q2ZFMgYteZqkwxyu1KQNJzQc+CdFCrxypyqdRoEPX6x7 ki/n2TfbAxtNRHYTVCWpWTRbuRoX7yMgZ9QSFQa0/7LU/BXygjIWXVOBTR2sxp7BxGpENgKbY+PR reqyIOKnOh0M445AjTjDCgrPSBNFKt9w8wMVU5g0Bg8kDAAKOutQAkNEQNBwDRAEeVEcAV6w84Ay KowQSRhmzNGAASIAYow2IP6DySPk8ANKCv1wINE2cpjxCUEgOIOPAKicQMMbKnhyhhg97HDNF4vs IEYkNkzwjwSP/PHIE2VIgIdEnxjAiBwNGIKGDKS8I0sw2VAzApNOQimGLlyMAIkDw2yhZTF/KKGE lxCEMtEPBtDhACQurLDCLkFIsoUeZLyRpx8OmEGHN3AEcU0HkFAhUDFulDroJvOU5M44iDjgDTQO 1P/hzRw2IFJPGw3AAY0LI/SAwxc7jEKQI2mkEUipRoxp0g821AMIGlG0McockMzihx5c1LkDDmSg UVAiafACRbGPVKDTFG3MYUYdLoThRxDE6DEMGUww8eQONGwTER9piFINFOPasaFJVIjTwC1xzOGP A3HUKoIMDTwJR4QRgdBOJzq8UM0Lj5QihU5ZdGMOCSSYUwYzAwwkDhNtUKTBOZ10koMOoohihDwm HZKPEDwb4fMe9An0g5Yl+SDKFTHnkMMLLQAjXUTxUCLEIyH0bIQAwuxVQhEMcEIIIUmHUEsWGCQg xQEaIFGAHV0+QnUIIWwyg2T/3MPLDQwwcAUhTjiswYsQl1SAxQKmbBJCIMe6ISjVmXwsWQKJEJJE 3l1/TY8O4wZyh8ZQ3IF4qX9cggTdAmEwCAMs3IB311fsDfbMGv97BxSBQBAP6QMN0QUhLCSRhOp5 e923zDpk/EIaRdyO+0C/eHBHEiz0vjrrfMfciSKD4LJ8RBEk88IN0ff+O/CEVEPLGK1tH1ECM7Dx RDWdcMLJFTpUQ44jfCyjvlShZNDE/0QAgT6ypr6AAAA7 """), 'LegacySwitch': PhotoImage( data=r""" R0lGODlhMgAYAPcAAAEBAXmDjbe4uAE5cjF7xwFWq2Sa0S9biSlrrdTW1k2Ly02a5xUvSQFHjmep 6bfI2Q5SlQIYLwFfvj6M3Jaan8fHyDuFzwFp0Vah60uU3AEiRhFgrgFRogFr10N9uTFrpytHYQFM mGWt9wIwX+bm5kaT4gtFgR1cnJPF9yt80CF0yAIMGHmp2c/P0AEoUb/P4Fei7qK4zgpLjgFkyQlf t1mf5jKD1WWJrQ86ZwFAgBhYmVOa4MPV52uv8y+A0iR3ywFbtUyX5ECI0Q1UmwIcOUGQ3RBXoQI0 aRJbpr3BxVeJvQUJDafH5wIlS2aq7xBmv52lr7fH12el5Wml3097ph1ru7vM3HCz91Ke6lid40KQ 4GSQvgQGClFnfwVJjszMzVCX3hljrdPT1AFLlBRnutPf6yd5zjeI2QE9eRBdrBNVl+3v70mV4ydf lwMVKwErVlul8AFChTGB1QE3bsTFxQImTVmAp0FjiUSM1k+b6QQvWQ1SlxMgLgFixEqU3xJhsgFT pn2Xs5OluZ+1yz1Xb6HN+Td9wy1zuYClykV5r0x2oeDh4qmvt8LDwxhuxRlLfyRioo2124mft9bi 71mDr7fT79nl8Z2hpQs9b7vN4QMQIOPj5XOPrU2Jx32z6xtvwzeBywFFikFnjwcPFa29yxJjuFmP xQFv3qGxwRc/Z8vb6wsRGBNqwqmpqTdvqQIbNQFPngMzZAEfP0mQ13mHlQFYsAFnznOXu2mPtQxj vQ1Vn4Ot1+/x8my0/CJgnxNNh8DT5CdJaWyx+AELFWmt8QxPkxBZpwMFB015pgFduGCNuyx7zdnZ 2WKm6h1xyOPp8aW70QtPkUmM0LrCyr/FyztljwFPm0OJzwFny7/L1xFjswE/e12i50iR2VR8o2Gf 3xszS2eTvz2BxSlloQdJiwMHDzF3u7bJ3T2I1WCp8+Xt80FokQFJklef6mORw2ap7SJ1y77Q47nN 3wFfu1Kb5cXJyxdhrdDR0wlNkTSF11Oa4yp4yQEuW0WQ3QIDBQI7dSH5BAEAAAAALAAAAAAyABgA Bwj/AAEIHDjKF6SDvhImPMHwhA6HOiLqUENRDYSLEIplxBcNHz4Z5GTI8BLKS5OBA1Ply2fDhxwf PlLITGFmmRkzP+DlVKHCmU9nnz45csSqKKsn9gileZKrVC4aRFACOGZu5UobNuRohRkzhc2b+36o qCaqrFmzZEV1ERBg3BOmMl5JZTBhwhm7ZyycYZnvJdeuNl21qkCHTiPDhxspTtKoQgUKCJ6wehMV 5QctWupeo6TkjOd8e1lmdQkTGbTTMaDFiDGINeskX6YhEicUiQa5A/kUKaFFwQ0oXzjZ8Tbcm3Hj irwpMtTSgg9QMJf5WEZ9375AiED19ImpSQSUB4Kw/8HFSMyiRWJaqG/xhf2X91+oCbmq1e/MFD/2 EcApVkWVJhp8J9AqsywQxDfAbLJJPAy+kMkL8shjxTkUnhOJZ5+JVp8cKfhwxwdf4fQLgG4MFAwW KOZRAxM81EAPPQvoE0QQfrDhx4399OMBMjz2yCMVivCoCAWXKLKMTPvoUYcsKwi0RCcwYCAlFjU0 A6OBM4pXAhsl8FYELYWFWZhiZCbRQgIC2AGTLy408coxAoEDx5wwtGPALTVg0E4NKC7gp4FsBKoA Ki8U+oIVmVih6DnZPMBMAlGwIARWOLiggSYC+ZNIOulwY4AkSZCyxaikbqHMqaeaIp4+rAaxQxBg 2P+IozuRzvLZIS4syYVAfMAhwhSC1EPCGoskIIYY9yS7Hny75OFnEIAGyiVvWkjjRxF11fXIG3WU KNA6wghDTCW88PKMJZOkm24Z7LarSjPtoIjFn1lKyyVmmBVhwRtvaDDMgFL0Eu4VhaiDwhXCXNFD D8QQw7ATEDsBw8RSxotFHs7CKJ60XWrRBj91EOGPQCA48c7J7zTjSTPctOzynjVkkYU+O9S8Axg4 Z6BzBt30003Ps+AhNB5C4PCGC5gKJMMTZJBRytOl/CH1HxvQkMbVVxujtdZGGKGL17rsEfYQe+xR zNnFcGQCv7LsKlAtp8R9Sgd0032BLXjPoPcMffTd3YcEgAMOxOBA1GJ4AYgXAMjiHDTgggveCgRI 3RfcnffefgcOeDKEG3444osDwgEspMNiTQhx5FoOShxcrrfff0uQjOycD+554qFzMHrpp4cwBju/ 5+CmVNbArnntndeCO+O689777+w0IH0o1P/TRJMohRA4EJwn47nyiocOSOmkn/57COxE3wD11Mfh fg45zCGyVF4Ufvvyze8ewv5jQK9++6FwXxzglwM0GPAfR8AeSo4gwAHCbxsQNCAa/kHBAVhwAHPI 4BE2eIRYeHAEIBwBP0Y4Qn41YWRSCQgAOw== """), 'LegacyRouter': PhotoImage( data=r""" R0lGODlhMgAYAPcAAAEBAXZ8gQNAgL29vQNctjl/xVSa4j1dfCF+3QFq1DmL3wJMmAMzZZW11dnZ 2SFrtyNdmTSO6gIZMUKa8gJVqEOHzR9Pf5W74wFjxgFx4jltn+np6Eyi+DuT6qKiohdtwwUPGWiq 6ymF4LHH3Rh11CV81kKT5AMoUA9dq1ap/mV0gxdXlytRdR1ptRNPjTt9vwNgvwJZsX+69gsXJQFH jTtjizF0tvHx8VOm9z2V736Dhz2N3QM2acPZ70qe8gFo0HS19wVRnTiR6hMpP0eP1i6J5iNlqAtg tktjfQFu3TNxryx4xAMTIzOE1XqAh1uf5SWC4AcfNy1XgQJny93n8a2trRh312Gt+VGm/AQIDTmB yAF37QJasydzvxM/ayF3zhdLf8zLywFdu4i56gFlyi2J4yV/1w8wUo2/8j+X8D2Q5Eee9jeR7Uia 7DpeggFt2QNPm97e3jRong9bpziH2DuT7aipqQoVICmG45vI9R5720eT4Q1hs1er/yVVhwJJktPh 70tfdbHP7Xev5xs5V7W1sz9jhz11rUVZcQ9WoCVVhQk7cRdtwWuw9QYOFyFHbSBnr0dznxtWkS18 zKfP9wwcLAMHCwFFiS5UeqGtuRNNiwMfPS1hlQMtWRE5XzGM5yhxusLCwCljnwMdOFWh7cve8pG/ 7Tlxp+Tr8g9bpXF3f0lheStrrYu13QEXLS1ppTV3uUuR1RMjNTF3vU2X4TZupwRSolNne4nB+T+L 2YGz4zJ/zYe99YGHjRdDcT95sx09XQldsgMLEwMrVc/X3yN3yQ1JhTRbggsdMQNfu9HPz6WlpW2t 7RctQ0GFyeHh4dvl8SBZklCb5kOO2kWR3Vmt/zdjkQIQHi90uvPz8wIVKBp42SV5zbfT7wtXpStV fwFWrBVvyTt3swFz5kGBv2+1/QlbrVFjdQM7d1+j54i67UmX51qn9i1vsy+D2TuR5zddhQsjOR1t u0GV6ghbsDVZf4+76RRisent8Xd9hQFBgwFNmwJLlcPDwwFr1z2T5yH5BAEAAAAALAAAAAAyABgA Bwj/AAEIHEiQYJY7Qwg9UsTplRIbENuxEiXJgpcz8e5YKsixY8Essh7JcbbOBwcOa1JOmJAmTY4c HeoIabJrCShI0XyB8YRso0eOjoAdWpciBZajJ1GuWcnSZY46Ed5N8hPATqEBoRB9gVJsxRlhPwHI 0kDkVywcRpGe9LF0adOnMpt8CxDnxg1o9lphKoEACoIvmlxxvHOKVg0n/Tzku2WoVoU2J1P6WNkS rtwADuxCG/MOjwgRUEIjGG3FhaOBzaThiDSCil27G8Isc3LLjZwXsA6YYJmDjhTMmseoKQIFDx7R oxHo2abnwygAlUj1mV6tWjlelEpRwfd6gzI7VeJQ/2vZoVaDUqigqftXpH0R46H9Kl++zUo4JnKq 9dGvv09RHFhcIUMe0NiFDyql0OJUHWywMc87TXRhhCRGiHAccvNZUR8JxpDTH38p9HEUFhxgMSAv jbBjQge8PSXEC6uo0IsHA6gAAShmgCbffNtsQwIJifhRHX/TpUUiSijlUk8AqgQixSwdNBjCa7CF oVggmEgCyRf01WcFCYvYUgB104k4YlK5HONEXXfpokYdMrXRAzMhmNINNNzB9p0T57AgyZckpKKP GFNgw06ZWKR10jTw6MAmFWj4AJcQQkQQwSefvFeGCemMIQggeaJywSQ/wgHOAmJskQEfWqBlFBEH 1P/QaGY3QOpDZXA2+A6m7hl3IRQKGDCIAj6iwE8yGKC6xbJv8IHNHgACQQybN2QiTi5NwdlBpZdi isd7vyanByOJ7CMGGRhgwE+qyy47DhnBPLDLEzLIAEQjBtChRmVPNWgpr+Be+Nc9icARww9TkIEu DAsQ0O7DzGIQzD2QdDEJHTsIAROc3F7qWQncyHPPHN5QQAAG/vjzw8oKp8sPPxDH3O44/kwBQzLB xBCMOTzzHEMMBMBARgJvZJBBEm/4k0ACKydMBgwYoKNNEjJXbTXE42Q9jtFIp8z0Dy1jQMA1AGzi z9VoW7310V0znYDTGMQgwUDXLDBO2nhvoTXbbyRk/XXL+pxWkAT8UJ331WsbnbTSK8MggDZhCTOM LQkcjvXeSPedAAw0nABWWARZIgEDfyTzxt15Z53BG1PEcEknrvgEelhZMDHKCTwI8EcQFHBBAAFc gGPLHwLwcMIo12Qxu0ABAQA7 """), 'Controller': PhotoImage( data=r""" R0lGODlhMAAwAPcAAAEBAWfNAYWFhcfHx+3t6/f390lJUaWlpfPz8/Hx72lpaZGRke/v77m5uc0B AeHh4e/v7WNjY3t7e5eXlyMjI4mJidPT0+3t7f///09PT7Ozs/X19fHx8ZWTk8HBwX9/fwAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAAALAAAAAAwADAA Bwj/AAEIHEiwoMGDCBMqXMiwocOHECNKnEixosWLGAEIeMCxo8ePHwVkBGABg8mTKFOmtDByAIYN MGPCRCCzQIENNzEMGOkBAwIKQIMKpYCgKAIHCDB4GNkAA4OnUJ9++CDhQ1QGFzA0GKkBA4GvYMOK BYtBA1cNaNOqXcuWq8q3b81m7Cqzbk2bMMu6/Tl0qFEEAZLKxdj1KlSqVA3rnet1rOOwiwmznUzZ LdzLJgdfpIv3pmebN2Pm1GyRbocNp1PLNMDaAM3Im1/alQk4gO28pCt2RdCBt+/eRg8IP1AUdmmf f5MrL56bYlcOvaP7Xo6Ag3HdGDho3869u/YE1507t+3AgLz58ujPMwg/sTBUCAzgy49PH0LW5u0x XFiwvz////5dcJ9bjxVIAHsSdUXAAgs2yOCDDn6FYEQaFGDgYxNCpEFfHHKIX4IDhCjiiCSS+CGF FlCmogYpcnVABTDGKGOMAlRQYwUHnKjhAjX2aOOPN8LImgAL6PiQBhLMqCSNAThQgQRGOqRBBD1W aaOVAggnQARRNqRBBxmEKeaYZIrZQZcMKbDiigqM5OabcMYp55x01ilnQAA7 """), 'Host': PhotoImage( data=r""" R0lGODlhIAAYAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A M/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zM AMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz /8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/ zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZ mZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkz ZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/ M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZ AGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA /2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPM zDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNm mTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMA ZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBm AAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0A ALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAA RAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3d1VVVURERCIiIhEREQAA ACH5BAEAAAAALAAAAAAgABgAAAiNAAH8G0iwoMGDCAcKTMiw4UBw BPXVm0ixosWLFvVBHFjPoUeC9Tb+6/jRY0iQ/8iVbHiS40CVKxG2 HEkQZsyCM0mmvGkw50uePUV2tEnOZkyfQA8iTYpTKNOgKJ+C3AhO p9SWVaVOfWj1KdauTL9q5UgVbFKsEjGqXVtP40NwcBnCjXtw7tx/ C8cSBBAQADs= """ ), 'OldSwitch': PhotoImage( data=r""" R0lGODlhIAAYAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A M/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zM AMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz /8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/ zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZ mZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkz ZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/ M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZ AGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA /2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPM zDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNm mTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMA ZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBm AAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0A ALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAA RAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3d1VVVURERCIiIhEREQAA ACH5BAEAAAAALAAAAAAgABgAAAhwAAEIHEiwoMGDCBMqXMiwocOH ECNKnEixosWB3zJq3Mixo0eNAL7xG0mypMmTKPl9Cznyn8uWL/m5 /AeTpsyYI1eKlBnO5r+eLYHy9Ck0J8ubPmPOrMmUpM6UUKMa/Ui1 6saLWLNq3cq1q9evYB0GBAA7 """ ), 'NetLink': PhotoImage( data=r""" R0lGODlhFgAWAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A M/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zM AMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz /8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/ zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZ mZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkz ZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/ M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZ AGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA /2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPM zDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNm mTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMA ZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBm AAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0A ALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAA RAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3d1VVVURERCIiIhEREQAA ACH5BAEAAAAALAAAAAAWABYAAAhIAAEIHEiwoEGBrhIeXEgwoUKG Cx0+hGhQoiuKBy1irChxY0GNHgeCDAlgZEiTHlFuVImRJUWXEGEy lBmxI8mSNknm1Dnx5sCAADs= """ ) } def addDictOption( opts, choicesDict, default, name, helpStr=None ): """Convenience function to add choices dicts to OptionParser. opts: OptionParser instance choicesDict: dictionary of valid choices, must include default default: default choice key name: long option name help: string""" if default not in choicesDict: raise Exception( 'Invalid default %s for choices dict: %s' % ( default, name ) ) if not helpStr: helpStr = ( '|'.join( sorted( choicesDict.keys() ) ) + '[,param=value...]' ) opts.add_option( '--' + name, type='string', default = default, help = helpStr ) if __name__ == '__main__': setLogLevel( 'info' ) app = MiniEdit() ### import topology if specified ### app.parseArgs() app.importTopo() app.mainloop() mininet-2.2.2/examples/mobility.py000077500000000000000000000101461306431124000171770ustar00rootroot00000000000000#!/usr/bin/python """ Simple example of Mobility with Mininet (aka enough rope to hang yourself.) We move a host from s1 to s2, s2 to s3, and then back to s1. Gotchas: The reference controller doesn't support mobility, so we need to manually flush the switch flow tables! Good luck! to-do: - think about wifi/hub behavior - think about clearing last hop - why doesn't that work? """ from mininet.net import Mininet from mininet.node import OVSSwitch from mininet.topo import LinearTopo from mininet.log import output, warn from random import randint class MobilitySwitch( OVSSwitch ): "Switch that can reattach and rename interfaces" def delIntf( self, intf ): "Remove (and detach) an interface" port = self.ports[ intf ] del self.ports[ intf ] del self.intfs[ port ] del self.nameToIntf[ intf.name ] def addIntf( self, intf, rename=False, **kwargs ): "Add (and reparent) an interface" OVSSwitch.addIntf( self, intf, **kwargs ) intf.node = self if rename: self.renameIntf( intf ) def attach( self, intf ): "Attach an interface and set its port" port = self.ports[ intf ] if port: if self.isOldOVS(): self.cmd( 'ovs-vsctl add-port', self, intf ) else: self.cmd( 'ovs-vsctl add-port', self, intf, '-- set Interface', intf, 'ofport_request=%s' % port ) self.validatePort( intf ) def validatePort( self, intf ): "Validate intf's OF port number" ofport = int( self.cmd( 'ovs-vsctl get Interface', intf, 'ofport' ) ) if ofport != self.ports[ intf ]: warn( 'WARNING: ofport for', intf, 'is actually', ofport, '\n' ) def renameIntf( self, intf, newname='' ): "Rename an interface (to its canonical name)" intf.ifconfig( 'down' ) if not newname: newname = '%s-eth%d' % ( self.name, self.ports[ intf ] ) intf.cmd( 'ip link set', intf, 'name', newname ) del self.nameToIntf[ intf.name ] intf.name = newname self.nameToIntf[ intf.name ] = intf intf.ifconfig( 'up' ) def moveIntf( self, intf, switch, port=None, rename=True ): "Move one of our interfaces to another switch" self.detach( intf ) self.delIntf( intf ) switch.addIntf( intf, port=port, rename=rename ) switch.attach( intf ) def printConnections( switches ): "Compactly print connected nodes to each switch" for sw in switches: output( '%s: ' % sw ) for intf in sw.intfList(): link = intf.link if link: intf1, intf2 = link.intf1, link.intf2 remote = intf1 if intf1.node != sw else intf2 output( '%s(%s) ' % ( remote.node, sw.ports[ intf ] ) ) output( '\n' ) def moveHost( host, oldSwitch, newSwitch, newPort=None ): "Move a host from old switch to new switch" hintf, sintf = host.connectionsTo( oldSwitch )[ 0 ] oldSwitch.moveIntf( sintf, newSwitch, port=newPort ) return hintf, sintf def mobilityTest(): "A simple test of mobility" print '* Simple mobility test' net = Mininet( topo=LinearTopo( 3 ), switch=MobilitySwitch ) print '* Starting network:' net.start() printConnections( net.switches ) print '* Testing network' net.pingAll() print '* Identifying switch interface for h1' h1, old = net.get( 'h1', 's1' ) for s in 2, 3, 1: new = net[ 's%d' % s ] port = randint( 10, 20 ) print '* Moving', h1, 'from', old, 'to', new, 'port', port hintf, sintf = moveHost( h1, old, new, newPort=port ) print '*', hintf, 'is now connected to', sintf print '* Clearing out old flows' for sw in net.switches: sw.dpctl( 'del-flows' ) print '* New network:' printConnections( net.switches ) print '* Testing connectivity:' net.pingAll() old = new net.stop() if __name__ == '__main__': mobilityTest() mininet-2.2.2/examples/multilink.py000077500000000000000000000015021306431124000173530ustar00rootroot00000000000000#!/usr/bin/python """ This is a simple example that demonstrates multiple links between nodes. """ from mininet.cli import CLI from mininet.log import setLogLevel from mininet.net import Mininet from mininet.topo import Topo def runMultiLink(): "Create and run multiple link network" topo = simpleMultiLinkTopo( n=2 ) net = Mininet( topo=topo ) net.start() CLI( net ) net.stop() class simpleMultiLinkTopo( Topo ): "Simple topology with multiple links" def __init__( self, n, **kwargs ): Topo.__init__( self, **kwargs ) h1, h2 = self.addHost( 'h1' ), self.addHost( 'h2' ) s1 = self.addSwitch( 's1' ) for _ in range( n ): self.addLink( s1, h1 ) self.addLink( s1, h2 ) if __name__ == '__main__': setLogLevel( 'info' ) runMultiLink() mininet-2.2.2/examples/multiping.py000077500000000000000000000042731306431124000173630ustar00rootroot00000000000000#!/usr/bin/python """ multiping.py: monitor multiple sets of hosts using ping This demonstrates how one may send a simple shell script to multiple hosts and monitor their output interactively for a period= of time. """ from mininet.net import Mininet from mininet.node import Node from mininet.topo import SingleSwitchTopo from mininet.log import setLogLevel from select import poll, POLLIN from time import time def chunks( l, n ): "Divide list l into chunks of size n - thanks Stackoverflow" return [ l[ i: i + n ] for i in range( 0, len( l ), n ) ] def startpings( host, targetips ): "Tell host to repeatedly ping targets" targetips = ' '.join( targetips ) # Simple ping loop cmd = ( 'while true; do ' ' for ip in %s; do ' % targetips + ' echo -n %s "->" $ip ' % host.IP() + ' `ping -c1 -w 1 $ip | grep packets` ;' ' sleep 1;' ' done; ' 'done &' ) print ( '*** Host %s (%s) will be pinging ips: %s' % ( host.name, host.IP(), targetips ) ) host.cmd( cmd ) def multiping( netsize, chunksize, seconds): "Ping subsets of size chunksize in net of size netsize" # Create network and identify subnets topo = SingleSwitchTopo( netsize ) net = Mininet( topo=topo ) net.start() hosts = net.hosts subnets = chunks( hosts, chunksize ) # Create polling object fds = [ host.stdout.fileno() for host in hosts ] poller = poll() for fd in fds: poller.register( fd, POLLIN ) # Start pings for subnet in subnets: ips = [ host.IP() for host in subnet ] #adding bogus to generate packet loss ips.append( '10.0.0.200' ) for host in subnet: startpings( host, ips ) # Monitor output endTime = time() + seconds while time() < endTime: readable = poller.poll(1000) for fd, _mask in readable: node = Node.outToNode[ fd ] print '%s:' % node.name, node.monitor().strip() # Stop pings for host in hosts: host.cmd( 'kill %while' ) net.stop() if __name__ == '__main__': setLogLevel( 'info' ) multiping( netsize=20, chunksize=4, seconds=10 ) mininet-2.2.2/examples/multipoll.py000077500000000000000000000046451306431124000173770ustar00rootroot00000000000000#!/usr/bin/python """ Simple example of sending output to multiple files and monitoring them """ from mininet.topo import SingleSwitchTopo from mininet.net import Mininet from mininet.log import setLogLevel from time import time from select import poll, POLLIN from subprocess import Popen, PIPE def monitorFiles( outfiles, seconds, timeoutms ): "Monitor set of files and return [(host, line)...]" devnull = open( '/dev/null', 'w' ) tails, fdToFile, fdToHost = {}, {}, {} for h, outfile in outfiles.iteritems(): tail = Popen( [ 'tail', '-f', outfile ], stdout=PIPE, stderr=devnull ) fd = tail.stdout.fileno() tails[ h ] = tail fdToFile[ fd ] = tail.stdout fdToHost[ fd ] = h # Prepare to poll output files readable = poll() for t in tails.values(): readable.register( t.stdout.fileno(), POLLIN ) # Run until a set number of seconds have elapsed endTime = time() + seconds while time() < endTime: fdlist = readable.poll(timeoutms) if fdlist: for fd, _flags in fdlist: f = fdToFile[ fd ] host = fdToHost[ fd ] # Wait for a line of output line = f.readline().strip() yield host, line else: # If we timed out, return nothing yield None, '' for t in tails.values(): t.terminate() devnull.close() # Not really necessary def monitorTest( N=3, seconds=3 ): "Run pings and monitor multiple hosts" topo = SingleSwitchTopo( N ) net = Mininet( topo ) net.start() hosts = net.hosts print "Starting test..." server = hosts[ 0 ] outfiles, errfiles = {}, {} for h in hosts: # Create and/or erase output files outfiles[ h ] = '/tmp/%s.out' % h.name errfiles[ h ] = '/tmp/%s.err' % h.name h.cmd( 'echo >', outfiles[ h ] ) h.cmd( 'echo >', errfiles[ h ] ) # Start pings h.cmdPrint('ping', server.IP(), '>', outfiles[ h ], '2>', errfiles[ h ], '&' ) print "Monitoring output for", seconds, "seconds" for h, line in monitorFiles( outfiles, seconds, timeoutms=500 ): if h: print '%s: %s' % ( h.name, line ) for h in hosts: h.cmd('kill %ping') net.stop() if __name__ == '__main__': setLogLevel('info') monitorTest() mininet-2.2.2/examples/multitest.py000077500000000000000000000020311306431124000173730ustar00rootroot00000000000000#!/usr/bin/python """ This example shows how to create a network and run multiple tests. For a more complicated test example, see udpbwtest.py. """ from mininet.cli import CLI from mininet.log import lg, info from mininet.net import Mininet from mininet.node import OVSKernelSwitch from mininet.topolib import TreeTopo def ifconfigTest( net ): "Run ifconfig on all hosts in net." hosts = net.hosts for host in hosts: info( host.cmd( 'ifconfig' ) ) if __name__ == '__main__': lg.setLogLevel( 'info' ) info( "*** Initializing Mininet and kernel modules\n" ) OVSKernelSwitch.setup() info( "*** Creating network\n" ) network = Mininet( TreeTopo( depth=2, fanout=2 ), switch=OVSKernelSwitch ) info( "*** Starting network\n" ) network.start() info( "*** Running ping test\n" ) network.pingAll() info( "*** Running ifconfig test\n" ) ifconfigTest( network ) info( "*** Starting CLI (type 'exit' to exit)\n" ) CLI( network ) info( "*** Stopping network\n" ) network.stop() mininet-2.2.2/examples/nat.py000077500000000000000000000010461306431124000161300ustar00rootroot00000000000000#!/usr/bin/python """ Example to create a Mininet topology and connect it to the internet via NAT """ from mininet.cli import CLI from mininet.log import lg from mininet.topolib import TreeNet if __name__ == '__main__': lg.setLogLevel( 'info') net = TreeNet( depth=1, fanout=4 ) # Add NAT connectivity net.addNAT().configDefault() net.start() print "*** Hosts are running and should have internet connectivity" print "*** Type 'exit' or control-D to shut down network" CLI( net ) # Shut down NAT net.stop() mininet-2.2.2/examples/natnet.py000077500000000000000000000036341306431124000166440ustar00rootroot00000000000000#!/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, **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() mininet-2.2.2/examples/numberedports.py000077500000000000000000000044321306431124000202410ustar00rootroot00000000000000#!/usr/bin/python """ Create a network with 5 hosts, numbered 1-4 and 9. Validate that the port numbers match to the interface name, and that the ovs ports match the mininet ports. """ from mininet.net import Mininet from mininet.node import Controller from mininet.log import setLogLevel, info, warn def validatePort( switch, intf ): "Validate intf's OF port number" ofport = int( switch.cmd( 'ovs-vsctl get Interface', intf, 'ofport' ) ) if ofport != switch.ports[ intf ]: warn( 'WARNING: ofport for', intf, 'is actually', ofport, '\n' ) return 0 else: return 1 def testPortNumbering(): """Test port numbering: Create a network with 5 hosts (using Mininet's mid-level API) and check that implicit and explicit port numbering works as expected.""" net = Mininet( controller=Controller ) info( '*** Adding controller\n' ) net.addController( 'c0' ) info( '*** Adding hosts\n' ) h1 = net.addHost( 'h1', ip='10.0.0.1' ) h2 = net.addHost( 'h2', ip='10.0.0.2' ) h3 = net.addHost( 'h3', ip='10.0.0.3' ) h4 = net.addHost( 'h4', ip='10.0.0.4' ) h5 = net.addHost( 'h5', ip='10.0.0.5' ) info( '*** Adding switch\n' ) s1 = net.addSwitch( 's1' ) info( '*** Creating links\n' ) # host 1-4 connect to ports 1-4 on the switch net.addLink( h1, s1 ) net.addLink( h2, s1 ) net.addLink( h3, s1 ) net.addLink( h4, s1 ) # specify a different port to connect host 5 to on the switch. net.addLink( h5, s1, port1=1, port2= 9) info( '*** Starting network\n' ) net.start() # print the interfaces and their port numbers info( '\n*** printing and validating the ports ' 'running on each interface\n' ) for intfs in s1.intfList(): if not intfs.name == "lo": info( intfs, ': ', s1.ports[intfs], '\n' ) info( 'Validating that', intfs, 'is actually on port', s1.ports[intfs], '... ' ) if validatePort( s1, intfs ): info( 'Validated.\n' ) print '\n' # test the network with pingall net.pingAll() print '\n' info( '*** Stopping network' ) net.stop() if __name__ == '__main__': setLogLevel( 'info' ) testPortNumbering() mininet-2.2.2/examples/popen.py000077500000000000000000000017771306431124000165020ustar00rootroot00000000000000#!/usr/bin/python """ This example monitors a number of hosts using host.popen() and pmonitor() """ from mininet.net import Mininet from mininet.node import CPULimitedHost from mininet.topo import SingleSwitchTopo from mininet.log import setLogLevel from mininet.util import custom, pmonitor def monitorhosts( hosts=5, sched='cfs' ): "Start a bunch of pings and monitor them using popen" mytopo = SingleSwitchTopo( hosts ) cpu = .5 / hosts myhost = custom( CPULimitedHost, cpu=cpu, sched=sched ) net = Mininet( topo=mytopo, host=myhost ) net.start() # Start a bunch of pings popens = {} last = net.hosts[ -1 ] for host in net.hosts: popens[ host ] = host.popen( "ping -c5 %s" % last.IP() ) last = host # Monitor them and print output for host, line in pmonitor( popens ): if host: print "<%s>: %s" % ( host.name, line.strip() ) # Done net.stop() if __name__ == '__main__': setLogLevel( 'info' ) monitorhosts( hosts=5 ) mininet-2.2.2/examples/popenpoll.py000077500000000000000000000016441306431124000173620ustar00rootroot00000000000000#!/usr/bin/python "Monitor multiple hosts using popen()/pmonitor()" from mininet.net import Mininet from mininet.topo import SingleSwitchTopo from mininet.util import pmonitor from time import time from signal import SIGINT def pmonitorTest( N=3, seconds=10 ): "Run pings and monitor multiple hosts using pmonitor" topo = SingleSwitchTopo( N ) net = Mininet( topo ) net.start() hosts = net.hosts print "Starting test..." server = hosts[ 0 ] popens = {} for h in hosts: popens[ h ] = h.popen('ping', server.IP() ) print "Monitoring output for", seconds, "seconds" endTime = time() + seconds for h, line in pmonitor( popens, timeoutms=500 ): if h: print '<%s>: %s' % ( h.name, line ), if time() >= endTime: for p in popens.values(): p.send_signal( SIGINT ) net.stop() if __name__ == '__main__': pmonitorTest() mininet-2.2.2/examples/scratchnet.py000077500000000000000000000037601306431124000175110ustar00rootroot00000000000000#!/usr/bin/python """ Build a simple network from scratch, using mininet primitives. This is more complicated than using the higher-level classes, but it exposes the configuration details and allows customization. For most tasks, the higher-level API will be preferable. """ from mininet.net import Mininet from mininet.node import Node from mininet.link import Link from mininet.log import setLogLevel, info from mininet.util import quietRun from time import sleep def scratchNet( cname='controller', cargs='-v ptcp:' ): "Create network from scratch using Open vSwitch." info( "*** Creating nodes\n" ) controller = Node( 'c0', inNamespace=False ) switch = Node( 's0', inNamespace=False ) h0 = Node( 'h0' ) h1 = Node( 'h1' ) info( "*** Creating links\n" ) Link( h0, switch ) Link( h1, switch ) info( "*** Configuring hosts\n" ) h0.setIP( '192.168.123.1/24' ) h1.setIP( '192.168.123.2/24' ) info( str( h0 ) + '\n' ) info( str( h1 ) + '\n' ) info( "*** Starting network using Open vSwitch\n" ) controller.cmd( cname + ' ' + cargs + '&' ) switch.cmd( 'ovs-vsctl del-br dp0' ) switch.cmd( 'ovs-vsctl add-br dp0' ) for intf in switch.intfs.values(): print switch.cmd( 'ovs-vsctl add-port dp0 %s' % intf ) # Note: controller and switch are in root namespace, and we # can connect via loopback interface switch.cmd( 'ovs-vsctl set-controller dp0 tcp:127.0.0.1:6633' ) info( '*** Waiting for switch to connect to controller' ) while 'is_connected' not in quietRun( 'ovs-vsctl show' ): sleep( 1 ) info( '.' ) info( '\n' ) info( "*** Running test\n" ) h0.cmdPrint( 'ping -c1 ' + h1.IP() ) info( "*** Stopping network\n" ) controller.cmd( 'kill %' + cname ) switch.cmd( 'ovs-vsctl del-br dp0' ) switch.deleteIntfs() info( '\n' ) if __name__ == '__main__': setLogLevel( 'info' ) info( '*** Scratch network demo (kernel datapath)\n' ) Mininet.init() scratchNet() mininet-2.2.2/examples/scratchnetuser.py000077500000000000000000000046271306431124000204130ustar00rootroot00000000000000#!/usr/bin/python """ Build a simple network from scratch, using mininet primitives. This is more complicated than using the higher-level classes, but it exposes the configuration details and allows customization. For most tasks, the higher-level API will be preferable. This version uses the user datapath and an explicit control network. """ from mininet.net import Mininet from mininet.node import Node from mininet.link import Link from mininet.log import setLogLevel, info def linkIntfs( node1, node2 ): "Create link from node1 to node2 and return intfs" link = Link( node1, node2 ) return link.intf1, link.intf2 def scratchNetUser( cname='controller', cargs='ptcp:' ): "Create network from scratch using user switch." # It's not strictly necessary for the controller and switches # to be in separate namespaces. For performance, they probably # should be in the root namespace. However, it's interesting to # see how they could work even if they are in separate namespaces. info( '*** Creating Network\n' ) controller = Node( 'c0' ) switch = Node( 's0') h0 = Node( 'h0' ) h1 = Node( 'h1' ) cintf, sintf = linkIntfs( controller, switch ) h0intf, sintf1 = linkIntfs( h0, switch ) h1intf, sintf2 = linkIntfs( h1, switch ) info( '*** Configuring control network\n' ) controller.setIP( '10.0.123.1/24', intf=cintf ) switch.setIP( '10.0.123.2/24', intf=sintf) info( '*** Configuring hosts\n' ) h0.setIP( '192.168.123.1/24', intf=h0intf ) h1.setIP( '192.168.123.2/24', intf=h1intf ) info( '*** Network state:\n' ) for node in controller, switch, h0, h1: info( str( node ) + '\n' ) info( '*** Starting controller and user datapath\n' ) controller.cmd( cname + ' ' + cargs + '&' ) switch.cmd( 'ifconfig lo 127.0.0.1' ) intfs = [ str( i ) for i in sintf1, sintf2 ] switch.cmd( 'ofdatapath -i ' + ','.join( intfs ) + ' ptcp: &' ) switch.cmd( 'ofprotocol tcp:' + controller.IP() + ' tcp:localhost &' ) info( '*** Running test\n' ) h0.cmdPrint( 'ping -c1 ' + h1.IP() ) info( '*** Stopping network\n' ) controller.cmd( 'kill %' + cname ) switch.cmd( 'kill %ofdatapath' ) switch.cmd( 'kill %ofprotocol' ) switch.deleteIntfs() info( '\n' ) if __name__ == '__main__': setLogLevel( 'info' ) info( '*** Scratch network demo (user datapath)\n' ) Mininet.init() scratchNetUser() mininet-2.2.2/examples/simpleperf.py000077500000000000000000000035401306431124000175150ustar00rootroot00000000000000#!/usr/bin/python """ Simple example of setting network and CPU parameters NOTE: link params limit BW, add latency, and loss. There is a high chance that pings WILL fail and that iperf will hang indefinitely if the TCP handshake fails to complete. """ from mininet.topo import Topo from mininet.net import Mininet from mininet.node import CPULimitedHost from mininet.link import TCLink from mininet.util import dumpNodeConnections from mininet.log import setLogLevel from sys import argv class SingleSwitchTopo(Topo): "Single switch connected to n hosts." def __init__(self, n=2, lossy=True, **opts): Topo.__init__(self, **opts) switch = self.addSwitch('s1') for h in range(n): # Each host gets 50%/n of system CPU host = self.addHost('h%s' % (h + 1), cpu=.5 / n) if lossy: # 10 Mbps, 5ms delay, 10% packet loss self.addLink(host, switch, bw=10, delay='5ms', loss=10, use_htb=True) else: # 10 Mbps, 5ms delay, no packet loss self.addLink(host, switch, bw=10, delay='5ms', loss=0, use_htb=True) def perfTest( lossy=True ): "Create network and run simple performance test" topo = SingleSwitchTopo( n=4, lossy=lossy ) net = Mininet( topo=topo, host=CPULimitedHost, link=TCLink, autoStaticArp=True ) net.start() print "Dumping host connections" dumpNodeConnections(net.hosts) print "Testing bandwidth between h1 and h4" h1, h4 = net.getNodeByName('h1', 'h4') net.iperf( ( h1, h4 ), l4Type='UDP' ) net.stop() if __name__ == '__main__': setLogLevel( 'info' ) # Prevent test_simpleperf from failing due to packet loss perfTest( lossy=( 'testmode' not in argv ) ) mininet-2.2.2/examples/sshd.py000077500000000000000000000057401306431124000163140ustar00rootroot00000000000000#!/usr/bin/python """ Create a network and start sshd(8) on each host. While something like rshd(8) would be lighter and faster, (and perfectly adequate on an in-machine network) the advantage of running sshd is that scripts can work unchanged on mininet and hardware. In addition to providing ssh access to hosts, this example demonstrates: - creating a convenience function to construct networks - connecting the host network to the root namespace - running server processes (sshd in this case) on hosts """ import sys from mininet.net import Mininet from mininet.cli import CLI from mininet.log import lg from mininet.node import Node from mininet.topolib import TreeTopo from mininet.util import waitListening def TreeNet( depth=1, fanout=2, **kwargs ): "Convenience function for creating tree networks." topo = TreeTopo( depth, fanout ) return Mininet( topo, **kwargs ) def connectToRootNS( network, switch, ip, routes ): """Connect hosts to root namespace via switch. Starts network. network: Mininet() network object switch: switch to connect to root namespace ip: IP address for root namespace node routes: host networks to route to""" # Create a node in root namespace and link to switch 0 root = Node( 'root', inNamespace=False ) intf = network.addLink( root, switch ).intf1 root.setIP( ip, intf=intf ) # Start network that now includes link to root namespace network.start() # Add routes from root ns to hosts for route in routes: root.cmd( 'route add -net ' + route + ' dev ' + str( intf ) ) def sshd( network, cmd='/usr/sbin/sshd', opts='-D', ip='10.123.123.1/32', routes=None, switch=None ): """Start a network, connect it to root ns, and run sshd on all hosts. ip: root-eth0 IP address in root namespace (10.123.123.1/32) routes: Mininet host networks to route to (10.0/24) switch: Mininet switch to connect to root namespace (s1)""" if not switch: switch = network[ 's1' ] # switch to use if not routes: routes = [ '10.0.0.0/24' ] connectToRootNS( network, switch, ip, routes ) for host in network.hosts: host.cmd( cmd + ' ' + opts + '&' ) print "*** Waiting for ssh daemons to start" for server in network.hosts: waitListening( server=server, port=22, timeout=5 ) print print "*** Hosts are running sshd at the following addresses:" print for host in network.hosts: print host.name, host.IP() print print "*** Type 'exit' or control-D to shut down network" CLI( network ) for host in network.hosts: host.cmd( 'kill %' + cmd ) network.stop() if __name__ == '__main__': lg.setLogLevel( 'info') net = TreeNet( depth=1, fanout=4 ) # get sshd args from the command line or use default args # useDNS=no -u0 to avoid reverse DNS lookup timeout argvopts = ' '.join( sys.argv[ 1: ] ) if len( sys.argv ) > 1 else ( '-D -o UseDNS=no -u0' ) sshd( net, opts=argvopts ) mininet-2.2.2/examples/test/000077500000000000000000000000001306431124000157475ustar00rootroot00000000000000mininet-2.2.2/examples/test/runner.py000077500000000000000000000023571306431124000176440ustar00rootroot00000000000000#!/usr/bin/env python """ Run all mininet.examples tests -v : verbose output -quick : skip tests that take more than ~30 seconds """ import unittest import os import sys from mininet.util import ensureRoot from mininet.clean import cleanup class MininetTestResult( unittest.TextTestResult ): def addFailure( self, test, err ): super( MininetTestResult, self ).addFailure( test, err ) cleanup() def addError( self,test, err ): super( MininetTestResult, self ).addError( test, err ) cleanup() class MininetTestRunner( unittest.TextTestRunner ): def _makeResult( self ): return MininetTestResult( self.stream, self.descriptions, self.verbosity ) def runTests( testDir, verbosity=1 ): "discover and run all tests in testDir" # ensure root and cleanup before starting tests ensureRoot() cleanup() # discover all tests in testDir testSuite = unittest.defaultTestLoader.discover( testDir ) # run tests MininetTestRunner( verbosity=verbosity ).run( testSuite ) if __name__ == '__main__': # get the directory containing example tests testDir = os.path.dirname( os.path.realpath( __file__ ) ) verbosity = 2 if '-v' in sys.argv else 1 runTests( testDir, verbosity ) mininet-2.2.2/examples/test/test_baresshd.py000077500000000000000000000040301306431124000211530ustar00rootroot00000000000000#!/usr/bin/env python """ Tests for baresshd.py """ import unittest import pexpect from mininet.clean import cleanup, sh class testBareSSHD( unittest.TestCase ): opts = [ 'Welcome to h1', pexpect.EOF, pexpect.TIMEOUT ] def connected( self ): "Log into ssh server, check banner, then exit" p = pexpect.spawn( 'ssh 10.0.0.1 -o StrictHostKeyChecking=no -i /tmp/ssh/test_rsa exit' ) while True: index = p.expect( self.opts ) if index == 0: return True else: return False def setUp( self ): # verify that sshd is not running self.assertFalse( self.connected() ) # create public key pair for testing sh( 'rm -rf /tmp/ssh' ) sh( 'mkdir /tmp/ssh' ) sh( "ssh-keygen -t rsa -P '' -f /tmp/ssh/test_rsa" ) sh( 'cat /tmp/ssh/test_rsa.pub >> /tmp/ssh/authorized_keys' ) # run example with custom sshd args cmd = ( 'python -m mininet.examples.baresshd ' '-o AuthorizedKeysFile=/tmp/ssh/authorized_keys ' '-o StrictModes=no' ) p = pexpect.spawn( cmd ) runOpts = [ 'You may now ssh into h1 at 10.0.0.1', 'after 5 seconds, h1 is not listening on port 22', pexpect.EOF, pexpect.TIMEOUT ] while True: index = p.expect( runOpts ) if index == 0: break else: self.tearDown() self.fail( 'sshd failed to start in host h1' ) def testSSH( self ): "Simple test to verify that we can ssh into h1" result = False # try to connect up to 3 times; sshd can take a while to start result = self.connected() self.assertTrue( result ) def tearDown( self ): # kill the ssh process sh( "ps aux | grep 'ssh.*Banner' | awk '{ print $2 }' | xargs kill" ) cleanup() # remove public key pair sh( 'rm -rf /tmp/ssh' ) if __name__ == '__main__': unittest.main() mininet-2.2.2/examples/test/test_bind.py000077500000000000000000000043361306431124000203050ustar00rootroot00000000000000#!/usr/bin/env python """ Tests for bind.py """ import unittest import pexpect class testBind( unittest.TestCase ): prompt = 'mininet>' def setUp( self ): self.net = pexpect.spawn( 'python -m mininet.examples.bind' ) self.net.expect( "Private Directories: \[([\w\s,'/]+)\]" ) self.directories = [] # parse directories from mn output for d in self.net.match.group(1).split(', '): self.directories.append( d.strip("'") ) self.net.expect( self.prompt ) self.assertTrue( len( self.directories ) > 0 ) def testCreateFile( self ): "Create a file, a.txt, in the first private directory and verify" fileName = 'a.txt' directory = self.directories[ 0 ] path = directory + '/' + fileName self.net.sendline( 'h1 touch %s; ls %s' % ( path, directory ) ) index = self.net.expect( [ fileName, self.prompt ] ) self.assertTrue( index == 0 ) self.net.expect( self.prompt ) self.net.sendline( 'h1 rm %s' % path ) self.net.expect( self.prompt ) def testIsolation( self ): "Create a file in two hosts and verify that contents are different" fileName = 'b.txt' directory = self.directories[ 0 ] path = directory + '/' + fileName contents = { 'h1' : '1', 'h2' : '2' } # Verify file doesn't exist, then write private copy of file for host in contents: value = contents[ host ] self.net.sendline( '%s cat %s' % ( host, path ) ) self.net.expect( 'No such file' ) self.net.expect( self.prompt ) self.net.sendline( '%s echo %s > %s' % ( host, value, path ) ) self.net.expect( self.prompt ) # Verify file contents for host in contents: value = contents[ host ] self.net.sendline( '%s cat %s' % ( host, path ) ) self.net.expect( value ) self.net.expect( self.prompt ) self.net.sendline( '%s rm %s' % ( host, path ) ) self.net.expect( self.prompt ) # TODO: need more tests def tearDown( self ): self.net.sendline( 'exit' ) self.net.wait() if __name__ == '__main__': unittest.main() mininet-2.2.2/examples/test/test_clusterSanity.py000077500000000000000000000011601306431124000222320ustar00rootroot00000000000000#!/usr/bin/env python ''' A simple sanity check test for cluster edition ''' import unittest import pexpect class clusterSanityCheck( unittest.TestCase ): prompt = 'mininet>' def testClusterPingAll( self ): p = pexpect.spawn( 'python -m mininet.examples.clusterSanity' ) p.expect( self.prompt ) p.sendline( 'pingall' ) p.expect ( '(\d+)% dropped' ) percent = int( p.match.group( 1 ) ) if p.match else -1 self.assertEqual( percent, 0 ) p.expect( self.prompt ) p.sendline( 'exit' ) p.wait() if __name__ == '__main__': unittest.main() mininet-2.2.2/examples/test/test_controllers.py000077500000000000000000000026671306431124000217440ustar00rootroot00000000000000#!/usr/bin/env python """ Tests for controllers.py and controllers2.py """ import unittest import pexpect class testControllers( unittest.TestCase ): prompt = 'mininet>' def connectedTest( self, name, cmap ): "Verify that switches are connected to the controller specified by cmap" p = pexpect.spawn( 'python -m %s' % name ) p.expect( self.prompt ) # but first a simple ping test p.sendline( 'pingall' ) p.expect ( '(\d+)% dropped' ) percent = int( p.match.group( 1 ) ) if p.match else -1 self.assertEqual( percent, 0 ) p.expect( self.prompt ) # verify connected controller for switch in cmap: p.sendline( 'sh ovs-vsctl get-controller %s' % switch ) p.expect( 'tcp:([\d.:]+)') actual = p.match.group(1) expected = cmap[ switch ] self.assertEqual( actual, expected ) p.expect( self.prompt ) p.sendline( 'exit' ) p.wait() def testControllers( self ): c0 = '127.0.0.1:6633' c1 = '127.0.0.1:6634' cmap = { 's1': c0, 's2': c1, 's3': c0 } self.connectedTest( 'mininet.examples.controllers', cmap ) def testControllers2( self ): c0 = '127.0.0.1:6633' c1 = '127.0.0.1:6634' cmap = { 's1': c0, 's2': c1 } self.connectedTest( 'mininet.examples.controllers2', cmap ) if __name__ == '__main__': unittest.main() mininet-2.2.2/examples/test/test_controlnet.py000077500000000000000000000026561306431124000215630ustar00rootroot00000000000000#!/usr/bin/env python """ Test for controlnet.py """ import unittest import pexpect class testControlNet( unittest.TestCase ): prompt = 'mininet>' def testPingall( self ): "Simple pingall test that verifies 0% packet drop in data network" p = pexpect.spawn( 'python -m mininet.examples.controlnet' ) p.expect( self.prompt ) p.sendline( 'pingall' ) p.expect ( '(\d+)% dropped' ) percent = int( p.match.group( 1 ) ) if p.match else -1 self.assertEqual( percent, 0 ) p.expect( self.prompt ) p.sendline( 'exit' ) p.wait() def testFailover( self ): "Kill controllers and verify that switch, s1, fails over properly" count = 1 p = pexpect.spawn( 'python -m mininet.examples.controlnet' ) p.expect( self.prompt ) lp = pexpect.spawn( 'tail -f /tmp/s1-ofp.log' ) lp.expect( 'tcp:\d+\.\d+\.\d+\.(\d+):\d+: connected' ) ip = int( lp.match.group( 1 ) ) self.assertEqual( count, ip ) count += 1 for c in [ 'c0', 'c1' ]: p.sendline( '%s ifconfig %s-eth0 down' % ( c, c) ) p.expect( self.prompt ) lp.expect( 'tcp:\d+\.\d+\.\d+\.(\d+):\d+: connected' ) ip = int( lp.match.group( 1 ) ) self.assertEqual( count, ip ) count += 1 p.sendline( 'exit' ) p.wait() if __name__ == '__main__': unittest.main() mininet-2.2.2/examples/test/test_cpu.py000077500000000000000000000025331306431124000201550ustar00rootroot00000000000000#!/usr/bin/env python """ Test for cpu.py results format: sched cpu received bits/sec cfs 50% 8.14e+09 cfs 40% 6.48e+09 cfs 30% 4.56e+09 cfs 20% 2.84e+09 cfs 10% 1.29e+09 """ import unittest import pexpect import sys class testCPU( unittest.TestCase ): prompt = 'mininet>' @unittest.skipIf( '-quick' in sys.argv, 'long test' ) def testCPU( self ): "Verify that CPU utilization is monotonically decreasing for each scheduler" p = pexpect.spawn( 'python -m mininet.examples.cpu', timeout=300 ) # matches each line from results( shown above ) opts = [ '([a-z]+)\t([\d\.]+)%\t([\d\.e\+]+)', pexpect.EOF ] scheds = [] while True: index = p.expect( opts ) if index == 0: sched = p.match.group( 1 ) cpu = float( p.match.group( 2 ) ) bw = float( p.match.group( 3 ) ) if sched not in scheds: scheds.append( sched ) else: self.assertTrue( bw < previous_bw, "%e should be less than %e\n" % ( bw, previous_bw ) ) previous_bw = bw else: break self.assertTrue( len( scheds ) > 0 ) if __name__ == '__main__': unittest.main() mininet-2.2.2/examples/test/test_emptynet.py000077500000000000000000000015031306431124000212270ustar00rootroot00000000000000#!/usr/bin/env python """ Test for emptynet.py """ import unittest import pexpect class testEmptyNet( unittest.TestCase ): prompt = 'mininet>' def testEmptyNet( self ): "Run simple CLI tests: pingall (verify 0% drop) and iperf (sanity)" p = pexpect.spawn( 'python -m mininet.examples.emptynet' ) p.expect( self.prompt ) # pingall test p.sendline( 'pingall' ) p.expect ( '(\d+)% dropped' ) percent = int( p.match.group( 1 ) ) if p.match else -1 self.assertEqual( percent, 0 ) p.expect( self.prompt ) # iperf test p.sendline( 'iperf' ) p.expect( "Results: \['[\d.]+ .bits/sec', '[\d.]+ .bits/sec'\]" ) p.expect( self.prompt ) p.sendline( 'exit' ) p.wait() if __name__ == '__main__': unittest.main() mininet-2.2.2/examples/test/test_hwintf.py000077500000000000000000000035161306431124000206670ustar00rootroot00000000000000#!/usr/bin/env python """ Test for hwintf.py """ import unittest import re import pexpect from mininet.log import setLogLevel from mininet.node import Node from mininet.link import Link class testHwintf( unittest.TestCase ): prompt = 'mininet>' def setUp( self ): self.h3 = Node( 't0', ip='10.0.0.3/8' ) self.n0 = Node( 't1', inNamespace=False ) Link( self.h3, self.n0 ) self.h3.configDefault() def testLocalPing( self ): "Verify connectivity between virtual hosts using pingall" p = pexpect.spawn( 'python -m mininet.examples.hwintf %s' % self.n0.intf() ) p.expect( self.prompt ) p.sendline( 'pingall' ) p.expect ( '(\d+)% dropped' ) percent = int( p.match.group( 1 ) ) if p.match else -1 self.assertEqual( percent, 0 ) p.expect( self.prompt ) p.sendline( 'exit' ) p.wait() def testExternalPing( self ): "Verify connnectivity between virtual host and virtual-physical 'external' host " p = pexpect.spawn( 'python -m mininet.examples.hwintf %s' % self.n0.intf() ) p.expect( self.prompt ) # test ping external to internal expectStr = '(\d+) packets transmitted, (\d+) received' m = re.search( expectStr, self.h3.cmd( 'ping -v -c 1 10.0.0.1' ) ) tx = m.group( 1 ) rx = m.group( 2 ) self.assertEqual( tx, rx ) # test ping internal to external p.sendline( 'h1 ping -c 1 10.0.0.3') p.expect( expectStr ) tx = p.match.group( 1 ) rx = p.match.group( 2 ) self.assertEqual( tx, rx ) p.expect( self.prompt ) p.sendline( 'exit' ) p.wait() def tearDown( self ): self.h3.terminate() self.n0.terminate() if __name__ == '__main__': setLogLevel( 'warning' ) unittest.main() mininet-2.2.2/examples/test/test_intfoptions.py000077500000000000000000000030151306431124000217360ustar00rootroot00000000000000#!/usr/bin/env python """ Test for intfOptions.py """ import unittest import pexpect import sys class testIntfOptions( unittest.TestCase ): def testIntfOptions( self ): "verify that intf.config is correctly limiting traffic" p = pexpect.spawn( 'python -m mininet.examples.intfoptions ' ) tolerance = .2 # plus or minus 20% opts = [ "Results: \['([\d\.]+) .bits/sec", "Results: \['10M', '([\d\.]+) .bits/sec", "h(\d+)->h(\d+): (\d)/(\d)," "rtt min/avg/max/mdev ([\d\.]+)/([\d\.]+)/([\d\.]+)/([\d\.]+) ms", pexpect.EOF ] while True: index = p.expect( opts, timeout=600 ) if index == 0: BW = 5 bw = float( p.match.group( 1 ) ) self.assertGreaterEqual( bw, BW * ( 1 - tolerance ) ) self.assertLessEqual( bw, BW * ( 1 + tolerance ) ) elif index == 1: BW = 10 measuredBw = float( p.match.group( 1 ) ) loss = ( measuredBw / BW ) * 100 self.assertGreaterEqual( loss, 50 * ( 1 - tolerance ) ) self.assertLessEqual( loss, 50 * ( 1 + tolerance ) ) elif index == 2: delay = float( p.match.group( 6 ) ) self.assertGreaterEqual( delay, 15 * ( 1 - tolerance ) ) self.assertLessEqual( delay, 15 * ( 1 + tolerance ) ) else: break if __name__ == '__main__': unittest.main() mininet-2.2.2/examples/test/test_limit.py000077500000000000000000000021611306431124000205010ustar00rootroot00000000000000#!/usr/bin/env python """ Test for limit.py """ import unittest import pexpect import sys class testLimit( unittest.TestCase ): @unittest.skipIf( '-quick' in sys.argv, 'long test' ) def testLimit( self ): "Verify that CPU limits are within a 2% tolerance of limit for each scheduler" p = pexpect.spawn( 'python -m mininet.examples.limit' ) opts = [ '\*\*\* Testing network ([\d\.]+) Mbps', '\*\*\* Results: \[([\d\., ]+)\]', pexpect.EOF ] count = 0 bw = 0 tolerance = 2 while True: index = p.expect( opts ) if index == 0: bw = float( p.match.group( 1 ) ) count += 1 elif index == 1: results = p.match.group( 1 ) for x in results.split( ',' ): result = float( x ) self.assertTrue( result < bw + tolerance ) self.assertTrue( result > bw - tolerance ) else: break self.assertTrue( count > 0 ) if __name__ == '__main__': unittest.main() mininet-2.2.2/examples/test/test_linearbandwidth.py000077500000000000000000000031771306431124000225320ustar00rootroot00000000000000#!/usr/bin/env python """ Test for linearbandwidth.py """ import unittest import pexpect import sys class testLinearBandwidth( unittest.TestCase ): @unittest.skipIf( '-quick' in sys.argv, 'long test' ) def testLinearBandwidth( self ): "Verify that bandwidth is monotonically decreasing as # of hops increases" p = pexpect.spawn( 'python -m mininet.examples.linearbandwidth' ) count = 0 opts = [ '\*\*\* Linear network results', '(\d+)\s+([\d\.]+) (.bits)', pexpect.EOF ] while True: index = p.expect( opts, timeout=600 ) if index == 0: count += 1 elif index == 1: n = int( p.match.group( 1 ) ) bw = float( p.match.group( 2 ) ) unit = p.match.group( 3 ) if unit[ 0 ] == 'K': bw *= 10 ** 3 elif unit[ 0 ] == 'M': bw *= 10 ** 6 elif unit[ 0 ] == 'G': bw *= 10 ** 9 # check that we have a previous result to compare to if n != 1: info = ( 'bw: %.2e bits/s across %d switches, ' 'previous: %.2e bits/s across %d switches' % ( bw, n, previous_bw, previous_n ) ) self.assertTrue( bw < previous_bw, info ) previous_bw, previous_n = bw, n else: break # verify that we received results from at least one switch self.assertTrue( count > 0 ) if __name__ == '__main__': unittest.main() mininet-2.2.2/examples/test/test_linuxrouter.py000066400000000000000000000027761306431124000217740ustar00rootroot00000000000000#!/usr/bin/env python """ Test for linuxrouter.py """ import unittest import pexpect from mininet.util import quietRun class testLinuxRouter( unittest.TestCase ): prompt = 'mininet>' def testPingall( self ): "Test connectivity between hosts" p = pexpect.spawn( 'python -m mininet.examples.linuxrouter' ) p.expect( self.prompt ) p.sendline( 'pingall' ) p.expect ( '(\d+)% dropped' ) percent = int( p.match.group( 1 ) ) if p.match else -1 p.expect( self.prompt ) p.sendline( 'exit' ) p.wait() self.assertEqual( percent, 0 ) def testRouterPing( self ): "Test connectivity from h1 to router" p = pexpect.spawn( 'python -m mininet.examples.linuxrouter' ) p.expect( self.prompt ) p.sendline( 'h1 ping -c 1 r0' ) p.expect ( '(\d+)% packet loss' ) percent = int( p.match.group( 1 ) ) if p.match else -1 p.expect( self.prompt ) p.sendline( 'exit' ) p.wait() self.assertEqual( percent, 0 ) def testTTL( self ): "Verify that the TTL is decremented" p = pexpect.spawn( 'python -m mininet.examples.linuxrouter' ) p.expect( self.prompt ) p.sendline( 'h1 ping -c 1 h2' ) p.expect ( 'ttl=(\d+)' ) ttl = int( p.match.group( 1 ) ) if p.match else -1 p.expect( self.prompt ) p.sendline( 'exit' ) p.wait() self.assertEqual( ttl, 63 ) # 64 - 1 if __name__ == '__main__': unittest.main() mininet-2.2.2/examples/test/test_mobility.py000077500000000000000000000007301306431124000212130ustar00rootroot00000000000000#!/usr/bin/env python """ Test for mobility.py """ import unittest from subprocess import check_output class testMobility( unittest.TestCase ): def testMobility( self ): "Run the example and verify its 4 ping results" cmd = 'python -m mininet.examples.mobility 2>&1' grep = ' | grep -c " 0% dropped" ' result = check_output( cmd + grep, shell=True ) assert int( result ) == 4 if __name__ == '__main__': unittest.main() mininet-2.2.2/examples/test/test_multilink.py000077500000000000000000000030431306431124000213730ustar00rootroot00000000000000#!/usr/bin/env python ''' Test for multiple links between nodes validates mininet interfaces against systems interfaces ''' import unittest import pexpect class testMultiLink( unittest.TestCase ): prompt = 'mininet>' def testMultiLink(self): p = pexpect.spawn( 'python -m mininet.examples.multilink' ) p.expect( self.prompt ) p.sendline( 'intfs' ) p.expect( 's(\d): lo' ) intfsOutput = p.before # parse interfaces from mininet intfs, and store them in a list hostToIntfs = intfsOutput.split( '\r\n' )[ 1:3 ] intfList = [] for hostToIntf in hostToIntfs: intfList += [ intf for intf in hostToIntf.split()[1].split(',') ] # get interfaces from system by running ifconfig on every host sysIntfList = [] opts = [ 'h(\d)-eth(\d)', self.prompt ] p.expect( self.prompt ) p.sendline( 'h1 ifconfig' ) while True: p.expect( opts ) if p.after == self.prompt: break sysIntfList.append( p.after ) p.sendline( 'h2 ifconfig' ) while True: p.expect( opts ) if p.after == self.prompt: break sysIntfList.append( p.after ) failMsg = ( 'The systems interfaces and mininet interfaces\n' 'are not the same' ) self.assertEqual( sysIntfList, intfList, msg=failMsg ) p.sendline( 'exit' ) p.wait() if __name__ == '__main__': unittest.main() mininet-2.2.2/examples/test/test_multiping.py000077500000000000000000000030731306431124000213760ustar00rootroot00000000000000#!/usr/bin/env python """ Test for multiping.py """ import unittest import pexpect from collections import defaultdict class testMultiPing( unittest.TestCase ): def testMultiPing( self ): """Verify that each target is pinged at least once, and that pings to 'real' targets are successful and unknown targets fail""" p = pexpect.spawn( 'python -m mininet.examples.multiping' ) opts = [ "Host (h\d+) \(([\d.]+)\) will be pinging ips: ([\d\. ]+)", "(h\d+): ([\d.]+) -> ([\d.]+) \d packets transmitted, (\d) received", pexpect.EOF ] pings = defaultdict( list ) while True: index = p.expect( opts ) if index == 0: name = p.match.group(1) ip = p.match.group(2) targets = p.match.group(3).split() pings[ name ] += targets elif index == 1: name = p.match.group(1) ip = p.match.group(2) target = p.match.group(3) received = int( p.match.group(4) ) if target == '10.0.0.200': self.assertEqual( received, 0 ) else: self.assertEqual( received, 1 ) try: pings[ name ].remove( target ) except: pass else: break self.assertTrue( len( pings ) > 0 ) for t in pings.values(): self.assertEqual( len( t ), 0 ) if __name__ == '__main__': unittest.main() mininet-2.2.2/examples/test/test_multipoll.py000077500000000000000000000021211306431124000214000ustar00rootroot00000000000000#!/usr/bin/env python """ Test for multipoll.py """ import unittest import pexpect class testMultiPoll( unittest.TestCase ): def testMultiPoll( self ): "Verify that we receive one ping per second per host" p = pexpect.spawn( 'python -m mininet.examples.multipoll' ) opts = [ "\*\*\* (h\d) :" , "(h\d+): \d+ bytes from", "Monitoring output for (\d+) seconds", pexpect.EOF ] pings = {} while True: index = p.expect( opts ) if index == 0: name = p.match.group( 1 ) pings[ name ] = 0 elif index == 1: name = p.match.group( 1 ) pings[ name ] += 1 elif index == 2: seconds = int( p.match.group( 1 ) ) else: break self.assertTrue( len( pings ) > 0 ) # make sure we have received at least one ping per second for count in pings.values(): self.assertTrue( count >= seconds ) if __name__ == '__main__': unittest.main() mininet-2.2.2/examples/test/test_multitest.py000077500000000000000000000014461306431124000214220ustar00rootroot00000000000000#!/usr/bin/env python """ Test for multitest.py """ import unittest import pexpect class testMultiTest( unittest.TestCase ): prompt = 'mininet>' def testMultiTest( self ): "Verify pingall (0% dropped) and hX-eth0 interface for each host (ifconfig)" p = pexpect.spawn( 'python -m mininet.examples.multitest' ) p.expect( '(\d+)% dropped' ) dropped = int( p.match.group( 1 ) ) self.assertEqual( dropped, 0 ) ifCount = 0 while True: index = p.expect( [ 'h\d-eth0', self.prompt ] ) if index == 0: ifCount += 1 elif index == 1: p.sendline( 'exit' ) break p.wait() self.assertEqual( ifCount, 4 ) if __name__ == '__main__': unittest.main() mininet-2.2.2/examples/test/test_nat.py000077500000000000000000000015261306431124000201510ustar00rootroot00000000000000#!/usr/bin/env python """ Test for nat.py """ import unittest import pexpect from mininet.util import quietRun destIP = '8.8.8.8' # Google DNS class testNAT( unittest.TestCase ): prompt = 'mininet>' @unittest.skipIf( '0 received' in quietRun( 'ping -c 1 %s' % destIP ), 'Destination IP is not reachable' ) def testNAT( self ): "Attempt to ping an IP on the Internet and verify 0% packet loss" p = pexpect.spawn( 'python -m mininet.examples.nat' ) p.expect( self.prompt ) p.sendline( 'h1 ping -c 1 %s' % destIP ) p.expect ( '(\d+)% packet loss' ) percent = int( p.match.group( 1 ) ) if p.match else -1 p.expect( self.prompt ) p.sendline( 'exit' ) p.wait() self.assertEqual( percent, 0 ) if __name__ == '__main__': unittest.main() mininet-2.2.2/examples/test/test_natnet.py000066400000000000000000000034431306431124000206550ustar00rootroot00000000000000#!/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() mininet-2.2.2/examples/test/test_numberedports.py000077500000000000000000000033201306431124000222520ustar00rootroot00000000000000#!/usr/bin/env python """ Test for numberedports.py """ import unittest import pexpect from collections import defaultdict from mininet.node import OVSSwitch class testNumberedports( unittest.TestCase ): @unittest.skipIf( OVSSwitch.setup() or OVSSwitch.isOldOVS(), "old version of OVS" ) def testConsistency( self ): """verify consistency between mininet and ovs ports""" p = pexpect.spawn( 'python -m mininet.examples.numberedports' ) opts = [ 'Validating that s1-eth\d is actually on port \d ... Validated.', 'Validating that s1-eth\d is actually on port \d ... WARNING', pexpect.EOF ] correct_ports = True count = 0 while True: index = p.expect( opts ) if index == 0: count += 1 elif index == 1: correct_ports = False elif index == 2: self.assertNotEqual( 0, count ) break self.assertTrue( correct_ports ) def testNumbering( self ): """verify that all of the port numbers are printed correctly and consistent with their interface""" p = pexpect.spawn( 'python -m mininet.examples.numberedports' ) opts = [ 's1-eth(\d+) : (\d+)', pexpect.EOF ] count_intfs = 0 while True: index = p.expect( opts ) if index == 0: count_intfs += 1 intfport = p.match.group( 1 ) ofport = p.match.group( 2 ) self.assertEqual( intfport, ofport ) elif index == 1: break self.assertNotEqual( 0, count_intfs ) if __name__ == '__main__': unittest.main() mininet-2.2.2/examples/test/test_popen.py000077500000000000000000000024521306431124000205070ustar00rootroot00000000000000#!/usr/bin/env python """ Test for popen.py and popenpoll.py """ import unittest import pexpect class testPopen( unittest.TestCase ): def pingTest( self, name ): "Verify that there are no dropped packets for each host" p = pexpect.spawn( 'python -m %s' % name ) opts = [ "<(h\d+)>: PING ", "<(h\d+)>: (\d+) packets transmitted, (\d+) received", pexpect.EOF ] pings = {} while True: index = p.expect( opts ) if index == 0: name = p.match.group(1) pings[ name ] = 0 elif index == 1: name = p.match.group(1) transmitted = p.match.group(2) received = p.match.group(3) # verify no dropped packets self.assertEqual( received, transmitted ) pings[ name ] += 1 else: break self.assertTrue( len(pings) > 0 ) # verify that each host has gotten results for count in pings.values(): self.assertEqual( count, 1 ) def testPopen( self ): self.pingTest( 'mininet.examples.popen' ) def testPopenPoll( self ): self.pingTest( 'mininet.examples.popenpoll' ) if __name__ == '__main__': unittest.main() mininet-2.2.2/examples/test/test_scratchnet.py000077500000000000000000000012241306431124000215200ustar00rootroot00000000000000#!/usr/bin/env python """ Test for scratchnet.py """ import unittest import pexpect class testScratchNet( unittest.TestCase ): opts = [ "1 packets transmitted, 1 received, 0% packet loss", pexpect.EOF ] def pingTest( self, name ): "Verify that no ping packets were dropped" p = pexpect.spawn( 'python -m %s' % name ) index = p.expect( self.opts, timeout=120 ) self.assertEqual( index, 0 ) def testPingKernel( self ): self.pingTest( 'mininet.examples.scratchnet' ) def testPingUser( self ): self.pingTest( 'mininet.examples.scratchnetuser' ) if __name__ == '__main__': unittest.main() mininet-2.2.2/examples/test/test_simpleperf.py000077500000000000000000000017201306431124000215310ustar00rootroot00000000000000#!/usr/bin/env python """ Test for simpleperf.py """ import unittest import pexpect import sys from mininet.log import setLogLevel from mininet.examples.simpleperf import SingleSwitchTopo class testSimplePerf( unittest.TestCase ): @unittest.skipIf( '-quick' in sys.argv, 'long test' ) def testE2E( self ): "Run the example and verify iperf results" # 10 Mb/s, plus or minus 20% tolerance BW = 10 TOLERANCE = .2 p = pexpect.spawn( 'python -m mininet.examples.simpleperf testmode' ) # check iperf results p.expect( "Results: \['10M', '([\d\.]+) .bits/sec", timeout=480 ) measuredBw = float( p.match.group( 1 ) ) lowerBound = BW * ( 1 - TOLERANCE ) upperBound = BW + ( 1 + TOLERANCE ) self.assertGreaterEqual( measuredBw, lowerBound ) self.assertLessEqual( measuredBw, upperBound ) p.wait() if __name__ == '__main__': setLogLevel( 'warning' ) unittest.main() mininet-2.2.2/examples/test/test_sshd.py000077500000000000000000000034751306431124000203350ustar00rootroot00000000000000#!/usr/bin/env python """ Test for sshd.py """ import unittest import pexpect from mininet.clean import sh class testSSHD( unittest.TestCase ): opts = [ '\(yes/no\)\?', 'refused', 'Welcome|\$|#', pexpect.EOF, pexpect.TIMEOUT ] def connected( self, ip ): "Log into ssh server, check banner, then exit" # Note: this test will fail if "Welcome" is not in the sshd banner # and '#'' or '$'' are not in the prompt p = pexpect.spawn( 'ssh -i /tmp/ssh/test_rsa %s' % ip, timeout=10 ) while True: index = p.expect( self.opts ) if index == 0: print p.match.group(0) p.sendline( 'yes' ) elif index == 1: return False elif index == 2: p.sendline( 'exit' ) p.wait() return True else: return False def setUp( self ): # create public key pair for testing sh( 'rm -rf /tmp/ssh' ) sh( 'mkdir /tmp/ssh' ) sh( "ssh-keygen -t rsa -P '' -f /tmp/ssh/test_rsa" ) sh( 'cat /tmp/ssh/test_rsa.pub >> /tmp/ssh/authorized_keys' ) cmd = ( 'python -m mininet.examples.sshd -D ' '-o AuthorizedKeysFile=/tmp/ssh/authorized_keys ' '-o StrictModes=no -o UseDNS=no -u0' ) # run example with custom sshd args self.net = pexpect.spawn( cmd ) self.net.expect( 'mininet>' ) def testSSH( self ): "Verify that we can ssh into all hosts (h1 to h4)" for h in range( 1, 5 ): self.assertTrue( self.connected( '10.0.0.%d' % h ) ) def tearDown( self ): self.net.sendline( 'exit' ) self.net.wait() # remove public key pair sh( 'rm -rf /tmp/ssh' ) if __name__ == '__main__': unittest.main() mininet-2.2.2/examples/test/test_tree1024.py000077500000000000000000000016141306431124000206330ustar00rootroot00000000000000#!/usr/bin/env python """ Test for tree1024.py """ import unittest import pexpect import sys class testTree1024( unittest.TestCase ): prompt = 'mininet>' @unittest.skipIf( '-quick' in sys.argv, 'long test' ) def testTree1024( self ): "Run the example and do a simple ping test from h1 to h1024" p = pexpect.spawn( 'python -m mininet.examples.tree1024' ) p.expect( self.prompt, timeout=6000 ) # it takes awhile to set up p.sendline( 'h1 ping -c 20 h1024' ) p.expect ( '(\d+)% packet loss' ) packetLossPercent = int( p.match.group( 1 ) ) if p.match else -1 p.expect( self.prompt ) p.sendline( 'exit' ) p.wait() # Tolerate slow startup on some systems - we should revisit this # and determine the root cause. self.assertLess( packetLossPercent, 60 ) if __name__ == '__main__': unittest.main() mininet-2.2.2/examples/test/test_treeping64.py000077500000000000000000000015141306431124000213530ustar00rootroot00000000000000#!/usr/bin/env python """ Test for treeping64.py """ import unittest import pexpect import sys class testTreePing64( unittest.TestCase ): prompt = 'mininet>' @unittest.skipIf( '-quick' in sys.argv, 'long test' ) def testTreePing64( self ): "Run the example and verify ping results" p = pexpect.spawn( 'python -m mininet.examples.treeping64' ) p.expect( 'Tree network ping results:', timeout=6000 ) count = 0 while True: index = p.expect( [ '(\d+)% packet loss', pexpect.EOF ] ) if index == 0: percent = int( p.match.group( 1 ) ) if p.match else -1 self.assertEqual( percent, 0 ) count += 1 else: break self.assertTrue( count > 0 ) if __name__ == '__main__': unittest.main() mininet-2.2.2/examples/test/test_vlanhost.py000066400000000000000000000030031306431124000212120ustar00rootroot00000000000000#!/usr/bin/env python """ Test for vlanhost.py """ import unittest import pexpect import sys from mininet.util import quietRun class testVLANHost( unittest.TestCase ): prompt = 'mininet>' @unittest.skipIf( '-quick' in sys.argv, 'long test' ) def testVLANTopo( self ): "Test connectivity (or lack thereof) between hosts in VLANTopo" p = pexpect.spawn( 'python -m mininet.examples.vlanhost' ) p.expect( self.prompt ) p.sendline( 'pingall 1' ) #ping timeout=1 p.expect( '(\d+)% dropped', timeout=30 ) # there should be 24 failed pings percent = int( p.match.group( 1 ) ) if p.match else -1 p.expect( self.prompt ) p.sendline( 'exit' ) p.wait() self.assertEqual( percent, 80 ) def testSpecificVLAN( self ): "Test connectivity between hosts on a specific VLAN" vlan = 1001 p = pexpect.spawn( 'python -m mininet.examples.vlanhost %d' % vlan ) p.expect( self.prompt ) p.sendline( 'h1 ping -c 1 h2' ) p.expect ( '(\d+)% packet loss' ) percent = int( p.match.group( 1 ) ) if p.match else -1 p.expect( self.prompt ) p.sendline( 'h1 ifconfig' ) i = p.expect( ['h1-eth0.%d' % vlan, pexpect.TIMEOUT ], timeout=2 ) p.expect( self.prompt ) p.sendline( 'exit' ) p.wait() self.assertEqual( percent, 0 ) # no packet loss on ping self.assertEqual( i, 0 ) # check vlan intf is present if __name__ == '__main__': unittest.main() mininet-2.2.2/examples/tree1024.py000077500000000000000000000010121306431124000166050ustar00rootroot00000000000000#!/usr/bin/python """ Create a 1024-host network, and run the CLI on it. If this fails because of kernel limits, you may have to adjust them, e.g. by adding entries to /etc/sysctl.conf and running sysctl -p. Check util/sysctl_addon. """ from mininet.cli import CLI from mininet.log import setLogLevel from mininet.node import OVSSwitch from mininet.topolib import TreeNet if __name__ == '__main__': setLogLevel( 'info' ) network = TreeNet( depth=2, fanout=32, switch=OVSSwitch ) network.run( CLI, network ) mininet-2.2.2/examples/treeping64.py000077500000000000000000000016661306431124000173450ustar00rootroot00000000000000#!/usr/bin/python "Create a 64-node tree network, and test connectivity using ping." from mininet.log import setLogLevel from mininet.node import UserSwitch, OVSKernelSwitch # , KernelSwitch from mininet.topolib import TreeNet def treePing64(): "Run ping test on 64-node tree networks." results = {} switches = { # 'reference kernel': KernelSwitch, 'reference user': UserSwitch, 'Open vSwitch kernel': OVSKernelSwitch } for name in switches: print "*** Testing", name, "datapath" switch = switches[ name ] network = TreeNet( depth=2, fanout=8, switch=switch ) result = network.run( network.pingAll ) results[ name ] = result print print "*** Tree network ping results:" for name in switches: print "%s: %d%% packet loss" % ( name, results[ name ] ) print if __name__ == '__main__': setLogLevel( 'info' ) treePing64() mininet-2.2.2/examples/vlanhost.py000077500000000000000000000071371306431124000172130ustar00rootroot00000000000000#!/usr/bin/env python """ vlanhost.py: Host subclass that uses a VLAN tag for the default interface. Dependencies: This class depends on the "vlan" package $ sudo apt-get install vlan Usage (example uses VLAN ID=1000): From the command line: sudo mn --custom vlanhost.py --host vlan,vlan=1000 From a script (see exampleUsage function below): from functools import partial from vlanhost import VLANHost .... host = partial( VLANHost, vlan=1000 ) net = Mininet( host=host, ... ) Directly running this script: sudo python vlanhost.py 1000 """ from mininet.node import Host from mininet.topo import Topo from mininet.util import quietRun from mininet.log import error class VLANHost( Host ): "Host connected to VLAN interface" def config( self, vlan=100, **params ): """Configure VLANHost according to (optional) parameters: vlan: VLAN ID for default interface""" r = super( VLANHost, self ).config( **params ) intf = self.defaultIntf() # remove IP from default, "physical" interface self.cmd( 'ifconfig %s inet 0' % intf ) # create VLAN interface self.cmd( 'vconfig add %s %d' % ( intf, vlan ) ) # assign the host's IP to the VLAN interface self.cmd( 'ifconfig %s.%d inet %s' % ( intf, vlan, params['ip'] ) ) # update the intf name and host's intf map newName = '%s.%d' % ( intf, vlan ) # update the (Mininet) interface to refer to VLAN interface name intf.name = newName # add VLAN interface to host's name to intf map self.nameToIntf[ newName ] = intf return r hosts = { 'vlan': VLANHost } def exampleAllHosts( vlan ): """Simple example of how VLANHost can be used in a script""" # This is where the magic happens... host = partial( VLANHost, vlan=vlan ) # vlan (type: int): VLAN ID to be used by all hosts # Start a basic network using our VLANHost topo = SingleSwitchTopo( k=2 ) net = Mininet( host=host, topo=topo ) net.start() CLI( net ) net.stop() # pylint: disable=arguments-differ class VLANStarTopo( Topo ): """Example topology that uses host in multiple VLANs The topology has a single switch. There are k VLANs with n hosts in each, all connected to the single switch. There are also n hosts that are not in any VLAN, also connected to the switch.""" def build( self, k=2, n=2, vlanBase=100 ): s1 = self.addSwitch( 's1' ) for i in range( k ): vlan = vlanBase + i for j in range(n): name = 'h%d-%d' % ( j+1, vlan ) h = self.addHost( name, cls=VLANHost, vlan=vlan ) self.addLink( h, s1 ) for j in range( n ): h = self.addHost( 'h%d' % (j+1) ) self.addLink( h, s1 ) def exampleCustomTags(): """Simple example that exercises VLANStarTopo""" net = Mininet( topo=VLANStarTopo() ) net.start() CLI( net ) net.stop() if __name__ == '__main__': import sys from functools import partial from mininet.net import Mininet from mininet.cli import CLI from mininet.topo import SingleSwitchTopo from mininet.log import setLogLevel setLogLevel( 'info' ) if not quietRun( 'which vconfig' ): error( "Cannot find command 'vconfig'\nThe package", "'vlan' is required in Ubuntu or Debian,", "or 'vconfig' in Fedora\n" ) exit() if len( sys.argv ) >= 2: exampleAllHosts( vlan=int( sys.argv[ 1 ] ) ) else: exampleCustomTags() mininet-2.2.2/mininet/000077500000000000000000000000001306431124000146155ustar00rootroot00000000000000mininet-2.2.2/mininet/__init__.py000066400000000000000000000001071306431124000167240ustar00rootroot00000000000000"Docstring to silence pylint; ignores --ignore option for __init__.py" mininet-2.2.2/mininet/clean.py000077500000000000000000000103751306431124000162620ustar00rootroot00000000000000""" Mininet Cleanup author: Bob Lantz (rlantz@cs.stanford.edu) Unfortunately, Mininet and OpenFlow (and the Linux kernel) don't always clean up properly after themselves. Until they do (or until cleanup functionality is integrated into the Python code), this script may be used to get rid of unwanted garbage. It may also get rid of 'false positives', but hopefully nothing irreplaceable! """ from subprocess import ( Popen, PIPE, check_output as co, CalledProcessError ) import time from mininet.log import info from mininet.term import cleanUpScreens def sh( cmd ): "Print a command and send it to the shell" info( cmd + '\n' ) return Popen( [ '/bin/sh', '-c', cmd ], stdout=PIPE ).communicate()[ 0 ] 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 ] ) except CalledProcessError: pids = '' if pids: sh( 'pkill -9 -f %s' % pattern ) time.sleep( .5 ) else: break class Cleanup( object ): "Wrapper for cleanup()" callbacks = [] @classmethod def cleanup( cls): """Clean up junk which might be left over from old runs; do fast stuff before slow dp and link removal!""" info( "*** Removing excess controllers/ofprotocols/ofdatapaths/" "pings/noxes\n" ) zombies = 'controller ofprotocol ofdatapath ping nox_core lt-nox_core ' zombies += 'ovs-openflowd ovs-controller udpbwtest mnexec ivs' # Note: real zombie processes can't actually be killed, since they # are already (un)dead. Then again, # you can't connect to them either, so they're mostly harmless. # Send SIGTERM first to give processes a chance to shutdown cleanly. sh( 'killall ' + zombies + ' 2> /dev/null' ) time.sleep( 1 ) sh( 'killall -9 ' + zombies + ' 2> /dev/null' ) # And kill off sudo mnexec sh( 'pkill -9 -f "sudo mnexec"') info( "*** Removing junk from /tmp\n" ) sh( 'rm -f /tmp/vconn* /tmp/vlogs* /tmp/*.out /tmp/*.log' ) info( "*** Removing old X11 tunnels\n" ) cleanUpScreens() info( "*** Removing excess kernel datapaths\n" ) dps = sh( "ps ax | egrep -o 'dp[0-9]+' | sed 's/dp/nl:/'" ).splitlines() for dp in dps: if dp: sh( 'dpctl deldp ' + dp ) info( "*** Removing OVS datapaths\n" ) dps = sh("ovs-vsctl --timeout=1 list-br").strip().splitlines() if dps: sh( "ovs-vsctl " + " -- ".join( "--if-exists del-br " + dp for dp in dps if dp ) ) # And in case the above didn't work... dps = sh( "ovs-vsctl --timeout=1 list-br" ).strip().splitlines() for dp in dps: sh( 'ovs-vsctl del-br ' + dp ) info( "*** Removing all links of the pattern foo-ethX\n" ) links = sh( "ip link show | " "egrep -o '([-_.[:alnum:]]+-eth[[:digit:]]+)'" ).splitlines() # Delete blocks of links n = 1000 # chunk size for i in xrange( 0, len( links ), n ): cmd = ';'.join( 'ip link del %s' % link for link in links[ i : i + n ] ) sh( '( %s ) 2> /dev/null' % cmd ) if 'tap9' in sh( 'ip link show' ): info( "*** Removing tap9 - assuming it's from cluster edition\n" ) sh( 'ip link del tap9' ) info( "*** Killing stale mininet node processes\n" ) killprocs( 'mininet:' ) info( "*** Shutting down stale tunnels\n" ) killprocs( 'Tunnel=Ethernet' ) killprocs( '.ssh/mn') sh( 'rm -f ~/.ssh/mn/*' ) # Call any additional cleanup code if necessary for callback in cls.callbacks: callback() info( "*** Cleanup complete.\n" ) @classmethod def addCleanupCallback( cls, callback ): "Add cleanup callback" if callback not in cls.callbacks: cls.callbacks.append( callback ) cleanup = Cleanup.cleanup addCleanupCallback = Cleanup.addCleanupCallback mininet-2.2.2/mininet/cli.py000066400000000000000000000370341306431124000157450ustar00rootroot00000000000000""" A simple command-line interface for Mininet. The Mininet CLI provides a simple control console which makes it easy to talk to nodes. For example, the command mininet> h27 ifconfig runs 'ifconfig' on host h27. Having a single console rather than, for example, an xterm for each node is particularly convenient for networks of any reasonable size. The CLI automatically substitutes IP addresses for node names, so commands like mininet> h2 ping h3 should work correctly and allow host h2 to ping host h3 Several useful commands are provided, including the ability to list all nodes ('nodes'), to print out the network topology ('net') and to check connectivity ('pingall', 'pingpair') and bandwidth ('iperf'.) """ from subprocess import call from cmd import Cmd from os import isatty from select import poll, POLLIN import sys import time import os import atexit from mininet.log import info, output, error from mininet.term import makeTerms, runX11 from mininet.util import ( quietRun, dumpNodeConnections, dumpPorts ) class CLI( Cmd ): "Simple command-line interface to talk to nodes." prompt = 'mininet> ' def __init__( self, mininet, stdin=sys.stdin, script=None ): """Start and run interactive or batch mode CLI mininet: Mininet network object stdin: standard input for CLI script: script to run in batch mode""" self.mn = mininet # Local variable bindings for py command self.locals = { 'net': mininet } # Attempt to handle input self.stdin = stdin self.inPoller = poll() self.inPoller.register( stdin ) self.inputFile = script Cmd.__init__( self ) info( '*** Starting CLI:\n' ) if self.inputFile: self.do_source( self.inputFile ) return self.initReadline() self.run() readlineInited = False @classmethod def initReadline( cls ): "Set up history if readline is available" # Only set up readline once to prevent multiplying the history file if cls.readlineInited: return cls.readlineInited = True try: from readline import ( read_history_file, write_history_file, set_history_length ) except ImportError: pass else: history_path = os.path.expanduser( '~/.mininet_history' ) if os.path.isfile( history_path ): read_history_file( history_path ) set_history_length( 1000 ) atexit.register( lambda: write_history_file( history_path ) ) def run( self ): "Run our cmdloop(), catching KeyboardInterrupt" while True: try: # Make sure no nodes are still waiting for node in self.mn.values(): while node.waiting: info( 'stopping', node, '\n' ) node.sendInt() node.waitOutput() if self.isatty(): quietRun( 'stty echo sane intr ^C' ) self.cmdloop() break except KeyboardInterrupt: # Output a message - unless it's also interrupted # pylint: disable=broad-except try: output( '\nInterrupt\n' ) except Exception: pass # pylint: enable=broad-except def emptyline( self ): "Don't repeat last command when you hit return." pass def getLocals( self ): "Local variable bindings for py command" self.locals.update( self.mn ) return self.locals helpStr = ( 'You may also send a command to a node using:\n' ' command {args}\n' 'For example:\n' ' mininet> h1 ifconfig\n' '\n' 'The interpreter automatically substitutes IP addresses\n' 'for node names when a node is the first arg, so commands\n' 'like\n' ' mininet> h2 ping h3\n' 'should work.\n' '\n' 'Some character-oriented interactive commands require\n' 'noecho:\n' ' mininet> noecho h2 vi foo.py\n' 'However, starting up an xterm/gterm is generally better:\n' ' mininet> xterm h2\n\n' ) def do_help( self, line ): "Describe available CLI commands." Cmd.do_help( self, line ) if line is '': output( self.helpStr ) def do_nodes( self, _line ): "List all nodes." nodes = ' '.join( sorted( self.mn ) ) output( 'available nodes are: \n%s\n' % nodes ) def do_ports( self, _line ): "display ports and interfaces for each switch" dumpPorts( self.mn.switches ) def do_net( self, _line ): "List network connections." dumpNodeConnections( self.mn.values() ) def do_sh( self, line ): """Run an external shell command Usage: sh [cmd args]""" assert self # satisfy pylint and allow override call( line, shell=True ) # do_py() and do_px() need to catch any exception during eval()/exec() # pylint: disable=broad-except def do_py( self, line ): """Evaluate a Python expression. Node names may be used, e.g.: py h1.cmd('ls')""" try: result = eval( line, globals(), self.getLocals() ) if not result: return elif isinstance( result, str ): output( result + '\n' ) else: output( repr( result ) + '\n' ) except Exception, e: output( str( e ) + '\n' ) # We are in fact using the exec() pseudo-function # pylint: disable=exec-used def do_px( self, line ): """Execute a Python statement. Node names may be used, e.g.: px print h1.cmd('ls')""" try: exec( line, globals(), self.getLocals() ) except Exception, e: output( str( e ) + '\n' ) # pylint: enable=broad-except,exec-used def do_pingall( self, line ): "Ping between all hosts." self.mn.pingAll( line ) def do_pingpair( self, _line ): "Ping between first two hosts, useful for testing." self.mn.pingPair() def do_pingallfull( self, _line ): "Ping between all hosts, returns all ping results." self.mn.pingAllFull() def do_pingpairfull( self, _line ): "Ping between first two hosts, returns all ping results." self.mn.pingPairFull() def do_iperf( self, line ): """Simple iperf TCP test between two (optionally specified) hosts. Usage: iperf node1 node2""" args = line.split() if not args: self.mn.iperf() elif len(args) == 2: hosts = [] err = False for arg in args: if arg not in self.mn: err = True error( "node '%s' not in network\n" % arg ) else: hosts.append( self.mn[ arg ] ) if not err: self.mn.iperf( hosts ) else: error( 'invalid number of args: iperf src dst\n' ) def do_iperfudp( self, line ): """Simple iperf UDP test between two (optionally specified) hosts. Usage: iperfudp bw node1 node2""" args = line.split() if not args: self.mn.iperf( l4Type='UDP' ) elif len(args) == 3: udpBw = args[ 0 ] hosts = [] err = False for arg in args[ 1:3 ]: if arg not in self.mn: err = True error( "node '%s' not in network\n" % arg ) else: hosts.append( self.mn[ arg ] ) if not err: self.mn.iperf( hosts, l4Type='UDP', udpBw=udpBw ) else: error( 'invalid number of args: iperfudp bw src dst\n' + 'bw examples: 10M\n' ) def do_intfs( self, _line ): "List interfaces." for node in self.mn.values(): output( '%s: %s\n' % ( node.name, ','.join( node.intfNames() ) ) ) def do_dump( self, _line ): "Dump node info." for node in self.mn.values(): output( '%s\n' % repr( node ) ) def do_link( self, line ): """Bring link(s) between two nodes up or down. Usage: link node1 node2 [up/down]""" args = line.split() if len(args) != 3: error( 'invalid number of args: link end1 end2 [up down]\n' ) elif args[ 2 ] not in [ 'up', 'down' ]: error( 'invalid type: link end1 end2 [up down]\n' ) else: self.mn.configLinkStatus( *args ) def do_xterm( self, line, term='xterm' ): """Spawn xterm(s) for the given node(s). Usage: xterm node1 node2 ...""" args = line.split() if not args: error( 'usage: %s node1 node2 ...\n' % term ) else: for arg in args: if arg not in self.mn: error( "node '%s' not in network\n" % arg ) else: node = self.mn[ arg ] self.mn.terms += makeTerms( [ node ], term = term ) def do_x( self, line ): """Create an X11 tunnel to the given node, optionally starting a client. Usage: x node [cmd args]""" args = line.split() if not args: error( 'usage: x node [cmd args]...\n' ) else: node = self.mn[ args[ 0 ] ] cmd = args[ 1: ] self.mn.terms += runX11( node, cmd ) def do_gterm( self, line ): """Spawn gnome-terminal(s) for the given node(s). Usage: gterm node1 node2 ...""" self.do_xterm( line, term='gterm' ) def do_exit( self, _line ): "Exit" assert self # satisfy pylint and allow override return 'exited by user command' def do_quit( self, line ): "Exit" return self.do_exit( line ) def do_EOF( self, line ): "Exit" output( '\n' ) return self.do_exit( line ) def isatty( self ): "Is our standard input a tty?" return isatty( self.stdin.fileno() ) def do_noecho( self, line ): """Run an interactive command with echoing turned off. Usage: noecho [cmd args]""" if self.isatty(): quietRun( 'stty -echo' ) self.default( line ) if self.isatty(): quietRun( 'stty echo' ) def do_source( self, line ): """Read commands from an input file. Usage: source """ args = line.split() if len(args) != 1: error( 'usage: source \n' ) return try: self.inputFile = open( args[ 0 ] ) while True: line = self.inputFile.readline() if len( line ) > 0: self.onecmd( line ) else: break except IOError: error( 'error reading file %s\n' % args[ 0 ] ) self.inputFile.close() self.inputFile = None def do_dpctl( self, line ): """Run dpctl (or ovs-ofctl) command on all switches. Usage: dpctl command [arg1] [arg2] ...""" args = line.split() if len(args) < 1: error( 'usage: dpctl command [arg1] [arg2] ...\n' ) return for sw in self.mn.switches: output( '*** ' + sw.name + ' ' + ('-' * 72) + '\n' ) output( sw.dpctl( *args ) ) def do_time( self, line ): "Measure time taken for any command in Mininet." start = time.time() self.onecmd(line) elapsed = time.time() - start self.stdout.write("*** Elapsed time: %0.6f secs\n" % elapsed) def do_links( self, _line ): "Report on links" for link in self.mn.links: print link, link.status() def do_switch( self, line ): "Starts or stops a switch" args = line.split() if len(args) != 2: error( 'invalid number of args: switch ' '{start, stop}\n' ) return sw = args[ 0 ] command = args[ 1 ] if sw not in self.mn or self.mn.get( sw ) not in self.mn.switches: error( 'invalid switch: %s\n' % args[ 1 ] ) else: sw = args[ 0 ] command = args[ 1 ] if command == 'start': self.mn.get( sw ).start( self.mn.controllers ) elif command == 'stop': self.mn.get( sw ).stop( deleteIntfs=False ) else: error( 'invalid command: ' 'switch {start, stop}\n' ) def default( self, line ): """Called on an input line when the command prefix is not recognized. Overridden to run shell commands when a node is the first CLI argument. Past the first CLI argument, node names are automatically replaced with corresponding IP addrs.""" first, args, line = self.parseline( line ) if first in self.mn: if not args: print "*** Enter a command for node: %s " % first return node = self.mn[ first ] rest = args.split( ' ' ) # Substitute IP addresses for node names in command # If updateIP() returns None, then use node name rest = [ self.mn[ arg ].defaultIntf().updateIP() or arg if arg in self.mn else arg for arg in rest ] rest = ' '.join( rest ) # Run cmd on node: node.sendCmd( rest ) self.waitForNode( node ) else: error( '*** Unknown command: %s\n' % line ) def waitForNode( self, node ): "Wait for a node to finish, and print its output." # Pollers nodePoller = poll() nodePoller.register( node.stdout ) bothPoller = poll() bothPoller.register( self.stdin, POLLIN ) bothPoller.register( node.stdout, POLLIN ) if self.isatty(): # Buffer by character, so that interactive # commands sort of work quietRun( 'stty -icanon min 1' ) while True: try: bothPoller.poll() # XXX BL: this doesn't quite do what we want. if False and self.inputFile: key = self.inputFile.read( 1 ) if key is not '': node.write( key ) else: self.inputFile = None if isReadable( self.inPoller ): key = self.stdin.read( 1 ) node.write( key ) if isReadable( nodePoller ): data = node.monitor() output( data ) if not node.waiting: break except KeyboardInterrupt: # There is an at least one race condition here, since # it's possible to interrupt ourselves after we've # read data but before it has been printed. node.sendInt() def precmd( self, line ): "allow for comments in the cli" if '#' in line: line = line.split( '#' )[ 0 ] return line # Helper functions def isReadable( poller ): "Check whether a Poll object has a readable fd." for fdmask in poller.poll( 0 ): mask = fdmask[ 1 ] if mask & POLLIN: return True mininet-2.2.2/mininet/examples000077700000000000000000000000001306431124000203252../examplesustar00rootroot00000000000000mininet-2.2.2/mininet/link.py000066400000000000000000000530251306431124000161310ustar00rootroot00000000000000""" link.py: interface and link abstractions for mininet It seems useful to bundle functionality for interfaces into a single class. Also it seems useful to enable the possibility of multiple flavors of links, including: - simple veth pairs - tunneled links - patchable links (which can be disconnected and reconnected via a patchbay) - link simulators (e.g. wireless) Basic division of labor: Nodes: know how to execute commands Intfs: know how to configure themselves Links: know how to connect nodes together Intf: basic interface object that can configure itself TCIntf: interface with bandwidth limiting and delay via tc Link: basic link class for creating veth pairs """ from mininet.log import info, error, debug from mininet.util import makeIntfPair import mininet.node import re class Intf( object ): "Basic interface object that can configure itself." def __init__( self, name, node=None, port=None, link=None, mac=None, **params ): """name: interface name (e.g. h1-eth0) node: owning node (where this intf most likely lives) link: parent link if we're part of a link other arguments are passed to config()""" self.node = node self.name = name self.link = link self.mac = mac self.ip, self.prefixLen = None, None # if interface is lo, we know the ip is 127.0.0.1. # This saves an ifconfig command per node if self.name == 'lo': self.ip = '127.0.0.1' self.prefixLen = 8 # Add to node (and move ourselves if necessary ) moveIntfFn = params.pop( 'moveIntfFn', None ) if moveIntfFn: node.addIntf( self, port=port, moveIntfFn=moveIntfFn ) else: node.addIntf( self, port=port ) # Save params for future reference self.params = params self.config( **params ) def cmd( self, *args, **kwargs ): "Run a command in our owning node" return self.node.cmd( *args, **kwargs ) def ifconfig( self, *args ): "Configure ourselves using ifconfig" return self.cmd( 'ifconfig', self.name, *args ) def setIP( self, ipstr, prefixLen=None ): """Set our IP address""" # This is a sign that we should perhaps rethink our prefix # mechanism and/or the way we specify IP addresses if '/' in ipstr: self.ip, self.prefixLen = ipstr.split( '/' ) return self.ifconfig( ipstr, 'up' ) else: if prefixLen is None: raise Exception( 'No prefix length set for IP address %s' % ( ipstr, ) ) self.ip, self.prefixLen = ipstr, prefixLen return self.ifconfig( '%s/%s' % ( ipstr, prefixLen ) ) def setMAC( self, macstr ): """Set the MAC address for an interface. macstr: MAC address as string""" self.mac = macstr return ( self.ifconfig( 'down' ) + self.ifconfig( 'hw', 'ether', macstr ) + self.ifconfig( 'up' ) ) _ipMatchRegex = re.compile( r'\d+\.\d+\.\d+\.\d+' ) _macMatchRegex = re.compile( r'..:..:..:..:..:..' ) def updateIP( self ): "Return updated IP address based on ifconfig" # use pexec instead of node.cmd so that we dont read # backgrounded output from the cli. ifconfig, _err, _exitCode = self.node.pexec( 'ifconfig %s' % self.name ) ips = self._ipMatchRegex.findall( ifconfig ) self.ip = ips[ 0 ] if ips else None return self.ip def updateMAC( self ): "Return updated MAC address based on ifconfig" ifconfig = self.ifconfig() macs = self._macMatchRegex.findall( ifconfig ) self.mac = macs[ 0 ] if macs else None return self.mac # Instead of updating ip and mac separately, # use one ifconfig call to do it simultaneously. # This saves an ifconfig command, which improves performance. def updateAddr( self ): "Return IP address and MAC address based on ifconfig." ifconfig = self.ifconfig() ips = self._ipMatchRegex.findall( ifconfig ) macs = self._macMatchRegex.findall( ifconfig ) self.ip = ips[ 0 ] if ips else None self.mac = macs[ 0 ] if macs else None return self.ip, self.mac def IP( self ): "Return IP address" return self.ip def MAC( self ): "Return MAC address" return self.mac def isUp( self, setUp=False ): "Return whether interface is up" if setUp: cmdOutput = self.ifconfig( 'up' ) # no output indicates success if cmdOutput: error( "Error setting %s up: %s " % ( self.name, cmdOutput ) ) return False else: return True else: return "UP" in self.ifconfig() def rename( self, newname ): "Rename interface" self.ifconfig( 'down' ) result = self.cmd( 'ip link set', self.name, 'name', newname ) self.name = newname self.ifconfig( 'up' ) return result # The reason why we configure things in this way is so # That the parameters can be listed and documented in # the config method. # Dealing with subclasses and superclasses is slightly # annoying, but at least the information is there! def setParam( self, results, method, **param ): """Internal method: configure a *single* parameter results: dict of results to update method: config method name param: arg=value (ignore if value=None) value may also be list or dict""" name, value = param.items()[ 0 ] f = getattr( self, method, None ) if not f or value is None: return if isinstance( value, list ): result = f( *value ) elif isinstance( value, dict ): result = f( **value ) else: result = f( value ) results[ name ] = result return result def config( self, mac=None, ip=None, ifconfig=None, up=True, **_params ): """Configure Node according to (optional) parameters: mac: MAC address ip: IP address ifconfig: arbitrary interface configuration Subclasses should override this method and call the parent class's config(**params)""" # If we were overriding this method, we would call # the superclass config method here as follows: # r = Parent.config( **params ) r = {} self.setParam( r, 'setMAC', mac=mac ) self.setParam( r, 'setIP', ip=ip ) self.setParam( r, 'isUp', up=up ) self.setParam( r, 'ifconfig', ifconfig=ifconfig ) return r def delete( self ): "Delete interface" self.cmd( 'ip link del ' + self.name ) # We used to do this, but it slows us down: # if self.node.inNamespace: # Link may have been dumped into root NS # quietRun( 'ip link del ' + self.name ) def status( self ): "Return intf status as a string" links, _err, _result = self.node.pexec( 'ip link show' ) if self.name in links: return "OK" else: return "MISSING" def __repr__( self ): return '<%s %s>' % ( self.__class__.__name__, self.name ) def __str__( self ): return self.name class TCIntf( Intf ): """Interface customized by tc (traffic control) utility Allows specification of bandwidth limits (various methods) as well as delay, loss and max queue length""" # The parameters we use seem to work reasonably up to 1 Gb/sec # For higher data rates, we will probably need to change them. bwParamMax = 1000 def bwCmds( self, bw=None, speedup=0, use_hfsc=False, use_tbf=False, latency_ms=None, enable_ecn=False, enable_red=False ): "Return tc commands to set bandwidth" cmds, parent = [], ' root ' if bw and ( bw < 0 or bw > self.bwParamMax ): error( 'Bandwidth limit', bw, 'is outside supported range 0..%d' % self.bwParamMax, '- ignoring\n' ) elif bw is not None: # BL: this seems a bit brittle... if ( speedup > 0 and self.node.name[0:1] == 's' ): bw = speedup # This may not be correct - we should look more closely # at the semantics of burst (and cburst) to make sure we # are specifying the correct sizes. For now I have used # the same settings we had in the mininet-hifi code. if use_hfsc: cmds += [ '%s qdisc add dev %s root handle 5:0 hfsc default 1', '%s class add dev %s parent 5:0 classid 5:1 hfsc sc ' + 'rate %fMbit ul rate %fMbit' % ( bw, bw ) ] elif use_tbf: if latency_ms is None: latency_ms = 15 * 8 / bw cmds += [ '%s qdisc add dev %s root handle 5: tbf ' + 'rate %fMbit burst 15000 latency %fms' % ( bw, latency_ms ) ] else: cmds += [ '%s qdisc add dev %s root handle 5:0 htb default 1', '%s class add dev %s parent 5:0 classid 5:1 htb ' + 'rate %fMbit burst 15k' % bw ] parent = ' parent 5:1 ' # ECN or RED if enable_ecn: cmds += [ '%s qdisc add dev %s' + parent + 'handle 6: red limit 1000000 ' + 'min 30000 max 35000 avpkt 1500 ' + 'burst 20 ' + 'bandwidth %fmbit probability 1 ecn' % bw ] parent = ' parent 6: ' elif enable_red: cmds += [ '%s qdisc add dev %s' + parent + 'handle 6: red limit 1000000 ' + 'min 30000 max 35000 avpkt 1500 ' + 'burst 20 ' + 'bandwidth %fmbit probability 1' % bw ] parent = ' parent 6: ' return cmds, parent @staticmethod def delayCmds( parent, delay=None, jitter=None, loss=None, max_queue_size=None ): "Internal method: return tc commands for delay and loss" cmds = [] if delay and delay < 0: error( 'Negative delay', delay, '\n' ) elif jitter and jitter < 0: error( 'Negative jitter', jitter, '\n' ) elif loss and ( loss < 0 or loss > 100 ): error( 'Bad loss percentage', loss, '%%\n' ) else: # Delay/jitter/loss/max queue size netemargs = '%s%s%s%s' % ( 'delay %s ' % delay if delay is not None else '', '%s ' % jitter if jitter is not None else '', 'loss %d ' % loss if loss is not None else '', 'limit %d' % max_queue_size if max_queue_size is not None else '' ) if netemargs: cmds = [ '%s qdisc add dev %s ' + parent + ' handle 10: netem ' + netemargs ] parent = ' parent 10:1 ' return cmds, parent def tc( self, cmd, tc='tc' ): "Execute tc command for our interface" c = cmd % (tc, self) # Add in tc command and our name debug(" *** executing command: %s\n" % c) return self.cmd( c ) def config( self, bw=None, delay=None, jitter=None, loss=None, gro=False, txo=True, rxo=True, speedup=0, use_hfsc=False, use_tbf=False, latency_ms=None, enable_ecn=False, enable_red=False, max_queue_size=None, **params ): """Configure the port and set its properties. bw: bandwidth in b/s (e.g. '10m') delay: transmit delay (e.g. '1ms' ) jitter: jitter (e.g. '1ms') loss: loss (e.g. '1%' ) gro: enable GRO (False) txo: enable transmit checksum offload (True) rxo: enable receive checksum offload (True) speedup: experimental switch-side bw option use_hfsc: use HFSC scheduling use_tbf: use TBF scheduling latency_ms: TBF latency parameter enable_ecn: enable ECN (False) enable_red: enable RED (False) max_queue_size: queue limit parameter for netem""" # Support old names for parameters gro = not params.pop( 'disable_gro', not gro ) result = Intf.config( self, **params) def on( isOn ): "Helper method: bool -> 'on'/'off'" return 'on' if isOn else 'off' # Set offload parameters with ethool self.cmd( 'ethtool -K', self, 'gro', on( gro ), 'tx', on( txo ), 'rx', on( rxo ) ) # Optimization: return if nothing else to configure # Question: what happens if we want to reset things? if ( bw is None and not delay and not loss and max_queue_size is None ): return # Clear existing configuration tcoutput = self.tc( '%s qdisc show dev %s' ) if "priomap" not in tcoutput and "noqueue" not in tcoutput: cmds = [ '%s qdisc del dev %s root' ] else: cmds = [] # Bandwidth limits via various methods bwcmds, parent = self.bwCmds( bw=bw, speedup=speedup, use_hfsc=use_hfsc, use_tbf=use_tbf, latency_ms=latency_ms, enable_ecn=enable_ecn, enable_red=enable_red ) cmds += bwcmds # Delay/jitter/loss/max_queue_size using netem delaycmds, parent = self.delayCmds( delay=delay, jitter=jitter, loss=loss, max_queue_size=max_queue_size, parent=parent ) cmds += delaycmds # Ugly but functional: display configuration info stuff = ( ( [ '%.2fMbit' % bw ] if bw is not None else [] ) + ( [ '%s delay' % delay ] if delay is not None else [] ) + ( [ '%s jitter' % jitter ] if jitter is not None else [] ) + ( ['%d%% loss' % loss ] if loss is not None else [] ) + ( [ 'ECN' ] if enable_ecn else [ 'RED' ] if enable_red else [] ) ) info( '(' + ' '.join( stuff ) + ') ' ) # Execute all the commands in our node debug("at map stage w/cmds: %s\n" % cmds) tcoutputs = [ self.tc(cmd) for cmd in cmds ] for output in tcoutputs: if output != '': error( "*** Error: %s" % output ) debug( "cmds:", cmds, '\n' ) debug( "outputs:", tcoutputs, '\n' ) result[ 'tcoutputs'] = tcoutputs result[ 'parent' ] = parent return result class Link( object ): """A basic link is just a veth pair. Other types of links could be tunnels, link emulators, etc..""" # pylint: disable=too-many-branches def __init__( self, node1, node2, port1=None, port2=None, intfName1=None, intfName2=None, addr1=None, addr2=None, intf=Intf, cls1=None, cls2=None, params1=None, params2=None, fast=True ): """Create veth link to another node, making two new interfaces. node1: first node node2: second node port1: node1 port number (optional) port2: node2 port number (optional) intf: default interface class/constructor cls1, cls2: optional interface-specific constructors intfName1: node1 interface name (optional) intfName2: node2 interface name (optional) params1: parameters for interface 1 params2: parameters for interface 2""" # This is a bit awkward; it seems that having everything in # params is more orthogonal, but being able to specify # in-line arguments is more convenient! So we support both. if params1 is None: params1 = {} if params2 is None: params2 = {} # Allow passing in params1=params2 if params2 is params1: params2 = dict( params1 ) if port1 is not None: params1[ 'port' ] = port1 if port2 is not None: params2[ 'port' ] = port2 if 'port' not in params1: params1[ 'port' ] = node1.newPort() if 'port' not in params2: params2[ 'port' ] = node2.newPort() if not intfName1: intfName1 = self.intfName( node1, params1[ 'port' ] ) if not intfName2: intfName2 = self.intfName( node2, params2[ 'port' ] ) self.fast = fast if fast: params1.setdefault( 'moveIntfFn', self._ignore ) params2.setdefault( 'moveIntfFn', self._ignore ) self.makeIntfPair( intfName1, intfName2, addr1, addr2, node1, node2, deleteIntfs=False ) else: self.makeIntfPair( intfName1, intfName2, addr1, addr2 ) if not cls1: cls1 = intf if not cls2: cls2 = intf intf1 = cls1( name=intfName1, node=node1, link=self, mac=addr1, **params1 ) intf2 = cls2( name=intfName2, node=node2, link=self, mac=addr2, **params2 ) # All we are is dust in the wind, and our two interfaces self.intf1, self.intf2 = intf1, intf2 # pylint: enable=too-many-branches @staticmethod def _ignore( *args, **kwargs ): "Ignore any arguments" pass def intfName( self, node, n ): "Construct a canonical interface name node-ethN for interface n." # Leave this as an instance method for now assert self return node.name + '-eth' + repr( n ) @classmethod def makeIntfPair( cls, intfname1, intfname2, addr1=None, addr2=None, node1=None, node2=None, deleteIntfs=True ): """Create pair of interfaces intfname1: name for interface 1 intfname2: name for interface 2 addr1: MAC address for interface 1 (optional) addr2: MAC address for interface 2 (optional) node1: home node for interface 1 (optional) node2: home node for interface 2 (optional) (override this method [and possibly delete()] to change link type)""" # Leave this as a class method for now assert cls return makeIntfPair( intfname1, intfname2, addr1, addr2, node1, node2, deleteIntfs=deleteIntfs ) def delete( self ): "Delete this link" self.intf1.delete() # We only need to delete one side, though this doesn't seem to # cost us much and might help subclasses. # self.intf2.delete() def stop( self ): "Override to stop and clean up link as needed" self.delete() def status( self ): "Return link status as a string" return "(%s %s)" % ( self.intf1.status(), self.intf2.status() ) def __str__( self ): return '%s<->%s' % ( self.intf1, self.intf2 ) class OVSIntf( Intf ): "Patch interface on an OVSSwitch" def ifconfig( self, *args ): cmd = ' '.join( args ) if cmd == 'up': # OVSIntf is always up return else: raise Exception( 'OVSIntf cannot do ifconfig ' + cmd ) class OVSLink( Link ): """Link that makes patch links between OVSSwitches Warning: in testing we have found that no more than ~64 OVS patch links should be used in row.""" def __init__( self, node1, node2, **kwargs ): "See Link.__init__() for options" self.isPatchLink = False if ( isinstance( node1, mininet.node.OVSSwitch ) and isinstance( node2, mininet.node.OVSSwitch ) ): self.isPatchLink = True kwargs.update( cls1=OVSIntf, cls2=OVSIntf ) Link.__init__( self, node1, node2, **kwargs ) def makeIntfPair( self, *args, **kwargs ): "Usually delegated to OVSSwitch" if self.isPatchLink: return None, None else: return Link.makeIntfPair( *args, **kwargs ) class TCLink( Link ): "Link with symmetric TC interfaces configured via opts" def __init__( self, node1, node2, port1=None, port2=None, intfName1=None, intfName2=None, addr1=None, addr2=None, **params ): Link.__init__( self, node1, node2, port1=port1, port2=port2, intfName1=intfName1, intfName2=intfName2, cls1=TCIntf, cls2=TCIntf, addr1=addr1, addr2=addr2, params1=params, params2=params ) class TCULink( TCLink ): """TCLink with default settings optimized for UserSwitch (txo=rxo=0/False). Unfortunately with recent Linux kernels, enabling TX and RX checksum offload on veth pairs doesn't work well with UserSwitch: either it gets terrible performance or TCP packets with bad checksums are generated, forwarded, and *dropped* due to having bad checksums! OVS and LinuxBridge seem to cope with this somehow, but it is likely to be an issue with many software Ethernet bridges.""" def __init__( self, *args, **kwargs ): kwargs.update( txo=False, rxo=False ) TCLink.__init__( self, *args, **kwargs ) mininet-2.2.2/mininet/log.py000066400000000000000000000136761306431124000157650ustar00rootroot00000000000000"Logging functions for Mininet." import logging from logging import Logger import types # Create a new loglevel, 'CLI info', which enables a Mininet user to see only # the output of the commands they execute, plus any errors or warnings. This # level is in between info and warning. CLI info-level commands should not be # printed during regression tests. OUTPUT = 25 LEVELS = { 'debug': logging.DEBUG, 'info': logging.INFO, 'output': OUTPUT, 'warning': logging.WARNING, 'error': logging.ERROR, 'critical': logging.CRITICAL } # change this to logging.INFO to get printouts when running unit tests LOGLEVELDEFAULT = OUTPUT #default: '%(asctime)s - %(name)s - %(levelname)s - %(message)s' LOGMSGFORMAT = '%(message)s' # Modified from python2.5/__init__.py class StreamHandlerNoNewline( logging.StreamHandler ): """StreamHandler that doesn't print newlines by default. Since StreamHandler automatically adds newlines, define a mod to more easily support interactive mode when we want it, or errors-only logging for running unit tests.""" def emit( self, record ): """Emit a record. If a formatter is specified, it is used to format the record. The record is then written to the stream with a trailing newline [ N.B. this may be removed depending on feedback ]. If exception information is present, it is formatted using traceback.printException and appended to the stream.""" try: msg = self.format( record ) fs = '%s' # was '%s\n' if not hasattr( types, 'UnicodeType' ): # if no unicode support... self.stream.write( fs % msg ) else: try: self.stream.write( fs % msg ) except UnicodeError: self.stream.write( fs % msg.encode( 'UTF-8' ) ) self.flush() except ( KeyboardInterrupt, SystemExit ): raise except: self.handleError( record ) class Singleton( type ): """Singleton pattern from Wikipedia See http://en.wikipedia.org/wiki/Singleton_Pattern Intended to be used as a __metaclass_ param, as shown for the class below.""" def __init__( cls, name, bases, dict_ ): super( Singleton, cls ).__init__( name, bases, dict_ ) cls.instance = None def __call__( cls, *args, **kw ): if cls.instance is None: cls.instance = super( Singleton, cls ).__call__( *args, **kw ) return cls.instance class MininetLogger( Logger, object ): """Mininet-specific logger Enable each mininet .py file to with one import: from mininet.log import [lg, info, error] ...get a default logger that doesn't require one newline per logging call. Inherit from object to ensure that we have at least one new-style base class, and can then use the __metaclass__ directive, to prevent this error: TypeError: Error when calling the metaclass bases a new-style class can't have only classic bases If Python2.5/logging/__init__.py defined Filterer as a new-style class, via Filterer( object ): rather than Filterer, we wouldn't need this. Use singleton pattern to ensure only one logger is ever created.""" __metaclass__ = Singleton def __init__( self ): Logger.__init__( self, "mininet" ) # create console handler ch = StreamHandlerNoNewline() # create formatter formatter = logging.Formatter( LOGMSGFORMAT ) # add formatter to ch ch.setFormatter( formatter ) # add ch to lg self.addHandler( ch ) self.setLogLevel() def setLogLevel( self, levelname=None ): """Setup loglevel. Convenience function to support lowercase names. levelName: level name from LEVELS""" level = LOGLEVELDEFAULT if levelname is not None: if levelname not in LEVELS: raise Exception( 'unknown levelname seen in setLogLevel' ) else: level = LEVELS.get( levelname, level ) self.setLevel( level ) self.handlers[ 0 ].setLevel( level ) # pylint: disable=method-hidden # "An attribute inherited from mininet.log hide this method" (sic) # Not sure why this is occurring - this function definitely gets called. # See /usr/lib/python2.5/logging/__init__.py; modified from warning() def output( self, msg, *args, **kwargs ): """Log 'msg % args' with severity 'OUTPUT'. To pass exception information, use the keyword argument exc_info with a true value, e.g. logger.warning("Houston, we have a %s", "cli output", exc_info=1) """ if self.manager.disable >= OUTPUT: return if self.isEnabledFor( OUTPUT ): self._log( OUTPUT, msg, args, kwargs ) # pylint: enable=method-hidden lg = MininetLogger() # Make things a bit more convenient by adding aliases # (info, warn, error, debug) and allowing info( 'this', 'is', 'OK' ) # In the future we may wish to make things more efficient by only # doing the join (and calling the function) unless the logging level # is high enough. def makeListCompatible( fn ): """Return a new function allowing fn( 'a 1 b' ) to be called as newfn( 'a', 1, 'b' )""" def newfn( *args ): "Generated function. Closure-ish." if len( args ) == 1: return fn( *args ) args = ' '.join( [ str( arg ) for arg in args ] ) return fn( args ) # Fix newfn's name and docstring setattr( newfn, '__name__', fn.__name__ ) setattr( newfn, '__doc__', fn.__doc__ ) return newfn info, output, warn, error, debug = ( lg.info, lg.output, lg.warn, lg.error, lg.debug ) = [ makeListCompatible( f ) for f in lg.info, lg.output, lg.warn, lg.error, lg.debug ] setLogLevel = lg.setLogLevel mininet-2.2.2/mininet/moduledeps.py000066400000000000000000000046451306431124000173410ustar00rootroot00000000000000"Module dependency utility functions for Mininet." from mininet.util import quietRun from mininet.log import info, error, debug from os import environ def lsmod(): "Return output of lsmod." return quietRun( 'lsmod' ) def rmmod( mod ): """Return output of lsmod. mod: module string""" return quietRun( [ 'rmmod', mod ] ) def modprobe( mod ): """Return output of modprobe mod: module string""" return quietRun( [ 'modprobe', mod ] ) OF_KMOD = 'ofdatapath' OVS_KMOD = 'openvswitch_mod' # Renamed 'openvswitch' in OVS 1.7+/Linux 3.5+ TUN = 'tun' def moduleDeps( subtract=None, add=None ): """Handle module dependencies. subtract: string or list of module names to remove, if already loaded add: string or list of module names to add, if not already loaded""" subtract = subtract if subtract is not None else [] add = add if add is not None else [] if isinstance( subtract, basestring ): subtract = [ subtract ] if isinstance( add, basestring ): add = [ add ] for mod in subtract: if mod in lsmod(): info( '*** Removing ' + mod + '\n' ) rmmodOutput = rmmod( mod ) if rmmodOutput: error( 'Error removing ' + mod + ': "%s">\n' % rmmodOutput ) exit( 1 ) if mod in lsmod(): error( 'Failed to remove ' + mod + '; still there!\n' ) exit( 1 ) for mod in add: if mod not in lsmod(): info( '*** Loading ' + mod + '\n' ) modprobeOutput = modprobe( mod ) if modprobeOutput: error( 'Error inserting ' + mod + ' - is it installed and available via modprobe?\n' + 'Error was: "%s"\n' % modprobeOutput ) if mod not in lsmod(): error( 'Failed to insert ' + mod + ' - quitting.\n' ) exit( 1 ) else: debug( '*** ' + mod + ' already loaded\n' ) def pathCheck( *args, **kwargs ): "Make sure each program in *args can be found in $PATH." moduleName = kwargs.get( 'moduleName', 'it' ) for arg in args: if not quietRun( 'which ' + arg ): error( 'Cannot find required executable %s.\n' % arg + 'Please make sure that %s is installed ' % moduleName + 'and available in your $PATH:\n(%s)\n' % environ[ 'PATH' ] ) exit( 1 ) mininet-2.2.2/mininet/net.py000077500000000000000000001100001306431124000157500ustar00rootroot00000000000000""" Mininet: A simple networking testbed for OpenFlow/SDN! 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 (openflowswitch.org) as well as OpenVSwitch (openvswitch.org.) 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 can be full-service nodes that live in their own network namespaces and have management interfaces and IP addresses on a control network (e.g. 192.168.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. Consistent, straightforward naming is important in order to easily identify hosts, switches and controllers, both from the CLI and from program code. Interfaces are named to make it easy to identify which interfaces belong to which node. The basic naming scheme is as follows: Host nodes are named h1-hN Switch nodes are named s1-sN Controller nodes are named c0-cN Interfaces are named {nodename}-eth0 .. {nodename}-ethN Note: If the network topology is created using mininet.topo, then node numbers are unique among hosts and switches (e.g. we have h1..hN and SN..SN+M) and also correspond to their default IP addresses of 10.x.y.z/8 where x.y.z is the base-256 representation of N for hN. This mapping allows easy determination of a node's IP address from its name, e.g. h1 -> 10.0.0.1, h257 -> 10.0.1.1. Note also that 10.0.0.1 can often be written as 10.1 for short, e.g. "ping 10.1" is equivalent to "ping 10.0.0.1". Currently we wrap the entire network in a 'mininet' object, which constructs a simulated network based on a network topology created using a topology object (e.g. LinearTopo) from mininet.topo or mininet.topolib, and a Controller which the switches will connect to. Several configuration options are provided for functions such as automatically setting MAC addresses, populating the ARP table, or even running a set of terminals to allow direct interaction with nodes. After the network is created, it can be started using start(), and a variety of useful tasks maybe performed, including basic connectivity and bandwidth tests and running the mininet CLI. Once the network is up and running, test code can easily get access to host and switch objects which can then be used for arbitrary experiments, typically involving running a series of commands on the hosts. After all desired tests or activities have been completed, the stop() method may be called to shut down the network. """ import os import re import select import signal import random from time import sleep from itertools import chain, groupby from math import ceil from mininet.cli import CLI from mininet.log import info, error, debug, output, warn from mininet.node import ( Node, Host, OVSKernelSwitch, DefaultController, Controller ) from mininet.nodelib import NAT from mininet.link import Link, Intf from mininet.util import ( quietRun, fixLimits, numCores, ensureRoot, macColonHex, ipStr, ipParse, netParse, ipAdd, waitListening ) from mininet.term import cleanUpScreens, makeTerms # Mininet version: should be consistent with README and LICENSE VERSION = "2.2.2" class Mininet( object ): "Network emulation with hosts spawned in network namespaces." def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host, controller=DefaultController, link=Link, intf=Intf, build=True, xterms=False, cleanup=False, ipBase='10.0.0.0/8', inNamespace=False, autoSetMacs=False, autoStaticArp=False, autoPinCpus=False, listenPort=None, waitConnected=False ): """Create Mininet object. topo: Topo (topology) object or None switch: default Switch class host: default Host class/constructor controller: default Controller class/constructor link: default Link class/constructor intf: default Intf class/constructor ipBase: base IP address for hosts, build: build now from topo? 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 automatically like IP addresses? autoStaticArp: set all-pairs static MAC addrs? autoPinCpus: pin hosts to (real) cores (requires CPULimitedHost)? listenPort: base listening port to open; will be incremented for each additional switch in the net if inNamespace=False""" self.topo = topo self.switch = switch self.host = host self.controller = controller self.link = link self.intf = intf self.ipBase = ipBase self.ipBaseNum, self.prefixLen = netParse( self.ipBase ) self.nextIP = 1 # start for address allocation self.inNamespace = inNamespace self.xterms = xterms self.cleanup = cleanup self.autoSetMacs = autoSetMacs self.autoStaticArp = autoStaticArp self.autoPinCpus = autoPinCpus self.numCores = numCores() self.nextCore = 0 # next core for pinning hosts to CPUs self.listenPort = listenPort self.waitConn = waitConnected self.hosts = [] self.switches = [] self.controllers = [] self.links = [] self.nameToNode = {} # name to Node (Host/Switch) objects self.terms = [] # list of spawned xterm processes Mininet.init() # Initialize Mininet if necessary self.built = False if topo and build: self.build() def waitConnected( self, timeout=None, delay=.5 ): """wait for each switch to connect to a controller, up to 5 seconds timeout: time to wait, or None to wait indefinitely delay: seconds to sleep per iteration returns: True if all switches are connected""" info( '*** Waiting for switches to connect\n' ) time = 0 remaining = list( self.switches ) while True: for switch in tuple( remaining ): if switch.connected(): info( '%s ' % switch ) remaining.remove( switch ) if not remaining: info( '\n' ) return True if time > timeout and timeout is not None: break sleep( delay ) time += delay warn( 'Timed out after %d seconds\n' % time ) for switch in remaining: if not switch.connected(): warn( 'Warning: %s is not connected to a controller\n' % switch.name ) else: remaining.remove( switch ) return not remaining def addHost( self, name, cls=None, **params ): """Add host. name: name of host to add cls: custom host class/constructor (optional) params: parameters for host returns: added host""" # Default IP and MAC addresses defaults = { 'ip': ipAdd( self.nextIP, ipBaseNum=self.ipBaseNum, prefixLen=self.prefixLen ) + '/%s' % self.prefixLen } if self.autoSetMacs: defaults[ 'mac' ] = macColonHex( self.nextIP ) if self.autoPinCpus: defaults[ 'cores' ] = self.nextCore self.nextCore = ( self.nextCore + 1 ) % self.numCores self.nextIP += 1 defaults.update( params ) if not cls: cls = self.host h = cls( name, **defaults ) self.hosts.append( h ) self.nameToNode[ name ] = h return h def addSwitch( self, name, cls=None, **params ): """Add switch. name: name of switch to add cls: custom switch class/constructor (optional) returns: added switch side effect: increments listenPort ivar .""" defaults = { 'listenPort': self.listenPort, 'inNamespace': self.inNamespace } defaults.update( params ) if not cls: cls = self.switch sw = cls( name, **defaults ) if not self.inNamespace and self.listenPort: self.listenPort += 1 self.switches.append( sw ) self.nameToNode[ name ] = sw return sw def addController( self, name='c0', controller=None, **params ): """Add controller. controller: Controller class""" # Get controller class if not controller: controller = self.controller # Construct new controller if one is not given if isinstance( name, Controller ): controller_new = name # Pylint thinks controller is a str() # pylint: disable=maybe-no-member name = controller_new.name # pylint: enable=maybe-no-member else: controller_new = controller( name, **params ) # Add new controller to net if controller_new: # allow controller-less setups self.controllers.append( controller_new ) self.nameToNode[ name ] = controller_new return controller_new def addNAT( self, name='nat0', connect=True, inNamespace=False, **params): """Add a NAT to the Mininet network name: name of NAT node connect: switch to connect to | True (s1) | None inNamespace: create in a network namespace params: other NAT node params, notably: ip: used as default gateway address""" nat = self.addHost( name, cls=NAT, inNamespace=inNamespace, subnet=self.ipBase, **params ) # find first switch and create link if connect: if not isinstance( connect, Node ): # Use first switch if not specified connect = self.switches[ 0 ] # Connect the nat to the switch self.addLink( nat, connect ) # Set the default route on hosts natIP = nat.params[ 'ip' ].split('/')[ 0 ] for host in self.hosts: if host.inNamespace: host.setDefaultRoute( 'via %s' % natIP ) return nat # BL: We now have four ways to look up nodes # This may (should?) be cleaned up in the future. def getNodeByName( self, *args ): "Return node(s) with given name(s)" if len( args ) == 1: return self.nameToNode[ args[ 0 ] ] return [ self.nameToNode[ n ] for n in args ] def get( self, *args ): "Convenience alias for getNodeByName" return self.getNodeByName( *args ) # Even more convenient syntax for node lookup and iteration def __getitem__( self, key ): """net [ name ] operator: Return node(s) with given name(s)""" return self.nameToNode[ key ] def __iter__( self ): "return iterator over node names" for node in chain( self.hosts, self.switches, self.controllers ): yield node.name def __len__( self ): "returns number of nodes in net" return ( len( self.hosts ) + len( self.switches ) + len( self.controllers ) ) def __contains__( self, item ): "returns True if net contains named node" return item in self.nameToNode def keys( self ): "return a list of all node names or net's keys" return list( self ) def values( self ): "return a list of all nodes or net's values" return [ self[name] for name in self ] def items( self ): "return (key,value) tuple list for every node in net" return zip( self.keys(), self.values() ) @staticmethod def randMac(): "Return a random, non-multicast MAC address" return macColonHex( random.randint(1, 2**48 - 1) & 0xfeffffffffff | 0x020000000000 ) def addLink( self, node1, node2, port1=None, port2=None, cls=None, **params ): """"Add a link from node1 to node2 node1: source node (or name) node2: dest node (or name) port1: source port (optional) port2: dest port (optional) cls: link class (optional) params: additional link params (optional) returns: link object""" # Accept node objects or names node1 = node1 if not isinstance( node1, basestring ) else self[ node1 ] node2 = node2 if not isinstance( node2, basestring ) else self[ node2 ] options = dict( params ) # Port is optional if port1 is not None: options.setdefault( 'port1', port1 ) if port2 is not None: options.setdefault( 'port2', port2 ) if self.intf is not None: options.setdefault( 'intf', self.intf ) # Set default MAC - this should probably be in Link options.setdefault( 'addr1', self.randMac() ) options.setdefault( 'addr2', self.randMac() ) cls = self.link if cls is None else cls link = cls( node1, node2, **options ) self.links.append( link ) return link def configHosts( self ): "Configure a set of hosts." for host in self.hosts: info( host.name + ' ' ) intf = host.defaultIntf() if intf: host.configDefault() else: # Don't configure nonexistent intf host.configDefault( ip=None, mac=None ) # You're low priority, dude! # BL: do we want to do this here or not? # May not make sense if we have CPU lmiting... # quietRun( 'renice +18 -p ' + repr( host.pid ) ) # This may not be the right place to do this, but # it needs to be done somewhere. info( '\n' ) def buildFromTopo( self, topo=None ): """Build mininet from a topology object At the end of this function, everything should be connected and up.""" # Possibly we should clean up here and/or validate # the topo if self.cleanup: pass info( '*** Creating network\n' ) if not self.controllers and self.controller: # Add a default controller info( '*** Adding controller\n' ) classes = self.controller if not isinstance( classes, list ): classes = [ classes ] for i, cls in enumerate( classes ): # Allow Controller objects because nobody understands partial() if isinstance( cls, Controller ): self.addController( cls ) else: self.addController( 'c%d' % i, cls ) info( '*** Adding hosts:\n' ) for hostName in topo.hosts(): self.addHost( hostName, **topo.nodeInfo( hostName ) ) info( hostName + ' ' ) info( '\n*** Adding switches:\n' ) for switchName in topo.switches(): # A bit ugly: add batch parameter if appropriate params = topo.nodeInfo( switchName) cls = params.get( 'cls', self.switch ) if hasattr( cls, 'batchStartup' ): params.setdefault( 'batch', True ) self.addSwitch( switchName, **params ) info( switchName + ' ' ) info( '\n*** Adding links:\n' ) for srcName, dstName, params in topo.links( sort=True, withInfo=True ): self.addLink( **params ) info( '(%s, %s) ' % ( srcName, dstName ) ) info( '\n' ) def configureControlNetwork( self ): "Control net config hook: override in subclass" raise Exception( 'configureControlNetwork: ' 'should be overriden in subclass', self ) def build( self ): "Build mininet." if self.topo: self.buildFromTopo( self.topo ) if self.inNamespace: self.configureControlNetwork() info( '*** Configuring hosts\n' ) self.configHosts() if self.xterms: self.startTerms() if self.autoStaticArp: self.staticArp() self.built = True def startTerms( self ): "Start a terminal for each node." if 'DISPLAY' not in os.environ: error( "Error starting terms: Cannot connect to display\n" ) return info( "*** Running terms on %s\n" % os.environ[ 'DISPLAY' ] ) cleanUpScreens() self.terms += makeTerms( self.controllers, 'controller' ) self.terms += makeTerms( self.switches, 'switch' ) self.terms += makeTerms( self.hosts, 'host' ) def stopXterms( self ): "Kill each xterm." for term in self.terms: os.kill( term.pid, signal.SIGKILL ) cleanUpScreens() def staticArp( self ): "Add all-pairs ARP entries to remove the need to handle broadcast." for src in self.hosts: for dst in self.hosts: if src != dst: src.setARP( ip=dst.IP(), mac=dst.MAC() ) def start( self ): "Start controller and switches." if not self.built: self.build() info( '*** Starting controller\n' ) for controller in self.controllers: info( controller.name + ' ') controller.start() info( '\n' ) info( '*** Starting %s switches\n' % len( self.switches ) ) for switch in self.switches: info( switch.name + ' ') switch.start( self.controllers ) started = {} for swclass, switches in groupby( sorted( self.switches, key=type ), type ): switches = tuple( switches ) if hasattr( swclass, 'batchStartup' ): success = swclass.batchStartup( switches ) started.update( { s: s for s in success } ) info( '\n' ) if self.waitConn: self.waitConnected() def stop( self ): "Stop the controller(s), switches and hosts" info( '*** Stopping %i controllers\n' % len( self.controllers ) ) for controller in self.controllers: info( controller.name + ' ' ) controller.stop() info( '\n' ) if self.terms: info( '*** Stopping %i terms\n' % len( self.terms ) ) self.stopXterms() info( '*** Stopping %i links\n' % len( self.links ) ) for link in self.links: info( '.' ) link.stop() info( '\n' ) info( '*** Stopping %i switches\n' % len( self.switches ) ) stopped = {} for swclass, switches in groupby( sorted( self.switches, key=type ), type ): switches = tuple( switches ) if hasattr( swclass, 'batchShutdown' ): success = swclass.batchShutdown( switches ) stopped.update( { s: s for s in success } ) for switch in self.switches: info( switch.name + ' ' ) if switch not in stopped: switch.stop() switch.terminate() info( '\n' ) info( '*** Stopping %i hosts\n' % len( self.hosts ) ) for host in self.hosts: info( host.name + ' ' ) host.terminate() info( '\n*** Done\n' ) def run( self, test, *args, **kwargs ): "Perform a complete start/test/stop cycle." self.start() info( '*** Running test\n' ) result = test( *args, **kwargs ) self.stop() return result def monitor( self, hosts=None, timeoutms=-1 ): """Monitor a set of hosts (or all hosts by default), and return their output, a line at a time. hosts: (optional) set of hosts to monitor timeoutms: (optional) timeout value in ms returns: iterator which returns host, line""" if hosts is None: hosts = self.hosts poller = select.poll() h1 = hosts[ 0 ] # so we can call class method fdToNode for host in hosts: poller.register( host.stdout ) while True: ready = poller.poll( timeoutms ) for fd, event in ready: host = h1.fdToNode( fd ) if event & select.POLLIN: line = host.readline() if line is not None: yield host, line # Return if non-blocking if not ready and timeoutms >= 0: yield None, None # XXX These test methods should be moved out of this class. # Probably we should create a tests.py for them @staticmethod def _parsePing( pingOutput ): "Parse ping output and return packets sent, received." # Check for downed link if 'connect: Network is unreachable' in pingOutput: return 1, 0 r = r'(\d+) packets transmitted, (\d+) received' m = re.search( r, pingOutput ) if m is None: error( '*** Error: could not parse ping output: %s\n' % pingOutput ) return 1, 0 sent, received = int( m.group( 1 ) ), int( m.group( 2 ) ) return sent, received def ping( self, hosts=None, timeout=None ): """Ping between all specified hosts. hosts: list of hosts timeout: time to wait for a response, as string returns: ploss packet loss percentage""" # should we check if running? packets = 0 lost = 0 ploss = None if not hosts: hosts = self.hosts output( '*** Ping: testing ping reachability\n' ) for node in hosts: output( '%s -> ' % node.name ) for dest in hosts: if node != dest: opts = '' if timeout: opts = '-W %s' % timeout if dest.intfs: result = node.cmd( 'ping -c1 %s %s' % (opts, dest.IP()) ) sent, received = self._parsePing( result ) else: sent, received = 0, 0 packets += sent if received > sent: error( '*** Error: received too many packets' ) error( '%s' % result ) node.cmdPrint( 'route' ) exit( 1 ) lost += sent - received output( ( '%s ' % dest.name ) if received else 'X ' ) output( '\n' ) if packets > 0: ploss = 100.0 * lost / packets received = packets - lost output( "*** Results: %i%% dropped (%d/%d received)\n" % ( ploss, received, packets ) ) else: ploss = 0 output( "*** Warning: No packets sent\n" ) return ploss @staticmethod def _parsePingFull( pingOutput ): "Parse ping output and return all data." errorTuple = (1, 0, 0, 0, 0, 0) # Check for downed link r = r'[uU]nreachable' m = re.search( r, pingOutput ) if m is not None: return errorTuple r = r'(\d+) packets transmitted, (\d+) received' m = re.search( r, pingOutput ) if m is None: error( '*** Error: could not parse ping output: %s\n' % pingOutput ) return errorTuple sent, received = int( m.group( 1 ) ), int( m.group( 2 ) ) r = r'rtt min/avg/max/mdev = ' r += r'(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+) ms' m = re.search( r, pingOutput ) if m is None: if received == 0: return errorTuple error( '*** Error: could not parse ping output: %s\n' % pingOutput ) return errorTuple rttmin = float( m.group( 1 ) ) rttavg = float( m.group( 2 ) ) rttmax = float( m.group( 3 ) ) rttdev = float( m.group( 4 ) ) return sent, received, rttmin, rttavg, rttmax, rttdev def pingFull( self, hosts=None, timeout=None ): """Ping between all specified hosts and return all data. hosts: list of hosts timeout: time to wait for a response, as string returns: all ping data; see function body.""" # should we check if running? # Each value is a tuple: (src, dsd, [all ping outputs]) all_outputs = [] if not hosts: hosts = self.hosts output( '*** Ping: testing ping reachability\n' ) for node in hosts: output( '%s -> ' % node.name ) for dest in hosts: if node != dest: opts = '' if timeout: opts = '-W %s' % timeout result = node.cmd( 'ping -c1 %s %s' % (opts, dest.IP()) ) outputs = self._parsePingFull( result ) sent, received, rttmin, rttavg, rttmax, rttdev = outputs all_outputs.append( (node, dest, outputs) ) output( ( '%s ' % dest.name ) if received else 'X ' ) output( '\n' ) output( "*** Results: \n" ) for outputs in all_outputs: src, dest, ping_outputs = outputs sent, received, rttmin, rttavg, rttmax, rttdev = ping_outputs output( " %s->%s: %s/%s, " % (src, dest, sent, received ) ) output( "rtt min/avg/max/mdev %0.3f/%0.3f/%0.3f/%0.3f ms\n" % (rttmin, rttavg, rttmax, rttdev) ) return all_outputs def pingAll( self, timeout=None ): """Ping between all hosts. returns: ploss packet loss percentage""" return self.ping( timeout=timeout ) def pingPair( self ): """Ping between first two hosts, useful for testing. returns: ploss packet loss percentage""" hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ] return self.ping( hosts=hosts ) def pingAllFull( self ): """Ping between all hosts. returns: ploss packet loss percentage""" return self.pingFull() def pingPairFull( self ): """Ping between first two hosts, useful for testing. returns: ploss packet loss percentage""" hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ] return self.pingFull( hosts=hosts ) @staticmethod def _parseIperf( iperfOutput ): """Parse iperf output and return bandwidth. iperfOutput: string returns: result string""" r = r'([\d\.]+ \w+/sec)' m = re.findall( r, iperfOutput ) if m: return m[-1] else: # was: raise Exception(...) error( 'could not parse iperf output: ' + iperfOutput ) return '' # XXX This should be cleaned up def iperf( self, hosts=None, l4Type='TCP', udpBw='10M', fmt=None, seconds=5, port=5001): """Run iperf between two hosts. hosts: list of hosts; if None, uses first and last hosts l4Type: string, one of [ TCP, UDP ] udpBw: bandwidth target for UDP test fmt: iperf format argument if any seconds: iperf time to transmit port: iperf port returns: two-element array of [ server, client ] speeds note: send() is buffered, so client rate can be much higher than the actual transmission rate; on an unloaded system, server rate should be much closer to the actual receive rate""" hosts = hosts or [ self.hosts[ 0 ], self.hosts[ -1 ] ] assert len( hosts ) == 2 client, server = hosts output( '*** Iperf: testing', l4Type, 'bandwidth between', client, 'and', server, '\n' ) server.cmd( 'killall -9 iperf' ) iperfArgs = 'iperf -p %d ' % port bwArgs = '' if l4Type == 'UDP': iperfArgs += '-u ' bwArgs = '-b ' + udpBw + ' ' elif l4Type != 'TCP': raise Exception( 'Unexpected l4 type: %s' % l4Type ) if fmt: iperfArgs += '-f %s ' % fmt server.sendCmd( iperfArgs + '-s' ) if l4Type == 'TCP': if not waitListening( client, server.IP(), port ): raise Exception( 'Could not connect to iperf on port %d' % port ) cliout = client.cmd( iperfArgs + '-t %d -c ' % seconds + server.IP() + ' ' + bwArgs ) debug( 'Client output: %s\n' % cliout ) servout = '' # We want the last *b/sec from the iperf server output # for TCP, there are two fo them because of waitListening count = 2 if l4Type == 'TCP' else 1 while len( re.findall( '/sec', servout ) ) < count: servout += server.monitor( timeoutms=5000 ) server.sendInt() servout += server.waitOutput() debug( 'Server output: %s\n' % servout ) result = [ self._parseIperf( servout ), self._parseIperf( cliout ) ] if l4Type == 'UDP': result.insert( 0, udpBw ) output( '*** Results: %s\n' % result ) return result def runCpuLimitTest( self, cpu, duration=5 ): """run CPU limit test with 'while true' processes. cpu: desired CPU fraction of each host duration: test duration in seconds (integer) returns a single list of measured CPU fractions as floats. """ cores = int( quietRun( 'nproc' ) ) pct = cpu * 100 info( '*** Testing CPU %.0f%% bandwidth limit\n' % pct ) hosts = self.hosts cores = int( quietRun( 'nproc' ) ) # number of processes to run a while loop on per host num_procs = int( ceil( cores * cpu ) ) pids = {} for h in hosts: pids[ h ] = [] for _core in range( num_procs ): h.cmd( 'while true; do a=1; done &' ) pids[ h ].append( h.cmd( 'echo $!' ).strip() ) outputs = {} time = {} # get the initial cpu time for each host for host in hosts: outputs[ host ] = [] with open( '/sys/fs/cgroup/cpuacct/%s/cpuacct.usage' % host, 'r' ) as f: time[ host ] = float( f.read() ) for _ in range( duration ): sleep( 1 ) for host in hosts: with open( '/sys/fs/cgroup/cpuacct/%s/cpuacct.usage' % host, 'r' ) as f: readTime = float( f.read() ) outputs[ host ].append( ( ( readTime - time[ host ] ) / 1000000000 ) / cores * 100 ) time[ host ] = readTime for h, pids in pids.items(): for pid in pids: h.cmd( 'kill -9 %s' % pid ) cpu_fractions = [] for _host, outputs in outputs.items(): for pct in outputs: cpu_fractions.append( pct ) output( '*** Results: %s\n' % cpu_fractions ) return cpu_fractions # BL: I think this can be rewritten now that we have # a real link class. def configLinkStatus( self, src, dst, status ): """Change status of src <-> dst links. src: node name dst: node name status: string {up, down}""" if src not in self.nameToNode: error( 'src not in network: %s\n' % src ) elif dst not in self.nameToNode: error( 'dst not in network: %s\n' % dst ) else: if isinstance( src, basestring ): src = self.nameToNode[ src ] if isinstance( dst, basestring ): dst = self.nameToNode[ dst ] connections = src.connectionsTo( dst ) if len( connections ) == 0: error( 'src and dst not connected: %s %s\n' % ( src, dst) ) for srcIntf, dstIntf in connections: result = srcIntf.ifconfig( status ) if result: error( 'link src status change failed: %s\n' % result ) result = dstIntf.ifconfig( status ) if result: error( 'link dst status change failed: %s\n' % result ) def interact( self ): "Start network and run our simple CLI." self.start() result = CLI( self ) self.stop() return result inited = False @classmethod def init( cls ): "Initialize Mininet" if cls.inited: return ensureRoot() fixLimits() cls.inited = True class MininetWithControlNet( Mininet ): """Control network support: Create an explicit control network. Currently this is only used/usable with the user datapath. Notes: 1. If the controller and switches are in the same (e.g. root) namespace, they can just use the loopback connection. 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!) 5. Basically nobody ever used this code, so it has been moved into its own class. 6. Ultimately we may wish to extend this to allow us to create a control network which every node's control interface is attached to.""" def configureControlNetwork( self ): "Configure control network." self.configureRoutedControlNetwork() # We still need to figure out the right way to pass # in the control network location. def configureRoutedControlNetwork( self, ip='192.168.123.1', prefixLen=16 ): """Configure a routed control network on controller and switches. For use with the user datapath only right now.""" controller = self.controllers[ 0 ] info( controller.name + ' <->' ) cip = ip snum = ipParse( ip ) for switch in self.switches: info( ' ' + switch.name ) link = self.link( switch, controller, port1=0 ) sintf, cintf = link.intf1, link.intf2 switch.controlIntf = sintf snum += 1 while snum & 0xff in [ 0, 255 ]: snum += 1 sip = ipStr( snum ) cintf.setIP( cip, prefixLen ) sintf.setIP( sip, prefixLen ) controller.setHostRoute( sip, cintf ) switch.setHostRoute( cip, sintf ) info( '\n' ) info( '*** Testing control network\n' ) while not cintf.isUp(): info( '*** Waiting for', cintf, 'to come up\n' ) sleep( 1 ) for switch in self.switches: while not sintf.isUp(): info( '*** Waiting for', sintf, 'to come up\n' ) sleep( 1 ) if self.ping( hosts=[ switch, controller ] ) != 0: error( '*** Error: control network test failed\n' ) exit( 1 ) info( '\n' ) mininet-2.2.2/mininet/node.py000066400000000000000000001655221306431124000161270ustar00rootroot00000000000000""" Node objects for Mininet. Nodes provide a simple abstraction for interacting with hosts, switches and controllers. Local nodes are simply one or more processes on the local machine. Node: superclass for all (primarily local) network nodes. Host: a virtual host. By default, a host is simply a shell; commands may be sent using Cmd (which waits for output), or using sendCmd(), which returns immediately, allowing subsequent monitoring using monitor(). Examples of how to run experiments using this functionality are provided in the examples/ directory. By default, hosts share the root file system, but they may also specify private directories. CPULimitedHost: a virtual host whose CPU bandwidth is limited by RT or CFS bandwidth limiting. Switch: superclass for switch nodes. UserSwitch: a switch using the user-space switch from the OpenFlow reference implementation. OVSSwitch: a switch using the Open vSwitch OpenFlow-compatible switch implementation (openvswitch.org). OVSBridge: an Ethernet bridge implemented using Open vSwitch. Supports STP. IVSSwitch: OpenFlow switch using the Indigo Virtual Switch. Controller: superclass for OpenFlow controllers. The default controller is controller(8) from the reference implementation. OVSController: The test controller from Open vSwitch. NOXController: a controller node using NOX (noxrepo.org). Ryu: The Ryu controller (https://osrg.github.io/ryu/) RemoteController: a remote controller node, which may use any arbitrary OpenFlow-compatible controller, and which is not created or managed by Mininet. Future enhancements: - Possibly make Node, Switch and Controller more abstract so that they can be used for both local and remote nodes - Create proxy objects for remote nodes (Mininet: Cluster Edition) """ import os import pty import re import signal import select from subprocess import Popen, PIPE from time import sleep from mininet.log import info, error, warn, debug from mininet.util import ( quietRun, errRun, errFail, moveIntf, isShellBuiltin, numCores, retry, mountCgroups ) from mininet.moduledeps import moduleDeps, pathCheck, TUN from mininet.link import Link, Intf, TCIntf, OVSIntf from re import findall from distutils.version import StrictVersion class Node( object ): """A virtual network node is simply a shell in a network namespace. We communicate with it using pipes.""" portBase = 0 # Nodes always start with eth0/port0, even in OF 1.0 def __init__( self, name, inNamespace=True, **params ): """name: name of node inNamespace: in network namespace? privateDirs: list of private directory strings or tuples params: Node parameters (see config() for details)""" # Make sure class actually works self.checkSetup() self.name = params.get( 'name', name ) self.privateDirs = params.get( 'privateDirs', [] ) self.inNamespace = params.get( 'inNamespace', inNamespace ) # Stash configuration parameters for future reference self.params = params self.intfs = {} # dict of port numbers to interfaces self.ports = {} # dict of interfaces to port numbers # replace with Port objects, eventually ? self.nameToIntf = {} # dict of interface names to Intfs # Make pylint happy ( self.shell, self.execed, self.pid, self.stdin, self.stdout, self.lastPid, self.lastCmd, self.pollOut ) = ( None, None, None, None, None, None, None, None ) self.waiting = False self.readbuf = '' # Start command interpreter shell self.startShell() self.mountPrivateDirs() # File descriptor to node mapping support # Class variables and methods inToNode = {} # mapping of input fds to nodes outToNode = {} # mapping of output fds to nodes @classmethod def fdToNode( cls, fd ): """Return node corresponding to given file descriptor. fd: file descriptor returns: node""" node = cls.outToNode.get( fd ) return node or cls.inToNode.get( fd ) # 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' # bash -i: force interactive # -s: pass $* to shell, and make process easy to find in ps # prompt is set to sentinel chr( 127 ) cmd = [ 'mnexec', opts, 'env', 'PS1=' + chr( 127 ), 'bash', '--norc', '-is', 'mininet:' + self.name ] # Spawn a shell subprocess in a pseudo-tty, to disable buffering # in the subprocess and insulate it from signals (e.g. SIGINT) # received by the parent master, slave = pty.openpty() self.shell = self._popen( cmd, stdin=slave, stdout=slave, stderr=slave, close_fds=False ) self.stdin = os.fdopen( master, 'rw' ) self.stdout = self.stdin self.pid = self.shell.pid self.pollOut = select.poll() self.pollOut.register( self.stdout ) # Maintain mapping between file descriptors and nodes # This is useful for monitoring multiple nodes # using select.poll() self.outToNode[ self.stdout.fileno() ] = self self.inToNode[ self.stdin.fileno() ] = self self.execed = False self.lastCmd = None 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 # +m: disable job control notification self.cmd( 'unset HISTFILE; stty -echo; set +m' ) def mountPrivateDirs( self ): "mount private directories" # Avoid expanding a string into a list of chars assert not isinstance( self.privateDirs, basestring ) for directory in self.privateDirs: if isinstance( directory, tuple ): # mount given private directory privateDir = directory[ 1 ] % self.__dict__ mountPoint = directory[ 0 ] self.cmd( 'mkdir -p %s' % privateDir ) self.cmd( 'mkdir -p %s' % mountPoint ) self.cmd( 'mount --bind %s %s' % ( privateDir, mountPoint ) ) else: # mount temporary filesystem on directory self.cmd( 'mkdir -p %s' % directory ) self.cmd( 'mount -n -t tmpfs tmpfs %s' % directory ) def unmountPrivateDirs( self ): "mount private directories" for directory in self.privateDirs: if isinstance( directory, tuple ): self.cmd( 'umount ', directory[ 0 ] ) else: self.cmd( 'umount ', directory ) def _popen( self, cmd, **params ): """Internal method: spawn and return a process cmd: command to run (list) params: parameters to Popen()""" # Leave this is as an instance method for now assert self return Popen( cmd, **params ) def cleanup( self ): "Help python collect its garbage." # We used to do this, but it slows us down: # Intfs may end up in root NS # for intfName in self.intfNames(): # if self.name in intfName: # quietRun( 'ip link del ' + intfName ) self.shell = None # Subshell I/O, commands and control def read( self, maxbytes=1024 ): """Buffered read from node, potentially blocking. maxbytes: maximum number of bytes to return""" count = len( self.readbuf ) if count < maxbytes: data = os.read( self.stdout.fileno(), maxbytes - count ) self.readbuf += data if maxbytes >= len( self.readbuf ): result = self.readbuf self.readbuf = '' else: result = self.readbuf[ :maxbytes ] self.readbuf = self.readbuf[ maxbytes: ] return result def readline( self ): """Buffered readline from node, potentially blocking. returns: line (minus newline) or None""" self.readbuf += self.read( 1024 ) if '\n' not in self.readbuf: return None pos = self.readbuf.find( '\n' ) line = self.readbuf[ 0: pos ] self.readbuf = self.readbuf[ pos + 1: ] return line def write( self, data ): """Write data to node. data: string""" os.write( self.stdin.fileno(), data ) def terminate( self ): "Send kill signal to Node and clean up after it." self.unmountPrivateDirs() if self.shell: if self.shell.poll() is None: os.killpg( self.shell.pid, signal.SIGHUP ) self.cleanup() def stop( self, deleteIntfs=False ): """Stop node. deleteIntfs: delete interfaces? (False)""" if deleteIntfs: self.deleteIntfs() self.terminate() def waitReadable( self, timeoutms=None ): """Wait until node's output is readable. timeoutms: timeout in ms or None to wait indefinitely. returns: result of poll()""" if len( self.readbuf ) == 0: return self.pollOut.poll( timeoutms ) def sendCmd( self, *args, **kwargs ): """Send a command, followed by a command to echo a sentinel, and return without waiting for the command to complete. args: command and arguments, or string printPid: print command's PID? (False)""" assert self.shell and not self.waiting printPid = kwargs.get( 'printPid', False ) # Allow sendCmd( [ list ] ) if len( args ) == 1 and isinstance( args[ 0 ], list ): cmd = args[ 0 ] # Allow sendCmd( cmd, arg1, arg2... ) elif len( args ) > 0: cmd = args # Convert to string if not isinstance( cmd, str ): cmd = ' '.join( [ str( c ) for c in cmd ] ) if not re.search( r'\w', cmd ): # Replace empty commands with something harmless cmd = 'echo -n' self.lastCmd = cmd # if a builtin command is backgrounded, it still yields a PID if len( cmd ) > 0 and cmd[ -1 ] == '&': # print ^A{pid}\n so monitor() can set lastPid cmd += ' printf "\\001%d\\012" $! ' elif printPid and not isShellBuiltin( cmd ): cmd = 'mnexec -p ' + cmd self.write( cmd + '\n' ) self.lastPid = None self.waiting = True def sendInt( self, intr=chr( 3 ) ): "Interrupt running command." debug( 'sendInt: writing chr(%d)\n' % ord( intr ) ) self.write( intr ) def monitor( self, timeoutms=None, findPid=True ): """Monitor and return the output of a command. Set self.waiting to False if command has completed. timeoutms: timeout in ms or None to wait indefinitely findPid: look for PID from mnexec -p""" ready = self.waitReadable( timeoutms ) if not ready: return '' 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 ): 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 data = data[ :-1 ] elif chr( 127 ) in data: self.waiting = False data = data.replace( chr( 127 ), '' ) return data def waitOutput( self, verbose=False, findPid=True ): """Wait for a command to complete. Completion is signaled by a sentinel character, ASCII(127) appearing in the output stream. Wait for the sentinel and return the output, including trailing newline. verbose: print output interactively""" log = info if verbose else debug output = '' while self.waiting: data = self.monitor( findPid=findPid ) output += data log( data ) return output def cmd( self, *args, **kwargs ): """Send a command, wait for output, and return it. cmd: string""" verbose = kwargs.get( 'verbose', False ) log = info if verbose else debug log( '*** %s : %s\n' % ( self.name, args ) ) if self.shell: self.sendCmd( *args, **kwargs ) return self.waitOutput( verbose ) else: warn( '(%s exited - ignoring cmd%s)\n' % ( self, args ) ) def cmdPrint( self, *args): """Call cmd and printing its output cmd: string""" return self.cmd( *args, **{ 'verbose': True } ) def popen( self, *args, **kwargs ): """Return a Popen() object in our namespace args: Popen() args, single list, or string kwargs: Popen() keyword args""" defaults = { 'stdout': PIPE, 'stderr': PIPE, 'mncmd': [ 'mnexec', '-da', str( self.pid ) ] } defaults.update( kwargs ) if len( args ) == 1: if isinstance( args[ 0 ], list ): # popen([cmd, arg1, arg2...]) cmd = args[ 0 ] elif isinstance( args[ 0 ], basestring ): # popen("cmd arg1 arg2...") cmd = args[ 0 ].split() else: raise Exception( 'popen() requires a string or list' ) elif len( args ) > 0: # popen( cmd, arg1, arg2... ) cmd = list( args ) # Attach to our namespace using mnexec -a cmd = defaults.pop( 'mncmd' ) + cmd # Shell requires a string, not a list! if defaults.get( 'shell', False ): cmd = ' '.join( cmd ) popen = self._popen( cmd, **defaults ) return popen def pexec( self, *args, **kwargs ): """Execute a command using popen returns: out, err, exitcode""" popen = self.popen( *args, stdin=PIPE, stdout=PIPE, stderr=PIPE, **kwargs ) # Warning: this can fail with large numbers of fds! out, err = popen.communicate() exitcode = popen.wait() return out, err, exitcode # Interface management, configuration, and routing # BL notes: This might be a bit redundant or over-complicated. # However, it does allow a bit of specialization, including # changing the canonical interface names. It's also tricky since # the real interfaces are created as veth pairs, so we can't # make a single interface at a time. def newPort( self ): "Return the next port number to allocate." if len( self.ports ) > 0: return max( self.ports.values() ) + 1 return self.portBase def addIntf( self, intf, port=None, moveIntfFn=moveIntf ): """Add an interface. intf: interface port: port number (optional, typically OpenFlow port number) moveIntfFn: function to move interface (optional)""" if port is None: port = self.newPort() self.intfs[ port ] = intf self.ports[ intf ] = port self.nameToIntf[ intf.name ] = intf debug( '\n' ) debug( 'added intf %s (%d) to node %s\n' % ( intf, port, self.name ) ) if self.inNamespace: debug( 'moving', intf, 'into namespace for', self.name, '\n' ) moveIntfFn( intf.name, self ) def defaultIntf( self ): "Return interface for lowest port" ports = self.intfs.keys() if ports: return self.intfs[ min( ports ) ] else: warn( '*** defaultIntf: warning:', self.name, 'has no interfaces\n' ) def intf( self, intf=None ): """Return our interface object with given string name, default intf if name is falsy (None, empty string, etc). or the input intf arg. Having this fcn return its arg for Intf objects makes it easier to construct functions with flexible input args for interfaces (those that accept both string names and Intf objects). """ if not intf: return self.defaultIntf() elif isinstance( intf, basestring): return self.nameToIntf[ intf ] else: return intf def connectionsTo( self, node): "Return [ intf1, intf2... ] for all intfs that connect self to node." # We could optimize this if it is important connections = [] for intf in self.intfList(): link = intf.link if link: node1, node2 = link.intf1.node, link.intf2.node if node1 == self and node2 == node: connections += [ ( intf, link.intf2 ) ] elif node1 == node and node2 == self: connections += [ ( intf, link.intf1 ) ] return connections def deleteIntfs( self, checkName=True ): """Delete all of our interfaces. checkName: only delete interfaces that contain our name""" # In theory the interfaces should go away after we shut down. # However, this takes time, so we're better off removing them # explicitly so that we won't get errors if we run before they # have been removed by the kernel. Unfortunately this is very slow, # at least with Linux kernels before 2.6.33 for intf in self.intfs.values(): # Protect against deleting hardware interfaces if ( self.name in intf.name ) or ( not checkName ): intf.delete() info( '.' ) # Routing support def setARP( self, ip, mac ): """Add an ARP entry. ip: IP address as string mac: MAC address as string""" result = self.cmd( 'arp', '-s', ip, mac ) return result def setHostRoute( self, ip, intf ): """Add route to host. ip: IP address as dotted decimal intf: string, interface name""" return self.cmd( 'route add -host', ip, 'dev', intf ) def setDefaultRoute( self, intf=None ): """Set the default route to go through intf. intf: Intf or {dev via ...}""" # Note setParam won't call us if intf is none if isinstance( intf, basestring ) and ' ' in intf: params = intf else: params = 'dev %s' % intf # Do this in one line in case we're messing with the root namespace self.cmd( 'ip route del default; ip route add default', params ) # Convenience and configuration methods def setMAC( self, mac, intf=None ): """Set the MAC address for an interface. intf: intf or intf name mac: MAC address as string""" return self.intf( intf ).setMAC( mac ) def setIP( self, ip, prefixLen=8, intf=None, **kwargs ): """Set the IP address for an interface. intf: intf or intf name ip: IP address as a string prefixLen: prefix length, e.g. 8 for /8 or 16M addrs kwargs: any additional arguments for intf.setIP""" return self.intf( intf ).setIP( ip, prefixLen, **kwargs ) def IP( self, intf=None ): "Return IP address of a node or specific interface." return self.intf( intf ).IP() def MAC( self, intf=None ): "Return MAC address of a node or specific interface." return self.intf( intf ).MAC() def intfIsUp( self, intf=None ): "Check if an interface is up." return self.intf( intf ).isUp() # The reason why we configure things in this way is so # That the parameters can be listed and documented in # the config method. # Dealing with subclasses and superclasses is slightly # annoying, but at least the information is there! def setParam( self, results, method, **param ): """Internal method: configure a *single* parameter results: dict of results to update method: config method name param: arg=value (ignore if value=None) value may also be list or dict""" name, value = param.items()[ 0 ] if value is None: return f = getattr( self, method, None ) if not f: return if isinstance( value, list ): result = f( *value ) elif isinstance( value, dict ): result = f( **value ) else: result = f( value ) results[ name ] = result return result def config( self, mac=None, ip=None, defaultRoute=None, lo='up', **_params ): """Configure Node according to (optional) parameters: mac: MAC address for default interface ip: IP address for default interface ifconfig: arbitrary interface configuration Subclasses should override this method and call the parent class's config(**params)""" # If we were overriding this method, we would call # the superclass config method here as follows: # r = Parent.config( **_params ) r = {} self.setParam( r, 'setMAC', mac=mac ) self.setParam( r, 'setIP', ip=ip ) self.setParam( r, 'setDefaultRoute', defaultRoute=defaultRoute ) # This should be examined self.cmd( 'ifconfig lo ' + lo ) return r def configDefault( self, **moreParams ): "Configure with default parameters" self.params.update( moreParams ) self.config( **self.params ) # This is here for backward compatibility def linkTo( self, node, link=Link ): """(Deprecated) Link to another node replace with Link( node1, node2)""" return link( self, node ) # Other methods def intfList( self ): "List of our interfaces sorted by port number" return [ self.intfs[ p ] for p in sorted( self.intfs.iterkeys() ) ] def intfNames( self ): "The names of our interfaces sorted by port number" return [ str( i ) for i in self.intfList() ] def __repr__( self ): "More informative string representation" intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() ) for i in self.intfList() ] ) ) return '<%s %s: %s pid=%s> ' % ( self.__class__.__name__, self.name, intfs, self.pid ) def __str__( self ): "Abbreviated string representation" return self.name # Automatic class setup support isSetup = False @classmethod def checkSetup( cls ): "Make sure our class and superclasses are set up" while cls and not getattr( cls, 'isSetup', True ): cls.setup() cls.isSetup = True # Make pylint happy cls = getattr( type( cls ), '__base__', None ) @classmethod def setup( cls ): "Make sure our class dependencies are available" pathCheck( 'mnexec', 'ifconfig', moduleName='Mininet') class Host( Node ): "A host is simply a Node" pass class CPULimitedHost( Host ): "CPU limited host" def __init__( self, name, sched='cfs', **kwargs ): Host.__init__( self, name, **kwargs ) # Initialize class if necessary if not CPULimitedHost.inited: CPULimitedHost.init() # Create a cgroup and move shell into it self.cgroup = 'cpu,cpuacct,cpuset:/' + self.name errFail( 'cgcreate -g ' + self.cgroup ) # We don't add ourselves to a cpuset because you must # specify the cpu and memory placement first errFail( 'cgclassify -g cpu,cpuacct:/%s %s' % ( self.name, self.pid ) ) # BL: Setting the correct period/quota is tricky, particularly # for RT. RT allows very small quotas, but the overhead # seems to be high. CFS has a mininimum quota of 1 ms, but # still does better with larger period values. self.period_us = kwargs.get( 'period_us', 100000 ) self.sched = sched if sched == 'rt': self.checkRtGroupSched() self.rtprio = 20 def cgroupSet( self, param, value, resource='cpu' ): "Set a cgroup parameter and return its value" cmd = 'cgset -r %s.%s=%s /%s' % ( resource, param, value, self.name ) quietRun( cmd ) nvalue = int( self.cgroupGet( param, resource ) ) if nvalue != value: error( '*** error: cgroupSet: %s set to %s instead of %s\n' % ( param, nvalue, value ) ) return nvalue def cgroupGet( self, param, resource='cpu' ): "Return value of cgroup parameter" cmd = 'cgget -r %s.%s /%s' % ( resource, param, self.name ) return int( quietRun( cmd ).split()[ -1 ] ) def cgroupDel( self ): "Clean up our cgroup" # info( '*** deleting cgroup', self.cgroup, '\n' ) _out, _err, exitcode = errRun( 'cgdelete -r ' + self.cgroup ) # Sometimes cgdelete returns a resource busy error but still # deletes the group; next attempt will give "no such file" return exitcode == 0 or ( 'no such file' in _err.lower() ) def popen( self, *args, **kwargs ): """Return a Popen() object in node's namespace args: Popen() args, single list, or string kwargs: Popen() keyword args""" # Tell mnexec to execute command in our cgroup mncmd = [ 'mnexec', '-g', self.name, '-da', str( self.pid ) ] # if our cgroup is not given any cpu time, # we cannot assign the RR Scheduler. if self.sched == 'rt': if int( self.cgroupGet( 'rt_runtime_us', 'cpu' ) ) <= 0: mncmd += [ '-r', str( self.rtprio ) ] else: debug( '*** error: not enough cpu time available for %s.' % self.name, 'Using cfs scheduler for subprocess\n' ) return Host.popen( self, *args, mncmd=mncmd, **kwargs ) def cleanup( self ): "Clean up Node, then clean up our cgroup" super( CPULimitedHost, self ).cleanup() retry( retries=3, delaySecs=.1, fn=self.cgroupDel ) _rtGroupSched = False # internal class var: Is CONFIG_RT_GROUP_SCHED set? @classmethod def checkRtGroupSched( cls ): "Check (Ubuntu,Debian) kernel config for CONFIG_RT_GROUP_SCHED for RT" if not cls._rtGroupSched: release = quietRun( 'uname -r' ).strip('\r\n') output = quietRun( 'grep CONFIG_RT_GROUP_SCHED /boot/config-%s' % release ) if output == '# CONFIG_RT_GROUP_SCHED is not set\n': error( '\n*** error: please enable RT_GROUP_SCHED ' 'in your kernel\n' ) exit( 1 ) cls._rtGroupSched = True def chrt( self ): "Set RT scheduling priority" quietRun( 'chrt -p %s %s' % ( self.rtprio, self.pid ) ) result = quietRun( 'chrt -p %s' % self.pid ) firstline = result.split( '\n' )[ 0 ] lastword = firstline.split( ' ' )[ -1 ] if lastword != 'SCHED_RR': error( '*** error: could not assign SCHED_RR to %s\n' % self.name ) return lastword def rtInfo( self, f ): "Internal method: return parameters for RT bandwidth" pstr, qstr = 'rt_period_us', 'rt_runtime_us' # RT uses wall clock time for period and quota quota = int( self.period_us * f ) return pstr, qstr, self.period_us, quota def cfsInfo( self, f ): "Internal method: return parameters for CFS bandwidth" pstr, qstr = 'cfs_period_us', 'cfs_quota_us' # CFS uses wall clock time for period and CPU time for quota. quota = int( self.period_us * f * numCores() ) period = self.period_us if f > 0 and quota < 1000: debug( '(cfsInfo: increasing default period) ' ) quota = 1000 period = int( quota / f / numCores() ) # Reset to unlimited on negative quota if quota < 0: quota = -1 return pstr, qstr, period, quota # BL comment: # This may not be the right API, # since it doesn't specify CPU bandwidth in "absolute" # units the way link bandwidth is specified. # We should use MIPS or SPECINT or something instead. # Alternatively, we should change from system fraction # to CPU seconds per second, essentially assuming that # all CPUs are the same. def setCPUFrac( self, f, sched=None ): """Set overall CPU fraction for this host f: CPU bandwidth limit (positive fraction, or -1 for cfs unlimited) sched: 'rt' or 'cfs' Note 'cfs' requires CONFIG_CFS_BANDWIDTH, and 'rt' requires CONFIG_RT_GROUP_SCHED""" if not sched: sched = self.sched if sched == 'rt': if not f or f < 0: raise Exception( 'Please set a positive CPU fraction' ' for sched=rt\n' ) pstr, qstr, period, quota = self.rtInfo( f ) elif sched == 'cfs': pstr, qstr, period, quota = self.cfsInfo( f ) else: return # Set cgroup's period and quota setPeriod = self.cgroupSet( pstr, period ) setQuota = self.cgroupSet( qstr, quota ) if sched == 'rt': # Set RT priority if necessary sched = self.chrt() info( '(%s %d/%dus) ' % ( sched, setQuota, setPeriod ) ) def setCPUs( self, cores, mems=0 ): "Specify (real) cores that our cgroup can run on" if not cores: return if isinstance( cores, list ): cores = ','.join( [ str( c ) for c in cores ] ) self.cgroupSet( resource='cpuset', param='cpus', value=cores ) # Memory placement is probably not relevant, but we # must specify it anyway self.cgroupSet( resource='cpuset', param='mems', value=mems) # We have to do this here after we've specified # cpus and mems errFail( 'cgclassify -g cpuset:/%s %s' % ( self.name, self.pid ) ) def config( self, cpu=-1, cores=None, **params ): """cpu: desired overall system CPU fraction cores: (real) core(s) this host can run on params: parameters for Node.config()""" r = Node.config( self, **params ) # Was considering cpu={'cpu': cpu , 'sched': sched}, but # that seems redundant self.setParam( r, 'setCPUFrac', cpu=cpu ) self.setParam( r, 'setCPUs', cores=cores ) return r inited = False @classmethod def init( cls ): "Initialization for CPULimitedHost class" mountCgroups() cls.inited = True # Some important things to note: # # The "IP" address which setIP() assigns to the switch is not # an "IP address for the switch" in the sense of IP routing. # Rather, it is the IP address for the control interface, # on the control network, and it is only relevant to the # controller. If you are running in the root namespace # (which is the only way to run OVS at the moment), the # control interface is the loopback interface, and you # normally never want to change its IP address! # # In general, you NEVER want to attempt to use Linux's # network stack (i.e. ifconfig) to "assign" an IP address or # MAC address to a switch data port. Instead, you "assign" # the IP and MAC addresses in the controller by specifying # packets that you want to receive or send. The "MAC" address # reported by ifconfig for a switch data port is essentially # meaningless. It is important to understand this if you # want to create a functional router using OpenFlow. class Switch( Node ): """A Switch is a Node that is running (or has execed?) an OpenFlow switch.""" portBase = 1 # Switches start with port 1 in OpenFlow dpidLen = 16 # digits in dpid passed to switch def __init__( self, name, dpid=None, opts='', listenPort=None, **params): """dpid: dpid hex string (or None to derive from name, e.g. s1 -> 1) opts: additional switch options listenPort: port to listen on for dpctl connections""" Node.__init__( self, name, **params ) self.dpid = self.defaultDpid( dpid ) self.opts = opts self.listenPort = listenPort if not self.inNamespace: self.controlIntf = Intf( 'lo', self, port=0 ) def defaultDpid( self, dpid=None ): "Return correctly formatted dpid from dpid or switch name (s1 -> 1)" if dpid: # Remove any colons and make sure it's a good hex number dpid = dpid.translate( None, ':' ) assert len( dpid ) <= self.dpidLen and int( dpid, 16 ) >= 0 else: # Use hex of the first number in the switch name nums = re.findall( r'\d+', self.name ) if nums: dpid = hex( int( nums[ 0 ] ) )[ 2: ] else: raise Exception( 'Unable to derive default datapath ID - ' 'please either specify a dpid or use a ' 'canonical switch name such as s23.' ) return '0' * ( self.dpidLen - len( dpid ) ) + dpid def defaultIntf( self ): "Return control interface" if self.controlIntf: return self.controlIntf else: return Node.defaultIntf( self ) def sendCmd( self, *cmd, **kwargs ): """Send command to Node. cmd: string""" kwargs.setdefault( 'printPid', False ) if not self.execed: return Node.sendCmd( self, *cmd, **kwargs ) else: error( '*** Error: %s has execed and cannot accept commands' % self.name ) def connected( self ): "Is the switch connected to a controller? (override this method)" # Assume that we are connected by default to whatever we need to # be connected to. This should be overridden by any OpenFlow # switch, but not by a standalone bridge. debug( 'Assuming', repr( self ), 'is connected to a controller\n' ) return True def stop( self, deleteIntfs=True ): """Stop switch deleteIntfs: delete interfaces? (True)""" if deleteIntfs: self.deleteIntfs() def __repr__( self ): "More informative string representation" intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() ) for i in self.intfList() ] ) ) return '<%s %s: %s pid=%s> ' % ( self.__class__.__name__, self.name, intfs, self.pid ) class UserSwitch( Switch ): "User-space switch." dpidLen = 12 def __init__( self, name, dpopts='--no-slicing', **kwargs ): """Init. name: name for the switch dpopts: additional arguments to ofdatapath (--no-slicing)""" Switch.__init__( self, name, **kwargs ) pathCheck( 'ofdatapath', 'ofprotocol', moduleName='the OpenFlow reference user switch' + '(openflow.org)' ) if self.listenPort: self.opts += ' --listen=ptcp:%i ' % self.listenPort else: self.opts += ' --listen=punix:/tmp/%s.listen' % self.name self.dpopts = dpopts @classmethod def setup( cls ): "Ensure any dependencies are loaded; if not, try to load them." if not os.path.exists( '/dev/net/tun' ): moduleDeps( add=TUN ) def dpctl( self, *args ): "Run dpctl command" listenAddr = None if not self.listenPort: listenAddr = 'unix:/tmp/%s.listen' % self.name else: listenAddr = 'tcp:127.0.0.1:%i' % self.listenPort return self.cmd( 'dpctl ' + ' '.join( args ) + ' ' + listenAddr ) def connected( self ): "Is the switch connected to a controller?" status = self.dpctl( 'status' ) return ( 'remote.is-connected=true' in status and 'local.is-connected=true' in status ) @staticmethod def TCReapply( intf ): """Unfortunately user switch and Mininet are fighting over tc queuing disciplines. To resolve the conflict, we re-create the user switch's configuration, but as a leaf of the TCIntf-created configuration.""" if isinstance( intf, TCIntf ): ifspeed = 10000000000 # 10 Gbps minspeed = ifspeed * 0.001 res = intf.config( **intf.params ) if res is None: # link may not have TC parameters return # Re-add qdisc, root, and default classes user switch created, but # with new parent, as setup by Mininet's TCIntf parent = res['parent'] intf.tc( "%s qdisc add dev %s " + parent + " handle 1: htb default 0xfffe" ) intf.tc( "%s class add dev %s classid 1:0xffff parent 1: htb rate " + str(ifspeed) ) intf.tc( "%s class add dev %s classid 1:0xfffe parent 1:0xffff " + "htb rate " + str(minspeed) + " ceil " + str(ifspeed) ) def start( self, controllers ): """Start OpenFlow reference user datapath. Log to /tmp/sN-{ofd,ofp}.log. controllers: list of controller objects""" # Add controllers clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port ) for c in controllers ] ) ofdlog = '/tmp/' + self.name + '-ofd.log' ofplog = '/tmp/' + self.name + '-ofp.log' intfs = [ str( i ) for i in self.intfList() if not i.IP() ] self.cmd( 'ofdatapath -i ' + ','.join( intfs ) + ' punix:/tmp/' + self.name + ' -d %s ' % self.dpid + self.dpopts + ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' ) self.cmd( 'ofprotocol unix:/tmp/' + self.name + ' ' + clist + ' --fail=closed ' + self.opts + ' 1> ' + ofplog + ' 2>' + ofplog + ' &' ) if "no-slicing" not in self.dpopts: # Only TCReapply if slicing is enable sleep(1) # Allow ofdatapath to start before re-arranging qdisc's for intf in self.intfList(): if not intf.IP(): self.TCReapply( intf ) def stop( self, deleteIntfs=True ): """Stop OpenFlow reference user datapath. deleteIntfs: delete interfaces? (True)""" self.cmd( 'kill %ofdatapath' ) self.cmd( 'kill %ofprotocol' ) super( UserSwitch, self ).stop( deleteIntfs ) class OVSSwitch( Switch ): "Open vSwitch switch. Depends on ovs-vsctl." def __init__( self, name, failMode='secure', datapath='kernel', inband=False, protocols=None, reconnectms=1000, stp=False, batch=False, **params ): """name: name for switch failMode: controller loss behavior (secure|standalone) datapath: userspace or kernel mode (kernel|user) inband: use in-band control (False) protocols: use specific OpenFlow version(s) (e.g. OpenFlow13) Unspecified (or old OVS version) uses OVS default reconnectms: max reconnect timeout in ms (0/None for default) stp: enable STP (False, requires failMode=standalone) batch: enable batch startup (False)""" Switch.__init__( self, name, **params ) self.failMode = failMode self.datapath = datapath self.inband = inband self.protocols = protocols self.reconnectms = reconnectms self.stp = stp self._uuids = [] # controller UUIDs self.batch = batch self.commands = [] # saved commands for batch startup @classmethod def setup( cls ): "Make sure Open vSwitch is installed and working" pathCheck( 'ovs-vsctl', moduleName='Open vSwitch (openvswitch.org)') # This should no longer be needed, and it breaks # with OVS 1.7 which has renamed the kernel module: # moduleDeps( subtract=OF_KMOD, add=OVS_KMOD ) out, err, exitcode = errRun( 'ovs-vsctl -t 1 show' ) if exitcode: error( out + err + 'ovs-vsctl exited with code %d\n' % exitcode + '*** Error connecting to ovs-db with ovs-vsctl\n' 'Make sure that Open vSwitch is installed, ' 'that ovsdb-server is running, and that\n' '"ovs-vsctl show" works correctly.\n' 'You may wish to try ' '"service openvswitch-switch start".\n' ) exit( 1 ) version = quietRun( 'ovs-vsctl --version' ) cls.OVSVersion = findall( r'\d+\.\d+', version )[ 0 ] @classmethod def isOldOVS( cls ): "Is OVS ersion < 1.10?" return ( StrictVersion( cls.OVSVersion ) < StrictVersion( '1.10' ) ) def dpctl( self, *args ): "Run ovs-ofctl command" return self.cmd( 'ovs-ofctl', args[ 0 ], self, *args[ 1: ] ) def vsctl( self, *args, **kwargs ): "Run ovs-vsctl command (or queue for later execution)" if self.batch: cmd = ' '.join( str( arg ).strip() for arg in args ) self.commands.append( cmd ) else: return self.cmd( 'ovs-vsctl', *args, **kwargs ) @staticmethod def TCReapply( intf ): """Unfortunately OVS and Mininet are fighting over tc queuing disciplines. As a quick hack/ workaround, we clear OVS's and reapply our own.""" if isinstance( intf, TCIntf ): intf.config( **intf.params ) def attach( self, intf ): "Connect a data port" self.vsctl( 'add-port', self, intf ) self.cmd( 'ifconfig', intf, 'up' ) self.TCReapply( intf ) def detach( self, intf ): "Disconnect a data port" self.vsctl( 'del-port', self, intf ) def controllerUUIDs( self, update=False ): """Return ovsdb UUIDs for our controllers update: update cached value""" if not self._uuids or update: controllers = self.cmd( 'ovs-vsctl -- get Bridge', self, 'Controller' ).strip() if controllers.startswith( '[' ) and controllers.endswith( ']' ): controllers = controllers[ 1 : -1 ] if controllers: self._uuids = [ c.strip() for c in controllers.split( ',' ) ] return self._uuids def connected( self ): "Are we connected to at least one of our controllers?" for uuid in self.controllerUUIDs(): if 'true' in self.vsctl( '-- get Controller', uuid, 'is_connected' ): return True return self.failMode == 'standalone' def intfOpts( self, intf ): "Return OVS interface options for intf" opts = '' if not self.isOldOVS(): # ofport_request is not supported on old OVS opts += ' ofport_request=%s' % self.ports[ intf ] # Patch ports don't work well with old OVS if isinstance( intf, OVSIntf ): intf1, intf2 = intf.link.intf1, intf.link.intf2 peer = intf1 if intf1 != intf else intf2 opts += ' type=patch options:peer=%s' % peer return '' if not opts else ' -- set Interface %s' % intf + opts def bridgeOpts( self ): "Return OVS bridge options" opts = ( ' other_config:datapath-id=%s' % self.dpid + ' fail_mode=%s' % self.failMode ) if not self.inband: opts += ' other-config:disable-in-band=true' if self.datapath == 'user': opts += ' datapath_type=netdev' if self.protocols and not self.isOldOVS(): opts += ' protocols=%s' % self.protocols if self.stp and self.failMode == 'standalone': opts += ' stp_enable=true' return opts def start( self, controllers ): "Start up a new OVS OpenFlow switch using ovs-vsctl" if self.inNamespace: raise Exception( 'OVS kernel switch does not work in a namespace' ) int( self.dpid, 16 ) # DPID must be a hex string # Command to add interfaces intfs = ''.join( ' -- add-port %s %s' % ( self, intf ) + self.intfOpts( intf ) for intf in self.intfList() if self.ports[ intf ] and not intf.IP() ) # Command to create controller entries clist = [ ( self.name + c.name, '%s:%s:%d' % ( c.protocol, c.IP(), c.port ) ) for c in controllers ] if self.listenPort: clist.append( ( self.name + '-listen', 'ptcp:%s' % self.listenPort ) ) ccmd = '-- --id=@%s create Controller target=\\"%s\\"' if self.reconnectms: ccmd += ' max_backoff=%d' % self.reconnectms cargs = ' '.join( ccmd % ( name, target ) for name, target in clist ) # Controller ID list cids = ','.join( '@%s' % name for name, _target in clist ) # Try to delete any existing bridges with the same name if not self.isOldOVS(): cargs += ' -- --if-exists del-br %s' % self # One ovs-vsctl command to rule them all! self.vsctl( cargs + ' -- add-br %s' % self + ' -- set bridge %s controller=[%s]' % ( self, cids ) + self.bridgeOpts() + intfs ) # If necessary, restore TC config overwritten by OVS if not self.batch: for intf in self.intfList(): self.TCReapply( intf ) # This should be ~ int( quietRun( 'getconf ARG_MAX' ) ), # but the real limit seems to be much lower argmax = 128000 @classmethod def batchStartup( cls, switches, run=errRun ): """Batch startup for OVS switches: switches to start up run: function to run commands (errRun)""" info( '...' ) cmds = 'ovs-vsctl' for switch in switches: if switch.isOldOVS(): # Ideally we'd optimize this also run( 'ovs-vsctl del-br %s' % switch ) for cmd in switch.commands: cmd = cmd.strip() # Don't exceed ARG_MAX if len( cmds ) + len( cmd ) >= cls.argmax: run( cmds, shell=True ) cmds = 'ovs-vsctl' cmds += ' ' + cmd switch.cmds = [] switch.batch = False if cmds: run( cmds, shell=True ) # Reapply link config if necessary... for switch in switches: for intf in switch.intfs.itervalues(): if isinstance( intf, TCIntf ): intf.config( **intf.params ) return switches def stop( self, deleteIntfs=True ): """Terminate OVS switch. deleteIntfs: delete interfaces? (True)""" self.cmd( 'ovs-vsctl del-br', self ) if self.datapath == 'user': self.cmd( 'ip link del', self ) super( OVSSwitch, self ).stop( deleteIntfs ) @classmethod def batchShutdown( cls, switches, run=errRun ): "Shut down a list of OVS switches" delcmd = 'del-br %s' if switches and not switches[ 0 ].isOldOVS(): delcmd = '--if-exists ' + delcmd # First, delete them all from ovsdb run( 'ovs-vsctl ' + ' -- '.join( delcmd % s for s in switches ) ) # Next, shut down all of the processes pids = ' '.join( str( switch.pid ) for switch in switches ) run( 'kill -HUP ' + pids ) for switch in switches: switch.shell = None return switches OVSKernelSwitch = OVSSwitch class OVSBridge( OVSSwitch ): "OVSBridge is an OVSSwitch in standalone/bridge mode" def __init__( self, *args, **kwargs ): """stp: enable Spanning Tree Protocol (False) see OVSSwitch for other options""" kwargs.update( failMode='standalone' ) OVSSwitch.__init__( self, *args, **kwargs ) def start( self, controllers ): "Start bridge, ignoring controllers argument" OVSSwitch.start( self, controllers=[] ) def connected( self ): "Are we forwarding yet?" if self.stp: status = self.dpctl( 'show' ) return 'STP_FORWARD' in status and not 'STP_LEARN' in status else: return True class IVSSwitch( Switch ): "Indigo Virtual Switch" def __init__( self, name, verbose=False, **kwargs ): Switch.__init__( self, name, **kwargs ) self.verbose = verbose @classmethod def setup( cls ): "Make sure IVS is installed" pathCheck( 'ivs-ctl', 'ivs', moduleName="Indigo Virtual Switch (projectfloodlight.org)" ) out, err, exitcode = errRun( 'ivs-ctl show' ) if exitcode: error( out + err + 'ivs-ctl exited with code %d\n' % exitcode + '*** The openvswitch kernel module might ' 'not be loaded. Try modprobe openvswitch.\n' ) exit( 1 ) @classmethod def batchShutdown( cls, switches ): "Kill each IVS switch, to be waited on later in stop()" for switch in switches: switch.cmd( 'kill %ivs' ) return switches def start( self, controllers ): "Start up a new IVS switch" args = ['ivs'] args.extend( ['--name', self.name] ) args.extend( ['--dpid', self.dpid] ) if self.verbose: args.extend( ['--verbose'] ) for intf in self.intfs.values(): if not intf.IP(): args.extend( ['-i', intf.name] ) for c in controllers: args.extend( ['-c', '%s:%d' % (c.IP(), c.port)] ) if self.listenPort: args.extend( ['--listen', '127.0.0.1:%i' % self.listenPort] ) args.append( self.opts ) logfile = '/tmp/ivs.%s.log' % self.name self.cmd( ' '.join(args) + ' >' + logfile + ' 2>&1 on controller. Log to /tmp/cN.log""" pathCheck( self.command ) cout = '/tmp/' + self.name + '.log' if self.cdir is not None: self.cmd( 'cd ' + self.cdir ) self.cmd( self.command + ' ' + self.cargs % self.port + ' 1>' + cout + ' 2>' + cout + ' &' ) self.execed = False def stop( self, *args, **kwargs ): "Stop controller." self.cmd( 'kill %' + self.command ) self.cmd( 'wait %' + self.command ) super( Controller, self ).stop( *args, **kwargs ) def IP( self, intf=None ): "Return IP address of the Controller" if self.intfs: ip = Node.IP( self, intf ) else: ip = self.ip return ip def __repr__( self ): "More informative string representation" return '<%s %s: %s:%s pid=%s> ' % ( self.__class__.__name__, self.name, self.IP(), self.port, self.pid ) @classmethod def isAvailable( cls ): "Is controller available?" return quietRun( 'which controller' ) class OVSController( Controller ): "Open vSwitch controller" def __init__( self, name, command='ovs-controller', **kwargs ): if quietRun( 'which test-controller' ): command = 'test-controller' Controller.__init__( self, name, command=command, **kwargs ) @classmethod def isAvailable( cls ): return ( quietRun( 'which ovs-controller' ) or quietRun( 'which test-controller' ) or quietRun( 'which ovs-testcontroller' ) ) class NOX( Controller ): "Controller to run a NOX application." def __init__( self, name, *noxArgs, **kwargs ): """Init. name: name to give controller noxArgs: arguments (strings) to pass to NOX""" if not noxArgs: warn( 'warning: no NOX modules specified; ' 'running packetdump only\n' ) noxArgs = [ 'packetdump' ] elif type( noxArgs ) not in ( list, tuple ): noxArgs = [ noxArgs ] if 'NOX_CORE_DIR' not in os.environ: exit( 'exiting; please set missing NOX_CORE_DIR env var' ) noxCoreDir = os.environ[ 'NOX_CORE_DIR' ] Controller.__init__( self, name, command=noxCoreDir + '/nox_core', cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' + ' '.join( noxArgs ), cdir=noxCoreDir, **kwargs ) class Ryu( Controller ): "Controller to run Ryu application" def __init__( self, name, *ryuArgs, **kwargs ): """Init. name: name to give controller. ryuArgs: arguments and modules to pass to Ryu""" homeDir = quietRun( 'printenv HOME' ).strip( '\r\n' ) ryuCoreDir = '%s/ryu/ryu/app/' % homeDir if not ryuArgs: warn( 'warning: no Ryu modules specified; ' 'running simple_switch only\n' ) ryuArgs = [ ryuCoreDir + 'simple_switch.py' ] elif type( ryuArgs ) not in ( list, tuple ): ryuArgs = [ ryuArgs ] Controller.__init__( self, name, command='ryu-manager', cargs='--ofp-tcp-listen-port %s ' + ' '.join( ryuArgs ), cdir=ryuCoreDir, **kwargs ) class RemoteController( Controller ): "Controller running outside of Mininet's control." def __init__( self, name, ip='127.0.0.1', port=None, **kwargs): """Init. name: name to give controller ip: the IP address where the remote controller is listening port: the port where the remote controller is listening""" Controller.__init__( self, name, ip=ip, port=port, **kwargs ) def start( self ): "Overridden to do nothing." return def stop( self ): "Overridden to do nothing." return def checkListening( self ): "Warn if remote controller is not accessible" if self.port is not None: self.isListening( self.ip, self.port ) else: for port in 6653, 6633: if self.isListening( self.ip, port ): self.port = port info( "Connecting to remote controller" " at %s:%d\n" % ( self.ip, self.port )) break if self.port is None: self.port = 6653 warn( "Setting remote controller" " to %s:%d\n" % ( self.ip, self.port )) def isListening( self, ip, port ): "Check if a remote controller is listening at a specific ip and port" listening = self.cmd( "echo A | telnet -e A %s %d" % ( ip, port ) ) if 'Connected' not in listening: warn( "Unable to contact the remote controller" " at %s:%d\n" % ( ip, port ) ) return False else: return True DefaultControllers = ( Controller, OVSController ) def findController( controllers=DefaultControllers ): "Return first available controller from list, if any" for controller in controllers: if controller.isAvailable(): return controller def DefaultController( name, controllers=DefaultControllers, **kwargs ): "Find a controller that is available and instantiate it" controller = findController( controllers ) if not controller: raise Exception( 'Could not find a default OpenFlow controller' ) return controller( name, **kwargs ) def NullController( *_args, **_kwargs ): "Nonexistent controller - simply returns None" return None mininet-2.2.2/mininet/nodelib.py000066400000000000000000000126431306431124000166110ustar00rootroot00000000000000""" Node Library for Mininet This contains additional Node types which you may find to be useful. """ from mininet.node import Node, Switch from mininet.log import info, warn from mininet.moduledeps import pathCheck from mininet.util import quietRun class LinuxBridge( Switch ): "Linux Bridge (with optional spanning tree)" nextPrio = 100 # next bridge priority for spanning tree def __init__( self, name, stp=False, prio=None, **kwargs ): """stp: use spanning tree protocol? (default False) prio: optional explicit bridge priority for STP""" self.stp = stp if prio: self.prio = prio else: self.prio = LinuxBridge.nextPrio LinuxBridge.nextPrio += 1 Switch.__init__( self, name, **kwargs ) def connected( self ): "Are we forwarding yet?" if self.stp: return 'forwarding' in self.cmd( 'brctl showstp', self ) else: return True def start( self, _controllers ): "Start Linux bridge" self.cmd( 'ifconfig', self, 'down' ) self.cmd( 'brctl delbr', self ) self.cmd( 'brctl addbr', self ) if self.stp: self.cmd( 'brctl setbridgeprio', self.prio ) self.cmd( 'brctl stp', self, 'on' ) for i in self.intfList(): if self.name in i.name: self.cmd( 'brctl addif', self, i ) self.cmd( 'ifconfig', self, 'up' ) def stop( self, deleteIntfs=True ): """Stop Linux bridge deleteIntfs: delete interfaces? (True)""" self.cmd( 'ifconfig', self, 'down' ) self.cmd( 'brctl delbr', self ) super( LinuxBridge, self ).stop( deleteIntfs ) def dpctl( self, *args ): "Run brctl command" return self.cmd( 'brctl', *args ) @classmethod def setup( cls ): "Check dependencies and warn about firewalling" pathCheck( 'brctl', moduleName='bridge-utils' ) # Disable Linux bridge firewalling so that traffic can flow! for table in 'arp', 'ip', 'ip6': cmd = 'sysctl net.bridge.bridge-nf-call-%stables' % table out = quietRun( cmd ).strip() if out.endswith( '1' ): warn( 'Warning: Linux bridge may not work with', out, '\n' ) class NAT( Node ): "NAT: Provides connectivity to external network" def __init__( self, name, subnet='10.0/8', localIntf=None, flush=False, **params): """Start NAT/forwarding between Mininet and external network subnet: Mininet subnet (default 10.0/8) flush: flush iptables before installing NAT rules""" super( NAT, self ).__init__( name, **params ) self.subnet = subnet self.localIntf = localIntf self.flush = flush self.forwardState = self.cmd( 'sysctl -n net.ipv4.ip_forward' ).strip() def config( self, **params ): """Configure the NAT and iptables""" super( NAT, self).config( **params ) if not self.localIntf: self.localIntf = self.defaultIntf() if self.flush: self.cmd( 'sysctl net.ipv4.ip_forward=0' ) 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' ) # Install NAT rules 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', '-o', self.localIntf, '-d', self.subnet, '-j ACCEPT' ) self.cmd( 'iptables -t nat -A POSTROUTING', '-s', self.subnet, "'!'", '-d', self.subnet, '-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 + '\n' ) 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" # Remote NAT rules self.cmd( 'iptables -D FORWARD', '-i', self.localIntf, '-d', self.subnet, '-j DROP' ) self.cmd( 'iptables -D FORWARD', '-i', self.localIntf, '-s', self.subnet, '-j ACCEPT' ) self.cmd( 'iptables -D FORWARD', '-o', self.localIntf, '-d', self.subnet, '-j ACCEPT' ) self.cmd( 'iptables -t nat -D POSTROUTING', '-s', self.subnet, '\'!\'', '-d', self.subnet, '-j MASQUERADE' ) # Put the forwarding state back to what it was self.cmd( 'sysctl net.ipv4.ip_forward=%s' % self.forwardState ) super( NAT, self ).terminate() mininet-2.2.2/mininet/term.py000066400000000000000000000053301306431124000161370ustar00rootroot00000000000000""" Terminal creation and cleanup. 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): """Create an X11 tunnel from node:6000 to the root host display: display on root host (optional) returns: node $DISPLAY, Popen object for tunnel""" if display is None and 'DISPLAY' in environ: display = environ[ 'DISPLAY' ] if display is None: error( "Error: Cannot connect to display\n" ) return None, None host, screen = display.split( ':' ) # Unix sockets should work if not host or host == 'unix': # GDM3 doesn't put credentials in .Xauthority, # so allow root to just connect quietRun( 'xhost +si:localuser:root' ) return display, None else: # Create a tunnel for the TCP connection 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 ] return 'localhost:' + screen, node.popen( cmd ) def makeTerm( node, title='Node', term='xterm', display=None, cmd='bash'): """Create an X11 tunnel to the node and start up a terminal. node: Node object title: base title term: 'xterm' or 'gterm' returns: two Popen objects, tunnel and terminal""" title = '"%s: %s"' % ( title, node.name ) if not node.inNamespace: title += ' (root)' cmds = { 'xterm': [ 'xterm', '-title', title, '-display' ], 'gterm': [ 'gnome-terminal', '--title', title, '--display' ] } if term not in cmds: error( 'invalid terminal type: %s' % term ) return display, tunnel = tunnelX11( node, display ) if display is None: return [] term = node.popen( cmds[ term ] + [ display, '-e', 'env TERM=ansi %s' % cmd ] ) return [ tunnel, term ] if tunnel else [ term ] def runX11( node, cmd ): "Run an X11 client on a node" _display, tunnel = tunnelX11( node ) if _display is None: return [] popen = node.popen( cmd ) return [ tunnel, popen ] def cleanUpScreens(): "Remove moldy socat X11 tunnels." errRun( "pkill -9 -f mnexec.*socat" ) def makeTerms( nodes, title='Node', term='xterm' ): """Create terminals. nodes: list of Node objects title: base title for each returns: list of created tunnel/terminal processes""" terms = [] for node in nodes: terms += makeTerm( node, title, term ) return terms mininet-2.2.2/mininet/test/000077500000000000000000000000001306431124000155745ustar00rootroot00000000000000mininet-2.2.2/mininet/test/runner.py000077500000000000000000000016121306431124000174620ustar00rootroot00000000000000#!/usr/bin/env python """ Run all mininet core tests -v : verbose output -quick : skip tests that take more than ~30 seconds """ from unittest import defaultTestLoader, TextTestRunner import os import sys from mininet.util import ensureRoot from mininet.clean import cleanup from mininet.log import setLogLevel def runTests( testDir, verbosity=1 ): "discover and run all tests in testDir" # ensure root and cleanup before starting tests ensureRoot() cleanup() # discover all tests in testDir testSuite = defaultTestLoader.discover( testDir ) # run tests TextTestRunner( verbosity=verbosity ).run( testSuite ) if __name__ == '__main__': setLogLevel( 'warning' ) # get the directory containing example tests thisdir = os.path.dirname( os.path.realpath( __file__ ) ) vlevel = 2 if '-v' in sys.argv else 1 runTests( testDir=thisdir, verbosity=vlevel ) mininet-2.2.2/mininet/test/test_hifi.py000077500000000000000000000260631306431124000201360ustar00rootroot00000000000000#!/usr/bin/env python """Package: mininet Test creation and pings for topologies with link and/or CPU options.""" import unittest import sys from functools import partial from mininet.net import Mininet from mininet.node import OVSSwitch, UserSwitch, IVSSwitch from mininet.node import CPULimitedHost from mininet.link import TCLink from mininet.topo import Topo from mininet.log import setLogLevel from mininet.util import quietRun from mininet.clean import cleanup # Number of hosts for each test N = 2 class SingleSwitchOptionsTopo(Topo): "Single switch connected to n hosts." def __init__(self, n=2, hopts=None, lopts=None): if not hopts: hopts = {} if not lopts: lopts = {} Topo.__init__(self, hopts=hopts, lopts=lopts) switch = self.addSwitch('s1') for h in range(n): host = self.addHost('h%s' % (h + 1)) self.addLink(host, switch) # Tell pylint not to complain about calls to other class # pylint: disable=E1101 class testOptionsTopoCommon( object ): """Verify ability to create networks with host and link options (common code).""" switchClass = None # overridden in subclasses @staticmethod def tearDown(): "Clean up if necessary" if sys.exc_info != ( None, None, None ): cleanup() def runOptionsTopoTest( self, n, msg, hopts=None, lopts=None ): "Generic topology-with-options test runner." mn = Mininet( topo=SingleSwitchOptionsTopo( n=n, hopts=hopts, lopts=lopts ), host=CPULimitedHost, link=TCLink, switch=self.switchClass, waitConnected=True ) dropped = mn.run( mn.ping ) hoptsStr = ', '.join( '%s: %s' % ( opt, value ) for opt, value in hopts.items() ) loptsStr = ', '.join( '%s: %s' % ( opt, value ) for opt, value in lopts.items() ) msg += ( '%s%% of pings were dropped during mininet.ping().\n' 'Topo = SingleSwitchTopo, %s hosts\n' 'hopts = %s\n' 'lopts = %s\n' 'host = CPULimitedHost\n' 'link = TCLink\n' 'Switch = %s\n' % ( dropped, n, hoptsStr, loptsStr, self.switchClass ) ) self.assertEqual( dropped, 0, msg=msg ) def assertWithinTolerance( self, measured, expected, tolerance_frac, msg ): """Check that a given value is within a tolerance of expected tolerance_frac: less-than-1.0 value; 0.8 would yield 20% tolerance. """ upperBound = ( float( expected ) + ( 1 - tolerance_frac ) * float( expected ) ) lowerBound = float( expected ) * tolerance_frac info = ( 'measured value is out of bounds\n' 'expected value: %s\n' 'measured value: %s\n' 'failure tolerance: %s\n' 'upper bound: %s\n' 'lower bound: %s\n' % ( expected, measured, tolerance_frac, upperBound, lowerBound ) ) msg += info self.assertGreaterEqual( float( measured ), lowerBound, msg=msg ) self.assertLessEqual( float( measured ), upperBound, msg=msg ) def testCPULimits( self ): "Verify topology creation with CPU limits set for both schedulers." CPU_FRACTION = 0.1 CPU_TOLERANCE = 0.8 # CPU fraction below which test should fail hopts = { 'cpu': CPU_FRACTION } #self.runOptionsTopoTest( N, hopts=hopts ) mn = Mininet( SingleSwitchOptionsTopo( n=N, hopts=hopts ), host=CPULimitedHost, switch=self.switchClass, waitConnected=True ) mn.start() results = mn.runCpuLimitTest( cpu=CPU_FRACTION ) mn.stop() hostUsage = '\n'.join( 'h%s: %s' % ( n + 1, results[ (n - 1) * 5 : (n * 5) - 1 ] ) for n in range( N ) ) hoptsStr = ', '.join( '%s: %s' % ( opt, value ) for opt, value in hopts.items() ) msg = ( '\nTesting cpu limited to %d%% of cpu per host\n' 'cpu usage percent per host:\n%s\n' 'Topo = SingleSwitchTopo, %s hosts\n' 'hopts = %s\n' 'host = CPULimitedHost\n' 'Switch = %s\n' % ( CPU_FRACTION * 100, hostUsage, N, hoptsStr, self.switchClass ) ) for pct in results: #divide cpu by 100 to convert from percentage to fraction self.assertWithinTolerance( pct/100, CPU_FRACTION, CPU_TOLERANCE, msg ) def testLinkBandwidth( self ): "Verify that link bandwidths are accurate within a bound." if self.switchClass is UserSwitch: self.skipTest( 'UserSwitch has very poor performance -' ' skipping for now' ) BW = 5 # Mbps BW_TOLERANCE = 0.8 # BW fraction below which test should fail # Verify ability to create limited-link topo first; lopts = { 'bw': BW, 'use_htb': True } # Also verify correctness of limit limitng within a bound. mn = Mininet( SingleSwitchOptionsTopo( n=N, lopts=lopts ), link=TCLink, switch=self.switchClass, waitConnected=True ) bw_strs = mn.run( mn.iperf, fmt='m' ) loptsStr = ', '.join( '%s: %s' % ( opt, value ) for opt, value in lopts.items() ) msg = ( '\nTesting link bandwidth limited to %d Mbps per link\n' 'iperf results[ client, server ]: %s\n' 'Topo = SingleSwitchTopo, %s hosts\n' 'Link = TCLink\n' 'lopts = %s\n' 'host = default\n' 'switch = %s\n' % ( BW, bw_strs, N, loptsStr, self.switchClass ) ) # On the client side, iperf doesn't wait for ACKs - it simply # reports how long it took to fill up the TCP send buffer. # As long as the kernel doesn't wait a long time before # delivering bytes to the iperf server, its reported data rate # should be close to the actual receive rate. serverRate, _clientRate = bw_strs bw = float( serverRate.split(' ')[0] ) self.assertWithinTolerance( bw, BW, BW_TOLERANCE, msg ) def testLinkDelay( self ): "Verify that link delays are accurate within a bound." DELAY_MS = 15 DELAY_TOLERANCE = 0.8 # Delay fraction below which test should fail REPS = 3 lopts = { 'delay': '%sms' % DELAY_MS, 'use_htb': True } mn = Mininet( SingleSwitchOptionsTopo( n=N, lopts=lopts ), link=TCLink, switch=self.switchClass, autoStaticArp=True, waitConnected=True ) mn.start() for _ in range( REPS ): ping_delays = mn.pingFull() mn.stop() test_outputs = ping_delays[0] # Ignore unused variables below # pylint: disable=W0612 node, dest, ping_outputs = test_outputs sent, received, rttmin, rttavg, rttmax, rttdev = ping_outputs pingFailMsg = 'sent %s pings, only received %s' % ( sent, received ) self.assertEqual( sent, received, msg=pingFailMsg ) # pylint: enable=W0612 loptsStr = ', '.join( '%s: %s' % ( opt, value ) for opt, value in lopts.items() ) msg = ( '\nTesting Link Delay of %s ms\n' 'ping results across 4 links:\n' '(Sent, Received, rttmin, rttavg, rttmax, rttdev)\n' '%s\n' 'Topo = SingleSwitchTopo, %s hosts\n' 'Link = TCLink\n' 'lopts = %s\n' 'host = default' 'switch = %s\n' % ( DELAY_MS, ping_outputs, N, loptsStr, self.switchClass ) ) for rttval in [rttmin, rttavg, rttmax]: # Multiply delay by 4 to cover there & back on two links self.assertWithinTolerance( rttval, DELAY_MS * 4.0, DELAY_TOLERANCE, msg ) def testLinkLoss( self ): "Verify that we see packet drops with a high configured loss rate." LOSS_PERCENT = 99 REPS = 1 lopts = { 'loss': LOSS_PERCENT, 'use_htb': True } mn = Mininet( topo=SingleSwitchOptionsTopo( n=N, lopts=lopts ), host=CPULimitedHost, link=TCLink, switch=self.switchClass, waitConnected=True ) # Drops are probabilistic, but the chance of no dropped packets is # 1 in 100 million with 4 hops for a link w/99% loss. dropped_total = 0 mn.start() for _ in range(REPS): dropped_total += mn.ping(timeout='1') mn.stop() loptsStr = ', '.join( '%s: %s' % ( opt, value ) for opt, value in lopts.items() ) msg = ( '\nTesting packet loss with %d%% loss rate\n' 'number of dropped pings during mininet.ping(): %s\n' 'expected number of dropped packets: 1\n' 'Topo = SingleSwitchTopo, %s hosts\n' 'Link = TCLink\n' 'lopts = %s\n' 'host = default\n' 'switch = %s\n' % ( LOSS_PERCENT, dropped_total, N, loptsStr, self.switchClass ) ) self.assertGreater( dropped_total, 0, msg ) def testMostOptions( self ): "Verify topology creation with most link options and CPU limits." lopts = { 'bw': 10, 'delay': '5ms', 'use_htb': True } hopts = { 'cpu': 0.5 / N } msg = '\nTesting many cpu and link options\n' self.runOptionsTopoTest( N, msg, hopts=hopts, lopts=lopts ) # pylint: enable=E1101 class testOptionsTopoOVSKernel( testOptionsTopoCommon, unittest.TestCase ): """Verify ability to create networks with host and link options (OVS kernel switch).""" longMessage = True switchClass = OVSSwitch @unittest.skip( 'Skipping OVS user switch test for now' ) class testOptionsTopoOVSUser( testOptionsTopoCommon, unittest.TestCase ): """Verify ability to create networks with host and link options (OVS user switch).""" longMessage = True switchClass = partial( OVSSwitch, datapath='user' ) @unittest.skipUnless( quietRun( 'which ivs-ctl' ), 'IVS is not installed' ) class testOptionsTopoIVS( testOptionsTopoCommon, unittest.TestCase ): "Verify ability to create networks with host and link options (IVS)." longMessage = True switchClass = IVSSwitch @unittest.skipUnless( quietRun( 'which ofprotocol' ), 'Reference user switch is not installed' ) class testOptionsTopoUserspace( testOptionsTopoCommon, unittest.TestCase ): """Verify ability to create networks with host and link options (UserSwitch).""" longMessage = True switchClass = UserSwitch if __name__ == '__main__': setLogLevel( 'warning' ) unittest.main() mininet-2.2.2/mininet/test/test_nets.py000077500000000000000000000072371306431124000201720ustar00rootroot00000000000000#!/usr/bin/env python """Package: mininet Test creation and all-pairs ping for each included mininet topo type.""" import unittest import sys from functools import partial from mininet.net import Mininet from mininet.node import Host, Controller from mininet.node import UserSwitch, OVSSwitch, IVSSwitch from mininet.topo import SingleSwitchTopo, LinearTopo from mininet.log import setLogLevel from mininet.util import quietRun from mininet.clean import cleanup # Tell pylint not to complain about calls to other class # pylint: disable=E1101 class testSingleSwitchCommon( object ): "Test ping with single switch topology (common code)." switchClass = None # overridden in subclasses @staticmethod def tearDown(): "Clean up if necessary" if sys.exc_info != ( None, None, None ): cleanup() def testMinimal( self ): "Ping test on minimal topology" mn = Mininet( SingleSwitchTopo(), self.switchClass, Host, Controller, waitConnected=True ) dropped = mn.run( mn.ping ) self.assertEqual( dropped, 0 ) def testSingle5( self ): "Ping test on 5-host single-switch topology" mn = Mininet( SingleSwitchTopo( k=5 ), self.switchClass, Host, Controller, waitConnected=True ) dropped = mn.run( mn.ping ) self.assertEqual( dropped, 0 ) # pylint: enable=E1101 class testSingleSwitchOVSKernel( testSingleSwitchCommon, unittest.TestCase ): "Test ping with single switch topology (OVS kernel switch)." switchClass = OVSSwitch class testSingleSwitchOVSUser( testSingleSwitchCommon, unittest.TestCase ): "Test ping with single switch topology (OVS user switch)." switchClass = partial( OVSSwitch, datapath='user' ) @unittest.skipUnless( quietRun( 'which ivs-ctl' ), 'IVS is not installed' ) class testSingleSwitchIVS( testSingleSwitchCommon, unittest.TestCase ): "Test ping with single switch topology (IVS switch)." switchClass = IVSSwitch @unittest.skipUnless( quietRun( 'which ofprotocol' ), 'Reference user switch is not installed' ) class testSingleSwitchUserspace( testSingleSwitchCommon, unittest.TestCase ): "Test ping with single switch topology (Userspace switch)." switchClass = UserSwitch # Tell pylint not to complain about calls to other class # pylint: disable=E1101 class testLinearCommon( object ): "Test all-pairs ping with LinearNet (common code)." switchClass = None # overridden in subclasses def testLinear5( self ): "Ping test on a 5-switch topology" mn = Mininet( LinearTopo( k=5 ), self.switchClass, Host, Controller, waitConnected=True ) dropped = mn.run( mn.ping ) self.assertEqual( dropped, 0 ) # pylint: enable=E1101 class testLinearOVSKernel( testLinearCommon, unittest.TestCase ): "Test all-pairs ping with LinearNet (OVS kernel switch)." switchClass = OVSSwitch class testLinearOVSUser( testLinearCommon, unittest.TestCase ): "Test all-pairs ping with LinearNet (OVS user switch)." switchClass = partial( OVSSwitch, datapath='user' ) @unittest.skipUnless( quietRun( 'which ivs-ctl' ), 'IVS is not installed' ) class testLinearIVS( testLinearCommon, unittest.TestCase ): "Test all-pairs ping with LinearNet (IVS switch)." switchClass = IVSSwitch @unittest.skipUnless( quietRun( 'which ofprotocol' ), 'Reference user switch is not installed' ) class testLinearUserspace( testLinearCommon, unittest.TestCase ): "Test all-pairs ping with LinearNet (Userspace switch)." switchClass = UserSwitch if __name__ == '__main__': setLogLevel( 'warning' ) unittest.main() mininet-2.2.2/mininet/test/test_switchdpidassignment.py000077500000000000000000000070661306431124000234540ustar00rootroot00000000000000#!/usr/bin/env python """Package: mininet Regression tests for switch dpid assignment.""" import unittest import sys from mininet.net import Mininet from mininet.node import Host, Controller from mininet.node import ( UserSwitch, OVSSwitch, IVSSwitch ) from mininet.topo import Topo from mininet.log import setLogLevel from mininet.util import quietRun from mininet.clean import cleanup class TestSwitchDpidAssignmentOVS( unittest.TestCase ): "Verify Switch dpid assignment." switchClass = OVSSwitch # overridden in subclasses def tearDown( self ): "Clean up if necessary" # satisfy pylint assert self if sys.exc_info != ( None, None, None ): cleanup() def testDefaultDpid( self ): """Verify that the default dpid is assigned using a valid provided canonical switchname if no dpid is passed in switch creation.""" switch = Mininet( Topo(), self.switchClass, Host, Controller ).addSwitch( 's1' ) self.assertEqual( switch.defaultDpid(), switch.dpid ) def dpidFrom( self, num ): "Compute default dpid from number" fmt = ( '%0' + str( self.switchClass.dpidLen ) + 'x' ) return fmt % num def testActualDpidAssignment( self ): """Verify that Switch dpid is the actual dpid assigned if dpid is passed in switch creation.""" dpid = self.dpidFrom( 0xABCD ) switch = Mininet( Topo(), self.switchClass, Host, Controller ).addSwitch( 's1', dpid=dpid ) self.assertEqual( switch.dpid, dpid ) def testDefaultDpidAssignmentFailure( self ): """Verify that Default dpid assignment raises an Exception if the name of the switch does not contin a digit. Also verify the exception message.""" with self.assertRaises( Exception ) as raises_cm: Mininet( Topo(), self.switchClass, Host, Controller ).addSwitch( 'A' ) self.assertEqual(raises_cm.exception.message, 'Unable to derive ' 'default datapath ID - please either specify a dpid ' 'or use a canonical switch name such as s23.') def testDefaultDpidLen( self ): """Verify that Default dpid length is 16 characters consisting of 16 - len(hex of first string of contiguous digits passed in switch name) 0's followed by hex of first string of contiguous digits passed in switch name.""" switch = Mininet( Topo(), self.switchClass, Host, Controller ).addSwitch( 's123' ) self.assertEqual( switch.dpid, self.dpidFrom( 123 ) ) class OVSUser( OVSSwitch): "OVS User Switch convenience class" def __init__( self, *args, **kwargs ): kwargs.update( datapath='user' ) OVSSwitch.__init__( self, *args, **kwargs ) class testSwitchOVSUser( TestSwitchDpidAssignmentOVS ): "Test dpid assignnment of OVS User Switch." switchClass = OVSUser @unittest.skipUnless( quietRun( 'which ivs-ctl' ), 'IVS switch is not installed' ) class testSwitchIVS( TestSwitchDpidAssignmentOVS ): "Test dpid assignment of IVS switch." switchClass = IVSSwitch @unittest.skipUnless( quietRun( 'which ofprotocol' ), 'Reference user switch is not installed' ) class testSwitchUserspace( TestSwitchDpidAssignmentOVS ): "Test dpid assignment of Userspace switch." switchClass = UserSwitch if __name__ == '__main__': setLogLevel( 'warning' ) unittest.main() mininet-2.2.2/mininet/test/test_walkthrough.py000077500000000000000000000324071306431124000215550ustar00rootroot00000000000000#!/usr/bin/env python """ Tests for the Mininet Walkthrough TODO: missing xterm test """ import unittest import pexpect import os import re from mininet.util import quietRun from distutils.version import StrictVersion from time import sleep def tsharkVersion(): "Return tshark version" versionStr = quietRun( 'tshark -v' ) versionMatch = re.findall( r'TShark[^\d]*(\d+.\d+.\d+)', versionStr ) return versionMatch[ 0 ] # pylint doesn't understand pexpect.match, unfortunately! # pylint:disable=maybe-no-member class testWalkthrough( unittest.TestCase ): "Test Mininet walkthrough" prompt = 'mininet>' # PART 1 def testHelp( self ): "Check the usage message" p = pexpect.spawn( 'mn -h' ) index = p.expect( [ 'Usage: mn', pexpect.EOF ] ) self.assertEqual( index, 0 ) def testWireshark( self ): "Use tshark to test the of dissector" # Satisfy pylint assert self if StrictVersion( tsharkVersion() ) < StrictVersion( '1.12.0' ): tshark = pexpect.spawn( 'tshark -i lo -R of' ) else: tshark = pexpect.spawn( 'tshark -i lo -Y openflow_v1' ) tshark.expect( [ 'Capturing on lo', "Capturing on 'Loopback'" ] ) mn = pexpect.spawn( 'mn --test pingall' ) mn.expect( '0% dropped' ) tshark.expect( [ '74 Hello', '74 of_hello', '74 Type: OFPT_HELLO' ] ) tshark.sendintr() mn.expect( pexpect.EOF ) tshark.expect( pexpect.EOF ) def testBasic( self ): "Test basic CLI commands (help, nodes, net, dump)" p = pexpect.spawn( 'mn' ) p.expect( self.prompt ) # help command p.sendline( 'help' ) index = p.expect( [ 'commands', self.prompt ] ) self.assertEqual( index, 0, 'No output for "help" command') # nodes command p.sendline( 'nodes' ) p.expect( r'([chs]\d ?){4}' ) nodes = p.match.group( 0 ).split() self.assertEqual( len( nodes ), 4, 'No nodes in "nodes" command') p.expect( self.prompt ) # net command p.sendline( 'net' ) expected = [ x for x in nodes ] while len( expected ) > 0: index = p.expect( expected ) node = p.match.group( 0 ) expected.remove( node ) p.expect( '\n' ) self.assertEqual( len( expected ), 0, '"nodes" and "net" differ') p.expect( self.prompt ) # dump command p.sendline( 'dump' ) expected = [ r'<\w+ (%s)' % n for n in nodes ] actual = [] for _ in nodes: index = p.expect( expected ) node = p.match.group( 1 ) actual.append( node ) p.expect( '\n' ) self.assertEqual( actual.sort(), nodes.sort(), '"nodes" and "dump" differ' ) p.expect( self.prompt ) p.sendline( 'exit' ) p.wait() def testHostCommands( self ): "Test ifconfig and ps on h1 and s1" p = pexpect.spawn( 'mn' ) p.expect( self.prompt ) # Third pattern is a local interface beginning with 'eth' or 'en' interfaces = [ 'h1-eth0', 's1-eth1', r'[^-](eth|en)\w*\d', 'lo', self.prompt ] # h1 ifconfig p.sendline( 'h1 ifconfig -a' ) ifcount = 0 while True: index = p.expect( interfaces ) if index == 0 or index == 3: ifcount += 1 elif index == 1: self.fail( 's1 interface displayed in "h1 ifconfig"' ) elif index == 2: self.fail( 'eth0 displayed in "h1 ifconfig"' ) else: break self.assertEqual( ifcount, 2, 'Missing interfaces on h1') # s1 ifconfig p.sendline( 's1 ifconfig -a' ) ifcount = 0 while True: index = p.expect( interfaces ) if index == 0: self.fail( 'h1 interface displayed in "s1 ifconfig"' ) elif index == 1 or index == 2 or index == 3: ifcount += 1 else: break self.assertTrue( ifcount >= 3, 'Missing interfaces on s1') # h1 ps p.sendline( "h1 ps -a | egrep -v 'ps|grep'" ) p.expect( self.prompt ) h1Output = p.before # s1 ps p.sendline( "s1 ps -a | egrep -v 'ps|grep'" ) p.expect( self.prompt ) s1Output = p.before # strip command from ps output and compute diffs h1Output = h1Output.split( '\n' )[ 1: ] s1Output = s1Output.split( '\n' )[ 1: ] diffs = set( h1Output ).difference( set( s1Output ) ) # allow up to two diffs to account for daemons, etc. self.assertTrue( len( diffs ) <= 2, 'h1 and s1 "ps" output differ too much: %s' % diffs ) p.sendline( 'exit' ) p.wait() def testConnectivity( self ): "Test ping and pingall" p = pexpect.spawn( 'mn' ) p.expect( self.prompt ) p.sendline( 'h1 ping -c 1 h2' ) p.expect( '1 packets transmitted, 1 received' ) p.expect( self.prompt ) p.sendline( 'pingall' ) p.expect( '0% dropped' ) p.expect( self.prompt ) p.sendline( 'exit' ) p.wait() def testSimpleHTTP( self ): "Start an HTTP server on h1 and wget from h2" p = pexpect.spawn( 'mn' ) p.expect( self.prompt ) p.sendline( 'h1 python -m SimpleHTTPServer 80 &' ) # The walkthrough doesn't specify a delay here, and # we also don't read the output (also a possible problem), # but for now let's wait a couple of seconds to make # it less likely to fail due to the race condition. sleep( 2 ) p.expect( self.prompt ) p.sendline( ' h2 wget -O - h1' ) p.expect( '200 OK' ) p.expect( self.prompt ) p.sendline( 'h1 kill %python' ) p.expect( self.prompt ) p.sendline( 'exit' ) p.wait() # PART 2 def testRegressionRun( self ): "Test pingpair (0% drop) and iperf (bw > 0) regression tests" # test pingpair p = pexpect.spawn( 'mn --test pingpair' ) p.expect( '0% dropped' ) p.expect( pexpect.EOF ) # test iperf p = pexpect.spawn( 'mn --test iperf' ) p.expect( r"Results: \['([\d\.]+) .bits/sec'," ) bw = float( p.match.group( 1 ) ) self.assertTrue( bw > 0 ) p.expect( pexpect.EOF ) def testTopoChange( self ): "Test pingall on single,3 and linear,4 topos" # testing single,3 p = pexpect.spawn( 'mn --test pingall --topo single,3' ) p.expect( r'(\d+)/(\d+) received') received = int( p.match.group( 1 ) ) sent = int( p.match.group( 2 ) ) self.assertEqual( sent, 6, 'Wrong number of pings sent in single,3' ) self.assertEqual( sent, received, 'Dropped packets in single,3') p.expect( pexpect.EOF ) # testing linear,4 p = pexpect.spawn( 'mn --test pingall --topo linear,4' ) p.expect( r'(\d+)/(\d+) received') received = int( p.match.group( 1 ) ) sent = int( p.match.group( 2 ) ) self.assertEqual( sent, 12, 'Wrong number of pings sent in linear,4' ) self.assertEqual( sent, received, 'Dropped packets in linear,4') p.expect( pexpect.EOF ) def testLinkChange( self ): "Test TCLink bw and delay" p = pexpect.spawn( 'mn --link tc,bw=10,delay=10ms' ) # test bw p.expect( self.prompt ) p.sendline( 'iperf' ) p.expect( r"Results: \['([\d\.]+) Mbits/sec'," ) bw = float( p.match.group( 1 ) ) self.assertTrue( bw < 10.1, 'Bandwidth > 10 Mb/s') self.assertTrue( bw > 9.0, 'Bandwidth < 9 Mb/s') p.expect( self.prompt ) # test delay p.sendline( 'h1 ping -c 4 h2' ) p.expect( r'rtt min/avg/max/mdev = ' r'([\d\.]+)/([\d\.]+)/([\d\.]+)/([\d\.]+) ms' ) delay = float( p.match.group( 2 ) ) self.assertTrue( delay > 40, 'Delay < 40ms' ) self.assertTrue( delay < 45, 'Delay > 40ms' ) p.expect( self.prompt ) p.sendline( 'exit' ) p.wait() def testVerbosity( self ): "Test debug and output verbosity" # test output p = pexpect.spawn( 'mn -v output' ) p.expect( self.prompt ) self.assertEqual( len( p.before ), 0, 'Too much output for "output"' ) p.sendline( 'exit' ) p.wait() # test debug p = pexpect.spawn( 'mn -v debug --test none' ) p.expect( pexpect.EOF ) lines = p.before.split( '\n' ) self.assertTrue( len( lines ) > 70, "Debug output is too short" ) def testCustomTopo( self ): "Start Mininet using a custom topo, then run pingall" # Satisfy pylint assert self custom = os.path.dirname( os.path.realpath( __file__ ) ) custom = os.path.join( custom, '../../custom/topo-2sw-2host.py' ) custom = os.path.normpath( custom ) p = pexpect.spawn( 'mn --custom %s --topo mytopo --test pingall' % custom ) p.expect( '0% dropped' ) p.expect( pexpect.EOF ) def testStaticMAC( self ): "Verify that MACs are set to easy to read numbers" p = pexpect.spawn( 'mn --mac' ) p.expect( self.prompt ) for i in range( 1, 3 ): p.sendline( 'h%d ifconfig' % i ) p.expect( 'HWaddr 00:00:00:00:00:0%d' % i ) p.expect( self.prompt ) p.sendline( 'exit' ) p.expect( pexpect.EOF ) def testSwitches( self ): "Run iperf test using user and ovsk switches" switches = [ 'user', 'ovsk' ] for sw in switches: p = pexpect.spawn( 'mn --switch %s --test iperf' % sw ) p.expect( r"Results: \['([\d\.]+) .bits/sec'," ) bw = float( p.match.group( 1 ) ) self.assertTrue( bw > 0 ) p.expect( pexpect.EOF ) def testBenchmark( self ): "Run benchmark and verify that it takes less than 2 seconds" p = pexpect.spawn( 'mn --test none' ) p.expect( r'completed in ([\d\.]+) seconds' ) time = float( p.match.group( 1 ) ) self.assertTrue( time < 2, 'Benchmark takes more than 2 seconds' ) def testOwnNamespace( self ): "Test running user switch in its own namespace" p = pexpect.spawn( 'mn --innamespace --switch user' ) p.expect( self.prompt ) interfaces = [ 'h1-eth0', 's1-eth1', '[^-]eth0', 'lo', self.prompt ] p.sendline( 's1 ifconfig -a' ) ifcount = 0 while True: index = p.expect( interfaces ) if index == 1 or index == 3: ifcount += 1 elif index == 0: self.fail( 'h1 interface displayed in "s1 ifconfig"' ) elif index == 2: self.fail( 'eth0 displayed in "s1 ifconfig"' ) else: break self.assertEqual( ifcount, 2, 'Missing interfaces on s1' ) # verify that all hosts a reachable p.sendline( 'pingall' ) p.expect( r'(\d+)% dropped' ) dropped = int( p.match.group( 1 ) ) self.assertEqual( dropped, 0, 'pingall failed') p.expect( self.prompt ) p.sendline( 'exit' ) p.wait() # PART 3 def testPythonInterpreter( self ): "Test py and px by checking IP for h1 and adding h3" p = pexpect.spawn( 'mn' ) p.expect( self.prompt ) # test host IP p.sendline( 'py h1.IP()' ) p.expect( '10.0.0.1' ) p.expect( self.prompt ) # test adding host p.sendline( "px net.addHost('h3')" ) p.expect( self.prompt ) p.sendline( "px net.addLink(s1, h3)" ) p.expect( self.prompt ) p.sendline( 'net' ) p.expect( 'h3' ) p.expect( self.prompt ) p.sendline( 'py h3.MAC()' ) p.expect( '([a-f0-9]{2}:?){6}' ) p.expect( self.prompt ) p.sendline( 'exit' ) p.wait() def testLink( self ): "Test link CLI command using ping" p = pexpect.spawn( 'mn' ) p.expect( self.prompt ) p.sendline( 'link s1 h1 down' ) p.expect( self.prompt ) p.sendline( 'h1 ping -c 1 h2' ) p.expect( 'unreachable' ) p.expect( self.prompt ) p.sendline( 'link s1 h1 up' ) p.expect( self.prompt ) p.sendline( 'h1 ping -c 1 h2' ) p.expect( '0% packet loss' ) p.expect( self.prompt ) p.sendline( 'exit' ) p.wait() @unittest.skipUnless( os.path.exists( '/tmp/pox' ) or '1 received' in quietRun( 'ping -c 1 github.com' ), 'Github is not reachable; cannot download Pox' ) def testRemoteController( self ): "Test Mininet using Pox controller" # Satisfy pylint assert self if not os.path.exists( '/tmp/pox' ): p = pexpect.spawn( 'git clone https://github.com/noxrepo/pox.git /tmp/pox' ) p.expect( pexpect.EOF ) pox = pexpect.spawn( '/tmp/pox/pox.py forwarding.l2_learning' ) net = pexpect.spawn( 'mn --controller=remote,ip=127.0.0.1,port=6633 --test pingall' ) net.expect( '0% dropped' ) net.expect( pexpect.EOF ) pox.sendintr() pox.wait() if __name__ == '__main__': unittest.main() mininet-2.2.2/mininet/topo.py000066400000000000000000000303701306431124000161530ustar00rootroot00000000000000#!/usr/bin/env python """@package topo Network topology creation. @author Brandon Heller (brandonh@stanford.edu) This package includes code to represent network topologies. A Topo object can be a topology database for NOX, can represent a physical setup for testing, and can even be emulated with the Mininet package. """ from mininet.util import irange, natural, naturalSeq class MultiGraph( object ): "Utility class to track nodes and edges - replaces networkx.MultiGraph" def __init__( self ): self.node = {} self.edge = {} def add_node( self, node, attr_dict=None, **attrs): """Add node to graph attr_dict: attribute dict (optional) attrs: more attributes (optional) warning: updates attr_dict with attrs""" attr_dict = {} if attr_dict is None else attr_dict attr_dict.update( attrs ) self.node[ node ] = attr_dict def add_edge( self, src, dst, key=None, attr_dict=None, **attrs ): """Add edge to graph key: optional key attr_dict: optional attribute dict attrs: more attributes warning: udpates attr_dict with attrs""" attr_dict = {} if attr_dict is None else attr_dict attr_dict.update( attrs ) self.node.setdefault( src, {} ) self.node.setdefault( dst, {} ) self.edge.setdefault( src, {} ) self.edge.setdefault( dst, {} ) self.edge[ src ].setdefault( dst, {} ) entry = self.edge[ dst ][ src ] = self.edge[ src ][ dst ] # If no key, pick next ordinal number if key is None: keys = [ k for k in entry.keys() if isinstance( k, int ) ] key = max( [ 0 ] + keys ) + 1 entry[ key ] = attr_dict return key def nodes( self, data=False): """Return list of graph nodes data: return list of ( node, attrs)""" return self.node.items() if data else self.node.keys() def edges_iter( self, data=False, keys=False ): "Iterator: return graph edges" for src, entry in self.edge.iteritems(): for dst, keys in entry.iteritems(): if src > dst: # Skip duplicate edges continue for k, attrs in keys.iteritems(): if data: if keys: yield( src, dst, k, attrs ) else: yield( src, dst, attrs ) else: if keys: yield( src, dst, k ) else: yield( src, dst ) def edges( self, data=False, keys=False ): "Return list of graph edges" return list( self.edges_iter( data=data, keys=keys ) ) def __getitem__( self, node ): "Return link dict for given src node" return self.edge[ node ] def __len__( self ): "Return the number of nodes" return len( self.node ) def convertTo( self, cls, data=False, keys=False ): """Convert to a new object of networkx.MultiGraph-like class cls data: include node and edge data keys: include edge keys as well as edge data""" g = cls() g.add_nodes_from( self.nodes( data=data ) ) g.add_edges_from( self.edges( data=( data or keys ), keys=keys ) ) return g class Topo( object ): "Data center network representation for structured multi-trees." def __init__( self, *args, **params ): """Topo object. Optional named parameters: hinfo: default host options sopts: default switch options lopts: default link options calls build()""" self.g = MultiGraph() self.hopts = params.pop( 'hopts', {} ) self.sopts = params.pop( 'sopts', {} ) self.lopts = params.pop( 'lopts', {} ) # ports[src][dst][sport] is port on dst that connects to src self.ports = {} self.build( *args, **params ) def build( self, *args, **params ): "Override this method to build your topology." pass def addNode( self, name, **opts ): """Add Node to graph. name: name opts: node options returns: node name""" self.g.add_node( name, **opts ) return name def addHost( self, name, **opts ): """Convenience method: Add host to graph. name: host name opts: host options returns: host name""" if not opts and self.hopts: opts = self.hopts return self.addNode( name, **opts ) def addSwitch( self, name, **opts ): """Convenience method: Add switch to graph. name: switch name opts: switch options returns: switch name""" if not opts and self.sopts: opts = self.sopts result = self.addNode( name, isSwitch=True, **opts ) return result def addLink( self, node1, node2, port1=None, port2=None, key=None, **opts ): """node1, node2: nodes to link together port1, port2: ports (optional) opts: link options (optional) returns: link info key""" if not opts and self.lopts: opts = self.lopts port1, port2 = self.addPort( node1, node2, port1, port2 ) opts = dict( opts ) opts.update( node1=node1, node2=node2, port1=port1, port2=port2 ) self.g.add_edge(node1, node2, key, opts ) return key def nodes( self, sort=True ): "Return nodes in graph" if sort: return self.sorted( self.g.nodes() ) else: return self.g.nodes() def isSwitch( self, n ): "Returns true if node is a switch." return self.g.node[ n ].get( 'isSwitch', False ) def switches( self, sort=True ): """Return switches. sort: sort switches alphabetically returns: dpids list of dpids""" return [ n for n in self.nodes( sort ) if self.isSwitch( n ) ] def hosts( self, sort=True ): """Return hosts. sort: sort hosts alphabetically returns: list of hosts""" return [ n for n in self.nodes( sort ) if not self.isSwitch( n ) ] def iterLinks( self, withKeys=False, withInfo=False ): """Return links (iterator) withKeys: return link keys withInfo: return link info returns: list of ( src, dst [,key, info ] )""" for _src, _dst, key, info in self.g.edges_iter( data=True, keys=True ): node1, node2 = info[ 'node1' ], info[ 'node2' ] if withKeys: if withInfo: yield( node1, node2, key, info ) else: yield( node1, node2, key ) else: if withInfo: yield( node1, node2, info ) else: yield( node1, node2 ) def links( self, sort=False, withKeys=False, withInfo=False ): """Return links sort: sort links alphabetically, preserving (src, dst) order withKeys: return link keys withInfo: return link info returns: list of ( src, dst [,key, info ] )""" links = list( self.iterLinks( withKeys, withInfo ) ) if not sort: return links # Ignore info when sorting tupleSize = 3 if withKeys else 2 return sorted( links, key=( lambda l: naturalSeq( l[ :tupleSize ] ) ) ) # This legacy port management mechanism is clunky and will probably # be removed at some point. def addPort( self, src, dst, sport=None, dport=None ): """Generate port mapping for new edge. src: source switch name dst: destination switch name""" # Initialize if necessary ports = self.ports ports.setdefault( src, {} ) ports.setdefault( dst, {} ) # New port: number of outlinks + base if sport is None: src_base = 1 if self.isSwitch( src ) else 0 sport = len( ports[ src ] ) + src_base if dport is None: dst_base = 1 if self.isSwitch( dst ) else 0 dport = len( ports[ dst ] ) + dst_base ports[ src ][ sport ] = ( dst, dport ) ports[ dst ][ dport ] = ( src, sport ) return sport, dport def port( self, src, dst ): """Get port numbers. src: source switch name dst: destination switch name sport: optional source port (otherwise use lowest src port) returns: tuple (sport, dport), where sport = port on source switch leading to the destination switch dport = port on destination switch leading to the source switch Note that you can also look up ports using linkInfo()""" # A bit ugly and slow vs. single-link implementation ;-( ports = [ ( sport, entry[ 1 ] ) for sport, entry in self.ports[ src ].items() if entry[ 0 ] == dst ] return ports if len( ports ) != 1 else ports[ 0 ] def _linkEntry( self, src, dst, key=None ): "Helper function: return link entry and key" entry = self.g[ src ][ dst ] if key is None: key = min( entry ) return entry, key def linkInfo( self, src, dst, key=None ): "Return link metadata dict" entry, key = self._linkEntry( src, dst, key ) return entry[ key ] def setlinkInfo( self, src, dst, info, key=None ): "Set link metadata dict" entry, key = self._linkEntry( src, dst, key ) entry[ key ] = info def nodeInfo( self, name ): "Return metadata (dict) for node" return self.g.node[ name ] def setNodeInfo( self, name, info ): "Set metadata (dict) for node" self.g.node[ name ] = info def convertTo( self, cls, data=True, keys=True ): """Convert to a new object of networkx.MultiGraph-like class cls data: include node and edge data (default True) keys: include edge keys as well as edge data (default True)""" return self.g.convertTo( cls, data=data, keys=keys ) @staticmethod def sorted( items ): "Items sorted in natural (i.e. alphabetical) order" return sorted( items, key=natural ) # Our idiom defines additional parameters in build(param...) # pylint: disable=arguments-differ class SingleSwitchTopo( Topo ): "Single switch connected to k hosts." def build( self, k=2, **_opts ): "k: number of hosts" self.k = k switch = self.addSwitch( 's1' ) for h in irange( 1, k ): host = self.addHost( 'h%s' % h ) self.addLink( host, switch ) class SingleSwitchReversedTopo( Topo ): """Single switch connected to k hosts, with reversed ports. The lowest-numbered host is connected to the highest-numbered port. Useful to verify that Mininet properly handles custom port numberings.""" def build( self, k=2 ): "k: number of hosts" self.k = k switch = self.addSwitch( 's1' ) for h in irange( 1, k ): host = self.addHost( 'h%s' % h ) self.addLink( host, switch, port1=0, port2=( k - h + 1 ) ) class MinimalTopo( SingleSwitchTopo ): "Minimal topology with two hosts and one switch" def build( self ): return SingleSwitchTopo.build( self, k=2 ) class LinearTopo( Topo ): "Linear topology of k switches, with n hosts per switch." def build( self, k=2, n=1, **_opts): """k: number of switches n: number of hosts per switch""" self.k = k self.n = n if n == 1: genHostName = lambda i, j: 'h%s' % i else: genHostName = lambda i, j: 'h%ss%d' % ( j, i ) lastSwitch = None for i in irange( 1, k ): # Add switch switch = self.addSwitch( 's%s' % i ) # Add hosts to switch for j in irange( 1, n ): host = self.addHost( genHostName( i, j ) ) self.addLink( host, switch ) # Connect switch to previous if lastSwitch: self.addLink( switch, lastSwitch ) lastSwitch = switch # pylint: enable=arguments-differ mininet-2.2.2/mininet/topolib.py000066400000000000000000000055401306431124000166430ustar00rootroot00000000000000"Library of potentially useful topologies for Mininet" from mininet.topo import Topo from mininet.net import Mininet # The build() method is expected to do this: # pylint: disable=arguments-differ class TreeTopo( Topo ): "Topology for a tree network with a given depth and fanout." def build( self, depth=1, fanout=2 ): # Numbering: h1..N, s1..M self.hostNum = 1 self.switchNum = 1 # Build topology self.addTree( depth, fanout ) def addTree( self, depth, fanout ): """Add a subtree starting with node n. returns: last node added""" isSwitch = depth > 0 if isSwitch: node = self.addSwitch( 's%s' % self.switchNum ) self.switchNum += 1 for _ in range( fanout ): child = self.addTree( depth - 1, fanout ) self.addLink( node, child ) else: node = self.addHost( 'h%s' % self.hostNum ) self.hostNum += 1 return node def TreeNet( depth=1, fanout=2, **kwargs ): "Convenience function for creating tree networks." topo = TreeTopo( depth, fanout ) return Mininet( topo, **kwargs ) class TorusTopo( Topo ): """2-D Torus 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 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""" if x < 3 or y < 3: raise Exception( 'Please use 3x3 or greater for compatibility ' 'with 2.1' ) if n == 1: genHostName = lambda loc, k: 'h%s' % ( loc ) else: genHostName = lambda loc, k: 'h%sx%d' % ( loc, k ) hosts, switches, dpid = {}, {}, 0 # Create and wire interior for i in range( 0, x ): for j in range( 0, y ): loc = '%dx%d' % ( i + 1, j + 1 ) # dpid cannot be zero for OVS dpid = ( i + 1 ) * 256 + ( j + 1 ) switch = switches[ i, j ] = self.addSwitch( 's' + loc, dpid='%x' % dpid ) for k in range( 0, n ): host = hosts[ i, j, k ] = self.addHost( genHostName( loc, k + 1 ) ) 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 ) # pylint: enable=arguments-differ mininet-2.2.2/mininet/util.py000066400000000000000000000520001306431124000161410ustar00rootroot00000000000000"Utility functions for Mininet." from mininet.log import output, info, error, warn, debug from time import sleep from resource import getrlimit, setrlimit, RLIMIT_NPROC, RLIMIT_NOFILE from select import poll, POLLIN, POLLHUP from subprocess import call, check_call, Popen, PIPE, STDOUT import re from fcntl import fcntl, F_GETFL, F_SETFL from os import O_NONBLOCK import os from functools import partial # Command execution support def run( cmd ): """Simple interface to subprocess.call() cmd: list of command params""" return call( cmd.split( ' ' ) ) def checkRun( cmd ): """Simple interface to subprocess.check_call() cmd: list of command params""" return check_call( cmd.split( ' ' ) ) # pylint doesn't understand explicit type checking # pylint: disable=maybe-no-member def oldQuietRun( *cmd ): """Run a command, routing stderr to stdout, and return the output. cmd: list of command params""" if len( cmd ) == 1: cmd = cmd[ 0 ] if isinstance( cmd, str ): cmd = cmd.split( ' ' ) popen = Popen( cmd, 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. out = '' readable = poll() readable.register( popen.stdout ) while True: while readable.poll(): data = popen.stdout.read( 1024 ) if len( data ) == 0: break out += data popen.poll() if popen.returncode is not None: break return out # This is a bit complicated, but it enables us to # monitor command output as it is happening # pylint: disable=too-many-branches def errRun( *cmd, **kwargs ): """Run a command and return stdout, stderr and return code cmd: string or list of command and args stderr: STDOUT to merge stderr with stdout shell: run command using shell echo: monitor output to console""" # By default we separate stderr, don't run in a shell, and don't echo stderr = kwargs.get( 'stderr', PIPE ) shell = kwargs.get( 'shell', False ) echo = kwargs.get( 'echo', False ) if echo: # cmd goes to stderr, output goes to stdout info( cmd, '\n' ) if len( cmd ) == 1: cmd = cmd[ 0 ] # Allow passing in a list or a string if isinstance( cmd, str ) and not shell: cmd = cmd.split( ' ' ) cmd = [ str( arg ) for arg in cmd ] elif isinstance( cmd, list ) and shell: cmd = " ".join( arg for arg in cmd ) debug( '*** errRun:', cmd, '\n' ) popen = Popen( cmd, stdout=PIPE, stderr=stderr, shell=shell ) # We use poll() because select() doesn't work with large fd numbers, # and thus communicate() doesn't work either out, err = '', '' poller = poll() poller.register( popen.stdout, POLLIN ) fdtofile = { popen.stdout.fileno(): popen.stdout } outDone, errDone = False, True if popen.stderr: fdtofile[ popen.stderr.fileno() ] = popen.stderr poller.register( popen.stderr, POLLIN ) errDone = False while not outDone or not errDone: readable = poller.poll() for fd, event in readable: f = fdtofile[ fd ] if event & POLLIN: data = f.read( 1024 ) if echo: output( data ) if f == popen.stdout: out += data if data == '': outDone = True elif f == popen.stderr: err += data if data == '': errDone = True else: # POLLHUP or something unexpected if f == popen.stdout: outDone = True elif f == popen.stderr: errDone = True poller.unregister( fd ) returncode = popen.wait() debug( out, err, returncode ) return out, err, returncode # pylint: enable=too-many-branches def errFail( *cmd, **kwargs ): "Run a command using errRun and raise exception on nonzero exit" out, err, ret = errRun( *cmd, **kwargs ) if ret: raise Exception( "errFail: %s failed with return code %s: %s" % ( cmd, ret, err ) ) return out, err, ret def quietRun( cmd, **kwargs ): "Run a command and return merged stdout and stderr" return errRun( cmd, stderr=STDOUT, **kwargs )[ 0 ] # pylint: enable=maybe-no-member def isShellBuiltin( cmd ): "Return True if cmd is a bash builtin." if isShellBuiltin.builtIns is None: isShellBuiltin.builtIns = quietRun( 'bash -c enable' ) space = cmd.find( ' ' ) if space > 0: cmd = cmd[ :space] return cmd in isShellBuiltin.builtIns isShellBuiltin.builtIns = None # Interface management # # Interfaces are managed as strings which are simply the # interface names, of the form 'nodeN-ethM'. # # To connect nodes, we create a pair of veth interfaces, and then place them # in the pair of nodes that we want to communicate. We then update the node's # list of interfaces and connectivity map. # # For the kernel datapath, switch interfaces # live in the root namespace and thus do not have to be # explicitly moved. def makeIntfPair( intf1, intf2, addr1=None, addr2=None, node1=None, node2=None, deleteIntfs=True, runCmd=None ): """Make a veth pair connnecting new interfaces intf1 and intf2 intf1: name for interface 1 intf2: name for interface 2 addr1: MAC address for interface 1 (optional) addr2: MAC address for interface 2 (optional) node1: home node for interface 1 (optional) node2: home node for interface 2 (optional) deleteIntfs: delete intfs before creating them runCmd: function to run shell commands (quietRun) raises Exception on failure""" if not runCmd: runCmd = quietRun if not node1 else node1.cmd runCmd2 = quietRun if not node2 else node2.cmd if deleteIntfs: # Delete any old interfaces with the same names runCmd( 'ip link del ' + intf1 ) runCmd2( 'ip link del ' + intf2 ) # Create new pair netns = 1 if not node2 else node2.pid if addr1 is None and addr2 is None: cmdOutput = runCmd( 'ip link add name %s ' 'type veth peer name %s ' 'netns %s' % ( intf1, intf2, netns ) ) else: cmdOutput = runCmd( 'ip link add name %s ' 'address %s ' 'type veth peer name %s ' 'address %s ' 'netns %s' % ( intf1, addr1, intf2, addr2, netns ) ) if cmdOutput: raise Exception( "Error creating interface pair (%s,%s): %s " % ( intf1, intf2, cmdOutput ) ) def retry( retries, delaySecs, fn, *args, **keywords ): """Try something several times before giving up. n: number of times to retry delaySecs: wait this long between tries fn: function to call args: args to apply to function call""" tries = 0 while not fn( *args, **keywords ) and tries < retries: sleep( delaySecs ) tries += 1 if tries >= retries: error( "*** gave up after %i retries\n" % tries ) exit( 1 ) def moveIntfNoRetry( intf, dstNode, printError=False ): """Move interface to node, without retrying. intf: string, interface dstNode: destination Node printError: if true, print error""" intf = str( intf ) cmd = 'ip link set %s netns %s' % ( intf, dstNode.pid ) cmdOutput = quietRun( cmd ) # If ip link set does not produce any output, then we can assume # that the link has been moved successfully. if cmdOutput: if printError: error( '*** Error: moveIntf: ' + intf + ' not successfully moved to ' + dstNode.name + ':\n', cmdOutput ) return False return True def moveIntf( intf, dstNode, printError=True, retries=3, delaySecs=0.001 ): """Move interface to node, retrying on failure. intf: string, interface dstNode: destination Node printError: if true, print error""" retry( retries, delaySecs, moveIntfNoRetry, intf, dstNode, printError=printError ) # Support for dumping network def dumpNodeConnections( nodes ): "Dump connections to/from nodes." def dumpConnections( node ): "Helper function: dump connections to node" for intf in node.intfList(): output( ' %s:' % intf ) if intf.link: intfs = [ intf.link.intf1, intf.link.intf2 ] intfs.remove( intf ) output( intfs[ 0 ] ) else: output( ' ' ) for node in nodes: output( node.name ) dumpConnections( node ) output( '\n' ) def dumpNetConnections( net ): "Dump connections in network" nodes = net.controllers + net.switches + net.hosts dumpNodeConnections( nodes ) def dumpPorts( switches ): "dump interface to openflow port mappings for each switch" for switch in switches: output( '%s ' % switch.name ) for intf in switch.intfList(): port = switch.ports[ intf ] output( '%s:%d ' % ( intf, port ) ) output( '\n' ) # IP and Mac address formatting and parsing def _colonHex( val, bytecount ): """Generate colon-hex string. val: input as unsigned int bytecount: number of bytes to convert returns: chStr colon-hex string""" pieces = [] for i in range( bytecount - 1, -1, -1 ): piece = ( ( 0xff << ( i * 8 ) ) & val ) >> ( i * 8 ) pieces.append( '%02x' % piece ) chStr = ':'.join( pieces ) return chStr def macColonHex( mac ): """Generate MAC colon-hex string from unsigned int. mac: MAC address as unsigned int returns: macStr MAC colon-hex string""" return _colonHex( mac, 6 ) def ipStr( ip ): """Generate IP address string from an unsigned int. ip: unsigned int of form w << 24 | x << 16 | y << 8 | z returns: ip address string w.x.y.z""" w = ( ip >> 24 ) & 0xff x = ( ip >> 16 ) & 0xff y = ( ip >> 8 ) & 0xff z = ip & 0xff return "%i.%i.%i.%i" % ( w, x, y, z ) def ipNum( w, x, y, z ): """Generate unsigned int from components of IP address returns: w << 24 | x << 16 | y << 8 | z""" return ( w << 24 ) | ( x << 16 ) | ( y << 8 ) | z def ipAdd( i, prefixLen=8, ipBaseNum=0x0a000000 ): """Return IP address string from ints i: int to be added to ipbase prefixLen: optional IP prefix length ipBaseNum: option base IP address as int returns IP address as string""" imax = 0xffffffff >> prefixLen assert i <= imax, 'Not enough IP addresses in the subnet' mask = 0xffffffff ^ imax ipnum = ( ipBaseNum & mask ) + i return ipStr( ipnum ) def ipParse( ip ): "Parse an IP address and return an unsigned int." args = [ int( arg ) for arg in ip.split( '.' ) ] while len(args) < 4: args.append( 0 ) return ipNum( *args ) def netParse( ipstr ): """Parse an IP network specification, returning address and prefix len as unsigned ints""" prefixLen = 0 if '/' in ipstr: ip, pf = ipstr.split( '/' ) prefixLen = int( pf ) #if no prefix is specified, set the prefix to 24 else: ip = ipstr prefixLen = 24 return ipParse( ip ), prefixLen def checkInt( s ): "Check if input string is an int" try: int( s ) return True except ValueError: return False def checkFloat( s ): "Check if input string is a float" try: float( s ) return True except ValueError: return False def makeNumeric( s ): "Convert string to int or float if numeric." if checkInt( s ): return int( s ) elif checkFloat( s ): return float( s ) else: return s # Popen support def pmonitor(popens, timeoutms=500, readline=True, readmax=1024 ): """Monitor dict of hosts to popen objects a line at a time timeoutms: timeout for poll() readline: return single line of output yields: host, line/output (if any) terminates: when all EOFs received""" poller = poll() fdToHost = {} for host, popen in popens.iteritems(): fd = popen.stdout.fileno() fdToHost[ fd ] = host poller.register( fd, POLLIN ) if not readline: # Use non-blocking reads flags = fcntl( fd, F_GETFL ) fcntl( fd, F_SETFL, flags | O_NONBLOCK ) while popens: fds = poller.poll( timeoutms ) if fds: for fd, event in fds: host = fdToHost[ fd ] popen = popens[ host ] if event & POLLIN: if readline: # Attempt to read a line of output # This blocks until we receive a newline! line = popen.stdout.readline() else: line = popen.stdout.read( readmax ) yield host, line # Check for EOF elif event & POLLHUP: poller.unregister( fd ) del popens[ host ] else: yield None, '' # Other stuff we use def sysctlTestAndSet( name, limit ): "Helper function to set sysctl limits" #convert non-directory names into directory names if '/' not in name: name = '/proc/sys/' + name.replace( '.', '/' ) #read limit with open( name, 'r' ) as readFile: oldLimit = readFile.readline() if isinstance( limit, int ): #compare integer limits before overriding if int( oldLimit ) < limit: with open( name, 'w' ) as writeFile: writeFile.write( "%d" % limit ) else: #overwrite non-integer limits with open( name, 'w' ) as writeFile: writeFile.write( limit ) def rlimitTestAndSet( name, limit ): "Helper function to set rlimits" soft, hard = getrlimit( name ) if soft < limit: hardLimit = hard if limit < hard else limit setrlimit( name, ( limit, hardLimit ) ) def fixLimits(): "Fix ridiculously small resource limits." debug( "*** Setting resource limits\n" ) try: rlimitTestAndSet( RLIMIT_NPROC, 8192 ) rlimitTestAndSet( RLIMIT_NOFILE, 16384 ) #Increase open file limit sysctlTestAndSet( 'fs.file-max', 10000 ) #Increase network buffer space sysctlTestAndSet( 'net.core.wmem_max', 16777216 ) sysctlTestAndSet( 'net.core.rmem_max', 16777216 ) sysctlTestAndSet( 'net.ipv4.tcp_rmem', '10240 87380 16777216' ) sysctlTestAndSet( 'net.ipv4.tcp_wmem', '10240 87380 16777216' ) sysctlTestAndSet( 'net.core.netdev_max_backlog', 5000 ) #Increase arp cache size sysctlTestAndSet( 'net.ipv4.neigh.default.gc_thresh1', 4096 ) sysctlTestAndSet( 'net.ipv4.neigh.default.gc_thresh2', 8192 ) sysctlTestAndSet( 'net.ipv4.neigh.default.gc_thresh3', 16384 ) #Increase routing table size sysctlTestAndSet( 'net.ipv4.route.max_size', 32768 ) #Increase number of PTYs for nodes sysctlTestAndSet( 'kernel.pty.max', 20000 ) # pylint: disable=broad-except except Exception: warn( "*** Error setting resource limits. " "Mininet's performance may be affected.\n" ) # pylint: enable=broad-except def mountCgroups(): "Make sure cgroups file system is mounted" mounts = quietRun( 'cat /proc/mounts' ) cgdir = '/sys/fs/cgroup' csdir = cgdir + '/cpuset' if ('cgroup %s' % cgdir not in mounts and 'cgroups %s' % cgdir not in mounts): raise Exception( "cgroups not mounted on " + cgdir ) if 'cpuset %s' % csdir not in mounts: errRun( 'mkdir -p ' + csdir ) errRun( 'mount -t cgroup -ocpuset cpuset ' + csdir ) def natural( text ): "To sort sanely/alphabetically: sorted( l, key=natural )" def num( s ): "Convert text segment to int if necessary" return int( s ) if s.isdigit() else s return [ num( s ) for s in re.split( r'(\d+)', str( text ) ) ] def naturalSeq( t ): "Natural sort key function for sequences" return [ natural( x ) for x in t ] def numCores(): "Returns number of CPU cores based on /proc/cpuinfo" if hasattr( numCores, 'ncores' ): return numCores.ncores try: numCores.ncores = int( quietRun('grep -c processor /proc/cpuinfo') ) except ValueError: return 0 return numCores.ncores def irange(start, end): """Inclusive range from start to end (vs. Python insanity.) irange(1,5) -> 1, 2, 3, 4, 5""" return range( start, end + 1 ) def custom( cls, **params ): "Returns customized constructor for class cls." # Note: we may wish to see if we can use functools.partial() here # and in customConstructor def customized( *args, **kwargs): "Customized constructor" kwargs = kwargs.copy() kwargs.update( params ) return cls( *args, **kwargs ) 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: ] # 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 def customClass( classes, argStr ): """Return customized class based on argStr The args and key/val pairs in argStr will be automatically applied when the generated class is later used. """ cname, args, kwargs = splitArgs( argStr ) cls = classes.get( cname, None ) if not cls: raise Exception( "error: %s is unknown - please specify one of %s" % ( cname, classes.keys() ) ) if not args and not kwargs: return cls return specialClass( cls, append=args, defaults=kwargs ) def specialClass( cls, prepend=None, append=None, defaults=None, override=None ): """Like functools.partial, but it returns a class prepend: arguments to prepend to argument list append: arguments to append to argument list defaults: default values for keyword arguments override: keyword arguments to override""" if prepend is None: prepend = [] if append is None: append = [] if defaults is None: defaults = {} if override is None: override = {} class CustomClass( cls ): "Customized subclass with preset args/params" def __init__( self, *args, **params ): newparams = defaults.copy() newparams.update( params ) newparams.update( override ) cls.__init__( self, *( list( prepend ) + list( args ) + list( append ) ), **newparams ) CustomClass.__name__ = '%s%s' % ( cls.__name__, defaults ) return CustomClass def buildTopo( topos, topoStr ): """Create topology from string with format (object, arg1, arg2,...). input topos is a dict of topo names to constructors, possibly w/args. """ topo, args, kwargs = splitArgs( topoStr ) if topo not in topos: raise Exception( 'Invalid topo name %s' % topo ) return topos[ topo ]( *args, **kwargs ) def ensureRoot(): """Ensure that we are running as root. Probably we should only sudo when needed as per Big Switch's patch. """ if os.getuid() != 0: print "*** Mininet must run as root." exit( 1 ) return def waitListening( client=None, server='127.0.0.1', port=80, timeout=None ): """Wait until server is listening on port. returns True if server is listening""" runCmd = ( client.cmd if client else partial( quietRun, shell=True ) ) if not runCmd( 'which telnet' ): raise Exception('Could not find telnet' ) # pylint: disable=maybe-no-member serverIP = server if isinstance( server, basestring ) else server.IP() cmd = ( 'echo A | telnet -e A %s %s' % ( serverIP, port ) ) time = 0 result = runCmd( cmd ) while 'Connected' not in result: if 'No route' in result: rtable = runCmd( 'route' ) error( 'no route to %s:\n%s' % ( server, rtable ) ) return False if timeout and time >= timeout: error( 'could not connect to %s on port %d\n' % ( server, port ) ) return False debug( 'waiting for', server, 'to listen on port', port, '\n' ) info( '.' ) sleep( .5 ) time += .5 result = runCmd( cmd ) return True mininet-2.2.2/mnexec.c000066400000000000000000000136101306431124000145760ustar00rootroot00000000000000/* mnexec: execution utility for mininet * * Starts up programs and does things that are slow or * difficult in Python, including: * * - closing all file descriptors except stdin/out/error * - detaching from a controlling tty using setsid * - running in network and mount namespaces * - printing out the pid of a process so we can identify it later * - attaching to a namespace and cgroup * - setting RT scheduling * * Partially based on public domain setsid(1) */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #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" "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" " -p: print ^A + pid\n" " -a pid: attach to pid's network and mount namespaces\n" " -g group: add to cgroup\n" " -r rtprio: run with SCHED_RR (usually requires -g)\n" " -v: print version\n", name); } int setns(int fd, int nstype) { return syscall(__NR_setns, fd, nstype); } /* Validate alphanumeric path foo1/bar2/baz */ void validate(char *path) { char *s; for (s=path; *s; s++) { if (!isalnum(*s) && *s != '/') { fprintf(stderr, "invalid path: %s\n", path); exit(1); } } } /* Add our pid to cgroup */ void cgroup(char *gname) { static char path[PATH_MAX]; static char *groups[] = { "cpu", "cpuacct", "cpuset", NULL }; char **gptr; pid_t pid = getpid(); int count = 0; validate(gname); for (gptr = groups; *gptr; gptr++) { FILE *f; snprintf(path, PATH_MAX, "/sys/fs/cgroup/%s/%s/tasks", *gptr, gname); f = fopen(path, "w"); if (f) { count++; fprintf(f, "%d\n", pid); fclose(f); } } if (!count) { fprintf(stderr, "cgroup: could not add to cgroup %s\n", gname); exit(1); } } 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) 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; } /* Mark our whole hierarchy recursively as private, so that our * mounts do not propagate to other processes. */ 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 (optind < argc) { execvp(argv[optind], &argv[optind]); perror(argv[optind]); return 1; } usage(argv[0]); return 0; } mininet-2.2.2/setup.py000066400000000000000000000023011306431124000146600ustar00rootroot00000000000000#!/usr/bin/env python "Setuptools params" from setuptools import setup, find_packages from os.path import join # Get version number from source tree import sys sys.path.append( '.' ) from mininet.net import VERSION scripts = [ join( 'bin', filename ) for filename in [ 'mn' ] ] modname = distname = 'mininet' setup( name=distname, version=VERSION, description='Process-based OpenFlow emulator', author='Bob Lantz', author_email='rlantz@cs.stanford.edu', packages=[ 'mininet', 'mininet.examples' ], long_description=""" Mininet is a network emulator which uses lightweight virtualization to create virtual networks for rapid prototyping of Software-Defined Network (SDN) designs using OpenFlow. http://mininet.org """, classifiers=[ "License :: OSI Approved :: BSD License", "Programming Language :: Python", "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Topic :: System :: Emulators", ], keywords='networking emulator protocol Internet OpenFlow SDN', license='BSD', install_requires=[ 'setuptools' ], scripts=scripts, ) mininet-2.2.2/util/000077500000000000000000000000001306431124000141275ustar00rootroot00000000000000mininet-2.2.2/util/build-ovs-packages.sh000077500000000000000000000055641306431124000201600ustar00rootroot00000000000000#!/bin/bash # Attempt to build debian packages for OVS set -e # exit on error set -u # exit on undefined variable kvers=`uname -r` ksrc=/lib/modules/$kvers/build dist=`lsb_release -is | tr [A-Z] [a-z]` release=`lsb_release -rs` arch=`uname -m` buildsuffix=-2 if [ "$arch" = "i686" ]; then arch=i386; fi if [ "$arch" = "x86_64" ]; then arch=amd64; fi overs=1.4.0 ovs=openvswitch-$overs ovstgz=$ovs.tar.gz ovsurl=http://openvswitch.org/releases/$ovstgz install='sudo apt-get install -y' echo "*** Installing debian/ubuntu build system" $install build-essential devscripts ubuntu-dev-tools debhelper dh-make $install diff patch cdbs quilt gnupg fakeroot lintian pbuilder piuparts $install module-assistant echo "*** Installing OVS dependencies" $install pkg-config gcc make python-dev libssl-dev libtool $install dkms ipsec-tools echo "*** Installing headers for $kvers" $install linux-headers-$kvers echo "*** Retrieving OVS source" wget -c $ovsurl tar xzf $ovstgz cd $ovs echo "*** Patching OVS source" # Not sure why this fails, but off it goes! sed -i -e 's/dh_strip/# dh_strip/' debian/rules if [ "$release" = "10.04" ]; then # Lucid doesn't seem to have all the packages for ovsdbmonitor echo "*** Patching debian/rules to remove dh_python2" sed -i -e 's/dh_python2/dh_pysupport/' debian/rules echo "*** Not building ovsdbmonitor since it's too hard on 10.04" mv debian/ovsdbmonitor.install debian/ovsdbmonitor.install.backup sed -i -e 's/ovsdbmonitor.install/ovsdbmonitor.install.backup/' Makefile.in else # Install a bag of hurt for ovsdbmonitor $install python-pyside.qtcore pyqt4-dev-tools python-twisted python-twisted-bin \ python-twisted-core python-twisted-conch python-anyjson python-zope.interface fi # init script was written to assume that commands complete sed -i -e 's/^set -e/#set -e/' debian/openvswitch-controller.init echo "*** Building OVS user packages" opts=--with-linux=/lib/modules/`uname -r`/build fakeroot make -f debian/rules DATAPATH_CONFIGURE_OPTS=$opts binary echo "*** Building OVS datapath kernel module package" # Still looking for the "right" way to do this... sudo mkdir -p /usr/src/linux ln -sf _debian/openvswitch.tar.gz . sudo make -f debian/rules.modules KSRC=$ksrc KVERS=$kvers binary-modules echo "*** Built the following packages:" cd ~ ls -l *deb archive=ovs-$overs-core-$dist-$release-$arch$buildsuffix.tar ovsbase='common pki switch brcompat controller datapath-dkms' echo "*** Packing up $ovsbase .debs into:" echo " $archive" pkgs="" for component in $ovsbase; do if echo $component | egrep 'dkms|pki'; then # Architecture-independent packages deb=(openvswitch-${component}_$overs*all.deb) else deb=(openvswitch-${component}_$overs*$arch.deb) fi pkgs="$pkgs $deb" done rm -rf $archive tar cf $archive $pkgs echo "*** Contents of archive $archive:" tar tf $archive echo "*** Done (hopefully)" mininet-2.2.2/util/clustersetup.sh000077500000000000000000000124561306431124000172400ustar00rootroot00000000000000#!/usr/bin/env bash # Mininet ssh authentication script for cluster edition # This script will create a single key pair, which is then # propagated throughout the entire cluster. # There are two options for setup; temporary setup # persistent setup. If no options are specified, and the script # is only given ip addresses or host names, it will default to # the temporary setup. An ssh directory is then created in # /tmp/mn/ssh on each node, and mounted with the keys over the # user's ssh directory. This setup can easily be torn down by running # clustersetup with the -c option. # If the -p option is used, the setup will be persistent. In this # case, the key pair will be be distributed directly to each node's # ssh directory, but will be called cluster_key. An option to # specify this key for use will be added to the config file in each # user's ssh directory. set -e num_options=0 persistent=false showHelp=false clean=false declare -a hosts=() user=$(whoami) SSHDIR=/tmp/mn/ssh USERDIR=$HOME/.ssh usage="./clustersetup.sh [ -p|h|c ] [ host1 ] [ host2 ] ...\n Authenticate yourself and other cluster nodes to each other via ssh for mininet cluster edition. By default, we use a temporary ssh setup. An ssh directory is mounted over $USERDIR on each machine in the cluster. -h: display this help -p: create a persistent ssh setup. This will add new ssh keys and known_hosts to each nodes $USERDIR directory -c: method to clean up a temporary ssh setup. Any hosts taken as arguments will be cleaned " persistentSetup() { echo "***creating key pair" ssh-keygen -t rsa -C "Cluster_Edition_Key" -f $USERDIR/cluster_key -N '' &> /dev/null cat $USERDIR/cluster_key.pub >> $USERDIR/authorized_keys echo "***configuring ssh" echo "IdentityFile $USERDIR/cluster_key" >> $USERDIR/config echo "IdentityFile $USERDIR/id_rsa" >> $USERDIR/config for host in $hosts; do echo "***copying public key to $host" ssh-copy-id -i $USERDIR/cluster_key.pub $user@$host &> /dev/null echo "***copying key pair to remote host" scp $USERDIR/cluster_key $user@$host:$USERDIR scp $USERDIR/cluster_key.pub $user@$host:$USERDIR echo "***configuring remote host" ssh -o ForwardAgent=yes $user@$host " echo 'IdentityFile $USERDIR/cluster_key' >> $USERDIR/config echo 'IdentityFile $USERDIR/id_rsa' >> $USERDIR/config" done for host in $hosts; do echo "***copying known_hosts to $host" scp $USERDIR/known_hosts $user@$host:$USERDIR/cluster_known_hosts ssh $user@$host " cat $USERDIR/cluster_known_hosts >> $USERDIR/known_hosts rm $USERDIR/cluster_known_hosts" done } tempSetup() { echo "***creating temporary ssh directory" mkdir -p $SSHDIR echo "***creating key pair" ssh-keygen -t rsa -C "Cluster_Edition_Key" -f $SSHDIR/id_rsa -N '' &> /dev/null echo "***mounting temporary ssh directory" sudo mount --bind $SSHDIR $USERDIR cp $SSHDIR/id_rsa.pub $SSHDIR/authorized_keys for host in $hosts; do echo "***copying public key to $host" ssh-copy-id $user@$host &> /dev/null echo "***mounting remote temporary ssh directory for $host" ssh -o ForwardAgent=yes $user@$host " mkdir -p $SSHDIR cp $USERDIR/authorized_keys $SSHDIR/authorized_keys sudo mount --bind $SSHDIR $USERDIR" echo "***copying key pair to $host" scp $SSHDIR/{id_rsa,id_rsa.pub} $user@$host:$SSHDIR done for host in $hosts; do echo "***copying known_hosts to $host" scp $SSHDIR/known_hosts $user@$host:$SSHDIR done } cleanup() { for host in $hosts; do echo "***cleaning up $host" ssh $user@$host "sudo umount $USERDIR sudo rm -rf $SSHDIR" done echo "**unmounting local directories" sudo umount $USERDIR echo "***removing temporary ssh directory" sudo rm -rf $SSHDIR echo "done!" } if [ $# -eq 0 ]; then echo "ERROR: No Arguments" echo "$usage" exit else while getopts 'hpc' OPTION do ((num_options+=1)) case $OPTION in h) showHelp=true;; p) persistent=true;; c) clean=true;; ?) showHelp=true;; esac done shift $(($OPTIND - 1)) fi if [ "$num_options" -gt 1 ]; then echo "ERROR: Too Many Options" echo "$usage" exit fi if $showHelp; then echo "$usage" exit fi for i in "$@"; do output=$(getent ahostsv4 "$i") if [ -z "$output" ]; then echo '***WARNING: could not find hostname "$i"' echo "" else hosts+="$i " fi done if $clean; then cleanup exit fi echo "***authenticating to:" for host in $hosts; do echo "$host" done echo if $persistent; then echo '***Setting up persistent SSH configuration between all nodes' persistentSetup echo $'\n*** Sucessfully set up ssh throughout the cluster!' else echo '*** Setting up temporary SSH configuration between all nodes' tempSetup echo $'\n***Finished temporary setup. When you are done with your cluster' echo $' session, tear down the SSH connections with' echo $' ./clustersetup.sh -c '$hosts'' fi echo mininet-2.2.2/util/colorfilters000066400000000000000000000026211306431124000165620ustar00rootroot00000000000000# DO NOT EDIT THIS FILE! It was created by Wireshark @Bad TCP@tcp.analysis.flags@[0,0,0][65535,24383,24383] @HSRP State Change@hsrp.state != 8 && hsrp.state != 16@[0,0,0][65535,63222,0] @Spanning Tree Topology Change@stp.type == 0x80@[0,0,0][65535,63222,0] @OSPF State Change@ospf.msg != 1@[0,0,0][65535,63222,0] @ICMP errors@icmp.type eq 3 || icmp.type eq 4 || icmp.type eq 5 || icmp.type eq 11@[0,0,0][0,65535,3616] @ARP@arp@[55011,59486,65534][0,0,0] @ICMP@icmp@[49680,49737,65535][0,0,0] @TCP RST@tcp.flags.reset eq 1@[37008,0,0][65535,63121,32911] @TTL low or unexpected@( ! ip.dst == 224.0.0.0/4 && ip.ttl < 5) || (ip.dst == 224.0.0.0/24 && ip.ttl != 1)@[37008,0,0][65535,65535,65535] @of@of@[0,5,65535][65535,65535,65535] @Checksum Errors@cdp.checksum_bad==1 || edp.checksum_bad==1 || ip.checksum_bad==1 || tcp.checksum_bad==1 || udp.checksum_bad==1@[0,0,0][65535,24383,24383] @SMB@smb || nbss || nbns || nbipx || ipxsap || netbios@[65534,64008,39339][0,0,0] @HTTP@http || tcp.port == 80@[36107,65535,32590][0,0,0] @IPX@ipx || spx@[65534,58325,58808][0,0,0] @DCERPC@dcerpc@[51199,38706,65533][0,0,0] @Routing@hsrp || eigrp || ospf || bgp || cdp || vrrp || gvrp || igmp || ismp@[65534,62325,54808][0,0,0] @TCP SYN/FIN@tcp.flags & 0x02 || tcp.flags.fin == 1@[41026,41026,41026][0,0,0] @TCP@tcp@[59345,58980,65534][0,0,0] @UDP@udp@[28834,57427,65533][0,0,0] @Broadcast@eth[0] & 1@[65535,65535,65535][32768,32768,32768] mininet-2.2.2/util/doxify.py000077500000000000000000000037241306431124000160140ustar00rootroot00000000000000#!/usr/bin/python """ Convert simple documentation to epydoc/pydoctor-compatible markup """ from sys import stdin, stdout, argv import os from tempfile import mkstemp from subprocess import call import re spaces = re.compile( r'\s+' ) singleLineExp = re.compile( r'\s+"([^"]+)"' ) commentStartExp = re.compile( r'\s+"""' ) commentEndExp = re.compile( r'"""$' ) returnExp = re.compile( r'\s+(returns:.*)' ) lastindent = '' comment = False def fixParam( line ): "Change foo: bar to @foo bar" result = re.sub( r'(\w+):', r'@param \1', line ) result = re.sub( r' @', r'@', result) return result def fixReturns( line ): "Change returns: foo to @return foo" return re.sub( 'returns:', r'@returns', line ) def fixLine( line ): global comment match = spaces.match( line ) if not match: return line else: indent = match.group(0) if singleLineExp.match( line ): return re.sub( '"', '"""', line ) if commentStartExp.match( line ): comment = True if comment: line = fixReturns( line ) line = fixParam( line ) if commentEndExp.search( line ): comment = False return line def test(): "Test transformations" assert fixLine(' "foo"') == ' """foo"""' assert fixParam( 'foo: bar' ) == '@param foo bar' assert commentStartExp.match( ' """foo"""') def funTest(): testFun = ( 'def foo():\n' ' "Single line comment"\n' ' """This is a test"""\n' ' bar: int\n' ' baz: string\n' ' returns: junk"""\n' ' if True:\n' ' print "OK"\n' ).splitlines( True ) fixLines( testFun ) def fixLines( lines, fid ): for line in lines: os.write( fid, fixLine( line ) ) if __name__ == '__main__': if False: funTest() infile = open( argv[1] ) outfid, outname = mkstemp() fixLines( infile.readlines(), outfid ) infile.close() os.close( outfid ) call( [ 'doxypy', outname ] ) mininet-2.2.2/util/install.sh000077500000000000000000000621501306431124000161400ustar00rootroot00000000000000#!/usr/bin/env bash # Mininet install script for Ubuntu (and Debian Wheezy+) # Brandon Heller (brandonh@stanford.edu) # Fail on error set -e # Fail on unset var usage set -o nounset # Get directory containing mininet folder MININET_DIR="$( cd -P "$( dirname "${BASH_SOURCE[0]}" )/../.." && pwd -P )" # Set up build directory, which by default is the working directory # unless the working directory is a subdirectory of mininet, # in which case we use the directory containing mininet BUILD_DIR="$(pwd -P)" case $BUILD_DIR in $MININET_DIR/*) BUILD_DIR=$MININET_DIR;; # currect directory is a subdirectory *) BUILD_DIR=$BUILD_DIR;; esac # Location of CONFIG_NET_NS-enabled kernel(s) KERNEL_LOC=http://www.openflow.org/downloads/mininet # Attempt to identify Linux release DIST=Unknown RELEASE=Unknown CODENAME=Unknown ARCH=`uname -m` if [ "$ARCH" = "x86_64" ]; then ARCH="amd64"; fi if [ "$ARCH" = "i686" ]; then ARCH="i386"; fi test -e /etc/debian_version && DIST="Debian" grep Ubuntu /etc/lsb-release &> /dev/null && DIST="Ubuntu" if [ "$DIST" = "Ubuntu" ] || [ "$DIST" = "Debian" ]; then # Truly non-interactive apt-get installation install='sudo DEBIAN_FRONTEND=noninteractive apt-get -y -q install' remove='sudo DEBIAN_FRONTEND=noninteractive apt-get -y -q remove' pkginst='sudo dpkg -i' # Prereqs for this script if ! which lsb_release &> /dev/null; then $install lsb-release fi fi test -e /etc/fedora-release && DIST="Fedora" test -e /etc/redhat-release && DIST="RedHatEnterpriseServer" if [ "$DIST" = "Fedora" -o "$DIST" = "RedHatEnterpriseServer" ]; then install='sudo yum -y install' remove='sudo yum -y erase' pkginst='sudo rpm -ivh' # Prereqs for this script if ! which lsb_release &> /dev/null; then $install redhat-lsb-core fi fi if which lsb_release &> /dev/null; then DIST=`lsb_release -is` RELEASE=`lsb_release -rs` CODENAME=`lsb_release -cs` fi echo "Detected Linux distribution: $DIST $RELEASE $CODENAME $ARCH" # Kernel params KERNEL_NAME=`uname -r` KERNEL_HEADERS=kernel-headers-${KERNEL_NAME} if ! echo $DIST | egrep 'Ubuntu|Debian|Fedora|RedHatEnterpriseServer'; then echo "Install.sh currently only supports Ubuntu, Debian, RedHat and Fedora." exit 1 fi # More distribution info DIST_LC=`echo $DIST | tr [A-Z] [a-z]` # as lower case # Determine whether version $1 >= version $2 # usage: if version_ge 1.20 1.2.3; then echo "true!"; fi function version_ge { # sort -V sorts by *version number* latest=`printf "$1\n$2" | sort -V | tail -1` # If $1 is latest version, then $1 >= $2 [ "$1" == "$latest" ] } # Kernel Deb pkg to be removed: KERNEL_IMAGE_OLD=linux-image-2.6.26-33-generic DRIVERS_DIR=/lib/modules/${KERNEL_NAME}/kernel/drivers/net OVS_RELEASE=1.4.0 OVS_PACKAGE_LOC=https://github.com/downloads/mininet/mininet OVS_BUILDSUFFIX=-ignore # was -2 OVS_PACKAGE_NAME=ovs-$OVS_RELEASE-core-$DIST_LC-$RELEASE-$ARCH$OVS_BUILDSUFFIX.tar OVS_TAG=v$OVS_RELEASE OF13_SWITCH_REV=${OF13_SWITCH_REV:-""} function kernel { echo "Install Mininet-compatible kernel if necessary" sudo apt-get update if ! $install linux-image-$KERNEL_NAME; then echo "Could not install linux-image-$KERNEL_NAME" echo "Skipping - assuming installed kernel is OK." fi } function kernel_clean { echo "Cleaning kernel..." # To save disk space, remove previous kernel if ! $remove $KERNEL_IMAGE_OLD; then echo $KERNEL_IMAGE_OLD not installed. fi # Also remove downloaded packages: rm -f $HOME/linux-headers-* $HOME/linux-image-* } # Install Mininet deps function mn_deps { echo "Installing Mininet dependencies" if [ "$DIST" = "Fedora" -o "$DIST" = "RedHatEnterpriseServer" ]; then $install gcc make socat psmisc xterm openssh-clients iperf \ iproute telnet python-setuptools libcgroup-tools \ ethtool help2man pyflakes pylint python-pep8 python-pexpect else $install gcc make socat psmisc xterm ssh iperf iproute telnet \ python-setuptools cgroup-bin ethtool help2man \ pyflakes pylint pep8 python-pexpect fi echo "Installing Mininet core" pushd $MININET_DIR/mininet sudo make install popd } # Install Mininet developer dependencies function mn_dev { echo "Installing Mininet developer dependencies" $install doxygen doxypy texlive-fonts-recommended if ! $install doxygen-latex; then echo "doxygen-latex not needed" fi } # The following will cause a full OF install, covering: # -user switch # The instructions below are an abbreviated version from # http://www.openflowswitch.org/wk/index.php/Debian_Install function of { echo "Installing OpenFlow reference implementation..." cd $BUILD_DIR $install autoconf automake libtool make gcc if [ "$DIST" = "Fedora" -o "$DIST" = "RedHatEnterpriseServer" ]; then $install git pkgconfig glibc-devel else $install git-core autotools-dev pkg-config libc6-dev fi # was: git clone git://openflowswitch.org/openflow.git # Use our own fork on github for now: git clone git://github.com/mininet/openflow cd $BUILD_DIR/openflow # Patch controller to handle more than 16 switches patch -p1 < $MININET_DIR/mininet/util/openflow-patches/controller.patch # Resume the install: ./boot.sh ./configure make sudo make install cd $BUILD_DIR } function of13 { echo "Installing OpenFlow 1.3 soft switch implementation..." cd $BUILD_DIR/ $install git-core autoconf automake autotools-dev pkg-config \ make gcc g++ libtool libc6-dev cmake libpcap-dev libxerces-c2-dev \ unzip libpcre3-dev flex bison libboost-dev if [ ! -d "ofsoftswitch13" ]; then git clone https://github.com/CPqD/ofsoftswitch13.git if [[ -n "$OF13_SWITCH_REV" ]]; then cd ofsoftswitch13 git checkout ${OF13_SWITCH_REV} cd .. fi fi # Install netbee if [ "$DIST" = "Ubuntu" ] && version_ge $RELEASE 14.04; then NBEESRC="nbeesrc-feb-24-2015" NBEEDIR="netbee" else NBEESRC="nbeesrc-jan-10-2013" NBEEDIR="nbeesrc-jan-10-2013" fi NBEEURL=${NBEEURL:-http://www.nbee.org/download/} wget -nc ${NBEEURL}${NBEESRC}.zip unzip ${NBEESRC}.zip cd ${NBEEDIR}/src cmake . make cd $BUILD_DIR/ sudo cp ${NBEEDIR}/bin/libn*.so /usr/local/lib sudo ldconfig sudo cp -R ${NBEEDIR}/include/ /usr/ # Resume the install: cd $BUILD_DIR/ofsoftswitch13 ./boot.sh ./configure make sudo make install cd $BUILD_DIR } function install_wireshark { if ! which wireshark; then echo "Installing Wireshark" if [ "$DIST" = "Fedora" -o "$DIST" = "RedHatEnterpriseServer" ]; then $install wireshark wireshark-gnome else $install wireshark tshark fi fi # Copy coloring rules: OF is white-on-blue: echo "Optionally installing wireshark color filters" mkdir -p $HOME/.wireshark cp -n $MININET_DIR/mininet/util/colorfilters $HOME/.wireshark echo "Checking Wireshark version" WSVER=`wireshark -v | egrep -o '[0-9\.]+' | head -1` if version_ge $WSVER 1.12; then echo "Wireshark version $WSVER >= 1.12 - returning" return fi echo "Cloning LoxiGen and building openflow.lua dissector" cd $BUILD_DIR git clone https://github.com/floodlight/loxigen.git cd loxigen make wireshark # Copy into plugin directory # libwireshark0/ on 11.04; libwireshark1/ on later WSDIR=`find /usr/lib -type d -name 'libwireshark*' | head -1` WSPLUGDIR=$WSDIR/plugins/ PLUGIN=loxi_output/wireshark/openflow.lua sudo cp $PLUGIN $WSPLUGDIR echo "Copied openflow plugin $PLUGIN to $WSPLUGDIR" cd $BUILD_DIR } # Install Open vSwitch specific version Ubuntu package function ubuntuOvs { echo "Creating and Installing Open vSwitch packages..." OVS_SRC=$BUILD_DIR/openvswitch OVS_TARBALL_LOC=http://openvswitch.org/releases if ! echo "$DIST" | egrep "Ubuntu|Debian" > /dev/null; then echo "OS must be Ubuntu or Debian" $cd BUILD_DIR return fi if [ "$DIST" = "Ubuntu" ] && ! version_ge $RELEASE 12.04; then echo "Ubuntu version must be >= 12.04" cd $BUILD_DIR return fi if [ "$DIST" = "Debian" ] && ! version_ge $RELEASE 7.0; then echo "Debian version must be >= 7.0" cd $BUILD_DIR return fi rm -rf $OVS_SRC mkdir -p $OVS_SRC cd $OVS_SRC if wget $OVS_TARBALL_LOC/openvswitch-$OVS_RELEASE.tar.gz 2> /dev/null; then tar xzf openvswitch-$OVS_RELEASE.tar.gz else echo "Failed to find OVS at $OVS_TARBALL_LOC/openvswitch-$OVS_RELEASE.tar.gz" cd $BUILD_DIR return fi # Remove any old packages $remove openvswitch-common openvswitch-datapath-dkms openvswitch-pki openvswitch-switch \ openvswitch-controller || true # Get build deps $install build-essential fakeroot debhelper autoconf automake libssl-dev \ pkg-config bzip2 openssl python-all procps python-qt4 \ python-zopeinterface python-twisted-conch dkms dh-python dh-autoreconf \ uuid-runtime # Build OVS parallel=`grep processor /proc/cpuinfo | wc -l` cd $BUILD_DIR/openvswitch/openvswitch-$OVS_RELEASE DEB_BUILD_OPTIONS='parallel=$parallel nocheck' fakeroot debian/rules binary cd .. for pkg in common datapath-dkms pki switch; do pkg=openvswitch-${pkg}_$OVS_RELEASE*.deb echo "Installing $pkg" $pkginst $pkg done if $pkginst openvswitch-controller_$OVS_RELEASE*.deb 2>/dev/null; then echo "Ignoring error installing openvswitch-controller" fi /sbin/modinfo openvswitch sudo ovs-vsctl show # Switch can run on its own, but # Mininet should control the controller # This appears to only be an issue on Ubuntu/Debian if sudo service openvswitch-controller stop 2>/dev/null; then echo "Stopped running controller" fi if [ -e /etc/init.d/openvswitch-controller ]; then sudo update-rc.d openvswitch-controller disable fi } # Install Open vSwitch function ovs { echo "Installing Open vSwitch..." if [ "$DIST" = "Fedora" -o "$DIST" = "RedHatEnterpriseServer" ]; then $install openvswitch openvswitch-controller return fi if [ "$DIST" = "Ubuntu" ] && ! version_ge $RELEASE 14.04; then # Older Ubuntu versions need openvswitch-datapath/-dkms # Manually installing openvswitch-datapath may be necessary # for manually built kernel .debs using Debian's defective kernel # packaging, which doesn't yield usable headers. if ! dpkg --get-selections | grep openvswitch-datapath; then # If you've already installed a datapath, assume you # know what you're doing and don't need dkms datapath. # Otherwise, install it. $install openvswitch-datapath-dkms fi fi $install openvswitch-switch OVSC="" if $install openvswitch-controller; then OVSC="openvswitch-controller" else echo "Attempting to install openvswitch-testcontroller" if $install openvswitch-testcontroller; then OVSC="openvswitch-testcontroller" else echo "Failed - skipping openvswitch-testcontroller" fi fi if [ "$OVSC" ]; then # Switch can run on its own, but # Mininet should control the controller # This appears to only be an issue on Ubuntu/Debian if sudo service $OVSC stop; then echo "Stopped running controller" fi if [ -e /etc/init.d/$OVSC ]; then sudo update-rc.d $OVSC disable fi fi } function remove_ovs { pkgs=`dpkg --get-selections | grep openvswitch | awk '{ print $1;}'` echo "Removing existing Open vSwitch packages:" echo $pkgs if ! $remove $pkgs; then echo "Not all packages removed correctly" fi # For some reason this doesn't happen if scripts=`ls /etc/init.d/*openvswitch* 2>/dev/null`; then echo $scripts for s in $scripts; do s=$(basename $s) echo SCRIPT $s sudo service $s stop sudo rm -f /etc/init.d/$s sudo update-rc.d -f $s remove done fi echo "Done removing OVS" } function ivs { echo "Installing Indigo Virtual Switch..." IVS_SRC=$BUILD_DIR/ivs # Install dependencies $install git pkg-config gcc make libnl-3-dev libnl-route-3-dev libnl-genl-3-dev # Install IVS from source cd $BUILD_DIR git clone git://github.com/floodlight/ivs $IVS_SRC --recursive cd $IVS_SRC make sudo make install } # Install RYU function ryu { echo "Installing RYU..." # install Ryu dependencies" $install autoconf automake g++ libtool python make if [ "$DIST" = "Ubuntu" -o "$DIST" = "Debian" ]; then $install libxml2 libxslt-dev python-pip python-dev sudo pip install --upgrade gevent pbr webob routes paramiko \\ oslo.config fi # if needed, update python-six SIX_VER=`pip show six | grep Version | awk '{print $2}'` if version_ge 1.7.0 $SIX_VER; then echo "Installing python-six version 1.7.0..." sudo pip install -I six==1.7.0 fi # fetch RYU cd $BUILD_DIR/ git clone git://github.com/osrg/ryu.git ryu cd ryu # install ryu sudo python ./setup.py install # Add symbolic link to /usr/bin sudo ln -s ./bin/ryu-manager /usr/local/bin/ryu-manager } # Install NOX with tutorial files function nox { echo "Installing NOX w/tutorial files..." # Install NOX deps: $install autoconf automake g++ libtool python python-twisted \ swig libssl-dev make if [ "$DIST" = "Debian" ]; then $install libboost1.35-dev elif [ "$DIST" = "Ubuntu" ]; then $install python-dev libboost-dev $install libboost-filesystem-dev $install libboost-test-dev fi # Install NOX optional deps: $install libsqlite3-dev python-simplejson # Fetch NOX destiny cd $BUILD_DIR/ git clone https://github.com/noxrepo/nox-classic.git noxcore cd noxcore if ! git checkout -b destiny remotes/origin/destiny ; then echo "Did not check out a new destiny branch - assuming current branch is destiny" fi # Apply patches git checkout -b tutorial-destiny git am $MININET_DIR/mininet/util/nox-patches/*tutorial-port-nox-destiny*.patch if [ "$DIST" = "Ubuntu" ] && version_ge $RELEASE 12.04; then git am $MININET_DIR/mininet/util/nox-patches/*nox-ubuntu12-hacks.patch fi # Build ./boot.sh mkdir build cd build ../configure make -j3 #make check # Add NOX_CORE_DIR env var: sed -i -e 's|# for examples$|&\nexport NOX_CORE_DIR=$BUILD_DIR/noxcore/build/src|' ~/.bashrc # To verify this install: #cd ~/noxcore/build/src #./nox_core -v -i ptcp: } # Install NOX Classic/Zaku for OpenFlow 1.3 function nox13 { echo "Installing NOX w/tutorial files..." # Install NOX deps: $install autoconf automake g++ libtool python python-twisted \ swig libssl-dev make if [ "$DIST" = "Debian" ]; then $install libboost1.35-dev elif [ "$DIST" = "Ubuntu" ]; then $install python-dev libboost-dev $install libboost-filesystem-dev $install libboost-test-dev fi # Fetch NOX destiny cd $BUILD_DIR/ git clone https://github.com/CPqD/nox13oflib.git cd nox13oflib # Build ./boot.sh mkdir build cd build ../configure make -j3 #make check # To verify this install: #cd ~/nox13oflib/build/src #./nox_core -v -i ptcp: } # "Install" POX function pox { echo "Installing POX into $BUILD_DIR/pox..." cd $BUILD_DIR git clone https://github.com/noxrepo/pox.git } # Install OFtest function oftest { echo "Installing oftest..." # Install deps: $install tcpdump python-scapy # Install oftest: cd $BUILD_DIR/ git clone git://github.com/floodlight/oftest } # Install cbench function cbench { echo "Installing cbench..." if [ "$DIST" = "Fedora" -o "$DIST" = "RedHatEnterpriseServer" ]; then $install net-snmp-devel libpcap-devel libconfig-devel else $install libsnmp-dev libpcap-dev libconfig-dev fi cd $BUILD_DIR/ # was: git clone git://gitosis.stanford.edu/oflops.git # Use our own fork on github for now: git clone git://github.com/mininet/oflops cd oflops sh boot.sh || true # possible error in autoreconf, so run twice sh boot.sh ./configure --with-openflow-src-dir=$BUILD_DIR/openflow make sudo make install || true # make install fails; force past this } function vm_other { echo "Doing other Mininet VM setup tasks..." # Remove avahi-daemon, which may cause unwanted discovery packets to be # sent during tests, near link status changes: echo "Removing avahi-daemon" $remove avahi-daemon # was: Disable IPv6. Add to /etc/modprobe.d/blacklist: #echo "Attempting to disable IPv6" #if [ "$DIST" = "Ubuntu" ]; then # BLACKLIST=/etc/modprobe.d/blacklist.conf #else # BLACKLIST=/etc/modprobe.d/blacklist #fi #sudo sh -c "echo 'blacklist net-pf-10\nblacklist ipv6' >> $BLACKLIST" echo "Disabling IPv6" # Disable IPv6 if ! grep 'disable_ipv6' /etc/sysctl.conf; then echo 'Disabling IPv6' echo ' # Mininet: disable IPv6 net.ipv6.conf.all.disable_ipv6 = 1 net.ipv6.conf.default.disable_ipv6 = 1 net.ipv6.conf.lo.disable_ipv6 = 1' | sudo tee -a /etc/sysctl.conf > /dev/null fi # Since the above doesn't disable neighbor discovery, also do this: if ! grep 'ipv6.disable' /etc/default/grub; then sudo sed -i -e \ 's/GRUB_CMDLINE_LINUX_DEFAULT="/GRUB_CMDLINE_LINUX_DEFAULT="ipv6.disable=1 /' \ /etc/default/grub sudo update-grub fi # Disabling IPv6 breaks X11 forwarding via ssh line='AddressFamily inet' file='/etc/ssh/sshd_config' echo "Adding $line to $file" if ! grep "$line" $file > /dev/null; then echo "$line" | sudo tee -a $file > /dev/null fi # Enable command auto completion using sudo; modify ~/.bashrc: sed -i -e 's|# for examples$|&\ncomplete -cf sudo|' ~/.bashrc # Install tcpdump, cmd-line packet dump tool. Also install gitk, # a graphical git history viewer. $install tcpdump gitk # Install common text editors $install vim nano emacs # Install NTP $install ntp # Install vconfig for VLAN example if [ "$DIST" = "Fedora" -o "$DIST" = "RedHatEnterpriseServer" ]; then $install vconfig else $install vlan fi # Set git to colorize everything. git config --global color.diff auto git config --global color.status auto git config --global color.branch auto # Reduce boot screen opt-out delay. Modify timeout in /boot/grub/menu.lst to 1: if [ "$DIST" = "Debian" ]; then sudo sed -i -e 's/^timeout.*$/timeout 1/' /boot/grub/menu.lst fi # Clean unneeded debs: rm -f ~/linux-headers-* ~/linux-image-* } # Script to copy built OVS kernel module to where modprobe will # find them automatically. Removes the need to keep an environment variable # for insmod usage, and works nicely with multiple kernel versions. # # The downside is that after each recompilation of OVS you'll need to # re-run this script. If you're using only one kernel version, then it may be # a good idea to use a symbolic link in place of the copy below. function modprobe { echo "Setting up modprobe for OVS kmod..." set +o nounset if [ -z "$OVS_KMODS" ]; then echo "OVS_KMODS not set. Aborting." else sudo cp $OVS_KMODS $DRIVERS_DIR sudo depmod -a ${KERNEL_NAME} fi set -o nounset } function all { if [ "$DIST" = "Fedora" ]; then printf "\nFedora 18+ support (still work in progress):\n" printf " * Fedora 18+ has kernel 3.10 RPMS in the updates repositories\n" printf " * Fedora 18+ has openvswitch 1.10 RPMS in the updates repositories\n" printf " * the install.sh script options [-bfnpvw] should work.\n" printf " * for a basic setup just try:\n" printf " install.sh -fnpv\n\n" exit 3 fi echo "Installing all packages except for -eix (doxypy, ivs, nox-classic)..." kernel mn_deps # Skip mn_dev (doxypy/texlive/fonts/etc.) because it's huge # mn_dev of install_wireshark ovs # We may add ivs once it's more mature # ivs # NOX-classic is deprecated, but you can install it manually if desired. # nox pox oftest cbench echo "Enjoy Mininet!" } # Restore disk space and remove sensitive files before shipping a VM. function vm_clean { echo "Cleaning VM..." sudo apt-get clean sudo apt-get autoremove sudo rm -rf /tmp/* sudo rm -rf openvswitch*.tar.gz # Remove sensistive files history -c # note this won't work if you have multiple bash sessions rm -f ~/.bash_history # need to clear in memory and remove on disk rm -f ~/.ssh/id_rsa* ~/.ssh/known_hosts sudo rm -f ~/.ssh/authorized_keys* # Remove SSH keys and regenerate on boot echo 'Removing SSH keys from /etc/ssh/' sudo rm -f /etc/ssh/*key* if ! grep mininet /etc/rc.local >& /dev/null; then sudo sed -i -e "s/exit 0//" /etc/rc.local echo ' # mininet: regenerate ssh keys if we deleted them if ! stat -t /etc/ssh/*key* >/dev/null 2>&1; then /usr/sbin/dpkg-reconfigure openssh-server fi exit 0 ' | sudo tee -a /etc/rc.local > /dev/null fi # Remove Mininet files #sudo rm -f /lib/modules/python2.5/site-packages/mininet* #sudo rm -f /usr/bin/mnexec # Clear optional dev script for SSH keychain load on boot rm -f ~/.bash_profile # Clear git changes git config --global user.name "None" git config --global user.email "None" # Note: you can shrink the .vmdk in vmware using # vmware-vdiskmanager -k *.vmdk echo "Zeroing out disk blocks for efficient compaction..." time sudo dd if=/dev/zero of=/tmp/zero bs=1M || true sync ; sleep 1 ; sync ; sudo rm -f /tmp/zero } function usage { printf '\nUsage: %s [-abcdfhikmnprtvVwxy03]\n\n' $(basename $0) >&2 printf 'This install script attempts to install useful packages\n' >&2 printf 'for Mininet. It should (hopefully) work on Ubuntu 11.10+\n' >&2 printf 'If you run into trouble, try\n' >&2 printf 'installing one thing at a time, and looking at the \n' >&2 printf 'specific installation function in this script.\n\n' >&2 printf 'options:\n' >&2 printf -- ' -a: (default) install (A)ll packages - good luck!\n' >&2 printf -- ' -b: install controller (B)enchmark (oflops)\n' >&2 printf -- ' -c: (C)lean up after kernel install\n' >&2 printf -- ' -d: (D)elete some sensitive files from a VM image\n' >&2 printf -- ' -e: install Mininet d(E)veloper dependencies\n' >&2 printf -- ' -f: install Open(F)low\n' >&2 printf -- ' -h: print this (H)elp message\n' >&2 printf -- ' -i: install (I)ndigo Virtual Switch\n' >&2 printf -- ' -k: install new (K)ernel\n' >&2 printf -- ' -m: install Open vSwitch kernel (M)odule from source dir\n' >&2 printf -- ' -n: install Mini(N)et dependencies + core files\n' >&2 printf -- ' -p: install (P)OX OpenFlow Controller\n' >&2 printf -- ' -r: remove existing Open vSwitch packages\n' >&2 printf -- ' -s : place dependency (S)ource/build trees in \n' >&2 printf -- ' -t: complete o(T)her Mininet VM setup tasks\n' >&2 printf -- ' -v: install Open (V)switch\n' >&2 printf -- ' -V : install a particular version of Open (V)switch on Ubuntu\n' >&2 printf -- ' -w: install OpenFlow (W)ireshark dissector\n' >&2 printf -- ' -y: install R(y)u Controller\n' >&2 printf -- ' -x: install NO(X) Classic OpenFlow controller\n' >&2 printf -- ' -0: (default) -0[fx] installs OpenFlow 1.0 versions\n' >&2 printf -- ' -3: -3[fx] installs OpenFlow 1.3 versions\n' >&2 exit 2 } OF_VERSION=1.0 if [ $# -eq 0 ] then all else while getopts 'abcdefhikmnprs:tvV:wxy03' OPTION do case $OPTION in a) all;; b) cbench;; c) kernel_clean;; d) vm_clean;; e) mn_dev;; f) case $OF_VERSION in 1.0) of;; 1.3) of13;; *) echo "Invalid OpenFlow version $OF_VERSION";; esac;; h) usage;; i) ivs;; k) kernel;; m) modprobe;; n) mn_deps;; p) pox;; r) remove_ovs;; s) mkdir -p $OPTARG; # ensure the directory is created BUILD_DIR="$( cd -P "$OPTARG" && pwd )"; # get the full path echo "Dependency installation directory: $BUILD_DIR";; t) vm_other;; v) ovs;; V) OVS_RELEASE=$OPTARG; ubuntuOvs;; w) install_wireshark;; x) case $OF_VERSION in 1.0) nox;; 1.3) nox13;; *) echo "Invalid OpenFlow version $OF_VERSION";; esac;; y) ryu;; 0) OF_VERSION=1.0;; 3) OF_VERSION=1.3;; ?) usage;; esac done shift $(($OPTIND - 1)) fi mininet-2.2.2/util/kbuild/000077500000000000000000000000001306431124000154015ustar00rootroot00000000000000mininet-2.2.2/util/kbuild/kbuild000077500000000000000000000036121306431124000166030ustar00rootroot00000000000000#!/bin/bash # Script to build new Debian kernel packages for 2.6.33.1 # # Caveats: # # Since kernel-package in debian-stable doesn't work with # 2.6.33.1, we attempt to patch it in place. This may not be the # right thing to do. A possibly better alternative is to install # a later version of kernel-package, although that could potentially # cause problems with upgrades, etc.. # # The patch to tun.c is a workaround rather than a real fix. # # Building a full Debian kernel package with all drivers takes a long # time, 60-80 minutes on my laptop. # # Re-running a make-kpkg may not work without running 'make-kpkg clean' # Season to taste # export PATH=/usr/lib/ccache:$PATH export CONCURRENCY_LEVEL=3 debversion=2.6.26-2-686-bigmem image=linux-image-$debversion echo "*** Installing $image" sudo aptitude install $image newversion=2.6.33.1 archive=linux-$newversion.tar.bz2 location=http://www.kernel.org/pub/linux/kernel/v2.6 echo "*** Fetching $location/$archive" wget -c $location/$archive tree=linux-$newversion if [ -e $tree ]; then echo "*** $tree already exists" else echo "*** Extracting $archive" tar xjf $archive fi echo "*** Patching tun driver" patch $tree/drivers/net/tun.c < tun.patch echo "*** Patching debian build script" sudo patch /usr/share/kernel-package/ruleset/misc/version_vars.mk < version_vars.patch config=/boot/config-$debversion echo "*** Copying $config to $tree/.config" cp $config $tree/.config echo "*** Updating config" cd $tree yes '' | make oldconfig 1> /dev/null sed 's/# CONFIG_NET_NS is not set/CONFIG_NET_NS=y/' .config > .config-new mv .config-new .config echo "*** Result: " `grep CONFIG_NET_NS .config` echo "*** Building kernel" time fakeroot make-kpkg --initrd --append-to-version=-mininet kernel_image kernel_headers cd .. echo "*** Done - package should be in current directory" ls *$newversion*.deb echo "To install:" echo "# dpkg -i " *$newversion*.deb mininet-2.2.2/util/kbuild/tun.patch000066400000000000000000000011001306431124000172200ustar00rootroot00000000000000--- linux-2.6.33.1/drivers/net/tun.c 2010-03-24 22:47:32.000000000 -0700 +++ tun-new.c 2010-03-24 22:45:00.000000000 -0700 @@ -1006,7 +1006,9 @@ if (err < 0) goto err_free_sk; - if (device_create_file(&tun->dev->dev, &dev_attr_tun_flags) || + /* BL hack: check for null parent kobj */ + if (!tun->dev->dev.kobj.sd || + device_create_file(&tun->dev->dev, &dev_attr_tun_flags) || device_create_file(&tun->dev->dev, &dev_attr_owner) || device_create_file(&tun->dev->dev, &dev_attr_group)) printk(KERN_ERR "Failed to create tun sysfs files\n"); mininet-2.2.2/util/kbuild/version_vars.patch000066400000000000000000000020171306431124000211420ustar00rootroot00000000000000--- /usr/share/kernel-package/ruleset/misc/version_vars.mk 2010-03-25 18:14:41.000000000 -0700 +++ version_vars.mk 2010-03-03 06:46:59.000000000 -0800 @@ -138,11 +138,13 @@ EXTRAV_ARG := endif -UTS_RELEASE_HEADER=$(call doit,if [ -f include/linux/utsrelease.h ]; then \ +UTS_RELEASE_HEADER=$(call doit, if [ -f include/generated/utsrelease.h ]; then \ + echo include/generated/utsrelease.h; \ + else if [ -f include/linux/utsrelease.h ]; then \ echo include/linux/utsrelease.h; \ else \ echo include/linux/version.h ; \ - fi) + fi fi) UTS_RELEASE_VERSION=$(call doit,if [ -f $(UTS_RELEASE_HEADER) ]; then \ grep 'define UTS_RELEASE' $(UTS_RELEASE_HEADER) | \ perl -nle 'm/^\s*\#define\s+UTS_RELEASE\s+("?)(\S+)\1/g && print $$2;';\ mininet-2.2.2/util/m000077500000000000000000000014601306431124000143120ustar00rootroot00000000000000#!/bin/bash # Attach to a Mininet host and run a command if [ -z $1 ]; then echo "usage: $0 host cmd [args...]" exit 1 else host=$1 fi pid=`ps ax | grep "mininet:$host$" | grep bash | grep -v mnexec | awk '{print $1};'` if echo $pid | grep -q ' '; then echo "Error: found multiple mininet:$host processes" exit 2 fi if [ "$pid" == "" ]; then echo "Could not find Mininet host $host" exit 3 fi if [ -z $2 ]; then cmd='bash' else shift cmd=$* fi cgroup=/sys/fs/cgroup/cpu/$host 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" eval $cmd mininet-2.2.2/util/nox-patches/000077500000000000000000000000001306431124000163605ustar00rootroot00000000000000mininet-2.2.2/util/nox-patches/0001-OpenFlow-tutorial-port-nox-destiny.patch000066400000000000000000000231401306431124000266120ustar00rootroot00000000000000From 5c9610ffb88c89b0f36359ad3c7547831482a3ff Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 3 Feb 2012 14:48:58 -0800 Subject: [PATCH] OpenFlow tutorial port nox-destiny. --- src/nox/coreapps/examples/Makefile.am | 2 +- src/nox/coreapps/examples/tutorial/Makefile.am | 25 ++++ src/nox/coreapps/examples/tutorial/meta.json | 12 ++ src/nox/coreapps/examples/tutorial/pytutorial.py | 67 +++++++++++ src/nox/coreapps/examples/tutorial/tutorial.cc | 134 ++++++++++++++++++++++ 5 files changed, 239 insertions(+), 1 deletions(-) create mode 100644 src/nox/coreapps/examples/tutorial/Makefile.am create mode 100644 src/nox/coreapps/examples/tutorial/__init__.py create mode 100644 src/nox/coreapps/examples/tutorial/meta.json create mode 100644 src/nox/coreapps/examples/tutorial/pytutorial.py create mode 100644 src/nox/coreapps/examples/tutorial/tutorial.cc diff --git a/src/nox/coreapps/examples/Makefile.am b/src/nox/coreapps/examples/Makefile.am index 126f32e..1a0458c 100644 --- a/src/nox/coreapps/examples/Makefile.am +++ b/src/nox/coreapps/examples/Makefile.am @@ -1,6 +1,6 @@ include ../../../Make.vars -SUBDIRS = t +SUBDIRS = tutorial t EXTRA_DIST =\ meta.json\ diff --git a/src/nox/coreapps/examples/tutorial/Makefile.am b/src/nox/coreapps/examples/tutorial/Makefile.am new file mode 100644 index 0000000..51cf921 --- /dev/null +++ b/src/nox/coreapps/examples/tutorial/Makefile.am @@ -0,0 +1,25 @@ +include ../../../../Make.vars + +EXTRA_DIST =\ + meta.xml \ + __init__.py \ + pytutorial.py + +if PY_ENABLED +AM_CPPFLAGS += $(PYTHON_CPPFLAGS) +endif # PY_ENABLED + +pkglib_LTLIBRARIES = \ + tutorial.la + +tutorial_la_CPPFLAGS = $(AM_CPPFLAGS) -I $(top_srcdir)/src/nox -I $(top_srcdir)/src/nox/coreapps/ +tutorial_la_SOURCES = tutorial.cc +tutorial_la_LDFLAGS = -module -export-dynamic + +NOX_RUNTIMEFILES = meta.json \ + __init__.py \ + pytutorial.py + +all-local: nox-all-local +clean-local: nox-clean-local +install-exec-hook: nox-install-local diff --git a/src/nox/coreapps/examples/tutorial/__init__.py b/src/nox/coreapps/examples/tutorial/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/nox/coreapps/examples/tutorial/meta.json b/src/nox/coreapps/examples/tutorial/meta.json new file mode 100644 index 0000000..7a9f227 --- /dev/null +++ b/src/nox/coreapps/examples/tutorial/meta.json @@ -0,0 +1,12 @@ +{ + "components": [ + { + "name": "tutorial", + "library": "tutorial" + }, + { + "name": "pytutorial", + "python": "nox.coreapps.examples.tutorial.pytutorial" + } + ] +} diff --git a/src/nox/coreapps/examples/tutorial/pytutorial.py b/src/nox/coreapps/examples/tutorial/pytutorial.py new file mode 100644 index 0000000..1e21c0b --- /dev/null +++ b/src/nox/coreapps/examples/tutorial/pytutorial.py @@ -0,0 +1,67 @@ +# Tutorial Controller +# Starts as a hub, and your job is to turn this into a learning switch. + +import logging + +from nox.lib.core import * +import nox.lib.openflow as openflow +from nox.lib.packet.ethernet import ethernet +from nox.lib.packet.packet_utils import mac_to_str, mac_to_int + +log = logging.getLogger('nox.coreapps.tutorial.pytutorial') + + +class pytutorial(Component): + + def __init__(self, ctxt): + Component.__init__(self, ctxt) + # Use this table to store MAC addresses in the format of your choice; + # Functions already imported, including mac_to_str, and mac_to_int, + # should prove useful for converting the byte array provided by NOX + # for packet MAC destination fields. + # This table is initialized to empty when your module starts up. + self.mac_to_port = {} # key: MAC addr; value: port + + def learn_and_forward(self, dpid, inport, packet, buf, bufid): + """Learn MAC src port mapping, then flood or send unicast.""" + + # Initial hub behavior: flood packet out everything but input port. + # Comment out the line below when starting the exercise. + self.send_openflow(dpid, bufid, buf, openflow.OFPP_FLOOD, inport) + + # Starter psuedocode for learning switch exercise below: you'll need to + # replace each pseudocode line with more specific Python code. + + # Learn the port for the source MAC + #self.mac_to_port = + #if (destination MAC of the packet is known): + # Send unicast packet to known output port + #self.send_openflow( ) + # Later, only after learning controller works: + # push down flow entry and remove the send_openflow command above. + #self.install_datapath_flow( ) + #else: + #flood packet out everything but the input port + #self.send_openflow(dpid, bufid, buf, openflow.OFPP_FLOOD, inport) + + def packet_in_callback(self, dpid, inport, reason, len, bufid, packet): + """Packet-in handler""" + if not packet.parsed: + log.debug('Ignoring incomplete packet') + else: + self.learn_and_forward(dpid, inport, packet, packet.arr, bufid) + + return CONTINUE + + def install(self): + self.register_for_packet_in(self.packet_in_callback) + + def getInterface(self): + return str(pytutorial) + +def getFactory(): + class Factory: + def instance(self, ctxt): + return pytutorial(ctxt) + + return Factory() diff --git a/src/nox/coreapps/examples/tutorial/tutorial.cc b/src/nox/coreapps/examples/tutorial/tutorial.cc new file mode 100644 index 0000000..e7240cc --- /dev/null +++ b/src/nox/coreapps/examples/tutorial/tutorial.cc @@ -0,0 +1,134 @@ +#include "component.hh" +#include "config.h" +#include "packet-in.hh" +#include "flow.hh" +#include "assert.hh" +#include "netinet++/ethernetaddr.hh" +#include "netinet++/ethernet.hh" +#include +#include +#ifdef LOG4CXX_ENABLED +#include +#include "log4cxx/logger.h" +#else +#include "vlog.hh" +#endif + +using namespace std; +using namespace vigil; +using namespace vigil::container; + +namespace +{ + static Vlog_module lg("tutorial"); + + /** Learning switch. + */ + class tutorial + : public Component + { + public: + /** Constructor. + */ + tutorial(const Context* c, const json_object* node) + : Component(c) + { } + + /** Configuration. + * Add handler for packet-in event. + */ + void configure(const Configuration*) + { + register_handler + (boost::bind(&tutorial::handle, this, _1)); + } + + /** Just simply install. + */ + void install() + { + lg.dbg(" Install called "); + } + + /** Function to setup flow. + */ + void setup_flow(Flow& flow, datapathid datapath_id , + uint32_t buffer_id, uint16_t out_port) + { + ofp_flow_mod* ofm; + size_t size = sizeof *ofm + sizeof(ofp_action_output); + boost::shared_array raw_of(new char[size]); + ofm = (ofp_flow_mod*) raw_of.get(); + + ofm->header.version = OFP_VERSION; + ofm->header.type = OFPT_FLOW_MOD; + ofm->header.length = htons(size); + ofm->match.wildcards = htonl(0); + ofm->match.in_port = htons(flow.in_port); + ofm->match.dl_vlan = flow.dl_vlan; + memcpy(ofm->match.dl_src, flow.dl_src.octet, sizeof ofm->match.dl_src); + memcpy(ofm->match.dl_dst, flow.dl_dst.octet, sizeof ofm->match.dl_dst); + ofm->match.dl_type = flow.dl_type; + ofm->match.nw_src = flow.nw_src; + ofm->match.nw_dst = flow.nw_dst; + ofm->match.nw_proto = flow.nw_proto; + ofm->match.tp_src = flow.tp_src; + ofm->match.tp_dst = flow.tp_dst; + ofm->command = htons(OFPFC_ADD); + ofm->buffer_id = htonl(buffer_id); + ofm->idle_timeout = htons(5); + ofm->hard_timeout = htons(OFP_FLOW_PERMANENT); + ofm->priority = htons(OFP_DEFAULT_PRIORITY); + ofp_action_output& action = *((ofp_action_output*)ofm->actions); + memset(&action, 0, sizeof(ofp_action_output)); + action.type = htons(OFPAT_OUTPUT); + action.len = htons(sizeof(ofp_action_output)); + action.max_len = htons(0); + action.port = htons(out_port); + send_openflow_command(datapath_id, &ofm->header, true); + } + + /** Function to handle packets. + * @param datapath_id datapath id of switch + * @param in_port port packet is received + * @param buffer_id buffer id of packet + * @param source source mac address in host order + * @param destination destination mac address in host order + */ + void handle_packet(datapathid datapath_id, uint16_t in_port, uint32_t buffer_id, + uint64_t source, uint64_t destination) + { + send_openflow_packet(datapath_id, buffer_id, OFPP_FLOOD, + in_port, true); + } + + /** Packet-on handler. + */ + Disposition handle(const Event& e) + { + const Packet_in_event& pi = assert_cast(e); + uint32_t buffer_id = pi.buffer_id; + Flow flow(pi.in_port, *pi.get_buffer()); + + // drop LLDP packets + if (flow.dl_type == ethernet::LLDP) + return CONTINUE; + + // pass handle of unicast packet, else flood + if (!flow.dl_src.is_multicast()) + handle_packet(pi.datapath_id, pi.in_port, buffer_id, + flow.dl_src.hb_long(), flow.dl_dst.hb_long()); + else + send_openflow_packet(pi.datapath_id, buffer_id, OFPP_FLOOD, + pi.in_port, true); + + return CONTINUE; + } + + private: +}; + +REGISTER_COMPONENT(container::Simple_component_factory, + tutorial); + +} // unnamed namespace -- 1.7.5.4 mininet-2.2.2/util/nox-patches/0002-nox-ubuntu12-hacks.patch000066400000000000000000000150401306431124000233360ustar00rootroot00000000000000From 166693d7cb640d4a41251b87e92c52d9c688196b Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Mon, 14 May 2012 15:30:44 -0700 Subject: [PATCH] Hacks to get NOX classic/destiny to compile under Ubuntu 12.04 Thanks to Srinivasu R. Kanduru for the initial patch. Apologies for the hacks - it is my hope that this will be fixed upstream eventually. --- config/ac_pkg_swig.m4 | 7 ++++--- src/Make.vars | 2 +- src/nox/coreapps/pyrt/deferredcallback.cc | 2 +- src/nox/coreapps/pyrt/pyglue.cc | 2 +- src/nox/coreapps/pyrt/pyrt.cc | 2 +- src/nox/netapps/authenticator/auth.i | 2 ++ src/nox/netapps/authenticator/flow_util.i | 1 + src/nox/netapps/routing/routing.i | 2 ++ .../switch_management/pyswitch_management.i | 2 ++ src/nox/netapps/tests/tests.cc | 2 +- src/nox/netapps/topology/pytopology.i | 2 ++ 11 files changed, 18 insertions(+), 8 deletions(-) diff --git a/config/ac_pkg_swig.m4 b/config/ac_pkg_swig.m4 index d12556e..9b608f2 100644 --- a/config/ac_pkg_swig.m4 +++ b/config/ac_pkg_swig.m4 @@ -78,9 +78,10 @@ AC_DEFUN([AC_PROG_SWIG],[ if test -z "$available_patch" ; then [available_patch=0] fi - if test $available_major -ne $required_major \ - -o $available_minor -ne $required_minor \ - -o $available_patch -lt $required_patch ; then + major_done=`test $available_major -gt $required_major` + minor_done=`test $available_minor -gt $required_minor` + if test !$major_done -a !$minor_done \ + -a $available_patch -lt $required_patch ; then AC_MSG_WARN([SWIG version >= $1 is required. You have $swig_version. You should look at http://www.swig.org]) SWIG='' else diff --git a/src/Make.vars b/src/Make.vars index d70d6aa..93b2879 100644 --- a/src/Make.vars +++ b/src/Make.vars @@ -53,7 +53,7 @@ AM_LDFLAGS += -export-dynamic endif # set python runtimefiles to be installed in the same directory as pkg -pkglib_SCRIPTS = $(NOX_RUNTIMEFILES) $(NOX_PYBUILDFILES) +pkgdata_SCRIPTS = $(NOX_RUNTIMEFILES) $(NOX_PYBUILDFILES) BUILT_SOURCES = $(NOX_PYBUILDFILES) # Runtime-files build and clean rules diff --git a/src/nox/coreapps/pyrt/deferredcallback.cc b/src/nox/coreapps/pyrt/deferredcallback.cc index 3a40fa7..111a586 100644 --- a/src/nox/coreapps/pyrt/deferredcallback.cc +++ b/src/nox/coreapps/pyrt/deferredcallback.cc @@ -69,7 +69,7 @@ DeferredCallback::get_instance(const Callback& c) DeferredCallback* cb = new DeferredCallback(c); // flag as used in *_wrap.cc....correct? - return SWIG_Python_NewPointerObj(cb, s, SWIG_POINTER_OWN | 0); + return SWIG_Python_NewPointerObj(m, cb, s, SWIG_POINTER_OWN | 0); } bool diff --git a/src/nox/coreapps/pyrt/pyglue.cc b/src/nox/coreapps/pyrt/pyglue.cc index 48b9716..317fd04 100644 --- a/src/nox/coreapps/pyrt/pyglue.cc +++ b/src/nox/coreapps/pyrt/pyglue.cc @@ -874,7 +874,7 @@ to_python(const Flow& flow) if (!s) { throw std::runtime_error("Could not find Flow SWIG type_info"); } - return SWIG_Python_NewPointerObj(f, s, SWIG_POINTER_OWN | 0); + return SWIG_Python_NewPointerObj(m, f, s, SWIG_POINTER_OWN | 0); // PyObject* dict = PyDict_New(); // if (!dict) { diff --git a/src/nox/coreapps/pyrt/pyrt.cc b/src/nox/coreapps/pyrt/pyrt.cc index fbda461..8ec05d6 100644 --- a/src/nox/coreapps/pyrt/pyrt.cc +++ b/src/nox/coreapps/pyrt/pyrt.cc @@ -776,7 +776,7 @@ Python_event_manager::create_python_context(const Context* ctxt, pretty_print_python_exception()); } - PyObject* pyctxt = SWIG_Python_NewPointerObj(p, s, 0); + PyObject* pyctxt = SWIG_Python_NewPointerObj(m, p, s, 0); Py_INCREF(pyctxt); // XXX needed? //Py_DECREF(m); diff --git a/src/nox/netapps/authenticator/auth.i b/src/nox/netapps/authenticator/auth.i index 1de1a17..bfa04e2 100644 --- a/src/nox/netapps/authenticator/auth.i +++ b/src/nox/netapps/authenticator/auth.i @@ -18,6 +18,8 @@ %module "nox.netapps.authenticator.pyauth" +// Hack to get it to compile -BL +%include "std_list.i" %{ #include "core_events.hh" #include "pyrt/pycontext.hh" diff --git a/src/nox/netapps/authenticator/flow_util.i b/src/nox/netapps/authenticator/flow_util.i index f67c3ef..2a314e2 100644 --- a/src/nox/netapps/authenticator/flow_util.i +++ b/src/nox/netapps/authenticator/flow_util.i @@ -32,6 +32,7 @@ using namespace vigil::applications; %} %include "common-defs.i" +%include "std_list.i" %import "netinet/netinet.i" %import "pyrt/event.i" diff --git a/src/nox/netapps/routing/routing.i b/src/nox/netapps/routing/routing.i index 44ccb3d..f9221a2 100644 --- a/src/nox/netapps/routing/routing.i +++ b/src/nox/netapps/routing/routing.i @@ -17,6 +17,8 @@ */ %module "nox.netapps.routing.pyrouting" +// Hack to get it to compile -BL +%include "std_list.i" %{ #include "pyrouting.hh" #include "routing.hh" diff --git a/src/nox/netapps/switch_management/pyswitch_management.i b/src/nox/netapps/switch_management/pyswitch_management.i index 72bfed4..ad2c90d 100644 --- a/src/nox/netapps/switch_management/pyswitch_management.i +++ b/src/nox/netapps/switch_management/pyswitch_management.i @@ -18,6 +18,8 @@ %module "nox.netapps.pyswitch_management" +// Hack to get it to compile -BL +%include "std_list.i" %{ #include "switch_management_proxy.hh" #include "pyrt/pycontext.hh" diff --git a/src/nox/netapps/tests/tests.cc b/src/nox/netapps/tests/tests.cc index 20e900d..f027028 100644 --- a/src/nox/netapps/tests/tests.cc +++ b/src/nox/netapps/tests/tests.cc @@ -306,7 +306,7 @@ private: throw runtime_error("Could not find PyContext SWIG type_info."); } - PyObject* pyctxt = SWIG_Python_NewPointerObj(p, s, 0); + PyObject* pyctxt = SWIG_Python_NewPointerObj(m, p, s, 0); assert(pyctxt); Py_DECREF(m); diff --git a/src/nox/netapps/topology/pytopology.i b/src/nox/netapps/topology/pytopology.i index 94a9f4b..7a8cd94 100644 --- a/src/nox/netapps/topology/pytopology.i +++ b/src/nox/netapps/topology/pytopology.i @@ -18,6 +18,8 @@ %module "nox.netapps.topology" +// Hack to get it to compile -BL +%include "std_list.i" %{ #include "pytopology.hh" #include "pyrt/pycontext.hh" -- 1.7.5.4 mininet-2.2.2/util/nox-patches/README000066400000000000000000000002161306431124000172370ustar00rootroot000000000000000001: This patch adds the OpenFlow tutorial module source code to nox-destiny. 0002: This patch hacks nox-destiny to compile on Ubuntu 12.04. mininet-2.2.2/util/openflow-patches/000077500000000000000000000000001306431124000174055ustar00rootroot00000000000000mininet-2.2.2/util/openflow-patches/README000066400000000000000000000003111306431124000202600ustar00rootroot00000000000000Patches for OpenFlow Reference Implementation controller.patch: patch controller to support up to 4096 switches (up from 16!) datapath.patch: patch to kernel datapath to compile with CONFIG_NET_NS=y mininet-2.2.2/util/openflow-patches/controller.patch000066400000000000000000000006001306431124000226050ustar00rootroot00000000000000diff --git a/controller/controller.c b/controller/controller.c index 41f2547..6eec590 100644 --- a/controller/controller.c +++ b/controller/controller.c @@ -58,8 +58,8 @@ #include "vlog.h" #define THIS_MODULE VLM_controller -#define MAX_SWITCHES 16 -#define MAX_LISTENERS 16 +#define MAX_SWITCHES 4096 +#define MAX_LISTENERS 4096 struct switch_ { struct lswitch *lswitch; mininet-2.2.2/util/openflow-patches/datapath.patch000066400000000000000000000014751306431124000222230ustar00rootroot00000000000000diff --git a/datapath/datapath.c b/datapath/datapath.c index 4a4d3a2..365aa25 100644 --- a/datapath/datapath.c +++ b/datapath/datapath.c @@ -47,6 +47,9 @@ #include "compat.h" +#ifdef CONFIG_NET_NS +#include +#endif /* Strings to describe the manufacturer, hardware, and software. This data * is queriable through the switch description stats message. */ @@ -259,6 +262,10 @@ send_openflow_skb(const struct datapath *dp, struct sk_buff *skb, const struct sender *sender) { return (sender - ? genlmsg_unicast(skb, sender->pid) +#ifdef CONFIG_NET_NS + ? genlmsg_unicast(&init_net, skb, sender->pid) +#else + ? genlmsg_unicast(skb, sender->pid) +#endif : genlmsg_multicast(skb, 0, dp_mc_group(dp), GFP_ATOMIC)); } mininet-2.2.2/util/sch_htb-ofbuf/000077500000000000000000000000001306431124000166405ustar00rootroot00000000000000mininet-2.2.2/util/sch_htb-ofbuf/Makefile000066400000000000000000000007431306431124000203040ustar00rootroot00000000000000obj-m = sch_htb.o KVERSION = $(shell uname -r) all: make -C /lib/modules/$(KVERSION)/build M=$(PWD) modules install: test -e /lib/modules/$(KVERSION)/kernel/net/sched/sch_htb.ko.bak || mv /lib/modules/$(KVERSION)/kernel/net/sched/sch_htb.ko /lib/modules/$(KVERSION)/kernel/net/sched/sch_htb.ko.bak cp sch_htb.ko /lib/modules/$(KVERSION)/kernel/net/sched/sch_htb.ko rmmod sch_htb modprobe sch_htb clean: make -C /lib/modules/$(KVERSION)/build M=$(PWD) clean mininet-2.2.2/util/sch_htb-ofbuf/README000066400000000000000000000003261306431124000175210ustar00rootroot00000000000000Modified sch_htb implementation with ofbuf support. To compile, just type make. To use this module instead of regular sch_htb, do: 0. make 1. rmmod sch_htb 2. insmod ./sch_htb.ko To revert, just rmmod sch_htb. mininet-2.2.2/util/sch_htb-ofbuf/sch_htb.c000066400000000000000000001256151306431124000204300ustar00rootroot00000000000000#define OFBUF (1) /* * net/sched/sch_htb.c Hierarchical token bucket, feed tree version * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. * * Authors: Martin Devera, * * Credits (in time order) for older HTB versions: * Stef Coene * HTB support at LARTC mailing list * Ondrej Kraus, * found missing INIT_QDISC(htb) * Vladimir Smelhaus, Aamer Akhter, Bert Hubert * helped a lot to locate nasty class stall bug * Andi Kleen, Jamal Hadi, Bert Hubert * code review and helpful comments on shaping * Tomasz Wrona, * created test case so that I was able to fix nasty bug * Wilfried Weissmann * spotted bug in dequeue code and helped with fix * Jiri Fojtasek * fixed requeue routine * and many others. thanks. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* HTB algorithm. Author: devik@cdi.cz ======================================================================== HTB is like TBF with multiple classes. It is also similar to CBQ because it allows to assign priority to each class in hierarchy. In fact it is another implementation of Floyd's formal sharing. Levels: Each class is assigned level. Leaf has ALWAYS level 0 and root classes have level TC_HTB_MAXDEPTH-1. Interior nodes has level one less than their parent. */ static int htb_hysteresis __read_mostly = 0; /* whether to use mode hysteresis for speedup */ #define HTB_VER 0x30011 /* major must be matched with number suplied by TC as version */ #if HTB_VER >> 16 != TC_HTB_PROTOVER #error "Mismatched sch_htb.c and pkt_sch.h" #endif /* Module parameter and sysfs export */ module_param (htb_hysteresis, int, 0640); MODULE_PARM_DESC(htb_hysteresis, "Hysteresis mode, less CPU load, less accurate"); /* used internaly to keep status of single class */ enum htb_cmode { HTB_CANT_SEND, /* class can't send and can't borrow */ HTB_MAY_BORROW, /* class can't send but may borrow */ HTB_CAN_SEND /* class can send */ }; /* interior & leaf nodes; props specific to leaves are marked L: */ struct htb_class { struct Qdisc_class_common common; /* general class parameters */ struct gnet_stats_basic_packed bstats; struct gnet_stats_queue qstats; struct gnet_stats_rate_est rate_est; struct tc_htb_xstats xstats; /* our special stats */ int refcnt; /* usage count of this class */ /* topology */ int level; /* our level (see above) */ unsigned int children; struct htb_class *parent; /* parent class */ int prio; /* these two are used only by leaves... */ int quantum; /* but stored for parent-to-leaf return */ union { struct htb_class_leaf { struct Qdisc *q; int deficit[TC_HTB_MAXDEPTH]; struct list_head drop_list; } leaf; struct htb_class_inner { struct rb_root feed[TC_HTB_NUMPRIO]; /* feed trees */ struct rb_node *ptr[TC_HTB_NUMPRIO]; /* current class ptr */ /* When class changes from state 1->2 and disconnects from * parent's feed then we lost ptr value and start from the * first child again. Here we store classid of the * last valid ptr (used when ptr is NULL). */ u32 last_ptr_id[TC_HTB_NUMPRIO]; } inner; } un; struct rb_node node[TC_HTB_NUMPRIO]; /* node for self or feed tree */ struct rb_node pq_node; /* node for event queue */ psched_time_t pq_key; int prio_activity; /* for which prios are we active */ enum htb_cmode cmode; /* current mode of the class */ /* class attached filters */ struct tcf_proto *filter_list; int filter_cnt; /* token bucket parameters */ struct qdisc_rate_table *rate; /* rate table of the class itself */ struct qdisc_rate_table *ceil; /* ceiling rate (limits borrows too) */ long buffer, cbuffer; /* token bucket depth/rate */ psched_tdiff_t mbuffer; /* max wait time */ long tokens, ctokens; /* current number of tokens */ psched_time_t t_c; /* checkpoint time */ }; struct htb_sched { struct Qdisc_class_hash clhash; struct list_head drops[TC_HTB_NUMPRIO];/* active leaves (for drops) */ /* self list - roots of self generating tree */ struct rb_root row[TC_HTB_MAXDEPTH][TC_HTB_NUMPRIO]; int row_mask[TC_HTB_MAXDEPTH]; struct rb_node *ptr[TC_HTB_MAXDEPTH][TC_HTB_NUMPRIO]; u32 last_ptr_id[TC_HTB_MAXDEPTH][TC_HTB_NUMPRIO]; /* self wait list - roots of wait PQs per row */ struct rb_root wait_pq[TC_HTB_MAXDEPTH]; /* time of nearest event per level (row) */ psched_time_t near_ev_cache[TC_HTB_MAXDEPTH]; int defcls; /* class where unclassified flows go to */ /* filters for qdisc itself */ struct tcf_proto *filter_list; int rate2quantum; /* quant = rate / rate2quantum */ psched_time_t now; /* cached dequeue time */ struct qdisc_watchdog watchdog; /* non shaped skbs; let them go directly thru */ struct sk_buff_head direct_queue; int direct_qlen; /* max qlen of above */ long direct_pkts; #if OFBUF /* overflow buffer */ struct sk_buff_head ofbuf; int ofbuf_queued; /* # packets queued in above */ #endif #define HTB_WARN_TOOMANYEVENTS 0x1 unsigned int warned; /* only one warning */ struct work_struct work; }; /* find class in global hash table using given handle */ static inline struct htb_class *htb_find(u32 handle, struct Qdisc *sch) { struct htb_sched *q = qdisc_priv(sch); struct Qdisc_class_common *clc; clc = qdisc_class_find(&q->clhash, handle); if (clc == NULL) return NULL; return container_of(clc, struct htb_class, common); } /** * htb_classify - classify a packet into class * * It returns NULL if the packet should be dropped or -1 if the packet * should be passed directly thru. In all other cases leaf class is returned. * We allow direct class selection by classid in priority. The we examine * filters in qdisc and in inner nodes (if higher filter points to the inner * node). If we end up with classid MAJOR:0 we enqueue the skb into special * internal fifo (direct). These packets then go directly thru. If we still * have no valid leaf we try to use MAJOR:default leaf. It still unsuccessful * then finish and return direct queue. */ #define HTB_DIRECT ((struct htb_class *)-1L) static struct htb_class *htb_classify(struct sk_buff *skb, struct Qdisc *sch, int *qerr) { struct htb_sched *q = qdisc_priv(sch); struct htb_class *cl; struct tcf_result res; struct tcf_proto *tcf; int result; /* allow to select class by setting skb->priority to valid classid; * note that nfmark can be used too by attaching filter fw with no * rules in it */ if (skb->priority == sch->handle) return HTB_DIRECT; /* X:0 (direct flow) selected */ cl = htb_find(skb->priority, sch); if (cl && cl->level == 0) return cl; *qerr = NET_XMIT_SUCCESS | __NET_XMIT_BYPASS; tcf = q->filter_list; while (tcf && (result = tc_classify(skb, tcf, &res)) >= 0) { #ifdef CONFIG_NET_CLS_ACT switch (result) { case TC_ACT_QUEUED: case TC_ACT_STOLEN: *qerr = NET_XMIT_SUCCESS | __NET_XMIT_STOLEN; case TC_ACT_SHOT: return NULL; } #endif cl = (void *)res.class; if (!cl) { if (res.classid == sch->handle) return HTB_DIRECT; /* X:0 (direct flow) */ cl = htb_find(res.classid, sch); if (!cl) break; /* filter selected invalid classid */ } if (!cl->level) return cl; /* we hit leaf; return it */ /* we have got inner class; apply inner filter chain */ tcf = cl->filter_list; } /* classification failed; try to use default class */ cl = htb_find(TC_H_MAKE(TC_H_MAJ(sch->handle), q->defcls), sch); if (!cl || cl->level) return HTB_DIRECT; /* bad default .. this is safe bet */ return cl; } /** * htb_add_to_id_tree - adds class to the round robin list * * Routine adds class to the list (actually tree) sorted by classid. * Make sure that class is not already on such list for given prio. */ static void htb_add_to_id_tree(struct rb_root *root, struct htb_class *cl, int prio) { struct rb_node **p = &root->rb_node, *parent = NULL; while (*p) { struct htb_class *c; parent = *p; c = rb_entry(parent, struct htb_class, node[prio]); if (cl->common.classid > c->common.classid) p = &parent->rb_right; else p = &parent->rb_left; } rb_link_node(&cl->node[prio], parent, p); rb_insert_color(&cl->node[prio], root); } /** * htb_add_to_wait_tree - adds class to the event queue with delay * * The class is added to priority event queue to indicate that class will * change its mode in cl->pq_key microseconds. Make sure that class is not * already in the queue. */ static void htb_add_to_wait_tree(struct htb_sched *q, struct htb_class *cl, long delay) { struct rb_node **p = &q->wait_pq[cl->level].rb_node, *parent = NULL; cl->pq_key = q->now + delay; if (cl->pq_key == q->now) cl->pq_key++; /* update the nearest event cache */ if (q->near_ev_cache[cl->level] > cl->pq_key) q->near_ev_cache[cl->level] = cl->pq_key; while (*p) { struct htb_class *c; parent = *p; c = rb_entry(parent, struct htb_class, pq_node); if (cl->pq_key >= c->pq_key) p = &parent->rb_right; else p = &parent->rb_left; } rb_link_node(&cl->pq_node, parent, p); rb_insert_color(&cl->pq_node, &q->wait_pq[cl->level]); } /** * htb_next_rb_node - finds next node in binary tree * * When we are past last key we return NULL. * Average complexity is 2 steps per call. */ static inline void htb_next_rb_node(struct rb_node **n) { *n = rb_next(*n); } /** * htb_add_class_to_row - add class to its row * * The class is added to row at priorities marked in mask. * It does nothing if mask == 0. */ static inline void htb_add_class_to_row(struct htb_sched *q, struct htb_class *cl, int mask) { q->row_mask[cl->level] |= mask; while (mask) { int prio = ffz(~mask); mask &= ~(1 << prio); htb_add_to_id_tree(q->row[cl->level] + prio, cl, prio); } } /* If this triggers, it is a bug in this code, but it need not be fatal */ static void htb_safe_rb_erase(struct rb_node *rb, struct rb_root *root) { if (RB_EMPTY_NODE(rb)) { WARN_ON(1); } else { rb_erase(rb, root); RB_CLEAR_NODE(rb); } } /** * htb_remove_class_from_row - removes class from its row * * The class is removed from row at priorities marked in mask. * It does nothing if mask == 0. */ static inline void htb_remove_class_from_row(struct htb_sched *q, struct htb_class *cl, int mask) { int m = 0; while (mask) { int prio = ffz(~mask); mask &= ~(1 << prio); if (q->ptr[cl->level][prio] == cl->node + prio) htb_next_rb_node(q->ptr[cl->level] + prio); htb_safe_rb_erase(cl->node + prio, q->row[cl->level] + prio); if (!q->row[cl->level][prio].rb_node) m |= 1 << prio; } q->row_mask[cl->level] &= ~m; } /** * htb_activate_prios - creates active classe's feed chain * * The class is connected to ancestors and/or appropriate rows * for priorities it is participating on. cl->cmode must be new * (activated) mode. It does nothing if cl->prio_activity == 0. */ static void htb_activate_prios(struct htb_sched *q, struct htb_class *cl) { struct htb_class *p = cl->parent; long m, mask = cl->prio_activity; while (cl->cmode == HTB_MAY_BORROW && p && mask) { m = mask; while (m) { int prio = ffz(~m); m &= ~(1 << prio); if (p->un.inner.feed[prio].rb_node) /* parent already has its feed in use so that * reset bit in mask as parent is already ok */ mask &= ~(1 << prio); htb_add_to_id_tree(p->un.inner.feed + prio, cl, prio); } p->prio_activity |= mask; cl = p; p = cl->parent; } if (cl->cmode == HTB_CAN_SEND && mask) htb_add_class_to_row(q, cl, mask); } /** * htb_deactivate_prios - remove class from feed chain * * cl->cmode must represent old mode (before deactivation). It does * nothing if cl->prio_activity == 0. Class is removed from all feed * chains and rows. */ static void htb_deactivate_prios(struct htb_sched *q, struct htb_class *cl) { struct htb_class *p = cl->parent; long m, mask = cl->prio_activity; while (cl->cmode == HTB_MAY_BORROW && p && mask) { m = mask; mask = 0; while (m) { int prio = ffz(~m); m &= ~(1 << prio); if (p->un.inner.ptr[prio] == cl->node + prio) { /* we are removing child which is pointed to from * parent feed - forget the pointer but remember * classid */ p->un.inner.last_ptr_id[prio] = cl->common.classid; p->un.inner.ptr[prio] = NULL; } htb_safe_rb_erase(cl->node + prio, p->un.inner.feed + prio); if (!p->un.inner.feed[prio].rb_node) mask |= 1 << prio; } p->prio_activity &= ~mask; cl = p; p = cl->parent; } if (cl->cmode == HTB_CAN_SEND && mask) htb_remove_class_from_row(q, cl, mask); } static inline long htb_lowater(const struct htb_class *cl) { if (htb_hysteresis) return cl->cmode != HTB_CANT_SEND ? -cl->cbuffer : 0; else return 0; } static inline long htb_hiwater(const struct htb_class *cl) { if (htb_hysteresis) return cl->cmode == HTB_CAN_SEND ? -cl->buffer : 0; else return 0; } /** * htb_class_mode - computes and returns current class mode * * It computes cl's mode at time cl->t_c+diff and returns it. If mode * is not HTB_CAN_SEND then cl->pq_key is updated to time difference * from now to time when cl will change its state. * Also it is worth to note that class mode doesn't change simply * at cl->{c,}tokens == 0 but there can rather be hysteresis of * 0 .. -cl->{c,}buffer range. It is meant to limit number of * mode transitions per time unit. The speed gain is about 1/6. */ static inline enum htb_cmode htb_class_mode(struct htb_class *cl, long *diff) { long toks; if ((toks = (cl->ctokens + *diff)) < htb_lowater(cl)) { *diff = -toks; return HTB_CANT_SEND; } if ((toks = (cl->tokens + *diff)) >= htb_hiwater(cl)) return HTB_CAN_SEND; *diff = -toks; return HTB_MAY_BORROW; } /** * htb_change_class_mode - changes classe's mode * * This should be the only way how to change classe's mode under normal * cirsumstances. Routine will update feed lists linkage, change mode * and add class to the wait event queue if appropriate. New mode should * be different from old one and cl->pq_key has to be valid if changing * to mode other than HTB_CAN_SEND (see htb_add_to_wait_tree). */ static void htb_change_class_mode(struct htb_sched *q, struct htb_class *cl, long *diff) { enum htb_cmode new_mode = htb_class_mode(cl, diff); if (new_mode == cl->cmode) return; if (cl->prio_activity) { /* not necessary: speed optimization */ if (cl->cmode != HTB_CANT_SEND) htb_deactivate_prios(q, cl); cl->cmode = new_mode; if (new_mode != HTB_CANT_SEND) htb_activate_prios(q, cl); } else cl->cmode = new_mode; } /** * htb_activate - inserts leaf cl into appropriate active feeds * * Routine learns (new) priority of leaf and activates feed chain * for the prio. It can be called on already active leaf safely. * It also adds leaf into droplist. */ static inline void htb_activate(struct htb_sched *q, struct htb_class *cl) { WARN_ON(cl->level || !cl->un.leaf.q || !cl->un.leaf.q->q.qlen); if (!cl->prio_activity) { cl->prio_activity = 1 << cl->prio; htb_activate_prios(q, cl); list_add_tail(&cl->un.leaf.drop_list, q->drops + cl->prio); } } /** * htb_deactivate - remove leaf cl from active feeds * * Make sure that leaf is active. In the other words it can't be called * with non-active leaf. It also removes class from the drop list. */ static inline void htb_deactivate(struct htb_sched *q, struct htb_class *cl) { WARN_ON(!cl->prio_activity); htb_deactivate_prios(q, cl); cl->prio_activity = 0; list_del_init(&cl->un.leaf.drop_list); } static int htb_enqueue(struct sk_buff *skb, struct Qdisc *sch) { int uninitialized_var(ret); struct htb_sched *q = qdisc_priv(sch); struct htb_class *cl = htb_classify(skb, sch, &ret); #if OFBUF if(cl != HTB_DIRECT && cl) skb_get(skb); #endif if (cl == HTB_DIRECT) { /* enqueue to helper queue */ if (q->direct_queue.qlen < q->direct_qlen) { __skb_queue_tail(&q->direct_queue, skb); q->direct_pkts++; } else { kfree_skb(skb); sch->qstats.drops++; return NET_XMIT_DROP; } #ifdef CONFIG_NET_CLS_ACT } else if (!cl) { if (ret & __NET_XMIT_BYPASS) sch->qstats.drops++; kfree_skb(skb); return ret; #endif } else if ((ret = qdisc_enqueue(skb, cl->un.leaf.q)) != NET_XMIT_SUCCESS) { /* We shouldn't drop this, but enqueue it into ofbuf */ // TODO: is skb actually valid? // Ans: looks like qdisc_enqueue will end up freeing the packet // if enqueue failed. So we should incr refcnt before calling qdisc_enqueue... #if OFBUF __skb_queue_tail(&q->ofbuf, skb); q->ofbuf_queued++; #else if (net_xmit_drop_count(ret)) { sch->qstats.drops++; cl->qstats.drops++; } return ret; #endif } else { bstats_update(&cl->bstats, skb); htb_activate(q, cl); #if OFBUF kfree_skb(skb); #endif } sch->q.qlen++; return NET_XMIT_SUCCESS; } static inline void htb_accnt_tokens(struct htb_class *cl, int bytes, long diff) { long toks = diff + cl->tokens; if (toks > cl->buffer) toks = cl->buffer; toks -= (long) qdisc_l2t(cl->rate, bytes); if (toks <= -cl->mbuffer) toks = 1 - cl->mbuffer; cl->tokens = toks; } static inline void htb_accnt_ctokens(struct htb_class *cl, int bytes, long diff) { long toks = diff + cl->ctokens; if (toks > cl->cbuffer) toks = cl->cbuffer; toks -= (long) qdisc_l2t(cl->ceil, bytes); if (toks <= -cl->mbuffer) toks = 1 - cl->mbuffer; cl->ctokens = toks; } /** * htb_charge_class - charges amount "bytes" to leaf and ancestors * * Routine assumes that packet "bytes" long was dequeued from leaf cl * borrowing from "level". It accounts bytes to ceil leaky bucket for * leaf and all ancestors and to rate bucket for ancestors at levels * "level" and higher. It also handles possible change of mode resulting * from the update. Note that mode can also increase here (MAY_BORROW to * CAN_SEND) because we can use more precise clock that event queue here. * In such case we remove class from event queue first. */ static void htb_charge_class(struct htb_sched *q, struct htb_class *cl, int level, struct sk_buff *skb) { int bytes = qdisc_pkt_len(skb); enum htb_cmode old_mode; long diff; while (cl) { diff = psched_tdiff_bounded(q->now, cl->t_c, cl->mbuffer); if (cl->level >= level) { if (cl->level == level) cl->xstats.lends++; htb_accnt_tokens(cl, bytes, diff); } else { cl->xstats.borrows++; cl->tokens += diff; /* we moved t_c; update tokens */ } htb_accnt_ctokens(cl, bytes, diff); cl->t_c = q->now; old_mode = cl->cmode; diff = 0; htb_change_class_mode(q, cl, &diff); if (old_mode != cl->cmode) { if (old_mode != HTB_CAN_SEND) htb_safe_rb_erase(&cl->pq_node, q->wait_pq + cl->level); if (cl->cmode != HTB_CAN_SEND) htb_add_to_wait_tree(q, cl, diff); } /* update basic stats except for leaves which are already updated */ if (cl->level) bstats_update(&cl->bstats, skb); cl = cl->parent; } } /** * htb_do_events - make mode changes to classes at the level * * Scans event queue for pending events and applies them. Returns time of * next pending event (0 for no event in pq, q->now for too many events). * Note: Applied are events whose have cl->pq_key <= q->now. */ static psched_time_t htb_do_events(struct htb_sched *q, int level, unsigned long start) { /* don't run for longer than 2 jiffies; 2 is used instead of * 1 to simplify things when jiffy is going to be incremented * too soon */ unsigned long stop_at = start + 2; while (time_before(jiffies, stop_at)) { struct htb_class *cl; long diff; struct rb_node *p = rb_first(&q->wait_pq[level]); if (!p) return 0; cl = rb_entry(p, struct htb_class, pq_node); if (cl->pq_key > q->now) return cl->pq_key; htb_safe_rb_erase(p, q->wait_pq + level); diff = psched_tdiff_bounded(q->now, cl->t_c, cl->mbuffer); htb_change_class_mode(q, cl, &diff); if (cl->cmode != HTB_CAN_SEND) htb_add_to_wait_tree(q, cl, diff); } /* too much load - let's continue after a break for scheduling */ if (!(q->warned & HTB_WARN_TOOMANYEVENTS)) { pr_warning("htb: too many events!\n"); q->warned |= HTB_WARN_TOOMANYEVENTS; } return q->now; } /* Returns class->node+prio from id-tree where classe's id is >= id. NULL * is no such one exists. */ static struct rb_node *htb_id_find_next_upper(int prio, struct rb_node *n, u32 id) { struct rb_node *r = NULL; while (n) { struct htb_class *cl = rb_entry(n, struct htb_class, node[prio]); if (id > cl->common.classid) { n = n->rb_right; } else if (id < cl->common.classid) { r = n; n = n->rb_left; } else { return n; } } return r; } /** * htb_lookup_leaf - returns next leaf class in DRR order * * Find leaf where current feed pointers points to. */ static struct htb_class *htb_lookup_leaf(struct rb_root *tree, int prio, struct rb_node **pptr, u32 * pid) { int i; struct { struct rb_node *root; struct rb_node **pptr; u32 *pid; } stk[TC_HTB_MAXDEPTH], *sp = stk; BUG_ON(!tree->rb_node); sp->root = tree->rb_node; sp->pptr = pptr; sp->pid = pid; for (i = 0; i < 65535; i++) { if (!*sp->pptr && *sp->pid) { /* ptr was invalidated but id is valid - try to recover * the original or next ptr */ *sp->pptr = htb_id_find_next_upper(prio, sp->root, *sp->pid); } *sp->pid = 0; /* ptr is valid now so that remove this hint as it * can become out of date quickly */ if (!*sp->pptr) { /* we are at right end; rewind & go up */ *sp->pptr = sp->root; while ((*sp->pptr)->rb_left) *sp->pptr = (*sp->pptr)->rb_left; if (sp > stk) { sp--; if (!*sp->pptr) { WARN_ON(1); return NULL; } htb_next_rb_node(sp->pptr); } } else { struct htb_class *cl; cl = rb_entry(*sp->pptr, struct htb_class, node[prio]); if (!cl->level) return cl; (++sp)->root = cl->un.inner.feed[prio].rb_node; sp->pptr = cl->un.inner.ptr + prio; sp->pid = cl->un.inner.last_ptr_id + prio; } } WARN_ON(1); return NULL; } /* dequeues packet at given priority and level; call only if * you are sure that there is active class at prio/level */ static struct sk_buff *htb_dequeue_tree(struct htb_sched *q, int prio, int level) { struct sk_buff *skb = NULL; struct htb_class *cl, *start; /* look initial class up in the row */ start = cl = htb_lookup_leaf(q->row[level] + prio, prio, q->ptr[level] + prio, q->last_ptr_id[level] + prio); do { next: if (unlikely(!cl)) return NULL; /* class can be empty - it is unlikely but can be true if leaf * qdisc drops packets in enqueue routine or if someone used * graft operation on the leaf since last dequeue; * simply deactivate and skip such class */ if (unlikely(cl->un.leaf.q->q.qlen == 0)) { struct htb_class *next; htb_deactivate(q, cl); /* row/level might become empty */ if ((q->row_mask[level] & (1 << prio)) == 0) return NULL; next = htb_lookup_leaf(q->row[level] + prio, prio, q->ptr[level] + prio, q->last_ptr_id[level] + prio); if (cl == start) /* fix start if we just deleted it */ start = next; cl = next; goto next; } skb = cl->un.leaf.q->dequeue(cl->un.leaf.q); if (likely(skb != NULL)) break; qdisc_warn_nonwc("htb", cl->un.leaf.q); htb_next_rb_node((level ? cl->parent->un.inner.ptr : q-> ptr[0]) + prio); cl = htb_lookup_leaf(q->row[level] + prio, prio, q->ptr[level] + prio, q->last_ptr_id[level] + prio); } while (cl != start); if (likely(skb != NULL)) { cl->un.leaf.deficit[level] -= qdisc_pkt_len(skb); if (cl->un.leaf.deficit[level] < 0) { cl->un.leaf.deficit[level] += cl->quantum; htb_next_rb_node((level ? cl->parent->un.inner.ptr : q-> ptr[0]) + prio); } /* this used to be after charge_class but this constelation * gives us slightly better performance */ if (!cl->un.leaf.q->q.qlen) htb_deactivate(q, cl); htb_charge_class(q, cl, level, skb); } return skb; } static struct sk_buff *htb_dequeue(struct Qdisc *sch) { struct sk_buff *skb; struct htb_sched *q = qdisc_priv(sch); int level; psched_time_t next_event; unsigned long start_at; u32 r, i; struct sk_buff *pkt; /* try to dequeue direct packets as high prio (!) to minimize cpu work */ skb = __skb_dequeue(&q->direct_queue); if (skb != NULL) { ok: qdisc_bstats_update(sch, skb); qdisc_unthrottled(sch); sch->q.qlen--; #if OFBUF if(q->ofbuf_queued > 0) { i = 0; r = net_random() % q->ofbuf_queued; // enqueue the rth packet and drop the rest while((pkt = __skb_dequeue(&q->ofbuf)) != NULL) { if(i == r) { // the chosen one htb_enqueue(pkt, sch); } else { kfree_skb(pkt); } i++; } q->ofbuf_queued = 0; } #endif return skb; } if (!sch->q.qlen) goto fin; q->now = psched_get_time(); start_at = jiffies; next_event = q->now + 5 * PSCHED_TICKS_PER_SEC; for (level = 0; level < TC_HTB_MAXDEPTH; level++) { /* common case optimization - skip event handler quickly */ int m; psched_time_t event; if (q->now >= q->near_ev_cache[level]) { event = htb_do_events(q, level, start_at); if (!event) event = q->now + PSCHED_TICKS_PER_SEC; q->near_ev_cache[level] = event; } else event = q->near_ev_cache[level]; if (next_event > event) next_event = event; m = ~q->row_mask[level]; while (m != (int)(-1)) { int prio = ffz(m); m |= 1 << prio; skb = htb_dequeue_tree(q, prio, level); if (likely(skb != NULL)) goto ok; } } sch->qstats.overlimits++; if (likely(next_event > q->now)) qdisc_watchdog_schedule(&q->watchdog, next_event); else schedule_work(&q->work); fin: return skb; } /* try to drop from each class (by prio) until one succeed */ static unsigned int htb_drop(struct Qdisc *sch) { struct htb_sched *q = qdisc_priv(sch); int prio; for (prio = TC_HTB_NUMPRIO - 1; prio >= 0; prio--) { struct list_head *p; list_for_each(p, q->drops + prio) { struct htb_class *cl = list_entry(p, struct htb_class, un.leaf.drop_list); unsigned int len; if (cl->un.leaf.q->ops->drop && (len = cl->un.leaf.q->ops->drop(cl->un.leaf.q))) { sch->q.qlen--; if (!cl->un.leaf.q->q.qlen) htb_deactivate(q, cl); return len; } } } return 0; } /* reset all classes */ /* always caled under BH & queue lock */ static void htb_reset(struct Qdisc *sch) { struct htb_sched *q = qdisc_priv(sch); struct htb_class *cl; struct hlist_node *n; unsigned int i; for (i = 0; i < q->clhash.hashsize; i++) { hlist_for_each_entry(cl, n, &q->clhash.hash[i], common.hnode) { if (cl->level) memset(&cl->un.inner, 0, sizeof(cl->un.inner)); else { if (cl->un.leaf.q) qdisc_reset(cl->un.leaf.q); INIT_LIST_HEAD(&cl->un.leaf.drop_list); } cl->prio_activity = 0; cl->cmode = HTB_CAN_SEND; } } qdisc_watchdog_cancel(&q->watchdog); __skb_queue_purge(&q->direct_queue); sch->q.qlen = 0; #if OFBUF __skb_queue_purge(&q->ofbuf); q->ofbuf_queued = 0; #endif memset(q->row, 0, sizeof(q->row)); memset(q->row_mask, 0, sizeof(q->row_mask)); memset(q->wait_pq, 0, sizeof(q->wait_pq)); memset(q->ptr, 0, sizeof(q->ptr)); for (i = 0; i < TC_HTB_NUMPRIO; i++) INIT_LIST_HEAD(q->drops + i); } static const struct nla_policy htb_policy[TCA_HTB_MAX + 1] = { [TCA_HTB_PARMS] = { .len = sizeof(struct tc_htb_opt) }, [TCA_HTB_INIT] = { .len = sizeof(struct tc_htb_glob) }, [TCA_HTB_CTAB] = { .type = NLA_BINARY, .len = TC_RTAB_SIZE }, [TCA_HTB_RTAB] = { .type = NLA_BINARY, .len = TC_RTAB_SIZE }, }; static void htb_work_func(struct work_struct *work) { struct htb_sched *q = container_of(work, struct htb_sched, work); struct Qdisc *sch = q->watchdog.qdisc; __netif_schedule(qdisc_root(sch)); } static int htb_init(struct Qdisc *sch, struct nlattr *opt) { struct htb_sched *q = qdisc_priv(sch); struct nlattr *tb[TCA_HTB_INIT + 1]; struct tc_htb_glob *gopt; int err; int i; if (!opt) return -EINVAL; err = nla_parse_nested(tb, TCA_HTB_INIT, opt, htb_policy); if (err < 0) return err; if (tb[TCA_HTB_INIT] == NULL) { pr_err("HTB: hey probably you have bad tc tool ?\n"); return -EINVAL; } gopt = nla_data(tb[TCA_HTB_INIT]); if (gopt->version != HTB_VER >> 16) { pr_err("HTB: need tc/htb version %d (minor is %d), you have %d\n", HTB_VER >> 16, HTB_VER & 0xffff, gopt->version); return -EINVAL; } err = qdisc_class_hash_init(&q->clhash); if (err < 0) return err; for (i = 0; i < TC_HTB_NUMPRIO; i++) INIT_LIST_HEAD(q->drops + i); qdisc_watchdog_init(&q->watchdog, sch); INIT_WORK(&q->work, htb_work_func); skb_queue_head_init(&q->direct_queue); #if OFBUF skb_queue_head_init(&q->ofbuf); q->ofbuf_queued = 0; #endif q->direct_qlen = qdisc_dev(sch)->tx_queue_len; if (q->direct_qlen < 2) /* some devices have zero tx_queue_len */ q->direct_qlen = 2; if ((q->rate2quantum = gopt->rate2quantum) < 1) q->rate2quantum = 1; q->defcls = gopt->defcls; return 0; } static int htb_dump(struct Qdisc *sch, struct sk_buff *skb) { spinlock_t *root_lock = qdisc_root_sleeping_lock(sch); struct htb_sched *q = qdisc_priv(sch); struct nlattr *nest; struct tc_htb_glob gopt; spin_lock_bh(root_lock); gopt.direct_pkts = q->direct_pkts; gopt.version = HTB_VER; gopt.rate2quantum = q->rate2quantum; gopt.defcls = q->defcls; gopt.debug = 0; nest = nla_nest_start(skb, TCA_OPTIONS); if (nest == NULL) goto nla_put_failure; NLA_PUT(skb, TCA_HTB_INIT, sizeof(gopt), &gopt); nla_nest_end(skb, nest); spin_unlock_bh(root_lock); return skb->len; nla_put_failure: spin_unlock_bh(root_lock); nla_nest_cancel(skb, nest); return -1; } static int htb_dump_class(struct Qdisc *sch, unsigned long arg, struct sk_buff *skb, struct tcmsg *tcm) { struct htb_class *cl = (struct htb_class *)arg; spinlock_t *root_lock = qdisc_root_sleeping_lock(sch); struct nlattr *nest; struct tc_htb_opt opt; spin_lock_bh(root_lock); tcm->tcm_parent = cl->parent ? cl->parent->common.classid : TC_H_ROOT; tcm->tcm_handle = cl->common.classid; if (!cl->level && cl->un.leaf.q) tcm->tcm_info = cl->un.leaf.q->handle; nest = nla_nest_start(skb, TCA_OPTIONS); if (nest == NULL) goto nla_put_failure; memset(&opt, 0, sizeof(opt)); opt.rate = cl->rate->rate; opt.buffer = cl->buffer; opt.ceil = cl->ceil->rate; opt.cbuffer = cl->cbuffer; opt.quantum = cl->quantum; opt.prio = cl->prio; opt.level = cl->level; NLA_PUT(skb, TCA_HTB_PARMS, sizeof(opt), &opt); nla_nest_end(skb, nest); spin_unlock_bh(root_lock); return skb->len; nla_put_failure: spin_unlock_bh(root_lock); nla_nest_cancel(skb, nest); return -1; } static int htb_dump_class_stats(struct Qdisc *sch, unsigned long arg, struct gnet_dump *d) { struct htb_class *cl = (struct htb_class *)arg; if (!cl->level && cl->un.leaf.q) cl->qstats.qlen = cl->un.leaf.q->q.qlen; cl->xstats.tokens = cl->tokens; cl->xstats.ctokens = cl->ctokens; if (gnet_stats_copy_basic(d, &cl->bstats) < 0 || gnet_stats_copy_rate_est(d, NULL, &cl->rate_est) < 0 || gnet_stats_copy_queue(d, &cl->qstats) < 0) return -1; return gnet_stats_copy_app(d, &cl->xstats, sizeof(cl->xstats)); } static int htb_graft(struct Qdisc *sch, unsigned long arg, struct Qdisc *new, struct Qdisc **old) { struct htb_class *cl = (struct htb_class *)arg; if (cl->level) return -EINVAL; if (new == NULL && (new = qdisc_create_dflt(sch->dev_queue, &pfifo_qdisc_ops, cl->common.classid)) == NULL) return -ENOBUFS; sch_tree_lock(sch); *old = cl->un.leaf.q; cl->un.leaf.q = new; if (*old != NULL) { qdisc_tree_decrease_qlen(*old, (*old)->q.qlen); qdisc_reset(*old); } sch_tree_unlock(sch); return 0; } static struct Qdisc *htb_leaf(struct Qdisc *sch, unsigned long arg) { struct htb_class *cl = (struct htb_class *)arg; return !cl->level ? cl->un.leaf.q : NULL; } static void htb_qlen_notify(struct Qdisc *sch, unsigned long arg) { struct htb_class *cl = (struct htb_class *)arg; if (cl->un.leaf.q->q.qlen == 0) htb_deactivate(qdisc_priv(sch), cl); } static unsigned long htb_get(struct Qdisc *sch, u32 classid) { struct htb_class *cl = htb_find(classid, sch); if (cl) cl->refcnt++; return (unsigned long)cl; } static inline int htb_parent_last_child(struct htb_class *cl) { if (!cl->parent) /* the root class */ return 0; if (cl->parent->children > 1) /* not the last child */ return 0; return 1; } static void htb_parent_to_leaf(struct htb_sched *q, struct htb_class *cl, struct Qdisc *new_q) { struct htb_class *parent = cl->parent; WARN_ON(cl->level || !cl->un.leaf.q || cl->prio_activity); if (parent->cmode != HTB_CAN_SEND) htb_safe_rb_erase(&parent->pq_node, q->wait_pq + parent->level); parent->level = 0; memset(&parent->un.inner, 0, sizeof(parent->un.inner)); INIT_LIST_HEAD(&parent->un.leaf.drop_list); parent->un.leaf.q = new_q ? new_q : &noop_qdisc; parent->tokens = parent->buffer; parent->ctokens = parent->cbuffer; parent->t_c = psched_get_time(); parent->cmode = HTB_CAN_SEND; } static void htb_destroy_class(struct Qdisc *sch, struct htb_class *cl) { if (!cl->level) { WARN_ON(!cl->un.leaf.q); qdisc_destroy(cl->un.leaf.q); } gen_kill_estimator(&cl->bstats, &cl->rate_est); qdisc_put_rtab(cl->rate); qdisc_put_rtab(cl->ceil); tcf_destroy_chain(&cl->filter_list); kfree(cl); } static void htb_destroy(struct Qdisc *sch) { struct htb_sched *q = qdisc_priv(sch); struct hlist_node *n, *next; struct htb_class *cl; unsigned int i; cancel_work_sync(&q->work); qdisc_watchdog_cancel(&q->watchdog); /* This line used to be after htb_destroy_class call below * and surprisingly it worked in 2.4. But it must precede it * because filter need its target class alive to be able to call * unbind_filter on it (without Oops). */ tcf_destroy_chain(&q->filter_list); for (i = 0; i < q->clhash.hashsize; i++) { hlist_for_each_entry(cl, n, &q->clhash.hash[i], common.hnode) tcf_destroy_chain(&cl->filter_list); } for (i = 0; i < q->clhash.hashsize; i++) { hlist_for_each_entry_safe(cl, n, next, &q->clhash.hash[i], common.hnode) htb_destroy_class(sch, cl); } qdisc_class_hash_destroy(&q->clhash); __skb_queue_purge(&q->direct_queue); #if OFBUF __skb_queue_purge(&q->ofbuf); q->ofbuf_queued = 0; #endif } static int htb_delete(struct Qdisc *sch, unsigned long arg) { struct htb_sched *q = qdisc_priv(sch); struct htb_class *cl = (struct htb_class *)arg; unsigned int qlen; struct Qdisc *new_q = NULL; int last_child = 0; // TODO: why don't allow to delete subtree ? references ? does // tc subsys quarantee us that in htb_destroy it holds no class // refs so that we can remove children safely there ? if (cl->children || cl->filter_cnt) return -EBUSY; if (!cl->level && htb_parent_last_child(cl)) { new_q = qdisc_create_dflt(sch->dev_queue, &pfifo_qdisc_ops, cl->parent->common.classid); last_child = 1; } sch_tree_lock(sch); if (!cl->level) { qlen = cl->un.leaf.q->q.qlen; qdisc_reset(cl->un.leaf.q); qdisc_tree_decrease_qlen(cl->un.leaf.q, qlen); } /* delete from hash and active; remainder in destroy_class */ qdisc_class_hash_remove(&q->clhash, &cl->common); if (cl->parent) cl->parent->children--; if (cl->prio_activity) htb_deactivate(q, cl); if (cl->cmode != HTB_CAN_SEND) htb_safe_rb_erase(&cl->pq_node, q->wait_pq + cl->level); if (last_child) htb_parent_to_leaf(q, cl, new_q); BUG_ON(--cl->refcnt == 0); /* * This shouldn't happen: we "hold" one cops->get() when called * from tc_ctl_tclass; the destroy method is done from cops->put(). */ sch_tree_unlock(sch); return 0; } static void htb_put(struct Qdisc *sch, unsigned long arg) { struct htb_class *cl = (struct htb_class *)arg; if (--cl->refcnt == 0) htb_destroy_class(sch, cl); } static int htb_change_class(struct Qdisc *sch, u32 classid, u32 parentid, struct nlattr **tca, unsigned long *arg) { int err = -EINVAL; struct htb_sched *q = qdisc_priv(sch); struct htb_class *cl = (struct htb_class *)*arg, *parent; struct nlattr *opt = tca[TCA_OPTIONS]; struct qdisc_rate_table *rtab = NULL, *ctab = NULL; struct nlattr *tb[__TCA_HTB_MAX]; struct tc_htb_opt *hopt; /* extract all subattrs from opt attr */ if (!opt) goto failure; err = nla_parse_nested(tb, TCA_HTB_MAX, opt, htb_policy); if (err < 0) goto failure; err = -EINVAL; if (tb[TCA_HTB_PARMS] == NULL) goto failure; parent = parentid == TC_H_ROOT ? NULL : htb_find(parentid, sch); hopt = nla_data(tb[TCA_HTB_PARMS]); rtab = qdisc_get_rtab(&hopt->rate, tb[TCA_HTB_RTAB]); ctab = qdisc_get_rtab(&hopt->ceil, tb[TCA_HTB_CTAB]); if (!rtab || !ctab) goto failure; if (!cl) { /* new class */ struct Qdisc *new_q; int prio; struct { struct nlattr nla; struct gnet_estimator opt; } est = { .nla = { .nla_len = nla_attr_size(sizeof(est.opt)), .nla_type = TCA_RATE, }, .opt = { /* 4s interval, 16s averaging constant */ .interval = 2, .ewma_log = 2, }, }; /* check for valid classid */ if (!classid || TC_H_MAJ(classid ^ sch->handle) || htb_find(classid, sch)) goto failure; /* check maximal depth */ if (parent && parent->parent && parent->parent->level < 2) { pr_err("htb: tree is too deep\n"); goto failure; } err = -ENOBUFS; cl = kzalloc(sizeof(*cl), GFP_KERNEL); if (!cl) goto failure; err = gen_new_estimator(&cl->bstats, &cl->rate_est, qdisc_root_sleeping_lock(sch), tca[TCA_RATE] ? : &est.nla); if (err) { kfree(cl); goto failure; } cl->refcnt = 1; cl->children = 0; INIT_LIST_HEAD(&cl->un.leaf.drop_list); RB_CLEAR_NODE(&cl->pq_node); for (prio = 0; prio < TC_HTB_NUMPRIO; prio++) RB_CLEAR_NODE(&cl->node[prio]); /* create leaf qdisc early because it uses kmalloc(GFP_KERNEL) * so that can't be used inside of sch_tree_lock * -- thanks to Karlis Peisenieks */ new_q = qdisc_create_dflt(sch->dev_queue, &pfifo_qdisc_ops, classid); sch_tree_lock(sch); if (parent && !parent->level) { unsigned int qlen = parent->un.leaf.q->q.qlen; /* turn parent into inner node */ qdisc_reset(parent->un.leaf.q); qdisc_tree_decrease_qlen(parent->un.leaf.q, qlen); qdisc_destroy(parent->un.leaf.q); if (parent->prio_activity) htb_deactivate(q, parent); /* remove from evt list because of level change */ if (parent->cmode != HTB_CAN_SEND) { htb_safe_rb_erase(&parent->pq_node, q->wait_pq); parent->cmode = HTB_CAN_SEND; } parent->level = (parent->parent ? parent->parent->level : TC_HTB_MAXDEPTH) - 1; memset(&parent->un.inner, 0, sizeof(parent->un.inner)); } /* leaf (we) needs elementary qdisc */ cl->un.leaf.q = new_q ? new_q : &noop_qdisc; cl->common.classid = classid; cl->parent = parent; /* set class to be in HTB_CAN_SEND state */ cl->tokens = hopt->buffer; cl->ctokens = hopt->cbuffer; cl->mbuffer = 60 * PSCHED_TICKS_PER_SEC; /* 1min */ cl->t_c = psched_get_time(); cl->cmode = HTB_CAN_SEND; /* attach to the hash list and parent's family */ qdisc_class_hash_insert(&q->clhash, &cl->common); if (parent) parent->children++; } else { if (tca[TCA_RATE]) { err = gen_replace_estimator(&cl->bstats, &cl->rate_est, qdisc_root_sleeping_lock(sch), tca[TCA_RATE]); if (err) return err; } sch_tree_lock(sch); } /* it used to be a nasty bug here, we have to check that node * is really leaf before changing cl->un.leaf ! */ if (!cl->level) { cl->quantum = rtab->rate.rate / q->rate2quantum; if (!hopt->quantum && cl->quantum < 1000) { pr_warning( "HTB: quantum of class %X is small. Consider r2q change.\n", cl->common.classid); cl->quantum = 1000; } if (!hopt->quantum && cl->quantum > 200000) { pr_warning( "HTB: quantum of class %X is big. Consider r2q change.\n", cl->common.classid); cl->quantum = 200000; } if (hopt->quantum) cl->quantum = hopt->quantum; if ((cl->prio = hopt->prio) >= TC_HTB_NUMPRIO) cl->prio = TC_HTB_NUMPRIO - 1; } cl->buffer = hopt->buffer; cl->cbuffer = hopt->cbuffer; if (cl->rate) qdisc_put_rtab(cl->rate); cl->rate = rtab; if (cl->ceil) qdisc_put_rtab(cl->ceil); cl->ceil = ctab; sch_tree_unlock(sch); qdisc_class_hash_grow(sch, &q->clhash); *arg = (unsigned long)cl; return 0; failure: if (rtab) qdisc_put_rtab(rtab); if (ctab) qdisc_put_rtab(ctab); return err; } static struct tcf_proto **htb_find_tcf(struct Qdisc *sch, unsigned long arg) { struct htb_sched *q = qdisc_priv(sch); struct htb_class *cl = (struct htb_class *)arg; struct tcf_proto **fl = cl ? &cl->filter_list : &q->filter_list; return fl; } static unsigned long htb_bind_filter(struct Qdisc *sch, unsigned long parent, u32 classid) { struct htb_class *cl = htb_find(classid, sch); /*if (cl && !cl->level) return 0; * The line above used to be there to prevent attaching filters to * leaves. But at least tc_index filter uses this just to get class * for other reasons so that we have to allow for it. * ---- * 19.6.2002 As Werner explained it is ok - bind filter is just * another way to "lock" the class - unlike "get" this lock can * be broken by class during destroy IIUC. */ if (cl) cl->filter_cnt++; return (unsigned long)cl; } static void htb_unbind_filter(struct Qdisc *sch, unsigned long arg) { struct htb_class *cl = (struct htb_class *)arg; if (cl) cl->filter_cnt--; } static void htb_walk(struct Qdisc *sch, struct qdisc_walker *arg) { struct htb_sched *q = qdisc_priv(sch); struct htb_class *cl; struct hlist_node *n; unsigned int i; if (arg->stop) return; for (i = 0; i < q->clhash.hashsize; i++) { hlist_for_each_entry(cl, n, &q->clhash.hash[i], common.hnode) { if (arg->count < arg->skip) { arg->count++; continue; } if (arg->fn(sch, (unsigned long)cl, arg) < 0) { arg->stop = 1; return; } arg->count++; } } } static const struct Qdisc_class_ops htb_class_ops = { .graft = htb_graft, .leaf = htb_leaf, .qlen_notify = htb_qlen_notify, .get = htb_get, .put = htb_put, .change = htb_change_class, .delete = htb_delete, .walk = htb_walk, .tcf_chain = htb_find_tcf, .bind_tcf = htb_bind_filter, .unbind_tcf = htb_unbind_filter, .dump = htb_dump_class, .dump_stats = htb_dump_class_stats, }; static struct Qdisc_ops htb_qdisc_ops __read_mostly = { .cl_ops = &htb_class_ops, .id = "htb", .priv_size = sizeof(struct htb_sched), .enqueue = htb_enqueue, .dequeue = htb_dequeue, .peek = qdisc_peek_dequeued, .drop = htb_drop, .init = htb_init, .reset = htb_reset, .destroy = htb_destroy, .dump = htb_dump, .owner = THIS_MODULE, }; static int __init htb_module_init(void) { return register_qdisc(&htb_qdisc_ops); } static void __exit htb_module_exit(void) { unregister_qdisc(&htb_qdisc_ops); } module_init(htb_module_init) module_exit(htb_module_exit) MODULE_LICENSE("GPL"); mininet-2.2.2/util/sysctl_addon000066400000000000000000000007731306431124000165470ustar00rootroot00000000000000# Mininet: Increase open file limit fs.file-max = 100000 # Mininet: increase network buffer space net.core.wmem_max = 16777216 net.core.rmem_max = 16777216 net.ipv4.tcp_rmem = 10240 87380 16777216 net.ipv4.tcp_rmem = 10240 87380 16777216 net.core.netdev_max_backlog = 5000 # Mininet: increase arp cache size net.ipv4.neigh.default.gc_thresh1 = 4096 net.ipv4.neigh.default.gc_thresh2 = 8192 net.ipv4.neigh.default.gc_thresh3 = 16384 # Mininet: increase routing table size net.ipv4.route.max_size=32768 mininet-2.2.2/util/unpep8000077500000000000000000000154341306431124000153030ustar00rootroot00000000000000#!/usr/bin/python """ Translate from PEP8 Python style to Mininet (i.e. Arista-like) Python style usage: unpep8 < old.py > new.py - Reinstates CapWords for methods and instance variables - Gets rid of triple single quotes - Eliminates triple quotes on single lines - Inserts extra spaces to improve readability - Fixes Doxygen (or doxypy) ugliness Does the following translations: ClassName.method_name(foo = bar) -> ClassName.methodName( foo=bar ) Triple-single-quotes -> triple-double-quotes @param foo description -> foo: description @return description -> returns: description @author me -> author: me @todo(me) -> TODO(me) Bugs/Limitations: - Hack to restore strings is ugly - Multiline strings get mangled - Comments are mangled (which is arguably the "right thing" to do, except that, for example, the left hand sides of the above would get translated!) - Doesn't eliminate unnecessary backslashes - Has no opinion on tab size - complicated indented docstrings get flattened - We don't (yet) have a filter to generate Doxygen/Doxypy - Currently leaves indents on blank comment lines - May lead to namespace collisions (e.g. some_thing and someThing) Bob Lantz, rlantz@cs.stanford.edu 1/24/2010 """ import re, sys def fixUnderscoreTriplet( match ): "Translate a matched triplet of the form a_b to aB." triplet = match.group() return triplet[ :-2 ] + triplet[ -1 ].capitalize() def reinstateCapWords( text ): underscoreTriplet = re.compile( r'[A-Za-z0-9]_[A-Za-z0-9]' ) return underscoreTriplet.sub( fixUnderscoreTriplet, text ) def replaceTripleApostrophes( text ): "Replace triple apostrophes with triple quotes." return text.replace( "'''", '"""') def simplifyTripleQuotes( text ): "Fix single-line doc strings." r = re.compile( r'"""([^\"\n]+)"""' ) return r.sub( r'"\1"', text ) def insertExtraSpaces( text ): "Insert extra spaces inside of parentheses and brackets/curly braces." lparen = re.compile( r'\((?![\s\)])' ) text = lparen.sub( r'( ', text ) rparen = re.compile( r'([^\s\(])(?=\))' ) text = rparen.sub( r'\1 ', text) # brackets lbrack = re.compile( r'\[(?![\s\]])' ) text = lbrack.sub( r'[ ', text ) rbrack = re.compile( r'([^\s\[])(?=\])' ) text = rbrack.sub( r'\1 ', text) # curly braces lcurly = re.compile( r'\{(?![\s\}])' ) text = lcurly.sub( r'{ ', text ) rcurly = re.compile( r'([^\s\{])(?=\})' ) text = rcurly.sub( r'\1 ', text) return text def fixDoxygen( text ): """Translate @param foo to foo:, @return bar to returns: bar, and @author me to author: me""" param = re.compile( r'@param (\w+)' ) text = param.sub( r'\1:', text ) returns = re.compile( r'@return' ) text = returns.sub( r'returns:', text ) author = re.compile( r'@author' ) text = author.sub( r'author:', text) # @todo -> TODO text = text.replace( '@todo', 'TODO' ) return text def removeCommentFirstBlankLine( text ): "Remove annoying blank lines after first line in comments." line = re.compile( r'("""[^\n]*\n)\s*\n', re.MULTILINE ) return line.sub( r'\1', text ) def fixArgs( match, kwarg = re.compile( r'(\w+) = ' ) ): "Replace foo = bar with foo=bar." return kwarg.sub( r'\1=', match.group() ) def fixKeywords( text ): "Change keyword argumentsfrom foo = bar to foo=bar." args = re.compile( r'\(([^\)]+)\)', re.MULTILINE ) return args.sub( fixArgs, text ) # Unfortunately, Python doesn't natively support balanced or recursive # regular expressions. We could use PyParsing, but that opens another can # of worms. For now, we just have a cheap hack to restore strings, # so we don't end up accidentally mangling things like messages, search strings, # and regular expressions. def lineIter( text ): "Simple iterator over lines in text." for line in text.splitlines(): yield line def stringIter( strList ): "Yield strings in strList." for s in strList: yield s def restoreRegex( regex, old, new ): "Find regexes in old and restore them into new." oldStrs = regex.findall( old ) # Sanity check - count should be the same! newStrs = regex.findall( new ) assert len( oldStrs ) == len( newStrs ) # Replace newStrs with oldStrs siter = stringIter( oldStrs ) reps = lambda dummy: siter.next() return regex.sub( reps, new ) # This is a cheap hack, and it may not work 100%, since # it doesn't handle multiline strings. # However, it should be mostly harmless... def restoreStrings( oldText, newText ): "Restore strings from oldText into newText, returning result." oldLines, newLines = lineIter( oldText ), lineIter( newText ) quoteStrings = re.compile( r'("[^"]*")' ) tickStrings = re.compile( r"('[^']*')" ) result = '' # It would be nice if we could blast the whole file, but for # now it seems to work line-by-line for newLine in newLines: oldLine = oldLines.next() newLine = restoreRegex( quoteStrings, oldLine, newLine ) newLine = restoreRegex( tickStrings, oldLine, newLine ) result += newLine + '\n' return result # This might be slightly controversial, since it uses # three spaces to line up multiline comments. However, # I much prefer it. Limitations: if you have deeper # indents in comments, they will be eliminated. ;-( def fixComment( match, indentExp=re.compile( r'\n([ ]*)(?=[^/s])', re.MULTILINE ), trailingQuotes=re.compile( r'\s+"""' ) ): "Re-indent comment, and join trailing quotes." originalIndent = match.group( 1 ) comment = match.group( 2 ) indent = '\n' + originalIndent # Exception: leave unindented things unindented! if len( originalIndent ) is not 0: indent += ' ' comment = indentExp.sub( indent, comment ) return originalIndent + trailingQuotes.sub( '"""', comment ) def fixCommentIndents( text ): "Fix multiline comment indentation." comments = re.compile( r'^([ ]*)("""[^"]*""")$', re.MULTILINE ) return comments.sub( fixComment, text ) def removeBogusLinefeeds( text ): "Remove extra linefeeds at the end of single-line comments." bogusLfs = re.compile( r'"([^"\n]*)\n"', re.MULTILINE ) return bogusLfs.sub( '"\1"', text) def convertFromPep8( program ): oldProgram = program # Program text transforms program = reinstateCapWords( program ) program = fixKeywords( program ) program = insertExtraSpaces( program ) # Undo string damage program = restoreStrings( oldProgram, program ) # Docstring transforms program = replaceTripleApostrophes( program ) program = simplifyTripleQuotes( program ) program = fixDoxygen( program ) program = fixCommentIndents( program ) program = removeBogusLinefeeds( program ) # Destructive transforms (these can delete lines) program = removeCommentFirstBlankLine( program ) return program if __name__ == '__main__': print convertFromPep8( sys.stdin.read() )mininet-2.2.2/util/versioncheck.py000077500000000000000000000012321306431124000171650ustar00rootroot00000000000000#!/usr/bin/python from subprocess import check_output as co from sys import exit # Actually run bin/mn rather than importing via python path version = 'Mininet ' + co( 'PYTHONPATH=. bin/mn --version', shell=True ) version = version.strip() # Find all Mininet path references lines = co( "egrep -or 'Mininet [0-9\.\+]+\w*' *", shell=True ) error = False for line in lines.split( '\n' ): if line and 'Binary' not in line: fname, fversion = line.split( ':' ) if version != fversion: print "%s: incorrect version '%s' (should be '%s')" % ( fname, fversion, version ) error = True if error: exit( 1 ) mininet-2.2.2/util/vm/000077500000000000000000000000001306431124000145515ustar00rootroot00000000000000mininet-2.2.2/util/vm/.bash_profile000066400000000000000000000010621306431124000172060ustar00rootroot00000000000000SSH_ENV="$HOME/.ssh/environment" function start_agent { echo "Initialising new SSH agent..." /usr/bin/ssh-agent | sed 's/^echo/#echo/' > "${SSH_ENV}" echo succeeded chmod 600 "${SSH_ENV}" . "${SSH_ENV}" > /dev/null /usr/bin/ssh-add; } # Source SSH settings, if applicable if [ -f "${SSH_ENV}" ]; then . "${SSH_ENV}" > /dev/null #ps ${SSH_AGENT_PID} doesn't work under cywgin ps -ef | grep ${SSH_AGENT_PID} | grep ssh-agent$ > /dev/null || { start_agent; } else start_agent; fi source ~/.bashrc mininet-2.2.2/util/vm/build.py000077500000000000000000001077271306431124000162430ustar00rootroot00000000000000#!/usr/bin/python """ build.py: build a Mininet VM Basic idea: prepare -> create base install image if it's missing - download iso if it's missing - install from iso onto image build -> create cow disk for new VM, based on base image -> boot it in qemu/kvm with text /serial console -> install Mininet test -> sudo mn --test pingall -> make test release -> shut down VM -> shrink-wrap VM -> upload to storage """ import os from os import stat, path from stat import ST_MODE, ST_SIZE from os.path import abspath from sys import exit, stdout, argv, modules import re from glob import glob from subprocess import check_output, call, Popen from tempfile import mkdtemp, NamedTemporaryFile from time import time, strftime, localtime import argparse from distutils.spawn import find_executable import inspect pexpect = None # For code check - imported dynamically # boot can be slooooow!!!! need to debug/optimize somehow TIMEOUT=600 # Some configuration options # Possibly change this to use the parsed arguments instead! LogToConsole = False # VM output to console rather than log file SaveQCOW2 = False # Save QCOW2 image rather than deleting it NoKVM = False # Don't use kvm and use emulation instead Branch = None # Branch to update and check out before testing Zip = False # Archive .ovf and .vmdk into a .zip file Forward = [] # VM port forwarding options (-redir) Chown = '' # Build directory owner VMImageDir = os.environ[ 'HOME' ] + '/vm-images' Prompt = '\$ ' # Shell prompt that pexpect will wait for isoURLs = { 'precise32server': 'http://mirrors.kernel.org/ubuntu-releases/12.04/' 'ubuntu-12.04.5-server-i386.iso', 'precise64server': 'http://mirrors.kernel.org/ubuntu-releases/12.04/' 'ubuntu-12.04.5-server-amd64.iso', 'trusty32server': 'http://mirrors.kernel.org/ubuntu-releases/14.04/' 'ubuntu-14.04.4-server-i386.iso', 'trusty64server': 'http://mirrors.kernel.org/ubuntu-releases/14.04/' 'ubuntu-14.04.4-server-amd64.iso', 'wily32server': 'http://mirrors.kernel.org/ubuntu-releases/15.10/' 'ubuntu-15.10-server-i386.iso', 'wily64server': 'http://mirrors.kernel.org/ubuntu-releases/15.10/' 'ubuntu-15.10-server-amd64.iso', 'xenial32server': 'http://mirrors.kernel.org/ubuntu-releases/16.04/' 'ubuntu-16.04.1-server-i386.iso', 'xenial64server': 'http://mirrors.kernel.org/ubuntu-releases/16.04/' 'ubuntu-16.04.1-server-amd64.iso', } def OSVersion( flavor ): "Return full OS version string for build flavor" urlbase = path.basename( isoURLs.get( flavor, 'unknown' ) ) return path.splitext( urlbase )[ 0 ] def OVFOSNameID( flavor ): "Return OVF-specified ( OS Name, ID ) for flavor" version = OSVersion( flavor ) arch = archFor( flavor ) if 'ubuntu' in version: map = { 'i386': ( 'Ubuntu', 93 ), 'x86_64': ( 'Ubuntu 64-bit', 94 ) } else: map = { 'i386': ( 'Linux', 36 ), 'x86_64': ( 'Linux 64-bit', 101 ) } osname, osid = map[ arch ] return osname, osid LogStartTime = time() LogFile = None def log( *args, **kwargs ): """Simple log function: log( message along with local and elapsed time cr: False/0 for no CR""" cr = kwargs.get( 'cr', True ) elapsed = time() - LogStartTime clocktime = strftime( '%H:%M:%S', localtime() ) msg = ' '.join( str( arg ) for arg in args ) output = '%s [ %.3f ] %s' % ( clocktime, elapsed, msg ) if cr: print output else: print output, # Optionally mirror to LogFile if type( LogFile ) is file: if cr: output += '\n' LogFile.write( output ) LogFile.flush() def run( cmd, **kwargs ): "Convenient interface to check_output" log( '-', cmd ) cmd = cmd.split() arg0 = cmd[ 0 ] if not find_executable( arg0 ): raise Exception( 'Cannot find executable "%s";' % arg0 + 'you might try %s --depend' % argv[ 0 ] ) return check_output( cmd, **kwargs ) def srun( cmd, **kwargs ): "Run + sudo" return run( 'sudo ' + cmd, **kwargs ) # BL: we should probably have a "checkDepend()" which # checks to make sure all dependencies are satisfied! def depend(): "Install package dependencies" log( '* Installing package dependencies' ) run( 'sudo apt-get -qy update' ) run( 'sudo apt-get -qy install' ' kvm cloud-utils genisoimage qemu-kvm qemu-utils' ' e2fsprogs curl' ' python-setuptools mtools zip' ) run( 'sudo easy_install pexpect' ) def popen( cmd ): "Convenient interface to popen" log( cmd ) cmd = cmd.split() return Popen( cmd ) def remove( fname ): "Remove a file, ignoring errors" try: os.remove( fname ) except OSError: pass def findiso( flavor ): "Find iso, fetching it if it's not there already" url = isoURLs[ flavor ] name = path.basename( url ) iso = path.join( VMImageDir, name ) if not path.exists( iso ) or ( stat( iso )[ ST_MODE ] & 0777 != 0444 ): log( '* Retrieving', url ) run( 'curl -C - -o %s %s' % ( iso, url ) ) # Make sure the file header/type is something reasonable like # 'ISO' or 'x86 boot sector', and not random html or text result = run( 'file ' + iso ) if 'ISO' not in result and 'boot' not in result: os.remove( iso ) raise Exception( 'findiso: could not download iso from ' + url ) # Write-protect iso, signaling it is complete log( '* Write-protecting iso', iso) os.chmod( iso, 0444 ) log( '* Using iso', iso ) return iso def attachNBD( cow, flags='' ): """Attempt to attach a COW disk image and return its nbd device flags: additional flags for qemu-nbd (e.g. -r for readonly)""" # qemu-nbd requires an absolute path cow = abspath( cow ) log( '* Checking for unused /dev/nbdX device ' ) for i in range ( 0, 63 ): nbd = '/dev/nbd%d' % i # Check whether someone's already messing with that device if call( [ 'pgrep', '-f', nbd ] ) == 0: continue srun( 'modprobe nbd max-part=64' ) srun( 'qemu-nbd %s -c %s %s' % ( flags, nbd, cow ) ) print return nbd raise Exception( "Error: could not find unused /dev/nbdX device" ) def detachNBD( nbd ): "Detatch an nbd device" srun( 'qemu-nbd -d ' + nbd ) def extractKernel( image, flavor, imageDir=VMImageDir ): "Extract kernel and initrd from base image" kernel = path.join( imageDir, flavor + '-vmlinuz' ) initrd = path.join( imageDir, flavor + '-initrd' ) if path.exists( kernel ) and ( stat( image )[ ST_MODE ] & 0777 ) == 0444: # If kernel is there, then initrd should also be there return kernel, initrd log( '* Extracting kernel to', kernel ) nbd = attachNBD( image, flags='-r' ) try: print( srun( 'partx ' + nbd ) ) except: log( 'Warning - partx failed with error' ) # Assume kernel is in partition 1/boot/vmlinuz*generic for now part = nbd + 'p1' mnt = mkdtemp() srun( 'mount -o ro,noload %s %s' % ( part, mnt ) ) kernsrc = glob( '%s/boot/vmlinuz*generic' % mnt )[ 0 ] initrdsrc = glob( '%s/boot/initrd*generic' % mnt )[ 0 ] srun( 'cp %s %s' % ( initrdsrc, initrd ) ) srun( 'chmod 0444 ' + initrd ) srun( 'cp %s %s' % ( kernsrc, kernel ) ) srun( 'chmod 0444 ' + kernel ) srun( 'umount ' + mnt ) run( 'rmdir ' + mnt ) detachNBD( nbd ) return kernel, initrd def findBaseImage( flavor, size='8G' ): "Return base VM image and kernel, creating them if needed" image = path.join( VMImageDir, flavor + '-base.qcow2' ) if path.exists( image ): # Detect race condition with multiple builds perms = stat( image )[ ST_MODE ] & 0777 if perms != 0444: raise Exception( 'Error - base image %s is writable.' % image + ' Are multiple builds running? if not,' ' remove %s and try again.' % image ) else: # We create VMImageDir here since we are called first run( 'mkdir -p %s' % VMImageDir ) iso = findiso( flavor ) log( '* Creating image file', image ) run( 'qemu-img create -f qcow2 %s %s' % ( image, size ) ) installUbuntu( iso, image ) # Write-protect image, also signaling it is complete log( '* Write-protecting image', image) os.chmod( image, 0444 ) kernel, initrd = extractKernel( image, flavor ) log( '* Using base image', image, 'and kernel', kernel ) return image, kernel, initrd # Kickstart and Preseed files for Ubuntu/Debian installer # # Comments: this is really clunky and painful. If Ubuntu # gets their act together and supports kickstart a bit better # then we can get rid of preseed and even use this as a # Fedora installer as well. # # Another annoying thing about Ubuntu is that it can't just # install a normal system from the iso - it has to download # junk from the internet, making this house of cards even # more precarious. KickstartText =""" #Generated by Kickstart Configurator #platform=x86 #System language lang en_US #Language modules to install langsupport en_US #System keyboard keyboard us #System mouse mouse #System timezone timezone America/Los_Angeles #Root password rootpw --disabled #Initial user user mininet --fullname "mininet" --password "mininet" #Use text mode install text #Install OS instead of upgrade install #Use CDROM installation media cdrom #System bootloader configuration bootloader --location=mbr #Clear the Master Boot Record zerombr yes #Partition clearing information clearpart --all --initlabel #Automatic partitioning autopart #System authorization information auth --useshadow --enablemd5 #Firewall configuration firewall --disabled #Do not configure the X Window System skipx """ # Tell the Ubuntu/Debian installer to stop asking stupid questions PreseedText = ( """ """ #d-i mirror/country string manual #d-i mirror/http/hostname string mirrors.kernel.org """ d-i mirror/http/directory string /ubuntu d-i mirror/http/proxy string d-i partman/confirm_write_new_label boolean true d-i partman/choose_partition select finish d-i partman/confirm boolean true d-i partman/confirm_nooverwrite boolean true d-i user-setup/allow-password-weak boolean true d-i finish-install/reboot_in_progress note d-i debian-installer/exit/poweroff boolean true """ ) def makeKickstartFloppy(): "Create and return kickstart floppy, kickstart, preseed" kickstart = 'ks.cfg' with open( kickstart, 'w' ) as f: f.write( KickstartText ) preseed = 'ks.preseed' with open( preseed, 'w' ) as f: f.write( PreseedText ) # Create floppy and copy files to it floppy = 'ksfloppy.img' run( 'qemu-img create %s 1440k' % floppy ) run( 'mkfs -t msdos ' + floppy ) run( 'mcopy -i %s %s ::/' % ( floppy, kickstart ) ) run( 'mcopy -i %s %s ::/' % ( floppy, preseed ) ) return floppy, kickstart, preseed def archFor( filepath ): "Guess architecture for file path" name = path.basename( filepath ) if 'amd64' in name or 'x86_64' in name: arch = 'x86_64' # Beware of version 64 of a 32-bit OS elif 'i386' in name or '32' in name or 'x86' in name: arch = 'i386' elif '64' in name: arch = 'x86_64' else: log( "Error: can't discern CPU for name", name ) exit( 1 ) return arch def installUbuntu( iso, image, logfilename='install.log', memory=1024 ): "Install Ubuntu from iso onto image" kvm = 'qemu-system-' + archFor( iso ) floppy, kickstart, preseed = makeKickstartFloppy() # Mount iso so we can use its kernel mnt = mkdtemp() srun( 'mount %s %s' % ( iso, mnt ) ) kernel = path.join( mnt, 'install/vmlinuz' ) initrd = path.join( mnt, 'install/initrd.gz' ) if NoKVM: accel = 'tcg' else: accel = 'kvm' try: run( 'kvm-ok' ) except: raise Exception( 'kvm-ok failed; try using --nokvm' ) cmd = [ 'sudo', kvm, '-machine', 'accel=%s' % accel, '-nographic', '-netdev', 'user,id=mnbuild', '-device', 'virtio-net,netdev=mnbuild', '-m', str( memory ), '-k', 'en-us', '-fda', floppy, '-drive', 'file=%s,if=virtio' % image, '-cdrom', iso, '-kernel', kernel, '-initrd', initrd, '-append', ' ks=floppy:/' + kickstart + ' preseed/file=floppy://' + preseed + ' console=ttyS0' ] ubuntuStart = time() log( '* INSTALLING UBUNTU FROM', iso, 'ONTO', image ) log( ' '.join( cmd ) ) log( '* logging to', abspath( logfilename ) ) params = {} if not LogToConsole: logfile = open( logfilename, 'w' ) params = { 'stdout': logfile, 'stderr': logfile } vm = Popen( cmd, **params ) log( '* Waiting for installation to complete') vm.wait() if not LogToConsole: logfile.close() elapsed = time() - ubuntuStart # Unmount iso and clean up srun( 'umount ' + mnt ) run( 'rmdir ' + mnt ) if vm.returncode != 0: raise Exception( 'Ubuntu installation returned error %d' % vm.returncode ) log( '* UBUNTU INSTALLATION COMPLETED FOR', image ) log( '* Ubuntu installation completed in %.2f seconds' % elapsed ) def boot( cow, kernel, initrd, logfile, memory=1024, cpuCores=1 ): """Boot qemu/kvm with a COW disk and local/user data store cow: COW disk path kernel: kernel path logfile: log file for pexpect object memory: memory size in MB cpuCores: number of CPU cores to use returns: pexpect object to qemu process""" # pexpect might not be installed until after depend() is called global pexpect if not pexpect: import pexpect class Spawn( pexpect.spawn ): "Subprocess is sudo, so we have to sudo kill it" def close( self, force=False ): srun( 'kill %d' % self.pid ) arch = archFor( kernel ) log( '* Detected kernel architecture', arch ) if NoKVM: accel = 'tcg' else: accel = 'kvm' cmd = [ 'sudo', 'qemu-system-' + arch, '-machine accel=%s' % accel, '-nographic', '-netdev user,id=mnbuild', '-device virtio-net,netdev=mnbuild', '-m %s' % memory, '-k en-us', '-kernel', kernel, '-initrd', initrd, '-drive file=%s,if=virtio' % cow, '-append "root=/dev/vda1 init=/sbin/init console=ttyS0" ' ] if Forward: cmd += sum( [ [ '-redir', f ] for f in Forward ], [] ) if cpuCores > 1: cmd += [ '-smp cores=%s' % cpuCores ] cmd = ' '.join( cmd ) log( '* BOOTING VM FROM', cow ) log( cmd ) vm = Spawn( cmd, timeout=TIMEOUT, logfile=logfile ) return vm def login( vm, user='mininet', password='mininet' ): "Log in to vm (pexpect object)" log( '* Waiting for login prompt' ) vm.expect( 'login: ' ) log( '* Logging in' ) vm.sendline( user ) log( '* Waiting for password prompt' ) vm.expect( 'Password: ' ) log( '* Sending password' ) vm.sendline( password ) log( '* Waiting for login...' ) def removeNtpd( vm, prompt=Prompt, ntpPackage='ntp' ): "Remove ntpd and set clock immediately" log( '* Removing ntpd' ) vm.sendline( 'sudo -n apt-get -qy remove ' + ntpPackage ) vm.expect( prompt ) # Try to make sure that it isn't still running vm.sendline( 'sudo -n pkill ntpd' ) vm.expect( prompt ) log( '* Getting seconds since epoch from this server' ) # Note r'date +%s' specifies a format for 'date', not python! seconds = int( run( r'date +%s' ) ) log( '* Setting VM clock' ) vm.sendline( 'sudo -n date -s @%d' % seconds ) def sanityTest( vm ): "Run Mininet sanity test (pingall) in vm" vm.sendline( 'sudo -n mn --test pingall' ) if vm.expect( [ ' 0% dropped', pexpect.TIMEOUT ], timeout=45 ) == 0: log( '* Sanity check OK' ) else: log( '* Sanity check FAILED' ) log( '* Sanity check output:' ) log( vm.before ) def coreTest( vm, prompt=Prompt ): "Run core tests (make test) in VM" log( '* Making sure cgroups are mounted' ) vm.sendline( 'sudo -n service cgroup-lite restart' ) vm.expect( prompt ) vm.sendline( 'sudo -n cgroups-mount' ) vm.expect( prompt ) log( '* Running make test' ) vm.sendline( 'cd ~/mininet; sudo make test' ) # We should change "make test" to report the number of # successful and failed tests. For now, we have to # know the time for each test, which means that this # script will have to change as we add more tests. for test in range( 0, 2 ): if vm.expect( [ 'OK.*\r\n', 'FAILED.*\r\n', pexpect.TIMEOUT ], timeout=180 ) == 0: log( '* Test', test, 'OK' ) else: log( '* Test', test, 'FAILED' ) log( '* Test', test, 'output:' ) log( vm.before ) def installPexpect( vm, prompt=Prompt ): "install pexpect" vm.sendline( 'sudo -n apt-get -qy install python-pexpect' ) vm.expect( prompt ) def noneTest( vm, prompt=Prompt ): "This test does nothing" installPexpect( vm, prompt ) vm.sendline( 'echo' ) def examplesquickTest( vm, prompt=Prompt ): "Quick test of mininet examples" installPexpect( vm, prompt ) vm.sendline( 'sudo -n python ~/mininet/examples/test/runner.py -v -quick' ) def examplesfullTest( vm, prompt=Prompt ): "Full (slow) test of mininet examples" installPexpect( vm, prompt ) vm.sendline( 'sudo -n python ~/mininet/examples/test/runner.py -v' ) def walkthroughTest( vm, prompt=Prompt ): "Test mininet walkthrough" installPexpect( vm, prompt ) vm.sendline( 'sudo -n python ~/mininet/mininet/test/test_walkthrough.py -v' ) def useTest( vm, prompt=Prompt ): "Use VM interactively - exit by pressing control-]" old = vm.logfile if old == stdout: # Avoid doubling every output character! log( '* Temporarily disabling logging to stdout' ) vm.logfile = None log( '* Switching to interactive use - press control-] to exit' ) vm.interact() if old == stdout: log( '* Restoring logging to stdout' ) vm.logfile = stdout # A convenient alias for use - 'run'; we might want to allow # 'run' to take a parameter runTest = useTest def checkOutBranch( vm, branch, prompt=Prompt ): # This is a bit subtle; it will check out an existing branch (e.g. master) # if it exists; otherwise it will create a detached branch. # The branch will be rebased to its parent on origin. # This probably doesn't matter since we're running on a COW disk # anyway. vm.sendline( 'cd ~/mininet; git fetch --all; git checkout ' + branch + '; git pull --rebase origin ' + branch ) vm.expect( prompt ) vm.sendline( 'sudo -n make install' ) def interact( vm, tests, pre='', post='', prompt=Prompt ): "Interact with vm, which is a pexpect object" login( vm ) log( '* Waiting for login...' ) vm.expect( prompt ) log( '* Sending hostname command' ) vm.sendline( 'hostname' ) log( '* Waiting for output' ) vm.expect( prompt ) log( '* Fetching Mininet VM install script' ) branch = Branch if Branch else 'master' vm.sendline( 'wget ' 'https://raw.github.com/mininet/mininet/%s/util/vm/' 'install-mininet-vm.sh' % branch ) vm.expect( prompt ) log( '* Running VM install script' ) installcmd = 'bash -v install-mininet-vm.sh' if Branch: installcmd += ' ' + Branch vm.sendline( installcmd ) vm.expect ( 'password for mininet: ' ) vm.sendline( 'mininet' ) log( '* Waiting for script to complete... ' ) # Gigantic timeout for now ;-( vm.expect( 'Done preparing Mininet', timeout=3600 ) log( '* Completed successfully' ) vm.expect( prompt ) version = getMininetVersion( vm ) vm.expect( prompt ) log( '* Mininet version: ', version ) log( '* Testing Mininet' ) runTests( vm, tests=tests, pre=pre, post=post ) # Ubuntu adds this because we install via a serial console, # but we want the VM to boot via the VM console. Otherwise # we get the message 'error: terminal "serial" not found' log( '* Disabling serial console' ) vm.sendline( "sudo sed -i -e 's/^GRUB_TERMINAL=serial/#GRUB_TERMINAL=serial/' " "/etc/default/grub; sudo update-grub" ) vm.expect( prompt ) log( '* Shutting down' ) vm.sendline( 'sync; sudo shutdown -h now' ) log( '* Waiting for EOF/shutdown' ) vm.read() log( '* Interaction complete' ) return version def cleanup(): "Clean up leftover qemu-nbd processes and other junk" call( [ 'sudo', 'pkill', '-9', 'qemu-nbd' ] ) def convert( cow, basename ): """Convert a qcow2 disk to a vmdk and put it a new directory basename: base name for output vmdk file""" vmdk = basename + '.vmdk' log( '* Converting qcow2 to vmdk' ) run( 'qemu-img convert -f qcow2 -O vmdk %s %s' % ( cow, vmdk ) ) return vmdk # Template for OVF - a very verbose format! # In the best of all possible worlds, we might use an XML # library to generate this, but a template is easier and # possibly more concise! # Warning: XML file cannot begin with a newline! OVFTemplate = """ Virtual disk information The list of logical networks The nat network %(vminfo)s (%(name)s) %(vmname)s The kind of installed guest operating system %(osname)s Virtual hardware requirements hertz * 10^6 Number of Virtual CPUs %(cpus)s virtual CPU(s) 1 3 %(cpus)s byte * 2^20 Memory Size %(mem)dMB of memory 2 4 %(mem)d 0 scsiController0 SCSI Controller scsiController0 4 lsilogic 6 0 disk1 ovf:/disk/vmdisk1 11 4 17 2 true nat E1000 ethernet adapter on nat ethernet0 12 E1000 10 0 usb USB Controller usb 9 23 """ def generateOVF( name, osname, osid, diskname, disksize, mem=1024, cpus=1, vmname='Mininet-VM', vminfo='A Mininet Virtual Machine' ): """Generate (and return) OVF file "name.ovf" name: root name of OVF file to generate osname: OS name for OVF (Ubuntu | Ubuntu 64-bit) osid: OS ID for OVF (93 | 94 ) diskname: name of disk file disksize: size of virtual disk in bytes mem: VM memory size in MB cpus: # of virtual CPUs vmname: Name for VM (default name when importing) vmimfo: Brief description of VM for OVF""" ovf = name + '.ovf' filesize = stat( diskname )[ ST_SIZE ] params = dict( osname=osname, osid=osid, diskname=diskname, filesize=filesize, disksize=disksize, name=name, mem=mem, cpus=cpus, vmname=vmname, vminfo=vminfo ) xmltext = OVFTemplate % params with open( ovf, 'w+' ) as f: f.write( xmltext ) return ovf def qcow2size( qcow2 ): "Return virtual disk size (in bytes) of qcow2 image" output = check_output( [ 'qemu-img', 'info', qcow2 ] ) try: assert 'format: qcow' in output bytes = int( re.findall( '(\d+) bytes', output )[ 0 ] ) except: raise Exception( 'Could not determine size of %s' % qcow2 ) return bytes def build( flavor='raring32server', tests=None, pre='', post='', memory=1024 ): """Build a Mininet VM; return vmdk and vdisk size tests: tests to run pre: command line to run in VM before tests post: command line to run in VM after tests prompt: shell prompt (default '$ ') memory: memory size in MB""" global LogFile, Zip, Chown start = time() lstart = localtime() date = strftime( '%y%m%d-%H-%M-%S', lstart) ovfdate = strftime( '%y%m%d', lstart ) dir = 'mn-%s-%s' % ( flavor, date ) if Branch: dirname = 'mn-%s-%s-%s' % ( Branch, flavor, date ) try: os.mkdir( dir) except: raise Exception( "Failed to create build directory %s" % dir ) if Chown: run( 'chown %s %s' % ( Chown, dir ) ) os.chdir( dir ) LogFile = open( 'build.log', 'w' ) log( '* Logging to', abspath( LogFile.name ) ) log( '* Created working directory', dir ) image, kernel, initrd = findBaseImage( flavor ) basename = 'mininet-' + flavor volume = basename + '.qcow2' run( 'qemu-img create -f qcow2 -b %s %s' % ( image, volume ) ) log( '* VM image for', flavor, 'created as', volume ) if LogToConsole: logfile = stdout else: logfile = open( flavor + '.log', 'w+' ) log( '* Logging results to', abspath( logfile.name ) ) vm = boot( volume, kernel, initrd, logfile, memory=memory ) version = interact( vm, tests=tests, pre=pre, post=post ) size = qcow2size( volume ) arch = archFor( flavor ) vmdk = convert( volume, basename='mininet-vm-' + arch ) if not SaveQCOW2: log( '* Removing qcow2 volume', volume ) os.remove( volume ) log( '* Converted VM image stored as', abspath( vmdk ) ) ovfname = 'mininet-%s-%s-%s' % ( version, ovfdate, OSVersion( flavor ) ) osname, osid = OVFOSNameID( flavor ) ovf = generateOVF( name=ovfname, osname=osname, osid=osid, diskname=vmdk, disksize=size ) log( '* Generated OVF descriptor file', ovf ) if Zip: log( '* Generating .zip file' ) run( 'zip %s-ovf.zip %s %s' % ( ovfname, ovf, vmdk ) ) end = time() elapsed = end - start log( '* Results logged to', abspath( logfile.name ) ) log( '* Completed in %.2f seconds' % elapsed ) log( '* %s VM build DONE!!!!! :D' % flavor ) os.chdir( '..' ) def runTests( vm, tests=None, pre='', post='', prompt=Prompt, uninstallNtpd=False ): "Run tests (list) in vm (pexpect object)" # We disable ntpd and set the time so that ntpd won't be # messing with the time during tests. Set to true for a COW # disk and False for a non-COW disk. if uninstallNtpd: removeNtpd( vm ) vm.expect( prompt ) if Branch: checkOutBranch( vm, branch=Branch ) vm.expect( prompt ) if not tests: tests = [] if pre: log( '* Running command', pre ) vm.sendline( pre ) vm.expect( prompt ) testfns = testDict() if tests: log( '* Running tests' ) for test in tests: if test not in testfns: raise Exception( 'Unknown test: ' + test ) log( '* Running test', test ) fn = testfns[ test ] fn( vm ) vm.expect( prompt ) if post: log( '* Running post-test command', post ) vm.sendline( post ) vm.expect( prompt ) def getMininetVersion( vm ): "Run mn to find Mininet version in VM" vm.sendline( '~/mininet/bin/mn --version' ) # Eat command line echo, then read output line vm.readline() version = vm.readline().strip() return version def bootAndRun( image, prompt=Prompt, memory=1024, cpuCores=1, outputFile=None, runFunction=None, **runArgs ): """Boot and test VM tests: list of tests to run pre: command line to run in VM before tests post: command line to run in VM after tests prompt: shell prompt (default '$ ') memory: VM memory size in MB cpuCores: number of CPU cores to use""" bootTestStart = time() basename = path.basename( image ) image = abspath( image ) tmpdir = mkdtemp( prefix='test-' + basename ) log( '* Using tmpdir', tmpdir ) cow = path.join( tmpdir, basename + '.qcow2' ) log( '* Creating COW disk', cow ) run( 'qemu-img create -f qcow2 -b %s %s' % ( image, cow ) ) log( '* Extracting kernel and initrd' ) kernel, initrd = extractKernel( image, flavor=basename, imageDir=tmpdir ) if LogToConsole: logfile = stdout else: logfile = NamedTemporaryFile( prefix=basename, suffix='.testlog', delete=False ) log( '* Logging VM output to', logfile.name ) vm = boot( cow=cow, kernel=kernel, initrd=initrd, logfile=logfile, memory=memory, cpuCores=cpuCores ) login( vm ) log( '* Waiting for prompt after login' ) vm.expect( prompt ) # runFunction should begin with sendline and should eat its last prompt if runFunction: runFunction( vm, **runArgs ) log( '* Shutting down' ) vm.sendline( 'sudo -n shutdown -h now ' ) log( '* Waiting for shutdown' ) vm.wait() if outputFile: log( '* Saving temporary image to %s' % outputFile ) convert( cow, outputFile ) log( '* Removing temporary dir', tmpdir ) srun( 'rm -rf ' + tmpdir ) elapsed = time() - bootTestStart log( '* Boot and test completed in %.2f seconds' % elapsed ) def buildFlavorString(): "Return string listing valid build flavors" return 'valid build flavors: %s' % ' '.join( sorted( isoURLs ) ) def testDict(): "Return dict of tests in this module" suffix = 'Test' trim = len( suffix ) fdict = dict( [ ( fname[ : -trim ], f ) for fname, f in inspect.getmembers( modules[ __name__ ], inspect.isfunction ) if fname.endswith( suffix ) ] ) return fdict def testString(): "Return string listing valid tests" tests = [ '%s <%s>' % ( name, func.__doc__ ) for name, func in testDict().iteritems() ] return 'valid tests: %s' % ', '.join( tests ) def parseArgs(): "Parse command line arguments and run" global LogToConsole, NoKVM, Branch, Zip, TIMEOUT, Forward, Chown parser = argparse.ArgumentParser( description='Mininet VM build script', epilog='' ) parser.add_argument( '-v', '--verbose', action='store_true', help='send VM output to console rather than log file' ) parser.add_argument( '-d', '--depend', action='store_true', help='install dependencies for this script' ) parser.add_argument( '-l', '--list', action='store_true', help='list valid build flavors and tests' ) parser.add_argument( '-c', '--clean', action='store_true', help='clean up leftover build junk (e.g. qemu-nbd)' ) parser.add_argument( '-q', '--qcow2', action='store_true', help='save qcow2 image rather than deleting it' ) parser.add_argument( '-n', '--nokvm', action='store_true', help="Don't use kvm - use tcg emulation instead" ) parser.add_argument( '-m', '--memory', metavar='MB', type=int, default=1024, help='VM memory size in MB' ) parser.add_argument( '-i', '--image', metavar='image', default=[], action='append', help='Boot and test an existing VM image' ) parser.add_argument( '-t', '--test', metavar='test', default=[], action='append', help='specify a test to run; ' + testString() ) parser.add_argument( '-w', '--timeout', metavar='timeout', type=int, default=0, help='set expect timeout' ) parser.add_argument( '-r', '--run', metavar='cmd', default='', help='specify a command line to run before tests' ) parser.add_argument( '-p', '--post', metavar='cmd', default='', help='specify a command line to run after tests' ) parser.add_argument( '-b', '--branch', metavar='branch', help='branch to install and/or check out and test' ) parser.add_argument( 'flavor', nargs='*', help='VM flavor(s) to build; ' + buildFlavorString() ) parser.add_argument( '-z', '--zip', action='store_true', help='archive .ovf and .vmdk into .zip file' ) parser.add_argument( '-o', '--out', help='output file for test image (vmdk)' ) parser.add_argument( '-f', '--forward', default=[], action='append', help='forward VM ports to local server, e.g. tcp:5555::22' ) parser.add_argument( '-u', '--chown', metavar='user', help='specify an owner for build directory' ) args = parser.parse_args() if args.depend: depend() if args.list: print buildFlavorString() if args.clean: cleanup() if args.verbose: LogToConsole = True if args.nokvm: NoKVM = True if args.branch: Branch = args.branch if args.zip: Zip = True if args.timeout: TIMEOUT = args.timeout if args.forward: Forward = args.forward if not args.test and not args.run and not args.post: args.test = [ 'sanity', 'core' ] if args.chown: Chown = args.chown for flavor in args.flavor: if flavor not in isoURLs: print "Unknown build flavor:", flavor print buildFlavorString() break try: build( flavor, tests=args.test, pre=args.run, post=args.post, memory=args.memory ) except Exception as e: log( '* BUILD FAILED with exception: ', e ) exit( 1 ) for image in args.image: bootAndRun( image, runFunction=runTests, tests=args.test, pre=args.run, post=args.post, memory=args.memory, outputFile=args.out, uninstallNtpd=True ) if not ( args.depend or args.list or args.clean or args.flavor or args.image ): parser.print_help() if __name__ == '__main__': parseArgs() mininet-2.2.2/util/vm/install-mininet-vm.sh000077500000000000000000000031361306431124000206420ustar00rootroot00000000000000#!/bin/bash # This script is intended to install Mininet into # a brand-new Ubuntu virtual machine, # to create a fully usable "tutorial" VM. # # optional argument: Mininet branch to install set -e echo "$(whoami) ALL=(ALL) NOPASSWD:ALL" | sudo tee -a /etc/sudoers > /dev/null sudo sed -i -e 's/Default/#Default/' /etc/sudoers echo mininet-vm | sudo tee /etc/hostname > /dev/null sudo sed -i -e 's/ubuntu/mininet-vm/g' /etc/hosts sudo hostname `cat /etc/hostname` sudo sed -i -e 's/splash//' /etc/default/grub sudo sed -i -e 's/quiet/text/' /etc/default/grub sudo update-grub # Update from official archive sudo apt-get update # 12.10 and earlier #sudo sed -i -e 's/us.archive.ubuntu.com/mirrors.kernel.org/' \ # /etc/apt/sources.list # 13.04 and later #sudo sed -i -e 's/\/archive.ubuntu.com/\/mirrors.kernel.org/' \ # /etc/apt/sources.list # Clean up vmware easy install junk if present if [ -e /etc/issue.backup ]; then sudo mv /etc/issue.backup /etc/issue fi if [ -e /etc/rc.local.backup ]; then sudo mv /etc/rc.local.backup /etc/rc.local fi # Fetch Mininet sudo apt-get -y install git-core openssh-server git clone git://github.com/mininet/mininet # Optionally check out branch if [ "$1" != "" ]; then pushd mininet #git checkout -b $1 $1 # TODO branch will in detached HEAD state if it is not master git checkout $1 popd fi # Install Mininet time mininet/util/install.sh # Finalize VM time mininet/util/install.sh -tcd # Ignoring this since NOX classic is deprecated #if ! grep NOX_CORE_DIR .bashrc; then # echo "export NOX_CORE_DIR=~/noxcore/build/src/" >> .bashrc #fi echo "Done preparing Mininet VM."