Commit a0056b9d authored by Martín Ferrari's avatar Martín Ferrari

Merge branch 'import'

parents 71fcf2ab bbf51768
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
Required packages for using nemu
================================
python-unshare http://pypi.python.org/pypi/python-unshare
python-passfd http://pypi.python.org/pypi/python-passfd
linux-kernel >= 2.6.35 http://kernel.org/
bridge-utils
iproute
procps
xauth Needed only for X11 forwarding support.
COPYING
DEPENDENCIES
examples/sample.py
Makefile
protocol.txt
sample-api.txt
setup.cfg
setup.py
src/nemu/environ.py
src/nemu/__init__.py
src/nemu/interface.py
src/nemu/iproute.py
src/nemu/node.py
src/nemu/protocol.py
src/nemu/subprocess_.py
test/test_core.py
test/test_interfaces.py
test/test_node.py
test/test_protocol.py
test/test_routing.py
test/test_subprocess.py
test/test_switch.py
test/test_util.py
SRC = src
TEST = test
BUILDDIR = build
DISTDIR = dist
COVERDIR = coverage
SRCFILES := $(shell find $(SRC) -type f)
# stupid distutils, it's broken in so many ways
SUBBUILDDIR := $(shell python -c 'import distutils.util, sys; \
print "lib.%s-%s" % (distutils.util.get_platform(), \
sys.version[0:3])')
PYTHON25 := $(shell python -c 'import sys; v = sys.version_info; \
print (1 if v[0] <= 2 and v[1] <= 5 else 0)')
ifeq ($(PYTHON25),0)
BUILDDIR := $(BUILDDIR)/$(SUBBUILDDIR)
else
BUILDDIR := $(BUILDDIR)/lib
endif
COVERAGE := $(or $(shell which coverage), $(shell which python-coverage), \
coverage)
all: build_stamp
build_stamp: $(SRCFILES)
./setup.py build
touch $@
install: all
./setup.py install
test: all
retval=0; \
for i in `find "$(TEST)" -perm -u+x -type f`; do \
echo $$i; \
PYTHONPATH="$(BUILDDIR)" $$i || retval=$$?; \
done; exit $$retval
coverage: coverage_stamp
$(COVERAGE) -r -m `find "$(BUILDDIR)" -name \\*.py -type f`
coverage-report: coverage_stamp
rm -rf $(COVERDIR)
$(COVERAGE) -b -d $(COVERDIR) `find "$(BUILDDIR)" -name \\*.py -type f`
@echo "Coverage report created in $(COVERDIR)/index.html"
coverage_stamp: build_stamp
if [ `id -u` -ne 0 ]; then \
echo "Coverage needs to be run as root."; false; fi
for i in `find "$(TEST)" -perm -u+x -type f`; do \
set -e; \
PYTHONPATH="$(BUILDDIR)" $(COVERAGE) -x $$i; \
done
$(COVERAGE) -c
touch $@
clean:
./setup.py clean
rm -f `find -name \*.pyc` .coverage *.pcap *_stamp
rm -rf $(COVERDIR)
#$(MAKE) -C $(CURDIR)/benchmarks/ clean
distclean: clean
rm -rf "$(DISTDIR)"
MANIFEST: distclean
find . -path ./.hg -prune -o -path ./build -prune -o \
-path ./benchmarks -prune -o \
-name \*.pyc -prune -o -name \*.swp -prune -o \
-name MANIFEST -prune -o -name .hg\* -prune -o \
-type f -print | sed 's#^\./##' | sort > MANIFEST
dist: MANIFEST
./setup.py sdist
.PHONY: clean distclean dist test coverage install MANIFEST
CFLAGS = -Wall -g -O3
all: udp-perf
clean:
rm -f udp-perf *.pyc
.PHONY: clean all
# vim: ts=4:sw=4:et:ai:sts=4
import csv, StringIO, subprocess
class Graph:
[LINE, DOT, POINT, LINEPOINT] = range(0, 4)
def __init__(self):
self._plots = []
self._title = None
def set_title(self, title):
self._title = title
def add(self, plot):
self._plots.append(plot)
def generate(self, output_file):
lines = self.gen_output()
lines.insert(0, "set terminal postscript")
lines.insert(0, "set output '%s'" % filename)
gnuplot = subprocess.Popen(['gnuplot', '-'],
stdin = subprocess.PIPE,
stdout = subprocess.PIPE,
stderr = subprocess.STDOUT)
gnuplot.communicate(input = "\n".join(lines))
def Xplot(self, plotnr):
lines = self.gen_output(plotnr)
lines.insert(0, "set terminal wxt")
lines.append('pause mouse')
gnuplot = subprocess.Popen(['gnuplot', '-'], stdin = subprocess.PIPE)
gnuplot.communicate(input = "\n".join(lines))
def _style_to_str(self, style):
d = {Graph.LINE: 'lines', Graph.DOT: 'dots', Graph.POINT: 'points',
Graph.LINEPOINT: 'linespoints'}
return d[style]
def gen_output(self, plots = None):
if plots:
plots = map(self._plots.__getitem__, plots)
else:
plots = self._plots
lines = []
if self._title:
lines.append("set title '%s'" % self._title)
line = []
for plot in plots:
line.append("'-' title '%s' with %s" % (plot.title(),
self._style_to_str(plot.style())))
lines.append('plot ' + ', '.join(line))
for plot in plots:
for r in plot.data():
r = [str(d) for d in r]
lines.append(' '.join(r))
lines.extend(['e'])
return lines
class Plot:
def __init__(self, title, data, style = Graph.LINE):
self._title = title
self._data = data
self._style = style
def style(self):
return self._style
def title(self):
return self._title
def data(self):
return self._data
class Row:
def __init__(self, data, names = None):
assert not names or len(names) == len(data)
assert not names or all(map(lambda x: isinstance(x, str), names))
self._data1 = list(data)
if names:
self._data2 = dict(zip(names, data))
else:
self._data2 = dict()
def append(self, value, name = None):
self._data1.append(value)
if self._data2:
assert name not in self._data2
self._data2[name] = value
def __getitem__(self, item):
if isinstance(item, int):
return self._data1[item]
else:
return self._data2[item]
def __len__(self):
return len(self._data1)
# def __repr__(self):
# return
class Data:
def __init__(self, rows = [], colnames = []):
assert not (colnames and rows) or len(colnames) == len(rows[0])
self._colnames = colnames
self._data = []
for r in rows:
self.add_row(r)
def add_row(self, row):
if isinstance(row, Row):
self._data.append(row)
else:
self._data.append(Row(row, self._colnames))
def nrows(self):
return len(self._data)
def ncols(self):
return len(self._data[0])
def read_csv(self, stream, has_header = False):
self._data = []
self._colnames = []
self._datadict = []
n = 0
reader = csv.reader(stream)
for line in reader:
if n and len(line) != n:
raise 'Not matching number of columns in different rows'
if not n:
n = len(line)
if has_header:
self._colnames = line
continue
row = []
for i in line:
try:
row.append(float(i))
except:
row.append(i)
self._data.append(row)
if has_header:
self._gen_data_dict()
def write_csv(self, stream):
writer = csv.writer(stream)
writer.writerows(self._data)
def column(self, col):
if isinstance(col, int):
return [row[col] for row in self._data]
else:
return [row[col] for row in self._datadict]
def row(self, row):
return self._data[row]
def cell(self, row, col):
if isinstance(col, int):
return self._data[row][col]
else:
return self._datadict[row][col]
def select(self, cols = None, selectfn = lambda x: True,
groupfn = lambda x: None):
if cols:
cols = list(cols)
else:
cols = range(self.ncols())
groups = {}
res = []
for row in self._data if isinstance(cols[0], int) else self._datadict:
if not selectfn(row):
continue
def add_column(self, fn, colname = None):
if self._colnames:
assert colname
for row in self._data:
row.append(fn(row))
if colname:
self._colname.append(colname)
for row in self._datadict:
row[colname] = fn(row)
return self.ncols() - 1
def uniq(l):
data = []
for i in l:
if i not in data:
data.append(i)
return data
#!/usr/bin/env python
# vim: ts=4:sw=4:et:ai:sts=4
import csv, getopt, nemu, os, os.path, re, select, subprocess, sys
__doc__ = """Creates a linear network topology, and measures the maximum
end-to-end throughput for the specified packet size."""
def usage(f):
f.write("Usage: %s --nodes=NUM [TOPOLOGY_OPTIONS] [TEST_OPTIONS]\n%s\n\n" %
(os.path.basename(sys.argv[0]), __doc__))
f.write("Topology configuration:\n")
f.write(" -n, --nodes=NUM Number of nodes to create (mandatory)\n")
f.write(" --use-p2p Use P2P links, to avoid bridging\n")
f.write(" --delay=SECS Add delay emulation in links\n")
f.write(" --jitter=PERCENT Add jitter emulation in links\n")
f.write(" --bandwidth=BPS Maximum bandwidth of links\n\n")
f.write("Test specification:\n")
f.write(" Parameters take single values or ranges of falues in the form " +
"nnn-NNN, a test\n")
f.write(" will be run for each possible combination.\n")
f.write(" -s, --pktsize=BYTES Size of packet payload (mandatory)\n")
f.write(" --bwlimit=BPS Apply bandwidth limitation in the " +
"traffic generator\n\n")
# background noise
f.write("How long should each test run (defaults to -t 10):\n")
f.write(" -t, --time=SECS Stop after SECS seconds\n")
f.write(" -p, --packets=NUM Stop after NUM packets\n")
f.write(" -b, --bytes=BYTES Stop after BYTES bytes sent\n\n")
f.write("Output format:\n")
f.write(" --format=FMT Valid values are `csv', `brief', " +
"and `verbose'\n")
def main():
error = None
opts = []
try:
opts, args = getopt.getopt(sys.argv[1:], "hn:s:t:p:b:", [
"help", "nodes=", "pktsize=", "time=", "packets=", "bytes=",
"use-p2p", "delay=", "jitter=", "bandwidth=", "format=" ])
except getopt.GetoptError, err:
error = str(err) # opts will be empty
pktsize = nr = time = packets = nbytes = None
delay = jitter = bandwidth = None
use_p2p = False
format = "verbose"
for o, a in opts:
if o in ("-h", "--help"):
usage(sys.stdout)
sys.exit(0)
elif o in ("-n", "--nodes"):
nr = int(a)
if nr > 65:
error = "Invalid value for %s: %s" % (o, a)
break
elif o in ("-s", "--pktsize"):
pktsize = int(a)
elif o in ("-t", "--time"):
time = float(a)
elif o in ("-p", "--packets"):
packets = int(a)
elif o in ("--bytes"):
nbytes = int(a)
elif o in ("--delay"):
delay = float(a)
elif o in ("--jitter"):
jitter = float(a)
elif o in ("--bandwidth"):
bandwidth = float(a)
elif o in ("--use-p2p"):
use_p2p = True
continue # avoid the value check
elif o == "--format":
if a not in ('csv', 'brief', 'verbose'):
error = "Invalid value for %s: %s" % (o, a)
break
format = a
continue
else:
raise RuntimeError("Cannot happen")
# In all cases, I take a number
if float(a) <= 0:
error = "Invalid value for %s: %s" % (o, a)
if not error:
if args:
error = "Unknown argument(s): %s" % " ".join(args)
elif not nr:
error = "Missing mandatory --nodes argument"
elif not pktsize:
error = "Missing mandatory --pktsize argument"
elif use_p2p and (delay or jitter or bandwidth):
error = "Cannot use links emulation with P2P links"
if error:
sys.stderr.write("%s: %s\n" % (os.path.basename(sys.argv[0]), error))
sys.stderr.write("Try `%s --help' for more information.\n" %
os.path.basename(sys.argv[0]))
#usage(sys.stderr)
sys.exit(2)
if not (time or nbytes or packets):
time = 10
udp_perf = nemu.environ.find_bin("udp-perf",
extra_path = [".", os.path.dirname(sys.argv[0])])
if not udp_perf:
raise RuntimeError("Cannot find `udp-perf'")
nodes, interfaces, links = create_topo(nr, use_p2p, delay, jitter,
bandwidth)
cmdline = [udp_perf, "--server"]
if time:
cmdline.append("--max-time=%d" % time)
if packets:
cmdline.append("--max-pkts=%d" % packets)
if nbytes:
cmdline.append("--max-bytes=%d" % nbytes)
if format == "verbose":
cmdline.append("--verbose")
srv = nodes[0].Popen(cmdline, stdout = subprocess.PIPE)
cmdline = [udp_perf, "--client", "--pktsize=%d" % pktsize]
if nr > 1:
cmdline.append("--host=10.0.0.1")
clt = nodes[nr - 1].Popen(cmdline)
out = ""
while(True):
ready = select.select([srv.stdout], [], [], 0.1)[0]
if ready:
r = os.read(srv.stdout.fileno(), 1024)
if not r:
break
out += r
if srv.poll() != None or clt.poll() != None:
break
if srv.poll():
raise RuntimeError("upd-perf server returned with error.")
if clt.poll():
raise RuntimeError("upd-perf client returned with error.")
srv.wait()
clt.wait()
out = out.strip()
if format != "csv":
print "Command line: %s" % (" ".join(sys.argv[1:]))
print out.strip()
return
data = out.split(" ")
data = dict(map(lambda s: s.partition(":")[::2], data))
if sorted(data.keys()) != sorted(["brx", "prx", "pksz", "plsz", "err",
"mind", "avgd", "maxd", "jit", "time"]):
raise RuntimeError("Invalid output from udp-perf")
data["nodes"] = nr
data["bridge"] = int(not use_p2p)
data["cfg_dly"] = delay if delay else ""
data["cfg_bw"] = bandwidth if bandwidth else ""
data["cfg_jit"] = jitter if jitter else ""
res = []
for i in ["nodes", "bridge", "cfg_dly", "cfg_bw", "cfg_jit",
"brx", "prx", "pksz", "plsz", "err", "mind", "avgd",
"maxd", "jit", "time"]:
res.append(data[i])
writer = csv.writer(sys.stdout)
writer.writerow(res)
def ip2dec(ip):
match = re.search(r'^(\d+)\.(\d+)\.(\d+)\.(\d+)$', ip)
assert match
return long(match.group(1)) * 2**24 + long(match.group(2)) * 2**16 + \
long(match.group(3)) * 2**8 + long(match.group(4))
def dec2ip(dec):
res = [None] * 4
for i in range(4):
res[3 - i] = dec % 256
dec >>= 8
return "%d.%d.%d.%d" % tuple(res)
def create_topo(n, p2p, delay, jitter, bw):
nodes = []
interfaces = []
links = []
for i in range(n):
nodes.append(nemu.Node())
if p2p:
interfaces = [[None]]
for i in range(n - 1):
a, b = nemu.P2PInterface.create_pair(nodes[i], nodes[i + 1])
interfaces[i].append(a)
interfaces.append([])
interfaces[i + 1] = [b]
interfaces[n - 1].append(None)
else:
for i in range(n):
if i > 0:
left = nodes[i].add_if()
else:
left = None
if i < n - 1:
right = nodes[i].add_if()
else:
right = None
interfaces.append((left, right))
for i in range(n - 1):
links = nemu.Switch(bandwidth = bw, delay = delay,
delay_jitter = jitter)
links.up = True
links.connect(interfaces[i][1])
links.connect(interfaces[i + 1][0])
links.append(links)
for i in range(n):
for j in (0, 1):
if interfaces[i][j]:
interfaces[i][j].up = True
ip = ip2dec("10.0.0.1")
for i in range(n - 1):
interfaces[i][1].add_v4_address(dec2ip(ip), 30)
interfaces[i + 1][0].add_v4_address(dec2ip(ip + 1), 30)
ip += 4
ipbase = ip2dec("10.0.0.0")
lastnet = dec2ip(ipbase + 4 * (n - 2))
for i in range(n - 2):
nodes[i].add_route(prefix = lastnet, prefix_len = 30,
nexthop = dec2ip(ipbase + 4 * i + 2))
nodes[n - 1 - i].add_route(prefix = "10.0.0.0", prefix_len = 30,
nexthop = dec2ip(ipbase + (n - 2 - i) * 4 + 1))
return nodes, interfaces, links
if __name__ == "__main__":
main()
set terminal postscript colour enhanced landscape lw 1 10
set key box left top width 1 title 'Experiment'
set xlabel 'Number of nodes'
set ylabel 'Processing cost per packet (10E-6 sec)'
set title 'Processing cost for 1000-byte packets'
set xrange [0:35]
plot \
'results-simu.txt' index 0 every 8::1 using 2:3 title "Exp 1" with linespoints, \
'results-simu.txt' index 1 every 8::1 using 2:3 title "Exp 2" with linespoints, \
'results-simu.txt' index 2 every 8::1 using 2:3 title "Exp 3" with linespoints, \
'results.txt' index 0 every 13::11 using 1:($10/$3) title "Exp 4" with linespoints, \
'results.txt' index 1 every 13::11 using 1:($10/$3) title "Exp 4bis" with linespoints
%!PS-Adobe-2.0
%%Creator: gnuplot 4.2 patchlevel 3
%%CreationDate: Mon Aug 16 13:02:27 2010
%%DocumentFonts: (atend)
%%BoundingBox: 50 50 554 770
%%Orientation: Landscape
%%Pages: (atend)
%%EndComments
%%BeginProlog
/gnudict 256 dict def
gnudict begin
%
% The following 6 true/false flags may be edited by hand if required
% The unit line width may also be changed
%
/Color true def
/Blacktext false def
/Solid false def
/Dashlength 1 def
/Landscape true def
/Level1 false def
/Rounded false def
/TransparentPatterns false def
/gnulinewidth 5.000 def
/userlinewidth gnulinewidth def
%
/vshift -33 def
/dl1 {
10.0 Dashlength mul mul
Rounded { currentlinewidth 0.75 mul sub dup 0 le { pop 0.01 } if } if
} def
/dl2 {
10.0 Dashlength mul mul
Rounded { currentlinewidth 0.75 mul add } if
} def
/hpt_ 31.5 def
/vpt_ 31.5 def
/hpt hpt_ def
/vpt vpt_ def
Level1 {} {
/SDict 10 dict def
systemdict /pdfmark known not {
userdict /pdfmark systemdict /cleartomark get put
} if
SDict begin [
/Title ()
/Subject (gnuplot plot)
/Creator (gnuplot 4.2 patchlevel 3 )
/Author (Martin_Hernan Ferrari,L133,2417)
% /Producer (gnuplot)
% /Keywords ()
/CreationDate (Mon Aug 16 13:02:27 2010)
/DOCINFO pdfmark
end
} ifelse
%
% Gnuplot Prolog Version 4.2 (August 2006)
%
/M {moveto} bind def
/L {lineto} bind def
/R {rmoveto} bind def
/V {rlineto} bind def
/N {newpath moveto} bind def
/Z {closepath} bind def
/C {setrgbcolor} bind def
/f {rlineto fill} bind def
/vpt2 vpt 2 mul def
/hpt2 hpt 2 mul def
/Lshow {currentpoint stroke M 0 vshift R
Blacktext {gsave 0 setgray show grestore} {show} ifelse} def
/Rshow {currentpoint stroke M dup stringwidth pop neg vshift R
Blacktext {gsave 0 setgray show grestore} {show} ifelse} def
/Cshow {currentpoint stroke M dup stringwidth pop -2 div vshift R
Blacktext {gsave 0 setgray show grestore} {show} ifelse} def
/UP {dup vpt_ mul /vpt exch def hpt_ mul /hpt exch def
/hpt2 hpt 2 mul def /vpt2 vpt 2 mul def} def
/DL {Color {setrgbcolor Solid {pop []} if 0 setdash}
{pop pop pop 0 setgray Solid {pop []} if 0 setdash} ifelse} def
/BL {stroke userlinewidth 2 mul setlinewidth
Rounded {1 setlinejoin 1 setlinecap} if} def
/AL {stroke userlinewidth 2 div setlinewidth
Rounded {1 setlinejoin 1 setlinecap} if} def
/UL {dup gnulinewidth mul /userlinewidth exch def
dup 1 lt {pop 1} if 10 mul /udl exch def} def
/PL {stroke userlinewidth setlinewidth
Rounded {1 setlinejoin 1 setlinecap} if} def
% Default Line colors
/LCw {1 1 1} def
/LCb {0 0 0} def
/LCa {0 0 0} def
/LC0 {1 0 0} def
/LC1 {0 1 0} def
/LC2 {0 0 1} def
/LC3 {1 0 1} def
/LC4 {0 1 1} def
/LC5 {1 1 0} def
/LC6 {0 0 0} def
/LC7 {1 0.3 0} def
/LC8 {0.5 0.5 0.5} def
% Default Line Types
/LTw {PL [] 1 setgray} def
/LTb {BL [] LCb DL} def
/LTa {AL [1 udl mul 2 udl mul] 0 setdash LCa setrgbcolor} def
/LT0 {PL [] LC0 DL} def
/LT1 {PL [4 dl1 2 dl2] LC1 DL} def
/LT2 {PL [2 dl1 3 dl2] LC2 DL} def
/LT3 {PL [1 dl1 1.5 dl2] LC3 DL} def
/LT4 {PL [6 dl1 2 dl2 1 dl1 2 dl2] LC4 DL} def
/LT5 {PL [3 dl1 3 dl2 1 dl1 3 dl2] LC5 DL} def
/LT6 {PL [2 dl1 2 dl2 2 dl1 6 dl2] LC6 DL} def
/LT7 {PL [1 dl1 2 dl2 6 dl1 2 dl2 1 dl1 2 dl2] LC7 DL} def
/LT8 {PL [2 dl1 2 dl2 2 dl1 2 dl2 2 dl1 2 dl2 2 dl1 4 dl2] LC8 DL} def
/Pnt {stroke [] 0 setdash gsave 1 setlinecap M 0 0 V stroke grestore} def
/Dia {stroke [] 0 setdash 2 copy vpt add M
hpt neg vpt neg V hpt vpt neg V
hpt vpt V hpt neg vpt V closepath stroke
Pnt} def
/Pls {stroke [] 0 setdash vpt sub M 0 vpt2 V
currentpoint stroke M
hpt neg vpt neg R hpt2 0 V stroke
} def
/Box {stroke [] 0 setdash 2 copy exch hpt sub exch vpt add M
0 vpt2 neg V hpt2 0 V 0 vpt2 V
hpt2 neg 0 V closepath stroke
Pnt} def
/Crs {stroke [] 0 setdash exch hpt sub exch vpt add M
hpt2 vpt2 neg V currentpoint stroke M
hpt2 neg 0 R hpt2 vpt2 V stroke} def
/TriU {stroke [] 0 setdash 2 copy vpt 1.12 mul add M
hpt neg vpt -1.62 mul V
hpt 2 mul 0 V
hpt neg vpt 1.62 mul V closepath stroke
Pnt} def
/Star {2 copy Pls Crs} def
/BoxF {stroke [] 0 setdash exch hpt sub exch vpt add M
0 vpt2 neg V hpt2 0 V 0 vpt2 V
hpt2 neg 0 V closepath fill} def
/TriUF {stroke [] 0 setdash vpt 1.12 mul add M
hpt neg vpt -1.62 mul V
hpt 2 mul 0 V
hpt neg vpt 1.62 mul V closepath fill} def
/TriD {stroke [] 0 setdash 2 copy vpt 1.12 mul sub M
hpt neg vpt 1.62 mul V
hpt 2 mul 0 V
hpt neg vpt -1.62 mul V closepath stroke
Pnt} def
/TriDF {stroke [] 0 setdash vpt 1.12 mul sub M
hpt neg vpt 1.62 mul V
hpt 2 mul 0 V
hpt neg vpt -1.62 mul V closepath fill} def
/DiaF {stroke [] 0 setdash vpt add M
hpt neg vpt neg V hpt vpt neg V
hpt vpt V hpt neg vpt V closepath fill} def
/Pent {stroke [] 0 setdash 2 copy gsave
translate 0 hpt M 4 {72 rotate 0 hpt L} repeat
closepath stroke grestore Pnt} def
/PentF {stroke [] 0 setdash gsave
translate 0 hpt M 4 {72 rotate 0 hpt L} repeat
closepath fill grestore} def
/Circle {stroke [] 0 setdash 2 copy
hpt 0 360 arc stroke Pnt} def
/CircleF {stroke [] 0 setdash hpt 0 360 arc fill} def
/C0 {BL [] 0 setdash 2 copy moveto vpt 90 450 arc} bind def
/C1 {BL [] 0 setdash 2 copy moveto
2 copy vpt 0 90 arc closepath fill
vpt 0 360 arc closepath} bind def
/C2 {BL [] 0 setdash 2 copy moveto
2 copy vpt 90 180 arc closepath fill
vpt 0 360 arc closepath} bind def
/C3 {BL [] 0 setdash 2 copy moveto
2 copy vpt 0 180 arc closepath fill
vpt 0 360 arc closepath} bind def
/C4 {BL [] 0 setdash 2 copy moveto
2 copy vpt 180 270 arc closepath fill
vpt 0 360 arc closepath} bind def
/C5 {BL [] 0 setdash 2 copy moveto
2 copy vpt 0 90 arc
2 copy moveto
2 copy vpt 180 270 arc closepath fill
vpt 0 360 arc} bind def
/C6 {BL [] 0 setdash 2 copy moveto
2 copy vpt 90 270 arc closepath fill
vpt 0 360 arc closepath} bind def
/C7 {BL [] 0 setdash 2 copy moveto
2 copy vpt 0 270 arc closepath fill
vpt 0 360 arc closepath} bind def
/C8 {BL [] 0 setdash 2 copy moveto
2 copy vpt 270 360 arc closepath fill
vpt 0 360 arc closepath} bind def
/C9 {BL [] 0 setdash 2 copy moveto
2 copy vpt 270 450 arc closepath fill
vpt 0 360 arc closepath} bind def
/C10 {BL [] 0 setdash 2 copy 2 copy moveto vpt 270 360 arc closepath fill
2 copy moveto
2 copy vpt 90 180 arc closepath fill
vpt 0 360 arc closepath} bind def
/C11 {BL [] 0 setdash 2 copy moveto
2 copy vpt 0 180 arc closepath fill
2 copy moveto
2 copy vpt 270 360 arc closepath fill
vpt 0 360 arc closepath} bind def
/C12 {BL [] 0 setdash 2 copy moveto
2 copy vpt 180 360 arc closepath fill
vpt 0 360 arc closepath} bind def
/C13 {BL [] 0 setdash 2 copy moveto
2 copy vpt 0 90 arc closepath fill
2 copy moveto
2 copy vpt 180 360 arc closepath fill
vpt 0 360 arc closepath} bind def
/C14 {BL [] 0 setdash 2 copy moveto
2 copy vpt 90 360 arc closepath fill
vpt 0 360 arc} bind def
/C15 {BL [] 0 setdash 2 copy vpt 0 360 arc closepath fill
vpt 0 360 arc closepath} bind def
/Rec {newpath 4 2 roll moveto 1 index 0 rlineto 0 exch rlineto
neg 0 rlineto closepath} bind def
/Square {dup Rec} bind def
/Bsquare {vpt sub exch vpt sub exch vpt2 Square} bind def
/S0 {BL [] 0 setdash 2 copy moveto 0 vpt rlineto BL Bsquare} bind def
/S1 {BL [] 0 setdash 2 copy vpt Square fill Bsquare} bind def
/S2 {BL [] 0 setdash 2 copy exch vpt sub exch vpt Square fill Bsquare} bind def
/S3 {BL [] 0 setdash 2 copy exch vpt sub exch vpt2 vpt Rec fill Bsquare} bind def
/S4 {BL [] 0 setdash 2 copy exch vpt sub exch vpt sub vpt Square fill Bsquare} bind def
/S5 {BL [] 0 setdash 2 copy 2 copy vpt Square fill
exch vpt sub exch vpt sub vpt Square fill Bsquare} bind def
/S6 {BL [] 0 setdash 2 copy exch vpt sub exch vpt sub vpt vpt2 Rec fill Bsquare} bind def
/S7 {BL [] 0 setdash 2 copy exch vpt sub exch vpt sub vpt vpt2 Rec fill
2 copy vpt Square fill Bsquare} bind def
/S8 {BL [] 0 setdash 2 copy vpt sub vpt Square fill Bsquare} bind def
/S9 {BL [] 0 setdash 2 copy vpt sub vpt vpt2 Rec fill Bsquare} bind def
/S10 {BL [] 0 setdash 2 copy vpt sub vpt Square fill 2 copy exch vpt sub exch vpt Square fill
Bsquare} bind def
/S11 {BL [] 0 setdash 2 copy vpt sub vpt Square fill 2 copy exch vpt sub exch vpt2 vpt Rec fill
Bsquare} bind def
/S12 {BL [] 0 setdash 2 copy exch vpt sub exch vpt sub vpt2 vpt Rec fill Bsquare} bind def
/S13 {BL [] 0 setdash 2 copy exch vpt sub exch vpt sub vpt2 vpt Rec fill
2 copy vpt Square fill Bsquare} bind def
/S14 {BL [] 0 setdash 2 copy exch vpt sub exch vpt sub vpt2 vpt Rec fill
2 copy exch vpt sub exch vpt Square fill Bsquare} bind def
/S15 {BL [] 0 setdash 2 copy Bsquare fill Bsquare} bind def
/D0 {gsave translate 45 rotate 0 0 S0 stroke grestore} bind def
/D1 {gsave translate 45 rotate 0 0 S1 stroke grestore} bind def
/D2 {gsave translate 45 rotate 0 0 S2 stroke grestore} bind def
/D3 {gsave translate 45 rotate 0 0 S3 stroke grestore} bind def
/D4 {gsave translate 45 rotate 0 0 S4 stroke grestore} bind def
/D5 {gsave translate 45 rotate 0 0 S5 stroke grestore} bind def
/D6 {gsave translate 45 rotate 0 0 S6 stroke grestore} bind def
/D7 {gsave translate 45 rotate 0 0 S7 stroke grestore} bind def
/D8 {gsave translate 45 rotate 0 0 S8 stroke grestore} bind def
/D9 {gsave translate 45 rotate 0 0 S9 stroke grestore} bind def
/D10 {gsave translate 45 rotate 0 0 S10 stroke grestore} bind def
/D11 {gsave translate 45 rotate 0 0 S11 stroke grestore} bind def
/D12 {gsave translate 45 rotate 0 0 S12 stroke grestore} bind def
/D13 {gsave translate 45 rotate 0 0 S13 stroke grestore} bind def
/D14 {gsave translate 45 rotate 0 0 S14 stroke grestore} bind def
/D15 {gsave translate 45 rotate 0 0 S15 stroke grestore} bind def
/DiaE {stroke [] 0 setdash vpt add M
hpt neg vpt neg V hpt vpt neg V
hpt vpt V hpt neg vpt V closepath stroke} def
/BoxE {stroke [] 0 setdash exch hpt sub exch vpt add M
0 vpt2 neg V hpt2 0 V 0 vpt2 V
hpt2 neg 0 V closepath stroke} def
/TriUE {stroke [] 0 setdash vpt 1.12 mul add M
hpt neg vpt -1.62 mul V
hpt 2 mul 0 V
hpt neg vpt 1.62 mul V closepath stroke} def
/TriDE {stroke [] 0 setdash vpt 1.12 mul sub M
hpt neg vpt 1.62 mul V
hpt 2 mul 0 V
hpt neg vpt -1.62 mul V closepath stroke} def
/PentE {stroke [] 0 setdash gsave
translate 0 hpt M 4 {72 rotate 0 hpt L} repeat
closepath stroke grestore} def
/CircE {stroke [] 0 setdash
hpt 0 360 arc stroke} def
/Opaque {gsave closepath 1 setgray fill grestore 0 setgray closepath} def
/DiaW {stroke [] 0 setdash vpt add M
hpt neg vpt neg V hpt vpt neg V
hpt vpt V hpt neg vpt V Opaque stroke} def
/BoxW {stroke [] 0 setdash exch hpt sub exch vpt add M
0 vpt2 neg V hpt2 0 V 0 vpt2 V
hpt2 neg 0 V Opaque stroke} def
/TriUW {stroke [] 0 setdash vpt 1.12 mul add M
hpt neg vpt -1.62 mul V
hpt 2 mul 0 V
hpt neg vpt 1.62 mul V Opaque stroke} def
/TriDW {stroke [] 0 setdash vpt 1.12 mul sub M
hpt neg vpt 1.62 mul V
hpt 2 mul 0 V
hpt neg vpt -1.62 mul V Opaque stroke} def
/PentW {stroke [] 0 setdash gsave
translate 0 hpt M 4 {72 rotate 0 hpt L} repeat
Opaque stroke grestore} def
/CircW {stroke [] 0 setdash
hpt 0 360 arc Opaque stroke} def
/BoxFill {gsave Rec 1 setgray fill grestore} def
/Density {
/Fillden exch def
currentrgbcolor
/ColB exch def /ColG exch def /ColR exch def
/ColR ColR Fillden mul Fillden sub 1 add def
/ColG ColG Fillden mul Fillden sub 1 add def
/ColB ColB Fillden mul Fillden sub 1 add def
ColR ColG ColB setrgbcolor} def
/BoxColFill {gsave Rec PolyFill} def
/PolyFill {gsave Density fill grestore grestore} def
/h {rlineto rlineto rlineto gsave closepath fill grestore} bind def
%
% PostScript Level 1 Pattern Fill routine for rectangles
% Usage: x y w h s a XX PatternFill
% x,y = lower left corner of box to be filled
% w,h = width and height of box
% a = angle in degrees between lines and x-axis
% XX = 0/1 for no/yes cross-hatch
%
/PatternFill {gsave /PFa [ 9 2 roll ] def
PFa 0 get PFa 2 get 2 div add PFa 1 get PFa 3 get 2 div add translate
PFa 2 get -2 div PFa 3 get -2 div PFa 2 get PFa 3 get Rec
gsave 1 setgray fill grestore clip
currentlinewidth 0.5 mul setlinewidth
/PFs PFa 2 get dup mul PFa 3 get dup mul add sqrt def
0 0 M PFa 5 get rotate PFs -2 div dup translate
0 1 PFs PFa 4 get div 1 add floor cvi
{PFa 4 get mul 0 M 0 PFs V} for
0 PFa 6 get ne {
0 1 PFs PFa 4 get div 1 add floor cvi
{PFa 4 get mul 0 2 1 roll M PFs 0 V} for
} if
stroke grestore} def
%
/languagelevel where
{pop languagelevel} {1} ifelse
2 lt
{/InterpretLevel1 true def}
{/InterpretLevel1 Level1 def}
ifelse
%
% PostScript level 2 pattern fill definitions
%
/Level2PatternFill {
/Tile8x8 {/PaintType 2 /PatternType 1 /TilingType 1 /BBox [0 0 8 8] /XStep 8 /YStep 8}
bind def
/KeepColor {currentrgbcolor [/Pattern /DeviceRGB] setcolorspace} bind def
<< Tile8x8
/PaintProc {0.5 setlinewidth pop 0 0 M 8 8 L 0 8 M 8 0 L stroke}
>> matrix makepattern
/Pat1 exch def
<< Tile8x8
/PaintProc {0.5 setlinewidth pop 0 0 M 8 8 L 0 8 M 8 0 L stroke
0 4 M 4 8 L 8 4 L 4 0 L 0 4 L stroke}
>> matrix makepattern
/Pat2 exch def
<< Tile8x8
/PaintProc {0.5 setlinewidth pop 0 0 M 0 8 L
8 8 L 8 0 L 0 0 L fill}
>> matrix makepattern
/Pat3 exch def
<< Tile8x8
/PaintProc {0.5 setlinewidth pop -4 8 M 8 -4 L
0 12 M 12 0 L stroke}
>> matrix makepattern
/Pat4 exch def
<< Tile8x8
/PaintProc {0.5 setlinewidth pop -4 0 M 8 12 L
0 -4 M 12 8 L stroke}
>> matrix makepattern
/Pat5 exch def
<< Tile8x8
/PaintProc {0.5 setlinewidth pop -2 8 M 4 -4 L
0 12 M 8 -4 L 4 12 M 10 0 L stroke}
>> matrix makepattern
/Pat6 exch def
<< Tile8x8
/PaintProc {0.5 setlinewidth pop -2 0 M 4 12 L
0 -4 M 8 12 L 4 -4 M 10 8 L stroke}
>> matrix makepattern
/Pat7 exch def
<< Tile8x8
/PaintProc {0.5 setlinewidth pop 8 -2 M -4 4 L
12 0 M -4 8 L 12 4 M 0 10 L stroke}
>> matrix makepattern
/Pat8 exch def
<< Tile8x8
/PaintProc {0.5 setlinewidth pop 0 -2 M 12 4 L
-4 0 M 12 8 L -4 4 M 8 10 L stroke}
>> matrix makepattern
/Pat9 exch def
/Pattern1 {PatternBgnd KeepColor Pat1 setpattern} bind def
/Pattern2 {PatternBgnd KeepColor Pat2 setpattern} bind def
/Pattern3 {PatternBgnd KeepColor Pat3 setpattern} bind def
/Pattern4 {PatternBgnd KeepColor Landscape {Pat5} {Pat4} ifelse setpattern} bind def
/Pattern5 {PatternBgnd KeepColor Landscape {Pat4} {Pat5} ifelse setpattern} bind def
/Pattern6 {PatternBgnd KeepColor Landscape {Pat9} {Pat6} ifelse setpattern} bind def
/Pattern7 {PatternBgnd KeepColor Landscape {Pat8} {Pat7} ifelse setpattern} bind def
} def
%
%
%End of PostScript Level 2 code
%
/PatternBgnd {
TransparentPatterns {} {gsave 1 setgray fill grestore} ifelse
} def
%
% Substitute for Level 2 pattern fill codes with
% grayscale if Level 2 support is not selected.
%
/Level1PatternFill {
/Pattern1 {0.250 Density} bind def
/Pattern2 {0.500 Density} bind def
/Pattern3 {0.750 Density} bind def
/Pattern4 {0.125 Density} bind def
/Pattern5 {0.375 Density} bind def
/Pattern6 {0.625 Density} bind def
/Pattern7 {0.875 Density} bind def
} def
%
% Now test for support of Level 2 code
%
Level1 {Level1PatternFill} {Level2PatternFill} ifelse
%
/Symbol-Oblique /Symbol findfont [1 0 .167 1 0 0] makefont
dup length dict begin {1 index /FID eq {pop pop} {def} ifelse} forall
currentdict end definefont pop
/MFshow {
{ dup 5 get 3 ge
{ 5 get 3 eq {gsave} {grestore} ifelse }
{dup dup 0 get findfont exch 1 get scalefont setfont
[ currentpoint ] exch dup 2 get 0 exch R dup 5 get 2 ne {dup dup 6
get exch 4 get {show} {stringwidth pop 0 R} ifelse }if dup 5 get 0 eq
{dup 3 get {2 get neg 0 exch R pop} {pop aload pop M} ifelse} {dup 5
get 1 eq {dup 2 get exch dup 3 get exch 6 get stringwidth pop -2 div
dup 0 R} {dup 6 get stringwidth pop -2 div 0 R 6 get
show 2 index {aload pop M neg 3 -1 roll neg R pop pop} {pop pop pop
pop aload pop M} ifelse }ifelse }ifelse }
ifelse }
forall} bind def
/MFwidth {0 exch { dup 5 get 3 ge { 5 get 3 eq { 0 } { pop } ifelse }
{dup 3 get{dup dup 0 get findfont exch 1 get scalefont setfont
6 get stringwidth pop add} {pop} ifelse} ifelse} forall} bind def
/MLshow { currentpoint stroke M
0 exch R
Blacktext {gsave 0 setgray MFshow grestore} {MFshow} ifelse } bind def
/MRshow { currentpoint stroke M
exch dup MFwidth neg 3 -1 roll R
Blacktext {gsave 0 setgray MFshow grestore} {MFshow} ifelse } bind def
/MCshow { currentpoint stroke M
exch dup MFwidth -2 div 3 -1 roll R
Blacktext {gsave 0 setgray MFshow grestore} {MFshow} ifelse } bind def
/XYsave { [( ) 1 2 true false 3 ()] } bind def
/XYrestore { [( ) 1 2 true false 4 ()] } bind def
end
%%EndProlog
%%Page: 1 1
gnudict begin
gsave
50 50 translate
0.100 0.100 scale
90 rotate
0 -5040 translate
0 setgray
newpath
(Helvetica) findfont 100 scalefont setfont
1.000 UL
LTb
510 300 M
63 0 V
6457 0 R
-63 0 V
stroke
450 300 M
[ [(Helvetica) 100.0 0.0 true true 0 ( 0)]
] -33.3 MRshow
1.000 UL
LTb
510 934 M
63 0 V
6457 0 R
-63 0 V
stroke
450 934 M
[ [(Helvetica) 100.0 0.0 true true 0 ( 50)]
] -33.3 MRshow
1.000 UL
LTb
510 1569 M
63 0 V
6457 0 R
-63 0 V
stroke
450 1569 M
[ [(Helvetica) 100.0 0.0 true true 0 ( 100)]
] -33.3 MRshow
1.000 UL
LTb
510 2203 M
63 0 V
6457 0 R
-63 0 V
stroke
450 2203 M
[ [(Helvetica) 100.0 0.0 true true 0 ( 150)]
] -33.3 MRshow
1.000 UL
LTb
510 2837 M
63 0 V
6457 0 R
-63 0 V
stroke
450 2837 M
[ [(Helvetica) 100.0 0.0 true true 0 ( 200)]
] -33.3 MRshow
1.000 UL
LTb
510 3471 M
63 0 V
6457 0 R
-63 0 V
stroke
450 3471 M
[ [(Helvetica) 100.0 0.0 true true 0 ( 250)]
] -33.3 MRshow
1.000 UL
LTb
510 4106 M
63 0 V
6457 0 R
-63 0 V
stroke
450 4106 M
[ [(Helvetica) 100.0 0.0 true true 0 ( 300)]
] -33.3 MRshow
1.000 UL
LTb
510 4740 M
63 0 V
6457 0 R
-63 0 V
stroke
450 4740 M
[ [(Helvetica) 100.0 0.0 true true 0 ( 350)]
] -33.3 MRshow
1.000 UL
LTb
510 300 M
0 63 V
0 4377 R
0 -63 V
stroke
510 200 M
[ [(Helvetica) 100.0 0.0 true true 0 ( 0)]
] -33.3 MCshow
1.000 UL
LTb
1441 300 M
0 63 V
0 4377 R
0 -63 V
stroke
1441 200 M
[ [(Helvetica) 100.0 0.0 true true 0 ( 5)]
] -33.3 MCshow
1.000 UL
LTb
2373 300 M
0 63 V
0 4377 R
0 -63 V
stroke
2373 200 M
[ [(Helvetica) 100.0 0.0 true true 0 ( 10)]
] -33.3 MCshow
1.000 UL
LTb
3304 300 M
0 63 V
0 4377 R
0 -63 V
stroke
3304 200 M
[ [(Helvetica) 100.0 0.0 true true 0 ( 15)]
] -33.3 MCshow
1.000 UL
LTb
4236 300 M
0 63 V
0 4377 R
0 -63 V
stroke
4236 200 M
[ [(Helvetica) 100.0 0.0 true true 0 ( 20)]
] -33.3 MCshow
1.000 UL
LTb
5167 300 M
0 63 V
0 4377 R
0 -63 V
stroke
5167 200 M
[ [(Helvetica) 100.0 0.0 true true 0 ( 25)]
] -33.3 MCshow
1.000 UL
LTb
6099 300 M
0 63 V
0 4377 R
0 -63 V
stroke
6099 200 M
[ [(Helvetica) 100.0 0.0 true true 0 ( 30)]
] -33.3 MCshow
1.000 UL
LTb
7030 300 M
0 63 V
0 4377 R
0 -63 V
stroke
7030 200 M
[ [(Helvetica) 100.0 0.0 true true 0 ( 35)]
] -33.3 MCshow
1.000 UL
LTb
1.000 UL
LTb
510 4740 N
510 300 L
6520 0 V
0 4440 V
-6520 0 V
Z stroke
LCb setrgbcolor
100 2520 M
currentpoint gsave translate 90 rotate 0 0 moveto
[ [(Helvetica) 100.0 0.0 true true 0 (Processing cost per packet \(10E-6 sec\))]
] -33.3 MCshow
grestore
LTb
LCb setrgbcolor
3770 50 M
[ [(Helvetica) 100.0 0.0 true true 0 (Number of nodes)]
] -33.3 MCshow
LTb
3770 4890 M
[ [(Helvetica) 100.0 0.0 true true 0 (Processing cost for 1000-byte packets)]
] -33.3 MCshow
1.000 UP
1.000 UL
LTb
1051 4627 M
[ [(Helvetica) 100.0 0.0 true true 0 (Experiment)]
] -33.3 MCshow
1.000 UL
LTb
570 4077 N
0 600 V
963 0 V
0 -600 V
-963 0 V
Z stroke
570 4577 M
963 0 V
1.000 UP
stroke
LT0
LTb
1110 4527 M
[ [(Helvetica) 100.0 0.0 true true 0 (Exp 1)]
] -33.3 MRshow
LT0
1170 4527 M
303 0 V
696 442 M
187 43 V
186 92 V
186 82 V
1118 526 V
1863 865 V
696 442 Pls
883 485 Pls
1069 577 Pls
1255 659 Pls
2373 1185 Pls
4236 2050 Pls
1321 4527 Pls
1.000 UP
1.000 UL
LT1
LTb
1110 4427 M
[ [(Helvetica) 100.0 0.0 true true 0 (Exp 2)]
] -33.3 MRshow
LT1
1170 4427 M
303 0 V
696 583 M
187 77 V
186 155 V
186 103 V
1118 660 V
4236 2684 L
696 583 Crs
883 660 Crs
1069 815 Crs
1255 918 Crs
2373 1578 Crs
4236 2684 Crs
1321 4427 Crs
1.000 UP
1.000 UL
LT2
LTb
1110 4327 M
[ [(Helvetica) 100.0 0.0 true true 0 (Exp 3)]
] -33.3 MRshow
LT2
1170 4327 M
303 0 V
696 521 M
883 726 L
186 189 V
186 188 V
2373 2361 L
4236 4119 L
696 521 Star
883 726 Star
1069 915 Star
1255 1103 Star
2373 2361 Star
4236 4119 Star
1321 4327 Star
1.000 UP
1.000 UL
LT3
LTb
1110 4227 M
[ [(Helvetica) 100.0 0.0 true true 0 (Exp 4)]
] -33.3 MRshow
LT3
1170 4227 M
303 0 V
696 456 M
187 23 V
186 30 V
186 28 V
373 53 V
372 53 V
745 129 V
746 108 V
2980 460 V
559 128 V
696 456 Box
883 479 Box
1069 509 Box
1255 537 Box
1628 590 Box
2000 643 Box
2745 772 Box
3491 880 Box
6471 1340 Box
1321 4227 Box
1.000 UP
1.000 UL
LT4
LTb
1110 4127 M
[ [(Helvetica) 100.0 0.0 true true 0 (Exp 4bis)]
] -33.3 MRshow
LT4
1170 4127 M
303 0 V
696 457 M
187 67 V
186 69 V
186 60 V
373 146 V
372 99 V
745 248 V
746 256 V
2980 957 V
559 198 V
696 457 BoxF
883 524 BoxF
1069 593 BoxF
1255 653 BoxF
1628 799 BoxF
2000 898 BoxF
2745 1146 BoxF
3491 1402 BoxF
6471 2359 BoxF
1321 4127 BoxF
1.000 UL
LTb
510 4740 N
510 300 L
6520 0 V
0 4440 V
-6520 0 V
Z stroke
1.000 UP
1.000 UL
LTb
stroke
grestore
end
showpage
%%Trailer
%%DocumentFonts: Helvetica
%%Pages: 1
set terminal postscript colour enhanced landscape lw 1 10
set key box left top width 1 title 'Experiment'
set xlabel 'Payload size (UDP packet)'
set ylabel 'Processing cost per packet (10E-6 sec)'
set title 'Processing cost for a 4-node topology'
set xrange [0:1500]
plot \
'results-simu.txt' index 0 every ::24::31 using 1:3 title "Exp 1" with linespoints, \
'results-simu.txt' index 1 every ::24::31 using 1:3 title "Exp 2" with linespoints, \
'results-simu.txt' index 3 every ::24::31 using 1:3 title "Exp 3" with linespoints, \
'results.txt' index 0 every ::39::51 using ($4-42):($10/$3) title "Exp 4" with linespoints, \
'results.txt' index 1 every ::39::51 using ($4-42):($10/$3) title "Exp 4bis" with linespoints
%!PS-Adobe-2.0
%%Creator: gnuplot 4.2 patchlevel 3
%%CreationDate: Mon Aug 16 13:02:31 2010
%%DocumentFonts: (atend)
%%BoundingBox: 50 50 554 770
%%Orientation: Landscape
%%Pages: (atend)
%%EndComments
%%BeginProlog
/gnudict 256 dict def
gnudict begin
%
% The following 6 true/false flags may be edited by hand if required
% The unit line width may also be changed
%
/Color true def
/Blacktext false def
/Solid false def
/Dashlength 1 def
/Landscape true def
/Level1 false def
/Rounded false def
/TransparentPatterns false def
/gnulinewidth 5.000 def
/userlinewidth gnulinewidth def
%
/vshift -33 def
/dl1 {
10.0 Dashlength mul mul
Rounded { currentlinewidth 0.75 mul sub dup 0 le { pop 0.01 } if } if
} def
/dl2 {
10.0 Dashlength mul mul
Rounded { currentlinewidth 0.75 mul add } if
} def
/hpt_ 31.5 def
/vpt_ 31.5 def
/hpt hpt_ def
/vpt vpt_ def
Level1 {} {
/SDict 10 dict def
systemdict /pdfmark known not {
userdict /pdfmark systemdict /cleartomark get put
} if
SDict begin [
/Title ()
/Subject (gnuplot plot)
/Creator (gnuplot 4.2 patchlevel 3 )
/Author (Martin_Hernan Ferrari,L133,2417)
% /Producer (gnuplot)
% /Keywords ()
/CreationDate (Mon Aug 16 13:02:31 2010)
/DOCINFO pdfmark
end
} ifelse
%
% Gnuplot Prolog Version 4.2 (August 2006)
%
/M {moveto} bind def
/L {lineto} bind def
/R {rmoveto} bind def
/V {rlineto} bind def
/N {newpath moveto} bind def
/Z {closepath} bind def
/C {setrgbcolor} bind def
/f {rlineto fill} bind def
/vpt2 vpt 2 mul def
/hpt2 hpt 2 mul def
/Lshow {currentpoint stroke M 0 vshift R
Blacktext {gsave 0 setgray show grestore} {show} ifelse} def
/Rshow {currentpoint stroke M dup stringwidth pop neg vshift R
Blacktext {gsave 0 setgray show grestore} {show} ifelse} def
/Cshow {currentpoint stroke M dup stringwidth pop -2 div vshift R
Blacktext {gsave 0 setgray show grestore} {show} ifelse} def
/UP {dup vpt_ mul /vpt exch def hpt_ mul /hpt exch def
/hpt2 hpt 2 mul def /vpt2 vpt 2 mul def} def
/DL {Color {setrgbcolor Solid {pop []} if 0 setdash}
{pop pop pop 0 setgray Solid {pop []} if 0 setdash} ifelse} def
/BL {stroke userlinewidth 2 mul setlinewidth
Rounded {1 setlinejoin 1 setlinecap} if} def
/AL {stroke userlinewidth 2 div setlinewidth
Rounded {1 setlinejoin 1 setlinecap} if} def
/UL {dup gnulinewidth mul /userlinewidth exch def
dup 1 lt {pop 1} if 10 mul /udl exch def} def
/PL {stroke userlinewidth setlinewidth
Rounded {1 setlinejoin 1 setlinecap} if} def
% Default Line colors
/LCw {1 1 1} def
/LCb {0 0 0} def
/LCa {0 0 0} def
/LC0 {1 0 0} def
/LC1 {0 1 0} def
/LC2 {0 0 1} def
/LC3 {1 0 1} def
/LC4 {0 1 1} def
/LC5 {1 1 0} def
/LC6 {0 0 0} def
/LC7 {1 0.3 0} def
/LC8 {0.5 0.5 0.5} def
% Default Line Types
/LTw {PL [] 1 setgray} def
/LTb {BL [] LCb DL} def
/LTa {AL [1 udl mul 2 udl mul] 0 setdash LCa setrgbcolor} def
/LT0 {PL [] LC0 DL} def
/LT1 {PL [4 dl1 2 dl2] LC1 DL} def
/LT2 {PL [2 dl1 3 dl2] LC2 DL} def
/LT3 {PL [1 dl1 1.5 dl2] LC3 DL} def
/LT4 {PL [6 dl1 2 dl2 1 dl1 2 dl2] LC4 DL} def
/LT5 {PL [3 dl1 3 dl2 1 dl1 3 dl2] LC5 DL} def
/LT6 {PL [2 dl1 2 dl2 2 dl1 6 dl2] LC6 DL} def
/LT7 {PL [1 dl1 2 dl2 6 dl1 2 dl2 1 dl1 2 dl2] LC7 DL} def
/LT8 {PL [2 dl1 2 dl2 2 dl1 2 dl2 2 dl1 2 dl2 2 dl1 4 dl2] LC8 DL} def
/Pnt {stroke [] 0 setdash gsave 1 setlinecap M 0 0 V stroke grestore} def
/Dia {stroke [] 0 setdash 2 copy vpt add M
hpt neg vpt neg V hpt vpt neg V
hpt vpt V hpt neg vpt V closepath stroke
Pnt} def
/Pls {stroke [] 0 setdash vpt sub M 0 vpt2 V
currentpoint stroke M
hpt neg vpt neg R hpt2 0 V stroke
} def
/Box {stroke [] 0 setdash 2 copy exch hpt sub exch vpt add M
0 vpt2 neg V hpt2 0 V 0 vpt2 V
hpt2 neg 0 V closepath stroke
Pnt} def
/Crs {stroke [] 0 setdash exch hpt sub exch vpt add M
hpt2 vpt2 neg V currentpoint stroke M
hpt2 neg 0 R hpt2 vpt2 V stroke} def
/TriU {stroke [] 0 setdash 2 copy vpt 1.12 mul add M
hpt neg vpt -1.62 mul V
hpt 2 mul 0 V
hpt neg vpt 1.62 mul V closepath stroke
Pnt} def
/Star {2 copy Pls Crs} def
/BoxF {stroke [] 0 setdash exch hpt sub exch vpt add M
0 vpt2 neg V hpt2 0 V 0 vpt2 V
hpt2 neg 0 V closepath fill} def
/TriUF {stroke [] 0 setdash vpt 1.12 mul add M
hpt neg vpt -1.62 mul V
hpt 2 mul 0 V
hpt neg vpt 1.62 mul V closepath fill} def
/TriD {stroke [] 0 setdash 2 copy vpt 1.12 mul sub M
hpt neg vpt 1.62 mul V
hpt 2 mul 0 V
hpt neg vpt -1.62 mul V closepath stroke
Pnt} def
/TriDF {stroke [] 0 setdash vpt 1.12 mul sub M
hpt neg vpt 1.62 mul V
hpt 2 mul 0 V
hpt neg vpt -1.62 mul V closepath fill} def
/DiaF {stroke [] 0 setdash vpt add M
hpt neg vpt neg V hpt vpt neg V
hpt vpt V hpt neg vpt V closepath fill} def
/Pent {stroke [] 0 setdash 2 copy gsave
translate 0 hpt M 4 {72 rotate 0 hpt L} repeat
closepath stroke grestore Pnt} def
/PentF {stroke [] 0 setdash gsave
translate 0 hpt M 4 {72 rotate 0 hpt L} repeat
closepath fill grestore} def
/Circle {stroke [] 0 setdash 2 copy
hpt 0 360 arc stroke Pnt} def
/CircleF {stroke [] 0 setdash hpt 0 360 arc fill} def
/C0 {BL [] 0 setdash 2 copy moveto vpt 90 450 arc} bind def
/C1 {BL [] 0 setdash 2 copy moveto
2 copy vpt 0 90 arc closepath fill
vpt 0 360 arc closepath} bind def
/C2 {BL [] 0 setdash 2 copy moveto
2 copy vpt 90 180 arc closepath fill
vpt 0 360 arc closepath} bind def
/C3 {BL [] 0 setdash 2 copy moveto
2 copy vpt 0 180 arc closepath fill
vpt 0 360 arc closepath} bind def
/C4 {BL [] 0 setdash 2 copy moveto
2 copy vpt 180 270 arc closepath fill
vpt 0 360 arc closepath} bind def
/C5 {BL [] 0 setdash 2 copy moveto
2 copy vpt 0 90 arc
2 copy moveto
2 copy vpt 180 270 arc closepath fill
vpt 0 360 arc} bind def
/C6 {BL [] 0 setdash 2 copy moveto
2 copy vpt 90 270 arc closepath fill
vpt 0 360 arc closepath} bind def
/C7 {BL [] 0 setdash 2 copy moveto
2 copy vpt 0 270 arc closepath fill
vpt 0 360 arc closepath} bind def
/C8 {BL [] 0 setdash 2 copy moveto
2 copy vpt 270 360 arc closepath fill
vpt 0 360 arc closepath} bind def
/C9 {BL [] 0 setdash 2 copy moveto
2 copy vpt 270 450 arc closepath fill
vpt 0 360 arc closepath} bind def
/C10 {BL [] 0 setdash 2 copy 2 copy moveto vpt 270 360 arc closepath fill
2 copy moveto
2 copy vpt 90 180 arc closepath fill
vpt 0 360 arc closepath} bind def
/C11 {BL [] 0 setdash 2 copy moveto
2 copy vpt 0 180 arc closepath fill
2 copy moveto
2 copy vpt 270 360 arc closepath fill
vpt 0 360 arc closepath} bind def
/C12 {BL [] 0 setdash 2 copy moveto
2 copy vpt 180 360 arc closepath fill
vpt 0 360 arc closepath} bind def
/C13 {BL [] 0 setdash 2 copy moveto
2 copy vpt 0 90 arc closepath fill
2 copy moveto
2 copy vpt 180 360 arc closepath fill
vpt 0 360 arc closepath} bind def
/C14 {BL [] 0 setdash 2 copy moveto
2 copy vpt 90 360 arc closepath fill
vpt 0 360 arc} bind def
/C15 {BL [] 0 setdash 2 copy vpt 0 360 arc closepath fill
vpt 0 360 arc closepath} bind def
/Rec {newpath 4 2 roll moveto 1 index 0 rlineto 0 exch rlineto
neg 0 rlineto closepath} bind def
/Square {dup Rec} bind def
/Bsquare {vpt sub exch vpt sub exch vpt2 Square} bind def
/S0 {BL [] 0 setdash 2 copy moveto 0 vpt rlineto BL Bsquare} bind def
/S1 {BL [] 0 setdash 2 copy vpt Square fill Bsquare} bind def
/S2 {BL [] 0 setdash 2 copy exch vpt sub exch vpt Square fill Bsquare} bind def
/S3 {BL [] 0 setdash 2 copy exch vpt sub exch vpt2 vpt Rec fill Bsquare} bind def
/S4 {BL [] 0 setdash 2 copy exch vpt sub exch vpt sub vpt Square fill Bsquare} bind def
/S5 {BL [] 0 setdash 2 copy 2 copy vpt Square fill
exch vpt sub exch vpt sub vpt Square fill Bsquare} bind def
/S6 {BL [] 0 setdash 2 copy exch vpt sub exch vpt sub vpt vpt2 Rec fill Bsquare} bind def
/S7 {BL [] 0 setdash 2 copy exch vpt sub exch vpt sub vpt vpt2 Rec fill
2 copy vpt Square fill Bsquare} bind def
/S8 {BL [] 0 setdash 2 copy vpt sub vpt Square fill Bsquare} bind def
/S9 {BL [] 0 setdash 2 copy vpt sub vpt vpt2 Rec fill Bsquare} bind def
/S10 {BL [] 0 setdash 2 copy vpt sub vpt Square fill 2 copy exch vpt sub exch vpt Square fill
Bsquare} bind def
/S11 {BL [] 0 setdash 2 copy vpt sub vpt Square fill 2 copy exch vpt sub exch vpt2 vpt Rec fill
Bsquare} bind def
/S12 {BL [] 0 setdash 2 copy exch vpt sub exch vpt sub vpt2 vpt Rec fill Bsquare} bind def
/S13 {BL [] 0 setdash 2 copy exch vpt sub exch vpt sub vpt2 vpt Rec fill
2 copy vpt Square fill Bsquare} bind def
/S14 {BL [] 0 setdash 2 copy exch vpt sub exch vpt sub vpt2 vpt Rec fill
2 copy exch vpt sub exch vpt Square fill Bsquare} bind def
/S15 {BL [] 0 setdash 2 copy Bsquare fill Bsquare} bind def
/D0 {gsave translate 45 rotate 0 0 S0 stroke grestore} bind def
/D1 {gsave translate 45 rotate 0 0 S1 stroke grestore} bind def
/D2 {gsave translate 45 rotate 0 0 S2 stroke grestore} bind def
/D3 {gsave translate 45 rotate 0 0 S3 stroke grestore} bind def
/D4 {gsave translate 45 rotate 0 0 S4 stroke grestore} bind def
/D5 {gsave translate 45 rotate 0 0 S5 stroke grestore} bind def
/D6 {gsave translate 45 rotate 0 0 S6 stroke grestore} bind def
/D7 {gsave translate 45 rotate 0 0 S7 stroke grestore} bind def
/D8 {gsave translate 45 rotate 0 0 S8 stroke grestore} bind def
/D9 {gsave translate 45 rotate 0 0 S9 stroke grestore} bind def
/D10 {gsave translate 45 rotate 0 0 S10 stroke grestore} bind def
/D11 {gsave translate 45 rotate 0 0 S11 stroke grestore} bind def
/D12 {gsave translate 45 rotate 0 0 S12 stroke grestore} bind def
/D13 {gsave translate 45 rotate 0 0 S13 stroke grestore} bind def
/D14 {gsave translate 45 rotate 0 0 S14 stroke grestore} bind def
/D15 {gsave translate 45 rotate 0 0 S15 stroke grestore} bind def
/DiaE {stroke [] 0 setdash vpt add M
hpt neg vpt neg V hpt vpt neg V
hpt vpt V hpt neg vpt V closepath stroke} def
/BoxE {stroke [] 0 setdash exch hpt sub exch vpt add M
0 vpt2 neg V hpt2 0 V 0 vpt2 V
hpt2 neg 0 V closepath stroke} def
/TriUE {stroke [] 0 setdash vpt 1.12 mul add M
hpt neg vpt -1.62 mul V
hpt 2 mul 0 V
hpt neg vpt 1.62 mul V closepath stroke} def
/TriDE {stroke [] 0 setdash vpt 1.12 mul sub M
hpt neg vpt 1.62 mul V
hpt 2 mul 0 V
hpt neg vpt -1.62 mul V closepath stroke} def
/PentE {stroke [] 0 setdash gsave
translate 0 hpt M 4 {72 rotate 0 hpt L} repeat
closepath stroke grestore} def
/CircE {stroke [] 0 setdash
hpt 0 360 arc stroke} def
/Opaque {gsave closepath 1 setgray fill grestore 0 setgray closepath} def
/DiaW {stroke [] 0 setdash vpt add M
hpt neg vpt neg V hpt vpt neg V
hpt vpt V hpt neg vpt V Opaque stroke} def
/BoxW {stroke [] 0 setdash exch hpt sub exch vpt add M
0 vpt2 neg V hpt2 0 V 0 vpt2 V
hpt2 neg 0 V Opaque stroke} def
/TriUW {stroke [] 0 setdash vpt 1.12 mul add M
hpt neg vpt -1.62 mul V
hpt 2 mul 0 V
hpt neg vpt 1.62 mul V Opaque stroke} def
/TriDW {stroke [] 0 setdash vpt 1.12 mul sub M
hpt neg vpt 1.62 mul V
hpt 2 mul 0 V
hpt neg vpt -1.62 mul V Opaque stroke} def
/PentW {stroke [] 0 setdash gsave
translate 0 hpt M 4 {72 rotate 0 hpt L} repeat
Opaque stroke grestore} def
/CircW {stroke [] 0 setdash
hpt 0 360 arc Opaque stroke} def
/BoxFill {gsave Rec 1 setgray fill grestore} def
/Density {
/Fillden exch def
currentrgbcolor
/ColB exch def /ColG exch def /ColR exch def
/ColR ColR Fillden mul Fillden sub 1 add def
/ColG ColG Fillden mul Fillden sub 1 add def
/ColB ColB Fillden mul Fillden sub 1 add def
ColR ColG ColB setrgbcolor} def
/BoxColFill {gsave Rec PolyFill} def
/PolyFill {gsave Density fill grestore grestore} def
/h {rlineto rlineto rlineto gsave closepath fill grestore} bind def
%
% PostScript Level 1 Pattern Fill routine for rectangles
% Usage: x y w h s a XX PatternFill
% x,y = lower left corner of box to be filled
% w,h = width and height of box
% a = angle in degrees between lines and x-axis
% XX = 0/1 for no/yes cross-hatch
%
/PatternFill {gsave /PFa [ 9 2 roll ] def
PFa 0 get PFa 2 get 2 div add PFa 1 get PFa 3 get 2 div add translate
PFa 2 get -2 div PFa 3 get -2 div PFa 2 get PFa 3 get Rec
gsave 1 setgray fill grestore clip
currentlinewidth 0.5 mul setlinewidth
/PFs PFa 2 get dup mul PFa 3 get dup mul add sqrt def
0 0 M PFa 5 get rotate PFs -2 div dup translate
0 1 PFs PFa 4 get div 1 add floor cvi
{PFa 4 get mul 0 M 0 PFs V} for
0 PFa 6 get ne {
0 1 PFs PFa 4 get div 1 add floor cvi
{PFa 4 get mul 0 2 1 roll M PFs 0 V} for
} if
stroke grestore} def
%
/languagelevel where
{pop languagelevel} {1} ifelse
2 lt
{/InterpretLevel1 true def}
{/InterpretLevel1 Level1 def}
ifelse
%
% PostScript level 2 pattern fill definitions
%
/Level2PatternFill {
/Tile8x8 {/PaintType 2 /PatternType 1 /TilingType 1 /BBox [0 0 8 8] /XStep 8 /YStep 8}
bind def
/KeepColor {currentrgbcolor [/Pattern /DeviceRGB] setcolorspace} bind def
<< Tile8x8
/PaintProc {0.5 setlinewidth pop 0 0 M 8 8 L 0 8 M 8 0 L stroke}
>> matrix makepattern
/Pat1 exch def
<< Tile8x8
/PaintProc {0.5 setlinewidth pop 0 0 M 8 8 L 0 8 M 8 0 L stroke
0 4 M 4 8 L 8 4 L 4 0 L 0 4 L stroke}
>> matrix makepattern
/Pat2 exch def
<< Tile8x8
/PaintProc {0.5 setlinewidth pop 0 0 M 0 8 L
8 8 L 8 0 L 0 0 L fill}
>> matrix makepattern
/Pat3 exch def
<< Tile8x8
/PaintProc {0.5 setlinewidth pop -4 8 M 8 -4 L
0 12 M 12 0 L stroke}
>> matrix makepattern
/Pat4 exch def
<< Tile8x8
/PaintProc {0.5 setlinewidth pop -4 0 M 8 12 L
0 -4 M 12 8 L stroke}
>> matrix makepattern
/Pat5 exch def
<< Tile8x8
/PaintProc {0.5 setlinewidth pop -2 8 M 4 -4 L
0 12 M 8 -4 L 4 12 M 10 0 L stroke}
>> matrix makepattern
/Pat6 exch def
<< Tile8x8
/PaintProc {0.5 setlinewidth pop -2 0 M 4 12 L
0 -4 M 8 12 L 4 -4 M 10 8 L stroke}
>> matrix makepattern
/Pat7 exch def
<< Tile8x8
/PaintProc {0.5 setlinewidth pop 8 -2 M -4 4 L
12 0 M -4 8 L 12 4 M 0 10 L stroke}
>> matrix makepattern
/Pat8 exch def
<< Tile8x8
/PaintProc {0.5 setlinewidth pop 0 -2 M 12 4 L
-4 0 M 12 8 L -4 4 M 8 10 L stroke}
>> matrix makepattern
/Pat9 exch def
/Pattern1 {PatternBgnd KeepColor Pat1 setpattern} bind def
/Pattern2 {PatternBgnd KeepColor Pat2 setpattern} bind def
/Pattern3 {PatternBgnd KeepColor Pat3 setpattern} bind def
/Pattern4 {PatternBgnd KeepColor Landscape {Pat5} {Pat4} ifelse setpattern} bind def
/Pattern5 {PatternBgnd KeepColor Landscape {Pat4} {Pat5} ifelse setpattern} bind def
/Pattern6 {PatternBgnd KeepColor Landscape {Pat9} {Pat6} ifelse setpattern} bind def
/Pattern7 {PatternBgnd KeepColor Landscape {Pat8} {Pat7} ifelse setpattern} bind def
} def
%
%
%End of PostScript Level 2 code
%
/PatternBgnd {
TransparentPatterns {} {gsave 1 setgray fill grestore} ifelse
} def
%
% Substitute for Level 2 pattern fill codes with
% grayscale if Level 2 support is not selected.
%
/Level1PatternFill {
/Pattern1 {0.250 Density} bind def
/Pattern2 {0.500 Density} bind def
/Pattern3 {0.750 Density} bind def
/Pattern4 {0.125 Density} bind def
/Pattern5 {0.375 Density} bind def
/Pattern6 {0.625 Density} bind def
/Pattern7 {0.875 Density} bind def
} def
%
% Now test for support of Level 2 code
%
Level1 {Level1PatternFill} {Level2PatternFill} ifelse
%
/Symbol-Oblique /Symbol findfont [1 0 .167 1 0 0] makefont
dup length dict begin {1 index /FID eq {pop pop} {def} ifelse} forall
currentdict end definefont pop
/MFshow {
{ dup 5 get 3 ge
{ 5 get 3 eq {gsave} {grestore} ifelse }
{dup dup 0 get findfont exch 1 get scalefont setfont
[ currentpoint ] exch dup 2 get 0 exch R dup 5 get 2 ne {dup dup 6
get exch 4 get {show} {stringwidth pop 0 R} ifelse }if dup 5 get 0 eq
{dup 3 get {2 get neg 0 exch R pop} {pop aload pop M} ifelse} {dup 5
get 1 eq {dup 2 get exch dup 3 get exch 6 get stringwidth pop -2 div
dup 0 R} {dup 6 get stringwidth pop -2 div 0 R 6 get
show 2 index {aload pop M neg 3 -1 roll neg R pop pop} {pop pop pop
pop aload pop M} ifelse }ifelse }ifelse }
ifelse }
forall} bind def
/MFwidth {0 exch { dup 5 get 3 ge { 5 get 3 eq { 0 } { pop } ifelse }
{dup 3 get{dup dup 0 get findfont exch 1 get scalefont setfont
6 get stringwidth pop add} {pop} ifelse} ifelse} forall} bind def
/MLshow { currentpoint stroke M
0 exch R
Blacktext {gsave 0 setgray MFshow grestore} {MFshow} ifelse } bind def
/MRshow { currentpoint stroke M
exch dup MFwidth neg 3 -1 roll R
Blacktext {gsave 0 setgray MFshow grestore} {MFshow} ifelse } bind def
/MCshow { currentpoint stroke M
exch dup MFwidth -2 div 3 -1 roll R
Blacktext {gsave 0 setgray MFshow grestore} {MFshow} ifelse } bind def
/XYsave { [( ) 1 2 true false 3 ()] } bind def
/XYrestore { [( ) 1 2 true false 4 ()] } bind def
end
%%EndProlog
%%Page: 1 1
gnudict begin
gsave
50 50 translate
0.100 0.100 scale
90 rotate
0 -5040 translate
0 setgray
newpath
(Helvetica) findfont 100 scalefont setfont
1.000 UL
LTb
450 300 M
63 0 V
6517 0 R
-63 0 V
stroke
390 300 M
[ [(Helvetica) 100.0 0.0 true true 0 ( 10)]
] -33.3 MRshow
1.000 UL
LTb
450 1040 M
63 0 V
6517 0 R
-63 0 V
stroke
390 1040 M
[ [(Helvetica) 100.0 0.0 true true 0 ( 20)]
] -33.3 MRshow
1.000 UL
LTb
450 1780 M
63 0 V
6517 0 R
-63 0 V
stroke
390 1780 M
[ [(Helvetica) 100.0 0.0 true true 0 ( 30)]
] -33.3 MRshow
1.000 UL
LTb
450 2520 M
63 0 V
6517 0 R
-63 0 V
stroke
390 2520 M
[ [(Helvetica) 100.0 0.0 true true 0 ( 40)]
] -33.3 MRshow
1.000 UL
LTb
450 3260 M
63 0 V
6517 0 R
-63 0 V
stroke
390 3260 M
[ [(Helvetica) 100.0 0.0 true true 0 ( 50)]
] -33.3 MRshow
1.000 UL
LTb
450 4000 M
63 0 V
6517 0 R
-63 0 V
stroke
390 4000 M
[ [(Helvetica) 100.0 0.0 true true 0 ( 60)]
] -33.3 MRshow
1.000 UL
LTb
450 4740 M
63 0 V
6517 0 R
-63 0 V
stroke
390 4740 M
[ [(Helvetica) 100.0 0.0 true true 0 ( 70)]
] -33.3 MRshow
1.000 UL
LTb
450 300 M
0 63 V
0 4377 R
0 -63 V
stroke
450 200 M
[ [(Helvetica) 100.0 0.0 true true 0 ( 0)]
] -33.3 MCshow
1.000 UL
LTb
1327 300 M
0 63 V
0 4377 R
0 -63 V
stroke
1327 200 M
[ [(Helvetica) 100.0 0.0 true true 0 ( 200)]
] -33.3 MCshow
1.000 UL
LTb
2205 300 M
0 63 V
0 4377 R
0 -63 V
stroke
2205 200 M
[ [(Helvetica) 100.0 0.0 true true 0 ( 400)]
] -33.3 MCshow
1.000 UL
LTb
3082 300 M
0 63 V
0 4377 R
0 -63 V
stroke
3082 200 M
[ [(Helvetica) 100.0 0.0 true true 0 ( 600)]
] -33.3 MCshow
1.000 UL
LTb
3959 300 M
0 63 V
0 4377 R
0 -63 V
stroke
3959 200 M
[ [(Helvetica) 100.0 0.0 true true 0 ( 800)]
] -33.3 MCshow
1.000 UL
LTb
4837 300 M
0 63 V
0 4377 R
0 -63 V
stroke
4837 200 M
[ [(Helvetica) 100.0 0.0 true true 0 ( 1000)]
] -33.3 MCshow
1.000 UL
LTb
5714 300 M
0 63 V
0 4377 R
0 -63 V
stroke
5714 200 M
[ [(Helvetica) 100.0 0.0 true true 0 ( 1200)]
] -33.3 MCshow
1.000 UL
LTb
6591 300 M
0 63 V
0 4377 R
0 -63 V
stroke
6591 200 M
[ [(Helvetica) 100.0 0.0 true true 0 ( 1400)]
] -33.3 MCshow
1.000 UL
LTb
1.000 UL
LTb
450 4740 N
450 300 L
6580 0 V
0 4440 V
-6580 0 V
Z stroke
LCb setrgbcolor
100 2520 M
currentpoint gsave translate 90 rotate 0 0 moveto
[ [(Helvetica) 100.0 0.0 true true 0 (Processing cost per packet \(10E-6 sec\))]
] -33.3 MCshow
grestore
LTb
LCb setrgbcolor
3740 50 M
[ [(Helvetica) 100.0 0.0 true true 0 (Payload size \(UDP packet\))]
] -33.3 MCshow
LTb
3740 4890 M
[ [(Helvetica) 100.0 0.0 true true 0 (Processing cost for a 4-node topology)]
] -33.3 MCshow
1.000 UP
1.000 UL
LTb
991 4627 M
[ [(Helvetica) 100.0 0.0 true true 0 (Experiment)]
] -33.3 MCshow
1.000 UL
LTb
510 4077 N
0 600 V
963 0 V
0 -600 V
-963 0 V
Z stroke
510 4577 M
963 0 V
1.000 UP
stroke
LT0
LTb
1050 4527 M
[ [(Helvetica) 100.0 0.0 true true 0 (Exp 1)]
] -33.3 MRshow
LT0
1110 4527 M
303 0 V
6591 1727 M
4837 1653 L
3082 1640 L
-877 -31 V
889 1587 L
-88 -21 V
582 1780 L
-88 788 V
6591 1727 Pls
4837 1653 Pls
3082 1640 Pls
2205 1609 Pls
889 1587 Pls
801 1566 Pls
582 1780 Pls
494 2568 Pls
1261 4527 Pls
1.000 UP
1.000 UL
LT1
LTb
1050 4427 M
[ [(Helvetica) 100.0 0.0 true true 0 (Exp 2)]
] -33.3 MRshow
LT1
1110 4427 M
303 0 V
6591 3321 M
4837 3166 L
-1755 70 V
2205 2635 L
889 2623 L
801 2523 L
582 2485 L
-88 778 V
6591 3321 Crs
4837 3166 Crs
3082 3236 Crs
2205 2635 Crs
889 2623 Crs
801 2523 Crs
582 2485 Crs
494 3263 Crs
1261 4427 Crs
1.000 UP
1.000 UL
LT2
LTb
1050 4327 M
[ [(Helvetica) 100.0 0.0 true true 0 (Exp 3)]
] -33.3 MRshow
LT2
1110 4327 M
303 0 V
5178 364 R
4837 4466 L
3082 4124 L
2205 3891 L
889 3743 L
801 3618 L
582 3562 L
-88 787 V
6591 4691 Star
4837 4466 Star
3082 4124 Star
2205 3891 Star
889 3743 Star
801 3618 Star
582 3562 Star
494 4349 Star
1261 4327 Star
1.000 UP
1.000 UL
LT3
LTb
1050 4227 M
[ [(Helvetica) 100.0 0.0 true true 0 (Exp 4)]
] -33.3 MRshow
LT3
1110 4227 M
303 0 V
450 871 M
4 44 V
5 -25 V
9 6 V
17 67 V
35 -75 V
70 13 V
731 886 L
280 12 V
562 5 V
1123 22 V
2246 17 V
1904 48 V
450 871 Box
454 915 Box
459 890 Box
468 896 Box
485 963 Box
520 888 Box
590 901 Box
731 886 Box
1011 898 Box
1573 903 Box
2696 925 Box
4942 942 Box
6846 990 Box
1261 4227 Box
1.000 UP
1.000 UL
LT4
LTb
1050 4127 M
[ [(Helvetica) 100.0 0.0 true true 0 (Exp 4bis)]
] -33.3 MRshow
LT4
1110 4127 M
303 0 V
450 1518 M
4 20 V
5 9 V
9 3 V
17 -10 V
35 5 V
70 -4 V
141 8 V
280 2 V
562 15 V
1123 1 V
2246 50 V
1904 12 V
450 1518 BoxF
454 1538 BoxF
459 1547 BoxF
468 1550 BoxF
485 1540 BoxF
520 1545 BoxF
590 1541 BoxF
731 1549 BoxF
1011 1551 BoxF
1573 1566 BoxF
2696 1567 BoxF
4942 1617 BoxF
6846 1629 BoxF
1261 4127 BoxF
1.000 UL
LTb
450 4740 N
450 300 L
6580 0 V
0 4440 V
-6580 0 V
Z stroke
1.000 UP
1.000 UL
LTb
stroke
grestore
end
showpage
%%Trailer
%%DocumentFonts: Helvetica
%%Pages: 1
Nodes,Bytes received,Packets received,Packet size,Payload size,Errors,Min delay,Average delay,Max delay,Jitter,Total time,Link-level bandwidth,Command line
1,35958426,856153,42,0,0,-1,0,0,,10000007,28766720,--format=csv -n 1 -s 42 --use-p2p
1,36061242,858601,42,0,0,-1,0,0,,10000004,28848982,--format=csv -n 1 -s 42
1,36350867,845369,43,1,0,-1,0,0,,10000009,29080667,--format=csv -n 1 -s 43 --use-p2p
1,36353920,845440,43,1,0,-1,0,0,,10000007,29083115,--format=csv -n 1 -s 43
1,37097060,843115,44,2,0,-1,0,0,,10000005,29677633,--format=csv -n 1 -s 44 --use-p2p
1,36964840,840110,44,2,0,-1,0,0,,10000003,29571863,--format=csv -n 1 -s 44
1,38149180,829330,46,4,0,-1,0,0,,10000006,30519325,--format=csv -n 1 -s 46 --use-p2p
1,38546988,837978,46,4,0,-1,0,0,,10000004,30837578,--format=csv -n 1 -s 46
1,41846900,836938,50,8,0,8,8,672,,10000006,33477499,--format=csv -n 1 -s 50 --use-p2p
1,41930800,838616,50,8,0,8,8,646,,10000010,33544606,--format=csv -n 1 -s 50
1,48969342,844299,58,16,0,8,8,237,,10000002,39175465,--format=csv -n 1 -s 58 --use-p2p
1,48604464,838008,58,16,0,8,8,669,,10000001,38883567,--format=csv -n 1 -s 58
1,61989578,837697,74,32,0,8,8,644,,10000009,49591617,--format=csv -n 1 -s 74 --use-p2p
1,62043820,838430,74,32,0,8,8,197,,10000006,49635026,--format=csv -n 1 -s 74
1,89067030,840255,106,64,0,8,8,198,,10000009,71253559,--format=csv -n 1 -s 106 --use-p2p
1,88529822,835187,106,64,0,8,8,643,,10000008,70823800,--format=csv -n 1 -s 106
1,141704690,833557,170,128,0,8,8,641,,10000002,113363729,--format=csv -n 1 -s 170 --use-p2p
1,141857690,834457,170,128,0,8,8,204,,10000005,113486095,--format=csv -n 1 -s 170
1,249269550,836475,298,256,0,8,8,197,,10000011,199415420,--format=csv -n 1 -s 298 --use-p2p
1,248590706,834197,298,256,0,8,8,199,,10000005,198872465,--format=csv -n 1 -s 298
1,454933720,821180,554,512,0,8,9,199,,10000010,363946612,--format=csv -n 1 -s 554 --use-p2p
1,457866596,826474,554,512,0,8,8,198,,10000006,366293057,--format=csv -n 1 -s 554
1,865598396,812006,1066,1024,0,8,9,269,,10000005,692478370,--format=csv -n 1 -s 1066 --use-p2p
1,859640522,806417,1066,1024,0,8,9,658,,10000004,687712142,--format=csv -n 1 -s 1066
1,1189230000,792820,1500,1458,0,9,9,199,,10000004,951383619,--format=csv -n 1 -s 1500 --use-p2p
1,1191405000,794270,1500,1458,0,9,9,519,,10000010,953123046,--format=csv -n 1 -s 1500
2,32702628,778634,42,0,0,-1,0,0,,10000006,26162086,--format=csv -n 2 -s 42 --use-p2p
2,25154388,598914,42,0,0,-1,0,0,,10000004,20123502,--format=csv -n 2 -s 42
2,32755207,761749,43,1,0,-1,0,0,,10000003,26204157,--format=csv -n 2 -s 43 --use-p2p
2,25566725,594575,43,1,0,-1,0,0,,10000004,20453371,--format=csv -n 2 -s 43
2,33459712,760448,44,2,0,-1,0,0,,10000006,26767753,--format=csv -n 2 -s 44 --use-p2p
2,26056624,592196,44,2,0,-1,0,0,,10000002,20845295,--format=csv -n 2 -s 44
2,34714682,754667,46,4,0,-1,0,0,,10000007,27771726,--format=csv -n 2 -s 46 --use-p2p
2,27253114,592459,46,4,0,-1,0,0,,10000011,21802467,--format=csv -n 2 -s 46
2,37886350,757727,50,8,0,9,9,680,,10000002,30309073,--format=csv -n 2 -s 50 --use-p2p
2,29665550,593311,50,8,0,13,13,325,,10000011,23732413,--format=csv -n 2 -s 50
2,44028786,759117,58,16,0,9,10,207,,10000006,35223007,--format=csv -n 2 -s 58 --use-p2p
2,34419984,593448,58,16,0,13,13,619,,10000015,27535945,--format=csv -n 2 -s 58
2,56526676,763874,74,32,0,9,9,211,,10000000,45221340,--format=csv -n 2 -s 74 --use-p2p
2,44018826,594849,74,32,0,13,13,290,,10000011,35215022,--format=csv -n 2 -s 74
2,80488026,759321,106,64,0,9,9,835,,10000003,64390401,--format=csv -n 2 -s 106 --use-p2p
2,62710554,591609,106,64,0,13,13,608,,10000007,50168408,--format=csv -n 2 -s 106
2,128596670,756451,170,128,0,9,10,2479,,10000006,102877274,--format=csv -n 2 -s 170 --use-p2p
2,100280960,589888,170,128,0,13,13,609,,10000000,80224768,--format=csv -n 2 -s 170
2,222942144,748128,298,256,0,9,10,665,,10000008,178353572,--format=csv -n 2 -s 298 --use-p2p
2,174893518,586891,298,256,0,13,13,205,,10000005,139914744,--format=csv -n 2 -s 298
2,413247436,745934,554,512,0,9,10,3599,,10000011,330597585,--format=csv -n 2 -s 554 --use-p2p
2,322896130,582845,554,512,0,13,13,3022,,10000008,258316697,--format=csv -n 2 -s 554
2,754548912,707832,1066,1024,0,10,10,2914,,10000010,603638525,--format=csv -n 2 -s 1066 --use-p2p
2,603938036,566546,1066,1024,0,13,14,1565,,10000013,483149800,--format=csv -n 2 -s 1066
2,1072513500,715009,1500,1458,0,10,10,2476,,10000011,858009856,--format=csv -n 2 -s 1500 --use-p2p
2,837000000,558000,1500,1458,0,14,14,610,,10000008,669599464,--format=csv -n 2 -s 1500
3,26998440,642820,42,0,0,-1,0,0,,10000009,21598732,--format=csv -n 3 -s 42 --use-p2p
3,19445160,462980,42,0,0,-1,0,0,,10000001,15556126,--format=csv -n 3 -s 42
3,27304828,634996,43,1,0,-1,0,0,,10000004,21843853,--format=csv -n 3 -s 43 --use-p2p
3,19648205,456935,43,1,0,-1,0,0,,10000008,15718551,--format=csv -n 3 -s 43
3,27947876,635179,44,2,0,-1,0,0,,10000011,22358276,--format=csv -n 3 -s 44 --use-p2p
3,19805412,450123,44,2,0,-1,0,0,,10000001,15844328,--format=csv -n 3 -s 44
3,29036120,631220,46,4,0,-1,0,0,,10000011,23228870,--format=csv -n 3 -s 46 --use-p2p
3,20852628,453318,46,4,0,-1,0,0,,10000001,16682100,--format=csv -n 3 -s 46
3,31800550,636011,50,8,0,12,12,318,,10000000,25440440,--format=csv -n 3 -s 50 --use-p2p
3,22827550,456551,50,8,0,18,18,409,,10000015,18262012,--format=csv -n 3 -s 50
3,36802392,634524,58,16,0,12,12,6536,,10000007,29441892,--format=csv -n 3 -s 58 --use-p2p
3,26451190,456055,58,16,0,18,18,6325,,10000013,21160924,--format=csv -n 3 -s 58
3,46947820,634430,74,32,0,12,12,480,,10000007,37558229,--format=csv -n 3 -s 74 --use-p2p
3,33574318,453707,74,32,0,18,18,3024,,10000006,26859438,--format=csv -n 3 -s 74
3,67244598,634383,106,64,0,12,12,3022,,10000012,53795613,--format=csv -n 3 -s 106 --use-p2p
3,48324022,455887,106,64,0,18,18,569,,10000006,38659194,--format=csv -n 3 -s 106
3,107662700,633310,170,128,0,12,12,262,,10000014,86130039,--format=csv -n 3 -s 170 --use-p2p
3,77066950,453335,170,128,0,18,18,619,,10000014,61653473,--format=csv -n 3 -s 170
3,175495478,588911,298,256,0,12,12,2506,,10000007,140396284,--format=csv -n 3 -s 298 --use-p2p
3,134931122,452789,298,256,0,18,18,605,,10000011,107944778,--format=csv -n 3 -s 298
3,343163666,619429,554,512,0,12,12,303,,10000006,274530768,--format=csv -n 3 -s 554 --use-p2p
3,249327700,450050,554,512,0,18,18,3026,,10000013,199461900,--format=csv -n 3 -s 554
3,645981076,605986,1066,1024,0,12,13,302,,10000001,516784809,--format=csv -n 3 -s 1066 --use-p2p
3,460952258,432413,1066,1024,0,18,19,222,,10000011,368761400,--format=csv -n 3 -s 1066
3,892099500,594733,1500,1458,0,13,13,596,,10000005,713679243,--format=csv -n 3 -s 1500 --use-p2p
3,653416500,435611,1500,1458,0,18,19,217,,10000002,522733095,--format=csv -n 3 -s 1500
4,23705682,564421,42,0,0,-1,0,0,,10000013,18964520,--format=csv -n 4 -s 42 --use-p2p
4,15876294,378007,42,0,0,-1,0,0,,10000012,12701019,--format=csv -n 4 -s 42
4,23482214,546098,43,1,0,-1,0,0,,10000011,18785750,--format=csv -n 4 -s 43 --use-p2p
4,16089009,374163,43,1,0,-1,0,0,,10000012,12871191,--format=csv -n 4 -s 43
4,24475396,556259,44,2,0,-1,0,0,,10000010,19580297,--format=csv -n 4 -s 44 --use-p2p
4,16384016,372364,44,2,0,-1,0,0,,10000006,13107204,--format=csv -n 4 -s 44
4,25477376,553856,46,4,0,-1,0,0,,10000009,20381882,--format=csv -n 4 -s 46 --use-p2p
4,17101604,371774,46,4,0,-1,0,0,,10000022,13681253,--format=csv -n 4 -s 46
4,26366100,527322,50,8,0,14,14,2328,,10000010,21092858,--format=csv -n 4 -s 50 --use-p2p
4,18684800,373696,50,8,0,22,23,266,,10000005,14947832,--format=csv -n 4 -s 50
4,32330244,557418,58,16,0,14,14,3020,,10000003,25864187,--format=csv -n 4 -s 58 --use-p2p
4,21619152,372744,58,16,0,22,23,621,,10000020,17295287,--format=csv -n 4 -s 58
4,40828464,551736,74,32,0,14,14,1992,,10000014,32662725,--format=csv -n 4 -s 74 --use-p2p
4,27643662,373563,74,32,0,22,23,619,,10000017,22114892,--format=csv -n 4 -s 74
4,59176514,558269,106,64,0,14,14,3022,,10000003,47341196,--format=csv -n 4 -s 106 --use-p2p
4,39427442,371957,106,64,0,22,23,2133,,10000019,31541893,--format=csv -n 4 -s 106
4,94033630,553139,170,128,0,14,14,2428,,10000010,75226828,--format=csv -n 4 -s 170 --use-p2p
4,63178290,371637,170,128,0,22,23,686,,10000018,50542541,--format=csv -n 4 -s 170
4,164163134,550883,298,256,0,14,14,1962,,10000015,131330310,--format=csv -n 4 -s 298 --use-p2p
4,109951570,368965,298,256,0,22,23,3002,,10000002,87961238,--format=csv -n 4 -s 298
4,300400960,542240,554,512,0,14,14,2925,,10000017,240320359,--format=csv -n 4 -s 554 --use-p2p
4,204265340,368710,554,512,0,23,23,3023,,10000002,163412239,--format=csv -n 4 -s 554
4,570597820,535270,1066,1024,0,14,15,6391,,10000011,456477753,--format=csv -n 4 -s 1066 --use-p2p
4,383543602,359797,1066,1024,0,23,24,2667,,10000023,306834175,--format=csv -n 4 -s 1066
4,775998000,517332,1500,1458,0,15,15,3719,,10000009,620797841,--format=csv -n 4 -s 1500 --use-p2p
4,536488500,357659,1500,1458,0,23,24,2659,,10000017,429190070,--format=csv -n 4 -s 1500
6,19315632,459896,42,0,0,-1,0,0,,10000000,15452505,--format=csv -n 6 -s 42 --use-p2p
6,11193042,266501,42,0,0,-1,0,0,,10000006,8954428,--format=csv -n 6 -s 42
6,18623257,433099,43,1,0,-1,0,0,,10000014,14898584,--format=csv -n 6 -s 43 --use-p2p
6,11817819,274833,43,1,0,-1,0,0,,10000007,9454248,--format=csv -n 6 -s 43
6,19882412,451873,44,2,0,-1,0,0,,10000010,15905913,--format=csv -n 6 -s 44 --use-p2p
6,12089792,274768,44,2,0,-1,0,0,,10000031,9671803,--format=csv -n 6 -s 44
6,20776130,451655,46,4,0,-1,0,0,,10000013,16620882,--format=csv -n 6 -s 46 --use-p2p
6,12628334,274529,46,4,0,-1,0,0,,10000026,10102640,--format=csv -n 6 -s 46
6,22367350,447347,50,8,0,18,19,2467,,10000013,17893856,--format=csv -n 6 -s 50 --use-p2p
6,13735350,274707,50,8,0,32,32,673,,10000034,10988242,--format=csv -n 6 -s 50
6,26372600,454700,58,16,0,18,18,597,,10000001,21098077,--format=csv -n 6 -s 58 --use-p2p
6,15723220,271090,58,16,0,32,32,623,,10000021,12578549,--format=csv -n 6 -s 58
6,33213050,448825,74,32,0,18,18,2423,,10000001,26570437,--format=csv -n 6 -s 74 --use-p2p
6,20231304,273396,74,32,0,32,33,628,,10000022,16185007,--format=csv -n 6 -s 74
6,47678906,449801,106,64,0,18,18,588,,10000010,38143086,--format=csv -n 6 -s 106 --use-p2p
6,29065094,274199,106,64,0,32,32,624,,10000029,23252007,--format=csv -n 6 -s 106
6,76695160,451148,170,128,0,18,18,346,,10000009,61356072,--format=csv -n 6 -s 170 --use-p2p
6,46496870,273511,170,128,0,32,32,2425,,10000003,37197484,--format=csv -n 6 -s 170
6,134036526,449787,298,256,0,18,18,810,,10000020,107229006,--format=csv -n 6 -s 298 --use-p2p
6,81152254,272323,298,256,0,32,33,623,,10000000,64921803,--format=csv -n 6 -s 298
6,247951564,447566,554,512,0,18,19,2903,,10000012,198361013,--format=csv -n 6 -s 554 --use-p2p
6,145439958,262527,554,512,0,32,33,956,,10000006,116351896,--format=csv -n 6 -s 554
6,466302512,437432,1066,1024,0,18,19,3024,,10000001,373041972,--format=csv -n 6 -s 1066 --use-p2p
6,271252228,254458,1066,1024,0,32,33,2727,,10000004,217001695,--format=csv -n 6 -s 1066
6,644083500,429389,1500,1458,0,19,19,2389,,10000005,515266542,--format=csv -n 6 -s 1500 --use-p2p
6,400338000,266892,1500,1458,0,33,33,625,,10000024,320269631,--format=csv -n 6 -s 1500
8,16154670,384635,42,0,0,-1,0,0,,10000001,12923734,--format=csv -n 8 -s 42 --use-p2p
8,9139620,217610,42,0,0,-1,0,0,,10000021,7311680,--format=csv -n 8 -s 42
8,15563635,361945,43,1,0,-1,0,0,,10000003,12450904,--format=csv -n 8 -s 43 --use-p2p
8,9342567,217269,43,1,0,-1,0,0,,10000021,7474037,--format=csv -n 8 -s 43
8,16707108,379707,44,2,0,-1,0,0,,10000005,13365679,--format=csv -n 8 -s 44 --use-p2p
8,9534712,216698,44,2,0,-1,0,0,,10000000,7627769,--format=csv -n 8 -s 44
8,17458288,379528,46,4,0,-1,0,0,,10000000,13966630,--format=csv -n 8 -s 46 --use-p2p
8,9972294,216789,46,4,0,-1,0,0,,10000017,7977821,--format=csv -n 8 -s 46
8,19030400,380608,50,8,0,22,22,653,,10000018,15224292,--format=csv -n 8 -s 50 --use-p2p
8,10814750,216295,50,8,0,41,42,3095,,10000015,8651787,--format=csv -n 8 -s 50
8,22049280,380160,58,16,0,22,22,3021,,10000012,17639402,--format=csv -n 8 -s 58 --use-p2p
8,12556072,216484,58,16,0,41,42,234,,10000030,10044827,--format=csv -n 8 -s 58
8,28167360,380640,74,32,0,22,22,850,,10000020,22533842,--format=csv -n 8 -s 74 --use-p2p
8,16076426,217249,74,32,0,41,42,635,,10000006,12861133,--format=csv -n 8 -s 74
8,40206330,379305,106,64,0,22,23,7066,,10000018,32165006,--format=csv -n 8 -s 106 --use-p2p
8,22943382,216447,106,64,0,41,42,620,,10000035,18354641,--format=csv -n 8 -s 106
8,64253370,377961,170,128,0,22,23,1730,,10000019,51402598,--format=csv -n 8 -s 170 --use-p2p
8,36731730,216069,170,128,0,41,42,500,,10000021,29385322,--format=csv -n 8 -s 170
8,112488742,377479,298,256,0,22,23,601,,10000021,89990804,--format=csv -n 8 -s 298 --use-p2p
8,64457102,216299,298,256,0,41,42,491,,10000041,51565470,--format=csv -n 8 -s 298
8,208162730,375745,554,512,0,22,23,362,,10000016,166529917,--format=csv -n 8 -s 554 --use-p2p
8,119535472,215768,554,512,0,41,42,988,,10000033,95628062,--format=csv -n 8 -s 554
8,393959488,369568,1066,1024,0,23,23,6760,,10000013,315167180,--format=csv -n 8 -s 1066 --use-p2p
8,226235048,212228,1066,1024,0,42,43,1049,,10000017,180987730,--format=csv -n 8 -s 1066
8,547935000,365290,1500,1458,0,23,23,2717,,10000007,438347693,--format=csv -n 8 -s 1500 --use-p2p
8,317413500,211609,1500,1458,0,42,43,1088,,10000004,253930698,--format=csv -n 8 -s 1500
12,12142116,289098,42,0,0,-1,0,0,,10000007,9713686,--format=csv -n 12 -s 42 --use-p2p
12,6481104,154312,42,0,0,-1,0,0,,10000011,5184877,--format=csv -n 12 -s 42
12,12338764,286948,43,1,0,-1,0,0,,10000018,9870993,--format=csv -n 12 -s 43 --use-p2p
12,6580247,153029,43,1,0,-1,0,0,,10000019,5264187,--format=csv -n 12 -s 43
12,12567984,285636,44,2,0,-1,0,0,,10000019,10054368,--format=csv -n 12 -s 44 --use-p2p
12,6736620,153105,44,2,0,-1,0,0,,10000043,5389272,--format=csv -n 12 -s 44
12,13163498,286163,46,4,0,-1,0,0,,10000005,10530793,--format=csv -n 12 -s 46 --use-p2p
12,7046372,153182,46,4,0,-1,0,0,,10000043,5637073,--format=csv -n 12 -s 46
12,13963400,279268,50,8,0,30,31,1661,,10000013,11170705,--format=csv -n 12 -s 50 --use-p2p
12,7619550,152391,50,8,0,59,61,2668,,10000044,6095613,--format=csv -n 12 -s 50
12,16665720,287340,58,16,0,30,31,621,,10000012,13332560,--format=csv -n 12 -s 58 --use-p2p
12,8873188,152986,58,16,0,59,61,1263,,10000004,7098547,--format=csv -n 12 -s 58
12,21206550,286575,74,32,0,30,31,7075,,10000022,16965202,--format=csv -n 12 -s 74 --use-p2p
12,11318818,152957,74,32,0,59,61,618,,10000046,9055012,--format=csv -n 12 -s 74
12,30329886,286131,106,64,0,30,31,6473,,10000027,24263843,--format=csv -n 12 -s 106 --use-p2p
12,16024762,151177,106,64,0,60,61,2854,,10000046,12819750,--format=csv -n 12 -s 106
12,48653150,286195,170,128,0,30,31,1298,,10000022,38922434,--format=csv -n 12 -s 170 --use-p2p
12,25947100,152630,170,128,0,60,61,1705,,10000020,20757638,--format=csv -n 12 -s 170
12,84861162,284769,298,256,0,30,31,625,,10000020,67888793,--format=csv -n 12 -s 298 --use-p2p
12,45321032,152084,298,256,0,59,61,860,,10000034,36256702,--format=csv -n 12 -s 298
12,157378658,284077,554,512,0,31,31,1792,,10000027,125902586,--format=csv -n 12 -s 554 --use-p2p
12,83980860,151590,554,512,0,60,61,1616,,10000006,67184647,--format=csv -n 12 -s 554
12,286565318,268823,1066,1024,0,31,32,1986,,10000018,229251841,--format=csv -n 12 -s 1066 --use-p2p
12,159903198,150003,1066,1024,0,60,62,619,,10000028,127922200,--format=csv -n 12 -s 1066
12,418357500,278905,1500,1458,0,31,32,635,,10000020,334685330,--format=csv -n 12 -s 1500 --use-p2p
12,223633500,149089,1500,1458,0,61,62,618,,10000044,178906012,--format=csv -n 12 -s 1500
16,9582594,228157,42,0,0,-1,0,0,,10000035,7666048,--format=csv -n 16 -s 42 --use-p2p
16,4931850,117425,42,0,0,-1,0,0,,10000065,3945454,--format=csv -n 16 -s 42
16,9784521,227547,43,1,0,-1,0,0,,10000030,7827593,--format=csv -n 16 -s 43 --use-p2p
16,5027560,116920,43,1,0,-1,0,0,,10000022,4022039,--format=csv -n 16 -s 43
16,10029008,227932,44,2,0,-1,0,0,,10000013,8023195,--format=csv -n 16 -s 44 --use-p2p
16,5088688,115652,44,2,0,-1,0,0,,10000008,4070947,--format=csv -n 16 -s 44
16,10544764,229234,46,4,0,-1,0,0,,10000016,8435797,--format=csv -n 16 -s 46 --use-p2p
16,5343590,116165,46,4,0,-1,0,0,,10000069,4274842,--format=csv -n 16 -s 46
16,11156150,223123,50,8,0,39,40,2095,,10000009,8924911,--format=csv -n 16 -s 50 --use-p2p
16,5805850,116117,50,8,0,79,81,2000,,10000019,4644671,--format=csv -n 16 -s 50
16,13174468,227146,58,16,0,39,40,3043,,10000006,10539568,--format=csv -n 16 -s 58 --use-p2p
16,6746618,116321,58,16,0,78,80,2045,,10000027,5397279,--format=csv -n 16 -s 58
16,16855572,227778,74,32,0,39,40,299,,10000001,13484456,--format=csv -n 16 -s 74 --use-p2p
16,8541228,115422,74,32,0,78,81,1311,,10000008,6832976,--format=csv -n 16 -s 74
16,24361980,229830,106,64,0,38,39,362,,10000031,19489523,--format=csv -n 16 -s 106 --use-p2p
16,12362568,116628,106,64,0,78,80,2020,,10000008,9890046,--format=csv -n 16 -s 106
16,38781250,228125,170,128,0,39,40,2131,,10000039,31024879,--format=csv -n 16 -s 170 --use-p2p
16,19882180,116954,170,128,0,78,80,1062,,10000072,15905629,--format=csv -n 16 -s 170
16,68188956,228822,298,256,0,39,39,646,,10000012,54551099,--format=csv -n 16 -s 298 --use-p2p
16,34508996,115802,298,256,0,78,81,2560,,10000072,27606998,--format=csv -n 16 -s 298
16,119657352,215988,554,512,0,39,40,3023,,10000036,95725536,--format=csv -n 16 -s 554 --use-p2p
16,63005312,113728,554,512,0,78,80,2676,,10000048,50404007,--format=csv -n 16 -s 554
16,233278110,218835,1066,1024,0,39,40,5541,,10000040,186621741,--format=csv -n 16 -s 1066 --use-p2p
16,122727514,115129,1066,1024,0,79,81,2908,,10000045,98181569,--format=csv -n 16 -s 1066
16,327915000,218610,1500,1458,0,39,40,4895,,10000025,262331344,--format=csv -n 16 -s 1500 --use-p2p
16,171390000,114260,1500,1458,0,79,81,635,,10000047,137111355,--format=csv -n 16 -s 1500
32,5328162,126861,42,0,0,-1,0,0,,10000043,4262511,--format=csv -n 32 -s 42 --use-p2p
32,2586570,61585,42,0,0,-1,0,0,,10000064,2069242,--format=csv -n 32 -s 42
32,5343911,124277,43,1,0,-1,0,0,,10000055,4275105,--format=csv -n 32 -s 43 --use-p2p
32,2678513,62291,43,1,0,-1,0,0,,10000110,2142786,--format=csv -n 32 -s 43
32,5521736,125494,44,2,0,-1,0,0,,10000007,4417385,--format=csv -n 32 -s 44 --use-p2p
32,2728484,62011,44,2,0,-1,0,0,,10000073,2182771,--format=csv -n 32 -s 44
32,5730588,124578,46,4,0,-1,0,0,,10000012,4584464,--format=csv -n 32 -s 46 --use-p2p
32,2860004,62174,46,4,0,-1,0,0,,10000127,2287974,--format=csv -n 32 -s 46
32,6203950,124079,50,8,0,72,74,779,,10000018,4963151,--format=csv -n 32 -s 50 --use-p2p
32,3092200,61844,50,8,0,153,157,2353,,10000152,2473722,--format=csv -n 32 -s 50
32,7210618,124321,58,16,0,72,73,2468,,10000056,5768462,--format=csv -n 32 -s 58 --use-p2p
32,3585618,61821,58,16,0,153,157,3066,,10000134,2868455,--format=csv -n 32 -s 58
32,9131156,123394,74,32,0,72,74,731,,10000004,7304921,--format=csv -n 32 -s 74 --use-p2p
32,4598138,62137,74,32,0,153,157,2098,,10000122,3678465,--format=csv -n 32 -s 74
32,13017542,122807,106,64,0,72,74,1982,,10000024,10414008,--format=csv -n 32 -s 106 --use-p2p
32,6599136,62256,106,64,0,153,156,715,,10000139,5279235,--format=csv -n 32 -s 106
32,21003330,123549,170,128,0,72,73,5149,,10000052,16802576,--format=csv -n 32 -s 170 --use-p2p
32,10559380,62114,170,128,0,153,157,1893,,10000060,8447453,--format=csv -n 32 -s 170
32,36544038,122631,298,256,0,72,74,1727,,10000061,29235052,--format=csv -n 32 -s 298 --use-p2p
32,18435472,61864,298,256,0,154,157,945,,10000044,14748312,--format=csv -n 32 -s 298
32,66123224,119356,554,512,0,72,74,2657,,10000053,52898298,--format=csv -n 32 -s 554 --use-p2p
32,33541376,60544,554,512,0,154,157,3166,,10000000,26833100,--format=csv -n 32 -s 554
32,130040274,121989,1066,1024,0,73,74,2579,,10000103,104031147,--format=csv -n 32 -s 1066 --use-p2p
32,65690118,61623,1066,1024,0,154,158,1183,,10000025,52551963,--format=csv -n 32 -s 1066
32,181837500,121225,1500,1458,0,73,75,6909,,10000054,145469214,--format=csv -n 32 -s 1500 --use-p2p
32,92371500,61581,1500,1458,0,154,158,2651,,10000096,73896490,--format=csv -n 32 -s 1500
64,2659860,63330,42,0,0,-1,0,0,,10000020,2127883,--format=csv -n 64 -s 42 --use-p2p
64,1298850,30925,42,0,0,-1,0,0,,10000297,1039049,--format=csv -n 64 -s 42
64,2617969,60883,43,1,0,-1,0,0,,10000016,2094371,--format=csv -n 64 -s 43 --use-p2p
64,1284152,29864,43,1,0,-1,0,0,,10000207,1027300,--format=csv -n 64 -s 43
64,2661032,60478,44,2,0,-1,0,0,,10000022,2128820,--format=csv -n 64 -s 44 --use-p2p
64,1353220,30755,44,2,0,-1,0,0,,10000271,1082546,--format=csv -n 64 -s 44
64,2779550,60425,46,4,0,-1,0,0,,10000061,2223626,--format=csv -n 64 -s 46 --use-p2p
64,1412476,30706,46,4,0,-1,0,0,,10000020,1129978,--format=csv -n 64 -s 46
64,2966000,59320,50,8,0,145,150,3123,,10000132,2372768,--format=csv -n 64 -s 50 --use-p2p
64,1497600,29952,50,8,0,313,322,3161,,10000064,1198072,--format=csv -n 64 -s 50
64,3410168,58796,58,16,0,146,150,2262,,10000100,2728107,--format=csv -n 64 -s 58 --use-p2p
64,1786632,30804,58,16,0,312,320,1095,,10000072,1429295,--format=csv -n 64 -s 58
64,4303544,58156,74,32,0,145,151,1590,,10000149,3442783,--format=csv -n 64 -s 74 --use-p2p
64,2254336,30464,74,32,0,315,323,1907,,10000301,1803414,--format=csv -n 64 -s 74
64,5980202,56417,106,64,0,147,152,3112,,10000063,4784131,--format=csv -n 64 -s 106 --use-p2p
64,3250490,30665,106,64,0,313,321,1081,,10000163,2600349,--format=csv -n 64 -s 106
64,9245960,54388,170,128,0,146,151,2991,,10000050,7396731,--format=csv -n 64 -s 170 --use-p2p
64,5216450,30685,170,128,0,313,321,2447,,10000028,4173148,--format=csv -n 64 -s 170
64,16653134,55883,298,256,0,148,152,1069,,10000129,13322335,--format=csv -n 64 -s 298 --use-p2p
64,9130720,30640,298,256,0,314,321,2367,,10000222,7304413,--format=csv -n 64 -s 298
64,30402966,54879,554,512,0,148,153,3068,,10000146,24322017,--format=csv -n 64 -s 554 --use-p2p
64,16951292,30598,554,512,0,314,322,2139,,10000197,13560766,--format=csv -n 64 -s 554
64,56105712,52632,1066,1024,0,148,154,2721,,10000069,44884259,--format=csv -n 64 -s 1066 --use-p2p
64,32371222,30367,1066,1024,0,315,324,2221,,10000312,25896169,--format=csv -n 64 -s 1066
64,74034000,49356,1500,1458,0,147,153,7388,,10000104,59226584,--format=csv -n 64 -s 1500 --use-p2p
64,45405000,30270,1500,1458,0,317,326,1971,,10000323,36322826,--format=csv -n 64 -s 1500
# ns3user-ns3kernel
# pktsz nodes usec mem
1400.0 1.0 11.6054 308000.0 exp1
1000.0 1.0 11.1844 308000.0 exp1
600.0 1.0 10.3422 308000.0 exp1
400.0 1.0 10.2632 308000.0 exp1
100.0 1.0 9.82896 308000.0 exp1
80.0 1.0 9.85264 308000.0 exp1
30.0 1.0 9.63947 308000.0 exp1
10.0 1.0 9.63816 308000.0 exp1
1400.0 2.0 15.4739 344000.0 exp1
1000.0 2.0 14.6055 344000.0 exp1
600.0 2.0 14.0527 344000.0 exp1
400.0 2.0 13.8948 344000.0 exp1
100.0 2.0 13.3948 344000.0 exp1
80.0 2.0 13.4211 344000.0 exp1
30.0 2.0 16.2884 344000.0 exp1
10.0 2.0 27.215 344000.0 exp1
1400.0 3.0 22.4744 356000.0 exp1
1000.0 3.0 21.8427 356000.0 exp1
600.0 3.0 20.8424 356000.0 exp1
400.0 3.0 21.0529 356000.0 exp1
100.0 3.0 20.4737 356000.0 exp1
80.0 3.0 20.3264 356000.0 exp1
30.0 3.0 23.3369 356000.0 exp1
10.0 3.0 34.38 356000.0 exp1
1400.0 4.0 29.2904 364000.0 exp1
1000.0 4.0 28.2902 364000.0 exp1
600.0 4.0 28.1056 364000.0 exp1
400.0 4.0 27.6845 364000.0 exp1
100.0 4.0 27.3948 364000.0 exp1
80.0 4.0 27.1053 364000.0 exp1
30.0 4.0 30.0064 364000.0 exp1
10.0 4.0 40.6451 364000.0 exp1
1400.0 10.0 69.0849 424000.0 exp1
1000.0 10.0 69.7414 424000.0 exp1
600.0 10.0 68.6083 424000.0 exp1
400.0 10.0 68.5285 424000.0 exp1
100.0 10.0 66.027 424000.0 exp1
80.0 10.0 66.8427 424000.0 exp1
30.0 10.0 69.9604 424000.0 exp1
10.0 10.0 80.4004 424000.0 exp1
1400.0 20.0 136.711 528000.0 exp1
1000.0 20.0 137.915 528000.0 exp1
600.0 20.0 138.17 528000.0 exp1
400.0 20.0 146.536 528000.0 exp1
100.0 20.0 134.371 528000.0 exp1
80.0 20.0 136.676 528000.0 exp1
30.0 20.0 135.614 528000.0 exp1
10.0 20.0 146.406 528000.0 exp1
# posixuser-ns3kernel
# pktsz nodes usec mem
1400.0 1.0 23.9935 284000.0 exp2
1000.0 1.0 22.275 284000.0 exp2
600.0 1.0 21.1502 284000.0 exp2
400.0 1.0 20.3162 284000.0 exp2
100.0 1.0 19.3629 284000.0 exp2
80.0 1.0 17.2199 284000.0 exp2
30.0 1.0 11.6907 284000.0 exp2
10.0 1.0 9.41529 284000.0 exp2
1400.0 2.0 29.8507 320000.0 exp2
1000.0 2.0 28.3504 320000.0 exp2
600.0 2.0 26.4979 320000.0 exp2
400.0 2.0 25.6115 320000.0 exp2
100.0 2.0 24.1463 320000.0 exp2
80.0 2.0 23.0901 320000.0 exp2
30.0 2.0 23.7398 320000.0 exp2
10.0 2.0 34.3538 320000.0 exp2
1400.0 3.0 41.754 332000.0 exp2
1000.0 3.0 40.6361 332000.0 exp2
600.0 3.0 37.2756 332000.0 exp2
400.0 3.0 35.2836 332000.0 exp2
100.0 3.0 34.4021 332000.0 exp2
80.0 3.0 32.5921 332000.0 exp2
30.0 3.0 32.4704 332000.0 exp2
10.0 3.0 42.9911 332000.0 exp2
1400.0 4.0 50.8228 340000.0 exp2
1000.0 4.0 48.7364 340000.0 exp2
600.0 4.0 49.6738 340000.0 exp2
400.0 4.0 41.5514 340000.0 exp2
100.0 4.0 41.3879 340000.0 exp2
80.0 4.0 40.0402 340000.0 exp2
30.0 4.0 39.5275 340000.0 exp2
10.0 4.0 50.0416 340000.0 exp2
1400.0 10.0 105.808 400000.0 exp2
1000.0 10.0 100.717 400000.0 exp2
600.0 10.0 92.6241 400000.0 exp2
400.0 10.0 88.9403 400000.0 exp2
100.0 10.0 84.3573 400000.0 exp2
80.0 10.0 83.9941 400000.0 exp2
30.0 10.0 82.3179 400000.0 exp2
10.0 10.0 90.7838 400000.0 exp2
1400.0 20.0 195.386 500000.0 exp2
1000.0 20.0 187.948 500000.0 exp2
600.0 20.0 174.074 500000.0 exp2
400.0 20.0 161.676 500000.0 exp2
100.0 20.0 158.474 500000.0 exp2
80.0 20.0 153.947 500000.0 exp2
30.0 20.0 149.705 500000.0 exp2
10.0 20.0 158.209 500000.0 exp2
# posixuser-linuxkernel
1400.0 1.0 18.8925 1804000.0 exp3-nopic
1000.0 1.0 17.415 1804000.0 exp3-nopic
600.0 1.0 15.4777 1804000.0 exp3-nopic
400.0 1.0 14.967 1804000.0 exp3-nopic
100.0 1.0 13.9716 1804000.0 exp3-nopic
80.0 1.0 11.4151 1804000.0 exp3-nopic
30.0 1.0 5.42379 1804000.0 exp3-nopic
10.0 1.0 3.0551 1804000.0 exp3-nopic
1400.0 2.0 35.1408 2312000.0 exp3-nopic
1000.0 2.0 33.6159 2312000.0 exp3-nopic
600.0 2.0 30.5503 2312000.0 exp3-nopic
400.0 2.0 29.2868 2312000.0 exp3-nopic
100.0 2.0 27.6743 2312000.0 exp3-nopic
80.0 2.0 25.5777 2312000.0 exp3-nopic
30.0 2.0 25.6222 2312000.0 exp3-nopic
10.0 2.0 34.1143 2312000.0 exp3-nopic
1400.0 3.0 50.8247 3408000.0 exp3-nopic
1000.0 3.0 48.469 3408000.0 exp3-nopic
600.0 3.0 44.0047 3408000.0 exp3-nopic
400.0 3.0 41.6631 3408000.0 exp3-nopic
100.0 3.0 39.9732 3408000.0 exp3-nopic
80.0 3.0 38.92 3408000.0 exp3-nopic
30.0 3.0 38.1046 3408000.0 exp3-nopic
10.0 3.0 46.4725 3408000.0 exp3-nopic
1400.0 4.0 67.4539 4508000.0 exp3-nopic
1000.0 4.0 63.3236 4508000.0 exp3-nopic
600.0 4.0 58.0279 4508000.0 exp3-nopic
400.0 4.0 55.6078 4508000.0 exp3-nopic
100.0 4.0 53.1384 4508000.0 exp3-nopic
80.0 4.0 51.3447 4508000.0 exp3-nopic
30.0 4.0 50.6402 4508000.0 exp3-nopic
10.0 4.0 59.1352 4508000.0 exp3-nopic
1400.0 10.0 160.839 11100000.0 exp3-nopic
1000.0 10.0 162.478 11100000.0 exp3-nopic
600.0 10.0 139.116 11100000.0 exp3-nopic
400.0 10.0 136.227 11100000.0 exp3-nopic
100.0 10.0 132.659 11100000.0 exp3-nopic
80.0 10.0 128.236 11100000.0 exp3-nopic
30.0 10.0 122.553 11100000.0 exp3-nopic
10.0 10.0 133.47 11100000.0 exp3-nopic
1400.0 20.0 311.62 22096000.0 exp3-nopic
1000.0 20.0 301.081 22096000.0 exp3-nopic
600.0 20.0 276.518 22096000.0 exp3-nopic
400.0 20.0 269.517 22096000.0 exp3-nopic
100.0 20.0 259.321 22096000.0 exp3-nopic
80.0 20.0 258.255 22096000.0 exp3-nopic
30.0 20.0 236.266 22096000.0 exp3-nopic
10.0 20.0 257.22 22096000.0 exp3-nopic
1400.0 1.0 20.7818 1108000.0 exp3-pic
1000.0 1.0 17.685 1108000.0 exp3-pic
600.0 1.0 16.4502 1108000.0 exp3-pic
400.0 1.0 15.5613 1108000.0 exp3-pic
100.0 1.0 14.3905 1108000.0 exp3-pic
80.0 1.0 11.9988 1108000.0 exp3-pic
30.0 1.0 5.92644 1108000.0 exp3-pic
10.0 1.0 3.4402 1108000.0 exp3-pic
1400.0 2.0 36.6522 832000.0 exp3-pic
1000.0 2.0 34.561 832000.0 exp3-pic
600.0 2.0 32.5762 832000.0 exp3-pic
400.0 2.0 30.2595 832000.0 exp3-pic
100.0 2.0 29.2824 832000.0 exp3-pic
80.0 2.0 27.0263 832000.0 exp3-pic
30.0 2.0 26.965 832000.0 exp3-pic
10.0 2.0 36.4201 832000.0 exp3-pic
1400.0 3.0 53.6588 1184000.0 exp3-pic
1000.0 3.0 51.5742 1184000.0 exp3-pic
600.0 3.0 47.6515 1184000.0 exp3-pic
400.0 3.0 44.6892 1184000.0 exp3-pic
100.0 3.0 43.6219 1184000.0 exp3-pic
80.0 3.0 41.2227 1184000.0 exp3-pic
30.0 3.0 40.3945 1184000.0 exp3-pic
10.0 3.0 49.6258 1184000.0 exp3-pic
1400.0 4.0 69.3434 1536000.0 exp3-pic
1000.0 4.0 66.294 1536000.0 exp3-pic
600.0 4.0 61.6749 1536000.0 exp3-pic
400.0 4.0 58.526 1536000.0 exp3-pic
100.0 4.0 56.5305 1536000.0 exp3-pic
80.0 4.0 54.8369 1536000.0 exp3-pic
30.0 4.0 54.085 1536000.0 exp3-pic
10.0 4.0 64.7179 1536000.0 exp3-pic
1400.0 10.0 164.997 3668000.0 exp3-pic
1000.0 10.0 157.48 3668000.0 exp3-pic
600.0 10.0 145.926 3668000.0 exp3-pic
400.0 10.0 142.443 3668000.0 exp3-pic
100.0 10.0 137.499 3668000.0 exp3-pic
80.0 10.0 136.347 3668000.0 exp3-pic
30.0 10.0 126.181 3668000.0 exp3-pic
10.0 10.0 139.126 3668000.0 exp3-pic
1400.0 20.0 321.641 7224000.0 exp3-pic
1000.0 20.0 309.865 7224000.0 exp3-pic
600.0 20.0 290.551 7224000.0 exp3-pic
400.0 20.0 282.715 7224000.0 exp3-pic
100.0 20.0 272.982 7224000.0 exp3-pic
80.0 20.0 266.403 7224000.0 exp3-pic
30.0 20.0 238.415 7224000.0 exp3-pic
10.0 20.0 271.268 7224000.0 exp3-pic
/* vim: ts=4:sw=4:et:ai:sts=4
*/
#include <sys/timerfd.h>
#include <time.h>
#include <sys/time.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/socket.h>
#include <stdint.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#define HDR_SIZE (14 + 20 + 8) /* eth + ip + udp headers */
static uint64_t current_time(void);
static void fatal(const char *func, const char *detailed) {
char *error;
if(detailed)
fprintf(stderr, "%s\n", detailed);
if(func) {
error = strerror(errno);
fprintf(stderr, "%s: %s (%d)\n", func, error, errno);
}
exit(1);
}
static void set_txbuf_size(int fd, int buffer_size) {
int msg_size = buffer_size;
int status = setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (char*)&msg_size,
sizeof(msg_size));
if(status == -1)
fatal("setsockopt", "Unable to set socket buffer size");
}
static void run_client(const char *to_ip, unsigned to_port, unsigned pkt_size) {
int status, fd, cfd;
uint64_t seq;
struct sockaddr_in addr, to;
void *buffer;
if(pkt_size < HDR_SIZE)
fatal(NULL, "Cannot send packets that small.");
pkt_size -= HDR_SIZE;
buffer = malloc(pkt_size);
if(! buffer)
fatal("malloc", NULL);
memset(buffer, 0, pkt_size);
fd = socket(AF_INET, SOCK_DGRAM, 0);
if(fd == -1)
fatal("socket", "Unable to create udp socket");
cfd = socket(AF_INET, SOCK_STREAM, 0);
if(cfd == -1)
fatal("socket", "Unable to create tcp socket");
#if 0
status = fcntl(fd, F_GETFL, 0);
if(status == -1)
fatal("fcntl", NULL);
status = fcntl(fd, F_SETFL, status | O_NONBLOCK);
if(status == -1)
fatal("fcntl", NULL);
set_txbuf_size(fd, 1<<20);
#endif
addr.sin_family = AF_INET;
addr.sin_port = 0;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
to.sin_family = AF_INET;
to.sin_port = htons(to_port);
to.sin_addr.s_addr = inet_addr(to_ip);
status = bind(fd, (struct sockaddr*)&addr, sizeof(addr));
if(status == -1)
fatal("bind", NULL);
status = connect(cfd, (struct sockaddr*)&to, sizeof(to));
if(status == -1)
fatal("connect", "Can not connect to server");
for(seq = 0; ; seq++) {
ssize_t written;
uint64_t now;
fd_set rfds;
struct timeval tv;
/* Check if asked to stop */
FD_ZERO(&rfds);
FD_SET(cfd, &rfds);
tv.tv_sec = 0;
tv.tv_usec = 0;
status = select(cfd + 1, &rfds, NULL, NULL, &tv);
if(status == -1)
fatal("select", NULL);
if(status) {
status = recv(cfd, buffer, 8, 0);
if(status == -1)
fatal("recv", NULL);
if(((uint64_t *)buffer)[0] == 0xdeadbeef)
break;
fatal(NULL, "Received invalid control message");
}
now = current_time();
if(pkt_size >= sizeof(uint64_t))
((uint64_t *)buffer)[0] = now;
if(pkt_size >= 2 * sizeof(uint64_t))
((uint64_t *)buffer)[1] = seq;
written = sendto(fd, buffer, pkt_size, 0, (struct sockaddr *)&to,
sizeof(to));
if(written == -1 && (errno == EWOULDBLOCK || errno == EAGAIN))
continue;
if(written == -1)
fatal("sendto", NULL);
}
free(buffer);
status = close(cfd);
if(status == -1)
fatal("close", "Unable to close socket");
status = close(fd);
if(status == -1)
fatal("close", "Unable to close socket");
}
static uint64_t current_time(void) {
struct timeval tv;
uint64_t current_time;
int status;
status = gettimeofday(&tv, 0);
if(status == -1)
fatal("gettimeofday", "Unable to get current time\n");
current_time = tv.tv_sec * 1000000 + tv.tv_usec;
return current_time;
}
static void run_server(int port, uint64_t max_time, uint64_t max_pkts,
uint64_t max_bytes, bool verbose) {
struct sockaddr_in addr;
int fd, cfd, serverfd, status;
uint64_t now, last_ts, last_seq, preceived, breceived, errors;
uint64_t start, tot_delay, max_delay, min_delay, last_delay;
double jitter = 0.0L;
ssize_t pkt_size = -1, buffer_sz = 1 << 17; /* should be enough */
void *buffer;
uint64_t magic = 0xdeadbeef;
buffer = malloc(buffer_sz);
if(! buffer)
fatal("malloc", NULL);
fd = socket(AF_INET, SOCK_DGRAM, 0);
if(fd == -1)
fatal("socket", "Unable to create udp socket");
serverfd = socket(AF_INET, SOCK_STREAM, 0);
if(serverfd == -1)
fatal("socket", "Unable to create tcp socket");
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
status = bind(fd,(struct sockaddr*)&addr, sizeof(addr));
if(status == -1)
fatal("bind", "Unable to bind to specified port");
status = 1; /* no need for other var :) */
status = setsockopt(serverfd, SOL_SOCKET, SO_REUSEADDR, &status,
sizeof(status));
if(status == -1)
fatal("setsockopt", "Unable to set SO_REUSEADDR");
status = bind(serverfd,(struct sockaddr*)&addr, sizeof(addr));
if(status == -1)
fatal("bind", "Unable to bind to specified port");
status = listen(serverfd, 1);
if(status == -1)
fatal("listen", "Unable to receive connections");
cfd = accept(serverfd, NULL, 0);
if(cfd == -1)
fatal("accept", "Unable to receive connection");
preceived = breceived = errors = 0;
last_ts = last_seq = start = 0;
tot_delay = max_delay = 0; last_delay = min_delay = -1;
while(true) {
uint64_t ts = 0, seq = 0;
ssize_t received;
received = recvfrom(fd, buffer, buffer_sz, 0, 0, 0);
now = current_time();
if(received >= sizeof(uint64_t))
ts = ((uint64_t *)buffer)[0];
if(received >= 2 * sizeof(uint64_t))
seq = ((uint64_t *)buffer)[1];
if(pkt_size == -1) {
/* init: first packet is ignored */
pkt_size = received;
last_ts = ts;
last_seq = seq;
start = now;
} else {
if(pkt_size != received) {
errors++;
fprintf(stderr, "Received packet of invalid size %ld.\n",
received);
} else {
preceived++;
breceived += received;
breceived += HDR_SIZE;
if(ts) {
if(last_delay >= 0) {
double delta;
if(last_delay > now - ts)
delta = last_delay - (now - ts);
else
delta = (now - ts) - last_delay;
jitter += (delta - jitter) / 16.0;
}
last_delay = now - ts;
tot_delay += last_delay;
if(last_delay < min_delay)
min_delay = last_delay;
if(last_delay > max_delay)
max_delay = last_delay;
}
if((ts && ts <= last_ts) || (seq && seq <= last_seq)) {
errors++;
fprintf(stderr, "Packet received out of order.\n");
}
last_ts = ts;
last_seq = seq;
}
if((max_pkts && preceived + errors >= max_pkts) ||
(max_time && now - start >= max_time) ||
(max_bytes && breceived >= max_bytes))
break;
}
}
free(buffer);
/* Tell client to die */
send(cfd, &magic, sizeof(magic), 0);
status = close(cfd);
if(status == -1)
fatal("close", "Unable to close socket");
status = close(serverfd);
if(status == -1)
fatal("close", "Unable to close socket");
if(verbose) {
printf("Received: %ld bytes %ld packets (size %ld/%ld) %ld errors.\n",
breceived, preceived, pkt_size + HDR_SIZE, pkt_size, errors);
printf("Delay: %ld/%ld/%ld (min/avg/max). Jitter: %lf. Time: %ld us\n",
min_delay, tot_delay / preceived, max_delay, jitter,
now - start);
printf("Bandwidth: %ld bit/s.\n",
(long)(1.0L * (breceived * 8000000) / (now - start)));
} else {
printf("brx:%ld prx:%ld pksz:%ld plsz:%ld err:%ld ",
breceived, preceived, pkt_size + HDR_SIZE, pkt_size, errors);
printf("mind:%ld avgd:%ld maxd:%ld jit:%lf time:%ld ",
min_delay, tot_delay / preceived, max_delay, jitter,
now - start);
}
status = close(fd);
if(status == -1)
fatal("close", "Unable to close socket");
}
#define CHECK_INT_ARG(arg, name, value) \
if(strncmp(arg, "--"name"=", strlen("--"name"=")) == 0) { \
value = atoi(arg + strlen("--"name"=")); \
continue; \
}
#define CHECK_LLINT_ARG(arg, name, value) \
if(strncmp(arg, "--"name"=", strlen("--"name"=")) == 0) { \
value = atoll(arg + strlen("--"name"=")); \
continue; \
}
#define CHECK_STR_ARG(arg, name, value) \
if(strncmp(arg, "--"name"=", strlen("--"name"=")) == 0) { \
value = arg + strlen("--"name"="); \
continue; \
}
static char *progname;
void usage(FILE *f) {
char *filler, *sp = " ";
if(strlen(progname) < strlen(sp))
filler = sp + strlen(sp) - strlen(progname);
else
filler = sp;
fprintf(f, "\n");
fprintf(f, "Usage: %s --client [--host=HOST] [--port=PORT] "
"[--pktsize=BYTES]\n", progname);
fprintf(f, " %s --server [--port=PORT] [--max-time=SECS] "
"[--max-pkts=NUM]\n", progname);
fprintf(f, " %s [--max-bytes=BYTES] [--verbose]\n", filler);
}
int main(int argc, char *argv[]) {
uint64_t max_time = 0, max_pkts = 0, max_bytes = 0;
int pkt_size = 1500, port = 5000;
const char *to_ip = "127.0.0.1";
bool server = false, client = false, verbose = false;
char **arg = argv + 1;
progname = strrchr(argv[0], '/');
if(progname)
progname++; /* skip over the slash */
else
progname = argv[0];
for(; *arg != 0; arg++)
{
CHECK_INT_ARG(*arg, "pktsize", pkt_size);
CHECK_INT_ARG(*arg, "port", port);
CHECK_LLINT_ARG(*arg, "max-time", max_time);
CHECK_LLINT_ARG(*arg, "max-pkts", max_pkts);
CHECK_LLINT_ARG(*arg, "max-bytes", max_bytes);
CHECK_STR_ARG(*arg, "host", to_ip);
if(strcmp(*arg, "--server") == 0) {
server = true;
continue;
}
if(strcmp(*arg, "--client") == 0) {
client = true;
continue;
}
if(strcmp(*arg, "--verbose") == 0) {
verbose = true;
continue;
}
if(strcmp(*arg, "--help") == 0) {
usage(stdout);
exit(0);
}
fprintf(stderr, "Unknown parameter: %s\n", *arg);
usage(stderr);
exit(1);
}
if(client == server) {
fprintf(stderr,
"Exactly one of --client and --server must be specified.\n");
usage(stderr);
exit(1);
}
if(!(max_time || max_pkts || max_bytes))
max_time = 10;
max_time *= 1000000;
if(client)
run_client(to_ip, port, pkt_size);
else
run_server(port, max_time, max_pkts, max_bytes, verbose);
return 0;
}
IMGS = openlogo.svg
PDF_IMGS := $(patsubst %.svg,%.pdf,$(patsubst %.dia,%.pdf,$(IMGS)))
DVI_IMGS := $(patsubst %.svg,%.eps,$(patsubst %.dia,%.eps,$(IMGS)))
ALL = nemu.pdf
all: $(ALL)
%.eps: %.dia
inkscape -E $@ $<
%.pdf: %.dia
inkscape -A $@ $<
%.eps: %.svg
inkscape -E $@ $<
%.pdf: %.svg
inkscape -A $@ $<
%.ps: %.dvi
dvips $<
nemu.dvi: nemu.tex $(DVI_IMGS)
latex $<
latex $<
nemu.pdf: nemu.tex $(PDF_IMGS)
pdflatex $<
pdflatex $<
clean:
rm -f $(PDF_IMGS) $(DVI_IMGS) *.aux *.out *.log *.dvi *.nav *.snm \
*.toc *.vrb *.bak $(ALL)
% vim:ts=2:sw=2:et:ai:sts=2
\documentclass{beamer}
\mode<presentation>
{
\usetheme{Boadilla} % simple
\usecolortheme{seahorse}
\useinnertheme{rectangles}
}
\usepackage[english]{babel}
\usepackage[utf8]{inputenc}
\usepackage[normalem]{ulem}
\DeclareRobustCommand{\hsout}[1]{\texorpdfstring{\sout{#1}}{#1}}
\pgfdeclareimage[height=0.5cm]{debian-logo}{openlogo}
\pgfdeclareimage[height=2cm]{debian-logo-big}{openlogo}
\title{Introducing Nemu}
\subtitle{Network EMUlator in a \hsout{box} Python library}
\author{Martín Ferrari}
\institute[DebConf 12]{\pgfuseimage{debian-logo-big}}
\date{July 14, 2012}
\subject{Talks}
\logo{\pgfuseimage{debian-logo}}
\begin{document}
\begin{frame}
\titlepage
\end{frame}
\begin{frame}{What is Nemu?}
\begin{itemize}
\item A \alert{python} library,
\item to create \alert{emulated networks},
\item and run \alert{tests and experiments}
\item that can be \alert{repeated}.
\item[]{}
\item[] \em{A by-product of research that found a practical use.}
\end{itemize}
\end{frame}
\begin{frame}{What can I use it for?}
\begin{itemize}
\item Test your new peer-to-peer application.
\item[] \small{\em{Run 50 instances in your machine!}}
\vfill
\item Observe behaviour on unreliable networks.
\item[] \small{\em{Configure packet loss, delay, throughput...}}
\vfill
\item Justify your changes with experimental data.
\item[] \small{\em{Make your script output nice GNUPlot graphs!}}
\vfill
\item Verify configuration changes before applying to the production network.
\item[] \small{\em{Change iptables and routing configuration with
confidence!}}
\vfill
\item Distribute your experiment/test easily, no configuration needed!
\item[] \small{\em{Here, execute this and see for yourself!}}
\end{itemize}
\end{frame}
\begin{frame}[fragile]{How does it look like?}
\begin{semiverbatim}
import nemu
node0 = nemu.Node()
node1 = nemu.Node()
(if0, if1) = nemu.P2PInterface.create_pair(node0, node1)
if0.up = if1.up = True
if0.add_v4_address(address='10.0.0.1', prefix_len=24)
if1.add_v4_address(address='10.0.0.2', prefix_len=24)
node0.system("ping -c 5 10.0.0.2")
\end{semiverbatim}
\end{frame}
\begin{frame}{Resources}
Related projects:\\
\begin{itemize}
\item NEPI: original project that spawned the development of Nemu.\\
High-level network description, GUI, multiple back-ends.
\item Mininet: similar project from Stanford, developed at the same time.
\end{itemize}
\hfill
Links:\\
\begin{itemize}
\item Nemu homepage: \texttt{http://code.google.com/p/nemu/}
\item NEPI homepage: \texttt{http://nepi.pl.sophia.inria.fr/}
\item This slides + code:
\texttt{\$HOME/source/browse/docs/debconf-talk/}
\end{itemize}
\end{frame}
\end{document}
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 10.0, SVG Export Plug-In . SVG Version: 3.0.0 Build 77) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
<!ENTITY ns_extend "http://ns.adobe.com/Extensibility/1.0/">
<!ENTITY ns_ai "http://ns.adobe.com/AdobeIllustrator/10.0/">
<!ENTITY ns_graphs "http://ns.adobe.com/Graphs/1.0/">
<!ENTITY ns_vars "http://ns.adobe.com/Variables/1.0/">
<!ENTITY ns_imrep "http://ns.adobe.com/ImageReplacement/1.0/">
<!ENTITY ns_sfw "http://ns.adobe.com/SaveForWeb/1.0/">
<!ENTITY ns_custom "http://ns.adobe.com/GenericCustomNamespace/1.0/">
<!ENTITY ns_adobe_xpath "http://ns.adobe.com/XPath/1.0/">
<!ENTITY ns_svg "http://www.w3.org/2000/svg">
<!ENTITY ns_xlink "http://www.w3.org/1999/xlink">
]>
<svg
xmlns:x="&ns_extend;" xmlns:i="&ns_ai;" xmlns:graph="&ns_graphs;" i:viewOrigin="251 467" i:rulerOrigin="0 0" i:pageBounds="0 792 612 0"
xmlns="&ns_svg;" xmlns:xlink="&ns_xlink;" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
width="108.758" height="144.133" viewBox="0 0 108.758 144.133" overflow="visible" enable-background="new 0 0 108.758 144.133"
xml:space="preserve">
<metadata>
<variableSets xmlns="&ns_vars;">
<variableSet varSetName="binding1" locked="none">
<variables></variables>
<v:sampleDataSets xmlns="&ns_custom;" xmlns:v="&ns_vars;"></v:sampleDataSets>
</variableSet>
</variableSets>
<sfw xmlns="&ns_sfw;">
<slices></slices>
<sliceSourceBounds y="322.867" x="251" width="108.758" height="144.133" bottomLeftOrigin="true"></sliceSourceBounds>
</sfw>
</metadata>
<g id="Layer_1" i:layer="yes" i:dimmedPercent="50" i:rgbTrio="#4F008000FFFF">
<g>
<path i:knockout="Off" fill="#D70751" d="M60.969,47.645c-1.494,0.02,0.281,0.768,2.232,1.069
c0.541-0.422,1.027-0.846,1.463-1.26C63.451,47.751,62.215,47.758,60.969,47.645"/>
<path i:knockout="Off" fill="#D70751" d="M68.986,45.646c0.893-1.229,1.541-2.573,1.77-3.963
c-0.201,0.99-0.736,1.845-1.244,2.749c-2.793,1.759-0.264-1.044-0.002-2.111C66.508,46.104,69.096,44.589,68.986,45.646"/>
<path i:knockout="Off" fill="#D70751" d="M71.949,37.942c0.182-2.691-0.529-1.839-0.768-0.814
C71.459,37.274,71.68,39.026,71.949,37.942"/>
<path i:knockout="Off" fill="#D70751" d="M55.301,1.163c0.798,0.142,1.724,0.252,1.591,0.443
C57.768,1.413,57.965,1.239,55.301,1.163"/>
<path i:knockout="Off" fill="#D70751" d="M56.893,1.606l-0.561,0.117l0.523-0.048L56.893,1.606"/>
<path i:knockout="Off" fill="#D70751" d="M81.762,38.962c0.09,2.416-0.705,3.59-1.424,5.666l-1.293,0.643
c-1.057,2.054,0.105,1.304-0.652,2.937c-1.652,1.467-5.006,4.589-6.08,4.875c-0.785-0.017,0.531-0.926,0.703-1.281
c-2.209,1.516-1.773,2.276-5.152,3.199l-0.098-0.221c-8.33,3.92-19.902-3.847-19.75-14.443c-0.088,0.672-0.253,0.504-0.437,0.774
c-0.43-5.451,2.518-10.926,7.49-13.165c4.863-2.406,10.564-1.42,14.045,1.829c-1.912-2.506-5.721-5.163-10.232-4.917
c-4.421,0.072-8.558,2.881-9.938,5.932c-2.264,1.425-2.528,5.496-3.514,6.242c-1.329,9.76,2.497,13.975,8.97,18.936
c1.016,0.686,0.286,0.791,0.422,1.313c-2.15-1.006-4.118-2.526-5.738-4.387c0.86,1.257,1.787,2.479,2.986,3.439
c-2.029-0.685-4.738-4.913-5.527-5.085c3.495,6.258,14.178,10.975,19.775,8.634c-2.59,0.096-5.879,0.053-8.787-1.022
c-1.225-0.629-2.884-1.93-2.587-2.173c7.636,2.851,15.522,2.158,22.128-3.137c1.682-1.31,3.518-3.537,4.049-3.567
c-0.799,1.202,0.137,0.578-0.477,1.639c1.672-2.701-0.729-1.1,1.73-4.664l0.908,1.25c-0.34-2.244,2.785-4.966,2.467-8.512
c0.717-1.084,0.799,1.168,0.039,3.662c1.055-2.767,0.279-3.212,0.549-5.496c0.291,0.768,0.678,1.583,0.875,2.394
c-0.688-2.675,0.703-4.503,1.049-6.058c-0.342-0.15-1.061,1.182-1.227-1.976c0.025-1.372,0.383-0.719,0.52-1.057
c-0.268-0.155-0.975-1.207-1.404-3.224c0.309-0.475,0.832,1.229,1.256,1.298c-0.273-1.603-0.742-2.826-0.762-4.057
c-1.24-2.59-0.439,0.346-1.443-1.112c-1.32-4.114,1.094-0.955,1.258-2.823c1.998,2.895,3.137,7.385,3.662,9.244
c-0.4-2.267-1.045-4.464-1.834-6.589c0.609,0.257-0.979-4.663,0.791-1.405c-1.889-6.945-8.078-13.435-13.773-16.479
c0.695,0.637,1.574,1.437,1.26,1.563c-2.834-1.685-2.336-1.818-2.742-2.53c-2.305-0.939-2.459,0.077-3.984,0.002
c-4.35-2.308-5.188-2.063-9.191-3.507l0.182,0.852c-2.881-0.96-3.357,0.362-6.47,0.002c-0.189-0.147,0.998-0.536,1.976-0.677
c-2.786,0.368-2.656-0.55-5.382,0.101c0.671-0.471,1.383-0.784,2.099-1.184c-2.271,0.138-5.424,1.322-4.451,0.244
c-3.705,1.654-10.286,3.975-13.979,7.438l-0.116-0.776c-1.692,2.031-7.379,6.066-7.832,8.699l-0.453,0.105
c-0.879,1.491-1.45,3.18-2.148,4.713c-1.151,1.963-1.688,0.756-1.524,1.064c-2.265,4.592-3.392,8.45-4.363,11.616
c0.692,1.035,0.017,6.232,0.278,10.391c-1.136,20.544,14.418,40.489,31.42,45.093c2.492,0.893,6.197,0.861,9.349,0.949
c-3.718-1.064-4.198-0.563-7.822-1.826c-2.613-1.232-3.185-2.637-5.037-4.244l0.733,1.295c-3.63-1.285-2.111-1.59-5.065-2.525
l0.783-1.021c-1.177-0.09-3.117-1.982-3.647-3.033l-1.288,0.051c-1.546-1.906-2.371-3.283-2.31-4.35l-0.416,0.742
c-0.471-0.809-5.691-7.158-2.983-5.68c-0.503-0.458-1.172-0.747-1.897-2.066l0.551-0.629c-1.301-1.677-2.398-3.826-2.314-4.542
c0.695,0.938,1.177,1.114,1.655,1.275c-3.291-8.164-3.476-0.449-5.967-8.31l0.526-0.042c-0.403-0.611-0.65-1.27-0.974-1.919
l0.23-2.285c-2.368-2.736-0.662-11.645-0.319-16.53c0.235-1.986,1.977-4.101,3.3-7.418l-0.806-0.138
c1.542-2.688,8.802-10.799,12.166-10.383c1.629-2.046-0.324-0.008-0.643-0.522c3.579-3.703,4.704-2.616,7.119-3.283
c2.603-1.545-2.235,0.604-1.001-0.589c4.503-1.149,3.19-2.614,9.063-3.197c0.62,0.352-1.437,0.544-1.953,1.001
c3.75-1.836,11.869-1.417,17.145,1.018c6.117,2.861,12.994,11.314,13.266,19.267l0.309,0.083
c-0.156,3.162,0.484,6.819-0.627,10.177L81.762,38.962"/>
<path i:knockout="Off" fill="#D70751" d="M44.658,49.695l-0.211,1.047c0.983,1.335,1.763,2.781,3.016,3.821
C46.561,52.804,45.892,52.077,44.658,49.695"/>
<path i:knockout="Off" fill="#D70751" d="M46.979,49.605c-0.52-0.576-0.826-1.268-1.172-1.956
c0.33,1.211,1.006,2.252,1.633,3.312L46.979,49.605"/>
<path i:knockout="Off" fill="#D70751" d="M88.063,40.675l-0.219,0.552c-0.402,2.858-1.273,5.686-2.605,8.309
C86.711,46.769,87.66,43.742,88.063,40.675"/>
<path i:knockout="Off" fill="#D70751" d="M55.598,0.446C56.607,0.077,58.08,0.243,59.154,0c-1.398,0.117-2.789,0.187-4.162,0.362
L55.598,0.446"/>
<path i:knockout="Off" fill="#D70751" d="M20.127,19.308c0.233,2.154-1.62,2.991,0.41,1.569
C21.627,18.423,20.113,20.2,20.127,19.308"/>
<path i:knockout="Off" fill="#D70751" d="M17.739,29.282c0.469-1.437,0.553-2.299,0.732-3.132
C17.178,27.804,17.875,28.157,17.739,29.282"/>
<path i:knockout="Off" d="M13.437,125.506c-0.045,0.047-0.045,7.506-0.138,9.453c-0.092,1.574-0.232,4.957-3.568,4.957
c-3.429,0-4.263-3.939-4.541-5.652c-0.324-1.9-0.324-3.477-0.324-4.17c0-2.225,0.139-8.436,5.375-8.436
c1.576,0,2.456,0.465,3.151,0.834L13.437,125.506z M0,130.975c0,13.066,6.951,13.066,7.97,13.066
c2.873,0,4.727-1.576,5.514-4.309l0.093,4.123c0.881-0.047,1.761-0.139,3.197-0.139c0.51,0,0.926,0,1.298,0.047
c0.371,0,0.741,0.045,1.158,0.092c-0.741-1.482-1.297-4.818-1.297-12.049c0-7.043,0-18.951,0.602-22.566
c-1.667,0.789-3.105,1.299-6.256,1.576c1.251,1.344,1.251,2.039,1.251,8.154c-0.879-0.277-1.992-0.602-3.892-0.602
C1.344,118.369,0,125.598,0,130.975"/>
<path i:knockout="Off" d="M25.13,128.609c0.047-3.846,0.835-7.275,4.124-7.275c3.615,0,3.891,3.984,3.799,7.275H25.13z
M37.64,129.074c0-5.422-1.065-10.752-7.923-10.752c-9.452,0-9.452,10.475-9.452,12.697c0,9.406,4.216,13.113,11.306,13.113
c3.149,0,4.68-0.461,5.514-0.695c-0.046-1.668,0.185-2.734,0.465-4.17c-0.975,0.604-2.226,1.391-5.006,1.391
c-7.229,0-7.322-6.582-7.322-8.852H37.55L37.64,129.074"/>
<path i:knockout="Off" d="M52.715,131.066c0,4.309-0.787,10.102-6.162,10.102c-0.742,0-1.668-0.141-2.27-0.279
c-0.093-1.668-0.093-4.541-0.093-7.877c0-3.986,0.416-6.068,0.742-7.09c0.972-3.289,3.15-3.334,3.566-3.334
C52.02,122.588,52.715,127.453,52.715,131.066z M39.417,136.117c0,3.43,0,5.375-0.556,6.857c1.9,0.742,4.262,1.158,7.09,1.158
c1.807,0,7.043,0,9.869-5.791c1.344-2.688,1.807-6.303,1.807-9.037c0-1.668-0.186-5.328-1.529-7.646
c-1.296-2.176-3.382-3.289-5.605-3.289c-4.449,0-5.746,3.707-6.44,5.607c0-2.363,0.045-10.611,0.415-14.828
c-3.011,1.391-4.866,1.621-6.857,1.807c1.807,0.74,1.807,3.801,1.807,13.764V136.117"/>
<path i:knockout="Off" d="M66.535,143.855c-0.928-0.139-1.578-0.232-2.922-0.232c-1.48,0-2.502,0.094-3.566,0.232
c0.463-0.881,0.648-1.299,0.787-4.309c0.186-4.125,0.232-15.154-0.092-17.471c-0.232-1.762-0.648-2.039-1.297-2.502
c3.799-0.371,4.865-0.648,6.625-1.482c-0.369,2.037-0.418,3.059-0.418,6.162C65.561,140.242,65.514,141.955,66.535,143.855"/>
<path i:knockout="Off" d="M81.373,130.74c-0.092,2.92-0.139,4.959-0.928,6.58c-0.973,2.086-2.594,2.688-3.799,2.688
c-2.783,0-3.383-2.316-3.383-4.586c0-4.355,3.893-4.682,5.652-4.682H81.373z M68.629,136.441c0,2.92,0.881,5.838,3.477,7.09
c1.158,0.51,2.316,0.51,2.688,0.51c4.264,0,5.699-3.152,6.58-5.098c-0.047,2.039,0,3.289,0.139,4.912
c0.834-0.047,1.668-0.139,3.059-0.139c0.787,0,1.529,0.092,2.316,0.139c-0.51-0.787-0.787-1.252-0.928-3.059
c-0.092-1.76-0.092-3.521-0.092-5.977l0.047-9.453c0-3.523-0.928-6.998-7.879-6.998c-4.586,0-7.273,1.391-8.617,2.086
c0.557,1.02,1.02,1.898,1.436,3.893c1.809-1.576,4.172-2.41,6.58-2.41c3.848,0,3.848,2.549,3.848,6.162
c-0.881-0.045-1.623-0.137-2.875-0.137C72.521,127.963,68.629,130.23,68.629,136.441"/>
<path i:knockout="Off" d="M108.063,139.268c0.047,1.576,0.047,3.244,0.695,4.588c-1.021-0.092-1.623-0.232-3.521-0.232
c-1.113,0-1.715,0.094-2.596,0.232c0.184-0.602,0.279-0.834,0.371-1.623c0.139-1.064,0.232-4.633,0.232-5.885v-5.004
c0-2.178,0-5.33-0.141-6.441c-0.092-0.787-0.322-2.918-3.012-2.918c-2.641,0-3.521,1.945-3.846,3.521
c-0.369,1.621-0.369,3.383-0.369,10.24c0.045,5.932,0.045,6.486,0.508,8.109c-0.787-0.092-1.76-0.184-3.15-0.184
c-1.113,0-1.854,0.045-2.779,0.184c0.324-0.742,0.51-1.113,0.602-3.707c0.094-2.549,0.279-15.061-0.141-18.025
c-0.23-1.809-0.695-2.225-1.203-2.688c3.754-0.186,4.957-0.789,6.117-1.389v4.91c0.555-1.438,1.713-4.635,6.348-4.635
c5.793,0,5.838,4.217,5.885,6.996V139.268"/>
<path i:knockout="Off" fill="#D70751" d="M66.926,111.533l-3.838,3.836l-3.836-3.836l3.836-3.836L66.926,111.533"/>
</g>
</g>
</svg>
#!/usr/bin/env python
# vim:ts=4:sw=4:et:ai:sts=4
import os, nemu, subprocess, time
xterm = nemu.environ.find_bin("xterm")
mtr = nemu.environ.find_bin("mtr")
X = "DISPLAY" in os.environ and xterm
# Do not use X stuff.
X = False
# each Node is a netns
node = []
switch = []
iface = {}
SIZE = 5
for i in range(SIZE):
node.append(nemu.Node(forward_X11 = X))
next_pair = (i, i + 1)
prev_pair = (i, i - 1)
if i < SIZE - 1:
iface[(i, i + 1)] = nemu.NodeInterface(node[i])
iface[(i, i + 1)].up = True
iface[(i, i + 1)].add_v4_address(address='10.0.%d.1' % i, prefix_len=24)
if i > 0:
iface[(i, i - 1)] = nemu.NodeInterface(node[i])
iface[(i, i - 1)].up = True
iface[(i, i - 1)].add_v4_address(address='10.0.%d.2' % (i - 1),
prefix_len=24)
switch.append(nemu.Switch())
switch[-1].connect(iface[(i, i - 1)])
switch[-1].connect(iface[(i - 1, i)])
switch[-1].up = True
# Configure routing
for j in range(SIZE - 1):
if j in (i, i - 1):
continue
if j < i:
node[i].add_route(prefix='10.0.%d.0' % j, prefix_len=24,
nexthop='10.0.%d.1' % (i - 1))
else:
node[i].add_route(prefix='10.0.%d.0' % j, prefix_len=24,
nexthop='10.0.%d.2' % i)
print "Nodes started with pids: %s" % str([n.pid for n in node])
#switch0 = nemu.Switch(
# bandwidth = 100 * 1024 * 1024,
# delay = 0.1, # 100 ms
# delay_jitter = 0.01, # 10ms
# delay_correlation = 0.25, # 25% correlation
# loss = 0.005)
# Test connectivity first. Run process, hide output and check
# return code
null = file("/dev/null", "w")
app0 = node[0].Popen("ping -c 1 10.0.%d.2" % (SIZE - 2), shell=True,
stdout=null)
ret = app0.wait()
assert ret == 0
app1 = node[-1].Popen("ping -c 1 10.0.0.1", shell = True, stdout = null)
ret = app1.wait()
assert ret == 0
print "Connectivity IPv4 OK!"
if X:
app = []
for i in range(SIZE - 1):
height = 102
base = 25
cmd = "%s -eni %s" % (nemu.environ.TCPDUMP_PATH, iface[(i, i + 1)].name)
xtermcmd = "%s -geometry 100x5+0+%d -T %s -e %s" % (
xterm, i * height + base, "node%d" % i, cmd)
app.append(node[i].Popen(xtermcmd, shell=True))
app.append(node[-1].Popen("%s -n 10.0.0.1" % mtr, shell=True))
app[-1].wait()
for i in range(SIZE - 1):
app[i].signal()
app[i].wait()
else:
node[-1].system("%s -n --report 10.0.0.1" % mtr)
#!/usr/bin/env python
# vim:ts=4:sw=4:et:ai:sts=4
import os, nemu, subprocess, time
xterm = nemu.environ.find_bin("xterm")
X = "DISPLAY" in os.environ and xterm
# each Node is a netns
node0 = nemu.Node(forward_X11 = X)
node1 = nemu.Node(forward_X11 = X)
node2 = nemu.Node(forward_X11 = X)
print "Nodes started with pids: %s" % str((node0.pid, node1.pid,
node2.pid))
# interface object maps to a veth pair with one end in a netns
if0 = nemu.NodeInterface(node0)
if1a = nemu.NodeInterface(node1)
# Between node1 and node2, we use a P2P interface
(if1b, if2) = nemu.P2PInterface.create_pair(node1, node2)
switch0 = nemu.Switch(
bandwidth = 100 * 1024 * 1024,
delay = 0.1, # 100 ms
delay_jitter = 0.01, # 10ms
delay_correlation = 0.25, # 25% correlation
loss = 0.005)
# connect the interfaces
switch0.connect(if0)
switch0.connect(if1a)
# bring the interfaces up
switch0.up = if0.up = if1a.up = if1b.up = if2.up = True
# Add IP addresses
if0.add_v4_address(address = '10.0.0.1', prefix_len = 24)
if1a.add_v4_address(address = '10.0.0.2', prefix_len = 24)
if1b.add_v4_address(address = '10.0.1.1', prefix_len = 24)
if2.add_v4_address(address = '10.0.1.2', prefix_len = 24)
# Configure routing
node0.add_route(prefix = '10.0.1.0', prefix_len = 24, nexthop = '10.0.0.2')
node2.add_route(prefix = '10.0.0.0', prefix_len = 24, nexthop = '10.0.1.1')
# Test connectivity first. Run process, hide output and check
# return code
null = file("/dev/null", "w")
app0 = node0.Popen("ping -c 1 10.0.1.2", shell = True, stdout = null)
ret = app0.wait()
assert ret == 0
app1 = node2.Popen("ping -c 1 10.0.0.1", shell = True, stdout = null)
ret = app1.wait()
assert ret == 0
print "Connectivity IPv4 OK!"
if X:
app1 = node1.Popen("%s -geometry -0+0 -e %s -ni %s" %
(xterm, nemu.environ.TCPDUMP_PATH, if1b.name), shell = True)
time.sleep(3)
app0 = node0.Popen("%s -geometry +0+0 -e ping -c 10 10.0.1.2" % xterm,
shell = True)
app0.wait()
app1.signal()
app1.wait()
# Now test the network conditions
# When using a args list, the shell is not needed
app2 = node2.Popen(["ping", "-q", "-c100000", "-f", "10.0.1.2"],
stdout = subprocess.PIPE)
out, err = app2.communicate()
print "Ping outout:"
print out
Protocol format:
----------------
RFC 2821-like.
At start-up, server sends 220 code and greeting text
To close the connection and the node, client sends QUIT command, and server
replies with 221 code.
Command Subcmd Arguments Response Effect
QUIT 221 Close the netns
IF LIST [if#] 200 serialised data ip link list
IF SET if# k v k v... 200/500 ip link set (1)
IF RTRN if# ns 200/500 ip link set netns $ns
IF DEL if# 200/500 ip link del
ADDR LIST [if#] 200 serialised data ip addr list
ADDR ADD if# addr_spec 200/500 ip addr add
ADDR DEL if# addr_spec 200/500 ip addr del
ROUT LIST 200 serialised data ip route list
ROUT ADD route_spec 200/500 ip route add
ROUT DEL route_spec 200/500 ip route del
PROC CRTE argv0 argv1... 200/500 (2)
PROC USER username 200/500 (3)
PROC CWD cwd 200/500 (3)
PROC ENV k v k v... 200/500 (3)
PROC SIN 354+200/500 (4)
PROC SOUT 354+200/500 (4)
PROC SERR 354+200/500 (4)
PROC RUN 200 <pid>/500 (5)
PROC ABRT 200 (5)
PROC POLL <pid> 200 <code>/450/500 check if process alive
PROC WAIT <pid> 200 <code>/500 waitpid(pid)
PROC KILL <pid> <signal> 200/500 kill(pid, signal)
X11 <prot> <data> 354+200/500 (6)
(1) valid arguments: mtu <n>, up <0|1>, name <name>, lladdr <addr>,
broadcast <addr>, multicast <0|1>, arp <0|1>.
(2) After PROC CRTE, only secondary PROC cmds are accepted until finished.
The parameters are parsed as base64-encoded strings if they start with a '='
character.
(3) Secondary PROC commands, only valid after PROC CRTE. All parameters parsed
as base64-encoded strings. Arguments for PROC ENV are pairs of key-value to
set up the process environment.
(4) Secondary PROC commands, only valid after PROC CRTE. Server reply 354 and
waits for a file descriptor to be passed along with a duplicate of the same
command. Answers 200/500 after processing the file descriptor.
(5) Secondary PROC commands, unconditionally end the PROC transaction. If RUN
was successful, the process is started and the process ID is returned as the
first token of the reply.
(6) Enable X11 forwarding, using the specified protocol and data for
authentication. A opened socket ready to receive X connections is passed over
the channel. Answers 200/500 after transmitting the file descriptor.
Sample session
--------------
Parent calls socketpair(), fork() and unshare(); thus creating a new netns;
protocol exchanges occur through the socket.
<S> 220 Hello
<C> IF LIST
<S> 200-[{id: 1, mtu: 16436, name: lo, up: true}, {id: 10,
<S> 200 lladdr: '12:34:56:78:9a:bc', mtu: 1500, name: eth0, up: true}]
<C> IF SET 10 MTU 1492
<S> 200 Ok.
<C> ADDR ADD 10 10.0.0.1 24 10.0.0.255
<S> 200 Ok.
<C> ADDR DEL 10 192.168.1.1 24
<S> 500 Address does not exist.
<C> PROC CRTE /bin/sh sh -c sleep 10
<S> 200 Entering PROC mode.
<C> PROC USER nobody
<S> 200 Program will run as `nobody'.
<C> PROC CWD /
<S> 200 CWD set to /.
<C> PROC SIN
<S> 354 Waiting for FD.
Server calls recvmsg()
Client calls sendmsg()
<S> 200 FD received OK.
<C> PROC RUN
<S> 200 1649 pid process started.
<C> PROC WAIT 1649
Time passes...
<S> 200 0 exit code
<C> QUIT
<S> 221 Exiting...
#/usr/bin/env python
# vim:ts=4:sw=4:et:ai:sts=4
import nemu
import signal
# run_as: user to setuid() to before running applications (this is assumed to
# run as root)
nemu.config.run_as = 'nobody'
# Clean-up is essential to avoid leaving bridge devices all over the place
# (luckily, the veths die automatically). This installs signals and exit
# handlers.
nemu.set_cleanup_hooks(on_exit = True,
on_signals = [signal.SIGTERM, signal.SIGINT])
# each Node is a netns
a = nemu.Node()
b = nemu.Node()
print "Nodes started with pids: %d and %d" % (a.pid, b.pid)
# interface object maps to a veth pair with one end in a netns
if0 = a.add_if(lladdr = '42:71:e0:90:ca:42')
# This is equivalent
#if0 = nemu.NodeInterface(a)
#if0.lladdr = '42:71:e0:90:ca:42'
if1 = b.add_if(mtu = 1492)
# for using with a tun device, to connect to the outside world
if2 = b.import_if('tun0')
# each Switch is a linux bridge, all the parameters are applied to the
# associated interfaces as tc qdiscs.
switch0 = nemu.Switch(bandwidth = 100 * 1024 * 1024,
delay = 0.01, delay_jitter = 0.001,
delay_correlation = 0.25, delay_distribution = 'normal',
loss = 0.005, loss_correlation = 0.20,
dup = 0.005, dup_correlation = 0.25,
corrupt = 0.005, corrupt_correlation = 0.25)
# connect to the bridge
switch0.connect(if0)
switch0.connect(if1)
# Should be experimented with Tom Geoff's patch to see if the bridge could be
# avoided; but for that the API would be slightly different, as these would be
# point-to-point interfaces and links.
# ppp0 = nemu.PPPSwitch(a, b, bandwidth = ....)
# if0 = ppp0.interface(a)
# For now, we have simple P2P interfaces:
(pppa, pppb) = nemu.P2PInterface.create_pair(a, b)
# Add and connect a tap device (as if a external router were plugged into a
# switch)
if2 = nemu.ImportedInterface('tap0')
switch0.connect(if2)
switch0.up = True
if0.up = True
if1.up = True
# addresses as iproute
if0.add_v4_address(addr = '10.0.0.1', prefix_len = 24)
if0.add_v6_address(addr = 'fe80::222:19ff:fe22:615d', prefix_len = 64)
if1.add_v4_address(addr = '10.0.0.2', prefix_len = 24,
broadcast = '10.1.0.255')
# ditto
#a.add_route(prefix = '0', prefix_len = 0, nexthop = '10.0.0.2')
a.add_default_route(nexthop = '10.0.0.2')
b.add_route(prefix = '10.1.0.0', prefix_len = 16, nexthop = '10.0.0.1')
b.add_route(prefix = '11.1.0.1', prefix_len = 32, device = if1)
# Some inspection methods: they will not read internal data but query the
# kernel
addrs = if0.get_addresses()
stats = if0.get_stats()
routes = a.get_routes()
ifaces = a.get_interfaces()
nodes = nemu.get_nodes()
switches = nemu.get_switches()
stats = link0.get_stats()
# Run a process in background
import subprocess
app0 = a.Popen("ping -c 3 10.0.0.2", shell = True, stdout = subprocess.PIPE)
print app0.stdout.readline()
app0.wait()
# Run, capture output and wait()
stdout = a.backticks(["ping", "-c", "3", "10.0.0.2"])
# Run an process with a pseudo-tty associated to it; provide a UNIX socket to
# interact with the process
app2 = a.start_tty_process("/bin/bash")
# app2.sockname, app2.sockfd
app2.wait()
# Example to set up a linear topology
def setup_linear_topology(n, bd, delay):
nodes = []
for i in range(n):
nodes.append(nemu.Node())
for i in range(n - 1):
if1 = nodes[i].add_if()
if2 = nodes[i + 1].add_if()
if1.add_v4_address(addr = ('10.0.%d.2' % i), prefix_len = 24)
if2.add_v4_address(addr = ('10.0.%d.1' % i), prefix_len = 24)
switch = nemu.Switch(bandwidth = bd, delay = delay)
switch.connect(if1)
switch.connect(if2)
for i in range(n):
for j in range(n):
if abs(i - j) <= 1:
continue
nodes[i].add_route(prefix = ('10.0.%d.0' % j), prefix_len = 24,
nexthop = ('10.0.%d.%d' % ((i, 1) if i < j else (i - 1, 2)))
)
return nodes
[clean]
all = 1
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# vim: ts=4:sw=4:et:ai:sts=4
from distutils.core import setup, Extension, Command
setup(
name = 'nemu',
version = '0.2',
description = 'A lightweight network emulator embedded in a small '
'python library.',
author = 'Martín Ferrari, Alina Quereilhac',
author_email = 'martin.ferrari@gmail.com, aquereilhac@gmail.com',
url = 'http://code.google.com/p/nemu/',
license = 'GPLv2',
platforms = 'Linux',
packages = ['nemu'],
package_dir = {'': 'src'}
)
# vim:ts=4:sw=4:et:ai:sts=4
# -*- coding: utf-8 -*-
# Copyright 2010, 2011 INRIA
# Copyright 2011 Martín Ferrari <martin.ferrari@gmail.com>
#
# This file is part of Nemu.
#
# Nemu is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License version 2, as published by the Free
# Software Foundation.
#
# Nemu is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# Nemu. If not, see <http://www.gnu.org/licenses/>.
"""Nemu package.
Nemu (Netwok EMUlator) is a small Python library to create emulated networks
and run and test programs in them.
"""
# pylint: disable=W0401,R0903
import os, pwd
from nemu.node import *
from nemu.interface import *
class _Config(object):
"""Global configuration singleton for Nemu."""
def __init__(self):
self._run_as = 65534
try:
pwd.getpwnam('nobody')
self._run_as = 'nobody'
except KeyError:
pass # User not found.
def _set_run_as(self, user):
"""Setter for `run_as'."""
if str(user).isdigit():
uid = int(user)
try:
_user = pwd.getpwuid(uid)[0]
except:
raise AttributeError("UID %d does not exist" % int(user))
run_as = int(user)
else:
try:
uid = pwd.getpwnam(str(user))[2]
except:
raise AttributeError("User %s does not exist" % str(user))
run_as = str(user)
if uid == 0:
raise AttributeError("Cannot run as root by default")
self._run_as = run_as
return run_as
def _get_run_as(self):
"""Setter for `run_as'."""
return self._run_as
run_as = property(_get_run_as, _set_run_as, None,
"Default user to run applications as")
config = _Config() # pylint: disable=C0103
# FIXME: set atfork hooks
# http://code.google.com/p/python-atfork/source/browse/atfork/__init__.py
#def set_cleanup_hooks(on_exit = False, on_signals = []):
# pass
# vim:ts=4:sw=4:et:ai:sts=4
# -*- coding: utf-8 -*-
# Copyright 2010, 2011 INRIA
# Copyright 2011 Martín Ferrari <martin.ferrari@gmail.com>
#
# This file is part of Nemu.
#
# Nemu is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License version 2, as published by the Free
# Software Foundation.
#
# Nemu is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# Nemu. If not, see <http://www.gnu.org/licenses/>.
import errno, os, os.path, socket, subprocess, sys, syslog
from syslog import LOG_ERR, LOG_WARNING, LOG_NOTICE, LOG_INFO, LOG_DEBUG
__all__ = ["IP_PATH", "TC_PATH", "BRCTL_PATH", "SYSCTL_PATH", "HZ"]
__all__ += ["TCPDUMP_PATH", "NETPERF_PATH", "XAUTH_PATH", "XDPYINFO_PATH"]
__all__ += ["execute", "backticks", "eintr_wrapper"]
__all__ += ["find_listen_port"]
__all__ += ["LOG_ERR", "LOG_WARNING", "LOG_NOTICE", "LOG_INFO", "LOG_DEBUG"]
__all__ += ["set_log_level", "logger"]
__all__ += ["error", "warning", "notice", "info", "debug"]
def find_bin(name, extra_path = None):
"""Try hard to find the location of needed programs."""
search = []
if "PATH" in os.environ:
search += os.environ["PATH"].split(":")
search.extend(os.path.join(x, y)
for x in ("/", "/usr/", "/usr/local/")
for y in ("bin", "sbin"))
if extra_path:
search += extra_path
for dirr in search:
path = os.path.join(dirr, name)
if os.path.exists(path):
return path
return None
def find_bin_or_die(name, extra_path = None):
"""Try hard to find the location of needed programs; raise on failure."""
res = find_bin(name, extra_path)
if not res:
raise RuntimeError("Cannot find `%s', impossible to continue." % name)
return res
IP_PATH = find_bin_or_die("ip")
TC_PATH = find_bin_or_die("tc")
BRCTL_PATH = find_bin_or_die("brctl")
SYSCTL_PATH = find_bin_or_die("sysctl")
# Optional tools
TCPDUMP_PATH = find_bin("tcpdump")
NETPERF_PATH = find_bin("netperf")
XAUTH_PATH = find_bin("xauth")
XDPYINFO_PATH = find_bin("xdpyinfo")
# Seems this is completely bogus. At least, we can assume that the internal HZ
# is bigger than this.
HZ = os.sysconf("SC_CLK_TCK")
try:
os.stat("/sys/class/net")
except:
raise RuntimeError("Sysfs does not seem to be mounted, impossible to " +
"continue.")
def execute(cmd):
"""Execute a command, if the return value is non-zero, raise an exception.
Raises:
RuntimeError: the command was unsuccessful (return code != 0).
"""
debug("execute(%s)" % cmd)
null = open("/dev/null", "r+")
proc = subprocess.Popen(cmd, stdout = null, stderr = subprocess.PIPE)
_, err = proc.communicate()
if proc.returncode != 0:
raise RuntimeError("Error executing `%s': %s" % (" ".join(cmd), err))
def backticks(cmd):
"""Execute a command and capture its output.
If the return value is non-zero, raise an exception.
Returns:
(stdout, stderr): tuple containing the captured output.
Raises:
RuntimeError: the command was unsuccessful (return code != 0).
"""
debug("backticks(%s)" % cmd)
proc = subprocess.Popen(cmd, stdout = subprocess.PIPE,
stderr = subprocess.PIPE)
out, err = proc.communicate()
if proc.returncode != 0:
raise RuntimeError("Error executing `%s': %s" % (" ".join(cmd), err))
return out
def eintr_wrapper(func, *args):
"Wraps some callable with a loop that retries on EINTR."
while True:
try:
return func(*args)
except OSError, ex: # pragma: no cover
if ex.errno == errno.EINTR:
continue
raise
except IOError, ex: # pragma: no cover
if ex.errno == errno.EINTR:
continue
raise
def find_listen_port(family = socket.AF_INET, type = socket.SOCK_STREAM,
proto = 0, addr = "127.0.0.1", min_port = 1, max_port = 65535):
sock = socket.socket(family, type, proto)
for port in range(min_port, max_port + 1):
try:
sock.bind((addr, port))
return sock, port
except socket.error:
pass
raise RuntimeError("Cannot find an usable port in the range specified")
# Logging
_log_level = LOG_WARNING
_log_use_syslog = False
_log_stream = sys.stderr
_log_syslog_opts = ()
_log_pid = os.getpid()
def set_log_level(level):
"Sets the log level for console messages, does not affect syslog logging."
global _log_level
assert level > LOG_ERR and level <= LOG_DEBUG
_log_level = level
def set_log_output(stream):
"Redirect console messages to the provided stream."
global _log_stream
assert hasattr(stream, "write") and hasattr(stream, "flush")
_log_stream = stream
def log_use_syslog(use = True, ident = None, logopt = 0,
facility = syslog.LOG_USER):
"Enable or disable the use of syslog for logging messages."
global _log_use_syslog, _log_syslog_opts
_log_syslog_opts = (ident, logopt, facility)
_log_use_syslog = use
_init_log()
def _init_log():
if not _log_use_syslog:
syslog.closelog()
return
(ident, logopt, facility) = _log_syslog_opts
if not ident:
#ident = os.path.basename(sys.argv[0])
ident = "nemu"
syslog.openlog("%s[%d]" % (ident, os.getpid()), logopt, facility)
info("Syslog logging started")
def logger(priority, message):
"Print a log message in syslog, console or both."
if _log_use_syslog:
if os.getpid() != _log_pid:
_init_log() # Need to tell syslog the new PID.
syslog.syslog(priority, message)
return
if priority > _log_level:
return
eintr_wrapper(_log_stream.write,
"[%d] %s\n" % (os.getpid(), message.rstrip()))
_log_stream.flush()
def error(message):
logger(LOG_ERR, message)
def warning(message):
logger(LOG_WARNING, message)
def notice(message):
logger(LOG_NOTICE, message)
def info(message):
logger(LOG_INFO, message)
def debug(message):
logger(LOG_DEBUG, message)
def _custom_hook(tipe, value, traceback): # pragma: no cover
"""Custom exception hook, to print nested exceptions information."""
if hasattr(value, "child_traceback"):
sys.stderr.write("Nested exception, original traceback " +
"(most recent call last):\n")
sys.stderr.write(value.child_traceback + ("-" * 70) + "\n")
sys.__excepthook__(tipe, value, traceback)
sys.excepthook = _custom_hook
# vim:ts=4:sw=4:et:ai:sts=4
# -*- coding: utf-8 -*-
# Copyright 2010, 2011 INRIA
# Copyright 2011 Martín Ferrari <martin.ferrari@gmail.com>
#
# This file is part of Nemu.
#
# Nemu is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License version 2, as published by the Free
# Software Foundation.
#
# Nemu is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# Nemu. If not, see <http://www.gnu.org/licenses/>.
import os, weakref
import nemu.iproute
from nemu.environ import *
__all__ = ['NodeInterface', 'P2PInterface', 'ImportedInterface',
'ImportedNodeInterface', 'Switch']
class Interface(object):
"""Just a base class for the *Interface classes: assign names and handle
destruction."""
_nextid = 0
@staticmethod
def _gen_next_id():
n = Interface._nextid
Interface._nextid += 1
return n
@staticmethod
def _gen_if_name():
n = Interface._gen_next_id()
# Max 15 chars
return "NETNSif-%.4x%.3x" % (os.getpid(), n)
def __init__(self, index):
self._idx = index
debug("%s(0x%x).__init__(), index = %d" % (self.__class__.__name__,
id(self), index))
def __del__(self):
debug("%s(0x%x).__del__()" % (self.__class__.__name__, id(self)))
self.destroy()
def destroy(self):
raise NotImplementedError
@property
def index(self):
"""Interface index as seen by the kernel."""
return self._idx
@property
def control(self):
"""Associated interface in the main name space (if it exists). Only
control interfaces can be put into a Switch, for example."""
return None
class NSInterface(Interface):
"""Add user-facing methods for interfaces that go into a netns."""
def __init__(self, node, index):
super(NSInterface, self).__init__(index)
self._slave = node._slave
# Disable auto-configuration
# you wish: need to take into account the nonetns mode; plus not
# touching some pre-existing ifaces
#node.system([SYSCTL_PATH, '-w', 'net.ipv6.conf.%s.autoconf=0' %
#self.name])
node._add_interface(self)
# some black magic to automatically get/set interface attributes
def __getattr__(self, name):
# If name starts with _, it must be a normal attr
if name[0] == '_':
return super(Interface, self).__getattribute__(name)
try:
slave = super(Interface, self).__getattribute__("_slave")
except:
# Not initialised yet
return super(Interface, self).__getattribute__(name)
iface = slave.get_if_data(self.index)
return getattr(iface, name)
def __setattr__(self, name, value):
if name[0] == '_': # forbid anything that doesn't start with a _
super(Interface, self).__setattr__(name, value)
return
iface = nemu.iproute.interface(index = self.index)
setattr(iface, name, value)
return self._slave.set_if(iface)
def add_v4_address(self, address, prefix_len, broadcast = None):
addr = nemu.iproute.ipv4address(address, prefix_len, broadcast)
self._slave.add_addr(self.index, addr)
def add_v6_address(self, address, prefix_len):
addr = nemu.iproute.ipv6address(address, prefix_len)
self._slave.add_addr(self.index, addr)
def del_v4_address(self, address, prefix_len, broadcast = None):
addr = nemu.iproute.ipv4address(address, prefix_len, broadcast)
self._slave.del_addr(self.index, addr)
def del_v6_address(self, address, prefix_len):
addr = nemu.iproute.ipv6address(address, prefix_len)
self._slave.del_addr(self.index, addr)
def get_addresses(self):
addresses = self._slave.get_addr_data(self.index)
ret = []
for a in addresses:
if hasattr(a, 'broadcast'):
ret.append(dict(
address = a.address,
prefix_len = a.prefix_len,
broadcast = a.broadcast,
family = 'inet'))
else:
ret.append(dict(
address = a.address,
prefix_len = a.prefix_len,
family = 'inet6'))
return ret
class NodeInterface(NSInterface):
"""Class to create and handle a virtual interface inside a name space, it
can be connected to a Switch object with emulation of link
characteristics."""
def __init__(self, node):
"""Create a new interface. `node' is the name space in which this
interface should be put."""
self._slave = None
if1 = nemu.iproute.interface(name = self._gen_if_name())
if2 = nemu.iproute.interface(name = self._gen_if_name())
ctl, ns = nemu.iproute.create_if_pair(if1, if2)
try:
nemu.iproute.change_netns(ns, node.pid)
except:
nemu.iproute.del_if(ctl)
# the other interface should go away automatically
raise
self._control = SlaveInterface(ctl.index)
super(NodeInterface, self).__init__(node, ns.index)
@property
def control(self):
return self._control
def destroy(self):
if not self._slave:
return
debug("NodeInterface(0x%x).destroy()" % id(self))
if self.index in self._slave.get_if_data():
self._slave.del_if(self.index)
self._slave = None
class P2PInterface(NSInterface):
"""Class to create and handle point-to-point interfaces between name
spaces, without using Switch objects. Those do not allow any kind of
traffic shaping.
As two interfaces need to be created, instead of using the class
constructor, use the P2PInterface.create_pair() static method."""
@staticmethod
def create_pair(node1, node2):
"""Create and return a pair of connected P2PInterface objects,
assigned to name spaces represented by `node1' and `node2'."""
if1 = nemu.iproute.interface(name = P2PInterface._gen_if_name())
if2 = nemu.iproute.interface(name = P2PInterface._gen_if_name())
pair = nemu.iproute.create_if_pair(if1, if2)
try:
nemu.iproute.change_netns(pair[0], node1.pid)
nemu.iproute.change_netns(pair[1], node2.pid)
except:
nemu.iproute.del_if(pair[0])
# the other interface should go away automatically
raise
o1 = P2PInterface.__new__(P2PInterface)
super(P2PInterface, o1).__init__(node1, pair[0].index)
o2 = P2PInterface.__new__(P2PInterface)
super(P2PInterface, o2).__init__(node2, pair[1].index)
return o1, o2
def __init__(self):
"Not to be called directly. Use P2PInterface.create_pair()"
raise RuntimeError(P2PInterface.__init__.__doc__)
def destroy(self):
if not self._slave:
return
debug("P2PInterface(0x%x).destroy()" % id(self))
if self.index in self._slave.get_if_data():
self._slave.del_if(self.index)
self._slave = None
class ImportedNodeInterface(NSInterface):
"""Class to handle already existing interfaces inside a name space:
real devices, tun devices, etc.
The flag 'migrate' in the constructor indicates that the interface needs
to be moved inside the name space.
On destruction, the interface will be restored to the original name space
and will try to restore the original state."""
def __init__(self, node, iface, migrate = True):
self._slave = None
self._migrate = migrate
if self._migrate:
iface = nemu.iproute.get_if(iface)
self._original_state = iface.copy()
# Change the name to avoid clashes
iface.name = self._gen_if_name()
nemu.iproute.set_if(iface)
# Migrate it
nemu.iproute.change_netns(iface, node.pid)
else:
iface = node._slave.get_if_data(iface)
self._original_state = iface.copy()
super(ImportedNodeInterface, self).__init__(node, iface.index)
def destroy(self): # override: restore as much as possible
if not self._slave:
return
debug("ImportedNodeInterface(0x%x).destroy()" % id(self))
if self.index in self._slave.get_if_data():
if self._migrate:
self._slave.change_netns(self.index, os.getpid())
else:
self._slave.set_if(self._original_state)
if self._migrate:
# else, assume it is already in the main name space
nemu.iproute.set_if(self._original_state)
self._slave = None
class TapNodeInterface(NSInterface):
"""Class to create a tap interface inside a name space, it
can be connected to a Switch object with emulation of link
characteristics."""
def __init__(self, node, use_pi = False):
"""Create a new tap interface. 'node' is the name space in which this
interface should be put."""
self._fd = None
self._slave = None
iface = nemu.iproute.interface(name = self._gen_if_name())
iface, self._fd = nemu.iproute.create_tap(iface, use_pi = use_pi)
nemu.iproute.change_netns(iface.name, node.pid)
super(TapNodeInterface, self).__init__(node, iface.index)
@property
def fd(self):
return self._fd
def destroy(self):
if not self._fd:
return
debug("TapNodeInterface(0x%x).destroy()" % id(self))
try:
os.close(self._fd)
except:
pass
class TunNodeInterface(NSInterface):
"""Class to create a tun interface inside a name space, it
can be connected to a Switch object with emulation of link
characteristics."""
def __init__(self, node, use_pi = False):
"""Create a new tap interface. 'node' is the name space in which this
interface should be put."""
self._fd = None
self._slave = None
iface = nemu.iproute.interface(name = self._gen_if_name())
iface, self._fd = nemu.iproute.create_tap(iface, use_pi = use_pi,
tun = True)
nemu.iproute.change_netns(iface.name, node.pid)
super(TunNodeInterface, self).__init__(node, iface.index)
@property
def fd(self):
return self._fd
def destroy(self):
if not self._fd:
return
debug("TunNodeInterface(0x%x).destroy()" % id(self))
try:
os.close(self._fd)
except:
pass
class ExternalInterface(Interface):
"""Add user-facing methods for interfaces that run in the main
namespace."""
@property
def control(self):
# This is *the* control interface
return self
# some black magic to automatically get/set interface attributes
def __getattr__(self, name):
iface = nemu.iproute.get_if(self.index)
return getattr(iface, name)
def __setattr__(self, name, value):
if name[0] == '_': # forbid anything that doesn't start with a _
super(ExternalInterface, self).__setattr__(name, value)
return
iface = nemu.iproute.interface(index = self.index)
setattr(iface, name, value)
return nemu.iproute.set_if(iface)
def add_v4_address(self, address, prefix_len, broadcast = None):
addr = nemu.iproute.ipv4address(address, prefix_len, broadcast)
nemu.iproute.add_addr(self.index, addr)
def add_v6_address(self, address, prefix_len):
addr = nemu.iproute.ipv6address(address, prefix_len)
nemu.iproute.add_addr(self.index, addr)
def del_v4_address(self, address, prefix_len, broadcast = None):
addr = nemu.iproute.ipv4address(address, prefix_len, broadcast)
nemu.iproute.del_addr(self.index, addr)
def del_v6_address(self, address, prefix_len):
addr = nemu.iproute.ipv6address(address, prefix_len)
nemu.iproute.del_addr(self.index, addr)
def get_addresses(self):
addresses = nemu.iproute.get_addr_data(self.index)
ret = []
for a in addresses:
if hasattr(a, 'broadcast'):
ret.append(dict(
address = a.address,
prefix_len = a.prefix_len,
broadcast = a.broadcast,
family = 'inet'))
else:
ret.append(dict(
address = a.address,
prefix_len = a.prefix_len,
family = 'inet6'))
return ret
class SlaveInterface(ExternalInterface):
"""Class to handle the main-name-space-facing half of NodeInterface.
Does nothing, just avoids any destroy code."""
def destroy(self):
pass
class ImportedInterface(ExternalInterface):
"""Class to handle already existing interfaces. Analogous to
ImportedNodeInterface, this class only differs in that the interface is
not migrated inside the name space. This kind of interfaces can only be
connected to Switch objects and not assigned to a name space. On
destruction, the code will try to restore the interface to the state it
was in before being imported into nemu."""
def __init__(self, iface):
self._original_state = None
iface = nemu.iproute.get_if(iface)
self._original_state = iface.copy()
super(ImportedInterface, self).__init__(iface.index)
# FIXME: register somewhere for destruction!
def destroy(self): # override: restore as much as possible
if self._original_state:
debug("ImportedInterface(0x%x).destroy()" % id(self))
nemu.iproute.set_if(self._original_state)
self._original_state = None
# Switch is just another interface type
class Switch(ExternalInterface):
@staticmethod
def _gen_br_name():
n = Switch._gen_next_id()
# Max 15 chars
return "NETNSbr-%.4x%.3x" % (os.getpid(), n)
def __init__(self, **args):
"""Creates a new Switch object, which models a linux bridge device.
Parameters are passed to the set_parameters() method after
creation."""
# attributes init
self._idx = None
self._parameters = {}
self._ports = weakref.WeakValueDictionary()
iface = nemu.iproute.create_bridge(self._gen_br_name())
super(Switch, self).__init__(iface.index)
# FIXME: is this correct/desirable/etc?
self.stp = False
self.forward_delay = 0
# FIXME: register somewhere
if args:
self.set_parameters(**args)
def __getattr__(self, name):
iface = nemu.iproute.get_bridge(self.index)
return getattr(iface, name)
def __setattr__(self, name, value):
if name[0] == '_': # forbid anything that doesn't start with a _
super(Switch, self).__setattr__(name, value)
return
# Set ports
if name in ('up', 'mtu'):
for i in self._ports.values():
if self._check_port(i.index):
setattr(i, name, value)
# Set bridge
iface = nemu.iproute.bridge(index = self.index)
setattr(iface, name, value)
nemu.iproute.set_bridge(iface)
def destroy(self):
if not self.index:
return
debug("Switch(0x%x).destroy()" % id(self))
# Verify they are still there
for p in self._ports.keys():
self._check_port(p)
self.up = False
for p in self._ports.values():
self.disconnect(p)
self._ports.clear()
nemu.iproute.del_bridge(self.index)
self._idx = None
def connect(self, iface):
assert iface.control.index not in self._ports
try:
self._apply_parameters(self._parameters, iface.control)
nemu.iproute.add_bridge_port(self.index, iface.control.index)
except:
self._apply_parameters({}, iface.control)
raise
iface.control.up = self.up
iface.control.mtu = self.mtu
self._ports[iface.control.index] = iface.control
def _check_port(self, port_index):
ports = nemu.iproute.get_bridge_data()[2]
if self.index in ports and port_index in ports[self.index]:
return True
# else
warning("Switch(0x%x): Port (index = %d) went away." % (id(self),
port_index))
del self._ports[port_index]
return False
def disconnect(self, iface):
assert iface.control.index in self._ports
if not self._check_port(iface.control.index):
return
nemu.iproute.del_bridge_port(self.index, iface.control.index)
self._apply_parameters({}, iface.control)
del self._ports[iface.control.index]
def set_parameters(self, bandwidth = None,
delay = None, delay_jitter = None,
delay_correlation = None, delay_distribution = None,
loss = None, loss_correlation = None,
dup = None, dup_correlation = None,
corrupt = None, corrupt_correlation = None):
"""Set the parameters that control the link characteristics. For the
description of each, refer to netem documentation:
http://www.linuxfoundation.org/collaborate/workgroups/networking/netem
Arguments:
- `bandwidth' should be specified in bits per second.
- `delay' and `delay_jitter' are specified in seconds.
- `delay_distribution' is the name of a distribution description file;
`iproute' comes by default with `normal', `pareto', and
`paretonormal'.
- `delay_correlation', `loss', `loss_correlation', `dup',
`dup_correlation', `corrupt', and `corrupt_correlation' take a
percentage value in the form of a number between 0 and 1. (50% is
passed as 0.5)."""
parameters = dict(bandwidth = bandwidth,
delay = delay, delay_jitter = delay_jitter,
delay_correlation = delay_correlation,
delay_distribution = delay_distribution,
loss = loss, loss_correlation = loss_correlation,
dup = dup, dup_correlation = dup_correlation,
corrupt = corrupt, corrupt_correlation = corrupt_correlation)
try:
self._apply_parameters(parameters)
except:
self._apply_parameters(self._parameters)
raise
self._parameters = parameters
def _apply_parameters(self, parameters, port = None):
for i in [port] if port else self._ports.values():
nemu.iproute.set_tc(i.index, **parameters)
# vim:ts=4:sw=4:et:ai:sts=4
# -*- coding: utf-8 -*-
# Copyright 2010, 2011 INRIA
# Copyright 2011 Martín Ferrari <martin.ferrari@gmail.com>
#
# This file is part of Nemu.
#
# Nemu is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License version 2, as published by the Free
# Software Foundation.
#
# Nemu is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# Nemu. If not, see <http://www.gnu.org/licenses/>.
import copy, fcntl, os, re, socket, struct, subprocess, sys
from nemu.environ import *
# helpers
def _any_to_bool(any):
if isinstance(any, bool):
return any
if isinstance(any, int):
return any != 0
if isinstance(any, str):
if any.isdigit():
return int(any) != 0
if any.lower() == "true":
return True
if any.lower() == "false":
return False
return any != ""
return bool(any)
def _positive(val):
v = int(val)
if v <= 0:
raise ValueError("Invalid value: %d" % v)
return v
def _non_empty_str(val):
if val == "":
return None
else:
return str(val)
def _fix_lladdr(addr):
foo = addr.lower()
if ":" in addr:
# Verify sanity and split
m = re.search("^" + ":".join(["([0-9a-f]{1,2})"] * 6) + "$", foo)
if m is None:
raise ValueError("Invalid address: `%s'." % addr)
# Fill missing zeros and glue again
return ":".join(("0" * (2 - len(x)) + x for x in m.groups()))
# Fill missing zeros
foo = "0" * (12 - len(foo)) + foo
# Verify sanity and split
m = re.search("^" + "([0-9a-f]{2})" * 6 + "$", foo)
if m is None:
raise ValueError("Invalid address: `%s'." % addr)
# Glue
return ":".join(m.groups())
def _make_getter(attr, conv = lambda x: x):
def getter(self):
return conv(getattr(self, attr))
return getter
def _make_setter(attr, conv = lambda x: x):
def setter(self, value):
if value == None:
setattr(self, attr, None)
else:
setattr(self, attr, conv(value))
return setter
# classes for internal use
class interface(object):
"""Class for internal use. It is mostly a data container used to easily
pass information around; with some convenience methods."""
# information for other parts of the code
changeable_attributes = ["name", "mtu", "lladdr", "broadcast", "up",
"multicast", "arp"]
# Index should be read-only
index = property(_make_getter("_index"))
up = property(_make_getter("_up"), _make_setter("_up", _any_to_bool))
mtu = property(_make_getter("_mtu"), _make_setter("_mtu", _positive))
lladdr = property(_make_getter("_lladdr"),
_make_setter("_lladdr", _fix_lladdr))
arp = property(_make_getter("_arp"), _make_setter("_arp", _any_to_bool))
multicast = property(_make_getter("_mc"), _make_setter("_mc", _any_to_bool))
def __init__(self, index = None, name = None, up = None, mtu = None,
lladdr = None, broadcast = None, multicast = None, arp = None):
self._index = _positive(index) if index is not None else None
self.name = name
self.up = up
self.mtu = mtu
self.lladdr = lladdr
self.broadcast = broadcast
self.multicast = multicast
self.arp = arp
def __repr__(self):
s = "%s.%s(index = %s, name = %s, up = %s, mtu = %s, lladdr = %s, "
s += "broadcast = %s, multicast = %s, arp = %s)"
return s % (self.__module__, self.__class__.__name__,
self.index.__repr__(), self.name.__repr__(),
self.up.__repr__(), self.mtu.__repr__(),
self.lladdr.__repr__(), self.broadcast.__repr__(),
self.multicast.__repr__(), self.arp.__repr__())
def __sub__(self, o):
"""Compare attributes and return a new object with just the attributes
that differ set (with the value they have in the first operand). The
index remains equal to the first operand."""
name = None if self.name == o.name else self.name
up = None if self.up == o.up else self.up
mtu = None if self.mtu == o.mtu else self.mtu
lladdr = None if self.lladdr == o.lladdr else self.lladdr
broadcast = None if self.broadcast == o.broadcast else self.broadcast
multicast = None if self.multicast == o.multicast else self.multicast
arp = None if self.arp == o.arp else self.arp
return self.__class__(self.index, name, up, mtu, lladdr, broadcast,
multicast, arp)
def copy(self):
return copy.copy(self)
class bridge(interface):
changeable_attributes = interface.changeable_attributes + ["stp",
"forward_delay", "hello_time", "ageing_time", "max_age"]
# Index should be read-only
stp = property(_make_getter("_stp"), _make_setter("_stp", _any_to_bool))
forward_delay = property(_make_getter("_forward_delay"),
_make_setter("_forward_delay", float))
hello_time = property(_make_getter("_hello_time"),
_make_setter("_hello_time", float))
ageing_time = property(_make_getter("_ageing_time"),
_make_setter("_ageing_time", float))
max_age = property(_make_getter("_max_age"),
_make_setter("_max_age", float))
@classmethod
def upgrade(cls, iface, *kargs, **kwargs):
"""Upgrade a interface to a bridge."""
return cls(iface.index, iface.name, iface.up, iface.mtu, iface.lladdr,
iface.broadcast, iface.multicast, iface.arp, *kargs, **kwargs)
def __init__(self, index = None, name = None, up = None, mtu = None,
lladdr = None, broadcast = None, multicast = None, arp = None,
stp = None, forward_delay = None, hello_time = None,
ageing_time = None, max_age = None):
super(bridge, self).__init__(index, name, up, mtu, lladdr, broadcast,
multicast, arp)
self.stp = stp
self.forward_delay = forward_delay
self.hello_time = hello_time
self.ageing_time = ageing_time
self.max_age = max_age
def __repr__(self):
s = "%s.%s(index = %s, name = %s, up = %s, mtu = %s, lladdr = %s, "
s += "broadcast = %s, multicast = %s, arp = %s, stp = %s, "
s += "forward_delay = %s, hello_time = %s, ageing_time = %s, "
s += "max_age = %s)"
return s % (self.__module__, self.__class__.__name__,
self.index.__repr__(), self.name.__repr__(),
self.up.__repr__(), self.mtu.__repr__(),
self.lladdr.__repr__(), self.broadcast.__repr__(),
self.multicast.__repr__(), self.arp.__repr__(),
self.stp.__repr__(), self.forward_delay.__repr__(),
self.hello_time.__repr__(), self.ageing_time.__repr__(),
self.max_age.__repr__())
def __sub__(self, o):
r = super(bridge, self).__sub__(o)
if type(o) == interface:
return r
r.stp = None if self.stp == o.stp else self.stp
r.hello_time = None if self.hello_time == o.hello_time else \
self.hello_time
r.forward_delay = None if self.forward_delay == o.forward_delay else \
self.forward_delay
r.ageing_time = None if self.ageing_time == o.ageing_time else \
self.ageing_time
r.max_age = None if self.max_age == o.max_age else self.max_age
return r
class address(object):
"""Class for internal use. It is mostly a data container used to easily
pass information around; with some convenience methods. __eq__ and
__hash__ are defined just to be able to easily find duplicated
addresses."""
# broadcast is not taken into account for differentiating addresses
def __eq__(self, o):
if not isinstance(o, address):
return False
return (self.family == o.family and self.address == o.address and
self.prefix_len == o.prefix_len)
def __hash__(self):
h = (self.address.__hash__() ^ self.prefix_len.__hash__() ^
self.family.__hash__())
return h
class ipv4address(address):
def __init__(self, address, prefix_len, broadcast):
self.address = address
self.prefix_len = int(prefix_len)
self.broadcast = broadcast
self.family = socket.AF_INET
def __repr__(self):
s = "%s.%s(address = %s, prefix_len = %d, broadcast = %s)"
return s % (self.__module__, self.__class__.__name__,
self.address.__repr__(), self.prefix_len,
self.broadcast.__repr__())
class ipv6address(address):
def __init__(self, address, prefix_len):
self.address = address
self.prefix_len = int(prefix_len)
self.family = socket.AF_INET6
def __repr__(self):
s = "%s.%s(address = %s, prefix_len = %d)"
return s % (self.__module__, self.__class__.__name__,
self.address.__repr__(), self.prefix_len)
class route(object):
tipes = ["unicast", "local", "broadcast", "multicast", "throw",
"unreachable", "prohibit", "blackhole", "nat"]
tipe = property(_make_getter("_tipe", tipes.__getitem__),
_make_setter("_tipe", tipes.index))
prefix = property(_make_getter("_prefix"),
_make_setter("_prefix", _non_empty_str))
prefix_len = property(_make_getter("_plen"),
lambda s, v: setattr(s, "_plen", int(v or 0)))
nexthop = property(_make_getter("_nexthop"),
_make_setter("_nexthop", _non_empty_str))
interface = property(_make_getter("_interface"),
_make_setter("_interface", _positive))
metric = property(_make_getter("_metric"),
lambda s, v: setattr(s, "_metric", int(v or 0)))
def __init__(self, tipe = "unicast", prefix = None, prefix_len = 0,
nexthop = None, interface = None, metric = 0):
self.tipe = tipe
self.prefix = prefix
self.prefix_len = prefix_len
self.nexthop = nexthop
self.interface = interface
self.metric = metric
assert nexthop or interface
def __repr__(self):
s = "%s.%s(tipe = %s, prefix = %s, prefix_len = %s, nexthop = %s, "
s += "interface = %s, metric = %s)"
return s % (self.__module__, self.__class__.__name__,
self.tipe.__repr__(), self.prefix.__repr__(),
self.prefix_len.__repr__(), self.nexthop.__repr__(),
self.interface.__repr__(), self.metric.__repr__())
def __eq__(self, o):
if not isinstance(o, route):
return False
return (self.tipe == o.tipe and self.prefix == o.prefix and
self.prefix_len == o.prefix_len and self.nexthop == o.nexthop
and self.interface == o.interface and self.metric == o.metric)
# helpers
def _get_if_name(iface):
if isinstance(iface, interface):
if iface.name != None:
return iface.name
if isinstance(iface, str):
return iface
return get_if(iface).name
# XXX: ideally this should be replaced by netlink communication
# Interface handling
# FIXME: try to lower the amount of calls to retrieve data!!
def get_if_data():
"""Gets current interface information. Returns a tuple (byidx, bynam) in
which each element is a dictionary with the same data, but using different
keys: interface indexes and interface names.
In each dictionary, values are interface objects.
"""
ipdata = backticks([IP_PATH, "-o", "link", "list"])
byidx = {}
bynam = {}
for line in ipdata.split("\n"):
if line == "":
continue
match = re.search(r'^(\d+):\s+(.*)', line)
idx = int(match.group(1))
match = re.search(r'^(\d+): (\S+): <(\S+)> mtu (\d+) qdisc \S+' +
r'.*link/\S+(?: ([0-9a-f:]+) brd ([0-9a-f:]+))?', line)
flags = match.group(3).split(",")
i = interface(
index = match.group(1),
name = match.group(2),
up = "UP" in flags,
mtu = match.group(4),
lladdr = match.group(5),
arp = not ("NOARP" in flags),
broadcast = match.group(6),
multicast = "MULTICAST" in flags)
byidx[idx] = bynam[i.name] = i
return byidx, bynam
def get_if(iface):
ifdata = get_if_data()
if isinstance(iface, interface):
if iface.index != None:
return ifdata[0][iface.index]
else:
return ifdata[1][iface.name]
if isinstance(iface, int):
return ifdata[0][iface]
return ifdata[1][iface]
def create_if_pair(if1, if2):
assert if1.name and if2.name
cmd = [[], []]
iface = [if1, if2]
for i in (0, 1):
cmd[i] = ["name", iface[i].name]
if iface[i].lladdr:
cmd[i] += ["address", iface[i].lladdr]
if iface[i].broadcast:
cmd[i] += ["broadcast", iface[i].broadcast]
if iface[i].mtu:
cmd[i] += ["mtu", str(iface[i].mtu)]
cmd = [IP_PATH, "link", "add"] + cmd[0] + ["type", "veth", "peer"] + cmd[1]
execute(cmd)
try:
set_if(if1)
set_if(if2)
except:
(t, v, bt) = sys.exc_info()
try:
del_if(if1)
del_if(if2)
except:
pass
raise t, v, bt
interfaces = get_if_data()[1]
return interfaces[if1.name], interfaces[if2.name]
def del_if(iface):
ifname = _get_if_name(iface)
execute([IP_PATH, "link", "del", ifname])
def set_if(iface, recover = True):
def do_cmds(cmds, orig_iface):
for c in cmds:
try:
execute(c)
except:
if recover:
set_if(orig_iface, recover = False) # rollback
raise
orig_iface = get_if(iface)
diff = iface - orig_iface # Only set what's needed
# Name goes first
if diff.name:
_ils = [IP_PATH, "link", "set", "dev"]
cmds = [_ils + [orig_iface.name, "name", diff.name]]
if orig_iface.up:
# iface needs to be down
cmds = [_ils + [orig_iface.name, "down"], cmds[0],
_ils + [diff.name, "up"]]
do_cmds(cmds, orig_iface)
# I need to use the new name after a name change, duh!
_ils = [IP_PATH, "link", "set", "dev", diff.name or orig_iface.name]
cmds = []
if diff.lladdr:
if orig_iface.up:
# iface needs to be down
cmds.append(_ils + ["down"])
cmds.append(_ils + ["address", diff.lladdr])
if orig_iface.up and diff.up == None:
# restore if it was up and it's not going to be set later
cmds.append(_ils + ["up"])
if diff.mtu:
cmds.append(_ils + ["mtu", str(diff.mtu)])
if diff.broadcast:
cmds.append(_ils + ["broadcast", diff.broadcast])
if diff.multicast != None:
cmds.append(_ils + ["multicast", "on" if diff.multicast else "off"])
if diff.arp != None:
cmds.append(_ils + ["arp", "on" if diff.arp else "off"])
if diff.up != None:
cmds.append(_ils + ["up" if diff.up else "down"])
do_cmds(cmds, orig_iface)
def change_netns(iface, netns):
ifname = _get_if_name(iface)
execute([IP_PATH, "link", "set", "dev", ifname, "netns", str(netns)])
# Address handling
def get_addr_data():
ipdata = backticks([IP_PATH, "-o", "addr", "list"])
byidx = {}
bynam = {}
for line in ipdata.split("\n"):
if line == "":
continue
match = re.search(r'^(\d+):\s+(\S+?)(:?)\s+(.*)', line)
if not match:
raise RuntimeError("Invalid `ip' command output")
idx = int(match.group(1))
name = match.group(2)
if name not in bynam:
bynam[name] = byidx[idx] = []
if match.group(3): # BBB: old iproute also shows link info
continue
bynam[name].append(_parse_ip_addr(match.group(4)))
return byidx, bynam
def _parse_ip_addr(line):
match = re.search(r'^inet ([0-9.]+)/(\d+)(?: brd ([0-9.]+))?', line)
if match != None:
return ipv4address(
address = match.group(1),
prefix_len = match.group(2),
broadcast = match.group(3))
match = re.search(r'^inet6 ([0-9a-f:]+)/(\d+)', line)
if match != None:
return ipv6address(
address = match.group(1),
prefix_len = match.group(2))
raise RuntimeError("Problems parsing ip command output")
def add_addr(iface, address):
ifname = _get_if_name(iface)
addresses = get_addr_data()[1][ifname]
assert address not in addresses
cmd = [IP_PATH, "addr", "add", "dev", ifname, "local",
"%s/%d" % (address.address, int(address.prefix_len))]
if hasattr(address, "broadcast"):
cmd += ["broadcast", address.broadcast if address.broadcast else "+"]
execute(cmd)
def del_addr(iface, address):
ifname = _get_if_name(iface)
addresses = get_addr_data()[1][ifname]
assert address in addresses
cmd = [IP_PATH, "addr", "del", "dev", ifname, "local",
"%s/%d" % (address.address, int(address.prefix_len))]
execute(cmd)
def set_addr(iface, addresses, recover = True):
ifname = _get_if_name(iface)
addresses = get_addr_data()[1][ifname]
to_remove = set(orig_addresses) - set(addresses)
to_add = set(addresses) - set(orig_addresses)
for a in to_remove:
try:
del_addr(ifname, a)
except:
if recover:
set_addr(orig_addresses, recover = False) # rollback
raise
for a in to_add:
try:
add_addr(ifname, a)
except:
if recover:
set_addr(orig_addresses, recover = False) # rollback
raise
# Bridge handling
def _sysfs_read_br(brname):
def readval(fname):
f = file(fname)
return f.readline().strip()
p = "/sys/class/net/%s/bridge/" % brname
p2 = "/sys/class/net/%s/brif/" % brname
try:
os.stat(p)
except:
return None
return dict(
stp = readval(p + "stp_state"),
forward_delay = float(readval(p + "forward_delay")) / 100,
hello_time = float(readval(p + "hello_time")) / 100,
ageing_time = float(readval(p + "ageing_time")) / 100,
max_age = float(readval(p + "max_age")) / 100,
ports = os.listdir(p2))
def get_bridge_data():
# brctl stinks too much; it is better to directly use sysfs, it is
# probably stable by now
byidx = {}
bynam = {}
ports = {}
ifdata = get_if_data()
for iface in ifdata[0].values():
brdata = _sysfs_read_br(iface.name)
if brdata == None:
continue
ports[iface.index] = [ifdata[1][x].index for x in brdata["ports"]]
del brdata["ports"]
bynam[iface.name] = byidx[iface.index] = \
bridge.upgrade(iface, **brdata)
return byidx, bynam, ports
def get_bridge(br):
iface = get_if(br)
brdata = _sysfs_read_br(iface.name)
#ports = [ifdata[1][x].index for x in brdata["ports"]]
del brdata["ports"]
return bridge.upgrade(iface, **brdata)
def create_bridge(br):
if isinstance(br, str):
br = interface(name = br)
assert br.name
execute([BRCTL_PATH, "addbr", br.name])
try:
set_if(br)
except:
(t, v, bt) = sys.exc_info()
try:
del_bridge(br)
except:
pass
raise t, v, bt
return get_if_data()[1][br.name]
def del_bridge(br):
brname = _get_if_name(br)
execute([BRCTL_PATH, "delbr", brname])
def set_bridge(br, recover = True):
def saveval(fname, val):
f = file(fname, "w")
f.write(str(val))
f.close()
def do_cmds(basename, cmds, orig_br):
for n, v in cmds:
try:
saveval(basename + n, v)
except:
if recover:
set_bridge(orig_br, recover = False) # rollback
set_if(orig_br, recover = False) # rollback
raise
orig_br = get_bridge(br)
diff = br - orig_br # Only set what's needed
cmds = []
if diff.stp != None:
cmds.append(("stp_state", int(diff.stp)))
if diff.forward_delay != None:
cmds.append(("forward_delay", int(diff.forward_delay)))
if diff.hello_time != None:
cmds.append(("hello_time", int(diff.hello_time)))
if diff.ageing_time != None:
cmds.append(("ageing_time", int(diff.ageing_time)))
if diff.max_age != None:
cmds.append(("max_age", int(diff.max_age)))
set_if(diff)
name = diff.name if diff.name != None else orig_br.name
do_cmds("/sys/class/net/%s/bridge/" % name, cmds, orig_br)
def add_bridge_port(br, iface):
ifname = _get_if_name(iface)
brname = _get_if_name(br)
execute([BRCTL_PATH, "addif", brname, ifname])
def del_bridge_port(br, iface):
ifname = _get_if_name(iface)
brname = _get_if_name(br)
execute([BRCTL_PATH, "delif", brname, ifname])
# Routing
def get_all_route_data():
ipdata = backticks([IP_PATH, "-o", "route", "list"]) # "table", "all"
ipdata += backticks([IP_PATH, "-o", "-f", "inet6", "route", "list"])
ifdata = get_if_data()[1]
ret = []
for line in ipdata.split("\n"):
if line == "":
continue
match = re.match(r'(?:(unicast|local|broadcast|multicast|throw|' +
r'unreachable|prohibit|blackhole|nat) )?' +
r'(\S+)(?: via (\S+))? dev (\S+).*(?: metric (\d+))?', line)
if not match:
raise RuntimeError("Invalid output from `ip route': `%s'" % line)
tipe = match.group(1) or "unicast"
prefix = match.group(2)
nexthop = match.group(3)
interface = ifdata[match.group(4)]
metric = match.group(5)
if prefix == "default" or re.search(r'/0$', prefix):
prefix = None
prefix_len = 0
else:
match = re.match(r'([0-9a-f:.]+)(?:/(\d+))?$', prefix)
prefix = match.group(1)
prefix_len = int(match.group(2) or 32)
ret.append(route(tipe, prefix, prefix_len, nexthop, interface.index,
metric))
return ret
def get_route_data():
# filter out non-unicast routes
return [x for x in get_all_route_data() if x.tipe == "unicast"]
def add_route(route):
# Cannot really test this
#if route in get_all_route_data():
# raise ValueError("Route already exists")
_add_del_route("add", route)
def del_route(route):
# Cannot really test this
#if route not in get_all_route_data():
# raise ValueError("Route does not exist")
_add_del_route("del", route)
def _add_del_route(action, route):
cmd = [IP_PATH, "route", action]
if route.tipe != "unicast":
cmd += [route.tipe]
if route.prefix:
cmd += ["%s/%d" % (route.prefix, route.prefix_len)]
else:
cmd += ["default"]
if route.nexthop:
cmd += ["via", route.nexthop]
if route.interface:
cmd += ["dev", _get_if_name(route.interface)]
execute(cmd)
# TC stuff
def get_tc_tree():
tcdata = backticks([TC_PATH, "qdisc", "show"])
data = {}
for line in tcdata.split("\n"):
if line == "":
continue
match = re.match(r'qdisc (\S+) ([0-9a-f]+):[0-9a-f]* dev (\S+) ' +
r'(?:parent ([0-9a-f]+):[0-9a-f]*|root)\s*(.*)', line)
if not match:
raise RuntimeError("Invalid output from `tc qdisc': `%s'" % line)
qdisc = match.group(1)
handle = match.group(2)
iface = match.group(3)
parent = match.group(4) # or None
extra = match.group(5)
if iface not in data:
data[iface] = {}
if parent not in data[iface]:
data[iface][parent] = []
data[iface][parent] += [[handle, qdisc, parent, extra]]
tree = {}
for iface in data:
def gen_tree(data, data_node):
children = []
node = {"handle": data_node[0],
"qdisc": data_node[1],
"extra": data_node[3],
"children": []}
if data_node[0] in data:
for h in data[data_node[0]]:
node["children"].append(gen_tree(data, h))
return node
tree[iface] = gen_tree(data[iface], data[iface][None][0])
return tree
_multipliers = {"M": 1000000, "K": 1000}
_dividers = {"m": 1000, "u": 1000000}
def _parse_netem_delay(line):
ret = {}
match = re.search(r'delay ([\d.]+)([mu]?)s(?: +([\d.]+)([mu]?)s)?' +
r'(?: *([\d.]+)%)?(?: *distribution (\S+))?', line)
if not match:
return ret
delay = float(match.group(1))
if match.group(2):
delay /= _dividers[match.group(2)]
ret["delay"] = delay
if match.group(3):
delay_jitter = float(match.group(3))
if match.group(4):
delay_jitter /= _dividers[match.group(4)]
ret["delay_jitter"] = delay_jitter
if match.group(5):
ret["delay_correlation"] = float(match.group(5)) / 100
if match.group(6):
ret["delay_distribution"] = match.group(6)
return ret
def _parse_netem_loss(line):
ret = {}
match = re.search(r'loss ([\d.]+)%(?: *([\d.]+)%)?', line)
if not match:
return ret
ret["loss"] = float(match.group(1)) / 100
if match.group(2):
ret["loss_correlation"] = float(match.group(2)) / 100
return ret
def _parse_netem_dup(line):
ret = {}
match = re.search(r'duplicate ([\d.]+)%(?: *([\d.]+)%)?', line)
if not match:
return ret
ret["dup"] = float(match.group(1)) / 100
if match.group(2):
ret["dup_correlation"] = float(match.group(2)) / 100
return ret
def _parse_netem_corrupt(line):
ret = {}
match = re.search(r'corrupt ([\d.]+)%(?: *([\d.]+)%)?', line)
if not match:
return ret
ret["corrupt"] = float(match.group(1)) / 100
if match.group(2):
ret["corrupt_correlation"] = float(match.group(2)) / 100
return ret
def get_tc_data():
tree = get_tc_tree()
ifdata = get_if_data()
ret = {}
for i in ifdata[0]:
ret[i] = {"qdiscs": {}}
if ifdata[0][i].name not in tree:
continue
node = tree[ifdata[0][i].name]
if not node["children"]:
if node["qdisc"] == "mq" or node["qdisc"] == "pfifo_fast" \
or node["qdisc"][1:] == "fifo":
continue
if node["qdisc"] == "netem":
tbf = None
netem = node["extra"], node["handle"]
elif node["qdisc"] == "tbf":
tbf = node["extra"], node["handle"]
netem = None
else:
ret[i] = "foreign"
continue
else:
if node["qdisc"] != "tbf" or len(node["children"]) != 1 or \
node["children"][0]["qdisc"] != "netem" or \
node["children"][0]["children"]:
ret[i] = "foreign"
continue
tbf = node["extra"], node["handle"]
netem = node["children"][0]["extra"], \
node["children"][0]["handle"]
if tbf:
ret[i]["qdiscs"]["tbf"] = tbf[1]
match = re.search(r'rate (\d+)([MK]?)bit', tbf[0])
if not match:
ret[i] = "foreign"
continue
bandwidth = int(match.group(1))
if match.group(2):
bandwidth *= _multipliers[match.group(2)]
ret[i]["bandwidth"] = bandwidth
if netem:
ret[i]["qdiscs"]["netem"] = netem[1]
ret[i].update(_parse_netem_delay(netem[0]))
ret[i].update(_parse_netem_loss(netem[0]))
ret[i].update(_parse_netem_dup(netem[0]))
ret[i].update(_parse_netem_corrupt(netem[0]))
return ret, ifdata[0], ifdata[1]
def clear_tc(iface):
iface = get_if(iface)
tcdata = get_tc_data()[0]
if tcdata[iface.index] == None:
return
# Any other case, we clean
execute([TC_PATH, "qdisc", "del", "dev", iface.name, "root"])
def set_tc(iface, bandwidth = None, delay = None, delay_jitter = None,
delay_correlation = None, delay_distribution = None,
loss = None, loss_correlation = None,
dup = None, dup_correlation = None,
corrupt = None, corrupt_correlation = None):
use_netem = bool(delay or delay_jitter or delay_correlation or
delay_distribution or loss or loss_correlation or dup or
dup_correlation or corrupt or corrupt_correlation)
iface = get_if(iface)
tcdata, ifdata = get_tc_data()[0:2]
commands = []
if tcdata[iface.index] == 'foreign':
# Avoid the overhead of calling tc+ip again
commands.append([TC_PATH, "qdisc", "del", "dev", iface.name, "root"])
tcdata[iface.index] = {'qdiscs': []}
has_netem = 'netem' in tcdata[iface.index]['qdiscs']
has_tbf = 'tbf' in tcdata[iface.index]['qdiscs']
if not bandwidth and not use_netem:
if has_netem or has_tbf:
clear_tc(iface)
return
if has_netem == use_netem and has_tbf == bool(bandwidth):
cmd = "change"
else:
# Too much work to do better :)
if has_netem or has_tbf:
commands.append([TC_PATH, "qdisc", "del", "dev", iface.name,
"root"])
cmd = "add"
if bandwidth:
rate = "%dbit" % int(bandwidth)
mtu = ifdata[iface.index].mtu
burst = max(mtu, int(bandwidth) / HZ)
limit = burst * 2 # FIXME?
handle = "1:"
if cmd == "change":
handle = "%d:" % int(tcdata[iface.index]["qdiscs"]["tbf"])
command = [TC_PATH, "qdisc", cmd, "dev", iface.name, "root", "handle",
handle, "tbf", "rate", rate, "limit", str(limit), "burst",
str(burst)]
commands.append(command)
if use_netem:
handle = "2:"
if cmd == "change":
handle = "%d:" % int(tcdata[iface.index]["qdiscs"]["netem"])
command = [TC_PATH, "qdisc", cmd, "dev", iface.name, "handle", handle]
if bandwidth:
parent = "1:"
if cmd == "change":
parent = "%d:" % int(tcdata[iface.index]["qdiscs"]["tbf"])
command += ["parent", parent]
else:
command += ["root"]
command += ["netem"]
if delay:
command += ["delay", "%fs" % delay]
if delay_jitter:
command += ["%fs" % delay_jitter]
if delay_correlation:
if not delay_jitter:
raise ValueError("delay_correlation requires delay_jitter")
command += ["%f%%" % (delay_correlation * 100)]
if delay_distribution:
if not delay_jitter:
raise ValueError("delay_distribution requires delay_jitter")
command += ["distribution", delay_distribution]
if loss:
command += ["loss", "%f%%" % (loss * 100)]
if loss_correlation:
command += ["%f%%" % (loss_correlation * 100)]
if dup:
command += ["duplicate", "%f%%" % (dup * 100)]
if dup_correlation:
command += ["%f%%" % (dup_correlation * 100)]
if corrupt:
command += ["corrupt", "%f%%" % (corrupt * 100)]
if corrupt_correlation:
command += ["%f%%" % (corrupt_correlation * 100)]
commands.append(command)
for c in commands:
execute(c)
def create_tap(iface, use_pi = False, tun = False):
"""Creates a tap/tun device and returns the associated file descriptor"""
if isinstance(iface, str):
iface = interface(name = iface)
assert iface.name
IFF_TUN = 0x0001
IFF_TAP = 0x0002
IFF_NO_PI = 0x1000
TUNSETIFF = 0x400454ca
if tun:
mode = IFF_TUN
else:
mode = IFF_TAP
if not use_pi:
mode |= IFF_NO_PI
fd = os.open("/dev/net/tun", os.O_RDWR)
err = fcntl.ioctl(fd, TUNSETIFF, struct.pack("16sH", iface.name, mode))
if err < 0:
os.close(fd)
raise RuntimeError("Could not configure device %s" % iface.name)
try:
set_if(iface)
except:
os.close(fd)
raise
interfaces = get_if_data()[1]
return interfaces[iface.name], fd
# vim:ts=4:sw=4:et:ai:sts=4
# -*- coding: utf-8 -*-
# Copyright 2010, 2011 INRIA
# Copyright 2011 Martín Ferrari <martin.ferrari@gmail.com>
#
# This file is part of Nemu.
#
# Nemu is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License version 2, as published by the Free
# Software Foundation.
#
# Nemu is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# Nemu. If not, see <http://www.gnu.org/licenses/>.
import os, socket, sys, traceback, unshare, weakref
from nemu.environ import *
import nemu.interface, nemu.protocol, nemu.subprocess_
__all__ = ['Node', 'get_nodes', 'import_if']
class Node(object):
_nodes = weakref.WeakValueDictionary()
_nextnode = 0
@staticmethod
def get_nodes():
s = sorted(Node._nodes.items(), key = lambda x: x[0])
return [x[1] for x in s]
def __init__(self, nonetns = False, forward_X11 = False):
"""Create a new node in the emulation. Implemented as a separate
process in a new network name space. Requires root privileges to run.
If nonetns is true, the network name space is not created and can be
run as a normal user, for testing."""
# Initialize attributes, in case something fails during __init__
self._pid = self._slave = None
self._processes = weakref.WeakValueDictionary()
self._interfaces = weakref.WeakValueDictionary()
self._auto_interfaces = [] # just to keep them alive!
fd, pid = _start_child(nonetns)
self._pid = pid
debug("Node(0x%x).__init__(), pid = %s" % (id(self), pid))
self._slave = nemu.protocol.Client(fd, fd)
if forward_X11:
self._slave.enable_x11_forwarding()
Node._nodes[Node._nextnode] = self
Node._nextnode += 1
# Bring loopback up
if not nonetns:
self.get_interface("lo").up = True
def __del__(self):
debug("Node(0x%x).__del__()" % id(self))
self.destroy()
def destroy(self):
if not self._pid:
return
debug("Node(0x%x).destroy()" % id(self))
for p in self._processes.values():
p.destroy()
self._processes.clear()
# Use get_interfaces to force a rescan
for i in self.get_interfaces():
i.destroy()
self._interfaces.clear()
if self._slave:
self._slave.shutdown()
exitcode = eintr_wrapper(os.waitpid, self._pid, 0)[1]
if exitcode != 0:
error("Node(0x%x) process %d exited with non-zero status: %d" %
(id(self), self._pid, exitcode))
self._pid = self._slave = None
@property
def pid(self):
return self._pid
# Subprocesses
def _add_subprocess(self, subprocess):
self._processes[subprocess.pid] = subprocess
def Subprocess(self, *kargs, **kwargs):
return nemu.subprocess_.Subprocess(self, *kargs, **kwargs)
def Popen(self, *kargs, **kwargs):
return nemu.subprocess_.Popen(self, *kargs, **kwargs)
def system(self, *kargs, **kwargs):
return nemu.subprocess_.system(self, *kargs, **kwargs)
def backticks(self, *kargs, **kwargs):
return nemu.subprocess_.backticks(self, *kargs, **kwargs)
def backticks_raise(self, *kargs, **kwargs):
return nemu.subprocess_.backticks_raise(self, *kargs, **kwargs)
# Interfaces
def _add_interface(self, interface):
self._interfaces[interface.index] = interface
def add_if(self, **kwargs):
i = nemu.interface.NodeInterface(self)
for k, v in kwargs.items():
setattr(i, k, v)
return i
def add_tap(self, use_pi = False, **kwargs):
i = nemu.interface.TapNodeInterface(self, use_pi)
for k, v in kwargs.items():
setattr(i, k, v)
return i
def add_tun(self, use_pi = False, **kwargs):
i = nemu.interface.TunNodeInterface(self, use_pi)
for k, v in kwargs.items():
setattr(i, k, v)
return i
def import_if(self, interface):
return nemu.interface.ImportedNodeInterface(self, interface)
def del_if(self, iface):
"""Doesn't destroy the interface if it wasn't created by us."""
del self._interfaces[iface.index]
iface.destroy()
def get_interface(self, name):
return [i for i in self.get_interfaces() if i.name == name][0]
def get_interfaces(self):
if not self._slave:
return []
ifaces = self._slave.get_if_data()
for i in ifaces:
if i not in self._interfaces:
iface = nemu.interface.ImportedNodeInterface(self, i,
migrate = False)
self._auto_interfaces.append(iface) # keep it referenced!
self._interfaces[i] = iface
# by the way, clean up _interfaces
for i in list(self._interfaces): # copy before deleting!
if i not in ifaces:
notice("Node(0x%x): interface #%d went away." % (id(self), i))
self._interfaces[i].destroy()
del self._interfaces[i]
return sorted(self._interfaces.values(), key = lambda x: x.index)
def route(self, tipe = 'unicast', prefix = None, prefix_len = 0,
nexthop = None, interface = None, metric = 0):
return nemu.iproute.route(tipe, prefix, prefix_len, nexthop,
interface.index if interface else None, metric)
def add_route(self, *args, **kwargs):
# Accepts either a route object or all its constructor's parameters
if len(args) == 1 and not kwargs:
r = args[0]
else:
r = self.route(*args, **kwargs)
return self._slave.add_route(r)
def del_route(self, *args, **kwargs):
if len(args) == 1 and not kwargs:
r = args[0]
else:
r = self.route(*args, **kwargs)
return self._slave.del_route(r)
def get_routes(self):
return self._slave.get_route_data()
# Handle the creation of the child; parent gets (fd, pid), child creates and
# runs a Server(); never returns.
# Requires CAP_SYS_ADMIN privileges to run.
def _start_child(nonetns):
# Create socket pair to communicate
(s0, s1) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM, 0)
# Spawn a child that will run in a loop
pid = os.fork()
if pid:
s1.close()
return (s0, pid)
# FIXME: clean up signal handers, atexit functions, etc.
try: # pragma: no cover
# coverage doesn't seem to understand fork
s0.close()
srv = nemu.protocol.Server(s1, s1)
if not nonetns:
# create new name space
unshare.unshare(unshare.CLONE_NEWNET)
# Enable packet forwarding
execute([SYSCTL_PATH, '-w', 'net.ipv4.ip_forward=1'])
execute([SYSCTL_PATH, '-w', 'net.ipv6.conf.default.forwarding=1'])
srv.run()
except BaseException, e:
s = "Slave node aborting: %s\n" % str(e)
sep = "=" * 70 + "\n"
sys.stderr.write(s + sep)
traceback.print_exc(file=sys.stdout)
sys.stderr.write(sep)
try:
# try to pass the error to parent, if possible
s1.send("500 " + s)
except:
pass
os._exit(1)
os._exit(0) # pragma: no cover
# NOTREACHED
get_nodes = Node.get_nodes
import_if = nemu.interface.ImportedInterface
# vim:ts=4:sw=4:et:ai:sts=4
# -*- coding: utf-8 -*-
# Copyright 2010, 2011 INRIA
# Copyright 2011 Martín Ferrari <martin.ferrari@gmail.com>
#
# This file is part of Nemu.
#
# Nemu is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License version 2, as published by the Free
# Software Foundation.
#
# Nemu is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# Nemu. If not, see <http://www.gnu.org/licenses/>.
import base64, errno, os, passfd, re, select, signal, socket, sys, tempfile
import time, traceback, unshare
import nemu.subprocess_, nemu.iproute
from nemu.environ import *
try:
from cPickle import loads, dumps
except:
from pickle import loads, dumps
# ============================================================================
# Server-side protocol implementation
#
# Protocol definition
# -------------------
#
# First key: command
# Second key: sub-command or None
# Value: pair of format strings for mandatory and optional parameters.
# The format string is a chain of "s" for string and "i" for integer
_proto_commands = {
"QUIT": { None: ("", "") },
"HELP": { None: ("", "") },
"X11": {
"SET": ("ss", ""),
"SOCK": ("", "")
},
"IF": {
"LIST": ("", "i"),
"SET": ("iss", "s*"),
"RTRN": ("ii", ""),
"DEL": ("i", "")
},
"ADDR": {
"LIST": ("", "i"),
"ADD": ("isi", "s"),
"DEL": ("iss", "s")
},
"ROUT": {
"LIST": ("", ""),
"ADD": ("bbibii", ""),
"DEL": ("bbibii", "")
},
"PROC": {
"CRTE": ("b", "b*"),
"POLL": ("i", ""),
"WAIT": ("i", ""),
"KILL": ("i", "i")
},
}
# Commands valid only after PROC CRTE
_proc_commands = {
"HELP": { None: ("", "") },
"QUIT": { None: ("", "") },
"PROC": {
"USER": ("b", ""),
"CWD": ("b", ""),
"ENV": ("bb", "b*"),
"SIN": ("", ""),
"SOUT": ("", ""),
"SERR": ("", ""),
"RUN": ("", ""),
"ABRT": ("", ""),
}
}
KILL_WAIT = 3 # seconds
class Server(object):
"""Class that implements the communication protocol and dispatches calls
to the required functions. Also works as the main loop for the slave
process."""
def __init__(self, rfd, wfd):
debug("Server(0x%x).__init__()" % id(self))
# Dictionary of valid commands
self._commands = _proto_commands
# Flag to stop the server
self._closed = False
# Set to keep track of started processes
self._children = set()
# Buffer and flag for PROC mode
self._proc = None
# temporary xauth files
self._xauthfiles = {}
# X11 forwarding info
self._xfwd = None
self._xsock = None
self._rfd = _get_file(rfd, "r")
self._wfd = _get_file(wfd, "w")
def clean(self):
try:
for pid in self._children:
# -PID to kill to whole process group
os.kill(-pid, signal.SIGTERM)
now = time.time()
while time.time() - now < KILL_WAIT:
ch = set(self._children)
for pid in ch:
try:
if nemu.subprocess_.poll(pid):
self._children.remove(pid)
except OSError, e:
if e.errno == errno.ECHILD:
self._children.remove(pid)
else:
raise
if not ch:
break
time.sleep(0.1)
for pid in self._children:
warning("Killing forcefully process %d." % pid)
# -PID to kill to whole process group
os.kill(-pid, signal.SIGKILL)
for pid in self._children:
try:
nemu.subprocess_.poll(pid)
except OSError, e:
if e.errno != errno.ECHILD:
raise
finally:
for f in self._xauthfiles.values():
try:
os.unlink(f)
except:
pass
def reply(self, code, text):
"Send back a reply to the client; handle multiline messages"
if not hasattr(text, '__iter__'):
text = [ text ]
clean = []
# Split lines with embedded \n
for i in text:
clean.extend(i.splitlines())
for i in range(len(clean) - 1):
s = str(code) + "-" + clean[i] + "\n"
self._wfd.write(s)
debug("<Reply> %s" % s)
s = str(code) + " " + clean[-1] + "\n"
self._wfd.write(s)
debug("<Reply> %s" % s)
return
def readline(self):
"Read a line from the socket and detect connection break-up."
# FIXME: should use the eintr_wrapper from environ; why was I using
# readline instead of read?
while True:
try:
line = self._rfd.readline()
except IOError, e:
line = None
if e.errno == errno.EINTR:
continue
else:
raise
break
if not line:
self._closed = True
return None
debug("<Query> %s" % line)
return line.rstrip()
def readcmd(self):
"""Main entry point: read and parse a line from the client, handle
argument validation and return a tuple (function, command_name,
arguments)"""
line = self.readline()
if not line:
return None
args = line.split()
cmd1 = args[0].upper()
if cmd1 not in self._commands:
self.reply(500, "Unknown command %s." % cmd1)
return None
del args[0]
cmd2 = None
subcommands = self._commands[cmd1]
if subcommands.keys() != [ None ]:
if len(args) < 1:
self.reply(500, "Incomplete command.")
return None
cmd2 = args[0].upper()
del args[0]
if cmd2 and cmd2 not in subcommands:
self.reply(500, "Unknown sub-command for %s: %s." % (cmd1, cmd2))
return None
(mandatory, optional) = subcommands[cmd2]
argstemplate = mandatory + optional
if cmd2:
cmdname = "%s %s" % (cmd1, cmd2)
funcname = "do_%s_%s" % (cmd1, cmd2)
else:
cmdname = cmd1
funcname = "do_%s" % cmd1
if not hasattr(self, funcname): # pragma: no cover
self.reply(500, "Not implemented.")
return None
if len(args) < len(mandatory):
self.reply(500, "Missing mandatory arguments for %s." % cmdname)
return None
if (not argstemplate or argstemplate[-1] != "*") and \
len(args) > len(argstemplate):
self.reply(500, "Too many arguments for %s." % cmdname)
return None
j = 0
for i in range(len(args)):
if argstemplate[j] == '*':
j = j - 1
if argstemplate[j] == 'i':
try:
args[i] = int(args[i])
except:
self.reply(500, "Invalid parameter %s: must be an integer."
% args[i])
return None
elif argstemplate[j] == 'b':
try:
args[i] = _db64(args[i])
except TypeError:
self.reply(500, "Invalid parameter: not base-64 encoded.")
return None
elif argstemplate[j] != 's': # pragma: no cover
raise RuntimeError("Invalid argument template: %s" % _argstmpl)
# Nothing done for "s" parameters
j += 1
func = getattr(self, funcname)
debug("Command: %s, args: %s" % (cmdname, args))
return (func, cmdname, args)
def run(self):
"""Main loop; reads commands until the server is shut down or the
connection is terminated."""
self.reply(220, "Hello.");
while not self._closed:
cmd = self.readcmd()
if cmd == None:
continue
try:
cmd[0](cmd[1], *cmd[2])
except:
(t, v, tb) = sys.exc_info()
v.child_traceback = "".join(
traceback.format_exception(t, v, tb))
self.reply(550, ["# Exception data follows:",
_b64(dumps(v, protocol = 2))])
try:
self._rfd.close()
self._wfd.close()
except:
pass
self.clean()
debug("Server(0x%x) exiting" % id(self))
# FIXME: cleanup
# Commands implementation
def do_HELP(self, cmdname):
reply = ["Available commands:"]
for c in sorted(self._commands):
for sc in sorted(self._commands[c]):
if sc:
reply.append("%s %s" % (c, sc))
else:
reply.append(c)
self.reply(200, reply)
def do_QUIT(self, cmdname):
self.reply(221, "Sayounara.");
self._closed = True
def do_PROC_CRTE(self, cmdname, executable, *argv):
self._proc = { 'executable': executable, 'argv': argv }
self._commands = _proc_commands
self.reply(200, "Entering PROC mode.")
def do_PROC_USER(self, cmdname, user):
self._proc['user'] = user
self.reply(200, "Program will run as `%s'." % user)
def do_PROC_CWD(self, cmdname, dir):
self._proc['cwd'] = dir
self.reply(200, "CWD set to `%s'." % dir)
def do_PROC_ENV(self, cmdname, *env):
if len(env) % 2:
self.reply(500,
"Invalid number of arguments for PROC ENV: must be even.")
return
self._proc['env'] = {}
for i in range(len(env)/2):
self._proc['env'][env[i * 2]] = env[i * 2 + 1]
self.reply(200, "%d environment definition(s) read." % (len(env) / 2))
def do_PROC_SIN(self, cmdname):
self.reply(354,
"Pass the file descriptor now, with `%s\\n' as payload." %
cmdname)
try:
fd, payload = passfd.recvfd(self._rfd, len(cmdname) + 1)
except (IOError, RuntimeError), e:
self.reply(500, "Error receiving FD: %s" % str(e))
return
if payload[0:len(cmdname)] != cmdname:
self.reply(500, "Invalid payload: %s." % payload)
return
m = {'PROC SIN': 'stdin', 'PROC SOUT': 'stdout', 'PROC SERR': 'stderr'}
self._proc[m[cmdname]] = fd
self.reply(200, 'FD saved as %s.' % m[cmdname])
# Same code for the three commands
do_PROC_SOUT = do_PROC_SERR = do_PROC_SIN
def do_PROC_RUN(self, cmdname):
params = self._proc
params['close_fds'] = True # forced
self._proc = None
self._commands = _proto_commands
if 'env' not in params:
params['env'] = dict(os.environ) # copy
xauth = None
if self._xfwd:
display, protoname, hexkey = self._xfwd
user = params['user'] if 'user' in params else None
try:
fd, xauth = tempfile.mkstemp()
os.close(fd)
# stupid xauth format: needs the 'hostname' for local
# connections
execute([XAUTH_PATH, "-f", xauth, "add",
"%s/unix:%d" % (socket.gethostname(), display),
protoname, hexkey])
if user:
user, uid, gid = nemu.subprocess_.get_user(user)
os.chown(xauth, uid, gid)
params['env']['DISPLAY'] = "127.0.0.1:%d" % display
params['env']['XAUTHORITY'] = xauth
except Exception, e:
warning("Cannot forward X: %s" % e)
try:
os.unlink(xauth)
except:
pass
else:
if 'DISPLAY' in params['env']:
del params['env']['DISPLAY']
try:
chld = nemu.subprocess_.spawn(**params)
finally:
# I can close the fds now
for d in ('stdin', 'stdout', 'stderr'):
if d in params:
os.close(params[d])
self._children.add(chld)
self._xauthfiles[chld] = xauth
self.reply(200, "%d running." % chld)
def do_PROC_ABRT(self, cmdname):
self._proc = None
self._commands = _proto_commands
self.reply(200, "Aborted.")
def do_PROC_POLL(self, cmdname, pid):
if pid not in self._children:
self.reply(500, "Process does not exist.")
return
if cmdname == 'PROC POLL':
ret = nemu.subprocess_.poll(pid)
else:
ret = nemu.subprocess_.wait(pid)
if ret != None:
self._children.remove(pid)
if pid in self._xauthfiles:
try:
os.unlink(self._xauthfiles[pid])
except:
pass
del self._xauthfiles[pid]
self.reply(200, "%d exitcode." % ret)
else:
self.reply(450, "Not finished yet.")
# Same code for the two commands
do_PROC_WAIT = do_PROC_POLL
def do_PROC_KILL(self, cmdname, pid, sig):
if pid not in self._children:
self.reply(500, "Process does not exist.")
return
if signal:
# -PID to kill to whole process group
os.kill(-pid, sig)
else:
os.kill(-pid, signal.SIGTERM)
self.reply(200, "Process signalled.")
def do_IF_LIST(self, cmdname, ifnr = None):
if ifnr == None:
ifdata = nemu.iproute.get_if_data()[0]
else:
ifdata = nemu.iproute.get_if(ifnr)
self.reply(200, ["# Interface data follows.",
_b64(dumps(ifdata, protocol = 2))])
def do_IF_SET(self, cmdname, ifnr, *args):
if len(args) % 2:
self.reply(500,
"Invalid number of arguments for IF SET: must be even.")
return
d = {'index': ifnr}
for i in range(len(args) / 2):
d[str(args[i * 2])] = args[i * 2 + 1]
iface = nemu.iproute.interface(**d)
nemu.iproute.set_if(iface)
self.reply(200, "Done.")
def do_IF_RTRN(self, cmdname, ifnr, ns):
nemu.iproute.change_netns(ifnr, ns)
self.reply(200, "Done.")
def do_IF_DEL(self, cmdname, ifnr):
nemu.iproute.del_if(ifnr)
self.reply(200, "Done.")
def do_ADDR_LIST(self, cmdname, ifnr = None):
addrdata = nemu.iproute.get_addr_data()[0]
if ifnr != None:
addrdata = addrdata[ifnr]
self.reply(200, ["# Address data follows.",
_b64(dumps(addrdata, protocol = 2))])
def do_ADDR_ADD(self, cmdname, ifnr, address, prefixlen, broadcast = None):
if address.find(":") < 0: # crude, I know
a = nemu.iproute.ipv4address(address, prefixlen, broadcast)
else:
a = nemu.iproute.ipv6address(address, prefixlen)
nemu.iproute.add_addr(ifnr, a)
self.reply(200, "Done.")
def do_ADDR_DEL(self, cmdname, ifnr, address, prefixlen):
if address.find(":") < 0: # crude, I know
a = nemu.iproute.ipv4address(address, prefixlen, None)
else:
a = nemu.iproute.ipv6address(address, prefixlen)
nemu.iproute.del_addr(ifnr, a)
self.reply(200, "Done.")
def do_ROUT_LIST(self, cmdname):
rdata = nemu.iproute.get_route_data()
self.reply(200, ["# Routing data follows.",
_b64(dumps(rdata, protocol = 2))])
def do_ROUT_ADD(self, cmdname, tipe, prefix, prefixlen, nexthop, ifnr,
metric):
nemu.iproute.add_route(nemu.iproute.route(tipe, prefix, prefixlen,
nexthop, ifnr or None, metric))
self.reply(200, "Done.")
def do_ROUT_DEL(self, cmdname, tipe, prefix, prefixlen, nexthop, ifnr,
metric):
nemu.iproute.del_route(nemu.iproute.route(tipe, prefix, prefixlen,
nexthop, ifnr or None, metric))
self.reply(200, "Done.")
def do_X11_SET(self, cmdname, protoname, hexkey):
if not XAUTH_PATH:
self.reply(500, "Impossible to forward X: xauth not present")
return
skt, port = None, None
try:
skt, port = find_listen_port(min_port = 6010, max_port = 6099)
except:
self.reply(500, "Cannot allocate a port for X forwarding.")
return
display = port - 6000
self.reply(200, "Socket created on port %d. Use X11 SOCK to get the "
"file descriptor "
"(fixed 1-byte payload before protocol response).")
self._xfwd = display, protoname, hexkey
self._xsock = skt
def do_X11_SOCK(self, cmdname):
if not self._xsock:
self.reply(500, "X forwarding not set up.")
return
# Needs to be a separate command to handle synch & buffering issues
try:
passfd.sendfd(self._wfd, self._xsock.fileno(), "1")
except:
# need to fill the buffer on the other side, nevertheless
self._wfd.write("1")
self.reply(500, "Error sending file descriptor.")
return
self._xsock = None
self.reply(200, "Will set up X forwarding.")
# ============================================================================
#
# Client-side protocol implementation.
#
class Client(object):
"""Client-side implementation of the communication protocol. Acts as a RPC
service."""
def __init__(self, rfd, wfd):
debug("Client(0x%x).__init__()" % id(self))
self._rfd = _get_file(rfd, "r")
self._wfd = _get_file(wfd, "w")
self._forwarder = None
# Wait for slave to send banner
self._read_and_check_reply()
def __del__(self):
debug("Client(0x%x).__del__()" % id(self))
self.shutdown()
def _send_cmd(self, *args):
if not self._wfd:
raise RuntimeError("Client already shut down.")
s = " ".join(map(str, args)) + "\n"
self._wfd.write(s)
def _read_reply(self):
"""Reads a (possibly multi-line) response from the server. Returns a
tuple containing (code, text)"""
if not self._rfd:
raise RuntimeError("Client already shut down.")
text = []
while True:
line = eintr_wrapper(self._rfd.readline).rstrip()
if not line:
raise RuntimeError("Protocol error, empty line received")
m = re.search(r'^(\d{3})([ -])(.*)', line)
if not m:
raise RuntimeError("Protocol error, read: %r" % line)
status = m.group(1)
text.append(m.group(3))
if m.group(2) == " ":
break
return (int(status), "\n".join(text))
def _read_and_check_reply(self, expected = 2):
"""Reads a response and raises an exception if the first digit of the
code is not the expected value. If expected is not specified, it
defaults to 2."""
code, text = self._read_reply()
if code == 550: # exception
e = loads(_db64(text.partition("\n")[2]))
raise e
if code / 100 != expected:
raise RuntimeError("Error from slave: %d %s" % (code, text))
return text
def shutdown(self):
"Tell the client to quit."
if not self._wfd:
return
debug("Client(0x%x).shutdown()" % id(self))
self._send_cmd("QUIT")
self._read_and_check_reply()
self._rfd.close()
self._rfd = None
self._wfd.close()
self._wfd = None
if self._forwarder:
os.kill(self._forwarder, signal.SIGTERM)
self._forwarder = None
def _send_fd(self, name, fd):
"Pass a file descriptor"
self._send_cmd("PROC", name)
self._read_and_check_reply(3)
try:
passfd.sendfd(self._wfd, fd, "PROC " + name)
except:
# need to fill the buffer on the other side, nevertheless
self._wfd.write("=" * (len(name) + 5) + "\n")
# And also read the expected error
self._read_and_check_reply(5)
raise
self._read_and_check_reply()
def spawn(self, argv, executable = None,
stdin = None, stdout = None, stderr = None,
cwd = None, env = None, user = None):
"""Start a subprocess in the slave; the interface resembles
subprocess.Popen, but with less functionality. In particular
stdin/stdout/stderr can only be None or a open file descriptor.
See nemu.subprocess_.spawn for details."""
if executable == None:
executable = argv[0]
params = ["PROC", "CRTE", _b64(executable)]
for i in argv:
params.append(_b64(i))
self._send_cmd(*params)
self._read_and_check_reply()
# After this, if we get an error, we have to abort the PROC
try:
if user != None:
self._send_cmd("PROC", "USER", _b64(user))
self._read_and_check_reply()
if cwd != None:
self._send_cmd("PROC", "CWD", _b64(cwd))
self._read_and_check_reply()
if env != None:
params = []
for k, v in env.items():
params.extend([_b64(k), _b64(v)])
self._send_cmd("PROC", "ENV", *params)
self._read_and_check_reply()
if stdin != None:
self._send_fd("SIN", stdin)
if stdout != None:
self._send_fd("SOUT", stdout)
if stderr != None:
self._send_fd("SERR", stderr)
except:
self._send_cmd("PROC", "ABRT")
self._read_and_check_reply()
raise
self._send_cmd("PROC", "RUN")
pid = int(self._read_and_check_reply().split()[0])
return pid
def poll(self, pid):
"""Equivalent to Popen.poll(), checks if the process has finished.
Returns the exitcode if finished, None otherwise."""
self._send_cmd("PROC", "POLL", pid)
code, text = self._read_reply()
if code / 100 == 2:
exitcode = int(text.split()[0])
return exitcode
if code / 100 == 4:
return None
else:
raise RuntimeError("Error on command: %d %s" % (code, text))
def wait(self, pid):
"""Equivalent to Popen.wait(). Waits for the process to finish and
returns the exitcode."""
self._send_cmd("PROC", "WAIT", pid)
text = self._read_and_check_reply()
exitcode = int(text.split()[0])
return exitcode
def signal(self, pid, sig = signal.SIGTERM):
"""Equivalent to Popen.send_signal(). Sends a signal to the child
process; signal defaults to SIGTERM."""
if sig:
self._send_cmd("PROC", "KILL", pid, sig)
else:
self._send_cmd("PROC", "KILL", pid)
self._read_and_check_reply()
def get_if_data(self, ifnr = None):
if ifnr:
self._send_cmd("IF", "LIST", ifnr)
else:
self._send_cmd("IF", "LIST")
data = self._read_and_check_reply()
return loads(_db64(data.partition("\n")[2]))
def set_if(self, interface):
cmd = ["IF", "SET", interface.index]
for k in interface.changeable_attributes:
v = getattr(interface, k)
if v != None:
cmd += [k, str(v)]
self._send_cmd(*cmd)
self._read_and_check_reply()
def del_if(self, ifnr):
self._send_cmd("IF", "DEL", ifnr)
self._read_and_check_reply()
def change_netns(self, ifnr, netns):
self._send_cmd("IF", "RTRN", ifnr, netns)
self._read_and_check_reply()
def get_addr_data(self, ifnr = None):
if ifnr:
self._send_cmd("ADDR", "LIST", ifnr)
else:
self._send_cmd("ADDR", "LIST")
data = self._read_and_check_reply()
return loads(_db64(data.partition("\n")[2]))
def add_addr(self, ifnr, address):
if hasattr(address, "broadcast") and address.broadcast:
self._send_cmd("ADDR", "ADD", ifnr, address.address,
address.prefix_len, address.broadcast)
else:
self._send_cmd("ADDR", "ADD", ifnr, address.address,
address.prefix_len)
self._read_and_check_reply()
def del_addr(self, ifnr, address):
self._send_cmd("ADDR", "DEL", ifnr, address.address, address.prefix_len)
self._read_and_check_reply()
def get_route_data(self):
self._send_cmd("ROUT", "LIST")
data = self._read_and_check_reply()
return loads(_db64(data.partition("\n")[2]))
def add_route(self, route):
self._add_del_route("ADD", route)
def del_route(self, route):
self._add_del_route("DEL", route)
def _add_del_route(self, action, route):
args = ["ROUT", action, _b64(route.tipe), _b64(route.prefix),
route.prefix_len or 0, _b64(route.nexthop),
route.interface or 0, route.metric or 0]
self._send_cmd(*args)
self._read_and_check_reply()
def set_x11(self, protoname, hexkey):
# Returns a socket ready to accept() connections
self._send_cmd("X11", "SET", protoname, hexkey)
self._read_and_check_reply()
# Receive the socket
self._send_cmd("X11", "SOCK")
fd, payload = passfd.recvfd(self._rfd, 1)
self._read_and_check_reply()
skt = socket.fromfd(fd, socket.AF_INET, socket.SOCK_DGRAM)
os.close(fd) # fromfd dup()'s
return skt
def enable_x11_forwarding(self):
xinfo = _parse_display()
if not xinfo:
raise RuntimeError("Impossible to forward X: DISPLAY variable not "
"set or invalid")
xauthdpy, sock, addr = xinfo
if not XAUTH_PATH:
raise RuntimeError("Impossible to forward X: xauth not present")
auth = backticks([XAUTH_PATH, "list", xauthdpy])
match = re.match(r"\S+\s+(\S+)\s+(\S+)\n", auth)
if not match:
raise RuntimeError("Impossible to forward X: invalid DISPLAY")
protoname, hexkey = match.groups()
server = self.set_x11(protoname, hexkey)
self._forwarder = _spawn_x11_forwarder(server, sock, addr)
def _b64(text):
if text == None:
# easier this way
text = ''
text = str(text)
if len(text) == 0 or filter(lambda x: ord(x) <= ord(" ") or
ord(x) > ord("z") or x == "=", text):
return "=" + base64.b64encode(text)
else:
return text
def _db64(text):
if not text or text[0] != '=':
return text
return base64.b64decode(text[1:])
def _get_file(fd, mode):
# Since fdopen insists on closing the fd on destruction, I need to dup()
if hasattr(fd, "fileno"):
nfd = os.dup(fd.fileno())
else:
nfd = os.dup(fd)
return os.fdopen(nfd, mode, 1)
def _parse_display():
if "DISPLAY" not in os.environ:
return None
dpy = os.environ["DISPLAY"]
match = re.search(r"^(.*):(\d+)(?:\.\d+)?$", dpy)
if not match:
return None
host, number = match.groups()
if host and host != "unix":
sock = (socket.AF_INET, socket.SOCK_STREAM, 0)
addr = (host, 6000 + int(number))
else:
sock = (socket.AF_UNIX, socket.SOCK_STREAM, 0)
addr = ("/tmp/.X11-unix/X%s" % number)
# Xauth does not work with DISPLAYs of the form localhost:X.
if host and host != "localhost":
xauthdpy = dpy
else:
xauthdpy = "unix:%s" % number
return xauthdpy, sock, addr
def _spawn_x11_forwarder(server, xsock, xaddr):
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.listen(10) # arbitrary
pid = os.fork()
if pid:
return pid
# XXX: clear signals, etc
try:
_x11_forwarder(server, xsock, xaddr)
except:
traceback.print_exc(file=sys.stderr)
os._exit(1)
def _x11_forwarder(server, xsock, xaddr):
def clean(idx, toread, fd):
# silently discards any buffer!
fd1 = fd
fd2 = idx[fd]["wr"]
try:
fd1.close()
except:
pass
try:
fd2.close()
except:
pass
del idx[fd1]
if fd1 in toread:
toread.remove(fd1)
del idx[fd2]
if fd2 in toread:
toread.remove(fd2)
toread = set([server])
idx = {}
while(True):
towrite = [x["wr"] for x in idx.values() if x["buf"]]
(rr, wr, er) = select.select(toread, towrite, [])
if server in rr:
xconn = socket.socket(*xsock)
xconn.connect(xaddr)
client, addr = server.accept()
toread.add(client)
toread.add(xconn)
idx[client] = {
"rd": client,
"wr": xconn,
"buf": [],
"closed": False
}
idx[xconn] = {
"rd": xconn,
"wr": client,
"buf": [],
"closed": False
}
continue
for fd in rr:
chan = idx[fd]
try:
s = os.read(fd.fileno(), 4096)
except OSError, e:
if e.errno == errno.ECONNRESET:
clean(idx, toread, fd)
continue
elif e.errno == errno.EINTR:
continue
else:
raise
if s == "":
# fd closed for read
toread.remove(fd)
chan["closed"] = True
if not chan["buf"]:
# close the writing side
try:
chan["wr"].shutdown(socket.SHUT_WR)
except:
pass # might fail sometimes
else:
chan["buf"].append(s)
for fd in wr:
chan = idx[idx[fd]["wr"]]
try:
x = os.write(fd.fileno(), chan["buf"][0])
except OSError, e:
if e.errno == errno.EINTR:
continue
if e.errno == errno.EPIPE or e.errno == errno.ECONNRESET:
clean(idx, toread, fd)
continue
raise
if x < len(chan["buf"][0]):
chan["buf"][0] = chan["buf"][x:]
else:
del chan["buf"][0]
if not chan["buf"] and chan["closed"]:
chan["wr"].shutdown(socket.SHUT_WR)
chan["buf"] = None
# clean-up
for chan in idx.values():
if chan["rd"] not in idx:
# already deleted
continue
twin = idx[chan["wr"]]
if not chan["closed"] or chan["buf"] or not twin["closed"] \
or twin["buf"]:
continue
clean(idx, toread, chan["rd"])
# vim:ts=4:sw=4:et:ai:sts=4
# -*- coding: utf-8 -*-
# Copyright 2010, 2011 INRIA
# Copyright 2011 Martín Ferrari <martin.ferrari@gmail.com>
#
# This file is part of Nemu.
#
# Nemu is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License version 2, as published by the Free
# Software Foundation.
#
# Nemu is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# Nemu. If not, see <http://www.gnu.org/licenses/>.
import fcntl, grp, os, pickle, pwd, signal, select, sys, time, traceback
from nemu.environ import eintr_wrapper
__all__ = [ 'PIPE', 'STDOUT', 'Popen', 'Subprocess', 'spawn', 'wait', 'poll',
'get_user', 'system', 'backticks', 'backticks_raise' ]
# User-facing interfaces
KILL_WAIT = 3 # seconds
class Subprocess(object):
"""Class that allows the execution of programs inside a nemu Node. This is
the base class for all process operations, Popen provides a more high level
interface."""
# FIXME
default_user = None
def __init__(self, node, argv, executable = None,
stdin = None, stdout = None, stderr = None,
shell = False, cwd = None, env = None, user = None):
self._slave = node._slave
"""Forks and execs a program, with stdio redirection and user
switching.
A nemu Node to run the program is is specified as the first parameter.
The program is specified by `executable', if it does not contain any
slash, the PATH environment variable is used to search for the file.
The `user` parameter, if not None, specifies a user name to run the
command as, after setting its primary and secondary groups. If a
numerical UID is given, a reverse lookup is performed to find the user
name and then set correctly the groups.
To run the program in a different directory than the current one, it
should be set in `cwd'.
If specified, `env' replaces the caller's environment with the
dictionary provided.
The standard input, output, and error of the created process will be
redirected to the file descriptors specified by `stdin`, `stdout`, and
`stderr`, respectively. These parameters must be open file objects,
integers, or None (for no redirection). Note that the descriptors will
not be closed by this class.
Exceptions occurred while trying to set up the environment or executing
the program are propagated to the parent."""
if user == None:
user = Subprocess.default_user
if isinstance(argv, str):
argv = [ argv ]
if shell:
argv = [ '/bin/sh', '-c' ] + argv
# Initialize attributes that would be used by the destructor if spawn
# fails
self._pid = self._returncode = None
# confusingly enough, to go to the function at the top of this file,
# I need to call it thru the communications protocol: remember that
# happens in another process!
self._pid = self._slave.spawn(argv, executable = executable,
stdin = stdin, stdout = stdout, stderr = stderr,
cwd = cwd, env = env, user = user)
node._add_subprocess(self)
@property
def pid(self):
"""The real process ID of this subprocess."""
return self._pid
def poll(self):
"""Checks status of program, returns exitcode or None if still running.
See Popen.poll."""
if self._returncode == None:
self._returncode = self._slave.poll(self._pid)
return self.returncode
def wait(self):
"""Waits for program to complete and returns the exitcode.
See Popen.wait"""
if self._returncode == None:
self._returncode = self._slave.wait(self._pid)
return self.returncode
def signal(self, sig = signal.SIGTERM):
"""Sends a signal to the process."""
if self._returncode == None:
self._slave.signal(self._pid, sig)
@property
def returncode(self):
"""When the program has finished (and has been waited for with
communicate, wait, or poll), returns the signal that killed the
program, if negative; otherwise, it is the exit code of the program.
"""
if self._returncode == None:
return None
if os.WIFSIGNALED(self._returncode):
return -os.WTERMSIG(self._returncode)
if os.WIFEXITED(self._returncode):
return os.WEXITSTATUS(self._returncode)
raise RuntimeError("Invalid return code") # pragma: no cover
def __del__(self):
self.destroy()
def destroy(self):
if self._returncode != None or self._pid == None:
return
self.signal()
now = time.time()
while time.time() - now < KILL_WAIT:
if self.poll() != None:
return
time.sleep(0.1)
sys.stderr.write("WARNING: killing forcefully process %d.\n" %
self._pid)
self.signal(signal.SIGKILL)
self.wait()
PIPE = -1
STDOUT = -2
class Popen(Subprocess):
"""Higher-level interface for executing processes, that tries to emulate
the stdlib's subprocess.Popen as much as possible."""
def __init__(self, node, argv, executable = None,
stdin = None, stdout = None, stderr = None, bufsize = 0,
shell = False, cwd = None, env = None, user = None):
"""As in Subprocess, `node' specifies the nemu Node to run in.
The `stdin', `stdout', and `stderr' parameters also accept the special
values subprocess.PIPE or subprocess.STDOUT. Check the stdlib's
subprocess module for more details. `bufsize' specifies the buffer size
for the buffered IO provided for PIPE'd descriptors.
"""
self.stdin = self.stdout = self.stderr = None
self._pid = self._returncode = None
fdmap = { "stdin": stdin, "stdout": stdout, "stderr": stderr }
# if PIPE: all should be closed at the end
for k, v in fdmap.items():
if v == None:
continue
if v == PIPE:
r, w = os.pipe()
if k == "stdin":
self.stdin = os.fdopen(w, 'wb', bufsize)
fdmap[k] = r
else:
setattr(self, k, os.fdopen(r, 'rb', bufsize))
fdmap[k] = w
elif isinstance(v, int):
pass
else:
fdmap[k] = v.fileno()
if stderr == STDOUT:
fdmap['stderr'] = fdmap['stdout']
super(Popen, self).__init__(node, argv, executable = executable,
stdin = fdmap['stdin'], stdout = fdmap['stdout'],
stderr = fdmap['stderr'],
shell = shell, cwd = cwd, env = env, user = user)
# Close pipes, they have been dup()ed to the child
for k, v in fdmap.items():
if getattr(self, k) != None:
eintr_wrapper(os.close, v)
def communicate(self, input = None):
"""See Popen.communicate."""
# FIXME: almost verbatim from stdlib version, need to be removed or
# something
wset = []
rset = []
err = None
out = None
if self.stdin != None:
self.stdin.flush()
if input:
wset.append(self.stdin)
else:
self.stdin.close()
if self.stdout != None:
rset.append(self.stdout)
out = []
if self.stderr != None:
rset.append(self.stderr)
err = []
offset = 0
while rset or wset:
r, w, x = select.select(rset, wset, [])
if self.stdin in w:
wrote = os.write(self.stdin.fileno(),
#buffer(input, offset, select.PIPE_BUF))
buffer(input, offset, 512)) # XXX: py2.7
offset += wrote
if offset >= len(input):
self.stdin.close()
wset = []
for i in self.stdout, self.stderr:
if i in r:
d = os.read(i.fileno(), 1024) # No need for eintr wrapper
if d == "":
i.close
rset.remove(i)
else:
if i == self.stdout:
out.append(d)
else:
err.append(d)
if out != None:
out = ''.join(out)
if err != None:
err = ''.join(err)
self.wait()
return (out, err)
def system(node, args):
"""Emulates system() function, if `args' is an string, it uses `/bin/sh' to
exexecute it, otherwise is interpreted as the argv array to call execve."""
shell = isinstance(args, str)
return Popen(node, args, shell = shell).wait()
def backticks(node, args):
"""Emulates shell backticks, if `args' is an string, it uses `/bin/sh' to
exexecute it, otherwise is interpreted as the argv array to call execve."""
shell = isinstance(args, str)
return Popen(node, args, shell = shell, stdout = PIPE).communicate()[0]
def backticks_raise(node, args):
"""Emulates shell backticks, if `args' is an string, it uses `/bin/sh' to
exexecute it, otherwise is interpreted as the argv array to call execve.
Raises an RuntimeError if the return value is not 0."""
shell = isinstance(args, str)
p = Popen(node, args, shell = shell, stdout = PIPE)
out = p.communicate()[0]
ret = p.returncode
if ret > 0:
raise RuntimeError("Command failed with return code %d." % ret)
if ret < 0:
raise RuntimeError("Command killed by signal %d." % -ret)
return out
# =======================================================================
#
# Server-side code, called from nemu.protocol.Server
def spawn(executable, argv = None, cwd = None, env = None, close_fds = False,
stdin = None, stdout = None, stderr = None, user = None):
"""Internal function that performs all the dirty work for Subprocess, Popen
and friends. This is executed in the slave process, directly from the
protocol.Server class.
Parameters have the same meaning as the stdlib's subprocess.Popen class,
with one addition: the `user` parameter, if not None, specifies a user name
to run the command as, after setting its primary and secondary groups. If a
numerical UID is given, a reverse lookup is performed to find the user name
and then set correctly the groups.
When close_fds is True, it closes all file descriptors bigger than 2. It
can also be an iterable of file descriptors to close after fork.
Note that 'std{in,out,err}' must be None, integers, or file objects, PIPE
is not supported here. Also, the original descriptors are not closed.
"""
userfd = [stdin, stdout, stderr]
filtered_userfd = filter(lambda x: x != None and x >= 0, userfd)
for i in range(3):
if userfd[i] != None and not isinstance(userfd[i], int):
userfd[i] = userfd[i].fileno() # pragma: no cover
# Verify there is no clash
assert not (set([0, 1, 2]) & set(filtered_userfd))
if user != None:
user, uid, gid = get_user(user)
home = pwd.getpwuid(uid)[5]
groups = [x[2] for x in grp.getgrall() if user in x[3]]
if not env:
env = dict(os.environ)
env['HOME'] = home
env['USER'] = user
(r, w) = os.pipe()
pid = os.fork()
if pid == 0: # pragma: no cover
# coverage doesn't seem to understand fork
try:
# Set up stdio piping
for i in range(3):
if userfd[i] != None and userfd[i] >= 0:
os.dup2(userfd[i], i)
if userfd[i] != i and userfd[i] not in userfd[0:i]:
eintr_wrapper(os.close, userfd[i]) # only in child!
# Set up special control pipe
eintr_wrapper(os.close, r)
flags = fcntl.fcntl(w, fcntl.F_GETFD)
fcntl.fcntl(w, fcntl.F_SETFD, flags | fcntl.FD_CLOEXEC)
if close_fds == True:
for i in xrange(3, MAXFD):
if i != w:
try:
os.close(i)
except:
pass
elif close_fds != False:
for i in close_fds:
os.close(i)
# changing process group id
# (it is necessary to kill the forked subprocesses)
os.setpgrp()
if user != None:
# Change user
os.setgid(gid)
os.setgroups(groups)
os.setuid(uid)
if cwd != None:
os.chdir(cwd)
if not argv:
argv = [ executable ]
if '/' in executable: # Should not search in PATH
if env != None:
os.execve(executable, argv, env)
else:
os.execv(executable, argv)
else: # use PATH
if env != None:
os.execvpe(executable, argv, env)
else:
os.execvp(executable, argv)
raise RuntimeError("Unreachable reached!")
except:
try:
(t, v, tb) = sys.exc_info()
# Got the child_traceback attribute trick from Python's
# subprocess.py
v.child_traceback = "".join(
traceback.format_exception(t, v, tb))
eintr_wrapper(os.write, w, pickle.dumps(v))
eintr_wrapper(os.close, w)
#traceback.print_exc()
except:
traceback.print_exc()
os._exit(1)
eintr_wrapper(os.close, w)
# read EOF for success, or a string as error info
s = ""
while True:
s1 = eintr_wrapper(os.read, r, 4096)
if s1 == "":
break
s += s1
eintr_wrapper(os.close, r)
if s == "":
return pid
# It was an error
eintr_wrapper(os.waitpid, pid, 0)
exc = pickle.loads(s)
# XXX: sys.excepthook
#print exc.child_traceback
raise exc
def poll(pid):
"""Check if the process already died. Returns the exit code or None if
the process is still alive."""
r = os.waitpid(pid, os.WNOHANG)
if r[0]:
return r[1]
return None
def wait(pid):
"""Wait for process to die and return the exit code."""
return eintr_wrapper(os.waitpid, pid, 0)[1]
def get_user(user):
"Take either an username or an uid, and return a tuple (user, uid, gid)."
if str(user).isdigit():
uid = int(user)
try:
user = pwd.getpwuid(uid)[0]
except KeyError:
raise ValueError("UID %d does not exist" % int(user))
else:
try:
uid = pwd.getpwnam(str(user))[2]
except KeyError:
raise ValueError("User %s does not exist" % str(user))
gid = pwd.getpwuid(uid)[3]
return user, uid, gid
# internal stuff, do not look!
try:
MAXFD = os.sysconf("SC_OPEN_MAX")
except: # pragma: no cover
MAXFD = 256
#!/usr/bin/env python
# vim:ts=4:sw=4:et:ai:sts=4
import grp, os, pwd, select, time, unittest
import nemu, test_util
class TestConfigure(unittest.TestCase):
def test_config_run_as_static(self):
# Don't allow root as default user
self.assertRaises(AttributeError, setattr, nemu.config,
'run_as', 'root')
self.assertRaises(AttributeError, setattr, nemu.config,
'run_as', 0)
# Don't allow invalid users
self.assertRaises(AttributeError, setattr, nemu.config,
'run_as', 'foobarbaz') # hope nobody has this user!
self.assertRaises(AttributeError, setattr, nemu.config,
'run_as', 65536)
try:
pwd.getpwnam('nobody')
nemu.config.run_as('nobody')
self.assertEquals(nemu.config.run_as, 'nobody')
except:
pass
class TestGlobal(unittest.TestCase):
@test_util.skipUnless(os.getuid() == 0, "Test requires root privileges")
def test_run_ping_p2pif(self):
n1 = nemu.Node()
n2 = nemu.Node()
i1, i2 = nemu.P2PInterface.create_pair(n1, n2)
i1.up = i2.up = True
i1.lladdr = 'd6:4b:3f:f7:ff:7e'
i2.lladdr = 'd6:4b:3f:f7:ff:7f'
i1.add_v4_address('10.0.0.1', 24)
i2.add_v4_address('10.0.0.2', 24)
null = file('/dev/null', 'wb')
a1 = n1.Popen(['ping', '-qc1', '10.0.0.2'], stdout = null)
a2 = n2.Popen(['ping', '-qc1', '10.0.0.1'], stdout = null)
self.assertEquals(a1.wait(), 0)
self.assertEquals(a2.wait(), 0)
# Test ipv6 autoconfigured addresses
time.sleep(2) # Wait for autoconfiguration
a1 = n1.Popen(['ping6', '-qc1', '-I', i1.name,
'fe80::d44b:3fff:fef7:ff7f'], stdout = null)
a2 = n2.Popen(['ping6', '-qc1', '-I', i2.name,
'fe80::d44b:3fff:fef7:ff7e'], stdout = null)
self.assertEquals(a1.wait(), 0)
self.assertEquals(a2.wait(), 0)
@test_util.skipUnless(os.getuid() == 0, "Test requires root privileges")
def test_run_ping_node_if(self):
n1 = nemu.Node()
n2 = nemu.Node()
i1 = n1.add_if()
i2 = n2.add_if()
i1.up = i2.up = True
l = nemu.Switch()
l.connect(i1)
l.connect(i2)
l.up = True
i1.add_v4_address('10.0.0.1', 24)
i2.add_v4_address('10.0.0.2', 24)
null = file('/dev/null', 'wb')
a1 = n1.Popen(['ping', '-qc1', '10.0.0.2'], stdout = null)
a2 = n2.Popen(['ping', '-qc1', '10.0.0.1'], stdout = null)
self.assertEquals(a1.wait(), 0)
self.assertEquals(a2.wait(), 0)
@test_util.skipUnless(os.getuid() == 0, "Test requires root privileges")
def test_run_ping_routing_p2p(self):
n1 = nemu.Node()
n2 = nemu.Node()
n3 = nemu.Node()
i12, i21 = nemu.P2PInterface.create_pair(n1, n2)
i23, i32 = nemu.P2PInterface.create_pair(n2, n3)
i12.up = i21.up = i23.up = i32.up = True
i12.add_v4_address('10.0.0.1', 24)
i21.add_v4_address('10.0.0.2', 24)
i23.add_v4_address('10.0.1.1', 24)
i32.add_v4_address('10.0.1.2', 24)
n1.add_route(prefix = '10.0.1.0', prefix_len = 24,
nexthop = '10.0.0.2')
n3.add_route(prefix = '10.0.0.0', prefix_len = 24,
nexthop = '10.0.1.1')
null = file('/dev/null', 'wb')
a1 = n1.Popen(['ping', '-qc1', '10.0.1.2'], stdout = null)
a2 = n3.Popen(['ping', '-qc1', '10.0.0.1'], stdout = null)
self.assertEquals(a1.wait(), 0)
self.assertEquals(a2.wait(), 0)
@test_util.skipUnless(os.getuid() == 0, "Test requires root privileges")
def test_run_ping_routing(self):
n1 = nemu.Node()
n2 = nemu.Node()
n3 = nemu.Node()
i1 = n1.add_if()
i2a = n2.add_if()
i2b = n2.add_if()
i3 = n3.add_if()
i1.up = i2a.up = i2b.up = i3.up = True
l1 = nemu.Switch()
l2 = nemu.Switch()
l1.connect(i1)
l1.connect(i2a)
l2.connect(i2b)
l2.connect(i3)
l1.up = l2.up = True
i1.add_v4_address('10.0.0.1', 24)
i2a.add_v4_address('10.0.0.2', 24)
i2b.add_v4_address('10.0.1.1', 24)
i3.add_v4_address('10.0.1.2', 24)
n1.add_route(prefix = '10.0.1.0', prefix_len = 24,
nexthop = '10.0.0.2')
n3.add_route(prefix = '10.0.0.0', prefix_len = 24,
nexthop = '10.0.1.1')
null = file('/dev/null', 'wb')
a1 = n1.Popen(['ping', '-qc1', '10.0.1.2'], stdout = null)
a2 = n3.Popen(['ping', '-qc1', '10.0.0.1'], stdout = null)
self.assertEquals(a1.wait(), 0)
self.assertEquals(a2.wait(), 0)
def _forward_packets(self, subproc, if1, if2):
while(True):
ready = select.select([if1.fd, if2.fd], [], [], 0.1)[0]
if ready:
s = os.read(ready[0], 65536)
if ready[0] == if1.fd:
os.write(if2.fd, s)
else:
os.write(if1.fd, s)
if not s:
break
if subproc.poll() != None:
break
@test_util.skipUnless(os.getuid() == 0, "Test requires root privileges")
def test_run_ping_tun(self):
"""This test simulates a point to point connection between two hosts
using two tun devices."""
n1 = nemu.Node()
n2 = nemu.Node()
# Use PI, so that's tested too.
tun1 = n1.add_tun(use_pi = True)
tun2 = n2.add_tun(use_pi = True)
tun1.up = tun2.up = True
tun1.add_v4_address('10.0.1.1', 24)
tun2.add_v4_address('10.0.1.2', 24)
null = file('/dev/null', 'wb')
a = n1.Popen(['ping', '-qc1', '10.0.1.2'], stdout = null)
self._forward_packets(a, tun1, tun2)
self.assertEquals(a.wait(), 0)
@test_util.skipUnless(os.getuid() == 0, "Test requires root privileges")
def test_run_ping_tap(self):
"""This test simulates a point to point connection between two hosts
using two tap devices."""
n1 = nemu.Node()
n2 = nemu.Node()
tap1 = n1.add_tap()
tap2 = n2.add_tap()
tap1.up = tap2.up = True
tap1.add_v4_address('10.0.1.1', 24)
tap2.add_v4_address('10.0.1.2', 24)
null = file('/dev/null', 'wb')
a = n1.Popen(['ping', '-qc1', '10.0.1.2'], stdout = null)
self._forward_packets(a, tap1, tap2)
self.assertEquals(a.wait(), 0)
@test_util.skipUnless(os.getuid() == 0, "Test requires root privileges")
def test_run_ping_tap_routing(self):
"""This test simulates a point to point connection between two hosts
using two tap devices, and normal connections with other two, to use
routing."""
n1 = nemu.Node()
n2 = nemu.Node()
n3 = nemu.Node()
n4 = nemu.Node()
i1 = n1.add_if()
i2 = n2.add_if()
tap1 = n2.add_tap()
tap2 = n3.add_tap()
i3 = n3.add_if()
i4 = n4.add_if()
i1.up = i2.up = tap1.up = tap2.up = i3.up = i4.up = True
l1 = nemu.Switch()
l2 = nemu.Switch()
l1.connect(i1)
l1.connect(i2)
l2.connect(i3)
l2.connect(i4)
l1.up = l2.up = True
i1.add_v4_address('10.0.0.1', 24)
i2.add_v4_address('10.0.0.2', 24)
tap1.add_v4_address('10.0.1.1', 24)
tap2.add_v4_address('10.0.1.2', 24)
i3.add_v4_address('10.0.2.1', 24)
i4.add_v4_address('10.0.2.2', 24)
n1.add_route(prefix = '10.0.1.0', prefix_len = 24, nexthop = '10.0.0.2')
n1.add_route(prefix = '10.0.2.0', prefix_len = 24, nexthop = '10.0.0.2')
n2.add_route(prefix = '10.0.2.0', prefix_len = 24, nexthop = '10.0.1.2')
n3.add_route(prefix = '10.0.0.0', prefix_len = 24, nexthop = '10.0.1.1')
n4.add_route(prefix = '10.0.1.0', prefix_len = 24, nexthop = '10.0.2.1')
n4.add_route(prefix = '10.0.0.0', prefix_len = 24, nexthop = '10.0.2.1')
null = file('/dev/null', 'wb')
a = n1.Popen(['ping', '-qc1', '10.0.2.2'], stdout = null)
self._forward_packets(a, tap1, tap2)
self.assertEquals(a.wait(), 0)
class TestX11(unittest.TestCase):
@test_util.skipUnless("DISPLAY" in os.environ, "Test requires working X11")
@test_util.skipUnless(nemu.environ.XDPYINFO_PATH, "Test requires xdpyinfo")
def test_run_xdpyinfo(self):
xdpy = nemu.environ.XDPYINFO_PATH
info = nemu.environ.backticks([xdpy])
# remove first line, contains the display name
info = info.partition("\n")[2]
n = nemu.Node(nonetns = True, forward_X11 = True)
info2 = n.backticks([xdpy])
info2 = info2.partition("\n")[2]
self.assertEquals(info, info2)
@test_util.skipUnless(os.getuid() == 0, "Test requires root privileges")
@test_util.skipUnless("DISPLAY" in os.environ, "Test requires working X11")
@test_util.skipUnless(nemu.environ.XDPYINFO_PATH, "Test requires xdpyinfo")
def test_run_xdpyinfo_netns(self):
xdpy = nemu.environ.XDPYINFO_PATH
info = nemu.environ.backticks([xdpy])
# remove first line, contains the display name
info = info.partition("\n")[2]
n = nemu.Node(forward_X11 = True)
info2 = n.backticks([xdpy])
info2 = info2.partition("\n")[2]
self.assertEquals(info, info2)
if __name__ == '__main__':
unittest.main()
#!/usr/bin/env python
# vim:ts=4:sw=4:et:ai:sts=4
from test_util import get_devs, get_devs_netns
from nemu.environ import *
import nemu, test_util
import os, unittest
class TestUtils(unittest.TestCase):
def test_utils(self):
devs = get_devs()
# There should be at least loopback!
self.assertTrue(len(devs) > 0)
self.assertTrue('lo' in devs)
self.assertTrue(devs['lo']['up'])
self.assertEquals(devs['lo']['lladdr'], '00:00:00:00:00:00')
self.assertTrue( {
'address': '127.0.0.1', 'prefix_len': 8,
'broadcast': None, 'family': 'inet'
} in devs['lo']['addr'])
class TestIPRouteStuff(unittest.TestCase):
def test_fix_lladdr(self):
fl = nemu.iproute._fix_lladdr
self.assertEquals(fl('42:71:e0:90:ca:42'), '42:71:e0:90:ca:42')
self.assertEquals(fl('4271E090CA42'), '42:71:e0:90:ca:42',
'Normalization of link-level address: missing colons and '
'upper caps')
self.assertEquals(fl('2:71:E:90:CA:42'), '02:71:0e:90:ca:42',
'Normalization of link-level address: missing zeroes')
self.assertEquals(fl('271E090CA42'), '02:71:e0:90:ca:42',
'Automatic normalization of link-level address: missing '
'colons and zeroes')
self.assertRaises(ValueError, fl, 'foo')
self.assertRaises(ValueError, fl, '42:71:e0:90:ca42')
self.assertRaises(ValueError, fl, '1234567890123')
def test_any_to_bool(self):
a2b = nemu.iproute._any_to_bool
for i in (True, 2, 'trUe', '1', 'foo', 1.0, [1]):
self.assertTrue(a2b(i))
for i in (False, 0, 'falsE', '0', '', 0.0, []):
self.assertFalse(a2b(i))
def test_non_empty_str(self):
nes = nemu.iproute._non_empty_str
self.assertEquals(nes(''), None)
self.assertEquals(nes('Foo'), 'Foo')
self.assertEquals(nes(1), '1')
def test_interface(self):
i = nemu.iproute.interface(index = 1)
self.assertRaises(AttributeError, setattr, i, 'index', 2)
self.assertRaises(ValueError, setattr, i, 'mtu', -1)
self.assertEquals(repr(i), 'nemu.iproute.interface(index = 1, '
'name = None, up = None, mtu = None, lladdr = None, '
'broadcast = None, multicast = None, arp = None)')
i.name = 'foo'; i.up = 1; i.arp = True; i.mtu = 1500
self.assertEquals(repr(i), 'nemu.iproute.interface(index = 1, '
'name = \'foo\', up = True, mtu = 1500, lladdr = None, '
'broadcast = None, multicast = None, arp = True)')
j = nemu.iproute.interface(index = 2)
j.name = 'bar'; j.up = False; j.arp = 1
# Modifications to turn j into i.
self.assertEquals(repr(i - j), 'nemu.iproute.interface(index = 1, '
'name = \'foo\', up = True, mtu = 1500, lladdr = None, '
'broadcast = None, multicast = None, arp = None)')
# Modifications to turn i into j.
self.assertEquals(repr(j - i), 'nemu.iproute.interface(index = 2, '
'name = \'bar\', up = False, mtu = None, lladdr = None, '
'broadcast = None, multicast = None, arp = None)')
class TestInterfaces(unittest.TestCase):
@test_util.skipUnless(os.getuid() == 0, "Test requires root privileges")
def test_interface_creation(self):
node0 = nemu.Node()
ifaces = []
for i in range(5):
ifaces.append(node0.add_if())
devs = get_devs_netns(node0)
for i in range(5):
self.assertTrue(devs['lo']['up'])
self.assertTrue(ifaces[i].name in devs)
node_devs = set(node0.get_interfaces())
self.assertTrue(set(ifaces).issubset(node_devs))
loopback = node_devs - set(ifaces) # should be!
self.assertEquals(len(loopback), 1)
self.assertEquals(loopback.pop().name, 'lo')
devs = get_devs()
for i in range(5):
peer_name = nemu.iproute.get_if(ifaces[i].control.index).name
self.assertTrue(peer_name in devs)
@test_util.skipUnless(os.getuid() == 0, "Test requires root privileges")
def test_interface_settings(self):
node0 = nemu.Node()
if0 = node0.add_if(lladdr = '42:71:e0:90:ca:42', mtu = 1492)
self.assertEquals(if0.lladdr, '42:71:e0:90:ca:42',
"Constructor parameters")
self.assertEquals(if0.mtu, 1492, "Constructor parameters")
if0.lladdr = '4271E090CA42'
self.assertEquals(if0.lladdr, '42:71:e0:90:ca:42', """Normalization of
link-level address: missing colons and upper caps""")
if0.lladdr = '2:71:E0:90:CA:42'
self.assertEquals(if0.lladdr, '02:71:e0:90:ca:42',
"""Normalization of link-level address: missing zeroes""")
if0.lladdr = '271E090CA42'
self.assertEquals(if0.lladdr, '02:71:e0:90:ca:42',
"""Automatic normalization of link-level address: missing
colons and zeroes""")
self.assertRaises(ValueError, setattr, if0, 'lladdr', 'foo')
self.assertRaises(ValueError, setattr, if0, 'lladdr', '1234567890123')
self.assertEquals(if0.mtu, 1492)
# detected by setter
self.assertRaises(ValueError, setattr, if0, 'mtu', 0)
# error from ip
self.assertRaises(RuntimeError, setattr, if0, 'mtu', 1)
self.assertRaises(RuntimeError, setattr, if0, 'mtu', 65537)
devs = get_devs_netns(node0)
self.assertTrue(if0.name in devs)
self.assertFalse(devs[if0.name]['up'])
self.assertEquals(devs[if0.name]['lladdr'], if0.lladdr)
self.assertEquals(devs[if0.name]['mtu'], if0.mtu)
if0.up = True
devs = get_devs_netns(node0)
self.assertTrue(devs[if0.name]['up'])
# Verify that data is actually read from the kernel
r = node0.system([IP_PATH, "link", "set", if0.name, "mtu", "1500"])
self.assertEquals(r, 0)
devs = get_devs_netns(node0)
self.assertEquals(devs[if0.name]['mtu'], 1500)
self.assertEquals(devs[if0.name]['mtu'], if0.mtu)
# FIXME: get_stats
@test_util.skipUnless(os.getuid() == 0, "Test requires root privileges")
def test_interface_addresses(self):
node0 = nemu.Node()
if0 = node0.add_if()
if0.add_v4_address(address = '10.0.0.1', prefix_len = 24,
broadcast = '10.0.0.255')
if0.add_v4_address(address = '10.0.2.1', prefix_len = 26)
if0.add_v6_address(address = 'fe80::222:19ff:fe22:615d',
prefix_len = 64)
devs = get_devs_netns(node0)
self.assertTrue( {
'address': '10.0.0.1', 'prefix_len': 24,
'broadcast': '10.0.0.255', 'family': 'inet'
} in devs[if0.name]['addr'])
self.assertTrue( {
'address': '10.0.2.1', 'prefix_len': 26,
'broadcast': '10.0.2.63', 'family': 'inet'
} in devs[if0.name]['addr'])
self.assertTrue( {
'address': 'fe80::222:19ff:fe22:615d', 'prefix_len': 64,
'family': 'inet6'
} in devs[if0.name]['addr'])
self.assertTrue(len(if0.get_addresses()) >= 2)
self.assertEquals(if0.get_addresses(), devs[if0.name]['addr'])
class TestWithDummy(unittest.TestCase):
def setUp(self):
self.cleanup = []
@test_util.skipUnless(os.getuid() == 0, "Test requires root privileges")
@test_util.skipUnless(
test_util.get_linux_ver() >= test_util.make_linux_ver("2.6.35"),
"Test trigger a kernel bug on 2.6.34")
def test_interface_migration(self):
node = nemu.Node()
self.dummyname = "dummy%d" % os.getpid()
self.assertEquals(os.system("%s link add name %s type dummy" %
(IP_PATH, self.dummyname)), 0)
devs = get_devs()
self.assertTrue(self.dummyname in devs)
dummyidx = devs[self.dummyname]['idx']
if0 = node.import_if(self.dummyname)
self.assertTrue(self.dummyname not in get_devs())
node_devs = dict([(i.index, i) for i in node.get_interfaces()])
self.assertTrue(dummyidx in node_devs)
if0.lladdr = '42:71:e0:90:ca:43'
if0.mtu = 1400
devs = get_devs_netns(node)
self.assertTrue(if0.name in devs)
self.assertEquals(devs[if0.name]['lladdr'], '42:71:e0:90:ca:43')
self.assertEquals(devs[if0.name]['mtu'], 1400)
node.destroy()
self.assertTrue(self.dummyname in get_devs())
def tearDown(self):
# oops here
if hasattr(self, 'dummyname'):
os.system("%s link del %s" % (IP_PATH, self.dummyname))
# FIXME: Links
if __name__ == '__main__':
unittest.main()
#!/usr/bin/env python
# vim:ts=4:sw=4:et:ai:sts=4
import nemu, nemu.environ, test_util
import os, signal, subprocess, sys, time
import unittest
class TestNode(unittest.TestCase):
@test_util.skipUnless(os.getuid() == 0, "Test requires root privileges")
def test_node(self):
node = nemu.Node()
self.failIfEqual(node.pid, os.getpid())
self.failIfEqual(node.pid, None)
# check if it really exists
os.kill(node.pid, 0)
nodes = nemu.get_nodes()
self.assertEquals(nodes, [node])
self.assertTrue(node.get_interface("lo").up)
@test_util.skip("Not implemented")
def test_detect_fork(self):
# Test that nemu recognises a fork
chld = os.fork()
if chld == 0:
if len(nemu.get_nodes()) == 0:
os._exit(0)
os._exit(1)
(pid, exitcode) = os.waitpid(chld, 0)
self.assertEquals(exitcode, 0, "Node does not recognise forks")
@test_util.skipUnless(os.getuid() == 0, "Test requires root privileges")
def test_cleanup(self):
def create_stuff():
a = nemu.Node()
b = nemu.Node()
ifa = a.add_if()
ifb = b.add_if()
switch = nemu.Switch()
switch.connect(ifa)
switch.connect(ifb)
# Test automatic destruction
orig_devs = len(test_util.get_devs())
create_stuff()
self.assertEquals(nemu.get_nodes(), [])
self.assertEquals(orig_devs, len(test_util.get_devs()))
# Test at_exit hooks
orig_devs = len(test_util.get_devs())
chld = os.fork()
if chld == 0:
# TODO: not implemented.
#nemu.set_cleanup_hooks(on_exit = True, on_signals = [])
create_stuff()
os._exit(0)
os.waitpid(chld, 0)
self.assertEquals(orig_devs, len(test_util.get_devs()))
# Test signal hooks
orig_devs = len(test_util.get_devs())
chld = os.fork()
if chld == 0:
# TODO: not implemented.
#nemu.set_cleanup_hooks(on_exit = False,
# on_signals = [signal.SIGTERM])
create_stuff()
while True:
time.sleep(10)
os.kill(chld, signal.SIGTERM)
os.waitpid(chld, 0)
self.assertEquals(orig_devs, len(test_util.get_devs()))
if __name__ == '__main__':
unittest.main()
#!/usr/bin/env python
# vim:ts=4:sw=4:et:ai:sts=4
import nemu.protocol
import os, socket, sys, threading, unittest
class TestServer(unittest.TestCase):
def test_server_startup(self):
# Test the creation of the server object with different ways of passing
# the file descriptor; and check the banner.
(s0, s1) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM, 0)
(s2, s3) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM, 0)
def test_help(fd):
fd.write("HELP\n")
# should be more than one line
self.assertEquals(fd.readline()[0:4], "200-")
while True:
l = fd.readline()
self.assertEquals(l[0:3], "200")
if l[3] == ' ':
break
def run_server():
srv = nemu.protocol.Server(s0, s0)
srv.run()
srv = nemu.protocol.Server(s2.fileno(), s2.fileno())
srv.run()
t = threading.Thread(target = run_server)
t.start()
s = os.fdopen(s1.fileno(), "r+", 1)
self.assertEquals(s.readline()[0:4], "220 ")
test_help(s)
s.close()
s0.close()
s = os.fdopen(s3.fileno(), "r+", 1)
self.assertEquals(s.readline()[0:4], "220 ")
test_help(s)
s.close()
s2.close()
t.join()
def test_server_clean(self):
(s0, s1) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM, 0)
def run_server():
nemu.protocol.Server(s0, s0).run()
t = threading.Thread(target = run_server)
t.start()
cli = nemu.protocol.Client(s1, s1)
null = file('/dev/null', 'wb')
argv = [ '/bin/sh', '-c', 'yes' ]
pid = cli.spawn(argv, stdout = null)
self.assertTrue(os.path.exists("/proc/%d" % pid))
# try to exit while there are still processes running
cli.shutdown()
t.join()
# Check that the process was killed.
# We are asumming that the pid is not going to be reused fast enough
# to generate a false possitive.
self.assertFalse(os.path.exists("/proc/%d" % pid))
def test_spawn_recovery(self):
(s0, s1) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM, 0)
def run_server():
nemu.protocol.Server(s0, s0).run()
t = threading.Thread(target = run_server)
t.start()
cli = nemu.protocol.Client(s1, s1)
# make PROC SIN fail
self.assertRaises(OSError, cli.spawn, "/bin/true", stdin = -1)
# check if the protocol is in a sane state:
# PROC CWD should not be valid
cli._send_cmd("PROC", "CWD", "/")
self.assertRaises(RuntimeError, cli._read_and_check_reply)
# Force a KeyError, and check that the exception is received correctly
cli._send_cmd("IF", "LIST", "-1")
self.assertRaises(KeyError, cli._read_and_check_reply)
cli.shutdown()
t.join()
def test_basic_stuff(self):
(s0, s1) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM, 0)
srv = nemu.protocol.Server(s0, s0)
s1 = s1.makefile("r+", 1)
def check_error(self, cmd, code = 500):
s1.write("%s\n" % cmd)
self.assertEquals(srv.readcmd(), None)
self.assertEquals(s1.readline()[0:4], "%d " % code)
def check_ok(self, cmd, func, args):
s1.write("%s\n" % cmd)
ccmd = " ".join(cmd.upper().split()[0:2])
if func == None:
self.assertEquals(srv.readcmd()[1:3], (ccmd, args))
else:
self.assertEquals(srv.readcmd(), (func, ccmd, args))
check_ok(self, "quit", srv.do_QUIT, [])
check_ok(self, " quit ", srv.do_QUIT, [])
# protocol error
check_error(self, "quit 1")
# Not allowed in normal mode
check_error(self, "proc user")
check_error(self, "proc sin")
check_error(self, "proc sout")
check_error(self, "proc serr")
check_error(self, "proc cwd")
check_error(self, "proc env")
check_error(self, "proc abrt")
check_error(self, "proc run")
check_ok(self, "if list", srv.do_IF_LIST, [])
check_ok(self, "if list 1", srv.do_IF_LIST, [1])
check_error(self, "proc poll") # missing arg
check_error(self, "proc poll 1 2") # too many args
check_error(self, "proc poll a") # invalid type
check_error(self, "proc") # incomplete command
check_error(self, "proc foo") # unknown subcommand
check_error(self, "foo bar") # unknown
check_ok(self, "proc crte /bin/sh", srv.do_PROC_CRTE,
['/bin/sh'])
# Commands that would fail, but the parsing is correct
check_ok(self, "proc poll 0", None, [0])
check_ok(self, "proc wait 0", None, [0])
check_ok(self, "proc kill 0", None, [0])
check_ok(self, "proc crte =", srv.do_PROC_CRTE, [""]) # empty b64
check_error(self, "proc crte =a") # invalid b64
# simulate proc mode
srv._commands = nemu.protocol._proc_commands
check_error(self, "proc crte foo")
check_error(self, "proc poll 0")
check_error(self, "proc wait 0")
check_error(self, "proc kill 0")
if __name__ == '__main__':
unittest.main()
#!/usr/bin/env python
# vim:ts=4:sw=4:et:ai:sts=4
import nemu, test_util
import os, unittest
class TestRouting(unittest.TestCase):
@test_util.skip("Programatic detection of duplicate routes not implemented")
def test_base_routing(self):
node = nemu.Node(nonetns = True)
routes = node.get_routes() # main netns routes!
if(len(routes)):
self.assertRaises(RuntimeError, node.add_route, routes[0])
routes[0].metric += 1 # should be enough to make it unique
self.assertRaises(RuntimeError, node.del_route, routes[0])
@test_util.skipUnless(os.getuid() == 0, "Test requires root privileges")
def test_routing(self):
node = nemu.Node()
self.assertEquals(len(node.get_routes()), 0)
if0 = node.add_if()
if0.add_v4_address('10.0.0.1', 24)
if0.up = True
routes = node.get_routes()
self.assertEquals(routes, [node.route(prefix = '10.0.0.0',
prefix_len = 24, interface = if0)])
node.add_route(nexthop = '10.0.0.2') # default route
node.add_route(prefix = '10.1.0.0', prefix_len = 16,
nexthop = '10.0.0.3')
node.add_route(prefix = '11.1.0.1', prefix_len = 32, interface = if0)
routes = node.get_routes()
self.assertTrue(node.route(nexthop = '10.0.0.2', interface = if0)
in routes)
self.assertTrue(node.route(prefix = '10.1.0.0', prefix_len = 16,
nexthop = '10.0.0.3', interface = if0) in routes)
self.assertTrue(node.route(prefix = '11.1.0.1', prefix_len = 32,
interface = if0) in routes)
node.del_route(nexthop = '10.0.0.2') # default route
node.del_route(prefix = '10.1.0.0', prefix_len = 16,
nexthop = '10.0.0.3')
node.del_route(prefix = '11.1.0.1', prefix_len = 32, interface = if0)
node.del_route(prefix = '10.0.0.0', prefix_len = 24, interface = if0)
self.assertEquals(node.get_routes(), [])
if __name__ == '__main__':
unittest.main()
#!/usr/bin/env python
# vim:ts=4:sw=4:et:ai:sts=4
import nemu, test_util
import nemu.subprocess_ as sp
import grp, os, pwd, signal, socket, sys, time, unittest
def _stat(path):
try:
return os.stat(user)
except:
return None
def _getpwnam(user):
try:
return pwd.getpwnam(user)
except:
return None
def _getpwuid(uid):
try:
return pwd.getpwuid(uid)
except:
return None
def _readall(fd):
s = ""
while True:
try:
s1 = os.read(fd, 4096)
except OSError, e:
if e.errno == errno.EINTR:
continue
else:
raise
if s1 == "":
break
s += s1
return s
_longstring = "Long string is long!\n" * 1000
class TestSubprocess(unittest.TestCase):
def _check_ownership(self, user, pid):
uid = pwd.getpwnam(user)[2]
gid = pwd.getpwnam(user)[3]
groups = [x[2] for x in grp.getgrall() if user in x[3]]
stat = open("/proc/%d/status" % pid)
while True:
data = stat.readline()
fields = data.split()
if fields[0] == 'Uid:':
self.assertEquals(fields[1:4], (uid,) * 4)
if fields[0] == 'Gid:':
self.assertEquals(fields[1:4], (gid,) * 4)
if fields[0] == 'Groups:':
self.assertEquals(set(fields[1:]), set(groups))
break
stat.close()
def setUp(self):
self.nouid = 65535
while _getpwuid(self.nouid):
self.nouid -= 1
self.nouser = 'foobar'
while _getpwnam(self.nouser):
self.nouser += '_'
self.nofile = '/foobar'
while _stat(self.nofile):
self.nofile += '_'
@test_util.skipUnless(os.getuid() == 0, "Test requires root privileges")
def test_spawn_chuser(self):
user = 'nobody'
pid = sp.spawn('/bin/sleep', ['/bin/sleep', '100'], user = user)
self._check_ownership(user, pid)
os.kill(pid, signal.SIGTERM)
self.assertEquals(sp.wait(pid), signal.SIGTERM)
@test_util.skipUnless(os.getuid() == 0, "Test requires root privileges")
def test_Subprocess_chuser(self):
node = nemu.Node(nonetns = True)
user = 'nobody'
p = node.Subprocess(['/bin/sleep', '1000'], user = user)
self._check_ownership(user, p.pid)
p.signal()
self.assertEquals(p.wait(), -signal.SIGTERM)
def test_spawn_basic(self):
# User does not exist
self.assertRaises(ValueError, sp.spawn,
'/bin/sleep', ['/bin/sleep', '1000'], user = self.nouser)
self.assertRaises(ValueError, sp.spawn,
'/bin/sleep', ['/bin/sleep', '1000'], user = self.nouid)
# Invalid CWD: it is a file
self.assertRaises(OSError, sp.spawn, '/bin/sleep', cwd = '/bin/sleep')
# Invalid CWD: does not exist
self.assertRaises(OSError, sp.spawn, '/bin/sleep', cwd = self.nofile)
# Exec failure
self.assertRaises(OSError, sp.spawn, self.nofile)
# Test that the environment is cleared: sleep should not be found
# XXX: This should be a python bug: if I don't set PATH explicitly, it
# uses a default search path
self.assertRaises(OSError, sp.spawn, 'sleep', env = {'PATH': ''})
r, w = os.pipe()
p = sp.spawn('/bin/echo', ['echo', 'hello world'], stdout = w)
os.close(w)
self.assertEquals(_readall(r), "hello world\n")
os.close(r)
# Check poll.
while True:
ret = sp.poll(p)
if ret is not None:
self.assertEquals(ret, 0)
break
time.sleep(0.2) # Wait a little bit.
# It cannot be wait()ed again.
self.assertRaises(OSError, sp.wait, p)
r0, w0 = os.pipe()
r1, w1 = os.pipe()
p = sp.spawn('/bin/cat', stdout = w0, stdin = r1, close_fds = [r0, w1])
os.close(w0)
os.close(r1)
self.assertEquals(sp.poll(p), None)
os.write(w1, "hello world\n")
os.close(w1)
self.assertEquals(_readall(r0), "hello world\n")
os.close(r0)
self.assertEquals(sp.wait(p), 0)
def test_Subprocess_basic(self):
node = nemu.Node(nonetns = True)
# User does not exist
self.assertRaises(ValueError, node.Subprocess,
['/bin/sleep', '1000'], user = self.nouser)
self.assertRaises(ValueError, node.Subprocess,
['/bin/sleep', '1000'], user = self.nouid)
# Invalid CWD: it is a file
self.assertRaises(OSError, node.Subprocess,
'/bin/sleep', cwd = '/bin/sleep')
# Invalid CWD: does not exist
self.assertRaises(OSError, node.Subprocess,
'/bin/sleep', cwd = self.nofile)
# Exec failure
self.assertRaises(OSError, node.Subprocess, self.nofile)
# Test that the environment is cleared: sleep should not be found
self.assertRaises(OSError, node.Subprocess,
'sleep', env = {'PATH': ''})
# Argv
self.assertRaises(OSError, node.Subprocess, 'true; false')
self.assertEquals(node.Subprocess('true').wait(), 0)
self.assertEquals(node.Subprocess('true; false', shell = True).wait(),
1)
# Piping
r, w = os.pipe()
p = node.Subprocess(['echo', 'hello world'], stdout = w)
os.close(w)
self.assertEquals(_readall(r), "hello world\n")
os.close(r)
p.wait()
# cwd
r, w = os.pipe()
p = node.Subprocess('/bin/pwd', stdout = w, cwd = "/")
os.close(w)
self.assertEquals(_readall(r), "/\n")
os.close(r)
p.wait()
p = node.Subprocess(['sleep', '100'])
self.assertTrue(p.pid > 0)
self.assertEquals(p.poll(), None) # not finished
p.signal()
p.signal() # verify no-op (otherwise there will be an exception)
self.assertEquals(p.wait(), -signal.SIGTERM)
self.assertEquals(p.wait(), -signal.SIGTERM) # no-op
self.assertEquals(p.poll(), -signal.SIGTERM) # no-op
# destroy
p = node.Subprocess(['sleep', '100'])
pid = p.pid
os.kill(pid, 0) # verify process still there
p.destroy()
self.assertRaises(OSError, os.kill, pid, 0) # should be dead by now
# forceful destroy
# Command: ignore SIGTERM, write \n to synchronise and then sleep while
# closing stdout (so _readall finishes)
cmd = 'trap "" TERM; echo; exec sleep 100 > /dev/null'
r, w = os.pipe()
p = node.Subprocess(cmd, shell = True, stdout = w)
os.close(w)
self.assertEquals(_readall(r), "\n") # wait for trap to be installed
os.close(r)
pid = p.pid
os.kill(pid, 0) # verify process still there
# Avoid the warning about the process being killed
orig_stderr = sys.stderr
sys.stderr = open("/dev/null", "w")
p.destroy()
sys.stderr = orig_stderr
self.assertRaises(OSError, os.kill, pid, 0) # should be dead by now
p = node.Subprocess(['sleep', '100'])
os.kill(p.pid, signal.SIGTERM)
time.sleep(0.2)
p.signal() # since it has not been waited for, it should not raise
self.assertEquals(p.wait(), -signal.SIGTERM)
def test_Popen(self):
node = nemu.Node(nonetns = True)
# repeat test with Popen interface
r0, w0 = os.pipe()
r1, w1 = os.pipe()
p = node.Popen('cat', stdout = w0, stdin = r1)
os.close(w0)
os.close(r1)
os.write(w1, "hello world\n")
os.close(w1)
self.assertEquals(_readall(r0), "hello world\n")
os.close(r0)
# now with a socketpair, not using integers
(s0, s1) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM, 0)
p = node.Popen('cat', stdout = s0, stdin = s0)
s0.close()
s1.send("hello world\n")
self.assertEquals(s1.recv(512), "hello world\n")
s1.close()
# pipes
p = node.Popen('cat', stdin = sp.PIPE, stdout = sp.PIPE)
p.stdin.write("hello world\n")
p.stdin.close()
self.assertEquals(p.stdout.readlines(), ["hello world\n"])
self.assertEquals(p.stderr, None)
self.assertEquals(p.wait(), 0)
p = node.Popen('cat', stdin = sp.PIPE, stdout = sp.PIPE)
self.assertEquals(p.communicate(_longstring), (_longstring, None))
p = node.Popen('cat', stdin = sp.PIPE, stdout = sp.PIPE)
p.stdin.write(_longstring)
self.assertEquals(p.communicate(), (_longstring, None))
p = node.Popen('cat', stdin = sp.PIPE)
self.assertEquals(p.communicate(), (None, None))
p = node.Popen('cat >&2', shell = True, stdin = sp.PIPE,
stderr = sp.PIPE)
p.stdin.write("hello world\n")
p.stdin.close()
self.assertEquals(p.stderr.readlines(), ["hello world\n"])
self.assertEquals(p.stdout, None)
self.assertEquals(p.wait(), 0)
p = node.Popen(['sh', '-c', 'cat >&2'], stdin = sp.PIPE,
stderr = sp.PIPE)
self.assertEquals(p.communicate(_longstring), (None, _longstring))
#
p = node.Popen(['sh', '-c', 'cat >&2'],
stdin = sp.PIPE, stdout = sp.PIPE, stderr = sp.STDOUT)
p.stdin.write("hello world\n")
p.stdin.close()
self.assertEquals(p.stdout.readlines(), ["hello world\n"])
self.assertEquals(p.stderr, None)
self.assertEquals(p.wait(), 0)
p = node.Popen(['sh', '-c', 'cat >&2'],
stdin = sp.PIPE, stdout = sp.PIPE, stderr = sp.STDOUT)
self.assertEquals(p.communicate(_longstring), (_longstring, None))
#
p = node.Popen(['tee', '/dev/stderr'],
stdin = sp.PIPE, stdout = sp.PIPE, stderr = sp.STDOUT)
p.stdin.write("hello world\n")
p.stdin.close()
self.assertEquals(p.stdout.readlines(), ["hello world\n"] * 2)
self.assertEquals(p.stderr, None)
self.assertEquals(p.wait(), 0)
p = node.Popen(['tee', '/dev/stderr'],
stdin = sp.PIPE, stdout = sp.PIPE, stderr = sp.STDOUT)
self.assertEquals(p.communicate(_longstring[0:512]),
(_longstring[0:512] * 2, None))
#
p = node.Popen(['tee', '/dev/stderr'],
stdin = sp.PIPE, stdout = sp.PIPE, stderr = sp.PIPE)
p.stdin.write("hello world\n")
p.stdin.close()
self.assertEquals(p.stdout.readlines(), ["hello world\n"])
self.assertEquals(p.stderr.readlines(), ["hello world\n"])
self.assertEquals(p.wait(), 0)
p = node.Popen(['tee', '/dev/stderr'],
stdin = sp.PIPE, stdout = sp.PIPE, stderr = sp.PIPE)
self.assertEquals(p.communicate(_longstring), (_longstring, ) * 2)
def test_backticks(self):
node = nemu.Node(nonetns = True)
self.assertEquals(node.backticks("echo hello world"), "hello world\n")
self.assertEquals(node.backticks(r"echo hello\ \ world"),
"hello world\n")
self.assertEquals(node.backticks(["echo", "hello", "world"]),
"hello world\n")
self.assertEquals(node.backticks("echo hello world > /dev/null"), "")
self.assertEquals(node.backticks_raise("true"), "")
self.assertRaises(RuntimeError, node.backticks_raise, "false")
self.assertRaises(RuntimeError, node.backticks_raise, "kill $$")
def test_system(self):
node = nemu.Node(nonetns = True)
self.assertEquals(node.system("true"), 0)
self.assertEquals(node.system("false"), 1)
if __name__ == '__main__':
unittest.main()
#!/usr/bin/env python
# vim:ts=4:sw=4:et:ai:sts=4
import os, unittest
import nemu, test_util, nemu.environ
class TestSwitch(unittest.TestCase):
@test_util.skipUnless(os.getuid() == 0, "Test requires root privileges")
def setUp(self):
n1 = nemu.Node()
n2 = nemu.Node()
i1 = n1.add_if()
i2 = n2.add_if()
l = nemu.Switch()
l.connect(i1)
l.connect(i2)
self.stuff = (n1, n2, i1, i2, l)
@test_util.skipUnless(os.getuid() == 0, "Test requires root privileges")
def test_switch_base(self):
(n1, n2, i1, i2, l) = self.stuff
l.mtu = 3000
ifdata = nemu.iproute.get_if_data()[0]
self.assertEquals(ifdata[l.index].mtu, 3000)
self.assertEquals(ifdata[i1.control.index].mtu, 3000,
"MTU propagation")
self.assertEquals(ifdata[i2.control.index].mtu, 3000,
"MTU propagation")
i1.mtu = i2.mtu = 3000
self.assertEquals(ifdata[l.index].up, False)
self.assertEquals(ifdata[i1.control.index].up, False,
"UP propagation")
self.assertEquals(ifdata[i2.control.index].up, False,
"UP propagation")
l.up = True
ifdata = nemu.iproute.get_if_data()[0]
self.assertEquals(ifdata[i1.control.index].up, True, "UP propagation")
self.assertEquals(ifdata[i2.control.index].up, True, "UP propagation")
tcdata = nemu.iproute.get_tc_data()[0]
self.assertEquals(tcdata[i1.control.index], {"qdiscs": {}})
self.assertEquals(tcdata[i2.control.index], {"qdiscs": {}})
@test_util.skipUnless(os.getuid() == 0, "Test requires root privileges")
def test_switch_changes(self):
(n1, n2, i1, i2, l) = self.stuff
# Test strange rules handling
os.system(("%s qd add dev %s root prio bands 3 " +
"priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1") %
(nemu.environ.TC_PATH, i1.control.name))
tcdata = nemu.iproute.get_tc_data()[0]
self.assertEquals(tcdata[i1.control.index], "foreign")
l.set_parameters(bandwidth = 13107200) # 100 mbits
tcdata = nemu.iproute.get_tc_data()[0]
self.assertEquals(tcdata[i1.control.index],
{"bandwidth": 13107000, "qdiscs": {"tbf": "1"}})
# Test tc replacements
self._test_tbf() # none => tbf
self._test_both() # tbf => both
self._test_netem() # both => netem
self._test_tbf() # netem => tbf
self._test_netem() # tbf => netem
self._test_none() # netem => none
self._test_netem() # none => netem
self._test_both() # netem => both
self._test_tbf() # both => tbf
self._test_none() # tbf => none
self._test_both() # none => both
self._test_none() # both => none
def _test_none(self):
(n1, n2, i1, i2, l) = self.stuff
l.set_parameters()
tcdata = nemu.iproute.get_tc_data()[0]
self.assertEquals(tcdata[i1.control.index], {"qdiscs": {}})
self.assertEquals(tcdata[i2.control.index], {"qdiscs": {}})
def _test_tbf(self):
(n1, n2, i1, i2, l) = self.stuff
l.set_parameters(bandwidth = 13107200) # 100 mbits
tcdata = nemu.iproute.get_tc_data()[0]
self.assertEquals(tcdata[i1.control.index],
# adjust for tc rounding
{"bandwidth": 13107000, "qdiscs": {"tbf": "1"}})
self.assertEquals(tcdata[i2.control.index],
{"bandwidth": 13107000, "qdiscs": {"tbf": "1"}})
def _test_netem(self):
(n1, n2, i1, i2, l) = self.stuff
l.set_parameters(delay = 0.001) # 1ms
tcdata = nemu.iproute.get_tc_data()[0]
self.assertEquals(tcdata[i1.control.index],
{"delay": 0.001, "qdiscs": {"netem": "2"}})
self.assertEquals(tcdata[i2.control.index],
{"delay": 0.001, "qdiscs": {"netem": "2"}})
def _test_both(self):
(n1, n2, i1, i2, l) = self.stuff
l.set_parameters(bandwidth = 13107200, delay = 0.001) # 100 mbits, 1ms
tcdata = nemu.iproute.get_tc_data()[0]
self.assertEquals(tcdata[i1.control.index],
{"bandwidth": 13107000, "delay": 0.001,
"qdiscs": {"tbf": "1", "netem": "2"}})
self.assertEquals(tcdata[i2.control.index],
{"bandwidth": 13107000, "delay": 0.001,
"qdiscs": {"tbf": "1", "netem": "2"}})
if __name__ == "__main__":
unittest.main()
#!/usr/bin/env python
# vim:ts=4:sw=4:et:ai:sts=4
import os, re, subprocess, sys
import nemu.subprocess_
from nemu.environ import *
def process_ipcmd(str):
cur = None
out = {}
for line in str.split("\n"):
if line == "":
cur = None
continue
match = re.search(r'^(\d+): (\S+): <(\S+)> mtu (\d+) qdisc (\S+)',
line)
if match != None:
cur = match.group(2)
out[cur] = {
'idx': int(match.group(1)),
'flags': match.group(3).split(","),
'mtu': int(match.group(4)),
'qdisc': match.group(5),
'addr': []
}
out[cur]['up'] = 'UP' in out[cur]['flags']
continue
# Assume cur is defined
assert cur != None
match = re.search(r'^\s+link/\S*(?: ([0-9a-f:]+))?(?: |$)', line)
if match != None:
out[cur]['lladdr'] = match.group(1)
continue
match = re.search(r'^\s+inet ([0-9.]+)/(\d+)(?: brd ([0-9.]+))?', line)
if match != None:
out[cur]['addr'].append({
'address': match.group(1),
'prefix_len': int(match.group(2)),
'broadcast': match.group(3),
'family': 'inet'})
continue
match = re.search(r'^\s+inet6 ([0-9a-f:]+)/(\d+)(?: |$)', line)
if match != None:
out[cur]['addr'].append({
'address': match.group(1),
'prefix_len': int(match.group(2)),
'family': 'inet6'})
continue
match = re.search(r'^\s{4}', line)
assert match != None
return out
def get_devs():
outdata = backticks([IP_PATH, "addr", "list"])
return process_ipcmd(outdata)
def get_devs_netns(node):
out = nemu.subprocess_.backticks_raise(node, [IP_PATH, "addr", "list"])
return process_ipcmd(out)
def make_linux_ver(string):
match = re.match(r'(\d+)\.(\d+)(?:\.(\d+))?(.*)', string)
version, patchlevel, sublevel, extraversion = match.groups()
if not sublevel:
sublevel = 0
return (int(version) << 16) + (int(patchlevel) << 8) + int(sublevel)
def get_linux_ver():
return make_linux_ver(os.uname()[2])
# Unittest from Python 2.6 doesn't have these decorators
def _bannerwrap(f, text):
name = f.__name__
def banner(*args, **kwargs):
sys.stderr.write("*** WARNING: Skipping test %s: `%s'\n" %
(name, text))
return None
return banner
def skip(text):
return lambda f: _bannerwrap(f, text)
def skipUnless(cond, text):
return (lambda f: _bannerwrap(f, text)) if not cond else lambda f: f
def skipIf(cond, text):
return (lambda f: _bannerwrap(f, text)) if cond else lambda f: f
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment