Page MenuHomeVyOS Platform

dhcp: IPv4 default route not reinstalled on RENEW after kernel default was removed
In progress, LowPublic

Description

Summary

When an interface still has a valid DHCPv4 address but the kernel default route via that interface is gone, subsequent dhclient RENEW events only refresh the address lifetime (ip addr change). They do not re-add default via <router> dev <iface>.

The interface looks healthy (address present, RENEWs succeed) while multi-WAN metric failover silently loses that path until something external reinstalls the route (reboot, dhclient restart, or ip route add).

This is distinct from "WAN is down" — the lease is alive; only the default is missing.

Why it matters

  1. Metric multi-WAN: VyOS installs DHCP defaults with default-route-distance / IF_METRIC. Operators assume those defaults remain as long as the lease is valid.
  1. Production triggers we hit (VyOS 1.5 rolling, 2026-07-11..13, two sites):
    • Staged HA lease apply with finite valid_lft: when valid_lft hits 0 the kernel removes the address and flushes routes on that interface (including the default). Later RENEW refreshes the address; the default stays gone.
    • External deletion of the default while the address remains (testing, remetric races).
  1. Related (separate issue): dhclient stuck in unicast RENEW to a server that lost the session can sit for the whole lease lifetime — not mixed into this ticket.

Reproduction

On a DHCP WAN (e.g. eth0) after BOUND:

ip -4 route show default dev eth0
ip -4 route del default dev eth0
# wait for RENEW (or trigger RENEW without full dhclient restart)
ip -4 addr show dev eth0          # address still present
ip -4 route show default dev eth0 # still empty

Expected: default via lease gateway reinstalled at configured metric (IF_METRIC / default-route-distance).

Actual: RENEW updates address lifetimes only; default remains missing indefinitely.

Suggested fix

On RENEW (and optionally BOUND/REBOOT when the address already exists): if new_routers is set and there is no matching default via that gateway on $interface, install it at IF_METRIC (EEXIST-safe).

Environment

  • VyOS 1.5 Circinus rolling (e.g. 2026.07.10-1446-rolling)
  • DHCP WANs with dhcp-options default-route-distance
  • Multi-WAN metric failover

Workaround

External periodic ensure: re-add missing default from lease gateway + IF_METRIC (we run this every ~10s in a health daemon). Staging floors valid_lft at 600s to reduce accidental flush.

Related

We abandoned demote via set default-route-distance + commit in favour of kernel remetric for speed/races; that is separate from this RENEW gap.

Details

Version
VyOS 1.5 Circinus rolling (e.g. 2026.07.10-1446-rolling)
Is it a breaking change?
Perfectly compatible
Issue type
Bug (incorrect behavior)

Event Timeline

Addressed review on https://github.com/vyos/vyos-1x/pull/5340 (fe9ceb1ac):

1. Presence check / FRR ip() wrapper (Copilot + alexk37, confirmed on-box):
The check now uses /usr/sbin/ip + ${VRF_OPTION} instead of the enter-hook ip() wrapper. Adds still go through the wrapper so FRR gets tag/distance. Matches delroute() and the one-line change already tested.

2. Same-IP option-3 change → ECMP over old+new (alexk37):
On RENEW/REBIND, if $old_routers differs from $new_routers, drop default via each gateway that left $new_routers (wrapper ip route del, so staticd is updated), then add any missing $new_routers as before. Same $old_routers / $new_routers compare as 98-vyos-static-routes-dhclient-hook.

Addressed alexk37 2026-08-22 follow-up on https://github.com/vyos/vyos-1x/pull/5340 (6d2ee03b2; rebased onto current rolling):

  1. Two-router option 3 / delete misses distance 211: stale-gateway delete now uses the same per-router metric as 01-vyos-cleanup (IF_METRIC, then +1 for every old_routers entry including keepers).
  1. Multipath default / false reinstall log: presence check is ip -j -4 route show default and matches .gateway/.dev or a .nexthops[] entry.
  1. no-default-route + force-sent option 3: 06-vyos-nodefaultroute sets VYOS_NO_DEFAULT_ROUTE; skip the delete unless a leftover kernel default is actually present.