Skip to content

VPN battery optimizations, ePDG routing, and UI fixes#546

Merged
kasnder merged 18 commits intomasterfrom
claude/vpn-routing-battery-research-CtsLW
Apr 6, 2026
Merged

VPN battery optimizations, ePDG routing, and UI fixes#546
kasnder merged 18 commits intomasterfrom
claude/vpn-routing-battery-research-CtsLW

Conversation

@kasnder
Copy link
Copy Markdown
Member

@kasnder kasnder commented Apr 4, 2026

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 correctly assesses VPN connectivity and metering
  • Mirror DuckDuckGo App Tracking Protection approach (same NetGuard native layer, validated in production)

VPN routing improvements

  • Remove subnet, tethering, and LAN toggles from UI (simplification)
  • Revert setMetered to match physical network status
  • Route local DNS through VPN instead of removing it
  • Restore jni_get_mtu() for MTU detection

ePDG / Wi-Fi Calling support

  • Add dynamic ePDG resolution for global Wi-Fi calling support
  • Add 1.5 s timeout to ePDG DNS resolution
  • Fix ePDG comment: resolution uses physical network, not pre-tunnel

Onboarding

  • Warn user when another always-on VPN blocks onboarding

Bug fixes

claude and others added 18 commits April 4, 2026 09:38
- 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
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
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
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
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
Using the actual network MTU avoids unnecessary packet fragmentation
and overhead. NetGuard has used this reliably for years.

https://claude.ai/code/session_01888KqFB93HxMCJGjscYT3X
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
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
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
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>
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>
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>
@kasnder kasnder changed the title Add VPN battery optimizations from DuckDuckGo ATP VPN battery optimizations, ePDG routing, and UI fixes Apr 6, 2026
@kasnder kasnder merged commit 68fcd53 into master Apr 6, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants