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)
5
exercises/basic/Makefile
Normal file
@@ -0,0 +1,5 @@
|
||||
BMV2_SWITCH_EXE = simple_switch_grpc
|
||||
NO_P4 = true
|
||||
P4C_ARGS = --p4runtime-file $(basename $@).p4info --p4runtime-format text
|
||||
|
||||
include ../../utils/Makefile
|
||||
|
After Width: | Height: | Size: 154 B |
174
exercises/basic/README.md
Normal file
@@ -0,0 +1,174 @@
|
||||
# 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-runtime.json` files, where
|
||||
`X` corresponds to the switch number.
|
||||
|
||||
**Important:** We use P4Runtime to install the control plane rules. The
|
||||
content of files `sX-runtime.json` refer to specific names of tables, keys, and
|
||||
actions, as defined in the P4Info file produced by the compiler (look for the
|
||||
file `build/basic.p4info` after executing `make run`). Any changes in the P4
|
||||
program that add or rename tables, keys, or actions will need to be reflected in
|
||||
these `sX-runtime.json` 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-runtime.json` through `s3-runtime.json` files that
|
||||
`make run` tries to install using P4Runtime. In this case, `make run` will
|
||||
report errors if control plane rules cannot be installed. 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)!
|
||||
|
||||
162
exercises/basic/basic.p4
Normal file
@@ -0,0 +1,162 @@
|
||||
/* -*- 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;
|
||||
53
exercises/basic/receive.py
Executable file
@@ -0,0 +1,53 @@
|
||||
#!/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()
|
||||
46
exercises/basic/s1-runtime.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"target": "bmv2",
|
||||
"p4info": "build/basic.p4info",
|
||||
"bmv2_json": "build/basic.json",
|
||||
"table_entries": [
|
||||
{
|
||||
"table": "MyIngress.ipv4_lpm",
|
||||
"default_action": true,
|
||||
"action_name": "MyIngress.drop",
|
||||
"action_params": { }
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.ipv4_lpm",
|
||||
"match": {
|
||||
"hdr.ipv4.dstAddr": ["10.0.1.1", 32]
|
||||
},
|
||||
"action_name": "MyIngress.ipv4_forward",
|
||||
"action_params": {
|
||||
"dstAddr": "00:00:00:00:01:01",
|
||||
"port": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.ipv4_lpm",
|
||||
"match": {
|
||||
"hdr.ipv4.dstAddr": ["10.0.2.2", 32]
|
||||
},
|
||||
"action_name": "MyIngress.ipv4_forward",
|
||||
"action_params": {
|
||||
"dstAddr": "00:00:00:02:02:00",
|
||||
"port": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.ipv4_lpm",
|
||||
"match": {
|
||||
"hdr.ipv4.dstAddr": ["10.0.3.3", 32]
|
||||
},
|
||||
"action_name": "MyIngress.ipv4_forward",
|
||||
"action_params": {
|
||||
"dstAddr": "00:00:00:03:03:00",
|
||||
"port": 3
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
46
exercises/basic/s2-runtime.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"target": "bmv2",
|
||||
"p4info": "build/basic.p4info",
|
||||
"bmv2_json": "build/basic.json",
|
||||
"table_entries": [
|
||||
{
|
||||
"table": "MyIngress.ipv4_lpm",
|
||||
"default_action": true,
|
||||
"action_name": "MyIngress.drop",
|
||||
"action_params": { }
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.ipv4_lpm",
|
||||
"match": {
|
||||
"hdr.ipv4.dstAddr": ["10.0.1.1", 32]
|
||||
},
|
||||
"action_name": "MyIngress.ipv4_forward",
|
||||
"action_params": {
|
||||
"dstAddr": "00:00:00:01:02:00",
|
||||
"port": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.ipv4_lpm",
|
||||
"match": {
|
||||
"hdr.ipv4.dstAddr": ["10.0.2.2", 32]
|
||||
},
|
||||
"action_name": "MyIngress.ipv4_forward",
|
||||
"action_params": {
|
||||
"dstAddr": "00:00:00:00:02:02",
|
||||
"port": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.ipv4_lpm",
|
||||
"match": {
|
||||
"hdr.ipv4.dstAddr": ["10.0.3.3", 32]
|
||||
},
|
||||
"action_name": "MyIngress.ipv4_forward",
|
||||
"action_params": {
|
||||
"dstAddr": "00:00:00:03:03:00",
|
||||
"port": 3
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
46
exercises/basic/s3-runtime.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"target": "bmv2",
|
||||
"p4info": "build/basic.p4info",
|
||||
"bmv2_json": "build/basic.json",
|
||||
"table_entries": [
|
||||
{
|
||||
"table": "MyIngress.ipv4_lpm",
|
||||
"default_action": true,
|
||||
"action_name": "MyIngress.drop",
|
||||
"action_params": { }
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.ipv4_lpm",
|
||||
"match": {
|
||||
"hdr.ipv4.dstAddr": ["10.0.1.1", 32]
|
||||
},
|
||||
"action_name": "MyIngress.ipv4_forward",
|
||||
"action_params": {
|
||||
"dstAddr": "00:00:00:01:03:00",
|
||||
"port": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.ipv4_lpm",
|
||||
"match": {
|
||||
"hdr.ipv4.dstAddr": ["10.0.2.2", 32]
|
||||
},
|
||||
"action_name": "MyIngress.ipv4_forward",
|
||||
"action_params": {
|
||||
"dstAddr": "00:00:00:02:03:00",
|
||||
"port": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.ipv4_lpm",
|
||||
"match": {
|
||||
"hdr.ipv4.dstAddr": ["10.0.3.3", 32]
|
||||
},
|
||||
"action_name": "MyIngress.ipv4_forward",
|
||||
"action_params": {
|
||||
"dstAddr": "00:00:00:00:03:03",
|
||||
"port": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
41
exercises/basic/send.py
Executable file
@@ -0,0 +1,41 @@
|
||||
#!/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()
|
||||
176
exercises/basic/solution/basic.p4
Normal 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 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 = drop();
|
||||
}
|
||||
|
||||
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;
|
||||
16
exercises/basic/topology.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"hosts": [
|
||||
"h1",
|
||||
"h2",
|
||||
"h3"
|
||||
],
|
||||
"switches": {
|
||||
"s1": { "runtime_json" : "s1-runtime.json" },
|
||||
"s2": { "runtime_json" : "s2-runtime.json" },
|
||||
"s3": { "runtime_json" : "s3-runtime.json" }
|
||||
},
|
||||
"links": [
|
||||
["h1", "s1"], ["s1", "s2"], ["s1", "s3"],
|
||||
["s3", "s2"], ["s2", "h2"], ["s3", "h3"]
|
||||
]
|
||||
}
|
||||
5
exercises/basic_tunnel/Makefile
Normal file
@@ -0,0 +1,5 @@
|
||||
BMV2_SWITCH_EXE = simple_switch_grpc
|
||||
NO_P4 = true
|
||||
P4C_ARGS = --p4runtime-file $(basename $@).p4info --p4runtime-format text
|
||||
|
||||
include ../../utils/Makefile
|
||||
|
After Width: | Height: | Size: 154 B |
167
exercises/basic_tunnel/README.md
Normal file
@@ -0,0 +1,167 @@
|
||||
# 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-runtime.json` files,
|
||||
where `X` corresponds to the switch number.
|
||||
|
||||
**Important:** We use P4Runtime to install the control plane rules. The
|
||||
content of files `sX-runtime.json` refer to specific names of tables, keys, and
|
||||
actions, as defined in the P4Info file produced by the compiler (look for the
|
||||
file `build/basic.p4info` after executing `make run`). Any changes in the P4
|
||||
program that add or rename tables, keys, or actions will need to be reflected in
|
||||
these `sX-runtime.json` 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-runtime.json` 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 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 `sX-runtime.json` files that `make run` tries to install using
|
||||
the P4Runtime. In this case, `make run` will report errors if control plane
|
||||
rules cannot be installed. 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)!
|
||||
|
||||
197
exercises/basic_tunnel/basic_tunnel.p4
Normal file
@@ -0,0 +1,197 @@
|
||||
/* -*- 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 = drop();
|
||||
}
|
||||
|
||||
// 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;
|
||||
20
exercises/basic_tunnel/myTunnel_header.py
Normal file
@@ -0,0 +1,20 @@
|
||||
|
||||
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)
|
||||
|
||||
43
exercises/basic_tunnel/receive.py
Executable file
@@ -0,0 +1,43 @@
|
||||
#!/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()
|
||||
71
exercises/basic_tunnel/s1-runtime.json
Normal file
@@ -0,0 +1,71 @@
|
||||
{
|
||||
"target": "bmv2",
|
||||
"p4info": "build/basic_tunnel.p4info",
|
||||
"bmv2_json": "build/basic_tunnel.json",
|
||||
"table_entries": [
|
||||
{
|
||||
"table": "MyIngress.ipv4_lpm",
|
||||
"match": {
|
||||
"hdr.ipv4.dstAddr": ["10.0.1.1", 32]
|
||||
},
|
||||
"action_name": "MyIngress.ipv4_forward",
|
||||
"action_params": {
|
||||
"dstAddr": "00:00:00:00:01:01",
|
||||
"port": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.ipv4_lpm",
|
||||
"match": {
|
||||
"hdr.ipv4.dstAddr": ["10.0.2.2", 32]
|
||||
},
|
||||
"action_name": "MyIngress.ipv4_forward",
|
||||
"action_params": {
|
||||
"dstAddr": "00:00:00:02:02:00",
|
||||
"port": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.ipv4_lpm",
|
||||
"match": {
|
||||
"hdr.ipv4.dstAddr": ["10.0.3.3", 32]
|
||||
},
|
||||
"action_name": "MyIngress.ipv4_forward",
|
||||
"action_params": {
|
||||
"dstAddr": "00:00:00:03:03:00",
|
||||
"port": 3
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"table": "MyIngress.myTunnel_exact",
|
||||
"match": {
|
||||
"hdr.myTunnel.dst_id": [1]
|
||||
},
|
||||
"action_name": "MyIngress.myTunnel_forward",
|
||||
"action_params": {
|
||||
"port": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.myTunnel_exact",
|
||||
"match": {
|
||||
"hdr.myTunnel.dst_id": [2]
|
||||
},
|
||||
"action_name": "MyIngress.myTunnel_forward",
|
||||
"action_params": {
|
||||
"port": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.myTunnel_exact",
|
||||
"match": {
|
||||
"hdr.myTunnel.dst_id": [3]
|
||||
},
|
||||
"action_name": "MyIngress.myTunnel_forward",
|
||||
"action_params": {
|
||||
"port": 3
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
71
exercises/basic_tunnel/s2-runtime.json
Normal file
@@ -0,0 +1,71 @@
|
||||
{
|
||||
"target": "bmv2",
|
||||
"p4info": "build/basic_tunnel.p4info",
|
||||
"bmv2_json": "build/basic_tunnel.json",
|
||||
"table_entries": [
|
||||
{
|
||||
"table": "MyIngress.ipv4_lpm",
|
||||
"match": {
|
||||
"hdr.ipv4.dstAddr": ["10.0.1.1", 32]
|
||||
},
|
||||
"action_name": "MyIngress.ipv4_forward",
|
||||
"action_params": {
|
||||
"dstAddr": "00:00:00:01:02:00",
|
||||
"port": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.ipv4_lpm",
|
||||
"match": {
|
||||
"hdr.ipv4.dstAddr": ["10.0.2.2", 32]
|
||||
},
|
||||
"action_name": "MyIngress.ipv4_forward",
|
||||
"action_params": {
|
||||
"dstAddr": "00:00:00:00:02:02",
|
||||
"port": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.ipv4_lpm",
|
||||
"match": {
|
||||
"hdr.ipv4.dstAddr": ["10.0.3.3", 32]
|
||||
},
|
||||
"action_name": "MyIngress.ipv4_forward",
|
||||
"action_params": {
|
||||
"dstAddr": "00:00:00:03:02:00",
|
||||
"port": 3
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"table": "MyIngress.myTunnel_exact",
|
||||
"match": {
|
||||
"hdr.myTunnel.dst_id": [1]
|
||||
},
|
||||
"action_name": "MyIngress.myTunnel_forward",
|
||||
"action_params": {
|
||||
"port": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.myTunnel_exact",
|
||||
"match": {
|
||||
"hdr.myTunnel.dst_id": [2]
|
||||
},
|
||||
"action_name": "MyIngress.myTunnel_forward",
|
||||
"action_params": {
|
||||
"port": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.myTunnel_exact",
|
||||
"match": {
|
||||
"hdr.myTunnel.dst_id": [3]
|
||||
},
|
||||
"action_name": "MyIngress.myTunnel_forward",
|
||||
"action_params": {
|
||||
"port": 3
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
71
exercises/basic_tunnel/s3-runtime.json
Normal file
@@ -0,0 +1,71 @@
|
||||
{
|
||||
"target": "bmv2",
|
||||
"p4info": "build/basic_tunnel.p4info",
|
||||
"bmv2_json": "build/basic_tunnel.json",
|
||||
"table_entries": [
|
||||
{
|
||||
"table": "MyIngress.ipv4_lpm",
|
||||
"match": {
|
||||
"hdr.ipv4.dstAddr": ["10.0.1.1", 32]
|
||||
},
|
||||
"action_name": "MyIngress.ipv4_forward",
|
||||
"action_params": {
|
||||
"dstAddr": "00:00:00:01:03:00",
|
||||
"port": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.ipv4_lpm",
|
||||
"match": {
|
||||
"hdr.ipv4.dstAddr": ["10.0.2.2", 32]
|
||||
},
|
||||
"action_name": "MyIngress.ipv4_forward",
|
||||
"action_params": {
|
||||
"dstAddr": "00:00:00:02:03:00",
|
||||
"port": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.ipv4_lpm",
|
||||
"match": {
|
||||
"hdr.ipv4.dstAddr": ["10.0.3.3", 32]
|
||||
},
|
||||
"action_name": "MyIngress.ipv4_forward",
|
||||
"action_params": {
|
||||
"dstAddr": "00:00:00:00:03:03",
|
||||
"port": 1
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"table": "MyIngress.myTunnel_exact",
|
||||
"match": {
|
||||
"hdr.myTunnel.dst_id": [1]
|
||||
},
|
||||
"action_name": "MyIngress.myTunnel_forward",
|
||||
"action_params": {
|
||||
"port": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.myTunnel_exact",
|
||||
"match": {
|
||||
"hdr.myTunnel.dst_id": [2]
|
||||
},
|
||||
"action_name": "MyIngress.myTunnel_forward",
|
||||
"action_params": {
|
||||
"port": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.myTunnel_exact",
|
||||
"match": {
|
||||
"hdr.myTunnel.dst_id": [3]
|
||||
},
|
||||
"action_name": "MyIngress.myTunnel_forward",
|
||||
"action_params": {
|
||||
"port": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
53
exercises/basic_tunnel/send.py
Executable file
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env python
|
||||
import argparse
|
||||
import sys
|
||||
import socket
|
||||
import random
|
||||
import struct
|
||||
import argparse
|
||||
|
||||
from scapy.all import sendp, send, get_if_list, get_if_hwaddr, hexdump
|
||||
from scapy.all import Packet
|
||||
from scapy.all import Ether, IP, UDP, TCP
|
||||
from myTunnel_header import MyTunnel
|
||||
|
||||
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():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('ip_addr', type=str, help="The destination IP address to use")
|
||||
parser.add_argument('message', type=str, help="The message to include in packet")
|
||||
parser.add_argument('--dst_id', type=int, default=None, help='The myTunnel dst_id to use, if unspecified then myTunnel header will not be included in packet')
|
||||
args = parser.parse_args()
|
||||
|
||||
addr = socket.gethostbyname(args.ip_addr)
|
||||
dst_id = args.dst_id
|
||||
iface = get_if()
|
||||
|
||||
if (dst_id is not None):
|
||||
print "sending on interface {} to dst_id {}".format(iface, str(dst_id))
|
||||
pkt = Ether(src=get_if_hwaddr(iface), dst='ff:ff:ff:ff:ff:ff')
|
||||
pkt = pkt / MyTunnel(dst_id=dst_id) / IP(dst=addr) / args.message
|
||||
else:
|
||||
print "sending on interface {} to IP addr {}".format(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)) / args.message
|
||||
|
||||
pkt.show2()
|
||||
# hexdump(pkt)
|
||||
# print "len(pkt) = ", len(pkt)
|
||||
sendp(pkt, iface=iface, verbose=False)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
215
exercises/basic_tunnel/solution/basic_tunnel.p4
Normal file
@@ -0,0 +1,215 @@
|
||||
/* -*- P4_16 -*- */
|
||||
#include <core.p4>
|
||||
#include <v1model.p4>
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 */
|
||||
}
|
||||
|
||||
struct headers {
|
||||
ethernet_t ethernet;
|
||||
myTunnel_t myTunnel;
|
||||
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_MYTUNNEL: parse_myTunnel;
|
||||
TYPE_IPV4: parse_ipv4;
|
||||
default: accept;
|
||||
}
|
||||
}
|
||||
|
||||
state parse_myTunnel {
|
||||
packet.extract(hdr.myTunnel);
|
||||
transition select(hdr.myTunnel.proto_id) {
|
||||
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 = drop();
|
||||
}
|
||||
|
||||
action myTunnel_forward(egressSpec_t port) {
|
||||
standard_metadata.egress_spec = port;
|
||||
}
|
||||
|
||||
table myTunnel_exact {
|
||||
key = {
|
||||
hdr.myTunnel.dst_id: exact;
|
||||
}
|
||||
actions = {
|
||||
myTunnel_forward;
|
||||
drop;
|
||||
}
|
||||
size = 1024;
|
||||
default_action = drop();
|
||||
}
|
||||
|
||||
apply {
|
||||
if (hdr.ipv4.isValid() && !hdr.myTunnel.isValid()) {
|
||||
// Process only non-tunneled IPv4 packets
|
||||
ipv4_lpm.apply();
|
||||
}
|
||||
|
||||
if (hdr.myTunnel.isValid()) {
|
||||
// process tunneled packets
|
||||
myTunnel_exact.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.myTunnel);
|
||||
packet.emit(hdr.ipv4);
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
*********************** S W I T C H *******************************
|
||||
*************************************************************************/
|
||||
|
||||
V1Switch(
|
||||
MyParser(),
|
||||
MyVerifyChecksum(),
|
||||
MyIngress(),
|
||||
MyEgress(),
|
||||
MyComputeChecksum(),
|
||||
MyDeparser()
|
||||
) main;
|
||||
BIN
exercises/basic_tunnel/topo.pdf
Normal file
BIN
exercises/basic_tunnel/topo.png
Normal file
|
After Width: | Height: | Size: 92 KiB |
16
exercises/basic_tunnel/topology.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"hosts": [
|
||||
"h1",
|
||||
"h2",
|
||||
"h3"
|
||||
],
|
||||
"switches": {
|
||||
"s1": { "runtime_json" : "s1-runtime.json" },
|
||||
"s2": { "runtime_json" : "s2-runtime.json" },
|
||||
"s3": { "runtime_json" : "s3-runtime.json" }
|
||||
},
|
||||
"links": [
|
||||
["h1", "s1"], ["s1", "s2"], ["s1", "s3"],
|
||||
["s3", "s2"], ["s2", "h2"], ["s3", "h3"]
|
||||
]
|
||||
}
|
||||
5
exercises/calc/Makefile
Normal file
@@ -0,0 +1,5 @@
|
||||
BMV2_SWITCH_EXE = simple_switch_grpc
|
||||
NO_P4 = true
|
||||
P4C_ARGS = --p4runtime-file $(basename $@).p4info --p4runtime-format text
|
||||
|
||||
include ../../utils/Makefile
|
||||
|
After Width: | Height: | Size: 154 B |
112
exercises/calc/README.md
Normal file
@@ -0,0 +1,112 @@
|
||||
# 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
|
||||
make
|
||||
```
|
||||
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.1` and `10.0.1.2`.
|
||||
|
||||
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
|
||||
>
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
Congratulations, your implementation works! Move on to
|
||||
[Load Balancer](../load_balance).
|
||||
250
exercises/calc/calc.p4
Normal file
@@ -0,0 +1,250 @@
|
||||
/* -*- 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 it (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> op;
|
||||
/* TODO
|
||||
* fill p4calc_t header with P, four, ver, op, operand_a, operand_b, and res
|
||||
entries based on above protocol header definition.
|
||||
*/
|
||||
}
|
||||
|
||||
/*
|
||||
* All headers, used in the program needs to be assembled 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 headers {
|
||||
ethernet_t ethernet;
|
||||
p4calc_t p4calc;
|
||||
}
|
||||
|
||||
/*
|
||||
* All metadata, globally used in the program, also needs to be assembled
|
||||
* 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 metadata {
|
||||
/* In our case it is empty */
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
*********************** 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 {
|
||||
packet.extract(hdr.ethernet);
|
||||
transition select(hdr.ethernet.etherType) {
|
||||
P4CALC_ETYPE : check_p4calc;
|
||||
default : accept;
|
||||
}
|
||||
}
|
||||
|
||||
state check_p4calc {
|
||||
/* TODO: just uncomment the following parse block */
|
||||
/*
|
||||
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 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 send_back(bit<32> result) {
|
||||
/* TODO
|
||||
* - put the result back in hdr.p4calc.res
|
||||
* - swap MAC addresses in hdr.ethernet.dstAddr and
|
||||
* hdr.ethernet.srcAddr using a temp variable
|
||||
* - Send the packet back to the port it came from
|
||||
by saving standard_metadata.ingress_port into
|
||||
standard_metadata.egress_spec
|
||||
*/
|
||||
}
|
||||
|
||||
action operation_add() {
|
||||
/* TODO call send_back with operand_a + operand_b */
|
||||
}
|
||||
|
||||
action operation_sub() {
|
||||
/* TODO call send_back with operand_a - operand_b */
|
||||
}
|
||||
|
||||
action operation_and() {
|
||||
/* TODO call send_back with operand_a & operand_b */
|
||||
}
|
||||
|
||||
action operation_or() {
|
||||
/* TODO call send_back with operand_a | operand_b */
|
||||
}
|
||||
|
||||
action operation_xor() {
|
||||
/* TODO call send_back with operand_a ^ 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 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 { }
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
*********************** D E P A R S E R *******************************
|
||||
*************************************************************************/
|
||||
control MyDeparser(packet_out packet, in headers hdr) {
|
||||
apply {
|
||||
packet.emit(hdr.ethernet);
|
||||
packet.emit(hdr.p4calc);
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
*********************** S W I T T C H **********************************
|
||||
*************************************************************************/
|
||||
|
||||
V1Switch(
|
||||
MyParser(),
|
||||
MyVerifyChecksum(),
|
||||
MyIngress(),
|
||||
MyEgress(),
|
||||
MyComputeChecksum(),
|
||||
MyDeparser()
|
||||
) main;
|
||||
97
exercises/calc/calc.py
Executable file
@@ -0,0 +1,97 @@
|
||||
#!/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
|
||||
import readline
|
||||
|
||||
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 = pkt/' '
|
||||
|
||||
# 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()
|
||||
6
exercises/calc/s1-runtime.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"target": "bmv2",
|
||||
"p4info": "build/calc.p4info",
|
||||
"bmv2_json": "build/calc.json",
|
||||
"table_entries": [ ]
|
||||
}
|
||||
256
exercises/calc/solution/calc.p4
Normal file
@@ -0,0 +1,256 @@
|
||||
/* -*- 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 headers {
|
||||
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 metadata {
|
||||
/* In our case it is empty */
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
*********************** 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 {
|
||||
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 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 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 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 { }
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
*********************** D E P A R S E R *******************************
|
||||
*************************************************************************/
|
||||
control MyDeparser(packet_out packet, in headers hdr) {
|
||||
apply {
|
||||
packet.emit(hdr.ethernet);
|
||||
packet.emit(hdr.p4calc);
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
*********************** S W I T T C H **********************************
|
||||
*************************************************************************/
|
||||
|
||||
V1Switch(
|
||||
MyParser(),
|
||||
MyVerifyChecksum(),
|
||||
MyIngress(),
|
||||
MyEgress(),
|
||||
MyComputeChecksum(),
|
||||
MyDeparser()
|
||||
) main;
|
||||
12
exercises/calc/topology.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"hosts": [
|
||||
"h1",
|
||||
"h2"
|
||||
],
|
||||
"switches": {
|
||||
"s1": { "runtime_json" : "s1-runtime.json" }
|
||||
},
|
||||
"links": [
|
||||
["h1", "s1"], ["h2", "s1"]
|
||||
]
|
||||
}
|
||||
5
exercises/ecn/Makefile
Normal file
@@ -0,0 +1,5 @@
|
||||
BMV2_SWITCH_EXE = simple_switch_grpc
|
||||
NO_P4 = true
|
||||
P4C_ARGS = --p4runtime-file $(basename $@).p4info --p4runtime-format text
|
||||
|
||||
include ../../utils/Makefile
|
||||
|
After Width: | Height: | Size: 154 B |
198
exercises/ecn/README.md
Normal file
@@ -0,0 +1,198 @@
|
||||
# Implementing ECN
|
||||
|
||||
## Introduction
|
||||
|
||||
The objective of this tutorial is to extend basic L3 forwarding with
|
||||
an implementation of Explicit Congestion Notification (ECN).
|
||||
|
||||
ECN allows end-to-end notification of network congestion without
|
||||
dropping packets. If an end-host supports ECN, it puts the value of 1
|
||||
or 2 in the `ipv4.ecn` field. For such packets, each switch may
|
||||
change the value to 3 if the queue size is larger than a threshold.
|
||||
The receiver copies the value to sender, and the sender can lower the
|
||||
rate.
|
||||
|
||||
As before, we have already defined the control plane rules for
|
||||
routing, 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,
|
||||
`ecn.p4`, which initially implements L3 forwarding. Your job (in the
|
||||
next step) will be to extend it to properly append set the ECN bits
|
||||
|
||||
Before that, let's compile the incomplete `ecn.p4` and bring up a
|
||||
network in Mininet to test its behavior.
|
||||
|
||||
1. In your shell, run:
|
||||
```bash
|
||||
make
|
||||
```
|
||||
This will:
|
||||
* compile `ecn.p4`, and
|
||||
* start a Mininet instance with three switches (`s1`, `s2`, `s3`) configured
|
||||
in a triangle. There are 5 hosts. `h1` and `h11` are connected to `s1`.
|
||||
`h2` and `h22` are connected to `s2` and `h3` is connected to `s3`.
|
||||
* The hosts are assigned IPs of `10.0.1.1`, `10.0.2.2`, etc
|
||||
(`10.0.<Switchid>.<hostID>`).
|
||||
* The control plane programs the P4 tables in each switch based on
|
||||
`sx-runtime.json`
|
||||
|
||||
2. We want to send a low rate traffic from `h1` to `h2` and a high
|
||||
rate iperf traffic from `h11` to `h22`. The link between `s1` and
|
||||
`s2` is common between the flows and is a bottleneck because we
|
||||
reduced its bandwidth to 512kbps in topology.json. Therefore, if we
|
||||
capture packets at `h2`, we should see the right ECN value.
|
||||
|
||||

|
||||
|
||||
3. You should now see a Mininet command prompt. Open four terminals
|
||||
for `h1`, `h11`, `h2`, `h22`, respectively:
|
||||
```bash
|
||||
mininet> xterm h1 h11 h2 h22
|
||||
```
|
||||
3. In `h2`'s XTerm, start the server that captures packets:
|
||||
```bash
|
||||
./receive.py
|
||||
```
|
||||
4. in `h22`'s XTerm, start the iperf UDP server:
|
||||
```bash
|
||||
iperf -s -u
|
||||
```
|
||||
5. In `h1`'s XTerm, send one packet per second to `h2` using send.py
|
||||
say for 30 seconds:
|
||||
```bash
|
||||
./send.py 10.0.2.2 "P4 is cool" 30
|
||||
```
|
||||
The message "P4 is cool" should be received in `h2`'s xterm,
|
||||
6. In `h11`'s XTerm, start iperf client sending for 15 seconds
|
||||
```bash
|
||||
iperf -c 10.0.2.22 -t 15 -u
|
||||
```
|
||||
7. At `h2`, the `ipv4.tos` field (DiffServ+ECN) is always 1
|
||||
8. type `exit` to close each XTerm window
|
||||
|
||||
Your job is to extend the code in `ecn.p4` to implement the ECN logic
|
||||
for setting the ECN flag.
|
||||
|
||||
## Step 2: Implement ECN
|
||||
|
||||
The `ecn.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.
|
||||
|
||||
First we have to change the ipv4_t header by splitting the TOS field
|
||||
into DiffServ and ECN fields. Remember to update the checksum block
|
||||
accordingly. Then, in the egress control block we must compare the
|
||||
queue length with ECN_THRESHOLD. If the queue length is larger than
|
||||
the threshold, the ECN flag will be set. Note that this logic should
|
||||
happen only if the end-host declared supporting ECN by setting the
|
||||
original ECN to 1 or 2.
|
||||
|
||||
A complete `ecn.p4` will contain the following components:
|
||||
|
||||
1. Header type definitions for Ethernet (`ethernet_t`) and IPv4 (`ipv4_t`).
|
||||
2. Parsers for Ethernet, IPv4,
|
||||
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 egress control block that checks the ECN and
|
||||
`standard_metadata.enq_qdepth` and sets the ipv4.ecn.
|
||||
6. A deparser that selects the order in which fields inserted into the outgoing
|
||||
packet.
|
||||
7. A `package` instantiation supplied with the parser, control,
|
||||
checksum verification and recomputation and deparser.
|
||||
|
||||
## 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 `tos` values change from 1
|
||||
to 3 as the queue builds up. `tos` may change back to 1 when iperf
|
||||
finishes and the queue depletes.
|
||||
|
||||
To easily track the `tos` values you may want to redirect the output
|
||||
of `h2` to a file by running the following for `h2`
|
||||
```bash
|
||||
./receive.py > h2.log
|
||||
```
|
||||
and just print the `tos` values `grep tos h2.log` in a separate window
|
||||
```
|
||||
tos = 0x1
|
||||
tos = 0x1
|
||||
tos = 0x1
|
||||
tos = 0x1
|
||||
tos = 0x1
|
||||
tos = 0x1
|
||||
tos = 0x1
|
||||
tos = 0x1
|
||||
tos = 0x1
|
||||
tos = 0x1
|
||||
tos = 0x1
|
||||
tos = 0x1
|
||||
tos = 0x1
|
||||
tos = 0x3
|
||||
tos = 0x3
|
||||
tos = 0x3
|
||||
tos = 0x3
|
||||
tos = 0x3
|
||||
tos = 0x3
|
||||
tos = 0x1
|
||||
tos = 0x1
|
||||
tos = 0x1
|
||||
tos = 0x1
|
||||
tos = 0x1
|
||||
tos = 0x1
|
||||
```
|
||||
|
||||
### Food for thought
|
||||
|
||||
How can we let the user configure the threshold?
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
There are several ways that problems might manifest:
|
||||
|
||||
1. `ecn.p4` fails to compile. In this case, `make` will report the
|
||||
error emitted from the compiler and stop.
|
||||
2. `ecn.p4` compiles but does not support the control plane rules in
|
||||
the `sX-runtime.json` files that `make` tries to install using
|
||||
the BMv2 CLI. In this case, `make` will log the CLI tool output
|
||||
in the `logs` directory. Use these error messages to fix your `ecn.p4`
|
||||
implementation.
|
||||
3. `ecn.p4` compiles, and the control plane rules are installed, 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.
|
||||
4. `ecn.p4` compiles and all rules are installed. Packets go through
|
||||
and the logs show that the queue length was not high enough to set
|
||||
the ECN bit. Then either lower the threshold in the p4 code or
|
||||
reduce the link bandwidth in `topology.json`
|
||||
|
||||
#### 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 on to the next
|
||||
exercise: [Multi-Hop Route Inspection](../mri), which identifies which
|
||||
link is the source of congestion.
|
||||
187
exercises/ecn/ecn.p4
Normal file
@@ -0,0 +1,187 @@
|
||||
/* -*- 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;
|
||||
}
|
||||
size = 1024;
|
||||
default_action = drop;
|
||||
}
|
||||
|
||||
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;
|
||||
37
exercises/ecn/receive.py
Executable file
@@ -0,0 +1,37 @@
|
||||
#!/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
|
||||
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
|
||||
|
||||
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()
|
||||
52
exercises/ecn/s1-runtime.json
Normal file
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"target": "bmv2",
|
||||
"p4info": "build/ecn.p4info",
|
||||
"bmv2_json": "build/ecn.json",
|
||||
"table_entries": [
|
||||
{
|
||||
"table": "MyIngress.ipv4_lpm",
|
||||
"match": {
|
||||
"hdr.ipv4.dstAddr": ["10.0.1.1", 32]
|
||||
},
|
||||
"action_name": "MyIngress.ipv4_forward",
|
||||
"action_params": {
|
||||
"dstAddr": "00:00:00:00:01:01",
|
||||
"port": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.ipv4_lpm",
|
||||
"match": {
|
||||
"hdr.ipv4.dstAddr": ["10.0.1.11", 32]
|
||||
},
|
||||
"action_name": "MyIngress.ipv4_forward",
|
||||
"action_params": {
|
||||
"dstAddr": "00:00:00:00:01:0b",
|
||||
"port": 1
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"table": "MyIngress.ipv4_lpm",
|
||||
"match": {
|
||||
"hdr.ipv4.dstAddr": ["10.0.2.0", 24]
|
||||
},
|
||||
"action_name": "MyIngress.ipv4_forward",
|
||||
"action_params": {
|
||||
"dstAddr": "00:00:00:02:03:00",
|
||||
"port": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.ipv4_lpm",
|
||||
"match": {
|
||||
"hdr.ipv4.dstAddr": ["10.0.3.0", 24]
|
||||
},
|
||||
"action_name": "MyIngress.ipv4_forward",
|
||||
"action_params": {
|
||||
"dstAddr": "00:00:00:03:02:00",
|
||||
"port": 4
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
51
exercises/ecn/s2-runtime.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"target": "bmv2",
|
||||
"p4info": "build/ecn.p4info",
|
||||
"bmv2_json": "build/ecn.json",
|
||||
"table_entries": [
|
||||
{
|
||||
"table": "MyIngress.ipv4_lpm",
|
||||
"match": {
|
||||
"hdr.ipv4.dstAddr": ["10.0.2.2", 32]
|
||||
},
|
||||
"action_name": "MyIngress.ipv4_forward",
|
||||
"action_params": {
|
||||
"dstAddr": "00:00:00:00:02:02",
|
||||
"port": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.ipv4_lpm",
|
||||
"match": {
|
||||
"hdr.ipv4.dstAddr": ["10.0.2.22", 32]
|
||||
},
|
||||
"action_name": "MyIngress.ipv4_forward",
|
||||
"action_params": {
|
||||
"dstAddr": "00:00:00:00:02:16",
|
||||
"port": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.ipv4_lpm",
|
||||
"match": {
|
||||
"hdr.ipv4.dstAddr": ["10.0.1.0", 24]
|
||||
},
|
||||
"action_name": "MyIngress.ipv4_forward",
|
||||
"action_params": {
|
||||
"dstAddr": "00:00:00:01:03:00",
|
||||
"port": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.ipv4_lpm",
|
||||
"match": {
|
||||
"hdr.ipv4.dstAddr": ["10.0.3.0", 24]
|
||||
},
|
||||
"action_name": "MyIngress.ipv4_forward",
|
||||
"action_params": {
|
||||
"dstAddr": "00:00:00:03:03:00",
|
||||
"port": 4
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
40
exercises/ecn/s3-runtime.json
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"target": "bmv2",
|
||||
"p4info": "build/ecn.p4info",
|
||||
"bmv2_json": "build/ecn.json",
|
||||
"table_entries": [
|
||||
{
|
||||
"table": "MyIngress.ipv4_lpm",
|
||||
"match": {
|
||||
"hdr.ipv4.dstAddr": ["10.0.3.3", 32]
|
||||
},
|
||||
"action_name": "MyIngress.ipv4_forward",
|
||||
"action_params": {
|
||||
"dstAddr": "00:00:00:00:03:03",
|
||||
"port": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.ipv4_lpm",
|
||||
"match": {
|
||||
"hdr.ipv4.dstAddr": ["10.0.1.0", 24]
|
||||
},
|
||||
"action_name": "MyIngress.ipv4_forward",
|
||||
"action_params": {
|
||||
"dstAddr": "00:00:00:01:04:00",
|
||||
"port": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.ipv4_lpm",
|
||||
"match": {
|
||||
"hdr.ipv4.dstAddr": ["10.0.2.0", 24]
|
||||
},
|
||||
"action_name": "MyIngress.ipv4_forward",
|
||||
"action_params": {
|
||||
"dstAddr": "00:00:00:02:04:00",
|
||||
"port": 3
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
50
exercises/ecn/send.py
Executable file
@@ -0,0 +1,50 @@
|
||||
#!/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
|
||||
|
||||
from time import sleep
|
||||
|
||||
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)<4:
|
||||
print 'pass 2 arguments: <destination> "<message>" <duration>'
|
||||
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, tos=1) / UDP(dport=4321, sport=1234) / sys.argv[2]
|
||||
pkt.show2()
|
||||
#hexdump(pkt)
|
||||
try:
|
||||
for i in range(int(sys.argv[3])):
|
||||
sendp(pkt, iface=iface)
|
||||
sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
BIN
exercises/ecn/setup.png
Normal file
|
After Width: | Height: | Size: 221 KiB |
188
exercises/ecn/solution/ecn.p4
Normal file
@@ -0,0 +1,188 @@
|
||||
/* -*- 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;
|
||||
}
|
||||
|
||||
header ipv4_t {
|
||||
bit<4> version;
|
||||
bit<4> ihl;
|
||||
bit<6> diffserv;
|
||||
bit<2> ecn;
|
||||
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) {
|
||||
action mark_ecn() {
|
||||
hdr.ipv4.ecn = 3;
|
||||
}
|
||||
apply {
|
||||
if (hdr.ipv4.ecn == 1 || hdr.ipv4.ecn == 2){
|
||||
if (standard_metadata.enq_qdepth >= ECN_THRESHOLD){
|
||||
mark_ecn();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
************* 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.ecn,
|
||||
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;
|
||||
18
exercises/ecn/topology.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"hosts": [
|
||||
"h1",
|
||||
"h2",
|
||||
"h3",
|
||||
"h11",
|
||||
"h22"
|
||||
],
|
||||
"switches": {
|
||||
"s1": { "runtime_json" : "s1-runtime.json" },
|
||||
"s2": { "runtime_json" : "s2-runtime.json" },
|
||||
"s3": { "runtime_json" : "s3-runtime.json" }
|
||||
},
|
||||
"links": [
|
||||
["h1", "s1"], ["h11", "s1"], ["s1", "s2", "0", 0.5], ["s1", "s3"],
|
||||
["s3", "s2"], ["s2", "h2"], ["s2", "h22"], ["s3", "h3"]
|
||||
]
|
||||
}
|
||||
5
exercises/load_balance/Makefile
Normal file
@@ -0,0 +1,5 @@
|
||||
BMV2_SWITCH_EXE = simple_switch_grpc
|
||||
NO_P4 = true
|
||||
P4C_ARGS = --p4runtime-file $(basename $@).p4info --p4runtime-format text
|
||||
|
||||
include ../../utils/Makefile
|
||||
|
After Width: | Height: | Size: 154 B |
142
exercises/load_balance/README.md
Normal file
@@ -0,0 +1,142 @@
|
||||
# Load Balancing
|
||||
|
||||
In this exercise, you will implement a form of load balancing based on
|
||||
a simple version of Equal-Cost Multipath Forwarding. The switch you
|
||||
will implement will use two tables to forward packets to one of two
|
||||
destination hosts at random. The first table will use a hash function
|
||||
(applied to a 5-tuple consisting of the source and destination
|
||||
IP addresses, IP protocol, and source and destination TCP ports)
|
||||
to select one of two hosts. The second table will use the
|
||||
computed hash value to forward the packet to the selected host.
|
||||
|
||||
> **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,
|
||||
`load_balance.p4`, which initially drops all packets. Your job (in
|
||||
the next step) will be to extend it to properly forward packets.
|
||||
|
||||
Before that, let's compile the incomplete `load_balance.p4` and bring
|
||||
up a switch in Mininet to test its behavior.
|
||||
|
||||
1. In your shell, run:
|
||||
```bash
|
||||
make
|
||||
```
|
||||
This will:
|
||||
* compile `load_balance.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.1`, `10.0.2.2`, etc.
|
||||
* We use the IP address 10.0.0.1 to indicate traffic that should be
|
||||
load balanced between `h2` and `h3`.
|
||||
|
||||
2. You should now see a Mininet command prompt. Open three terminals
|
||||
for `h1`, `h2` and `h3`, respectively:
|
||||
```bash
|
||||
mininet> xterm h1 h2 h3
|
||||
```
|
||||
3. Each host includes a small Python-based messaging client and
|
||||
server. In `h2` and `h3`'s XTerms, start the servers:
|
||||
```bash
|
||||
./receive.py
|
||||
```
|
||||
4. In `h1`'s XTerm, send a message from the client:
|
||||
```bash
|
||||
./send.py 10.0.0.1 "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
|
||||
`load_balance.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
|
||||
`make` script will install packet-processing rules in the tables of
|
||||
each switch. These are defined in the `s1-commands.txt` file.
|
||||
|
||||
**Important:** A P4 program also defines the interface between the
|
||||
switch pipeline and control plane. The `s1-commands.txt` file contains
|
||||
a list 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 Load Balancing
|
||||
|
||||
The `load_balance.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 `load_balance.p4` will contain the following components:
|
||||
|
||||
1. Header type definitions for Ethernet (`ethernet_t`) and IPv4 (`ipv4_t`).
|
||||
2. 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 `set_ecmp_select`), which will:
|
||||
1. Hashes the 5-tuple specified above using the `hash` extern
|
||||
2. Stores the result in the `meta.ecmp_select` field
|
||||
5. **TODO:** A control that:
|
||||
1. Applies the `ecmp_group` table.
|
||||
2. Applies the `ecmp_nhop` table.
|
||||
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` or `h3`. If you send several
|
||||
messages, some should be received by each server.
|
||||
|
||||
### Food for thought
|
||||
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
There are several ways that problems might manifest:
|
||||
|
||||
1. `load_balance.p4` fails to compile. In this case, `make` will
|
||||
report the error emitted from the compiler and stop.
|
||||
|
||||
2. `load_balance.p4` compiles but does not support the control plane
|
||||
rules in the `sX-commands.txt` files that `make` tries to install
|
||||
using the BMv2 CLI. In this case, `make` will log the CLI tool output
|
||||
in the `logs` directory. Use these error messages to fix your `load_balance.p4`
|
||||
implementation.
|
||||
|
||||
3. `load_balance.p4` compiles, and the control plane rules are
|
||||
installed, 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.
|
||||
|
||||
#### 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
|
||||
mn -c
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
Congratulations, your implementation works!
|
||||
218
exercises/load_balance/load_balance.p4
Normal file
@@ -0,0 +1,218 @@
|
||||
/* -*- P4_16 -*- */
|
||||
#include <core.p4>
|
||||
#include <v1model.p4>
|
||||
|
||||
/*************************************************************************
|
||||
*********************** H E A D E R S ***********************************
|
||||
*************************************************************************/
|
||||
|
||||
header ethernet_t {
|
||||
bit<48> dstAddr;
|
||||
bit<48> 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;
|
||||
bit<32> srcAddr;
|
||||
bit<32> dstAddr;
|
||||
}
|
||||
|
||||
header tcp_t {
|
||||
bit<16> srcPort;
|
||||
bit<16> dstPort;
|
||||
bit<32> seqNo;
|
||||
bit<32> ackNo;
|
||||
bit<4> dataOffset;
|
||||
bit<3> res;
|
||||
bit<3> ecn;
|
||||
bit<6> ctrl;
|
||||
bit<16> window;
|
||||
bit<16> checksum;
|
||||
bit<16> urgentPtr;
|
||||
}
|
||||
|
||||
struct metadata {
|
||||
bit<14> ecmp_select;
|
||||
}
|
||||
|
||||
struct headers {
|
||||
ethernet_t ethernet;
|
||||
ipv4_t ipv4;
|
||||
tcp_t tcp;
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
*********************** 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) {
|
||||
0x800: parse_ipv4;
|
||||
default: accept;
|
||||
}
|
||||
}
|
||||
state parse_ipv4 {
|
||||
packet.extract(hdr.ipv4);
|
||||
transition select(hdr.ipv4.protocol) {
|
||||
6: parse_tcp;
|
||||
default: accept;
|
||||
}
|
||||
}
|
||||
state parse_tcp {
|
||||
packet.extract(hdr.tcp);
|
||||
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 set_ecmp_select(bit<16> ecmp_base, bit<32> ecmp_count) {
|
||||
/* TODO: hash on 5-tuple and save the hash result in meta.ecmp_select
|
||||
so that the ecmp_nhop table can use it to make a forwarding decision accordingly */
|
||||
}
|
||||
action set_nhop(bit<48> nhop_dmac, bit<32> nhop_ipv4, bit<9> port) {
|
||||
hdr.ethernet.dstAddr = nhop_dmac;
|
||||
hdr.ipv4.dstAddr = nhop_ipv4;
|
||||
standard_metadata.egress_spec = port;
|
||||
hdr.ipv4.ttl = hdr.ipv4.ttl - 1;
|
||||
}
|
||||
table ecmp_group {
|
||||
key = {
|
||||
hdr.ipv4.dstAddr: lpm;
|
||||
}
|
||||
actions = {
|
||||
drop;
|
||||
set_ecmp_select;
|
||||
}
|
||||
size = 1024;
|
||||
}
|
||||
table ecmp_nhop {
|
||||
key = {
|
||||
meta.ecmp_select: exact;
|
||||
}
|
||||
actions = {
|
||||
drop;
|
||||
set_nhop;
|
||||
}
|
||||
size = 2;
|
||||
}
|
||||
apply {
|
||||
if (hdr.ipv4.isValid() && hdr.ipv4.ttl > 0) {
|
||||
ecmp_group.apply();
|
||||
ecmp_nhop.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 rewrite_mac(bit<48> smac) {
|
||||
hdr.ethernet.srcAddr = smac;
|
||||
}
|
||||
action drop() {
|
||||
mark_to_drop();
|
||||
}
|
||||
table send_frame {
|
||||
key = {
|
||||
standard_metadata.egress_port: exact;
|
||||
}
|
||||
actions = {
|
||||
rewrite_mac;
|
||||
drop;
|
||||
}
|
||||
size = 256;
|
||||
}
|
||||
apply {
|
||||
send_frame.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);
|
||||
packet.emit(hdr.tcp);
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
*********************** S W I T C H *******************************
|
||||
*************************************************************************/
|
||||
|
||||
V1Switch(
|
||||
MyParser(),
|
||||
MyVerifyChecksum(),
|
||||
MyIngress(),
|
||||
MyEgress(),
|
||||
MyComputeChecksum(),
|
||||
MyDeparser()
|
||||
) main;
|
||||
52
exercises/load_balance/receive.py
Executable file
@@ -0,0 +1,52 @@
|
||||
#!/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, 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():
|
||||
ifaces = filter(lambda i: 'eth' in i, os.listdir('/sys/class/net/'))
|
||||
iface = ifaces[0]
|
||||
print "sniffing on %s" % iface
|
||||
sys.stdout.flush()
|
||||
sniff(filter="tcp", iface = iface,
|
||||
prn = lambda x: handle_pkt(x))
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
68
exercises/load_balance/s1-runtime.json
Normal file
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"target": "bmv2",
|
||||
"p4info": "build/load_balance.p4info",
|
||||
"bmv2_json": "build/load_balance.json",
|
||||
"table_entries": [
|
||||
{
|
||||
"table": "MyIngress.ecmp_group",
|
||||
"default_action": true,
|
||||
"action_name": "MyIngress.drop",
|
||||
"action_params": { }
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.ecmp_group",
|
||||
"match": {
|
||||
"hdr.ipv4.dstAddr": ["10.0.0.1", 32]
|
||||
},
|
||||
"action_name": "MyIngress.set_ecmp_select",
|
||||
"action_params": {
|
||||
"ecmp_base": 0,
|
||||
"ecmp_count": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.ecmp_nhop",
|
||||
"match": {
|
||||
"meta.ecmp_select": 0
|
||||
},
|
||||
"action_name": "MyIngress.set_nhop",
|
||||
"action_params": {
|
||||
"nhop_dmac": "00:00:00:00:01:02",
|
||||
"nhop_ipv4": "10.0.2.2",
|
||||
"port" : 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.ecmp_nhop",
|
||||
"match": {
|
||||
"meta.ecmp_select": 1
|
||||
},
|
||||
"action_name": "MyIngress.set_nhop",
|
||||
"action_params": {
|
||||
"nhop_dmac": "00:00:00:00:01:03",
|
||||
"nhop_ipv4": "10.0.3.3",
|
||||
"port" : 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "MyEgress.send_frame",
|
||||
"match": {
|
||||
"standard_metadata.egress_port": 2
|
||||
},
|
||||
"action_name": "MyEgress.rewrite_mac",
|
||||
"action_params": {
|
||||
"smac": "00:00:00:01:02:00"
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "MyEgress.send_frame",
|
||||
"match": {
|
||||
"standard_metadata.egress_port": 3
|
||||
},
|
||||
"action_name": "MyEgress.rewrite_mac",
|
||||
"action_params": {
|
||||
"smac": "00:00:00:01:03:00"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
46
exercises/load_balance/s2-runtime.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"target": "bmv2",
|
||||
"p4info": "build/load_balance.p4info",
|
||||
"bmv2_json": "build/load_balance.json",
|
||||
"table_entries": [
|
||||
{
|
||||
"table": "MyIngress.ecmp_group",
|
||||
"default_action": true,
|
||||
"action_name": "MyIngress.drop",
|
||||
"action_params": { }
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.ecmp_group",
|
||||
"match": {
|
||||
"hdr.ipv4.dstAddr": ["10.0.2.2", 32]
|
||||
},
|
||||
"action_name": "MyIngress.set_ecmp_select",
|
||||
"action_params": {
|
||||
"ecmp_base": 0,
|
||||
"ecmp_count": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.ecmp_nhop",
|
||||
"match": {
|
||||
"meta.ecmp_select": 0
|
||||
},
|
||||
"action_name": "MyIngress.set_nhop",
|
||||
"action_params": {
|
||||
"nhop_dmac": "00:00:00:00:02:02",
|
||||
"nhop_ipv4": "10.0.2.2",
|
||||
"port" : 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "MyEgress.send_frame",
|
||||
"match": {
|
||||
"standard_metadata.egress_port": 1
|
||||
},
|
||||
"action_name": "MyEgress.rewrite_mac",
|
||||
"action_params": {
|
||||
"smac": "00:00:00:02:01:00"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
46
exercises/load_balance/s3-runtime.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"target": "bmv2",
|
||||
"p4info": "build/load_balance.p4info",
|
||||
"bmv2_json": "build/load_balance.json",
|
||||
"table_entries": [
|
||||
{
|
||||
"table": "MyIngress.ecmp_group",
|
||||
"default_action": true,
|
||||
"action_name": "MyIngress.drop",
|
||||
"action_params": { }
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.ecmp_group",
|
||||
"match": {
|
||||
"hdr.ipv4.dstAddr": ["10.0.3.3", 32]
|
||||
},
|
||||
"action_name": "MyIngress.set_ecmp_select",
|
||||
"action_params": {
|
||||
"ecmp_base": 0,
|
||||
"ecmp_count": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.ecmp_nhop",
|
||||
"match": {
|
||||
"meta.ecmp_select": 0
|
||||
},
|
||||
"action_name": "MyIngress.set_nhop",
|
||||
"action_params": {
|
||||
"nhop_dmac": "00:00:00:00:03:03",
|
||||
"nhop_ipv4": "10.0.3.3",
|
||||
"port" : 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "MyEgress.send_frame",
|
||||
"match": {
|
||||
"standard_metadata.egress_port": 1
|
||||
},
|
||||
"action_name": "MyEgress.rewrite_mac",
|
||||
"action_params": {
|
||||
"smac": "00:00:00:03:01:00"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
41
exercises/load_balance/send.py
Executable file
@@ -0,0 +1,41 @@
|
||||
#!/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()
|
||||
225
exercises/load_balance/solution/load_balance.p4
Normal file
@@ -0,0 +1,225 @@
|
||||
/* -*- P4_16 -*- */
|
||||
#include <core.p4>
|
||||
#include <v1model.p4>
|
||||
|
||||
/*************************************************************************
|
||||
*********************** H E A D E R S ***********************************
|
||||
*************************************************************************/
|
||||
|
||||
header ethernet_t {
|
||||
bit<48> dstAddr;
|
||||
bit<48> 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;
|
||||
bit<32> srcAddr;
|
||||
bit<32> dstAddr;
|
||||
}
|
||||
|
||||
header tcp_t {
|
||||
bit<16> srcPort;
|
||||
bit<16> dstPort;
|
||||
bit<32> seqNo;
|
||||
bit<32> ackNo;
|
||||
bit<4> dataOffset;
|
||||
bit<3> res;
|
||||
bit<3> ecn;
|
||||
bit<6> ctrl;
|
||||
bit<16> window;
|
||||
bit<16> checksum;
|
||||
bit<16> urgentPtr;
|
||||
}
|
||||
|
||||
struct metadata {
|
||||
bit<14> ecmp_select;
|
||||
}
|
||||
|
||||
struct headers {
|
||||
ethernet_t ethernet;
|
||||
ipv4_t ipv4;
|
||||
tcp_t tcp;
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
*********************** 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) {
|
||||
0x800: parse_ipv4;
|
||||
default: accept;
|
||||
}
|
||||
}
|
||||
state parse_ipv4 {
|
||||
packet.extract(hdr.ipv4);
|
||||
transition select(hdr.ipv4.protocol) {
|
||||
6: parse_tcp;
|
||||
default: accept;
|
||||
}
|
||||
}
|
||||
state parse_tcp {
|
||||
packet.extract(hdr.tcp);
|
||||
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 set_ecmp_select(bit<16> ecmp_base, bit<32> ecmp_count) {
|
||||
hash(meta.ecmp_select,
|
||||
HashAlgorithm.crc16,
|
||||
ecmp_base,
|
||||
{ hdr.ipv4.srcAddr,
|
||||
hdr.ipv4.dstAddr,
|
||||
hdr.ipv4.protocol,
|
||||
hdr.tcp.srcPort,
|
||||
hdr.tcp.dstPort },
|
||||
ecmp_count);
|
||||
}
|
||||
action set_nhop(bit<48> nhop_dmac, bit<32> nhop_ipv4, bit<9> port) {
|
||||
hdr.ethernet.dstAddr = nhop_dmac;
|
||||
hdr.ipv4.dstAddr = nhop_ipv4;
|
||||
standard_metadata.egress_spec = port;
|
||||
hdr.ipv4.ttl = hdr.ipv4.ttl - 1;
|
||||
}
|
||||
table ecmp_group {
|
||||
key = {
|
||||
hdr.ipv4.dstAddr: lpm;
|
||||
}
|
||||
actions = {
|
||||
drop;
|
||||
set_ecmp_select;
|
||||
}
|
||||
size = 1024;
|
||||
}
|
||||
table ecmp_nhop {
|
||||
key = {
|
||||
meta.ecmp_select: exact;
|
||||
}
|
||||
actions = {
|
||||
drop;
|
||||
set_nhop;
|
||||
}
|
||||
size = 2;
|
||||
}
|
||||
apply {
|
||||
if (hdr.ipv4.isValid() && hdr.ipv4.ttl > 0) {
|
||||
ecmp_group.apply();
|
||||
ecmp_nhop.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 rewrite_mac(bit<48> smac) {
|
||||
hdr.ethernet.srcAddr = smac;
|
||||
}
|
||||
action drop() {
|
||||
mark_to_drop();
|
||||
}
|
||||
table send_frame {
|
||||
key = {
|
||||
standard_metadata.egress_port: exact;
|
||||
}
|
||||
actions = {
|
||||
rewrite_mac;
|
||||
drop;
|
||||
}
|
||||
size = 256;
|
||||
}
|
||||
apply {
|
||||
send_frame.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);
|
||||
packet.emit(hdr.tcp);
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
*********************** S W I T C H *******************************
|
||||
*************************************************************************/
|
||||
|
||||
V1Switch(
|
||||
MyParser(),
|
||||
MyVerifyChecksum(),
|
||||
MyIngress(),
|
||||
MyEgress(),
|
||||
MyComputeChecksum(),
|
||||
MyDeparser()
|
||||
) main;
|
||||
16
exercises/load_balance/topology.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"hosts": [
|
||||
"h1",
|
||||
"h2",
|
||||
"h3"
|
||||
],
|
||||
"switches": {
|
||||
"s1": { "runtime_json" : "s1-runtime.json" },
|
||||
"s2": { "runtime_json" : "s2-runtime.json" },
|
||||
"s3": { "runtime_json" : "s3-runtime.json" }
|
||||
},
|
||||
"links": [
|
||||
["h1", "s1"], ["s1", "s2"], ["s1", "s3"],
|
||||
["s3", "s2"], ["s2", "h2"], ["s3", "h3"]
|
||||
]
|
||||
}
|
||||
5
exercises/mri/Makefile
Normal file
@@ -0,0 +1,5 @@
|
||||
BMV2_SWITCH_EXE = simple_switch_grpc
|
||||
NO_P4 = true
|
||||
P4C_ARGS = --p4runtime-file $(basename $@).p4info --p4runtime-format text
|
||||
|
||||
include ../../utils/Makefile
|
||||
|
After Width: | Height: | Size: 154 B |
244
exercises/mri/README.md
Normal file
@@ -0,0 +1,244 @@
|
||||
# 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 and the length of queues that every
|
||||
packet travels through. To support this functionality, you will need
|
||||
to write a P4 program that appends an ID and queue length to the
|
||||
header stack of every packet. At the destination, the sequence of
|
||||
switch IDs correspond to the path, and each ID is followed by the
|
||||
queue length of the port at switch.
|
||||
|
||||
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 prepend 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
|
||||
make
|
||||
```
|
||||
This will:
|
||||
* compile `mri.p4`, and
|
||||
* start a Mininet instance with three switches (`s1`, `s2`, `s3`) configured
|
||||
in a triangle. There are 5 hosts. `h1` and `h11` are connected to `s1`.
|
||||
`h2` and `h22` are connected to `s2` and `h3` is connected to `s3`.
|
||||
* The hosts are assigned IPs of `10.0.1.1`, `10.0.2.2`, etc
|
||||
(`10.0.<Switchid>.<hostID>`).
|
||||
* The control plane programs the P4 tables in each switch based on
|
||||
`sx-runtime.json`
|
||||
|
||||
2. We want to send a low rate traffic from `h1` to `h2` and a high
|
||||
rate iperf traffic from `h11` to `h22`. The link between `s1` and
|
||||
`s2` is common between the flows and is a bottleneck because we
|
||||
reduced its bandwidth to 512kbps in topology.json. Therefore, if we
|
||||
capture packets at `h2`, we should see high queue size for that
|
||||
link.
|
||||
|
||||

|
||||
|
||||
3. You should now see a Mininet command prompt. Open four terminals
|
||||
for `h1`, `h11`, `h2`, `h22`, respectively:
|
||||
```bash
|
||||
mininet> xterm h1 h11 h2 h22
|
||||
```
|
||||
3. In `h2`'s xterm, start the server that captures packets:
|
||||
```bash
|
||||
./receive.py
|
||||
```
|
||||
4. in `h22`'s xterm, start the iperf UDP server:
|
||||
```bash
|
||||
iperf -s -u
|
||||
```
|
||||
|
||||
5. In `h1`'s xterm, send one packet per second to `h2` using send.py
|
||||
say for 30 seconds:
|
||||
```bash
|
||||
./send.py 10.0.2.2 "P4 is cool" 30
|
||||
```
|
||||
The message "P4 is cool" should be received in `h2`'s xterm,
|
||||
6. In `h11`'s xterm, start iperf client sending for 15 seconds
|
||||
```bash
|
||||
iperf -c 10.0.2.22 -t 15 -u
|
||||
```
|
||||
7. At `h2`, the MRI header has no hop info (`count=0`)
|
||||
8. type `exit` to close each xterm window
|
||||
|
||||
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
|
||||
`make` script will install packet-processing rules in the tables of
|
||||
each switch. These are defined in the `sX-runtime.json` 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 switch ID and
|
||||
Queue depth fields of each switch hop the packet goes through.
|
||||
|
||||
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_swtrace` state,
|
||||
this field should be decremented. The `parse_swtrace` 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 in egress, `swtrace` to
|
||||
store the switch ID and queue depth, and actions that 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 ingress 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. At egress, an action (called `add_swtrace`) that will add the
|
||||
switch ID and queue depth.
|
||||
8. An egress control that applies a table (`swtrace`) to store the
|
||||
switch ID and queue depth, and calls `add_swtrace`.
|
||||
9. A deparser that selects the order in which fields inserted into the outgoing
|
||||
packet.
|
||||
10. A `package` instantiation supplied with the parser, control,
|
||||
checksum verification and recomputation and deparser.
|
||||
|
||||
## 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 sequence of
|
||||
switches through which the packet traveled plus the corresponding
|
||||
queue depths. 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. The queue depth at the common link (from s1 to
|
||||
s2) is high.
|
||||
|
||||
```
|
||||
got a packet
|
||||
###[ Ethernet ]###
|
||||
dst = 00:04:00:02:00:02
|
||||
src = f2:ed:e6:df:4e:fa
|
||||
type = 0x800
|
||||
###[ IP ]###
|
||||
version = 4L
|
||||
ihl = 10L
|
||||
tos = 0x0
|
||||
len = 42
|
||||
id = 1
|
||||
flags =
|
||||
frag = 0L
|
||||
ttl = 62
|
||||
proto = udp
|
||||
chksum = 0x60c0
|
||||
src = 10.0.1.1
|
||||
dst = 10.0.2.2
|
||||
\options \
|
||||
|###[ MRI ]###
|
||||
| copy_flag = 0L
|
||||
| optclass = control
|
||||
| option = 31L
|
||||
| length = 20
|
||||
| count = 2
|
||||
| \swtraces \
|
||||
| |###[ SwitchTrace ]###
|
||||
| | swid = 2
|
||||
| | qdepth = 0
|
||||
| |###[ SwitchTrace ]###
|
||||
| | swid = 1
|
||||
| | qdepth = 17
|
||||
###[ UDP ]###
|
||||
sport = 1234
|
||||
dport = 4321
|
||||
len = 18
|
||||
chksum = 0x1c7b
|
||||
###[ Raw ]###
|
||||
load = 'P4 is cool'
|
||||
|
||||
```
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
There are several ways that problems might manifest:
|
||||
|
||||
1. `mri.p4` fails to compile. In this case, `make` will report the
|
||||
error emitted from the compiler and stop.
|
||||
2. `mri.p4` compiles but does not support the control plane rules in
|
||||
the `sX-runtime.json` files that `make` tries to install using the BMv2 CLI.
|
||||
In this case, `make` will log the CLI tool output in the `logs` directory.
|
||||
Use these error messages to fix your `mri.p4` implementation.
|
||||
3. `mri.p4` compiles, and the control plane rules are installed, 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.
|
||||
4. `mri.p4` compiles and all rules are installed. Packets go through
|
||||
and the logs show that the queue length is always 0. Then either
|
||||
reduce the link bandwidth in `topology.json`.
|
||||
|
||||
#### 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 on to [Source
|
||||
Routing](../source_routing).
|
||||
|
||||
281
exercises/mri/mri.p4
Normal file
@@ -0,0 +1,281 @@
|
||||
/* -*- 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 = {
|
||||
add_swtrace;
|
||||
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;
|
||||
57
exercises/mri/receive.py
Executable file
@@ -0,0 +1,57 @@
|
||||
#!/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 PacketListField, 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 SwitchTrace(Packet):
|
||||
fields_desc = [ IntField("swid", 0),
|
||||
IntField("qdepth", 0)]
|
||||
def extract_padding(self, p):
|
||||
return "", p
|
||||
|
||||
class IPOption_MRI(IPOption):
|
||||
name = "MRI"
|
||||
option = 31
|
||||
fields_desc = [ _IPOption_HDR,
|
||||
FieldLenField("length", None, fmt="B",
|
||||
length_of="swtraces",
|
||||
adjust=lambda pkt,l:l*2+4),
|
||||
ShortField("count", 0),
|
||||
PacketListField("swtraces",
|
||||
[],
|
||||
SwitchTrace,
|
||||
count_from=lambda pkt:(pkt.count*1)) ]
|
||||
|
||||
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()
|
||||
60
exercises/mri/s1-runtime.json
Normal file
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"target": "bmv2",
|
||||
"p4info": "build/mri.p4info",
|
||||
"bmv2_json": "build/mri.json",
|
||||
"table_entries": [
|
||||
{
|
||||
"table": "MyEgress.swtrace",
|
||||
"default_action": true,
|
||||
"action_name": "MyEgress.add_swtrace",
|
||||
"action_params": {
|
||||
"swid": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.ipv4_lpm",
|
||||
"match": {
|
||||
"hdr.ipv4.dstAddr": ["10.0.1.1", 32]
|
||||
},
|
||||
"action_name": "MyIngress.ipv4_forward",
|
||||
"action_params": {
|
||||
"dstAddr": "00:00:00:00:01:01",
|
||||
"port": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.ipv4_lpm",
|
||||
"match": {
|
||||
"hdr.ipv4.dstAddr": ["10.0.1.11", 32]
|
||||
},
|
||||
"action_name": "MyIngress.ipv4_forward",
|
||||
"action_params": {
|
||||
"dstAddr": "00:00:00:00:01:0b",
|
||||
"port": 1
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"table": "MyIngress.ipv4_lpm",
|
||||
"match": {
|
||||
"hdr.ipv4.dstAddr": ["10.0.2.0", 24]
|
||||
},
|
||||
"action_name": "MyIngress.ipv4_forward",
|
||||
"action_params": {
|
||||
"dstAddr": "00:00:00:02:03:00",
|
||||
"port": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.ipv4_lpm",
|
||||
"match": {
|
||||
"hdr.ipv4.dstAddr": ["10.0.3.0", 24]
|
||||
},
|
||||
"action_name": "MyIngress.ipv4_forward",
|
||||
"action_params": {
|
||||
"dstAddr": "00:00:00:03:02:00",
|
||||
"port": 4
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
59
exercises/mri/s2-runtime.json
Normal file
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"target": "bmv2",
|
||||
"p4info": "build/mri.p4info",
|
||||
"bmv2_json": "build/mri.json",
|
||||
"table_entries": [
|
||||
{
|
||||
"table": "MyEgress.swtrace",
|
||||
"default_action": true,
|
||||
"action_name": "MyEgress.add_swtrace",
|
||||
"action_params": {
|
||||
"swid": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.ipv4_lpm",
|
||||
"match": {
|
||||
"hdr.ipv4.dstAddr": ["10.0.2.2", 32]
|
||||
},
|
||||
"action_name": "MyIngress.ipv4_forward",
|
||||
"action_params": {
|
||||
"dstAddr": "00:00:00:00:02:02",
|
||||
"port": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.ipv4_lpm",
|
||||
"match": {
|
||||
"hdr.ipv4.dstAddr": ["10.0.2.22", 32]
|
||||
},
|
||||
"action_name": "MyIngress.ipv4_forward",
|
||||
"action_params": {
|
||||
"dstAddr": "00:00:00:00:02:16",
|
||||
"port": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.ipv4_lpm",
|
||||
"match": {
|
||||
"hdr.ipv4.dstAddr": ["10.0.1.0", 24]
|
||||
},
|
||||
"action_name": "MyIngress.ipv4_forward",
|
||||
"action_params": {
|
||||
"dstAddr": "00:00:00:01:03:00",
|
||||
"port": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.ipv4_lpm",
|
||||
"match": {
|
||||
"hdr.ipv4.dstAddr": ["10.0.3.0", 24]
|
||||
},
|
||||
"action_name": "MyIngress.ipv4_forward",
|
||||
"action_params": {
|
||||
"dstAddr": "00:00:00:03:03:00",
|
||||
"port": 4
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
48
exercises/mri/s3-runtime.json
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"target": "bmv2",
|
||||
"p4info": "build/mri.p4info",
|
||||
"bmv2_json": "build/mri.json",
|
||||
"table_entries": [
|
||||
{
|
||||
"table": "MyEgress.swtrace",
|
||||
"default_action": true,
|
||||
"action_name": "MyEgress.add_swtrace",
|
||||
"action_params": {
|
||||
"swid": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.ipv4_lpm",
|
||||
"match": {
|
||||
"hdr.ipv4.dstAddr": ["10.0.3.3", 32]
|
||||
},
|
||||
"action_name": "MyIngress.ipv4_forward",
|
||||
"action_params": {
|
||||
"dstAddr": "00:00:00:00:03:03",
|
||||
"port": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.ipv4_lpm",
|
||||
"match": {
|
||||
"hdr.ipv4.dstAddr": ["10.0.1.0", 24]
|
||||
},
|
||||
"action_name": "MyIngress.ipv4_forward",
|
||||
"action_params": {
|
||||
"dstAddr": "00:00:00:01:04:00",
|
||||
"port": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "MyIngress.ipv4_lpm",
|
||||
"match": {
|
||||
"hdr.ipv4.dstAddr": ["10.0.2.0", 24]
|
||||
},
|
||||
"action_name": "MyIngress.ipv4_forward",
|
||||
"action_params": {
|
||||
"dstAddr": "00:00:00:02:04:00",
|
||||
"port": 3
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
78
exercises/mri/send.py
Executable file
@@ -0,0 +1,78 @@
|
||||
#!/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, PacketListField
|
||||
from scapy.layers.inet import _IPOption_HDR
|
||||
|
||||
from time import sleep
|
||||
|
||||
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 SwitchTrace(Packet):
|
||||
fields_desc = [ IntField("swid", 0),
|
||||
IntField("qdepth", 0)]
|
||||
def extract_padding(self, p):
|
||||
return "", p
|
||||
|
||||
class IPOption_MRI(IPOption):
|
||||
name = "MRI"
|
||||
option = 31
|
||||
fields_desc = [ _IPOption_HDR,
|
||||
FieldLenField("length", None, fmt="B",
|
||||
length_of="swtraces",
|
||||
adjust=lambda pkt,l:l*2+4),
|
||||
ShortField("count", 0),
|
||||
PacketListField("swtraces",
|
||||
[],
|
||||
SwitchTrace,
|
||||
count_from=lambda pkt:(pkt.count*1)) ]
|
||||
|
||||
|
||||
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, options = IPOption_MRI(count=0,
|
||||
swtraces=[])) / 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,
|
||||
# swtraces=[SwitchTrace(swid=0,qdepth=0), SwitchTrace(swid=1,qdepth=0)])) / UDP(
|
||||
# dport=4321, sport=1234) / sys.argv[2]
|
||||
pkt.show2()
|
||||
#hexdump(pkt)
|
||||
try:
|
||||
for i in range(int(sys.argv[3])):
|
||||
sendp(pkt, iface=iface)
|
||||
sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
BIN
exercises/mri/setup.png
Normal file
|
After Width: | Height: | Size: 221 KiB |
273
exercises/mri/solution/mri.p4
Normal file
@@ -0,0 +1,273 @@
|
||||
/* -*- 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 {
|
||||
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_swtrace;
|
||||
}
|
||||
}
|
||||
|
||||
state parse_swtrace {
|
||||
packet.extract(hdr.swtraces.next);
|
||||
meta.parser_metadata.remaining = meta.parser_metadata.remaining - 1;
|
||||
transition select(meta.parser_metadata.remaining) {
|
||||
0 : accept;
|
||||
default: parse_swtrace;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*************************************************************************
|
||||
************ 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) {
|
||||
hdr.mri.count = hdr.mri.count + 1;
|
||||
hdr.swtraces.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.swtraces[0].setValid();
|
||||
hdr.swtraces[0].swid = swid;
|
||||
hdr.swtraces[0].qdepth = (qdepth_t)standard_metadata.deq_qdepth;
|
||||
|
||||
hdr.ipv4.ihl = hdr.ipv4.ihl + 2;
|
||||
hdr.ipv4_option.optionLength = hdr.ipv4_option.optionLength + 8;
|
||||
hdr.ipv4.totalLen = hdr.ipv4.totalLen + 8;
|
||||
}
|
||||
|
||||
table swtrace {
|
||||
actions = {
|
||||
add_swtrace;
|
||||
NoAction;
|
||||
}
|
||||
default_action = NoAction();
|
||||
}
|
||||
|
||||
apply {
|
||||
if (hdr.mri.isValid()) {
|
||||
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);
|
||||
packet.emit(hdr.ipv4_option);
|
||||
packet.emit(hdr.mri);
|
||||
packet.emit(hdr.swtraces);
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
*********************** S W I T C H *******************************
|
||||
*************************************************************************/
|
||||
|
||||
V1Switch(
|
||||
MyParser(),
|
||||
MyVerifyChecksum(),
|
||||
MyIngress(),
|
||||
MyEgress(),
|
||||
MyComputeChecksum(),
|
||||
MyDeparser()
|
||||
) main;
|
||||
18
exercises/mri/topology.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"hosts": [
|
||||
"h1",
|
||||
"h2",
|
||||
"h3",
|
||||
"h11",
|
||||
"h22"
|
||||
],
|
||||
"switches": {
|
||||
"s1": { "runtime_json" : "s1-runtime.json" },
|
||||
"s2": { "runtime_json" : "s2-runtime.json" },
|
||||
"s3": { "runtime_json" : "s3-runtime.json" }
|
||||
},
|
||||
"links": [
|
||||
["h1", "s1"], ["h11", "s1"], ["s1", "s2", "0", 0.5], ["s1", "s3"],
|
||||
["s3", "s2"], ["s2", "h2"], ["s2", "h22"], ["s3", "h3"]
|
||||
]
|
||||
}
|
||||
5
exercises/p4runtime/Makefile
Normal file
@@ -0,0 +1,5 @@
|
||||
BMV2_SWITCH_EXE = simple_switch_grpc
|
||||
NO_P4 = true
|
||||
P4C_ARGS = --p4runtime-file $(basename $@).p4info --p4runtime-format text
|
||||
|
||||
include ../../utils/Makefile
|
||||
|
After Width: | Height: | Size: 154 B |
188
exercises/p4runtime/README.md
Normal file
@@ -0,0 +1,188 @@
|
||||
# Implementing a Control Plane using P4 Runtime
|
||||
|
||||
## Introduction
|
||||
|
||||
In this exercise, we will be using P4 Runtime to send flow entries to the
|
||||
switch instead of using the switch's CLI. We will be building on the same P4
|
||||
program that you used in the [basic_tunnel](../basic_tunnel) exercise. The
|
||||
P4 program has been renamed to `advanced_tunnel.py` and has been augmented
|
||||
with two counters (`ingressTunnelCounter`, `egressTunnelCounter`) and
|
||||
two new actions (`myTunnel_ingress`, `myTunnel_egress`).
|
||||
|
||||
You will use the starter program, `mycontroller.py`, and a few helper
|
||||
libraries in the `p4runtime_lib` directory to create the table entries
|
||||
necessary to tunnel traffic between host 1 and 2.
|
||||
|
||||
> **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 `mycontroller.py`,
|
||||
and it will install only some of the rules that you need to tunnel traffic between
|
||||
two hosts.
|
||||
|
||||
Let's first compile the new P4 program, start the network, use `mycontroller.py`
|
||||
to install a few rules, and look at the `ingressTunnelCounter` to see that things
|
||||
are working as expected.
|
||||
|
||||
1. In your shell, run:
|
||||
```bash
|
||||
make
|
||||
```
|
||||
This will:
|
||||
* compile `advanced_tunnel.p4`,
|
||||
* start a Mininet instance with three switches (`s1`, `s2`, `s3`)
|
||||
configured in a triangle, each connected to one host (`h1`, `h2`, `h3`), and
|
||||
* assign IPs of `10.0.1.1`, `10.0.2.2`, `10.0.3.3` to the respective hosts.
|
||||
|
||||
2. You should now see a Mininet command prompt. Start a ping between h1 and h2:
|
||||
```bash
|
||||
mininet> h1 ping h2
|
||||
```
|
||||
Because there are no rules on the switches, you should **not** receive any
|
||||
replies yet. You should leave the ping running in this shell.
|
||||
|
||||
3. Open another shell and run the starter code:
|
||||
```bash
|
||||
cd ~/tutorials/P4D2_2017_Fall/exercises/p4runtime
|
||||
./mycontroller.py
|
||||
```
|
||||
This will install the `advanced_tunnel.p4` program on the switches and push the
|
||||
tunnel ingress rules.
|
||||
The program prints the tunnel ingress and egress counters every 2 seconds.
|
||||
You should see the ingress tunnel counter for s1 increasing:
|
||||
```
|
||||
s1 ingressTunnelCounter 100: 2 packets
|
||||
```
|
||||
The other counters should remain at zero.
|
||||
|
||||
4. Press `Ctrl-C` to the second shell to stop `mycontroller.py`
|
||||
|
||||
Each switch is currently mapping traffic into tunnels based on the destination IP
|
||||
address. Your job is to write the rules that forward the traffic between the switches
|
||||
based on the tunnel ID.
|
||||
|
||||
### Potential Issues
|
||||
|
||||
If you see the following error message when running `mycontroller.py`, then
|
||||
the gRPC server is not running on one or more switches.
|
||||
|
||||
```
|
||||
p4@p4:~/tutorials/P4D2_2017_Fall/exercises/p4runtime$ ./mycontroller.py
|
||||
...
|
||||
grpc._channel._Rendezvous: <_Rendezvous of RPC that terminated with (StatusCode.UNAVAILABLE, Connect Failed)>
|
||||
```
|
||||
|
||||
You can check to see which of gRPC ports are listening on the machine by running:
|
||||
```bash
|
||||
sudo netstat -lpnt
|
||||
```
|
||||
|
||||
The easiest solution is to enter `Ctrl-D` or `exit` in the `mininet>` prompt,
|
||||
and re-run `make`.
|
||||
|
||||
### 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. In this case,
|
||||
`mycontroller.py` implements our control plane, instead of installing static
|
||||
table entries like we have in the previous exercises.
|
||||
|
||||
**Important:** A P4 program also defines the interface between the
|
||||
switch pipeline and control plane. This interface is defined in the
|
||||
`advanced_tunnel.p4info` file. The table entries that you build in `mycontroller.py`
|
||||
refer to specific tables, keys, and actions by name, and we use a P4Info helper
|
||||
to convert the names into the IDs that are required for P4 Runtime. Any changes
|
||||
in the P4 program that add or rename tables, keys, or actions will need to be
|
||||
reflected in your table entries.
|
||||
|
||||
## Step 2: Implement Tunnel Forwarding
|
||||
|
||||
The `mycontroller.py` file is a basic controller plane that does the following:
|
||||
1. Establishes a gRPC connection to the switches for the P4 Runtime service.
|
||||
2. Pushes the P4 program to each switch.
|
||||
3. Writes tunnel ingress and tunnel egress rules for two tunnels between h1 and h2.
|
||||
4. Reads tunnel ingress and egress counters every 2 seconds.
|
||||
|
||||
It also contains comments marked with `TODO` which indicate the functionality
|
||||
that you need to implement.
|
||||
|
||||
Your job will be to write the tunnel transit rule in the `writeTunnelRules` function
|
||||
that will match on tunnel ID and forward packets to the next hop.
|
||||
|
||||

|
||||
|
||||
In this exercise, you will be interacting with some of the classes and methods in
|
||||
the `p4runtime_lib` directory. Here is a summary of each of the files in the directory:
|
||||
- `helper.py`
|
||||
- Contains the `P4InfoHelper` class which is used to parse the `p4info` files.
|
||||
- Provides translation methods from entity name to and from ID number.
|
||||
- Builds P4 program-dependent sections of P4 Runtime table entries.
|
||||
- `switch.py`
|
||||
- Contains the `SwitchConnection` class which grabs the gRPC client stub, and
|
||||
establishes connections to the switches.
|
||||
- Provides helper methods that construct the P4 Runtime protocol buffer messages
|
||||
and makes the P4 Runtime gRPC service calls.
|
||||
- `bmv2.py`
|
||||
- Contains `Bmv2SwitchConnection` which extends `SwitchConnections` and provides
|
||||
the BMv2-specific device payload to load the P4 program.
|
||||
- `convert.py`
|
||||
- Provides convenience methods to encode and decode from friendly strings and
|
||||
numbers to the byte strings required for the protocol buffer messages.
|
||||
- Used by `helper.py`
|
||||
|
||||
|
||||
## Step 3: Run your solution
|
||||
|
||||
Follow the instructions from Step 1. If your Mininet network is still running,
|
||||
you will just need to run the following in your second shell:
|
||||
```bash
|
||||
./my_controller.py
|
||||
```
|
||||
|
||||
You should start to see ICMP replies in your Mininet prompt, and you should start to
|
||||
see the values for all counters start to increment.
|
||||
|
||||
### Extra Credit and Food for Thought
|
||||
|
||||
You might notice that the rules that are printed by `mycontroller.py` contain the entity
|
||||
IDs rather than the table names. You can use the P4Info helper to translate these IDs
|
||||
into entry names.
|
||||
|
||||
Also, you may want to think about the following:
|
||||
- What assumptions about the topology are baked into your implementation? How would you
|
||||
need to change it for a more realistic network?
|
||||
|
||||
- Why are the byte counters different between the ingress and egress counters?
|
||||
|
||||
- What is the TTL in the ICMP replies? Why is it the value that it is?
|
||||
Hint: The default TTL is 64 for packets sent by the hosts.
|
||||
|
||||
If you are interested, you can find the protocol buffer and gRPC definitions here:
|
||||
- [P4 Runtime](https://github.com/p4lang/PI/blob/master/proto/p4/p4runtime.proto)
|
||||
- [P4 Info](https://github.com/p4lang/PI/blob/master/proto/p4/config/p4info.proto)
|
||||
|
||||
#### Cleaning up Mininet
|
||||
|
||||
If the Mininet shell crashes, it may leave a Mininet instance
|
||||
running in the background. Use the following command to clean up:
|
||||
```bash
|
||||
make clean
|
||||
```
|
||||
|
||||
#### Running the reference solution
|
||||
|
||||
To run the reference solution, you should run the following command from the
|
||||
`~/tutorials/P4D2_2017_Fall/exercises/p4runtime` directory:
|
||||
```bash
|
||||
solution/my_controller.py
|
||||
```
|
||||
|
||||
|
||||
## Next Steps
|
||||
|
||||
Congratulations, your implementation works! Move onto the next assignment
|
||||
[ecn](../ecn)!
|
||||
|
||||
238
exercises/p4runtime/advanced_tunnel.p4
Executable file
@@ -0,0 +1,238 @@
|
||||
/* -*- P4_16 -*- */
|
||||
#include <core.p4>
|
||||
#include <v1model.p4>
|
||||
|
||||
const bit<16> TYPE_MYTUNNEL = 0x1212;
|
||||
const bit<16> TYPE_IPV4 = 0x800;
|
||||
const bit<32> MAX_TUNNEL_ID = 1 << 16;
|
||||
|
||||
/*************************************************************************
|
||||
*********************** 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 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 */
|
||||
}
|
||||
|
||||
struct headers {
|
||||
ethernet_t ethernet;
|
||||
myTunnel_t myTunnel;
|
||||
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_MYTUNNEL: parse_myTunnel;
|
||||
TYPE_IPV4: parse_ipv4;
|
||||
default: accept;
|
||||
}
|
||||
}
|
||||
|
||||
state parse_myTunnel {
|
||||
packet.extract(hdr.myTunnel);
|
||||
transition select(hdr.myTunnel.proto_id) {
|
||||
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) {
|
||||
|
||||
counter(MAX_TUNNEL_ID, CounterType.packets_and_bytes) ingressTunnelCounter;
|
||||
counter(MAX_TUNNEL_ID, CounterType.packets_and_bytes) egressTunnelCounter;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
action myTunnel_ingress(bit<16> dst_id) {
|
||||
hdr.myTunnel.setValid();
|
||||
hdr.myTunnel.dst_id = dst_id;
|
||||
hdr.myTunnel.proto_id = hdr.ethernet.etherType;
|
||||
hdr.ethernet.etherType = TYPE_MYTUNNEL;
|
||||
ingressTunnelCounter.count((bit<32>) hdr.myTunnel.dst_id);
|
||||
}
|
||||
|
||||
action myTunnel_forward(egressSpec_t port) {
|
||||
standard_metadata.egress_spec = port;
|
||||
}
|
||||
|
||||
action myTunnel_egress(macAddr_t dstAddr, egressSpec_t port) {
|
||||
standard_metadata.egress_spec = port;
|
||||
hdr.ethernet.dstAddr = dstAddr;
|
||||
hdr.ethernet.etherType = hdr.myTunnel.proto_id;
|
||||
hdr.myTunnel.setInvalid();
|
||||
egressTunnelCounter.count((bit<32>) hdr.myTunnel.dst_id);
|
||||
}
|
||||
|
||||
table ipv4_lpm {
|
||||
key = {
|
||||
hdr.ipv4.dstAddr: lpm;
|
||||
}
|
||||
actions = {
|
||||
ipv4_forward;
|
||||
myTunnel_ingress;
|
||||
drop;
|
||||
NoAction;
|
||||
}
|
||||
size = 1024;
|
||||
default_action = NoAction();
|
||||
}
|
||||
|
||||
table myTunnel_exact {
|
||||
key = {
|
||||
hdr.myTunnel.dst_id: exact;
|
||||
}
|
||||
actions = {
|
||||
myTunnel_forward;
|
||||
myTunnel_egress;
|
||||
drop;
|
||||
}
|
||||
size = 1024;
|
||||
default_action = drop();
|
||||
}
|
||||
|
||||
apply {
|
||||
if (hdr.ipv4.isValid() && !hdr.myTunnel.isValid()) {
|
||||
// Process only non-tunneled IPv4 packets.
|
||||
ipv4_lpm.apply();
|
||||
}
|
||||
|
||||
if (hdr.myTunnel.isValid()) {
|
||||
// Process all tunneled packets.
|
||||
myTunnel_exact.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.myTunnel);
|
||||
packet.emit(hdr.ipv4);
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
*********************** S W I T C H *******************************
|
||||
*************************************************************************/
|
||||
|
||||
V1Switch(
|
||||
MyParser(),
|
||||
MyVerifyChecksum(),
|
||||
MyIngress(),
|
||||
MyEgress(),
|
||||
MyComputeChecksum(),
|
||||
MyDeparser()
|
||||
) main;
|
||||
213
exercises/p4runtime/mycontroller.py
Executable file
@@ -0,0 +1,213 @@
|
||||
#!/usr/bin/env python2
|
||||
import argparse
|
||||
import grpc
|
||||
import os
|
||||
import sys
|
||||
from time import sleep
|
||||
|
||||
# Import P4Runtime lib from parent utils dir
|
||||
# Probably there's a better way of doing this.
|
||||
sys.path.append(
|
||||
os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
||||
'../../utils/'))
|
||||
import p4runtime_lib.bmv2
|
||||
from p4runtime_lib.switch import ShutdownAllSwitchConnections
|
||||
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="MyIngress.ipv4_lpm",
|
||||
match_fields={
|
||||
"hdr.ipv4.dstAddr": (dst_ip_addr, 32)
|
||||
},
|
||||
action_name="MyIngress.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="MyIngress.myTunnel_exact",
|
||||
match_fields={
|
||||
"hdr.myTunnel.dst_id": tunnel_id
|
||||
},
|
||||
action_name="MyIngress.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 in 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 printGrpcError(e):
|
||||
print "gRPC Error:", e.details(),
|
||||
status_code = e.code()
|
||||
print "(%s)" % status_code.name,
|
||||
traceback = sys.exc_info()[2]
|
||||
print "[%s:%d]" % (traceback.tb_frame.f_code.co_filename, traceback.tb_lineno)
|
||||
|
||||
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)
|
||||
|
||||
try:
|
||||
# Create a switch connection object for s1 and s2;
|
||||
# this is backed by a P4 Runtime gRPC connection.
|
||||
# Also, dump all P4Runtime messages sent to switch to given txt files.
|
||||
s1 = p4runtime_lib.bmv2.Bmv2SwitchConnection(
|
||||
name='s1',
|
||||
address='127.0.0.1:50051',
|
||||
device_id=0,
|
||||
proto_dump_file='logs/s1-p4runtime-requests.txt')
|
||||
s2 = p4runtime_lib.bmv2.Bmv2SwitchConnection(
|
||||
name='s2',
|
||||
address='127.0.0.1:50052',
|
||||
device_id=1,
|
||||
proto_dump_file='logs/s2-p4runtime-requests.txt')
|
||||
|
||||
# Send master arbitration update message to establish this controller as
|
||||
# master (required by P4Runtime before performing any other write operation)
|
||||
s1.MasterArbitrationUpdate()
|
||||
s2.MasterArbitrationUpdate()
|
||||
|
||||
# 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 s1"
|
||||
s2.SetForwardingPipelineConfig(p4info=p4info_helper.p4info,
|
||||
bmv2_json_file_path=bmv2_file_path)
|
||||
print "Installed P4 Program using SetForwardingPipelineConfig on s2"
|
||||
|
||||
# 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
|
||||
while True:
|
||||
sleep(2)
|
||||
print '\n----- Reading tunnel counters -----'
|
||||
printCounter(p4info_helper, s1, "MyIngress.ingressTunnelCounter", 100)
|
||||
printCounter(p4info_helper, s2, "MyIngress.egressTunnelCounter", 100)
|
||||
printCounter(p4info_helper, s2, "MyIngress.ingressTunnelCounter", 200)
|
||||
printCounter(p4info_helper, s1, "MyIngress.egressTunnelCounter", 200)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print " Shutting down."
|
||||
except grpc.RpcError as e:
|
||||
printGrpcError(e)
|
||||
|
||||
ShutdownAllSwitchConnections()
|
||||
|
||||
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)
|
||||
222
exercises/p4runtime/solution/mycontroller.py
Executable file
@@ -0,0 +1,222 @@
|
||||
#!/usr/bin/env python2
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
from time import sleep
|
||||
|
||||
# Import P4Runtime lib from parent utils dir
|
||||
# Probably there's a better way of doing this.
|
||||
sys.path.append(
|
||||
os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
||||
'../../utils/'))
|
||||
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="MyIngress.ipv4_lpm",
|
||||
match_fields={
|
||||
"hdr.ipv4.dstAddr": (dst_ip_addr, 32)
|
||||
},
|
||||
action_name="MyIngress.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
|
||||
table_entry = p4info_helper.buildTableEntry(
|
||||
table_name="MyIngress.myTunnel_exact",
|
||||
match_fields={
|
||||
"hdr.myTunnel.dst_id": tunnel_id
|
||||
},
|
||||
action_name="MyIngress.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="MyIngress.myTunnel_exact",
|
||||
match_fields={
|
||||
"hdr.myTunnel.dst_id": tunnel_id
|
||||
},
|
||||
action_name="MyIngress.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.
|
||||
# Also, dump all P4Runtime messages sent to switch to given txt files.
|
||||
s1 = p4runtime_lib.bmv2.Bmv2SwitchConnection(
|
||||
name='s1',
|
||||
address='127.0.0.1:50051',
|
||||
device_id=0,
|
||||
proto_dump_file='logs/s1-p4runtime-requests.txt')
|
||||
s2 = p4runtime_lib.bmv2.Bmv2SwitchConnection(
|
||||
name='s2',
|
||||
address='127.0.0.1:50052',
|
||||
device_id=1,
|
||||
proto_dump_file='logs/s2-p4runtime-requests.txt')
|
||||
|
||||
# Send master arbitration update message to establish this controller as
|
||||
# master (required by P4Runtime before performing any other write operation)
|
||||
s1.MasterArbitrationUpdate()
|
||||
s2.MasterArbitrationUpdate()
|
||||
|
||||
# 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 s1"
|
||||
s2.SetForwardingPipelineConfig(p4info=p4info_helper.p4info,
|
||||
bmv2_json_file_path=bmv2_file_path)
|
||||
print "Installed P4 Program using SetForwardingPipelineConfig on s2"
|
||||
|
||||
# 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, "MyIngress.ingressTunnelCounter", 100)
|
||||
printCounter(p4info_helper, s2, "MyIngress.egressTunnelCounter", 100)
|
||||
printCounter(p4info_helper, s2, "MyIngress.ingressTunnelCounter", 200)
|
||||
printCounter(p4info_helper, s1, "MyIngress.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)
|
||||
16
exercises/p4runtime/topology.json
Executable file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"hosts": [
|
||||
"h1",
|
||||
"h2",
|
||||
"h3"
|
||||
],
|
||||
"switches": {
|
||||
"s1": {},
|
||||
"s2": {},
|
||||
"s3": {}
|
||||
},
|
||||
"links": [
|
||||
["h1", "s1"], ["s1", "s2"], ["s1", "s3"],
|
||||
["s3", "s2"], ["s2", "h2"], ["s3", "h3"]
|
||||
]
|
||||
}
|
||||
5
exercises/source_routing/Makefile
Normal file
@@ -0,0 +1,5 @@
|
||||
BMV2_SWITCH_EXE = simple_switch_grpc
|
||||
NO_P4 = true
|
||||
P4C_ARGS = --p4runtime-file $(basename $@).p4info --p4runtime-format text
|
||||
|
||||
include ../../utils/Makefile
|
||||
|
After Width: | Height: | Size: 154 B |
147
exercises/source_routing/README.md
Normal file
@@ -0,0 +1,147 @@
|
||||
# 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 `/tmp/p4s.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
|
||||
`<switch-name>-<interface-name>_<direction>.pcap` files contain pcap captures
|
||||
of all packets sent and received 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).
|
||||
59
exercises/source_routing/receive.py
Executable file
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env python
|
||||
import sys
|
||||
import struct
|
||||
|
||||
from scapy.all import sniff, sendp, hexdump, get_if_list, get_if_hwaddr, bind_layers
|
||||
from scapy.all import Packet, IPOption
|
||||
from scapy.all import IP, UDP, Raw, Ether
|
||||
from scapy.layers.inet import _IPOption_HDR
|
||||
from scapy.fields import *
|
||||
|
||||
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()
|
||||
|
||||
class SourceRoute(Packet):
|
||||
fields_desc = [ BitField("bos", 0, 1),
|
||||
BitField("port", 0, 15)]
|
||||
class SourceRoutingTail(Packet):
|
||||
fields_desc = [ XShortField("etherType", 0x800)]
|
||||
|
||||
bind_layers(Ether, SourceRoute, type=0x1234)
|
||||
bind_layers(SourceRoute, SourceRoute, bos=0)
|
||||
bind_layers(SourceRoute, SourceRoutingTail, bos=1)
|
||||
|
||||
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()
|
||||
6
exercises/source_routing/s1-runtime.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"target": "bmv2",
|
||||
"p4info": "build/source_routing.p4info",
|
||||
"bmv2_json": "build/source_routing.json",
|
||||
"table_entries": [ ]
|
||||
}
|
||||
6
exercises/source_routing/s2-runtime.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"target": "bmv2",
|
||||
"p4info": "build/source_routing.p4info",
|
||||
"bmv2_json": "build/source_routing.json",
|
||||
"table_entries": [ ]
|
||||
}
|
||||
6
exercises/source_routing/s3-runtime.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"target": "bmv2",
|
||||
"p4info": "build/source_routing.p4info",
|
||||
"bmv2_json": "build/source_routing.json",
|
||||
"table_entries": [ ]
|
||||
}
|
||||
73
exercises/source_routing/send.py
Executable file
@@ -0,0 +1,73 @@
|
||||
#!/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, bind_layers
|
||||
from scapy.all import Packet
|
||||
from scapy.all import Ether, IP, UDP
|
||||
from scapy.fields import *
|
||||
import readline
|
||||
|
||||
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 SourceRoute(Packet):
|
||||
fields_desc = [ BitField("bos", 0, 1),
|
||||
BitField("port", 0, 15)]
|
||||
|
||||
bind_layers(Ether, SourceRoute, type=0x1234)
|
||||
bind_layers(SourceRoute, SourceRoute, bos=0)
|
||||
bind_layers(SourceRoute, IP, bos=1)
|
||||
|
||||
def main():
|
||||
|
||||
if len(sys.argv)<2:
|
||||
print 'pass 2 arguments: <destination>'
|
||||
exit(1)
|
||||
|
||||
addr = socket.gethostbyname(sys.argv[1])
|
||||
iface = get_if()
|
||||
print "sending on interface %s to %s" % (iface, str(addr))
|
||||
|
||||
while True:
|
||||
print
|
||||
s = str(raw_input('Type space separated port nums '
|
||||
'(example: "2 3 2 2 1") or "q" to quit: '))
|
||||
if s == "q":
|
||||
break;
|
||||
print
|
||||
|
||||
i = 0
|
||||
pkt = Ether(src=get_if_hwaddr(iface), dst='ff:ff:ff:ff:ff:ff');
|
||||
for p in s.split(" "):
|
||||
try:
|
||||
pkt = pkt / SourceRoute(bos=0, port=int(p))
|
||||
i = i+1
|
||||
except ValueError:
|
||||
pass
|
||||
if pkt.haslayer(SourceRoute):
|
||||
pkt.getlayer(SourceRoute, i).bos = 1
|
||||
|
||||
pkt = pkt / IP(dst=addr) / UDP(dport=4321, sport=1234)
|
||||
pkt.show2()
|
||||
sendp(pkt, iface=iface, verbose=False)
|
||||
|
||||
#pkt = pkt / SourceRoute(bos=0, port=2) / SourceRoute(bos=0, port=3);
|
||||
#pkt = pkt / SourceRoute(bos=0, port=2) / SourceRoute(bos=0, port=2);
|
||||
#pkt = pkt / SourceRoute(bos=1, port=1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
181
exercises/source_routing/solution/source_routing.p4
Normal file
@@ -0,0 +1,181 @@
|
||||
/* -*- P4_16 -*- */
|
||||
#include <core.p4>
|
||||
#include <v1model.p4>
|
||||
|
||||
const bit<16> TYPE_IPV4 = 0x800;
|
||||
const bit<16> TYPE_SRCROUTING = 0x1234;
|
||||
|
||||
#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;
|
||||
|
||||
header ethernet_t {
|
||||
macAddr_t dstAddr;
|
||||
macAddr_t srcAddr;
|
||||
bit<16> etherType;
|
||||
}
|
||||
|
||||
header srcRoute_t {
|
||||
bit<1> bos;
|
||||
bit<15> port;
|
||||
}
|
||||
|
||||
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;
|
||||
srcRoute_t[MAX_HOPS] srcRoutes;
|
||||
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_SRCROUTING: parse_srcRouting;
|
||||
default: accept;
|
||||
}
|
||||
}
|
||||
|
||||
state parse_srcRouting {
|
||||
packet.extract(hdr.srcRoutes.next);
|
||||
transition select(hdr.srcRoutes.last.bos) {
|
||||
1: parse_ipv4;
|
||||
default: parse_srcRouting;
|
||||
}
|
||||
}
|
||||
|
||||
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 srcRoute_nhop() {
|
||||
standard_metadata.egress_spec = (bit<9>)hdr.srcRoutes[0].port;
|
||||
hdr.srcRoutes.pop_front(1);
|
||||
}
|
||||
|
||||
action srcRoute_finish() {
|
||||
hdr.ethernet.etherType = TYPE_IPV4;
|
||||
}
|
||||
|
||||
action update_ttl(){
|
||||
hdr.ipv4.ttl = hdr.ipv4.ttl - 1;
|
||||
}
|
||||
|
||||
apply {
|
||||
if (hdr.srcRoutes[0].isValid()){
|
||||
if (hdr.srcRoutes[0].bos == 1){
|
||||
srcRoute_finish();
|
||||
}
|
||||
srcRoute_nhop();
|
||||
if (hdr.ipv4.isValid()){
|
||||
update_ttl();
|
||||
}
|
||||
}else{
|
||||
drop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
**************** 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 { }
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
*********************** D E P A R S E R *******************************
|
||||
*************************************************************************/
|
||||
|
||||
control MyDeparser(packet_out packet, in headers hdr) {
|
||||
apply {
|
||||
packet.emit(hdr.ethernet);
|
||||
packet.emit(hdr.srcRoutes);
|
||||
packet.emit(hdr.ipv4);
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
*********************** S W I T C H *******************************
|
||||
*************************************************************************/
|
||||
|
||||
V1Switch(
|
||||
MyParser(),
|
||||
MyVerifyChecksum(),
|
||||
MyIngress(),
|
||||
MyEgress(),
|
||||
MyComputeChecksum(),
|
||||
MyDeparser()
|
||||
) main;
|
||||
191
exercises/source_routing/source_routing.p4
Normal file
@@ -0,0 +1,191 @@
|
||||
/* -*- P4_16 -*- */
|
||||
#include <core.p4>
|
||||
#include <v1model.p4>
|
||||
|
||||
const bit<16> TYPE_IPV4 = 0x800;
|
||||
const bit<16> TYPE_SRCROUTING = 0x1234;
|
||||
|
||||
#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;
|
||||
|
||||
header ethernet_t {
|
||||
macAddr_t dstAddr;
|
||||
macAddr_t srcAddr;
|
||||
bit<16> etherType;
|
||||
}
|
||||
|
||||
header srcRoute_t {
|
||||
bit<1> bos;
|
||||
bit<15> port;
|
||||
}
|
||||
|
||||
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;
|
||||
srcRoute_t[MAX_HOPS] srcRoutes;
|
||||
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);
|
||||
/*
|
||||
* TODO: Modify the next line to select on hdr.ethernet.etherType
|
||||
* If the value is TYPE_SRCROUTING transition to parse_srcRouting
|
||||
* otherwise transition to accept.
|
||||
*/
|
||||
transition accept;
|
||||
}
|
||||
|
||||
state parse_srcRouting {
|
||||
/*
|
||||
* TODO: extract the next entry of hdr.srcRoutes
|
||||
* while hdr.srcRoutes.last.bos is 0 transition to this state
|
||||
* otherwise parse ipv4
|
||||
*/
|
||||
transition 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 srcRoute_nhop() {
|
||||
/*
|
||||
* TODO: set standard_metadata.egress_spec
|
||||
* to the port in hdr.srcRoutes[0] and
|
||||
* pop an entry from hdr.srcRoutes
|
||||
*/
|
||||
}
|
||||
|
||||
action srcRoute_finish() {
|
||||
hdr.ethernet.etherType = TYPE_IPV4;
|
||||
}
|
||||
|
||||
action update_ttl(){
|
||||
hdr.ipv4.ttl = hdr.ipv4.ttl - 1;
|
||||
}
|
||||
|
||||
apply {
|
||||
if (hdr.srcRoutes[0].isValid()){
|
||||
/*
|
||||
* TODO: add logic to:
|
||||
* - If final srcRoutes (top of stack has bos==1):
|
||||
* - change etherType to IP
|
||||
* - choose next hop and remove top of srcRoutes stack
|
||||
*/
|
||||
|
||||
if (hdr.ipv4.isValid()){
|
||||
update_ttl();
|
||||
}
|
||||
}else{
|
||||
drop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
**************** 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 { }
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
*********************** D E P A R S E R *******************************
|
||||
*************************************************************************/
|
||||
|
||||
control MyDeparser(packet_out packet, in headers hdr) {
|
||||
apply {
|
||||
packet.emit(hdr.ethernet);
|
||||
packet.emit(hdr.srcRoutes);
|
||||
packet.emit(hdr.ipv4);
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
*********************** S W I T C H *******************************
|
||||
*************************************************************************/
|
||||
|
||||
V1Switch(
|
||||
MyParser(),
|
||||
MyVerifyChecksum(),
|
||||
MyIngress(),
|
||||
MyEgress(),
|
||||
MyComputeChecksum(),
|
||||
MyDeparser()
|
||||
) main;
|
||||
16
exercises/source_routing/topology.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"hosts": [
|
||||
"h1",
|
||||
"h2",
|
||||
"h3"
|
||||
],
|
||||
"switches": {
|
||||
"s1": { "runtime_json" : "s1-runtime.json" },
|
||||
"s2": { "runtime_json" : "s2-runtime.json" },
|
||||
"s3": { "runtime_json" : "s3-runtime.json" }
|
||||
},
|
||||
"links": [
|
||||
["h1", "s1"], ["s1", "s2"], ["s1", "s3"],
|
||||
["s3", "s2"], ["s2", "h2"], ["s3", "h3"]
|
||||
]
|
||||
}
|
||||