From: Stephen Hemminger <stephen@networkplumber.org>
To: dev@dpdk.org
Cc: Stephen Hemminger <stephen@networkplumber.org>
Subject: [PATCH 6/6] app/test: add test for IP reassembly
Date: Tue, 16 Jun 2026 14:05:38 -0700 [thread overview]
Message-ID: <20260616210656.464062-7-stephen@networkplumber.org> (raw)
In-Reply-To: <20260616210656.464062-1-stephen@networkplumber.org>
There was no functional test for IPv4/IPv6 reassembly,
only a performance test. Add a new test modeled on the Linux kernel
selftest tools/testing/selftests/net/ip_defrag.c. This is new
code so no license conflict.
Test covers:
size and fragment-size sweep across in-order, reverse,
odd/even and block delivery orders with byte-exact payload validation;
minimum 8-byte fragments; the fragment-count limit;
timeout of incomplete datagrams;
and the duplicate, overlap, extension-header and
oversized-fragment cases fixed earlier in this series.
The reassembled packet is checked with rte_mbuf_check() to catch
malformed segment chains in addition to wrong content.
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
app/test/meson.build | 1 +
app/test/test_reassembly.c | 644 +++++++++++++++++++++++++++++++++++++
2 files changed, 645 insertions(+)
create mode 100644 app/test/test_reassembly.c
diff --git a/app/test/meson.build b/app/test/meson.build
index 61024125a7..b8c2208d0b 100644
--- a/app/test/meson.build
+++ b/app/test/meson.build
@@ -160,6 +160,7 @@ source_file_deps = {
'test_rawdev.c': ['rawdev', 'bus_vdev', 'raw_skeleton'],
'test_rcu_qsbr.c': ['rcu', 'hash'],
'test_rcu_qsbr_perf.c': ['rcu', 'hash'],
+ 'test_reassembly.c': ['net', 'ip_frag'],
'test_reassembly_perf.c': ['net', 'ip_frag'],
'test_reciprocal_division.c': [],
'test_reciprocal_division_perf.c': [],
diff --git a/app/test/test_reassembly.c b/app/test/test_reassembly.c
new file mode 100644
index 0000000000..9cada5f3b4
--- /dev/null
+++ b/app/test/test_reassembly.c
@@ -0,0 +1,644 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026
+ *
+ * Functional unit tests for the IP reassembly path of librte_ip_frag.
+ *
+ * Coverage mirrors the Linux selftest tools/testing/selftests/net/ip_defrag.c
+ * adapted to the library API and to DPDK-specific constraints:
+ *
+ * - size / fragment-size sweep, bounded by RTE_LIBRTE_IP_FRAG_MAX_FRAG
+ * - in-order, reverse, odd-then-even, and block-reordered delivery
+ * - byte-exact validation of the reassembled payload (not just length)
+ * - minimum (8-byte) fragments
+ * - fragment-count boundary: exactly MAX reassembles, MAX + 1 fails
+ * - incomplete datagram reaped on timeout
+ * - zero-length fragment rejected
+ * - duplicate fragment tolerated in a reordered set
+ * - overlapping fragments (leading/trailing/contained) discarded
+ * - IPv6 fragment with extension headers in the unfragmentable part dropped
+ * - fragment whose end exceeds the maximum datagram size dropped
+ *
+ * The last four groups depend on the corresponding reassembly fixes
+ * (duplicate tolerance, overlap discard, extension-header drop, oversize
+ * drop); they pass once those are applied and fail on unpatched code. The
+ * remaining cases pass regardless.
+ *
+ * Fragments use l2_len == 0; the library reads the L3 header at offset 0.
+ */
+
+#include "test.h"
+
+#include <string.h>
+
+#include <rte_common.h>
+#include <rte_cycles.h>
+#include <rte_ip.h>
+#include <rte_ip_frag.h>
+#include <rte_log.h>
+#include <rte_mbuf.h>
+#include <rte_mempool.h>
+
+#define NB_MBUF 1024
+#define MBUF_CACHE 0 /* exact accounting for leak checks */
+#define MBUF_DATA 2048
+#define V4_L3_LEN ((uint16_t)sizeof(struct rte_ipv4_hdr))
+#define V6_L3_LEN ((uint16_t)(sizeof(struct rte_ipv6_hdr) + \
+ RTE_IPV6_FRAG_HDR_SIZE))
+#define TEST_ID 0x4242
+
+#ifndef RTE_LIBRTE_IP_FRAG_MAX_FRAG
+#define RTE_LIBRTE_IP_FRAG_MAX_FRAG 8
+#endif
+#define MAX_FRAG RTE_LIBRTE_IP_FRAG_MAX_FRAG
+
+#define MAX_PAYLOAD (MAX_FRAG * 256) /* keeps a fragment in one mbuf */
+
+enum family { V4, V6 };
+enum order { IN_ORDER, REVERSE, ODD_EVEN, BLOCK };
+
+struct frag_desc {
+ uint16_t ofs; /* byte offset into the payload */
+ uint16_t plen; /* payload bytes after L3 */
+ uint8_t mf;
+};
+
+static struct rte_mempool *pkt_pool;
+
+/* position-dependent payload pattern, non-periodic at 256 so a misordered
+ * reassembly is detected even when lengths line up.
+ */
+static inline uint8_t
+pat(uint32_t k)
+{
+ return (uint8_t)(k * 31u + 7u);
+}
+
+/* ------------------------------- harness -------------------------------- */
+
+static int
+testsuite_setup(void)
+{
+ /* the table create/destroy per case is chatty at INFO level */
+ rte_log_set_level_pattern("lib.ip_frag", RTE_LOG_NOTICE);
+
+ pkt_pool = rte_pktmbuf_pool_create("REASM_POOL", NB_MBUF, MBUF_CACHE,
+ 0, MBUF_DATA, SOCKET_ID_ANY);
+ return pkt_pool == NULL ? TEST_FAILED : TEST_SUCCESS;
+}
+
+static void
+testsuite_teardown(void)
+{
+ rte_mempool_free(pkt_pool);
+ pkt_pool = NULL;
+}
+
+/* Every case must start and end with a full pool, so a leak in one case is
+ * pinpointed here rather than silently masking the next one.
+ */
+static int
+ut_setup(void)
+{
+ if (rte_mempool_avail_count(pkt_pool) != NB_MBUF) {
+ printf("pool not full at case start: %u/%u\n",
+ rte_mempool_avail_count(pkt_pool), NB_MBUF);
+ return TEST_FAILED;
+ }
+ return TEST_SUCCESS;
+}
+
+static struct rte_ip_frag_tbl *
+tbl_new(uint64_t max_cycles)
+{
+ return rte_ip_frag_table_create(16, MAX_FRAG, 16, max_cycles,
+ rte_socket_id());
+}
+
+/* Build one fragment with a position-dependent payload. */
+static struct rte_mbuf *
+build_frag(enum family fam, uint16_t ofs, uint16_t plen, uint8_t mf)
+{
+ struct rte_mbuf *m = rte_pktmbuf_alloc(pkt_pool);
+ uint16_t l3 = (fam == V4) ? V4_L3_LEN : V6_L3_LEN;
+ char *p;
+ uint16_t i;
+
+ if (m == NULL)
+ return NULL;
+ m->data_off = 0;
+
+ if (fam == V4) {
+ struct rte_ipv4_hdr *ip = rte_pktmbuf_mtod(m,
+ struct rte_ipv4_hdr *);
+ uint16_t fo = ofs / RTE_IPV4_HDR_OFFSET_UNITS;
+
+ memset(ip, 0, V4_L3_LEN);
+ if (mf)
+ fo |= RTE_IPV4_HDR_MF_FLAG;
+ ip->version_ihl = 0x45;
+ ip->total_length = rte_cpu_to_be_16(V4_L3_LEN + plen);
+ ip->packet_id = rte_cpu_to_be_16(TEST_ID);
+ ip->fragment_offset = rte_cpu_to_be_16(fo);
+ ip->time_to_live = 64;
+ ip->next_proto_id = IPPROTO_UDP;
+ ip->src_addr = rte_cpu_to_be_32(0x0a000001);
+ ip->dst_addr = rte_cpu_to_be_32(0x0a000002);
+ } else {
+ struct rte_ipv6_hdr *ip = rte_pktmbuf_mtod(m,
+ struct rte_ipv6_hdr *);
+ struct rte_ipv6_fragment_ext *fh =
+ rte_pktmbuf_mtod_offset(m,
+ struct rte_ipv6_fragment_ext *,
+ sizeof(struct rte_ipv6_hdr));
+
+ memset(ip, 0, V6_L3_LEN);
+ ip->vtc_flow = rte_cpu_to_be_32(6u << 28);
+ ip->payload_len = rte_cpu_to_be_16(RTE_IPV6_FRAG_HDR_SIZE + plen);
+ ip->proto = IPPROTO_FRAGMENT;
+ ip->hop_limits = 64;
+ ip->src_addr.a[15] = 1;
+ ip->dst_addr.a[15] = 2;
+ fh->next_header = IPPROTO_UDP;
+ fh->reserved = 0;
+ fh->frag_data = rte_cpu_to_be_16(
+ RTE_IPV6_SET_FRAG_DATA(ofs, mf ? 1 : 0));
+ fh->id = rte_cpu_to_be_32(TEST_ID);
+ }
+
+ p = rte_pktmbuf_mtod_offset(m, char *, l3);
+ for (i = 0; i < plen; i++)
+ p[i] = (char)pat(ofs + i);
+
+ m->data_len = m->pkt_len = l3 + plen;
+ m->l2_len = 0;
+ m->l3_len = l3;
+ return m;
+}
+
+static struct rte_mbuf *
+feed(enum family fam, struct rte_ip_frag_tbl *tbl,
+ struct rte_ip_frag_death_row *dr, const struct frag_desc *d, uint64_t tms)
+{
+ struct rte_mbuf *m = build_frag(fam, d->ofs, d->plen, d->mf);
+
+ if (m == NULL)
+ return NULL;
+ if (fam == V4) {
+ struct rte_ipv4_hdr *ip = rte_pktmbuf_mtod(m, struct rte_ipv4_hdr *);
+ return rte_ipv4_frag_reassemble_packet(tbl, dr, m, tms, ip);
+ } else {
+ struct rte_ipv6_hdr *ip = rte_pktmbuf_mtod(m, struct rte_ipv6_hdr *);
+ struct rte_ipv6_fragment_ext *fh =
+ rte_pktmbuf_mtod_offset(m, struct rte_ipv6_fragment_ext *,
+ sizeof(struct rte_ipv6_hdr));
+ return rte_ipv6_frag_reassemble_packet(tbl, dr, m, tms, ip, fh);
+ }
+}
+
+/* Split a datagram of total_plen into fragments of frag_size (multiple of 8).
+ * Returns the fragment count, or -1 if it would exceed MAX_FRAG.
+ */
+static int
+make_datagram(uint16_t total_plen, uint16_t frag_size, struct frag_desc *out)
+{
+ int n = 0;
+ uint16_t ofs = 0;
+
+ while (ofs < total_plen) {
+ uint16_t rem = total_plen - ofs;
+ uint16_t len = rem <= frag_size ? rem : frag_size;
+
+ if (n >= MAX_FRAG)
+ return -1;
+ out[n].ofs = ofs;
+ out[n].plen = len;
+ out[n].mf = (ofs + len < total_plen);
+ ofs += len;
+ n++;
+ }
+ return n;
+}
+
+/* Produce a delivery order (array of indices into descs). */
+static void
+make_order(enum order ord, int n, int *idx)
+{
+ int i, k = 0;
+
+ switch (ord) {
+ case IN_ORDER:
+ for (i = 0; i < n; i++)
+ idx[i] = i;
+ break;
+ case REVERSE:
+ for (i = 0; i < n; i++)
+ idx[i] = n - 1 - i;
+ break;
+ case ODD_EVEN:
+ for (i = 1; i < n; i += 2)
+ idx[k++] = i;
+ for (i = 0; i < n; i += 2)
+ idx[k++] = i;
+ break;
+ case BLOCK: {
+ int t = n / 3 ? n / 3 : 1;
+
+ for (i = 2 * t; i < n; i++)
+ idx[k++] = i;
+ for (i = t; i < 2 * t && i < n; i++)
+ idx[k++] = i;
+ for (i = 0; i < t && i < n; i++)
+ idx[k++] = i;
+ break;
+ }
+ }
+}
+
+/* Feed descs in the given order; return reassembled mbuf or NULL. */
+static struct rte_mbuf *
+run_ordered(enum family fam, const struct frag_desc *descs, int n,
+ const int *idx)
+{
+ struct rte_ip_frag_death_row dr;
+ struct rte_ip_frag_tbl *tbl;
+ struct rte_mbuf *out = NULL;
+ uint64_t tms = rte_rdtsc();
+ int i;
+
+ memset(&dr, 0, sizeof(dr));
+ tbl = tbl_new(rte_get_tsc_hz());
+ if (tbl == NULL)
+ return NULL;
+ for (i = 0; i < n; i++) {
+ struct rte_mbuf *r = feed(fam, tbl, &dr, &descs[idx[i]], tms);
+
+ if (r != NULL)
+ out = r;
+ }
+ rte_ip_frag_free_death_row(&dr, 0);
+ rte_ip_frag_table_destroy(tbl);
+ return out;
+}
+
+/* Validate length and byte-exact payload, then free. Returns 0 on success.
+ * Note: reassembly strips the IPv6 fragment header, so the reassembled v6
+ * header is sizeof(rte_ipv6_hdr), not the V6_L3_LEN the fragments were built
+ * with. v4 has no fragment header to remove.
+ */
+static int
+validate(struct rte_mbuf *m, enum family fam, uint16_t total_plen)
+{
+ uint16_t l3 = (fam == V4) ? V4_L3_LEN :
+ (uint16_t)sizeof(struct rte_ipv6_hdr);
+ uint8_t buf[MAX_PAYLOAD];
+ const uint8_t *p;
+ const char *reason;
+ uint16_t k;
+ int rc = 0;
+
+ if (m == NULL)
+ return -1;
+ if (rte_mbuf_check(m, 1, &reason) != 0) {
+ printf(" bad mbuf fam=%d total=%u: %s\n", fam, total_plen,
+ reason);
+ rte_pktmbuf_free(m);
+ return -1;
+ }
+ if (m->pkt_len != (uint32_t)(l3 + total_plen)) {
+ rte_pktmbuf_free(m);
+ return -1;
+ }
+ p = rte_pktmbuf_read(m, l3, total_plen, buf);
+ if (p == NULL) {
+ rte_pktmbuf_free(m);
+ return -1;
+ }
+ for (k = 0; k < total_plen; k++) {
+ if (p[k] != pat(k)) {
+ rc = -1;
+ break;
+ }
+ }
+ rte_pktmbuf_free(m);
+ return rc;
+}
+
+/* --------------------------- baseline / sweep --------------------------- */
+
+static int
+sweep_one(enum family fam, uint16_t total_plen, uint16_t frag_size)
+{
+ struct frag_desc descs[MAX_FRAG];
+ int idx[MAX_FRAG];
+ const enum order orders[] = { IN_ORDER, REVERSE, ODD_EVEN, BLOCK };
+ int n = make_datagram(total_plen, frag_size, descs);
+ unsigned int o;
+
+ if (n < 2) /* skip single-fragment / oversized for sweep */
+ return 0;
+
+ for (o = 0; o < RTE_DIM(orders); o++) {
+ make_order(orders[o], n, idx);
+ if (validate(run_ordered(fam, descs, n, idx), fam,
+ total_plen) != 0) {
+ printf(" sweep fail: fam=%d total=%u fs=%u order=%u n=%d\n",
+ fam, total_plen, frag_size, orders[o], n);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static int
+sweep(enum family fam)
+{
+ const uint16_t fsizes[] = { 8, 16, 64, 256 };
+ unsigned int f;
+
+ for (f = 0; f < RTE_DIM(fsizes); f++) {
+ uint16_t fs = fsizes[f];
+ uint16_t total;
+
+ /* cover 2..MAX_FRAG fragments, last fragment partial */
+ for (total = fs + 8; total <= fs * MAX_FRAG; total += fs) {
+ if (sweep_one(fam, total, fs) != 0)
+ return TEST_FAILED;
+ if (total > fs + 4 &&
+ sweep_one(fam, total - 4, fs) != 0)
+ return TEST_FAILED;
+ }
+ }
+ return TEST_SUCCESS;
+}
+
+static int test_sweep_v4(void) { return sweep(V4); }
+static int test_sweep_v6(void) { return sweep(V6); }
+
+/* Minimum 8-byte fragments. */
+static int
+test_min_fragment(void)
+{
+ struct frag_desc d[3] = {
+ { 0, 8, 1 }, { 8, 8, 1 }, { 16, 8, 0 },
+ };
+ int idx[3];
+
+ make_order(REVERSE, 3, idx);
+ TEST_ASSERT_SUCCESS(validate(run_ordered(V4, d, 3, idx), V4, 24),
+ "min 8-byte fragments not reassembled");
+ make_order(ODD_EVEN, 3, idx);
+ TEST_ASSERT_SUCCESS(validate(run_ordered(V6, d, 3, idx), V6, 24),
+ "min 8-byte fragments not reassembled (v6)");
+ return TEST_SUCCESS;
+}
+
+/* Exactly MAX_FRAG fragments reassembles; MAX_FRAG + 1 fails. */
+static int
+test_cap_boundary(void)
+{
+ struct frag_desc d[MAX_FRAG + 1];
+ int idx[MAX_FRAG + 1];
+ uint16_t fs = 8, total = fs * MAX_FRAG;
+ int n, i;
+
+ n = make_datagram(total, fs, d);
+ TEST_ASSERT_EQUAL(n, MAX_FRAG, "expected MAX_FRAG fragments");
+ make_order(IN_ORDER, n, idx);
+ TEST_ASSERT_SUCCESS(validate(run_ordered(V4, d, n, idx), V4, total),
+ "MAX_FRAG fragments should reassemble");
+
+ /* one more fragment than the table can hold */
+ for (i = 0; i <= MAX_FRAG; i++) {
+ d[i].ofs = i * fs;
+ d[i].plen = fs;
+ d[i].mf = (i < MAX_FRAG);
+ idx[i] = i;
+ }
+ TEST_ASSERT_NULL(run_ordered(V4, d, MAX_FRAG + 1, idx),
+ "MAX_FRAG + 1 fragments should not reassemble");
+ TEST_ASSERT_EQUAL(rte_mempool_avail_count(pkt_pool), NB_MBUF,
+ "overflowing set leaked mbufs");
+ return TEST_SUCCESS;
+}
+
+/* Incomplete datagram: no output, reaped on timeout. */
+static int
+test_incomplete_timeout(void)
+{
+ struct rte_ip_frag_death_row dr;
+ struct rte_ip_frag_tbl *tbl;
+ uint64_t mc = rte_get_tsc_hz(), tms = rte_rdtsc();
+ struct frag_desc d[2] = { { 0, 64, 1 }, { 128, 64, 0 } }; /* gap */
+ struct rte_mbuf *out = NULL;
+ int i;
+
+ memset(&dr, 0, sizeof(dr));
+ tbl = tbl_new(mc);
+ TEST_ASSERT_NOT_NULL(tbl, "table create failed");
+ for (i = 0; i < 2; i++) {
+ struct rte_mbuf *r = feed(V4, tbl, &dr, &d[i], tms);
+
+ if (r != NULL)
+ out = r;
+ }
+ TEST_ASSERT_NULL(out, "incomplete datagram reassembled");
+ rte_ip_frag_table_del_expired_entries(tbl, &dr, tms + mc + 1);
+ rte_ip_frag_free_death_row(&dr, 0);
+ rte_ip_frag_table_destroy(tbl);
+ TEST_ASSERT_EQUAL(rte_mempool_avail_count(pkt_pool), NB_MBUF,
+ "expired fragments not freed");
+ return TEST_SUCCESS;
+}
+
+static int
+test_zero_len(void)
+{
+ struct frag_desc d = { 0, 0, 1 };
+ int idx = 0;
+
+ TEST_ASSERT_NULL(run_ordered(V4, &d, 1, &idx),
+ "zero-length fragment accepted");
+ TEST_ASSERT_EQUAL(rte_mempool_avail_count(pkt_pool), NB_MBUF,
+ "zero-length fragment leaked");
+ return TEST_SUCCESS;
+}
+
+/* --------------------- duplicate / overlap / reject --------------------- */
+
+/* A duplicate anywhere in a reordered set must not break reassembly. */
+static int
+test_dup_tolerated(void)
+{
+ /* offsets 0,64,128,192 with 64B frags; inject a dup of frag 1 */
+ struct frag_desc d[5] = {
+ { 0, 64, 1 }, { 64, 64, 1 }, { 64, 64, 1 }, /* dup */
+ { 128, 64, 1 }, { 192, 64, 0 },
+ };
+ int idx[5] = { 1, 4, 2, 0, 3 }; /* reordered, dup interleaved */
+
+ TEST_ASSERT_SUCCESS(validate(run_ordered(V4, d, 5, idx), V4, 256),
+ "duplicate fragment broke reassembly");
+ return TEST_SUCCESS;
+}
+
+/* Overlap geometries; the datagram must be discarded and every collected
+ * fragment freed. The last fragment is withheld so that on unfixed code the
+ * entry is *retained* (total_size stays UINT32_MAX) rather than torn down by
+ * the frag_size > total_size path: that retention is what we detect. We
+ * capture the mbufs still held in the table after draining the death row,
+ * before destroying the table (destroy frees held mbufs, hiding the leak).
+ */
+static int
+overlap_case(enum family fam, const struct frag_desc *d, int n, const char *what)
+{
+ struct rte_ip_frag_death_row dr;
+ struct rte_ip_frag_tbl *tbl;
+ struct rte_mbuf *out = NULL;
+ uint64_t tms = rte_rdtsc();
+ unsigned int held;
+ int i;
+
+ memset(&dr, 0, sizeof(dr));
+ tbl = tbl_new(rte_get_tsc_hz());
+ if (tbl == NULL)
+ return -1;
+ for (i = 0; i < n; i++) {
+ struct rte_mbuf *r = feed(fam, tbl, &dr, &d[i], tms);
+
+ if (r != NULL)
+ out = r;
+ }
+ rte_ip_frag_free_death_row(&dr, 0);
+ held = NB_MBUF - rte_mempool_avail_count(pkt_pool);
+ rte_ip_frag_table_destroy(tbl);
+
+ if (out != NULL) {
+ rte_pktmbuf_free(out);
+ printf(" overlap reassembled instead of discarded: %s\n", what);
+ return -1;
+ }
+ if (held != 0) {
+ printf(" overlap kept %u fragment(s) instead of discarding: %s\n",
+ held, what);
+ return -1;
+ }
+ return 0;
+}
+
+static int
+test_overlap(void)
+{
+ /* last fragment withheld in every case (all MF=1) */
+
+ /* overlapping fragment arrives second */
+ const struct frag_desc tail[2] = { { 0, 600, 1 }, { 300, 600, 1 } };
+ /* overlapping fragment arrives first */
+ const struct frag_desc head[2] = { { 300, 600, 1 }, { 0, 600, 1 } };
+ /* a fragment fully contained in an existing one */
+ const struct frag_desc cont[2] = { { 0, 600, 1 }, { 200, 200, 1 } };
+
+ TEST_ASSERT_SUCCESS(overlap_case(V6, tail, 2, "v6 overlap second"), "");
+ TEST_ASSERT_SUCCESS(overlap_case(V6, head, 2, "v6 overlap first"), "");
+ TEST_ASSERT_SUCCESS(overlap_case(V6, cont, 2, "v6 contained"), "");
+ TEST_ASSERT_SUCCESS(overlap_case(V4, tail, 2, "v4 overlap second"), "");
+ return TEST_SUCCESS;
+}
+
+/* IPv6 fragment with an unfragmentable extension header is dropped, not
+ * stored. Capture whether the fragment is still held in the table after the
+ * death row is drained but before the table is destroyed.
+ */
+static int
+test_v6_ext_header_drop(void)
+{
+ struct rte_ip_frag_death_row dr;
+ struct rte_ip_frag_tbl *tbl;
+ struct rte_mbuf *m, *r;
+ struct rte_ipv6_hdr *ip;
+ struct rte_ipv6_fragment_ext *fh;
+ unsigned int held;
+
+ memset(&dr, 0, sizeof(dr));
+ tbl = tbl_new(rte_get_tsc_hz());
+ TEST_ASSERT_NOT_NULL(tbl, "table create failed");
+
+ m = build_frag(V6, 0, 64, 1);
+ TEST_ASSERT_NOT_NULL(m, "alloc failed");
+ /* pretend an 8-byte extension header sits before the fragment header */
+ m->l3_len += 8;
+ ip = rte_pktmbuf_mtod(m, struct rte_ipv6_hdr *);
+ fh = rte_pktmbuf_mtod_offset(m, struct rte_ipv6_fragment_ext *,
+ sizeof(struct rte_ipv6_hdr));
+ r = rte_ipv6_frag_reassemble_packet(tbl, &dr, m, rte_rdtsc(), ip, fh);
+ rte_ip_frag_free_death_row(&dr, 0);
+ held = NB_MBUF - rte_mempool_avail_count(pkt_pool);
+ rte_ip_frag_table_destroy(tbl);
+
+ TEST_ASSERT_NULL(r, "fragment with extension header accepted");
+ TEST_ASSERT_EQUAL(held, 0,
+ "extension-header fragment stored instead of dropped");
+ return TEST_SUCCESS;
+}
+
+/* A fragment whose end exceeds the max datagram size is dropped, not stored. */
+static int
+oversize_drop_one(enum family fam)
+{
+ struct rte_ip_frag_death_row dr;
+ struct rte_ip_frag_tbl *tbl;
+ struct frag_desc d = { 0xFFF8, 64, 0 }; /* offset 65528 + 64 > 65535 */
+ struct rte_mbuf *r;
+ unsigned int held;
+
+ memset(&dr, 0, sizeof(dr));
+ tbl = tbl_new(rte_get_tsc_hz());
+ if (tbl == NULL)
+ return -1;
+ r = feed(fam, tbl, &dr, &d, rte_rdtsc());
+ rte_ip_frag_free_death_row(&dr, 0);
+ held = NB_MBUF - rte_mempool_avail_count(pkt_pool);
+ rte_ip_frag_table_destroy(tbl);
+
+ if (r != NULL) {
+ rte_pktmbuf_free(r);
+ return -1;
+ }
+ return held == 0 ? 0 : -1;
+}
+
+static int
+test_oversize_drop(void)
+{
+ TEST_ASSERT_SUCCESS(oversize_drop_one(V4),
+ "oversized v4 fragment stored instead of dropped");
+ TEST_ASSERT_SUCCESS(oversize_drop_one(V6),
+ "oversized v6 fragment stored instead of dropped");
+ return TEST_SUCCESS;
+}
+
+static struct unit_test_suite reassembly_testsuite = {
+ .suite_name = "IP Reassembly Unit Test Suite",
+ .setup = testsuite_setup,
+ .teardown = testsuite_teardown,
+ .unit_test_cases = {
+ TEST_CASE_ST(ut_setup, NULL, test_sweep_v4),
+ TEST_CASE_ST(ut_setup, NULL, test_sweep_v6),
+ TEST_CASE_ST(ut_setup, NULL, test_min_fragment),
+ TEST_CASE_ST(ut_setup, NULL, test_cap_boundary),
+ TEST_CASE_ST(ut_setup, NULL, test_incomplete_timeout),
+ TEST_CASE_ST(ut_setup, NULL, test_zero_len),
+ TEST_CASE_ST(ut_setup, NULL, test_dup_tolerated),
+ TEST_CASE_ST(ut_setup, NULL, test_overlap),
+ TEST_CASE_ST(ut_setup, NULL, test_v6_ext_header_drop),
+ TEST_CASE_ST(ut_setup, NULL, test_oversize_drop),
+ TEST_CASES_END()
+ }
+};
+
+static int
+test_reassembly(void)
+{
+ return unit_test_suite_runner(&reassembly_testsuite);
+}
+
+REGISTER_FAST_TEST(reassembly_autotest, NOHUGE_SKIP, ASAN_OK, test_reassembly);
--
2.53.0
prev parent reply other threads:[~2026-06-16 21:07 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-16 21:05 [PATCH 0/6] ip_frag: fix reassembly defects and add test Stephen Hemminger
2026-06-16 21:05 ` [PATCH 1/6] ip_frag: tolerate duplicate fragments Stephen Hemminger
2026-06-16 21:05 ` [PATCH 2/6] ip_frag: discard datagrams with overlapping fragments Stephen Hemminger
2026-06-16 21:05 ` [PATCH 3/6] ip_frag: include protocol in IPv4 reassembly key Stephen Hemminger
2026-06-16 21:05 ` [PATCH 4/6] ip_frag: drop IPv6 fragments with unexpected headers Stephen Hemminger
2026-06-16 21:05 ` [PATCH 5/6] ip_frag: reject oversized reassembled datagrams Stephen Hemminger
2026-06-16 21:05 ` Stephen Hemminger [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260616210656.464062-7-stephen@networkplumber.org \
--to=stephen@networkplumber.org \
--cc=dev@dpdk.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox