LCOV - code coverage report
Current view: top level - app/test - test_reassembly.c (source / functions) Hit Total Coverage
Test: Code coverage Lines: 225 240 93.8 %
Date: 2026-08-01 17:54:00 Functions: 25 25 100.0 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 118 173 68.2 %

           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);

Generated by: LCOV version 1.14