diff --git a/tailscale/rootfs/command/with-contenv-merge b/tailscale/rootfs/command/with-contenv-merge new file mode 100755 index 00000000..4da5ad4e --- /dev/null +++ b/tailscale/rootfs/command/with-contenv-merge @@ -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 + $@ +} + +$@ \ No newline at end of file diff --git a/tailscale/rootfs/etc/NetworkManager/dispatcher.d/connectivity-change b/tailscale/rootfs/etc/NetworkManager/dispatcher.d/connectivity-change deleted file mode 100755 index 8d2bb0f0..00000000 --- a/tailscale/rootfs/etc/NetworkManager/dispatcher.d/connectivity-change +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash -# DO NOT use #!/command/with-contenv bashio, because that won't set the environment variables - -if [[ "$NM_DISPATCHER_ACTION" == "connectivity-change" ]]; then - if [[ "$CONNECTIVITY_STATE" == "FULL" ]]; then - 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 - else # UNKNOWN, NONE, PORTAL, LIMITED - unprotect-subnet-routes - fi -fi diff --git a/tailscale/rootfs/etc/NetworkManager/dispatcher.d/protect-subnets b/tailscale/rootfs/etc/NetworkManager/dispatcher.d/protect-subnets new file mode 100755 index 00000000..258f115b --- /dev/null +++ b/tailscale/rootfs/etc/NetworkManager/dispatcher.d/protect-subnets @@ -0,0 +1,22 @@ +#!/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 + bashio::log.error "Failed to protect subnet routes. Halting add-on to prevent network loss." + 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 diff --git a/tailscale/rootfs/usr/bin/protect-subnet-routes b/tailscale/rootfs/usr/bin/protect-subnet-routes index 2245dc33..52f7786a 100755 --- a/tailscale/rootfs/usr/bin/protect-subnet-routes +++ b/tailscale/rootfs/usr/bin/protect-subnet-routes @@ -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 @@ -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 diff --git a/tailscale/rootfs/usr/bin/subnet-routes b/tailscale/rootfs/usr/bin/subnet-routes index ad2c72b8..43c9fd1d 100755 --- a/tailscale/rootfs/usr/bin/subnet-routes +++ b/tailscale/rootfs/usr/bin/subnet-routes @@ -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 diff --git a/tailscale/rootfs/usr/bin/unprotect-subnet-routes b/tailscale/rootfs/usr/bin/unprotect-subnet-routes index 5ddeccb3..6f128430 100755 --- a/tailscale/rootfs/usr/bin/unprotect-subnet-routes +++ b/tailscale/rootfs/usr/bin/unprotect-subnet-routes @@ -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"