This commit includes example tutorials and solutions for P4D2 2017. (#29)
- Added example P4 programs for ipv4_forwarding, mri, arp, calc - Added python code to invoke compiler, start bmv2, run mininet - Added solutions to above programs - Added README files that describe exercises
This commit is contained in:
parent
5616d53a1a
commit
a78dba7a5a
63
P4D2_2017/exercises/README.md
Normal file
63
P4D2_2017/exercises/README.md
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
# P4 Tutorial
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
Welcome to the P4 Tutorial!
|
||||||
|
|
||||||
|
We've prepared a set of four exercises that will help you get started
|
||||||
|
with P4 programming:
|
||||||
|
|
||||||
|
1. [L3 forwarding](./ipv4_forward)
|
||||||
|
|
||||||
|
2. [Multi-Hop Route Inspection](./mri)
|
||||||
|
|
||||||
|
3. [ARP/ICMP Responder](./arp)
|
||||||
|
|
||||||
|
4. [Calculator](./calc)
|
||||||
|
|
||||||
|
## Obtaining required software
|
||||||
|
|
||||||
|
If you are starting this tutorial as part of the 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 clone two p4lang Github repositories
|
||||||
|
and install their dependencies. To clonde the repositories:
|
||||||
|
|
||||||
|
- `git clone https://github.com/p4lang/behavioral-model.git bmv2`
|
||||||
|
- `git clone https://github.com/p4lang/p4c-bm.git p4c-bmv2`
|
||||||
|
|
||||||
|
The first repository ([bmv2](https://github.com/p4lang/behavioral-model)) is the
|
||||||
|
second version of the behavioral model. It is a C++ software switch that will
|
||||||
|
behave according to your P4 program. The second repository
|
||||||
|
([p4c-bmv2](https://github.com/p4lang/p4c-bm)) is the compiler for the
|
||||||
|
behavioral model: it takes P4 program and output a JSON file which can be loaded
|
||||||
|
by the behavioral model.
|
||||||
|
|
||||||
|
Each of these repositories come with dependencies. `p4c-bmv2` is a Python
|
||||||
|
repository and installing the required Python dependencies is very easy to do
|
||||||
|
using `pip`: `sudo pip install -r requirements.txt`.
|
||||||
|
|
||||||
|
`bmv2` is a C++ repository and has more external dependencies. They are listed
|
||||||
|
in the
|
||||||
|
[README](https://github.com/p4lang/behavioral-model/blob/master/README.md). If
|
||||||
|
you are running Ubuntu 14.04+, the dependencies should be easy to install (you
|
||||||
|
can use the `install_deps.sh` script that comes with `bmv2`). Do not forget to
|
||||||
|
build the code once all the dependencies have been installed:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ./autogen.sh
|
||||||
|
$ ./configure
|
||||||
|
$ make
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
You will also need to install `mininet`, as well as the following Python
|
||||||
|
packages: `scapy`, `thrift` (>= 0.9.2) and `networkx`. On Ubuntu, it would look
|
||||||
|
like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo apt-get install mininet
|
||||||
|
$ sudo pip install scapy thrift networkx
|
||||||
|
```
|
||||||
|
|
174
P4D2_2017/exercises/arp/README.md
Normal file
174
P4D2_2017/exercises/arp/README.md
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
# Implementing an ARP/ICMP Responder
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
This exercise extends the [IPv4 Forwarding](../ipv4_forward) program to
|
||||||
|
allow your switches to respond to ARP and ICMP requests. Once implemented,
|
||||||
|
your hosts will be able to `ping` other hosts connected to the switch, and
|
||||||
|
have the switch respond.
|
||||||
|
|
||||||
|
This exercise makes several simplifying assumptions:
|
||||||
|
|
||||||
|
1. The network topology contains exactly one switch and two hosts.
|
||||||
|
1. ARP and ICMP requests to the hosts are ignored; only requests sent to the
|
||||||
|
switch receive responses.
|
||||||
|
|
||||||
|
Implementing the full functionality of ARP and ICMP is straightforward but
|
||||||
|
beyond the scope of this tutorial and left as an exercise to the reader.
|
||||||
|
|
||||||
|
> **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,
|
||||||
|
`arp.p4`, which initially drops all packets. Your job will be to
|
||||||
|
extend it to reply to ARP and ICMP requests.
|
||||||
|
|
||||||
|
As a first step, compile the incomplete `arp.p4` and bring up a
|
||||||
|
switch in Mininet to test its behavior.
|
||||||
|
|
||||||
|
1. In your shell, run:
|
||||||
|
```bash
|
||||||
|
./run.sh
|
||||||
|
```
|
||||||
|
This will:
|
||||||
|
* compile `arp.p4`, and
|
||||||
|
* start a Mininet instance with one switch (`s1`) connected to two hosts (`h1`, `h2`).
|
||||||
|
* The hosts are assigned IPs of `10.0.1.10` and `10.0.2.10`.
|
||||||
|
|
||||||
|
2. Once the P4 source compiles without error, you can test your program at the
|
||||||
|
mininet prompt using the `ping` utility.
|
||||||
|
|
||||||
|
``` mininet> h1 ping h2 ```
|
||||||
|
|
||||||
|
Once the program is implemented correctly, you will see a response to the
|
||||||
|
ping in the mininet window.
|
||||||
|
|
||||||
|
3. Type `exit` in the mininet terminal to exit.
|
||||||
|
|
||||||
|
|
||||||
|
### 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 `run.sh` script will install
|
||||||
|
packet-processing rules in the tables of each switch. These are defined in the
|
||||||
|
`simple_router.config` file.
|
||||||
|
|
||||||
|
|
||||||
|
## Step 2: Implement ARP/ICMP Replies
|
||||||
|
|
||||||
|
In this exercise, we are using entries in the `ipv4_lpm` table as a
|
||||||
|
database, which we can reference when responding to ARP and ICMP requests.
|
||||||
|
Without the proper entries in the table, the solution will not work.
|
||||||
|
|
||||||
|
From a high-level, the task involves implementing two main components: ARP
|
||||||
|
replies and ICMP replies.
|
||||||
|
|
||||||
|
### ARP Reply
|
||||||
|
|
||||||
|
When the switch receives and ARP request asking to resolve the switch's IP
|
||||||
|
address, it will need to perform the following actions:
|
||||||
|
|
||||||
|
1. Swap the source and destination MAC addresses in the Ethernet header,
|
||||||
|
1. set the ARP operation to ARP_REPLY (`2`) in the ARP header,
|
||||||
|
1. update the sender hardware address (SHA) and sender protocol address (SPA) in the ARP header to
|
||||||
|
be the MAC and IP addresses of the switch, and
|
||||||
|
1. set the target hardware address (THA) and target protocol address (TPA) to be the SHA
|
||||||
|
and SPA of the arriving ARP packet.
|
||||||
|
|
||||||
|
### ICMP Reply
|
||||||
|
|
||||||
|
When the switch receives and ICMP request containing the switch's IP and MAC
|
||||||
|
addresses, it will need to perform the following actions:
|
||||||
|
|
||||||
|
1. Swap the source and destination MAC addresses in the Ethernet header,
|
||||||
|
1. swap the source and destination IP addresses in the ICMP header, and
|
||||||
|
1. set the type field in the ICMP header to `ICMP_ECHO_REPLY` (`0`).
|
||||||
|
1. To simplify the exercise, we can ignore the checksum by setting to checksum
|
||||||
|
field to 0.
|
||||||
|
|
||||||
|
|
||||||
|
We have provided a skeleton `arp.p4` file to get you started. In this
|
||||||
|
file, places to modify are marked by `TODO`.
|
||||||
|
|
||||||
|
There are, of course, different possible solutions. We describe one approach
|
||||||
|
below. It builds on the [IPv4 Forwarding](../ipv4_forward) solution, which
|
||||||
|
used the table `ipv4_lpm` for L3 forwarding, by adding a second table named
|
||||||
|
`forward`, which checks if a packet is an ARP or ICMP packet and invokes
|
||||||
|
actions to send an ARP reply, forward an IPv4 packet, or send an ICMP reply.
|
||||||
|
|
||||||
|
Broadly speaking, a complete solution will contain the following components:
|
||||||
|
|
||||||
|
1. Header type definitions for `ethernet_t`, `arp_t`, `ipv4_t`, and `icmp_t`.
|
||||||
|
|
||||||
|
1. A structure (named `my_metadata_t` in `arp.p4`) with metadata fields for the
|
||||||
|
packet's souce and destination MAC addresses, IPv4 address, egress port, as
|
||||||
|
well as a hard-coded MAC address for the switch.
|
||||||
|
|
||||||
|
1. **TODO:** Parsers for Ethernet, ARP, IPv4, and ICMP packet header types.
|
||||||
|
|
||||||
|
1. **TODO:** A control type declaration for ingress processing, containing:
|
||||||
|
|
||||||
|
1. An action for `drop`.
|
||||||
|
|
||||||
|
1. An action (named `ipv4_forward`) to store information in the metadata
|
||||||
|
structure, rather than immediately writing to the packet header.
|
||||||
|
|
||||||
|
1. A table (named `ipv4_lpm`) that will match on the destination IP address
|
||||||
|
and invoke the `ipv4_forward` action.
|
||||||
|
|
||||||
|
1. An action to send an ICMP reply.
|
||||||
|
|
||||||
|
1. An action to send an ARP reply.
|
||||||
|
|
||||||
|
1. A table (named `forward`) that will forward IPv4 packets, send an ARP
|
||||||
|
reply, send an ICMP reply, or drop a packet.
|
||||||
|
|
||||||
|
1. An `apply` block that implements the control logic to invoke the two
|
||||||
|
tables.
|
||||||
|
|
||||||
|
1. A deparser that emits headers in the proper order.
|
||||||
|
|
||||||
|
To keep the exercise simple, we will ignore the `ipv4_checksum`. You should not
|
||||||
|
need any control plane rules for this exercise.
|
||||||
|
|
||||||
|
## Step 3: Run your solution
|
||||||
|
|
||||||
|
Follow the instructions from Step 1. This time, you should be able to
|
||||||
|
successfully `ping` the switch.
|
||||||
|
|
||||||
|
### Troubleshooting
|
||||||
|
|
||||||
|
There are several ways that problems might manifest:
|
||||||
|
|
||||||
|
1. `arp.p4` fails to compile. In this case, `run.sh` will report the
|
||||||
|
error emitted from the compiler and stop.
|
||||||
|
|
||||||
|
1. `arp.p4` compiles, but the switch does not process packets in the desired
|
||||||
|
way. The `build/logs/<switch-name>.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.
|
||||||
|
|
||||||
|
> Note that there are no control plane rules installed in this example, and so
|
||||||
|
> the `receive.py` and `send.py` scripts from the [IPv4
|
||||||
|
> Forwarding](../ipv4_forward) example will not work.
|
||||||
|
|
||||||
|
#### Cleaning up Mininet
|
||||||
|
|
||||||
|
In the latter case above, `run.sh` 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 the next exercise:
|
||||||
|
turning your switch into a [Calculator](../calc).
|
232
P4D2_2017/exercises/arp/arp.p4
Normal file
232
P4D2_2017/exercises/arp/arp.p4
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
/* -*- P4_16 -*- */
|
||||||
|
|
||||||
|
#include <core.p4>
|
||||||
|
#include <v1model.p4>
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
*********************** C O N S T A N T S *****************************
|
||||||
|
*************************************************************************/
|
||||||
|
/* Define the useful global constants for your program */
|
||||||
|
const bit<16> ETHERTYPE_IPV4 = 0x0800;
|
||||||
|
const bit<16> ETHERTYPE_ARP = 0x0806;
|
||||||
|
const bit<8> IPPROTO_ICMP = 0x01;
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
*********************** H E A D E R S *********************************
|
||||||
|
*************************************************************************/
|
||||||
|
/* Define the headers the program will recognize */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Standard ethernet header
|
||||||
|
*/
|
||||||
|
typedef bit<48> mac_addr_t;
|
||||||
|
typedef bit<32> ipv4_addr_t;
|
||||||
|
typedef bit<9> port_id_t;
|
||||||
|
|
||||||
|
header ethernet_t {
|
||||||
|
mac_addr_t dstAddr;
|
||||||
|
mac_addr_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;
|
||||||
|
ipv4_addr_t srcAddr;
|
||||||
|
ipv4_addr_t dstAddr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bit<16> ARP_HTYPE_ETHERNET = 0x0001;
|
||||||
|
const bit<16> ARP_PTYPE_IPV4 = 0x0800;
|
||||||
|
const bit<8> ARP_HLEN_ETHERNET = 6;
|
||||||
|
const bit<8> ARP_PLEN_IPV4 = 4;
|
||||||
|
const bit<16> ARP_OPER_REQUEST = 1;
|
||||||
|
const bit<16> ARP_OPER_REPLY = 2;
|
||||||
|
|
||||||
|
header arp_t {
|
||||||
|
bit<16> htype;
|
||||||
|
bit<16> ptype;
|
||||||
|
bit<8> hlen;
|
||||||
|
bit<8> plen;
|
||||||
|
bit<16> oper;
|
||||||
|
}
|
||||||
|
|
||||||
|
header arp_ipv4_t {
|
||||||
|
mac_addr_t sha;
|
||||||
|
ipv4_addr_t spa;
|
||||||
|
mac_addr_t tha;
|
||||||
|
ipv4_addr_t tpa;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bit<8> ICMP_ECHO_REQUEST = 8;
|
||||||
|
const bit<8> ICMP_ECHO_REPLY = 0;
|
||||||
|
|
||||||
|
header icmp_t {
|
||||||
|
bit<8> type;
|
||||||
|
bit<8> code;
|
||||||
|
bit<16> checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Assemble headers in a single struct */
|
||||||
|
struct my_headers_t {
|
||||||
|
ethernet_t ethernet;
|
||||||
|
arp_t arp;
|
||||||
|
arp_ipv4_t arp_ipv4;
|
||||||
|
ipv4_t ipv4;
|
||||||
|
icmp_t icmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
*********************** M E T A D A T A *******************************
|
||||||
|
*************************************************************************/
|
||||||
|
/* Define the global metadata for your program */
|
||||||
|
|
||||||
|
struct my_metadata_t {
|
||||||
|
ipv4_addr_t dst_ipv4;
|
||||||
|
mac_addr_t mac_da;
|
||||||
|
mac_addr_t mac_sa;
|
||||||
|
port_id_t egress_port;
|
||||||
|
mac_addr_t my_mac;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
*********************** P A R S E R ***********************************
|
||||||
|
*************************************************************************/
|
||||||
|
parser MyParser(
|
||||||
|
packet_in packet,
|
||||||
|
out my_headers_t hdr,
|
||||||
|
inout my_metadata_t meta,
|
||||||
|
inout standard_metadata_t standard_metadata)
|
||||||
|
{
|
||||||
|
state start {
|
||||||
|
packet.extract(hdr.ethernet);
|
||||||
|
transition select(hdr.ethernet.etherType) {
|
||||||
|
ETHERTYPE_IPV4 : parse_ipv4;
|
||||||
|
ETHERTYPE_ARP : parse_arp;
|
||||||
|
default : accept;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state parse_arp {
|
||||||
|
/*
|
||||||
|
* TODO: parse ARP. If the ARP protocol field is
|
||||||
|
* IPV4, transtion to parse_arp_ipv4
|
||||||
|
*/
|
||||||
|
transition accept;
|
||||||
|
}
|
||||||
|
|
||||||
|
state parse_arp_ipv4 {
|
||||||
|
/*
|
||||||
|
* TODO: parse ARP_IPV4. Hint: one
|
||||||
|
* possible solution is to store the
|
||||||
|
* target packet address in a meta data
|
||||||
|
* field when parsing.
|
||||||
|
*/
|
||||||
|
transition accept;
|
||||||
|
}
|
||||||
|
|
||||||
|
state parse_ipv4 {
|
||||||
|
packet.extract(hdr.ipv4);
|
||||||
|
meta.dst_ipv4 = hdr.ipv4.dstAddr;
|
||||||
|
transition select(hdr.ipv4.protocol) {
|
||||||
|
IPPROTO_ICMP : parse_icmp;
|
||||||
|
default : accept;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state parse_icmp {
|
||||||
|
/* TODO: parse ICMP */
|
||||||
|
transition accept;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
************ C H E C K S U M V E R I F I C A T I O N *************
|
||||||
|
*************************************************************************/
|
||||||
|
control MyVerifyChecksum(
|
||||||
|
in my_headers_t hdr,
|
||||||
|
inout my_metadata_t meta)
|
||||||
|
{
|
||||||
|
apply { }
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
************** I N G R E S S P R O C E S S I N G *******************
|
||||||
|
*************************************************************************/
|
||||||
|
control MyIngress(
|
||||||
|
inout my_headers_t hdr,
|
||||||
|
inout my_metadata_t meta,
|
||||||
|
inout standard_metadata_t standard_metadata)
|
||||||
|
{
|
||||||
|
action drop() {
|
||||||
|
mark_to_drop();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO: Define actions and tables here */
|
||||||
|
|
||||||
|
|
||||||
|
table ipv4_lpm {
|
||||||
|
key = { meta.dst_ipv4 : lpm; }
|
||||||
|
actions = { /* TODO: add actions */ drop; }
|
||||||
|
default_action = drop();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
apply {
|
||||||
|
meta.my_mac = 0x000102030405;
|
||||||
|
ipv4_lpm.apply();
|
||||||
|
/* TODO: add contol logic */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
**************** E G R E S S P R O C E S S I N G *******************
|
||||||
|
*************************************************************************/
|
||||||
|
control MyEgress(
|
||||||
|
inout my_headers_t hdr,
|
||||||
|
inout my_metadata_t 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 my_headers_t hdr,
|
||||||
|
inout my_metadata_t meta)
|
||||||
|
{
|
||||||
|
|
||||||
|
apply { }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
*********************** D E P A R S E R *******************************
|
||||||
|
*************************************************************************/
|
||||||
|
control MyDeparser(
|
||||||
|
packet_out packet,
|
||||||
|
in my_headers_t hdr)
|
||||||
|
{
|
||||||
|
apply {
|
||||||
|
/* TODO: Implement deparser */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
V1Switch(
|
||||||
|
MyParser(),
|
||||||
|
MyVerifyChecksum(),
|
||||||
|
MyIngress(),
|
||||||
|
MyEgress(),
|
||||||
|
MyComputeChecksum(),
|
||||||
|
MyDeparser()
|
||||||
|
) main;
|
10
P4D2_2017/exercises/arp/p4app.json
Normal file
10
P4D2_2017/exercises/arp/p4app.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"program": "arp.p4",
|
||||||
|
"language": "p4-16",
|
||||||
|
"targets": {
|
||||||
|
"mininet": {
|
||||||
|
"num-hosts": 2,
|
||||||
|
"switch-config": "simple_router.config"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
5
P4D2_2017/exercises/arp/run.sh
Executable file
5
P4D2_2017/exercises/arp/run.sh
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
P4APPRUNNER=../../utils/p4apprunner.py
|
||||||
|
mkdir -p build
|
||||||
|
tar -czf build/p4app.tgz * --exclude='build'
|
||||||
|
#cd build
|
||||||
|
sudo python $P4APPRUNNER p4app.tgz --build-dir ./build
|
1
P4D2_2017/exercises/arp/simple_router.config
Normal file
1
P4D2_2017/exercises/arp/simple_router.config
Normal file
@ -0,0 +1 @@
|
|||||||
|
table_add ipv4_lpm set_dst_info 10.0.1.10/24 => 00:00:01:00:00:01 00:00:02:00:00:02 1
|
305
P4D2_2017/exercises/arp/solution/arp.p4
Normal file
305
P4D2_2017/exercises/arp/solution/arp.p4
Normal file
@ -0,0 +1,305 @@
|
|||||||
|
/* -*- P4_16 -*- */
|
||||||
|
|
||||||
|
#include <core.p4>
|
||||||
|
#include <v1model.p4>
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
*********************** C O N S T A N T S *****************************
|
||||||
|
*************************************************************************/
|
||||||
|
/* Define the useful global constants for your program */
|
||||||
|
const bit<16> ETHERTYPE_IPV4 = 0x0800;
|
||||||
|
const bit<16> ETHERTYPE_ARP = 0x0806;
|
||||||
|
const bit<8> IPPROTO_ICMP = 0x01;
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
*********************** H E A D E R S *********************************
|
||||||
|
*************************************************************************/
|
||||||
|
/* Define the headers the program will recognize */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Standard ethernet header
|
||||||
|
*/
|
||||||
|
typedef bit<48> mac_addr_t;
|
||||||
|
typedef bit<32> ipv4_addr_t;
|
||||||
|
typedef bit<9> port_id_t;
|
||||||
|
|
||||||
|
header ethernet_t {
|
||||||
|
mac_addr_t dstAddr;
|
||||||
|
mac_addr_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;
|
||||||
|
ipv4_addr_t srcAddr;
|
||||||
|
ipv4_addr_t dstAddr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bit<16> ARP_HTYPE_ETHERNET = 0x0001;
|
||||||
|
const bit<16> ARP_PTYPE_IPV4 = 0x0800;
|
||||||
|
const bit<8> ARP_HLEN_ETHERNET = 6;
|
||||||
|
const bit<8> ARP_PLEN_IPV4 = 4;
|
||||||
|
const bit<16> ARP_OPER_REQUEST = 1;
|
||||||
|
const bit<16> ARP_OPER_REPLY = 2;
|
||||||
|
|
||||||
|
header arp_t {
|
||||||
|
bit<16> htype;
|
||||||
|
bit<16> ptype;
|
||||||
|
bit<8> hlen;
|
||||||
|
bit<8> plen;
|
||||||
|
bit<16> oper;
|
||||||
|
}
|
||||||
|
|
||||||
|
header arp_ipv4_t {
|
||||||
|
mac_addr_t sha;
|
||||||
|
ipv4_addr_t spa;
|
||||||
|
mac_addr_t tha;
|
||||||
|
ipv4_addr_t tpa;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bit<8> ICMP_ECHO_REQUEST = 8;
|
||||||
|
const bit<8> ICMP_ECHO_REPLY = 0;
|
||||||
|
|
||||||
|
header icmp_t {
|
||||||
|
bit<8> type;
|
||||||
|
bit<8> code;
|
||||||
|
bit<16> checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Assemble headers in a single struct */
|
||||||
|
struct my_headers_t {
|
||||||
|
ethernet_t ethernet;
|
||||||
|
arp_t arp;
|
||||||
|
arp_ipv4_t arp_ipv4;
|
||||||
|
ipv4_t ipv4;
|
||||||
|
icmp_t icmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
*********************** M E T A D A T A *******************************
|
||||||
|
*************************************************************************/
|
||||||
|
/* Define the global metadata for your program */
|
||||||
|
|
||||||
|
struct my_metadata_t {
|
||||||
|
ipv4_addr_t dst_ipv4;
|
||||||
|
mac_addr_t mac_da;
|
||||||
|
mac_addr_t mac_sa;
|
||||||
|
port_id_t egress_port;
|
||||||
|
mac_addr_t my_mac;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
*********************** P A R S E R ***********************************
|
||||||
|
*************************************************************************/
|
||||||
|
parser MyParser(
|
||||||
|
packet_in packet,
|
||||||
|
out my_headers_t hdr,
|
||||||
|
inout my_metadata_t meta,
|
||||||
|
inout standard_metadata_t standard_metadata)
|
||||||
|
{
|
||||||
|
state start {
|
||||||
|
packet.extract(hdr.ethernet);
|
||||||
|
transition select(hdr.ethernet.etherType) {
|
||||||
|
ETHERTYPE_IPV4 : parse_ipv4;
|
||||||
|
ETHERTYPE_ARP : parse_arp;
|
||||||
|
default : accept;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state parse_arp {
|
||||||
|
packet.extract(hdr.arp);
|
||||||
|
transition select(hdr.arp.htype, hdr.arp.ptype,
|
||||||
|
hdr.arp.hlen, hdr.arp.plen) {
|
||||||
|
(ARP_HTYPE_ETHERNET, ARP_PTYPE_IPV4,
|
||||||
|
ARP_HLEN_ETHERNET, ARP_PLEN_IPV4) : parse_arp_ipv4;
|
||||||
|
default : accept;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state parse_arp_ipv4 {
|
||||||
|
packet.extract(hdr.arp_ipv4);
|
||||||
|
meta.dst_ipv4 = hdr.arp_ipv4.tpa;
|
||||||
|
transition accept;
|
||||||
|
}
|
||||||
|
|
||||||
|
state parse_ipv4 {
|
||||||
|
packet.extract(hdr.ipv4);
|
||||||
|
meta.dst_ipv4 = hdr.ipv4.dstAddr;
|
||||||
|
transition select(hdr.ipv4.protocol) {
|
||||||
|
IPPROTO_ICMP : parse_icmp;
|
||||||
|
default : accept;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state parse_icmp {
|
||||||
|
packet.extract(hdr.icmp);
|
||||||
|
transition accept;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
************ C H E C K S U M V E R I F I C A T I O N *************
|
||||||
|
*************************************************************************/
|
||||||
|
control MyVerifyChecksum(
|
||||||
|
in my_headers_t hdr,
|
||||||
|
inout my_metadata_t meta)
|
||||||
|
{
|
||||||
|
apply { }
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
************** I N G R E S S P R O C E S S I N G *******************
|
||||||
|
*************************************************************************/
|
||||||
|
control MyIngress(
|
||||||
|
inout my_headers_t hdr,
|
||||||
|
inout my_metadata_t meta,
|
||||||
|
inout standard_metadata_t standard_metadata)
|
||||||
|
{
|
||||||
|
action drop() {
|
||||||
|
mark_to_drop();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
action set_dst_info(mac_addr_t mac_da,
|
||||||
|
mac_addr_t mac_sa,
|
||||||
|
port_id_t egress_port)
|
||||||
|
{
|
||||||
|
meta.mac_da = mac_da;
|
||||||
|
meta.mac_sa = mac_sa;
|
||||||
|
meta.egress_port = egress_port;
|
||||||
|
}
|
||||||
|
|
||||||
|
table ipv4_lpm {
|
||||||
|
key = { meta.dst_ipv4 : lpm; }
|
||||||
|
actions = { set_dst_info; drop; }
|
||||||
|
default_action = drop();
|
||||||
|
}
|
||||||
|
|
||||||
|
action forward_ipv4() {
|
||||||
|
hdr.ethernet.dstAddr = meta.mac_da;
|
||||||
|
hdr.ethernet.srcAddr = meta.mac_sa;
|
||||||
|
hdr.ipv4.ttl = hdr.ipv4.ttl - 1;
|
||||||
|
|
||||||
|
standard_metadata.egress_spec = meta.egress_port;
|
||||||
|
}
|
||||||
|
|
||||||
|
action send_arp_reply() {
|
||||||
|
hdr.ethernet.dstAddr = hdr.arp_ipv4.sha;
|
||||||
|
hdr.ethernet.srcAddr = meta.mac_da;
|
||||||
|
|
||||||
|
hdr.arp.oper = ARP_OPER_REPLY;
|
||||||
|
|
||||||
|
hdr.arp_ipv4.tha = hdr.arp_ipv4.sha;
|
||||||
|
hdr.arp_ipv4.tpa = hdr.arp_ipv4.spa;
|
||||||
|
hdr.arp_ipv4.sha = meta.mac_da;
|
||||||
|
hdr.arp_ipv4.spa = meta.dst_ipv4;
|
||||||
|
|
||||||
|
standard_metadata.egress_spec = standard_metadata.ingress_port;
|
||||||
|
}
|
||||||
|
|
||||||
|
action send_icmp_reply() {
|
||||||
|
mac_addr_t tmp_mac;
|
||||||
|
ipv4_addr_t tmp_ip;
|
||||||
|
|
||||||
|
tmp_mac = hdr.ethernet.dstAddr;
|
||||||
|
hdr.ethernet.dstAddr = hdr.ethernet.srcAddr;
|
||||||
|
hdr.ethernet.srcAddr = tmp_mac;
|
||||||
|
|
||||||
|
tmp_ip = hdr.ipv4.dstAddr;
|
||||||
|
hdr.ipv4.dstAddr = hdr.ipv4.srcAddr;
|
||||||
|
hdr.ipv4.srcAddr = tmp_ip;
|
||||||
|
|
||||||
|
hdr.icmp.type = ICMP_ECHO_REPLY;
|
||||||
|
hdr.icmp.checksum = 0; // For now
|
||||||
|
|
||||||
|
standard_metadata.egress_spec = standard_metadata.ingress_port;
|
||||||
|
}
|
||||||
|
|
||||||
|
table forward {
|
||||||
|
key = {
|
||||||
|
hdr.arp.isValid() : exact;
|
||||||
|
hdr.arp.oper : ternary;
|
||||||
|
hdr.arp_ipv4.isValid() : exact;
|
||||||
|
hdr.ipv4.isValid() : exact;
|
||||||
|
hdr.icmp.isValid() : exact;
|
||||||
|
hdr.icmp.type : ternary;
|
||||||
|
}
|
||||||
|
actions = {
|
||||||
|
forward_ipv4;
|
||||||
|
send_arp_reply;
|
||||||
|
send_icmp_reply;
|
||||||
|
drop;
|
||||||
|
}
|
||||||
|
const default_action = drop();
|
||||||
|
const entries = {
|
||||||
|
( true, ARP_OPER_REQUEST, true, false, false, _ ) :
|
||||||
|
send_arp_reply();
|
||||||
|
( false, _, false, true, false, _ ) :
|
||||||
|
forward_ipv4();
|
||||||
|
( false, _, false, true, true, ICMP_ECHO_REQUEST ) :
|
||||||
|
send_icmp_reply();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply {
|
||||||
|
meta.my_mac = 0x000102030405;
|
||||||
|
ipv4_lpm.apply();
|
||||||
|
forward.apply();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
**************** E G R E S S P R O C E S S I N G *******************
|
||||||
|
*************************************************************************/
|
||||||
|
control MyEgress(
|
||||||
|
inout my_headers_t hdr,
|
||||||
|
inout my_metadata_t 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 my_headers_t hdr,
|
||||||
|
inout my_metadata_t meta)
|
||||||
|
{
|
||||||
|
apply { }
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
*********************** D E P A R S E R *******************************
|
||||||
|
*************************************************************************/
|
||||||
|
control MyDeparser(
|
||||||
|
packet_out packet,
|
||||||
|
in my_headers_t hdr)
|
||||||
|
{
|
||||||
|
apply {
|
||||||
|
packet.emit(hdr.ethernet);
|
||||||
|
/* ARP Case */
|
||||||
|
packet.emit(hdr.arp);
|
||||||
|
packet.emit(hdr.arp_ipv4);
|
||||||
|
/* IPv4 case */
|
||||||
|
packet.emit(hdr.ipv4);
|
||||||
|
packet.emit(hdr.icmp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
V1Switch(
|
||||||
|
MyParser(),
|
||||||
|
MyVerifyChecksum(),
|
||||||
|
MyIngress(),
|
||||||
|
MyEgress(),
|
||||||
|
MyComputeChecksum(),
|
||||||
|
MyDeparser()
|
||||||
|
) main;
|
115
P4D2_2017/exercises/calc/README.md
Normal file
115
P4D2_2017/exercises/calc/README.md
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
# 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
|
||||||
|
./run.sh
|
||||||
|
```
|
||||||
|
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.10` and `10.0.2.10`.
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
>
|
||||||
|
```
|
||||||
|
|
||||||
|
If all of this works well. Congratulations! You have finished this
|
||||||
|
tutorial.
|
||||||
|
|
127
P4D2_2017/exercises/calc/calc.p4
Normal file
127
P4D2_2017/exercises/calc/calc.p4
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
/* -*- 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 <core.p4>
|
||||||
|
#include <v1model.p4>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Define the headers the program will recognize
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
struct my_headers_t {
|
||||||
|
/* TODO: fill this in */
|
||||||
|
}
|
||||||
|
|
||||||
|
struct my_metadata_t {
|
||||||
|
/* In our case it is empty */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
*********************** P A R S E R ***********************************
|
||||||
|
*************************************************************************/
|
||||||
|
parser MyParser(
|
||||||
|
packet_in packet,
|
||||||
|
out my_headers_t hdr,
|
||||||
|
inout my_metadata_t meta,
|
||||||
|
inout standard_metadata_t standard_metadata)
|
||||||
|
{
|
||||||
|
state start { transition accept; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
************ C H E C K S U M V E R I F I C A T I O N *************
|
||||||
|
*************************************************************************/
|
||||||
|
control MyVerifyChecksum(
|
||||||
|
in my_headers_t hdr,
|
||||||
|
inout my_metadata_t meta)
|
||||||
|
{
|
||||||
|
apply {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
************** I N G R E S S P R O C E S S I N G *******************
|
||||||
|
*************************************************************************/
|
||||||
|
control MyIngress(
|
||||||
|
inout my_headers_t hdr,
|
||||||
|
inout my_metadata_t meta,
|
||||||
|
inout standard_metadata_t standard_metadata)
|
||||||
|
{
|
||||||
|
apply { }
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
**************** E G R E S S P R O C E S S I N G *******************
|
||||||
|
*************************************************************************/
|
||||||
|
control MyEgress(
|
||||||
|
inout my_headers_t hdr,
|
||||||
|
inout my_metadata_t 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 my_headers_t hdr,
|
||||||
|
inout my_metadata_t meta)
|
||||||
|
{
|
||||||
|
apply { }
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
*********************** D E P A R S E R *******************************
|
||||||
|
*************************************************************************/
|
||||||
|
control MyDeparser(
|
||||||
|
packet_out packet,
|
||||||
|
in my_headers_t hdr)
|
||||||
|
{
|
||||||
|
apply { }
|
||||||
|
}
|
||||||
|
|
||||||
|
V1Switch(
|
||||||
|
MyParser(),
|
||||||
|
MyVerifyChecksum(),
|
||||||
|
MyIngress(),
|
||||||
|
MyEgress(),
|
||||||
|
MyComputeChecksum(),
|
||||||
|
MyDeparser()
|
||||||
|
) main;
|
95
P4D2_2017/exercises/calc/calc.py
Executable file
95
P4D2_2017/exercises/calc/calc.py
Executable file
@ -0,0 +1,95 @@
|
|||||||
|
#!/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
|
||||||
|
|
||||||
|
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.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()
|
10
P4D2_2017/exercises/calc/p4app.json
Normal file
10
P4D2_2017/exercises/calc/p4app.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"program": "calc.p4",
|
||||||
|
"language": "p4-16",
|
||||||
|
"targets": {
|
||||||
|
"mininet": {
|
||||||
|
"num-hosts": 2,
|
||||||
|
"switch-config": "simple_router.config"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
5
P4D2_2017/exercises/calc/run.sh
Executable file
5
P4D2_2017/exercises/calc/run.sh
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
P4APPRUNNER=../../utils/p4apprunner.py
|
||||||
|
mkdir -p build
|
||||||
|
tar -czf build/p4app.tgz * --exclude='build'
|
||||||
|
#cd build
|
||||||
|
sudo python $P4APPRUNNER p4app.tgz --build-dir ./build
|
0
P4D2_2017/exercises/calc/simple_router.config
Normal file
0
P4D2_2017/exercises/calc/simple_router.config
Normal file
263
P4D2_2017/exercises/calc/solution/calc.p4
Normal file
263
P4D2_2017/exercises/calc/solution/calc.p4
Normal file
@ -0,0 +1,263 @@
|
|||||||
|
/* -*- 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 <core.p4>
|
||||||
|
#include <v1model.p4>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 = 0x7e; // '|'
|
||||||
|
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 my_headers_t {
|
||||||
|
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 my_metadata_t {
|
||||||
|
/* In our case it is empty */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
*********************** P A R S E R ***********************************
|
||||||
|
*************************************************************************/
|
||||||
|
parser MyParser(
|
||||||
|
packet_in packet,
|
||||||
|
out my_headers_t hdr,
|
||||||
|
inout my_metadata_t 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<p4calc_t>().p,
|
||||||
|
packet.lookahead<p4calc_t>().four,
|
||||||
|
packet.lookahead<p4calc_t>().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(
|
||||||
|
in my_headers_t hdr,
|
||||||
|
inout my_metadata_t meta)
|
||||||
|
{
|
||||||
|
apply {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
************** I N G R E S S P R O C E S S I N G *******************
|
||||||
|
*************************************************************************/
|
||||||
|
control MyIngress(
|
||||||
|
inout my_headers_t hdr,
|
||||||
|
inout my_metadata_t 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 my_headers_t hdr,
|
||||||
|
inout my_metadata_t 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 my_headers_t hdr,
|
||||||
|
inout my_metadata_t meta)
|
||||||
|
{
|
||||||
|
apply { }
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
*********************** D E P A R S E R *******************************
|
||||||
|
*************************************************************************/
|
||||||
|
control MyDeparser(
|
||||||
|
packet_out packet,
|
||||||
|
in my_headers_t hdr)
|
||||||
|
{
|
||||||
|
apply {
|
||||||
|
packet.emit(hdr.ethernet);
|
||||||
|
packet.emit(hdr.p4calc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
V1Switch(
|
||||||
|
MyParser(),
|
||||||
|
MyVerifyChecksum(),
|
||||||
|
MyIngress(),
|
||||||
|
MyEgress(),
|
||||||
|
MyComputeChecksum(),
|
||||||
|
MyDeparser()
|
||||||
|
) main;
|
156
P4D2_2017/exercises/ipv4_forward/README.md
Normal file
156
P4D2_2017/exercises/ipv4_forward/README.md
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
# Implementing L3 Forwarding
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
The objective of this tutorial is to implement basic L3 forwarding. To
|
||||||
|
keep the exercise small, 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,
|
||||||
|
`ipv4_forward.p4`, which initially drops all packets. Your job (in the next
|
||||||
|
step) will be to extend it to properly forward IPv4 packets.
|
||||||
|
|
||||||
|
Before that, let's compile the incomplete `ip4v_forward.p4` and bring up a
|
||||||
|
switch in Mininet to test its behavior.
|
||||||
|
|
||||||
|
1. In your shell, run:
|
||||||
|
```bash
|
||||||
|
./run.sh
|
||||||
|
```
|
||||||
|
This will:
|
||||||
|
* compile `ip4v_forward.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.10`, `10.0.2.10`, etc.
|
||||||
|
|
||||||
|
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.10 "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
|
||||||
|
`ip4v_forward.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 `run.sh` 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.
|
||||||
|
|
||||||
|
**Important:** A P4 program also defines the interface between the switch
|
||||||
|
pipeline and control plane. The `sX-commands.txt` files contain lists 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 L3 forwarding
|
||||||
|
|
||||||
|
The `ipv4_forward.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 `ipv4_forward.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`), 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. **TODO:** A control that:
|
||||||
|
1. Defines a table that will read an IPv4 destination address, and
|
||||||
|
invoke either `drop` or `ipv4_forward`.
|
||||||
|
1. An `apply` block that applies the table.
|
||||||
|
7. A deparser that selects the order in which fields inserted into the outgoing
|
||||||
|
packet.
|
||||||
|
8. 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 ways that problems might manifest:
|
||||||
|
|
||||||
|
1. `ipv4_forward.p4` fails to compile. In this case, `run.sh` will report the
|
||||||
|
error emitted from the compiler and stop.
|
||||||
|
|
||||||
|
2. `ipv4_forward.p4` compiles but does not support the control plane rules in
|
||||||
|
the `sX-commands.txt` files that `run.sh` tries to install using the BMv2 CLI.
|
||||||
|
In this case, `run.sh` will report these errors to `stderr`. Use these error
|
||||||
|
messages to fix your `ipv4_forward.p4` implementation.
|
||||||
|
|
||||||
|
3. `ipv4_forward.p4` compiles, and the control plane rules are installed, but
|
||||||
|
the switch does not process packets in the desired way. The
|
||||||
|
`build/logs/<switch-name>.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, `run.sh` 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 the next exercise:
|
||||||
|
implementing basic network telemetry [Multi-Hop Route Inspection](../mri).
|
170
P4D2_2017/exercises/ipv4_forward/ipv4_forward.p4
Normal file
170
P4D2_2017/exercises/ipv4_forward/ipv4_forward.p4
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
/* -*- P4_16 -*- */
|
||||||
|
#include <core.p4>
|
||||||
|
#include <v1model.p4>
|
||||||
|
|
||||||
|
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 ParserImpl(packet_in packet,
|
||||||
|
out headers hdr,
|
||||||
|
inout metadata meta,
|
||||||
|
inout standard_metadata_t standard_metadata) {
|
||||||
|
|
||||||
|
|
||||||
|
state start {
|
||||||
|
/* TODO: add transition to parsing ethernet */
|
||||||
|
transition accept;
|
||||||
|
}
|
||||||
|
|
||||||
|
state parse_ethernet {
|
||||||
|
/* TODO: add parsing ethernet */
|
||||||
|
}
|
||||||
|
|
||||||
|
state parse_ipv4 {
|
||||||
|
/* TODO: add parsing ipv4 */
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
************ C H E C K S U M V E R I F I C A T I O N *************
|
||||||
|
*************************************************************************/
|
||||||
|
|
||||||
|
control verifyChecksum(in headers hdr, inout metadata meta) {
|
||||||
|
apply { }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
************** I N G R E S S P R O C E S S I N G *******************
|
||||||
|
*************************************************************************/
|
||||||
|
|
||||||
|
control ingress(inout headers hdr, inout metadata meta, inout standard_metadata_t standard_metadata) {
|
||||||
|
|
||||||
|
/* This action will drop packets */
|
||||||
|
action drop() {
|
||||||
|
mark_to_drop();
|
||||||
|
}
|
||||||
|
|
||||||
|
action ipv4_forward(macAddr_t dstAddr, egressSpec_t port) {
|
||||||
|
/*
|
||||||
|
* TODO: Implement the logic to:
|
||||||
|
* 1. Set the standard_metadata.egress_spec to the output port.
|
||||||
|
* 2. Set the ethernet srcAddr to the ethernet dstAddr.
|
||||||
|
* 3. Set the ethernet dstAddr to the dstAddr passed as a parameter.
|
||||||
|
* 4. Decrement the IP TTL.
|
||||||
|
* BONUS: Handle the case where TTL is 0.
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
table ipv4_lpm {
|
||||||
|
key = {
|
||||||
|
/* TODO: declare that the table will do a longest-prefix match (lpm)
|
||||||
|
on the IP destination address. */
|
||||||
|
}
|
||||||
|
actions = {
|
||||||
|
/* TODO: declare the possible actions: ipv4_forward or drop. */
|
||||||
|
NoAction;
|
||||||
|
}
|
||||||
|
size = 1024;
|
||||||
|
default_action = NoAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
apply {
|
||||||
|
/* TODO: replace drop with logic to:
|
||||||
|
* 1. Check if the ipv4 header is valid.
|
||||||
|
* 2. apply the table ipv4_lpm.
|
||||||
|
*/
|
||||||
|
drop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
**************** E G R E S S P R O C E S S I N G *******************
|
||||||
|
*************************************************************************/
|
||||||
|
|
||||||
|
control egress(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 computeChecksum(
|
||||||
|
inout headers hdr,
|
||||||
|
inout metadata meta)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Ignore checksum for now. The reference solution contains a checksum
|
||||||
|
* implementation.
|
||||||
|
*/
|
||||||
|
apply { }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
*********************** D E P A R S E R *******************************
|
||||||
|
*************************************************************************/
|
||||||
|
|
||||||
|
control DeparserImpl(packet_out packet, in headers hdr) {
|
||||||
|
apply {
|
||||||
|
packet.emit(hdr.ethernet);
|
||||||
|
packet.emit(hdr.ipv4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
*********************** S W I T C H *******************************
|
||||||
|
*************************************************************************/
|
||||||
|
|
||||||
|
V1Switch(
|
||||||
|
ParserImpl(),
|
||||||
|
verifyChecksum(),
|
||||||
|
ingress(),
|
||||||
|
egress(),
|
||||||
|
computeChecksum(),
|
||||||
|
DeparserImpl()
|
||||||
|
) main;
|
33
P4D2_2017/exercises/ipv4_forward/p4app.json
Normal file
33
P4D2_2017/exercises/ipv4_forward/p4app.json
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"program": "ipv4_forward.p4",
|
||||||
|
"language": "p4-16",
|
||||||
|
"targets": {
|
||||||
|
"multiswitch": {
|
||||||
|
"auto-control-plane": true,
|
||||||
|
"cli": true,
|
||||||
|
"pcap_dump": true,
|
||||||
|
"bmv2_log": true,
|
||||||
|
"links": [["h1", "s1"], ["s1", "s2"], ["s1", "s3"], ["s3", "s2"], ["s2", "h2"], ["s3", "h3"]],
|
||||||
|
"hosts": {
|
||||||
|
"h1": {
|
||||||
|
},
|
||||||
|
"h2": {
|
||||||
|
},
|
||||||
|
"h3": {
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
"switches": {
|
||||||
|
"s1": {
|
||||||
|
"entries": "s1-commands.txt"
|
||||||
|
},
|
||||||
|
"s2": {
|
||||||
|
"entries": "s2-commands.txt"
|
||||||
|
},
|
||||||
|
"s3": {
|
||||||
|
"entries": "s3-commands.txt"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
50
P4D2_2017/exercises/ipv4_forward/receive.py
Executable file
50
P4D2_2017/exercises/ipv4_forward/receive.py
Executable file
@ -0,0 +1,50 @@
|
|||||||
|
#!/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 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():
|
||||||
|
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()
|
5
P4D2_2017/exercises/ipv4_forward/run.sh
Executable file
5
P4D2_2017/exercises/ipv4_forward/run.sh
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
P4APPRUNNER=../../utils/p4apprunner.py
|
||||||
|
mkdir -p build
|
||||||
|
tar -czf build/p4app.tgz * --exclude='build'
|
||||||
|
#cd build
|
||||||
|
sudo python $P4APPRUNNER p4app.tgz --build-dir ./build
|
4
P4D2_2017/exercises/ipv4_forward/s1-commands.txt
Normal file
4
P4D2_2017/exercises/ipv4_forward/s1-commands.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
table_set_default ipv4_lpm drop
|
||||||
|
table_add ipv4_lpm ipv4_forward 10.0.1.10/32 => 00:aa:00:01:00:01 1
|
||||||
|
table_add ipv4_lpm ipv4_forward 10.0.2.10/32 => f2:ed:e6:df:4e:fa 2
|
||||||
|
table_add ipv4_lpm ipv4_forward 10.0.3.10/32 => f2:ed:e6:df:4e:fb 3
|
4
P4D2_2017/exercises/ipv4_forward/s2-commands.txt
Normal file
4
P4D2_2017/exercises/ipv4_forward/s2-commands.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
table_set_default ipv4_lpm drop
|
||||||
|
table_add ipv4_lpm ipv4_forward 10.0.2.10/32 => 00:aa:00:02:00:02 1
|
||||||
|
table_add ipv4_lpm ipv4_forward 10.0.1.10/32 => 22:a8:04:41:ab:d3 2
|
||||||
|
table_add ipv4_lpm ipv4_forward 10.0.3.10/32 => 22:a8:04:41:ab:d4 3
|
4
P4D2_2017/exercises/ipv4_forward/s3-commands.txt
Normal file
4
P4D2_2017/exercises/ipv4_forward/s3-commands.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
table_set_default ipv4_lpm drop
|
||||||
|
table_add ipv4_lpm ipv4_forward 10.0.3.10/32 => 00:aa:00:03:00:01 1
|
||||||
|
table_add ipv4_lpm ipv4_forward 10.0.1.10/32 => f2:ed:e6:df:4e:fb 2
|
||||||
|
table_add ipv4_lpm ipv4_forward 10.0.2.10/32 => f2:ed:e6:df:4e:fa 3
|
40
P4D2_2017/exercises/ipv4_forward/send.py
Executable file
40
P4D2_2017/exercises/ipv4_forward/send.py
Executable file
@ -0,0 +1,40 @@
|
|||||||
|
#!/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
|
||||||
|
|
||||||
|
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: <destination> "<message>"'
|
||||||
|
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') / IP(dst=addr) / UDP(dport=4321, sport=1234) / sys.argv[2]
|
||||||
|
pkt.show2()
|
||||||
|
sendp(pkt, iface=iface, verbose=False)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
180
P4D2_2017/exercises/ipv4_forward/solution/ipv4_forward.p4
Normal file
180
P4D2_2017/exercises/ipv4_forward/solution/ipv4_forward.p4
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
/* -*- P4_16 -*- */
|
||||||
|
#include <core.p4>
|
||||||
|
#include <v1model.p4>
|
||||||
|
|
||||||
|
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 ParserImpl(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 verifyChecksum(in headers hdr, inout metadata meta) {
|
||||||
|
apply { }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
************** I N G R E S S P R O C E S S I N G *******************
|
||||||
|
*************************************************************************/
|
||||||
|
|
||||||
|
control ingress(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 egress(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 computeChecksum(
|
||||||
|
inout headers hdr,
|
||||||
|
inout metadata meta)
|
||||||
|
{
|
||||||
|
Checksum16() ipv4_checksum;
|
||||||
|
|
||||||
|
apply {
|
||||||
|
if (hdr.ipv4.isValid()) {
|
||||||
|
hdr.ipv4.hdrChecksum = ipv4_checksum.get(
|
||||||
|
{
|
||||||
|
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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
*********************** D E P A R S E R *******************************
|
||||||
|
*************************************************************************/
|
||||||
|
|
||||||
|
control DeparserImpl(packet_out packet, in headers hdr) {
|
||||||
|
apply {
|
||||||
|
packet.emit(hdr.ethernet);
|
||||||
|
packet.emit(hdr.ipv4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
*********************** S W I T C H *******************************
|
||||||
|
*************************************************************************/
|
||||||
|
|
||||||
|
V1Switch(
|
||||||
|
ParserImpl(),
|
||||||
|
verifyChecksum(),
|
||||||
|
ingress(),
|
||||||
|
egress(),
|
||||||
|
computeChecksum(),
|
||||||
|
DeparserImpl()
|
||||||
|
) main;
|
206
P4D2_2017/exercises/mri/README.md
Normal file
206
P4D2_2017/exercises/mri/README.md
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
# 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 that every packet travels through
|
||||||
|
the network. To support this functionality, you will need to write a
|
||||||
|
P4 program that appends an ID to the header stack of every packet. At
|
||||||
|
the destination, the sequence of switch IDs correspond to the path.
|
||||||
|
|
||||||
|
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 append 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
|
||||||
|
./run.sh
|
||||||
|
```
|
||||||
|
This will:
|
||||||
|
* compile `mri.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.10`, `10.0.2.10`, etc.
|
||||||
|
|
||||||
|
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.10 "P4 is cool"
|
||||||
|
```
|
||||||
|
The message "P4 is cool" should be received in `h2`'s xterm,
|
||||||
|
5. Type `exit` to leave each xterm and the Mininet command line.
|
||||||
|
|
||||||
|
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 `run.sh` 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 a single
|
||||||
|
field with the switch ID.
|
||||||
|
|
||||||
|
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_swid` state,
|
||||||
|
this field should be decremented. The `parse_swid` 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, `swid` to store the
|
||||||
|
switch ID, and actions that add the `mri_t` header if it doesn't
|
||||||
|
exist, 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 action (called `add_mri_option`) that will add the IP Options and MRI
|
||||||
|
header. Note that you can use the `setValid()` function, which adds a
|
||||||
|
header if it does not exist, but otherwise leaves the packet
|
||||||
|
unmodified.
|
||||||
|
6. An action (called `add_swid`) that will add the switch ID header.
|
||||||
|
7. A table (`swid`) to store the switch ID, and calls `add_swid`.
|
||||||
|
8. A control that:
|
||||||
|
1. Defines a table that will read an IPv4 destination address, and
|
||||||
|
invoke either `drop` or `ipv4_forward`.
|
||||||
|
1. An `apply` block that applies the table.
|
||||||
|
9. A deparser that selects the order in which fields inserted into the outgoing
|
||||||
|
packet.
|
||||||
|
10. A `package` instantiation supplied with the parser, control, and deparser.
|
||||||
|
|
||||||
|
> In general, a package also requires instances of checksum verification
|
||||||
|
> and recomputation controls. These are not necessary for this tutorial
|
||||||
|
> and are replaced with instantiations of empty controls.
|
||||||
|
|
||||||
|
|
||||||
|
## Step 3: Run your solution
|
||||||
|
|
||||||
|
Follow the instructions from Step 1. This time, when your message from `h1` is
|
||||||
|
delivered to `h2`, you should see the seqeunce of switches
|
||||||
|
through which the packet traveled. 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.
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
got a packet
|
||||||
|
###[ Ethernet ]###
|
||||||
|
dst = 00:aa:00:02:00:02
|
||||||
|
src = f2:ed:e6:df:4e:fa
|
||||||
|
type = 0x800
|
||||||
|
###[ IP ]###
|
||||||
|
version = 4L
|
||||||
|
ihl = 8L
|
||||||
|
tos = 0x0
|
||||||
|
len = 33
|
||||||
|
id = 1
|
||||||
|
flags =
|
||||||
|
frag = 0L
|
||||||
|
ttl = 62
|
||||||
|
proto = udp
|
||||||
|
chksum = 0x63b8
|
||||||
|
src = 10.0.1.10
|
||||||
|
dst = 10.0.2.10
|
||||||
|
\options \
|
||||||
|
|###[ MRI ]###
|
||||||
|
| copy_flag = 1L
|
||||||
|
| optclass = debug
|
||||||
|
| option = 31L
|
||||||
|
| length = 12
|
||||||
|
| count = 2
|
||||||
|
| swids = [2, 1]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Troubleshooting
|
||||||
|
|
||||||
|
There are several ways that problems might manifest:
|
||||||
|
|
||||||
|
1. `mri.p4` fails to compile. In this case, `run.sh` will report the
|
||||||
|
error emitted from the compiler and stop.
|
||||||
|
|
||||||
|
1. `mri.p4` compiles but does not support the control plane rules in
|
||||||
|
the `sX-commands.txt` files that `run.sh` tries to install using the BMv2 CLI.
|
||||||
|
In this case, `run.sh` will report these errors to `stderr`. Use these error
|
||||||
|
messages to fix your `ipv4_forward.p4` implementation.
|
||||||
|
|
||||||
|
1. `mri.p4` compiles, and the control plane rules are installed, but
|
||||||
|
the switch does not process packets in the desired way. The
|
||||||
|
`build/logs/<switch-name>.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, `run.sh` 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 the next exercise:
|
||||||
|
implementing an [ARP and ICMP Responder](../arp).
|
||||||
|
|
||||||
|
|
||||||
|
|
286
P4D2_2017/exercises/mri/mri.p4
Normal file
286
P4D2_2017/exercises/mri/mri.p4
Normal file
@ -0,0 +1,286 @@
|
|||||||
|
/* -*- P4_16 -*- */
|
||||||
|
#include <core.p4>
|
||||||
|
#include <v1model.p4>
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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] swids;
|
||||||
|
}
|
||||||
|
|
||||||
|
error { IPHeaderTooShort }
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
*********************** P A R S E R ***********************************
|
||||||
|
*************************************************************************/
|
||||||
|
|
||||||
|
parser ParserImpl(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);
|
||||||
|
/*
|
||||||
|
* TODO: Modify the next line to select on the value of hdr.ipv4.ihl.
|
||||||
|
* If the value of hdr.ipv4.ihl is set to 5, accept.
|
||||||
|
* Otherwise, transition to parse_ipv4_option.
|
||||||
|
*/
|
||||||
|
transition accept;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO: Implement the logic for parse_ipv4_options, parse_mri, and parse_swid */
|
||||||
|
|
||||||
|
|
||||||
|
state parse_ipv4_option {
|
||||||
|
/*
|
||||||
|
* TODO: Add logic to:
|
||||||
|
* - Extract the ipv4_option header.
|
||||||
|
* - If the value is equal to IPV4_OPTION_MRI, transition to parse_mri.
|
||||||
|
* - Otherwise, 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_swid.
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
state parse_swid {
|
||||||
|
/*
|
||||||
|
* TODO: Add logic to:
|
||||||
|
* - Extract hdr.swids.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_swid.
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
************ C H E C K S U M V E R I F I C A T I O N *************
|
||||||
|
*************************************************************************/
|
||||||
|
|
||||||
|
control verifyChecksum(in headers hdr, inout metadata meta) {
|
||||||
|
apply { }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
************** I N G R E S S P R O C E S S I N G *******************
|
||||||
|
*************************************************************************/
|
||||||
|
|
||||||
|
control ingress(inout headers hdr, inout metadata meta, inout standard_metadata_t standard_metadata) {
|
||||||
|
action drop() {
|
||||||
|
mark_to_drop();
|
||||||
|
}
|
||||||
|
|
||||||
|
action add_mri_option() {
|
||||||
|
/*
|
||||||
|
* TODO: add logic to:
|
||||||
|
* - Call setValid() on hdr.ipv4_option, which will add the header if it is not
|
||||||
|
* there, or leave the packet unchanged.
|
||||||
|
* - Set hdr.ipv4_option.copyFlag to 1
|
||||||
|
* - Set hdr.ipv4_option.optClass to 2
|
||||||
|
* - Set hdr.ipv4_option.option to IPV4_OPTION_MRI
|
||||||
|
* - Set the hdr.ipv4_option.optionLength to 4
|
||||||
|
* - Call setValid() on hdr.mri
|
||||||
|
* - Set hdr.mri.count to 0
|
||||||
|
* - Increment hdr.ipv4.ihl by 1
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
action add_swid(switchID_t id) {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO: add logic to:
|
||||||
|
- Increment hdr.mri.count by 1
|
||||||
|
- Add a new swid header by calling push_front(1) on hdr.swids.
|
||||||
|
- Set hdr.swids[0].swid to the id paremeter
|
||||||
|
- Incremement hdr.ipv4.ihl by 1
|
||||||
|
- Incrememtn hdr.ipv4_option.optionLength by 4
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
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 swid {
|
||||||
|
actions =
|
||||||
|
{
|
||||||
|
/* TODO: repace NoAction with the correct action */
|
||||||
|
NoAction;
|
||||||
|
}
|
||||||
|
/* TODO: set a default action. */
|
||||||
|
}
|
||||||
|
|
||||||
|
table ipv4_lpm {
|
||||||
|
key = {
|
||||||
|
hdr.ipv4.dstAddr: lpm;
|
||||||
|
}
|
||||||
|
actions = {
|
||||||
|
ipv4_forward;
|
||||||
|
drop;
|
||||||
|
NoAction;
|
||||||
|
}
|
||||||
|
size = 1024;
|
||||||
|
default_action = NoAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
apply {
|
||||||
|
|
||||||
|
ipv4_lpm.apply();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO: add logic to:
|
||||||
|
* - If hdr.ipv4 is valid:
|
||||||
|
* - Apply table ipv4_lpm
|
||||||
|
* - If hdr.mri is not valid, call add_mri_option()
|
||||||
|
* - Apply table swid
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
**************** E G R E S S P R O C E S S I N G *******************
|
||||||
|
*************************************************************************/
|
||||||
|
|
||||||
|
control egress(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 computeChecksum(
|
||||||
|
inout headers hdr,
|
||||||
|
inout metadata meta)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Ignore checksum for now. The reference solution contains a checksum
|
||||||
|
* implementation.
|
||||||
|
*/
|
||||||
|
apply { }
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
*********************** D E P A R S E R *******************************
|
||||||
|
*************************************************************************/
|
||||||
|
|
||||||
|
control DeparserImpl(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.swids);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
*********************** S W I T C H *******************************
|
||||||
|
*************************************************************************/
|
||||||
|
|
||||||
|
V1Switch(
|
||||||
|
ParserImpl(),
|
||||||
|
verifyChecksum(),
|
||||||
|
ingress(),
|
||||||
|
egress(),
|
||||||
|
computeChecksum(),
|
||||||
|
DeparserImpl()
|
||||||
|
) main;
|
33
P4D2_2017/exercises/mri/p4app.json
Normal file
33
P4D2_2017/exercises/mri/p4app.json
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"program": "mri.p4",
|
||||||
|
"language": "p4-16",
|
||||||
|
"targets": {
|
||||||
|
"multiswitch": {
|
||||||
|
"auto-control-plane": true,
|
||||||
|
"cli": true,
|
||||||
|
"pcap_dump": true,
|
||||||
|
"bmv2_log": true,
|
||||||
|
"links": [["h1", "s1"], ["s1", "s2"], ["s1", "s3"], ["s3", "s2"], ["s2", "h2"], ["s3", "h3"]],
|
||||||
|
"hosts": {
|
||||||
|
"h1": {
|
||||||
|
},
|
||||||
|
"h2": {
|
||||||
|
},
|
||||||
|
"h3": {
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
"switches": {
|
||||||
|
"s1": {
|
||||||
|
"entries": "s1-commands.txt"
|
||||||
|
},
|
||||||
|
"s2": {
|
||||||
|
"entries": "s2-commands.txt"
|
||||||
|
},
|
||||||
|
"s3": {
|
||||||
|
"entries": "s3-commands.txt"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
50
P4D2_2017/exercises/mri/receive.py
Executable file
50
P4D2_2017/exercises/mri/receive.py
Executable file
@ -0,0 +1,50 @@
|
|||||||
|
#!/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 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():
|
||||||
|
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()
|
5
P4D2_2017/exercises/mri/run.sh
Executable file
5
P4D2_2017/exercises/mri/run.sh
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
P4APPRUNNER=../../utils/p4apprunner.py
|
||||||
|
mkdir -p build
|
||||||
|
tar -czf build/p4app.tgz * --exclude='build'
|
||||||
|
#cd build
|
||||||
|
sudo python $P4APPRUNNER p4app.tgz --build-dir ./build
|
5
P4D2_2017/exercises/mri/s1-commands.txt
Normal file
5
P4D2_2017/exercises/mri/s1-commands.txt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
table_set_default ipv4_lpm drop
|
||||||
|
table_set_default swid add_swid 1
|
||||||
|
table_add ipv4_lpm ipv4_forward 10.0.1.10/32 => 00:aa:00:01:00:01 1
|
||||||
|
table_add ipv4_lpm ipv4_forward 10.0.2.10/32 => f2:ed:e6:df:4e:fa 2
|
||||||
|
table_add ipv4_lpm ipv4_forward 10.0.3.10/32 => f2:ed:e6:df:4e:fb 3
|
5
P4D2_2017/exercises/mri/s2-commands.txt
Normal file
5
P4D2_2017/exercises/mri/s2-commands.txt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
table_set_default ipv4_lpm drop
|
||||||
|
table_set_default swid add_swid 2
|
||||||
|
table_add ipv4_lpm ipv4_forward 10.0.2.10/32 => 00:aa:00:02:00:02 1
|
||||||
|
table_add ipv4_lpm ipv4_forward 10.0.1.10/32 => 22:a8:04:41:ab:d3 2
|
||||||
|
table_add ipv4_lpm ipv4_forward 10.0.3.10/32 => 22:a8:04:41:ab:d4 3
|
5
P4D2_2017/exercises/mri/s3-commands.txt
Normal file
5
P4D2_2017/exercises/mri/s3-commands.txt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
table_set_default ipv4_lpm drop
|
||||||
|
table_set_default swid add_swid 3
|
||||||
|
table_add ipv4_lpm ipv4_forward 10.0.3.10/32 => 00:aa:00:03:00:01 1
|
||||||
|
table_add ipv4_lpm ipv4_forward 10.0.1.10/32 => f2:ed:e6:df:4e:fb 2
|
||||||
|
table_add ipv4_lpm ipv4_forward 10.0.2.10/32 => f2:ed:e6:df:4e:fa 3
|
58
P4D2_2017/exercises/mri/send.py
Executable file
58
P4D2_2017/exercises/mri/send.py
Executable file
@ -0,0 +1,58 @@
|
|||||||
|
#!/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
|
||||||
|
|
||||||
|
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 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 main():
|
||||||
|
|
||||||
|
if len(sys.argv)<3:
|
||||||
|
print 'pass 2 arguments: <destination> "<message>"'
|
||||||
|
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) / 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, swids=[3,4])) / UDP(dport=4321, sport=1234) / sys.argv[2]
|
||||||
|
pkt.show2()
|
||||||
|
#hexdump(pkt)
|
||||||
|
sendp(pkt, iface=iface)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
278
P4D2_2017/exercises/mri/solution/mri.p4
Normal file
278
P4D2_2017/exercises/mri/solution/mri.p4
Normal file
@ -0,0 +1,278 @@
|
|||||||
|
/* -*- P4_16 -*- */
|
||||||
|
#include <core.p4>
|
||||||
|
#include <v1model.p4>
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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] swids;
|
||||||
|
}
|
||||||
|
|
||||||
|
error { IPHeaderTooShort }
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
*********************** P A R S E R ***********************************
|
||||||
|
*************************************************************************/
|
||||||
|
|
||||||
|
parser ParserImpl(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_swid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state parse_swid {
|
||||||
|
packet.extract(hdr.swids.next);
|
||||||
|
meta.parser_metadata.remaining = meta.parser_metadata.remaining - 1;
|
||||||
|
transition select(meta.parser_metadata.remaining) {
|
||||||
|
0 : accept;
|
||||||
|
default: parse_swid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
************ C H E C K S U M V E R I F I C A T I O N *************
|
||||||
|
*************************************************************************/
|
||||||
|
|
||||||
|
control verifyChecksum(in headers hdr, inout metadata meta) {
|
||||||
|
apply { }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
************** I N G R E S S P R O C E S S I N G *******************
|
||||||
|
*************************************************************************/
|
||||||
|
|
||||||
|
control ingress(inout headers hdr, inout metadata meta, inout standard_metadata_t standard_metadata) {
|
||||||
|
action drop() {
|
||||||
|
mark_to_drop();
|
||||||
|
}
|
||||||
|
|
||||||
|
action add_mri_option() {
|
||||||
|
hdr.ipv4_option.setValid();
|
||||||
|
hdr.ipv4_option.copyFlag = 1;
|
||||||
|
hdr.ipv4_option.optClass = 2; /* Debugging and Measurement */
|
||||||
|
hdr.ipv4_option.option = IPV4_OPTION_MRI;
|
||||||
|
hdr.ipv4_option.optionLength = 4; /* sizeof(ipv4_option) + sizeof(mri) */
|
||||||
|
|
||||||
|
hdr.mri.setValid();
|
||||||
|
hdr.mri.count = 0;
|
||||||
|
hdr.ipv4.ihl = hdr.ipv4.ihl + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
action add_swid(switchID_t id) {
|
||||||
|
hdr.mri.count = hdr.mri.count + 1;
|
||||||
|
hdr.swids.push_front(1);
|
||||||
|
hdr.swids[0].swid = id;
|
||||||
|
|
||||||
|
hdr.ipv4.ihl = hdr.ipv4.ihl + 1;
|
||||||
|
hdr.ipv4_option.optionLength = hdr.ipv4_option.optionLength + 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 swid {
|
||||||
|
actions = { add_swid; NoAction; }
|
||||||
|
default_action = NoAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
if (!hdr.mri.isValid()) {
|
||||||
|
add_mri_option();
|
||||||
|
}
|
||||||
|
|
||||||
|
swid.apply();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
**************** E G R E S S P R O C E S S I N G *******************
|
||||||
|
*************************************************************************/
|
||||||
|
|
||||||
|
control egress(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 computeChecksum(
|
||||||
|
inout headers hdr,
|
||||||
|
inout metadata meta)
|
||||||
|
{
|
||||||
|
Checksum16() ipv4_checksum;
|
||||||
|
|
||||||
|
apply {
|
||||||
|
if (hdr.ipv4.isValid()) {
|
||||||
|
hdr.ipv4.hdrChecksum = ipv4_checksum.get(
|
||||||
|
{
|
||||||
|
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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
*********************** D E P A R S E R *******************************
|
||||||
|
*************************************************************************/
|
||||||
|
|
||||||
|
control DeparserImpl(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.swids);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
*********************** S W I T C H *******************************
|
||||||
|
*************************************************************************/
|
||||||
|
|
||||||
|
V1Switch(
|
||||||
|
ParserImpl(),
|
||||||
|
verifyChecksum(),
|
||||||
|
ingress(),
|
||||||
|
egress(),
|
||||||
|
computeChecksum(),
|
||||||
|
DeparserImpl()
|
||||||
|
) main;
|
103
P4D2_2017/utils/mininet/appcontroller.py
Normal file
103
P4D2_2017/utils/mininet/appcontroller.py
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
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'], 16)
|
||||||
|
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)
|
||||||
|
self.add_entries(sw=sw, entries=entries[sw_name])
|
||||||
|
print "Configuration complete."
|
||||||
|
print "**********"
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
pass
|
69
P4D2_2017/utils/mininet/apptopo.py
Normal file
69
P4D2_2017/utils/mininet/apptopo.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
from mininet.topo import Topo
|
||||||
|
|
||||||
|
class AppTopo(Topo):
|
||||||
|
|
||||||
|
def __init__(self, links, latencies={}, manifest=None, target=None,
|
||||||
|
log_dir="/tmp", **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:])
|
||||||
|
|
||||||
|
host_ip = "10.0.%d.10" % host_num
|
||||||
|
host_mac = '00:04:00:00:00:%02x' % host_num
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
delay_key = ''.join([host_name, sw])
|
||||||
|
delay = latencies[delay_key] if delay_key in latencies else '0ms'
|
||||||
|
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:aa:00:%02x:00:%02x" % (sw_num, host_num),
|
||||||
|
sw_ip = "10.0.%d.%d" % (host_num, sw_idx+1),
|
||||||
|
sw_port = sw_ports[sw].index(host_name)+1
|
||||||
|
)
|
||||||
|
self.addLink(host_name, sw, delay=delay,
|
||||||
|
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([host_name, sw]))
|
||||||
|
delay = latencies[delay_key] if delay_key in latencies else '0ms'
|
||||||
|
self.addLink(sw1, sw2, delay=delay)
|
||||||
|
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:aa:00:%02x:%02x:00" % (sw1_num, sw2_num), port=sw_ports[sw1].index(sw2)+1)
|
||||||
|
sw2_port = dict(mac="00:aa: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]
|
||||||
|
|
242
P4D2_2017/utils/mininet/multi_switch_mininet.py
Executable file
242
P4D2_2017/utils/mininet/multi_switch_mininet.py
Executable file
@ -0,0 +1,242 @@
|
|||||||
|
#!/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])
|
||||||
|
|
||||||
|
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)
|
||||||
|
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()
|
161
P4D2_2017/utils/mininet/p4_mininet.py
Normal file
161
P4D2_2017/utils/mininet/p4_mininet.py
Normal file
@ -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)
|
78
P4D2_2017/utils/mininet/shortest_path.py
Normal file
78
P4D2_2017/utils/mininet/shortest_path.py
Normal file
@ -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
|
||||||
|
|
133
P4D2_2017/utils/mininet/single_switch_mininet.py
Executable file
133
P4D2_2017/utils/mininet/single_switch_mininet.py
Executable file
@ -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()
|
320
P4D2_2017/utils/p4apprunner.py
Executable file
320
P4D2_2017/utils/p4apprunner.py
Executable file
@ -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 <switch 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/<switchname>.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()
|
Loading…
x
Reference in New Issue
Block a user