Compare commits
190 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 964d7c6ed9 | |||
| 9665f5c9c1 | |||
| af975e5ed5 | |||
| 273e80f1fe | |||
| aed282d565 | |||
| 92bc429fe1 | |||
| cb8c911f41 | |||
| 2e7b584ab5 | |||
| 86647794f8 | |||
| edd777fdfc | |||
| dc05e3ce90 | |||
| 047b92cf30 | |||
| aad8dbea87 | |||
| 1e720875d2 | |||
| 63a330d317 | |||
| d8e6b8e508 | |||
| c69041f603 | |||
| 97a347fb3a | |||
| 3e95ee8bd7 | |||
| a169f5702e | |||
| c32a07be3e | |||
| 49e43c39cd | |||
| 90faa588cc | |||
| 1d3c0a8ac5 | |||
| 2085544799 | |||
| 8c8e633d26 | |||
| 48ada89a8a | |||
| 578f2ec695 | |||
| b428660d54 | |||
| f53ab47678 | |||
| 407c5f0ef3 | |||
| 0ed7772f3f | |||
| eef6ee890e | |||
| ea2d5d6802 | |||
| 5dbf99dd78 | |||
| 576a419ef2 | |||
| 587b78ff64 | |||
| ccd85b1cf1 | |||
| 722dcd53bd | |||
| 9efd73922b | |||
| cf8e7541ab | |||
| 76dbe6633c | |||
| f48424a4a7 | |||
| bb31ed2fcd | |||
| 1f1de1f87b | |||
| 082e4449bb | |||
| 55e2dd9087 | |||
| 52223f7144 | |||
| ad8d37e89d | |||
| 5c79db0aec | |||
| a95b681d95 | |||
| 6c86e30f63 | |||
| 8ae870a591 | |||
| 61a2be8726 | |||
| 0dcdb32992 | |||
| 658c78b15f | |||
| 6651a74f37 | |||
| 50778df223 | |||
| 2b7acee13a | |||
| 27b5e1bf84 | |||
| 2b899536e9 | |||
| 6f7a64fc75 | |||
| d6c3cad602 | |||
| 9944cf5129 | |||
| 052aabbc7b | |||
| 517d34cf64 | |||
| 670ff15691 | |||
| 4fa88819c1 | |||
| 1bc9b47be9 | |||
| 212cb82889 | |||
| e842cc204d | |||
| 532302bbf2 | |||
| 5f8c796609 | |||
| 092755eaae | |||
| 73caa524d3 | |||
| ed557f5a29 | |||
| 55d2bf4503 | |||
| 7b3655a52a | |||
| 22642a39ca | |||
| 7a6978eb52 | |||
| a80484e0bd | |||
| 5d9ba60f95 | |||
| 74da84ce4d | |||
| 80eeea157b | |||
| 8fb3a545bb | |||
| 501d4d6a3a | |||
| ad5de70d4b | |||
| a013686a11 | |||
| 2763f1978e | |||
| 2ef39c064e | |||
| ecf9c5a1bc | |||
| 7adc7f7275 | |||
| 9850d65151 | |||
| 6a075c25ff | |||
| 02852a0161 | |||
| ec084835ce | |||
| 708fccabdc | |||
| 59f8624201 | |||
| da475f06c4 | |||
| f6a610bd2e | |||
| 95789ccc5e | |||
| 5f47020f43 | |||
| 3bed483792 | |||
| f5f304b9cd | |||
| 044611d101 | |||
| f932a18f7b | |||
| e144ceb8c2 | |||
| 5518f71bfe | |||
| 0cccdb8a3a | |||
| e626b63cd9 | |||
| 52e47e161d | |||
| 3807c1b64f | |||
| c09c16da83 | |||
| b07ada8b5e | |||
| 5d5a566f8b | |||
| 14aa596935 | |||
| bf600a4334 | |||
| dd4c6b78e4 | |||
| 008387d311 | |||
| 68b0815584 | |||
| 5fee776a3b | |||
| d9c9e52507 | |||
| 0c5eb69d30 | |||
| d829bfc385 | |||
| d96eed31e8 | |||
| c64dcb8366 | |||
| fbd47c9ef8 | |||
| 1b66369d43 | |||
| 194be2465a | |||
| 498625bb71 | |||
| 4e545d967b | |||
| 1cebdfc7fb | |||
| 37bffa9ceb | |||
| 4e7df5ad86 | |||
| e9f116d781 | |||
| 673b013984 | |||
| 608a1337ec | |||
| d1ee56a16a | |||
| d367dbb032 | |||
| ce7f4e9236 | |||
| 3a1b6e54e4 | |||
| b8b1806546 | |||
| 1c0e097ccf | |||
| ab087daa66 | |||
| 557cb8466a | |||
| 792c6aa970 | |||
| 3a4afb19d5 | |||
| 3b8bc652d4 | |||
| c3ed2b9960 | |||
| 20f70762b6 | |||
| 1a0129681a | |||
| 2ad32e2a61 | |||
| cb20c547af | |||
| 8bf379eaf3 | |||
| c264d44357 | |||
| 7b6ba18c7e | |||
| 34c3ee0b6a | |||
| 4c85880a01 | |||
| 01b62f7c46 | |||
| ef3490b052 | |||
| 188d2f3940 | |||
| 6bdcecfbfc | |||
| 24c0cbddbf | |||
| ac585d8778 | |||
| 2fa082da91 | |||
| e904f533e3 | |||
| 47b243c59d | |||
| d8711c8d1d | |||
| b5e55fa658 | |||
| c6b56036cf | |||
| 21d9179ac0 | |||
| 7736b82f92 | |||
| 652bad492c | |||
| 3d060fc824 | |||
| ceef99c108 | |||
| b9d85b724e | |||
| b4430fb7b6 | |||
| 6d3dd60010 | |||
| d7ccfd9f52 | |||
| e3c810e23f | |||
| f8ccd38513 | |||
| e121a7b55b | |||
| cd5073b5e5 | |||
| 4cf72ea2ef | |||
| 9ab2f8dd86 | |||
| fc8a032ed2 | |||
| 29432250ec | |||
| b2d76332dc | |||
| f40ecd179c | |||
| 0fb51cc605 |
@@ -0,0 +1 @@
|
||||
Dockerfile
|
||||
@@ -0,0 +1,44 @@
|
||||
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 }}
|
||||
+19
-6
@@ -1,8 +1,21 @@
|
||||
mnexec
|
||||
# Python
|
||||
build
|
||||
dist
|
||||
Mini_NDN.egg-info
|
||||
*.pyc
|
||||
*~
|
||||
\#*\#
|
||||
mininet.egg-info
|
||||
build/*
|
||||
dist/*
|
||||
|
||||
# Docs
|
||||
docs/html
|
||||
docs/latex
|
||||
docs/_build
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
dl
|
||||
*.apconf
|
||||
|
||||
# Vagrant
|
||||
.vagrant
|
||||
|
||||
venv/
|
||||
.vscode
|
||||
|
||||
@@ -1,297 +0,0 @@
|
||||
# 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
|
||||
|
||||
# List of plugins (as comma separated values of python modules names) to load,
|
||||
# usually to register additional checkers.
|
||||
load-plugins=
|
||||
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
|
||||
# Enable the message, report, category or checker with the given id(s). You can
|
||||
# either give multiple identifier separated by comma (,) or put this option
|
||||
# multiple time.
|
||||
#enable=
|
||||
|
||||
# Disable the message, report, category or checker with the given id(s). You
|
||||
# can either give multiple identifier separated by comma (,) or put this option
|
||||
# multiple time (only on the command line, not in the configuration file where
|
||||
# it should appear only once).
|
||||
disable=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=colorized
|
||||
|
||||
# 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=1500
|
||||
|
||||
# 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
@@ -0,0 +1,38 @@
|
||||
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
@@ -0,0 +1,674 @@
|
||||
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
@@ -0,0 +1,37 @@
|
||||
# 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"]
|
||||
@@ -1,273 +0,0 @@
|
||||
|
||||
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.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
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.
|
||||
@@ -1,39 +0,0 @@
|
||||
MININET = mininet/*.py
|
||||
TEST = mininet/test/*.py
|
||||
EXAMPLES = examples/*.py
|
||||
BIN = bin/mn
|
||||
PYSRC = $(MININET) $(TEST) $(EXAMPLES) $(BIN)
|
||||
MNEXEC = mnexec
|
||||
P8IGN = E251,E201,E302,E202
|
||||
|
||||
all: codecheck test
|
||||
|
||||
clean:
|
||||
rm -rf build dist *.egg-info *.pyc $(MNEXEC)
|
||||
|
||||
codecheck: $(PYSRC)
|
||||
-echo "Running code check"
|
||||
pyflakes $(PYSRC)
|
||||
pylint --rcfile=.pylint $(PYSRC)
|
||||
pep8 --repeat --ignore=$(P8IGN) $(PYSRC)
|
||||
|
||||
errcheck: $(PYSRC)
|
||||
-echo "Running check for errors only"
|
||||
pyflakes $(PYSRC)
|
||||
pylint -E --rcfile=.pylint $(PYSRC)
|
||||
|
||||
test: $(MININET) $(TEST)
|
||||
-echo "Running tests"
|
||||
mininet/test/test_nets.py
|
||||
|
||||
install: $(MNEXEC)
|
||||
install $(MNEXEC) /usr/local/bin/
|
||||
python setup.py install
|
||||
|
||||
develop: $(MNEXEC)
|
||||
install $(MNEXEC) /usr/local/bin/
|
||||
python setup.py develop
|
||||
|
||||
doc:
|
||||
doxygen doxygen.cfg
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
|
||||
Mininet: A Simple Virtual Testbed for OpenFlow/SDN
|
||||
or
|
||||
How to Squeeze a 1024-node OpenFlow Network onto your Laptop
|
||||
|
||||
Mininet 1.0.0
|
||||
|
||||
---
|
||||
Welcome to Mininet!
|
||||
|
||||
Mininet creates OpenFlow test networks by using process-based
|
||||
virtualization and network namespaces.
|
||||
|
||||
Simulated hosts (as well as switches and controllers with the user
|
||||
datapath) are created as processes in separate network namespaces. This
|
||||
allows a complete OpenFlow network to be simulated on top of a single
|
||||
Linux kernel.
|
||||
|
||||
Mininet may be invoked directly from the command line, and also provides a
|
||||
handy Python API for creating networks of varying sizes and topologies.
|
||||
|
||||
Mininet is currently in *limited alpha release*. We encourage you to
|
||||
experiment with it and hope that you will provide us with feedback on
|
||||
features, documentation, and how you're using it. We plan to make it
|
||||
available publicly via a GPL or BSD license (probably in April), but please
|
||||
don't distribute the code or URLs yet! The feedback you provide will help
|
||||
us improve Mininet for general release.
|
||||
|
||||
In order to run Mininet, you must have:
|
||||
|
||||
* A Linux 2.6.26 or greater kernel compiled with network namespace support
|
||||
enabled (see INSTALL for additional information.)
|
||||
|
||||
* An OpenFlow implementation (either the reference user or kernel
|
||||
space implementations, or Open vSwitch.) Appropriate kernel modules
|
||||
(e.g. tun and ofdatapath for the reference kernel implementation) must
|
||||
be loaded.
|
||||
|
||||
* Python, bash, ping, iperf, etc.
|
||||
|
||||
* Root privileges (required for network device access)
|
||||
|
||||
Currently Mininet includes:
|
||||
|
||||
- A simple node infrastructure (Host, Switch, Controller classes) for
|
||||
creating virtual OpenFlow networks
|
||||
|
||||
- A simple network infrastructure (Mininet class) supporting parametrized
|
||||
topologies (Topo subclasses.) For example, a tree network may be created
|
||||
with the command
|
||||
|
||||
# mn --topo tree,depth=2,fanout=3
|
||||
|
||||
- Basic tests, including connectivity (ping) and bandwidth (iperf)
|
||||
|
||||
- A command-line interface (CLI class) which provides useful
|
||||
diagnostic commands, as well as the ability to send a command to a
|
||||
node. For example,
|
||||
|
||||
mininet> h11 ifconfig -a
|
||||
|
||||
tells host h11 to run the command 'ifconfig -a'
|
||||
|
||||
- A 'cleanup' command to get rid of junk (interfaces, processes, files in
|
||||
/tmp, etc.) which might be left around by Mininet or Linux. Try this if
|
||||
things stop working!
|
||||
|
||||
# mn -c
|
||||
|
||||
- Examples (in the examples/ directory) to help you get started.
|
||||
|
||||
Batteries are not included (yet!)
|
||||
|
||||
However, some preliminary installation notes are included in the INSTALL
|
||||
file.
|
||||
|
||||
Additionally, much useful information, including a Mininet tutorial,
|
||||
is available on the Mininet wiki:
|
||||
|
||||
http://openflow.org/mininet
|
||||
|
||||
Enjoy, and good luck!
|
||||
|
||||
---
|
||||
Bob Lantz
|
||||
rlantz@cs.stanford.edu
|
||||
@@ -0,0 +1,32 @@
|
||||
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`.
|
||||
@@ -1,258 +0,0 @@
|
||||
#!/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, warn
|
||||
from mininet.net import Mininet, MininetWithControlNet
|
||||
from mininet.node import ( Host, CPULimitedHost, Controller, OVSController,
|
||||
NOX, RemoteController, UserSwitch, OVSKernelSwitch,
|
||||
OVSLegacyKernelSwitch )
|
||||
from mininet.link import Link, TCLink
|
||||
from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo
|
||||
from mininet.topolib import TreeTopo
|
||||
from mininet.util import makeNumeric, custom, customConstructor, splitArgs
|
||||
from mininet.util import buildTopo
|
||||
|
||||
|
||||
# built in topologies, created only when run
|
||||
TOPODEF = 'minimal'
|
||||
TOPOS = { 'minimal': lambda: SingleSwitchTopo( k=2 ),
|
||||
'linear': LinearTopo,
|
||||
'reversed': SingleSwitchReversedTopo,
|
||||
'single': SingleSwitchTopo,
|
||||
'tree': TreeTopo }
|
||||
|
||||
SWITCHDEF = 'ovsk'
|
||||
SWITCHES = { 'user': UserSwitch,
|
||||
'ovsk': OVSKernelSwitch,
|
||||
'ovsl': OVSLegacyKernelSwitch }
|
||||
|
||||
HOSTDEF = 'proc'
|
||||
HOSTS = { 'proc': Host,
|
||||
'rt': custom( CPULimitedHost, sched='rt' ),
|
||||
'cfs': custom( CPULimitedHost, sched='cfs' ) }
|
||||
|
||||
CONTROLLERDEF = 'ref'
|
||||
CONTROLLERS = { 'ref': Controller,
|
||||
'ovsc': OVSController,
|
||||
'nox': NOX,
|
||||
'remote': RemoteController,
|
||||
'none': lambda name: None }
|
||||
|
||||
LINKDEF = 'default'
|
||||
LINKS = { 'default': Link,
|
||||
'tc': TCLink }
|
||||
|
||||
|
||||
# optional tests to run
|
||||
TESTS = [ 'cli', 'build', 'pingall', 'pingpair', 'iperf', 'all', 'iperfudp',
|
||||
'none' ]
|
||||
|
||||
ALTSPELLING = { 'pingall': 'pingAll', 'pingpair': 'pingPair',
|
||||
'iperfudp': 'iperfUdp', 'iperfUDP': 'iperfUdp', 'prefixlen': 'prefixLen' }
|
||||
|
||||
|
||||
def addDictOption( opts, choicesDict, default, name, helpStr=None ):
|
||||
"""Convenience function to add choices dicts to OptionParser.
|
||||
opts: OptionParser instance
|
||||
choicesDict: dictionary of valid choices, must include default
|
||||
default: default choice key
|
||||
name: long option name
|
||||
help: string"""
|
||||
if default not in choicesDict:
|
||||
raise Exception( 'Invalid default %s for choices dict: %s' %
|
||||
( default, name ) )
|
||||
if not helpStr:
|
||||
helpStr = ( '|'.join( sorted( choicesDict.keys() ) ) +
|
||||
'[,param=value...]' )
|
||||
opts.add_option( '--' + name,
|
||||
type='string',
|
||||
default = default,
|
||||
help = helpStr )
|
||||
|
||||
|
||||
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."
|
||||
customs = {}
|
||||
if os.path.isfile( fileName ):
|
||||
execfile( fileName, customs, customs )
|
||||
for name, val in customs.iteritems():
|
||||
self.setCustom( name, val )
|
||||
else:
|
||||
raise Exception( 'could not find custom file: %s' % fileName )
|
||||
|
||||
def parseArgs( self ):
|
||||
"""Parse command-line args and return options object.
|
||||
returns: opts parse options dict"""
|
||||
if '--custom' in sys.argv:
|
||||
index = sys.argv.index( '--custom' )
|
||||
if len( sys.argv ) > index + 1:
|
||||
filename = sys.argv[ index + 1 ]
|
||||
self.parseCustomFile( filename )
|
||||
else:
|
||||
raise Exception( 'Custom file name not found' )
|
||||
|
||||
opts = OptionParser()
|
||||
addDictOption( opts, SWITCHES, SWITCHDEF, 'switch' )
|
||||
addDictOption( opts, HOSTS, HOSTDEF, 'host' )
|
||||
addDictOption( opts, CONTROLLERS, CONTROLLERDEF, 'controller' )
|
||||
addDictOption( opts, LINKS, LINKDEF, 'link' )
|
||||
addDictOption( opts, TOPOS, TOPODEF, 'topo' )
|
||||
|
||||
opts.add_option( '--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( '--ipbase', '-i', type='string', default='10.0.0.0/8',
|
||||
help='base IP address for hosts' )
|
||||
opts.add_option( '--mac', action='store_true',
|
||||
default=False, help='automatically set host MACs' )
|
||||
opts.add_option( '--arp', action='store_true',
|
||||
default=False, help='set all-pairs ARP entries' )
|
||||
opts.add_option( '--verbosity', '-v', type='choice',
|
||||
choices=LEVELS.keys(), default = 'info',
|
||||
help = '|'.join( LEVELS.keys() ) )
|
||||
opts.add_option( '--innamespace', action='store_true',
|
||||
default=False, help='sw and ctrl in namespace?' )
|
||||
opts.add_option( '--listenport', type='int', default=6635,
|
||||
help='base port for passive switch listening' )
|
||||
opts.add_option( '--nolistenport', action='store_true',
|
||||
default=False, help="don't use passive listening port")
|
||||
opts.add_option( '--pre', type='string', default=None,
|
||||
help='CLI script to run before tests' )
|
||||
opts.add_option( '--post', type='string', default=None,
|
||||
help='CLI script to run after tests' )
|
||||
opts.add_option( '--prefixlen', type='int', default=8,
|
||||
help='prefix length (e.g. /8) for automatic '
|
||||
'network configuration' )
|
||||
opts.add_option( '--pin', action='store_true',
|
||||
default=False, help="pin hosts to CPU cores "
|
||||
"(requires --host cfs or --host rt)" )
|
||||
|
||||
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 )
|
||||
|
||||
def begin( self ):
|
||||
"Create and run mininet."
|
||||
|
||||
if self.options.clean:
|
||||
cleanup()
|
||||
exit()
|
||||
|
||||
start = time.time()
|
||||
|
||||
topo = buildTopo( TOPOS, self.options.topo )
|
||||
switch = customConstructor( SWITCHES, self.options.switch )
|
||||
host = customConstructor( HOSTS, self.options.host )
|
||||
controller = customConstructor( CONTROLLERS, self.options.controller )
|
||||
link = customConstructor( LINKS, self.options.link )
|
||||
|
||||
if self.validate:
|
||||
self.validate( self.options )
|
||||
|
||||
inNamespace = self.options.innamespace
|
||||
Net = MininetWithControlNet if inNamespace else Mininet
|
||||
ipBase = self.options.ipbase
|
||||
xterms = self.options.xterms
|
||||
mac = self.options.mac
|
||||
arp = self.options.arp
|
||||
pin = self.options.pin
|
||||
listenPort = None
|
||||
if not self.options.nolistenport:
|
||||
listenPort = self.options.listenport
|
||||
mn = Net( topo=topo,
|
||||
switch=switch, host=host, controller=controller,
|
||||
link=link,
|
||||
ipBase=ipBase,
|
||||
inNamespace=inNamespace,
|
||||
xterms=xterms, autoSetMacs=mac,
|
||||
autoStaticArp=arp, autoPinCpus=pin,
|
||||
listenPort=listenPort )
|
||||
|
||||
if self.options.pre:
|
||||
CLI( mn, script=self.options.pre )
|
||||
|
||||
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()
|
||||
@@ -1,6 +0,0 @@
|
||||
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
|
||||
@@ -1,45 +0,0 @@
|
||||
"""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() ) }
|
||||
@@ -0,0 +1,11 @@
|
||||
#!/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
|
||||
@@ -0,0 +1,35 @@
|
||||
|
||||
[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.
|
||||
@@ -0,0 +1,20 @@
|
||||
# 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)
|
||||
@@ -0,0 +1 @@
|
||||
.. include:: ../AUTHORS.rst
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/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
|
||||
@@ -0,0 +1,61 @@
|
||||
# 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'
|
||||
@@ -25,7 +25,7 @@ DOXYFILE_ENCODING = UTF-8
|
||||
# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
|
||||
# by quotes) that should identify the project.
|
||||
|
||||
PROJECT_NAME = Mininet
|
||||
PROJECT_NAME = "Mininet Python API Reference Manual"
|
||||
|
||||
# The PROJECT_NUMBER tag can be used to enter a project or revision number.
|
||||
# This could be handy for archiving the generated documentation or
|
||||
@@ -114,7 +114,7 @@ FULL_PATH_NAMES = YES
|
||||
# If left blank the directory from which doxygen is run is used as the
|
||||
# path to strip.
|
||||
|
||||
STRIP_FROM_PATH =
|
||||
STRIP_FROM_PATH =
|
||||
|
||||
# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
|
||||
# the path mentioned in the documentation of a class, which tells
|
||||
@@ -919,7 +919,7 @@ COMPACT_LATEX = NO
|
||||
# by the printer. Possible values are: a4, a4wide, letter, legal and
|
||||
# executive. If left blank a4wide will be used.
|
||||
|
||||
PAPER_TYPE = a4wide
|
||||
PAPER_TYPE = letter
|
||||
|
||||
# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
|
||||
# packages that should be included in the LaTeX output.
|
||||
@@ -0,0 +1,274 @@
|
||||
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.
|
||||
@@ -0,0 +1,48 @@
|
||||
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 |
|
||||
+--------------------------------+-----------------------------------------------------------+-----------------------------------------+
|
||||
@@ -0,0 +1,10 @@
|
||||
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
@@ -0,0 +1,128 @@
|
||||
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.
|
||||
@@ -0,0 +1,30 @@
|
||||
.. 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>`
|
||||
@@ -0,0 +1,184 @@
|
||||
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.
|
||||
@@ -0,0 +1,40 @@
|
||||
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>
|
||||
@@ -0,0 +1,126 @@
|
||||
<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>
|
||||
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,286 @@
|
||||
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
|
||||
@@ -0,0 +1,18 @@
|
||||
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>
|
||||
@@ -1,84 +0,0 @@
|
||||
|
||||
Mininet Examples
|
||||
|
||||
These examples are intended to help you get started using
|
||||
Mininet's Python API.
|
||||
|
||||
---
|
||||
|
||||
baresshd.py:
|
||||
|
||||
This example uses Mininet's medium-level API to create an sshd
|
||||
process running in a namespace. Doesn't use OpenFlow.
|
||||
|
||||
consoles.py:
|
||||
|
||||
This example creates a grid of console windows, one for each node,
|
||||
and allows interaction with and monitoring of each console, including
|
||||
graphical monitoring.
|
||||
|
||||
controllers.py:
|
||||
|
||||
This example creates a network and adds multiple controllers to it.
|
||||
|
||||
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.
|
||||
|
||||
multiping.py:
|
||||
|
||||
This example demonstrates one method for
|
||||
monitoring output from multiple hosts, using node.monitor().
|
||||
|
||||
multipoll.py:
|
||||
|
||||
This example demonstrates monitoring output files from multiple hosts.
|
||||
|
||||
multitest.py:
|
||||
|
||||
This example creates a network and runs multiple tests on it.
|
||||
|
||||
popenpoll.py:
|
||||
|
||||
This example demonstrates monitoring output from multiple hosts using
|
||||
the node.popen() interface (which returns Popen objects) and pmonitor().
|
||||
|
||||
scratchnet.py, scratchnetuser.py:
|
||||
|
||||
These two examples demonstrate how to create a network by using the lowest-
|
||||
level Mininet functions. Generally the higher-level API is easier to use,
|
||||
but scratchnet shows what is going on behind the scenes.
|
||||
|
||||
simpleperf.py:
|
||||
|
||||
A simple example of configuring network and CPU bandwidth limits.
|
||||
|
||||
sshd.py:
|
||||
|
||||
This example shows how to run an sshd process in each host, allowing
|
||||
you to log in via ssh. This requires connecting the Mininet data network
|
||||
to an interface in the root namespace (generaly the control network
|
||||
already lives in the root namespace, so it does not need to be explicitly
|
||||
connected.)
|
||||
|
||||
treeping64.py:
|
||||
|
||||
This example creates a 64-host tree network, and attempts to check full
|
||||
connectivity using ping, for 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.)
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
#!/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 h1
|
||||
|
||||
print "*** Configuring nodes"
|
||||
h1.setIP( '10.0.0.1', 8 )
|
||||
root.setIP( '10.0.0.2', 8 )
|
||||
|
||||
print "*** Creating banner file"
|
||||
f = open( '/tmp/%s.banner' % h1.name, 'w' )
|
||||
f.write( 'Welcome to %s at %s\n' % ( h1.name, h1.IP() ) )
|
||||
f.close()
|
||||
|
||||
print "*** Running sshd"
|
||||
h1.cmd( '/usr/sbin/sshd -o "Banner /tmp/%s.banner"' % h1.name )
|
||||
|
||||
print "*** You may now ssh into", h1.name, "at", h1.IP()
|
||||
@@ -0,0 +1,95 @@
|
||||
# -*- 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()
|
||||
@@ -1,459 +0,0 @@
|
||||
#!/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' )
|
||||
outputHook = lambda x, y: True # make pylint happier
|
||||
if self.outputHook:
|
||||
outputHook = self.outputHook
|
||||
outputHook( self, text )
|
||||
|
||||
def handleKey( self, event ):
|
||||
"If it's an interactive command, send it to the node."
|
||||
char = event.char
|
||||
if self.node.waiting:
|
||||
self.node.write( char )
|
||||
|
||||
def handleReturn( self, event ):
|
||||
"Handle a carriage return."
|
||||
cmd = self.text.get( 'insert linestart', 'insert lineend' )
|
||||
# Send it immediately, if "interactive" command
|
||||
if self.node.waiting:
|
||||
self.node.write( event.char )
|
||||
return
|
||||
# Otherwise send the whole line to the shell
|
||||
pos = cmd.find( self.prompt )
|
||||
if pos >= 0:
|
||||
cmd = cmd[ pos + len( self.prompt ): ]
|
||||
self.sendCmd( cmd )
|
||||
|
||||
# Callback ignores event
|
||||
def handleInt( self, _event=None ):
|
||||
"Handle control-c."
|
||||
self.node.sendInt()
|
||||
|
||||
def sendCmd( self, cmd ):
|
||||
"Send a command to our node."
|
||||
if not self.node.waiting:
|
||||
self.node.sendCmd( cmd )
|
||||
|
||||
def handleReadable( self, _fds, timeoutms=None ):
|
||||
"Handle file readable event."
|
||||
data = self.node.monitor( timeoutms )
|
||||
self.append( data )
|
||||
if not self.node.waiting:
|
||||
# Print prompt
|
||||
self.append( self.prompt )
|
||||
|
||||
def waiting( self ):
|
||||
"Are we waiting for output?"
|
||||
return self.node.waiting
|
||||
|
||||
def waitOutput( self ):
|
||||
"Wait for any remaining output."
|
||||
while self.node.waiting:
|
||||
# A bit of a trade-off here...
|
||||
self.handleReadable( self, timeoutms=1000)
|
||||
self.update()
|
||||
|
||||
def clear( self ):
|
||||
"Clear all of our text."
|
||||
self.text.delete( '1.0', 'end' )
|
||||
|
||||
|
||||
class Graph( Frame ):
|
||||
|
||||
"Graph that we can add bars to over time."
|
||||
|
||||
def __init__( self, parent=None,
|
||||
bg = 'white',
|
||||
gheight=200, gwidth=500,
|
||||
barwidth=10,
|
||||
ymax=3.5,):
|
||||
|
||||
Frame.__init__( self, parent )
|
||||
|
||||
self.bg = bg
|
||||
self.gheight = gheight
|
||||
self.gwidth = gwidth
|
||||
self.barwidth = barwidth
|
||||
self.ymax = float( ymax )
|
||||
self.xpos = 0
|
||||
|
||||
# Create everything
|
||||
self.title, self.scale, self.graph = self.createWidgets()
|
||||
self.updateScrollRegions()
|
||||
self.yview( 'moveto', '1.0' )
|
||||
|
||||
def createScale( self ):
|
||||
"Create a and return a new canvas with scale markers."
|
||||
height = float( self.gheight )
|
||||
width = 25
|
||||
ymax = self.ymax
|
||||
scale = Canvas( self, width=width, height=height,
|
||||
background=self.bg )
|
||||
opts = { 'fill': 'red' }
|
||||
# Draw scale line
|
||||
scale.create_line( width - 1, height, width - 1, 0, **opts )
|
||||
# Draw ticks and numbers
|
||||
for y in range( 0, int( ymax + 1 ) ):
|
||||
ypos = height * (1 - float( y ) / ymax )
|
||||
scale.create_line( width, ypos, width - 10, ypos, **opts )
|
||||
scale.create_text( 10, ypos, text=str( y ), **opts )
|
||||
return scale
|
||||
|
||||
def updateScrollRegions( self ):
|
||||
"Update graph and scale scroll regions."
|
||||
ofs = 20
|
||||
height = self.gheight + ofs
|
||||
self.graph.configure( scrollregion=( 0, -ofs,
|
||||
self.xpos * self.barwidth, height ) )
|
||||
self.scale.configure( scrollregion=( 0, -ofs, 0, height ) )
|
||||
|
||||
def yview( self, *args ):
|
||||
"Scroll both scale and graph."
|
||||
self.graph.yview( *args )
|
||||
self.scale.yview( *args )
|
||||
|
||||
def createWidgets( self ):
|
||||
"Create initial widget set."
|
||||
|
||||
# Objects
|
||||
title = Label( self, text='Bandwidth (Gb/s)', bg=self.bg )
|
||||
width = self.gwidth
|
||||
height = self.gheight
|
||||
scale = self.createScale()
|
||||
graph = Canvas( self, width=width, height=height, background=self.bg)
|
||||
xbar = Scrollbar( self, orient='horizontal', command=graph.xview )
|
||||
ybar = Scrollbar( self, orient='vertical', command=self.yview )
|
||||
graph.configure( xscrollcommand=xbar.set, yscrollcommand=ybar.set,
|
||||
scrollregion=(0, 0, width, height ) )
|
||||
scale.configure( yscrollcommand=ybar.set )
|
||||
|
||||
# Layout
|
||||
title.grid( row=0, columnspan=3, sticky='new')
|
||||
scale.grid( row=1, column=0, sticky='nsew' )
|
||||
graph.grid( row=1, column=1, sticky='nsew' )
|
||||
ybar.grid( row=1, column=2, sticky='ns' )
|
||||
xbar.grid( row=2, column=0, columnspan=2, sticky='ew' )
|
||||
self.rowconfigure( 1, weight=1 )
|
||||
self.columnconfigure( 1, weight=1 )
|
||||
return title, scale, graph
|
||||
|
||||
def addBar( self, yval ):
|
||||
"Add a new bar to our graph."
|
||||
percent = yval / self.ymax
|
||||
c = self.graph
|
||||
x0 = self.xpos * self.barwidth
|
||||
x1 = x0 + self.barwidth
|
||||
y0 = self.gheight
|
||||
y1 = ( 1 - percent ) * self.gheight
|
||||
c.create_rectangle( x0 , y0, x1, y1, fill='green' )
|
||||
self.xpos += 1
|
||||
self.updateScrollRegions()
|
||||
self.graph.xview( 'moveto', '1.0' )
|
||||
|
||||
def clear( self ):
|
||||
"Clear graph contents."
|
||||
self.graph.delete( 'all' )
|
||||
self.xpos = 0
|
||||
|
||||
def test( self ):
|
||||
"Add a bar for testing purposes."
|
||||
ms = 1000
|
||||
if self.xpos < 10:
|
||||
self.addBar( self.xpos / 10 * self.ymax )
|
||||
self.after( ms, self.test )
|
||||
|
||||
def setTitle( self, text ):
|
||||
"Set graph title"
|
||||
self.title.configure( text=text, font='Helvetica 9 bold' )
|
||||
|
||||
|
||||
class ConsoleApp( Frame ):
|
||||
|
||||
"Simple Tk consoles for Mininet."
|
||||
|
||||
menuStyle = { 'font': 'Geneva 7 bold' }
|
||||
|
||||
def __init__( self, net, parent=None, width=4 ):
|
||||
Frame.__init__( self, parent )
|
||||
self.top = self.winfo_toplevel()
|
||||
self.top.title( 'Mininet' )
|
||||
self.net = net
|
||||
self.menubar = self.createMenuBar()
|
||||
cframe = self.cframe = Frame( self )
|
||||
self.consoles = {} # consoles themselves
|
||||
titles = {
|
||||
'hosts': 'Host',
|
||||
'switches': 'Switch',
|
||||
'controllers': 'Controller'
|
||||
}
|
||||
for name in titles:
|
||||
nodes = getattr( net, name )
|
||||
frame, consoles = self.createConsoles(
|
||||
cframe, nodes, width, titles[ name ] )
|
||||
self.consoles[ name ] = Object( frame=frame, consoles=consoles )
|
||||
self.selected = None
|
||||
self.select( 'hosts' )
|
||||
self.cframe.pack( expand=True, fill='both' )
|
||||
cleanUpScreens()
|
||||
# Close window gracefully
|
||||
Wm.wm_protocol( self.top, name='WM_DELETE_WINDOW', func=self.quit )
|
||||
|
||||
# Initialize graph
|
||||
graph = Graph( cframe )
|
||||
self.consoles[ 'graph' ] = Object( frame=graph, consoles=[ graph ] )
|
||||
self.graph = graph
|
||||
self.graphVisible = False
|
||||
self.updates = 0
|
||||
self.hostCount = len( self.consoles[ 'hosts' ].consoles )
|
||||
self.bw = 0
|
||||
|
||||
self.pack( expand=True, fill='both' )
|
||||
|
||||
def updateGraph( self, _console, output ):
|
||||
"Update our graph."
|
||||
m = re.search( r'(\d+) 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
|
||||
|
||||
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()
|
||||
@@ -0,0 +1,70 @@
|
||||
# -*- 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()
|
||||
@@ -1,64 +0,0 @@
|
||||
#!/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()
|
||||
@@ -1,81 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
cpu.py: test iperf bandwidth for varying cpu limtis
|
||||
"""
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import CPULimitedHost
|
||||
from mininet.topolib import TreeTopo
|
||||
from mininet.util import custom
|
||||
from mininet.log import setLogLevel, output
|
||||
|
||||
from time import sleep
|
||||
|
||||
def waitListening(client, server, port):
|
||||
"Wait until server is listening on port"
|
||||
if not client.cmd('which telnet'):
|
||||
raise Exception('Could not find telnet')
|
||||
cmd = ('sh -c "echo A | telnet -e A %s %s"' %
|
||||
(server.IP(), port))
|
||||
while 'Connected' not in client.cmd(cmd):
|
||||
output('waiting for', server,
|
||||
'to listen on port', port, '\n')
|
||||
sleep(.5)
|
||||
|
||||
|
||||
def bwtest( cpuLimits, period_us=100000, seconds=5 ):
|
||||
"""Example/test of link and CPU bandwidth limits
|
||||
cpu: cpu limit as fraction of overall CPU time"""
|
||||
|
||||
topo = TreeTopo( depth=1, fanout=2 )
|
||||
|
||||
results = {}
|
||||
|
||||
for sched in 'rt', 'cfs':
|
||||
print '*** Testing with', sched, 'bandwidth limiting'
|
||||
for cpu in cpuLimits:
|
||||
host = custom( CPULimitedHost, sched=sched,
|
||||
period_us=period_us,
|
||||
cpu=cpu )
|
||||
net = Mininet( topo=topo, host=host )
|
||||
net.start()
|
||||
net.pingAll()
|
||||
hosts = [ net.getNodeByName( h ) for h in topo.hosts() ]
|
||||
client, server = hosts[ 0 ], hosts[ -1 ]
|
||||
server.cmd( 'iperf -s -p 5001 &' )
|
||||
waitListening( client, server, 5001 )
|
||||
result = client.cmd( 'iperf -yc -t %s -c %s' % (
|
||||
seconds, server.IP() ) ).split( ',' )
|
||||
bps = float( result[ -1 ] )
|
||||
server.cmdPrint( 'kill %iperf' )
|
||||
net.stop()
|
||||
updated = results.get( sched, [] )
|
||||
updated += [ ( cpu, bps ) ]
|
||||
results[ sched ] = updated
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def dump( results ):
|
||||
"Dump results"
|
||||
|
||||
fmt = '%s\t%s\t%s'
|
||||
|
||||
print
|
||||
print fmt % ( 'sched', 'cpu', 'client MB/s' )
|
||||
print
|
||||
|
||||
for sched in sorted( results.keys() ):
|
||||
entries = results[ sched ]
|
||||
for cpu, bps in entries:
|
||||
pct = '%.2f%%' % ( cpu * 100 )
|
||||
mbps = bps / 1e6
|
||||
print fmt % ( sched, pct, mbps )
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
limits = [ .45, .4, .3, .2, .1 ]
|
||||
out = bwtest( limits )
|
||||
dump( out )
|
||||
@@ -1,44 +0,0 @@
|
||||
#!/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()
|
||||
@@ -1,43 +0,0 @@
|
||||
#!/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()
|
||||
@@ -0,0 +1,62 @@
|
||||
# -*- 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()
|
||||
@@ -1,69 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
limit.py: example of using link and CPU limits
|
||||
"""
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.link import TCIntf
|
||||
from mininet.node import CPULimitedHost
|
||||
from mininet.topolib import TreeTopo
|
||||
from mininet.util import custom, quietRun
|
||||
from mininet.log import setLogLevel
|
||||
from time import sleep
|
||||
|
||||
def testLinkLimit( net, bw ):
|
||||
"Run bandwidth limit test"
|
||||
print '*** Testing network %.2f Mbps bandwidth limit' % bw
|
||||
net.iperf( )
|
||||
|
||||
def testCpuLimit( net, cpu ):
|
||||
"run CPU limit test"
|
||||
pct = cpu * 100
|
||||
print '*** Testing CPU %.0f%% bandwidth limit' % pct
|
||||
h1, h2 = net.hosts
|
||||
h1.cmd( 'while true; do a=1; done &' )
|
||||
h2.cmd( 'while true; do a=1; done &' )
|
||||
pid1 = h1.cmd( 'echo $!' ).strip()
|
||||
pid2 = h2.cmd( 'echo $!' ).strip()
|
||||
cmd = 'ps -p %s,%s -o pid,%%cpu,args' % ( pid1, pid2 )
|
||||
# It's a shame that this is what pylint prefers
|
||||
for _ in range( 5 ):
|
||||
sleep( 1 )
|
||||
print quietRun( cmd ).strip()
|
||||
h1.cmd( 'kill %1')
|
||||
h2.cmd( 'kill %1')
|
||||
|
||||
def limit( bw=10, cpu=.4 ):
|
||||
"""Example/test of link and CPU bandwidth limits
|
||||
bw: interface bandwidth limit in Mbps
|
||||
cpu: cpu limit as fraction of overall CPU time"""
|
||||
intf = custom( TCIntf, bw=bw )
|
||||
myTopo = TreeTopo( depth=1, fanout=2 )
|
||||
for sched in 'rt', 'cfs':
|
||||
print '*** Testing with', sched, 'bandwidth limiting'
|
||||
host = custom( CPULimitedHost, sched=sched, cpu=cpu )
|
||||
net = Mininet( topo=myTopo, intf=intf, host=host )
|
||||
net.start()
|
||||
testLinkLimit( net, bw=bw )
|
||||
testCpuLimit( net, cpu=cpu )
|
||||
net.stop()
|
||||
|
||||
def verySimpleLimit( bw=150 ):
|
||||
"Absurdly simple limiting test"
|
||||
intf = custom( TCIntf, bw=bw )
|
||||
net = Mininet( intf=intf )
|
||||
h1, h2 = net.addHost( 'h1' ), net.addHost( 'h2' )
|
||||
net.addLink( h1, h2 )
|
||||
net.start()
|
||||
net.pingAll()
|
||||
net.iperf()
|
||||
h1.cmdPrint( 'tc -s qdisc ls dev', h1.defaultIntf() )
|
||||
h2.cmdPrint( 'tc -d class show dev', h2.defaultIntf() )
|
||||
h1.cmdPrint( 'tc -s qdisc ls dev', h1.defaultIntf() )
|
||||
h2.cmdPrint( 'tc -d class show dev', h2.defaultIntf() )
|
||||
net.stop()
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
limit()
|
||||
@@ -1,110 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
Test bandwidth (using iperf) on linear networks of varying size,
|
||||
using both kernel and user datapaths.
|
||||
|
||||
We construct a network of N hosts and N-1 switches, connected as follows:
|
||||
|
||||
h1 <-> s1 <-> s2 .. sN-1
|
||||
| | |
|
||||
h2 h3 hN
|
||||
|
||||
WARNING: by default, the reference controller only supports 16
|
||||
switches, so this test WILL NOT WORK unless you have recompiled
|
||||
your controller to support 100 switches (or more.)
|
||||
|
||||
In addition to testing the bandwidth across varying numbers
|
||||
of switches, this example demonstrates:
|
||||
|
||||
- creating a custom topology, LinearTestTopo
|
||||
- using the ping() and iperf() tests from Mininet()
|
||||
- testing both the kernel and user switches
|
||||
|
||||
"""
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import UserSwitch, OVSKernelSwitch
|
||||
from mininet.topo import Topo
|
||||
from mininet.log import lg
|
||||
from mininet.util import irange
|
||||
|
||||
import sys
|
||||
flush = sys.stdout.flush
|
||||
|
||||
class LinearTestTopo( Topo ):
|
||||
"Topology for a string of N hosts and N-1 switches."
|
||||
|
||||
def __init__( self, N, **params ):
|
||||
|
||||
# Initialize topology
|
||||
Topo.__init__( self, **params )
|
||||
|
||||
# Create switches and hosts
|
||||
hosts = [ self.add_host( 'h%s' % h )
|
||||
for h in irange( 1, N ) ]
|
||||
switches = [ self.add_switch( 's%s' % s )
|
||||
for s in irange( 1, N - 1 ) ]
|
||||
|
||||
# Wire up switches
|
||||
last = None
|
||||
for switch in switches:
|
||||
if last:
|
||||
self.add_link( last, switch )
|
||||
last = switch
|
||||
|
||||
# Wire up hosts
|
||||
self.add_link( hosts[ 0 ], switches[ 0 ] )
|
||||
for host, switch in zip( hosts[ 1: ], switches ):
|
||||
self.add_link( host, switch )
|
||||
|
||||
|
||||
def linearBandwidthTest( lengths ):
|
||||
|
||||
"Check bandwidth at various lengths along a switch chain."
|
||||
|
||||
results = {}
|
||||
switchCount = max( lengths )
|
||||
hostCount = switchCount + 1
|
||||
|
||||
switches = { 'reference user': UserSwitch,
|
||||
'Open vSwitch kernel': OVSKernelSwitch }
|
||||
|
||||
topo = LinearTestTopo( hostCount )
|
||||
|
||||
for datapath in switches.keys():
|
||||
print "*** testing", datapath, "datapath"
|
||||
Switch = switches[ datapath ]
|
||||
results[ datapath ] = []
|
||||
net = Mininet( topo=topo, 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' )
|
||||
sizes = [ 1, 10, 20, 40, 60, 80, 100 ]
|
||||
print "*** Running linearBandwidthTest", sizes
|
||||
linearBandwidthTest( sizes )
|
||||
@@ -1,703 +0,0 @@
|
||||
#!/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 )
|
||||
|
||||
def deleteSelection( self, _event ):
|
||||
"Delete the selected item."
|
||||
if self.selection is not None:
|
||||
self.deleteItem( self.selection )
|
||||
self.selectItem( None )
|
||||
|
||||
def nodeIcon( self, node, name ):
|
||||
"Create a new node icon."
|
||||
icon = Button( self.canvas, image=self.images[ node ],
|
||||
text=name, compound='top' )
|
||||
# Unfortunately bindtags wants a tuple
|
||||
bindtags = [ str( self.nodeBindings ) ]
|
||||
bindtags += list( icon.bindtags() )
|
||||
icon.bindtags( tuple( bindtags ) )
|
||||
return icon
|
||||
|
||||
def newNode( self, node, event ):
|
||||
"Add a new node to our canvas."
|
||||
c = self.canvas
|
||||
x, y = c.canvasx( event.x ), c.canvasy( event.y )
|
||||
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 )
|
||||
|
||||
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
|
||||
|
||||
# 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 )
|
||||
|
||||
def leaveNode( self, _event ):
|
||||
"Restore old selection on exit."
|
||||
self.selectItem( self.lastSelection )
|
||||
|
||||
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
|
||||
|
||||
def select( _event, link=self.link ):
|
||||
"Select item on mouse entry."
|
||||
self.selectItem( link )
|
||||
|
||||
def highlight( _event, link=self.link ):
|
||||
"Highlight item on mouse entry."
|
||||
# self.selectItem( link )
|
||||
self.canvas.itemconfig( link, fill='green' )
|
||||
|
||||
def unhighlight( _event, link=self.link ):
|
||||
"Unhighlight item on mouse exit."
|
||||
self.canvas.itemconfig( link, fill='blue' )
|
||||
# self.selectItem( None )
|
||||
|
||||
self.canvas.tag_bind( self.link, '<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, _=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()
|
||||
@@ -0,0 +1,49 @@
|
||||
# -*- 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()
|
||||
@@ -1,86 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
multiping.py: monitor multiple sets of hosts using ping
|
||||
|
||||
This demonstrates how one may send a simple shell script to
|
||||
multiple hosts and monitor their output interactively for a period=
|
||||
of time.
|
||||
"""
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import Node
|
||||
from mininet.topo import SingleSwitchTopo
|
||||
from mininet.log import setLogLevel
|
||||
|
||||
from select import poll, POLLIN
|
||||
from time import time
|
||||
|
||||
def chunks( l, n ):
|
||||
"Divide list l into chunks of size n - thanks Stackoverflow"
|
||||
return [ l[ i : i + n ] for i in range( 0, len( l ), n ) ]
|
||||
|
||||
def startpings( host, targetips ):
|
||||
"Tell host to repeatedly ping targets"
|
||||
|
||||
targetips.append( '10.0.0.200' )
|
||||
|
||||
targetips = ' '.join( targetips )
|
||||
|
||||
# BL: Not sure why loopback intf isn't up!
|
||||
host.cmd( 'ifconfig lo up' )
|
||||
|
||||
# Simple ping loop
|
||||
cmd = ( 'while true; do '
|
||||
' for ip in %s; do ' % targetips +
|
||||
' echo -n %s "->" $ip ' % host.IP() +
|
||||
' `ping -c1 -w 1 $ip | grep packets` ;'
|
||||
' sleep 1;'
|
||||
' done; '
|
||||
'done &' )
|
||||
|
||||
print ( '*** Host %s (%s) will be pinging ips: %s' %
|
||||
( host.name, host.IP(), targetips ) )
|
||||
|
||||
host.cmd( cmd )
|
||||
|
||||
def multiping( netsize, chunksize, seconds):
|
||||
"Ping subsets of size chunksize in net of size netsize"
|
||||
|
||||
# Create network and identify subnets
|
||||
topo = SingleSwitchTopo( netsize )
|
||||
net = Mininet( topo=topo )
|
||||
net.start()
|
||||
hosts = net.hosts
|
||||
subnets = chunks( hosts, chunksize )
|
||||
|
||||
# Create polling object
|
||||
fds = [ host.stdout.fileno() for host in hosts ]
|
||||
poller = poll()
|
||||
for fd in fds:
|
||||
poller.register( fd, POLLIN )
|
||||
|
||||
# Start pings
|
||||
for subnet in subnets:
|
||||
ips = [ host.IP() for host in subnet ]
|
||||
for host in subnet:
|
||||
startpings( host, ips )
|
||||
|
||||
# Monitor output
|
||||
endTime = time() + seconds
|
||||
while time() < endTime:
|
||||
readable = poller.poll(1000)
|
||||
for fd, _mask in readable:
|
||||
node = Node.outToNode[ fd ]
|
||||
print '%s:' % node.name, node.monitor().strip()
|
||||
|
||||
# Stop pings
|
||||
for host in hosts:
|
||||
host.cmd( 'kill %while' )
|
||||
|
||||
net.stop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
multiping( netsize=20, chunksize=4, seconds=10 )
|
||||
@@ -1,81 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
Simple example of sending output to multiple files and
|
||||
monitoring them
|
||||
"""
|
||||
|
||||
from mininet.topo import SingleSwitchTopo
|
||||
from mininet.net import Mininet
|
||||
from mininet.log import setLogLevel
|
||||
|
||||
from time import time
|
||||
from select import poll, POLLIN
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
def monitorFiles( outfiles, seconds, timeoutms ):
|
||||
"Monitor set of files and return [(host, line)...]"
|
||||
devnull = open( '/dev/null', 'w' )
|
||||
tails, fdToFile, fdToHost = {}, {}, {}
|
||||
for h, outfile in outfiles.iteritems():
|
||||
tail = Popen( [ 'tail', '-f', outfile ],
|
||||
stdout=PIPE, stderr=devnull )
|
||||
fd = tail.stdout.fileno()
|
||||
tails[ h ] = tail
|
||||
fdToFile[ fd ] = tail.stdout
|
||||
fdToHost[ fd ] = h
|
||||
# Prepare to poll output files
|
||||
readable = poll()
|
||||
for t in tails.values():
|
||||
readable.register( t.stdout.fileno(), POLLIN )
|
||||
# Run until a set number of seconds have elapsed
|
||||
endTime = time() + seconds
|
||||
while time() < endTime:
|
||||
fdlist = readable.poll(timeoutms)
|
||||
if fdlist:
|
||||
for fd, _flags in fdlist:
|
||||
f = fdToFile[ fd ]
|
||||
host = fdToHost[ fd ]
|
||||
# Wait for a line of output
|
||||
line = f.readline().strip()
|
||||
yield host, line
|
||||
else:
|
||||
# If we timed out, return nothing
|
||||
yield None, ''
|
||||
for t in tails.values():
|
||||
t.terminate()
|
||||
devnull.close() # Not really necessary
|
||||
|
||||
|
||||
def monitorTest( N=3, seconds=3 ):
|
||||
"Run pings and monitor multiple hosts"
|
||||
topo = SingleSwitchTopo( N )
|
||||
net = Mininet( topo )
|
||||
net.start()
|
||||
hosts = net.hosts
|
||||
print "Starting test..."
|
||||
server = hosts[ 0 ]
|
||||
outfiles, errfiles = {}, {}
|
||||
for h in hosts:
|
||||
# Create and/or erase output files
|
||||
outfiles[ h ] = '/tmp/%s.out' % h.name
|
||||
errfiles[ h ] = '/tmp/%s.err' % h.name
|
||||
h.cmd( 'echo >', outfiles[ h ] )
|
||||
h.cmd( 'echo >', errfiles[ h ] )
|
||||
# Start pings
|
||||
h.cmdPrint('ping', server.IP(),
|
||||
'>', outfiles[ h ],
|
||||
'2>', errfiles[ h ],
|
||||
'&' )
|
||||
print "Monitoring output for", seconds, "seconds"
|
||||
for h, line in monitorFiles( outfiles, seconds, timeoutms=500 ):
|
||||
if h:
|
||||
print '%s: %s' % ( h.name, line )
|
||||
for h in hosts:
|
||||
h.cmd('kill %ping')
|
||||
net.stop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel('info')
|
||||
monitorTest()
|
||||
@@ -1,35 +0,0 @@
|
||||
#!/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()
|
||||
@@ -0,0 +1,79 @@
|
||||
# -*- 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()
|
||||
@@ -0,0 +1,64 @@
|
||||
# -*- 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()
|
||||
@@ -0,0 +1,90 @@
|
||||
# -*- 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()
|
||||
@@ -0,0 +1,109 @@
|
||||
# -*- 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()
|
||||
@@ -0,0 +1,57 @@
|
||||
# -*- 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
|
||||
@@ -0,0 +1,63 @@
|
||||
# -*- 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()
|
||||
@@ -0,0 +1,85 @@
|
||||
# -*- 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()
|
||||
@@ -0,0 +1,53 @@
|
||||
# -*- 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()
|
||||
@@ -0,0 +1,121 @@
|
||||
# -*- 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,36 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
This example monitors a number of hosts using host.popen() and
|
||||
pmonitor()
|
||||
"""
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import CPULimitedHost
|
||||
from mininet.topo import SingleSwitchTopo
|
||||
from mininet.log import setLogLevel
|
||||
from mininet.util import custom, pmonitor
|
||||
|
||||
def monitorhosts( hosts=5, sched='cfs' ):
|
||||
"Start a bunch of pings and monitor them using popen"
|
||||
mytopo = SingleSwitchTopo( hosts )
|
||||
cpu = .5 / hosts
|
||||
myhost = custom( CPULimitedHost, cpu=cpu, sched=sched )
|
||||
net = Mininet( topo=mytopo, host=myhost )
|
||||
net.start()
|
||||
# Start a bunch of pings
|
||||
popens = {}
|
||||
last = net.hosts[ -1 ]
|
||||
for host in net.hosts:
|
||||
popens[ host ] = host.popen( "ping -c5 %s" % last.IP() )
|
||||
last = host
|
||||
# Monitor them and print output
|
||||
for host, line in pmonitor( popens ):
|
||||
if host:
|
||||
print "<%s>: %s" % ( host.name, line.strip() )
|
||||
# Done
|
||||
net.stop()
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
monitorhosts( hosts=5 )
|
||||
@@ -1,33 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"Monitor multiple hosts using popen()/pmonitor()"
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.topo import SingleSwitchTopo
|
||||
from mininet.util import pmonitor
|
||||
from time import time
|
||||
from signal import SIGINT
|
||||
|
||||
def pmonitorTest( N=3, seconds=10 ):
|
||||
"Run pings and monitor multiple hosts using pmonitor"
|
||||
topo = SingleSwitchTopo( N )
|
||||
net = Mininet( topo )
|
||||
net.start()
|
||||
hosts = net.hosts
|
||||
print "Starting test..."
|
||||
server = hosts[ 0 ]
|
||||
popens = {}
|
||||
for h in hosts:
|
||||
popens[ h ] = h.popen('ping', server.IP() )
|
||||
print "Monitoring output for", seconds, "seconds"
|
||||
endTime = time() + seconds
|
||||
for h, line in pmonitor( popens, timeoutms=500 ):
|
||||
if h:
|
||||
print '%s: %s' % ( h.name, line ),
|
||||
if time() >= endTime:
|
||||
for p in popens.values():
|
||||
p.send_signal( SIGINT )
|
||||
net.stop()
|
||||
|
||||
if __name__ == '__main__':
|
||||
pmonitorTest()
|
||||
@@ -0,0 +1,81 @@
|
||||
# -*- 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)
|
||||
@@ -0,0 +1,67 @@
|
||||
# -*- 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)
|
||||
@@ -0,0 +1,111 @@
|
||||
# -*- 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()
|
||||
@@ -1,68 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
Build a simple network from scratch, using mininet primitives.
|
||||
This is more complicated than using the higher-level classes,
|
||||
but it exposes the configuration details and allows customization.
|
||||
|
||||
For most tasks, the higher-level API will be preferable.
|
||||
"""
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import Node
|
||||
from mininet.link import Link
|
||||
from mininet.log import setLogLevel, info
|
||||
from mininet.util import quietRun
|
||||
|
||||
from time import sleep
|
||||
|
||||
def scratchNet( cname='controller', cargs='-v ptcp:' ):
|
||||
"Create network from scratch using Open vSwitch."
|
||||
|
||||
info( "*** Creating nodes\n" )
|
||||
controller = Node( 'c0', inNamespace=False )
|
||||
switch = Node( 's0', inNamespace=False )
|
||||
h0 = Node( 'h0' )
|
||||
h1 = Node( 'h1' )
|
||||
|
||||
info( "*** Creating links\n" )
|
||||
Link( h0, switch )
|
||||
Link( h1, switch )
|
||||
|
||||
info( "*** Configuring hosts\n" )
|
||||
h0.setIP( '192.168.123.1/24' )
|
||||
h1.setIP( '192.168.123.2/24' )
|
||||
info( str( h0 ) + '\n' )
|
||||
info( str( h1 ) + '\n' )
|
||||
|
||||
info( "*** Starting network using Open vSwitch\n" )
|
||||
controller.cmd( cname + ' ' + cargs + '&' )
|
||||
switch.cmd( 'ovs-vsctl del-br dp0' )
|
||||
switch.cmd( 'ovs-vsctl add-br dp0' )
|
||||
for intf in switch.intfs.values():
|
||||
print switch.cmd( 'ovs-vsctl add-port dp0 %s' % intf )
|
||||
|
||||
# Note: controller and switch are in root namespace, and we
|
||||
# can connect via loopback interface
|
||||
switch.cmd( 'ovs-vsctl set-controller dp0 tcp:127.0.0.1:6633' )
|
||||
|
||||
info( '*** Waiting for switch to connect to controller' )
|
||||
while 'is_connected' not in quietRun( 'ovs-vsctl show' ):
|
||||
sleep( 1 )
|
||||
info( '.' )
|
||||
info( '\n' )
|
||||
|
||||
info( "*** Running test\n" )
|
||||
h0.cmdPrint( 'ping -c1 ' + h1.IP() )
|
||||
|
||||
info( "*** Stopping network\n" )
|
||||
controller.cmd( 'kill %' + cname )
|
||||
switch.cmd( 'ovs-vsctl del-br dp0' )
|
||||
switch.deleteIntfs()
|
||||
info( '\n' )
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
info( '*** Scratch network demo (kernel datapath)\n' )
|
||||
Mininet.init()
|
||||
scratchNet()
|
||||
@@ -1,73 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
Build a simple network from scratch, using mininet primitives.
|
||||
This is more complicated than using the higher-level classes,
|
||||
but it exposes the configuration details and allows customization.
|
||||
|
||||
For most tasks, the higher-level API will be preferable.
|
||||
|
||||
This version uses the user datapath and an explicit control network.
|
||||
"""
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import Node
|
||||
from mininet.link import Link
|
||||
from mininet.log import setLogLevel, info
|
||||
|
||||
def linkIntfs( node1, node2 ):
|
||||
"Create link from node1 to node2 and return intfs"
|
||||
link = Link( node1, node2 )
|
||||
return link.intf1, link.intf2
|
||||
|
||||
def scratchNetUser( cname='controller', cargs='ptcp:' ):
|
||||
"Create network from scratch using user switch."
|
||||
|
||||
# It's not strictly necessary for the controller and switches
|
||||
# to be in separate namespaces. For performance, they probably
|
||||
# should be in the root namespace. However, it's interesting to
|
||||
# see how they could work even if they are in separate namespaces.
|
||||
|
||||
info( '*** Creating Network\n' )
|
||||
controller = Node( 'c0' )
|
||||
switch = Node( 's0')
|
||||
h0 = Node( 'h0' )
|
||||
h1 = Node( 'h1' )
|
||||
cintf, sintf = linkIntfs( controller, switch )
|
||||
h0intf, sintf1 = linkIntfs( h0, switch )
|
||||
h1intf, sintf2 = linkIntfs( h1, switch )
|
||||
|
||||
info( '*** Configuring control network\n' )
|
||||
controller.setIP( '10.0.123.1/24', cintf )
|
||||
switch.setIP( '10.0.123.2/24', sintf)
|
||||
|
||||
info( '*** Configuring hosts\n' )
|
||||
h0.setIP( '192.168.123.1/24', h0intf )
|
||||
h1.setIP( '192.168.123.2/24', h1intf )
|
||||
|
||||
info( '*** Network state:\n' )
|
||||
for node in controller, switch, h0, h1:
|
||||
info( str( node ) + '\n' )
|
||||
|
||||
info( '*** Starting controller and user datapath\n' )
|
||||
controller.cmd( cname + ' ' + cargs + '&' )
|
||||
switch.cmd( 'ifconfig lo 127.0.0.1' )
|
||||
intfs = [ str( i ) for i in sintf1, sintf2 ]
|
||||
switch.cmd( 'ofdatapath -i ' + ','.join( intfs ) + ' ptcp: &' )
|
||||
switch.cmd( 'ofprotocol tcp:' + controller.IP() + ' tcp:localhost &' )
|
||||
|
||||
info( '*** Running test\n' )
|
||||
h0.cmdPrint( 'ping -c1 ' + h1.IP() )
|
||||
|
||||
info( '*** Stopping network\n' )
|
||||
controller.cmd( 'kill %' + cname )
|
||||
switch.cmd( 'kill %ofdatapath' )
|
||||
switch.cmd( 'kill %ofprotocol' )
|
||||
switch.deleteIntfs()
|
||||
info( '\n' )
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
info( '*** Scratch network demo (user datapath)\n' )
|
||||
Mininet.init()
|
||||
scratchNetUser()
|
||||
@@ -1,44 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
Simple example of setting network and CPU parameters
|
||||
"""
|
||||
|
||||
from mininet.topo import Topo
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import CPULimitedHost
|
||||
from mininet.link import TCLink
|
||||
from mininet.util import dumpNodeConnections
|
||||
from mininet.log import setLogLevel
|
||||
|
||||
class SingleSwitchTopo(Topo):
|
||||
"Single switch connected to n hosts."
|
||||
def __init__(self, n=2, **opts):
|
||||
Topo.__init__(self, **opts)
|
||||
switch = self.add_switch('s1')
|
||||
for h in range(n):
|
||||
# Each host gets 50%/n of system CPU
|
||||
host = self.add_host('h%s' % (h + 1),
|
||||
cpu=.5 / n)
|
||||
# 10 Mbps, 5ms delay, 10% loss
|
||||
self.add_link(host, switch,
|
||||
bw=10, delay='5ms', loss=10, use_htb=True)
|
||||
|
||||
def perfTest():
|
||||
"Create network and run simple performance test"
|
||||
topo = SingleSwitchTopo(n=4)
|
||||
net = Mininet(topo=topo,
|
||||
host=CPULimitedHost, link=TCLink)
|
||||
net.start()
|
||||
print "Dumping host connections"
|
||||
dumpNodeConnections(net.hosts)
|
||||
print "Testing network connectivity"
|
||||
net.pingAll()
|
||||
print "Testing bandwidth between h1 and h4"
|
||||
h1, h4 = net.getNodeByName('h1', 'h4')
|
||||
net.iperf((h1, h4))
|
||||
net.stop()
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel('info')
|
||||
perfTest()
|
||||
@@ -1,71 +0,0 @@
|
||||
#!/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.link import Link
|
||||
|
||||
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 = Link( root, switch ).intf1
|
||||
root.setIP( ip, prefixLen, intf )
|
||||
# Start network that now includes link to root namespace
|
||||
network.start()
|
||||
# Add routes from root ns to hosts
|
||||
for route in routes:
|
||||
root.cmd( 'route add -net ' + route + ' dev ' + str( intf ) )
|
||||
|
||||
def sshd( network, cmd='/usr/sbin/sshd', opts='-D' ):
|
||||
"Start a network, connect it to root ns, and run sshd on all hosts."
|
||||
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 )
|
||||
@@ -0,0 +1,100 @@
|
||||
# -*- 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()
|
||||
@@ -0,0 +1,105 @@
|
||||
# -*- 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()
|
||||
@@ -1,18 +0,0 @@
|
||||
#!/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 )
|
||||
@@ -1,32 +0,0 @@
|
||||
#!/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()
|
||||
@@ -0,0 +1,44 @@
|
||||
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()
|
||||
@@ -0,0 +1,84 @@
|
||||
# -*- 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()
|
||||
Executable
+186
@@ -0,0 +1,186 @@
|
||||
#!/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
|
||||
@@ -0,0 +1 @@
|
||||
__version__ = '0.5.0'
|
||||
@@ -0,0 +1,56 @@
|
||||
# -*- 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__()
|
||||
@@ -0,0 +1,50 @@
|
||||
# -*- 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()
|
||||
@@ -0,0 +1,75 @@
|
||||
# -*- 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)
|
||||
@@ -0,0 +1,286 @@
|
||||
# -*- 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))
|
||||
@@ -0,0 +1,60 @@
|
||||
# -*- 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))
|
||||
@@ -0,0 +1,132 @@
|
||||
# -*- 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
|
||||
@@ -0,0 +1,203 @@
|
||||
# -*- 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))
|
||||
@@ -0,0 +1,394 @@
|
||||
# -*- 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
|
||||
@@ -0,0 +1,73 @@
|
||||
# -*- 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)
|
||||
@@ -0,0 +1,131 @@
|
||||
# -*- 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
|
||||
@@ -0,0 +1,52 @@
|
||||
# -*- 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()
|
||||
@@ -0,0 +1,376 @@
|
||||
# -*- 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
@@ -0,0 +1,103 @@
|
||||
# -*- 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')
|
||||
@@ -0,0 +1,307 @@
|
||||
# -*- 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
|
||||
@@ -1 +0,0 @@
|
||||
"Docstring to silence pylint; ignores --ignore option for __init__.py"
|
||||
@@ -1,82 +0,0 @@
|
||||
"""
|
||||
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
|
||||
import os
|
||||
|
||||
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 ]
|
||||
|
||||
CGROUPS_LOC='/mnt/cgroups'
|
||||
|
||||
def kill_cgroups(cgroups = None):
|
||||
"""cgroups is a list of cgroup names."""
|
||||
if not cgroups:
|
||||
cpudir = os.path.join(CGROUPS_LOC, 'cpu')
|
||||
if not os.path.exists(cpudir):
|
||||
return
|
||||
else:
|
||||
cgroups = os.listdir(cpudir)
|
||||
info( "killing cgroups: %s" % " ".join(cgroups) )
|
||||
for g in cgroups:
|
||||
if 'sysdefault' in g:
|
||||
continue
|
||||
for resource in os.listdir(CGROUPS_LOC):
|
||||
cgdir = "%s/%s/%s" % (CGROUPS_LOC, resource, g)
|
||||
if os.path.exists(cgdir):
|
||||
sh( "sudo rmdir %s" % cgdir )
|
||||
|
||||
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 OVS datapaths" )
|
||||
dps = sh("ovs-vsctl list-br").split( '\n' )
|
||||
for dp in dps:
|
||||
if dp:
|
||||
sh( 'ovs-vsctl del-br ' + dp )
|
||||
|
||||
info( "*** Removing all links of the pattern foo-ethX\n" )
|
||||
links = sh( "ip link show | egrep -o '(\w+-eth\w+)'" ).split( '\n' )
|
||||
for link in links:
|
||||
if link != '':
|
||||
sh( "ip link del " + link )
|
||||
|
||||
kill_cgroups()
|
||||
|
||||
info( "*** Cleanup complete.\n" )
|
||||
-358
@@ -1,358 +0,0 @@
|
||||
"""
|
||||
A simple command-line interface for Mininet.
|
||||
|
||||
The Mininet CLI provides a simple control console which
|
||||
makes it easy to talk to nodes. For example, the command
|
||||
|
||||
mininet> h27 ifconfig
|
||||
|
||||
runs 'ifconfig' on host h27.
|
||||
|
||||
Having a single console rather than, for example, an xterm for each
|
||||
node is particularly convenient for networks of any reasonable
|
||||
size.
|
||||
|
||||
The CLI automatically substitutes IP addresses for node names,
|
||||
so commands like
|
||||
|
||||
mininet> h2 ping h3
|
||||
|
||||
should work correctly and allow host h2 to ping host h3
|
||||
|
||||
Several useful commands are provided, including the ability to
|
||||
list all nodes ('nodes'), to print out the network topology
|
||||
('net') and to check connectivity ('pingall', 'pingpair')
|
||||
and bandwidth ('iperf'.)
|
||||
"""
|
||||
|
||||
from subprocess import call
|
||||
from cmd import Cmd
|
||||
from os import isatty
|
||||
from select import poll, POLLIN
|
||||
import sys
|
||||
import time
|
||||
|
||||
from mininet.log import info, output, error
|
||||
from mininet.term import makeTerms
|
||||
from mininet.util import quietRun, isShellBuiltin, dumpNodeConnections
|
||||
|
||||
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
|
||||
# Local variable bindings for py command
|
||||
self.locals = { 'net': mininet }
|
||||
self.locals.update( self.nodemap )
|
||||
# Attempt to handle input
|
||||
self.stdin = stdin
|
||||
self.inPoller = poll()
|
||||
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=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."
|
||||
dumpNodeConnections( self.nodelist )
|
||||
|
||||
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.locals )
|
||||
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( node.intfNames() ) ) )
|
||||
|
||||
def do_dump( self, _line ):
|
||||
"Dump node info."
|
||||
for node in self.nodelist:
|
||||
output( '%s\n' % repr( 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) < 1:
|
||||
error( 'usage: dpctl command [arg1] [arg2] ...\n' )
|
||||
return
|
||||
for sw in self.mn.switches:
|
||||
output( '*** ' + sw.name + ' ' + ('-' * 72) + '\n' )
|
||||
output( sw.dpctl( *args ) )
|
||||
|
||||
def do_time( self, line ):
|
||||
"Measure time taken for any command in Mininet."
|
||||
start = time.time()
|
||||
self.onecmd(line)
|
||||
elapsed = time.time() - start
|
||||
self.stdout.write("*** Elapsed time: %0.6f secs\n" % elapsed)
|
||||
|
||||
def 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 not args:
|
||||
return
|
||||
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=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, POLLIN )
|
||||
bothPoller.register( node.stdout, POLLIN )
|
||||
if self.isatty():
|
||||
# Buffer by character, so that interactive
|
||||
# commands sort of work
|
||||
quietRun( 'stty -icanon min 1' )
|
||||
while True:
|
||||
try:
|
||||
bothPoller.poll()
|
||||
# XXX BL: this doesn't quite do what we want.
|
||||
if False and self.inputFile:
|
||||
key = self.inputFile.read( 1 )
|
||||
if key is not '':
|
||||
node.write(key)
|
||||
else:
|
||||
self.inputFile = None
|
||||
if isReadable( self.inPoller ):
|
||||
key = self.stdin.read( 1 )
|
||||
node.write( key )
|
||||
if isReadable( nodePoller ):
|
||||
data = node.monitor()
|
||||
output( data )
|
||||
if not node.waiting:
|
||||
break
|
||||
except KeyboardInterrupt:
|
||||
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
|
||||
-390
@@ -1,390 +0,0 @@
|
||||
"""
|
||||
link.py: interface and link abstractions for mininet
|
||||
|
||||
It seems useful to bundle functionality for interfaces into a single
|
||||
class.
|
||||
|
||||
Also it seems useful to enable the possibility of multiple flavors of
|
||||
links, including:
|
||||
|
||||
- simple veth pairs
|
||||
- tunneled links
|
||||
- patchable links (which can be disconnected and reconnected via a patchbay)
|
||||
- link simulators (e.g. wireless)
|
||||
|
||||
Basic division of labor:
|
||||
|
||||
Nodes: know how to execute commands
|
||||
Intfs: know how to configure themselves
|
||||
Links: know how to connect nodes together
|
||||
|
||||
Intf: basic interface object that can configure itself
|
||||
TCIntf: interface with bandwidth limiting and delay via tc
|
||||
|
||||
Link: basic link class for creating veth pairs
|
||||
"""
|
||||
|
||||
from mininet.log import info, error, debug
|
||||
from mininet.util import makeIntfPair
|
||||
from time import sleep
|
||||
import re
|
||||
|
||||
class Intf( object ):
|
||||
|
||||
"Basic interface object that can configure itself."
|
||||
|
||||
def __init__( self, name, node=None, port=None, link=None, **params ):
|
||||
"""name: interface name (e.g. h1-eth0)
|
||||
node: owning node (where this intf most likely lives)
|
||||
link: parent link if we're part of a link
|
||||
other arguments are passed to config()"""
|
||||
self.node = node
|
||||
self.name = name
|
||||
self.link = link
|
||||
self.mac, self.ip, self.prefixLen = None, None, None
|
||||
# Add to node (and move ourselves if necessary )
|
||||
node.addIntf( self, port=port )
|
||||
# Save params for future reference
|
||||
self.params = params
|
||||
self.config( **params )
|
||||
|
||||
def cmd( self, *args, **kwargs ):
|
||||
"Run a command in our owning node"
|
||||
return self.node.cmd( *args, **kwargs )
|
||||
|
||||
def ifconfig( self, *args ):
|
||||
"Configure ourselves using ifconfig"
|
||||
return self.cmd( 'ifconfig', self.name, *args )
|
||||
|
||||
def setIP( self, ipstr, prefixLen=None ):
|
||||
"""Set our IP address"""
|
||||
# This is a sign that we should perhaps rethink our prefix
|
||||
# mechanism and/or the way we specify IP addresses
|
||||
if '/' in ipstr:
|
||||
self.ip, self.prefixLen = ipstr.split( '/' )
|
||||
return self.ifconfig( ipstr, 'up' )
|
||||
else:
|
||||
self.ip, self.prefixLen = ipstr, prefixLen
|
||||
return self.ifconfig( '%s/%s' % ( ipstr, prefixLen ) )
|
||||
|
||||
def setMAC( self, macstr ):
|
||||
"""Set the MAC address for an interface.
|
||||
macstr: MAC address as string"""
|
||||
self.mac = macstr
|
||||
return ( self.ifconfig( 'down' ) +
|
||||
self.ifconfig( 'hw', 'ether', macstr ) +
|
||||
self.ifconfig( 'up' ) )
|
||||
|
||||
_ipMatchRegex = re.compile( r'\d+\.\d+\.\d+\.\d+' )
|
||||
_macMatchRegex = re.compile( r'..:..:..:..:..:..' )
|
||||
|
||||
def updateIP( self ):
|
||||
"Return updated IP address based on ifconfig"
|
||||
ifconfig = self.ifconfig()
|
||||
ips = self._ipMatchRegex.findall( ifconfig )
|
||||
self.ip = ips[ 0 ] if ips else None
|
||||
return self.ip
|
||||
|
||||
def updateMAC( self ):
|
||||
"Return updated MAC address based on ifconfig"
|
||||
ifconfig = self.ifconfig()
|
||||
macs = self._macMatchRegex.findall( ifconfig )
|
||||
self.mac = macs[ 0 ] if macs else None
|
||||
return self.mac
|
||||
|
||||
def IP( self ):
|
||||
"Return IP address"
|
||||
return self.ip
|
||||
|
||||
def MAC( self ):
|
||||
"Return MAC address"
|
||||
return self.mac
|
||||
|
||||
def isUp( self, setUp=False ):
|
||||
"Return whether interface is up"
|
||||
if setUp:
|
||||
self.ifconfig( 'up' )
|
||||
return "UP" in self.ifconfig()
|
||||
|
||||
def rename( self, newname ):
|
||||
"Rename interface"
|
||||
self.ifconfig( 'down' )
|
||||
result = self.cmd( 'ip link set', self.name, 'name', newname )
|
||||
self.name = newname
|
||||
self.ifconfig( 'up' )
|
||||
return result
|
||||
|
||||
# The reason why we configure things in this way is so
|
||||
# That the parameters can be listed and documented in
|
||||
# the config method.
|
||||
# Dealing with subclasses and superclasses is slightly
|
||||
# annoying, but at least the information is there!
|
||||
|
||||
def setParam( self, results, method, **param ):
|
||||
"""Internal method: configure a *single* parameter
|
||||
results: dict of results to update
|
||||
method: config method name
|
||||
param: arg=value (ignore if value=None)
|
||||
value may also be list or dict"""
|
||||
name, value = param.items()[ 0 ]
|
||||
f = getattr( self, method, None )
|
||||
if not f or value is None:
|
||||
return
|
||||
if type( value ) is list:
|
||||
result = f( *value )
|
||||
elif type( value ) is dict:
|
||||
result = f( **value )
|
||||
else:
|
||||
result = f( value )
|
||||
results[ name ] = result
|
||||
return result
|
||||
|
||||
def config( self, mac=None, ip=None, ifconfig=None,
|
||||
up=True, **_params ):
|
||||
"""Configure Node according to (optional) parameters:
|
||||
mac: MAC address
|
||||
ip: IP address
|
||||
ifconfig: arbitrary interface configuration
|
||||
Subclasses should override this method and call
|
||||
the parent class's config(**params)"""
|
||||
# If we were overriding this method, we would call
|
||||
# the superclass config method here as follows:
|
||||
# r = Parent.config( **params )
|
||||
r = {}
|
||||
self.setParam( r, 'setMAC', mac=mac )
|
||||
self.setParam( r, 'setIP', ip=ip )
|
||||
self.setParam( r, 'isUp', up=up )
|
||||
self.setParam( r, 'ifconfig', ifconfig=ifconfig )
|
||||
self.updateIP()
|
||||
self.updateMAC()
|
||||
return r
|
||||
|
||||
def delete( self ):
|
||||
"Delete interface"
|
||||
self.cmd( 'ip link del ' + self.name )
|
||||
# Does it help to sleep to let things run?
|
||||
sleep( 0.001 )
|
||||
|
||||
def __repr__( self ):
|
||||
return '<%s %s>' % ( self.__class__.__name__, self.name )
|
||||
|
||||
def __str__( self ):
|
||||
return self.name
|
||||
|
||||
|
||||
class TCIntf( Intf ):
|
||||
"""Interface customized by tc (traffic control) utility
|
||||
Allows specification of bandwidth limits (various methods)
|
||||
as well as delay, loss and max queue length"""
|
||||
|
||||
def bwCmds( self, bw=None, speedup=0, use_hfsc=False, use_tbf=False,
|
||||
enable_ecn=False, enable_red=False ):
|
||||
"Return tc commands to set bandwidth"
|
||||
|
||||
cmds, parent = [], ' root '
|
||||
|
||||
if bw and ( bw < 0 or bw > 10000 ):
|
||||
warn( 'Bandwidth', bw, 'is outside range 0..10000 Mbps\n' )
|
||||
|
||||
elif bw is not None:
|
||||
# BL: this seems a bit brittle...
|
||||
if ( speedup > 0 and
|
||||
self.node.name[0:1] == 's' ):
|
||||
bw = speedup
|
||||
# This may not be correct - we should look more closely
|
||||
# at the semantics of burst (and cburst) to make sure we
|
||||
# are specifying the correct sizes. For now I have used
|
||||
# the same settings we had in the mininet-hifi code.
|
||||
if use_hfsc:
|
||||
cmds += [ '%s qdisc add dev %s root handle 1:0 hfsc default 1',
|
||||
'%s class add dev %s parent 1:0 classid 1:1 hfsc sc '
|
||||
+ 'rate %fMbit ul rate %fMbit' % ( bw, bw ) ]
|
||||
elif use_tbf:
|
||||
latency_us = 10 * 1500 * 8 / bw
|
||||
cmds += ['%s qdisc add dev %s root handle 1: tbf ' +
|
||||
'rate %fMbit burst 15000 latency %fus' %
|
||||
( bw, latency_us ) ]
|
||||
else:
|
||||
cmds += [ '%s qdisc add dev %s root handle 1:0 htb default 1',
|
||||
'%s class add dev %s parent 1:0 classid 1:1 htb ' +
|
||||
'rate %fMbit burst 15k' % bw ]
|
||||
parent = ' parent 1:1 '
|
||||
|
||||
# ECN or RED
|
||||
if enable_ecn:
|
||||
cmds += [ '%s qdisc add dev %s' + parent +
|
||||
'handle 10: red limit 1000000 ' +
|
||||
'min 30000 max 35000 avpkt 1500 ' +
|
||||
'burst 20 ' +
|
||||
'bandwidth %fmbit probability 1 ecn' % bw ]
|
||||
parent = ' parent 10: '
|
||||
elif enable_red:
|
||||
cmds += [ '%s qdisc add dev %s' + parent +
|
||||
'handle 10: red limit 1000000 ' +
|
||||
'min 30000 max 35000 avpkt 1500 ' +
|
||||
'burst 20 ' +
|
||||
'bandwidth %fmbit probability 1' % bw ]
|
||||
parent = ' parent 10: '
|
||||
return cmds, parent
|
||||
|
||||
@staticmethod
|
||||
def delayCmds( parent, delay=None, loss=None,
|
||||
max_queue_size=None ):
|
||||
"Internal method: return tc commands for delay and loss"
|
||||
cmds = []
|
||||
if delay and delay < 0:
|
||||
error( 'Negative delay', delay, '\n' )
|
||||
elif loss and ( loss < 0 or loss > 100 ):
|
||||
error( 'Bad loss percentage', loss, '%%\n' )
|
||||
else:
|
||||
# Delay/loss/max queue size
|
||||
netemargs = '%s%s%s' % (
|
||||
'delay %s ' % delay if delay is not None else '',
|
||||
'loss %d ' % loss if loss is not None else '',
|
||||
'limit %d' % max_queue_size if max_queue_size is not None
|
||||
else '' )
|
||||
if netemargs:
|
||||
cmds = [ '%s qdisc add dev %s ' + parent +
|
||||
' handle 10: netem ' +
|
||||
netemargs ]
|
||||
return cmds
|
||||
|
||||
def tc( self, cmd, tc='tc' ):
|
||||
"Execute tc command for our interface"
|
||||
c = cmd % (tc, self) # Add in tc command and our name
|
||||
debug(" *** executing command: %s\n" % c)
|
||||
return self.cmd( c )
|
||||
|
||||
def config( self, bw=None, delay=None, loss=None, disable_gro=True,
|
||||
speedup=0, use_hfsc=False, use_tbf=False, enable_ecn=False,
|
||||
enable_red=False, max_queue_size=None, **params ):
|
||||
"Configure the port and set its properties."
|
||||
|
||||
result = Intf.config( self, **params)
|
||||
|
||||
# Disable GRO
|
||||
if disable_gro:
|
||||
self.cmd( 'ethtool -K %s gro off' % self )
|
||||
|
||||
# Optimization: return if nothing else to configure
|
||||
# Question: what happens if we want to reset things?
|
||||
if ( bw is None and not delay and not loss
|
||||
and max_queue_size is None ):
|
||||
return
|
||||
|
||||
# Clear existing configuration
|
||||
cmds = [ '%s qdisc del dev %s root' ]
|
||||
|
||||
# Bandwidth limits via various methods
|
||||
bwcmds, parent = self.bwCmds( bw=bw, speedup=speedup,
|
||||
use_hfsc=use_hfsc, use_tbf=use_tbf,
|
||||
enable_ecn=enable_ecn,
|
||||
enable_red=enable_red )
|
||||
cmds += bwcmds
|
||||
|
||||
# Delay/loss/max_queue_size using netem
|
||||
cmds += self.delayCmds( delay=delay, loss=loss,
|
||||
max_queue_size=max_queue_size,
|
||||
parent=parent )
|
||||
|
||||
# Ugly but functional: display configuration info
|
||||
stuff = ( ( [ '%.2fMbit' % bw ] if bw is not None else [] ) +
|
||||
( [ '%s delay' % delay ] if delay is not None else [] ) +
|
||||
( ['%d%% loss' % loss ] if loss is not None else [] ) +
|
||||
( [ 'ECN' ] if enable_ecn else [ 'RED' ]
|
||||
if enable_red else [] ) )
|
||||
info( '(' + ' '.join( stuff ) + ') ' )
|
||||
|
||||
# Execute all the commands in our node
|
||||
debug("at map stage w/cmds: %s\n" % cmds)
|
||||
tcoutputs = [ self.tc(cmd) for cmd in cmds ]
|
||||
debug( "cmds:", cmds, '\n' )
|
||||
debug( "outputs:", tcoutputs, '\n' )
|
||||
result[ 'tcoutputs'] = tcoutputs
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class Link( object ):
|
||||
|
||||
"""A basic link is just a veth pair.
|
||||
Other types of links could be tunnels, link emulators, etc.."""
|
||||
|
||||
def __init__( self, node1, node2, port1=None, port2=None,
|
||||
intfName1=None, intfName2=None,
|
||||
intf=Intf, cls1=None, cls2=None, params1=None,
|
||||
params2=None ):
|
||||
"""Create veth link to another node, making two new interfaces.
|
||||
node1: first node
|
||||
node2: second node
|
||||
port1: node1 port number (optional)
|
||||
port2: node2 port number (optional)
|
||||
intf: default interface class/constructor
|
||||
cls1, cls2: optional interface-specific constructors
|
||||
intfName1: node1 interface name (optional)
|
||||
intfName2: node2 interface name (optional)
|
||||
params1: parameters for interface 1
|
||||
params2: parameters for interface 2"""
|
||||
# This is a bit awkward; it seems that having everything in
|
||||
# params would be more orthogonal, but being able to specify
|
||||
# in-line arguments is more convenient!
|
||||
if port1 is None:
|
||||
port1 = node1.newPort()
|
||||
if port2 is None:
|
||||
port2 = node2.newPort()
|
||||
if not intfName1:
|
||||
intfName1 = self.intfName( node1, port1 )
|
||||
if not intfName2:
|
||||
intfName2 = self.intfName( node2, port2 )
|
||||
|
||||
self.makeIntfPair( intfName1, intfName2 )
|
||||
|
||||
if not cls1:
|
||||
cls1 = intf
|
||||
if not cls2:
|
||||
cls2 = intf
|
||||
if not params1:
|
||||
params1 = {}
|
||||
if not params2:
|
||||
params2 = {}
|
||||
|
||||
intf1 = cls1( name=intfName1, node=node1, port=port1,
|
||||
link=self, **params1 )
|
||||
intf2 = cls2( name=intfName2, node=node2, port=port2,
|
||||
link=self, **params2 )
|
||||
|
||||
# All we are is dust in the wind, and our two interfaces
|
||||
self.intf1, self.intf2 = intf1, intf2
|
||||
|
||||
@classmethod
|
||||
def intfName( cls, node, n ):
|
||||
"Construct a canonical interface name node-ethN for interface n."
|
||||
return node.name + '-eth' + repr( n )
|
||||
|
||||
@classmethod
|
||||
def makeIntfPair( cls, intf1, intf2 ):
|
||||
"""Create pair of interfaces
|
||||
intf1: name of interface 1
|
||||
intf2: name of interface 2
|
||||
(override this class method [and possibly delete()]
|
||||
to change link type)"""
|
||||
makeIntfPair( intf1, intf2 )
|
||||
|
||||
def delete( self ):
|
||||
"Delete this link"
|
||||
self.intf1.delete()
|
||||
self.intf2.delete()
|
||||
|
||||
def __str__( self ):
|
||||
return '%s<->%s' % ( self.intf1, self.intf2 )
|
||||
|
||||
class TCLink( Link ):
|
||||
"Link with symmetric TC interfaces configured via opts"
|
||||
def __init__( self, node1, node2, port1=None, port2=None,
|
||||
intfName1=None, intfName2=None, **params ):
|
||||
Link.__init__( self, node1, node2, port1=port1, port2=port2,
|
||||
intfName1=intfName1, intfName2=intfName2,
|
||||
cls1=TCIntf,
|
||||
cls2=TCIntf,
|
||||
params1=params,
|
||||
params2=params)
|
||||
-178
@@ -1,178 +0,0 @@
|
||||
"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
|
||||
@@ -1,68 +0,0 @@
|
||||
"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 )
|
||||
-645
@@ -1,645 +0,0 @@
|
||||
"""
|
||||
|
||||
Mininet: A simple networking testbed for OpenFlow/SDN!
|
||||
|
||||
author: Bob Lantz (rlantz@cs.stanford.edu)
|
||||
author: Brandon Heller (brandonh@stanford.edu)
|
||||
|
||||
Mininet creates scalable OpenFlow test networks by using
|
||||
process-based virtualization and network namespaces.
|
||||
|
||||
Simulated hosts are created as processes in separate network
|
||||
namespaces. This allows a complete OpenFlow network to be simulated on
|
||||
top of a single Linux kernel.
|
||||
|
||||
Each host has:
|
||||
|
||||
A virtual console (pipes to a shell)
|
||||
A virtual interfaces (half of a veth pair)
|
||||
A parent shell (and possibly some child processes) in a namespace
|
||||
|
||||
Hosts have a network interface which is configured via ifconfig/ip
|
||||
link/etc.
|
||||
|
||||
This version supports both the kernel and user space datapaths
|
||||
from the OpenFlow reference implementation (openflowswitch.org)
|
||||
as well as OpenVSwitch (openvswitch.org.)
|
||||
|
||||
In kernel datapath mode, the controller and switches are simply
|
||||
processes in the root namespace.
|
||||
|
||||
Kernel OpenFlow datapaths are instantiated using dpctl(8), and are
|
||||
attached to the one side of a veth pair; the other side resides in the
|
||||
host namespace. In this mode, switch processes can simply connect to the
|
||||
controller via the loopback interface.
|
||||
|
||||
In user datapath mode, the controller and switches can be full-service
|
||||
nodes that live in their own network namespaces and have management
|
||||
interfaces and IP addresses on a control network (e.g. 192.168.123.1,
|
||||
currently routed although it could be bridged.)
|
||||
|
||||
In addition to a management interface, user mode switches also have
|
||||
several switch interfaces, halves of veth pairs whose other halves
|
||||
reside in the host nodes that the switches are connected to.
|
||||
|
||||
Consistent, straightforward naming is important in order to easily
|
||||
identify hosts, switches and controllers, both from the CLI and
|
||||
from program code. Interfaces are named to make it easy to identify
|
||||
which interfaces belong to which node.
|
||||
|
||||
The basic naming scheme is as follows:
|
||||
|
||||
Host nodes are named h1-hN
|
||||
Switch nodes are named s1-sN
|
||||
Controller nodes are named c0-cN
|
||||
Interfaces are named {nodename}-eth0 .. {nodename}-ethN
|
||||
|
||||
Note: If the network topology is created using mininet.topo, then
|
||||
node numbers are unique among hosts and switches (e.g. we have
|
||||
h1..hN and SN..SN+M) and also correspond to their default IP addresses
|
||||
of 10.x.y.z/8 where x.y.z is the base-256 representation of N for
|
||||
hN. This mapping allows easy determination of a node's IP
|
||||
address from its name, e.g. h1 -> 10.0.0.1, h257 -> 10.0.1.1.
|
||||
|
||||
Note also that 10.0.0.1 can often be written as 10.1 for short, e.g.
|
||||
"ping 10.1" is equivalent to "ping 10.0.0.1".
|
||||
|
||||
Currently we wrap the entire network in a 'mininet' object, which
|
||||
constructs a simulated network based on a network topology created
|
||||
using a topology object (e.g. LinearTopo) from mininet.topo or
|
||||
mininet.topolib, and a Controller which the switches will connect
|
||||
to. Several configuration options are provided for functions such as
|
||||
automatically setting MAC addresses, populating the ARP table, or
|
||||
even running a set of terminals to allow direct interaction with nodes.
|
||||
|
||||
After the network is created, it can be started using start(), and a
|
||||
variety of useful tasks maybe performed, including basic connectivity
|
||||
and bandwidth tests and running the mininet CLI.
|
||||
|
||||
Once the network is up and running, test code can easily get access
|
||||
to host and switch objects which can then be used for arbitrary
|
||||
experiments, typically involving running a series of commands on the
|
||||
hosts.
|
||||
|
||||
After all desired tests or activities have been completed, the stop()
|
||||
method may be called to shut down the network.
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import select
|
||||
import signal
|
||||
from time import sleep
|
||||
|
||||
from mininet.cli import CLI
|
||||
from mininet.log import info, error, debug, output
|
||||
from mininet.node import Host, OVSKernelSwitch, Controller
|
||||
from mininet.link import Link, Intf
|
||||
from mininet.util import quietRun, fixLimits, numCores
|
||||
from mininet.util import macColonHex, ipStr, ipParse, netParse, ipAdd
|
||||
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, link=Link, intf=Intf,
|
||||
build=True, xterms=False, cleanup=False, ipBase='10.0.0.0/8',
|
||||
inNamespace=False,
|
||||
autoSetMacs=False, autoStaticArp=False, autoPinCpus=False,
|
||||
listenPort=None ):
|
||||
"""Create Mininet object.
|
||||
topo: Topo (topology) object or None
|
||||
switch: default Switch class
|
||||
host: default Host class/constructor
|
||||
controller: default Controller class/constructor
|
||||
link: default Link class/constructor
|
||||
intf: default Intf class/constructor
|
||||
ipBase: base IP address for hosts,
|
||||
build: build now from topo?
|
||||
xterms: if build now, spawn xterms?
|
||||
cleanup: if build now, cleanup before creating?
|
||||
inNamespace: spawn switches and controller in net namespaces?
|
||||
autoSetMacs: set MAC addrs automatically like IP addresses?
|
||||
autoStaticArp: set all-pairs static MAC addrs?
|
||||
autoPinCpus: pin hosts to (real) cores (requires CPULimitedHost)?
|
||||
listenPort: base listening port to open; will be incremented for
|
||||
each additional switch in the net if inNamespace=False"""
|
||||
self.topo = topo
|
||||
self.switch = switch
|
||||
self.host = host
|
||||
self.controller = controller
|
||||
self.link = link
|
||||
self.intf = intf
|
||||
self.ipBase = ipBase
|
||||
self.ipBaseNum, self.prefixLen = netParse( self.ipBase )
|
||||
self.nextIP = 1 # start for address allocation
|
||||
self.inNamespace = inNamespace
|
||||
self.xterms = xterms
|
||||
self.cleanup = cleanup
|
||||
self.autoSetMacs = autoSetMacs
|
||||
self.autoStaticArp = autoStaticArp
|
||||
self.autoPinCpus = autoPinCpus
|
||||
self.numCores = numCores()
|
||||
self.nextCore = 0 # next core for pinning hosts to CPUs
|
||||
self.listenPort = listenPort
|
||||
|
||||
self.hosts = []
|
||||
self.switches = []
|
||||
self.controllers = []
|
||||
|
||||
self.nameToNode = {} # name to Node (Host/Switch) objects
|
||||
|
||||
self.terms = [] # list of spawned xterm processes
|
||||
|
||||
Mininet.init() # Initialize Mininet if necessary
|
||||
|
||||
self.built = False
|
||||
if topo and build:
|
||||
self.build()
|
||||
|
||||
def addHost( self, name, cls=None, **params ):
|
||||
"""Add host.
|
||||
name: name of host to add
|
||||
cls: custom host class/constructor (optional)
|
||||
params: parameters for host
|
||||
returns: added host"""
|
||||
# Default IP and MAC addresses
|
||||
defaults = { 'ip': ipAdd( self.nextIP,
|
||||
ipBaseNum=self.ipBaseNum,
|
||||
prefixLen=self.prefixLen ) +
|
||||
'/%s' % self.prefixLen }
|
||||
if self.autoSetMacs:
|
||||
defaults[ 'mac'] = macColonHex( self.nextIP )
|
||||
if self.autoPinCpus:
|
||||
defaults[ 'cores' ] = self.nextCore
|
||||
self.nextCore = ( self.nextCore + 1 ) % self.numCores
|
||||
self.nextIP += 1
|
||||
defaults.update( params )
|
||||
if not cls:
|
||||
cls = self.host
|
||||
h = cls( name, **defaults )
|
||||
self.hosts.append( h )
|
||||
self.nameToNode[ name ] = h
|
||||
return h
|
||||
|
||||
def addSwitch( self, name, cls=None, **params ):
|
||||
"""Add switch.
|
||||
name: name of switch to add
|
||||
cls: custom switch class/constructor (optional)
|
||||
returns: added switch
|
||||
side effect: increments listenPort ivar ."""
|
||||
defaults = { 'listenPort': self.listenPort,
|
||||
'inNamespace': self.inNamespace }
|
||||
defaults.update( params )
|
||||
if not cls:
|
||||
cls = self.switch
|
||||
sw = cls( name, **defaults )
|
||||
if not self.inNamespace and self.listenPort:
|
||||
self.listenPort += 1
|
||||
self.switches.append( sw )
|
||||
self.nameToNode[ name ] = sw
|
||||
return sw
|
||||
|
||||
def addController( self, name='c0', controller=None, **params ):
|
||||
"""Add controller.
|
||||
controller: Controller class"""
|
||||
if not controller:
|
||||
controller = self.controller
|
||||
controller_new = controller( name, **params )
|
||||
if controller_new: # allow controller-less setups
|
||||
self.controllers.append( controller_new )
|
||||
self.nameToNode[ name ] = controller_new
|
||||
return controller_new
|
||||
|
||||
# BL: is this better than just using nameToNode[] ?
|
||||
# Should it have a better name?
|
||||
def getNodeByName( self, *args ):
|
||||
"Return node(s) with given name(s)"
|
||||
if len( args ) == 1:
|
||||
return self.nameToNode[ args[ 0 ] ]
|
||||
return [ self.nameToNode[ n ] for n in args ]
|
||||
|
||||
def get( self, *args ):
|
||||
"Convenience alias for getNodeByName"
|
||||
return self.getNodeByName( *args )
|
||||
|
||||
def addLink( self, node1, node2, port1=None, port2=None,
|
||||
cls=None, **params ):
|
||||
""""Add a link from node1 to node2
|
||||
node1: source node
|
||||
node2: dest node
|
||||
port1: source port
|
||||
port2: dest port
|
||||
returns: link object"""
|
||||
defaults = { 'port1': port1,
|
||||
'port2': port2,
|
||||
'intf': self.intf }
|
||||
defaults.update( params )
|
||||
if not cls:
|
||||
cls = self.link
|
||||
return cls( node1, node2, **defaults )
|
||||
|
||||
def configHosts( self ):
|
||||
"Configure a set of hosts."
|
||||
for host in self.hosts:
|
||||
info( host.name + ' ' )
|
||||
intf = host.defaultIntf()
|
||||
if intf:
|
||||
host.configDefault( defaultRoute=intf )
|
||||
else:
|
||||
# Don't configure nonexistent intf
|
||||
host.configDefault( ip=None, mac=None )
|
||||
# You're low priority, dude!
|
||||
# BL: do we want to do this here or not?
|
||||
# May not make sense if we have CPU lmiting...
|
||||
# quietRun( 'renice +18 -p ' + repr( host.pid ) )
|
||||
# This may not be the right place to do this, but
|
||||
# it needs to be done somewhere.
|
||||
host.cmd( 'ifconfig lo up' )
|
||||
info( '\n' )
|
||||
|
||||
def buildFromTopo( self, topo=None ):
|
||||
"""Build mininet from a topology object
|
||||
At the end of this function, everything should be connected
|
||||
and up."""
|
||||
|
||||
# Possibly we should clean up here and/or validate
|
||||
# the topo
|
||||
if self.cleanup:
|
||||
pass
|
||||
|
||||
info( '*** Creating network\n' )
|
||||
|
||||
if not self.controllers:
|
||||
# Add a default controller
|
||||
info( '*** Adding controller\n' )
|
||||
self.addController( 'c0' )
|
||||
|
||||
info( '*** Adding hosts:\n' )
|
||||
for hostName in topo.hosts():
|
||||
self.addHost( hostName, **topo.nodeInfo( hostName ) )
|
||||
info( hostName + ' ' )
|
||||
|
||||
info( '\n*** Adding switches:\n' )
|
||||
for switchName in topo.switches():
|
||||
self.addSwitch( switchName, **topo.nodeInfo( switchName) )
|
||||
info( switchName + ' ' )
|
||||
|
||||
info( '\n*** Adding links:\n' )
|
||||
for srcName, dstName in topo.links(sort=True):
|
||||
src, dst = self.nameToNode[ srcName ], self.nameToNode[ dstName ]
|
||||
params = topo.linkInfo( srcName, dstName )
|
||||
srcPort, dstPort = topo.port( srcName, dstName )
|
||||
self.addLink( src, dst, srcPort, dstPort, **params )
|
||||
info( '(%s, %s) ' % ( src.name, dst.name ) )
|
||||
|
||||
info( '\n' )
|
||||
|
||||
def configureControlNetwork( self ):
|
||||
"Control net config hook: override in subclass"
|
||||
raise Exception( 'configureControlNetwork: '
|
||||
'should be overriden in subclass', self )
|
||||
|
||||
def build( self ):
|
||||
"Build mininet."
|
||||
if self.topo:
|
||||
self.buildFromTopo( self.topo )
|
||||
if ( self.inNamespace ):
|
||||
self.configureControlNetwork()
|
||||
info( '*** Configuring hosts\n' )
|
||||
self.configHosts()
|
||||
if self.xterms:
|
||||
self.startTerms()
|
||||
if self.autoStaticArp:
|
||||
self.staticArp()
|
||||
self.built = True
|
||||
|
||||
def startTerms( self ):
|
||||
"Start a terminal for each node."
|
||||
info( "*** Running terms on %s\n" % os.environ[ 'DISPLAY' ] )
|
||||
cleanUpScreens()
|
||||
self.terms += makeTerms( self.controllers, 'controller' )
|
||||
self.terms += makeTerms( self.switches, 'switch' )
|
||||
self.terms += makeTerms( self.hosts, 'host' )
|
||||
|
||||
def stopXterms( self ):
|
||||
"Kill each xterm."
|
||||
for term in self.terms:
|
||||
os.kill( term.pid, signal.SIGKILL )
|
||||
cleanUpScreens()
|
||||
|
||||
def staticArp( self ):
|
||||
"Add all-pairs ARP entries to remove the need to handle broadcast."
|
||||
for src in self.hosts:
|
||||
for dst in self.hosts:
|
||||
if src != dst:
|
||||
src.setARP( ip=dst.IP(), mac=dst.MAC() )
|
||||
|
||||
def start( self ):
|
||||
"Start controller and switches."
|
||||
if not self.built:
|
||||
self.build()
|
||||
info( '*** Starting controller\n' )
|
||||
for controller in self.controllers:
|
||||
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( 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:
|
||||
info( controller.name + ' ' )
|
||||
controller.stop()
|
||||
info( '\n*** Done\n' )
|
||||
|
||||
def run( self, test, *args, **kwargs ):
|
||||
"Perform a complete start/test/stop cycle."
|
||||
self.start()
|
||||
info( '*** Running test\n' )
|
||||
result = test( *args, **kwargs )
|
||||
self.stop()
|
||||
return result
|
||||
|
||||
def monitor( self, hosts=None, timeoutms=-1 ):
|
||||
"""Monitor a set of hosts (or all hosts by default),
|
||||
and return their output, a line at a time.
|
||||
hosts: (optional) set of hosts to monitor
|
||||
timeoutms: (optional) timeout value in ms
|
||||
returns: iterator which returns host, line"""
|
||||
if hosts is None:
|
||||
hosts = self.hosts
|
||||
poller = select.poll()
|
||||
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
|
||||
|
||||
# XXX These test methods should be moved out of this class.
|
||||
# Probably we should create a tests.py for them
|
||||
|
||||
@staticmethod
|
||||
def _parsePing( pingOutput ):
|
||||
"Parse ping output and return packets sent, received."
|
||||
# Check for downed link
|
||||
if 'connect: Network is unreachable' in pingOutput:
|
||||
return (1, 0)
|
||||
r = r'(\d+) packets transmitted, (\d+) received'
|
||||
m = re.search( r, pingOutput )
|
||||
if m == 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 ''
|
||||
|
||||
# XXX This should be cleaned up
|
||||
|
||||
def iperf( self, hosts=None, l4Type='TCP', udpBw='10M' ):
|
||||
"""Run iperf between two hosts.
|
||||
hosts: list of hosts; if None, uses opposite hosts
|
||||
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()
|
||||
if l4Type == 'TCP':
|
||||
while 'Connected' not in client.cmd(
|
||||
'sh -c "echo A | telnet -e A %s 5001"' % server.IP()):
|
||||
output('waiting for iperf to start up...')
|
||||
sleep(.5)
|
||||
cliout = client.cmd( iperfArgs + '-t 5 -c ' + server.IP() + ' ' +
|
||||
bwArgs )
|
||||
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
|
||||
|
||||
# BL: I think this can be rewritten now that we have
|
||||
# a real link class.
|
||||
def configLinkStatus( self, src, dst, status ):
|
||||
"""Change status of src <-> dst links.
|
||||
src: node name
|
||||
dst: node name
|
||||
status: string {up, down}"""
|
||||
if src not in self.nameToNode:
|
||||
error( 'src not in network: %s\n' % src )
|
||||
elif dst not in self.nameToNode:
|
||||
error( 'dst not in network: %s\n' % dst )
|
||||
else:
|
||||
if type( src ) is str:
|
||||
src = self.nameToNode[ src ]
|
||||
if type( dst ) is str:
|
||||
dst = self.nameToNode[ dst ]
|
||||
connections = src.connectionsTo( dst )
|
||||
if len( connections ) == 0:
|
||||
error( 'src and dst not connected: %s %s\n' % ( src, dst) )
|
||||
for srcIntf, dstIntf in connections:
|
||||
result = srcIntf.ifconfig( status )
|
||||
if result:
|
||||
error( 'link src status change failed: %s\n' % result )
|
||||
result = dstIntf.ifconfig( status )
|
||||
if result:
|
||||
error( 'link dst status change failed: %s\n' % result )
|
||||
|
||||
def interact( self ):
|
||||
"Start network and run our simple CLI."
|
||||
self.start()
|
||||
result = CLI( self )
|
||||
self.stop()
|
||||
return result
|
||||
|
||||
inited = False
|
||||
|
||||
@classmethod
|
||||
def init( cls ):
|
||||
"Initialize Mininet"
|
||||
if cls.inited:
|
||||
return
|
||||
if os.getuid() != 0:
|
||||
# Note: this script must be run as root
|
||||
# Probably we should only sudo when we need
|
||||
# to as per Big Switch's patch
|
||||
print "*** Mininet must run as root."
|
||||
exit( 1 )
|
||||
fixLimits()
|
||||
cls.inited = True
|
||||
|
||||
|
||||
class MininetWithControlNet( Mininet ):
|
||||
|
||||
"""Control network support:
|
||||
|
||||
Create an explicit control network. Currently this is only
|
||||
used/usable with the user datapath.
|
||||
|
||||
Notes:
|
||||
|
||||
1. If the controller and switches are in the same (e.g. root)
|
||||
namespace, they can just use the loopback connection.
|
||||
|
||||
2. If we can get unix domain sockets to work, we can use them
|
||||
instead of an explicit control network.
|
||||
|
||||
3. Instead of routing, we could bridge or use 'in-band' control.
|
||||
|
||||
4. Even if we dispense with this in general, it could still be
|
||||
useful for people who wish to simulate a separate control
|
||||
network (since real networks may need one!)
|
||||
|
||||
5. Basically nobody ever used this code, so it has been moved
|
||||
into its own class.
|
||||
|
||||
6. Ultimately we may wish to extend this to allow us to create a
|
||||
control network which every node's control interface is
|
||||
attached to."""
|
||||
|
||||
def configureControlNetwork( self ):
|
||||
"Configure control network."
|
||||
self.configureRoutedControlNetwork()
|
||||
|
||||
# We still need to figure out the right way to pass
|
||||
# in the control network location.
|
||||
|
||||
def configureRoutedControlNetwork( self, ip='192.168.123.1',
|
||||
prefixLen=16 ):
|
||||
"""Configure a routed control network on controller and switches.
|
||||
For use with the user datapath only right now."""
|
||||
controller = self.controllers[ 0 ]
|
||||
info( controller.name + ' <->' )
|
||||
cip = ip
|
||||
snum = ipParse( ip )
|
||||
for switch in self.switches:
|
||||
info( ' ' + switch.name )
|
||||
link = self.link( switch, controller, port1=0 )
|
||||
sintf, cintf = link.intf1, link.intf2
|
||||
switch.controlIntf = sintf
|
||||
snum += 1
|
||||
while snum & 0xff in [ 0, 255 ]:
|
||||
snum += 1
|
||||
sip = ipStr( snum )
|
||||
cintf.setIP( cip, prefixLen )
|
||||
sintf.setIP( sip, prefixLen )
|
||||
controller.setHostRoute( sip, cintf )
|
||||
switch.setHostRoute( cip, sintf )
|
||||
info( '\n' )
|
||||
info( '*** Testing control network\n' )
|
||||
while not cintf.isUp():
|
||||
info( '*** Waiting for', cintf, 'to come up\n' )
|
||||
sleep( 1 )
|
||||
for switch in self.switches:
|
||||
while not sintf.isUp():
|
||||
info( '*** Waiting for', sintf, 'to come up\n' )
|
||||
sleep( 1 )
|
||||
if self.ping( hosts=[ switch, controller ] ) != 0:
|
||||
error( '*** Error: control network test failed\n' )
|
||||
exit( 1 )
|
||||
info( '\n' )
|
||||
-1093
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user