My First P4 program

 

Download my P4 VM.

Import the VM to your VirtualBox or VMWARE environment.

Start the VM.

Open a terminal to change to tutorial/P4D2_2017_Spring Directory.

 

Create a directory named myfirstp4.

 

Copy the ipv4_forward.p4 to this directory.

[ipv4_forward.p4]

/* -*- P4_16 -*- */

#include <core.p4>

#include <v1model.p4>

 

const bit<16> TYPE_IPV4 = 0x800;

 

/*************************************************************************

*********************** H E A D E R S  ***********************************

*************************************************************************/

 

typedef bit<9>  egressSpec_t;

typedef bit<48> macAddr_t;

typedef bit<32> ip4Addr_t;

 

header ethernet_t {

    macAddr_t dstAddr;

    macAddr_t srcAddr;

    bit<16>   etherType;

}

 

header ipv4_t {

    bit<4>    version;

    bit<4>    ihl;

    bit<8>    diffserv;

    bit<16>   totalLen;

    bit<16>   identification;

    bit<3>    flags;

    bit<13>   fragOffset;

    bit<8>    ttl;

    bit<8>    protocol;

    bit<16>   hdrChecksum;

    ip4Addr_t srcAddr;

    ip4Addr_t dstAddr;

}

 

struct metadata {

    /* empty */

}

 

struct headers {

    ethernet_t   ethernet;

    ipv4_t       ipv4;

}

 

/*************************************************************************

*********************** P A R S E R  ***********************************

*************************************************************************/

 

parser ParserImpl(packet_in packet,

                  out headers hdr,

                  inout metadata meta,

                  inout standard_metadata_t standard_metadata) {

 

    state start {

        transition parse_ethernet;

    }

 

    state parse_ethernet {

        packet.extract(hdr.ethernet);

        transition select(hdr.ethernet.etherType) {

            TYPE_IPV4: parse_ipv4;

            default: accept;

        }

    }

 

    state parse_ipv4 {

        packet.extract(hdr.ipv4);

        transition accept;

    }

 

}

 

 

/*************************************************************************

************   C H E C K S U M    V E R I F I C A T I O N   *************

*************************************************************************/

 

control verifyChecksum(inout headers hdr, inout metadata meta) {  

    apply {  }

}

 

 

/*************************************************************************

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

*************************************************************************/

 

control ingress(inout headers hdr, inout metadata meta, inout standard_metadata_t standard_metadata) {

    action drop() {

        mark_to_drop();

    }

   

    action ipv4_forward(macAddr_t dstAddr, egressSpec_t port) {

        standard_metadata.egress_spec = port;

        hdr.ethernet.srcAddr = hdr.ethernet.dstAddr;

        hdr.ethernet.dstAddr = dstAddr;

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

    }

   

    table ipv4_lpm {

        key = {

            hdr.ipv4.dstAddr: lpm;

        }

        actions = {

            ipv4_forward;

            drop;

            NoAction;

        }

        size = 1024;

        default_action = NoAction();

    }

   

    apply {

        if (hdr.ipv4.isValid()) {

            ipv4_lpm.apply();

        }

    }

}

 

/*************************************************************************

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

*************************************************************************/

 

control egress(inout headers hdr, inout metadata meta, inout standard_metadata_t standard_metadata) {

    apply {  }

}

 

/*************************************************************************

*************   C H E C K S U M    C O M P U T A T I O N   **************

*************************************************************************/

 

control computeChecksum(

    inout headers  hdr,

    inout metadata meta)

{

    apply {

        update_checksum(true,

            { hdr.ipv4.version,

                hdr.ipv4.ihl,

                hdr.ipv4.diffserv,

                hdr.ipv4.totalLen,

                hdr.ipv4.identification,

                hdr.ipv4.flags,

                hdr.ipv4.fragOffset,

                hdr.ipv4.ttl,

                hdr.ipv4.protocol,

                hdr.ipv4.srcAddr,

                hdr.ipv4.dstAddr

            },

            hdr.ipv4.hdrChecksum, HashAlgorithm.csum16);

    }

}

 

 

/*************************************************************************

***********************  D E P A R S E R  *******************************

*************************************************************************/

 

control DeparserImpl(packet_out packet, in headers hdr) {

    apply {

        packet.emit(hdr.ethernet);

        packet.emit(hdr.ipv4);

    }

}

 

/*************************************************************************

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

*************************************************************************/

 

V1Switch(

ParserImpl(),

verifyChecksum(),

ingress(),

egress(),

computeChecksum(),

DeparserImpl()

) main;

 

Copy run.sh to this directory.

 

Normally, we do not need to change the content in this file.

 

Copy p4app.json to this directory.

 

Change the content like the following file. (We will have a network:  h1----s1 (p4switch)----h2)

When we run the script. The IP address for h1 is 10.0.1.10. The MAC address for h1 is 00:04:00:00:00:01. The IP address for h2 is 10.0.2.10. The MAC address for h2 is 00:04:00:00:00:01.

The h1 is connected to port 1 of s1 and h2 is connected to port 2 of s1.

[p4app.json]

{

    "program": "ipv4_forward.p4",

    "language": "p4-16",

    "targets": {

      "multiswitch": {

          "auto-control-plane": true,

          "cli": true,

          "pcap_dump": true,

          "bmv2_log": true,

          "links": [["h1", "s1"], ["s1", "h2"]],

          "hosts": {

            "h1": {

            },

            "h2": {

            }

          },

          "switches": {

              "s1": {

                  "entries": "s1-commands.txt"

              }

          }

               }

    }

}

 

Edit the commands for s1

[s1-commands.txt] (The first command says that when the switch gets packet that goes to 10.0.1.10/32, the switch has to change the destination mac to 00:04:00:00:00:01 and send this packet to port 1.

The second command says that when the switch gets packet that goes to 10.0.2.10/32, the switch has to change the destination mac to 00:04:00:00:00:02 and send this packet to port 2.)

table_add ipv4_lpm ipv4_forward 10.0.1.10/32 => 00:04:00:00:00:01 1

table_add ipv4_lpm ipv4_forward 10.0.2.10/32 => 00:04:00:00:00:02 2

 

Show all the files.

 

Run the emulation.

…..

 

Before we are running ping program. Some network settings are already done for h1 and h2, e.g. IP and MAC address.

 

Routing and ARP cache are also done for h1 and h2

 

Run ping program. (h1 can ping h2)

 

Open terminals for h1 and h2

 

Use wireshark to capture packets on h1-eth0 and h2-eth0. Run ping program again

Check the first packet on h1-eth0. (The packet is sent from 10.0.1.10 to 10.0.2.10. The src MAC is 00:04:00:00:00:01 and dst MAC is 00:aa:00:01:00:010. The dst MAC address is the default router for h1)

 

Check the first packet on h2-eth0. (The packet is sent from 10.0.10 to 10.2.10. The src MAC is 00:aa:00:01:00:01 and dst MAC is 00:04:00:00:00:02)

 

The above is for ping ICMP request. The similar steps can be used to trace ping ICMP response. (See packet 2).

 

Now I will describe how this p4 switch works. We will take a closer look at following code. We trace the code from the bottom. If the ipv4 header for the packet is valid, we will use table ipv4_lpm.

In table ipv4_lpm, the packet will be checked using destination IP address and use longest prefix match method. If a packet is matched with predefined rule (in s1-commmands.txt), ipv4_forward function will be called. For example, “table_add ipv4_lpm ipv4_forward 10.0.2.10/32 => 00:04:00:00:00:02 2”à if a packet with destination IP address 10.0.2.10/32, ipv4_forward is called with parameters dstAddr=00:04:00:00:00:02, port=2. So the final results are the packet will be sent to port 2, the original dst mac address will be changed to src mac address, the dstAddr will be set to destination mac address, and ttl will decrease by 1. We can see the changes in the corresponding fields in the first packet in the output of wireshark.

control ingress(inout headers hdr, inout metadata meta, inout standard_metadata_t standard_metadata) {

    action drop() {

        mark_to_drop();

    }

   

    action ipv4_forward(macAddr_t dstAddr, egressSpec_t port) {

        standard_metadata.egress_spec = port;

        hdr.ethernet.srcAddr = hdr.ethernet.dstAddr;

        hdr.ethernet.dstAddr = dstAddr;

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

    }

   

    table ipv4_lpm {

        key = {

            hdr.ipv4.dstAddr: lpm;

        }

        actions = {

            ipv4_forward;

            drop;

            NoAction;

        }

        size = 1024;

        default_action = NoAction();

    }

   

    apply {

        if (hdr.ipv4.isValid()) {

            ipv4_lpm.apply();

        }

    }

}

 

Dr. Chih-Heng Ke (smallko@gmail.com)

Department of Computer Science and Information Engineering,

National Quemoy University, Kinmen, Taiwan.