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