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 subnet protection for non-full network states (eg. limited, etc.) #419

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 14 additions & 0 deletions tailscale/rootfs/command/with-contenv-merge
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/command/execlineb -S0

ifelse
{
importas -D 0 S6_KEEP_ENV S6_KEEP_ENV
eltest 0${S6_KEEP_ENV} -eq 0
}
{
s6-envdir -Lfn -- /run/s6/container_environment
exec
$@
}

$@

This file was deleted.

21 changes: 21 additions & 0 deletions tailscale/rootfs/etc/NetworkManager/dispatcher.d/protect-subnets
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/command/with-contenv-merge bashio
# shellcheck shell=bash
# The shebang 'with-contenv-merge' above is identical with 'with-contenv', but doesn't clear the current environment containing the dispatcher variables

case "${NM_DISPATCHER_ACTION}" in
up|down|dhcp4-change|dhcp6-change)
bashio::log.info "Handling Network Manager action ${DEVICE_IP_IFACE-} ${NM_DISPATCHER_ACTION}"
unprotect-subnet-routes
if ! protect-subnet-routes; then
# Better stop add-on than risking losing all network connections
echo -n 1 > /run/s6-linux-init-container-results/exitcode
exec /run/s6/basedir/bin/halt
fi
;;
connectivity-change)
bashio::log.debug "Unhandled Network Manager action ${NM_DISPATCHER_ACTION} ${CONNECTIVITY_STATE-}"
;;
*)
bashio::log.debug "Unhandled Network Manager action ${DEVICE_IP_IFACE-} ${NM_DISPATCHER_ACTION}"
;;
esac
52 changes: 31 additions & 21 deletions tailscale/rootfs/usr/bin/protect-subnet-routes
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,22 @@
# In case of non userspace networking,
# add local subnets to ip rules with higher priority than Tailscale's routing
# ==============================================================================
readonly PROTECTION_RULE_PRIORITY=5000

declare -a routes=()
declare route family
declare response
declare wait_counter=0

if bashio::config.false "userspace_networking"; then
if bashio::config.false "userspace_networking" && \
(! bashio::config.has_value "accept_routes" || bashio::config.true "accept_routes")
then
# If it is called after network configuration is changed, we need to drop cached network info
bashio::cache.flush_all
# It is possible to get "ERROR: Got unexpected response from the API: System is not ready with state: setup"
# So we wait a little
# So we wait a little, 60*5s = 300s = 5m
while ! bashio::api.supervisor GET "/addons/self/options/config" false &> /dev/null; do
if (( wait_counter++ == 18 )); then
if (( wait_counter++ == 60 )); then
bashio::log.error "Supervisor is unreachable"
bashio::exit.nok
fi
Expand All @@ -28,24 +31,31 @@ if bashio::config.false "userspace_networking"; then
fi

readarray -t routes < <(subnet-routes local)
if (( 0 < ${#routes[@]} )); then
bashio::log.info "Adding local subnets to ip rules with higher priority than Tailscale's routing,"
bashio::log.info "to prevent routing local subnets if the same subnet is routed within your tailnet."
fi
for route in "${routes[@]}"; do
if [[ "${route}" =~ .*:.* ]]; then
family="-6"
else
family="-4"
fi
bashio::log.info " Adding route ${route} to ip rules"
if ! response=$(ip "${family}" rule add to "${route}" priority 5000 table main 2>&1); then
if [[ "${response}" != "RTNETLINK answers: File exists" ]]; then
echo "${response}"
bashio::exit.nok
bashio::log.info "Adding local subnets to ip rules with higher priority than Tailscale's routing,"
bashio::log.info "to prevent routing local subnets if the same subnet is routed within your tailnet."
if (( 0 == ${#routes[@]} )); then
# Do not remote this warning, usually this is superfluous,
# but I've run into situation where Supervisor needed a restart to return valid interface address data
# (that seems to be a hard to reproduce bug, better have some log in the future than not.)
# See: https://github.com/home-assistant/supervisor/issues/5361
bashio::log.warning " There are no local subnets to protect!"
bashio::log.warning " Maybe this is a temporary situation due to configuration change underway."
else
for route in "${routes[@]}"; do
if [[ "${route}" =~ .*:.* ]]; then
family="-6"
else
bashio::log.notice " Route ${route} is already added to ip rules"
family="-4"
fi
fi
done
bashio::log.info " Adding route ${route} to ip rules"
if ! response=$(ip "${family}" rule add to "${route}" priority ${PROTECTION_RULE_PRIORITY} table main 2>&1); then
if [[ "${response}" != "RTNETLINK answers: File exists" ]]; then
echo "${response}"
bashio::exit.nok
else
bashio::log.notice " Route ${route} is already added to ip rules"
fi
fi
done
fi
fi
2 changes: 1 addition & 1 deletion tailscale/rootfs/usr/bin/subnet-routes
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ function appendarray() {
readarray -t -O "${#array[@]}" array
}

if ! [[ $1 =~ ^(local|advertised)$ ]]; then
if ! [[ "${1-}" =~ ^(local|advertised)$ ]]; then
echo "Usage: subnet-routes local|advertised" 1>&2
exit 1
fi
Expand Down
7 changes: 5 additions & 2 deletions tailscale/rootfs/usr/bin/unprotect-subnet-routes
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@
# In case of non userspace networking,
# remove local subnets from ip rules
# ==============================================================================
readonly PROTECTION_RULE_PRIORITY=5000

declare -a routes=()
declare route family

if bashio::config.false "userspace_networking"; then
if bashio::config.false "userspace_networking" && \
(! bashio::config.has_value "accept_routes" || bashio::config.true "accept_routes")
then
readarray -t routes < <( \
{ ip -4 rule list; ip -6 rule list; } \
| { grep -E '^5000:' || true ;} \
| { grep -E "^${PROTECTION_RULE_PRIORITY}:" || true ;} \
| sed -nr 's/^\d+:\s+from all to ([^\s]+) lookup main$/\1/p')
for route in "${routes[@]}"; do
bashio::log.info "Removing route ${route} from ip rules"
Expand Down