Currently, `install_to_dp()` builds one tbl8 level more than a route
range needs when the range ends on a byte boundary. If the range
spans a whole table at its first uncommon byte, e.g. a /32 or a /40,
`build_common_root()` allocates a tbl8 one level too deep, all of
its entries get the same next hop and `recycle_root_path()` frees it
again. Below /24, an edge aligned on /24, e.g. of a /16, gets a tbl8 that
`write_edge()` fills and recycles at once.

These temporary tbl8s are not covered by `rsvd_tbl8s`, which counts
only the levels the route set keeps. With a nearly full pool, an add
that passed the reservation check runs out of tbl8s in the middle of the
install, the tbl8 already linked into the dataplane is leaked and a later
add that fits is refused. A /32 can not be added to a pool of one tbl8,
nor deleted from a full pool.

Fix it by not building these levels.

Bugzilla ID: 1970
Fixes: c3e12e0f0354 ("fib: add dataplane algorithm for IPv6")
Cc: [email protected]

Reported-by: Maxime Leroy <[email protected]>
Signed-off-by: Vladimir Medvedkin <[email protected]>
---
 lib/fib/trie.c | 17 +++++++++++++++--
 1 file changed, 15 insertions(+), 2 deletions(-)

diff --git a/lib/fib/trie.c b/lib/fib/trie.c
index 187360d1c8..0ae669c689 100644
--- a/lib/fib/trie.c
+++ b/lib/fib/trie.c
@@ -410,20 +410,33 @@ install_to_dp(struct rte_trie_tbl *dp, const struct 
rte_ipv6_addr *ledge,
                        break;
        }
 
+       /*
+        * A range covering a whole tbl8 is a single entry one level up.
+        * Write that entry rather than build a tbl8 only to recycle it.
+        */
+       if (common_bytes >= TBL24_BYTES) {
+               for (i = common_bytes; i <= IPV6_MAX_IDX; i++) {
+                       if (ledge->a[i] != 0 || redge.a[i] != UINT8_MAX)
+                               break;
+               }
+               if (i > IPV6_MAX_IDX)
+                       common_bytes--;
+       }
+
        ret = build_common_root(dp, ledge, common_bytes, &common_root_tbl);
        if (unlikely(ret != 0))
                return ret;
        /*first uncommon tbl8 byte idx*/
        uint8_t first_tbl8_byte = RTE_MAX(common_bytes, TBL24_BYTES);
 
-       for (i = IPV6_MAX_IDX; i > first_tbl8_byte; i--) {
+       for (i = IPV6_MAX_IDX; i >= first_tbl8_byte + !(common_bytes < 3); i--) 
{
                if (ledge->a[i] != 0)
                        break;
        }
 
        llen = i - first_tbl8_byte + (common_bytes < 3);
 
-       for (i = IPV6_MAX_IDX; i > first_tbl8_byte; i--) {
+       for (i = IPV6_MAX_IDX; i >= first_tbl8_byte + !(common_bytes < 3); i--) 
{
                if (redge.a[i] != UINT8_MAX)
                        break;
        }
-- 
2.43.0

Reply via email to