fib: fix load-balance and replicate dpos buckets overflow

load-balance and replicate dpos both store their number of buckets as
u16, which can overflow if too many paths are configured. For
load-balance it can happens quite quickly because of weights
normalization.

Type: fix

Change-Id: I0c78c39fc3d40626dfc58b49e7d99d71f9852b50
Signed-off-by: Benoît Ganne <bganne@cisco.com>
diff --git a/src/vnet/dpo/load_balance.c b/src/vnet/dpo/load_balance.c
index ff46d56..fae7c1d 100644
--- a/src/vnet/dpo/load_balance.c
+++ b/src/vnet/dpo/load_balance.c
@@ -244,6 +244,8 @@
 {
     load_balance_t *lb;
 
+    ASSERT (num_buckets <= LB_MAX_BUCKETS);
+
     lb = load_balance_alloc_i();
     lb->lb_hash_config = fhc;
     lb->lb_n_buckets = num_buckets;
@@ -455,8 +457,9 @@
 
     /* Try larger and larger power of 2 sized adjacency blocks until we
        find one where traffic flows to within 1% of specified weights. */
-    for (n_adj = max_pow2 (n_nhs); ; n_adj *= 2)
+    for (n_adj = clib_min(max_pow2 (n_nhs), LB_MAX_BUCKETS); ; n_adj *= 2)
     {
+        ASSERT (n_adj <= LB_MAX_BUCKETS);
         error = 0;
 
         norm = n_adj / ((f64) sum_weight);
@@ -487,12 +490,22 @@
 
         nhs[0].path_weight += n_adj_left;
 
-        /* Less than 5% average error per adjacency with this size adjacency block? */
-        if (error <= multipath_next_hop_error_tolerance*n_adj)
+        /* Less than 1% average error per adjacency with this size adjacency block,
+         * or did we reached the maximum number of buckets we support? */
+        if (error <= multipath_next_hop_error_tolerance*n_adj ||
+            n_adj >= LB_MAX_BUCKETS)
         {
-            /* Truncate any next hops with zero weight. */
-            vec_set_len (nhs, i);
-            break;
+          if (i < n_nhs)
+          {
+            /* Truncate any next hops in excess */
+            vlib_log_err(load_balance_logger,
+                         "Too many paths for load-balance, truncating %d -> %d",
+                         n_nhs, i);
+            for (int j = i; j < n_nhs; j++)
+              dpo_reset (&vec_elt(nhs, j).path_dpo);
+          }
+          vec_set_len (nhs, i);
+          break;
         }
     }
 
@@ -622,6 +635,7 @@
 load_balance_set_n_buckets (load_balance_t *lb,
                             u32 n_buckets)
 {
+    ASSERT (n_buckets <= LB_MAX_BUCKETS);
     lb->lb_n_buckets = n_buckets;
     lb->lb_n_buckets_minus_1 = n_buckets-1;
 }
@@ -651,8 +665,6 @@
                                          &sum_of_weights,
                                          multipath_next_hop_error_tolerance);
 
-    ASSERT (n_buckets >= vec_len (raw_nhs));
-
     /*
      * Save the old load-balance map used, and get a new one if required.
      */