P4 Developer Day 2018 Spring (#159)

* Repository reorganization for 2018 Spring P4 Developer Day.

* Port tutorial exercises to P4Runtime with static controller (#156)

* Switch VM to a minimal Ubuntu 16.04 desktop image

* Add commands to install Protobuf Python bindings to user_bootstrap.sh

* Implement P4Runtime static controller for use in exercises

From the exercise perspective, the main difference is that control plane
rules are now specified using JSON files instead of CLI commands. Such
JSON files define rules that use the same name for tables, keys, etc. as
in the P4Info file.

All P4Runtime requests generated as part of the make run process are
logged in the exercise's “logs” directory, making it easier for students
to see the actual P4Runtime messages sent to the switch.

Only the "basic" exercise has been ported to use P4Runtime.
The "p4runtime" exercise has been updated to work with P4Runtime
protocol changes.

Known issues:
- make run hangs in case of errors when running the P4Runtime controller
    (probably due to gRPC stream channel threads not terminated properly)
- missing support for inserting table entries with default action
    (can specify in P4 program as a workaround)

* Force install protobuf python module

* Fixing Ctrl-C hang by shutdown switches

* Moving gRPC error print to function for readability

Unforuntately, if this gets moved out of the file, the process hangs.
We'll need to figure out how why later.

* Renaming ShutdownAllSwitches -> ShutdownAllSwitchConnections

* Reverting counter index change

* Porting the ECN exercise to use P4 Runtime Static Controller

* updating the README in the ecn exercise to reflect the change in rule files

* Allow set table default action in P4Runtime static controller

* Fixed undefined match string when printing P4Runtime table entry

* Updated basic_tunnel exercise to use P4Runtime controller.

* Changed default action in the basic exercise's ipv4_lpm table to drop

* Porting the MRI exercise to use P4runtime with static controller

* Updating readme to reflect the change of controller for mri

* Update calc exercise for P4Runtime static controller

* Port source_routing to P4 Runtime static controller (#157)

* Port Load Balance to P4 Runtime Static Controller (#158)
This commit is contained in:
Nate Foster 2018-06-01 02:54:33 -04:00 committed by GitHub
parent e7e6899d5c
commit dc08948a34
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
503 changed files with 1432 additions and 30666 deletions

1
.gitignore vendored
View File

@ -9,6 +9,7 @@
*.json
!*p4app.json
!topology.json
!*-runtime.json
*.pcap

View File

@ -1,49 +0,0 @@
# P4 Tutorial
## Introduction
Welcome to the P4 Tutorial!
We've prepared a set of exercises to help you get started with P4
programming, organized into four modules:
1. Introduction and Language Basics
* [Basic Forwarding](./basic)
* [Basic Tunneling](./basic_tunnel)
2. P4 Runtime and the Control Plane
* [P4 Runtime](./p4runtime)
3. Monitoring and Debugging
* [Explicit Congestion Notification](./ecn)
* [Multi-Hop Route Inspection](./mri)
4. Advanced Data Structures
* [Source Routing](./source_routing)
* [Calculator](./calc)
5. Dynamic Behavior
* [Load Balancing](./load_balance)
## Obtaining required software
If you are starting this tutorial at the Fall 2017 P4 Developer Day, then we've already
provided you with a virtual machine that has all of the required
software installed.
Otherwise, to complete the exercises, you will need to either build a
virtual machine or install several dependencies.
To build the virtual machine:
- Install [Vagrant](https://vagrantup.com) and [VirtualBox](https://virtualbox.org)
- `cd vm`
- `vagrant up`
- Log in with username `p4` and password `p4` and issue the command `sudo shutdown -r now`
- When the machine reboots, you should have a graphical desktop machine with the required
software pre-installed.
To install dependencies by hand, please reference the [vm](../vm) installation scripts.
They contain the dependencies, versions, and installation procedure.
You can run them directly on an Ubuntu 16.04 machine:
- `sudo ./root-bootstrap.sh`
- `sudo ./user-bootstrap.sh`

View File

@ -1 +0,0 @@
include ../../utils/Makefile

View File

@ -1,173 +0,0 @@
# Implementing Basic Forwarding
## Introduction
The objective of this exercise is to write a P4 program that
implements basic forwarding. To keep things simple, we will just
implement forwarding for IPv4.
With IPv4 forwarding, the switch must perform the following actions
for every packet: (i) update the source and destination MAC addresses,
(ii) decrement the time-to-live (TTL) in the IP header, and (iii)
forward the packet out the appropriate port.
Your switch will have a single table, which the control plane will
populate with static rules. Each rule will map an IP address to the
MAC address and output port for the next hop. We have already defined
the control plane rules, so you only need to implement the data plane
logic of your P4 program.
> **Spoiler alert:** There is a reference solution in the `solution`
> sub-directory. Feel free to compare your implementation to the
> reference.
## Step 1: Run the (incomplete) starter code
The directory with this README also contains a skeleton P4 program,
`basic.p4`, which initially drops all packets. Your job will be to
extend this skeleton program to properly forward IPv4 packets.
Before that, let's compile the incomplete `basic.p4` and bring
up a switch in Mininet to test its behavior.
1. In your shell, run:
```bash
make run
```
This will:
* compile `basic.p4`, and
* start a Mininet instance with three switches (`s1`, `s2`, `s3`)
configured in a triangle, each connected to one host (`h1`, `h2`,
and `h3`).
* The hosts are assigned IPs of `10.0.1.1`, `10.0.2.2`, and `10.0.3.3`.
2. You should now see a Mininet command prompt. Open two terminals
for `h1` and `h2`, respectively:
```bash
mininet> xterm h1 h2
```
3. Each host includes a small Python-based messaging client and
server. In `h2`'s xterm, start the server:
```bash
./receive.py
```
4. In `h1`'s xterm, send a message to `h2`:
```bash
./send.py 10.0.2.2 "P4 is cool"
```
The message will not be received.
5. Type `exit` to leave each xterm and the Mininet command line.
Then, to stop mininet:
```bash
make stop
```
And to delete all pcaps, build files, and logs:
```bash
make clean
```
The message was not received because each switch is programmed
according to `basic.p4`, which drops all packets on arrival.
Your job is to extend this file so it forwards packets.
### A note about the control plane
A P4 program defines a packet-processing pipeline, but the rules
within each table are inserted by the control plane. When a rule
matches a packet, its action is invoked with parameters supplied by
the control plane as part of the rule.
In this exercise, we have already implemented the the control plane
logic for you. As part of bringing up the Mininet instance, the
`make run` command will install packet-processing rules in the tables of
each switch. These are defined in the `sX-commands.txt` files, where
`X` corresponds to the switch number.
**Important:** A P4 program also defines the interface between the
switch pipeline and control plane. The commands in the files
`sX-commands.txt` refer to specific tables, keys, and actions by name,
and any changes in the P4 program that add or rename tables, keys, or
actions will need to be reflected in these command files.
## Step 2: Implement L3 forwarding
The `basic.p4` file contains a skeleton P4 program with key pieces of
logic replaced by `TODO` comments. Your implementation should follow
the structure given in this file---replace each `TODO` with logic
implementing the missing piece.
A complete `basic.p4` will contain the following components:
1. Header type definitions for Ethernet (`ethernet_t`) and IPv4 (`ipv4_t`).
2. **TODO:** Parsers for Ethernet and IPv4 that populate `ethernet_t` and `ipv4_t` fields.
3. An action to drop a packet, using `mark_to_drop()`.
4. **TODO:** An action (called `ipv4_forward`) that:
1. Sets the egress port for the next hop.
2. Updates the ethernet destination address with the address of the next hop.
3. Updates the ethernet source address with the address of the switch.
4. Decrements the TTL.
5. **TODO:** A control that:
1. Defines a table that will read an IPv4 destination address, and
invoke either `drop` or `ipv4_forward`.
2. An `apply` block that applies the table.
6. **TODO:** A deparser that selects the order
in which fields inserted into the outgoing packet.
7. A `package` instantiation supplied with the parser, control, and deparser.
> In general, a package also requires instances of checksum verification
> and recomputation controls. These are not necessary for this tutorial
> and are replaced with instantiations of empty controls.
## Step 3: Run your solution
Follow the instructions from Step 1. This time, your message from
`h1` should be delivered to `h2`.
### Food for thought
The "test suite" for your solution---sending a message from `h1` to
`h2`---is not very robust. What else should you test to be confident
of your implementation?
> Although the Python `scapy` library is outside the scope of this tutorial,
> it can be used to generate packets for testing. The `send.py` file shows how
> to use it.
Other questions to consider:
- How would you enhance your program to support next hops?
- Is this program enough to replace a router? What's missing?
### Troubleshooting
There are several problems that might manifest as you develop your program:
1. `basic.p4` might fail to compile. In this case, `make run` will
report the error emitted from the compiler and halt.
2. `basic.p4` might compile but fail to support the control plane
rules in the `s1-commands.txt` through `s3-command.txt` files that
`make run` tries to install using the Bmv2 CLI. In this case, `make run`
will log the CLI tool output in the `logs` directory. Use these error
messages to fix your `basic.p4` implementation.
3. `basic.p4` might compile, and the control plane rules might be
installed, but the switch might not process packets in the desired
way. The `/tmp/p4s.<switch-name>.log` files contain detailed logs
that describing how each switch processes each packet. The output is
detailed and can help pinpoint logic errors in your implementation.
#### Cleaning up Mininet
In the latter two cases above, `make run` may leave a Mininet instance
running in the background. Use the following command to clean up
these instances:
```bash
make stop
```
## Next Steps
Congratulations, your implementation works! In the next exercise we
will build on top of this and add support for a basic tunneling
protocol: [basic_tunnel](../basic_tunnel)!

View File

@ -1,4 +0,0 @@
table_set_default ipv4_lpm drop
table_add ipv4_lpm ipv4_forward 10.0.1.1/32 => 00:00:00:00:01:01 1
table_add ipv4_lpm ipv4_forward 10.0.2.2/32 => 00:00:00:02:02:00 2
table_add ipv4_lpm ipv4_forward 10.0.3.3/32 => 00:00:00:03:03:00 3

View File

@ -1,4 +0,0 @@
table_set_default ipv4_lpm drop
table_add ipv4_lpm ipv4_forward 10.0.1.1/32 => 00:00:00:01:02:00 2
table_add ipv4_lpm ipv4_forward 10.0.2.2/32 => 00:00:00:00:02:02 1
table_add ipv4_lpm ipv4_forward 10.0.3.3/32 => 00:00:00:03:03:00 3

View File

@ -1,4 +0,0 @@
table_set_default ipv4_lpm drop
table_add ipv4_lpm ipv4_forward 10.0.1.1/32 => 00:00:00:01:03:00 2
table_add ipv4_lpm ipv4_forward 10.0.2.2/32 => 00:00:00:02:03:00 3
table_add ipv4_lpm ipv4_forward 10.0.3.3/32 => 00:00:00:00:03:03 1

View File

@ -1 +0,0 @@
include ../../utils/Makefile

View File

@ -1,166 +0,0 @@
# Implementing Basic Tunneling
## Introduction
In this exercise, we will add support for a basic tunneling protocol
to the IP router that you completed in the previous assignment. To do so,
we will define a new header type to encapsulate the IP packet and
modify the switch to perform routing using our new header.
The new header type will contain a protocol ID, which indicates the type
of packet being encapsulated, along with a destination ID to be used for
routing.
> **Spoiler alert:** There is a reference solution in the `solution`
> sub-directory. Feel free to compare your implementation to the
> reference.
## Step 1: Run the (incomplete) starter code
The starter code for this assignment is in a file called `basic_tunnel.p4`
and is simply the solution to the IP router from the previous exercise.
Let's first compile this code to and send a packet between two end hosts
to ensure that the IP routing is working as expected.
1. In your shell, run:
```bash
make run
```
This will:
* compile `basic_tunnel.p4`, and
* start a Mininet instance with three switches (`s1`, `s2`, `s3`)
configured in a triangle, each connected to one host (`h1`, `h2`,
and `h3`).
* The hosts are assigned IPs of `10.0.1.1`, `10.0.2.2`, and `10.0.3.3`.
2. You should now see a Mininet command prompt. Open two terminals
for `h1` and `h2`, respectively:
```bash
mininet> xterm h1 h2
```
3. Each host includes a small Python-based messaging client and
server. In `h2`'s xterm, start the server:
```bash
./receive.py
```
4. In `h1`'s xterm, send a message to `h2`:
```bash
./send.py 10.0.2.2 "P4 is cool"
```
The packet should be received at `h2`. If you examine the received
packet you should see that is consists of an Ethernet header, an IP
header, a TCP header, and the message. If you change the destination IP address
(e.g. try to send to `10.0.3.3`) then the message should not be
received by h2.
5. Type `exit` or `Ctrl-D` to leave each xterm and the Mininet command line.
Each switch is forwarding based on the destination IP address. Your
job is to change the switch functionality so that they instead decide
the destination port using our new tunnel header.
### A note about the control plane
A P4 program defines a packet-processing pipeline, but the rules
within each table are inserted by the control plane. When a rule
matches a packet, its action is invoked with parameters supplied by
the control plane as part of the rule.
For this exercise, we have already added the necessary static control
plane entries. As part of bringing up the Mininet instance, the
`make run` command will install packet-processing rules in the tables
of each switch. These are defined in the `sX-commands.txt` files,
where `X` corresponds to the switch number.
**Important:** A P4 program also defines the interface between the
switch pipeline and control plane. The commands in the files
`sX-commands.txt` refer to specific tables, keys, and actions by name,
and any changes in the P4 program that add or rename tables, keys, or
actions will need to be reflected in these command files.
## Step 2: Implement Basic Tunneling
The `basic_tunnel.p4` file contains an implementation of a basic IP router.
It also contains comments marked with `TODO` which indicate the functionality
that you need to implement. A complete implementation of the `basic_tunnel.p4`
switch will be able to forward based on the contents of a custom encapsulation
header as well as perform normal IP forwarding if the encapsulation header
does not exist in the packet.
Your job will be to do the following:
1. **NOTE:** A new header type has been added called `myTunnel_t` that contains two 16-bit fields: `proto_id` and `dst_id`.
2. **NOTE:** The `myTunnel_t` header has been added to the `headers` struct.
2. **TODO:** Update the parser to extract either the `myTunnel` header or `ipv4` header based on the `etherType` field in the Ethernet header. The etherType corresponding to the myTunnel header is `0x1212`. The parser should also extract the `ipv4` header after the `myTunnel` header if `proto_id` == `TYPE_IPV4` (i.e. 0x0800).
3. **TODO:** Define a new action called `myTunnel_forward` that simply sets the egress port (i.e. `egress_spec` field of the `standard_metadata` bus) to the port number provided by the control plane.
4. **TODO:** Define a new table called `myTunnel_exact` that perfoms an exact match on the `dst_id` field of the `myTunnel` header. This table should invoke either the `myTunnel_forward` action if the there is a match in the table and it should invoke the `drop` action otherwise.
5. **TODO:** Update the `apply` statement in the `MyIngress` control block to apply your newly defined `myTunnel_exact` table if the `myTunnel` header is valid. Otherwise, invoke the `ipv4_lpm` table if the `ipv4` header is valid.
6. **TODO:** Update the deparser to emit the `ethernet`, then `myTunnel`, then `ipv4` headers. Remember that the deparser will only emit a header if it is valid. A header's implicit validity bit is set by the parser upon extraction. So there is no need to check header validity here.
7. **TODO:** Add static rules for your newly defined table so that the switches will forward correctly for each possible value of `dst_id`. See the diagram below for the topology's port configuration as well as how we will assign IDs to hosts. For this step you will need to add your forwarding rules to the `sX-commands.txt` files.
![topology](./topo.png)
## Step 3: Run your solution
Follow the instructions from Step 1. This time when you send a packet from
`h1` to `h2` try using the following command to send a packet that uses
our new `myTunnel` header.
```bash
./send.py 10.0.2.2 "P4 is cool" --dst_id 2
```
You should see a packet arrive at `h2` which contains the `MyTunnel` header.
Also note that changing the destination IP address will not prevent the packet
from arriving at `h2`. This is because the switch is no longer using the IP header for routing when the `MyTunnel` header is in the packet.
> Python Scapy does not natively support the `myTunnel` header
> type so we have provided a file called `myTunnel_header.py` which
> adds support to Scapy for our new custom header. Feel free to inspect
> this file if you are interested in learning how to do this.
### Food for thought
To make this tunneling exercise a bit more interesting (and realistic)
how might you change the P4 code to have the switches add the `myTunnel`
header to an IP packet upon ingress to the network and then remove the
`myTunnel` header as the packet leaves to the network to an end host?
Hints:
- The ingress switch will need to map the destination IP address to the corresponding `dst_id` for the `myTunnel` header. Also remember to set explicitly set the validity bit for the `myTunnel` header so that it can be emitted by the deparser.
- The egress switch will need to remove the `myTunnel` header from the packet after looking up the appropriate output port using the `dst_id` field.
### Troubleshooting
There are several problems that might manifest as you develop your program:
1. `basic_tunnel.p4` might fail to compile. In this case, `make run` will
report the error emitted from the compiler and halt.
2. `basic_tunnel.p4` might compile but fail to support the control plane
rules in the `s1-commands.txt` through `s3-command.txt` files that
`make run` tries to install using the Bmv2 CLI. In this case, `make run`
will log the CLI tool output in the `logs` directory. Use these error
messages to fix your `basic_tunnel.p4` implementation or forwarding rules.
3. `basic_tunnel.p4` might compile, and the control plane rules might be
installed, but the switch might not process packets in the desired
way. The `/tmp/p4s.<switch-name>.log` files contain detailed logs
that describing how each switch processes each packet. The output is
detailed and can help pinpoint logic errors in your implementation.
#### Cleaning up Mininet
In the latter two cases above, `make` may leave a Mininet instance
running in the background. Use the following command to clean up
these instances:
```bash
make stop
```
## Next Steps
Congratulations, your implementation works! Move onto the next assignment
[p4runtime](../p4runtime)!

View File

@ -1,197 +0,0 @@
/* -*- P4_16 -*- */
#include <core.p4>
#include <v1model.p4>
// NOTE: new type added here
const bit<16> TYPE_MYTUNNEL = 0x1212;
const bit<16> TYPE_IPV4 = 0x800;
/*************************************************************************
*********************** H E A D E R S ***********************************
*************************************************************************/
typedef bit<9> egressSpec_t;
typedef bit<48> macAddr_t;
typedef bit<32> ip4Addr_t;
header ethernet_t {
macAddr_t dstAddr;
macAddr_t srcAddr;
bit<16> etherType;
}
// NOTE: added new header type
header myTunnel_t {
bit<16> proto_id;
bit<16> dst_id;
}
header ipv4_t {
bit<4> version;
bit<4> ihl;
bit<8> diffserv;
bit<16> totalLen;
bit<16> identification;
bit<3> flags;
bit<13> fragOffset;
bit<8> ttl;
bit<8> protocol;
bit<16> hdrChecksum;
ip4Addr_t srcAddr;
ip4Addr_t dstAddr;
}
struct metadata {
/* empty */
}
// NOTE: Added new header type to headers struct
struct headers {
ethernet_t ethernet;
myTunnel_t myTunnel;
ipv4_t ipv4;
}
/*************************************************************************
*********************** P A R S E R ***********************************
*************************************************************************/
// TODO: Update the parser to parse the myTunnel header as well
parser MyParser(packet_in packet,
out headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
state start {
transition parse_ethernet;
}
state parse_ethernet {
packet.extract(hdr.ethernet);
transition select(hdr.ethernet.etherType) {
TYPE_IPV4 : parse_ipv4;
default : accept;
}
}
state parse_ipv4 {
packet.extract(hdr.ipv4);
transition accept;
}
}
/*************************************************************************
************ C H E C K S U M V E R I F I C A T I O N *************
*************************************************************************/
control MyVerifyChecksum(inout headers hdr, inout metadata meta) {
apply { }
}
/*************************************************************************
************** I N G R E S S P R O C E S S I N G *******************
*************************************************************************/
control MyIngress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
action drop() {
mark_to_drop();
}
action ipv4_forward(macAddr_t dstAddr, egressSpec_t port) {
standard_metadata.egress_spec = port;
hdr.ethernet.srcAddr = hdr.ethernet.dstAddr;
hdr.ethernet.dstAddr = dstAddr;
hdr.ipv4.ttl = hdr.ipv4.ttl - 1;
}
table ipv4_lpm {
key = {
hdr.ipv4.dstAddr: lpm;
}
actions = {
ipv4_forward;
drop;
NoAction;
}
size = 1024;
default_action = NoAction();
}
// TODO: declare a new action: myTunnel_forward(egressSpec_t port)
// TODO: declare a new table: myTunnel_exact
// TODO: also remember to add table entries!
apply {
// TODO: Update control flow
if (hdr.ipv4.isValid()) {
ipv4_lpm.apply();
}
}
}
/*************************************************************************
**************** E G R E S S P R O C E S S I N G *******************
*************************************************************************/
control MyEgress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
apply { }
}
/*************************************************************************
************* C H E C K S U M C O M P U T A T I O N **************
*************************************************************************/
control MyComputeChecksum(inout headers hdr, inout metadata meta) {
apply {
update_checksum(
hdr.ipv4.isValid(),
{ hdr.ipv4.version,
hdr.ipv4.ihl,
hdr.ipv4.diffserv,
hdr.ipv4.totalLen,
hdr.ipv4.identification,
hdr.ipv4.flags,
hdr.ipv4.fragOffset,
hdr.ipv4.ttl,
hdr.ipv4.protocol,
hdr.ipv4.srcAddr,
hdr.ipv4.dstAddr },
hdr.ipv4.hdrChecksum,
HashAlgorithm.csum16);
}
}
/*************************************************************************
*********************** D E P A R S E R *******************************
*************************************************************************/
control MyDeparser(packet_out packet, in headers hdr) {
apply {
packet.emit(hdr.ethernet);
// TODO: emit myTunnel header as well
packet.emit(hdr.ipv4);
}
}
/*************************************************************************
*********************** S W I T C H *******************************
*************************************************************************/
V1Switch(
MyParser(),
MyVerifyChecksum(),
MyIngress(),
MyEgress(),
MyComputeChecksum(),
MyDeparser()
) main;

View File

@ -1,9 +0,0 @@
table_set_default ipv4_lpm drop
table_add ipv4_lpm ipv4_forward 10.0.1.1/32 => 00:00:00:00:01:01 1
table_add ipv4_lpm ipv4_forward 10.0.2.2/32 => 00:00:00:02:01:00 2
table_add ipv4_lpm ipv4_forward 10.0.3.3/32 => 00:00:00:03:01:00 3
table_set_default myTunnel_exact drop
table_add myTunnel_exact myTunnel_forward 1 => 1
table_add myTunnel_exact myTunnel_forward 2 => 2
table_add myTunnel_exact myTunnel_forward 3 => 3

View File

@ -1,9 +0,0 @@
table_set_default ipv4_lpm drop
table_add ipv4_lpm ipv4_forward 10.0.1.1/32 => 00:00:00:01:02:00 2
table_add ipv4_lpm ipv4_forward 10.0.2.2/32 => 00:00:00:00:02:02 1
table_add ipv4_lpm ipv4_forward 10.0.3.3/32 => 00:00:00:03:02:00 3
table_set_default myTunnel_exact drop
table_add myTunnel_exact myTunnel_forward 1 => 2
table_add myTunnel_exact myTunnel_forward 2 => 1
table_add myTunnel_exact myTunnel_forward 3 => 3

View File

@ -1,9 +0,0 @@
table_set_default ipv4_lpm drop
table_add ipv4_lpm ipv4_forward 10.0.1.1/32 => 00:00:00:01:03:00 2
table_add ipv4_lpm ipv4_forward 10.0.2.2/32 => 00:00:00:02:03:00 3
table_add ipv4_lpm ipv4_forward 10.0.3.3/32 => 00:00:00:00:03:03 1
table_set_default myTunnel_exact drop
table_add myTunnel_exact myTunnel_forward 1 => 2
table_add myTunnel_exact myTunnel_forward 2 => 3
table_add myTunnel_exact myTunnel_forward 3 => 1

View File

@ -1 +0,0 @@
include ../../utils/Makefile

View File

@ -1,12 +0,0 @@
{
"hosts": [
"h1",
"h2"
],
"switches": {
"s1": { "cli_input" : "s1-commands.txt" }
},
"links": [
["h1", "s1"], ["h2", "s1"]
]
}

View File

@ -1 +0,0 @@
include ../../utils/Makefile

View File

@ -1,188 +0,0 @@
/* -*- P4_16 -*- */
#include <core.p4>
#include <v1model.p4>
const bit<8> TCP_PROTOCOL = 0x06;
const bit<16> TYPE_IPV4 = 0x800;
const bit<19> ECN_THRESHOLD = 10;
/*************************************************************************
*********************** H E A D E R S ***********************************
*************************************************************************/
typedef bit<9> egressSpec_t;
typedef bit<48> macAddr_t;
typedef bit<32> ip4Addr_t;
header ethernet_t {
macAddr_t dstAddr;
macAddr_t srcAddr;
bit<16> etherType;
}
/*
* TODO: split tos to two fields 6 bit diffserv and 2 bit ecn
*/
header ipv4_t {
bit<4> version;
bit<4> ihl;
bit<8> tos;
bit<16> totalLen;
bit<16> identification;
bit<3> flags;
bit<13> fragOffset;
bit<8> ttl;
bit<8> protocol;
bit<16> hdrChecksum;
ip4Addr_t srcAddr;
ip4Addr_t dstAddr;
}
struct metadata {
}
struct headers {
ethernet_t ethernet;
ipv4_t ipv4;
}
/*************************************************************************
*********************** P A R S E R ***********************************
*************************************************************************/
parser MyParser(packet_in packet,
out headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
state start {
transition parse_ethernet;
}
state parse_ethernet {
packet.extract(hdr.ethernet);
transition select(hdr.ethernet.etherType) {
TYPE_IPV4: parse_ipv4;
default: accept;
}
}
state parse_ipv4 {
packet.extract(hdr.ipv4);
transition accept;
}
}
/*************************************************************************
************ C H E C K S U M V E R I F I C A T I O N *************
*************************************************************************/
control MyVerifyChecksum(inout headers hdr, inout metadata meta) {
apply { }
}
/*************************************************************************
************** I N G R E S S P R O C E S S I N G *******************
*************************************************************************/
control MyIngress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
action drop() {
mark_to_drop();
}
action ipv4_forward(macAddr_t dstAddr, egressSpec_t port) {
standard_metadata.egress_spec = port;
hdr.ethernet.srcAddr = hdr.ethernet.dstAddr;
hdr.ethernet.dstAddr = dstAddr;
hdr.ipv4.ttl = hdr.ipv4.ttl - 1;
}
table ipv4_lpm {
key = {
hdr.ipv4.dstAddr: lpm;
}
actions = {
ipv4_forward;
drop;
NoAction;
}
size = 1024;
default_action = NoAction();
}
apply {
if (hdr.ipv4.isValid()) {
ipv4_lpm.apply();
}
}
}
/*************************************************************************
**************** E G R E S S P R O C E S S I N G *******************
*************************************************************************/
control MyEgress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
apply {
/*
* TODO:
* - if ecn is 1 or 2
* - compare standard_metadata.enq_qdepth with threshold
* and set hdr.ipv4.ecn to 3 if larger
*/
}
}
/*************************************************************************
************* C H E C K S U M C O M P U T A T I O N **************
*************************************************************************/
control MyComputeChecksum(inout headers hdr, inout metadata meta) {
apply {
/* TODO: replace tos with diffserve and ecn */
update_checksum(
hdr.ipv4.isValid(),
{ hdr.ipv4.version,
hdr.ipv4.ihl,
hdr.ipv4.tos,
hdr.ipv4.totalLen,
hdr.ipv4.identification,
hdr.ipv4.flags,
hdr.ipv4.fragOffset,
hdr.ipv4.ttl,
hdr.ipv4.protocol,
hdr.ipv4.srcAddr,
hdr.ipv4.dstAddr },
hdr.ipv4.hdrChecksum,
HashAlgorithm.csum16);
}
}
/*************************************************************************
*********************** D E P A R S E R *******************************
*************************************************************************/
control MyDeparser(packet_out packet, in headers hdr) {
apply {
packet.emit(hdr.ethernet);
packet.emit(hdr.ipv4);
}
}
/*************************************************************************
*********************** S W I T C H *******************************
*************************************************************************/
V1Switch(
MyParser(),
MyVerifyChecksum(),
MyIngress(),
MyEgress(),
MyComputeChecksum(),
MyDeparser()
) main;

View File

@ -1,5 +0,0 @@
table_set_default ipv4_lpm drop
table_add ipv4_lpm ipv4_forward 10.0.1.1/32 => 00:00:00:00:01:01 2
table_add ipv4_lpm ipv4_forward 10.0.1.11/32 => 00:00:00:00:01:0b 1
table_add ipv4_lpm ipv4_forward 10.0.2.0/24 => 00:00:00:02:03:00 3
table_add ipv4_lpm ipv4_forward 10.0.3.0/24 => 00:00:00:03:02:00 4

View File

@ -1,5 +0,0 @@
table_set_default ipv4_lpm drop
table_add ipv4_lpm ipv4_forward 10.0.2.2/32 => 00:00:00:00:02:02 2
table_add ipv4_lpm ipv4_forward 10.0.2.22/32 => 00:00:00:00:02:16 1
table_add ipv4_lpm ipv4_forward 10.0.1.0/24 => 00:00:00:01:03:00 3
table_add ipv4_lpm ipv4_forward 10.0.3.0/24 => 00:00:00:03:03:00 4

View File

@ -1,4 +0,0 @@
table_set_default ipv4_lpm drop
table_add ipv4_lpm ipv4_forward 10.0.3.3/32 => 00:00:00:00:03:03 1
table_add ipv4_lpm ipv4_forward 10.0.1.10/24 => 00:00:00:01:04:00 2
table_add ipv4_lpm ipv4_forward 10.0.2.10/24 => 00:00:00:02:04:00 3

View File

@ -1,18 +0,0 @@
{
"hosts": [
"h1",
"h2",
"h3",
"h11",
"h22"
],
"switches": {
"s1": { "cli_input" : "s1-commands.txt" },
"s2": { "cli_input" : "s2-commands.txt" },
"s3": { "cli_input" : "s3-commands.txt" }
},
"links": [
["h1", "s1"], ["h11", "s1"], ["s1", "s2", "0", 0.5], ["s1", "s3"],
["s3", "s2"], ["s2", "h2"], ["s2", "h22"], ["s3", "h3"]
]
}

View File

@ -1 +0,0 @@
include ../../utils/Makefile

View File

@ -1,6 +0,0 @@
table_set_default ecmp_group drop
table_add ecmp_group set_ecmp_select 10.0.0.1/32 => 0 2
table_add ecmp_nhop set_nhop 0 => 00:00:00:00:01:02 10.0.2.2 2
table_add ecmp_nhop set_nhop 1 => 00:00:00:00:01:03 10.0.3.3 3
table_add send_frame rewrite_mac 2 => 00:00:00:01:02:00
table_add send_frame rewrite_mac 3 => 00:00:00:01:03:00

View File

@ -1,4 +0,0 @@
table_set_default ecmp_group drop
table_add ecmp_group set_ecmp_select 10.0.2.2/32 => 0 1
table_add ecmp_nhop set_nhop 0 => 00:00:00:00:02:02 10.0.2.2 1
table_add send_frame rewrite_mac 1 => 00:00:00:02:01:00

View File

@ -1,4 +0,0 @@
table_set_default ecmp_group drop
table_add ecmp_group set_ecmp_select 10.0.3.3/32 => 0 1
table_add ecmp_nhop set_nhop 0 => 00:00:00:00:03:03 10.0.3.3 1
table_add send_frame rewrite_mac 1 => 00:00:00:03:01:00

View File

@ -1 +0,0 @@
include ../../utils/Makefile

View File

@ -1,281 +0,0 @@
/* -*- 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;
typedef bit<32> qdepth_t;
header ethernet_t {
macAddr_t dstAddr;
macAddr_t srcAddr;
bit<16> etherType;
}
header ipv4_t {
bit<4> version;
bit<4> ihl;
bit<8> diffserv;
bit<16> totalLen;
bit<16> identification;
bit<3> flags;
bit<13> fragOffset;
bit<8> ttl;
bit<8> protocol;
bit<16> hdrChecksum;
ip4Addr_t srcAddr;
ip4Addr_t dstAddr;
}
header ipv4_option_t {
bit<1> copyFlag;
bit<2> optClass;
bit<5> option;
bit<8> optionLength;
}
header mri_t {
bit<16> count;
}
header switch_t {
switchID_t swid;
qdepth_t qdepth;
}
struct ingress_metadata_t {
bit<16> count;
}
struct parser_metadata_t {
bit<16> remaining;
}
struct metadata {
ingress_metadata_t ingress_metadata;
parser_metadata_t parser_metadata;
}
struct headers {
ethernet_t ethernet;
ipv4_t ipv4;
ipv4_option_t ipv4_option;
mri_t mri;
switch_t[MAX_HOPS] swtraces;
}
error { IPHeaderTooShort }
/*************************************************************************
*********************** P A R S E R ***********************************
*************************************************************************/
parser MyParser(packet_in packet,
out headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
state start {
transition parse_ethernet;
}
state parse_ethernet {
packet.extract(hdr.ethernet);
transition select(hdr.ethernet.etherType) {
TYPE_IPV4: parse_ipv4;
default: accept;
}
}
state parse_ipv4 {
packet.extract(hdr.ipv4);
verify(hdr.ipv4.ihl >= 5, error.IPHeaderTooShort);
transition select(hdr.ipv4.ihl) {
5 : accept;
default : parse_ipv4_option;
}
}
state parse_ipv4_option {
/*
* TODO: Add logic to:
* - Extract the ipv4_option header.
* - If value is equal to IPV4_OPTION_MRI, transition to parse_mri.
* - Otherwise, accept.
*/
transition accept;
}
state parse_mri {
/*
* TODO: Add logic to:
* - Extract hdr.mri.
* - Set meta.parser_metadata.remaining to hdr.mri.count
* - Select on the value of meta.parser_metadata.remaining
* - If the value is equal to 0, accept.
* - Otherwise, transition to parse_swtrace.
*/
transition accept;
}
state parse_swtrace {
/*
* TODO: Add logic to:
* - Extract hdr.swtraces.next.
* - Decrement meta.parser_metadata.remaining by 1
* - Select on the value of meta.parser_metadata.remaining
* - If the value is equal to 0, accept.
* - Otherwise, transition to parse_swtrace.
*/
transition accept;
}
}
/*************************************************************************
************ C H E C K S U M V E R I F I C A T I O N *************
*************************************************************************/
control MyVerifyChecksum(inout headers hdr, inout metadata meta) {
apply { }
}
/*************************************************************************
************** I N G R E S S P R O C E S S I N G *******************
*************************************************************************/
control MyIngress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
action drop() {
mark_to_drop();
}
action ipv4_forward(macAddr_t dstAddr, egressSpec_t port) {
standard_metadata.egress_spec = port;
hdr.ethernet.srcAddr = hdr.ethernet.dstAddr;
hdr.ethernet.dstAddr = dstAddr;
hdr.ipv4.ttl = hdr.ipv4.ttl - 1;
}
table ipv4_lpm {
key = {
hdr.ipv4.dstAddr: lpm;
}
actions = {
ipv4_forward;
drop;
NoAction;
}
size = 1024;
default_action = NoAction();
}
apply {
if (hdr.ipv4.isValid()) {
ipv4_lpm.apply();
}
}
}
/*************************************************************************
**************** E G R E S S P R O C E S S I N G *******************
*************************************************************************/
control MyEgress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
action add_swtrace(switchID_t swid) {
/*
* TODO: add logic to:
- Increment hdr.mri.count by 1
- Add a new swtrace header by calling push_front(1) on hdr.swtraces.
- Set hdr.swtraces[0].swid to the id parameter
- Set hdr.swtraces[0].qdepth to (qdepth_t)standard_metadata.deq_qdepth
- Increment hdr.ipv4.ihl by 2
- Increment hdr.ipv4.totalLen by 8
- Increment hdr.ipv4_option.optionLength by 8
*/
}
table swtrace {
actions = {
/* TODO: add the correct action */
NoAction;
}
default_action = NoAction();
}
apply {
/*
* TODO: add logic to:
* - If hdr.mri is valid:
* - Apply table swtrace
*/
swtrace.apply();
}
}
/*************************************************************************
************* C H E C K S U M C O M P U T A T I O N **************
*************************************************************************/
control MyComputeChecksum(inout headers hdr, inout metadata meta) {
apply {
update_checksum(
hdr.ipv4.isValid(),
{ hdr.ipv4.version,
hdr.ipv4.ihl,
hdr.ipv4.diffserv,
hdr.ipv4.totalLen,
hdr.ipv4.identification,
hdr.ipv4.flags,
hdr.ipv4.fragOffset,
hdr.ipv4.ttl,
hdr.ipv4.protocol,
hdr.ipv4.srcAddr,
hdr.ipv4.dstAddr },
hdr.ipv4.hdrChecksum,
HashAlgorithm.csum16);
}
}
/*************************************************************************
*********************** D E P A R S E R *******************************
*************************************************************************/
control MyDeparser(packet_out packet, in headers hdr) {
apply {
packet.emit(hdr.ethernet);
packet.emit(hdr.ipv4);
/* TODO: emit ipv4_option, mri and swtraces headers */
}
}
/*************************************************************************
*********************** S W I T C H *******************************
*************************************************************************/
V1Switch(
MyParser(),
MyVerifyChecksum(),
MyIngress(),
MyEgress(),
MyComputeChecksum(),
MyDeparser()
) main;

View File

@ -1,6 +0,0 @@
table_set_default ipv4_lpm drop
table_set_default swtrace add_swtrace 1
table_add ipv4_lpm ipv4_forward 10.0.1.1/32 => 00:00:00:00:01:01 2
table_add ipv4_lpm ipv4_forward 10.0.1.11/32 => 00:00:00:00:01:0b 1
table_add ipv4_lpm ipv4_forward 10.0.2.0/24 => 00:00:00:02:03:00 3
table_add ipv4_lpm ipv4_forward 10.0.3.0/24 => 00:00:00:03:02:00 4

View File

@ -1,6 +0,0 @@
table_set_default ipv4_lpm drop
table_set_default swtrace add_swtrace 2
table_add ipv4_lpm ipv4_forward 10.0.2.2/32 => 00:00:00:00:02:02 2
table_add ipv4_lpm ipv4_forward 10.0.2.22/32 => 00:00:00:00:02:16 1
table_add ipv4_lpm ipv4_forward 10.0.1.0/24 => 00:00:00:01:03:00 3
table_add ipv4_lpm ipv4_forward 10.0.3.0/24 => 00:00:00:03:03:00 4

View File

@ -1,5 +0,0 @@
table_set_default ipv4_lpm drop
table_set_default swtrace add_swtrace 3
table_add ipv4_lpm ipv4_forward 10.0.3.3/32 => 00:00:00:00:03:01 1
table_add ipv4_lpm ipv4_forward 10.0.1.0/24 => 00:00:00:01:04:00 2
table_add ipv4_lpm ipv4_forward 10.0.2.0/24 => 00:00:00:02:04:00 3

View File

@ -1,179 +0,0 @@
#!/usr/bin/env python2
import argparse
import os
from time import sleep
import p4runtime_lib.bmv2
import p4runtime_lib.helper
SWITCH_TO_HOST_PORT = 1
SWITCH_TO_SWITCH_PORT = 2
def writeTunnelRules(p4info_helper, ingress_sw, egress_sw, tunnel_id,
dst_eth_addr, dst_ip_addr):
'''
Installs three rules:
1) An tunnel ingress rule on the ingress switch in the ipv4_lpm table that
encapsulates traffic into a tunnel with the specified ID
2) A transit rule on the ingress switch that forwards traffic based on
the specified ID
3) An tunnel egress rule on the egress switch that decapsulates traffic
with the specified ID and sends it to the host
:param p4info_helper: the P4Info helper
:param ingress_sw: the ingress switch connection
:param egress_sw: the egress switch connection
:param tunnel_id: the specified tunnel ID
:param dst_eth_addr: the destination IP to match in the ingress rule
:param dst_ip_addr: the destination Ethernet address to write in the
egress rule
'''
# 1) Tunnel Ingress Rule
table_entry = p4info_helper.buildTableEntry(
table_name="ipv4_lpm",
match_fields={
"hdr.ipv4.dstAddr": (dst_ip_addr, 32)
},
action_name="myTunnel_ingress",
action_params={
"dst_id": tunnel_id,
})
ingress_sw.WriteTableEntry(table_entry)
print "Installed ingress tunnel rule on %s" % ingress_sw.name
# 2) Tunnel Transit Rule
# The rule will need to be added to the myTunnel_exact table and match on
# the tunnel ID (hdr.myTunnel.dst_id). Traffic will need to be forwarded
# using the myTunnel_forward action on the port connected to the next switch.
#
# For our simple topology, switch 1 and switch 2 are connected using a
# link attached to port 2 on both switches. We have defined a variable at
# the top of the file, SWITCH_TO_SWITCH_PORT, that you can use as the output
# port for this action.
#
# We will only need a transit rule on the ingress switch because we are
# using a simple topology. In general, you'll need on transit rule for
# each switch in the path (except the last switch, which has the egress rule),
# and you will need to select the port dynamically for each switch based on
# your topology.
# TODO build the transit rule
# TODO install the transit rule on the ingress switch
print "TODO Install transit tunnel rule"
# 3) Tunnel Egress Rule
# For our simple topology, the host will always be located on the
# SWITCH_TO_HOST_PORT (port 1).
# In general, you will need to keep track of which port the host is
# connected to.
table_entry = p4info_helper.buildTableEntry(
table_name="myTunnel_exact",
match_fields={
"hdr.myTunnel.dst_id": tunnel_id
},
action_name="myTunnel_egress",
action_params={
"dstAddr": dst_eth_addr,
"port": SWITCH_TO_HOST_PORT
})
egress_sw.WriteTableEntry(table_entry)
print "Installed egress tunnel rule on %s" % egress_sw.name
def readTableRules(p4info_helper, sw):
'''
Reads the table entries from all tables on the switch.
:param p4info_helper: the P4Info helper
:param sw: the switch connection
'''
print '\n----- Reading tables rules for %s -----' % sw.name
for response in sw.ReadTableEntries():
for entity in response.entities:
entry = entity.table_entry
# TODO For extra credit, you can use the p4info_helper to translate
# the IDs the entry to names
print entry
print '-----'
def printCounter(p4info_helper, sw, counter_name, index):
'''
Reads the specified counter at the specified index from the switch. In our
program, the index is the tunnel ID. If the index is 0, it will return all
values from the counter.
:param p4info_helper: the P4Info helper
:param sw: the switch connection
:param counter_name: the name of the counter from the P4 program
:param index: the counter index (in our case, the tunnel ID)
'''
for response in sw.ReadCounters(p4info_helper.get_counters_id(counter_name), index):
for entity in response.entities:
counter = entity.counter_entry
print "%s %s %d: %d packets (%d bytes)" % (
sw.name, counter_name, index,
counter.data.packet_count, counter.data.byte_count
)
def main(p4info_file_path, bmv2_file_path):
# Instantiate a P4 Runtime helper from the p4info file
p4info_helper = p4runtime_lib.helper.P4InfoHelper(p4info_file_path)
# Create a switch connection object for s1 and s2;
# this is backed by a P4 Runtime gRPC connection
s1 = p4runtime_lib.bmv2.Bmv2SwitchConnection('s1', address='127.0.0.1:50051')
s2 = p4runtime_lib.bmv2.Bmv2SwitchConnection('s2', address='127.0.0.1:50052')
# Install the P4 program on the switches
s1.SetForwardingPipelineConfig(p4info=p4info_helper.p4info,
bmv2_json_file_path=bmv2_file_path)
print "Installed P4 Program using SetForwardingPipelineConfig on %s" % s1.name
s2.SetForwardingPipelineConfig(p4info=p4info_helper.p4info,
bmv2_json_file_path=bmv2_file_path)
print "Installed P4 Program using SetForwardingPipelineConfig on %s" % s2.name
# Write the rules that tunnel traffic from h1 to h2
writeTunnelRules(p4info_helper, ingress_sw=s1, egress_sw=s2, tunnel_id=100,
dst_eth_addr="00:00:00:00:02:02", dst_ip_addr="10.0.2.2")
# Write the rules that tunnel traffic from h2 to h1
writeTunnelRules(p4info_helper, ingress_sw=s2, egress_sw=s1, tunnel_id=200,
dst_eth_addr="00:00:00:00:01:01", dst_ip_addr="10.0.1.1")
# TODO Uncomment the following two lines to read table entries from s1 and s2
#readTableRules(p4info_helper, s1)
#readTableRules(p4info_helper, s2)
# Print the tunnel counters every 2 seconds
try:
while True:
sleep(2)
print '\n----- Reading tunnel counters -----'
printCounter(p4info_helper, s1, "ingressTunnelCounter", 100)
printCounter(p4info_helper, s2, "egressTunnelCounter", 100)
printCounter(p4info_helper, s2, "ingressTunnelCounter", 200)
printCounter(p4info_helper, s1, "egressTunnelCounter", 200)
except KeyboardInterrupt:
print " Shutting down."
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='P4Runtime Controller')
parser.add_argument('--p4info', help='p4info proto in text format from p4c',
type=str, action="store", required=False,
default='./build/advanced_tunnel.p4info')
parser.add_argument('--bmv2-json', help='BMv2 JSON file from p4c',
type=str, action="store", required=False,
default='./build/advanced_tunnel.json')
args = parser.parse_args()
if not os.path.exists(args.p4info):
parser.print_help()
print "\np4info file not found: %s\nHave you run 'make'?" % args.p4info
parser.exit(1)
if not os.path.exists(args.bmv2_json):
parser.print_help()
print "\nBMv2 JSON file not found: %s\nHave you run 'make'?" % args.bmv2_json
parser.exit(1)
main(args.p4info, args.bmv2_json)

View File

@ -1,183 +0,0 @@
# Copyright 2017-present Open Networking Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import re
import google.protobuf.text_format
from p4 import p4runtime_pb2
from p4.config import p4info_pb2
from p4runtime_lib.convert import encode
class P4InfoHelper(object):
def __init__(self, p4_info_filepath):
p4info = p4info_pb2.P4Info()
# Load the p4info file into a skeleton P4Info object
with open(p4_info_filepath) as p4info_f:
google.protobuf.text_format.Merge(p4info_f.read(), p4info)
self.p4info = p4info
def get(self, entity_type, name=None, id=None):
if name is not None and id is not None:
raise AssertionError("name or id must be None")
for o in getattr(self.p4info, entity_type):
pre = o.preamble
if name:
if (pre.name == name or pre.alias == name):
return o
else:
if pre.id == id:
return o
if name:
raise AttributeError("Could not find %r of type %s" % (name, entity_type))
else:
raise AttributeError("Could not find id %r of type %s" % (id, entity_type))
def get_id(self, entity_type, name):
return self.get(entity_type, name=name).preamble.id
def get_name(self, entity_type, id):
return self.get(entity_type, id=id).preamble.name
def get_alias(self, entity_type, id):
return self.get(entity_type, id=id).preamble.alias
def __getattr__(self, attr):
# Synthesize convenience functions for name to id lookups for top-level entities
# e.g. get_tables_id(name_string) or get_actions_id(name_string)
m = re.search("^get_(\w+)_id$", attr)
if m:
primitive = m.group(1)
return lambda name: self.get_id(primitive, name)
# Synthesize convenience functions for id to name lookups
# e.g. get_tables_name(id) or get_actions_name(id)
m = re.search("^get_(\w+)_name$", attr)
if m:
primitive = m.group(1)
return lambda id: self.get_name(primitive, id)
raise AttributeError("%r object has no attribute %r" % (self.__class__, attr))
def get_match_field(self, table_name, name=None, id=None):
for t in self.p4info.tables:
pre = t.preamble
if pre.name == table_name:
for mf in t.match_fields:
if name is not None:
if mf.name == name:
return mf
elif id is not None:
if mf.id == id:
return mf
raise AttributeError("%r has no attribute %r" % (table_name, name if name is not None else id))
def get_match_field_id(self, table_name, match_field_name):
return self.get_match_field(table_name, name=match_field_name).id
def get_match_field_name(self, table_name, match_field_id):
return self.get_match_field(table_name, id=match_field_id).name
def get_match_field_pb(self, table_name, match_field_name, value):
p4info_match = self.get_match_field(table_name, match_field_name)
bitwidth = p4info_match.bitwidth
p4runtime_match = p4runtime_pb2.FieldMatch()
p4runtime_match.field_id = p4info_match.id
match_type = p4info_match.match_type
if match_type == p4info_pb2.MatchField.VALID:
valid = p4runtime_match.valid
valid.value = bool(value)
elif match_type == p4info_pb2.MatchField.EXACT:
exact = p4runtime_match.exact
exact.value = encode(value, bitwidth)
elif match_type == p4info_pb2.MatchField.LPM:
lpm = p4runtime_match.lpm
lpm.value = encode(value[0], bitwidth)
lpm.prefix_len = value[1]
elif match_type == p4info_pb2.MatchField.TERNARY:
lpm = p4runtime_match.ternary
lpm.value = encode(value[0], bitwidth)
lpm.mask = encode(value[1], bitwidth)
elif match_type == p4info_pb2.MatchField.RANGE:
lpm = p4runtime_match.range
lpm.low = encode(value[0], bitwidth)
lpm.high = encode(value[1], bitwidth)
else:
raise Exception("Unsupported match type with type %r" % match_type)
return p4runtime_match
def get_match_field_value(self, match_field):
match_type = match_field.WhichOneof("field_match_type")
if match_type == 'valid':
return match_field.valid.value
elif match_type == 'exact':
return match_field.exact.value
elif match_type == 'lpm':
return (match_field.lpm.value, match_field.lpm.prefix_len)
elif match_type == 'ternary':
return (match_field.ternary.value, match_field.ternary.mask)
elif match_type == 'range':
return (match_field.range.low, match_field.range.high)
else:
raise Exception("Unsupported match type with type %r" % match_type)
def get_action_param(self, action_name, name=None, id=None):
for a in self.p4info.actions:
pre = a.preamble
if pre.name == action_name:
for p in a.params:
if name is not None:
if p.name == name:
return p
elif id is not None:
if p.id == id:
return p
raise AttributeError("action %r has no param %r" % (action_name, name if name is not None else id))
def get_action_param_id(self, action_name, param_name):
return self.get_action_param(action_name, name=param_name).id
def get_action_param_name(self, action_name, param_id):
return self.get_action_param(action_name, id=param_id).name
def get_action_param_pb(self, action_name, param_name, value):
p4info_param = self.get_action_param(action_name, param_name)
p4runtime_param = p4runtime_pb2.Action.Param()
p4runtime_param.param_id = p4info_param.id
p4runtime_param.value = encode(value, p4info_param.bitwidth)
return p4runtime_param
def buildTableEntry(self,
table_name,
match_fields={},
action_name=None,
action_params={}):
table_entry = p4runtime_pb2.TableEntry()
table_entry.table_id = self.get_tables_id(table_name)
if match_fields:
table_entry.match.extend([
self.get_match_field_pb(table_name, match_field_name, value)
for match_field_name, value in match_fields.iteritems()
])
if action_name:
action = table_entry.action.action
action.action_id = self.get_actions_id(action_name)
if action_params:
action.params.extend([
self.get_action_param_pb(action_name, field_name, value)
for field_name, value in action_params.iteritems()
])
return table_entry

View File

@ -1,88 +0,0 @@
# Copyright 2017-present Open Networking Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from abc import abstractmethod
import grpc
from p4 import p4runtime_pb2
from p4.tmp import p4config_pb2
class SwitchConnection(object):
def __init__(self, name, address='127.0.0.1:50051', device_id=0):
self.name = name
self.address = address
self.device_id = device_id
self.p4info = None
self.channel = grpc.insecure_channel(self.address)
self.client_stub = p4runtime_pb2.P4RuntimeStub(self.channel)
@abstractmethod
def buildDeviceConfig(self, **kwargs):
return p4config_pb2.P4DeviceConfig()
def SetForwardingPipelineConfig(self, p4info, dry_run=False, **kwargs):
device_config = self.buildDeviceConfig(**kwargs)
request = p4runtime_pb2.SetForwardingPipelineConfigRequest()
config = request.configs.add()
config.device_id = self.device_id
config.p4info.CopyFrom(p4info)
config.p4_device_config = device_config.SerializeToString()
request.action = p4runtime_pb2.SetForwardingPipelineConfigRequest.VERIFY_AND_COMMIT
if dry_run:
print "P4 Runtime SetForwardingPipelineConfig:", request
else:
self.client_stub.SetForwardingPipelineConfig(request)
def WriteTableEntry(self, table_entry, dry_run=False):
request = p4runtime_pb2.WriteRequest()
request.device_id = self.device_id
update = request.updates.add()
update.type = p4runtime_pb2.Update.INSERT
update.entity.table_entry.CopyFrom(table_entry)
if dry_run:
print "P4 Runtime Write:", request
else:
self.client_stub.Write(request)
def ReadTableEntries(self, table_id=None, dry_run=False):
request = p4runtime_pb2.ReadRequest()
request.device_id = self.device_id
entity = request.entities.add()
table_entry = entity.table_entry
if table_id is not None:
table_entry.table_id = table_id
else:
table_entry.table_id = 0
if dry_run:
print "P4 Runtime Read:", request
else:
for response in self.client_stub.Read(request):
yield response
def ReadCounters(self, counter_id=None, index=None, dry_run=False):
request = p4runtime_pb2.ReadRequest()
request.device_id = self.device_id
entity = request.entities.add()
counter_entry = entity.counter_entry
if counter_id is not None:
counter_entry.counter_id = counter_id
else:
counter_entry.counter_id = 0
if index is not None:
counter_entry.index = index
if dry_run:
print "P4 Runtime Read:", request
else:
for response in self.client_stub.Read(request):
yield response

View File

@ -1,202 +0,0 @@
#!/usr/bin/env python2
import argparse
import os
from time import sleep
# NOTE: Appending to the PYTHON_PATH is only required in the `solution` directory.
# It is not required for mycontroller.py in the top-level directory.
import sys
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
import p4runtime_lib.bmv2
import p4runtime_lib.helper
SWITCH_TO_HOST_PORT = 1
SWITCH_TO_SWITCH_PORT = 2
def writeTunnelRules(p4info_helper, ingress_sw, egress_sw, tunnel_id,
dst_eth_addr, dst_ip_addr):
'''
Installs three rules:
1) An tunnel ingress rule on the ingress switch in the ipv4_lpm table that
encapsulates traffic into a tunnel with the specified ID
2) A transit rule on the ingress switch that forwards traffic based on
the specified ID
3) An tunnel egress rule on the egress switch that decapsulates traffic
with the specified ID and sends it to the host
:param p4info_helper: the P4Info helper
:param ingress_sw: the ingress switch connection
:param egress_sw: the egress switch connection
:param tunnel_id: the specified tunnel ID
:param dst_eth_addr: the destination IP to match in the ingress rule
:param dst_ip_addr: the destination Ethernet address to write in the
egress rule
'''
# 1) Tunnel Ingress Rule
table_entry = p4info_helper.buildTableEntry(
table_name="ipv4_lpm",
match_fields={
"hdr.ipv4.dstAddr": (dst_ip_addr, 32)
},
action_name="myTunnel_ingress",
action_params={
"dst_id": tunnel_id,
})
ingress_sw.WriteTableEntry(table_entry)
print "Installed ingress tunnel rule on %s" % ingress_sw.name
# 2) Tunnel Transit Rule
# The rule will need to be added to the myTunnel_exact table and match on
# the tunnel ID (hdr.myTunnel.dst_id). Traffic will need to be forwarded
# using the myTunnel_forward action on the port connected to the next switch.
#
# For our simple topology, switch 1 and switch 2 are connected using a
# link attached to port 2 on both switches. We have defined a variable at
# the top of the file, SWITCH_TO_SWITCH_PORT, that you can use as the output
# port for this action.
#
# We will only need a transit rule on the ingress switch because we are
# using a simple topology. In general, you'll need on transit rule for
# each switch in the path (except the last switch, which has the egress rule),
# and you will need to select the port dynamically for each switch based on
# your topology.
table_entry = p4info_helper.buildTableEntry(
table_name="myTunnel_exact",
match_fields={
"hdr.myTunnel.dst_id": tunnel_id
},
action_name="myTunnel_forward",
action_params={
"port": SWITCH_TO_SWITCH_PORT
})
ingress_sw.WriteTableEntry(table_entry)
print "Installed transit tunnel rule on %s" % ingress_sw.name
# 3) Tunnel Egress Rule
# For our simple topology, the host will always be located on the
# SWITCH_TO_HOST_PORT (port 1).
# In general, you will need to keep track of which port the host is
# connected to.
table_entry = p4info_helper.buildTableEntry(
table_name="myTunnel_exact",
match_fields={
"hdr.myTunnel.dst_id": tunnel_id
},
action_name="myTunnel_egress",
action_params={
"dstAddr": dst_eth_addr,
"port": SWITCH_TO_HOST_PORT
})
egress_sw.WriteTableEntry(table_entry)
print "Installed egress tunnel rule on %s" % egress_sw.name
def readTableRules(p4info_helper, sw):
'''
Reads the table entries from all tables on the switch.
:param p4info_helper: the P4Info helper
:param sw: the switch connection
'''
print '\n----- Reading tables rules for %s -----' % sw.name
for response in sw.ReadTableEntries():
for entity in response.entities:
entry = entity.table_entry
# TODO For extra credit, you can use the p4info_helper to translate
# the IDs the entry to names
table_name = p4info_helper.get_tables_name(entry.table_id)
print '%s: ' % table_name,
for m in entry.match:
print p4info_helper.get_match_field_name(table_name, m.field_id),
print '%r' % (p4info_helper.get_match_field_value(m),),
action = entry.action.action
action_name = p4info_helper.get_actions_name(action.action_id)
print '->', action_name,
for p in action.params:
print p4info_helper.get_action_param_name(action_name, p.param_id),
print '%r' % p.value,
print
def printCounter(p4info_helper, sw, counter_name, index):
'''
Reads the specified counter at the specified index from the switch. In our
program, the index is the tunnel ID. If the index is 0, it will return all
values from the counter.
:param p4info_helper: the P4Info helper
:param sw: the switch connection
:param counter_name: the name of the counter from the P4 program
:param index: the counter index (in our case, the tunnel ID)
'''
for response in sw.ReadCounters(p4info_helper.get_counters_id(counter_name), index):
for entity in response.entities:
counter = entity.counter_entry
print "%s %s %d: %d packets (%d bytes)" % (
sw.name, counter_name, index,
counter.data.packet_count, counter.data.byte_count
)
def main(p4info_file_path, bmv2_file_path):
# Instantiate a P4 Runtime helper from the p4info file
p4info_helper = p4runtime_lib.helper.P4InfoHelper(p4info_file_path)
# Create a switch connection object for s1 and s2;
# this is backed by a P4 Runtime gRPC connection
s1 = p4runtime_lib.bmv2.Bmv2SwitchConnection('s1', address='127.0.0.1:50051')
s2 = p4runtime_lib.bmv2.Bmv2SwitchConnection('s2', address='127.0.0.1:50052')
# Install the P4 program on the switches
s1.SetForwardingPipelineConfig(p4info=p4info_helper.p4info,
bmv2_json_file_path=bmv2_file_path)
print "Installed P4 Program using SetForwardingPipelineConfig on %s" % s1.name
s2.SetForwardingPipelineConfig(p4info=p4info_helper.p4info,
bmv2_json_file_path=bmv2_file_path)
print "Installed P4 Program using SetForwardingPipelineConfig on %s" % s2.name
# Write the rules that tunnel traffic from h1 to h2
writeTunnelRules(p4info_helper, ingress_sw=s1, egress_sw=s2, tunnel_id=100,
dst_eth_addr="00:00:00:00:02:02", dst_ip_addr="10.0.2.2")
# Write the rules that tunnel traffic from h2 to h1
writeTunnelRules(p4info_helper, ingress_sw=s2, egress_sw=s1, tunnel_id=200,
dst_eth_addr="00:00:00:00:01:01", dst_ip_addr="10.0.1.1")
# TODO Uncomment the following two lines to read table entries from s1 and s2
readTableRules(p4info_helper, s1)
readTableRules(p4info_helper, s2)
# Print the tunnel counters every 2 seconds
try:
while True:
sleep(2)
print '\n----- Reading tunnel counters -----'
printCounter(p4info_helper, s1, "ingressTunnelCounter", 100)
printCounter(p4info_helper, s2, "egressTunnelCounter", 100)
printCounter(p4info_helper, s2, "ingressTunnelCounter", 200)
printCounter(p4info_helper, s1, "egressTunnelCounter", 200)
except KeyboardInterrupt:
print " Shutting down."
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='P4Runtime Controller')
parser.add_argument('--p4info', help='p4info proto in text format from p4c',
type=str, action="store", required=False,
default='./build/advanced_tunnel.p4info')
parser.add_argument('--bmv2-json', help='BMv2 JSON file from p4c',
type=str, action="store", required=False,
default='./build/advanced_tunnel.json')
args = parser.parse_args()
if not os.path.exists(args.p4info):
parser.print_help()
print "\np4info file not found: %s\nHave you run 'make'?" % args.p4info
parser.exit(1)
if not os.path.exists(args.bmv2_json):
parser.print_help()
print "\nBMv2 JSON file not found: %s\nHave you run 'make'?" % args.bmv2_json
parser.exit(1)
main(args.p4info, args.bmv2_json)

View File

@ -1 +0,0 @@
include ../../utils/Makefile

View File

@ -1,147 +0,0 @@
# Implementing Source Routing
## Introduction
The objective of this exercise is to implement source routing. With
source routing, the source host guides each switch in the network to
send the packet to a specific port. The host puts a stack of output
ports in the packet. In this example, we just put the stack after
Ethernet header and select a special etherType to indicate that. Each
switch pops an item from the stack and forwards the packet according
to the specified port number.
Your switch must parse the source routing stack. Each item has a bos
(bottom of stack) bit and a port number. The bos bit is 1 only for the
last entry of stack. Then at ingress, it should pop an entry from the
stack and set the egress port accordingly. Note that the last hop can
also revert back the etherType to `TYPE_IPV4`.
> **Spoiler alert:** There is a reference solution in the `solution`
> sub-directory. Feel free to compare your implementation to the
> reference.
## Step 1: Run the (incomplete) starter code
The directory with this README also contains a skeleton P4 program,
`source_routing.p4`, which initially drops all packets. Your job (in
the next step) will be to extend it to properly to route packets.
Before that, let's compile the incomplete `source_routing.p4` and
bring up a network in Mininet to test its behavior.
1. In your shell, run:
```bash
make
```
This will:
* compile `source_routing.p4`, and
* start a Mininet instance with three switches (`s1`, `s2`, `s3`) configured
in a triangle, each connected to one host (`h1`, `h2`, `h3`).
Check the network topology using the `net` command in mininet.
You can also change the topology in topology.json
* The hosts are assigned IPs of `10.0.1.1`, `10.0.2.2`, etc
(`10.0.<Switchid>.<hostID>`).
2. You should now see a Mininet command prompt. Open two terminals for
`h1` and `h2`, respectively:
```bash
mininet> xterm h1 h2
```
3. Each host includes a small Python-based messaging client and
server. In `h2`'s xterm, start the server:
```bash
./receive.py
```
4. In `h1`'s xterm, send a message from the client:
```bash
./send.py 10.0.2.2
```
5. Type a list of port numbers. say `2 3 2 2 1`. This should send the
packet through `h1`, `s1`, `s2`, `s3`, `s1`, `s2`, and
`h2`. However, `h2` will not receive the message.
6. Type `q` to exit send.py and type `exit` to leave each xterm and
the Mininet command line.
The message was not received because each switch is programmed with
`source_routing.p4`, which drops all packets on arrival. You can
verify this by looking at `build/logs/s1.log`. Your job is to extend
the P4 code so packets are delivered to their destination.
## Step 2: Implement source routing
The `source_routing.p4` file contains a skeleton P4 program with key
pieces of logic replaced by `TODO` comments. These should guide your
implementation---replace each `TODO` with logic implementing the
missing piece.
A complete `source_routing.p4` will contain the following components:
1. Header type definitions for Ethernet (`ethernet_t`) and IPv4
(`ipv4_t`) and Source Route (`srcRoute_t`).
2. **TODO:** Parsers for Ethernet and Source Route that populate
`ethernet` and `srcRoutes` fields.
3. An action to drop a packet, using `mark_to_drop()`.
4. **TODO:** An action (called `srcRoute_nhop`), which will:
1. Set the egress port for the next hop.
2. remove the first entry of srcRoutes
5. A control with an `apply` block that:
1. checks the existence of source routes.
2. **TODO:** if statement to change etherent.etherType if it is the last hop
3. **TODO:** call srcRoute_nhop action
6. A deparser that selects the order in which fields inserted into the outgoing
packet.
7. A `package` instantiation supplied with the parser, control, and deparser.
> In general, a package also requires instances of checksum verification
> and recomputation controls. These are not necessary for this tutorial
> and are replaced with instantiations of empty controls.
## Step 3: Run your solution
Follow the instructions from Step 1. This time, your message from `h1`
should be delivered to `h2`.
Check the `ttl` of the IP header. Each hop decrements `ttl`. The port
sequence `2 3 2 2 1`, forces the packet to have a loop, so the `ttl`
should be 59 at `h2`. Can you find the port sequence for the shortest
path?
### Food for thought
* Can we change the program to handle both IPv4 forwarding and source
routing at the same time?
* How would you enhance your program to let the first switch add the
path, so that source routing would be transparent to end-hosts?
### Troubleshooting
There are several ways that problems might manifest:
1. `source_routing.p4` fails to compile. In this case, `make` will
report the error emitted from the compiler and stop.
2. `source_routing.p4` compiles but switches or mininet do not start.
Do you have another instance of mininet running? Did the previous
run of mininet crash? if yes, check "Cleaning up Mininet" bellow.
3. `source_routing.p4` compiles but the switch does not process
packets in the desired way. The `/tmp/p4s.<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. The
`build/<switch-name>-<interface-name>.pcap` also contains the pcap
of packets on each interface. Use `tcpdump -r <filename> -xxx` to
print the hexdump of the packets.
#### Cleaning up Mininet
In the cases above, `make` may leave a Mininet instance running in
the background. Use the following command to clean up these
instances:
```bash
mn -c
```
## Next Steps
Congratulations, your implementation works! Move on to
[Calculator](../calc).

View File

@ -1,382 +0,0 @@
#!/usr/bin/env python2
# Copyright 2013-present Barefoot Networks, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Adapted by Robert MacDavid (macdavid@cs.princeton.edu) from scripts found in
# the p4app repository (https://github.com/p4lang/p4app)
#
# We encourage you to dissect this script to better understand the BMv2/Mininet
# environment used by the P4 tutorial.
#
import os, sys, json, subprocess, re, argparse
from time import sleep
from p4_mininet import P4Switch, P4Host
from mininet.net import Mininet
from mininet.topo import Topo
from mininet.link import TCLink
from mininet.cli import CLI
from p4runtime_switch import P4RuntimeSwitch
def configureP4Switch(**switch_args):
""" Helper class that is called by mininet to initialize
the virtual P4 switches. The purpose is to ensure each
switch's thrift server is using a unique port.
"""
if "sw_path" in switch_args and 'grpc' in switch_args['sw_path']:
# If grpc appears in the BMv2 switch target, we assume will start P4 Runtime
class ConfiguredP4RuntimeSwitch(P4RuntimeSwitch):
def __init__(self, *opts, **kwargs):
kwargs.update(switch_args)
P4RuntimeSwitch.__init__(self, *opts, **kwargs)
def describe(self):
print "%s -> gRPC port: %d" % (self.name, self.grpc_port)
return ConfiguredP4RuntimeSwitch
else:
class ConfiguredP4Switch(P4Switch):
next_thrift_port = 9090
def __init__(self, *opts, **kwargs):
global next_thrift_port
kwargs.update(switch_args)
kwargs['thrift_port'] = ConfiguredP4Switch.next_thrift_port
ConfiguredP4Switch.next_thrift_port += 1
P4Switch.__init__(self, *opts, **kwargs)
def describe(self):
print "%s -> Thrift port: %d" % (self.name, self.thrift_port)
return ConfiguredP4Switch
class ExerciseTopo(Topo):
""" The mininet topology class for the P4 tutorial exercises.
A custom class is used because the exercises make a few topology
assumptions, mostly about the IP and MAC addresses.
"""
def __init__(self, hosts, switches, links, log_dir, **opts):
Topo.__init__(self, **opts)
host_links = []
switch_links = []
self.sw_port_mapping = {}
for link in links:
if link['node1'][0] == 'h':
host_links.append(link)
else:
switch_links.append(link)
link_sort_key = lambda x: x['node1'] + x['node2']
# Links must be added in a sorted order so bmv2 port numbers are predictable
host_links.sort(key=link_sort_key)
switch_links.sort(key=link_sort_key)
for sw in switches:
self.addSwitch(sw, log_file="%s/%s.log" %(log_dir, sw))
for link in host_links:
host_name = link['node1']
host_sw = link['node2']
host_num = int(host_name[1:])
sw_num = int(host_sw[1:])
host_ip = "10.0.%d.%d" % (sw_num, host_num)
host_mac = '00:00:00:00:%02x:%02x' % (sw_num, host_num)
# Each host IP should be /24, so all exercise traffic will use the
# default gateway (the switch) without sending ARP requests.
self.addHost(host_name, ip=host_ip+'/24', mac=host_mac)
self.addLink(host_name, host_sw,
delay=link['latency'], bw=link['bandwidth'],
addr1=host_mac, addr2=host_mac)
self.addSwitchPort(host_sw, host_name)
for link in switch_links:
self.addLink(link['node1'], link['node2'],
delay=link['latency'], bw=link['bandwidth'])
self.addSwitchPort(link['node1'], link['node2'])
self.addSwitchPort(link['node2'], link['node1'])
self.printPortMapping()
def addSwitchPort(self, sw, node2):
if sw not in self.sw_port_mapping:
self.sw_port_mapping[sw] = []
portno = len(self.sw_port_mapping[sw])+1
self.sw_port_mapping[sw].append((portno, node2))
def printPortMapping(self):
print "Switch port mapping:"
for sw in sorted(self.sw_port_mapping.keys()):
print "%s: " % sw,
for portno, node2 in self.sw_port_mapping[sw]:
print "%d:%s\t" % (portno, node2),
print
class ExerciseRunner:
"""
Attributes:
log_dir : string // directory for mininet log files
pcap_dir : string // directory for mininet switch pcap files
quiet : bool // determines if we print logger messages
hosts : list<string> // list of mininet host names
switches : dict<string, dict> // mininet host names and their associated properties
links : list<dict> // list of mininet link properties
switch_json : string // json of the compiled p4 example
bmv2_exe : string // name or path of the p4 switch binary
topo : Topo object // The mininet topology instance
net : Mininet object // The mininet instance
"""
def logger(self, *items):
if not self.quiet:
print(' '.join(items))
def formatLatency(self, l):
""" Helper method for parsing link latencies from the topology json. """
if isinstance(l, (str, unicode)):
return l
else:
return str(l) + "ms"
def __init__(self, topo_file, log_dir, pcap_dir,
switch_json, bmv2_exe='simple_switch', quiet=False):
""" Initializes some attributes and reads the topology json. Does not
actually run the exercise. Use run_exercise() for that.
Arguments:
topo_file : string // A json file which describes the exercise's
mininet topology.
log_dir : string // Path to a directory for storing exercise logs
pcap_dir : string // Ditto, but for mininet switch pcap files
switch_json : string // Path to a compiled p4 json for bmv2
bmv2_exe : string // Path to the p4 behavioral binary
quiet : bool // Enable/disable script debug messages
"""
self.quiet = quiet
self.logger('Reading topology file.')
with open(topo_file, 'r') as f:
topo = json.load(f)
self.hosts = topo['hosts']
self.switches = topo['switches']
self.links = self.parse_links(topo['links'])
# Ensure all the needed directories exist and are directories
for dir_name in [log_dir, pcap_dir]:
if not os.path.isdir(dir_name):
if os.path.exists(dir_name):
raise Exception("'%s' exists and is not a directory!" % dir_name)
os.mkdir(dir_name)
self.log_dir = log_dir
self.pcap_dir = pcap_dir
self.switch_json = switch_json
self.bmv2_exe = bmv2_exe
def run_exercise(self):
""" Sets up the mininet instance, programs the switches,
and starts the mininet CLI. This is the main method to run after
initializing the object.
"""
# Initialize mininet with the topology specified by the config
self.create_network()
self.net.start()
sleep(1)
# some programming that must happen after the net has started
self.program_hosts()
self.program_switches()
# wait for that to finish. Not sure how to do this better
sleep(1)
self.do_net_cli()
# stop right after the CLI is exited
self.net.stop()
def parse_links(self, unparsed_links):
""" Given a list of links descriptions of the form [node1, node2, latency, bandwidth]
with the latency and bandwidth being optional, parses these descriptions
into dictionaries and store them as self.links
"""
links = []
for link in unparsed_links:
# make sure each link's endpoints are ordered alphabetically
s, t, = link[0], link[1]
if s > t:
s,t = t,s
link_dict = {'node1':s,
'node2':t,
'latency':'0ms',
'bandwidth':None
}
if len(link) > 2:
link_dict['latency'] = self.formatLatency(link[2])
if len(link) > 3:
link_dict['bandwidth'] = link[3]
if link_dict['node1'][0] == 'h':
assert link_dict['node2'][0] == 's', 'Hosts should be connected to switches, not ' + str(link_dict['node2'])
links.append(link_dict)
return links
def create_network(self):
""" Create the mininet network object, and store it as self.net.
Side effects:
- Mininet topology instance stored as self.topo
- Mininet instance stored as self.net
"""
self.logger("Building mininet topology.")
self.topo = ExerciseTopo(self.hosts, self.switches.keys(), self.links, self.log_dir)
switchClass = configureP4Switch(
sw_path=self.bmv2_exe,
json_path=self.switch_json,
log_console=True,
pcap_dump=self.pcap_dir)
self.net = Mininet(topo = self.topo,
link = TCLink,
host = P4Host,
switch = switchClass,
controller = None)
def program_switches(self):
""" If any command files were provided for the switches,
this method will start up the CLI on each switch and use the
contents of the command files as input.
Assumes:
- A mininet instance is stored as self.net and self.net.start() has
been called.
"""
cli = 'simple_switch_CLI'
for sw_name, sw_dict in self.switches.iteritems():
if 'cli_input' not in sw_dict: continue
# get the port for this particular switch's thrift server
sw_obj = self.net.get(sw_name)
thrift_port = sw_obj.thrift_port
cli_input_commands = sw_dict['cli_input']
self.logger('Configuring switch %s with file %s' % (sw_name, cli_input_commands))
with open(cli_input_commands, 'r') as fin:
cli_outfile = '%s/%s_cli_output.log'%(self.log_dir, sw_name)
with open(cli_outfile, 'w') as fout:
subprocess.Popen([cli, '--thrift-port', str(thrift_port)],
stdin=fin, stdout=fout)
def program_hosts(self):
""" Adds static ARP entries and default routes to each mininet host.
Assumes:
- A mininet instance is stored as self.net and self.net.start() has
been called.
"""
for host_name in self.topo.hosts():
h = self.net.get(host_name)
h_iface = h.intfs.values()[0]
link = h_iface.link
sw_iface = link.intf1 if link.intf1 != h_iface else link.intf2
# phony IP to lie to the host about
host_id = int(host_name[1:])
sw_ip = '10.0.%d.254' % host_id
# Ensure each host's interface name is unique, or else
# mininet cannot shutdown gracefully
h.defaultIntf().rename('%s-eth0' % host_name)
# static arp entries and default routes
h.cmd('arp -i %s -s %s %s' % (h_iface.name, sw_ip, sw_iface.mac))
h.cmd('ethtool --offload %s rx off tx off' % h_iface.name)
h.cmd('ip route add %s dev %s' % (sw_ip, h_iface.name))
h.setDefaultRoute("via %s" % sw_ip)
def do_net_cli(self):
""" Starts up the mininet CLI and prints some helpful output.
Assumes:
- A mininet instance is stored as self.net and self.net.start() has
been called.
"""
for s in self.net.switches:
s.describe()
for h in self.net.hosts:
h.describe()
self.logger("Starting mininet CLI")
# Generate a message that will be printed by the Mininet CLI to make
# interacting with the simple switch a little easier.
print('')
print('======================================================================')
print('Welcome to the BMV2 Mininet CLI!')
print('======================================================================')
print('Your P4 program is installed into the BMV2 software switch')
print('and your initial configuration is loaded. You can interact')
print('with the network using the mininet CLI below.')
print('')
if self.switch_json:
print('To inspect or change the switch configuration, connect to')
print('its CLI from your host operating system using this command:')
print(' simple_switch_CLI --thrift-port <switch thrift port>')
print('')
print('To view a switch log, run this command from your host OS:')
print(' tail -f %s/<switchname>.log' % self.log_dir)
print('')
print('To view the switch output pcap, check the pcap files in %s:' % self.pcap_dir)
print(' for example run: sudo tcpdump -xxx -r s1-eth1.pcap')
print('')
CLI(self.net)
def get_args():
cwd = os.getcwd()
default_logs = os.path.join(cwd, 'logs')
default_pcaps = os.path.join(cwd, 'pcaps')
parser = argparse.ArgumentParser()
parser.add_argument('-q', '--quiet', help='Suppress log messages.',
action='store_true', required=False, default=False)
parser.add_argument('-t', '--topo', help='Path to topology json',
type=str, required=False, default='./topology.json')
parser.add_argument('-l', '--log-dir', type=str, required=False, default=default_logs)
parser.add_argument('-p', '--pcap-dir', type=str, required=False, default=default_pcaps)
parser.add_argument('-j', '--switch_json', type=str, required=False)
parser.add_argument('-b', '--behavioral-exe', help='Path to behavioral executable',
type=str, required=False, default='simple_switch')
return parser.parse_args()
if __name__ == '__main__':
# from mininet.log import setLogLevel
# setLogLevel("info")
args = get_args()
exercise = ExerciseRunner(args.topo, args.log_dir, args.pcap_dir,
args.switch_json, args.behavioral_exe, args.quiet)
exercise.run_exercise()

View File

@ -1,22 +0,0 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant.configure(2) do |config|
config.vm.box = "bento/ubuntu-16.04"
config.vm.define "p4-tutorial" do |tutorial|
end
config.vm.provider "virtualbox" do |vb|
vb.name = "P4 Tutorial" + Time.now.strftime(" %Y-%m-%d")
vb.gui = true
vb.memory = 2048
vb.cpus = 2
vb.customize ["modifyvm", :id, "--cableconnected1", "on"]
end
config.vm.synced_folder '.', '/vagrant', disabled: true
config.vm.hostname = "p4"
config.vm.provision "file", source: "p4-logo.png", destination: "/home/vagrant/p4-logo.png"
config.vm.provision "file", source: "p4_16-mode.el", destination: "/home/vagrant/p4_16-mode.el"
config.vm.provision "file", source: "p4.vim", destination: "/home/vagrant/p4.vim"
config.vm.provision "shell", path: "root-bootstrap.sh"
config.vm.provision "shell", privileged: false, path: "user-bootstrap.sh"
end

View File

@ -1,90 +0,0 @@
#!/bin/bash
# Print commands and exit on errors
set -xe
sudo add-apt-repository ppa:webupd8team/sublime-text-3
sudo add-apt-repository ppa:webupd8team/atom
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" upgrade
apt-get install -y --no-install-recommends \
atom \
autoconf \
automake \
bison \
build-essential \
ca-certificates \
cmake \
cpp \
curl \
emacs24 \
flex \
git \
libboost-dev \
libboost-filesystem-dev \
libboost-iostreams1.58-dev \
libboost-program-options-dev \
libboost-system-dev \
libboost-test-dev \
libboost-thread-dev \
libc6-dev \
libevent-dev \
libffi-dev \
libfl-dev \
libgc-dev \
libgc1c2 \
libgflags-dev \
libgmp-dev \
libgmp10 \
libgmpxx4ldbl \
libjudy-dev \
libpcap-dev \
libreadline6 \
libreadline6-dev \
libssl-dev \
libtool \
lubuntu-desktop \
make \
mktemp \
pkg-config \
python \
python-dev \
python-ipaddr \
python-pip \
python-scapy \
python-setuptools \
sublime-text-installer \
tcpdump \
unzip \
vim \
wget \
xcscope-el \
xterm
useradd -m -d /home/p4 -s /bin/bash p4
echo "p4:p4" | chpasswd
echo "p4 ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/99_p4
chmod 440 /etc/sudoers.d/99_p4
usermod -aG vboxsf p4
cd /usr/share/lubuntu/wallpapers/
cp /home/vagrant/p4-logo.png .
rm lubuntu-default-wallpaper.png
ln -s p4-logo.png lubuntu-default-wallpaper.png
rm /home/vagrant/p4-logo.png
cd /home/vagrant
sed -i s@#background=@background=/usr/share/lubuntu/wallpapers/1604-lubuntu-default-wallpaper.png@ /etc/lightdm/lightdm-gtk-greeter.conf
# Disable screensaver
apt-get -y remove light-locker
# Automatically log into the P4 user
cat << EOF | tee -a /etc/lightdm/lightdm.conf.d/10-lightdm.conf
[SeatDefaults]
autologin-user=p4
autologin-user-timeout=0
user-session=Lubuntu
EOF

View File

@ -1,171 +0,0 @@
#!/bin/bash
# Print script commands.
set -x
# Exit on errors.
set -e
BMV2_COMMIT="ae84c2f6d5bc3dd6873a62e351f26c39038804da"
PI_COMMIT="f06a4df7d56413849dbe9ab8f9441321ff140bca"
P4C_COMMIT="3ad8d93f334a34d181e8d9d83100d797bac3f65a"
PROTOBUF_COMMIT="tags/v3.0.2"
GRPC_COMMIT="tags/v1.3.0"
NUM_CORES=`grep -c ^processor /proc/cpuinfo`
# Mininet
git clone git://github.com/mininet/mininet mininet
cd mininet
sudo ./util/install.sh -nwv
cd ..
# Protobuf
git clone https://github.com/google/protobuf.git
cd protobuf
git checkout ${PROTOBUF_COMMIT}
export CFLAGS="-Os"
export CXXFLAGS="-Os"
export LDFLAGS="-Wl,-s"
./autogen.sh
./configure --prefix=/usr
make -j${NUM_CORES}
sudo make install
sudo ldconfig
unset CFLAGS CXXFLAGS LDFLAGS
cd ..
# gRPC
git clone https://github.com/grpc/grpc.git
cd grpc
git checkout ${GRPC_COMMIT}
git submodule update --init
export LDFLAGS="-Wl,-s"
make -j${NUM_CORES}
sudo make install
sudo ldconfig
unset LDFLAGS
cd ..
# Install gRPC Python Package
sudo pip install grpcio
# BMv2 deps (needed by PI)
git clone https://github.com/p4lang/behavioral-model.git
cd behavioral-model
git checkout ${BMV2_COMMIT}
# From bmv2's install_deps.sh, we can skip apt-get install.
# Nanomsg is required by p4runtime, p4runtime is needed by BMv2...
tmpdir=`mktemp -d -p .`
cd ${tmpdir}
bash ../travis/install-thrift.sh
bash ../travis/install-nanomsg.sh
sudo ldconfig
bash ../travis/install-nnpy.sh
cd ..
sudo rm -rf $tmpdir
cd ..
# PI/P4Runtime
git clone https://github.com/p4lang/PI.git
cd PI
git checkout ${PI_COMMIT}
git submodule update --init --recursive
./autogen.sh
./configure --with-proto
make -j${NUM_CORES}
sudo make install
sudo ldconfig
cd ..
# Bmv2
cd behavioral-model
./autogen.sh
./configure --enable-debugger --with-pi
make -j${NUM_CORES}
sudo make install
sudo ldconfig
# Simple_switch_grpc target
cd targets/simple_switch_grpc
./autogen.sh
./configure
make -j${NUM_CORES}
sudo make install
sudo ldconfig
cd ..
cd ..
cd ..
# P4C
git clone https://github.com/p4lang/p4c
cd p4c
git checkout ${P4C_COMMIT}
git submodule update --init --recursive
mkdir -p build
cd build
cmake ..
make -j${NUM_CORES}
sudo make install
sudo ldconfig
cd ..
cd ..
# Tutorials
sudo pip install crcmod
git clone https://github.com/p4lang/tutorials
sudo mv tutorials /home/p4
sudo chown -R p4:p4 /home/p4/tutorials
# Emacs
sudo cp p4_16-mode.el /usr/share/emacs/site-lisp/
echo "(add-to-list 'auto-mode-alist '(\"\\.p4\\'\" . p4_16-mode))" | sudo tee /home/p4/.emacs
sudo chown p4:p4 /home/p4/.emacs
# Vim
cd /home/vagrant
mkdir .vim
cd .vim
mkdir ftdetect
mkdir syntax
echo "au BufRead,BufNewFile *.p4 set filetype=p4" >> ftdetect/p4.vim
echo "set bg=dark" >> /home/p4/.vimrc
cp /home/vagrant/p4.vim syntax/p4.vim
cd /home/vagrant
sudo mv .vim /home/p4/.vim
sudo chown -R p4:p4 /home/p4/.vim
sudo chown p4:p4 /home/p4/.vimrc
# Adding Desktop icons
DESKTOP=/home/${USER}/Desktop
mkdir -p ${DESKTOP}
cat > ${DESKTOP}/Terminal << EOF
[Desktop Entry]
Encoding=UTF-8
Type=Application
Name=Terminal
Name[en_US]=Terminal
Icon=konsole
Exec=/usr/bin/x-terminal-emulator
Comment[en_US]=
EOF
cat > ${DESKTOP}/Wireshark << EOF
[Desktop Entry]
Encoding=UTF-8
Type=Application
Name=Wireshark
Name[en_US]=Wireshark
Icon=wireshark
Exec=/usr/bin/wireshark
Comment[en_US]=
EOF
cat > ${DESKTOP}/Sublime\ Text << EOF
[Desktop Entry]
Encoding=UTF-8
Type=Application
Name=Sublime Text
Name[en_US]=Sublime Text
Icon=sublime-text
Exec=/opt/sublime_text/sublime_text
Comment[en_US]=
EOF

View File

@ -1,63 +0,0 @@
# 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 clone 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
```

View File

@ -1,174 +0,0 @@
# 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 `set_dst_info`) 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 `set_dst_info` 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).

View File

@ -1,243 +0,0 @@
/* -*- 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(
inout 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 */
action set_dst_info(mac_addr_t mac_da,
mac_addr_t mac_sa,
port_id_t egress_port)
{
/*
* TODO: add logic to store mac addresses and
* egress ports in meta data
*/
}
table ipv4_lpm {
key = { meta.dst_ipv4 : lpm; }
actions = { set_dst_info; 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;

View File

@ -1,10 +0,0 @@
{
"program": "arp.p4",
"language": "p4-16",
"targets": {
"mininet": {
"num-hosts": 2,
"switch-config": "simple_router.config"
}
}
}

View File

@ -1,5 +0,0 @@
P4APPRUNNER=../../utils/p4apprunner.py
mkdir -p build
tar -czf build/p4app.tgz * --exclude='build'
#cd build
sudo python $P4APPRUNNER p4app.tgz --build-dir ./build

View File

@ -1 +0,0 @@
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

View File

@ -1,305 +0,0 @@
/* -*- 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(
inout 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;

View File

@ -1,115 +0,0 @@
# 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.

View File

@ -1,127 +0,0 @@
/* -*- 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(
inout 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;

View File

@ -1,95 +0,0 @@
#!/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()

View File

@ -1,10 +0,0 @@
{
"program": "calc.p4",
"language": "p4-16",
"targets": {
"mininet": {
"num-hosts": 2,
"switch-config": "simple_router.config"
}
}
}

View File

@ -1,5 +0,0 @@
P4APPRUNNER=../../utils/p4apprunner.py
mkdir -p build
tar -czf build/p4app.tgz * --exclude='build'
#cd build
sudo python $P4APPRUNNER p4app.tgz --build-dir ./build

View File

@ -1,263 +0,0 @@
/* -*- 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 = 0x7c; // '|'
const bit<8> P4CALC_CARET = 0x5e; // '^'
header p4calc_t {
bit<8> p;
bit<8> four;
bit<8> ver;
bit<8> op;
bit<32> operand_a;
bit<32> operand_b;
bit<32> res;
}
/*
* All headers, used in the program needs to be assembed into a single struct.
* We only need to declare the type, but there is no need to instantiate it,
* because it is done "by the architecture", i.e. outside of P4 functions
*/
struct 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(
inout 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;

View File

@ -1,156 +0,0 @@
# 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).

View File

@ -1,170 +0,0 @@
/* -*- 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(inout headers hdr, inout metadata meta) {
apply { }
}
/*************************************************************************
************** I N G R E S S P R O C E S S I N G *******************
*************************************************************************/
control 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;

View File

@ -1,33 +0,0 @@
{
"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"
}
}
}
}
}

View File

@ -1,50 +0,0 @@
#!/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()

View File

@ -1,5 +0,0 @@
P4APPRUNNER=../../utils/p4apprunner.py
mkdir -p build
tar -czf build/p4app.tgz * --exclude='build'
#cd build
sudo python $P4APPRUNNER p4app.tgz --build-dir ./build

View File

@ -1,4 +0,0 @@
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

View File

@ -1,4 +0,0 @@
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

View File

@ -1,4 +0,0 @@
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

View File

@ -1,40 +0,0 @@
#!/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()

View File

@ -1,176 +0,0 @@
/* -*- 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(inout headers hdr, inout metadata meta) {
apply { }
}
/*************************************************************************
************** I N G R E S S P R O C E S S I N G *******************
*************************************************************************/
control 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)
{
apply {
update_checksum(true,
{ hdr.ipv4.version,
hdr.ipv4.ihl,
hdr.ipv4.diffserv,
hdr.ipv4.totalLen,
hdr.ipv4.identification,
hdr.ipv4.flags,
hdr.ipv4.fragOffset,
hdr.ipv4.ttl,
hdr.ipv4.protocol,
hdr.ipv4.srcAddr,
hdr.ipv4.dstAddr
},
hdr.ipv4.hdrChecksum, HashAlgorithm.csum16);
}
}
/*************************************************************************
*********************** D E P A R S E R *******************************
*************************************************************************/
control 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;

View File

@ -1,206 +0,0 @@
# 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).

View File

@ -1,286 +0,0 @@
/* -*- 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(inout headers hdr, inout metadata meta) {
apply { }
}
/*************************************************************************
************** I N G R E S S P R O C E S S I N G *******************
*************************************************************************/
control 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;

View File

@ -1,33 +0,0 @@
{
"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"
}
}
}
}
}

View File

@ -1,50 +0,0 @@
#!/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()

View File

@ -1,5 +0,0 @@
P4APPRUNNER=../../utils/p4apprunner.py
mkdir -p build
tar -czf build/p4app.tgz * --exclude='build'
#cd build
sudo python $P4APPRUNNER p4app.tgz --build-dir ./build

View File

@ -1,5 +0,0 @@
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

View File

@ -1,5 +0,0 @@
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

View File

@ -1,5 +0,0 @@
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

View File

@ -1,58 +0,0 @@
#!/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()

View File

@ -1,279 +0,0 @@
/* -*- 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(inout headers hdr, inout metadata meta) {
apply { }
}
/*************************************************************************
************** I N G R E S S P R O C E S S I N G *******************
*************************************************************************/
control 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);
// According to the P4_16 spec, pushed elements are invalid, so we need
// to call setValid(). Older bmv2 versions would mark the new header(s)
// valid automatically (P4_14 behavior), but starting with version 1.11,
// bmv2 conforms with the P4_16 spec.
hdr.swids[0].setValid();
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)
{
apply {
update_checksum(true,
{ hdr.ipv4.version,
hdr.ipv4.ihl,
hdr.ipv4.diffserv,
hdr.ipv4.totalLen,
hdr.ipv4.identification,
hdr.ipv4.flags,
hdr.ipv4.fragOffset,
hdr.ipv4.ttl,
hdr.ipv4.protocol,
hdr.ipv4.srcAddr,
hdr.ipv4.dstAddr
},
hdr.ipv4.hdrChecksum, HashAlgorithm.csum16);
}
}
/*************************************************************************
*********************** D E P A R S E R *******************************
*************************************************************************/
control 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;

View File

@ -1,103 +0,0 @@
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

View File

@ -1,69 +0,0 @@
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]

View File

@ -1,242 +0,0 @@
#!/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()

View File

@ -1,161 +0,0 @@
# 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)

View File

@ -1,78 +0,0 @@
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

View File

@ -1,133 +0,0 @@
#!/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()

View File

@ -1,320 +0,0 @@
#!/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()

Binary file not shown.

View File

@ -1,49 +0,0 @@
# P4 Tutorial
## Introduction
Welcome to the P4 Tutorial!
We've prepared a set of exercises to help you get started with P4
programming, organized into four modules:
1. Introduction and Language Basics
* [Basic Forwarding](./basic)
* [Basic Tunneling](./basic_tunnel)
2. P4 Runtime and the Control Plane
* [P4 Runtime](./p4runtime)
3. Monitoring and Debugging
* [Explicit Congestion Notification](./ecn)
* [Multi-Hop Route Inspection](./mri)
4. Advanced Data Structures
* [Source Routing](./source_routing)
* [Calculator](./calc)
5. Dynamic Behavior
* [Load Balancing](./load_balance)
## Obtaining required software
If you are starting this tutorial at the Fall 2017 P4 Developer Day, then we've already
provided you with a virtual machine that has all of the required
software installed.
Otherwise, to complete the exercises, you will need to either build a
virtual machine or install several dependencies.
To build the virtual machine:
- Install [Vagrant](https://vagrantup.com) and [VirtualBox](https://virtualbox.org)
- `cd vm`
- `vagrant up`
- Log in with username `p4` and password `p4` and issue the command `sudo shutdown -r now`
- When the machine reboots, you should have a graphical desktop machine with the required
software pre-installed.
To install dependencies by hand, please reference the [vm](../vm) installation scripts.
They contain the dependencies, versions, and installation procedure.
You can run them directly on an Ubuntu 16.04 machine:
- `sudo ./root-bootstrap.sh`
- `sudo ./user-bootstrap.sh`

View File

@ -1 +0,0 @@
include ../../utils/Makefile

View File

@ -1,162 +0,0 @@
/* -*- 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 MyParser(packet_in packet,
out headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
state start {
/* TODO: add parser logic */
transition accept;
}
}
/*************************************************************************
************ C H E C K S U M V E R I F I C A T I O N *************
*************************************************************************/
control MyVerifyChecksum(inout headers hdr, inout metadata meta) {
apply { }
}
/*************************************************************************
************** I N G R E S S P R O C E S S I N G *******************
*************************************************************************/
control MyIngress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
action drop() {
mark_to_drop();
}
action ipv4_forward(macAddr_t dstAddr, egressSpec_t port) {
/* TODO: fill out code in action body */
}
table ipv4_lpm {
key = {
hdr.ipv4.dstAddr: lpm;
}
actions = {
ipv4_forward;
drop;
NoAction;
}
size = 1024;
default_action = NoAction();
}
apply {
/* TODO: fix ingress control logic
* - ipv4_lpm should be applied only when IPv4 header is valid
*/
ipv4_lpm.apply();
}
}
/*************************************************************************
**************** E G R E S S P R O C E S S I N G *******************
*************************************************************************/
control MyEgress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
apply { }
}
/*************************************************************************
************* C H E C K S U M C O M P U T A T I O N **************
*************************************************************************/
control MyComputeChecksum(inout headers hdr, inout metadata meta) {
apply {
update_checksum(
hdr.ipv4.isValid(),
{ hdr.ipv4.version,
hdr.ipv4.ihl,
hdr.ipv4.diffserv,
hdr.ipv4.totalLen,
hdr.ipv4.identification,
hdr.ipv4.flags,
hdr.ipv4.fragOffset,
hdr.ipv4.ttl,
hdr.ipv4.protocol,
hdr.ipv4.srcAddr,
hdr.ipv4.dstAddr },
hdr.ipv4.hdrChecksum,
HashAlgorithm.csum16);
}
}
/*************************************************************************
*********************** D E P A R S E R *******************************
*************************************************************************/
control MyDeparser(packet_out packet, in headers hdr) {
apply {
/* TODO: add deparser logic */
}
}
/*************************************************************************
*********************** S W I T C H *******************************
*************************************************************************/
V1Switch(
MyParser(),
MyVerifyChecksum(),
MyIngress(),
MyEgress(),
MyComputeChecksum(),
MyDeparser()
) main;

View File

@ -1,53 +0,0 @@
#!/usr/bin/env python
import sys
import struct
import os
from scapy.all import sniff, sendp, hexdump, get_if_list, get_if_hwaddr
from scapy.all import Packet, IPOption
from scapy.all import ShortField, IntField, LongField, BitField, FieldListField, FieldLenField
from scapy.all import IP, TCP, UDP, Raw
from scapy.layers.inet import _IPOption_HDR
def get_if():
ifs=get_if_list()
iface=None
for i in get_if_list():
if "eth0" in i:
iface=i
break;
if not iface:
print "Cannot find eth0 interface"
exit(1)
return iface
class IPOption_MRI(IPOption):
name = "MRI"
option = 31
fields_desc = [ _IPOption_HDR,
FieldLenField("length", None, fmt="B",
length_of="swids",
adjust=lambda pkt,l:l+4),
ShortField("count", 0),
FieldListField("swids",
[],
IntField("", 0),
length_from=lambda pkt:pkt.count*4) ]
def handle_pkt(pkt):
if TCP in pkt and pkt[TCP].dport == 1234:
print "got a packet"
pkt.show2()
# hexdump(pkt)
sys.stdout.flush()
def main():
ifaces = filter(lambda i: 'eth' in i, os.listdir('/sys/class/net/'))
iface = ifaces[0]
print "sniffing on %s" % iface
sys.stdout.flush()
sniff(iface = iface,
prn = lambda x: handle_pkt(x))
if __name__ == '__main__':
main()

View File

@ -1,4 +0,0 @@
table_set_default ipv4_lpm drop
table_add ipv4_lpm ipv4_forward 10.0.1.1/32 => 00:00:00:00:01:01 1
table_add ipv4_lpm ipv4_forward 10.0.2.2/32 => 00:00:00:02:02:00 2
table_add ipv4_lpm ipv4_forward 10.0.3.3/32 => 00:00:00:03:03:00 3

View File

@ -1,4 +0,0 @@
table_set_default ipv4_lpm drop
table_add ipv4_lpm ipv4_forward 10.0.1.1/32 => 00:00:00:01:02:00 2
table_add ipv4_lpm ipv4_forward 10.0.2.2/32 => 00:00:00:00:02:02 1
table_add ipv4_lpm ipv4_forward 10.0.3.3/32 => 00:00:00:03:03:00 3

View File

@ -1,4 +0,0 @@
table_set_default ipv4_lpm drop
table_add ipv4_lpm ipv4_forward 10.0.1.1/32 => 00:00:00:01:03:00 2
table_add ipv4_lpm ipv4_forward 10.0.2.2/32 => 00:00:00:02:03:00 3
table_add ipv4_lpm ipv4_forward 10.0.3.3/32 => 00:00:00:00:03:03 1

View File

@ -1,41 +0,0 @@
#!/usr/bin/env python
import argparse
import sys
import socket
import random
import struct
from scapy.all import sendp, send, get_if_list, get_if_hwaddr
from scapy.all import Packet
from scapy.all import Ether, IP, UDP, TCP
def get_if():
ifs=get_if_list()
iface=None # "h1-eth0"
for i in get_if_list():
if "eth0" in i:
iface=i
break;
if not iface:
print "Cannot find eth0 interface"
exit(1)
return iface
def main():
if len(sys.argv)<3:
print 'pass 2 arguments: <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')
pkt = pkt /IP(dst=addr) / TCP(dport=1234, sport=random.randint(49152,65535)) / sys.argv[2]
pkt.show2()
sendp(pkt, iface=iface, verbose=False)
if __name__ == '__main__':
main()

View File

@ -1,176 +0,0 @@
/* -*- 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 MyParser(packet_in packet,
out headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
state start {
transition parse_ethernet;
}
state parse_ethernet {
packet.extract(hdr.ethernet);
transition select(hdr.ethernet.etherType) {
TYPE_IPV4: parse_ipv4;
default: accept;
}
}
state parse_ipv4 {
packet.extract(hdr.ipv4);
transition accept;
}
}
/*************************************************************************
************ C H E C K S U M V E R I F I C A T I O N *************
*************************************************************************/
control MyVerifyChecksum(inout headers hdr, inout metadata meta) {
apply { }
}
/*************************************************************************
************** I N G R E S S P R O C E S S I N G *******************
*************************************************************************/
control MyIngress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
action drop() {
mark_to_drop();
}
action ipv4_forward(macAddr_t dstAddr, egressSpec_t port) {
standard_metadata.egress_spec = port;
hdr.ethernet.srcAddr = hdr.ethernet.dstAddr;
hdr.ethernet.dstAddr = dstAddr;
hdr.ipv4.ttl = hdr.ipv4.ttl - 1;
}
table ipv4_lpm {
key = {
hdr.ipv4.dstAddr: lpm;
}
actions = {
ipv4_forward;
drop;
NoAction;
}
size = 1024;
default_action = NoAction();
}
apply {
if (hdr.ipv4.isValid()) {
ipv4_lpm.apply();
}
}
}
/*************************************************************************
**************** E G R E S S P R O C E S S I N G *******************
*************************************************************************/
control MyEgress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
apply { }
}
/*************************************************************************
************* C H E C K S U M C O M P U T A T I O N **************
*************************************************************************/
control MyComputeChecksum(inout headers hdr, inout metadata meta) {
apply {
update_checksum(
hdr.ipv4.isValid(),
{ hdr.ipv4.version,
hdr.ipv4.ihl,
hdr.ipv4.diffserv,
hdr.ipv4.totalLen,
hdr.ipv4.identification,
hdr.ipv4.flags,
hdr.ipv4.fragOffset,
hdr.ipv4.ttl,
hdr.ipv4.protocol,
hdr.ipv4.srcAddr,
hdr.ipv4.dstAddr },
hdr.ipv4.hdrChecksum,
HashAlgorithm.csum16);
}
}
/*************************************************************************
*********************** D E P A R S E R *******************************
*************************************************************************/
control MyDeparser(packet_out packet, in headers hdr) {
apply {
packet.emit(hdr.ethernet);
packet.emit(hdr.ipv4);
}
}
/*************************************************************************
*********************** S W I T C H *******************************
*************************************************************************/
V1Switch(
MyParser(),
MyVerifyChecksum(),
MyIngress(),
MyEgress(),
MyComputeChecksum(),
MyDeparser()
) main;

View File

@ -1,16 +0,0 @@
{
"hosts": [
"h1",
"h2",
"h3"
],
"switches": {
"s1": { "cli_input" : "s1-commands.txt" },
"s2": { "cli_input" : "s2-commands.txt" },
"s3": { "cli_input" : "s3-commands.txt" }
},
"links": [
["h1", "s1"], ["s1", "s2"], ["s1", "s3"],
["s3", "s2"], ["s2", "h2"], ["s3", "h3"]
]
}

View File

@ -1 +0,0 @@
include ../../utils/Makefile

View File

@ -1,20 +0,0 @@
from scapy.all import *
import sys, os
TYPE_MYTUNNEL = 0x1212
TYPE_IPV4 = 0x0800
class MyTunnel(Packet):
name = "MyTunnel"
fields_desc = [
ShortField("pid", 0),
ShortField("dst_id", 0)
]
def mysummary(self):
return self.sprintf("pid=%pid%, dst_id=%dst_id%")
bind_layers(Ether, MyTunnel, type=TYPE_MYTUNNEL)
bind_layers(MyTunnel, IP, pid=TYPE_IPV4)

View File

@ -1,43 +0,0 @@
#!/usr/bin/env python
import sys
import struct
import os
from scapy.all import sniff, sendp, hexdump, get_if_list, get_if_hwaddr
from scapy.all import Packet, IPOption
from scapy.all import ShortField, IntField, LongField, BitField, FieldListField, FieldLenField
from scapy.all import IP, TCP, UDP, Raw
from scapy.layers.inet import _IPOption_HDR
from myTunnel_header import MyTunnel
def get_if():
ifs=get_if_list()
iface=None
for i in get_if_list():
if "eth0" in i:
iface=i
break;
if not iface:
print "Cannot find eth0 interface"
exit(1)
return iface
def handle_pkt(pkt):
if MyTunnel in pkt or (TCP in pkt and pkt[TCP].dport == 1234):
print "got a packet"
pkt.show2()
# hexdump(pkt)
# print "len(pkt) = ", len(pkt)
sys.stdout.flush()
def main():
ifaces = filter(lambda i: 'eth' in i, os.listdir('/sys/class/net/'))
iface = ifaces[0]
print "sniffing on %s" % iface
sys.stdout.flush()
sniff(iface = iface,
prn = lambda x: handle_pkt(x))
if __name__ == '__main__':
main()

View File

@ -1,9 +0,0 @@
table_set_default ipv4_lpm drop
table_add ipv4_lpm ipv4_forward 10.0.1.1/32 => 00:00:00:00:01:01 1
table_add ipv4_lpm ipv4_forward 10.0.2.2/32 => 00:00:00:02:01:00 2
table_add ipv4_lpm ipv4_forward 10.0.3.3/32 => 00:00:00:03:01:00 3
table_set_default myTunnel_exact drop
table_add myTunnel_exact myTunnel_forward 1 => 1
table_add myTunnel_exact myTunnel_forward 2 => 2
table_add myTunnel_exact myTunnel_forward 3 => 3

Some files were not shown because too many files have changed in this diff Show More