Skip to content

Releases: enesilhaydin/lan-killswitch

v1.2.5

Choose a tag to compare

@enesilhaydin enesilhaydin released this 13 Jun 09:57

Fixes endpoint-guard supervision: the main watchdog now restarts endpoint-guard.sh if Android kills it or the PID file goes stale. This prevents the WireGuard endpoint route from silently falling back into tun0 after boot.\n\nSHA256: 7c3adcb390682b7ad27636210310b175cd0a997c460437a9183937cbd610a801

v1.2.4

Choose a tag to compare

@enesilhaydin enesilhaydin released this 11 Jun 22:55
  • Adds built-in WireGuard endpoint-loop guard for ZTE F50 / Mullvad style full-tunnel routing.
  • Adds TCP MSS clamp on tun+ forwarding to avoid MTU blackholes.
  • Adds tun+ MASQUERADE keepalive for strict tunnel providers such as Mullvad.
  • Keeps the watchdog passive/idempotent: no ip monitor listener, no order policing, no APN accept/NAT path.
  • Verified with endpoint-guard, logic, and netns leak tests.

v1.2.1 - CI fix + chain-integrity hardening

Choose a tag to compare

@enesilhaydin enesilhaydin released this 11 Jun 11:09

Fixes the red CI (logic tests still referenced the removed ensure_top) and adds a chain-integrity safeguard found in code review.

Code

  • ensure_chain now verifies chain CONTENTS, not just existence. A chain that exists but lost its rules is dangerous:

    • empty chain → implicit RETURN → packet falls through → leak
    • missing the -o tun+ RETURN → even VPN traffic is rejected → permanent outage (VPN up but no internet)

    It now rebuilds whenever either required rule is absent (not just when the chain is gone). Applies to both service.sh and post-fs-data.sh.

Tests (CI green)

  • logic-test.sh: dropped the obsolete ensure_top tests; now asserts the v1.2.0 invariants (ensure_top and ip monitor MUST stay absent — regression guard against the churn/sweep-storm) and tests ensure_hook install / idempotency / de-dup.
  • leak-netns.sh: replaced the old DRIFT test with
    • [E] order-independence — a qualified -o tun0 ACCEPT placed above our hook still cannot leak while the VPN is down (proves ensure_top was unnecessary), and
    • [F] chain-integrity recovery — sabotage the tun+ RETURN, prove ensure_chain rebuilds it.

Local + CI: logic 5/5, netns 12/12 (incl. IPv6, allow-lan, order-independence, chain-integrity).

Guarantees unchanged

VPN down → hotspot blocked; VPN up → traffic flows. This release only hardens against a broken-chain edge case and turns CI green.

v1.2.0 - passive watchdog (stability)

Choose a tag to compare

@enesilhaydin enesilhaydin released this 11 Jun 10:53

Reworks the watchdog to be passive, fixing iptables churn and a self-inflicted leak window introduced by over-aggressive order-policing.

What was wrong

Previous versions ran a 10s sweep plus an ip monitor link listener, both policing FORWARD rule order and re-lifting the hook to the top whenever another module (e.g. vpn-gateway) inserted a rule above it. On a device running both, this became a tug-of-war over the top slot:

  • constant iptables -D/-I churn + high CPU
  • a sweep storm on every link event
  • brief windows where the kill switch was momentarily removed during the delete-then-reinsert — i.e. the safety mechanism created its own leak window

Why order never mattered

Every vpn-gateway ACCEPT is qualified with -o tun0 / -i tun0. With the VPN down, tun0 has no route, those ACCEPTs can't match, and the packet still reaches our REJECT — regardless of where our hook sits. With the VPN up, the traffic is meant to pass anyway. Verdict is identical either way: blocked when VPN down, allowed when VPN up.

Changes

  • Removed ensure_top() (order policing) entirely.
  • Removed the ip monitor link event listener (sweep-storm source).
  • Watchdog cadence 10s → 60s, fully passive: writes to iptables only when a hook is genuinely missing or duplicated. Steady state = read-only checks, zero writes.
  • post-fs-data hook install made idempotent too (no churn across the 90s boot retry).
  • README: new Why the watchdog is passive section with the position-irrelevance proof.

Guarantees unchanged

Kill-switch behaviour is identical: VPN down → hotspot blocked, VPN up → traffic flows. This release only removes harmful churn; it does not relax protection. In fact it removes the order-churn leak window.

Note

If your hotspot internet drops ~20-60s after connecting, that is most likely a WireGuard endpoint routing-loop / handshake issue (the routing module's domain), not this kill switch. Use scripts/diag.sh up / down to confirm — it auto-flags wg_endpoint_via_tunnel=LOOP_BAD.

v1.1.4 - top-position guarantee + opt-in intra-LAN

Choose a tag to compare

@enesilhaydin enesilhaydin released this 03 Jun 13:30

Hardening release. No change to the core fail-closed behavior.

Fixed

  • Top-position guarantee (was a real leak risk). The watchdog now
    verifies our FORWARD hooks sit at the very top of the chain, not
    merely that one exists. A lone hook could otherwise end up below an
    ACCEPT that another module (e.g. a VPN routing module) inserted after
    boot, and be silently bypassed. ensure_top() re-lifts on drift,
    without opening a leak gap.
  • toybox grep portability. load_ifaces() used \s, unsupported by
    Android's toybox grep -> indented comments in the interface list were
    mis-parsed. Switched to [[:space:]].

Added

  • Opt-in intra-LAN exception via /data/adb/lan-killswitch.allow-lan:
    permits forwarding between local/tether interfaces while still
    rejecting non-tunnel WAN egress. Default stays full-deny. Toggle is
    picked up on the next sweep (<=10s), no reboot.

Housekeeping

  • uninstall.sh also clears .debug / .allow-lan flags and the lock
    dir (user .interfaces config intentionally kept).
  • README: documented the above; fixed stale "every 30s" -> 10s + link-event.

Install: Magisk Manager -> Modules -> Install from storage -> pick the zip -> reboot.

v1.1.3 - silent by default

Choose a tag to compare

@enesilhaydin enesilhaydin released this 21 May 12:28

Logging is now opt-in. The module writes nothing to /data/adb/lan-killswitch.log unless the sentinel file /data/adb/lan-killswitch.debug exists.

Enable / disable

# turn on
adb shell su -c 'touch /data/adb/lan-killswitch.debug'

# turn off
adb shell su -c 'rm /data/adb/lan-killswitch.debug'

This stops the log file from growing unbounded on devices that see frequent network cycles.

The optional companion boot-trace diagnostic (described in the README) follows the same pattern with /data/adb/boot-trace.enable.

v1.1.2 - dedupe race fix

Choose a tag to compare

@enesilhaydin enesilhaydin released this 21 May 11:57

Fixes duplicate FORWARD-hook accumulation that built up over reboots and link-UP events in v1.1.1.

Bug

post-fs-data retry, service.sh periodic sweep, and the ip-monitor event sweep all ran concurrently. Each did -C ... || -I without coordination. Racing -C checks could pass on the same missing rule, then both inserts would land. Net effect: duplicate -i <iface> -j lan_killswitch rules accumulated.

Fix

  • mkdir-based atomic mutex at /data/adb/lan-killswitch.lock wrapping every sweep. Stale-lock detection (mtime > 30s) prevents permanent deadlock if a holder dies.
  • ensure_hook() is now strictly idempotent: counts existing matches, deletes any extras, inserts exactly one if absent.
  • Identical logic in post-fs-data so it never races with service.sh.

Hook count converges to exactly 1 per interface per family regardless of how many sweeps fire in parallel.

v1.1.1 - watchdog daemonization fix (critical)

Choose a tag to compare

@enesilhaydin enesilhaydin released this 21 May 11:49

Critical fix

Previous v1.1.0 had two bugs that combined to allow a real-world leak after a reboot:

  1. post-fs-data ran before Android brought tether/AP interfaces up, so the interface scan returned an empty list and nothing got hooked into the FORWARD chain.
  2. service.sh watchdog was not properly daemonized( ... ) & left the background loop tied to Magisk's service-init PID context, where Android reaped it once the parent shell exited.

Combined effect: chain installed, but FORWARD had zero references to it. Hotspot traffic flowed straight through tetherctrl_FORWARD to the cellular interface.

What changed

  • Proper daemonization: setsid sh -c '...' </dev/null >/dev/null 2>&1 & ; disown — both post-fs-data retry loop and service.sh watchdog now run with PPID=1 (orphaned to init), so they survive Magisk session teardown.
  • post-fs-data runs a 90-second retry loop, hooking each interface as soon as it appears.
  • Watchdog interval reduced from 30s to 10s.
  • New: ip monitor link event listener — re-asserts the chain immediately on any link UP event (zero-latency catch-up for late-boot or runtime tether toggles).

Upgrade strongly recommended for v1.1.0 users.

v1.1.0 - Initial release

Choose a tag to compare

@enesilhaydin enesilhaydin released this 21 May 11:30

Tethering-aware VPN kill switch for rooted Android (Magisk module).

What it does

Blocks forwarded traffic from hotspot / USB-tether / Wi-Fi-AP interfaces whenever no tun* interface exists. Closes the gap left by Android's built-in Block connections without VPN setting, which is UID-based and does not cover tethering forward paths.

Features

  • Multi-interface support out of the box: br0, wlan0/1/2, swlan0, ap0, rndis0, usb0, wlan_ap
  • User-overridable interface list via /data/adb/lan-killswitch.interfaces
  • IPv4 + IPv6 symmetric
  • VPN-client agnostic: matches -o tun+ (WireGuard userspace, kernel mode, OpenVPN, etc.)
  • Watchdog re-asserts rules every 30s and picks up new tether interfaces
  • Independent of VPN-routing modules — survives tunnel drops

Installation

  1. Download lan-killswitch-v1.1.0.zip below
  2. Magisk Manager → Modules → Install from storage → pick the zip
  3. Reboot
  4. Verify: adb shell su -c 'iptables -L lan_killswitch -nv'

See README for full details.

Tested on

ZTE F50 (Android 13, Magisk) with vpn-gateway routing hotspot clients into WireGuard tunnels (Mullvad + self-hosted).