realtek: l3: trap a host route through a nexthop object

A route through a nexthop object is left out of the offload, but the
switch still looks its destination up. When an offloaded shorter prefix
covers it, the switch forwards that traffic through the prefix's
gateway, and the route the kernel holds never sees a packet.

Where the host table can hold the route, write a trap entry for it, the
way otto_l3_fib_add_v4() already does for a host route with no gateway:
the lookup stops there and the CPU forwards by the kernel's route. A
delete for such a route now reaches the worker as well, and takes that
entry out with the same otto_l3_route_find() and
otto_l3_route_teardown() pair the replace uses.

A prefix route through a nexthop object still gets no entry: here the
prefix table only takes a route once its gateway resolves. Neither does
a host route on a family without a host table.

Tested on an RTL9303 (Hasivo S1100W-8XGT-SE) with the L3 offload on,
this series on main at 248601f492. With 10.99.0.0/16 offloaded through
one host and 10.99.1.1/32 through a nexthop object to the same host,
the /32 gets a trap entry in the host table. Five pings from a second
host reach the first with ttl 63 for 10.99.1.1, forwarded by the CPU,
and with ttl 64 for 10.99.2.2, which only the /16 covers: on this tree
the hardware path leaves the ttl alone. A replace through a gateway
turns the entry into a forwarding one, a replace back makes it a trap
again, and the delete takes it out. A /24 through the nexthop object
gets no entry. No warning or oops in the log.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Gennaro Cimmino <gcimmino@rayonra.net>
Link: https://github.com/openwrt/openwrt/pull/25310
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
This commit is contained in:
Gennaro Cimmino authored and Markus Stockhausen committed 2026-09-29 13:58:05 +02:00
1 parent 279b4d763c
commit bfb9858944
1 file changed
+46 -10
@@ -1403,6 +1403,35 @@ static int otto_l3_fib_check_v4(struct otto_l3_ctrl *ctrl,
return 0;
}
static void otto_l3_host_route_trap(struct otto_l3_ctrl *ctrl,
struct fib_entry_notifier_info *info)
{
struct otto_l3_route *route;
int slot;
route = otto_l3_host_route_alloc(ctrl, 0);
if (!route)
return;
route->dst_ip = info->dst;
route->prefix_len = info->dst_len;
route->tb_id = info->tb_id;
route->attr.valid = true;
route->attr.action = ROUTE_ACT_TRAP2CPU;
route->attr.type = ROUTE_TYPE_IP4UC;
slot = ctrl->cfg->find_slot(ctrl, route, true);
if (slot < 0)
slot = ctrl->cfg->find_slot(ctrl, route, false);
if (slot < 0) {
dev_err(ctrl->dev, "no slot for host route %pI4\n", &route->dst_ip);
return;
}
ctrl->cfg->host_route_write(ctrl, slot, route);
}
static int otto_l3_fib_add_v4(struct otto_l3_ctrl *ctrl, struct fib_entry_notifier_info *info)
{
struct rtl838x_switch_priv *priv = ctrl->priv;
@@ -1412,7 +1441,9 @@ static int otto_l3_fib_add_v4(struct otto_l3_ctrl *ctrl, struct fib_entry_notifi
int port, vlan;
/* A route through a nexthop object is not offloaded and has no
* nexthop array to read, but it can replace a route that is.
* nexthop array to read, but it can replace a route that is. An
* offloaded shorter prefix would forward its traffic, so trap it where
* the host table can hold it.
*/
if (info->fi->nh) {
dev_dbg(ctrl->dev, "route through a nexthop object, not offloaded\n");
@@ -1420,6 +1451,8 @@ static int otto_l3_fib_add_v4(struct otto_l3_ctrl *ctrl, struct fib_entry_notifi
NULL, info->dst_len);
if (route)
otto_l3_route_teardown(ctrl, route);
if (info->dst_len == 32 && ctrl->cfg->host_route_write)
otto_l3_host_route_trap(ctrl, info);
return 0;
}
@@ -1518,10 +1551,21 @@ out_free_rt:
static int otto_l3_fib_del_v4(struct otto_l3_ctrl *ctrl, struct fib_entry_notifier_info *info)
{
struct fib_nh *nh = fib_info_nh(info->fi, 0);
struct rhlist_head *tmp, *list;
struct otto_l3_route *route;
bool found = false;
struct fib_nh *nh;
/* A route through a nexthop object holds at most a trap entry */
if (info->fi->nh) {
route = otto_l3_route_find(ctrl, info->tb_id, ROUTE_TYPE_IP4UC, info->dst,
NULL, info->dst_len);
if (route)
otto_l3_route_teardown(ctrl, route);
return 0;
}
nh = fib_info_nh(info->fi, 0);
if (otto_l3_fib_check_v4(ctrl, info, FIB_EVENT_ENTRY_DEL))
return 0;
@@ -1641,14 +1685,6 @@ 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;
/* A route through a nexthop object is never offloaded,
* and the delete path reads a nexthop array it lacks.
*/
if (fen_info->fi->nh && event == FIB_EVENT_ENTRY_DEL) {
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");