forked from Mirror/frr
Merge pull request #13430 from opensourcerouting/feature/rip_allow-ecmp_limit
ripd: Implement allow-ecmp X command
This commit is contained in:
commit
907183cb35
|
@ -159,6 +159,11 @@ RIP Configuration
|
|||
If `poisoned-reverse` is also set, the router sends the poisoned routes
|
||||
with highest metric back to the sending router.
|
||||
|
||||
.. clicmd:: allow-ecmp [1-MULTIPATH_NUM]
|
||||
|
||||
Control how many ECMP paths RIP can inject for the same prefix. If specified
|
||||
without a number, a maximum is taken (compiled with ``--enable-multipath``).
|
||||
|
||||
.. _rip-version-control:
|
||||
|
||||
RIP Version Control
|
||||
|
|
|
@ -85,14 +85,33 @@ void cli_show_router_rip(struct vty *vty, const struct lyd_node *dnode,
|
|||
/*
|
||||
* XPath: /frr-ripd:ripd/instance/allow-ecmp
|
||||
*/
|
||||
DEFPY_YANG (rip_allow_ecmp,
|
||||
DEFUN_YANG (rip_allow_ecmp,
|
||||
rip_allow_ecmp_cmd,
|
||||
"[no] allow-ecmp",
|
||||
NO_STR
|
||||
"Allow Equal Cost MultiPath\n")
|
||||
"allow-ecmp [" CMD_RANGE_STR(1, MULTIPATH_NUM) "]",
|
||||
"Allow Equal Cost MultiPath\n"
|
||||
"Number of paths\n")
|
||||
{
|
||||
nb_cli_enqueue_change(vty, "./allow-ecmp", NB_OP_MODIFY,
|
||||
no ? "false" : "true");
|
||||
int idx_number = 1;
|
||||
char mpaths[3] = {};
|
||||
uint32_t paths = MULTIPATH_NUM;
|
||||
|
||||
if (argv[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_rip_allow_ecmp,
|
||||
no_rip_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);
|
||||
}
|
||||
|
@ -100,10 +119,14 @@ DEFPY_YANG (rip_allow_ecmp,
|
|||
void cli_show_rip_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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1156,6 +1179,7 @@ void rip_cli_init(void)
|
|||
install_element(RIP_NODE, &rip_no_distribute_list_cmd);
|
||||
|
||||
install_element(RIP_NODE, &rip_allow_ecmp_cmd);
|
||||
install_element(RIP_NODE, &no_rip_allow_ecmp_cmd);
|
||||
install_element(RIP_NODE, &rip_default_information_originate_cmd);
|
||||
install_element(RIP_NODE, &rip_default_metric_cmd);
|
||||
install_element(RIP_NODE, &no_rip_default_metric_cmd);
|
||||
|
|
|
@ -99,9 +99,13 @@ int ripd_instance_allow_ecmp_modify(struct nb_cb_modify_args *args)
|
|||
return NB_OK;
|
||||
|
||||
rip = nb_running_get_entry(args->dnode, NULL, true);
|
||||
rip->ecmp = yang_dnode_get_bool(args->dnode, NULL);
|
||||
if (!rip->ecmp)
|
||||
rip->ecmp = yang_dnode_get_uint8(args->dnode, NULL);
|
||||
if (!rip->ecmp) {
|
||||
rip_ecmp_disable(rip);
|
||||
return NB_OK;
|
||||
}
|
||||
|
||||
rip_ecmp_change(rip);
|
||||
|
||||
return NB_OK;
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
|
||||
/* All information about zebra. */
|
||||
struct zclient *zclient = NULL;
|
||||
uint32_t zebra_ecmp_count = MULTIPATH_NUM;
|
||||
|
||||
/* Send ECMP routes to zebra. */
|
||||
static void rip_zebra_ipv4_send(struct rip *rip, struct route_node *rp,
|
||||
|
@ -30,7 +31,7 @@ static void rip_zebra_ipv4_send(struct rip *rip, struct route_node *rp,
|
|||
struct zapi_nexthop *api_nh;
|
||||
struct listnode *listnode = NULL;
|
||||
struct rip_info *rinfo = NULL;
|
||||
int count = 0;
|
||||
uint32_t count = 0;
|
||||
|
||||
memset(&api, 0, sizeof(api));
|
||||
api.vrf_id = rip->vrf->vrf_id;
|
||||
|
@ -39,7 +40,7 @@ static void rip_zebra_ipv4_send(struct rip *rip, struct route_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 = rip->vrf->vrf_id;
|
||||
|
@ -227,6 +228,11 @@ zclient_handler *const rip_handlers[] = {
|
|||
[ZEBRA_REDISTRIBUTE_ROUTE_DEL] = rip_zebra_read_route,
|
||||
};
|
||||
|
||||
static void rip_zebra_capabilities(struct zclient_capabilities *cap)
|
||||
{
|
||||
zebra_ecmp_count = MIN(cap->ecmp, zebra_ecmp_count);
|
||||
}
|
||||
|
||||
void rip_zclient_init(struct event_loop *master)
|
||||
{
|
||||
/* Set default value to the zebra client structure. */
|
||||
|
@ -234,6 +240,7 @@ void rip_zclient_init(struct event_loop *master)
|
|||
array_size(rip_handlers));
|
||||
zclient_init(zclient, ZEBRA_ROUTE_RIP, 0, &ripd_privs);
|
||||
zclient->zebra_connected = rip_zebra_connected;
|
||||
zclient->zebra_capabilities = rip_zebra_capabilities;
|
||||
}
|
||||
|
||||
void rip_zclient_stop(void)
|
||||
|
|
62
ripd/ripd.c
62
ripd/ripd.c
|
@ -156,7 +156,10 @@ struct rip_info *rip_ecmp_add(struct rip *rip, struct rip_info *rinfo_new)
|
|||
{
|
||||
struct route_node *rp = rinfo_new->rp;
|
||||
struct rip_info *rinfo = NULL;
|
||||
struct rip_info *rinfo_exist = NULL;
|
||||
struct list *list = NULL;
|
||||
struct listnode *node = NULL;
|
||||
struct listnode *nnode = NULL;
|
||||
|
||||
if (rp->info == NULL)
|
||||
rp->info = list_new();
|
||||
|
@ -167,6 +170,33 @@ struct rip_info *rip_ecmp_add(struct rip *rip, struct rip_info *rinfo_new)
|
|||
if (listcount(list) && !rip->ecmp)
|
||||
return NULL;
|
||||
|
||||
/* Add or replace an existing ECMP path with lower neighbor IP */
|
||||
if (listcount(list) && listcount(list) >= rip->ecmp) {
|
||||
struct rip_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 &&
|
||||
IPV4_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 (rip->ecmp > 1 && from_highest &&
|
||||
IPV4_ADDR_CMP(&from_highest->from, &rinfo_new->from) > 0) {
|
||||
rip_ecmp_delete(rip, from_highest);
|
||||
goto add_or_replace;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
add_or_replace:
|
||||
rinfo = rip_info_new();
|
||||
memcpy(rinfo, rinfo_new, sizeof(struct rip_info));
|
||||
listnode_add(list, rinfo);
|
||||
|
@ -2632,6 +2662,36 @@ struct rip *rip_lookup_by_vrf_name(const char *vrf_name)
|
|||
return RB_FIND(rip_instance_head, &rip_instances, &rip);
|
||||
}
|
||||
|
||||
/* Update ECMP routes to zebra when `allow-ecmp` changed. */
|
||||
void rip_ecmp_change(struct rip *rip)
|
||||
{
|
||||
struct route_node *rp;
|
||||
struct rip_info *rinfo;
|
||||
struct list *list;
|
||||
struct listnode *node, *nextnode;
|
||||
|
||||
for (rp = route_top(rip->table); rp; rp = route_next(rp)) {
|
||||
list = rp->info;
|
||||
if (list && listcount(list) > 1) {
|
||||
while (listcount(list) > rip->ecmp) {
|
||||
struct rip_info *from_highest = NULL;
|
||||
|
||||
for (ALL_LIST_ELEMENTS(list, node, nextnode,
|
||||
rinfo)) {
|
||||
if (!from_highest ||
|
||||
(from_highest &&
|
||||
IPV4_ADDR_CMP(
|
||||
&rinfo->from,
|
||||
&from_highest->from) > 0))
|
||||
from_highest = rinfo;
|
||||
}
|
||||
|
||||
rip_ecmp_delete(rip, from_highest);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Create new RIP instance and set it to global variable. */
|
||||
struct rip *rip_create(const char *vrf_name, struct vrf *vrf, int socket)
|
||||
{
|
||||
|
@ -2641,7 +2701,7 @@ struct rip *rip_create(const char *vrf_name, struct vrf *vrf, int socket)
|
|||
rip->vrf_name = XSTRDUP(MTYPE_RIP_VRF_NAME, vrf_name);
|
||||
|
||||
/* Set initial value. */
|
||||
rip->ecmp = yang_get_default_bool("%s/allow-ecmp", RIP_INSTANCE);
|
||||
rip->ecmp = yang_get_default_uint8("%s/allow-ecmp", RIP_INSTANCE);
|
||||
rip->default_metric =
|
||||
yang_get_default_uint8("%s/default-metric", RIP_INSTANCE);
|
||||
rip->distance =
|
||||
|
|
|
@ -141,7 +141,7 @@ struct rip {
|
|||
struct route_table *distance_table;
|
||||
|
||||
/* RIP ECMP flag */
|
||||
bool ecmp;
|
||||
uint8_t ecmp;
|
||||
|
||||
/* Are we in passive-interface default mode? */
|
||||
bool passive_default;
|
||||
|
@ -537,4 +537,6 @@ extern struct event_loop *master;
|
|||
DECLARE_HOOK(rip_ifaddr_add, (struct connected * ifc), (ifc));
|
||||
DECLARE_HOOK(rip_ifaddr_del, (struct connected * ifc), (ifc));
|
||||
|
||||
extern void rip_ecmp_change(struct rip *rip);
|
||||
|
||||
#endif /* _ZEBRA_RIP_H */
|
||||
|
|
13
tests/topotests/rip_allow_ecmp/r4/frr.conf
Normal file
13
tests/topotests/rip_allow_ecmp/r4/frr.conf
Normal file
|
@ -0,0 +1,13 @@
|
|||
!
|
||||
int lo
|
||||
ip address 10.10.10.1/32
|
||||
!
|
||||
int r4-eth0
|
||||
ip address 192.168.1.4/24
|
||||
!
|
||||
router rip
|
||||
network 192.168.1.0/24
|
||||
network 10.10.10.1/32
|
||||
timers basic 5 15 10
|
||||
exit
|
||||
|
13
tests/topotests/rip_allow_ecmp/r5/frr.conf
Normal file
13
tests/topotests/rip_allow_ecmp/r5/frr.conf
Normal file
|
@ -0,0 +1,13 @@
|
|||
!
|
||||
int lo
|
||||
ip address 10.10.10.1/32
|
||||
!
|
||||
int r5-eth0
|
||||
ip address 192.168.1.5/24
|
||||
!
|
||||
router rip
|
||||
network 192.168.1.0/24
|
||||
network 10.10.10.1/32
|
||||
timers basic 5 15 10
|
||||
exit
|
||||
|
|
@ -21,12 +21,13 @@ 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.ripd]
|
||||
|
||||
|
||||
def setup_module(mod):
|
||||
topodef = {"s1": ("r1", "r2", "r3")}
|
||||
topodef = {"s1": ("r1", "r2", "r3", "r4", "r5")}
|
||||
tgen = Topogen(topodef, mod.__name__)
|
||||
tgen.start_topology()
|
||||
|
||||
|
@ -102,11 +103,13 @@ def test_rip_allow_ecmp():
|
|||
_, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
|
||||
assert result is None, "Can't see 10.10.10.1/32 as multipath in `show ip rip`"
|
||||
|
||||
def _show_routes():
|
||||
def _show_routes(nh_num):
|
||||
output = json.loads(r1.vtysh_cmd("show ip route json"))
|
||||
expected = {
|
||||
"10.10.10.1/32": [
|
||||
{
|
||||
"internalNextHopNum": nh_num,
|
||||
"internalNextHopActiveNum": nh_num,
|
||||
"nexthops": [
|
||||
{
|
||||
"ip": "192.168.1.2",
|
||||
|
@ -116,15 +119,36 @@ def test_rip_allow_ecmp():
|
|||
"ip": "192.168.1.3",
|
||||
"active": True,
|
||||
},
|
||||
]
|
||||
],
|
||||
}
|
||||
]
|
||||
}
|
||||
return topotest.json_cmp(output, expected)
|
||||
|
||||
test_func = functools.partial(_show_routes)
|
||||
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 10.10.10.1/32 as multipath in `show ip route`"
|
||||
assert result is None, "Can't see 10.10.10.1/32 as multipath (4) in `show ip route`"
|
||||
|
||||
step(
|
||||
"Configure allow-ecmp 2, ECMP group routes SHOULD have next-hops with the lowest IPs"
|
||||
)
|
||||
r1.vtysh_cmd(
|
||||
"""
|
||||
configure terminal
|
||||
router rip
|
||||
allow-ecmp 2
|
||||
"""
|
||||
)
|
||||
|
||||
test_func = functools.partial(_show_rip_routes)
|
||||
_, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
|
||||
assert (
|
||||
result is None
|
||||
), "Can't see 10.10.10.1/32 as ECMP with the lowest next-hop IPs"
|
||||
|
||||
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 10.10.10.1/32 as multipath (2) in `show ip route`"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
"instance": [
|
||||
{
|
||||
"vrf": "default",
|
||||
"allow-ecmp": "true",
|
||||
"allow-ecmp": 1,
|
||||
"distance": {
|
||||
"source": [
|
||||
{
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
<ripd xmlns="http://frrouting.org/yang/ripd">
|
||||
<instance>
|
||||
<vrf>default</vrf>
|
||||
<allow-ecmp>true</allow-ecmp>
|
||||
<allow-ecmp>1</allow-ecmp>
|
||||
<static-route>10.0.1.0/24</static-route>
|
||||
<distance>
|
||||
<source>
|
||||
|
|
|
@ -119,8 +119,8 @@ module frr-ripd {
|
|||
"VRF name.";
|
||||
}
|
||||
leaf allow-ecmp {
|
||||
type boolean;
|
||||
default "false";
|
||||
type uint8;
|
||||
default 0;
|
||||
description
|
||||
"Allow equal-cost multi-path.";
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue