Commit 269f796183 for frr
commit 269f7961837154119b805f206c5ef907e1126918
Author: Harini Nattamai Subramanian <hnattamaisub@nvidia.com>
Date: Mon Oct 12 23:39:00 2026 -0700
bgpd: make the gateway-ip advertise option additive
The route-map and skip-evpn-imported advertise options are additive:
naming one configures it, and omitting it from a later command leaves it
as it was. The overlay index was left absolute, so two of the three
options on the same command followed one rule and the third followed
another. Nothing in the grammar distinguishes them, so "advertise ipv4
unicast route-map RMAP_NAME" silently dropped a configured gateway-ip
while preserving skip-evpn-imported.
Make the overlay index additive as well. The positive command now only
ever moves the mode to gateway-ip, and "no advertise <afi> unicast
gateway-ip" drops back to no overlay index while type-5 export, the
route-map and skip-evpn-imported stay as configured.
This changes the behavior of an existing command: "advertise <afi>
unicast" no longer clears a configured gateway-ip. Update
bgp_evpn_rt5_addpath accordingly, which relied on it to switch a vrf
back to bestpath advertisement.
frr-reload.py needs the same treatment, and did not handle the overlay
index at all. evpn_advertise_options() did not parse the gateway-ip
keyword, so a running "advertise <afi> unicast gateway-ip" and a desired
"advertise <afi> unicast" produced the same base command with the same
options. The pairing logic then read the change as "no option changed",
which suppressed the delete of the running command and also dropped the
desired command, rendering nothing at all. So the overlay index kept its
old value until bgpd restarted. Track gateway-ip as a third option and
render its targeted no form when it is removed.
Expected rendering:
Add gateway-ip => advertise ipv4 unicast gateway-ip
Remove gateway-ip => no advertise ipv4 unicast gateway-ip
Remove gateway-ip, retain map => no advertise ipv4 unicast gateway-ip
Add gateway-ip, retain skip => advertise ipv4 unicast gateway-ip skip-evpn-imported
Remove gateway-ip and map => no advertise ipv4 unicast gateway-ip
no advertise ipv4 unicast route-map map1
Signed-off-by: hnattamaisub <hnattamaisub@nvidia.com>
diff --git a/bgpd/bgp_evpn_vty.c b/bgpd/bgp_evpn_vty.c
index b12afedae5..fae02e8d0f 100644
--- a/bgpd/bgp_evpn_vty.c
+++ b/bgpd/bgp_evpn_vty.c
@@ -4493,14 +4493,15 @@ DEFPY (bgp_evpn_advertise_type5,
!CHECK_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN],
suppress_flag);
- if (!has_flag_oi_none && !has_flag_oi_gw_ip) {
- if (oly == OVERLAY_INDEX_GATEWAY_IP)
- adv_flag_changed = true;
- } else if (!has_flag_oi_none && oly == OVERLAY_INDEX_TYPE_NONE)
- adv_flag_changed = true;
- else if (!has_flag_oi_gw_ip && oly == OVERLAY_INDEX_GATEWAY_IP)
- adv_flag_changed = true;
- else if (!rmap_changed && !skip_evpn_imported_newly_enabled) {
+ /* Omitting gateway-ip leaves the overlay index as configured, so the
+ * mode only ever moves to gateway-ip here. Use
+ * "no advertise <afi> unicast gateway-ip" to drop back to no overlay
+ * index.
+ */
+ adv_flag_changed = oly == OVERLAY_INDEX_GATEWAY_IP && !has_flag_oi_gw_ip;
+
+ if (was_advertising && !adv_flag_changed && !rmap_changed &&
+ !skip_evpn_imported_newly_enabled) {
/* The supplied options are already configured. */
return CMD_SUCCESS;
}
@@ -4539,23 +4540,15 @@ DEFPY (bgp_evpn_advertise_type5,
bgp_vrf->adv_cmd_rmap[afi][safi].map = NULL;
}
- if (!has_flag_oi_none && !has_flag_oi_gw_ip) {
- /* this is the case for first time ever configuration
- * adv ipv4 unicast is enabled for the first time. So no
- * need to reset any flag
- */
- if (oly == OVERLAY_INDEX_TYPE_NONE)
- SET_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], flag_oi_none);
- else if (oly == OVERLAY_INDEX_GATEWAY_IP)
- SET_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], flag_oi_gw_ip);
- } else if (oly == OVERLAY_INDEX_TYPE_NONE && !has_flag_oi_none) {
- /* This is modify case from gateway-ip to no overlay index */
- UNSET_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], flag_oi_gw_ip);
- SET_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], flag_oi_none);
- } else if (oly == OVERLAY_INDEX_GATEWAY_IP && !has_flag_oi_gw_ip) {
+ if (adv_flag_changed) {
/* This is modify case from no overlay index to gateway-ip */
UNSET_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], flag_oi_none);
SET_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], flag_oi_gw_ip);
+ } else if (!was_advertising) {
+ /* Type-5 export is enabled for the first time, without an
+ * overlay index. So no need to reset any flag.
+ */
+ SET_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], flag_oi_none);
}
if (adv_flag_changed)
@@ -4592,11 +4585,12 @@ DEFPY (bgp_evpn_advertise_type5,
DEFPY (no_bgp_evpn_advertise_type5,
no_bgp_evpn_advertise_type5_cmd,
"no advertise " BGP_AFI_CMD_STR "" BGP_SAFI_CMD_STR
- " [route-map RMAP_NAME$rmap_name] [skip-evpn-imported$skip_evpn_imported]",
+ " [gateway-ip$gateway_ip] [route-map RMAP_NAME$rmap_name] [skip-evpn-imported$skip_evpn_imported]",
NO_STR
"Advertise prefix routes\n"
BGP_AFI_HELP_STR
BGP_SAFI_HELP_STR
+ "advertise gateway IP overlay index\n"
"route-map for filtering specific routes\n"
"Name of the route map\n"
"Do not advertise routes imported from EVPN as Type-5 routes\n")
@@ -4632,6 +4626,11 @@ DEFPY (no_bgp_evpn_advertise_type5,
return CMD_WARNING;
}
+ if (safi == SAFI_MPLS_VPN && gateway_ip) {
+ vty_out(vty, "%% gateway-ip overlay index is only supported for unicast SAFI\n");
+ return CMD_WARNING;
+ }
+
if (safi == SAFI_UNICAST) {
if (afi == AFI_IP) {
flag_oi_none = BGP_L2VPN_EVPN_ADV_IPV4_UNICAST;
@@ -4651,26 +4650,40 @@ DEFPY (no_bgp_evpn_advertise_type5,
has_flag_oi_none = CHECK_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], flag_oi_none);
has_flag_oi_gw_ip = CHECK_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], flag_oi_gw_ip);
- /* Remove a single advertise option and keep Type-5 export enabled. */
- if (skip_evpn_imported)
- return bgp_evpn_clear_suppress_import_from_evpn(bgp_vrf, afi, safi);
-
- if (rmap_name) {
- if (!bgp_vrf->adv_cmd_rmap[afi][safi].name ||
- strcmp(bgp_vrf->adv_cmd_rmap[afi][safi].name, rmap_name) != 0)
- return CMD_SUCCESS;
-
- bgp_evpn_withdraw_type5_routes(bgp_vrf, afi, safi);
- XFREE(MTYPE_ROUTE_MAP_NAME, bgp_vrf->adv_cmd_rmap[afi][safi].name);
- route_map_counter_decrement(bgp_vrf->adv_cmd_rmap[afi][safi].map);
- bgp_vrf->adv_cmd_rmap[afi][safi].name = NULL;
- bgp_vrf->adv_cmd_rmap[afi][safi].map = NULL;
+ /* Remove the named advertise options and keep Type-5 export enabled. */
+ if (skip_evpn_imported || rmap_name || gateway_ip) {
+ if (skip_evpn_imported)
+ bgp_evpn_clear_suppress_import_from_evpn(bgp_vrf, afi, safi);
+
+ if (rmap_name && bgp_vrf->adv_cmd_rmap[afi][safi].name &&
+ strcmp(bgp_vrf->adv_cmd_rmap[afi][safi].name, rmap_name) == 0) {
+ bgp_evpn_withdraw_type5_routes(bgp_vrf, afi, safi);
+ XFREE(MTYPE_ROUTE_MAP_NAME, bgp_vrf->adv_cmd_rmap[afi][safi].name);
+ route_map_counter_decrement(bgp_vrf->adv_cmd_rmap[afi][safi].map);
+ bgp_vrf->adv_cmd_rmap[afi][safi].name = NULL;
+ bgp_vrf->adv_cmd_rmap[afi][safi].map = NULL;
+
+ /* Re-advertise without the removed route-map when Type-5
+ * export remains enabled in normal or gateway-IP
+ * overlay-index mode.
+ */
+ if (has_flag_oi_none || has_flag_oi_gw_ip)
+ bgp_evpn_advertise_type5_routes(bgp_vrf, afi, safi);
+ }
- /* Re-advertise without the removed route-map when Type-5 export
- * remains enabled in normal or gateway-IP overlay-index mode.
- */
- if (has_flag_oi_none || has_flag_oi_gw_ip)
+ if (gateway_ip && has_flag_oi_gw_ip) {
+ /* Drop back to no overlay index. Type-5 export stays
+ * enabled, so the routes are re-originated without a
+ * gateway IP.
+ */
+ bgp_evpn_withdraw_type5_routes(bgp_vrf, afi, safi);
+ UNSET_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], flag_oi_gw_ip);
+ SET_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], flag_oi_none);
+ /* Cleanup addpath IDs allocated for the overlay index */
+ bgp_addpath_type_changed(bgp_vrf);
bgp_evpn_advertise_type5_routes(bgp_vrf, afi, safi);
+ }
+
return CMD_SUCCESS;
}
diff --git a/doc/user/bgp.rst b/doc/user/bgp.rst
index 4d26f25678..95aca407a5 100644
--- a/doc/user/bgp.rst
+++ b/doc/user/bgp.rst
@@ -4487,9 +4487,8 @@ prefix:
When this CLI is configured for a BGP vrf under L2VPN EVPN address family, EVPN
type-5 routes are generated for BGP prefixes in the vrf.
-If the above command is configured without the "gateway-ip" keyword, type-5
-routes are generated without overlay index, and only the best path for each
-prefix is exported to EVPN.
+By default type-5 routes are generated without overlay index, and only the best
+path for each prefix is exported to EVPN.
If the above command is configured with the "gateway-ip" keyword, all paths are
exported to EVPN using AddPath, and each path's nexthop becomes the gateway IP
@@ -4501,11 +4500,29 @@ prefix, and addpath must be properly configured in EVPN to enable multipathing.
The optional ``skip-evpn-imported`` keyword prevents exporting a
unicast route as a Type-5 route when its ultimate VRF-leak import parent is in
the EVPN table. This prevents an EVPN-origin route leaked between VRFs from
-being re-exported into EVPN. It can be removed without disabling Type-5 export
-with the following command:
+being re-exported into EVPN.
+
+The ``gateway-ip``, ``route-map`` and ``skip-evpn-imported`` keywords are
+additive: each one is configured by naming it, and omitting it from a later
+command leaves it as it was. So ``advertise ipv4 unicast route-map RMAP_NAME``
+does not clear a previously configured ``gateway-ip``. Each option is removed
+individually, without disabling type-5 export, using the matching no form:
+
+.. clicmd:: no advertise <ipv4|ipv6> unicast gateway-ip
+
+.. clicmd:: no advertise <ipv4|ipv6> unicast route-map RMAP_NAME
.. clicmd:: no advertise <ipv4|ipv6> unicast skip-evpn-imported
+Removing ``gateway-ip`` returns the vrf to exporting only the best path for
+each prefix, without an overlay index. Removing ``route-map`` requires the
+configured route-map name, and re-exports the prefixes without any filtering.
+
+To stop generating type-5 routes altogether, and clear all of the options
+above, use the no form without any keyword:
+
+.. clicmd:: no advertise <ipv4|ipv6> unicast
+
VPN routes can also be advertised as EVPN type-5 routes:
.. clicmd:: advertise <ipv4|ipv6> vpn [route-map RMAP_NAME]
diff --git a/tests/topotests/bgp_evpn_rt5_addpath/test_bgp_evpn_rt5_addpath.py b/tests/topotests/bgp_evpn_rt5_addpath/test_bgp_evpn_rt5_addpath.py
index 5bd459604c..e450b5a44d 100644
--- a/tests/topotests/bgp_evpn_rt5_addpath/test_bgp_evpn_rt5_addpath.py
+++ b/tests/topotests/bgp_evpn_rt5_addpath/test_bgp_evpn_rt5_addpath.py
@@ -549,7 +549,7 @@ def test_bgp_evpn_rt5_addpath_transitions():
conf
router bgp 64001 vrf vrf100
address-family l2vpn evpn
- advertise ipv4 unicast
+ no advertise ipv4 unicast gateway-ip
"""
)
diff --git a/tools/frr-reload.py b/tools/frr-reload.py
index 173e8ed08c..17b43cfaf5 100755
--- a/tools/frr-reload.py
+++ b/tools/frr-reload.py
@@ -1555,6 +1555,7 @@ def ignore_delete_re_add_lines(lines_to_add, lines_to_del):
return None
# The word after route-map is a name and may match an option keyword.
+ gateway_ip = False
route_map = None
skip_evpn_imported = False
i = 3
@@ -1564,12 +1565,15 @@ def ignore_delete_re_add_lines(lines_to_add, lines_to_del):
route_map = words[i + 1]
i += 2
continue
- if words[i] == "skip-evpn-imported":
+ if words[i] == "gateway-ip":
+ gateway_ip = True
+ elif words[i] == "skip-evpn-imported":
skip_evpn_imported = True
i += 1
return {
"base": " ".join(words[:3]),
+ "gateway_ip": gateway_ip,
"route_map": route_map,
"skip_evpn_imported": skip_evpn_imported,
}
@@ -1856,14 +1860,16 @@ def ignore_delete_re_add_lines(lines_to_add, lines_to_del):
if desired_advertise:
add_ctx_keys, add_line, add_options = desired_advertise
- # Each parsed command records its base command, route-map,
- # and skip option. For example, changing
+ # Each parsed command records its base command, overlay
+ # index, route-map, and skip option. For example, changing
# "advertise ipv4 unicast route-map map1 skip-evpn-imported"
# to "advertise ipv4 unicast skip-evpn-imported" produces:
# evpn_options = {"base": "advertise ipv4 unicast",
- # "route_map": "map1", "skip_evpn_imported": True}
+ # "gateway_ip": False, "route_map": "map1",
+ # "skip_evpn_imported": True}
# add_options = {"base": "advertise ipv4 unicast",
- # "route_map": None, "skip_evpn_imported": True}
+ # "gateway_ip": False, "route_map": None,
+ # "skip_evpn_imported": True}
route_map_removed = (
evpn_options["route_map"] and not add_options["route_map"]
)
@@ -1878,11 +1884,18 @@ def ignore_delete_re_add_lines(lines_to_add, lines_to_del):
add_options["skip_evpn_imported"]
and not evpn_options["skip_evpn_imported"]
)
+ gateway_ip_removed = (
+ evpn_options["gateway_ip"] and not add_options["gateway_ip"]
+ )
+ gateway_ip_added = (
+ add_options["gateway_ip"] and not evpn_options["gateway_ip"]
+ )
- # Route-map and skip-evpn-imported are updated independently.
- # Do not turn an option change into a bare "no advertise",
- # because that disables Type-5 export before the replacement
- # command can apply the requested option state.
+ # gateway-ip, route-map and skip-evpn-imported are updated
+ # independently. Do not turn an option change into a bare
+ # "no advertise", because that disables Type-5 export
+ # before the replacement command can apply the requested
+ # option state.
lines_to_del_to_del.append((ctx_keys, line))
# If route-map is deleted, add its no form.
@@ -1900,8 +1913,18 @@ def ignore_delete_re_add_lines(lines_to_add, lines_to_del):
(ctx_keys, "%s skip-evpn-imported" % evpn_options["base"])
)
+ # If gateway-ip is deleted, add its no form.
+ if gateway_ip_removed:
+ evpn_option_deletes.append(
+ (ctx_keys, "%s gateway-ip" % evpn_options["base"])
+ )
+
# Removes the desired positive command from lines_to_add.
- if not route_map_added_or_changed and not skip_added:
+ if (
+ not route_map_added_or_changed
+ and not skip_added
+ and not gateway_ip_added
+ ):
lines_to_add_to_del.append((add_ctx_keys, add_line))
else:
# When the whole command is removed, render the bare