Skip to content

Static route is deleted on reload when a config change alters the vlist key but not the kernel route #88

Description

@sudosanet

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions