realtek: l3: keep main /32s out of a local address's entry

The host table holds one entry per IPv4 address, found by the address
alone. A /32 in main for an address of the switch shared that entry
with the local route: whichever wrote last owned it, a neighbour update
of the main route turned the local trap into forwarding, and deleting
the main route took the entry from the local one. The kernel looks the
local table up first, so the address is the switch's for as long as it
holds it.

While a local route holds an address, a host route from main for it
leaves the entry alone - on add, neighbour update, trap and delete -
and keeps its state in memory. When the local route goes, the main
route writes its entry back as it last stood.

Tested on a Hasivo S1100W-8XGT-SE (RTL9303) with L3 offload enabled,
main against main with this and the previous commit. A /32 route
through a gateway in hardware, then the same address added to the
switch, and 5 echoes to it:
- after a neighbour update of the gateway, main turned the address's
  entry into forwarding and sent all 5 to the gateway, unanswered;
  now the switch answers all 5.
- with a /24 through the gateway covering it and the /32 route
  deleted, main took the address's entry away and the /24 forwarded
  all 5; now the switch answers all 5.
- with a /24 through another gateway covering it and the address
  deleted, main took the /32 route's entry with it and the /24 sent
  all 5 to that other gateway; now the /32 route forwards all 5 to
  its own.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Gennaro Cimmino <gcimmino@rayonra.net>
Link: https://github.com/openwrt/openwrt/pull/25598
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
This commit is contained in:
Gennaro Cimmino authored and Markus Stockhausen committed 2026-10-04 18:47:46 +02:00
1 parent d3301f3ea0
commit df85424b93
1 file changed
+63 -2
@@ -1186,6 +1186,46 @@ static const char *otto_l3_route_dst(struct otto_l3_route *r, char *buf, size_t
return buf;
}
/* The host table holds one entry per address, and the local table is looked up
* before main: while an address is the switch's own, the entry is the local
* route's, and a host route from main for that address keeps out of it.
*/
static bool otto_l3_host_shadowed(struct otto_l3_ctrl *ctrl, struct otto_l3_route *r)
{
struct otto_l3_route *q;
if (!r->is_host_route || r->tb_id == RT_TABLE_LOCAL)
return false;
list_for_each_entry(q, &ctrl->routes_list, list)
if (q != r && q->is_host_route && q->tb_id == RT_TABLE_LOCAL &&
q->attr.type == r->attr.type && q->dst_ip == r->dst_ip)
return true;
return false;
}
/* The address is the switch's no longer: a host route from main for it gets
* the entry back, as it last wrote it
*/
static void otto_l3_host_unshadow(struct otto_l3_ctrl *ctrl, struct otto_l3_route *r)
{
struct otto_l3_route *q;
int slot;
list_for_each_entry(q, &ctrl->routes_list, list) {
if (q == r || !q->is_host_route || q->tb_id == RT_TABLE_LOCAL ||
q->attr.type != r->attr.type || q->dst_ip != r->dst_ip ||
!q->attr.valid)
continue;
slot = ctrl->cfg->find_slot(ctrl, q, false);
if (slot >= 0)
ctrl->cfg->host_route_write(ctrl, slot, q);
return;
}
}
static void otto_l3_route_update_hw(struct otto_l3_ctrl *ctrl, struct otto_l3_route *r,
u64 mac)
{
@@ -1249,7 +1289,10 @@ static void otto_l3_route_update_hw(struct otto_l3_ctrl *ctrl, struct otto_l3_ro
r->pr.dip_m = inet_make_mask(r->prefix_len);
}
if (r->is_host_route) {
if (otto_l3_host_shadowed(ctrl, r)) {
dev_dbg(ctrl->dev, "%pI4 is an address of the switch, its entry stays\n",
&r->dst_ip);
} else if (r->is_host_route) {
int slot = ctrl->cfg->find_slot(ctrl, r, true);
if (slot < 0)
@@ -1334,6 +1377,9 @@ static void otto_l3_route_trap_hw(struct otto_l3_ctrl *ctrl, struct otto_l3_rout
r->attr.ttl_dec = false;
r->attr.ttl_check = false;
if (otto_l3_host_shadowed(ctrl, r))
return;
if (r->is_host_route)
ctrl->cfg->host_route_write(ctrl, slot, r);
else
@@ -1546,7 +1592,9 @@ static void otto_l3_route_remove(struct otto_l3_ctrl *ctrl, struct otto_l3_route
char dst[INET6_ADDRSTRLEN + sizeof("/128")];
int id;
if (r->is_host_route) {
if (otto_l3_host_shadowed(ctrl, r)) {
/* The entry for the address is the local route's */
} else if (r->is_host_route) {
id = ctrl->cfg->find_slot(ctrl, r, true);
if (id >= 0) {
dev_dbg(ctrl->dev, "Got id for host route: %d\n", id);
@@ -1556,6 +1604,9 @@ static void otto_l3_route_remove(struct otto_l3_ctrl *ctrl, struct otto_l3_route
dev_err(ctrl->dev, "Host route %pI4 was not in hardware\n",
&r->dst_ip);
}
if (r->tb_id == RT_TABLE_LOCAL)
otto_l3_host_unshadow(ctrl, r);
} else {
/* If there is a HW representation of the route, delete it */
if (ctrl->cfg->route_lookup_hw && r->row >= FIRST_PREFIX_ROW) {
@@ -1763,6 +1814,9 @@ static void otto_l3_host_route_trap(struct otto_l3_ctrl *ctrl,
route->attr.action = ROUTE_ACT_TRAP2CPU;
route->attr.type = ROUTE_TYPE_IP4UC;
if (otto_l3_host_shadowed(ctrl, route))
return;
slot = ctrl->cfg->find_slot(ctrl, route, true);
if (slot < 0)
slot = ctrl->cfg->find_slot(ctrl, route, false);
@@ -1827,6 +1881,9 @@ static void otto_l3_route_trap_new(struct otto_l3_ctrl *ctrl, struct otto_l3_rou
r->attr.valid = true;
r->attr.action = ROUTE_ACT_TRAP2CPU;
if (otto_l3_host_shadowed(ctrl, r))
return;
if (r->is_host_route) {
slot = ctrl->cfg->find_slot(ctrl, r, true);
if (slot < 0)
@@ -1950,6 +2007,9 @@ static int otto_l3_fib_add_v4(struct otto_l3_ctrl *ctrl, struct fib_entry_notifi
route->attr.action = ROUTE_ACT_TRAP2CPU;
route->attr.type = ROUTE_TYPE_IP4UC;
if (otto_l3_host_shadowed(ctrl, route))
goto resolve;
slot = ctrl->cfg->find_slot(ctrl, route, true);
if (slot < 0)
slot = ctrl->cfg->find_slot(ctrl, route, false);
@@ -1974,6 +2034,7 @@ static int otto_l3_fib_add_v4(struct otto_l3_ctrl *ctrl, struct fib_entry_notifi
if (ctrl->cfg->use_l3_tables && (nh->fib_nh_gw4 || !route->is_host_route))
otto_l3_route_trap_new(ctrl, route);
resolve:
/* We need to resolve the mac address of the GW */
if (nh->fib_nh_gw4)
otto_l3_port_gw_resolve(ctrl, ndev, &arp_tbl, &gw);