Skip to content

v1.0.22: Two-layer kill switch, Kill Switch Follows Exit Node, Hide Tailscale Search Domain

Latest

Choose a tag to compare

@github-actions github-actions released this 05 Oct 00:21
· 2 commits to main since this release

gl-tailscale-fix v1.0.22

Two-layer kill switch (plus IPv6 improvements), Kill Switch Follows Exit Node, Hide Tailscale Search Domain, hardening, fixes & more.

Kill switch: two independent layers

v1.0.21's kill switch was one layer of routing rules, installed when the network first came up at
boot. Additional IPv6 improvement: on our GL-MT3000 on 4.9.0 those rules arrived 10–22 seconds after
the router's internet connection came up, and in that gap a LAN device's IPv6 traffic reached the
internet (its IPv4 stayed blocked). v1.0.22's saved firewall layer is re-applied at boot before the
firewall and the network start; through two armed reboots of a GL-MT3000 on 4.11.0, the LAN test
client only saw the exit node or was blocked. This only applies when the router's internet
connection has IPv6 and IPv6 is enabled on the router itself.

v1.0.22 runs two independent layers. Each survives the event that clears the other:

  • A firewall layer (new). While armed, it closes every firewall forwarding from your LAN, guest
    and iot networks into an internet connection or another VPN client on the router (GL's WireGuard,
    OpenVPN and AmneziaWG clients, and ZeroTier), and records what it closed in the router's saved
    configuration. Because it's saved configuration, it's already in place when the firewall starts at
    boot, before any internet connection comes up — and a network restart, which clears every routing
    rule, does not touch it. A boot-time pass, running before the firewall itself starts, closes
    anything that came back enabled since the kill switch closed it — for example, defaults restored
    by a firmware upgrade.
  • The routing layer from v1.0.21, kept. Policy routing rules that block LAN, guest and iot
    traffic at the kernel routing layer, before conntrack and firewall evaluation. GL's own Tailscale
    restart — which runs on every Tailscale Apply, and on every network event while Tailscale is
    enabled, whenever IP Masquerading is on or has ever been on — briefly restarts the firewall too,
    opening the firewall layer for up to about a second and a half. The routing layer doesn't depend
    on the firewall, so it covers that moment: on the MT3000, GL firmware 4.11.0, it held with zero
    packets reaching the internet on every sample, while the firewall layer was open for as long as
    0.8 seconds.

iot networks are covered by the kill switch for the first time in v1.0.22 (GL's own kill switch
covers only the main LAN).

When it's armed hasn't changed: it's on whenever the toggle is on and Tailscale is enabled, and
changing or removing your exit node never turns it off unless you turn on the new opt-in Kill
Switch Follows Exit Node (below).

While a Custom Exit Node is in use, run no other routing on the router: no VPN client (GL's
WireGuard, OpenVPN or AmneziaWG), no ZeroTier routing (ZeroTier only as an overlay for management
and remote access), and no Tor.

Guest and iot no longer bypass the exit node: whenever an exit node is set, GL's firmware adds
a routing rule that sends guest IPv4 traffic (and, from 4.9, iot traffic) around it, outside GL's
LAN-only kill switch. v1.0.21 replaced that rule only when "Route Guest via Tailscale" was on;
v1.0.22 replaces it whenever the kill switch is armed, with or without Route Guest. LAN devices and
IPv6 were not affected. If you use a guest or iot network with an exit node, upgrade.

Changed from v1.0.21: the router's own upstream network is no longer reachable while the kill
switch is armed.
Devices on the LAN, guest and iot networks cannot reach the network the router
itself is connected through — a hotel or office network, for example. That includes a hotel or venue
captive-portal sign-in page and the upstream router's admin page. To sign in to a captive portal,
turn the kill switch off briefly, sign in, then turn it back on. In v1.0.21 the upstream network
stayed reachable while armed.

While armed, the kill switch also:

  • closes the LAN, guest and iot paths to other VPN clients on the router — WireGuard, OpenVPN,
    AmneziaWG and ZeroTier
  • lets guest devices (unless "Route Guest via Tailscale" is on) and iot devices reach the internet
    only through the exit node, and only while Tailscale's own rule allows it, so they may have no
    internet at times. Turn on "Route Guest via Tailscale" for dependable guest access through the
    exit node; there's no equivalent switch for iot.
  • recognises internet connections it doesn't know by name, from whichever one carries the default
    route, and closes them within seconds
  • refuses to arm when the router isn't in Router mode (Access Point, Extender, WDS), and says why
  • logs a warning, without changing anything, when a firewall setting would let traffic around it
  • stops an already-open direct connection within about two seconds of arming
  • restores what it closed when you turn it off, disable Tailscale or uninstall the plugin, except a
    forwarding that GL 4.11's per-network setting for turning off internet access on the guest or iot
    network has switched off in the meantime: that one stays off, and is named in the router's log.
    One more exception is under Known limitations. If a routing-layer rule is ever left behind after
    you disarm it, the watchdog removes it normally within one poll (about five seconds)

On the MT3000 (GL firmware 4.11.0), two armed reboots and a network restart while armed were also
tested end to end: the LAN client only
ever saw the exit node's addresses or was blocked — never its own — and both layers came back every
time.

Upgrading in place from v1.0.21 was tested under this two-layer design too, on the MT3000 (GL
firmware 4.11.0): an armed v1.0.21 router
with an exit node set was upgraded while armed. The LAN client only ever saw the exit node's
addresses or was blocked — v1.0.21's kill-switch rules stayed in place throughout, since v1.0.22 only
adds to them — and both layers were confirmed up within seconds of the upgrade.

The Beryl 7 was also tested, on GL firmware 4.9.0: installing, arming, GL's own Tailscale restart, a
plain firewall restart with a guest client connected, and arming and disarming with Tailscale
stopped — all with the same results as above.

Tested on three routers: the MT3000 on GL firmware 4.11.0, the Beryl 7 on GL firmware 4.9.0, and
the Slate AX (GL-AXT1800) on GL firmware 4.8.4 with the newer nftables-based firewall (fw4). On the
Slate AX the kill switch was tested through a firewall restart while armed, the guest network's rule
swap, guest traffic through the exit node, an already-open connection being stopped, an armed reboot
and the side switch, and in every armed window the test client saw only the exit node's addresses or
nothing. For the reboot, the Wi-Fi test client could only watch from when it rejoined; the router
itself was checked for the whole boot.

New: Block WAN Subnets now covers IPv6

GL's own "Block WAN Subnets" setting (on firmware 4.8 and 4.9, Network → Guest Network, plus Network
→ IoT Network on 4.9; on 4.10, each network's card under Network → Subnet; on 4.11, each network's
card under Network → LAN, custom VLANs included) blocks those networks from reaching the network the
router itself is connected through — a hotel or office network, for example — but only its IPv4
subnet. A guest device could still reach the upstream network over IPv6 (observed on GL firmware
4.8.4, 4.9.0 and 4.11.0).

v1.0.22 adds the matching IPv6 block for every network GL isolates, following GL's own decision for
each internet connection: it is added only where GL's IPv4 block is in place, and removed again when
GL's block goes away — including when the setting is turned off; turning it back on adds the IPv6
block again. The IPv6 rules are also removed when the plugin is uninstalled. It works whether or not
Tailscale or the kill switch is on. It is kept current as internet connections change — on every
connection event, and re-checked by the watchdog about every 30 seconds.

Side-switch accessory

The side-switch script (accessories/gl-switch.d/tailscale.sh) is installed separately from the plugin — re-run its
install command from the README to update it.

  • With plugin v1.0.22 and the kill switch enabled in the script's configuration (the default), an "on" flip arms the
    plugin's kill switch before GL starts Tailscale, so the routing rules GL adds for the guest and IoT networks as
    Tailscale starts land on paths that are already blocked. The transition lockdown now covers GL's IoT network too.
  • The lockdown is released only once the kill switch's routing rules are actually in place.
  • The lockdown waits up to 20 seconds for the kill switch and stays in place if it can't confirm it. On 4.9 and later,
    every "on" flip also turns on GL's IP Masquerading so LAN devices can use the exit node, even if you had switched it
    off.
  • On firmware 4.9 and later, an "off" flip now also removes GL's own LAN kill-switch rule, which previously left LAN
    devices without IPv4 internet access after the switch was turned off.
  • Tested on the GL-MT3000 (Beryl AX) running firmware 4.11.0, the GL-MT3600BE (Beryl 7) running firmware 4.9.0 and
    the GL-AXT1800 (Slate AX) running firmware 4.8.4.

Exit-node reconcile, both directions — fixes #14

GL's stored Custom Exit Node setting is now treated as the authoritative intent. The plugin
pushes set and clear to the daemon when they diverge — on re-apply events and UI applies —
with watchdog backstops that detect a daemon stuck routing against an empty setting (or
failing to route against a configured one) and retry until the daemon matches the
configuration. This addresses the field failure reported in
#14 (thanks @basav-raj):
a router whose daemon never picked up the configured exit node, leaving LAN clients
blackholed. Upgrading to v1.0.22 on firmware 4.9+ resets the reconcile tracking so the next
re-apply re-evaluates the stored setting — routers stuck this way heal without manual
steps; on pre-4.9 the upgrade seeds the tracking instead, so a long-stale stored exit node
is not resurrected. Tested on GL 4.8.4 (fw4) and 4.9.0 (fw3). Consequence, by design: an
exit node set manually via tailscale set --exit-node=<ip> from SSH (bypassing GL's UI) is
reconciled away — GL's UI is the supported control surface.

Route Guest via Tailscale: now applies when the exit node is chosen from the GL app

"Route Guest via Tailscale" now also applies when the exit node is chosen from the GL mobile app or
GoodCloud, not only when it is applied from the router's web page. GL adds its routing rule for the
guest network several seconds after the exit node is chosen, and the watchdog replaces it on its next
pass, normally within about 5 seconds of GL adding it (longer if the watchdog is busy with a full
re-apply). While the kill switch is off, guest traffic can use your real internet connection, around
the exit node, from when GL adds its rule until the watchdog replaces it. Before this fix, guest
traffic could keep using your real internet connection, around the exit node, until the next network
event while the kill switch was off.

Kill Switch Follows Exit Node (opt-in)

New toggle: the kill switch arms itself when you configure a Custom Exit Node and disarms
when you clear it. Keyed strictly on GL's stored setting — never the daemon's transient
state — so restart transients can't flap it. Default off; the standard lifecycle (manual
arm/disarm, fail-secure through exit-node removal) is unchanged.

With this on, turning Custom Exit Node off also turns the kill switch off, and traffic uses
your normal connection until you pick a new exit node. To switch exit nodes without a gap,
leave this off.

Kill switch toggle stays operable while tailscaled is down

The kill switch doesn't depend on the Tailscale daemon: it can be armed and disarmed without a
working one, so you're never stranded behind a blocked LAN with a crashed daemon. With the kill
switch armed and Tailscale itself stopped, the admin page and SSH both stayed reachable — over
ZeroTier, and the admin page from a LAN device too — while LAN internet stayed blocked.

Exit-node settings survive a slow start

Some routers lost their exit-node advertisement, Tailscale SSH and advertised routes after a power
cycle, until the next Apply (two field reports). Those settings are now retried as soon as
Tailscale is running.

Hide Tailscale Search Domain (opt-in)

GL firmware writes your tailnet's MagicDNS suffix into the DHCP domain that every LAN
client receives — a passive fingerprint that the network sits behind Tailscale. On 4.8 GL adds it
in front of your local domain; on 4.9 and 4.11 it replaces your local domain outright. Either way,
.lan names stop resolving. This opt-in
toggle strips the tailnet suffix from what DHCP advertises while leaving resolution intact:
explicit *.ts.net names still resolve, your local domain returns to clients, and the
configured DNS server list is not modified (the advertised search suffix itself necessarily
changes — that is the feature). On 4.9 and 4.11, when GL's own Tailscale restart writes the suffix
back, the plugin removes it again within a few seconds. Turning the toggle off gives GL its suffix
back at the next network event — on 4.8, only after you turn Tailscale off and on again. Tested on
GL 4.8.4 (GL-AXT1800), 4.9.0 (GL-MT3600BE) and 4.11.0 (GL-MT3000).

Version Manager: rebuild detection + more robust downloads

Upstream occasionally replaces a release binary in place under the same version number
(the June ipnbus fix shipped that way). The Version Manager now compares full build strings
and offers these as "(rebuild)" updates, so routers stuck on a superseded build can
self-serve the fix. All downloads are time-bounded with a guaranteed terminal status — a
dead or crawling connection produces a clear error instead of a stuck spinner.

Package for GL's OpenWrt 25 firmware (beta, untested)

GL's OpenWrt 25 firmware line (the -op25 builds) replaced the opkg package manager with apk,
so the .ipk can't be installed there. This release attaches an .apk build,
gl-tailscale-fix-1.0.22-beta-untested.apk: beta and untested — we haven't tested this version on
GL's OpenWrt 25 firmware yet (the v1.0.21 package installed cleanly there). Install it over SSH with
apk add --allow-untrusted: the package isn't signed yet, and GL's web uploader rejects unsigned
packages.

Packaging and service correctness

  • Installed files now carry root ownership regardless of build host.
  • Upgrades restart the watchdog so new code takes effect immediately, and restart nginx in
    an ordered stop→wait→start sequence.
  • Package removal now actually drops the UI injection (moved to a post-removal hook — the
    old pre-removal restart ran while the files still existed).
  • Fixed an internal exit-code check that had silently disabled the daemon-down guard around
    CLI calls (present since v1.0.15), and a BusyBox pgrep -x mismatch it had been masking.
  • Upgrades now check that the web interface files carry the new version and log an error if
    they don't — a field report of a stale interface after an interrupted upgrade.
  • Uninstalling now stops the plugin's background tasks first, so none of them can re-apply
    settings after removal has cleaned up.
  • Firmware upgrades no longer bring the old firmware's Tailscale back: the Tailscale binary is kept
    across an upgrade only while one installed with the Version Manager is in place (Restore stops
    keeping it). Found on a real 4.8.5 → 4.9.0 upgrade, which left Tailscale 1.80.3 running instead of
    the firmware's 1.92.5. On a router already upgraded that way, Restore in the Version Manager
    switches to the firmware's own Tailscale.

Hardening

  • A failed Apply now reverts the switches AND shows a visible error notice (previously a
    silent revert; original revert contribution by @slipstyle in #13).
  • Advertised-route and exit-node values are charset-validated before reaching any shell.
  • Every call the plugin's background scripts make into the Tailscale command-line tool is now
    time-limited (10 seconds), so one hung call can no longer stop the watchdog for good. A hung
    daemon can still delay the watchdog's checks, because a full re-apply waits for Tailscale to
    report Running.
  • If the plugin cannot read its own or GL's Tailscale settings at all, it now leaves the kill switch
    and every setting as they are and retries, instead of treating the failed read as "turned off".
    In v1.0.21 a failed read of GL's Tailscale setting, repeated by the re-apply script it then
    started, ran the full "Tailscale disabled" teardown, which turned the kill switch off and reset
    the plugin's switches to off; no such failure has been observed on a router, and it is fixed
    because the kill switch must never fail open.

Known limitations

  • Firewall settings that route traffic around the kill switch — a global forward policy of ACCEPT,
    or an ACCEPT rule from lan, guest or iot toward an internet connection or VPN-client zone — are
    logged as warnings, not changed.
  • Devices on GL 4.11's custom VLAN subnets are not protected by the kill switch. It doesn't close
    such a subnet's path to the internet, adds no routing rule for it and logs no warning about it, so
    when the exit node isn't set or isn't working, those devices use your normal internet connection.
    Measured on the MT3000, GL firmware 4.11.0, with the kill switch armed and no exit node set: a
    device on a custom VLAN subnet reached the internet with the router's real ISP address on every
    probe, while a LAN device in the same test was blocked on every probe. v1.0.21 had the same gap.
    Coverage is planned for v1.0.23.
  • On MediaTek routers with hardware NAT offload and a wired internet connection, a direct connection
    that was already running before the kill switch was turned on can keep flowing in the offload
    engine until it ends; new connections and exit-node connections are not affected, and a reboot
    clears it. In practice this only matters if you arm in the middle of a direct session (v1.0.21
    behaved the same way).
  • While armed it closes LAN access to ZeroTier, which affects anyone routing LAN traffic over
    ZeroTier.
  • If two forwardings connect the same two zones and you'd disabled one of them by hand, turning the
    kill switch off re-enables both. The two can differ, because both of the router's firewall
    generations (fw3 and fw4) honour a forwarding's address-family option — for example, one can be
    for IPv4 and the other for IPv6. Precise per-forwarding tracking is planned for v1.0.23.
  • After upgrading the firmware from 4.8.x to 4.9 or later, turn on IP Masquerading on GL's Tailscale page: from 4.9
    GL manages IPv4 masquerade with that toggle, which is off after the upgrade, and it switches off the masquerade the
    plugin had set up on 4.8.x. Until then, LAN devices get no IPv4 internet through the exit node; IPv6 keeps
    working, and nothing leaks. Measured on a GL-MT3600BE (Beryl 7) upgraded from 4.8.5 to 4.9.0.
  • After any firmware upgrade the package lists no longer show the plugin, although it keeps running: run the
    installer again to list it. Your settings and the kill switch stay as they were.

Credits

  • @basav-raj — issue #14 field report (exit-node divergence, MT3000 4.9.0)
  • @slipstyle — PR #13 staged-changes revert contribution
  • Tailscale binaries by @admonstrator's glinet-tailscale-updater
  • xPliZit_xs — tested the OpenWrt 25 .apk on a Flint 2 (GL 4.9.1-op25)

Full Changelog: v1.0.21...v1.0.22