diff --git a/exercises/multicast/Makefile b/exercises/multicast/Makefile new file mode 100644 index 0000000..7313d14 --- /dev/null +++ b/exercises/multicast/Makefile @@ -0,0 +1,4 @@ +BMV2_SWITCH_EXE = simple_switch_grpc +TOPO = sig-topo/topology.json + +include ../../utils/Makefile diff --git a/exercises/multicast/README.md b/exercises/multicast/README.md new file mode 100644 index 0000000..8d13791 --- /dev/null +++ b/exercises/multicast/README.md @@ -0,0 +1,154 @@ +# Implementing Multicast + +## Introduction + +The objective of this exercise is to write a P4 program that multicasts packets +to a group of ports. + + +Upon receiving an Ethernet packet, the switch looks up the output port based on +the destination MAC address. If it is a miss, the switch broadcast packets on +ports belonging to a multicast group (if ingress port appears in the group, the +packet will be dropped in the egress pipeline). + + +Your switch will have a single table, which the control plane will populate with +static rules. Each rule will map an Ethernet MAC address to the output port. We +have already defined the control plane rules, so you only need to implement the +data plane logic of your P4 program. + +We will use the linear topology for this exercise. It is a single switch that +connects four hosts as follow: + + h1 h2 + | | + ---------------------------- s1 + | | + h3 h4 + +Our P4 program will be written for the V1Model architecture implemented on +P4.org's bmv2 software switch. The architecture file for the V1Model can be +found at: /usr/local/share/p4c/p4include/v1model.p4. This file describes the +interfaces of the P4 programmable elements in the architecture, the supported +externs, as well as the architecture's standard metadata fields. We encourage +you to take a look at it. + +> **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, +`multicast.p4`, which initially drops all packets. Your job will be to extend +this skeleton program to properly forward Ethernet packets. + +Before that, let's compile the incomplete `multicast.p4` and bring up a switch +in Mininet to test its behavior. + +1. In your shell, run: + ```bash + make run + ``` + This will: + * compile `multicast.p4`, and + * start the sig-topo in Mininet and configure all switches with + the appropriate P4 program + table entries, and + * configure all hosts with the commands listed in + [pod-topo/topology.json](./pod-topo/topology.json) + +2. You should now see a Mininet command prompt. Try to ping between + hosts in the topology: + ```bash + mininet> h1 ping h2 + mininet> pingall + ``` +3. 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 ping failed because each switch is programmed according to `multicast.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 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.p4.p4info.txt` 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 L2 Multicast + +The `multicast.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 `multicast.p4` will contain the following components: + +1. Header type definitions for Ethernet (`ethernet_t`) +2. An action to drop a packet, using `mark_to_drop()`. +3. **TODO:** An action (called `multicast`) that sends multiple copies of packets + to a group of output ports. +4. **TODO:** Add the `multicast` action to the list of available actions +5. **TODO:** Set `multicast` as default action for table `mac_lookup` + +## Step 3: Run your solution + +Follow the instructions from Step 1. This time, you should be able to +successfully ping between `h1`, `h2` and `h3` but not `h4` in the topology. + +6. **TODO:** Add port 4 to the multicast group in file `sig-topo/s1-runtime.json` + +### Food for thought + +Other questions to consider: + - How would you enhance your program to respond to ARP requests? + - How would you enhance your program to support MAC learning from the controller? + +### Troubleshooting + +There are several problems that might manifest as you develop your program: + +1. `multicast.p4` might fail to compile. In this case, `make run` will +report the error emitted from the compiler and halt. + +2. `multicast.p4` might compile but fail to support the control plane rules in +the `s1-runtime.json` file 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 `multicast.p4` implementation. + +3. `multicast.p4` might compile, and the control plane rules might be installed, +but the switch might not process packets in the desired way. The `logs/sX.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 +``` diff --git a/exercises/multicast/disable_ipv6.sh b/exercises/multicast/disable_ipv6.sh new file mode 100755 index 0000000..5fdc838 --- /dev/null +++ b/exercises/multicast/disable_ipv6.sh @@ -0,0 +1,3 @@ +#!/bin/bash +sudo sysctl -w net.ipv6.conf.all.disable_ipv6=1 +sudo sysctl -w net.ipv6.conf.default.disable_ipv6=1 diff --git a/exercises/multicast/multicast.p4 b/exercises/multicast/multicast.p4 new file mode 100644 index 0000000..b23ece7 --- /dev/null +++ b/exercises/multicast/multicast.p4 @@ -0,0 +1,149 @@ +/* -*- P4_16 -*- */ +#include +#include + +/************************************************************************* +*********************** 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; +} + + +struct metadata { + /* empty */ +} + +struct headers { + ethernet_t ethernet; +} + +/************************************************************************* +*********************** 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) { + default : 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(standard_metadata); + } + + // TODO: define `multicast` action to multicast packets to group 1 + // Hint: Check v1model for multicast group + + action mac_forward(egressSpec_t port) { + standard_metadata.egress_spec = port; + } + + table mac_lookup { + key = { + hdr.ethernet.dstAddr : exact; + } + actions = { + // TODO: add `multicast` action to the list of available actions + mac_forward; + drop; + } + size = 1024; + // TODO : replace default drop action by multicast + default_action = drop; + } + apply { + if (hdr.ethernet.isValid()) + mac_lookup.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 drop() { + mark_to_drop(standard_metadata); + } + + apply { + // Prune multicast packet to ingress port to preventing loop + if (standard_metadata.egress_port == standard_metadata.ingress_port) + drop(); + } +} + +/************************************************************************* +************* 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); + } +} + +/************************************************************************* +*********************** S W I T C H ******************************* +*************************************************************************/ + +V1Switch( +MyParser(), +MyVerifyChecksum(), +MyIngress(), +MyEgress(), +MyComputeChecksum(), +MyDeparser() +) main; diff --git a/exercises/multicast/sig-topo/s1-runtime.json b/exercises/multicast/sig-topo/s1-runtime.json new file mode 100644 index 0000000..22b3e38 --- /dev/null +++ b/exercises/multicast/sig-topo/s1-runtime.json @@ -0,0 +1,66 @@ +{ + "target": "bmv2", + "p4info": "build/multicast.p4.p4info.txt", + "bmv2_json": "build/multicast.json", + "table_entries": [ + { + "table": "MyIngress.mac_lookup", + "match": { + "hdr.ethernet.dstAddr": "08:00:00:00:01:11" + }, + "action_name": "MyIngress.mac_forward", + "action_params": { + "port": 1 + } + }, + { + "table": "MyIngress.mac_lookup", + "match": { + "hdr.ethernet.dstAddr": "08:00:00:00:02:22" + }, + "action_name": "MyIngress.mac_forward", + "action_params": { + "port": 2 + } + }, + { + "table": "MyIngress.mac_lookup", + "match": { + "hdr.ethernet.dstAddr": "08:00:00:00:03:33" + }, + "action_name": "MyIngress.mac_forward", + "action_params": { + "port": 3 + } + }, + { + "table": "MyIngress.mac_lookup", + "match": { + "hdr.ethernet.dstAddr": "08:00:00:00:04:44" + }, + "action_name": "MyIngress.mac_forward", + "action_params": { + "port": 4 + } + } + ], + "multicast_group_entries" : [ + { + "multicast_group_id" : 1, + "replicas" : [ + { + "egress_port" : 1, + "instance" : 1 + }, + { + "egress_port" : 2, + "instance" : 1 + }, + { + "egress_port" : 3, + "instance" : 1 + } + ] + } + ] +} diff --git a/exercises/multicast/sig-topo/topology.json b/exercises/multicast/sig-topo/topology.json new file mode 100644 index 0000000..3571cbf --- /dev/null +++ b/exercises/multicast/sig-topo/topology.json @@ -0,0 +1,18 @@ +{ + "hosts": { + "h1": {"ip": "10.0.0.1/24", "mac": "08:00:00:00:01:11", + "commands":["ip route add 10.0.0.0/24 dev eth0"]}, + "h2": {"ip": "10.0.0.2/24", "mac": "08:00:00:00:02:22", + "commands":["ip route add 10.0.0.0/24 dev eth0"]}, + "h3": {"ip": "10.0.0.3/24", "mac": "08:00:00:00:03:33", + "commands":["ip route add 10.0.0.0/24 dev eth0"]}, + "h4": {"ip": "10.0.0.4/24", "mac": "08:00:00:00:04:44", + "commands":["ip route add 10.0.0.0/24 dev eth0"]} + }, + "switches": { + "s1": { "runtime_json" : "sig-topo/s1-runtime.json" } + }, + "links": [ + ["h1", "s1-p1"], ["h2", "s1-p2"], ["h3", "s1-p3"], ["h4", "s1-p4"] + ] +} diff --git a/exercises/multicast/solution/multicast.p4 b/exercises/multicast/solution/multicast.p4 new file mode 100644 index 0000000..fcbd022 --- /dev/null +++ b/exercises/multicast/solution/multicast.p4 @@ -0,0 +1,149 @@ +/* -*- P4_16 -*- */ +#include +#include + +/************************************************************************* +*********************** 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; +} + + +struct metadata { + /* empty */ +} + +struct headers { + ethernet_t ethernet; +} + +/************************************************************************* +*********************** 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) { + default : 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(standard_metadata); + } + + action multicast() { + standard_metadata.mcast_grp = 1; + } + + action mac_forward(egressSpec_t port) { + standard_metadata.egress_spec = port; + } + + table mac_lookup { + key = { + hdr.ethernet.dstAddr : exact; + } + actions = { + multicast; + mac_forward; + drop; + } + size = 1024; + default_action = multicast; + } + apply { + if (hdr.ethernet.isValid()) + mac_lookup.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 drop() { + mark_to_drop(standard_metadata); + } + + apply { + // Prune multicast packet to ingress port to preventing loop + if (standard_metadata.egress_port == standard_metadata.ingress_port) + drop(); + } +} + +/************************************************************************* +************* 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); + } +} + +/************************************************************************* +*********************** S W I T C H ******************************* +*************************************************************************/ + +V1Switch( +MyParser(), +MyVerifyChecksum(), +MyIngress(), +MyEgress(), +MyComputeChecksum(), +MyDeparser() +) main; diff --git a/exercises/multicast/solution/s1-runtime.json b/exercises/multicast/solution/s1-runtime.json new file mode 100644 index 0000000..17b48d5 --- /dev/null +++ b/exercises/multicast/solution/s1-runtime.json @@ -0,0 +1,70 @@ +{ + "target": "bmv2", + "p4info": "build/multicast.p4.p4info.txt", + "bmv2_json": "build/multicast.json", + "table_entries": [ + { + "table": "MyIngress.mac_lookup", + "match": { + "hdr.ethernet.dstAddr": "08:00:00:00:01:11" + }, + "action_name": "MyIngress.mac_forward", + "action_params": { + "port": 1 + } + }, + { + "table": "MyIngress.mac_lookup", + "match": { + "hdr.ethernet.dstAddr": "08:00:00:00:02:22" + }, + "action_name": "MyIngress.mac_forward", + "action_params": { + "port": 2 + } + }, + { + "table": "MyIngress.mac_lookup", + "match": { + "hdr.ethernet.dstAddr": "08:00:00:00:03:33" + }, + "action_name": "MyIngress.mac_forward", + "action_params": { + "port": 3 + } + }, + { + "table": "MyIngress.mac_lookup", + "match": { + "hdr.ethernet.dstAddr": "08:00:00:00:04:44" + }, + "action_name": "MyIngress.mac_forward", + "action_params": { + "port": 4 + } + } + ], + "multicast_group_entries" : [ + { + "multicast_group_id" : 1, + "replicas" : [ + { + "egress_port" : 1, + "instance" : 1 + }, + { + "egress_port" : 2, + "instance" : 1 + }, + { + "egress_port" : 3, + "instance" : 1 + }, + { + "egress_port" : 4, + "instance" : 1 + } + ] + } + ] +}