Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
d7a5c9e
Move MagicDNS egress proxy to non-default port
lmagyar Apr 8, 2026
9f40826
Move egress proxy setup script into separate file like ingress proxy
lmagyar Apr 9, 2026
e304660
Move MagicDNS ingress proxy to non-default port
lmagyar Apr 9, 2026
9dcf4f3
replace bind-dynamic with bind-interfaces for egress proxy
lmagyar Apr 10, 2026
66f54fb
use different ports for the proxies
lmagyar Apr 10, 2026
989caa9
tailscaled waits for the egress proxy
lmagyar Apr 10, 2026
155244f
comment on bind-dynamic vs bind-interfaces
lmagyar Apr 12, 2026
367b9a9
Fix magicdns for appconnectors
lmagyar Apr 29, 2026
f79be40
fix shellcheck errors
lmagyar May 1, 2026
96d786e
minor fixes, thanks bunny
lmagyar May 2, 2026
6936b26
halp app on dns reconfig failure
lmagyar May 2, 2026
81871d4
use plain exit.nok
lmagyar May 2, 2026
d15f1bc
test file content also, thanks bunny
lmagyar May 2, 2026
6118310
don't poll for dns config changes, use ipn bus events
lmagyar May 2, 2026
c240649
add log line
lmagyar May 3, 2026
afb8cf3
Advance previous_ipn_dns_config only after a successful probe.
lmagyar May 3, 2026
1d26902
use both login and both log server domains
lmagyar May 3, 2026
3efddfd
fix jq errors on non-DNS netmaps
lmagyar May 3, 2026
85cefa0
Graceful shutdown on SIGTERM caused by manual app stop
lmagyar Jun 16, 2026
a65f402
use bashio::trace
lmagyar Apr 13, 2026
f741caf
use bashio::try
lmagyar Apr 13, 2026
103374a
use bashio::fs.file_non_empty
lmagyar Jun 9, 2026
0659a7b
remove deprecated TS flags
lmagyar Aug 4, 2026
1c11291
use Notify.SelfChange signal as trigger for MagicDNS reconfiguration
lmagyar Aug 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/etc/s6-overlay/s6-rc.d/init-magicdns-proxies/finish
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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}"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/etc/s6-overlay/s6-rc.d/init-magicdns-proxies/run
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ readonly exit_code_service="${1}"
readonly exit_code_signal="${2}"
readonly service="magicdns-egress-proxy"

magicdns-egress-proxy-forwarding remove forwarding

bashio::log.info \
"Service ${service} exited with code ${exit_code_service}" \
"(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
Expand Down
84 changes: 64 additions & 20 deletions tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/run
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,31 @@ 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.
# 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_PORT=53
readonly DNSMASQ_BLACK_WHITE_LIST_LOCATION="/etc/dnsmasq-black-white-list"
readonly DNSMASQ_EGRESS_DEFAULT_PORT=53127

# 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
declare dnsmasq_egress_port

bashio::log.info "Starting MagicDNS egress proxy..."

Expand All @@ -36,6 +41,17 @@ function dig_hassio_dns() {
| head -n 1
}

# 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 >= DNSMASQ_EGRESS_DEFAULT_PORT + 1000)); then
bashio::exit.nok "Can't find free port for MagicDNS egress proxy"
fi
done
Comment thread
lmagyar marked this conversation as resolved.

options+=(--no-hosts)
options+=(--no-resolv)
options+=(--conf-file=/dev/null)
Expand All @@ -44,8 +60,11 @@ options+=(--log-facility='-')
options+=(--cache-size=0)

options+=(--listen-address="${DNSMASQ_EGRESS_ADDRESS_IPV4}")
options+=(--bind-dynamic)
options+=(--port="${DNSMASQ_EGRESS_PORT}")
# 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}")

# Hassio DNS's IP addresses
if ! hassio_dns_ipv4=$(dig_hassio_dns A) || \
Expand All @@ -54,24 +73,49 @@ 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}"
if bashio::var.equals "${MAGICDNS_MODE}" "RESTRICTED"; then
# This means "Unrestricted" on egress side, ie. everything except domains in black-white-list are allowed

# Return NXDOMAIN for everything, except the white_list
options+=(--address=/#/)
for domain in "${white_list[@]}"; do
options+=(--server=/${domain}/${hassio_dns_ipv4})
done
# 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
elif bashio::var.equals "${MAGICDNS_MODE}" "UNRESTRICTED"; then
# This means "Restricted" on egress side, ie. only domains in black-white-list are allowed

if bashio_custom::trace; then
# 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
else
bashio::exit.nok "Invalid MAGICDNS_MODE: '${MAGICDNS_MODE}'"
fi
Comment thread
lmagyar marked this conversation as resolved.

if bashio::trace; then
options+=(--log-queries)
options+=(--log-debug)
fi

# 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
magicdns-egress-proxy-forwarding setup forwarding "${dnsmasq_egress_port}"

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
# It must run on port 53 to be able to specify it in a resolv.conf
exec dnsmasq "${options[@]}"
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,20 @@ 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}" \
"(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
Expand Down
77 changes: 55 additions & 22 deletions tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-ingress-proxy/run
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,45 @@ 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

source /usr/lib/trace.sh
# Sets up IPv4 and IPv6 ingress DNS proxy for MagicDNS, that inercepts queries from hassio DNS and SU.

readonly MAGIC_DNS_IPV4="100.100.100.100"

readonly DNSMASQ_INGRESS_PORT=53
readonly DNSMASQ_BLACK_WHITE_LIST_LOCATION="/etc/dnsmasq-black-white-list"
readonly DNSMASQ_INGRESS_DEFAULT_PORT=51100

# 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
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 >= DNSMASQ_INGRESS_DEFAULT_PORT + 1000)); 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)
Expand All @@ -51,39 +68,55 @@ 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})
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
elif bashio::var.equals "${MAGICDNS_MODE}" "UNRESTRICTED"; then
# 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
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

magicdns-ingress-proxy-forwarding setup forwarding
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[@]}"
Original file line number Diff line number Diff line change
@@ -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
Loading
Loading