It seems that truncate does not work when coexists with recirculate

Hello, I got some problem when I tried truncate one packet and then recirculate it. When the packet is recirculated, other modification still remains, but truncate does not works. Here is a simplified sample to show my problem:

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

typedef bit<9>  egressSpec_t;
typedef bit<32> cloneSpec_t;
typedef bit<32> cloneSession_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 test_t {
    bit<8> test_info;
}

struct metadata { }

struct headers {
    ethernet_t   ethernet;
    ipv4_t       ipv4;
    test_t       test;
}

/*************************************************************************
*********************** 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);
        transition select(packet.lookahead<test_t>().test_info) {
            65 : parse_test;
            default: accept;
        }
    }

    state parse_test {
        packet.extract(hdr.test);
        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(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 (standard_metadata.instance_type == PKT_INSTANCE_TYPE_NORMAL) {
            clone_packet();
        } 
        if (hdr.ipv4.isValid()) {
            ipv4_lpm.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 {
        if (hdr.test_first.isValid()) {
            if (standard_metadata.instance_type == PKT_INSTANCE_TYPE_INGRESS_CLONE) {
                hdr.ipv4.totalLen = hdr.ipv4.minSizeInBytes();
                truncate((bit<32>)hdr.ethernet.minSizeInBytes() + (bit<32>)hdr.ipv4.minSizeInBytes());
                recirculate(standard_metadata);
            }
        } 
    }
}

I’m not familiar with recirculate and I am wondering is there anything I got wrong?

The truncate extern function in the v1model architecture is currently the least well-documented. I would have documented it better when writing documentation for other v1model primitives if I had known its behavior, but apparently I did not dig into that one in its current bmv2 implementation nearly as much as I did most other features.

If anyone knows how it works, or is willing to dig in with experiments to find out from experiments, I would be happy to review your pull request to the p4lang/p4c and/or p4lang/behavioral-model repositories for such documentation efforts. It might also be that it does not work as the P4_14 specification implies it should. If so, since the v1model architecture was originally developed as a porting target for P4_14 programs to (P4_16+v1model architecture), there may be bugs in its implementation that no one has reported yet.

2 Likes

By the way, which parameter should I pass to recirculate() to silence the warning

warning: recirculate: recirculate with non-empty argument not supported.

if I don’t need to preserve any user-defined metadata?

First, if you do not need to preserve metadata, then the easiest thing to do for now is just ignore the warning. The operation should work fine, except for the metadata preservation part of it. Read below if you feel comfortable building your own updated p4c from source code.

Preservation of metadata has been at best flaky, and at worst completely broken, in the v1model implementation for years. Recently (2021-Dec-06 in fact) Mihai Budiu implemented and added some changes in how v1model preserves metadata for recirculate, resubmit, and clone operations that should be more stable and working. It changes the name of the recirculate operation. See the latest v1model.p4 include file [1] and the other link below [2] for the changes.

As of right now, the only way I know to get these changes is to build p4c from source code. On a freshly installed Ubuntu 18.04 or 20.04 system, you can do this in about 1 to 2 hours (depending upon your Internet connection speed and computer speed) using scripts I publish at [3].

[1] p4c/v1model.p4 at main · p4lang/p4c · GitHub
[2] behavioral-model/simple_switch.md at main · p4lang/behavioral-model · GitHub
[3] p4-guide/README-install-troubleshooting.md at master · jafingerhut/p4-guide · GitHub

2 Likes

Thanks a lot for your information.

Oh, and because it is relatively recently tested by me, I forgot about another alternative which is to install P4 dev tools via pre-compiled binary package, which I believe today have only been released for Ubuntu 20.04, and some version of Fedora or other Linux distribution that I have not tested. My link [3] above has a script install-p4dev-v5.sh that is quite quick because it installs binary packages of the P4 tools, which as long as the current volunteer continues to build new binary packages from new versions of the P4 tool source code, can be upgraded more quickly than building it from source yourself.

Thanks, and it seems that on environment set with p4-guide/README-install-troubleshooting.md at master · jafingerhut/p4-guide · GitHub on Ubuntu 20.04, using hs.pop_front() and hs.size of header_stack will cause compiling error like:

hdr.my_header_stack.size: argument used for directionless parameter 'count' must be a compile-time constant

and

Function type 'pop_front' does not match invocation type '<Method call>'

, but on Ubuntu 16.04, they both work.

If there is a complete P4 program that causes these errors that you can publish and send a link to them here, someone could take a look at it. There are existing test programs in p4c that exercise these things, I am pretty sure, and if there are I have run them successfully on Ubuntu 20.04 before, so it might be the way they are used in the context of the rest of your program.

It might also be that you have different versions of the P4 compiler installed on your Ubuntu 16.04 vs Ubuntu 20.04 systems (you can check the version using the command p4c --version, although it might require looking up the 6-digit hex string in the p4c Github repo to see which one is newer), and they behave differently with your program. Without seeing the program and trying it out, it isn’t obvious from your description whether a bug might have been introduced, or a bug was fixed.

I tested hs.size and hs.pop_front() respectively, and they both work. And the code caused the compile error is

hdr.hs.pop_front(hdr.hs.size);

My environment checked with p4c --version on Ubuntu 20.04 is:
p4c 1.2.2 (SHA: 9b969dd6e BUILD: DEBUG)
and the p4c version on Ubuntu 16.04 is:
p4c 1.1.0-rc1 (SHA: 69e132d)