Stateful metadata per-packet

Hello. I’m creating a P4 program using P4_16 in a BMv2 environment.

The task I want to achieve is to modify packet metadata in S1 and then forward the packet to S2, where S2 would be able to read and use the metadata values modified in S1. However, when S2 receives a new packet, it initializes all metadata values to 0.

I also considered registers, but my desired task is to use stateful values for each individual packet, not values that are maintained across the entire program (i.e., affecting all subsequent packets globally).

The first image shows S1 modifying the ‘weight’ value within the packet’s metadata and the second image shows S2 initializing the metadata’s ‘weight’ value to 0, rather than using the value modified by S1.

Is there a way to keep the metadata values I modified in S1 persisted to S2 without them being initialized? or Is there a way to implement per-packet registers?

Thank you.

Metadata fields are associated with a packet only within a single switch. The only thing sent out of a device are the headers of the packet that you choose to emit, and its payload. If you want something in the packet carried from one switch to the next device, it must be in the packet. A common way to carry “extra” data with a packet is to define an additional header, and decide where to put it in the packet so that the next device can recognize/parse it, and eventually you typically want to remove it, too, before forwarding it to a device that does not understand the new header.

Thank you for your answer!

I am currently trying to implement the solution you suggested by adding a header.

However, when I ping between hosts, tcpdump only shows “echo reply,” and they are not properly exchanging pings.

Below is the code I wrote.

// SPDX-License-Identifier: Apache-2.0
/* -*- 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;
}

header my_header_t {
    bit<32> first;
    bit<32> second;
}

struct metadata {}

struct headers {
    ethernet_t   ethernet;
    ipv4_t       ipv4;
    my_header_t  my_header;
}

/*************************************************************************
*********************** 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);
        packet.extract(hdr.my_header);
        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 forwarding_action (macAddr_t dstAddr, egressSpec_t port) {
        standard_metadata.egress_spec = port;
        hdr.ethernet.srcAddr = hdr.ethernet.dstAddr;
        hdr.ethernet.dstAddr = dstAddr;
    }

    table forwarding {
        key = {
            hdr.ipv4.dstAddr: exact;
        }
        actions = { forwarding_action; }
    }

    action header_set_action () {
        hdr.my_header.first = 100;
        hdr.my_header.second = hdr.my_header.second + 1;
}

    table header_set {
        key = { hdr.ipv4.srcAddr: exact; }
        actions = { header_set_action; }
}

    apply {
        hdr.my_header.setValid();
        header_set.apply();
        forwarding.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);
        packet.emit(hdr.my_header);
    }
}

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

V1Switch(
MyParser(),
MyVerifyChecksum(),
MyIngress(),
MyEgress(),
MyComputeChecksum(),
MyDeparser()
) main;
table_add MyIngress.forwarding MyIngress.forwarding_action 10.0.1.1 => 00:00:10:00:01:01 0
table_add MyIngress.forwarding MyIngress.forwarding_action 10.0.2.2 => 00:00:10:00:02:02 0

table_add MyIngress.header_set MyIngress.header_set_action 10.0.1.1 =>
table_add MyIngress.header_set MyIngress.header_set_action 10.0.2.2 =>

There is something I wrote incorrectly? Thank you.

When you run the ping command on a host (the normal one that comes installed with Linux, not some version that you customized yourself), it does not add any extra custom headers to it. It sends a standard format ping packet.

This has two implications:

  1. A switch receiving a packet from a host does not have your extra custom header in it. You should parse and process as a normal standard packet.
  2. A switch sending a packet to a host should not have your extra custom header in it. If you write P4 code that includes such an extra custom header in it, there is no reason that the host will expect this, and could easily lead to a situation where it receives what you hope is an ICMP echo request packet, but the host does not recognize it as that, and therefore does not send back an ICMP echo response packet. In such a case, ping from host h1 to host h2 will show no resopnses on h1.