diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..95105a3 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.py diff=python diff --git a/.gitignore b/.gitignore index 92e57eb..324dabd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,14 @@ mnexec *.pyc *~ +*.1 +*.xcodeproj +*.xcworkspace +\#*\# mininet.egg-info build dist +doc/html +doc/latex +trunk + diff --git a/.pylint b/.pylint index 85c2153..1912748 100644 --- a/.pylint +++ b/.pylint @@ -25,9 +25,6 @@ ignore=CVS # Pickle collected data for later comparisons. persistent=yes -# Set the cache size for astng objects. -cache-size=500 - # List of plugins (as comma separated values of python modules names) to load, # usually to register additional checkers. load-plugins= @@ -35,24 +32,15 @@ load-plugins= [MESSAGES CONTROL] -# Enable only checker(s) with the given id(s). This option conflicts with the -# disable-checker option -#enable-checker= +# 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= -# Enable all checker(s) except those with the given id(s). This option -# conflicts with the enable-checker option -#disable-checker= - -# Enable all messages in the listed categories (IRCWEF). -#enable-msg-cat= - -# Disable all messages in the listed categories (IRCWEF). -disable-msg-cat=IR - -# Enable the message(s) with the given id(s). -#enable-msg= - -# Disable the message(s) with the given id(s). +# 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=W0704,C0103,W0231,E1102,W0511,W0142,R0902,R0903,R0904,R0913,R0914,R0801,I0011 @@ -60,7 +48,7 @@ disable=W0704,C0103,W0231,E1102,W0511,W0142,R0902,R0903,R0904,R0913,R0914,R0801, # Set the output format. Available formats are text, parseable, colorized, msvs # (visual studio) and html -output-format=text +output-format=colorized # Include message's id in outpu include-ids=yes @@ -276,7 +264,7 @@ int-import-graph= max-line-length=80 # Maximum number of lines in a module -max-module-lines=1000 +max-module-lines=1500 # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 # tab). diff --git a/INSTALL b/INSTALL index a03af19..59aee0a 100644 --- a/INSTALL +++ b/INSTALL @@ -1,29 +1,54 @@ Mininet Installation/Configuration Notes -Mininet 1.0.0 +Mininet 2.0.0rc1 --- The supported installation methods for Mininet are 1) using -a pre-built VM image, and 2) native installation on Ubuntu or Debian. +a pre-built VM image, and 2) native installation on Ubuntu. You +can also easily create your own Mininet VM image (3). + (Other distributions may be supported in the future - if you would like to contribute an installation script, we would welcome it!) -1. Easiest "install" - use our pre-built VM image! +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 http://openflow.org/mininet Boot up the VM image, log in, and follow the instructions on the wiki page. -An additional advantage of using the VM image is that it doesn't mess with +One advantage of using the VM image is that it doesn't mess with your native OS installation or damage it in any way. -2. Native installation (experimental!) for Ubuntu 10.04 LTS +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. -If you are running Ubuntu 10.04 LTS (or possibly Debian 5), you may be -able to use our handy install.sh script, which is in mininet/util. +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 on Ubuntu 11.10+ + +If you're reading this, you've probably already done so, but the command to +download the Mininet source code is; + + git clone git://openflow.org/mininet.git + +If you are running Ubuntu, you may be able to use our handy install.sh script, +which is in mininet/util. WARNING: USE AT YOUR OWN RISK! @@ -34,240 +59,59 @@ 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: + +$ mininet/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, -you may use +including NOX classic, the OpenFlow WireShark dissector, the oftest +framework, and other potentially useful software (and to add some stuff +to /etc/sysctl.conf which may or may not be useful) you may use -$ mininet/util/install.sh +$ mininet/util/install.sh -a -This takes about 20-30 minutes. +This takes about 20 minutes on our test system. -Alternately, you can install just the pieces you need. +4. Creating your own Mininet/OpenFlow tutorial VM -We recommend the following steps, in order: +Creating your own Ubuntu Mininet VM for use with the OpenFlow tutorial +is easy! First, create a new Ubuntu VM. Then, run -[a) On Debian 5, first install a Mininet-compatible kernel: - $ mininet/util/install.sh -k - Reboot and run 'uname -r' to make sure you're running the new kernel.] +$ wget https://raw.github.com/mininet/mininet/util/vm/install-mininet-vm.sh +$ time install-mininet-vm.sh -b) Install mininet and its dependencies: - $ mininet/util/install.sh -n - -c) Install OpenFlow 1.0 and associated useful software - $ mininet/util/install.sh -f +5. Installation on other Linux distributions -d) Install Open vSwitch and its kernel module - $ mininet/util/install.sh -vm +Although we don't support other Linux distributions directly, it should be +possible to install and run Mininet with some degree of manual effort. -e) If you wish to install the version of NOX we use in the tutorial: - $ mininet/util/install.sh -x +In general, you must have: - Note: NOX development is progressing over time, so after you complete - the tutorial you may wish to install the latest and greatest NOX from - noxrepo.org. +* A Linux kernel compiled with network namespace support enabled -Good luck! Some additional installation notes are provided below, for -the brave and/or Linux-savvy, or those who are trying to understand what -is installed and why. +* An OpenFlow implementation (either the reference user or kernel + space implementations, or Open vSwitch.) Appropriate kernel modules + (e.g. tun and ofdatapath for the reference kernel implementation) must + be loaded. -p.s. Note that only one instance of Mininet is currently supported on a single -machine - that's one reason we recommend using a VM to run it. +* Python, `bash`, `ping`, `iperf`, etc. ---- +* Root privileges (required for network device access) -Mininet Manual Installation Notes - -These installation notes assume you understand how to do things like -compile kernels, apply patches, configure networks, write code, etc.. If -this is unfamiliar territory, or if you run into trouble, we recommend -using one of our pre-built virtual machine images (see above.) - -If you wish to try to create a VM to run Mininet, you may also wish -to look at the Wiki page: - -http://openflow.org/foswiki/bin/view/OpenFlow/MininetVMCreationNotes - -0. Obtaining Mininet - - If you're reading this, you've already done it, but the command to - download mininet is: - - git clone git://openflow.org/mininet.git - -1. Core Mininet installation - - The core Mininet installation requires gcc, make, python, - and setuptools. On Ubuntu and Debian you may install them with: - - # aptitude install gcc make python setuptools - - To install Mininet itself, with root privileges: - - # cd mininet - # make install - - This places the mininet package in /usr/lib/python-*/site-packages/, - so that 'import mininet' will work, and installs the primary mn - script (mn) as well as its helper utility (mnexec.) - - On Ubuntu and Debian, Mininet's dependencies and core files may also be - installed using mininet/util/install.sh -n - -2. Installation script for Ubuntu/Debian Lenny - - If you are running Ubuntu 10.04 or Debian Lenny, you may be able to use the - util/install.sh script to install a compatible Linux kernel as well as - other software including the OpenFlow reference implementation, the Open - vSwitch switch implementation, and the NOX OpenFlow controller. - - Many different installation options are possible by passing different - options to install.sh; install.sh -h lists them all. - - Assuming the mininet source tree is installed in ~/mininet, the steps to run - install.sh to install EVERYTHING we use for OpenFlow tutorials are: - - % cd - % time ~/mininet/util/install.sh # installs tons of stuff - % sudo reboot # to load new kernel - % ~/mininet/util/install.sh -c # to clean out unneeded kernel stuff - - This installs a lot of useful software, but it will take a while (30 - minutes or more, depending on your network connection, computer, etc..) - - Probably the minimal semi-useful configuration would be to install - Mininet itself, kernel support if necessary, and either the - reference OpenFlow switch or Open vSwitch. This could be installed - as follows: - - % sudo ~/mininet/util/install.sh -knvm - - Respectively, this installs kernel support, core mininet dependencies, - Open vSwitch, and the Open vSwitch kernel module. If a new kernel was - installed, then a reboot may be required. - - If install.sh cannot be used for some reason (e.g. you're on Fedora - or some other Linux - please don't say CentOS) or if you don't want to - install all of these components (they're useful!), the kernel and - OpenFlow software requirements are described in steps [3] and [4], - which follow. - - If you successfully used install.sh, congratulations! You're basically - done. Proceed to step [6] for additional advice. - -3. Linux Kernel requirements - - Mininet requires a kernel built with network namespace support enabled, - i.e. with CONFIG_NET_NS=Y, such as the kernel shipped with - Ubuntu 10.04 LTS, currently 2.6.32. On Ubuntu 10.04, you should not need - to install or build a custom kernel, although 2.6.33+ is faster at - tearing down virtual ethernet pairs. - - For Ubuntu and Debian, we provide a 2.6.33 kernel package which you may be - able to install using "util/install.sh -k". Note our kernel package - requires an ext2 or ext3 root file system, so it won't work if you have - a default Ubuntu install, which uses ext4. - - If your kernel wasn't compiled with CONFIG_NET_NS=Y, you will need to - build and install a kernel that does! >= 2.6.33 works better, but may - be harder to get working, depending on your Linux distribution. - - A script for building Debian packages for 2.6.33.1 is provided in - mininet/util/kbuild. You may wish to read it, as it applies patches - to enable 2.6.33.1 to build under debian-stable, and to enable the - tun driver to work correctly with Mininet. - - Earlier kernels (e.g. 2.6.29) work with CONFIG_NET_NS enabled and no - additional patches, but are much slower at removing veth interfaces, - resulting in much slower switch shutdown. - - For scalable configurations, you might need to increase some of your - kernel limits. Sample params are in util/sysctl_addon, which can be - appended to /etc/sysctl.conf (and modified as necessary for your - desired configuration): - - sudo su -c "cat sysctl_addon >> /etc/sysctl.conf" - - To save the config change, run: - - sudo sysctl -p - -4. OpenFlow software and configuration requirements - - Mininet requires either the reference OpenFlow switch implementation - (from openflowswitch.org) or Open vSwitch (openvswitch.org) to be - installed. "make test" requires the reference user space - implementations as well as Open vSwitch. Note the reference kernel - implementation is not currently included in OpenFlow 1.0. - - On Ubuntu and Debian, the install.sh script may be used with the '-f' - option to install the OpenFlow reference implementation, the '-v' option - to build Open vSwitch, and the '-m' option to install the Open vSwitch - kernel module into /lib/modules (note: you must build Open vSwitch first!) - - Mininet will automatically load and remove kernel module dependencies - for supported switch types, using modprobe and rmmod - but these - modules must be in a location where modprobe can find them (e.g. - something like /lib/modules/`uname -r`/kernel/drivers/net/) - - The reference OpenFlow controller (controller(8)) only supports 16 - switches by default! If you wish to run a network with more than 16 - switches, please recompile controller(8) with larger limits, or use a - different controller such as nox. A patch to controller(8) is included - as util/openflow-patches/controller.patch. - -5. Other software dependencies - - On Ubuntu and Debian, other Mininet dependencies may be installed using - the '-n' option of the install.sh script. - - To run the iperf test, you need to install iperf: - - sudo aptitude/yum install iperf - - We assume you already have ping installed. ;-) - - To use xterm or sshd with Mininet, you need the following: - - sudo aptitude/yum install sshd xterm screen - - Some examples may have additional requirements - consult the specific - example file for details. - - The install.sh script has an '-x' option to install the version of - NOX from the OpenFlow tutorial. - -6. Other notes and recommendations - - If you did not install certain useful packages and you wish to later, - it may be possible to install them using install.sh. - - Mininet should be run either on a machine with - no other important processes, or on a virtual machine (recommended!) - - Multiple concurrent Mininet instances are not supported! +We encourage contribution of patches to the `install.sh` script to support +other Linux distributions. Good luck! +Mininet Team + --- -Historical information on OpenFlow 0.8.9 and the reference kernel module: - - The kernel reference implementation has been deprecated, but it may - be possible to get it work with Mininet. - - To switch to the most recent OpenFlow 0.8.9 release branch (the most - recent one with full NOX support and kernel datapath support) in your - OpenFlow git tree: - - git checkout -b release/0.8.9 remotes/origin/release/0.8.9 - - A patch to enable datapath.c to compile with recent kernels - is included in util/openflow-patches/datapath.patch. - - In OpenFlow 1.0, switch port numbering starts at 1 (for better or for worse.) - To run with previous versions of OpenFlow, it may be necessary - to change SWITCH_PORT_BASE from 1 to 0 in node.py. - - - diff --git a/LICENSE b/LICENSE index 7546c7f..ba81938 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,10 @@ -Mininet 1.0.0 License +Mininet 2.0.0rc1 License -Copyright (c) 2009-2011 Bob Lantz and Brandon Heller +Copyright (c) 2012 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 diff --git a/Makefile b/Makefile index f3e26e0..c0c53f2 100644 --- a/Makefile +++ b/Makefile @@ -1,30 +1,65 @@ -all: codecheck test - -clean: - rm -rf build dist *.egg-info *.pyc mnexec bin/mnexec - MININET = mininet/*.py TEST = mininet/test/*.py EXAMPLES = examples/*.py -BIN = bin/mn +MN = bin/mn +BIN = $(MN) PYSRC = $(MININET) $(TEST) $(EXAMPLES) $(BIN) - +MNEXEC = mnexec +MANPAGES = mn.1 mnexec.1 P8IGN = E251,E201,E302,E202 +BINDIR = /usr/bin +MANDIR = /usr/share/man/man1 +DOCDIRS = doc/html doc/latex +PDF = doc/latex/refman.pdf + +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) pep8 --repeat --ignore=$(P8IGN) $(PYSRC) +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 -install: mnexec - cp mnexec bin/ +mnexec: mnexec.c $(MN) mininet/net.py + cc $(CFLAGS) $(LDFLAGS) -DVERSION=\"`PYTHONPATH=. $(MN) --version`\" $< -o $@ + +install: $(MNEXEC) $(MANPAGES) + install $(MNEXEC) $(BINDIR) + install $(MANPAGES) $(MANDIR) python setup.py install -doc: - doxygen doxygen.cfg +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 $< -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 diff --git a/README b/README deleted file mode 100644 index dc8d25f..0000000 --- a/README +++ /dev/null @@ -1,86 +0,0 @@ - - Mininet: A Simple Virtual Testbed for OpenFlow/SDN - or -How to Squeeze a 1024-node OpenFlow Network onto your Laptop - -Mininet 1.0.0 - ---- -Welcome to Mininet! - -Mininet creates OpenFlow test networks by using process-based -virtualization and network namespaces. - -Simulated hosts (as well as switches and controllers with the user -datapath) are created as processes in separate network namespaces. This -allows a complete OpenFlow network to be simulated on top of a single -Linux kernel. - -Mininet may be invoked directly from the command line, and also provides a -handy Python API for creating networks of varying sizes and topologies. - -Mininet is currently in *limited alpha release*. We encourage you to -experiment with it and hope that you will provide us with feedback on -features, documentation, and how you're using it. We plan to make it -available publicly via a GPL or BSD license (probably in April), but please -don't distribute the code or URLs yet! The feedback you provide will help -us improve Mininet for general release. - -In order to run Mininet, you must have: - -* A Linux 2.6.26 or greater kernel compiled with network namespace support - enabled (see INSTALL for additional information.) - -* An OpenFlow implementation (either the reference user or kernel - space implementations, or Open vSwitch.) Appropriate kernel modules - (e.g. tun and ofdatapath for the reference kernel implementation) must - be loaded. - -* Python, bash, ping, iperf, etc. - -* Root privileges (required for network device access) - -Currently Mininet includes: - -- A simple node infrastructure (Host, Switch, Controller classes) for - creating virtual OpenFlow networks - -- A simple network infrastructure (Mininet class) supporting parametrized - topologies (Topo subclasses.) For example, a tree network may be created - with the command - - # mn --topo tree,depth=2,fanout=3 - -- Basic tests, including connectivity (ping) and bandwidth (iperf) - -- A command-line interface (CLI class) which provides useful - diagnostic commands, as well as the ability to send 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 - -- Examples (in the examples/ directory) to help you get started. - -Batteries are not included (yet!) - -However, some preliminary installation notes are included in the INSTALL -file. - -Additionally, much useful information, including a Mininet tutorial, -is available on the Mininet wiki: - -http://openflow.org/mininet - -Enjoy, and good luck! - ---- -Bob Lantz -rlantz@cs.stanford.edu diff --git a/README.md b/README.md new file mode 100644 index 0000000..3e75af3 --- /dev/null +++ b/README.md @@ -0,0 +1,134 @@ + +Mininet: Rapid Prototyping for Software Defined Networks +======================================================== + +*The best way to emulate almost any network on your laptop!* + +Version 2.0.0rc1 + +### 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 2.0.0rc1 + +Mininet 2.0.0rc1 is a major upgrade and provides +a number of enhancements and new features, including: + +* "Mininet-HiFi" functionality: + + * Link bandwidth limits using `tc` (`TCIntf` and `TCLink` classes) + + * CPU isolation and bandwidth limits (`CPULimitedHost` class) + +* Support for Open vSwitch 1.4+ (including Ubuntu OVS packages) + +* Debian packaging (and `apt-get install mininet` in Ubuntu 12.10) + +* First-class Interface (`Intf`) and Link (`Link`) classes for easier + extensibility + +* An upgraded Topology (`Topo`) class which supports node and link + customization + +* Man pages for the `mn` and `mnexec` utilities. + +[Since the API (most notably the topology) has changed, existing code +that runs in Mininet 1.0 will need to be changed to run with Mininet +2.0. This is the primary reason for the major version number change.] + +### 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://openflow.org/mininet). +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: + + + +### Contributing + +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, and enhancements! + +Best wishes, and we look forward to seeing what you can do with +Mininet to change the networking world! + +### Credits + +The Mininet Team: + +* Bob Lantz +* Brandon Heller +* Nikhil Handigol +* Vimal Jeyakumar diff --git a/bin/mn b/bin/mn index 1a63737..80def32 100755 --- a/bin/mn +++ b/bin/mn @@ -12,69 +12,67 @@ Example to pull custom params (topo, switch, etc.) from a file: """ from optparse import OptionParser -import os.path +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 -from mininet.net import Mininet, init -from mininet.node import KernelSwitch, Host, Controller, ControllerParams, NOX -from mininet.node import RemoteController, UserSwitch, OVSKernelSwitch +from mininet.net import Mininet, MininetWithControlNet, VERSION +from mininet.node import ( Host, CPULimitedHost, Controller, OVSController, + NOX, RemoteController, UserSwitch, OVSKernelSwitch, + OVSLegacyKernelSwitch ) +from mininet.link import Link, TCLink from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo from mininet.topolib import TreeTopo -from mininet.util import makeNumeric +from mininet.util import custom, customConstructor +from mininet.util import buildTopo + # built in topologies, created only when run TOPODEF = 'minimal' TOPOS = { 'minimal': lambda: SingleSwitchTopo( k=2 ), - 'linear': LinearTopo, - 'reversed': SingleSwitchReversedTopo, - 'single': SingleSwitchTopo, - 'tree': TreeTopo } + 'linear': LinearTopo, + 'reversed': SingleSwitchReversedTopo, + 'single': SingleSwitchTopo, + 'tree': TreeTopo } SWITCHDEF = 'ovsk' -SWITCHES = { 'kernel': KernelSwitch, - 'user': UserSwitch, - 'ovsk': OVSKernelSwitch } +SWITCHES = { 'user': UserSwitch, + 'ovsk': OVSKernelSwitch, + 'ovsl': OVSLegacyKernelSwitch } -HOSTDEF = 'process' -HOSTS = { 'process': Host } +HOSTDEF = 'proc' +HOSTS = { 'proc': Host, + 'rt': custom( CPULimitedHost, sched='rt' ), + 'cfs': custom( CPULimitedHost, sched='cfs' ) } -CONTROLLERDEF = 'ref' -# a and b are the name and inNamespace params. +CONTROLLERDEF = 'ovsc' CONTROLLERS = { 'ref': Controller, - 'nox_dump': lambda name: NOX( name, 'packetdump' ), - 'nox_pysw': lambda name: NOX( name, 'pyswitch' ), - 'remote': lambda name: None, - 'none': lambda name: None } + 'ovsc': OVSController, + 'nox': NOX, + 'remote': RemoteController, + 'none': lambda name: None } + +LINKDEF = 'default' +LINKS = { 'default': Link, + 'tc': TCLink } + # optional tests to run TESTS = [ 'cli', 'build', 'pingall', 'pingpair', 'iperf', 'all', 'iperfudp', - 'none' ] + 'none' ] -ALTSPELLING = { 'pingall': 'pingAll', 'pingpair': 'pingPair', - 'iperfudp': 'iperfUdp', 'iperfUDP': 'iperfUdp', 'prefixlen': 'prefixLen' } - -def buildTopo( topo ): - "Create topology from string with format (object, arg1, arg2,...)." - topo_split = topo.split( ',' ) - topo_name = topo_split[ 0 ] - topo_params = topo_split[ 1: ] - - # Convert int and float args; removes the need for every topology to - # be flexible with input arg formats. - topo_seq_params = [ s for s in topo_params if '=' not in s ] - topo_seq_params = [ makeNumeric( s ) for s in topo_seq_params ] - topo_kw_params = {} - for s in [ p for p in topo_params if '=' in p ]: - key, val = s.split( '=' ) - topo_kw_params[ key ] = makeNumeric( val ) - - if topo_name not in TOPOS.keys(): - raise Exception( 'Invalid topo_name %s' % topo_name ) - return TOPOS[ topo_name ]( *topo_seq_params, **topo_kw_params ) +ALTSPELLING = { 'pingall': 'pingAll', + 'pingpair': 'pingPair', + 'iperfudp': 'iperfUdp', + 'iperfUDP': 'iperfUdp', + 'prefixlen': 'prefixLen' } def addDictOption( opts, choicesDict, default, name, helpStr=None ): @@ -86,16 +84,21 @@ def addDictOption( opts, choicesDict, default, name, helpStr=None ): help: string""" if default not in choicesDict: raise Exception( 'Invalid default %s for choices dict: %s' % - ( default, name ) ) + ( default, name ) ) if not helpStr: - helpStr = '[' + ' '.join( choicesDict.keys() ) + ']' + helpStr = ( '|'.join( sorted( choicesDict.keys() ) ) + + '[,param=value...]' ) opts.add_option( '--' + name, - type='choice', - choices=choicesDict.keys(), - default = default, - help = helpStr ) + type='string', + default = default, + help = helpStr ) +def version( *_args ): + "Print Mininet version and exit" + print "%s" % VERSION + exit() + class MininetRunner( object ): "Build, setup, and run Mininet." @@ -124,11 +127,11 @@ class MininetRunner( object ): def parseCustomFile( self, fileName ): "Parse custom file and add params before parsing cmd-line options." - custom = {} + customs = {} if os.path.isfile( fileName ): - execfile( fileName, custom, custom ) - for name in custom: - self.setCustom( name, custom[ name ] ) + execfile( fileName, customs, customs ) + for name, val in customs.iteritems(): + self.setCustom( name, val ) else: raise Exception( 'could not find custom file: %s' % fileName ) @@ -136,58 +139,64 @@ class MininetRunner( object ): """Parse command-line args and return options object. returns: opts parse options dict""" if '--custom' in sys.argv: - print "custom in sys.argv" index = sys.argv.index( '--custom' ) if len( sys.argv ) > index + 1: - custom = sys.argv[ index + 1 ] - self.parseCustomFile( custom ) + filename = sys.argv[ index + 1 ] + self.parseCustomFile( filename ) else: raise Exception( 'Custom file name not found' ) - opts = OptionParser() + 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, CONTROLLERDEF, 'controller' ) + addDictOption( opts, LINKS, LINKDEF, 'link' ) + addDictOption( opts, TOPOS, TOPODEF, 'topo' ) - opts.add_option( '--topo', type='string', default=TOPODEF, - help='[' + ' '.join( TOPOS.keys() ) + '],arg1,arg2,' - '...argN') opts.add_option( '--clean', '-c', action='store_true', - default=False, help='clean and exit' ) + default=False, help='clean and exit' ) opts.add_option( '--custom', type='string', default=None, - help='read custom topo and node params from .py file' ) + help='read custom topo and node params from .py' + + 'file' ) opts.add_option( '--test', type='choice', choices=TESTS, - default=TESTS[ 0 ], - help='[' + ' '.join( TESTS ) + ']' ) + default=TESTS[ 0 ], + help='|'.join( TESTS ) ) opts.add_option( '--xterms', '-x', action='store_true', - default=False, help='spawn xterms for each node' ) + 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='set MACs equal to DPIDs' ) + default=False, help='automatically set host MACs' ) opts.add_option( '--arp', action='store_true', - default=False, help='set all-pairs ARP entries' ) + 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( '--ip', type='string', default='127.0.0.1', - help='[ip address as a dotted decimal string for a' - 'remote controller]' ) - opts.add_option( '--port', type='int', default=6633, - help='[port integer for a listening remote' - ' controller]' ) + 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=6634, - help='[base port for passive switch listening' - ' controller]' ) + default=False, help='sw and ctrl in namespace?' ) + opts.add_option( '--listenport', type='int', default=6635, + help='base port for passive switch listening' ) opts.add_option( '--nolistenport', action='store_true', - default=False, help="don't use passive listening port") + default=False, help="don't use passive listening " + + "port") opts.add_option( '--pre', type='string', default=None, - help='[CLI script to run before tests]' ) + help='CLI script to run before tests' ) opts.add_option( '--post', type='string', default=None, - help='[CLI script to run after tests]' ) + help='CLI script to run after tests' ) opts.add_option( '--prefixlen', type='int', default=8, - help='[prefix length (e.g. /8) for automatic ' - 'network configuration]' ) + help='prefix length (e.g. /8) for automatic ' + 'network configuration' ) + opts.add_option( '--pin', action='store_true', + default=False, help="pin hosts to CPU cores " + "(requires --host cfs or --host rt)" ) + opts.add_option( '--version', action='callback', callback=version ) self.options, self.args = opts.parse_args() @@ -202,9 +211,6 @@ class MininetRunner( object ): % self.options.verbosity ) lg.setLogLevel( self.options.verbosity ) - # validate environment setup - init() - def begin( self ): "Create and run mininet." @@ -214,35 +220,33 @@ class MininetRunner( object ): start = time.time() - topo = buildTopo( self.options.topo ) - switch = SWITCHES[ self.options.switch ] - host = HOSTS[ self.options.host ] - controller = CONTROLLERS[ self.options.controller ] - if self.options.controller == 'remote': - controller = lambda a: RemoteController( a, - defaultIP=self.options.ip, - port=self.options.port ) + topo = buildTopo( TOPOS, self.options.topo ) + switch = customConstructor( SWITCHES, self.options.switch ) + host = customConstructor( HOSTS, self.options.host ) + controller = customConstructor( CONTROLLERS, self.options.controller ) + link = customConstructor( LINKS, self.options.link ) if self.validate: self.validate( self.options ) - # We should clarify what this is actually for... - # It seems like it should be default values for the - # *data* network, so it may be misnamed. - controllerParams = ControllerParams( '10.0.0.0', - self.options.prefixlen) - inNamespace = self.options.innamespace + Net = MininetWithControlNet if inNamespace else Mininet + 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 - mn = Mininet( topo, switch, host, controller, controllerParams, - inNamespace=inNamespace, - xterms=xterms, autoSetMacs=mac, - autoStaticArp=arp, listenPort=listenPort ) + 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.pre: CLI( mn, script=self.options.pre ) diff --git a/custom/topo-2sw-2host.py b/custom/topo-2sw-2host.py index b0fff06..ee5ec35 100644 --- a/custom/topo-2sw-2host.py +++ b/custom/topo-2sw-2host.py @@ -28,10 +28,10 @@ class MyTopo( Topo ): rightHost = 4 # Add nodes - self.add_node( leftSwitch, Node( is_switch=True ) ) - self.add_node( rightSwitch, Node( is_switch=True ) ) - self.add_node( leftHost, Node( is_switch=False ) ) - self.add_node( rightHost, Node( is_switch=False ) ) + self.addNode( leftSwitch, Node( isSwitch=True ) ) + self.addNode( rightSwitch, Node( isSwitch=True ) ) + self.addNode( leftHost, Node( isSwitch=False ) ) + self.addNode( rightHost, Node( isSwitch=False ) ) # Add edges self.add_edge( leftHost, leftSwitch ) diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..af2b1d9 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,6 @@ +mininet (2.0.0d2-0ubuntu1) quantal; urgency=low + + * Initial release. + + -- Bob Lantz Sun, 01 Jul 2012 23:19:54 +0000 + diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..7f8f011 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +7 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..45597f3 --- /dev/null +++ b/debian/control @@ -0,0 +1,29 @@ +Source: mininet +Section: net +Priority: extra +Maintainer: Ubuntu Developers +XSBC-Original-Maintainer: Bob Lantz +Standards-Version: 3.9.3 +Build-Depends: + debhelper (>= 7.0.50~), + help2man, + python-dev, + python-pkg-resources, + python-setuptools +Homepage: http://openflow.org/mininet + +Package: mininet +Architecture: any +Depends: + openvswitch-controller, + openvswitch-switch, + python-networkx, + telnet, + ${misc:Depends}, + ${python:Depends}, + ${shlibs:Depends} +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. diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..b571896 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,33 @@ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0 +Upstream-Name: mininet +Source: https://github.com/mininet/mininet + +Files: * +Copyright: 2009-2012 Bob Lantz, + 2009-2012 Brandon Heller +License: + 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. diff --git a/debian/docs b/debian/docs new file mode 100644 index 0000000..e845566 --- /dev/null +++ b/debian/docs @@ -0,0 +1 @@ +README diff --git a/debian/examples b/debian/examples new file mode 100644 index 0000000..e39721e --- /dev/null +++ b/debian/examples @@ -0,0 +1 @@ +examples/* diff --git a/debian/install b/debian/install new file mode 100644 index 0000000..790f822 --- /dev/null +++ b/debian/install @@ -0,0 +1 @@ +mnexec /usr/bin diff --git a/debian/manpages b/debian/manpages new file mode 100644 index 0000000..f7e585b --- /dev/null +++ b/debian/manpages @@ -0,0 +1 @@ +*.1 diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..f000338 --- /dev/null +++ b/debian/rules @@ -0,0 +1,9 @@ +#!/usr/bin/make -f + +%: + dh $@ --buildsystem=python_distutils --with=python2 + +override_dh_auto_build: + make man + make mnexec + dh_auto_build diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 0000000..163aaf8 --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/debian/watch b/debian/watch new file mode 100644 index 0000000..9d78e15 --- /dev/null +++ b/debian/watch @@ -0,0 +1,3 @@ +version=3 +opts=filenamemangle=s/.*tarball\/(.*)/$1\.tar\.gz/ \ +https://github.com/mininet/mininet/tags .*/tarball/(\d[\d\.abd]+) diff --git a/doxygen.cfg b/doc/doxygen.cfg similarity index 99% rename from doxygen.cfg rename to doc/doxygen.cfg index acc568c..a92bb15 100644 --- a/doxygen.cfg +++ b/doc/doxygen.cfg @@ -25,7 +25,7 @@ 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 +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 @@ -114,7 +114,7 @@ FULL_PATH_NAMES = YES # If left blank the directory from which doxygen is run is used as the # path to strip. -STRIP_FROM_PATH = +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 @@ -919,7 +919,7 @@ COMPACT_LATEX = NO # by the printer. Possible values are: a4, a4wide, letter, legal and # executive. If left blank a4wide will be used. -PAPER_TYPE = a4wide +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. diff --git a/examples/README b/examples/README index 4f233a0..dd71bea 100644 --- a/examples/README +++ b/examples/README @@ -6,17 +6,39 @@ 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. + 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 and adds multiple controllers to it. + +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. + +limit.py: + +This example shows how to use link and CPU limits. + linearbandwidth.py: This example shows how to create a custom topology programatically @@ -26,16 +48,39 @@ miniedit.py: This example demonstrates creating a network via a graphical editor. +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. +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 @@ -44,10 +89,10 @@ 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.) -treeping64: +treeping64.py: This example creates a 64-host tree network, and attempts to check full -connectivity using ping, for three different switch/datapath types. +connectivity using ping, for different switch/datapath types. tree1024.py: @@ -55,7 +100,3 @@ 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.) -udpbwtest.py: - -This example shows how to run a test across an entire network, and monitor -the output of a set of hosts in real time. diff --git a/examples/baresshd.py b/examples/baresshd.py index 841454a..a714edb 100755 --- a/examples/baresshd.py +++ b/examples/baresshd.py @@ -3,17 +3,23 @@ "This example doesn't use OpenFlow, but attempts to run sshd in a namespace." from mininet.node import Host +from mininet.util import ensureRoot + +ensureRoot() print "*** Creating nodes" h1 = Host( 'h1' ) + root = Host( 'root', inNamespace=False ) print "*** Creating links" h1.linkTo( root ) +print h1 + print "*** Configuring nodes" -h1.setIP( h1.intfs[ 0 ], '10.0.0.1', 8 ) -root.setIP( root.intfs[ 0 ], '10.0.0.2', 8 ) +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' ) diff --git a/examples/consoles.py b/examples/consoles.py index 607163d..ea2e28d 100755 --- a/examples/consoles.py +++ b/examples/consoles.py @@ -74,11 +74,11 @@ class Console( Frame ): "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 ) + **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 ) + command=text.yview ) text.configure( yscrollcommand=ybar.set ) text.pack( side='left', expand=True, fill='both' ) ybar.pack( side='right', fill='y' ) @@ -95,7 +95,7 @@ class Console( Frame ): # way to trigger a file event handler from Tk's # event loop! self.tk.createfilehandler( self.node.stdout, READABLE, - self.handleReadable ) + self.handleReadable ) # We're not a terminal (yet?), so we ignore the following # control characters other than [\b\n\r] @@ -107,8 +107,10 @@ class Console( Frame ): 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: - self.outputHook( self, text ) + outputHook = self.outputHook + outputHook( self, text ) def handleKey( self, event ): "If it's an interactive command, send it to the node." @@ -130,27 +132,22 @@ class Console( Frame ): self.sendCmd( cmd ) # Callback ignores event - # pylint: disable-msg=W0613 - def handleInt( self, event=None ): + def handleInt( self, _event=None ): "Handle control-c." self.node.sendInt() - # pylint: enable-msg=W0613 def sendCmd( self, cmd ): "Send a command to our node." if not self.node.waiting: self.node.sendCmd( cmd ) - # Callback ignores fds - # pylint: disable-msg=W0613 - def handleReadable( self, fds, timeoutms=None ): + 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 ) - # pylint: enable-msg=W0613 def waiting( self ): "Are we waiting for output?" @@ -172,11 +169,8 @@ 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,): + def __init__( self, parent=None, bg = 'white', gheight=200, gwidth=500, + barwidth=10, ymax=3.5,): Frame.__init__( self, parent ) @@ -198,7 +192,7 @@ class Graph( Frame ): width = 25 ymax = self.ymax scale = Canvas( self, width=width, height=height, - background=self.bg ) + background=self.bg ) opts = { 'fill': 'red' } # Draw scale line scale.create_line( width - 1, height, width - 1, 0, **opts ) @@ -214,7 +208,7 @@ class Graph( Frame ): ofs = 20 height = self.gheight + ofs self.graph.configure( scrollregion=( 0, -ofs, - self.xpos * self.barwidth, height ) ) + self.xpos * self.barwidth, height ) ) self.scale.configure( scrollregion=( 0, -ofs, 0, height ) ) def yview( self, *args ): @@ -234,7 +228,7 @@ class Graph( Frame ): 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 ) ) + scrollregion=(0, 0, width, height ) ) scale.configure( yscrollcommand=ybar.set ) # Layout @@ -255,7 +249,7 @@ class Graph( Frame ): x1 = x0 + self.barwidth y0 = self.gheight y1 = ( 1 - percent ) * self.gheight - c.create_rectangle( x0 , y0, x1, y1, fill='green' ) + c.create_rectangle( x0, y0, x1, y1, fill='green' ) self.xpos += 1 self.updateScrollRegions() self.graph.xview( 'moveto', '1.0' ) @@ -319,9 +313,7 @@ class ConsoleApp( Frame ): self.pack( expand=True, fill='both' ) - # Update callback doesn't use console arg - # pylint: disable-msg=W0613 - def updateGraph( self, console, output ): + def updateGraph( self, _console, output ): "Update our graph." m = re.search( r'(\d+) Mbits/sec', output ) if not m: @@ -332,7 +324,6 @@ class ConsoleApp( Frame ): self.graph.addBar( self.bw ) self.bw = 0 self.updates = 0 - # pylint: enable-msg=W0613 def setOutputHook( self, fn=None, consoles=None ): "Register fn as output hook [on specific consoles.]" diff --git a/examples/cpu.py b/examples/cpu.py new file mode 100755 index 0000000..6dfc936 --- /dev/null +++ b/examples/cpu.py @@ -0,0 +1,81 @@ +#!/usr/bin/python + +""" +cpu.py: test iperf bandwidth for varying cpu limits +""" + +from mininet.net import Mininet +from mininet.node import CPULimitedHost +from mininet.topolib import TreeTopo +from mininet.util import custom +from mininet.log import setLogLevel, output + +from time import sleep + +def waitListening(client, server, port): + "Wait until server is listening on port" + if not client.cmd('which telnet'): + raise Exception('Could not find telnet') + cmd = ('sh -c "echo A | telnet -e A %s %s"' % + (server.IP(), port)) + while 'Connected' not in client.cmd(cmd): + output('waiting for', server, + 'to listen on port', port, '\n') + sleep(.5) + + +def bwtest( cpuLimits, period_us=100000, seconds=5 ): + """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': + print '*** Testing with', sched, 'bandwidth limiting' + for cpu in cpuLimits: + host = custom( CPULimitedHost, sched=sched, + period_us=period_us, + cpu=cpu ) + net = Mininet( topo=topo, host=host ) + net.start() + net.pingAll() + hosts = [ net.getNodeByName( h ) for h in topo.hosts() ] + client, server = hosts[ 0 ], hosts[ -1 ] + server.cmd( 'iperf -s -p 5001 &' ) + waitListening( client, server, 5001 ) + result = client.cmd( 'iperf -yc -t %s -c %s' % ( + seconds, server.IP() ) ).split( ',' ) + bps = float( result[ -1 ] ) + server.cmdPrint( 'kill %iperf' ) + 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' + + print + print fmt % ( 'sched', 'cpu', 'client MB/s' ) + print + + for sched in sorted( results.keys() ): + entries = results[ sched ] + for cpu, bps in entries: + pct = '%.2f%%' % ( cpu * 100 ) + mbps = bps / 1e6 + print fmt % ( sched, pct, mbps ) + + +if __name__ == '__main__': + setLogLevel( 'info' ) + limits = [ .45, .4, .3, .2, .1 ] + out = bwtest( limits ) + dump( out ) diff --git a/examples/hwintf.py b/examples/hwintf.py index 71c72a3..d8d3fe2 100755 --- a/examples/hwintf.py +++ b/examples/hwintf.py @@ -21,7 +21,7 @@ def checkIntf( intf ): ips = re.findall( r'\d+\.\d+\.\d+\.\d+', quietRun( 'ifconfig ' + intf ) ) if ips: error( 'Error:', intf, 'has an IP address,' - 'and is probably in use!\n' ) + 'and is probably in use!\n' ) exit( 1 ) if __name__ == '__main__': diff --git a/examples/limit.py b/examples/limit.py new file mode 100755 index 0000000..0b23ca1 --- /dev/null +++ b/examples/limit.py @@ -0,0 +1,53 @@ +#!/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 +from mininet.log import setLogLevel + + +def testLinkLimit( net, bw ): + "Run bandwidth limit test" + print '*** Testing network %.2f Mbps bandwidth limit' % 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': + print '*** Testing with', sched, 'bandwidth limiting' + 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() diff --git a/examples/linearbandwidth.py b/examples/linearbandwidth.py index da14898..3fd06c7 100755 --- a/examples/linearbandwidth.py +++ b/examples/linearbandwidth.py @@ -6,9 +6,9 @@ using both kernel and user datapaths. We construct a network of N hosts and N-1 switches, connected as follows: -h1 <-> sN+1 <-> sN+2 .. sN+N-1 - | | | - h2 h3 hN +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 @@ -23,42 +23,40 @@ of switches, this example demonstrates: """ +from mininet.net import Mininet +from mininet.node import UserSwitch, OVSKernelSwitch +from mininet.topo import Topo +from mininet.log import lg +from mininet.util import irange + import sys flush = sys.stdout.flush -from mininet.net import init, Mininet -# from mininet.node import KernelSwitch -from mininet.node import UserSwitch, OVSKernelSwitch -from mininet.topo import Topo, Node -from mininet.log import lg - class LinearTestTopo( Topo ): "Topology for a string of N hosts and N-1 switches." - def __init__( self, N ): + def __init__( self, N, **params ): - # Add default members to class. - super( LinearTestTopo, self ).__init__() + # Initialize topology + Topo.__init__( self, **params ) - # Create switch and host nodes - hosts = range( 1, N + 1 ) - switches = range( N + 1 , N + N ) - for h in hosts: - self.add_node( h, Node( is_switch=False ) ) - for s in switches: - self.add_node( s, Node( is_switch=True ) ) + # 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 - for s in switches[ :-1 ]: - self.add_edge( s, s + 1 ) + last = None + for switch in switches: + if last: + self.addLink( last, switch ) + last = switch # Wire up hosts - self.add_edge( hosts[ 0 ], switches[ 0 ] ) - for h in hosts[ 1: ]: - self.add_edge( h, h + N - 1 ) - - # Consider all switches and hosts 'on' - self.enable_all() + self.addLink( hosts[ 0 ], switches[ 0 ] ) + for host, switch in zip( hosts[ 1: ], switches ): + self.addLink( host, switch ) def linearBandwidthTest( lengths ): @@ -69,15 +67,16 @@ def linearBandwidthTest( lengths ): switchCount = max( lengths ) hostCount = switchCount + 1 - switches = { # 'reference kernel': KernelSwitch, - 'reference user': UserSwitch, - 'Open vSwitch kernel': OVSKernelSwitch } + switches = { 'reference user': UserSwitch, + 'Open vSwitch kernel': OVSKernelSwitch } + + topo = LinearTestTopo( hostCount ) for datapath in switches.keys(): print "*** testing", datapath, "datapath" Switch = switches[ datapath ] results[ datapath ] = [] - net = Mininet( topo=LinearTestTopo( hostCount ), switch=Switch ) + net = Mininet( topo=topo, switch=Switch ) net.start() print "*** testing basic connectivity" for n in lengths: @@ -106,7 +105,6 @@ def linearBandwidthTest( lengths ): if __name__ == '__main__': lg.setLogLevel( 'info' ) - init() sizes = [ 1, 10, 20, 40, 60, 80, 100 ] print "*** Running linearBandwidthTest", sizes linearBandwidthTest( sizes ) diff --git a/examples/miniedit.py b/examples/miniedit.py index bd4908d..6653c3d 100755 --- a/examples/miniedit.py +++ b/examples/miniedit.py @@ -112,7 +112,7 @@ class MiniEdit( Frame ): appMenu = Menu( mbar, tearoff=False ) mbar.add_cascade( label=self.appName, font=font, menu=appMenu ) appMenu.add_command( label='About MiniEdit', command=self.about, - font=font) + font=font) appMenu.add_separator() appMenu.add_command( label='Quit', command=self.quit, font=font ) @@ -127,7 +127,7 @@ class MiniEdit( Frame ): 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 ) ) + command=lambda: self.deleteSelection( None ) ) runMenu = Menu( mbar, tearoff=False ) mbar.add_cascade( label="Run", font=font, menu=runMenu ) @@ -143,7 +143,7 @@ class MiniEdit( Frame ): f = Frame( self ) canvas = Canvas( f, width=self.cwidth, height=self.cheight, - bg=self.bg ) + bg=self.bg ) # Scroll bars xbar = Scrollbar( f, orient='horizontal', command=canvas.xview ) @@ -177,7 +177,7 @@ class MiniEdit( Frame ): bbox = self.canvas.bbox( 'all' ) if bbox is not None: self.canvas.configure( scrollregion=( 0, 0, bbox[ 2 ], - bbox[ 3 ] ) ) + bbox[ 3 ] ) ) def canvasx( self, x_root ): "Convert root x coordinate to canvas coordinate." @@ -223,7 +223,7 @@ class MiniEdit( Frame ): 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 ) + fg=color, command=doCmd ) b.pack( fill='x', side='bottom' ) return toolbar @@ -289,7 +289,7 @@ class MiniEdit( Frame ): def deleteItem( self, item ): "Delete an item." # Don't delete while network is running - if self.buttons[ 'Select' ][ 'state' ] == 'disabled' : + if self.buttons[ 'Select' ][ 'state' ] == 'disabled': return # Delete from model if item in self.links: @@ -299,19 +299,16 @@ class MiniEdit( Frame ): # Delete from view self.canvas.delete( item ) - # Callback ignores event - # pylint: disable-msg=W0613 - def deleteSelection( self, event ): + def deleteSelection( self, _event ): "Delete the selected item." if self.selection is not None: self.deleteItem( self.selection ) self.selectItem( None ) - # pylint: enable-msg=W0613 def nodeIcon( self, node, name ): "Create a new node icon." icon = Button( self.canvas, image=self.images[ node ], - text=name, compound='top' ) + text=name, compound='top' ) # Unfortunately bindtags wants a tuple bindtags = [ str( self.nodeBindings ) ] bindtags += list( icon.bindtags() ) @@ -325,8 +322,8 @@ class MiniEdit( Frame ): self.nodeCount += 1 name = self.nodePrefixes[ node ] + str( self.nodeCount ) icon = self.nodeIcon( node, name ) - item = self.canvas.create_window( x, y, anchor='c', - window=icon, tags=node ) + item = self.canvas.create_window( x, y, anchor='c', window=icon, + tags=node ) self.widgetToItem[ icon ] = item self.itemToWidget[ item ] = icon self.selectItem( item ) @@ -350,14 +347,11 @@ class MiniEdit( Frame ): c = self.canvas c.coords( self.link, self.linkx, self.linky, x, y ) - # Callback ignores event - # pylint: disable-msg=W0613 - def releaseLink( self, event ): + def releaseLink( 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 - # pylint: enable-msg=W0613 # Generic node handlers @@ -385,12 +379,9 @@ class MiniEdit( Frame ): "Select node on entry." self.selectNode( event ) - # Callback ignores event - # pylint: disable-msg=W0613 - def leaveNode( self, event ): + def leaveNode( self, _event ): "Restore old selection on exit." self.selectItem( self.lastSelection ) - # pylint: enable-msg=W0613 def clickNode( self, event ): "Node click handler." @@ -446,7 +437,7 @@ class MiniEdit( Frame ): 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' ) + fill='blue', tag='link' ) self.linkx, self.linky = x, y self.linkWidget = w self.linkItem = item @@ -454,23 +445,21 @@ class MiniEdit( Frame ): # Link bindings # Selection still needs a bit of work overall # Callbacks ignore event - # pylint: disable-msg=W0613 - def select( event, link=self.link ): + def select( _event, link=self.link ): "Select item on mouse entry." self.selectItem( link ) - def highlight( event, link=self.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 ): + def unhighlight( _event, link=self.link ): "Unhighlight item on mouse exit." self.canvas.itemconfig( link, fill='blue' ) # self.selectItem( None ) - # pylint: disable-msg=W0613 self.canvas.tag_bind( self.link, '', highlight ) self.canvas.tag_bind( self.link, '', unhighlight ) self.canvas.tag_bind( self.link, '', select ) @@ -486,7 +475,7 @@ class MiniEdit( Frame ): 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 ): + or dest in source.links or source in dest.links ): self.releaseLink( event ) return # For now, don't allow hosts to be directly linked diff --git a/examples/multiping.py b/examples/multiping.py new file mode 100755 index 0000000..3bd231c --- /dev/null +++ b/examples/multiping.py @@ -0,0 +1,86 @@ +#!/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.append( '10.0.0.200' ) + + targetips = ' '.join( targetips ) + + # BL: Not sure why loopback intf isn't up! + host.cmd( 'ifconfig lo up' ) + + # 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 ] + 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 ) diff --git a/examples/multipoll.py b/examples/multipoll.py new file mode 100755 index 0000000..aef1b10 --- /dev/null +++ b/examples/multipoll.py @@ -0,0 +1,81 @@ +#!/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() diff --git a/examples/popen.py b/examples/popen.py new file mode 100755 index 0000000..332822f --- /dev/null +++ b/examples/popen.py @@ -0,0 +1,36 @@ +#!/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 ) diff --git a/examples/popenpoll.py b/examples/popenpoll.py new file mode 100755 index 0000000..c581c27 --- /dev/null +++ b/examples/popenpoll.py @@ -0,0 +1,33 @@ +#!/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() diff --git a/examples/scratchnet.py b/examples/scratchnet.py index cdb1329..966a183 100755 --- a/examples/scratchnet.py +++ b/examples/scratchnet.py @@ -8,13 +8,16 @@ but it exposes the configuration details and allows customization. For most tasks, the higher-level API will be preferable. """ -from mininet.net import init -from mininet.node import Node, OVSKernelSwitch -from mininet.util import createLink +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 -def scratchNet( cname='controller', cargs='ptcp:' ): - "Create network from scratch using kernel switch." +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 ) @@ -23,36 +26,43 @@ def scratchNet( cname='controller', cargs='ptcp:' ): h1 = Node( 'h1' ) info( "*** Creating links\n" ) - createLink( node1=h0, node2=switch, port1=0, port2=0 ) - createLink( node1=h1, node2=switch, port1=0, port2=1 ) + Link( h0, switch ) + Link( h1, switch ) info( "*** Configuring hosts\n" ) - h0.setIP( h0.intfs[ 0 ], '192.168.123.1', 24 ) - h1.setIP( h1.intfs[ 0 ], '192.168.123.2', 24 ) + 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 kernel datapath\n" ) + info( "*** Starting network using Open vSwitch\n" ) controller.cmd( cname + ' ' + cargs + '&' ) - switch.cmd( 'ovs-dpctl del-dp dp0' ) - switch.cmd( 'ovs-dpctl add-dp dp0' ) + switch.cmd( 'ovs-vsctl del-br dp0' ) + switch.cmd( 'ovs-vsctl add-br dp0' ) for intf in switch.intfs.values(): - print switch.cmd( 'ovs-dpctl add-if dp0 ' + intf ) - print switch.cmd( 'ovs-openflowd dp0 tcp:127.0.0.1 &' ) + 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-dpctl del-dp dp0' ) - switch.cmd( 'kill %ovs-openflowd' ) + switch.cmd( 'ovs-vsctl del-br dp0' ) switch.deleteIntfs() info( '\n' ) if __name__ == '__main__': setLogLevel( 'info' ) info( '*** Scratch network demo (kernel datapath)\n' ) - OVSKernelSwitch.setup() - init() + Mininet.init() scratchNet() diff --git a/examples/scratchnetuser.py b/examples/scratchnetuser.py index ea053fa..ccd20e9 100755 --- a/examples/scratchnetuser.py +++ b/examples/scratchnetuser.py @@ -10,11 +10,16 @@ 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 init +from mininet.net import Mininet from mininet.node import Node -from mininet.util import createLink +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." @@ -28,17 +33,17 @@ def scratchNetUser( cname='controller', cargs='ptcp:' ): switch = Node( 's0') h0 = Node( 'h0' ) h1 = Node( 'h1' ) - cintf, sintf = createLink( controller, switch ) - h0intf, sintf1 = createLink( h0, switch ) - h1intf, sintf2 = createLink( h1, switch ) + cintf, sintf = linkIntfs( controller, switch ) + h0intf, sintf1 = linkIntfs( h0, switch ) + h1intf, sintf2 = linkIntfs( h1, switch ) info( '*** Configuring control network\n' ) - controller.setIP( cintf, '10.0.123.1', 24 ) - switch.setIP( sintf, '10.0.123.2', 24 ) + controller.setIP( '10.0.123.1/24', intf=cintf ) + switch.setIP( '10.0.123.2/24', intf=sintf) info( '*** Configuring hosts\n' ) - h0.setIP( h0intf, '192.168.123.1', 24 ) - h1.setIP( h1intf, '192.168.123.2', 24 ) + 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: @@ -47,7 +52,7 @@ def scratchNetUser( cname='controller', cargs='ptcp:' ): info( '*** Starting controller and user datapath\n' ) controller.cmd( cname + ' ' + cargs + '&' ) switch.cmd( 'ifconfig lo 127.0.0.1' ) - intfs = [ sintf1, sintf2 ] + intfs = [ str( i ) for i in sintf1, sintf2 ] switch.cmd( 'ofdatapath -i ' + ','.join( intfs ) + ' ptcp: &' ) switch.cmd( 'ofprotocol tcp:' + controller.IP() + ' tcp:localhost &' ) @@ -64,5 +69,5 @@ def scratchNetUser( cname='controller', cargs='ptcp:' ): if __name__ == '__main__': setLogLevel( 'info' ) info( '*** Scratch network demo (user datapath)\n' ) - init() + Mininet.init() scratchNetUser() diff --git a/examples/simpleperf.py b/examples/simpleperf.py new file mode 100755 index 0000000..1da4b66 --- /dev/null +++ b/examples/simpleperf.py @@ -0,0 +1,49 @@ +#!/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 + +class SingleSwitchTopo(Topo): + "Single switch connected to n hosts." + def __init__(self, n=2, **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) + # 10 Mbps, 5ms delay, 10% loss + self.addLink(host, switch, + bw=10, delay='5ms', loss=10, use_htb=True) + +def perfTest(): + "Create network and run simple performance test" + topo = SingleSwitchTopo(n=4) + net = Mininet(topo=topo, + host=CPULimitedHost, link=TCLink) + net.start() + print "Dumping host connections" + dumpNodeConnections(net.hosts) + print "Testing network connectivity" + net.pingAll() + print "Testing bandwidth between h1 and h4" + h1, h4 = net.getNodeByName('h1', 'h4') + net.iperf((h1, h4)) + net.stop() + +if __name__ == '__main__': + setLogLevel('info') + perfTest() diff --git a/examples/sshd.py b/examples/sshd.py index 9082d7d..2bedb9c 100755 --- a/examples/sshd.py +++ b/examples/sshd.py @@ -21,7 +21,7 @@ from mininet.cli import CLI from mininet.log import lg from mininet.node import Node, OVSKernelSwitch from mininet.topolib import TreeTopo -from mininet.util import createLink +from mininet.link import Link def TreeNet( depth=1, fanout=2, **kwargs ): "Convenience function for creating tree networks." @@ -37,13 +37,13 @@ def connectToRootNS( network, switch, ip, prefixLen, routes ): routes: host networks to route to""" # Create a node in root namespace and link to switch 0 root = Node( 'root', inNamespace=False ) - intf = createLink( root, switch )[ 0 ] - root.setIP( intf, ip, prefixLen ) + intf = Link( root, switch ).intf1 + root.setIP( ip, prefixLen, 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 ' + intf ) + root.cmd( 'route add -net ' + route + ' dev ' + str( intf ) ) def sshd( network, cmd='/usr/sbin/sshd', opts='-D' ): "Start a network, connect it to root ns, and run sshd on all hosts." diff --git a/examples/treeping64.py b/examples/treeping64.py index b0737da..ba60f1b 100755 --- a/examples/treeping64.py +++ b/examples/treeping64.py @@ -11,8 +11,8 @@ def treePing64(): results = {} switches = { # 'reference kernel': KernelSwitch, - 'reference user': UserSwitch, - 'Open vSwitch kernel': OVSKernelSwitch } + 'reference user': UserSwitch, + 'Open vSwitch kernel': OVSKernelSwitch } for name in switches: print "*** Testing", name, "datapath" diff --git a/mininet/clean.py b/mininet/clean.py index 3052e97..eac8fda 100755 --- a/mininet/clean.py +++ b/mininet/clean.py @@ -45,6 +45,12 @@ def cleanup(): if dp != '': sh( 'dpctl deldp ' + dp ) + info( "*** Removing OVS datapaths" ) + dps = sh("ovs-vsctl list-br").split( '\n' ) + for dp in dps: + if dp: + sh( 'ovs-vsctl del-br ' + dp ) + info( "*** Removing all links of the pattern foo-ethX\n" ) links = sh( "ip link show | egrep -o '(\w+-eth\w+)'" ).split( '\n' ) for link in links: diff --git a/mininet/cli.py b/mininet/cli.py index 5843e95..efb1808 100644 --- a/mininet/cli.py +++ b/mininet/cli.py @@ -30,10 +30,11 @@ from cmd import Cmd from os import isatty from select import poll, POLLIN import sys +import time from mininet.log import info, output, error from mininet.term import makeTerms -from mininet.util import quietRun, isShellBuiltin +from mininet.util import quietRun, isShellBuiltin, dumpNodeConnections class CLI( Cmd ): "Simple command-line interface to talk to nodes." @@ -46,6 +47,9 @@ class CLI( Cmd ): self.nodemap = {} # map names to Node objects for node in self.nodelist: self.nodemap[ node.name ] = node + # Local variable bindings for py command + self.locals = { 'net': mininet } + self.locals.update( self.nodemap ) # Attempt to handle input self.stdin = stdin self.inPoller = poll() @@ -77,7 +81,7 @@ class CLI( Cmd ): # Disable pylint "Unused argument: 'arg's'" messages, as well as # "method could be a function" warning, since each CLI function # must have the same interface - # pylint: disable-msg=W0613,R0201 + # pylint: disable-msg=R0201 helpStr = ( 'You may also send a command to a node using:\n' @@ -104,21 +108,14 @@ class CLI( Cmd ): if line is '': output( self.helpStr ) - def do_nodes( self, line ): + def do_nodes( self, _line ): "List all nodes." nodes = ' '.join( [ node.name for node in sorted( self.nodelist ) ] ) output( 'available nodes are: \n%s\n' % nodes ) - def do_net( self, line ): + def do_net( self, _line ): "List network connections." - for switch in self.mn.switches: - output( switch.name, '<->' ) - for intf in switch.intfs.values(): - # Ugly, but pylint wants it - name = switch.connection.get( intf, - ( None, 'Unknown ' ) )[ 1 ] - output( ' %s' % name ) - output( '\n' ) + dumpNodeConnections( self.nodelist ) def do_sh( self, line ): "Run an external shell command" @@ -131,7 +128,7 @@ class CLI( Cmd ): """Evaluate a Python expression. Node names may be used, e.g.: h1.cmd('ls')""" try: - result = eval( line, globals(), self.nodemap ) + result = eval( line, globals(), self.locals ) if not result: return elif isinstance( result, str ): @@ -143,14 +140,22 @@ class CLI( Cmd ): # pylint: enable-msg=W0703 - def do_pingall( self, line ): + def do_pingall( self, _line ): "Ping between all hosts." self.mn.pingAll() - def do_pingpair( self, line ): + def do_pingpair( self, _line ): "Ping between first two hosts, useful for testing." self.mn.pingPair() + def do_pingallfull( self, _line ): + "Ping between first two 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." args = line.split() @@ -191,16 +196,16 @@ class CLI( Cmd ): error( 'invalid number of args: iperfudp bw src dst\n' + 'bw examples: 10M\n' ) - def do_intfs( self, line ): + def do_intfs( self, _line ): "List interfaces." for node in self.nodelist: output( '%s: %s\n' % - ( node.name, ' '.join( sorted( node.intfs.values() ) ) ) ) + ( node.name, ','.join( node.intfNames() ) ) ) - def do_dump( self, line ): + def do_dump( self, _line ): "Dump node info." for node in self.nodelist: - output( '%s\n' % node ) + output( '%s\n' % repr( node ) ) def do_link( self, line ): "Bring link(s) between two nodes up or down." @@ -229,7 +234,7 @@ class CLI( Cmd ): "Spawn gnome-terminal(s) for the given node(s)." self.do_xterm( line, term='gterm' ) - def do_exit( self, line ): + def do_exit( self, _line ): "Exit" return 'exited by user command' @@ -275,16 +280,19 @@ class CLI( Cmd ): def do_dpctl( self, line ): "Run dpctl command on all switches." args = line.split() - if len(args) == 0: + if len(args) < 1: error( 'usage: dpctl command [arg1] [arg2] ...\n' ) return - if not self.mn.listenPort: - error( "can't run dpctl w/no passive listening port\n") - return for sw in self.mn.switches: output( '*** ' + sw.name + ' ' + ('-' * 72) + '\n' ) - output( sw.cmd( 'dpctl ' + ' '.join(args) + - ' tcp:127.0.0.1:%i' % sw.listenPort ) ) + 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 default( self, line ): """Called on an input line when the command prefix is not recognized. @@ -293,6 +301,8 @@ class CLI( Cmd ): corresponding IP addrs.""" first, args, line = self.parseline( line ) + if not args: + return if args and len(args) > 0 and args[ -1 ] == '\n': args = args[ :-1 ] rest = args.split( ' ' ) @@ -301,8 +311,8 @@ class CLI( Cmd ): node = self.nodemap[ first ] # Substitute IP addresses for node names in command rest = [ self.nodemap[ arg ].IP() - if arg in self.nodemap else arg - for arg in rest ] + if arg in self.nodemap else arg + for arg in rest ] rest = ' '.join( rest ) # Run cmd on node: builtin = isShellBuiltin( first ) @@ -311,7 +321,7 @@ class CLI( Cmd ): else: error( '*** Unknown command: %s\n' % first ) - # pylint: enable-msg=W0613,R0201 + # pylint: enable-msg=R0201 def waitForNode( self, node ): "Wait for a node to finish, and print its output." diff --git a/mininet/link.py b/mininet/link.py new file mode 100644 index 0000000..21e18ba --- /dev/null +++ b/mininet/link.py @@ -0,0 +1,397 @@ +""" +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 +from time import sleep +import re + +class Intf( object ): + + "Basic interface object that can configure itself." + + def __init__( self, name, node=None, port=None, link=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, self.ip, self.prefixLen = None, None, None + # Add to node (and move ourselves if necessary ) + 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: + 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" + ifconfig = self.ifconfig() + 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 + + 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: + self.ifconfig( 'up' ) + 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 type( value ) is list: + result = f( *value ) + elif type( value ) is 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 ) + self.updateIP() + self.updateMAC() + return r + + def delete( self ): + "Delete interface" + self.cmd( 'ip link del ' + self.name ) + # Does it help to sleep to let things run? + sleep( 0.001 ) + + 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""" + + 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 > 1000 ): + error( 'Bandwidth', bw, 'is outside range 0..1000 Mbps\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 1:0 hfsc default 1', + '%s class add dev %s parent 1:0 classid 1: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 1: tbf ' + + 'rate %fMbit burst 15000 latency %fms' % + ( bw, latency_ms ) ] + else: + cmds += [ '%s qdisc add dev %s root handle 1:0 htb default 1', + '%s class add dev %s parent 1:0 classid 1:1 htb ' + + 'rate %fMbit burst 15k' % bw ] + parent = ' parent 1:1 ' + + # ECN or RED + if enable_ecn: + cmds += [ '%s qdisc add dev %s' + parent + + 'handle 10: red limit 1000000 ' + + 'min 30000 max 35000 avpkt 1500 ' + + 'burst 20 ' + + 'bandwidth %fmbit probability 1 ecn' % bw ] + parent = ' parent 10: ' + elif enable_red: + cmds += [ '%s qdisc add dev %s' + parent + + 'handle 10: red limit 1000000 ' + + 'min 30000 max 35000 avpkt 1500 ' + + 'burst 20 ' + + 'bandwidth %fmbit probability 1' % bw ] + parent = ' parent 10: ' + 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 ] + return cmds + + 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, + disable_gro=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." + + result = Intf.config( self, **params) + + # Disable GRO + if disable_gro: + self.cmd( 'ethtool -K %s gro off' % self ) + + # 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 + cmds = [ '%s qdisc del dev %s root' ] + + # 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 + cmds += self.delayCmds( delay=delay, jitter=jitter, loss=loss, + max_queue_size=max_queue_size, + parent=parent ) + + # 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 ] + debug( "cmds:", cmds, '\n' ) + debug( "outputs:", tcoutputs, '\n' ) + result[ 'tcoutputs'] = tcoutputs + + return result + + +class Link( object ): + + """A basic link is just a veth pair. + Other types of links could be tunnels, link emulators, etc..""" + + def __init__( self, node1, node2, port1=None, port2=None, + intfName1=None, intfName2=None, + intf=Intf, cls1=None, cls2=None, params1=None, + params2=None ): + """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 would be more orthogonal, but being able to specify + # in-line arguments is more convenient! + if port1 is None: + port1 = node1.newPort() + if port2 is None: + port2 = node2.newPort() + if not intfName1: + intfName1 = self.intfName( node1, port1 ) + if not intfName2: + intfName2 = self.intfName( node2, port2 ) + + self.makeIntfPair( intfName1, intfName2 ) + + if not cls1: + cls1 = intf + if not cls2: + cls2 = intf + if not params1: + params1 = {} + if not params2: + params2 = {} + + intf1 = cls1( name=intfName1, node=node1, port=port1, + link=self, **params1 ) + intf2 = cls2( name=intfName2, node=node2, port=port2, + link=self, **params2 ) + + # All we are is dust in the wind, and our two interfaces + self.intf1, self.intf2 = intf1, intf2 + + @classmethod + def intfName( cls, node, n ): + "Construct a canonical interface name node-ethN for interface n." + return node.name + '-eth' + repr( n ) + + @classmethod + def makeIntfPair( cls, intf1, intf2 ): + """Create pair of interfaces + intf1: name of interface 1 + intf2: name of interface 2 + (override this class method [and possibly delete()] + to change link type)""" + makeIntfPair( intf1, intf2 ) + + def delete( self ): + "Delete this link" + self.intf1.delete() + self.intf2.delete() + + def __str__( self ): + return '%s<->%s' % ( self.intf1, self.intf2 ) + +class TCLink( Link ): + "Link with symmetric TC interfaces configured via opts" + def __init__( self, node1, node2, port1=None, port2=None, + intfName1=None, intfName2=None, **params ): + Link.__init__( self, node1, node2, port1=port1, port2=port2, + intfName1=intfName1, intfName2=intfName2, + cls1=TCIntf, + cls2=TCIntf, + params1=params, + params2=params) diff --git a/mininet/log.py b/mininet/log.py index 50d8cf7..cd00821 100644 --- a/mininet/log.py +++ b/mininet/log.py @@ -11,11 +11,11 @@ import types OUTPUT = 25 LEVELS = { 'debug': logging.DEBUG, - 'info': logging.INFO, - 'output': OUTPUT, - 'warning': logging.WARNING, - 'error': logging.ERROR, - 'critical': logging.CRITICAL } + '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 @@ -117,7 +117,7 @@ class MininetLogger( Logger, object ): Convenience function to support lowercase names. levelName: level name from LEVELS""" level = LOGLEVELDEFAULT - if levelname != None: + if levelname is not None: if levelname not in LEVELS: raise Exception( 'unknown levelname seen in setLogLevel' ) else: diff --git a/mininet/moduledeps.py b/mininet/moduledeps.py index 15b575d..862c1f6 100644 --- a/mininet/moduledeps.py +++ b/mininet/moduledeps.py @@ -19,7 +19,7 @@ def modprobe( mod ): return quietRun( [ 'modprobe', mod ] ) OF_KMOD = 'ofdatapath' -OVS_KMOD = 'openvswitch_mod' +OVS_KMOD = 'openvswitch_mod' # Renamed 'openvswitch' in OVS 1.7+/Linux 3.5+ TUN = 'tun' def moduleDeps( subtract=None, add=None ): @@ -48,8 +48,8 @@ def moduleDeps( subtract=None, add=None ): modprobeOutput = modprobe( mod ) if modprobeOutput: error( 'Error inserting ' + mod + - ' - is it installed and available via modprobe?\n' + - 'Error was: "%s"\n' % modprobeOutput ) + ' - 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 ) @@ -63,6 +63,6 @@ def pathCheck( *args, **kwargs ): 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' ] ) + 'Please make sure that %s is installed ' % moduleName + + 'and available in your $PATH:\n(%s)\n' % environ[ 'PATH' ] ) exit( 1 ) diff --git a/mininet/net.py b/mininet/net.py index 973a694..066751c 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -1,6 +1,6 @@ """ - Mininet: A simple networking testbed for OpenFlow! + Mininet: A simple networking testbed for OpenFlow/SDN! author: Bob Lantz (rlantz@cs.stanford.edu) author: Brandon Heller (brandonh@stanford.edu) @@ -94,225 +94,231 @@ from time import sleep from mininet.cli import CLI from mininet.log import info, error, debug, output -from mininet.node import Host, UserSwitch, OVSKernelSwitch, Controller -from mininet.node import ControllerParams -from mininet.util import quietRun, fixLimits -from mininet.util import createLink, macColonHex, ipStr, ipParse +from mininet.node import Host, OVSKernelSwitch, Controller +from mininet.link import Link, Intf +from mininet.util import quietRun, fixLimits, numCores, ensureRoot +from mininet.util import macColonHex, ipStr, ipParse, netParse, ipAdd from mininet.term import cleanUpScreens, makeTerms +# Mininet version: should be consistent with README and LICENSE +VERSION = "2.0.0rc1" + class Mininet( object ): "Network emulation with hosts spawned in network namespaces." def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host, - controller=Controller, - cparams=ControllerParams( '10.0.0.0', 8 ), - build=True, xterms=False, cleanup=False, - inNamespace=False, - autoSetMacs=False, autoStaticArp=False, listenPort=None ): + controller=Controller, 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 ): """Create Mininet object. topo: Topo (topology) object or None - switch: Switch class - host: Host class - controller: Controller class - cparams: ControllerParams object + 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 from topo? + 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.cparams = cparams - self.topo = topo + 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.hosts = [] self.switches = [] self.controllers = [] + self.nameToNode = {} # name to Node (Host/Switch) objects - self.idToNode = {} # dpid to Node (Host/Switch) objects - self.dps = 0 # number of created kernel datapaths + self.terms = [] # list of spawned xterm processes - init() - switch.setup() + Mininet.init() # Initialize Mininet if necessary self.built = False if topo and build: self.build() - def addHost( self, name, mac=None, ip=None ): + def addHost( self, name, cls=None, **params ): """Add host. name: name of host to add - mac: default MAC address for intf 0 - ip: default IP address for intf 0 + cls: custom host class/constructor (optional) + params: parameters for host returns: added host""" - host = self.host( name, defaultMAC=mac, defaultIP=ip ) - self.hosts.append( host ) - self.nameToNode[ name ] = host - return 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, mac=None, ip=None ): + def addSwitch( self, name, cls=None, **params ): """Add switch. name: name of switch to add - mac: default MAC address for kernel/OVS switch intf 0 + cls: custom switch class/constructor (optional) returns: added switch - side effect: increments the listenPort member variable.""" - if self.switch == UserSwitch: - sw = self.switch( name, listenPort=self.listenPort, - defaultMAC=mac, defaultIP=ip, inNamespace=self.inNamespace ) - else: - sw = self.switch( name, listenPort=self.listenPort, - defaultMAC=mac, defaultIP=ip, dp=self.dps, - inNamespace=self.inNamespace ) + 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.dps += 1 self.switches.append( sw ) self.nameToNode[ name ] = sw return sw - def addController( self, name='c0', controller=None, **kwargs ): + def addController( self, name='c0', controller=None, **params ): """Add controller. controller: Controller class""" if not controller: controller = self.controller - controller_new = controller( name, **kwargs ) + controller_new = controller( name, **params ) if controller_new: # allow controller-less setups self.controllers.append( controller_new ) self.nameToNode[ name ] = controller_new return controller_new - # Control network support: - # - # Create an explicit control network. Currently this is only - # used by the user datapath configuration. - # - # Notes: - # - # 1. If the controller and switches are in the same (e.g. root) - # namespace, they can just use the loopback connection. - # - # 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!) + # BL: is this better than just using nameToNode[] ? + # Should it have a better name? + 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 configureControlNetwork( self ): - "Configure control network." - self.configureRoutedControlNetwork() + def get( self, *args ): + "Convenience alias for getNodeByName" + return self.getNodeByName( *args ) - # 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 ) - sintf, cintf = createLink( switch, controller ) - snum += 1 - while snum & 0xff in [ 0, 255 ]: - snum += 1 - sip = ipStr( snum ) - controller.setIP( cintf, cip, prefixLen ) - switch.setIP( sintf, sip, prefixLen ) - controller.setHostRoute( sip, cintf ) - switch.setHostRoute( cip, sintf ) - info( '\n' ) - info( '*** Testing control network\n' ) - while not controller.intfIsUp( cintf ): - info( '*** Waiting for', cintf, 'to come up\n' ) - sleep( 1 ) - for switch in self.switches: - while not switch.intfIsUp( sintf ): - 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' ) + def addLink( self, node1, node2, port1=None, port2=None, + cls=None, **params ): + """"Add a link from node1 to node2 + node1: source node + node2: dest node + port1: source port + port2: dest port + returns: link object""" + defaults = { 'port1': port1, + 'port2': port2, + 'intf': self.intf } + defaults.update( params ) + if not cls: + cls = self.link + return cls( node1, node2, **defaults ) def configHosts( self ): "Configure a set of hosts." - # params were: hosts, ips for host in self.hosts: - hintf = host.intfs[ 0 ] - host.setIP( hintf, host.defaultIP, self.cparams.prefixLen ) - host.setDefaultRoute( hintf ) - # You're low priority, dude! - quietRun( 'renice +18 -p ' + repr( host.pid ) ) info( host.name + ' ' ) + intf = host.defaultIntf() + if intf: + host.configDefault( defaultRoute=intf ) + 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. + host.cmd( 'ifconfig lo up' ) info( '\n' ) - def buildFromTopo( self, topo ): + def buildFromTopo( self, topo=None ): """Build mininet from a topology object At the end of this function, everything should be connected and up.""" - def addNode( prefix, addMethod, nodeId ): - "Add a host or a switch." - name = prefix + topo.name( nodeId ) - mac = macColonHex( nodeId ) if self.setMacs else None - ip = topo.ip( nodeId ) - node = addMethod( name, mac=mac, ip=ip ) - self.idToNode[ nodeId ] = node - info( name + ' ' ) - # Possibly we should clean up here and/or validate # the topo if self.cleanup: pass - info( '*** Adding controller\n' ) - self.addController( 'c0' ) info( '*** Creating network\n' ) + + if not self.controllers: + # Add a default controller + info( '*** Adding controller\n' ) + classes = self.controller + if type( classes ) is not list: + classes = [ classes ] + for i, cls in enumerate( classes ): + self.addController( 'c%d' % i, cls ) + info( '*** Adding hosts:\n' ) - for hostId in sorted( topo.hosts() ): - addNode( 'h', self.addHost, hostId ) + for hostName in topo.hosts(): + self.addHost( hostName, **topo.nodeInfo( hostName ) ) + info( hostName + ' ' ) + info( '\n*** Adding switches:\n' ) - for switchId in sorted( topo.switches() ): - addNode( 's', self.addSwitch, switchId ) + for switchName in topo.switches(): + self.addSwitch( switchName, **topo.nodeInfo( switchName) ) + info( switchName + ' ' ) + info( '\n*** Adding links:\n' ) - for srcId, dstId in sorted( topo.edges() ): - src, dst = self.idToNode[ srcId ], self.idToNode[ dstId ] - srcPort, dstPort = topo.port( srcId, dstId ) - createLink( src, dst, srcPort, dstPort ) + for srcName, dstName in topo.links(sort=True): + src, dst = self.nameToNode[ srcName ], self.nameToNode[ dstName ] + params = topo.linkInfo( srcName, dstName ) + srcPort, dstPort = topo.port( srcName, dstName ) + self.addLink( src, dst, srcPort, dstPort, **params ) info( '(%s, %s) ' % ( src.name, dst.name ) ) + 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: - info( '*** Configuring control network\n' ) + if ( self.inNamespace ): self.configureControlNetwork() info( '*** Configuring hosts\n' ) self.configHosts() if self.xterms: self.startTerms() - if self.autoSetMacs: - self.setMacs() if self.autoStaticArp: self.staticArp() self.built = True @@ -327,17 +333,10 @@ class Mininet( object ): def stopXterms( self ): "Kill each xterm." - # Kill xterms for term in self.terms: os.kill( term.pid, signal.SIGKILL ) cleanUpScreens() - def setMacs( self ): - """Set MAC addrs to correspond to default MACs on hosts. - Assume that the host only has one interface.""" - for host in self.hosts: - host.setMAC( host.intfs[ 0 ], host.defaultMAC ) - def staticArp( self ): "Add all-pairs ARP entries to remove the need to handle broadcast." for src in self.hosts: @@ -365,18 +364,19 @@ class Mininet( object ): self.stopXterms() info( '*** Stopping %i hosts\n' % len( self.hosts ) ) for host in self.hosts: - info( '%s ' % host.name ) + info( host.name + ' ' ) host.terminate() info( '\n' ) info( '*** Stopping %i switches\n' % len( self.switches ) ) for switch in self.switches: - info( switch.name ) + info( switch.name + ' ' ) switch.stop() info( '\n' ) info( '*** Stopping %i controllers\n' % len( self.controllers ) ) for controller in self.controllers: + info( controller.name + ' ' ) controller.stop() - info( '*** Done\n' ) + info( '\n*** Done\n' ) def run( self, test, *args, **kwargs ): "Perform a complete start/test/stop cycle." @@ -410,6 +410,9 @@ class Mininet( object ): 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." @@ -418,16 +421,17 @@ class Mininet( object ): return (1, 0) r = r'(\d+) packets transmitted, (\d+) received' m = re.search( r, pingOutput ) - if m == None: + if m is None: error( '*** Error: could not parse ping output: %s\n' % - pingOutput ) + pingOutput ) return (1, 0) sent, received = int( m.group( 1 ) ), int( m.group( 2 ) ) return sent, received - def ping( self, hosts=None ): + 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 @@ -440,7 +444,10 @@ class Mininet( object ): output( '%s -> ' % node.name ) for dest in hosts: if node != dest: - result = node.cmd( 'ping -c1 ' + dest.IP() ) + opts = '' + if timeout: + opts = '-W %s' % timeout + result = node.cmd( 'ping -c1 %s %s' % (opts, dest.IP()) ) sent, received = self._parsePing( result ) packets += sent if received > sent: @@ -456,6 +463,61 @@ class Mininet( object ): ( ploss, lost, packets ) ) return ploss + @staticmethod + def _parsePingFull( pingOutput ): + "Parse ping output and return all data." + # 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, 0, 0, 0, 0) + 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 ) + 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 ): """Ping between all hosts. returns: ploss packet loss percentage""" @@ -467,6 +529,17 @@ class Mininet( object ): 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. @@ -481,6 +554,8 @@ class Mininet( object ): error( 'could not parse iperf output: ' + iperfOutput ) return '' + # XXX This should be cleaned up + def iperf( self, hosts=None, l4Type='TCP', udpBw='10M' ): """Run iperf between two hosts. hosts: list of hosts; if None, uses opposite hosts @@ -508,12 +583,13 @@ class Mininet( object ): servout = '' while server.lastPid is None: servout += server.monitor() - while 'Connected' not in client.cmd( - 'sh -c "echo A | telnet -e A %s 5001"' % server.IP()): - output('waiting for iperf to start up') - sleep(.5) + if l4Type == 'TCP': + while 'Connected' not in client.cmd( + 'sh -c "echo A | telnet -e A %s 5001"' % server.IP()): + output('waiting for iperf to start up...') + sleep(.5) cliout = client.cmd( iperfArgs + '-t 5 -c ' + server.IP() + ' ' + - bwArgs ) + bwArgs ) debug( 'Client output: %s\n' % cliout ) server.sendInt() servout += server.waitOutput() @@ -524,6 +600,44 @@ class Mininet( object ): 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 + returns a single list of measured CPU fractions as floats. + """ + pct = cpu * 100 + info('*** Testing CPU %.0f%% bandwidth limit\n' % pct) + hosts = self.hosts + for h in hosts: + h.cmd( 'while true; do a=1; done &' ) + pids = [h.cmd( 'echo $!' ).strip() for h in hosts] + pids_str = ",".join(["%s" % pid for pid in pids]) + cmd = 'ps -p %s -o pid,%%cpu,args' % pids_str + # It's a shame that this is what pylint prefers + outputs = [] + for _ in range( duration ): + sleep( 1 ) + outputs.append( quietRun( cmd ).strip() ) + for h in hosts: + h.cmd( 'kill %1' ) + cpu_fractions = [] + for test_output in outputs: + # Split by line. Ignore first line, which looks like this: + # PID %CPU COMMAND\n + for line in test_output.split('\n')[1:]: + r = r'\d+ (\d+\.\d+)' + m = re.search( r, line ) + if m is None: + error( '*** Error: could not extract CPU fraction: %s\n' % + line ) + return None + cpu_fractions.append( float( m.group( 1 ) ) ) + 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 @@ -534,15 +648,18 @@ class Mininet( object ): elif dst not in self.nameToNode: error( 'dst not in network: %s\n' % dst ) else: - srcNode, dstNode = self.nameToNode[ src ], self.nameToNode[ dst ] - connections = srcNode.connectionsTo( dstNode ) + if type( src ) is str: + src = self.nameToNode[ src ] + if type( dst ) is str: + 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 = srcNode.cmd( 'ifconfig', srcIntf, status ) + result = srcIntf.ifconfig( status ) if result: error( 'link src status change failed: %s\n' % result ) - result = dstNode.cmd( 'ifconfig', dstIntf, status ) + result = dstIntf.ifconfig( status ) if result: error( 'link dst status change failed: %s\n' % result ) @@ -553,26 +670,84 @@ class Mininet( object ): self.stop() return result + inited = False -# pylint thinks inited is unused -# pylint: disable-msg=W0612 + @classmethod + def init( cls ): + "Initialize Mininet" + if cls.inited: + return + ensureRoot() + fixLimits() + cls.inited = True -def init(): - "Initialize Mininet." - if init.inited: - return - if os.getuid() != 0: - # Note: this script must be run as root - # Perhaps we should do so automatically! - print "*** Mininet must run as root." - exit( 1 ) - # If which produces no output, then mnexec is not in the path. - # May want to loosen this to handle mnexec in the current dir. - if not quietRun( 'which mnexec' ): - raise Exception( "Could not find mnexec - check $PATH" ) - fixLimits() - init.inited = True -init.inited = False +class MininetWithControlNet( Mininet ): -# pylint: enable-msg=W0612 + """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' ) diff --git a/mininet/node.py b/mininet/node.py index 8ebb676..48ef1af 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -13,6 +13,9 @@ Host: a virtual host. By default, a host is simply a shell; commands monitor(). Examples of how to run experiments using this functionality are provided in the examples/ directory. +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 @@ -46,39 +49,78 @@ import re import signal import select from subprocess import Popen, PIPE, STDOUT -from time import sleep -from mininet.log import info, error, debug -from mininet.util import quietRun, makeIntfPair, moveIntf, isShellBuiltin +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, OVS_KMOD, OF_KMOD, TUN - -SWITCH_PORT_BASE = 1 # For OF > 0.9, switch ports start at 1 rather than zero +from mininet.link import Link, Intf, TCIntf 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? + params: Node parameters (see config() for details)""" + + # Make sure class actually works + self.checkSetup() + + self.name = name + self.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() + + # File descriptor to node mapping support + # Class variables and methods + inToNode = {} # mapping of input fds to nodes outToNode = {} # mapping of output fds to nodes - portBase = 0 # Nodes always start with eth0/port0, even in OF 1.0 + @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 ) - def __init__( self, name, inNamespace=True, - defaultMAC=None, defaultIP=None, **kwargs ): - """name: name of node - inNamespace: in network namespace? - defaultMAC: default MAC address for intf 0 - defaultIP: default IP address for intf 0""" - self.name = name - self.inNamespace = inNamespace - self.defaultIP = defaultIP - self.defaultMAC = defaultMAC + # Command support via shell process in namespace + + def startShell( self ): + "Start a shell process for running commands" + if self.shell: + error( "%s: shell is already running" ) + return + # mnexec: (c)lose descriptors, (d)etach from tty, + # (p)rint pid, and run in (n)amespace opts = '-cdp' if self.inNamespace: opts += 'n' + # bash -m: enable job control cmd = [ 'mnexec', opts, 'bash', '-m' ] self.shell = Popen( cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT, - close_fds=False ) + close_fds=True ) self.stdin = self.shell.stdin self.stdout = self.shell.stdout self.pid = self.shell.pid @@ -89,33 +131,22 @@ class Node( object ): # using select.poll() self.outToNode[ self.stdout.fileno() ] = self self.inToNode[ self.stdin.fileno() ] = self - self.intfs = {} # dict of port numbers to interface names - self.ports = {} # dict of interface names to port numbers - # replace with Port objects, eventually ? - self.ips = {} # dict of interfaces to ip addresses as strings - self.macs = {} # dict of interfacesto mac addresses as strings - self.connection = {} # remote node connected to each interface self.execed = False self.lastCmd = None self.lastPid = None self.readbuf = '' self.waiting = False - # Stash additional information as desired - self.args = kwargs - - @classmethod - def fdToNode( cls, fd ): - """Return node corresponding to given file descriptor. - fd: file descriptor - returns: node""" - node = Node.outToNode.get( fd ) - return node or Node.inToNode.get( fd ) def cleanup( self ): "Help python collect its garbage." + if not self.inNamespace: + 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, non-blocking. maxbytes: maximum number of bytes to return""" @@ -138,7 +169,7 @@ class Node( object ): if '\n' not in self.readbuf: return None pos = self.readbuf.find( '\n' ) - line = self.readbuf[ 0 : pos ] + line = self.readbuf[ 0: pos ] self.readbuf = self.readbuf[ pos + 1: ] return line @@ -169,22 +200,29 @@ class Node( object ): printPid: print command's PID?""" assert not self.waiting printPid = kwargs.get( 'printPid', True ) - if len( args ) > 0: + # Allow sendCmd( [ list ] ) + if len( args ) == 1 and type( args[ 0 ] ) is 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( cmd ) + 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 + printPid = printPid and not isShellBuiltin( cmd ) if len( cmd ) > 0 and cmd[ -1 ] == '&': - separator = '&' - cmd = cmd[ :-1 ] + # print ^A{pid}\n{sentinel} + cmd += ' printf "\\001%d\n\\177" $! \n' else: - separator = ';' + # print sentinel + cmd += '; printf "\\177"' if printPid and not isShellBuiltin( cmd ): cmd = 'mnexec -p ' + cmd - self.write( cmd + separator + ' printf "\\177" \n' ) - self.lastCmd = cmd + self.write( cmd + '\n' ) self.lastPid = None self.waiting = True @@ -246,6 +284,43 @@ class Node( object ): 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', '-a', str( self.pid ) ] } + defaults.update( kwargs ) + if len( args ) == 1: + if type( args[ 0 ] ) is list: + # popen([cmd, arg1, arg2...]) + cmd = args[ 0 ] + elif type( args[ 0 ] ) is str: + # 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 + mncmd = defaults[ 'mncmd' ] + del defaults[ 'mncmd' ] + cmd = mncmd + cmd + # Shell requires a string, not a list! + if defaults.get( 'shell', False ): + cmd = ' '.join( cmd ) + return Popen( cmd, **defaults ) + + def pexec( self, *args, **kwargs ): + """Execute a command using popen + returns: out, err, exitcode""" + popen = self.popen( *args, **kwargs) + 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. @@ -254,10 +329,6 @@ class Node( object ): # the real interfaces are created as veth pairs, so we can't # make a single interface at a time. - def intfName( self, n ): - "Construct a canonical interface name node-ethN for interface n." - return self.name + '-eth' + repr( n ) - def newPort( self ): "Return the next port number to allocate." if len( self.ports ) > 0: @@ -266,57 +337,58 @@ class Node( object ): def addIntf( self, intf, port=None ): """Add an interface. - intf: interface name (e.g. nodeN-ethM) + intf: interface port: port number (optional, typically OpenFlow port number)""" if port is None: port = self.newPort() self.intfs[ port ] = intf self.ports[ intf ] = port - #info( '\n' ) - #info( 'added intf %s:%d to node %s\n' % ( intf,port, self.name ) ) + self.nameToIntf[ intf.name ] = intf + debug( '\n' ) + debug( 'added intf %s:%d to node %s\n' % ( intf, port, self.name ) ) if self.inNamespace: - #info( 'moving w/inNamespace set\n' ) - moveIntf( intf, self ) + debug( 'moving', intf, 'into namespace for', self.name, '\n' ) + moveIntf( intf.name, self ) - def registerIntf( self, intf, dstNode, dstIntf ): - "Register connection of intf to dstIntf on dstNode." - self.connection[ intf ] = ( dstNode, dstIntf ) + 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='' ): + """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 type( intf) is str: + return self.nameToIntf[ intf ] + else: + return intf def connectionsTo( self, node): - "Return [(srcIntf, dstIntf)..] for connections to dstNode." + "Return [ intf1, intf2... ] for all intfs that connect self to node." # We could optimize this if it is important connections = [] - for intf in self.connection.keys(): - dstNode, dstIntf = self.connection[ intf ] - if dstNode == node: - connections.append( ( intf, dstIntf ) ) + 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 - # This is a symmetric operation, but it makes sense to put - # the code here since it is tightly coupled to routines in - # this class. For a more symmetric API, you can use - # mininet.util.createLink() - - def linkTo( self, node2, port1=None, port2=None ): - """Create link to another node, making two new interfaces. - node2: Node to link us to - port1: our port number (optional) - port2: node2 port number (optional) - returns: intf1 name, intf2 name""" - node1 = self - if port1 is None: - port1 = node1.newPort() - if port2 is None: - port2 = node2.newPort() - intf1 = node1.intfName( port1 ) - intf2 = node2.intfName( port2 ) - makeIntfPair( intf1, intf2 ) - node1.addIntf( intf1, port1 ) - node2.addIntf( intf2, port2 ) - node1.registerIntf( intf1, node2, intf2 ) - node2.registerIntf( intf2, node1, intf1 ) - return intf1, intf2 - def deleteIntfs( self ): "Delete all of our interfaces." # In theory the interfaces should go away after we shut down. @@ -325,18 +397,10 @@ class Node( object ): # 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(): - quietRun( 'ip link del ' + intf ) + intf.delete() info( '.' ) - # Does it help to sleep to let things run? - sleep( 0.001 ) - def setMAC( self, intf, mac ): - """Set the MAC address for an interface. - mac: MAC address as string""" - result = self.cmd( 'ifconfig', intf, 'down' ) - result += self.cmd( 'ifconfig', intf, 'hw', 'ether', mac ) - result += self.cmd( 'ifconfig', intf, 'up' ) - return result + # Routing support def setARP( self, ip, mac ): """Add an ARP entry. @@ -345,107 +409,366 @@ class Node( object ): result = self.cmd( 'arp', '-s', ip, mac ) return result - def setIP( self, intf, ip, prefixLen=8 ): - """Set the IP address for an interface. - intf: interface name - ip: IP address as a string - prefixLen: prefix length, e.g. 8 for /8 or 16M addrs""" - ipSub = '%s/%d' % ( ip, prefixLen ) - result = self.cmd( 'ifconfig', intf, ipSub, 'up' ) - self.ips[ intf ] = ip - 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 ) + return self.cmd( 'route add -host', ip, 'dev', intf ) - def setDefaultRoute( self, intf ): + def setDefaultRoute( self, intf=None ): """Set the default route to go through intf. intf: string, interface name""" + if not intf: + intf = self.defaultIntf() self.cmd( 'ip route flush root 0/0' ) - return self.cmd( 'route add default ' + intf ) + return self.cmd( 'route add default %s' % intf ) - def defaultIntf( self ): - "Return interface for lowest port" - ports = self.intfs.keys() - if ports: - return self.intfs[ min( ports ) ] + # Convenience and configuration methods - _ipMatchRegex = re.compile( r'\d+\.\d+\.\d+\.\d+' ) - _macMatchRegex = re.compile( r'..:..:..:..:..:..' ) + 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 ): + """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""" + # This should probably be rethought + if '/' not in ip: + ip = '%s/%s' % ( ip, prefixLen ) + return self.intf( intf ).setIP( ip ) def IP( self, intf=None ): "Return IP address of a node or specific interface." - if intf is None: - intf = self.defaultIntf() - if intf and not self.waiting: - self.updateIP( intf ) - return self.ips.get( intf, None ) + return self.intf( intf ).IP() def MAC( self, intf=None ): "Return MAC address of a node or specific interface." - if intf is None: - intf = self.defaultIntf() - if intf and not self.waiting: - self.updateMAC( intf ) - return self.macs.get( intf, None ) + return self.intf( intf ).IP() - def updateIP( self, intf ): - "Update IP address for an interface" - assert not self.waiting - ifconfig = self.cmd( 'ifconfig ' + intf ) - ips = self._ipMatchRegex.findall( ifconfig ) - if ips: - self.ips[ intf ] = ips[ 0 ] - else: - self.ips[ intf ] = None - - def updateMAC( self, intf ): - "Update MAC address for an interface" - assert not self.waiting - ifconfig = self.cmd( 'ifconfig ' + intf ) - macs = self._macMatchRegex.findall( ifconfig ) - if macs: - self.macs[ intf ] = macs[ 0 ] - else: - self.macs[ intf ] = None - - def intfIsUp( self, intf ): + def intfIsUp( self, intf=None ): "Check if an interface is up." - return 'UP' in self.cmd( 'ifconfig ' + intf ) + 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 ] + f = getattr( self, method, None ) + if not f or value is None: + return + if type( value ) is list: + result = f( *value ) + elif type( value ) is 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, 'defaultRoute', 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 ): - intfs = sorted( self.intfs.values() ) - return '%s: IP=%s intfs=%s pid=%s' % ( - self.name, self.IP(), ','.join( intfs ), self.pid ) + "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." + "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 + 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 ) + return exitcode != 0 + + 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', '-a', str( self.pid ), + '-g', self.name ] + if self.sched == 'rt': + mncmd += [ '-r', str( self.rtprio ) ] + return Host.popen( self, *args, mncmd=mncmd, **kwargs ) + + def cleanup( self ): + "Clean up our cgroup" + retry( retries=3, delaySecs=1, fn=self.cgroupDel ) + + 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 * numCores() ) + 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() ) + 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=-1, sched=None): + """Set overall CPU fraction for this host + f: CPU bandwidth limit (fraction) + sched: 'rt' or 'cfs' + Note 'cfs' requires CONFIG_CFS_BANDWIDTH""" + if not f: + return + if not sched: + sched = self.sched + if sched == 'rt': + pstr, qstr, period, quota = self.rtInfo( f ) + elif sched == 'cfs': + pstr, qstr, period, quota = self.cfsInfo( f ) + else: + return + if quota < 0: + # Reset to unlimited + quota = -1 + # Set cgroup's period and quota + self.cgroupSet( pstr, period ) + self.cgroupSet( qstr, quota ) + if sched == 'rt': + # Set RT priority if necessary + self.chrt() + info( '(%s %d/%dus) ' % ( sched, quota, period ) ) + + def setCPUs( self, cores, mems=0 ): + "Specify (real) cores that our cgroup can run on" + if type( cores ) is 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=None, 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 = SWITCH_PORT_BASE # 0 for OF < 1.0, 1 for OF >= 1.0 + portBase = 1 # Switches start with port 1 in OpenFlow + dpidLen = 16 # digits in dpid passed to switch - def __init__( self, name, opts='', listenPort=None, **kwargs): - Node.__init__( self, name, **kwargs ) + def __init__( self, name, dpid=None, opts='', listenPort=None, **params): + """dpid: dpid for switch (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 = dpid if dpid else self.defaultDpid() self.opts = opts self.listenPort = listenPort - if self.listenPort: - self.opts += ' --listen=ptcp:%i ' % self.listenPort + if not self.inNamespace: + self.controlIntf = Intf( 'lo', self, port=0 ) + + def defaultDpid( self ): + "Derive dpid from switch name, s1 -> 1" + try: + dpid = int( re.findall( '\d+', self.name )[ 0 ] ) + dpid = hex( dpid )[ 2: ] + dpid = '0' * ( self.dpidLen - len( dpid ) ) + dpid + return dpid + except IndexError: + raise Exception( 'Unable to derive default datapath ID - ' + 'please either specify a dpid or use a ' + 'canonical switch name such as s23.' ) def defaultIntf( self ): - "Return interface for HIGHEST port" - ports = self.intfs.keys() - if ports: - intf = self.intfs[ max( ports ) ] - return intf + "Return control interface" + if self.controlIntf: + return self.controlIntf + else: + return Node.defaultIntf( self ) def sendCmd( self, *cmd, **kwargs ): """Send command to Node. @@ -455,46 +778,61 @@ class Switch( Node ): return Node.sendCmd( self, *cmd, **kwargs ) else: error( '*** Error: %s has execed and cannot accept commands' % - self.name ) + self.name ) + + 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, **kwargs ): """Init. name: name for the switch""" Switch.__init__( self, name, **kwargs ) pathCheck( 'ofdatapath', 'ofprotocol', - moduleName='the OpenFlow reference user switch (openflow.org)' ) + moduleName='the OpenFlow reference user switch' + + '(openflow.org)' ) + if self.listenPort: + self.opts += ' --listen=ptcp:%i ' % self.listenPort - @staticmethod - def setup(): + @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" + if not self.listenPort: + return "can't run dpctl without passive listening port" + return self.cmd( 'dpctl ' + ' '.join( args ) + + ' tcp:127.0.0.1:%i' % self.listenPort ) + def start( self, controllers ): """Start OpenFlow reference user datapath. Log to /tmp/sN-{ofd,ofp}.log. controllers: list of controller objects""" - controller = controllers[ 0 ] + # 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' self.cmd( 'ifconfig lo up' ) - mac_str = '' - if self.defaultMAC: - # ofdatapath expects a string of hex digits with no colons. - mac_str = ' -d ' + ''.join( self.defaultMAC.split( ':' ) ) - intfs = sorted( self.intfs.values() ) - if self.inNamespace: - intfs = intfs[ :-1 ] + intfs = [ str( i ) for i in self.intfList() if not i.IP() ] self.cmd( 'ofdatapath -i ' + ','.join( intfs ) + - ' punix:/tmp/' + self.name + mac_str + ' --no-slicing ' + - ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' ) + ' punix:/tmp/' + self.name + ' -d ' + self.dpid + + ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' ) self.cmd( 'ofprotocol unix:/tmp/' + self.name + - ' tcp:%s:%d' % ( controller.IP(), controller.port ) + - ' --fail=closed ' + self.opts + - ' 1> ' + ofplog + ' 2>' + ofplog + ' &' ) + ' ' + clist + + ' --fail=closed ' + self.opts + + ' 1> ' + ofplog + ' 2>' + ofplog + ' &' ) def stop( self ): "Stop OpenFlow reference user datapath." @@ -502,64 +840,9 @@ class UserSwitch( Switch ): self.cmd( 'kill %ofprotocol' ) self.deleteIntfs() -class KernelSwitch( Switch ): - """Kernel-space switch. - Currently only works in root namespace.""" - def __init__( self, name, dp=None, **kwargs ): - """Init. - name: name for switch - dp: netlink id (0, 1, 2, ...) - defaultMAC: default MAC as string; random value if None""" - Switch.__init__( self, name, **kwargs ) - self.dp = 'nl:%i' % dp - self.intf = 'of%i' % dp - if self.inNamespace: - error( "KernelSwitch currently only works" - " in the root namespace." ) - exit( 1 ) - - @staticmethod - def setup(): - "Ensure any dependencies are loaded; if not, try to load them." - pathCheck( 'ofprotocol', - moduleName='the OpenFlow reference kernel switch' - ' (openflow.org) (NOTE: not available in OpenFlow 1.0!)' ) - moduleDeps( subtract=OVS_KMOD, add=OF_KMOD ) - - def start( self, controllers ): - "Start up reference kernel datapath." - ofplog = '/tmp/' + self.name + '-ofp.log' - quietRun( 'ifconfig lo up' ) - # Delete local datapath if it exists; - # then create a new one monitoring the given interfaces - quietRun( 'dpctl deldp ' + self.dp ) - self.cmd( 'dpctl adddp ' + self.dp ) - if self.defaultMAC: - self.cmd( 'ifconfig', self.intf, 'hw', 'ether', self.defaultMAC ) - ports = sorted( self.ports.values() ) - if len( ports ) != ports[ -1 ] + 1 - self.portBase: - raise Exception( 'only contiguous, zero-indexed port ranges' - 'supported: %s' % ports ) - intfs = [ self.intfs[ port ] for port in ports ] - self.cmd( 'dpctl', 'addif', self.dp, ' '.join( intfs ) ) - # Run protocol daemon - controller = controllers[ 0 ] - self.cmd( 'ofprotocol ' + self.dp + - ' tcp:%s:%d' % ( controller.IP(), controller.port ) + - ' --fail=closed ' + self.opts + - ' 1> ' + ofplog + ' 2>' + ofplog + ' &' ) - self.execed = False - - def stop( self ): - "Terminate kernel datapath." - quietRun( 'dpctl deldp ' + self.dp ) - self.cmd( 'kill %ofprotocol' ) - self.deleteIntfs() - - -class OVSKernelSwitch( Switch ): - """Open VSwitch kernel-space switch. +class OVSLegacyKernelSwitch( Switch ): + """Open VSwitch legacy kernel-space switch using ovs-openflowd. Currently only works in the root namespace.""" def __init__( self, name, dp=None, **kwargs ): @@ -568,18 +851,18 @@ class OVSKernelSwitch( Switch ): dp: netlink id (0, 1, 2, ...) defaultMAC: default MAC as unsigned int; random value if None""" Switch.__init__( self, name, **kwargs ) - self.dp = 'dp%i' % dp + self.dp = dp if dp else self.name self.intf = self.dp if self.inNamespace: error( "OVSKernelSwitch currently only works" - " in the root namespace.\n" ) + " in the root namespace.\n" ) exit( 1 ) - @staticmethod - def setup(): + @classmethod + def setup( cls ): "Ensure any dependencies are loaded; if not, try to load them." pathCheck( 'ovs-dpctl', 'ovs-openflowd', - moduleName='Open vSwitch (openvswitch.org)') + moduleName='Open vSwitch (openvswitch.org)') moduleDeps( subtract=OF_KMOD, add=OVS_KMOD ) def start( self, controllers ): @@ -588,26 +871,18 @@ class OVSKernelSwitch( Switch ): quietRun( 'ifconfig lo up' ) # Delete local datapath if it exists; # then create a new one monitoring the given interfaces - quietRun( 'ovs-dpctl del-dp ' + self.dp ) + self.cmd( 'ovs-dpctl del-dp ' + self.dp ) self.cmd( 'ovs-dpctl add-dp ' + self.dp ) - mac_str = '' - if self.defaultMAC: - # ovs-openflowd expects a string of exactly 16 hex digits with no - # colons. - mac_str = ' --datapath-id=0000' + \ - ''.join( self.defaultMAC.split( ':' ) ) + ' ' - ports = sorted( self.ports.values() ) - if len( ports ) != ports[ -1 ] + 1 - self.portBase: - raise Exception( 'only contiguous, one-indexed port ranges ' - 'supported: %s' % self.intfs ) - intfs = [ self.intfs[ port ] for port in ports ] + intfs = [ str( i ) for i in self.intfList() if not i.IP() ] self.cmd( 'ovs-dpctl', 'add-if', self.dp, ' '.join( intfs ) ) # Run protocol daemon - controller = controllers[ 0 ] + clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port ) + for c in controllers ] ) self.cmd( 'ovs-openflowd ' + self.dp + - ' tcp:%s:%d' % ( controller.IP(), controller.port ) + - ' --fail=secure ' + self.opts + mac_str + - ' 1>' + ofplog + ' 2>' + ofplog + '&' ) + ' ' + clist + + ' --fail=secure ' + self.opts + + ' --datapath-id=' + self.dpid + + ' 1>' + ofplog + ' 2>' + ofplog + '&' ) self.execed = False def stop( self ): @@ -617,19 +892,116 @@ class OVSKernelSwitch( Switch ): self.deleteIntfs() +class OVSSwitch( Switch ): + "Open vSwitch switch. Depends on ovs-vsctl." + + def __init__( self, name, failMode='secure', **params ): + """Init. + name: name for switch + failMode: controller loss behavior (secure|open)""" + Switch.__init__( self, name, **params ) + self.failMode = failMode + + @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 ) + + def dpctl( self, *args ): + "Run ovs-dpctl command" + return self.cmd( 'ovs-dpctl', args[ 0 ], self, *args[ 1: ] ) + + @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 type( intf ) is TCIntf: + intf.config( **intf.params ) + + def attach( self, intf ): + "Connect a data port" + self.cmd( 'ovs-vsctl add-port', self, intf ) + self.cmd( 'ifconfig', intf, 'up' ) + self.TCReapply( intf ) + + def detach( self, intf ): + "Disconnect a data port" + self.cmd( 'ovs-vsctl del-port', self, intf ) + + 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' ) + # We should probably call config instead, but this + # requires some rethinking... + self.cmd( 'ifconfig lo up' ) + # Annoyingly, --if-exists option seems not to work + self.cmd( 'ovs-vsctl del-br', self ) + self.cmd( 'ovs-vsctl add-br', self ) + self.cmd( 'ovs-vsctl -- set Bridge', self, + 'other_config:datapath-id=' + self.dpid ) + self.cmd( 'ovs-vsctl set-fail-mode', self, self.failMode ) + for intf in self.intfList(): + if not intf.IP(): + self.attach( intf ) + # Add controllers + clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port ) + for c in controllers ] ) + self.cmd( 'ovs-vsctl set-controller', self, clist ) + + def stop( self ): + "Terminate OVS switch." + self.cmd( 'ovs-vsctl del-br', self ) + self.deleteIntfs() + +OVSKernelSwitch = OVSSwitch + + class Controller( Node ): """A Controller is a Node that is running (or has execed?) an OpenFlow controller.""" def __init__( self, name, inNamespace=False, command='controller', - cargs='-v ptcp:%d', cdir=None, defaultIP="127.0.0.1", - port=6633 ): + cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1", + port=6633, **params ): self.command = command self.cargs = cargs self.cdir = cdir + self.ip = ip self.port = port Node.__init__( self, name, inNamespace=inNamespace, - defaultIP=defaultIP ) + ip=ip, **params ) + self.cmd( 'ifconfig lo up' ) # Shouldn't be necessary + self.checkListening() + + def checkListening( self ): + "Make sure no controllers are running on our port" + listening = self.cmd( "echo A | telnet -e A %s %d" % + ( self.ip, self.port ) ) + if 'Unable' not in listening: + servers = self.cmd( 'netstat -atp' ).split( '\n' ) + pstr = ':%d ' % self.port + clist = servers[ 0:1 ] + [ s for s in servers if pstr in s ] + raise Exception( "Please shut down the controller which is" + " running on port %d:\n" % self.port + + '\n'.join( clist ) ) def start( self ): """Start on controller. @@ -639,7 +1011,7 @@ class Controller( Node ): if self.cdir is not None: self.cmd( 'cd ' + self.cdir ) self.cmd( self.command + ' ' + self.cargs % self.port + - ' 1>' + cout + ' 2>' + cout + '&' ) + ' 1>' + cout + ' 2>' + cout + '&' ) self.execed = False def stop( self ): @@ -649,32 +1021,37 @@ class Controller( Node ): def IP( self, intf=None ): "Return IP address of the Controller" - ip = Node.IP( self, intf=intf ) - if ip is None: - ip = self.defaultIP + if self.intfs: + ip = Node.IP( self, intf ) + else: + ip = self.ip return ip -class ControllerParams( object ): - "Container for controller IP parameters." + 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 ) - def __init__( self, ip, prefixLen ): - """Init. - ip: string, controller IP address - prefixLen: prefix length, e.g. 8 for /8, covering 16M""" - self.ip = ip - self.prefixLen = prefixLen + +class OVSController( Controller ): + "Open vSwitch controller" + def __init__( self, name, command='ovs-controller', **kwargs ): + Controller.__init__( self, name, command=command, **kwargs ) class NOX( Controller ): "Controller to run a NOX application." - def __init__( self, name, noxArgs=None, **kwargs ): + def __init__( self, name, *noxArgs, **kwargs ): """Init. name: name to give controller - noxArgs: list of args, or single arg, to pass to NOX""" + 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 ) != list: + elif type( noxArgs ) not in ( list, tuple ): noxArgs = [ noxArgs ] if 'NOX_CORE_DIR' not in os.environ: @@ -682,24 +1059,24 @@ class NOX( Controller ): 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 ) + command=noxCoreDir + '/nox_core', + cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' + + ' '.join( noxArgs ), + cdir=noxCoreDir, + **kwargs ) class RemoteController( Controller ): "Controller running outside of Mininet's control." - def __init__( self, name, defaultIP='127.0.0.1', - port=6633, **kwargs): + def __init__( self, name, ip='127.0.0.1', + port=6633, **kwargs): """Init. name: name to give controller - defaultIP: the IP address where the remote controller is + ip: the IP address where the remote controller is listening port: the port where the remote controller is listening""" - Controller.__init__( self, name, defaultIP=defaultIP, port=port, - **kwargs ) + Controller.__init__( self, name, ip=ip, port=port, **kwargs ) def start( self ): "Overridden to do nothing." @@ -708,3 +1085,11 @@ class RemoteController( Controller ): def stop( self ): "Overridden to do nothing." return + + def checkListening( self ): + "Warn if remote controller is not accessible" + listening = self.cmd( "echo A | telnet -e A %s %d" % + ( self.ip, self.port ) ) + if 'Unable' in listening: + warn( "Unable to contact the remote controller" + " at %s:%d\n" % ( self.ip, self.port ) ) diff --git a/mininet/test/test_hifi.py b/mininet/test/test_hifi.py new file mode 100755 index 0000000..ace7bb5 --- /dev/null +++ b/mininet/test/test_hifi.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python + +"""Package: mininet + Test creation and pings for topologies with link and/or CPU options.""" + +import unittest + +from mininet.net import Mininet +from mininet.node import OVSKernelSwitch +from mininet.node import CPULimitedHost +from mininet.link import TCLink +from mininet.topo import Topo +from mininet.log import setLogLevel + + +SWITCH = OVSKernelSwitch +# 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) + + +class testOptionsTopo( unittest.TestCase ): + "Verify ability to create networks with host and link options." + + def runOptionsTopoTest( self, n, hopts=None, lopts=None ): + "Generic topology-with-options test runner." + mn = Mininet( topo=SingleSwitchOptionsTopo( n=n, hopts=hopts, + lopts=lopts ), + host=CPULimitedHost, link=TCLink ) + dropped = mn.run( mn.ping ) + self.assertEqual( dropped, 0 ) + + def assertWithinTolerance(self, measured, expected, tolerance_frac): + """Check that a given value is within a tolerance of expected + tolerance_frac: less-than-1.0 value; 0.8 would yield 20% tolerance. + """ + self.assertTrue( float(measured) >= float(expected) * tolerance_frac ) + self.assertTrue( float(measured) >= float(expected) * tolerance_frac ) + + 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 ) + mn.start() + results = mn.runCpuLimitTest( cpu=CPU_FRACTION ) + mn.stop() + for cpu in results: + self.assertWithinTolerance( cpu, CPU_FRACTION, CPU_TOLERANCE ) + + def testLinkBandwidth( self ): + "Verify that link bandwidths are accurate within a bound." + 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 ) + bw_strs = mn.run( mn.iperf ) + for bw_str in bw_strs: + bw = float( bw_str.split(' ')[0] ) + self.assertWithinTolerance( bw, BW, BW_TOLERANCE ) + + 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 + lopts = { 'delay': '%sms' % DELAY_MS, 'use_htb': True } + mn = Mininet( SingleSwitchOptionsTopo( n=N, lopts=lopts ), + link=TCLink ) + ping_delays = mn.run( mn.pingFull ) + test_outputs = ping_delays[0] + # Ignore unused variables below + # pylint: disable-msg=W0612 + node, dest, ping_outputs = test_outputs + sent, received, rttmin, rttavg, rttmax, rttdev = ping_outputs + self.assertEqual( sent, received ) + # pylint: enable-msg=W0612 + 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) + + 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 ) + # 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() + self.assertTrue(dropped_total > 0) + + 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 } + self.runOptionsTopoTest( N, hopts=hopts, lopts=lopts ) + + +if __name__ == '__main__': + setLogLevel( 'warning' ) + unittest.main() diff --git a/mininet/test/test_nets.py b/mininet/test/test_nets.py index 37094c4..fde8e87 100755 --- a/mininet/test/test_nets.py +++ b/mininet/test/test_nets.py @@ -5,16 +5,14 @@ import unittest -from mininet.net import init, Mininet -from mininet.node import Host, Controller, ControllerParams -# from mininet.node import KernelSwitch +from mininet.net import Mininet +from mininet.node import Host, Controller from mininet.node import UserSwitch, OVSKernelSwitch from mininet.topo import SingleSwitchTopo, LinearTopo from mininet.log import setLogLevel SWITCHES = { 'user': UserSwitch, 'ovsk': OVSKernelSwitch, - # 'kernel': KernelSwitch } @@ -23,21 +21,15 @@ class testSingleSwitch( unittest.TestCase ): def testMinimal( self ): "Ping test with both datapaths on minimal topology" - init() for switch in SWITCHES.values(): - controllerParams = ControllerParams( '10.0.0.0', 8 ) - mn = Mininet( SingleSwitchTopo(), switch, Host, Controller, - controllerParams ) + mn = Mininet( SingleSwitchTopo(), switch, Host, Controller ) dropped = mn.run( mn.ping ) self.assertEqual( dropped, 0 ) def testSingle5( self ): "Ping test with both datapaths on 5-host single-switch topology" - init() for switch in SWITCHES.values(): - controllerParams = ControllerParams( '10.0.0.0', 8 ) - mn = Mininet( SingleSwitchTopo( k=5 ), switch, Host, Controller, - controllerParams ) + mn = Mininet( SingleSwitchTopo( k=5 ), switch, Host, Controller ) dropped = mn.run( mn.ping ) self.assertEqual( dropped, 0 ) @@ -47,15 +39,12 @@ class testLinear( unittest.TestCase ): def testLinear5( self ): "Ping test with both datapaths on a 5-switch topology" - init() for switch in SWITCHES.values(): - controllerParams = ControllerParams( '10.0.0.0', 8 ) - mn = Mininet( LinearTopo( k=5 ), switch, Host, Controller, - controllerParams ) + mn = Mininet( LinearTopo( k=5 ), switch, Host, Controller ) dropped = mn.run( mn.ping ) self.assertEqual( dropped, 0 ) if __name__ == '__main__': - setLogLevel('warning') + setLogLevel( 'warning' ) unittest.main() diff --git a/mininet/topo.py b/mininet/topo.py index f7de5fc..fff9604 100644 --- a/mininet/topo.py +++ b/mininet/topo.py @@ -16,258 +16,125 @@ setup for testing, and can even be emulated with the Mininet package. # from networkx.classes.graph import Graph from networkx import Graph -from mininet.node import SWITCH_PORT_BASE - -class NodeID(object): - '''Topo node identifier.''' - - def __init__(self, dpid = None): - '''Init. - - @param dpid dpid - ''' - # DPID-compatible hashable identifier: opaque 64-bit unsigned int - self.dpid = dpid - - def __str__(self): - '''String conversion. - - @return str dpid as string - ''' - return str(self.dpid) - - def name_str(self): - '''Name conversion. - - @return name name as string - ''' - return str(self.dpid) - - def ip_str(self): - '''Name conversion. - - @return ip ip as string - ''' - hi = (self.dpid & 0xff0000) >> 16 - mid = (self.dpid & 0xff00) >> 8 - lo = self.dpid & 0xff - return "10.%i.%i.%i" % (hi, mid, lo) - - -class Node(object): - '''Node-specific vertex metadata for a Topo object.''' - - def __init__(self, connected = False, admin_on = True, - power_on = True, fault = False, is_switch = True): - '''Init. - - @param connected actively connected to controller - @param admin_on administratively on or off - @param power_on powered on or off - @param fault fault seen on node - @param is_switch switch or host - ''' - self.connected = connected - self.admin_on = admin_on - self.power_on = power_on - self.fault = fault - self.is_switch = is_switch - - -class Edge(object): - '''Edge-specific metadata for a StructuredTopo graph.''' - - def __init__(self, admin_on = True, power_on = True, fault = False): - '''Init. - - @param admin_on administratively on or off; defaults to True - @param power_on powered on or off; defaults to True - @param fault fault seen on edge; defaults to False - ''' - self.admin_on = admin_on - self.power_on = power_on - self.fault = fault - +from mininet.util import irange, natural, naturalSeq class Topo(object): - '''Data center network representation for structured multi-trees.''' + "Data center network representation for structured multi-trees." - def __init__(self): - '''Create Topo object. - - ''' + def __init__(self, hopts=None, sopts=None, lopts=None): + """Topo object: + hinfo: default host options + sopts: default switch options + lopts: default link options""" self.g = Graph() - self.node_info = {} # dpids hash to Node objects - self.edge_info = {} # (src_dpid, dst_dpid) tuples hash to Edge objects + self.node_info = {} + self.link_info = {} # (src, dst) tuples hash to EdgeInfo objects + self.hopts = {} if hopts is None else hopts + self.sopts = {} if sopts is None else sopts + self.lopts = {} if lopts is None else lopts self.ports = {} # ports[src][dst] is port on src that connects to dst - self.id_gen = NodeID # class used to generate dpid - def add_node(self, dpid, node): - '''Add Node to graph. + def addNode(self, name, **opts): + """Add Node to graph. + name: name + opts: node options + returns: node name""" + self.g.add_node(name) + self.node_info[name] = opts + return name - @param dpid dpid - @param node Node object - ''' - self.g.add_node(dpid) - self.node_info[dpid] = node + 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 add_edge(self, src, dst, edge = None): - '''Add edge (Node, Node) to graph. + 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 - @param src src dpid - @param dst dst dpid - @param edge Edge object - ''' - src, dst = tuple(sorted([src, dst])) - self.g.add_edge(src, dst) - if not edge: - edge = Edge() - self.edge_info[(src, dst)] = edge - self.add_port(src, dst) + def addLink(self, node1, node2, port1=None, port2=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 + self.addPort(node1, node2, port1, port2) + key = tuple(self.sorted([node1, node2])) + self.link_info[key] = opts + self.g.add_edge(*key) + return key - def add_port(self, src, dst): + def addPort(self, src, dst, sport=None, dport=None): '''Generate port mapping for new edge. - - @param src source switch DPID - @param dst destination switch DPID + @param src source switch name + @param dst destination switch name ''' - src_base = SWITCH_PORT_BASE if self.is_switch(src) else 0 - dst_base = SWITCH_PORT_BASE if self.is_switch(dst) else 0 - if src not in self.ports: - self.ports[src] = {} - if dst not in self.ports[src]: - # num outlinks - self.ports[src][dst] = len(self.ports[src]) + src_base - if dst not in self.ports: - self.ports[dst] = {} - if src not in self.ports[dst]: - # num outlinks - self.ports[dst][src] = len(self.ports[dst]) + dst_base + self.ports.setdefault(src, {}) + self.ports.setdefault(dst, {}) + # New port: number of outlinks + base + src_base = 1 if self.isSwitch(src) else 0 + dst_base = 1 if self.isSwitch(dst) else 0 + if sport is None: + sport = len(self.ports[src]) + src_base + if dport is None: + dport = len(self.ports[dst]) + dst_base + self.ports[src][dst] = sport + self.ports[dst][src] = dport - def node_enabled(self, dpid): - '''Is node connected, admin on, powered on, and fault-free? - - @param dpid dpid - - @return bool node is enabled - ''' - ni = self.node_info[dpid] - return ni.connected and ni.admin_on and ni.power_on and not ni.fault - - def nodes_enabled(self, dpids, enabled = True): - '''Return subset of enabled nodes - - @param dpids list of dpids - @param enabled only return enabled nodes? - - @return dpids filtered list of dpids - ''' - if enabled: - return [n for n in dpids if self.node_enabled(n)] + def nodes(self, sort=True): + "Return nodes in graph" + if sort: + return self.sorted( self.g.nodes() ) else: - return dpids + return self.g.nodes() - def nodes(self, enabled = True): - '''Return graph nodes. - - @param enabled only return enabled nodes? - - @return dpids list of dpids - ''' - return self.nodes_enabled(self.g.nodes(), enabled) - - def nodes_str(self, dpids): - '''Return string of custom-encoded nodes. - - @param dpids list of dpids - - @return str string - ''' - return [str(self.id_gen(dpid = dpid)) for dpid in dpids] - - def is_switch(self, n): + def isSwitch(self, n): '''Returns true if node is a switch.''' - return self.node_info[n].is_switch + info = self.node_info[n] + return info and info.get('isSwitch', False) - def switches(self, enabled = True): + def switches(self, sort=True): '''Return switches. - - @param enabled only return enabled nodes? - + sort: sort switches alphabetically @return dpids list of dpids ''' - nodes = [n for n in self.g.nodes() if self.is_switch(n)] - return self.nodes_enabled(nodes, enabled) + return [n for n in self.nodes(sort) if self.isSwitch(n)] - def hosts(self, enabled = True): + def hosts(self, sort=True): '''Return hosts. - - @param enabled only return enabled nodes? - + sort: sort hosts alphabetically @return dpids list of dpids ''' + return [n for n in self.nodes(sort) if not self.isSwitch(n)] - def is_host(n): - '''Returns true if node is a host.''' - return not self.node_info[n].is_switch - - nodes = [n for n in self.g.nodes() if is_host(n)] - return self.nodes_enabled(nodes, enabled) - - def edge_enabled(self, edge): - '''Is edge admin on, powered on, and fault-free? - - @param edge (src, dst) dpid tuple - - @return bool edge is enabled + def links(self, sort=True): + '''Return links. + sort: sort links alphabetically + @return links list of name pairs ''' - src, dst = edge - src, dst = tuple(sorted([src, dst])) - ei = self.edge_info[tuple(sorted([src, dst]))] - return ei.admin_on and ei.power_on and not ei.fault - - def edges_enabled(self, edges, enabled = True): - '''Return subset of enabled edges - - @param edges list of edges - @param enabled only return enabled edges? - - @return edges filtered list of edges - ''' - if enabled: - return [e for e in edges if self.edge_enabled(e)] + if not sort: + return self.g.edges() else: - return edges - - def edges(self, enabled = True): - '''Return edges. - - @param enabled only return enabled edges? - - @return edges list of dpid pairs - ''' - return self.edges_enabled(self.g.edges(), enabled) - - def edges_str(self, dpid_pairs): - '''Return string of custom-encoded node pairs. - - @param dpid_pairs list of dpid pairs (src, dst) - - @return str string - ''' - edges = [] - for pair in dpid_pairs: - src, dst = pair - src = str(self.id_gen(dpid = src)) - dst = str(self.id_gen(dpid = dst)) - edges.append((src, dst)) - return edges + links = [tuple(self.sorted(e)) for e in self.g.edges()] + return sorted( links, key=naturalSeq ) def port(self, src, dst): '''Get port number. - @param src source switch DPID - @param dst destination switch DPID + @param src source switch name + @param dst destination switch name @return tuple (src_port, dst_port): src_port: port on source switch leading to the destination switch dst_port: port on destination switch leading to the source switch @@ -276,130 +143,88 @@ class Topo(object): assert dst in self.ports and src in self.ports[dst] return (self.ports[src][dst], self.ports[dst][src]) - def enable_edges(self): - '''Enable all edges in the network graph. + def linkInfo( self, src, dst ): + "Return link metadata" + src, dst = self.sorted([src, dst]) + return self.link_info[(src, dst)] - Set admin on, power on, and fault off. - ''' - for e in self.g.edges(): - src, dst = e - ei = self.edge_info[tuple(sorted([src, dst]))] - ei.admin_on = True - ei.power_on = True - ei.fault = False + def setlinkInfo( self, src, dst, info ): + "Set link metadata" + src, dst = self.sorted([src, dst]) + self.link_info[(src, dst)] = info - def enable_nodes(self): - '''Enable all nodes in the network graph. + def nodeInfo( self, name ): + "Return metadata (dict) for node" + info = self.node_info[ name ] + return info if info is not None else {} - Set connected on, admin on, power on, and fault off. - ''' - for node in self.g.nodes(): - ni = self.node_info[node] - ni.connected = True - ni.admin_on = True - ni.power_on = True - ni.fault = False - - def enable_all(self): - '''Enable all nodes and edges in the network graph.''' - self.enable_nodes() - self.enable_edges() - - def name(self, dpid): - '''Get string name of node ID. - - @param dpid DPID of host or switch - @return name_str string name with no dashes - ''' - return self.id_gen(dpid = dpid).name_str() - - def ip(self, dpid): - '''Get IP dotted-decimal string of node ID. - - @param dpid DPID of host or switch - @return ip_str - ''' - return self.id_gen(dpid = dpid).ip_str() + def setNodeInfo( self, name, info ): + "Set metadata (dict) for node" + self.node_info[ name ] = info + @staticmethod + def sorted( items ): + "Items sorted in natural (i.e. alphabetical) order" + return sorted(items, key=natural) class SingleSwitchTopo(Topo): '''Single switch connected to k hosts.''' - def __init__(self, k = 2, enable_all = True): + def __init__(self, k=2, **opts): '''Init. @param k number of hosts @param enable_all enables all nodes and switches? ''' - super(SingleSwitchTopo, self).__init__() + super(SingleSwitchTopo, self).__init__(**opts) self.k = k - self.add_node(1, Node()) - hosts = range(2, k + 2) - for h in hosts: - self.add_node(h, Node(is_switch = False)) - self.add_edge(h, 1, Edge()) - - if enable_all: - self.enable_all() + switch = self.addSwitch('s1') + for h in irange(1, k): + host = self.addHost('h%s' % h) + self.addLink(host, switch) -class SingleSwitchReversedTopo(SingleSwitchTopo): +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 port(self, src, dst): - '''Get port number. - - @param src source switch DPID - @param dst destination switch DPID - @return tuple (src_port, dst_port): - src_port: port on source switch leading to the destination switch - dst_port: port on destination switch leading to the source switch - ''' - if src == 1: - if dst in range(2, self.k + 2): - dst_index = dst - 2 - highest = self.k - 1 - return (highest - dst_index, 0) - else: - raise Exception('unexpected dst: %i' % dst) - elif src in range(2, self.k + 2): - if dst == 1: - raise Exception('unexpected dst: %i' % dst) - else: - src_index = src - 2 - highest = self.k - 1 - return (0, highest - src_index) - - -class LinearTopo(Topo): - '''Linear topology of k switches, with one host per switch.''' - - def __init__(self, k = 2, enable_all = True): + def __init__(self, k=2, **opts): '''Init. - @param k number of switches (and hosts too) + @param k number of hosts @param enable_all enables all nodes and switches? ''' - super(LinearTopo, self).__init__() + super(SingleSwitchReversedTopo, self).__init__(**opts) + 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 LinearTopo(Topo): + "Linear topology of k switches, with one host per switch." + + def __init__(self, k=2, **opts): + """Init. + k: number of switches (and hosts) + hconf: host configuration options + lconf: link configuration options""" + + super(LinearTopo, self).__init__(**opts) self.k = k - switches = range(1, k + 1) - for s in switches: - h = s + k - self.add_node(s, Node()) - self.add_node(h, Node(is_switch = False)) - self.add_edge(s, h, Edge()) - for s in switches: - if s != k: - self.add_edge(s, s + 1, Edge()) - - if enable_all: - self.enable_all() + lastSwitch = None + for i in irange(1, k): + host = self.addHost('h%s' % i) + switch = self.addSwitch('s%s' % i) + self.addLink( host, switch) + if lastSwitch: + self.addLink( switch, lastSwitch) + lastSwitch = switch diff --git a/mininet/topolib.py b/mininet/topolib.py index d42a42a..63ba36d 100644 --- a/mininet/topolib.py +++ b/mininet/topolib.py @@ -1,6 +1,6 @@ "Library of potentially useful topologies for Mininet" -from mininet.topo import Topo, Node +from mininet.topo import Topo from mininet.net import Mininet class TreeTopo( Topo ): @@ -8,36 +8,27 @@ class TreeTopo( Topo ): def __init__( self, depth=1, fanout=2 ): super( TreeTopo, self ).__init__() - # Numbering: h1..N, sN+1..M - hostCount = fanout ** depth + # Numbering: h1..N, s1..M self.hostNum = 1 - self.switchNum = hostCount + 1 + self.switchNum = 1 # Build topology self.addTree( depth, fanout ) - # Consider all switches and hosts 'on' - self.enable_all() - - # It is OK that i is "unused" in the for loop. - # pylint: disable-msg=W0612 def addTree( self, depth, fanout ): """Add a subtree starting with node n. returns: last node added""" isSwitch = depth > 0 if isSwitch: - num = self.switchNum + node = self.addSwitch( 's%s' % self.switchNum ) self.switchNum += 1 - else: - num = self.hostNum - self.hostNum += 1 - self.add_node( num, Node( is_switch=isSwitch ) ) - if isSwitch: - for i in range( 0, fanout ): + for _ in range( fanout ): child = self.addTree( depth - 1, fanout ) - self.add_edge( num, child ) - return num + self.addLink( node, child ) + else: + node = self.addHost( 'h%s' % self.hostNum ) + self.hostNum += 1 + return node - # pylint: enable-msg=W0612 def TreeNet( depth=1, fanout=2, **kwargs ): "Convenience function for creating tree networks." diff --git a/mininet/util.py b/mininet/util.py index d78db43..ff75b10 100644 --- a/mininet/util.py +++ b/mininet/util.py @@ -1,11 +1,15 @@ "Utility functions for Mininet." +from mininet.log import output, info, error, warn + from time import sleep from resource import setrlimit, RLIMIT_NPROC, RLIMIT_NOFILE -import select +from select import poll, POLLIN from subprocess import call, check_call, Popen, PIPE, STDOUT - -from mininet.log import error +import re +from fcntl import fcntl, F_GETFL, F_SETFL +from os import O_NONBLOCK +import os # Command execution support @@ -22,7 +26,7 @@ def checkRun( cmd ): # pylint doesn't understand explicit type checking # pylint: disable-msg=E1103 -def quietRun( *cmd ): +def oldQuietRun( *cmd ): """Run a command, routing stderr to stdout, and return the output. cmd: list of command params""" if len( cmd ) == 1: @@ -33,22 +37,87 @@ def quietRun( *cmd ): # We can't use Popen.communicate() because it uses # select(), which can't handle # high file descriptor numbers! poll() can, however. - output = '' - readable = select.poll() + out = '' + readable = poll() readable.register( popen.stdout ) while True: while readable.poll(): data = popen.stdout.read( 1024 ) if len( data ) == 0: break - output += data + out += data popen.poll() - if popen.returncode != None: + if popen.returncode is not None: break - return output + return out + + +# This is a bit complicated, but it enables us to +# monitor command output as it is happening + +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""" + # Allow passing in a list or a string + if len( cmd ) == 1: + cmd = cmd[ 0 ] + if isinstance( cmd, str ): + cmd = cmd.split( ' ' ) + cmd = [ str( arg ) for arg in cmd ] + # 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' ) + 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 ] + 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 + returncode = popen.wait() + return out, err, returncode + +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-msg=E1103 -# pylint: disable-msg=E1101,W0612 +# pylint: disable-msg=E1101 def isShellBuiltin( cmd ): "Return True if cmd is a bash builtin." @@ -61,7 +130,7 @@ def isShellBuiltin( cmd ): isShellBuiltin.builtIns = None -# pylint: enable-msg=E1101,W0612 +# pylint: enable-msg=E1101 # Interface management # @@ -113,7 +182,7 @@ def moveIntfNoRetry( intf, node, printError=False ): if not ( ' %s:' % intf ) in links: if printError: error( '*** Error: moveIntf: ' + intf + - ' not successfully moved to ' + node.name + '\n' ) + ' not successfully moved to ' + node.name + '\n' ) return False return True @@ -124,25 +193,41 @@ def moveIntf( intf, node, printError=False, retries=3, delaySecs=0.001 ): printError: if true, print error""" retry( retries, delaySecs, moveIntfNoRetry, intf, node, printError ) -def createLink( node1, node2, port1=None, port2=None ): - """Create a link between nodes, making an interface for each. - node1: Node object - node2: Node object - port1: node1 port number (optional) - port2: node2 port number (optional) - returns: intf1 name, intf2 name""" - return node1.linkTo( node2, port1, port2 ) +# 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 ) # IP and Mac address formatting and parsing -def _colonHex( val, count ): +def _colonHex( val, bytecount ): """Generate colon-hex string. val: input as unsigned int - count: number of bytes to convert + bytecount: number of bytes to convert returns: chStr colon-hex string""" pieces = [] - for i in range( count - 1, -1, -1 ): + for i in range( bytecount - 1, -1, -1 ): piece = ( ( 0xff << ( i * 8 ) ) & val ) >> ( i * 8 ) pieces.append( '%02x' % piece ) chStr = ':'.join( pieces ) @@ -158,23 +243,44 @@ 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, or 10.x.y.z if w==0""" - w = ( ip & 0xff000000 ) >> 24 + w = ( ip >> 24 ) & 0xff w = 10 if w == 0 else w - x = ( ip & 0xff0000 ) >> 16 - y = ( ip & 0xff00 ) >> 8 + 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 ofIP address + """Generate unsigned int from components of IP address returns: w << 24 | x << 16 | y << 8 | z""" - return ( 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""" + # Ugly but functional + assert i < ( 1 << ( 32 - prefixLen ) ) + mask = 0xffffffff ^ ( ( 1 << prefixLen ) - 1 ) + ipnum = i + ( ipBaseNum & mask ) + return ipStr( ipnum ) def ipParse( ip ): "Parse an IP address and return an unsigned int." args = [ int( arg ) for arg in ip.split( '.' ) ] 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 ) + return ipParse( ip ), prefixLen + def checkInt( s ): "Check if input string is an int" try: @@ -200,10 +306,164 @@ def makeNumeric( 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 True: + fds = poller.poll( timeoutms ) + if fds: + for fd, _event in fds: + host = fdToHost[ fd ] + popen = popens[ host ] + 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 + if not line: + popen.poll() + if popen.returncode is not None: + poller.unregister( fd ) + del popens[ host ] + if not popens: + return + else: + yield None, '' # Other stuff we use def fixLimits(): "Fix ridiculously small resource limits." - setrlimit( RLIMIT_NPROC, ( 4096, 8192 ) ) - setrlimit( RLIMIT_NOFILE, ( 16384, 32768 ) ) + setrlimit( RLIMIT_NPROC, ( 8192, 8192 ) ) + setrlimit( RLIMIT_NOFILE, ( 16384, 16384 ) ) + +def mountCgroups(): + "Make sure cgroups file system is mounted" + mounts = quietRun( 'mount' ) + cgdir = '/sys/fs/cgroup' + csdir = cgdir + '/cpuset' + if ('cgroup on %s' % cgdir not in mounts and + 'cgroups on %s' % cgdir not in mounts): + raise Exception( "cgroups not mounted on " + cgdir ) + if 'cpuset on %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+)', 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( '=' ) + kwargs[ key ] = makeNumeric( val ) + return fn, args, kwargs + +def customConstructor( constructors, argStr ): + """Return custom constructor based on argStr + The args and key/val pairs in argsStr will be automatically applied + when the generated constructor is later used. + """ + cname, newargs, kwargs = splitArgs( argStr ) + constructor = constructors.get( cname, None ) + + if not constructor: + raise Exception( "error: %s is unknown - please specify one of %s" % + ( cname, constructors.keys() ) ) + + def customized( name, *args, **params ): + "Customized constructor, useful for Node, Link, and other classes" + params = params.copy() + params.update( kwargs ) + if not newargs: + return constructor( name, *args, **params ) + if args: + warn( 'warning: %s replacing %s with %s\n' % ( + constructor, args, newargs ) ) + return constructor( name, *newargs, **params ) + + customized.__name__ = 'customConstructor(%s)' % argStr + return customized + +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 diff --git a/mnexec.c b/mnexec.c index 19f81f0..42a9cf6 100644 --- a/mnexec.c +++ b/mnexec.c @@ -7,6 +7,8 @@ * - detaching from a controlling tty using setsid * - running in a network namespace * - 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) */ @@ -14,23 +16,89 @@ #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" - "usage: %s [-cdnp]\n" - "-c: close all file descriptors except stdin/out/error\n" - "-d: detach from tty by calling setsid()\n" - "-n: run in new network namespace\n" - "-p: print ^A + pid\n", 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 namespace\n" + " -p: print ^A + pid\n" + " -a pid: attach to pid's network namespace\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(308, 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 */ +int 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[]) { char c; int fd; - - while ((c = getopt(argc, argv, "+cdnp")) != -1) + char path[PATH_MAX]; + int nsid; + int pid; + 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 */ @@ -64,16 +132,48 @@ int main(int argc, char *argv[]) printf("\001%d\n", getpid()); fflush(stdout); break; + case 'a': + /* Attach to pid's network 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; + } + 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]); - break; + exit(1); } if (optind < argc) { - execvp(argv[optind], &argv[optind]); - perror(argv[optind]); - return 1; - } + execvp(argv[optind], &argv[optind]); + perror(argv[optind]); + return 1; + } usage(argv[0]); } diff --git a/setup.py b/setup.py index 0394245..3f9f0eb 100644 --- a/setup.py +++ b/setup.py @@ -5,30 +5,37 @@ from setuptools import setup, find_packages from os.path import join -scripts = [ join( 'bin', filename ) for filename in [ - 'mn', 'mnexec' ] ] +# 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='0.0.0', + version=VERSION, description='Process-based OpenFlow emulator', author='Bob Lantz', author_email='rlantz@cs.stanford.edu', packages=find_packages(exclude='test'), long_description=""" -Insert longer description here. - """, + 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://openflow.org/mininet + """, classifiers=[ - "License :: OSI Approved :: GNU General Public License (GPL)", + "License :: OSI Approved :: BSD License", "Programming Language :: Python", - "Development Status :: 4 - Beta", + "Development Status :: 2 - Pre-Alpha", "Intended Audience :: Developers", "Topic :: Internet", ], - keywords='networking protocol Internet OpenFlow', - license='unspecified', + keywords='networking emulator protocol Internet OpenFlow SDN', + license='BSD', install_requires=[ 'setuptools', 'networkx' diff --git a/util/build-ovs-packages.sh b/util/build-ovs-packages.sh new file mode 100755 index 0000000..6a14659 --- /dev/null +++ b/util/build-ovs-packages.sh @@ -0,0 +1,94 @@ +#!/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)" + diff --git a/util/doxify.py b/util/doxify.py index 62e40ac..f9f60ad 100755 --- a/util/doxify.py +++ b/util/doxify.py @@ -82,7 +82,7 @@ if __name__ == '__main__': fixLines( infile.readlines(), outfid ) infile.close() os.close( outfid ) - call( [ 'doxypy.py', outname ] ) + call( [ 'doxypy', outname ] ) diff --git a/util/install.sh b/util/install.sh index ffc3c96..e019741 100755 --- a/util/install.sh +++ b/util/install.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash + # Mininet install script for Ubuntu (and Debian Lenny) # Brandon Heller (brandonh@stanford.edu) @@ -16,57 +17,78 @@ KERNEL_LOC=http://www.openflow.org/downloads/mininet 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 - sudo apt-get install -y lsb-release + install='sudo apt-get -y install' + remove='sudo apt-get -y remove' + pkginst='sudo dpkg -i' + # Prereqs for this script + if ! which lsb_release &> /dev/null; then + $install lsb-release + fi + if ! which bc &> /dev/null; then + $install bc + 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" +echo "Detected Linux distribution: $DIST $RELEASE $CODENAME $ARCH" # Kernel params -if [ "$DIST" = "Debian" ]; then - KERNEL_NAME=2.6.33.1-mininet - KERNEL_HEADERS=linux-headers-${KERNEL_NAME}_${KERNEL_NAME}-10.00.Custom_i386.deb - KERNEL_IMAGE=linux-image-${KERNEL_NAME}_${KERNEL_NAME}-10.00.Custom_i386.deb -elif [ "$DIST" = "Ubuntu" ]; then +if [ "$DIST" = "Ubuntu" ]; then if [ "$RELEASE" = "10.04" ]; then KERNEL_NAME='3.0.0-15-generic' else KERNEL_NAME=`uname -r` fi KERNEL_HEADERS=linux-headers-${KERNEL_NAME} +elif [ "$DIST" = "Debian" ] && [ "$ARCH" = "i386" ] && [ "$CODENAME" = "lenny" ]; then + KERNEL_NAME=2.6.33.1-mininet + KERNEL_HEADERS=linux-headers-${KERNEL_NAME}_${KERNEL_NAME}-10.00.Custom_i386.deb + KERNEL_IMAGE=linux-image-${KERNEL_NAME}_${KERNEL_NAME}-10.00.Custom_i386.deb else - echo "Install.sh currently only supports Ubuntu and Debian." + echo "Install.sh currently only supports Ubuntu and Debian Lenny i386." exit 1 fi +# More distribution info +DIST_LC=`echo $DIST | tr [A-Z] [a-z]` # as lower case # Kernel Deb pkg to be removed: -KERNEL_IMAGE_OLD=linux-image-2.6.26-2-686 +KERNEL_IMAGE_OLD=linux-image-2.6.26-33-generic DRIVERS_DIR=/lib/modules/${KERNEL_NAME}/kernel/drivers/net -OVS_RELEASE=v1.2.2 +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_SRC=~/openvswitch +OVS_TAG=v$OVS_RELEASE OVS_BUILD=$OVS_SRC/build-$KERNEL_NAME OVS_KMODS=($OVS_BUILD/datapath/linux/{openvswitch_mod.ko,brcompat_mod.ko}) function kernel { echo "Install Mininet-compatible kernel if necessary" sudo apt-get update - if [ "$DIST" = "Debian" ]; then + if [ "$DIST" = "Ubuntu" ] && [ "$RELEASE" = "10.04" ]; then + $install linux-image-$KERNEL_NAME + elif [ "$DIST" = "Debian" ]; then # The easy approach: download pre-built linux-image and linux-headers packages: wget -c $KERNEL_LOC/$KERNEL_HEADERS wget -c $KERNEL_LOC/$KERNEL_IMAGE # Install custom linux headers and image: - sudo dpkg -i $KERNEL_IMAGE $KERNEL_HEADERS + $pkginst $KERNEL_IMAGE $KERNEL_HEADERS # The next two steps are to work around a bug in newer versions of # kernel-package, which fails to add initrd images with the latest kernels. @@ -83,16 +105,15 @@ function kernel { # /boot/grub/menu.lst to set the default to the entry corresponding to the # kernel you just installed. fi - if [ "$DIST" = "Ubuntu" ] && [ "$RELEASE" = "10.04" ]; then - sudo apt-get -y install linux-image-$KERNEL_NAME - fi } function kernel_clean { echo "Cleaning kernel..." # To save disk space, remove previous kernel - sudo apt-get -y remove $KERNEL_IMAGE_OLD + if ! $remove $KERNEL_IMAGE_OLD; then + echo $KERNEL_IMAGE_OLD not installed. + fi # Also remove downloaded packages: rm -f ~/linux-headers-* ~/linux-image-* @@ -101,8 +122,9 @@ function kernel_clean { # Install Mininet deps function mn_deps { echo "Installing Mininet dependencies" - sudo aptitude install -y gcc make screen psmisc xterm ssh iperf iproute \ - python-setuptools python-networkx + $install gcc make screen psmisc xterm ssh iperf iproute \ + python-setuptools python-networkx cgroup-bin ethtool help2man \ + pyflakes pylint pep8 if [ "$DIST" = "Ubuntu" ] && [ "$RELEASE" = "10.04" ]; then echo "Upgrading networkx to avoid deprecation warning" @@ -124,16 +146,14 @@ function mn_deps { # The following will cause a full OF install, covering: # -user switch -# -dissector # The instructions below are an abbreviated version from # http://www.openflowswitch.org/wk/index.php/Debian_Install # ... modified to use Debian Lenny rather than unstable. function of { - echo "Installing OpenFlow and its tools..." - + echo "Installing OpenFlow reference implementation..." cd ~/ - sudo apt-get install -y git-core automake m4 pkg-config libtool \ - make libc6-dev autoconf autotools-dev gcc + $install git-core autoconf automake autotools-dev pkg-config \ + make gcc libtool libc6-dev git clone git://openflowswitch.org/openflow.git cd ~/openflow @@ -147,8 +167,8 @@ function of { sudo make install # Remove avahi-daemon, which may cause unwanted discovery packets to be - # sent during tests, near link status changes: - sudo apt-get remove -y avahi-daemon + # sent during tests, near link status changes: + $remove avahi-daemon # Disable IPv6. Add to /etc/modprobe.d/blacklist: if [ "$DIST" = "Ubuntu" ]; then @@ -157,6 +177,38 @@ function of { BLACKLIST=/etc/modprobe.d/blacklist fi sudo sh -c "echo 'blacklist net-pf-10\nblacklist ipv6' >> $BLACKLIST" + cd ~ +} + +function wireshark { + echo "Installing Wireshark dissector..." + + sudo apt-get install -y wireshark libgtk2.0-dev + + if [ "$DIST" = "Ubuntu" ] && [ "$RELEASE" != "10.04" ]; then + # Install newer version + sudo apt-get install -y scons mercurial libglib2.0-dev + sudo apt-get install -y libwiretap-dev libwireshark-dev + cd ~ + hg clone https://bitbucket.org/barnstorm/of-dissector + cd of-dissector/src + export WIRESHARK=/usr/include/wireshark + scons + # libwireshark0/ on 11.04; libwireshark1/ on later + WSDIR=`ls -d /usr/lib/wireshark/libwireshark* | head -1` + WSPLUGDIR=$WSDIR/plugins/ + sudo cp openflow.so $WSPLUGDIR + echo "Copied openflow plugin to $WSPLUGDIR" + else + # Install older version from reference source + cd ~/openflow/utilities/wireshark_dissectors/openflow + make + sudo make install + fi + + # Copy coloring rules: OF is white-on-blue: + mkdir -p ~/.wireshark + cp ~/mininet/util/colorfilters ~/.wireshark } @@ -194,42 +246,120 @@ function wireshark { # Install Open vSwitch # Instructions derived from OVS INSTALL, INSTALL.OpenFlow and README files. + function ovs { echo "Installing Open vSwitch..." - if [ "$DIST" = "Debian" ] && [ "$CODENAME" == "lenny" ]; then - sudo aptitude -y install pkg-config gcc make git-core python-dev libssl-dev - # Install Autoconf 2.63+ backport from Debian Backports repo: - # Instructions from http://backports.org/dokuwiki/doku.php?id=instructions - sudo su -c "echo 'deb http://www.backports.org/debian lenny-backports main contrib non-free' >> /etc/apt/sources.list" - sudo apt-get update - sudo apt-get -y --force-yes install debian-backports-keyring - sudo apt-get -y --force-yes -t lenny-backports install autoconf + # Required for module build/dkms install + $install $KERNEL_HEADERS + + ovspresent=0 + + # First see if we have packages + # XXX wget -c seems to fail from github/amazon s3 + cd /tmp + if wget $OVS_PACKAGE_LOC/$OVS_PACKAGE_NAME 2> /dev/null; then + $install patch dkms fakeroot python-argparse + tar xf $OVS_PACKAGE_NAME + orig=`tar tf $OVS_PACKAGE_NAME` + # Now install packages in reasonable dependency order + order='dkms common pki openvswitch-switch brcompat controller' + pkgs="" + for p in $order; do + pkg=`echo "$orig" | grep $p` + # Annoyingly, things seem to be missing without this flag + $pkginst --force-confmiss $pkg + done + ovspresent=1 fi - if [ "$DIST" = "Ubuntu" ]; then - sudo apt-get -y install $KERNEL_HEADERS + # Otherwise try distribution's OVS packages + if [ "$DIST" = "Ubuntu" ] && [ `expr $RELEASE '>=' 11.10` = 1 ]; then + 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 + if $install openvswitch-switch openvswitch-controller; then + echo "Ignoring error installing openvswitch-controller" + fi + ovspresent=1 fi + # Switch can run on its own, but + # Mininet should control the controller + if [ -e /etc/init.d/openvswitch-controller ]; then + if sudo service openvswitch-controller stop; then + echo "Stopped running controller" + fi + sudo update-rc.d openvswitch-controller disable + fi + + if [ $ovspresent = 1 ]; then + echo "Done (hopefully) installing packages" + cd ~ + return + fi + + # Otherwise attempt to install from source + + $install pkg-config gcc make python-dev libssl-dev libtool + + if [ "$DIST" = "Debian" ]; then + if [ "$CODENAME" = "lenny" ]; then + $install git-core + # Install Autoconf 2.63+ backport from Debian Backports repo: + # Instructions from http://backports.org/dokuwiki/doku.php?id=instructions + sudo su -c "echo 'deb http://www.backports.org/debian lenny-backports main contrib non-free' >> /etc/apt/sources.list" + sudo apt-get update + sudo apt-get -y --force-yes install debian-backports-keyring + sudo apt-get -y --force-yes -t lenny-backports install autoconf + fi + else + $install git + fi + # Install OVS from release cd ~/ - git clone git://openvswitch.org/openvswitch + git clone git://openvswitch.org/openvswitch $OVS_SRC cd $OVS_SRC - git checkout $OVS_RELEASE + git checkout $OVS_TAG ./boot.sh BUILDDIR=/lib/modules/${KERNEL_NAME}/build if [ ! -e $BUILDDIR ]; then echo "Creating build sdirectory $BUILDDIR" sudo mkdir -p $BUILDDIR fi - opts="--with-linux=$BUILDDIR" - mkdir -p $OVS_BUILD - cd $OVS_BUILD + opts="--with-linux=$BUILDDIR" + mkdir -p $OVS_BUILD + cd $OVS_BUILD ../configure $opts make sudo make install - # openflowd is deprecated, but for now copy it in - sudo cp tests/test-openflowd /usr/local/bin/ovs-openflowd + + modprobe +} + +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" } # Install NOX with tutorial files @@ -237,17 +367,17 @@ function nox { echo "Installing NOX w/tutorial files..." # Install NOX deps: - sudo apt-get -y install autoconf automake g++ libtool python python-twisted \ + $install autoconf automake g++ libtool python python-twisted \ swig libssl-dev make if [ "$DIST" = "Debian" ]; then - sudo apt-get -y install libboost1.35-dev + $install libboost1.35-dev elif [ "$DIST" = "Ubuntu" ]; then - sudo apt-get -y install python-dev libboost-dev - sudo apt-get -y install libboost-filesystem-dev - sudo apt-get -y install libboost-test-dev + $install python-dev libboost-dev + $install libboost-filesystem-dev + $install libboost-test-dev fi # Install NOX optional deps: - sudo apt-get install -y libsqlite3-dev python-simplejson + $install libsqlite3-dev python-simplejson # Fetch NOX destiny cd ~/ @@ -259,7 +389,10 @@ function nox { # Apply patches git checkout -b tutorial-destiny - git am ~/mininet/util/nox-patches/*.patch + git am ~/mininet/util/nox-patches/*tutorial-port-nox-destiny*.patch + if [ "$DIST" = "Ubuntu" ] && [ `expr $RELEASE '>=' 12.04` = 1 ]; then + git am ~/mininet/util/nox-patches/*nox-ubuntu12-hacks.patch + fi # Build ./boot.sh @@ -277,12 +410,19 @@ function nox { #./nox_core -v -i ptcp: } +# "Install" POX +function pox { + echo "Installing POX into $HOME/pox..." + cd ~ + git clone https://github.com/noxrepo/pox.git +} + # Install OFtest function oftest { echo "Installing oftest..." # Install deps: - sudo apt-get install -y tcpdump python-scapy + $install tcpdump python-scapy # Install oftest: cd ~/ @@ -296,7 +436,7 @@ function oftest { function cbench { echo "Installing cbench..." - sudo apt-get install -y libsnmp-dev libpcap-dev libconfig-dev + $install libsnmp-dev libpcap-dev libconfig-dev cd ~/ git clone git://openflow.org/oflops.git cd oflops @@ -315,13 +455,13 @@ function other { # Install tcpdump and tshark, cmd-line packet dump tools. Also install gitk, # a graphical git history viewer. - sudo apt-get install -y tcpdump tshark gitk + $install tcpdump tshark gitk # Install common text editors - sudo apt-get install -y vim nano emacs + $install vim nano emacs # Install NTP - sudo apt-get install -y ntp + $install ntp # Set git to colorize everything. git config --global color.diff auto @@ -358,8 +498,9 @@ function all { of wireshark ovs - modprobe - nox + # NOX-classic is deprecated, but you can install it manually if desired. + # nox + pox oftest cbench other @@ -412,8 +553,9 @@ function usage { printf -- ' -f: install open(F)low\n' >&2 printf -- ' -h: print this (H)elp message\n' >&2 printf -- ' -k: install new (K)ernel\n' >&2 - printf -- ' -m: install Open vSwitch kernel (M)odule\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 -- ' -r: remove existing Open vSwitch packages\n' >&2 printf -- ' -t: install o(T)her stuff\n' >&2 printf -- ' -v: install open (V)switch\n' >&2 printf -- ' -w: install OpenFlow (w)ireshark dissector\n' >&2 @@ -427,7 +569,7 @@ if [ $# -eq 0 ] then all else - while getopts 'abcdfhkmntvwx' OPTION + while getopts 'abcdfhkmnprtvwx' OPTION do case $OPTION in a) all;; @@ -439,6 +581,8 @@ else k) kernel;; m) modprobe;; n) mn_deps;; + p) pox;; + r) remove_ovs;; t) other;; v) ovs;; w) wireshark;; diff --git a/util/nox-patches/0002-nox-ubuntu12-hacks.patch b/util/nox-patches/0002-nox-ubuntu12-hacks.patch new file mode 100644 index 0000000..77619bc --- /dev/null +++ b/util/nox-patches/0002-nox-ubuntu12-hacks.patch @@ -0,0 +1,175 @@ +From 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 + diff --git a/util/nox-patches/README b/util/nox-patches/README index 5c8dce8..b74a668 100644 --- a/util/nox-patches/README +++ b/util/nox-patches/README @@ -1 +1,2 @@ -This patch adds the OpenFlow tutorial module source code to nox-destiny. +0001: This patch adds the OpenFlow tutorial module source code to nox-destiny. +0002: This patch hacks nox-destiny to compile on Ubuntu 12.04. diff --git a/util/sch_htb-ofbuf/Makefile b/util/sch_htb-ofbuf/Makefile new file mode 100644 index 0000000..c4d714f --- /dev/null +++ b/util/sch_htb-ofbuf/Makefile @@ -0,0 +1,11 @@ +obj-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 diff --git a/util/sch_htb-ofbuf/README b/util/sch_htb-ofbuf/README new file mode 100644 index 0000000..711ed77 --- /dev/null +++ b/util/sch_htb-ofbuf/README @@ -0,0 +1,10 @@ +Modified 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. diff --git a/util/sch_htb-ofbuf/sch_htb.c b/util/sch_htb-ofbuf/sch_htb.c new file mode 100644 index 0000000..baead1c --- /dev/null +++ b/util/sch_htb-ofbuf/sch_htb.c @@ -0,0 +1,1644 @@ +#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"); diff --git a/util/versioncheck.py b/util/versioncheck.py new file mode 100755 index 0000000..d9e5483 --- /dev/null +++ b/util/versioncheck.py @@ -0,0 +1,24 @@ +#!/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( "grep -or 'Mininet \w\.\w\.\w\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 )