mirror of
				https://github.com/vyos/vyos-build.git
				synced 2025-10-01 20:28:40 +02:00 
			
		
		
		
	T6962: frr: fix wrong kernel routes updates
The patch 0007-zebra-remove-kernel-route-on-last-address-deletion.patch fixes root cause of the issue: Zebra didn't do anything on last IPv4 address deletion though kernel in such case deletes all IPv4 routes. FRR PR: https://github.com/FRRouting/frr/pull/19564
This commit is contained in:
		
							parent
							
								
									1a6747b038
								
							
						
					
					
						commit
						4fbffe2efd
					
				| @ -0,0 +1,214 @@ | ||||
| From 1ba7319656c548075e466a8ad1d068f7b1d66756 Mon Sep 17 00:00:00 2001 | ||||
| From: Kyrylo Yatsenko <hedrok@gmail.com> | ||||
| Date: Thu, 11 Sep 2025 17:43:08 +0300 | ||||
| Subject: [PATCH] zebra: remove kernel route on last address deletion | ||||
| 
 | ||||
| Fixes issue #13561 | ||||
| 
 | ||||
| Linux kernel deletes IPv4 routes when last interface IPv4 address | ||||
| is deleted, but intentionally doesn't send RTM_DELROUTE in this case. | ||||
| 
 | ||||
| FRR has function rib_update_handle_kernel_route_down_possibility | ||||
| that handles setting interface down, but not removal of last address. | ||||
| 
 | ||||
| To fix the situation: | ||||
| 
 | ||||
| * Add RIB_UPDATE_KERNEL_LAST_ADDRESS_DELETED to enum rib_update_event | ||||
| * In zebra_if_addr_update_ctx make more specific check that last | ||||
|   address is deleted and trigger RIB_UPDATE_KERNEL_LAST_ADDRESS_DELETED | ||||
|   instead of RIB_UPDATE_KERNEL in this case. If it was not last address, | ||||
|   don't emit any RIB_UPDATE. | ||||
| * Call rib_update_handle_kernel_route_down_possibility not only | ||||
|   for event RIB_UPDATE_INTERFACE_DOWN, but also for | ||||
|   RIB_UPDATE_KERNEL_LAST_ADDRESS_DELETED. | ||||
| * Change rib_update_handle_kernel_route_down_possibility not to | ||||
|   consider IPv4 route alive when interface is up, but there | ||||
|   are no IPv4 addresses left. | ||||
| 
 | ||||
| Relevant kernel code: | ||||
| 
 | ||||
| * net/ipv4/fib_frontend.c fib_inetaddr_event | ||||
| * net/ipv4/fib_semantics.c fib_sync_down_dev | ||||
| 
 | ||||
| Comment from kernel networking author that RTM_DELROUTE isn't sent | ||||
| intentionally: | ||||
| 
 | ||||
| https://bugzilla.kernel.org/show_bug.cgi?id=207089 | ||||
| Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com> | ||||
| ---
 | ||||
|  lib/if.c          | 12 +++++++++++ | ||||
|  lib/if.h          |  1 + | ||||
|  zebra/interface.c |  8 ++++--- | ||||
|  zebra/rib.h       |  1 + | ||||
|  zebra/zebra_rib.c | 55 +++++++++++++++++++++++++++++++++++++++-------- | ||||
|  5 files changed, 65 insertions(+), 12 deletions(-) | ||||
| 
 | ||||
| diff --git a/lib/if.c b/lib/if.c
 | ||||
| index 8f15230f23..2402d17992 100644
 | ||||
| --- a/lib/if.c
 | ||||
| +++ b/lib/if.c
 | ||||
| @@ -899,6 +899,18 @@ nbr_connected_log(struct nbr_connected *connected, char *str)
 | ||||
|  	zlog_info("%s", logbuf); | ||||
|  } | ||||
|   | ||||
| +/* Return true if there is at least one connected address in the given family  */
 | ||||
| +bool if_has_connected_with_family(struct interface *ifp, int family)
 | ||||
| +{
 | ||||
| +	struct connected *connected;
 | ||||
| +
 | ||||
| +	frr_each (if_connected, ifp->connected, connected)
 | ||||
| +		if (connected->address->family == family)
 | ||||
| +			return true;
 | ||||
| +
 | ||||
| +	return false;
 | ||||
| +}
 | ||||
| +
 | ||||
|  /* count the number of connected addresses that are in the given family */ | ||||
|  unsigned int connected_count_by_family(struct interface *ifp, int family) | ||||
|  { | ||||
| diff --git a/lib/if.h b/lib/if.h
 | ||||
| index 0dc56bd210..233213ca84 100644
 | ||||
| --- a/lib/if.h
 | ||||
| +++ b/lib/if.h
 | ||||
| @@ -606,6 +606,7 @@ extern struct connected *connected_lookup_prefix(struct interface *ifp,
 | ||||
|  						 const struct prefix *p); | ||||
|  extern struct connected *connected_lookup_prefix_exact(struct interface *ifp, | ||||
|  						       const struct prefix *p); | ||||
| +extern bool if_has_connected_with_family(struct interface *ifp, int family);
 | ||||
|  extern unsigned int connected_count_by_family(struct interface *ifp, int family); | ||||
|  extern struct nbr_connected *nbr_connected_new(void); | ||||
|  extern void nbr_connected_free(struct nbr_connected *connected); | ||||
| diff --git a/zebra/interface.c b/zebra/interface.c
 | ||||
| index e2c2b4a80c..48343355ba 100644
 | ||||
| --- a/zebra/interface.c
 | ||||
| +++ b/zebra/interface.c
 | ||||
| @@ -1313,11 +1313,13 @@ static void zebra_if_addr_update_ctx(struct zebra_dplane_ctx *ctx,
 | ||||
|  	} | ||||
|   | ||||
|  	/* | ||||
| -	 * Linux kernel does not send route delete on interface down/addr del
 | ||||
| +	 * Linux kernel does not send route delete on interface down/last addr del
 | ||||
|  	 * so we have to re-process routes it owns (i.e. kernel routes) | ||||
| +	 * See rib_update_handle_kernel_route_down_possibility for more details
 | ||||
|  	 */ | ||||
| -	if (op != DPLANE_OP_INTF_ADDR_ADD)
 | ||||
| -		rib_update(RIB_UPDATE_KERNEL);
 | ||||
| +	if (op != DPLANE_OP_INTF_ADDR_ADD && addr->family == AF_INET &&
 | ||||
| +	    !if_has_connected_with_family(ifp, AF_INET))
 | ||||
| +		rib_update(RIB_UPDATE_KERNEL_LAST_ADDRESS_DELETED);
 | ||||
|  } | ||||
|   | ||||
|  static void zebra_if_update_ctx(struct zebra_dplane_ctx *ctx, | ||||
| diff --git a/zebra/rib.h b/zebra/rib.h
 | ||||
| index 5fedb07335..2f1954de9d 100644
 | ||||
| --- a/zebra/rib.h
 | ||||
| +++ b/zebra/rib.h
 | ||||
| @@ -330,6 +330,7 @@ enum rib_update_event {
 | ||||
|  	RIB_UPDATE_KERNEL, | ||||
|  	RIB_UPDATE_RMAP_CHANGE, | ||||
|  	RIB_UPDATE_OTHER, | ||||
| +	RIB_UPDATE_KERNEL_LAST_ADDRESS_DELETED,
 | ||||
|  	RIB_UPDATE_MAX | ||||
|  }; | ||||
|  void rib_update_finish(void); | ||||
| diff --git a/zebra/zebra_rib.c b/zebra/zebra_rib.c
 | ||||
| index 752f8282df..eaba976265 100644
 | ||||
| --- a/zebra/zebra_rib.c
 | ||||
| +++ b/zebra/zebra_rib.c
 | ||||
| @@ -4465,6 +4465,9 @@ static const char *rib_update_event2str(enum rib_update_event event)
 | ||||
|  	case RIB_UPDATE_OTHER: | ||||
|  		ret = "RIB_UPDATE_OTHER"; | ||||
|  		break; | ||||
| +	case RIB_UPDATE_KERNEL_LAST_ADDRESS_DELETED:
 | ||||
| +		ret = "RIB_UPDATE_KERNEL_LAST_ADDRESS_DELETED";
 | ||||
| +		break;
 | ||||
|  	case RIB_UPDATE_MAX: | ||||
|  		break; | ||||
|  	} | ||||
| @@ -4487,13 +4490,45 @@ rib_update_handle_kernel_route_down_possibility(struct route_node *rn,
 | ||||
|  	bool alive = false; | ||||
|   | ||||
|  	for (ALL_NEXTHOPS(re->nhe->nhg, nexthop)) { | ||||
| +		if (!nexthop->ifindex || nexthop->type == NEXTHOP_TYPE_BLACKHOLE) {
 | ||||
| +			/* blackhole nexthops have no interfaces */
 | ||||
| +			alive = true;
 | ||||
| +			break;
 | ||||
| +		}
 | ||||
| +
 | ||||
|  		struct interface *ifp = if_lookup_by_index(nexthop->ifindex, | ||||
|  							   nexthop->vrf_id); | ||||
|   | ||||
| -		if ((ifp && if_is_up(ifp)) || nexthop->type == NEXTHOP_TYPE_BLACKHOLE) {
 | ||||
| +		if (!ifp || !if_is_up(ifp)) {
 | ||||
| +			/* interface is not up, not alive */
 | ||||
| +			continue;
 | ||||
| +		}
 | ||||
| +
 | ||||
| +		/*
 | ||||
| +		 * Kernel deletes IPv4 routes from interface when last IPv4 address is deleted
 | ||||
| +		 * It is not important whether nexthop is reachable after address
 | ||||
| +		 * is deleted - route is deleted only after last address deletion.
 | ||||
| +		 *
 | ||||
| +		 * See net/ipv4/fib_frontend.c fib_inetaddr_event,
 | ||||
| +		 * net/ipv4/fib_semantics.c fib_sync_down_dev
 | ||||
| +		 */
 | ||||
| +
 | ||||
| +		/* If not IPv4, set alive
 | ||||
| +		 * Check rn->p->family: for nexthop->type=NEXTHOP_TYPE_IFINDEX it
 | ||||
| +		 * depends on destination only
 | ||||
| +		 */
 | ||||
| +		if (rn->p.family != AF_INET) {
 | ||||
|  			alive = true; | ||||
|  			break; | ||||
|  		} | ||||
| +
 | ||||
| +		/* Check if there are any IPv4 addresses connected, if yes - set alive */
 | ||||
| +		if (if_has_connected_with_family(ifp, AF_INET)) {
 | ||||
| +			alive = true;
 | ||||
| +			break;
 | ||||
| +		}
 | ||||
| +
 | ||||
| +		/* Otherwise kernel deletes the route */
 | ||||
|  	} | ||||
|   | ||||
|  	if (!alive) { | ||||
| @@ -4518,8 +4553,9 @@ static void rib_update_route_node(struct route_node *rn, int type,
 | ||||
|  	bool re_changed = false; | ||||
|   | ||||
|  	RNODE_FOREACH_RE_SAFE (rn, re, next) { | ||||
| -		if (event == RIB_UPDATE_INTERFACE_DOWN && type == re->type &&
 | ||||
| -		    type == ZEBRA_ROUTE_KERNEL)
 | ||||
| +		if ((event == RIB_UPDATE_INTERFACE_DOWN ||
 | ||||
| +		     event == RIB_UPDATE_KERNEL_LAST_ADDRESS_DELETED) &&
 | ||||
| +		    type == re->type && type == ZEBRA_ROUTE_KERNEL)
 | ||||
|  			rib_update_handle_kernel_route_down_possibility(rn, re); | ||||
|  		else if (type == ZEBRA_ROUTE_ALL || type == re->type) { | ||||
|  			SET_FLAG(re->status, ROUTE_ENTRY_CHANGED); | ||||
| @@ -4562,17 +4598,18 @@ void rib_update_table(struct route_table *table, enum rib_update_event event,
 | ||||
|  		 * If we are looking at a route node and the node | ||||
|  		 * has already been queued  we don't | ||||
|  		 * need to queue it up again, unless it is | ||||
| -		 * an interface down event as that we need
 | ||||
| -		 * to process this no matter what.
 | ||||
| +		 * an interface down event or last address down event
 | ||||
| +		 * as that we need
 | ||||
| +		 * to process these no matter what.
 | ||||
|  		 */ | ||||
| -		if (rn->info &&
 | ||||
| -		    CHECK_FLAG(rib_dest_from_rnode(rn)->flags,
 | ||||
| -			       RIB_ROUTE_ANY_QUEUED) &&
 | ||||
| -		    event != RIB_UPDATE_INTERFACE_DOWN)
 | ||||
| +		if (rn->info && CHECK_FLAG(rib_dest_from_rnode(rn)->flags, RIB_ROUTE_ANY_QUEUED) &&
 | ||||
| +		    event != RIB_UPDATE_INTERFACE_DOWN &&
 | ||||
| +		    event != RIB_UPDATE_KERNEL_LAST_ADDRESS_DELETED)
 | ||||
|  			continue; | ||||
|   | ||||
|  		switch (event) { | ||||
|  		case RIB_UPDATE_INTERFACE_DOWN: | ||||
| +		case RIB_UPDATE_KERNEL_LAST_ADDRESS_DELETED:
 | ||||
|  		case RIB_UPDATE_KERNEL: | ||||
|  			rib_update_route_node(rn, ZEBRA_ROUTE_KERNEL, event); | ||||
|  			break; | ||||
| -- 
 | ||||
| 2.50.1 | ||||
| 
 | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user