134 lines
5.1 KiB
Plaintext
134 lines
5.1 KiB
Plaintext
|
|
#!/bin/bash
|
||
|
|
# Auto-revert the VLAN 2 IPv6 change if IPv4 stops working.
|
||
|
|
#
|
||
|
|
# The change it guards is additive and IPv6-only, so in theory it cannot affect
|
||
|
|
# IPv4 at all. This exists because "in theory" is exactly what has been wrong
|
||
|
|
# repeatedly, and because it is applied in an unattended window: the person who
|
||
|
|
# would notice is away, and a router that has lost IPv4 takes the house and the
|
||
|
|
# cluster with it.
|
||
|
|
#
|
||
|
|
# Modelled on wan-drill-watchdog: arm before the risky thing, disarm after, and
|
||
|
|
# in between let the ROUTER decide for itself rather than depending on anything
|
||
|
|
# off-box. A watchdog that needs the network it is protecting is not a watchdog.
|
||
|
|
#
|
||
|
|
# vlan2-v6-watchdog arm [seconds] start watching (default 2400 = 40 min)
|
||
|
|
# vlan2-v6-watchdog disarm stop
|
||
|
|
# vlan2-v6-watchdog status is it armed, and what has it seen
|
||
|
|
#
|
||
|
|
# Runs ON the router, out of /config, detached via setsid so it outlives the ssh
|
||
|
|
# session that started it.
|
||
|
|
set -uo pipefail
|
||
|
|
|
||
|
|
STATE=/run/vlan2-v6-watchdog
|
||
|
|
PIDF=$STATE/pid
|
||
|
|
LOGF=$STATE/log
|
||
|
|
V6_PREFIX="${V6_PREFIX:-2001:470:187e:2}"
|
||
|
|
|
||
|
|
# How long IPv4 must be continuously bad before reverting. Long enough that a
|
||
|
|
# commit's own brief disruption, or one lost probe, does not trigger it; short
|
||
|
|
# enough to matter. The reconciler ticks at 30s, so 90s is three chances.
|
||
|
|
GRACE="${GRACE:-90}"
|
||
|
|
PROBE="${PROBE:-9.9.9.9}"
|
||
|
|
|
||
|
|
log() { mkdir -p "$STATE"; printf '%s %s\n' "$(date -Is)" "$*" >> "$LOGF"; }
|
||
|
|
|
||
|
|
# IPv4 health, asked of the box itself -- and it MUST be role-aware.
|
||
|
|
#
|
||
|
|
# The first version required a default route plus internet on both routers, and
|
||
|
|
# would have reverted on vyos002 within 90s of being armed. That box is the
|
||
|
|
# gated BACKUP: by design it holds no VIP, has bond0.53 disabled and pppoe0
|
||
|
|
# down, so it has no default route and no internet, and that is the correct
|
||
|
|
# resting state rather than a fault. Caught at 20s of the 90s grace.
|
||
|
|
#
|
||
|
|
# So: the master must be able to reach the internet. The backup only has to
|
||
|
|
# still be on the network and able to see its peer -- which is what would
|
||
|
|
# actually be at risk if a VLAN 2 change went wrong.
|
||
|
|
VIP="${VIP:-192.168.1.1}"
|
||
|
|
holds_vip() { ip -4 -o addr show 2>/dev/null | grep -q " ${VIP}/"; }
|
||
|
|
|
||
|
|
v4_ok() {
|
||
|
|
if holds_vip; then
|
||
|
|
ip -4 route show default 2>/dev/null | grep -q . || return 1
|
||
|
|
ping -c1 -W2 "$PROBE" >/dev/null 2>&1
|
||
|
|
else
|
||
|
|
# Backup: management address present and the VIP answers. If the VIP has
|
||
|
|
# gone too then the pair has a bigger problem than this change, and
|
||
|
|
# reverting an IPv6 addition would not help -- so this deliberately does
|
||
|
|
# not fire on peer loss alone.
|
||
|
|
ip -4 -o addr show bond0.1 2>/dev/null | grep -q 'inet ' || return 1
|
||
|
|
ping -c1 -W2 "$VIP" >/dev/null 2>&1
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
revert() {
|
||
|
|
log "REVERTING: IPv4 has been down for ${GRACE}s"
|
||
|
|
# A script file, not vbash -c: the latter never starts a config session and
|
||
|
|
# the commit fails to stderr where nobody sees it.
|
||
|
|
cat > /tmp/vlan2-v6-revert.sh <<'REOF'
|
||
|
|
#!/bin/vbash
|
||
|
|
source /opt/vyatta/etc/functions/script-template
|
||
|
|
configure
|
||
|
|
delete service dhcpv6-server
|
||
|
|
delete service router-advert interface bond0.2
|
||
|
|
commit
|
||
|
|
save
|
||
|
|
exit
|
||
|
|
REOF
|
||
|
|
chmod +x /tmp/vlan2-v6-revert.sh
|
||
|
|
/tmp/vlan2-v6-revert.sh >> "$LOGF" 2>&1
|
||
|
|
# The interface address is deleted separately: it is the one node whose
|
||
|
|
# removal could plausibly disturb something else, so it goes last and its
|
||
|
|
# failure does not block the rest.
|
||
|
|
for n in 1 2; do
|
||
|
|
ip -6 addr del "${V6_PREFIX}::${n}/64" dev bond0.2 2>/dev/null
|
||
|
|
done
|
||
|
|
log "revert done; IPv4 now $(v4_ok && echo OK || echo STILL BAD)"
|
||
|
|
}
|
||
|
|
|
||
|
|
watch_loop() {
|
||
|
|
local deadline=$(( $(date +%s) + $1 )) bad=0
|
||
|
|
log "armed for $1s (grace ${GRACE}s, probe ${PROBE})"
|
||
|
|
while [ "$(date +%s)" -lt "$deadline" ]; do
|
||
|
|
if v4_ok; then
|
||
|
|
[ "$bad" -ne 0 ] && log "IPv4 recovered after ${bad}s"
|
||
|
|
bad=0
|
||
|
|
else
|
||
|
|
bad=$((bad + 10))
|
||
|
|
log "IPv4 bad (${bad}s/${GRACE}s)"
|
||
|
|
if [ "$bad" -ge "$GRACE" ]; then
|
||
|
|
revert
|
||
|
|
log "disarming after revert"
|
||
|
|
rm -f "$PIDF"
|
||
|
|
return 0
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
sleep 10
|
||
|
|
done
|
||
|
|
log "expired without incident"
|
||
|
|
rm -f "$PIDF"
|
||
|
|
}
|
||
|
|
|
||
|
|
case "${1:-status}" in
|
||
|
|
arm)
|
||
|
|
mkdir -p "$STATE"
|
||
|
|
[ -f "$PIDF" ] && kill -0 "$(cat "$PIDF")" 2>/dev/null && { echo "already armed"; exit 0; }
|
||
|
|
setsid "$0" _run "${2:-2400}" >/dev/null 2>&1 < /dev/null &
|
||
|
|
sleep 1
|
||
|
|
[ -f "$PIDF" ] && echo " armed (pid $(cat "$PIDF"), ${2:-2400}s)" || echo " FAILED to arm"
|
||
|
|
;;
|
||
|
|
_run)
|
||
|
|
echo $$ > "$PIDF"
|
||
|
|
watch_loop "${2:-2400}"
|
||
|
|
;;
|
||
|
|
disarm)
|
||
|
|
if [ -f "$PIDF" ]; then kill "$(cat "$PIDF")" 2>/dev/null; rm -f "$PIDF"; echo " disarmed"
|
||
|
|
else echo " not armed"; fi
|
||
|
|
;;
|
||
|
|
status)
|
||
|
|
if [ -f "$PIDF" ] && kill -0 "$(cat "$PIDF")" 2>/dev/null; then echo " armed (pid $(cat "$PIDF"))"
|
||
|
|
else echo " not armed"; fi
|
||
|
|
echo " --- log ---"; tail -12 "$LOGF" 2>/dev/null | sed 's/^/ /' || echo " (none)"
|
||
|
|
;;
|
||
|
|
*) echo "usage: $0 {arm [seconds]|disarm|status}" >&2; exit 2 ;;
|
||
|
|
esac
|