Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix MagicDNS incompatibility with Home Assistant's DNS #455

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
144 changes: 134 additions & 10 deletions tailscale/DOCS.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,12 @@ userspace_networking: true

### Option: `accept_dns`

If you are experiencing trouble with MagicDNS on this device and wish to
disable, you can do so using this option.
This option allows you to accept DNS settings of your tailnet that are
configured on the [DNS page][tailscale_dns] of the admin console.

When not set, this option is enabled by default.
For more information see the "DNS" section of this documentation.

MagicDNS may cause issues if you run things like Pi-hole or AdGuard Home
on the same machine as this add-on. In such cases disabling `accept_dns`
will help. You can still leverage MagicDNS on other devices on your network,
by adding `100.100.100.100` as a DNS server in your Pi-hole or AdGuard Home.
When not set, this option is enabled by default.

### Option: `accept_routes`

Expand Down Expand Up @@ -320,9 +317,10 @@ When not set, this option is enabled by default.

If you need to access other clients on your tailnet from your Home Assistant
instance, disable userspace networking mode, which will create a `tailscale0`
network interface on your host. To be able to address those clients not only
with their tailnet IP, but with their tailnet name, you have to configure Home
Assistant's DNS options also.
network interface on your host.

To be able to address other clients on your tailnet not only with their tailnet
IP, but with their tailnet name, see the "DNS" section of this documentation.

If you want to access other clients on your tailnet even from your local subnet,
follow steps from step 3 on [Site-to-site
Expand All @@ -347,6 +345,128 @@ CGNAT networks). You can test connections with `tailscale ping

When not set, an automatically selected port is used by default.

## DNS

**When `userspace_networking` option is disabled**, Tailscale provides a DNS (at
100.100.100.100) to be able to address other clients on your tailnet not only
with their tailnet IP, but with their tailnet name.

To let DNS resolution work correctly, you have to configure both Tailscale and
Home Assistant properly. Because the Tailscale client runs in a Home Assistant
add-on, configuration has some specialties compared to the general rules
described in Tailscale's online documentation or read in forums.

**Important:** Tailscale's MagicDNS consists of 2 parts, the DNS server
mentioned above, and the "magical" local DNS configuration manipulation to let
the device use this DNS. This manipulation interferes with Home Assistant's DNS,
and completely disabled in this add-on. You have to configure Tailscale's DNS
address in Home Assistant's network configuration manually to let Home Assistant
(and any add-on, even this Tailscale add-on) to use Tailscale's DNS.

**Important:** The only difference of this configuration compared to the general
Tailscale experience, is that you always have to use the fully qualified domain
name instead of only the device name, ie. `ping
some-tailnet-device.tail1234.ts.net` works, but `ping some-tailnet-device` does
not work.

**Note:** Disabling MagicDNS globally on the [DNS page][tailscale_dns] of the
admin console disables the "magical" local DNS configuration manipulation
(already permanently disabled in the add-on), but do not disable Tailscale's
DNS. Tailscale's DNS is always functional when `userspace_networking` option is
disabled.

**Note:** Disabling `accept_dns` option disables the "magical" local DNS
configuration manipulation (already permanently disabled in the add-on), but do
not disable Tailscale's DNS, in this case Tailscale's DNS only rejects DNS
configuration from the [DNS page][tailscale_dns] of the admin panel. Tailscale's
DNS is always functional when `userspace_networking` option is disabled.

**Note:** The reason that Tailscale's "magical" local DNS configuration
manipulation is permanently disabled in the add-on, is that Tailscale's DNS when
can't resolve a query, instead of returning an error and letting the operating
system call the next DNS, calls itself the originally configured DNS. But that
DNS is Home Assistant's DNS, where we configure, to call Tailscale's DNS... This
is a loop, that had to be cut in the add-on.

More information: [What is 100.100.100.100][tailscale_info_quad100], [DNS in
Tailscale][tailscale_info_dns], [MagicDNS][tailscale_info_magicdns], [Access a
Pi-hole from anywhere][tailscale_info_pi_hole]

Below we describe some typical scenarios for DNS configuration.

### Using Tailscale DNS to resolve only tailnet devices

On the [DNS page][tailscale_dns] of the admin console check, that "Override
local DNS" is disabled, and no "Global nameservers" are configured.

- Under **Settings** -> **System** -> **Network** configure Tailscale's DNS as
the first DNS server (IPv4: 100.100.100.100, IPv6: fd7a:115c:a1e0::53). Move
your normal DNS servers (eg. 192.168.1.1 or 1.1.1.1) to lower positions.

In this configuration Home Assistant will first try to resolve addresses with
Tailscale's DNS, if Tailscale's DNS can't resolve it (because it is not in the
tailnet), Home Assistant will use DNS servers specified at the second or lower
positions.

**Note:** If the Tailscale add-on is not started and Tailscale's DNS is not
available, Home Assistant's DNS will resolve the public IP of devices where
funnel is enabled. These values remain in the DNS cache for some time even after
the add-on is started and Tailscale's DNS is available.

### Using Tailscale DNS to resolve everything

On the [DNS page][tailscale_dns] of the admin console you already enabled
"Override local DNS", and configured "Global nameservers".

**Important:** In this scenario your Home Assistant device's tailnet IP (and
especially LAN IP) **is NOT configured** as global nameserver on the admin
console.

- Under **Settings** -> **System** -> **Network** configure Tailscale's DNS as
the only DNS server (IPv4: 100.100.100.100, IPv6: fd7a:115c:a1e0::53).

**Note:** As a backup, if the Tailscale add-on fails to start up, you can
configure your normal DNS servers (eg. 192.168.1.1 or 1.1.1.1) at the second or
lower positions.

In this configuration Home Assistant (as any other general device on the
tailnet) will always try to resolve addresses with Tailscale's DNS and
Tailscale's DNS will resolve non-tailnet addresses also. Whether you have your
own DNS (like AdGuard) _on another tailnet device_, is irrelevant for this
configuration.

### Using Tailscale DNS to resolve everything and you have your own DNS (like AdGuard) _on this device_ also

On the [DNS page][tailscale_dns] of the admin console you already enabled
"Override local DNS", and configured "Global nameservers".

**Important:** In this scenario your Home Assistant device's tailnet IP (not LAN
IP) **is configured** as global nameserver on the admin console, because you
want to redirect all DNS queries within your tailnet to the DNS running on this
device.

- In the add-on's configuration disable `accept_dns` option and restart the
add-on. This will prevent your local Tailscale DNS to accept DNS settings of
your tailnet that are configured on the the admin console above. This will
prevent the Tailscale DNS to redirect queries from your device back to your
device, causing a loop.

- Under **Settings** -> **System** -> **Network** configure your DNS as the only
DNS server (eg. IPv4: 127.0.0.1, IPv6: ::1).

- In your DNS configure Tailscale DNS for your tailnet domain as upstream DNS
server (eg. in case of AdGuard `[/tail1234.ts.net/]100.100.100.100`).

**Note:** As a backup, if the DNS add-on fails to start up, you can configure
your normal DNS servers (eg. 192.168.1.1 or 1.1.1.1) at the second or lower
positions.

**Note:** Do not configure Tailscale's DNS in Home Assistant's network
configuration, because when `accept_dns` option is disabled, Tailscale's DNS
resolves only tailnet addresses and logs a warning for each DNS query that
doesn't query this domain, and in Home Assistant you can't specify domains for a
DNS.

## Changelog & Releases

This repository keeps a change log using [GitHub's releases][releases]
Expand Down Expand Up @@ -422,12 +542,16 @@ SOFTWARE.
[taildrop]: https://tailscale.com/taildrop
[tailscale_acls]: https://login.tailscale.com/admin/acls
[tailscale_dns]: https://login.tailscale.com/admin/dns
[tailscale_info_dns]: https://tailscale.com/kb/1054/dns
[tailscale_info_exit_nodes]: https://tailscale.com/kb/1103/exit-nodes
[tailscale_info_app_connectors]: https://tailscale.com/kb/1281/app-connectors
[tailscale_info_funnel]: https://tailscale.com/kb/1223/funnel
[tailscale_info_funnel_policy_requirement]: https://tailscale.com/kb/1223/funnel#requirements-and-limitations
[tailscale_info_https]: https://tailscale.com/kb/1153/enabling-https
[tailscale_info_key_expiry]: https://tailscale.com/kb/1028/key-expiry
[tailscale_info_magicdns]: https://tailscale.com/kb/1081/magicdns
[tailscale_info_pi_hole]: https://tailscale.com/kb/1114/pi-hole
[tailscale_info_quad100]: https://tailscale.com/kb/1381/what-is-quad100
[tailscale_info_site_to_site]: https://tailscale.com/kb/1214/site-to-site
[tailscale_info_subnets]: https://tailscale.com/kb/1019/subnets
[tailscale_info_tags]: https://tailscale.com/kb/1068/tags
Expand Down
1 change: 1 addition & 0 deletions tailscale/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ ARG BUILD_ARCH=amd64
ARG TAILSCALE_VERSION="v1.78.1"
RUN \
apk add --no-cache \
dnsmasq=2.90-r3 \
ethtool=6.11-r0 \
ipcalc=1.0.3-r0 \
iproute2=6.11.0-r0 \
Expand Down
47 changes: 47 additions & 0 deletions tailscale/apparmor.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#include <tunables/global>

profile tailscale flags=(attach_disconnected,mediate_deleted) {
#include <abstractions/base>

# Capabilities
file,
signal (send) set=(kill,term,int,hup,cont),

# S6-Overlay
/init ix,
/bin/** ix,
/usr/bin/** ix,
/run/{s6,s6-rc*,service}/** ix,
/package/** ix,
/command/** ix,
/etc/services.d/** rwix,
/etc/cont-init.d/** rwix,
/etc/cont-finish.d/** rwix,
/run/{,**} rwk,
/dev/tty rw,

# Bashio
/usr/lib/bashio/** ix,
/tmp/** rwk,

# Access to options.json and other files within your addon
/data/** rw,

# General - based on complain
capability net_bind_service,
capability dac_override,
capability fsetid,
capability setgid,
capability setuid,
capability chown,
capability kill,

# General - based on Config.yaml
capability net_admin,
capability net_raw,

# Mount for MagicDNS fix
capability sys_admin,
mount options=(rw, rprivate) -> /, # unshare -m
mount options=(rw, bind) /etc/resolv.for-tailscaled.conf -> /etc/resolv.conf, # mount --bind
}
1 change: 1 addition & 0 deletions tailscale/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ host_dbus: true
privileged:
- NET_ADMIN
- NET_RAW
- SYS_ADMIN
devices:
- /dev/net/tun
map:
Expand Down
Empty file.
25 changes: 25 additions & 0 deletions tailscale/rootfs/etc/s6-overlay/s6-rc.d/dnsmasq/finish
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/command/with-contenv bashio
# ==============================================================================
# Home Assistant Community Add-on: Tailscale
# Take down the S6 supervision tree when dnsmasq fails
# ==============================================================================
readonly exit_code_container=$(</run/s6-linux-init-container-results/exitcode)
readonly exit_code_service="${1}"
readonly exit_code_signal="${2}"
readonly service="dnsmasq"

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
echo $((128 + $exit_code_signal)) > /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
16 changes: 16 additions & 0 deletions tailscale/rootfs/etc/s6-overlay/s6-rc.d/dnsmasq/run
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/command/with-contenv bashio
# shellcheck shell=bash
# ==============================================================================
# Home Assistant Community Add-on: Tailscale
# Runs the dnsmasq daemon
# ==============================================================================

readonly DNSMASQ_ADDRESS=127.52.52.52

bashio::log.info "Starting dnsmasq..."

# This is a dummy DNS to suppress tailscaled warnings about not configured upstream on each DNS query
# It answers REFUSED for everything
# It must run on port 53 to be able to specify it in a resolv.conf
echo "nameserver ${DNSMASQ_ADDRESS}" > /etc/resolv.dnsmasq.conf
exec dnsmasq --no-hosts --no-resolv --keep-in-foreground --log-facility='-' --listen-address=${DNSMASQ_ADDRESS} --port=53 --bind-interfaces
1 change: 1 addition & 0 deletions tailscale/rootfs/etc/s6-overlay/s6-rc.d/dnsmasq/type
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
longrun
38 changes: 28 additions & 10 deletions tailscale/rootfs/etc/s6-overlay/s6-rc.d/tailscaled/run
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
# Home Assistant Community Add-on: Tailscale
# Runs tailscale
# ==============================================================================

readonly TAILSCALED_LOGLEVEL_NOTICE="Tailscale logs will be suppressed after 200 lines, set add-on's configuration option 'log_level' to 'debug' to see further logs"
readonly TAILSCALED_LOGLEVEL_MESSAGE="[further tailscaled logs suppressed, set add-on's configuration option 'log_level' to 'debug' to see further tailscaled logs]"

declare -a options
declare udp_port

Expand Down Expand Up @@ -31,15 +35,29 @@ then
fi

# Run Tailscale
if bashio::debug ; then
exec /opt/tailscaled "${options[@]}"
# 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
# Running the regular way, no resolv.conf replacement
if bashio::debug ; then
exec /opt/tailscaled "${options[@]}"
else
bashio::log.notice "${TAILSCALED_LOGLEVEL_NOTICE}"
/opt/tailscaled "${options[@]}" 2>&1 \
| stdbuf -i0 -oL -eL \
sed -n -e '1,200p' \
-e "201c${TAILSCALED_LOGLEVEL_MESSAGE}"
fi
else
bashio::log.notice \
"Tailscale logs will be suppressed after 200 lines, set add-on's" \
"configuration option 'log_level' to 'debug' to see further logs"

/opt/tailscaled "${options[@]}" 2>&1 \
| stdbuf -i0 -oL -eL \
sed -n -e '1,200p' \
-e "201c[further tailscaled logs suppressed, set add-on's configuration option 'log_level' to 'debug' to see further tailscaled logs]"
# Using fake resolv.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[@]}")"
else
bashio::log.notice "${TAILSCALED_LOGLEVEL_NOTICE}"
unshare -m bash -c "mount --bind /etc/resolv.for-tailscaled.conf /etc/resolv.conf; exec /opt/tailscaled $(printf "\"%s\" " "${options[@]}") 2>&1" \
| stdbuf -i0 -oL -eL \
sed -n -e '1,200p' \
-e "201c${TAILSCALED_LOGLEVEL_MESSAGE}"
fi
fi
Empty file.
9 changes: 9 additions & 0 deletions tailscale/rootfs/etc/s6-overlay/scripts/stage2_hook.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@
# S6 Overlay stage2 hook to customize services
# ==============================================================================

# Disable dnsmasq service when userspace-networking is enabled or accepting dns is disabled
if ! bashio::config.has_value "userspace_networking" || \
bashio::config.true "userspace_networking" || \
bashio::config.false "accept_dns";
then
rm /etc/s6-overlay/s6-rc.d/user/contents.d/dnsmasq
rm /etc/s6-overlay/s6-rc.d/tailscaled/dependencies.d/dnsmasq
fi

# Disable protect-subnets service when userspace-networking is enabled or accepting routes is disabled
if ! bashio::config.has_value "userspace_networking" || \
bashio::config.true "userspace_networking" || \
Expand Down
4 changes: 2 additions & 2 deletions tailscale/translations/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ configuration:
accept_dns:
name: Accept DNS
description: >-
If you are experiencing trouble with MagicDNS on this device and wish to
disable, you can do so using this option.
This option allows you to accept DNS settings of your tailnet that are
configured on the DNS page of the admin console.
When not set, this option is enabled by default.
accept_routes:
name: Accept routes
Expand Down
Loading