Created new subdirectory for Fall developer day. (#60)

This commit is contained in:
Robert Soule
2017-10-19 12:06:37 -07:00
committed by GitHub
parent 28a8414c58
commit cf4885329d
130 changed files with 8004 additions and 0 deletions

View File

@@ -0,0 +1,174 @@
# Implementing an ARP/ICMP Responder
## Introduction
This exercise extends the [IPv4 Forwarding](../ipv4_forward) program to
allow your switches to respond to ARP and ICMP requests. Once implemented,
your hosts will be able to `ping` other hosts connected to the switch, and
have the switch respond.
This exercise makes several simplifying assumptions:
1. The network topology contains exactly one switch and two hosts.
1. ARP and ICMP requests to the hosts are ignored; only requests sent to the
switch receive responses.
Implementing the full functionality of ARP and ICMP is straightforward but
beyond the scope of this tutorial and left as an exercise to the reader.
> **Spoiler alert:** There is a reference solution in the `solution`
> sub-directory. Feel free to compare your implementation to the reference.
## Step 1: Run the (incomplete) starter code
The directory with this README also contains a skeleton P4 program,
`arp.p4`, which initially drops all packets. Your job will be to
extend it to reply to ARP and ICMP requests.
As a first step, compile the incomplete `arp.p4` and bring up a
switch in Mininet to test its behavior.
1. In your shell, run:
```bash
./run.sh
```
This will:
* compile `arp.p4`, and
* start a Mininet instance with one switch (`s1`) connected to two hosts (`h1`, `h2`).
* The hosts are assigned IPs of `10.0.1.10` and `10.0.2.10`.
2. Once the P4 source compiles without error, you can test your program at the
mininet prompt using the `ping` utility.
``` mininet> h1 ping h2 ```
Once the program is implemented correctly, you will see a response to the
ping in the mininet window.
3. Type `exit` in the mininet terminal to exit.
### A note about the control plane
P4 programs define a packet-processing pipeline, but the rules governing packet
processing are inserted into the pipeline by the control plane. When a rule
matches a packet, its action is invoked with parameters supplied by the control
plane as part of the rule.
In this exercise, the control plane logic has already been implemented. As
part of bringing up the Mininet instance, the `run.sh` script will install
packet-processing rules in the tables of each switch. These are defined in the
`simple_router.config` file.
## Step 2: Implement ARP/ICMP Replies
In this exercise, we are using entries in the `ipv4_lpm` table as a
database, which we can reference when responding to ARP and ICMP requests.
Without the proper entries in the table, the solution will not work.
From a high-level, the task involves implementing two main components: ARP
replies and ICMP replies.
### ARP Reply
When the switch receives and ARP request asking to resolve the switch's IP
address, it will need to perform the following actions:
1. Swap the source and destination MAC addresses in the Ethernet header,
1. set the ARP operation to ARP_REPLY (`2`) in the ARP header,
1. update the sender hardware address (SHA) and sender protocol address (SPA) in the ARP header to
be the MAC and IP addresses of the switch, and
1. set the target hardware address (THA) and target protocol address (TPA) to be the SHA
and SPA of the arriving ARP packet.
### ICMP Reply
When the switch receives and ICMP request containing the switch's IP and MAC
addresses, it will need to perform the following actions:
1. Swap the source and destination MAC addresses in the Ethernet header,
1. swap the source and destination IP addresses in the ICMP header, and
1. set the type field in the ICMP header to `ICMP_ECHO_REPLY` (`0`).
1. To simplify the exercise, we can ignore the checksum by setting to checksum
field to 0.
We have provided a skeleton `arp.p4` file to get you started. In this
file, places to modify are marked by `TODO`.
There are, of course, different possible solutions. We describe one approach
below. It builds on the [IPv4 Forwarding](../ipv4_forward) solution, which
used the table `ipv4_lpm` for L3 forwarding, by adding a second table named
`forward`, which checks if a packet is an ARP or ICMP packet and invokes
actions to send an ARP reply, forward an IPv4 packet, or send an ICMP reply.
Broadly speaking, a complete solution will contain the following components:
1. Header type definitions for `ethernet_t`, `arp_t`, `ipv4_t`, and `icmp_t`.
1. A structure (named `my_metadata_t` in `arp.p4`) with metadata fields for the
packet's souce and destination MAC addresses, IPv4 address, egress port, as
well as a hard-coded MAC address for the switch.
1. **TODO:** Parsers for Ethernet, ARP, IPv4, and ICMP packet header types.
1. **TODO:** A control type declaration for ingress processing, containing:
1. An action for `drop`.
1. An action (named `set_dst_info`) to store information in the metadata
structure, rather than immediately writing to the packet header.
1. A table (named `ipv4_lpm`) that will match on the destination IP address
and invoke the `set_dst_info` action.
1. An action to send an ICMP reply.
1. An action to send an ARP reply.
1. A table (named `forward`) that will forward IPv4 packets, send an ARP
reply, send an ICMP reply, or drop a packet.
1. An `apply` block that implements the control logic to invoke the two
tables.
1. A deparser that emits headers in the proper order.
To keep the exercise simple, we will ignore the `ipv4_checksum`. You should not
need any control plane rules for this exercise.
## Step 3: Run your solution
Follow the instructions from Step 1. This time, you should be able to
successfully `ping` the switch.
### Troubleshooting
There are several ways that problems might manifest:
1. `arp.p4` fails to compile. In this case, `run.sh` will report the
error emitted from the compiler and stop.
1. `arp.p4` compiles, but the switch does not process packets in the desired
way. The `build/logs/<switch-name>.log` files contain trace messages
describing how each switch processes each packet. The output is detailed and
can help pinpoint logic errors in your implementation.
> Note that there are no control plane rules installed in this example, and so
> the `receive.py` and `send.py` scripts from the [IPv4
> Forwarding](../ipv4_forward) example will not work.
#### Cleaning up Mininet
In the latter case above, `run.sh` may leave a Mininet instance running in
the background. Use the following command to clean up these instances:
```bash
mn -c
```
## Next Steps
Congratulations, your implementation works! Move on to the next exercise:
turning your switch into a [Calculator](../calc).

View File

@@ -0,0 +1,243 @@
/* -*- P4_16 -*- */
#include <core.p4>
#include <v1model.p4>
/*************************************************************************
*********************** C O N S T A N T S *****************************
*************************************************************************/
/* Define the useful global constants for your program */
const bit<16> ETHERTYPE_IPV4 = 0x0800;
const bit<16> ETHERTYPE_ARP = 0x0806;
const bit<8> IPPROTO_ICMP = 0x01;
/*************************************************************************
*********************** H E A D E R S *********************************
*************************************************************************/
/* Define the headers the program will recognize */
/*
* Standard ethernet header
*/
typedef bit<48> mac_addr_t;
typedef bit<32> ipv4_addr_t;
typedef bit<9> port_id_t;
header ethernet_t {
mac_addr_t dstAddr;
mac_addr_t srcAddr;
bit<16> etherType;
}
header ipv4_t {
bit<4> version;
bit<4> ihl;
bit<8> diffserv;
bit<16> totalLen;
bit<16> identification;
bit<3> flags;
bit<13> fragOffset;
bit<8> ttl;
bit<8> protocol;
bit<16> hdrChecksum;
ipv4_addr_t srcAddr;
ipv4_addr_t dstAddr;
}
const bit<16> ARP_HTYPE_ETHERNET = 0x0001;
const bit<16> ARP_PTYPE_IPV4 = 0x0800;
const bit<8> ARP_HLEN_ETHERNET = 6;
const bit<8> ARP_PLEN_IPV4 = 4;
const bit<16> ARP_OPER_REQUEST = 1;
const bit<16> ARP_OPER_REPLY = 2;
header arp_t {
bit<16> htype;
bit<16> ptype;
bit<8> hlen;
bit<8> plen;
bit<16> oper;
}
header arp_ipv4_t {
mac_addr_t sha;
ipv4_addr_t spa;
mac_addr_t tha;
ipv4_addr_t tpa;
}
const bit<8> ICMP_ECHO_REQUEST = 8;
const bit<8> ICMP_ECHO_REPLY = 0;
header icmp_t {
bit<8> type;
bit<8> code;
bit<16> checksum;
}
/* Assemble headers in a single struct */
struct my_headers_t {
ethernet_t ethernet;
arp_t arp;
arp_ipv4_t arp_ipv4;
ipv4_t ipv4;
icmp_t icmp;
}
/*************************************************************************
*********************** M E T A D A T A *******************************
*************************************************************************/
/* Define the global metadata for your program */
struct my_metadata_t {
ipv4_addr_t dst_ipv4;
mac_addr_t mac_da;
mac_addr_t mac_sa;
port_id_t egress_port;
mac_addr_t my_mac;
}
/*************************************************************************
*********************** P A R S E R ***********************************
*************************************************************************/
parser MyParser(
packet_in packet,
out my_headers_t hdr,
inout my_metadata_t meta,
inout standard_metadata_t standard_metadata)
{
state start {
packet.extract(hdr.ethernet);
transition select(hdr.ethernet.etherType) {
ETHERTYPE_IPV4 : parse_ipv4;
ETHERTYPE_ARP : parse_arp;
default : accept;
}
}
state parse_arp {
/*
* TODO: parse ARP. If the ARP protocol field is
* IPV4, transtion to parse_arp_ipv4
*/
transition accept;
}
state parse_arp_ipv4 {
/*
* TODO: parse ARP_IPV4. Hint: one
* possible solution is to store the
* target packet address in a meta data
* field when parsing.
*/
transition accept;
}
state parse_ipv4 {
packet.extract(hdr.ipv4);
meta.dst_ipv4 = hdr.ipv4.dstAddr;
transition select(hdr.ipv4.protocol) {
IPPROTO_ICMP : parse_icmp;
default : accept;
}
}
state parse_icmp {
/* TODO: parse ICMP */
transition accept;
}
}
/*************************************************************************
************ C H E C K S U M V E R I F I C A T I O N *************
*************************************************************************/
control MyVerifyChecksum(
inout my_headers_t hdr,
inout my_metadata_t meta)
{
apply { }
}
/*************************************************************************
************** I N G R E S S P R O C E S S I N G *******************
*************************************************************************/
control MyIngress(
inout my_headers_t hdr,
inout my_metadata_t meta,
inout standard_metadata_t standard_metadata)
{
action drop() {
mark_to_drop();
exit;
}
/* TODO: Define actions and tables here */
action set_dst_info(mac_addr_t mac_da,
mac_addr_t mac_sa,
port_id_t egress_port)
{
/*
* TODO: add logic to store mac addresses and
* egress ports in meta data
*/
}
table ipv4_lpm {
key = { meta.dst_ipv4 : lpm; }
actions = { set_dst_info; drop; }
default_action = drop();
}
apply {
meta.my_mac = 0x000102030405;
ipv4_lpm.apply();
/* TODO: add contol logic */
}
}
/*************************************************************************
**************** E G R E S S P R O C E S S I N G *******************
*************************************************************************/
control MyEgress(
inout my_headers_t hdr,
inout my_metadata_t meta,
inout standard_metadata_t standard_metadata) {
apply { }
}
/*************************************************************************
************* C H E C K S U M C O M P U T A T I O N **************
*************************************************************************/
control MyComputeChecksum(
inout my_headers_t hdr,
inout my_metadata_t meta)
{
apply { }
}
/*************************************************************************
*********************** D E P A R S E R *******************************
*************************************************************************/
control MyDeparser(
packet_out packet,
in my_headers_t hdr)
{
apply {
/* TODO: Implement deparser */
}
}
V1Switch(
MyParser(),
MyVerifyChecksum(),
MyIngress(),
MyEgress(),
MyComputeChecksum(),
MyDeparser()
) main;

View File

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

View File

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

View File

@@ -0,0 +1 @@
table_add ipv4_lpm set_dst_info 10.0.1.10/24 => 00:00:01:00:00:01 00:00:02:00:00:02 1

View File

@@ -0,0 +1,305 @@
/* -*- P4_16 -*- */
#include <core.p4>
#include <v1model.p4>
/*************************************************************************
*********************** C O N S T A N T S *****************************
*************************************************************************/
/* Define the useful global constants for your program */
const bit<16> ETHERTYPE_IPV4 = 0x0800;
const bit<16> ETHERTYPE_ARP = 0x0806;
const bit<8> IPPROTO_ICMP = 0x01;
/*************************************************************************
*********************** H E A D E R S *********************************
*************************************************************************/
/* Define the headers the program will recognize */
/*
* Standard ethernet header
*/
typedef bit<48> mac_addr_t;
typedef bit<32> ipv4_addr_t;
typedef bit<9> port_id_t;
header ethernet_t {
mac_addr_t dstAddr;
mac_addr_t srcAddr;
bit<16> etherType;
}
header ipv4_t {
bit<4> version;
bit<4> ihl;
bit<8> diffserv;
bit<16> totalLen;
bit<16> identification;
bit<3> flags;
bit<13> fragOffset;
bit<8> ttl;
bit<8> protocol;
bit<16> hdrChecksum;
ipv4_addr_t srcAddr;
ipv4_addr_t dstAddr;
}
const bit<16> ARP_HTYPE_ETHERNET = 0x0001;
const bit<16> ARP_PTYPE_IPV4 = 0x0800;
const bit<8> ARP_HLEN_ETHERNET = 6;
const bit<8> ARP_PLEN_IPV4 = 4;
const bit<16> ARP_OPER_REQUEST = 1;
const bit<16> ARP_OPER_REPLY = 2;
header arp_t {
bit<16> htype;
bit<16> ptype;
bit<8> hlen;
bit<8> plen;
bit<16> oper;
}
header arp_ipv4_t {
mac_addr_t sha;
ipv4_addr_t spa;
mac_addr_t tha;
ipv4_addr_t tpa;
}
const bit<8> ICMP_ECHO_REQUEST = 8;
const bit<8> ICMP_ECHO_REPLY = 0;
header icmp_t {
bit<8> type;
bit<8> code;
bit<16> checksum;
}
/* Assemble headers in a single struct */
struct my_headers_t {
ethernet_t ethernet;
arp_t arp;
arp_ipv4_t arp_ipv4;
ipv4_t ipv4;
icmp_t icmp;
}
/*************************************************************************
*********************** M E T A D A T A *******************************
*************************************************************************/
/* Define the global metadata for your program */
struct my_metadata_t {
ipv4_addr_t dst_ipv4;
mac_addr_t mac_da;
mac_addr_t mac_sa;
port_id_t egress_port;
mac_addr_t my_mac;
}
/*************************************************************************
*********************** P A R S E R ***********************************
*************************************************************************/
parser MyParser(
packet_in packet,
out my_headers_t hdr,
inout my_metadata_t meta,
inout standard_metadata_t standard_metadata)
{
state start {
packet.extract(hdr.ethernet);
transition select(hdr.ethernet.etherType) {
ETHERTYPE_IPV4 : parse_ipv4;
ETHERTYPE_ARP : parse_arp;
default : accept;
}
}
state parse_arp {
packet.extract(hdr.arp);
transition select(hdr.arp.htype, hdr.arp.ptype,
hdr.arp.hlen, hdr.arp.plen) {
(ARP_HTYPE_ETHERNET, ARP_PTYPE_IPV4,
ARP_HLEN_ETHERNET, ARP_PLEN_IPV4) : parse_arp_ipv4;
default : accept;
}
}
state parse_arp_ipv4 {
packet.extract(hdr.arp_ipv4);
meta.dst_ipv4 = hdr.arp_ipv4.tpa;
transition accept;
}
state parse_ipv4 {
packet.extract(hdr.ipv4);
meta.dst_ipv4 = hdr.ipv4.dstAddr;
transition select(hdr.ipv4.protocol) {
IPPROTO_ICMP : parse_icmp;
default : accept;
}
}
state parse_icmp {
packet.extract(hdr.icmp);
transition accept;
}
}
/*************************************************************************
************ C H E C K S U M V E R I F I C A T I O N *************
*************************************************************************/
control MyVerifyChecksum(
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;