Skip to content

Commit 6d2ee03

Browse files
committed
dhcp: T9085: metric-aware stale delete; JSON presence; no-default-route skip
alexk37 on-box follow-up (2026-08-22): 1. Two-router option 3 installs at distance 210 and 211. A wrapper delete without metric always issues 210, so the 211 leftover stays in FRR and can become the kernel default after option 3 is cleared. Delete with the same per-router metric 01-vyos-cleanup uses. 2. Two DHCP WANs at the same distance install a multipath default. `ip route show default via GW dev IF` returns nothing for a nexthop group, so every renew logged a false reinstall. Match gateways in `ip -j` output. 3. no-default-route blanks new_routers while a force-sent option 3 still fills old_routers, so cleanup logged and opened vtysh every renew. 06-vyos-nodefaultroute now sets VYOS_NO_DEFAULT_ROUTE; skip the delete unless a leftover kernel default is actually present.
1 parent 2866694 commit 6d2ee03

2 files changed

Lines changed: 47 additions & 13 deletions

File tree

src/etc/dhcp/dhclient-enter-hooks.d/06-vyos-nodefaultroute

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,7 @@ PYEND
1717

1818
if [[ "$(get_no_default_route)" == 'True' ]]; then
1919
new_routers=""
20+
# Exit-hooks (same dhclient-script shell) use this to skip stale-default
21+
# cleanup when the lease still carries a force-sent option 3.
22+
VYOS_NO_DEFAULT_ROUTE=1
2023
fi

src/etc/dhcp/dhclient-exit-hooks.d/02-vyos-dhcp-renew-default-route

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,27 @@
1010
# Presence check MUST use /usr/sbin/ip, not the enter-hook ip() wrapper.
1111
# That wrapper sends any `ip … route` that is not `del` through vtysh as an
1212
# add, so `ip -4 route show` both misses the kernel table and writes a
13-
# staticd duplicate. delroute() already reads via /usr/sbin/ip; match that,
14-
# and append ${VRF_OPTION} so VRF leases see the same table the add uses.
13+
# staticd duplicate. Use `ip -j` so a multipath default (two DHCP WANs at
14+
# the same distance) is visible — `ip route show default via GW dev IF`
15+
# returns nothing for a nexthop group. Append ${VRF_OPTION} so VRF leases
16+
# see the same table the add uses.
1517
#
1618
# If option 3 changes across a same-IP RENEW, drop defaults via gateways
1719
# that disappeared from $new_routers (including option 3 going away entirely).
1820
# Otherwise FRR keeps the old nexthop and the kernel shows ECMP over old+new.
1921
# Compare $old_routers vs $new_routers (same pattern as
20-
# 98-vyos-static-routes-dhclient-hook). The no-default-route enter hook blanks
21-
# new_routers; removing a leftover option-3 default is the right outcome there
22-
# too, so cleanup runs before the empty-new_routers return.
22+
# 98-vyos-static-routes-dhclient-hook). Delete with the same per-router
23+
# metric 01-vyos-cleanup uses (${IF_METRIC}, then +1 per old_routers
24+
# entry): a wrapper delete without metric always issues distance 210, which
25+
# misses the 211+ FRR leftovers and can leave a ghost as the kernel default
26+
# after option 3 is cleared.
27+
#
28+
# 06-vyos-nodefaultroute blanks new_routers and sets VYOS_NO_DEFAULT_ROUTE.
29+
# Skip the stale-delete (and its vtysh session) when that flag is set and
30+
# the kernel has no default via that gateway — otherwise a server that
31+
# force-sends option 3 logs "removing stale default" on every renew.
32+
# If a leftover kernel default exists (no-default-route just set), still
33+
# delete it.
2334
#
2435
# Adds still use the enter-hook ip() wrapper so FRR gets tag/distance
2536
# IF_METRIC. Skip when classless static routes are present (stock ignores
@@ -42,16 +53,26 @@ if [ -n "$new_rfc3442_classless_static_routes" ]; then
4253
return 0 2>/dev/null || exit 0
4354
fi
4455

45-
# Kernel/FRR-installed default present? Real ip(8) only — never the wrapper.
56+
# Kernel default via $1 on $interface? Real ip(8) JSON — never the wrapper.
57+
# Matches both a single-nexthop default and a multipath nexthop group.
4658
_default_via_present() {
47-
/usr/sbin/ip -4 route show default via "$1" dev "${interface}" ${VRF_OPTION} 2>/dev/null | grep -q .
59+
local gw="$1"
60+
local json
61+
json=$(/usr/sbin/ip -j -4 route show default ${VRF_OPTION} 2>/dev/null) || return 1
62+
[ -n "$json" ] || return 1
63+
echo "$json" | jq -e --arg gw "$gw" --arg dev "$interface" \
64+
'any(.[];
65+
(.gateway == $gw and .dev == $dev)
66+
or any(.nexthops[]?; .gateway == $gw and .dev == $dev)
67+
)' >/dev/null 2>&1
4868
}
4969

50-
if_metric="${IF_METRIC:-210}"
51-
5270
# Drop defaults via gateways the server no longer announces, including when
53-
# option 3 disappeared (empty new_routers) or no-default-route blanked it.
71+
# option 3 disappeared (empty new_routers). Metric numbering matches the
72+
# original install / 01-vyos-cleanup: IF_METRIC, then +1 per old_routers
73+
# entry (keepers included, or the 211+ leftover is missed).
5474
if [ -n "$old_routers" ] && [ "$old_routers" != "$new_routers" ]; then
75+
if_metric="${IF_METRIC:-210}"
5576
for router in $old_routers; do
5677
skip=
5778
for keep in $new_routers; do
@@ -61,16 +82,25 @@ if [ -n "$old_routers" ] && [ "$old_routers" != "$new_routers" ]; then
6182
fi
6283
done
6384
if [ -n "$skip" ]; then
85+
if_metric=$((if_metric + 1))
86+
continue
87+
fi
88+
# no-default-route blanks new_routers every renew; only act if a
89+
# leftover default is actually present.
90+
if [ -n "$VYOS_NO_DEFAULT_ROUTE" ] && ! _default_via_present "${router}"; then
91+
if_metric=$((if_metric + 1))
6492
continue
6593
fi
6694
logmsg info \
67-
"RENEW/REBIND: removing stale default via ${router} dev ${interface}"
68-
# Wrapper: FRR-aware del (staticd + kernel).
69-
ip -4 route del default via "${router}" dev "${interface}" >/dev/null 2>&1
95+
"RENEW/REBIND: removing stale default via ${router} dev ${interface} metric ${if_metric}"
96+
# Wrapper: FRR-aware del (staticd + kernel) at the install distance.
97+
ip -4 route del default via "${router}" dev "${interface}" \
98+
metric "${if_metric}" >/dev/null 2>&1
7099
if _default_via_present "${router}"; then
71100
logmsg warn \
72101
"RENEW/REBIND: stale default still present via ${router} dev ${interface}"
73102
fi
103+
if_metric=$((if_metric + 1))
74104
done
75105
fi
76106

@@ -79,6 +109,7 @@ if [ -z "$new_routers" ]; then
79109
return 0 2>/dev/null || exit 0
80110
fi
81111

112+
if_metric="${IF_METRIC:-210}"
82113
for router in $new_routers; do
83114
if _default_via_present "${router}"; then
84115
if_metric=$((if_metric + 1))

0 commit comments

Comments
 (0)