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

Merge branch 'import'

parents 71fcf2ab bbf51768
This diff is collapsed.
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
This diff is collapsed.
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
This diff is collapsed.
This diff is collapsed.
# 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
This diff is collapsed.
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}
This diff is collapsed.
#!/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
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
#!/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()
This diff is collapsed.
#!/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()
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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