rte_lpm6_delete_bulk_func() clears tbl24 and tbl8 and rebuilds them
by calling rte_lpm6_add() for each rule left in the rules table.
As these rules are still in the rules table, rule_add() reports them
as existing ones, and add_step() does not increment the reference
counter of the tbl8 group the rule ends in. After the rebuild, every
tbl8 group is under-counted by the number of rules ending in it.
A following rte_lpm6_delete() then drops the counter to zero too early,
and remove_tbl() frees a tbl8 group that still holds more specific
rules. Lookups for these rules return the next hop of a less specific
rule. Deleting them walks an entry that is no longer extended and uses
its next hop as a tbl8 group index: this triggers an assert in
rule_find_range(), or an out of bounds access with NDEBUG.
Add the rules to tbl24/tbl8 as new ones during the rebuild,
and add a unit test for this case.
Fixes: e480688dce6d ("lpm6: add incremental update on delete")
Cc: [email protected]
Signed-off-by: Andrey Mokrushin <[email protected]>
---
app/test/test_lpm6.c | 67 ++++++++++++++++++++++++++++++++++++++++++++
lib/lpm/rte_lpm6.c | 47 +++++++++++++++++++++++--------
2 files changed, 103 insertions(+), 11 deletions(-)
diff --git a/app/test/test_lpm6.c b/app/test/test_lpm6.c
index 7340331..7d9d87b 100644
--- a/app/test/test_lpm6.c
+++ b/app/test/test_lpm6.c
@@ -52,6 +52,7 @@ static int32_t test25(void);
static int32_t test26(void);
static int32_t test27(void);
static int32_t test28(void);
+static int32_t test29(void);
rte_lpm6_test tests6[] = {
/* Test Cases */
@@ -84,6 +85,7 @@ rte_lpm6_test tests6[] = {
test26,
test27,
test28,
+ test29,
};
#define MAX_DEPTH 128
@@ -1745,6 +1747,71 @@ test28(void)
return PASS;
}
+/*
+ * Add three nested rules deeper than 24 bits and one more rule.
+ * Delete the last rule with the delete_bulk function, which rebuilds
+ * the tables. Then delete the middle rule with the incremental delete
+ * function and check that the most specific rule is still found,
+ * i.e. the tbl8 groups were not freed while still in use.
+ */
+int32_t
+test29(void)
+{
+ struct rte_lpm6 *lpm = NULL;
+ struct rte_lpm6_config config;
+ struct rte_ipv6_addr ip_16 = RTE_IPV6(0x2001, 0, 0, 0, 0, 0, 0, 0);
+ struct rte_ipv6_addr ip_32 = RTE_IPV6(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0);
+ struct rte_ipv6_addr ip_48 = RTE_IPV6(0x2001, 0xdb8, 1, 0, 0, 0, 0, 0);
+ struct rte_ipv6_addr ip_del = RTE_IPV6(0x2001, 0xdb8, 2, 0, 0, 0, 0, 0);
+ uint8_t depth_del = 48;
+ uint32_t next_hop_return = 0;
+ int32_t status = 0;
+
+ config.max_rules = MAX_RULES;
+ config.number_tbl8s = NUMBER_TBL8S;
+ config.flags = 0;
+
+ lpm = rte_lpm6_create(__func__, SOCKET_ID_ANY, &config);
+ TEST_LPM_ASSERT(lpm != NULL);
+
+ status = rte_lpm6_add(lpm, &ip_16, 16, 16);
+ TEST_LPM_ASSERT(status == 0);
+ status = rte_lpm6_add(lpm, &ip_32, 32, 32);
+ TEST_LPM_ASSERT(status == 0);
+ status = rte_lpm6_add(lpm, &ip_48, 48, 48);
+ TEST_LPM_ASSERT(status == 0);
+ status = rte_lpm6_add(lpm, &ip_del, 48, 100);
+ TEST_LPM_ASSERT(status == 0);
+
+ status = rte_lpm6_delete_bulk_func(lpm, &ip_del, &depth_del, 1);
+ TEST_LPM_ASSERT(status == 0);
+
+ status = rte_lpm6_lookup(lpm, &ip_48, &next_hop_return);
+ TEST_LPM_ASSERT((status == 0) && (next_hop_return == 48));
+
+ status = rte_lpm6_delete(lpm, &ip_32, 32);
+ TEST_LPM_ASSERT(status == 0);
+
+ status = rte_lpm6_lookup(lpm, &ip_48, &next_hop_return);
+ TEST_LPM_ASSERT((status == 0) && (next_hop_return == 48));
+
+ status = rte_lpm6_delete(lpm, &ip_48, 48);
+ TEST_LPM_ASSERT(status == 0);
+
+ status = rte_lpm6_lookup(lpm, &ip_48, &next_hop_return);
+ TEST_LPM_ASSERT((status == 0) && (next_hop_return == 16));
+
+ status = rte_lpm6_delete(lpm, &ip_16, 16);
+ TEST_LPM_ASSERT(status == 0);
+
+ status = rte_lpm6_lookup(lpm, &ip_48, &next_hop_return);
+ TEST_LPM_ASSERT(status == -ENOENT);
+
+ rte_lpm6_free(lpm);
+
+ return PASS;
+}
+
/*
* Do all unit tests.
*/
diff --git a/lib/lpm/rte_lpm6.c b/lib/lpm/rte_lpm6.c
index e23c886..2c2c17e 100644
--- a/lib/lpm/rte_lpm6.c
+++ b/lib/lpm/rte_lpm6.c
@@ -189,6 +189,10 @@ rule_key_init(struct rte_lpm6_rule_key *key, const struct
rte_ipv6_addr *ip, uin
key->depth = depth;
}
+static int
+add_rule_to_tbl(struct rte_lpm6 *lpm, struct rte_ipv6_addr *masked_ip,
+ uint8_t depth, uint32_t next_hop, uint8_t is_new_rule);
+
/*
* Rebuild the entire LPM tree by reinserting all rules
*/
@@ -197,12 +201,21 @@ rebuild_lpm(struct rte_lpm6 *lpm)
{
uint64_t next_hop;
struct rte_lpm6_rule_key *rule_key;
+ struct rte_ipv6_addr ip;
uint32_t iter = 0;
+ /*
+ * The rules are already in the rules table, so rte_lpm6_add() would
+ * treat them as existing ones and would not account them in the
+ * reference counters of the freshly allocated tbl8 groups.
+ * Insert them as new rules instead.
+ */
while (rte_hash_iterate(lpm->rules_tbl, (void *) &rule_key,
- (void **) &next_hop, &iter) >= 0)
- rte_lpm6_add(lpm, &rule_key->ip, rule_key->depth,
- (uint32_t) next_hop);
+ (void **) &next_hop, &iter) >= 0) {
+ ip = rule_key->ip;
+ add_rule_to_tbl(lpm, &ip, rule_key->depth,
+ (uint32_t) next_hop, 1);
+ }
}
/*
@@ -828,13 +841,7 @@ int
rte_lpm6_add(struct rte_lpm6 *lpm, const struct rte_ipv6_addr *ip, uint8_t
depth,
uint32_t next_hop)
{
- struct rte_lpm6_tbl_entry *tbl;
- struct rte_lpm6_tbl_entry *tbl_next = NULL;
- /* init to avoid compiler warning */
- uint32_t tbl_next_num = 123456;
- int status;
struct rte_ipv6_addr masked_ip;
- int i;
/* Check user arguments. */
if ((lpm == NULL) || (depth < 1) || (depth > RTE_IPV6_MAX_DEPTH))
@@ -855,10 +862,28 @@ rte_lpm6_add(struct rte_lpm6 *lpm, const struct
rte_ipv6_addr *ip, uint8_t depth
if (is_new_rule < 0)
return is_new_rule;
+ return add_rule_to_tbl(lpm, &masked_ip, depth, next_hop, is_new_rule);
+}
+
+/*
+ * Add a rule to the data structure (tbl24+tbl8s).
+ * The rule must be already present in the rules table.
+ */
+static int
+add_rule_to_tbl(struct rte_lpm6 *lpm, struct rte_ipv6_addr *masked_ip,
+ uint8_t depth, uint32_t next_hop, uint8_t is_new_rule)
+{
+ struct rte_lpm6_tbl_entry *tbl;
+ struct rte_lpm6_tbl_entry *tbl_next = NULL;
+ /* init to avoid compiler warning */
+ uint32_t tbl_next_num = 123456;
+ int status;
+ int i;
+
/* Inspect the first three bytes through tbl24 on the first step. */
tbl = lpm->tbl24;
status = add_step(lpm, tbl, TBL24_IND, &tbl_next, &tbl_next_num,
- &masked_ip, ADD_FIRST_BYTE, 1, depth, next_hop,
+ masked_ip, ADD_FIRST_BYTE, 1, depth, next_hop,
is_new_rule);
assert(status >= 0);
@@ -869,7 +894,7 @@ rte_lpm6_add(struct rte_lpm6 *lpm, const struct
rte_ipv6_addr *ip, uint8_t depth
for (i = ADD_FIRST_BYTE; i < RTE_IPV6_ADDR_SIZE && status == 1; i++) {
tbl = tbl_next;
status = add_step(lpm, tbl, tbl_next_num, &tbl_next,
- &tbl_next_num, &masked_ip, 1, (uint8_t)(i + 1),
+ &tbl_next_num, masked_ip, 1, (uint8_t)(i + 1),
depth, next_hop, is_new_rule);
assert(status >= 0);
}
--
2.56.0