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

View File

@@ -0,0 +1,170 @@
/* -*- P4_16 -*- */
#include <core.p4>
#include <v1model.p4>
const bit<16> TYPE_IPV4 = 0x800;
/*************************************************************************
*********************** H E A D E R S ***********************************
*************************************************************************/
typedef bit<9> egressSpec_t;
typedef bit<48> macAddr_t;
typedef bit<32> ip4Addr_t;
header ethernet_t {
macAddr_t dstAddr;
macAddr_t srcAddr;
bit<16> etherType;
}
header ipv4_t {
bit<4> version;
bit<4> ihl;
bit<8> diffserv;
bit<16> totalLen;
bit<16> identification;
bit<3> flags;
bit<13> fragOffset;
bit<8> ttl;
bit<8> protocol;
bit<16> hdrChecksum;
ip4Addr_t srcAddr;
ip4Addr_t dstAddr;
}
struct metadata {
/* empty */
}
struct headers {
ethernet_t ethernet;
ipv4_t ipv4;
}
/*************************************************************************
*********************** P A R S E R ***********************************
*************************************************************************/
parser ParserImpl(packet_in packet,
out headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
state start {
/* TODO: add transition to parsing ethernet */
transition accept;
}
state parse_ethernet {
/* TODO: add parsing ethernet */
}
state parse_ipv4 {
/* TODO: add parsing ipv4 */
}
}
/*************************************************************************
************ C H E C K S U M V E R I F I C A T I O N *************
*************************************************************************/
control verifyChecksum(inout headers hdr, inout metadata meta) {
apply { }
}
/*************************************************************************
************** I N G R E S S P R O C E S S I N G *******************
*************************************************************************/
control ingress(inout headers hdr, inout metadata meta, inout standard_metadata_t standard_metadata) {
/* This action will drop packets */
action drop() {
mark_to_drop();
}
action ipv4_forward(macAddr_t dstAddr, egressSpec_t port) {
/*
* TODO: Implement the logic to:
* 1. Set the standard_metadata.egress_spec to the output port.
* 2. Set the ethernet srcAddr to the ethernet dstAddr.
* 3. Set the ethernet dstAddr to the dstAddr passed as a parameter.
* 4. Decrement the IP TTL.
* BONUS: Handle the case where TTL is 0.
*/
}
table ipv4_lpm {
key = {
/* TODO: declare that the table will do a longest-prefix match (lpm)
on the IP destination address. */
}
actions = {
/* TODO: declare the possible actions: ipv4_forward or drop. */
NoAction;
}
size = 1024;
default_action = NoAction();
}
apply {
/* TODO: replace drop with logic to:
* 1. Check if the ipv4 header is valid.
* 2. apply the table ipv4_lpm.
*/
drop();
}
}
/*************************************************************************
**************** E G R E S S P R O C E S S I N G *******************
*************************************************************************/
control egress(inout headers hdr, inout metadata meta, inout standard_metadata_t standard_metadata) {
apply { }
}
/*************************************************************************
************* C H E C K S U M C O M P U T A T I O N **************
*************************************************************************/
control computeChecksum(
inout headers hdr,
inout metadata meta)
{
/*
* Ignore checksum for now. The reference solution contains a checksum
* implementation.
*/
apply { }
}
/*************************************************************************
*********************** D E P A R S E R *******************************
*************************************************************************/
control DeparserImpl(packet_out packet, in headers hdr) {
apply {
packet.emit(hdr.ethernet);
packet.emit(hdr.ipv4);
}
}
/*************************************************************************
*********************** S W I T C H *******************************
*************************************************************************/
V1Switch(
ParserImpl(),
verifyChecksum(),
ingress(),
egress(),
computeChecksum(),
DeparserImpl()
) main;

View File

@@ -0,0 +1,33 @@
{
"program": "ipv4_forward.p4",
"language": "p4-16",
"targets": {
"multiswitch": {
"auto-control-plane": true,
"cli": true,
"pcap_dump": true,
"bmv2_log": true,
"links": [["h1", "s1"], ["s1", "s2"], ["s1", "s3"], ["s3", "s2"], ["s2", "h2"], ["s3", "h3"]],
"hosts": {
"h1": {
},
"h2": {
},
"h3": {
}
},
"switches": {
"s1": {
"entries": "s1-commands.txt"
},
"s2": {
"entries": "s2-commands.txt"
},
"s3": {
"entries": "s3-commands.txt"
}
}
}
}
}

View File

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

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,4 @@
table_set_default ipv4_lpm drop
table_add ipv4_lpm ipv4_forward 10.0.1.10/32 => 00:aa:00:01:00:01 1
table_add ipv4_lpm ipv4_forward 10.0.2.10/32 => f2:ed:e6:df:4e:fa 2
table_add ipv4_lpm ipv4_forward 10.0.3.10/32 => f2:ed:e6:df:4e:fb 3

View File

@@ -0,0 +1,4 @@
table_set_default ipv4_lpm drop
table_add ipv4_lpm ipv4_forward 10.0.2.10/32 => 00:aa:00:02:00:02 1
table_add ipv4_lpm ipv4_forward 10.0.1.10/32 => 22:a8:04:41:ab:d3 2
table_add ipv4_lpm ipv4_forward 10.0.3.10/32 => 22:a8:04:41:ab:d4 3

View File

@@ -0,0 +1,4 @@
table_set_default ipv4_lpm drop
table_add ipv4_lpm ipv4_forward 10.0.3.10/32 => 00:aa:00:03:00:01 1
table_add ipv4_lpm ipv4_forward 10.0.1.10/32 => f2:ed:e6:df:4e:fb 2
table_add ipv4_lpm ipv4_forward 10.0.2.10/32 => f2:ed:e6:df:4e:fa 3

View File

@@ -0,0 +1,40 @@
#!/usr/bin/env python
import argparse
import sys
import socket
import random
import struct
from scapy.all import sendp, send, get_if_list, get_if_hwaddr
from scapy.all import Packet
from scapy.all import Ether, IP, UDP
def get_if():
ifs=get_if_list()
iface=None # "h1-eth0"
for i in get_if_list():
if "eth0" in i:
iface=i
break;
if not iface:
print "Cannot find eth0 interface"
exit(1)
return iface
def main():
if len(sys.argv)<3:
print 'pass 2 arguments: <destination> "<message>"'
exit(1)
addr = socket.gethostbyname(sys.argv[1])
iface = get_if()
print "sending on interface %s to %s" % (iface, str(addr))
pkt = Ether(src=get_if_hwaddr(iface), dst='ff:ff:ff:ff:ff:ff') / IP(dst=addr) / UDP(dport=4321, sport=1234) / sys.argv[2]
pkt.show2()
sendp(pkt, iface=iface, verbose=False)
if __name__ == '__main__':
main()

View File

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