Skip to content

Commit 68fcd53

Browse files
kasnderclaude
andauthored
VPN battery optimizations, ePDG routing, and UI fixes (#546)
* Add VPN battery optimizations from DuckDuckGo ATP - Enable setBlocking(true) on VPN builder for CPU-efficient blocking I/O instead of polling the TUN file descriptor - Re-enable setUnderlyingNetworks() after establish() so Android can correctly assess VPN connectivity and metering (was disabled with && false since NetGuard v2.330, Sep 2024) - Acknowledge DuckDuckGo ATP (Apache 2.0) in README credits Both optimizations mirror DuckDuckGo's App Tracking Protection, which uses the same NetGuard native layer (libnetguard) and has validated these work correctly with epoll-based I/O. https://claude.ai/code/session_01888KqFB93HxMCJGjscYT3X * Mirror DuckDuckGo ATP VPN routing approach Replace dynamic subnet routing and 0.0.0.0/0 catch-all with static pre-computed routes covering all public IPv4 space. This eliminates the need for subnet/tethering/LAN user toggles — LAN access, tethering, and carrier Wi-Fi calling all work out of the box. Changes mirroring DuckDuckGo App Tracking Protection (Apache 2.0): - Add VpnRoutes.java with static route list excluding RFC1918, CGNAT, link-local, multicast, reserved, and carrier Wi-Fi calling ranges - Always use static routes (no dynamic CIDR complement computation) - setMetered(false) to prevent background sync restrictions - MTU 1280 (conservative, reduces fragmentation overhead) - Wrap each addRoute() in try/catch for OEM-specific failures - Always remove local DNS servers (LAN always excluded) - Remove unused imports (Configuration, NetworkInterface, etc.) The subnet, tethering, and lan preferences are now effectively no-ops. https://claude.ai/code/session_01888KqFB93HxMCJGjscYT3X * Remove subnet, tethering, and LAN toggles from UI These settings are no longer needed — static VPN routes now always exclude local networks and carrier Wi-Fi calling ranges, making LAN access, tethering, and Wi-Fi calling work out of the box. https://claude.ai/code/session_01888KqFB93HxMCJGjscYT3X * Revert setMetered to match physical network status setMetered(false) (DDG's approach) causes apps to think they're on an unmetered connection when on cellular, potentially increasing data usage. Matching the actual network status makes the VPN transparent with respect to metering — apps behave identically to without the VPN. https://claude.ai/code/session_01888KqFB93HxMCJGjscYT3X * Route local DNS through VPN instead of removing it Instead of stripping local DNS servers (which breaks Pi-hole and similar setups), add /32 host routes for them. A /32 beats the excluded /16, so DNS traffic to e.g. 192.168.1.1 enters the tunnel where TC can filter it, while all other LAN traffic still bypasses. https://claude.ai/code/session_01888KqFB93HxMCJGjscYT3X * Restore jni_get_mtu() for MTU detection Using the actual network MTU avoids unnecessary packet fragmentation and overhead. NetGuard has used this reliably for years. https://claude.ai/code/session_01888KqFB93HxMCJGjscYT3X * Add dynamic ePDG resolution for global Wi-Fi calling support Resolve the carrier's ePDG domain at VPN setup using the 3GPP standard pattern (epdg.epc.mnc{MNC}.mcc{MCC}.pub.3gppnetwork.org) and exclude those IPs via excludeRoute(). This uses the carrier's own DNS (before the tunnel is established), avoiding geo-fencing issues. Works for any carrier worldwide without hardcoding IP ranges. Requires Android 13+ (API 33) for excludeRoute(); older versions fall back to the static T-Mobile/Verizon exclusions in VpnRoutes. https://claude.ai/code/session_01888KqFB93HxMCJGjscYT3X * Fix ePDG comment: resolution uses physical network, not pre-tunnel TC excludes itself from VPN routing (addDisallowedApplication), so ePDG DNS resolution always goes through the physical network. This re-runs on each VPN rebuild (network switch), picking up the correct carrier DNS when switching from WiFi to cellular. https://claude.ai/code/session_01888KqFB93HxMCJGjscYT3X * Add 1.5s timeout to ePDG DNS resolution DNS resolution for non-existent ePDG domains (no SIM, non-standard carrier) could block VPN startup for 5-30 seconds. Wrap in a Future with 1.5s timeout — successful lookups typically complete in <500ms, so this catches most carriers while failing fast otherwise. https://claude.ai/code/session_01888KqFB93HxMCJGjscYT3X * Warn user when another always-on VPN blocks onboarding If another app is set as Always-on VPN, Android silently refuses to show TrackerControl's VPN consent dialog, so tapping "Enable On-Device VPN" in onboarding appears to do nothing. Detect this both proactively (pre-Android S, where the setting is readable) and reactively (via onActivityResult) and show a dialog pointing the user to system VPN settings so they can disable the other always-on VPN. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Document LAN/tethering bypass and reframe system-apps VPN note LAN and tethering already bypass TC's VPN via OS-level routing (kernel connected-route preference for LAN, separate netd forwarding context for tethering), so the excludes in VpnRoutes are defense-in-depth, not load-bearing. Capture this so it doesn't get re-litigated. Also reframe the include_system_vpn note: the real cost is wakeup frequency from background system-app traffic, not tun throughput — the earlier "download slowdown" framing misread the battery symptom. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix doubled top inset gap on Insights toolbar The navigation-bar overlap fix in #558 added fitsSystemWindows="true" to the Insights CoordinatorLayout. Because Insights uses the theme's window-decor action bar (not an in-layout AppBarLayout), the status bar inset was effectively counted twice and a large empty gap appeared between the toolbar and the hero card. Mirror the approach already used for Settings/Timeline in c50bea9: drop fitsSystemWindows from the layout root and instead apply only the bottom system-bar inset to the ScrollView so the original nav-bar overlap fix still holds. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent f2766d0 commit 68fcd53

8 files changed

Lines changed: 244 additions & 218 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,8 @@ that is distributed with the Firefox browser.
261261

262262
*DuckDuckGo App Exclusions*: TrackerControl's Minimal blocking mode uses a list of excluded apps (browsers, system services, known incompatible apps) derived from DuckDuckGo's [privacy-configuration](https://github.com/duckduckgo/privacy-configuration) (Apache 2.0).
263263

264+
*DuckDuckGo App Tracking Protection*: TrackerControl's VPN battery optimizations (`setBlocking`, `setUnderlyingNetworks`) are informed by DuckDuckGo's [App Tracking Protection](https://github.com/duckduckgo/Android) implementation (Apache 2.0).
265+
264266
## License
265267
Except where indicated otherwise, this project is licensed under [GPLv3](https://www.gnu.org/licenses/gpl-3.0.html).
266268

TODO.md

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,26 @@ The VPN file descriptor can be closed by `stopVPN()` while native code in `jni_r
1010

1111
**If revisited:** The proposed fix would move the FD close into `stopNative()`, after `jni_clear()`, so the sequence becomes: `jni_stop()` -> `join thread` -> `jni_clear()` -> `close FD`. Risk is high — touches the critical VPN path, and the likely failure modes (double-close, FD leak) are harder to detect than the original race. Only worth pursuing if user reports indicate the race is actually happening.
1212

13+
## LAN and tethering VPN routing (resolved knowledge)
14+
15+
The `VpnRoutes` exclusions for RFC1918 / CGNAT / link-local are **defense-in-depth, not load-bearing**. On modern Android, LAN access and tethering already bypass the app's VPN tun for OS-level reasons:
16+
17+
- **LAN access**: When Wi-Fi is on `192.168.x.0/24`, the kernel routing table has a more-specific connected route for that subnet via `wlan0`. It outranks the VPN's `0.0.0.0/0`, so local-subnet packets never hit the tun interface regardless of what the VPN advertises.
18+
- **Tethering (USB/hotspot)**: Tethered traffic is forwarded by `netd`/`iptables` in a separate routing context and does not traverse the owning app's `VpnService` tun at all.
19+
20+
The old `lan`/`tethering`/`subnet` toggles in NetGuard were effectively no-ops on modern Android and were removed in PR #546. The new static excludes still matter for the edge case of reaching an RFC1918 destination that is *not* on the currently connected subnet (e.g. talking to `10.x` from a `192.168.x` Wi-Fi), where the kernel would otherwise prefer the VPN's default route.
21+
22+
Don't re-litigate this when someone asks why LAN/tethering work without any toggle — it's intentional.
23+
1324
## System apps VPN routing
1425

15-
Including system apps in the VPN (`include_system_vpn`) causes noticeable download speed slowdowns (e.g. Play Store). Unclear if this is inherent tun overhead or a fixable implementation issue.
26+
Routing system apps through the VPN (`include_system_vpn`) is a noticeable battery drain. The working hypothesis is **wakeup frequency**, not tun throughput: while TC runs permanently as a VPN, any system-app background activity (Play Store updates, Play Services sync, carrier services, etc.) has to traverse the tun and wakes the packet-processing threads. Excluding system apps lets those flows bypass TC entirely so the CPU can stay idle.
27+
28+
Earlier framing as a "download speed slowdown" was likely a misread of the battery symptom — raw tun throughput is probably fine.
1629

17-
- Investigate tun performance: profile packet processing path, test buffer size tuning
30+
- Investigate wakeup behaviour rather than throughput: count wakeups / time-in-packet-loop with system apps included vs excluded, not MB/s.
1831
- Consider simplifying UX: current flow requires three toggles (`include_system_vpn` -> `manage_system` -> `show_system`). Could consolidate to one toggle that drives both VPN routing and UI visibility.
19-
- Note: always routing system apps through VPN was rejected due to the performance impact, but excluding them breaks Android's "Block connections without VPN" setting.
32+
- Note: always routing system apps through VPN was rejected due to the battery impact, but excluding them breaks Android's "Block connections without VPN" setting.
2033

2134
## SNI inspection for tracker detection
2235

app/src/main/java/eu/faircode/netguard/ActivitySettings.java

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -550,15 +550,6 @@ else if ("pause".equals(name))
550550
getPreferenceScreen().findPreference(name)
551551
.setTitle(getString(R.string.setting_pause, prefs.getString(name, "10")));
552552

553-
else if ("subnet".equals(name))
554-
ServiceSinkhole.reload("changed " + name, this, false);
555-
556-
else if ("tethering".equals(name))
557-
ServiceSinkhole.reload("changed " + name, this, false);
558-
559-
else if ("lan".equals(name))
560-
ServiceSinkhole.reload("changed " + name, this, false);
561-
562553
else if ("ip6".equals(name))
563554
ServiceSinkhole.reload("changed " + name, this, false);
564555

0 commit comments

Comments
 (0)