Regression test for the rsvd_tbl8s drift bug fixed in
"fib6: fix tbl8 reservation drift in trie".

The test installs a /28 parent and three /48 children, deletes the
/28 parent while children are alive, then deletes each /48 child.
One asymmetric cycle wraps rsvd_tbl8s past zero (net -2 per cycle).

The probe is a /28 ADD (depth_diff=1) so UINT32_MAX-1 + 1 exceeds
the pool size without further uint32_t overflow.

Without the fix, the final ADD /28 fails with -ENOSPC.
With the fix, it succeeds.

Signed-off-by: Maxime Leroy <[email protected]>
---
 app/test/test_fib6.c | 83 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 83 insertions(+)

diff --git a/app/test/test_fib6.c b/app/test/test_fib6.c
index fffb590dbf..85a6c0abc3 100644
--- a/app/test/test_fib6.c
+++ b/app/test/test_fib6.c
@@ -25,6 +25,7 @@ static int32_t test_get_invalid(void);
 static int32_t test_lookup(void);
 static int32_t test_invalid_rcu(void);
 static int32_t test_fib_rcu_sync_rw(void);
+static int32_t test_drift(void);
 
 #define MAX_ROUTES     (1 << 16)
 /** Maximum number of tbl8 for 2-byte entries */
@@ -599,6 +600,87 @@ test_fib_rcu_sync_rw(void)
        return status == 0 ? TEST_SUCCESS : TEST_FAILED;
 }
 
+/*
+ * Reproducer for the rsvd_tbl8s drift bug. depth_diff used to maintain
+ * rsvd_tbl8s is computed from the current RIB state, so it is not
+ * invariant between the ADD of a prefix and its later DEL when a
+ * covering parent prefix is removed in between.
+ *
+ * Layout: one /28 parent (fcde::/28) and three /48 siblings under it
+ * (fcde:0:6000::/48, fcde:1:6000::/48, fcde:2:6000::/48). The second
+ * hextet's high 12 bits are zero, so the three /48 IPs all fall inside
+ * the /28.
+ *
+ * One asymmetric sequence is enough to wrap the counter:
+ *   ADD /28                                  rsvd_tbl8s += 1
+ *   ADD /48 child_0,1,2 (with /28 parent)    rsvd_tbl8s += 2 each (+6)
+ *   DEL /28 (sibling /48 found)              rsvd_tbl8s -= 0
+ *   DEL /48 child_0,1,2 (no parent left)     rsvd_tbl8s -= 3 each (-9)
+ *
+ * Net: -2. Starting from 0, rsvd_tbl8s wraps to UINT32_MAX-1. The
+ * next ADD of a prefix longer than /24 then unconditionally fails the
+ * pre-check (rsvd_tbl8s + depth_diff > number_tbl8s), even though the
+ * pool is empty.
+ */
+static int32_t
+test_drift(void)
+{
+       struct rte_fib6_conf config = { 0 };
+       struct rte_fib6 *fib;
+       struct rte_ipv6_addr parent =
+               RTE_IPV6(0xfcde, 0, 0, 0, 0, 0, 0, 0);
+       struct rte_ipv6_addr child[3] = {
+               RTE_IPV6(0xfcde, 0, 0x6000, 0, 0, 0, 0, 0),
+               RTE_IPV6(0xfcde, 1, 0x6000, 0, 0, 0, 0, 0),
+               RTE_IPV6(0xfcde, 2, 0x6000, 0, 0, 0, 0, 0),
+       };
+       unsigned int c;
+       int ret;
+
+       config.max_routes = 1024;
+       config.rib_ext_sz = 0;
+       config.default_nh = 0;
+       config.type = RTE_FIB6_TRIE;
+       config.trie.nh_sz = RTE_FIB6_TRIE_2B;
+       config.trie.num_tbl8 = 256;
+
+       fib = rte_fib6_create(__func__, SOCKET_ID_ANY, &config);
+       RTE_TEST_ASSERT(fib != NULL, "Failed to create FIB\n");
+
+       ret = rte_fib6_add(fib, &parent, 28, 0xa);
+       RTE_TEST_ASSERT(ret == 0, "ADD /28 failed (ret=%d)\n", ret);
+
+       for (c = 0; c < 3; c++) {
+               ret = rte_fib6_add(fib, &child[c], 48, 0xb + c);
+               RTE_TEST_ASSERT(ret == 0,
+                       "ADD /48 child %u failed (ret=%d)\n", c, ret);
+       }
+
+       ret = rte_fib6_delete(fib, &parent, 28);
+       RTE_TEST_ASSERT(ret == 0, "DEL /28 failed (ret=%d)\n", ret);
+
+       for (c = 0; c < 3; c++) {
+               ret = rte_fib6_delete(fib, &child[c], 48);
+               RTE_TEST_ASSERT(ret == 0,
+                       "DEL /48 child %u failed (ret=%d)\n", c, ret);
+       }
+
+       /*
+        * Pre-fix: -ENOSPC because rsvd_tbl8s wrapped to UINT32_MAX-1.
+        * Post-fix: succeeds; the pre-check uses tbl8_pool_pos which
+        * accurately reflects the (empty) pool.
+        */
+       ret = rte_fib6_add(fib, &parent, 28, 0xe);
+       RTE_TEST_ASSERT(ret == 0,
+               "Fresh ADD /28 spuriously failed (ret=%d)\n", ret);
+
+       ret = rte_fib6_delete(fib, &parent, 28);
+       RTE_TEST_ASSERT(ret == 0, "Final DEL /28 failed (ret=%d)\n", ret);
+
+       rte_fib6_free(fib);
+       return TEST_SUCCESS;
+}
+
 static struct unit_test_suite fib6_fast_tests = {
        .suite_name = "fib6 autotest",
        .setup = NULL,
@@ -611,6 +693,7 @@ static struct unit_test_suite fib6_fast_tests = {
        TEST_CASE(test_lookup),
        TEST_CASE(test_invalid_rcu),
        TEST_CASE(test_fib_rcu_sync_rw),
+       TEST_CASE(test_drift),
        TEST_CASES_END()
        }
 };
-- 
2.43.0

Reply via email to