Branch data Line data Source code
1 : : /* SPDX-License-Identifier: BSD-3-Clause
2 : : * Copyright(c) 2026
3 : : *
4 : : * Functional unit tests for the IP reassembly path of librte_ip_frag.
5 : : *
6 : : * Coverage mirrors the Linux selftest tools/testing/selftests/net/ip_defrag.c
7 : : * adapted to the library API and to DPDK-specific constraints:
8 : : *
9 : : * - size / fragment-size sweep, bounded by RTE_LIBRTE_IP_FRAG_MAX_FRAG
10 : : * - in-order, reverse, odd-then-even, and block-reordered delivery
11 : : * - byte-exact validation of the reassembled payload (not just length)
12 : : * - minimum (8-byte) fragments
13 : : * - fragment-count boundary: exactly MAX reassembles, MAX + 1 fails
14 : : * - incomplete datagram reaped on timeout
15 : : * - zero-length fragment rejected
16 : : * - duplicate fragment tolerated in a reordered set
17 : : * - overlapping fragments (leading/trailing/contained) discarded
18 : : * - IPv6 fragment with extension headers in the unfragmentable part dropped
19 : : * - fragment whose end exceeds the maximum datagram size dropped
20 : : *
21 : : * The last four groups depend on the corresponding reassembly fixes
22 : : * (duplicate tolerance, overlap discard, extension-header drop, oversize
23 : : * drop); they pass once those are applied and fail on unpatched code. The
24 : : * remaining cases pass regardless.
25 : : *
26 : : * Fragments use l2_len == 0; the library reads the L3 header at offset 0.
27 : : */
28 : :
29 : : #include "test.h"
30 : :
31 : : #include <string.h>
32 : :
33 : : #include <rte_common.h>
34 : : #include <rte_cycles.h>
35 : : #include <rte_ip.h>
36 : : #include <rte_ip_frag.h>
37 : : #include <rte_log.h>
38 : : #include <rte_mbuf.h>
39 : : #include <rte_mempool.h>
40 : :
41 : : #define NB_MBUF 1024
42 : : #define MBUF_CACHE 0 /* exact accounting for leak checks */
43 : : #define MBUF_DATA 2048
44 : : #define V4_L3_LEN ((uint16_t)sizeof(struct rte_ipv4_hdr))
45 : : #define V6_L3_LEN ((uint16_t)(sizeof(struct rte_ipv6_hdr) + \
46 : : RTE_IPV6_FRAG_HDR_SIZE))
47 : : #define TEST_ID 0x4242
48 : :
49 : : #ifndef RTE_LIBRTE_IP_FRAG_MAX_FRAG
50 : : #define RTE_LIBRTE_IP_FRAG_MAX_FRAG 8
51 : : #endif
52 : : #define MAX_FRAG RTE_LIBRTE_IP_FRAG_MAX_FRAG
53 : :
54 : : #define MAX_PAYLOAD (MAX_FRAG * 256) /* keeps a fragment in one mbuf */
55 : :
56 : : enum family { V4, V6 };
57 : : enum order { IN_ORDER, REVERSE, ODD_EVEN, BLOCK };
58 : :
59 : : struct frag_desc {
60 : : uint16_t ofs; /* byte offset into the payload */
61 : : uint16_t plen; /* payload bytes after L3 */
62 : : uint8_t mf;
63 : : };
64 : :
65 : : static struct rte_mempool *pkt_pool;
66 : :
67 : : /* position-dependent payload pattern, non-periodic at 256 so a misordered
68 : : * reassembly is detected even when lengths line up.
69 : : */
70 : : static inline uint8_t
71 : : pat(uint32_t k)
72 : : {
73 : 319128 : return (uint8_t)(k * 31u + 7u);
74 : : }
75 : :
76 : : /* ------------------------------- harness -------------------------------- */
77 : :
78 : : static int
79 : 1 : testsuite_setup(void)
80 : : {
81 : : /* the table create/destroy per case is chatty at INFO level */
82 : 1 : rte_log_set_level_pattern("lib.ip_frag", RTE_LOG_NOTICE);
83 : :
84 : 1 : pkt_pool = rte_pktmbuf_pool_create("REASM_POOL", NB_MBUF, MBUF_CACHE,
85 : : 0, MBUF_DATA, SOCKET_ID_ANY);
86 [ + - ]: 1 : return pkt_pool == NULL ? TEST_FAILED : TEST_SUCCESS;
87 : : }
88 : :
89 : : static void
90 : 1 : testsuite_teardown(void)
91 : : {
92 : 1 : rte_mempool_free(pkt_pool);
93 : 1 : pkt_pool = NULL;
94 : 1 : }
95 : :
96 : : /* Every case must start and end with a full pool, so a leak in one case is
97 : : * pinpointed here rather than silently masking the next one.
98 : : */
99 : : static int
100 : 10 : ut_setup(void)
101 : : {
102 [ - + ]: 10 : if (rte_mempool_avail_count(pkt_pool) != NB_MBUF) {
103 : 0 : printf("pool not full at case start: %u/%u\n",
104 : : rte_mempool_avail_count(pkt_pool), NB_MBUF);
105 : 0 : return TEST_FAILED;
106 : : }
107 : : return TEST_SUCCESS;
108 : : }
109 : :
110 : : static struct rte_ip_frag_tbl *
111 : 462 : tbl_new(uint64_t max_cycles)
112 : : {
113 : 462 : return rte_ip_frag_table_create(16, MAX_FRAG, 16, max_cycles,
114 : 462 : rte_socket_id());
115 : : }
116 : :
117 : : /* Build one fragment with a position-dependent payload. */
118 : : static struct rte_mbuf *
119 : 2281 : build_frag(enum family fam, uint16_t ofs, uint16_t plen, uint8_t mf)
120 : : {
121 : 2281 : struct rte_mbuf *m = rte_pktmbuf_alloc(pkt_pool);
122 [ + + ]: 2281 : uint16_t l3 = (fam == V4) ? V4_L3_LEN : V6_L3_LEN;
123 : : char *p;
124 : : uint16_t i;
125 : :
126 [ + - ]: 2281 : if (m == NULL)
127 : : return NULL;
128 : 2281 : m->data_off = 0;
129 : :
130 [ + + ]: 2281 : if (fam == V4) {
131 : 1151 : struct rte_ipv4_hdr *ip = rte_pktmbuf_mtod(m,
132 : : struct rte_ipv4_hdr *);
133 [ + + ]: 1151 : uint16_t fo = ofs / RTE_IPV4_HDR_OFFSET_UNITS;
134 : :
135 : : memset(ip, 0, V4_L3_LEN);
136 [ + + ]: 1151 : if (mf)
137 : 921 : fo |= RTE_IPV4_HDR_MF_FLAG;
138 : 1151 : ip->version_ihl = 0x45;
139 [ - + ]: 1151 : ip->total_length = rte_cpu_to_be_16(V4_L3_LEN + plen);
140 : 1151 : ip->packet_id = rte_cpu_to_be_16(TEST_ID);
141 [ - + ]: 1151 : ip->fragment_offset = rte_cpu_to_be_16(fo);
142 : 1151 : ip->time_to_live = 64;
143 : 1151 : ip->next_proto_id = IPPROTO_UDP;
144 : 1151 : ip->src_addr = rte_cpu_to_be_32(0x0a000001);
145 : 1151 : ip->dst_addr = rte_cpu_to_be_32(0x0a000002);
146 : : } else {
147 : 1130 : struct rte_ipv6_hdr *ip = rte_pktmbuf_mtod(m,
148 : : struct rte_ipv6_hdr *);
149 [ - + ]: 1130 : struct rte_ipv6_fragment_ext *fh =
150 : : rte_pktmbuf_mtod_offset(m,
151 : : struct rte_ipv6_fragment_ext *,
152 : : sizeof(struct rte_ipv6_hdr));
153 : :
154 : : memset(ip, 0, V6_L3_LEN);
155 : 1130 : ip->vtc_flow = rte_cpu_to_be_32(6u << 28);
156 [ - + ]: 1130 : ip->payload_len = rte_cpu_to_be_16(RTE_IPV6_FRAG_HDR_SIZE + plen);
157 : 1130 : ip->proto = IPPROTO_FRAGMENT;
158 : 1130 : ip->hop_limits = 64;
159 : 1130 : ip->src_addr.a[15] = 1;
160 : 1130 : ip->dst_addr.a[15] = 2;
161 : 1130 : fh->next_header = IPPROTO_UDP;
162 : : fh->reserved = 0;
163 [ - + ]: 1130 : fh->frag_data = rte_cpu_to_be_16(
164 : : RTE_IPV6_SET_FRAG_DATA(ofs, mf ? 1 : 0));
165 : 1130 : fh->id = rte_cpu_to_be_32(TEST_ID);
166 : : }
167 : :
168 : 2281 : p = rte_pktmbuf_mtod_offset(m, char *, l3);
169 [ + + ]: 164241 : for (i = 0; i < plen; i++)
170 : 161960 : p[i] = (char)pat(ofs + i);
171 : :
172 : 2281 : m->data_len = m->pkt_len = l3 + plen;
173 : 2281 : m->l2_len = 0;
174 : 2281 : m->l3_len = l3;
175 : 2281 : return m;
176 : : }
177 : :
178 : : static struct rte_mbuf *
179 : 2281 : feed(enum family fam, struct rte_ip_frag_tbl *tbl,
180 : : struct rte_ip_frag_death_row *dr, const struct frag_desc *d, uint64_t tms)
181 : : {
182 : 2281 : struct rte_mbuf *m = build_frag(fam, d->ofs, d->plen, d->mf);
183 : :
184 [ + - ]: 2281 : if (m == NULL)
185 : : return NULL;
186 [ + + ]: 2281 : if (fam == V4) {
187 : 1151 : struct rte_ipv4_hdr *ip = rte_pktmbuf_mtod(m, struct rte_ipv4_hdr *);
188 : 1151 : return rte_ipv4_frag_reassemble_packet(tbl, dr, m, tms, ip);
189 : : } else {
190 : 1130 : struct rte_ipv6_hdr *ip = rte_pktmbuf_mtod(m, struct rte_ipv6_hdr *);
191 : 1130 : struct rte_ipv6_fragment_ext *fh =
192 : 1130 : rte_pktmbuf_mtod_offset(m, struct rte_ipv6_fragment_ext *,
193 : : sizeof(struct rte_ipv6_hdr));
194 : 1130 : return rte_ipv6_frag_reassemble_packet(tbl, dr, m, tms, ip, fh);
195 : : }
196 : : }
197 : :
198 : : /* Split a datagram of total_plen into fragments of frag_size (multiple of 8).
199 : : * Returns the fragment count, or -1 if it would exceed MAX_FRAG.
200 : : */
201 : : static int
202 : : make_datagram(uint16_t total_plen, uint16_t frag_size, struct frag_desc *out)
203 : : {
204 : : int n = 0;
205 : : uint16_t ofs = 0;
206 : :
207 [ + + + + ]: 681 : while (ofs < total_plen) {
208 : 568 : uint16_t rem = total_plen - ofs;
209 : 568 : uint16_t len = rem <= frag_size ? rem : frag_size;
210 : :
211 [ + - + - ]: 568 : if (n >= MAX_FRAG)
212 : : return -1;
213 : 568 : out[n].ofs = ofs;
214 : 568 : out[n].plen = len;
215 : 568 : out[n].mf = (ofs + len < total_plen);
216 : 568 : ofs += len;
217 : 568 : n++;
218 : : }
219 : : return n;
220 : : }
221 : :
222 : : /* Produce a delivery order (array of indices into descs). */
223 : : static void
224 : 448 : make_order(enum order ord, int n, int *idx)
225 : : {
226 : : int i, k = 0;
227 : :
228 [ + + + + : 448 : switch (ord) {
- ]
229 : : case IN_ORDER:
230 [ + + + + ]: 681 : for (i = 0; i < n; i++)
231 : 568 : idx[i] = i;
232 : : break;
233 : : case REVERSE:
234 [ + + + + ]: 676 : for (i = 0; i < n; i++)
235 : 563 : idx[i] = n - 1 - i;
236 : : break;
237 : : case ODD_EVEN:
238 [ + + + + ]: 370 : for (i = 1; i < n; i += 2)
239 : 257 : idx[k++] = i;
240 [ + + + + ]: 419 : for (i = 0; i < n; i += 2)
241 : 306 : idx[k++] = i;
242 : : break;
243 : 112 : case BLOCK: {
244 [ + + ]: 112 : int t = n / 3 ? n / 3 : 1;
245 : :
246 [ + + ]: 352 : for (i = 2 * t; i < n; i++)
247 : 240 : idx[k++] = i;
248 [ + + + - ]: 272 : for (i = t; i < 2 * t && i < n; i++)
249 : 160 : idx[k++] = i;
250 [ + + ]: 272 : for (i = 0; i < t && i < n; i++)
251 : 160 : idx[k++] = i;
252 : : break;
253 : : }
254 : : }
255 : 448 : }
256 : :
257 : : /* Feed descs in the given order; return reassembled mbuf or NULL. */
258 : : static struct rte_mbuf *
259 : 454 : run_ordered(enum family fam, const struct frag_desc *descs, int n,
260 : : const int *idx)
261 : : {
262 : : struct rte_ip_frag_death_row dr;
263 : : struct rte_ip_frag_tbl *tbl;
264 : : struct rte_mbuf *out = NULL;
265 : : uint64_t tms = rte_rdtsc();
266 : : int i;
267 : :
268 : : memset(&dr, 0, sizeof(dr));
269 : 454 : tbl = tbl_new(rte_get_tsc_hz());
270 [ + - ]: 454 : if (tbl == NULL)
271 : : return NULL;
272 [ + + ]: 2723 : for (i = 0; i < n; i++) {
273 : 2269 : struct rte_mbuf *r = feed(fam, tbl, &dr, &descs[idx[i]], tms);
274 : :
275 [ + + ]: 2269 : if (r != NULL)
276 : : out = r;
277 : : }
278 : 454 : rte_ip_frag_free_death_row(&dr, 0);
279 : 454 : rte_ip_frag_table_destroy(tbl);
280 : 454 : return out;
281 : : }
282 : :
283 : : /* Validate length and byte-exact payload, then free. Returns 0 on success.
284 : : * Note: reassembly strips the IPv6 fragment header, so the reassembled v6
285 : : * header is sizeof(rte_ipv6_hdr), not the V6_L3_LEN the fragments were built
286 : : * with. v4 has no fragment header to remove.
287 : : */
288 : : static int
289 : 452 : validate(struct rte_mbuf *m, enum family fam, uint16_t total_plen)
290 : : {
291 [ + + ]: 452 : uint16_t l3 = (fam == V4) ? V4_L3_LEN :
292 : : (uint16_t)sizeof(struct rte_ipv6_hdr);
293 : : uint8_t buf[MAX_PAYLOAD];
294 : : const uint8_t *p;
295 : : const char *reason;
296 : : uint16_t k;
297 : : int rc = 0;
298 : :
299 [ + - ]: 452 : if (m == NULL)
300 : : return -1;
301 [ - + ]: 452 : if (rte_mbuf_check(m, 1, &reason) != 0) {
302 : 0 : printf(" bad mbuf fam=%d total=%u: %s\n", fam, total_plen,
303 : : reason);
304 : 0 : rte_pktmbuf_free(m);
305 : 0 : return -1;
306 : : }
307 [ - + ]: 452 : if (m->pkt_len != (uint32_t)(l3 + total_plen)) {
308 : 0 : rte_pktmbuf_free(m);
309 : 0 : return -1;
310 : : }
311 [ - + ]: 452 : p = rte_pktmbuf_read(m, l3, total_plen, buf);
312 [ - + ]: 452 : if (p == NULL) {
313 : 0 : rte_pktmbuf_free(m);
314 : 0 : return -1;
315 : : }
316 [ + + ]: 157620 : for (k = 0; k < total_plen; k++) {
317 [ + - ]: 157168 : if (p[k] != pat(k)) {
318 : : rc = -1;
319 : : break;
320 : : }
321 : : }
322 : 452 : rte_pktmbuf_free(m);
323 : 452 : return rc;
324 : : }
325 : :
326 : : /* --------------------------- baseline / sweep --------------------------- */
327 : :
328 : : static int
329 : 112 : sweep_one(enum family fam, uint16_t total_plen, uint16_t frag_size)
330 : : {
331 : : struct frag_desc descs[MAX_FRAG];
332 : : int idx[MAX_FRAG];
333 : 112 : const enum order orders[] = { IN_ORDER, REVERSE, ODD_EVEN, BLOCK };
334 : 112 : int n = make_datagram(total_plen, frag_size, descs);
335 : : unsigned int o;
336 : :
337 [ + - ]: 112 : if (n < 2) /* skip single-fragment / oversized for sweep */
338 : : return 0;
339 : :
340 [ + + ]: 560 : for (o = 0; o < RTE_DIM(orders); o++) {
341 : 448 : make_order(orders[o], n, idx);
342 [ - + ]: 448 : if (validate(run_ordered(fam, descs, n, idx), fam,
343 : : total_plen) != 0) {
344 : : printf(" sweep fail: fam=%d total=%u fs=%u order=%u n=%d\n",
345 : : fam, total_plen, frag_size, orders[o], n);
346 : 0 : return -1;
347 : : }
348 : : }
349 : : return 0;
350 : : }
351 : :
352 : : static int
353 : 2 : sweep(enum family fam)
354 : : {
355 : 2 : const uint16_t fsizes[] = { 8, 16, 64, 256 };
356 : : unsigned int f;
357 : :
358 [ + + ]: 10 : for (f = 0; f < RTE_DIM(fsizes); f++) {
359 : 8 : uint16_t fs = fsizes[f];
360 : : uint16_t total;
361 : :
362 : : /* cover 2..MAX_FRAG fragments, last fragment partial */
363 [ + + ]: 64 : for (total = fs + 8; total <= fs * MAX_FRAG; total += fs) {
364 [ + - ]: 56 : if (sweep_one(fam, total, fs) != 0)
365 : : return TEST_FAILED;
366 [ + - + - ]: 112 : if (total > fs + 4 &&
367 : 56 : sweep_one(fam, total - 4, fs) != 0)
368 : : return TEST_FAILED;
369 : : }
370 : : }
371 : : return TEST_SUCCESS;
372 : : }
373 : :
374 : 1 : static int test_sweep_v4(void) { return sweep(V4); }
375 : 1 : static int test_sweep_v6(void) { return sweep(V6); }
376 : :
377 : : /* Minimum 8-byte fragments. */
378 : : static int
379 : 1 : test_min_fragment(void)
380 : : {
381 : 1 : struct frag_desc d[3] = {
382 : : { 0, 8, 1 }, { 8, 8, 1 }, { 16, 8, 0 },
383 : : };
384 : : int idx[3];
385 : :
386 : : make_order(REVERSE, 3, idx);
387 [ - + ]: 1 : TEST_ASSERT_SUCCESS(validate(run_ordered(V4, d, 3, idx), V4, 24),
388 : : "min 8-byte fragments not reassembled");
389 : : make_order(ODD_EVEN, 3, idx);
390 [ - + ]: 1 : TEST_ASSERT_SUCCESS(validate(run_ordered(V6, d, 3, idx), V6, 24),
391 : : "min 8-byte fragments not reassembled (v6)");
392 : : return TEST_SUCCESS;
393 : : }
394 : :
395 : : /* Exactly MAX_FRAG fragments reassembles; MAX_FRAG + 1 fails. */
396 : : static int
397 : 1 : test_cap_boundary(void)
398 : : {
399 : : struct frag_desc d[MAX_FRAG + 1];
400 : : int idx[MAX_FRAG + 1];
401 : : uint16_t fs = 8, total = fs * MAX_FRAG;
402 : : int n, i;
403 : :
404 : : n = make_datagram(total, fs, d);
405 [ - + ]: 1 : TEST_ASSERT_EQUAL(n, MAX_FRAG, "expected MAX_FRAG fragments");
406 : : make_order(IN_ORDER, n, idx);
407 [ - + ]: 1 : TEST_ASSERT_SUCCESS(validate(run_ordered(V4, d, n, idx), V4, total),
408 : : "MAX_FRAG fragments should reassemble");
409 : :
410 : : /* one more fragment than the table can hold */
411 [ + + ]: 10 : for (i = 0; i <= MAX_FRAG; i++) {
412 : 9 : d[i].ofs = i * fs;
413 : 9 : d[i].plen = fs;
414 : 9 : d[i].mf = (i < MAX_FRAG);
415 : 9 : idx[i] = i;
416 : : }
417 [ - + ]: 1 : TEST_ASSERT_NULL(run_ordered(V4, d, MAX_FRAG + 1, idx),
418 : : "MAX_FRAG + 1 fragments should not reassemble");
419 [ - + ]: 1 : TEST_ASSERT_EQUAL(rte_mempool_avail_count(pkt_pool), NB_MBUF,
420 : : "overflowing set leaked mbufs");
421 : : return TEST_SUCCESS;
422 : : }
423 : :
424 : : /* Incomplete datagram: no output, reaped on timeout. */
425 : : static int
426 : 1 : test_incomplete_timeout(void)
427 : : {
428 : : struct rte_ip_frag_death_row dr;
429 : : struct rte_ip_frag_tbl *tbl;
430 : 1 : uint64_t mc = rte_get_tsc_hz(), tms = rte_rdtsc();
431 : 1 : struct frag_desc d[2] = { { 0, 64, 1 }, { 128, 64, 0 } }; /* gap */
432 : : struct rte_mbuf *out = NULL;
433 : : int i;
434 : :
435 : : memset(&dr, 0, sizeof(dr));
436 : 1 : tbl = tbl_new(mc);
437 [ - + ]: 1 : TEST_ASSERT_NOT_NULL(tbl, "table create failed");
438 [ + + ]: 3 : for (i = 0; i < 2; i++) {
439 : 2 : struct rte_mbuf *r = feed(V4, tbl, &dr, &d[i], tms);
440 : :
441 [ - + ]: 2 : if (r != NULL)
442 : : out = r;
443 : : }
444 [ - + ]: 1 : TEST_ASSERT_NULL(out, "incomplete datagram reassembled");
445 : 1 : rte_ip_frag_table_del_expired_entries(tbl, &dr, tms + mc + 1);
446 : 1 : rte_ip_frag_free_death_row(&dr, 0);
447 : 1 : rte_ip_frag_table_destroy(tbl);
448 [ - + ]: 1 : TEST_ASSERT_EQUAL(rte_mempool_avail_count(pkt_pool), NB_MBUF,
449 : : "expired fragments not freed");
450 : : return TEST_SUCCESS;
451 : : }
452 : :
453 : : static int
454 : 1 : test_zero_len(void)
455 : : {
456 : 1 : struct frag_desc d = { 0, 0, 1 };
457 : 1 : int idx = 0;
458 : :
459 [ - + ]: 1 : TEST_ASSERT_NULL(run_ordered(V4, &d, 1, &idx),
460 : : "zero-length fragment accepted");
461 [ - + ]: 1 : TEST_ASSERT_EQUAL(rte_mempool_avail_count(pkt_pool), NB_MBUF,
462 : : "zero-length fragment leaked");
463 : : return TEST_SUCCESS;
464 : : }
465 : :
466 : : /* --------------------- duplicate / overlap / reject --------------------- */
467 : :
468 : : /* A duplicate anywhere in a reordered set must not break reassembly. */
469 : : static int
470 : 1 : test_dup_tolerated(void)
471 : : {
472 : : /* offsets 0,64,128,192 with 64B frags; inject a dup of frag 1 */
473 : 1 : struct frag_desc d[5] = {
474 : : { 0, 64, 1 }, { 64, 64, 1 }, { 64, 64, 1 }, /* dup */
475 : : { 128, 64, 1 }, { 192, 64, 0 },
476 : : };
477 : 1 : int idx[5] = { 1, 4, 2, 0, 3 }; /* reordered, dup interleaved */
478 : :
479 [ - + ]: 1 : TEST_ASSERT_SUCCESS(validate(run_ordered(V4, d, 5, idx), V4, 256),
480 : : "duplicate fragment broke reassembly");
481 : : return TEST_SUCCESS;
482 : : }
483 : :
484 : : /* Overlap geometries; the datagram must be discarded and every collected
485 : : * fragment freed. The last fragment is withheld so that on unfixed code the
486 : : * entry is *retained* (total_size stays UINT32_MAX) rather than torn down by
487 : : * the frag_size > total_size path: that retention is what we detect. We
488 : : * capture the mbufs still held in the table after draining the death row,
489 : : * before destroying the table (destroy frees held mbufs, hiding the leak).
490 : : */
491 : : static int
492 : 4 : overlap_case(enum family fam, const struct frag_desc *d, int n, const char *what)
493 : : {
494 : : struct rte_ip_frag_death_row dr;
495 : : struct rte_ip_frag_tbl *tbl;
496 : : struct rte_mbuf *out = NULL;
497 : : uint64_t tms = rte_rdtsc();
498 : : unsigned int held;
499 : : int i;
500 : :
501 : : memset(&dr, 0, sizeof(dr));
502 : 4 : tbl = tbl_new(rte_get_tsc_hz());
503 [ + - ]: 4 : if (tbl == NULL)
504 : : return -1;
505 [ + + ]: 12 : for (i = 0; i < n; i++) {
506 : 8 : struct rte_mbuf *r = feed(fam, tbl, &dr, &d[i], tms);
507 : :
508 [ - + ]: 8 : if (r != NULL)
509 : : out = r;
510 : : }
511 : 4 : rte_ip_frag_free_death_row(&dr, 0);
512 : 4 : held = NB_MBUF - rte_mempool_avail_count(pkt_pool);
513 : 4 : rte_ip_frag_table_destroy(tbl);
514 : :
515 [ - + ]: 4 : if (out != NULL) {
516 : 0 : rte_pktmbuf_free(out);
517 : : printf(" overlap reassembled instead of discarded: %s\n", what);
518 : 0 : return -1;
519 : : }
520 [ - + ]: 4 : if (held != 0) {
521 : : printf(" overlap kept %u fragment(s) instead of discarding: %s\n",
522 : : held, what);
523 : 0 : return -1;
524 : : }
525 : : return 0;
526 : : }
527 : :
528 : : static int
529 : 1 : test_overlap(void)
530 : : {
531 : : /* last fragment withheld in every case (all MF=1) */
532 : :
533 : : /* overlapping fragment arrives second */
534 : 1 : const struct frag_desc tail[2] = { { 0, 600, 1 }, { 300, 600, 1 } };
535 : : /* overlapping fragment arrives first */
536 : 1 : const struct frag_desc head[2] = { { 300, 600, 1 }, { 0, 600, 1 } };
537 : : /* a fragment fully contained in an existing one */
538 : 1 : const struct frag_desc cont[2] = { { 0, 600, 1 }, { 200, 200, 1 } };
539 : :
540 [ - + ]: 1 : TEST_ASSERT_SUCCESS(overlap_case(V6, tail, 2, "v6 overlap second"), "");
541 [ - + ]: 1 : TEST_ASSERT_SUCCESS(overlap_case(V6, head, 2, "v6 overlap first"), "");
542 [ - + ]: 1 : TEST_ASSERT_SUCCESS(overlap_case(V6, cont, 2, "v6 contained"), "");
543 [ - + ]: 1 : TEST_ASSERT_SUCCESS(overlap_case(V4, tail, 2, "v4 overlap second"), "");
544 : : return TEST_SUCCESS;
545 : : }
546 : :
547 : : /*
548 : : * An IPv6 fragment whose fragment header does not directly follow the base
549 : : * header (a per-fragment extension header precedes it) is dropped, not stored.
550 : : * Build base hdr + an 8-byte routing header + fragment header, and pass the
551 : : * fragment header at its real offset (48), so the library sees frag_hdr !=
552 : : * ip_hdr + 1. Captures whether the fragment is still held in the table after
553 : : * the death row is drained but before the table is destroyed.
554 : : */
555 : : static int
556 : 1 : test_v6_ext_header_drop(void)
557 : : {
558 : : struct rte_ip_frag_death_row dr;
559 : : struct rte_ip_frag_tbl *tbl;
560 : : struct rte_mbuf *m, *r;
561 : : struct rte_ipv6_hdr *ip;
562 : : struct rte_ipv6_fragment_ext *fh;
563 : : uint8_t *rthdr;
564 : : unsigned int held;
565 : : const uint16_t plen = 64;
566 : : const uint16_t ext = 8; /* one 8-byte routing header */
567 : :
568 : : memset(&dr, 0, sizeof(dr));
569 : 1 : tbl = tbl_new(rte_get_tsc_hz());
570 [ - + ]: 1 : TEST_ASSERT_NOT_NULL(tbl, "table create failed");
571 : :
572 : 1 : m = rte_pktmbuf_alloc(pkt_pool);
573 [ - + ]: 1 : TEST_ASSERT_NOT_NULL(m, "alloc failed");
574 : 1 : m->data_off = 0;
575 : 1 : ip = rte_pktmbuf_mtod(m, struct rte_ipv6_hdr *);
576 : : memset(ip, 0, sizeof(*ip));
577 : 1 : ip->vtc_flow = rte_cpu_to_be_32(6u << 28);
578 : 1 : ip->payload_len = rte_cpu_to_be_16(ext + RTE_IPV6_FRAG_HDR_SIZE + plen);
579 : 1 : ip->proto = IPPROTO_ROUTING; /* per-fragment header before frag hdr */
580 : 1 : ip->hop_limits = 64;
581 : 1 : ip->src_addr.a[15] = 1;
582 : 1 : ip->dst_addr.a[15] = 2;
583 : :
584 : : /* 8-byte routing header, next = fragment */
585 : 1 : rthdr = rte_pktmbuf_mtod_offset(m, uint8_t *, sizeof(*ip));
586 : : memset(rthdr, 0, ext);
587 : 1 : rthdr[0] = IPPROTO_FRAGMENT; /* next header */
588 : : rthdr[1] = 0; /* hdr ext len: (0 + 1) * 8 = 8 bytes */
589 : :
590 : : /* fragment header at offset 48, not 40 */
591 : 1 : fh = rte_pktmbuf_mtod_offset(m, struct rte_ipv6_fragment_ext *,
592 : : sizeof(*ip) + ext);
593 : 1 : fh->next_header = IPPROTO_UDP;
594 : 1 : fh->reserved = 0;
595 : 1 : fh->frag_data = rte_cpu_to_be_16(RTE_IPV6_SET_FRAG_DATA(0, 1));
596 : 1 : fh->id = rte_cpu_to_be_32(TEST_ID);
597 : :
598 : 1 : m->data_len = m->pkt_len = sizeof(*ip) + ext + RTE_IPV6_FRAG_HDR_SIZE +
599 : : plen;
600 : 1 : m->l2_len = 0;
601 : 1 : m->l3_len = sizeof(*ip) + ext + RTE_IPV6_FRAG_HDR_SIZE;
602 : :
603 : 1 : r = rte_ipv6_frag_reassemble_packet(tbl, &dr, m, rte_rdtsc(), ip, fh);
604 : 1 : rte_ip_frag_free_death_row(&dr, 0);
605 : 1 : held = NB_MBUF - rte_mempool_avail_count(pkt_pool);
606 : 1 : rte_ip_frag_table_destroy(tbl);
607 : :
608 [ - + ]: 1 : TEST_ASSERT_NULL(r, "fragment with per-fragment header accepted");
609 [ - + ]: 1 : TEST_ASSERT_EQUAL(held, 0,
610 : : "per-fragment-header fragment stored instead of dropped");
611 : : return TEST_SUCCESS;
612 : : }
613 : :
614 : : /* A fragment whose end exceeds the max datagram size is dropped, not stored. */
615 : : static int
616 : 2 : oversize_drop_one(enum family fam)
617 : : {
618 : : struct rte_ip_frag_death_row dr;
619 : : struct rte_ip_frag_tbl *tbl;
620 : 2 : struct frag_desc d = { 0xFFF8, 64, 0 }; /* offset 65528 + 64 > 65535 */
621 : : struct rte_mbuf *r;
622 : : unsigned int held;
623 : :
624 : : memset(&dr, 0, sizeof(dr));
625 : 2 : tbl = tbl_new(rte_get_tsc_hz());
626 [ + - ]: 2 : if (tbl == NULL)
627 : : return -1;
628 : 2 : r = feed(fam, tbl, &dr, &d, rte_rdtsc());
629 : 2 : rte_ip_frag_free_death_row(&dr, 0);
630 : 2 : held = NB_MBUF - rte_mempool_avail_count(pkt_pool);
631 : 2 : rte_ip_frag_table_destroy(tbl);
632 : :
633 [ - + ]: 2 : if (r != NULL) {
634 : 0 : rte_pktmbuf_free(r);
635 : 0 : return -1;
636 : : }
637 [ - + ]: 2 : return held == 0 ? 0 : -1;
638 : : }
639 : :
640 : : static int
641 : 1 : test_oversize_drop(void)
642 : : {
643 [ - + ]: 1 : TEST_ASSERT_SUCCESS(oversize_drop_one(V4),
644 : : "oversized v4 fragment stored instead of dropped");
645 [ - + ]: 1 : TEST_ASSERT_SUCCESS(oversize_drop_one(V6),
646 : : "oversized v6 fragment stored instead of dropped");
647 : : return TEST_SUCCESS;
648 : : }
649 : :
650 : : static struct unit_test_suite reassembly_testsuite = {
651 : : .suite_name = "IP Reassembly Unit Test Suite",
652 : : .setup = testsuite_setup,
653 : : .teardown = testsuite_teardown,
654 : : .unit_test_cases = {
655 : : TEST_CASE_ST(ut_setup, NULL, test_sweep_v4),
656 : : TEST_CASE_ST(ut_setup, NULL, test_sweep_v6),
657 : : TEST_CASE_ST(ut_setup, NULL, test_min_fragment),
658 : : TEST_CASE_ST(ut_setup, NULL, test_cap_boundary),
659 : : TEST_CASE_ST(ut_setup, NULL, test_incomplete_timeout),
660 : : TEST_CASE_ST(ut_setup, NULL, test_zero_len),
661 : : TEST_CASE_ST(ut_setup, NULL, test_dup_tolerated),
662 : : TEST_CASE_ST(ut_setup, NULL, test_overlap),
663 : : TEST_CASE_ST(ut_setup, NULL, test_v6_ext_header_drop),
664 : : TEST_CASE_ST(ut_setup, NULL, test_oversize_drop),
665 : : TEST_CASES_END()
666 : : }
667 : : };
668 : :
669 : : static int
670 : 1 : test_reassembly(void)
671 : : {
672 : 1 : return unit_test_suite_runner(&reassembly_testsuite);
673 : : }
674 : :
675 : 303 : REGISTER_FAST_TEST(reassembly_autotest, NOHUGE_OK, ASAN_OK, test_reassembly);
|