# redefine ip command to use FRR when it is available and preserve static addresses # default route distance 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 # 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 if [ "$?" -eq "0" ] ; then logmsg info "FRR status: running" return 0 else logmsg info "FRR status: not running" return 1 fi } # convert ip route command to vtysh function iptovtysh () { # prepare variables for vtysh command local VTYSH_ACTION=$3 local VTYSH_NETADDR="" local VTYSH_GATEWAY="" local VTYSH_DEV="" local VTYSH_TAG="210" local VTYSH_DISTANCE=$IF_METRIC # convert default route to 0.0.0.0/0 if [ "$4" == "default" ] ; then VTYSH_NETADDR="0.0.0.0/0" else VTYSH_NETADDR=$4 fi # add /32 to ip addresses without netmasks if [[ ! $VTYSH_NETADDR =~ ^.*/[[:digit:]]+$ ]] ; then VTYSH_NETADDR="$VTYSH_NETADDR/32" fi shift 4 # get gateway address if [ "$1" == "via" ] ; then VTYSH_GATEWAY=$2 shift 2 fi # get device name if [ "$1" == "dev" ]; then VTYSH_DEV=$2 shift 2 fi # get distance if [ "$1" == "metric" ]; then VTYSH_DISTANCE=$2 shift 2 fi VTYSH_CMD="ip route $VTYSH_NETADDR $VTYSH_GATEWAY $VTYSH_DEV tag $VTYSH_TAG $VTYSH_DISTANCE $VRF_OPTION" # delete route if the command is "del" if [ "$VTYSH_ACTION" == "del" ] ; then VTYSH_CMD="no $VTYSH_CMD" fi logmsg info "Converted vtysh command: \"$VTYSH_CMD\"" } # delete the same route from kernel before adding new one function delroute () { logmsg info "Checking if the route presented in kernel: $@ $VRF_OPTION" if /usr/sbin/ip route show $@ $VRF_OPTION | grep -qx "$1 " ; then logmsg info "Deleting IP route: \"/usr/sbin/ip route del $@ $VRF_OPTION\"" /usr/sbin/ip route del $@ $VRF_OPTION fi } # try to communicate with vtysh function vtysh_conf () { # perform 10 attempts with 1 second delay for retries for i in {1..10} ; do if vtysh -c "conf t" -c "$1" ; then logmsg info "Command was executed successfully via vtysh: \"$1\"" return 0 else logmsg info "Failed to send command to vtysh, retrying in 1 second" sleep 1 fi done logmsg error "Failed to execute command via vtysh after 10 attempts: \"$1\"" return 1 } # replace ip command with this wrapper function ip () { # Intercept: ip -4 addr flush dev # 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 command to system `ip` if this is not related to routes change if [ "$2" != "route" ] ; then logmsg info "Passing command to /usr/sbin/ip: \"$@\"" /usr/sbin/ip $@ else # if we want to work with routes, try to use FRR first if frr_alive ; then delroute ${@:4} iptovtysh $@ logmsg info "Sending command to vtysh" vtysh_conf "$VTYSH_CMD" else # add ip route to kernel logmsg info "Modifying routes in kernel: \"/usr/sbin/ip $@\"" /usr/sbin/ip $@ $VRF_OPTION fi fi }