Help with bmv2/v1model meter + “color” usage for Max–Min Fairness (P4 Tutorial VM)

Hi all,

I’m using the recommended “P4 Tutorial Development” VM (prebuilt from the official P4 repo). I’m trying to modify basic.p4 to prototype a max–min fairness (MMF) controller: the controller computes per-flow rates; the switch enforces them with a per-flow meter and drops packets marked RED. I’d like a minimal, correct pattern for declaring and using meters in v1model on bmv2.

Below is the current program I’m working with (trimmed to essentials). The idea is:

  • ipv4_lpm picks the egress/next hop.

  • ipv4_src (exact on srcAddr) assigns a flow index via action set_flow(…, idx).

  • In apply, I call a meter array with that index and drop if color==RED.

Code I’m trying now:

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

struct metadata {
    bit<32> flow_idx;   // indeks używany przez tablicę meterów
}

struct headers {
    ethernet_t ethernet;
    ipv4_t     ipv4;
}

/*************************************************************************
*********************** 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 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) {

    meter src_meter;

    action drop() {
        mark_to_drop(standard_metadata);
    }

    action ipv4_forward(macAddr_t dstAddr, egressSpec_t port) {
        hdr.ethernet.dstAddr = dstAddr;
        hdr.ipv4.ttl = hdr.ipv4.ttl - 1;
        standard_metadata.egress_spec = port;
    }

    action set_flow(macAddr_t dstAddr, egressSpec_t port, bit<32> idx) {
        hdr.ethernet.dstAddr = dstAddr;
        hdr.ipv4.ttl = hdr.ipv4.ttl - 1;
        standard_metadata.egress_spec = port;
        meta.flow_idx = idx;
    }

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

    table ipv4_src {
        key = { hdr.ipv4.srcAddr: exact; }
        actions = { set_flow; drop; NoAction; }
        size = 1024;
        default_action = NoAction();
    }

    apply {
        bit<32> mval;      // raw value from meter
        bit<2>  col;       // color = mval[1:0]
        meta.flow_idx = 0;
        mval = 0;

        if (hdr.ipv4.isValid()) {
            bool hit = ipv4_lpm.apply().hit;
            if (!hit) {
                drop();
            } else {
                ipv4_src.apply();                         // assign egress + flow_idx
                src_meter.execute_meter(meta.flow_idx, mval); // call meter
                col = (bit<2>) mval;                      // color is 2 LSBs

                if (col == 2w2) {                         // 2 == RED
                    drop();
                }
            }
        } else {
            drop();
        }
    }
}

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

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

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

Environment / toolchain

  • VM: P4 Tutorial Development 2025-04-01

  • Compiler: p4c-bm2-ss --p4v 16

  • Target/runtime: simple_switch_grpc (bmv2, v1model)

Issues encountered (confusion around meters & colors)

Across attempts I have hit several inconsistencies and errors, for example:

  • MeterColor_t not found / color constants not found:

    • error: METER_COLOR_GREEN: declaration not found

    • error: RED: declaration not found

    • syntax error, unexpected IDENTIFIER "MeterColor_t"

  • Meter type / constructor confusion:

    • meter<bit<32>>(1024) src_meter;Type meter has 0 type parameter(s)

    • meter(1024) src_meter;type meter has no matching constructor

  • Local variable declaration weirdness (older p4c behavior):

    • syntax error, unexpected IDENTIFIER "color" if declaring a local color var in apply after statements.
  • Bit-width literal warnings:

    • value does not fit in 0 bits when using something like 0w2 vs 2w0.

Given the above, I’m not sure what is the canonical way on this VM to:

  1. Declare a meter array in v1model for bmv2 (is meter src_meter; correct? or should I use a direct_meter attached to a table instead?),

  2. Call the meter in ingress and obtain the color (return value vs out param, type of that value),

  3. Interpret the color (are symbolic enums available? or should I treat it as an integer 0/1/2?),

  4. Configure the meter via P4Runtime (CIR/PIR/burst) for index = flow_idx (assigned by ipv4_src).

My goal is a simple, robust pattern that works on this toolchain, e.g.:

  • minimal example for meter declaration and execute_meter usage in ingress,

  • what type should I use for the color (and how to compare it),

  • whether I should switch to a direct meter attached to ipv4_src (per-entry) instead of a stand-alone meter array with index,

  • sample P4Runtime calls (or pointers) to set per-index rates.

If the recommended approach on this VM is different (e.g., use a direct meter bound to a match-action table), I’m happy to adopt that—just need the correct v1model idiom.

Thanks a ton for any guidance or a minimal working snippet!

I do not have example code to show you for configuring the rates of the meter, but here is a link to a modified version of file tutorials/exercises/ecn/solution/ecn.p4 in the tutorials repository that has a direct meter associated with table ipv4_lpm:

The direct_meter extern instance declaration: tutorials/exercises/ecn/solution/ecn.p4 at add-direct-meter-to-ecn-exercise-p4 · jafingerhut/tutorials · GitHub

Extra lines of code added to action ipv4_forward that updates the meter, gets the color, compares it to red, and drops the packet if it is red: tutorials/exercises/ecn/solution/ecn.p4 at add-direct-meter-to-ecn-exercise-p4 · jafingerhut/tutorials · GitHub

The table property added to table ipv4_lpm that associates the direct meter named mymeter1 with the table: tutorials/exercises/ecn/solution/ecn.p4 at add-direct-meter-to-ecn-exercise-p4 · jafingerhut/tutorials · GitHub

There are preprocessor symbols defined for the numeric values of the three colors here in the v1model.p4 include file: p4c/p4include/v1model.p4 at main · p4lang/p4c · GitHub

There are quite a few comments in the v1model.p4 include file for the definitions of the direct_meter and meter externs that might be useful, but they don’t include example code for putting it all together: p4c/p4include/v1model.p4 at main · p4lang/p4c · GitHub

The literal 0w2 would mean an unsigned value with type bit<0> whose value is 2, which is almost never what you want. The warning is because the numeric value 2 is too large to fit into a value that is 0 bits wide. 2w0 is an unsigned value with type bit<2> whose value is 0, and is more likely what you want, if you want a numeric value of 0. In most cases you can omit the 2w prefix on integer constant values, and the compiler will infer what its bit width should be from the context where it is used.

It should also work fine to have an indirect meter, i.e. one with the extern type named meter. If you want an example of a P4 program for that, that compiles without error, let me know.

Thanks a lot, Andy — this clears up most of my confusion.