From bb2057e3458a0f6e6fb84da5de0fd0128b9e5c48 Mon Sep 17 00:00:00 2001 From: Matias Elo Date: Thu, 30 Apr 2026 08:47:33 +0300 Subject: [PATCH] linux-gen: parse: fix parsing of non-first IPv4 fragments Non-first IPv4 fragments do not include a complete L4 header. The parser was unconditionally parsing TCP/UDP/SCTP headers for all fragments, causing spurious errors. This in turn caused odp_pktio_stats() to report invalid 'in_errors' for every non-first fragment received. Fix this by stopping parsing after L3 for non-first IPv4 fragments. Also, make _odp_packet_l4_chksum() handling of fragmented packets more robust and change the if clauses to be exclusive. Closes: https://github.com/OpenDataPlane/odp/issues/2342 Signed-off-by: Matias Elo Reviewed-by: Janne Peltonen --- platform/linux-generic/odp_packet.c | 17 ++++++----------- platform/linux-generic/odp_parse.c | 28 ++++++++++++++++++++++------ 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/platform/linux-generic/odp_packet.c b/platform/linux-generic/odp_packet.c index 65a236237d6..662106d09fd 100644 --- a/platform/linux-generic/odp_packet.c +++ b/platform/linux-generic/odp_packet.c @@ -2065,10 +2065,13 @@ int _odp_packet_sctp_chksum_insert(odp_packet_t pkt) int _odp_packet_l4_chksum(odp_packet_hdr_t *pkt_hdr, odp_pktin_config_opt_t opt, uint64_t l4_part_sum) { + /* Skip fragmented packets */ + if (pkt_hdr->p.input_flags.ipfrag) + return pkt_hdr->p.flags.all.error != 0; + /* UDP chksum == 0 case is covered in parse_udp() */ if (opt.bit.udp_chksum && pkt_hdr->p.input_flags.udp && - !pkt_hdr->p.input_flags.ipfrag && !pkt_hdr->p.input_flags.udp_chksum_zero) { uint16_t sum = ~packet_sum(pkt_hdr, pkt_hdr->p.l3_offset, @@ -2085,11 +2088,7 @@ int _odp_packet_l4_chksum(odp_packet_hdr_t *pkt_hdr, if (opt.bit.drop_udp_err) return -1; } - } - - if (opt.bit.tcp_chksum && - pkt_hdr->p.input_flags.tcp && - !pkt_hdr->p.input_flags.ipfrag) { + } else if (opt.bit.tcp_chksum && pkt_hdr->p.input_flags.tcp) { uint16_t sum = ~packet_sum(pkt_hdr, pkt_hdr->p.l3_offset, pkt_hdr->p.l4_offset, @@ -2105,11 +2104,7 @@ int _odp_packet_l4_chksum(odp_packet_hdr_t *pkt_hdr, if (opt.bit.drop_tcp_err) return -1; } - } - - if (opt.bit.sctp_chksum && - pkt_hdr->p.input_flags.sctp && - !pkt_hdr->p.input_flags.ipfrag) { + } else if (opt.bit.sctp_chksum && pkt_hdr->p.input_flags.sctp) { uint32_t seg_len = 0; _odp_sctphdr_t hdr_copy; uint32_t sum = ~packet_sum_crc32c(pkt_hdr, diff --git a/platform/linux-generic/odp_parse.c b/platform/linux-generic/odp_parse.c index 98dd652a2cd..a337c55441d 100644 --- a/platform/linux-generic/odp_parse.c +++ b/platform/linux-generic/odp_parse.c @@ -112,7 +112,8 @@ uint16_t _odp_parse_eth(packet_parser_t *prs, const uint8_t **parseptr, static inline uint8_t parse_ipv4(packet_parser_t *prs, const uint8_t **parseptr, uint32_t *offset, uint32_t frame_len, odp_pktin_config_opt_t opt, - uint64_t *l4_part_sum) + uint64_t *l4_part_sum, + odp_bool_t *non_first_frag) { const _odp_ipv4hdr_t *ipv4 = (const _odp_ipv4hdr_t *)*parseptr; uint32_t dstaddr = odp_be_to_cpu_32(ipv4->dst_addr); @@ -154,9 +155,13 @@ static inline uint8_t parse_ipv4(packet_parser_t *prs, const uint8_t **parseptr, * OR * "fragment offset" field is nonzero (all fragments except the first) */ - if (odp_unlikely(_ODP_IPV4HDR_IS_FRAGMENT(frag_offset))) + if (odp_unlikely(_ODP_IPV4HDR_IS_FRAGMENT(frag_offset))) { prs->input_flags.ipfrag = 1; + if (_ODP_IPV4HDR_FRAG_OFFSET(frag_offset) != 0) + *non_first_frag = true; + } + /* Handle IPv4 broadcast / multicast */ if (odp_unlikely(dstaddr == 0xffffffff)) prs->input_flags.ip_bcast = 1; @@ -362,6 +367,7 @@ int _odp_packet_parse_common_l3_l4(packet_parser_t *prs, odp_pktin_config_opt_t opt) { uint8_t ip_proto; + odp_bool_t non_first_frag = false; prs->l3_offset = offset; @@ -376,7 +382,7 @@ int _odp_packet_parse_common_l3_l4(packet_parser_t *prs, case _ODP_ETHTYPE_IPV4: prs->input_flags.ipv4 = 1; ip_proto = parse_ipv4(prs, &parseptr, &offset, frame_len, - opt, l4_part_sum); + opt, l4_part_sum, &non_first_frag); if (odp_likely(!prs->flags.ip_err)) prs->l4_offset = offset; else if (opt.bit.drop_ipv4_err) @@ -406,7 +412,6 @@ int _odp_packet_parse_common_l3_l4(packet_parser_t *prs, if (layer == ODP_PROTO_LAYER_L3) return prs->flags.all.error != 0; - /* Set l4 flag only for known ip_proto */ prs->input_flags.l4 = 1; /* Parse Layer 4 headers */ @@ -423,9 +428,12 @@ int _odp_packet_parse_common_l3_l4(packet_parser_t *prs, break; case _ODP_IPPROTO_TCP: + prs->input_flags.tcp = 1; + /* Non-first IP fragments do not contain an L4 header */ + if (odp_unlikely(non_first_frag)) + return prs->flags.all.error != 0; if (odp_unlikely(offset + _ODP_TCPHDR_LEN > seg_end)) return -1; - prs->input_flags.tcp = 1; parse_tcp(prs, &parseptr, frame_len - prs->l4_offset, opt, l4_part_sum); if (prs->flags.tcp_err && opt.bit.drop_tcp_err) @@ -433,9 +441,12 @@ int _odp_packet_parse_common_l3_l4(packet_parser_t *prs, break; case _ODP_IPPROTO_UDP: + prs->input_flags.udp = 1; + /* Non-first IP fragments do not contain an L4 header */ + if (odp_unlikely(non_first_frag)) + return prs->flags.all.error != 0; if (odp_unlikely(offset + _ODP_UDPHDR_LEN > seg_end)) return -1; - prs->input_flags.udp = 1; parse_udp(prs, &parseptr, opt, l4_part_sum); if (prs->flags.udp_err && opt.bit.drop_udp_err) return -1; /* drop */ @@ -453,6 +464,11 @@ int _odp_packet_parse_common_l3_l4(packet_parser_t *prs, case _ODP_IPPROTO_SCTP: prs->input_flags.sctp = 1; + /* Non-first IP fragments do not contain an L4 header */ + if (odp_unlikely(non_first_frag)) + return prs->flags.all.error != 0; + if (odp_unlikely(offset + _ODP_SCTPHDR_LEN > seg_end)) + return -1; parse_sctp(prs, &parseptr, frame_len - prs->l4_offset, opt, l4_part_sum); if (prs->flags.sctp_err && opt.bit.drop_sctp_err)