Hi Andy and ederollora2,
Thank you both for your helpful replies.
@AndyAndy I realized from your explanation that my initial question might have been a bit misleading. My current goal is actually simpler than building a full ARP responder or controller-based learning. I simply want to implement basic L2 flooding behavior for ARP requests in P4. I want the switch to broadcast ARP packets to all ports so that the connected hosts can handle ARP resolution normally.
I have written the following P4 code to achieve this. Does this look like the correct approach for simple ARP broadcasting?
This is a copy of the tutorial, so there are still hints and other information written on it, sorry.
//P4//
// SPDX-License-Identifier: Apache-2.0
/* -*- P4_16 -*- */
#include <core.p4>
#include <v1model.p4>
const bit<16> TYPE_IPV4 = 0x800;
const bit<16> TYPE_ARP = 0x0806;
/*************************************************************************
*********************** H E A D E R S ***********************************
* This program skeleton defines minimal Ethernet and IPv4 headers and *
* a simple LPM (Longest-Prefix Match) IPv4 forwarding pipeline. *
* The exercise intentionally leaves TODOs for learners to implement. *
*************************************************************************/
typedef bit<9> egressSpec_t; // Standard BMv2 uses 9 bits for egress_spec
typedef bit<48> macAddr_t; // Ethernet MAC address
typedef bit<32> ip4Addr_t; // IPv4 address
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 ARP_t{
bit<16> hardware_type;
bit<16> prtocol_type;
bit<8> hardware_size;
bit<8> protocol_size;
bit<16> opcode;
macAddr_t src_macAddr;
ip4Addr_t src_ipAddr;
macAddr_t dst_macAddr;
ip4Addr_t dst_ipAddr;
}
struct metadata {
/* empty */
}
struct headers {
ethernet_t ethernet;
ipv4_t ipv4;
ARP_t ARP;
}
/*************************************************************************
*********************** P A R S E R *************************************
* New to P4? A typical parser does this:
* start -> parse_ethernet
* parse_ethernet:
* if etherType == TYPE_IPV4 -> parse_ipv4
* else accept
* parse_ipv4 -> accept
* This skeleton leaves the actual states as a TODO to implement later. *
*************************************************************************/
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;
TYPE_ARP : parse_ARP;
default : accept;
}
}
state parse_ipv4{
packet.extract(hdr.ipv4);
transition accept;
}
state parse_ARP{
packet.extract(hdr.ARP);
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 *******************
* High-level intent:
* - Do an LPM lookup on IPv4 dstAddr
* - On hit, call ipv4_forward(next-hop MAC, output port)
* - Otherwise, drop or NoAction (as configured) *
*************************************************************************/
control MyIngress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
action drop() {
mark_to_drop(standard_metadata);
}
/*********************************************************************
* NOTE FOR NEW READERS:
* 'ipv4_forward(dstAddr, port)' is invoked by table 'ipv4_lpm'.
*
* The values for 'dstAddr' and 'port' are *action data* supplied by
* the control plane when it installs entries in 'ipv4_lpm'.
*
* They mean:
* - dstAddr => Ethernet destination MAC for the next hop
* - port => output port (ultimately written to standard_metadata.egress_spec)
*
* Example (BMv2 simple_switch_CLI):
* table_add ipv4_lpm ipv4_forward 10.0.1.1/32 => 00:00:00:00:01:00 1
* which passes MAC=00:00:00:00:01:00 and PORT=1 as action parameters
* into ipv4_forward(dstAddr, port).
*********************************************************************/
action ipv4_forward(macAddr_t dstAddr, egressSpec_t port) {
hdr.ethernet.srcAddr = hdr.ethernet.dstAddr;
hdr.ethernet.dstAddr = dstAddr;
standard_metadata.egress_spec = port;
hdr.ipv4.ttl = hdr.ipv4.ttl - 1;
}
action ARPrequest_forward(){
standard_metadata.mcast_grp = 1;
}
action ARPreply_forward(egressSpec_t port){
standard_metadata.egress_spec = port;
}
/*********************************************************************
* LPM table for IPv4:
* - Matches on hdr.ipv4.dstAddr using longest-prefix match (lpm)
* - On hit, calls ipv4_forward with *action data* populated by the
* control plane when it installs the table entry.
*********************************************************************/
table ipv4_lpm {
key = {
hdr.ipv4.dstAddr: lpm;
}
actions = {
ipv4_forward;
drop;
NoAction;
}
size = 1024;
default_action = NoAction();
}
table ARPreply{
key = {
hdr.ethernet.dstAddr : exact;
}
actions = {
ARPreply_forward;
NoAction;
}
size = 1024;
default_action = NoAction();
}
apply {
if(hdr.ipv4.isValid()){
ipv4_lpm.apply();
}
else if(hdr.ARP.isValid()){
if(hdr.ARP.opcode==1){
ARPrequest_forward();
}
else if(hdr.ARP.opcode==2){
ARPreply.apply();
}
}
}
}
/*************************************************************************
**************** E G R E S S P R O C E S S I N G *******************
* Often used for queue marks, mirroring, or post-routing edits. *
*************************************************************************/
control MyEgress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
action drop() {
mark_to_drop(standard_metadata);
}
apply {
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 **************
* This block shows how to compute IPv4 header checksum when needed. *
*************************************************************************/
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 *******************************
* The deparser serializes headers back onto the packet in order. *
*************************************************************************/
control MyDeparser(packet_out packet, in headers hdr) {
apply {
packet.emit(hdr.ethernet);
packet.emit(hdr.ipv4);
packet.emit(hdr.ARP);
}
}
/*************************************************************************
*********************** S W I T C H ***********************************
*************************************************************************/
V1Switch(
MyParser(),
MyVerifyChecksum(),
MyIngress(),
MyEgress(),
MyComputeChecksum(),
MyDeparser()
) main;
s1-runtime.json
{
"target": "bmv2",
"p4info": "build/ARP-INT.p4.p4info.txtpb",
"bmv2_json": "build/ARP-INT.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.0.1", 32]
},
"action_name": "MyIngress.ipv4_forward",
"action_params": {
"dstAddr": "08:00:00:00:01:11",
"port": 1
}
},
{
"table": "MyIngress.ipv4_lpm",
"match": {
"hdr.ipv4.dstAddr": ["10.0.0.2", 32]
},
"action_name": "MyIngress.ipv4_forward",
"action_params": {
"dstAddr": "08:00:00:00:02:22",
"port": 2
}
},
{
"table": "MyIngress.ARPreply",
"match": {
"hdr.ethernet.dstAddr": "08:00:00:00:01:11"
},
"action_name": "MyIngress.ARPreply_forward",
"action_params": {
"port": 1
}
},
{
"table": "MyIngress.ARPreply",
"match": {
"hdr.ethernet.dstAddr": "08:00:00:00:02:22"
},
"action_name": "MyIngress.ARPreply_forward",
"action_params": {
"port": 2
}
}
],
"multicast_group_entries": [
{
"multicast_group_id": 1,
"replicas": [
{
"egress_port": 1,
"instance": 1
},
{
"egress_port": 2,
"instance": 1
}
]
}
]
}
@ ederollora2 Congratulations on your Master’s thesis! That sounds like a fantastic resource. Although my current goal is just basic flooding as mentioned above, I would love to study your full ARP implementation for my future reference. Please send the draft to: sx22013g@st.omu.ac.jp