summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniil Baturin <daniil@vyos.io>2026-03-24 15:23:46 +0000
committerGitHub <noreply@github.com>2026-03-24 15:23:46 +0000
commitcb1b97548ce0cb51ee4445c2b1cdc375cd9b6121 (patch)
treefd21ac49c3eac27c625a376bf9df3d4b5d0b6863
parent5c070bcf12f9ba0194331898d351fb0f61cb1078 (diff)
parent2a6a63391173e8cef9354a696e188e90f43def74 (diff)
downloadvyos-1x-cb1b97548ce0cb51ee4445c2b1cdc375cd9b6121.tar.gz
vyos-1x-cb1b97548ce0cb51ee4445c2b1cdc375cd9b6121.zip
Merge pull request #4968 from natali-rs1985/T8188
T8188: Preserve static IPv4 addresses flushed by dhclient
-rw-r--r--src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper53
1 files changed, 52 insertions, 1 deletions
diff --git a/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper b/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper
index 1f27ee14c..7c119b0dd 100644
--- a/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper
+++ b/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper
@@ -1,4 +1,4 @@
-# redefine ip command to use FRR when it is available
+# redefine ip command to use FRR when it is available and preserve static addresses
# default route distance
IF_METRIC=${IF_METRIC:-210}
@@ -6,6 +6,43 @@ IF_METRIC=${IF_METRIC:-210}
# Check if interface is inside a VRF
VRF_OPTION=$(/usr/sbin/ip --json --detail link show ${interface} | jq -r '.[0] | select(.linkinfo.info_slave_kind == "vrf") | "vrf \(.master)"')
+# Flush only DHCP (dynamic) addresses from interface, preserving static addresses
+#
+# When dhclient renews/rebinds a lease, it calls:
+# ip -4 addr flush dev <interface>
+# This removes ALL IPv4 addresses, including static addresses configured
+# via VyOS (e.g., "set interfaces ethernet eth0 address 192.168.1.1/24").
+#
+# The ip() wrapper below intercepts the "ip -4 addr flush" command and replaces
+# it with a selective flush that only removes addresses marked as "dynamic" by
+# the kernel. DHCP-assigned addresses have the "dynamic" flag set automatically.
+_flush_dhcp_addrs() {
+ local dev="${1:-$interface}"
+
+ logmsg info "Selectively flushing only dynamic (DHCP) addresses from ${dev}"
+
+ local addrs
+ addrs=$(/usr/sbin/ip -4 -j addr show dev "${dev}" 2>&1 | jq -r '
+ .[] | .addr_info[]? | select(.family == "inet" and .dynamic == true)
+ | "\(.local)/\(.prefixlen)"
+ ' 2>&1)
+ if [ $? -ne 0 ]; then
+ logmsg warn "Failed to list dynamic IPv4 addresses on ${dev}; skipping selective flush. ip/jq error output: $addrs"
+ return 0
+ fi
+
+ while IFS= read -r addr; do
+ if [ -n "$addr" ]; then
+ logmsg info "Removing dynamic address ${addr} from ${dev}"
+ if ! /usr/sbin/ip -4 addr del "${addr}" dev "${dev}" 2>&1; then
+ logmsg warn "Failed to remove dynamic address ${addr} from ${dev}"
+ fi
+ fi
+ done <<< "$addrs"
+
+ return 0
+}
+
# get status of FRR
function frr_alive () {
/usr/lib/frr/watchfrr.sh all_status
@@ -90,6 +127,20 @@ function vtysh_conf () {
# replace ip command with this wrapper
function ip () {
+ # Intercept: ip -4 addr flush dev <interface>
+ # to preserve static addresses (only remove dynamic/DHCP addresses)
+ if [ "$1" = "-4" ] && [ "$2" = "addr" ] && [ "$3" = "flush" ]; then
+ logmsg info "Intercepting 'ip -4 addr flush' to preserve static addresses"
+ shift 3
+ local dev=""
+ while [ $# -gt 0 ]; do
+ [ "$1" = "dev" ] && dev="$2" && break
+ shift
+ done
+ _flush_dhcp_addrs "${dev:-$interface}"
+ return $?
+ fi
+
# pass comand to system `ip` if this is not related to routes change
if [ "$2" != "route" ] ; then
logmsg info "Passing command to /usr/sbin/ip: \"$@\""