Checksum calculation ICMPv6

I am converting an ICMPv6 RS packet into an RA one and send it back, but I can not get the calculations of the ICMPv6 checksum to work correctly, wireshark keeps detecting as being wrong, what am I missing here?


header icmpv6_t {
    bit<8> type;
    bit<8> code;
    bit<16> checksum;
}
header ndp_rs_t {               //Router Solicitation
    bit<32> flags;              //represents the reserved fields in the RS pkts
}

header ndp_ra_t {               // Router Advertisement
    bit<8>  cur_hop_limit;      // Current Hop Limit
    bit<8>  auto_config_flags;  // Autoconfiguration flag  (1bit) m_flag, (1bit) o_flag, (6bits) reserved_flags)
    bit<16> router_lifetime;    // Lifetime of the router (in seconds)
    bit<32> reachable_time;     // Time a node assumes a neighbor is reachable
    bit<32> retrans_timer;      // Time between retransmitted Neighbor Solicitation messages
}

header ndp_option_t {           //Represents the ICMPv6 Options field
    bit<8> type;
    bit<8> length;
    bit<48> value;
}
   /*
   * NDP router solicitation reply table and actions.
   * Handles NDP router solicitation message and send router advertisement to the sender.
   */
action ndp_rs_to_ra(mac_addr_t my_router_mac, ipv6_addr_t my_router_ipv6) { 

        // Ethernet
        hdr.ethernet.dst_addr = hdr.ethernet.src_addr;
        hdr.ethernet.src_addr = my_router_mac;

        // IPv6
        hdr.ipv6.dst_addr = hdr.ipv6.src_addr;
        hdr.ipv6.src_addr = my_router_ipv6;
        hdr.ipv6.payload_len = 16w24;         // value in bytes, ICMPv6 + NDP RA + NDP Option length (32 + 96 + 64 = 192 bits = 24 bytes)
        hdr.ipv6.next_header = PROTO_ICMPV6;


        // ICMPv6
        hdr.icmpv6.type = ICMP6_TYPE_RA;       // Router Advertisement

        // RA fields
        hdr.ndp_ra.setValid();
        hdr.ndp_ra.cur_hop_limit = 64;         // Example default
        hdr.ndp_ra.auto_config_flags = 0;      // m,o, and reserved flags
        hdr.ndp_ra.router_lifetime = 1800;     // 30 minutes
        hdr.ndp_ra.reachable_time = 0;         // Set as needed (no suggestion)
        hdr.ndp_ra.retrans_timer = 0;          // Set as needed (use your own value)

        // Optional: Include link-layer address option
        hdr.ndp_option.setValid();
        hdr.ndp_option.type = NDP_OPT_TARGET_LL_ADDR;
        hdr.ndp_option.length = 1;             // Length of NDP Options Header in 8-byte (64 bits) units
        hdr.ndp_option.value = my_router_mac;

        // Send back out the same port
        standard_metadata.egress_spec = standard_metadata.ingress_port;

        // set old header as invalid
        hdr.ndp_rs.setInvalid();
    }
    update_checksum(hdr.ndp_ra.isValid(),
        {
            // IPv6 Pseudo-header fields
            hdr.ipv6.src_addr,             // IPv6 source address
            hdr.ipv6.dst_addr,             // IPv6 destination address
            hdr.ipv6.payload_len,          // IPv6 payload length
            hdr.ipv6.next_header,          // Next Header (58 for ICMPv6)
            
            // ICMPv6 fields (header and body)
            hdr.icmpv6.type,               // ICMPv6 type
            hdr.icmpv6.code,               // ICMPv6 code
            16w0,                          // checksum field placeholder (zeroed out)
            hdr.ndp_ra.cur_hop_limit,      // Router Advertisement hop limit
            hdr.ndp_ra.auto_config_flags,  // Router Advertisement auto configuration flags
            hdr.ndp_ra.router_lifetime,    // Router Advertisement lifetime
            hdr.ndp_ra.reachable_time,     // Router Advertisement reachable time
            hdr.ndp_ra.retrans_timer,      // Router Advertisement retransmission timer
            hdr.ndp_option.type,           // NDP option type
            hdr.ndp_option.length,         // NDP option length
            hdr.ndp_option.value           // NDP option value
        },
        hdr.icmpv6.checksum,               // The field to store the calculated checksum
        HashAlgorithm.csum16               // Use the 16-bit checksum algorithm
    );

I suspected that I may have changed the IPv6 Payload Length to a wrong value but after some tests it seems fine:

Frame 7: 78 bytes on wire (624 bits), 78 bytes captured (624 bits) on interface r1-eth11, id 0
    Section number: 1
    Interface id: 0 (r1-eth11)
        Interface name: r1-eth11
    Encapsulation type: Ethernet (1)
    Arrival Time: Apr 12, 2025 14:35:57.287722105 WEST
    UTC Arrival Time: Apr 12, 2025 13:35:57.287722105 UTC
    Epoch Arrival Time: 1744464957.287722105
    [Time shift for this packet: 0.000000000 seconds]
    [Time delta from previous captured frame: 0.001876865 seconds]
    [Time delta from previous displayed frame: 0.001876865 seconds]
    [Time since reference or first frame: 13.074830438 seconds]
    Frame Number: 7
    Frame Length: 78 bytes (624 bits)
    Capture Length: 78 bytes (624 bits)
    [Frame is marked: False]
    [Frame is ignored: False]
    [Protocols in frame: eth:ethertype:ipv6:icmpv6]
    [Coloring Rule Name: ICMP]
    [Coloring Rule String: icmp || icmpv6]
Ethernet II, Src: Intel_00:00:01 (00:aa:00:00:00:01), Dst: 7e:8c:e7:f4:20:6e (7e:8c:e7:f4:20:6e)
    Destination: 7e:8c:e7:f4:20:6e (7e:8c:e7:f4:20:6e)
        Address: 7e:8c:e7:f4:20:6e (7e:8c:e7:f4:20:6e)
        .... ..1. .... .... .... .... = LG bit: Locally administered address (this is NOT the factory default)
        .... ...0 .... .... .... .... = IG bit: Individual address (unicast)
    Source: Intel_00:00:01 (00:aa:00:00:00:01)
        Address: Intel_00:00:01 (00:aa:00:00:00:01)
        .... ..0. .... .... .... .... = LG bit: Globally unique address (factory default)
        .... ...0 .... .... .... .... = IG bit: Individual address (unicast)
    Type: IPv6 (0x86dd)
Internet Protocol Version 6, Src: 2001:1:1::ff, Dst: fe80::7c8c:e7ff:fef4:206e
    0110 .... = Version: 6
    .... 0000 0000 .... .... .... .... .... = Traffic Class: 0x00 (DSCP: CS0, ECN: Not-ECT)
        .... 0000 00.. .... .... .... .... .... = Differentiated Services Codepoint: Default (0)
        .... .... ..00 .... .... .... .... .... = Explicit Congestion Notification: Not ECN-Capable Transport (0)
    .... 0000 0000 0000 0000 0000 = Flow Label: 0x00000
    Payload Length: 24
    Next Header: ICMPv6 (58)
    Hop Limit: 255
    Source Address: 2001:1:1::ff
    Destination Address: fe80::7c8c:e7ff:fef4:206e
Internet Control Message Protocol v6
    Type: Router Advertisement (134)
    Code: 0
    Checksum: 0x6da5 incorrect, should be 0x8c86
        [Expert Info (Warning/Checksum): Bad checksum [should be 0x8c86]]
            [Bad checksum [should be 0x8c86]]
            [Severity level: Warning]
            [Group: Checksum]
    [Checksum Status: Bad]
    Cur hop limit: 64
    Flags: 0x00, Prf (Default Router Preference): Medium
        0... .... = Managed address configuration: Not set
        .0.. .... = Other configuration: Not set
        ..0. .... = Home Agent: Not set
        ...0 0... = Prf (Default Router Preference): Medium (0)
        .... .0.. = ND Proxy: Not set
        .... ..00 = Reserved: 0
    Router lifetime (s): 1800
    Reachable time (ms): 0
    Retrans timer (ms): 0
    ICMPv6 Option (Target link-layer address : 00:aa:00:00:00:01)
        Type: Target link-layer address (2)
        Length: 1 (8 bytes)
        Link-layer address: Intel_00:00:01 (00:aa:00:00:00:01)

ok solved the problem and I am leaving the solution here for others, i missed next_hader and payload_lenght were swaped and i was missing the appending of some zeros.

        update_checksum(hdr.ndp_ra.isValid(),
            {
                // IPv6 Pseudo-header fields
                hdr.ipv6.src_addr,             // IPv6 source address
                hdr.ipv6.dst_addr,             // IPv6 destination address
                hdr.ipv6.payload_len,          // IPv6 payload length
                24w0,                          // 3 bytes of zero
                hdr.ipv6.next_header,          // Next Header (58 for ICMPv6)
                
                // ICMPv6 fields (header and body)
                hdr.icmpv6.type,               // ICMPv6 type RA
                hdr.icmpv6.code,               // ICMPv6 code 
                16w0,                          // checksum field placeholder (zeroed out)
                hdr.ndp_ra.cur_hop_limit,      // Router Advertisement hop limit
                hdr.ndp_ra.auto_config_flags,  // Router Advertisement auto configuration flags
                hdr.ndp_ra.router_lifetime,    // Router Advertisement lifetime
                hdr.ndp_ra.reachable_time,     // Router Advertisement reachable time
                hdr.ndp_ra.retrans_timer,      // Router Advertisement retransmission timer
                hdr.ndp_option                 // Contains 3 flags
            },
            hdr.icmpv6.checksum,               // The field to store the calculated checksum
            HashAlgorithm.csum16               // Use the 16-bit checksum algorithm
        );