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:
parent
e7e6899d5c
commit
dc08948a34
1
.gitignore
vendored
1
.gitignore
vendored
@ -9,6 +9,7 @@
|
||||
*.json
|
||||
!*p4app.json
|
||||
!topology.json
|
||||
!*-runtime.json
|
||||
|
||||
*.pcap
|
||||
|
||||
|
@ -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`
|
@ -1 +0,0 @@
|
||||
include ../../utils/Makefile
|
@ -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)!
|
||||
|
@ -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
|
@ -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
|
@ -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
|
@ -1 +0,0 @@
|
||||
include ../../utils/Makefile
|
@ -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.
|
||||
|
||||

|
||||
|
||||
## 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)!
|
||||
|
@ -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;
|
@ -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
|
@ -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
|
@ -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
|
@ -1 +0,0 @@
|
||||
include ../../utils/Makefile
|
@ -1,12 +0,0 @@
|
||||
{
|
||||
"hosts": [
|
||||
"h1",
|
||||
"h2"
|
||||
],
|
||||
"switches": {
|
||||
"s1": { "cli_input" : "s1-commands.txt" }
|
||||
},
|
||||
"links": [
|
||||
["h1", "s1"], ["h2", "s1"]
|
||||
]
|
||||
}
|
@ -1 +0,0 @@
|
||||
include ../../utils/Makefile
|
@ -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;
|
@ -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
|
@ -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
|
@ -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
|
@ -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"]
|
||||
]
|
||||
}
|
@ -1 +0,0 @@
|
||||
include ../../utils/Makefile
|
@ -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
|
@ -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
|
@ -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
|
@ -1 +0,0 @@
|
||||
include ../../utils/Makefile
|
@ -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;
|
@ -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
|
@ -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
|
@ -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
|
@ -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)
|
@ -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
|
@ -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
|
@ -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)
|
@ -1 +0,0 @@
|
||||
include ../../utils/Makefile
|
@ -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).
|
@ -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()
|
||||
|
22
P4D2_2017_Fall/vm/Vagrantfile
vendored
22
P4D2_2017_Fall/vm/Vagrantfile
vendored
@ -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
|
@ -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
|
@ -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
|
@ -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
|
||||
```
|
||||
|
@ -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).
|
@ -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;
|
@ -1,10 +0,0 @@
|
||||
{
|
||||
"program": "arp.p4",
|
||||
"language": "p4-16",
|
||||
"targets": {
|
||||
"mininet": {
|
||||
"num-hosts": 2,
|
||||
"switch-config": "simple_router.config"
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
@ -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
|
@ -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;
|
@ -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.
|
||||
|
@ -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;
|
@ -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()
|
@ -1,10 +0,0 @@
|
||||
{
|
||||
"program": "calc.p4",
|
||||
"language": "p4-16",
|
||||
"targets": {
|
||||
"mininet": {
|
||||
"num-hosts": 2,
|
||||
"switch-config": "simple_router.config"
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
@ -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;
|
@ -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).
|
@ -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;
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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()
|
@ -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;
|
@ -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).
|
||||
|
||||
|
||||
|
@ -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;
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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()
|
@ -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;
|
@ -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
|
@ -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]
|
||||
|
@ -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()
|
@ -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)
|
@ -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
|
||||
|
@ -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()
|
@ -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.
@ -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`
|
@ -1 +0,0 @@
|
||||
include ../../utils/Makefile
|
@ -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;
|
@ -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()
|
@ -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
|
@ -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
|
@ -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
|
@ -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()
|
@ -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;
|
@ -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"]
|
||||
]
|
||||
}
|
@ -1 +0,0 @@
|
||||
include ../../utils/Makefile
|
@ -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)
|
||||
|
@ -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()
|
@ -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
Loading…
x
Reference in New Issue
Block a user