Commit 36add0f5da for frr

commit 36add0f5da76696f4864d0acee10c3aca001d67e
Author: Enke Chen <enchen@paloaltonetworks.com>
Date:   Tue Nov 3 14:14:03 2026 -0800

    bgpd: fix SUBGRP_STATUS_DEFAULT_ORIGINATE flag handling

    Fix inconsistent handling of the SUBGRP_STATUS_DEFAULT_ORIGINATE flag
    which tracks whether the synthesized default route has been sent to a
    subgroup.

    Problems fixed:

    1. Spurious withdraws: SET_FLAG was called when the flag was NOT set
       (in RMAP_DENYMATCH and undefined route-map cases), causing withdraws
       to be sent for routes that were never advertised.

    2. Missing withdraws: At the time of a policy change, we don't yet know
       whether the new policy would result in a re-advertisement or a
       withdraw. UNSET_FLAG was called in various places (route refresh,
       policy update, soft clear, etc.) to "force re-announcement", but
       clearing the flag prematurely can result in missing withdraws since
       the flag is checked before sending withdraw.

    3. Stale updates: When the table changed and the default-originate timer
       fired, the flag check prevented sending an UPDATE with new attributes
       if a default route had been previously advertised, resulting in stale
       routing information.

    The fix ensures:
    - SET_FLAG is only called before sending UPDATE
    - UNSET_FLAG is only called after sending WITHDRAW
    - UPDATE is always sent in the else branch

    With this fix, redundant UPDATEs can be sent upon a policy change or
    table change when the resulting attributes remain the same. This issue
    existed before, although to a lesser extent. We will address it in a
    subsequent PR by comparing attributes to avoid redundant UPDATEs.

    Signed-off-by: Enke Chen <enchen@paloaltonetworks.com>

diff --git a/bgpd/bgp_packet.c b/bgpd/bgp_packet.c
index 461e23cbcf..2eda2afb0b 100644
--- a/bgpd/bgp_packet.c
+++ b/bgpd/bgp_packet.c
@@ -3009,7 +3009,6 @@ static int bgp_route_refresh_receive(struct peer_connection *connection, bgp_siz
 	iana_safi_t pkt_safi;
 	safi_t safi;
 	struct stream *s;
-	struct peer_af *paf;
 	uint8_t subtype;
 	bgp_size_t msg_length =
 		size - (BGP_MSG_ROUTE_REFRESH_MIN_SIZE - BGP_HEADER_SIZE);
@@ -3285,19 +3284,6 @@ static int bgp_route_refresh_receive(struct peer_connection *connection, bgp_siz
 		UNSET_FLAG(peer->af_sflags[afi][safi],
 			   PEER_STATUS_ORF_WAIT_REFRESH);

-	paf = peer_af_find(peer, afi, safi);
-	if (paf && paf->subgroup) {
-		/* If the peer is configured for default-originate clear the
-		 * SUBGRP_STATUS_DEFAULT_ORIGINATE flag so that we will
-		 * re-advertise the
-		 * default
-		 */
-		if (CHECK_FLAG(paf->subgroup->sflags,
-			       SUBGRP_STATUS_DEFAULT_ORIGINATE))
-			UNSET_FLAG(paf->subgroup->sflags,
-				   SUBGRP_STATUS_DEFAULT_ORIGINATE);
-	}
-
 	if (bgp_debug_neighbor_events(peer))
 		zlog_debug("%pBP rcvd route-refresh (REQUEST) for %s/%s", peer, afi2str(afi),
 			   safi2str(safi));
diff --git a/bgpd/bgp_updgrp.c b/bgpd/bgp_updgrp.c
index d89c6bf46f..66edd3c561 100644
--- a/bgpd/bgp_updgrp.c
+++ b/bgpd/bgp_updgrp.c
@@ -1890,11 +1890,7 @@ static int updgrp_policy_update_walkcb(struct update_group *updgrp, void *arg)
 			 * the route-map is still present.  If it was deleted,
 			 * subgroup_default_originate() detects the missing map
 			 * and withdraws the default route.
-			 *
-			 * Clear the flag to force re-announcement with
-			 * potentially updated attributes from the route-map.
 			 */
-			UNSET_FLAG(subgrp->sflags, SUBGRP_STATUS_DEFAULT_ORIGINATE);
 			subgroup_default_originate(subgrp);
 		}
 		update_subgroup_set_needs_refresh(subgrp, 0);
@@ -2300,14 +2296,8 @@ update_group_default_originate_route_map_walkcb(struct update_group *updgrp,
 		afi = SUBGRP_AFI(subgrp);
 		safi = SUBGRP_SAFI(subgrp);

-		if (peer->default_rmap[afi][safi].name) {
-			/*
-			 * Clear the flag to force re-announcement with
-			 * potentially updated attributes from the route-map.
-			 */
-			UNSET_FLAG(subgrp->sflags, SUBGRP_STATUS_DEFAULT_ORIGINATE);
+		if (peer->default_rmap[afi][safi].name)
 			subgroup_default_originate(subgrp);
-		}
 	}

 	return UPDWALK_CONTINUE;
diff --git a/bgpd/bgp_updgrp_adv.c b/bgpd/bgp_updgrp_adv.c
index c2f2d4f435..b48484c095 100644
--- a/bgpd/bgp_updgrp_adv.c
+++ b/bgpd/bgp_updgrp_adv.c
@@ -1240,9 +1240,6 @@ void subgroup_default_originate(struct update_subgroup *subgrp)
 			 * Route-map is configured but undefined or empty.
 			 * Treat as implicit deny - withdraw the default.
 			 */
-			if (!do_withdraw &&
-			    !CHECK_FLAG(subgrp->sflags, SUBGRP_STATUS_DEFAULT_ORIGINATE))
-				SET_FLAG(subgrp->sflags, SUBGRP_STATUS_DEFAULT_ORIGINATE);
 			do_withdraw = true;
 		}
 	}
@@ -1322,18 +1319,14 @@ rmap_done:;
 	}

 	if (do_withdraw) {
-		/* Withdraw the synthesized default.  Always send the withdraw
-		 * packet - the caller may have cleared the flag before calling
-		 * to force re-evaluation.
-		 *
-		 * TBD: Track the synthesized route's attributes so we can check
-		 * the flag properly and avoid unnecessary withdraw packets.
-		 */
-		subgroup_default_withdraw_packet(subgrp);
-		UNSET_FLAG(subgrp->sflags, SUBGRP_STATUS_DEFAULT_ORIGINATE);
+		/* Withdraw the synthesized default if it was previously sent. */
+		if (CHECK_FLAG(subgrp->sflags, SUBGRP_STATUS_DEFAULT_ORIGINATE)) {
+			subgroup_default_withdraw_packet(subgrp);
+			UNSET_FLAG(subgrp->sflags, SUBGRP_STATUS_DEFAULT_ORIGINATE);
+		}
 	} else {
-		if (!CHECK_FLAG(subgrp->sflags,
-				SUBGRP_STATUS_DEFAULT_ORIGINATE)) {
+		/* TBD: compare attributes to avoid sending redundant UPDATEs. */
+		{
 			struct attr attr_copy;

 			/*
@@ -1356,17 +1349,13 @@ rmap_done:;
 				p.prefixlen = 0;
 				if (bgp_route_map_setting_only(bgp, rmap, PEER_RMAP_TYPE_DEFAULT,
 							       &p, &attr_copy) == RMAP_DENYMATCH) {
-					/*
-					 * Route-map denied - withdraw default.
-					 * The flag may have been cleared by callers,
-					 * so withdraw unconditionally. This may send
-					 * an unnecessary withdraw, but a deny-only
-					 * route-map is not a practical scenario.
-					 */
+					/* Route-map denied - withdraw default if previously sent. */
 					bgp_attr_flush(&attr_copy);
 					aspath_unintern(&aspath);
-					subgroup_default_withdraw_packet(subgrp);
-					UNSET_FLAG(subgrp->sflags, SUBGRP_STATUS_DEFAULT_ORIGINATE);
+					if (CHECK_FLAG(subgrp->sflags, SUBGRP_STATUS_DEFAULT_ORIGINATE)) {
+						subgroup_default_withdraw_packet(subgrp);
+						UNSET_FLAG(subgrp->sflags, SUBGRP_STATUS_DEFAULT_ORIGINATE);
+					}
 					return;
 				}
 				/* Clear reflection attributes - default-originate
diff --git a/bgpd/bgpd.c b/bgpd/bgpd.c
index 723146905e..b091121137 100644
--- a/bgpd/bgpd.c
+++ b/bgpd/bgpd.c
@@ -6716,13 +6716,10 @@ int peer_default_originate_set(struct peer *peer, afi_t afi, safi_t safi,
 {
 	struct peer *member;
 	struct listnode *node, *nnode;
-	struct update_subgroup *subgrp;

 	/* Set flag and configuration on peer. */
 	peer_af_flag_set(peer, afi, safi, PEER_FLAG_DEFAULT_ORIGINATE);

-	subgrp = peer_subgroup(peer, afi, safi);
-
 	if (rmap) {
 		if (!peer->bgp->rmap_def_originate_eval_timer)
 			peer->bgp->rmap_def_originate_eval_timer =
@@ -6739,17 +6736,6 @@ int peer_default_originate_set(struct peer *peer, afi_t afi, safi_t safi,
 				      peer->default_rmap[afi][safi].name);
 			}

-			/*
-			 * When there is a change in route-map policy,
-			 * this flow gets triggered. Since, the default
-			 * route is already originated, the flag is set.
-			 * The flag should be unset here,
-			 * to trigger the flow of sending update message.
-			 */
-			if (subgrp)
-				UNSET_FLAG(subgrp->sflags,
-					   SUBGRP_STATUS_DEFAULT_ORIGINATE);
-
 			route_map_counter_decrement(map);
 			peer->default_rmap[afi][safi].name =
 				XSTRDUP(MTYPE_ROUTE_MAP_NAME, rmap);
@@ -6766,15 +6752,6 @@ int peer_default_originate_set(struct peer *peer, afi_t afi, safi_t safi,
 			      peer->default_rmap[afi][safi].name);
 		}

-		/*
-		 * This is triggered in case of route-map deletion.
-		 * The flag needs to be unset, to trigger the flow
-		 * of sending an update message.
-		 */
-		if (subgrp)
-			UNSET_FLAG(subgrp->sflags,
-				   SUBGRP_STATUS_DEFAULT_ORIGINATE);
-
 		route_map_counter_decrement(map);
 		peer->default_rmap[afi][safi].name = NULL;
 		peer->default_rmap[afi][safi].map = NULL;
@@ -9484,8 +9461,6 @@ int peer_clear(struct peer *peer, struct listnode **nnode)
 int peer_clear_soft(struct peer *peer, afi_t afi, safi_t safi,
 		    enum bgp_clear_type stype)
 {
-	struct peer_af *paf;
-
 	if (!peer_established(peer->connection))
 		return 0;

@@ -9494,17 +9469,8 @@ int peer_clear_soft(struct peer *peer, afi_t afi, safi_t safi,

 	peer->rtt = sockopt_tcp_rtt(peer->connection->fd);

-	if (stype == BGP_CLEAR_SOFT_OUT || stype == BGP_CLEAR_SOFT_BOTH) {
-		/* Clear the "neighbor x.x.x.x default-originate" flag */
-		paf = peer_af_find(peer, afi, safi);
-		if (paf && paf->subgroup
-		    && CHECK_FLAG(paf->subgroup->sflags,
-				  SUBGRP_STATUS_DEFAULT_ORIGINATE))
-			UNSET_FLAG(paf->subgroup->sflags,
-				   SUBGRP_STATUS_DEFAULT_ORIGINATE);
-
+	if (stype == BGP_CLEAR_SOFT_OUT || stype == BGP_CLEAR_SOFT_BOTH)
 		bgp_announce_route(peer, afi, safi, true);
-	}

 	if (stype == BGP_CLEAR_SOFT_IN_ORF_PREFIX) {
 		if (CHECK_FLAG(peer->af_cap[afi][safi],