]> git.proxmox.com Git - mirror_frr.git/commitdiff
Merge pull request #13535 from opensourcerouting/feature/ripng_allow_ecmp
authorRuss White <russ@riw.us>
Tue, 23 May 2023 11:35:32 +0000 (07:35 -0400)
committerGitHub <noreply@github.com>
Tue, 23 May 2023 11:35:32 +0000 (07:35 -0400)
ripng: Implement allow-ecmp X command

15 files changed:
doc/user/ripngd.rst
ripngd/ripng_cli.c
ripngd/ripng_main.c
ripngd/ripng_nb_config.c
ripngd/ripng_zebra.c
ripngd/ripngd.c
ripngd/ripngd.h
tests/topotests/ripng_allow_ecmp/__init__.py [new file with mode: 0644]
tests/topotests/ripng_allow_ecmp/r1/frr.conf [new file with mode: 0644]
tests/topotests/ripng_allow_ecmp/r2/frr.conf [new file with mode: 0644]
tests/topotests/ripng_allow_ecmp/r3/frr.conf [new file with mode: 0644]
tests/topotests/ripng_allow_ecmp/r4/frr.conf [new file with mode: 0644]
tests/topotests/ripng_allow_ecmp/r5/frr.conf [new file with mode: 0644]
tests/topotests/ripng_allow_ecmp/test_ripng_allow_ecmp.py [new file with mode: 0644]
yang/frr-ripngd.yang

index df7a0e249ec7a2e9219179ede992d86eb8cad061..4c9b734d8839e19c8644fdf981798e5f1be72d4b 100644 (file)
@@ -38,6 +38,10 @@ Currently ripngd supports the following commands:
 
    Set RIPng static routing announcement of NETWORK.
 
+.. clicmd:: allow-ecmp [1-MULTIPATH_NUM]
+
+   Control how many ECMP paths RIPng can inject for the same prefix. If specified
+   without a number, a maximum is taken (compiled with ``--enable-multipath``).
 
 .. _ripngd-terminal-mode-commands:
 
index 5e59dfd2c409eba23c8b3ac0afbe4de4282e18cd..9a96e29313d6858c742be44d95275a9e3b8c88bb 100644 (file)
@@ -85,14 +85,32 @@ void cli_show_router_ripng(struct vty *vty, const struct lyd_node *dnode,
 /*
  * XPath: /frr-ripngd:ripngd/instance/allow-ecmp
  */
-DEFPY_YANG (ripng_allow_ecmp,
-       ripng_allow_ecmp_cmd,
-       "[no] allow-ecmp",
-       NO_STR
-       "Allow Equal Cost MultiPath\n")
+DEFUN_YANG (ripng_allow_ecmp,
+            ripng_allow_ecmp_cmd,
+            "allow-ecmp [" CMD_RANGE_STR(1, MULTIPATH_NUM) "]",
+            "Allow Equal Cost MultiPath\n"
+            "Number of paths\n")
+{
+       int idx_number = 0;
+       char mpaths[3] = {};
+       uint32_t paths = MULTIPATH_NUM;
+
+    if (argv_find(argv, argc, CMD_RANGE_STR(1, MULTIPATH_NUM), &idx_number))
+               paths = strtol(argv[idx_number]->arg, NULL, 10);
+       snprintf(mpaths, sizeof(mpaths), "%u", paths);
+
+       nb_cli_enqueue_change(vty, "./allow-ecmp", NB_OP_MODIFY, mpaths);
+
+       return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFUN_YANG (no_ripng_allow_ecmp,
+            no_ripng_allow_ecmp_cmd,
+            "no allow-ecmp [" CMD_RANGE_STR(1, MULTIPATH_NUM) "]", NO_STR
+            "Allow Equal Cost MultiPath\n"
+            "Number of paths\n")
 {
-       nb_cli_enqueue_change(vty, "./allow-ecmp", NB_OP_MODIFY,
-                             no ? "false" : "true");
+       nb_cli_enqueue_change(vty, "./allow-ecmp", NB_OP_MODIFY, 0);
 
        return nb_cli_apply_changes(vty, NULL);
 }
@@ -100,10 +118,14 @@ DEFPY_YANG (ripng_allow_ecmp,
 void cli_show_ripng_allow_ecmp(struct vty *vty, const struct lyd_node *dnode,
                               bool show_defaults)
 {
-       if (!yang_dnode_get_bool(dnode, NULL))
-               vty_out(vty, " no");
+       uint8_t paths;
+
+       paths = yang_dnode_get_uint8(dnode, NULL);
 
-       vty_out(vty, " allow-ecmp\n");
+       if (!paths)
+               vty_out(vty, " no allow-ecmp\n");
+       else
+               vty_out(vty, " allow-ecmp %d\n", paths);
 }
 
 /*
@@ -547,6 +569,7 @@ void ripng_cli_init(void)
        install_element(RIPNG_NODE, &ripng_no_ipv6_distribute_list_cmd);
 
        install_element(RIPNG_NODE, &ripng_allow_ecmp_cmd);
+       install_element(RIPNG_NODE, &no_ripng_allow_ecmp_cmd);
        install_element(RIPNG_NODE, &ripng_default_information_originate_cmd);
        install_element(RIPNG_NODE, &ripng_default_metric_cmd);
        install_element(RIPNG_NODE, &no_ripng_default_metric_cmd);
index 1d392efdde9bfe3079091551cd767f7ea08cdffa..9933dae5cd762c24d305eb9599a73dfc2c9beb37 100644 (file)
@@ -32,6 +32,8 @@ struct option longopts[] = {{0}};
 /* ripngd privileges */
 zebra_capabilities_t _caps_p[] = {ZCAP_NET_RAW, ZCAP_BIND, ZCAP_SYS_ADMIN};
 
+uint32_t zebra_ecmp_count = MULTIPATH_NUM;
+
 struct zebra_privs_t ripngd_privs = {
 #if defined(FRR_USER)
        .user = FRR_USER,
index 30f707e061e989266a36e0a2c030a5a8ef2e30ac..0b1bd68eca20ab7db4b4cdc7ab54b368b3ec3e43 100644 (file)
@@ -129,9 +129,14 @@ int ripngd_instance_allow_ecmp_modify(struct nb_cb_modify_args *args)
                return NB_OK;
 
        ripng = nb_running_get_entry(args->dnode, NULL, true);
-       ripng->ecmp = yang_dnode_get_bool(args->dnode, NULL);
-       if (!ripng->ecmp)
+       ripng->ecmp =
+               MIN(yang_dnode_get_uint8(args->dnode, NULL), zebra_ecmp_count);
+       if (!ripng->ecmp) {
                ripng_ecmp_disable(ripng);
+               return NB_OK;
+       }
+
+       ripng_ecmp_change(ripng);
 
        return NB_OK;
 }
index d974d65df5e4fb27be09cac9bea698d7ed266e29..49b8a197add15d457251c4fad629463472aedd95 100644 (file)
@@ -30,7 +30,7 @@ static void ripng_zebra_ipv6_send(struct ripng *ripng, struct agg_node *rp,
        struct zapi_nexthop *api_nh;
        struct listnode *listnode = NULL;
        struct ripng_info *rinfo = NULL;
-       int count = 0;
+       uint32_t count = 0;
        const struct prefix *p = agg_node_get_prefix(rp);
 
        memset(&api, 0, sizeof(api));
@@ -41,7 +41,7 @@ static void ripng_zebra_ipv6_send(struct ripng *ripng, struct agg_node *rp,
 
        SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP);
        for (ALL_LIST_ELEMENTS_RO(list, listnode, rinfo)) {
-               if (count >= MULTIPATH_NUM)
+               if (count >= zebra_ecmp_count)
                        break;
                api_nh = &api.nexthops[count];
                api_nh->vrf_id = ripng->vrf->vrf_id;
@@ -227,6 +227,11 @@ static zclient_handler *const ripng_handlers[] = {
        [ZEBRA_REDISTRIBUTE_ROUTE_DEL] = ripng_zebra_read_route,
 };
 
+static void ripng_zebra_capabilities(struct zclient_capabilities *cap)
+{
+       zebra_ecmp_count = MIN(cap->ecmp, zebra_ecmp_count);
+}
+
 /* Initialize zebra structure and it's commands. */
 void zebra_init(struct event_loop *master)
 {
@@ -236,6 +241,7 @@ void zebra_init(struct event_loop *master)
        zclient_init(zclient, ZEBRA_ROUTE_RIPNG, 0, &ripngd_privs);
 
        zclient->zebra_connected = ripng_zebra_connected;
+       zclient->zebra_capabilities = ripng_zebra_capabilities;
 }
 
 void ripng_zebra_stop(void)
index 2f6409a70d31d6ddf5cb55dadc91161bafe46a5b..7269e76656b8f487f46ff77de8f8a6f7cc2aecd4 100644 (file)
@@ -443,7 +443,10 @@ struct ripng_info *ripng_ecmp_add(struct ripng *ripng,
 {
        struct agg_node *rp = rinfo_new->rp;
        struct ripng_info *rinfo = NULL;
+       struct ripng_info *rinfo_exist = NULL;
        struct list *list = NULL;
+       struct listnode *node = NULL;
+       struct listnode *nnode = NULL;
 
        if (rp->info == NULL)
                rp->info = list_new();
@@ -454,6 +457,33 @@ struct ripng_info *ripng_ecmp_add(struct ripng *ripng,
        if (listcount(list) && !ripng->ecmp)
                return NULL;
 
+       /* Add or replace an existing ECMP path with lower neighbor IP */
+       if (listcount(list) && listcount(list) >= ripng->ecmp) {
+               struct ripng_info *from_highest = NULL;
+
+               /* Find the rip_info struct that has the highest nexthop IP */
+               for (ALL_LIST_ELEMENTS(list, node, nnode, rinfo_exist))
+                       if (!from_highest ||
+                           (from_highest &&
+                            IPV6_ADDR_CMP(&rinfo_exist->from,
+                                          &from_highest->from) > 0)) {
+                               from_highest = rinfo_exist;
+                       }
+
+               /* If we have a route in ECMP group, delete the old
+                * one that has a higher next-hop address. Lower IP is
+                * preferred.
+                */
+               if (ripng->ecmp > 1 && from_highest &&
+                   IPV6_ADDR_CMP(&from_highest->from, &rinfo_new->from) > 0) {
+                       ripng_ecmp_delete(ripng, from_highest);
+                       goto add_or_replace;
+               }
+
+               return NULL;
+       }
+
+add_or_replace:
        rinfo = ripng_info_new();
        memcpy(rinfo, rinfo_new, sizeof(struct ripng_info));
        listnode_add(list, rinfo);
@@ -475,6 +505,36 @@ struct ripng_info *ripng_ecmp_add(struct ripng *ripng,
        return rinfo;
 }
 
+/* Update ECMP routes to zebra when `allow-ecmp` changed. */
+void ripng_ecmp_change(struct ripng *ripng)
+{
+       struct agg_node *rp;
+       struct ripng_info *rinfo;
+       struct list *list;
+       struct listnode *node, *nextnode;
+
+       for (rp = agg_route_top(ripng->table); rp; rp = agg_route_next(rp)) {
+               list = rp->info;
+               if (list && listcount(list) > 1) {
+                       while (listcount(list) > ripng->ecmp) {
+                               struct ripng_info *from_highest = NULL;
+
+                               for (ALL_LIST_ELEMENTS(list, node, nextnode,
+                                                      rinfo)) {
+                                       if (!from_highest ||
+                                           (from_highest &&
+                                            IPV6_ADDR_CMP(
+                                                    &rinfo->from,
+                                                    &from_highest->from) > 0))
+                                               from_highest = rinfo;
+                               }
+
+                               ripng_ecmp_delete(ripng, from_highest);
+                       }
+               }
+       }
+}
+
 /* Replace the ECMP list with the new route.
  * RETURN: the new entry added in the list
  */
@@ -1814,7 +1874,7 @@ struct ripng *ripng_create(const char *vrf_name, struct vrf *vrf, int socket)
                "%s/timers/flush-interval", RIPNG_INSTANCE);
        ripng->default_metric =
                yang_get_default_uint8("%s/default-metric", RIPNG_INSTANCE);
-       ripng->ecmp = yang_get_default_bool("%s/allow-ecmp", RIPNG_INSTANCE);
+       ripng->ecmp = yang_get_default_uint8("%s/allow-ecmp", RIPNG_INSTANCE);
 
        /* Make buffer.  */
        ripng->ibuf = stream_new(RIPNG_MAX_PACKET_SIZE * 5);
index eefcb0ee69cba787feda3231985ce6d880c2e461..c7468b6317c57ef4afcd7d36ea7093fc25248677 100644 (file)
@@ -130,7 +130,7 @@ struct ripng {
        struct event *t_triggered_interval;
 
        /* RIPng ECMP flag */
-       bool ecmp;
+       uint8_t ecmp;
 
        /* RIPng redistribute configuration. */
        struct {
@@ -429,9 +429,12 @@ extern struct ripng_info *ripng_ecmp_replace(struct ripng *ripng,
                                             struct ripng_info *rinfo);
 extern struct ripng_info *ripng_ecmp_delete(struct ripng *ripng,
                                            struct ripng_info *rinfo);
+extern void ripng_ecmp_change(struct ripng *ripng);
 
 extern void ripng_vrf_init(void);
 extern void ripng_vrf_terminate(void);
 extern void ripng_cli_init(void);
 
+extern uint32_t zebra_ecmp_count;
+
 #endif /* _ZEBRA_RIPNG_RIPNGD_H */
diff --git a/tests/topotests/ripng_allow_ecmp/__init__.py b/tests/topotests/ripng_allow_ecmp/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tests/topotests/ripng_allow_ecmp/r1/frr.conf b/tests/topotests/ripng_allow_ecmp/r1/frr.conf
new file mode 100644 (file)
index 0000000..effb5df
--- /dev/null
@@ -0,0 +1,9 @@
+!
+int r1-eth0
+ ipv6 address 2001:db8:1::1/64
+!
+router ripng
+ allow-ecmp
+ network 2001:db8:1::/64
+ timers basic 5 15 10
+exit
diff --git a/tests/topotests/ripng_allow_ecmp/r2/frr.conf b/tests/topotests/ripng_allow_ecmp/r2/frr.conf
new file mode 100644 (file)
index 0000000..71da101
--- /dev/null
@@ -0,0 +1,13 @@
+!
+int lo
+ ipv6 address 2001:db8:2::1/64
+!
+int r2-eth0
+ ipv6 address 2001:db8:1::2/64
+!
+router ripng
+ redistribute connected
+ network 2001:db8:1::/64
+ network 2001:db8:2::/64
+ timers basic 5 15 10
+exit
diff --git a/tests/topotests/ripng_allow_ecmp/r3/frr.conf b/tests/topotests/ripng_allow_ecmp/r3/frr.conf
new file mode 100644 (file)
index 0000000..fe9594d
--- /dev/null
@@ -0,0 +1,14 @@
+!
+int lo
+ ipv6 address 2001:db8:2::1/64
+!
+int r3-eth0
+ ipv6 address 2001:db8:1::3/64
+!
+router ripng
+ redistribute connected
+ network 2001:db8:1::/64
+ network 2001:db8:2::/64
+ timers basic 5 15 10
+exit
+
diff --git a/tests/topotests/ripng_allow_ecmp/r4/frr.conf b/tests/topotests/ripng_allow_ecmp/r4/frr.conf
new file mode 100644 (file)
index 0000000..0d3ea0b
--- /dev/null
@@ -0,0 +1,14 @@
+!
+int lo
+ ipv6 address 2001:db8:2::1/64
+!
+int r4-eth0
+ ipv6 address 2001:db8:1::4/64
+!
+router ripng
+ redistribute connected
+ network 2001:db8:1::/64
+ network 2001:db8:2::/64
+ timers basic 5 15 10
+exit
+
diff --git a/tests/topotests/ripng_allow_ecmp/r5/frr.conf b/tests/topotests/ripng_allow_ecmp/r5/frr.conf
new file mode 100644 (file)
index 0000000..6d6ca56
--- /dev/null
@@ -0,0 +1,14 @@
+!
+int lo
+ ipv6 address 2001:db8:2::1/64
+!
+int r5-eth0
+ ipv6 address 2001:db8:1::5/64
+!
+router ripng
+ redistribute connected
+ network 2001:db8:1::/64
+ network 2001:db8:2::/64
+ timers basic 5 15 10
+exit
+
diff --git a/tests/topotests/ripng_allow_ecmp/test_ripng_allow_ecmp.py b/tests/topotests/ripng_allow_ecmp/test_ripng_allow_ecmp.py
new file mode 100644 (file)
index 0000000..08bb999
--- /dev/null
@@ -0,0 +1,93 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: ISC
+
+# Copyright (c) 2023 by
+# Donatas Abraitis <donatas@opensourcerouting.org>
+#
+
+"""
+Test if RIPng `allow-ecmp` command works correctly.
+"""
+
+import os
+import sys
+import json
+import pytest
+import functools
+
+CWD = os.path.dirname(os.path.realpath(__file__))
+sys.path.append(os.path.join(CWD, "../"))
+
+# pylint: disable=C0413
+from lib import topotest
+from lib.topogen import Topogen, TopoRouter, get_topogen
+from lib.common_config import step
+
+pytestmark = [pytest.mark.ripngd]
+
+
+def setup_module(mod):
+    topodef = {"s1": ("r1", "r2", "r3", "r4", "r5")}
+    tgen = Topogen(topodef, mod.__name__)
+    tgen.start_topology()
+
+    router_list = tgen.routers()
+
+    for _, (rname, router) in enumerate(router_list.items(), 1):
+        router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname)))
+
+    tgen.start_router()
+
+
+def teardown_module(mod):
+    tgen = get_topogen()
+    tgen.stop_topology()
+
+
+def test_ripng_allow_ecmp():
+    tgen = get_topogen()
+
+    if tgen.routers_have_failure():
+        pytest.skip(tgen.errors)
+
+    r1 = tgen.gears["r1"]
+
+    def _show_routes(nh_num):
+        output = json.loads(r1.vtysh_cmd("show ipv6 route json"))
+        expected = {
+            "2001:db8:2::/64": [
+                {
+                    "internalNextHopNum": nh_num,
+                    "internalNextHopActiveNum": nh_num,
+                }
+            ]
+        }
+        return topotest.json_cmp(output, expected)
+
+    test_func = functools.partial(_show_routes, 4)
+    _, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
+    assert (
+        result is None
+    ), "Can't see 2001:db8:2::/64 as multipath (4) in `show ipv6 route`"
+
+    step(
+        "Configure allow-ecmp 2, ECMP group routes SHOULD have next-hops with the lowest IPs"
+    )
+    r1.vtysh_cmd(
+        """
+    configure terminal
+        router ripng
+            allow-ecmp 2
+    """
+    )
+
+    test_func = functools.partial(_show_routes, 2)
+    _, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
+    assert (
+        result is None
+    ), "Can't see 2001:db8:2::/64 as multipath (2) in `show ipv6 route`"
+
+
+if __name__ == "__main__":
+    args = ["-s"] + sys.argv[1:]
+    sys.exit(pytest.main(args))
index 7b2b135fb5695c71f15202ac174dd7d185d86ec1..4aeaf36400751f1636a24cededd2fdc0db0d7203 100644 (file)
@@ -86,8 +86,8 @@ module frr-ripngd {
           "VRF name.";
       }
       leaf allow-ecmp {
-        type boolean;
-        default "false";
+        type uint8;
+        default 0;
         description
           "Allow equal-cost multi-path.";
       }