diff --git a/P4D2_2018_East/P4_tutorial_labs.pdf b/P4D2_2018_East/P4_tutorial_labs.pdf new file mode 100644 index 0000000..687663c Binary files /dev/null and b/P4D2_2018_East/P4_tutorial_labs.pdf differ diff --git a/P4D2_2018_East/exercises/README.md b/P4D2_2018_East/exercises/README.md new file mode 100644 index 0000000..6e95c09 --- /dev/null +++ b/P4D2_2018_East/exercises/README.md @@ -0,0 +1,49 @@ +# P4 Tutorial + +## Introduction + +Welcome to the P4 Tutorial! + +We've prepared a set of exercises to help you get started with P4 +programming, organized into four modules: + +1. Introduction and Language Basics +* [Basic Forwarding](./basic) +* [Basic Tunneling](./basic_tunnel) + +2. P4 Runtime and the Control Plane +* [P4 Runtime](./p4runtime) + +3. Monitoring and Debugging +* [Explicit Congestion Notification](./ecn) +* [Multi-Hop Route Inspection](./mri) + +4. Advanced Data Structures +* [Source Routing](./source_routing) +* [Calculator](./calc) + +5. Dynamic Behavior +* [Load Balancing](./load_balance) + +## Obtaining required software + +If you are starting this tutorial at the Fall 2017 P4 Developer Day, then we've already +provided you with a virtual machine that has all of the required +software installed. + +Otherwise, to complete the exercises, you will need to either build a +virtual machine or install several dependencies. + +To build the virtual machine: +- Install [Vagrant](https://vagrantup.com) and [VirtualBox](https://virtualbox.org) +- `cd vm` +- `vagrant up` +- Log in with username `p4` and password `p4` and issue the command `sudo shutdown -r now` +- When the machine reboots, you should have a graphical desktop machine with the required +software pre-installed. + +To install dependencies by hand, please reference the [vm](../vm) installation scripts. +They contain the dependencies, versions, and installation procedure. +You can run them directly on an Ubuntu 16.04 machine: +- `sudo ./root-bootstrap.sh` +- `sudo ./user-bootstrap.sh` diff --git a/P4D2_2018_East/exercises/basic/Makefile b/P4D2_2018_East/exercises/basic/Makefile new file mode 100644 index 0000000..f378756 --- /dev/null +++ b/P4D2_2018_East/exercises/basic/Makefile @@ -0,0 +1 @@ +include ../../utils/Makefile diff --git a/P4D2_2018_East/exercises/basic/README.md b/P4D2_2018_East/exercises/basic/README.md new file mode 100644 index 0000000..70a7e95 --- /dev/null +++ b/P4D2_2018_East/exercises/basic/README.md @@ -0,0 +1,173 @@ +# Implementing Basic Forwarding + +## Introduction + +The objective of this exercise is to write a P4 program that +implements basic forwarding. To keep things simple, we will just +implement forwarding for IPv4. + +With IPv4 forwarding, the switch must perform the following actions +for every packet: (i) update the source and destination MAC addresses, +(ii) decrement the time-to-live (TTL) in the IP header, and (iii) +forward the packet out the appropriate port. + +Your switch will have a single table, which the control plane will +populate with static rules. Each rule will map an IP address to the +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. + +> **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, +`basic.p4`, which initially drops all packets. Your job will be to +extend this skeleton program to properly forward IPv4 packets. + +Before that, let's compile the incomplete `basic.p4` and bring +up a switch in Mininet to test its behavior. + +1. In your shell, run: + ```bash + make run + ``` + 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`. + +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: + ```bash + ./receive.py + ``` +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. + Then, to stop mininet: + ```bash + make stop + ``` + And to delete all pcaps, build files, and logs: + ```bash + make clean + ``` + +The message was not received 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. + +### 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 run` command will install packet-processing rules in the tables of +each switch. These are defined in the `sX-commands.txt` files, where +`X` corresponds to the switch number. + +**Important:** A P4 program also defines the interface between the +switch pipeline and control plane. The commands in the files +`sX-commands.txt` refer to specific tables, keys, and actions by name, +and any changes in the P4 program that add or rename tables, keys, or +actions will need to be reflected in these command files. + +## Step 2: Implement L3 forwarding + +The `basic.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. + +A complete `basic.p4` will contain the following components: + +1. Header type definitions for Ethernet (`ethernet_t`) and IPv4 (`ipv4_t`). +2. **TODO:** Parsers for Ethernet and IPv4 that populate `ethernet_t` and `ipv4_t` fields. +3. An action to drop a packet, using `mark_to_drop()`. +4. **TODO:** An action (called `ipv4_forward`) that: + 1. Sets the egress port for the next hop. + 2. Updates the ethernet destination address with the address of the next hop. + 3. Updates the ethernet source address with the address of the switch. + 4. Decrements the TTL. +5. **TODO:** A control that: + 1. Defines a table that will read an IPv4 destination address, and + invoke either `drop` or `ipv4_forward`. + 2. An `apply` block that applies the table. +6. **TODO:** A deparser that selects the order + in which fields inserted into the outgoing packet. +7. 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, your message from +`h1` should be delivered to `h2`. + +### 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? + +> 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 support next hops? + - Is this program enough to replace a router? What's missing? + +### Troubleshooting + +There are several problems that might manifest as you develop your program: + +1. `basic.p4` might fail to compile. In this case, `make run` will +report the error emitted from the compiler and halt. + +2. `basic.p4` might compile but fail to support the control plane +rules in the `s1-commands.txt` through `s3-command.txt` files that +`make run` tries to install using the Bmv2 CLI. In this case, `make run` +will log the CLI tool output in the `logs` directory. Use these error +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 +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 +``` + +## 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/P4D2_2018_East/exercises/basic/basic.p4 b/P4D2_2018_East/exercises/basic/basic.p4 new file mode 100644 index 0000000..8b89237 --- /dev/null +++ b/P4D2_2018_East/exercises/basic/basic.p4 @@ -0,0 +1,162 @@ +/* -*- 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 { + /* TODO: add parser logic */ + 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(); + } + + action ipv4_forward(macAddr_t dstAddr, egressSpec_t port) { + /* TODO: fill out code in action body */ + } + + table ipv4_lpm { + key = { + hdr.ipv4.dstAddr: lpm; + } + actions = { + ipv4_forward; + drop; + NoAction; + } + size = 1024; + default_action = NoAction(); + } + + apply { + /* TODO: fix ingress control logic + * - ipv4_lpm should be applied only when IPv4 header is valid + */ + 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 { + /* TODO: add deparser logic */ + } +} + +/************************************************************************* +*********************** S W I T C H ******************************* +*************************************************************************/ + +V1Switch( +MyParser(), +MyVerifyChecksum(), +MyIngress(), +MyEgress(), +MyComputeChecksum(), +MyDeparser() +) main; diff --git a/P4D2_2018_East/exercises/basic/receive.py b/P4D2_2018_East/exercises/basic/receive.py new file mode 100755 index 0000000..f729682 --- /dev/null +++ b/P4D2_2018_East/exercises/basic/receive.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python +import sys +import struct +import os + +from scapy.all import sniff, sendp, hexdump, get_if_list, get_if_hwaddr +from scapy.all import Packet, IPOption +from scapy.all import ShortField, IntField, LongField, BitField, FieldListField, FieldLenField +from scapy.all import IP, TCP, UDP, Raw +from scapy.layers.inet import _IPOption_HDR + +def get_if(): + ifs=get_if_list() + iface=None + for i in get_if_list(): + if "eth0" in i: + iface=i + break; + if not iface: + print "Cannot find eth0 interface" + exit(1) + return iface + +class IPOption_MRI(IPOption): + name = "MRI" + option = 31 + fields_desc = [ _IPOption_HDR, + FieldLenField("length", None, fmt="B", + length_of="swids", + adjust=lambda pkt,l:l+4), + ShortField("count", 0), + FieldListField("swids", + [], + IntField("", 0), + length_from=lambda pkt:pkt.count*4) ] +def handle_pkt(pkt): + if TCP in pkt and pkt[TCP].dport == 1234: + print "got a packet" + pkt.show2() + # hexdump(pkt) + sys.stdout.flush() + + +def main(): + ifaces = filter(lambda i: 'eth' in i, os.listdir('/sys/class/net/')) + iface = ifaces[0] + print "sniffing on %s" % iface + sys.stdout.flush() + sniff(iface = iface, + prn = lambda x: handle_pkt(x)) + +if __name__ == '__main__': + main() diff --git a/P4D2_2018_East/exercises/basic/s1-commands.txt b/P4D2_2018_East/exercises/basic/s1-commands.txt new file mode 100644 index 0000000..3bfdf61 --- /dev/null +++ b/P4D2_2018_East/exercises/basic/s1-commands.txt @@ -0,0 +1,4 @@ +table_set_default ipv4_lpm drop +table_add ipv4_lpm ipv4_forward 10.0.1.1/32 => 00:00:00:00:01:01 1 +table_add ipv4_lpm ipv4_forward 10.0.2.2/32 => 00:00:00:02:02:00 2 +table_add ipv4_lpm ipv4_forward 10.0.3.3/32 => 00:00:00:03:03:00 3 diff --git a/P4D2_2018_East/exercises/basic/s2-commands.txt b/P4D2_2018_East/exercises/basic/s2-commands.txt new file mode 100644 index 0000000..35a49d7 --- /dev/null +++ b/P4D2_2018_East/exercises/basic/s2-commands.txt @@ -0,0 +1,4 @@ +table_set_default ipv4_lpm drop +table_add ipv4_lpm ipv4_forward 10.0.1.1/32 => 00:00:00:01:02:00 2 +table_add ipv4_lpm ipv4_forward 10.0.2.2/32 => 00:00:00:00:02:02 1 +table_add ipv4_lpm ipv4_forward 10.0.3.3/32 => 00:00:00:03:03:00 3 diff --git a/P4D2_2018_East/exercises/basic/s3-commands.txt b/P4D2_2018_East/exercises/basic/s3-commands.txt new file mode 100644 index 0000000..c111108 --- /dev/null +++ b/P4D2_2018_East/exercises/basic/s3-commands.txt @@ -0,0 +1,4 @@ +table_set_default ipv4_lpm drop +table_add ipv4_lpm ipv4_forward 10.0.1.1/32 => 00:00:00:01:03:00 2 +table_add ipv4_lpm ipv4_forward 10.0.2.2/32 => 00:00:00:02:03:00 3 +table_add ipv4_lpm ipv4_forward 10.0.3.3/32 => 00:00:00:00:03:03 1 diff --git a/P4D2_2018_East/exercises/basic/send.py b/P4D2_2018_East/exercises/basic/send.py new file mode 100755 index 0000000..00496d9 --- /dev/null +++ b/P4D2_2018_East/exercises/basic/send.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +import argparse +import sys +import socket +import random +import struct + +from scapy.all import sendp, send, get_if_list, get_if_hwaddr +from scapy.all import Packet +from scapy.all import Ether, IP, UDP, TCP + +def get_if(): + ifs=get_if_list() + iface=None # "h1-eth0" + for i in get_if_list(): + if "eth0" in i: + iface=i + break; + if not iface: + print "Cannot find eth0 interface" + exit(1) + return iface + +def main(): + + if len(sys.argv)<3: + print 'pass 2 arguments: ""' + exit(1) + + addr = socket.gethostbyname(sys.argv[1]) + iface = get_if() + + print "sending on interface %s to %s" % (iface, str(addr)) + pkt = Ether(src=get_if_hwaddr(iface), dst='ff:ff:ff:ff:ff:ff') + pkt = pkt /IP(dst=addr) / TCP(dport=1234, sport=random.randint(49152,65535)) / sys.argv[2] + pkt.show2() + sendp(pkt, iface=iface, verbose=False) + + +if __name__ == '__main__': + main() diff --git a/P4D2_2018_East/exercises/basic/solution/basic.p4 b/P4D2_2018_East/exercises/basic/solution/basic.p4 new file mode 100644 index 0000000..7d491ea --- /dev/null +++ b/P4D2_2018_East/exercises/basic/solution/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(); + } + + 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 = NoAction(); + } + + 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/P4D2_2018_East/exercises/basic/topology.json b/P4D2_2018_East/exercises/basic/topology.json new file mode 100644 index 0000000..e33477c --- /dev/null +++ b/P4D2_2018_East/exercises/basic/topology.json @@ -0,0 +1,16 @@ +{ + "hosts": [ + "h1", + "h2", + "h3" + ], + "switches": { + "s1": { "cli_input" : "s1-commands.txt" }, + "s2": { "cli_input" : "s2-commands.txt" }, + "s3": { "cli_input" : "s3-commands.txt" } + }, + "links": [ + ["h1", "s1"], ["s1", "s2"], ["s1", "s3"], + ["s3", "s2"], ["s2", "h2"], ["s3", "h3"] + ] +} diff --git a/P4D2_2018_East/exercises/basic_tunnel/Makefile b/P4D2_2018_East/exercises/basic_tunnel/Makefile new file mode 100644 index 0000000..f378756 --- /dev/null +++ b/P4D2_2018_East/exercises/basic_tunnel/Makefile @@ -0,0 +1 @@ +include ../../utils/Makefile diff --git a/P4D2_2018_East/exercises/basic_tunnel/README.md b/P4D2_2018_East/exercises/basic_tunnel/README.md new file mode 100644 index 0000000..d3c21ed --- /dev/null +++ b/P4D2_2018_East/exercises/basic_tunnel/README.md @@ -0,0 +1,166 @@ +# Implementing Basic Tunneling + +## Introduction + +In this exercise, we will add support for a basic tunneling protocol +to the IP router that you completed in the previous assignment. To do so, +we will define a new header type to encapsulate the IP packet and +modify the switch to perform routing using our new header. + +The new header type will contain a protocol ID, which indicates the type +of packet being encapsulated, along with a destination ID to be used for +routing. + +> **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 starter code for this assignment is in a file called `basic_tunnel.p4` +and is simply the solution to the IP router from the previous exercise. + +Let's first compile this code to and send a packet between two end hosts +to ensure that the IP routing is working as expected. + +1. In your shell, run: + ```bash + make run + ``` + This will: + * compile `basic_tunnel.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`. + +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: + ```bash + ./receive.py + ``` +4. In `h1`'s xterm, send a message to `h2`: + ```bash + ./send.py 10.0.2.2 "P4 is cool" + ``` + The packet should be received at `h2`. If you examine the received + packet you should see that is consists of an Ethernet header, an IP + header, a TCP header, and the message. If you change the destination IP address + (e.g. try to send to `10.0.3.3`) then the message should not be + received by h2. +5. Type `exit` or `Ctrl-D` to leave each xterm and the Mininet command line. + +Each switch is forwarding based on the destination IP address. Your +job is to change the switch functionality so that they instead decide +the destination port using our new tunnel header. + +### 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. + +For this exercise, we have already added the necessary static control +plane entries. 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-commands.txt` files, +where `X` corresponds to the switch number. + +**Important:** A P4 program also defines the interface between the +switch pipeline and control plane. The commands in the files +`sX-commands.txt` refer to specific tables, keys, and actions by name, +and any changes in the P4 program that add or rename tables, keys, or +actions will need to be reflected in these command files. + +## Step 2: Implement Basic Tunneling + +The `basic_tunnel.p4` file contains an implementation of a basic IP router. +It also contains comments marked with `TODO` which indicate the functionality +that you need to implement. A complete implementation of the `basic_tunnel.p4` +switch will be able to forward based on the contents of a custom encapsulation +header as well as perform normal IP forwarding if the encapsulation header +does not exist in the packet. + +Your job will be to do the following: + +1. **NOTE:** A new header type has been added called `myTunnel_t` that contains two 16-bit fields: `proto_id` and `dst_id`. +2. **NOTE:** The `myTunnel_t` header has been added to the `headers` struct. +2. **TODO:** Update the parser to extract either the `myTunnel` header or `ipv4` header based on the `etherType` field in the Ethernet header. The etherType corresponding to the myTunnel header is `0x1212`. The parser should also extract the `ipv4` header after the `myTunnel` header if `proto_id` == `TYPE_IPV4` (i.e. 0x0800). +3. **TODO:** Define a new action called `myTunnel_forward` that simply sets the egress port (i.e. `egress_spec` field of the `standard_metadata` bus) to the port number provided by the control plane. +4. **TODO:** Define a new table called `myTunnel_exact` that perfoms an exact match on the `dst_id` field of the `myTunnel` header. This table should invoke either the `myTunnel_forward` action if the there is a match in the table and it should invoke the `drop` action otherwise. +5. **TODO:** Update the `apply` statement in the `MyIngress` control block to apply your newly defined `myTunnel_exact` table if the `myTunnel` header is valid. Otherwise, invoke the `ipv4_lpm` table if the `ipv4` header is valid. +6. **TODO:** Update the deparser to emit the `ethernet`, then `myTunnel`, then `ipv4` headers. Remember that the deparser will only emit a header if it is valid. A header's implicit validity bit is set by the parser upon extraction. So there is no need to check header validity here. +7. **TODO:** Add static rules for your newly defined table so that the switches will forward correctly for each possible value of `dst_id`. See the diagram below for the topology's port configuration as well as how we will assign IDs to hosts. For this step you will need to add your forwarding rules to the `sX-commands.txt` files. + +![topology](./topo.png) + +## Step 3: Run your solution + +Follow the instructions from Step 1. This time when you send a packet from +`h1` to `h2` try using the following command to send a packet that uses +our new `myTunnel` header. +```bash +./send.py 10.0.2.2 "P4 is cool" --dst_id 2 +``` + +You should see a packet arrive at `h2` which contains the `MyTunnel` header. +Also note that changing the destination IP address will not prevent the packet +from arriving at `h2`. This is because the switch is no longer using the IP header for routing when the `MyTunnel` header is in the packet. + +> Python Scapy does not natively support the `myTunnel` header +> type so we have provided a file called `myTunnel_header.py` which +> adds support to Scapy for our new custom header. Feel free to inspect +> this file if you are interested in learning how to do this. + +### Food for thought + +To make this tunneling exercise a bit more interesting (and realistic) +how might you change the P4 code to have the switches add the `myTunnel` +header to an IP packet upon ingress to the network and then remove the +`myTunnel` header as the packet leaves to the network to an end host? + +Hints: + + - The ingress switch will need to map the destination IP address to the corresponding `dst_id` for the `myTunnel` header. Also remember to set explicitly set the validity bit for the `myTunnel` header so that it can be emitted by the deparser. + - The egress switch will need to remove the `myTunnel` header from the packet after looking up the appropriate output port using the `dst_id` field. + +### Troubleshooting + +There are several problems that might manifest as you develop your program: + +1. `basic_tunnel.p4` might fail to compile. In this case, `make run` will +report the error emitted from the compiler and halt. + +2. `basic_tunnel.p4` might compile but fail to support the control plane +rules in the `s1-commands.txt` through `s3-command.txt` files that +`make run` tries to install using the Bmv2 CLI. In this case, `make run` +will log the CLI tool output in the `logs` directory. Use these error +messages to fix your `basic_tunnel.p4` implementation or forwarding rules. + +3. `basic_tunnel.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 +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` may leave a Mininet instance +running in the background. Use the following command to clean up +these instances: + +```bash +make stop +``` + +## Next Steps + +Congratulations, your implementation works! Move onto the next assignment +[p4runtime](../p4runtime)! + diff --git a/P4D2_2018_East/exercises/basic_tunnel/basic_tunnel.p4 b/P4D2_2018_East/exercises/basic_tunnel/basic_tunnel.p4 new file mode 100644 index 0000000..156c077 --- /dev/null +++ b/P4D2_2018_East/exercises/basic_tunnel/basic_tunnel.p4 @@ -0,0 +1,197 @@ +/* -*- P4_16 -*- */ +#include +#include + +// NOTE: new type added here +const bit<16> TYPE_MYTUNNEL = 0x1212; +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; +} + +// NOTE: added new header type +header myTunnel_t { + bit<16> proto_id; + bit<16> dst_id; +} + +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 */ +} + +// NOTE: Added new header type to headers struct +struct headers { + ethernet_t ethernet; + myTunnel_t myTunnel; + ipv4_t ipv4; +} + +/************************************************************************* +*********************** P A R S E R *********************************** +*************************************************************************/ + +// TODO: Update the parser to parse the myTunnel header as well +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(); + } + + 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 = NoAction(); + } + + // TODO: declare a new action: myTunnel_forward(egressSpec_t port) + + + // TODO: declare a new table: myTunnel_exact + // TODO: also remember to add table entries! + + + apply { + // TODO: Update control flow + 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); + // TODO: emit myTunnel header as well + packet.emit(hdr.ipv4); + } +} + +/************************************************************************* +*********************** S W I T C H ******************************* +*************************************************************************/ + +V1Switch( +MyParser(), +MyVerifyChecksum(), +MyIngress(), +MyEgress(), +MyComputeChecksum(), +MyDeparser() +) main; diff --git a/P4D2_2018_East/exercises/basic_tunnel/myTunnel_header.py b/P4D2_2018_East/exercises/basic_tunnel/myTunnel_header.py new file mode 100644 index 0000000..5a5922e --- /dev/null +++ b/P4D2_2018_East/exercises/basic_tunnel/myTunnel_header.py @@ -0,0 +1,20 @@ + +from scapy.all import * +import sys, os + +TYPE_MYTUNNEL = 0x1212 +TYPE_IPV4 = 0x0800 + +class MyTunnel(Packet): + name = "MyTunnel" + fields_desc = [ + ShortField("pid", 0), + ShortField("dst_id", 0) + ] + def mysummary(self): + return self.sprintf("pid=%pid%, dst_id=%dst_id%") + + +bind_layers(Ether, MyTunnel, type=TYPE_MYTUNNEL) +bind_layers(MyTunnel, IP, pid=TYPE_IPV4) + diff --git a/P4D2_2018_East/exercises/basic_tunnel/receive.py b/P4D2_2018_East/exercises/basic_tunnel/receive.py new file mode 100755 index 0000000..167a9c8 --- /dev/null +++ b/P4D2_2018_East/exercises/basic_tunnel/receive.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python +import sys +import struct +import os + +from scapy.all import sniff, sendp, hexdump, get_if_list, get_if_hwaddr +from scapy.all import Packet, IPOption +from scapy.all import ShortField, IntField, LongField, BitField, FieldListField, FieldLenField +from scapy.all import IP, TCP, UDP, Raw +from scapy.layers.inet import _IPOption_HDR +from myTunnel_header import MyTunnel + +def get_if(): + ifs=get_if_list() + iface=None + for i in get_if_list(): + if "eth0" in i: + iface=i + break; + if not iface: + print "Cannot find eth0 interface" + exit(1) + return iface + +def handle_pkt(pkt): + if MyTunnel in pkt or (TCP in pkt and pkt[TCP].dport == 1234): + print "got a packet" + pkt.show2() +# hexdump(pkt) +# print "len(pkt) = ", len(pkt) + sys.stdout.flush() + + +def main(): + ifaces = filter(lambda i: 'eth' in i, os.listdir('/sys/class/net/')) + iface = ifaces[0] + print "sniffing on %s" % iface + sys.stdout.flush() + sniff(iface = iface, + prn = lambda x: handle_pkt(x)) + +if __name__ == '__main__': + main() diff --git a/P4D2_2018_East/exercises/basic_tunnel/s1-commands.txt b/P4D2_2018_East/exercises/basic_tunnel/s1-commands.txt new file mode 100644 index 0000000..179d7a5 --- /dev/null +++ b/P4D2_2018_East/exercises/basic_tunnel/s1-commands.txt @@ -0,0 +1,9 @@ +table_set_default ipv4_lpm drop +table_add ipv4_lpm ipv4_forward 10.0.1.1/32 => 00:00:00:00:01:01 1 +table_add ipv4_lpm ipv4_forward 10.0.2.2/32 => 00:00:00:02:01:00 2 +table_add ipv4_lpm ipv4_forward 10.0.3.3/32 => 00:00:00:03:01:00 3 + +table_set_default myTunnel_exact drop +table_add myTunnel_exact myTunnel_forward 1 => 1 +table_add myTunnel_exact myTunnel_forward 2 => 2 +table_add myTunnel_exact myTunnel_forward 3 => 3 diff --git a/P4D2_2018_East/exercises/basic_tunnel/s2-commands.txt b/P4D2_2018_East/exercises/basic_tunnel/s2-commands.txt new file mode 100644 index 0000000..fb5a2bc --- /dev/null +++ b/P4D2_2018_East/exercises/basic_tunnel/s2-commands.txt @@ -0,0 +1,9 @@ +table_set_default ipv4_lpm drop +table_add ipv4_lpm ipv4_forward 10.0.1.1/32 => 00:00:00:01:02:00 2 +table_add ipv4_lpm ipv4_forward 10.0.2.2/32 => 00:00:00:00:02:02 1 +table_add ipv4_lpm ipv4_forward 10.0.3.3/32 => 00:00:00:03:02:00 3 + +table_set_default myTunnel_exact drop +table_add myTunnel_exact myTunnel_forward 1 => 2 +table_add myTunnel_exact myTunnel_forward 2 => 1 +table_add myTunnel_exact myTunnel_forward 3 => 3 diff --git a/P4D2_2018_East/exercises/basic_tunnel/s3-commands.txt b/P4D2_2018_East/exercises/basic_tunnel/s3-commands.txt new file mode 100644 index 0000000..a6c2777 --- /dev/null +++ b/P4D2_2018_East/exercises/basic_tunnel/s3-commands.txt @@ -0,0 +1,9 @@ +table_set_default ipv4_lpm drop +table_add ipv4_lpm ipv4_forward 10.0.1.1/32 => 00:00:00:01:03:00 2 +table_add ipv4_lpm ipv4_forward 10.0.2.2/32 => 00:00:00:02:03:00 3 +table_add ipv4_lpm ipv4_forward 10.0.3.3/32 => 00:00:00:00:03:03 1 + +table_set_default myTunnel_exact drop +table_add myTunnel_exact myTunnel_forward 1 => 2 +table_add myTunnel_exact myTunnel_forward 2 => 3 +table_add myTunnel_exact myTunnel_forward 3 => 1 diff --git a/P4D2_2018_East/exercises/basic_tunnel/send.py b/P4D2_2018_East/exercises/basic_tunnel/send.py new file mode 100755 index 0000000..4f9828c --- /dev/null +++ b/P4D2_2018_East/exercises/basic_tunnel/send.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python +import argparse +import sys +import socket +import random +import struct +import argparse + +from scapy.all import sendp, send, get_if_list, get_if_hwaddr, hexdump +from scapy.all import Packet +from scapy.all import Ether, IP, UDP, TCP +from myTunnel_header import MyTunnel + +def get_if(): + ifs=get_if_list() + iface=None # "h1-eth0" + for i in get_if_list(): + if "eth0" in i: + iface=i + break; + if not iface: + print "Cannot find eth0 interface" + exit(1) + return iface + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('ip_addr', type=str, help="The destination IP address to use") + parser.add_argument('message', type=str, help="The message to include in packet") + parser.add_argument('--dst_id', type=int, default=None, help='The myTunnel dst_id to use, if unspecified then myTunnel header will not be included in packet') + args = parser.parse_args() + + addr = socket.gethostbyname(args.ip_addr) + dst_id = args.dst_id + iface = get_if() + + if (dst_id is not None): + print "sending on interface {} to dst_id {}".format(iface, str(dst_id)) + pkt = Ether(src=get_if_hwaddr(iface), dst='ff:ff:ff:ff:ff:ff') + pkt = pkt / MyTunnel(dst_id=dst_id) / IP(dst=addr) / args.message + else: + print "sending on interface {} to IP addr {}".format(iface, str(addr)) + pkt = Ether(src=get_if_hwaddr(iface), dst='ff:ff:ff:ff:ff:ff') + pkt = pkt / IP(dst=addr) / TCP(dport=1234, sport=random.randint(49152,65535)) / args.message + + pkt.show2() +# hexdump(pkt) +# print "len(pkt) = ", len(pkt) + sendp(pkt, iface=iface, verbose=False) + + +if __name__ == '__main__': + main() diff --git a/P4D2_2018_East/exercises/basic_tunnel/solution/basic_tunnel.p4 b/P4D2_2018_East/exercises/basic_tunnel/solution/basic_tunnel.p4 new file mode 100644 index 0000000..83b29db --- /dev/null +++ b/P4D2_2018_East/exercises/basic_tunnel/solution/basic_tunnel.p4 @@ -0,0 +1,215 @@ +/* -*- P4_16 -*- */ +#include +#include + +const bit<16> TYPE_MYTUNNEL = 0x1212; +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 myTunnel_t { + bit<16> proto_id; + bit<16> dst_id; +} + +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; + myTunnel_t myTunnel; + 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_MYTUNNEL: parse_myTunnel; + TYPE_IPV4: parse_ipv4; + default: accept; + } + } + + state parse_myTunnel { + packet.extract(hdr.myTunnel); + transition select(hdr.myTunnel.proto_id) { + 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(); + } + + 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 = NoAction(); + } + + action myTunnel_forward(egressSpec_t port) { + standard_metadata.egress_spec = port; + } + + table myTunnel_exact { + key = { + hdr.myTunnel.dst_id: exact; + } + actions = { + myTunnel_forward; + drop; + } + size = 1024; + default_action = drop(); + } + + apply { + if (hdr.ipv4.isValid() && !hdr.myTunnel.isValid()) { + // Process only non-tunneled IPv4 packets + ipv4_lpm.apply(); + } + + if (hdr.myTunnel.isValid()) { + // process tunneled packets + myTunnel_exact.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.myTunnel); + packet.emit(hdr.ipv4); + } +} + +/************************************************************************* +*********************** S W I T C H ******************************* +*************************************************************************/ + +V1Switch( +MyParser(), +MyVerifyChecksum(), +MyIngress(), +MyEgress(), +MyComputeChecksum(), +MyDeparser() +) main; diff --git a/P4D2_2018_East/exercises/basic_tunnel/topo.pdf b/P4D2_2018_East/exercises/basic_tunnel/topo.pdf new file mode 100644 index 0000000..111acd6 Binary files /dev/null and b/P4D2_2018_East/exercises/basic_tunnel/topo.pdf differ diff --git a/P4D2_2018_East/exercises/basic_tunnel/topo.png b/P4D2_2018_East/exercises/basic_tunnel/topo.png new file mode 100644 index 0000000..12ff49e Binary files /dev/null and b/P4D2_2018_East/exercises/basic_tunnel/topo.png differ diff --git a/P4D2_2018_East/exercises/basic_tunnel/topology.json b/P4D2_2018_East/exercises/basic_tunnel/topology.json new file mode 100644 index 0000000..e33477c --- /dev/null +++ b/P4D2_2018_East/exercises/basic_tunnel/topology.json @@ -0,0 +1,16 @@ +{ + "hosts": [ + "h1", + "h2", + "h3" + ], + "switches": { + "s1": { "cli_input" : "s1-commands.txt" }, + "s2": { "cli_input" : "s2-commands.txt" }, + "s3": { "cli_input" : "s3-commands.txt" } + }, + "links": [ + ["h1", "s1"], ["s1", "s2"], ["s1", "s3"], + ["s3", "s2"], ["s2", "h2"], ["s3", "h3"] + ] +} diff --git a/P4D2_2018_East/exercises/calc/Makefile b/P4D2_2018_East/exercises/calc/Makefile new file mode 100644 index 0000000..f378756 --- /dev/null +++ b/P4D2_2018_East/exercises/calc/Makefile @@ -0,0 +1 @@ +include ../../utils/Makefile diff --git a/P4D2_2018_East/exercises/calc/README.md b/P4D2_2018_East/exercises/calc/README.md new file mode 100644 index 0000000..4b6d92b --- /dev/null +++ b/P4D2_2018_East/exercises/calc/README.md @@ -0,0 +1,112 @@ +# Implementing a P4 Calculator + +## Introduction + +The objective of this tutorial is to implement a basic calculator +using a custom protocol header written in P4. The header will contain +an operation to perform and two operands. When a switch receives a +calculator packet header, it will execute the operation on the +operands, and return the result to the sender. + +## Step 1: Run the (incomplete) starter code + +The directory with this README also contains a skeleton P4 program, +`calc.p4`, which initially drops all packets. Your job will be to +extend it to properly implement the calculator logic. + +As a first step, compile the incomplete `calc.p4` and bring up a +switch in Mininet to test its behavior. + +1. In your shell, run: + ```bash + make + ``` + This will: + * compile `calc.p4`, and + + * start a Mininet instance with one switches (`s1`) connected to + two hosts (`h1`, `h2`). + * The hosts are assigned IPs of `10.0.1.1` and `10.0.1.2`. + +2. We've written a small Python-based driver program that will allow +you to test your calculator. You can run the driver program directly +from the Mininet command prompt: + +``` +mininet> h1 python calc.py +> +``` + +3. The driver program will provide a new prompt, at which you can type +basic expressions. The test harness will parse your expression, and +prepare a packet with the corresponding operator and operands. It will +then send a packet to the switch for evaluation. When the switch +returns the result of the computation, the test program will print the +result. However, because the calculator program is not implemented, +you should see an error message. + +``` +> 1+1 +Didn't receive response +> +``` + +## Step 2: Implement Calculator + +To implement the calculator, you will need to define a custom +calculator header, and implement the switch logic to parse header, +perform the requested operation, write the result in the header, and +return the packet to the sender. + +We will use the following header format: + + 0 1 2 3 + +----------------+----------------+----------------+---------------+ + | P | 4 | Version | Op | + +----------------+----------------+----------------+---------------+ + | Operand A | + +----------------+----------------+----------------+---------------+ + | Operand B | + +----------------+----------------+----------------+---------------+ + | Result | + +----------------+----------------+----------------+---------------+ + + +- P is an ASCII Letter 'P' (0x50) +- 4 is an ASCII Letter '4' (0x34) +- Version is currently 0.1 (0x01) +- Op is an operation to Perform: + - '+' (0x2b) Result = OperandA + OperandB + - '-' (0x2d) Result = OperandA - OperandB + - '&' (0x26) Result = OperandA & OperandB + - '|' (0x7c) Result = OperandA | OperandB + - '^' (0x5e) Result = OperandA ^ OperandB + + +We will assume that the calculator header is carried over Ethernet, +and we will use the Ethernet type 0x1234 to indicate the presence of +the header. + +Given what you have learned so far, your task is to implement the P4 +calculator program. There is no control plane logic, so you need only +worry about the data plane implementation. + +A working calculator implementation will parse the custom headers, +execute the mathematical operation, write the result in the result +field, and return the packet to the sender. + +## Step 3: Run your solution + +Follow the instructions from Step 1. This time, you should see the +correct result: + +``` +> 1+1 +2 +> +``` + +## Next Steps + +Congratulations, your implementation works! Move on to +[Load Balancer](../load_balance). diff --git a/P4D2_2018_East/exercises/calc/calc.p4 b/P4D2_2018_East/exercises/calc/calc.p4 new file mode 100644 index 0000000..557cbc5 --- /dev/null +++ b/P4D2_2018_East/exercises/calc/calc.p4 @@ -0,0 +1,250 @@ +/* -*- P4_16 -*- */ + +/* + * P4 Calculator + * + * This program implements a simple protocol. It can be carried over Ethernet + * (Ethertype 0x1234). + * + * The Protocol header looks like this: + * + * 0 1 2 3 + * +----------------+----------------+----------------+---------------+ + * | P | 4 | Version | Op | + * +----------------+----------------+----------------+---------------+ + * | Operand A | + * +----------------+----------------+----------------+---------------+ + * | Operand B | + * +----------------+----------------+----------------+---------------+ + * | Result | + * +----------------+----------------+----------------+---------------+ + * + * P is an ASCII Letter 'P' (0x50) + * 4 is an ASCII Letter '4' (0x34) + * Version is currently 0.1 (0x01) + * Op is an operation to Perform: + * '+' (0x2b) Result = OperandA + OperandB + * '-' (0x2d) Result = OperandA - OperandB + * '&' (0x26) Result = OperandA & OperandB + * '|' (0x7c) Result = OperandA | OperandB + * '^' (0x5e) Result = OperandA ^ OperandB + * + * The device receives a packet, performs the requested operation, fills in the + * result and sends the packet back out of the same port it came in on, while + * swapping the source and destination addresses. + * + * If an unknown operation is specified or the header is not valid, the packet + * is dropped + */ + +#include +#include + +/* + * Define the headers the program will recognize + */ + +/* + * Standard Ethernet header + */ +header ethernet_t { + bit<48> dstAddr; + bit<48> srcAddr; + bit<16> etherType; +} + +/* + * This is a custom protocol header for the calculator. We'll use + * etherType 0x1234 for it (see parser) + */ +const bit<16> P4CALC_ETYPE = 0x1234; +const bit<8> P4CALC_P = 0x50; // 'P' +const bit<8> P4CALC_4 = 0x34; // '4' +const bit<8> P4CALC_VER = 0x01; // v0.1 +const bit<8> P4CALC_PLUS = 0x2b; // '+' +const bit<8> P4CALC_MINUS = 0x2d; // '-' +const bit<8> P4CALC_AND = 0x26; // '&' +const bit<8> P4CALC_OR = 0x7c; // '|' +const bit<8> P4CALC_CARET = 0x5e; // '^' + +header p4calc_t { + bit<8> op; +/* TODO + * fill p4calc_t header with P, four, ver, op, operand_a, operand_b, and res + entries based on above protocol header definition. + */ +} + +/* + * All headers, used in the program needs to be assembled into a single struct. + * We only need to declare the type, but there is no need to instantiate it, + * because it is done "by the architecture", i.e. outside of P4 functions + */ +struct headers { + ethernet_t ethernet; + p4calc_t p4calc; +} + +/* + * All metadata, globally used in the program, also needs to be assembled + * into a single struct. As in the case of the headers, we only need to + * declare the type, but there is no need to instantiate it, + * because it is done "by the architecture", i.e. outside of P4 functions + */ + +struct metadata { + /* In our case it is empty */ +} + +/************************************************************************* + *********************** 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 { + packet.extract(hdr.ethernet); + transition select(hdr.ethernet.etherType) { + P4CALC_ETYPE : check_p4calc; + default : accept; + } + } + + state check_p4calc { + /* TODO: just uncomment the following parse block */ + /* + transition select(packet.lookahead().p, + packet.lookahead().four, + packet.lookahead().ver) { + (P4CALC_P, P4CALC_4, P4CALC_VER) : parse_p4calc; + default : accept; + } + */ + } + + state parse_p4calc { + packet.extract(hdr.p4calc); + 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 send_back(bit<32> result) { + /* TODO + * - put the result back in hdr.p4calc.res + * - swap MAC addresses in hdr.ethernet.dstAddr and + * hdr.ethernet.srcAddr using a temp variable + * - Send the packet back to the port it came from + by saving standard_metadata.ingress_port into + standard_metadata.egress_spec + */ + } + + action operation_add() { + /* TODO call send_back with operand_a + operand_b */ + } + + action operation_sub() { + /* TODO call send_back with operand_a - operand_b */ + } + + action operation_and() { + /* TODO call send_back with operand_a & operand_b */ + } + + action operation_or() { + /* TODO call send_back with operand_a | operand_b */ + } + + action operation_xor() { + /* TODO call send_back with operand_a ^ operand_b */ + } + + action operation_drop() { + mark_to_drop(); + } + + table calculate { + key = { + hdr.p4calc.op : exact; + } + actions = { + operation_add; + operation_sub; + operation_and; + operation_or; + operation_xor; + operation_drop; + } + const default_action = operation_drop(); + const entries = { + P4CALC_PLUS : operation_add(); + P4CALC_MINUS: operation_sub(); + P4CALC_AND : operation_and(); + P4CALC_OR : operation_or(); + P4CALC_CARET: operation_xor(); + } + } + + apply { + if (hdr.p4calc.isValid()) { + calculate.apply(); + } else { + operation_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 { } +} + +/************************************************************************* + *********************** D E P A R S E R ******************************* + *************************************************************************/ +control MyDeparser(packet_out packet, in headers hdr) { + apply { + packet.emit(hdr.ethernet); + packet.emit(hdr.p4calc); + } +} + +/************************************************************************* + *********************** S W I T T C H ********************************** + *************************************************************************/ + +V1Switch( +MyParser(), +MyVerifyChecksum(), +MyIngress(), +MyEgress(), +MyComputeChecksum(), +MyDeparser() +) main; diff --git a/P4D2_2018_East/exercises/calc/calc.py b/P4D2_2018_East/exercises/calc/calc.py new file mode 100755 index 0000000..d2fb035 --- /dev/null +++ b/P4D2_2018_East/exercises/calc/calc.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python + +import argparse +import sys +import socket +import random +import struct +import re + +from scapy.all import sendp, send, srp1 +from scapy.all import Packet, hexdump +from scapy.all import Ether, StrFixedLenField, XByteField, IntField +from scapy.all import bind_layers +import readline + +class P4calc(Packet): + name = "P4calc" + fields_desc = [ StrFixedLenField("P", "P", length=1), + StrFixedLenField("Four", "4", length=1), + XByteField("version", 0x01), + StrFixedLenField("op", "+", length=1), + IntField("operand_a", 0), + IntField("operand_b", 0), + IntField("result", 0xDEADBABE)] + +bind_layers(Ether, P4calc, type=0x1234) + +class NumParseError(Exception): + pass + +class OpParseError(Exception): + pass + +class Token: + def __init__(self,type,value = None): + self.type = type + self.value = value + +def num_parser(s, i, ts): + pattern = "^\s*([0-9]+)\s*" + match = re.match(pattern,s[i:]) + if match: + ts.append(Token('num', match.group(1))) + return i + match.end(), ts + raise NumParseError('Expected number literal.') + + +def op_parser(s, i, ts): + pattern = "^\s*([-+&|^])\s*" + match = re.match(pattern,s[i:]) + if match: + ts.append(Token('num', match.group(1))) + return i + match.end(), ts + raise NumParseError("Expected binary operator '-', '+', '&', '|', or '^'.") + + +def make_seq(p1, p2): + def parse(s, i, ts): + i,ts2 = p1(s,i,ts) + return p2(s,i,ts2) + return parse + + +def main(): + + p = make_seq(num_parser, make_seq(op_parser,num_parser)) + s = '' + iface = 'h1-eth0' + + while True: + s = str(raw_input('> ')) + if s == "quit": + break + print s + try: + i,ts = p(s,0,[]) + pkt = Ether(dst='00:04:00:00:00:00', type=0x1234) / P4calc(op=ts[1].value, + operand_a=int(ts[0].value), + operand_b=int(ts[2].value)) + pkt = pkt/' ' + +# pkt.show() + resp = srp1(pkt, iface=iface, timeout=1, verbose=False) + if resp: + p4calc=resp[P4calc] + if p4calc: + print p4calc.result + else: + print "cannot find P4calc header in the packet" + else: + print "Didn't receive response" + except Exception as error: + print error + + +if __name__ == '__main__': + main() diff --git a/P4D2_2018_East/exercises/calc/s1-commands.txt b/P4D2_2018_East/exercises/calc/s1-commands.txt new file mode 100644 index 0000000..e69de29 diff --git a/P4D2_2018_East/exercises/calc/solution/calc.p4 b/P4D2_2018_East/exercises/calc/solution/calc.p4 new file mode 100644 index 0000000..3c7e86c --- /dev/null +++ b/P4D2_2018_East/exercises/calc/solution/calc.p4 @@ -0,0 +1,256 @@ +/* -*- P4_16 -*- */ + +/* + * P4 Calculator + * + * This program implements a simple protocol. It can be carried over Ethernet + * (Ethertype 0x1234). + * + * The Protocol header looks like this: + * + * 0 1 2 3 + * +----------------+----------------+----------------+---------------+ + * | P | 4 | Version | Op | + * +----------------+----------------+----------------+---------------+ + * | Operand A | + * +----------------+----------------+----------------+---------------+ + * | Operand B | + * +----------------+----------------+----------------+---------------+ + * | Result | + * +----------------+----------------+----------------+---------------+ + * + * P is an ASCII Letter 'P' (0x50) + * 4 is an ASCII Letter '4' (0x34) + * Version is currently 0.1 (0x01) + * Op is an operation to Perform: + * '+' (0x2b) Result = OperandA + OperandB + * '-' (0x2d) Result = OperandA - OperandB + * '&' (0x26) Result = OperandA & OperandB + * '|' (0x7c) Result = OperandA | OperandB + * '^' (0x5e) Result = OperandA ^ OperandB + * + * The device receives a packet, performs the requested operation, fills in the + * result and sends the packet back out of the same port it came in on, while + * swapping the source and destination addresses. + * + * If an unknown operation is specified or the header is not valid, the packet + * is dropped + */ + +#include +#include + +/* + * Define the headers the program will recognize + */ + +/* + * Standard ethernet header + */ +header ethernet_t { + bit<48> dstAddr; + bit<48> srcAddr; + bit<16> etherType; +} + +/* + * This is a custom protocol header for the calculator. We'll use + * ethertype 0x1234 for is (see parser) + */ +const bit<16> P4CALC_ETYPE = 0x1234; +const bit<8> P4CALC_P = 0x50; // 'P' +const bit<8> P4CALC_4 = 0x34; // '4' +const bit<8> P4CALC_VER = 0x01; // v0.1 +const bit<8> P4CALC_PLUS = 0x2b; // '+' +const bit<8> P4CALC_MINUS = 0x2d; // '-' +const bit<8> P4CALC_AND = 0x26; // '&' +const bit<8> P4CALC_OR = 0x7c; // '|' +const bit<8> P4CALC_CARET = 0x5e; // '^' + +header p4calc_t { + bit<8> p; + bit<8> four; + bit<8> ver; + bit<8> op; + bit<32> operand_a; + bit<32> operand_b; + bit<32> res; +} + +/* + * All headers, used in the program needs to be assembed into a single struct. + * We only need to declare the type, but there is no need to instantiate it, + * because it is done "by the architecture", i.e. outside of P4 functions + */ +struct headers { + ethernet_t ethernet; + p4calc_t p4calc; +} + +/* + * All metadata, globally used in the program, also needs to be assembed + * into a single struct. As in the case of the headers, we only need to + * declare the type, but there is no need to instantiate it, + * because it is done "by the architecture", i.e. outside of P4 functions + */ + +struct metadata { + /* In our case it is empty */ +} + +/************************************************************************* + *********************** 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 { + packet.extract(hdr.ethernet); + transition select(hdr.ethernet.etherType) { + P4CALC_ETYPE : check_p4calc; + default : accept; + } + } + + state check_p4calc { + transition select(packet.lookahead().p, + packet.lookahead().four, + packet.lookahead().ver) { + (P4CALC_P, P4CALC_4, P4CALC_VER) : parse_p4calc; + default : accept; + } + } + + state parse_p4calc { + packet.extract(hdr.p4calc); + 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 send_back(bit<32> result) { + bit<48> tmp; + + /* Put the result back in */ + hdr.p4calc.res = result; + + /* Swap the MAC addresses */ + tmp = hdr.ethernet.dstAddr; + hdr.ethernet.dstAddr = hdr.ethernet.srcAddr; + hdr.ethernet.srcAddr = tmp; + + /* Send the packet back to the port it came from */ + standard_metadata.egress_spec = standard_metadata.ingress_port; + } + + action operation_add() { + send_back(hdr.p4calc.operand_a + hdr.p4calc.operand_b); + } + + action operation_sub() { + send_back(hdr.p4calc.operand_a - hdr.p4calc.operand_b); + } + + action operation_and() { + send_back(hdr.p4calc.operand_a & hdr.p4calc.operand_b); + } + + action operation_or() { + send_back(hdr.p4calc.operand_a | hdr.p4calc.operand_b); + } + + action operation_xor() { + send_back(hdr.p4calc.operand_a ^ hdr.p4calc.operand_b); + } + + action operation_drop() { + mark_to_drop(); + } + + table calculate { + key = { + hdr.p4calc.op : exact; + } + actions = { + operation_add; + operation_sub; + operation_and; + operation_or; + operation_xor; + operation_drop; + } + const default_action = operation_drop(); + const entries = { + P4CALC_PLUS : operation_add(); + P4CALC_MINUS: operation_sub(); + P4CALC_AND : operation_and(); + P4CALC_OR : operation_or(); + P4CALC_CARET: operation_xor(); + } + } + + + apply { + if (hdr.p4calc.isValid()) { + calculate.apply(); + } else { + operation_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 { } +} + +/************************************************************************* + *********************** D E P A R S E R ******************************* + *************************************************************************/ +control MyDeparser(packet_out packet, in headers hdr) { + apply { + packet.emit(hdr.ethernet); + packet.emit(hdr.p4calc); + } +} + +/************************************************************************* + *********************** S W I T T C H ********************************** + *************************************************************************/ + +V1Switch( +MyParser(), +MyVerifyChecksum(), +MyIngress(), +MyEgress(), +MyComputeChecksum(), +MyDeparser() +) main; diff --git a/P4D2_2018_East/exercises/calc/topology.json b/P4D2_2018_East/exercises/calc/topology.json new file mode 100644 index 0000000..29d7cdf --- /dev/null +++ b/P4D2_2018_East/exercises/calc/topology.json @@ -0,0 +1,12 @@ +{ + "hosts": [ + "h1", + "h2" + ], + "switches": { + "s1": { "cli_input" : "s1-commands.txt" } + }, + "links": [ + ["h1", "s1"], ["h2", "s1"] + ] +} diff --git a/P4D2_2018_East/exercises/ecn/Makefile b/P4D2_2018_East/exercises/ecn/Makefile new file mode 100644 index 0000000..f378756 --- /dev/null +++ b/P4D2_2018_East/exercises/ecn/Makefile @@ -0,0 +1 @@ +include ../../utils/Makefile diff --git a/P4D2_2018_East/exercises/ecn/README.md b/P4D2_2018_East/exercises/ecn/README.md new file mode 100644 index 0000000..eb30bb6 --- /dev/null +++ b/P4D2_2018_East/exercises/ecn/README.md @@ -0,0 +1,198 @@ +# Implementing ECN + +## Introduction + +The objective of this tutorial is to extend basic L3 forwarding with +an implementation of Explicit Congestion Notification (ECN). + +ECN allows end-to-end notification of network congestion without +dropping packets. If an end-host supports ECN, it puts the value of 1 +or 2 in the `ipv4.ecn` field. For such packets, each switch may +change the value to 3 if the queue size is larger than a threshold. +The receiver copies the value to sender, and the sender can lower the +rate. + +As before, we have already defined the control plane rules for +routing, so you only need to implement the data plane logic of your P4 +program. + +> **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, +`ecn.p4`, which initially implements L3 forwarding. Your job (in the +next step) will be to extend it to properly append set the ECN bits + +Before that, let's compile the incomplete `ecn.p4` and bring up a +network in Mininet to test its behavior. + +1. In your shell, run: + ```bash + make + ``` + This will: + * compile `ecn.p4`, and + * start a Mininet instance with three switches (`s1`, `s2`, `s3`) configured + in a triangle. There are 5 hosts. `h1` and `h11` are connected to `s1`. + `h2` and `h22` are connected to `s2` and `h3` is connected to `s3`. + * The hosts are assigned IPs of `10.0.1.1`, `10.0.2.2`, etc + (`10.0..`). + * The control plane programs the P4 tables in each switch based on + `sx-commands.txt` + +2. We want to send a low rate traffic from `h1` to `h2` and a high +rate iperf traffic from `h11` to `h22`. The link between `s1` and +`s2` is common between the flows and is a bottleneck because we +reduced its bandwidth to 512kbps in topology.json. Therefore, if we +capture packets at `h2`, we should see the right ECN value. + +![Setup](setup.png) + +3. You should now see a Mininet command prompt. Open four terminals +for `h1`, `h11`, `h2`, `h22`, respectively: + ```bash + mininet> xterm h1 h11 h2 h22 + ``` +3. In `h2`'s XTerm, start the server that captures packets: + ```bash + ./receive.py + ``` +4. in `h22`'s XTerm, start the iperf UDP server: + ```bash + iperf -s -u + ``` +5. In `h1`'s XTerm, send one packet per second to `h2` using send.py +say for 30 seconds: + ```bash + ./send.py 10.0.2.2 "P4 is cool" 30 + ``` + The message "P4 is cool" should be received in `h2`'s xterm, +6. In `h11`'s XTerm, start iperf client sending for 15 seconds + ```bash + iperf -c 10.0.2.22 -t 15 -u + ``` +7. At `h2`, the `ipv4.tos` field (DiffServ+ECN) is always 1 +8. type `exit` to close each XTerm window + +Your job is to extend the code in `ecn.p4` to implement the ECN logic +for setting the ECN flag. + +## Step 2: Implement ECN + +The `ecn.p4` file contains a skeleton P4 program with key pieces of +logic replaced by `TODO` comments. These should guide your +implementation---replace each `TODO` with logic implementing the +missing piece. + +First we have to change the ipv4_t header by splitting the TOS field +into DiffServ and ECN fields. Remember to update the checksum block +accordingly. Then, in the egress control block we must compare the +queue length with ECN_THRESHOLD. If the queue length is larger than +the threshold, the ECN flag will be set. Note that this logic should +happen only if the end-host declared supporting ECN by setting the +original ECN to 1 or 2. + +A complete `ecn.p4` will contain the following components: + +1. Header type definitions for Ethernet (`ethernet_t`) and IPv4 (`ipv4_t`). +2. Parsers for Ethernet, IPv4, +3. An action to drop a packet, using `mark_to_drop()`. +4. An action (called `ipv4_forward`), which will: + 1. Set the egress port for the next hop. + 2. Update the ethernet destination address with the address of + the next hop. + 3. Update the ethernet source address with the address of the switch. + 4. Decrement the TTL. +5. An egress control block that checks the ECN and +`standard_metadata.enq_qdepth` and sets the ipv4.ecn. +6. A deparser that selects the order in which fields inserted into the outgoing + packet. +7. A `package` instantiation supplied with the parser, control, + checksum verification and recomputation and deparser. + +## Step 3: Run your solution + +Follow the instructions from Step 1. This time, when your message from +`h1` is delivered to `h2`, you should see `tos` values change from 1 +to 3 as the queue builds up. `tos` may change back to 1 when iperf +finishes and the queue depletes. + +To easily track the `tos` values you may want to redirect the output +of `h2` to a file by running the following for `h2` + ```bash + ./receive.py > h2.log + ``` +and just print the `tos` values `grep tos h2.log` in a separate window +``` + tos = 0x1 + tos = 0x1 + tos = 0x1 + tos = 0x1 + tos = 0x1 + tos = 0x1 + tos = 0x1 + tos = 0x1 + tos = 0x1 + tos = 0x1 + tos = 0x1 + tos = 0x1 + tos = 0x1 + tos = 0x3 + tos = 0x3 + tos = 0x3 + tos = 0x3 + tos = 0x3 + tos = 0x3 + tos = 0x1 + tos = 0x1 + tos = 0x1 + tos = 0x1 + tos = 0x1 + tos = 0x1 +``` + +### Food for thought + +How can we let the user configure the threshold? + +### Troubleshooting + +There are several ways that problems might manifest: + +1. `ecn.p4` fails to compile. In this case, `make` will report the + error emitted from the compiler and stop. +2. `ecn.p4` compiles but does not support the control plane rules in + the `sX-commands.txt` files that `make` tries to install using + the BMv2 CLI. In this case, `make` will log the CLI tool output + in the `logs` directory. Use these error messages to fix your `ecn.p4` + implementation. +3. `ecn.p4` compiles, and the control plane rules are installed, but + the switch does not process packets in the desired way. The + `/tmp/p4s..log` files contain trace messages + describing how each switch processes each packet. The output is + detailed and can help pinpoint logic errors in your implementation. + The `build/-.pcap` also contains the + pcap of packets on each interface. Use `tcpdump -r -xxx` + to print the hexdump of the packets. +4. `ecn.p4` compiles and all rules are installed. Packets go through + and the logs show that the queue length was not high enough to set + the ECN bit. Then either lower the threshold in the p4 code or + reduce the link bandwidth in `topology.json` + +#### Cleaning up Mininet + +In the latter two cases above, `make` may leave a Mininet instance +running in the background. Use the following command to clean up +these instances: + +```bash +make stop +``` + +## Next Steps + +Congratulations, your implementation works! Move on to the next +exercise: [Multi-Hop Route Inspection](../mri), which identifies which +link is the source of congestion. diff --git a/P4D2_2018_East/exercises/ecn/ecn.p4 b/P4D2_2018_East/exercises/ecn/ecn.p4 new file mode 100644 index 0000000..576169f --- /dev/null +++ b/P4D2_2018_East/exercises/ecn/ecn.p4 @@ -0,0 +1,188 @@ +/* -*- P4_16 -*- */ +#include +#include + +const bit<8> TCP_PROTOCOL = 0x06; +const bit<16> TYPE_IPV4 = 0x800; +const bit<19> ECN_THRESHOLD = 10; + +/************************************************************************* +*********************** 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; +} + +/* + * TODO: split tos to two fields 6 bit diffserv and 2 bit ecn + */ +header ipv4_t { + bit<4> version; + bit<4> ihl; + bit<8> tos; + 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 { +} + +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(); + } + + 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 = NoAction(); + } + + 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 { + /* + * TODO: + * - if ecn is 1 or 2 + * - compare standard_metadata.enq_qdepth with threshold + * and set hdr.ipv4.ecn to 3 if larger + */ + } +} + +/************************************************************************* +************* 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 { + /* TODO: replace tos with diffserve and ecn */ + update_checksum( + hdr.ipv4.isValid(), + { hdr.ipv4.version, + hdr.ipv4.ihl, + hdr.ipv4.tos, + 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/P4D2_2018_East/exercises/ecn/receive.py b/P4D2_2018_East/exercises/ecn/receive.py new file mode 100755 index 0000000..ca5f119 --- /dev/null +++ b/P4D2_2018_East/exercises/ecn/receive.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +import sys +import struct + +from scapy.all import sniff, sendp, hexdump, get_if_list, get_if_hwaddr +from scapy.all import Packet +from scapy.all import IP, UDP, Raw +from scapy.layers.inet import _IPOption_HDR + +def get_if(): + ifs=get_if_list() + iface=None + for i in get_if_list(): + if "eth0" in i: + iface=i + break; + if not iface: + print "Cannot find eth0 interface" + exit(1) + return iface + +def handle_pkt(pkt): + print "got a packet" + pkt.show2() +# hexdump(pkt) + sys.stdout.flush() + + +def main(): + iface = 'h2-eth0' + print "sniffing on %s" % iface + sys.stdout.flush() + sniff(filter="udp and port 4321", iface = iface, + prn = lambda x: handle_pkt(x)) + +if __name__ == '__main__': + main() diff --git a/P4D2_2018_East/exercises/ecn/s1-commands.txt b/P4D2_2018_East/exercises/ecn/s1-commands.txt new file mode 100644 index 0000000..dc4e260 --- /dev/null +++ b/P4D2_2018_East/exercises/ecn/s1-commands.txt @@ -0,0 +1,5 @@ +table_set_default ipv4_lpm drop +table_add ipv4_lpm ipv4_forward 10.0.1.1/32 => 00:00:00:00:01:01 2 +table_add ipv4_lpm ipv4_forward 10.0.1.11/32 => 00:00:00:00:01:0b 1 +table_add ipv4_lpm ipv4_forward 10.0.2.0/24 => 00:00:00:02:03:00 3 +table_add ipv4_lpm ipv4_forward 10.0.3.0/24 => 00:00:00:03:02:00 4 diff --git a/P4D2_2018_East/exercises/ecn/s2-commands.txt b/P4D2_2018_East/exercises/ecn/s2-commands.txt new file mode 100644 index 0000000..3b80bed --- /dev/null +++ b/P4D2_2018_East/exercises/ecn/s2-commands.txt @@ -0,0 +1,5 @@ +table_set_default ipv4_lpm drop +table_add ipv4_lpm ipv4_forward 10.0.2.2/32 => 00:00:00:00:02:02 2 +table_add ipv4_lpm ipv4_forward 10.0.2.22/32 => 00:00:00:00:02:16 1 +table_add ipv4_lpm ipv4_forward 10.0.1.0/24 => 00:00:00:01:03:00 3 +table_add ipv4_lpm ipv4_forward 10.0.3.0/24 => 00:00:00:03:03:00 4 diff --git a/P4D2_2018_East/exercises/ecn/s3-commands.txt b/P4D2_2018_East/exercises/ecn/s3-commands.txt new file mode 100644 index 0000000..c7285fb --- /dev/null +++ b/P4D2_2018_East/exercises/ecn/s3-commands.txt @@ -0,0 +1,4 @@ +table_set_default ipv4_lpm drop +table_add ipv4_lpm ipv4_forward 10.0.3.3/32 => 00:00:00:00:03:03 1 +table_add ipv4_lpm ipv4_forward 10.0.1.10/24 => 00:00:00:01:04:00 2 +table_add ipv4_lpm ipv4_forward 10.0.2.10/24 => 00:00:00:02:04:00 3 diff --git a/P4D2_2018_East/exercises/ecn/send.py b/P4D2_2018_East/exercises/ecn/send.py new file mode 100755 index 0000000..f279d41 --- /dev/null +++ b/P4D2_2018_East/exercises/ecn/send.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python + +import argparse +import sys +import socket +import random +import struct + +from scapy.all import sendp, send, hexdump, get_if_list, get_if_hwaddr +from scapy.all import Packet, IPOption +from scapy.all import Ether, IP, UDP +from scapy.all import IntField, FieldListField, FieldLenField, ShortField +from scapy.layers.inet import _IPOption_HDR + +from time import sleep + +def get_if(): + ifs=get_if_list() + iface=None # "h1-eth0" + for i in get_if_list(): + if "eth0" in i: + iface=i + break; + if not iface: + print "Cannot find eth0 interface" + exit(1) + return iface + +def main(): + + if len(sys.argv)<4: + print 'pass 2 arguments: "" ' + exit(1) + + addr = socket.gethostbyname(sys.argv[1]) + iface = get_if() + + pkt = Ether(src=get_if_hwaddr(iface), dst="ff:ff:ff:ff:ff:ff") / IP(dst=addr, tos=1) / UDP(dport=4321, sport=1234) / sys.argv[2] + pkt.show2() + #hexdump(pkt) + try: + for i in range(int(sys.argv[3])): + sendp(pkt, iface=iface) + sleep(1) + except KeyboardInterrupt: + raise + + +if __name__ == '__main__': + main() diff --git a/P4D2_2018_East/exercises/ecn/setup.png b/P4D2_2018_East/exercises/ecn/setup.png new file mode 100644 index 0000000..aa568ec Binary files /dev/null and b/P4D2_2018_East/exercises/ecn/setup.png differ diff --git a/P4D2_2018_East/exercises/ecn/solution/ecn.p4 b/P4D2_2018_East/exercises/ecn/solution/ecn.p4 new file mode 100644 index 0000000..2aebe20 --- /dev/null +++ b/P4D2_2018_East/exercises/ecn/solution/ecn.p4 @@ -0,0 +1,188 @@ +/* -*- P4_16 -*- */ +#include +#include + +const bit<8> TCP_PROTOCOL = 0x06; +const bit<16> TYPE_IPV4 = 0x800; +const bit<19> ECN_THRESHOLD = 10; + +/************************************************************************* +*********************** 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<6> diffserv; + bit<2> ecn; + 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 { +} + +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(); + } + + 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 = NoAction(); + } + + 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) { + action mark_ecn() { + hdr.ipv4.ecn = 3; + } + apply { + if (hdr.ipv4.ecn == 1 || hdr.ipv4.ecn == 2){ + if (standard_metadata.enq_qdepth >= ECN_THRESHOLD){ + mark_ecn(); + } + } + } +} + +/************************************************************************* +************* 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.ecn, + 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/P4D2_2018_East/exercises/ecn/topology.json b/P4D2_2018_East/exercises/ecn/topology.json new file mode 100644 index 0000000..2640a2b --- /dev/null +++ b/P4D2_2018_East/exercises/ecn/topology.json @@ -0,0 +1,18 @@ +{ + "hosts": [ + "h1", + "h2", + "h3", + "h11", + "h22" + ], + "switches": { + "s1": { "cli_input" : "s1-commands.txt" }, + "s2": { "cli_input" : "s2-commands.txt" }, + "s3": { "cli_input" : "s3-commands.txt" } + }, + "links": [ + ["h1", "s1"], ["h11", "s1"], ["s1", "s2", "0", 0.5], ["s1", "s3"], + ["s3", "s2"], ["s2", "h2"], ["s2", "h22"], ["s3", "h3"] + ] +} diff --git a/P4D2_2018_East/exercises/load_balance/Makefile b/P4D2_2018_East/exercises/load_balance/Makefile new file mode 100644 index 0000000..f378756 --- /dev/null +++ b/P4D2_2018_East/exercises/load_balance/Makefile @@ -0,0 +1 @@ +include ../../utils/Makefile diff --git a/P4D2_2018_East/exercises/load_balance/README.md b/P4D2_2018_East/exercises/load_balance/README.md new file mode 100644 index 0000000..69ff639 --- /dev/null +++ b/P4D2_2018_East/exercises/load_balance/README.md @@ -0,0 +1,142 @@ +# Load Balancing + +In this exercise, you will implement a form of load balancing based on +a simple version of Equal-Cost Multipath Forwarding. The switch you +will implement will use two tables to forward packets to one of two +destination hosts at random. The first table will use a hash function +(applied to a 5-tuple consisting of the source and destination +IP addresses, IP protocol, and source and destination TCP ports) +to select one of two hosts. The second table will use the +computed hash value to forward the packet to the selected host. + +> **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, +`load_balance.p4`, which initially drops all packets. Your job (in +the next step) will be to extend it to properly forward packets. + +Before that, let's compile the incomplete `load_balance.p4` and bring +up a switch in Mininet to test its behavior. + +1. In your shell, run: + ```bash + make + ``` + This will: + * compile `load_balance.p4`, and + * start a Mininet instance with three switches (`s1`, `s2`, `s3`) configured + in a triangle, each connected to one host (`h1`, `h2`, `h3`). + * The hosts are assigned IPs of `10.0.1.1`, `10.0.2.2`, etc. + * We use the IP address 10.0.0.1 to indicate traffic that should be + load balanced between `h2` and `h3`. + +2. You should now see a Mininet command prompt. Open three terminals + for `h1`, `h2` and `h3`, respectively: + ```bash + mininet> xterm h1 h2 h3 + ``` +3. Each host includes a small Python-based messaging client and + server. In `h2` and `h3`'s XTerms, start the servers: + ```bash + ./receive.py + ``` +4. In `h1`'s XTerm, send a message from the client: + ```bash + ./send.py 10.0.0.1 "P4 is cool" + ``` + The message will not be received. +5. Type `exit` to leave each XTerm and the Mininet command line. + +The message was not received because each switch is programmed with +`load_balance.p4`, which drops all packets on arrival. Your job is to +extend this file. + +### A note about the control plane + +P4 programs define a packet-processing pipeline, but the rules +governing packet processing are inserted into the pipeline 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, the control plane logic has already been +implemented. As part of bringing up the Mininet instance, the +`make` script will install packet-processing rules in the tables of +each switch. These are defined in the `s1-commands.txt` file. + +**Important:** A P4 program also defines the interface between the +switch pipeline and control plane. The `s1-commands.txt` file contains +a list of commands for the BMv2 switch API. These commands refer to +specific tables, keys, and actions by name, and any changes in the P4 +program that add or rename tables, keys, or actions will need to be +reflected in these command files. + +## Step 2: Implement Load Balancing + +The `load_balance.p4` file contains a skeleton P4 program with key +pieces of logic replaced by `TODO` comments. These should guide your +implementation---replace each `TODO` with logic implementing the +missing piece. + +A complete `load_balance.p4` will contain the following components: + +1. Header type definitions for Ethernet (`ethernet_t`) and IPv4 (`ipv4_t`). +2. Parsers for Ethernet and IPv4 that populate `ethernet_t` and `ipv4_t` fields. +3. An action to drop a packet, using `mark_to_drop()`. +4. **TODO:** An action (called `set_ecmp_select`), which will: + 1. Hashes the 5-tuple specified above using the `hash` extern + 2. Stores the result in the `meta.ecmp_select` field +5. **TODO:** A control that: + 1. Applies the `ecmp_group` table. + 2. Applies the `ecmp_nhop` table. +6. A deparser that selects the order in which fields inserted into the outgoing + packet. +7. 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, your message from +`h1` should be delivered to `h2` or `h3`. If you send several +messages, some should be received by each server. + +### Food for thought + + +### Troubleshooting + +There are several ways that problems might manifest: + +1. `load_balance.p4` fails to compile. In this case, `make` will +report the error emitted from the compiler and stop. + +2. `load_balance.p4` compiles but does not support the control plane +rules in the `sX-commands.txt` files that `make` tries to install +using the BMv2 CLI. In this case, `make` will log the CLI tool output +in the `logs` directory. Use these error messages to fix your `load_balance.p4` +implementation. + +3. `load_balance.p4` compiles, and the control plane rules are +installed, but the switch does not process packets in the desired way. +The `/tmp/p4s..log` files contain trace messages +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` may leave a Mininet instance +running in the background. Use the following command to clean up +these instances: + +```bash +mn -c +``` + +## Next Steps + +Congratulations, your implementation works! diff --git a/P4D2_2018_East/exercises/load_balance/load_balance.p4 b/P4D2_2018_East/exercises/load_balance/load_balance.p4 new file mode 100644 index 0000000..54dfed6 --- /dev/null +++ b/P4D2_2018_East/exercises/load_balance/load_balance.p4 @@ -0,0 +1,218 @@ +/* -*- P4_16 -*- */ +#include +#include + +/************************************************************************* +*********************** H E A D E R S *********************************** +*************************************************************************/ + +header ethernet_t { + bit<48> dstAddr; + bit<48> 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; + bit<32> srcAddr; + bit<32> dstAddr; +} + +header tcp_t { + bit<16> srcPort; + bit<16> dstPort; + bit<32> seqNo; + bit<32> ackNo; + bit<4> dataOffset; + bit<3> res; + bit<3> ecn; + bit<6> ctrl; + bit<16> window; + bit<16> checksum; + bit<16> urgentPtr; +} + +struct metadata { + bit<14> ecmp_select; +} + +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) { + 0x800: parse_ipv4; + default: accept; + } + } + state parse_ipv4 { + packet.extract(hdr.ipv4); + transition select(hdr.ipv4.protocol) { + 6: parse_tcp; + default: accept; + } + } + state parse_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) { + + action drop() { + mark_to_drop(); + } + action set_ecmp_select(bit<16> ecmp_base, bit<32> ecmp_count) { + /* TODO: hash on 5-tuple and save the hash result in meta.ecmp_select + so that the ecmp_nhop table can use it to make a forwarding decision accordingly */ + } + action set_nhop(bit<48> nhop_dmac, bit<32> nhop_ipv4, bit<9> port) { + hdr.ethernet.dstAddr = nhop_dmac; + hdr.ipv4.dstAddr = nhop_ipv4; + standard_metadata.egress_spec = port; + hdr.ipv4.ttl = hdr.ipv4.ttl - 1; + } + table ecmp_group { + key = { + hdr.ipv4.dstAddr: lpm; + } + actions = { + drop; + set_ecmp_select; + } + size = 1024; + } + table ecmp_nhop { + key = { + meta.ecmp_select: exact; + } + actions = { + drop; + set_nhop; + } + size = 2; + } + apply { + if (hdr.ipv4.isValid() && hdr.ipv4.ttl > 0) { + ecmp_group.apply(); + ecmp_nhop.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) { + + action rewrite_mac(bit<48> smac) { + hdr.ethernet.srcAddr = smac; + } + action drop() { + mark_to_drop(); + } + table send_frame { + key = { + standard_metadata.egress_port: exact; + } + actions = { + rewrite_mac; + drop; + } + size = 256; + } + apply { + send_frame.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/P4D2_2018_East/exercises/load_balance/receive.py b/P4D2_2018_East/exercises/load_balance/receive.py new file mode 100755 index 0000000..c93182f --- /dev/null +++ b/P4D2_2018_East/exercises/load_balance/receive.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python +import sys +import struct +import os + +from scapy.all import sniff, sendp, hexdump, get_if_list, get_if_hwaddr +from scapy.all import Packet, IPOption +from scapy.all import ShortField, IntField, LongField, BitField, FieldListField, FieldLenField +from scapy.all import IP, UDP, Raw +from scapy.layers.inet import _IPOption_HDR + +def get_if(): + ifs=get_if_list() + iface=None + for i in get_if_list(): + if "eth0" in i: + iface=i + break; + if not iface: + print "Cannot find eth0 interface" + exit(1) + return iface + +class IPOption_MRI(IPOption): + name = "MRI" + option = 31 + fields_desc = [ _IPOption_HDR, + FieldLenField("length", None, fmt="B", + length_of="swids", + adjust=lambda pkt,l:l+4), + ShortField("count", 0), + FieldListField("swids", + [], + IntField("", 0), + length_from=lambda pkt:pkt.count*4) ] +def handle_pkt(pkt): + print "got a packet" + pkt.show2() +# hexdump(pkt) + sys.stdout.flush() + + +def main(): + ifaces = filter(lambda i: 'eth' in i, os.listdir('/sys/class/net/')) + iface = ifaces[0] + print "sniffing on %s" % iface + sys.stdout.flush() + sniff(filter="tcp", iface = iface, + prn = lambda x: handle_pkt(x)) + +if __name__ == '__main__': + main() diff --git a/P4D2_2018_East/exercises/load_balance/s1-commands.txt b/P4D2_2018_East/exercises/load_balance/s1-commands.txt new file mode 100644 index 0000000..a53e07c --- /dev/null +++ b/P4D2_2018_East/exercises/load_balance/s1-commands.txt @@ -0,0 +1,6 @@ +table_set_default ecmp_group drop +table_add ecmp_group set_ecmp_select 10.0.0.1/32 => 0 2 +table_add ecmp_nhop set_nhop 0 => 00:00:00:00:01:02 10.0.2.2 2 +table_add ecmp_nhop set_nhop 1 => 00:00:00:00:01:03 10.0.3.3 3 +table_add send_frame rewrite_mac 2 => 00:00:00:01:02:00 +table_add send_frame rewrite_mac 3 => 00:00:00:01:03:00 diff --git a/P4D2_2018_East/exercises/load_balance/s2-commands.txt b/P4D2_2018_East/exercises/load_balance/s2-commands.txt new file mode 100644 index 0000000..ca78c19 --- /dev/null +++ b/P4D2_2018_East/exercises/load_balance/s2-commands.txt @@ -0,0 +1,4 @@ +table_set_default ecmp_group drop +table_add ecmp_group set_ecmp_select 10.0.2.2/32 => 0 1 +table_add ecmp_nhop set_nhop 0 => 00:00:00:00:02:02 10.0.2.2 1 +table_add send_frame rewrite_mac 1 => 00:00:00:02:01:00 diff --git a/P4D2_2018_East/exercises/load_balance/s3-commands.txt b/P4D2_2018_East/exercises/load_balance/s3-commands.txt new file mode 100644 index 0000000..0f9e6b3 --- /dev/null +++ b/P4D2_2018_East/exercises/load_balance/s3-commands.txt @@ -0,0 +1,4 @@ +table_set_default ecmp_group drop +table_add ecmp_group set_ecmp_select 10.0.3.3/32 => 0 1 +table_add ecmp_nhop set_nhop 0 => 00:00:00:00:03:03 10.0.3.3 1 +table_add send_frame rewrite_mac 1 => 00:00:00:03:01:00 diff --git a/P4D2_2018_East/exercises/load_balance/send.py b/P4D2_2018_East/exercises/load_balance/send.py new file mode 100755 index 0000000..00496d9 --- /dev/null +++ b/P4D2_2018_East/exercises/load_balance/send.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +import argparse +import sys +import socket +import random +import struct + +from scapy.all import sendp, send, get_if_list, get_if_hwaddr +from scapy.all import Packet +from scapy.all import Ether, IP, UDP, TCP + +def get_if(): + ifs=get_if_list() + iface=None # "h1-eth0" + for i in get_if_list(): + if "eth0" in i: + iface=i + break; + if not iface: + print "Cannot find eth0 interface" + exit(1) + return iface + +def main(): + + if len(sys.argv)<3: + print 'pass 2 arguments: ""' + exit(1) + + addr = socket.gethostbyname(sys.argv[1]) + iface = get_if() + + print "sending on interface %s to %s" % (iface, str(addr)) + pkt = Ether(src=get_if_hwaddr(iface), dst='ff:ff:ff:ff:ff:ff') + pkt = pkt /IP(dst=addr) / TCP(dport=1234, sport=random.randint(49152,65535)) / sys.argv[2] + pkt.show2() + sendp(pkt, iface=iface, verbose=False) + + +if __name__ == '__main__': + main() diff --git a/P4D2_2018_East/exercises/load_balance/solution/load_balance.p4 b/P4D2_2018_East/exercises/load_balance/solution/load_balance.p4 new file mode 100644 index 0000000..64d9bcb --- /dev/null +++ b/P4D2_2018_East/exercises/load_balance/solution/load_balance.p4 @@ -0,0 +1,225 @@ +/* -*- P4_16 -*- */ +#include +#include + +/************************************************************************* +*********************** H E A D E R S *********************************** +*************************************************************************/ + +header ethernet_t { + bit<48> dstAddr; + bit<48> 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; + bit<32> srcAddr; + bit<32> dstAddr; +} + +header tcp_t { + bit<16> srcPort; + bit<16> dstPort; + bit<32> seqNo; + bit<32> ackNo; + bit<4> dataOffset; + bit<3> res; + bit<3> ecn; + bit<6> ctrl; + bit<16> window; + bit<16> checksum; + bit<16> urgentPtr; +} + +struct metadata { + bit<14> ecmp_select; +} + +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) { + 0x800: parse_ipv4; + default: accept; + } + } + state parse_ipv4 { + packet.extract(hdr.ipv4); + transition select(hdr.ipv4.protocol) { + 6: parse_tcp; + default: accept; + } + } + state parse_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) { + action drop() { + mark_to_drop(); + } + action set_ecmp_select(bit<16> ecmp_base, bit<32> ecmp_count) { + hash(meta.ecmp_select, + HashAlgorithm.crc16, + ecmp_base, + { hdr.ipv4.srcAddr, + hdr.ipv4.dstAddr, + hdr.ipv4.protocol, + hdr.tcp.srcPort, + hdr.tcp.dstPort }, + ecmp_count); + } + action set_nhop(bit<48> nhop_dmac, bit<32> nhop_ipv4, bit<9> port) { + hdr.ethernet.dstAddr = nhop_dmac; + hdr.ipv4.dstAddr = nhop_ipv4; + standard_metadata.egress_spec = port; + hdr.ipv4.ttl = hdr.ipv4.ttl - 1; + } + table ecmp_group { + key = { + hdr.ipv4.dstAddr: lpm; + } + actions = { + drop; + set_ecmp_select; + } + size = 1024; + } + table ecmp_nhop { + key = { + meta.ecmp_select: exact; + } + actions = { + drop; + set_nhop; + } + size = 2; + } + apply { + if (hdr.ipv4.isValid() && hdr.ipv4.ttl > 0) { + ecmp_group.apply(); + ecmp_nhop.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) { + + action rewrite_mac(bit<48> smac) { + hdr.ethernet.srcAddr = smac; + } + action drop() { + mark_to_drop(); + } + table send_frame { + key = { + standard_metadata.egress_port: exact; + } + actions = { + rewrite_mac; + drop; + } + size = 256; + } + apply { + send_frame.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/P4D2_2018_East/exercises/load_balance/topology.json b/P4D2_2018_East/exercises/load_balance/topology.json new file mode 100644 index 0000000..e33477c --- /dev/null +++ b/P4D2_2018_East/exercises/load_balance/topology.json @@ -0,0 +1,16 @@ +{ + "hosts": [ + "h1", + "h2", + "h3" + ], + "switches": { + "s1": { "cli_input" : "s1-commands.txt" }, + "s2": { "cli_input" : "s2-commands.txt" }, + "s3": { "cli_input" : "s3-commands.txt" } + }, + "links": [ + ["h1", "s1"], ["s1", "s2"], ["s1", "s3"], + ["s3", "s2"], ["s2", "h2"], ["s3", "h3"] + ] +} diff --git a/P4D2_2018_East/exercises/mri/Makefile b/P4D2_2018_East/exercises/mri/Makefile new file mode 100644 index 0000000..f378756 --- /dev/null +++ b/P4D2_2018_East/exercises/mri/Makefile @@ -0,0 +1 @@ +include ../../utils/Makefile diff --git a/P4D2_2018_East/exercises/mri/README.md b/P4D2_2018_East/exercises/mri/README.md new file mode 100644 index 0000000..c389110 --- /dev/null +++ b/P4D2_2018_East/exercises/mri/README.md @@ -0,0 +1,244 @@ +# Implementing MRI + +## Introduction + +The objective of this tutorial is to extend basic L3 forwarding with a +scaled-down version of In-Band Network Telemetry (INT), which we call +Multi-Hop Route Inspection (MRI). + +MRI allows users to track the path and the length of queues that every +packet travels through. To support this functionality, you will need +to write a P4 program that appends an ID and queue length to the +header stack of every packet. At the destination, the sequence of +switch IDs correspond to the path, and each ID is followed by the +queue length of the port at switch. + +As before, we have already defined the control plane rules, so you +only need to implement the data plane logic of your P4 program. + +> **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, +`mri.p4`, which initially implements L3 forwarding. Your job (in the +next step) will be to extend it to properly prepend the MRI custom +headers. + +Before that, let's compile the incomplete `mri.p4` and bring up a +switch in Mininet to test its behavior. + +1. In your shell, run: + ```bash + make + ``` + This will: + * compile `mri.p4`, and + * start a Mininet instance with three switches (`s1`, `s2`, `s3`) configured + in a triangle. There are 5 hosts. `h1` and `h11` are connected to `s1`. + `h2` and `h22` are connected to `s2` and `h3` is connected to `s3`. + * The hosts are assigned IPs of `10.0.1.1`, `10.0.2.2`, etc + (`10.0..`). + * The control plane programs the P4 tables in each switch based on + `sx-commands.txt` + +2. We want to send a low rate traffic from `h1` to `h2` and a high + rate iperf traffic from `h11` to `h22`. The link between `s1` and + `s2` is common between the flows and is a bottleneck because we + reduced its bandwidth to 512kbps in topology.json. Therefore, if we + capture packets at `h2`, we should see high queue size for that + link. + +![Setup](setup.png) + +3. You should now see a Mininet command prompt. Open four terminals + for `h1`, `h11`, `h2`, `h22`, respectively: + ```bash + mininet> xterm h1 h11 h2 h22 + ``` +3. In `h2`'s xterm, start the server that captures packets: + ```bash + ./receive.py + ``` +4. in `h22`'s xterm, start the iperf UDP server: + ```bash + iperf -s -u + ``` + +5. In `h1`'s xterm, send one packet per second to `h2` using send.py + say for 30 seconds: + ```bash + ./send.py 10.0.2.2 "P4 is cool" 30 + ``` + The message "P4 is cool" should be received in `h2`'s xterm, +6. In `h11`'s xterm, start iperf client sending for 15 seconds + ```bash + iperf -c 10.0.2.22 -t 15 -u + ``` +7. At `h2`, the MRI header has no hop info (`count=0`) +8. type `exit` to close each xterm window + +You should see the message received at host `h2`, but without any +information about the path the message took. Your job is to extend +the code in `mri.p4` to implement the MRI logic to record the path. + +### A note about the control plane + +P4 programs define a packet-processing pipeline, but the rules +governing packet processing are inserted into the pipeline 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, the control plane logic has already been +implemented. As part of bringing up the Mininet instance, the +`make` script will install packet-processing rules in the tables of +each switch. These are defined in the `sX-commands.txt` files, where +`X` corresponds to the switch number. + +## Step 2: Implement MRI + +The `mri.p4` file contains a skeleton P4 program with key pieces of +logic replaced by `TODO` comments. These should guide your +implementation---replace each `TODO` with logic implementing the +missing piece. + +MRI will require two custom headers. The first header, `mri_t`, +contains a single field `count`, which indicates the number of switch +IDs that follow. The second header, `switch_t`, contains switch ID and +Queue depth fields of each switch hop the packet goes through. + +One of the biggest challenges in implementing MRI is handling the +recursive logic for parsing these two headers. We will use a +`parser_metadata` field, `remaining`, to keep track of how many +`switch_t` headers we need to parse. In the `parse_mri` state, this +field should be set to `hdr.mri.count`. In the `parse_swtrace` state, +this field should be decremented. The `parse_swtrace` state will +transition to itself until `remaining` is 0. + +The MRI custom headers will be carried inside an IP Options +header. The IP Options header contains a field, `option`, which +indicates the type of the option. We will use a special type 31 to +indicate the presence of the MRI headers. + +Beyond the parser logic, you will add a table in egress, `swtrace` to +store the switch ID and queue depth, and actions that increment the +`count` field, and append a `switch_t` header. + +A complete `mri.p4` will contain the following components: + +1. Header type definitions for Ethernet (`ethernet_t`), IPv4 (`ipv4_t`), + IP Options (`ipv4_option_t`), MRI (`mri_t`), and Switch (`switch_t`). +2. Parsers for Ethernet, IPv4, IP Options, MRI, and Switch that will +populate `ethernet_t`, `ipv4_t`, `ipv4_option_t`, `mri_t`, and +`switch_t`. +3. An action to drop a packet, using `mark_to_drop()`. +4. An action (called `ipv4_forward`), which will: + 1. Set the egress port for the next hop. + 2. Update the ethernet destination address with the address of + the next hop. + 3. Update the ethernet source address with the address of the switch. + 4. Decrement the TTL. +5. An ingress control that: + 1. Defines a table that will read an IPv4 destination address, and + invoke either `drop` or `ipv4_forward`. + 2. An `apply` block that applies the table. +6. At egress, an action (called `add_swtrace`) that will add the + switch ID and queue depth. +8. An egress control that applies a table (`swtrace`) to store the + switch ID and queue depth, and calls `add_swtrace`. +9. A deparser that selects the order in which fields inserted into the outgoing + packet. +10. A `package` instantiation supplied with the parser, control, + checksum verification and recomputation and deparser. + +## Step 3: Run your solution + +Follow the instructions from Step 1. This time, when your message + from `h1` is delivered to `h2`, you should see the sequence of + switches through which the packet traveled plus the corresponding + queue depths. The expected output will look like the following, + which shows the MRI header, with a `count` of 2, and switch ids + (`swids`) 2 and 1. The queue depth at the common link (from s1 to + s2) is high. + +``` +got a packet +###[ Ethernet ]### + dst = 00:04:00:02:00:02 + src = f2:ed:e6:df:4e:fa + type = 0x800 +###[ IP ]### + version = 4L + ihl = 10L + tos = 0x0 + len = 42 + id = 1 + flags = + frag = 0L + ttl = 62 + proto = udp + chksum = 0x60c0 + src = 10.0.1.1 + dst = 10.0.2.2 + \options \ + |###[ MRI ]### + | copy_flag = 0L + | optclass = control + | option = 31L + | length = 20 + | count = 2 + | \swtraces \ + | |###[ SwitchTrace ]### + | | swid = 2 + | | qdepth = 0 + | |###[ SwitchTrace ]### + | | swid = 1 + | | qdepth = 17 +###[ UDP ]### + sport = 1234 + dport = 4321 + len = 18 + chksum = 0x1c7b +###[ Raw ]### + load = 'P4 is cool' + +``` + +### Troubleshooting + +There are several ways that problems might manifest: + +1. `mri.p4` fails to compile. In this case, `make` will report the +error emitted from the compiler and stop. +2. `mri.p4` compiles but does not support the control plane rules in +the `sX-commands.txt` files that `make` tries to install using the BMv2 CLI. +In this case, `make` will log the CLI tool output in the `logs` directory. +Use these error messages to fix your `mri.p4` implementation. +3. `mri.p4` compiles, and the control plane rules are installed, but +the switch does not process packets in the desired way. The +`/tmp/p4s..log` files contain trace messages describing +how each switch processes each packet. The output is detailed and can +help pinpoint logic errors in your implementation. The +`build/-.pcap` also contains the pcap of +packets on each interface. Use `tcpdump -r -xxx` to print +the hexdump of the packets. +4. `mri.p4` compiles and all rules are installed. Packets go through +and the logs show that the queue length is always 0. Then either +reduce the link bandwidth in `topology.json`. + +#### Cleaning up Mininet + +In the latter two cases above, `make` may leave a Mininet instance +running in the background. Use the following command to clean up +these instances: + +```bash +make stop +``` + +## Next Steps + +Congratulations, your implementation works! Move on to [Source +Routing](../source_routing). + diff --git a/P4D2_2018_East/exercises/mri/mri.p4 b/P4D2_2018_East/exercises/mri/mri.p4 new file mode 100644 index 0000000..15f5c3b --- /dev/null +++ b/P4D2_2018_East/exercises/mri/mri.p4 @@ -0,0 +1,281 @@ +/* -*- P4_16 -*- */ +#include +#include + +const bit<8> UDP_PROTOCOL = 0x11; +const bit<16> TYPE_IPV4 = 0x800; +const bit<5> IPV4_OPTION_MRI = 31; + +#define MAX_HOPS 9 + +/************************************************************************* +*********************** H E A D E R S *********************************** +*************************************************************************/ + +typedef bit<9> egressSpec_t; +typedef bit<48> macAddr_t; +typedef bit<32> ip4Addr_t; +typedef bit<32> switchID_t; +typedef bit<32> qdepth_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 ipv4_option_t { + bit<1> copyFlag; + bit<2> optClass; + bit<5> option; + bit<8> optionLength; +} + +header mri_t { + bit<16> count; +} + +header switch_t { + switchID_t swid; + qdepth_t qdepth; +} + +struct ingress_metadata_t { + bit<16> count; +} + +struct parser_metadata_t { + bit<16> remaining; +} + +struct metadata { + ingress_metadata_t ingress_metadata; + parser_metadata_t parser_metadata; +} + +struct headers { + ethernet_t ethernet; + ipv4_t ipv4; + ipv4_option_t ipv4_option; + mri_t mri; + switch_t[MAX_HOPS] swtraces; +} + +error { IPHeaderTooShort } + +/************************************************************************* +*********************** 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); + verify(hdr.ipv4.ihl >= 5, error.IPHeaderTooShort); + transition select(hdr.ipv4.ihl) { + 5 : accept; + default : parse_ipv4_option; + } + } + + state parse_ipv4_option { + /* + * TODO: Add logic to: + * - Extract the ipv4_option header. + * - If value is equal to IPV4_OPTION_MRI, transition to parse_mri. + * - Otherwise, accept. + */ + transition accept; + } + + state parse_mri { + /* + * TODO: Add logic to: + * - Extract hdr.mri. + * - Set meta.parser_metadata.remaining to hdr.mri.count + * - Select on the value of meta.parser_metadata.remaining + * - If the value is equal to 0, accept. + * - Otherwise, transition to parse_swtrace. + */ + transition accept; + } + + state parse_swtrace { + /* + * TODO: Add logic to: + * - Extract hdr.swtraces.next. + * - Decrement meta.parser_metadata.remaining by 1 + * - Select on the value of meta.parser_metadata.remaining + * - If the value is equal to 0, accept. + * - Otherwise, transition to parse_swtrace. + */ + 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(); + } + + 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 = NoAction(); + } + + 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) { + action add_swtrace(switchID_t swid) { + /* + * TODO: add logic to: + - Increment hdr.mri.count by 1 + - Add a new swtrace header by calling push_front(1) on hdr.swtraces. + - Set hdr.swtraces[0].swid to the id parameter + - Set hdr.swtraces[0].qdepth to (qdepth_t)standard_metadata.deq_qdepth + - Increment hdr.ipv4.ihl by 2 + - Increment hdr.ipv4.totalLen by 8 + - Increment hdr.ipv4_option.optionLength by 8 + */ + } + + table swtrace { + actions = { + /* TODO: add the correct action */ + NoAction; + } + + default_action = NoAction(); + } + + apply { + /* + * TODO: add logic to: + * - If hdr.mri is valid: + * - Apply table swtrace + */ + swtrace.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); + + /* TODO: emit ipv4_option, mri and swtraces headers */ + } +} + +/************************************************************************* +*********************** S W I T C H ******************************* +*************************************************************************/ + +V1Switch( +MyParser(), +MyVerifyChecksum(), +MyIngress(), +MyEgress(), +MyComputeChecksum(), +MyDeparser() +) main; diff --git a/P4D2_2018_East/exercises/mri/receive.py b/P4D2_2018_East/exercises/mri/receive.py new file mode 100755 index 0000000..b3c3b35 --- /dev/null +++ b/P4D2_2018_East/exercises/mri/receive.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python +import sys +import struct + +from scapy.all import sniff, sendp, hexdump, get_if_list, get_if_hwaddr +from scapy.all import Packet, IPOption +from scapy.all import PacketListField, ShortField, IntField, LongField, BitField, FieldListField, FieldLenField +from scapy.all import IP, UDP, Raw +from scapy.layers.inet import _IPOption_HDR + +def get_if(): + ifs=get_if_list() + iface=None + for i in get_if_list(): + if "eth0" in i: + iface=i + break; + if not iface: + print "Cannot find eth0 interface" + exit(1) + return iface + +class SwitchTrace(Packet): + fields_desc = [ IntField("swid", 0), + IntField("qdepth", 0)] + def extract_padding(self, p): + return "", p + +class IPOption_MRI(IPOption): + name = "MRI" + option = 31 + fields_desc = [ _IPOption_HDR, + FieldLenField("length", None, fmt="B", + length_of="swtraces", + adjust=lambda pkt,l:l*2+4), + ShortField("count", 0), + PacketListField("swtraces", + [], + SwitchTrace, + count_from=lambda pkt:(pkt.count*1)) ] + +def handle_pkt(pkt): + print "got a packet" + pkt.show2() +# hexdump(pkt) + sys.stdout.flush() + + +def main(): + iface = 'h2-eth0' + print "sniffing on %s" % iface + sys.stdout.flush() + sniff(filter="udp and port 4321", iface = iface, + prn = lambda x: handle_pkt(x)) + +if __name__ == '__main__': + main() diff --git a/P4D2_2018_East/exercises/mri/s1-commands.txt b/P4D2_2018_East/exercises/mri/s1-commands.txt new file mode 100644 index 0000000..5f15633 --- /dev/null +++ b/P4D2_2018_East/exercises/mri/s1-commands.txt @@ -0,0 +1,6 @@ +table_set_default ipv4_lpm drop +table_set_default swtrace add_swtrace 1 +table_add ipv4_lpm ipv4_forward 10.0.1.1/32 => 00:00:00:00:01:01 2 +table_add ipv4_lpm ipv4_forward 10.0.1.11/32 => 00:00:00:00:01:0b 1 +table_add ipv4_lpm ipv4_forward 10.0.2.0/24 => 00:00:00:02:03:00 3 +table_add ipv4_lpm ipv4_forward 10.0.3.0/24 => 00:00:00:03:02:00 4 diff --git a/P4D2_2018_East/exercises/mri/s2-commands.txt b/P4D2_2018_East/exercises/mri/s2-commands.txt new file mode 100644 index 0000000..8b9bd15 --- /dev/null +++ b/P4D2_2018_East/exercises/mri/s2-commands.txt @@ -0,0 +1,6 @@ +table_set_default ipv4_lpm drop +table_set_default swtrace add_swtrace 2 +table_add ipv4_lpm ipv4_forward 10.0.2.2/32 => 00:00:00:00:02:02 2 +table_add ipv4_lpm ipv4_forward 10.0.2.22/32 => 00:00:00:00:02:16 1 +table_add ipv4_lpm ipv4_forward 10.0.1.0/24 => 00:00:00:01:03:00 3 +table_add ipv4_lpm ipv4_forward 10.0.3.0/24 => 00:00:00:03:03:00 4 diff --git a/P4D2_2018_East/exercises/mri/s3-commands.txt b/P4D2_2018_East/exercises/mri/s3-commands.txt new file mode 100644 index 0000000..d27b73c --- /dev/null +++ b/P4D2_2018_East/exercises/mri/s3-commands.txt @@ -0,0 +1,5 @@ +table_set_default ipv4_lpm drop +table_set_default swtrace add_swtrace 3 +table_add ipv4_lpm ipv4_forward 10.0.3.3/32 => 00:00:00:00:03:01 1 +table_add ipv4_lpm ipv4_forward 10.0.1.0/24 => 00:00:00:01:04:00 2 +table_add ipv4_lpm ipv4_forward 10.0.2.0/24 => 00:00:00:02:04:00 3 diff --git a/P4D2_2018_East/exercises/mri/send.py b/P4D2_2018_East/exercises/mri/send.py new file mode 100755 index 0000000..7b0aca7 --- /dev/null +++ b/P4D2_2018_East/exercises/mri/send.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python + +import argparse +import sys +import socket +import random +import struct + +from scapy.all import sendp, send, hexdump, get_if_list, get_if_hwaddr +from scapy.all import Packet, IPOption +from scapy.all import Ether, IP, UDP +from scapy.all import IntField, FieldListField, FieldLenField, ShortField, PacketListField +from scapy.layers.inet import _IPOption_HDR + +from time import sleep + +def get_if(): + ifs=get_if_list() + iface=None # "h1-eth0" + for i in get_if_list(): + if "eth0" in i: + iface=i + break; + if not iface: + print "Cannot find eth0 interface" + exit(1) + return iface + +class SwitchTrace(Packet): + fields_desc = [ IntField("swid", 0), + IntField("qdepth", 0)] + def extract_padding(self, p): + return "", p + +class IPOption_MRI(IPOption): + name = "MRI" + option = 31 + fields_desc = [ _IPOption_HDR, + FieldLenField("length", None, fmt="B", + length_of="swtraces", + adjust=lambda pkt,l:l*2+4), + ShortField("count", 0), + PacketListField("swtraces", + [], + SwitchTrace, + count_from=lambda pkt:(pkt.count*1)) ] + + +def main(): + + if len(sys.argv)<3: + print 'pass 2 arguments: ""' + exit(1) + + addr = socket.gethostbyname(sys.argv[1]) + iface = get_if() + + pkt = Ether(src=get_if_hwaddr(iface), dst="ff:ff:ff:ff:ff:ff") / IP( + dst=addr, options = IPOption_MRI(count=0, + swtraces=[])) / UDP( + dport=4321, sport=1234) / sys.argv[2] + + # pkt = Ether(src=get_if_hwaddr(iface), dst="ff:ff:ff:ff:ff:ff") / IP( + # dst=addr, options = IPOption_MRI(count=2, + # swtraces=[SwitchTrace(swid=0,qdepth=0), SwitchTrace(swid=1,qdepth=0)])) / UDP( + # dport=4321, sport=1234) / sys.argv[2] + pkt.show2() + #hexdump(pkt) + try: + for i in range(int(sys.argv[3])): + sendp(pkt, iface=iface) + sleep(1) + except KeyboardInterrupt: + raise + + +if __name__ == '__main__': + main() diff --git a/P4D2_2018_East/exercises/mri/setup.png b/P4D2_2018_East/exercises/mri/setup.png new file mode 100644 index 0000000..aa568ec Binary files /dev/null and b/P4D2_2018_East/exercises/mri/setup.png differ diff --git a/P4D2_2018_East/exercises/mri/solution/mri.p4 b/P4D2_2018_East/exercises/mri/solution/mri.p4 new file mode 100644 index 0000000..654f01b --- /dev/null +++ b/P4D2_2018_East/exercises/mri/solution/mri.p4 @@ -0,0 +1,268 @@ +/* -*- P4_16 -*- */ +#include +#include + +const bit<8> UDP_PROTOCOL = 0x11; +const bit<16> TYPE_IPV4 = 0x800; +const bit<5> IPV4_OPTION_MRI = 31; + +#define MAX_HOPS 9 + +/************************************************************************* +*********************** H E A D E R S *********************************** +*************************************************************************/ + +typedef bit<9> egressSpec_t; +typedef bit<48> macAddr_t; +typedef bit<32> ip4Addr_t; +typedef bit<32> switchID_t; +typedef bit<32> qdepth_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 ipv4_option_t { + bit<1> copyFlag; + bit<2> optClass; + bit<5> option; + bit<8> optionLength; +} + +header mri_t { + bit<16> count; +} + +header switch_t { + switchID_t swid; + qdepth_t qdepth; +} + +struct ingress_metadata_t { + bit<16> count; +} + +struct parser_metadata_t { + bit<16> remaining; +} + +struct metadata { + ingress_metadata_t ingress_metadata; + parser_metadata_t parser_metadata; +} + +struct headers { + ethernet_t ethernet; + ipv4_t ipv4; + ipv4_option_t ipv4_option; + mri_t mri; + switch_t[MAX_HOPS] swtraces; +} + +error { IPHeaderTooShort } + +/************************************************************************* +*********************** 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); + verify(hdr.ipv4.ihl >= 5, error.IPHeaderTooShort); + transition select(hdr.ipv4.ihl) { + 5 : accept; + default : parse_ipv4_option; + } + } + + state parse_ipv4_option { + packet.extract(hdr.ipv4_option); + transition select(hdr.ipv4_option.option) { + IPV4_OPTION_MRI: parse_mri; + default: accept; + } + } + + state parse_mri { + packet.extract(hdr.mri); + meta.parser_metadata.remaining = hdr.mri.count; + transition select(meta.parser_metadata.remaining) { + 0 : accept; + default: parse_swtrace; + } + } + + state parse_swtrace { + packet.extract(hdr.swtraces.next); + meta.parser_metadata.remaining = meta.parser_metadata.remaining - 1; + transition select(meta.parser_metadata.remaining) { + 0 : accept; + default: parse_swtrace; + } + } +} + + +/************************************************************************* +************ 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(); + } + + 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 = NoAction(); + } + + 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) { + action add_swtrace(switchID_t swid) { + hdr.mri.count = hdr.mri.count + 1; + hdr.swtraces.push_front(1); + hdr.swtraces[0].swid = swid; + hdr.swtraces[0].qdepth = (qdepth_t)standard_metadata.deq_qdepth; + + hdr.ipv4.ihl = hdr.ipv4.ihl + 2; + hdr.ipv4_option.optionLength = hdr.ipv4_option.optionLength + 8; + hdr.ipv4.totalLen = hdr.ipv4.totalLen + 8; + } + + table swtrace { + actions = { + add_swtrace; + NoAction; + } + default_action = NoAction(); + } + + apply { + if (hdr.mri.isValid()) { + swtrace.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.ipv4_option); + packet.emit(hdr.mri); + packet.emit(hdr.swtraces); + } +} + +/************************************************************************* +*********************** S W I T C H ******************************* +*************************************************************************/ + +V1Switch( +MyParser(), +MyVerifyChecksum(), +MyIngress(), +MyEgress(), +MyComputeChecksum(), +MyDeparser() +) main; diff --git a/P4D2_2018_East/exercises/mri/topology.json b/P4D2_2018_East/exercises/mri/topology.json new file mode 100644 index 0000000..2640a2b --- /dev/null +++ b/P4D2_2018_East/exercises/mri/topology.json @@ -0,0 +1,18 @@ +{ + "hosts": [ + "h1", + "h2", + "h3", + "h11", + "h22" + ], + "switches": { + "s1": { "cli_input" : "s1-commands.txt" }, + "s2": { "cli_input" : "s2-commands.txt" }, + "s3": { "cli_input" : "s3-commands.txt" } + }, + "links": [ + ["h1", "s1"], ["h11", "s1"], ["s1", "s2", "0", 0.5], ["s1", "s3"], + ["s3", "s2"], ["s2", "h2"], ["s2", "h22"], ["s3", "h3"] + ] +} diff --git a/P4D2_2018_East/exercises/p4runtime/Makefile b/P4D2_2018_East/exercises/p4runtime/Makefile new file mode 100644 index 0000000..463ae07 --- /dev/null +++ b/P4D2_2018_East/exercises/p4runtime/Makefile @@ -0,0 +1,5 @@ +BMV2_SWITCH_EXE = simple_switch_grpc +NO_P4 = true +P4C_ARGS = --p4runtime-file $(basename $@).p4info --p4runtime-format text + +include ../../utils/Makefile diff --git a/P4D2_2018_East/exercises/p4runtime/README.md b/P4D2_2018_East/exercises/p4runtime/README.md new file mode 100644 index 0000000..9cc60e1 --- /dev/null +++ b/P4D2_2018_East/exercises/p4runtime/README.md @@ -0,0 +1,188 @@ +# Implementing a Control Plane using P4 Runtime + +## Introduction + +In this exercise, we will be using P4 Runtime to send flow entries to the +switch instead of using the switch's CLI. We will be building on the same P4 +program that you used in the [basic_tunnel](../basic_tunnel) exercise. The +P4 program has been renamed to `advanced_tunnel.py` and has been augmented +with two counters (`ingressTunnelCounter`, `egressTunnelCounter`) and +two new actions (`myTunnel_ingress`, `myTunnel_egress`). + +You will use the starter program, `mycontroller.py`, and a few helper +libraries in the `p4runtime_lib` directory to create the table entries +necessary to tunnel traffic between host 1 and 2. + +> **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 starter code for this assignment is in a file called `mycontroller.py`, +and it will install only some of the rules that you need to tunnel traffic between +two hosts. + +Let's first compile the new P4 program, start the network, use `mycontroller.py` +to install a few rules, and look at the `ingressTunnelCounter` to see that things +are working as expected. + +1. In your shell, run: + ```bash + make + ``` + This will: + * compile `advanced_tunnel.p4`, + * start a Mininet instance with three switches (`s1`, `s2`, `s3`) + configured in a triangle, each connected to one host (`h1`, `h2`, `h3`), and + * assign IPs of `10.0.1.1`, `10.0.2.2`, `10.0.3.3` to the respective hosts. + +2. You should now see a Mininet command prompt. Start a ping between h1 and h2: + ```bash + mininet> h1 ping h2 + ``` + Because there are no rules on the switches, you should **not** receive any + replies yet. You should leave the ping running in this shell. + +3. Open another shell and run the starter code: + ```bash + cd ~/tutorials/P4D2_2017_Fall/exercises/p4runtime + ./mycontroller.py + ``` + This will install the `advanced_tunnel.p4` program on the switches and push the + tunnel ingress rules. + The program prints the tunnel ingress and egress counters every 2 seconds. + You should see the ingress tunnel counter for s1 increasing: + ``` + s1 ingressTunnelCounter 100: 2 packets + ``` + The other counters should remain at zero. + +4. Press `Ctrl-C` to the second shell to stop `mycontroller.py` + +Each switch is currently mapping traffic into tunnels based on the destination IP +address. Your job is to write the rules that forward the traffic between the switches +based on the tunnel ID. + +### Potential Issues + +If you see the following error message when running `mycontroller.py`, then +the gRPC server is not running on one or more switches. + +``` +p4@p4:~/tutorials/P4D2_2017_Fall/exercises/p4runtime$ ./mycontroller.py +... +grpc._channel._Rendezvous: <_Rendezvous of RPC that terminated with (StatusCode.UNAVAILABLE, Connect Failed)> +``` + +You can check to see which of gRPC ports are listening on the machine by running: +```bash +sudo netstat -lpnt +``` + +The easiest solution is to enter `Ctrl-D` or `exit` in the `mininet>` prompt, +and re-run `make`. + +### 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. In this case, +`mycontroller.py` implements our control plane, instead of installing static +table entries like we have in the previous exercises. + +**Important:** A P4 program also defines the interface between the +switch pipeline and control plane. This interface is defined in the +`advanced_tunnel.p4info` file. The table entries that you build in `mycontroller.py` +refer to specific tables, keys, and actions by name, and we use a P4Info helper +to convert the names into the IDs that are required for P4 Runtime. Any changes +in the P4 program that add or rename tables, keys, or actions will need to be +reflected in your table entries. + +## Step 2: Implement Tunnel Forwarding + +The `mycontroller.py` file is a basic controller plane that does the following: +1. Establishes a gRPC connection to the switches for the P4 Runtime service. +2. Pushes the P4 program to each switch. +3. Writes tunnel ingress and tunnel egress rules for two tunnels between h1 and h2. +4. Reads tunnel ingress and egress counters every 2 seconds. + +It also contains comments marked with `TODO` which indicate the functionality +that you need to implement. + +Your job will be to write the tunnel transit rule in the `writeTunnelRules` function +that will match on tunnel ID and forward packets to the next hop. + +![topology](../basic_tunnel/topo.png) + +In this exercise, you will be interacting with some of the classes and methods in +the `p4runtime_lib` directory. Here is a summary of each of the files in the directory: +- `helper.py` + - Contains the `P4InfoHelper` class which is used to parse the `p4info` files. + - Provides translation methods from entity name to and from ID number. + - Builds P4 program-dependent sections of P4 Runtime table entries. +- `switch.py` + - Contains the `SwitchConnection` class which grabs the gRPC client stub, and + establishes connections to the switches. + - Provides helper methods that construct the P4 Runtime protocol buffer messages + and makes the P4 Runtime gRPC service calls. +- `bmv2.py` + - Contains `Bmv2SwitchConnection` which extends `SwitchConnections` and provides + the BMv2-specific device payload to load the P4 program. +- `convert.py` + - Provides convenience methods to encode and decode from friendly strings and + numbers to the byte strings required for the protocol buffer messages. + - Used by `helper.py` + + +## Step 3: Run your solution + +Follow the instructions from Step 1. If your Mininet network is still running, +you will just need to run the following in your second shell: +```bash +./my_controller.py +``` + +You should start to see ICMP replies in your Mininet prompt, and you should start to +see the values for all counters start to increment. + +### Extra Credit and Food for Thought + +You might notice that the rules that are printed by `mycontroller.py` contain the entity +IDs rather than the table names. You can use the P4Info helper to translate these IDs +into entry names. + +Also, you may want to think about the following: +- What assumptions about the topology are baked into your implementation? How would you +need to change it for a more realistic network? + +- Why are the byte counters different between the ingress and egress counters? + +- What is the TTL in the ICMP replies? Why is it the value that it is? +Hint: The default TTL is 64 for packets sent by the hosts. + +If you are interested, you can find the protocol buffer and gRPC definitions here: +- [P4 Runtime](https://github.com/p4lang/PI/blob/master/proto/p4/p4runtime.proto) +- [P4 Info](https://github.com/p4lang/PI/blob/master/proto/p4/config/p4info.proto) + +#### Cleaning up Mininet + +If the Mininet shell crashes, it may leave a Mininet instance +running in the background. Use the following command to clean up: +```bash +make clean +``` + +#### Running the reference solution + +To run the reference solution, you should run the following command from the +`~/tutorials/P4D2_2017_Fall/exercises/p4runtime` directory: +```bash +solution/my_controller.py +``` + + +## Next Steps + +Congratulations, your implementation works! Move onto the next assignment +[ecn](../ecn)! + diff --git a/P4D2_2018_East/exercises/p4runtime/advanced_tunnel.p4 b/P4D2_2018_East/exercises/p4runtime/advanced_tunnel.p4 new file mode 100755 index 0000000..5ea8fc0 --- /dev/null +++ b/P4D2_2018_East/exercises/p4runtime/advanced_tunnel.p4 @@ -0,0 +1,238 @@ +/* -*- P4_16 -*- */ +#include +#include + +const bit<16> TYPE_MYTUNNEL = 0x1212; +const bit<16> TYPE_IPV4 = 0x800; +const bit<32> MAX_TUNNEL_ID = 1 << 16; + +/************************************************************************* +*********************** 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 myTunnel_t { + bit<16> proto_id; + bit<16> dst_id; +} + +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; + myTunnel_t myTunnel; + 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_MYTUNNEL: parse_myTunnel; + TYPE_IPV4: parse_ipv4; + default: accept; + } + } + + state parse_myTunnel { + packet.extract(hdr.myTunnel); + transition select(hdr.myTunnel.proto_id) { + 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) { + + counter(MAX_TUNNEL_ID, CounterType.packets_and_bytes) ingressTunnelCounter; + counter(MAX_TUNNEL_ID, CounterType.packets_and_bytes) egressTunnelCounter; + + action drop() { + mark_to_drop(); + } + + 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; + } + + action myTunnel_ingress(bit<16> dst_id) { + hdr.myTunnel.setValid(); + hdr.myTunnel.dst_id = dst_id; + hdr.myTunnel.proto_id = hdr.ethernet.etherType; + hdr.ethernet.etherType = TYPE_MYTUNNEL; + ingressTunnelCounter.count((bit<32>) hdr.myTunnel.dst_id); + } + + action myTunnel_forward(egressSpec_t port) { + standard_metadata.egress_spec = port; + } + + action myTunnel_egress(macAddr_t dstAddr, egressSpec_t port) { + standard_metadata.egress_spec = port; + hdr.ethernet.dstAddr = dstAddr; + hdr.ethernet.etherType = hdr.myTunnel.proto_id; + hdr.myTunnel.setInvalid(); + egressTunnelCounter.count((bit<32>) hdr.myTunnel.dst_id); + } + + table ipv4_lpm { + key = { + hdr.ipv4.dstAddr: lpm; + } + actions = { + ipv4_forward; + myTunnel_ingress; + drop; + NoAction; + } + size = 1024; + default_action = NoAction(); + } + + table myTunnel_exact { + key = { + hdr.myTunnel.dst_id: exact; + } + actions = { + myTunnel_forward; + myTunnel_egress; + drop; + } + size = 1024; + default_action = drop(); + } + + apply { + if (hdr.ipv4.isValid() && !hdr.myTunnel.isValid()) { + // Process only non-tunneled IPv4 packets. + ipv4_lpm.apply(); + } + + if (hdr.myTunnel.isValid()) { + // Process all tunneled packets. + myTunnel_exact.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.myTunnel); + packet.emit(hdr.ipv4); + } +} + +/************************************************************************* +*********************** S W I T C H ******************************* +*************************************************************************/ + +V1Switch( +MyParser(), +MyVerifyChecksum(), +MyIngress(), +MyEgress(), +MyComputeChecksum(), +MyDeparser() +) main; diff --git a/P4D2_2018_East/exercises/p4runtime/mycontroller.py b/P4D2_2018_East/exercises/p4runtime/mycontroller.py new file mode 100755 index 0000000..b9a4d20 --- /dev/null +++ b/P4D2_2018_East/exercises/p4runtime/mycontroller.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python2 +import argparse +import os +from time import sleep + +import p4runtime_lib.bmv2 +import p4runtime_lib.helper + +SWITCH_TO_HOST_PORT = 1 +SWITCH_TO_SWITCH_PORT = 2 + +def writeTunnelRules(p4info_helper, ingress_sw, egress_sw, tunnel_id, + dst_eth_addr, dst_ip_addr): + ''' + Installs three rules: + 1) An tunnel ingress rule on the ingress switch in the ipv4_lpm table that + encapsulates traffic into a tunnel with the specified ID + 2) A transit rule on the ingress switch that forwards traffic based on + the specified ID + 3) An tunnel egress rule on the egress switch that decapsulates traffic + with the specified ID and sends it to the host + + :param p4info_helper: the P4Info helper + :param ingress_sw: the ingress switch connection + :param egress_sw: the egress switch connection + :param tunnel_id: the specified tunnel ID + :param dst_eth_addr: the destination IP to match in the ingress rule + :param dst_ip_addr: the destination Ethernet address to write in the + egress rule + ''' + # 1) Tunnel Ingress Rule + table_entry = p4info_helper.buildTableEntry( + table_name="MyIngress.ipv4_lpm", + match_fields={ + "hdr.ipv4.dstAddr": (dst_ip_addr, 32) + }, + action_name="MyIngress.myTunnel_ingress", + action_params={ + "dst_id": tunnel_id, + }) + ingress_sw.WriteTableEntry(table_entry) + print "Installed ingress tunnel rule on %s" % ingress_sw.name + + # 2) Tunnel Transit Rule + # The rule will need to be added to the myTunnel_exact table and match on + # the tunnel ID (hdr.myTunnel.dst_id). Traffic will need to be forwarded + # using the myTunnel_forward action on the port connected to the next switch. + # + # For our simple topology, switch 1 and switch 2 are connected using a + # link attached to port 2 on both switches. We have defined a variable at + # the top of the file, SWITCH_TO_SWITCH_PORT, that you can use as the output + # port for this action. + # + # We will only need a transit rule on the ingress switch because we are + # using a simple topology. In general, you'll need on transit rule for + # each switch in the path (except the last switch, which has the egress rule), + # and you will need to select the port dynamically for each switch based on + # your topology. + + # TODO build the transit rule + # TODO install the transit rule on the ingress switch + print "TODO Install transit tunnel rule" + + # 3) Tunnel Egress Rule + # For our simple topology, the host will always be located on the + # SWITCH_TO_HOST_PORT (port 1). + # In general, you will need to keep track of which port the host is + # connected to. + table_entry = p4info_helper.buildTableEntry( + table_name="MyIngress.myTunnel_exact", + match_fields={ + "hdr.myTunnel.dst_id": tunnel_id + }, + action_name="MyIngress.myTunnel_egress", + action_params={ + "dstAddr": dst_eth_addr, + "port": SWITCH_TO_HOST_PORT + }) + egress_sw.WriteTableEntry(table_entry) + print "Installed egress tunnel rule on %s" % egress_sw.name + +def readTableRules(p4info_helper, sw): + ''' + Reads the table entries from all tables on the switch. + + :param p4info_helper: the P4Info helper + :param sw: the switch connection + ''' + print '\n----- Reading tables rules for %s -----' % sw.name + for response in sw.ReadTableEntries(): + for entity in response.entities: + entry = entity.table_entry + # TODO For extra credit, you can use the p4info_helper to translate + # the IDs the entry to names + print entry + print '-----' + +def printCounter(p4info_helper, sw, counter_name, index): + ''' + Reads the specified counter at the specified index from the switch. In our + program, the index is the tunnel ID. If the index is 0, it will return all + values from the counter. + + :param p4info_helper: the P4Info helper + :param sw: the switch connection + :param counter_name: the name of the counter from the P4 program + :param index: the counter index (in our case, the tunnel ID) + ''' + for response in sw.ReadCounters(p4info_helper.get_counters_id(counter_name), index): + for entity in response.entities: + counter = entity.counter_entry + print "%s %s %d: %d packets (%d bytes)" % ( + sw.name, counter_name, index, + counter.data.packet_count, counter.data.byte_count + ) + + +def main(p4info_file_path, bmv2_file_path): + # Instantiate a P4 Runtime helper from the p4info file + p4info_helper = p4runtime_lib.helper.P4InfoHelper(p4info_file_path) + + # Create a switch connection object for s1 and s2; + # this is backed by a P4 Runtime gRPC connection + s1 = p4runtime_lib.bmv2.Bmv2SwitchConnection('s1', + address='127.0.0.1:50051', + device_id=0) + s2 = p4runtime_lib.bmv2.Bmv2SwitchConnection('s2', + address='127.0.0.1:50052', + device_id=1) + + # Install the P4 program on the switches + s1.SetForwardingPipelineConfig(p4info=p4info_helper.p4info, + bmv2_json_file_path=bmv2_file_path) + print "Installed P4 Program using SetForwardingPipelineConfig on %s" % s1.name + s2.SetForwardingPipelineConfig(p4info=p4info_helper.p4info, + bmv2_json_file_path=bmv2_file_path) + print "Installed P4 Program using SetForwardingPipelineConfig on %s" % s2.name + + # 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") + + # 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") + + # TODO Uncomment the following two lines to read table entries from s1 and s2 + #readTableRules(p4info_helper, s1) + #readTableRules(p4info_helper, s2) + + # Print the tunnel counters every 2 seconds + try: + while True: + sleep(2) + print '\n----- Reading tunnel counters -----' + printCounter(p4info_helper, s1, "MyIngress.ingressTunnelCounter", 100) + printCounter(p4info_helper, s2, "MyIngress.egressTunnelCounter", 100) + printCounter(p4info_helper, s2, "MyIngress.ingressTunnelCounter", 200) + printCounter(p4info_helper, s1, "MyIngress.egressTunnelCounter", 200) + except KeyboardInterrupt: + print " Shutting down." + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='P4Runtime Controller') + parser.add_argument('--p4info', help='p4info proto in text format from p4c', + type=str, action="store", required=False, + default='./build/advanced_tunnel.p4info') + parser.add_argument('--bmv2-json', help='BMv2 JSON file from p4c', + type=str, action="store", required=False, + default='./build/advanced_tunnel.json') + args = parser.parse_args() + + if not os.path.exists(args.p4info): + parser.print_help() + print "\np4info file not found: %s\nHave you run 'make'?" % args.p4info + parser.exit(1) + if not os.path.exists(args.bmv2_json): + parser.print_help() + print "\nBMv2 JSON file not found: %s\nHave you run 'make'?" % args.bmv2_json + parser.exit(1) + + main(args.p4info, args.bmv2_json) diff --git a/P4D2_2018_East/exercises/p4runtime/p4runtime_lib/__init__.py b/P4D2_2018_East/exercises/p4runtime/p4runtime_lib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/P4D2_2018_East/exercises/p4runtime/p4runtime_lib/bmv2.py b/P4D2_2018_East/exercises/p4runtime/p4runtime_lib/bmv2.py new file mode 100644 index 0000000..7f483f4 --- /dev/null +++ b/P4D2_2018_East/exercises/p4runtime/p4runtime_lib/bmv2.py @@ -0,0 +1,30 @@ +# Copyright 2017-present Open Networking Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from switch import SwitchConnection +from p4.tmp import p4config_pb2 + + +def buildDeviceConfig(bmv2_json_file_path=None): + "Builds the device config for BMv2" + device_config = p4config_pb2.P4DeviceConfig() + device_config.reassign = True + with open(bmv2_json_file_path) as f: + device_config.device_data = f.read() + return device_config + + +class Bmv2SwitchConnection(SwitchConnection): + def buildDeviceConfig(self, **kwargs): + return buildDeviceConfig(**kwargs) diff --git a/P4D2_2018_East/exercises/p4runtime/p4runtime_lib/convert.py b/P4D2_2018_East/exercises/p4runtime/p4runtime_lib/convert.py new file mode 100644 index 0000000..0375e17 --- /dev/null +++ b/P4D2_2018_East/exercises/p4runtime/p4runtime_lib/convert.py @@ -0,0 +1,119 @@ +# Copyright 2017-present Open Networking Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +import re +import socket + +import math + +''' +This package contains several helper functions for encoding to and decoding from byte strings: +- integers +- IPv4 address strings +- Ethernet address strings +''' + +mac_pattern = re.compile('^([\da-fA-F]{2}:){5}([\da-fA-F]{2})$') +def matchesMac(mac_addr_string): + return mac_pattern.match(mac_addr_string) is not None + +def encodeMac(mac_addr_string): + return mac_addr_string.replace(':', '').decode('hex') + +def decodeMac(encoded_mac_addr): + return ':'.join(s.encode('hex') for s in encoded_mac_addr) + +ip_pattern = re.compile('^(\d{1,3}\.){3}(\d{1,3})$') +def matchesIPv4(ip_addr_string): + return ip_pattern.match(ip_addr_string) is not None + +def encodeIPv4(ip_addr_string): + return socket.inet_aton(ip_addr_string) + +def decodeIPv4(encoded_ip_addr): + return socket.inet_ntoa(encoded_ip_addr) + +def bitwidthToBytes(bitwidth): + return int(math.ceil(bitwidth / 8.0)) + +def encodeNum(number, bitwidth): + byte_len = bitwidthToBytes(bitwidth) + num_str = '%x' % number + if number >= 2 ** bitwidth: + raise Exception("Number, %d, does not fit in %d bits" % (number, bitwidth)) + return ('0' * (byte_len * 2 - len(num_str)) + num_str).decode('hex') + +def decodeNum(encoded_number): + return int(encoded_number.encode('hex'), 16) + +def encode(x, bitwidth): + 'Tries to infer the type of `x` and encode it' + byte_len = bitwidthToBytes(bitwidth) + if (type(x) == list or type(x) == tuple) and len(x) == 1: + x = x[0] + encoded_bytes = None + if type(x) == str: + if matchesMac(x): + encoded_bytes = encodeMac(x) + elif matchesIPv4(x): + encoded_bytes = encodeIPv4(x) + else: + # Assume that the string is already encoded + encoded_bytes = x + elif type(x) == int: + encoded_bytes = encodeNum(x, bitwidth) + else: + raise Exception("Encoding objects of %r is not supported" % type(x)) + assert(len(encoded_bytes) == byte_len) + return encoded_bytes + +if __name__ == '__main__': + # TODO These tests should be moved out of main eventually + mac = "aa:bb:cc:dd:ee:ff" + enc_mac = encodeMac(mac) + assert(enc_mac == '\xaa\xbb\xcc\xdd\xee\xff') + dec_mac = decodeMac(enc_mac) + assert(mac == dec_mac) + + ip = "10.0.0.1" + enc_ip = encodeIPv4(ip) + assert(enc_ip == '\x0a\x00\x00\x01') + dec_ip = decodeIPv4(enc_ip) + assert(ip == dec_ip) + + num = 1337 + byte_len = 5 + enc_num = encodeNum(num, byte_len * 8) + assert(enc_num == '\x00\x00\x00\x05\x39') + dec_num = decodeNum(enc_num) + assert(num == dec_num) + + assert(matchesIPv4('10.0.0.1')) + assert(not matchesIPv4('10.0.0.1.5')) + assert(not matchesIPv4('1000.0.0.1')) + assert(not matchesIPv4('10001')) + + assert(encode(mac, 6 * 8) == enc_mac) + assert(encode(ip, 4 * 8) == enc_ip) + assert(encode(num, 5 * 8) == enc_num) + assert(encode((num,), 5 * 8) == enc_num) + assert(encode([num], 5 * 8) == enc_num) + + num = 256 + byte_len = 2 + try: + enc_num = encodeNum(num, 8) + raise Exception("expected exception") + except Exception as e: + print e diff --git a/P4D2_2018_East/exercises/p4runtime/p4runtime_lib/helper.py b/P4D2_2018_East/exercises/p4runtime/p4runtime_lib/helper.py new file mode 100644 index 0000000..4906567 --- /dev/null +++ b/P4D2_2018_East/exercises/p4runtime/p4runtime_lib/helper.py @@ -0,0 +1,183 @@ +# Copyright 2017-present Open Networking Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +import re + +import google.protobuf.text_format +from p4 import p4runtime_pb2 +from p4.config import p4info_pb2 + +from p4runtime_lib.convert import encode + +class P4InfoHelper(object): + def __init__(self, p4_info_filepath): + p4info = p4info_pb2.P4Info() + # Load the p4info file into a skeleton P4Info object + with open(p4_info_filepath) as p4info_f: + google.protobuf.text_format.Merge(p4info_f.read(), p4info) + self.p4info = p4info + + def get(self, entity_type, name=None, id=None): + if name is not None and id is not None: + raise AssertionError("name or id must be None") + + for o in getattr(self.p4info, entity_type): + pre = o.preamble + if name: + if (pre.name == name or pre.alias == name): + return o + else: + if pre.id == id: + return o + + if name: + raise AttributeError("Could not find %r of type %s" % (name, entity_type)) + else: + raise AttributeError("Could not find id %r of type %s" % (id, entity_type)) + + def get_id(self, entity_type, name): + return self.get(entity_type, name=name).preamble.id + + def get_name(self, entity_type, id): + return self.get(entity_type, id=id).preamble.name + + def get_alias(self, entity_type, id): + return self.get(entity_type, id=id).preamble.alias + + def __getattr__(self, attr): + # Synthesize convenience functions for name to id lookups for top-level entities + # e.g. get_tables_id(name_string) or get_actions_id(name_string) + m = re.search("^get_(\w+)_id$", attr) + if m: + primitive = m.group(1) + return lambda name: self.get_id(primitive, name) + + # Synthesize convenience functions for id to name lookups + # e.g. get_tables_name(id) or get_actions_name(id) + m = re.search("^get_(\w+)_name$", attr) + if m: + primitive = m.group(1) + return lambda id: self.get_name(primitive, id) + + raise AttributeError("%r object has no attribute %r" % (self.__class__, attr)) + + def get_match_field(self, table_name, name=None, id=None): + for t in self.p4info.tables: + pre = t.preamble + if pre.name == table_name: + for mf in t.match_fields: + if name is not None: + if mf.name == name: + return mf + elif id is not None: + if mf.id == id: + return mf + raise AttributeError("%r has no attribute %r" % (table_name, name if name is not None else id)) + + def get_match_field_id(self, table_name, match_field_name): + return self.get_match_field(table_name, name=match_field_name).id + + def get_match_field_name(self, table_name, match_field_id): + return self.get_match_field(table_name, id=match_field_id).name + + def get_match_field_pb(self, table_name, match_field_name, value): + p4info_match = self.get_match_field(table_name, match_field_name) + bitwidth = p4info_match.bitwidth + p4runtime_match = p4runtime_pb2.FieldMatch() + p4runtime_match.field_id = p4info_match.id + match_type = p4info_match.match_type + if match_type == p4info_pb2.MatchField.VALID: + valid = p4runtime_match.valid + valid.value = bool(value) + elif match_type == p4info_pb2.MatchField.EXACT: + exact = p4runtime_match.exact + exact.value = encode(value, bitwidth) + elif match_type == p4info_pb2.MatchField.LPM: + lpm = p4runtime_match.lpm + lpm.value = encode(value[0], bitwidth) + lpm.prefix_len = value[1] + elif match_type == p4info_pb2.MatchField.TERNARY: + lpm = p4runtime_match.ternary + lpm.value = encode(value[0], bitwidth) + lpm.mask = encode(value[1], bitwidth) + elif match_type == p4info_pb2.MatchField.RANGE: + lpm = p4runtime_match.range + lpm.low = encode(value[0], bitwidth) + lpm.high = encode(value[1], bitwidth) + else: + raise Exception("Unsupported match type with type %r" % match_type) + return p4runtime_match + + def get_match_field_value(self, match_field): + match_type = match_field.WhichOneof("field_match_type") + if match_type == 'valid': + return match_field.valid.value + elif match_type == 'exact': + return match_field.exact.value + elif match_type == 'lpm': + return (match_field.lpm.value, match_field.lpm.prefix_len) + elif match_type == 'ternary': + return (match_field.ternary.value, match_field.ternary.mask) + elif match_type == 'range': + return (match_field.range.low, match_field.range.high) + else: + raise Exception("Unsupported match type with type %r" % match_type) + + def get_action_param(self, action_name, name=None, id=None): + for a in self.p4info.actions: + pre = a.preamble + if pre.name == action_name: + for p in a.params: + if name is not None: + if p.name == name: + return p + elif id is not None: + if p.id == id: + return p + raise AttributeError("action %r has no param %r" % (action_name, name if name is not None else id)) + + def get_action_param_id(self, action_name, param_name): + return self.get_action_param(action_name, name=param_name).id + + def get_action_param_name(self, action_name, param_id): + return self.get_action_param(action_name, id=param_id).name + + def get_action_param_pb(self, action_name, param_name, value): + p4info_param = self.get_action_param(action_name, param_name) + p4runtime_param = p4runtime_pb2.Action.Param() + p4runtime_param.param_id = p4info_param.id + p4runtime_param.value = encode(value, p4info_param.bitwidth) + return p4runtime_param + + def buildTableEntry(self, + table_name, + match_fields={}, + action_name=None, + action_params={}): + table_entry = p4runtime_pb2.TableEntry() + table_entry.table_id = self.get_tables_id(table_name) + if match_fields: + table_entry.match.extend([ + self.get_match_field_pb(table_name, match_field_name, value) + for match_field_name, value in match_fields.iteritems() + ]) + if action_name: + action = table_entry.action.action + action.action_id = self.get_actions_id(action_name) + if action_params: + action.params.extend([ + self.get_action_param_pb(action_name, field_name, value) + for field_name, value in action_params.iteritems() + ]) + return table_entry \ No newline at end of file diff --git a/P4D2_2018_East/exercises/p4runtime/p4runtime_lib/switch.py b/P4D2_2018_East/exercises/p4runtime/p4runtime_lib/switch.py new file mode 100644 index 0000000..3322b8c --- /dev/null +++ b/P4D2_2018_East/exercises/p4runtime/p4runtime_lib/switch.py @@ -0,0 +1,88 @@ +# Copyright 2017-present Open Networking Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from abc import abstractmethod + +import grpc +from p4 import p4runtime_pb2 +from p4.tmp import p4config_pb2 + +class SwitchConnection(object): + def __init__(self, name, address='127.0.0.1:50051', device_id=0): + self.name = name + self.address = address + self.device_id = device_id + self.p4info = None + self.channel = grpc.insecure_channel(self.address) + self.client_stub = p4runtime_pb2.P4RuntimeStub(self.channel) + + @abstractmethod + def buildDeviceConfig(self, **kwargs): + return p4config_pb2.P4DeviceConfig() + + def SetForwardingPipelineConfig(self, p4info, dry_run=False, **kwargs): + device_config = self.buildDeviceConfig(**kwargs) + request = p4runtime_pb2.SetForwardingPipelineConfigRequest() + request.device_id = self.device_id + config = request.config + config.p4info.CopyFrom(p4info) + config.p4_device_config = device_config.SerializeToString() + request.action = p4runtime_pb2.SetForwardingPipelineConfigRequest.VERIFY_AND_COMMIT + if dry_run: + print "P4 Runtime SetForwardingPipelineConfig:", request + else: + self.client_stub.SetForwardingPipelineConfig(request) + + def WriteTableEntry(self, table_entry, dry_run=False): + request = p4runtime_pb2.WriteRequest() + request.device_id = self.device_id + update = request.updates.add() + update.type = p4runtime_pb2.Update.INSERT + update.entity.table_entry.CopyFrom(table_entry) + if dry_run: + print "P4 Runtime Write:", request + else: + self.client_stub.Write(request) + + def ReadTableEntries(self, table_id=None, dry_run=False): + request = p4runtime_pb2.ReadRequest() + request.device_id = self.device_id + entity = request.entities.add() + table_entry = entity.table_entry + if table_id is not None: + table_entry.table_id = table_id + else: + table_entry.table_id = 0 + if dry_run: + print "P4 Runtime Read:", request + else: + for response in self.client_stub.Read(request): + yield response + + def ReadCounters(self, counter_id=None, index=None, dry_run=False): + request = p4runtime_pb2.ReadRequest() + request.device_id = self.device_id + entity = request.entities.add() + counter_entry = entity.counter_entry + if counter_id is not None: + counter_entry.counter_id = counter_id + else: + counter_entry.counter_id = 0 + if index is not None: + counter_entry.index = index + if dry_run: + print "P4 Runtime Read:", request + else: + for response in self.client_stub.Read(request): + yield response diff --git a/P4D2_2018_East/exercises/p4runtime/solution/mycontroller.py b/P4D2_2018_East/exercises/p4runtime/solution/mycontroller.py new file mode 100755 index 0000000..283134d --- /dev/null +++ b/P4D2_2018_East/exercises/p4runtime/solution/mycontroller.py @@ -0,0 +1,206 @@ +#!/usr/bin/env python2 +import argparse +import os +from time import sleep + +# NOTE: Appending to the PYTHON_PATH is only required in the `solution` directory. +# It is not required for mycontroller.py in the top-level directory. +import sys +sys.path.append(os.path.join(os.path.dirname(__file__), '..')) + +import p4runtime_lib.bmv2 +import p4runtime_lib.helper + +SWITCH_TO_HOST_PORT = 1 +SWITCH_TO_SWITCH_PORT = 2 + +def writeTunnelRules(p4info_helper, ingress_sw, egress_sw, tunnel_id, + dst_eth_addr, dst_ip_addr): + ''' + Installs three rules: + 1) An tunnel ingress rule on the ingress switch in the ipv4_lpm table that + encapsulates traffic into a tunnel with the specified ID + 2) A transit rule on the ingress switch that forwards traffic based on + the specified ID + 3) An tunnel egress rule on the egress switch that decapsulates traffic + with the specified ID and sends it to the host + + :param p4info_helper: the P4Info helper + :param ingress_sw: the ingress switch connection + :param egress_sw: the egress switch connection + :param tunnel_id: the specified tunnel ID + :param dst_eth_addr: the destination IP to match in the ingress rule + :param dst_ip_addr: the destination Ethernet address to write in the + egress rule + ''' + # 1) Tunnel Ingress Rule + table_entry = p4info_helper.buildTableEntry( + table_name="MyIngress.ipv4_lpm", + match_fields={ + "hdr.ipv4.dstAddr": (dst_ip_addr, 32) + }, + action_name="MyIngress.myTunnel_ingress", + action_params={ + "dst_id": tunnel_id, + }) + ingress_sw.WriteTableEntry(table_entry) + print "Installed ingress tunnel rule on %s" % ingress_sw.name + + # 2) Tunnel Transit Rule + # The rule will need to be added to the myTunnel_exact table and match on + # the tunnel ID (hdr.myTunnel.dst_id). Traffic will need to be forwarded + # using the myTunnel_forward action on the port connected to the next switch. + # + # For our simple topology, switch 1 and switch 2 are connected using a + # link attached to port 2 on both switches. We have defined a variable at + # the top of the file, SWITCH_TO_SWITCH_PORT, that you can use as the output + # port for this action. + # + # We will only need a transit rule on the ingress switch because we are + # using a simple topology. In general, you'll need on transit rule for + # each switch in the path (except the last switch, which has the egress rule), + # and you will need to select the port dynamically for each switch based on + # your topology. + + table_entry = p4info_helper.buildTableEntry( + table_name="MyIngress.myTunnel_exact", + match_fields={ + "hdr.myTunnel.dst_id": tunnel_id + }, + action_name="MyIngress.myTunnel_forward", + action_params={ + "port": SWITCH_TO_SWITCH_PORT + }) + ingress_sw.WriteTableEntry(table_entry) + print "Installed transit tunnel rule on %s" % ingress_sw.name + + # 3) Tunnel Egress Rule + # For our simple topology, the host will always be located on the + # SWITCH_TO_HOST_PORT (port 1). + # In general, you will need to keep track of which port the host is + # connected to. + table_entry = p4info_helper.buildTableEntry( + table_name="MyIngress.myTunnel_exact", + match_fields={ + "hdr.myTunnel.dst_id": tunnel_id + }, + action_name="MyIngress.myTunnel_egress", + action_params={ + "dstAddr": dst_eth_addr, + "port": SWITCH_TO_HOST_PORT + }) + egress_sw.WriteTableEntry(table_entry) + print "Installed egress tunnel rule on %s" % egress_sw.name + +def readTableRules(p4info_helper, sw): + ''' + Reads the table entries from all tables on the switch. + + :param p4info_helper: the P4Info helper + :param sw: the switch connection + ''' + print '\n----- Reading tables rules for %s -----' % sw.name + for response in sw.ReadTableEntries(): + for entity in response.entities: + entry = entity.table_entry + # TODO For extra credit, you can use the p4info_helper to translate + # the IDs the entry to names + table_name = p4info_helper.get_tables_name(entry.table_id) + print '%s: ' % table_name, + for m in entry.match: + print p4info_helper.get_match_field_name(table_name, m.field_id), + print '%r' % (p4info_helper.get_match_field_value(m),), + action = entry.action.action + action_name = p4info_helper.get_actions_name(action.action_id) + print '->', action_name, + for p in action.params: + print p4info_helper.get_action_param_name(action_name, p.param_id), + print '%r' % p.value, + print + +def printCounter(p4info_helper, sw, counter_name, index): + ''' + Reads the specified counter at the specified index from the switch. In our + program, the index is the tunnel ID. If the index is 0, it will return all + values from the counter. + + :param p4info_helper: the P4Info helper + :param sw: the switch connection + :param counter_name: the name of the counter from the P4 program + :param index: the counter index (in our case, the tunnel ID) + ''' + for response in sw.ReadCounters(p4info_helper.get_counters_id(counter_name), index): + for entity in response.entities: + counter = entity.counter_entry + print "%s %s %d: %d packets (%d bytes)" % ( + sw.name, counter_name, index, + counter.data.packet_count, counter.data.byte_count + ) + + +def main(p4info_file_path, bmv2_file_path): + # Instantiate a P4 Runtime helper from the p4info file + p4info_helper = p4runtime_lib.helper.P4InfoHelper(p4info_file_path) + + # Create a switch connection object for s1 and s2; + # this is backed by a P4 Runtime gRPC connection + s1 = p4runtime_lib.bmv2.Bmv2SwitchConnection('s1', + address='127.0.0.1:50051', + device_id=0) + s2 = p4runtime_lib.bmv2.Bmv2SwitchConnection('s2', + address='127.0.0.1:50052', + device_id=1) + + # Install the P4 program on the switches + s1.SetForwardingPipelineConfig(p4info=p4info_helper.p4info, + bmv2_json_file_path=bmv2_file_path) + print "Installed P4 Program using SetForwardingPipelineConfig on %s" % s1.name + s2.SetForwardingPipelineConfig(p4info=p4info_helper.p4info, + bmv2_json_file_path=bmv2_file_path) + print "Installed P4 Program using SetForwardingPipelineConfig on %s" % s2.name + + # 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") + + # 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") + + # TODO Uncomment the following two lines to read table entries from s1 and s2 + readTableRules(p4info_helper, s1) + readTableRules(p4info_helper, s2) + + # Print the tunnel counters every 2 seconds + try: + while True: + sleep(2) + print '\n----- Reading tunnel counters -----' + printCounter(p4info_helper, s1, "MyIngress.ingressTunnelCounter", 100) + printCounter(p4info_helper, s2, "MyIngress.egressTunnelCounter", 100) + printCounter(p4info_helper, s2, "MyIngress.ingressTunnelCounter", 200) + printCounter(p4info_helper, s1, "MyIngress.egressTunnelCounter", 200) + except KeyboardInterrupt: + print " Shutting down." + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='P4Runtime Controller') + parser.add_argument('--p4info', help='p4info proto in text format from p4c', + type=str, action="store", required=False, + default='./build/advanced_tunnel.p4info') + parser.add_argument('--bmv2-json', help='BMv2 JSON file from p4c', + type=str, action="store", required=False, + default='./build/advanced_tunnel.json') + args = parser.parse_args() + + if not os.path.exists(args.p4info): + parser.print_help() + print "\np4info file not found: %s\nHave you run 'make'?" % args.p4info + parser.exit(1) + if not os.path.exists(args.bmv2_json): + parser.print_help() + print "\nBMv2 JSON file not found: %s\nHave you run 'make'?" % args.bmv2_json + parser.exit(1) + + main(args.p4info, args.bmv2_json) diff --git a/P4D2_2018_East/exercises/p4runtime/topology.json b/P4D2_2018_East/exercises/p4runtime/topology.json new file mode 100755 index 0000000..f8a5b48 --- /dev/null +++ b/P4D2_2018_East/exercises/p4runtime/topology.json @@ -0,0 +1,16 @@ +{ + "hosts": [ + "h1", + "h2", + "h3" + ], + "switches": { + "s1": {}, + "s2": {}, + "s3": {} + }, + "links": [ + ["h1", "s1"], ["s1", "s2"], ["s1", "s3"], + ["s3", "s2"], ["s2", "h2"], ["s3", "h3"] + ] +} diff --git a/P4D2_2018_East/exercises/source_routing/Makefile b/P4D2_2018_East/exercises/source_routing/Makefile new file mode 100644 index 0000000..f378756 --- /dev/null +++ b/P4D2_2018_East/exercises/source_routing/Makefile @@ -0,0 +1 @@ +include ../../utils/Makefile diff --git a/P4D2_2018_East/exercises/source_routing/README.md b/P4D2_2018_East/exercises/source_routing/README.md new file mode 100644 index 0000000..7b5f42a --- /dev/null +++ b/P4D2_2018_East/exercises/source_routing/README.md @@ -0,0 +1,147 @@ +# Implementing Source Routing + +## Introduction + +The objective of this exercise is to implement source routing. With +source routing, the source host guides each switch in the network to +send the packet to a specific port. The host puts a stack of output +ports in the packet. In this example, we just put the stack after +Ethernet header and select a special etherType to indicate that. Each +switch pops an item from the stack and forwards the packet according +to the specified port number. + +Your switch must parse the source routing stack. Each item has a bos +(bottom of stack) bit and a port number. The bos bit is 1 only for the +last entry of stack. Then at ingress, it should pop an entry from the +stack and set the egress port accordingly. Note that the last hop can +also revert back the etherType to `TYPE_IPV4`. + +> **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, +`source_routing.p4`, which initially drops all packets. Your job (in +the next step) will be to extend it to properly to route packets. + +Before that, let's compile the incomplete `source_routing.p4` and +bring up a network in Mininet to test its behavior. + +1. In your shell, run: + ```bash + make + ``` + This will: + * compile `source_routing.p4`, and + * start a Mininet instance with three switches (`s1`, `s2`, `s3`) configured + in a triangle, each connected to one host (`h1`, `h2`, `h3`). + Check the network topology using the `net` command in mininet. + You can also change the topology in topology.json + * The hosts are assigned IPs of `10.0.1.1`, `10.0.2.2`, etc + (`10.0..`). + +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: + ```bash + ./receive.py + ``` +4. In `h1`'s xterm, send a message from the client: + ```bash + ./send.py 10.0.2.2 + ``` + +5. Type a list of port numbers. say `2 3 2 2 1`. This should send the + packet through `h1`, `s1`, `s2`, `s3`, `s1`, `s2`, and + `h2`. However, `h2` will not receive the message. + +6. Type `q` to exit send.py and type `exit` to leave each xterm and + the Mininet command line. + +The message was not received because each switch is programmed with +`source_routing.p4`, which drops all packets on arrival. You can +verify this by looking at `build/logs/s1.log`. Your job is to extend +the P4 code so packets are delivered to their destination. + +## Step 2: Implement source routing + +The `source_routing.p4` file contains a skeleton P4 program with key +pieces of logic replaced by `TODO` comments. These should guide your +implementation---replace each `TODO` with logic implementing the +missing piece. + +A complete `source_routing.p4` will contain the following components: + +1. Header type definitions for Ethernet (`ethernet_t`) and IPv4 + (`ipv4_t`) and Source Route (`srcRoute_t`). +2. **TODO:** Parsers for Ethernet and Source Route that populate + `ethernet` and `srcRoutes` fields. +3. An action to drop a packet, using `mark_to_drop()`. +4. **TODO:** An action (called `srcRoute_nhop`), which will: + 1. Set the egress port for the next hop. + 2. remove the first entry of srcRoutes +5. A control with an `apply` block that: + 1. checks the existence of source routes. + 2. **TODO:** if statement to change etherent.etherType if it is the last hop + 3. **TODO:** call srcRoute_nhop action +6. A deparser that selects the order in which fields inserted into the outgoing + packet. +7. 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, your message from `h1` +should be delivered to `h2`. + +Check the `ttl` of the IP header. Each hop decrements `ttl`. The port +sequence `2 3 2 2 1`, forces the packet to have a loop, so the `ttl` +should be 59 at `h2`. Can you find the port sequence for the shortest +path? + +### Food for thought +* Can we change the program to handle both IPv4 forwarding and source +routing at the same time? +* How would you enhance your program to let the first switch add the +path, so that source routing would be transparent to end-hosts? + +### Troubleshooting + +There are several ways that problems might manifest: + +1. `source_routing.p4` fails to compile. In this case, `make` will + report the error emitted from the compiler and stop. +2. `source_routing.p4` compiles but switches or mininet do not start. + Do you have another instance of mininet running? Did the previous + run of mininet crash? if yes, check "Cleaning up Mininet" bellow. +3. `source_routing.p4` compiles but the switch does not process + packets in the desired way. The `/tmp/p4s..log` + files contain trace messages describing how each switch processes + each packet. The output is detailed and can help pinpoint logic + errors in your implementation. The + `build/-.pcap` also contains the pcap + of packets on each interface. Use `tcpdump -r -xxx` to + print the hexdump of the packets. + +#### Cleaning up Mininet + +In the cases above, `make` may leave a Mininet instance running in +the background. Use the following command to clean up these +instances: + +```bash +mn -c +``` + +## Next Steps + +Congratulations, your implementation works! Move on to +[Calculator](../calc). diff --git a/P4D2_2018_East/exercises/source_routing/receive.py b/P4D2_2018_East/exercises/source_routing/receive.py new file mode 100755 index 0000000..efb93d6 --- /dev/null +++ b/P4D2_2018_East/exercises/source_routing/receive.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python +import sys +import struct + +from scapy.all import sniff, sendp, hexdump, get_if_list, get_if_hwaddr, bind_layers +from scapy.all import Packet, IPOption +from scapy.all import IP, UDP, Raw, Ether +from scapy.layers.inet import _IPOption_HDR +from scapy.fields import * + +def get_if(): + ifs=get_if_list() + iface=None + for i in get_if_list(): + if "eth0" in i: + iface=i + break; + if not iface: + print "Cannot find eth0 interface" + exit(1) + return iface + +class IPOption_MRI(IPOption): + name = "MRI" + option = 31 + fields_desc = [ _IPOption_HDR, + FieldLenField("length", None, fmt="B", + length_of="swids", + adjust=lambda pkt,l:l+4), + ShortField("count", 0), + FieldListField("swids", + [], + IntField("", 0), + length_from=lambda pkt:pkt.count*4) ] +def handle_pkt(pkt): + print "got a packet" + pkt.show2() +# hexdump(pkt) + sys.stdout.flush() + +class SourceRoute(Packet): + fields_desc = [ BitField("bos", 0, 1), + BitField("port", 0, 15)] +class SourceRoutingTail(Packet): + fields_desc = [ XShortField("etherType", 0x800)] + +bind_layers(Ether, SourceRoute, type=0x1234) +bind_layers(SourceRoute, SourceRoute, bos=0) +bind_layers(SourceRoute, SourceRoutingTail, bos=1) + +def main(): + iface = 'h2-eth0' + print "sniffing on %s" % iface + sys.stdout.flush() + sniff(filter="udp and port 4321", iface = iface, + prn = lambda x: handle_pkt(x)) + +if __name__ == '__main__': + main() diff --git a/P4D2_2018_East/exercises/source_routing/s1-commands.txt b/P4D2_2018_East/exercises/source_routing/s1-commands.txt new file mode 100644 index 0000000..e69de29 diff --git a/P4D2_2018_East/exercises/source_routing/s2-commands.txt b/P4D2_2018_East/exercises/source_routing/s2-commands.txt new file mode 100644 index 0000000..e69de29 diff --git a/P4D2_2018_East/exercises/source_routing/s3-commands.txt b/P4D2_2018_East/exercises/source_routing/s3-commands.txt new file mode 100644 index 0000000..e69de29 diff --git a/P4D2_2018_East/exercises/source_routing/send.py b/P4D2_2018_East/exercises/source_routing/send.py new file mode 100755 index 0000000..21331db --- /dev/null +++ b/P4D2_2018_East/exercises/source_routing/send.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python +import argparse +import sys +import socket +import random +import struct + +from scapy.all import sendp, send, get_if_list, get_if_hwaddr, bind_layers +from scapy.all import Packet +from scapy.all import Ether, IP, UDP +from scapy.fields import * +import readline + +def get_if(): + ifs=get_if_list() + iface=None # "h1-eth0" + for i in get_if_list(): + if "eth0" in i: + iface=i + break; + if not iface: + print "Cannot find eth0 interface" + exit(1) + return iface + +class SourceRoute(Packet): + fields_desc = [ BitField("bos", 0, 1), + BitField("port", 0, 15)] + +bind_layers(Ether, SourceRoute, type=0x1234) +bind_layers(SourceRoute, SourceRoute, bos=0) +bind_layers(SourceRoute, IP, bos=1) + +def main(): + + if len(sys.argv)<2: + print 'pass 2 arguments: ' + exit(1) + + addr = socket.gethostbyname(sys.argv[1]) + iface = get_if() + print "sending on interface %s to %s" % (iface, str(addr)) + + while True: + print + s = str(raw_input('Type space separated port nums ' + '(example: "2 3 2 2 1") or "q" to quit: ')) + if s == "q": + break; + print + + i = 0 + pkt = Ether(src=get_if_hwaddr(iface), dst='ff:ff:ff:ff:ff:ff'); + for p in s.split(" "): + try: + pkt = pkt / SourceRoute(bos=0, port=int(p)) + i = i+1 + except ValueError: + pass + if pkt.haslayer(SourceRoute): + pkt.getlayer(SourceRoute, i).bos = 1 + + pkt = pkt / IP(dst=addr) / UDP(dport=4321, sport=1234) + pkt.show2() + sendp(pkt, iface=iface, verbose=False) + + #pkt = pkt / SourceRoute(bos=0, port=2) / SourceRoute(bos=0, port=3); + #pkt = pkt / SourceRoute(bos=0, port=2) / SourceRoute(bos=0, port=2); + #pkt = pkt / SourceRoute(bos=1, port=1) + + +if __name__ == '__main__': + main() diff --git a/P4D2_2018_East/exercises/source_routing/solution/source_routing.p4 b/P4D2_2018_East/exercises/source_routing/solution/source_routing.p4 new file mode 100644 index 0000000..30c497b --- /dev/null +++ b/P4D2_2018_East/exercises/source_routing/solution/source_routing.p4 @@ -0,0 +1,181 @@ +/* -*- P4_16 -*- */ +#include +#include + +const bit<16> TYPE_IPV4 = 0x800; +const bit<16> TYPE_SRCROUTING = 0x1234; + +#define MAX_HOPS 9 + +/************************************************************************* +*********************** 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 srcRoute_t { + bit<1> bos; + bit<15> port; +} + +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; + srcRoute_t[MAX_HOPS] srcRoutes; + 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_SRCROUTING: parse_srcRouting; + default: accept; + } + } + + state parse_srcRouting { + packet.extract(hdr.srcRoutes.next); + transition select(hdr.srcRoutes.last.bos) { + 1: parse_ipv4; + default: parse_srcRouting; + } + } + + 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(); + } + + action srcRoute_nhop() { + standard_metadata.egress_spec = (bit<9>)hdr.srcRoutes[0].port; + hdr.srcRoutes.pop_front(1); + } + + action srcRoute_finish() { + hdr.ethernet.etherType = TYPE_IPV4; + } + + action update_ttl(){ + hdr.ipv4.ttl = hdr.ipv4.ttl - 1; + } + + apply { + if (hdr.srcRoutes[0].isValid()){ + if (hdr.srcRoutes[0].bos == 1){ + srcRoute_finish(); + } + srcRoute_nhop(); + if (hdr.ipv4.isValid()){ + update_ttl(); + } + }else{ + 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 { } +} + +/************************************************************************* +*********************** D E P A R S E R ******************************* +*************************************************************************/ + +control MyDeparser(packet_out packet, in headers hdr) { + apply { + packet.emit(hdr.ethernet); + packet.emit(hdr.srcRoutes); + packet.emit(hdr.ipv4); + } +} + +/************************************************************************* +*********************** S W I T C H ******************************* +*************************************************************************/ + +V1Switch( +MyParser(), +MyVerifyChecksum(), +MyIngress(), +MyEgress(), +MyComputeChecksum(), +MyDeparser() +) main; diff --git a/P4D2_2018_East/exercises/source_routing/source_routing.p4 b/P4D2_2018_East/exercises/source_routing/source_routing.p4 new file mode 100644 index 0000000..a84479e --- /dev/null +++ b/P4D2_2018_East/exercises/source_routing/source_routing.p4 @@ -0,0 +1,191 @@ +/* -*- P4_16 -*- */ +#include +#include + +const bit<16> TYPE_IPV4 = 0x800; +const bit<16> TYPE_SRCROUTING = 0x1234; + +#define MAX_HOPS 9 + +/************************************************************************* +*********************** 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 srcRoute_t { + bit<1> bos; + bit<15> port; +} + +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; + srcRoute_t[MAX_HOPS] srcRoutes; + 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); + /* + * TODO: Modify the next line to select on hdr.ethernet.etherType + * If the value is TYPE_SRCROUTING transition to parse_srcRouting + * otherwise transition to accept. + */ + transition accept; + } + + state parse_srcRouting { + /* + * TODO: extract the next entry of hdr.srcRoutes + * while hdr.srcRoutes.last.bos is 0 transition to this state + * otherwise parse ipv4 + */ + transition 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(); + } + + action srcRoute_nhop() { + /* + * TODO: set standard_metadata.egress_spec + * to the port in hdr.srcRoutes[0] and + * pop an entry from hdr.srcRoutes + */ + } + + action srcRoute_finish() { + hdr.ethernet.etherType = TYPE_IPV4; + } + + action update_ttl(){ + hdr.ipv4.ttl = hdr.ipv4.ttl - 1; + } + + apply { + if (hdr.srcRoutes[0].isValid()){ + /* + * TODO: add logic to: + * - If final srcRoutes (top of stack has bos==1): + * - change etherType to IP + * - choose next hop and remove top of srcRoutes stack + */ + + if (hdr.ipv4.isValid()){ + update_ttl(); + } + }else{ + 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 { } +} + +/************************************************************************* +*********************** D E P A R S E R ******************************* +*************************************************************************/ + +control MyDeparser(packet_out packet, in headers hdr) { + apply { + packet.emit(hdr.ethernet); + packet.emit(hdr.srcRoutes); + packet.emit(hdr.ipv4); + } +} + +/************************************************************************* +*********************** S W I T C H ******************************* +*************************************************************************/ + +V1Switch( +MyParser(), +MyVerifyChecksum(), +MyIngress(), +MyEgress(), +MyComputeChecksum(), +MyDeparser() +) main; diff --git a/P4D2_2018_East/exercises/source_routing/topology.json b/P4D2_2018_East/exercises/source_routing/topology.json new file mode 100644 index 0000000..e33477c --- /dev/null +++ b/P4D2_2018_East/exercises/source_routing/topology.json @@ -0,0 +1,16 @@ +{ + "hosts": [ + "h1", + "h2", + "h3" + ], + "switches": { + "s1": { "cli_input" : "s1-commands.txt" }, + "s2": { "cli_input" : "s2-commands.txt" }, + "s3": { "cli_input" : "s3-commands.txt" } + }, + "links": [ + ["h1", "s1"], ["s1", "s2"], ["s1", "s3"], + ["s3", "s2"], ["s2", "h2"], ["s3", "h3"] + ] +} diff --git a/P4D2_2018_East/utils/Makefile b/P4D2_2018_East/utils/Makefile new file mode 100644 index 0000000..99e44fb --- /dev/null +++ b/P4D2_2018_East/utils/Makefile @@ -0,0 +1,42 @@ +BUILD_DIR = build +PCAP_DIR = pcaps +LOG_DIR = logs + +TOPO = topology.json +P4C = p4c-bm2-ss +RUN_SCRIPT = ../../utils/run_exercise.py + +source := $(wildcard *.p4) +outfile := $(source:.p4=.json) + +compiled_json := $(BUILD_DIR)/$(outfile) + +# Define NO_P4 to start BMv2 without a program +ifndef NO_P4 +run_args += -j $(compiled_json) +endif + +# Set BMV2_SWITCH_EXE to override the BMv2 target +ifdef BMV2_SWITCH_EXE +run_args += -b $(BMV2_SWITCH_EXE) +endif + +all: run + +run: build + sudo python $(RUN_SCRIPT) -t $(TOPO) $(run_args) + +stop: + sudo mn -c + +build: dirs $(compiled_json) + +$(BUILD_DIR)/%.json: %.p4 + $(P4C) --p4v 16 $(P4C_ARGS) -o $@ $< + +dirs: + mkdir -p $(BUILD_DIR) $(PCAP_DIR) $(LOG_DIR) + +clean: stop + rm -f *.pcap + rm -rf $(BUILD_DIR) $(PCAP_DIR) $(LOG_DIR) diff --git a/P4D2_2018_East/utils/mininet/appcontroller.py b/P4D2_2018_East/utils/mininet/appcontroller.py new file mode 100644 index 0000000..2cf9732 --- /dev/null +++ b/P4D2_2018_East/utils/mininet/appcontroller.py @@ -0,0 +1,104 @@ +import subprocess + +from shortest_path import ShortestPath + +class AppController: + + def __init__(self, manifest=None, target=None, topo=None, net=None, links=None): + self.manifest = manifest + self.target = target + self.conf = manifest['targets'][target] + self.topo = topo + self.net = net + self.links = links + + def read_entries(self, filename): + entries = [] + with open(filename, 'r') as f: + for line in f: + line = line.strip() + if line == '': continue + entries.append(line) + return entries + + def add_entries(self, thrift_port=9090, sw=None, entries=None): + assert entries + if sw: thrift_port = sw.thrift_port + + print '\n'.join(entries) + p = subprocess.Popen(['simple_switch_CLI', '--thrift-port', str(thrift_port)], stdin=subprocess.PIPE) + p.communicate(input='\n'.join(entries)) + + def read_register(self, register, idx, thrift_port=9090, sw=None): + if sw: thrift_port = sw.thrift_port + p = subprocess.Popen(['simple_switch_CLI', '--thrift-port', str(thrift_port)], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout, stderr = p.communicate(input="register_read %s %d" % (register, idx)) + reg_val = filter(lambda l: ' %s[%d]' % (register, idx) in l, stdout.split('\n'))[0].split('= ', 1)[1] + return long(reg_val) + + def start(self): + shortestpath = ShortestPath(self.links) + entries = {} + for sw in self.topo.switches(): + entries[sw] = [] + if 'switches' in self.conf and sw in self.conf['switches'] and 'entries' in self.conf['switches'][sw]: + extra_entries = self.conf['switches'][sw]['entries'] + if type(extra_entries) == list: # array of entries + entries[sw] += extra_entries + else: # path to file that contains entries + entries[sw] += self.read_entries(extra_entries) + #entries[sw] += [ + # 'table_set_default send_frame _drop', + # 'table_set_default forward _drop', + # 'table_set_default ipv4_lpm _drop'] + + for host_name in self.topo._host_links: + h = self.net.get(host_name) + for link in self.topo._host_links[host_name].values(): + sw = link['sw'] + #entries[sw].append('table_add send_frame rewrite_mac %d => %s' % (link['sw_port'], link['sw_mac'])) + #entries[sw].append('table_add forward set_dmac %s => %s' % (link['host_ip'], link['host_mac'])) + #entries[sw].append('table_add ipv4_lpm set_nhop %s/32 => %s %d' % (link['host_ip'], link['host_ip'], link['sw_port'])) + iface = h.intfNames()[link['idx']] + # use mininet to set ip and mac to let it know the change + h.setIP(link['host_ip'], 24) + h.setMAC(link['host_mac']) + #h.cmd('ifconfig %s %s hw ether %s' % (iface, link['host_ip'], link['host_mac'])) + h.cmd('arp -i %s -s %s %s' % (iface, link['sw_ip'], link['sw_mac'])) + h.cmd('ethtool --offload %s rx off tx off' % iface) + h.cmd('ip route add %s dev %s' % (link['sw_ip'], iface)) + h.setDefaultRoute("via %s" % link['sw_ip']) + + for h in self.net.hosts: + h_link = self.topo._host_links[h.name].values()[0] + for sw in self.net.switches: + path = shortestpath.get(sw.name, h.name, exclude=lambda n: n[0]=='h') + if not path: continue + if not path[1][0] == 's': continue # next hop is a switch + sw_link = self.topo._sw_links[sw.name][path[1]] + #entries[sw.name].append('table_add send_frame rewrite_mac %d => %s' % (sw_link[0]['port'], sw_link[0]['mac'])) + #entries[sw.name].append('table_add forward set_dmac %s => %s' % (h_link['host_ip'], sw_link[1]['mac'])) + #entries[sw.name].append('table_add ipv4_lpm set_nhop %s/32 => %s %d' % (h_link['host_ip'], h_link['host_ip'], sw_link[0]['port'])) + + for h2 in self.net.hosts: + if h == h2: continue + path = shortestpath.get(h.name, h2.name, exclude=lambda n: n[0]=='h') + if not path: continue + h_link = self.topo._host_links[h.name][path[1]] + h2_link = self.topo._host_links[h2.name].values()[0] + h.cmd('ip route add %s via %s' % (h2_link['host_ip'], h_link['sw_ip'])) + + + print "**********" + print "Configuring entries in p4 tables" + for sw_name in entries: + print + print "Configuring switch... %s" % sw_name + sw = self.net.get(sw_name) + if entries[sw_name]: + self.add_entries(sw=sw, entries=entries[sw_name]) + print "Configuration complete." + print "**********" + + def stop(self): + pass diff --git a/P4D2_2018_East/utils/mininet/apptopo.py b/P4D2_2018_East/utils/mininet/apptopo.py new file mode 100644 index 0000000..3491a3d --- /dev/null +++ b/P4D2_2018_East/utils/mininet/apptopo.py @@ -0,0 +1,70 @@ +from mininet.topo import Topo + +class AppTopo(Topo): + + def __init__(self, links, latencies={}, manifest=None, target=None, + log_dir="/tmp", bws={}, **opts): + Topo.__init__(self, **opts) + + nodes = sum(map(list, zip(*links)), []) + host_names = sorted(list(set(filter(lambda n: n[0] == 'h', nodes)))) + sw_names = sorted(list(set(filter(lambda n: n[0] == 's', nodes)))) + sw_ports = dict([(sw, []) for sw in sw_names]) + + self._host_links = {} + self._sw_links = dict([(sw, {}) for sw in sw_names]) + + for sw_name in sw_names: + self.addSwitch(sw_name, log_file="%s/%s.log" %(log_dir, sw_name)) + + for host_name in host_names: + host_num = int(host_name[1:]) + + self.addHost(host_name) + + self._host_links[host_name] = {} + host_links = filter(lambda l: l[0]==host_name or l[1]==host_name, links) + + sw_idx = 0 + for link in host_links: + sw = link[0] if link[0] != host_name else link[1] + sw_num = int(sw[1:]) + assert sw[0]=='s', "Hosts should be connected to switches, not " + str(sw) + host_ip = "10.0.%d.%d" % (sw_num, host_num) + host_mac = '00:00:00:00:%02x:%02x' % (sw_num, host_num) + delay_key = ''.join([host_name, sw]) + delay = latencies[delay_key] if delay_key in latencies else '0ms' + bw = bws[delay_key] if delay_key in bws else None + sw_ports[sw].append(host_name) + self._host_links[host_name][sw] = dict( + idx=sw_idx, + host_mac = host_mac, + host_ip = host_ip, + sw = sw, + sw_mac = "00:00:00:00:%02x:%02x" % (sw_num, host_num), + sw_ip = "10.0.%d.%d" % (sw_num, 254), + sw_port = sw_ports[sw].index(host_name)+1 + ) + self.addLink(host_name, sw, delay=delay, bw=bw, + addr1=host_mac, addr2=self._host_links[host_name][sw]['sw_mac']) + sw_idx += 1 + + for link in links: # only check switch-switch links + sw1, sw2 = link + if sw1[0] != 's' or sw2[0] != 's': continue + + delay_key = ''.join(sorted([sw1, sw2])) + delay = latencies[delay_key] if delay_key in latencies else '0ms' + bw = bws[delay_key] if delay_key in bws else None + + self.addLink(sw1, sw2, delay=delay, bw=bw)#, max_queue_size=10) + sw_ports[sw1].append(sw2) + sw_ports[sw2].append(sw1) + + sw1_num, sw2_num = int(sw1[1:]), int(sw2[1:]) + sw1_port = dict(mac="00:00:00:%02x:%02x:00" % (sw1_num, sw2_num), port=sw_ports[sw1].index(sw2)+1) + sw2_port = dict(mac="00:00:00:%02x:%02x:00" % (sw2_num, sw1_num), port=sw_ports[sw2].index(sw1)+1) + + self._sw_links[sw1][sw2] = [sw1_port, sw2_port] + self._sw_links[sw2][sw1] = [sw2_port, sw1_port] + diff --git a/P4D2_2018_East/utils/mininet/multi_switch_mininet.py b/P4D2_2018_East/utils/mininet/multi_switch_mininet.py new file mode 100755 index 0000000..0bb406f --- /dev/null +++ b/P4D2_2018_East/utils/mininet/multi_switch_mininet.py @@ -0,0 +1,243 @@ +#!/usr/bin/env python2 + +# Copyright 2013-present Barefoot Networks, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import signal +import os +import sys +import subprocess +import argparse +import json +import importlib +import re +from time import sleep + +from mininet.net import Mininet +from mininet.topo import Topo +from mininet.link import TCLink +from mininet.log import setLogLevel, info +from mininet.cli import CLI + +from p4_mininet import P4Switch, P4Host +import apptopo +import appcontroller + +parser = argparse.ArgumentParser(description='Mininet demo') +parser.add_argument('--behavioral-exe', help='Path to behavioral executable', + type=str, action="store", required=True) +parser.add_argument('--thrift-port', help='Thrift server port for table updates', + type=int, action="store", default=9090) +parser.add_argument('--bmv2-log', help='verbose messages in log file', action="store_true") +parser.add_argument('--cli', help="start the mininet cli", action="store_true") +parser.add_argument('--auto-control-plane', help='enable automatic control plane population', action="store_true") +parser.add_argument('--json', help='Path to JSON config file', + type=str, action="store", required=True) +parser.add_argument('--pcap-dump', help='Dump packets on interfaces to pcap files', + action="store_true") +parser.add_argument('--manifest', '-m', help='Path to manifest file', + type=str, action="store", required=True) +parser.add_argument('--target', '-t', help='Target in manifest file to run', + type=str, action="store", required=True) +parser.add_argument('--log-dir', '-l', help='Location to save output to', + type=str, action="store", required=True) +parser.add_argument('--cli-message', help='Message to print before starting CLI', + type=str, action="store", required=False, default=False) + + +args = parser.parse_args() + + +next_thrift_port = args.thrift_port + +def run_command(command): + return os.WEXITSTATUS(os.system(command)) + +def configureP4Switch(**switch_args): + class ConfiguredP4Switch(P4Switch): + def __init__(self, *opts, **kwargs): + global next_thrift_port + kwargs.update(switch_args) + kwargs['thrift_port'] = next_thrift_port + next_thrift_port += 1 + P4Switch.__init__(self, *opts, **kwargs) + return ConfiguredP4Switch + + +def main(): + + with open(args.manifest, 'r') as f: + manifest = json.load(f) + + conf = manifest['targets'][args.target] + params = conf['parameters'] if 'parameters' in conf else {} + + os.environ.update(dict(map(lambda (k,v): (k, str(v)), params.iteritems()))) + + def formatParams(s): + for param in params: + s = re.sub('\$'+param+'(\W|$)', str(params[param]) + r'\1', s) + s = s.replace('${'+param+'}', str(params[param])) + return s + + AppTopo = apptopo.AppTopo + AppController = appcontroller.AppController + + if 'topo_module' in conf: + sys.path.insert(0, os.path.dirname(args.manifest)) + topo_module = importlib.import_module(conf['topo_module']) + AppTopo = topo_module.CustomAppTopo + + if 'controller_module' in conf: + sys.path.insert(0, os.path.dirname(args.manifest)) + controller_module = importlib.import_module(conf['controller_module']) + AppController = controller_module.CustomAppController + + if not os.path.isdir(args.log_dir): + if os.path.exists(args.log_dir): raise Exception('Log dir exists and is not a dir') + os.mkdir(args.log_dir) + os.environ['P4APP_LOGDIR'] = args.log_dir + + + links = [l[:2] for l in conf['links']] + latencies = dict([(''.join(sorted(l[:2])), l[2]) for l in conf['links'] if len(l)>=3]) + bws = dict([(''.join(sorted(l[:2])), l[3]) for l in conf['links'] if len(l)>=4]) + + for host_name in sorted(conf['hosts'].keys()): + host = conf['hosts'][host_name] + if 'latency' not in host: continue + for a, b in links: + if a != host_name and b != host_name: continue + other = a if a != host_name else b + latencies[host_name+other] = host['latency'] + + for l in latencies: + if isinstance(latencies[l], (str, unicode)): + latencies[l] = formatParams(latencies[l]) + else: + latencies[l] = str(latencies[l]) + "ms" + + bmv2_log = args.bmv2_log or ('bmv2_log' in conf and conf['bmv2_log']) + pcap_dump = args.pcap_dump or ('pcap_dump' in conf and conf['pcap_dump']) + + topo = AppTopo(links, latencies, manifest=manifest, target=args.target, + log_dir=args.log_dir, bws=bws) + switchClass = configureP4Switch( + sw_path=args.behavioral_exe, + json_path=args.json, + log_console=bmv2_log, + pcap_dump=pcap_dump) + net = Mininet(topo = topo, + link = TCLink, + host = P4Host, + switch = switchClass, + controller = None) + net.start() + + sleep(1) + + controller = None + if args.auto_control_plane or 'controller_module' in conf: + controller = AppController(manifest=manifest, target=args.target, + topo=topo, net=net, links=links) + controller.start() + + + for h in net.hosts: + h.describe() + + if args.cli_message is not None: + with open(args.cli_message, 'r') as message_file: + print message_file.read() + + if args.cli or ('cli' in conf and conf['cli']): + CLI(net) + + stdout_files = dict() + return_codes = [] + host_procs = [] + + + def formatCmd(cmd): + for h in net.hosts: + cmd = cmd.replace(h.name, h.defaultIntf().updateIP()) + return cmd + + def _wait_for_exit(p, host): + print p.communicate() + if p.returncode is None: + p.wait() + print p.communicate() + return_codes.append(p.returncode) + if host_name in stdout_files: + stdout_files[host_name].flush() + stdout_files[host_name].close() + + print '\n'.join(map(lambda (k,v): "%s: %s"%(k,v), params.iteritems())) + '\n' + + for host_name in sorted(conf['hosts'].keys()): + host = conf['hosts'][host_name] + if 'cmd' not in host: continue + + h = net.get(host_name) + stdout_filename = os.path.join(args.log_dir, h.name + '.stdout') + stdout_files[h.name] = open(stdout_filename, 'w') + cmd = formatCmd(host['cmd']) + print h.name, cmd + p = h.popen(cmd, stdout=stdout_files[h.name], shell=True, preexec_fn=os.setpgrp) + if 'startup_sleep' in host: sleep(host['startup_sleep']) + + if 'wait' in host and host['wait']: + _wait_for_exit(p, host_name) + else: + host_procs.append((p, host_name)) + + for p, host_name in host_procs: + if 'wait' in conf['hosts'][host_name] and conf['hosts'][host_name]['wait']: + _wait_for_exit(p, host_name) + + + for p, host_name in host_procs: + if 'wait' in conf['hosts'][host_name] and conf['hosts'][host_name]['wait']: + continue + if p.returncode is None: + run_command('pkill -INT -P %d' % p.pid) + sleep(0.2) + rc = run_command('pkill -0 -P %d' % p.pid) # check if it's still running + if rc == 0: # the process group is still running, send TERM + sleep(1) # give it a little more time to exit gracefully + run_command('pkill -TERM -P %d' % p.pid) + _wait_for_exit(p, host_name) + + if 'after' in conf and 'cmd' in conf['after']: + cmds = conf['after']['cmd'] if type(conf['after']['cmd']) == list else [conf['after']['cmd']] + for cmd in cmds: + os.system(cmd) + + if controller: controller.stop() + + net.stop() + +# if bmv2_log: +# os.system('bash -c "cp /tmp/p4s.s*.log \'%s\'"' % args.log_dir) +# if pcap_dump: +# os.system('bash -c "cp *.pcap \'%s\'"' % args.log_dir) + + bad_codes = [rc for rc in return_codes if rc != 0] + if len(bad_codes): sys.exit(1) + +if __name__ == '__main__': + setLogLevel( 'info' ) + main() diff --git a/P4D2_2018_East/utils/mininet/p4_mininet.py b/P4D2_2018_East/utils/mininet/p4_mininet.py new file mode 100644 index 0000000..8abe79f --- /dev/null +++ b/P4D2_2018_East/utils/mininet/p4_mininet.py @@ -0,0 +1,161 @@ +# Copyright 2013-present Barefoot Networks, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from mininet.net import Mininet +from mininet.node import Switch, Host +from mininet.log import setLogLevel, info, error, debug +from mininet.moduledeps import pathCheck +from sys import exit +from time import sleep +import os +import tempfile +import socket + +class P4Host(Host): + def config(self, **params): + r = super(P4Host, self).config(**params) + + for off in ["rx", "tx", "sg"]: + cmd = "/sbin/ethtool --offload %s %s off" % (self.defaultIntf().name, off) + self.cmd(cmd) + + # disable IPv6 + self.cmd("sysctl -w net.ipv6.conf.all.disable_ipv6=1") + self.cmd("sysctl -w net.ipv6.conf.default.disable_ipv6=1") + self.cmd("sysctl -w net.ipv6.conf.lo.disable_ipv6=1") + + return r + + def describe(self, sw_addr=None, sw_mac=None): + print "**********" + print "Network configuration for: %s" % self.name + print "Default interface: %s\t%s\t%s" %( + self.defaultIntf().name, + self.defaultIntf().IP(), + self.defaultIntf().MAC() + ) + if sw_addr is not None or sw_mac is not None: + print "Default route to switch: %s (%s)" % (sw_addr, sw_mac) + print "**********" + +class P4Switch(Switch): + """P4 virtual switch""" + device_id = 0 + + def __init__(self, name, sw_path = None, json_path = None, + log_file = None, + thrift_port = None, + pcap_dump = False, + log_console = False, + verbose = False, + device_id = None, + enable_debugger = False, + **kwargs): + Switch.__init__(self, name, **kwargs) + assert(sw_path) + assert(json_path) + # make sure that the provided sw_path is valid + pathCheck(sw_path) + # make sure that the provided JSON file exists + if not os.path.isfile(json_path): + error("Invalid JSON file.\n") + exit(1) + self.sw_path = sw_path + self.json_path = json_path + self.verbose = verbose + self.log_file = log_file + if self.log_file is None: + self.log_file = "/tmp/p4s.{}.log".format(self.name) + self.output = open(self.log_file, 'w') + self.thrift_port = thrift_port + self.pcap_dump = pcap_dump + self.enable_debugger = enable_debugger + self.log_console = log_console + if device_id is not None: + self.device_id = device_id + P4Switch.device_id = max(P4Switch.device_id, device_id) + else: + self.device_id = P4Switch.device_id + P4Switch.device_id += 1 + self.nanomsg = "ipc:///tmp/bm-{}-log.ipc".format(self.device_id) + + @classmethod + def setup(cls): + pass + + def check_switch_started(self, pid): + """While the process is running (pid exists), we check if the Thrift + server has been started. If the Thrift server is ready, we assume that + the switch was started successfully. This is only reliable if the Thrift + server is started at the end of the init process""" + while True: + if not os.path.exists(os.path.join("/proc", str(pid))): + return False + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(0.5) + result = sock.connect_ex(("localhost", self.thrift_port)) + if result == 0: + return True + + def start(self, controllers): + "Start up a new P4 switch" + info("Starting P4 switch {}.\n".format(self.name)) + args = [self.sw_path] + for port, intf in self.intfs.items(): + if not intf.IP(): + args.extend(['-i', str(port) + "@" + intf.name]) + if self.pcap_dump: + args.append("--pcap") + # args.append("--useFiles") + if self.thrift_port: + args.extend(['--thrift-port', str(self.thrift_port)]) + if self.nanomsg: + args.extend(['--nanolog', self.nanomsg]) + args.extend(['--device-id', str(self.device_id)]) + P4Switch.device_id += 1 + args.append(self.json_path) + if self.enable_debugger: + args.append("--debugger") + if self.log_console: + args.append("--log-console") + info(' '.join(args) + "\n") + + pid = None + with tempfile.NamedTemporaryFile() as f: + # self.cmd(' '.join(args) + ' > /dev/null 2>&1 &') + self.cmd(' '.join(args) + ' >' + self.log_file + ' 2>&1 & echo $! >> ' + f.name) + pid = int(f.read()) + debug("P4 switch {} PID is {}.\n".format(self.name, pid)) + sleep(1) + if not self.check_switch_started(pid): + error("P4 switch {} did not start correctly." + "Check the switch log file.\n".format(self.name)) + exit(1) + info("P4 switch {} has been started.\n".format(self.name)) + + def stop(self): + "Terminate P4 switch." + self.output.flush() + self.cmd('kill %' + self.sw_path) + self.cmd('wait') + self.deleteIntfs() + + def attach(self, intf): + "Connect a data port" + assert(0) + + def detach(self, intf): + "Disconnect a data port" + assert(0) diff --git a/P4D2_2018_East/utils/mininet/shortest_path.py b/P4D2_2018_East/utils/mininet/shortest_path.py new file mode 100644 index 0000000..971b1b4 --- /dev/null +++ b/P4D2_2018_East/utils/mininet/shortest_path.py @@ -0,0 +1,78 @@ +class ShortestPath: + + def __init__(self, edges=[]): + self.neighbors = {} + for edge in edges: + self.addEdge(*edge) + + def addEdge(self, a, b): + if a not in self.neighbors: self.neighbors[a] = [] + if b not in self.neighbors[a]: self.neighbors[a].append(b) + + if b not in self.neighbors: self.neighbors[b] = [] + if a not in self.neighbors[b]: self.neighbors[b].append(a) + + def get(self, a, b, exclude=lambda node: False): + # Shortest path from a to b + return self._recPath(a, b, [], exclude) + + def _recPath(self, a, b, visited, exclude): + if a == b: return [a] + new_visited = visited + [a] + paths = [] + for neighbor in self.neighbors[a]: + if neighbor in new_visited: continue + if exclude(neighbor) and neighbor != b: continue + path = self._recPath(neighbor, b, new_visited, exclude) + if path: paths.append(path) + + paths.sort(key=len) + return [a] + paths[0] if len(paths) else None + +if __name__ == '__main__': + + edges = [ + (1, 2), + (1, 3), + (1, 5), + (2, 4), + (3, 4), + (3, 5), + (3, 6), + (4, 6), + (5, 6), + (7, 8) + + ] + sp = ShortestPath(edges) + + assert sp.get(1, 1) == [1] + assert sp.get(2, 2) == [2] + + assert sp.get(1, 2) == [1, 2] + assert sp.get(2, 1) == [2, 1] + + assert sp.get(1, 3) == [1, 3] + assert sp.get(3, 1) == [3, 1] + + assert sp.get(4, 6) == [4, 6] + assert sp.get(6, 4) == [6, 4] + + assert sp.get(2, 6) == [2, 4, 6] + assert sp.get(6, 2) == [6, 4, 2] + + assert sp.get(1, 6) in [[1, 3, 6], [1, 5, 6]] + assert sp.get(6, 1) in [[6, 3, 1], [6, 5, 1]] + + assert sp.get(2, 5) == [2, 1, 5] + assert sp.get(5, 2) == [5, 1, 2] + + assert sp.get(4, 5) in [[4, 3, 5], [4, 6, 5]] + assert sp.get(5, 4) in [[5, 3, 4], [6, 6, 4]] + + assert sp.get(7, 8) == [7, 8] + assert sp.get(8, 7) == [8, 7] + + assert sp.get(1, 7) == None + assert sp.get(7, 2) == None + diff --git a/P4D2_2018_East/utils/mininet/single_switch_mininet.py b/P4D2_2018_East/utils/mininet/single_switch_mininet.py new file mode 100755 index 0000000..e2e7636 --- /dev/null +++ b/P4D2_2018_East/utils/mininet/single_switch_mininet.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python2 + +# Copyright 2013-present Barefoot Networks, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from mininet.net import Mininet +from mininet.topo import Topo +from mininet.log import setLogLevel, info +from mininet.cli import CLI + +from p4_mininet import P4Switch, P4Host + +import argparse +from subprocess import PIPE, Popen +from time import sleep + +parser = argparse.ArgumentParser(description='Mininet demo') +parser.add_argument('--behavioral-exe', help='Path to behavioral executable', + type=str, action="store", required=True) +parser.add_argument('--thrift-port', help='Thrift server port for table updates', + type=int, action="store", default=9090) +parser.add_argument('--num-hosts', help='Number of hosts to connect to switch', + type=int, action="store", default=2) +parser.add_argument('--mode', choices=['l2', 'l3'], type=str, default='l3') +parser.add_argument('--json', help='Path to JSON config file', + type=str, action="store", required=True) +parser.add_argument('--log-file', help='Path to write the switch log file', + type=str, action="store", required=False) +parser.add_argument('--pcap-dump', help='Dump packets on interfaces to pcap files', + type=str, action="store", required=False, default=False) +parser.add_argument('--switch-config', help='simple_switch_CLI script to configure switch', + type=str, action="store", required=False, default=False) +parser.add_argument('--cli-message', help='Message to print before starting CLI', + type=str, action="store", required=False, default=False) + +args = parser.parse_args() + + +class SingleSwitchTopo(Topo): + "Single switch connected to n (< 256) hosts." + def __init__(self, sw_path, json_path, log_file, + thrift_port, pcap_dump, n, **opts): + # Initialize topology and default options + Topo.__init__(self, **opts) + + switch = self.addSwitch('s1', + sw_path = sw_path, + json_path = json_path, + log_console = True, + log_file = log_file, + thrift_port = thrift_port, + enable_debugger = False, + pcap_dump = pcap_dump) + + for h in xrange(n): + host = self.addHost('h%d' % (h + 1), + ip = "10.0.%d.10/24" % h, + mac = '00:04:00:00:00:%02x' %h) + print "Adding host", str(host) + self.addLink(host, switch) + +def main(): + num_hosts = args.num_hosts + mode = args.mode + + topo = SingleSwitchTopo(args.behavioral_exe, + args.json, + args.log_file, + args.thrift_port, + args.pcap_dump, + num_hosts) + net = Mininet(topo = topo, + host = P4Host, + switch = P4Switch, + controller = None) + net.start() + + + sw_mac = ["00:aa:bb:00:00:%02x" % n for n in xrange(num_hosts)] + + sw_addr = ["10.0.%d.1" % n for n in xrange(num_hosts)] + + for n in xrange(num_hosts): + h = net.get('h%d' % (n + 1)) + if mode == "l2": + h.setDefaultRoute("dev %s" % h.defaultIntf().name) + else: + h.setARP(sw_addr[n], sw_mac[n]) + h.setDefaultRoute("dev %s via %s" % (h.defaultIntf().name, sw_addr[n])) + + for n in xrange(num_hosts): + h = net.get('h%d' % (n + 1)) + h.describe(sw_addr[n], sw_mac[n]) + + sleep(1) + + if args.switch_config is not None: + print + print "Reading switch configuration script:", args.switch_config + with open(args.switch_config, 'r') as config_file: + switch_config = config_file.read() + + print "Configuring switch..." + proc = Popen(["simple_switch_CLI"], stdin=PIPE) + proc.communicate(input=switch_config) + + print "Configuration complete." + print + + print "Ready !" + + if args.cli_message is not None: + with open(args.cli_message, 'r') as message_file: + print message_file.read() + + CLI( net ) + net.stop() + +if __name__ == '__main__': + setLogLevel( 'info' ) + main() diff --git a/P4D2_2018_East/utils/netstat.py b/P4D2_2018_East/utils/netstat.py new file mode 100644 index 0000000..bb12ffd --- /dev/null +++ b/P4D2_2018_East/utils/netstat.py @@ -0,0 +1,21 @@ +# Copyright 2017-present Open Networking Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import psutil +def check_listening_on_port(port): + for c in psutil.net_connections(kind='inet'): + if c.status == 'LISTEN' and c.laddr[1] == port: + return True + return False diff --git a/P4D2_2018_East/utils/p4_mininet.py b/P4D2_2018_East/utils/p4_mininet.py new file mode 100644 index 0000000..b7fbbcd --- /dev/null +++ b/P4D2_2018_East/utils/p4_mininet.py @@ -0,0 +1,162 @@ +# Copyright 2013-present Barefoot Networks, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from mininet.net import Mininet +from mininet.node import Switch, Host +from mininet.log import setLogLevel, info, error, debug +from mininet.moduledeps import pathCheck +from sys import exit +import os +import tempfile +import socket +from time import sleep + +from netstat import check_listening_on_port + +SWITCH_START_TIMEOUT = 10 # seconds + +class P4Host(Host): + def config(self, **params): + r = super(Host, self).config(**params) + + self.defaultIntf().rename("eth0") + + for off in ["rx", "tx", "sg"]: + cmd = "/sbin/ethtool --offload eth0 %s off" % off + self.cmd(cmd) + + # disable IPv6 + self.cmd("sysctl -w net.ipv6.conf.all.disable_ipv6=1") + self.cmd("sysctl -w net.ipv6.conf.default.disable_ipv6=1") + self.cmd("sysctl -w net.ipv6.conf.lo.disable_ipv6=1") + + return r + + def describe(self): + print "**********" + print self.name + print "default interface: %s\t%s\t%s" %( + self.defaultIntf().name, + self.defaultIntf().IP(), + self.defaultIntf().MAC() + ) + print "**********" + +class P4Switch(Switch): + """P4 virtual switch""" + device_id = 0 + + def __init__(self, name, sw_path = None, json_path = None, + thrift_port = None, + pcap_dump = False, + log_console = False, + verbose = False, + device_id = None, + enable_debugger = False, + **kwargs): + Switch.__init__(self, name, **kwargs) + assert(sw_path) + assert(json_path) + # make sure that the provided sw_path is valid + pathCheck(sw_path) + # make sure that the provided JSON file exists + if not os.path.isfile(json_path): + error("Invalid JSON file.\n") + exit(1) + self.sw_path = sw_path + self.json_path = json_path + self.verbose = verbose + logfile = "/tmp/p4s.{}.log".format(self.name) + self.output = open(logfile, 'w') + self.thrift_port = thrift_port + if check_listening_on_port(self.thrift_port): + error('%s cannot bind port %d because it is bound by another process\n' % (self.name, self.grpc_port)) + exit(1) + self.pcap_dump = pcap_dump + self.enable_debugger = enable_debugger + self.log_console = log_console + if device_id is not None: + self.device_id = device_id + P4Switch.device_id = max(P4Switch.device_id, device_id) + else: + self.device_id = P4Switch.device_id + P4Switch.device_id += 1 + self.nanomsg = "ipc:///tmp/bm-{}-log.ipc".format(self.device_id) + + @classmethod + def setup(cls): + pass + + def check_switch_started(self, pid): + """While the process is running (pid exists), we check if the Thrift + server has been started. If the Thrift server is ready, we assume that + the switch was started successfully. This is only reliable if the Thrift + server is started at the end of the init process""" + while True: + if not os.path.exists(os.path.join("/proc", str(pid))): + return False + if check_listening_on_port(self.thrift_port): + return True + sleep(0.5) + + def start(self, controllers): + "Start up a new P4 switch" + info("Starting P4 switch {}.\n".format(self.name)) + args = [self.sw_path] + for port, intf in self.intfs.items(): + if not intf.IP(): + args.extend(['-i', str(port) + "@" + intf.name]) + if self.pcap_dump: + args.append("--pcap") + # args.append("--useFiles") + if self.thrift_port: + args.extend(['--thrift-port', str(self.thrift_port)]) + if self.nanomsg: + args.extend(['--nanolog', self.nanomsg]) + args.extend(['--device-id', str(self.device_id)]) + P4Switch.device_id += 1 + args.append(self.json_path) + if self.enable_debugger: + args.append("--debugger") + if self.log_console: + args.append("--log-console") + logfile = "/tmp/p4s.{}.log".format(self.name) + info(' '.join(args) + "\n") + + pid = None + with tempfile.NamedTemporaryFile() as f: + # self.cmd(' '.join(args) + ' > /dev/null 2>&1 &') + self.cmd(' '.join(args) + ' >' + logfile + ' 2>&1 & echo $! >> ' + f.name) + pid = int(f.read()) + debug("P4 switch {} PID is {}.\n".format(self.name, pid)) + if not self.check_switch_started(pid): + error("P4 switch {} did not start correctly.\n".format(self.name)) + exit(1) + info("P4 switch {} has been started.\n".format(self.name)) + + def stop(self): + "Terminate P4 switch." + self.output.flush() + self.cmd('kill %' + self.sw_path) + self.cmd('wait') + self.deleteIntfs() + + def attach(self, intf): + "Connect a data port" + assert(0) + + def detach(self, intf): + "Disconnect a data port" + assert(0) diff --git a/P4D2_2018_East/utils/p4apprunner.py b/P4D2_2018_East/utils/p4apprunner.py new file mode 100755 index 0000000..36b9eea --- /dev/null +++ b/P4D2_2018_East/utils/p4apprunner.py @@ -0,0 +1,320 @@ +#!/usr/bin/env python2 +# Copyright 2013-present Barefoot Networks, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import print_function + +import argparse +from collections import OrderedDict +import json +import os +import sys +import tarfile + +parser = argparse.ArgumentParser(description='p4apprunner') +parser.add_argument('--build-dir', help='Directory to build in.', + type=str, action='store', required=False, default='/tmp') +parser.add_argument('--quiet', help='Suppress log messages.', + action='store_true', required=False, default=False) +parser.add_argument('--manifest', help='Path to manifest file.', + type=str, action='store', required=False, default='./p4app.json') +parser.add_argument('app', help='.p4app package to run.', type=str) +parser.add_argument('target', help=('Target to run. Defaults to the first target ' + 'in the package.'), + nargs='?', type=str) + +args = parser.parse_args() + +def log(*items): + if args.quiet != True: + print(*items) + +def log_error(*items): + print(*items, file=sys.stderr) + +def run_command(command): + log('>', command) + return os.WEXITSTATUS(os.system(command)) + +class Manifest: + def __init__(self, program_file, language, target, target_config): + self.program_file = program_file + self.language = language + self.target = target + self.target_config = target_config + +def read_manifest(manifest_file): + manifest = json.load(manifest_file, object_pairs_hook=OrderedDict) + + if 'program' not in manifest: + log_error('No program defined in manifest.') + sys.exit(1) + program_file = manifest['program'] + + if 'language' not in manifest: + log_error('No language defined in manifest.') + sys.exit(1) + language = manifest['language'] + + if 'targets' not in manifest or len(manifest['targets']) < 1: + log_error('No targets defined in manifest.') + sys.exit(1) + + if args.target is not None: + chosen_target = args.target + elif 'default-target' in manifest: + chosen_target = manifest['default-target'] + else: + chosen_target = manifest['targets'].keys()[0] + + if chosen_target not in manifest['targets']: + log_error('Target not found in manifest:', chosen_target) + sys.exit(1) + + return Manifest(program_file, language, chosen_target, manifest['targets'][chosen_target]) + + +def run_compile_bmv2(manifest): + if 'run-before-compile' in manifest.target_config: + commands = manifest.target_config['run-before-compile'] + if not isinstance(commands, list): + log_error('run-before-compile should be a list:', commands) + sys.exit(1) + for command in commands: + run_command(command) + + compiler_args = [] + + if manifest.language == 'p4-14': + compiler_args.append('--p4v 14') + elif manifest.language == 'p4-16': + compiler_args.append('--p4v 16') + else: + log_error('Unknown language:', manifest.language) + sys.exit(1) + + if 'compiler-flags' in manifest.target_config: + flags = manifest.target_config['compiler-flags'] + if not isinstance(flags, list): + log_error('compiler-flags should be a list:', flags) + sys.exit(1) + compiler_args.extend(flags) + + # Compile the program. + output_file = manifest.program_file + '.json' + compiler_args.append('"%s"' % manifest.program_file) + compiler_args.append('-o "%s"' % output_file) + rv = run_command('p4c-bm2-ss %s' % ' '.join(compiler_args)) + + if 'run-after-compile' in manifest.target_config: + commands = manifest.target_config['run-after-compile'] + if not isinstance(commands, list): + log_error('run-after-compile should be a list:', commands) + sys.exit(1) + for command in commands: + run_command(command) + + if rv != 0: + log_error('Compile failed.') + sys.exit(1) + + return output_file + +def run_mininet(manifest): + output_file = run_compile_bmv2(manifest) + + # Run the program using the BMV2 Mininet simple switch. + switch_args = [] + + # We'll place the switch's log file in current (build) folder. + cwd = os.getcwd() + log_file = os.path.join(cwd, manifest.program_file + '.log') + print ("*** Log file %s" % log_file) + switch_args.append('--log-file "%s"' % log_file) + + pcap_dir = os.path.join(cwd) + print ("*** Pcap folder %s" % pcap_dir) + switch_args.append('--pcap-dump "%s" '% pcap_dir) + + # Generate a message that will be printed by the Mininet CLI to make + # interacting with the simple switch a little easier. + message_file = 'mininet_message.txt' + with open(message_file, 'w') as message: + + print(file=message) + print('======================================================================', + file=message) + print('Welcome to the BMV2 Mininet CLI!', file=message) + print('======================================================================', + file=message) + print('Your P4 program is installed into the BMV2 software switch', file=message) + print('and your initial configuration is loaded. You can interact', file=message) + print('with the network using the mininet CLI below.', file=message) + print(file=message) + print('To inspect or change the switch configuration, connect to', file=message) + print('its CLI from your host operating system using this command:', file=message) + print(' simple_switch_CLI', file=message) + print(file=message) + print('To view the switch log, run this command from your host OS:', file=message) + print(' tail -f %s' % log_file, file=message) + print(file=message) + print('To view the switch output pcap, check the pcap files in %s:' % pcap_dir, file=message) + print(' for example run: sudo tcpdump -xxx -r s1-eth1.pcap', file=message) + print(file=message) +# print('To run the switch debugger, run this command from your host OS:', file=message) +# print(' bm_p4dbg' , file=message) +# print(file=message) + + switch_args.append('--cli-message "%s"' % message_file) + + if 'num-hosts' in manifest.target_config: + switch_args.append('--num-hosts %s' % manifest.target_config['num-hosts']) + + if 'switch-config' in manifest.target_config: + switch_args.append('--switch-config "%s"' % manifest.target_config['switch-config']) + + switch_args.append('--behavioral-exe "%s"' % 'simple_switch') + switch_args.append('--json "%s"' % output_file) + + program = '"%s/mininet/single_switch_mininet.py"' % sys.path[0] + return run_command('python2 %s %s' % (program, ' '.join(switch_args))) + +def run_multiswitch(manifest): + output_file = run_compile_bmv2(manifest) + + script_args = [] + cwd = os.getcwd() + log_dir = os.path.join(cwd, cwd + '/logs') + print ("*** Log directory %s" % log_dir) + script_args.append('--log-dir "%s"' % log_dir) + pcap_dir = os.path.join(cwd) + print ("*** Pcap directory %s" % cwd) + script_args.append('--manifest "%s"' % args.manifest) + script_args.append('--target "%s"' % manifest.target) + if 'auto-control-plane' in manifest.target_config and manifest.target_config['auto-control-plane']: + script_args.append('--auto-control-plane' ) + script_args.append('--behavioral-exe "%s"' % 'simple_switch') + script_args.append('--json "%s"' % output_file) + #script_args.append('--cli') + + # Generate a message that will be printed by the Mininet CLI to make + # interacting with the simple switch a little easier. + message_file = 'mininet_message.txt' + with open(message_file, 'w') as message: + + print(file=message) + print('======================================================================', + file=message) + print('Welcome to the BMV2 Mininet CLI!', file=message) + print('======================================================================', + file=message) + print('Your P4 program is installed into the BMV2 software switch', file=message) + print('and your initial configuration is loaded. You can interact', file=message) + print('with the network using the mininet CLI below.', file=message) + print(file=message) + print('To inspect or change the switch configuration, connect to', file=message) + print('its CLI from your host operating system using this command:', file=message) + print(' simple_switch_CLI --thrift-port ', file=message) + print(file=message) + print('To view a switch log, run this command from your host OS:', file=message) + print(' tail -f %s/.log' % log_dir, file=message) + print(file=message) + print('To view the switch output pcap, check the pcap files in %s:' % pcap_dir, file=message) + print(' for example run: sudo tcpdump -xxx -r s1-eth1.pcap', file=message) + print(file=message) +# print('To run the switch debugger, run this command from your host OS:', file=message) +# print(' bm_p4dbg' , file=message) +# print(file=message) + + script_args.append('--cli-message "%s"' % message_file) + + program = '"%s/mininet/multi_switch_mininet.py"' % sys.path[0] + return run_command('python2 %s %s' % (program, ' '.join(script_args))) + +def run_stf(manifest): + output_file = run_compile_bmv2(manifest) + + if not 'test' in manifest.target_config: + log_error('No STF test file provided.') + sys.exit(1) + stf_file = manifest.target_config['test'] + + # Run the program using the BMV2 STF interpreter. + stf_args = [] + stf_args.append('-v') + stf_args.append(os.path.join(args.build_dir, output_file)) + stf_args.append(os.path.join(args.build_dir, stf_file)) + + program = '"%s/stf/bmv2stf.py"' % sys.path[0] + rv = run_command('python2 %s %s' % (program, ' '.join(stf_args))) + if rv != 0: + sys.exit(1) + return rv + +def run_custom(manifest): + output_file = run_compile_bmv2(manifest) + python_path = 'PYTHONPATH=$PYTHONPATH:/scripts/mininet/' + script_args = [] + script_args.append('--behavioral-exe "%s"' % 'simple_switch') + script_args.append('--json "%s"' % output_file) + script_args.append('--cli "%s"' % 'simple_switch_CLI') + if not 'program' in manifest.target_config: + log_error('No mininet program file provided.') + sys.exit(1) + program = manifest.target_config['program'] + rv = run_command('%s python2 %s %s' % (python_path, program, ' '.join(script_args))) + + if rv != 0: + sys.exit(1) + return rv + +def main(): + log('Entering build directory.') + os.chdir(args.build_dir) + + # A '.p4app' package is really just a '.tar.gz' archive. Extract it so we + # can process its contents. + log('Extracting package.') + tar = tarfile.open(args.app) + tar.extractall() + tar.close() + + log('Reading package manifest.') + with open(args.manifest, 'r') as manifest_file: + manifest = read_manifest(manifest_file) + + # Dispatch to the backend implementation for this target. + backend = manifest.target + if 'use' in manifest.target_config: + backend = manifest.target_config['use'] + + if backend == 'mininet': + rc = run_mininet(manifest) + elif backend == 'multiswitch': + rc = run_multiswitch(manifest) + elif backend == 'stf': + rc = run_stf(manifest) + elif backend == 'custom': + rc = run_custom(manifest) + elif backend == 'compile-bmv2': + run_compile_bmv2(manifest) + rc = 0 + else: + log_error('Target specifies unknown backend:', backend) + sys.exit(1) + + sys.exit(rc) + +if __name__ == '__main__': + main() diff --git a/P4D2_2018_East/utils/p4runtime_switch.py b/P4D2_2018_East/utils/p4runtime_switch.py new file mode 100644 index 0000000..df919ed --- /dev/null +++ b/P4D2_2018_East/utils/p4runtime_switch.py @@ -0,0 +1,122 @@ +# Copyright 2017-present Barefoot Networks, Inc. +# Copyright 2017-present Open Networking Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import sys, os, tempfile, socket +from time import sleep + +from mininet.node import Switch +from mininet.moduledeps import pathCheck +from mininet.log import info, error, debug + +from p4_mininet import P4Switch, SWITCH_START_TIMEOUT +from netstat import check_listening_on_port + +class P4RuntimeSwitch(P4Switch): + "BMv2 switch with gRPC support" + next_grpc_port = 50051 + + def __init__(self, name, sw_path = None, json_path = None, + grpc_port = None, + pcap_dump = False, + log_console = False, + verbose = False, + device_id = None, + enable_debugger = False, + **kwargs): + Switch.__init__(self, name, **kwargs) + assert (sw_path) + self.sw_path = sw_path + # make sure that the provided sw_path is valid + pathCheck(sw_path) + + 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") + exit(1) + self.json_path = json_path + else: + self.json_path = None + + if grpc_port is not None: + self.grpc_port = grpc_port + else: + self.grpc_port = P4RuntimeSwitch.next_grpc_port + P4RuntimeSwitch.next_grpc_port += 1 + + if check_listening_on_port(self.grpc_port): + error('%s cannot bind port %d because it is bound by another process\n' % (self.name, self.grpc_port)) + exit(1) + + self.verbose = verbose + logfile = "/tmp/p4s.{}.log".format(self.name) + self.output = open(logfile, 'w') + self.pcap_dump = pcap_dump + self.enable_debugger = enable_debugger + self.log_console = log_console + if device_id is not None: + self.device_id = device_id + P4Switch.device_id = max(P4Switch.device_id, device_id) + else: + self.device_id = P4Switch.device_id + P4Switch.device_id += 1 + self.nanomsg = "ipc:///tmp/bm-{}-log.ipc".format(self.device_id) + + + def check_switch_started(self, pid): + for _ in range(SWITCH_START_TIMEOUT * 2): + if not os.path.exists(os.path.join("/proc", str(pid))): + return False + if check_listening_on_port(self.grpc_port): + return True + sleep(0.5) + + def start(self, controllers): + info("Starting P4 switch {}.\n".format(self.name)) + args = [self.sw_path] + for port, intf in self.intfs.items(): + if not intf.IP(): + args.extend(['-i', str(port) + "@" + intf.name]) + if self.pcap_dump: + args.append("--pcap") + if self.nanomsg: + args.extend(['--nanolog', self.nanomsg]) + args.extend(['--device-id', str(self.device_id)]) + P4Switch.device_id += 1 + if self.json_path: + args.append(self.json_path) + else: + args.append("--no-p4") + if self.enable_debugger: + args.append("--debugger") + if self.log_console: + args.append("--log-console") + if self.grpc_port: + args.append("-- --grpc-server-addr 0.0.0.0:" + str(self.grpc_port)) + cmd = ' '.join(args) + info(cmd + "\n") + + logfile = "/tmp/p4s.{}.log".format(self.name) + pid = None + with tempfile.NamedTemporaryFile() as f: + self.cmd(cmd + ' >' + logfile + ' 2>&1 & echo $! >> ' + f.name) + pid = int(f.read()) + debug("P4 switch {} PID is {}.\n".format(self.name, pid)) + if not self.check_switch_started(pid): + error("P4 switch {} did not start correctly.\n".format(self.name)) + exit(1) + info("P4 switch {} has been started.\n".format(self.name)) + diff --git a/P4D2_2018_East/utils/run_exercise.py b/P4D2_2018_East/utils/run_exercise.py new file mode 100755 index 0000000..84997d7 --- /dev/null +++ b/P4D2_2018_East/utils/run_exercise.py @@ -0,0 +1,382 @@ +#!/usr/bin/env python2 +# Copyright 2013-present Barefoot Networks, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Adapted by Robert MacDavid (macdavid@cs.princeton.edu) from scripts found in +# the p4app repository (https://github.com/p4lang/p4app) +# +# We encourage you to dissect this script to better understand the BMv2/Mininet +# environment used by the P4 tutorial. +# +import os, sys, json, subprocess, re, argparse +from time import sleep + +from p4_mininet import P4Switch, P4Host + +from mininet.net import Mininet +from mininet.topo import Topo +from mininet.link import TCLink +from mininet.cli import CLI + +from p4runtime_switch import P4RuntimeSwitch + +def configureP4Switch(**switch_args): + """ Helper class that is called by mininet to initialize + the virtual P4 switches. The purpose is to ensure each + switch's thrift server is using a unique port. + """ + if "sw_path" in switch_args and 'grpc' in switch_args['sw_path']: + # If grpc appears in the BMv2 switch target, we assume will start P4 Runtime + class ConfiguredP4RuntimeSwitch(P4RuntimeSwitch): + def __init__(self, *opts, **kwargs): + kwargs.update(switch_args) + P4RuntimeSwitch.__init__(self, *opts, **kwargs) + + def describe(self): + print "%s -> gRPC port: %d" % (self.name, self.grpc_port) + + return ConfiguredP4RuntimeSwitch + else: + class ConfiguredP4Switch(P4Switch): + next_thrift_port = 9090 + def __init__(self, *opts, **kwargs): + global next_thrift_port + kwargs.update(switch_args) + kwargs['thrift_port'] = ConfiguredP4Switch.next_thrift_port + ConfiguredP4Switch.next_thrift_port += 1 + P4Switch.__init__(self, *opts, **kwargs) + + def describe(self): + print "%s -> Thrift port: %d" % (self.name, self.thrift_port) + + return ConfiguredP4Switch + + +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): + Topo.__init__(self, **opts) + host_links = [] + switch_links = [] + self.sw_port_mapping = {} + + 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 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, + delay=link['latency'], bw=link['bandwidth'], + addr1=host_mac, addr2=host_mac) + self.addSwitchPort(host_sw, host_name) + + for link in switch_links: + self.addLink(link['node1'], link['node2'], + 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 + + +class ExerciseRunner: + """ + Attributes: + log_dir : string // directory for mininet log files + 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 + links : list // list of mininet link properties + + switch_json : string // json of the compiled p4 example + bmv2_exe : string // name or path of the p4 switch binary + + topo : Topo object // The mininet topology instance + net : Mininet object // The mininet instance + + """ + def logger(self, *items): + if not self.quiet: + print(' '.join(items)) + + def formatLatency(self, l): + """ Helper method for parsing link latencies from the topology json. """ + if isinstance(l, (str, unicode)): + return l + else: + return str(l) + "ms" + + + def __init__(self, topo_file, log_dir, pcap_dir, + switch_json, bmv2_exe='simple_switch', quiet=False): + """ Initializes some attributes and reads the topology json. Does not + actually run the exercise. Use run_exercise() for that. + + Arguments: + topo_file : string // A json file which describes the exercise's + mininet topology. + log_dir : string // Path to a directory for storing exercise logs + pcap_dir : string // Ditto, but for mininet switch pcap files + switch_json : string // Path to a compiled p4 json for bmv2 + bmv2_exe : string // Path to the p4 behavioral binary + quiet : bool // Enable/disable script debug messages + """ + + self.quiet = quiet + self.logger('Reading topology file.') + with open(topo_file, 'r') as f: + topo = json.load(f) + self.hosts = topo['hosts'] + self.switches = topo['switches'] + self.links = self.parse_links(topo['links']) + + # Ensure all the needed directories exist and are directories + for dir_name in [log_dir, pcap_dir]: + if not os.path.isdir(dir_name): + if os.path.exists(dir_name): + raise Exception("'%s' exists and is not a directory!" % dir_name) + os.mkdir(dir_name) + self.log_dir = log_dir + self.pcap_dir = pcap_dir + self.switch_json = switch_json + self.bmv2_exe = bmv2_exe + + + def run_exercise(self): + """ Sets up the mininet instance, programs the switches, + and starts the mininet CLI. This is the main method to run after + initializing the object. + """ + # Initialize mininet with the topology specified by the config + self.create_network() + self.net.start() + sleep(1) + + # some programming that must happen after the net has started + self.program_hosts() + self.program_switches() + + # wait for that to finish. Not sure how to do this better + sleep(1) + + self.do_net_cli() + # stop right after the CLI is exited + self.net.stop() + + + def parse_links(self, unparsed_links): + """ Given a list of links descriptions of the form [node1, node2, latency, bandwidth] + with the latency and bandwidth being optional, parses these descriptions + into dictionaries and store them as self.links + """ + links = [] + for link in unparsed_links: + # make sure each link's endpoints are ordered alphabetically + s, t, = link[0], link[1] + if s > t: + s,t = t,s + + link_dict = {'node1':s, + 'node2':t, + 'latency':'0ms', + 'bandwidth':None + } + if len(link) > 2: + link_dict['latency'] = self.formatLatency(link[2]) + if len(link) > 3: + link_dict['bandwidth'] = link[3] + + if link_dict['node1'][0] == 'h': + assert link_dict['node2'][0] == 's', 'Hosts should be connected to switches, not ' + str(link_dict['node2']) + links.append(link_dict) + return links + + + def create_network(self): + """ Create the mininet network object, and store it as self.net. + + Side effects: + - Mininet topology instance stored as self.topo + - Mininet instance stored as self.net + """ + self.logger("Building mininet topology.") + + self.topo = ExerciseTopo(self.hosts, self.switches.keys(), self.links, self.log_dir) + + switchClass = configureP4Switch( + sw_path=self.bmv2_exe, + json_path=self.switch_json, + log_console=True, + pcap_dump=self.pcap_dir) + + self.net = Mininet(topo = self.topo, + link = TCLink, + host = P4Host, + switch = switchClass, + controller = None) + + + def program_switches(self): + """ If any command files were provided for the switches, + this method will start up the CLI on each switch and use the + contents of the command files as input. + + Assumes: + - A mininet instance is stored as self.net and self.net.start() has + been called. + """ + cli = 'simple_switch_CLI' + for sw_name, sw_dict in self.switches.iteritems(): + if 'cli_input' not in sw_dict: continue + # get the port for this particular switch's thrift server + sw_obj = self.net.get(sw_name) + thrift_port = sw_obj.thrift_port + + cli_input_commands = sw_dict['cli_input'] + self.logger('Configuring switch %s with file %s' % (sw_name, cli_input_commands)) + with open(cli_input_commands, 'r') as fin: + cli_outfile = '%s/%s_cli_output.log'%(self.log_dir, sw_name) + with open(cli_outfile, 'w') as fout: + subprocess.Popen([cli, '--thrift-port', str(thrift_port)], + stdin=fin, stdout=fout) + + 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. + """ + for host_name in self.topo.hosts(): + 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) + + + def do_net_cli(self): + """ Starts up the mininet CLI and prints some helpful output. + + Assumes: + - A mininet instance is stored as self.net and self.net.start() has + been called. + """ + for s in self.net.switches: + s.describe() + for h in self.net.hosts: + h.describe() + self.logger("Starting mininet CLI") + # Generate a message that will be printed by the Mininet CLI to make + # interacting with the simple switch a little easier. + print('') + print('======================================================================') + print('Welcome to the BMV2 Mininet CLI!') + print('======================================================================') + print('Your P4 program is installed into the BMV2 software switch') + print('and your initial configuration is loaded. You can interact') + print('with the network using the mininet CLI below.') + print('') + if self.switch_json: + print('To inspect or change the switch configuration, connect to') + print('its CLI from your host operating system using this command:') + print(' simple_switch_CLI --thrift-port ') + print('') + print('To view a switch log, run this command from your host OS:') + print(' tail -f %s/.log' % self.log_dir) + print('') + print('To view the switch output pcap, check the pcap files in %s:' % self.pcap_dir) + print(' for example run: sudo tcpdump -xxx -r s1-eth1.pcap') + print('') + + CLI(self.net) + + +def get_args(): + cwd = os.getcwd() + default_logs = os.path.join(cwd, 'logs') + default_pcaps = os.path.join(cwd, 'pcaps') + parser = argparse.ArgumentParser() + parser.add_argument('-q', '--quiet', help='Suppress log messages.', + action='store_true', required=False, default=False) + parser.add_argument('-t', '--topo', help='Path to topology json', + type=str, required=False, default='./topology.json') + parser.add_argument('-l', '--log-dir', type=str, required=False, default=default_logs) + parser.add_argument('-p', '--pcap-dir', type=str, required=False, default=default_pcaps) + parser.add_argument('-j', '--switch_json', type=str, required=False) + parser.add_argument('-b', '--behavioral-exe', help='Path to behavioral executable', + type=str, required=False, default='simple_switch') + return parser.parse_args() + + +if __name__ == '__main__': + # from mininet.log import setLogLevel + # setLogLevel("info") + + args = get_args() + exercise = ExerciseRunner(args.topo, args.log_dir, args.pcap_dir, + args.switch_json, args.behavioral_exe, args.quiet) + + exercise.run_exercise() + diff --git a/P4D2_2018_East/vm/Vagrantfile b/P4D2_2018_East/vm/Vagrantfile new file mode 100644 index 0000000..0802c15 --- /dev/null +++ b/P4D2_2018_East/vm/Vagrantfile @@ -0,0 +1,29 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +Vagrant::DEFAULT_SERVER_URL.replace('https://vagrantcloud.com') + +Vagrant.configure(2) do |config| + config.vm.box = "bento/ubuntu-16.04" + config.vm.define "p4-tutorial" do |tutorial| + end + config.vm.provider "virtualbox" do |vb| + vb.name = "P4 Tutorial" + Time.now.strftime(" %Y-%m-%d") + vb.gui = true + vb.memory = 2048 + vb.cpus = 2 + vb.customize ["modifyvm", :id, "--cableconnected1", "on"] + vb.customize ["storageattach", :id, + "--storagectl", "IDE Controller", + "--port", "0", "--device", "0", + "--type", "dvddrive", + "--medium", "emptydrive"] + end + config.vm.synced_folder '.', '/vagrant', disabled: true + config.vm.hostname = "p4" + config.vm.provision "file", source: "p4-logo.png", destination: "/home/vagrant/p4-logo.png" + config.vm.provision "file", source: "p4_16-mode.el", destination: "/home/vagrant/p4_16-mode.el" + config.vm.provision "file", source: "p4.vim", destination: "/home/vagrant/p4.vim" + config.vm.provision "shell", path: "root-bootstrap.sh" + config.vm.provision "shell", privileged: false, path: "user-bootstrap.sh" +end diff --git a/P4D2_2018_East/vm/p4-logo.png b/P4D2_2018_East/vm/p4-logo.png new file mode 100644 index 0000000..cc36615 Binary files /dev/null and b/P4D2_2018_East/vm/p4-logo.png differ diff --git a/P4D2_2018_East/vm/p4.vim b/P4D2_2018_East/vm/p4.vim new file mode 100644 index 0000000..a62d815 --- /dev/null +++ b/P4D2_2018_East/vm/p4.vim @@ -0,0 +1,133 @@ +" Vim syntax file +" Language: P4_16 +" Maintainer: Antonin Bas, Barefoot Networks Inc +" Latest Revision: 5 August 2014 +" Updated By: Gyanesh Patra, Unicamp University +" Latest Revision: 12 April 2016 +" Updated Again By: Robert MacDavid, Princeton University +" Latest Revision: 12 June 2017 + +if version < 600 + syntax clear +elseif exists("b:current_syntax") + finish +endif + +" Use case sensitive matching of keywords +syn case match + +syn keyword p4ObjectKeyword action apply control default +syn keyword p4ObjectKeyword enum extern exit +syn keyword p4ObjectKeyword header header_union +syn keyword p4ObjectKeyword match_kind +syn keyword p4ObjectKeyword package parser +syn keyword p4ObjectKeyword state struct switch size +syn keyword p4ObjectKeyword table transition tuple typedef +syn keyword p4ObjectKeyword verify + +" Tables +syn keyword p4ObjectAttributeKeyword key actions default_action entries +syn keyword p4ObjectAttributeKeyword implementation +" Counters and meters +syn keyword p4ObjectAttributeKeyword counters meters +" Var Attributes +syn keyword p4ObjectKeyword const in out inout + + +syn keyword p4Annotation @name @tableonly @defaultonly +syn keyword p4Annotation @globalname @atomic @hidden + + +syn keyword p4MatchTypeKeyword exact ternary lpm range + +syn keyword p4TODO contained FIXME TODO +syn match p4Comment '\/\/.*' contains=p4TODO +syn region p4BlockComment start='\/\*' end='\*\/' contains=p4TODO keepend + +syn match p4Preprocessor '#(include|define|undef|if|ifdef) .*$' +syn match p4Preprocessor '#(if|ifdef|ifndef|elif|else) .*$' +syn match p4Preprocessor '#(endif|defined|line|file) .*$' +syn match p4Preprocessor '#(error|warning) .*$' + +syn keyword p4Type bit bool int varbit void error + +" Integer Literals + +syn match p4Int '[0-9][0-9_]*' +syn match p4Indentifier '[A-Za-z_][A-Za-z0-9_]*' +syn match p4HexadecimalInt '0[Xx][0-9a-fA-F]\+' +syn match p4DecimalInt '0[dD][0-9_]\+' +syn match p4OctalInt '0[oO][0-7_]\+' +syn match p4BinaryInt '0[bB][01_]\+' + + +syn region p4SizedType start='(bit|int|varbit)\<' end='\>' +syn match p4UserType '[A-Za-z_][A-Za-z0-9_]*[_][t]\W' +syn keyword p4Operators and or not &&& mask + + +" Header Methods +syn keyword p4Primitive isValid setValid setInvalid +" Table Methods +syn keyword p4Primitive hit action_run +" Packet_in methods +syn keyword p4Primitive extract lookahead advance length +" Packet_out methods +syn keyword p4Primitive emit +" Known parser states +syn keyword p4Primitive accept reject +" Misc +syn keyword p4Primitive NoAction + + +syn keyword p4Conditional if else select +syn keyword p4Statement return + +" Don't Care +syn keyword p4Constant _ +" Error +syn keyword p4Constant NoError PacketTooShort NoMatch StackOutOfBounds +syn keyword p4Constant OverwritingHeader HeaderTooShort ParserTiimeout +" Boolean +syn keyword p4Boolean false true + +"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +" Apply highlight groups to syntax groups defined above +" For version 5.7 and earlier: only when not done already +" For version 5.8 and later: only when an item doesn't have highlighting yet +if version >= 508 || !exists("did_p4_syntax_inits") + if version <= 508 + let did_p4_syntax_inits = 1 + command -nargs=+ HiLink hi link + else + command -nargs=+ HiLink hi def link + endif + + HiLink p4ObjectKeyword Repeat + HiLink p4UserType Type + HiLink p4ObjectAttributeKeyword Keyword + HiLink p4TypeAttribute StorageClass + HiLink p4Annotation Special + HiLink p4MatchTypeKeyword Keyword + HiLink p4TODO Todo + HiLink p4Comment Comment + HiLink p4BlockComment Comment + HiLink p4Preprocessor PreProc + HiLink p4SizedType Type + HiLink p4Type Type + HiLink p4DecimalInt Number + HiLink p4HexadecimalInt Number + HiLink p4OctalInt Number + HiLink p4BinaryInt Number + HiLink p4Int Number + HiLink p4Operators Operator + HiLink p4Primitive Function + HiLink p4Conditional Conditional + HiLink p4Statement Statement + HiLink p4Constant Constant + HiLink p4Boolean Boolean + + delcommand HiLink +endif + +let b:current_syntax = "p4" diff --git a/P4D2_2018_East/vm/p4_16-mode.el b/P4D2_2018_East/vm/p4_16-mode.el new file mode 100644 index 0000000..7625d90 --- /dev/null +++ b/P4D2_2018_East/vm/p4_16-mode.el @@ -0,0 +1,222 @@ +;;; p4_16-mode.el --- Support for the P4_16 programming language + +;; Copyright (C) 2016- Barefoot Networks +;; Author: Vladimir Gurevich +;; Maintainer: Vladimir Gurevich +;; Created: 15 April 2017 +;; Version: 0.2 +;; Keywords: languages p4_16 +;; Homepage: http://p4.org + +;; This file is not part of GNU Emacs. + +;; This file is free software… + +;; This mode has preliminary support for P4_16. It covers the core language, +;; but it is not clear yet, how we can highlight the indentifiers, defined +;; for a particular architecture. Core library definitions are included + +;; Placeholder for user customization code +(defvar p4_16-mode-hook nil) + +;; Define the keymap (for now it is pretty much default) +(defvar p4_16-mode-map + (let ((map (make-keymap))) + (define-key map "\C-j" 'newline-and-indent) + map) + "Keymap for P4_16 major mode") + +;; Syntactic HighLighting + +;; Main keywors (declarations and operators) +(setq p4_16-keywords + '("action" "apply" + "control" + "default" + "else" "enum" "extern" "exit" + "header" "header_union" + "if" + "match_kind" + "package" "parser" + "return" + "select" "state" "struct" "switch" + "table" "transition" "tuple" "typedef" + "verify" + )) + +(setq p4_16-annotations + '("@name" "@metadata" "@alias" + )) + +(setq p4_16-attributes + '("const" "in" "inout" "out" + ;; Tables + "key" "actions" "default_action" "entries" "implementation" + "counters" "meters" + )) + +(setq p4_16-variables + '("packet_in" "packet_out" + )) + +(setq p4_16-operations + '("&&&" ".." "++" "?" ":")) + +(setq p4_16-constants + '( + ;;; Don't care + "_" + ;;; bool + "false" "true" + ;;; error + "NoError" "PacketTooShort" "NoMatch" "StackOutOfBounds" + "OverwritingHeader" "HeaderTooShort" "ParserTiimeout" + ;;; match_kind + "exact" "ternary" "lpm" "range" + ;;; We can add constants for supported architectures here + )) + +(setq p4_16-types + '("bit" "bool" "int" "varbit" "void" "error" + )) + +(setq p4_16-primitives + '( + ;;; Header methods + "isValid" "setValid" "setInvalid" + ;;; Table Methods + "hit" "action_run" + ;;; packet_in methods + "extract" "lookahead" "advance" "length" + ;;; packet_out methods + "emit" + ;;; Known parser states + "accept" "reject" + ;;; misc + "NoAction" + )) + +(setq p4_16-cpp + '("#include" + "#define" "#undef" + "#if" "#ifdef" "#ifndef" + "#elif" "#else" + "#endif" + "defined" + "#line" "#file")) + +(setq p4_16-cppwarn + '("#error" "#warning")) + +;; Optimize the strings +(setq p4_16-keywords-regexp (regexp-opt p4_16-keywords 'words)) +(setq p4_16-annotations-regexp (regexp-opt p4_16-annotations 1)) +(setq p4_16-attributes-regexp (regexp-opt p4_16-attributes 'words)) +(setq p4_16-variables-regexp (regexp-opt p4_16-variables 'words)) +(setq p4_16-operations-regexp (regexp-opt p4_16-operations 'words)) +(setq p4_16-constants-regexp (regexp-opt p4_16-constants 'words)) +(setq p4_16-types-regexp (regexp-opt p4_16-types 'words)) +(setq p4_16-primitives-regexp (regexp-opt p4_16-primitives 'words)) +(setq p4_16-cpp-regexp (regexp-opt p4_16-cpp 1)) +(setq p4_16-cppwarn-regexp (regexp-opt p4_16-cppwarn 1)) + + +;; create the list for font-lock. +;; each category of keyword is given a particular face +(defconst p4_16-font-lock-keywords + (list + (cons p4_16-cpp-regexp font-lock-preprocessor-face) + (cons p4_16-cppwarn-regexp font-lock-warning-face) + (cons p4_16-types-regexp font-lock-type-face) + (cons p4_16-constants-regexp font-lock-constant-face) + (cons p4_16-attributes-regexp font-lock-builtin-face) + (cons p4_16-variables-regexp font-lock-variable-name-face) + ;;; This is a special case to distinguish the method from the keyword + (cons "\\.apply" font-lock-function-name-face) + (cons p4_16-primitives-regexp font-lock-function-name-face) + (cons p4_16-operations-regexp font-lock-builtin-face) + (cons p4_16-keywords-regexp font-lock-keyword-face) + (cons p4_16-annotations-regexp font-lock-keyword-face) + (cons "\\(\\w*_t +\\)" font-lock-type-face) + (cons "[^A-Z_][A-Z] " font-lock-type-face) ;; Total hack for templates + (cons "<[A-Z, ]*>" font-lock-type-face) + (cons "\\(<[^>]+>\\)" font-lock-string-face) + (cons "\\([^_A-Za-z]\\([0-9]+w\\)?0x[0-9A-Fa-f]+\\)" font-lock-constant-face) + (cons "\\([^_A-Za-z]\\([0-9]+w\\)?0b[01]+\\)" font-lock-constant-face) + (cons "\\([^_A-Za-z][+-]?\\([0-9]+w\\)?[0-9]+\\)" font-lock-constant-face) + ;;(cons "\\(\\w*\\)" font-lock-variable-name-face) + ) + "Default Highlighting Expressions for P4_16") + +(defvar p4_16-mode-syntax-table + (let ((st (make-syntax-table))) + (modify-syntax-entry ?_ "w" st) + (modify-syntax-entry ?/ ". 124b" st) + (modify-syntax-entry ?* ". 23" st) + (modify-syntax-entry ?\n "> b" st) + st) + "Syntax table for p4_16-mode") + +;;; Indentation +(defvar p4_16-indent-offset 4 + "Indentation offset for `p4_16-mode'.") + +(defun p4_16-indent-line () + "Indent current line for any balanced-paren-mode'." + (interactive) + (let ((indent-col 0) + (indentation-increasers "[{(]") + (indentation-decreasers "[})]") + ) + (save-excursion + (beginning-of-line) + (condition-case nil + (while t + (backward-up-list 1) + (when (looking-at indentation-increasers) + (setq indent-col (+ indent-col p4_16-indent-offset)))) + (error nil))) + (save-excursion + (back-to-indentation) + (when (and (looking-at indentation-decreasers) + (>= indent-col p4_16-indent-offset)) + (setq indent-col (- indent-col p4_16-indent-offset)))) + (indent-line-to indent-col))) + +;;; Imenu support +(require 'imenu) +(setq p4_16-imenu-generic-expression + '( + ("Controls" "^ *control +\\([A-Za-z0-9_]*\\)" 1) + ("Externs" "^ *extern +\\([A-Za-z0-9_]*\\) *\\([A-Za-z0-9_]*\\)" 2) + ("Tables" "^ *table +\\([A-Za-z0-9_]*\\)" 1) + ("Actions" "^ *action +\\([A-Za-z0-9_]*\\)" 1) + ("Parsers" "^ *parser +\\([A-Za-z0-9_]*\\)" 1) + ("Parser States" "^ *state +\\([A-Za-z0-9_]*\\)" 1) + ("Headers" "^ *header +\\([A-Za-z0-9_]*\\)" 1) + ("Header Unions" "^ *header_union +\\([A-Za-z0-9_]*\\)" 1) + ("Structs" "^ *struct +\\([A-Za-z0-9_]*\\)" 1) + )) + +;;; Cscope Support +(require 'xcscope) + +;; Put everything together +(defun p4_16-mode () + "Major mode for editing P4_16 programs" + (interactive) + (kill-all-local-variables) + (set-syntax-table p4_16-mode-syntax-table) + (use-local-map p4_16-mode-map) + (set (make-local-variable 'font-lock-defaults) '(p4_16-font-lock-keywords)) + (set (make-local-variable 'indent-line-function) 'p4_16-indent-line) + (setq major-mode 'p4_16-mode) + (setq mode-name "P4_16") + (setq imenu-generic-expression p4_16-imenu-generic-expression) + (imenu-add-to-menubar "P4_16") + (cscope-minor-mode) + (run-hooks 'p4_16-mode-hook) +) + +;; The most important line +(provide 'p4_16-mode) diff --git a/P4D2_2018_East/vm/root-bootstrap.sh b/P4D2_2018_East/vm/root-bootstrap.sh new file mode 100755 index 0000000..0cc24a7 --- /dev/null +++ b/P4D2_2018_East/vm/root-bootstrap.sh @@ -0,0 +1,90 @@ +#!/bin/bash + +# Print commands and exit on errors +set -xe + +sudo add-apt-repository ppa:webupd8team/sublime-text-3 +sudo add-apt-repository ppa:webupd8team/atom + +apt-get update + +DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" upgrade + +apt-get install -y --no-install-recommends \ + atom \ + autoconf \ + automake \ + bison \ + build-essential \ + ca-certificates \ + cmake \ + cpp \ + curl \ + emacs24 \ + flex \ + git \ + libboost-dev \ + libboost-filesystem-dev \ + libboost-iostreams1.58-dev \ + libboost-program-options-dev \ + libboost-system-dev \ + libboost-test-dev \ + libboost-thread-dev \ + libc6-dev \ + libevent-dev \ + libffi-dev \ + libfl-dev \ + libgc-dev \ + libgc1c2 \ + libgflags-dev \ + libgmp-dev \ + libgmp10 \ + libgmpxx4ldbl \ + libjudy-dev \ + libpcap-dev \ + libreadline6 \ + libreadline6-dev \ + libssl-dev \ + libtool \ + lubuntu-desktop \ + make \ + mktemp \ + pkg-config \ + python \ + python-dev \ + python-ipaddr \ + python-pip \ + python-scapy \ + python-setuptools \ + sublime-text-installer \ + tcpdump \ + unzip \ + vim \ + wget \ + xcscope-el \ + xterm + +useradd -m -d /home/p4 -s /bin/bash p4 +echo "p4:p4" | chpasswd +echo "p4 ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/99_p4 +chmod 440 /etc/sudoers.d/99_p4 +usermod -aG vboxsf p4 + +cd /usr/share/lubuntu/wallpapers/ +cp /home/vagrant/p4-logo.png . +rm lubuntu-default-wallpaper.png +ln -s p4-logo.png lubuntu-default-wallpaper.png +rm /home/vagrant/p4-logo.png +cd /home/vagrant +sed -i s@#background=@background=/usr/share/lubuntu/wallpapers/1604-lubuntu-default-wallpaper.png@ /etc/lightdm/lightdm-gtk-greeter.conf + +# Disable screensaver +apt-get -y remove light-locker + +# Automatically log into the P4 user +cat << EOF | tee -a /etc/lightdm/lightdm.conf.d/10-lightdm.conf +[SeatDefaults] +autologin-user=p4 +autologin-user-timeout=0 +user-session=Lubuntu +EOF diff --git a/P4D2_2018_East/vm/user-bootstrap.sh b/P4D2_2018_East/vm/user-bootstrap.sh new file mode 100644 index 0000000..cb2bfaf --- /dev/null +++ b/P4D2_2018_East/vm/user-bootstrap.sh @@ -0,0 +1,178 @@ +#!/bin/bash + +# Print script commands. +set -x +# Exit on errors. +set -e + +BMV2_COMMIT="39abe290b4143e829b8f983965fcdc711e3c450c" +PI_COMMIT="afd5831393824228246ea01b26da2f93d38fd20c" +P4C_COMMIT="80f8970b5ec8e57c4a3611da343461b5b0a8dda3" +PROTOBUF_COMMIT="v3.2.0" +GRPC_COMMIT="v1.3.2" + +NUM_CORES=`grep -c ^processor /proc/cpuinfo` + +# Mininet +git clone git://github.com/mininet/mininet mininet +cd mininet +sudo ./util/install.sh -nwv +cd .. + +# Protobuf +git clone https://github.com/google/protobuf.git +cd protobuf +git checkout ${PROTOBUF_COMMIT} +export CFLAGS="-Os" +export CXXFLAGS="-Os" +export LDFLAGS="-Wl,-s" +./autogen.sh +./configure --prefix=/usr +make -j${NUM_CORES} +sudo make install +sudo ldconfig +unset CFLAGS CXXFLAGS LDFLAGS +cd .. + +# gRPC +git clone https://github.com/grpc/grpc.git +cd grpc +git checkout ${GRPC_COMMIT} +git submodule update --init --recursive +export LDFLAGS="-Wl,-s" +make -j${NUM_CORES} +sudo make install +sudo ldconfig +unset LDFLAGS +cd .. +# Install gRPC Python Package +sudo pip install grpcio + +# BMv2 deps (needed by PI) +git clone https://github.com/p4lang/behavioral-model.git +cd behavioral-model +git checkout ${BMV2_COMMIT} +# From bmv2's install_deps.sh, we can skip apt-get install. +# Nanomsg is required by p4runtime, p4runtime is needed by BMv2... +tmpdir=`mktemp -d -p .` +cd ${tmpdir} +bash ../travis/install-thrift.sh +bash ../travis/install-nanomsg.sh +sudo ldconfig +bash ../travis/install-nnpy.sh +cd .. +sudo rm -rf $tmpdir +cd .. + +# PI/P4Runtime +git clone https://github.com/p4lang/PI.git +cd PI +git checkout ${PI_COMMIT} +git submodule update --init --recursive +./autogen.sh +./configure --with-proto +make -j${NUM_CORES} +sudo make install +sudo ldconfig +cd .. + +# Bmv2 +cd behavioral-model +./autogen.sh +./configure --enable-debugger --with-pi +make -j${NUM_CORES} +sudo make install +sudo ldconfig +# Simple_switch_grpc target +cd targets/simple_switch_grpc +./autogen.sh +./configure +make -j${NUM_CORES} +sudo make install +sudo ldconfig +cd .. +cd .. +cd .. + +# P4C +git clone https://github.com/p4lang/p4c +cd p4c +git checkout ${P4C_COMMIT} +git submodule update --init --recursive +mkdir -p build +cd build +cmake .. +make -j${NUM_CORES} +make -j${NUM_CORES} check +sudo make install +sudo ldconfig +cd .. +cd .. + +# Tutorials +sudo pip install crcmod +git clone https://github.com/p4lang/tutorials +sudo mv tutorials /home/p4 +sudo chown -R p4:p4 /home/p4/tutorials + +# Emacs +sudo cp p4_16-mode.el /usr/share/emacs/site-lisp/ +echo "(add-to-list 'auto-mode-alist '(\"\\.p4\\'\" . p4_16-mode))" | sudo tee /home/p4/.emacs +sudo chown p4:p4 /home/p4/.emacs + +# Vim +cd /home/vagrant +mkdir .vim +cd .vim +mkdir ftdetect +mkdir syntax +echo "au BufRead,BufNewFile *.p4 set filetype=p4" >> ftdetect/p4.vim +echo "set bg=dark" >> /home/vagrant/.vimrc +sudo mv /home/vagrant/.vimrc /home/p4/.vimrc +cp /home/vagrant/p4.vim syntax/p4.vim +cd /home/vagrant +sudo mv .vim /home/p4/.vim +sudo chown -R p4:p4 /home/p4/.vim +sudo chown p4:p4 /home/p4/.vimrc + +# Adding Desktop icons +DESKTOP=/home/${USER}/Desktop +mkdir -p ${DESKTOP} + +cat > ${DESKTOP}/Terminal << EOF +[Desktop Entry] +Encoding=UTF-8 +Type=Application +Name=Terminal +Name[en_US]=Terminal +Icon=konsole +Exec=/usr/bin/x-terminal-emulator +Comment[en_US]= +EOF + +cat > ${DESKTOP}/Wireshark << EOF +[Desktop Entry] +Encoding=UTF-8 +Type=Application +Name=Wireshark +Name[en_US]=Wireshark +Icon=wireshark +Exec=/usr/bin/wireshark +Comment[en_US]= +EOF + +cat > ${DESKTOP}/Sublime\ Text << EOF +[Desktop Entry] +Encoding=UTF-8 +Type=Application +Name=Sublime Text +Name[en_US]=Sublime Text +Icon=sublime-text +Exec=/opt/sublime_text/sublime_text +Comment[en_US]= +EOF + +sudo mkdir -p /home/p4/Desktop +sudo mv /home/${USER}/Desktop/* /home/p4/Desktop +sudo chown -R p4:p4 /home/p4/Desktop/ +sudo reboot