Merge pull request #13535 from opensourcerouting/feature/ripng_allow_ecmp

ripng: Implement allow-ecmp X command
This commit is contained in:
Russ White 2023-05-23 07:35:32 -04:00 committed by GitHub
commit fa45a19a60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 278 additions and 18 deletions

View File

@ -38,6 +38,10 @@ Currently ripngd supports the following commands:
Set RIPng static routing announcement of NETWORK. 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: .. _ripngd-terminal-mode-commands:

View 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 * XPath: /frr-ripngd:ripngd/instance/allow-ecmp
*/ */
DEFPY_YANG (ripng_allow_ecmp, DEFUN_YANG (ripng_allow_ecmp,
ripng_allow_ecmp_cmd, ripng_allow_ecmp_cmd,
"[no] allow-ecmp", "allow-ecmp [" CMD_RANGE_STR(1, MULTIPATH_NUM) "]",
NO_STR "Allow Equal Cost MultiPath\n"
"Allow Equal Cost MultiPath\n") "Number of paths\n")
{ {
nb_cli_enqueue_change(vty, "./allow-ecmp", NB_OP_MODIFY, int idx_number = 0;
no ? "false" : "true"); 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, 0);
return nb_cli_apply_changes(vty, NULL); 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, void cli_show_ripng_allow_ecmp(struct vty *vty, const struct lyd_node *dnode,
bool show_defaults) bool show_defaults)
{ {
if (!yang_dnode_get_bool(dnode, NULL)) uint8_t paths;
vty_out(vty, " no");
vty_out(vty, " allow-ecmp\n"); paths = yang_dnode_get_uint8(dnode, NULL);
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_no_ipv6_distribute_list_cmd);
install_element(RIPNG_NODE, &ripng_allow_ecmp_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_information_originate_cmd);
install_element(RIPNG_NODE, &ripng_default_metric_cmd); install_element(RIPNG_NODE, &ripng_default_metric_cmd);
install_element(RIPNG_NODE, &no_ripng_default_metric_cmd); install_element(RIPNG_NODE, &no_ripng_default_metric_cmd);

View File

@ -32,6 +32,8 @@ struct option longopts[] = {{0}};
/* ripngd privileges */ /* ripngd privileges */
zebra_capabilities_t _caps_p[] = {ZCAP_NET_RAW, ZCAP_BIND, ZCAP_SYS_ADMIN}; 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 = { struct zebra_privs_t ripngd_privs = {
#if defined(FRR_USER) #if defined(FRR_USER)
.user = FRR_USER, .user = FRR_USER,

View File

@ -129,9 +129,14 @@ int ripngd_instance_allow_ecmp_modify(struct nb_cb_modify_args *args)
return NB_OK; return NB_OK;
ripng = nb_running_get_entry(args->dnode, NULL, true); ripng = nb_running_get_entry(args->dnode, NULL, true);
ripng->ecmp = yang_dnode_get_bool(args->dnode, NULL); ripng->ecmp =
if (!ripng->ecmp) MIN(yang_dnode_get_uint8(args->dnode, NULL), zebra_ecmp_count);
if (!ripng->ecmp) {
ripng_ecmp_disable(ripng); ripng_ecmp_disable(ripng);
return NB_OK;
}
ripng_ecmp_change(ripng);
return NB_OK; return NB_OK;
} }

View File

@ -30,7 +30,7 @@ static void ripng_zebra_ipv6_send(struct ripng *ripng, struct agg_node *rp,
struct zapi_nexthop *api_nh; struct zapi_nexthop *api_nh;
struct listnode *listnode = NULL; struct listnode *listnode = NULL;
struct ripng_info *rinfo = NULL; struct ripng_info *rinfo = NULL;
int count = 0; uint32_t count = 0;
const struct prefix *p = agg_node_get_prefix(rp); const struct prefix *p = agg_node_get_prefix(rp);
memset(&api, 0, sizeof(api)); 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); SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP);
for (ALL_LIST_ELEMENTS_RO(list, listnode, rinfo)) { for (ALL_LIST_ELEMENTS_RO(list, listnode, rinfo)) {
if (count >= MULTIPATH_NUM) if (count >= zebra_ecmp_count)
break; break;
api_nh = &api.nexthops[count]; api_nh = &api.nexthops[count];
api_nh->vrf_id = ripng->vrf->vrf_id; 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, [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. */ /* Initialize zebra structure and it's commands. */
void zebra_init(struct event_loop *master) 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_init(zclient, ZEBRA_ROUTE_RIPNG, 0, &ripngd_privs);
zclient->zebra_connected = ripng_zebra_connected; zclient->zebra_connected = ripng_zebra_connected;
zclient->zebra_capabilities = ripng_zebra_capabilities;
} }
void ripng_zebra_stop(void) void ripng_zebra_stop(void)

View File

@ -443,7 +443,10 @@ struct ripng_info *ripng_ecmp_add(struct ripng *ripng,
{ {
struct agg_node *rp = rinfo_new->rp; struct agg_node *rp = rinfo_new->rp;
struct ripng_info *rinfo = NULL; struct ripng_info *rinfo = NULL;
struct ripng_info *rinfo_exist = NULL;
struct list *list = NULL; struct list *list = NULL;
struct listnode *node = NULL;
struct listnode *nnode = NULL;
if (rp->info == NULL) if (rp->info == NULL)
rp->info = list_new(); rp->info = list_new();
@ -454,6 +457,33 @@ struct ripng_info *ripng_ecmp_add(struct ripng *ripng,
if (listcount(list) && !ripng->ecmp) if (listcount(list) && !ripng->ecmp)
return NULL; 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(); rinfo = ripng_info_new();
memcpy(rinfo, rinfo_new, sizeof(struct ripng_info)); memcpy(rinfo, rinfo_new, sizeof(struct ripng_info));
listnode_add(list, rinfo); listnode_add(list, rinfo);
@ -475,6 +505,36 @@ struct ripng_info *ripng_ecmp_add(struct ripng *ripng,
return rinfo; 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. /* Replace the ECMP list with the new route.
* RETURN: the new entry added in the list * 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); "%s/timers/flush-interval", RIPNG_INSTANCE);
ripng->default_metric = ripng->default_metric =
yang_get_default_uint8("%s/default-metric", RIPNG_INSTANCE); 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. */ /* Make buffer. */
ripng->ibuf = stream_new(RIPNG_MAX_PACKET_SIZE * 5); ripng->ibuf = stream_new(RIPNG_MAX_PACKET_SIZE * 5);

View File

@ -130,7 +130,7 @@ struct ripng {
struct event *t_triggered_interval; struct event *t_triggered_interval;
/* RIPng ECMP flag */ /* RIPng ECMP flag */
bool ecmp; uint8_t ecmp;
/* RIPng redistribute configuration. */ /* RIPng redistribute configuration. */
struct { struct {
@ -429,9 +429,12 @@ extern struct ripng_info *ripng_ecmp_replace(struct ripng *ripng,
struct ripng_info *rinfo); struct ripng_info *rinfo);
extern struct ripng_info *ripng_ecmp_delete(struct ripng *ripng, extern struct ripng_info *ripng_ecmp_delete(struct ripng *ripng,
struct ripng_info *rinfo); struct ripng_info *rinfo);
extern void ripng_ecmp_change(struct ripng *ripng);
extern void ripng_vrf_init(void); extern void ripng_vrf_init(void);
extern void ripng_vrf_terminate(void); extern void ripng_vrf_terminate(void);
extern void ripng_cli_init(void); extern void ripng_cli_init(void);
extern uint32_t zebra_ecmp_count;
#endif /* _ZEBRA_RIPNG_RIPNGD_H */ #endif /* _ZEBRA_RIPNG_RIPNGD_H */

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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))

View File

@ -86,8 +86,8 @@ module frr-ripngd {
"VRF name."; "VRF name.";
} }
leaf allow-ecmp { leaf allow-ecmp {
type boolean; type uint8;
default "false"; default 0;
description description
"Allow equal-cost multi-path."; "Allow equal-cost multi-path.";
} }