Adding option onlink '1' to an existing config route section and running /etc/init.d/network reload does not install the route; the route only appears after /etc/init.d/network restart. Nothing is logged either way. Seen with netifd 2025.05.23~7901e66c, the stock 24.10.4 package; the code below is unchanged in master.
A dummy device is enough to reproduce. The gateway is deliberately outside the interface prefix, so the route needs onlink to be accepted at all:
ip link add ktdummy type dummy
ip link set ktdummy up
uci set network.kt=interface
uci set network.kt.device=ktdummy
uci set network.kt.proto=static
uci set network.kt.ipaddr=10.77.77.1
uci set network.kt.netmask=255.255.255.0
uci add network route
uci set network.@route[-1].interface=kt
uci set network.@route[-1].target=10.88.0.0/16
uci set network.@route[-1].gateway=10.99.99.1
uci commit network
/etc/init.d/network restart
ip route show 10.88.0.0/16
# empty — expected, the kernel refuses an off-link next-hop without onlink
# now add the option that is supposed to fix exactly that
uci set network.@route[-1].onlink=1
uci commit network
/etc/init.d/network reload
ip route show 10.88.0.0/16
# still empty
/etc/init.d/network restart
ip route show 10.88.0.0/16
# 10.88.0.0/16 via 10.99.99.1 dev ktdummy proto static onlink
ip monitor route during the reload:
10.88.0.0/16 via 10.99.99.1 dev ktdummy proto static onlink
Deleted 10.88.0.0/16 via 10.99.99.1 dev ktdummy proto static onlink
The add succeeds — system_rt() uses NLM_F_CREATE | NLM_F_REPLACE — and the delete for the old route then removes what the add just installed. Both netlink calls succeed, so there is no error to report.
route_cmp() has flags in the key, so DEVROUTE_ONLINK changes the vlist key even though dst, metric and table, and therefore the kernel route slot, stay the same. With a changed key vlist_add() does not find the old node, so __interface_update_route() is called once with node_old == NULL (add) and later from vlist_flush() with node_new == NULL (delete). The keep check that would suppress the delete only runs when both nodes are present.
The same thing happens in the other direction (removing onlink from a working route), and when option metric or option table is set to the value the interface already supplies: interface_set_route_info() fills those in without setting DEVROUTE_METRIC / DEVROUTE_TABLE, so the resulting kernel route is identical but flags differs. Changing gateway or target, or setting a different metric or table, works as expected.
This looks like the counterpart of f35f095 ("iprule: only keep unchanged rules whose installation succeeded"): the bookkeeping is correct while the key is stable, but not when the key changes and the kernel object does not. Two ideas, I don't know which one you would prefer:
- On the delete path, skip
system_del_route() when a node added in the current vlist version resolves to the same kernel route (family, dst, mask, metric, table).
- Drop the flag bits that do not affect kernel route identity (
DEVROUTE_ONLINK, DEVROUTE_METRIC, DEVROUTE_TABLE, DEVROUTE_SRCTABLE) from the route_cmp key — DEVADDR_FAMILY has to stay — and add flags to the keep comparison so that a flags-only change still goes through an ordered delete and re-add.
Happy to test or send a patch for either.
Adding
option onlink '1'to an existingconfig routesection and running/etc/init.d/network reloaddoes not install the route; the route only appears after/etc/init.d/network restart. Nothing is logged either way. Seen with netifd 2025.05.23~7901e66c, the stock 24.10.4 package; the code below is unchanged in master.A
dummydevice is enough to reproduce. The gateway is deliberately outside the interface prefix, so the route needsonlinkto be accepted at all:ip monitor routeduring the reload:The add succeeds —
system_rt()usesNLM_F_CREATE | NLM_F_REPLACE— and the delete for the old route then removes what the add just installed. Both netlink calls succeed, so there is no error to report.route_cmp()hasflagsin the key, soDEVROUTE_ONLINKchanges the vlist key even though dst, metric and table, and therefore the kernel route slot, stay the same. With a changed keyvlist_add()does not find the old node, so__interface_update_route()is called once withnode_old == NULL(add) and later fromvlist_flush()withnode_new == NULL(delete). Thekeepcheck that would suppress the delete only runs when both nodes are present.The same thing happens in the other direction (removing
onlinkfrom a working route), and whenoption metricoroption tableis set to the value the interface already supplies:interface_set_route_info()fills those in without settingDEVROUTE_METRIC/DEVROUTE_TABLE, so the resulting kernel route is identical butflagsdiffers. Changinggatewayortarget, or setting a different metric or table, works as expected.This looks like the counterpart of f35f095 ("iprule: only keep unchanged rules whose installation succeeded"): the bookkeeping is correct while the key is stable, but not when the key changes and the kernel object does not. Two ideas, I don't know which one you would prefer:
system_del_route()when a node added in the current vlist version resolves to the same kernel route (family, dst, mask, metric, table).DEVROUTE_ONLINK,DEVROUTE_METRIC,DEVROUTE_TABLE,DEVROUTE_SRCTABLE) from theroute_cmpkey —DEVADDR_FAMILYhas to stay — and addflagsto thekeepcomparison so that a flags-only change still goes through an ordered delete and re-add.Happy to test or send a patch for either.