Skip to content

Commit d7c3843

Browse files
authored
Merge pull request #1029 from hedrok/T6962-frr-zebra-kernel-routes
T6962: frr: fix wrong kernel routes updates
2 parents d2f3cd8 + 4fbffe2 commit d7c3843

1 file changed

Lines changed: 214 additions & 0 deletions

File tree

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
From 1ba7319656c548075e466a8ad1d068f7b1d66756 Mon Sep 17 00:00:00 2001
2+
From: Kyrylo Yatsenko <hedrok@gmail.com>
3+
Date: Thu, 11 Sep 2025 17:43:08 +0300
4+
Subject: [PATCH] zebra: remove kernel route on last address deletion
5+
6+
Fixes issue #13561
7+
8+
Linux kernel deletes IPv4 routes when last interface IPv4 address
9+
is deleted, but intentionally doesn't send RTM_DELROUTE in this case.
10+
11+
FRR has function rib_update_handle_kernel_route_down_possibility
12+
that handles setting interface down, but not removal of last address.
13+
14+
To fix the situation:
15+
16+
* Add RIB_UPDATE_KERNEL_LAST_ADDRESS_DELETED to enum rib_update_event
17+
* In zebra_if_addr_update_ctx make more specific check that last
18+
address is deleted and trigger RIB_UPDATE_KERNEL_LAST_ADDRESS_DELETED
19+
instead of RIB_UPDATE_KERNEL in this case. If it was not last address,
20+
don't emit any RIB_UPDATE.
21+
* Call rib_update_handle_kernel_route_down_possibility not only
22+
for event RIB_UPDATE_INTERFACE_DOWN, but also for
23+
RIB_UPDATE_KERNEL_LAST_ADDRESS_DELETED.
24+
* Change rib_update_handle_kernel_route_down_possibility not to
25+
consider IPv4 route alive when interface is up, but there
26+
are no IPv4 addresses left.
27+
28+
Relevant kernel code:
29+
30+
* net/ipv4/fib_frontend.c fib_inetaddr_event
31+
* net/ipv4/fib_semantics.c fib_sync_down_dev
32+
33+
Comment from kernel networking author that RTM_DELROUTE isn't sent
34+
intentionally:
35+
36+
https://bugzilla.kernel.org/show_bug.cgi?id=207089
37+
Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com>
38+
---
39+
lib/if.c | 12 +++++++++++
40+
lib/if.h | 1 +
41+
zebra/interface.c | 8 ++++---
42+
zebra/rib.h | 1 +
43+
zebra/zebra_rib.c | 55 +++++++++++++++++++++++++++++++++++++++--------
44+
5 files changed, 65 insertions(+), 12 deletions(-)
45+
46+
diff --git a/lib/if.c b/lib/if.c
47+
index 8f15230f23..2402d17992 100644
48+
--- a/lib/if.c
49+
+++ b/lib/if.c
50+
@@ -899,6 +899,18 @@ nbr_connected_log(struct nbr_connected *connected, char *str)
51+
zlog_info("%s", logbuf);
52+
}
53+
54+
+/* Return true if there is at least one connected address in the given family */
55+
+bool if_has_connected_with_family(struct interface *ifp, int family)
56+
+{
57+
+ struct connected *connected;
58+
+
59+
+ frr_each (if_connected, ifp->connected, connected)
60+
+ if (connected->address->family == family)
61+
+ return true;
62+
+
63+
+ return false;
64+
+}
65+
+
66+
/* count the number of connected addresses that are in the given family */
67+
unsigned int connected_count_by_family(struct interface *ifp, int family)
68+
{
69+
diff --git a/lib/if.h b/lib/if.h
70+
index 0dc56bd210..233213ca84 100644
71+
--- a/lib/if.h
72+
+++ b/lib/if.h
73+
@@ -606,6 +606,7 @@ extern struct connected *connected_lookup_prefix(struct interface *ifp,
74+
const struct prefix *p);
75+
extern struct connected *connected_lookup_prefix_exact(struct interface *ifp,
76+
const struct prefix *p);
77+
+extern bool if_has_connected_with_family(struct interface *ifp, int family);
78+
extern unsigned int connected_count_by_family(struct interface *ifp, int family);
79+
extern struct nbr_connected *nbr_connected_new(void);
80+
extern void nbr_connected_free(struct nbr_connected *connected);
81+
diff --git a/zebra/interface.c b/zebra/interface.c
82+
index e2c2b4a80c..48343355ba 100644
83+
--- a/zebra/interface.c
84+
+++ b/zebra/interface.c
85+
@@ -1313,11 +1313,13 @@ static void zebra_if_addr_update_ctx(struct zebra_dplane_ctx *ctx,
86+
}
87+
88+
/*
89+
- * Linux kernel does not send route delete on interface down/addr del
90+
+ * Linux kernel does not send route delete on interface down/last addr del
91+
* so we have to re-process routes it owns (i.e. kernel routes)
92+
+ * See rib_update_handle_kernel_route_down_possibility for more details
93+
*/
94+
- if (op != DPLANE_OP_INTF_ADDR_ADD)
95+
- rib_update(RIB_UPDATE_KERNEL);
96+
+ if (op != DPLANE_OP_INTF_ADDR_ADD && addr->family == AF_INET &&
97+
+ !if_has_connected_with_family(ifp, AF_INET))
98+
+ rib_update(RIB_UPDATE_KERNEL_LAST_ADDRESS_DELETED);
99+
}
100+
101+
static void zebra_if_update_ctx(struct zebra_dplane_ctx *ctx,
102+
diff --git a/zebra/rib.h b/zebra/rib.h
103+
index 5fedb07335..2f1954de9d 100644
104+
--- a/zebra/rib.h
105+
+++ b/zebra/rib.h
106+
@@ -330,6 +330,7 @@ enum rib_update_event {
107+
RIB_UPDATE_KERNEL,
108+
RIB_UPDATE_RMAP_CHANGE,
109+
RIB_UPDATE_OTHER,
110+
+ RIB_UPDATE_KERNEL_LAST_ADDRESS_DELETED,
111+
RIB_UPDATE_MAX
112+
};
113+
void rib_update_finish(void);
114+
diff --git a/zebra/zebra_rib.c b/zebra/zebra_rib.c
115+
index 752f8282df..eaba976265 100644
116+
--- a/zebra/zebra_rib.c
117+
+++ b/zebra/zebra_rib.c
118+
@@ -4465,6 +4465,9 @@ static const char *rib_update_event2str(enum rib_update_event event)
119+
case RIB_UPDATE_OTHER:
120+
ret = "RIB_UPDATE_OTHER";
121+
break;
122+
+ case RIB_UPDATE_KERNEL_LAST_ADDRESS_DELETED:
123+
+ ret = "RIB_UPDATE_KERNEL_LAST_ADDRESS_DELETED";
124+
+ break;
125+
case RIB_UPDATE_MAX:
126+
break;
127+
}
128+
@@ -4487,13 +4490,45 @@ rib_update_handle_kernel_route_down_possibility(struct route_node *rn,
129+
bool alive = false;
130+
131+
for (ALL_NEXTHOPS(re->nhe->nhg, nexthop)) {
132+
+ if (!nexthop->ifindex || nexthop->type == NEXTHOP_TYPE_BLACKHOLE) {
133+
+ /* blackhole nexthops have no interfaces */
134+
+ alive = true;
135+
+ break;
136+
+ }
137+
+
138+
struct interface *ifp = if_lookup_by_index(nexthop->ifindex,
139+
nexthop->vrf_id);
140+
141+
- if ((ifp && if_is_up(ifp)) || nexthop->type == NEXTHOP_TYPE_BLACKHOLE) {
142+
+ if (!ifp || !if_is_up(ifp)) {
143+
+ /* interface is not up, not alive */
144+
+ continue;
145+
+ }
146+
+
147+
+ /*
148+
+ * Kernel deletes IPv4 routes from interface when last IPv4 address is deleted
149+
+ * It is not important whether nexthop is reachable after address
150+
+ * is deleted - route is deleted only after last address deletion.
151+
+ *
152+
+ * See net/ipv4/fib_frontend.c fib_inetaddr_event,
153+
+ * net/ipv4/fib_semantics.c fib_sync_down_dev
154+
+ */
155+
+
156+
+ /* If not IPv4, set alive
157+
+ * Check rn->p->family: for nexthop->type=NEXTHOP_TYPE_IFINDEX it
158+
+ * depends on destination only
159+
+ */
160+
+ if (rn->p.family != AF_INET) {
161+
alive = true;
162+
break;
163+
}
164+
+
165+
+ /* Check if there are any IPv4 addresses connected, if yes - set alive */
166+
+ if (if_has_connected_with_family(ifp, AF_INET)) {
167+
+ alive = true;
168+
+ break;
169+
+ }
170+
+
171+
+ /* Otherwise kernel deletes the route */
172+
}
173+
174+
if (!alive) {
175+
@@ -4518,8 +4553,9 @@ static void rib_update_route_node(struct route_node *rn, int type,
176+
bool re_changed = false;
177+
178+
RNODE_FOREACH_RE_SAFE (rn, re, next) {
179+
- if (event == RIB_UPDATE_INTERFACE_DOWN && type == re->type &&
180+
- type == ZEBRA_ROUTE_KERNEL)
181+
+ if ((event == RIB_UPDATE_INTERFACE_DOWN ||
182+
+ event == RIB_UPDATE_KERNEL_LAST_ADDRESS_DELETED) &&
183+
+ type == re->type && type == ZEBRA_ROUTE_KERNEL)
184+
rib_update_handle_kernel_route_down_possibility(rn, re);
185+
else if (type == ZEBRA_ROUTE_ALL || type == re->type) {
186+
SET_FLAG(re->status, ROUTE_ENTRY_CHANGED);
187+
@@ -4562,17 +4598,18 @@ void rib_update_table(struct route_table *table, enum rib_update_event event,
188+
* If we are looking at a route node and the node
189+
* has already been queued we don't
190+
* need to queue it up again, unless it is
191+
- * an interface down event as that we need
192+
- * to process this no matter what.
193+
+ * an interface down event or last address down event
194+
+ * as that we need
195+
+ * to process these no matter what.
196+
*/
197+
- if (rn->info &&
198+
- CHECK_FLAG(rib_dest_from_rnode(rn)->flags,
199+
- RIB_ROUTE_ANY_QUEUED) &&
200+
- event != RIB_UPDATE_INTERFACE_DOWN)
201+
+ if (rn->info && CHECK_FLAG(rib_dest_from_rnode(rn)->flags, RIB_ROUTE_ANY_QUEUED) &&
202+
+ event != RIB_UPDATE_INTERFACE_DOWN &&
203+
+ event != RIB_UPDATE_KERNEL_LAST_ADDRESS_DELETED)
204+
continue;
205+
206+
switch (event) {
207+
case RIB_UPDATE_INTERFACE_DOWN:
208+
+ case RIB_UPDATE_KERNEL_LAST_ADDRESS_DELETED:
209+
case RIB_UPDATE_KERNEL:
210+
rib_update_route_node(rn, ZEBRA_ROUTE_KERNEL, event);
211+
break;
212+
--
213+
2.50.1
214+

0 commit comments

Comments
 (0)