realtek: l3: offload only main and local table routes

The switch has a single routing table. The driver tells its routes apart
by the kernel table they came from, but writes every one of them to that
single table, where it matches whatever packet the hardware routes. A
route that only a policy rule selects - one per uplink, the way
multi-WAN setups do it - is then applied to all traffic, and two routes
to one prefix from two tables become two rows for the same destination,
either of which can answer.

Take FIB events from the main and local tables only, for both address
families, and warn about a route from any other: it stays in software.
The local table carries the switch's own addresses, which the driver
traps. A route from another table is set aside before anything else
looks at it, so an IPv4 route there with an IPv6 gateway is no longer
refused.

This is a change for IPv4 as well, which has had the same exposure: a
route in another table was offloaded until now. The RTL839x, whose L3
code runs in a stock build, is affected the same way and was not
tested.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Gennaro Cimmino <gcimmino@rayonra.net>
Link: https://github.com/openwrt/openwrt/pull/25313
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
This commit is contained in:
Gennaro Cimmino authored and Markus Stockhausen committed 2026-10-03 15:34:36 +02:00
1 parent 7abc4c6404
commit 0d212bc523
1 file changed
+26
@@ -2312,6 +2312,20 @@ static void otto_l3_fib6_event_work_do(struct work_struct *work)
}
/* The switch has a single routing table. A route from any table but these two
* would be written to it as if the kernel looked it up for every packet.
*/
static bool otto_l3_fib_table_ok(struct otto_l3_ctrl *ctrl, unsigned long event, u32 tb_id)
{
if (tb_id == RT_TABLE_MAIN || tb_id == RT_TABLE_LOCAL)
return true;
if (event != FIB_EVENT_ENTRY_DEL)
dev_warn_ratelimited(ctrl->dev, "routes in table %u are not offloaded\n", tb_id);
return false;
}
/* Called with rcu_read_lock() */
static int otto_l3_fib_notifier(struct notifier_block *this, unsigned long event, void *ptr)
{
@@ -2343,6 +2357,11 @@ static int otto_l3_fib_notifier(struct notifier_block *this, unsigned long event
if (info->family == AF_INET) {
struct fib_entry_notifier_info *fen_info = ptr;
if (!otto_l3_fib_table_ok(ctrl, event, fen_info->tb_id)) {
kfree(fib_work);
return NOTIFY_DONE;
}
if (fen_info->fi->fib_nh_is_v6) {
NL_SET_ERR_MSG_MOD(info->extack,
"IPv6 gateway with IPv4 route is not supported");
@@ -2358,6 +2377,13 @@ static int otto_l3_fib_notifier(struct notifier_block *this, unsigned long event
INIT_WORK(&fib_work->work, otto_l3_fib_event_work_do);
} else if (info->family == AF_INET6 && IS_REACHABLE(CONFIG_IPV6) &&
ctrl->cfg->use_l3_tables) {
struct fib6_entry_notifier_info *fen6_info = ptr;
if (!otto_l3_fib_table_ok(ctrl, event, fen6_info->rt->fib6_table->tb6_id)) {
kfree(fib_work);
return NOTIFY_DONE;
}
memcpy(&fib_work->fen6_info, ptr, sizeof(fib_work->fen6_info));
fib6_info_hold(fib_work->fen6_info.rt);
INIT_WORK(&fib_work->work, otto_l3_fib6_event_work_do);