How to add ARP protocol to basic.p4 program?

Dear Community,

I deployed basic.p4 (p4_tutorials/pl_basic/basic.p4 at main · nagmat1/p4_tutorials · GitHub) program on fabric_testbed and it is working fine. My architecture is as shown here :


In my architecture, I have 3 hosts each connected to a P4 switch and P4 switches connected to each other. I can exchange the packets properly.
Now I want to check the ethernet throughput between 3 nodes, for that I need to use Iperf3, in order to use iperf3 I need to add ARP protocol to my existing protocol. My program ARP protocol included is : p4_tutorials/pl_basic/arp_pl.p4 at main · nagmat1/p4_tutorials · GitHub .
I can still exchange UDP packets among the nodes but Iperf3 is still not working. What maybe the issue? Screenshot of my packet exchange is here :

Kind regards,
Nagmat

Hi @nagmat

did you sniff the traffic via wireshark? based on the traffic you see coming through the switch you can figure out what flow rules you need to add in the switch.

Hi @DavideS

Yeah, I am sniffing the packets. When iperf3 -s from host1(10.0.1.1) and iperf3 -c 10.0.1.1 from host2, only ARP packets I am seeing on switch2 are :

11:44:24.416145 ARP, Request who-has 10.0.1.1 tell 10.0.2.2, length 42
11:44:24.416691 ARP, Reply 10.0.1.1 is-at 00:00:00:00:00:00 (oui Ethernet), length 58
11:45:28.993620 ARP, Request who-has 10.0.1.1 tell 10.0.2.2, length 42
11:45:28.994422 ARP, Reply 10.0.1.1 is-at 00:00:00:00:00:00 (oui Ethernet), length 58

What rule to what table do you suggest adding?

Hi @nagmat

did you have a table that handle the ARP traffic? if yes you shoud add a flow rules to hande that flows. Otherwise you can forward it via IP/L2 but you need to have a table that handle it.

The table that handles the ARP traffic is :

RuntimeCmd: table_dump MyIngress.forward 
==========
TABLE ENTRIES
**********
Dumping entry 0x0
Match key:
* arp_valid           : EXACT     01
* arp.oper            : TERNARY   0001 &&& ffff
* arp_ipv4_valid      : EXACT     01
* ipv4_valid          : EXACT     00
* icmp_valid          : EXACT     00
* icmp.type           : TERNARY   00 &&& 00
Priority: 1
Action entry: MyIngress.send_arp_reply - 
**********
Dumping entry 0x1
Match key:
* arp_valid           : EXACT     00
* arp.oper            : TERNARY   0000 &&& 0000
* arp_ipv4_valid      : EXACT     00
* ipv4_valid          : EXACT     01
* icmp_valid          : EXACT     00
* icmp.type           : TERNARY   00 &&& 00
Priority: 2
Action entry: MyIngress.forward_ipv4 - 
**********
Dumping entry 0x2
Match key:
* arp_valid           : EXACT     00
* arp.oper            : TERNARY   0000 &&& 0000
* arp_ipv4_valid      : EXACT     00
* ipv4_valid          : EXACT     01
* icmp_valid          : EXACT     01
* icmp.type           : TERNARY   08 &&& ff
Priority: 3
Action entry: MyIngress.send_icmp_reply - 
==========
Dumping default entry
Action entry: NoAction - 
==========

and

MyIngress.send_arp_reply

is the action which handles the requests.

I am not sure which entries to insert.

Maybe you should simplify it using only arp_valid and an IP/MAC address, but if you wrote the code you should be able to know what flow rule you need to write

Hi,

Just adding some information, we have a few topics talking about ARP in P4.

Hope it helps. If you need help with the rules let us know.

Cheers,

1 Like

Thank you @DavideS and @ederollora for your support.

I used your (@ederollora) arp headers and implemented arp_forward table inside P4 switch. Now,
when I execute iperf3 -s on host3(10.0.3.3) and execute iperf3 -c 10.0.3.3 on host1, I can see that packet

16:09:37.736109 ARP, Request who-has 10.0.3.3 tell 10.0.1.1, length 42

reaches port1 of switch 1, I forward it to port3, it comes to port3 of switch1 as

16:09:37.736572 ARP, Request who-has 10.0.3.3 tell 10.0.1.1, length 58.

The packet automatically reaches port3 of switch3 as

16:09:37.744707 ARP, Request who-has 10.0.3.3 tell 10.0.1.1, length 58 ,
then I arp_forward it to port1 of switch3, it reaches there as

16:09:37.745040 ARP, Request who-has 10.0.3.3 tell 10.0.1.1, length 74 .

Finally it reaches the host3. Host3 is sending reply but it doesn’t reach port1 of switch3 somehow.
Host3 :

listening on ens7, link-type EN10MB (Ethernet), capture size 262144 bytes
20:09:32.651431 ARP, Request who-has 10.0.3.3 tell 10.0.1.1, length 74
20:09:32.651456 ARP, Reply 10.0.3.3 is-at 0e:0e:1e:d0:59:85 (oui Unknown), length 28
20:09:33.652963 ARP, Request who-has 10.0.3.3 tell 10.0.1.1, length 74
20:09:33.652973 ARP, Reply 10.0.3.3 is-at 0e:0e:1e:d0:59:85 (oui Unknown), length 28
20:09:34.677334 ARP, Request who-has 10.0.3.3 tell 10.0.1.1, length 74
20:09:34.677342 ARP, Reply 10.0.3.3 is-at 0e:0e:1e:d0:59:85 (oui Unknown), length 28
20:09:35.701361 ARP, Request who-has 10.0.3.3 tell 10.0.1.1, length 74
20:09:35.701366 ARP, Reply 10.0.3.3 is-at 0e:0e:1e:d0:59:85 (oui Unknown), length 28
20:09:36.725329 ARP, Request who-has 10.0.3.3 tell 10.0.1.1, length 74
20:09:36.725337 ARP, Reply 10.0.3.3 is-at 0e:0e:1e:d0:59:85 (oui Unknown), length 28
20:09:37.748862 ARP, Request who-has 10.0.3.3 tell 10.0.1.1, length 74
20:09:37.748884 ARP, Reply 10.0.3.3 is-at 0e:0e:1e:d0:59:85 (oui Unknown), length 28

According to my assumption reply packets should reach port1 of switch3, but it doesn’t. What may be the reason?

My P4 program is : p4_tutorials/pl_basic/arp_pl.p4 at main · nagmat1/p4_tutorials · GitHub

Attaching screenshot as well : First row is switch1, second row switch2, thrid row switch3.

Can you post here your program? Or upload it to another platform.

Cheers,

/* -*- P4_17 -*- */
#include <core.p4>
#include <v1model.p4>

const bit<16> TYPE_IPV4 = 0x800;
const bit<8> UDP_PROTOCOL = 0x11;
const bit<8> TCP_PROTOCOL = 0x06;
const bit<16> ETHERTYPE_ARP  = 0x0806;
const bit<8>  IPPROTO_ICMP   = 0x01;

/*************************************************************************
*********************** 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;
}

const bit<16> ARP_HTYPE_ETHERNET = 0x0001;
const bit<16> ARP_PTYPE_IPV4     = 0x0800;
const bit<8>  ARP_HLEN_ETHERNET  = 6;
const bit<8>  ARP_PLEN_IPV4      = 4;
const bit<16> ARP_OPER_REQUEST   = 1;
const bit<16> ARP_OPER_REPLY     = 2;

header arp_t {
    bit<16> hrd; // Hardware Type
    bit<16> pro; // Protocol Type
    bit<8> hln; // Hardware Address Length
    bit<8> pln; // Protocol Address Length
    bit<16> op;  // Opcode
    macAddr_t sha; // Sender Hardware Address
    ip4Addr_t spa; // Sender Protocol Address
    macAddr_t tha; // Target Hardware Address
    ip4Addr_t tpa; // Target Protocol Address
}

const bit<8> ICMP_ECHO_REQUEST = 8;
const bit<8> ICMP_ECHO_REPLY   = 0;

header icmp_t {
    bit<8>  type;
    bit<8>  code;
    bit<16> checksum;
}

header udp_t {
    bit<16> srcPort;
    bit<16> dstPort;
    bit<16> length_;
    bit<16> checksum;
}

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 {
    ip4Addr_t dst_ipv4;
    macAddr_t  mac_da;
    macAddr_t  mac_sa;
    egressSpec_t egress_port;
    macAddr_t  my_mac;

    /* empty */
}

header metadata_t {
    bit<32> enq_timestamp;
    bit<32> enq_qdepth;
    bit<32> deq_timedelta;
    bit<32> deq_qdepth;
}


struct headers {
    ethernet_t   ethernet;
    arp_t         arp;
    ipv4_t       ipv4;
    udp_t        udp;
    tcp_t        tcp;
    icmp_t        icmp;
    metadata_t   my_meta;
}

/*************************************************************************
*********************** 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;
            ETHERTYPE_ARP  : parse_arp;
            default: accept;
        }
    }

    state parse_ipv4 {
        packet.extract(hdr.ipv4);
        transition select(hdr.ipv4.protocol){
        UDP_PROTOCOL: parse_udp;
        TCP_PROTOCOL: parse_tcp;
        IPPROTO_ICMP : parse_icmp;
        default : accept;
    }

}

state parse_arp {
        packet.extract(hdr.arp);
            transition accept;
    }


 state parse_udp{
         packet.extract(hdr.udp);
         transition accept;
    }

 state parse_tcp{
         packet.extract(hdr.tcp);
         transition accept;
    }

state parse_icmp {
        packet.extract(hdr.icmp);
        transition accept;
    }

}

/*************************************************************************
************   C H E C K S U M    V E R I F I C A T I O N   *************
*************************************************************************/
control MyVerifyChecksum(inout 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 ipv4_forward(egressSpec_t port) {
        //standard_metadata.egress_spec = port;
        //hdr.ethernet.srcAddr = hdr.ethernet.dstAddr;
        //hdr.ethernet.dstAddr = dstAddr;
        standard_metadata.egress_spec = port;
        hdr.ipv4.ttl = hdr.ipv4.ttl - 1;
    }

    action arp_forward2(egressSpec_t port) {
        //standard_metadata.egress_spec = port;
        //hdr.ethernet.srcAddr = hdr.ethernet.dstAddr;
        //hdr.ethernet.dstAddr = dstAddr;
        standard_metadata.egress_spec = port;
        hdr.ipv4.ttl = hdr.ipv4.ttl - 1;
    }


    table ipv4_lpm {
        key = {
            hdr.ipv4.dstAddr: exact;
        }
        actions = {
            ipv4_forward;
            drop;
            NoAction;
        }
        size = 1024;
        default_action = NoAction();
    }

    action forward_ipv4() {
        //hdr.ethernet.dstAddr = meta.mac_da;
        //hdr.ethernet.srcAddr = meta.mac_sa;
        hdr.ipv4.ttl         = hdr.ipv4.ttl - 1;

        standard_metadata.egress_spec = meta.egress_port;
    }
   table arp_forward {
        key = {
            hdr.arp.tpa: exact;
        }
        actions = {
            arp_forward2;
            NoAction;
        }
        size = 1024;
        default_action = NoAction();
    }


    apply {
        ipv4_lpm.apply();
        //forward.apply();
        if (((hdr.arp.isValid()) && (hdr.arp.op == ARP_OPER_REQUEST))) { arp_forward.apply(); }
    }
}

/*************************************************************************
****************  E G R E S S   P R O C E S S I N G   *******************
*************************************************************************/

struct my_egress_metadata{
    bit<48> enq_timestamp;
    bit<19> enq_depth;
    bit<32> deq_time_delta;
    bit<19> deq_depth;
    bit<8> qid;

}
control MyEgress(inout headers hdr,
                 inout metadata meta,
                 inout standard_metadata_t standard_metadata) {
    apply {
           hdr.my_meta.setValid();
           hdr.my_meta.enq_timestamp = 0xA; //standard_metadata.enq_timestamp;
           hdr.my_meta.enq_qdepth = (bit<32>) 0xB; //standard_metadata.enq_qdepth;
           hdr.my_meta.deq_timedelta = 0xC; //standard_metadata.deq_timedelta;
           hdr.my_meta.deq_qdepth = (bit<32>) 0xD; //standard_metadata.deq_qdepth;
 }
}

/*************************************************************************
*************   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.arp);
        packet.emit(hdr.ipv4);
        packet.emit(hdr.udp);
        packet.emit(hdr.tcp);
        packet.emit(hdr.my_meta);
    }
}

/*************************************************************************
***********************  S W I T C H  *******************************
*************************************************************************/

V1Switch(
MyParser(),
MyVerifyChecksum(),
MyIngress(),
MyEgress(),
MyComputeChecksum(),
MyDeparser()
) main;

Hi,

The reason is because you are only checking requests, and not requests and replies.

const bit<16> ARP_OPER_REQUEST = 1;
const bit<16> ARP_OPER_REPLY = 2;

    if ( hdr.arp.isValid() &&
            (hdr.arp.op == ARP_OPER_REQUEST ||
                hdr.arp.op == ARP_OPER_REPLY) ) { 
        arp_forward.apply(); 
    }

  1. Remember that you also need to install the rules to send it back to the origin.

  2. Also, no need to decrement TTL for ARP action:

        hdr.ipv4.ttl = hdr.ipv4.ttl - 1;

Cheers,

1 Like

At least it should reach port1 of switch3 without any rules, isn’t it?

Yes but you need to send it back so the first host learns about the Mac address of that one answering the request.

I am assuming that even if you do not install any rules you will still get the packet in the first port. But you still need to add the requests to the P4 code, else the packet is dropped before sending it to the output, thus losing it before it is being treated.

Cheers

1 Like

I changed the code accordingly. The same as it was before. Reply is being sent by host3, but doesn’t reach switch3, port1.

Hi,

Could you send here the mininet script? Maybe the whole folder would help, to be honest.

You should ZIP file the whole tutorial if you can.

Cheers,

I am not doing experiment on mininet. I am doing it on Fabric testbed. Is it ok If I send the logs of the switch?

I do not have a lot of experience, but you can try. Right now, I am not sure if there is any mininet involved, but please send it.

Cheers,

I uploaded the logs of switch1 and switch3 and other config files into github repository :

Hi

Can you capture traffic at switch 1 (port 1, and 3) and switch 3 (port 1)? (post pcap)

Why are the ARP packets of different size?

EDIT: you are adding my_meta to the end of the packet XD

Cheers,

Hi,

Can you remove the my_meta header from the deparser? Look if it works now.

control MyDeparser(packet_out packet, in headers hdr) {
    apply {
        packet.emit(hdr.ethernet);
        packet.emit(hdr.arp);
        packet.emit(hdr.ipv4);
        packet.emit(hdr.udp);
        packet.emit(hdr.tcp);
        //packet.emit(hdr.my_meta);
    }
}

Cheers,