diff --git a/README.md b/README.md index 5924e1dc2..4d44bdac1 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,10 @@ you get started with P4 programming, organized into several modules: * [Calculator](./exercises/other/calc) * [Load Balancing](./exercises/load_balance) +5. Stateful Packet Processing +* [Firewall](./exercises/firewall) +* [Link Monitoring](./exercises/link_monitor) + ## Presentation The slides are available [online](http://bit.ly/p4d2-2018-spring) and @@ -31,9 +35,10 @@ which contains various examples that you can refer to. ## Obtaining required software -If you are starting this tutorial at the Spring 2018 P4 Developer Day, +If you are starting this tutorial at one of the proctored tutorial events, then we've already provided you with a virtual machine that has all of -the required software installed. +the required software installed. Ask an instructor for a USB stick with +the VM image. Otherwise, to complete the exercises, you will need to either build a virtual machine or install several dependencies. @@ -47,11 +52,11 @@ To build the virtual machine: - When the machine reboots, you should have a graphical desktop machine with the required software pre-installed. -*Note: Before running the `vagrant up` command, make sure you have enabled virtualization in your environment; otherwise you may get a "VT-x is disabled in the BIOS for both all CPU modes" error. Check [this](https://stackoverflow.com/questions/33304393/vt-x-is-disabled-in-the-bios-for-both-all-cpu-modes-verr-vmx-msr-all-vmx-disabl) for enabling it in virtualbox and/or BIOS for different system configurations. +*Note*: Before running the `vagrant up` command, make sure you have enabled virtualization in your environment; otherwise you may get a "VT-x is disabled in the BIOS for both all CPU modes" error. Check [this](https://stackoverflow.com/questions/33304393/vt-x-is-disabled-in-the-bios-for-both-all-cpu-modes-verr-vmx-msr-all-vmx-disabl) for enabling it in virtualbox and/or BIOS for different system configurations. -You will need the script to execute to completion before you can see the `p4` login on your virtual machine's GUI. In some cases, the `vagrant up` command brings up only the default `vagrant` login with the password `vagrant`. Dependencies may or may not have been installed for you to proceed with running P4 programs. Please refer the existing issues to help fix your problem or create a new one if your specific problem isn't addressed there.* +You will need the script to execute to completion before you can see the `p4` login on your virtual machine's GUI. In some cases, the `vagrant up` command brings up only the default `vagrant` login with the password `vagrant`. Dependencies may or may not have been installed for you to proceed with running P4 programs. Please refer the [existing issues](https://github.com/p4lang/tutorials/issues) to help fix your problem or create a new one if your specific problem isn't addressed there. -To install dependencies by hand, please reference the [vm](../vm) installation scripts. +To install dependencies by hand, please reference the [vm](./vm) installation scripts. They contain the dependencies, versions, and installation procedure. You should be able to run them directly on an Ubuntu 16.04 machine: - `sudo ./root-bootstrap.sh` diff --git a/exercises/basic/Makefile b/exercises/basic/Makefile index cfbeb1652..476f2659c 100644 --- a/exercises/basic/Makefile +++ b/exercises/basic/Makefile @@ -1,5 +1,4 @@ BMV2_SWITCH_EXE = simple_switch_grpc -NO_P4 = true -P4C_ARGS = --p4runtime-files $(basename $@).p4.p4info.txt +TOPO = pod-topo/topology.json include ../../utils/Makefile diff --git a/exercises/basic/README.md b/exercises/basic/README.md index 77fefc933..dd077cc22 100644 --- a/exercises/basic/README.md +++ b/exercises/basic/README.md @@ -17,6 +17,17 @@ MAC address and output port for the next hop. We have already defined the control plane rules, so you only need to implement the data plane logic of your P4 program. +We will use the following topology for this exercise. It is a single +pod of a fat-tree topology and henceforth referred to as pod-topo: +![pod-topo](./pod-topo/pod-topo.png) + +Our P4 program will be written for the V1Model architecture implemented +on P4.org's bmv2 software switch. The architecture file for the V1Model +can be found at: /usr/local/share/p4c/p4include/v1model.p4. This file +desribes the interfaces of the P4 programmable elements in the architecture, +the supported externs, as well as the architecture's standard metadata +fields. We encourage you to take a look at it. + > **Spoiler alert:** There is a reference solution in the `solution` > sub-directory. Feel free to compare your implementation to the > reference. @@ -36,27 +47,18 @@ up a switch in Mininet to test its behavior. ``` This will: * compile `basic.p4`, and - * start a Mininet instance with three switches (`s1`, `s2`, `s3`) - configured in a triangle, each connected to one host (`h1`, `h2`, - and `h3`). - * The hosts are assigned IPs of `10.0.1.1`, `10.0.2.2`, and `10.0.3.3`. + * start the pod-topo in Mininet and configure all switches with + the appropriate P4 program + table entries, and + * configure all hosts with the commands listed in + [pod-topo/topology.json](./pod-topo/topology.json) -2. You should now see a Mininet command prompt. Open two terminals -for `h1` and `h2`, respectively: - ```bash - mininet> xterm h1 h2 - ``` -3. Each host includes a small Python-based messaging client and -server. In `h2`'s xterm, start the server: +2. You should now see a Mininet command prompt. Try to ping between + hosts in the topology: ```bash - ./receive.py + mininet> h1 ping h2 + mininet> pingall ``` -4. In `h1`'s xterm, send a message to `h2`: - ```bash - ./send.py 10.0.2.2 "P4 is cool" - ``` - The message will not be received. -5. Type `exit` to leave each xterm and the Mininet command line. +3. Type `exit` to leave each xterm and the Mininet command line. Then, to stop mininet: ```bash make stop @@ -66,7 +68,7 @@ server. In `h2`'s xterm, start the server: make clean ``` -The message was not received because each switch is programmed +The ping failed because each switch is programmed according to `basic.p4`, which drops all packets on arrival. Your job is to extend this file so it forwards packets. @@ -77,7 +79,7 @@ within each table are inserted by the control plane. When a rule matches a packet, its action is invoked with parameters supplied by the control plane as part of the rule. -In this exercise, we have already implemented the the control plane +In this exercise, we have already implemented the control plane logic for you. As part of bringing up the Mininet instance, the `make run` command will install packet-processing rules in the tables of each switch. These are defined in the `sX-runtime.json` files, where @@ -86,7 +88,7 @@ each switch. These are defined in the `sX-runtime.json` files, where **Important:** We use P4Runtime to install the control plane rules. The content of files `sX-runtime.json` refer to specific names of tables, keys, and actions, as defined in the P4Info file produced by the compiler (look for the -file `build/basic.p4info` after executing `make run`). Any changes in the P4 +file `build/basic.p4.p4info.txt` after executing `make run`). Any changes in the P4 program that add or rename tables, keys, or actions will need to be reflected in these `sX-runtime.json` files. @@ -120,20 +122,22 @@ A complete `basic.p4` will contain the following components: ## Step 3: Run your solution -Follow the instructions from Step 1. This time, your message from -`h1` should be delivered to `h2`. +Follow the instructions from Step 1. This time, you should be able to +sucessfully ping between any two hosts in the topology. ### Food for thought -The "test suite" for your solution---sending a message from `h1` to -`h2`---is not very robust. What else should you test to be confident -of your implementation? +The "test suite" for your solution---sending pings between hosts in the +topology---is not very robust. What else should you test to be confident +that you implementation is correct? > Although the Python `scapy` library is outside the scope of this tutorial, > it can be used to generate packets for testing. The `send.py` file shows how > to use it. Other questions to consider: + - How would you enhance your program to respond to ARP requests? + - How would you enhance your program to support traceroute? - How would you enhance your program to support next hops? - Is this program enough to replace a router? What's missing? @@ -152,7 +156,7 @@ messages to fix your `basic.p4` implementation. 3. `basic.p4` might compile, and the control plane rules might be installed, but the switch might not process packets in the desired -way. The `/tmp/p4s..log` files contain detailed logs +way. The `logs/sX.log` files contain detailed logs that describing how each switch processes each packet. The output is detailed and can help pinpoint logic errors in your implementation. @@ -166,9 +170,3 @@ these instances: make stop ``` -## Next Steps - -Congratulations, your implementation works! In the next exercise we -will build on top of this and add support for a basic tunneling -protocol: [basic_tunnel](../basic_tunnel)! - diff --git a/exercises/basic/pod-topo/pod-topo.png b/exercises/basic/pod-topo/pod-topo.png new file mode 100644 index 000000000..d5b17b2c5 Binary files /dev/null and b/exercises/basic/pod-topo/pod-topo.png differ diff --git a/exercises/basic/pod-topo/s1-runtime.json b/exercises/basic/pod-topo/s1-runtime.json new file mode 100644 index 000000000..834df41e3 --- /dev/null +++ b/exercises/basic/pod-topo/s1-runtime.json @@ -0,0 +1,57 @@ +{ + "target": "bmv2", + "p4info": "build/basic.p4.p4info.txt", + "bmv2_json": "build/basic.json", + "table_entries": [ + { + "table": "MyIngress.ipv4_lpm", + "default_action": true, + "action_name": "MyIngress.drop", + "action_params": { } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.1.1", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:01:11", + "port": 1 + } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.2.2", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:02:22", + "port": 2 + } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.3.3", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:03:00", + "port": 3 + } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.4.4", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:04:00", + "port": 4 + } + } + ] +} diff --git a/exercises/basic/pod-topo/s2-runtime.json b/exercises/basic/pod-topo/s2-runtime.json new file mode 100644 index 000000000..501dff20c --- /dev/null +++ b/exercises/basic/pod-topo/s2-runtime.json @@ -0,0 +1,57 @@ +{ + "target": "bmv2", + "p4info": "build/basic.p4.p4info.txt", + "bmv2_json": "build/basic.json", + "table_entries": [ + { + "table": "MyIngress.ipv4_lpm", + "default_action": true, + "action_name": "MyIngress.drop", + "action_params": { } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.1.1", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:03:00", + "port": 4 + } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.2.2", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:04:00", + "port": 3 + } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.3.3", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:03:33", + "port": 1 + } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.4.4", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:04:44", + "port": 2 + } + } + ] +} diff --git a/exercises/basic/pod-topo/s3-runtime.json b/exercises/basic/pod-topo/s3-runtime.json new file mode 100644 index 000000000..8cbc646b4 --- /dev/null +++ b/exercises/basic/pod-topo/s3-runtime.json @@ -0,0 +1,57 @@ +{ + "target": "bmv2", + "p4info": "build/basic.p4.p4info.txt", + "bmv2_json": "build/basic.json", + "table_entries": [ + { + "table": "MyIngress.ipv4_lpm", + "default_action": true, + "action_name": "MyIngress.drop", + "action_params": { } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.1.1", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:01:00", + "port": 1 + } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.2.2", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:01:00", + "port": 1 + } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.3.3", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:02:00", + "port": 2 + } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.4.4", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:02:00", + "port": 2 + } + } + ] +} diff --git a/exercises/basic/pod-topo/s4-runtime.json b/exercises/basic/pod-topo/s4-runtime.json new file mode 100644 index 000000000..fbdad6836 --- /dev/null +++ b/exercises/basic/pod-topo/s4-runtime.json @@ -0,0 +1,57 @@ +{ + "target": "bmv2", + "p4info": "build/basic.p4.p4info.txt", + "bmv2_json": "build/basic.json", + "table_entries": [ + { + "table": "MyIngress.ipv4_lpm", + "default_action": true, + "action_name": "MyIngress.drop", + "action_params": { } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.1.1", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:01:00", + "port": 2 + } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.2.2", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:01:00", + "port": 2 + } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.3.3", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:02:00", + "port": 1 + } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.4.4", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:02:00", + "port": 1 + } + } + ] +} diff --git a/exercises/basic/pod-topo/topology.json b/exercises/basic/pod-topo/topology.json new file mode 100644 index 000000000..a963a87ad --- /dev/null +++ b/exercises/basic/pod-topo/topology.json @@ -0,0 +1,26 @@ +{ + "hosts": { + "h1": {"ip": "10.0.1.1/24", "mac": "08:00:00:00:01:11", + "commands":["route add default gw 10.0.1.10 dev eth0", + "arp -i eth0 -s 10.0.1.10 08:00:00:00:01:00"]}, + "h2": {"ip": "10.0.2.2/24", "mac": "08:00:00:00:02:22", + "commands":["route add default gw 10.0.2.20 dev eth0", + "arp -i eth0 -s 10.0.2.20 08:00:00:00:02:00"]}, + "h3": {"ip": "10.0.3.3/24", "mac": "08:00:00:00:03:33", + "commands":["route add default gw 10.0.3.30 dev eth0", + "arp -i eth0 -s 10.0.3.30 08:00:00:00:03:00"]}, + "h4": {"ip": "10.0.4.4/24", "mac": "08:00:00:00:04:44", + "commands":["route add default gw 10.0.4.40 dev eth0", + "arp -i eth0 -s 10.0.4.40 08:00:00:00:04:00"]} + }, + "switches": { + "s1": { "runtime_json" : "pod-topo/s1-runtime.json" }, + "s2": { "runtime_json" : "pod-topo/s2-runtime.json" }, + "s3": { "runtime_json" : "pod-topo/s3-runtime.json" }, + "s4": { "runtime_json" : "pod-topo/s4-runtime.json" } + }, + "links": [ + ["h1", "s1-p1"], ["h2", "s1-p2"], ["s1-p3", "s3-p1"], ["s1-p4", "s4-p2"], + ["h3", "s2-p1"], ["h4", "s2-p2"], ["s2-p3", "s4-p1"], ["s2-p4", "s3-p2"] + ] +} diff --git a/exercises/basic/topology.json b/exercises/basic/topology.json deleted file mode 100644 index e43e3c0c4..000000000 --- a/exercises/basic/topology.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "hosts": [ - "h1", - "h2", - "h3" - ], - "switches": { - "s1": { "runtime_json" : "s1-runtime.json" }, - "s2": { "runtime_json" : "s2-runtime.json" }, - "s3": { "runtime_json" : "s3-runtime.json" } - }, - "links": [ - ["h1", "s1"], ["s1", "s2"], ["s1", "s3"], - ["s3", "s2"], ["s2", "h2"], ["s3", "h3"] - ] -} diff --git a/exercises/basic/s1-runtime.json b/exercises/basic/triangle-topo/s1-runtime.json similarity index 88% rename from exercises/basic/s1-runtime.json rename to exercises/basic/triangle-topo/s1-runtime.json index 7500c8005..22185f236 100644 --- a/exercises/basic/s1-runtime.json +++ b/exercises/basic/triangle-topo/s1-runtime.json @@ -16,7 +16,7 @@ }, "action_name": "MyIngress.ipv4_forward", "action_params": { - "dstAddr": "00:00:00:00:01:01", + "dstAddr": "08:00:00:00:01:11", "port": 1 } }, @@ -27,7 +27,7 @@ }, "action_name": "MyIngress.ipv4_forward", "action_params": { - "dstAddr": "00:00:00:02:02:00", + "dstAddr": "08:00:00:00:02:00", "port": 2 } }, @@ -38,7 +38,7 @@ }, "action_name": "MyIngress.ipv4_forward", "action_params": { - "dstAddr": "00:00:00:03:03:00", + "dstAddr": "08:00:00:00:03:00", "port": 3 } } diff --git a/exercises/basic/s2-runtime.json b/exercises/basic/triangle-topo/s2-runtime.json similarity index 88% rename from exercises/basic/s2-runtime.json rename to exercises/basic/triangle-topo/s2-runtime.json index a8a4e9b18..e056a9be1 100644 --- a/exercises/basic/s2-runtime.json +++ b/exercises/basic/triangle-topo/s2-runtime.json @@ -16,7 +16,7 @@ }, "action_name": "MyIngress.ipv4_forward", "action_params": { - "dstAddr": "00:00:00:01:02:00", + "dstAddr": "08:00:00:00:01:00", "port": 2 } }, @@ -27,7 +27,7 @@ }, "action_name": "MyIngress.ipv4_forward", "action_params": { - "dstAddr": "00:00:00:00:02:02", + "dstAddr": "08:00:00:00:02:22", "port": 1 } }, @@ -38,7 +38,7 @@ }, "action_name": "MyIngress.ipv4_forward", "action_params": { - "dstAddr": "00:00:00:03:03:00", + "dstAddr": "08:00:00:00:03:00", "port": 3 } } diff --git a/exercises/basic/s3-runtime.json b/exercises/basic/triangle-topo/s3-runtime.json similarity index 88% rename from exercises/basic/s3-runtime.json rename to exercises/basic/triangle-topo/s3-runtime.json index 0d7f0bdd5..1714a072f 100644 --- a/exercises/basic/s3-runtime.json +++ b/exercises/basic/triangle-topo/s3-runtime.json @@ -16,7 +16,7 @@ }, "action_name": "MyIngress.ipv4_forward", "action_params": { - "dstAddr": "00:00:00:01:03:00", + "dstAddr": "08:00:00:00:01:00", "port": 2 } }, @@ -27,7 +27,7 @@ }, "action_name": "MyIngress.ipv4_forward", "action_params": { - "dstAddr": "00:00:00:02:03:00", + "dstAddr": "08:00:00:00:02:00", "port": 3 } }, @@ -38,7 +38,7 @@ }, "action_name": "MyIngress.ipv4_forward", "action_params": { - "dstAddr": "00:00:00:00:03:03", + "dstAddr": "08:00:00:00:03:33", "port": 1 } } diff --git a/exercises/basic/triangle-topo/topology.json b/exercises/basic/triangle-topo/topology.json new file mode 100644 index 000000000..edaf991d3 --- /dev/null +++ b/exercises/basic/triangle-topo/topology.json @@ -0,0 +1,22 @@ +{ + "hosts": { + "h1": {"ip": "10.0.1.1/24", "mac": "08:00:00:00:01:11", + "commands":["route add default gw 10.0.1.10 dev eth0", + "arp -i eth0 -s 10.0.1.10 08:00:00:00:01:00"]}, + "h2": {"ip": "10.0.2.2/24", "mac": "08:00:00:00:02:22", + "commands":["route add default gw 10.0.2.20 dev eth0", + "arp -i eth0 -s 10.0.2.20 08:00:00:00:02:00"]}, + "h3": {"ip": "10.0.3.3/24", "mac": "08:00:00:00:03:33", + "commands":["route add default gw 10.0.3.30 dev eth0", + "arp -i eth0 -s 10.0.3.30 08:00:00:00:03:00"]} + }, + "switches": { + "s1": { "runtime_json" : "triangle-topo/s1-runtime.json" }, + "s2": { "runtime_json" : "triangle-topo/s2-runtime.json" }, + "s3": { "runtime_json" : "triangle-topo/s3-runtime.json" } + }, + "links": [ + ["h1", "s1-p1"], ["s1-p2", "s2-p2"], ["s1-p3", "s3-p2"], + ["s3-p3", "s2-p3"], ["h2", "s2-p1"], ["h3", "s3-p1"] + ] +} diff --git a/exercises/basic_tunnel/Makefile b/exercises/basic_tunnel/Makefile index cfbeb1652..23bdc5b99 100644 --- a/exercises/basic_tunnel/Makefile +++ b/exercises/basic_tunnel/Makefile @@ -1,5 +1,3 @@ BMV2_SWITCH_EXE = simple_switch_grpc -NO_P4 = true -P4C_ARGS = --p4runtime-files $(basename $@).p4.p4info.txt include ../../utils/Makefile diff --git a/exercises/basic_tunnel/s1-runtime.json b/exercises/basic_tunnel/s1-runtime.json index d54bfa67d..f1d717d4e 100644 --- a/exercises/basic_tunnel/s1-runtime.json +++ b/exercises/basic_tunnel/s1-runtime.json @@ -10,7 +10,7 @@ }, "action_name": "MyIngress.ipv4_forward", "action_params": { - "dstAddr": "00:00:00:00:01:01", + "dstAddr": "08:00:00:00:01:11", "port": 1 } }, @@ -21,7 +21,7 @@ }, "action_name": "MyIngress.ipv4_forward", "action_params": { - "dstAddr": "00:00:00:02:02:00", + "dstAddr": "08:00:00:00:02:00", "port": 2 } }, @@ -32,7 +32,7 @@ }, "action_name": "MyIngress.ipv4_forward", "action_params": { - "dstAddr": "00:00:00:03:03:00", + "dstAddr": "08:00:00:00:03:00", "port": 3 } }, diff --git a/exercises/basic_tunnel/s2-runtime.json b/exercises/basic_tunnel/s2-runtime.json index 74e3716b5..531689118 100644 --- a/exercises/basic_tunnel/s2-runtime.json +++ b/exercises/basic_tunnel/s2-runtime.json @@ -10,7 +10,7 @@ }, "action_name": "MyIngress.ipv4_forward", "action_params": { - "dstAddr": "00:00:00:01:02:00", + "dstAddr": "08:00:00:00:01:00", "port": 2 } }, @@ -21,7 +21,7 @@ }, "action_name": "MyIngress.ipv4_forward", "action_params": { - "dstAddr": "00:00:00:00:02:02", + "dstAddr": "08:00:00:00:02:22", "port": 1 } }, @@ -32,7 +32,7 @@ }, "action_name": "MyIngress.ipv4_forward", "action_params": { - "dstAddr": "00:00:00:03:02:00", + "dstAddr": "08:00:00:00:03:00", "port": 3 } }, diff --git a/exercises/basic_tunnel/s3-runtime.json b/exercises/basic_tunnel/s3-runtime.json index 6bd82dc94..7883fa019 100644 --- a/exercises/basic_tunnel/s3-runtime.json +++ b/exercises/basic_tunnel/s3-runtime.json @@ -10,7 +10,7 @@ }, "action_name": "MyIngress.ipv4_forward", "action_params": { - "dstAddr": "00:00:00:01:03:00", + "dstAddr": "08:00:00:00:01:00", "port": 2 } }, @@ -21,7 +21,7 @@ }, "action_name": "MyIngress.ipv4_forward", "action_params": { - "dstAddr": "00:00:00:02:03:00", + "dstAddr": "08:00:00:00:02:00", "port": 3 } }, @@ -32,7 +32,7 @@ }, "action_name": "MyIngress.ipv4_forward", "action_params": { - "dstAddr": "00:00:00:00:03:03", + "dstAddr": "08:00:00:00:03:33", "port": 1 } }, diff --git a/exercises/basic_tunnel/topology.json b/exercises/basic_tunnel/topology.json index e43e3c0c4..2193ad59c 100644 --- a/exercises/basic_tunnel/topology.json +++ b/exercises/basic_tunnel/topology.json @@ -1,16 +1,22 @@ { - "hosts": [ - "h1", - "h2", - "h3" - ], + "hosts": { + "h1": {"ip": "10.0.1.1/24", "mac": "08:00:00:00:01:11", + "commands":["route add default gw 10.0.1.10 dev eth0", + "arp -i eth0 -s 10.0.1.10 08:00:00:00:01:00"]}, + "h2": {"ip": "10.0.2.2/24", "mac": "08:00:00:00:02:22", + "commands":["route add default gw 10.0.2.20 dev eth0", + "arp -i eth0 -s 10.0.2.20 08:00:00:00:02:00"]}, + "h3": {"ip": "10.0.3.3/24", "mac": "08:00:00:00:03:33", + "commands":["route add default gw 10.0.3.30 dev eth0", + "arp -i eth0 -s 10.0.3.30 08:00:00:00:03:00"]} + }, "switches": { "s1": { "runtime_json" : "s1-runtime.json" }, "s2": { "runtime_json" : "s2-runtime.json" }, "s3": { "runtime_json" : "s3-runtime.json" } }, "links": [ - ["h1", "s1"], ["s1", "s2"], ["s1", "s3"], - ["s3", "s2"], ["s2", "h2"], ["s3", "h3"] + ["h1", "s1-p1"], ["s1-p2", "s2-p2"], ["s1-p3", "s3-p2"], + ["s3-p3", "s2-p3"], ["h2", "s2-p1"], ["h3", "s3-p1"] ] } diff --git a/exercises/ecn/Makefile b/exercises/ecn/Makefile index cfbeb1652..23bdc5b99 100644 --- a/exercises/ecn/Makefile +++ b/exercises/ecn/Makefile @@ -1,5 +1,3 @@ BMV2_SWITCH_EXE = simple_switch_grpc -NO_P4 = true -P4C_ARGS = --p4runtime-files $(basename $@).p4.p4info.txt include ../../utils/Makefile diff --git a/exercises/ecn/s1-runtime.json b/exercises/ecn/s1-runtime.json index 1fe63680d..ddb232338 100644 --- a/exercises/ecn/s1-runtime.json +++ b/exercises/ecn/s1-runtime.json @@ -10,7 +10,7 @@ }, "action_name": "MyIngress.ipv4_forward", "action_params": { - "dstAddr": "00:00:00:00:01:01", + "dstAddr": "08:00:00:00:01:01", "port": 2 } }, @@ -21,7 +21,7 @@ }, "action_name": "MyIngress.ipv4_forward", "action_params": { - "dstAddr": "00:00:00:00:01:0b", + "dstAddr": "08:00:00:00:01:11", "port": 1 } }, @@ -33,7 +33,7 @@ }, "action_name": "MyIngress.ipv4_forward", "action_params": { - "dstAddr": "00:00:00:02:03:00", + "dstAddr": "08:00:00:00:02:00", "port": 3 } }, @@ -44,7 +44,7 @@ }, "action_name": "MyIngress.ipv4_forward", "action_params": { - "dstAddr": "00:00:00:03:02:00", + "dstAddr": "08:00:00:00:03:00", "port": 4 } } diff --git a/exercises/ecn/s2-runtime.json b/exercises/ecn/s2-runtime.json index 1b5b2185a..9f3153447 100644 --- a/exercises/ecn/s2-runtime.json +++ b/exercises/ecn/s2-runtime.json @@ -10,7 +10,7 @@ }, "action_name": "MyIngress.ipv4_forward", "action_params": { - "dstAddr": "00:00:00:00:02:02", + "dstAddr": "08:00:00:00:02:02", "port": 2 } }, @@ -21,7 +21,7 @@ }, "action_name": "MyIngress.ipv4_forward", "action_params": { - "dstAddr": "00:00:00:00:02:16", + "dstAddr": "08:00:00:00:02:22", "port": 1 } }, @@ -32,7 +32,7 @@ }, "action_name": "MyIngress.ipv4_forward", "action_params": { - "dstAddr": "00:00:00:01:03:00", + "dstAddr": "08:00:00:00:01:00", "port": 3 } }, @@ -43,7 +43,7 @@ }, "action_name": "MyIngress.ipv4_forward", "action_params": { - "dstAddr": "00:00:00:03:03:00", + "dstAddr": "08:00:00:00:03:00", "port": 4 } } diff --git a/exercises/ecn/s3-runtime.json b/exercises/ecn/s3-runtime.json index 8a7af795d..16895846d 100644 --- a/exercises/ecn/s3-runtime.json +++ b/exercises/ecn/s3-runtime.json @@ -10,7 +10,7 @@ }, "action_name": "MyIngress.ipv4_forward", "action_params": { - "dstAddr": "00:00:00:00:03:03", + "dstAddr": "08:00:00:00:03:03", "port": 1 } }, @@ -21,7 +21,7 @@ }, "action_name": "MyIngress.ipv4_forward", "action_params": { - "dstAddr": "00:00:00:01:04:00", + "dstAddr": "08:00:00:00:01:00", "port": 2 } }, @@ -32,7 +32,7 @@ }, "action_name": "MyIngress.ipv4_forward", "action_params": { - "dstAddr": "00:00:00:02:04:00", + "dstAddr": "08:00:00:00:02:00", "port": 3 } } diff --git a/exercises/ecn/topology.json b/exercises/ecn/topology.json index 29df53e35..0e53e92d3 100644 --- a/exercises/ecn/topology.json +++ b/exercises/ecn/topology.json @@ -1,18 +1,28 @@ { - "hosts": [ - "h1", - "h2", - "h3", - "h11", - "h22" - ], + "hosts": { + "h1": {"ip": "10.0.1.1/31", "mac": "08:00:00:00:01:01", + "commands":["route add default gw 10.0.1.0 dev eth0", + "arp -i eth0 -s 10.0.1.0 08:00:00:00:01:00"]}, + "h11": {"ip": "10.0.1.11/31", "mac": "08:00:00:00:01:11", + "commands":["route add default gw 10.0.1.10 dev eth0", + "arp -i eth0 -s 10.0.1.10 08:00:00:00:01:00"]}, + "h2": {"ip": "10.0.2.2/31", "mac": "08:00:00:00:02:02", + "commands":["route add default gw 10.0.2.3 dev eth0", + "arp -i eth0 -s 10.0.2.3 08:00:00:00:02:00"]}, + "h22": {"ip": "10.0.2.22/31", "mac": "08:00:00:00:02:22", + "commands":["route add default gw 10.0.2.23 dev eth0", + "arp -i eth0 -s 10.0.2.23 08:00:00:00:02:00"]}, + "h3": {"ip": "10.0.3.3/31", "mac": "08:00:00:00:03:03", + "commands":["route add default gw 10.0.3.2 dev eth0", + "arp -i eth0 -s 10.0.3.2 08:00:00:00:03:00"]} + }, "switches": { "s1": { "runtime_json" : "s1-runtime.json" }, "s2": { "runtime_json" : "s2-runtime.json" }, "s3": { "runtime_json" : "s3-runtime.json" } }, "links": [ - ["h1", "s1"], ["h11", "s1"], ["s1", "s2", "0", 0.5], ["s1", "s3"], - ["s3", "s2"], ["s2", "h2"], ["s2", "h22"], ["s3", "h3"] + ["h1", "s1-p2"], ["h11", "s1-p1"], ["s1-p3", "s2-p3", "0", 0.5], ["s1-p4", "s3-p2"], + ["s3-p3", "s2-p4"], ["h2", "s2-p2"], ["h22", "s2-p1"], ["h3", "s3-p1"] ] } diff --git a/exercises/firewall/Makefile b/exercises/firewall/Makefile new file mode 100644 index 000000000..f09794c1a --- /dev/null +++ b/exercises/firewall/Makefile @@ -0,0 +1,5 @@ +BMV2_SWITCH_EXE = simple_switch_grpc +TOPO = pod-topo/topology.json +DEFAULT_PROG = basic.p4 + +include ../../utils/Makefile diff --git a/exercises/firewall/README.md b/exercises/firewall/README.md new file mode 100644 index 000000000..c91ebfe81 --- /dev/null +++ b/exercises/firewall/README.md @@ -0,0 +1,215 @@ +# Implementing A Basic Stateful Firewall + +## Introduction + +The objective of this exercise is to write a P4 program that +implements a simple stateful firewall. To do this, we will use +a bloom filter. This exercise builds upon the basic exercise +so be sure to complete that one before trying this one. + +We will use the pod-topology for this exercise, which consists of +four hosts connected to four switches, which are wired up as they +would be in a single pod of a fat tree topology. + +![topology](./firewall-topo.png) + +Switch s1 will be configured with a P4 program that implements a +simple stateful firewall (`firewall.p4`), the rest of the switches will run the +basic IPv4 router program (`basic.p4`) from the previous exercise. + +The firewall on s1 should have the following functionality: +* Hosts h1 and h2 are on the internal network and can always + connect to one another. +* Hosts h1 and h2 can freely connect to h3 and h4 on the + external network. +* Hosts h3 and h4 can only reply to connections once they have been + established from either h1 or h2, but cannot initiate new + connections to hosts on the internal network. + +**Note**: This stateful firewall is implemented 100% in the dataplane +using a simple bloom filter. Thus there is some probability of +hash collisions that would let unwanted flows to pass through. + +Our P4 program will be written for the V1Model architecture implemented +on P4.org's bmv2 software switch. The architecture file for the V1Model +can be found at: /usr/local/share/p4c/p4include/v1model.p4. This file +desribes the interfaces of the P4 programmable elements in the architecture, +the supported externs, as well as the architecture's standard metadata +fields. We encourage you to take a look at it. + +> **Spoiler alert:** There is a reference solution in the `solution` +> sub-directory. Feel free to compare your implementation to the +> reference. + +## Step 1: Run the (incomplete) starter code + +The directory with this README also contains a skeleton P4 program, +`firewall.p4`. Your job will be to extend this skeleton program to +properly implement the firewall. + +Before that, let's compile the incomplete `firewall.p4` and bring +up a switch in Mininet to test its behavior. + +1. In your shell, run: + ```bash + make run + ``` + This will: + * compile `firewall.p4`, and + * start the pod-topo in Mininet and configure all switches with + the appropriate P4 program + table entries, and + * configure all hosts with the commands listed in + [pod-topo/topology.json](./pod-topo/topology.json) + +2. You should now see a Mininet command prompt. Try to run some iperf + TCP flows between the hosts. TCP flows within the internal + network should work: + ```bash + mininet> iperf h1 h2 + ``` + + TCP flows from hosts in the internal network to the outside hosts + should also work: + ```bash + mininet> iperf h1 h3 + ``` + + TCP flows from the outside hosts to hosts inside the + internal network should NOT work. However, since the firewall is not + implemented yet, the following should work: + ```bash + mininet> iperf h3 h1 + ``` + +3. Type `exit` to leave the Mininet command line. + Then, to stop mininet: + ```bash + make stop + ``` + And to delete all pcaps, build files, and logs: + ```bash + make clean + ``` + +### A note about the control plane + +A P4 program defines a packet-processing pipeline, but the rules +within each table are inserted by the control plane. When a rule +matches a packet, its action is invoked with parameters supplied by +the control plane as part of the rule. + +In this exercise, we have already implemented the the control plane +logic for you. As part of bringing up the Mininet instance, the +`make` command will install packet-processing rules in the tables of +each switch. These are defined in the `sX-runtime.json` files, where +`X` corresponds to the switch number. + +**Important:** We use P4Runtime to install the control plane rules. The +content of files `sX-runtime.json` refer to specific names of tables, keys, and +actions, as defined in the P4Info file produced by the compiler (look for the +file `build/firewall.p4.p4info.txt` after executing `make run`). Any changes in the P4 +program that add or rename tables, keys, or actions will need to be reflected in +these `sX-runtime.json` files. + +## Step 2: Implement Firewall + +The `firewall.p4` file contains a skeleton P4 program with key pieces of +logic replaced by `TODO` comments. Your implementation should follow +the structure given in this file --- replace each `TODO` with logic +implementing the missing piece. + +**High-level Approach:** We will use a bloom filter with two hash functions +to check if a packet coming into the internal network is a part of +an already established TCP connection. We will use two different register +arrays for the bloom filter, each to be updated by a hash function. +Using different register arrays makes our design amenable to high-speed +P4 targets that typically allow only one access to a register array per packet. + +A complete `firewall.p4` will contain the following components: + +1. Header type definitions for Ethernet (`ethernet_t`), IPv4 (`ipv4_t`) and TCP (`tcp_t`). +2. Parsers for Ethernet, IPv4 and TCP that populate `ethernet_t`, `ipv4_t` and `tcp_t` fields. +3. An action to drop a packet, using `mark_to_drop()`. +4. An action (called `compute_hashes`) to compute the bloom filter's two hashes using hash +algorithms `crc16` and `crc32`. The hashes will be computed on the packet 5-tuple consisting +of IPv4 source and destination addresses, source and destination port numbers and +the IPv4 protocol type. +5. An action (`ipv4_forward`) and a table (`ipv4_lpm`) that will perform basic +IPv4 forwarding (adopted from `basic.p4`). +6. An action (called `set_direction`) that will simply set a one-bit direction variable +as per the action's parameter. +7. A table (called `check_ports`) that will read the ingress and egress port of a packet +(after IPv4 forwarding) and invoke `set_direction`. The direction will be set to `1`, +if the packet is incoming into the internal network. Otherwise, the direction will be set to `0`. +To achieve this, the file `pod-topo/s1-runtime.json` contains the appropriate control plane +entries for the `check_ports` table. +8. A control that will: + 1. First apply the table `ipv4_lpm` if the packet has a valid IPv4 header. + 2. Then if the TCP header is valid, apply the `check_ports` table to determine the direction. + 3. Apply the `compute_hashes` action to compute the two hash values which are the bit positions + in the two register arrays of the bloom filter (`reg_pos_one` and `reg_pos_two`). + When the direction is `1` i.e. the packet is incoming into the internal network, + `compute_hashes` will be invoked by swapping the source and destination IPv4 addresses + and the source and destination ports. This is to check against bloom filter's set bits + when the TCP connection was initially made from the internal network. + 4. **TODO:** If the TCP packet is going out of the internal network and is a SYN packet, + set both the bloom filter arrays at the computed bit positions (`reg_pos_one` and `reg_pos_two`). + Else, if the TCP packet is entering the internal network, + read both the bloom filter arrays at the computed bit positions and drop the packet if + either is not set. +9. A deparser that emits the Ethernet, IPv4 and TCP headers in the right order. +10. A `package` instantiation supplied with the parser, control, and deparser. + > In general, a package also requires instances of checksum verification + > and recomputation controls. These are not necessary for this tutorial + > and are replaced with instantiations of empty controls. + + +## Step 3: Run your solution + +Follow the instructions from Step 1. This time, the `iperf` flow between +h3 and h1 should be blocked by the firewall. + +### Food for thought + +You may have noticed that in this simple stateful firewall, we are adding +new TCP connections to the bloom filter (based on outgoing SYN packets). +However, we are not removing them in case of TCP connection teardown +(FIN packets). How would you implement the removal of TCP connections that are +no longer active? + +Things to consider: + - Can we simply set the bloom filter array bits to `0` on + receiving a FIN packet? What happens when there is one hash collision in + the bloom filter arrays between two _active_ TCP connections? + - How can we modify our bloom filter structure so that the deletion + operation can be properly supported? + +### Troubleshooting + +There are several problems that might manifest as you develop your program: + +1. `firewall.p4` might fail to compile. In this case, `make run` will +report the error emitted from the compiler and halt. + +2. `firewall.p4` might compile but fail to support the control plane +rules in the `s1-runtime.json` file that `make run` tries to install +using P4Runtime. In this case, `make run` will report errors if control +plane rules cannot be installed. Use these error messages to fix your +`firewall.p4` implementation. + +3. `firewall.p4` might compile, and the control plane rules might be +installed, but the switch might not process packets in the desired +way. The `logs/sX.log` files contain detailed logs that describe +how each switch processes each packet. The output is detailed and can +help pinpoint logic errors in your implementation. + +#### Cleaning up Mininet + +In the latter two cases above, `make run` may leave a Mininet instance +running in the background. Use the following command to clean up +these instances: + +```bash +make stop +``` + diff --git a/exercises/firewall/basic.p4 b/exercises/firewall/basic.p4 new file mode 100644 index 000000000..09899f30c --- /dev/null +++ b/exercises/firewall/basic.p4 @@ -0,0 +1,176 @@ +/* -*- P4_16 -*- */ +#include +#include + +const bit<16> TYPE_IPV4 = 0x800; + +/************************************************************************* +*********************** H E A D E R S *********************************** +*************************************************************************/ + +typedef bit<9> egressSpec_t; +typedef bit<48> macAddr_t; +typedef bit<32> ip4Addr_t; + +header ethernet_t { + macAddr_t dstAddr; + macAddr_t srcAddr; + bit<16> etherType; +} + +header ipv4_t { + bit<4> version; + bit<4> ihl; + bit<8> diffserv; + bit<16> totalLen; + bit<16> identification; + bit<3> flags; + bit<13> fragOffset; + bit<8> ttl; + bit<8> protocol; + bit<16> hdrChecksum; + ip4Addr_t srcAddr; + ip4Addr_t dstAddr; +} + +struct metadata { + /* empty */ +} + +struct headers { + ethernet_t ethernet; + ipv4_t ipv4; +} + +/************************************************************************* +*********************** P A R S E R *********************************** +*************************************************************************/ + +parser MyParser(packet_in packet, + out headers hdr, + inout metadata meta, + inout standard_metadata_t standard_metadata) { + + state start { + transition parse_ethernet; + } + + state parse_ethernet { + packet.extract(hdr.ethernet); + transition select(hdr.ethernet.etherType) { + TYPE_IPV4: parse_ipv4; + default: accept; + } + } + + state parse_ipv4 { + packet.extract(hdr.ipv4); + transition accept; + } + +} + +/************************************************************************* +************ C H E C K S U M V E R I F I C A T I O N ************* +*************************************************************************/ + +control MyVerifyChecksum(inout headers hdr, inout metadata meta) { + apply { } +} + + +/************************************************************************* +************** I N G R E S S P R O C E S S I N G ******************* +*************************************************************************/ + +control MyIngress(inout headers hdr, + inout metadata meta, + inout standard_metadata_t standard_metadata) { + action drop() { + mark_to_drop(standard_metadata); + } + + action ipv4_forward(macAddr_t dstAddr, egressSpec_t port) { + standard_metadata.egress_spec = port; + hdr.ethernet.srcAddr = hdr.ethernet.dstAddr; + hdr.ethernet.dstAddr = dstAddr; + hdr.ipv4.ttl = hdr.ipv4.ttl - 1; + } + + table ipv4_lpm { + key = { + hdr.ipv4.dstAddr: lpm; + } + actions = { + ipv4_forward; + drop; + NoAction; + } + size = 1024; + default_action = drop(); + } + + apply { + if (hdr.ipv4.isValid()) { + ipv4_lpm.apply(); + } + } +} + +/************************************************************************* +**************** E G R E S S P R O C E S S I N G ******************* +*************************************************************************/ + +control MyEgress(inout headers hdr, + inout metadata meta, + inout standard_metadata_t standard_metadata) { + apply { } +} + +/************************************************************************* +************* C H E C K S U M C O M P U T A T I O N ************** +*************************************************************************/ + +control MyComputeChecksum(inout headers hdr, inout metadata meta) { + apply { + update_checksum( + hdr.ipv4.isValid(), + { hdr.ipv4.version, + hdr.ipv4.ihl, + hdr.ipv4.diffserv, + hdr.ipv4.totalLen, + hdr.ipv4.identification, + hdr.ipv4.flags, + hdr.ipv4.fragOffset, + hdr.ipv4.ttl, + hdr.ipv4.protocol, + hdr.ipv4.srcAddr, + hdr.ipv4.dstAddr }, + hdr.ipv4.hdrChecksum, + HashAlgorithm.csum16); + } +} + +/************************************************************************* +*********************** D E P A R S E R ******************************* +*************************************************************************/ + +control MyDeparser(packet_out packet, in headers hdr) { + apply { + packet.emit(hdr.ethernet); + packet.emit(hdr.ipv4); + } +} + +/************************************************************************* +*********************** S W I T C H ******************************* +*************************************************************************/ + +V1Switch( +MyParser(), +MyVerifyChecksum(), +MyIngress(), +MyEgress(), +MyComputeChecksum(), +MyDeparser() +) main; diff --git a/exercises/firewall/firewall-topo.png b/exercises/firewall/firewall-topo.png new file mode 100644 index 000000000..1917557b1 Binary files /dev/null and b/exercises/firewall/firewall-topo.png differ diff --git a/exercises/firewall/firewall.p4 b/exercises/firewall/firewall.p4 new file mode 100644 index 000000000..64b969239 --- /dev/null +++ b/exercises/firewall/firewall.p4 @@ -0,0 +1,276 @@ +/* -*- P4_16 -*- */ +#include +#include + +/* CONSTANTS */ + +const bit<16> TYPE_IPV4 = 0x800; +const bit<8> TYPE_TCP = 6; + +#define BLOOM_FILTER_ENTRIES 4096 +#define BLOOM_FILTER_BIT_WIDTH 1 + +/************************************************************************* +*********************** H E A D E R S *********************************** +*************************************************************************/ + +typedef bit<9> egressSpec_t; +typedef bit<48> macAddr_t; +typedef bit<32> ip4Addr_t; + +header ethernet_t { + macAddr_t dstAddr; + macAddr_t srcAddr; + bit<16> etherType; +} + +header ipv4_t { + bit<4> version; + bit<4> ihl; + bit<8> diffserv; + bit<16> totalLen; + bit<16> identification; + bit<3> flags; + bit<13> fragOffset; + bit<8> ttl; + bit<8> protocol; + bit<16> hdrChecksum; + ip4Addr_t srcAddr; + ip4Addr_t dstAddr; +} + +header tcp_t{ + bit<16> srcPort; + bit<16> dstPort; + bit<32> seqNo; + bit<32> ackNo; + bit<4> dataOffset; + bit<4> res; + bit<1> cwr; + bit<1> ece; + bit<1> urg; + bit<1> ack; + bit<1> psh; + bit<1> rst; + bit<1> syn; + bit<1> fin; + bit<16> window; + bit<16> checksum; + bit<16> urgentPtr; +} + +struct metadata { + /* empty */ +} + +struct headers { + ethernet_t ethernet; + ipv4_t ipv4; + tcp_t tcp; +} + +/************************************************************************* +*********************** P A R S E R *********************************** +*************************************************************************/ + +parser MyParser(packet_in packet, + out headers hdr, + inout metadata meta, + inout standard_metadata_t standard_metadata) { + + state start { + transition parse_ethernet; + } + + state parse_ethernet { + packet.extract(hdr.ethernet); + transition select(hdr.ethernet.etherType) { + TYPE_IPV4: parse_ipv4; + default: accept; + } + } + + state parse_ipv4 { + packet.extract(hdr.ipv4); + transition select(hdr.ipv4.protocol){ + TYPE_TCP: tcp; + default: accept; + } + } + + state tcp { + packet.extract(hdr.tcp); + transition accept; + } +} + +/************************************************************************* +************ C H E C K S U M V E R I F I C A T I O N ************* +*************************************************************************/ + +control MyVerifyChecksum(inout headers hdr, inout metadata meta) { + apply { } +} + + +/************************************************************************* +************** I N G R E S S P R O C E S S I N G ******************* +*************************************************************************/ + +control MyIngress(inout headers hdr, + inout metadata meta, + inout standard_metadata_t standard_metadata) { + + register>(BLOOM_FILTER_ENTRIES) bloom_filter_1; + register>(BLOOM_FILTER_ENTRIES) bloom_filter_2; + bit<32> reg_pos_one; bit<32> reg_pos_two; + bit<1> reg_val_one; bit<1> reg_val_two; + bit<1> direction; + + action drop() { + mark_to_drop(standard_metadata); + } + + action compute_hashes(ip4Addr_t ipAddr1, ip4Addr_t ipAddr2, bit<16> port1, bit<16> port2){ + //Get register position + hash(reg_pos_one, HashAlgorithm.crc16, (bit<32>)0, {ipAddr1, + ipAddr2, + port1, + port2, + hdr.ipv4.protocol}, + (bit<32>)BLOOM_FILTER_ENTRIES); + + hash(reg_pos_two, HashAlgorithm.crc32, (bit<32>)0, {ipAddr1, + ipAddr2, + port1, + port2, + hdr.ipv4.protocol}, + (bit<32>)BLOOM_FILTER_ENTRIES); + } + + action ipv4_forward(macAddr_t dstAddr, egressSpec_t port) { + standard_metadata.egress_spec = port; + hdr.ethernet.srcAddr = hdr.ethernet.dstAddr; + hdr.ethernet.dstAddr = dstAddr; + hdr.ipv4.ttl = hdr.ipv4.ttl - 1; + } + + table ipv4_lpm { + key = { + hdr.ipv4.dstAddr: lpm; + } + actions = { + ipv4_forward; + drop; + NoAction; + } + size = 1024; + default_action = drop(); + } + + action set_direction(bit<1> dir) { + direction = dir; + } + + table check_ports { + key = { + standard_metadata.ingress_port: exact; + standard_metadata.egress_spec: exact; + } + actions = { + set_direction; + NoAction; + } + size = 1024; + default_action = NoAction(); + } + + apply { + if (hdr.ipv4.isValid()){ + ipv4_lpm.apply(); + if (hdr.tcp.isValid()){ + direction = 0; // default + if (check_ports.apply().hit) { + // test and set the bloom filter + if (direction == 0) { + compute_hashes(hdr.ipv4.srcAddr, hdr.ipv4.dstAddr, hdr.tcp.srcPort, hdr.tcp.dstPort); + } + else { + compute_hashes(hdr.ipv4.dstAddr, hdr.ipv4.srcAddr, hdr.tcp.dstPort, hdr.tcp.srcPort); + } + // Packet comes from internal network + if (direction == 0){ + // TODO: this packet is part of an outgoing TCP connection. + // We need to set the bloom filter if this is a SYN packet + // E.g. bloom_filter_1.write(, ); + } + // Packet comes from outside + else if (direction == 1){ + // TODO: this packet is part of an incomming TCP connection. + // We need to check if this packet is allowed to pass by reading the bloom filter + // E.g. bloom_filter_1.read(, ); + } + } + } + } + } +} + +/************************************************************************* +**************** E G R E S S P R O C E S S I N G ******************* +*************************************************************************/ + +control MyEgress(inout headers hdr, + inout metadata meta, + inout standard_metadata_t standard_metadata) { + apply { } +} + +/************************************************************************* +************* C H E C K S U M C O M P U T A T I O N ************** +*************************************************************************/ + +control MyComputeChecksum(inout headers hdr, inout metadata meta) { + apply { + update_checksum( + hdr.ipv4.isValid(), + { hdr.ipv4.version, + hdr.ipv4.ihl, + hdr.ipv4.diffserv, + hdr.ipv4.totalLen, + hdr.ipv4.identification, + hdr.ipv4.flags, + hdr.ipv4.fragOffset, + hdr.ipv4.ttl, + hdr.ipv4.protocol, + hdr.ipv4.srcAddr, + hdr.ipv4.dstAddr }, + hdr.ipv4.hdrChecksum, + HashAlgorithm.csum16); + } +} + +/************************************************************************* +*********************** D E P A R S E R ******************************* +*************************************************************************/ + +control MyDeparser(packet_out packet, in headers hdr) { + apply { + packet.emit(hdr.ethernet); + packet.emit(hdr.ipv4); + packet.emit(hdr.tcp); + } +} + +/************************************************************************* +*********************** S W I T C H ******************************* +*************************************************************************/ + +V1Switch( +MyParser(), +MyVerifyChecksum(), +MyIngress(), +MyEgress(), +MyComputeChecksum(), +MyDeparser() +) main; diff --git a/exercises/firewall/pod-topo/s1-runtime.json b/exercises/firewall/pod-topo/s1-runtime.json new file mode 100644 index 000000000..82c2f9d00 --- /dev/null +++ b/exercises/firewall/pod-topo/s1-runtime.json @@ -0,0 +1,145 @@ +{ + "target": "bmv2", + "p4info": "build/firewall.p4.p4info.txt", + "bmv2_json": "build/firewall.json", + "table_entries": [ + { + "table": "MyIngress.check_ports", + "match": { + "standard_metadata.ingress_port": 1, + "standard_metadata.egress_spec": 3 + }, + "action_name": "MyIngress.set_direction", + "action_params": { + "dir": 0 + } + }, + { + "table": "MyIngress.check_ports", + "match": { + "standard_metadata.ingress_port": 1, + "standard_metadata.egress_spec": 4 + }, + "action_name": "MyIngress.set_direction", + "action_params": { + "dir": 0 + } + }, + { + "table": "MyIngress.check_ports", + "match": { + "standard_metadata.ingress_port": 2, + "standard_metadata.egress_spec": 3 + }, + "action_name": "MyIngress.set_direction", + "action_params": { + "dir": 0 + } + }, + { + "table": "MyIngress.check_ports", + "match": { + "standard_metadata.ingress_port": 2, + "standard_metadata.egress_spec": 4 + }, + "action_name": "MyIngress.set_direction", + "action_params": { + "dir": 0 + } + }, + { + "table": "MyIngress.check_ports", + "match": { + "standard_metadata.ingress_port": 3, + "standard_metadata.egress_spec": 1 + }, + "action_name": "MyIngress.set_direction", + "action_params": { + "dir": 1 + } + }, + { + "table": "MyIngress.check_ports", + "match": { + "standard_metadata.ingress_port": 3, + "standard_metadata.egress_spec": 2 + }, + "action_name": "MyIngress.set_direction", + "action_params": { + "dir": 1 + } + }, + { + "table": "MyIngress.check_ports", + "match": { + "standard_metadata.ingress_port": 4, + "standard_metadata.egress_spec": 1 + }, + "action_name": "MyIngress.set_direction", + "action_params": { + "dir": 1 + } + }, + { + "table": "MyIngress.check_ports", + "match": { + "standard_metadata.ingress_port": 4, + "standard_metadata.egress_spec": 2 + }, + "action_name": "MyIngress.set_direction", + "action_params": { + "dir": 1 + } + }, + { + "table": "MyIngress.ipv4_lpm", + "default_action": true, + "action_name": "MyIngress.drop", + "action_params": { } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.1.1", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:01:11", + "port": 1 + } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.2.2", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:02:22", + "port": 2 + } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.3.3", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:03:00", + "port": 3 + } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.4.4", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:04:00", + "port": 4 + } + } + ] +} diff --git a/exercises/firewall/pod-topo/s2-runtime.json b/exercises/firewall/pod-topo/s2-runtime.json new file mode 100644 index 000000000..501dff20c --- /dev/null +++ b/exercises/firewall/pod-topo/s2-runtime.json @@ -0,0 +1,57 @@ +{ + "target": "bmv2", + "p4info": "build/basic.p4.p4info.txt", + "bmv2_json": "build/basic.json", + "table_entries": [ + { + "table": "MyIngress.ipv4_lpm", + "default_action": true, + "action_name": "MyIngress.drop", + "action_params": { } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.1.1", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:03:00", + "port": 4 + } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.2.2", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:04:00", + "port": 3 + } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.3.3", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:03:33", + "port": 1 + } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.4.4", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:04:44", + "port": 2 + } + } + ] +} diff --git a/exercises/firewall/pod-topo/s3-runtime.json b/exercises/firewall/pod-topo/s3-runtime.json new file mode 100644 index 000000000..8cbc646b4 --- /dev/null +++ b/exercises/firewall/pod-topo/s3-runtime.json @@ -0,0 +1,57 @@ +{ + "target": "bmv2", + "p4info": "build/basic.p4.p4info.txt", + "bmv2_json": "build/basic.json", + "table_entries": [ + { + "table": "MyIngress.ipv4_lpm", + "default_action": true, + "action_name": "MyIngress.drop", + "action_params": { } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.1.1", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:01:00", + "port": 1 + } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.2.2", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:01:00", + "port": 1 + } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.3.3", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:02:00", + "port": 2 + } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.4.4", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:02:00", + "port": 2 + } + } + ] +} diff --git a/exercises/firewall/pod-topo/s4-runtime.json b/exercises/firewall/pod-topo/s4-runtime.json new file mode 100644 index 000000000..fbdad6836 --- /dev/null +++ b/exercises/firewall/pod-topo/s4-runtime.json @@ -0,0 +1,57 @@ +{ + "target": "bmv2", + "p4info": "build/basic.p4.p4info.txt", + "bmv2_json": "build/basic.json", + "table_entries": [ + { + "table": "MyIngress.ipv4_lpm", + "default_action": true, + "action_name": "MyIngress.drop", + "action_params": { } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.1.1", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:01:00", + "port": 2 + } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.2.2", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:01:00", + "port": 2 + } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.3.3", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:02:00", + "port": 1 + } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.4.4", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:02:00", + "port": 1 + } + } + ] +} diff --git a/exercises/firewall/pod-topo/topology.json b/exercises/firewall/pod-topo/topology.json new file mode 100644 index 000000000..696355d58 --- /dev/null +++ b/exercises/firewall/pod-topo/topology.json @@ -0,0 +1,27 @@ +{ + "hosts": { + "h1": {"ip": "10.0.1.1/24", "mac": "08:00:00:00:01:11", + "commands":["route add default gw 10.0.1.10 dev eth0", + "arp -i eth0 -s 10.0.1.10 08:00:00:00:01:00"]}, + "h2": {"ip": "10.0.2.2/24", "mac": "08:00:00:00:02:22", + "commands":["route add default gw 10.0.2.20 dev eth0", + "arp -i eth0 -s 10.0.2.20 08:00:00:00:02:00"]}, + "h3": {"ip": "10.0.3.3/24", "mac": "08:00:00:00:03:33", + "commands":["route add default gw 10.0.3.30 dev eth0", + "arp -i eth0 -s 10.0.3.30 08:00:00:00:03:00"]}, + "h4": {"ip": "10.0.4.4/24", "mac": "08:00:00:00:04:44", + "commands":["route add default gw 10.0.4.40 dev eth0", + "arp -i eth0 -s 10.0.4.40 08:00:00:00:04:00"]} + }, + "switches": { + "s1": { "runtime_json" : "pod-topo/s1-runtime.json", + "program" : "build/firewall.json" }, + "s2": { "runtime_json" : "pod-topo/s2-runtime.json" }, + "s3": { "runtime_json" : "pod-topo/s3-runtime.json" }, + "s4": { "runtime_json" : "pod-topo/s4-runtime.json" } + }, + "links": [ + ["h1", "s1-p1"], ["h2", "s1-p2"], ["s1-p3", "s3-p1"], ["s1-p4", "s4-p2"], + ["h3", "s2-p1"], ["h4", "s2-p2"], ["s2-p3", "s4-p1"], ["s2-p4", "s3-p2"] + ] +} diff --git a/exercises/firewall/solution/firewall.p4 b/exercises/firewall/solution/firewall.p4 new file mode 100644 index 000000000..4b513b19e --- /dev/null +++ b/exercises/firewall/solution/firewall.p4 @@ -0,0 +1,282 @@ +/* -*- P4_16 -*- */ +#include +#include + +/* CONSTANTS */ + +const bit<16> TYPE_IPV4 = 0x800; +const bit<8> TYPE_TCP = 6; + +#define BLOOM_FILTER_ENTRIES 4096 +#define BLOOM_FILTER_BIT_WIDTH 1 + +/************************************************************************* +*********************** H E A D E R S *********************************** +*************************************************************************/ + +typedef bit<9> egressSpec_t; +typedef bit<48> macAddr_t; +typedef bit<32> ip4Addr_t; + +header ethernet_t { + macAddr_t dstAddr; + macAddr_t srcAddr; + bit<16> etherType; +} + +header ipv4_t { + bit<4> version; + bit<4> ihl; + bit<8> diffserv; + bit<16> totalLen; + bit<16> identification; + bit<3> flags; + bit<13> fragOffset; + bit<8> ttl; + bit<8> protocol; + bit<16> hdrChecksum; + ip4Addr_t srcAddr; + ip4Addr_t dstAddr; +} + +header tcp_t{ + bit<16> srcPort; + bit<16> dstPort; + bit<32> seqNo; + bit<32> ackNo; + bit<4> dataOffset; + bit<4> res; + bit<1> cwr; + bit<1> ece; + bit<1> urg; + bit<1> ack; + bit<1> psh; + bit<1> rst; + bit<1> syn; + bit<1> fin; + bit<16> window; + bit<16> checksum; + bit<16> urgentPtr; +} + +struct metadata { + /* empty */ +} + +struct headers { + ethernet_t ethernet; + ipv4_t ipv4; + tcp_t tcp; +} + +/************************************************************************* +*********************** P A R S E R *********************************** +*************************************************************************/ + +parser MyParser(packet_in packet, + out headers hdr, + inout metadata meta, + inout standard_metadata_t standard_metadata) { + + state start { + transition parse_ethernet; + } + + state parse_ethernet { + packet.extract(hdr.ethernet); + transition select(hdr.ethernet.etherType) { + TYPE_IPV4: parse_ipv4; + default: accept; + } + } + + state parse_ipv4 { + packet.extract(hdr.ipv4); + transition select(hdr.ipv4.protocol){ + TYPE_TCP: tcp; + default: accept; + } + } + + state tcp { + packet.extract(hdr.tcp); + transition accept; + } +} + +/************************************************************************* +************ C H E C K S U M V E R I F I C A T I O N ************* +*************************************************************************/ + +control MyVerifyChecksum(inout headers hdr, inout metadata meta) { + apply { } +} + + +/************************************************************************* +************** I N G R E S S P R O C E S S I N G ******************* +*************************************************************************/ + +control MyIngress(inout headers hdr, + inout metadata meta, + inout standard_metadata_t standard_metadata) { + + register>(BLOOM_FILTER_ENTRIES) bloom_filter_1; + register>(BLOOM_FILTER_ENTRIES) bloom_filter_2; + bit<32> reg_pos_one; bit<32> reg_pos_two; + bit<1> reg_val_one; bit<1> reg_val_two; + bit<1> direction; + + action drop() { + mark_to_drop(standard_metadata); + } + + action compute_hashes(ip4Addr_t ipAddr1, ip4Addr_t ipAddr2, bit<16> port1, bit<16> port2){ + //Get register position + hash(reg_pos_one, HashAlgorithm.crc16, (bit<32>)0, {ipAddr1, + ipAddr2, + port1, + port2, + hdr.ipv4.protocol}, + (bit<32>)BLOOM_FILTER_ENTRIES); + + hash(reg_pos_two, HashAlgorithm.crc32, (bit<32>)0, {ipAddr1, + ipAddr2, + port1, + port2, + hdr.ipv4.protocol}, + (bit<32>)BLOOM_FILTER_ENTRIES); + } + + action ipv4_forward(macAddr_t dstAddr, egressSpec_t port) { + standard_metadata.egress_spec = port; + hdr.ethernet.srcAddr = hdr.ethernet.dstAddr; + hdr.ethernet.dstAddr = dstAddr; + hdr.ipv4.ttl = hdr.ipv4.ttl - 1; + } + + table ipv4_lpm { + key = { + hdr.ipv4.dstAddr: lpm; + } + actions = { + ipv4_forward; + drop; + NoAction; + } + size = 1024; + default_action = drop(); + } + + action set_direction(bit<1> dir) { + direction = dir; + } + + table check_ports { + key = { + standard_metadata.ingress_port: exact; + standard_metadata.egress_spec: exact; + } + actions = { + set_direction; + NoAction; + } + size = 1024; + default_action = NoAction(); + } + + apply { + if (hdr.ipv4.isValid()){ + ipv4_lpm.apply(); + if (hdr.tcp.isValid()){ + direction = 0; // default + if (check_ports.apply().hit) { + // test and set the bloom filter + if (direction == 0) { + compute_hashes(hdr.ipv4.srcAddr, hdr.ipv4.dstAddr, hdr.tcp.srcPort, hdr.tcp.dstPort); + } + else { + compute_hashes(hdr.ipv4.dstAddr, hdr.ipv4.srcAddr, hdr.tcp.dstPort, hdr.tcp.srcPort); + } + // Packet comes from internal network + if (direction == 0){ + // If there is a syn we update the bloom filter and add the entry + if (hdr.tcp.syn == 1){ + bloom_filter_1.write(reg_pos_one, 1); + bloom_filter_2.write(reg_pos_two, 1); + } + } + // Packet comes from outside + else if (direction == 1){ + // Read bloom filter cells to check if there are 1's + bloom_filter_1.read(reg_val_one, reg_pos_one); + bloom_filter_2.read(reg_val_two, reg_pos_two); + // only allow flow to pass if both entries are set + if (reg_val_one != 1 || reg_val_two != 1){ + drop(); + } + } + } + } + } + } +} + +/************************************************************************* +**************** E G R E S S P R O C E S S I N G ******************* +*************************************************************************/ + +control MyEgress(inout headers hdr, + inout metadata meta, + inout standard_metadata_t standard_metadata) { + apply { } +} + +/************************************************************************* +************* C H E C K S U M C O M P U T A T I O N ************** +*************************************************************************/ + +control MyComputeChecksum(inout headers hdr, inout metadata meta) { + apply { + update_checksum( + hdr.ipv4.isValid(), + { hdr.ipv4.version, + hdr.ipv4.ihl, + hdr.ipv4.diffserv, + hdr.ipv4.totalLen, + hdr.ipv4.identification, + hdr.ipv4.flags, + hdr.ipv4.fragOffset, + hdr.ipv4.ttl, + hdr.ipv4.protocol, + hdr.ipv4.srcAddr, + hdr.ipv4.dstAddr }, + hdr.ipv4.hdrChecksum, + HashAlgorithm.csum16); + } +} + +/************************************************************************* +*********************** D E P A R S E R ******************************* +*************************************************************************/ + +control MyDeparser(packet_out packet, in headers hdr) { + apply { + packet.emit(hdr.ethernet); + packet.emit(hdr.ipv4); + packet.emit(hdr.tcp); + } +} + +/************************************************************************* +*********************** S W I T C H ******************************* +*************************************************************************/ + +V1Switch( +MyParser(), +MyVerifyChecksum(), +MyIngress(), +MyEgress(), +MyComputeChecksum(), +MyDeparser() +) main; diff --git a/exercises/link_monitor/Makefile b/exercises/link_monitor/Makefile new file mode 100644 index 000000000..476f2659c --- /dev/null +++ b/exercises/link_monitor/Makefile @@ -0,0 +1,4 @@ +BMV2_SWITCH_EXE = simple_switch_grpc +TOPO = pod-topo/topology.json + +include ../../utils/Makefile diff --git a/exercises/link_monitor/README.md b/exercises/link_monitor/README.md new file mode 100644 index 000000000..e5086b101 --- /dev/null +++ b/exercises/link_monitor/README.md @@ -0,0 +1,227 @@ +# Implementing Link Monitoring + +## Introduction + +The objective of this exercise is to write a P4 program that enables +a host to monitor the utilization of all links in the network. This +exercise builds upon the basic IPv4 forwarding exercise so be sure +to complete that one before attempting this one. Specifically, we +will modify the basic P4 program to process a source routed probe +packet such that it is able to pick up the egress link utilization +at each hop and deliver it to a host for monitoring purposes. + +Our probe packet will contain the following three header types: +``` +// Top-level probe header, indicates how many hops this probe +// packet has traversed so far. +header probe_t { + bit<8> hop_cnt; +} + +// The data added to the probe by each switch at each hop. +header probe_data_t { + bit<1> bos; + bit<7> swid; + bit<8> port; + bit<32> byte_cnt; + time_t last_time; + time_t cur_time; +} + +// Indicates the egress port the switch should send this probe +// packet out of. There is one of these headers for each hop. +header probe_fwd_t { + bit<8> egress_spec; +} +``` + +We will use the pod-topology for this exercise, which consists of +four hosts connected to four switches that are wired up as they +would be in a single pod of a fat tree topology. + +![topology](./link-monitor-topo.png) + +In order to monitor the link utilization our switch will maintain +two register arrays: +* `byte_cnt_reg` - counts the number of bytes transmitted out of + each port since the last probe packet was transmitted out of + the port. +* `last_time_reg` - stores the last time that a probe packet was + transmitted out of each port. + +Our P4 program will be written for the V1Model architecture implemented +on P4.org's bmv2 software switch. The architecture file for the V1Model +can be found at: /usr/local/share/p4c/p4include/v1model.p4. This file +desribes the interfaces of the P4 programmable elements in the architecture, +the supported externs, as well as the architecture's standard metadata +fields. We encourage you to take a look at it. + +> **Spoiler alert:** There is a reference solution in the `solution` +> sub-directory. Feel free to compare your implementation to the +> reference. + +## Step 1: Run the (incomplete) starter code + +The directory with this README contains a skeleton P4 program, +`link_monitor.p4`, which implements basic IPv4 forwarding, as well +as source routing of the probe packets. Your job will be to +extend this skeleton program to fill out the fields in the probe +packet. + +Before that, let's compile and test the incomplete `link_monitor.p4` +program: + +1. In your shell, run: + ```bash + make run + ``` + This will: + * compile `link_monitor.p4`, and + * start the pod-topo in Mininet and configure all switches with + the `link_monitor.p4` program + table entries, and + * configure all hosts with the commands listed in + [pod-topo/topology.json](./pod-topo/topology.json) + +2. You should now see a Mininet command prompt. Open two terminals +on `h1`: + ```bash + mininet> xterm h1 h1 + ``` +3. In one of the xterms run the `send.py` script to start sending +probe packets every second. Each of these probe packets takes the +path indicated in link-monitor-topo.png. + ```bash + ./send.py + ``` +4. In the other terminal run the `receive.py` script to start +receiving and parsing the probe packets. This allows us to monitor +the link utilization within the network. + ```bash + ./receive.py + ``` +The reported link utilization and the switch port numbers will +always be 0 because the probe fields have not been filled out yet. + +5. Run an iperf flow between h1 and h4: + ```bash + mininet> iperf h1 h4 + ``` +6. Type `exit` to leave each xterm and the Mininet command line. + Then, to stop mininet: + ```bash + make stop + ``` + And to delete all pcaps, build files, and logs: + ```bash + make clean + ``` + +The measured link utilizations will not agree with what iperf reports +because the probe packet fields have not been populated yet. Your +goal is to fill out the probe packet fields so that the two +measurements agree. + +### A note about the control plane + +A P4 program defines a packet-processing pipeline, but the rules +within each table are inserted by the control plane. When a rule +matches a packet, its action is invoked with parameters supplied by +the control plane as part of the rule. + +In this exercise, we have already implemented the control plane +logic for you. As part of bringing up the Mininet instance, the +`make run` command will install packet-processing rules in the tables of +each switch. These are defined in the `sX-runtime.json` files, where +`X` corresponds to the switch number. + +**Important:** We use P4Runtime to install the control plane rules. The +content of files `sX-runtime.json` refer to specific names of tables, keys, and +actions, as defined in the P4Info file produced by the compiler (look for the +file `build/link_monitor.p4.p4info.txt` after executing `make run`). Any +changes in the P4 program that add or rename tables, keys, or actions +will need to be reflected in these `sX-runtime.json` files. + +## Step 2: Implement Link Monitoring Logic + +The `link_monitor.p4` file contains a skeleton P4 program with key pieces of +logic replaced by `TODO` comments. Your implementation should follow +the structure given in this file---replace each `TODO` with logic +implementing the missing piece. + +Here are a few more details about the design: + +**Parser** +* The parser has been extended support parsing of the source routed probe packets. +The parser is the most complicated part of the design so spend a bit of time +reading over it. Note that it does not contain any TODO comments so there is +nothing you need to change here. +* To parse the probe packets, we use the `hdr.probe.hop_cnt` to determine how many +hops the packet has traversed prior to reaching the switch. If this is the first +hop then there will not be any `probe_data` in the packet so we skip that state +and transition directly to the `parse_probe_fwd` state. In the `parse_probe_fwd` +state, we use the `hdr.probe.hop_cnt` field to figure out which `egress_spec` +header field to use to perform forwarding and we save that port value into a +metadata field which is subsequently used to perform forwarding. + +**Ingress Control** +* The ingress control block looks very similar to the `basic` exercise. The only +difference is that the `apply` block contains another condition to forward probe +packets using the `egress_spec` field extracted by the parser. It also increments +the `hdr.probe.hop_cnt` field. + +**Egress Control** +* This is where the interesting stateful processing occurs. It uses the +`byte_cnt_reg` register to count the number of bytes that have passed through each +port since the last probe packet passed through the port. +* It adds a new `probe_data` header to the packet and filld out the `bos` +(bottom of stack) field, as well as the `swid` (switch ID) field. +* TODO: your job is to fill out the rest of the probe packet fields in order to +ensure that you can properly measure link utilization. + +**Deparser** +* Simply emits all headers in the correct order. +* Note that emitting a header stack will only emit the headers within the stack +that are actually marked as valid. + +## Step 3: Run your solution + +Follow the instructions from Step 1. This time, the measured link +utilizations should agree with what `iperf` reports. + +### Troubleshooting + +There are several problems that might manifest as you develop your program: + +1. `link_monitor.p4` might fail to compile. In this case, `make run` will +report the error emitted from the compiler and halt. + +2. `link_monitor.p4` might compile but fail to support the control plane +rules in the `s1-runtime.json` through `s4-runtime.json` files that +`make run` tries to install using P4Runtime. In this case, `make run` will +report errors if control plane rules cannot be installed. Use these error +messages to fix your `link_monitor.p4` implementation. + +3. `link_monitor.p4` might compile, and the control plane rules might be +installed, but the switch might not process packets in the desired +way. The `logs/sX.log` files contain detailed logs that describing +how each switch processes each packet. The output is detailed and can +help pinpoint logic errors in your implementation. + +#### Cleaning up Mininet + +In the latter two cases above, `make run` may leave a Mininet instance +running in the background. Use the following command to clean up +these instances: + +```bash +make stop +``` + +### Food For Thought + +Now that you've implemented this basic monitoring framework can you +think of ways to leverage this information about link utilization +within the core of the network? For instance, how might you use this +data, either at the hosts or at the switches, to make real-time +load-balancing decisions? + diff --git a/exercises/link_monitor/link-monitor-topo.png b/exercises/link_monitor/link-monitor-topo.png new file mode 100644 index 000000000..e50ba7e92 Binary files /dev/null and b/exercises/link_monitor/link-monitor-topo.png differ diff --git a/exercises/link_monitor/link_monitor.p4 b/exercises/link_monitor/link_monitor.p4 new file mode 100644 index 000000000..580d6dcce --- /dev/null +++ b/exercises/link_monitor/link_monitor.p4 @@ -0,0 +1,299 @@ +/* -*- P4_16 -*- */ +#include +#include + +const bit<16> TYPE_IPV4 = 0x800; +const bit<16> TYPE_PROBE = 0x812; + +#define MAX_HOPS 10 +#define MAX_PORTS 8 + +/************************************************************************* +*********************** H E A D E R S *********************************** +*************************************************************************/ + +typedef bit<9> egressSpec_t; +typedef bit<48> macAddr_t; +typedef bit<32> ip4Addr_t; + +typedef bit<48> time_t; + +header ethernet_t { + macAddr_t dstAddr; + macAddr_t srcAddr; + bit<16> etherType; +} + +header ipv4_t { + bit<4> version; + bit<4> ihl; + bit<8> diffserv; + bit<16> totalLen; + bit<16> identification; + bit<3> flags; + bit<13> fragOffset; + bit<8> ttl; + bit<8> protocol; + bit<16> hdrChecksum; + ip4Addr_t srcAddr; + ip4Addr_t dstAddr; +} + +// Top-level probe header, indicates how many hops this probe +// packet has traversed so far. +header probe_t { + bit<8> hop_cnt; +} + +// The data added to the probe by each switch at each hop. +header probe_data_t { + bit<1> bos; + bit<7> swid; + bit<8> port; + bit<32> byte_cnt; + time_t last_time; + time_t cur_time; +} + +// Indicates the egress port the switch should send this probe +// packet out of. There is one of these headers for each hop. +header probe_fwd_t { + bit<8> egress_spec; +} + +struct parser_metadata_t { + bit<8> remaining; +} + +struct metadata { + bit<8> egress_spec; + parser_metadata_t parser_metadata; +} + +struct headers { + ethernet_t ethernet; + ipv4_t ipv4; + probe_t probe; + probe_data_t[MAX_HOPS] probe_data; + probe_fwd_t[MAX_HOPS] probe_fwd; +} + +/************************************************************************* +*********************** P A R S E R *********************************** +*************************************************************************/ + +parser MyParser(packet_in packet, + out headers hdr, + inout metadata meta, + inout standard_metadata_t standard_metadata) { + + state start { + transition parse_ethernet; + } + + state parse_ethernet { + packet.extract(hdr.ethernet); + transition select(hdr.ethernet.etherType) { + TYPE_IPV4: parse_ipv4; + TYPE_PROBE: parse_probe; + default: accept; + } + } + + state parse_ipv4 { + packet.extract(hdr.ipv4); + transition accept; + } + + state parse_probe { + packet.extract(hdr.probe); + meta.parser_metadata.remaining = hdr.probe.hop_cnt + 1; + transition select(hdr.probe.hop_cnt) { + 0: parse_probe_fwd; + default: parse_probe_data; + } + } + + state parse_probe_data { + packet.extract(hdr.probe_data.next); + transition select(hdr.probe_data.last.bos) { + 1: parse_probe_fwd; + default: parse_probe_data; + } + } + + state parse_probe_fwd { + packet.extract(hdr.probe_fwd.next); + meta.parser_metadata.remaining = meta.parser_metadata.remaining - 1; + // extract the forwarding data + meta.egress_spec = hdr.probe_fwd.last.egress_spec; + transition select(meta.parser_metadata.remaining) { + 0: accept; + default: parse_probe_fwd; + } + } +} + +/************************************************************************* +************ C H E C K S U M V E R I F I C A T I O N ************* +*************************************************************************/ + +control MyVerifyChecksum(inout headers hdr, inout metadata meta) { + apply { } +} + + +/************************************************************************* +************** I N G R E S S P R O C E S S I N G ******************* +*************************************************************************/ + +control MyIngress(inout headers hdr, + inout metadata meta, + inout standard_metadata_t standard_metadata) { + + action drop() { + mark_to_drop(standard_metadata); + } + + action ipv4_forward(macAddr_t dstAddr, egressSpec_t port) { + standard_metadata.egress_spec = port; + hdr.ethernet.srcAddr = hdr.ethernet.dstAddr; + hdr.ethernet.dstAddr = dstAddr; + hdr.ipv4.ttl = hdr.ipv4.ttl - 1; + } + + table ipv4_lpm { + key = { + hdr.ipv4.dstAddr: lpm; + } + actions = { + ipv4_forward; + drop; + NoAction; + } + size = 1024; + default_action = drop(); + } + + apply { + if (hdr.ipv4.isValid()) { + ipv4_lpm.apply(); + } + else if (hdr.probe.isValid()) { + standard_metadata.egress_spec = (bit<9>)meta.egress_spec; + hdr.probe.hop_cnt = hdr.probe.hop_cnt + 1; + } + } +} + +/************************************************************************* +**************** E G R E S S P R O C E S S I N G ******************** +*************************************************************************/ + +control MyEgress(inout headers hdr, + inout metadata meta, + inout standard_metadata_t standard_metadata) { + + // count the number of bytes seen since the last probe + register>(MAX_PORTS) byte_cnt_reg; + // remember the time of the last probe + register(MAX_PORTS) last_time_reg; + + action set_swid(bit<7> swid) { + hdr.probe_data[0].swid = swid; + } + + table swid { + actions = { + set_swid; + NoAction; + } + default_action = NoAction(); + } + + apply { + bit<32> byte_cnt; + bit<32> new_byte_cnt; + time_t last_time; + time_t cur_time = standard_metadata.egress_global_timestamp; + // increment byte cnt for this packet's port + byte_cnt_reg.read(byte_cnt, (bit<32>)standard_metadata.egress_port); + byte_cnt = byte_cnt + standard_metadata.packet_length; + // reset the byte count when a probe packet passes through + new_byte_cnt = (hdr.probe.isValid()) ? 0 : byte_cnt; + byte_cnt_reg.write((bit<32>)standard_metadata.egress_port, new_byte_cnt); + + if (hdr.probe.isValid()) { + // fill out probe fields + hdr.probe_data.push_front(1); + hdr.probe_data[0].setValid(); + if (hdr.probe.hop_cnt == 1) { + hdr.probe_data[0].bos = 1; + } + else { + hdr.probe_data[0].bos = 0; + } + // set switch ID field + swid.apply(); + // TODO: fill out the rest of the probe packet fields + // hdr.probe_data[0].port = ... + // hdr.probe_data[0].byte_cnt = ... + // TODO: read / update the last_time_reg + // last_time_reg.read(, ); + // last_time_reg.write(, ); + // hdr.probe_data[0].last_time = ... + // hdr.probe_data[0].cur_time = ... + } + } +} + +/************************************************************************* +************* C H E C K S U M C O M P U T A T I O N *************** +*************************************************************************/ + +control MyComputeChecksum(inout headers hdr, inout metadata meta) { + apply { + update_checksum( + hdr.ipv4.isValid(), + { hdr.ipv4.version, + hdr.ipv4.ihl, + hdr.ipv4.diffserv, + hdr.ipv4.totalLen, + hdr.ipv4.identification, + hdr.ipv4.flags, + hdr.ipv4.fragOffset, + hdr.ipv4.ttl, + hdr.ipv4.protocol, + hdr.ipv4.srcAddr, + hdr.ipv4.dstAddr }, + hdr.ipv4.hdrChecksum, + HashAlgorithm.csum16); + } +} + +/************************************************************************* +*********************** D E P A R S E R ******************************* +*************************************************************************/ + +control MyDeparser(packet_out packet, in headers hdr) { + apply { + packet.emit(hdr.ethernet); + packet.emit(hdr.ipv4); + packet.emit(hdr.probe); + packet.emit(hdr.probe_data); + packet.emit(hdr.probe_fwd); + } +} + +/************************************************************************* +*********************** S W I T C H ******************************* +*************************************************************************/ + +V1Switch( +MyParser(), +MyVerifyChecksum(), +MyIngress(), +MyEgress(), +MyComputeChecksum(), +MyDeparser() +) main; diff --git a/exercises/link_monitor/pod-topo/s1-runtime.json b/exercises/link_monitor/pod-topo/s1-runtime.json new file mode 100644 index 000000000..552202ce7 --- /dev/null +++ b/exercises/link_monitor/pod-topo/s1-runtime.json @@ -0,0 +1,65 @@ +{ + "target": "bmv2", + "p4info": "build/link_monitor.p4.p4info.txt", + "bmv2_json": "build/link_monitor.json", + "table_entries": [ + { + "table": "MyEgress.swid", + "default_action": true, + "action_name": "MyEgress.set_swid", + "action_params": { + "swid": 1 + } + }, + { + "table": "MyIngress.ipv4_lpm", + "default_action": true, + "action_name": "MyIngress.drop", + "action_params": { } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.1.1", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:01:11", + "port": 1 + } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.2.2", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:02:22", + "port": 2 + } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.3.3", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:03:00", + "port": 3 + } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.4.4", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:04:00", + "port": 4 + } + } + ] +} diff --git a/exercises/link_monitor/pod-topo/s2-runtime.json b/exercises/link_monitor/pod-topo/s2-runtime.json new file mode 100644 index 000000000..04285397a --- /dev/null +++ b/exercises/link_monitor/pod-topo/s2-runtime.json @@ -0,0 +1,65 @@ +{ + "target": "bmv2", + "p4info": "build/link_monitor.p4.p4info.txt", + "bmv2_json": "build/link_monitor.json", + "table_entries": [ + { + "table": "MyEgress.swid", + "default_action": true, + "action_name": "MyEgress.set_swid", + "action_params": { + "swid": 2 + } + }, + { + "table": "MyIngress.ipv4_lpm", + "default_action": true, + "action_name": "MyIngress.drop", + "action_params": { } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.1.1", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:03:00", + "port": 4 + } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.2.2", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:04:00", + "port": 3 + } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.3.3", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:03:33", + "port": 1 + } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.4.4", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:04:44", + "port": 2 + } + } + ] +} diff --git a/exercises/link_monitor/pod-topo/s3-runtime.json b/exercises/link_monitor/pod-topo/s3-runtime.json new file mode 100644 index 000000000..00c235979 --- /dev/null +++ b/exercises/link_monitor/pod-topo/s3-runtime.json @@ -0,0 +1,65 @@ +{ + "target": "bmv2", + "p4info": "build/link_monitor.p4.p4info.txt", + "bmv2_json": "build/link_monitor.json", + "table_entries": [ + { + "table": "MyEgress.swid", + "default_action": true, + "action_name": "MyEgress.set_swid", + "action_params": { + "swid": 3 + } + }, + { + "table": "MyIngress.ipv4_lpm", + "default_action": true, + "action_name": "MyIngress.drop", + "action_params": { } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.1.1", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:01:00", + "port": 1 + } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.2.2", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:01:00", + "port": 1 + } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.3.3", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:02:00", + "port": 2 + } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.4.4", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:02:00", + "port": 2 + } + } + ] +} diff --git a/exercises/link_monitor/pod-topo/s4-runtime.json b/exercises/link_monitor/pod-topo/s4-runtime.json new file mode 100644 index 000000000..0aaa3fbfd --- /dev/null +++ b/exercises/link_monitor/pod-topo/s4-runtime.json @@ -0,0 +1,65 @@ +{ + "target": "bmv2", + "p4info": "build/link_monitor.p4.p4info.txt", + "bmv2_json": "build/link_monitor.json", + "table_entries": [ + { + "table": "MyEgress.swid", + "default_action": true, + "action_name": "MyEgress.set_swid", + "action_params": { + "swid": 4 + } + }, + { + "table": "MyIngress.ipv4_lpm", + "default_action": true, + "action_name": "MyIngress.drop", + "action_params": { } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.1.1", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:01:00", + "port": 2 + } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.2.2", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:01:00", + "port": 2 + } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.3.3", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:02:00", + "port": 1 + } + }, + { + "table": "MyIngress.ipv4_lpm", + "match": { + "hdr.ipv4.dstAddr": ["10.0.4.4", 32] + }, + "action_name": "MyIngress.ipv4_forward", + "action_params": { + "dstAddr": "08:00:00:00:02:00", + "port": 1 + } + } + ] +} diff --git a/exercises/link_monitor/pod-topo/topology.json b/exercises/link_monitor/pod-topo/topology.json new file mode 100644 index 000000000..a963a87ad --- /dev/null +++ b/exercises/link_monitor/pod-topo/topology.json @@ -0,0 +1,26 @@ +{ + "hosts": { + "h1": {"ip": "10.0.1.1/24", "mac": "08:00:00:00:01:11", + "commands":["route add default gw 10.0.1.10 dev eth0", + "arp -i eth0 -s 10.0.1.10 08:00:00:00:01:00"]}, + "h2": {"ip": "10.0.2.2/24", "mac": "08:00:00:00:02:22", + "commands":["route add default gw 10.0.2.20 dev eth0", + "arp -i eth0 -s 10.0.2.20 08:00:00:00:02:00"]}, + "h3": {"ip": "10.0.3.3/24", "mac": "08:00:00:00:03:33", + "commands":["route add default gw 10.0.3.30 dev eth0", + "arp -i eth0 -s 10.0.3.30 08:00:00:00:03:00"]}, + "h4": {"ip": "10.0.4.4/24", "mac": "08:00:00:00:04:44", + "commands":["route add default gw 10.0.4.40 dev eth0", + "arp -i eth0 -s 10.0.4.40 08:00:00:00:04:00"]} + }, + "switches": { + "s1": { "runtime_json" : "pod-topo/s1-runtime.json" }, + "s2": { "runtime_json" : "pod-topo/s2-runtime.json" }, + "s3": { "runtime_json" : "pod-topo/s3-runtime.json" }, + "s4": { "runtime_json" : "pod-topo/s4-runtime.json" } + }, + "links": [ + ["h1", "s1-p1"], ["h2", "s1-p2"], ["s1-p3", "s3-p1"], ["s1-p4", "s4-p2"], + ["h3", "s2-p1"], ["h4", "s2-p2"], ["s2-p3", "s4-p1"], ["s2-p4", "s3-p2"] + ] +} diff --git a/exercises/link_monitor/probe_hdrs.py b/exercises/link_monitor/probe_hdrs.py new file mode 100755 index 000000000..59f606873 --- /dev/null +++ b/exercises/link_monitor/probe_hdrs.py @@ -0,0 +1,25 @@ +from scapy.all import * + +TYPE_PROBE = 0x812 + +class Probe(Packet): + fields_desc = [ ByteField("hop_cnt", 0)] + +class ProbeData(Packet): + fields_desc = [ BitField("bos", 0, 1), + BitField("swid", 0, 7), + ByteField("port", 0), + IntField("byte_cnt", 0), + BitField("last_time", 0, 48), + BitField("cur_time", 0, 48)] + +class ProbeFwd(Packet): + fields_desc = [ ByteField("egress_spec", 0)] + +bind_layers(Ether, Probe, type=TYPE_PROBE) +bind_layers(Probe, ProbeFwd, hop_cnt=0) +bind_layers(Probe, ProbeData) +bind_layers(ProbeData, ProbeData, bos=0) +bind_layers(ProbeData, ProbeFwd, bos=1) +bind_layers(ProbeFwd, ProbeFwd) + diff --git a/exercises/link_monitor/receive.py b/exercises/link_monitor/receive.py new file mode 100755 index 000000000..6162972d3 --- /dev/null +++ b/exercises/link_monitor/receive.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python + +from probe_hdrs import * + +def expand(x): + yield x + while x.payload: + x = x.payload + yield x + +def handle_pkt(pkt): + if ProbeData in pkt: + data_layers = [l for l in expand(pkt) if l.name=='ProbeData'] + print "" + for sw in data_layers: + utilization = 0 if sw.cur_time == sw.last_time else 8.0*sw.byte_cnt/(sw.cur_time - sw.last_time) + print "Switch {} - Port {}: {} Mbps".format(sw.swid, sw.port, utilization) + +def main(): + iface = 'eth0' + print "sniffing on {}".format(iface) + sniff(iface = iface, + prn = lambda x: handle_pkt(x)) + +if __name__ == '__main__': + main() diff --git a/exercises/link_monitor/send.py b/exercises/link_monitor/send.py new file mode 100755 index 000000000..d6fba03df --- /dev/null +++ b/exercises/link_monitor/send.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python +import sys +import time +from probe_hdrs import * + +def main(): + + probe_pkt = Ether(dst='ff:ff:ff:ff:ff:ff', src=get_if_hwaddr('eth0')) / \ + Probe(hop_cnt=0) / \ + ProbeFwd(egress_spec=4) / \ + ProbeFwd(egress_spec=1) / \ + ProbeFwd(egress_spec=4) / \ + ProbeFwd(egress_spec=1) / \ + ProbeFwd(egress_spec=3) / \ + ProbeFwd(egress_spec=2) / \ + ProbeFwd(egress_spec=3) / \ + ProbeFwd(egress_spec=2) / \ + ProbeFwd(egress_spec=1) + + while True: + try: + sendp(probe_pkt, iface='eth0') + time.sleep(1) + except KeyboardInterrupt: + sys.exit() + +if __name__ == '__main__': + main() diff --git a/exercises/link_monitor/solution/link_monitor.p4 b/exercises/link_monitor/solution/link_monitor.p4 new file mode 100644 index 000000000..ca5a507d1 --- /dev/null +++ b/exercises/link_monitor/solution/link_monitor.p4 @@ -0,0 +1,298 @@ +/* -*- P4_16 -*- */ +#include +#include + +const bit<16> TYPE_IPV4 = 0x800; +const bit<16> TYPE_PROBE = 0x812; + +#define MAX_HOPS 10 +#define MAX_PORTS 8 + +/************************************************************************* +*********************** H E A D E R S *********************************** +*************************************************************************/ + +typedef bit<9> egressSpec_t; +typedef bit<48> macAddr_t; +typedef bit<32> ip4Addr_t; + +typedef bit<48> time_t; + +header ethernet_t { + macAddr_t dstAddr; + macAddr_t srcAddr; + bit<16> etherType; +} + +header ipv4_t { + bit<4> version; + bit<4> ihl; + bit<8> diffserv; + bit<16> totalLen; + bit<16> identification; + bit<3> flags; + bit<13> fragOffset; + bit<8> ttl; + bit<8> protocol; + bit<16> hdrChecksum; + ip4Addr_t srcAddr; + ip4Addr_t dstAddr; +} + +// Top-level probe header, indicates how many hops this probe +// packet has traversed so far. +header probe_t { + bit<8> hop_cnt; +} + +// The data added to the probe by each switch at each hop. +header probe_data_t { + bit<1> bos; + bit<7> swid; + bit<8> port; + bit<32> byte_cnt; + time_t last_time; + time_t cur_time; +} + +// Indicates the egress port the switch should send this probe +// packet out of. There is one of these headers for each hop. +header probe_fwd_t { + bit<8> egress_spec; +} + +struct parser_metadata_t { + bit<8> remaining; +} + +struct metadata { + bit<8> egress_spec; + parser_metadata_t parser_metadata; +} + +struct headers { + ethernet_t ethernet; + ipv4_t ipv4; + probe_t probe; + probe_data_t[MAX_HOPS] probe_data; + probe_fwd_t[MAX_HOPS] probe_fwd; +} + +/************************************************************************* +*********************** P A R S E R *********************************** +*************************************************************************/ + +parser MyParser(packet_in packet, + out headers hdr, + inout metadata meta, + inout standard_metadata_t standard_metadata) { + + state start { + transition parse_ethernet; + } + + state parse_ethernet { + packet.extract(hdr.ethernet); + transition select(hdr.ethernet.etherType) { + TYPE_IPV4: parse_ipv4; + TYPE_PROBE: parse_probe; + default: accept; + } + } + + state parse_ipv4 { + packet.extract(hdr.ipv4); + transition accept; + } + + state parse_probe { + packet.extract(hdr.probe); + meta.parser_metadata.remaining = hdr.probe.hop_cnt + 1; + transition select(hdr.probe.hop_cnt) { + 0: parse_probe_fwd; + default: parse_probe_data; + } + } + + state parse_probe_data { + packet.extract(hdr.probe_data.next); + transition select(hdr.probe_data.last.bos) { + 1: parse_probe_fwd; + default: parse_probe_data; + } + } + + state parse_probe_fwd { + packet.extract(hdr.probe_fwd.next); + meta.parser_metadata.remaining = meta.parser_metadata.remaining - 1; + // extract the forwarding data + meta.egress_spec = hdr.probe_fwd.last.egress_spec; + transition select(meta.parser_metadata.remaining) { + 0: accept; + default: parse_probe_fwd; + } + } +} + +/************************************************************************* +************ C H E C K S U M V E R I F I C A T I O N ************* +*************************************************************************/ + +control MyVerifyChecksum(inout headers hdr, inout metadata meta) { + apply { } +} + + +/************************************************************************* +************** I N G R E S S P R O C E S S I N G ******************* +*************************************************************************/ + +control MyIngress(inout headers hdr, + inout metadata meta, + inout standard_metadata_t standard_metadata) { + + action drop() { + mark_to_drop(standard_metadata); + } + + action ipv4_forward(macAddr_t dstAddr, egressSpec_t port) { + standard_metadata.egress_spec = port; + hdr.ethernet.srcAddr = hdr.ethernet.dstAddr; + hdr.ethernet.dstAddr = dstAddr; + hdr.ipv4.ttl = hdr.ipv4.ttl - 1; + } + + table ipv4_lpm { + key = { + hdr.ipv4.dstAddr: lpm; + } + actions = { + ipv4_forward; + drop; + NoAction; + } + size = 1024; + default_action = drop(); + } + + apply { + if (hdr.ipv4.isValid()) { + ipv4_lpm.apply(); + } + else if (hdr.probe.isValid()) { + standard_metadata.egress_spec = (bit<9>)meta.egress_spec; + hdr.probe.hop_cnt = hdr.probe.hop_cnt + 1; + } + } +} + +/************************************************************************* +**************** E G R E S S P R O C E S S I N G ******************** +*************************************************************************/ + +control MyEgress(inout headers hdr, + inout metadata meta, + inout standard_metadata_t standard_metadata) { + + // count the number of bytes seen since the last probe + register>(MAX_PORTS) byte_cnt_reg; + // remember the time of the last probe + register(MAX_PORTS) last_time_reg; + + action set_swid(bit<7> swid) { + hdr.probe_data[0].swid = swid; + } + + table swid { + actions = { + set_swid; + NoAction; + } + default_action = NoAction(); + } + + apply { + bit<32> byte_cnt; + bit<32> new_byte_cnt; + time_t last_time; + time_t cur_time = standard_metadata.egress_global_timestamp; + // increment byte cnt for this packet's port + byte_cnt_reg.read(byte_cnt, (bit<32>)standard_metadata.egress_port); + byte_cnt = byte_cnt + standard_metadata.packet_length; + // reset the byte count when a probe packet passes through + new_byte_cnt = (hdr.probe.isValid()) ? 0 : byte_cnt; + byte_cnt_reg.write((bit<32>)standard_metadata.egress_port, new_byte_cnt); + + if (hdr.probe.isValid()) { + // fill out probe fields + hdr.probe_data.push_front(1); + hdr.probe_data[0].setValid(); + if (hdr.probe.hop_cnt == 1) { + hdr.probe_data[0].bos = 1; + } + else { + hdr.probe_data[0].bos = 0; + } + // set switch ID field + swid.apply(); + hdr.probe_data[0].port = (bit<8>)standard_metadata.egress_port; + hdr.probe_data[0].byte_cnt = byte_cnt; + // read / update the last_time_reg + last_time_reg.read(last_time, (bit<32>)standard_metadata.egress_port); + last_time_reg.write((bit<32>)standard_metadata.egress_port, cur_time); + hdr.probe_data[0].last_time = last_time; + hdr.probe_data[0].cur_time = cur_time; + } + } +} + +/************************************************************************* +************* C H E C K S U M C O M P U T A T I O N *************** +*************************************************************************/ + +control MyComputeChecksum(inout headers hdr, inout metadata meta) { + apply { + update_checksum( + hdr.ipv4.isValid(), + { hdr.ipv4.version, + hdr.ipv4.ihl, + hdr.ipv4.diffserv, + hdr.ipv4.totalLen, + hdr.ipv4.identification, + hdr.ipv4.flags, + hdr.ipv4.fragOffset, + hdr.ipv4.ttl, + hdr.ipv4.protocol, + hdr.ipv4.srcAddr, + hdr.ipv4.dstAddr }, + hdr.ipv4.hdrChecksum, + HashAlgorithm.csum16); + } +} + +/************************************************************************* +*********************** D E P A R S E R ******************************* +*************************************************************************/ + +control MyDeparser(packet_out packet, in headers hdr) { + apply { + packet.emit(hdr.ethernet); + packet.emit(hdr.ipv4); + packet.emit(hdr.probe); + packet.emit(hdr.probe_data); + packet.emit(hdr.probe_fwd); + } +} + +/************************************************************************* +*********************** S W I T C H ******************************* +*************************************************************************/ + +V1Switch( +MyParser(), +MyVerifyChecksum(), +MyIngress(), +MyEgress(), +MyComputeChecksum(), +MyDeparser() +) main; diff --git a/exercises/load_balance/Makefile b/exercises/load_balance/Makefile index cfbeb1652..23bdc5b99 100644 --- a/exercises/load_balance/Makefile +++ b/exercises/load_balance/Makefile @@ -1,5 +1,3 @@ BMV2_SWITCH_EXE = simple_switch_grpc -NO_P4 = true -P4C_ARGS = --p4runtime-files $(basename $@).p4.p4info.txt include ../../utils/Makefile diff --git a/exercises/load_balance/s2-runtime.json b/exercises/load_balance/s2-runtime.json index 0ac9046d5..f87527791 100644 --- a/exercises/load_balance/s2-runtime.json +++ b/exercises/load_balance/s2-runtime.json @@ -27,7 +27,7 @@ }, "action_name": "MyIngress.set_nhop", "action_params": { - "nhop_dmac": "00:00:00:00:02:02", + "nhop_dmac": "08:00:00:00:02:02", "nhop_ipv4": "10.0.2.2", "port" : 1 } diff --git a/exercises/load_balance/s3-runtime.json b/exercises/load_balance/s3-runtime.json index daad210aa..0b8e76581 100644 --- a/exercises/load_balance/s3-runtime.json +++ b/exercises/load_balance/s3-runtime.json @@ -27,7 +27,7 @@ }, "action_name": "MyIngress.set_nhop", "action_params": { - "nhop_dmac": "00:00:00:00:03:03", + "nhop_dmac": "08:00:00:00:03:03", "nhop_ipv4": "10.0.3.3", "port" : 1 } diff --git a/exercises/load_balance/topology.json b/exercises/load_balance/topology.json index e43e3c0c4..c45a2c77a 100644 --- a/exercises/load_balance/topology.json +++ b/exercises/load_balance/topology.json @@ -1,16 +1,22 @@ { - "hosts": [ - "h1", - "h2", - "h3" - ], + "hosts": { + "h1": {"ip": "10.0.1.1/24", "mac": "08:00:00:00:01:01", + "commands":["route add default gw 10.0.1.10 dev eth0", + "arp -i eth0 -s 10.0.1.10 08:00:00:00:01:00"]}, + "h2": {"ip": "10.0.2.2/24", "mac": "08:00:00:00:02:02", + "commands":["route add default gw 10.0.2.20 dev eth0", + "arp -i eth0 -s 10.0.2.20 08:00:00:00:02:00"]}, + "h3": {"ip": "10.0.3.3/24", "mac": "08:00:00:00:03:03", + "commands":["route add default gw 10.0.3.30 dev eth0", + "arp -i eth0 -s 10.0.3.30 08:00:00:00:03:00"]} + }, "switches": { "s1": { "runtime_json" : "s1-runtime.json" }, "s2": { "runtime_json" : "s2-runtime.json" }, "s3": { "runtime_json" : "s3-runtime.json" } }, "links": [ - ["h1", "s1"], ["s1", "s2"], ["s1", "s3"], - ["s3", "s2"], ["s2", "h2"], ["s3", "h3"] + ["h1", "s1-p1"], ["s1-p2", "s2-p2"], ["s1-p3", "s3-p2"], + ["s3-p3", "s2-p3"], ["h2", "s2-p1"], ["h3", "s3-p1"] ] } diff --git a/exercises/mri/Makefile b/exercises/mri/Makefile index cfbeb1652..23bdc5b99 100644 --- a/exercises/mri/Makefile +++ b/exercises/mri/Makefile @@ -1,5 +1,3 @@ BMV2_SWITCH_EXE = simple_switch_grpc -NO_P4 = true -P4C_ARGS = --p4runtime-files $(basename $@).p4.p4info.txt include ../../utils/Makefile diff --git a/exercises/mri/s1-runtime.json b/exercises/mri/s1-runtime.json index ed5651fa9..9c205728e 100644 --- a/exercises/mri/s1-runtime.json +++ b/exercises/mri/s1-runtime.json @@ -18,7 +18,7 @@ }, "action_name": "MyIngress.ipv4_forward", "action_params": { - "dstAddr": "00:00:00:00:01:01", + "dstAddr": "08:00:00:00:01:01", "port": 2 } }, @@ -29,7 +29,7 @@ }, "action_name": "MyIngress.ipv4_forward", "action_params": { - "dstAddr": "00:00:00:00:01:0b", + "dstAddr": "08:00:00:00:01:11", "port": 1 } }, @@ -41,7 +41,7 @@ }, "action_name": "MyIngress.ipv4_forward", "action_params": { - "dstAddr": "00:00:00:02:03:00", + "dstAddr": "08:00:00:00:02:00", "port": 3 } }, @@ -52,7 +52,7 @@ }, "action_name": "MyIngress.ipv4_forward", "action_params": { - "dstAddr": "00:00:00:03:02:00", + "dstAddr": "08:00:00:00:03:00", "port": 4 } } diff --git a/exercises/mri/s2-runtime.json b/exercises/mri/s2-runtime.json index 75a38d5be..4f976a3b7 100644 --- a/exercises/mri/s2-runtime.json +++ b/exercises/mri/s2-runtime.json @@ -18,7 +18,7 @@ }, "action_name": "MyIngress.ipv4_forward", "action_params": { - "dstAddr": "00:00:00:00:02:02", + "dstAddr": "08:00:00:00:02:02", "port": 2 } }, @@ -29,7 +29,7 @@ }, "action_name": "MyIngress.ipv4_forward", "action_params": { - "dstAddr": "00:00:00:00:02:16", + "dstAddr": "08:00:00:00:02:22", "port": 1 } }, @@ -40,7 +40,7 @@ }, "action_name": "MyIngress.ipv4_forward", "action_params": { - "dstAddr": "00:00:00:01:03:00", + "dstAddr": "08:00:00:00:01:00", "port": 3 } }, @@ -51,7 +51,7 @@ }, "action_name": "MyIngress.ipv4_forward", "action_params": { - "dstAddr": "00:00:00:03:03:00", + "dstAddr": "08:00:00:00:03:00", "port": 4 } } diff --git a/exercises/mri/s3-runtime.json b/exercises/mri/s3-runtime.json index 2b91bcae7..e5fd65231 100644 --- a/exercises/mri/s3-runtime.json +++ b/exercises/mri/s3-runtime.json @@ -18,7 +18,7 @@ }, "action_name": "MyIngress.ipv4_forward", "action_params": { - "dstAddr": "00:00:00:00:03:03", + "dstAddr": "08:00:00:00:03:03", "port": 1 } }, @@ -29,7 +29,7 @@ }, "action_name": "MyIngress.ipv4_forward", "action_params": { - "dstAddr": "00:00:00:01:04:00", + "dstAddr": "08:00:00:00:01:00", "port": 2 } }, @@ -40,7 +40,7 @@ }, "action_name": "MyIngress.ipv4_forward", "action_params": { - "dstAddr": "00:00:00:02:04:00", + "dstAddr": "08:00:00:00:02:00", "port": 3 } } diff --git a/exercises/mri/topology.json b/exercises/mri/topology.json index 29df53e35..0e53e92d3 100644 --- a/exercises/mri/topology.json +++ b/exercises/mri/topology.json @@ -1,18 +1,28 @@ { - "hosts": [ - "h1", - "h2", - "h3", - "h11", - "h22" - ], + "hosts": { + "h1": {"ip": "10.0.1.1/31", "mac": "08:00:00:00:01:01", + "commands":["route add default gw 10.0.1.0 dev eth0", + "arp -i eth0 -s 10.0.1.0 08:00:00:00:01:00"]}, + "h11": {"ip": "10.0.1.11/31", "mac": "08:00:00:00:01:11", + "commands":["route add default gw 10.0.1.10 dev eth0", + "arp -i eth0 -s 10.0.1.10 08:00:00:00:01:00"]}, + "h2": {"ip": "10.0.2.2/31", "mac": "08:00:00:00:02:02", + "commands":["route add default gw 10.0.2.3 dev eth0", + "arp -i eth0 -s 10.0.2.3 08:00:00:00:02:00"]}, + "h22": {"ip": "10.0.2.22/31", "mac": "08:00:00:00:02:22", + "commands":["route add default gw 10.0.2.23 dev eth0", + "arp -i eth0 -s 10.0.2.23 08:00:00:00:02:00"]}, + "h3": {"ip": "10.0.3.3/31", "mac": "08:00:00:00:03:03", + "commands":["route add default gw 10.0.3.2 dev eth0", + "arp -i eth0 -s 10.0.3.2 08:00:00:00:03:00"]} + }, "switches": { "s1": { "runtime_json" : "s1-runtime.json" }, "s2": { "runtime_json" : "s2-runtime.json" }, "s3": { "runtime_json" : "s3-runtime.json" } }, "links": [ - ["h1", "s1"], ["h11", "s1"], ["s1", "s2", "0", 0.5], ["s1", "s3"], - ["s3", "s2"], ["s2", "h2"], ["s2", "h22"], ["s3", "h3"] + ["h1", "s1-p2"], ["h11", "s1-p1"], ["s1-p3", "s2-p3", "0", 0.5], ["s1-p4", "s3-p2"], + ["s3-p3", "s2-p4"], ["h2", "s2-p2"], ["h22", "s2-p1"], ["h3", "s3-p1"] ] } diff --git a/exercises/p4runtime/Makefile b/exercises/p4runtime/Makefile index cfbeb1652..23bdc5b99 100644 --- a/exercises/p4runtime/Makefile +++ b/exercises/p4runtime/Makefile @@ -1,5 +1,3 @@ BMV2_SWITCH_EXE = simple_switch_grpc -NO_P4 = true -P4C_ARGS = --p4runtime-files $(basename $@).p4.p4info.txt include ../../utils/Makefile diff --git a/exercises/p4runtime/mycontroller.py b/exercises/p4runtime/mycontroller.py index 866d66897..8932cade3 100755 --- a/exercises/p4runtime/mycontroller.py +++ b/exercises/p4runtime/mycontroller.py @@ -160,11 +160,11 @@ def main(p4info_file_path, bmv2_file_path): # Write the rules that tunnel traffic from h1 to h2 writeTunnelRules(p4info_helper, ingress_sw=s1, egress_sw=s2, tunnel_id=100, - dst_eth_addr="00:00:00:00:02:02", dst_ip_addr="10.0.2.2") + dst_eth_addr="08:00:00:00:02:22", dst_ip_addr="10.0.2.2") # Write the rules that tunnel traffic from h2 to h1 writeTunnelRules(p4info_helper, ingress_sw=s2, egress_sw=s1, tunnel_id=200, - dst_eth_addr="00:00:00:00:01:01", dst_ip_addr="10.0.1.1") + dst_eth_addr="08:00:00:00:01:11", dst_ip_addr="10.0.1.1") # TODO Uncomment the following two lines to read table entries from s1 and s2 # readTableRules(p4info_helper, s1) diff --git a/exercises/p4runtime/solution/mycontroller.py b/exercises/p4runtime/solution/mycontroller.py index 1add68aa5..5b6b61fce 100755 --- a/exercises/p4runtime/solution/mycontroller.py +++ b/exercises/p4runtime/solution/mycontroller.py @@ -185,11 +185,11 @@ def main(p4info_file_path, bmv2_file_path): # Write the rules that tunnel traffic from h1 to h2 writeTunnelRules(p4info_helper, ingress_sw=s1, egress_sw=s2, tunnel_id=100, - dst_eth_addr="00:00:00:00:02:02", dst_ip_addr="10.0.2.2") + dst_eth_addr="08:00:00:00:02:22", dst_ip_addr="10.0.2.2") # Write the rules that tunnel traffic from h2 to h1 writeTunnelRules(p4info_helper, ingress_sw=s2, egress_sw=s1, tunnel_id=200, - dst_eth_addr="00:00:00:00:01:01", dst_ip_addr="10.0.1.1") + dst_eth_addr="08:00:00:00:01:11", dst_ip_addr="10.0.1.1") # TODO Uncomment the following two lines to read table entries from s1 and s2 readTableRules(p4info_helper, s1) diff --git a/exercises/p4runtime/topology.json b/exercises/p4runtime/topology.json index f8a5b48c5..74cbd4b14 100755 --- a/exercises/p4runtime/topology.json +++ b/exercises/p4runtime/topology.json @@ -1,16 +1,22 @@ { - "hosts": [ - "h1", - "h2", - "h3" - ], + "hosts": { + "h1": {"ip": "10.0.1.1/24", "mac": "08:00:00:00:01:11", + "commands":["route add default gw 10.0.1.10 dev eth0", + "arp -i eth0 -s 10.0.1.10 08:00:00:00:01:00"]}, + "h2": {"ip": "10.0.2.2/24", "mac": "08:00:00:00:02:22", + "commands":["route add default gw 10.0.2.20 dev eth0", + "arp -i eth0 -s 10.0.2.20 08:00:00:00:02:00"]}, + "h3": {"ip": "10.0.3.3/24", "mac": "08:00:00:00:03:33", + "commands":["route add default gw 10.0.3.30 dev eth0", + "arp -i eth0 -s 10.0.3.30 08:00:00:00:03:00"]} + }, "switches": { "s1": {}, "s2": {}, "s3": {} }, "links": [ - ["h1", "s1"], ["s1", "s2"], ["s1", "s3"], - ["s3", "s2"], ["s2", "h2"], ["s3", "h3"] + ["h1", "s1-p1"], ["s1-p2", "s2-p2"], ["s1-p3", "s3-p2"], + ["s3-p3", "s2-p3"], ["h2", "s2-p1"], ["h3", "s3-p1"] ] } diff --git a/exercises/source_routing/Makefile b/exercises/source_routing/Makefile index cfbeb1652..23bdc5b99 100644 --- a/exercises/source_routing/Makefile +++ b/exercises/source_routing/Makefile @@ -1,5 +1,3 @@ BMV2_SWITCH_EXE = simple_switch_grpc -NO_P4 = true -P4C_ARGS = --p4runtime-files $(basename $@).p4.p4info.txt include ../../utils/Makefile diff --git a/exercises/source_routing/receive.py b/exercises/source_routing/receive.py index efb93d691..049d3467c 100755 --- a/exercises/source_routing/receive.py +++ b/exercises/source_routing/receive.py @@ -49,7 +49,7 @@ class SourceRoutingTail(Packet): bind_layers(SourceRoute, SourceRoutingTail, bos=1) def main(): - iface = 'h2-eth0' + iface = 'eth0' print "sniffing on %s" % iface sys.stdout.flush() sniff(filter="udp and port 4321", iface = iface, diff --git a/exercises/source_routing/topology.json b/exercises/source_routing/topology.json index e43e3c0c4..2193ad59c 100644 --- a/exercises/source_routing/topology.json +++ b/exercises/source_routing/topology.json @@ -1,16 +1,22 @@ { - "hosts": [ - "h1", - "h2", - "h3" - ], + "hosts": { + "h1": {"ip": "10.0.1.1/24", "mac": "08:00:00:00:01:11", + "commands":["route add default gw 10.0.1.10 dev eth0", + "arp -i eth0 -s 10.0.1.10 08:00:00:00:01:00"]}, + "h2": {"ip": "10.0.2.2/24", "mac": "08:00:00:00:02:22", + "commands":["route add default gw 10.0.2.20 dev eth0", + "arp -i eth0 -s 10.0.2.20 08:00:00:00:02:00"]}, + "h3": {"ip": "10.0.3.3/24", "mac": "08:00:00:00:03:33", + "commands":["route add default gw 10.0.3.30 dev eth0", + "arp -i eth0 -s 10.0.3.30 08:00:00:00:03:00"]} + }, "switches": { "s1": { "runtime_json" : "s1-runtime.json" }, "s2": { "runtime_json" : "s2-runtime.json" }, "s3": { "runtime_json" : "s3-runtime.json" } }, "links": [ - ["h1", "s1"], ["s1", "s2"], ["s1", "s3"], - ["s3", "s2"], ["s2", "h2"], ["s3", "h3"] + ["h1", "s1-p1"], ["s1-p2", "s2-p2"], ["s1-p3", "s3-p2"], + ["s3-p3", "s2-p3"], ["h2", "s2-p1"], ["h3", "s3-p1"] ] } diff --git a/utils/Makefile b/utils/Makefile index 99e44fb3a..2846b612b 100644 --- a/utils/Makefile +++ b/utils/Makefile @@ -2,18 +2,26 @@ BUILD_DIR = build PCAP_DIR = pcaps LOG_DIR = logs -TOPO = topology.json P4C = p4c-bm2-ss +P4C_ARGS += --p4runtime-files $(BUILD_DIR)/$(basename $@).p4.p4info.txt + RUN_SCRIPT = ../../utils/run_exercise.py -source := $(wildcard *.p4) -outfile := $(source:.p4=.json) +ifndef TOPO +TOPO = topology.json +endif + +source = $(wildcard *.p4) +compiled_json := $(source:.p4=.json) -compiled_json := $(BUILD_DIR)/$(outfile) +ifndef DEFAULT_PROG +DEFAULT_PROG = $(wildcard *.p4) +endif +DEFAULT_JSON = $(BUILD_DIR)/$(DEFAULT_PROG:.p4=.json) # Define NO_P4 to start BMv2 without a program ifndef NO_P4 -run_args += -j $(compiled_json) +run_args += -j $(DEFAULT_JSON) endif # Set BMV2_SWITCH_EXE to override the BMv2 target @@ -31,8 +39,8 @@ stop: build: dirs $(compiled_json) -$(BUILD_DIR)/%.json: %.p4 - $(P4C) --p4v 16 $(P4C_ARGS) -o $@ $< +%.json: %.p4 + $(P4C) --p4v 16 $(P4C_ARGS) -o $(BUILD_DIR)/$@ $< dirs: mkdir -p $(BUILD_DIR) $(PCAP_DIR) $(LOG_DIR) diff --git a/utils/p4runtime_switch.py b/utils/p4runtime_switch.py index a3b8bcf3f..1f89373f2 100644 --- a/utils/p4runtime_switch.py +++ b/utils/p4runtime_switch.py @@ -48,7 +48,7 @@ def __init__(self, name, sw_path = None, json_path = None, if json_path is not None: # make sure that the provided JSON file exists if not os.path.isfile(json_path): - error("Invalid JSON file.\n") + error("Invalid JSON file: {}\n".format(json_path)) exit(1) self.json_path = json_path else: diff --git a/utils/run_exercise.py b/utils/run_exercise.py index dfaf8fd92..f44068978 100755 --- a/utils/run_exercise.py +++ b/utils/run_exercise.py @@ -66,65 +66,57 @@ def describe(self): class ExerciseTopo(Topo): """ The mininet topology class for the P4 tutorial exercises. - A custom class is used because the exercises make a few topology - assumptions, mostly about the IP and MAC addresses. """ - def __init__(self, hosts, switches, links, log_dir, **opts): + def __init__(self, hosts, switches, links, log_dir, bmv2_exe, pcap_dir, **opts): Topo.__init__(self, **opts) host_links = [] switch_links = [] - self.sw_port_mapping = {} + # assumes host always comes first for host<-->switch links for link in links: if link['node1'][0] == 'h': host_links.append(link) else: switch_links.append(link) - link_sort_key = lambda x: x['node1'] + x['node2'] - # Links must be added in a sorted order so bmv2 port numbers are predictable - host_links.sort(key=link_sort_key) - switch_links.sort(key=link_sort_key) - - for sw in switches: - self.addSwitch(sw, log_file="%s/%s.log" %(log_dir, sw)) + for sw, params in switches.iteritems(): + if "program" in params: + switchClass = configureP4Switch( + sw_path=bmv2_exe, + json_path=params["program"], + log_console=True, + pcap_dump=pcap_dir) + else: + # add default switch + switchClass = None + self.addSwitch(sw, log_file="%s/%s.log" %(log_dir, sw), cls=switchClass) for link in host_links: host_name = link['node1'] - host_sw = link['node2'] - host_num = int(host_name[1:]) - sw_num = int(host_sw[1:]) - host_ip = "10.0.%d.%d" % (sw_num, host_num) - host_mac = '00:00:00:00:%02x:%02x' % (sw_num, host_num) - # Each host IP should be /24, so all exercise traffic will use the - # default gateway (the switch) without sending ARP requests. - self.addHost(host_name, ip=host_ip+'/24', mac=host_mac) - self.addLink(host_name, host_sw, + sw_name, sw_port = self.parse_switch_node(link['node2']) + host_ip = hosts[host_name]['ip'] + host_mac = hosts[host_name]['mac'] + self.addHost(host_name, ip=host_ip, mac=host_mac) + self.addLink(host_name, sw_name, delay=link['latency'], bw=link['bandwidth'], - addr1=host_mac, addr2=host_mac) - self.addSwitchPort(host_sw, host_name) + port2=sw_port) for link in switch_links: - self.addLink(link['node1'], link['node2'], + sw1_name, sw1_port = self.parse_switch_node(link['node1']) + sw2_name, sw2_port = self.parse_switch_node(link['node2']) + self.addLink(sw1_name, sw2_name, + port1=sw1_port, port2=sw2_port, delay=link['latency'], bw=link['bandwidth']) - self.addSwitchPort(link['node1'], link['node2']) - self.addSwitchPort(link['node2'], link['node1']) - self.printPortMapping() - def addSwitchPort(self, sw, node2): - if sw not in self.sw_port_mapping: - self.sw_port_mapping[sw] = [] - portno = len(self.sw_port_mapping[sw])+1 - self.sw_port_mapping[sw].append((portno, node2)) - - def printPortMapping(self): - print "Switch port mapping:" - for sw in sorted(self.sw_port_mapping.keys()): - print "%s: " % sw, - for portno, node2 in self.sw_port_mapping[sw]: - print "%d:%s\t" % (portno, node2), - print + def parse_switch_node(self, node): + assert(len(node.split('-')) == 2) + sw_name, sw_port = node.split('-') + try: + sw_port = int(sw_port[1]) + except: + raise Exception('Invalid switch node in topology file: {}'.format(node)) + return sw_name, sw_port class ExerciseRunner: @@ -134,8 +126,8 @@ class ExerciseRunner: pcap_dir : string // directory for mininet switch pcap files quiet : bool // determines if we print logger messages - hosts : list // list of mininet host names - switches : dict // mininet host names and their associated properties + hosts : dict // mininet host names and their associated properties + switches : dict // mininet switch names and their associated properties links : list // list of mininet link properties switch_json : string // json of the compiled p4 example @@ -149,7 +141,7 @@ def logger(self, *items): if not self.quiet: print(' '.join(items)) - def formatLatency(self, l): + def format_latency(self, l): """ Helper method for parsing link latencies from the topology json. """ if isinstance(l, (str, unicode)): return l @@ -232,7 +224,7 @@ def parse_links(self, unparsed_links): 'bandwidth':None } if len(link) > 2: - link_dict['latency'] = self.formatLatency(link[2]) + link_dict['latency'] = self.format_latency(link[2]) if len(link) > 3: link_dict['bandwidth'] = link[3] @@ -251,18 +243,18 @@ def create_network(self): """ self.logger("Building mininet topology.") - self.topo = ExerciseTopo(self.hosts, self.switches.keys(), self.links, self.log_dir) + defaultSwitchClass = configureP4Switch( + sw_path=self.bmv2_exe, + json_path=self.switch_json, + log_console=True, + pcap_dump=self.pcap_dir) - switchClass = configureP4Switch( - sw_path=self.bmv2_exe, - json_path=self.switch_json, - log_console=True, - pcap_dump=self.pcap_dir) + self.topo = ExerciseTopo(self.hosts, self.switches, self.links, self.log_dir, self.bmv2_exe, self.pcap_dir) self.net = Mininet(topo = self.topo, link = TCLink, host = P4Host, - switch = switchClass, + switch = defaultSwitchClass, controller = None) def program_switch_p4runtime(self, sw_name, sw_dict): @@ -312,30 +304,13 @@ def program_switches(self): self.program_switch_p4runtime(sw_name, sw_dict) def program_hosts(self): - """ Adds static ARP entries and default routes to each mininet host. - - Assumes: - - A mininet instance is stored as self.net and self.net.start() has - been called. + """ Execute any commands provided in the topology.json file on each Mininet host """ - for host_name in self.topo.hosts(): + for host_name, host_info in self.hosts.items(): h = self.net.get(host_name) - h_iface = h.intfs.values()[0] - link = h_iface.link - - sw_iface = link.intf1 if link.intf1 != h_iface else link.intf2 - # phony IP to lie to the host about - host_id = int(host_name[1:]) - sw_ip = '10.0.%d.254' % host_id - - # Ensure each host's interface name is unique, or else - # mininet cannot shutdown gracefully - h.defaultIntf().rename('%s-eth0' % host_name) - # static arp entries and default routes - h.cmd('arp -i %s -s %s %s' % (h_iface.name, sw_ip, sw_iface.mac)) - h.cmd('ethtool --offload %s rx off tx off' % h_iface.name) - h.cmd('ip route add %s dev %s' % (sw_ip, h_iface.name)) - h.setDefaultRoute("via %s" % sw_ip) + if "commands" in host_info: + for cmd in host_info["commands"]: + h.cmd(cmd) def do_net_cli(self): diff --git a/vm/root-bootstrap.sh b/vm/root-bootstrap.sh index 01e92d282..99d433d82 100755 --- a/vm/root-bootstrap.sh +++ b/vm/root-bootstrap.sh @@ -73,10 +73,10 @@ chmod 440 /etc/sudoers.d/99_p4 usermod -aG vboxsf p4 cd /usr/share/lubuntu/wallpapers/ -cp ~/p4-logo.png . +cp /home/vagrant/p4-logo.png . rm lubuntu-default-wallpaper.png ln -s p4-logo.png lubuntu-default-wallpaper.png -rm ~/p4-logo.png +rm /home/vagrant/p4-logo.png cd ~ sed -i s@#background=@background=/usr/share/lubuntu/wallpapers/1604-lubuntu-default-wallpaper.png@ /etc/lightdm/lightdm-gtk-greeter.conf diff --git a/vm/user-bootstrap.sh b/vm/user-bootstrap.sh index ec818aa66..421cd1d86 100755 --- a/vm/user-bootstrap.sh +++ b/vm/user-bootstrap.sh @@ -4,9 +4,9 @@ set -xe #Src -BMV2_COMMIT="884e01b531c6fd078cc2438a40258ecae011a65b" # Apr 24, 2019 -PI_COMMIT="19de33e83bae7b737a3f8a1c9507c6e84173d96f" # Apr 24, 2019 -P4C_COMMIT="61409c890c58d14ec7d6790f263eb44f393e542a" # Apr 24, 2019 +BMV2_COMMIT="b447ac4c0cfd83e5e72a3cc6120251c1e91128ab" # August 10, 2019 +PI_COMMIT="41358da0ff32c94fa13179b9cee0ab597c9ccbcc" # August 10, 2019 +P4C_COMMIT="69e132d0d663e3408d740aaf8ed534ecefc88810" # August 10, 2019 PROTOBUF_COMMIT="v3.2.0" GRPC_COMMIT="v1.3.2" @@ -112,6 +112,8 @@ sudo pip install crcmod git clone https://github.com/p4lang/tutorials sudo mv tutorials /home/p4 sudo chown -R p4:p4 /home/p4/tutorials +# Install grip for offline markdown rendering +sudo pip install grip # --- Emacs --- # sudo cp p4_16-mode.el /usr/share/emacs/site-lisp/