[PSA eBPF] Incremental Checksum

Hello everyone! I’m trying to implement incremental checksum in P4 PSA eBPF and I’m using NIKSS switch. My program changes source IP and source port of packet. The problem is that my checksum update implementation seems to not work, I can see in wireshark that TCP checksum and IPv4 checksum is the same as before entering the switch. I followed the example program- p4-spec/p4-16/psa/examples/psa-example-incremental-checksum2.p4 at main · p4lang/p4-spec · GitHub and added some changes in parser and deparser. Do you recognize mabe some bugs in code below which may cause this issue? Or it should be implemented in completely different way?

Ingress Parser:

parser IngressParserImpl(packet_in buffer,
                         out headers hdr,
                         inout metadata user_meta,
                         in psa_ingress_parser_input_metadata_t istd,
                         in empty_metadata_t resubmit_meta,
                         in empty_metadata_t recirculate_meta) { 
    InternetChecksum() ck;
    state start {
        transition parse_ethernet;
    }

    state parse_ethernet {
        buffer.extract(hdr.ethernet);
        transition select(hdr.ethernet.etherType) {
            TYPE_MPLS: parse_mpls;
            TYPE_IPV4: parse_ipv4;
            default: accept;
        }
    }

    state parse_mpls {
        buffer.extract(hdr.mpls);
        transition parse_ipv4;
    }

    state parse_ipv4 {
        buffer.extract(hdr.ipv4);
        ck.clear();
        ck.add({
                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
            });
        verify(ck.get() == hdr.ipv4.hdrChecksum, error.BadIPv4HeaderChecksum);

        ck.subtract({
            hdr.ipv4.srcAddr,
            hdr.ipv4.dstAddr
        });
        transition select(hdr.ipv4.protocol) {
               TYPE_TCP: parse_trans;
               TYPE_UDP: parse_trans;
               default: accept;
        }
    }

    state parse_trans {
        buffer.extract(hdr.trans);
        transition select(hdr.ipv4.protocol) {
               TYPE_TCP: parse_tcp;
               TYPE_UDP: parse_udp;
               default: accept;
        }
    }

     state parse_tcp {
        buffer.extract(hdr.tcp);
        ck.subtract({
                hdr.trans.srcPort,
                hdr.trans.dstPort,
                hdr.tcp.seqNo,
                hdr.tcp.ackNo,
                hdr.tcp.dataOffset, hdr.tcp.res,
                hdr.tcp.ecn, hdr.tcp.ctrl,
                hdr.tcp.window,
                hdr.tcp.checksum,
                hdr.tcp.urgentPtr
            });
        user_meta.fwd_metadata.checksum_state = ck.get_state();
        transition accept;
    }

    state parse_udp {
        buffer.extract(hdr.udp);
        ck.subtract({
                hdr.trans.srcPort,
                hdr.trans.dstPort,
                hdr.udp.length_,
                hdr.udp.checksum
            });
        user_meta.fwd_metadata.checksum_state = ck.get_state();
        transition accept;
    }

}

Egress Deparser:

control EgressDeparserImpl(packet_out packet,
                           out empty_metadata_t clone_e2e_meta,
                           out empty_metadata_t recirculate_meta,
                           inout headers hdr,
                           in metadata user_meta,
                           in psa_egress_output_metadata_t istd,
                           in psa_egress_deparser_input_metadata_t edstd){
    InternetChecksum() ck;
    apply {
         if (hdr.ipv4.isValid()) {
            ck.clear();
            ck.add({
                    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 = ck.get();
        }

        ck.set_state(user_meta.fwd_metadata.checksum_state);

        if (hdr.ipv4.isValid()) {
            ck.add({
                hdr.ipv4.srcAddr,
                hdr.ipv4.dstAddr
        });

        if (hdr.tcp.isValid()) {
            ck.add({
                hdr.trans.srcPort,
                hdr.trans.dstPort,
                hdr.tcp.seqNo,
                hdr.tcp.ackNo,
                hdr.tcp.dataOffset, hdr.tcp.res,
                hdr.tcp.ecn, hdr.tcp.ctrl,
                hdr.tcp.urgentPtr
            });
            hdr.tcp.checksum = ck.get();
        }

        if (hdr.udp.isValid()) {
            ck.add({
                hdr.trans.srcPort,
                hdr.trans.dstPort,
                hdr.udp.length_
            });

            if (hdr.udp.checksum != 0) {
                hdr.udp.checksum = ck.get();
                if (hdr.udp.checksum == 0) {
                    hdr.udp.checksum = 0xffff;
                }
            }
        }

        packet.emit(hdr.ethernet);
        packet.emit(hdr.ipv4);
        packet.emit(hdr.trans);
        packet.emit(hdr.tcp);
        packet.emit(hdr.udp);
    }
    }
}

Egress code has too many brackets, this code is correct but still does not work :confused:

control EgressDeparserImpl(packet_out packet,
                           out empty_metadata_t clone_e2e_meta,
                           out empty_metadata_t recirculate_meta,
                           inout headers hdr,
                           in metadata user_meta,
                           in psa_egress_output_metadata_t istd,
                           in psa_egress_deparser_input_metadata_t edstd){
    InternetChecksum() ck;
    apply {
         if (hdr.ipv4.isValid()) {
            ck.clear();
            ck.add({
                    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 = ck.get();
        }

        ck.set_state(user_meta.fwd_metadata.checksum_state);

        if (hdr.ipv4.isValid()) {
            ck.add({
                hdr.ipv4.srcAddr,
                hdr.ipv4.dstAddr
            });
        }

        if (hdr.tcp.isValid()) {
            ck.add({
                hdr.trans.srcPort,
                hdr.trans.dstPort,
                hdr.tcp.seqNo,
                hdr.tcp.ackNo,
                hdr.tcp.dataOffset, hdr.tcp.res,
                hdr.tcp.ecn, hdr.tcp.ctrl,
                hdr.tcp.urgentPtr
            });
            hdr.tcp.checksum = ck.get();
        }

        if (hdr.udp.isValid()) {
            ck.add({
                hdr.trans.srcPort,
                hdr.trans.dstPort,
                hdr.udp.length_
            });

            if (hdr.udp.checksum != 0) {
                hdr.udp.checksum = ck.get();
                if (hdr.udp.checksum == 0) {
                    hdr.udp.checksum = 0xffff;
                }
            }
        }

        packet.emit(hdr.ethernet);
        packet.emit(hdr.ipv4);
        packet.emit(hdr.trans);
        packet.emit(hdr.tcp);
        packet.emit(hdr.udp);
    }
}

I would recommend posting a link to a full copy of your P4 program, and also give the following details:

  • exactly what command line options you are using with the P4 compiler
  • The version of the P4 compiler you are using, i.e. the output of the command p4c --version

Hi,
Command:
make -f /home/marsontic/p4c-1.2.4.16/backends/ebpf/runtime/kernel.mk BPFOBJ=elephant_switch_nikss.o P4FILE=elephant_switch_nikss.p4 ARGS="-DPSA_PORT_RECIRCULATE=2" P4ARGS="--Wdisable=unused" psa

p4c version: p4c-1.2.4.16

Fortunately, I found a workaround for this issue and I’m calculating checksum in Ingress Deparser. At least, I obtain the same checksum if no changes are made in packet. Also, if I change something, the iperf tests for TCP work fine- no retransmissions. It looks like the packet does not enter Egress. I have to mention that except EgressDeparser I have empty Egress blocks, so maybe that’s the issue.

Here is a part of code:

Ingress Parser:

parser IngressParserImpl(packet_in buffer,
                         out headers hdr,
                         inout metadata user_meta,
                         in psa_ingress_parser_input_metadata_t istd,
                         in empty_metadata_t resubmit_meta,
                         in empty_metadata_t recirculate_meta) { 
    InternetChecksum() ck;
    state start {
        transition parse_ethernet;
    }

    state parse_ethernet {
        buffer.extract(hdr.ethernet);
        transition select(hdr.ethernet.etherType) {
            TYPE_MPLS: parse_mpls;
            TYPE_IPV4: parse_ipv4;
            default: accept;
        }
    }

    state parse_mpls {
        buffer.extract(hdr.mpls);
        transition parse_ipv4;
    }

    state parse_ipv4 {
        buffer.extract(hdr.ipv4);
        verify(hdr.ipv4.ihl == 5, error.UnhandledIPv4Options);
        ck.clear();
        ck.add({
                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
        });
        verify(ck.get() == hdr.ipv4.hdrChecksum, error.BadIPv4HeaderChecksum);
        ck.clear();
        ck.subtract({
            hdr.ipv4.srcAddr,
            hdr.ipv4.dstAddr
        });
        transition select(hdr.ipv4.protocol) {
               TYPE_TCP: parse_trans;
               TYPE_UDP: parse_trans;
               default: accept;
        }
    }

    state parse_trans {
        buffer.extract(hdr.trans);
        ck.subtract({
                hdr.trans.srcPort,
                hdr.trans.dstPort,
        });
        transition select(hdr.ipv4.protocol) {
               TYPE_TCP: parse_tcp;
               TYPE_UDP: parse_udp;
               default: accept;
        }
    }

     state parse_tcp {
        buffer.extract(hdr.tcp);
        ck.subtract(hdr.tcp.checksum);
        user_meta.fwd_metadata.checksum_state = ck.get_state();
        transition accept;
    }

    state parse_udp {
        buffer.extract(hdr.udp);
        ck.subtract(hdr.udp.checksum);
        user_meta.fwd_metadata.checksum_state = ck.get_state();
        transition accept;
    }

}

Ingress Deparser:

control IngressDeparserImpl(packet_out packet,
                            out empty_metadata_t clone_i2e_meta,
                            out empty_metadata_t resubmit_meta,
                            out empty_metadata_t normal_meta,
                            inout headers hdr,
                            in metadata user_meta,
                            in psa_ingress_output_metadata_t istd){
    InternetChecksum() ck;
    apply {
        if (hdr.ipv4.isValid()) {
            ck.clear();
            ck.add({
                    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 = ck.get();
        }
        ck.clear();
        ck.set_state(user_meta.fwd_metadata.checksum_state);

        if (hdr.ipv4.isValid()) {
            ck.add({
                hdr.ipv4.srcAddr,
                hdr.ipv4.dstAddr
            });
        }

        if (hdr.tcp.isValid()) {
            ck.add({
                hdr.trans.srcPort,
                hdr.trans.dstPort,
            });
            hdr.tcp.checksum = ck.get();
        }

        if (hdr.udp.isValid()) {
            ck.add({
                hdr.trans.srcPort,
                hdr.trans.dstPort,
            });
            if (hdr.udp.checksum != 0) {
                hdr.udp.checksum = ck.get();
                if (hdr.udp.checksum == 0) {
                    hdr.udp.checksum = 0xffff;
                }
            }
        }
        packet.emit(hdr.ethernet);
        packet.emit(hdr.ipv4);
        packet.emit(hdr.trans);
        packet.emit(hdr.tcp);
        packet.emit(hdr.udp);
    }
}

I have not used the p4c ebpf back end before, but I know that for some back ends like p4c-dpdk, they have an implementation where they implement ingress only, but not egress, or they implement egress only with extra command line options. Perhaps someone else who knows the p4c ebpf back end better than I do can comment on that there.

1 Like