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/finish b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-egress-proxy/finish index 25314e796..1787e54cc 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,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 @@ -23,5 +25,10 @@ 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 + if ! s6-rc list &>/dev/null; then + # Required during startup + exit 125 + else + exec /run/s6/basedir/bin/halt + fi fi 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..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 @@ -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..." @@ -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 + options+=(--no-hosts) options+=(--no-resolv) options+=(--conf-file=/dev/null) @@ -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) || \ @@ -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 + +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[@]}" 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..1a2d330ad 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,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 @@ -26,5 +31,10 @@ 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 + if ! s6-rc list &>/dev/null; then + # Required during startup + exit 125 + else + exec /run/s6/basedir/bin/halt + fi 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 7e8e54677..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 @@ -6,18 +6,23 @@ 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 @@ -25,9 +30,21 @@ 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) @@ -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[@]}" 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..e67938848 --- /dev/null +++ b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/finish @@ -0,0 +1,32 @@ +#!/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 + if ! s6-rc list &>/dev/null; then + # Required during startup + exit 125 + else + exec /run/s6/basedir/bin/halt + fi +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..6c7551179 --- /dev/null +++ b/tailscale/rootfs/etc/s6-overlay/s6-rc.d/magicdns-proxies-reconfigurator/run @@ -0,0 +1,78 @@ +#!/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 + +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}" + if ! bashio::fs.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}")" + rm -r -f "${DNSMASQ_INGRESS_PORT_LOCATION}" + magicdns-ingress-proxy-forwarding remove drop +} + +bashio::log.info "Subscribing to IPN message bus..." +# 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 + # 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 + 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 +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 577ef9552..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,7 +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" - 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..c3183b8be --- /dev/null +++ b/tailscale/rootfs/usr/bin/configure-magicdns-proxies @@ -0,0 +1,115 @@ +#!/usr/bin/env bashio +# shellcheck shell=bash +export LOG_FD +# ============================================================================== +# Configures the dnsmasq proxies black/white list +# ============================================================================== + +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 +# - "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_1}" +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}") + 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_1}") + black_white_list+=("${LOG_SERVER_2}") + fi + + # Always resolve letsencrypt's api also (independent of share_homeassistant) + black_white_list+=("${LETSENCRYPT_API}") + fi +fi + +printf "%s" "${MAGICDNS_MODE}" > /var/run/s6/container_environment/MAGICDNS_MODE +printf "%s" "${black_white_list[@]/%/$'\n'}" > "${DNSMASQ_BLACK_WHITE_LIST_LOCATION}" + +printf "%s" "${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 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 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 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}" -}