Compare commits
490
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f15744cc9 | ||
|
|
a0497d8c38 | ||
|
|
2e4ef57cf0 | ||
|
|
99222e70f7 | ||
|
|
5562e66a2d | ||
|
|
28f46c8d2d | ||
|
|
8d1444a914 | ||
|
|
7e79d1351c | ||
|
|
c120e37897 | ||
|
|
d990eb0717 | ||
|
|
d2f9aea273 | ||
|
|
0bfd19fe83 | ||
|
|
5cc808286a | ||
|
|
2668484163 | ||
|
|
7596de5903 | ||
|
|
5675f2a142 | ||
|
|
c2e1451997 | ||
|
|
4e13f615aa | ||
|
|
37e1eafdbb | ||
|
|
2b03a115d2 | ||
|
|
85cc906f14 | ||
|
|
d00fa51f4c | ||
|
|
518c75b548 | ||
|
|
ad2fda2522 | ||
|
|
1dcc047624 | ||
|
|
1ee6fad705 | ||
|
|
219f9de778 | ||
|
|
54d320ce9f | ||
|
|
996098081e | ||
|
|
8a130deae5 | ||
|
|
1d67218d1f | ||
|
|
76521ef1d4 | ||
|
|
7e643d36c0 | ||
|
|
11782ae0de | ||
|
|
24baea734f | ||
|
|
87737a7064 | ||
|
|
4876b43fe9 | ||
|
|
57fd19efaa | ||
|
|
e30f2c99c2 | ||
|
|
55ca2d0c96 | ||
|
|
74a56e2bae | ||
|
|
444f9d8d76 | ||
|
|
300efb2b80 | ||
|
|
e0e69bbeec | ||
|
|
276bdaf0d3 | ||
|
|
cde0c25bfe | ||
|
|
73a098a499 | ||
|
|
eaf5888ae0 | ||
|
|
2a75022403 | ||
|
|
fe769afa22 | ||
|
|
5c24e18639 | ||
|
|
cc960215fc | ||
|
|
ec226c9b83 | ||
|
|
ee7ee4065c | ||
|
|
40beaabc14 | ||
|
|
e900a16caf | ||
|
|
05c2d669a9 | ||
|
|
cc16d2ce29 | ||
|
|
4800435d1a | ||
|
|
f9654e5654 | ||
|
|
a77e7a029e | ||
|
|
bd507eecfa | ||
|
|
9802686b32 | ||
|
|
4d1c17e17e | ||
|
|
9da63d4e85 | ||
|
|
0a9358c9fc | ||
|
|
b480b6ef77 | ||
|
|
ccca871ae6 | ||
|
|
2d48b4633c | ||
|
|
f6149e4d18 | ||
|
|
6555c9294b | ||
|
|
4c85d6d3ec | ||
|
|
8e5892e2fd | ||
|
|
59d326295f | ||
|
|
dd2a2442c4 | ||
|
|
7268f6943c | ||
|
|
a0ca68b6b8 | ||
|
|
cf9c968756 | ||
|
|
25f64a39a0 | ||
|
|
57a0bb2f95 | ||
|
|
01eea3503b | ||
|
|
85b69adbf8 | ||
|
|
52c700ff0c | ||
|
|
5d4d630580 | ||
|
|
455194a3b8 | ||
|
|
0533e1cc1e | ||
|
|
b13a2c17c5 | ||
|
|
2f40c85691 | ||
|
|
ab312967fe | ||
|
|
2c07d62b3c | ||
|
|
e3a1fbb047 | ||
|
|
9de7873bc8 | ||
|
|
b7097daad4 | ||
|
|
52082ff3f9 | ||
|
|
b78c938201 | ||
|
|
dde9c91dcf | ||
|
|
9d0dbe488a | ||
|
|
6d2416ecbe | ||
|
|
f00101716b | ||
|
|
b47cdfea26 | ||
|
|
f95aebb4f9 | ||
|
|
d869d82027 | ||
|
|
54d026f656 | ||
|
|
68c89df8a1 | ||
|
|
dd159b4a73 | ||
|
|
299925c22f | ||
|
|
7d83d46270 | ||
|
|
2fffa0bb6f | ||
|
|
0a6e54231e | ||
|
|
6b2a3b187b | ||
|
|
e282b6e29f | ||
|
|
7abca147c4 | ||
|
|
71ba12c539 | ||
|
|
b97415eed4 | ||
|
|
9f8c25515f | ||
|
|
011b02f886 | ||
|
|
fa8693480a | ||
|
|
fc75a1b185 | ||
|
|
6e9074f89e | ||
|
|
6aff964ea0 | ||
|
|
57aae3e103 | ||
|
|
60a39a7241 | ||
|
|
1a52deeea6 | ||
|
|
956546acc7 | ||
|
|
e9d4482e42 | ||
|
|
d26abda426 | ||
|
|
81d27b3c56 | ||
|
|
72ddde869c | ||
|
|
cd19cf520f | ||
|
|
a434c3d1b6 | ||
|
|
6aa7f3493b | ||
|
|
32b122bf42 | ||
|
|
272d496dfe | ||
|
|
a6661f0ac1 | ||
|
|
68f5925ed6 | ||
|
|
3b48e82990 | ||
|
|
c26875cb55 | ||
|
|
e3f6ecca97 | ||
|
|
82b7207295 | ||
|
|
259d713315 | ||
|
|
c79882b766 | ||
|
|
fa20913b56 | ||
|
|
271013e0dc | ||
|
|
6c5cc2bfee | ||
|
|
83e7ea2a0d | ||
|
|
6ef2973fe2 | ||
|
|
e55cbef182 | ||
|
|
3758da2ec8 | ||
|
|
121eb4496b | ||
|
|
8bc0037938 | ||
|
|
54dfb2434a | ||
|
|
b145865407 | ||
|
|
83332c1a42 | ||
|
|
d2ff558da7 | ||
|
|
34954fe671 | ||
|
|
ab1fb093d1 | ||
|
|
2a554ae33c | ||
|
|
caf024bc98 | ||
|
|
e555f83c97 | ||
|
|
c16679863b | ||
|
|
f800e5124d | ||
|
|
f24e70a435 | ||
|
|
3482b4467c | ||
|
|
e7ba6b9ebb | ||
|
|
bbe5f8a389 | ||
|
|
fce4d59dc1 | ||
|
|
f583900da9 | ||
|
|
68f97b74ad | ||
|
|
9ab2cba870 | ||
|
|
99c035d9ca | ||
|
|
6c82d1bae7 | ||
|
|
03e5965236 | ||
|
|
c3a583c813 | ||
|
|
e123afea97 | ||
|
|
7ad4829203 | ||
|
|
a650b8e657 | ||
|
|
a4db2c2fca | ||
|
|
4720f559c3 | ||
|
|
56278434a8 | ||
|
|
4a67fc9a4a | ||
|
|
257a2f636b | ||
|
|
c6e7eaf07c | ||
|
|
cd7457480b | ||
|
|
5fb3d5426c | ||
|
|
724f1144ac | ||
|
|
8e4d818a60 | ||
|
|
d0e53ca863 | ||
|
|
f89015971f | ||
|
|
388f54e148 | ||
|
|
ff6145c015 | ||
|
|
3e6b3dd273 | ||
|
|
c58bb42f57 | ||
|
|
afe0ce81a5 | ||
|
|
e10abba08d | ||
|
|
b5672f1565 | ||
|
|
de901ef058 | ||
|
|
b1affce867 | ||
|
|
8bbd368576 | ||
|
|
ce67093a62 | ||
|
|
15b482e375 | ||
|
|
e540ab5726 | ||
|
|
ce9cd5bee5 | ||
|
|
b14b1ee441 | ||
|
|
06b8210ea1 | ||
|
|
f2dee12d93 | ||
|
|
5eac2a8403 | ||
|
|
5791f32a2d | ||
|
|
ffd089ea1c | ||
|
|
8eae25501c | ||
|
|
9a17de9166 | ||
|
|
74cd084315 | ||
|
|
a0c97d110c | ||
|
|
8125370d8e | ||
|
|
add0ee6059 | ||
|
|
e21aa14fac | ||
|
|
3f02f15e47 | ||
|
|
1e4f337584 | ||
|
|
f5191b855f | ||
|
|
f32a546855 | ||
|
|
e4370f7ee7 | ||
|
|
c9ae0ab003 | ||
|
|
fc48e6ee7c | ||
|
|
9a7717ed9d | ||
|
|
c532689266 | ||
|
|
d82b6183d6 | ||
|
|
a9aebba7f7 | ||
|
|
9098528a16 | ||
|
|
55c335849a | ||
|
|
4779fa84f9 | ||
|
|
0b1832ddc2 | ||
|
|
e91b2815fa | ||
|
|
c2cc1aacf6 | ||
|
|
41badb9686 | ||
|
|
45e82d0952 | ||
|
|
d93c6ef6ce | ||
|
|
fe414722ac | ||
|
|
8423875b37 | ||
|
|
47acf53974 | ||
|
|
67516aa44f | ||
|
|
6e4e79afc3 | ||
|
|
22f807fc6f | ||
|
|
b2ef87ae51 | ||
|
|
0774c8bbce | ||
|
|
cf6f67045b | ||
|
|
353411422e | ||
|
|
c70aab0a38 | ||
|
|
fb2f652319 | ||
|
|
178a3d8410 | ||
|
|
c8fbd4465c | ||
|
|
f2eaeac78c | ||
|
|
6ab417ccd7 | ||
|
|
3e624fb237 | ||
|
|
8abc3472e3 | ||
|
|
ec7b211c41 | ||
|
|
6ab1f1ff23 | ||
|
|
736038f8ef | ||
|
|
e4c82e526e | ||
|
|
a3d899123d | ||
|
|
b055728fd5 | ||
|
|
a165881049 | ||
|
|
e01292c167 | ||
|
|
956bf4f2d0 | ||
|
|
e100d1d9d0 | ||
|
|
ad824e4e84 | ||
|
|
3fac5a43d7 | ||
|
|
07aad11081 | ||
|
|
28cd95c374 | ||
|
|
af380c8912 | ||
|
|
8d3c2859e4 | ||
|
|
e7c787b343 | ||
|
|
cae423e543 | ||
|
|
7f9eb9e540 | ||
|
|
3465c9ea64 | ||
|
|
ed097b3b9c | ||
|
|
bca38c997e | ||
|
|
0e2a7240c3 | ||
|
|
51a1482407 | ||
|
|
0e3fef2fb3 | ||
|
|
bcacfc0510 | ||
|
|
c4ae423238 | ||
|
|
d4b53ea73d | ||
|
|
e953444fa5 | ||
|
|
74993b6536 | ||
|
|
7b323bd4f4 | ||
|
|
7cbde77496 | ||
|
|
08ed5bb68a | ||
|
|
9e7aee2061 | ||
|
|
740d7ce39a | ||
|
|
4065511abb | ||
|
|
2ad03eea40 | ||
|
|
406f5da19d | ||
|
|
5947e017fd | ||
|
|
44c63c2abd | ||
|
|
77529ce550 | ||
|
|
c30e500731 | ||
|
|
b403cc7f96 | ||
|
|
3b1174eff6 | ||
|
|
c46c1ae6ef | ||
|
|
9dbe572abe | ||
|
|
9f32a08b28 | ||
|
|
47abe6d950 | ||
|
|
3a7d2a133e | ||
|
|
b0a7a2579a | ||
|
|
4e57ddbce1 | ||
|
|
8f30aa0017 | ||
|
|
f7f2f977d5 | ||
|
|
188964c911 | ||
|
|
e3a2ef011f | ||
|
|
d44a5843d3 | ||
|
|
47dbca2921 | ||
|
|
1a9d2eac29 | ||
|
|
73a323f2a2 | ||
|
|
315cbf9e79 | ||
|
|
7ecd095c45 | ||
|
|
7ba9e05542 | ||
|
|
80be564274 | ||
|
|
2626693241 | ||
|
|
a16fae676f | ||
|
|
91b9e374a0 | ||
|
|
be96355468 | ||
|
|
b924c5b562 | ||
|
|
73360ab95c | ||
|
|
5156b3894c | ||
|
|
11a6d400c5 | ||
|
|
fd21c46f64 | ||
|
|
c80e18cd46 | ||
|
|
1bda2d21ad | ||
|
|
6f45478f18 | ||
|
|
75d72d960b | ||
|
|
cdeaca8682 | ||
|
|
dfc08a869e | ||
|
|
c8641d7d15 | ||
|
|
7ae73e677f | ||
|
|
f259e2fa1c | ||
|
|
509a852f18 | ||
|
|
3eb5abe6a7 | ||
|
|
4e69ae83dd | ||
|
|
bb941950d5 | ||
|
|
1a40cd0477 | ||
|
|
c246e2d55e | ||
|
|
95f6e7b728 | ||
|
|
47e26cce19 | ||
|
|
086ef80e8e | ||
|
|
48a8d297d1 | ||
|
|
ddfbfc734c | ||
|
|
dc630c540a | ||
|
|
efc9a01cf1 | ||
|
|
04fa18dc61 | ||
|
|
a681f9b645 | ||
|
|
54977c79cd | ||
|
|
1fdcd6768c | ||
|
|
137ec3054d | ||
|
|
63214ea2af | ||
|
|
e0cfcdd505 | ||
|
|
2235f21698 | ||
|
|
c3a4440025 | ||
|
|
5468377680 | ||
|
|
dba3b59929 | ||
|
|
78073e1b6f | ||
|
|
d40b0a9973 | ||
|
|
235415cf49 | ||
|
|
019bff8282 | ||
|
|
28ab4a33d6 | ||
|
|
4f4f1dd2fa | ||
|
|
8ff7ee4297 | ||
|
|
4e94bac0ae | ||
|
|
fe29f64dd3 | ||
|
|
2b083dd191 | ||
|
|
777c90236e | ||
|
|
8d8ac76e85 | ||
|
|
3e021c2579 | ||
|
|
31b43002e7 | ||
|
|
64c451e01f | ||
|
|
e34cd9a6cf | ||
|
|
f52edfc545 | ||
|
|
befa1310df | ||
|
|
3774f234f0 | ||
|
|
114dcd5653 | ||
|
|
83097ff942 | ||
|
|
6d72a3cb0c | ||
|
|
d5886525cc | ||
|
|
f8952d56e1 | ||
|
|
bd1e997f15 | ||
|
|
8895862acf | ||
|
|
7c371cf32a | ||
|
|
e85f8bdf29 | ||
|
|
7d4b7b7f47 | ||
|
|
496b5f9ed0 | ||
|
|
281f6e59db | ||
|
|
80a8fa62d5 | ||
|
|
bebe9dbed2 | ||
|
|
46fa564a8a | ||
|
|
0df8b19568 | ||
|
|
ac9554d6dc | ||
|
|
799242c617 | ||
|
|
8f99421e02 | ||
|
|
8e8081c408 | ||
|
|
3031d31efe | ||
|
|
1b6a9c3a20 | ||
|
|
f7c2df2513 | ||
|
|
723d068c51 | ||
|
|
ca58c896dd | ||
|
|
d856c81896 | ||
|
|
6d2cd77b5a | ||
|
|
80b3dbbd48 | ||
|
|
74ef761529 | ||
|
|
ee00736307 | ||
|
|
60d9ead65a | ||
|
|
540379957b | ||
|
|
376bcba442 | ||
|
|
433a7cc88c | ||
|
|
c98514ae44 | ||
|
|
ac65ea3ff3 | ||
|
|
fcf6a16f3d | ||
|
|
0cd489a78b | ||
|
|
8a034f4f6c | ||
|
|
1bb4412ff3 | ||
|
|
16c57ddb33 | ||
|
|
4804237fa7 | ||
|
|
eeb9cb3c2b | ||
|
|
83086439cc | ||
|
|
8f20b95d56 | ||
|
|
a6b473227f | ||
|
|
8b5062a3a1 | ||
|
|
e3621eb057 | ||
|
|
89bf31030b | ||
|
|
4d2d52c3db | ||
|
|
220890a0c9 | ||
|
|
bc547080b8 | ||
|
|
75810224ed | ||
|
|
8e63d3cb72 | ||
|
|
fabbac885f | ||
|
|
2e52801de5 | ||
|
|
b426d24e45 | ||
|
|
b6423f8c4d | ||
|
|
15f37cc292 | ||
|
|
7b804ffbae | ||
|
|
1095628b8a | ||
|
|
7c1d7c9f90 | ||
|
|
345bf7cc29 | ||
|
|
ff43615ab1 | ||
|
|
fd99d67c33 | ||
|
|
51270ce4a2 | ||
|
|
cd27f9db57 | ||
|
|
ede34b6a4d | ||
|
|
40580731cd | ||
|
|
9bb15c76e9 | ||
|
|
b7640209a8 | ||
|
|
1417fe7283 | ||
|
|
7df36e476e | ||
|
|
f939eb5625 | ||
|
|
0b4ae53a7e | ||
|
|
e04f092232 | ||
|
|
05cce994c3 | ||
|
|
0a9ea29fd2 | ||
|
|
696a619d0e | ||
|
|
ea420ee29f | ||
|
|
2708cadd40 | ||
|
|
657333199b | ||
|
|
6a30c394b1 | ||
|
|
93519c04f9 | ||
|
|
9dbb68df9e | ||
|
|
77131e8f24 | ||
|
|
c53fa3ba32 | ||
|
|
0b084dd51a | ||
|
|
2f53491343 | ||
|
|
845dedca32 | ||
|
|
0608b5939b | ||
|
|
ea7c8795f5 | ||
|
|
ac75d7cff4 | ||
|
|
fb7658c8ca | ||
|
|
98332cb616 | ||
|
|
433503cf8a | ||
|
|
55dd936829 | ||
|
|
08cef003f7 | ||
|
|
f4d9e05d05 | ||
|
|
2a0cad3c85 | ||
|
|
b3dd44d41b | ||
|
|
3da2b1482c | ||
|
|
d8ef79dff7 | ||
|
|
af8f0b61b6 | ||
|
|
42ba5d92c3 | ||
|
|
748e35d50f | ||
|
|
4ccc7ee941 | ||
|
|
95d9a374b2 | ||
|
|
994c68f648 | ||
|
|
9011e0d22c | ||
|
|
6bd22292b6 | ||
|
|
eddef947a4 | ||
|
|
98d4f1891c |
No files matched your search
@@ -1 +0,0 @@
|
||||
Dockerfile
|
||||
@@ -1,44 +0,0 @@
|
||||
name: Publish Docker image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Login to ${{ env.REGISTRY }}
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
pull: true
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
+4
-19
@@ -1,21 +1,6 @@
|
||||
# Python
|
||||
mnexec
|
||||
*.pyc
|
||||
*~
|
||||
mininet.egg-info
|
||||
build
|
||||
dist
|
||||
Mini_NDN.egg-info
|
||||
*.pyc
|
||||
|
||||
# Docs
|
||||
docs/html
|
||||
docs/latex
|
||||
docs/_build
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
dl
|
||||
*.apconf
|
||||
|
||||
# Vagrant
|
||||
.vagrant
|
||||
|
||||
venv/
|
||||
.vscode
|
||||
@@ -0,0 +1,309 @@
|
||||
# lint Python modules using external checkers.
|
||||
#
|
||||
# This is the main checker controlling the other ones and the reports
|
||||
# generation. It is itself both a raw checker and an astng checker in order
|
||||
# to:
|
||||
# * handle message activation / deactivation at the module level
|
||||
# * handle some basic but necessary stats'data (number of classes, methods...)
|
||||
#
|
||||
[MASTER]
|
||||
|
||||
# Specify a configuration file.
|
||||
#rcfile=
|
||||
|
||||
# Python code to execute, usually for sys.path manipulation such as
|
||||
# pygtk.require().
|
||||
#init-hook=
|
||||
|
||||
# Profiled execution.
|
||||
profile=no
|
||||
|
||||
# Add <file or directory> to the black list. It should be a base name, not a
|
||||
# path. You may set this option multiple times.
|
||||
ignore=CVS
|
||||
|
||||
# Pickle collected data for later comparisons.
|
||||
persistent=yes
|
||||
|
||||
# Set the cache size for astng objects.
|
||||
cache-size=500
|
||||
|
||||
# List of plugins (as comma separated values of python modules names) to load,
|
||||
# usually to register additional checkers.
|
||||
load-plugins=
|
||||
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
|
||||
# Enable only checker(s) with the given id(s). This option conflicts with the
|
||||
# disable-checker option
|
||||
#enable-checker=
|
||||
|
||||
# Enable all checker(s) except those with the given id(s). This option
|
||||
# conflicts with the enable-checker option
|
||||
#disable-checker=
|
||||
|
||||
# Enable all messages in the listed categories (IRCWEF).
|
||||
#enable-msg-cat=
|
||||
|
||||
# Disable all messages in the listed categories (IRCWEF).
|
||||
disable-msg-cat=IR
|
||||
|
||||
# Enable the message(s) with the given id(s).
|
||||
#enable-msg=
|
||||
|
||||
# Disable the message(s) with the given id(s).
|
||||
disable=W0704,C0103,W0231,E1102,W0511,W0142,R0902,R0903,R0904,R0913,R0914,R0801,I0011
|
||||
|
||||
|
||||
[REPORTS]
|
||||
|
||||
# Set the output format. Available formats are text, parseable, colorized, msvs
|
||||
# (visual studio) and html
|
||||
output-format=text
|
||||
|
||||
# Include message's id in outpu
|
||||
include-ids=yes
|
||||
|
||||
# Put messages in a separate file for each module / package specified on the
|
||||
# command line instead of printing them on stdout. Reports (if any) will be
|
||||
# written in a file name "pylint_global.[txt|html]".
|
||||
files-output=no
|
||||
|
||||
# Tells wether to display a full report or only the messages
|
||||
reports=no
|
||||
|
||||
# Python expression which should return a note less than 10 (10 is the highes
|
||||
# note). You have access to the variables errors warning, statement which
|
||||
# respectivly contain the number of errors / warnings messages and the total
|
||||
# number of statements analyzed. This is used by the global evaluation repor
|
||||
# (R0004).
|
||||
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
|
||||
|
||||
# Add a comment according to your evaluation note. This is used by the global
|
||||
# evaluation report (R0004).
|
||||
comment=no
|
||||
|
||||
# Enable the report(s) with the given id(s).
|
||||
#enable-report=
|
||||
|
||||
# Disable the report(s) with the given id(s).
|
||||
#disable-report=
|
||||
|
||||
|
||||
# checks for :
|
||||
# * doc strings
|
||||
# * modules / classes / functions / methods / arguments / variables name
|
||||
# * number of arguments, local variables, branchs, returns and statements in
|
||||
# functions, methods
|
||||
# * required module attributes
|
||||
# * dangerous default values as arguments
|
||||
# * redefinition of function / method / class
|
||||
# * uses of the global statemen
|
||||
#
|
||||
[BASIC]
|
||||
|
||||
# Required attributes for module, separated by a comma
|
||||
required-attributes=
|
||||
|
||||
# Regular expression which should only match functions or classes name which do
|
||||
# not require a docstring
|
||||
no-docstring-rgx=__.*__
|
||||
|
||||
# Regular expression which should only match correct module names
|
||||
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
|
||||
|
||||
# Regular expression which should only match correct module level names
|
||||
const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
|
||||
|
||||
# Regular expression which should only match correct class names
|
||||
class-rgx=[A-Z_][a-zA-Z0-9]+$
|
||||
|
||||
# Regular expression which should only match correct function names
|
||||
function-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Regular expression which should only match correct method names
|
||||
method-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Regular expression which should only match correct instance attribute names
|
||||
attr-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Regular expression which should only match correct argument names
|
||||
argument-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Regular expression which should only match correct variable names
|
||||
variable-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Regular expression which should only match correct list comprehension /
|
||||
# generator expression variable names
|
||||
inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
|
||||
|
||||
# Good variable names which should always be accepted, separated by a comma
|
||||
good-names=i,j,k,ex,Run,_
|
||||
|
||||
# Bad variable names which should always be refused, separated by a comma
|
||||
bad-names=foo,bar,baz,toto,tutu,tata
|
||||
|
||||
# List of builtins function names that should not be used, separated by a comma
|
||||
bad-functions=map,filter,apply,inpu
|
||||
|
||||
|
||||
# try to find bugs in the code using type inference
|
||||
#
|
||||
[TYPECHECK]
|
||||
|
||||
# Tells wether missing members accessed in mixin class should be ignored. A
|
||||
# mixin class is detected if its name ends with "mixin" (case insensitive).
|
||||
ignore-mixin-members=yes
|
||||
|
||||
# List of classes names for which member attributes should not be checked
|
||||
# (useful for classes with attributes dynamicaly set).
|
||||
ignored-classes=SQLObjec
|
||||
|
||||
# When zope mode is activated, add a predefined set of Zope acquired attributes
|
||||
# to generated-members.
|
||||
zope=no
|
||||
|
||||
# List of members which are set dynamically and missed by pylint inference
|
||||
# system, and so shouldn't trigger E0201 when accessed.
|
||||
generated-members=REQUEST,acl_users,aq_paren
|
||||
|
||||
|
||||
# checks for
|
||||
# * unused variables / imports
|
||||
# * undefined variables
|
||||
# * redefinition of variable from builtins or from an outer scope
|
||||
# * use of variable before assigmen
|
||||
#
|
||||
[VARIABLES]
|
||||
|
||||
# Tells wether we should check for unused import in __init__ files.
|
||||
init-import=no
|
||||
|
||||
# A regular expression matching names used for dummy variables (i.e. not used).
|
||||
dummy-variables-rgx=_|dummy
|
||||
|
||||
# List of additional names supposed to be defined in builtins. Remember tha
|
||||
# you should avoid to define new builtins when possible.
|
||||
additional-builtins=
|
||||
|
||||
|
||||
# checks for :
|
||||
# * methods without self as first argumen
|
||||
# * overridden methods signature
|
||||
# * access only to existant members via self
|
||||
# * attributes not defined in the __init__ method
|
||||
# * supported interfaces implementation
|
||||
# * unreachable code
|
||||
#
|
||||
[CLASSES]
|
||||
|
||||
# List of interface methods to ignore, separated by a comma. This is used for
|
||||
# instance to not check methods defines in Zope's Interface base class.
|
||||
ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
|
||||
|
||||
# List of method names used to declare (i.e. assign) instance attributes.
|
||||
defining-attr-methods=__init__,__new__,setUp
|
||||
|
||||
|
||||
# checks for sign of poor/misdesign:
|
||||
# * number of methods, attributes, local variables...
|
||||
# * size, complexity of functions, methods
|
||||
#
|
||||
[DESIGN]
|
||||
|
||||
# Maximum number of arguments for function / method
|
||||
max-args=5
|
||||
|
||||
# Maximum number of locals for function / method body
|
||||
max-locals=15
|
||||
|
||||
# Maximum number of return / yield for function / method body
|
||||
max-returns=6
|
||||
|
||||
# Maximum number of branch for function / method body
|
||||
max-branchs=12
|
||||
|
||||
# Maximum number of statements in function / method body
|
||||
max-statements=50
|
||||
|
||||
# Maximum number of parents for a class (see R0901).
|
||||
max-parents=7
|
||||
|
||||
# Maximum number of attributes for a class (see R0902).
|
||||
max-attributes=7
|
||||
|
||||
# Minimum number of public methods for a class (see R0903).
|
||||
min-public-methods=2
|
||||
|
||||
# Maximum number of public methods for a class (see R0904).
|
||||
max-public-methods=20
|
||||
|
||||
|
||||
# checks for
|
||||
# * external modules dependencies
|
||||
# * relative / wildcard imports
|
||||
# * cyclic imports
|
||||
# * uses of deprecated modules
|
||||
#
|
||||
[IMPORTS]
|
||||
|
||||
# Deprecated modules which should not be used, separated by a comma
|
||||
deprecated-modules=regsub,string,TERMIOS,Bastion,rexec
|
||||
|
||||
# Create a graph of every (i.e. internal and external) dependencies in the
|
||||
# given file (report R0402 must not be disabled)
|
||||
import-graph=
|
||||
|
||||
# Create a graph of external dependencies in the given file (report R0402 mus
|
||||
# not be disabled)
|
||||
ext-import-graph=
|
||||
|
||||
# Create a graph of internal dependencies in the given file (report R0402 mus
|
||||
# not be disabled)
|
||||
int-import-graph=
|
||||
|
||||
|
||||
# checks for :
|
||||
# * unauthorized constructions
|
||||
# * strict indentation
|
||||
# * line length
|
||||
# * use of <> instead of !=
|
||||
#
|
||||
[FORMAT]
|
||||
|
||||
# Maximum number of characters on a single line.
|
||||
max-line-length=80
|
||||
|
||||
# Maximum number of lines in a module
|
||||
max-module-lines=1000
|
||||
|
||||
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
|
||||
# tab).
|
||||
indent-string=' '
|
||||
|
||||
|
||||
# checks for:
|
||||
# * warning notes in the code like FIXME, XXX
|
||||
# * PEP 263: source code with non ascii character but no encoding declaration
|
||||
#
|
||||
[MISCELLANEOUS]
|
||||
|
||||
# List of note tags to take in consideration, separated by a comma.
|
||||
notes=FIXME,XXX,TODO
|
||||
|
||||
|
||||
# checks for similarities and duplicated code. This computation may be
|
||||
# memory / CPU intensive, so you should disable it if you experiments some
|
||||
# problems.
|
||||
#
|
||||
[SIMILARITIES]
|
||||
|
||||
# Minimum lines number of a similarity.
|
||||
min-similarity-lines=4
|
||||
|
||||
# Ignore comments when computing similarities.
|
||||
ignore-comments=yes
|
||||
|
||||
# Ignore docstrings when computing similarities.
|
||||
ignore-docstrings=yes
|
||||
-38
@@ -1,38 +0,0 @@
|
||||
Mini-NDN Authors
|
||||
=================
|
||||
|
||||
The following lists maintainers, primary developers, and all much-appreciated contributors to Mini-NDN in alphabetic order.
|
||||
The specific contributions of individual authors can be obtained from the git history of the [official Mini-NDN repository](https://github.com/named-data/mini-ndn).
|
||||
If you would like to become a contributor to the official repository, please follow the recommendations in https://github.com/named-data/.github/blob/master/CONTRIBUTING.md.
|
||||
|
||||
* Alexander Afanasyev <https://users.cs.fiu.edu/~afanasyev>
|
||||
* Muktadir R. Chowdhury <https://github.com/alvyC>
|
||||
* Damian Coomes <https://github.com/dmcoomes>
|
||||
* ***(Maintainer)*** Saurab Dulal <https://dulalsaurab.github.io>
|
||||
* Laqin Fan <https://github.com/laqinfan>
|
||||
* ***(Former Maintainer)*** Ashlesh Gawande <https://www.linkedin.com/in/agawande>
|
||||
* Nicholas Gordon <https://github.com/gorgonical>
|
||||
* Giovanni Grieco <https://github.com/GiovanniGrieco>
|
||||
* ***(Maintainer)*** Alexander Lane <https://github.com/awlane>
|
||||
* Vince Lehman <http://vslehman.com>
|
||||
* Philipp Moll <https://github.com/phylib>
|
||||
* Eric Newberry <https://ericnewberry.com>
|
||||
* Junxiao Shi <https://cs.arizona.edu/~shijunxiao>
|
||||
* Jeff Thompson <https://remap.ucla.edu/jeff-thompson>
|
||||
* Yucheng Zhang <https://peterskycloud.wixsite.com/yzportfolio>
|
||||
* Italo Valcy S Brito <https://github.com/italovalcy>
|
||||
|
||||
|
||||
Technical Advisors
|
||||
-------------------
|
||||
|
||||
* Lan Wang <http://www.cs.memphis.edu/~lanwang>
|
||||
* Beichuan Zhang <http://cs.arizona.edu/~bzhang>
|
||||
|
||||
|
||||
The Mini-CCNx team
|
||||
-------------------
|
||||
|
||||
* Carlos Cabral <https://github.com/carlosmscabral>
|
||||
* Caio de Moraes Elias <https://github.com/ocaio>
|
||||
* Christian Esteve Rothenberg <http://www.dca.fee.unicamp.br/~chesteve>
|
||||
-674
@@ -1,674 +0,0 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. 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
|
||||
them 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 prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. 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.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey 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;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If 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 convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU 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 that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
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.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
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.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
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
|
||||
state 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program 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, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU 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. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||
-37
@@ -1,37 +0,0 @@
|
||||
# Setup container with Ubuntu 22.04 image
|
||||
FROM ubuntu:22.04
|
||||
|
||||
# Set the working directory to /
|
||||
WORKDIR /
|
||||
|
||||
# expose ports for openvswitch-switch
|
||||
EXPOSE 6633 6653 6640
|
||||
|
||||
# Update container image
|
||||
RUN apt-get update -y && \
|
||||
apt-get install --no-install-recommends -y \
|
||||
lsb-release sudo \
|
||||
zip unzip wget git ca-certificates \
|
||||
curl iproute2 iputils-ping net-tools \
|
||||
python3 python3-pip \
|
||||
tcpdump vim x11-xserver-utils xterm && \
|
||||
update-ca-certificates && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
alias python=python3
|
||||
|
||||
COPY . /mini-ndn
|
||||
|
||||
RUN cd mini-ndn && \
|
||||
pip3 install -r requirements.txt && \
|
||||
./install.sh -y --source && \
|
||||
cd dl/mininet && make install && cd ../.. && \
|
||||
cd dl/mininet-wifi && make install && cd ../.. && \
|
||||
rm -rf dl && rm -rf /var/lib/apt/lists/* && cd /
|
||||
|
||||
COPY docker/ENTRYPOINT.sh /
|
||||
RUN chmod +x ENTRYPOINT.sh
|
||||
|
||||
# Change the working directory to /mini-ndn
|
||||
WORKDIR /mini-ndn
|
||||
|
||||
ENTRYPOINT ["/ENTRYPOINT.sh"]
|
||||
@@ -0,0 +1,273 @@
|
||||
|
||||
Mininet Installation/Configuration Notes
|
||||
|
||||
Mininet 1.0.0
|
||||
|
||||
---
|
||||
|
||||
The supported installation methods for Mininet are 1) using
|
||||
a pre-built VM image, and 2) native installation on Ubuntu or Debian.
|
||||
(Other distributions may be supported in the future - if you would
|
||||
like to contribute an installation script, we would welcome it!)
|
||||
|
||||
1. Easiest "install" - use our pre-built VM image!
|
||||
|
||||
The easiest way to get Mininet running is to start with one of our pre-built
|
||||
virtual machine images from http://openflow.org/mininet
|
||||
|
||||
Boot up the VM image, log in, and follow the instructions on the wiki page.
|
||||
|
||||
An additional advantage of using the VM image is that it doesn't mess with
|
||||
your native OS installation or damage it in any way.
|
||||
|
||||
2. Native installation (experimental!) for Ubuntu 10.04 LTS
|
||||
|
||||
If you are running Ubuntu 10.04 LTS (or possibly Debian 5), you may be
|
||||
able to use our handy install.sh script, which is in mininet/util.
|
||||
|
||||
WARNING: USE AT YOUR OWN RISK!
|
||||
|
||||
install.sh is a bit intrusive and may possibly damage your OS and/or
|
||||
home directory, by creating/modifying several directories such as
|
||||
mininet, openflow, openvswitch and noxcore. Although we hope it won't
|
||||
do anything completely terrible, you may want to look at the script
|
||||
before you run it, and you should make sure your system and home
|
||||
directory are backed up just in case!
|
||||
|
||||
To install ALL of the software which we use for OpenFlow tutorials,
|
||||
you may use
|
||||
|
||||
$ mininet/util/install.sh
|
||||
|
||||
This takes about 20-30 minutes.
|
||||
|
||||
Alternately, you can install just the pieces you need.
|
||||
|
||||
We recommend the following steps, in order:
|
||||
|
||||
[a) On Debian 5, first install a Mininet-compatible kernel:
|
||||
$ mininet/util/install.sh -k
|
||||
Reboot and run 'uname -r' to make sure you're running the new kernel.]
|
||||
|
||||
b) Install mininet and its dependencies:
|
||||
$ mininet/util/install.sh -n
|
||||
|
||||
c) Install OpenFlow 1.0 and associated useful software
|
||||
$ mininet/util/install.sh -f
|
||||
|
||||
d) Install Open vSwitch and its kernel module
|
||||
$ mininet/util/install.sh -vm
|
||||
|
||||
e) If you wish to install the version of NOX we use in the tutorial:
|
||||
$ mininet/util/install.sh -x
|
||||
|
||||
Note: NOX development is progressing over time, so after you complete
|
||||
the tutorial you may wish to install the latest and greatest NOX from
|
||||
noxrepo.org.
|
||||
|
||||
Good luck! Some additional installation notes are provided below, for
|
||||
the brave and/or Linux-savvy, or those who are trying to understand what
|
||||
is installed and why.
|
||||
|
||||
p.s. Note that only one instance of Mininet is currently supported on a single
|
||||
machine - that's one reason we recommend using a VM to run it.
|
||||
|
||||
---
|
||||
|
||||
Mininet Manual Installation Notes
|
||||
|
||||
These installation notes assume you understand how to do things like
|
||||
compile kernels, apply patches, configure networks, write code, etc.. If
|
||||
this is unfamiliar territory, or if you run into trouble, we recommend
|
||||
using one of our pre-built virtual machine images (see above.)
|
||||
|
||||
If you wish to try to create a VM to run Mininet, you may also wish
|
||||
to look at the Wiki page:
|
||||
|
||||
http://openflow.org/foswiki/bin/view/OpenFlow/MininetVMCreationNotes
|
||||
|
||||
0. Obtaining Mininet
|
||||
|
||||
If you're reading this, you've already done it, but the command to
|
||||
download mininet is:
|
||||
|
||||
git clone git://openflow.org/mininet.git
|
||||
|
||||
1. Core Mininet installation
|
||||
|
||||
The core Mininet installation requires gcc, make, python,
|
||||
and setuptools. On Ubuntu and Debian you may install them with:
|
||||
|
||||
# aptitude install gcc make python setuptools
|
||||
|
||||
To install Mininet itself, with root privileges:
|
||||
|
||||
# cd mininet
|
||||
# make install
|
||||
|
||||
This places the mininet package in /usr/lib/python-*/site-packages/,
|
||||
so that 'import mininet' will work, and installs the primary mn
|
||||
script (mn) as well as its helper utility (mnexec.)
|
||||
|
||||
On Ubuntu and Debian, Mininet's dependencies and core files may also be
|
||||
installed using mininet/util/install.sh -n
|
||||
|
||||
2. Installation script for Ubuntu/Debian Lenny
|
||||
|
||||
If you are running Ubuntu 10.04 or Debian Lenny, you may be able to use the
|
||||
util/install.sh script to install a compatible Linux kernel as well as
|
||||
other software including the OpenFlow reference implementation, the Open
|
||||
vSwitch switch implementation, and the NOX OpenFlow controller.
|
||||
|
||||
Many different installation options are possible by passing different
|
||||
options to install.sh; install.sh -h lists them all.
|
||||
|
||||
Assuming the mininet source tree is installed in ~/mininet, the steps to run
|
||||
install.sh to install EVERYTHING we use for OpenFlow tutorials are:
|
||||
|
||||
% cd
|
||||
% time ~/mininet/util/install.sh # installs tons of stuff
|
||||
% sudo reboot # to load new kernel
|
||||
% ~/mininet/util/install.sh -c # to clean out unneeded kernel stuff
|
||||
|
||||
This installs a lot of useful software, but it will take a while (30
|
||||
minutes or more, depending on your network connection, computer, etc..)
|
||||
|
||||
Probably the minimal semi-useful configuration would be to install
|
||||
Mininet itself, kernel support if necessary, and either the
|
||||
reference OpenFlow switch or Open vSwitch. This could be installed
|
||||
as follows:
|
||||
|
||||
% sudo ~/mininet/util/install.sh -knvm
|
||||
|
||||
Respectively, this installs kernel support, core mininet dependencies,
|
||||
Open vSwitch, and the Open vSwitch kernel module. If a new kernel was
|
||||
installed, then a reboot may be required.
|
||||
|
||||
If install.sh cannot be used for some reason (e.g. you're on Fedora
|
||||
or some other Linux - please don't say CentOS) or if you don't want to
|
||||
install all of these components (they're useful!), the kernel and
|
||||
OpenFlow software requirements are described in steps [3] and [4],
|
||||
which follow.
|
||||
|
||||
If you successfully used install.sh, congratulations! You're basically
|
||||
done. Proceed to step [6] for additional advice.
|
||||
|
||||
3. Linux Kernel requirements
|
||||
|
||||
Mininet requires a kernel built with network namespace support enabled,
|
||||
i.e. with CONFIG_NET_NS=Y, such as the kernel shipped with
|
||||
Ubuntu 10.04 LTS, currently 2.6.32. On Ubuntu 10.04, you should not need
|
||||
to install or build a custom kernel, although 2.6.33+ is faster at
|
||||
tearing down virtual ethernet pairs.
|
||||
|
||||
For Ubuntu and Debian, we provide a 2.6.33 kernel package which you may be
|
||||
able to install using "util/install.sh -k". Note our kernel package
|
||||
requires an ext2 or ext3 root file system, so it won't work if you have
|
||||
a default Ubuntu install, which uses ext4.
|
||||
|
||||
If your kernel wasn't compiled with CONFIG_NET_NS=Y, you will need to
|
||||
build and install a kernel that does! >= 2.6.33 works better, but may
|
||||
be harder to get working, depending on your Linux distribution.
|
||||
|
||||
A script for building Debian packages for 2.6.33.1 is provided in
|
||||
mininet/util/kbuild. You may wish to read it, as it applies patches
|
||||
to enable 2.6.33.1 to build under debian-stable, and to enable the
|
||||
tun driver to work correctly with Mininet.
|
||||
|
||||
Earlier kernels (e.g. 2.6.29) work with CONFIG_NET_NS enabled and no
|
||||
additional patches, but are much slower at removing veth interfaces,
|
||||
resulting in much slower switch shutdown.
|
||||
|
||||
For scalable configurations, you might need to increase some of your
|
||||
kernel limits. Sample params are in util/sysctl_addon, which can be
|
||||
appended to /etc/sysctl.conf (and modified as necessary for your
|
||||
desired configuration):
|
||||
|
||||
sudo su -c "cat sysctl_addon >> /etc/sysctl.conf"
|
||||
|
||||
To save the config change, run:
|
||||
|
||||
sudo sysctl -p
|
||||
|
||||
4. OpenFlow software and configuration requirements
|
||||
|
||||
Mininet requires either the reference OpenFlow switch implementation
|
||||
(from openflowswitch.org) or Open vSwitch (openvswitch.org) to be
|
||||
installed. "make test" requires the reference user space
|
||||
implementations as well as Open vSwitch. Note the reference kernel
|
||||
implementation is not currently included in OpenFlow 1.0.
|
||||
|
||||
On Ubuntu and Debian, the install.sh script may be used with the '-f'
|
||||
option to install the OpenFlow reference implementation, the '-v' option
|
||||
to build Open vSwitch, and the '-m' option to install the Open vSwitch
|
||||
kernel module into /lib/modules (note: you must build Open vSwitch first!)
|
||||
|
||||
Mininet will automatically load and remove kernel module dependencies
|
||||
for supported switch types, using modprobe and rmmod - but these
|
||||
modules must be in a location where modprobe can find them (e.g.
|
||||
something like /lib/modules/`uname -r`/kernel/drivers/net/)
|
||||
|
||||
The reference OpenFlow controller (controller(8)) only supports 16
|
||||
switches by default! If you wish to run a network with more than 16
|
||||
switches, please recompile controller(8) with larger limits, or use a
|
||||
different controller such as nox. A patch to controller(8) is included
|
||||
as util/openflow-patches/controller.patch.
|
||||
|
||||
5. Other software dependencies
|
||||
|
||||
On Ubuntu and Debian, other Mininet dependencies may be installed using
|
||||
the '-n' option of the install.sh script.
|
||||
|
||||
To run the iperf test, you need to install iperf:
|
||||
|
||||
sudo aptitude/yum install iperf
|
||||
|
||||
We assume you already have ping installed. ;-)
|
||||
|
||||
To use xterm or sshd with Mininet, you need the following:
|
||||
|
||||
sudo aptitude/yum install sshd xterm screen
|
||||
|
||||
Some examples may have additional requirements - consult the specific
|
||||
example file for details.
|
||||
|
||||
The install.sh script has an '-x' option to install the version of
|
||||
NOX from the OpenFlow tutorial.
|
||||
|
||||
6. Other notes and recommendations
|
||||
|
||||
If you did not install certain useful packages and you wish to later,
|
||||
it may be possible to install them using install.sh.
|
||||
|
||||
Mininet should be run either on a machine with
|
||||
no other important processes, or on a virtual machine (recommended!)
|
||||
|
||||
Multiple concurrent Mininet instances are not supported!
|
||||
|
||||
Good luck!
|
||||
|
||||
---
|
||||
|
||||
Historical information on OpenFlow 0.8.9 and the reference kernel module:
|
||||
|
||||
The kernel reference implementation has been deprecated, but it may
|
||||
be possible to get it work with Mininet.
|
||||
|
||||
To switch to the most recent OpenFlow 0.8.9 release branch (the most
|
||||
recent one with full NOX support and kernel datapath support) in your
|
||||
OpenFlow git tree:
|
||||
|
||||
git checkout -b release/0.8.9 remotes/origin/release/0.8.9
|
||||
|
||||
A patch to enable datapath.c to compile with recent kernels
|
||||
is included in util/openflow-patches/datapath.patch.
|
||||
|
||||
In OpenFlow 1.0, switch port numbering starts at 1 (for better or for worse.)
|
||||
To run with previous versions of OpenFlow, it may be necessary
|
||||
to change SWITCH_PORT_BASE from 1 to 0 in node.py.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
Mininet 1.0.0 License
|
||||
|
||||
Copyright (c) 2009-2011 Bob Lantz and Brandon Heller
|
||||
|
||||
We are making Mininet available for public use and benefit with the
|
||||
expectation that others will use, modify and enhance the Software and
|
||||
contribute those enhancements back to the community. However, since we
|
||||
would like to make the Software available for broadest use, with as few
|
||||
restrictions as possible permission is hereby granted, free of charge, to
|
||||
any person obtaining a copy of this Software to deal in the Software
|
||||
under the copyrights without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
The name and trademarks of copyright holder(s) may NOT be used in
|
||||
advertising or publicity pertaining to the Software or any derivatives
|
||||
without specific, written prior permission.
|
||||
@@ -0,0 +1,2 @@
|
||||
include mnexec.c
|
||||
exclude bin/mnexec
|
||||
@@ -0,0 +1,32 @@
|
||||
all: codecheck test
|
||||
|
||||
clean:
|
||||
rm -rf build dist *.egg-info *.pyc mnexec bin/mnexec
|
||||
|
||||
MININET = mininet/*.py
|
||||
TEST = mininet/test/*.py
|
||||
EXAMPLES = examples/*.py
|
||||
BIN = bin/mn
|
||||
PYSRC = $(MININET) $(TEST) $(EXAMPLES) $(BIN)
|
||||
|
||||
P8IGN = E251,E201,E302,E202
|
||||
|
||||
codecheck: $(PYSRC)
|
||||
-echo "Running code check"
|
||||
pyflakes $(PYSRC)
|
||||
pylint --rcfile=.pylint $(PYSRC)
|
||||
pep8 --repeat --ignore=$(P8IGN) $(PYSRC)
|
||||
|
||||
test: $(MININET) $(TEST)
|
||||
-echo "Running tests"
|
||||
mininet/test/test_nets.py
|
||||
|
||||
install:
|
||||
python setup.py install
|
||||
|
||||
develop:
|
||||
python setup.py develop
|
||||
|
||||
doc:
|
||||
doxygen doxygen.cfg
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
|
||||
Mininet: A Simple Virtual Testbed for OpenFlow/SDN
|
||||
or
|
||||
How to Squeeze a 1024-node OpenFlow Network onto your Laptop
|
||||
|
||||
Mininet 1.0.0
|
||||
|
||||
---
|
||||
Welcome to Mininet!
|
||||
|
||||
Mininet creates OpenFlow test networks by using process-based
|
||||
virtualization and network namespaces.
|
||||
|
||||
Simulated hosts (as well as switches and controllers with the user
|
||||
datapath) are created as processes in separate network namespaces. This
|
||||
allows a complete OpenFlow network to be simulated on top of a single
|
||||
Linux kernel.
|
||||
|
||||
Mininet may be invoked directly from the command line, and also provides a
|
||||
handy Python API for creating networks of varying sizes and topologies.
|
||||
|
||||
Mininet is currently in *limited alpha release*. We encourage you to
|
||||
experiment with it and hope that you will provide us with feedback on
|
||||
features, documentation, and how you're using it. We plan to make it
|
||||
available publicly via a GPL or BSD license (probably in April), but please
|
||||
don't distribute the code or URLs yet! The feedback you provide will help
|
||||
us improve Mininet for general release.
|
||||
|
||||
In order to run Mininet, you must have:
|
||||
|
||||
* A Linux 2.6.26 or greater kernel compiled with network namespace support
|
||||
enabled (see INSTALL for additional information.)
|
||||
|
||||
* An OpenFlow implementation (either the reference user or kernel
|
||||
space implementations, or Open vSwitch.) Appropriate kernel modules
|
||||
(e.g. tun and ofdatapath for the reference kernel implementation) must
|
||||
be loaded.
|
||||
|
||||
* Python, bash, ping, iperf, etc.
|
||||
|
||||
* Root privileges (required for network device access)
|
||||
|
||||
Currently Mininet includes:
|
||||
|
||||
- A simple node infrastructure (Host, Switch, Controller classes) for
|
||||
creating virtual OpenFlow networks
|
||||
|
||||
- A simple network infrastructure (Mininet class) supporting parametrized
|
||||
topologies (Topo subclasses.) For example, a tree network may be created
|
||||
with the command
|
||||
|
||||
# mn --topo tree,depth=2,fanout=3
|
||||
|
||||
- Basic tests, including connectivity (ping) and bandwidth (iperf)
|
||||
|
||||
- A command-line interface (CLI class) which provides useful
|
||||
diagnostic commands, as well as the ability to send a command to a
|
||||
node. For example,
|
||||
|
||||
mininet> h11 ifconfig -a
|
||||
|
||||
tells host h11 to run the command 'ifconfig -a'
|
||||
|
||||
- A 'cleanup' command to get rid of junk (interfaces, processes, files in
|
||||
/tmp, etc.) which might be left around by Mininet or Linux. Try this if
|
||||
things stop working!
|
||||
|
||||
# mn -c
|
||||
|
||||
- Examples (in the examples/ directory) to help you get started.
|
||||
|
||||
Batteries are not included (yet!)
|
||||
|
||||
However, some preliminary installation notes are included in the INSTALL
|
||||
file.
|
||||
|
||||
Additionally, much useful information, including a Mininet tutorial,
|
||||
is available on the Mininet wiki:
|
||||
|
||||
http://openflow.org/mininet
|
||||
|
||||
Enjoy, and good luck!
|
||||
|
||||
---
|
||||
Bob Lantz
|
||||
rlantz@cs.stanford.edu
|
||||
@@ -1,32 +0,0 @@
|
||||
Mini-NDN
|
||||
========
|
||||
|
||||
If you are new to the NDN community of software generally, read the
|
||||
[Contributor's Guide](https://github.com/named-data/.github/blob/master/CONTRIBUTING.md).
|
||||
|
||||
### What is Mini-NDN?
|
||||
|
||||
Mini-NDN is a lightweight networking emulation tool that enables testing, experimentation, and
|
||||
research on the NDN platform based on [Mininet](https://github.com/mininet/mininet).
|
||||
Mini-NDN uses the NDN libraries, NFD, NLSR, and tools released by the
|
||||
[NDN project](http://named-data.net/codebase/platform/) to emulate an NDN network on a single system.
|
||||
|
||||
Mini-NDN is open and free software licensed under the GPL 3.0 license. Mini-NDN is free to all
|
||||
users and developers. For more information about licensing details and limitations,
|
||||
please refer to [COPYING.md](COPYING.md).
|
||||
|
||||
The first release of Mini-NDN is developed by members of the NSF-sponsored NDN project team.
|
||||
Mini-NDN is open to contribution from the public.
|
||||
For more details, please refer to [AUTHORS.rst](AUTHORS.rst).
|
||||
Bug reports and feedback are highly appreciated and can be made through our
|
||||
[Redmine site](http://redmine.named-data.net/projects/mini-ndn) and the
|
||||
[mini-ndn mailing list](http://www.lists.cs.ucla.edu/mailman/listinfo/mini-ndn).
|
||||
|
||||
### Documentation
|
||||
|
||||
Please refer to http://minindn.memphis.edu/ or [docs/index.rst](docs/index.rst) for installation, usage, and other documentation.
|
||||
The documentation can be built using:
|
||||
|
||||
./docs/build.sh
|
||||
|
||||
and is available under `docs/_build/html`.
|
||||
@@ -0,0 +1,276 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Mininet runner
|
||||
author: Brandon Heller (brandonh@stanford.edu)
|
||||
|
||||
To see options:
|
||||
sudo mn -h
|
||||
|
||||
Example to pull custom params (topo, switch, etc.) from a file:
|
||||
sudo mn --custom ~/mininet/custom/custom_example.py
|
||||
"""
|
||||
|
||||
from optparse import OptionParser
|
||||
import os.path
|
||||
import sys
|
||||
import time
|
||||
|
||||
from mininet.clean import cleanup
|
||||
from mininet.cli import CLI
|
||||
from mininet.log import lg, LEVELS, info
|
||||
from mininet.net import Mininet, init
|
||||
from mininet.node import KernelSwitch, Host, Controller, ControllerParams, NOX
|
||||
from mininet.node import RemoteController, UserSwitch, OVSKernelSwitch
|
||||
from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo
|
||||
from mininet.topolib import TreeTopo
|
||||
from mininet.util import makeNumeric
|
||||
|
||||
# built in topologies, created only when run
|
||||
TOPODEF = 'minimal'
|
||||
TOPOS = { 'minimal': lambda: SingleSwitchTopo( k=2 ),
|
||||
'linear': LinearTopo,
|
||||
'reversed': SingleSwitchReversedTopo,
|
||||
'single': SingleSwitchTopo,
|
||||
'tree': TreeTopo }
|
||||
|
||||
SWITCHDEF = 'ovsk'
|
||||
SWITCHES = { 'kernel': KernelSwitch,
|
||||
'user': UserSwitch,
|
||||
'ovsk': OVSKernelSwitch }
|
||||
|
||||
HOSTDEF = 'process'
|
||||
HOSTS = { 'process': Host }
|
||||
|
||||
CONTROLLERDEF = 'ref'
|
||||
# a and b are the name and inNamespace params.
|
||||
CONTROLLERS = { 'ref': Controller,
|
||||
'nox_dump': lambda name: NOX( name, 'packetdump' ),
|
||||
'nox_pysw': lambda name: NOX( name, 'pyswitch' ),
|
||||
'remote': lambda name: None,
|
||||
'none': lambda name: None }
|
||||
|
||||
# optional tests to run
|
||||
TESTS = [ 'cli', 'build', 'pingall', 'pingpair', 'iperf', 'all', 'iperfudp',
|
||||
'none' ]
|
||||
|
||||
ALTSPELLING = { 'pingall': 'pingAll', 'pingpair': 'pingPair',
|
||||
'iperfudp': 'iperfUdp', 'iperfUDP': 'iperfUdp', 'prefixlen': 'prefixLen' }
|
||||
|
||||
def buildTopo( topo ):
|
||||
"Create topology from string with format (object, arg1, arg2,...)."
|
||||
topo_split = topo.split( ',' )
|
||||
topo_name = topo_split[ 0 ]
|
||||
topo_params = topo_split[ 1: ]
|
||||
|
||||
# Convert int and float args; removes the need for every topology to
|
||||
# be flexible with input arg formats.
|
||||
topo_seq_params = [ s for s in topo_params if '=' not in s ]
|
||||
topo_seq_params = [ makeNumeric( s ) for s in topo_seq_params ]
|
||||
topo_kw_params = {}
|
||||
for s in [ p for p in topo_params if '=' in p ]:
|
||||
key, val = s.split( '=' )
|
||||
topo_kw_params[ key ] = makeNumeric( val )
|
||||
|
||||
if topo_name not in TOPOS.keys():
|
||||
raise Exception( 'Invalid topo_name %s' % topo_name )
|
||||
return TOPOS[ topo_name ]( *topo_seq_params, **topo_kw_params )
|
||||
|
||||
|
||||
def addDictOption( opts, choicesDict, default, name, helpStr=None ):
|
||||
"""Convenience function to add choices dicts to OptionParser.
|
||||
opts: OptionParser instance
|
||||
choicesDict: dictionary of valid choices, must include default
|
||||
default: default choice key
|
||||
name: long option name
|
||||
help: string"""
|
||||
if default not in choicesDict:
|
||||
raise Exception( 'Invalid default %s for choices dict: %s' %
|
||||
( default, name ) )
|
||||
if not helpStr:
|
||||
helpStr = '[' + ' '.join( choicesDict.keys() ) + ']'
|
||||
opts.add_option( '--' + name,
|
||||
type='choice',
|
||||
choices=choicesDict.keys(),
|
||||
default = default,
|
||||
help = helpStr )
|
||||
|
||||
|
||||
class MininetRunner( object ):
|
||||
"Build, setup, and run Mininet."
|
||||
|
||||
def __init__( self ):
|
||||
"Init."
|
||||
self.options = None
|
||||
self.args = None # May be used someday for more CLI scripts
|
||||
self.validate = None
|
||||
|
||||
self.parseArgs()
|
||||
self.setup()
|
||||
self.begin()
|
||||
|
||||
def setCustom( self, name, value ):
|
||||
"Set custom parameters for MininetRunner."
|
||||
if name in ( 'topos', 'switches', 'hosts', 'controllers' ):
|
||||
# Update dictionaries
|
||||
param = name.upper()
|
||||
globals()[ param ].update( value )
|
||||
elif name == 'validate':
|
||||
# Add custom validate function
|
||||
self.validate = value
|
||||
else:
|
||||
# Add or modify global variable or class
|
||||
globals()[ name ] = value
|
||||
|
||||
def parseCustomFile( self, fileName ):
|
||||
"Parse custom file and add params before parsing cmd-line options."
|
||||
custom = {}
|
||||
if os.path.isfile( fileName ):
|
||||
execfile( fileName, custom, custom )
|
||||
for name in custom:
|
||||
self.setCustom( name, custom[ name ] )
|
||||
else:
|
||||
raise Exception( 'could not find custom file: %s' % fileName )
|
||||
|
||||
def parseArgs( self ):
|
||||
"""Parse command-line args and return options object.
|
||||
returns: opts parse options dict"""
|
||||
if '--custom' in sys.argv:
|
||||
print "custom in sys.argv"
|
||||
index = sys.argv.index( '--custom' )
|
||||
if len( sys.argv ) > index + 1:
|
||||
custom = sys.argv[ index + 1 ]
|
||||
self.parseCustomFile( custom )
|
||||
else:
|
||||
raise Exception( 'Custom file name not found' )
|
||||
|
||||
opts = OptionParser()
|
||||
addDictOption( opts, SWITCHES, SWITCHDEF, 'switch' )
|
||||
addDictOption( opts, HOSTS, HOSTDEF, 'host' )
|
||||
addDictOption( opts, CONTROLLERS, CONTROLLERDEF, 'controller' )
|
||||
|
||||
opts.add_option( '--topo', type='string', default=TOPODEF,
|
||||
help='[' + ' '.join( TOPOS.keys() ) + '],arg1,arg2,'
|
||||
'...argN')
|
||||
opts.add_option( '--clean', '-c', action='store_true',
|
||||
default=False, help='clean and exit' )
|
||||
opts.add_option( '--custom', type='string', default=None,
|
||||
help='read custom topo and node params from .py file' )
|
||||
opts.add_option( '--test', type='choice', choices=TESTS,
|
||||
default=TESTS[ 0 ],
|
||||
help='[' + ' '.join( TESTS ) + ']' )
|
||||
opts.add_option( '--xterms', '-x', action='store_true',
|
||||
default=False, help='spawn xterms for each node' )
|
||||
opts.add_option( '--mac', action='store_true',
|
||||
default=False, help='set MACs equal to DPIDs' )
|
||||
opts.add_option( '--arp', action='store_true',
|
||||
default=False, help='set all-pairs ARP entries' )
|
||||
opts.add_option( '--verbosity', '-v', type='choice',
|
||||
choices=LEVELS.keys(), default = 'info',
|
||||
help = '[' + ' '.join( LEVELS.keys() ) + ']' )
|
||||
opts.add_option( '--ip', type='string', default='127.0.0.1',
|
||||
help='[ip address as a dotted decimal string for a'
|
||||
'remote controller]' )
|
||||
opts.add_option( '--port', type='int', default=6633,
|
||||
help='[port integer for a listening remote'
|
||||
' controller]' )
|
||||
opts.add_option( '--innamespace', action='store_true',
|
||||
default=False, help='sw and ctrl in namespace?' )
|
||||
opts.add_option( '--listenport', type='int', default=6634,
|
||||
help='[base port for passive switch listening'
|
||||
' controller]' )
|
||||
opts.add_option( '--nolistenport', action='store_true',
|
||||
default=False, help="don't use passive listening port")
|
||||
opts.add_option( '--pre', type='string', default=None,
|
||||
help='[CLI script to run before tests]' )
|
||||
opts.add_option( '--post', type='string', default=None,
|
||||
help='[CLI script to run after tests]' )
|
||||
opts.add_option( '--prefixlen', type='int', default=8,
|
||||
help='[prefix length (e.g. /8) for automatic '
|
||||
'network configuration]' )
|
||||
|
||||
self.options, self.args = opts.parse_args()
|
||||
|
||||
def setup( self ):
|
||||
"Setup and validate environment."
|
||||
|
||||
# set logging verbosity
|
||||
if LEVELS[self.options.verbosity] > LEVELS['output']:
|
||||
print ( '*** WARNING: selected verbosity level (%s) will hide CLI '
|
||||
'output!\n'
|
||||
'Please restart Mininet with -v [debug, info, output].'
|
||||
% self.options.verbosity )
|
||||
lg.setLogLevel( self.options.verbosity )
|
||||
|
||||
# validate environment setup
|
||||
init()
|
||||
|
||||
def begin( self ):
|
||||
"Create and run mininet."
|
||||
|
||||
if self.options.clean:
|
||||
cleanup()
|
||||
exit()
|
||||
|
||||
start = time.time()
|
||||
|
||||
topo = buildTopo( self.options.topo )
|
||||
switch = SWITCHES[ self.options.switch ]
|
||||
host = HOSTS[ self.options.host ]
|
||||
controller = CONTROLLERS[ self.options.controller ]
|
||||
if self.options.controller == 'remote':
|
||||
controller = lambda a: RemoteController( a,
|
||||
defaultIP=self.options.ip,
|
||||
port=self.options.port )
|
||||
|
||||
if self.validate:
|
||||
self.validate( self.options )
|
||||
|
||||
# We should clarify what this is actually for...
|
||||
# It seems like it should be default values for the
|
||||
# *data* network, so it may be misnamed.
|
||||
controllerParams = ControllerParams( '10.0.0.0',
|
||||
self.options.prefixlen)
|
||||
|
||||
inNamespace = self.options.innamespace
|
||||
xterms = self.options.xterms
|
||||
mac = self.options.mac
|
||||
arp = self.options.arp
|
||||
listenPort = None
|
||||
if not self.options.nolistenport:
|
||||
listenPort = self.options.listenport
|
||||
mn = Mininet( topo, switch, host, controller, controllerParams,
|
||||
inNamespace=inNamespace,
|
||||
xterms=xterms, autoSetMacs=mac,
|
||||
autoStaticArp=arp, listenPort=listenPort )
|
||||
|
||||
if self.options.pre:
|
||||
CLI( mn, script=self.options.pre )
|
||||
|
||||
test = self.options.test
|
||||
test = ALTSPELLING.get( test, test )
|
||||
|
||||
mn.start()
|
||||
|
||||
if test == 'none':
|
||||
pass
|
||||
elif test == 'all':
|
||||
mn.start()
|
||||
mn.ping()
|
||||
mn.iperf()
|
||||
elif test == 'cli':
|
||||
CLI( mn )
|
||||
elif test != 'build':
|
||||
getattr( mn, test )()
|
||||
|
||||
if self.options.post:
|
||||
CLI( mn, script=self.options.post )
|
||||
|
||||
mn.stop()
|
||||
|
||||
elapsed = float( time.time() - start )
|
||||
info( 'completed in %0.3f seconds\n' % elapsed )
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
MininetRunner()
|
||||
@@ -0,0 +1,6 @@
|
||||
This directory should hold configuration files for custom mininets.
|
||||
|
||||
See custom_example.py, which loads the default minimal topology. The advantage of defining a mininet in a separate file is that you then use the --custom option in mn to run the CLI or specific tests with it.
|
||||
|
||||
To start up a mininet with the provided custom topology, do:
|
||||
sudo mn --custom custom_example.py --topo mytopo
|
||||
@@ -0,0 +1,45 @@
|
||||
"""Custom topology example
|
||||
|
||||
author: Brandon Heller (brandonh@stanford.edu)
|
||||
|
||||
Two directly connected switches plus a host for each switch:
|
||||
|
||||
host --- switch --- switch --- host
|
||||
|
||||
Adding the 'topos' dict with a key/value pair to generate our newly defined
|
||||
topology enables one to pass in '--topo=mytopo' from the command line.
|
||||
"""
|
||||
|
||||
from mininet.topo import Topo, Node
|
||||
|
||||
class MyTopo( Topo ):
|
||||
"Simple topology example."
|
||||
|
||||
def __init__( self, enable_all = True ):
|
||||
"Create custom topo."
|
||||
|
||||
# Add default members to class.
|
||||
super( MyTopo, self ).__init__()
|
||||
|
||||
# Set Node IDs for hosts and switches
|
||||
leftHost = 1
|
||||
leftSwitch = 2
|
||||
rightSwitch = 3
|
||||
rightHost = 4
|
||||
|
||||
# Add nodes
|
||||
self.add_node( leftSwitch, Node( is_switch=True ) )
|
||||
self.add_node( rightSwitch, Node( is_switch=True ) )
|
||||
self.add_node( leftHost, Node( is_switch=False ) )
|
||||
self.add_node( rightHost, Node( is_switch=False ) )
|
||||
|
||||
# Add edges
|
||||
self.add_edge( leftHost, leftSwitch )
|
||||
self.add_edge( leftSwitch, rightSwitch )
|
||||
self.add_edge( rightSwitch, rightHost )
|
||||
|
||||
# Consider all switches and hosts 'on'
|
||||
self.enable_all()
|
||||
|
||||
|
||||
topos = { 'mytopo': ( lambda: MyTopo() ) }
|
||||
@@ -1,11 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# set python3 alias, but needs permanent fix in image directly
|
||||
alias python=python3
|
||||
|
||||
service openvswitch-switch start
|
||||
ovs-vsctl set-manager ptcp:6640
|
||||
|
||||
bash
|
||||
|
||||
service openvswitch-switch stop
|
||||
@@ -1,35 +0,0 @@
|
||||
|
||||
[comments]: The original author of Mini-NDN docker is Md Ashiqur Rahman (marahman@email.arizona.edu)
|
||||
|
||||
## Running Mini-NDN inside Docker
|
||||
|
||||
You can use the nightly build from GitHub package registry
|
||||
```bash
|
||||
docker run -m 4g --cpus=4 -it --privileged \
|
||||
-v /lib/modules:/lib/modules \
|
||||
ghcr.io/named-data/mini-ndn:master bash
|
||||
```
|
||||
|
||||
## Building your own image
|
||||
|
||||
The Dockerfile can be used directly to `build` an image from scratch.
|
||||
|
||||
* Build with `Dockerfile`:
|
||||
* Clone the repository and type.
|
||||
```bash
|
||||
docker build -t minindn .
|
||||
```
|
||||
* You can then access the container through shell with,
|
||||
```bash
|
||||
docker run -m 4g --cpus=4 -it --privileged \
|
||||
-v /lib/modules:/lib/modules \
|
||||
minindn bin/bash
|
||||
```
|
||||
|
||||
### Notes:
|
||||
|
||||
* Memory (-m), CPU (--cpus) are recommended by Mini-NDN.
|
||||
* `--privileged` is mandatory for underlying [Mininet](http://mininet.org/) to utilize virtual switch
|
||||
* Root directory on `run` is `/mini-ndn` containing the installation and examples.
|
||||
* GUI may not work for now due to docker and xterm setup issues and is independent from Mini-NDN.
|
||||
If you intend to run the GUI, pass `-e DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix` to the `docker run` command.
|
||||
@@ -1,20 +0,0 @@
|
||||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line, and also
|
||||
# from the environment for the first two.
|
||||
SPHINXOPTS ?=
|
||||
SPHINXBUILD ?= sphinx-build
|
||||
SOURCEDIR = .
|
||||
BUILDDIR = _build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
@@ -1 +0,0 @@
|
||||
.. include:: ../AUTHORS.rst
|
||||
@@ -1,36 +0,0 @@
|
||||
#!/bin/bash
|
||||
# -*- Mode:bash; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
|
||||
#
|
||||
# Copyright (C) 2015-2021, The University of Memphis,
|
||||
# Arizona Board of Regents,
|
||||
# Regents of the University of California.
|
||||
#
|
||||
# This file is part of Mini-NDN.
|
||||
# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
|
||||
#
|
||||
# Mini-NDN 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mini-NDN 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 Mini-NDN, e.g., in COPYING.md file.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
set -eo pipefail
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||
|
||||
PIP='python3 -m pip'
|
||||
|
||||
for PIPPKG in sphinx sphinx_rtd_theme; do
|
||||
if ! $PIP show $PIPPKG >/dev/null; then
|
||||
sudo $PIP install $PIPPKG
|
||||
fi
|
||||
done
|
||||
|
||||
make clean html
|
||||
@@ -1,61 +0,0 @@
|
||||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
# This file only contains a selection of the most common options. For a full
|
||||
# list see the documentation:
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||
|
||||
# -- Path setup --------------------------------------------------------------
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#
|
||||
# import os
|
||||
# import sys
|
||||
# sys.path.insert(0, os.path.abspath('.'))
|
||||
from datetime import datetime
|
||||
from minindn import __version__
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
project = 'Mini-NDN'
|
||||
copyright = '2015-{}, Mini-NDN. This research is partially supported by NSF.'.format(datetime.now().year)
|
||||
author = 'Mini-NDN'
|
||||
|
||||
# The full version, including alpha/beta/rc tags
|
||||
release = __version__
|
||||
version = __version__
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This pattern also affects html_static_path and html_extra_path.
|
||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# Need to specify the line below if custom conf.py
|
||||
# https://github.com/readthedocs/readthedocs.org/issues/2569
|
||||
# https://stackoverflow.com/questions/56336234/build-fail-sphinx-error-contents-rst-not-found
|
||||
master_doc = 'index'
|
||||
@@ -1,274 +0,0 @@
|
||||
Experiment
|
||||
==========
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
Mini-NDN uses a configuration file describing the topology and its parameters to setup a network.
|
||||
|
||||
The [nodes] section:
|
||||
|
||||
At the bare minimum, the node section describes the nodes present in the
|
||||
topology.
|
||||
|
||||
::
|
||||
|
||||
[nodes]
|
||||
a: key1=value1 key2=value2
|
||||
b: key1=value1
|
||||
|
||||
Any key and value passed here is accessible in Mini-NDN as:
|
||||
|
||||
::
|
||||
|
||||
ndn = Minindn(...)
|
||||
value = ndn.net.hosts[0].params['params'].get('key1', "defaultValue")
|
||||
|
||||
One can specify log levels for each node's NFD and NLSR using this key-value system:
|
||||
|
||||
::
|
||||
|
||||
[nodes]
|
||||
a: nfd-log-level=DEBUG nlsr-log-level=DEBUG
|
||||
b: nfd-log-level=INFO
|
||||
|
||||
To specify a log level for certain modules of NFD, the following line can be added to `nfd.py`:
|
||||
|
||||
::
|
||||
|
||||
node.cmd('infoedit -f {} -s log.Forwarder -v {}'.format(self.confFile, 'INFO'))
|
||||
|
||||
This will turn on FORWARDER logging to INFO for all nodes.
|
||||
|
||||
.. Todo: Add switch section
|
||||
|
||||
The [links] section:
|
||||
|
||||
The links section describes the links in the topology.
|
||||
|
||||
::
|
||||
|
||||
e.g.)
|
||||
|
||||
[links]
|
||||
a:b delay=10ms
|
||||
|
||||
This would create a link between a and b. 'b:a' would also result in the
|
||||
same. The following parameters can be configured for a node:
|
||||
|
||||
- delay : Delay parameter is a required parameter which defines the
|
||||
delay of the link (1-1000ms)
|
||||
|
||||
- bw : Bandwidth of a link (<1-1000> Mbps)
|
||||
|
||||
- loss : Percentage of packet loss (<1-100>)
|
||||
|
||||
Example configuration file
|
||||
|
||||
::
|
||||
|
||||
[nodes]
|
||||
a:
|
||||
b:
|
||||
[links]
|
||||
a:b delay=10ms bw=100
|
||||
|
||||
See ``ndn_utils/topologies`` for more sample files
|
||||
|
||||
Sample
|
||||
------
|
||||
|
||||
Sample experiment may written as follows:
|
||||
|
||||
.. code:: python
|
||||
|
||||
from mininet.log import setLogLevel, info
|
||||
|
||||
from minindn.minindn import Minindn
|
||||
from minindn.util import MiniNDNCLI
|
||||
from minindn.apps.appmanager import AppManager
|
||||
from minindn.apps.nfd import Nfd
|
||||
from minindn.apps.nlsr import Nlsr
|
||||
from minindn.helpers.routing_helper import IPRoutingHelper
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel('info')
|
||||
|
||||
Minindn.cleanUp()
|
||||
Minindn.verifyDependencies()
|
||||
|
||||
# Can pass a custom parser, custom topology, or any Mininet params here
|
||||
ndn = Minindn()
|
||||
|
||||
ndn.start()
|
||||
|
||||
# IP reachability if needed
|
||||
# IPRoutingHelper.calcAllRoutes(ndn.net)
|
||||
# info("IP routes configured, start ping\n")
|
||||
# ndn.net.pingAll()
|
||||
|
||||
# Start apps with AppManager which registers a clean up function with ndn
|
||||
info('Starting NFD on nodes\n')
|
||||
nfds = AppManager(ndn, ndn.net.hosts, Nfd)
|
||||
info('Starting NLSR on nodes\n')
|
||||
nlsrs = AppManager(ndn, ndn.net.hosts, Nlsr)
|
||||
|
||||
# or can not start NLSRs with some delay in between:
|
||||
# nlsrs = AppManager(ndn, ndn.net.hosts, Nlsr)
|
||||
# for host in ndn.net.hosts:
|
||||
# nlsrs.startOnNode(host)
|
||||
# time.sleep(30)
|
||||
|
||||
MiniNDNCLI(ndn.net)
|
||||
|
||||
# Calls the clean up functions registered via AppManager
|
||||
ndn.stop()
|
||||
|
||||
Users may look at how the NFD and NLSR applications are written as a sub class of Application
|
||||
in the ``minindn/apps`` folder. Or users may choose to directly run their application on nodes
|
||||
such as ndnpingserver is run in ``minindn/helpers/experiment.py``.
|
||||
|
||||
**Note:** A certain log-level can be set-up for all the NFD or NLSR nodes at once by passing it as an argument during the startup.
|
||||
|
||||
``nfds = AppManager(self.ndn, self.ndn.net.hosts, Nfd, logLevel='DEBUG')`` (same for NLSR)
|
||||
|
||||
Execution
|
||||
---------
|
||||
|
||||
To run Mini-NDN with the default topology,
|
||||
``ndn_utils/topologies/default-topology.conf``, type:
|
||||
|
||||
::
|
||||
|
||||
sudo python examples/minindn.py
|
||||
|
||||
To run Mini-NDN with a topology file, provide the filename as the first
|
||||
argument:
|
||||
|
||||
::
|
||||
|
||||
sudo python examples/minindn.py my-topology.conf
|
||||
|
||||
After Mini-NDN is installed, users can run examples from anywhere with python directly as follows:
|
||||
|
||||
::
|
||||
|
||||
sudo python /path/to/myexample.py
|
||||
|
||||
The user no longer needs to create an experiment in the old Mini-NDN way, then install it to the system before executing it via the minindn binary. The new examples can be separate from the Mini-NDN folder if the core is not being modified.
|
||||
|
||||
CLI Interface
|
||||
_____________
|
||||
|
||||
During set up, the list of nodes in the network will be listed as they
|
||||
are initialized:
|
||||
|
||||
::
|
||||
|
||||
*** Adding hosts:
|
||||
a b c d
|
||||
|
||||
After set up, the command-line interface (CLI) will display a prompt.
|
||||
|
||||
::
|
||||
|
||||
mini-ndn>
|
||||
|
||||
To interact with a node, first type the node's name and then the command
|
||||
to be executed:
|
||||
|
||||
::
|
||||
|
||||
mini-ndn> a echo "Hello, world!"
|
||||
Hello, world!
|
||||
|
||||
To see the status of the forwarder on the node:
|
||||
|
||||
::
|
||||
|
||||
mini-ndn> a nfdc status report
|
||||
|
||||
To see the status of routing on the node:
|
||||
|
||||
::
|
||||
|
||||
mini-ndn> a nlsrc status
|
||||
|
||||
To exit Mini-NDN, type ``quit`` in the CLI or use ``ctrl + D``:
|
||||
|
||||
::
|
||||
|
||||
mini-ndn> quit
|
||||
|
||||
``Ctrl + C`` is used to quit an application run in the foreground of the command line.
|
||||
|
||||
For a more in depth explanation of the CLI, please see the `Mininet
|
||||
Walkthrough <http://mininet.org/walkthrough/>`__.
|
||||
|
||||
To run NDN commands from the outside the command line user can also open a new terminal
|
||||
and export the HOME folder of a node ``export HOME=/tmp/minindn/a && cd ~``
|
||||
|
||||
Working Directory Structure
|
||||
---------------------------
|
||||
|
||||
Currently Mini-NDN uses /tmp/minindn as the working directory if not
|
||||
specified otherwise by using the option --work-dir.
|
||||
|
||||
Each node is given a HOME directory under /tmp/minindn/<node-name> where
|
||||
<node-name> is the name of the node specified in the [nodes] section of
|
||||
the conf file.
|
||||
|
||||
NFD
|
||||
___
|
||||
|
||||
- NFD conf file is stored at ``/tmp/minindn/<node-name>/nfd.conf``
|
||||
|
||||
- NFD log file is stored at ``/tmp/minindn/<node-name>/log/nfd.log``
|
||||
|
||||
- ``.ndn`` folder is stored at ``/tmp/minindn/<node-name>/.ndn``
|
||||
|
||||
NLSR
|
||||
____
|
||||
|
||||
- NLSR conf file is stored at ``/tmp/minindn/<node-name>/nlsr.conf``
|
||||
- NLSR log file is stored at ``/tmp/minindn/<node-name>/log/nlsr.log``
|
||||
|
||||
When security is enabled, NLSR security certificates are stored in:
|
||||
``/tmp/minindn/<node-name>/security`` Note that no NLSR publishes the root
|
||||
certificate, Mini-NDN installs root.cert in security folder for each
|
||||
NLSR.
|
||||
|
||||
While a host's NLSR neighbors are by default populated by adjacent nodes in wired scenarios,
|
||||
for those running NLSR on wifi stations it is required that you specify "neighbor" faces
|
||||
manually. The framework for this is provided either via a dictionary object or through
|
||||
additional sections in topology files, and may also be used for wired experiments.
|
||||
See an example of a topo of this sort in ``mini-ndn/topologies/wifi/nlsr_wifi_example.conf``.
|
||||
NLSR faces to be created can be manually specified from topology files in a ``[faces]``
|
||||
section, with the format ``nodeA:nodeB [cost=X]``. You should then call the ``setupFaces()``
|
||||
method of an initialized Mini-NDN object to get a dictionary based on this parse in the format
|
||||
``faceA:[(faceB, cost), (faceC, cost),...]``, which can finally be passed to the NLSR
|
||||
helper via the faceDict parameter. An example experiment using this methodology is located
|
||||
at ``mini-ndn/examples/wifi/nlsr_wifi.py``. Note that the aforementioned dict can also be
|
||||
created manually in the previously established format.
|
||||
|
||||
Routing Options
|
||||
----------------
|
||||
|
||||
Link State Routing (NLSR)
|
||||
_________________________
|
||||
By default, Mini-NDN uses `NLSR <https://github.com/named-data/NLSR>`__ for route management i.e route computation, route installation and so on. Additionally, the command line utility ``nlsrc`` can be used to advertise and withdraw prefixes and route status.
|
||||
|
||||
|
||||
NDN Routing Helper
|
||||
____________________
|
||||
Computes link-state or hyperbolic route/s from a given minindn topology and installs them in the FIB. The major benefit of the routing helper is to eliminate the overhead of NLSR when using larger topology. See ``examples/static_routing_experiment.py`` on how to use the helper class.
|
||||
|
||||
**IMPORTANT:** NLSR and NDN Routing Helper are mutually exclusive, meaning you can only use one at a time, not both.
|
||||
|
||||
**Note:** The current version of ``ndn_routing_helper`` is still in the experimental phase. It doesn't support node or link failure and runtime prefix advertisement/withdrawal. If you find any bug please report `here <https://redmine.named-data.net/projects/mini-ndn>`__ or contact the :doc:`authors <authors>`.
|
||||
|
||||
IP Routing Helper
|
||||
____________________
|
||||
|
||||
The routing helper allows to run IP-based evaluations with Mini-NDN. It configures static IP routes to all nodes, which means that all nodes can reach all other nodes in the network
|
||||
reachable, even when relaying is required. Please see ``examples/ip_rounting_experiment.py`` for a simple example.
|
||||
@@ -1,48 +0,0 @@
|
||||
FAQ
|
||||
=========
|
||||
|
||||
* ``How does Mini-NDN work?``
|
||||
|
||||
Mini-NDN's principles of operation most heavily rely on the underlying Mininet code it relies on.
|
||||
Mininet uses a combination of limited containerization via network namespaces (which give processes
|
||||
isolated interfaces and routing tables) and emulated ethernet connections via veth connections.
|
||||
In practical terms, Mini-NDN ensures that processes running on distinct nodes will run seperately
|
||||
and without interfering with each other.
|
||||
|
||||
* ``How does Mini-NDN apply link loss/delay/etc.?``
|
||||
|
||||
Mini-NDN relies on Mininet's code, which in turn uses the Linux tc utility on a stations' virtualized
|
||||
interfaces to apply configurations known as qdiscs to these links. Note that these will only be applied
|
||||
on egress packets from a station where it's applied.
|
||||
For more information on qdiscs and tc, view the information `here <http://wiki.linuxwall.info/doku.php/en%3aressources%3adossiers%3anetworking%3atraffic_control>`_.
|
||||
|
||||
* ``Why use Mini-NDN rather than a simulator such as ndnSIM?``
|
||||
|
||||
Mini-NDN is easier and faster to use because, rather than serving as a mathematical model of a network,
|
||||
it is instead running real NDN code on a real Linux kernel. This also means it's quite useful for testing code changes, as it can more accurately test the interaction of software componenents.
|
||||
|
||||
+--------------------------------+-----------------------------------------------------------+-----------------------------------------+
|
||||
| Criteria | Mini-NDN | ndnSIM |
|
||||
+--------------------------------+-----------------------------------------------------------+-----------------------------------------+
|
||||
| Based On | Mininet | ns-3 |
|
||||
+--------------------------------+-----------------------------------------------------------+-----------------------------------------+
|
||||
| Language | Python | C++ |
|
||||
+--------------------------------+-----------------------------------------------------------+-----------------------------------------+
|
||||
| Library/Forwarder/Applications | Use system binaries (free to use any compatible versions) | Integrated (fixed release version) |
|
||||
+--------------------------------+-----------------------------------------------------------+-----------------------------------------+
|
||||
| Application language | C++ (ndn-cxx), CCL (ndn-cpp, PyNDN, ndn-js, jNDN) | C++ (ndn-cxx) |
|
||||
+--------------------------------+-----------------------------------------------------------+-----------------------------------------+
|
||||
| Simulation size | Medium - Large (cluster edition in development) | Large (can be parallelized using MPI) |
|
||||
+--------------------------------+-----------------------------------------------------------+-----------------------------------------+
|
||||
| Simulation time | Real time | Quick (depending on size/memory) |
|
||||
+--------------------------------+-----------------------------------------------------------+-----------------------------------------+
|
||||
| Porting real applications | Drop in | Changes required |
|
||||
+--------------------------------+-----------------------------------------------------------+-----------------------------------------+
|
||||
| Interactivity | Can interact directly with NFD, NLSR or Apps | Can show stats while running |
|
||||
+--------------------------------+-----------------------------------------------------------+-----------------------------------------+
|
||||
| Logs | May need to manually setup to collect | Available with tracer |
|
||||
+--------------------------------+-----------------------------------------------------------+-----------------------------------------+
|
||||
| Post processing scripts | Not available, users need to write their own | Available to use to process the logs |
|
||||
+--------------------------------+-----------------------------------------------------------+-----------------------------------------+
|
||||
| Other | Not yet supported (Wifi in development) | WiFi, LTE, etc available from ns-3 |
|
||||
+--------------------------------+-----------------------------------------------------------+-----------------------------------------+
|
||||
@@ -1,10 +0,0 @@
|
||||
Past NDN Hackathon projects
|
||||
===========================
|
||||
|
||||
- 1st NDN Hackathon: `NFD integration test <http://ndncomm.github.io/mini-ndn/>`_.
|
||||
- 2st NDN Hackathon: `Mini-NDN metrics <https://github.com/2nd-ndn-hackathon/mini-ndn-metrics>`_.
|
||||
- 3rd NDN Hackathon: `Mini-NDN cluster <https://github.com/3rd-ndn-hackathon/mini-NDN-cluster>`_.
|
||||
- 4th NDN Hackathon: `Mini-NDN wifi <https://github.com/4th-ndn-hackathon/Mini-NDN-Wi-Fi>`_.
|
||||
- 7th NDN Hackathon: `Mini-NDN documentation <https://github.com/7th-ndn-hackathon/mini-ndn-documentation>`_.
|
||||
- 11th NDN Hackathon: `Mini-NDN improvements <https://11th-ndn-hackathon.named-data.net/hacks.html#8-mini-ndn-improvements>`_.
|
||||
- 12th NDN Hackathon: `Mini-NDN improvements and Refactoring <https://12th-ndn-hackathon.named-data.net/hacks.html#7-mini-ndn-improvements>`_.
|
||||
-128
@@ -1,128 +0,0 @@
|
||||
Howtos
|
||||
======
|
||||
|
||||
Connect Mini-NDN nodes to an outside network
|
||||
---------------------------------------------
|
||||
|
||||
Mini-NDN nodes can be connected to an outside network indirectly by
|
||||
running NFD on the local machine:
|
||||
|
||||
::
|
||||
|
||||
(Mini-NDN node) ------ (NFD running on the host machine where Mini-NDN is running) ------- (External Network)
|
||||
|
||||
Add a node in root namespace
|
||||
____________________________
|
||||
|
||||
For this simple example, we can use a single node topology with node 'a'
|
||||
|
||||
If we want node 'a' to connect to the host machine, we need to add a
|
||||
"root" node which has a link with node "a."
|
||||
|
||||
Then the following code can be used:
|
||||
|
||||
.. code:: python
|
||||
|
||||
topo = Topo()
|
||||
root = topo.addHost('root', inNamespace=False)
|
||||
a = topo.addHost('a')
|
||||
topo.addLink(root, a, delay='10ms')
|
||||
|
||||
ndn = Minindn(topo=topo)
|
||||
|
||||
...
|
||||
|
||||
Configuration
|
||||
_____________
|
||||
|
||||
Run Mini-NDN with the above code and issue ifconfig on the local
|
||||
machine to confirm the addition of the interface. You should be able to
|
||||
locate "root-eth0":
|
||||
|
||||
::
|
||||
|
||||
root-eth0 Link encap:Ethernet HWaddr 3e:eb:77:d2:6f:1f
|
||||
inet addr:1.0.0.9 Bcast:1.0.0.11 Mask:255.255.255.252
|
||||
inet6 addr: fe80::3ceb:77ff:fed2:6f1f/64 Scope:Link
|
||||
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
|
||||
RX packets:34 errors:0 dropped:0 overruns:0 frame:0
|
||||
TX packets:33 errors:0 dropped:0 overruns:0 carrier:0
|
||||
collisions:0 txqueuelen:1000
|
||||
RX bytes:2667 (2.6 KB) TX bytes:2797 (2.7 KB)
|
||||
|
||||
To make the IP address associated with this interface persistent, add
|
||||
the following line to /etc/network/interfaces and reboot your machine:
|
||||
|
||||
::
|
||||
|
||||
iface root-eth0 inet manual
|
||||
|
||||
Check connection
|
||||
________________
|
||||
|
||||
After rebooting, run Mini-NDN and issue the following command:
|
||||
|
||||
::
|
||||
|
||||
mini-ndn>net
|
||||
a a-eth0:b-eth0 a-eth1:c-eth0 a-eth2:root-eth0
|
||||
|
||||
Node "a" is connected to "root-eth0". Now issue "ifconfig a-eth2" on
|
||||
node "a":
|
||||
|
||||
::
|
||||
|
||||
mini-ndn>a ifconfig a-eth2
|
||||
a-eth2 Link encap:Ethernet HWaddr fa:76:d4:86:d3:ba
|
||||
inet addr:1.0.0.10 Bcast:1.0.0.11 Mask:255.255.255.252
|
||||
|
||||
As learned from the previous step, the IP address of root-eth0 is
|
||||
1.0.0.9.
|
||||
|
||||
::
|
||||
|
||||
mini-ndn>a ping 1.0.0.9
|
||||
PING 1.0.0.9 (1.0.0.9) 56(84) bytes of data.
|
||||
64 bytes from 1.0.0.9: icmp_seq=1 ttl=64 time=0.137 ms
|
||||
64 bytes from 1.0.0.9: icmp_seq=2 ttl=64 time=0.123 ms
|
||||
|
||||
The host machine will also be able to ping node "a":
|
||||
|
||||
::
|
||||
|
||||
VirtualBox:~$ ping 1.0.0.10
|
||||
PING 1.0.0.10 (1.0.0.10) 56(84) bytes of data.
|
||||
64 bytes from 1.0.0.10: icmp_seq=1 ttl=64 time=0.086 ms
|
||||
|
||||
Run NFD on local machine and register route
|
||||
___________________________________________
|
||||
|
||||
Start NFD on the local machine by using:
|
||||
|
||||
::
|
||||
|
||||
sudo nfd
|
||||
|
||||
The "nfd-start" script cannot be used, since the script allows only one
|
||||
instance of NFD at a time. The NFD processes running on the Mini-NDN
|
||||
nodes will prevent the "nfd-start" script from working.
|
||||
|
||||
Now, using "nfdc register", we can register a route from node "a" in
|
||||
Mini-NDN to the NFD process on the host machine and from the host
|
||||
machine to an external machine.
|
||||
|
||||
Also, if the local machine has a public IP, Mini-NDN nodes can be
|
||||
reached via external machines.
|
||||
|
||||
Generate NDN testbed topology
|
||||
___________________________________________
|
||||
|
||||
Run the following install.sh command
|
||||
|
||||
::
|
||||
|
||||
python3 util/testbed_topo_generator.py
|
||||
|
||||
This will place a "testbed.conf" file in the topologies subdirectory,
|
||||
where it can be referenced as desired. To update the topology, simply
|
||||
rerun this command.
|
||||
@@ -1,30 +0,0 @@
|
||||
.. Mini-NDN documentation master file, created by
|
||||
sphinx-quickstart on Mon Sep 23 11:15:54 2019.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
Mini-NDN: A Mininet-based NDN emulator
|
||||
======================================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Contents
|
||||
|
||||
introduction
|
||||
install
|
||||
experiment
|
||||
howtos
|
||||
release-notes
|
||||
faq
|
||||
hackathon
|
||||
videos
|
||||
|
||||
Helpful Links
|
||||
-------------
|
||||
|
||||
* `NDN Website <http://named-data.net/>`_
|
||||
* `NDN Contributor's Guide <https://github.com/named-data/NFD/blob/master/CONTRIBUTING.md>`_
|
||||
* `Mininet Documentation <http://mininet.org/>`_
|
||||
* `Mini-NDN redmine <https://redmine.named-data.net/projects/mini-ndn>`_
|
||||
* `Mailing list <http://www.lists.cs.ucla.edu/mailman/listinfo/mini-ndn>`_
|
||||
* :doc:`Mini-NDN Team <authors>`
|
||||
@@ -1,184 +0,0 @@
|
||||
Install
|
||||
=======
|
||||
|
||||
Prerequisites
|
||||
-------------
|
||||
|
||||
Mini-NDN is tested on the following Linux distributions:
|
||||
|
||||
- Ubuntu 20.04 (recommended)
|
||||
- Ubuntu 22.04
|
||||
- Debian 11 (WiFi scenario does not work)
|
||||
- Fedora 33 (WiFi scenario does not work)
|
||||
|
||||
You must have sudo privileges to install and run Mini-NDN.
|
||||
|
||||
Using Vagrantfile
|
||||
-----------------
|
||||
|
||||
With Vagrant installed, simply do ``vagrant up`` which will bring up an Ubuntu 18.04 virtual machine
|
||||
and install Mini-NDN and all its dependencies on it. Please make sure to tweak the CPU core count
|
||||
(default 4 cores) and RAM (default 4GB) according to your needs before doing vagrant up. Mini-NDN
|
||||
can be found in /home/vagrant/mini-ndn which is a symlink to /vagrant if Vagrantfile was used from within mini-ndn cloned on the host. Otherwise it is an actual clone of mini-ndn.
|
||||
|
||||
Using install.sh
|
||||
----------------
|
||||
|
||||
Mini-NDN has the following dependencies:
|
||||
|
||||
- `NDN Forwarding Daemon (NFD) <https://named-data.net/doc/NFD/>`_
|
||||
- `Named Data Link State Routing (NLSR) <https://named-data.net/doc/NLSR/>`_
|
||||
- `NDN Essential Tools (ndn-tools) <https://github.com/named-data/ndn-tools>`_
|
||||
- `NDN Traffic Generator <https://github.com/named-data/ndn-traffic-generator>`_
|
||||
- `infoedit <https://github.com/NDN-Routing/infoedit>`_
|
||||
- `Mininet <http://mininet.org/>`_
|
||||
- `Mininet-WiFi <https://mininet-wifi.github.io/>`_ (optional)
|
||||
|
||||
To install Mini-NDN and its dependencies, clone this repository and run:
|
||||
|
||||
::
|
||||
|
||||
./install.sh
|
||||
|
||||
The script accepts various command line flags.
|
||||
Some notable flags are:
|
||||
|
||||
- ``-y`` skips interactive confirmation before installation.
|
||||
- ``--ppa`` prefers installing NDN software from `named-data PPA <https://launchpad.net/~named-data/+archive/ubuntu/ppa>`_.
|
||||
This shortens installation time by downloading binary packages, but is only available on Ubuntu.
|
||||
- ``--source`` prefers installing NDN software from source code.
|
||||
|
||||
IMPORTANT: For now, Mininet-WiFi only works with ``--source`` installation because the current NFD release (0.7.1) doesn't
|
||||
incorporate `issue 5155 <https://redmine.named-data.net/issues/5155>`, a required patch for WiFi module to work properly.
|
||||
With the next NFD release, Mininet-WiFi will work with both ``source`` and ``ppa``. Alternatively, you can
|
||||
checkout (at your own risk) a third-party source "`Use NFD nightly with Mini-NDN <https://yoursunny.com/t/2021/NFD-nightly-minindn/>`", which provides
|
||||
NFD-nightly version and contains all the necessary patches.
|
||||
|
||||
- ``--dummy-keychain`` patches ndn-cxx to use an in-memory dummy KeyChain, which reduces CPU overhead
|
||||
and allows you to scale up Mini-NDN experiments. Large Mini-NDN experiments would run significantly
|
||||
faster after applying this patch. However, your experiments cannot use any NDN security related
|
||||
features (signatures, verifier, access control, etc).
|
||||
- ``--no-wifi`` skips Mininet-WiFi dependency.
|
||||
Currently Mininet-WiFi only works on Ubuntu, so that you must specify this option when installing on other distros.
|
||||
|
||||
You can see all command line flags by running:
|
||||
|
||||
::
|
||||
|
||||
./install.sh -h
|
||||
|
||||
The script uses ``setup.py develop`` to point the system install of Python packages to the codebase
|
||||
directory. Therefore, you can modify ``mininet``, ``mininet-wifi``, and ``mini-ndn``, and the
|
||||
changes will be reflected immediately.
|
||||
|
||||
If NDN software is installed from source code (not PPA), the code is downloaded to ``dl`` directory
|
||||
under your ``mini-ndn`` clone. If you modify the source code, you need to manually recompile and
|
||||
reinstall the software (``./waf && sudo ./waf install``).
|
||||
|
||||
|
||||
Installing Dependencies
|
||||
-----------------------
|
||||
|
||||
This section outlines how to install dependnecies manually.
|
||||
If you used ``install.sh``, you do not need to perform these steps.
|
||||
|
||||
Mininet
|
||||
_______
|
||||
|
||||
Mini-NDN is based on Mininet. To install Mininet:
|
||||
|
||||
::
|
||||
|
||||
git clone --depth 1 https://github.com/mininet/mininet.git
|
||||
|
||||
After Mininet source is on your system, run the following command to
|
||||
install Mininet core dependencies and Open vSwitch:
|
||||
|
||||
::
|
||||
|
||||
./util/install.sh -nv
|
||||
|
||||
To check if Mininet is working correctly, run this test:
|
||||
|
||||
::
|
||||
|
||||
sudo mn --test pingall
|
||||
|
||||
This will print out a series of statements that show the test setup and
|
||||
the results of the test. Look for ``Results:`` two-thirds of the way
|
||||
down where it will indicate the percentage of dropped packets. Your
|
||||
results should show "0% dropped (2/2 received)".
|
||||
|
||||
NOTE: Mini-NDN, while providing a high level of emulation of hosts,
|
||||
requires programs to be installed onto your computer. It will not work
|
||||
if they are not installed. If you do not want NDN software installed
|
||||
onto your computer, you can use a virtual machine, which can be quite
|
||||
simply set up with the provided Vagrantfile.
|
||||
|
||||
NDN dependencies
|
||||
________________
|
||||
|
||||
Each node in Mini-NDN will run the official implementation of NDN
|
||||
installed on your system. The following dependencies are needed:
|
||||
|
||||
Mini-NDN uses NFD, NLSR, and ndn-tools.
|
||||
|
||||
- To install NFD: https://named-data.net/doc/NFD/current/INSTALL.html
|
||||
- To install NLSR: https://named-data.net/doc/NLSR/current/INSTALL.html
|
||||
- To install ndn-tools: https://github.com/named-data/ndn-tools
|
||||
|
||||
.. warning::
|
||||
Please do not try to install NDN software from both the source (GitHub) and PPA (apt).
|
||||
It will not work in most cases! If you used ./install.sh -a in the past but now want
|
||||
to use apt, please run ``sudo ./waf uninstall`` in all the NDN projects before proceeding
|
||||
with apt. Similarly, remove from apt if switching to source.
|
||||
|
||||
Please see the :ref:`scaling-note <scaling-note>` to learn about disabling
|
||||
security for better scalability.
|
||||
|
||||
Note that all three of these can be installed from the Named Data PPA.
|
||||
Instructions for setting it up can be found in the NFD installation
|
||||
instructions. Note that PPA and installs from source **cannot** be
|
||||
mixed. You must completely remove PPA installs from the system if switching
|
||||
to source and vice-versa.
|
||||
|
||||
For PPA installs, if you are using a custom nfd.conf file in an experiment, you should
|
||||
place it in /usr/local/etc/ndn/ rather than /etc/ndn/. This is to avoid
|
||||
a bug from the default configuration file for the PPA, which is
|
||||
incompatible with Mini-NDN.
|
||||
|
||||
Infoedit
|
||||
________
|
||||
|
||||
Infoedit is used to edit configuration files for NFD and NLSR.
|
||||
To install infoedit:
|
||||
|
||||
::
|
||||
|
||||
git clone --depth 1 https://github.com/NDN-Routing/infoedit
|
||||
cd infoedit
|
||||
make
|
||||
sudo make install
|
||||
|
||||
Verification
|
||||
------------
|
||||
|
||||
You can execute the following example to bring up the Mini-NDN command line
|
||||
with NFD and NLSR running on each node:
|
||||
|
||||
::
|
||||
|
||||
sudo python examples/mnndn.py
|
||||
|
||||
You can use these steps to run the sample pingall experiment:
|
||||
|
||||
1. Issue the command: ``sudo python examples/nlsr/pingall.py``
|
||||
2. When the ``mini-ndn>`` CLI prompt appears, the experiment has
|
||||
finished. On the Mini-NDN CLI, issue the command ``exit`` to exit the
|
||||
experiment.
|
||||
3. Issue the command:
|
||||
``grep -c content /tmp/minindn/*/ping-data/*.txt``. Each file should
|
||||
report a count of 50.
|
||||
4. Issue the command:
|
||||
``grep -c timeout /tmp/minindn/*/ping-data/*.txt``. Each file should
|
||||
report a count of 0.
|
||||
@@ -1,40 +0,0 @@
|
||||
Introduction
|
||||
=================
|
||||
|
||||
If you are new to the NDN community of software generally, read the
|
||||
`Contributor's Guide <https://github.com/named-data/NFD/blob/master/CONTRIBUTING.md>`_.
|
||||
|
||||
What is Mini-NDN?
|
||||
-----------------
|
||||
|
||||
Mini-NDN is a lightweight networking emulation tool that enables testing, experimentation, and
|
||||
research on the NDN platform. It was initially based on `Mini-CCNx <https://github.com/chesteve/mn-ccnx>`_ which was a fork of `Mininet <https://github.com/mininet/mininet>`_. Mini-NDN uses the NDN libraries, NFD, NLSR, and tools released by the `NDN project <http://named-data.net/codebase/platform/>`_ to emulate an NDN network on a single system.
|
||||
|
||||
The first release of Mini-NDN is developed by members of the NSF-sponsored NDN project team.
|
||||
Mini-NDN is open to contribution from the public.
|
||||
|
||||
.. image:: minindnnet.svg
|
||||
|
||||
License
|
||||
_______
|
||||
|
||||
Mini-NDN is open and free software licensed under the GPL 3.0 license. Mini-NDN is free to all
|
||||
users and developers. For more information about licensing details and limitations,
|
||||
please refer to COPYING.md.
|
||||
|
||||
Feedback/Mailing List
|
||||
_____________________
|
||||
|
||||
Bug reports and feedback are highly appreciated and can be made through our
|
||||
`Redmine site <http://redmine.named-data.net/projects/mini-ndn>`_ and the
|
||||
`mini-ndn mailing list <http://www.lists.cs.ucla.edu/mailman/listinfo/mini-ndn>`_.
|
||||
|
||||
Video
|
||||
_____
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<div id="video-container" class="col-md-6 ">
|
||||
<p>Mini-NDN (content maybe outdated)</p>
|
||||
<iframe width="600" height="345" src="https://www.youtube.com/embed/UxHPqaUwefg" frameborder="0" allowfullscreen=""></iframe>
|
||||
</div>
|
||||
@@ -1,126 +0,0 @@
|
||||
<svg width="692" height="380" xmlns="http://www.w3.org/2000/svg">
|
||||
|
||||
<metadata id="metadata7">image/svg+xml</metadata>
|
||||
<g>
|
||||
<title>background</title>
|
||||
<rect fill="none" id="canvas_background" height="382" width="694" y="-1" x="-1"/>
|
||||
</g>
|
||||
<g>
|
||||
<title>Layer 1</title>
|
||||
<g stroke="null" id="g3790">
|
||||
<rect stroke="#000000" fill="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="4" id="rect2985" width="680.84682" height="350.443392" x="4.962804" y="5.773119" rx="0.324652"/>
|
||||
<path stroke="#000000" fill="none" stroke-miterlimit="4" d="m4,175.828501l681.727736,1.357954" id="path3770"/>
|
||||
</g>
|
||||
<rect stroke="#000000" fill="#e3dedb" stroke-width="0.99538" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="4" stroke-dashoffset="0" rx="2.988095" y="176.111777" x="5.140841" height="180.524655" width="680.312936" id="rect3813"/>
|
||||
<rect stroke="#000000" fill="#ffffff" stroke-width="1.602133" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="4" stroke-dasharray="4.80639973, 4.80639973" stroke-dashoffset="0" ry="8.582684" rx="7.476771" y="44.78072" x="55.492192" height="195.219243" width="148.366914" id="rect3764"/>
|
||||
<rect stroke="#000000" fill="#ffffff" stroke-width="1.638042" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="4" stroke-dasharray="4.9141253, 4.9141253" stroke-dashoffset="0" id="rect3772" width="155.115252" height="195.190135" x="270.220868" y="46.153231" rx="7.816845" ry="8.581405"/>
|
||||
<rect stroke="#000000" fill="#ffffff" stroke-width="1.602133" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="4" stroke-dasharray="4.80639973, 4.80639973" stroke-dashoffset="0" ry="8.582684" rx="7.476771" y="45.459696" x="489.86568" height="195.219243" width="148.366914" id="rect3774"/>
|
||||
<text transform="matrix(1.0192718802397343,0,0,0.9115536073383187,-2.8313337784806336,-12.183034089773187) " font-size="16.005802px" font-style="normal" font-weight="normal" fill="#000000" font-family="Sans" id="text3776" y="392.709737" x="551.171304" xml:space="preserve">
|
||||
<tspan stroke="null" font-weight="normal" y="398.194877" x="552.152396" id="tspan3778">Kernel Space</tspan>
|
||||
</text>
|
||||
<text transform="matrix(1.0524848569125234,0,0,0.8827879284304422,-52.30106698198968,-77.5201889102731) " font-size="14.180006px" font-style="normal" font-weight="normal" fill="#000000" font-family="Sans" xml:space="preserve" x="601.466013" y="108.524983" id="text3782">
|
||||
<tspan stroke="null" font-weight="normal" id="tspan3784" x="602.416145" y="114.188857">User Space</tspan>
|
||||
</text>
|
||||
<path stroke="#000000" fill="none" stroke-width="1px" id="path3796" d="m488.56554,176.773764c1.782236,0.480113 1.527628,0.360085 1.527628,0.360085"/>
|
||||
<rect stroke="#000000" fill="#ffffff" stroke-width="0.649807" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="4" stroke-dashoffset="0" rx="0.838801" y="202.851471" x="84.255138" height="26.434283" width="94.537572" id="rect3815"/>
|
||||
<rect stroke="#000000" fill="#ffffff" stroke-width="0.792138" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="4" stroke-dashoffset="0" id="rect3817" width="131.339764" height="28.275376" x="282.077514" y="201.912133" rx="1.165334"/>
|
||||
<path stroke="#000000" fill="none" stroke-width="0.865499px" id="path3823" d="m347.74739,201.086282l0,28.351986"/>
|
||||
<text transform="matrix(0.7776670647796514,0,0,1.19475416519067,-52.30106724263901,-77.52018902784596) " font-size="12.623897px" font-style="normal" font-weight="normal" fill="#000000" font-family="Sans" xml:space="preserve" x="434.634023" y="248.956571" id="text3835">
|
||||
<tspan stroke="null" font-size="15.579311px" font-weight="bold" font-family="Monospace" x="435.91992" y="253.141532" id="tspan3837">n2-eth0</tspan>
|
||||
</text>
|
||||
<text transform="matrix(0.9031157255303414,0,0,1.0287949657006918,-2.8313337284492768,-13.069358400177762) " font-size="13.163442px" font-style="normal" font-weight="normal" fill="#000000" font-family="Sans" xml:space="preserve" x="110.835751" y="225.210496" id="text3863">
|
||||
<tspan stroke="null" font-size="16.245171px" font-weight="bold" font-family="Monospace" x="111.943029" y="230.070551" id="tspan3865">n1-eth0</tspan>
|
||||
</text>
|
||||
<path stroke="#000000" fill="none" stroke-width="0.992521px" id="path3887" d="m131.263954,229.882774l0,67.221296l176.555867,-0.960307l1.337531,-65.300682"/>
|
||||
<rect stroke="#000000" fill="#ffffff" stroke-width="0.676601" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="4" stroke-dashoffset="0" id="rect3891" width="95.679394" height="28.317195" x="515.346399" y="202.85306" rx="0.848932"/>
|
||||
<path stroke="#000000" fill="none" stroke-width="0.992521px" d="m383.833251,230.842992l0,67.221296l176.555867,-0.960307l1.337531,-65.300682" id="path3897"/>
|
||||
<text transform="matrix(0.9778213687833123,0,0,0.9501950205038839,-52.301066914088324,-76.5370021990862) " font-size="16.459864px" font-style="normal" font-weight="normal" fill="#000000" font-family="Sans" xml:space="preserve" x="164.136782" y="409.888383" id="text3907">
|
||||
<tspan stroke="null" id="tspan3915" x="165.159464" y="415.15046">isolated point to point link</tspan>
|
||||
</text>
|
||||
<text transform="matrix(0.9778213687833123,0,0,0.9501950205038839,-52.301066914088324,-76.5370021990862) " font-size="16.459864px" font-style="normal" font-weight="normal" fill="#000000" font-family="Sans" id="text3919" y="409.573373" x="429.378262" xml:space="preserve">
|
||||
<tspan stroke="null" y="414.83545" x="430.400944" id="tspan3921">isolated point to point link</tspan>
|
||||
</text>
|
||||
<text transform="matrix(0.9778213687833123,0,0,0.9501950205038839,-52.301066914088324,-76.5370021990862) " font-size="16.459864px" font-style="normal" font-weight="normal" fill="#000000" font-family="Sans" id="text3923" y="428.078233" x="194.615642" xml:space="preserve">
|
||||
<tspan stroke="null" font-weight="bold" y="433.34031" x="195.638324" id="tspan3925">(eg: 1Mbps, 10ms)</tspan>
|
||||
</text>
|
||||
<text transform="matrix(0.9778213687833123,0,0,0.9501950205038839,-52.301066914088324,-76.5370021990862) " font-size="16.459864px" font-style="normal" font-weight="normal" fill="#000000" font-family="Sans" id="text3927" y="409.888383" x="164.136782" xml:space="preserve">
|
||||
<tspan stroke="null" y="415.15046" x="165.159464" id="tspan3929">Isolated point to point link</tspan>
|
||||
</text>
|
||||
<text transform="matrix(0.9778213687833123,0,0,0.9501950205038839,-52.301066914088324,-76.5370021990862) " font-size="16.459864px" font-style="normal" font-weight="normal" fill="#000000" font-family="Sans" xml:space="preserve" x="429.378262" y="409.573373" id="text3931">
|
||||
<tspan stroke="null" id="tspan3933" x="430.400944" y="414.83545">Isolated point to point link</tspan>
|
||||
</text>
|
||||
<text transform="matrix(0.877827228897152,0,0,1.0584326041598764,-53.30106731891522,-78.4233265445007) " font-size="13.706223px" font-style="normal" font-weight="normal" fill="#000000" font-family="Sans" id="text3935" y="282.545218" x="661.482605" xml:space="preserve">
|
||||
<tspan stroke="null" font-size="16.915024px" font-weight="bold" font-family="Monospace" id="tspan3937" y="287.269185" x="662.621781">n3-eth0</tspan>
|
||||
</text>
|
||||
<text transform="matrix(0.7776670647796514,0,0,1.19475416519067,-52.30106724263901,-77.52018902784596) " font-size="12.623897px" font-style="normal" font-weight="normal" fill="#000000" font-family="Sans" id="text3939" y="248.297391" x="520.751053" xml:space="preserve">
|
||||
<tspan stroke="null" font-size="15.579311px" font-weight="bold" font-family="Monospace" id="tspan3941" y="252.482352" x="522.03695">n2-eth1</tspan>
|
||||
</text>
|
||||
<text transform="matrix(0.8745385143393076,0,0,1.0624128521471832,-2.8313338199928846,-14.069358364146652) " font-size="16.205975px" font-style="normal" font-weight="normal" fill="#000000" font-family="Sans" id="text3943" y="80.648481" x="141.655977" xml:space="preserve">
|
||||
<tspan stroke="null" font-size="20px" font-weight="bold" font-family="Monospace" id="tspan3945" y="85.354749" x="142.799437">n1</tspan>
|
||||
</text>
|
||||
<text transform="matrix(0.8745385143393076,0,0,1.0624128521471832,-2.8313338199928846,-14.069358364146652) " font-size="16.205975px" font-style="normal" font-weight="normal" fill="#000000" font-family="Sans" xml:space="preserve" x="385.430517" y="78.840861" id="text3955">
|
||||
<tspan stroke="null" font-size="20px" font-weight="bold" font-family="Monospace" x="386.573977" y="83.547129" id="tspan3957">n2</tspan>
|
||||
</text>
|
||||
<text transform="matrix(0.8745385143393076,0,0,1.0624128521471832,-2.8313338199928846,-14.069358364146652) " font-size="16.205975px" font-style="normal" font-weight="normal" fill="#000000" font-family="Sans" id="text3959" y="79.181231" x="632.310457" xml:space="preserve">
|
||||
<tspan stroke="null" font-size="20px" font-weight="bold" font-family="Monospace" id="tspan3961" y="83.887499" x="633.453917">n3</tspan>
|
||||
</text>
|
||||
<text transform="matrix(0.9526376792163275,0,0,0.9753141035001042,-53.30106707952308,-77.42332645354041) " font-size="14.959435px" font-style="normal" font-weight="normal" fill="#000000" font-family="Sans" xml:space="preserve" x="125.371536" y="171.060971" id="text3967">
|
||||
<tspan stroke="null" font-weight="normal" id="tspan3969" x="126.421253" y="176.187525">(NDN Container)</tspan>
|
||||
</text>
|
||||
<text transform="matrix(0.9526376792163275,0,0,0.9753141035001042,-53.30106707952308,-77.42332645354041) " font-size="14.959435px" font-style="normal" font-weight="normal" fill="#000000" font-family="Sans" id="text3979" y="170.076461" x="352.011746" xml:space="preserve">
|
||||
<tspan stroke="null" font-weight="normal" y="175.203015" x="353.061463" id="tspan3981">(NDN Container)</tspan>
|
||||
</text>
|
||||
<text transform="matrix(0.9526376792163275,0,0,0.9753141035001042,-53.30106707952308,-77.42332645354041) " font-size="14.959435px" font-style="normal" font-weight="normal" fill="#000000" font-family="Sans" xml:space="preserve" x="580.077396" y="169.091931" id="text3983">
|
||||
<tspan stroke="null" font-weight="normal" id="tspan3985" x="581.127113" y="174.218485">(NDN Container)</tspan>
|
||||
</text>
|
||||
<text transform="matrix(0.9526376792163275,0,0,0.9753141035001042,-53.30106707952308,-77.42332645354041) " font-size="14.959435px" font-style="normal" font-weight="normal" fill="#000000" font-family="Sans" id="text3987" y="193.704961" x="123.946116" xml:space="preserve">
|
||||
<tspan stroke="null" font-weight="bold" y="198.831515" x="124.995833" id="tspan3989">NFD</tspan>
|
||||
</text>
|
||||
<text transform="matrix(0.9526376792163275,0,0,0.9753141035001042,-53.30106707952308,-77.42332645354041) " font-size="14.959435px" font-style="normal" font-weight="normal" fill="#000000" font-family="Sans" xml:space="preserve" x="140.791686" y="208.664491" id="text3991">
|
||||
<tspan stroke="null" font-weight="normal" id="tspan3993" x="141.841403" y="213.791045">n1.sock</tspan>
|
||||
</text>
|
||||
<text transform="matrix(0.9526376792163275,0,0,0.9753141035001042,-53.30106707952308,-77.42332645354041) " font-size="14.959435px" font-style="normal" font-weight="normal" fill="#000000" font-family="Sans" xml:space="preserve" x="123.946116" y="225.286191" id="text3995">
|
||||
<tspan stroke="null" font-weight="bold" id="tspan3997" x="124.995833" y="230.412745">NLSR</tspan>
|
||||
</text>
|
||||
<text transform="matrix(0.9526376792163275,0,0,0.9753141035001042,-53.30106707952308,-77.42332645354041) " font-size="14.959435px" font-style="normal" font-weight="normal" fill="#000000" font-family="Sans" id="text3999" y="240.245731" x="138.385176" xml:space="preserve">
|
||||
<tspan stroke="null" font-weight="normal" y="245.372285" x="139.434893" id="tspan4001">%C1.Router/n1</tspan>
|
||||
</text>
|
||||
<text transform="matrix(0.9526376792163275,0,0,0.9753141035001042,-53.30106707952308,-77.42332645354041) " font-size="14.959435px" font-style="normal" font-weight="normal" fill="#000000" font-family="Sans" xml:space="preserve" x="352.011746" y="191.735901" id="text4003">
|
||||
<tspan stroke="null" font-weight="bold" id="tspan4005" x="353.061463" y="196.862455">NFD</tspan>
|
||||
</text>
|
||||
<text transform="matrix(0.9526376792163275,0,0,0.9753141035001042,-53.30106707952308,-77.42332645354041) " font-size="14.959435px" font-style="normal" font-weight="normal" fill="#000000" font-family="Sans" id="text4007" y="206.695431" x="366.450896" xml:space="preserve">
|
||||
<tspan stroke="null" font-weight="normal" y="211.821985" x="367.500613" id="tspan4009">n2.sock</tspan>
|
||||
</text>
|
||||
<text transform="matrix(0.9526376792163275,0,0,0.9753141035001042,-53.30106707952308,-77.42332645354041) " font-size="14.959435px" font-style="normal" font-weight="normal" fill="#000000" font-family="Sans" id="text4011" y="223.317141" x="352.011746" xml:space="preserve">
|
||||
<tspan stroke="null" font-weight="bold" y="228.443695" x="353.061463" id="tspan4013">NLSR</tspan>
|
||||
</text>
|
||||
<text transform="matrix(0.9526376792163275,0,0,0.9753141035001042,-53.30106707952308,-77.42332645354041) " font-size="14.959435px" font-style="normal" font-weight="normal" fill="#000000" font-family="Sans" xml:space="preserve" x="366.450896" y="238.276671" id="text4015">
|
||||
<tspan stroke="null" font-weight="normal" id="tspan4017" x="367.500613" y="243.403225">%C1.Router/n2</tspan>
|
||||
</text>
|
||||
<text transform="matrix(0.9526376792163275,0,0,0.9753141035001042,-53.30106707952308,-77.42332645354041) " font-size="14.959435px" font-style="normal" font-weight="normal" fill="#000000" font-family="Sans" id="text4019" y="190.751371" x="582.928226" xml:space="preserve">
|
||||
<tspan stroke="null" font-weight="bold" y="195.877925" x="583.977943" id="tspan4021">NFD</tspan>
|
||||
</text>
|
||||
<text transform="matrix(0.9526376792163275,0,0,0.9753141035001042,-53.30106707952308,-77.42332645354041) " font-size="14.959435px" font-style="normal" font-weight="normal" fill="#000000" font-family="Sans" xml:space="preserve" x="599.773686" y="205.710911" id="text4023">
|
||||
<tspan stroke="null" font-weight="normal" id="tspan4025" x="600.823403" y="210.837465">n3.sock</tspan>
|
||||
</text>
|
||||
<text transform="matrix(0.9526376792163275,0,0,0.9753141035001042,-53.30106707952308,-77.42332645354041) " font-size="14.959435px" font-style="normal" font-weight="normal" fill="#000000" font-family="Sans" xml:space="preserve" x="582.928226" y="222.332611" id="text4027">
|
||||
<tspan stroke="null" font-weight="bold" id="tspan4029" x="583.977943" y="227.459165">NLSR</tspan>
|
||||
</text>
|
||||
<text transform="matrix(0.9526376792163275,0,0,0.9753141035001042,-53.30106707952308,-77.42332645354041) " font-size="14.959435px" font-style="normal" font-weight="normal" fill="#000000" font-family="Sans" id="text4031" y="237.292141" x="597.367186" xml:space="preserve">
|
||||
<tspan stroke="null" font-weight="normal" y="242.418695" x="598.416903" id="tspan4033">%C1.Router/n3</tspan>
|
||||
</text>
|
||||
<text transform="matrix(0.9778213687833123,0,0,0.9501950205038839,-52.301066914088324,-76.5370021990862) " font-size="16.459864px" font-style="normal" font-weight="normal" fill="#000000" font-family="Sans" id="text4035" y="102.475413" x="322.701992" xml:space="preserve">
|
||||
<tspan stroke="null" y="107.73749" x="323.724674" id="tspan4037">Isolated NDN nodes</tspan>
|
||||
</text>
|
||||
<text transform="matrix(0.935021961689298,0,0,0.9936889310882339,-2.9798247609055646,-12.086171479984756) " font-size="11.279781px" font-style="normal" font-weight="normal" fill="#000000" font-family="Sans" xml:space="preserve" x="75.549398" y="206.131281" id="text4039">
|
||||
<tspan stroke="null" id="tspan4041" x="76.618892" y="211.163037">Private network space</tspan>
|
||||
</text>
|
||||
<text transform="matrix(0.935021961689298,0,0,0.9936889310882339,-2.9798247609055646,-12.086171479984756) " font-size="11.279781px" font-style="normal" font-weight="normal" fill="#000000" font-family="Sans" id="text4043" y="206.946991" x="308.474828" xml:space="preserve">
|
||||
<tspan stroke="null" y="211.978747" x="309.544322" id="tspan4045">Private network space</tspan>
|
||||
</text>
|
||||
<text transform="matrix(0.935021961689298,0,0,0.9936889310882339,-2.9798247609055646,-12.086171479984756) " font-size="11.279781px" font-style="normal" font-weight="normal" fill="#000000" font-family="Sans" xml:space="preserve" x="540.174348" y="206.131281" id="text4047">
|
||||
<tspan stroke="null" id="tspan4049" x="541.243842" y="211.163037">Private network space</tspan>
|
||||
</text>
|
||||
<text xml:space="preserve" text-anchor="start" font-family="Helvetica,Arial,sans-serif" font-size="15px" id="svg_1" y="377" x="203.5" stroke-width="null" stroke="null" fill="#333">Figure: Relationship between Minindn and Mininet</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 16 KiB |
@@ -1,286 +0,0 @@
|
||||
Release Notes
|
||||
=============
|
||||
|
||||
Mini-NDN version 0.6.0 (Major changes since version 0.5.0)
|
||||
----------------------------------------------------------
|
||||
|
||||
**Breaking Changes**:
|
||||
|
||||
- Rewrite install script (`issue: 4630 <https://redmine.named-data.net/issues/4630>`__)
|
||||
|
||||
- Set dependency versions: PPA, git repository & commit
|
||||
- Separate download and build+install steps
|
||||
- Don't reinstall package if it's already installed
|
||||
- More details `here <https://github.com/named-data/mini-ndn/blob/master/docs/install.rst>`__
|
||||
|
||||
- `Note: <https://redmine.named-data.net/issues/5161>`__ We have dropped support for python 2, the latest Mini-NDN requires at least python 3.0
|
||||
|
||||
**New features**:
|
||||
|
||||
- Update Mini-NDN codebase with Mini-NDN-Wifi code (`issue: 4858 <https://redmine.named-data.net/issues/4858>`__)
|
||||
|
||||
- Provide pre-built Mini-NDN Vagrant box and Docker container
|
||||
|
||||
- Added several new examples:
|
||||
|
||||
- consumer/producer
|
||||
- ndnping
|
||||
- traffic generator
|
||||
- catchunks/putchunks
|
||||
|
||||
- Allow for creation of net object without topology (`issue: 5162 <https://redmine.named-data.net/issues/5162>`__)
|
||||
|
||||
**Improvements and Bug Fixes**:
|
||||
|
||||
- Support running NDN applications on mixed topologies (`issue: 5160 <https://redmine.named-data.net/issues/5160>`__)
|
||||
|
||||
- Support route addition using face-id in `Nfdc` helper (`issue: 5130 <https://redmine.named-data.net/issues/5130>`__)
|
||||
|
||||
- Add wrapper for `ndnpingserver` and fix passing the Mininet host object as a prefix on ndnpingclient
|
||||
|
||||
- Show status of route calculation in `NdnRoutingHelper`
|
||||
|
||||
- Incorporate changes of `NDNPing` Class (wrapper of pingserver and pingclient) in the examples
|
||||
|
||||
- Support simple topology files with no additional parameters
|
||||
|
||||
|
||||
Mini-NDN version 0.5.0 (Major changes since version 0.4.0)
|
||||
----------------------------------------------------------
|
||||
|
||||
**Breaking Changes**:
|
||||
|
||||
- `Mini-NDN re-design <https://redmine.named-data.net/issues/5062>`__: simple and robust design with better quality, control, and more consistency with Mininet
|
||||
|
||||
**New features**:
|
||||
|
||||
- Add a script to generate up-to-date NDN testbed topologies for Mini-NDN
|
||||
|
||||
- Add Mini-NDN utility application for PCAP logging
|
||||
|
||||
- Add NDN routing helper to compute centralized LS and HR routes
|
||||
|
||||
- Add routing helper to allow IP communication in experiments
|
||||
|
||||
- Add startup experiments for NLSR and current testbed topology
|
||||
|
||||
- Move the NDNPing wrapper method to a helper class
|
||||
|
||||
- Create a helper class to provide a wrapper around ``nfdc``
|
||||
|
||||
**Improvements and Bug Fixes**:
|
||||
|
||||
- Change workDir and resultDir to be class attribute
|
||||
|
||||
- Quiet apt install for Vagrant
|
||||
|
||||
- Fix route computation bug in ``ndn_routing_helper``
|
||||
|
||||
- Fix overwriting of existing prefixes in ``ndn_routing_helper``
|
||||
|
||||
- Move log files to resultDir after evaluation finishes
|
||||
|
||||
- Check for duplicate HR coordinates in the topology file
|
||||
|
||||
- Check PSync integration and add a tests case for it
|
||||
|
||||
- Bug fixes in nfdc and experiments
|
||||
|
||||
- Added functionality to check Mini-NDN dependencies
|
||||
|
||||
- Parser fix to avoid an infinite loop
|
||||
|
||||
- Allow use of NFD and NLSR PPA with Mini-NDN
|
||||
|
||||
- Remove arbitrary arguments in favor of parsing arguments from experiment files
|
||||
|
||||
- Auto-complete command-line arguments
|
||||
|
||||
- Add option to set CS size
|
||||
|
||||
- Adjust to use ndn-cxx logging
|
||||
|
||||
|
||||
Mini-NDN version 0.4.0 (changes since version 0.3.0)
|
||||
----------------------------------------------------
|
||||
|
||||
Release date: January 10, 2018
|
||||
|
||||
**New features**:
|
||||
|
||||
- Use SIGQUIT to quit Mini-NDN, SIGINT to kill programs
|
||||
|
||||
- Use Infoedit to edit NFD and NLSR configuration files
|
||||
|
||||
- Use nlsr.conf installed in the system
|
||||
|
||||
- Provide a Vagrantfile to setup Mini-NDN and NDN
|
||||
|
||||
- Provide option to disable NLSR
|
||||
|
||||
- Provide an option to run NLSR in dry-run mode
|
||||
|
||||
- Add option to specify whether to use TCP or UDP face in nlsr.conf
|
||||
|
||||
- Add option to specify arbitrary arguments to use in experiments
|
||||
|
||||
- Include a single option to install Mini-NDN and all the dependencies
|
||||
|
||||
**Bug fixes**:
|
||||
|
||||
- Fix "key does not exist error" after NLSR starts
|
||||
|
||||
- Update install.sh to call ldconfig after installing ChronoSync
|
||||
|
||||
- Add hyperbolic coordinates to default topology
|
||||
|
||||
**Misc changes**:
|
||||
|
||||
- Add an experiment to test nlsrc
|
||||
|
||||
- Create faces in NFD for each neighbor in NLSR
|
||||
|
||||
- Update to latest ndn-cxx
|
||||
|
||||
- Use /tmp/minindn folder as default work dir instead of /tmp
|
||||
|
||||
Mini-NDN version 0.3.0 (changes since version 0.2.0)
|
||||
----------------------------------------------------
|
||||
|
||||
Release date: March 3, 2017
|
||||
|
||||
**New features**:
|
||||
|
||||
- Mini-NDN cluster edition
|
||||
|
||||
- New experiments for making NLSR testing easier
|
||||
|
||||
**Bug fixes**:
|
||||
|
||||
- Set site name correctly
|
||||
|
||||
- Install missing certificates in NLSR security config
|
||||
|
||||
- Fix quitting of NLSR due to key not found error
|
||||
|
||||
**Misc changes**:
|
||||
|
||||
- Removed nlsr.conf file, generate it within the code
|
||||
|
||||
- Use argparse instead of deprecated optparse
|
||||
|
||||
- Update security config section for NLSR
|
||||
|
||||
- Change mininet prompt to mini-ndn
|
||||
|
||||
- Set network name at one place
|
||||
|
||||
- Update install.sh script to install openssl
|
||||
|
||||
- Update install.sh script to install cryptopp from package instead of
|
||||
compiling from source
|
||||
|
||||
- Update install.sh to clean build folder every time to get rid of
|
||||
removed files such as old experiments
|
||||
|
||||
- Fix old code - use net.hosts instead of storing hosts in a variable
|
||||
|
||||
- Use nfdc instead of deprecated nfd-status
|
||||
|
||||
Mini-NDN version 0.2.0 (changes since version 0.1.1)
|
||||
----------------------------------------------------
|
||||
|
||||
Release date: August 18, 2016
|
||||
|
||||
**New features**:
|
||||
|
||||
- Automatic security configuration for NLSR
|
||||
|
||||
- Use /usr/local/etc/ndn/nfd.conf as default config file for NFD
|
||||
|
||||
- Class to monitor /proc/$PID/stat file for PID
|
||||
|
||||
- Mini-NDN exits gracefully on SIGINT and non-convergence
|
||||
|
||||
- Faster Mini-NDN install script - does not do apt-get update everytime
|
||||
|
||||
- NLSR is launched with explicit config file for easier process
|
||||
identification
|
||||
|
||||
- Add and update more documentation
|
||||
|
||||
**Bug fixes**:
|
||||
|
||||
- NFD is killed correctly on exit
|
||||
|
||||
- Best route strategy is set correctly
|
||||
|
||||
Mini-NDN version 0.1.1 (changes since version 0.1.0)
|
||||
----------------------------------------------------
|
||||
|
||||
Release date: November 4, 2015
|
||||
|
||||
**New features**:
|
||||
|
||||
- Use nfd.conf.sample from currently installed NFD
|
||||
|
||||
- Add working directory option to allow execution environment outside
|
||||
of /tmp
|
||||
|
||||
- Add results directory option to store experiment results after
|
||||
completion
|
||||
|
||||
- Add support for switches in GUI and configuration file
|
||||
|
||||
- Add failNode and recoverNode methods to Experiment class
|
||||
|
||||
- Add most connected node (MCN) failure experiment
|
||||
|
||||
- Add option to specify percentage of nodes pinged
|
||||
|
||||
**Code changes**:
|
||||
|
||||
- Refactor program options into container class
|
||||
|
||||
- Remove unused "FIB Entries" option from NDN host options
|
||||
|
||||
**Bug fixes**:
|
||||
|
||||
- Abort start up if experiment name is invalid
|
||||
|
||||
- Restart pings after recovery in failure experiment
|
||||
|
||||
Mini-NDN version 0.1.0 (initial release)
|
||||
----------------------------------------
|
||||
|
||||
Release date: July 15, 2015
|
||||
|
||||
Mini-NDN is a lightweight networking emulation tool that enables
|
||||
testing, experimentation, and research on the NDN platform. Based on
|
||||
Mininet, Mini-NDN uses the NDN libraries, NFD, NLSR, and tools released
|
||||
by the `NDN project <http://named-data.net/codebase/platform/>`__ to
|
||||
emulate an NDN network on a single system.
|
||||
|
||||
**Included features**:
|
||||
|
||||
- Run a complete NDN network on a single system
|
||||
|
||||
- Automatic configuration of NLSR to provide a routable NDN network
|
||||
|
||||
- Supports user created NDN applications
|
||||
|
||||
- Create a topology using the included Mini-NDN Edit GUI application
|
||||
|
||||
- Allows individual configuration of NFD and NLSR parameters for each
|
||||
node
|
||||
|
||||
- Provides an experiment management framework for easy creation of
|
||||
custom networking experiments
|
||||
|
||||
- Uses a simple topology file format to define hosts, links, and
|
||||
configuration values
|
||||
|
||||
- Configure network link parameters including bandwidth, delay, and
|
||||
loss rate
|
||||
|
||||
- Includes a pre-configured topology file to replicate the NDN testbed
|
||||
@@ -1,18 +0,0 @@
|
||||
Video Tutorials
|
||||
===============
|
||||
|
||||
Maybe outdated since version 0.5.0.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<div id="video-container" class="col-md-6 ">
|
||||
<p>Mini-NDN Demo at ACM, 2017</p>
|
||||
<iframe width="400" height="230" src="https://www.youtube.com/embed/xYRPHZe18o0" frameborder="0" allowfullscreen=""></iframe>
|
||||
</div>
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<div id="video-container" class="col-md-6 "">
|
||||
<p>Mini-NDN Overview </p>
|
||||
<iframe width="400" height="230" src="https://www.youtube.com/embed/Da7t8yBWzv0" frameborder="0" allowfullscreen="">
|
||||
</div>
|
||||
@@ -25,7 +25,7 @@ DOXYFILE_ENCODING = UTF-8
|
||||
# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
|
||||
# by quotes) that should identify the project.
|
||||
|
||||
PROJECT_NAME = "Mininet Python API Reference Manual"
|
||||
PROJECT_NAME = Mininet
|
||||
|
||||
# The PROJECT_NUMBER tag can be used to enter a project or revision number.
|
||||
# This could be handy for archiving the generated documentation or
|
||||
@@ -114,7 +114,7 @@ FULL_PATH_NAMES = YES
|
||||
# If left blank the directory from which doxygen is run is used as the
|
||||
# path to strip.
|
||||
|
||||
STRIP_FROM_PATH =
|
||||
STRIP_FROM_PATH =
|
||||
|
||||
# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
|
||||
# the path mentioned in the documentation of a class, which tells
|
||||
@@ -919,7 +919,7 @@ COMPACT_LATEX = NO
|
||||
# by the printer. Possible values are: a4, a4wide, letter, legal and
|
||||
# executive. If left blank a4wide will be used.
|
||||
|
||||
PAPER_TYPE = letter
|
||||
PAPER_TYPE = a4wide
|
||||
|
||||
# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
|
||||
# packages that should be included in the LaTeX output.
|
||||
@@ -0,0 +1,61 @@
|
||||
|
||||
Mininet Examples
|
||||
|
||||
These examples are intended to help you get started using
|
||||
Mininet's Python API.
|
||||
|
||||
---
|
||||
|
||||
consoles.py:
|
||||
|
||||
This example creates a grid of console windows, one for each node,
|
||||
and allows interaction with and monitoring of each console, including
|
||||
graphical monitoring.
|
||||
|
||||
emptynet.py:
|
||||
|
||||
This example demonstrates creating an empty network (i.e. with no
|
||||
topology object) and adding nodes to it.
|
||||
|
||||
linearbandwidth.py:
|
||||
|
||||
This example shows how to create a custom topology programatically
|
||||
by subclassing Topo, and how to run a series of tests on it.
|
||||
|
||||
miniedit.py:
|
||||
|
||||
This example demonstrates creating a network via a graphical editor.
|
||||
|
||||
multitest.py:
|
||||
|
||||
This example creates a network and runs multiple tests on it.
|
||||
|
||||
scratchnet.py, scratchnetuser.py:
|
||||
|
||||
These two examples demonstrate how to create a network by using the lowest-
|
||||
level Mininet functions. Generally the higher-level API is easier to use,
|
||||
but scratchnet shows what is going on behind the scenes.
|
||||
|
||||
sshd.py:
|
||||
|
||||
This example shows how to run an sshd process in each host, allowing
|
||||
you to log in via ssh. This requires connecting the Mininet data network
|
||||
to an interface in the root namespace (generaly the control network
|
||||
already lives in the root namespace, so it does not need to be explicitly
|
||||
connected.)
|
||||
|
||||
treeping64:
|
||||
|
||||
This example creates a 64-host tree network, and attempts to check full
|
||||
connectivity using ping, for three different switch/datapath types.
|
||||
|
||||
tree1024.py:
|
||||
|
||||
This example attempts to create a 1024-host network, and then runs the
|
||||
CLI on it. It may run into scalability limits, depending on available
|
||||
memory and sysctl configuration (see INSTALL.)
|
||||
|
||||
udpbwtest.py:
|
||||
|
||||
This example shows how to run a test across an entire network, and monitor
|
||||
the output of a set of hosts in real time.
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"This example doesn't use OpenFlow, but attempts to run sshd in a namespace."
|
||||
|
||||
from mininet.node import Host
|
||||
|
||||
print "*** Creating nodes"
|
||||
h1 = Host( 'h1' )
|
||||
root = Host( 'root', inNamespace=False )
|
||||
|
||||
print "*** Creating links"
|
||||
h1.linkTo( root )
|
||||
|
||||
print "*** Configuring nodes"
|
||||
h1.setIP( h1.intfs[ 0 ], '10.0.0.1', 8 )
|
||||
root.setIP( root.intfs[ 0 ], '10.0.0.2', 8 )
|
||||
|
||||
print "*** Creating banner file"
|
||||
f = open( '/tmp/%s.banner' % h1.name, 'w' )
|
||||
f.write( 'Welcome to %s at %s\n' % ( h1.name, h1.IP() ) )
|
||||
f.close()
|
||||
|
||||
print "*** Running sshd"
|
||||
h1.cmd( '/usr/sbin/sshd -o "Banner /tmp/%s.banner"' % h1.name )
|
||||
|
||||
print "*** You may now ssh into", h1.name, "at", h1.IP()
|
||||
@@ -1,95 +0,0 @@
|
||||
# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
|
||||
#
|
||||
# Copyright (C) 2015-2021, The University of Memphis,
|
||||
# Arizona Board of Regents,
|
||||
# Regents of the University of California.
|
||||
#
|
||||
# This file is part of Mini-NDN.
|
||||
# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
|
||||
#
|
||||
# Mini-NDN 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mini-NDN 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 Mini-NDN, e.g., in COPYING.md file.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
This example demonstrates the functionality of the ndncatchunks and ndnputchunks. There are
|
||||
programs to transfer a file as Data segments in NDN.
|
||||
https://github.com/named-data/ndn-tools/tree/master/tools/chunks.
|
||||
"""
|
||||
|
||||
from time import sleep
|
||||
import subprocess
|
||||
|
||||
from mininet.log import setLogLevel, info
|
||||
from minindn.minindn import Minindn
|
||||
from minindn.util import MiniNDNCLI
|
||||
from minindn.apps.app_manager import AppManager
|
||||
from minindn.apps.nfd import Nfd
|
||||
from minindn.apps.nlsr import Nlsr
|
||||
|
||||
def sendFile(node, prefix, file):
|
||||
"""
|
||||
Publish a file using ndnputchunks
|
||||
:parma mininet.node.Host node: mininet node object
|
||||
:param string prefix: prefix to publish the chunks of the file
|
||||
:param string file: file to publish
|
||||
"""
|
||||
info ("File published:", file)
|
||||
cmd = 'ndnputchunks {}/{} < {} > putchunks.log 2>&1 &'.format(prefix, "fname", file)
|
||||
node.cmd(cmd)
|
||||
# Sleep for appropriate time based on the file size
|
||||
sleep(5)
|
||||
|
||||
def receiveFile(node, prefix, filename):
|
||||
"""
|
||||
Fetch a file using ndncatchunks
|
||||
:parma mininet.node.Host node: mininet node object
|
||||
:param string prefix: producer's prefix under which the file the published
|
||||
:param string file: name given to the file that will be received
|
||||
"""
|
||||
info ("Fething file: ", filename)
|
||||
cmd = 'ndncatchunks {}/{} > {} 2> catchunks.log &'.format(prefix, "fname", filename)
|
||||
node.cmd(cmd)
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel('info')
|
||||
|
||||
# Create a test file to publish
|
||||
testFile = "/tmp/test-chunks"
|
||||
cmd = 'echo "demonstrate file transfer using catchunks and putchunks" > {}'.format(testFile)
|
||||
subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE).communicate()
|
||||
|
||||
Minindn.cleanUp()
|
||||
Minindn.verifyDependencies()
|
||||
ndn = Minindn()
|
||||
ndn.start()
|
||||
|
||||
nfds = AppManager(ndn, ndn.net.hosts, Nfd)
|
||||
nlsrs = AppManager(ndn, ndn.net.hosts, Nlsr)
|
||||
sleep(70)
|
||||
|
||||
# Default topology is used in this experiment "/topologies/default-topology.conf"
|
||||
# lets make node "a" as a producer node, and node "c" as a conumer node
|
||||
producer = ndn.net['a']
|
||||
producerPrefix = "/test-producer" # prefix under which the file will be published
|
||||
consumer = ndn.net['c']
|
||||
|
||||
# Advertise the producer prefix to the network
|
||||
producer.cmd('nlsrc advertise {}'.format(producerPrefix))
|
||||
sleep (5) # sleep for routing convergence.
|
||||
|
||||
sendFile(producer, producerPrefix, testFile)
|
||||
receiveFile(consumer, producerPrefix, "test-chunks")
|
||||
|
||||
MiniNDNCLI(ndn.net)
|
||||
ndn.stop()
|
||||
Executable
+465
@@ -0,0 +1,465 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
consoles.py: bring up a bunch of miniature consoles on a virtual network
|
||||
|
||||
This demo shows how to monitor a set of nodes by using
|
||||
Node's monitor() and Tkinter's createfilehandler().
|
||||
|
||||
We monitor nodes in a couple of ways:
|
||||
|
||||
- First, each individual node is monitored, and its output is added
|
||||
to its console window
|
||||
|
||||
- Second, each time a console window gets iperf output, it is parsed
|
||||
and accumulated. Once we have output for all consoles, a bar is
|
||||
added to the bandwidth graph.
|
||||
|
||||
The consoles also support limited interaction:
|
||||
|
||||
- Pressing "return" in a console will send a command to it
|
||||
|
||||
- Pressing the console's title button will open up an xterm
|
||||
|
||||
Bob Lantz, April 2010
|
||||
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from Tkinter import Frame, Button, Label, Text, Scrollbar, Canvas, Wm, READABLE
|
||||
|
||||
from mininet.log import setLogLevel
|
||||
from mininet.topolib import TreeNet
|
||||
from mininet.term import makeTerms, cleanUpScreens
|
||||
from mininet.util import quietRun
|
||||
|
||||
class Console( Frame ):
|
||||
"A simple console on a host."
|
||||
|
||||
def __init__( self, parent, net, node, height=10, width=32, title='Node' ):
|
||||
Frame.__init__( self, parent )
|
||||
|
||||
self.net = net
|
||||
self.node = node
|
||||
self.prompt = node.name + '# '
|
||||
self.height, self.width, self.title = height, width, title
|
||||
|
||||
# Initialize widget styles
|
||||
self.buttonStyle = { 'font': 'Monaco 7' }
|
||||
self.textStyle = {
|
||||
'font': 'Monaco 7',
|
||||
'bg': 'black',
|
||||
'fg': 'green',
|
||||
'width': self.width,
|
||||
'height': self.height,
|
||||
'relief': 'sunken',
|
||||
'insertbackground': 'green',
|
||||
'highlightcolor': 'green',
|
||||
'selectforeground': 'black',
|
||||
'selectbackground': 'green'
|
||||
}
|
||||
|
||||
# Set up widgets
|
||||
self.text = self.makeWidgets( )
|
||||
self.bindEvents()
|
||||
self.sendCmd( 'export TERM=dumb' )
|
||||
|
||||
self.outputHook = None
|
||||
|
||||
def makeWidgets( self ):
|
||||
"Make a label, a text area, and a scroll bar."
|
||||
|
||||
def newTerm( net=self.net, node=self.node, title=self.title ):
|
||||
"Pop up a new terminal window for a node."
|
||||
net.terms += makeTerms( [ node ], title )
|
||||
label = Button( self, text=self.node.name, command=newTerm,
|
||||
**self.buttonStyle )
|
||||
label.pack( side='top', fill='x' )
|
||||
text = Text( self, wrap='word', **self.textStyle )
|
||||
ybar = Scrollbar( self, orient='vertical', width=7,
|
||||
command=text.yview )
|
||||
text.configure( yscrollcommand=ybar.set )
|
||||
text.pack( side='left', expand=True, fill='both' )
|
||||
ybar.pack( side='right', fill='y' )
|
||||
return text
|
||||
|
||||
def bindEvents( self ):
|
||||
"Bind keyboard and file events."
|
||||
# The text widget handles regular key presses, but we
|
||||
# use special handlers for the following:
|
||||
self.text.bind( '<Return>', self.handleReturn )
|
||||
self.text.bind( '<Control-c>', self.handleInt )
|
||||
self.text.bind( '<KeyPress>', self.handleKey )
|
||||
# This is not well-documented, but it is the correct
|
||||
# way to trigger a file event handler from Tk's
|
||||
# event loop!
|
||||
self.tk.createfilehandler( self.node.stdout, READABLE,
|
||||
self.handleReadable )
|
||||
|
||||
# We're not a terminal (yet?), so we ignore the following
|
||||
# control characters other than [\b\n\r]
|
||||
ignoreChars = re.compile( r'[\x00-\x07\x09\x0b\x0c\x0e-\x1f]+' )
|
||||
|
||||
def append( self, text ):
|
||||
"Append something to our text frame."
|
||||
text = self.ignoreChars.sub( '', text )
|
||||
self.text.insert( 'end', text )
|
||||
self.text.mark_set( 'insert', 'end' )
|
||||
self.text.see( 'insert' )
|
||||
if self.outputHook:
|
||||
self.outputHook( self, text )
|
||||
|
||||
def handleKey( self, event ):
|
||||
"If it's an interactive command, send it to the node."
|
||||
char = event.char
|
||||
if self.node.waiting:
|
||||
self.node.write( char )
|
||||
|
||||
def handleReturn( self, event ):
|
||||
"Handle a carriage return."
|
||||
cmd = self.text.get( 'insert linestart', 'insert lineend' )
|
||||
# Send it immediately, if "interactive" command
|
||||
if self.node.waiting:
|
||||
self.node.write( event.char )
|
||||
return
|
||||
# Otherwise send the whole line to the shell
|
||||
pos = cmd.find( self.prompt )
|
||||
if pos >= 0:
|
||||
cmd = cmd[ pos + len( self.prompt ): ]
|
||||
self.sendCmd( cmd )
|
||||
|
||||
# Callback ignores event
|
||||
# pylint: disable-msg=W0613
|
||||
def handleInt( self, event=None ):
|
||||
"Handle control-c."
|
||||
self.node.sendInt()
|
||||
# pylint: enable-msg=W0613
|
||||
|
||||
def sendCmd( self, cmd ):
|
||||
"Send a command to our node."
|
||||
if not self.node.waiting:
|
||||
self.node.sendCmd( cmd )
|
||||
|
||||
# Callback ignores fds
|
||||
# pylint: disable-msg=W0613
|
||||
def handleReadable( self, fds, timeoutms=None ):
|
||||
"Handle file readable event."
|
||||
data = self.node.monitor( timeoutms )
|
||||
self.append( data )
|
||||
if not self.node.waiting:
|
||||
# Print prompt
|
||||
self.append( self.prompt )
|
||||
# pylint: enable-msg=W0613
|
||||
|
||||
def waiting( self ):
|
||||
"Are we waiting for output?"
|
||||
return self.node.waiting
|
||||
|
||||
def waitOutput( self ):
|
||||
"Wait for any remaining output."
|
||||
while self.node.waiting:
|
||||
# A bit of a trade-off here...
|
||||
self.handleReadable( self, timeoutms=1000)
|
||||
self.update()
|
||||
|
||||
def clear( self ):
|
||||
"Clear all of our text."
|
||||
self.text.delete( '1.0', 'end' )
|
||||
|
||||
|
||||
class Graph( Frame ):
|
||||
|
||||
"Graph that we can add bars to over time."
|
||||
|
||||
def __init__( self, parent=None,
|
||||
bg = 'white',
|
||||
gheight=200, gwidth=500,
|
||||
barwidth=10,
|
||||
ymax=3.5,):
|
||||
|
||||
Frame.__init__( self, parent )
|
||||
|
||||
self.bg = bg
|
||||
self.gheight = gheight
|
||||
self.gwidth = gwidth
|
||||
self.barwidth = barwidth
|
||||
self.ymax = float( ymax )
|
||||
self.xpos = 0
|
||||
|
||||
# Create everything
|
||||
self.title, self.scale, self.graph = self.createWidgets()
|
||||
self.updateScrollRegions()
|
||||
self.yview( 'moveto', '1.0' )
|
||||
|
||||
def createScale( self ):
|
||||
"Create a and return a new canvas with scale markers."
|
||||
height = float( self.gheight )
|
||||
width = 25
|
||||
ymax = self.ymax
|
||||
scale = Canvas( self, width=width, height=height,
|
||||
background=self.bg )
|
||||
opts = { 'fill': 'red' }
|
||||
# Draw scale line
|
||||
scale.create_line( width - 1, height, width - 1, 0, **opts )
|
||||
# Draw ticks and numbers
|
||||
for y in range( 0, int( ymax + 1 ) ):
|
||||
ypos = height * (1 - float( y ) / ymax )
|
||||
scale.create_line( width, ypos, width - 10, ypos, **opts )
|
||||
scale.create_text( 10, ypos, text=str( y ), **opts )
|
||||
return scale
|
||||
|
||||
def updateScrollRegions( self ):
|
||||
"Update graph and scale scroll regions."
|
||||
ofs = 20
|
||||
height = self.gheight + ofs
|
||||
self.graph.configure( scrollregion=( 0, -ofs,
|
||||
self.xpos * self.barwidth, height ) )
|
||||
self.scale.configure( scrollregion=( 0, -ofs, 0, height ) )
|
||||
|
||||
def yview( self, *args ):
|
||||
"Scroll both scale and graph."
|
||||
self.graph.yview( *args )
|
||||
self.scale.yview( *args )
|
||||
|
||||
def createWidgets( self ):
|
||||
"Create initial widget set."
|
||||
|
||||
# Objects
|
||||
title = Label( self, text='Bandwidth (Gb/s)', bg=self.bg )
|
||||
width = self.gwidth
|
||||
height = self.gheight
|
||||
scale = self.createScale()
|
||||
graph = Canvas( self, width=width, height=height, background=self.bg)
|
||||
xbar = Scrollbar( self, orient='horizontal', command=graph.xview )
|
||||
ybar = Scrollbar( self, orient='vertical', command=self.yview )
|
||||
graph.configure( xscrollcommand=xbar.set, yscrollcommand=ybar.set,
|
||||
scrollregion=(0, 0, width, height ) )
|
||||
scale.configure( yscrollcommand=ybar.set )
|
||||
|
||||
# Layout
|
||||
title.grid( row=0, columnspan=3, sticky='new')
|
||||
scale.grid( row=1, column=0, sticky='nsew' )
|
||||
graph.grid( row=1, column=1, sticky='nsew' )
|
||||
ybar.grid( row=1, column=2, sticky='ns' )
|
||||
xbar.grid( row=2, column=0, columnspan=2, sticky='ew' )
|
||||
self.rowconfigure( 1, weight=1 )
|
||||
self.columnconfigure( 1, weight=1 )
|
||||
return title, scale, graph
|
||||
|
||||
def addBar( self, yval ):
|
||||
"Add a new bar to our graph."
|
||||
percent = yval / self.ymax
|
||||
c = self.graph
|
||||
x0 = self.xpos * self.barwidth
|
||||
x1 = x0 + self.barwidth
|
||||
y0 = self.gheight
|
||||
y1 = ( 1 - percent ) * self.gheight
|
||||
c.create_rectangle( x0 , y0, x1, y1, fill='green' )
|
||||
self.xpos += 1
|
||||
self.updateScrollRegions()
|
||||
self.graph.xview( 'moveto', '1.0' )
|
||||
|
||||
def clear( self ):
|
||||
"Clear graph contents."
|
||||
self.graph.delete( 'all' )
|
||||
self.xpos = 0
|
||||
|
||||
def test( self ):
|
||||
"Add a bar for testing purposes."
|
||||
ms = 1000
|
||||
if self.xpos < 10:
|
||||
self.addBar( self.xpos / 10 * self.ymax )
|
||||
self.after( ms, self.test )
|
||||
|
||||
def setTitle( self, text ):
|
||||
"Set graph title"
|
||||
self.title.configure( text=text, font='Helvetica 9 bold' )
|
||||
|
||||
|
||||
class ConsoleApp( Frame ):
|
||||
|
||||
"Simple Tk consoles for Mininet."
|
||||
|
||||
menuStyle = { 'font': 'Geneva 7 bold' }
|
||||
|
||||
def __init__( self, net, parent=None, width=4 ):
|
||||
Frame.__init__( self, parent )
|
||||
self.top = self.winfo_toplevel()
|
||||
self.top.title( 'Mininet' )
|
||||
self.net = net
|
||||
self.menubar = self.createMenuBar()
|
||||
cframe = self.cframe = Frame( self )
|
||||
self.consoles = {} # consoles themselves
|
||||
titles = {
|
||||
'hosts': 'Host',
|
||||
'switches': 'Switch',
|
||||
'controllers': 'Controller'
|
||||
}
|
||||
for name in titles:
|
||||
nodes = getattr( net, name )
|
||||
frame, consoles = self.createConsoles(
|
||||
cframe, nodes, width, titles[ name ] )
|
||||
self.consoles[ name ] = Object( frame=frame, consoles=consoles )
|
||||
self.selected = None
|
||||
self.select( 'hosts' )
|
||||
self.cframe.pack( expand=True, fill='both' )
|
||||
cleanUpScreens()
|
||||
# Close window gracefully
|
||||
Wm.wm_protocol( self.top, name='WM_DELETE_WINDOW', func=self.quit )
|
||||
|
||||
# Initialize graph
|
||||
graph = Graph( cframe )
|
||||
self.consoles[ 'graph' ] = Object( frame=graph, consoles=[ graph ] )
|
||||
self.graph = graph
|
||||
self.graphVisible = False
|
||||
self.updates = 0
|
||||
self.hostCount = len( self.consoles[ 'hosts' ].consoles )
|
||||
self.bw = 0
|
||||
|
||||
self.pack( expand=True, fill='both' )
|
||||
|
||||
# Update callback doesn't use console arg
|
||||
# pylint: disable-msg=W0613
|
||||
def updateGraph( self, console, output ):
|
||||
"Update our graph."
|
||||
m = re.search( r'(\d+) Mbits/sec', output )
|
||||
if not m:
|
||||
return
|
||||
self.updates += 1
|
||||
self.bw += .001 * float( m.group( 1 ) )
|
||||
if self.updates >= self.hostCount:
|
||||
self.graph.addBar( self.bw )
|
||||
self.bw = 0
|
||||
self.updates = 0
|
||||
# pylint: enable-msg=W0613
|
||||
|
||||
def setOutputHook( self, fn=None, consoles=None ):
|
||||
"Register fn as output hook [on specific consoles.]"
|
||||
if consoles is None:
|
||||
consoles = self.consoles[ 'hosts' ].consoles
|
||||
for console in consoles:
|
||||
console.outputHook = fn
|
||||
|
||||
def createConsoles( self, parent, nodes, width, title ):
|
||||
"Create a grid of consoles in a frame."
|
||||
f = Frame( parent )
|
||||
# Create consoles
|
||||
consoles = []
|
||||
index = 0
|
||||
for node in nodes:
|
||||
console = Console( f, self.net, node, title=title )
|
||||
consoles.append( console )
|
||||
row = index / width
|
||||
column = index % width
|
||||
console.grid( row=row, column=column, sticky='nsew' )
|
||||
index += 1
|
||||
f.rowconfigure( row, weight=1 )
|
||||
f.columnconfigure( column, weight=1 )
|
||||
return f, consoles
|
||||
|
||||
def select( self, groupName ):
|
||||
"Select a group of consoles to display."
|
||||
if self.selected is not None:
|
||||
self.selected.frame.pack_forget()
|
||||
self.selected = self.consoles[ groupName ]
|
||||
self.selected.frame.pack( expand=True, fill='both' )
|
||||
|
||||
def createMenuBar( self ):
|
||||
"Create and return a menu (really button) bar."
|
||||
f = Frame( self )
|
||||
buttons = [
|
||||
( 'Hosts', lambda: self.select( 'hosts' ) ),
|
||||
( 'Switches', lambda: self.select( 'switches' ) ),
|
||||
( 'Controllers', lambda: self.select( 'controllers' ) ),
|
||||
( 'Graph', lambda: self.select( 'graph' ) ),
|
||||
( 'Ping', self.ping ),
|
||||
( 'Iperf', self.iperf ),
|
||||
( 'Interrupt', self.stop ),
|
||||
( 'Clear', self.clear ),
|
||||
( 'Quit', self.quit )
|
||||
]
|
||||
for name, cmd in buttons:
|
||||
b = Button( f, text=name, command=cmd, **self.menuStyle )
|
||||
b.pack( side='left' )
|
||||
f.pack( padx=4, pady=4, fill='x' )
|
||||
return f
|
||||
|
||||
def clear( self ):
|
||||
"Clear selection."
|
||||
for console in self.selected.consoles:
|
||||
console.clear()
|
||||
|
||||
def waiting( self, consoles=None ):
|
||||
"Are any of our hosts waiting for output?"
|
||||
if consoles is None:
|
||||
consoles = self.consoles[ 'hosts' ].consoles
|
||||
for console in consoles:
|
||||
if console.waiting():
|
||||
return True
|
||||
return False
|
||||
|
||||
def ping( self ):
|
||||
"Tell each host to ping the next one."
|
||||
consoles = self.consoles[ 'hosts' ].consoles
|
||||
if self.waiting( consoles ):
|
||||
return
|
||||
count = len( consoles )
|
||||
i = 0
|
||||
for console in consoles:
|
||||
i = ( i + 1 ) % count
|
||||
ip = consoles[ i ].node.IP()
|
||||
console.sendCmd( 'ping ' + ip )
|
||||
|
||||
def iperf( self ):
|
||||
"Tell each host to iperf to the next one."
|
||||
consoles = self.consoles[ 'hosts' ].consoles
|
||||
if self.waiting( consoles ):
|
||||
return
|
||||
count = len( consoles )
|
||||
self.setOutputHook( self.updateGraph )
|
||||
for console in consoles:
|
||||
console.node.cmd( 'iperf -sD' )
|
||||
i = 0
|
||||
for console in consoles:
|
||||
i = ( i + 1 ) % count
|
||||
ip = consoles[ i ].node.IP()
|
||||
console.sendCmd( 'iperf -t 99999 -i 1 -c ' + ip )
|
||||
|
||||
def stop( self, wait=True ):
|
||||
"Interrupt all hosts."
|
||||
consoles = self.consoles[ 'hosts' ].consoles
|
||||
for console in consoles:
|
||||
console.handleInt()
|
||||
if wait:
|
||||
for console in consoles:
|
||||
console.waitOutput()
|
||||
self.setOutputHook( None )
|
||||
# Shut down any iperfs that might still be running
|
||||
quietRun( 'killall -9 iperf' )
|
||||
|
||||
def quit( self ):
|
||||
"Stop everything and quit."
|
||||
self.stop( wait=False)
|
||||
Frame.quit( self )
|
||||
|
||||
|
||||
# Make it easier to construct and assign objects
|
||||
|
||||
def assign( obj, **kwargs ):
|
||||
"Set a bunch of fields in an object."
|
||||
obj.__dict__.update( kwargs )
|
||||
|
||||
class Object( object ):
|
||||
"Generic object you can stuff junk into."
|
||||
def __init__( self, **kwargs ):
|
||||
assign( self, **kwargs )
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
network = TreeNet( depth=2, fanout=4 )
|
||||
network.start()
|
||||
app = ConsoleApp( network, width=4 )
|
||||
app.mainloop()
|
||||
network.stop()
|
||||
@@ -1,70 +0,0 @@
|
||||
# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
|
||||
#
|
||||
# Copyright (C) 2015-2021, The University of Memphis,
|
||||
# Arizona Board of Regents,
|
||||
# Regents of the University of California.
|
||||
#
|
||||
# This file is part of Mini-NDN.
|
||||
# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
|
||||
#
|
||||
# Mini-NDN 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mini-NDN 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 Mini-NDN, e.g., in COPYING.md file.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from time import sleep
|
||||
|
||||
from mininet.log import setLogLevel, info
|
||||
from minindn.minindn import Minindn
|
||||
from minindn.util import MiniNDNCLI
|
||||
from minindn.apps.app_manager import AppManager
|
||||
from minindn.apps.nfd import Nfd
|
||||
from minindn.apps.nlsr import Nlsr
|
||||
|
||||
|
||||
"""
|
||||
This example demonstrates a basic consumer-producer using Mini-NDN. It uses ndnpeek and ndnpoke as
|
||||
a consumer and a producer respectively. If you want to build your own consumer/producer program, you
|
||||
can take help from here: https://github.com/named-data/ndn-cxx/tree/master/examples and
|
||||
here: https://github.com/dulalsaurab/multicast-supression-ndn/tree/main/ndn-src/consumer-producer
|
||||
"""
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel('info')
|
||||
|
||||
Minindn.cleanUp()
|
||||
Minindn.verifyDependencies()
|
||||
ndn = Minindn()
|
||||
ndn.start()
|
||||
|
||||
info('Starting nfd and nlsr on nodes')
|
||||
nfds = AppManager(ndn, ndn.net.hosts, Nfd)
|
||||
nlsrs = AppManager(ndn, ndn.net.hosts, Nlsr)
|
||||
sleep(90)
|
||||
|
||||
# Default topology is used in this experiment "/topologies/default-topology.conf"
|
||||
# lets make node "a" as a producer node, and node "c" as a consumer node
|
||||
producer = ndn.net['a']
|
||||
consumer = ndn.net['c']
|
||||
|
||||
# start producer
|
||||
producerPrefix = "/example"
|
||||
producer.cmd('nlsrc advertise {}'.format(producerPrefix))
|
||||
sleep(5) # sleep for routing convergence
|
||||
|
||||
# Make sure that basic consumer/producer example are compiled and installed in the system
|
||||
info('Starting consumer and producer application')
|
||||
producer.cmd("echo 'HELLO WORLD' | ndnpoke {} &> producer.log &".format(producerPrefix))
|
||||
consumer.cmd("ndnpeek -p {} &> consumer.log &".format(producerPrefix))
|
||||
|
||||
MiniNDNCLI(ndn.net)
|
||||
ndn.stop()
|
||||
Executable
+64
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
This example creates a multi-controller network from
|
||||
semi-scratch; note a topo object could also be used and
|
||||
would be passed into the Mininet() constructor.
|
||||
"""
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import Controller, OVSKernelSwitch
|
||||
from mininet.cli import CLI
|
||||
from mininet.log import setLogLevel
|
||||
|
||||
Switch = OVSKernelSwitch
|
||||
|
||||
def addHost( net, N ):
|
||||
"Create host hN and add to net."
|
||||
name = 'h%d' % N
|
||||
ip = '10.0.0.%d' % N
|
||||
return net.addHost( name, ip=ip )
|
||||
|
||||
def multiControllerNet():
|
||||
"Create a network with multiple controllers."
|
||||
|
||||
net = Mininet( controller=Controller, switch=Switch)
|
||||
|
||||
print "*** Creating controllers"
|
||||
c1 = net.addController( 'c1', port=6633 )
|
||||
c2 = net.addController( 'c2', port=6634 )
|
||||
|
||||
print "*** Creating switches"
|
||||
s1 = net.addSwitch( 's1' )
|
||||
s2 = net.addSwitch( 's2' )
|
||||
|
||||
print "*** Creating hosts"
|
||||
hosts1 = [ addHost( net, n ) for n in 3, 4 ]
|
||||
hosts2 = [ addHost( net, n ) for n in 5, 6 ]
|
||||
|
||||
print "*** Creating links"
|
||||
for h in hosts1:
|
||||
s1.linkTo( h )
|
||||
for h in hosts2:
|
||||
s2.linkTo( h )
|
||||
s1.linkTo( s2 )
|
||||
|
||||
print "*** Starting network"
|
||||
net.build()
|
||||
c1.start()
|
||||
c2.start()
|
||||
s1.start( [ c1 ] )
|
||||
s2.start( [ c2 ] )
|
||||
|
||||
print "*** Testing network"
|
||||
net.pingAll()
|
||||
|
||||
print "*** Running CLI"
|
||||
CLI( net )
|
||||
|
||||
print "*** Stopping network"
|
||||
net.stop()
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' ) # for CLI output
|
||||
multiControllerNet()
|
||||
Executable
+44
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
This example shows how to create an empty Mininet object
|
||||
(without a topology object) and add nodes to it manually.
|
||||
"""
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import Controller
|
||||
from mininet.cli import CLI
|
||||
from mininet.log import setLogLevel, info
|
||||
|
||||
def emptyNet():
|
||||
|
||||
"Create an empty network and add nodes to it."
|
||||
|
||||
net = Mininet( controller=Controller )
|
||||
|
||||
info( '*** Adding controller\n' )
|
||||
net.addController( 'c0' )
|
||||
|
||||
info( '*** Adding hosts\n' )
|
||||
h1 = net.addHost( 'h1', ip='10.0.0.1' )
|
||||
h2 = net.addHost( 'h2', ip='10.0.0.2' )
|
||||
|
||||
info( '*** Adding switch\n' )
|
||||
s3 = net.addSwitch( 's3' )
|
||||
|
||||
info( '*** Creating links\n' )
|
||||
h1.linkTo( s3 )
|
||||
h2.linkTo( s3 )
|
||||
|
||||
info( '*** Starting network\n')
|
||||
net.start()
|
||||
|
||||
info( '*** Running CLI\n' )
|
||||
CLI( net )
|
||||
|
||||
info( '*** Stopping network' )
|
||||
net.stop()
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
emptyNet()
|
||||
Executable
+43
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
This example shows how to add an interface (for example a real
|
||||
hardware interface) to a network after the network is created.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from mininet.cli import CLI
|
||||
from mininet.log import setLogLevel, info, error
|
||||
from mininet.net import Mininet
|
||||
from mininet.topolib import TreeTopo
|
||||
from mininet.util import quietRun
|
||||
|
||||
def checkIntf( intf ):
|
||||
"Make sure intf exists and is not configured."
|
||||
if ( ' %s:' % intf ) not in quietRun( 'ip link show' ):
|
||||
error( 'Error:', intf, 'does not exist!\n' )
|
||||
exit( 1 )
|
||||
ips = re.findall( r'\d+\.\d+\.\d+\.\d+', quietRun( 'ifconfig ' + intf ) )
|
||||
if ips:
|
||||
error( 'Error:', intf, 'has an IP address,'
|
||||
'and is probably in use!\n' )
|
||||
exit( 1 )
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
|
||||
newIntf = 'eth1'
|
||||
info( '*** Checking', newIntf, '\n' )
|
||||
checkIntf( newIntf )
|
||||
|
||||
info( '*** Creating network\n' )
|
||||
net = Mininet( topo=TreeTopo( depth=1, fanout=2 ) )
|
||||
|
||||
switch = net.switches[ 0 ]
|
||||
info( '*** Adding', newIntf, 'to switch', switch.name, '\n' )
|
||||
switch.addIntf( newIntf )
|
||||
|
||||
net.start()
|
||||
CLI( net )
|
||||
net.stop()
|
||||
@@ -1,62 +0,0 @@
|
||||
# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
|
||||
#
|
||||
# Copyright (C) 2015-2020, The University of Memphis,
|
||||
# Arizona Board of Regents,
|
||||
# Regents of the University of California.
|
||||
#
|
||||
# This file is part of Mini-NDN.
|
||||
# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
|
||||
#
|
||||
# Mini-NDN 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mini-NDN 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 Mini-NDN, e.g., in COPYING.md file.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from mininet.log import setLogLevel, info
|
||||
|
||||
from minindn.minindn import Minindn
|
||||
from minindn.apps.app_manager import AppManager
|
||||
from minindn.apps.nfd import Nfd
|
||||
from minindn.apps.nlsr import Nlsr
|
||||
from minindn.helpers.ip_routing_helper import IPRoutingHelper
|
||||
|
||||
"""
|
||||
This scenario demonstrates the functionality of the IPRoutingHelper. First, the routing helper
|
||||
calculates and configures routes between all nodes and then calls the `pingAll` command to
|
||||
demonstrate that all nodes are reachable.
|
||||
Successful experiments end with: `*** Results: 0% dropped`
|
||||
|
||||
To demonstrate the IPRoutingHelper in more complex scenarios, consider starting the experiment with
|
||||
the Geant-Topology (topologies/geant.conf).
|
||||
"""
|
||||
if __name__ == '__main__':
|
||||
setLogLevel('info')
|
||||
|
||||
Minindn.cleanUp()
|
||||
Minindn.verifyDependencies()
|
||||
|
||||
ndn = Minindn()
|
||||
|
||||
ndn.start()
|
||||
|
||||
info('Starting NFD on nodes\n')
|
||||
nfds = AppManager(ndn, ndn.net.hosts, Nfd)
|
||||
info('Starting NLSR on nodes\n')
|
||||
nlsrs = AppManager(ndn, ndn.net.hosts, Nlsr)
|
||||
|
||||
# Calculate all routes for IP routing
|
||||
IPRoutingHelper.calcAllRoutes(ndn.net)
|
||||
info("IP routes configured, start ping\n")
|
||||
|
||||
ndn.net.pingAll()
|
||||
|
||||
ndn.stop()
|
||||
Executable
+112
@@ -0,0 +1,112 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
Test bandwidth (using iperf) on linear networks of varying size,
|
||||
using both kernel and user datapaths.
|
||||
|
||||
We construct a network of N hosts and N-1 switches, connected as follows:
|
||||
|
||||
h1 <-> sN+1 <-> sN+2 .. sN+N-1
|
||||
| | |
|
||||
h2 h3 hN
|
||||
|
||||
WARNING: by default, the reference controller only supports 16
|
||||
switches, so this test WILL NOT WORK unless you have recompiled
|
||||
your controller to support 100 switches (or more.)
|
||||
|
||||
In addition to testing the bandwidth across varying numbers
|
||||
of switches, this example demonstrates:
|
||||
|
||||
- creating a custom topology, LinearTestTopo
|
||||
- using the ping() and iperf() tests from Mininet()
|
||||
- testing both the kernel and user switches
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
flush = sys.stdout.flush
|
||||
|
||||
from mininet.net import init, Mininet
|
||||
# from mininet.node import KernelSwitch
|
||||
from mininet.node import UserSwitch, OVSKernelSwitch
|
||||
from mininet.topo import Topo, Node
|
||||
from mininet.log import lg
|
||||
|
||||
class LinearTestTopo( Topo ):
|
||||
"Topology for a string of N hosts and N-1 switches."
|
||||
|
||||
def __init__( self, N ):
|
||||
|
||||
# Add default members to class.
|
||||
super( LinearTestTopo, self ).__init__()
|
||||
|
||||
# Create switch and host nodes
|
||||
hosts = range( 1, N + 1 )
|
||||
switches = range( N + 1 , N + N )
|
||||
for h in hosts:
|
||||
self.add_node( h, Node( is_switch=False ) )
|
||||
for s in switches:
|
||||
self.add_node( s, Node( is_switch=True ) )
|
||||
|
||||
# Wire up switches
|
||||
for s in switches[ :-1 ]:
|
||||
self.add_edge( s, s + 1 )
|
||||
|
||||
# Wire up hosts
|
||||
self.add_edge( hosts[ 0 ], switches[ 0 ] )
|
||||
for h in hosts[ 1: ]:
|
||||
self.add_edge( h, h + N - 1 )
|
||||
|
||||
# Consider all switches and hosts 'on'
|
||||
self.enable_all()
|
||||
|
||||
|
||||
def linearBandwidthTest( lengths ):
|
||||
|
||||
"Check bandwidth at various lengths along a switch chain."
|
||||
|
||||
results = {}
|
||||
switchCount = max( lengths )
|
||||
hostCount = switchCount + 1
|
||||
|
||||
switches = { # 'reference kernel': KernelSwitch,
|
||||
'reference user': UserSwitch,
|
||||
'Open vSwitch kernel': OVSKernelSwitch }
|
||||
|
||||
for datapath in switches.keys():
|
||||
print "*** testing", datapath, "datapath"
|
||||
Switch = switches[ datapath ]
|
||||
results[ datapath ] = []
|
||||
net = Mininet( topo=LinearTestTopo( hostCount ), switch=Switch )
|
||||
net.start()
|
||||
print "*** testing basic connectivity"
|
||||
for n in lengths:
|
||||
net.ping( [ net.hosts[ 0 ], net.hosts[ n ] ] )
|
||||
print "*** testing bandwidth"
|
||||
for n in lengths:
|
||||
src, dst = net.hosts[ 0 ], net.hosts[ n ]
|
||||
print "testing", src.name, "<->", dst.name,
|
||||
bandwidth = net.iperf( [ src, dst ] )
|
||||
print bandwidth
|
||||
flush()
|
||||
results[ datapath ] += [ ( n, bandwidth ) ]
|
||||
net.stop()
|
||||
|
||||
for datapath in switches.keys():
|
||||
print
|
||||
print "*** Linear network results for", datapath, "datapath:"
|
||||
print
|
||||
result = results[ datapath ]
|
||||
print "SwitchCount\tiperf Results"
|
||||
for switchCount, bandwidth in result:
|
||||
print switchCount, '\t\t',
|
||||
print bandwidth[ 0 ], 'server, ', bandwidth[ 1 ], 'client'
|
||||
print
|
||||
print
|
||||
|
||||
if __name__ == '__main__':
|
||||
lg.setLogLevel( 'info' )
|
||||
init()
|
||||
sizes = [ 1, 10, 20, 40, 60, 80, 100 ]
|
||||
print "*** Running linearBandwidthTest", sizes
|
||||
linearBandwidthTest( sizes )
|
||||
Executable
+714
@@ -0,0 +1,714 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
MiniEdit: a simple network editor for Mininet
|
||||
|
||||
This is a simple demonstration of how one might build a
|
||||
GUI application using Mininet as the network model.
|
||||
|
||||
Development version - not entirely functional!
|
||||
|
||||
Bob Lantz, April 2010
|
||||
"""
|
||||
|
||||
from Tkinter import Frame, Button, Label, Scrollbar, Canvas
|
||||
from Tkinter import Menu, BitmapImage, PhotoImage, Wm, Toplevel
|
||||
|
||||
# someday: from ttk import *
|
||||
|
||||
from mininet.log import setLogLevel
|
||||
from mininet.net import Mininet
|
||||
from mininet.util import ipStr
|
||||
from mininet.term import makeTerm, cleanUpScreens
|
||||
|
||||
class MiniEdit( Frame ):
|
||||
|
||||
"A simple network editor for Mininet."
|
||||
|
||||
def __init__( self, parent=None, cheight=200, cwidth=500 ):
|
||||
|
||||
Frame.__init__( self, parent )
|
||||
self.action = None
|
||||
self.appName = 'MiniEdit'
|
||||
|
||||
# Style
|
||||
self.font = ( 'Geneva', 9 )
|
||||
self.smallFont = ( 'Geneva', 7 )
|
||||
self.bg = 'white'
|
||||
|
||||
# Title
|
||||
self.top = self.winfo_toplevel()
|
||||
self.top.title( self.appName )
|
||||
|
||||
# Menu bar
|
||||
self.createMenubar()
|
||||
|
||||
# Editing canvas
|
||||
self.cheight, self.cwidth = cheight, cwidth
|
||||
self.cframe, self.canvas = self.createCanvas()
|
||||
|
||||
# Toolbar
|
||||
self.images = miniEditImages()
|
||||
self.buttons = {}
|
||||
self.active = None
|
||||
self.tools = ( 'Select', 'Host', 'Switch', 'Link' )
|
||||
self.customColors = { 'Switch': 'darkGreen', 'Host': 'blue' }
|
||||
self.toolbar = self.createToolbar()
|
||||
|
||||
# Layout
|
||||
self.toolbar.grid( column=0, row=0, sticky='nsew')
|
||||
self.cframe.grid( column=1, row=0 )
|
||||
self.columnconfigure( 1, weight=1 )
|
||||
self.rowconfigure( 0, weight=1 )
|
||||
self.pack( expand=True, fill='both' )
|
||||
|
||||
# About box
|
||||
self.aboutBox = None
|
||||
|
||||
# Initialize node data
|
||||
self.nodeBindings = self.createNodeBindings()
|
||||
self.nodePrefixes = { 'Switch': 's', 'Host': 'h' }
|
||||
self.widgetToItem = {}
|
||||
self.itemToWidget = {}
|
||||
|
||||
# Initialize link tool
|
||||
self.link = self.linkWidget = None
|
||||
|
||||
# Selection support
|
||||
self.selection = None
|
||||
|
||||
# Keyboard bindings
|
||||
self.bind( '<Control-q>', lambda event: self.quit() )
|
||||
self.bind( '<KeyPress-Delete>', self.deleteSelection )
|
||||
self.bind( '<KeyPress-BackSpace>', self.deleteSelection )
|
||||
self.focus()
|
||||
|
||||
# Event handling initalization
|
||||
self.linkx = self.linky = self.linkItem = None
|
||||
self.lastSelection = None
|
||||
|
||||
# Model initialization
|
||||
self.links = {}
|
||||
self.nodeCount = 0
|
||||
self.net = None
|
||||
|
||||
# Close window gracefully
|
||||
Wm.wm_protocol( self.top, name='WM_DELETE_WINDOW', func=self.quit )
|
||||
|
||||
def quit( self ):
|
||||
"Stop our network, if any, then quit."
|
||||
self.stop()
|
||||
Frame.quit( self )
|
||||
|
||||
def createMenubar( self ):
|
||||
"Create our menu bar."
|
||||
|
||||
font = self.font
|
||||
|
||||
mbar = Menu( self.top, font=font )
|
||||
self.top.configure( menu=mbar )
|
||||
|
||||
# Application menu
|
||||
appMenu = Menu( mbar, tearoff=False )
|
||||
mbar.add_cascade( label=self.appName, font=font, menu=appMenu )
|
||||
appMenu.add_command( label='About MiniEdit', command=self.about,
|
||||
font=font)
|
||||
appMenu.add_separator()
|
||||
appMenu.add_command( label='Quit', command=self.quit, font=font )
|
||||
|
||||
#fileMenu = Menu( mbar, tearoff=False )
|
||||
#mbar.add_cascade( label="File", font=font, menu=fileMenu )
|
||||
#fileMenu.add_command( label="Load...", font=font )
|
||||
#fileMenu.add_separator()
|
||||
#fileMenu.add_command( label="Save", font=font )
|
||||
#fileMenu.add_separator()
|
||||
#fileMenu.add_command( label="Print", font=font )
|
||||
|
||||
editMenu = Menu( mbar, tearoff=False )
|
||||
mbar.add_cascade( label="Edit", font=font, menu=editMenu )
|
||||
editMenu.add_command( label="Cut", font=font,
|
||||
command=lambda: self.deleteSelection( None ) )
|
||||
|
||||
runMenu = Menu( mbar, tearoff=False )
|
||||
mbar.add_cascade( label="Run", font=font, menu=runMenu )
|
||||
runMenu.add_command( label="Run", font=font, command=self.doRun )
|
||||
runMenu.add_command( label="Stop", font=font, command=self.doStop )
|
||||
runMenu.add_separator()
|
||||
runMenu.add_command( label='Xterm', font=font, command=self.xterm )
|
||||
|
||||
# Canvas
|
||||
|
||||
def createCanvas( self ):
|
||||
"Create and return our scrolling canvas frame."
|
||||
f = Frame( self )
|
||||
|
||||
canvas = Canvas( f, width=self.cwidth, height=self.cheight,
|
||||
bg=self.bg )
|
||||
|
||||
# Scroll bars
|
||||
xbar = Scrollbar( f, orient='horizontal', command=canvas.xview )
|
||||
ybar = Scrollbar( f, orient='vertical', command=canvas.yview )
|
||||
canvas.configure( xscrollcommand=xbar.set, yscrollcommand=ybar.set )
|
||||
|
||||
# Resize box
|
||||
resize = Label( f, bg='white' )
|
||||
|
||||
# Layout
|
||||
canvas.grid( row=0, column=1, sticky='nsew')
|
||||
ybar.grid( row=0, column=2, sticky='ns')
|
||||
xbar.grid( row=1, column=1, sticky='ew' )
|
||||
resize.grid( row=1, column=2, sticky='nsew' )
|
||||
|
||||
# Resize behavior
|
||||
f.rowconfigure( 0, weight=1 )
|
||||
f.columnconfigure( 1, weight=1 )
|
||||
f.grid( row=0, column=0, sticky='nsew' )
|
||||
f.bind( '<Configure>', lambda event: self.updateScrollRegion() )
|
||||
|
||||
# Mouse bindings
|
||||
canvas.bind( '<ButtonPress-1>', self.clickCanvas )
|
||||
canvas.bind( '<B1-Motion>', self.dragCanvas )
|
||||
canvas.bind( '<ButtonRelease-1>', self.releaseCanvas )
|
||||
|
||||
return f, canvas
|
||||
|
||||
def updateScrollRegion( self ):
|
||||
"Update canvas scroll region to hold everything."
|
||||
bbox = self.canvas.bbox( 'all' )
|
||||
if bbox is not None:
|
||||
self.canvas.configure( scrollregion=( 0, 0, bbox[ 2 ],
|
||||
bbox[ 3 ] ) )
|
||||
|
||||
def canvasx( self, x_root ):
|
||||
"Convert root x coordinate to canvas coordinate."
|
||||
c = self.canvas
|
||||
return c.canvasx( x_root ) - c.winfo_rootx()
|
||||
|
||||
def canvasy( self, y_root ):
|
||||
"Convert root y coordinate to canvas coordinate."
|
||||
c = self.canvas
|
||||
return c.canvasy( y_root ) - c.winfo_rooty()
|
||||
|
||||
# Toolbar
|
||||
|
||||
def activate( self, toolName ):
|
||||
"Activate a tool and press its button."
|
||||
# Adjust button appearance
|
||||
if self.active:
|
||||
self.buttons[ self.active ].configure( relief='raised' )
|
||||
self.buttons[ toolName ].configure( relief='sunken' )
|
||||
# Activate dynamic bindings
|
||||
self.active = toolName
|
||||
|
||||
def createToolbar( self ):
|
||||
"Create and return our toolbar frame."
|
||||
|
||||
toolbar = Frame( self )
|
||||
|
||||
# Tools
|
||||
for tool in self.tools:
|
||||
cmd = ( lambda t=tool: self.activate( t ) )
|
||||
b = Button( toolbar, text=tool, font=self.smallFont, command=cmd)
|
||||
if tool in self.images:
|
||||
b.config( height=35, image=self.images[ tool ] )
|
||||
# b.config( compound='top' )
|
||||
b.pack( fill='x' )
|
||||
self.buttons[ tool ] = b
|
||||
self.activate( self.tools[ 0 ] )
|
||||
|
||||
# Spacer
|
||||
Label( toolbar, text='' ).pack()
|
||||
|
||||
# Commands
|
||||
for cmd, color in [ ( 'Stop', 'darkRed' ), ( 'Run', 'darkGreen' ) ]:
|
||||
doCmd = getattr( self, 'do' + cmd )
|
||||
b = Button( toolbar, text=cmd, font=self.smallFont,
|
||||
fg=color, command=doCmd )
|
||||
b.pack( fill='x', side='bottom' )
|
||||
|
||||
return toolbar
|
||||
|
||||
def doRun( self ):
|
||||
"Run command."
|
||||
self.activate( 'Select' )
|
||||
for tool in self.tools:
|
||||
self.buttons[ tool ].config( state='disabled' )
|
||||
self.start()
|
||||
|
||||
def doStop( self ):
|
||||
"Stop command."
|
||||
self.stop()
|
||||
for tool in self.tools:
|
||||
self.buttons[ tool ].config( state='normal' )
|
||||
|
||||
# Generic canvas handler
|
||||
#
|
||||
# We could have used bindtags, as in nodeIcon, but
|
||||
# the dynamic approach used here
|
||||
# may actually require less code. In any case, it's an
|
||||
# interesting introspection-based alternative to bindtags.
|
||||
|
||||
def canvasHandle( self, eventName, event ):
|
||||
"Generic canvas event handler"
|
||||
if self.active is None:
|
||||
return
|
||||
toolName = self.active
|
||||
handler = getattr( self, eventName + toolName, None )
|
||||
if handler is not None:
|
||||
handler( event )
|
||||
|
||||
def clickCanvas( self, event ):
|
||||
"Canvas click handler."
|
||||
self.canvasHandle( 'click', event )
|
||||
|
||||
def dragCanvas( self, event ):
|
||||
"Canvas drag handler."
|
||||
self.canvasHandle( 'drag', event )
|
||||
|
||||
def releaseCanvas( self, event ):
|
||||
"Canvas mouse up handler."
|
||||
self.canvasHandle( 'release', event )
|
||||
|
||||
# Currently the only items we can select directly are
|
||||
# links. Nodes are handled by bindings in the node icon.
|
||||
|
||||
def findItem( self, x, y ):
|
||||
"Find items at a location in our canvas."
|
||||
items = self.canvas.find_overlapping( x, y, x, y )
|
||||
if len( items ) == 0:
|
||||
return None
|
||||
else:
|
||||
return items[ 0 ]
|
||||
|
||||
# Canvas bindings for Select, Host, Switch and Link tools
|
||||
|
||||
def clickSelect( self, event ):
|
||||
"Select an item."
|
||||
self.selectItem( self.findItem( event.x, event.y ) )
|
||||
|
||||
def deleteItem( self, item ):
|
||||
"Delete an item."
|
||||
# Don't delete while network is running
|
||||
if self.buttons[ 'Select' ][ 'state' ] == 'disabled' :
|
||||
return
|
||||
# Delete from model
|
||||
if item in self.links:
|
||||
self.deleteLink( item )
|
||||
if item in self.itemToWidget:
|
||||
self.deleteNode( item )
|
||||
# Delete from view
|
||||
self.canvas.delete( item )
|
||||
|
||||
# Callback ignores event
|
||||
# pylint: disable-msg=W0613
|
||||
def deleteSelection( self, event ):
|
||||
"Delete the selected item."
|
||||
if self.selection is not None:
|
||||
self.deleteItem( self.selection )
|
||||
self.selectItem( None )
|
||||
# pylint: enable-msg=W0613
|
||||
|
||||
def nodeIcon( self, node, name ):
|
||||
"Create a new node icon."
|
||||
icon = Button( self.canvas, image=self.images[ node ],
|
||||
text=name, compound='top' )
|
||||
# Unfortunately bindtags wants a tuple
|
||||
bindtags = [ str( self.nodeBindings ) ]
|
||||
bindtags += list( icon.bindtags() )
|
||||
icon.bindtags( tuple( bindtags ) )
|
||||
return icon
|
||||
|
||||
def newNode( self, node, event ):
|
||||
"Add a new node to our canvas."
|
||||
c = self.canvas
|
||||
x, y = c.canvasx( event.x ), c.canvasy( event.y )
|
||||
self.nodeCount += 1
|
||||
name = self.nodePrefixes[ node ] + str( self.nodeCount )
|
||||
icon = self.nodeIcon( node, name )
|
||||
item = self.canvas.create_window( x, y, anchor='c',
|
||||
window=icon, tags=node )
|
||||
self.widgetToItem[ icon ] = item
|
||||
self.itemToWidget[ item ] = icon
|
||||
self.selectItem( item )
|
||||
icon.links = {}
|
||||
|
||||
def clickHost( self, event ):
|
||||
"Add a new host to our canvas."
|
||||
self.newNode( 'Host', event )
|
||||
|
||||
def clickSwitch( self, event ):
|
||||
"Add a new switch to our canvas."
|
||||
self.newNode( 'Switch', event )
|
||||
|
||||
def dragLink( self, event ):
|
||||
"Drag a link's endpoint to another node."
|
||||
if self.link is None:
|
||||
return
|
||||
# Since drag starts in widget, we use root coords
|
||||
x = self.canvasx( event.x_root )
|
||||
y = self.canvasy( event.y_root )
|
||||
c = self.canvas
|
||||
c.coords( self.link, self.linkx, self.linky, x, y )
|
||||
|
||||
# Callback ignores event
|
||||
# pylint: disable-msg=W0613
|
||||
def releaseLink( self, event ):
|
||||
"Give up on the current link."
|
||||
if self.link is not None:
|
||||
self.canvas.delete( self.link )
|
||||
self.linkWidget = self.linkItem = self.link = None
|
||||
# pylint: enable-msg=W0613
|
||||
|
||||
# Generic node handlers
|
||||
|
||||
def createNodeBindings( self ):
|
||||
"Create a set of bindings for nodes."
|
||||
bindings = {
|
||||
'<ButtonPress-1>': self.clickNode,
|
||||
'<B1-Motion>': self.dragNode,
|
||||
'<ButtonRelease-1>': self.releaseNode,
|
||||
'<Enter>': self.enterNode,
|
||||
'<Leave>': self.leaveNode,
|
||||
'<Double-ButtonPress-1>': self.xterm
|
||||
}
|
||||
l = Label() # lightweight-ish owner for bindings
|
||||
for event, binding in bindings.items():
|
||||
l.bind( event, binding )
|
||||
return l
|
||||
|
||||
def selectItem( self, item ):
|
||||
"Select an item and remember old selection."
|
||||
self.lastSelection = self.selection
|
||||
self.selection = item
|
||||
|
||||
def enterNode( self, event ):
|
||||
"Select node on entry."
|
||||
self.selectNode( event )
|
||||
|
||||
# Callback ignores event
|
||||
# pylint: disable-msg=W0613
|
||||
def leaveNode( self, event ):
|
||||
"Restore old selection on exit."
|
||||
self.selectItem( self.lastSelection )
|
||||
# pylint: enable-msg=W0613
|
||||
|
||||
def clickNode( self, event ):
|
||||
"Node click handler."
|
||||
if self.active is 'Link':
|
||||
self.startLink( event )
|
||||
else:
|
||||
self.selectNode( event )
|
||||
return 'break'
|
||||
|
||||
def dragNode( self, event ):
|
||||
"Node drag handler."
|
||||
if self.active is 'Link':
|
||||
self.dragLink( event )
|
||||
else:
|
||||
self.dragNodeAround( event )
|
||||
|
||||
def releaseNode( self, event ):
|
||||
"Node release handler."
|
||||
if self.active is 'Link':
|
||||
self.finishLink( event )
|
||||
|
||||
# Specific node handlers
|
||||
|
||||
def selectNode( self, event ):
|
||||
"Select the node that was clicked on."
|
||||
item = self.widgetToItem.get( event.widget, None )
|
||||
self.selectItem( item )
|
||||
|
||||
def dragNodeAround( self, event ):
|
||||
"Drag a node around on the canvas."
|
||||
c = self.canvas
|
||||
# Convert global to local coordinates;
|
||||
# Necessary since x, y are widget-relative
|
||||
x = self.canvasx( event.x_root )
|
||||
y = self.canvasy( event.y_root )
|
||||
w = event.widget
|
||||
# Adjust node position
|
||||
item = self.widgetToItem[ w ]
|
||||
c.coords( item, x, y )
|
||||
# Adjust link positions
|
||||
for dest in w.links:
|
||||
link = w.links[ dest ]
|
||||
item = self.widgetToItem[ dest ]
|
||||
x1, y1 = c.coords( item )
|
||||
c.coords( link, x, y, x1, y1 )
|
||||
|
||||
def startLink( self, event ):
|
||||
"Start a new link."
|
||||
if event.widget not in self.widgetToItem:
|
||||
# Didn't click on a node
|
||||
return
|
||||
w = event.widget
|
||||
item = self.widgetToItem[ w ]
|
||||
x, y = self.canvas.coords( item )
|
||||
self.link = self.canvas.create_line( x, y, x, y, width=4,
|
||||
fill='blue', tag='link' )
|
||||
self.linkx, self.linky = x, y
|
||||
self.linkWidget = w
|
||||
self.linkItem = item
|
||||
|
||||
# Link bindings
|
||||
# Selection still needs a bit of work overall
|
||||
# Callbacks ignore event
|
||||
# pylint: disable-msg=W0613
|
||||
|
||||
def select( event, link=self.link ):
|
||||
"Select item on mouse entry."
|
||||
self.selectItem( link )
|
||||
|
||||
def highlight( event, link=self.link ):
|
||||
"Highlight item on mouse entry."
|
||||
# self.selectItem( link )
|
||||
self.canvas.itemconfig( link, fill='green' )
|
||||
|
||||
def unhighlight( event, link=self.link ):
|
||||
"Unhighlight item on mouse exit."
|
||||
self.canvas.itemconfig( link, fill='blue' )
|
||||
# self.selectItem( None )
|
||||
|
||||
# pylint: disable-msg=W0613
|
||||
self.canvas.tag_bind( self.link, '<Enter>', highlight )
|
||||
self.canvas.tag_bind( self.link, '<Leave>', unhighlight )
|
||||
self.canvas.tag_bind( self.link, '<ButtonPress-1>', select )
|
||||
|
||||
def finishLink( self, event ):
|
||||
"Finish creating a link"
|
||||
if self.link is None:
|
||||
return
|
||||
source = self.linkWidget
|
||||
c = self.canvas
|
||||
# Since we dragged from the widget, use root coords
|
||||
x, y = self.canvasx( event.x_root ), self.canvasy( event.y_root )
|
||||
target = self.findItem( x, y )
|
||||
dest = self.itemToWidget.get( target, None )
|
||||
if ( source is None or dest is None or source == dest
|
||||
or dest in source.links or source in dest.links ):
|
||||
self.releaseLink( event )
|
||||
return
|
||||
# For now, don't allow hosts to be directly linked
|
||||
stags = self.canvas.gettags( self.widgetToItem[ source ] )
|
||||
dtags = self.canvas.gettags( target )
|
||||
if 'Host' in stags and 'Host' in dtags:
|
||||
self.releaseLink( event )
|
||||
return
|
||||
x, y = c.coords( target )
|
||||
c.coords( self.link, self.linkx, self.linky, x, y )
|
||||
self.addLink( source, dest )
|
||||
# We're done
|
||||
self.link = self.linkWidget = None
|
||||
|
||||
# Menu handlers
|
||||
|
||||
def about( self ):
|
||||
"Display about box."
|
||||
about = self.aboutBox
|
||||
if about is None:
|
||||
bg = 'white'
|
||||
about = Toplevel( bg='white' )
|
||||
about.title( 'About' )
|
||||
info = self.appName + ': a simple network editor for MiniNet'
|
||||
warning = 'Development version - not entirely functional!'
|
||||
author = 'Bob Lantz <rlantz@cs>, April 2010'
|
||||
line1 = Label( about, text=info, font='Helvetica 10 bold', bg=bg )
|
||||
line2 = Label( about, text=warning, font='Helvetica 9', bg=bg )
|
||||
line3 = Label( about, text=author, font='Helvetica 9', bg=bg )
|
||||
line1.pack( padx=20, pady=10 )
|
||||
line2.pack(pady=10 )
|
||||
line3.pack(pady=10 )
|
||||
hide = ( lambda about=about: about.withdraw() )
|
||||
self.aboutBox = about
|
||||
# Hide on close rather than destroying window
|
||||
Wm.wm_protocol( about, name='WM_DELETE_WINDOW', func=hide )
|
||||
# Show (existing) window
|
||||
about.deiconify()
|
||||
|
||||
def createToolImages( self ):
|
||||
"Create toolbar (and icon) images."
|
||||
|
||||
# Model interface
|
||||
#
|
||||
# Ultimately we will either want to use a topo or
|
||||
# mininet object here, probably.
|
||||
|
||||
def addLink( self, source, dest ):
|
||||
"Add link to model."
|
||||
source.links[ dest ] = self.link
|
||||
dest.links[ source ] = self.link
|
||||
self.links[ self.link ] = ( source, dest )
|
||||
|
||||
def deleteLink( self, link ):
|
||||
"Delete link from model."
|
||||
pair = self.links.get( link, None )
|
||||
if pair is not None:
|
||||
source, dest = pair
|
||||
del source.links[ dest ]
|
||||
del dest.links[ source ]
|
||||
if link is not None:
|
||||
del self.links[ link ]
|
||||
|
||||
def deleteNode( self, item ):
|
||||
"Delete node (and its links) from model."
|
||||
widget = self.itemToWidget[ item ]
|
||||
for link in widget.links.values():
|
||||
# Delete from view and model
|
||||
self.deleteItem( link )
|
||||
del self.itemToWidget[ item ]
|
||||
del self.widgetToItem[ widget ]
|
||||
|
||||
def build( self ):
|
||||
"Build network based on our topology."
|
||||
|
||||
net = Mininet( topo=None )
|
||||
|
||||
# Make controller
|
||||
net.addController( 'c0' )
|
||||
# Make nodes
|
||||
for widget in self.widgetToItem:
|
||||
name = widget[ 'text' ]
|
||||
tags = self.canvas.gettags( self.widgetToItem[ widget ] )
|
||||
nodeNum = int( name[ 1: ] )
|
||||
if 'Switch' in tags:
|
||||
net.addSwitch( name )
|
||||
elif 'Host' in tags:
|
||||
net.addHost( name, ip=ipStr( nodeNum ) )
|
||||
else:
|
||||
raise Exception( "Cannot create mystery node: " + name )
|
||||
# Make links
|
||||
for link in self.links.values():
|
||||
( src, dst ) = link
|
||||
srcName, dstName = src[ 'text' ], dst[ 'text' ]
|
||||
src, dst = net.nameToNode[ srcName ], net.nameToNode[ dstName ]
|
||||
src.linkTo( dst )
|
||||
|
||||
# Build network (we have to do this separately at the moment )
|
||||
net.build()
|
||||
|
||||
return net
|
||||
|
||||
def start( self ):
|
||||
"Start network."
|
||||
if self.net is None:
|
||||
self.net = self.build()
|
||||
self.net.start()
|
||||
|
||||
def stop( self ):
|
||||
"Stop network."
|
||||
if self.net is not None:
|
||||
self.net.stop()
|
||||
cleanUpScreens()
|
||||
self.net = None
|
||||
|
||||
def xterm( self, _ignore=None ):
|
||||
"Make an xterm when a button is pressed."
|
||||
if ( self.selection is None or
|
||||
self.net is None or
|
||||
self.selection not in self.itemToWidget ):
|
||||
return
|
||||
name = self.itemToWidget[ self.selection ][ 'text' ]
|
||||
if name not in self.net.nameToNode:
|
||||
return
|
||||
term = makeTerm( self.net.nameToNode[ name ], 'Host' )
|
||||
self.net.terms.append( term )
|
||||
|
||||
|
||||
def miniEditImages():
|
||||
"Create and return images for MiniEdit."
|
||||
|
||||
# Image data. Git will be unhappy. However, the alternative
|
||||
# is to keep track of separate binary files, which is also
|
||||
# unappealing.
|
||||
|
||||
return {
|
||||
'Select': BitmapImage(
|
||||
file='/usr/include/X11/bitmaps/left_ptr' ),
|
||||
|
||||
'Host': PhotoImage( data=r"""
|
||||
R0lGODlhIAAYAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M
|
||||
mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m
|
||||
Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A
|
||||
M/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zM
|
||||
AMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz
|
||||
/8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/
|
||||
zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZ
|
||||
mZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkz
|
||||
ZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/
|
||||
M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZ
|
||||
AGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA
|
||||
/2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPM
|
||||
zDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNm
|
||||
mTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMA
|
||||
ZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM
|
||||
MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBm
|
||||
AAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0A
|
||||
ALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI
|
||||
AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAA
|
||||
RAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3d1VVVURERCIiIhEREQAA
|
||||
ACH5BAEAAAAALAAAAAAgABgAAAiNAAH8G0iwoMGDCAcKTMiw4UBw
|
||||
BPXVm0ixosWLFvVBHFjPoUeC9Tb+6/jRY0iQ/8iVbHiS40CVKxG2
|
||||
HEkQZsyCM0mmvGkw50uePUV2tEnOZkyfQA8iTYpTKNOgKJ+C3AhO
|
||||
p9SWVaVOfWj1KdauTL9q5UgVbFKsEjGqXVtP40NwcBnCjXtw7tx/
|
||||
C8cSBBAQADs=
|
||||
""" ),
|
||||
|
||||
'Switch': PhotoImage( data=r"""
|
||||
R0lGODlhIAAYAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M
|
||||
mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m
|
||||
Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A
|
||||
M/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zM
|
||||
AMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz
|
||||
/8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/
|
||||
zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZ
|
||||
mZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkz
|
||||
ZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/
|
||||
M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZ
|
||||
AGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA
|
||||
/2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPM
|
||||
zDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNm
|
||||
mTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMA
|
||||
ZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM
|
||||
MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBm
|
||||
AAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0A
|
||||
ALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI
|
||||
AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAA
|
||||
RAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3d1VVVURERCIiIhEREQAA
|
||||
ACH5BAEAAAAALAAAAAAgABgAAAhwAAEIHEiwoMGDCBMqXMiwocOH
|
||||
ECNKnEixosWB3zJq3Mixo0eNAL7xG0mypMmTKPl9Cznyn8uWL/m5
|
||||
/AeTpsyYI1eKlBnO5r+eLYHy9Ck0J8ubPmPOrMmUpM6UUKMa/Ui1
|
||||
6saLWLNq3cq1q9evYB0GBAA7
|
||||
""" ),
|
||||
|
||||
'Link': PhotoImage( data=r"""
|
||||
R0lGODlhFgAWAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M
|
||||
mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m
|
||||
Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A
|
||||
M/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zM
|
||||
AMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz
|
||||
/8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/
|
||||
zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZ
|
||||
mZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkz
|
||||
ZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/
|
||||
M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZ
|
||||
AGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA
|
||||
/2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPM
|
||||
zDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNm
|
||||
mTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMA
|
||||
ZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM
|
||||
MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBm
|
||||
AAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0A
|
||||
ALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI
|
||||
AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAA
|
||||
RAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3d1VVVURERCIiIhEREQAA
|
||||
ACH5BAEAAAAALAAAAAAWABYAAAhIAAEIHEiwoEGBrhIeXEgwoUKG
|
||||
Cx0+hGhQoiuKBy1irChxY0GNHgeCDAlgZEiTHlFuVImRJUWXEGEy
|
||||
lBmxI8mSNknm1Dnx5sCAADs=
|
||||
""" )
|
||||
}
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
app = MiniEdit()
|
||||
app.mainloop()
|
||||
@@ -1,49 +0,0 @@
|
||||
# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
|
||||
#
|
||||
# Copyright (C) 2015-2020, The University of Memphis,
|
||||
# Arizona Board of Regents,
|
||||
# Regents of the University of California.
|
||||
#
|
||||
# This file is part of Mini-NDN.
|
||||
# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
|
||||
#
|
||||
# Mini-NDN 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mini-NDN 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 Mini-NDN, e.g., in COPYING.md file.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from mininet.log import setLogLevel, info
|
||||
|
||||
from minindn.minindn import Minindn
|
||||
from minindn.util import MiniNDNCLI
|
||||
from minindn.apps.app_manager import AppManager
|
||||
from minindn.apps.nfd import Nfd
|
||||
from minindn.apps.nlsr import Nlsr
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel('info')
|
||||
|
||||
Minindn.cleanUp()
|
||||
Minindn.verifyDependencies()
|
||||
|
||||
ndn = Minindn()
|
||||
|
||||
ndn.start()
|
||||
|
||||
info('Starting NFD on nodes\n')
|
||||
nfds = AppManager(ndn, ndn.net.hosts, Nfd)
|
||||
info('Starting NLSR on nodes\n')
|
||||
nlsrs = AppManager(ndn, ndn.net.hosts, Nlsr)
|
||||
|
||||
MiniNDNCLI(ndn.net)
|
||||
|
||||
ndn.stop()
|
||||
Executable
+35
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
This example shows how to create a network and run multiple tests.
|
||||
For a more complicated test example, see udpbwtest.py.
|
||||
"""
|
||||
|
||||
from mininet.cli import CLI
|
||||
from mininet.log import lg, info
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import OVSKernelSwitch
|
||||
from mininet.topolib import TreeTopo
|
||||
|
||||
def ifconfigTest( net ):
|
||||
"Run ifconfig on all hosts in net."
|
||||
hosts = net.hosts
|
||||
for host in hosts:
|
||||
info( host.cmd( 'ifconfig' ) )
|
||||
|
||||
if __name__ == '__main__':
|
||||
lg.setLogLevel( 'info' )
|
||||
info( "*** Initializing Mininet and kernel modules\n" )
|
||||
OVSKernelSwitch.setup()
|
||||
info( "*** Creating network\n" )
|
||||
network = Mininet( TreeTopo( depth=2, fanout=2 ), switch=OVSKernelSwitch)
|
||||
info( "*** Starting network\n" )
|
||||
network.start()
|
||||
info( "*** Running ping test\n" )
|
||||
network.pingAll()
|
||||
info( "*** Running ifconfig test\n" )
|
||||
ifconfigTest( network )
|
||||
info( "*** Starting CLI (type 'exit' to exit)\n" )
|
||||
CLI( network )
|
||||
info( "*** Stopping network\n" )
|
||||
network.stop()
|
||||
@@ -1,79 +0,0 @@
|
||||
# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
|
||||
#
|
||||
# Copyright (C) 2015-2019, The University of Memphis,
|
||||
# Arizona Board of Regents,
|
||||
# Regents of the University of California.
|
||||
#
|
||||
# This file is part of Mini-NDN.
|
||||
# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
|
||||
#
|
||||
# Mini-NDN 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mini-NDN 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 Mini-NDN, e.g., in COPYING.md file.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import time
|
||||
import sys
|
||||
|
||||
from mininet.log import setLogLevel, info
|
||||
from mininet.topo import Topo
|
||||
|
||||
from minindn.minindn import Minindn
|
||||
from minindn.apps.app_manager import AppManager
|
||||
from minindn.apps.nfd import Nfd
|
||||
from minindn.apps.nlsr import Nlsr
|
||||
|
||||
from nlsr_common import getParser
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel('info')
|
||||
|
||||
topo = Topo()
|
||||
h1 = topo.addHost('h1')
|
||||
h2 = topo.addHost('h2')
|
||||
topo.addLink(h1, h2, delay='10ms')
|
||||
|
||||
ndn = Minindn(parser=getParser(), topo=topo)
|
||||
args = ndn.args
|
||||
|
||||
ndn.start()
|
||||
|
||||
nfds = AppManager(ndn, ndn.net.hosts, Nfd)
|
||||
nlsrs = AppManager(ndn, [], Nlsr)
|
||||
|
||||
host1 = ndn.net.hosts[0]
|
||||
nlsrs.startOnNode(host1, security=args.security, faceType=args.faceType,
|
||||
nFaces=args.faces, routingType=args.routingType)
|
||||
|
||||
|
||||
expectedTotalCount = 500
|
||||
for i in range(0, expectedTotalCount):
|
||||
host1.cmd('nlsrc advertise /long/name/to/exceed/max/packet/size/host1/{}'.format(i))
|
||||
|
||||
time.sleep(60)
|
||||
|
||||
host2 = ndn.net.hosts[1]
|
||||
nlsrs.startOnNode(host2, security=args.security, faceType=args.faceType,
|
||||
nFaces=args.faces, routingType=args.routingType)
|
||||
|
||||
time.sleep(60)
|
||||
|
||||
advertiseCount = int(host2.cmd('nfdc fib | grep host1 | wc -l'))
|
||||
info(advertiseCount)
|
||||
if advertiseCount == expectedTotalCount:
|
||||
info('\nSuccessfully advertised {} prefixes\n'.format(expectedTotalCount))
|
||||
else:
|
||||
info('\nAdvertising {} prefixes failed. Exiting...\n'.format(expectedTotalCount))
|
||||
ndn.stop()
|
||||
sys.exit(1)
|
||||
|
||||
ndn.stop()
|
||||
@@ -1,64 +0,0 @@
|
||||
# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
|
||||
#
|
||||
# Copyright (C) 2015-2019, The University of Memphis,
|
||||
# Arizona Board of Regents,
|
||||
# Regents of the University of California.
|
||||
#
|
||||
# This file is part of Mini-NDN.
|
||||
# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
|
||||
#
|
||||
# Mini-NDN 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mini-NDN 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 Mini-NDN, e.g., in COPYING.md file.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import time
|
||||
|
||||
from mininet.log import setLogLevel, info
|
||||
|
||||
from minindn.minindn import Minindn
|
||||
from minindn.apps.app_manager import AppManager
|
||||
from minindn.apps.nfd import Nfd
|
||||
from minindn.apps.nlsr import Nlsr
|
||||
from minindn.helpers.experiment import Experiment
|
||||
|
||||
from nlsr_common import getParser
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel('info')
|
||||
|
||||
ndn = Minindn(parser=getParser())
|
||||
args = ndn.args
|
||||
|
||||
ndn.start()
|
||||
|
||||
nfds = AppManager(ndn, ndn.net.hosts, Nfd)
|
||||
nlsrs = AppManager(ndn, [], Nlsr)
|
||||
|
||||
i = 1
|
||||
info('Starting NLSR on nodes\n')
|
||||
for host in ndn.net.hosts:
|
||||
nlsrs.startOnNode(host, security=args.security, sync=args.sync, faceType=args.faceType,
|
||||
nFaces=args.faces, routingType=args.routingType)
|
||||
|
||||
# Wait 1/2 minute between starting NLSRs
|
||||
# Wait 1 hour before starting last NLSR
|
||||
if i == len(ndn.net.hosts) - 1:
|
||||
info('Sleeping 1 hour before starting last NLSR\n')
|
||||
time.sleep(3600)
|
||||
else:
|
||||
time.sleep(30)
|
||||
i += 1
|
||||
|
||||
Experiment.checkConvergence(ndn, ndn.net.hosts, args.ctime, quit=True)
|
||||
|
||||
ndn.stop()
|
||||
@@ -1,90 +0,0 @@
|
||||
# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
|
||||
#
|
||||
# Copyright (C) 2015-2021, The University of Memphis,
|
||||
# Arizona Board of Regents,
|
||||
# Regents of the University of California.
|
||||
#
|
||||
# This file is part of Mini-NDN.
|
||||
# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
|
||||
#
|
||||
# Mini-NDN 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mini-NDN 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 Mini-NDN, e.g., in COPYING.md file.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import time
|
||||
|
||||
from mininet.log import setLogLevel, info
|
||||
|
||||
from minindn.minindn import Minindn
|
||||
from minindn.util import MiniNDNCLI
|
||||
from minindn.apps.app_manager import AppManager
|
||||
from minindn.apps.nfd import Nfd
|
||||
from minindn.apps.nlsr import Nlsr
|
||||
from minindn.helpers.experiment import Experiment
|
||||
from minindn.helpers.nfdc import Nfdc
|
||||
from minindn.helpers.ndnping import NDNPing
|
||||
|
||||
from nlsr_common import getParser
|
||||
|
||||
def mcnFailure(ndn, nfds, nlsrs, args):
|
||||
Experiment.checkConvergence(ndn, ndn.net.hosts, args.ctime, quit=True)
|
||||
if args.nPings != 0:
|
||||
Experiment.setupPing(ndn.net.hosts, Nfdc.STRATEGY_BEST_ROUTE)
|
||||
pingedDict = Experiment.startPctPings(ndn.net, args.nPings, args.pctTraffic)
|
||||
|
||||
PING_COLLECTION_TIME_BEFORE_FAILURE = 60
|
||||
PING_COLLECTION_TIME_AFTER_RECOVERY = 120
|
||||
|
||||
time.sleep(PING_COLLECTION_TIME_BEFORE_FAILURE)
|
||||
|
||||
mcn = max(ndn.net.hosts, key=lambda host: len(host.intfNames()))
|
||||
|
||||
info('Bringing down node {}\n'.format(mcn.name))
|
||||
nlsrs[mcn.name].stop()
|
||||
nfds[mcn.name].stop()
|
||||
|
||||
time.sleep(args.ctime)
|
||||
|
||||
info('Bringing up node {}\n'.format(mcn.name))
|
||||
nfds[mcn.name].start()
|
||||
nlsrs[mcn.name].start()
|
||||
|
||||
# Restart pings
|
||||
if args.nPings != 0:
|
||||
Experiment.setupPing([mcn], Nfdc.STRATEGY_BEST_ROUTE)
|
||||
for nodeToPing in pingedDict[mcn]:
|
||||
NDNPing.ping(mcn, nodeToPing, nPings=PING_COLLECTION_TIME_AFTER_RECOVERY)
|
||||
|
||||
time.sleep(PING_COLLECTION_TIME_AFTER_RECOVERY)
|
||||
|
||||
Experiment.checkConvergence(ndn, ndn.net.hosts, args.ctime, quit=True)
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel('info')
|
||||
|
||||
ndn = Minindn(parser=getParser())
|
||||
args = ndn.args
|
||||
|
||||
ndn.start()
|
||||
|
||||
nfds = AppManager(ndn, ndn.net.hosts, Nfd)
|
||||
nlsrs = AppManager(ndn, ndn.net.hosts, Nlsr, sync=args.sync,
|
||||
security=args.security, faceType=args.faceType,
|
||||
nFaces=args.faces, routingType=args.routingType)
|
||||
|
||||
mcnFailure(ndn, nfds, nlsrs, args)
|
||||
|
||||
if args.isCliEnabled:
|
||||
MiniNDNCLI(ndn.net)
|
||||
|
||||
ndn.stop()
|
||||
@@ -1,109 +0,0 @@
|
||||
# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
|
||||
#
|
||||
# Copyright (C) 2015-2021, The University of Memphis,
|
||||
# Arizona Board of Regents,
|
||||
# Regents of the University of California.
|
||||
#
|
||||
# This file is part of Mini-NDN.
|
||||
# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
|
||||
#
|
||||
# Mini-NDN 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mini-NDN 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 Mini-NDN, e.g., in COPYING.md file.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import time
|
||||
|
||||
from mininet.log import setLogLevel, info
|
||||
|
||||
from minindn.minindn import Minindn
|
||||
from minindn.util import MiniNDNCLI
|
||||
from minindn.apps.app_manager import AppManager
|
||||
from minindn.apps.nfd import Nfd
|
||||
from minindn.apps.nlsr import Nlsr
|
||||
from minindn.helpers.experiment import Experiment
|
||||
from minindn.helpers.nfdc import Nfdc
|
||||
from minindn.helpers.ndnping import NDNPing
|
||||
|
||||
from nlsr_common import getParser
|
||||
|
||||
def multipleFailure(ndn, nfds, nlsrs, args):
|
||||
|
||||
Experiment.checkConvergence(ndn, ndn.net.hosts, args.ctime, quit=True)
|
||||
Experiment.setupPing(ndn.net.hosts, Nfdc.STRATEGY_BEST_ROUTE)
|
||||
|
||||
PING_COLLECTION_TIME_BEFORE_FAILURE = 60
|
||||
FAILURE_INTERVAL = 60
|
||||
RECOVERY_INTERVAL = 60
|
||||
|
||||
# Number of pings required to make it through the full experiment
|
||||
nInitialPings = (PING_COLLECTION_TIME_BEFORE_FAILURE +
|
||||
len(ndn.net.hosts) * (FAILURE_INTERVAL + RECOVERY_INTERVAL))
|
||||
print('Scheduling with {} initial pings'.format(nInitialPings))
|
||||
|
||||
pingedDict = Experiment.startPctPings(ndn.net, nInitialPings, args.pctTraffic)
|
||||
time.sleep(PING_COLLECTION_TIME_BEFORE_FAILURE)
|
||||
|
||||
nNodesRemainingToFail = len(ndn.net.hosts)
|
||||
|
||||
for host in ndn.net.hosts:
|
||||
# Fail the node
|
||||
info('Bringing down node {}\n'.format(host.name))
|
||||
nlsrs[host.name].stop()
|
||||
nfds[host.name].stop()
|
||||
|
||||
# Stay in failure state for FAILURE_INTERVAL seconds
|
||||
time.sleep(FAILURE_INTERVAL)
|
||||
|
||||
# Bring the node back up
|
||||
start_time = time.time()
|
||||
info('Bringing up node {}\n'.format(host.name))
|
||||
nfds[host.name].start()
|
||||
nlsrs[host.name].start()
|
||||
Experiment.setupPing([host], Nfdc.STRATEGY_BEST_ROUTE)
|
||||
|
||||
recovery_time = int(time.time() - start_time)
|
||||
|
||||
# Number of pings required to reach the end of the test
|
||||
nNodesRemainingToFail -= 1
|
||||
nPings = ((RECOVERY_INTERVAL - recovery_time) +
|
||||
nNodesRemainingToFail * (FAILURE_INTERVAL + RECOVERY_INTERVAL))
|
||||
|
||||
info('Scheduling with {} remaining pings\n'.format(nPings))
|
||||
|
||||
# Restart pings
|
||||
for nodeToPing in pingedDict[host]:
|
||||
NDNPing.ping(host, nodeToPing, nPings=nPings)
|
||||
|
||||
time.sleep(RECOVERY_INTERVAL - recovery_time)
|
||||
|
||||
Experiment.checkConvergence(ndn, ndn.net.hosts, args.ctime, quit=True)
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel('info')
|
||||
|
||||
ndn = Minindn(parser=getParser())
|
||||
args = ndn.args
|
||||
|
||||
ndn.start()
|
||||
|
||||
nfds = AppManager(ndn, ndn.net.hosts, Nfd)
|
||||
nlsrs = AppManager(ndn, ndn.net.hosts, Nlsr, sync=args.sync,
|
||||
security=args.security, faceType=args.faceType,
|
||||
nFaces=args.faces, routingType=args.routingType)
|
||||
|
||||
multipleFailure(ndn, nfds, nlsrs, args)
|
||||
|
||||
if args.isCliEnabled:
|
||||
MiniNDNCLI(ndn.net)
|
||||
|
||||
ndn.stop()
|
||||
@@ -1,57 +0,0 @@
|
||||
# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
|
||||
#
|
||||
# Copyright (C) 2015-2019, The University of Memphis,
|
||||
# Arizona Board of Regents,
|
||||
# Regents of the University of California.
|
||||
#
|
||||
# This file is part of Mini-NDN.
|
||||
# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
|
||||
#
|
||||
# Mini-NDN 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mini-NDN 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 Mini-NDN, e.g., in COPYING.md file.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import argparse
|
||||
|
||||
def getParser():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--ctime', type=int, default=60,
|
||||
help='Specify convergence time for the topology (Default: 60 seconds)')
|
||||
|
||||
parser.add_argument('--faces', type=int, default=3,
|
||||
help='Specify number of max faces per prefix for NLSR 0-60')
|
||||
|
||||
parser.add_argument('--routing', dest='routingType', default='link-state',
|
||||
choices=['link-state', 'hr', 'dry'],
|
||||
help='''Choose routing type, dry = link-state is used
|
||||
but hr is calculated for comparision.''')
|
||||
|
||||
parser.add_argument('--sync', dest='sync', default='psync',
|
||||
choices=['chronosync', 'psync'],
|
||||
help='choose the sync protocol to be used by NLSR.')
|
||||
|
||||
parser.add_argument('--security', action='store_true', dest='security',
|
||||
help='Enables NLSR security')
|
||||
|
||||
parser.add_argument('--face-type', dest='faceType', default='udp', choices=['udp', 'tcp'])
|
||||
|
||||
parser.add_argument('--no-cli', action='store_false', dest='isCliEnabled',
|
||||
help='Run experiments and exit without showing the command line interface')
|
||||
|
||||
parser.add_argument('--pct-traffic', dest='pctTraffic', type=float, default=1.0,
|
||||
help='Specify the percentage of nodes each node should ping')
|
||||
|
||||
parser.add_argument('--nPings', type=int, default=300,
|
||||
help='Number of pings to perform between each node in the experiment')
|
||||
|
||||
return parser
|
||||
@@ -1,63 +0,0 @@
|
||||
# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
|
||||
#
|
||||
# Copyright (C) 2015-2019, The University of Memphis,
|
||||
# Arizona Board of Regents,
|
||||
# Regents of the University of California.
|
||||
#
|
||||
# This file is part of Mini-NDN.
|
||||
# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
|
||||
#
|
||||
# Mini-NDN 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mini-NDN 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 Mini-NDN, e.g., in COPYING.md file.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import time
|
||||
|
||||
from mininet.log import setLogLevel
|
||||
|
||||
from minindn.minindn import Minindn
|
||||
from minindn.util import MiniNDNCLI
|
||||
from minindn.apps.app_manager import AppManager
|
||||
from minindn.apps.nfd import Nfd
|
||||
from minindn.apps.nlsr import Nlsr
|
||||
from minindn.helpers.experiment import Experiment
|
||||
from minindn.helpers.nfdc import Nfdc
|
||||
|
||||
from nlsr_common import getParser
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel('info')
|
||||
|
||||
ndn = Minindn(parser=getParser())
|
||||
args = ndn.args
|
||||
|
||||
ndn.start()
|
||||
|
||||
nfds = AppManager(ndn, ndn.net.hosts, Nfd)
|
||||
nlsrs = AppManager(ndn, ndn.net.hosts, Nlsr, sync=args.sync,
|
||||
security=args.security, faceType=args.faceType,
|
||||
nFaces=args.faces, routingType=args.routingType,
|
||||
logLevel='ndn.*=TRACE:nlsr.*=TRACE')
|
||||
|
||||
Experiment.checkConvergence(ndn, ndn.net.hosts, args.ctime, quit=False)
|
||||
|
||||
if args.nPings != 0:
|
||||
Experiment.setupPing(ndn.net.hosts, Nfdc.STRATEGY_BEST_ROUTE)
|
||||
Experiment.startPctPings(ndn.net, args.nPings, args.pctTraffic)
|
||||
|
||||
time.sleep(args.nPings + 10)
|
||||
|
||||
if args.isCliEnabled:
|
||||
MiniNDNCLI(ndn.net)
|
||||
|
||||
ndn.stop()
|
||||
@@ -1,85 +0,0 @@
|
||||
# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
|
||||
#
|
||||
# Copyright (C) 2015-2021, The University of Memphis,
|
||||
# Arizona Board of Regents,
|
||||
# Regents of the University of California.
|
||||
#
|
||||
# This file is part of Mini-NDN.
|
||||
# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
|
||||
#
|
||||
# Mini-NDN 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mini-NDN 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 Mini-NDN, e.g., in COPYING.md file.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import time
|
||||
import sys
|
||||
|
||||
from mininet.log import setLogLevel, info
|
||||
|
||||
from minindn.minindn import Minindn
|
||||
from minindn.util import MiniNDNCLI
|
||||
from minindn.apps.app_manager import AppManager
|
||||
from minindn.apps.nfd import Nfd
|
||||
from minindn.apps.nlsr import Nlsr
|
||||
from minindn.helpers.experiment import Experiment
|
||||
|
||||
from nlsr_common import getParser
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel('info')
|
||||
|
||||
ndn = Minindn(parser=getParser())
|
||||
args = ndn.args
|
||||
|
||||
ndn.start()
|
||||
|
||||
nfds = AppManager(ndn, ndn.net.hosts, Nfd)
|
||||
nlsrs = AppManager(ndn, ndn.net.hosts, Nlsr, sync=args.sync,
|
||||
security=args.security, faceType=args.faceType,
|
||||
nFaces=args.faces, routingType=args.routingType)
|
||||
|
||||
Experiment.checkConvergence(ndn, ndn.net.hosts, args.ctime, quit=True)
|
||||
|
||||
firstNode = ndn.net.hosts[0]
|
||||
|
||||
if args.security:
|
||||
firstNode.cmd('ndnsec-set-default /ndn/{}-site/%C1.Operator/op'.format(firstNode.name))
|
||||
|
||||
info('Testing advertise\n')
|
||||
firstNode.cmd('nlsrc advertise /testPrefix')
|
||||
time.sleep(30)
|
||||
|
||||
for host in ndn.net.hosts:
|
||||
if host.name != firstNode.name:
|
||||
if (int(host.cmd('nfdc fib | grep testPrefix | wc -l')) != 1 or
|
||||
int(host.cmd('nlsrc status | grep testPrefix | wc -l')) != 1):
|
||||
info('Advertise test failed\n')
|
||||
ndn.stop()
|
||||
sys.exit(1)
|
||||
|
||||
info('Testing withdraw\n')
|
||||
firstNode.cmd('nlsrc withdraw /testPrefix')
|
||||
time.sleep(30)
|
||||
|
||||
for host in ndn.net.hosts:
|
||||
if host.name != firstNode.name:
|
||||
if (int(host.cmd('nfdc fib | grep testPrefix | wc -l')) != 0 or
|
||||
int(host.cmd('nlsrc status | grep testPrefix | wc -l')) != 0):
|
||||
info('Withdraw test failed\n')
|
||||
ndn.stop()
|
||||
sys.exit(1)
|
||||
|
||||
if args.isCliEnabled:
|
||||
MiniNDNCLI(ndn.net)
|
||||
|
||||
ndn.stop()
|
||||
@@ -1,53 +0,0 @@
|
||||
# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
|
||||
#
|
||||
# Copyright (C) 2015-2020, The University of Memphis,
|
||||
# Arizona Board of Regents,
|
||||
# Regents of the University of California.
|
||||
#
|
||||
# This file is part of Mini-NDN.
|
||||
# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
|
||||
#
|
||||
# Mini-NDN 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mini-NDN 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 Mini-NDN, e.g., in COPYING.md file.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from mininet.log import setLogLevel, info
|
||||
|
||||
from minindn.minindn import Minindn
|
||||
from minindn.apps.app_manager import AppManager
|
||||
from minindn.util import MiniNDNCLI
|
||||
from minindn.apps.nfd import Nfd
|
||||
from minindn.apps.nlsr import Nlsr
|
||||
from minindn.apps.tshark import Tshark
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel('info')
|
||||
|
||||
Minindn.cleanUp()
|
||||
Minindn.verifyDependencies()
|
||||
|
||||
ndn = Minindn()
|
||||
|
||||
ndn.start()
|
||||
|
||||
info('Starting tshark logging on nodes\n')
|
||||
tshark = AppManager(ndn, ndn.net.hosts, Tshark, logFolder="./log/", singleLogFile=False)
|
||||
|
||||
info('Starting NFD on nodes\n')
|
||||
nfds = AppManager(ndn, ndn.net.hosts, Nfd)
|
||||
info('Starting NLSR on nodes\n')
|
||||
nlsrs = AppManager(ndn, ndn.net.hosts, Nlsr)
|
||||
|
||||
MiniNDNCLI(ndn.net)
|
||||
|
||||
ndn.stop()
|
||||
@@ -1,121 +0,0 @@
|
||||
# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
|
||||
#
|
||||
# Copyright (C) 2015-2021, The University of Memphis,
|
||||
# Arizona Board of Regents,
|
||||
# Regents of the University of California.
|
||||
#
|
||||
# This file is part of Mini-NDN.
|
||||
# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
|
||||
#
|
||||
# Mini-NDN 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mini-NDN 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 Mini-NDN, e.g., in COPYING.md file.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from subprocess import PIPE
|
||||
|
||||
from mininet.log import setLogLevel, info
|
||||
from mininet.topo import Topo
|
||||
|
||||
from minindn.minindn import Minindn
|
||||
from minindn.apps.app_manager import AppManager
|
||||
from minindn.util import MiniNDNCLI, getPopen
|
||||
from minindn.apps.nfd import Nfd
|
||||
from minindn.helpers.nfdc import Nfdc
|
||||
|
||||
PREFIX = "/example"
|
||||
|
||||
def printOutput(output):
|
||||
_out = output.decode("utf-8").split("\n")
|
||||
for _line in _out:
|
||||
info(_line + "\n")
|
||||
|
||||
def run():
|
||||
Minindn.cleanUp()
|
||||
Minindn.verifyDependencies()
|
||||
|
||||
# Topology can be created/modified using Mininet topo object
|
||||
topo = Topo()
|
||||
info("Setup\n")
|
||||
# add hosts
|
||||
a = topo.addHost('a')
|
||||
b = topo.addHost('b')
|
||||
c = topo.addHost('c')
|
||||
|
||||
# add links
|
||||
topo.addLink(a, b, delay='10ms', bw=2.5) # bw = bandwidth
|
||||
topo.addLink(b, c, delay='10ms', bw=2.5)
|
||||
|
||||
info(topo.links(withInfo=True))
|
||||
|
||||
ndn = Minindn(topo=topo)
|
||||
ndn.start()
|
||||
|
||||
# configure and start nfd on each node
|
||||
info("Configuring NFD111\n")
|
||||
AppManager(ndn, ndn.net.hosts, Nfd, logLevel="DEBUG")
|
||||
|
||||
"""
|
||||
There are multiple ways of setting up routes in Mini-NDN
|
||||
refer: https://minindn.memphis.edu/experiment.html#routing-options
|
||||
It can also be set manually as follows. The important bit to note here
|
||||
is the use of the Nfdc command
|
||||
"""
|
||||
for link in topo.links(withInfo=True):
|
||||
node1, node2, node_info = link
|
||||
host1 = ndn.net[node1]
|
||||
host2 = ndn.net[node2]
|
||||
interface = host2.connectionsTo(host1)[0][0]
|
||||
interface_ip = interface.IP()
|
||||
bandwidth = interface.params.get("bw", 100) * 1000000
|
||||
info(f"Setting up route from {node1} to {node2} with bandwidth {bandwidth}\n")
|
||||
Nfdc.createFace(host1, interface_ip, bandwidth=bandwidth)
|
||||
Nfdc.registerRoute(host1, PREFIX, interface_ip, cost=0)
|
||||
# links = {"a":["b"], "b":["c"]}
|
||||
# for first in links:
|
||||
# for second in links[first]:
|
||||
# host1 = ndn.net[first]
|
||||
# host2 = ndn.net[second]
|
||||
# interface = host2.connectionsTo(host1)[0][0]
|
||||
# interface_ip = interface.IP()
|
||||
# Nfdc.createFace(host1, interface_ip)
|
||||
# Nfdc.registerRoute(host1, PREFIX, interface_ip, cost=0)
|
||||
|
||||
# Start ping server
|
||||
info("Starting pings...\n")
|
||||
pingserver_log = open("{}/c/ndnpingserver.log".format(ndn.workDir), "w")
|
||||
getPopen(ndn.net["c"], "ndnpingserver {}".format(PREFIX), stdout=pingserver_log,\
|
||||
stderr=pingserver_log)
|
||||
|
||||
# start ping client
|
||||
ping1 = getPopen(ndn.net["a"], "ndnping {} -c 5".format(PREFIX), stdout=PIPE, stderr=PIPE)
|
||||
ping1.wait()
|
||||
printOutput(ping1.stdout.read())
|
||||
|
||||
interface = ndn.net["b"].connectionsTo(ndn.net["a"])[0][0]
|
||||
info("Failing link\n") # failing link by setting link loss to 100%
|
||||
interface.config(delay="10ms", bw=10, loss=100)
|
||||
info ("\n starting ping2 client \n")
|
||||
|
||||
ping2 = getPopen(ndn.net["a"], "ndnping {} -c 5".format(PREFIX), stdout=PIPE, stderr=PIPE)
|
||||
ping2.wait()
|
||||
printOutput(ping2.stdout.read())
|
||||
|
||||
interface.config(delay="10ms", bw=10, loss=0) # bringing back the link
|
||||
|
||||
info("\nExperiment Completed!\n")
|
||||
MiniNDNCLI(ndn.net)
|
||||
ndn.stop()
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel("info")
|
||||
run()
|
||||
@@ -1,81 +0,0 @@
|
||||
# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
|
||||
#
|
||||
# Copyright (C) 2015-2019, The University of Memphis,
|
||||
# Arizona Board of Regents,
|
||||
# Regents of the University of California.
|
||||
#
|
||||
# This file is part of Mini-NDN.
|
||||
# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
|
||||
#
|
||||
# Mini-NDN 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mini-NDN 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 Mini-NDN, e.g., in COPYING.md file.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import time
|
||||
import sys
|
||||
|
||||
from mininet.log import setLogLevel, info
|
||||
|
||||
from minindn.minindn import Minindn
|
||||
from minindn.apps.app_manager import AppManager
|
||||
from minindn.apps.nfd import Nfd
|
||||
from minindn.helpers.nfdc import Nfdc
|
||||
|
||||
def registerRouteToAllNeighbors(ndn, host, syncPrefix):
|
||||
for node in ndn.net.hosts:
|
||||
for neighbor in node.connectionsTo(host):
|
||||
ip = node.IP(neighbor[0])
|
||||
faceID = Nfdc.createFace(host, ip)
|
||||
Nfdc.registerRoute(host, syncPrefix, faceID)
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel('info')
|
||||
|
||||
ndn = Minindn()
|
||||
args = ndn.args
|
||||
|
||||
ndn.start()
|
||||
|
||||
nfds = AppManager(ndn, ndn.net.hosts, Nfd)
|
||||
|
||||
syncPrefix = "/sync"
|
||||
numUserPrefixesPerNode = 2
|
||||
maxUpdatesPerUserPrefixPerNode = 3
|
||||
|
||||
for host in ndn.net.hosts:
|
||||
Nfdc.setStrategy(host, syncPrefix, Nfdc.STRATEGY_MULTICAST)
|
||||
registerRouteToAllNeighbors(ndn, host, syncPrefix)
|
||||
|
||||
info('Starting psync-full-sync on all the nodes\n')
|
||||
for host in ndn.net.hosts:
|
||||
host.cmd('export NDN_LOG=examples.FullSyncApp=INFO')
|
||||
host.cmd('psync-full-sync {} {} {} {} &> psync.logs &'
|
||||
.format(syncPrefix, host.name, numUserPrefixesPerNode,
|
||||
maxUpdatesPerUserPrefixPerNode))
|
||||
|
||||
info('Sleeping 5 minutes for convergence\n')
|
||||
# Estimated time for 4 node default topology
|
||||
time.sleep(300)
|
||||
|
||||
totalUpdates = int(host.cmd('grep -r Update {}/*/psync.logs | wc -l'
|
||||
.format(ndn.workDir)))
|
||||
|
||||
expectedUpdates = (maxUpdatesPerUserPrefixPerNode *
|
||||
len(ndn.net.hosts) * (len(ndn.net.hosts) - 1) * numUserPrefixesPerNode)
|
||||
|
||||
if totalUpdates == expectedUpdates:
|
||||
info('PSync full sync has successfully converged.\n')
|
||||
else:
|
||||
info('PSync full sync convergence was not successful. Exiting...\n')
|
||||
ndn.stop()
|
||||
sys.exit(1)
|
||||
@@ -1,67 +0,0 @@
|
||||
# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
|
||||
#
|
||||
# Copyright (C) 2015-2019, The University of Memphis,
|
||||
# Arizona Board of Regents,
|
||||
# Regents of the University of California.
|
||||
#
|
||||
# This file is part of Mini-NDN.
|
||||
# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
|
||||
#
|
||||
# Mini-NDN 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mini-NDN 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 Mini-NDN, e.g., in COPYING.md file.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import time
|
||||
import sys
|
||||
|
||||
from mininet.log import setLogLevel, info
|
||||
from mininet.topo import Topo
|
||||
|
||||
from minindn.minindn import Minindn
|
||||
from minindn.apps.app_manager import AppManager
|
||||
from minindn.apps.nfd import Nfd
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel('info')
|
||||
|
||||
topo = Topo()
|
||||
topo.addHost('h1')
|
||||
|
||||
ndn = Minindn(topo=topo)
|
||||
args = ndn.args
|
||||
|
||||
ndn.start()
|
||||
|
||||
nfds = AppManager(ndn, ndn.net.hosts, Nfd)
|
||||
|
||||
host1 = ndn.net.hosts[0]
|
||||
host1.cmd('export NDN_LOG=examples.PartialSyncProducerApp=INFO')
|
||||
host1.cmd('psync-producer /sync /{} 10 1 &> producer.log &'.format(host1.name))
|
||||
time.sleep(1)
|
||||
|
||||
host1.cmd('export NDN_LOG=examples.PartialSyncConsumerApp=INFO:$NDN_LOG')
|
||||
host1.cmd('psync-consumer /sync 5 &> consumer.log &')
|
||||
|
||||
info('Sleeping 90 seconds for convergence\n')
|
||||
time.sleep(90)
|
||||
|
||||
consumerSubs = int(host1.cmd('cat consumer.log | grep -c Subscribing'))
|
||||
consumerUpdates = int(host1.cmd('cat consumer.log | grep -c Update'))
|
||||
producerPublish = int(host1.cmd('cat producer.log | grep -c Publish'))
|
||||
|
||||
if consumerSubs == 5 and consumerUpdates == 5 and producerPublish == 10:
|
||||
info('PSync partial sync has successfully converged.\n')
|
||||
else:
|
||||
info('PSync partial sync convergence was not successful. Exiting...\n')
|
||||
ndn.stop()
|
||||
sys.exit(1)
|
||||
@@ -1,111 +0,0 @@
|
||||
# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
|
||||
#
|
||||
# Copyright (C) 2015-2021, The University of Memphis,
|
||||
# Arizona Board of Regents,
|
||||
# Regents of the University of California.
|
||||
#
|
||||
# This file is part of Mini-NDN.
|
||||
# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
|
||||
#
|
||||
# Mini-NDN 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mini-NDN 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 Mini-NDN, e.g., in COPYING.md file.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from subprocess import PIPE
|
||||
|
||||
from mininet.log import setLogLevel, info
|
||||
from mininet.topo import Topo
|
||||
|
||||
from minindn.minindn import Minindn
|
||||
from minindn.apps.app_manager import AppManager
|
||||
from minindn.util import MiniNDNCLI, getPopen
|
||||
from minindn.apps.nfd import Nfd
|
||||
from minindn.helpers.nfdc import Nfdc
|
||||
|
||||
PREFIX = "/A"
|
||||
|
||||
def printOutput(output):
|
||||
_out = output.decode("utf-8").split("\n")
|
||||
for _line in _out:
|
||||
info(_line + "\n")
|
||||
|
||||
def run():
|
||||
Minindn.cleanUp()
|
||||
Minindn.verifyDependencies()
|
||||
|
||||
# Topology can be created/modified using Mininet topo object
|
||||
topo = Topo()
|
||||
info("Setup\n")
|
||||
# add hosts
|
||||
a = topo.addHost('a')
|
||||
b = topo.addHost('b')
|
||||
|
||||
# add links
|
||||
topo.addLink(a, b, delay='10ms', bw=10) # bw = bandwidth
|
||||
|
||||
info(topo.links(withInfo=True))
|
||||
|
||||
ndn = Minindn(topo=topo)
|
||||
ndn.start()
|
||||
|
||||
# configure and start nfd on each node
|
||||
info("Configuring NFD\n")
|
||||
AppManager(ndn, ndn.net.hosts, Nfd, logLevel="INFO")
|
||||
|
||||
"""
|
||||
There are multiple ways of setting up routes in Mini-NDN
|
||||
refer: https://minindn.memphis.edu/experiment.html#routing-options
|
||||
It can also be set manually as follows. The important bit to note here
|
||||
is the use of the Nfdc command
|
||||
"""
|
||||
for link in topo.links(withInfo=True):
|
||||
node1, node2, node_info = link
|
||||
host1 = ndn.net[node1]
|
||||
host2 = ndn.net[node2]
|
||||
interface = host2.connectionsTo(host1)[0][0]
|
||||
interface_ip = interface.IP()
|
||||
bandwidth = interface.params.get("bw", 100) * 1000000
|
||||
info(f"Setting up route from {node1} to {node2} with bandwidth {bandwidth}\n")
|
||||
Nfdc.createFace(host1, interface_ip, bandwidth=bandwidth)
|
||||
Nfdc.registerRoute(host1, PREFIX, interface_ip, cost=0)
|
||||
|
||||
# Start cc server
|
||||
info("Starting cc...\n")
|
||||
qsccp_server_log = open(f"{ndn.workDir}/qsccp-demo/qsccp-server.log", "w")
|
||||
getPopen(ndn.net["b"], "cc-producer --prefix {}".format(PREFIX), stdout=qsccp_server_log,\
|
||||
stderr=qsccp_server_log)
|
||||
|
||||
# start cc client
|
||||
qsccp_client_log = open(f"{ndn.workDir}/qsccp-demo/qsccp-client.log", "w")
|
||||
ping1 = getPopen(ndn.net["a"], "qsccp-client --prefix {} --timingStop 10000".format(PREFIX), stdout=qsccp_client_log, stderr=qsccp_client_log)
|
||||
ping1.wait()
|
||||
# printOutput(ping1.stdout.read())
|
||||
|
||||
# interface = ndn.net["b"].connectionsTo(ndn.net["a"])[0][0]
|
||||
# info("Failing link\n") # failing link by setting link loss to 100%
|
||||
# interface.config(delay="10ms", bw=10, loss=100)
|
||||
# info ("\n starting ping2 client \n")
|
||||
|
||||
# ping2 = getPopen(ndn.net["a"], "ndnping {} -c 5".format(PREFIX), stdout=PIPE, stderr=PIPE)
|
||||
# ping2.wait()
|
||||
# printOutput(ping2.stdout.read())
|
||||
|
||||
# interface.config(delay="10ms", bw=10, loss=0) # bringing back the link
|
||||
|
||||
info("\nExperiment Completed!\n")
|
||||
MiniNDNCLI(ndn.net)
|
||||
ndn.stop()
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel("info")
|
||||
run()
|
||||
Executable
+58
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
Build a simple network from scratch, using mininet primitives.
|
||||
This is more complicated than using the higher-level classes,
|
||||
but it exposes the configuration details and allows customization.
|
||||
|
||||
For most tasks, the higher-level API will be preferable.
|
||||
"""
|
||||
|
||||
from mininet.net import init
|
||||
from mininet.node import Node, OVSKernelSwitch
|
||||
from mininet.util import createLink
|
||||
from mininet.log import setLogLevel, info
|
||||
|
||||
def scratchNet( cname='controller', cargs='ptcp:' ):
|
||||
"Create network from scratch using kernel switch."
|
||||
|
||||
info( "*** Creating nodes\n" )
|
||||
controller = Node( 'c0', inNamespace=False )
|
||||
switch = Node( 's0', inNamespace=False )
|
||||
h0 = Node( 'h0' )
|
||||
h1 = Node( 'h1' )
|
||||
|
||||
info( "*** Creating links\n" )
|
||||
createLink( node1=h0, node2=switch, port1=0, port2=0 )
|
||||
createLink( node1=h1, node2=switch, port1=0, port2=1 )
|
||||
|
||||
info( "*** Configuring hosts\n" )
|
||||
h0.setIP( h0.intfs[ 0 ], '192.168.123.1', 24 )
|
||||
h1.setIP( h1.intfs[ 0 ], '192.168.123.2', 24 )
|
||||
info( str( h0 ) + '\n' )
|
||||
info( str( h1 ) + '\n' )
|
||||
|
||||
info( "*** Starting network using Open vSwitch kernel datapath\n" )
|
||||
controller.cmd( cname + ' ' + cargs + '&' )
|
||||
switch.cmd( 'ovs-dpctl del-dp dp0' )
|
||||
switch.cmd( 'ovs-dpctl add-dp dp0' )
|
||||
for intf in switch.intfs.values():
|
||||
print switch.cmd( 'ovs-dpctl add-if dp0 ' + intf )
|
||||
print switch.cmd( 'ovs-openflowd dp0 tcp:127.0.0.1 &' )
|
||||
|
||||
info( "*** Running test\n" )
|
||||
h0.cmdPrint( 'ping -c1 ' + h1.IP() )
|
||||
|
||||
info( "*** Stopping network\n" )
|
||||
controller.cmd( 'kill %' + cname )
|
||||
switch.cmd( 'ovs-dpctl del-dp dp0' )
|
||||
switch.cmd( 'kill %ovs-openflowd' )
|
||||
switch.deleteIntfs()
|
||||
info( '\n' )
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
info( '*** Scratch network demo (kernel datapath)\n' )
|
||||
OVSKernelSwitch.setup()
|
||||
init()
|
||||
scratchNet()
|
||||
Executable
+68
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
Build a simple network from scratch, using mininet primitives.
|
||||
This is more complicated than using the higher-level classes,
|
||||
but it exposes the configuration details and allows customization.
|
||||
|
||||
For most tasks, the higher-level API will be preferable.
|
||||
|
||||
This version uses the user datapath and an explicit control network.
|
||||
"""
|
||||
|
||||
from mininet.net import init
|
||||
from mininet.node import Node
|
||||
from mininet.util import createLink
|
||||
from mininet.log import setLogLevel, info
|
||||
|
||||
def scratchNetUser( cname='controller', cargs='ptcp:' ):
|
||||
"Create network from scratch using user switch."
|
||||
|
||||
# It's not strictly necessary for the controller and switches
|
||||
# to be in separate namespaces. For performance, they probably
|
||||
# should be in the root namespace. However, it's interesting to
|
||||
# see how they could work even if they are in separate namespaces.
|
||||
|
||||
info( '*** Creating Network\n' )
|
||||
controller = Node( 'c0' )
|
||||
switch = Node( 's0')
|
||||
h0 = Node( 'h0' )
|
||||
h1 = Node( 'h1' )
|
||||
cintf, sintf = createLink( controller, switch )
|
||||
h0intf, sintf1 = createLink( h0, switch )
|
||||
h1intf, sintf2 = createLink( h1, switch )
|
||||
|
||||
info( '*** Configuring control network\n' )
|
||||
controller.setIP( cintf, '10.0.123.1', 24 )
|
||||
switch.setIP( sintf, '10.0.123.2', 24 )
|
||||
|
||||
info( '*** Configuring hosts\n' )
|
||||
h0.setIP( h0intf, '192.168.123.1', 24 )
|
||||
h1.setIP( h1intf, '192.168.123.2', 24 )
|
||||
|
||||
info( '*** Network state:\n' )
|
||||
for node in controller, switch, h0, h1:
|
||||
info( str( node ) + '\n' )
|
||||
|
||||
info( '*** Starting controller and user datapath\n' )
|
||||
controller.cmd( cname + ' ' + cargs + '&' )
|
||||
switch.cmd( 'ifconfig lo 127.0.0.1' )
|
||||
intfs = [ sintf1, sintf2 ]
|
||||
switch.cmd( 'ofdatapath -i ' + ','.join( intfs ) + ' ptcp: &' )
|
||||
switch.cmd( 'ofprotocol tcp:' + controller.IP() + ' tcp:localhost &' )
|
||||
|
||||
info( '*** Running test\n' )
|
||||
h0.cmdPrint( 'ping -c1 ' + h1.IP() )
|
||||
|
||||
info( '*** Stopping network\n' )
|
||||
controller.cmd( 'kill %' + cname )
|
||||
switch.cmd( 'kill %ofdatapath' )
|
||||
switch.cmd( 'kill %ofprotocol' )
|
||||
switch.deleteIntfs()
|
||||
info( '\n' )
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
info( '*** Scratch network demo (user datapath)\n' )
|
||||
init()
|
||||
scratchNetUser()
|
||||
Executable
+71
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
Create a network and start sshd(8) on each host.
|
||||
|
||||
While something like rshd(8) would be lighter and faster,
|
||||
(and perfectly adequate on an in-machine network)
|
||||
the advantage of running sshd is that scripts can work
|
||||
unchanged on mininet and hardware.
|
||||
|
||||
In addition to providing ssh access to hosts, this example
|
||||
demonstrates:
|
||||
|
||||
- creating a convenience function to construct networks
|
||||
- connecting the host network to the root namespace
|
||||
- running server processes (sshd in this case) on hosts
|
||||
"""
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.cli import CLI
|
||||
from mininet.log import lg
|
||||
from mininet.node import Node, OVSKernelSwitch
|
||||
from mininet.topolib import TreeTopo
|
||||
from mininet.util import createLink
|
||||
|
||||
def TreeNet( depth=1, fanout=2, **kwargs ):
|
||||
"Convenience function for creating tree networks."
|
||||
topo = TreeTopo( depth, fanout )
|
||||
return Mininet( topo, **kwargs )
|
||||
|
||||
def connectToRootNS( network, switch, ip, prefixLen, routes ):
|
||||
"""Connect hosts to root namespace via switch. Starts network.
|
||||
network: Mininet() network object
|
||||
switch: switch to connect to root namespace
|
||||
ip: IP address for root namespace node
|
||||
prefixLen: IP address prefix length (e.g. 8, 16, 24)
|
||||
routes: host networks to route to"""
|
||||
# Create a node in root namespace and link to switch 0
|
||||
root = Node( 'root', inNamespace=False )
|
||||
intf = createLink( root, switch )[ 0 ]
|
||||
root.setIP( intf, ip, prefixLen )
|
||||
# Start network that now includes link to root namespace
|
||||
network.start()
|
||||
# Add routes from root ns to hosts
|
||||
for route in routes:
|
||||
root.cmd( 'route add -net ' + route + ' dev ' + intf )
|
||||
|
||||
def sshd( network, cmd='/usr/sbin/sshd', opts='-D' ):
|
||||
"Start a network, connect it to root ns, and run sshd on all hosts."
|
||||
switch = network.switches[ 0 ] # switch to use
|
||||
ip = '10.123.123.1' # our IP address on host network
|
||||
routes = [ '10.0.0.0/8' ] # host networks to route to
|
||||
connectToRootNS( network, switch, ip, 8, routes )
|
||||
for host in network.hosts:
|
||||
host.cmd( cmd + ' ' + opts + '&' )
|
||||
print
|
||||
print "*** Hosts are running sshd at the following addresses:"
|
||||
print
|
||||
for host in network.hosts:
|
||||
print host.name, host.IP()
|
||||
print
|
||||
print "*** Type 'exit' or control-D to shut down network"
|
||||
CLI( network )
|
||||
for host in network.hosts:
|
||||
host.cmd( 'kill %' + cmd )
|
||||
network.stop()
|
||||
|
||||
if __name__ == '__main__':
|
||||
lg.setLogLevel( 'info')
|
||||
net = TreeNet( depth=1, fanout=4, switch=OVSKernelSwitch )
|
||||
sshd( net )
|
||||
@@ -1,100 +0,0 @@
|
||||
# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
|
||||
#
|
||||
# Copyright (C) 2015-2020, The University of Memphis,
|
||||
# Arizona Board of Regents,
|
||||
# Regents of the University of California.
|
||||
#
|
||||
# This file is part of Mini-NDN.
|
||||
# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
|
||||
#
|
||||
# Mini-NDN 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mini-NDN 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 Mini-NDN, e.g., in COPYING.md file.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
from mininet.log import setLogLevel, info
|
||||
from mininet.topo import Topo
|
||||
|
||||
from minindn.minindn import Minindn
|
||||
from minindn.util import MiniNDNCLI
|
||||
from minindn.apps.app_manager import AppManager
|
||||
from minindn.apps.nfd import Nfd
|
||||
from minindn.helpers.ndn_routing_helper import NdnRoutingHelper
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel('info')
|
||||
|
||||
Minindn.cleanUp()
|
||||
Minindn.verifyDependencies()
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--face-type', dest='faceType', default='udp', choices=['udp', 'tcp'])
|
||||
parser.add_argument('--routing', dest='routingType', default='link-state',
|
||||
choices=['link-state', 'hr', 'dry'],
|
||||
help='''Choose routing type, dry = link-state is used
|
||||
but hr is calculated for comparision.''')
|
||||
|
||||
'''
|
||||
Experiment run with default topology, test cases won't work with other topologies
|
||||
# With calculateNPossibleRoutes,
|
||||
10 # routing = hr, N = All, from A, 3 routes needs to added to NFD.
|
||||
a +++++++ b # a - b -- cost 10
|
||||
+ + # a - c -- cost 10
|
||||
10 + + 10 # a - d -- cost 20
|
||||
+ + # Same goes for B being a source.
|
||||
c d #
|
||||
'''
|
||||
topo = Topo()
|
||||
a = topo.addHost('a')
|
||||
b = topo.addHost('b')
|
||||
c = topo.addHost('c')
|
||||
d = topo.addHost('d')
|
||||
topo.addLink(a, b, delay='10ms')
|
||||
topo.addLink(a, c, delay='10ms')
|
||||
topo.addLink(b, d, delay='10ms')
|
||||
|
||||
ndn = Minindn(parser=parser, topo=topo)
|
||||
|
||||
ndn.start()
|
||||
|
||||
info('Starting NFD on nodes\n')
|
||||
nfds = AppManager(ndn, ndn.net.hosts, Nfd)
|
||||
|
||||
info('Adding static routes to NFD\n')
|
||||
grh = NdnRoutingHelper(ndn.net, ndn.args.faceType, ndn.args.routingType)
|
||||
# For all host, pass ndn.net.hosts or a list, [ndn.net['a'], ..] or [ndn.net.hosts[0],.]
|
||||
grh.addOrigin([ndn.net['a']], ["/abc"])
|
||||
grh.calculateNPossibleRoutes()
|
||||
|
||||
'''
|
||||
prefix "/abc" is advertise from node A, it should be reachable from all other nodes.
|
||||
'''
|
||||
routesFromA = ndn.net['a'].cmd("nfdc route | grep -v '/localhost/nfd'")
|
||||
if '/ndn/b-site/b' not in routesFromA or \
|
||||
'/ndn/c-site/c' not in routesFromA or \
|
||||
'/ndn/d-site/d' not in routesFromA:
|
||||
info("Route addition failed\n")
|
||||
|
||||
routesToPrefix = ndn.net['b'].cmd("nfdc fib | grep '/abc'")
|
||||
if '/abc' not in routesToPrefix:
|
||||
info("Missing route to advertised prefix, Route addition failed\n")
|
||||
ndn.net.stop()
|
||||
sys.exit(1)
|
||||
|
||||
info('Route addition to NFD completed\n')
|
||||
|
||||
MiniNDNCLI(ndn.net)
|
||||
|
||||
ndn.stop()
|
||||
@@ -1,105 +0,0 @@
|
||||
# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
|
||||
#
|
||||
# Copyright (C) 2015-2021, The University of Memphis,
|
||||
# Arizona Board of Regents,
|
||||
# Regents of the University of California.
|
||||
#
|
||||
# This file is part of Mini-NDN.
|
||||
# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
|
||||
#
|
||||
# Mini-NDN 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mini-NDN 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 Mini-NDN, e.g., in COPYING.md file.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
"""
|
||||
This example demonstrates the functionality of the Traffic generator. It consists of a traffic
|
||||
server and client. The server will listen for interest on the prefix specified in the server
|
||||
configuration file. The client will send a designated number of interests to the server and
|
||||
get the data back.
|
||||
More details on traffic generator here: https://github.com/named-data/ndn-traffic-generator
|
||||
"""
|
||||
|
||||
from time import sleep
|
||||
|
||||
from mininet.log import setLogLevel, info
|
||||
from minindn.minindn import Minindn
|
||||
from minindn.util import MiniNDNCLI
|
||||
from minindn.apps.app_manager import AppManager
|
||||
from minindn.apps.nfd import Nfd
|
||||
from minindn.apps.nlsr import Nlsr
|
||||
from minindn.util import copyExistentFile
|
||||
from examples.nlsr.nlsr_common import getParser
|
||||
|
||||
def trafficServer(node, serverConfFile):
|
||||
"""
|
||||
Start traffic server
|
||||
:parma mininet.node.Host node: mininet node object
|
||||
:param string serverConfFile: server configuration file
|
||||
"""
|
||||
info ("Starting traffic server \n")
|
||||
# c = 10, i.e maximum number of Interests to respond
|
||||
cmd = 'ndn-traffic-server -c {} {} &> traffic-server.log &'.format(10, serverConfFile)
|
||||
node.cmd(cmd)
|
||||
sleep(10)
|
||||
|
||||
# The server configuration file uses /example prefix to advertise its service
|
||||
# thus, server needs to advertise this prefix for the client to reach it
|
||||
serverPrefix = "/example"
|
||||
server.cmd('nlsrc advertise {}'.format(serverPrefix))
|
||||
sleep(5) # sleep for routing convergence
|
||||
|
||||
def trafficClient(node, clientConfFile):
|
||||
"""
|
||||
Start traffic client
|
||||
:parma mininet.node.Host node: The expiration period in milliseconds, or None if not specified.
|
||||
:param string clientConfFile: client configuration file
|
||||
"""
|
||||
info ("Starting ndn traffic client \n")
|
||||
# c = 10, total number of Interests to be generated each at 200ms interval
|
||||
cmd = 'ndn-traffic-client -c {} -i {} {} &> traffic-client.log &'.format(10, 200, clientConfFile)
|
||||
node.cmd(cmd)
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel('info')
|
||||
|
||||
# Traffic generator configuration files. For this example, we are using default conf files.
|
||||
# More details on configuration files here: https://github.com/named-data/ndn-traffic-generator
|
||||
possibleServerConfPath = ["/etc/ndn/ndn-traffic-server.conf.sample", "/usr/local/etc/ndn/ndn-traffic-server.conf.sample"]
|
||||
possibleClientConfPath = ["/etc/ndn/ndn-traffic-client.conf.sample", "/usr/local/etc/ndn/ndn-traffic-client.conf.sample"]
|
||||
|
||||
Minindn.cleanUp()
|
||||
Minindn.verifyDependencies()
|
||||
ndn = Minindn(parser=getParser())
|
||||
ndn.start()
|
||||
|
||||
nfds = AppManager(ndn, ndn.net.hosts, Nfd)
|
||||
nlsrs = AppManager(ndn, ndn.net.hosts, Nlsr)
|
||||
sleep(90)
|
||||
|
||||
# Default topology is used in this experiment "/topologies/default-topology.conf"
|
||||
# lets make node "a" as a traffic-server node, and node "c" as a traffic-client node
|
||||
server = ndn.net['a']
|
||||
client = ndn.net['c']
|
||||
serverConf = '{}/{}/server-conf'.format(ndn.workDir, server.name)
|
||||
clientConf = '{}/{}/client-conf'.format(ndn.workDir, client.name)
|
||||
|
||||
copyExistentFile(server, possibleServerConfPath, serverConf)
|
||||
copyExistentFile(server, possibleClientConfPath, clientConf)
|
||||
|
||||
trafficServer(server, serverConf)
|
||||
trafficClient(client, clientConf)
|
||||
# default location for the results: /tmp/minindn/
|
||||
|
||||
MiniNDNCLI(ndn.net)
|
||||
ndn.stop()
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
Create a 1024-host network, and run the CLI on it.
|
||||
If this fails because of kernel limits, you may have
|
||||
to adjust them, e.g. by adding entries to /etc/sysctl.conf
|
||||
and running sysctl -p. Check util/sysctl_addon.
|
||||
"""
|
||||
|
||||
from mininet.cli import CLI
|
||||
from mininet.log import setLogLevel
|
||||
from mininet.node import OVSKernelSwitch
|
||||
from mininet.topolib import TreeNet
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
network = TreeNet( depth=2, fanout=32, switch=OVSKernelSwitch )
|
||||
network.run( CLI, network )
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"Create a 64-node tree network, and test connectivity using ping."
|
||||
|
||||
from mininet.log import setLogLevel
|
||||
from mininet.node import UserSwitch, OVSKernelSwitch # , KernelSwitch
|
||||
from mininet.topolib import TreeNet
|
||||
|
||||
def treePing64():
|
||||
"Run ping test on 64-node tree networks."
|
||||
|
||||
results = {}
|
||||
switches = { # 'reference kernel': KernelSwitch,
|
||||
'reference user': UserSwitch,
|
||||
'Open vSwitch kernel': OVSKernelSwitch }
|
||||
|
||||
for name in switches:
|
||||
print "*** Testing", name, "datapath"
|
||||
switch = switches[ name ]
|
||||
network = TreeNet( depth=2, fanout=8, switch=switch )
|
||||
result = network.run( network.pingAll )
|
||||
results[ name ] = result
|
||||
|
||||
print
|
||||
print "*** Tree network ping results:"
|
||||
for name in switches:
|
||||
print "%s: %d%% packet loss" % ( name, results[ name ] )
|
||||
print
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
treePing64()
|
||||
@@ -1,44 +0,0 @@
|
||||
import time
|
||||
|
||||
from mininet.log import setLogLevel, info
|
||||
|
||||
from minindn.wifi.minindnwifi import MinindnWifi
|
||||
from minindn.apps.app_manager import AppManager
|
||||
from minindn.apps.nfd import Nfd
|
||||
from minindn.apps.nlsr import Nlsr
|
||||
from minindn.helpers.experiment import Experiment
|
||||
|
||||
setLogLevel('info')
|
||||
|
||||
# Setup code
|
||||
sync = Nlsr.SYNC_PSYNC
|
||||
|
||||
ndn = MinindnWifi()
|
||||
args = ndn.args
|
||||
|
||||
ndn.start()
|
||||
|
||||
info('Starting NFD on nodes\n')
|
||||
nfds = AppManager(ndn, ndn.net.stations, Nfd, logLevel='INFO')
|
||||
face_dict = ndn.setupFaces()
|
||||
|
||||
if not face_dict:
|
||||
print("Hint: Are you sure you're running with a topology with a faces section? \
|
||||
Try topologies/wifi/nlsr_wifi_example.conf")
|
||||
|
||||
info('Starting NLSR on nodes\n')
|
||||
nlsrs = AppManager(ndn, [], Nlsr)
|
||||
|
||||
print(face_dict)
|
||||
|
||||
for host in ndn.net.stations:
|
||||
nlsrs.startOnNode(host, sync=sync, logLevel='INFO', faceDict=face_dict)
|
||||
time.sleep(0.1)
|
||||
|
||||
Experiment.checkConvergence(ndn, ndn.net.stations, 60, False)
|
||||
|
||||
# Uncomment for CLI access
|
||||
from minindn.util import MiniNDNWifiCLI
|
||||
MiniNDNWifiCLI(ndn.net)
|
||||
|
||||
ndn.stop()
|
||||
@@ -1,84 +0,0 @@
|
||||
# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
|
||||
#
|
||||
# Copyright (C) 2015-2021, The University of Memphis,
|
||||
# Arizona Board of Regents,
|
||||
# Regents of the University of California.
|
||||
#
|
||||
# This file is part of Mini-NDN.
|
||||
# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
|
||||
#
|
||||
# Mini-NDN 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mini-NDN 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 Mini-NDN, e.g., in COPYING.md file.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from mininet.log import setLogLevel, info
|
||||
from minindn.wifi.minindnwifi import MinindnWifi
|
||||
from minindn.util import MiniNDNWifiCLI
|
||||
from minindn.apps.app_manager import AppManager
|
||||
from minindn.apps.nfd import Nfd
|
||||
from minindn.helpers.nfdc import Nfdc
|
||||
from minindn.helpers.ndnping import NDNPing
|
||||
from time import sleep
|
||||
# This experiment uses the singleap topology and is intended to be a basic
|
||||
# test case where we see if two nodes can send interests to each other.
|
||||
def runExperiment():
|
||||
setLogLevel('info')
|
||||
|
||||
info("Starting network")
|
||||
ndnwifi = MinindnWifi()
|
||||
a = ndnwifi.net["sta1"]
|
||||
b = ndnwifi.net["sta2"]
|
||||
# Test for model-based mobility
|
||||
if ndnwifi.args.modelMob:
|
||||
ndnwifi.startMobilityModel(model='GaussMarkov')
|
||||
#Test for replay based mobility
|
||||
if ndnwifi.args.mobility:
|
||||
info("Running with mobility...")
|
||||
|
||||
p1, p2, p3, p4 = dict(), dict(), dict(), dict()
|
||||
p1 = {'position': '40.0,30.0,0.0'}
|
||||
p2 = {'position': '40.0,40.0,0.0'}
|
||||
p3 = {'position': '31.0,10.0,0.0'}
|
||||
p4 = {'position': '200.0,200.0,0.0'}
|
||||
|
||||
ndnwifi.net.mobility(a, 'start', time=1, **p1)
|
||||
ndnwifi.net.mobility(b, 'start', time=2, **p2)
|
||||
ndnwifi.net.mobility(a, 'stop', time=12, **p3)
|
||||
ndnwifi.net.mobility(b, 'stop', time=22, **p4)
|
||||
ndnwifi.net.stopMobility(time=23)
|
||||
ndnwifi.startMobility(time=0, mob_rep=1, reverse=False)
|
||||
|
||||
ndnwifi.start()
|
||||
info("Starting NFD")
|
||||
sleep(2)
|
||||
AppManager(ndnwifi, ndnwifi.net.stations, Nfd)
|
||||
|
||||
info("Starting pingserver...")
|
||||
NDNPing.startPingServer(b, "/example")
|
||||
faceID = Nfdc.createFace(a, b.IP())
|
||||
Nfdc.registerRoute(a, "/example", faceID)
|
||||
|
||||
info("Starting ping...")
|
||||
NDNPing.ping(a, "/example", nPings=10)
|
||||
|
||||
sleep(10)
|
||||
# Start the CLI
|
||||
MiniNDNWifiCLI(ndnwifi.net)
|
||||
ndnwifi.net.stop()
|
||||
ndnwifi.cleanUp()
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
runExperiment()
|
||||
except Exception as e:
|
||||
MinindnWifi.handleException()
|
||||
-186
@@ -1,186 +0,0 @@
|
||||
#!/bin/bash
|
||||
# -*- Mode:bash; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
|
||||
#
|
||||
# Copyright (C) 2015-2021, The University of Memphis,
|
||||
# Arizona Board of Regents,
|
||||
# Regents of the University of California.
|
||||
#
|
||||
# This file is part of Mini-NDN.
|
||||
# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
|
||||
#
|
||||
# Mini-NDN 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mini-NDN 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 Mini-NDN, e.g., in COPYING.md file.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
set -eo pipefail
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||
|
||||
# These commands are generally installed on most systems. If not, user must install manually.
|
||||
# 'sudo' is not directly used by this script, but Mininet install.sh uses it, so we check that also.
|
||||
NEEDED_BINARIES=(
|
||||
awk
|
||||
git
|
||||
sudo
|
||||
)
|
||||
MISSING_BINARIES=()
|
||||
|
||||
SUDO=
|
||||
if [[ $(id -u) -eq 0 ]]; then
|
||||
if [[ -n $SUDO_USER ]] && [[ -z $SKIPSUDOCHECK ]]; then
|
||||
cat <<EOT
|
||||
Do not run this script through sudo
|
||||
Instead, run this script as a regular user; the script will call sudo when needed
|
||||
To bypass this check, set the environment variable SKIPSUDOCHECK=1
|
||||
EOT
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
SUDO=sudo
|
||||
fi
|
||||
|
||||
for B in "${NEEDED_BINARIES[@]}"; do
|
||||
if ! command -v "$B" >/dev/null; then
|
||||
MISSING_BINARIES+=("$B")
|
||||
fi
|
||||
done
|
||||
if [[ ${#MISSING_BINARIES[@]} -gt 0 ]] ; then
|
||||
echo "Missing commands (${MISSING_BINARIES[*]}) to start this script. To install, run:"
|
||||
echo " $SUDO apt install --no-install-recommends ca-certificates curl git mawk sudo"
|
||||
echo " $SUDO yum install ca-certificates curl git mawk sudo"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CODEROOT="$(pwd)/dl"
|
||||
NJOBS=$(nproc)
|
||||
MEM_JOBS=$(awk '$1=="MemAvailable:" { print int($2/(1536*1024)); exit }' /proc/meminfo)
|
||||
if [[ $MEM_JOBS -lt 1 ]]; then
|
||||
NJOBS=1
|
||||
echo 'Insufficient available RAM, build may fail'
|
||||
elif [[ $MEM_JOBS -lt $NJOBS ]]; then
|
||||
NJOBS=$MEM_JOBS
|
||||
fi
|
||||
PREFER_FROM=ppa
|
||||
PPA_PKGS=()
|
||||
|
||||
ARGS=$(getopt -o 'hy' -l 'help,dir:,jobs:,no-wifi,ppa,source,cxx:,dummy-keychain,nfd:,psync:,nlsr:,tools:,traffic:,infoedit:,mininet:,mnwifi:,dl-only,ignore-existing' -- "$@")
|
||||
eval set -- "$ARGS"
|
||||
while true; do
|
||||
case $1 in
|
||||
-h|--help) HELP=1; shift;;
|
||||
-y) CONFIRM=1; shift;;
|
||||
--dir) CODEROOT=$2; shift 2;;
|
||||
--jobs) NJOBS=$((0+$2)); shift 2;;
|
||||
--no-wifi) NO_WIFI=1; shift;;
|
||||
--ppa) PREFER_FROM=ppa; shift;;
|
||||
--source) PREFER_FROM=source; shift;;
|
||||
--cxx) CXX_VERSION=$2; NO_PPA=1; shift 2;;
|
||||
--dummy-keychain) CXX_DUMMY_KEYCHAIN=1; NO_PPA=1; shift;;
|
||||
--nfd) NFD_VERSION=$2; NO_PPA=1; shift 2;;
|
||||
--psync) PSYNC_VERSION=$2; NO_PPA=1; shift 2;;
|
||||
--nlsr) NLSR_VERSION=$2; NO_PPA=1; shift 2;;
|
||||
--tools) TOOLS_VERSION=$2; NO_PPA=1; shift 2;;
|
||||
--traffic) TRAFFIC_VERSION=$2; NO_PPA=1; shift 2;;
|
||||
--infoedit) INFOEDIT_VERSION=$2; shift 2;;
|
||||
--mininet) MININET_VERSION=$2; shift 2;;
|
||||
--mnwifi) MNWIFI_VERSION=$2; shift 2;;
|
||||
--dl-only) DL_ONLY=1; shift;;
|
||||
--ignore-existing) IGNORE_EXISTING=1; shift;;
|
||||
--) shift; break;;
|
||||
*) exit 1;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ $NJOBS -le 0 ]]; then
|
||||
cat <<EOT
|
||||
--jobs must be a positive integer.
|
||||
Run ./install.sh -h to see help message.
|
||||
EOT
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ $HELP -eq 1 ]]; then
|
||||
cat <<EOT
|
||||
./install.sh [OPTION]...
|
||||
|
||||
General options:
|
||||
-h Display help and exit.
|
||||
-y Skip confirmation.
|
||||
--dir=${CODEROOT}
|
||||
Set where to download and compile the code.
|
||||
--jobs=${NJOBS}
|
||||
Set number of parallel jobs.
|
||||
--no-wifi
|
||||
Do not install Mininet-WiFi.
|
||||
|
||||
Install preference options:
|
||||
--ppa
|
||||
Install available packages from named-data PPA.
|
||||
This is the default on Ubuntu, unless a source code version option is used.
|
||||
--source
|
||||
Install all packages from source code.
|
||||
|
||||
Source code version options:
|
||||
--cxx=[VERSION]
|
||||
Set ndn-cxx version.
|
||||
--dummy-keychain
|
||||
Patch ndn-cxx to use dummy KeyChain.
|
||||
This disables signing and verifications, which allows experiments to run faster.
|
||||
Use this option only if your scenario does not require signature verification.
|
||||
--nfd=[VERSION]
|
||||
Set NFD version.
|
||||
--psync=[VERSION]
|
||||
Set PSync version.
|
||||
--nlsr=[VERSION]
|
||||
Set NLSR version.
|
||||
--tools=[VERSION]
|
||||
Set NDN Essential Tools version.
|
||||
--traffic=[VERSION]
|
||||
Set NDN Traffic Generator version.
|
||||
--infoedit=[VERSION]
|
||||
Set infoedit version.
|
||||
--mininet=[VERSION]
|
||||
Set Mininet version.
|
||||
--mnwifi=[VERSION]
|
||||
Set Mininet-WiFi version.
|
||||
Acceptable version syntaxes:
|
||||
* git commit/tag/branch, example: --nfd=NFD-0.7.1
|
||||
* git repository (e.g. fork) and commit/tag/branch, example:
|
||||
--nfd=https://github.com/named-data/NFD.git@NFD-0.7.1
|
||||
* change,patchset on named-data Gerrit, example: --nfd=6236,8
|
||||
|
||||
Misc options:
|
||||
--dl-only
|
||||
Download the source code only.
|
||||
You may modify the code in ${CODEROOT} and then rerun this script to install them.
|
||||
--ignore-existing
|
||||
Ignore already installed binaries and libraries, and attempt to reinstall.
|
||||
This is useful if you have modified source code checkout and want to install again.
|
||||
EOT
|
||||
exit 0
|
||||
fi
|
||||
|
||||
trap 'set +e; trap - ERR; echo "Error!"; exit 1;' ERR
|
||||
|
||||
PKGDEPDIR="$(pwd)/util/pkgdep"
|
||||
if [[ -e /etc/os-release ]]; then
|
||||
source /etc/os-release
|
||||
fi
|
||||
for id in ${ID,,} ${ID_LIKE,,}; do
|
||||
if [[ -e $PKGDEPDIR/$id.sh ]]; then
|
||||
source "$PKGDEPDIR/$id.sh"
|
||||
source "$PKGDEPDIR/common.sh"
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
echo "Unsupported platform ${ID}, aborting"
|
||||
exit 1
|
||||
@@ -1 +0,0 @@
|
||||
__version__ = '0.5.0'
|
||||
Whitespace-only changes.
@@ -1,56 +0,0 @@
|
||||
# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
|
||||
#
|
||||
# Copyright (C) 2015-2019, The University of Memphis,
|
||||
# Arizona Board of Regents,
|
||||
# Regents of the University of California.
|
||||
#
|
||||
# This file is part of Mini-NDN.
|
||||
# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
|
||||
#
|
||||
# Mini-NDN 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mini-NDN 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 Mini-NDN, e.g., in COPYING.md file.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from mininet.node import Node
|
||||
|
||||
class AppManager(object):
|
||||
def __init__(self, minindn, hosts, cls, **appParams):
|
||||
self.cls = cls
|
||||
self.apps = []
|
||||
for host in hosts:
|
||||
# Don't run NDN apps on switches
|
||||
if isinstance(host, Node):
|
||||
self.startOnNode(host, **appParams)
|
||||
|
||||
minindn.cleanups.append(self.cleanup)
|
||||
|
||||
def startOnNode(self, host, **appParams):
|
||||
app = self.cls(host, **appParams)
|
||||
app.start()
|
||||
self.apps.append(app)
|
||||
|
||||
def cleanup(self):
|
||||
for app in self.apps:
|
||||
app.stop()
|
||||
|
||||
def __getitem__(self, nodeName):
|
||||
for app in self.apps:
|
||||
if app.node.name == nodeName:
|
||||
return app
|
||||
return None
|
||||
|
||||
def __iter__(self):
|
||||
return self.apps.__iter__()
|
||||
|
||||
def __next__(self):
|
||||
return self.apps.__next__()
|
||||
@@ -1,50 +0,0 @@
|
||||
# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
|
||||
#
|
||||
# Copyright (C) 2015-2019, The University of Memphis,
|
||||
# Arizona Board of Regents,
|
||||
# Regents of the University of California.
|
||||
#
|
||||
# This file is part of Mini-NDN.
|
||||
# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
|
||||
#
|
||||
# Mini-NDN 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mini-NDN 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 Mini-NDN, e.g., in COPYING.md file.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from minindn.util import getPopen
|
||||
|
||||
class Application(object):
|
||||
def __init__(self, node):
|
||||
self.node = node
|
||||
self.process = None
|
||||
self.logfile = None
|
||||
self.homeDir = self.node.params['params']['homeDir']
|
||||
|
||||
# Make directory for log file
|
||||
self.logDir = '{}/log'.format(self.homeDir)
|
||||
self.node.cmd('mkdir -p {}'.format(self.logDir))
|
||||
|
||||
def start(self, command, logfile, envDict=None):
|
||||
if self.process is None:
|
||||
self.logfile = open('{}/{}'.format(self.logDir, logfile), 'w')
|
||||
if isinstance(command, str):
|
||||
command = command.split()
|
||||
self.process = getPopen(self.node, command, envDict,
|
||||
stdout=self.logfile, stderr=self.logfile)
|
||||
|
||||
def stop(self):
|
||||
if self.process is not None:
|
||||
self.process.kill()
|
||||
self.process = None
|
||||
if self.logfile is not None:
|
||||
self.logfile.close()
|
||||
@@ -1,75 +0,0 @@
|
||||
# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
|
||||
#
|
||||
# Copyright (C) 2015-2019, The University of Memphis,
|
||||
# Arizona Board of Regents,
|
||||
# Regents of the University of California.
|
||||
#
|
||||
# This file is part of Mini-NDN.
|
||||
# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
|
||||
#
|
||||
# Mini-NDN 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mini-NDN 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 Mini-NDN, e.g., in COPYING.md file.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from minindn.apps.application import Application
|
||||
from minindn.util import copyExistentFile
|
||||
from minindn.minindn import Minindn
|
||||
|
||||
class Nfd(Application):
|
||||
|
||||
def __init__(self, node, logLevel='NONE', csSize=65536,
|
||||
csPolicy='lru', csUnsolicitedPolicy='drop-all'):
|
||||
Application.__init__(self, node)
|
||||
|
||||
self.logLevel = node.params['params'].get('nfd-log-level', logLevel)
|
||||
|
||||
self.confFile = '{}/nfd.conf'.format(self.homeDir)
|
||||
self.logFile = 'nfd.log'
|
||||
self.sockFile = '/run/{}.sock'.format(node.name)
|
||||
self.ndnFolder = '{}/.ndn'.format(self.homeDir)
|
||||
self.clientConf = '{}/client.conf'.format(self.ndnFolder)
|
||||
|
||||
# Copy nfd.conf file from /usr/local/etc/ndn or /etc/ndn to the node's home directory
|
||||
# Use nfd.conf as default configuration for NFD, else use the sample
|
||||
possibleConfPaths = ['/usr/local/etc/ndn/nfd.conf.sample', '/usr/local/etc/ndn/nfd.conf',
|
||||
'/etc/ndn/nfd.conf.sample', '/etc/ndn/nfd.conf']
|
||||
copyExistentFile(node, possibleConfPaths, self.confFile)
|
||||
|
||||
# Set log level
|
||||
node.cmd('infoedit -f {} -s log.default_level -v {}'.format(self.confFile, self.logLevel))
|
||||
# Open the conf file and change socket file name
|
||||
node.cmd('infoedit -f {} -s face_system.unix.path -v {}'.format(self.confFile, self.sockFile))
|
||||
|
||||
# Set CS parameters
|
||||
node.cmd('infoedit -f {} -s tables.cs_max_packets -v {}'.format(self.confFile, csSize))
|
||||
node.cmd('infoedit -f {} -s tables.cs_policy -v {}'.format(self.confFile, csPolicy))
|
||||
node.cmd('infoedit -f {} -s tables.cs_unsolicited_policy -v {}'.format(self.confFile, csUnsolicitedPolicy))
|
||||
|
||||
# Make NDN folder
|
||||
node.cmd('mkdir -p {}'.format(self.ndnFolder))
|
||||
|
||||
# Copy client configuration to host
|
||||
possibleClientConfPaths = ['/usr/local/etc/ndn/client.conf.sample', '/etc/ndn/client.conf.sample']
|
||||
copyExistentFile(node, possibleClientConfPaths, self.clientConf)
|
||||
|
||||
# Change the unix socket
|
||||
node.cmd('sudo sed -i "s|;transport|transport|g" {}'.format(self.clientConf))
|
||||
node.cmd('sudo sed -i "s|nfd.sock|{}.sock|g" {}'.format(node.name, self.clientConf))
|
||||
|
||||
if not Minindn.ndnSecurityDisabled:
|
||||
# Generate key and install cert for /localhost/operator to be used by NFD
|
||||
node.cmd('ndnsec-key-gen /localhost/operator | ndnsec-cert-install -')
|
||||
|
||||
def start(self):
|
||||
Application.start(self, 'nfd --config {}'.format(self.confFile), logfile=self.logFile)
|
||||
Minindn.sleep(0.5)
|
||||
@@ -1,286 +0,0 @@
|
||||
# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
|
||||
#
|
||||
# Copyright (C) 2015-2021, The University of Memphis,
|
||||
# Arizona Board of Regents,
|
||||
# Regents of the University of California.
|
||||
#
|
||||
# This file is part of Mini-NDN.
|
||||
# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
|
||||
#
|
||||
# Mini-NDN 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mini-NDN 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 Mini-NDN, e.g., in COPYING.md file.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import shutil
|
||||
import os, sys
|
||||
|
||||
from mininet.clean import sh
|
||||
from mininet.examples.cluster import RemoteMixin
|
||||
from mininet.log import warn
|
||||
from mininet.node import Switch
|
||||
|
||||
from minindn.apps.application import Application
|
||||
from minindn.util import scp, copyExistentFile
|
||||
from minindn.helpers.nfdc import Nfdc
|
||||
from minindn.minindn import Minindn
|
||||
|
||||
class Nlsr(Application):
|
||||
ROUTING_LINK_STATE = 'link-state'
|
||||
ROUTING_HYPERBOLIC = 'hr'
|
||||
ROUTING_DRY_RUN = 'dry'
|
||||
SYNC_PSYNC = 'psync'
|
||||
|
||||
def __init__(self, node, logLevel='NONE', security=False, sync=SYNC_PSYNC,
|
||||
faceType='udp', nFaces=3, routingType=ROUTING_LINK_STATE, faceDict=None):
|
||||
Application.__init__(self, node)
|
||||
try:
|
||||
from mn_wifi.node import Node_wifi
|
||||
if isinstance(node, Node_wifi) and faceDict == None:
|
||||
warn("Wifi nodes need to have faces configured manually. Please see \
|
||||
documentation on provided helper methods.\r\n")
|
||||
sys.exit(1)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
self.network = '/ndn/'
|
||||
self.node = node
|
||||
self.parameters = self.node.params['params']
|
||||
|
||||
if self.parameters.get('nlsr-log-level', None) != None:
|
||||
logLevel = self.parameters.get('nlsr-log-level')
|
||||
|
||||
if logLevel in ['NONE', 'WARN', 'INFO', 'DEBUG', 'TRACE']:
|
||||
self.envDict = {'NDN_LOG': 'nlsr.*={}'.format(logLevel)}
|
||||
else:
|
||||
self.envDict = {'NDN_LOG': logLevel}
|
||||
|
||||
self.logFile = 'nlsr.log'
|
||||
self.routerName = '/{}C1.Router/cs/{}'.format('%', node.name)
|
||||
self.confFile = '{}/nlsr.conf'.format(self.homeDir)
|
||||
self.security = security
|
||||
self.sync = sync
|
||||
self.faceType = faceType
|
||||
self.infocmd = 'infoedit -f nlsr.conf'
|
||||
# Expected format- node : tuple (node name, IP, cost)
|
||||
self.faceDict = faceDict
|
||||
|
||||
self.parameters = self.node.params['params']
|
||||
|
||||
self.nFaces = nFaces
|
||||
if routingType == Nlsr.ROUTING_HYPERBOLIC:
|
||||
self.hyperbolicState = 'on'
|
||||
elif routingType == Nlsr.ROUTING_DRY_RUN:
|
||||
self.hyperbolicState = 'dry-run'
|
||||
else:
|
||||
self.hyperbolicState = 'off'
|
||||
self.hyperRadius = self.parameters.get('radius', 0.0)
|
||||
self.hyperAngle = self.parameters.get('angle', 0.0)
|
||||
|
||||
if ((self.hyperbolicState == 'on' or self.hyperbolicState == 'dry-run') and
|
||||
(self.hyperRadius == 0.0 or self.hyperAngle == 0.0)):
|
||||
warn('Hyperbolic coordinates in topology file are either missing or misconfigured.')
|
||||
warn('Check that each node has one radius value and one or two angle value(s).')
|
||||
sys.exit(1)
|
||||
|
||||
self.neighborIPs = []
|
||||
possibleConfPaths = ['/usr/local/etc/ndn/nlsr.conf.sample', '/etc/ndn/nlsr.conf.sample']
|
||||
copyExistentFile(node, possibleConfPaths, '{}/nlsr.conf'.format(self.homeDir))
|
||||
|
||||
self.createConfigFile()
|
||||
|
||||
if security and not Minindn.ndnSecurityDisabled:
|
||||
self.createKeysAndCertificates()
|
||||
|
||||
def start(self):
|
||||
self.createFaces()
|
||||
Application.start(self, 'nlsr -f {}'.format(self.confFile), self.logFile, self.envDict)
|
||||
Minindn.sleep(1)
|
||||
|
||||
def createFaces(self):
|
||||
for ip in self.neighborIPs:
|
||||
Nfdc.createFace(self.node, ip, self.faceType, isPermanent=True)
|
||||
|
||||
@staticmethod
|
||||
def createKey(host, name, outputFile):
|
||||
host.cmd('ndnsec-key-gen {} > {}'.format(name, outputFile))
|
||||
|
||||
@staticmethod
|
||||
def createCertificate(host, signer, keyFile, outputFile):
|
||||
host.cmd('ndnsec-cert-gen -s {} -r {} > {}'.format(signer, keyFile, outputFile))
|
||||
|
||||
def createKeysAndCertificates(self):
|
||||
securityDir = '{}/security'.format(Minindn.workDir)
|
||||
|
||||
if not os.path.exists(securityDir):
|
||||
os.mkdir(securityDir)
|
||||
|
||||
rootName = self.network
|
||||
rootCertFile = '{}/root.cert'.format(securityDir)
|
||||
if not os.path.isfile(rootCertFile):
|
||||
# Create root certificate
|
||||
sh('ndnsec-key-gen {}'.format(rootName)) # Installs a self-signed cert into the system
|
||||
sh('ndnsec-cert-dump -i {} > {}'.format(rootName, rootCertFile))
|
||||
|
||||
# Create necessary certificates for each site
|
||||
nodeSecurityFolder = '{}/security'.format(self.homeDir)
|
||||
|
||||
self.node.cmd('mkdir -p {}'.format(nodeSecurityFolder))
|
||||
|
||||
# Create temp folders for remote nodes on this machine (localhost) to store site.key file
|
||||
# from RemoteNodes
|
||||
if not os.path.exists(nodeSecurityFolder) and \
|
||||
isinstance(self.node, RemoteMixin) and self.node.isRemote:
|
||||
os.makedirs(nodeSecurityFolder)
|
||||
|
||||
shutil.copyfile('{}/root.cert'.format(securityDir),
|
||||
'{}/root.cert'.format(nodeSecurityFolder))
|
||||
|
||||
# Create site certificate
|
||||
siteName = '{}{}-site'.format(self.network, self.node.name)
|
||||
siteKeyFile = '{}/site.keys'.format(nodeSecurityFolder)
|
||||
siteCertFile = '{}/site.cert'.format(nodeSecurityFolder)
|
||||
Nlsr.createKey(self.node, siteName, siteKeyFile)
|
||||
|
||||
# Copy siteKeyFile from remote for ndnsec-cert-gen
|
||||
if isinstance(self.node, RemoteMixin) and self.node.isRemote:
|
||||
login = 'mininet@{}'.format(self.node.server)
|
||||
src = '{}:{}'.format(login, siteKeyFile)
|
||||
dst = siteKeyFile
|
||||
scp(src, dst)
|
||||
|
||||
# Root key is in root namespace, must sign site key and then install on host
|
||||
sh('ndnsec-cert-gen -s {} -r {} > {}'.format(rootName, siteKeyFile, siteCertFile))
|
||||
|
||||
# Copy root.cert and site.cert from localhost to remote host
|
||||
if isinstance(self.node, RemoteMixin) and self.node.isRemote:
|
||||
login = 'mininet@{}'.format(self.node.server)
|
||||
src = '{}/site.cert'.format(nodeSecurityFolder)
|
||||
src2 = '{}/root.cert'.format(nodeSecurityFolder)
|
||||
dst = '{}:/tmp/'.format(login)
|
||||
scp(src, src2, dst)
|
||||
self.node.cmd('mv /tmp/*.cert {}'.format(nodeSecurityFolder))
|
||||
|
||||
self.node.cmd('ndnsec-cert-install -f {}'.format(siteCertFile))
|
||||
|
||||
# Create and install operator certificate
|
||||
opName = '{}/%C1.Operator/op'.format(siteName)
|
||||
opKeyFile = '{}/op.keys'.format(nodeSecurityFolder)
|
||||
opCertFile = '{}/op.cert'.format(nodeSecurityFolder)
|
||||
Nlsr.createKey(self.node, opName, opKeyFile)
|
||||
Nlsr.createCertificate(self.node, siteName, opKeyFile, opCertFile)
|
||||
self.node.cmd('ndnsec-cert-install -f {}'.format(opCertFile))
|
||||
|
||||
# Create and install router certificate
|
||||
routerName = '{}/%C1.Router/cs/{}'.format(siteName, self.node.name)
|
||||
routerKeyFile = '{}/router.keys'.format(nodeSecurityFolder)
|
||||
routerCertFile = '{}/router.cert'.format(nodeSecurityFolder)
|
||||
Nlsr.createKey(self.node, routerName, routerKeyFile)
|
||||
Nlsr.createCertificate(self.node, opName, routerKeyFile, routerCertFile)
|
||||
self.node.cmd('ndnsec-cert-install -f {}'.format(routerCertFile))
|
||||
|
||||
def createConfigFile(self):
|
||||
|
||||
self.__editGeneralSection()
|
||||
if self.faceDict:
|
||||
self.__editNeighborsSectionManual()
|
||||
else:
|
||||
self.__editNeighborsSection()
|
||||
self.__editHyperbolicSection()
|
||||
self.__editFibSection()
|
||||
self.__editAdvertisingSection()
|
||||
self.__editSecuritySection()
|
||||
|
||||
def __editGeneralSection(self):
|
||||
|
||||
self.node.cmd('{} -s general.network -v {}'.format(self.infocmd, self.network))
|
||||
self.node.cmd('{} -s general.site -v /{}-site'.format(self.infocmd, self.node.name))
|
||||
self.node.cmd('{} -s general.router -v /%C1.Router/cs/{}'.format(self.infocmd, self.node.name))
|
||||
self.node.cmd('{} -s general.state-dir -v {}/log'.format(self.infocmd, self.homeDir))
|
||||
self.node.cmd('{} -s general.sync-protocol -v {}'.format(self.infocmd, self.sync))
|
||||
|
||||
def __editNeighborsSection(self):
|
||||
|
||||
self.node.cmd('{} -d neighbors.neighbor'.format(self.infocmd))
|
||||
for intf in self.node.intfList():
|
||||
link = intf.link
|
||||
if not link:
|
||||
continue
|
||||
|
||||
node1, node2 = link.intf1.node, link.intf2.node
|
||||
|
||||
# Todo: add some switch support
|
||||
if isinstance(node1, Switch) or isinstance(node2, Switch):
|
||||
continue
|
||||
|
||||
if node1 == self.node:
|
||||
other = node2
|
||||
ip = other.IP(str(link.intf2))
|
||||
else:
|
||||
other = node1
|
||||
ip = other.IP(str(link.intf1))
|
||||
|
||||
linkCost = intf.params.get('delay', '10ms').replace('ms', '')
|
||||
|
||||
self.neighborIPs.append(ip)
|
||||
|
||||
self.node.cmd('{} -a neighbors.neighbor \
|
||||
<<<\'name {}{}-site/%C1.Router/cs/{} face-uri {}://{}\n link-cost {}\''
|
||||
.format(self.infocmd, self.network, other.name, other.name,
|
||||
self.faceType, ip, linkCost))
|
||||
|
||||
def __editNeighborsSectionManual(self):
|
||||
|
||||
self.node.cmd('{} -d neighbors.neighbor'.format(self.infocmd))
|
||||
if self.node not in self.faceDict:
|
||||
return
|
||||
for link in self.faceDict[self.node]:
|
||||
nodeName = link[0]
|
||||
nodeIP = link[1]
|
||||
linkCost = link[2]
|
||||
|
||||
self.node.cmd('{} -a neighbors.neighbor \
|
||||
<<<\'name {}{}-site/%C1.Router/cs/{} face-uri {}://{}\n link-cost {}\''
|
||||
.format(self.infocmd, self.network, nodeName, nodeName,
|
||||
self.faceType, nodeIP, linkCost))
|
||||
|
||||
|
||||
def __editHyperbolicSection(self):
|
||||
|
||||
self.node.cmd('{} -s hyperbolic.state -v {}'.format(self.infocmd, self.hyperbolicState))
|
||||
self.node.cmd('{} -s hyperbolic.radius -v {}'.format(self.infocmd, self.hyperRadius))
|
||||
self.node.cmd('{} -s hyperbolic.angle -v {}'.format(self.infocmd, self.hyperAngle))
|
||||
|
||||
def __editFibSection(self):
|
||||
|
||||
self.node.cmd('{} -s fib.max-faces-per-prefix -v {}'.format(self.infocmd, self.nFaces))
|
||||
|
||||
def __editAdvertisingSection(self):
|
||||
|
||||
self.node.cmd('{} -d advertising.prefix'.format(self.infocmd))
|
||||
self.node.cmd('{} -s advertising.prefix -v {}{}-site/{}'
|
||||
.format(self.infocmd, self.network, self.node.name, self.node.name))
|
||||
|
||||
def __editSecuritySection(self):
|
||||
|
||||
self.node.cmd('{} -d security.cert-to-publish'.format(self.infocmd))
|
||||
if not self.security:
|
||||
self.node.cmd('{} -s security.validator.trust-anchor.type -v any'.format(self.infocmd))
|
||||
self.node.cmd('{} -d security.validator.trust-anchor.file-name'.format(self.infocmd))
|
||||
self.node.cmd('{} -s security.prefix-update-validator.trust-anchor.type -v any'.format(self.infocmd))
|
||||
self.node.cmd('{} -d security.prefix-update-validator.trust-anchor.file-name'.format(self.infocmd))
|
||||
else:
|
||||
self.node.cmd('{} -s security.validator.trust-anchor.file-name -v security/root.cert'.format(self.infocmd))
|
||||
self.node.cmd('{} -s security.prefix-update-validator.trust-anchor.file-name -v security/site.cert'.format(self.infocmd))
|
||||
self.node.cmd('{} -p security.cert-to-publish -v security/site.cert'.format(self.infocmd))
|
||||
self.node.cmd('{} -p security.cert-to-publish -v security/op.cert'.format(self.infocmd))
|
||||
self.node.cmd('{} -p security.cert-to-publish -v security/router.cert'.format(self.infocmd))
|
||||
@@ -1,60 +0,0 @@
|
||||
# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
|
||||
#
|
||||
# Copyright (C) 2015-2020, The University of Memphis,
|
||||
# Arizona Board of Regents,
|
||||
# Regents of the University of California.
|
||||
#
|
||||
# This file is part of Mini-NDN.
|
||||
# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
|
||||
#
|
||||
# Mini-NDN 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mini-NDN 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 Mini-NDN, e.g., in COPYING.md file.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from minindn.apps.application import Application
|
||||
from mininet.log import debug
|
||||
|
||||
|
||||
class Tshark(Application):
|
||||
"""
|
||||
Logging utility to dump network traffic of a node to a PCAP file.
|
||||
|
||||
The app is based on the command line tool tshark and requires tshark to be installed on the system.
|
||||
"""
|
||||
|
||||
def __init__(self, node, logFolder="./", singleLogFile=False):
|
||||
"""
|
||||
:param logFolder Folder, where PCAP files are stored.
|
||||
:param singleLogFile Single PCAP file per node, or individual PCAP for each interface
|
||||
"""
|
||||
|
||||
Application.__init__(self, node)
|
||||
|
||||
self.logFolder = logFolder
|
||||
self.singleLogFile = singleLogFile
|
||||
|
||||
# Create logfile folder in case it does not exist
|
||||
node.cmd('mkdir -p {}'.format(self.logFolder))
|
||||
|
||||
def start(self):
|
||||
# Start capturing traffic with Tshark.
|
||||
debug("[{0}] Starting tshark logging\n".format(self.node.name))
|
||||
|
||||
if self.singleLogFile:
|
||||
interfaces = ["-i " + intf for intf in self.node.intfNames()]
|
||||
ndnDumpOutputFile = "{}/{}-interfaces.pcap".format(self.logFolder, self.node.name)
|
||||
self.node.cmd("tshark {} -w {} -q &".format(" ".join(interfaces), ndnDumpOutputFile))
|
||||
else:
|
||||
for intf in self.node.intfNames():
|
||||
ndnDumpOutputFile = "{}/{}.pcap".format(self.logFolder, intf)
|
||||
self.node.cmd("tshark -i {} -w {} -q &".format(intf, ndnDumpOutputFile))
|
||||
Whitespace-only changes.
@@ -1,132 +0,0 @@
|
||||
# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
|
||||
#
|
||||
# Copyright (C) 2015-2020, The University of Memphis,
|
||||
# Arizona Board of Regents,
|
||||
# Regents of the University of California.
|
||||
#
|
||||
# This file is part of Mini-NDN.
|
||||
# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
|
||||
#
|
||||
# Mini-NDN 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mini-NDN 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 Mini-NDN, e.g., in COPYING.md file.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import time
|
||||
import sys
|
||||
from itertools import cycle
|
||||
|
||||
from mininet.log import info
|
||||
|
||||
from minindn.helpers.nfdc import Nfdc
|
||||
from minindn.helpers.ndnping import NDNPing
|
||||
from minindn.util import getSafeName
|
||||
|
||||
class Experiment(object):
|
||||
@staticmethod
|
||||
def checkConvergence(ndn, hosts, convergenceTime, quit=False, returnConvergenceInfo=False):
|
||||
# Wait for convergence time period
|
||||
info('Waiting {} seconds for convergence...\n'.format(convergenceTime))
|
||||
time.sleep(convergenceTime)
|
||||
info('...done\n')
|
||||
|
||||
didNlsrConverge = True
|
||||
convergeInfo = {}
|
||||
|
||||
for host in hosts:
|
||||
convergeInfo[host.name] = {}
|
||||
statusRouter = host.cmd('nfdc fib list | grep site/%C1.Router/cs/')
|
||||
statusPrefix = host.cmd('nfdc fib list | grep ndn | grep site | grep -v Router')
|
||||
for node in hosts:
|
||||
# Node has its own router name in the fib list, but not name prefix
|
||||
routerPrefix = ('/ndn/{}-site/%C1.Router/cs/{}'.format(node.name, node.name))
|
||||
namePrefix = ('/ndn/{}-site/{}'.format(node.name, node.name))
|
||||
|
||||
statusRouterCheck = routerPrefix not in statusRouter
|
||||
statusPrefixCheck = host.name != node.name and namePrefix not in statusPrefix
|
||||
|
||||
if statusRouterCheck or statusPrefixCheck:
|
||||
didNlsrConverge = False
|
||||
host.cmd('echo {} > convergence-result &'.format(False))
|
||||
|
||||
if returnConvergenceInfo:
|
||||
convergeInfo[host.name][node.name] = []
|
||||
if statusRouterCheck:
|
||||
convergeInfo[host.name][node.name].append(routerPrefix)
|
||||
|
||||
if statusPrefixCheck:
|
||||
convergeInfo[host.name][node.name].append(namePrefix)
|
||||
else:
|
||||
host.cmd('echo {} > convergence-result &'.format(True))
|
||||
|
||||
if didNlsrConverge:
|
||||
if quit:
|
||||
info('NLSR has converged successfully. Exiting...\n')
|
||||
ndn.stop()
|
||||
sys.exit(0)
|
||||
else:
|
||||
info('NLSR has converged successfully.\n')
|
||||
else:
|
||||
if quit:
|
||||
info('NLSR has not converged. Exiting...\n')
|
||||
ndn.stop()
|
||||
sys.exit(1)
|
||||
else:
|
||||
info('NLSR has not converged.\n')
|
||||
|
||||
if returnConvergenceInfo:
|
||||
return didNlsrConverge, convergeInfo
|
||||
else:
|
||||
return didNlsrConverge
|
||||
|
||||
@staticmethod
|
||||
def setupPing(hosts, strategy):
|
||||
for host in hosts:
|
||||
host.cmd('mkdir -p ping-data')
|
||||
Nfdc.setStrategy(host, '/ndn/', strategy)
|
||||
prefix = getSafeName('/ndn/{}-site/{}'.format(host.name, host.name))
|
||||
NDNPing.startPingServer(host, prefix)
|
||||
|
||||
@staticmethod
|
||||
def startPctPings(net, nPings, pctTraffic=1.0):
|
||||
nNodesToPing = int(round(len(net.hosts) * pctTraffic))
|
||||
info('Each node will ping {} node(s)\n'.format(nNodesToPing))
|
||||
# Temporarily store all the nodes being pinged by a particular node
|
||||
nodesPingedList = []
|
||||
pingedDict = {}
|
||||
|
||||
for host in net.hosts:
|
||||
# Create a circular list
|
||||
pool = cycle(net.hosts)
|
||||
|
||||
# Move iterator to current node
|
||||
next(x for x in pool if host.name == x.name)
|
||||
|
||||
# Track number of nodes to ping scheduled for this node
|
||||
nNodesScheduled = 0
|
||||
|
||||
while nNodesScheduled < nNodesToPing:
|
||||
other = next(pool)
|
||||
|
||||
# Do not ping self
|
||||
if host.name != other.name:
|
||||
destPrefix = getSafeName('/ndn/{}-site/{}'.format(other.name, other.name))
|
||||
NDNPing.ping(host, destPrefix, other.name, nPings)
|
||||
nodesPingedList.append(other)
|
||||
|
||||
# Always increment because in 100% case a node should not ping itself
|
||||
nNodesScheduled = nNodesScheduled + 1
|
||||
|
||||
pingedDict[host] = nodesPingedList
|
||||
nodesPingedList = []
|
||||
|
||||
return pingedDict
|
||||
@@ -1,203 +0,0 @@
|
||||
# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
|
||||
#
|
||||
# Copyright (C) 2015-2020, The University of Memphis,
|
||||
# Arizona Board of Regents,
|
||||
# Regents of the University of California.
|
||||
#
|
||||
# This file is part of Mini-NDN.
|
||||
# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
|
||||
#
|
||||
# Mini-NDN 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mini-NDN 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 Mini-NDN, e.g., in COPYING.md file.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from igraph import Graph
|
||||
from mininet.log import info, debug
|
||||
|
||||
|
||||
class LinkInfo(object):
|
||||
"""
|
||||
This class is used to encapsule link information (IP and interface names).
|
||||
"""
|
||||
|
||||
def __init__(self, start_intf_name, start_ip, end_intf_name, end_ip):
|
||||
self.start_intf_name = start_intf_name
|
||||
self.start_intf_ip = start_ip
|
||||
self.end_intf_name = end_intf_name
|
||||
self.end_ip = end_ip
|
||||
|
||||
|
||||
class IPRoutingHelper(object):
|
||||
"""The routing helper allows to run IP-based evaluations with Mini-NDN. It configures static IP
|
||||
routes to all nodes, which means that all nodes can reach all other nodes in the network
|
||||
reachable, even when relaying is required.
|
||||
|
||||
Usage from Experiment folder: `IPRoutingHelper.calcAllRoutes(self.net)`
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def findLinkInformation(links, first_node, second_node):
|
||||
""" This method returns link information of a link connecting two nodes.
|
||||
|
||||
:param links: All links in the emulation topology
|
||||
:param first_node: Current node which is looked at
|
||||
:param second_node: Target node (neighbour of first_node)
|
||||
:return: Link information as LinkInfo object, or returns null None if the
|
||||
nodes are not directly connected
|
||||
"""
|
||||
for link in links:
|
||||
if link.intf1.node.name == first_node and link.intf2.node.name == second_node:
|
||||
return LinkInfo(link.intf1.name, link.intf1.ip, link.intf2.name, link.intf2.ip)
|
||||
elif link.intf2.node.name == first_node and link.intf1.node.name == second_node:
|
||||
return LinkInfo(link.intf2.name, link.intf2.ip, link.intf1.name, link.intf1.ip)
|
||||
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def calculateAllSubPaths(path):
|
||||
""" This method returns all subpaths in forward and reverse order of a given path
|
||||
|
||||
:param path: Given path for which subpaths are calculated
|
||||
:return: List of all subpaths
|
||||
"""
|
||||
paths = []
|
||||
|
||||
# Append original path and reversed path
|
||||
paths.append(path)
|
||||
reversed = path[:]
|
||||
reversed.reverse()
|
||||
paths.append(reversed)
|
||||
|
||||
# Iterate over all possible lengths for subpaths
|
||||
for subpath_length in range(2, len(path)):
|
||||
# Get all subpaths of length subpath_lenght of the given path
|
||||
for start_index in range(0, len(path) - subpath_length + 1):
|
||||
subpath = path[start_index: start_index + subpath_length]
|
||||
paths.append(subpath)
|
||||
subpath = subpath[:]
|
||||
subpath.reverse()
|
||||
paths.append(subpath)
|
||||
return paths
|
||||
|
||||
@staticmethod
|
||||
def replaceExistingSubpaths(path, existing_paths):
|
||||
|
||||
subpaths = []
|
||||
for subpath_length in range(3, len(path)):
|
||||
for start_index in range(0, len(path) - subpath_length + 1):
|
||||
subpaths.append(path[start_index: start_index + subpath_length])
|
||||
subpaths.reverse()
|
||||
|
||||
for subpath in subpaths:
|
||||
if len(subpath) == len(path):
|
||||
continue
|
||||
if (subpath[0], subpath[-1]) in existing_paths:
|
||||
existing = existing_paths[(subpath[0], subpath[-1])]
|
||||
path = path[:path.index(existing[0])] + existing[:] + path[
|
||||
path.index(existing[-1]) + 1:]
|
||||
break
|
||||
return path
|
||||
|
||||
@staticmethod
|
||||
def calcAllRoutes(net):
|
||||
""" Configures IP routes between all nodes in the emulation topology. This is done in three
|
||||
steps:
|
||||
|
||||
1) IP forwarding is enabled on all nodes
|
||||
2) The igraph lib is used to calculate all shortest paths between the nodes
|
||||
3) Route add commands are used to actually configure the ip routes
|
||||
|
||||
:param net:
|
||||
"""
|
||||
|
||||
mini_nodes = net.hosts
|
||||
mini_links = net.links
|
||||
|
||||
# Enabling IP forwaring on all nodes
|
||||
info('Configure IP forwarding on all nodes\n')
|
||||
for node in mini_nodes:
|
||||
node.cmd('sysctl -w net.ipv4.ip_forward=1')
|
||||
|
||||
# Create the network graph to calculate all shortest paths between nodes
|
||||
node_names = [node.name for node in mini_nodes]
|
||||
links = []
|
||||
for link in mini_links:
|
||||
links.append((link.intf1.node.name, link.intf2.node.name))
|
||||
links.append((link.intf2.node.name, link.intf1.node.name))
|
||||
networkGraph = Graph()
|
||||
networkGraph = networkGraph.as_directed()
|
||||
for node in node_names:
|
||||
networkGraph.add_vertex(node)
|
||||
for (a, b) in links:
|
||||
networkGraph.add_edges([(a, b), (b, a)])
|
||||
|
||||
existing_paths = {} # Variable existing_paths stores all paths that are installed
|
||||
shortest_paths = [] # List of calculated shorted paths betweeen all nodes
|
||||
|
||||
# Calculate shortest paths between all nodes using libigraph
|
||||
for from_node in node_names:
|
||||
for to_node in node_names:
|
||||
if from_node != to_node:
|
||||
if (from_node, to_node) in existing_paths \
|
||||
or (to_node, from_node) in existing_paths:
|
||||
continue
|
||||
paths = networkGraph.get_all_shortest_paths(from_node, to_node)
|
||||
if len(paths) == 0:
|
||||
continue
|
||||
paths.sort(key=lambda x: str(x))
|
||||
paths.sort(key=lambda x: len(x))
|
||||
shortest_path = paths[0] # Shortest path with node indizes as nodes
|
||||
# Translate node indizes to node names
|
||||
shortest_path_nodenames = [networkGraph.vs['name'][node]
|
||||
for node in shortest_path]
|
||||
shortest_paths.append(shortest_path_nodenames)
|
||||
|
||||
# Iterate over shortest paths and store subpaths that need to be installed on the nodes.
|
||||
# Also, it is made sure that all paths and reverse paths are the same
|
||||
shortest_paths.sort(key=lambda x: len(x), reverse=True)
|
||||
for path in shortest_paths:
|
||||
# Replace already existing subpaths of the path to make sure that no two paths between
|
||||
# two nodes exist
|
||||
path = IPRoutingHelper.replaceExistingSubpaths(path, existing_paths)
|
||||
|
||||
# Mark all subpaths of path to install on nodes, unless they already exist
|
||||
subpaths = IPRoutingHelper.calculateAllSubPaths(path)
|
||||
for subpath in subpaths:
|
||||
if (subpath[0], subpath[-1]) not in existing_paths:
|
||||
existing_paths[(subpath[0], subpath[-1])] = subpath
|
||||
|
||||
# Iterate over all paths and configure the routes using the 'route add'
|
||||
info('Configure routes on all nodes\n')
|
||||
for path in existing_paths.values():
|
||||
start_node = path[0]
|
||||
end_node = path[-1]
|
||||
mini_start = net.get(start_node)
|
||||
mini_end = net.get(end_node)
|
||||
|
||||
link_info = IPRoutingHelper.findLinkInformation(mini_links, path[0], path[1])
|
||||
start_intf = link_info.start_intf_name
|
||||
|
||||
# Configure the route for every IP address of the destination
|
||||
for intf in mini_end.intfs:
|
||||
addr = mini_end.intfs[intf].ip
|
||||
if len(path) == 2:
|
||||
# For direct connection, configure exit interface
|
||||
debug('[{}] route add -host {} dev {}\n'.format(start_node, addr, start_intf))
|
||||
mini_start.cmd('route add -host {} dev {}'.format(addr, start_intf))
|
||||
elif len(path) > 2:
|
||||
# For longer paths, configure next hop as gateway
|
||||
gateway_ip = link_info.end_ip
|
||||
debug('[{}] route add -host {} dev {} gw {}\n'
|
||||
.format(start_node, addr, start_intf, gateway_ip))
|
||||
mini_start.cmd('route add -host {} dev {} gw {}'
|
||||
.format(addr, start_intf, gateway_ip))
|
||||
@@ -1,394 +0,0 @@
|
||||
# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
|
||||
#
|
||||
# Copyright (C) 2015-2020, The University of Memphis
|
||||
#
|
||||
# This file is part of Mini-NDN.
|
||||
# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
|
||||
#
|
||||
# Mini-NDN 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mini-NDN 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 Mini-NDN, e.g., in COPYING.md file.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# IMPORTANT! This feature is in highly experimental phase and may go several changes
|
||||
# in future
|
||||
|
||||
'''
|
||||
This module will compute link state, hyperbolic and geohyperbolic
|
||||
routes and their costs from the given Mini-NDN topology
|
||||
'''
|
||||
|
||||
import sys
|
||||
import heapq
|
||||
from math import sin, cos, sinh, cosh, acos, acosh
|
||||
import json
|
||||
import operator
|
||||
from collections import defaultdict
|
||||
from joblib import Parallel, delayed
|
||||
|
||||
from mininet.log import info, debug, error, warn
|
||||
from minindn.helpers.nfdc import Nfdc as nfdc
|
||||
|
||||
UNKNOWN_DISTANCE = -1
|
||||
HYPERBOLIC_COST_ADJUSTMENT_FACTOR = 1000
|
||||
|
||||
def dijkstra(graph, start, end, ignoredNode=None):
|
||||
"""
|
||||
Compute shortest path and cost from a given source to a destination
|
||||
using Dijkstra algorithm
|
||||
|
||||
:param Graph graph: given network topology/graph
|
||||
:param Start start: source node in a given network graph/topology
|
||||
:end End end: destination node in a given network graph/topology
|
||||
:param Node ignoredNode: node to ignore computing shortest path from
|
||||
"""
|
||||
queue = [(0, start, [])]
|
||||
seen = set()
|
||||
while True:
|
||||
(cost, v, path) = heapq.heappop(queue)
|
||||
if v not in seen:
|
||||
path = path + [v]
|
||||
seen.add(v)
|
||||
if v == end:
|
||||
debug("Distance from {} to {} is {}".format(start, end, cost))
|
||||
return cost, path
|
||||
for (_next, c) in graph[v].items():
|
||||
if _next != ignoredNode: # Ignore path going via ignoreNode
|
||||
heapq.heappush(queue, (cost + c, _next, path))
|
||||
|
||||
if not queue: # return if no path exist from source - destination except via ignoreNode
|
||||
debug("Distance from {} to {} is {}".format(start, end, cost))
|
||||
return cost, None
|
||||
|
||||
def calculateAngularDistance(angleVectorI, angleVectorJ):
|
||||
"""
|
||||
For hyperbolic/geohyperbolic routing algorithm, this function computes angular distance between
|
||||
two nodes. A node can have two or more than two angular coordinates.
|
||||
|
||||
:param AngleVectorI angleVectorI: list of angular coordinate of a give node I
|
||||
:param AngleVectorJ angleVectorJ: list of angular coordinate of a give node J
|
||||
|
||||
ref: https://en.wikipedia.org/wiki/N-sphere#Spherical_coordinates
|
||||
|
||||
"""
|
||||
innerProduct = 0.0
|
||||
if len(angleVectorI) != len(angleVectorJ):
|
||||
error("Angle vector sizes do not match")
|
||||
return UNKNOWN_DISTANCE
|
||||
|
||||
# Calculate x0 of the vectors
|
||||
x0i = cos(angleVectorI[0])
|
||||
x0j = cos(angleVectorJ[0])
|
||||
|
||||
# Calculate xn of the vectors
|
||||
xni = sin(angleVectorI[len(angleVectorI) - 1])
|
||||
xnj = sin(angleVectorJ[len(angleVectorJ) - 1])
|
||||
|
||||
# Do the aggregation of the (n-1) coordinates (if there is more than one angle)
|
||||
# i.e contraction of all (n-1)-dimensional angular coordinates to one variable
|
||||
for k in range(0, len(angleVectorI)-1):
|
||||
xni *= sin(angleVectorI[k])
|
||||
xnj *= sin(angleVectorJ[k])
|
||||
|
||||
innerProduct += (x0i * x0j) + (xni * xnj)
|
||||
|
||||
if len(angleVectorI) > 1:
|
||||
for m in range(1, len(angleVectorI)):
|
||||
# Calculate euclidean coordinates given the angles and assuming R_sphere = 1
|
||||
xmi = cos(angleVectorI[m])
|
||||
xmj = cos(angleVectorJ[m])
|
||||
for l in range(0, m):
|
||||
xmi *= sin(angleVectorI[l])
|
||||
xmj *= sin(angleVectorJ[l])
|
||||
|
||||
innerProduct += xmi * xmj
|
||||
|
||||
# ArcCos of the inner product gives the angular distance
|
||||
# between two points on a d-dimensional sphere
|
||||
angularDist = acos(innerProduct)
|
||||
debug("Angular distance from {} to {} is {}".format(angleVectorI, angleVectorJ, angularDist))
|
||||
return angularDist
|
||||
|
||||
def getHyperbolicDistance(sourceNode, destNode):
|
||||
"""
|
||||
Return hyperbolic or geohyperbolic distance between two nodes. The distance is computed
|
||||
on the basis of following algorithm/mathematics
|
||||
ref: https://en.wikipedia.org/wiki/Hyperbolic_geometry
|
||||
"""
|
||||
r1 = [key for key in sourceNode][0]
|
||||
r2 = [key for key in destNode][0]
|
||||
zeta = 1.0
|
||||
dtheta = calculateAngularDistance(sourceNode[r1], destNode[r2])
|
||||
hyperbolicDistance = (1./zeta) * acosh(cosh(zeta * r1) * cosh(zeta * r2) -\
|
||||
sinh(zeta * r1) * sinh(zeta * r2) * cos(dtheta))
|
||||
|
||||
debug("Distance from {} to {} is {}".format(sourceNode, destNode, hyperbolicDistance))
|
||||
return hyperbolicDistance
|
||||
|
||||
class _CalculateRoutes(object):
|
||||
"""
|
||||
Creates a route calculation object, which is used to compute routes from a node to
|
||||
every other nodes in a given topology topology using hyperbolic or geohyperbolic
|
||||
routing algorithm
|
||||
|
||||
:param NetObject netObj: Mininet net object
|
||||
:param RoutingType routingType: (optional) Routing algorithm, link-state or hr etc
|
||||
"""
|
||||
def __init__(self, netObj, routingType):
|
||||
self.adjacenctMatrix = defaultdict(dict)
|
||||
self.nodeDict = defaultdict(dict)
|
||||
self.routingType = routingType
|
||||
self.isHrConfigValid = True
|
||||
for host in netObj.hosts:
|
||||
if 'radius' in host.params['params']:
|
||||
radius = float(host.params['params']['radius'])
|
||||
else:
|
||||
self.isHrConfigValid = False
|
||||
radius = 0.0
|
||||
if 'angle' in host.params['params']:
|
||||
angles = [float(x) for x in host.params['params']['angle'].split(',')]
|
||||
else:
|
||||
self.isHrConfigValid = False
|
||||
angles = [0.0]
|
||||
self.nodeDict[host.name][radius] = angles
|
||||
for link in netObj.topo.links(withInfo=True):
|
||||
linkDelay = int(link[2]['delay'].replace("ms", ""))
|
||||
self.adjacenctMatrix[link[0]][link[1]] = linkDelay
|
||||
self.adjacenctMatrix[link[1]][link[0]] = linkDelay
|
||||
|
||||
def getNestedDictionary(self):
|
||||
return defaultdict(self.getNestedDictionary)
|
||||
|
||||
def getRoutes(self, nFaces):
|
||||
resultMatrix = self.getNestedDictionary()
|
||||
routes = defaultdict(list)
|
||||
|
||||
if self.routingType == "link-state":
|
||||
if nFaces == 1:
|
||||
resultMatrix = self.computeDijkastra() # only best routes.
|
||||
else:
|
||||
resultMatrix = self.computeDijkastraAll() # all possible routes
|
||||
elif self.routingType == "hr":
|
||||
if self.isHrConfigValid == True:
|
||||
# Note: For hyperbolic, only way to find the best routes is by
|
||||
# computing all possible routes and getting the best one.
|
||||
resultMatrix = self.computeHyperbolic()
|
||||
else:
|
||||
warn('Hyperbolic coordinates in topology file are either missing or misconfigured.\n')
|
||||
warn('Check that each node has one radius value and one or two angle value(s).\n')
|
||||
return None
|
||||
|
||||
for node in resultMatrix:
|
||||
for destinationNode in resultMatrix[node]:
|
||||
# Sort node - destination via neighbor based on their cost
|
||||
tempDict = resultMatrix[node][destinationNode]
|
||||
shortedTempDict = sorted(tempDict.items(), key=operator.itemgetter(1))
|
||||
# nFaces option gets n-best faces based on shortest distance, default is all
|
||||
if nFaces == 0:
|
||||
for item in shortedTempDict:
|
||||
viaNeighbor = item[0]
|
||||
cost = item[1]
|
||||
routes[node].append([destinationNode, str(cost), viaNeighbor])
|
||||
else:
|
||||
for index, item in enumerate(shortedTempDict):
|
||||
if index >= nFaces:
|
||||
break
|
||||
viaNeighbor = item[0]
|
||||
cost = item[1]
|
||||
routes[node].append([destinationNode, str(cost), viaNeighbor])
|
||||
|
||||
debug("-routes----", routes)
|
||||
return routes
|
||||
|
||||
def getNodeNames(self):
|
||||
return [k for k in self.nodeDict]
|
||||
|
||||
def computeHyperbolic(self):
|
||||
paths = self.getNestedDictionary()
|
||||
nodeNames = self.getNodeNames()
|
||||
for node in self.nodeDict:
|
||||
neighbors = [k for k in self.adjacenctMatrix[node]]
|
||||
for viaNeighbor in neighbors:
|
||||
others = [x for x in nodeNames if x not in [viaNeighbor, node]]
|
||||
paths[node][viaNeighbor][viaNeighbor] = 0
|
||||
# Compute distance from neighbors to no-neighbors
|
||||
for destinationNode in others:
|
||||
hyperbolicDistance = getHyperbolicDistance(self.nodeDict[viaNeighbor],
|
||||
self.nodeDict[destinationNode])
|
||||
hyperbolicCost = int(HYPERBOLIC_COST_ADJUSTMENT_FACTOR \
|
||||
* round(hyperbolicDistance, 6))
|
||||
paths[node][destinationNode][viaNeighbor] = hyperbolicCost
|
||||
debug("Shortest Distance Matrix: {}".format(json.dumps(paths)))
|
||||
return paths
|
||||
|
||||
def computeDijkastra(self):
|
||||
"""
|
||||
Dijkstra computation: Compute all the shortest paths from nodes to the destinations.
|
||||
And fills the distance matrix with the corresponding source to destination cost
|
||||
"""
|
||||
distanceMatrix = self.getNestedDictionary()
|
||||
nodeNames = self.getNodeNames()
|
||||
for node in nodeNames:
|
||||
others = [x for x in nodeNames if x not in [node]]
|
||||
for destinationNode in others:
|
||||
cost, path = dijkstra(self.adjacenctMatrix, node, destinationNode)
|
||||
viaNeighbor = path[1]
|
||||
distanceMatrix[node][destinationNode][viaNeighbor] = cost
|
||||
|
||||
debug("Shortest Distance Matrix: {}".format(json.dumps(distanceMatrix)))
|
||||
return distanceMatrix
|
||||
|
||||
def computeDijkastraAll(self):
|
||||
"""
|
||||
Multi-path Dijkastra computation: Compute all the shortest paths from nodes to the
|
||||
destinations via all of its neighbors individually. And fills the distanceMatrixViaNeighbor
|
||||
with a corresponding source to its destination cost
|
||||
|
||||
Important: distanceMatrixViaNeighbor represents the shortest distance from a source to a
|
||||
destination via specific neighbors
|
||||
"""
|
||||
distanceMatrixViaNeighbor = self.getNestedDictionary()
|
||||
nodeNames = self.getNodeNames()
|
||||
for node in nodeNames:
|
||||
neighbors = [k for k in self.adjacenctMatrix[node]]
|
||||
for viaNeighbor in neighbors:
|
||||
directCost = self.adjacenctMatrix[node][viaNeighbor]
|
||||
distanceMatrixViaNeighbor[node][viaNeighbor][viaNeighbor] = directCost
|
||||
others = [x for x in nodeNames if x not in [viaNeighbor, node]]
|
||||
for destinationNode in others:
|
||||
nodeNeighborCost = self.adjacenctMatrix[node][viaNeighbor]
|
||||
# path variable is not used for now
|
||||
cost, path = dijkstra(self.adjacenctMatrix, viaNeighbor, destinationNode, node)
|
||||
if cost != 0 and path != None:
|
||||
totalCost = cost + nodeNeighborCost
|
||||
distanceMatrixViaNeighbor[node][destinationNode][viaNeighbor] = totalCost
|
||||
|
||||
debug("Shortest Distance Matrix: {}".format(json.dumps(distanceMatrixViaNeighbor)))
|
||||
return distanceMatrixViaNeighbor
|
||||
|
||||
class NdnRoutingHelper(object):
|
||||
"""
|
||||
This module is a helper class which helps to create face and register routes
|
||||
to NFD from a given node to all of its neighbors.
|
||||
|
||||
:param NetObject netObject: Mininet net object
|
||||
:param FaceType faceType: UDP, Ethernet etc.
|
||||
:param Routing routingType: (optional) Routing algorithm, link-state or hr etc
|
||||
|
||||
"""
|
||||
def __init__(self, netObject, faceType=nfdc.PROTOCOL_UDP, routingType="link-state", permanentFaces=False):
|
||||
self.net = netObject
|
||||
self.faceType = faceType
|
||||
self.routingType = routingType
|
||||
self.permanentFaces = permanentFaces
|
||||
self.routes = []
|
||||
self.namePrefixes = {host_name.name: [] for host_name in self.net.hosts}
|
||||
self.routeObject = _CalculateRoutes(self.net, self.routingType)
|
||||
|
||||
def globalRoutingHelperHandler(self):
|
||||
info('Creating faces and adding routes to FIB\n')
|
||||
|
||||
res = Parallel(n_jobs=-1, require='sharedmem',
|
||||
prefer="threads", verbose=1)(delayed(self.addNodeRoutes)(host) for host in self.net.hosts)
|
||||
|
||||
info('Processed all the routes to NFD\n')
|
||||
|
||||
def addNodeRoutes(self, node):
|
||||
"""
|
||||
Create faces to neighbors and add all routes for one node
|
||||
|
||||
:param Node node: Node from net object
|
||||
"""
|
||||
neighborIPs = self.getNeighbor(node)
|
||||
neighborFaces = self.createFaces(node, neighborIPs)
|
||||
self.routeAdd(node, neighborFaces)
|
||||
|
||||
def addOrigin(self, nodes, prefix):
|
||||
"""
|
||||
Add prefix/s as origin on node/s
|
||||
|
||||
:param Prefix prefix: Prefix that is originated by node/s (as producer) for this prefix
|
||||
:param Nodes nodes: List of nodes from net object
|
||||
"""
|
||||
for node in nodes:
|
||||
if not node.name in self.namePrefixes:
|
||||
self.namePrefixes[node.name] = []
|
||||
self.namePrefixes[node.name] += prefix
|
||||
|
||||
def calculateNPossibleRoutes(self, nFaces=0):
|
||||
"""
|
||||
By default, calculates all possible routes i.e. routes via all the faces of a node.
|
||||
pass nFaces if want to compute routes via n number of faces. e.g. 2. For larger topology
|
||||
the computation might take huge amount of time.
|
||||
|
||||
:param int nFaces: (optional) number of faces to consider while computing routes. Default
|
||||
i.e. nFaces = 0 will compute all possible routes
|
||||
|
||||
"""
|
||||
self.routes = self.routeObject.getRoutes(nFaces)
|
||||
if self.routes is not None:
|
||||
info('Route computation completed\n')
|
||||
self.globalRoutingHelperHandler()
|
||||
else:
|
||||
warn('Route computation failed\n')
|
||||
self.net.stop()
|
||||
sys.exit(1)
|
||||
|
||||
def calculateRoutes(self):
|
||||
# Calculate shortest path for every node
|
||||
self.calculateNPossibleRoutes(nFaces=1)
|
||||
|
||||
def createFaces(self, node, neighborIPs):
|
||||
neighborFaces = {}
|
||||
for k, ip in neighborIPs.items():
|
||||
faceID = nfdc.createFace(node, ip, self.faceType, self.permanentFaces)
|
||||
if not isinstance(faceID, str): raise ValueError(faceID)
|
||||
neighborFaces[k] = faceID
|
||||
return neighborFaces
|
||||
|
||||
|
||||
def routeAdd(self, node, neighborFaces):
|
||||
"""
|
||||
Add route from a node to its neighbors for each prefix/s advertised by destination node
|
||||
|
||||
:param Node node: source node (Mininet net.host)
|
||||
:param IP neighborIPs: IP addresses of neighbors
|
||||
"""
|
||||
neighbors = self.routes[node.name]
|
||||
for route in neighbors:
|
||||
destination = route[0]
|
||||
cost = int(route[1])
|
||||
nextHop = route[2]
|
||||
defaultPrefix = "/ndn/{}-site/{}".format(destination, destination)
|
||||
prefixes = [defaultPrefix] + self.namePrefixes[destination]
|
||||
for prefix in prefixes:
|
||||
# Register routes to all the available destination name prefix/s
|
||||
nfdc.registerRoute(node, prefix, neighborFaces[nextHop], cost=cost)
|
||||
@staticmethod
|
||||
def getNeighbor(node):
|
||||
# Nodes to IP mapping
|
||||
neighborIPs = defaultdict()
|
||||
for intf in node.intfList():
|
||||
link = intf.link
|
||||
if link:
|
||||
node1, node2 = link.intf1.node, link.intf2.node
|
||||
|
||||
if node1 == node:
|
||||
other = node2
|
||||
ip = other.IP(str(link.intf2))
|
||||
else:
|
||||
other = node1
|
||||
ip = other.IP(str(link.intf1))
|
||||
|
||||
# Used later to create faces
|
||||
neighborIPs[other.name] = ip
|
||||
return neighborIPs
|
||||
@@ -1,73 +0,0 @@
|
||||
# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
|
||||
#
|
||||
# Copyright (C) 2015-2021, The University of Memphis,
|
||||
# Arizona Board of Regents,
|
||||
# Regents of the University of California.
|
||||
#
|
||||
# This file is part of Mini-NDN.
|
||||
# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
|
||||
#
|
||||
# Mini-NDN 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mini-NDN 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 Mini-NDN, e.g., in COPYING.md file.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import time
|
||||
|
||||
# Todo: convert to app
|
||||
|
||||
class NDNPing(object):
|
||||
@staticmethod
|
||||
def ping(source, prefix, pingDataFile="output-client", nPings=1, interval=None, timeout=None,
|
||||
starting_seq_num=None, identifier=None, allow_stale_data=False, print_timestamp=True,
|
||||
sleepTime=0.2):
|
||||
print('Scheduling ping(s) from {} for {}'.format(source.name, prefix))
|
||||
# Use '&' to run in background and perform parallel pings
|
||||
source.cmd("mkdir -p ping-data")
|
||||
source.cmd('ndnping{1}{2}{3}{4}{5}{6}{7} {0} >> ping-data/{8}.txt &'
|
||||
.format(
|
||||
prefix,
|
||||
' -c {}'.format(nPings),
|
||||
' -i {}'.format(interval) if interval else '',
|
||||
' -o {}'.format(timeout) if timeout else '',
|
||||
' -n {}'.format(starting_seq_num) if starting_seq_num else '',
|
||||
' -p {}'.format(identifier) if identifier else '',
|
||||
' -a' if allow_stale_data else '',
|
||||
' -t' if print_timestamp else '',
|
||||
pingDataFile
|
||||
))
|
||||
time.sleep(sleepTime)
|
||||
|
||||
@staticmethod
|
||||
def startPingServer(source, prefix, pingDataFile="output-server", freshness=None, satisfy=None,
|
||||
size=None, timestamp=False, quiet=False):
|
||||
"""
|
||||
Start a pingserver
|
||||
:param string preifx: prefix to start pingserver on
|
||||
:param int freshness: FreshnessPeriod of the ping response, in milliseconds
|
||||
:param int satisfy: maximum number of pings to satisfy
|
||||
:param int size: size of response payload
|
||||
:param boolean timestamp: prepend a timestamp to each log message
|
||||
:param boolean quite: do not print a log message each time a ping packet is received
|
||||
"""
|
||||
print('Staring ping server on prefix {}'.format(prefix))
|
||||
source.cmd("mkdir -p ping-data")
|
||||
cmd = 'ndnpingserver {1}{2}{3}{4}{5} {0}>> ping-data/{6}.txt &'.format(
|
||||
prefix,
|
||||
'-f {}'.format(freshness) if freshness else '',
|
||||
'-p {}'.format(satisfy) if satisfy else '',
|
||||
'-s {}'.format(size) if size else '',
|
||||
'{}'.format('-t') if timestamp else '',
|
||||
'{}'.format('-q') if quiet else '',
|
||||
pingDataFile
|
||||
)
|
||||
source.cmd(cmd)
|
||||
@@ -1,131 +0,0 @@
|
||||
# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
|
||||
#
|
||||
# Copyright (C) 2015-2020, The University of Memphis,
|
||||
# Arizona Board of Regents,
|
||||
# Regents of the University of California.
|
||||
#
|
||||
# This file is part of Mini-NDN.
|
||||
# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
|
||||
#
|
||||
# Mini-NDN 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mini-NDN 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 Mini-NDN, e.g., in COPYING.md file.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from mininet.log import debug, warn
|
||||
from minindn.minindn import Minindn
|
||||
|
||||
# If needed (e.g. to speed up the process), use a smaller (or larger value)
|
||||
# based on your machines resource (CPU, memory)
|
||||
SLEEP_TIME = 0.0015
|
||||
|
||||
class Nfdc(object):
|
||||
STRATEGY_ASF = 'asf'
|
||||
STRATEGY_BEST_ROUTE = 'best-route'
|
||||
STRATEGY_MULTICAST = 'multicast'
|
||||
STRATEGY_NCC = 'ncc'
|
||||
PROTOCOL_UDP = 'udp'
|
||||
PROTOCOL_TCP = 'tcp'
|
||||
PROTOCOL_ETHER = 'ether'
|
||||
|
||||
@staticmethod
|
||||
def registerRoute(node, namePrefix, remoteNode, protocol=PROTOCOL_UDP, origin=255,
|
||||
cost=0, inheritFlag=True, captureFlag=False, expirationInMillis=None):
|
||||
cmd = ""
|
||||
if remoteNode.isdigit() and not protocol == "fd":
|
||||
cmd = ('nfdc route add {} {} origin {} cost {} {}{}{}').format(
|
||||
namePrefix,
|
||||
remoteNode,
|
||||
origin,
|
||||
cost,
|
||||
'no-inherit ' if not inheritFlag else '',
|
||||
'capture ' if captureFlag else '',
|
||||
'expires {}'.format(expirationInMillis) if expirationInMillis else ''
|
||||
)
|
||||
else:
|
||||
cmd = ('nfdc route add {} {}://{} origin {} cost {} {}{}{}').format(
|
||||
namePrefix,
|
||||
protocol,
|
||||
remoteNode,
|
||||
origin,
|
||||
cost,
|
||||
'no-inherit ' if not inheritFlag else '',
|
||||
'capture ' if captureFlag else '',
|
||||
'expires {}'.format(expirationInMillis) if expirationInMillis else ''
|
||||
)
|
||||
debug(node.cmd(cmd))
|
||||
Minindn.sleep(SLEEP_TIME)
|
||||
|
||||
@staticmethod
|
||||
def unregisterRoute(node, namePrefix, remoteNode, origin=255):
|
||||
cmd = ""
|
||||
if remoteNode.isdigit() and not protocol == "fd":
|
||||
cmd = 'nfdc route remove {} {} {}'.format(namePrefix, remoteNode, origin)
|
||||
else:
|
||||
cmd = 'nfdc route remove {} {} {}'.format(namePrefix, remoteNode, origin)
|
||||
debug(node.cmd(cmd))
|
||||
Minindn.sleep(SLEEP_TIME)
|
||||
|
||||
@staticmethod
|
||||
def createFace(node, remoteNodeAddress, protocol='udp', isPermanent=False, allowExisting=True, bandwidth=100000000):
|
||||
'''Create face in node's NFD instance. Returns FaceID of created face or -1 if failed.'''
|
||||
cmd = ('nfdc face create {}://{} bandwidth {} persistency {}'.format(
|
||||
protocol,
|
||||
remoteNodeAddress,
|
||||
int(bandwidth),
|
||||
'permanent' if isPermanent else 'persistent'
|
||||
))
|
||||
output = node.cmd(cmd)
|
||||
debug(output)
|
||||
Minindn.sleep(SLEEP_TIME)
|
||||
if "face-created" in output or (allowExisting and "face-exists" in output):
|
||||
faceID = output.split(" ")[1][3:]
|
||||
return faceID
|
||||
warn("["+ node.name + "] Face register failed: " + output)
|
||||
return -1
|
||||
|
||||
@staticmethod
|
||||
def destroyFace(node, remoteNode, protocol='udp'):
|
||||
if remoteNode.isdigit() and not protocol == "fd":
|
||||
debug(node.cmd('nfdc face destroy {}'.format(protocol, remoteNode)))
|
||||
else:
|
||||
debug(node.cmd('nfdc face destroy {}://{}'.format(protocol, remoteNode)))
|
||||
Minindn.sleep(SLEEP_TIME)
|
||||
|
||||
@staticmethod
|
||||
def setStrategy(node, namePrefix, strategy):
|
||||
cmd = 'nfdc strategy set {} ndn:/localhost/nfd/strategy/{}'.format(namePrefix, strategy)
|
||||
out = node.cmd(cmd)
|
||||
if out.find('error') != -1:
|
||||
warn("[" + node.name + "] Error on strategy set out: " + out)
|
||||
Minindn.sleep(SLEEP_TIME)
|
||||
|
||||
@staticmethod
|
||||
def unsetStrategy(node, namePrefix):
|
||||
debug(node.cmd("nfdc strategy unset {}".format(namePrefix)))
|
||||
Minindn.sleep(SLEEP_TIME)
|
||||
|
||||
@staticmethod
|
||||
def getFaceId(node, remoteNodeAddress, localEndpoint=None, protocol="udp", portNum="6363"):
|
||||
'''Returns the faceId for a remote node based on FaceURI, or -1 if a face is not found'''
|
||||
#Should this be cached or is the hit not worth it?
|
||||
local = ""
|
||||
if localEndpoint:
|
||||
local = " local {}".format(localEndpoint)
|
||||
output = node.cmd("nfdc face list remote {}://{}:{}{}".format(protocol, remoteNodeAddress, portNum, local))
|
||||
debug(output)
|
||||
Minindn.sleep(SLEEP_TIME)
|
||||
# This is fragile but we don't have that many better options
|
||||
if "faceid=" not in output:
|
||||
return -1
|
||||
faceId = output.split(" ")[0][7:]
|
||||
return faceId
|
||||
@@ -1,52 +0,0 @@
|
||||
# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
|
||||
#
|
||||
# Copyright (C) 2015-2020, The University of Memphis,
|
||||
# Arizona Board of Regents,
|
||||
# Regents of the University of California.
|
||||
#
|
||||
# This file is part of Mini-NDN.
|
||||
# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
|
||||
#
|
||||
# Mini-NDN 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mini-NDN 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 Mini-NDN, e.g., in COPYING.md file.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import time
|
||||
|
||||
from threading import Timer
|
||||
|
||||
class ProcessMonitor(object):
|
||||
def __init__(self, processId, processName, outputDir, interval=1):
|
||||
self._processId = processId.strip()
|
||||
self._processName = processName
|
||||
self._statFile = '/proc/{}/stat'.format(self._processId)
|
||||
self._logFile = '{}/{}-{}-stat.txt'.format(outputDir, self._processName, self._processId)
|
||||
self._interval = interval
|
||||
|
||||
def _recordStats(self):
|
||||
try:
|
||||
with open(self._statFile, 'r') as stat:
|
||||
currentTime = int(time.time())
|
||||
with open(self._logFile, 'a') as log:
|
||||
for line in stat:
|
||||
log.write('{} {}'.format(currentTime, line))
|
||||
except IOError as e:
|
||||
print('I/O error({0}): {1}'.format(e.errno, e.strerror))
|
||||
print('No process with PID={}'.format(self._processId))
|
||||
return
|
||||
|
||||
self.start() # Reschedule event
|
||||
|
||||
def start(self):
|
||||
self._timer = Timer(self._interval, self._recordStats)
|
||||
self._timer.start()
|
||||
@@ -1,376 +0,0 @@
|
||||
# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
|
||||
#
|
||||
# Copyright (C) 2015-2021, The University of Memphis,
|
||||
# Arizona Board of Regents,
|
||||
# Regents of the University of California.
|
||||
#
|
||||
# This file is part of Mini-NDN.
|
||||
# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
|
||||
#
|
||||
# Mini-NDN 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mini-NDN 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 Mini-NDN, e.g., in COPYING.md file.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
import time
|
||||
import os
|
||||
import configparser
|
||||
from subprocess import call, Popen, PIPE
|
||||
import shutil
|
||||
import glob
|
||||
from traceback import format_exc
|
||||
|
||||
from mininet.topo import Topo
|
||||
from mininet.net import Mininet
|
||||
from mininet.link import TCLink
|
||||
from mininet.node import Switch
|
||||
from mininet.util import ipStr, ipParse
|
||||
from mininet.log import info, debug, error
|
||||
|
||||
class Minindn(object):
|
||||
"""
|
||||
This class provides the following features to the user:
|
||||
1) Wrapper around Mininet object with option to pass topology directly
|
||||
1.1) Users can pass custom argument parser to extend the default on here
|
||||
2) Parses the topology file given via command line if user does not pass a topology object
|
||||
3) Provides way to stop Mini-NDN via stop
|
||||
3.1) Applications register their clean up function with this class
|
||||
4) Sets IPs on neighbors for connectivity required in a switch-less topology
|
||||
5) Some other utility functions
|
||||
"""
|
||||
ndnSecurityDisabled = False
|
||||
workDir = '/tmp/minindn'
|
||||
resultDir = None
|
||||
|
||||
def __init__(self, parser=argparse.ArgumentParser(), topo=None, topoFile=None, noTopo=False,
|
||||
link=TCLink, workDir=None, **mininetParams):
|
||||
"""
|
||||
Create MiniNDN object
|
||||
:param parser: Parent parser of Mini-NDN parser
|
||||
:param topo: Mininet topo object (optional)
|
||||
:param topoFile: Mininet topology file location (optional)
|
||||
:param noTopo: Allows specification of topology after network object is
|
||||
initialized (optional)
|
||||
:param link: Allows specification of default Mininet link type for connections between
|
||||
nodes (optional)
|
||||
:param mininetParams: Any params to pass to Mininet
|
||||
"""
|
||||
self.parser = Minindn.parseArgs(parser)
|
||||
self.args = self.parser.parse_args()
|
||||
|
||||
if not workDir:
|
||||
Minindn.workDir = os.path.abspath(self.args.workDir)
|
||||
else:
|
||||
Minindn.workDir = os.path.abspath(workDir)
|
||||
|
||||
Minindn.resultDir = self.args.resultDir
|
||||
|
||||
if not topoFile:
|
||||
# Args has default topology if none specified
|
||||
self.topoFile = self.args.topoFile
|
||||
else:
|
||||
self.topoFile = topoFile
|
||||
|
||||
self.faces_to_create = {}
|
||||
if topo is None and not noTopo:
|
||||
try:
|
||||
info('Using topology file {}\n'.format(self.topoFile))
|
||||
self.topo, self.faces_to_create = self.processTopo(self.topoFile)
|
||||
except configparser.NoSectionError as e:
|
||||
info('Error reading config file: {}\n'.format(e))
|
||||
sys.exit(1)
|
||||
else:
|
||||
self.topo = topo
|
||||
|
||||
if not noTopo:
|
||||
self.net = Mininet(topo=self.topo, link=link, **mininetParams)
|
||||
else:
|
||||
self.net = Mininet(link=link, **mininetParams)
|
||||
|
||||
self.initParams(self.net.hosts)
|
||||
|
||||
self.cleanups = []
|
||||
|
||||
if not self.net.switches:
|
||||
self.ethernetPairConnectivity()
|
||||
|
||||
try:
|
||||
process = Popen(['ndnsec-get-default', '-k'], stdout=PIPE, stderr=PIPE)
|
||||
output, error = process.communicate()
|
||||
if process.returncode == 0:
|
||||
Minindn.ndnSecurityDisabled = '/dummy/KEY/-%9C%28r%B8%AA%3B%60' in output.decode("utf-8")
|
||||
info('Dummy key chain patch is installed in ndn-cxx. Security will be disabled.\n')
|
||||
else:
|
||||
debug(error)
|
||||
except:
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def parseArgs(parent):
|
||||
parser = argparse.ArgumentParser(prog='minindn', parents=[parent], add_help=False)
|
||||
|
||||
# nargs='?' required here since optional argument
|
||||
parser.add_argument('topoFile', nargs='?', default='/usr/local/etc/mini-ndn/default-topology.conf',
|
||||
help='If no template_file is given, topologies/default-topology.conf \
|
||||
will be used.')
|
||||
|
||||
parser.add_argument('--work-dir', action='store', dest='workDir', default='/tmp/minindn',
|
||||
help='Specify the working directory; default is /tmp/minindn')
|
||||
|
||||
parser.add_argument('--result-dir', action='store', dest='resultDir', default=None,
|
||||
help='Specify the full path destination folder where experiment \
|
||||
results will be moved')
|
||||
|
||||
return parser
|
||||
|
||||
def ethernetPairConnectivity(self):
|
||||
ndnNetBase = '10.0.0.0'
|
||||
interfaces = []
|
||||
for host in self.net.hosts:
|
||||
for intf in host.intfList():
|
||||
link = intf.link
|
||||
node1, node2 = link.intf1.node, link.intf2.node
|
||||
|
||||
if isinstance(node1, Switch) or isinstance(node2, Switch):
|
||||
continue
|
||||
|
||||
if link.intf1 not in interfaces and link.intf2 not in interfaces:
|
||||
interfaces.append(link.intf1)
|
||||
interfaces.append(link.intf2)
|
||||
node1.setIP(ipStr(ipParse(ndnNetBase) + 1) + '/30', intf=link.intf1)
|
||||
node2.setIP(ipStr(ipParse(ndnNetBase) + 2) + '/30', intf=link.intf2)
|
||||
ndnNetBase = ipStr(ipParse(ndnNetBase) + 4)
|
||||
|
||||
@staticmethod
|
||||
def processTopo(topoFile):
|
||||
config = configparser.ConfigParser(delimiters=' ', allow_no_value=True)
|
||||
config.read(topoFile)
|
||||
topo = Topo()
|
||||
|
||||
items = config.items('nodes')
|
||||
coordinates = []
|
||||
|
||||
for item in items:
|
||||
name = item[0].split(':')[0]
|
||||
params = {}
|
||||
if item[1]:
|
||||
if all (x in item[1] for x in ['radius', 'angle']) and item[1] in coordinates:
|
||||
error("FATAL: Duplicate Coordinate, \'{}\' used by multiple nodes\n" \
|
||||
.format(item[1]))
|
||||
sys.exit(1)
|
||||
coordinates.append(item[1])
|
||||
|
||||
for param in item[1].split(' '):
|
||||
if param == '_':
|
||||
continue
|
||||
params[param.split('=')[0]] = param.split('=')[1]
|
||||
|
||||
topo.addHost(name, params=params)
|
||||
|
||||
try:
|
||||
items = config.items('switches')
|
||||
for item in items:
|
||||
name = item[0].split(':')[0]
|
||||
topo.addSwitch(name)
|
||||
except configparser.NoSectionError:
|
||||
# Switches are optional
|
||||
pass
|
||||
|
||||
items = config.items('links')
|
||||
for item in items:
|
||||
link = item[0].split(':')
|
||||
|
||||
params = {}
|
||||
for param in item[1].split(' '):
|
||||
key = param.split('=')[0]
|
||||
value = param.split('=')[1]
|
||||
if key in ['jitter', 'max_queue_size']:
|
||||
value = int(value)
|
||||
if key == 'loss' or key == 'bw':
|
||||
value = float(value)
|
||||
params[key] = value
|
||||
|
||||
topo.addLink(link[0], link[1], **params)
|
||||
|
||||
faces = {}
|
||||
try:
|
||||
items = config.items('faces')
|
||||
debug("Faces")
|
||||
for item in items:
|
||||
face_a, face_b = item[0].split(':')
|
||||
debug(item)
|
||||
cost = -1
|
||||
for param in item[1].split(' '):
|
||||
if param.split("=")[0] == 'cost':
|
||||
cost = param.split("=")[1]
|
||||
face_info = (face_b, int(cost))
|
||||
if face_a not in faces:
|
||||
faces[face_a] = [face_info]
|
||||
else:
|
||||
faces[face_a].append(face_info)
|
||||
except configparser.NoSectionError:
|
||||
debug("Faces section is optional")
|
||||
pass
|
||||
|
||||
return (topo, faces)
|
||||
|
||||
return topo
|
||||
|
||||
def start(self):
|
||||
self.net.start()
|
||||
time.sleep(3)
|
||||
|
||||
def stop(self):
|
||||
for cleanup in self.cleanups:
|
||||
cleanup()
|
||||
self.net.stop()
|
||||
|
||||
if Minindn.resultDir is not None:
|
||||
info("Moving results to \'{}\'\n".format(Minindn.resultDir))
|
||||
os.system("mkdir -p {}".format(Minindn.resultDir))
|
||||
for file in glob.glob('{}/*'.format(Minindn.workDir)):
|
||||
shutil.move(file, Minindn.resultDir)
|
||||
|
||||
@staticmethod
|
||||
def cleanUp():
|
||||
devnull = open(os.devnull, 'w')
|
||||
call('nfd-stop', stdout=devnull, stderr=devnull)
|
||||
call('mn --clean'.split(), stdout=devnull, stderr=devnull)
|
||||
|
||||
@staticmethod
|
||||
def verifyDependencies():
|
||||
"""Prevent MiniNDN from running without necessary dependencies"""
|
||||
dependencies = ['nfd', 'nlsr', 'infoedit', 'ndnping', 'ndnpingserver']
|
||||
devnull = open(os.devnull, 'w')
|
||||
# Checks that each program is in the system path
|
||||
for program in dependencies:
|
||||
if call(['which', program], stdout=devnull):
|
||||
error('{} is missing from the system path! Exiting...\n'.format(program))
|
||||
sys.exit(1)
|
||||
devnull.close()
|
||||
|
||||
@staticmethod
|
||||
def sleep(seconds):
|
||||
# sleep is not required if ndn-cxx is using in-memory keychain
|
||||
if not Minindn.ndnSecurityDisabled:
|
||||
time.sleep(seconds)
|
||||
|
||||
@staticmethod
|
||||
def handleException():
|
||||
"""Utility method to perform cleanup steps and exit after catching exception"""
|
||||
Minindn.cleanUp()
|
||||
info(format_exc())
|
||||
exit(1)
|
||||
|
||||
def getInterfaceDelay(self, node, interface):
|
||||
tc_output = node.cmd("tc qdisc show dev {}".format(interface))
|
||||
for line in tc_output.splitlines():
|
||||
if "qdisc netem 10:" in line:
|
||||
split_line = line.split(" ")
|
||||
for index in range(0, len(split_line)):
|
||||
if split_line[index] == "delay":
|
||||
return float(split_line[index + 1][:-2])
|
||||
return 0.0
|
||||
|
||||
def initParams(self, nodes):
|
||||
"""Initialize Mini-NDN parameters for array of nodes"""
|
||||
for host in nodes:
|
||||
if 'params' not in host.params:
|
||||
host.params['params'] = {}
|
||||
host.params['params']['workDir'] = Minindn.workDir
|
||||
homeDir = '{}/{}'.format(Minindn.workDir, host.name)
|
||||
host.params['params']['homeDir'] = homeDir
|
||||
host.cmd('mkdir -p {}'.format(homeDir))
|
||||
host.cmd('export HOME={} && cd ~'.format(homeDir))
|
||||
|
||||
def nfdcBatchProcessing(self, station, faces):
|
||||
print("???")
|
||||
# Input format: [IP, protocol, isPermanent]
|
||||
batch_file = open("{}/{}/nfdc.batch".format(Minindn.workDir, station.name), "w")
|
||||
lines = []
|
||||
for face in faces:
|
||||
ip = face[0]
|
||||
protocol = face[1]
|
||||
if face[2]:
|
||||
face_type = "permanent"
|
||||
else:
|
||||
face_type = "persistent"
|
||||
nfdc_command = "face create {}://{} {}\n".format(protocol, ip, face_type)
|
||||
lines.append(nfdc_command)
|
||||
batch_file.writelines(lines)
|
||||
batch_file.close()
|
||||
debug(station.cmd("nfdc -f {}/{}/nfdc.batch".format(Minindn.workDir, station.name)))
|
||||
|
||||
def setupFaces(self, faces_to_create=None):
|
||||
""" Method to create unicast faces between connected nodes;
|
||||
Returns dict- {node: (other node name, other node IP, other node's delay as int)}.
|
||||
This is intended to pass to the NLSR helper via the faceDict param """
|
||||
if not faces_to_create:
|
||||
faces_to_create = self.faces_to_create
|
||||
# (nodeName, IP, delay as int)
|
||||
# list of tuples
|
||||
created_faces = dict()
|
||||
batch_faces = dict()
|
||||
for nodeAname in faces_to_create.keys():
|
||||
if not nodeAname in batch_faces.keys():
|
||||
batch_faces[nodeAname] = []
|
||||
for nodeBname, faceCost in faces_to_create[nodeAname]:
|
||||
if not nodeBname in batch_faces.keys():
|
||||
batch_faces[nodeBname] = []
|
||||
nodeA = self.net[nodeAname]
|
||||
nodeB = self.net[nodeBname]
|
||||
if nodeA.connectionsTo(nodeB):
|
||||
best_interface = None
|
||||
delay = None
|
||||
for interface in nodeA.connectionsTo(nodeB):
|
||||
interface_delay = self.getInterfaceDelay(nodeA, interface[0])
|
||||
if not delay or int(interface_delay) < delay:
|
||||
best_interface = interface
|
||||
faceAIP = best_interface[0].IP()
|
||||
faceBIP = best_interface[1].IP()
|
||||
# Node delay will be symmetrical for connected nodes
|
||||
nodeDelay = int(self.getInterfaceDelay(nodeA, best_interface[0]))
|
||||
#nodeBDelay = int(self.getInterfaceDelay(nodeB, best_interface[1]))
|
||||
else:
|
||||
# If no direct wired connections exist (ie when using a switch),
|
||||
# assume the default interface
|
||||
faceAIP = nodeA.IP()
|
||||
faceBIP = nodeB.IP()
|
||||
nodeADelay = int(self.getInterfaceDelay(nodeA, nodeA.defaultIntf()))
|
||||
nodeBDelay = int(self.getInterfaceDelay(nodeB, nodeB.defaultIntf()))
|
||||
nodeDelay = nodeADelay + nodeBDelay
|
||||
|
||||
if not faceCost == -1:
|
||||
nodeALink = (nodeA.name, faceAIP, faceCost)
|
||||
nodeBLink = (nodeB.name, faceBIP, faceCost)
|
||||
else:
|
||||
nodeALink = (nodeA.name, faceAIP, nodeDelay)
|
||||
nodeBLink = (nodeB.name, faceBIP, nodeDelay)
|
||||
|
||||
# Importing this before initialization causes issues
|
||||
batch_faces[nodeAname].append([faceBIP, "udp", True])
|
||||
batch_faces[nodeBname].append([faceAIP, "udp", True])
|
||||
|
||||
if nodeA not in created_faces:
|
||||
created_faces[nodeA] = [nodeBLink]
|
||||
else:
|
||||
created_faces[nodeA].append(nodeBLink)
|
||||
if nodeB not in created_faces:
|
||||
created_faces[nodeB] = [nodeALink]
|
||||
else:
|
||||
created_faces[nodeB].append(nodeALink)
|
||||
for station_name in batch_faces.keys():
|
||||
print("========= {} =========".format(station_name))
|
||||
self.nfdcBatchProcessing(self.net[station_name], batch_faces[station_name])
|
||||
return created_faces
|
||||
-103
@@ -1,103 +0,0 @@
|
||||
# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
|
||||
#
|
||||
# Copyright (C) 2015-2020, The University of Memphis,
|
||||
# Arizona Board of Regents,
|
||||
# Regents of the University of California.
|
||||
#
|
||||
# This file is part of Mini-NDN.
|
||||
# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
|
||||
#
|
||||
# Mini-NDN 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mini-NDN 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 Mini-NDN, e.g., in COPYING.md file.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import sys
|
||||
from os.path import isfile
|
||||
from subprocess import call
|
||||
from six.moves.urllib.parse import quote
|
||||
|
||||
from mininet.cli import CLI
|
||||
|
||||
sshbase = ['ssh', '-q', '-t', '-i/home/mininet/.ssh/id_rsa']
|
||||
scpbase = ['scp', '-i', '/home/mininet/.ssh/id_rsa']
|
||||
devnull = open('/dev/null', 'w')
|
||||
|
||||
def getSafeName(namePrefix):
|
||||
"""
|
||||
Check if the prefix/string is safe to use with ndn commands or not.
|
||||
return safe prefix.
|
||||
:param namePrefix: name of the prefix
|
||||
"""
|
||||
# remove redundant "/"es, multiple "/"es are an invalid representation for empty name component
|
||||
# https://named-data.net/doc/NDN-packet-spec/current/changelog.html#version-0-3
|
||||
namePrefix= "/" + ("/".join(filter(None, namePrefix.split("/"))))
|
||||
return quote(namePrefix, safe='/')
|
||||
|
||||
def ssh(login, cmd):
|
||||
rcmd = sshbase + [login, cmd]
|
||||
call(rcmd, stdout=devnull, stderr=devnull)
|
||||
|
||||
def scp(*args):
|
||||
tmp = []
|
||||
for arg in args:
|
||||
tmp.append(arg)
|
||||
rcmd = scpbase + tmp
|
||||
call(rcmd, stdout=devnull, stderr=devnull)
|
||||
|
||||
def copyExistentFile(node, fileList, destination):
|
||||
for f in fileList:
|
||||
if isfile(f):
|
||||
node.cmd('cp {} {}'.format(f, destination))
|
||||
break
|
||||
if not isfile(destination):
|
||||
fileName = destination.split('/')[-1]
|
||||
raise IOError('{} not found in expected directory.'.format(fileName))
|
||||
|
||||
def popenGetEnv(node, envDict=None):
|
||||
env = {}
|
||||
homeDir = node.params['params']['homeDir']
|
||||
printenv = node.popen('printenv'.split(), cwd=homeDir).communicate()[0].decode('utf-8')
|
||||
for var in printenv.split('\n'):
|
||||
if var == '':
|
||||
break
|
||||
p = var.split('=')
|
||||
env[p[0]] = p[1]
|
||||
env['HOME'] = homeDir
|
||||
|
||||
if envDict is not None:
|
||||
for key, value in envDict.items():
|
||||
env[key] = str(value)
|
||||
|
||||
return env
|
||||
|
||||
def getPopen(host, cmd, envDict=None, **params):
|
||||
return host.popen(cmd, cwd=host.params['params']['homeDir'],
|
||||
env=popenGetEnv(host, envDict), **params)
|
||||
|
||||
class MiniNDNCLI(CLI):
|
||||
prompt = 'mini-ndn> '
|
||||
def __init__(self, mininet, stdin=sys.stdin, script=None):
|
||||
CLI.__init__(self, mininet, stdin, script)
|
||||
|
||||
try:
|
||||
from mn_wifi.cli import CLI as CLI_wifi
|
||||
|
||||
class MiniNDNWifiCLI(CLI_wifi):
|
||||
prompt = 'mini-ndn-wifi> '
|
||||
def __init__(self, mininet, stdin=sys.stdin, script=None):
|
||||
CLI_wifi.__init__(self, mininet, stdin, script)
|
||||
|
||||
except ImportError:
|
||||
class MiniNDNWifiCLI:
|
||||
def __init__(self):
|
||||
raise ImportError('Mininet-WiFi is not installed')
|
||||
Whitespace-only changes.
@@ -1,307 +0,0 @@
|
||||
# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
|
||||
#
|
||||
# Copyright (C) 2015-2021, The University of Memphis,
|
||||
# Arizona Board of Regents,
|
||||
# Regents of the University of California.
|
||||
#
|
||||
# This file is part of Mini-NDN.
|
||||
# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
|
||||
#
|
||||
# Mini-NDN 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mini-NDN 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 Mini-NDN, e.g., in COPYING.md file.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import argparse
|
||||
import sys
|
||||
import configparser
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
from mininet.log import info, debug
|
||||
|
||||
from mn_wifi.topo import Topo as Topo_WiFi
|
||||
from mn_wifi.net import Mininet_wifi
|
||||
from mn_wifi.link import WirelessLink
|
||||
|
||||
from minindn.minindn import Minindn
|
||||
from minindn.helpers.nfdc import Nfdc
|
||||
|
||||
class MinindnWifi(Minindn):
|
||||
""" Class for handling default args, Mininet-wifi object and home directories """
|
||||
def __init__(self, parser=argparse.ArgumentParser(), topo=None, topoFile=None, noTopo=False,
|
||||
link=WirelessLink, workDir=None, **mininetParams):
|
||||
"""
|
||||
Create Mini-NDN-Wifi object
|
||||
parser: Parent parser of Mini-NDN-Wifi parser (use to specify experiment arguments)
|
||||
topo: Mininet topo object (optional)
|
||||
topoFile: topology file location (optional)
|
||||
noTopo: Allows specification of topology after network object is initialized (optional)
|
||||
link: Allows specification of default Mininet/Mininet-Wifi link type for
|
||||
connections between nodes (optional)mininetParams: Any params to pass to Mininet-WiFi
|
||||
"""
|
||||
self.parser = self.parseArgs(parser)
|
||||
self.args = self.parser.parse_args()
|
||||
|
||||
if not workDir:
|
||||
Minindn.workDir = os.path.abspath(self.args.workDir)
|
||||
else:
|
||||
Minindn.workDir = os.path.abspath(workDir)
|
||||
|
||||
Minindn.resultDir = self.args.resultDir
|
||||
|
||||
self.topoFile = None
|
||||
if not topoFile:
|
||||
# Args has default topology if none specified
|
||||
self.topoFile = self.args.topoFile
|
||||
else:
|
||||
self.topoFile = topoFile
|
||||
|
||||
self.faces_to_create = {}
|
||||
if topo is None and not noTopo:
|
||||
try:
|
||||
info('Using topology file {}\n'.format(self.topoFile))
|
||||
self.topo, self.faces_to_create = self.processTopo(self.topoFile)
|
||||
except configparser.NoSectionError as e:
|
||||
info('Error reading config file: {}\n'.format(e))
|
||||
sys.exit(1)
|
||||
else:
|
||||
self.topo = topo
|
||||
|
||||
if not noTopo:
|
||||
self.net = Mininet_wifi(topo=self.topo, ifb=self.args.ifb, link=link, **mininetParams)
|
||||
else:
|
||||
self.net = Mininet_wifi(ifb=self.args.ifb, link=link, **mininetParams)
|
||||
|
||||
# Prevents crashes running mixed topos
|
||||
nodes = self.net.stations + self.net.hosts + self.net.cars
|
||||
self.initParams(nodes)
|
||||
|
||||
try:
|
||||
process = Popen(['ndnsec-get-default', '-k'], stdout=PIPE, stderr=PIPE)
|
||||
output, error = process.communicate()
|
||||
if process.returncode == 0:
|
||||
Minindn.ndnSecurityDisabled = '/dummy/KEY/-%9C%28r%B8%AA%3B%60' in output.decode("utf-8")
|
||||
info('Dummy key chain patch is installed in ndn-cxx. Security will be disabled.\n')
|
||||
else:
|
||||
debug(error)
|
||||
except:
|
||||
pass
|
||||
|
||||
self.cleanups = []
|
||||
|
||||
@staticmethod
|
||||
def parseArgs(parent):
|
||||
parser = argparse.ArgumentParser(prog='minindn-wifi', parents=[parent], add_help=False)
|
||||
|
||||
# nargs='?' required here since optional argument
|
||||
parser.add_argument('topoFile', nargs='?', default='/usr/local/etc/mini-ndn/singleap-topology.conf',
|
||||
help='If no template_file is given, topologies/wifi/singleap-topology.conf will be used.')
|
||||
|
||||
parser.add_argument('--work-dir', action='store', dest='workDir', default='/tmp/minindn',
|
||||
help='Specify the working directory; default is /tmp/minindn')
|
||||
|
||||
parser.add_argument('--result-dir', action='store', dest='resultDir', default=None,
|
||||
help='Specify the full path destination folder where experiment results will be moved')
|
||||
|
||||
parser.add_argument('--mobility',action='store_true',dest='mobility',default=False,
|
||||
help='Enable custom mobility for topology (defined in topology file)')
|
||||
|
||||
parser.add_argument('--model-mob',action='store_true',dest='modelMob',default=False,
|
||||
help='Enable model mobility for topology (defined in topology file)')
|
||||
|
||||
parser.add_argument('--ifb',action='store_true',dest='ifb',default=False,
|
||||
help='Simulate delay on receiver-side by use of virtual IFB devices (see docs)')
|
||||
|
||||
return parser
|
||||
|
||||
@staticmethod
|
||||
def processTopo(topoFile):
|
||||
config = configparser.ConfigParser(delimiters=' ')
|
||||
config.read(topoFile)
|
||||
topo = Topo_WiFi()
|
||||
|
||||
items = config.items('stations')
|
||||
debug("Stations")
|
||||
for item in items:
|
||||
debug(item[0].split(':'))
|
||||
name = item[0].split(':')[0]
|
||||
params = {}
|
||||
for param in item[1].split(' '):
|
||||
if param == "_":
|
||||
continue
|
||||
key = param.split('=')[0]
|
||||
value = param.split('=')[1]
|
||||
if key in ['range']:
|
||||
value = int(value)
|
||||
params[key] = value
|
||||
|
||||
topo.addStation(name, **params)
|
||||
|
||||
try:
|
||||
debug("Switches")
|
||||
items = config.items('switches')
|
||||
for item in items:
|
||||
debug(item[0].split(':'))
|
||||
name = item[0].split(':')[0]
|
||||
topo.addSwitch(name)
|
||||
except configparser.NoSectionError:
|
||||
debug("Switches are optional")
|
||||
pass
|
||||
|
||||
try:
|
||||
debug("APs")
|
||||
items = config.items('accessPoints')
|
||||
for item in items:
|
||||
debug(item[0].split(':'))
|
||||
name = item[0].split(':')[0]
|
||||
ap_params = {}
|
||||
for param in item[1].split(' '):
|
||||
if param == "_":
|
||||
continue
|
||||
key = param.split('=')[0]
|
||||
value = param.split('=')[1]
|
||||
if key in ['range']:
|
||||
value = int(value)
|
||||
ap_params[key] = value
|
||||
topo.addAccessPoint(name, **ap_params)
|
||||
except configparser.NoSectionError:
|
||||
debug("APs are optional")
|
||||
pass
|
||||
|
||||
items = config.items('links')
|
||||
debug("Links")
|
||||
for item in items:
|
||||
link = item[0].split(':')
|
||||
debug(link)
|
||||
params = {}
|
||||
for param in item[1].split(' '):
|
||||
if param == "_":
|
||||
continue
|
||||
key = param.split('=')[0]
|
||||
value = param.split('=')[1]
|
||||
if key in ['bw', 'jitter', 'max_queue_size']:
|
||||
value = int(value)
|
||||
if key == 'loss':
|
||||
value = float(value)
|
||||
params[key] = value
|
||||
|
||||
topo.addLink(link[0], link[1], **params)
|
||||
|
||||
faces = {}
|
||||
try:
|
||||
items = config.items('faces')
|
||||
debug("Faces")
|
||||
for item in items:
|
||||
face_a, face_b = item[0].split(':')
|
||||
debug(item)
|
||||
cost = -1
|
||||
for param in item[1].split(' '):
|
||||
if param.split("=")[0] == 'cost':
|
||||
cost = param.split("=")[1]
|
||||
face_info = (face_b, int(cost))
|
||||
if face_a not in faces:
|
||||
faces[face_a] = [face_info]
|
||||
else:
|
||||
faces[face_a].append(face_info)
|
||||
except configparser.NoSectionError:
|
||||
debug("Faces section is optional")
|
||||
pass
|
||||
|
||||
return (topo, faces)
|
||||
|
||||
def startMobility(self, max_x=1000, max_y=1000, **kwargs):
|
||||
""" Method to run a basic mobility setup on your net"""
|
||||
self.net.plotGraph(max_x=max_x, max_y=max_y)
|
||||
self.net.startMobility(**kwargs)
|
||||
|
||||
def startMobilityModel(self, max_x=1000, max_y=1000, **kwargs):
|
||||
""" Method to run a mobility model on your net until exited"""
|
||||
self.net.plotGraph(max_x=max_x, max_y=max_y)
|
||||
self.net.setMobilityModel(**kwargs)
|
||||
|
||||
def getWifiInterfaceDelay(self, node, interface=None):
|
||||
"""Method to return the configured tc delay of a wifi node's interface as a float"""
|
||||
if not interface:
|
||||
wifi_interface = "{}-wlan0".format(node.name)
|
||||
else:
|
||||
wifi_interface = interface
|
||||
tc_output = node.cmd("tc qdisc show dev {}".format(wifi_interface))
|
||||
for line in tc_output.splitlines():
|
||||
if "qdisc netem 10:" in line:
|
||||
split_line = line.split(" ")
|
||||
for index in range(0, len(split_line)):
|
||||
if split_line[index] == "delay":
|
||||
return float(split_line[index + 1][:-2])
|
||||
return 0.0
|
||||
|
||||
def setupFaces(self, faces_to_create=None):
|
||||
"""
|
||||
Method to create unicast faces between nodes connected by an AP based on name or faces
|
||||
between connected nodes; Returns dict- {node: (other node name, other node IP, other
|
||||
node's delay as int)}. This is intended to pass to the NLSR helper via the faceDict param
|
||||
"""
|
||||
if not faces_to_create:
|
||||
faces_to_create = self.faces_to_create
|
||||
# (nodeName, IP, delay as int)
|
||||
# list of tuples
|
||||
created_faces = dict()
|
||||
batch_faces = dict()
|
||||
for nodeAname in faces_to_create.keys():
|
||||
if not nodeAname in batch_faces.keys():
|
||||
batch_faces[nodeAname] = []
|
||||
for nodeBname, faceCost in faces_to_create[nodeAname]:
|
||||
if not nodeBname in batch_faces.keys():
|
||||
batch_faces[nodeBname] = []
|
||||
nodeA = self.net[nodeAname]
|
||||
nodeB = self.net[nodeBname]
|
||||
if nodeA.connectionsTo(nodeB):
|
||||
best_interface = None
|
||||
delay = None
|
||||
for interface in nodeA.connectionsTo(nodeB):
|
||||
interface_delay = self.getInterfaceDelay(nodeA, interface[0])
|
||||
if not delay or int(interface_delay) < delay:
|
||||
best_interface = interface
|
||||
faceAIP = best_interface[0].IP()
|
||||
faceBIP = best_interface[1].IP()
|
||||
# Node delay should be symmetrical
|
||||
nodeDelay = int(self.getInterfaceDelay(nodeA, best_interface[0]))
|
||||
else:
|
||||
# Default IP will be the primary wireless interface, unclear if multiple wireless
|
||||
# interfaces should be handled
|
||||
faceAIP = nodeA.IP()
|
||||
faceBIP = nodeB.IP()
|
||||
nodeADelay = self.getWifiInterfaceDelay(nodeA)
|
||||
nodeBDelay = self.getWifiInterfaceDelay(nodeB)
|
||||
nodeDelay = nodeADelay + nodeBDelay
|
||||
|
||||
if not faceCost == -1:
|
||||
nodeALink = (nodeA.name, faceAIP, faceCost)
|
||||
nodeBLink = (nodeB.name, faceBIP, faceCost)
|
||||
else:
|
||||
nodeALink = (nodeA.name, faceAIP, nodeDelay)
|
||||
nodeBLink = (nodeB.name, faceBIP, nodeDelay)
|
||||
|
||||
batch_faces[nodeAname].append([faceBIP, "udp", True])
|
||||
batch_faces[nodeBname].append([faceAIP, "udp", True])
|
||||
|
||||
if nodeA not in created_faces:
|
||||
created_faces[nodeA] = [nodeBLink]
|
||||
else:
|
||||
created_faces[nodeA].append(nodeBLink)
|
||||
if nodeB not in created_faces:
|
||||
created_faces[nodeB] = [nodeALink]
|
||||
else:
|
||||
created_faces[nodeB].append(nodeALink)
|
||||
for station_name in batch_faces.keys():
|
||||
self.nfdcBatchProcessing(self.net[station_name], batch_faces[station_name])
|
||||
return created_faces
|
||||
@@ -0,0 +1 @@
|
||||
"Docstring to silence pylint; ignores --ignore option for __init__.py"
|
||||
Executable
+54
@@ -0,0 +1,54 @@
|
||||
"""
|
||||
Mininet Cleanup
|
||||
author: Bob Lantz (rlantz@cs.stanford.edu)
|
||||
|
||||
Unfortunately, Mininet and OpenFlow (and the Linux kernel)
|
||||
don't always clean up properly after themselves. Until they do
|
||||
(or until cleanup functionality is integrated into the Python
|
||||
code), this script may be used to get rid of unwanted garbage.
|
||||
It may also get rid of 'false positives', but hopefully
|
||||
nothing irreplaceable!
|
||||
"""
|
||||
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
from mininet.log import info
|
||||
from mininet.term import cleanUpScreens
|
||||
|
||||
def sh( cmd ):
|
||||
"Print a command and send it to the shell"
|
||||
info( cmd + '\n' )
|
||||
return Popen( [ '/bin/sh', '-c', cmd ], stdout=PIPE ).communicate()[ 0 ]
|
||||
|
||||
def cleanup():
|
||||
"""Clean up junk which might be left over from old runs;
|
||||
do fast stuff before slow dp and link removal!"""
|
||||
|
||||
info("*** Removing excess controllers/ofprotocols/ofdatapaths/pings/noxes"
|
||||
"\n")
|
||||
zombies = 'controller ofprotocol ofdatapath ping nox_core lt-nox_core '
|
||||
zombies += 'ovs-openflowd udpbwtest'
|
||||
# Note: real zombie processes can't actually be killed, since they
|
||||
# are already (un)dead. Then again,
|
||||
# you can't connect to them either, so they're mostly harmless.
|
||||
sh( 'killall -9 ' + zombies + ' 2> /dev/null' )
|
||||
|
||||
info( "*** Removing junk from /tmp\n" )
|
||||
sh( 'rm -f /tmp/vconn* /tmp/vlogs* /tmp/*.out /tmp/*.log' )
|
||||
|
||||
info( "*** Removing old screen sessions\n" )
|
||||
cleanUpScreens()
|
||||
|
||||
info( "*** Removing excess kernel datapaths\n" )
|
||||
dps = sh( "ps ax | egrep -o 'dp[0-9]+' | sed 's/dp/nl:/'" ).split( '\n' )
|
||||
for dp in dps:
|
||||
if dp != '':
|
||||
sh( 'dpctl deldp ' + dp )
|
||||
|
||||
info( "*** Removing all links of the pattern foo-ethX\n" )
|
||||
links = sh( "ip link show | egrep -o '(\w+-eth\w+)'" ).split( '\n' )
|
||||
for link in links:
|
||||
if link != '':
|
||||
sh( "ip link del " + link )
|
||||
|
||||
info( "*** Cleanup complete.\n" )
|
||||
+356
@@ -0,0 +1,356 @@
|
||||
"""
|
||||
A simple command-line interface for Mininet.
|
||||
|
||||
The Mininet CLI provides a simple control console which
|
||||
makes it easy to talk to nodes. For example, the command
|
||||
|
||||
mininet> h27 ifconfig
|
||||
|
||||
runs 'ifconfig' on host h27.
|
||||
|
||||
Having a single console rather than, for example, an xterm for each
|
||||
node is particularly convenient for networks of any reasonable
|
||||
size.
|
||||
|
||||
The CLI automatically substitutes IP addresses for node names,
|
||||
so commands like
|
||||
|
||||
mininet> h2 ping h3
|
||||
|
||||
should work correctly and allow host h2 to ping host h3
|
||||
|
||||
Several useful commands are provided, including the ability to
|
||||
list all nodes ('nodes'), to print out the network topology
|
||||
('net') and to check connectivity ('pingall', 'pingpair')
|
||||
and bandwidth ('iperf'.)
|
||||
"""
|
||||
|
||||
from subprocess import call
|
||||
from cmd import Cmd
|
||||
from os import isatty
|
||||
from select import poll, POLLIN
|
||||
import sys
|
||||
|
||||
from mininet.log import info, output, error
|
||||
from mininet.term import makeTerms
|
||||
from mininet.util import quietRun, isShellBuiltin
|
||||
|
||||
class CLI( Cmd ):
|
||||
"Simple command-line interface to talk to nodes."
|
||||
|
||||
prompt = 'mininet> '
|
||||
|
||||
def __init__( self, mininet, stdin=sys.stdin, script=None ):
|
||||
self.mn = mininet
|
||||
self.nodelist = self.mn.controllers + self.mn.switches + self.mn.hosts
|
||||
self.nodemap = {} # map names to Node objects
|
||||
for node in self.nodelist:
|
||||
self.nodemap[ node.name ] = node
|
||||
# Attempt to handle input
|
||||
self.stdin = stdin
|
||||
self.inPoller = poll()
|
||||
self.inPoller.register( stdin )
|
||||
self.inputFile = script
|
||||
Cmd.__init__( self )
|
||||
info( '*** Starting CLI:\n' )
|
||||
if self.inputFile:
|
||||
self.do_source( self.inputFile )
|
||||
return
|
||||
while True:
|
||||
try:
|
||||
# Make sure no nodes are still waiting
|
||||
for node in self.nodelist:
|
||||
while node.waiting:
|
||||
node.sendInt()
|
||||
node.monitor()
|
||||
if self.isatty():
|
||||
quietRun( 'stty sane' )
|
||||
self.cmdloop()
|
||||
break
|
||||
except KeyboardInterrupt:
|
||||
output( '\nInterrupt\n' )
|
||||
|
||||
def emptyline( self ):
|
||||
"Don't repeat last command when you hit return."
|
||||
pass
|
||||
|
||||
# Disable pylint "Unused argument: 'arg's'" messages, as well as
|
||||
# "method could be a function" warning, since each CLI function
|
||||
# must have the same interface
|
||||
# pylint: disable-msg=W0613,R0201
|
||||
|
||||
helpStr = (
|
||||
'You may also send a command to a node using:\n'
|
||||
' <node> command {args}\n'
|
||||
'For example:\n'
|
||||
' mininet> h1 ifconfig\n'
|
||||
'\n'
|
||||
'The interpreter automatically substitutes IP addresses\n'
|
||||
'for node names when a node is the first arg, so commands\n'
|
||||
'like\n'
|
||||
' mininet> h2 ping h3\n'
|
||||
'should work.\n'
|
||||
'\n'
|
||||
'Some character-oriented interactive commands require\n'
|
||||
'noecho:\n'
|
||||
' mininet> noecho h2 vi foo.py\n'
|
||||
'However, starting up an xterm/gterm is generally better:\n'
|
||||
' mininet> xterm h2\n\n'
|
||||
)
|
||||
|
||||
def do_help( self, line ):
|
||||
"Describe available CLI commands."
|
||||
Cmd.do_help( self, line )
|
||||
if line is '':
|
||||
output( self.helpStr )
|
||||
|
||||
def do_nodes( self, line ):
|
||||
"List all nodes."
|
||||
nodes = ' '.join( [ node.name for node in sorted( self.nodelist ) ] )
|
||||
output( 'available nodes are: \n%s\n' % nodes )
|
||||
|
||||
def do_net( self, line ):
|
||||
"List network connections."
|
||||
for switch in self.mn.switches:
|
||||
output( switch.name, '<->' )
|
||||
for intf in switch.intfs.values():
|
||||
# Ugly, but pylint wants it
|
||||
name = switch.connection.get( intf,
|
||||
( None, 'Unknown ' ) )[ 1 ]
|
||||
output( ' %s' % name )
|
||||
output( '\n' )
|
||||
|
||||
def do_sh( self, line ):
|
||||
"Run an external shell command"
|
||||
call( line, shell=True )
|
||||
|
||||
# do_py() needs to catch any exception during eval()
|
||||
# pylint: disable-msg=W0703
|
||||
|
||||
def do_py( self, line ):
|
||||
"""Evaluate a Python expression.
|
||||
Node names may be used, e.g.: h1.cmd('ls')"""
|
||||
try:
|
||||
result = eval( line, globals(), self.nodemap )
|
||||
if not result:
|
||||
return
|
||||
elif isinstance( result, str ):
|
||||
output( result + '\n' )
|
||||
else:
|
||||
output( repr( result ) + '\n' )
|
||||
except Exception, e:
|
||||
output( str( e ) + '\n' )
|
||||
|
||||
# pylint: enable-msg=W0703
|
||||
|
||||
def do_pingall( self, line ):
|
||||
"Ping between all hosts."
|
||||
self.mn.pingAll()
|
||||
|
||||
def do_pingpair( self, line ):
|
||||
"Ping between first two hosts, useful for testing."
|
||||
self.mn.pingPair()
|
||||
|
||||
def do_iperf( self, line ):
|
||||
"Simple iperf TCP test between two (optionally specified) hosts."
|
||||
args = line.split()
|
||||
if not args:
|
||||
self.mn.iperf()
|
||||
elif len(args) == 2:
|
||||
hosts = []
|
||||
err = False
|
||||
for arg in args:
|
||||
if arg not in self.nodemap:
|
||||
err = True
|
||||
error( "node '%s' not in network\n" % arg )
|
||||
else:
|
||||
hosts.append( self.nodemap[ arg ] )
|
||||
if not err:
|
||||
self.mn.iperf( hosts )
|
||||
else:
|
||||
error( 'invalid number of args: iperf src dst\n' )
|
||||
|
||||
def do_iperfudp( self, line ):
|
||||
"Simple iperf TCP test between two (optionally specified) hosts."
|
||||
args = line.split()
|
||||
if not args:
|
||||
self.mn.iperf( l4Type='UDP' )
|
||||
elif len(args) == 3:
|
||||
udpBw = args[ 0 ]
|
||||
hosts = []
|
||||
err = False
|
||||
for arg in args[ 1:3 ]:
|
||||
if arg not in self.nodemap:
|
||||
err = True
|
||||
error( "node '%s' not in network\n" % arg )
|
||||
else:
|
||||
hosts.append( self.nodemap[ arg ] )
|
||||
if not err:
|
||||
self.mn.iperf( hosts, l4Type='UDP', udpBw=udpBw )
|
||||
else:
|
||||
error( 'invalid number of args: iperfudp bw src dst\n' +
|
||||
'bw examples: 10M\n' )
|
||||
|
||||
def do_intfs( self, line ):
|
||||
"List interfaces."
|
||||
for node in self.nodelist:
|
||||
output( '%s: %s\n' %
|
||||
( node.name, ' '.join( sorted( node.intfs.values() ) ) ) )
|
||||
|
||||
def do_dump( self, line ):
|
||||
"Dump node info."
|
||||
for node in self.nodelist:
|
||||
output( '%s\n' % node )
|
||||
|
||||
def do_link( self, line ):
|
||||
"Bring link(s) between two nodes up or down."
|
||||
args = line.split()
|
||||
if len(args) != 3:
|
||||
error( 'invalid number of args: link end1 end2 [up down]\n' )
|
||||
elif args[ 2 ] not in [ 'up', 'down' ]:
|
||||
error( 'invalid type: link end1 end2 [up down]\n' )
|
||||
else:
|
||||
self.mn.configLinkStatus( *args )
|
||||
|
||||
def do_xterm( self, line, term='xterm' ):
|
||||
"Spawn xterm(s) for the given node(s)."
|
||||
args = line.split()
|
||||
if not args:
|
||||
error( 'usage: %s node1 node2 ...\n' % term )
|
||||
else:
|
||||
for arg in args:
|
||||
if arg not in self.nodemap:
|
||||
error( "node '%s' not in network\n" % arg )
|
||||
else:
|
||||
node = self.nodemap[ arg ]
|
||||
self.mn.terms += makeTerms( [ node ], term = term )
|
||||
|
||||
def do_gterm( self, line ):
|
||||
"Spawn gnome-terminal(s) for the given node(s)."
|
||||
self.do_xterm( line, term='gterm' )
|
||||
|
||||
def do_exit( self, line ):
|
||||
"Exit"
|
||||
return 'exited by user command'
|
||||
|
||||
def do_quit( self, line ):
|
||||
"Exit"
|
||||
return self.do_exit( line )
|
||||
|
||||
def do_EOF( self, line ):
|
||||
"Exit"
|
||||
output( '\n' )
|
||||
return self.do_exit( line )
|
||||
|
||||
def isatty( self ):
|
||||
"Is our standard input a tty?"
|
||||
return isatty( self.stdin.fileno() )
|
||||
|
||||
def do_noecho( self, line ):
|
||||
"Run an interactive command with echoing turned off."
|
||||
if self.isatty():
|
||||
quietRun( 'stty -echo' )
|
||||
self.default( line )
|
||||
if self.isatty():
|
||||
quietRun( 'stty echo' )
|
||||
|
||||
def do_source( self, line ):
|
||||
"Read commands from an input file."
|
||||
args = line.split()
|
||||
if len(args) != 1:
|
||||
error( 'usage: source <file>\n' )
|
||||
return
|
||||
try:
|
||||
self.inputFile = open( args[ 0 ] )
|
||||
while True:
|
||||
line = self.inputFile.readline()
|
||||
if len( line ) > 0:
|
||||
self.onecmd( line )
|
||||
else:
|
||||
break
|
||||
except IOError:
|
||||
error( 'error reading file %s\n' % args[ 0 ] )
|
||||
self.inputFile = None
|
||||
|
||||
def do_dpctl( self, line ):
|
||||
"Run dpctl command on all switches."
|
||||
args = line.split()
|
||||
if len(args) == 0:
|
||||
error( 'usage: dpctl command [arg1] [arg2] ...\n' )
|
||||
return
|
||||
if not self.mn.listenPort:
|
||||
error( "can't run dpctl w/no passive listening port\n")
|
||||
return
|
||||
for sw in self.mn.switches:
|
||||
output( '*** ' + sw.name + ' ' + ('-' * 72) + '\n' )
|
||||
output( sw.cmd( 'dpctl ' + ' '.join(args) +
|
||||
' tcp:127.0.0.1:%i' % sw.listenPort ) )
|
||||
|
||||
def default( self, line ):
|
||||
"""Called on an input line when the command prefix is not recognized.
|
||||
Overridden to run shell commands when a node is the first CLI argument.
|
||||
Past the first CLI argument, node names are automatically replaced with
|
||||
corresponding IP addrs."""
|
||||
|
||||
first, args, line = self.parseline( line )
|
||||
if args and len(args) > 0 and args[ -1 ] == '\n':
|
||||
args = args[ :-1 ]
|
||||
rest = args.split( ' ' )
|
||||
|
||||
if first in self.nodemap:
|
||||
node = self.nodemap[ first ]
|
||||
# Substitute IP addresses for node names in command
|
||||
rest = [ self.nodemap[ arg ].IP()
|
||||
if arg in self.nodemap else arg
|
||||
for arg in rest ]
|
||||
rest = ' '.join( rest )
|
||||
# Run cmd on node:
|
||||
builtin = isShellBuiltin( first )
|
||||
node.sendCmd( rest, printPid=( not builtin ) )
|
||||
self.waitForNode( node )
|
||||
else:
|
||||
error( '*** Unknown command: %s\n' % first )
|
||||
|
||||
# pylint: enable-msg=W0613,R0201
|
||||
|
||||
def waitForNode( self, node ):
|
||||
"Wait for a node to finish, and print its output."
|
||||
# Pollers
|
||||
nodePoller = poll()
|
||||
nodePoller.register( node.stdout )
|
||||
bothPoller = poll()
|
||||
bothPoller.register( self.stdin )
|
||||
bothPoller.register( node.stdout )
|
||||
if self.isatty():
|
||||
# Buffer by character, so that interactive
|
||||
# commands sort of work
|
||||
quietRun( 'stty -icanon min 1' )
|
||||
while True:
|
||||
try:
|
||||
bothPoller.poll()
|
||||
# XXX BL: this doesn't quite do what we want.
|
||||
if False and self.inputFile:
|
||||
key = self.inputFile.read( 1 )
|
||||
if key is not '':
|
||||
node.write(key)
|
||||
else:
|
||||
self.inputFile = None
|
||||
if isReadable( self.inPoller ):
|
||||
key = self.stdin.read( 1 )
|
||||
node.write( key )
|
||||
if isReadable( nodePoller ):
|
||||
data = node.monitor()
|
||||
output( data )
|
||||
if not node.waiting:
|
||||
break
|
||||
except KeyboardInterrupt:
|
||||
node.sendInt()
|
||||
|
||||
# Helper functions
|
||||
|
||||
def isReadable( poller ):
|
||||
"Check whether a Poll object has a readable fd."
|
||||
for fdmask in poller.poll( 0 ):
|
||||
mask = fdmask[ 1 ]
|
||||
if mask & POLLIN:
|
||||
return True
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
"Logging functions for Mininet."
|
||||
|
||||
import logging
|
||||
from logging import Logger
|
||||
import types
|
||||
|
||||
# Create a new loglevel, 'CLI info', which enables a Mininet user to see only
|
||||
# the output of the commands they execute, plus any errors or warnings. This
|
||||
# level is in between info and warning. CLI info-level commands should not be
|
||||
# printed during regression tests.
|
||||
OUTPUT = 25
|
||||
|
||||
LEVELS = { 'debug': logging.DEBUG,
|
||||
'info': logging.INFO,
|
||||
'output': OUTPUT,
|
||||
'warning': logging.WARNING,
|
||||
'error': logging.ERROR,
|
||||
'critical': logging.CRITICAL }
|
||||
|
||||
# change this to logging.INFO to get printouts when running unit tests
|
||||
LOGLEVELDEFAULT = OUTPUT
|
||||
|
||||
#default: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
LOGMSGFORMAT = '%(message)s'
|
||||
|
||||
|
||||
# Modified from python2.5/__init__.py
|
||||
class StreamHandlerNoNewline( logging.StreamHandler ):
|
||||
"""StreamHandler that doesn't print newlines by default.
|
||||
Since StreamHandler automatically adds newlines, define a mod to more
|
||||
easily support interactive mode when we want it, or errors-only logging
|
||||
for running unit tests."""
|
||||
|
||||
def emit( self, record ):
|
||||
"""Emit a record.
|
||||
If a formatter is specified, it is used to format the record.
|
||||
The record is then written to the stream with a trailing newline
|
||||
[ N.B. this may be removed depending on feedback ]. If exception
|
||||
information is present, it is formatted using
|
||||
traceback.printException and appended to the stream."""
|
||||
try:
|
||||
msg = self.format( record )
|
||||
fs = '%s' # was '%s\n'
|
||||
if not hasattr( types, 'UnicodeType' ): # if no unicode support...
|
||||
self.stream.write( fs % msg )
|
||||
else:
|
||||
try:
|
||||
self.stream.write( fs % msg )
|
||||
except UnicodeError:
|
||||
self.stream.write( fs % msg.encode( 'UTF-8' ) )
|
||||
self.flush()
|
||||
except ( KeyboardInterrupt, SystemExit ):
|
||||
raise
|
||||
except:
|
||||
self.handleError( record )
|
||||
|
||||
|
||||
class Singleton( type ):
|
||||
"""Singleton pattern from Wikipedia
|
||||
See http://en.wikipedia.org/wiki/SingletonPattern#Python
|
||||
|
||||
Intended to be used as a __metaclass_ param, as shown for the class
|
||||
below.
|
||||
|
||||
Changed cls first args to mcs to satisfy pylint."""
|
||||
|
||||
def __init__( mcs, name, bases, dict_ ):
|
||||
super( Singleton, mcs ).__init__( name, bases, dict_ )
|
||||
mcs.instance = None
|
||||
|
||||
def __call__( mcs, *args, **kw ):
|
||||
if mcs.instance is None:
|
||||
mcs.instance = super( Singleton, mcs ).__call__( *args, **kw )
|
||||
return mcs.instance
|
||||
|
||||
|
||||
class MininetLogger( Logger, object ):
|
||||
"""Mininet-specific logger
|
||||
Enable each mininet .py file to with one import:
|
||||
|
||||
from mininet.log import [lg, info, error]
|
||||
|
||||
...get a default logger that doesn't require one newline per logging
|
||||
call.
|
||||
|
||||
Inherit from object to ensure that we have at least one new-style base
|
||||
class, and can then use the __metaclass__ directive, to prevent this
|
||||
error:
|
||||
|
||||
TypeError: Error when calling the metaclass bases
|
||||
a new-style class can't have only classic bases
|
||||
|
||||
If Python2.5/logging/__init__.py defined Filterer as a new-style class,
|
||||
via Filterer( object ): rather than Filterer, we wouldn't need this.
|
||||
|
||||
Use singleton pattern to ensure only one logger is ever created."""
|
||||
|
||||
__metaclass__ = Singleton
|
||||
|
||||
def __init__( self ):
|
||||
|
||||
Logger.__init__( self, "mininet" )
|
||||
|
||||
# create console handler
|
||||
ch = StreamHandlerNoNewline()
|
||||
# create formatter
|
||||
formatter = logging.Formatter( LOGMSGFORMAT )
|
||||
# add formatter to ch
|
||||
ch.setFormatter( formatter )
|
||||
# add ch to lg
|
||||
self.addHandler( ch )
|
||||
|
||||
self.setLogLevel()
|
||||
|
||||
def setLogLevel( self, levelname=None ):
|
||||
"""Setup loglevel.
|
||||
Convenience function to support lowercase names.
|
||||
levelName: level name from LEVELS"""
|
||||
level = LOGLEVELDEFAULT
|
||||
if levelname != None:
|
||||
if levelname not in LEVELS:
|
||||
raise Exception( 'unknown levelname seen in setLogLevel' )
|
||||
else:
|
||||
level = LEVELS.get( levelname, level )
|
||||
|
||||
self.setLevel( level )
|
||||
self.handlers[ 0 ].setLevel( level )
|
||||
|
||||
# pylint: disable-msg=E0202
|
||||
# "An attribute inherited from mininet.log hide this method"
|
||||
# Not sure why this is occurring - this function definitely gets called.
|
||||
|
||||
# See /usr/lib/python2.5/logging/__init__.py; modified from warning()
|
||||
def output( self, msg, *args, **kwargs ):
|
||||
"""Log 'msg % args' with severity 'OUTPUT'.
|
||||
|
||||
To pass exception information, use the keyword argument exc_info
|
||||
with a true value, e.g.
|
||||
|
||||
logger.warning("Houston, we have a %s", "cli output", exc_info=1)
|
||||
"""
|
||||
if self.manager.disable >= OUTPUT:
|
||||
return
|
||||
if self.isEnabledFor( OUTPUT ):
|
||||
self._log( OUTPUT, msg, args, kwargs )
|
||||
|
||||
# pylint: enable-msg=E0202
|
||||
|
||||
lg = MininetLogger()
|
||||
|
||||
# Make things a bit more convenient by adding aliases
|
||||
# (info, warn, error, debug) and allowing info( 'this', 'is', 'OK' )
|
||||
# In the future we may wish to make things more efficient by only
|
||||
# doing the join (and calling the function) unless the logging level
|
||||
# is high enough.
|
||||
|
||||
def makeListCompatible( fn ):
|
||||
"""Return a new function allowing fn( 'a 1 b' ) to be called as
|
||||
newfn( 'a', 1, 'b' )"""
|
||||
|
||||
def newfn( *args ):
|
||||
"Generated function. Closure-ish."
|
||||
if len( args ) == 1:
|
||||
return fn( *args )
|
||||
args = ' '.join( [ str( arg ) for arg in args ] )
|
||||
return fn( args )
|
||||
|
||||
# Fix newfn's name and docstring
|
||||
setattr( newfn, '__name__', fn.__name__ )
|
||||
setattr( newfn, '__doc__', fn.__doc__ )
|
||||
return newfn
|
||||
|
||||
info, output, warn, error, debug = (
|
||||
lg.info, lg.output, lg.warn, lg.error, lg.debug ) = [
|
||||
makeListCompatible( f ) for f in
|
||||
lg.info, lg.output, lg.warn, lg.error, lg.debug ]
|
||||
|
||||
setLogLevel = lg.setLogLevel
|
||||
@@ -0,0 +1,68 @@
|
||||
"Module dependency utility functions for Mininet."
|
||||
|
||||
from mininet.util import quietRun
|
||||
from mininet.log import info, error, debug
|
||||
from os import environ
|
||||
|
||||
def lsmod():
|
||||
"Return output of lsmod."
|
||||
return quietRun( 'lsmod' )
|
||||
|
||||
def rmmod( mod ):
|
||||
"""Return output of lsmod.
|
||||
mod: module string"""
|
||||
return quietRun( [ 'rmmod', mod ] )
|
||||
|
||||
def modprobe( mod ):
|
||||
"""Return output of modprobe
|
||||
mod: module string"""
|
||||
return quietRun( [ 'modprobe', mod ] )
|
||||
|
||||
OF_KMOD = 'ofdatapath'
|
||||
OVS_KMOD = 'openvswitch_mod'
|
||||
TUN = 'tun'
|
||||
|
||||
def moduleDeps( subtract=None, add=None ):
|
||||
"""Handle module dependencies.
|
||||
subtract: string or list of module names to remove, if already loaded
|
||||
add: string or list of module names to add, if not already loaded"""
|
||||
subtract = subtract if subtract is not None else []
|
||||
add = add if add is not None else []
|
||||
if type( subtract ) is str:
|
||||
subtract = [ subtract ]
|
||||
if type( add ) is str:
|
||||
add = [ add ]
|
||||
for mod in subtract:
|
||||
if mod in lsmod():
|
||||
info( '*** Removing ' + mod + '\n' )
|
||||
rmmodOutput = rmmod( mod )
|
||||
if rmmodOutput:
|
||||
error( 'Error removing ' + mod + ': "%s">\n' % rmmodOutput )
|
||||
exit( 1 )
|
||||
if mod in lsmod():
|
||||
error( 'Failed to remove ' + mod + '; still there!\n' )
|
||||
exit( 1 )
|
||||
for mod in add:
|
||||
if mod not in lsmod():
|
||||
info( '*** Loading ' + mod + '\n' )
|
||||
modprobeOutput = modprobe( mod )
|
||||
if modprobeOutput:
|
||||
error( 'Error inserting ' + mod +
|
||||
' - is it installed and available via modprobe?\n' +
|
||||
'Error was: "%s"\n' % modprobeOutput )
|
||||
if mod not in lsmod():
|
||||
error( 'Failed to insert ' + mod + ' - quitting.\n' )
|
||||
exit( 1 )
|
||||
else:
|
||||
debug( '*** ' + mod + ' already loaded\n' )
|
||||
|
||||
|
||||
def pathCheck( *args, **kwargs ):
|
||||
"Make sure each program in *args can be found in $PATH."
|
||||
moduleName = kwargs.get( 'moduleName', 'it' )
|
||||
for arg in args:
|
||||
if not quietRun( 'which ' + arg ):
|
||||
error( 'Cannot find required executable %s.\n' % arg +
|
||||
'Please make sure that %s is installed ' % moduleName +
|
||||
'and available in your $PATH:\n(%s)\n' % environ[ 'PATH' ] )
|
||||
exit( 1 )
|
||||
Executable
+578
@@ -0,0 +1,578 @@
|
||||
"""
|
||||
|
||||
Mininet: A simple networking testbed for OpenFlow!
|
||||
|
||||
author: Bob Lantz (rlantz@cs.stanford.edu)
|
||||
author: Brandon Heller (brandonh@stanford.edu)
|
||||
|
||||
Mininet creates scalable OpenFlow test networks by using
|
||||
process-based virtualization and network namespaces.
|
||||
|
||||
Simulated hosts are created as processes in separate network
|
||||
namespaces. This allows a complete OpenFlow network to be simulated on
|
||||
top of a single Linux kernel.
|
||||
|
||||
Each host has:
|
||||
|
||||
A virtual console (pipes to a shell)
|
||||
A virtual interfaces (half of a veth pair)
|
||||
A parent shell (and possibly some child processes) in a namespace
|
||||
|
||||
Hosts have a network interface which is configured via ifconfig/ip
|
||||
link/etc.
|
||||
|
||||
This version supports both the kernel and user space datapaths
|
||||
from the OpenFlow reference implementation (openflowswitch.org)
|
||||
as well as OpenVSwitch (openvswitch.org.)
|
||||
|
||||
In kernel datapath mode, the controller and switches are simply
|
||||
processes in the root namespace.
|
||||
|
||||
Kernel OpenFlow datapaths are instantiated using dpctl(8), and are
|
||||
attached to the one side of a veth pair; the other side resides in the
|
||||
host namespace. In this mode, switch processes can simply connect to the
|
||||
controller via the loopback interface.
|
||||
|
||||
In user datapath mode, the controller and switches can be full-service
|
||||
nodes that live in their own network namespaces and have management
|
||||
interfaces and IP addresses on a control network (e.g. 192.168.123.1,
|
||||
currently routed although it could be bridged.)
|
||||
|
||||
In addition to a management interface, user mode switches also have
|
||||
several switch interfaces, halves of veth pairs whose other halves
|
||||
reside in the host nodes that the switches are connected to.
|
||||
|
||||
Consistent, straightforward naming is important in order to easily
|
||||
identify hosts, switches and controllers, both from the CLI and
|
||||
from program code. Interfaces are named to make it easy to identify
|
||||
which interfaces belong to which node.
|
||||
|
||||
The basic naming scheme is as follows:
|
||||
|
||||
Host nodes are named h1-hN
|
||||
Switch nodes are named s1-sN
|
||||
Controller nodes are named c0-cN
|
||||
Interfaces are named {nodename}-eth0 .. {nodename}-ethN
|
||||
|
||||
Note: If the network topology is created using mininet.topo, then
|
||||
node numbers are unique among hosts and switches (e.g. we have
|
||||
h1..hN and SN..SN+M) and also correspond to their default IP addresses
|
||||
of 10.x.y.z/8 where x.y.z is the base-256 representation of N for
|
||||
hN. This mapping allows easy determination of a node's IP
|
||||
address from its name, e.g. h1 -> 10.0.0.1, h257 -> 10.0.1.1.
|
||||
|
||||
Note also that 10.0.0.1 can often be written as 10.1 for short, e.g.
|
||||
"ping 10.1" is equivalent to "ping 10.0.0.1".
|
||||
|
||||
Currently we wrap the entire network in a 'mininet' object, which
|
||||
constructs a simulated network based on a network topology created
|
||||
using a topology object (e.g. LinearTopo) from mininet.topo or
|
||||
mininet.topolib, and a Controller which the switches will connect
|
||||
to. Several configuration options are provided for functions such as
|
||||
automatically setting MAC addresses, populating the ARP table, or
|
||||
even running a set of terminals to allow direct interaction with nodes.
|
||||
|
||||
After the network is created, it can be started using start(), and a
|
||||
variety of useful tasks maybe performed, including basic connectivity
|
||||
and bandwidth tests and running the mininet CLI.
|
||||
|
||||
Once the network is up and running, test code can easily get access
|
||||
to host and switch objects which can then be used for arbitrary
|
||||
experiments, typically involving running a series of commands on the
|
||||
hosts.
|
||||
|
||||
After all desired tests or activities have been completed, the stop()
|
||||
method may be called to shut down the network.
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import select
|
||||
import signal
|
||||
from time import sleep
|
||||
|
||||
from mininet.cli import CLI
|
||||
from mininet.log import info, error, debug, output
|
||||
from mininet.node import Host, UserSwitch, OVSKernelSwitch, Controller
|
||||
from mininet.node import ControllerParams
|
||||
from mininet.util import quietRun, fixLimits
|
||||
from mininet.util import createLink, macColonHex, ipStr, ipParse
|
||||
from mininet.term import cleanUpScreens, makeTerms
|
||||
|
||||
class Mininet( object ):
|
||||
"Network emulation with hosts spawned in network namespaces."
|
||||
|
||||
def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host,
|
||||
controller=Controller,
|
||||
cparams=ControllerParams( '10.0.0.0', 8 ),
|
||||
build=True, xterms=False, cleanup=False,
|
||||
inNamespace=False,
|
||||
autoSetMacs=False, autoStaticArp=False, listenPort=None ):
|
||||
"""Create Mininet object.
|
||||
topo: Topo (topology) object or None
|
||||
switch: Switch class
|
||||
host: Host class
|
||||
controller: Controller class
|
||||
cparams: ControllerParams object
|
||||
build: build now from topo?
|
||||
xterms: if build now, spawn xterms?
|
||||
cleanup: if build now, cleanup before creating?
|
||||
inNamespace: spawn switches and controller in net namespaces?
|
||||
autoSetMacs: set MAC addrs from topo?
|
||||
autoStaticArp: set all-pairs static MAC addrs?
|
||||
listenPort: base listening port to open; will be incremented for
|
||||
each additional switch in the net if inNamespace=False"""
|
||||
self.switch = switch
|
||||
self.host = host
|
||||
self.controller = controller
|
||||
self.cparams = cparams
|
||||
self.topo = topo
|
||||
self.inNamespace = inNamespace
|
||||
self.xterms = xterms
|
||||
self.cleanup = cleanup
|
||||
self.autoSetMacs = autoSetMacs
|
||||
self.autoStaticArp = autoStaticArp
|
||||
self.listenPort = listenPort
|
||||
|
||||
self.hosts = []
|
||||
self.switches = []
|
||||
self.controllers = []
|
||||
self.nameToNode = {} # name to Node (Host/Switch) objects
|
||||
self.idToNode = {} # dpid to Node (Host/Switch) objects
|
||||
self.dps = 0 # number of created kernel datapaths
|
||||
self.terms = [] # list of spawned xterm processes
|
||||
|
||||
init()
|
||||
switch.setup()
|
||||
|
||||
self.built = False
|
||||
if topo and build:
|
||||
self.build()
|
||||
|
||||
def addHost( self, name, mac=None, ip=None ):
|
||||
"""Add host.
|
||||
name: name of host to add
|
||||
mac: default MAC address for intf 0
|
||||
ip: default IP address for intf 0
|
||||
returns: added host"""
|
||||
host = self.host( name, defaultMAC=mac, defaultIP=ip )
|
||||
self.hosts.append( host )
|
||||
self.nameToNode[ name ] = host
|
||||
return host
|
||||
|
||||
def addSwitch( self, name, mac=None, ip=None ):
|
||||
"""Add switch.
|
||||
name: name of switch to add
|
||||
mac: default MAC address for kernel/OVS switch intf 0
|
||||
returns: added switch
|
||||
side effect: increments the listenPort member variable."""
|
||||
if self.switch == UserSwitch:
|
||||
sw = self.switch( name, listenPort=self.listenPort,
|
||||
defaultMAC=mac, defaultIP=ip, inNamespace=self.inNamespace )
|
||||
else:
|
||||
sw = self.switch( name, listenPort=self.listenPort,
|
||||
defaultMAC=mac, defaultIP=ip, dp=self.dps,
|
||||
inNamespace=self.inNamespace )
|
||||
if not self.inNamespace and self.listenPort:
|
||||
self.listenPort += 1
|
||||
self.dps += 1
|
||||
self.switches.append( sw )
|
||||
self.nameToNode[ name ] = sw
|
||||
return sw
|
||||
|
||||
def addController( self, name='c0', controller=None, **kwargs ):
|
||||
"""Add controller.
|
||||
controller: Controller class"""
|
||||
if not controller:
|
||||
controller = self.controller
|
||||
controller_new = controller( name, **kwargs )
|
||||
if controller_new: # allow controller-less setups
|
||||
self.controllers.append( controller_new )
|
||||
self.nameToNode[ name ] = controller_new
|
||||
return controller_new
|
||||
|
||||
# Control network support:
|
||||
#
|
||||
# Create an explicit control network. Currently this is only
|
||||
# used by the user datapath configuration.
|
||||
#
|
||||
# Notes:
|
||||
#
|
||||
# 1. If the controller and switches are in the same (e.g. root)
|
||||
# namespace, they can just use the loopback connection.
|
||||
#
|
||||
# 2. If we can get unix domain sockets to work, we can use them
|
||||
# instead of an explicit control network.
|
||||
#
|
||||
# 3. Instead of routing, we could bridge or use 'in-band' control.
|
||||
#
|
||||
# 4. Even if we dispense with this in general, it could still be
|
||||
# useful for people who wish to simulate a separate control
|
||||
# network (since real networks may need one!)
|
||||
|
||||
def configureControlNetwork( self ):
|
||||
"Configure control network."
|
||||
self.configureRoutedControlNetwork()
|
||||
|
||||
# We still need to figure out the right way to pass
|
||||
# in the control network location.
|
||||
|
||||
def configureRoutedControlNetwork( self, ip='192.168.123.1',
|
||||
prefixLen=16 ):
|
||||
"""Configure a routed control network on controller and switches.
|
||||
For use with the user datapath only right now.
|
||||
"""
|
||||
controller = self.controllers[ 0 ]
|
||||
info( controller.name + ' <->' )
|
||||
cip = ip
|
||||
snum = ipParse( ip )
|
||||
for switch in self.switches:
|
||||
info( ' ' + switch.name )
|
||||
sintf, cintf = createLink( switch, controller )
|
||||
snum += 1
|
||||
while snum & 0xff in [ 0, 255 ]:
|
||||
snum += 1
|
||||
sip = ipStr( snum )
|
||||
controller.setIP( cintf, cip, prefixLen )
|
||||
switch.setIP( sintf, sip, prefixLen )
|
||||
controller.setHostRoute( sip, cintf )
|
||||
switch.setHostRoute( cip, sintf )
|
||||
info( '\n' )
|
||||
info( '*** Testing control network\n' )
|
||||
while not controller.intfIsUp( cintf ):
|
||||
info( '*** Waiting for', cintf, 'to come up\n' )
|
||||
sleep( 1 )
|
||||
for switch in self.switches:
|
||||
while not switch.intfIsUp( sintf ):
|
||||
info( '*** Waiting for', sintf, 'to come up\n' )
|
||||
sleep( 1 )
|
||||
if self.ping( hosts=[ switch, controller ] ) != 0:
|
||||
error( '*** Error: control network test failed\n' )
|
||||
exit( 1 )
|
||||
info( '\n' )
|
||||
|
||||
def configHosts( self ):
|
||||
"Configure a set of hosts."
|
||||
# params were: hosts, ips
|
||||
for host in self.hosts:
|
||||
hintf = host.intfs[ 0 ]
|
||||
host.setIP( hintf, host.defaultIP, self.cparams.prefixLen )
|
||||
host.setDefaultRoute( hintf )
|
||||
# You're low priority, dude!
|
||||
quietRun( 'renice +18 -p ' + repr( host.pid ) )
|
||||
info( host.name + ' ' )
|
||||
info( '\n' )
|
||||
|
||||
def buildFromTopo( self, topo ):
|
||||
"""Build mininet from a topology object
|
||||
At the end of this function, everything should be connected
|
||||
and up."""
|
||||
|
||||
def addNode( prefix, addMethod, nodeId ):
|
||||
"Add a host or a switch."
|
||||
name = prefix + topo.name( nodeId )
|
||||
mac = macColonHex( nodeId ) if self.setMacs else None
|
||||
ip = topo.ip( nodeId )
|
||||
node = addMethod( name, mac=mac, ip=ip )
|
||||
self.idToNode[ nodeId ] = node
|
||||
info( name + ' ' )
|
||||
|
||||
# Possibly we should clean up here and/or validate
|
||||
# the topo
|
||||
if self.cleanup:
|
||||
pass
|
||||
|
||||
info( '*** Adding controller\n' )
|
||||
self.addController( 'c0' )
|
||||
info( '*** Creating network\n' )
|
||||
info( '*** Adding hosts:\n' )
|
||||
for hostId in sorted( topo.hosts() ):
|
||||
addNode( 'h', self.addHost, hostId )
|
||||
info( '\n*** Adding switches:\n' )
|
||||
for switchId in sorted( topo.switches() ):
|
||||
addNode( 's', self.addSwitch, switchId )
|
||||
info( '\n*** Adding links:\n' )
|
||||
for srcId, dstId in sorted( topo.edges() ):
|
||||
src, dst = self.idToNode[ srcId ], self.idToNode[ dstId ]
|
||||
srcPort, dstPort = topo.port( srcId, dstId )
|
||||
createLink( src, dst, srcPort, dstPort )
|
||||
info( '(%s, %s) ' % ( src.name, dst.name ) )
|
||||
info( '\n' )
|
||||
|
||||
def build( self ):
|
||||
"Build mininet."
|
||||
if self.topo:
|
||||
self.buildFromTopo( self.topo )
|
||||
if self.inNamespace:
|
||||
info( '*** Configuring control network\n' )
|
||||
self.configureControlNetwork()
|
||||
info( '*** Configuring hosts\n' )
|
||||
self.configHosts()
|
||||
if self.xterms:
|
||||
self.startTerms()
|
||||
if self.autoSetMacs:
|
||||
self.setMacs()
|
||||
if self.autoStaticArp:
|
||||
self.staticArp()
|
||||
self.built = True
|
||||
|
||||
def startTerms( self ):
|
||||
"Start a terminal for each node."
|
||||
info( "*** Running terms on %s\n" % os.environ[ 'DISPLAY' ] )
|
||||
cleanUpScreens()
|
||||
self.terms += makeTerms( self.controllers, 'controller' )
|
||||
self.terms += makeTerms( self.switches, 'switch' )
|
||||
self.terms += makeTerms( self.hosts, 'host' )
|
||||
|
||||
def stopXterms( self ):
|
||||
"Kill each xterm."
|
||||
# Kill xterms
|
||||
for term in self.terms:
|
||||
os.kill( term.pid, signal.SIGKILL )
|
||||
cleanUpScreens()
|
||||
|
||||
def setMacs( self ):
|
||||
"""Set MAC addrs to correspond to default MACs on hosts.
|
||||
Assume that the host only has one interface."""
|
||||
for host in self.hosts:
|
||||
host.setMAC( host.intfs[ 0 ], host.defaultMAC )
|
||||
|
||||
def staticArp( self ):
|
||||
"Add all-pairs ARP entries to remove the need to handle broadcast."
|
||||
for src in self.hosts:
|
||||
for dst in self.hosts:
|
||||
if src != dst:
|
||||
src.setARP( ip=dst.IP(), mac=dst.MAC() )
|
||||
|
||||
def start( self ):
|
||||
"Start controller and switches."
|
||||
if not self.built:
|
||||
self.build()
|
||||
info( '*** Starting controller\n' )
|
||||
for controller in self.controllers:
|
||||
controller.start()
|
||||
info( '*** Starting %s switches\n' % len( self.switches ) )
|
||||
for switch in self.switches:
|
||||
info( switch.name + ' ')
|
||||
switch.start( self.controllers )
|
||||
info( '\n' )
|
||||
|
||||
def stop( self ):
|
||||
"Stop the controller(s), switches and hosts"
|
||||
if self.terms:
|
||||
info( '*** Stopping %i terms\n' % len( self.terms ) )
|
||||
self.stopXterms()
|
||||
info( '*** Stopping %i hosts\n' % len( self.hosts ) )
|
||||
for host in self.hosts:
|
||||
info( '%s ' % host.name )
|
||||
host.terminate()
|
||||
info( '\n' )
|
||||
info( '*** Stopping %i switches\n' % len( self.switches ) )
|
||||
for switch in self.switches:
|
||||
info( switch.name )
|
||||
switch.stop()
|
||||
info( '\n' )
|
||||
info( '*** Stopping %i controllers\n' % len( self.controllers ) )
|
||||
for controller in self.controllers:
|
||||
controller.stop()
|
||||
info( '*** Done\n' )
|
||||
|
||||
def run( self, test, *args, **kwargs ):
|
||||
"Perform a complete start/test/stop cycle."
|
||||
self.start()
|
||||
info( '*** Running test\n' )
|
||||
result = test( *args, **kwargs )
|
||||
self.stop()
|
||||
return result
|
||||
|
||||
def monitor( self, hosts=None, timeoutms=-1 ):
|
||||
"""Monitor a set of hosts (or all hosts by default),
|
||||
and return their output, a line at a time.
|
||||
hosts: (optional) set of hosts to monitor
|
||||
timeoutms: (optional) timeout value in ms
|
||||
returns: iterator which returns host, line"""
|
||||
if hosts is None:
|
||||
hosts = self.hosts
|
||||
poller = select.poll()
|
||||
Node = hosts[ 0 ] # so we can call class method fdToNode
|
||||
for host in hosts:
|
||||
poller.register( host.stdout )
|
||||
while True:
|
||||
ready = poller.poll( timeoutms )
|
||||
for fd, event in ready:
|
||||
host = Node.fdToNode( fd )
|
||||
if event & select.POLLIN:
|
||||
line = host.readline()
|
||||
if line is not None:
|
||||
yield host, line
|
||||
# Return if non-blocking
|
||||
if not ready and timeoutms >= 0:
|
||||
yield None, None
|
||||
|
||||
@staticmethod
|
||||
def _parsePing( pingOutput ):
|
||||
"Parse ping output and return packets sent, received."
|
||||
# Check for downed link
|
||||
if 'connect: Network is unreachable' in pingOutput:
|
||||
return (1, 0)
|
||||
r = r'(\d+) packets transmitted, (\d+) received'
|
||||
m = re.search( r, pingOutput )
|
||||
if m == None:
|
||||
error( '*** Error: could not parse ping output: %s\n' %
|
||||
pingOutput )
|
||||
return (1, 0)
|
||||
sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
|
||||
return sent, received
|
||||
|
||||
def ping( self, hosts=None ):
|
||||
"""Ping between all specified hosts.
|
||||
hosts: list of hosts
|
||||
returns: ploss packet loss percentage"""
|
||||
# should we check if running?
|
||||
packets = 0
|
||||
lost = 0
|
||||
ploss = None
|
||||
if not hosts:
|
||||
hosts = self.hosts
|
||||
output( '*** Ping: testing ping reachability\n' )
|
||||
for node in hosts:
|
||||
output( '%s -> ' % node.name )
|
||||
for dest in hosts:
|
||||
if node != dest:
|
||||
result = node.cmd( 'ping -c1 ' + dest.IP() )
|
||||
sent, received = self._parsePing( result )
|
||||
packets += sent
|
||||
if received > sent:
|
||||
error( '*** Error: received too many packets' )
|
||||
error( '%s' % result )
|
||||
node.cmdPrint( 'route' )
|
||||
exit( 1 )
|
||||
lost += sent - received
|
||||
output( ( '%s ' % dest.name ) if received else 'X ' )
|
||||
output( '\n' )
|
||||
ploss = 100 * lost / packets
|
||||
output( "*** Results: %i%% dropped (%d/%d lost)\n" %
|
||||
( ploss, lost, packets ) )
|
||||
return ploss
|
||||
|
||||
def pingAll( self ):
|
||||
"""Ping between all hosts.
|
||||
returns: ploss packet loss percentage"""
|
||||
return self.ping()
|
||||
|
||||
def pingPair( self ):
|
||||
"""Ping between first two hosts, useful for testing.
|
||||
returns: ploss packet loss percentage"""
|
||||
hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
|
||||
return self.ping( hosts=hosts )
|
||||
|
||||
@staticmethod
|
||||
def _parseIperf( iperfOutput ):
|
||||
"""Parse iperf output and return bandwidth.
|
||||
iperfOutput: string
|
||||
returns: result string"""
|
||||
r = r'([\d\.]+ \w+/sec)'
|
||||
m = re.findall( r, iperfOutput )
|
||||
if m:
|
||||
return m[-1]
|
||||
else:
|
||||
# was: raise Exception(...)
|
||||
error( 'could not parse iperf output: ' + iperfOutput )
|
||||
return ''
|
||||
|
||||
def iperf( self, hosts=None, l4Type='TCP', udpBw='10M' ):
|
||||
"""Run iperf between two hosts.
|
||||
hosts: list of hosts; if None, uses opposite hosts
|
||||
l4Type: string, one of [ TCP, UDP ]
|
||||
returns: results two-element array of server and client speeds"""
|
||||
if not quietRun( 'which telnet' ):
|
||||
error( 'Cannot find telnet in $PATH - required for iperf test' )
|
||||
return
|
||||
if not hosts:
|
||||
hosts = [ self.hosts[ 0 ], self.hosts[ -1 ] ]
|
||||
else:
|
||||
assert len( hosts ) == 2
|
||||
client, server = hosts
|
||||
output( '*** Iperf: testing ' + l4Type + ' bandwidth between ' )
|
||||
output( "%s and %s\n" % ( client.name, server.name ) )
|
||||
server.cmd( 'killall -9 iperf' )
|
||||
iperfArgs = 'iperf '
|
||||
bwArgs = ''
|
||||
if l4Type == 'UDP':
|
||||
iperfArgs += '-u '
|
||||
bwArgs = '-b ' + udpBw + ' '
|
||||
elif l4Type != 'TCP':
|
||||
raise Exception( 'Unexpected l4 type: %s' % l4Type )
|
||||
server.sendCmd( iperfArgs + '-s', printPid=True )
|
||||
servout = ''
|
||||
while server.lastPid is None:
|
||||
servout += server.monitor()
|
||||
while 'Connected' not in client.cmd(
|
||||
'sh -c "echo A | telnet -e A %s 5001"' % server.IP()):
|
||||
output('waiting for iperf to start up')
|
||||
sleep(.5)
|
||||
cliout = client.cmd( iperfArgs + '-t 5 -c ' + server.IP() + ' ' +
|
||||
bwArgs )
|
||||
debug( 'Client output: %s\n' % cliout )
|
||||
server.sendInt()
|
||||
servout += server.waitOutput()
|
||||
debug( 'Server output: %s\n' % servout )
|
||||
result = [ self._parseIperf( servout ), self._parseIperf( cliout ) ]
|
||||
if l4Type == 'UDP':
|
||||
result.insert( 0, udpBw )
|
||||
output( '*** Results: %s\n' % result )
|
||||
return result
|
||||
|
||||
def configLinkStatus( self, src, dst, status ):
|
||||
"""Change status of src <-> dst links.
|
||||
src: node name
|
||||
dst: node name
|
||||
status: string {up, down}"""
|
||||
if src not in self.nameToNode:
|
||||
error( 'src not in network: %s\n' % src )
|
||||
elif dst not in self.nameToNode:
|
||||
error( 'dst not in network: %s\n' % dst )
|
||||
else:
|
||||
srcNode, dstNode = self.nameToNode[ src ], self.nameToNode[ dst ]
|
||||
connections = srcNode.connectionsTo( dstNode )
|
||||
if len( connections ) == 0:
|
||||
error( 'src and dst not connected: %s %s\n' % ( src, dst) )
|
||||
for srcIntf, dstIntf in connections:
|
||||
result = srcNode.cmd( 'ifconfig', srcIntf, status )
|
||||
if result:
|
||||
error( 'link src status change failed: %s\n' % result )
|
||||
result = dstNode.cmd( 'ifconfig', dstIntf, status )
|
||||
if result:
|
||||
error( 'link dst status change failed: %s\n' % result )
|
||||
|
||||
def interact( self ):
|
||||
"Start network and run our simple CLI."
|
||||
self.start()
|
||||
result = CLI( self )
|
||||
self.stop()
|
||||
return result
|
||||
|
||||
|
||||
# pylint thinks inited is unused
|
||||
# pylint: disable-msg=W0612
|
||||
|
||||
def init():
|
||||
"Initialize Mininet."
|
||||
if init.inited:
|
||||
return
|
||||
if os.getuid() != 0:
|
||||
# Note: this script must be run as root
|
||||
# Perhaps we should do so automatically!
|
||||
print "*** Mininet must run as root."
|
||||
exit( 1 )
|
||||
# If which produces no output, then mnexec is not in the path.
|
||||
# May want to loosen this to handle mnexec in the current dir.
|
||||
if not quietRun( 'which mnexec' ):
|
||||
raise Exception( "Could not find mnexec - check $PATH" )
|
||||
fixLimits()
|
||||
init.inited = True
|
||||
|
||||
init.inited = False
|
||||
|
||||
# pylint: enable-msg=W0612
|
||||
+710
@@ -0,0 +1,710 @@
|
||||
"""
|
||||
Node objects for Mininet.
|
||||
|
||||
Nodes provide a simple abstraction for interacting with hosts, switches
|
||||
and controllers. Local nodes are simply one or more processes on the local
|
||||
machine.
|
||||
|
||||
Node: superclass for all (primarily local) network nodes.
|
||||
|
||||
Host: a virtual host. By default, a host is simply a shell; commands
|
||||
may be sent using Cmd (which waits for output), or using sendCmd(),
|
||||
which returns immediately, allowing subsequent monitoring using
|
||||
monitor(). Examples of how to run experiments using this
|
||||
functionality are provided in the examples/ directory.
|
||||
|
||||
Switch: superclass for switch nodes.
|
||||
|
||||
UserSwitch: a switch using the user-space switch from the OpenFlow
|
||||
reference implementation.
|
||||
|
||||
KernelSwitch: a switch using the kernel switch from the OpenFlow reference
|
||||
implementation.
|
||||
|
||||
OVSSwitch: a switch using the OpenVSwitch OpenFlow-compatible switch
|
||||
implementation (openvswitch.org).
|
||||
|
||||
Controller: superclass for OpenFlow controllers. The default controller
|
||||
is controller(8) from the reference implementation.
|
||||
|
||||
NOXController: a controller node using NOX (noxrepo.org).
|
||||
|
||||
RemoteController: a remote controller node, which may use any
|
||||
arbitrary OpenFlow-compatible controller, and which is not
|
||||
created or managed by mininet.
|
||||
|
||||
Future enhancements:
|
||||
|
||||
- Possibly make Node, Switch and Controller more abstract so that
|
||||
they can be used for both local and remote nodes
|
||||
|
||||
- Create proxy objects for remote nodes (Mininet: Cluster Edition)
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import signal
|
||||
import select
|
||||
from subprocess import Popen, PIPE, STDOUT
|
||||
from time import sleep
|
||||
|
||||
from mininet.log import info, error, debug
|
||||
from mininet.util import quietRun, makeIntfPair, moveIntf, isShellBuiltin
|
||||
from mininet.moduledeps import moduleDeps, pathCheck, OVS_KMOD, OF_KMOD, TUN
|
||||
|
||||
SWITCH_PORT_BASE = 1 # For OF > 0.9, switch ports start at 1 rather than zero
|
||||
|
||||
class Node( object ):
|
||||
"""A virtual network node is simply a shell in a network namespace.
|
||||
We communicate with it using pipes."""
|
||||
|
||||
inToNode = {} # mapping of input fds to nodes
|
||||
outToNode = {} # mapping of output fds to nodes
|
||||
|
||||
portBase = 0 # Nodes always start with eth0/port0, even in OF 1.0
|
||||
|
||||
def __init__( self, name, inNamespace=True,
|
||||
defaultMAC=None, defaultIP=None, **kwargs ):
|
||||
"""name: name of node
|
||||
inNamespace: in network namespace?
|
||||
defaultMAC: default MAC address for intf 0
|
||||
defaultIP: default IP address for intf 0"""
|
||||
self.name = name
|
||||
self.inNamespace = inNamespace
|
||||
self.defaultIP = defaultIP
|
||||
self.defaultMAC = defaultMAC
|
||||
opts = '-cdp'
|
||||
if self.inNamespace:
|
||||
opts += 'n'
|
||||
cmd = [ 'mnexec', opts, 'bash', '-m' ]
|
||||
self.shell = Popen( cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT,
|
||||
close_fds=False )
|
||||
self.stdin = self.shell.stdin
|
||||
self.stdout = self.shell.stdout
|
||||
self.pid = self.shell.pid
|
||||
self.pollOut = select.poll()
|
||||
self.pollOut.register( self.stdout )
|
||||
# Maintain mapping between file descriptors and nodes
|
||||
# This is useful for monitoring multiple nodes
|
||||
# using select.poll()
|
||||
self.outToNode[ self.stdout.fileno() ] = self
|
||||
self.inToNode[ self.stdin.fileno() ] = self
|
||||
self.intfs = {} # dict of port numbers to interface names
|
||||
self.ports = {} # dict of interface names to port numbers
|
||||
# replace with Port objects, eventually ?
|
||||
self.ips = {} # dict of interfaces to ip addresses as strings
|
||||
self.macs = {} # dict of interfacesto mac addresses as strings
|
||||
self.connection = {} # remote node connected to each interface
|
||||
self.execed = False
|
||||
self.lastCmd = None
|
||||
self.lastPid = None
|
||||
self.readbuf = ''
|
||||
self.waiting = False
|
||||
# Stash additional information as desired
|
||||
self.args = kwargs
|
||||
|
||||
@classmethod
|
||||
def fdToNode( cls, fd ):
|
||||
"""Return node corresponding to given file descriptor.
|
||||
fd: file descriptor
|
||||
returns: node"""
|
||||
node = Node.outToNode.get( fd )
|
||||
return node or Node.inToNode.get( fd )
|
||||
|
||||
def cleanup( self ):
|
||||
"Help python collect its garbage."
|
||||
self.shell = None
|
||||
|
||||
# Subshell I/O, commands and control
|
||||
def read( self, maxbytes=1024 ):
|
||||
"""Buffered read from node, non-blocking.
|
||||
maxbytes: maximum number of bytes to return"""
|
||||
count = len( self.readbuf )
|
||||
if count < maxbytes:
|
||||
data = os.read( self.stdout.fileno(), maxbytes - count )
|
||||
self.readbuf += data
|
||||
if maxbytes >= len( self.readbuf ):
|
||||
result = self.readbuf
|
||||
self.readbuf = ''
|
||||
else:
|
||||
result = self.readbuf[ :maxbytes ]
|
||||
self.readbuf = self.readbuf[ maxbytes: ]
|
||||
return result
|
||||
|
||||
def readline( self ):
|
||||
"""Buffered readline from node, non-blocking.
|
||||
returns: line (minus newline) or None"""
|
||||
self.readbuf += self.read( 1024 )
|
||||
if '\n' not in self.readbuf:
|
||||
return None
|
||||
pos = self.readbuf.find( '\n' )
|
||||
line = self.readbuf[ 0 : pos ]
|
||||
self.readbuf = self.readbuf[ pos + 1: ]
|
||||
return line
|
||||
|
||||
def write( self, data ):
|
||||
"""Write data to node.
|
||||
data: string"""
|
||||
os.write( self.stdin.fileno(), data )
|
||||
|
||||
def terminate( self ):
|
||||
"Send kill signal to Node and clean up after it."
|
||||
os.kill( self.pid, signal.SIGKILL )
|
||||
self.cleanup()
|
||||
|
||||
def stop( self ):
|
||||
"Stop node."
|
||||
self.terminate()
|
||||
|
||||
def waitReadable( self, timeoutms=None ):
|
||||
"""Wait until node's output is readable.
|
||||
timeoutms: timeout in ms or None to wait indefinitely."""
|
||||
if len( self.readbuf ) == 0:
|
||||
self.pollOut.poll( timeoutms )
|
||||
|
||||
def sendCmd( self, *args, **kwargs ):
|
||||
"""Send a command, followed by a command to echo a sentinel,
|
||||
and return without waiting for the command to complete.
|
||||
args: command and arguments, or string
|
||||
printPid: print command's PID?"""
|
||||
assert not self.waiting
|
||||
printPid = kwargs.get( 'printPid', True )
|
||||
if len( args ) > 0:
|
||||
cmd = args
|
||||
if not isinstance( cmd, str ):
|
||||
cmd = ' '.join( cmd )
|
||||
if not re.search( r'\w', cmd ):
|
||||
# Replace empty commands with something harmless
|
||||
cmd = 'echo -n'
|
||||
if len( cmd ) > 0 and cmd[ -1 ] == '&':
|
||||
separator = '&'
|
||||
cmd = cmd[ :-1 ]
|
||||
else:
|
||||
separator = ';'
|
||||
if printPid and not isShellBuiltin( cmd ):
|
||||
cmd = 'mnexec -p ' + cmd
|
||||
self.write( cmd + separator + ' printf "\\177" \n' )
|
||||
self.lastCmd = cmd
|
||||
self.lastPid = None
|
||||
self.waiting = True
|
||||
|
||||
def sendInt( self, sig=signal.SIGINT ):
|
||||
"Interrupt running command."
|
||||
if self.lastPid:
|
||||
try:
|
||||
os.kill( self.lastPid, sig )
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def monitor( self, timeoutms=None ):
|
||||
"""Monitor and return the output of a command.
|
||||
Set self.waiting to False if command has completed.
|
||||
timeoutms: timeout in ms or None to wait indefinitely."""
|
||||
self.waitReadable( timeoutms )
|
||||
data = self.read( 1024 )
|
||||
# Look for PID
|
||||
marker = chr( 1 ) + r'\d+\n'
|
||||
if chr( 1 ) in data:
|
||||
markers = re.findall( marker, data )
|
||||
if markers:
|
||||
self.lastPid = int( markers[ 0 ][ 1: ] )
|
||||
data = re.sub( marker, '', data )
|
||||
# Look for sentinel/EOF
|
||||
if len( data ) > 0 and data[ -1 ] == chr( 127 ):
|
||||
self.waiting = False
|
||||
data = data[ :-1 ]
|
||||
elif chr( 127 ) in data:
|
||||
self.waiting = False
|
||||
data = data.replace( chr( 127 ), '' )
|
||||
return data
|
||||
|
||||
def waitOutput( self, verbose=False ):
|
||||
"""Wait for a command to complete.
|
||||
Completion is signaled by a sentinel character, ASCII(127)
|
||||
appearing in the output stream. Wait for the sentinel and return
|
||||
the output, including trailing newline.
|
||||
verbose: print output interactively"""
|
||||
log = info if verbose else debug
|
||||
output = ''
|
||||
while self.waiting:
|
||||
data = self.monitor()
|
||||
output += data
|
||||
log( data )
|
||||
return output
|
||||
|
||||
def cmd( self, *args, **kwargs ):
|
||||
"""Send a command, wait for output, and return it.
|
||||
cmd: string"""
|
||||
verbose = kwargs.get( 'verbose', False )
|
||||
log = info if verbose else debug
|
||||
log( '*** %s : %s\n' % ( self.name, args ) )
|
||||
self.sendCmd( *args, **kwargs )
|
||||
return self.waitOutput( verbose )
|
||||
|
||||
def cmdPrint( self, *args):
|
||||
"""Call cmd and printing its output
|
||||
cmd: string"""
|
||||
return self.cmd( *args, **{ 'verbose': True } )
|
||||
|
||||
# Interface management, configuration, and routing
|
||||
|
||||
# BL notes: This might be a bit redundant or over-complicated.
|
||||
# However, it does allow a bit of specialization, including
|
||||
# changing the canonical interface names. It's also tricky since
|
||||
# the real interfaces are created as veth pairs, so we can't
|
||||
# make a single interface at a time.
|
||||
|
||||
def intfName( self, n ):
|
||||
"Construct a canonical interface name node-ethN for interface n."
|
||||
return self.name + '-eth' + repr( n )
|
||||
|
||||
def newPort( self ):
|
||||
"Return the next port number to allocate."
|
||||
if len( self.ports ) > 0:
|
||||
return max( self.ports.values() ) + 1
|
||||
return self.portBase
|
||||
|
||||
def addIntf( self, intf, port=None ):
|
||||
"""Add an interface.
|
||||
intf: interface name (e.g. nodeN-ethM)
|
||||
port: port number (optional, typically OpenFlow port number)"""
|
||||
if port is None:
|
||||
port = self.newPort()
|
||||
self.intfs[ port ] = intf
|
||||
self.ports[ intf ] = port
|
||||
#info( '\n' )
|
||||
#info( 'added intf %s:%d to node %s\n' % ( intf,port, self.name ) )
|
||||
if self.inNamespace:
|
||||
#info( 'moving w/inNamespace set\n' )
|
||||
moveIntf( intf, self )
|
||||
|
||||
def registerIntf( self, intf, dstNode, dstIntf ):
|
||||
"Register connection of intf to dstIntf on dstNode."
|
||||
self.connection[ intf ] = ( dstNode, dstIntf )
|
||||
|
||||
def connectionsTo( self, node):
|
||||
"Return [(srcIntf, dstIntf)..] for connections to dstNode."
|
||||
# We could optimize this if it is important
|
||||
connections = []
|
||||
for intf in self.connection.keys():
|
||||
dstNode, dstIntf = self.connection[ intf ]
|
||||
if dstNode == node:
|
||||
connections.append( ( intf, dstIntf ) )
|
||||
return connections
|
||||
|
||||
# This is a symmetric operation, but it makes sense to put
|
||||
# the code here since it is tightly coupled to routines in
|
||||
# this class. For a more symmetric API, you can use
|
||||
# mininet.util.createLink()
|
||||
|
||||
def linkTo( self, node2, port1=None, port2=None ):
|
||||
"""Create link to another node, making two new interfaces.
|
||||
node2: Node to link us to
|
||||
port1: our port number (optional)
|
||||
port2: node2 port number (optional)
|
||||
returns: intf1 name, intf2 name"""
|
||||
node1 = self
|
||||
if port1 is None:
|
||||
port1 = node1.newPort()
|
||||
if port2 is None:
|
||||
port2 = node2.newPort()
|
||||
intf1 = node1.intfName( port1 )
|
||||
intf2 = node2.intfName( port2 )
|
||||
makeIntfPair( intf1, intf2 )
|
||||
node1.addIntf( intf1, port1 )
|
||||
node2.addIntf( intf2, port2 )
|
||||
node1.registerIntf( intf1, node2, intf2 )
|
||||
node2.registerIntf( intf2, node1, intf1 )
|
||||
return intf1, intf2
|
||||
|
||||
def deleteIntfs( self ):
|
||||
"Delete all of our interfaces."
|
||||
# In theory the interfaces should go away after we shut down.
|
||||
# However, this takes time, so we're better off removing them
|
||||
# explicitly so that we won't get errors if we run before they
|
||||
# have been removed by the kernel. Unfortunately this is very slow,
|
||||
# at least with Linux kernels before 2.6.33
|
||||
for intf in self.intfs.values():
|
||||
quietRun( 'ip link del ' + intf )
|
||||
info( '.' )
|
||||
# Does it help to sleep to let things run?
|
||||
sleep( 0.001 )
|
||||
|
||||
def setMAC( self, intf, mac ):
|
||||
"""Set the MAC address for an interface.
|
||||
mac: MAC address as string"""
|
||||
result = self.cmd( 'ifconfig', intf, 'down' )
|
||||
result += self.cmd( 'ifconfig', intf, 'hw', 'ether', mac )
|
||||
result += self.cmd( 'ifconfig', intf, 'up' )
|
||||
return result
|
||||
|
||||
def setARP( self, ip, mac ):
|
||||
"""Add an ARP entry.
|
||||
ip: IP address as string
|
||||
mac: MAC address as string"""
|
||||
result = self.cmd( 'arp', '-s', ip, mac )
|
||||
return result
|
||||
|
||||
def setIP( self, intf, ip, prefixLen=8 ):
|
||||
"""Set the IP address for an interface.
|
||||
intf: interface name
|
||||
ip: IP address as a string
|
||||
prefixLen: prefix length, e.g. 8 for /8 or 16M addrs"""
|
||||
ipSub = '%s/%d' % ( ip, prefixLen )
|
||||
result = self.cmd( 'ifconfig', intf, ipSub, 'up' )
|
||||
self.ips[ intf ] = ip
|
||||
return result
|
||||
|
||||
def setHostRoute( self, ip, intf ):
|
||||
"""Add route to host.
|
||||
ip: IP address as dotted decimal
|
||||
intf: string, interface name"""
|
||||
return self.cmd( 'route add -host ' + ip + ' dev ' + intf )
|
||||
|
||||
def setDefaultRoute( self, intf ):
|
||||
"""Set the default route to go through intf.
|
||||
intf: string, interface name"""
|
||||
self.cmd( 'ip route flush root 0/0' )
|
||||
return self.cmd( 'route add default ' + intf )
|
||||
|
||||
def defaultIntf( self ):
|
||||
"Return interface for lowest port"
|
||||
ports = self.intfs.keys()
|
||||
if ports:
|
||||
return self.intfs[ min( ports ) ]
|
||||
|
||||
_ipMatchRegex = re.compile( r'\d+\.\d+\.\d+\.\d+' )
|
||||
_macMatchRegex = re.compile( r'..:..:..:..:..:..' )
|
||||
|
||||
def IP( self, intf=None ):
|
||||
"Return IP address of a node or specific interface."
|
||||
if intf is None:
|
||||
intf = self.defaultIntf()
|
||||
if intf and not self.waiting:
|
||||
self.updateIP( intf )
|
||||
return self.ips.get( intf, None )
|
||||
|
||||
def MAC( self, intf=None ):
|
||||
"Return MAC address of a node or specific interface."
|
||||
if intf is None:
|
||||
intf = self.defaultIntf()
|
||||
if intf and not self.waiting:
|
||||
self.updateMAC( intf )
|
||||
return self.macs.get( intf, None )
|
||||
|
||||
def updateIP( self, intf ):
|
||||
"Update IP address for an interface"
|
||||
assert not self.waiting
|
||||
ifconfig = self.cmd( 'ifconfig ' + intf )
|
||||
ips = self._ipMatchRegex.findall( ifconfig )
|
||||
if ips:
|
||||
self.ips[ intf ] = ips[ 0 ]
|
||||
else:
|
||||
self.ips[ intf ] = None
|
||||
|
||||
def updateMAC( self, intf ):
|
||||
"Update MAC address for an interface"
|
||||
assert not self.waiting
|
||||
ifconfig = self.cmd( 'ifconfig ' + intf )
|
||||
macs = self._macMatchRegex.findall( ifconfig )
|
||||
if macs:
|
||||
self.macs[ intf ] = macs[ 0 ]
|
||||
else:
|
||||
self.macs[ intf ] = None
|
||||
|
||||
def intfIsUp( self, intf ):
|
||||
"Check if an interface is up."
|
||||
return 'UP' in self.cmd( 'ifconfig ' + intf )
|
||||
|
||||
# Other methods
|
||||
def __str__( self ):
|
||||
intfs = sorted( self.intfs.values() )
|
||||
return '%s: IP=%s intfs=%s pid=%s' % (
|
||||
self.name, self.IP(), ','.join( intfs ), self.pid )
|
||||
|
||||
|
||||
class Host( Node ):
|
||||
"A host is simply a Node."
|
||||
|
||||
|
||||
class Switch( Node ):
|
||||
"""A Switch is a Node that is running (or has execed?)
|
||||
an OpenFlow switch."""
|
||||
|
||||
portBase = SWITCH_PORT_BASE # 0 for OF < 1.0, 1 for OF >= 1.0
|
||||
|
||||
def __init__( self, name, opts='', listenPort=None, **kwargs):
|
||||
Node.__init__( self, name, **kwargs )
|
||||
self.opts = opts
|
||||
self.listenPort = listenPort
|
||||
if self.listenPort:
|
||||
self.opts += ' --listen=ptcp:%i ' % self.listenPort
|
||||
|
||||
def defaultIntf( self ):
|
||||
"Return interface for HIGHEST port"
|
||||
ports = self.intfs.keys()
|
||||
if ports:
|
||||
intf = self.intfs[ max( ports ) ]
|
||||
return intf
|
||||
|
||||
def sendCmd( self, *cmd, **kwargs ):
|
||||
"""Send command to Node.
|
||||
cmd: string"""
|
||||
kwargs.setdefault( 'printPid', False )
|
||||
if not self.execed:
|
||||
return Node.sendCmd( self, *cmd, **kwargs )
|
||||
else:
|
||||
error( '*** Error: %s has execed and cannot accept commands' %
|
||||
self.name )
|
||||
|
||||
class UserSwitch( Switch ):
|
||||
"User-space switch."
|
||||
|
||||
def __init__( self, name, **kwargs ):
|
||||
"""Init.
|
||||
name: name for the switch"""
|
||||
Switch.__init__( self, name, **kwargs )
|
||||
pathCheck( 'ofdatapath', 'ofprotocol',
|
||||
moduleName='the OpenFlow reference user switch (openflow.org)' )
|
||||
|
||||
@staticmethod
|
||||
def setup():
|
||||
"Ensure any dependencies are loaded; if not, try to load them."
|
||||
if not os.path.exists( '/dev/net/tun' ):
|
||||
moduleDeps( add=TUN )
|
||||
|
||||
def start( self, controllers ):
|
||||
"""Start OpenFlow reference user datapath.
|
||||
Log to /tmp/sN-{ofd,ofp}.log.
|
||||
controllers: list of controller objects"""
|
||||
controller = controllers[ 0 ]
|
||||
ofdlog = '/tmp/' + self.name + '-ofd.log'
|
||||
ofplog = '/tmp/' + self.name + '-ofp.log'
|
||||
self.cmd( 'ifconfig lo up' )
|
||||
mac_str = ''
|
||||
if self.defaultMAC:
|
||||
# ofdatapath expects a string of hex digits with no colons.
|
||||
mac_str = ' -d ' + ''.join( self.defaultMAC.split( ':' ) )
|
||||
intfs = sorted( self.intfs.values() )
|
||||
if self.inNamespace:
|
||||
intfs = intfs[ :-1 ]
|
||||
self.cmd( 'ofdatapath -i ' + ','.join( intfs ) +
|
||||
' punix:/tmp/' + self.name + mac_str + ' --no-slicing ' +
|
||||
' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' )
|
||||
self.cmd( 'ofprotocol unix:/tmp/' + self.name +
|
||||
' tcp:%s:%d' % ( controller.IP(), controller.port ) +
|
||||
' --fail=closed ' + self.opts +
|
||||
' 1> ' + ofplog + ' 2>' + ofplog + ' &' )
|
||||
|
||||
def stop( self ):
|
||||
"Stop OpenFlow reference user datapath."
|
||||
self.cmd( 'kill %ofdatapath' )
|
||||
self.cmd( 'kill %ofprotocol' )
|
||||
self.deleteIntfs()
|
||||
|
||||
class KernelSwitch( Switch ):
|
||||
"""Kernel-space switch.
|
||||
Currently only works in root namespace."""
|
||||
|
||||
def __init__( self, name, dp=None, **kwargs ):
|
||||
"""Init.
|
||||
name: name for switch
|
||||
dp: netlink id (0, 1, 2, ...)
|
||||
defaultMAC: default MAC as string; random value if None"""
|
||||
Switch.__init__( self, name, **kwargs )
|
||||
self.dp = 'nl:%i' % dp
|
||||
self.intf = 'of%i' % dp
|
||||
if self.inNamespace:
|
||||
error( "KernelSwitch currently only works"
|
||||
" in the root namespace." )
|
||||
exit( 1 )
|
||||
|
||||
@staticmethod
|
||||
def setup():
|
||||
"Ensure any dependencies are loaded; if not, try to load them."
|
||||
pathCheck( 'ofprotocol',
|
||||
moduleName='the OpenFlow reference kernel switch'
|
||||
' (openflow.org) (NOTE: not available in OpenFlow 1.0!)' )
|
||||
moduleDeps( subtract=OVS_KMOD, add=OF_KMOD )
|
||||
|
||||
def start( self, controllers ):
|
||||
"Start up reference kernel datapath."
|
||||
ofplog = '/tmp/' + self.name + '-ofp.log'
|
||||
quietRun( 'ifconfig lo up' )
|
||||
# Delete local datapath if it exists;
|
||||
# then create a new one monitoring the given interfaces
|
||||
quietRun( 'dpctl deldp ' + self.dp )
|
||||
self.cmd( 'dpctl adddp ' + self.dp )
|
||||
if self.defaultMAC:
|
||||
self.cmd( 'ifconfig', self.intf, 'hw', 'ether', self.defaultMAC )
|
||||
ports = sorted( self.ports.values() )
|
||||
if len( ports ) != ports[ -1 ] + 1 - self.portBase:
|
||||
raise Exception( 'only contiguous, zero-indexed port ranges'
|
||||
'supported: %s' % ports )
|
||||
intfs = [ self.intfs[ port ] for port in ports ]
|
||||
self.cmd( 'dpctl', 'addif', self.dp, ' '.join( intfs ) )
|
||||
# Run protocol daemon
|
||||
controller = controllers[ 0 ]
|
||||
self.cmd( 'ofprotocol ' + self.dp +
|
||||
' tcp:%s:%d' % ( controller.IP(), controller.port ) +
|
||||
' --fail=closed ' + self.opts +
|
||||
' 1> ' + ofplog + ' 2>' + ofplog + ' &' )
|
||||
self.execed = False
|
||||
|
||||
def stop( self ):
|
||||
"Terminate kernel datapath."
|
||||
quietRun( 'dpctl deldp ' + self.dp )
|
||||
self.cmd( 'kill %ofprotocol' )
|
||||
self.deleteIntfs()
|
||||
|
||||
|
||||
class OVSKernelSwitch( Switch ):
|
||||
"""Open VSwitch kernel-space switch.
|
||||
Currently only works in the root namespace."""
|
||||
|
||||
def __init__( self, name, dp=None, **kwargs ):
|
||||
"""Init.
|
||||
name: name for switch
|
||||
dp: netlink id (0, 1, 2, ...)
|
||||
defaultMAC: default MAC as unsigned int; random value if None"""
|
||||
Switch.__init__( self, name, **kwargs )
|
||||
self.dp = 'dp%i' % dp
|
||||
self.intf = self.dp
|
||||
if self.inNamespace:
|
||||
error( "OVSKernelSwitch currently only works"
|
||||
" in the root namespace.\n" )
|
||||
exit( 1 )
|
||||
|
||||
@staticmethod
|
||||
def setup():
|
||||
"Ensure any dependencies are loaded; if not, try to load them."
|
||||
pathCheck( 'ovs-dpctl', 'ovs-openflowd',
|
||||
moduleName='Open vSwitch (openvswitch.org)')
|
||||
moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
|
||||
|
||||
def start( self, controllers ):
|
||||
"Start up kernel datapath."
|
||||
ofplog = '/tmp/' + self.name + '-ofp.log'
|
||||
quietRun( 'ifconfig lo up' )
|
||||
# Delete local datapath if it exists;
|
||||
# then create a new one monitoring the given interfaces
|
||||
quietRun( 'ovs-dpctl del-dp ' + self.dp )
|
||||
self.cmd( 'ovs-dpctl add-dp ' + self.dp )
|
||||
mac_str = ''
|
||||
if self.defaultMAC:
|
||||
# ovs-openflowd expects a string of exactly 16 hex digits with no
|
||||
# colons.
|
||||
mac_str = ' --datapath-id=0000' + \
|
||||
''.join( self.defaultMAC.split( ':' ) ) + ' '
|
||||
ports = sorted( self.ports.values() )
|
||||
if len( ports ) != ports[ -1 ] + 1 - self.portBase:
|
||||
raise Exception( 'only contiguous, one-indexed port ranges '
|
||||
'supported: %s' % self.intfs )
|
||||
intfs = [ self.intfs[ port ] for port in ports ]
|
||||
self.cmd( 'ovs-dpctl', 'add-if', self.dp, ' '.join( intfs ) )
|
||||
# Run protocol daemon
|
||||
controller = controllers[ 0 ]
|
||||
self.cmd( 'ovs-openflowd ' + self.dp +
|
||||
' tcp:%s:%d' % ( controller.IP(), controller.port ) +
|
||||
' --fail=secure ' + self.opts + mac_str +
|
||||
' 1>' + ofplog + ' 2>' + ofplog + '&' )
|
||||
self.execed = False
|
||||
|
||||
def stop( self ):
|
||||
"Terminate kernel datapath."
|
||||
quietRun( 'ovs-dpctl del-dp ' + self.dp )
|
||||
self.cmd( 'kill %ovs-openflowd' )
|
||||
self.deleteIntfs()
|
||||
|
||||
|
||||
class Controller( Node ):
|
||||
"""A Controller is a Node that is running (or has execed?) an
|
||||
OpenFlow controller."""
|
||||
|
||||
def __init__( self, name, inNamespace=False, command='controller',
|
||||
cargs='-v ptcp:%d', cdir=None, defaultIP="127.0.0.1",
|
||||
port=6633 ):
|
||||
self.command = command
|
||||
self.cargs = cargs
|
||||
self.cdir = cdir
|
||||
self.port = port
|
||||
Node.__init__( self, name, inNamespace=inNamespace,
|
||||
defaultIP=defaultIP )
|
||||
|
||||
def start( self ):
|
||||
"""Start <controller> <args> on controller.
|
||||
Log to /tmp/cN.log"""
|
||||
pathCheck( self.command )
|
||||
cout = '/tmp/' + self.name + '.log'
|
||||
if self.cdir is not None:
|
||||
self.cmd( 'cd ' + self.cdir )
|
||||
self.cmd( self.command + ' ' + self.cargs % self.port +
|
||||
' 1>' + cout + ' 2>' + cout + '&' )
|
||||
self.execed = False
|
||||
|
||||
def stop( self ):
|
||||
"Stop controller."
|
||||
self.cmd( 'kill %' + self.command )
|
||||
self.terminate()
|
||||
|
||||
def IP( self, intf=None ):
|
||||
"Return IP address of the Controller"
|
||||
ip = Node.IP( self, intf=intf )
|
||||
if ip is None:
|
||||
ip = self.defaultIP
|
||||
return ip
|
||||
|
||||
class ControllerParams( object ):
|
||||
"Container for controller IP parameters."
|
||||
|
||||
def __init__( self, ip, prefixLen ):
|
||||
"""Init.
|
||||
ip: string, controller IP address
|
||||
prefixLen: prefix length, e.g. 8 for /8, covering 16M"""
|
||||
self.ip = ip
|
||||
self.prefixLen = prefixLen
|
||||
|
||||
|
||||
class NOX( Controller ):
|
||||
"Controller to run a NOX application."
|
||||
|
||||
def __init__( self, name, noxArgs=None, **kwargs ):
|
||||
"""Init.
|
||||
name: name to give controller
|
||||
noxArgs: list of args, or single arg, to pass to NOX"""
|
||||
if not noxArgs:
|
||||
noxArgs = [ 'packetdump' ]
|
||||
elif type( noxArgs ) != list:
|
||||
noxArgs = [ noxArgs ]
|
||||
|
||||
if 'NOX_CORE_DIR' not in os.environ:
|
||||
exit( 'exiting; please set missing NOX_CORE_DIR env var' )
|
||||
noxCoreDir = os.environ[ 'NOX_CORE_DIR' ]
|
||||
|
||||
Controller.__init__( self, name,
|
||||
command=noxCoreDir + '/nox_core',
|
||||
cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' +
|
||||
' '.join( noxArgs ),
|
||||
cdir=noxCoreDir, **kwargs )
|
||||
|
||||
|
||||
class RemoteController( Controller ):
|
||||
"Controller running outside of Mininet's control."
|
||||
|
||||
def __init__( self, name, defaultIP='127.0.0.1',
|
||||
port=6633, **kwargs):
|
||||
"""Init.
|
||||
name: name to give controller
|
||||
defaultIP: the IP address where the remote controller is
|
||||
listening
|
||||
port: the port where the remote controller is listening"""
|
||||
Controller.__init__( self, name, defaultIP=defaultIP, port=port,
|
||||
**kwargs )
|
||||
|
||||
def start( self ):
|
||||
"Overridden to do nothing."
|
||||
return
|
||||
|
||||
def stop( self ):
|
||||
"Overridden to do nothing."
|
||||
return
|
||||
@@ -0,0 +1,60 @@
|
||||
"""
|
||||
Terminal creation and cleanup.
|
||||
Utility functions to run a term (connected via screen(1)) on each host.
|
||||
|
||||
Requires GNU screen(1) and xterm(1).
|
||||
Optionally uses gnome-terminal.
|
||||
"""
|
||||
|
||||
import re
|
||||
from subprocess import Popen
|
||||
|
||||
from mininet.log import error
|
||||
from mininet.util import quietRun
|
||||
|
||||
def quoteArg( arg ):
|
||||
"Quote an argument if it contains spaces."
|
||||
return repr( arg ) if ' ' in arg else arg
|
||||
|
||||
def makeTerm( node, title='Node', term='xterm' ):
|
||||
"""Run screen on a node, and hook up a terminal.
|
||||
node: Node object
|
||||
title: base title
|
||||
term: 'xterm' or 'gterm'
|
||||
returns: process created"""
|
||||
title += ': ' + node.name
|
||||
if not node.inNamespace:
|
||||
title += ' (root)'
|
||||
cmds = {
|
||||
'xterm': [ 'xterm', '-title', title, '-e' ],
|
||||
'gterm': [ 'gnome-terminal', '--title', title, '-e' ]
|
||||
}
|
||||
if term not in cmds:
|
||||
error( 'invalid terminal type: %s' % term )
|
||||
return
|
||||
if not node.execed:
|
||||
node.cmd( 'screen -dmS ' + 'mininet.' + node.name)
|
||||
args = [ 'screen', '-D', '-RR', '-S', 'mininet.' + node.name ]
|
||||
else:
|
||||
args = [ 'sh', '-c', 'exec tail -f /tmp/' + node.name + '*.log' ]
|
||||
if term == 'gterm':
|
||||
# Compress these for gnome-terminal, which expects one token
|
||||
# to follow the -e option
|
||||
args = [ ' '.join( [ quoteArg( arg ) for arg in args ] ) ]
|
||||
return Popen( cmds[ term ] + args )
|
||||
|
||||
def cleanUpScreens():
|
||||
"Remove moldy old screen sessions."
|
||||
r = r'(\d+\.mininet\.[hsc]\d+)'
|
||||
output = quietRun( 'screen -ls' ).split( '\n' )
|
||||
for line in output:
|
||||
m = re.search( r, line )
|
||||
if m:
|
||||
quietRun( 'screen -S ' + m.group( 1 ) + ' -X quit' )
|
||||
|
||||
def makeTerms( nodes, title='Node', term='xterm' ):
|
||||
"""Create terminals.
|
||||
nodes: list of Node objects
|
||||
title: base title for each
|
||||
returns: list of created terminal processes"""
|
||||
return [ makeTerm( node, title, term ) for node in nodes ]
|
||||
Executable
+61
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""Package: mininet
|
||||
Test creation and all-pairs ping for each included mininet topo type."""
|
||||
|
||||
import unittest
|
||||
|
||||
from mininet.net import init, Mininet
|
||||
from mininet.node import Host, Controller, ControllerParams
|
||||
# from mininet.node import KernelSwitch
|
||||
from mininet.node import UserSwitch, OVSKernelSwitch
|
||||
from mininet.topo import SingleSwitchTopo, LinearTopo
|
||||
from mininet.log import setLogLevel
|
||||
|
||||
SWITCHES = { 'user': UserSwitch,
|
||||
'ovsk': OVSKernelSwitch,
|
||||
# 'kernel': KernelSwitch
|
||||
}
|
||||
|
||||
|
||||
class testSingleSwitch( unittest.TestCase ):
|
||||
"For each datapath type, test ping with single switch topologies."
|
||||
|
||||
def testMinimal( self ):
|
||||
"Ping test with both datapaths on minimal topology"
|
||||
init()
|
||||
for switch in SWITCHES.values():
|
||||
controllerParams = ControllerParams( '10.0.0.0', 8 )
|
||||
mn = Mininet( SingleSwitchTopo(), switch, Host, Controller,
|
||||
controllerParams )
|
||||
dropped = mn.run( mn.ping )
|
||||
self.assertEqual( dropped, 0 )
|
||||
|
||||
def testSingle5( self ):
|
||||
"Ping test with both datapaths on 5-host single-switch topology"
|
||||
init()
|
||||
for switch in SWITCHES.values():
|
||||
controllerParams = ControllerParams( '10.0.0.0', 8 )
|
||||
mn = Mininet( SingleSwitchTopo( k=5 ), switch, Host, Controller,
|
||||
controllerParams )
|
||||
dropped = mn.run( mn.ping )
|
||||
self.assertEqual( dropped, 0 )
|
||||
|
||||
|
||||
class testLinear( unittest.TestCase ):
|
||||
"For each datapath type, test all-pairs ping with LinearNet."
|
||||
|
||||
def testLinear5( self ):
|
||||
"Ping test with both datapaths on a 5-switch topology"
|
||||
init()
|
||||
for switch in SWITCHES.values():
|
||||
controllerParams = ControllerParams( '10.0.0.0', 8 )
|
||||
mn = Mininet( LinearTopo( k=5 ), switch, Host, Controller,
|
||||
controllerParams )
|
||||
dropped = mn.run( mn.ping )
|
||||
self.assertEqual( dropped, 0 )
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel('warning')
|
||||
unittest.main()
|
||||
+405
@@ -0,0 +1,405 @@
|
||||
#!/usr/bin/env python
|
||||
'''@package topo
|
||||
|
||||
Network topology creation.
|
||||
|
||||
@author Brandon Heller (brandonh@stanford.edu)
|
||||
|
||||
This package includes code to represent network topologies.
|
||||
|
||||
A Topo object can be a topology database for NOX, can represent a physical
|
||||
setup for testing, and can even be emulated with the Mininet package.
|
||||
'''
|
||||
|
||||
# BL: we may have to fix compatibility here.
|
||||
# networkx is also a fairly heavyweight dependency
|
||||
# from networkx.classes.graph import Graph
|
||||
|
||||
from networkx import Graph
|
||||
from mininet.node import SWITCH_PORT_BASE
|
||||
|
||||
class NodeID(object):
|
||||
'''Topo node identifier.'''
|
||||
|
||||
def __init__(self, dpid = None):
|
||||
'''Init.
|
||||
|
||||
@param dpid dpid
|
||||
'''
|
||||
# DPID-compatible hashable identifier: opaque 64-bit unsigned int
|
||||
self.dpid = dpid
|
||||
|
||||
def __str__(self):
|
||||
'''String conversion.
|
||||
|
||||
@return str dpid as string
|
||||
'''
|
||||
return str(self.dpid)
|
||||
|
||||
def name_str(self):
|
||||
'''Name conversion.
|
||||
|
||||
@return name name as string
|
||||
'''
|
||||
return str(self.dpid)
|
||||
|
||||
def ip_str(self):
|
||||
'''Name conversion.
|
||||
|
||||
@return ip ip as string
|
||||
'''
|
||||
hi = (self.dpid & 0xff0000) >> 16
|
||||
mid = (self.dpid & 0xff00) >> 8
|
||||
lo = self.dpid & 0xff
|
||||
return "10.%i.%i.%i" % (hi, mid, lo)
|
||||
|
||||
|
||||
class Node(object):
|
||||
'''Node-specific vertex metadata for a Topo object.'''
|
||||
|
||||
def __init__(self, connected = False, admin_on = True,
|
||||
power_on = True, fault = False, is_switch = True):
|
||||
'''Init.
|
||||
|
||||
@param connected actively connected to controller
|
||||
@param admin_on administratively on or off
|
||||
@param power_on powered on or off
|
||||
@param fault fault seen on node
|
||||
@param is_switch switch or host
|
||||
'''
|
||||
self.connected = connected
|
||||
self.admin_on = admin_on
|
||||
self.power_on = power_on
|
||||
self.fault = fault
|
||||
self.is_switch = is_switch
|
||||
|
||||
|
||||
class Edge(object):
|
||||
'''Edge-specific metadata for a StructuredTopo graph.'''
|
||||
|
||||
def __init__(self, admin_on = True, power_on = True, fault = False):
|
||||
'''Init.
|
||||
|
||||
@param admin_on administratively on or off; defaults to True
|
||||
@param power_on powered on or off; defaults to True
|
||||
@param fault fault seen on edge; defaults to False
|
||||
'''
|
||||
self.admin_on = admin_on
|
||||
self.power_on = power_on
|
||||
self.fault = fault
|
||||
|
||||
|
||||
class Topo(object):
|
||||
'''Data center network representation for structured multi-trees.'''
|
||||
|
||||
def __init__(self):
|
||||
'''Create Topo object.
|
||||
|
||||
'''
|
||||
self.g = Graph()
|
||||
self.node_info = {} # dpids hash to Node objects
|
||||
self.edge_info = {} # (src_dpid, dst_dpid) tuples hash to Edge objects
|
||||
self.ports = {} # ports[src][dst] is port on src that connects to dst
|
||||
self.id_gen = NodeID # class used to generate dpid
|
||||
|
||||
def add_node(self, dpid, node):
|
||||
'''Add Node to graph.
|
||||
|
||||
@param dpid dpid
|
||||
@param node Node object
|
||||
'''
|
||||
self.g.add_node(dpid)
|
||||
self.node_info[dpid] = node
|
||||
|
||||
def add_edge(self, src, dst, edge = None):
|
||||
'''Add edge (Node, Node) to graph.
|
||||
|
||||
@param src src dpid
|
||||
@param dst dst dpid
|
||||
@param edge Edge object
|
||||
'''
|
||||
src, dst = tuple(sorted([src, dst]))
|
||||
self.g.add_edge(src, dst)
|
||||
if not edge:
|
||||
edge = Edge()
|
||||
self.edge_info[(src, dst)] = edge
|
||||
self.add_port(src, dst)
|
||||
|
||||
def add_port(self, src, dst):
|
||||
'''Generate port mapping for new edge.
|
||||
|
||||
@param src source switch DPID
|
||||
@param dst destination switch DPID
|
||||
'''
|
||||
src_base = SWITCH_PORT_BASE if self.is_switch(src) else 0
|
||||
dst_base = SWITCH_PORT_BASE if self.is_switch(dst) else 0
|
||||
if src not in self.ports:
|
||||
self.ports[src] = {}
|
||||
if dst not in self.ports[src]:
|
||||
# num outlinks
|
||||
self.ports[src][dst] = len(self.ports[src]) + src_base
|
||||
if dst not in self.ports:
|
||||
self.ports[dst] = {}
|
||||
if src not in self.ports[dst]:
|
||||
# num outlinks
|
||||
self.ports[dst][src] = len(self.ports[dst]) + dst_base
|
||||
|
||||
def node_enabled(self, dpid):
|
||||
'''Is node connected, admin on, powered on, and fault-free?
|
||||
|
||||
@param dpid dpid
|
||||
|
||||
@return bool node is enabled
|
||||
'''
|
||||
ni = self.node_info[dpid]
|
||||
return ni.connected and ni.admin_on and ni.power_on and not ni.fault
|
||||
|
||||
def nodes_enabled(self, dpids, enabled = True):
|
||||
'''Return subset of enabled nodes
|
||||
|
||||
@param dpids list of dpids
|
||||
@param enabled only return enabled nodes?
|
||||
|
||||
@return dpids filtered list of dpids
|
||||
'''
|
||||
if enabled:
|
||||
return [n for n in dpids if self.node_enabled(n)]
|
||||
else:
|
||||
return dpids
|
||||
|
||||
def nodes(self, enabled = True):
|
||||
'''Return graph nodes.
|
||||
|
||||
@param enabled only return enabled nodes?
|
||||
|
||||
@return dpids list of dpids
|
||||
'''
|
||||
return self.nodes_enabled(self.g.nodes(), enabled)
|
||||
|
||||
def nodes_str(self, dpids):
|
||||
'''Return string of custom-encoded nodes.
|
||||
|
||||
@param dpids list of dpids
|
||||
|
||||
@return str string
|
||||
'''
|
||||
return [str(self.id_gen(dpid = dpid)) for dpid in dpids]
|
||||
|
||||
def is_switch(self, n):
|
||||
'''Returns true if node is a switch.'''
|
||||
return self.node_info[n].is_switch
|
||||
|
||||
def switches(self, enabled = True):
|
||||
'''Return switches.
|
||||
|
||||
@param enabled only return enabled nodes?
|
||||
|
||||
@return dpids list of dpids
|
||||
'''
|
||||
nodes = [n for n in self.g.nodes() if self.is_switch(n)]
|
||||
return self.nodes_enabled(nodes, enabled)
|
||||
|
||||
def hosts(self, enabled = True):
|
||||
'''Return hosts.
|
||||
|
||||
@param enabled only return enabled nodes?
|
||||
|
||||
@return dpids list of dpids
|
||||
'''
|
||||
|
||||
def is_host(n):
|
||||
'''Returns true if node is a host.'''
|
||||
return not self.node_info[n].is_switch
|
||||
|
||||
nodes = [n for n in self.g.nodes() if is_host(n)]
|
||||
return self.nodes_enabled(nodes, enabled)
|
||||
|
||||
def edge_enabled(self, edge):
|
||||
'''Is edge admin on, powered on, and fault-free?
|
||||
|
||||
@param edge (src, dst) dpid tuple
|
||||
|
||||
@return bool edge is enabled
|
||||
'''
|
||||
src, dst = edge
|
||||
src, dst = tuple(sorted([src, dst]))
|
||||
ei = self.edge_info[tuple(sorted([src, dst]))]
|
||||
return ei.admin_on and ei.power_on and not ei.fault
|
||||
|
||||
def edges_enabled(self, edges, enabled = True):
|
||||
'''Return subset of enabled edges
|
||||
|
||||
@param edges list of edges
|
||||
@param enabled only return enabled edges?
|
||||
|
||||
@return edges filtered list of edges
|
||||
'''
|
||||
if enabled:
|
||||
return [e for e in edges if self.edge_enabled(e)]
|
||||
else:
|
||||
return edges
|
||||
|
||||
def edges(self, enabled = True):
|
||||
'''Return edges.
|
||||
|
||||
@param enabled only return enabled edges?
|
||||
|
||||
@return edges list of dpid pairs
|
||||
'''
|
||||
return self.edges_enabled(self.g.edges(), enabled)
|
||||
|
||||
def edges_str(self, dpid_pairs):
|
||||
'''Return string of custom-encoded node pairs.
|
||||
|
||||
@param dpid_pairs list of dpid pairs (src, dst)
|
||||
|
||||
@return str string
|
||||
'''
|
||||
edges = []
|
||||
for pair in dpid_pairs:
|
||||
src, dst = pair
|
||||
src = str(self.id_gen(dpid = src))
|
||||
dst = str(self.id_gen(dpid = dst))
|
||||
edges.append((src, dst))
|
||||
return edges
|
||||
|
||||
def port(self, src, dst):
|
||||
'''Get port number.
|
||||
|
||||
@param src source switch DPID
|
||||
@param dst destination switch DPID
|
||||
@return tuple (src_port, dst_port):
|
||||
src_port: port on source switch leading to the destination switch
|
||||
dst_port: port on destination switch leading to the source switch
|
||||
'''
|
||||
if src in self.ports and dst in self.ports[src]:
|
||||
assert dst in self.ports and src in self.ports[dst]
|
||||
return (self.ports[src][dst], self.ports[dst][src])
|
||||
|
||||
def enable_edges(self):
|
||||
'''Enable all edges in the network graph.
|
||||
|
||||
Set admin on, power on, and fault off.
|
||||
'''
|
||||
for e in self.g.edges():
|
||||
src, dst = e
|
||||
ei = self.edge_info[tuple(sorted([src, dst]))]
|
||||
ei.admin_on = True
|
||||
ei.power_on = True
|
||||
ei.fault = False
|
||||
|
||||
def enable_nodes(self):
|
||||
'''Enable all nodes in the network graph.
|
||||
|
||||
Set connected on, admin on, power on, and fault off.
|
||||
'''
|
||||
for node in self.g.nodes():
|
||||
ni = self.node_info[node]
|
||||
ni.connected = True
|
||||
ni.admin_on = True
|
||||
ni.power_on = True
|
||||
ni.fault = False
|
||||
|
||||
def enable_all(self):
|
||||
'''Enable all nodes and edges in the network graph.'''
|
||||
self.enable_nodes()
|
||||
self.enable_edges()
|
||||
|
||||
def name(self, dpid):
|
||||
'''Get string name of node ID.
|
||||
|
||||
@param dpid DPID of host or switch
|
||||
@return name_str string name with no dashes
|
||||
'''
|
||||
return self.id_gen(dpid = dpid).name_str()
|
||||
|
||||
def ip(self, dpid):
|
||||
'''Get IP dotted-decimal string of node ID.
|
||||
|
||||
@param dpid DPID of host or switch
|
||||
@return ip_str
|
||||
'''
|
||||
return self.id_gen(dpid = dpid).ip_str()
|
||||
|
||||
|
||||
class SingleSwitchTopo(Topo):
|
||||
'''Single switch connected to k hosts.'''
|
||||
|
||||
def __init__(self, k = 2, enable_all = True):
|
||||
'''Init.
|
||||
|
||||
@param k number of hosts
|
||||
@param enable_all enables all nodes and switches?
|
||||
'''
|
||||
super(SingleSwitchTopo, self).__init__()
|
||||
|
||||
self.k = k
|
||||
|
||||
self.add_node(1, Node())
|
||||
hosts = range(2, k + 2)
|
||||
for h in hosts:
|
||||
self.add_node(h, Node(is_switch = False))
|
||||
self.add_edge(h, 1, Edge())
|
||||
|
||||
if enable_all:
|
||||
self.enable_all()
|
||||
|
||||
|
||||
class SingleSwitchReversedTopo(SingleSwitchTopo):
|
||||
'''Single switch connected to k hosts, with reversed ports.
|
||||
|
||||
The lowest-numbered host is connected to the highest-numbered port.
|
||||
|
||||
Useful to verify that Mininet properly handles custom port numberings.
|
||||
'''
|
||||
|
||||
def port(self, src, dst):
|
||||
'''Get port number.
|
||||
|
||||
@param src source switch DPID
|
||||
@param dst destination switch DPID
|
||||
@return tuple (src_port, dst_port):
|
||||
src_port: port on source switch leading to the destination switch
|
||||
dst_port: port on destination switch leading to the source switch
|
||||
'''
|
||||
if src == 1:
|
||||
if dst in range(2, self.k + 2):
|
||||
dst_index = dst - 2
|
||||
highest = self.k - 1
|
||||
return (highest - dst_index, 0)
|
||||
else:
|
||||
raise Exception('unexpected dst: %i' % dst)
|
||||
elif src in range(2, self.k + 2):
|
||||
if dst == 1:
|
||||
raise Exception('unexpected dst: %i' % dst)
|
||||
else:
|
||||
src_index = src - 2
|
||||
highest = self.k - 1
|
||||
return (0, highest - src_index)
|
||||
|
||||
|
||||
class LinearTopo(Topo):
|
||||
'''Linear topology of k switches, with one host per switch.'''
|
||||
|
||||
def __init__(self, k = 2, enable_all = True):
|
||||
'''Init.
|
||||
|
||||
@param k number of switches (and hosts too)
|
||||
@param enable_all enables all nodes and switches?
|
||||
'''
|
||||
super(LinearTopo, self).__init__()
|
||||
|
||||
self.k = k
|
||||
|
||||
switches = range(1, k + 1)
|
||||
for s in switches:
|
||||
h = s + k
|
||||
self.add_node(s, Node())
|
||||
self.add_node(h, Node(is_switch = False))
|
||||
self.add_edge(s, h, Edge())
|
||||
for s in switches:
|
||||
if s != k:
|
||||
self.add_edge(s, s + 1, Edge())
|
||||
|
||||
if enable_all:
|
||||
self.enable_all()
|
||||
@@ -0,0 +1,45 @@
|
||||
"Library of potentially useful topologies for Mininet"
|
||||
|
||||
from mininet.topo import Topo, Node
|
||||
from mininet.net import Mininet
|
||||
|
||||
class TreeTopo( Topo ):
|
||||
"Topology for a tree network with a given depth and fanout."
|
||||
|
||||
def __init__( self, depth=1, fanout=2 ):
|
||||
super( TreeTopo, self ).__init__()
|
||||
# Numbering: h1..N, sN+1..M
|
||||
hostCount = fanout ** depth
|
||||
self.hostNum = 1
|
||||
self.switchNum = hostCount + 1
|
||||
# Build topology
|
||||
self.addTree( depth, fanout )
|
||||
# Consider all switches and hosts 'on'
|
||||
self.enable_all()
|
||||
|
||||
# It is OK that i is "unused" in the for loop.
|
||||
# pylint: disable-msg=W0612
|
||||
|
||||
def addTree( self, depth, fanout ):
|
||||
"""Add a subtree starting with node n.
|
||||
returns: last node added"""
|
||||
isSwitch = depth > 0
|
||||
if isSwitch:
|
||||
num = self.switchNum
|
||||
self.switchNum += 1
|
||||
else:
|
||||
num = self.hostNum
|
||||
self.hostNum += 1
|
||||
self.add_node( num, Node( is_switch=isSwitch ) )
|
||||
if isSwitch:
|
||||
for i in range( 0, fanout ):
|
||||
child = self.addTree( depth - 1, fanout )
|
||||
self.add_edge( num, child )
|
||||
return num
|
||||
|
||||
# pylint: enable-msg=W0612
|
||||
|
||||
def TreeNet( depth=1, fanout=2, **kwargs ):
|
||||
"Convenience function for creating tree networks."
|
||||
topo = TreeTopo( depth, fanout )
|
||||
return Mininet( topo, **kwargs )
|
||||
+209
@@ -0,0 +1,209 @@
|
||||
"Utility functions for Mininet."
|
||||
|
||||
from time import sleep
|
||||
from resource import setrlimit, RLIMIT_NPROC, RLIMIT_NOFILE
|
||||
import select
|
||||
from subprocess import call, check_call, Popen, PIPE, STDOUT
|
||||
|
||||
from mininet.log import error
|
||||
|
||||
# Command execution support
|
||||
|
||||
def run( cmd ):
|
||||
"""Simple interface to subprocess.call()
|
||||
cmd: list of command params"""
|
||||
return call( cmd.split( ' ' ) )
|
||||
|
||||
def checkRun( cmd ):
|
||||
"""Simple interface to subprocess.check_call()
|
||||
cmd: list of command params"""
|
||||
return check_call( cmd.split( ' ' ) )
|
||||
|
||||
# pylint doesn't understand explicit type checking
|
||||
# pylint: disable-msg=E1103
|
||||
|
||||
def quietRun( *cmd ):
|
||||
"""Run a command, routing stderr to stdout, and return the output.
|
||||
cmd: list of command params"""
|
||||
if len( cmd ) == 1:
|
||||
cmd = cmd[ 0 ]
|
||||
if isinstance( cmd, str ):
|
||||
cmd = cmd.split( ' ' )
|
||||
popen = Popen( cmd, stdout=PIPE, stderr=STDOUT )
|
||||
# We can't use Popen.communicate() because it uses
|
||||
# select(), which can't handle
|
||||
# high file descriptor numbers! poll() can, however.
|
||||
output = ''
|
||||
readable = select.poll()
|
||||
readable.register( popen.stdout )
|
||||
while True:
|
||||
while readable.poll():
|
||||
data = popen.stdout.read( 1024 )
|
||||
if len( data ) == 0:
|
||||
break
|
||||
output += data
|
||||
popen.poll()
|
||||
if popen.returncode != None:
|
||||
break
|
||||
return output
|
||||
|
||||
# pylint: enable-msg=E1103
|
||||
# pylint: disable-msg=E1101,W0612
|
||||
|
||||
def isShellBuiltin( cmd ):
|
||||
"Return True if cmd is a bash builtin."
|
||||
if isShellBuiltin.builtIns is None:
|
||||
isShellBuiltin.builtIns = quietRun( 'bash -c enable' )
|
||||
space = cmd.find( ' ' )
|
||||
if space > 0:
|
||||
cmd = cmd[ :space]
|
||||
return cmd in isShellBuiltin.builtIns
|
||||
|
||||
isShellBuiltin.builtIns = None
|
||||
|
||||
# pylint: enable-msg=E1101,W0612
|
||||
|
||||
# Interface management
|
||||
#
|
||||
# Interfaces are managed as strings which are simply the
|
||||
# interface names, of the form 'nodeN-ethM'.
|
||||
#
|
||||
# To connect nodes, we create a pair of veth interfaces, and then place them
|
||||
# in the pair of nodes that we want to communicate. We then update the node's
|
||||
# list of interfaces and connectivity map.
|
||||
#
|
||||
# For the kernel datapath, switch interfaces
|
||||
# live in the root namespace and thus do not have to be
|
||||
# explicitly moved.
|
||||
|
||||
def makeIntfPair( intf1, intf2 ):
|
||||
"""Make a veth pair connecting intf1 and intf2.
|
||||
intf1: string, interface
|
||||
intf2: string, interface
|
||||
returns: success boolean"""
|
||||
# Delete any old interfaces with the same names
|
||||
quietRun( 'ip link del ' + intf1 )
|
||||
quietRun( 'ip link del ' + intf2 )
|
||||
# Create new pair
|
||||
cmd = 'ip link add name ' + intf1 + ' type veth peer name ' + intf2
|
||||
return quietRun( cmd )
|
||||
|
||||
def retry( retries, delaySecs, fn, *args, **keywords ):
|
||||
"""Try something several times before giving up.
|
||||
n: number of times to retry
|
||||
delaySecs: wait this long between tries
|
||||
fn: function to call
|
||||
args: args to apply to function call"""
|
||||
tries = 0
|
||||
while not fn( *args, **keywords ) and tries < retries:
|
||||
sleep( delaySecs )
|
||||
tries += 1
|
||||
if tries >= retries:
|
||||
error( "*** gave up after %i retries\n" % tries )
|
||||
exit( 1 )
|
||||
|
||||
def moveIntfNoRetry( intf, node, printError=False ):
|
||||
"""Move interface to node, without retrying.
|
||||
intf: string, interface
|
||||
node: Node object
|
||||
printError: if true, print error"""
|
||||
cmd = 'ip link set ' + intf + ' netns ' + repr( node.pid )
|
||||
quietRun( cmd )
|
||||
links = node.cmd( 'ip link show' )
|
||||
if not ( ' %s:' % intf ) in links:
|
||||
if printError:
|
||||
error( '*** Error: moveIntf: ' + intf +
|
||||
' not successfully moved to ' + node.name + '\n' )
|
||||
return False
|
||||
return True
|
||||
|
||||
def moveIntf( intf, node, printError=False, retries=3, delaySecs=0.001 ):
|
||||
"""Move interface to node, retrying on failure.
|
||||
intf: string, interface
|
||||
node: Node object
|
||||
printError: if true, print error"""
|
||||
retry( retries, delaySecs, moveIntfNoRetry, intf, node, printError )
|
||||
|
||||
def createLink( node1, node2, port1=None, port2=None ):
|
||||
"""Create a link between nodes, making an interface for each.
|
||||
node1: Node object
|
||||
node2: Node object
|
||||
port1: node1 port number (optional)
|
||||
port2: node2 port number (optional)
|
||||
returns: intf1 name, intf2 name"""
|
||||
return node1.linkTo( node2, port1, port2 )
|
||||
|
||||
|
||||
# IP and Mac address formatting and parsing
|
||||
|
||||
def _colonHex( val, count ):
|
||||
"""Generate colon-hex string.
|
||||
val: input as unsigned int
|
||||
count: number of bytes to convert
|
||||
returns: chStr colon-hex string"""
|
||||
pieces = []
|
||||
for i in range( count - 1, -1, -1 ):
|
||||
piece = ( ( 0xff << ( i * 8 ) ) & val ) >> ( i * 8 )
|
||||
pieces.append( '%02x' % piece )
|
||||
chStr = ':'.join( pieces )
|
||||
return chStr
|
||||
|
||||
def macColonHex( mac ):
|
||||
"""Generate MAC colon-hex string from unsigned int.
|
||||
mac: MAC address as unsigned int
|
||||
returns: macStr MAC colon-hex string"""
|
||||
return _colonHex( mac, 6 )
|
||||
|
||||
def ipStr( ip ):
|
||||
"""Generate IP address string from an unsigned int.
|
||||
ip: unsigned int of form w << 24 | x << 16 | y << 8 | z
|
||||
returns: ip address string w.x.y.z, or 10.x.y.z if w==0"""
|
||||
w = ( ip & 0xff000000 ) >> 24
|
||||
w = 10 if w == 0 else w
|
||||
x = ( ip & 0xff0000 ) >> 16
|
||||
y = ( ip & 0xff00 ) >> 8
|
||||
z = ip & 0xff
|
||||
return "%i.%i.%i.%i" % ( w, x, y, z )
|
||||
|
||||
def ipNum( w, x, y, z ):
|
||||
"""Generate unsigned int from components ofIP address
|
||||
returns: w << 24 | x << 16 | y << 8 | z"""
|
||||
return ( w << 24 ) | ( x << 16 ) | ( y << 8 ) | z
|
||||
|
||||
def ipParse( ip ):
|
||||
"Parse an IP address and return an unsigned int."
|
||||
args = [ int( arg ) for arg in ip.split( '.' ) ]
|
||||
return ipNum( *args )
|
||||
|
||||
def checkInt( s ):
|
||||
"Check if input string is an int"
|
||||
try:
|
||||
int( s )
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
def checkFloat( s ):
|
||||
"Check if input string is a float"
|
||||
try:
|
||||
float( s )
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
def makeNumeric( s ):
|
||||
"Convert string to int or float if numeric."
|
||||
if checkInt( s ):
|
||||
return int( s )
|
||||
elif checkFloat( s ):
|
||||
return float( s )
|
||||
else:
|
||||
return s
|
||||
|
||||
|
||||
# Other stuff we use
|
||||
|
||||
def fixLimits():
|
||||
"Fix ridiculously small resource limits."
|
||||
setrlimit( RLIMIT_NPROC, ( 4096, 8192 ) )
|
||||
setrlimit( RLIMIT_NOFILE, ( 16384, 32768 ) )
|
||||
@@ -0,0 +1,79 @@
|
||||
/* mnexec: execution utility for mininet
|
||||
*
|
||||
* Starts up programs and does things that are slow or
|
||||
* difficult in Python, including:
|
||||
*
|
||||
* - closing all file descriptors except stdin/out/error
|
||||
* - detaching from a controlling tty using setsid
|
||||
* - running in a network namespace
|
||||
* - printing out the pid of a process so we can identify it later
|
||||
*
|
||||
* Partially based on public domain setsid(1)
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <linux/sched.h>
|
||||
#include <unistd.h>
|
||||
|
||||
void usage(char *name)
|
||||
{
|
||||
printf("Execution utility for Mininet.\n"
|
||||
"usage: %s [-cdnp]\n"
|
||||
"-c: close all file descriptors except stdin/out/error\n"
|
||||
"-d: detach from tty by calling setsid()\n"
|
||||
"-n: run in new network namespace\n"
|
||||
"-p: print ^A + pid\n", name);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
char c;
|
||||
int fd;
|
||||
|
||||
while ((c = getopt(argc, argv, "+cdnp")) != -1)
|
||||
switch(c) {
|
||||
case 'c':
|
||||
/* close file descriptors except stdin/out/error */
|
||||
for (fd = getdtablesize(); fd > 2; fd--)
|
||||
close(fd);
|
||||
break;
|
||||
case 'd':
|
||||
/* detach from tty */
|
||||
if (getpgrp() == getpid()) {
|
||||
switch(fork()) {
|
||||
case -1:
|
||||
perror("fork");
|
||||
return 1;
|
||||
case 0: /* child */
|
||||
break;
|
||||
default: /* parent */
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
setsid();
|
||||
break;
|
||||
case 'n':
|
||||
/* run in network namespace */
|
||||
if (unshare(CLONE_NEWNET) == -1) {
|
||||
perror("unshare");
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
case 'p':
|
||||
/* print pid */
|
||||
printf("\001%d\n", getpid());
|
||||
fflush(stdout);
|
||||
break;
|
||||
default:
|
||||
usage(argv[0]);
|
||||
break;
|
||||
}
|
||||
|
||||
if (optind < argc) {
|
||||
execvp(argv[optind], &argv[optind]);
|
||||
perror(argv[optind]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
usage(argv[0]);
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
python-igraph
|
||||
setuptools
|
||||
tqdm
|
||||
joblib
|
||||
Loaded 100 of 140 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user