ripngd: allow to enable/disable the ECMP feature

Introduce a new command "[no] allow-ecmp" to enable/disable the
ECMP feature in RIPng. By default, ECMP is not allowed.

Once ECMP is disabled, only one route entry can exist in the list.

* ripng_zebra.c: adjust a debugging information, which shows the number
                 of nexthops according to whether ECMP is enabled.
* ripngd.c: ripng_ecmp_add() will reject the new route if ECMP is not
            allowed and some entry already exists.
            A new configurable command "allow-ecmp" is added to control
            whether ECMP is allowed.
            When ECMP is disabled, ripng_ecmp_disable() is called to
            remove the multiple nexthops.
* ripngd.h: Add a new member "ecmp" to "struct ripng", indicating whether
            ECMP is allowed or not.

Signed-off-by: Feng Lu <lu.feng@6wind.com>
Reviewed-by: Alain Ritoux <alain.ritoux@6wind.com>
Signed-off-by: Nicolas Dichtel <nicolas.dichtel@6wind.com>
Acked-by: Vincent Jardin <vincent.jardin@6wind.com>
Signed-off-by: David Lamparter <equinox@opensourcerouting.org>
diff --git a/ripngd/ripng_zebra.c b/ripngd/ripng_zebra.c
index b5cf445..5d383fa 100644
--- a/ripngd/ripng_zebra.c
+++ b/ripngd/ripng_zebra.c
@@ -102,10 +102,18 @@
                        (struct prefix_ipv6 *)&rp->p, &api);
 
       if (IS_RIPNG_DEBUG_ZEBRA)
-        zlog_debug ("%s: %s/%d nexthops %d",
-                    (cmd == ZEBRA_IPV6_ROUTE_ADD) ? \
-                        "Install into zebra" : "Delete from zebra",
-                    inet6_ntoa (rp->p.u.prefix6), rp->p.prefixlen, count);
+        {
+          if (ripng->ecmp)
+            zlog_debug ("%s: %s/%d nexthops %d",
+                        (cmd == ZEBRA_IPV6_ROUTE_ADD) ? \
+                            "Install into zebra" : "Delete from zebra",
+                        inet6_ntoa (rp->p.u.prefix6), rp->p.prefixlen, count);
+          else
+            zlog_debug ("%s: %s/%d",
+                        (cmd == ZEBRA_IPV6_ROUTE_ADD) ? \
+                            "Install into zebra" : "Delete from zebra",
+                        inet6_ntoa (rp->p.u.prefix6), rp->p.prefixlen);
+        }
     }
 }
 
diff --git a/ripngd/ripngd.c b/ripngd/ripngd.c
index 21e45b6..4a7f52f 100644
--- a/ripngd/ripngd.c
+++ b/ripngd/ripngd.c
@@ -438,7 +438,8 @@
 static void ripng_timeout_update (struct ripng_info *rinfo);
 
 /* Add new route to the ECMP list.
- * RETURN: the new entry added in the list
+ * RETURN: the new entry added in the list, or NULL if it is not the first
+ *         entry and ECMP is not allowed.
  */
 struct ripng_info *
 ripng_ecmp_add (struct ripng_info *rinfo_new)
@@ -451,6 +452,11 @@
     rp->info = list_new ();
   list = (struct list *)rp->info;
 
+  /* If ECMP is not allowed and some entry already exists in the list,
+   * do nothing. */
+  if (listcount (list) && !ripng->ecmp)
+    return NULL;
+
   rinfo = ripng_info_new ();
   memcpy (rinfo, rinfo_new, sizeof (struct ripng_info));
   listnode_add (list, rinfo);
@@ -2636,6 +2642,80 @@
   return CMD_SUCCESS;
 }
 
+/* Update ECMP routes to zebra when ECMP is disabled. */
+static void
+ripng_ecmp_disable (void)
+{
+  struct route_node *rp;
+  struct ripng_info *rinfo, *tmp_rinfo;
+  struct list *list;
+  struct listnode *node, *nextnode;
+
+  if (!ripng)
+    return;
+
+  for (rp = route_top (ripng->table); rp; rp = route_next (rp))
+    if ((list = rp->info) != NULL && listcount (list) > 1)
+      {
+        rinfo = listgetdata (listhead (list));
+        if (!ripng_route_rte (rinfo))
+          continue;
+
+        /* Drop all other entries, except the first one. */
+        for (ALL_LIST_ELEMENTS (list, node, nextnode, tmp_rinfo))
+          if (tmp_rinfo != rinfo)
+            {
+              RIPNG_TIMER_OFF (tmp_rinfo->t_timeout);
+              RIPNG_TIMER_OFF (tmp_rinfo->t_garbage_collect);
+              list_delete_node (list, node);
+              ripng_info_free (tmp_rinfo);
+            }
+
+        /* Update zebra. */
+        ripng_zebra_ipv6_add (rp);
+
+        /* Set the route change flag. */
+        SET_FLAG (rinfo->flags, RIPNG_RTF_CHANGED);
+
+        /* Signal the output process to trigger an update. */
+        ripng_event (RIPNG_TRIGGERED_UPDATE, 0);
+      }
+}
+
+DEFUN (ripng_allow_ecmp,
+       ripng_allow_ecmp_cmd,
+       "allow-ecmp",
+       "Allow Equal Cost MultiPath\n")
+{
+  if (ripng->ecmp)
+    {
+      vty_out (vty, "ECMP is already enabled.%s", VTY_NEWLINE);
+      return CMD_WARNING;
+    }
+
+  ripng->ecmp = 1;
+  zlog_info ("ECMP is enabled.");
+  return CMD_SUCCESS;
+}
+
+DEFUN (no_ripng_allow_ecmp,
+       no_ripng_allow_ecmp_cmd,
+       "no allow-ecmp",
+       NO_STR
+       "Allow Equal Cost MultiPath\n")
+{
+  if (!ripng->ecmp)
+    {
+      vty_out (vty, "ECMP is already disabled.%s", VTY_NEWLINE);
+      return CMD_WARNING;
+    }
+
+  ripng->ecmp = 0;
+  zlog_info ("ECMP is disabled.");
+  ripng_ecmp_disable ();
+  return CMD_SUCCESS;
+}
+
 /* RIPng configuration write function. */
 static int
 ripng_config_write (struct vty *vty)
@@ -2675,6 +2755,10 @@
 
 		   VTY_NEWLINE);
 
+      /* ECMP configuration. */
+      if (ripng->ecmp)
+        vty_out (vty, " allow-ecmp%s", VTY_NEWLINE);
+
       /* RIPng static routes. */
       for (rp = route_top (ripng->route); rp; rp = route_next (rp))
 	if (rp->info != NULL)
@@ -3040,6 +3124,9 @@
   install_element (RIPNG_NODE, &ripng_default_information_originate_cmd);
   install_element (RIPNG_NODE, &no_ripng_default_information_originate_cmd);
 
+  install_element (RIPNG_NODE, &ripng_allow_ecmp_cmd);
+  install_element (RIPNG_NODE, &no_ripng_allow_ecmp_cmd);
+
   ripng_if_init ();
   ripng_debug_init ();
 
diff --git a/ripngd/ripngd.h b/ripngd/ripngd.h
index 75c5dfa..706ff54 100644
--- a/ripngd/ripngd.h
+++ b/ripngd/ripngd.h
@@ -129,6 +129,9 @@
   struct thread *t_triggered_update;
   struct thread *t_triggered_interval;
 
+  /* RIPng ECMP flag */
+  unsigned int ecmp;
+
   /* For redistribute route map. */
   struct
   {