From d7a5c9efaa4b69000c0fdc6ce52cd30cc809ba13 Mon Sep 17 00:00:00 2001 From: Laszlo Magyar Date: Thu, 9 Apr 2026 00:53:42 +0200 Subject: [PATCH 01/24] Move MagicDNS egress proxy to non-default port --- .../s6-rc.d/magicdns-egress-proxy/finish | 28 ++++++++++++++ .../s6-rc.d/magicdns-egress-proxy/run | 38 +++++++++++++++++-- 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/finish b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/finish index 25314e796..9969b6db7 100755 --- a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/finish +++ b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/finish @@ -10,6 +10,34 @@ readonly exit_code_service="${1}" readonly exit_code_signal="${2}" readonly service="magicdns-egress-proxy" +readonly DNSMASQ_EGRESS_ADDRESS_IPV4="127.100.100.100" + +function remove_forwarding() { + local cmd="${1}" + local ip_version="${2}" + local address_bits="${3}" + local from_address="${4}" + local to_address="${5}" + + local variables + local proto + local to_port + + for variables in $( \ + ${cmd} -t nat -S OUTPUT \ + | { grep -E "^-A OUTPUT -d ${from_address//[\[\]]/}/${address_bits} -p \S+ -m \S+ --dport 53 -j DNAT --to-destination $(sed -r 's/(\[|\])/\\\1/g' <<< "${to_address}"):\d+$" || true ;} \ + | sed -nr 's/^.*?-p\s(\S+).*?:(\d+)$/\1|\2/p') + do + IFS='|' read -r proto to_port <<< "${variables}" + bashio::log.info "Removing forwarding for MagicDNS egress proxy (${proto}, ${ip_version})" + if ! ${cmd} -t nat -D OUTPUT -d "${from_address//[\[\]]/}" -p "${proto}" --dport 53 -j DNAT --to-destination "${to_address}:${to_port}"; then + bashio::log.warning "Removing forwarding for MagicDNS egress proxy is unsuccessful (${proto}, ${ip_version})" + fi + done +} + +remove_forwarding "iptables" "IPv4" "32" "${DNSMASQ_EGRESS_ADDRESS_IPV4}" "${DNSMASQ_EGRESS_ADDRESS_IPV4}" + bashio::log.info \ "Service ${service} exited with code ${exit_code_service}" \ "(by signal ${exit_code_signal})" diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/run b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/run index 3bc59adef..21b4cb257 100755 --- a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/run +++ b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/run @@ -19,13 +19,14 @@ export LOG_FD source /usr/lib/trace.sh readonly DNSMASQ_EGRESS_ADDRESS_IPV4="127.100.100.100" -readonly DNSMASQ_EGRESS_PORT=53 +readonly DNSMASQ_EGRESS_DEFAULT_PORT=12753 readonly DNSMASQ_BLACK_WHITE_LIST_LOCATION="/etc/dnsmasq-black-white-list" declare hassio_dns_ipv4 declare -a white_list declare domain declare -a options +declare dnsmasq_egress_port bashio::log.info "Starting MagicDNS egress proxy..." @@ -36,6 +37,35 @@ function dig_hassio_dns() { | head -n 1 } +function setup_forwarding() { + local cmd="${1}" + local proto="${2}" + local ip_version="${3}" + local address_bits="${4}" + local from_address="${5}" + local to_address="${6}" + local to_port="${7}" + + bashio::log.info "Setting up forwarding for MagicDNS egress proxy (${proto}, ${ip_version})" + if ${cmd} -t nat -S OUTPUT \ + | grep -Eq "^-A OUTPUT -d ${from_address//[\[\]]/}/${address_bits} -p ${proto} -m ${proto} --dport 53 -j DNAT --to-destination $(sed -r 's/(\[|\])/\\\1/g' <<< "${to_address}"):${to_port}$" + then + bashio::log.notice "Forwarding is already set for MagicDNS egress proxy (${proto}, ${ip_version})" + else + if ! ${cmd} -t nat -A OUTPUT -d "${from_address//[\[\]]/}" -p "${proto}" --dport 53 -j DNAT --to-destination "${to_address}:${to_port}"; then + bashio::exit.nok "Setting up forwarding for MagicDNS egress proxy is unsuccessful (${proto}, ${ip_version})" + fi + fi +} + +# Search for unallocated port in case of collision +dnsmasq_egress_port="${DNSMASQ_EGRESS_DEFAULT_PORT}" +while netstat -lntu | awk '{print $4}' | grep -q ":${dnsmasq_egress_port}$"; do + if ((++dnsmasq_egress_port == 65536)); then + bashio::exit.nok "Can't find free port for MagicDNS egress proxy" + fi +done + options+=(--no-hosts) options+=(--no-resolv) options+=(--conf-file=/dev/null) @@ -45,7 +75,7 @@ options+=(--cache-size=0) options+=(--listen-address="${DNSMASQ_EGRESS_ADDRESS_IPV4}") options+=(--bind-dynamic) -options+=(--port="${DNSMASQ_EGRESS_PORT}") +options+=(--port="${dnsmasq_egress_port}") # Hassio DNS's IP addresses if ! hassio_dns_ipv4=$(dig_hassio_dns A) || \ @@ -68,10 +98,12 @@ if bashio_custom::trace; then options+=(--log-debug) fi +setup_forwarding "iptables" "udp" "IPv4" "32" "${DNSMASQ_EGRESS_ADDRESS_IPV4}" "${DNSMASQ_EGRESS_ADDRESS_IPV4}" "${dnsmasq_egress_port}" +setup_forwarding "iptables" "tcp" "IPv4" "32" "${DNSMASQ_EGRESS_ADDRESS_IPV4}" "${DNSMASQ_EGRESS_ADDRESS_IPV4}" "${dnsmasq_egress_port}" + # We need to delay the starting of the dependent services until the conf file is written echo "nameserver ${DNSMASQ_EGRESS_ADDRESS_IPV4}" > /etc/resolv.dnsmasq.conf echo "" >&3 # This DNS forwards the white_list to HA's DNS, otherwise replies NXDOMAIN for everything -# It must run on port 53 to be able to specify it in a resolv.conf exec dnsmasq "${options[@]}" From 9f40826b820d94333b840f58c63f32152d2ef693 Mon Sep 17 00:00:00 2001 From: Laszlo Magyar Date: Thu, 9 Apr 2026 13:41:11 +0200 Subject: [PATCH 02/24] Move egress proxy setup script into separate file like ingress proxy --- .../s6-rc.d/magicdns-egress-proxy/finish | 28 +------- .../s6-rc.d/magicdns-egress-proxy/run | 24 +------ .../usr/bin/magicdns-egress-proxy-forwarding | 72 +++++++++++++++++++ 3 files changed, 74 insertions(+), 50 deletions(-) create mode 100755 tailscale/rootfs/usr/bin/magicdns-egress-proxy-forwarding diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/finish b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/finish index 9969b6db7..41f116b76 100755 --- a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/finish +++ b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/finish @@ -10,33 +10,7 @@ readonly exit_code_service="${1}" readonly exit_code_signal="${2}" readonly service="magicdns-egress-proxy" -readonly DNSMASQ_EGRESS_ADDRESS_IPV4="127.100.100.100" - -function remove_forwarding() { - local cmd="${1}" - local ip_version="${2}" - local address_bits="${3}" - local from_address="${4}" - local to_address="${5}" - - local variables - local proto - local to_port - - for variables in $( \ - ${cmd} -t nat -S OUTPUT \ - | { grep -E "^-A OUTPUT -d ${from_address//[\[\]]/}/${address_bits} -p \S+ -m \S+ --dport 53 -j DNAT --to-destination $(sed -r 's/(\[|\])/\\\1/g' <<< "${to_address}"):\d+$" || true ;} \ - | sed -nr 's/^.*?-p\s(\S+).*?:(\d+)$/\1|\2/p') - do - IFS='|' read -r proto to_port <<< "${variables}" - bashio::log.info "Removing forwarding for MagicDNS egress proxy (${proto}, ${ip_version})" - if ! ${cmd} -t nat -D OUTPUT -d "${from_address//[\[\]]/}" -p "${proto}" --dport 53 -j DNAT --to-destination "${to_address}:${to_port}"; then - bashio::log.warning "Removing forwarding for MagicDNS egress proxy is unsuccessful (${proto}, ${ip_version})" - fi - done -} - -remove_forwarding "iptables" "IPv4" "32" "${DNSMASQ_EGRESS_ADDRESS_IPV4}" "${DNSMASQ_EGRESS_ADDRESS_IPV4}" +magicdns-egress-proxy-forwarding remove forwarding bashio::log.info \ "Service ${service} exited with code ${exit_code_service}" \ diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/run b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/run index 21b4cb257..a2b3bc3b4 100755 --- a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/run +++ b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/run @@ -37,27 +37,6 @@ function dig_hassio_dns() { | head -n 1 } -function setup_forwarding() { - local cmd="${1}" - local proto="${2}" - local ip_version="${3}" - local address_bits="${4}" - local from_address="${5}" - local to_address="${6}" - local to_port="${7}" - - bashio::log.info "Setting up forwarding for MagicDNS egress proxy (${proto}, ${ip_version})" - if ${cmd} -t nat -S OUTPUT \ - | grep -Eq "^-A OUTPUT -d ${from_address//[\[\]]/}/${address_bits} -p ${proto} -m ${proto} --dport 53 -j DNAT --to-destination $(sed -r 's/(\[|\])/\\\1/g' <<< "${to_address}"):${to_port}$" - then - bashio::log.notice "Forwarding is already set for MagicDNS egress proxy (${proto}, ${ip_version})" - else - if ! ${cmd} -t nat -A OUTPUT -d "${from_address//[\[\]]/}" -p "${proto}" --dport 53 -j DNAT --to-destination "${to_address}:${to_port}"; then - bashio::exit.nok "Setting up forwarding for MagicDNS egress proxy is unsuccessful (${proto}, ${ip_version})" - fi - fi -} - # Search for unallocated port in case of collision dnsmasq_egress_port="${DNSMASQ_EGRESS_DEFAULT_PORT}" while netstat -lntu | awk '{print $4}' | grep -q ":${dnsmasq_egress_port}$"; do @@ -98,8 +77,7 @@ if bashio_custom::trace; then options+=(--log-debug) fi -setup_forwarding "iptables" "udp" "IPv4" "32" "${DNSMASQ_EGRESS_ADDRESS_IPV4}" "${DNSMASQ_EGRESS_ADDRESS_IPV4}" "${dnsmasq_egress_port}" -setup_forwarding "iptables" "tcp" "IPv4" "32" "${DNSMASQ_EGRESS_ADDRESS_IPV4}" "${DNSMASQ_EGRESS_ADDRESS_IPV4}" "${dnsmasq_egress_port}" +magicdns-egress-proxy-forwarding setup forwarding "${dnsmasq_egress_port}" # We need to delay the starting of the dependent services until the conf file is written echo "nameserver ${DNSMASQ_EGRESS_ADDRESS_IPV4}" > /etc/resolv.dnsmasq.conf diff --git a/tailscale/rootfs/usr/bin/magicdns-egress-proxy-forwarding b/tailscale/rootfs/usr/bin/magicdns-egress-proxy-forwarding new file mode 100755 index 000000000..7043de036 --- /dev/null +++ b/tailscale/rootfs/usr/bin/magicdns-egress-proxy-forwarding @@ -0,0 +1,72 @@ +#!/usr/bin/env bashio +# shellcheck shell=bash +export LOG_FD +# ============================================================================== +# Setup DNAT for outgoing MagicDNS queries toward egress dnsmasq proxy +# ============================================================================== + +readonly DNSMASQ_EGRESS_ADDRESS_IPV4="127.100.100.100" + +function setup_forwarding() { + local cmd="${1}" + local proto="${2}" + local ip_version="${3}" + local address_bits="${4}" + local from_address="${5}" + local to_address="${6}" + local to_port="${7}" + + bashio::log.info "Setting up forwarding for MagicDNS egress proxy (${proto}, ${ip_version})" + if ${cmd} -t nat -S OUTPUT \ + | grep -Eq "^-A OUTPUT -d ${from_address//[\[\]]/}/${address_bits} -p ${proto} -m ${proto} --dport 53 -j DNAT --to-destination $(sed -r 's/(\[|\])/\\\1/g' <<< "${to_address}"):${to_port}$" + then + bashio::log.notice "Forwarding is already set for MagicDNS egress proxy (${proto}, ${ip_version})" + else + if ! ${cmd} -t nat -A OUTPUT -d "${from_address//[\[\]]/}" -p "${proto}" --dport 53 -j DNAT --to-destination "${to_address}:${to_port}"; then + bashio::exit.nok "Setting up forwarding for MagicDNS egress proxy is unsuccessful (${proto}, ${ip_version})" + fi + fi +} + +function remove_forwarding() { + local cmd="${1}" + local ip_version="${2}" + local address_bits="${3}" + local from_address="${4}" + local to_address="${5}" + + local variables + local proto + local to_port + + for variables in $( \ + ${cmd} -t nat -S OUTPUT \ + | { grep -E "^-A OUTPUT -d ${from_address//[\[\]]/}/${address_bits} -p \S+ -m \S+ --dport 53 -j DNAT --to-destination $(sed -r 's/(\[|\])/\\\1/g' <<< "${to_address}"):\d+$" || true ;} \ + | sed -nr 's/^.*?-p\s(\S+).*?:(\d+)$/\1|\2/p') + do + IFS='|' read -r proto to_port <<< "${variables}" + bashio::log.info "Removing forwarding for MagicDNS egress proxy (${proto}, ${ip_version})" + if ! ${cmd} -t nat -D OUTPUT -d "${from_address//[\[\]]/}" -p "${proto}" --dport 53 -j DNAT --to-destination "${to_address}:${to_port}"; then + bashio::log.warning "Removing forwarding for MagicDNS egress proxy is unsuccessful (${proto}, ${ip_version})" + fi + done +} + +case "${1-}-${2-}" in + setup-forwarding) + if ! bashio::var.has_value "${3-}"; then + echo "Port is missing" 1>&2 + echo "Usage: $(basename "$0") setup forwarding port" 1>&2 + exit 1 + fi + setup_forwarding "iptables" "udp" "IPv4" "32" "${DNSMASQ_EGRESS_ADDRESS_IPV4}" "${DNSMASQ_EGRESS_ADDRESS_IPV4}" "${3}" + setup_forwarding "iptables" "tcp" "IPv4" "32" "${DNSMASQ_EGRESS_ADDRESS_IPV4}" "${DNSMASQ_EGRESS_ADDRESS_IPV4}" "${3}" + ;; + remove-forwarding) + remove_forwarding "iptables" "IPv4" "32" "${DNSMASQ_EGRESS_ADDRESS_IPV4}" "${DNSMASQ_EGRESS_ADDRESS_IPV4}" + ;; + *) + echo "Usage: $(basename "$0") setup|remove forwarding [port in case of setup forwarding]" 1>&2 + exit 1 + ;; +esac From e3046608015cf7280552032937f0d45d755bb753 Mon Sep 17 00:00:00 2001 From: Laszlo Magyar Date: Thu, 9 Apr 2026 13:43:30 +0200 Subject: [PATCH 03/24] Move MagicDNS ingress proxy to non-default port --- .../s6-rc.d/magicdns-ingress-proxy/run | 15 ++++- .../usr/bin/magicdns-ingress-proxy-forwarding | 55 ++++++++++--------- 2 files changed, 42 insertions(+), 28 deletions(-) diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/run b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/run index 7e8e54677..8cebb33e7 100755 --- a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/run +++ b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/run @@ -16,7 +16,7 @@ source /usr/lib/trace.sh readonly MAGIC_DNS_IPV4="100.100.100.100" -readonly DNSMASQ_INGRESS_PORT=53 +readonly DNSMASQ_INGRESS_DEFAULT_PORT=12753 readonly DNSMASQ_BLACK_WHITE_LIST_LOCATION="/etc/dnsmasq-black-white-list" declare tailscale_address_ipv4 @@ -25,9 +25,18 @@ declare -a black_list declare -a white_list declare domain declare -a options +declare dnsmasq_ingress_port bashio::log.info "Starting MagicDNS ingress proxy..." +# Search for unallocated port in case of collision +dnsmasq_ingress_port="${DNSMASQ_INGRESS_DEFAULT_PORT}" +while netstat -lntu | awk '{print $4}' | grep -q ":${dnsmasq_ingress_port}$"; do + if ((++dnsmasq_ingress_port == 65536)); then + bashio::exit.nok "Can't find free port for MagicDNS ingress proxy" + fi +done + options+=(--no-hosts) options+=(--no-resolv) options+=(--conf-file=/dev/null) @@ -52,7 +61,7 @@ if bashio::var.has_value "${tailscale_address_ipv6-}"; then fi options+=(--bind-dynamic) -options+=(--port=${DNSMASQ_INGRESS_PORT}) +options+=(--port=${dnsmasq_ingress_port}) if bashio::config.true "accept_dns"; then # Black-list @@ -79,7 +88,7 @@ if bashio_custom::trace; then options+=(--log-debug) fi -magicdns-ingress-proxy-forwarding setup forwarding +magicdns-ingress-proxy-forwarding setup forwarding "${dnsmasq_ingress_port}" magicdns-ingress-proxy-forwarding remove drop # We need to delay the starting of the dependent services until iptables are configured diff --git a/tailscale/rootfs/usr/bin/magicdns-ingress-proxy-forwarding b/tailscale/rootfs/usr/bin/magicdns-ingress-proxy-forwarding index 9f4ddb0b3..ecaf6da98 100755 --- a/tailscale/rootfs/usr/bin/magicdns-ingress-proxy-forwarding +++ b/tailscale/rootfs/usr/bin/magicdns-ingress-proxy-forwarding @@ -8,8 +8,6 @@ export LOG_FD readonly MAGIC_DNS_IPV4="100.100.100.100" readonly MAGIC_DNS_IPV6="fd7a:115c:a1e0::53" -readonly DNSMASQ_INGRESS_PORT=53 - declare hassio_dns_ipv4 declare hassio_dns_ipv6 declare hassio_supervisor_ipv4 @@ -55,17 +53,19 @@ function remove_forwarding() { local source_address="${4}" local from_address="${5}" local to_address="${6}" - local to_port="${7}" - local name="${8}" - local source_name="${9}" + local name="${7}" + local source_name="${8}" + local variables local proto + local to_port - for proto in $( \ + for variables in $( \ ${cmd} -t nat -S PREROUTING \ - | { grep -E "^-A PREROUTING -s ${source_address//[\[\]]/}/${address_bits} -d ${from_address//[\[\]]/}/${address_bits} -i hassio -p \S+ -m \S+ --dport 53 -j DNAT --to-destination $(sed -r 's/(\[|\])/\\\1/g' <<< "${to_address}"):${to_port}$" || true ;} \ - | sed -nr 's/^.*?-p\s(\S+).*$/\1/p') + | { grep -E "^-A PREROUTING -s ${source_address//[\[\]]/}/${address_bits} -d ${from_address//[\[\]]/}/${address_bits} -i hassio -p \S+ -m \S+ --dport 53 -j DNAT --to-destination $(sed -r 's/(\[|\])/\\\1/g' <<< "${to_address}"):\d+$" || true ;} \ + | sed -nr 's/^.*?-p\s(\S+).*?:(\d+)$/\1|\2/p') do + IFS='|' read -r proto to_port <<< "${variables}" bashio::log.info "Removing ${name} for MagicDNS ingress proxy (${proto}, ${ip_version}, ${source_name})" if ! ${cmd} -t nat -D PREROUTING -s "${source_address//[\[\]]/}" -d "${from_address//[\[\]]/}" -i hassio -p "${proto}" --dport 53 -j DNAT --to-destination "${to_address}:${to_port}"; then bashio::log.warning "Removing ${name} for MagicDNS ingress proxy is unsuccessful (${proto}, ${ip_version}, ${source_name})" @@ -158,53 +158,58 @@ case "${1-}-${2-}" in fi ;; setup-forwarding) + if ! bashio::var.has_value "${3-}"; then + echo "Port is missing" 1>&2 + echo "Usage: $(basename "$0") setup forwarding port" 1>&2 + exit 1 + fi if bashio::var.has_value "${hassio_dns_ipv4-}"; then - setup_forwarding "iptables" "A" "udp" "IPv4" "32" "${hassio_dns_ipv4}" "${MAGIC_DNS_IPV4}" "${tailscale_address_ipv4}" "${DNSMASQ_INGRESS_PORT}" "${2}" "DNS" - setup_forwarding "iptables" "A" "tcp" "IPv4" "32" "${hassio_dns_ipv4}" "${MAGIC_DNS_IPV4}" "${tailscale_address_ipv4}" "${DNSMASQ_INGRESS_PORT}" "${2}" "DNS" + setup_forwarding "iptables" "A" "udp" "IPv4" "32" "${hassio_dns_ipv4}" "${MAGIC_DNS_IPV4}" "${tailscale_address_ipv4}" "${3}" "${2}" "DNS" + setup_forwarding "iptables" "A" "tcp" "IPv4" "32" "${hassio_dns_ipv4}" "${MAGIC_DNS_IPV4}" "${tailscale_address_ipv4}" "${3}" "${2}" "DNS" fi if bashio::var.has_value "${hassio_dns_ipv6-}"; then - setup_forwarding "ip6tables" "A" "udp" "IPv6" "128" "${hassio_dns_ipv6}" "${MAGIC_DNS_IPV6}" "[${tailscale_address_ipv6}]" "${DNSMASQ_INGRESS_PORT}" "${2}" "DNS" - setup_forwarding "ip6tables" "A" "tcp" "IPv6" "128" "${hassio_dns_ipv6}" "${MAGIC_DNS_IPV6}" "[${tailscale_address_ipv6}]" "${DNSMASQ_INGRESS_PORT}" "${2}" "DNS" + setup_forwarding "ip6tables" "A" "udp" "IPv6" "128" "${hassio_dns_ipv6}" "${MAGIC_DNS_IPV6}" "[${tailscale_address_ipv6}]" "${3}" "${2}" "DNS" + setup_forwarding "ip6tables" "A" "tcp" "IPv6" "128" "${hassio_dns_ipv6}" "${MAGIC_DNS_IPV6}" "[${tailscale_address_ipv6}]" "${3}" "${2}" "DNS" fi if bashio::var.has_value "${hassio_supervisor_ipv4-}"; then - setup_forwarding "iptables" "A" "udp" "IPv4" "32" "${hassio_supervisor_ipv4}" "${MAGIC_DNS_IPV4}" "${tailscale_address_ipv4}" "${DNSMASQ_INGRESS_PORT}" "${2}" "Supervisor" - setup_forwarding "iptables" "A" "tcp" "IPv4" "32" "${hassio_supervisor_ipv4}" "${MAGIC_DNS_IPV4}" "${tailscale_address_ipv4}" "${DNSMASQ_INGRESS_PORT}" "${2}" "Supervisor" + setup_forwarding "iptables" "A" "udp" "IPv4" "32" "${hassio_supervisor_ipv4}" "${MAGIC_DNS_IPV4}" "${tailscale_address_ipv4}" "${3}" "${2}" "Supervisor" + setup_forwarding "iptables" "A" "tcp" "IPv4" "32" "${hassio_supervisor_ipv4}" "${MAGIC_DNS_IPV4}" "${tailscale_address_ipv4}" "${3}" "${2}" "Supervisor" fi if bashio::var.has_value "${hassio_supervisor_ipv6-}"; then - setup_forwarding "ip6tables" "A" "udp" "IPv6" "128" "${hassio_supervisor_ipv6}" "${MAGIC_DNS_IPV6}" "[${tailscale_address_ipv6}]" "${DNSMASQ_INGRESS_PORT}" "${2}" "Supervisor" - setup_forwarding "ip6tables" "A" "tcp" "IPv6" "128" "${hassio_supervisor_ipv6}" "${MAGIC_DNS_IPV6}" "[${tailscale_address_ipv6}]" "${DNSMASQ_INGRESS_PORT}" "${2}" "Supervisor" + setup_forwarding "ip6tables" "A" "udp" "IPv6" "128" "${hassio_supervisor_ipv6}" "${MAGIC_DNS_IPV6}" "[${tailscale_address_ipv6}]" "${3}" "${2}" "Supervisor" + setup_forwarding "ip6tables" "A" "tcp" "IPv6" "128" "${hassio_supervisor_ipv6}" "${MAGIC_DNS_IPV6}" "[${tailscale_address_ipv6}]" "${3}" "${2}" "Supervisor" fi ;; remove-drop) if bashio::var.has_value "${hassio_dns_ipv4-}"; then - remove_forwarding "iptables" "IPv4" "32" "${hassio_dns_ipv4}" "${MAGIC_DNS_IPV4}" "127.0.0.1" "0" "${2}" "DNS" + remove_forwarding "iptables" "IPv4" "32" "${hassio_dns_ipv4}" "${MAGIC_DNS_IPV4}" "127.0.0.1" "${2}" "DNS" fi if bashio::var.has_value "${hassio_dns_ipv6-}"; then - remove_forwarding "ip6tables" "IPv6" "128" "${hassio_dns_ipv6}" "${MAGIC_DNS_IPV6}" "[::1]" "0" "${2}" "DNS" + remove_forwarding "ip6tables" "IPv6" "128" "${hassio_dns_ipv6}" "${MAGIC_DNS_IPV6}" "[::1]" "${2}" "DNS" fi if bashio::var.has_value "${hassio_supervisor_ipv4-}"; then - remove_forwarding "iptables" "IPv4" "32" "${hassio_supervisor_ipv4}" "${MAGIC_DNS_IPV4}" "127.0.0.1" "0" "${2}" "Supervisor" + remove_forwarding "iptables" "IPv4" "32" "${hassio_supervisor_ipv4}" "${MAGIC_DNS_IPV4}" "127.0.0.1" "${2}" "Supervisor" fi if bashio::var.has_value "${hassio_supervisor_ipv6-}"; then - remove_forwarding "ip6tables" "IPv6" "128" "${hassio_supervisor_ipv6}" "${MAGIC_DNS_IPV6}" "[::1]" "0" "${2}" "Supervisor" + remove_forwarding "ip6tables" "IPv6" "128" "${hassio_supervisor_ipv6}" "${MAGIC_DNS_IPV6}" "[::1]" "${2}" "Supervisor" fi ;; remove-forwarding) if bashio::var.has_value "${hassio_dns_ipv4-}"; then - remove_forwarding "iptables" "IPv4" "32" "${hassio_dns_ipv4}" "${MAGIC_DNS_IPV4}" "${tailscale_address_ipv4}" "${DNSMASQ_INGRESS_PORT}" "${2}" "DNS" + remove_forwarding "iptables" "IPv4" "32" "${hassio_dns_ipv4}" "${MAGIC_DNS_IPV4}" "${tailscale_address_ipv4}" "${2}" "DNS" fi if bashio::var.has_value "${hassio_dns_ipv6-}"; then - remove_forwarding "ip6tables" "IPv6" "128" "${hassio_dns_ipv6}" "${MAGIC_DNS_IPV6}" "[${tailscale_address_ipv6}]" "${DNSMASQ_INGRESS_PORT}" "${2}" "DNS" + remove_forwarding "ip6tables" "IPv6" "128" "${hassio_dns_ipv6}" "${MAGIC_DNS_IPV6}" "[${tailscale_address_ipv6}]" "${2}" "DNS" fi if bashio::var.has_value "${hassio_supervisor_ipv4-}"; then - remove_forwarding "iptables" "IPv4" "32" "${hassio_supervisor_ipv4}" "${MAGIC_DNS_IPV4}" "${tailscale_address_ipv4}" "${DNSMASQ_INGRESS_PORT}" "${2}" "Supervisor" + remove_forwarding "iptables" "IPv4" "32" "${hassio_supervisor_ipv4}" "${MAGIC_DNS_IPV4}" "${tailscale_address_ipv4}" "${2}" "Supervisor" fi if bashio::var.has_value "${hassio_supervisor_ipv6-}"; then - remove_forwarding "ip6tables" "IPv6" "128" "${hassio_supervisor_ipv6}" "${MAGIC_DNS_IPV6}" "[${tailscale_address_ipv6}]" "${DNSMASQ_INGRESS_PORT}" "${2}" "Supervisor" + remove_forwarding "ip6tables" "IPv6" "128" "${hassio_supervisor_ipv6}" "${MAGIC_DNS_IPV6}" "[${tailscale_address_ipv6}]" "${2}" "Supervisor" fi ;; *) - echo "Usage: $(basename "$0") setup|remove drop|forwarding" 1>&2 + echo "Usage: $(basename "$0") setup|remove drop|forwarding [port in case of setup forwarding]" 1>&2 exit 1 ;; esac From 9dcf4f365ef980bc69fe924a9be63277bed15bd0 Mon Sep 17 00:00:00 2001 From: Laszlo Magyar Date: Fri, 10 Apr 2026 14:03:12 +0200 Subject: [PATCH 04/24] replace bind-dynamic with bind-interfaces for egress proxy --- .../rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/run | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/run b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/run index a2b3bc3b4..f7470bd60 100755 --- a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/run +++ b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/run @@ -53,7 +53,7 @@ options+=(--log-facility='-') options+=(--cache-size=0) options+=(--listen-address="${DNSMASQ_EGRESS_ADDRESS_IPV4}") -options+=(--bind-dynamic) +options+=(--bind-interfaces) options+=(--port="${dnsmasq_egress_port}") # Hassio DNS's IP addresses From 66f54fbebb41c73a79c6a4268b77eeea61232c6b Mon Sep 17 00:00:00 2001 From: Laszlo Magyar Date: Fri, 10 Apr 2026 22:55:44 +0200 Subject: [PATCH 05/24] use different ports for the proxies --- .../etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/run | 9 ++++++--- .../etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/run | 9 ++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/run b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/run index f7470bd60..4a0b14359 100755 --- a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/run +++ b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/run @@ -19,7 +19,7 @@ export LOG_FD source /usr/lib/trace.sh readonly DNSMASQ_EGRESS_ADDRESS_IPV4="127.100.100.100" -readonly DNSMASQ_EGRESS_DEFAULT_PORT=12753 +readonly DNSMASQ_EGRESS_DEFAULT_PORT=53127 readonly DNSMASQ_BLACK_WHITE_LIST_LOCATION="/etc/dnsmasq-black-white-list" declare hassio_dns_ipv4 @@ -39,8 +39,11 @@ function dig_hassio_dns() { # Search for unallocated port in case of collision dnsmasq_egress_port="${DNSMASQ_EGRESS_DEFAULT_PORT}" -while netstat -lntu | awk '{print $4}' | grep -q ":${dnsmasq_egress_port}$"; do - if ((++dnsmasq_egress_port == 65536)); then +while netstat -lntu \ + | awk '{print $4}' \ + | grep -q ":${dnsmasq_egress_port}$" +do + if ((++dnsmasq_egress_port == DNSMASQ_EGRESS_DEFAULT_PORT + 1000)); then bashio::exit.nok "Can't find free port for MagicDNS egress proxy" fi done diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/run b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/run index 8cebb33e7..6cd43de80 100755 --- a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/run +++ b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/run @@ -16,7 +16,7 @@ source /usr/lib/trace.sh readonly MAGIC_DNS_IPV4="100.100.100.100" -readonly DNSMASQ_INGRESS_DEFAULT_PORT=12753 +readonly DNSMASQ_INGRESS_DEFAULT_PORT=51100 readonly DNSMASQ_BLACK_WHITE_LIST_LOCATION="/etc/dnsmasq-black-white-list" declare tailscale_address_ipv4 @@ -31,8 +31,11 @@ bashio::log.info "Starting MagicDNS ingress proxy..." # Search for unallocated port in case of collision dnsmasq_ingress_port="${DNSMASQ_INGRESS_DEFAULT_PORT}" -while netstat -lntu | awk '{print $4}' | grep -q ":${dnsmasq_ingress_port}$"; do - if ((++dnsmasq_ingress_port == 65536)); then +while netstat -lntu \ + | awk '{print $4}' \ + | grep -q ":${dnsmasq_ingress_port}$" +do + if ((++dnsmasq_ingress_port == DNSMASQ_INGRESS_DEFAULT_PORT + 1000)); then bashio::exit.nok "Can't find free port for MagicDNS ingress proxy" fi done From 989caa946629895080f4afecc52ad589bbe40cf8 Mon Sep 17 00:00:00 2001 From: Laszlo Magyar Date: Fri, 10 Apr 2026 22:56:34 +0200 Subject: [PATCH 06/24] tailscaled waits for the egress proxy --- tailscale/rootfs/etc/s6-overlay/s6-rc.d/tailscaled/run | 1 + 1 file changed, 1 insertion(+) diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/tailscaled/run b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/tailscaled/run index 577ef9552..0a022fb0f 100755 --- a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/tailscaled/run +++ b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/tailscaled/run @@ -56,6 +56,7 @@ if ! bashio::fs.file_exists "/etc/resolv.dnsmasq.conf"; then else # Using fake resolv.conf bashio::log.info "Using dnsmasq as upstream DNS server for tailscaled" + bashio::net.wait_for 53 $(awk '{ print $2 }' < /etc/resolv.dnsmasq.conf) mv /etc/resolv.dnsmasq.conf /etc/resolv.for-tailscaled.conf if bashio::debug ; then exec unshare -m bash -c "mount --bind /etc/resolv.for-tailscaled.conf /etc/resolv.conf; exec /opt/tailscaled $(printf "\"%s\" " "${options[@]}")" From 155244fcdaf03027ef507f2cf84eb5d2ec50fe9b Mon Sep 17 00:00:00 2001 From: Laszlo Magyar Date: Sun, 12 Apr 2026 13:16:56 +0200 Subject: [PATCH 07/24] comment on bind-dynamic vs bind-interfaces --- .../rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/run | 3 +++ .../rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/run | 3 +++ 2 files changed, 6 insertions(+) diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/run b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/run index 4a0b14359..a40e28bc3 100755 --- a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/run +++ b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/run @@ -56,6 +56,9 @@ options+=(--log-facility='-') options+=(--cache-size=0) options+=(--listen-address="${DNSMASQ_EGRESS_ADDRESS_IPV4}") +# on the lo interface this bind-interfaces option has the effect that dnsmasq connects only to the specified address +# and on the lo interface bind-interfaces option doesn't result in a logged warning +# this is intentionally different from the ingress proxy, where on a non-lo interface bind-dynamic option has the same effect options+=(--bind-interfaces) options+=(--port="${dnsmasq_egress_port}") diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/run b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/run index 6cd43de80..783b1aa7f 100755 --- a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/run +++ b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/run @@ -63,6 +63,9 @@ if bashio::var.has_value "${tailscale_address_ipv6-}"; then options+=(--listen-address=${tailscale_address_ipv6}) fi +# on a non-lo interface this bind-dynamic option has the effect that dnsmasq connects only to the specified address +# this is intentionally different from the egress proxy, where on the lo interface bind-interfaces option has the same effect +# and on the lo interface bind-interfaces option doesn't result in a logged warning options+=(--bind-dynamic) options+=(--port=${dnsmasq_ingress_port}) From 367b9a9f3b0becfda9ec94c9bbedfb468e7a9b28 Mon Sep 17 00:00:00 2001 From: Laszlo Magyar Date: Thu, 30 Apr 2026 01:51:23 +0200 Subject: [PATCH 08/24] Fix magicdns for appconnectors --- .../s6-rc.d/init-magicdns-ingress-proxy/down | 1 - .../s6-rc.d/init-magicdns-ingress-proxy/up | 1 - .../init-magicdns-proxies-upstream-list/run | 55 --------- .../init-magicdns-proxies-upstream-list/up | 1 - .../dependencies.d/base | 0 .../s6-rc.d/init-magicdns-proxies/down | 1 + .../finish | 0 .../run | 11 +- .../type | 0 .../s6-rc.d/init-magicdns-proxies/up | 1 + .../dependencies.d/init-magicdns-proxies} | 0 .../s6-rc.d/magicdns-egress-proxy/run | 57 +++++++--- .../init-magicdns-proxies-upstream-list | 0 .../magicdns-proxies-configurator} | 0 .../dependencies.d/post-tailscaled | 0 .../s6-rc.d/magicdns-ingress-proxy/finish | 9 +- .../s6-rc.d/magicdns-ingress-proxy/run | 54 ++++++--- .../dependencies.d/post-tailscaled | 0 .../s6-rc.d/magicdns-proxies-configurator/run | 13 +++ .../type | 0 .../s6-rc.d/magicdns-proxies-configurator/up | 1 + .../dependencies.d/magicdns-ingress-proxy} | 0 .../magicdns-proxies-reconfigurator/finish | 27 +++++ .../magicdns-proxies-reconfigurator/run | 51 +++++++++ .../magicdns-proxies-reconfigurator/type | 1 + .../init-magicdns-ingress-proxy | 0 .../etc/s6-overlay/s6-rc.d/tailscaled/run | 5 +- .../magicdns-proxies-reconfigurator} | 0 .../etc/s6-overlay/scripts/stage2_hook.sh | 49 ++++---- .../rootfs/usr/bin/configure-magicdns-proxies | 106 ++++++++++++++++++ tailscale/rootfs/usr/bin/healthcheck | 6 +- 31 files changed, 324 insertions(+), 126 deletions(-) delete mode 100644 tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-ingress-proxy/down delete mode 100644 tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-ingress-proxy/up delete mode 100755 tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-proxies-upstream-list/run delete mode 100644 tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-proxies-upstream-list/up rename tailscale/rootfs/etc/s6-overlay/s6-rc.d/{init-magicdns-ingress-proxy => init-magicdns-proxies}/dependencies.d/base (100%) create mode 100644 tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-proxies/down rename tailscale/rootfs/etc/s6-overlay/s6-rc.d/{init-magicdns-ingress-proxy => init-magicdns-proxies}/finish (100%) rename tailscale/rootfs/etc/s6-overlay/s6-rc.d/{init-magicdns-ingress-proxy => init-magicdns-proxies}/run (75%) rename tailscale/rootfs/etc/s6-overlay/s6-rc.d/{init-magicdns-ingress-proxy => init-magicdns-proxies}/type (100%) create mode 100644 tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-proxies/up rename tailscale/rootfs/etc/s6-overlay/s6-rc.d/{forwarding/dependencies.d/magicdns-ingress-proxy => magicdns-egress-proxy/dependencies.d/init-magicdns-proxies} (100%) delete mode 100644 tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/dependencies.d/init-magicdns-proxies-upstream-list rename tailscale/rootfs/etc/s6-overlay/s6-rc.d/{init-magicdns-proxies-upstream-list/dependencies.d/base => magicdns-ingress-proxy/dependencies.d/magicdns-proxies-configurator} (100%) delete mode 100644 tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/dependencies.d/post-tailscaled rename tailscale/rootfs/etc/s6-overlay/s6-rc.d/{init-magicdns-proxies-upstream-list => magicdns-proxies-configurator}/dependencies.d/post-tailscaled (100%) create mode 100755 tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-configurator/run rename tailscale/rootfs/etc/s6-overlay/s6-rc.d/{init-magicdns-proxies-upstream-list => magicdns-proxies-configurator}/type (100%) create mode 100644 tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-configurator/up rename tailscale/rootfs/etc/s6-overlay/s6-rc.d/{magicdns-egress-proxy/dependencies.d/init-magicdns-proxies-upstream-list => magicdns-proxies-reconfigurator/dependencies.d/magicdns-ingress-proxy} (100%) create mode 100755 tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/finish create mode 100755 tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run create mode 100644 tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/type delete mode 100644 tailscale/rootfs/etc/s6-overlay/s6-rc.d/tailscaled/dependencies.d/init-magicdns-ingress-proxy rename tailscale/rootfs/etc/s6-overlay/s6-rc.d/{magicdns-ingress-proxy/dependencies.d/init-magicdns-ingress-proxy => user/contents.d/magicdns-proxies-reconfigurator} (100%) create mode 100755 tailscale/rootfs/usr/bin/configure-magicdns-proxies diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-ingress-proxy/down b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-ingress-proxy/down deleted file mode 100644 index 50553e745..000000000 --- a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-ingress-proxy/down +++ /dev/null @@ -1 +0,0 @@ -/etc/s6-overlay/s6-rc.d/init-magicdns-ingress-proxy/finish diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-ingress-proxy/up b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-ingress-proxy/up deleted file mode 100644 index 745e46d8c..000000000 --- a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-ingress-proxy/up +++ /dev/null @@ -1 +0,0 @@ -/etc/s6-overlay/s6-rc.d/init-magicdns-ingress-proxy/run diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-proxies-upstream-list/run b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-proxies-upstream-list/run deleted file mode 100755 index e6b661023..000000000 --- a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-proxies-upstream-list/run +++ /dev/null @@ -1,55 +0,0 @@ -#!/command/with-contenv bashio -# shellcheck shell=bash -export LOG_FD -# ============================================================================== -# Home Assistant Community App: Tailscale -# Runs the dnsmasq proxies upstream black/white list initialization -# ============================================================================== - -# Note: This service runs when userspace_networking is disabled, and -# - when accept_dns is enabled, adds login server, log server and letsencrypt api to the black_white_list, -# to prevent DNS query loops (both egress and ingress proxies are running) -# - when accept_dns is disabled, adds tailnet to the black_white_list, -# to prevent logging SERVFAIL by 100.100.100.100 for non-tailnet queries (only ingress proxy is running) - -readonly MAGIC_DNS_IPV4="100.100.100.100" -readonly MAGIC_DNS_IPV6="fd7a:115c:a1e0::53" - -readonly DEFAULT_LOGIN_SERVER="controlplane.tailscale.com" -readonly LOG_SERVER="log.tailscale.com" -readonly LETSENCRYPT_API="acme-v02.api.letsencrypt.org" -readonly DNSMASQ_BLACK_WHITE_LIST_LOCATION="/etc/dnsmasq-black-white-list" - -declare login_server="${DEFAULT_LOGIN_SERVER}" -declare -a black_white_list=() -declare tailnet - -if bashio::config.true "accept_dns"; then - # We have to be able to determine login_server from this address - if ! login_server=$(awk -F[/:] '{print $4}' <<<"$(bashio::config "login_server")") || \ - ! bashio::var.has_value "${login_server}" - then - bashio::exit.nok "Determining host name from '$(bashio::config "login_server")' has failed" - fi - black_white_list+=("${login_server}") - - # When log upload is enabled, resolve log server also - if bashio::debug; then - black_white_list+=("${LOG_SERVER}") - fi - - # Always resolve letsencrypt's api also (independent of share_homeassistant) - black_white_list+=("${LETSENCRYPT_API}") -else - # Add the tailnet domain as white list - if ! tailnet=$( \ - /opt/tailscale status --json --peers=false --self=false \ - | jq -re '.MagicDNSSuffix') || \ - ! bashio::var.has_value "${tailnet}" - then - bashio::exit.nok "Determining tailnet name has failed" - fi - black_white_list+=("${tailnet}") -fi - -printf "%s" "${black_white_list[@]/%/$'\n'}" > "${DNSMASQ_BLACK_WHITE_LIST_LOCATION}" diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-proxies-upstream-list/up b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-proxies-upstream-list/up deleted file mode 100644 index 9d89e6ff2..000000000 --- a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-proxies-upstream-list/up +++ /dev/null @@ -1 +0,0 @@ -/etc/s6-overlay/s6-rc.d/init-magicdns-proxies-upstream-list/run diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-ingress-proxy/dependencies.d/base b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-proxies/dependencies.d/base similarity index 100% rename from tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-ingress-proxy/dependencies.d/base rename to tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-proxies/dependencies.d/base diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-proxies/down b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-proxies/down new file mode 100644 index 000000000..8e0d981bc --- /dev/null +++ b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-proxies/down @@ -0,0 +1 @@ +/etc/s6-overlay/s6-rc.d/init-magicdns-proxies/finish diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-ingress-proxy/finish b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-proxies/finish similarity index 100% rename from tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-ingress-proxy/finish rename to tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-proxies/finish diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-ingress-proxy/run b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-proxies/run similarity index 75% rename from tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-ingress-proxy/run rename to tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-proxies/run index 51d01fc8a..37d7f03f7 100755 --- a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-ingress-proxy/run +++ b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-proxies/run @@ -12,6 +12,11 @@ readonly MAGIC_DNS_IPV6="fd7a:115c:a1e0::53" declare dns declare invalid_dns_config +# MAGICDNS_MODE is in contenv at /var/run/s6/container_environment +# - "RESTRICTED" only domains in black-white-list are allowed on ingress side, everything else on egress side +# - "UNRESTRICTED" everything except domains in black-white-list are allowed on ingress side, only domains in black-white-list on egress side +readonly DNSMASQ_BLACK_WHITE_LIST_LOCATION="/run/dnsmasq_black_white_list" + # Check DNS configuration invalid_dns_config="true" for dns in $(bashio::dns.servers); do @@ -48,6 +53,8 @@ fi # This is necessary to prevent accessing MagicDNS before the ingress proxy starts up # The ingress proxy will remove these entries on startup -# In case of accept_dns enabled, this will prevent DNS query loops -# In case of accept_dns disabled, this will prevent SERVFAIL logging by MagicDNS for non-tailnet queries magicdns-ingress-proxy-forwarding setup drop + +# During startup allow all egress queries, this will be reevaluated before ingress proxy starts +printf 'RESTRICTED' > /var/run/s6/container_environment/MAGICDNS_MODE +printf '' > "${DNSMASQ_BLACK_WHITE_LIST_LOCATION}" diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-ingress-proxy/type b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-proxies/type similarity index 100% rename from tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-ingress-proxy/type rename to tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-proxies/type diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-proxies/up b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-proxies/up new file mode 100644 index 000000000..f81376c7f --- /dev/null +++ b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-proxies/up @@ -0,0 +1 @@ +/etc/s6-overlay/s6-rc.d/init-magicdns-proxies/run diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/forwarding/dependencies.d/magicdns-ingress-proxy b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/dependencies.d/init-magicdns-proxies similarity index 100% rename from tailscale/rootfs/etc/s6-overlay/s6-rc.d/forwarding/dependencies.d/magicdns-ingress-proxy rename to tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/dependencies.d/init-magicdns-proxies diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/run b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/run index a40e28bc3..d52f89efd 100755 --- a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/run +++ b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/run @@ -6,10 +6,11 @@ export LOG_FD # Runs the MagicDNS egress proxy # ============================================================================== -# Note: This service runs when userspace_networking is disabled, and accept_dns is enabled. +# Note: This service runs when userspace_networking is disabled + +# Sets up IPv4 egress DNS proxy for tailscaled/MagicDNS, that forwards queries to IPv4 hassio DNS. # Note: This script assumes that internal hassio network is available in IPv4. -# Sets up IPv4 upstream DNS for tailscaled, and redirects the white_list to IPv4 hassio DNS. # In case of an IPv6-only hassio network this IPv4 solution won't work. # But running an egress DNS proxy on ::1 IPv6 localhost is impossible, because DNS apps can bind to this address also, # but we can specify only port 53 DNS in resolv.conf for tailscaled. @@ -20,9 +21,14 @@ source /usr/lib/trace.sh readonly DNSMASQ_EGRESS_ADDRESS_IPV4="127.100.100.100" readonly DNSMASQ_EGRESS_DEFAULT_PORT=53127 -readonly DNSMASQ_BLACK_WHITE_LIST_LOCATION="/etc/dnsmasq-black-white-list" + +# MAGICDNS_MODE is in contenv at /var/run/s6/container_environment +# - "RESTRICTED" only domains in black-white-list are allowed on ingress side, everything else on egress side +# - "UNRESTRICTED" everything except domains in black-white-list are allowed on ingress side, only domains in black-white-list on egress side +readonly DNSMASQ_BLACK_WHITE_LIST_LOCATION="/run/dnsmasq_black_white_list" declare hassio_dns_ipv4 +declare -a black_list declare -a white_list declare domain declare -a options @@ -69,14 +75,34 @@ then bashio::exit.nok "Failed to resolve Home Assistant's IPv4 DNS address" fi -# White-list -readarray -t white_list < "${DNSMASQ_BLACK_WHITE_LIST_LOCATION}" - -# Return NXDOMAIN for everything, except the white_list -options+=(--address=/#/) -for domain in "${white_list[@]}"; do - options+=(--server=/${domain}/${hassio_dns_ipv4}) -done +if bashio::var.equals "${MAGICDNS_MODE}" "RESTRICTED"; then + # This means "Unrestricted" on egress side, ie. everything except domains in black-white-list are allowed + + # Black-list + readarray -t black_list < "${DNSMASQ_BLACK_WHITE_LIST_LOCATION}" + + # Forward everything to hassio DNS, except the black_list + bashio::log.info "MagicDNS egress proxy: forwarding everything to ${hassio_dns_ipv4}${black_list:+, except:}" + options+=(--server=${hassio_dns_ipv4}) + for domain in "${black_list[@]}"; do + bashio::log.info " ${domain}" + options+=(--server=/${domain}/) + done + +else + # This means "Restricted" on egress side, ie. only domains in black-white-list are allowed + + # White-list + readarray -t white_list < "${DNSMASQ_BLACK_WHITE_LIST_LOCATION}" + + # Return NXDOMAIN for everything, except the white_list + bashio::log.info "MagicDNS egress proxy: returning NXDOMAIN for everything${white_list:+, except forwarding to ${hassio_dns_ipv4} in case of:}" + options+=(--address=/#/) + for domain in "${white_list[@]}"; do + bashio::log.info " ${domain}" + options+=(--server=/${domain}/${hassio_dns_ipv4}) + done +fi if bashio_custom::trace; then options+=(--log-queries) @@ -85,9 +111,12 @@ fi magicdns-egress-proxy-forwarding setup forwarding "${dnsmasq_egress_port}" -# We need to delay the starting of the dependent services until the conf file is written -echo "nameserver ${DNSMASQ_EGRESS_ADDRESS_IPV4}" > /etc/resolv.dnsmasq.conf +if ! bashio::fs.file_exists "/etc/resolv.for-tailscaled.conf"; then + # Overwrite it only on the first service startup + echo "nameserver ${DNSMASQ_EGRESS_ADDRESS_IPV4}" > /etc/resolv.for-tailscaled.conf +fi + +# We need to delay the starting of the dependent services until iptables are configured and the conf file is written echo "" >&3 -# This DNS forwards the white_list to HA's DNS, otherwise replies NXDOMAIN for everything exec dnsmasq "${options[@]}" diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/dependencies.d/init-magicdns-proxies-upstream-list b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/dependencies.d/init-magicdns-proxies-upstream-list deleted file mode 100644 index e69de29bb..000000000 diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-proxies-upstream-list/dependencies.d/base b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/dependencies.d/magicdns-proxies-configurator similarity index 100% rename from tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-proxies-upstream-list/dependencies.d/base rename to tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/dependencies.d/magicdns-proxies-configurator diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/dependencies.d/post-tailscaled b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/dependencies.d/post-tailscaled deleted file mode 100644 index e69de29bb..000000000 diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/finish b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/finish index 7c168526d..e8658ea70 100755 --- a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/finish +++ b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/finish @@ -10,8 +10,13 @@ readonly exit_code_service="${1}" readonly exit_code_signal="${2}" readonly service="magicdns-ingress-proxy" -magicdns-ingress-proxy-forwarding setup drop -magicdns-ingress-proxy-forwarding remove forwarding +# updated by the magicdns-proxies-reconfigurator service +readonly MAGICDNS_INGRESS_PROXY_SUPPRESS_FORWARDING_CONFIGURATION_LOCATION="/run/magicdns_ingress_proxy_suppress_forwarding_configuration" + +if ! bashio::fs.file_exists "${MAGICDNS_INGRESS_PROXY_SUPPRESS_FORWARDING_CONFIGURATION_LOCATION}"; then + magicdns-ingress-proxy-forwarding setup drop + magicdns-ingress-proxy-forwarding remove forwarding +fi bashio::log.info \ "Service ${service} exited with code ${exit_code_service}" \ diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/run b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/run index 783b1aa7f..4bc3054bb 100755 --- a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/run +++ b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/run @@ -6,18 +6,25 @@ export LOG_FD # Runs the MagicDNS ingress proxy # ============================================================================== -# Note: This service runs when userspace_networking is disabled, and -# - when accept_dns is enabled, it treats black_white_list as black_list, and -# returns NXDOMAIN for anything that is on the list -# - when accept_dns is disabled, it treats black_white_list as white_list, and -# returns NXDOMAIN for anything that is NOT on the list +# Note: This service runs when userspace_networking is disabled + +# Sets up IPv4 and IPv6 ingress DNS proxy for MagicDNS, that inercepts queries from hassio DNS and SU. source /usr/lib/trace.sh readonly MAGIC_DNS_IPV4="100.100.100.100" readonly DNSMASQ_INGRESS_DEFAULT_PORT=51100 -readonly DNSMASQ_BLACK_WHITE_LIST_LOCATION="/etc/dnsmasq-black-white-list" + +# MAGICDNS_MODE is in contenv at /var/run/s6/container_environment +# - "RESTRICTED" only domains in black-white-list are allowed on ingress side, everything else on egress side +# - "UNRESTRICTED" everything except domains in black-white-list are allowed on ingress side, only domains in black-white-list on egress side +readonly DNSMASQ_BLACK_WHITE_LIST_LOCATION="/run/dnsmasq_black_white_list" + +# updated by the magicdns-proxies-reconfigurator service +readonly MAGICDNS_INGRESS_PROXY_SUPPRESS_FORWARDING_CONFIGURATION_LOCATION="/run/magicdns_ingress_proxy_suppress_forwarding_configuration" +# used by the magicdns-proxies-reconfigurator service +readonly DNSMASQ_INGRESS_PORT_LOCATION="/run/dnsmasq_ingress_port" declare tailscale_address_ipv4 declare tailscale_address_ipv6 @@ -69,24 +76,32 @@ fi options+=(--bind-dynamic) options+=(--port=${dnsmasq_ingress_port}) -if bashio::config.true "accept_dns"; then - # Black-list - readarray -t black_list < "${DNSMASQ_BLACK_WHITE_LIST_LOCATION}" +if bashio::var.equals "${MAGICDNS_MODE}" "RESTRICTED"; then + # Only domains in black-white-list are allowed - # Forward everything to MagicDNS, except the black_list - options+=(--server=${MAGIC_DNS_IPV4}) - for domain in "${black_list[@]}"; do - options+=(--server=/${domain}/) - done -else # White-list readarray -t white_list < "${DNSMASQ_BLACK_WHITE_LIST_LOCATION}" # Return NXDOMAIN for everything, except the white_list + bashio::log.info "MagicDNS ingress proxy: returning NXDOMAIN for everything${white_list:+, except forwarding to ${MAGIC_DNS_IPV4} in case of:}" options+=(--address=/#/) for domain in "${white_list[@]}"; do + bashio::log.info " ${domain}" options+=(--server=/${domain}/${MAGIC_DNS_IPV4}) done +else + # Everything except domains in black-white-list are allowed + + # Black-list + readarray -t black_list < "${DNSMASQ_BLACK_WHITE_LIST_LOCATION}" + + # Forward everything to MagicDNS, except the black_list + bashio::log.info "MagicDNS ingress proxy: forwarding everything to ${MAGIC_DNS_IPV4}${black_list:+, except}" + options+=(--server=${MAGIC_DNS_IPV4}) + for domain in "${black_list[@]}"; do + bashio::log.info " ${domain}" + options+=(--server=/${domain}/) + done fi if bashio_custom::trace; then @@ -94,11 +109,14 @@ if bashio_custom::trace; then options+=(--log-debug) fi -magicdns-ingress-proxy-forwarding setup forwarding "${dnsmasq_ingress_port}" -magicdns-ingress-proxy-forwarding remove drop +if ! bashio::fs.file_exists "${MAGICDNS_INGRESS_PROXY_SUPPRESS_FORWARDING_CONFIGURATION_LOCATION}"; then + magicdns-ingress-proxy-forwarding setup forwarding "${dnsmasq_ingress_port}" + magicdns-ingress-proxy-forwarding remove drop +else + printf "${dnsmasq_ingress_port}" > "${DNSMASQ_INGRESS_PORT_LOCATION}" +fi # We need to delay the starting of the dependent services until iptables are configured echo "" >&3 -# This DNS replies NXDOMAIN for the black_list, otherwise forwards everything to MagicDNS exec dnsmasq "${options[@]}" diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-proxies-upstream-list/dependencies.d/post-tailscaled b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-configurator/dependencies.d/post-tailscaled similarity index 100% rename from tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-proxies-upstream-list/dependencies.d/post-tailscaled rename to tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-configurator/dependencies.d/post-tailscaled diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-configurator/run b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-configurator/run new file mode 100755 index 000000000..c495f2de0 --- /dev/null +++ b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-configurator/run @@ -0,0 +1,13 @@ +#!/command/with-contenv bashio +# shellcheck shell=bash +export LOG_FD +# ============================================================================== +# Home Assistant Community App: Tailscale +# Runs the dnsmasq proxies black/white list initialization +# ============================================================================== + +configure-magicdns-proxies + +# Restart the egress proxy, the configuration is changed +bashio::log.info "MagicDNS proxy configuration has changed, restarting MagicDNS egress proxy..." +s6-svc -ruwR /run/service/magicdns-egress-proxy diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-proxies-upstream-list/type b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-configurator/type similarity index 100% rename from tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-magicdns-proxies-upstream-list/type rename to tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-configurator/type diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-configurator/up b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-configurator/up new file mode 100644 index 000000000..de3b96f83 --- /dev/null +++ b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-configurator/up @@ -0,0 +1 @@ +/etc/s6-overlay/s6-rc.d/magicdns-proxies-configurator/run diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/dependencies.d/init-magicdns-proxies-upstream-list b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/dependencies.d/magicdns-ingress-proxy similarity index 100% rename from tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/dependencies.d/init-magicdns-proxies-upstream-list rename to tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/dependencies.d/magicdns-ingress-proxy diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/finish b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/finish new file mode 100755 index 000000000..034e11cf5 --- /dev/null +++ b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/finish @@ -0,0 +1,27 @@ +#!/command/with-contenv bashio +# shellcheck shell=bash +export LOG_FD +# ============================================================================== +# Home Assistant Community App: Tailscale +# Take down the S6 supervision tree when MagicDNS proxies reconfigurator fails +# ============================================================================== +readonly exit_code_container=$( /run/s6-linux-init-container-results/exitcode + fi + [[ "${exit_code_signal}" -eq 15 ]] && exec /run/s6/basedir/bin/halt +elif [[ "${exit_code_service}" -ne 0 ]]; then + if [[ "${exit_code_container}" -eq 0 ]]; then + echo "${exit_code_service}" > /run/s6-linux-init-container-results/exitcode + fi + exec /run/s6/basedir/bin/halt +fi diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run new file mode 100755 index 000000000..80315c2a0 --- /dev/null +++ b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run @@ -0,0 +1,51 @@ +#!/command/with-contenv bashio +# shellcheck shell=bash +export LOG_FD +# ============================================================================== +# Home Assistant Community App: Tailscale +# Periodically runs the dnsmasq proxies black/white list initialization +# ============================================================================== + +# used by the healthcheck script +# MAGICDNS_PROXIES_RECONFIGURATOR_HEALTH_STATE is in contenv at /var/run/s6/container_environment + +# used by the magicdns-ingress-proxy service +readonly MAGICDNS_INGRESS_PROXY_SUPPRESS_FORWARDING_CONFIGURATION_LOCATION="/run/magicdns_ingress_proxy_suppress_forwarding_configuration" +# updated by the magicdns-ingress-proxy service +readonly DNSMASQ_INGRESS_PORT_LOCATION="/run/dnsmasq_ingress_port" + +declare magicdns_proxies_configuration +declare healthy=true + +while true +do + # crond is great for long term schedules, but not so great for "fast" repetition + sleep 30 + # If we can't check the DNS configuration, the healthcheck script will treat it as an offline situation + if ! magicdns_proxies_configuration=$(configure-magicdns-proxies test); then + printf "UNHEALTHY" > /var/run/s6/container_environment/MAGICDNS_PROXIES_RECONFIGURATOR_HEALTH_STATE + healthy=false + elif bashio::var.false "${healthy}"; then + printf "HEALTHY" > /var/run/s6/container_environment/MAGICDNS_PROXIES_RECONFIGURATOR_HEALTH_STATE + healthy=true + fi + # Restart dnsmasq proxies if required + if ! bashio::var.equals "${magicdns_proxies_configuration}" 'unchanged'; then + bashio::log.info "MagicDNS proxy configuration has changed" + bashio::log.info "Disabling incoming DNS queries during reconfiguration..." + touch "${MAGICDNS_INGRESS_PROXY_SUPPRESS_FORWARDING_CONFIGURATION_LOCATION}" + magicdns-ingress-proxy-forwarding setup drop + magicdns-ingress-proxy-forwarding remove forwarding + + bashio::log.info "Restarting MagicDNS egress proxy..." + s6-svc -ruwR /run/service/magicdns-egress-proxy + bashio::log.info "Restarting MagicDNS ingress proxy..." + s6-svc -ruwR /run/service/magicdns-ingress-proxy + + bashio::log.info "Enabling incoming DNS queries after reconfiguration..." + magicdns-ingress-proxy-forwarding setup forwarding "$(<"${DNSMASQ_INGRESS_PORT_LOCATION}")" + magicdns-ingress-proxy-forwarding remove drop + rm -r -f "${MAGICDNS_INGRESS_PROXY_SUPPRESS_FORWARDING_CONFIGURATION_LOCATION}" + rm -r -f "${DNSMASQ_INGRESS_PORT_LOCATION}" + fi +done diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/type b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/type new file mode 100644 index 000000000..5883cff0c --- /dev/null +++ b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/type @@ -0,0 +1 @@ +longrun diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/tailscaled/dependencies.d/init-magicdns-ingress-proxy b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/tailscaled/dependencies.d/init-magicdns-ingress-proxy deleted file mode 100644 index e69de29bb..000000000 diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/tailscaled/run b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/tailscaled/run index 0a022fb0f..500cece9a 100755 --- a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/tailscaled/run +++ b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/tailscaled/run @@ -42,7 +42,7 @@ fi # Run Tailscale # If exists, resolv.dnsmasq.conf pointing to the dummy dnsmasq will be mounted in place of the real resolv.conf only for tailscaled # This will prevent the DNS at 100.100.100.100 to call back to hassio_dns causing a loop -if ! bashio::fs.file_exists "/etc/resolv.dnsmasq.conf"; then +if ! bashio::fs.file_exists "/etc/resolv.for-tailscaled.conf"; then # Running the regular way, no resolv.conf replacement if bashio::debug ; then exec /opt/tailscaled "${options[@]}" @@ -56,8 +56,7 @@ if ! bashio::fs.file_exists "/etc/resolv.dnsmasq.conf"; then else # Using fake resolv.conf bashio::log.info "Using dnsmasq as upstream DNS server for tailscaled" - bashio::net.wait_for 53 $(awk '{ print $2 }' < /etc/resolv.dnsmasq.conf) - mv /etc/resolv.dnsmasq.conf /etc/resolv.for-tailscaled.conf + bashio::net.wait_for 53 $(awk '{ print $2 }' < /etc/resolv.for-tailscaled.conf) if bashio::debug ; then exec unshare -m bash -c "mount --bind /etc/resolv.for-tailscaled.conf /etc/resolv.conf; exec /opt/tailscaled $(printf "\"%s\" " "${options[@]}")" else diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/dependencies.d/init-magicdns-ingress-proxy b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/magicdns-proxies-reconfigurator similarity index 100% rename from tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/dependencies.d/init-magicdns-ingress-proxy rename to tailscale/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/magicdns-proxies-reconfigurator diff --git a/tailscale/rootfs/etc/s6-overlay/scripts/stage2_hook.sh b/tailscale/rootfs/etc/s6-overlay/scripts/stage2_hook.sh index 1f86618a9..d9864fc9d 100755 --- a/tailscale/rootfs/etc/s6-overlay/scripts/stage2_hook.sh +++ b/tailscale/rootfs/etc/s6-overlay/scripts/stage2_hook.sh @@ -69,37 +69,30 @@ fi # MagicDNS related service dependencies: # -# +-------- magicdns-ingress-proxy -# | | | -# | | | -# ˅ !! ˅ | -# init-magicdns-proxies-upstream-list -----> post-tailscaled | -# ˄ | | -# | | | -# | !! ˅ | -# magicdns-egress-proxy <----- tailscaled | -# | | -# | | -# ˅ ˅ -# init-magicdns-ingress-proxy +# user +# | ˅ +# | magicdns-proxies-reconfigurator +# ˅ ˅ +# magicdns-ingress-proxy +# | ˅ +# | magicdns-proxies-configurator +# | ˅ +# | post-tailscaled +# | ˅ +# | tailscaled +# | ˅ +# | magicdns-egress-proxy +# ˅ ˅ +# init-magicdns-proxies # -# Disable MagicDNS egress proxy service when userspace-networking is enabled or accepting dns is disabled -if bashio::config.true "userspace_networking" || \ - bashio::config.false "accept_dns"; -then - # Either this or init-magicdns-proxies-upstream-list/dependencies.d/post-tailscaled below has to be removed - # When accepting dns is disabled init-magicdns-proxies-upstream-list depends on post-tailscaled - rm /etc/s6-overlay/s6-rc.d/tailscaled/dependencies.d/magicdns-egress-proxy -else - # Either this or tailscaled/dependencies.d/magicdns-egress-proxy above has to be removed - # When accepting dns is enabled init-magicdns-proxies-upstream-list doesn't depend on post-tailscaled - rm /etc/s6-overlay/s6-rc.d/init-magicdns-proxies-upstream-list/dependencies.d/post-tailscaled -fi -# Disable MagicDNS ingress proxy service when userspace-networking is enabled if bashio::config.true "userspace_networking"; then - rm /etc/s6-overlay/s6-rc.d/forwarding/dependencies.d/magicdns-ingress-proxy + # Disable MagicDNS egress and ingress proxy related services when userspace_networking is enabled + rm /etc/s6-overlay/s6-rc.d/user/contents.d/magicdns-proxies-reconfigurator rm /etc/s6-overlay/s6-rc.d/user/contents.d/magicdns-ingress-proxy - rm /etc/s6-overlay/s6-rc.d/tailscaled/dependencies.d/init-magicdns-ingress-proxy + rm /etc/s6-overlay/s6-rc.d/tailscaled/dependencies.d/magicdns-egress-proxy +elif bashio::config.false "accept_dns"; then + # Disable MagicDNS egress and ingress proxy reconfigurator when userspace_networking is disabled but accept_dns is also disabled + rm /etc/s6-overlay/s6-rc.d/user/contents.d/magicdns-proxies-reconfigurator fi # Disable protect-subnets service when userspace-networking is enabled or accepting routes is disabled diff --git a/tailscale/rootfs/usr/bin/configure-magicdns-proxies b/tailscale/rootfs/usr/bin/configure-magicdns-proxies new file mode 100755 index 000000000..f732ed866 --- /dev/null +++ b/tailscale/rootfs/usr/bin/configure-magicdns-proxies @@ -0,0 +1,106 @@ +#!/usr/bin/env bashio +# shellcheck shell=bash +export LOG_FD +# ============================================================================== +# Configures the dnsmasq proxies black/white list +# ============================================================================== + +readonly DEFAULT_LOGIN_SERVER="controlplane.tailscale.com" +readonly LOG_SERVER="log.tailscale.com" +readonly LETSENCRYPT_API="acme-v02.api.letsencrypt.org" + +# MAGICDNS_MODE is in contenv at /var/run/s6/container_environment +# - "RESTRICTED" only domains in black-white-list are allowed on ingress side, everything else on egress side +# - "UNRESTRICTED" everything except domains in black-white-list are allowed on ingress side, only domains in black-white-list on egress side +readonly DNSMASQ_BLACK_WHITE_LIST_LOCATION="/run/dnsmasq_black_white_list" + +readonly PREVIOUS_MAGICDNS_STATUS_LOCATION="/run/previous_magicdns_status" + +declare login_server="${DEFAULT_LOGIN_SERVER}" +declare -a black_white_list=() +declare dns_status_json +declare previous_dns_status_json +declare tailnet +declare override_dns_servers +declare domain +declare add_tailnet + +if ! [[ "${1-}" =~ ^(|test)$ ]]; then + echo "Usage: $(basename "$0") [test]" 1>&2 + exit 1 +fi + +if ! dns_status_json=$( \ + /opt/tailscale dns status --json \ + | jq -rcM '{"MagicDNSSuffix": .CurrentTailnet.MagicDNSSuffix, "Resolvers": has("Resolvers"), "SplitDNSRoutes": .SplitDNSRoutes | keys}') || \ + ! bashio::var.has_value "${dns_status_json}" +then + bashio::exit.nok "Determining MagicDNS status has failed" +fi + +if [[ "${1-}" == "test" ]]; then + # It doesn't test accept_dns change, but that can't change while the app is running, that requires a restart + previous_dns_status_json=$(<"${PREVIOUS_MAGICDNS_STATUS_LOCATION}") + if bashio::var.equals "${dns_status_json}" "${previous_dns_status_json}"; then + printf 'unchanged' + bashio::exit.ok + fi +fi + +if bashio::config.false "accept_dns"; then + # local accept_dns is disabled, MagicDNS can resolve only tailnet related queries + # this prevents logging SERVFAIL by 100.100.100.100 for non-tailnet queries + MAGICDNS_MODE="RESTRICTED" + + tailnet=$(bashio::jq "${dns_status_json}" '.MagicDNSSuffix') + black_white_list+=("${tailnet}") +else + override_dns_servers=$(bashio::jq "${dns_status_json}" '.Resolvers') + if bashio::var.false "${override_dns_servers}"; then + # local accept_dns is enabled, but global override_dns_servers is disabled, MagicDNS can resolve only splitDNS domains related queries + # splitDNS domains normally contain the tailnet also, though we add it if not + MAGICDNS_MODE="RESTRICTED" + + # Add the split dns domains as white list + tailnet=$(bashio::jq "${dns_status_json}" '.MagicDNSSuffix') + add_tailnet=true + for domain in $( \ + bashio::jq "${dns_status_json}" '.SplitDNSRoutes[]' \ + | sed -e 's/\.$//') + do + black_white_list+=("${domain}") + if [[ "${tailnet}" == *${domain} ]]; then + add_tailnet=false + fi + done + # Add the tailnet if missing + if bashio::var.true "${add_tailnet}"; then + black_white_list+=("${tailnet}") + fi + else + # local accept_dns and global override_dns_servers are enabled, MagicDNS can resolve nearly anything + # except a short list that always resolved by hassio DNS to make sure tailscaled functions properly + MAGICDNS_MODE="UNRESTRICTED" + + # We have to be able to determine login_server from this address + if ! login_server=$(awk -F[/:] '{print $4}' <<<"$(bashio::config "login_server")") || \ + ! bashio::var.has_value "${login_server}" + then + bashio::exit.nok "Determining host name from '$(bashio::config "login_server")' has failed" + fi + black_white_list+=("${login_server}") + + # When log upload is enabled, resolve log server also + if bashio::debug; then + black_white_list+=("${LOG_SERVER}") + fi + + # Always resolve letsencrypt's api also (independent of share_homeassistant) + black_white_list+=("${LETSENCRYPT_API}") + fi +fi + +printf "${MAGICDNS_MODE}" > /var/run/s6/container_environment/MAGICDNS_MODE +printf "%s" "${black_white_list[@]/%/$'\n'}" > "${DNSMASQ_BLACK_WHITE_LIST_LOCATION}" + +printf "${dns_status_json}" > "${PREVIOUS_MAGICDNS_STATUS_LOCATION}" diff --git a/tailscale/rootfs/usr/bin/healthcheck b/tailscale/rootfs/usr/bin/healthcheck index 17b6e0ab8..9add3f752 100755 --- a/tailscale/rootfs/usr/bin/healthcheck +++ b/tailscale/rootfs/usr/bin/healthcheck @@ -23,6 +23,9 @@ declare backend_state is_self_online # STARTED_TIMESTAMP is in contenv at /var/run/s6/container_environment # LAST_ONLINE_TIMESTAMP is in contenv at /var/run/s6/container_environment +# updated by the magicdns-proxies-reconfigurator service +# MAGICDNS_PROXIES_RECONFIGURATOR_HEALTH_STATE is in contenv at /var/run/s6/container_environment + if ! bashio::var.has_value "${STARTED_TIMESTAMP-}" then STARTED_TIMESTAMP=$(date +"%s") @@ -34,7 +37,8 @@ if status_json=$(/opt/tailscale status --json --self=true --peers=false 2> /dev/ is_self_online=$(jq -r '.Self.Online' <<< "${status_json}") fi -if bashio::var.equals "${backend_state-}" "Running" && bashio::var.true "${is_self_online-}" +if bashio::var.equals "${backend_state-}" "Running" && bashio::var.true "${is_self_online-}" && \ + ! bashio::var.equals "${MAGICDNS_PROXIES_RECONFIGURATOR_HEALTH_STATE-}" 'UNHEALTHY' then LAST_ONLINE_TIMESTAMP=$(date +"%s") printf "${LAST_ONLINE_TIMESTAMP}" > /var/run/s6/container_environment/LAST_ONLINE_TIMESTAMP From f79be402914cd5057b29374e2bfbe4b9a92d57ec Mon Sep 17 00:00:00 2001 From: Laszlo Magyar Date: Sat, 2 May 2026 00:05:05 +0200 Subject: [PATCH 09/24] fix shellcheck errors --- tailscale/rootfs/usr/bin/configure-magicdns-proxies | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tailscale/rootfs/usr/bin/configure-magicdns-proxies b/tailscale/rootfs/usr/bin/configure-magicdns-proxies index f732ed866..93f8abc2a 100755 --- a/tailscale/rootfs/usr/bin/configure-magicdns-proxies +++ b/tailscale/rootfs/usr/bin/configure-magicdns-proxies @@ -100,7 +100,7 @@ else fi fi -printf "${MAGICDNS_MODE}" > /var/run/s6/container_environment/MAGICDNS_MODE +printf "%s" "${MAGICDNS_MODE}" > /var/run/s6/container_environment/MAGICDNS_MODE printf "%s" "${black_white_list[@]/%/$'\n'}" > "${DNSMASQ_BLACK_WHITE_LIST_LOCATION}" -printf "${dns_status_json}" > "${PREVIOUS_MAGICDNS_STATUS_LOCATION}" +printf "%s" "${dns_status_json}" > "${PREVIOUS_MAGICDNS_STATUS_LOCATION}" From 96d786ec33a3185ab4c70072ac0b3c01e1797eb0 Mon Sep 17 00:00:00 2001 From: Laszlo Magyar Date: Sat, 2 May 2026 11:16:07 +0200 Subject: [PATCH 10/24] minor fixes, thanks bunny --- .../s6-rc.d/magicdns-egress-proxy/run | 7 ++-- .../s6-rc.d/magicdns-ingress-proxy/run | 6 ++- .../magicdns-proxies-reconfigurator/run | 42 ++++++++++--------- 3 files changed, 30 insertions(+), 25 deletions(-) diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/run b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/run index d52f89efd..c8822176a 100755 --- a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/run +++ b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/run @@ -49,7 +49,7 @@ while netstat -lntu \ | awk '{print $4}' \ | grep -q ":${dnsmasq_egress_port}$" do - if ((++dnsmasq_egress_port == DNSMASQ_EGRESS_DEFAULT_PORT + 1000)); then + if ((++dnsmasq_egress_port >= DNSMASQ_EGRESS_DEFAULT_PORT + 1000)); then bashio::exit.nok "Can't find free port for MagicDNS egress proxy" fi done @@ -88,8 +88,7 @@ if bashio::var.equals "${MAGICDNS_MODE}" "RESTRICTED"; then bashio::log.info " ${domain}" options+=(--server=/${domain}/) done - -else +elif bashio::var.equals "${MAGICDNS_MODE}" "UNRESTRICTED"; then # This means "Restricted" on egress side, ie. only domains in black-white-list are allowed # White-list @@ -102,6 +101,8 @@ else bashio::log.info " ${domain}" options+=(--server=/${domain}/${hassio_dns_ipv4}) done +else + bashio::exit.nok "Invalid MAGICDNS_MODE: '${MAGICDNS_MODE}'" fi if bashio_custom::trace; then diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/run b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/run index 4bc3054bb..e62a240ab 100755 --- a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/run +++ b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/run @@ -42,7 +42,7 @@ while netstat -lntu \ | awk '{print $4}' \ | grep -q ":${dnsmasq_ingress_port}$" do - if ((++dnsmasq_ingress_port == DNSMASQ_INGRESS_DEFAULT_PORT + 1000)); then + if ((++dnsmasq_ingress_port >= DNSMASQ_INGRESS_DEFAULT_PORT + 1000)); then bashio::exit.nok "Can't find free port for MagicDNS ingress proxy" fi done @@ -89,7 +89,7 @@ if bashio::var.equals "${MAGICDNS_MODE}" "RESTRICTED"; then bashio::log.info " ${domain}" options+=(--server=/${domain}/${MAGIC_DNS_IPV4}) done -else +elif bashio::var.equals "${MAGICDNS_MODE}" "UNRESTRICTED"; then # Everything except domains in black-white-list are allowed # Black-list @@ -102,6 +102,8 @@ else bashio::log.info " ${domain}" options+=(--server=/${domain}/) done +else + bashio::exit.nok "Invalid MAGICDNS_MODE: '${MAGICDNS_MODE}'" fi if bashio_custom::trace; then diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run index 80315c2a0..a120cbdf2 100755 --- a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run +++ b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run @@ -25,27 +25,29 @@ do if ! magicdns_proxies_configuration=$(configure-magicdns-proxies test); then printf "UNHEALTHY" > /var/run/s6/container_environment/MAGICDNS_PROXIES_RECONFIGURATOR_HEALTH_STATE healthy=false - elif bashio::var.false "${healthy}"; then - printf "HEALTHY" > /var/run/s6/container_environment/MAGICDNS_PROXIES_RECONFIGURATOR_HEALTH_STATE - healthy=true - fi - # Restart dnsmasq proxies if required - if ! bashio::var.equals "${magicdns_proxies_configuration}" 'unchanged'; then - bashio::log.info "MagicDNS proxy configuration has changed" - bashio::log.info "Disabling incoming DNS queries during reconfiguration..." - touch "${MAGICDNS_INGRESS_PROXY_SUPPRESS_FORWARDING_CONFIGURATION_LOCATION}" - magicdns-ingress-proxy-forwarding setup drop - magicdns-ingress-proxy-forwarding remove forwarding + else + if bashio::var.false "${healthy}"; then + printf "HEALTHY" > /var/run/s6/container_environment/MAGICDNS_PROXIES_RECONFIGURATOR_HEALTH_STATE + healthy=true + fi + # Restart dnsmasq proxies if required + if ! bashio::var.equals "${magicdns_proxies_configuration}" 'unchanged'; then + bashio::log.info "MagicDNS proxy configuration has changed" + bashio::log.info "Disabling incoming DNS queries during reconfiguration..." + touch "${MAGICDNS_INGRESS_PROXY_SUPPRESS_FORWARDING_CONFIGURATION_LOCATION}" + magicdns-ingress-proxy-forwarding setup drop + magicdns-ingress-proxy-forwarding remove forwarding - bashio::log.info "Restarting MagicDNS egress proxy..." - s6-svc -ruwR /run/service/magicdns-egress-proxy - bashio::log.info "Restarting MagicDNS ingress proxy..." - s6-svc -ruwR /run/service/magicdns-ingress-proxy + bashio::log.info "Restarting MagicDNS egress proxy..." + s6-svc -ruwR /run/service/magicdns-egress-proxy + bashio::log.info "Restarting MagicDNS ingress proxy..." + s6-svc -ruwR /run/service/magicdns-ingress-proxy - bashio::log.info "Enabling incoming DNS queries after reconfiguration..." - magicdns-ingress-proxy-forwarding setup forwarding "$(<"${DNSMASQ_INGRESS_PORT_LOCATION}")" - magicdns-ingress-proxy-forwarding remove drop - rm -r -f "${MAGICDNS_INGRESS_PROXY_SUPPRESS_FORWARDING_CONFIGURATION_LOCATION}" - rm -r -f "${DNSMASQ_INGRESS_PORT_LOCATION}" + bashio::log.info "Enabling incoming DNS queries after reconfiguration..." + magicdns-ingress-proxy-forwarding setup forwarding "$(<"${DNSMASQ_INGRESS_PORT_LOCATION}")" + magicdns-ingress-proxy-forwarding remove drop + rm -r -f "${MAGICDNS_INGRESS_PROXY_SUPPRESS_FORWARDING_CONFIGURATION_LOCATION}" + rm -r -f "${DNSMASQ_INGRESS_PORT_LOCATION}" + fi fi done From 6936b2618a46400434355f7a770888a4284da023 Mon Sep 17 00:00:00 2001 From: Laszlo Magyar Date: Sat, 2 May 2026 12:11:46 +0200 Subject: [PATCH 11/24] halp app on dns reconfig failure --- .../magicdns-proxies-reconfigurator/run | 52 +++++++++++++------ 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run index a120cbdf2..8ea5e3baf 100755 --- a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run +++ b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run @@ -17,6 +17,36 @@ readonly DNSMASQ_INGRESS_PORT_LOCATION="/run/dnsmasq_ingress_port" declare magicdns_proxies_configuration declare healthy=true +function try { + set +e + (set -e; "$@") + declare -gx TRY_ERROR=$? + set -e +} + +function restart_proxies() { + bashio::log.info "MagicDNS proxy configuration has changed" + + bashio::log.info "Disabling incoming DNS queries during reconfiguration..." + magicdns-ingress-proxy-forwarding setup drop + magicdns-ingress-proxy-forwarding remove forwarding + + bashio::log.info "Restarting MagicDNS egress proxy..." + s6-svc -ruwR /run/service/magicdns-egress-proxy + + bashio::log.info "Restarting MagicDNS ingress proxy..." + touch "${MAGICDNS_INGRESS_PROXY_SUPPRESS_FORWARDING_CONFIGURATION_LOCATION}" + rm -r -f "${DNSMASQ_INGRESS_PORT_LOCATION}" + s6-svc -ruwR /run/service/magicdns-ingress-proxy + rm -r -f "${MAGICDNS_INGRESS_PROXY_SUPPRESS_FORWARDING_CONFIGURATION_LOCATION}" + bashio::fs.file_exists "${DNSMASQ_INGRESS_PORT_LOCATION}" + + bashio::log.info "Enabling incoming DNS queries after reconfiguration..." + magicdns-ingress-proxy-forwarding setup forwarding "$(<"${DNSMASQ_INGRESS_PORT_LOCATION}")" + rm -r -f "${DNSMASQ_INGRESS_PORT_LOCATION}" + magicdns-ingress-proxy-forwarding remove drop +} + while true do # crond is great for long term schedules, but not so great for "fast" repetition @@ -32,22 +62,12 @@ do fi # Restart dnsmasq proxies if required if ! bashio::var.equals "${magicdns_proxies_configuration}" 'unchanged'; then - bashio::log.info "MagicDNS proxy configuration has changed" - bashio::log.info "Disabling incoming DNS queries during reconfiguration..." - touch "${MAGICDNS_INGRESS_PROXY_SUPPRESS_FORWARDING_CONFIGURATION_LOCATION}" - magicdns-ingress-proxy-forwarding setup drop - magicdns-ingress-proxy-forwarding remove forwarding - - bashio::log.info "Restarting MagicDNS egress proxy..." - s6-svc -ruwR /run/service/magicdns-egress-proxy - bashio::log.info "Restarting MagicDNS ingress proxy..." - s6-svc -ruwR /run/service/magicdns-ingress-proxy - - bashio::log.info "Enabling incoming DNS queries after reconfiguration..." - magicdns-ingress-proxy-forwarding setup forwarding "$(<"${DNSMASQ_INGRESS_PORT_LOCATION}")" - magicdns-ingress-proxy-forwarding remove drop - rm -r -f "${MAGICDNS_INGRESS_PROXY_SUPPRESS_FORWARDING_CONFIGURATION_LOCATION}" - rm -r -f "${DNSMASQ_INGRESS_PORT_LOCATION}" + try restart_proxies + if ((TRY_ERROR)); then + bashio::log.fatal "Failed to reconfigure and restart MagicDNS proxies. Halting app to prevent network loss." + echo -n 1 > /run/s6-linux-init-container-results/exitcode + exec /run/s6/basedir/bin/halt + fi fi fi done From 81871d4b2e75a4c0b6cc221615dd66b5252d104e Mon Sep 17 00:00:00 2001 From: Laszlo Magyar Date: Sat, 2 May 2026 12:45:26 +0200 Subject: [PATCH 12/24] use plain exit.nok --- .../s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run index 8ea5e3baf..5c368a676 100755 --- a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run +++ b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run @@ -64,9 +64,7 @@ do if ! bashio::var.equals "${magicdns_proxies_configuration}" 'unchanged'; then try restart_proxies if ((TRY_ERROR)); then - bashio::log.fatal "Failed to reconfigure and restart MagicDNS proxies. Halting app to prevent network loss." - echo -n 1 > /run/s6-linux-init-container-results/exitcode - exec /run/s6/basedir/bin/halt + bashio::exit.nok "Failed to reconfigure and restart MagicDNS proxies. Halting app to prevent network loss." fi fi fi From d15f1bc66d97bb18796713f34bb287216e3298c8 Mon Sep 17 00:00:00 2001 From: Laszlo Magyar Date: Sat, 2 May 2026 14:14:11 +0200 Subject: [PATCH 13/24] test file content also, thanks bunny --- .../s6-rc.d/magicdns-proxies-reconfigurator/run | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run index 5c368a676..3e1d11376 100755 --- a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run +++ b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run @@ -24,6 +24,16 @@ function try { set -e } +function file_non_empty() { + local file=${1} + + if [[ -s "${file}" ]]; then + return "${__BASHIO_EXIT_OK}" + fi + + return "${__BASHIO_EXIT_NOK}" +} + function restart_proxies() { bashio::log.info "MagicDNS proxy configuration has changed" @@ -39,7 +49,10 @@ function restart_proxies() { rm -r -f "${DNSMASQ_INGRESS_PORT_LOCATION}" s6-svc -ruwR /run/service/magicdns-ingress-proxy rm -r -f "${MAGICDNS_INGRESS_PROXY_SUPPRESS_FORWARDING_CONFIGURATION_LOCATION}" - bashio::fs.file_exists "${DNSMASQ_INGRESS_PORT_LOCATION}" + if ! file_non_empty "${DNSMASQ_INGRESS_PORT_LOCATION}"; then + bashio::log.fatal "MagicDNS ingress proxy port is unknown" + return 1 + fi bashio::log.info "Enabling incoming DNS queries after reconfiguration..." magicdns-ingress-proxy-forwarding setup forwarding "$(<"${DNSMASQ_INGRESS_PORT_LOCATION}")" From 61183102812705d35f5b6ad5b4a37706e2f3765c Mon Sep 17 00:00:00 2001 From: Laszlo Magyar Date: Sat, 2 May 2026 23:33:56 +0200 Subject: [PATCH 14/24] don't poll for dns config changes, use ipn bus events --- .../magicdns-proxies-reconfigurator/run | 50 ++++++++++++------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run index 3e1d11376..4bf1fe907 100755 --- a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run +++ b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run @@ -14,6 +14,9 @@ readonly MAGICDNS_INGRESS_PROXY_SUPPRESS_FORWARDING_CONFIGURATION_LOCATION="/run # updated by the magicdns-ingress-proxy service readonly DNSMASQ_INGRESS_PORT_LOCATION="/run/dnsmasq_ingress_port" +declare current_ipn_dns_config +declare previous_ipn_dns_config= + declare magicdns_proxies_configuration declare healthy=true @@ -60,24 +63,37 @@ function restart_proxies() { magicdns-ingress-proxy-forwarding remove drop } -while true +# This jq filters the whole state into sg. like: +# {"Resolvers":true,"Routes":["foo.com","tail1234.ts.net.","ts.net."]} +# If the structure changes, that should break the most basic smoke test after a TS client update, so we can fix it before release +/opt/tailscale debug watch-ipn --initial=true --netmap=true --rate-limit=false \ +| stdbuf -i0 -oL -eL jq -rcM '{"Resolvers": .NetMap.DNS | has("Resolvers"), "Routes": .NetMap.DNS.Routes | keys}' \ +| while IFS= read -r current_ipn_dns_config do - # crond is great for long term schedules, but not so great for "fast" repetition - sleep 30 - # If we can't check the DNS configuration, the healthcheck script will treat it as an offline situation - if ! magicdns_proxies_configuration=$(configure-magicdns-proxies test); then - printf "UNHEALTHY" > /var/run/s6/container_environment/MAGICDNS_PROXIES_RECONFIGURATOR_HEALTH_STATE - healthy=false - else - if bashio::var.false "${healthy}"; then - printf "HEALTHY" > /var/run/s6/container_environment/MAGICDNS_PROXIES_RECONFIGURATOR_HEALTH_STATE - healthy=true - fi - # Restart dnsmasq proxies if required - if ! bashio::var.equals "${magicdns_proxies_configuration}" 'unchanged'; then - try restart_proxies - if ((TRY_ERROR)); then - bashio::exit.nok "Failed to reconfigure and restart MagicDNS proxies. Halting app to prevent network loss." + if [[ "${current_ipn_dns_config}" != "${previous_ipn_dns_config}" ]]; then + previous_ipn_dns_config="${current_ipn_dns_config}" + + # This seems to be double check, but not completely: + # - The ipn watcher will send the initial state, and we compare then the current DNS state + # with the previous call of configure-magicdns-proxies from the magicdns-proxies-configurator service. + # This way we don't miss any change between the startup of these 2 services. + # - On subsequent ipn watcher state changes we use the `tailscale dns status` json, that should be stable + + # If we can't check the DNS configuration, the healthcheck script will treat it as an offline situation + if ! magicdns_proxies_configuration=$(configure-magicdns-proxies test); then + printf "UNHEALTHY" > /var/run/s6/container_environment/MAGICDNS_PROXIES_RECONFIGURATOR_HEALTH_STATE + healthy=false + else + if bashio::var.false "${healthy}"; then + printf "HEALTHY" > /var/run/s6/container_environment/MAGICDNS_PROXIES_RECONFIGURATOR_HEALTH_STATE + healthy=true + fi + # Restart dnsmasq proxies if required + if ! bashio::var.equals "${magicdns_proxies_configuration}" 'unchanged'; then + try restart_proxies + if ((TRY_ERROR)); then + bashio::exit.nok "Failed to reconfigure and restart MagicDNS proxies. Halting app to prevent network loss." + fi fi fi fi From c2406490eef5893fa6bc49d1206155797e076153 Mon Sep 17 00:00:00 2001 From: Laszlo Magyar Date: Sun, 3 May 2026 21:34:08 +0200 Subject: [PATCH 15/24] add log line --- .../etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run | 1 + 1 file changed, 1 insertion(+) diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run index 4bf1fe907..1ad313d30 100755 --- a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run +++ b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run @@ -63,6 +63,7 @@ function restart_proxies() { magicdns-ingress-proxy-forwarding remove drop } +bashio::log.info "Subscribing to IPN message bus..." # This jq filters the whole state into sg. like: # {"Resolvers":true,"Routes":["foo.com","tail1234.ts.net.","ts.net."]} # If the structure changes, that should break the most basic smoke test after a TS client update, so we can fix it before release From afb8cf369199b5f451844635fc5303607e978ce1 Mon Sep 17 00:00:00 2001 From: Laszlo Magyar Date: Sun, 3 May 2026 22:35:44 +0200 Subject: [PATCH 16/24] Advance previous_ipn_dns_config only after a successful probe. --- .../etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run index 1ad313d30..405cc01c1 100755 --- a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run +++ b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run @@ -72,7 +72,6 @@ bashio::log.info "Subscribing to IPN message bus..." | while IFS= read -r current_ipn_dns_config do if [[ "${current_ipn_dns_config}" != "${previous_ipn_dns_config}" ]]; then - previous_ipn_dns_config="${current_ipn_dns_config}" # This seems to be double check, but not completely: # - The ipn watcher will send the initial state, and we compare then the current DNS state @@ -85,6 +84,7 @@ do printf "UNHEALTHY" > /var/run/s6/container_environment/MAGICDNS_PROXIES_RECONFIGURATOR_HEALTH_STATE healthy=false else + previous_ipn_dns_config="${current_ipn_dns_config}" if bashio::var.false "${healthy}"; then printf "HEALTHY" > /var/run/s6/container_environment/MAGICDNS_PROXIES_RECONFIGURATOR_HEALTH_STATE healthy=true From 1d26902bdd55fdaa82dba5acf95e0bbb1ee2228a Mon Sep 17 00:00:00 2001 From: Laszlo Magyar Date: Sun, 3 May 2026 23:08:36 +0200 Subject: [PATCH 17/24] use both login and both log server domains --- .../rootfs/usr/bin/configure-magicdns-proxies | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/tailscale/rootfs/usr/bin/configure-magicdns-proxies b/tailscale/rootfs/usr/bin/configure-magicdns-proxies index 93f8abc2a..c3183b8be 100755 --- a/tailscale/rootfs/usr/bin/configure-magicdns-proxies +++ b/tailscale/rootfs/usr/bin/configure-magicdns-proxies @@ -5,8 +5,10 @@ export LOG_FD # Configures the dnsmasq proxies black/white list # ============================================================================== -readonly DEFAULT_LOGIN_SERVER="controlplane.tailscale.com" -readonly LOG_SERVER="log.tailscale.com" +readonly DEFAULT_LOGIN_SERVER_1="controlplane.tailscale.com" +readonly DEFAULT_LOGIN_SERVER_2="login.tailscale.com" +readonly LOG_SERVER_1="log.tailscale.com" +readonly LOG_SERVER_2="log.tailscale.io" readonly LETSENCRYPT_API="acme-v02.api.letsencrypt.org" # MAGICDNS_MODE is in contenv at /var/run/s6/container_environment @@ -16,7 +18,7 @@ readonly DNSMASQ_BLACK_WHITE_LIST_LOCATION="/run/dnsmasq_black_white_list" readonly PREVIOUS_MAGICDNS_STATUS_LOCATION="/run/previous_magicdns_status" -declare login_server="${DEFAULT_LOGIN_SERVER}" +declare login_server="${DEFAULT_LOGIN_SERVER_1}" declare -a black_white_list=() declare dns_status_json declare previous_dns_status_json @@ -89,10 +91,17 @@ else bashio::exit.nok "Determining host name from '$(bashio::config "login_server")' has failed" fi black_white_list+=("${login_server}") + if bashio::var.equals "${login_server}" "${DEFAULT_LOGIN_SERVER_1}"; then + black_white_list+=("${DEFAULT_LOGIN_SERVER_2}") + fi + if bashio::var.equals "${login_server}" "${DEFAULT_LOGIN_SERVER_2}"; then + black_white_list+=("${DEFAULT_LOGIN_SERVER_1}") + fi # When log upload is enabled, resolve log server also if bashio::debug; then - black_white_list+=("${LOG_SERVER}") + black_white_list+=("${LOG_SERVER_1}") + black_white_list+=("${LOG_SERVER_2}") fi # Always resolve letsencrypt's api also (independent of share_homeassistant) From 3efddfd8daf33e68ef413d2cb5e0cb5c468785ab Mon Sep 17 00:00:00 2001 From: Laszlo Magyar Date: Mon, 4 May 2026 00:52:15 +0200 Subject: [PATCH 18/24] fix jq errors on non-DNS netmaps --- .../s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run index 405cc01c1..fac3e36a5 100755 --- a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run +++ b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run @@ -68,10 +68,10 @@ bashio::log.info "Subscribing to IPN message bus..." # {"Resolvers":true,"Routes":["foo.com","tail1234.ts.net.","ts.net."]} # If the structure changes, that should break the most basic smoke test after a TS client update, so we can fix it before release /opt/tailscale debug watch-ipn --initial=true --netmap=true --rate-limit=false \ -| stdbuf -i0 -oL -eL jq -rcM '{"Resolvers": .NetMap.DNS | has("Resolvers"), "Routes": .NetMap.DNS.Routes | keys}' \ +| stdbuf -i0 -oL -eL jq -rcM '{"DNS": .NetMap | has("DNS"), "Resolvers": .NetMap.DNS | has("Resolvers"), "Routes": .NetMap.DNS.Routes // {} | keys}' \ | while IFS= read -r current_ipn_dns_config do - if [[ "${current_ipn_dns_config}" != "${previous_ipn_dns_config}" ]]; then + if [[ "${current_ipn_dns_config}" == '{"DNS":true,'* && "${current_ipn_dns_config}" != "${previous_ipn_dns_config}" ]]; then # This seems to be double check, but not completely: # - The ipn watcher will send the initial state, and we compare then the current DNS state From 85cefa06b8d9cff158727dc3e80b318ffb0eac7c Mon Sep 17 00:00:00 2001 From: Laszlo Magyar Date: Wed, 17 Jun 2026 01:25:39 +0200 Subject: [PATCH 19/24] Graceful shutdown on SIGTERM caused by manual app stop --- .../rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/finish | 2 +- .../rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/finish | 2 +- .../s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/finish | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/finish b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/finish index 41f116b76..0b3dbdbaf 100755 --- a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/finish +++ b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/finish @@ -17,7 +17,7 @@ bashio::log.info \ "(by signal ${exit_code_signal})" if [[ "${exit_code_service}" -eq 256 ]]; then - if [[ "${exit_code_container}" -eq 0 ]]; then + if [[ "${exit_code_signal}" -ne 15 && "${exit_code_container}" -eq 0 ]]; then echo $((128 + $exit_code_signal)) > /run/s6-linux-init-container-results/exitcode fi [[ "${exit_code_signal}" -eq 15 ]] && exec /run/s6/basedir/bin/halt diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/finish b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/finish index e8658ea70..1de026585 100755 --- a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/finish +++ b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/finish @@ -23,7 +23,7 @@ bashio::log.info \ "(by signal ${exit_code_signal})" if [[ "${exit_code_service}" -eq 256 ]]; then - if [[ "${exit_code_container}" -eq 0 ]]; then + if [[ "${exit_code_signal}" -ne 15 && "${exit_code_container}" -eq 0 ]]; then echo $((128 + $exit_code_signal)) > /run/s6-linux-init-container-results/exitcode fi [[ "${exit_code_signal}" -eq 15 ]] && exec /run/s6/basedir/bin/halt diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/finish b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/finish index 034e11cf5..4bb4fad5a 100755 --- a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/finish +++ b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/finish @@ -15,7 +15,7 @@ bashio::log.info \ "(by signal ${exit_code_signal})" if [[ "${exit_code_service}" -eq 256 ]]; then - if [[ "${exit_code_container}" -eq 0 ]]; then + if [[ "${exit_code_signal}" -ne 15 && "${exit_code_container}" -eq 0 ]]; then echo $((128 + $exit_code_signal)) > /run/s6-linux-init-container-results/exitcode fi [[ "${exit_code_signal}" -eq 15 ]] && exec /run/s6/basedir/bin/halt From a65f402097f0a76887045c007a9796377538fcb4 Mon Sep 17 00:00:00 2001 From: Laszlo Magyar Date: Mon, 13 Apr 2026 13:24:34 +0200 Subject: [PATCH 20/24] use bashio::trace --- .../s6-overlay/s6-rc.d/magicdns-egress-proxy/run | 4 +--- .../s6-overlay/s6-rc.d/magicdns-ingress-proxy/run | 4 +--- tailscale/rootfs/usr/lib/trace.sh | 13 ------------- 3 files changed, 2 insertions(+), 19 deletions(-) delete mode 100755 tailscale/rootfs/usr/lib/trace.sh diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/run b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/run index c8822176a..45a39fd39 100755 --- a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/run +++ b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/run @@ -17,8 +17,6 @@ export LOG_FD # So in case of IPv6-only hassio, we can't use [::1]:53 for the egress DNS proxy, but we can't use anything else in resolv.conf for tailscaled. # For the status of IPv6 support see https://github.com/home-assistant/supervisor/issues/2133 -source /usr/lib/trace.sh - readonly DNSMASQ_EGRESS_ADDRESS_IPV4="127.100.100.100" readonly DNSMASQ_EGRESS_DEFAULT_PORT=53127 @@ -105,7 +103,7 @@ else bashio::exit.nok "Invalid MAGICDNS_MODE: '${MAGICDNS_MODE}'" fi -if bashio_custom::trace; then +if bashio::trace; then options+=(--log-queries) options+=(--log-debug) fi diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/run b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/run index e62a240ab..14861f6bb 100755 --- a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/run +++ b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/run @@ -10,8 +10,6 @@ export LOG_FD # Sets up IPv4 and IPv6 ingress DNS proxy for MagicDNS, that inercepts queries from hassio DNS and SU. -source /usr/lib/trace.sh - readonly MAGIC_DNS_IPV4="100.100.100.100" readonly DNSMASQ_INGRESS_DEFAULT_PORT=51100 @@ -106,7 +104,7 @@ else bashio::exit.nok "Invalid MAGICDNS_MODE: '${MAGICDNS_MODE}'" fi -if bashio_custom::trace; then +if bashio::trace; then options+=(--log-queries) options+=(--log-debug) fi diff --git a/tailscale/rootfs/usr/lib/trace.sh b/tailscale/rootfs/usr/lib/trace.sh deleted file mode 100755 index 2770eba3e..000000000 --- a/tailscale/rootfs/usr/lib/trace.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env bash -# shellcheck shell=bash - -# ------------------------------------------------------------------------------ -# Checks if we are currently running in trace mode, based on the bashio log module. -# ------------------------------------------------------------------------------ -function bashio_custom::trace() { - if [[ "${__BASHIO_LOG_LEVEL}" -lt "${__BASHIO_LOG_LEVEL_TRACE}" ]]; then - return "${__BASHIO_EXIT_NOK}" - fi - - return "${__BASHIO_EXIT_OK}" -} From f741cafb1f34c99eb3396f981c0cec2225c6cd24 Mon Sep 17 00:00:00 2001 From: Laszlo Magyar Date: Mon, 13 Apr 2026 13:28:39 +0200 Subject: [PATCH 21/24] use bashio::try --- .../s6-rc.d/magicdns-proxies-reconfigurator/run | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run index fac3e36a5..6fd1c98ab 100755 --- a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run +++ b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run @@ -20,13 +20,6 @@ declare previous_ipn_dns_config= declare magicdns_proxies_configuration declare healthy=true -function try { - set +e - (set -e; "$@") - declare -gx TRY_ERROR=$? - set -e -} - function file_non_empty() { local file=${1} @@ -91,8 +84,8 @@ do fi # Restart dnsmasq proxies if required if ! bashio::var.equals "${magicdns_proxies_configuration}" 'unchanged'; then - try restart_proxies - if ((TRY_ERROR)); then + bashio::try restart_proxies + if bashio::try.failed; then bashio::exit.nok "Failed to reconfigure and restart MagicDNS proxies. Halting app to prevent network loss." fi fi From 103374a5d3c975be588f108f81209fed0842ebd4 Mon Sep 17 00:00:00 2001 From: Laszlo Magyar Date: Tue, 9 Jun 2026 21:48:52 +0200 Subject: [PATCH 22/24] use bashio::fs.file_non_empty --- .../s6-rc.d/magicdns-proxies-reconfigurator/run | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run index 6fd1c98ab..481e9f4db 100755 --- a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run +++ b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run @@ -20,16 +20,6 @@ declare previous_ipn_dns_config= declare magicdns_proxies_configuration declare healthy=true -function file_non_empty() { - local file=${1} - - if [[ -s "${file}" ]]; then - return "${__BASHIO_EXIT_OK}" - fi - - return "${__BASHIO_EXIT_NOK}" -} - function restart_proxies() { bashio::log.info "MagicDNS proxy configuration has changed" @@ -45,7 +35,7 @@ function restart_proxies() { rm -r -f "${DNSMASQ_INGRESS_PORT_LOCATION}" s6-svc -ruwR /run/service/magicdns-ingress-proxy rm -r -f "${MAGICDNS_INGRESS_PROXY_SUPPRESS_FORWARDING_CONFIGURATION_LOCATION}" - if ! file_non_empty "${DNSMASQ_INGRESS_PORT_LOCATION}"; then + if ! bashio::fs.file_non_empty "${DNSMASQ_INGRESS_PORT_LOCATION}"; then bashio::log.fatal "MagicDNS ingress proxy port is unknown" return 1 fi From 0659a7bca0a83ac5328d9d4b649f7ce756db0d19 Mon Sep 17 00:00:00 2001 From: Laszlo Magyar Date: Tue, 4 Aug 2026 12:09:04 +0200 Subject: [PATCH 23/24] remove deprecated TS flags --- .../etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run index 481e9f4db..a2adebeba 100755 --- a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run +++ b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run @@ -50,7 +50,7 @@ bashio::log.info "Subscribing to IPN message bus..." # This jq filters the whole state into sg. like: # {"Resolvers":true,"Routes":["foo.com","tail1234.ts.net.","ts.net."]} # If the structure changes, that should break the most basic smoke test after a TS client update, so we can fix it before release -/opt/tailscale debug watch-ipn --initial=true --netmap=true --rate-limit=false \ +/opt/tailscale debug watch-ipn --initial=true \ | stdbuf -i0 -oL -eL jq -rcM '{"DNS": .NetMap | has("DNS"), "Resolvers": .NetMap.DNS | has("Resolvers"), "Routes": .NetMap.DNS.Routes // {} | keys}' \ | while IFS= read -r current_ipn_dns_config do From 1c11291decc0750ec820865d913dd1cfdb2eb4cf Mon Sep 17 00:00:00 2001 From: Laszlo Magyar Date: Tue, 4 Aug 2026 22:39:49 +0200 Subject: [PATCH 24/24] use Notify.SelfChange signal as trigger for MagicDNS reconfiguration --- .../magicdns-proxies-reconfigurator/run | 58 +++++++++---------- 1 file changed, 26 insertions(+), 32 deletions(-) diff --git a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run index a2adebeba..6c7551179 100755 --- a/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run +++ b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run @@ -14,9 +14,6 @@ readonly MAGICDNS_INGRESS_PROXY_SUPPRESS_FORWARDING_CONFIGURATION_LOCATION="/run # updated by the magicdns-ingress-proxy service readonly DNSMASQ_INGRESS_PORT_LOCATION="/run/dnsmasq_ingress_port" -declare current_ipn_dns_config -declare previous_ipn_dns_config= - declare magicdns_proxies_configuration declare healthy=true @@ -47,37 +44,34 @@ function restart_proxies() { } bashio::log.info "Subscribing to IPN message bus..." -# This jq filters the whole state into sg. like: -# {"Resolvers":true,"Routes":["foo.com","tail1234.ts.net.","ts.net."]} -# If the structure changes, that should break the most basic smoke test after a TS client update, so we can fix it before release -/opt/tailscale debug watch-ipn --initial=true \ -| stdbuf -i0 -oL -eL jq -rcM '{"DNS": .NetMap | has("DNS"), "Resolvers": .NetMap.DNS | has("Resolvers"), "Routes": .NetMap.DNS.Routes // {} | keys}' \ -| while IFS= read -r current_ipn_dns_config +# After version v1.102 TS doesn't emit the NetMap in IPN messages, we should detect the Notify.SelfChange signal, then query the DNS status. +# Details: https://github.com/tailscale/tailscale/commit/aa5da2e5f22a785f345e0a05dd8a318a8a50976e +# The --initial=true flag is intentionally not used, there is no equivalent for the SelfChange signal, we echo a dummy SelfChange signal instead. +# The jq emits only empty lines, there is no usable DNS info in the messages. +{ echo '{"SelfChange":true}'; /opt/tailscale debug watch-ipn; } \ +| stdbuf -i0 -oL -eL jq -rcM 'select(.SelfChange != null) | ""' \ +| while IFS= read -r do - if [[ "${current_ipn_dns_config}" == '{"DNS":true,'* && "${current_ipn_dns_config}" != "${previous_ipn_dns_config}" ]]; then - - # This seems to be double check, but not completely: - # - The ipn watcher will send the initial state, and we compare then the current DNS state - # with the previous call of configure-magicdns-proxies from the magicdns-proxies-configurator service. - # This way we don't miss any change between the startup of these 2 services. - # - On subsequent ipn watcher state changes we use the `tailscale dns status` json, that should be stable + # On the first run and on subsequent SelfChange events we compare the current + # DNS state with the previous call of configure-magicdns-proxies script. + # The configure-magicdns-proxies script is first called from the + # magicdns-proxies-configurator service, then from this service. + # This way we don't miss any change even between the startup of these 2 services - # If we can't check the DNS configuration, the healthcheck script will treat it as an offline situation - if ! magicdns_proxies_configuration=$(configure-magicdns-proxies test); then - printf "UNHEALTHY" > /var/run/s6/container_environment/MAGICDNS_PROXIES_RECONFIGURATOR_HEALTH_STATE - healthy=false - else - previous_ipn_dns_config="${current_ipn_dns_config}" - if bashio::var.false "${healthy}"; then - printf "HEALTHY" > /var/run/s6/container_environment/MAGICDNS_PROXIES_RECONFIGURATOR_HEALTH_STATE - healthy=true - fi - # Restart dnsmasq proxies if required - if ! bashio::var.equals "${magicdns_proxies_configuration}" 'unchanged'; then - bashio::try restart_proxies - if bashio::try.failed; then - bashio::exit.nok "Failed to reconfigure and restart MagicDNS proxies. Halting app to prevent network loss." - fi + # If we can't check the DNS configuration, the healthcheck script will treat it as an offline situation + if ! magicdns_proxies_configuration=$(configure-magicdns-proxies test); then + printf "UNHEALTHY" > /var/run/s6/container_environment/MAGICDNS_PROXIES_RECONFIGURATOR_HEALTH_STATE + healthy=false + else + if bashio::var.false "${healthy}"; then + printf "HEALTHY" > /var/run/s6/container_environment/MAGICDNS_PROXIES_RECONFIGURATOR_HEALTH_STATE + healthy=true + fi + # Restart dnsmasq proxies if required + if ! bashio::var.equals "${magicdns_proxies_configuration}" 'unchanged'; then + bashio::try restart_proxies + if bashio::try.failed; then + bashio::exit.nok "Failed to reconfigure and restart MagicDNS proxies. Halting app to prevent network loss." fi fi fi