Skip to content

Instantly share code, notes, and snippets.

@levid0s
Last active July 21, 2024 13:07
Show Gist options
  • Save levid0s/2cb9f64234aab3b3a400b57d5d92f840 to your computer and use it in GitHub Desktop.
Save levid0s/2cb9f64234aab3b3a400b57d5d92f840 to your computer and use it in GitHub Desktop.
OpenWRT DNS Watch
#!/bin/ash
# https://gist.github.com/levid0s/2cb9f64234aab3b3a400b57d5d92f840
set -eu
usage="
Suggested crontab:
*/5 * * * * /root/dns_watch.sh 2>> /tmp/dns_watch.log
"
PREFERRED_DNS="192.168.1.2" # eg. Adguard Home
FALLBACK_DNS="8.8.8.8"
ROUTER_IP=$(ip addr show br-lan | grep 'inet ' | awk '{print $2}' | cut -d'/' -f1)
TEST_QUERY="www.example.com"
RESOLVER="/usr/bin/nslookup"
CHECK_INTERVAL=20
FALLBACK_THRESHOLD="${1-120}" # $1 or default
PREFERRED_THRESHOLD="${1-120}" # $1 or default
: ${debug:=false}
[ "$debug" = 1 ] && debug=true
command -v $debug >/dev/null || { echo "ERROR: Incorrect value for \$debug" >&2; exit 1; }
echo "$ROUTER_IP" | grep -qE '^((10\.([0-9]{1,2}|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.([0-9]{1,2}|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.([0-9]{1,2}|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(172\.(1[6-9]|2[0-9]|3[0-1])\.([0-9]{1,2}|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.([0-9]{1,2}|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(192\.168\.([0-9]{1,2}|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.([0-9]{1,2}|1[0-9]{2}|2[0-4][0-9]|25[0-5])))$' || { echo "ERROR: Error fetching the router's LAN IP: $ROUTER_IP" >&2; exit 1; }
check_preferred_dns_alive() {
$RESOLVER $TEST_QUERY $PREFERRED_DNS >/dev/null 2>&1
}
update_to_failover() {
dnsfw_base=$(uci get dhcp.@dnsmasq[0].server | sed "s~\s\?$PREFERRED_DNS~~g")
dnsfw_new="$dnsfw_base $FALLBACK_DNS"
$debug && echo "DEBUG: uci set dhcp.@dnsmasq[0].server=\"$dnsfw_new\"" >&2
$debug || uci set dhcp.@dnsmasq[0].server="$dnsfw_new"
dhcp_base=$(uci get dhcp.lan.dhcp_option | sed "s~\s\?6,$PREFERRED_DNS~~g")
dhcp_new="$dhcp_base 6,$ROUTER_IP"
$debug && echo "DEBUG: uci set dhcp.lan.dhcp_option=\"$dhcp_new\"" >&2
$debug || uci set dhcp.lan.dhcp_option="$dhcp_new"
$debug && echo "DEBUG: restarting dnsmasq.." >&2
$debug || /etc/init.d/dnsmasq restart
echo "INFO: DNS FAILED OVER to: $ROUTER_IP -> $FALLBACK_DNS" >&2
}
update_to_preferred() {
dnsfw_base=$(uci get dhcp.@dnsmasq[0].server | sed "s~\s\?$FALLBACK_DNS~~g")
dnsfw_new="$dnsfw_base $PREFERRED_DNS"
$debug && echo "DEBUG: uci set dhcp.@dnsmasq[0].server="$dnsfw_new"" >&2
$debug || uci set dhcp.@dnsmasq[0].server="$dnsfw_new"
dhcp_base=$(uci get dhcp.lan.dhcp_option | sed "s~\s\?6,$ROUTER_IP~~g")
dhcp_new="$dhcp_base 6,$PREFERRED_DNS"
$debug && echo "DEBUG: uci set dhcp.lan.dhcp_option="$dhcp_new"" >&2
$debug || uci set dhcp.lan.dhcp_option="$dhcp_new"
$debug || /etc/init.d/dnsmasq restart
echo "INFO: DNS RESTORED to: $PREFERRED_DNS" >&2
}
wait_for_dns() {
local target_status="$1" duration="$2"
local elapsed=0 last_status
$debug && echo "DEBUG: Waiting ${duration}s for preferred_dns_status to keep the status $target_status" >&2
while [ $elapsed -lt $duration ]; do
check_preferred_dns_alive && last_status=0 || last_status=1
$debug && echo "DEBUG: check_preferred_dns_alive status: $last_status" >&2
if [ "$last_status" -ne "$target_status" ]; then
echo "WARN: Got DNS server status: $last_status, cancelling operation." >&2
exit 0
fi
sleep $CHECK_INTERVAL
elapsed=$((elapsed + CHECK_INTERVAL))
done
}
get_current_mode() {
if uci get dhcp.@dnsmasq[0].server | grep -qE "[^/]$PREFERRED_DNS"; then
echo PREFERRED
elif uci get dhcp.@dnsmasq[0].server | grep -qE "[^/]$FALLBACK_DNS"; then
echo FALLBACK
else
echo "ERROR: Error determining fallback mode" >&2
exit 1
fi
}
CURRENT_MODE=$(get_current_mode)
DESIRED_MODE=$(check_preferred_dns_alive && echo "PREFERRED" || echo "FALLBACK")
if $debug; then
echo -e "CURRENT=$CURRENT_MODE\nDESIRED=$DESIRED_MODE" >&2
echo "uci get dhcp.@dnsmasq[0].server=$(uci get dhcp.@dnsmasq[0].server)" >&2
echo "uci get dhcp.lan.dhcp_option=$(uci get dhcp.lan.dhcp_option)" >&2
fi
if [ "$CURRENT_MODE" = "$DESIRED_MODE" ]; then
$debug && echo "DEBUG: Current mode already matches desired mode, exiting. ($CURRENT_MODE)" >&2
exit 0
fi
if [ "$DESIRED_MODE" = FALLBACK ]; then
echo "INFO: DNS server unreachable: $PREFERRED_DNS, waiting ${FALLBACK_THRESHOLD}s before failover.." >&2
wait_for_dns 1 $FALLBACK_THRESHOLD && update_to_failover
exit 0
fi
if [ "$DESIRED_MODE" = PREFERRED ]; then
echo "INFO: DNS server back online: $PREFERRED_DNS, waiting ${PREFERRED_THRESHOLD}s before restore.." >&2
wait_for_dns 0 $PREFERRED_THRESHOLD && update_to_preferred
exit 0
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment