cyruz
Occasional Visitor
Hello guys, I just want to share this small script I made to set a DNS fallback when using a resolver inside your network, different from the router itself (AdGuard, PiHole or whatever).
The main target of this setup is to be able to monitor the queries from the network devices on our main resolver (no proxying from the router) and fall back to the router internal resolver in case of unavailability.
The idea is to poll the ARP table to check for the reachability of the resolver and, if not reachable, assign the resolver IP to a sub-interface of the main interface of the router (br0:1) and add a piece of configuration to dnsmasq. The only issue I found so far, is the client-side ARP table update timing. From my test on a MacOS Sonoma, I had variable results, with the arp table updated in around ~1 minute. In the worst case a reconnection should fix everything.
The setup:
The script:
It's basically a hack and as such, I'm aware it can break anytime. If you have any suggestion, please feel free to share it.
---
EDIT: 20.01.2024 - rev 0.2 - implemented dave14305 suggestions
EDIT: 25.01.2024 - rev 0.3 - implemented Martinski suggestions
EDIT: 27.01.2024 - rev 0.4 - run through cru
EDIT: 27.01.2024 - rev 0.5 - implemented SomeWhereOverTheRainBow suggestions
EDIT: 27.01.2024 - rev 0.6 - added log limit management & modified script test presence with -x
EDIT: 16.02.2024 - rev 0.7 - added email notifications
---
The main target of this setup is to be able to monitor the queries from the network devices on our main resolver (no proxying from the router) and fall back to the router internal resolver in case of unavailability.
The idea is to poll the ARP table to check for the reachability of the resolver and, if not reachable, assign the resolver IP to a sub-interface of the main interface of the router (br0:1) and add a piece of configuration to dnsmasq. The only issue I found so far, is the client-side ARP table update timing. From my test on a MacOS Sonoma, I had variable results, with the arp table updated in around ~1 minute. In the worst case a reconnection should fix everything.
The setup:
- Set the router DHCP server to release only the DNS resolver IP and disable "Advertise router's IP in addition to user-specified DNS".
- Due to the router being the fallback, set the WAN DNS to an external DNS of your liking.
The script:
- Write the following script to /jffs/scripts/dns-fallback, adjust the RESOLVER_MAC and RESOLVER_IP variables (and any other variable, if you need) and make it executable with
chmod 755 /jffs/scripts/dns-fallback
Bash:#!/bin/sh # dns-fallback # ------------ # Poll for the availability of the DNS resolver through the ARP table, # if not available, assign the resolver IP to a router subinterface and restart dnsmasq, # if available, revert everything and let the DNS resolver do its job. # * The script must be executable and its path should be "/jffs/scripts/dns-fallback". # * The variables should be adjusted to your liking. # * To run this script with cron (recommended) add the following line to "/jffs/scripts/services-start": # [ -x /jffs/scripts/dns-fallback ] && cru a dns-fallback "* * * * * /jffs/scripts/dns-fallback > /dev/null" # Adjust the '* * * * *' part to your liking. Suggested values are '*/5 * * * *' or '*/10 * * * *'. # * To run this script continuosly, un-comment the while loop and the sleep and # add the following line to "/jffs/script/services-start": # [ -x /jffs/scripts/dns-fallback ] && /jffs/scripts/dns-fallback > /dev/null & # * Logging enabled by default, empty "LOG_FILE" variable to disable it. # * Email notification disabled by default, set "MAIL_NOTIFY" variable to 1 to enable it. Tested with "Brevo". # ------------ # Variables # --------- RESOLVER_MAC=02:42:c0:a8:01:64 RESOLVER_IP=192.168.1.100 NETMASK=255.255.255.0 INTERFACE=br0 SUB_IF=:1 POLLING_TIME=60s CONF_FILE=/jffs/configs/dnsmasq.conf.add LOCK_FILE=/tmp/dns-fallback.lock LOG_FILE=/tmp/dns-fallback.log LOG_MAX_SIZE=1024000 MAIL_NOTIFY=0 MAIL_SMTP=smtp-relay.brevo.com:587 MAIL_USER=someuser@icloud.com MAIL_PASSWORD="mypassword" MAIL_FROM=someuser@icloud.com MAIL_NAME="ASUS GT-AX11000" MAIL_NAME_FROM="asus-gt-ax11000@local.lan" MAIL_TO=someuser@icloud.com MAIL_SUBJECT="DNS fallback notifier" # --------- mail() { [ "${MAIL_NOTIFY}" -eq "1" ] && sendmail -S"${MAIL_SMTP}" -f"${MAIL_FROM}" ${MAIL_TO} -au"${MAIL_USER}" -ap"${MAIL_PASSWORD}" << EOM Subject: ${MAIL_SUBJECT} From: \"${MAIL_NAME}\" <${MAIL_NAME_FROM}> Date: `date -R` $@ EOM } log() { [ -n "${LOG_FILE}" ] && echo "[`date '+%F %H:%M:%S'`] ${SCRIPT} :: $@" | tee -a ${LOG_FILE} } # Quit if the script is already running. SCRIPT=`basename $0` PID=`pidof ${SCRIPT}` [ "`echo ${PID} | wc -w`" -gt "1" ] && log "script already running!" && exit trap '' HUP INT QUIT ABRT TERM TSTP touch ${LOCK_FILE} # Delete the log file if the size is greater than max. [ "`wc -c ${LOG_FILE} | tr -dc '0-9'`" -gt "${LOG_MAX_SIZE}" ] && rm -f ${LOG_FILE} SED_PATTERN="^(no-dhcp-)?interface=${INTERFACE}${SUB_IF}" #while true; do ( flock -x 200 ping -I ${INTERFACE} -c 1 ${RESOLVER_IP} > /dev/null && sleep 1s if arp -D -i ${INTERFACE} | grep -i ${RESOLVER_MAC} > /dev/null; then log "resolver found" if grep -E "${SED_PATTERN}" ${CONF_FILE} > /dev/null 2>&1; then log "<${CONF_FILE}> present - disabling router dnsmasq on ${INTERFACE}${SUB_IF}" ifconfig ${INTERFACE}${SUB_IF} down && log "interface ${INTERFACE}${SUB_IF} disabled" sed -i -E "/${SED_PATTERN}/d" ${CONF_FILE} && log "dnsmasq configuration addition removed from <${CONF_FILE}>" [ -s ${CONF_FILE} ] || rm -f ${CONF_FILE} && log "<${CONF_FILE}> is empty - removed" sleep 1s && service restart_dnsmasq && log "dnsmasq service restarted" mail "Resolver found, deconfigured router dsnmasq resolver." fi else log "resolver not found - fallback to router dnsmasq" if ! grep -E "${SED_PATTERN}" ${CONF_FILE} > /dev/null 2>&1; then log "dnsmasq configuration addition not present - appending it to <${CONF_FILE}>" echo "interface=${INTERFACE}${SUB_IF}" >> ${CONF_FILE} echo "no-dhcp-interface=${INTERFACE}${SUB_IF}" >> ${CONF_FILE} ifconfig ${INTERFACE}${SUB_IF} ${RESOLVER_IP} netmask ${NETMASK} up && log "interface ${INTERFACE}${SUB_IF} configured with IP: ${RESOLVER_IP} and NETMA sleep 1s && service restart_dnsmasq && log "dnsmasq service restarted" mail "Resolver not found, configured router dnsmasq resolver." fi fi log "###" ) 200>"${LOCK_FILE}" # sleep ${POLLING_TIME} #done
- Add the following line to /jffs/scripts/services-start:
Bash:[ -x /jffs/scripts/dns-fallback ] && cru a dns-fallback "* * * * * /jffs/scripts/dns-fallback > /dev/null"
Adjust* * * * *
(every minute) to your liking. Suggested values are*/5 * * * *
(every 5 minutes) or*/10 * * * *
(every 10 minutes).
It's basically a hack and as such, I'm aware it can break anytime. If you have any suggestion, please feel free to share it.
---
EDIT: 20.01.2024 - rev 0.2 - implemented dave14305 suggestions
EDIT: 25.01.2024 - rev 0.3 - implemented Martinski suggestions
EDIT: 27.01.2024 - rev 0.4 - run through cru
EDIT: 27.01.2024 - rev 0.5 - implemented SomeWhereOverTheRainBow suggestions
EDIT: 27.01.2024 - rev 0.6 - added log limit management & modified script test presence with -x
EDIT: 16.02.2024 - rev 0.7 - added email notifications
---
Last edited: