├── .gitignore ├── LICENSE.md ├── README.md ├── docs ├── .buildinfo ├── .nojekyll ├── _images │ └── l2_topology.png ├── _static │ ├── ajax-loader.gif │ ├── basic.css │ ├── comment-bright.png │ ├── comment-close.png │ ├── comment.png │ ├── css │ │ ├── badge_only.css │ │ ├── fonts │ │ │ ├── Roboto-Slab-Bold.woff │ │ │ ├── Roboto-Slab-Bold.woff2 │ │ │ ├── Roboto-Slab-Regular.woff │ │ │ ├── Roboto-Slab-Regular.woff2 │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.svg │ │ │ ├── fontawesome-webfont.ttf │ │ │ ├── fontawesome-webfont.woff │ │ │ ├── fontawesome-webfont.woff2 │ │ │ ├── lato-bold-italic.woff │ │ │ ├── lato-bold-italic.woff2 │ │ │ ├── lato-bold.woff │ │ │ ├── lato-bold.woff2 │ │ │ ├── lato-normal-italic.woff │ │ │ ├── lato-normal-italic.woff2 │ │ │ ├── lato-normal.woff │ │ │ └── lato-normal.woff2 │ │ └── theme.css │ ├── doctools.js │ ├── documentation_options.js │ ├── down-pressed.png │ ├── down.png │ ├── file.png │ ├── jquery.js │ ├── js │ │ ├── badge_only.js │ │ ├── html5shiv-printshiv.min.js │ │ ├── html5shiv.min.js │ │ └── theme.js │ ├── language_data.js │ ├── minus.png │ ├── plus.png │ ├── pygments.css │ ├── searchtools.js │ ├── underscore.js │ ├── up-pressed.png │ ├── up.png │ └── websupport.js ├── advanced_usage.html ├── genindex.html ├── index.html ├── installation.html ├── introduction.html ├── objects.inv ├── p4utils.html ├── p4utils.mininetlib.cli.html ├── p4utils.mininetlib.html ├── p4utils.mininetlib.log.html ├── p4utils.mininetlib.net.html ├── p4utils.mininetlib.network_API.html ├── p4utils.mininetlib.node.html ├── p4utils.p4run.html ├── p4utils.utils.client.html ├── p4utils.utils.compiler.html ├── p4utils.utils.helper.html ├── p4utils.utils.html ├── p4utils.utils.monitor.html ├── p4utils.utils.p4runtime_API.api.html ├── p4utils.utils.p4runtime_API.bytes_utils.html ├── p4utils.utils.p4runtime_API.context.html ├── p4utils.utils.p4runtime_API.html ├── p4utils.utils.p4runtime_API.p4runtime.html ├── p4utils.utils.p4runtime_API.utils.html ├── p4utils.utils.sswitch_p4runtime_API.html ├── p4utils.utils.sswitch_thrift_API.html ├── p4utils.utils.task_scheduler.html ├── p4utils.utils.thrift_API.html ├── p4utils.utils.topology.html ├── p4utils.utils.traffic_utils.html ├── py-modindex.html ├── search.html ├── searchindex.js └── usage.html ├── docsrc ├── Makefile ├── _images │ └── l2_topology.png ├── advanced_usage.rst ├── conf.py ├── index.rst ├── installation.rst ├── introduction.rst ├── p4utils.mininetlib.cli.rst ├── p4utils.mininetlib.log.rst ├── p4utils.mininetlib.net.rst ├── p4utils.mininetlib.network_API.rst ├── p4utils.mininetlib.node.rst ├── p4utils.mininetlib.rst ├── p4utils.p4run.rst ├── p4utils.rst ├── p4utils.utils.client.rst ├── p4utils.utils.compiler.rst ├── p4utils.utils.helper.rst ├── p4utils.utils.monitor.rst ├── p4utils.utils.p4runtime_API.api.rst ├── p4utils.utils.p4runtime_API.bytes_utils.rst ├── p4utils.utils.p4runtime_API.context.rst ├── p4utils.utils.p4runtime_API.p4runtime.rst ├── p4utils.utils.p4runtime_API.rst ├── p4utils.utils.p4runtime_API.utils.rst ├── p4utils.utils.rst ├── p4utils.utils.sswitch_p4runtime_API.rst ├── p4utils.utils.sswitch_thrift_API.rst ├── p4utils.utils.task_scheduler.rst ├── p4utils.utils.thrift_API.rst ├── p4utils.utils.topology.rst ├── p4utils.utils.traffic_utils.rst └── usage.rst ├── examples ├── README.md ├── adv-net │ ├── controller.py │ ├── default-2.traffic │ ├── default-3.traffic │ ├── default.traffic │ ├── network.py │ ├── performance.py │ ├── routers │ │ ├── r1.conf │ │ ├── r2.conf │ │ ├── r3.conf │ │ └── r4.conf │ ├── switch.p4 │ └── udp.py ├── frrouters │ ├── README.md │ ├── images │ │ └── frr_example_topo.png │ ├── l2_learning_controller.py │ ├── l2_learning_digest.p4 │ ├── network.py │ ├── p4app.json │ ├── receive.py │ ├── routers │ │ ├── r1.conf │ │ ├── r2.conf │ │ ├── r3.conf │ │ ├── r4.conf │ │ └── r5.conf │ ├── send.py │ └── tasks.txt ├── switches │ ├── README.md │ ├── forwarding.p4 │ ├── network.py │ ├── p4app.json │ ├── receive.py │ ├── s1-commands.txt │ ├── s2-commands.txt │ ├── s3-commands.txt │ ├── send.py │ └── tasks.txt └── tofino │ ├── README.md │ ├── controller_1.py │ ├── controller_2.py │ ├── heavy_hitter.p4 │ ├── network.py │ ├── receive.py │ └── send.py ├── install-tools ├── README.md ├── conf_files │ ├── mininet.patch │ └── tmux.conf ├── install-p4-dev.sh ├── old_installs │ └── install-p4-dev.sh └── scripts │ ├── protoinitfix.py │ ├── py3localpath.py │ └── test_veth_intf.py ├── install.sh ├── p4app_example.json ├── p4utils ├── __init__.py ├── mininetlib │ ├── __init__.py │ ├── cli.py │ ├── log.py │ ├── net.py │ ├── network_API.py │ └── node.py ├── p4run.py └── utils │ ├── __init__.py │ ├── client.py │ ├── compiler.py │ ├── helper.py │ ├── monitor.py │ ├── p4runtime_API │ ├── README.md │ ├── __init__.py │ ├── api.py │ ├── bytes_utils.py │ ├── context.py │ ├── p4runtime.py │ └── utils.py │ ├── sswitch_p4runtime_API.py │ ├── sswitch_thrift_API.py │ ├── task_scheduler.py │ ├── thrift_API.py │ ├── topology.py │ └── traffic_utils.py ├── setup.py ├── uninstall.sh ├── utils ├── mx └── mxexec.c └── vm ├── README.md ├── build-qemu.sh ├── build-qemu20.sh ├── build-virtualbox.sh ├── http └── preseed.cfg ├── qemu.pkr.hcl ├── qemu20.pkr.hcl └── virtualbox.pkr.hcl /.gitignore: -------------------------------------------------------------------------------- 1 | # Python byte code 2 | *.pyc 3 | 4 | # Emacs 5 | *~ 6 | ls 7 | 8 | # Compiled JSON 9 | *.json 10 | !*p4app*.json 11 | 12 | # P4 files 13 | *p4i 14 | *p4rt 15 | 16 | # Logs and dumps 17 | log*/ 18 | *.pcap 19 | 20 | *.egg-info/ 21 | 22 | *mxexec.1 23 | *mxexec 24 | 25 | flows/ 26 | *.csv 27 | 28 | # Docs hidden files 29 | docs/.doctrees 30 | 31 | # Packer auto generated files 32 | vm/packer_cache 33 | vm/output-* -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # P4-Utils 2 | 3 | P4-Utils is an extension to *Mininet* that makes P4 networks easier to build, run and debug. P4-utils is strongly 4 | inspired by [p4app](https://github.com/p4lang/p4app). Here we only provide a quick summary of the main information 5 | about P4-Utils. **Check out the [online documentation](https://nsg-ethz.github.io/p4-utils/index.html) 6 | for more details about P4-Utils installation and usage.** 7 | 8 | ## Installation 9 | 10 | In order to work, P4-Utils needs different programs coming from different sources as prerequisites. 11 | Since the installation process can be long and cumbersome, we provide different methods to make the 12 | deployment easier for the users: 13 | 14 | - [virtual machine](#virtual-machine) configured to work with P4-Utils, 15 | - [manual installation](#manual-installation) using an installation script. 16 | 17 | ### Virtual Machine 18 | 19 | P4-Utils can run in a virtual machine to keep its environment separated from the rest of the system. 20 | Moreover, since P4-Utils is only available on Linux, other OS users can run it in a linux VM. 21 | 22 | > Running P4-Utils in a completely separated environment can be beneficial: in this way, installation 23 | > and execution errors, that may arise, will not affect the whole system. For this reason, we **recommend** 24 | > using a virtual machine. 25 | 26 | You can choose to download and use one of our 27 | [preconfigured VMs](https://nsg-ethz.github.io/p4-utils/installation.html#use-our-preconfigured-vm) 28 | or to [build it by yourself](./vm). 29 | 30 | ### Manual Installation 31 | 32 | If you have already installed all the [requirements](#requirements), you can simply 33 | install P4-Utils using the following commands: 34 | 35 | ```bash 36 | git clone https://github.com/nsg-ethz/p4-utils.git 37 | cd p4-utils 38 | sudo ./install.sh 39 | ``` 40 | 41 | > **Attention!** 42 | > The install script will use `pip -e` to install the project in editable mode, meaning that every time you update the files 43 | > in this repository, either by pulling or doing local edits, the changes will automatically take place without the need of 44 | > installing the package again. 45 | 46 | If you want to uninstall run: 47 | 48 | ```bash 49 | sudo ./uninstall.sh 50 | ``` 51 | 52 | This will remove all the scripts that were added to `/usr/bin` as well as uninstall the python package using `pip`. 53 | 54 | #### Requirements 55 | 56 | P4-Utils depends on the following programs in the given order: 57 | 58 | 1. [PI LIBRARY REPOSITORY](https://github.com/p4lang/PI) **is required only for topologies with 59 | P4Runtime switches** 60 | 2. [BEHAVIORAL MODEL (bmv2)](https://github.com/p4lang/behavioral-model) 61 | 3. [p4c](https://github.com/p4lang/p4c) 62 | 4. [Mininet](https://github.com/mininet/mininet) 63 | 5. [FRRouting](https://github.com/FRRouting/FRR) **is required 64 | only for topologies with routers** 65 | 66 | Since the installation process is long and cumbersome, **we provide a [Bash script](./install-tools) 67 | that automatically installs the dependencies.** 68 | 69 | ### How does it work ? 70 | 71 | P4-Utils creates virtual networks using *Mininet* and extended nodes that run P4-enabled switches. To create hosts, 72 | *Mininet* uses a bash process running in a network namespace, in order words, all the processes that run within the 73 | network namespaces have an isolated network stack. Switches are software-based switches like Open vSwitch, Linux Bridge, 74 | or BMV2 switches. Mininet uses virtual ethernet pairs, which live in the Linux kernel to connect the emulated hosts and switches. 75 | 76 | For more information see: 77 | 78 | - [Mininet](http://mininet.org/) 79 | - [Linux Namespaces](https://blogs.igalia.com/dpino/2016/04/10/network-namespaces/) 80 | - [Virtual ethernet interfaces](http://man7.org/linux/man-pages/man4/veth.4.html) 81 | - [BMV2](https://github.com/p4lang/behavioral-model) 82 | - [p4runtime-shell](https://github.com/p4lang/p4runtime-shell) 83 | - [FRRouting](https://frrouting.org/) 84 | - [OVS](https://www.openvswitch.org/) 85 | - [LinuxBridge](https://cloudbuilder.in/blogs/2013/12/02/linux-bridge-virtual-networking/) 86 | 87 | ### Features 88 | 89 | P4-Utils adds on top of *Mininet*: 90 | 91 | - A command-line launcher (`p4run`) to instantiate networks. 92 | - A helper script (`mx`) to run processes in namespaces. 93 | - Custom `P4Host`, `P4Switch`, `P4RuntimeSwitch`, `FFRouter` nodes (based on [BMV2](https://github.com/p4lang/behavioral-model) and [FRRouting](https://github.com/FRRouting/FRR)). 94 | - A very simple way of defining networks using JSON files (see `p4app_example.json` and [related documentation](https://nsg-ethz.github.io/p4-utils/usage.html#json)). 95 | - A very intuitive programmatic way of defining networks using a Python API (see [related documentation](https://nsg-ethz.github.io/p4-utils/usage.html#python)). 96 | - Enhances *Mininet* command-line interface by adding useful commands to manage P4 switches. 97 | - Saves the topology information in an object that can be loaded and queried (see [related documentation](https://nsg-ethz.github.io/p4-utils/advanced_usage.html#topology-database)). 98 | - Re-implementation of the `runtime_CLI` and `simple_switch_CLI` as Python objects to use in controller code. 99 | - Re-implementation of the [p4runtime-shell](https://github.com/p4lang/p4runtime-shell) as Python objects to use in controller code. 100 | -------------------------------------------------------------------------------- /docs/.buildinfo: -------------------------------------------------------------------------------- 1 | # Sphinx build info version 1 2 | # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. 3 | config: 3c53dc7865980dcffb1f8ddcd50488c5 4 | tags: 645f666f9bcd5a90fca523b33c5a78b7 5 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nsg-ethz/p4-utils/83b118bbae530b31cc74e7fa32f9174f7c0a1184/docs/.nojekyll -------------------------------------------------------------------------------- /docs/_images/l2_topology.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nsg-ethz/p4-utils/83b118bbae530b31cc74e7fa32f9174f7c0a1184/docs/_images/l2_topology.png -------------------------------------------------------------------------------- /docs/_static/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nsg-ethz/p4-utils/83b118bbae530b31cc74e7fa32f9174f7c0a1184/docs/_static/ajax-loader.gif -------------------------------------------------------------------------------- /docs/_static/comment-bright.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nsg-ethz/p4-utils/83b118bbae530b31cc74e7fa32f9174f7c0a1184/docs/_static/comment-bright.png -------------------------------------------------------------------------------- /docs/_static/comment-close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nsg-ethz/p4-utils/83b118bbae530b31cc74e7fa32f9174f7c0a1184/docs/_static/comment-close.png -------------------------------------------------------------------------------- /docs/_static/comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nsg-ethz/p4-utils/83b118bbae530b31cc74e7fa32f9174f7c0a1184/docs/_static/comment.png -------------------------------------------------------------------------------- /docs/_static/css/badge_only.css: -------------------------------------------------------------------------------- 1 | .clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} -------------------------------------------------------------------------------- /docs/_static/css/fonts/Roboto-Slab-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nsg-ethz/p4-utils/83b118bbae530b31cc74e7fa32f9174f7c0a1184/docs/_static/css/fonts/Roboto-Slab-Bold.woff -------------------------------------------------------------------------------- /docs/_static/css/fonts/Roboto-Slab-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nsg-ethz/p4-utils/83b118bbae530b31cc74e7fa32f9174f7c0a1184/docs/_static/css/fonts/Roboto-Slab-Bold.woff2 -------------------------------------------------------------------------------- /docs/_static/css/fonts/Roboto-Slab-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nsg-ethz/p4-utils/83b118bbae530b31cc74e7fa32f9174f7c0a1184/docs/_static/css/fonts/Roboto-Slab-Regular.woff -------------------------------------------------------------------------------- /docs/_static/css/fonts/Roboto-Slab-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nsg-ethz/p4-utils/83b118bbae530b31cc74e7fa32f9174f7c0a1184/docs/_static/css/fonts/Roboto-Slab-Regular.woff2 -------------------------------------------------------------------------------- /docs/_static/css/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nsg-ethz/p4-utils/83b118bbae530b31cc74e7fa32f9174f7c0a1184/docs/_static/css/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /docs/_static/css/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nsg-ethz/p4-utils/83b118bbae530b31cc74e7fa32f9174f7c0a1184/docs/_static/css/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /docs/_static/css/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nsg-ethz/p4-utils/83b118bbae530b31cc74e7fa32f9174f7c0a1184/docs/_static/css/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /docs/_static/css/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nsg-ethz/p4-utils/83b118bbae530b31cc74e7fa32f9174f7c0a1184/docs/_static/css/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /docs/_static/css/fonts/lato-bold-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nsg-ethz/p4-utils/83b118bbae530b31cc74e7fa32f9174f7c0a1184/docs/_static/css/fonts/lato-bold-italic.woff -------------------------------------------------------------------------------- /docs/_static/css/fonts/lato-bold-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nsg-ethz/p4-utils/83b118bbae530b31cc74e7fa32f9174f7c0a1184/docs/_static/css/fonts/lato-bold-italic.woff2 -------------------------------------------------------------------------------- /docs/_static/css/fonts/lato-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nsg-ethz/p4-utils/83b118bbae530b31cc74e7fa32f9174f7c0a1184/docs/_static/css/fonts/lato-bold.woff -------------------------------------------------------------------------------- /docs/_static/css/fonts/lato-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nsg-ethz/p4-utils/83b118bbae530b31cc74e7fa32f9174f7c0a1184/docs/_static/css/fonts/lato-bold.woff2 -------------------------------------------------------------------------------- /docs/_static/css/fonts/lato-normal-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nsg-ethz/p4-utils/83b118bbae530b31cc74e7fa32f9174f7c0a1184/docs/_static/css/fonts/lato-normal-italic.woff -------------------------------------------------------------------------------- /docs/_static/css/fonts/lato-normal-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nsg-ethz/p4-utils/83b118bbae530b31cc74e7fa32f9174f7c0a1184/docs/_static/css/fonts/lato-normal-italic.woff2 -------------------------------------------------------------------------------- /docs/_static/css/fonts/lato-normal.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nsg-ethz/p4-utils/83b118bbae530b31cc74e7fa32f9174f7c0a1184/docs/_static/css/fonts/lato-normal.woff -------------------------------------------------------------------------------- /docs/_static/css/fonts/lato-normal.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nsg-ethz/p4-utils/83b118bbae530b31cc74e7fa32f9174f7c0a1184/docs/_static/css/fonts/lato-normal.woff2 -------------------------------------------------------------------------------- /docs/_static/documentation_options.js: -------------------------------------------------------------------------------- 1 | var DOCUMENTATION_OPTIONS = { 2 | URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), 3 | VERSION: '1.0', 4 | LANGUAGE: 'None', 5 | COLLAPSE_INDEX: false, 6 | FILE_SUFFIX: '.html', 7 | HAS_SOURCE: false, 8 | SOURCELINK_SUFFIX: '.txt', 9 | NAVIGATION_WITH_KEYS: false, 10 | }; -------------------------------------------------------------------------------- /docs/_static/down-pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nsg-ethz/p4-utils/83b118bbae530b31cc74e7fa32f9174f7c0a1184/docs/_static/down-pressed.png -------------------------------------------------------------------------------- /docs/_static/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nsg-ethz/p4-utils/83b118bbae530b31cc74e7fa32f9174f7c0a1184/docs/_static/down.png -------------------------------------------------------------------------------- /docs/_static/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nsg-ethz/p4-utils/83b118bbae530b31cc74e7fa32f9174f7c0a1184/docs/_static/file.png -------------------------------------------------------------------------------- /docs/_static/js/badge_only.js: -------------------------------------------------------------------------------- 1 | !function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=4)}({4:function(e,t,r){}}); -------------------------------------------------------------------------------- /docs/_static/js/html5shiv-printshiv.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @preserve HTML5 Shiv 3.7.3-pre | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | */ 4 | !function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=y.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=y.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),y.elements=c+" "+a,j(b)}function f(a){var b=x[a[v]];return b||(b={},w++,a[v]=w,x[w]=b),b}function g(a,c,d){if(c||(c=b),q)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():u.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||t.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),q)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return y.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(y,b.frag)}function j(a){a||(a=b);var d=f(a);return!y.shivCSS||p||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),q||i(a,d),a}function k(a){for(var b,c=a.getElementsByTagName("*"),e=c.length,f=RegExp("^(?:"+d().join("|")+")$","i"),g=[];e--;)b=c[e],f.test(b.nodeName)&&g.push(b.applyElement(l(b)));return g}function l(a){for(var b,c=a.attributes,d=c.length,e=a.ownerDocument.createElement(A+":"+a.nodeName);d--;)b=c[d],b.specified&&e.setAttribute(b.nodeName,b.nodeValue);return e.style.cssText=a.style.cssText,e}function m(a){for(var b,c=a.split("{"),e=c.length,f=RegExp("(^|[\\s,>+~])("+d().join("|")+")(?=[[\\s,>+~#.:]|$)","gi"),g="$1"+A+"\\:$2";e--;)b=c[e]=c[e].split("}"),b[b.length-1]=b[b.length-1].replace(f,g),c[e]=b.join("}");return c.join("{")}function n(a){for(var b=a.length;b--;)a[b].removeNode()}function o(a){function b(){clearTimeout(g._removeSheetTimer),d&&d.removeNode(!0),d=null}var d,e,g=f(a),h=a.namespaces,i=a.parentWindow;return!B||a.printShived?a:("undefined"==typeof h[A]&&h.add(A),i.attachEvent("onbeforeprint",function(){b();for(var f,g,h,i=a.styleSheets,j=[],l=i.length,n=Array(l);l--;)n[l]=i[l];for(;h=n.pop();)if(!h.disabled&&z.test(h.media)){try{f=h.imports,g=f.length}catch(o){g=0}for(l=0;g>l;l++)n.push(f[l]);try{j.push(h.cssText)}catch(o){}}j=m(j.reverse().join("")),e=k(a),d=c(a,j)}),i.attachEvent("onafterprint",function(){n(e),clearTimeout(g._removeSheetTimer),g._removeSheetTimer=setTimeout(b,500)}),a.printShived=!0,a)}var p,q,r="3.7.3",s=a.html5||{},t=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,u=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,v="_html5shiv",w=0,x={};!function(){try{var a=b.createElement("a");a.innerHTML="",p="hidden"in a,q=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){p=!0,q=!0}}();var y={elements:s.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:r,shivCSS:s.shivCSS!==!1,supportsUnknownElements:q,shivMethods:s.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=y,j(b);var z=/^$|\b(?:all|print)\b/,A="html5shiv",B=!q&&function(){var c=b.documentElement;return!("undefined"==typeof b.namespaces||"undefined"==typeof b.parentWindow||"undefined"==typeof c.applyElement||"undefined"==typeof c.removeNode||"undefined"==typeof a.attachEvent)}();y.type+=" print",y.shivPrint=o,o(b),"object"==typeof module&&module.exports&&(module.exports=y)}("undefined"!=typeof window?window:this,document); -------------------------------------------------------------------------------- /docs/_static/js/html5shiv.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | */ 4 | !function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.3-pre",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML="",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b),"object"==typeof module&&module.exports&&(module.exports=t)}("undefined"!=typeof window?window:this,document); -------------------------------------------------------------------------------- /docs/_static/js/theme.js: -------------------------------------------------------------------------------- 1 | !function(n){var e={};function t(i){if(e[i])return e[i].exports;var o=e[i]={i:i,l:!1,exports:{}};return n[i].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=n,t.c=e,t.d=function(n,e,i){t.o(n,e)||Object.defineProperty(n,e,{enumerable:!0,get:i})},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.t=function(n,e){if(1&e&&(n=t(n)),8&e)return n;if(4&e&&"object"==typeof n&&n&&n.__esModule)return n;var i=Object.create(null);if(t.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:n}),2&e&&"string"!=typeof n)for(var o in n)t.d(i,o,function(e){return n[e]}.bind(null,o));return i},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,"a",e),e},t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.p="",t(t.s=0)}([function(n,e,t){t(1),n.exports=t(3)},function(n,e,t){(function(){var e="undefined"!=typeof window?window.jQuery:t(2);n.exports.ThemeNav={navBar:null,win:null,winScroll:!1,winResize:!1,linkScroll:!1,winPosition:0,winHeight:null,docHeight:null,isRunning:!1,enable:function(n){var t=this;void 0===n&&(n=!0),t.isRunning||(t.isRunning=!0,e((function(e){t.init(e),t.reset(),t.win.on("hashchange",t.reset),n&&t.win.on("scroll",(function(){t.linkScroll||t.winScroll||(t.winScroll=!0,requestAnimationFrame((function(){t.onScroll()})))})),t.win.on("resize",(function(){t.winResize||(t.winResize=!0,requestAnimationFrame((function(){t.onResize()})))})),t.onResize()})))},enableSticky:function(){this.enable(!0)},init:function(n){n(document);var e=this;this.navBar=n("div.wy-side-scroll:first"),this.win=n(window),n(document).on("click","[data-toggle='wy-nav-top']",(function(){n("[data-toggle='wy-nav-shift']").toggleClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift")})).on("click",".wy-menu-vertical .current ul li a",(function(){var t=n(this);n("[data-toggle='wy-nav-shift']").removeClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift"),e.toggleCurrent(t),e.hashChange()})).on("click","[data-toggle='rst-current-version']",(function(){n("[data-toggle='rst-versions']").toggleClass("shift-up")})),n("table.docutils:not(.field-list,.footnote,.citation)").wrap("
"),n("table.docutils.footnote").wrap("
"),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(''),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}if(t.length>0){$(".wy-menu-vertical .current").removeClass("current").attr("aria-expanded","false"),t.addClass("current").attr("aria-expanded","true"),t.closest("li.toctree-l1").parent().addClass("current").attr("aria-expanded","true");for(let n=1;n<=10;n++)t.closest("li.toctree-l"+n).addClass("current").attr("aria-expanded","true");t[0].scrollIntoView()}}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current").attr("aria-expanded","false"),e.siblings().find("li.current").removeClass("current").attr("aria-expanded","false");var t=e.find("> ul li");t.length&&(t.removeClass("current").attr("aria-expanded","false"),e.toggleClass("current").attr("aria-expanded",(function(n,e){return"true"==e?"false":"true"})))}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t 2 | 3 | 4 | 5 | 6 | Introduction — P4-Utils 1.0 documentation 7 | 8 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 65 | 66 |
70 | 71 |
72 |
73 |
74 | 81 |
82 |
83 |
84 |
85 | 86 |
87 |

Introduction

88 |
89 |

About P4-Utils

90 |

P4-Utils is a Python package that allows the user to create and test virtual networks 91 | that can include P4 switches. The network creation capabilities are inherited from Mininet, 92 | whereas the P4 targets are taken from the behavioral-model.

93 |

The behavioral-model is a collection of P4 software switches. It is meant to be used as a 94 | tool for developing, testing and debugging P4 data planes and control plane software 95 | written for them. Indeed, P4 programmable hardware switches are still expensive 96 | and operate them might still be somehow cumbersome.

97 |

Mininet, on the other hand, is a very powerful network emulation framework. Indeed, it can 98 | efficiently virtualize nodes (hosts and switches) in a network by exploiting Linux kernel 99 | features. This allows P4-Utils to create a realistic environment in which P4 switches can 100 | be connected together and tested.

101 |
102 |
103 |

P4 Language

104 |

P4 is a domain-specific programming language that specifies how data plane devices 105 | process packets. The key factor that makes it a very useful tool is that it has been 106 | designed to be target-independent (i.e. it can be used with a wide range of both 107 | hardware-based and software-based architecture) and protocol-independent (i.e. targets 108 | are not bound to any specific network protocol).

109 |
110 |
111 |

Previous Work

112 |

The application p4app is the ancestor of P4-Utils: the former was created by the P4 113 | community to provide a testing and prototyping platform based on P4 language, whereas the latter 114 | is an adaptation made by the ETH Networked Systems Group to simplify the application use 115 | and have a tool for P4 teaching.

116 |
117 |
118 | 119 | 120 |
121 |
122 |
126 | 127 |
128 | 129 |
130 |

© Copyright 2023, Networked Systems Group (NSG@ETH).

131 |
132 | 133 | Built with Sphinx using a 134 | theme 135 | provided by Read the Docs. 136 | 137 | 138 |
139 |
140 |
141 |
142 |
143 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /docs/objects.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nsg-ethz/p4-utils/83b118bbae530b31cc74e7fa32f9174f7c0a1184/docs/objects.inv -------------------------------------------------------------------------------- /docs/p4utils.mininetlib.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | p4utils.mininetlib package — P4-Utils 1.0 documentation 7 | 8 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 73 | 74 |
78 | 79 |
80 |
81 |
82 | 90 |
91 |
92 |
93 |
94 | 95 |
96 |

p4utils.mininetlib package

97 | 109 |
110 |

Module contents

111 |
112 |
113 | 114 | 115 |
116 |
117 |
121 | 122 |
123 | 124 |
125 |

© Copyright 2023, Networked Systems Group (NSG@ETH).

126 |
127 | 128 | Built with Sphinx using a 129 | theme 130 | provided by Read the Docs. 131 | 132 | 133 |
134 |
135 |
136 |
137 |
138 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /docs/p4utils.utils.p4runtime_API.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | p4utils.utils.p4runtime_API package — P4-Utils 1.0 documentation 7 | 8 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 74 | 75 |
79 | 80 |
81 |
82 |
83 | 92 |
93 |
94 |
95 |
96 | 97 |
98 |

p4utils.utils.p4runtime_API package

99 | 111 |
112 |

Module contents

113 |
114 |
115 | 116 | 117 |
118 |
119 |
123 | 124 |
125 | 126 |
127 |

© Copyright 2023, Networked Systems Group (NSG@ETH).

128 |
129 | 130 | Built with Sphinx using a 131 | theme 132 | provided by Read the Docs. 133 | 134 | 135 |
136 |
137 |
138 |
139 |
140 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /docs/search.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Search — P4-Utils 1.0 documentation 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 61 | 62 |
66 | 67 |
68 |
69 |
70 |
    71 |
  • 72 | 73 |
  • 74 |
  • 75 |
76 |
77 |
78 |
79 |
80 | 81 | 88 | 89 | 90 |
91 | 92 |
93 | 94 |
95 |
96 |
97 | 98 |
99 | 100 |
101 |

© Copyright 2023, Networked Systems Group (NSG@ETH).

102 |
103 | 104 | Built with Sphinx using a 105 | theme 106 | provided by Read the Docs. 107 | 108 | 109 |
110 |
111 |
112 |
113 |
114 | 119 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /docsrc/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = P4-Utils 8 | SOURCEDIR = . 9 | BUILDDIR = ../docs 10 | NUM_CPUS = `grep -c ^processor /proc/cpuinfo` 11 | 12 | # Put it first so that "make" without argument is like "make help". 13 | help: 14 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 15 | 16 | .PHONY: help Makefile 17 | 18 | # Catch html target 19 | html: 20 | @$(SPHINXBUILD) -E -j $(NUM_CPUS) -b html "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | 22 | # Catch-all target: route all unknown targets to Sphinx using the new 23 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 24 | %: Makefile 25 | @$(SPHINXBUILD) -j $(NUM_CPUS) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docsrc/_images/l2_topology.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nsg-ethz/p4-utils/83b118bbae530b31cc74e7fa32f9174f7c0a1184/docsrc/_images/l2_topology.png -------------------------------------------------------------------------------- /docsrc/index.rst: -------------------------------------------------------------------------------- 1 | .. P4-Utils documentation master file, created by 2 | sphinx-quickstart on Wed Aug 11 09:01:10 2021. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to P4-Utils's documentation! 7 | ==================================== 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :caption: General Documentation 12 | 13 | introduction 14 | installation 15 | usage 16 | advanced_usage 17 | 18 | .. toctree:: 19 | :maxdepth: 2 20 | :caption: API Reference 21 | 22 | p4utils 23 | 24 | 25 | Indices and tables 26 | ================== 27 | 28 | * :ref:`genindex` 29 | * :ref:`modindex` 30 | * :ref:`search` 31 | -------------------------------------------------------------------------------- /docsrc/installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | In order to work, P4-Utils needs different programs coming from different sources as prerequisites. 5 | Since the installation process can be long and cumbersome, we provide different methods to make the 6 | deployment easier for the users: 7 | 8 | - __ #virtual-machine 9 | 10 | `virtual machine`__ configured to work with P4-Utils, 11 | - __ #manual-installation 12 | 13 | `manual installation`__ using an installation script. 14 | 15 | __ #virtual-machine 16 | 17 | .. Note:: 18 | Running P4-Utils in a completely separated environment can be beneficial: in this way, installation 19 | and execution errors, that may arise, will not affect the whole system. For this reason, we **recommend** 20 | using a `virtual machine`__. 21 | 22 | Virtual Machine 23 | --------------- 24 | 25 | .. _VirtualBox: https://www.virtualbox.org/ 26 | 27 | .. _QEMU: https://www.qemu.org/ 28 | 29 | P4-Utils can run in a virtual machine to keep its environment separated from the rest of the system. 30 | Moreover, since P4-Utils is only available on Linux, other OS users can run it in a linux VM. 31 | We provide two different solutions for the P4-Utils VM and both are supported by a wide range of 32 | operating systems: 33 | 34 | - VirtualBox_ 35 | - QEMU_ 36 | 37 | __ #use-our-preconfigured-vm 38 | __ #build-your-own-vm 39 | 40 | You can choose to download and use one of our `preconfigured VMs`__ or to `build it by yourself`__. 41 | 42 | .. Important:: 43 | Whether you are building your own VM or you are using the preconfigured images, you still 44 | need to install one of the above virtualizer according to your VM choice. 45 | 46 | Build your own VM 47 | +++++++++++++++++ 48 | 49 | .. _Packer: https://www.packer.io/ 50 | 51 | To get started, you need to install the required software: 52 | 53 | - VirtualBox_ or QEMU_ 54 | - Packer_ 55 | 56 | .. Note:: 57 | Packer is a handy framework designed to automatically build custom VM images. 58 | 59 | Clone the P4-Utils repository:: 60 | 61 | git clone https://github.com/nsg-ethz/p4-utils 62 | 63 | Go to the Packer configurations folder:: 64 | 65 | cd p4-utils/vm 66 | 67 | If you want to build the *VirtualBox VM*, execute:: 68 | 69 | ./build-virtualbox.sh [--cpus 4] [--disk_size 25000] [--memory 4000] [--vm_name p4] [--username p4] [--password p4] 70 | 71 | On the other hand, if you prefer the *QEMU VM*, run:: 72 | 73 | ./build-qemu.sh [--cpus 4] [--disk_size 25000] [--memory 4000] [--vm_name p4] [--username p4] [--password p4] 74 | 75 | .. Important:: 76 | The default VMs configuration parameters are shown above. If you do not specify anything, 77 | they will be used to build your VM. However, please pass to the scripts the parameters 78 | that best fit your needs. In particular, we have that: 79 | 80 | - ``--cpus`` specifies the **number of cores** to use, 81 | - ``--disk_size`` is the **size of the disk** reserved by the VM in MBytes, 82 | - ``--memory`` is the amount of **RAM** to assign to the VM in MBytes, 83 | - ``--vm_name`` is the **name of the VM**, 84 | - ``--username`` is the **login username**, 85 | - ``--password`` is the **login password**. 86 | 87 | The building process will generate the following files: 88 | 89 | - If you chose the QEMU VM, in ``p4-utils/vm/output-ubuntu18044_qemu`` you will find 90 | a ``.qcow2`` file to use to set up your VM. 91 | - If you chose the VirtualBox VM, in ``p4-utils/vm/output-ubuntu18044_vb`` you will 92 | find an ``.ova`` file to import in the VirtualBox VM manager. 93 | 94 | Use our preconfigured VM 95 | ++++++++++++++++++++++++ 96 | 97 | To download our preconfiugred VMs, please click on the folllwing links: 98 | 99 | .. Important:: 100 | VM credentials: 101 | ``username:`` **p4** 102 | ``password:`` **p4** 103 | 104 | - __ https://polybox.ethz.ch/index.php/s/QlrfHm7uYw6vISe 105 | 106 | `QEMU VM (Ubuntu 20)`__ 107 | 108 | - __ https://polybox.ethz.ch/index.php/s/9orcmetpNxOAhlI 109 | 110 | `Deprecated: QEMU VM (UBuntu 18.04)`__ 111 | 112 | - __ # 113 | 114 | `VirtualBox VM (unavailable)`__ 115 | 116 | 117 | 118 | Manual Installation 119 | ------------------- 120 | 121 | __ #prerequisites 122 | 123 | If you have already installed all the `requirements`__, you can simply 124 | install P4-Utils using the following commands:: 125 | 126 | git clone https://github.com/nsg-ethz/p4-utils 127 | cd p4-utils 128 | sudo ./install.sh 129 | 130 | You can also uninstall it by running the command:: 131 | 132 | sudo ./uninstall.sh 133 | 134 | Prerequisites 135 | +++++++++++++ 136 | 137 | P4-Utils depends on the following programs in the given order: 138 | 139 | 1. __ https://github.com/p4lang/PI 140 | 141 | `PI LIBRARY REPOSITORY`__ provides an implementation framework 142 | for a P4Runtime server. **It is required only for topologies with 143 | P4Runtime switches.** 144 | 2. __ https://github.com/p4lang/behavioral-model 145 | 146 | `BEHAVIORAL MODEL (bmv2)`__ contains the software implementation several 147 | variations of the behavioral model (e.g. ``simple_switch`` and 148 | ``simple_switch_grpc``). 149 | 3. __ https://github.com/p4lang/p4c 150 | 151 | `p4c`__ is a reference compiler for the P4 programming language that 152 | supports both **P4_14** and **P4_16**. 153 | 4. __ https://github.com/mininet/mininet 154 | 155 | `Mininet`__ allows to create a realistic virtual network, running real 156 | kernel, switch and application code, on a single machine (VM, cloud or native). 157 | 5. __ https://github.com/FRRouting/FRR 158 | 159 | `FRRouting`__ is a free and open source Internet routing protocol suite 160 | for Linux and Unix platforms. It implements BGP, OSPF, RIP, IS-IS, PIM, 161 | LDP, BFD, Babel, PBR, OpenFabric and VRRP, with alpha support for EIGRP 162 | and NHRP. Router nodes in P4-Utils are based on FRRouting. **It is required 163 | only for topologies with routers.** 164 | 165 | __ https://github.com/nsg-ethz/p4-utils/blob/master/install-tools/install-p4-dev.sh 166 | 167 | The manual installation process is quite long and cumbersome because of the 168 | dependencies that are needed by P4-Utils. For this reason, we provide a `Bash 169 | script`__ that automatically goes through every step. 170 | 171 | .. Warning:: 172 | The script has been tested with **Ubuntu 20.04 and Ubuntu 22.04** and the compiler 173 | **GCC 9.4**. 174 | 175 | .. Important:: 176 | With the following installation methods, you will download and install *Mininet* 177 | and the P4-Tools suite (P4-Utils, P4-Learning and their dependencies) in your 178 | user's home directory. 179 | 180 | One-Step Automated Install 181 | __________________________ 182 | 183 | To get started quickly and conveniently, you may want to install the P4-Tools suite 184 | using the following command:: 185 | 186 | curl -sSL https://raw.githubusercontent.com/nsg-ethz/p4-utils/master/install-tools/install-p4-dev.sh | bash 187 | 188 | Alternative Installation Method 189 | _______________________________ 190 | 191 | The main drawback of piping to `bash` is that you cannot review the code 192 | that is going to run on your system. Therefore, we provide this alternative 193 | methods that allows you to inspect the intallation script:: 194 | 195 | wget -O install-p4-dev.sh https://raw.githubusercontent.com/nsg-ethz/p4-utils/master/install-tools/install-p4-dev.sh 196 | bash install-p4-dev.sh 197 | -------------------------------------------------------------------------------- /docsrc/introduction.rst: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | 4 | About P4-Utils 5 | -------------- 6 | 7 | .. _Mininet: http://mininet.org/ 8 | .. _behavioral-model: https://github.com/p4lang/behavioral-model 9 | 10 | P4-Utils is a Python package that allows the user to create and test virtual networks 11 | that can include P4 switches. The network creation capabilities are inherited from Mininet_, 12 | whereas the P4 targets are taken from the behavioral-model_. 13 | 14 | The *behavioral-model* is a collection of P4 software switches. It is meant to be used as a 15 | tool for developing, testing and debugging P4 data planes and control plane software 16 | written for them. Indeed, P4 programmable hardware switches are still expensive 17 | and operate them might still be somehow cumbersome. 18 | 19 | *Mininet*, on the other hand, is a very powerful network emulation framework. Indeed, it can 20 | efficiently virtualize nodes (hosts and switches) in a network by exploiting Linux kernel 21 | features. This allows P4-Utils to create a realistic environment in which P4 switches can 22 | be connected together and tested. 23 | 24 | P4 Language 25 | ----------- 26 | 27 | *P4* is a domain-specific programming language that specifies how data plane devices 28 | process packets. The key factor that makes it a very useful tool is that it has been 29 | designed to be *target-independent* (i.e. it can be used with a wide range of both 30 | hardware-based and software-based architecture) and *protocol-independent* (i.e. targets 31 | are not bound to any specific network protocol). 32 | 33 | Previous Work 34 | ------------- 35 | 36 | .. _p4app: https://github.com/p4lang/p4app 37 | 38 | The application p4app_ is the ancestor of P4-Utils: the former was created by the P4 39 | community to provide a testing and prototyping platform based on P4 language, whereas the latter 40 | is an adaptation made by the ETH Networked Systems Group to simplify the application use 41 | and have a tool for P4 teaching. -------------------------------------------------------------------------------- /docsrc/p4utils.mininetlib.cli.rst: -------------------------------------------------------------------------------- 1 | p4utils\.mininetlib\.cli module 2 | =============================== 3 | 4 | .. automodule:: p4utils.mininetlib.cli 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docsrc/p4utils.mininetlib.log.rst: -------------------------------------------------------------------------------- 1 | p4utils\.mininetlib\.log module 2 | =============================== 3 | 4 | .. automodule:: p4utils.mininetlib.log 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docsrc/p4utils.mininetlib.net.rst: -------------------------------------------------------------------------------- 1 | p4utils\.mininetlib\.net module 2 | =============================== 3 | 4 | .. automodule:: p4utils.mininetlib.net 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docsrc/p4utils.mininetlib.network_API.rst: -------------------------------------------------------------------------------- 1 | p4utils\.mininetlib\.network\_API module 2 | ======================================== 3 | 4 | .. automodule:: p4utils.mininetlib.network_API 5 | :members: 6 | :undoc-members: 7 | :exclude-members: cleanup, is_multigraph, save_topology, compile, program_switches, 8 | program_hosts, exec_scripts, start_scheduler, start_schedulers, 9 | distribute_tasks, start_net_cli, module, node_ports, node_intfs, 10 | switch_ids, thrift_ports, grpc_ports, mac_addresses, ip_addresses, 11 | check_host_valid_ip_from_name, intf_name, auto_switch_id, auto_grpc_port, 12 | auto_thrift_port, auto_port_num, auto_mac_address, auto_ip_address, 13 | auto_assignment, get_default_intf, is_default_intf 14 | :show-inheritance: 15 | -------------------------------------------------------------------------------- /docsrc/p4utils.mininetlib.node.rst: -------------------------------------------------------------------------------- 1 | p4utils\.mininetlib\.node module 2 | ================================ 3 | 4 | .. automodule:: p4utils.mininetlib.node 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docsrc/p4utils.mininetlib.rst: -------------------------------------------------------------------------------- 1 | p4utils\.mininetlib package 2 | =========================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | .. toctree:: 8 | 9 | p4utils.mininetlib.cli 10 | p4utils.mininetlib.log 11 | p4utils.mininetlib.net 12 | p4utils.mininetlib.network_API 13 | p4utils.mininetlib.node 14 | 15 | Module contents 16 | --------------- 17 | 18 | .. automodule:: p4utils.mininetlib 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | -------------------------------------------------------------------------------- /docsrc/p4utils.p4run.rst: -------------------------------------------------------------------------------- 1 | p4utils\.p4run module 2 | ===================== 3 | 4 | .. automodule:: p4utils.p4run 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docsrc/p4utils.rst: -------------------------------------------------------------------------------- 1 | P4-Utils API reference 2 | ====================== 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | :maxdepth: 3 9 | 10 | p4utils.mininetlib 11 | p4utils.utils 12 | 13 | Submodules 14 | ---------- 15 | 16 | .. toctree:: 17 | :maxdepth: 3 18 | 19 | p4utils.p4run 20 | 21 | Module contents 22 | --------------- 23 | 24 | .. automodule:: p4utils 25 | :members: 26 | :undoc-members: 27 | :show-inheritance: 28 | -------------------------------------------------------------------------------- /docsrc/p4utils.utils.client.rst: -------------------------------------------------------------------------------- 1 | p4utils\.utils\.client module 2 | ============================= 3 | 4 | .. automodule:: p4utils.utils.client 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docsrc/p4utils.utils.compiler.rst: -------------------------------------------------------------------------------- 1 | p4utils\.utils\.compiler module 2 | =============================== 3 | 4 | .. automodule:: p4utils.utils.compiler 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docsrc/p4utils.utils.helper.rst: -------------------------------------------------------------------------------- 1 | p4utils\.utils\.helper module 2 | ============================= 3 | 4 | .. automodule:: p4utils.utils.helper 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docsrc/p4utils.utils.monitor.rst: -------------------------------------------------------------------------------- 1 | p4utils\.utils\.monitor module 2 | ============================== 3 | 4 | .. automodule:: p4utils.utils.monitor 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docsrc/p4utils.utils.p4runtime_API.api.rst: -------------------------------------------------------------------------------- 1 | p4utils\.utils\.p4runtime\_API\.api module 2 | ========================================== 3 | 4 | .. automodule:: p4utils.utils.p4runtime_API.api 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docsrc/p4utils.utils.p4runtime_API.bytes_utils.rst: -------------------------------------------------------------------------------- 1 | p4utils\.utils\.p4runtime\_API\.bytes\_utils module 2 | =================================================== 3 | 4 | .. automodule:: p4utils.utils.p4runtime_API.bytes_utils 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docsrc/p4utils.utils.p4runtime_API.context.rst: -------------------------------------------------------------------------------- 1 | p4utils\.utils\.p4runtime\_API\.context module 2 | ============================================== 3 | 4 | .. automodule:: p4utils.utils.p4runtime_API.context 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docsrc/p4utils.utils.p4runtime_API.p4runtime.rst: -------------------------------------------------------------------------------- 1 | p4utils\.utils\.p4runtime\_API\.p4runtime module 2 | ================================================ 3 | 4 | .. automodule:: p4utils.utils.p4runtime_API.p4runtime 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docsrc/p4utils.utils.p4runtime_API.rst: -------------------------------------------------------------------------------- 1 | p4utils\.utils\.p4runtime\_API package 2 | ====================================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | .. toctree:: 8 | 9 | p4utils.utils.p4runtime_API.api 10 | p4utils.utils.p4runtime_API.bytes_utils 11 | p4utils.utils.p4runtime_API.context 12 | p4utils.utils.p4runtime_API.p4runtime 13 | p4utils.utils.p4runtime_API.utils 14 | 15 | Module contents 16 | --------------- 17 | 18 | .. automodule:: p4utils.utils.p4runtime_API 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | -------------------------------------------------------------------------------- /docsrc/p4utils.utils.p4runtime_API.utils.rst: -------------------------------------------------------------------------------- 1 | p4utils\.utils\.p4runtime\_API\.utils module 2 | ============================================ 3 | 4 | .. automodule:: p4utils.utils.p4runtime_API.utils 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docsrc/p4utils.utils.rst: -------------------------------------------------------------------------------- 1 | p4utils\.utils package 2 | ====================== 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | 9 | p4utils.utils.p4runtime_API 10 | 11 | Submodules 12 | ---------- 13 | 14 | .. toctree:: 15 | 16 | p4utils.utils.client 17 | p4utils.utils.compiler 18 | p4utils.utils.helper 19 | p4utils.utils.monitor 20 | p4utils.utils.sswitch_p4runtime_API 21 | p4utils.utils.sswitch_thrift_API 22 | p4utils.utils.task_scheduler 23 | p4utils.utils.thrift_API 24 | p4utils.utils.topology 25 | p4utils.utils.traffic_utils 26 | 27 | Module contents 28 | --------------- 29 | 30 | .. automodule:: p4utils.utils 31 | :members: 32 | :undoc-members: 33 | :show-inheritance: 34 | -------------------------------------------------------------------------------- /docsrc/p4utils.utils.sswitch_p4runtime_API.rst: -------------------------------------------------------------------------------- 1 | p4utils\.utils\.sswitch\_p4runtime\_API module 2 | ============================================== 3 | 4 | .. automodule:: p4utils.utils.sswitch_p4runtime_API 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docsrc/p4utils.utils.sswitch_thrift_API.rst: -------------------------------------------------------------------------------- 1 | p4utils\.utils\.sswitch\_thrift\_API module 2 | =========================================== 3 | 4 | .. automodule:: p4utils.utils.sswitch_thrift_API 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docsrc/p4utils.utils.task_scheduler.rst: -------------------------------------------------------------------------------- 1 | p4utils\.utils\.task\_scheduler module 2 | ====================================== 3 | 4 | .. automodule:: p4utils.utils.task_scheduler 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docsrc/p4utils.utils.thrift_API.rst: -------------------------------------------------------------------------------- 1 | p4utils\.utils\.thrift\_API module 2 | ================================== 3 | 4 | .. automodule:: p4utils.utils.thrift_API 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docsrc/p4utils.utils.topology.rst: -------------------------------------------------------------------------------- 1 | p4utils\.utils\.topology module 2 | =============================== 3 | 4 | .. automodule:: p4utils.utils.topology 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docsrc/p4utils.utils.traffic_utils.rst: -------------------------------------------------------------------------------- 1 | p4utils\.utils\.traffic\_utils module 2 | ===================================== 3 | 4 | .. automodule:: p4utils.utils.traffic_utils 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | This directory collects some working examples that make use of *P4-Utils*. In particular we have the following subfolders, each one containing a specific topology. 4 | 5 | - [switches](./switches): topology including only `P4Switches`. The goal of this example is ensuring L2 and L3 connectivity among hosts. 6 | - [frrouters](./frrouters): topology including `P4Switches` and `FRRouters`. The goal is ensuring L2 and L3 connectivity among hosts, considering also different ASes. 7 | - [adv-net](./adv-net): topology including both `P4Switches` and `FRRouter` used in the *Advanced Topics in Communication Networks* 2020 project. It is not fully functional for what concerns *Traffic Control*. -------------------------------------------------------------------------------- /examples/adv-net/controller.py: -------------------------------------------------------------------------------- 1 | """Implement your controller in this file. 2 | 3 | The existing controller already implements L2-forwarding to ensure connectivity. 4 | Use this code as an example to get you started. 5 | You are free to completely change this! 6 | 7 | Tip: 8 | For debugging, you can start this file in interactive mode. 9 | This will execute the whole file, but then *keep* the python interpreter open, 10 | allowing you to inspect objects and try out things! 11 | 12 | ``` 13 | $ python3 -i controller.py --topo 14 | ``` 15 | 16 | The controller will be available as the variable `control`. 17 | """ 18 | # pylint: disable=superfluous-parens,invalid-name 19 | 20 | import argparse 21 | import csv 22 | 23 | from p4utils.utils.helper import load_topo 24 | from p4utils.utils.sswitch_thrift_API import SimpleSwitchThriftAPI 25 | 26 | class Controller(object): 27 | """The central controller for your p4 switches.""" 28 | 29 | L2_BROADCAST_GROUP_ID = 1 30 | 31 | def __init__(self, topo, traffic=None): 32 | self.topo = load_topo(topo) 33 | if traffic is not None: 34 | # Parse traffic matrix. 35 | self.traffic = self._parse_traffic_file(traffic) 36 | else: 37 | self.traffic = [] 38 | 39 | # Basic initialization. *Do not* change. 40 | self.controllers = {} 41 | self._connect_to_switches() 42 | self._reset_states() 43 | 44 | # Start main loop 45 | self.main() 46 | 47 | # Controller helpers. 48 | # =================== 49 | 50 | def _connect_to_switches(self): 51 | for p4switch in self.topo.get_p4switches(): 52 | print("Connecting to %s" % p4switch) 53 | thrift_port = self.topo.get_thrift_port(p4switch) 54 | thrift_ip = self.topo.get_thrift_ip(p4switch) 55 | self.controllers[p4switch] = SimpleSwitchThriftAPI( 56 | thrift_port, thrift_ip) 57 | 58 | def _reset_states(self): 59 | for controller in self.controllers.values(): 60 | controller.reset_state() 61 | 62 | @staticmethod 63 | def _parse_traffic_file(trafficpath): 64 | with open(trafficpath, 'r') as csvfile: 65 | dialect = csv.Sniffer().sniff(csvfile.read(1024)) 66 | csvfile.seek(0) 67 | reader = csv.DictReader(csvfile, dialect=dialect) 68 | return list(reader) 69 | 70 | # Controller methods. 71 | # =================== 72 | 73 | def main(self): 74 | """Main controller method.""" 75 | # Initialization of L2 forwarding. Feel free to modify. 76 | self.create_l2_multicast_group() 77 | self.add_l2_forwarding_rules() 78 | 79 | # while True 80 | # do_something() 81 | 82 | def add_l2_forwarding_rules(self): 83 | """Add L2 forwarding groups to all switches. 84 | 85 | We check the topology object to get all connected nodes and their 86 | MAC addresses, and configure static rules accordingly. 87 | """ 88 | for switch, controller in self.controllers.items(): 89 | # Add broadcast rule. 90 | controller.table_add("l2_forward", "broadcast", 91 | ["ff:ff:ff:ff:ff:ff/48"]) 92 | 93 | # Add rule for connected host. 94 | my_host = self.topo.get_hosts_connected_to(switch)[0] 95 | host_mac = self.topo.node_to_node_mac(my_host, switch) 96 | host_port = self.topo.node_to_node_port_num(switch, my_host) 97 | controller.table_add("l2_forward", "l2_forward_action", 98 | [str(host_mac)+"/48"], [str(host_port)]) 99 | 100 | # Add rules for connected routers. 101 | for router in self.topo.get_routers_connected_to(switch): 102 | router_mac = self.topo.node_to_node_mac(router, switch) 103 | router_port = self.topo.node_to_node_port_num(switch, router) 104 | controller.table_add("l2_forward", "l2_forward_action", 105 | [str(router_mac)+"/48"], [str(router_port)]) 106 | 107 | def create_l2_multicast_group(self): 108 | """Create a multicast group to enable L2 broadcasting.""" 109 | for switch, controller in self.controllers.items(): 110 | controller.mc_mgrp_create(self.L2_BROADCAST_GROUP_ID) 111 | port_list = [] 112 | 113 | # Get host port. 114 | my_host = self.topo.get_hosts_connected_to(switch)[0] 115 | port_list.append(self.topo.node_to_node_port_num(switch, my_host)) 116 | 117 | # Get router ports. 118 | for router in self.topo.get_routers_connected_to(switch): 119 | port_list.append( 120 | self.topo.node_to_node_port_num(switch, router)) 121 | 122 | # Update group. 123 | controller.mc_node_create(0, port_list) 124 | controller.mc_node_associate(1, 0) 125 | 126 | 127 | if __name__ == "__main__": 128 | parser = argparse.ArgumentParser() 129 | parser.add_argument('--topo', help='Path of topology.db.', 130 | type=str, required=False, 131 | default="./topology.json") 132 | parser.add_argument('--traffic', help='Path of traffic scenario.', 133 | type=str, required=False, 134 | default=None) 135 | args = parser.parse_args() 136 | 137 | control = Controller(args.topo, args.traffic) 138 | -------------------------------------------------------------------------------- /examples/adv-net/default-2.traffic: -------------------------------------------------------------------------------- 1 | src, dst, sport, dport, tos, rate, duration, packet_size, start_time 2 | h2, h3, 5000, 5001, 128, 1M, 50, 1400, 45 3 | h5, h2, 5000, 5002, 64, 6M, 50, 1400, 45 4 | h6, h4, 5000, 5003, 64, 6M, 50, 1400, 45 5 | h4, h6, 5000, 5004, 32, 12M, 50, 1400, 45 6 | h1, h5, 5000, 5005, 32, 12M, 50, 1400, 45 7 | h3, h1, 5000, 5006, 32, 12M, 50, 1400, 45 8 | -------------------------------------------------------------------------------- /examples/adv-net/default-3.traffic: -------------------------------------------------------------------------------- 1 | src, dst, sport, dport, tos, rate, duration, packet_size, start_time 2 | h6, h2, 5000, 5001, 128, 1M, 50, 1400, 45 3 | h2, h4, 5000, 5002, 64, 6M, 50, 1400, 45 4 | h1, h3, 5000, 5003, 64, 6M, 50, 1400, 45 5 | h3, h5, 5000, 5004, 32, 12M, 50, 1400, 45 6 | h4, h6, 5000, 5005, 32, 12M, 50, 1400, 45 7 | h5, h1, 5000, 5006, 32, 12M, 50, 1400, 45 8 | -------------------------------------------------------------------------------- /examples/adv-net/default.traffic: -------------------------------------------------------------------------------- 1 | src, dst, sport, dport, tos, rate, duration, packet_size, start_time 2 | h1, h4, 5000, 5001, 128, 1M, 50, 1400, 45 3 | h2, h5, 5000, 5002, 64, 6M, 50, 1400, 45 4 | h3, h6, 5000, 5003, 64, 6M, 50, 1400, 45 5 | h4, h1, 5000, 5004, 32, 12M, 50, 1400, 45 6 | h5, h2, 5000, 5005, 32, 12M, 50, 1400, 45 7 | h6, h3, 5000, 5006, 32, 12M, 50, 1400, 45 8 | -------------------------------------------------------------------------------- /examples/adv-net/network.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import csv 3 | from udp import * 4 | from p4utils.mininetlib.network_API import NetworkAPI 5 | 6 | HOSTS_TO_IP = { 7 | 'h1': '1.0.0.1', 8 | 'h2': '2.0.0.1', 9 | 'h3': '3.0.0.1', 10 | 'h4': '4.0.0.1', 11 | 'h5': '5.0.0.1', 12 | 'h6': '6.0.0.1' 13 | } 14 | 15 | def load_flows_file(config_file): 16 | flows = [] 17 | with open(config_file, 'r') as csvfile: 18 | dialect = csv.Sniffer().sniff(csvfile.read(1024)) 19 | csvfile.seek(0) 20 | reader = csv.DictReader(csvfile, dialect=dialect) 21 | return list(reader) 22 | 23 | def main(config_file): 24 | net = NetworkAPI() 25 | 26 | # Network general options 27 | net.enableCli() 28 | net.disableGwArp() 29 | net.disableArpTables() 30 | net.setLogLevel('info') 31 | 32 | # Network definition 33 | # Switches 34 | net.addP4Switch('s1') 35 | net.addP4Switch('s2') 36 | net.addP4Switch('s3') 37 | net.addP4Switch('s4') 38 | net.addP4Switch('s5') 39 | net.addP4Switch('s6') 40 | net.setP4SourceAll('switch.p4') 41 | 42 | net.execScript('python controller.py --traffic {}'.format(config_file), reboot=True) 43 | net.execScript('sleep 100 && python performance.py --traffic-spec {} &'.format(config_file), reboot=True) 44 | 45 | # Hosts 46 | net.addHost('h1') 47 | net.setDefaultRoute('h1', "1.0.0.2") 48 | net.addHost('h2') 49 | net.setDefaultRoute('h2', "2.0.0.2") 50 | net.addHost('h3') 51 | net.setDefaultRoute('h3', "3.0.0.2") 52 | net.addHost('h4') 53 | net.setDefaultRoute('h4', '4.0.0.2') 54 | net.addHost('h5') 55 | net.setDefaultRoute('h5', '5.0.0.2') 56 | net.addHost('h6') 57 | net.setDefaultRoute('h6', '6.0.0.2') 58 | 59 | # Routers 60 | net.addRouter('r1', int_conf='./routers/r1.conf', bgpd=False) 61 | net.addRouter('r2', int_conf='./routers/r2.conf', bgpd=False) 62 | net.addRouter('r3', int_conf='./routers/r3.conf', bgpd=False) 63 | net.addRouter('r4', int_conf='./routers/r4.conf', bgpd=False) 64 | 65 | # Links 66 | net.addLink('h1', 's1', bw=12, intfName2='s1-host') 67 | net.setIntfIp('h1', 's1', HOSTS_TO_IP['h1']+'/24') 68 | net.setIntfMac('h1', 's1', '00:00:01:00:00:01') 69 | 70 | net.addLink('h2', 's2', bw=12, intfName2='s2-host') 71 | net.setIntfIp('h2', 's2', HOSTS_TO_IP['h2']+'/24') 72 | net.setIntfMac('h2', 's2', '00:00:02:00:00:01') 73 | 74 | net.addLink('h3', 's3', bw=12, intfName2='s3-host') 75 | net.setIntfIp('h3', 's3', HOSTS_TO_IP['h3']+'/24') 76 | net.setIntfMac('h3', 's3', '00:00:03:00:00:01') 77 | 78 | net.addLink('h4', 's4', bw=12, intfName2='s4-host') 79 | net.setIntfIp('h4', 's4', HOSTS_TO_IP['h4']+'/24') 80 | net.setIntfMac('h4', 's4', '00:00:04:00:00:01') 81 | 82 | net.addLink('h5', 's5', bw=12, intfName2='s5-host') 83 | net.setIntfIp('h5', 's5', HOSTS_TO_IP['h5']+'/24') 84 | net.setIntfMac('h5', 's5', '00:00:05:00:00:01') 85 | 86 | net.addLink('h6', 's6', bw=12, intfName2='s6-host') 87 | net.setIntfIp('h6', 's6', HOSTS_TO_IP['h6']+'/24') 88 | net.setIntfMac('h6', 's6', '00:00:06:00:00:01') 89 | 90 | net.addLink('s1', 'r1', port2=1, intfName1='s1-port_R1', intfName2='port_S1', bw=6) 91 | net.addLink('s1', 'r4', port2=5, intfName1='s1-port_R4', intfName2='port_S1', bw=4) 92 | net.addLink('s1', 's6', intfName1='s1-port_S6', intfName2='s6-port_S1',bw=6) 93 | net.addLink('s6', 'r1', port2=5, intfName1='s6-port_R1', intfName2='port_S6', bw=4) 94 | net.addLink('s6', 'r4', port2=3, intfName1='s6-port_R4', intfName2='port_S6', bw=6) 95 | 96 | net.addLink('s3', 'r2', port2=1, intfName1='s3-port_R2', intfName2='port_S3', bw=6) 97 | net.addLink('s3', 'r3', port2=5, intfName1='s3-port_R3', intfName2='port_S3', bw=4) 98 | net.addLink('s3', 's4', intfName1='s3-port_S4', intfName2='s4-port_S3', bw=6) 99 | net.addLink('s4', 'r2', port2=4, intfName1='s4-port_R2', intfName2='port_S4', bw=4) 100 | net.addLink('s4', 'r3', port2=4, intfName1='s4-port_R3', intfName2='port_S4', bw=6) 101 | 102 | net.addLink('s2', 'r1', port2=2, intfName1='s2-port_R1', intfName2='port_S2', bw=4) 103 | net.addLink('s2', 'r2', port2=6, intfName1='s2-port_R2', intfName2='port_S2', bw=4) 104 | 105 | net.addLink('s5', 'r3', port2=3, intfName1='s5-port_R3', intfName2='port_S5', bw=4) 106 | net.addLink('s5', 'r4', port2=6, intfName1='s5-port_R4', intfName2='port_S5', bw=4) 107 | 108 | net.addLink('r1', 'r2', port1=3, port2=2, intfName1='port_R2', intfName2='port_R1', bw=4) 109 | net.addLink('r1', 'r3', port1=4, port2=2, intfName1='port_R3', intfName2='port_R1', bw=6) 110 | net.addLink('r1', 'r4', port1=6, port2=4, intfName1='port_R4', intfName2='port_R1', bw=4) 111 | net.addLink('r2', 'r3', port1=5, port2=6, intfName1='port_R3', intfName2='port_R2', bw=4) 112 | net.addLink('r2', 'r4', port1=3, port2=2, intfName1='port_R4', intfName2='port_R2', bw=6) 113 | net.addLink('r3', 'r4', port1=1, port2=1, intfName1='port_R4', intfName2='port_R3', bw=4) 114 | 115 | # Nodes general options 116 | net.disablePcapDumpAll() 117 | net.enableLogAll() 118 | 119 | # Add tasks for traffic generation 120 | flows = load_flows_file(config_file) 121 | for kwargs in flows: 122 | kwargs['d'] = kwargs['duration'] 123 | kwargs['start'] = float(kwargs['start_time']) 124 | kwargs['src_name'] = kwargs['src'] 125 | kwargs['dst_name'] = kwargs['dst'] 126 | kwargs['src'] = HOSTS_TO_IP[kwargs['src_name']] 127 | kwargs['dst'] = HOSTS_TO_IP[kwargs['dst_name']] 128 | del kwargs['duration'] 129 | del kwargs['start_time'] 130 | 131 | out_file = "{}/sender_{}_{}_{}_{}.txt".format( 132 | './flows/', kwargs['src_name'], kwargs['dst_name'], kwargs["sport"], kwargs["dport"]) 133 | net.addTask(kwargs['src_name'], send_udp_flow, out_file=out_file, **kwargs) 134 | 135 | out_file = "{}/receiver_{}_{}_{}_{}.txt".format( 136 | './flows/', kwargs['src_name'], kwargs['dst_name'], kwargs["sport"], kwargs["dport"]) 137 | net.addTask(kwargs['dst_name'], recv_udp_flow, src=kwargs["src"], dport=int(kwargs["dport"]), out_file=out_file) 138 | 139 | # Start the network 140 | net.startNetwork() 141 | 142 | if __name__ == "__main__": 143 | 144 | main(sys.argv[1]) 145 | -------------------------------------------------------------------------------- /examples/adv-net/performance.py: -------------------------------------------------------------------------------- 1 | """Computes the performance of your run 2 | 3 | Example usage: python2 performance.py --traffic-spec ../scenarios/default.traffic 4 | 5 | """ 6 | 7 | import os 8 | import argparse 9 | import csv 10 | from collections import OrderedDict 11 | 12 | 13 | traffic_weights = { 14 | "128": 10, 15 | "64": 4, 16 | "32": 1 17 | } 18 | 19 | traffic_names = OrderedDict([ 20 | ("128", "Gold"), 21 | ("64", "Silver"), 22 | ("32", "Bronze") 23 | ]) 24 | 25 | class Performance(object): 26 | """Load the traffic matrix and generate everything.""" 27 | 28 | def __init__(self, traffic, out_path): 29 | self.traffic_spec = traffic 30 | self.out_path = out_path 31 | self.flows = self._load_traffic_spec() 32 | self._count_points() 33 | 34 | def _load_traffic_spec(self): 35 | """Loads traffic matrix spec""" 36 | 37 | flows = [] 38 | with open(self.traffic_spec, 'r') as csvfile: 39 | dialect = csv.Sniffer().sniff(csvfile.read(1024)) 40 | csvfile.seek(0) 41 | reader = csv.DictReader(csvfile, dialect=dialect) 42 | return list(reader) 43 | 44 | def _read_sequences(self, file_name): 45 | """Helper function to read""" 46 | 47 | with open(file_name, "r") as f: 48 | return set([int(x) for x in f.read().split()]) 49 | 50 | def _count_flow(self, flow): 51 | """ Counts the packet in out for a given flow""" 52 | 53 | sender_path = "{}/sender_{}_{}_{}_{}.txt".format(self.out_path, 54 | flow["src"], flow["dst"], flow["sport"], flow["dport"]) 55 | sender_seq = self._read_sequences(sender_path) 56 | receiver_path = "{}/receiver_{}_{}_{}_{}.txt".format(self.out_path, 57 | flow["src"], flow["dst"], flow["sport"], flow["dport"]) 58 | receiver_seq = self._read_sequences(receiver_path) 59 | received_and_sent = sender_seq.intersection(receiver_seq) 60 | 61 | # returns pkt_in and pkt_out (sent from this sender and not repeated) 62 | return len(sender_seq), len(received_and_sent) 63 | 64 | def _count_points(self): 65 | """ Counts all flows in/out """ 66 | 67 | self._traffic_counts = { 68 | "128": 69 | {"pkts_in": 0.0, "pkts_out": 0.0}, 70 | "64": 71 | {"pkts_in": 0.0, "pkts_out": 0.0}, 72 | "32": 73 | {"pkts_in": 0.0, "pkts_out": 0.0}, 74 | } 75 | for flow in self.flows: 76 | tos = flow["tos"] 77 | pkt_in, pkt_out = self._count_flow(flow) 78 | 79 | self._traffic_counts[tos]["pkts_in"] += pkt_in 80 | self._traffic_counts[tos]["pkts_out"] += pkt_out 81 | 82 | def get_weighted_perfomance(self): 83 | """ computes final performance """ 84 | 85 | self._count_points() 86 | weighted_performance = 0 87 | for traffic_type, packets in self._traffic_counts.items(): 88 | # type weighted performance 89 | if packets["pkts_in"] == 0: 90 | _performance = 0 91 | else: 92 | _performance = (packets["pkts_out"]/packets["pkts_in"]) * (traffic_weights[traffic_type])/(sum(traffic_weights.values())) 93 | weighted_performance += _performance 94 | 95 | return weighted_performance 96 | 97 | def print_performance(self): 98 | """ Print per traffic class and weighted performance """ 99 | 100 | print("\n===============================") 101 | print("""Your last run performances are:\n===============================""") 102 | 103 | weighted_performance = self.get_weighted_perfomance() 104 | for traffic_type, traffic_name in traffic_names.items(): 105 | packets = self._traffic_counts[traffic_type] 106 | warning = "" 107 | if packets["pkts_in"] == 0: 108 | _performance = 0 109 | warning = "\033[31m(warning: you did not send traffic for this type)\033[39m" 110 | else: 111 | _performance = (packets["pkts_out"]/packets["pkts_in"]) 112 | print("{:10} {:.5f} {}".format(traffic_names[traffic_type], _performance, warning)) 113 | print("-------------------------------") 114 | print("Weighted {:.5f}".format(weighted_performance)) 115 | 116 | 117 | if __name__ == "__main__": 118 | # pylint: disable=invalid-name 119 | parser = argparse.ArgumentParser() 120 | parser.add_argument('--traffic-spec', 121 | help='Traffic generation specification', 122 | type=str, required=True) 123 | parser.add_argument('--out-path', 124 | help='Path to flows logs', 125 | type=str, required=False, default="./flows") 126 | 127 | args = parser.parse_args() 128 | 129 | performance = Performance( 130 | args.traffic_spec, args.out_path 131 | ) 132 | 133 | performance.print_performance() 134 | -------------------------------------------------------------------------------- /examples/adv-net/routers/r1.conf: -------------------------------------------------------------------------------- 1 | conf t 2 | interface lo 3 | ip address 1.151.0.1/32 4 | exit 5 | router ospf 6 | ospf router-id 1.151.0.1 7 | network 1.151.0.1/32 area 0 8 | exit 9 | 10 | interface port_R2 11 | ip address 10.1.0.1/24 12 | ip ospf cost 1 13 | exit 14 | router ospf 15 | network 10.1.0.1/24 area 0 16 | exit 17 | 18 | interface port_R3 19 | ip address 10.2.0.1/24 20 | ip ospf cost 1 21 | exit 22 | router ospf 23 | network 10.2.0.1/24 area 0 24 | exit 25 | 26 | interface port_R4 27 | ip address 10.3.0.1/24 28 | ip ospf cost 1 29 | exit 30 | router ospf 31 | network 10.3.0.1/24 area 0 32 | exit 33 | 34 | interface port_S1 35 | ip address 1.0.0.2/24 36 | ip ospf cost 1 37 | exit 38 | router ospf 39 | network 1.0.0.2/24 area 0 40 | exit 41 | 42 | interface port_S2 43 | ip address 2.0.0.2/24 44 | ip ospf cost 1 45 | exit 46 | router ospf 47 | network 2.0.0.2/24 area 0 48 | exit 49 | 50 | interface port_S6 51 | ip address 6.0.0.3/24 52 | ip ospf cost 1 53 | exit 54 | router ospf 55 | network 6.0.0.3/24 area 0 56 | exit 57 | -------------------------------------------------------------------------------- /examples/adv-net/routers/r2.conf: -------------------------------------------------------------------------------- 1 | conf t 2 | interface lo 3 | ip address 1.152.0.1/32 4 | exit 5 | router ospf 6 | ospf router-id 1.152.0.1 7 | network 1.152.0.1/32 area 0 8 | exit 9 | interface port_R1 10 | ip address 10.1.0.2/24 11 | ip ospf cost 1 12 | exit 13 | router ospf 14 | network 10.1.0.2/24 area 0 15 | exit 16 | interface port_R3 17 | ip address 10.5.0.1/24 18 | ip ospf cost 1 19 | exit 20 | router ospf 21 | network 10.5.0.1/24 area 0 22 | exit 23 | interface port_R4 24 | ip address 10.4.0.1/24 25 | ip ospf cost 1 26 | exit 27 | router ospf 28 | network 10.4.0.1/24 area 0 29 | exit 30 | 31 | interface port_S2 32 | ip address 2.0.0.3/24 33 | ip ospf cost 1 34 | exit 35 | router ospf 36 | network 2.0.0.3/24 area 0 37 | exit 38 | 39 | interface port_S3 40 | ip address 3.0.0.2/24 41 | ip ospf cost 1 42 | exit 43 | router ospf 44 | network 3.0.0.2/24 area 0 45 | exit 46 | 47 | interface port_S4 48 | ip address 4.0.0.3/24 49 | ip ospf cost 1 50 | exit 51 | router ospf 52 | network 4.0.0.3/24 area 0 53 | exit 54 | -------------------------------------------------------------------------------- /examples/adv-net/routers/r3.conf: -------------------------------------------------------------------------------- 1 | conf t 2 | interface lo 3 | ip address 1.153.0.1/32 4 | exit 5 | router ospf 6 | ospf router-id 1.153.0.1 7 | network 1.153.0.1/32 area 0 8 | exit 9 | interface port_R1 10 | ip address 10.2.0.2/24 11 | ip ospf cost 1 12 | exit 13 | router ospf 14 | network 10.2.0.2/24 area 0 15 | exit 16 | interface port_R2 17 | ip address 10.5.0.2/24 18 | ip ospf cost 1 19 | exit 20 | router ospf 21 | network 10.5.0.2/24 area 0 22 | exit 23 | interface port_R4 24 | ip address 10.6.0.1/24 25 | ip ospf cost 1 26 | exit 27 | router ospf 28 | network 10.6.0.1/24 area 0 29 | exit 30 | 31 | interface port_S3 32 | ip address 3.0.0.3/24 33 | ip ospf cost 1 34 | exit 35 | router ospf 36 | network 3.0.0.3/24 area 0 37 | exit 38 | 39 | interface port_S4 40 | ip address 4.0.0.2/24 41 | ip ospf cost 1 42 | exit 43 | router ospf 44 | network 4.0.0.2/24 area 0 45 | exit 46 | 47 | interface port_S5 48 | ip address 5.0.0.3/24 49 | ip ospf cost 1 50 | exit 51 | router ospf 52 | network 5.0.0.3/24 area 0 53 | exit 54 | -------------------------------------------------------------------------------- /examples/adv-net/routers/r4.conf: -------------------------------------------------------------------------------- 1 | conf t 2 | interface lo 3 | ip address 1.154.0.1/32 4 | exit 5 | router ospf 6 | ospf router-id 1.154.0.1 7 | network 1.154.0.1/32 area 0 8 | exit 9 | interface port_R1 10 | ip address 10.3.0.2/24 11 | ip ospf cost 1 12 | exit 13 | router ospf 14 | network 10.3.0.2/24 area 0 15 | exit 16 | interface port_R2 17 | ip address 10.4.0.2/24 18 | ip ospf cost 1 19 | exit 20 | router ospf 21 | network 10.4.0.2/24 area 0 22 | exit 23 | interface port_R3 24 | ip address 10.6.0.2/24 25 | ip ospf cost 1 26 | exit 27 | router ospf 28 | network 10.6.0.2/24 area 0 29 | exit 30 | 31 | interface port_S5 32 | ip address 5.0.0.2/24 33 | ip ospf cost 1 34 | exit 35 | router ospf 36 | network 5.0.0.2/24 area 0 37 | exit 38 | 39 | interface port_S6 40 | ip address 6.0.0.2/24 41 | ip ospf cost 1 42 | exit 43 | router ospf 44 | network 6.0.0.2/24 area 0 45 | exit 46 | 47 | interface port_S1 48 | ip address 1.0.0.3/24 49 | ip ospf cost 1 50 | exit 51 | router ospf 52 | network 1.0.0.3/24 area 0 53 | exit -------------------------------------------------------------------------------- /examples/adv-net/udp.py: -------------------------------------------------------------------------------- 1 | import time 2 | import socket 3 | import math 4 | 5 | # min udp packet size 6 | minSizeUDP = 42 7 | maxUDPSize = 1400 8 | DEFAULT_BATCH_SIZE = 1 9 | 10 | 11 | def setSizeToInt(size): 12 | """" Converts the sizes string notation to the corresponding integer 13 | (in bytes). Input size can be given with the following 14 | magnitudes: B, K, M and G. 15 | """ 16 | if isinstance(size, int): 17 | return size 18 | elif isinstance(size, float): 19 | return int(size) 20 | try: 21 | conversions = {'B': 1, 'K': 1e3, 'M': 1e6, 'G': 1e9} 22 | digits_list = list(range(48, 58)) + [ord(".")] 23 | magnitude = chr( 24 | sum([ord(x) if (ord(x) not in digits_list) else 0 for x in size])) 25 | digit = float(size[0:(size.index(magnitude))]) 26 | magnitude = conversions[magnitude] 27 | return int(magnitude*digit) 28 | except: 29 | print("Conversion Fail") 30 | return 0 31 | 32 | 33 | def send_udp_flow(dst="10.0.1.2", sport=5000, dport=5001, tos=0, rate='10M', d=10, 34 | packet_size=maxUDPSize, batch_size=DEFAULT_BATCH_SIZE, out_file="send.txt", **kwargs): 35 | """Udp sending function that keeps a constant rate and logs sent packets to a file. 36 | Args: 37 | dst (str, optional): [description]. Defaults to "10.0.1.2". 38 | sport (int, optional): [description]. Defaults to 5000. 39 | dport (int, optional): [description]. Defaults to 5001. 40 | tos (int, optional): [description]. Defaults to 0. 41 | rate (str, optional): [description]. Defaults to '10M'. 42 | d (int, optional): [description]. Defaults to 10. 43 | packet_size ([type], optional): [description]. Defaults to maxUDPSize. 44 | batch_size (int, optional): [description]. Defaults to 5. 45 | out_file (str, optional): [description]. Defaults to "send.txt". 46 | """ 47 | 48 | sport = int(sport) 49 | dport = int(dport) 50 | packet_size = int(packet_size) 51 | tos = int(tos) 52 | if packet_size > maxUDPSize: 53 | packet_size = maxUDPSize 54 | 55 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 56 | s.setsockopt(socket.SOL_IP, socket.IP_TOS, tos) 57 | s.bind(('', sport)) 58 | 59 | rate = int(setSizeToInt(rate)/8) 60 | totalTime = float(d) 61 | 62 | # we use 17 to correct a bit the bw 63 | packet = b"A" * int((packet_size - 17)) 64 | seq = 0 65 | output_log = open(out_file, "w") 66 | 67 | try: 68 | startTime = time.time() 69 | while (time.time() - startTime < totalTime): 70 | 71 | packets_to_send = rate/packet_size 72 | times = math.ceil((float(rate) / (packet_size))/batch_size) 73 | time_step = 1/times 74 | start = time.time() 75 | i = 0 76 | packets_sent = 0 77 | # batches of 1 sec 78 | while packets_sent < packets_to_send: 79 | for _ in range(batch_size): 80 | s.sendto(seq.to_bytes(4, byteorder='big') + 81 | packet, (dst, dport)) 82 | output_log.write("{}\n".format(seq)) 83 | output_log.flush() 84 | # sequence_numbers.append(seq) 85 | packets_sent += 1 86 | seq += 1 87 | 88 | i += 1 89 | next_send_time = start + (i * time_step) 90 | time.sleep(max(0, next_send_time - time.time())) 91 | # return 92 | time.sleep(max(0, 1-(time.time()-start))) 93 | 94 | finally: 95 | s.close() 96 | output_log.close() 97 | 98 | 99 | def recv_udp_flow(src, dport, out_file="recv.txt"): 100 | """Receiving function. It blocks reciving packets and store the first 101 | 4 bytes into out file 102 | 103 | Args: 104 | src ([str]): source ip address to listen 105 | dport ([int]): port to listen 106 | out_file ([str]): out file to log 107 | """ 108 | 109 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 110 | s.bind(("", dport)) 111 | output_log = open(out_file, "w") 112 | c = 0 113 | try: 114 | while True: 115 | data, address = s.recvfrom(2048) 116 | # only accept packets from the expected source 117 | if address[0] == src: 118 | c += 1 119 | output_log.write("{}\n".format( 120 | int.from_bytes(data[:4], 'big'))) 121 | output_log.flush() 122 | 123 | except: 124 | print("Packets received {}".format(c)) 125 | s.close() 126 | output_log.close() 127 | 128 | 129 | def save_sequences(sequences, file_name): 130 | """Helper function to save sequence numbers in bulk 131 | 132 | Args: 133 | sequences ([list]): list of sequences 134 | file_name ([str]): output file 135 | """ 136 | 137 | with open(file_name, "w") as f: 138 | for seq in sequences: 139 | f.write("{}\n".format(seq)) 140 | -------------------------------------------------------------------------------- /examples/frrouters/README.md: -------------------------------------------------------------------------------- 1 | # Fundamental network operations 2 | 3 |

4 | 5 |

6 | 7 | ## Introduction 8 | 9 | The objective of this example is to provide an overview of the capabilities of **p4-utils**. According to the topology above, we have 2 ASes. AS 1 is responsible for the prefix `1.0.0.0/8`, while AS 2 `2.0.0.0/8`. In each one of them we have serveral hosts, routers and P4 switches. The goal is using all the main protocols and technologies to gain full connectivity among the hosts. In particular, this involves the following: 10 | - *L2 learning* for P4 switches, in order to have L2 connectivity within each network segment; 11 | - *OSPF* for routers, in order to have connectivity within the same AS; 12 | - *BGP* for routers, in order to have connectivity among different ASes; 13 | - *LDP* for routers, in order to make the core of AS 1 (R3) *BGP*-free and still have full connectivity. 14 | 15 | ## Execution 16 | 17 | The scenario can be executed with both the following commands: 18 | ``` 19 | sudo p4run 20 | ``` 21 | or 22 | ``` 23 | sudo python network.py 24 | ``` 25 | 26 | Please notice that *OSPF*, *LDP* and *BGP* take some time to converge so, at the beginning, the network can still be partitioned. -------------------------------------------------------------------------------- /examples/frrouters/images/frr_example_topo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nsg-ethz/p4-utils/83b118bbae530b31cc74e7fa32f9174f7c0a1184/examples/frrouters/images/frr_example_topo.png -------------------------------------------------------------------------------- /examples/frrouters/l2_learning_controller.py: -------------------------------------------------------------------------------- 1 | import nnpy 2 | import struct 3 | from p4utils.utils.helper import load_topo 4 | from p4utils.utils.sswitch_thrift_API import SimpleSwitchThriftAPI 5 | from scapy.all import Ether, sniff, Packet, BitField, raw 6 | 7 | 8 | class CpuHeader(Packet): 9 | name = 'CpuPacket' 10 | fields_desc = [BitField('macAddr',0,48), BitField('ingress_port', 0, 16)] 11 | 12 | 13 | class L2Controller(object): 14 | 15 | def __init__(self, sw_name): 16 | self.topo = load_topo('topology.json') 17 | self.sw_name = sw_name 18 | self.thrift_port = self.topo.get_thrift_port(sw_name) 19 | self.cpu_port = self.topo.get_cpu_port_index(self.sw_name) 20 | self.controller = SimpleSwitchThriftAPI(self.thrift_port) 21 | self.init() 22 | 23 | def init(self): 24 | self.controller.reset_state() 25 | self.add_boadcast_groups() 26 | self.add_mirror() 27 | 28 | def add_mirror(self): 29 | if self.cpu_port: 30 | self.controller.mirroring_add(100, self.cpu_port) 31 | 32 | def add_boadcast_groups(self): 33 | interfaces_to_port = self.topo.get_node_intfs(fields=['port'])[self.sw_name].copy() 34 | # Filter lo and cpu port 35 | interfaces_to_port.pop('lo', None) 36 | interfaces_to_port.pop(self.topo.get_cpu_port_intf(self.sw_name), None) 37 | 38 | mc_grp_id = 1 39 | rid = 0 40 | for ingress_port in interfaces_to_port.values(): 41 | port_list = list(interfaces_to_port.values()) 42 | del(port_list[port_list.index(ingress_port)]) 43 | #add multicast group 44 | self.controller.mc_mgrp_create(mc_grp_id) 45 | #add multicast node group 46 | handle = self.controller.mc_node_create(rid, port_list) 47 | #associate with mc grp 48 | self.controller.mc_node_associate(mc_grp_id, handle) 49 | #fill broadcast table 50 | self.controller.table_add("broadcast", "set_mcast_grp", [str(ingress_port)], [str(mc_grp_id)]) 51 | mc_grp_id +=1 52 | rid +=1 53 | 54 | def learn(self, learning_data): 55 | for mac_addr, ingress_port in learning_data: 56 | print("mac: %012X ingress_port: %s " % (mac_addr, ingress_port)) 57 | self.controller.table_add("smac", "NoAction", [str(mac_addr)]) 58 | self.controller.table_add("dmac", "forward", [str(mac_addr)], [str(ingress_port)]) 59 | 60 | def unpack_digest(self, msg, num_samples): 61 | digest = [] 62 | starting_index = 32 63 | for sample in range(num_samples): 64 | mac0, mac1, ingress_port = struct.unpack(">LHH", msg[starting_index:starting_index+8]) 65 | starting_index +=8 66 | mac_addr = (mac0 << 16) + mac1 67 | digest.append((mac_addr, ingress_port)) 68 | return digest 69 | 70 | def recv_msg_digest(self, msg): 71 | topic, device_id, ctx_id, list_id, buffer_id, num = struct.unpack(" 3 | #include 4 | 5 | const bit<16> TYPE_IPV4 = 0x800; 6 | const bit<16> TYPE_BROADCAST = 0x1234; 7 | 8 | /************************************************************************* 9 | *********************** H E A D E R S *********************************** 10 | *************************************************************************/ 11 | 12 | typedef bit<9> egressSpec_t; 13 | typedef bit<48> macAddr_t; 14 | typedef bit<32> ip4Addr_t; 15 | 16 | header ethernet_t { 17 | macAddr_t dstAddr; 18 | macAddr_t srcAddr; 19 | bit<16> etherType; 20 | } 21 | 22 | struct learn_t { 23 | 24 | bit<48> srcAddr; 25 | bit<9> ingress_port; 26 | 27 | } 28 | 29 | struct metadata { 30 | /* empty */ 31 | learn_t learn; 32 | } 33 | 34 | struct headers { 35 | ethernet_t ethernet; 36 | } 37 | 38 | 39 | /************************************************************************* 40 | *********************** P A R S E R *********************************** 41 | *************************************************************************/ 42 | 43 | parser MyParser(packet_in packet, 44 | out headers hdr, 45 | inout metadata meta, 46 | inout standard_metadata_t standard_metadata) { 47 | 48 | state start { 49 | packet.extract(hdr.ethernet); 50 | transition accept; 51 | } 52 | } 53 | 54 | 55 | /************************************************************************* 56 | ************ C H E C K S U M V E R I F I C A T I O N ************* 57 | *************************************************************************/ 58 | 59 | control MyVerifyChecksum(inout headers hdr, inout metadata meta) { 60 | apply { } 61 | } 62 | 63 | 64 | /************************************************************************* 65 | ************** I N G R E S S P R O C E S S I N G ******************* 66 | *************************************************************************/ 67 | 68 | control MyIngress(inout headers hdr, 69 | inout metadata meta, 70 | inout standard_metadata_t standard_metadata) { 71 | 72 | action drop() { 73 | 74 | mark_to_drop(standard_metadata); 75 | } 76 | 77 | action mac_learn(){ 78 | meta.learn.srcAddr = hdr.ethernet.srcAddr; 79 | meta.learn.ingress_port = standard_metadata.ingress_port; 80 | digest(1, meta.learn); 81 | } 82 | 83 | table smac { 84 | 85 | key = { 86 | hdr.ethernet.srcAddr: exact; 87 | } 88 | 89 | actions = { 90 | mac_learn; 91 | NoAction; 92 | } 93 | size = 256; 94 | default_action = mac_learn; 95 | } 96 | 97 | action forward(bit<9> egress_port) { 98 | standard_metadata.egress_spec = egress_port; 99 | } 100 | 101 | table dmac { 102 | key = { 103 | hdr.ethernet.dstAddr: exact; 104 | } 105 | 106 | actions = { 107 | forward; 108 | NoAction; 109 | } 110 | size = 256; 111 | default_action = NoAction; 112 | } 113 | 114 | action set_mcast_grp(bit<16> mcast_grp) { 115 | standard_metadata.mcast_grp = mcast_grp; 116 | } 117 | 118 | table broadcast { 119 | key = { 120 | standard_metadata.ingress_port: exact; 121 | } 122 | 123 | actions = { 124 | set_mcast_grp; 125 | NoAction; 126 | } 127 | size = 256; 128 | default_action = NoAction; 129 | } 130 | 131 | apply { 132 | 133 | smac.apply(); 134 | if (dmac.apply().hit){ 135 | // 136 | } 137 | else { 138 | broadcast.apply(); 139 | } 140 | } 141 | } 142 | 143 | /************************************************************************* 144 | **************** E G R E S S P R O C E S S I N G ******************* 145 | *************************************************************************/ 146 | 147 | control MyEgress(inout headers hdr, 148 | inout metadata meta, 149 | inout standard_metadata_t standard_metadata) { 150 | 151 | 152 | apply { } 153 | } 154 | 155 | /************************************************************************* 156 | ************* C H E C K S U M C O M P U T A T I O N ************** 157 | *************************************************************************/ 158 | 159 | control MyComputeChecksum(inout headers hdr, inout metadata meta) { 160 | apply { 161 | 162 | } 163 | } 164 | 165 | 166 | /************************************************************************* 167 | *********************** D E P A R S E R ******************************* 168 | *************************************************************************/ 169 | 170 | control MyDeparser(packet_out packet, in headers hdr) { 171 | apply { 172 | //parsed headers have to be added again into the packet. 173 | packet.emit(hdr.ethernet); 174 | } 175 | } 176 | 177 | /************************************************************************* 178 | *********************** S W I T C H ******************************* 179 | *************************************************************************/ 180 | 181 | //switch architecture 182 | V1Switch( 183 | MyParser(), 184 | MyVerifyChecksum(), 185 | MyIngress(), 186 | MyEgress(), 187 | MyComputeChecksum(), 188 | MyDeparser() 189 | ) main; -------------------------------------------------------------------------------- /examples/frrouters/network.py: -------------------------------------------------------------------------------- 1 | from p4utils.mininetlib.network_API import NetworkAPI 2 | 3 | net = NetworkAPI() 4 | 5 | # Network general options 6 | net.setLogLevel('info') 7 | net.execScript('python l2_learning_controller.py s1 digest &', reboot=True) 8 | net.execScript('python l2_learning_controller.py s2 digest &', reboot=True) 9 | net.execScript('python l2_learning_controller.py s3 digest &', reboot=True) 10 | net.enableCli() 11 | net.disableArpTables() 12 | net.disableGwArp() 13 | 14 | # Network definition 15 | 16 | # Switches 17 | # AS 1 18 | net.addP4Switch('s1') 19 | net.addP4Switch('s2') 20 | # AS 2 21 | net.addP4Switch('s3') 22 | net.setP4SourceAll('l2_learning_digest.p4') 23 | 24 | # Hosts 25 | # AS 1 26 | net.addHost('h1') 27 | net.setDefaultRoute('h1', "1.0.0.1") 28 | net.addHost('h4') 29 | net.setDefaultRoute('h4', "1.0.0.1") 30 | net.addHost('h2') 31 | net.setDefaultRoute('h2', "1.7.0.1") 32 | net.addHost('h5') 33 | net.setDefaultRoute('h5', '1.7.0.1') 34 | 35 | # AS 2 36 | net.addHost('h3') 37 | net.setDefaultRoute('h3', "2.0.0.1") 38 | net.addHost('h6') 39 | net.setDefaultRoute('h6', '2.0.0.1') 40 | 41 | # Routers 42 | # AS 1 43 | net.addRouter('r1', int_conf='./routers/r1.conf', ldpd=True) 44 | net.addRouter('r2', int_conf='./routers/r2.conf', ldpd=True) 45 | net.addRouter('r3', int_conf='./routers/r3.conf', ldpd=True) 46 | net.addRouter('r4', int_conf='./routers/r4.conf', ldpd=True) 47 | 48 | # AS 2 49 | net.addRouter('r5', int_conf='./routers/r5.conf', ldpd=True) 50 | 51 | # Links 52 | # AS 1 53 | net.addLink('h1', 's1') 54 | net.setIntfIp('h1', 's1', '1.0.0.2/24') 55 | net.addLink('h4', 's1') 56 | net.setIntfIp('h4', 's1', '1.0.0.3/24') 57 | net.addLink('s1', 'r1', intfName2='port_S1') 58 | 59 | net.addLink('h2', 's2') 60 | net.setIntfIp('h2', 's2', '1.7.0.2/24') 61 | net.addLink('h5', 's2') 62 | net.setIntfIp('h5', 's2', '1.7.0.3/24') 63 | net.addLink('s2', 'r2', intfName2='port_S2') 64 | 65 | net.addLink('r1', 'r2', intfName1='port_R2', intfName2='port_R1') 66 | net.addLink('r1', 'r3', intfName1='port_R3', intfName2='port_R1') 67 | net.addLink('r1', 'r4', intfName1='port_R4', intfName2='port_R1') 68 | net.addLink('r2', 'r3', intfName1='port_R3', intfName2='port_R2') 69 | net.addLink('r2', 'r4', intfName1='port_R4', intfName2='port_R2') 70 | net.addLink('r3', 'r4', intfName1='port_R4', intfName2='port_R3') 71 | 72 | # Inter-AS 73 | net.addLink('r4', 'r5', intfName1='port_AS2', intfName2='port_AS1') 74 | 75 | # AS 2 76 | net.addLink('h3', 's3') 77 | net.setIntfIp('h3', 's3', '2.0.0.2/24') 78 | net.addLink('h6', 's3') 79 | net.setIntfIp('h6', 's3', '2.0.0.3/24') 80 | net.addLink('s3', 'r5', intfName2='port_S3') 81 | 82 | # Links general options 83 | net.setBwAll(5) 84 | 85 | # Nodes general options 86 | net.addTaskFile('tasks.txt') 87 | net.enablePcapDumpAll() 88 | net.enableLogAll() 89 | 90 | # Start the network 91 | net.startNetwork() -------------------------------------------------------------------------------- /examples/frrouters/p4app.json: -------------------------------------------------------------------------------- 1 | { 2 | "p4_src": "l2_learning_digest.p4", 3 | "cli": true, 4 | "pcap_dump": true, 5 | "enable_log": true, 6 | "tasks_file": "tasks.txt", 7 | "exec_scripts": [ 8 | { 9 | "cmd": "python l2_learning_controller.py s1 digest &", 10 | "reboot_run": true 11 | }, 12 | { 13 | "cmd": "python l2_learning_controller.py s2 digest &", 14 | "reboot_run": true 15 | }, 16 | { 17 | "cmd": "python l2_learning_controller.py s3 digest &", 18 | "reboot_run": true 19 | } 20 | ], 21 | "topology": { 22 | "default":{ 23 | "auto_arp_tables": false, 24 | "auto_gw_arp": false, 25 | "bw": 5 26 | }, 27 | "links": [ 28 | ["h1", "s1", 29 | {"params1" : {"ip": "1.0.0.2/24"}} 30 | ], 31 | ["h4", "s1", 32 | {"params1" : {"ip": "1.0.0.3/24"}} 33 | ], 34 | ["h2", "s2", 35 | {"params1" : {"ip": "1.7.0.2/24"}} 36 | ], 37 | ["h5", "s2", 38 | {"params1" : {"ip": "1.7.0.3/24"}} 39 | ], 40 | ["h3", "s3", 41 | {"params1" : {"ip": "2.0.0.2/24"}} 42 | ], 43 | ["h6", "s3", 44 | {"params1" : {"ip": "2.0.0.3/24"}} 45 | ], 46 | ["s1", "r1", 47 | {"intfName2" : "port_S1"} 48 | ], 49 | ["s2", "r2", 50 | {"intfName2" : "port_S2"} 51 | ], 52 | ["s3", "r5", 53 | {"intfName2" : "port_S3"} 54 | ], 55 | ["r1", "r2", 56 | {"intfName1" : "port_R2", "intfName2": "port_R1"} 57 | ], 58 | ["r1", "r3", 59 | {"intfName1" : "port_R3", "intfName2": "port_R1"} 60 | ], 61 | ["r1", "r4", 62 | {"intfName1" : "port_R4", "intfName2": "port_R1"} 63 | ], 64 | ["r2", "r3", 65 | {"intfName1" : "port_R3", "intfName2": "port_R2"} 66 | ], 67 | ["r2", "r4", 68 | {"intfName1" : "port_R4", "intfName2": "port_R2"} 69 | ], 70 | ["r3", "r4", 71 | {"intfName1" : "port_R4", "intfName2": "port_R3"} 72 | ], 73 | ["r4", "r5", 74 | {"intfName1" : "port_AS2", "intfName2": "port_AS1"} 75 | ] 76 | ], 77 | "hosts": { 78 | "h1": { 79 | "defaultRoute": "via 1.0.0.1" 80 | }, 81 | "h2": { 82 | "defaultRoute": "via 1.7.0.1" 83 | }, 84 | "h3": { 85 | "defaultRoute": "via 2.0.0.1" 86 | }, 87 | "h4": { 88 | "defaultRoute": "via 1.0.0.1" 89 | }, 90 | "h5": { 91 | "defaultRoute": "via 1.7.0.1" 92 | }, 93 | "h6": { 94 | "defaultRoute": "via 2.0.0.1" 95 | } 96 | }, 97 | "switches": { 98 | "s1": { 99 | }, 100 | "s2": { 101 | }, 102 | "s3": { 103 | } 104 | }, 105 | "routers": { 106 | "r1": { 107 | "int_conf": "./routers/r1.conf", 108 | "ldpd": "True" 109 | }, 110 | "r2": { 111 | "int_conf": "./routers/r2.conf", 112 | "ldpd": "True" 113 | }, 114 | "r3": { 115 | "int_conf": "./routers/r3.conf", 116 | "ldpd": "True" 117 | }, 118 | "r4": { 119 | "int_conf": "./routers/r4.conf", 120 | "ldpd": "True" 121 | }, 122 | "r5": { 123 | "int_conf": "./routers/r5.conf", 124 | "ldpd": "True" 125 | } 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /examples/frrouters/receive.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys 3 | import struct 4 | import os 5 | 6 | from scapy.all import sniff, sendp, hexdump, get_if_list, get_if_hwaddr 7 | from scapy.all import Packet, IPOption 8 | from scapy.all import ShortField, IntField, LongField, BitField, FieldListField, FieldLenField 9 | from scapy.all import IP, UDP, Raw 10 | from scapy.layers.inet import _IPOption_HDR 11 | 12 | def get_if(): 13 | ifs=get_if_list() 14 | iface=None 15 | for i in get_if_list(): 16 | if "eth0" in i: 17 | iface=i 18 | break 19 | if not iface: 20 | print("Cannot find eth0 interface") 21 | exit(1) 22 | return iface 23 | 24 | class IPOption_MRI(IPOption): 25 | name = "MRI" 26 | option = 31 27 | fields_desc = [ _IPOption_HDR, 28 | FieldLenField("length", None, fmt="B", 29 | length_of="swids", 30 | adjust=lambda pkt,l:l+4), 31 | ShortField("count", 0), 32 | FieldListField("swids", 33 | [], 34 | IntField("", 0), 35 | length_from=lambda pkt:pkt.count*4) ] 36 | def handle_pkt(pkt): 37 | print("got a packet") 38 | pkt.show2() 39 | # hexdump(pkt) 40 | sys.stdout.flush() 41 | 42 | 43 | def main(): 44 | ifaces = [i for i in os.listdir('/sys/class/net/') if 'eth' in i] 45 | iface = ifaces[0] 46 | print("sniffing on %s" % iface) 47 | sys.stdout.flush() 48 | sniff(filter="tcp", iface = iface, 49 | prn = lambda x: handle_pkt(x)) 50 | 51 | if __name__ == '__main__': 52 | main() 53 | -------------------------------------------------------------------------------- /examples/frrouters/routers/r1.conf: -------------------------------------------------------------------------------- 1 | ip forwarding 2 | ! 3 | no ipv6 forwarding 4 | ! 5 | interface lo 6 | ip address 1.0.151.1/32 7 | ! 8 | interface port_S1 9 | ip address 1.0.0.1/24 10 | ip ospf cost 1 11 | ! 12 | interface port_R2 13 | ip address 1.1.0.1/24 14 | ip ospf cost 3 15 | ! 16 | interface port_R3 17 | ip address 1.2.0.1/24 18 | ip ospf cost 1 19 | ! 20 | interface port_R4 21 | ip address 1.5.0.1/24 22 | ip ospf cost 3 23 | ! 24 | router ospf 25 | ospf router-id 1.0.151.1 26 | network 1.0.0.0/8 area 0 27 | ! 28 | router bgp 1 29 | neighbor 1.0.152.1 remote-as 1 30 | neighbor 1.0.152.1 update-source lo 31 | neighbor 1.0.154.1 remote-as 1 32 | neighbor 1.0.154.1 update-source lo 33 | ! 34 | address-family ipv4 unicast 35 | network 1.0.0.0/8 36 | no bgp network import-check 37 | exit-address-family 38 | ! 39 | mpls ldp 40 | router-id 1.0.151.1 41 | ! 42 | address-family ipv4 43 | discovery transport-address 1.0.151.1 44 | ! 45 | interface port_R2 46 | ! 47 | interface port_R3 48 | ! 49 | interface port_R4 50 | ! 51 | exit-address-family 52 | ! 53 | ! 54 | line vty 55 | ! -------------------------------------------------------------------------------- /examples/frrouters/routers/r2.conf: -------------------------------------------------------------------------------- 1 | ip forwarding 2 | ! 3 | no ipv6 forwarding 4 | ! 5 | interface lo 6 | ip address 1.0.152.1/32 7 | ! 8 | interface port_S2 9 | ip address 1.7.0.1/24 10 | ip ospf cost 1 11 | ! 12 | interface port_R1 13 | ip address 1.1.0.2/24 14 | ip ospf cost 3 15 | ! 16 | interface port_R3 17 | ip address 1.3.0.1/24 18 | ip ospf cost 1 19 | ! 20 | interface port_R4 21 | ip address 1.6.0.1/24 22 | ip ospf cost 3 23 | ! 24 | router ospf 25 | ospf router-id 1.0.152.1 26 | network 1.0.0.0/8 area 0 27 | ! 28 | router bgp 1 29 | neighbor 1.0.151.1 remote-as 1 30 | neighbor 1.0.151.1 update-source lo 31 | neighbor 1.0.154.1 remote-as 1 32 | neighbor 1.0.154.1 update-source lo 33 | ! 34 | address-family ipv4 unicast 35 | network 1.0.0.0/8 36 | no bgp network import-check 37 | exit-address-family 38 | ! 39 | mpls ldp 40 | router-id 1.0.152.1 41 | ! 42 | address-family ipv4 43 | discovery transport-address 1.0.152.1 44 | ! 45 | interface port_R1 46 | ! 47 | interface port_R3 48 | ! 49 | interface port_R4 50 | ! 51 | exit-address-family 52 | ! 53 | ! 54 | line vty 55 | ! -------------------------------------------------------------------------------- /examples/frrouters/routers/r3.conf: -------------------------------------------------------------------------------- 1 | ip forwarding 2 | ! 3 | no ipv6 forwarding 4 | ! 5 | interface lo 6 | ip address 1.0.153.1/32 7 | ! 8 | interface port_R1 9 | ip address 1.2.0.2/24 10 | ip ospf cost 1 11 | ! 12 | interface port_R2 13 | ip address 1.3.0.2/24 14 | ip ospf cost 1 15 | ! 16 | interface port_R4 17 | ip address 1.4.0.2/24 18 | ip ospf cost 1 19 | ! 20 | router ospf 21 | ospf router-id 1.0.153.1 22 | network 1.0.0.0/8 area 0 23 | ! 24 | mpls ldp 25 | router-id 1.0.153.1 26 | ! 27 | address-family ipv4 28 | discovery transport-address 1.0.153.1 29 | ! 30 | interface port_R1 31 | ! 32 | interface port_R2 33 | ! 34 | interface port_R4 35 | ! 36 | exit-address-family 37 | ! 38 | ! 39 | line vty 40 | ! -------------------------------------------------------------------------------- /examples/frrouters/routers/r4.conf: -------------------------------------------------------------------------------- 1 | ip forwarding 2 | ! 3 | no ipv6 forwarding 4 | ! 5 | interface lo 6 | ip address 1.0.154.1/32 7 | ! 8 | interface port_R1 9 | ip address 1.5.0.2/24 10 | ip ospf cost 3 11 | ! 12 | interface port_R2 13 | ip address 1.6.0.2/24 14 | ip ospf cost 3 15 | ! 16 | interface port_R3 17 | ip address 1.4.0.1/24 18 | ip ospf cost 1 19 | ! 20 | interface port_AS2 21 | ip address 179.0.0.1/24 22 | ! 23 | router ospf 24 | ospf router-id 1.0.154.1 25 | network 1.0.0.0/8 area 0 26 | ! 27 | router bgp 1 28 | neighbor 1.0.151.1 remote-as 1 29 | neighbor 1.0.151.1 update-source lo 30 | neighbor 1.0.152.1 remote-as 1 31 | neighbor 1.0.152.1 update-source lo 32 | neighbor 179.0.0.2 remote-as 2 33 | ! 34 | address-family ipv4 unicast 35 | network 1.0.0.0/8 36 | no bgp network import-check 37 | neighbor 1.0.151.1 next-hop-self 38 | neighbor 1.0.152.1 next-hop-self 39 | neighbor 179.0.0.2 route-map ACCEPT in 40 | neighbor 179.0.0.2 route-map ACCEPT out 41 | exit-address-family 42 | ! 43 | mpls ldp 44 | router-id 1.0.154.1 45 | ! 46 | address-family ipv4 47 | discovery transport-address 1.0.154.1 48 | ! 49 | interface port_R1 50 | ! 51 | interface port_R2 52 | ! 53 | interface port_R3 54 | ! 55 | exit-address-family 56 | ! 57 | ! 58 | route-map ACCEPT permit 10 59 | ! 60 | line vty 61 | ! -------------------------------------------------------------------------------- /examples/frrouters/routers/r5.conf: -------------------------------------------------------------------------------- 1 | ip forwarding 2 | ! 3 | no ipv6 forwarding 4 | ! 5 | interface lo 6 | ip address 2.0.155.1/32 7 | ! 8 | interface port_S3 9 | ip address 2.0.0.1/24 10 | ip ospf cost 2 11 | ! 12 | interface port_AS1 13 | ip address 179.0.0.2/24 14 | ip ospf cost 2 15 | ! 16 | router ospf 17 | ospf router-id 2.0.155.1 18 | network 2.0.0.0/8 area 0 19 | ! 20 | router bgp 2 21 | neighbor 179.0.0.1 remote-as 1 22 | ! 23 | address-family ipv4 unicast 24 | network 2.0.0.0/8 25 | no bgp network import-check 26 | neighbor 179.0.0.1 route-map ACCEPT in 27 | neighbor 179.0.0.1 route-map ACCEPT out 28 | exit-address-family 29 | ! 30 | route-map ACCEPT permit 10 31 | ! 32 | line vty 33 | ! -------------------------------------------------------------------------------- /examples/frrouters/send.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import argparse 3 | import sys 4 | import socket 5 | import random 6 | import struct 7 | 8 | from scapy.all import sendp, send, get_if_list, get_if_hwaddr 9 | from scapy.all import Packet 10 | from scapy.all import Ether, IP, UDP, TCP 11 | 12 | def get_if(): 13 | ifs=get_if_list() 14 | iface=None # "h1-eth0" 15 | for i in get_if_list(): 16 | if "eth0" in i: 17 | iface=i 18 | break 19 | if not iface: 20 | print("Cannot find eth0 interface") 21 | exit(1) 22 | return iface 23 | 24 | def main(): 25 | 26 | if len(sys.argv)<3: 27 | print('pass 2 arguments: ""') 28 | exit(1) 29 | 30 | addr = socket.gethostbyname(sys.argv[1]) 31 | iface = get_if() 32 | 33 | print("sending on interface %s to %s" % (iface, str(addr))) 34 | pkt = Ether(src=get_if_hwaddr(iface), dst='ff:ff:ff:ff:ff:ff') 35 | pkt = pkt /IP(dst=addr) / TCP(dport=1234, sport=random.randint(49152,65535)) / sys.argv[2] 36 | pkt.show2() 37 | sendp(pkt, iface=iface, verbose=False) 38 | 39 | 40 | if __name__ == '__main__': 41 | main() 42 | -------------------------------------------------------------------------------- /examples/frrouters/tasks.txt: -------------------------------------------------------------------------------- 1 | # Communication in the same subnet 2 | h1 15 30 send_udp_flow --dst 1.0.0.3 --sport 5000 --dport 5051 --rate 10M 3 | h4 15 30 recv_udp_flow --dport 5051 4 | # Monitor bandwitdth 5 | s1 0 0 "python -m p4utils.utils.monitor -i s1-eth1 -d 100 h1.csv" 6 | s1 0 0 "python -m p4utils.utils.monitor -i s1-eth2 -d 100 h4.csv" 7 | 8 | # Communication in the same AS 9 | h4 45 30 send_udp_flow --dst 1.7.0.2 --sport 5001 --dport 5051 --rate 10M 10 | h2 45 30 recv_udp_flow --dport 5051 11 | # Monitor bandwitdth 12 | s2 0 0 "python -m p4utils.utils.monitor -i s2-eth1 -d 100 h2.csv" 13 | 14 | # Communication among ASes 15 | h2 60 30 send_udp_flow --dst 2.0.0.2 --sport 5002 --dport 5051 --rate 10M 16 | h3 60 30 recv_udp_flow --dport 5051 17 | # Monitor bandwitdth 18 | s3 0 0 "python -m p4utils.utils.monitor -i s3-eth1 -d 100 h3.csv" 19 | -------------------------------------------------------------------------------- /examples/switches/README.md: -------------------------------------------------------------------------------- 1 | # Implementing Basic Forwarding 2 | 3 | ``` 4 | +--+ 5 | |h4| 6 | ++-+ 7 | | 8 | | 9 | +--+ +--+ ++-+ +--+ 10 | |h1+------+s1+-----+s3+-----+h3| 11 | +--+ +-++ +--+ +--+ 12 | | 13 | | 14 | +-++ 15 | |s2| 16 | +-++ 17 | | 18 | | 19 | +-++ 20 | |h2| 21 | +--+ 22 | ``` 23 | 24 | ## Introduction 25 | 26 | The objective of this exercise is to write a P4 program that 27 | implements basic forwarding. To keep things simple, we will just 28 | implement forwarding for IPv4. 29 | 30 | With IPv4 forwarding, the switch must perform the following actions 31 | for every packet: (i) update the source and destination MAC addresses, 32 | (ii) decrement the time-to-live (TTL) in the IP header, and (iii) 33 | forward the packet out the appropriate port. 34 | 35 | Your switch will have a single table, which the control plane will 36 | populate with static rules. Each rule will map an IP address to the 37 | MAC address and output port for the next hop. We have already defined 38 | the control plane rules, so you only need to implement the data plane 39 | logic of your P4 program. 40 | 41 | 42 | # Parser 43 | 44 | The parser describes a state machine with one `start` state and two possible final states: `accept` 45 | or `reject`. Explain the basic state machine used to parse ethernet and ipv4, and explain that this 46 | can be used later to access those headers fields. -------------------------------------------------------------------------------- /examples/switches/forwarding.p4: -------------------------------------------------------------------------------- 1 | /* -*- P4_16 -*- */ 2 | #include 3 | #include 4 | 5 | const bit<16> TYPE_IPV4 = 0x800; 6 | 7 | /************************************************************************* 8 | *********************** H E A D E R S *********************************** 9 | *************************************************************************/ 10 | 11 | typedef bit<9> egressSpec_t; 12 | typedef bit<48> macAddr_t; 13 | typedef bit<32> ip4Addr_t; 14 | 15 | header ethernet_t { 16 | macAddr_t dstAddr; 17 | macAddr_t srcAddr; 18 | bit<16> etherType; 19 | } 20 | 21 | header ipv4_t { 22 | bit<4> version; 23 | bit<4> ihl; 24 | bit<8> diffserv; 25 | bit<16> totalLen; 26 | bit<16> identification; 27 | bit<3> flags; 28 | bit<13> fragOffset; 29 | bit<8> ttl; 30 | bit<8> protocol; 31 | bit<16> hdrChecksum; 32 | ip4Addr_t srcAddr; 33 | ip4Addr_t dstAddr; 34 | } 35 | 36 | struct metadata { 37 | /* empty */ 38 | } 39 | 40 | struct headers { 41 | ethernet_t ethernet; 42 | ipv4_t ipv4; 43 | } 44 | 45 | /************************************************************************* 46 | *********************** P A R S E R *********************************** 47 | *************************************************************************/ 48 | 49 | parser MyParser(packet_in packet, 50 | out headers hdr, 51 | inout metadata meta, 52 | inout standard_metadata_t standard_metadata) { 53 | 54 | state start { 55 | 56 | packet.extract(hdr.ethernet); 57 | transition select(hdr.ethernet.etherType){ 58 | 59 | TYPE_IPV4: ipv4; 60 | default: accept; 61 | 62 | } 63 | 64 | } 65 | 66 | state ipv4 { 67 | 68 | packet.extract(hdr.ipv4); 69 | transition accept; 70 | } 71 | 72 | } 73 | 74 | 75 | /************************************************************************* 76 | ************ C H E C K S U M V E R I F I C A T I O N ************* 77 | *************************************************************************/ 78 | 79 | control MyVerifyChecksum(inout headers hdr, inout metadata meta) { 80 | apply { } 81 | } 82 | 83 | 84 | /************************************************************************* 85 | ************** I N G R E S S P R O C E S S I N G ******************* 86 | *************************************************************************/ 87 | 88 | control MyIngress(inout headers hdr, 89 | inout metadata meta, 90 | inout standard_metadata_t standard_metadata) { 91 | 92 | action drop() { 93 | mark_to_drop(standard_metadata); 94 | } 95 | 96 | action ipv4_forward(macAddr_t dstAddr, egressSpec_t port) { 97 | 98 | //set the src mac address as the previous dst, this is not correct right? 99 | hdr.ethernet.srcAddr = hdr.ethernet.dstAddr; 100 | 101 | //set the destination mac address that we got from the match in the table 102 | hdr.ethernet.dstAddr = dstAddr; 103 | 104 | //set the output port that we also get from the table 105 | standard_metadata.egress_spec = port; 106 | 107 | //decrease ttl by 1 108 | hdr.ipv4.ttl = hdr.ipv4.ttl -1; 109 | 110 | } 111 | 112 | table ipv4_lpm { 113 | key = { 114 | hdr.ipv4.dstAddr: lpm; 115 | } 116 | actions = { 117 | ipv4_forward; 118 | drop; 119 | NoAction; 120 | } 121 | size = 1024; 122 | default_action = NoAction(); 123 | } 124 | 125 | apply { 126 | 127 | //only if IPV4 the rule is applied. Therefore other packets will not be forwarded. 128 | if (hdr.ipv4.isValid()){ 129 | ipv4_lpm.apply(); 130 | 131 | } 132 | } 133 | } 134 | 135 | /************************************************************************* 136 | **************** E G R E S S P R O C E S S I N G ******************* 137 | *************************************************************************/ 138 | 139 | control MyEgress(inout headers hdr, 140 | inout metadata meta, 141 | inout standard_metadata_t standard_metadata) { 142 | apply { } 143 | } 144 | 145 | /************************************************************************* 146 | ************* C H E C K S U M C O M P U T A T I O N ************** 147 | *************************************************************************/ 148 | 149 | control MyComputeChecksum(inout headers hdr, inout metadata meta) { 150 | apply { 151 | update_checksum( 152 | hdr.ipv4.isValid(), 153 | { hdr.ipv4.version, 154 | hdr.ipv4.ihl, 155 | hdr.ipv4.diffserv, 156 | hdr.ipv4.totalLen, 157 | hdr.ipv4.identification, 158 | hdr.ipv4.flags, 159 | hdr.ipv4.fragOffset, 160 | hdr.ipv4.ttl, 161 | hdr.ipv4.protocol, 162 | hdr.ipv4.srcAddr, 163 | hdr.ipv4.dstAddr }, 164 | hdr.ipv4.hdrChecksum, 165 | HashAlgorithm.csum16); 166 | } 167 | } 168 | 169 | 170 | /************************************************************************* 171 | *********************** D E P A R S E R ******************************* 172 | *************************************************************************/ 173 | 174 | control MyDeparser(packet_out packet, in headers hdr) { 175 | apply { 176 | 177 | //parsed headers have to be added again into the packet. 178 | packet.emit(hdr.ethernet); 179 | packet.emit(hdr.ipv4); 180 | 181 | } 182 | } 183 | 184 | /************************************************************************* 185 | *********************** S W I T C H ******************************* 186 | *************************************************************************/ 187 | 188 | //switch architecture 189 | V1Switch( 190 | MyParser(), 191 | MyVerifyChecksum(), 192 | MyIngress(), 193 | MyEgress(), 194 | MyComputeChecksum(), 195 | MyDeparser() 196 | ) main; -------------------------------------------------------------------------------- /examples/switches/network.py: -------------------------------------------------------------------------------- 1 | from p4utils.mininetlib.network_API import NetworkAPI 2 | 3 | net = NetworkAPI() 4 | 5 | # Network general options 6 | net.setLogLevel('info') 7 | net.enableCli() 8 | 9 | # Network definition 10 | net.addP4Switch('s1') 11 | net.setP4CliInput('s1', 's1-commands.txt') 12 | net.addP4Switch('s2') 13 | net.setP4CliInput('s2', 's2-commands.txt') 14 | net.addP4Switch('s3') 15 | net.setP4CliInput('s3', 's3-commands.txt') 16 | net.setP4SourceAll('forwarding.p4') 17 | 18 | net.addHost('h1') 19 | net.addHost('h2') 20 | net.addHost('h3') 21 | net.addHost('h4') 22 | 23 | net.addLink('h1', 's1', weight=5) 24 | net.setBw('h1', 's1', 20) 25 | net.setDelay('h1', 's1', 20) 26 | net.setMaxQueueSize('h1', 's1', 100) 27 | net.setLoss('h1', 's1', 0.01) 28 | net.addLink('h2', 's2') 29 | net.addLink('s1', 's2') 30 | net.addLink('h3', 's3') 31 | net.addLink('h4', 's3') 32 | net.addLink('s1', 's3') 33 | 34 | # Assignment strategy 35 | net.mixed() 36 | 37 | # Nodes general options 38 | net.addTaskFile('tasks.txt') 39 | net.enablePcapDumpAll() 40 | net.enableLogAll() 41 | 42 | # Start the network 43 | net.startNetwork() -------------------------------------------------------------------------------- /examples/switches/p4app.json: -------------------------------------------------------------------------------- 1 | { 2 | "p4_src": "forwarding.p4", 3 | "cli": true, 4 | "pcap_dump": true, 5 | "enable_log": true, 6 | "tasks_file": "tasks.txt", 7 | "topology": { 8 | "assignment_strategy": "mixed", 9 | "links": [["h1", "s1", {"delay":"20ms", "loss": 1, "bw": 20, "max_queue_size": 100, "weight":5}], ["h2", "s2"], ["s1", "s2"], ["h3", "s3"], ["h4", "s3"], ["s1", "s3"]], 10 | "hosts": { 11 | "h1": { 12 | }, 13 | "h2": { 14 | }, 15 | "h3": { 16 | }, 17 | "h4": { 18 | } 19 | }, 20 | "switches": { 21 | "s1": { 22 | "cli_input": "s1-commands.txt" 23 | }, 24 | "s2": { 25 | "cli_input": "s2-commands.txt" 26 | }, 27 | "s3": { 28 | "cli_input": "s3-commands.txt" 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/switches/receive.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys 3 | import struct 4 | import os 5 | 6 | from scapy.all import sniff, sendp, hexdump, get_if_list, get_if_hwaddr 7 | from scapy.all import Packet, IPOption 8 | from scapy.all import ShortField, IntField, LongField, BitField, FieldListField, FieldLenField 9 | from scapy.all import IP, UDP, Raw 10 | from scapy.layers.inet import _IPOption_HDR 11 | 12 | def get_if(): 13 | ifs=get_if_list() 14 | iface=None 15 | for i in get_if_list(): 16 | if "eth0" in i: 17 | iface=i 18 | break 19 | if not iface: 20 | print("Cannot find eth0 interface") 21 | exit(1) 22 | return iface 23 | 24 | class IPOption_MRI(IPOption): 25 | name = "MRI" 26 | option = 31 27 | fields_desc = [ _IPOption_HDR, 28 | FieldLenField("length", None, fmt="B", 29 | length_of="swids", 30 | adjust=lambda pkt,l:l+4), 31 | ShortField("count", 0), 32 | FieldListField("swids", 33 | [], 34 | IntField("", 0), 35 | length_from=lambda pkt:pkt.count*4) ] 36 | def handle_pkt(pkt): 37 | print("got a packet") 38 | pkt.show2() 39 | # hexdump(pkt) 40 | sys.stdout.flush() 41 | 42 | 43 | def main(): 44 | ifaces = [i for i in os.listdir('/sys/class/net/') if 'eth' in i] 45 | iface = ifaces[0] 46 | print("sniffing on %s" % iface) 47 | sys.stdout.flush() 48 | sniff(filter="tcp", iface = iface, 49 | prn = lambda x: handle_pkt(x)) 50 | 51 | if __name__ == '__main__': 52 | main() 53 | -------------------------------------------------------------------------------- /examples/switches/s1-commands.txt: -------------------------------------------------------------------------------- 1 | table_set_default ipv4_lpm drop 2 | table_add ipv4_lpm ipv4_forward 10.0.1.1/32 => 00:00:0a:00:01:01 1 3 | table_add ipv4_lpm ipv4_forward 10.0.2.2/32 => 00:00:00:02:01:00 2 4 | table_add ipv4_lpm ipv4_forward 10.0.3.0/24 => 00:00:00:03:01:00 3 5 | 6 | -------------------------------------------------------------------------------- /examples/switches/s2-commands.txt: -------------------------------------------------------------------------------- 1 | table_set_default ipv4_lpm drop 2 | table_add ipv4_lpm ipv4_forward 10.0.1.1/32 => 00:00:00:01:02:00 2 3 | table_add ipv4_lpm ipv4_forward 10.0.2.2/32 => 00:00:0a:00:02:02 1 4 | table_add ipv4_lpm ipv4_forward 10.0.3.0/24 => 00:00:00:01:02:00 2 5 | -------------------------------------------------------------------------------- /examples/switches/s3-commands.txt: -------------------------------------------------------------------------------- 1 | table_set_default ipv4_lpm drop 2 | table_add ipv4_lpm ipv4_forward 10.0.1.0/24 => 00:00:00:01:03:00 3 3 | table_add ipv4_lpm ipv4_forward 10.0.2.0/24 => 00:00:00:01:03:00 3 4 | table_add ipv4_lpm ipv4_forward 10.0.3.3/32 => 00:00:0a:00:03:03 1 5 | table_add ipv4_lpm ipv4_forward 10.0.3.4/32 => 00:00:0a:00:03:04 2 6 | 7 | -------------------------------------------------------------------------------- /examples/switches/send.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import argparse 3 | import sys 4 | import socket 5 | import random 6 | import struct 7 | 8 | from scapy.all import sendp, send, get_if_list, get_if_hwaddr 9 | from scapy.all import Packet 10 | from scapy.all import Ether, IP, UDP, TCP 11 | 12 | def get_if(): 13 | ifs=get_if_list() 14 | iface=None # "h1-eth0" 15 | for i in get_if_list(): 16 | if "eth0" in i: 17 | iface=i 18 | break 19 | if not iface: 20 | print("Cannot find eth0 interface") 21 | exit(1) 22 | return iface 23 | 24 | def main(): 25 | 26 | if len(sys.argv)<3: 27 | print('pass 2 arguments: ""') 28 | exit(1) 29 | 30 | addr = socket.gethostbyname(sys.argv[1]) 31 | iface = get_if() 32 | 33 | print("sending on interface %s to %s" % (iface, str(addr))) 34 | pkt = Ether(src=get_if_hwaddr(iface), dst='ff:ff:ff:ff:ff:ff') 35 | pkt = pkt /IP(dst=addr) / TCP(dport=1234, sport=random.randint(49152,65535)) / sys.argv[2] 36 | pkt.show2() 37 | sendp(pkt, iface=iface, verbose=False) 38 | 39 | 40 | if __name__ == '__main__': 41 | main() 42 | -------------------------------------------------------------------------------- /examples/switches/tasks.txt: -------------------------------------------------------------------------------- 1 | h2 10.5 30 "ping 10.0.3.3" 2 | h1 20 41 recv_udp_flow --dport 5051 3 | h3 20.9 40 send_udp_flow --dst 10.0.1.1 --dport 5051 --rate 1M -------------------------------------------------------------------------------- /examples/tofino/README.md: -------------------------------------------------------------------------------- 1 | # Heavy hitter with tofino model switches 2 | 3 | You can use p4-utils to start the topology with Tofino switches and hosts. As 4 | first parameter you have to set the path to the SDE. 5 | 6 | ``` 7 | # start network 8 | sudo python network.py 9 | 10 | # controller code 11 | mx s1 12 | $SDE/run_bfshell.sh -b `pwd`/controller_1.py 13 | 14 | # controller code 15 | mx s2 16 | $SDE/run_bfshell.sh -b `pwd`/controller_2.py 17 | 18 | # start receiver 19 | mx h2 20 | python receive.py 21 | 22 | # send 1500 packets, and observe how only 1000 are received 23 | mx h1 24 | python send.py 10.2.2.2 1500 25 | ``` 26 | 27 | -------------------------------------------------------------------------------- /examples/tofino/controller_1.py: -------------------------------------------------------------------------------- 1 | from netaddr import IPAddress 2 | 3 | # Pipe variable 4 | p4 = bfrt.heavy_hitter.pipe 5 | 6 | # Table variable 7 | l3 = p4.Ingress.ipv4_lpm 8 | 9 | # Add forwarding rules 10 | l3.add_with_ipv4_forward(dstAddr=IPAddress('10.1.1.2'), dstAddr_p_length=32, port=1, dst_addr='00:00:0a:01:01:02') 11 | l3.add_with_ipv4_forward(dstAddr=IPAddress('10.2.2.2'), dstAddr_p_length=32, port=2, dst_addr='42:dc:00:d2:d6:9f') -------------------------------------------------------------------------------- /examples/tofino/controller_2.py: -------------------------------------------------------------------------------- 1 | from netaddr import IPAddress 2 | 3 | # Pipe variable 4 | p4 = bfrt.heavy_hitter.pipe 5 | 6 | # Table variable 7 | l3 = p4.Ingress.ipv4_lpm 8 | 9 | # Add forwarding rules 10 | l3.add_with_ipv4_forward(dstAddr=IPAddress('10.2.2.2'), dstAddr_p_length=32, port=1, dst_addr='00:00:0a:02:02:02') 11 | l3.add_with_ipv4_forward(dstAddr=IPAddress('10.1.1.2'), dstAddr_p_length=32, port=2, dst_addr='3a:e6:5d:d5:f5:0b') -------------------------------------------------------------------------------- /examples/tofino/network.py: -------------------------------------------------------------------------------- 1 | from p4utils.utils.compiler import BF_P4C 2 | from p4utils.mininetlib.network_API import NetworkAPI 3 | 4 | import sys 5 | 6 | SDE = sys.argv[1] 7 | SDE_INSTALL = SDE + "/install" 8 | 9 | net = NetworkAPI() 10 | 11 | # Network general options 12 | net.setLogLevel('info') 13 | net.enableCli() 14 | 15 | # Tofino compiler 16 | net.setCompiler(compilerClass=BF_P4C, sde=SDE, sde_install=SDE_INSTALL) 17 | 18 | # Network definition 19 | net.addTofino('s1', sde=SDE, sde_install=SDE_INSTALL) 20 | net.addTofino('s2', sde=SDE, sde_install=SDE_INSTALL) 21 | net.setP4SourceAll('heavy_hitter.p4') 22 | 23 | net.addHost('h1') 24 | net.addHost('h2') 25 | 26 | net.addLink('h1', 's1', port2=1) 27 | net.addLink('s1', 's2', port1=2, port2=2) 28 | net.addLink('s2', 'h2', port1=1) 29 | 30 | # Assignment strategy 31 | net.l3() 32 | 33 | # Nodes general options 34 | net.enableLogAll() 35 | 36 | # Start the network 37 | net.startNetwork() 38 | -------------------------------------------------------------------------------- /examples/tofino/receive.py: -------------------------------------------------------------------------------- 1 | from scapy.all import sniff, get_if_list, get_if_hwaddr 2 | from scapy.all import IP, TCP, Ether 3 | 4 | def get_if(): 5 | ifs=get_if_list() 6 | iface=None # "h1-eth0" 7 | for i in get_if_list(): 8 | if "eth0" in i: 9 | iface=i 10 | break 11 | if not iface: 12 | print("Cannot find eth0 interface") 13 | exit(1) 14 | return iface 15 | 16 | 17 | totals = {} 18 | iface = get_if() 19 | 20 | 21 | def handle_pkt(pkt): 22 | if IP in pkt and TCP in pkt: 23 | src_ip = pkt[IP].src 24 | dst_ip = pkt[IP].dst 25 | proto = pkt[IP].proto 26 | sport = pkt[TCP].sport 27 | dport = pkt[TCP].dport 28 | id_tup = (src_ip, dst_ip, proto, sport, dport) 29 | 30 | #filter packets that are sent from this interface. This is done to just focus on the receiving ones. 31 | #Some people had problems with this line since they set the src mac address to be the same than the destination, thus 32 | #all packets got filtered here. 33 | if get_if_hwaddr(iface) == pkt[Ether].src: 34 | return 35 | 36 | if id_tup not in totals: 37 | totals[id_tup] = 0 38 | totals[id_tup] += 1 39 | print("Received from %s total: %s" % 40 | (id_tup, totals[id_tup])) 41 | 42 | def main(): 43 | sniff(iface = iface, 44 | prn = lambda x: handle_pkt(x)) 45 | 46 | if __name__ == '__main__': 47 | main() -------------------------------------------------------------------------------- /examples/tofino/send.py: -------------------------------------------------------------------------------- 1 | from scapy.all import Ether, IP, sendp, get_if_hwaddr, get_if_list, TCP, Raw 2 | import sys, socket, random 3 | 4 | def get_if(): 5 | ifs=get_if_list() 6 | iface=None # "h1-eth0" 7 | for i in get_if_list(): 8 | if "eth0" in i: 9 | iface=i 10 | break 11 | if not iface: 12 | print("Cannot find eth0 interface") 13 | exit(1) 14 | return iface 15 | 16 | def send_random_traffic(dst_ip, num_packets): 17 | 18 | dst_addr = socket.gethostbyname(dst_ip) 19 | total_pkts = 0 20 | random_port = random.randint(1024,65000) 21 | iface = get_if() 22 | #For this exercise the destination mac address is not important. Just ignore the value we use. 23 | p = Ether(dst="00:01:0a:02:02:00", src=get_if_hwaddr(iface)) / IP(dst=dst_addr) 24 | p = p / TCP(dport=random_port) 25 | for i in range(num_packets): 26 | sendp(p, iface = iface) 27 | total_pkts += 1 28 | print("Sent %s packets in total" % total_pkts) 29 | 30 | if __name__ == '__main__': 31 | if len(sys.argv) < 2: 32 | print("Usage: python send.py ") 33 | sys.exit(1) 34 | else: 35 | dst_name = sys.argv[1] 36 | num_packets = int(sys.argv[2]) 37 | send_random_traffic(dst_name, num_packets) -------------------------------------------------------------------------------- /install-tools/README.md: -------------------------------------------------------------------------------- 1 | # Installation tools 2 | 3 | The manual installation process is quite long and cumbersome because of the 4 | dependencies that are needed by P4-Utils. For this reason, we provide a 5 | [Bash script](https://github.com/nsg-ethz/p4-utils/blob/master/install-tools/install-p4-dev.sh) 6 | that automatically goes through every step. 7 | 8 | > **Warning!** 9 | > The script has been tested with **Ubuntu 20.04.6** and **Ubuntu 22.04.3**. 10 | > To install for **Ubuntu 18.04** refer to [this older script](./old_installs/install-p4-dev.sh). 11 | 12 | With the following installation methods, you will download and install *Mininet* 13 | and the P4-Tools suite (P4-Utils, P4-Learning and their dependencies) in your 14 | user's home directory. 15 | 16 | ## One-Step Automated Install 17 | 18 | To get started quickly and conveniently, you may want to install the P4-Tools suite 19 | using the following command: 20 | 21 | ```bash 22 | curl -sSL https://raw.githubusercontent.com/nsg-ethz/p4-utils/master/install-tools/install-p4-dev.sh | bash 23 | ``` 24 | 25 | ## Alternative Installation Method 26 | 27 | The main drawback of piping to `bash` is that you cannot review the code 28 | that is going to run on your system. Therefore, we provide this alternative 29 | methods that allows you to inspect the intallation script: 30 | 31 | ```bash 32 | wget -O install-p4-dev.sh https://raw.githubusercontent.com/nsg-ethz/p4-utils/master/install-tools/install-p4-dev.sh 33 | bash install-p4-dev.sh 34 | ``` 35 | -------------------------------------------------------------------------------- /install-tools/conf_files/mininet.patch: -------------------------------------------------------------------------------- 1 | diff --git a/util/install.sh b/util/install.sh 2 | index 04f85b0..6cd4221 100755 3 | --- a/util/install.sh 4 | +++ b/util/install.sh 5 | @@ -5,6 +5,7 @@ 6 | 7 | # Fail on error 8 | set -e 9 | +set -x 10 | 11 | # Fail on unset var usage 12 | set -o nounset 13 | @@ -165,7 +166,7 @@ function mn_deps { 14 | $install gcc make socat psmisc xterm openssh-clients iperf \ 15 | iproute telnet python-setuptools libcgroup-tools \ 16 | ethtool help2man net-tools 17 | - $install ${PYPKG}-pyflakes pylint ${PYPKG}-pep8-naming 18 | + $install ${PYPKG}-pyflakes pylint ${PYPKG}-pep8-naming \ 19 | ${PYPKG}-pexpect 20 | elif [ "$DIST" = "SUSE LINUX" ]; then 21 | $install gcc make socat psmisc xterm openssh iperf \ 22 | @@ -175,6 +176,7 @@ function mn_deps { 23 | else # Debian/Ubuntu 24 | pf=pyflakes 25 | pep8=pep8 26 | + pylint=pylint 27 | # Starting around 20.04, installing pyflakes instead of pyflakes3 28 | # causes Python 2 to be installed, which is exactly NOT what we want. 29 | if [ "$DIST" = "Ubuntu" -a `expr $RELEASE '>=' 20.04` = "1" ]; then 30 | @@ -187,10 +189,14 @@ function mn_deps { 31 | pf=pyflakes3 32 | pep8=python3-pep8 33 | fi 34 | + if [ "$DIST" = "Ubuntu" -a `expr $RELEASE '<=' 20.04` = "1" ]; then 35 | + pylint=pylint3 36 | + fi 37 | 38 | $install gcc make socat psmisc xterm ssh iperf telnet \ 39 | - ethtool help2man $pf pylint $pep8 \ 40 | + ethtool help2man $pylint \ 41 | net-tools ${PYPKG}-tk 42 | + $install --no-install-recommends $pf $pep8 43 | 44 | # Install pip 45 | $install ${PYPKG}-pip || $install ${PYPKG}-pip-whl 46 | @@ -303,7 +309,7 @@ function install_wireshark { 47 | if ! which wireshark; then 48 | echo "Installing Wireshark" 49 | if [ "$DIST" = "Fedora" -o "$DIST" = "RedHatEnterpriseServer" ]; then 50 | - $install wireshark wireshark-gnome 51 | + $install wireshark 52 | elif [ "$DIST" = "SUSE LINUX" ]; then 53 | $install wireshark 54 | else -------------------------------------------------------------------------------- /install-tools/conf_files/tmux.conf: -------------------------------------------------------------------------------- 1 | unbind C-b 2 | set -g prefix C-a 3 | bind-key C-a send-prefix 4 | set -g mouse on 5 | set-option -g history-limit 50000 6 | set-option -g renumber-windows on 7 | 8 | set -g default-terminal "screen-256color" 9 | set -g status-keys emacs 10 | 11 | set -g aggressive-resize on 12 | 13 | bind '"' split-window -c "#{pane_current_path}" 14 | bind % split-window -h -c "#{pane_current_path}" 15 | bind c new-window -c "#{pane_current_path}" 16 | -------------------------------------------------------------------------------- /install-tools/scripts/protoinitfix.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # This scripts creates a __init__.py file in the main directory of 3 | # the google module if it is not present already. Indeed the lack of such 4 | # file prevent sphinx from working correctly. 5 | 6 | import os 7 | import re 8 | import google 9 | 10 | 11 | path = re.findall(r"\['(.*)'\]", str(google.__path__))[0] 12 | init_file = os.path.join(path, '__init__.py') 13 | 14 | # If __init__.py is not present in the google module directory 15 | if not os.path.isfile(init_file): 16 | # Create __init__.py file 17 | with open(init_file, 'w') as f: 18 | pass 19 | print('File {} correctly created.'.format(init_file)) 20 | else: 21 | print('File {} already present.'.format(init_file)) -------------------------------------------------------------------------------- /install-tools/scripts/py3localpath.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Modified file from 3 | # https://github.com/jafingerhut/p4-guide/blob/master/bin/py3localpath.py 4 | 5 | import re 6 | import sys 7 | 8 | l1=[x for x in sys.path if re.match(r'/usr/local/lib/python3.[0-9]+/dist-packages$', x)] 9 | 10 | if len(l1) == 1: 11 | m = re.match(r'(/usr/local/lib/python3.[0-9]+)/dist-packages$', l1[0]) 12 | if m: 13 | print(m.group(1)) 14 | else: 15 | print("Inconceivable! Somehow the second pattern did not match but the first did.") 16 | sys.exit(1) 17 | else: 18 | print("Found %d matching entries in Python3 sys.path instead of 1: %s." 19 | % (len(l1), l1)) 20 | sys.exit(1) 21 | 22 | sys.exit(0) -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # p4-utils branch 4 | P4_UTILS_BRANCH="master" 5 | 6 | install_p4utils() { 7 | pip3 install -e "." 8 | } 9 | 10 | mx() { 11 | 12 | BINDIR=/usr/bin 13 | MANDIR=/usr/share/man/man1 14 | 15 | UTILSDIR=utils 16 | 17 | cd ${UTILSDIR} 18 | 19 | #compile mxexec 20 | cc -Wall -Wextra -DVERSION=\"1.4\" mxexec.c -o mxexec 21 | #create man page 22 | help2man -N -n "Mininet namespace execution utility" -h "-h" -v "-v" --no-discard-stderr ./mxexec -o mxexec.1 23 | 24 | #install 25 | install mxexec ${BINDIR} 26 | install mx ${BINDIR} 27 | install mxexec.1 ${MANDIR} 28 | 29 | cd .. 30 | } 31 | 32 | PY3LOCALPATH=`curl -sSL https://raw.githubusercontent.com/nsg-ethz/p4-utils/${P4_UTILS_BRANCH}/install-tools/scripts/py3localpath.py | python3` 33 | function site_packages_fix { 34 | local SRC_DIR 35 | local DST_DIR 36 | 37 | SRC_DIR="${PY3LOCALPATH}/site-packages" 38 | DST_DIR="${PY3LOCALPATH}/dist-packages" 39 | 40 | # When I tested this script on Ubunt 16.04, there was no 41 | # site-packages directory. Return without doing anything else if 42 | # this is the case. 43 | if [ ! -d ${SRC_DIR} ]; then 44 | return 0 45 | fi 46 | 47 | echo "Adding ${SRC_DIR} to Python3 path..." 48 | sudo su -c "echo '${SRC_DIR}' > ${DST_DIR}/p4-tools.pth" 49 | echo "Done!" 50 | } 51 | 52 | install_p4utils 53 | mx 54 | site_packages_fix 55 | -------------------------------------------------------------------------------- /p4app_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "p4_src": "" (string), 3 | "cli": "" (bool), 4 | "pcap_dump": "" (bool), 5 | "enable_log": "" (bool), 6 | "tasks_file": "" (string), 7 | "host_node": 8 | { 9 | "file_path": "" (string), 10 | "module_name": "" (string), 11 | "object_name": "" (string) 12 | }, 13 | "switch_node": 14 | { 15 | "file_path": "" (string), 16 | "module_name": "" (string), 17 | "object_name": "" (string) 18 | }, 19 | "router_node": 20 | { 21 | "file_path": "" (string), 22 | "module_name": "" (string), 23 | "object_name": "" (string) 24 | }, 25 | "compiler_module": 26 | { 27 | "file_path": "" (string), 28 | "module_name": "" (string), 29 | "object_name": "" (string), 30 | "options": "" (dict) 31 | }, 32 | "client_module": 33 | { 34 | "file_path": "" (string), 35 | "module_name": "" (string), 36 | "object_name": "" (string), 37 | "options": "" (dict) 38 | }, 39 | "mininet_module": 40 | { 41 | "file_path": "" (string), 42 | "module_name": "" (string), 43 | "object_name": "" (string) 44 | }, 45 | "exec_scripts": 46 | [ 47 | { 48 | "cmd": "" (string), 49 | "reboot_run": "" (bool) 50 | }, 51 | ... 52 | ], 53 | "topology": 54 | { 55 | "assignment_strategy": "" (string), 56 | "default": 57 | { 58 | "weight": "" (int) (*), 59 | "bw": "" (int) (*), 60 | "delay": "" (int) (*), 61 | "loss": "" (float) (*), 62 | "max_queue_size": "" (int) (*), 63 | "auto_arp_tables": "" (bool) (*), 64 | "auto_gw_arp": "" (bool) (*) 65 | }, 66 | "links": 67 | [ 68 | [ 69 | "r1", 70 | "h1", 71 | { 72 | "weight": "" (int) (*), 73 | "port1": "" (int) (*), 74 | "port2": "" (int) (*), 75 | "intfName1": "" (string) (*), 76 | "intfName2": "" (string) (*), 77 | "addr1": "" (string) (*), 78 | "addr2": "" (string) (*), 79 | "params1": "" (dict) (*), 80 | "params2": "" (dict) (*), 81 | "bw": "" (int) (*), 82 | "delay": "" (int) (*), 83 | "loss": "" (float) (*), 84 | "max_queue_size": "" (int) (*) 85 | } 86 | ], 87 | ... 88 | ], 89 | "hosts": 90 | { 91 | "h1": 92 | { 93 | "scheduler": "" (bool) (*), 94 | "socket_path": "

" (string) (*), 95 | "defaultRoute": "via """ (string) (*), 96 | "dhcp": "" (bool) (*), 97 | "log_enabled" : "" (bool) (*), 98 | "log_dir": "" (string) (*), 99 | "host_node": "" (dict) (*) 100 | }, 101 | ... 102 | }, 103 | "switches": 104 | { 105 | "s1": 106 | { 107 | "p4_src": "" (string) (*), 108 | "cpu_port": "" (bool) (*), 109 | "cli_input": "" (string) (*), 110 | "switch_node": "" (dict) (*), 111 | "log_enabled" : "" (bool) (*), 112 | "log_dir": "" (string) (*), 113 | "pcap_dump": "" (bool) (*), 114 | "pcap_dir": "" (string) (*), 115 | "sw_bin": "" (string) (*), 116 | "thrift_port": "" (int) (*), 117 | "grpc_port": "" (int) (*) 118 | }, 119 | ... 120 | }, 121 | "routers": 122 | { 123 | "r1": 124 | { 125 | "int_conf": "" (string), 126 | "conf_dir": "" (string), 129 | "router_node": "" (dict) (*), 130 | "zebra": "" (bool) (*), 131 | "bgpd": "" (bool) (*), 132 | "ospfd": "" (bool) (*), 133 | "ospf6d": "" (bool) (*), 134 | "ripd": "" (bool) (*), 135 | "ripngd": "" (bool) (*), 136 | "isisd": "" (bool) (*), 137 | "pimd": "" (bool) (*), 138 | "ldpd": "" (bool) (*), 139 | "nhrpd": "" (bool) (*), 140 | "eigrpd": "" (bool) (*), 141 | "babeld": "" (bool) (*), 142 | "sharpd": "" (bool) (*), 143 | "staticd": "" (bool) (*), 144 | "pbrd": "" (bool) (*), 145 | "bfdd": "" (bool) (*), 146 | "fabricd" : "" (bool) (*) 147 | }, 148 | ... 149 | } 150 | } 151 | } -------------------------------------------------------------------------------- /p4utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nsg-ethz/p4-utils/83b118bbae530b31cc74e7fa32f9174f7c0a1184/p4utils/__init__.py -------------------------------------------------------------------------------- /p4utils/mininetlib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nsg-ethz/p4-utils/83b118bbae530b31cc74e7fa32f9174f7c0a1184/p4utils/mininetlib/__init__.py -------------------------------------------------------------------------------- /p4utils/mininetlib/log.py: -------------------------------------------------------------------------------- 1 | """__ https://github.com/mininet/mininet/blob/master/mininet/log.py 2 | 3 | This module is an extension of `mininet.log`__ that implements colored logs. 4 | """ 5 | 6 | import sys 7 | import logging 8 | from mininet.log import * 9 | 10 | 11 | class ShellStyles: 12 | """Shell styles.""" 13 | reset='\033[0m' 14 | bold='\033[01m' 15 | disable='\033[02m' 16 | underline='\033[04m' 17 | reverse='\033[07m' 18 | strikethrough='\033[09m' 19 | invisible='\033[08m' 20 | 21 | 22 | class ShellFGColors: 23 | """Shell foreground colors.""" 24 | black='\033[30m' 25 | red='\033[31m' 26 | green='\033[32m' 27 | orange='\033[33m' 28 | blue='\033[34m' 29 | purple='\033[35m' 30 | cyan='\033[36m' 31 | lightgrey='\033[37m' 32 | darkgrey='\033[90m' 33 | lightred='\033[91m' 34 | lightgreen='\033[92m' 35 | yellow='\033[93m' 36 | lightblue='\033[94m' 37 | pink='\033[95m' 38 | lightcyan='\033[96m' 39 | 40 | 41 | class ShellBGColors: 42 | """Shell background colors.""" 43 | black='\033[40m' 44 | red='\033[41m' 45 | green='\033[42m' 46 | orange='\033[43m' 47 | blue='\033[44m' 48 | purple='\033[45m' 49 | cyan='\033[46m' 50 | lightgrey='\033[47m' 51 | darkgrey='\033[100m' 52 | lightred='\033[101m' 53 | lightgreen='\033[102m' 54 | yellow='\033[103m' 55 | lightblue='\033[104m' 56 | pink='\033[105m' 57 | lightcyan='\033[106m' 58 | 59 | 60 | LOG_FORMAT = { 61 | LEVELS['debug']: ShellStyles.disable, 62 | LEVELS['info']: ShellStyles.reset, 63 | LEVELS['output']: ShellStyles.bold, 64 | LEVELS['warning']: ShellStyles.bold + ShellFGColors.yellow, 65 | LEVELS['warn']: ShellStyles.bold + ShellFGColors.yellow, 66 | LEVELS['error']: ShellStyles.bold + ShellFGColors.red, 67 | LEVELS['critical']: ShellStyles.bold + ShellBGColors.red 68 | } 69 | 70 | 71 | class ColoredFormatter(logging.Formatter): 72 | """Get colored logs.""" 73 | def format(self, record): 74 | s = super().format(record) 75 | if record.levelno in LOG_FORMAT: 76 | s = LOG_FORMAT[record.levelno] + s 77 | if record.levelno == LEVELS['critical']: 78 | s += '\n' 79 | if s[-1] == '\n': 80 | s = s[:-1] + ShellStyles.reset + '\n' 81 | else: 82 | s += ShellStyles.reset 83 | return s 84 | 85 | 86 | # Add critical level 87 | critical = lg.critical 88 | 89 | # Set formatter 90 | formatter = ColoredFormatter( LOGMSGFORMAT ) 91 | lg.ch.setFormatter( formatter ) 92 | 93 | # Handle exceptions as critical 94 | def excepthook(type, value, traceback): 95 | critical('', exc_info=(type, value, traceback)) 96 | 97 | sys.excepthook = excepthook -------------------------------------------------------------------------------- /p4utils/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nsg-ethz/p4-utils/83b118bbae530b31cc74e7fa32f9174f7c0a1184/p4utils/utils/__init__.py -------------------------------------------------------------------------------- /p4utils/utils/client.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | 4 | from p4utils.utils.helper import * 5 | from p4utils.mininetlib.log import debug, info, error 6 | 7 | class ThriftClient: 8 | """ 9 | This controller reads commands from a thrift configuration 10 | file and uses it to set up the thrift switch. 11 | 12 | 13 | Args: 14 | cli_bin (string) : client binary file path. 15 | cli_input (string) : path of the configuration text file. 16 | log_enabled (bool) : whether to enable logs. 17 | log_dir (string) : directory to store logs. 18 | thrift_port (int) : thrift server thrift_port number. 19 | sw_name (string) : name of the switch to configure. 20 | """ 21 | cli_bin = 'simple_switch_CLI' 22 | 23 | @classmethod 24 | def set_binary(self, cli_bin): 25 | """Set class default binary.""" 26 | ThriftClient.cli_bin = cli_bin 27 | 28 | def __init__(self, thrift_port, 29 | sw_name, 30 | cli_bin=None, 31 | cli_input=None, 32 | log_enabled=True, 33 | log_dir='/tmp', 34 | **kwargs): 35 | 36 | self.set_conf(cli_input) 37 | self.sw_name = sw_name 38 | self.thrift_port = thrift_port 39 | self.log_enabled = log_enabled 40 | self.log_dir = log_dir 41 | 42 | if self.log_enabled: 43 | # Make sure that the provided log path is not pointing to a file 44 | # and, if necessary, create an empty log dir 45 | if not os.path.isdir(self.log_dir): 46 | if os.path.exists(self.log_dir): 47 | raise NotADirectoryError("'{}' exists and is not a directory.".format(self.log_dir)) 48 | else: 49 | os.mkdir(self.log_dir) 50 | 51 | if cli_bin is not None: 52 | self.set_binary(cli_bin) 53 | 54 | def get_conf(self): 55 | """Returns self.cli_input""" 56 | return self.cli_input 57 | 58 | def set_conf(self, cli_input): 59 | """Set the configuration file path.""" 60 | # Check whether the conf file is valid 61 | if cli_input is not None: 62 | self.cli_input = os.path.realpath(cli_input) 63 | else: 64 | self.cli_input = None 65 | 66 | def configure(self): 67 | """This method configures the switch with the provided file.""" 68 | if self.cli_input is not None: 69 | if not os.path.isfile(self.cli_input): 70 | raise FileNotFoundError('could not find file {} for switch {}.'.format(self.cli_input, self.sw_name)) 71 | elif check_listening_on_port(self.thrift_port): 72 | log_path = self.log_dir + '/{}_cli_output.log'.format(self.sw_name) 73 | with open(self.cli_input, 'r') as fin: 74 | entries = [x.strip() for x in fin.readlines() if x.strip() != ''] 75 | # Remove comments 76 | entries = [x for x in entries if ( not x.startswith('//') and not x.startswith('#')) ] 77 | # Join commands 78 | entries = '\n'.join(entries) 79 | # Execute commands 80 | debug(self.cli_bin + ' --thrift-port ' + str(self.thrift_port) + '\n') 81 | p = subprocess.Popen([self.cli_bin, '--thrift-port', str(self.thrift_port)], 82 | stdin=subprocess.PIPE, 83 | stdout=subprocess.PIPE, 84 | stderr=subprocess.STDOUT) 85 | stdout, _ = p.communicate(input=entries.encode()) 86 | stdout = stdout.decode(errors='backslashreplace') 87 | # Save logs 88 | if self.log_enabled: 89 | with open(log_path, 'w') as log_file: 90 | log_file.write(stdout) 91 | # Successful configuration 92 | success = True 93 | # Check for errors in the commands (they are in the stdout) 94 | if 'Invalid' in stdout or 'Error' in stdout: 95 | error('Switch {}: error in file {}, ' 96 | 'check {} for details.\n'.format(self.sw_name, 97 | self.cli_input, 98 | log_path)) 99 | success = False 100 | # Check returncode 101 | if p.returncode != 0: 102 | error('Switch {}: thrift client exited with error, ' 103 | 'check {} for details.\n'.format(self.sw_name, 104 | log_path)) 105 | 106 | success = False 107 | # Print success 108 | if success: 109 | info('Switch {}: successfully configured with file {}.\n'.format(self.sw_name, 110 | self.cli_input)) 111 | else: 112 | raise ConnectionRefusedError('could not connect to switch {} on port {}.'.format(self.sw_name, self.thrift_port)) 113 | else: 114 | raise FileNotFoundError('could not find file {} for switch {}.'.format(self.cli_input, self.sw_name)) 115 | -------------------------------------------------------------------------------- /p4utils/utils/monitor.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import csv 3 | import time 4 | import argparse 5 | 6 | class Monitor: 7 | """Monitor the interface bandwidth and dump a .csv file with 8 | the rates in Mbps. 9 | 10 | Args: 11 | csv_file (string): path to the output file 12 | i (string) : name of the interface to monitor 13 | t (float) : interval between data points 14 | d (float) : monitoring duration 15 | """ 16 | 17 | def __init__(self, csv_file, i, t=0.5, d=60): 18 | 19 | current_time = time.time() 20 | start_time = current_time 21 | stop_time = current_time + d 22 | old_tx = None 23 | old_rx = None 24 | data = [] 25 | 26 | while current_time < stop_time: 27 | current_time = time.time() 28 | with open('/sys/class/net/{}/statistics/tx_bytes'.format(i), 'r') as tx: 29 | with open('/sys/class/net/{}/statistics/rx_bytes'.format(i), 'r') as rx: 30 | tx = int(tx.read()) * 8 31 | rx = int(rx.read()) * 8 32 | if not (old_tx is None or old_rx is None): 33 | delta_tx = tx - old_tx 34 | delta_rx = rx - old_rx 35 | tx_rate = (delta_tx / t) / 10**6 36 | rx_rate = (delta_rx / t) / 10**6 37 | row = { 38 | 'time': current_time-start_time, 39 | 'tx_rate': tx_rate, 40 | 'rx_rate': rx_rate 41 | } 42 | data.append(row) 43 | old_tx = tx 44 | old_rx = rx 45 | time.sleep(max(current_time + t - time.time(), 0)) 46 | 47 | with open(csv_file, 'w', newline='') as f: 48 | fieldnames = ['time', 'tx_rate', 'rx_rate'] 49 | writer = csv.DictWriter(f, fieldnames=fieldnames) 50 | writer.writeheader() 51 | for row in data: 52 | writer.writerow(row) 53 | 54 | def get_args(): 55 | parser = argparse.ArgumentParser() 56 | 57 | parser.add_argument('-i', metavar='intf', help='interface to monitor', type=str, required=True) 58 | parser.add_argument('-t', metavar='interval', help='interval between data points in seconds', type=float, required=False, default=0.5) 59 | parser.add_argument('-d', metavar='duration', help='monitoring duration in seconds', required=False, type=float, default=60) 60 | parser.add_argument('csv', metavar='OUTFILE', type=str, help='csv dump file') 61 | 62 | return parser.parse_args() 63 | 64 | if __name__ == '__main__': 65 | 66 | args = get_args() 67 | monitor = Monitor(args.csv, 68 | args.i, 69 | t=args.t, 70 | d=args.d) -------------------------------------------------------------------------------- /p4utils/utils/p4runtime_API/README.md: -------------------------------------------------------------------------------- 1 | # P4Runtime Client 2 | This is a pure API and multi-switch version of the [p4runtime-shell](https://github.com/p4lang/p4runtime-shell) repository. 3 | -------------------------------------------------------------------------------- /p4utils/utils/p4runtime_API/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nsg-ethz/p4-utils/83b118bbae530b31cc74e7fa32f9174f7c0a1184/p4utils/utils/p4runtime_API/__init__.py -------------------------------------------------------------------------------- /p4utils/utils/p4runtime_API/bytes_utils.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Barefoot Networks, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | from ipaddr import IPv4Address, IPv6Address, AddressValueError 17 | 18 | from p4utils.utils.p4runtime_API.utils import UserError 19 | 20 | 21 | class UserBadIPv4Error(UserError): 22 | def __init__(self, addr): 23 | self.addr = addr 24 | 25 | def __str__(self): 26 | return "'{}' is not a valid IPv4 address".format(self.addr) 27 | 28 | def _render_traceback_(self): 29 | return [str(self)] 30 | 31 | 32 | class UserBadIPv6Error(UserError): 33 | def __init__(self, addr): 34 | self.addr = addr 35 | 36 | def __str__(self): 37 | return "'{}' is not a valid IPv6 address".format(self.addr) 38 | 39 | def _render_traceback_(self): 40 | return [str(self)] 41 | 42 | 43 | class UserBadMacError(UserError): 44 | def __init__(self, addr): 45 | self.addr = addr 46 | 47 | def __str__(self): 48 | return "'{}' is not a valid MAC address".format(self.addr) 49 | 50 | def _render_traceback_(self): 51 | return [str(self)] 52 | 53 | 54 | class UserBadValueError(UserError): 55 | def __init__(self, info=""): 56 | self.info = info 57 | 58 | def __str__(self): 59 | return self.info 60 | 61 | def _render_traceback_(self): 62 | return [str(self)] 63 | 64 | 65 | def ipv4Addr_to_bytes(addr): 66 | try: 67 | ip = IPv4Address(addr) 68 | except AddressValueError: 69 | raise UserBadIPv4Error(addr) 70 | return ip.packed 71 | 72 | 73 | def ipv6Addr_to_bytes(addr): 74 | try: 75 | ip = IPv6Address(addr) 76 | except AddressValueError: 77 | raise UserBadIPv6Error(addr) 78 | return ip.packed 79 | 80 | 81 | def macAddr_to_bytes(addr): 82 | bytes_ = [int(b, 16) for b in addr.split(':')] 83 | if len(bytes_) != 6: 84 | raise UserBadMacError(addr) 85 | return bytes(bytes_) 86 | 87 | 88 | def parse_value(value_str, bitwidth, base=0): 89 | if bitwidth == 32 and '.' in value_str: 90 | return ipv4Addr_to_bytes(value_str) 91 | elif bitwidth == 48 and ':' in value_str: 92 | return macAddr_to_bytes(value_str) 93 | elif bitwidth == 128 and ':' in value_str: 94 | return ipv6Addr_to_bytes(value_str) 95 | try: 96 | value = int(value_str, base) 97 | except ValueError: 98 | raise UserBadValueError( 99 | "Invalid value '{}': could not cast to integer, try in hex with 0x prefix".format( 100 | value_str)) 101 | nbytes = (bitwidth + 7) // 8 102 | try: 103 | return value.to_bytes(nbytes, byteorder='big') 104 | except OverflowError: 105 | raise UserBadValueError( 106 | "Invalid value '{}': cannot be represented with '{}' bytes".format( 107 | value_str, nbytes)) 108 | -------------------------------------------------------------------------------- /p4utils/utils/p4runtime_API/context.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Barefoot Networks, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | from collections import Counter 17 | import enum 18 | from functools import partialmethod 19 | 20 | 21 | @enum.unique 22 | class P4Type(enum.Enum): 23 | table = 1 24 | action = 2 25 | action_profile = 3 26 | counter = 4 27 | direct_counter = 5 28 | meter = 6 29 | direct_meter = 7 30 | digest = 8 31 | 32 | 33 | P4Type.table.p4info_name = "tables" 34 | P4Type.action.p4info_name = "actions" 35 | P4Type.action_profile.p4info_name = "action_profiles" 36 | P4Type.counter.p4info_name = "counters" 37 | P4Type.direct_counter.p4info_name = "direct_counters" 38 | P4Type.meter.p4info_name = "meters" 39 | P4Type.direct_meter.p4info_name = "direct_meters" 40 | P4Type.digest.p4info_name = "digests" 41 | 42 | 43 | for obj_type in P4Type: 44 | obj_type.pretty_name = obj_type.name.replace('_', ' ') 45 | obj_type.pretty_names = obj_type.pretty_name + 's' 46 | 47 | 48 | @enum.unique 49 | class P4RuntimeEntity(enum.Enum): 50 | table_entry = 1 51 | action_profile_member = 2 52 | action_profile_group = 3 53 | meter_entry = 4 54 | direct_meter_entry = 5 55 | counter_entry = 6 56 | direct_counter_entry = 7 57 | packet_replication_engine_entry = 8 58 | digest_entry = 9 59 | 60 | 61 | class Context: 62 | def __init__(self): 63 | self.p4info = None 64 | 65 | def set_p4info(self, p4info): 66 | self.p4info = p4info 67 | self.p4info_obj_map = {} 68 | self.p4info_obj_map_by_id = {} 69 | self.p4info_objs_by_type = {} 70 | self._import_p4info_names() 71 | 72 | def get_obj(self, obj_type, name): 73 | key = (obj_type, name) 74 | return self.p4info_obj_map.get(key, None) 75 | 76 | def get_obj_id(self, obj_type, name): 77 | obj = self.get_obj(obj_type, name) 78 | if obj is None: 79 | return None 80 | return obj.preamble.id 81 | 82 | def get_param(self, action_name, name): 83 | a = self.get_obj(P4Type.action, action_name) 84 | if a is None: 85 | return None 86 | for p in a.params: 87 | if p.name == name: 88 | return p 89 | 90 | def get_param_len(self, action_name): 91 | a = self.get_obj(P4Type.action, action_name) 92 | if a is None: 93 | return None 94 | return len(a.params) 95 | 96 | def get_mf(self, table_name, name): 97 | t = self.get_obj(P4Type.table, table_name) 98 | if t is None: 99 | return None 100 | for mf in t.match_fields: 101 | if mf.name == name: 102 | return mf 103 | 104 | def get_mf_len(self, table_name): 105 | t = self.get_obj(P4Type.table, table_name) 106 | if t is None: 107 | return None 108 | return len(t.match_fields) 109 | 110 | def get_param_id(self, action_name, name): 111 | p = self.get_param(action_name, name) 112 | return None if p is None else p.id 113 | 114 | def get_mf_id(self, table_name, name): 115 | mf = self.get_mf(table_name, name) 116 | return None if mf is None else mf.id 117 | 118 | def get_param_name(self, action_name, id_): 119 | a = self.get_obj(P4Type.action, action_name) 120 | if a is None: 121 | return None 122 | for p in a.params: 123 | if p.id == id_: 124 | return p.name 125 | 126 | def get_mf_name(self, table_name, id_): 127 | t = self.get_obj(P4Type.table, table_name) 128 | if t is None: 129 | return None 130 | for mf in t.match_fields: 131 | if mf.id == id_: 132 | return mf.name 133 | 134 | def get_objs(self, obj_type): 135 | m = self.p4info_objs_by_type[obj_type] 136 | for name, obj in m.items(): 137 | yield name, obj 138 | 139 | def get_name_from_id(self, id_): 140 | return self.p4info_obj_map_by_id[id_].preamble.name 141 | 142 | def get_obj_by_id(self, id_): 143 | return self.p4info_obj_map_by_id[id_] 144 | 145 | # In order to make the CLI easier to use, we accept any suffix that 146 | # uniquely identifies the object among p4info objects of the same type. 147 | def _import_p4info_names(self): 148 | suffix_count = Counter() 149 | for obj_type in P4Type: 150 | self.p4info_objs_by_type[obj_type] = {} 151 | for obj in getattr(self.p4info, obj_type.p4info_name): 152 | pre = obj.preamble 153 | self.p4info_obj_map_by_id[pre.id] = obj 154 | self.p4info_objs_by_type[obj_type][pre.name] = obj 155 | suffix = None 156 | for s in reversed(pre.name.split(".")): 157 | suffix = s if suffix is None else s + "." + suffix 158 | key = (obj_type, suffix) 159 | self.p4info_obj_map[key] = obj 160 | suffix_count[key] += 1 161 | for key, c in suffix_count.items(): 162 | if c > 1: 163 | del self.p4info_obj_map[key] 164 | 165 | 166 | # Add p4info object and object id "getters" for each object type; these are just 167 | # wrappers around Context.get_obj and Context.get_obj_id. 168 | # For example: get_table(x) and get_table_id(x) respectively call 169 | # get_obj(P4Type.table, x) and get_obj_id(P4Type.table, x) 170 | for obj_type in P4Type: 171 | name = "_".join(["get", obj_type.name]) 172 | setattr(Context, name, partialmethod( 173 | Context.get_obj, obj_type)) 174 | name = "_".join(["get", obj_type.name, "id"]) 175 | setattr(Context, name, partialmethod( 176 | Context.get_obj_id, obj_type)) 177 | 178 | for obj_type in P4Type: 179 | name = "_".join(["get", obj_type.p4info_name]) 180 | setattr(Context, name, partialmethod(Context.get_objs, obj_type)) 181 | -------------------------------------------------------------------------------- /p4utils/utils/p4runtime_API/utils.py: -------------------------------------------------------------------------------- 1 | # See https://stackoverflow.com/a/32997046 2 | def my_partialmethod(func, *args1, **kwargs1): 3 | def method(self, *args2, **kwargs2): 4 | return func(self, *args1, *args2, **kwargs1, **kwargs2) 5 | return method 6 | 7 | 8 | class UserError(Exception): 9 | def __init__(self, info=""): 10 | self.info = info 11 | 12 | def __str__(self): 13 | return self.info 14 | 15 | # TODO(antonin): is this the best way to get a custom traceback? 16 | def _render_traceback_(self): 17 | return [str(self)] 18 | 19 | 20 | class InvalidP4InfoError(Exception): 21 | def __init__(self, info=""): 22 | self.info = info 23 | 24 | def __str__(self): 25 | return "Invalid P4Info message: {}".format(self.info) 26 | 27 | def _render_traceback_(self): 28 | return [str(self)] 29 | -------------------------------------------------------------------------------- /p4utils/utils/traffic_utils.py: -------------------------------------------------------------------------------- 1 | import math 2 | import time 3 | import socket 4 | 5 | 6 | def setSizeToInt(size): 7 | """Converts the sizes string notation to the corresponding integer 8 | (in bytes). Input size can be given with the following magnitudes: B, K, M and G. 9 | """ 10 | if isinstance(size, int): 11 | return size 12 | elif isinstance(size, float): 13 | return int(size) 14 | try: 15 | conversions = {'B': 1, 'K': 1e3, 'M': 1e6, 'G': 1e9} 16 | digits_list = list(range(48, 58)) + [ord(".")] 17 | magnitude = chr( 18 | sum([ord(x) if (ord(x) not in digits_list) else 0 for x in size])) 19 | digit = float(size[0:(size.index(magnitude))]) 20 | magnitude = conversions[magnitude] 21 | return int(magnitude*digit) 22 | except: 23 | print("Conversion Fail") 24 | return 0 25 | 26 | 27 | def send_udp_flow(dst="10.0.0.2", sport=5000, dport=5001, tos=0, rate='10M', duration=0, 28 | packet_size=1400, batch_size=1, **kwargs): 29 | """Udp sending function that keeps a constant rate and logs sent packets to a file. 30 | 31 | Args: 32 | dst (str, optional): destination IP. Defaults to "10.0.0.2". 33 | sport (int, optional): destination port. Defaults to 5000. 34 | dport (int, optional): source port. Defaults to 5001. 35 | tos (int, optional): type of service. Defaults to 0. 36 | rate (str, optional): flow rate. Defaults to '10M'. 37 | duration (int, optional): flow duration. Defaults to 0, i.e. no time limit. 38 | packet_size (int, optional): packet size. Defaults to 1400. 39 | batch_size (int, optional): batch size. Defaults to 1. 40 | """ 41 | 42 | sport = int(sport) 43 | dport = int(dport) 44 | packet_size = int(packet_size) 45 | tos = int(tos) 46 | 47 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 48 | s.setsockopt(socket.SOL_IP, socket.IP_TOS, tos) 49 | s.bind(('', sport)) 50 | 51 | rate = int(setSizeToInt(rate)/8) 52 | totalTime = float(duration) 53 | 54 | # we use 17 to correct a bit the bw 55 | packet = b"A" * int((packet_size - 17)) 56 | seq = 0 57 | 58 | try: 59 | startTime = time.time() 60 | while True: 61 | 62 | # If a finite duration is given 63 | if totalTime > 0: 64 | if time.time() - startTime >= totalTime: 65 | break 66 | 67 | packets_to_send = rate/packet_size 68 | times = math.ceil((float(rate) / (packet_size))/batch_size) 69 | time_step = 1/times 70 | start = time.time() 71 | i = 0 72 | packets_sent = 0 73 | # batches of 1 sec 74 | while packets_sent < packets_to_send: 75 | for _ in range(batch_size): 76 | s.sendto(seq.to_bytes(4, byteorder='big') + 77 | packet, (dst, dport)) 78 | # sequence_numbers.append(seq) 79 | packets_sent += 1 80 | seq += 1 81 | 82 | i += 1 83 | next_send_time = start + (i * time_step) 84 | time.sleep(max(0, next_send_time - time.time())) 85 | # return 86 | time.sleep(max(0, 1-(time.time()-start))) 87 | 88 | finally: 89 | s.close() 90 | 91 | 92 | def recv_udp_flow(dport): 93 | """ 94 | Receiving function. 95 | 96 | Args: 97 | dport (int): port to listen 98 | """ 99 | dport = int(dport) 100 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 101 | s.bind(("", dport)) 102 | try: 103 | while True: 104 | data, address = s.recvfrom(2048) 105 | except: 106 | s.close() 107 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | "Setuptools params" 4 | 5 | from setuptools import setup, find_packages 6 | 7 | VERSION = '0.2' 8 | 9 | modname = distname = 'p4utils' 10 | 11 | def readme(): 12 | 13 | with open('README.md','r') as f: 14 | return f.read() 15 | 16 | setup( 17 | name=distname, 18 | version=VERSION, 19 | description='P4 language and bmv2 model utilities', 20 | author='Edgar Costa Molero', 21 | author_email='cedgar@ethz.ch', 22 | packages=find_packages(), 23 | long_description=readme(), 24 | entry_points={'console_scripts': ['p4run = p4utils.p4run:main']}, 25 | include_package_data = True, 26 | classifiers=[ 27 | "License :: OSI Approved :: BSD License", 28 | "Programming Language :: Python 3", 29 | "Development Status :: 2 - Pre-Alpha", 30 | "Intended Audience :: Developers", 31 | "Topic :: System :: Networking", 32 | ], 33 | keywords='networking p4 mininet', 34 | license='GPLv2', 35 | install_requires=[ 36 | 'googleapis-common-protos >= 1.52', 37 | 'grpcio == 1.44.0', 38 | 'ipaddr', 39 | 'ipaddress', 40 | 'networkx', 41 | 'p4runtime', 42 | 'protobuf == 3.20.3', 43 | 'psutil', 44 | 'scapy >= 2.5.0', 45 | 'setuptools', 46 | ], 47 | extras_require={} 48 | ) 49 | -------------------------------------------------------------------------------- /uninstall.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | uninstall_p4utils() { 4 | pip uninstall p4utils 5 | } 6 | 7 | remove_mx() { 8 | 9 | BINDIR=/usr/bin 10 | MANDIR=/usr/share/man/man1 11 | 12 | rm -f ${BINDIR}/"mxexec" 13 | rm -f ${BINDIR}/"mx" 14 | rm -rf ${MANDIR}/"mxexec.1" 15 | 16 | } 17 | 18 | uninstall_p4utils 19 | remove_mx -------------------------------------------------------------------------------- /utils/mx: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Attach to a MiniNExT host and run a command 4 | # (Extends existing script to provide support for PID namespaces) 5 | # Credit to MiniNext: https://github.com/USC-NSL/miniNExT 6 | 7 | if [ -z $1 ]; then 8 | echo "usage: $0 host cmd [args...]" 9 | exit 1 10 | else 11 | host=$1 12 | fi 13 | 14 | pid=`ps ax | grep "mininet:$host$" | grep bash | grep -v mxexec | awk '{print $1};'` 15 | 16 | if echo $pid | grep -q ' '; then 17 | echo "Error: found multiple mininet:$host processes" 18 | exit 2 19 | fi 20 | 21 | if [ "$pid" == "" ]; then 22 | echo "Could not find Mininet host $host" 23 | exit 3 24 | fi 25 | 26 | if [ -z $2 ]; then 27 | cmd="bash -c 'cd `pwd`; bash'" 28 | else 29 | shift 30 | cmd=$* 31 | cmd="bash -c 'cd `pwd`; $cmd'" 32 | fi 33 | 34 | cgroup=/sys/fs/cgroup/cpu/$host 35 | if [ -d "$cgroup" ]; then 36 | cg="-g $host" 37 | fi 38 | 39 | # Check whether host should be running in a chroot dir 40 | rootdir="/var/run/mn/$host/root" 41 | if [ -d $rootdir -a -x $rootdir/bin/bash ]; then 42 | cmd="'cd `pwd`; exec $cmd'" 43 | cmd="chroot $rootdir /bin/bash -c $cmd" 44 | fi 45 | 46 | cmd="exec sudo mxexec -a $pid -b $pid -k $pid $cg $cmd" 47 | eval $cmd -------------------------------------------------------------------------------- /vm/README.md: -------------------------------------------------------------------------------- 1 | # P4-Utils Virtual Machine 2 | 3 | > **Important** 4 | > In this section we explain how to build a VM with all the needed tools from scratch. 5 | > However, since some part of the installation depends on Packer, and this tool sometimes gives problems 6 | > we recommend to simply use the install script provided [here](../install-tools) and run it in a clean Ubuntu machine or VM. 7 | 8 | Although P4-Utils can be installed directly on your system, running P4-Utils in a completely separated 9 | environment can be beneficial: in this way, installation and execution errors, that may arise, will not 10 | affect the whole system. For this reason, **we recommend using one of the VM methods that we provide**. 11 | In addition, since P4-Utils is only available on Linux, other OS users can run it in a Linux virtual machine. 12 | 13 | We provide two different solutions for the P4-Utils VM and both are supported by a wide range of 14 | operating systems: 15 | 16 | - [VirtualBox](https://www.virtualbox.org/) 17 | - [QEMU](https://www.qemu.org/) 18 | 19 | You can choose to download and use one of our 20 | [preconfigured VMs](https://nsg-ethz.github.io/p4-utils/installation.html#use-our-preconfigured-vm) 21 | or to [build it yourself](#build-your-own-vm). 22 | 23 | > **Important** 24 | > Whether you are building your own VM or you are using the preconfigured images, you still 25 | > need to install one of the above virtualizer according to your VM choice. 26 | 27 | ## Build your own VM 28 | 29 | To get started, you need to install the required software: 30 | 31 | - [VirtualBox](https://www.virtualbox.org/) or [QEMU](https://www.qemu.org/) 32 | - [Packer](https://www.packer.io/) 33 | 34 | > **Note** 35 | > Packer is a handy framework designed to automatically build custom VM images. 36 | 37 | Clone the P4-Utils repository: 38 | 39 | ``` 40 | git clone https://github.com/nsg-ethz/p4-utils 41 | ``` 42 | 43 | Go to the Packer configurations folder: 44 | 45 | ``` 46 | cd p4-utils/vm 47 | ``` 48 | 49 | If you want to build the *VirtualBox VM*, execute: 50 | 51 | ``` 52 | ./build-virtualbox.sh [--cpus 4] [--disk_size 25000] [--memory 4000] [--vm_name p4] [--username p4] [--password p4] 53 | ``` 54 | 55 | On the other hand, if you prefer the *QEMU VM*, run: 56 | 57 | ``` 58 | ./build-qemu.sh [--cpus 4] [--disk_size 25000] [--memory 4000] [--vm_name p4] [--username p4] [--password p4] 59 | ``` 60 | 61 | According to the commands shown above, some parameters can be passed to the building scripts 62 | to customize the VM: 63 | 64 | - ``--cpus`` specifies the **number of cores** to use, 65 | - ``--disk_size`` is the **size of the disk** reserved by the VM in MBytes, 66 | - ``--memory`` is the amount of **RAM** to assign to the VM in MBytes, 67 | - ``--vm_name`` is the **name of the VM**, 68 | - ``--username`` is the **login username**, 69 | - ``--password`` is the **login password**. 70 | 71 | > **Attention** 72 | > The default VMs configuration parameters are shown above between square brakets. If you do not 73 | > specify anything, they will be used to build your VM. However, please pass to the scripts the 74 | > parameters that best fit your needs. 75 | 76 | The building process will generate the following files: 77 | 78 | - If you chose the QEMU VM, in `p4-utils/vm/output-ubuntu18044_qemu` you will find 79 | a `.qcow2` file to use to set up your VM. 80 | - If you chose the VirtualBox VM, in `p4-utils/vm/output-ubuntu18044_vb` you will 81 | find an `.ova` file to import in the VirtualBox VM manager. -------------------------------------------------------------------------------- /vm/build-qemu.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | SCRIPT_DIR=$(dirname "$(realpath $0)") 3 | 4 | OPTS="" 5 | while [[ $# -gt 0 ]]; do 6 | OPTS="$OPTS -var ${1:2}=$2 " 7 | shift # past argument 8 | shift # past value 9 | done 10 | 11 | packer init "$SCRIPT_DIR/qemu.pkr.hcl" 12 | packer validate "$SCRIPT_DIR/qemu.pkr.hcl" 13 | packer build $OPTS "$SCRIPT_DIR/qemu.pkr.hcl" 14 | -------------------------------------------------------------------------------- /vm/build-qemu20.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | SCRIPT_DIR=$(dirname "$(realpath $0)") 3 | 4 | OPTS="" 5 | while [[ $# -gt 0 ]]; do 6 | OPTS="$OPTS -var ${1:2}=$2 " 7 | shift # past argument 8 | shift # past value 9 | done 10 | 11 | packer init "$SCRIPT_DIR/qemu20.pkr.hcl" 12 | packer validate "$SCRIPT_DIR/qemu20.pkr.hcl" 13 | packer build -debug $OPTS "$SCRIPT_DIR/qemu20.pkr.hcl" 14 | -------------------------------------------------------------------------------- /vm/build-virtualbox.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | SCRIPT_DIR=$(dirname "$(realpath $0)") 3 | 4 | OPTS="" 5 | while [[ $# -gt 0 ]]; do 6 | OPTS="$OPTS -var ${1:2}=$2 " 7 | shift # past argument 8 | shift # past value 9 | done 10 | 11 | packer init "$SCRIPT_DIR/virtualbox.pkr.hcl" 12 | packer validate "$SCRIPT_DIR/virtualbox.pkr.hcl" 13 | packer build $OPTS "$SCRIPT_DIR/virtualbox.pkr.hcl" 14 | -------------------------------------------------------------------------------- /vm/http/preseed.cfg: -------------------------------------------------------------------------------- 1 | # Preseeding only locale sets language, country and locale. 2 | d-i debian-installer/locale string en_US 3 | 4 | # Keyboard selection. 5 | d-i console-setup/ask_detect boolean false 6 | d-i keyboard-configuration/xkb-keymap select us 7 | 8 | choose-mirror-bin mirror/http/proxy string 9 | 10 | ### Clock and time zone setup 11 | d-i clock-setup/utc boolean true 12 | d-i time/zone string UTC 13 | 14 | # Avoid that last message about the install being complete. 15 | d-i finish-install/reboot_in_progress note 16 | 17 | # This is fairly safe to set, it makes grub install automatically to the MBR 18 | # if no other operating system is detected on the machine. 19 | d-i grub-installer/only_debian boolean true 20 | 21 | # This one makes grub-installer install to the MBR if it also finds some other 22 | # OS, which is less safe as it might not be able to boot that other OS. 23 | d-i grub-installer/with_other_os boolean true 24 | 25 | ### Mirror settings 26 | # If you select ftp, the mirror/country string does not need to be set. 27 | d-i mirror/country string manual 28 | d-i mirror/http/directory string /ubuntu/ 29 | d-i mirror/http/hostname string archive.ubuntu.com 30 | d-i mirror/http/proxy string 31 | 32 | ### Partitioning 33 | d-i partman-auto/method string lvm 34 | 35 | # This makes partman automatically partition without confirmation. 36 | d-i partman-lvm/confirm boolean true 37 | d-i partman-lvm/confirm_nooverwrite boolean true 38 | d-i partman-lvm/device_remove_lvm boolean true 39 | d-i partman/choose_partition select finish 40 | d-i partman/confirm boolean true 41 | d-i partman/confirm_nooverwrite boolean true 42 | d-i partman/confirm_write_new_label boolean true 43 | 44 | ### Account setup 45 | d-i passwd/user-fullname string p4 46 | d-i passwd/user-uid string 1000 47 | d-i passwd/user-password password p4 48 | d-i passwd/user-password-again password p4 49 | d-i passwd/username string p4 50 | 51 | # The installer will warn about weak passwords. If you are sure you know 52 | # what you're doing and want to override it, uncomment this. 53 | d-i user-setup/allow-password-weak boolean true 54 | d-i user-setup/encrypt-home boolean false 55 | 56 | ### Package selection 57 | tasksel tasksel/first standard 58 | d-i pkgsel/include string openssh-server build-essential 59 | d-i pkgsel/install-language-support boolean false 60 | 61 | # disable automatic package updates 62 | d-i pkgsel/update-policy select none 63 | d-i pkgsel/upgrade select full-upgrade 64 | 65 | # make sure ssh is started 66 | d-i preseed/late_command string in-target systemctl enable ssh -------------------------------------------------------------------------------- /vm/qemu.pkr.hcl: -------------------------------------------------------------------------------- 1 | variable "memory" { 2 | default = 4096 3 | } 4 | 5 | variable "cpus" { 6 | default = 4 7 | } 8 | 9 | variable "disk_size" { 10 | default = 25000 11 | } 12 | 13 | variable "vm_name" { 14 | default = "p4" 15 | } 16 | 17 | variable "username" { 18 | default = "p4" 19 | } 20 | 21 | variable "password" { 22 | default = "p4" 23 | } 24 | 25 | variable "iso_url" { 26 | default = "http://old-releases.ubuntu.com/releases/18.04.4/ubuntu-18.04.4-server-amd64.iso" 27 | } 28 | 29 | variable "iso_checksum" { 30 | default = "sha256:e2ecdace33c939527cbc9e8d23576381c493b071107207d2040af72595f8990b" 31 | } 32 | 33 | variable "target" { 34 | default = "sources.qemu.ubuntu18044_qemu" 35 | } 36 | 37 | packer { 38 | required_plugins { 39 | qemu = { 40 | version = ">= 0.0.1" 41 | source = "github.com/hashicorp/qemu" 42 | } 43 | } 44 | } 45 | 46 | source "qemu" "ubuntu18044_qemu" { 47 | vm_name = "${var.vm_name}.qcow2" 48 | headless = true 49 | iso_url = var.iso_url 50 | iso_checksum = var.iso_checksum 51 | http_directory = "http" 52 | cpus = var.cpus 53 | memory = var.memory 54 | disk_size = "${var.disk_size}" 55 | accelerator = "kvm" 56 | ssh_username = var.username 57 | ssh_password = var.password 58 | ssh_timeout = "1h" 59 | shutdown_command = "echo ${var.password} | sudo -S shutdown -P now" 60 | format = "qcow2" 61 | boot_wait = "10s" 62 | boot_command = [ 63 | "", 64 | "", 65 | "", 66 | "/install/vmlinuz", 67 | " initrd=/install/initrd.gz", 68 | " auto-install/enable=true", 69 | " debconf/priority=critical", 70 | " preseed/url=http://{{ .HTTPIP }}:{{ .HTTPPort }}/preseed.cfg", 71 | " hostname=${var.vm_name}", 72 | " -- ", 73 | "" 74 | ] 75 | } 76 | 77 | build { 78 | sources = [ 79 | "sources.qemu.ubuntu18044_qemu" 80 | ] 81 | provisioner "shell" { 82 | inline = [ 83 | "echo ${var.password} | sudo -S bash -c \"echo '${var.username} ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/99_vm\"", 84 | "echo ${var.password} | sudo -S sudo chmod 440 /etc/sudoers.d/99_vm", 85 | "sudo bash -c 'cat << EOF > /etc/netplan/01-netcfg.yaml", 86 | "network:", 87 | " version: 2", 88 | " renderer: networkd", 89 | " ethernets:", 90 | " id0:", 91 | " match:", 92 | " name: e*", 93 | " dhcp4: yes", 94 | "EOF'", 95 | "sudo apt-get install -y git curl", 96 | "curl -sSL https://raw.githubusercontent.com/nsg-ethz/p4-utils/master/install-tools/install-p4-dev.sh | bash" 97 | ] 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /vm/qemu20.pkr.hcl: -------------------------------------------------------------------------------- 1 | variable "memory" { 2 | default = 4096 3 | } 4 | 5 | variable "cpus" { 6 | default = 4 7 | } 8 | 9 | variable "disk_size" { 10 | default = 25000 11 | } 12 | 13 | variable "vm_name" { 14 | default = "p4" 15 | } 16 | 17 | variable "username" { 18 | default = "p4" 19 | } 20 | 21 | variable "password" { 22 | default = "p4" 23 | } 24 | 25 | variable "iso_url" { 26 | default = "https://releases.ubuntu.com/20.04.6/ubuntu-20.04.6-live-server-amd64.iso" 27 | } 28 | 29 | variable "iso_checksum" { 30 | default = "sha256:b8f31413336b9393ad5d8ef0282717b2ab19f007df2e9ed5196c13d8f9153c8b" 31 | } 32 | 33 | variable "target" { 34 | default = "sources.qemu.ubuntu20046_qemu" 35 | } 36 | 37 | packer { 38 | required_plugins { 39 | qemu = { 40 | version = ">= 0.0.1" 41 | source = "github.com/hashicorp/qemu" 42 | } 43 | } 44 | } 45 | 46 | source "qemu" "ubuntu20046_qemu" { 47 | vm_name = "${var.vm_name}.qcow2" 48 | headless = true 49 | iso_url = var.iso_url 50 | iso_checksum = var.iso_checksum 51 | http_directory = "http" 52 | cpus = var.cpus 53 | memory = var.memory 54 | disk_size = "${var.disk_size}" 55 | accelerator = "kvm" 56 | ssh_username = var.username 57 | ssh_password = var.password 58 | ssh_timeout = "1h" 59 | shutdown_command = "echo ${var.password} | sudo -S shutdown -P now" 60 | format = "qcow2" 61 | boot_wait = "20s" 62 | boot_command = [ 63 | "", 64 | "", 65 | "", 66 | "/install/vmlinuz", 67 | " initrd=/install/initrd.gz", 68 | " auto-install/enable=true", 69 | " debconf/priority=critical", 70 | " preseed/url=http://{{ .HTTPIP }}:{{ .HTTPPort }}/preseed.cfg", 71 | " hostname=${var.vm_name}", 72 | " -- ", 73 | "" 74 | ] 75 | } 76 | 77 | build { 78 | sources = [ 79 | "sources.qemu.ubuntu20046_qemu" 80 | ] 81 | provisioner "shell" { 82 | inline = [ 83 | "echo ${var.password} | sudo -S bash -c \"echo '${var.username} ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/99_vm\"", 84 | "echo ${var.password} | sudo -S sudo chmod 440 /etc/sudoers.d/99_vm", 85 | "sudo bash -c 'cat << EOF > /etc/netplan/01-netcfg.yaml", 86 | "network:", 87 | " version: 2", 88 | " renderer: networkd", 89 | " ethernets:", 90 | " id0:", 91 | " match:", 92 | " name: e*", 93 | " dhcp4: yes", 94 | "EOF'", 95 | "sudo apt-get install -y git curl", 96 | "curl -sSL https://raw.githubusercontent.com/nsg-ethz/p4-utils/master/install-tools/install-p4-dev-ubuntu20.sh | bash" 97 | ] 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /vm/virtualbox.pkr.hcl: -------------------------------------------------------------------------------- 1 | variable "memory" { 2 | default = 4096 3 | } 4 | 5 | variable "cpus" { 6 | default = 4 7 | } 8 | 9 | variable "disk_size" { 10 | default = 25000 11 | } 12 | 13 | variable "vm_name" { 14 | default = "p4" 15 | } 16 | 17 | variable "username" { 18 | default = "p4" 19 | } 20 | 21 | variable "password" { 22 | default = "p4" 23 | } 24 | 25 | variable "iso_url" { 26 | default = "http://old-releases.ubuntu.com/releases/18.04.4/ubuntu-18.04.4-server-amd64.iso" 27 | } 28 | 29 | variable "iso_checksum" { 30 | default = "sha256:e2ecdace33c939527cbc9e8d23576381c493b071107207d2040af72595f8990b" 31 | } 32 | 33 | variable "target" { 34 | default = "sources.qemu.ubuntu18044_qemu" 35 | } 36 | 37 | packer { 38 | required_plugins { 39 | virtualbox = { 40 | version = ">= 0.0.1" 41 | source = "github.com/hashicorp/virtualbox" 42 | } 43 | qemu = { 44 | version = ">= 0.0.1" 45 | source = "github.com/hashicorp/qemu" 46 | } 47 | } 48 | } 49 | 50 | source "virtualbox-iso" "ubuntu18044_vb" { 51 | vm_name = var.vm_name 52 | headless = true 53 | guest_os_type = "Ubuntu_64" 54 | iso_url = var.iso_url 55 | iso_checksum = var.iso_checksum 56 | http_directory = "http" 57 | cpus = var.cpus 58 | memory = var.memory 59 | disk_size = var.disk_size 60 | ssh_username = var.username 61 | ssh_password = var.password 62 | ssh_timeout = "1h" 63 | shutdown_command = "echo ${var.password} | sudo -S shutdown -P now" 64 | format = "ova" 65 | boot_wait = "10s" 66 | boot_command = [ 67 | "", 68 | "", 69 | "", 70 | "/install/vmlinuz", 71 | " initrd=/install/initrd.gz", 72 | " auto-install/enable=true", 73 | " debconf/priority=critical", 74 | " preseed/url=http://{{ .HTTPIP }}:{{ .HTTPPort }}/preseed.cfg", 75 | " hostname={{ .Name }}", 76 | " -- ", 77 | "" 78 | ] 79 | } 80 | 81 | build { 82 | sources = [ 83 | "sources.virtualbox-iso.ubuntu18044_vb" 84 | ] 85 | provisioner "shell" { 86 | inline = [ 87 | "echo ${var.password} | sudo -S bash -c \"echo '${var.username} ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/99_advnet\"", 88 | "echo ${var.password} | sudo -S sudo chmod 440 /etc/sudoers.d/99_advnet", 89 | "sudo apt-get install -y git curl", 90 | "curl -sSL https://raw.githubusercontent.com/nsg-ethz/p4-utils/master/install-tools/install-p4-dev.sh | bash" 91 | ] 92 | } 93 | } --------------------------------------------------------------------------------