realtek: l3: keep a trap whose source list is incomplete

A prefix with source-specific IPv6 routes gets one trap row, which
keeps a list of their sources and goes once the list is empty and no
route without a source is left. When the allocation for a source
failed, that source was left out: once the list was empty the row
went, or a route without a source took it over, though the kernel
still had the source-specific route the row was trapping for.

Mark the row when a source could not be tracked, count it as still
standing for sources, and ask for a FIB replay, which builds the list
again from scratch.

Tested on a Hasivo S1100W-8XGT-SE (RTL9303) with L3 offload enabled,
through a bench-only debugfs counter that fails the next source
allocation: under an IPv6 /32 forwarding in hardware, a /48 with a
source-specific route via another host and a source-specific blackhole
for the sender, whose tracking fails. Once the first is deleted, the
kernel drops the sender's echoes: without this change the trap row
goes and the /32 forwards all 5 to the host, with it the row stays, the
FIB is replayed and none arrive.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Gennaro Cimmino <gcimmino@rayonra.net>
Link: https://github.com/openwrt/openwrt/pull/25623
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
This commit is contained in:
Gennaro Cimmino authored and Markus Stockhausen committed 2026-10-05 11:46:22 +02:00
1 parent 580a493cb9
commit 47b46ced13
2 files changed
+22 -4

No files matched your search

@@ -2235,6 +2235,14 @@ static struct otto_l3_route_src *otto_l3_route_src_find(struct otto_l3_route *r,
return NULL;
}
/* A source that could not be tracked is still a route the row stands for */
static bool otto_l3_route_has_srcs(struct otto_l3_route *r)
{
return !list_empty(&r->srcs) || r->srcs_incomplete;
}
static void otto_l3_resync_request(struct otto_l3_ctrl *ctrl);
/* A source-specific route matches only the packets from its source, and the
* hardware matches on the destination alone, so the destination traps for as
* long as one of them remains. The trap row counts the members of each source
@@ -2278,8 +2286,17 @@ static int otto_l3_fib_add_v6_src(struct otto_l3_ctrl *ctrl, struct fib6_info *r
s = otto_l3_route_src_find(route, &rt->fib6_src);
if (!s) {
s = kzalloc(sizeof(*s), GFP_KERNEL);
if (!s)
if (!s) {
/* The row keeps trapping whatever the list says; a replay
* builds the list again
*/
route->srcs_incomplete = true;
dev_err(ctrl->dev, "%pI6c/%d from %pI6c/%d: no memory to track the source\n",
&rt->fib6_dst.addr, rt->fib6_dst.plen,
&rt->fib6_src.addr, rt->fib6_src.plen);
otto_l3_resync_request(ctrl);
return -ENOMEM;
}
s->addr = rt->fib6_src.addr;
s->plen = rt->fib6_src.plen;
list_add_tail(&s->list, &route->srcs);
@@ -2312,7 +2329,7 @@ static int otto_l3_fib_del_v6_src(struct otto_l3_ctrl *ctrl, struct fib6_info *r
list_del(&s->list);
kfree(s);
if (list_empty(&route->srcs) && !route->members)
if (!otto_l3_route_has_srcs(route) && !route->members)
otto_l3_route_teardown(ctrl, route);
return 0;
@@ -2389,7 +2406,7 @@ static int otto_l3_fib_add_v6(struct otto_l3_ctrl *ctrl, struct fib6_entry_notif
*/
route = otto_l3_route_find(ctrl, rt->fib6_table->tb6_id, ROUTE_TYPE_IP6UC,
0, &rt->fib6_dst.addr, rt->fib6_dst.plen);
if (route && !list_empty(&route->srcs)) {
if (route && otto_l3_route_has_srcs(route)) {
route->members = members;
return 0;
}
@@ -2507,7 +2524,7 @@ static int otto_l3_fib_del_v6(struct otto_l3_ctrl *ctrl, struct fib6_entry_notif
if (route && ipv6_addr_any(&route->gw_ip)) {
if (route->members > members)
route->members -= members;
else if (!list_empty(&route->srcs))
else if (otto_l3_route_has_srcs(route))
route->members = 0;
else
otto_l3_route_teardown(ctrl, route);
@@ -87,6 +87,7 @@ struct otto_l3_route {
int row; /* Row it occupies in the prefix route table */
unsigned int members; /* FIB entries a trap row stands for */
struct list_head srcs; /* source-specific routes a trap row stands for */
bool srcs_incomplete; /* a source could not be tracked */
struct rhlist_head linkage;
struct list_head list; /* all routes, for lookups by destination */
u32 tb_id; /* routing table the route came from */