From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124]) by inbox.dpdk.org (Postfix) with ESMTP id 349BE46C6E for ; Fri, 8 Aug 2025 20:56:47 +0200 (CEST) Received: from mails.dpdk.org (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id BE40C40611; Fri, 8 Aug 2025 20:56:46 +0200 (CEST) Received: from frogstar.hit.bme.hu (frogstar.hit.bme.hu [152.66.248.44]) by mails.dpdk.org (Postfix) with ESMTP id 9937F40270 for ; Fri, 8 Aug 2025 20:56:45 +0200 (CEST) Received: from [IPV6:2a03:bf01:f7e:d700:91d5:6740:bd5c:b973] (ipv6-0f7ed70091d56740bd5cb973.customers.kabelnet.hu [IPv6:2a03:bf01:f7e:d700:91d5:6740:bd5c:b973]) (authenticated bits=0) by frogstar.hit.bme.hu (8.18.1/8.17.1) with ESMTPSA id 578IuZhX046647 (version=TLSv1.3 cipher=TLS_AES_128_GCM_SHA256 bits=128 verify=NO); Fri, 8 Aug 2025 20:56:41 +0200 (CEST) (envelope-from lencse@hit.bme.hu) X-Authentication-Warning: frogstar.hit.bme.hu: Host ipv6-0f7ed70091d56740bd5cb973.customers.kabelnet.hu [IPv6:2a03:bf01:f7e:d700:91d5:6740:bd5c:b973] claimed to be [IPV6:2a03:bf01:f7e:d700:91d5:6740:bd5c:b973] Content-Type: multipart/alternative; boundary="------------pwoiLIxhXuXlldF50z6yWCdn" Message-ID: Date: Fri, 8 Aug 2025 20:56:33 +0200 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Subject: Re: How to calculate ICMPv6 checksum? To: Stephen Hemminger Cc: "users@dpdk.org" References: <40938de8-49b3-46d0-964b-9cd296000d10@hit.bme.hu> <20250807105703.22de669d@hermes.local> Content-Language: en-US From: =?UTF-8?Q?G=C3=A1bor_LENCSE?= In-Reply-To: <20250807105703.22de669d@hermes.local> X-Virus-Scanned: clamav-milter 0.103.12 at frogstar.hit.bme.hu X-Virus-Status: Clean Received-SPF: pass (frogstar.hit.bme.hu: authenticated connection) receiver=frogstar.hit.bme.hu; client-ip=2a03:bf01:f7e:d700:91d5:6740:bd5c:b973; helo=[IPV6:2a03:bf01:f7e:d700:91d5:6740:bd5c:b973]; envelope-from=lencse@hit.bme.hu; x-software=spfmilter 2.001 http://www.acme.com/software/spfmilter/ with libspf2-1.2.11; X-DCC--Metrics: frogstar.hit.bme.hu; whitelist X-Spam-Status: No, score=-0.9 required=5.0 tests=ALL_TRUSTED, AWL, HTML_MESSAGE, T_SCC_BODY_TEXT_LINE autolearn=disabled version=3.4.6-frogstar X-Spam-Checker-Version: SpamAssassin 3.4.6-frogstar (2021-04-09) on frogstar.hit.bme.hu X-Scanned-By: MIMEDefang 2.86 X-BeenThere: users@dpdk.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: DPDK usage discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: users-bounces@dpdk.org This is a multi-part message in MIME format. --------------pwoiLIxhXuXlldF50z6yWCdn Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 8bit Dear Stephen, Thank you very much for your answer. It helps me a lot, but I have further questions. Please see my comments inline. > The pseudo-header part is different. If I understand it correctly, then it means that I need to write the ICMPv6 checksum function myself. To that end, I reviewed the source code of the "rte_ipv6_udptcp_cksum()" function so that I can learn from it. However, I did not find where it differs from the one that I need. I took the below source code from here: https://doc.dpdk.org/api/rte__ip6_8h_source.html#l00610 rte_ipv6_udptcp_cksum(const struct rte_ipv6_hdr *ipv6_hdr, const void *l4_hdr) { uint16_t cksum = __rte_ipv6_udptcp_cksum(ipv6_hdr, l4_hdr); cksum = ~cksum; /* * Per RFC 768: If the computed checksum is zero for UDP, * it is transmitted as all ones * (the equivalent in one's complement arithmetic). */ if (cksum == 0 && ipv6_hdr->proto == IPPROTO_UDP) cksum = 0xffff; return cksum; } It is the highest level. It calls an internal function and at the end it considers the protocol number (with other words, the next header field of the IPv6 header) when it handles UDP specific things, thus I think that this time it does not cause any problem in the case of ICMPv6. This is the source code of the internal function: static inline uint16_t __rte_ipv6_udptcp_cksum(const struct rte_ipv6_hdr *ipv6_hdr, const void *l4_hdr) {     uint32_t cksum;     uint32_t l4_len;     l4_len = rte_be_to_cpu_16(ipv6_hdr->payload_len);     cksum = rte_raw_cksum(l4_hdr, l4_len);     cksum += rte_ipv6_phdr_cksum(ipv6_hdr, 0);     cksum = ((cksum & 0xffff0000) >> 16) + (cksum & 0xffff);     return (uint16_t)cksum; } It calculates the checksum for the L4 part and also for the pseudo-header separately. The latter could be different than what I need for ICMPv6. I also checked the source code of  "rte_ipv6_phdr_cksum(ipv6_hdr, 0)", please see it below the figure from RFC 2460. > https://www.rfc-editor.org/rfc/rfc4443 > > 2.3. Message Checksum Calculation > > The checksum is the 16-bit one's complement of the one's complement > sum of the entire ICMPv6 message, starting with the ICMPv6 message > type field, and prepended with a "pseudo-header" of IPv6 header > fields, as specified in [IPv6, Section 8.1]. The Next Header value > used in the pseudo-header is 58. (The inclusion of a pseudo-header > in the ICMPv6 checksum is a change from IPv4; see [IPv6] for the > rationale for this change.) > > For computing the checksum, the checksum field is first set to zero. > > https://www.rfc-editor.org/rfc/rfc2460#section-8.1 > > 8.1 Upper-Layer Checksums > > Any transport or other upper-layer protocol that includes the > addresses from the IP header in its checksum computation must be > modified for use over IPv6, to include the 128-bit IPv6 addresses > instead of 32-bit IPv4 addresses. In particular, the following > illustration shows the TCP and UDP "pseudo-header" for IPv6: > > +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ > | | > + + > | | > + Source Address + > | | > + + > | | > +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ > | | > + + > | | > + Destination Address + > | | > + + > | | > +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ > | Upper-Layer Packet Length | > +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ > | zero | Next Header | > +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ So this is what I need. And it seems to me, that the below source code does exactly the same: static inline uint16_t rte_ipv6_phdr_cksum(const struct rte_ipv6_hdr *ipv6_hdr, uint64_t ol_flags) {     uint32_t sum;     struct {         rte_be32_t len;   /* L4 length. */         rte_be32_t proto; /* L4 protocol - top 3 bytes must be zero */     } psd_hdr;     psd_hdr.proto = (uint32_t)(ipv6_hdr->proto << 24);     if (ol_flags & (RTE_MBUF_F_TX_TCP_SEG | RTE_MBUF_F_TX_UDP_SEG))         psd_hdr.len = 0;     else         psd_hdr.len = ipv6_hdr->payload_len;     sum = __rte_raw_cksum(&ipv6_hdr->src_addr,         sizeof(ipv6_hdr->src_addr) + sizeof(ipv6_hdr->dst_addr),         0);     sum = __rte_raw_cksum(&psd_hdr, sizeof(psd_hdr), sum);     return __rte_raw_cksum_reduce(sum); } As required, it handles length field on 32 bits, and shifts the protocol field (containing the value of 58) to the left by 24 bit, which means the same as the "next header" field is at the topmost 8 bits of a 32 bit number in the drawing. Then it does a "trick" that it uses the source and destination IPv6 addresses from the IPv6 packet (likely to spare their copying). Thus, I did not find anything what I would need to do differently. However, on the other hand, _there should be something_, because I tried using the "rte_ipv6_udptcp_cksum()" function (of course, I set the checksum field to 0 before using it), but Wireshark said that the checksum was incorrect. Both tshark and Wireshark decodes my NA message perfectly, but the Linux kernel of the device under test does not accept it, this is why it sends further NS messages. This is a tshark capture on the device under test: root@dut:~# tshark -i eno1 Running as user "root" and group "root". This could be dangerous. Capturing on 'eno1'     1 0.000000000 fe80::baca:3aff:fe5e:25a8 → ff02::16     ICMPv6 170 Multicast Listener Report Message v2     2 0.379986848 fe80::baca:3aff:fe5e:25a8 → ff02::16     ICMPv6 170 Multicast Listener Report Message v2     3 4.156047617    2001:2::2 → 2001:2:0:8000::2 UDP 80 58488 → 27971 Len=18     4 4.156066982 fe80::baca:3aff:fe5e:25a8 → ff02::1:ff00:2 ICMPv6 86 Neighbor Solicitation for 2001:2::2 from b8:ca:3a:5e:25:a8     5 4.156092949    2001:2::2 → fe80::baca:3aff:fe5e:25a8 ICMPv6 86 Neighbor Advertisement 2001:2::2 (ovr) is at 24:6e:96:3c:3f:40     6 5.183987802 fe80::baca:3aff:fe5e:25a8 → ff02::1:ff00:2 ICMPv6 86 Neighbor Solicitation for 2001:2::2 from b8:ca:3a:5e:25:a8     7 5.184007499    2001:2::2 → fe80::baca:3aff:fe5e:25a8 ICMPv6 86 Neighbor Advertisement 2001:2::2 (ovr) is at 24:6e:96:3c:3f:40     8 6.203987286 fe80::baca:3aff:fe5e:25a8 → ff02::1:ff00:2 ICMPv6 86 Neighbor Solicitation for 2001:2::2 from b8:ca:3a:5e:25:a8     9 6.204007429    2001:2::2 → fe80::baca:3aff:fe5e:25a8 ICMPv6 86 Neighbor Advertisement 2001:2::2 (ovr) is at 24:6e:96:3c:3f:40    10 7.232005250    2001:2::1 → ff02::1:ff00:2 ICMPv6 86 Neighbor Solicitation for 2001:2::2 from b8:ca:3a:5e:25:a8    11 8.251987771 fe80::baca:3aff:fe5e:25a8 → ff02::1:ff00:2 ICMPv6 86 Neighbor Solicitation for 2001:2::2 from b8:ca:3a:5e:25:a8    12 9.275986860 fe80::baca:3aff:fe5e:25a8 → ff02::1:ff00:2 ICMPv6 86 Neighbor Solicitation for 2001:2::2 from b8:ca:3a:5e:25:a8 And Wireshark says: "Checksum: 0x1baf incorrect, should be 0x035d". Could you please advise me, what I could overlook? Best regards, Gábor --------------pwoiLIxhXuXlldF50z6yWCdn Content-Type: text/html; charset=UTF-8 Content-Transfer-Encoding: 8bit Dear Stephen,

Thank you very much for your answer. It helps me a lot, but I have further questions. Please see my comments inline.
The pseudo-header part is different.
If I understand it correctly, then it means that I need to write the ICMPv6 checksum function myself. To that end, I reviewed the source code of the "rte_ipv6_udptcp_cksum()" function so that I can learn from it. However, I did not find where it differs from the one that I need. I took the below source code from here: https://doc.dpdk.org/api/rte__ip6_8h_source.html#l00610 rte_ipv6_udptcp_cksum(const struct rte_ipv6_hdr *ipv6_hdr, const void *l4_hdr) { uint16_t cksum = __rte_ipv6_udptcp_cksum(ipv6_hdr, l4_hdr); cksum = ~cksum; /* * Per RFC 768: If the computed checksum is zero for UDP, * it is transmitted as all ones * (the equivalent in one's complement arithmetic). */ if (cksum == 0 && ipv6_hdr->proto == IPPROTO_UDP) cksum = 0xffff; return cksum; } It is the highest level. It calls an internal function and at the end it considers the protocol number (with other words, the next header field of the IPv6 header) when it handles UDP specific things, thus I think that this time it does not cause any problem in the case of ICMPv6.

This is the source code of the internal function:

static inline uint16_t
__rte_ipv6_udptcp_cksum(const struct rte_ipv6_hdr *ipv6_hdr, const void *l4_hdr)
{
    uint32_t cksum;
    uint32_t l4_len;
 
    l4_len = rte_be_to_cpu_16(ipv6_hdr->payload_len);
 
    cksum = rte_raw_cksum(l4_hdr, l4_len);
    cksum += rte_ipv6_phdr_cksum(ipv6_hdr, 0);
 
    cksum = ((cksum & 0xffff0000) >> 16) + (cksum & 0xffff);
 
    return (uint16_t)cksum;
}

It calculates the checksum for the L4 part and also for the pseudo-header separately. The latter could be different than what I need for ICMPv6.

I also checked the source code of  "rte_ipv6_phdr_cksum(ipv6_hdr, 0)", please see it below the figure from RFC 2460.

https://www.rfc-editor.org/rfc/rfc4443

2.3.  Message Checksum Calculation

   The checksum is the 16-bit one's complement of the one's complement
   sum of the entire ICMPv6 message, starting with the ICMPv6 message
   type field, and prepended with a "pseudo-header" of IPv6 header
   fields, as specified in [IPv6, Section 8.1].  The Next Header value
   used in the pseudo-header is 58.  (The inclusion of a pseudo-header
   in the ICMPv6 checksum is a change from IPv4; see [IPv6] for the
   rationale for this change.)

   For computing the checksum, the checksum field is first set to zero.

https://www.rfc-editor.org/rfc/rfc2460#section-8.1

8.1 Upper-Layer Checksums

   Any transport or other upper-layer protocol that includes the
   addresses from the IP header in its checksum computation must be
   modified for use over IPv6, to include the 128-bit IPv6 addresses
   instead of 32-bit IPv4 addresses.  In particular, the following
   illustration shows the TCP and UDP "pseudo-header" for IPv6:

   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                                                               |
   +                                                               +
   |                                                               |
   +                         Source Address                        +
   |                                                               |
   +                                                               +
   |                                                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                                                               |
   +                                                               +
   |                                                               |
   +                      Destination Address                      +
   |                                                               |
   +                                                               +
   |                                                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                   Upper-Layer Packet Length                   |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                      zero                     |  Next Header  |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

So this is what I need. And it seems to me, that the below source code does exactly the same:

static inline uint16_t
rte_ipv6_phdr_cksum(const struct rte_ipv6_hdr *ipv6_hdr, uint64_t ol_flags)
{
    uint32_t sum;
    struct {
        rte_be32_t len;   /* L4 length. */
        rte_be32_t proto; /* L4 protocol - top 3 bytes must be zero */
    } psd_hdr;
 
    psd_hdr.proto = (uint32_t)(ipv6_hdr->proto << 24);
    if (ol_flags & (RTE_MBUF_F_TX_TCP_SEG | RTE_MBUF_F_TX_UDP_SEG))
        psd_hdr.len = 0;
    else
        psd_hdr.len = ipv6_hdr->payload_len;
 
    sum = __rte_raw_cksum(&ipv6_hdr->src_addr,
        sizeof(ipv6_hdr->src_addr) + sizeof(ipv6_hdr->dst_addr),
        0);
    sum = __rte_raw_cksum(&psd_hdr, sizeof(psd_hdr), sum);
    return __rte_raw_cksum_reduce(sum);
}

As required, it handles length field on 32 bits, and shifts the protocol field (containing the value of 58) to the left by 24 bit, which means the same as the "next header" field is at the topmost 8 bits of a 32 bit number in the drawing.

Then it does a "trick" that it uses the source and destination IPv6 addresses from the IPv6 packet (likely to spare their copying).

Thus, I did not find anything what I would need to do differently.

However, on the other hand, there should be something, because I tried using the "rte_ipv6_udptcp_cksum()" function (of course, I set the checksum field to 0 before using it), but Wireshark said that the checksum was incorrect. Both tshark and Wireshark decodes my NA message perfectly, but the Linux kernel of the device under test does not accept it, this is why it sends further NS messages.

This is a tshark capture on the device under test:

root@dut:~# tshark -i eno1
Running as user "root" and group "root". This could be dangerous.
Capturing on 'eno1'
    1 0.000000000 fe80::baca:3aff:fe5e:25a8 → ff02::16     ICMPv6 170 Multicast Listener Report Message v2
    2 0.379986848 fe80::baca:3aff:fe5e:25a8 → ff02::16     ICMPv6 170 Multicast Listener Report Message v2
    3 4.156047617    2001:2::2 → 2001:2:0:8000::2 UDP 80 58488 → 27971 Len=18
    4 4.156066982 fe80::baca:3aff:fe5e:25a8 → ff02::1:ff00:2 ICMPv6 86 Neighbor Solicitation for 2001:2::2 from b8:ca:3a:5e:25:a8
    5 4.156092949    2001:2::2 → fe80::baca:3aff:fe5e:25a8 ICMPv6 86 Neighbor Advertisement 2001:2::2 (ovr) is at 24:6e:96:3c:3f:40
    6 5.183987802 fe80::baca:3aff:fe5e:25a8 → ff02::1:ff00:2 ICMPv6 86 Neighbor Solicitation for 2001:2::2 from b8:ca:3a:5e:25:a8
    7 5.184007499    2001:2::2 → fe80::baca:3aff:fe5e:25a8 ICMPv6 86 Neighbor Advertisement 2001:2::2 (ovr) is at 24:6e:96:3c:3f:40
    8 6.203987286 fe80::baca:3aff:fe5e:25a8 → ff02::1:ff00:2 ICMPv6 86 Neighbor Solicitation for 2001:2::2 from b8:ca:3a:5e:25:a8
    9 6.204007429    2001:2::2 → fe80::baca:3aff:fe5e:25a8 ICMPv6 86 Neighbor Advertisement 2001:2::2 (ovr) is at 24:6e:96:3c:3f:40
   10 7.232005250    2001:2::1 → ff02::1:ff00:2 ICMPv6 86 Neighbor Solicitation for 2001:2::2 from b8:ca:3a:5e:25:a8
   11 8.251987771 fe80::baca:3aff:fe5e:25a8 → ff02::1:ff00:2 ICMPv6 86 Neighbor Solicitation for 2001:2::2 from b8:ca:3a:5e:25:a8
   12 9.275986860 fe80::baca:3aff:fe5e:25a8 → ff02::1:ff00:2 ICMPv6 86 Neighbor Solicitation for 2001:2::2 from b8:ca:3a:5e:25:a8


And Wireshark says: "Checksum: 0x1baf incorrect, should be 0x035d".

Could you please advise me, what I could overlook?

Best regards,

Gábor


--------------pwoiLIxhXuXlldF50z6yWCdn--