Implementing Checksum for IPv4 Headers with Options in P4 without Extern functions

Hi all,

I am new to this forum and also new to P4 coding in general. I have been really struggling with a specific lab problem that I have been trying to work out.

The problem is that I have been trying to implement a simple p4 code that would implement a checksum function for the IPv4 headers (with options) from scratch without using any extra functions in P4 and adding that to an existing forwarding example(attached below). But, as of now, I have not been able to make any progress for 2 months. Most of the online forums talk about coding in the v1model switch architecture but I am trying to implement the checksum functionality in xsa.p4 architecture. I know that there is a Checksum extern but I am not allowed to use it. I need to implement the algorithm from scratch. Also, it has been really difficult for me since there is no option of using loops in the language as well. I have scrambled around a lot to find anything that could help me with it but, to no avail. I am in desperate need of help from somebody who is experienced.

Could you anybody please kindly help me with it? I really, really need your help especially since I am new to this language and it has been really difficult for me to navigate through the language’s capabilities and limitations.

Hoping for your kind consideration. Thanks in advance.

Regards,

I have attached the forwarding code below:

#include <core.p4>
#include <xsa.p4>

typedef bit<48> MacAddr;
typedef bit<32> IPv4Addr;
typedef bit<128> IPv6Addr;

const bit<16> VLAN_TYPE = 0x8100;
const bit<16> IPV4_TYPE = 0x0800;
const bit<16> IPV6_TYPE = 0x86DD;

const bit<8> TCP_PROT = 0x06;
const bit<8> UDP_PROT = 0x11;

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

header eth_mac_t {
MacAddr dmac; // Destination MAC address
MacAddr smac; // Source MAC address
bit<16> type; // Tag Protocol Identifier
}

header vlan_t {
bit<3> pcp; // Priority code point
bit<1> cfi; // Drop eligible indicator
bit<12> vid; // VLAN identifier
bit<16> tpid; // Tag protocol identifier
}

header ipv4_t {
bit<4> version; // Version (4 for IPv4)
bit<4> hdr_len; // Header length in 32b words
bit<8> tos; // Type of Service
bit<16> length; // Packet length in 32b words
bit<16> id; // Identification
bit<3> flags; // Flags
bit<13> offset; // Fragment offset
bit<8> ttl; // Time to live
bit<8> protocol; // Next protocol
bit<16> hdr_chk; // Header checksum
IPv4Addr src; // Source address
IPv4Addr dst; // Destination address
}

header ipv4_opt_t {
varbit<320> options; // IPv4 options - length = (ipv4.hdr_len - 5) * 32
}

header ipv6_t {
bit<4> version; // Version = 6
bit<8> priority; // Traffic class
bit<20> flow_label; // Flow label
bit<16> length; // Payload length
bit<8> protocol; // Next protocol
bit<8> hop_limit; // Hop limit
IPv6Addr src; // Source address
IPv6Addr dst; // Destination address
}

header tcp_t {
bit<16> src_port; // Source port
bit<16> dst_port; // Destination port
bit<32> seqNum; // Sequence number
bit<32> ackNum; // Acknowledgment number
bit<4> dataOffset; // Data offset
bit<6> resv; // Offset
bit<6> flags; // Flags
bit<16> window; // Window
bit<16> checksum; // TCP checksum
bit<16> urgPtr; // Urgent pointer
}

header tcp_opt_t {
varbit<320> options; // TCP options - length = (tcp.dataOffset - 5) * 32
}

header udp_t {
bit<16> src_port; // Source port
bit<16> dst_port; // Destination port
bit<16> length; // UDP length
bit<16> checksum; // UDP checksum
}

// ****************************************************************************** //
// ************************* S T R U C T U R E S ******************************* //
// ****************************************************************************** //

// header structure
struct headers {
eth_mac_t eth;
vlan_t[2] vlan;
ipv4_t ipv4;
ipv4_opt_t ipv4opt;
ipv6_t ipv6;
tcp_t tcp;
tcp_opt_t tcpopt;
udp_t udp;
}

// User metadata structure
struct metadata {
//bit<9> port;
bit<16> tuser_size;
bit<16> tuser_src;
bit<16> tuser_dst;
}

// User-defined errors
error {
InvalidIPpacket,
InvalidTCPpacket
}

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

parser MyParser(packet_in packet,
out headers hdr,
inout metadata meta,
inout standard_metadata_t smeta) {

state start {
    transition parse_eth;
}

state parse_eth {
    packet.extract(hdr.eth);
    transition select(hdr.eth.type) {
        VLAN_TYPE : parse_vlan;
        IPV4_TYPE : parse_ipv4;
        IPV6_TYPE : parse_ipv6;
        default   : accept;
    }
}

state parse_vlan {
    packet.extract(hdr.vlan.next);
    transition select(hdr.vlan.last.tpid) {
        VLAN_TYPE : parse_vlan;
        IPV4_TYPE : parse_ipv4;
        IPV6_TYPE : parse_ipv6;
        default   : accept;
    }
}

state parse_ipv4 {
    packet.extract(hdr.ipv4);
    verify(hdr.ipv4.version == 4 && hdr.ipv4.hdr_len >= 5, error.InvalidIPpacket);
    packet.extract(hdr.ipv4opt, (((bit<32>)hdr.ipv4.hdr_len - 5) * 32));
    transition select(hdr.ipv4.protocol) {
        TCP_PROT  : parse_tcp;
        UDP_PROT  : parse_udp;
        default   : accept;
    }
}

state parse_ipv6 {
    packet.extract(hdr.ipv6);
    verify(hdr.ipv6.version == 6, error.InvalidIPpacket);
    transition select(hdr.ipv6.protocol) {
        TCP_PROT  : parse_tcp;
        UDP_PROT  : parse_udp;
        default   : accept;
    }
}

state parse_tcp {
    packet.extract(hdr.tcp);
    verify(hdr.tcp.dataOffset >= 5, error.InvalidTCPpacket);
    packet.extract(hdr.tcpopt, (((bit<32>)hdr.tcp.dataOffset - 5) * 32));
    transition accept;
}

state parse_udp {
    packet.extract(hdr.udp);
    transition accept;
}

}

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

control MyProcessing(inout headers hdr,
inout metadata meta,
inout standard_metadata_t smeta) {

// action forwardPacket(bit<9> port) {
// meta.port = port;
// }

action forwardPacket() {
}

action dropPacket() {
            smeta.drop = 1;
}

table forwardIPv4 {
    key             = { hdr.ipv4.dst : lpm; }
    actions         = { forwardPacket;
                        dropPacket; }
    size            = 1024;
            num_masks       = 64;
    default_action  = forwardPacket;
}

table forwardIPv6 {
    key             = { hdr.ipv6.dst : lpm; }
    actions         = { forwardPacket;
                        dropPacket; }
    size            = 1024;
    default_action  = forwardPacket;
}

apply {

    if (smeta.parser_error != error.NoError) {
        dropPacket();
        return;
    }

    if (hdr.ipv4.isValid())
        forwardIPv4.apply();
    else if (hdr.ipv6.isValid())
        forwardIPv6.apply();
    else
                    forwardPacket();

}

}

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

control MyDeparser(packet_out packet,
in headers hdr,
inout metadata meta,
inout standard_metadata_t smeta) {
apply {
packet.emit(hdr.eth);
packet.emit(hdr.vlan);
packet.emit(hdr.ipv4);
packet.emit(hdr.ipv4opt);
packet.emit(hdr.ipv6);
packet.emit(hdr.tcp);
packet.emit(hdr.tcpopt);
packet.emit(hdr.udp);
}
}

// ****************************************************************************** //
// ******************************* M A I N ************************************ //
// ****************************************************************************** //

XilinxPipeline(
MyParser(),
MyProcessing(),
MyDeparser()
) main;

Hello Sandeep,

The Internet checksum algorithm is well-defined (RFC 1071 → RFC1141 → RFC1624). In short, you need to do the following:

  • Split the required data into 16-bit chunks C1, C2, …., Cn.
  • Compute the sum of these chunks using 1-complement arithmetic.

The splitting can be accomplished relatively easily as long as you The Internet checksum algorithm is well-defined (RFC 1071 → RFC1141 → RFC1624). In short, you need to do the following:
Split the required data into 16-bit chunks C1, C2, …., Cn.
Compute the sum of these chunks using 1-complement arithmetic.

The splitting can be accomplished relatively easily as long as you do not use varbit to represent IPv4 options but use individual headers instead. For example, if you use these definitions for IPv4 header and option words:

header ipv4_h {
    bit<4>       version;
    bit<4>       ihl;
    bit<8>       diffserv;
    bit<16>      total_len;
    bit<16>      identification;
    bit<3>       flags;
    bit<13>      frag_offset;
    bit<8>       ttl;
    ip_proto_t   protocol;
    bit<16>      hdr_checksum;
    ipv4_addr_t  src_addr;
    ipv4_addr_t  dst_addr;
}
 
header ipv4_option_word_h {
    bit<32>      data;
}
 
struct headers_h {
    . . .
    ipv4_h                  ipv4;
    ipv4_option_word_h[10]  ipv4_options;
}

then your chunks can be defined as follows:

bit<16> c1  = h.ipv4.version ++ h.ipv.ihl ++ h.ipv4.diffserv;
bit<16> c2  = h.ipv4.total_len;
bit<16> c3  = h.ipv4.identification;
bit<16> c4  = h.ipv4.flags ++ h.ipv4_frag_offset;
bit<16> c5  = h.ipv4.ttl ++ h.ipv4.protocol;
bit<16> c6  = h.ipv4.hdr_checksum;
bit<16> c7  = h.ipv4.src_addr[31:16];
bit<16> c8  = h.ipv4.src_addr[15:0];
bit<16> c9  = h.ipv4.src_addr[31:16];
bit<16> c10 = h.ipv4.src_addr[15:0];
bit<16> c11 = h.ipv4_options[0].data[31:16];
bit<16> c12 = h.ipv4_options[0].data[15:0];
. . .
bit<16> c29 = h.ipv4_options[9].data[31:16];
bit<16> c30 = h.ipv4_options[9].data[15:0];

Obviously, you need to take care of header validity by predicating those assignments accordingly and writing 0 into these chunks in case the corresponding header is not valid. I assume you know how to do that.

After that, the algorithm is pretty easy, and follows the way 1-complement arithmetic is supposed to be done using the standard 2-complement one. Specifically, you need to compute the sum and add the carry back:

bit<32> sum = (bit<32>)c1 + (bit<32>)c2 + ... + (bit<32>)c30;
bit<16> internet_checksum = sum[15:0] + sum[31:16];

If you are trying to verify IPv4 checksum, you need to add together all the chunks and the result has to be equal to 0x0000 or 0xFFFF. If you are trying to recompute the IPv4 checksum, you need to add together all the chunks except c6, invert the value and then write it into h.ipv4.hdr_checksum.

Unfortunately, I am not familiar with your specific target, so this general algorithm might need to be coded in a way that works for it. For example, it is highly likely that you might need to split the expression to compute the sum into many individual additions in a tree-like fashion. I hope, you do know how to do that as well. It might also be better to declare c1c30 as bit<32> and perform the casts when they are initialized, etc. etc., but the algorithm itself is simple and requires no loops.

Looking at your code, I can see that you are using varbit to represent IPv4 options. This is a problem, because as of today, P4_16 does not allow you to extract data from varbit types and copy it, say, into bit<16> (or other types). This is the first issue that needs to be fixed if you want to be able to compute the checksum for an IPv4 packet with IPv4 options while staying within the core language.

There might be other problems – I can’t tell since you the code you attached does not include anything that would show how exactly have you tried to compute the checksum.

Last, but not least, I wonder why are you trying to compute the checksum without the extern? It is quite expensive to do it without it. Is this just some sort of a school assignment?

Happy hacking,
Vladimir

Hello Vladimir,

Thank you for your response. You’re a godsend. I don’t know why I waited for so long to post my P4-related questions over here in the forum where brilliant and helpful minds like you are kind enough to share their wisdom. This is definitely really helpful to a newbie like me and would propel me in the right direction.

To answer some of the queries posed in your reply:

  1. The specific architecture that I am targeting is the xsa.p4 switch architecture which basically stands for xilinx switch architecture.

  2. I am trying to compute the checksum without the extern because my professor asked me to do it as part of my P4 learning process.

Having said that, I also had a few follow-up questions, which I’d be really grateful if you could answer:

  1. Previously, I had only written a few P4 codes for the v1model architecture, which had Verify_Checksum and a Compute_Checksum control back. That made my life easier since I knew where I needed to verify and compute the outgoing checksum. But in this xsa.p4 architecture, as you can see, there is no dedicated control block for checksums. Could you please kindly suggest to me where it’d be best to put these checksum verification and computation blocks? Also, I’d like to drop the packets whose checksum verification fails. Do you have any suggestions on how to do that? I am pretty sure it’s different from dropping packets defined in the match-action tables, which I have already implemented in the control plane.
  1. These are three packets that I captured with Wireshark:

% Packet 1 (188 bytes)
% Ethernet header:[ DstMAC=111111111111 SrcMAC=aaaaaaaaaaaa EtherType=0800 ]
11 11 11 11 11 11 aa aa aa aa aa aa 08 00
% IPv4 header:[ Version=4 HdrLen=f DSCP=32 ECN=1 Length=00ae ID=50fa Flags=0 Fragment=0000 TTL=b3 Protocol=11 Checksum=d386 SrcAddr=fd5f0bc8 DstAddr=9aaa2010 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionEND=00 ]
4f c9 00 ae 50 fa 00 00 b3 11 d3 86 fd 5f 0b c8 9a aa 20 10 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 00
% UDP header:[ SrcPort=2411 DstPort=6cc1 Length=0072 Checksum=99c3 ]
24 11 6c c1 00 72 99 c3
% Payload
79 72 5b cf c6 6e 5f fa b9 2f 38 cb 50 39 30 91 91 98 27 88 ed fa be 03 08 b9 bf f3 d0 e9 c0 01 5b 13 ac 7e 62 9a 26 36 06 ae 5d 96 ec 71 2d a8 66 be 46 02 65 89 87 66 c1 9b ca c2 4b 89 19 ba 0e ed f4 af 84 d3 e4 3c 34 d6 da 25 1d 8f ad ff af e1 bb b9 37 4f 64 4b 06 a2 0e e9 36 5d c1 87 11 26 20 a6 0f c9 5b e5 4f fd
;

% Packet 2 (89 bytes)
% Ethernet header:[ DstMAC=111111111112 SrcMAC=aaaaaaaaaaab EtherType=0800 ]
11 11 11 11 11 12 aa aa aa aa aa ab 08 00
% IPv4 header:[ Version=4 HdrLen=5 DSCP=00 ECN=3 Length=004b ID=4357 Flags=0 Fragment=0000 TTL=39 Protocol=11 Checksum=1d1f SrcAddr=7cf6cd9c DstAddr=cc930a03 ]
45 03 00 4b 43 57 00 00 39 11 1d 1f 7c f6 cd 9c cc 93 0a 03
% UDP header:[ SrcPort=211e DstPort=5b98 Length=0037 Checksum=aa9a ]
21 1e 5b 98 00 37 aa 9a
% Payload
6b fa a4 95 d2 54 47 71 92 29 8b 0f 8d e7 e2 99 08 f0 13 0b ef 64 07 3b fe e0 d4 6a ad 3f 5b 3e fd 58 33 49 fc 8f 86 00 1c 4f 00 a0 d0 6d 70
;

% Packet 3 (222 bytes)
% Ethernet header:[ DstMAC=333333333333 SrcMAC=cccccccccccc EtherType=8100 ]
33 33 33 33 33 33 cc cc cc cc cc cc 81 00
% Vlan header:[ Pcp=4 Dei=0 Vid=003 EtherType=0800 ]
80 03 08 00
% IPv4 header:[ Version=4 HdrLen=e DSCP=1d ECN=2 Length=00cc ID=f84e Flags=0 Fragment=0000 TTL=f6 Protocol=11 Checksum=61b1 SrcAddr=e90a5c71 DstAddr=6353a5ca OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionNOP=01 OptionEND=00 ]
4e 76 00 cc f8 4e 00 00 f6 11 61 b1 e9 0a 5c 71 63 53 a5 ca 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 00
% UDP header:[ SrcPort=7320 DstPort=85bb Length=0094 Checksum=8133 ]
73 20 85 bb 00 94 81 33
% Payload
71 fb 3d 2d 60 26 42 3a 7b 2d fb f8 1c f4 04 dd fc dc d4 83 80 82 1f 72 a2 9e 7c e8 f0 04 50 ba 2a 58 a5 59 8a d0 5a b7 f9 88 64 55 f3 85 d0 11 2d 4a 28 12 51 da 71 33 74 3c 72 22 b0 43 de e6 57 62 61 96 6c 72 d1 3d e0 90 98 7c 6a cf 3b 74 0d 5b d3 8b cf eb 02 94 87 de 6f 60 bb de 84 ec a6 0f 14 f4 06 ac 1f 5d 81 ae 0c 16 de 47 7c b6 5f c0 0a 31 3e 47 dc 25 c3 db 09 e8 fe e2 03 51 e0 26 06 5c 8e 02 c5 d3 b6 62 05 10
;

NOTICE that in all three packets, apart from the first 10 fixed fields of the IPv4 header, the Options fields are not fixed at all. In the first packet, there are 20 options. In the second, there are none. Similarly, in the third, there are multiple as well but not the same as packet one. So, how would it work unless I define the Options field as varbit? How will I predict how many are there in a given packet? Also, going by your logic, if I keep them fixed I’d need to validate the headers or zero-pad them in case they’re invalid. How do you usually go about doing that in P4? I know how to do it in C++ but not P4, yet. I have just begun learning a few days back from the Xilinx User Guide but they didn’t mention this anywhere.

bit<32> sum = (bit<32>)c1 + (bit<32>)c2 + … + (bit<32>)c30;
bit<16> internet_checksum = sum[15:0] + sum[31:16];

YOU SUGGESTED THE ABOVE FOR THE CHECKSUM COMPUTATION ALONG WITH 2’s COMPLEMENT.
Is there a way to add every variable from c1 to c30 one by one and add the overflow back to the sum in each step IF there is an overflow? Instead of adding the overflow right at the end? Also, wouldn’t we need to compute the one’s complement at the end as well for the final checksum?

  1. When I verify the checksum, can’t I just compute the checksum without the hdr_checksum field? And, can’t I check IF they’re equal at the end of the computation?

For example, it is highly likely that you might need to split the expression to compute the sum into many individual additions in a tree-like fashion. I hope you do know how to do that as well.

As I said, I am still a novice. I don’t know what you meant by that. Could you please kindly elaborate? Or, give a code snippet?

It might also be better to declare c1c30 as bit<32> and perform the casts when they are initialized, etc., etc., but the algorithm itself is simple and requires no loops.

Why is it better? And, wouldn’t that require me to initialize all the variables to be 0x0000 first to prevent garbage values?

  1. FINALLY, do you have any suggestions on how to debug P4 codes in general? Like, for high-level languages, one might use print statements to check the intermediate values and variables for error-free computations. But, there is no print statement in P4. So, how do you do it?

In the end, I would like to thank you again. You have been really helpful.

Hello Sandeep,

Let me try to answer some of your questions, but overall you should really talk to the vendor (Xilinx) or your professor (BTW, be aware that most professors do read this Forum and do know me as well :slight_smile: ). Even though I can find some documentation for XSA, I am sure that there are a lot more details to that.

Q: Where do I insert checksum processing code if I am writing it without using any externs?
A: The most natural places would be:

  • The beginning of the ingress Match-Action control (MyProcessing). Before you start processing the IPv4 header(s), you can compute and verify the checksum of the IPv4 header and then drop the packet if the checksum is not correct.
  • The end of the ingress Match-Action control (MyProcessing) or the end of the egress Match-Action control (if present in the architecture), after all header modifications have been completed. That’s where you would recompute IPv4 header checksum.

As an aside, this has little to do with the specialized controls, defined by v1model. They exist so that you can instantiate the Checksum() extern in there. Obviously, you can do whatever you want on a software target (like BMv2), but most real HW targets impose strict restrictions on what you can do in each of the controls. Note also, that some architectures (e.g. TNA, but PSA might be the same way) allow the programmers to instantiate the Checksum() externs in the parser and in the deparser (instead of declaring a separate control). But in any case, this should not matter in your case.

Q: How to parse IPv4 packets with options?
A: As per IPv4 RFC(s), the overall IPv4 header length (in 32-bit words) is specified in the h.ipv4.ihl field. Since the fixed portion is 20 bytes (5 32-bit-words long), the number of words in the options is equal to h.ipv4.ihl - 5.

Assuming the header structure from my previous post, the parser might look like this:

parser MyParser(packet_in pkt, out headers_t h, ...) {
. . .
    state parse_ipv4 {
        pkt.extract(h.ipv4); 
        transition select(h.ipv4.version, h.ipv4.ihl) {
             (4, 5) : parse_ipv4_no_options;
             (4, 6) : parse_ipv4_options_0_0;
             (4, 7) : parse_ipv4_options_0_1;
             (4, 8) : parse_ipv4_options_0_2;
             . . .
             (4, 15): parse_ipv4_options_0_9;
             default: reject;
    }

    state parse_ipv4_options_0_0 {
        pkt.extract(h.ipv4_options[0]);
        transition parse_ipv4_no_options;
    }

    state parse_ipv4_options_0_1 {
        pkt.extract(h.ipv4_options[0]);
        pkt.extract(h.ipv4_options[1]);
        transition parse_ipv4_no_options;
    }
    . . .
    state parse_ipv4_options_0_9 {
        pkt.extract(h.ipv4_options[0]);
        pkt.extract(h.ipv4_options[1]);
        . . .
        pkt.extract(h.ipv4_options[8]);
        pkt.extract(h.ipv4_options[9]);
        transition parse_ipv4_no_options;
    }

    state parse_ipv4_no_options {
        transition select(h.ipv4.frag_offset, h.ipv4_protocol) {
             . . .
        }
    }
    . . .
}

Q: How to quickly add multiple numbers together?
A: Frankly, it all depends on a target, but assuming a HW target, that is a lot better in doing things in parallel then sequentially, the typical approach is to replace:

res = a + b + c + d + e + f + g + h;

with

/* Step (stage) 1 */
tmp1 = a + b;
tmp2 = c + d;
tmp3 = e + f;
tmp4 = g + h;

/* Step (stage) 2 */
tmp1 = tmp1 + tmp2;
tmp3 = tmp3 + tmp4;

/* Step (stage) 3 */
res = tmp1 + tmp3;

Q: Can you clarify what you do with the carry?
A: Because we use 32-bit addition to add 16-bit values, the carry will accumulate in the upper bits ([31:16])

Q: How can I properly verify the checksum?
A: Please, read the RFC and follow it to the letter. One of the problems with 1-complement arithmetic is that the values 0x0000 and 0xFFFF (essentially that’s 0 and -0) are considered to be the same, so you need to be careful when doing comparisons.

Q: Why it might be better to do casting during chunk initialization and not during the addition?
A: It depends on which specific instructions the target’s ISA offers

Happy hacking,
Vladimir

Hello Vladimir,

Wow! That was really insightful. Thank you very much. I know that I might be asking basic questions here and I am sorry for that. I did not have much background in networking and P4 before jumping into this. So, I am really grateful for your patience and appreciate your bearing with me through this. I can’t thank you enough for this.

And, I am pretty sure that my professor would most definitely know you. In fact, anybody working in this field would be aware of you and your contributions to this field. But, I am also sure my professor doesn’t mind me getting all the help I need, especially from someone as experienced as you. I have been stuck at this stage for quite some time now.

I still have a few more queries:

Q.1) In your first reply, you mentioned that:

Obviously, you need to take care of header validity by predicating those assignments accordingly and writing 0 into these chunks in case the corresponding header is not valid. I assume you know how to do that.

I was wondering if you could give me a hint on how to check the validity of a particular header? I tried going through the Xilinx documentation but, they only mentioned an isValid() functionality. But, I think they use it to validate the entire header, be it the IPv4, v6, or the VLANs. But, when you said take care of header validity, I am assuming you meant that I should ensure each of the Option header fields is valid individually. Which, I don’t think the isValid() method does. So, any hint or direction in that regard would be really helpful.

What I was thinking of doing is:

STEP 1: Declare all the header fields and then initialize all of them with 0s before parsing them or taking data input from the packets as field. Once, I start calculating the checksum, if a field is invalid and is already padded in with 0s, adding them to the checksum wouldn’t matter anyway. That’s where I thought a loop would be necessary since I can loop it till the actual valid length of the IPv4 header and ignore the rest or pad them with 0s.

Q.2) In your first reply, you ALSO mentioned a code that:

bit<32> sum = (bit<32>)c1 + (bit<32>)c2 + … + (bit<32>)c30;
bit<16> internet_checksum = sum[15:0] + sum[31:16];

So, wouldn’t the above basically add all the valid fields in the IP headers, and then if it is greater than 16 bits it’d add the extra carry to the 16 LSBs? If I add the above without the hdr_checksum field, I am assuming that I’d still have to perform one’s complement on the final internet_checksum, like this:

bit<16> ones_complement_result;

// Calculate the one’s complement
ones_complement_result = ~internet_checksum;

…and then, check if the checksum is valid and decide whether to drop the packet or not:

apply {

if (hdr.ipv4.hdr_checksum != ones_complement_result) {
    dropPacket();
    return;
}

}

Wouldn’t this be the way? Because I computed the IP header checksums of the packets that I previously posted in my reply manually using this same approach and they all matched with the original packet checksums.

Q.3)Finally, I already asked this in one of my previous replies but may be you missed it. Is there a way in which P4 programmers could debug their programs? Anything? Like, we could use print statements in high-level languages but there is none in P4. Is there anything equivalent or similar debugging tools or strategy?

Hello Sandeep,

In order to keep this Forum well, organized I suggest that we agree on the following:

  1. Let’s try to stay on a subject. The question was “How can I calculate an IPv4 checksum without using an extern?” and I believe it has been fully answered. Just to reiterate:
    a. Split the necessary data into 16-bit chunks
    b. Calculate 1-complement sum of those. The algorithm for that calculation was also provided.
  2. If something is unclear, please ask, but do not tack on the follow-up questions to the post. Instead create posts for the specific questions that you have (e.g. how to check a header for validity)
  3. I think that as a matter of basic respect (for the other people’s time and for your own reputation) it is important that you come prepared. There is an expectation that you are familiar with the basic language features and are aware of other information that requires no more than a simple Google search.
  4. I think it should also be understood that we can’t teach you the language in this forum – that’s what the classes, professors and books(or other reading materials) are for.

With that said, let me answer the follow-up questions:

Q How to check header validity?
A The header method isValid() is a standard language feature. Please see above. if something is not clear, create another post

Q Can you clarify the details of the checksum calculation?
A The basic idea is that the sum of all the fields, including the checksum field should be equal to 0. That is the definition of the correct checksum.

Therefore, if you want to verify the checksum, you add together all the chunks and the result must be equal to 0 (or -0, i.e. 0xFFFF). In other words:

checksum = sum(c1, ..., c30)
if ((checksum == 0) || (checksum = 0xFFFF) {
    /* Header checksum is good */
} else {
   /* Header checksum is bad */
}

If you want to calculate the new value of the checksum (so that the next hop can verify the checksum using the algorithm above), then you need to make sure that the sum of all chunks (c1…c30) is equal to 0. Since you do not know the value of the chunk c6 (corresponding to h.ipv4.hdr_checksum) you need to solve a simple equation for c6:

sum(c1, ..., c5, c6 /* h.ipv4.hdr_checksum */, c7, ..., c30) = 0
h.ipv4.hdr_checksum = 0 - sum(c1, ..., c5, c7, ...., c30)
h.ipv4.hdr_checksum = -sum(c1, ..., c5, c7, ...., c30)
h.ipv4.hdr_checksum = ~sum(c1, ..., c5, c7, ...., c30) /* by definition of 1-complement arithmetic */

Q How can one debug P4 programs?
A This is worth a separate post. However, to make the discussion more productive, please keep in mind that there is no standard way to debug P4 programs, although there are many approaches there. Each target offers its own ways to do it (if any), so you need to ask the question for a specific platform (i.e. target+architecture). A lot if this information might be under NDA, though, so that’s where asking the vendor might be more helpful.

Happy hacking,
Vladimir