Releases: enesilhaydin/lan-killswitch
Release list
v1.2.5
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
- 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
Fixes the red CI (logic tests still referenced the removed ensure_top) and adds a chain-integrity safeguard found in code review.
Code
-
ensure_chainnow 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.shandpost-fs-data.sh.
Tests (CI green)
logic-test.sh: dropped the obsoleteensure_toptests; now asserts the v1.2.0 invariants (ensure_topandip monitorMUST stay absent — regression guard against the churn/sweep-storm) and testsensure_hookinstall / idempotency / de-dup.leak-netns.sh: replaced the old DRIFT test with- [E] order-independence — a qualified
-o tun0ACCEPT placed above our hook still cannot leak while the VPN is down (provesensure_topwas unnecessary), and - [F] chain-integrity recovery — sabotage the
tun+ RETURN, proveensure_chainrebuilds it.
- [E] order-independence — a qualified
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)
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/-Ichurn + 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 linkevent 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-datahook 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
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
ACCEPTthat 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
grepportability.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.shalso clears.debug/.allow-lanflags and the lock
dir (user.interfacesconfig 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
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
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.lockwrapping 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)
Critical fix
Previous v1.1.0 had two bugs that combined to allow a real-world leak after a reboot:
- 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.
- 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 linkevent 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
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
- Download
lan-killswitch-v1.1.0.zipbelow - Magisk Manager → Modules → Install from storage → pick the zip
- Reboot
- 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).