blob: bd111d2826da7a812dfe43b1c8ae07fb79ab5bde (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
|
# 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 <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
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 <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 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
}
|