Support Android 17 local network protections (#701) - #704
Draft
kasnder wants to merge 2 commits into
Draft
Conversation
Android 17 gates traffic to local network addresses behind the ACCESS_LOCAL_NETWORK runtime permission for apps targeting API 37, which TrackerControl does: TCP connections time out and UDP fails with EPERM. Traffic other apps send to the LAN is unaffected — those are their own sockets, and RFC 1918 ranges stay out of the VPN routes, so that traffic never enters the tun. What breaks is traffic TrackerControl itself sends to the local network: a custom VPN DNS server on the LAN gets a host route into the tun and is re-sent from our own socket, so pointing the VPN DNS at a Pi-hole or the router leaves the device with no working name resolution at all. The same applies to Secure DNS aimed at a local resolver, to tethering compatibility mode (whose full-tunnel route puts LAN traffic back inside the tun), and to a WireGuard peer hosted at home. Declare the permission and ask for it where the configuration actually needs it: immediately when such a setting changes, and from a tappable warning on the main screen while it is missing. The system's own resolvers are deliberately left out — Android exempts port 53 traffic to the network's DNS servers, so the common "router is the DNS server" setup keeps working, and users who never point TrackerControl at the LAN are never prompted. Below Android 17 nothing is checked or shown. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MsAkkinA9Jfjt9khTKvBma
Merges NetGuard 552ef140: dnslytics moved its lookup from /whois-lookup/<ip> to search.dnslytics.com/ip/<ip>. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MsAkkinA9Jfjt9khTKvBma
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #701.
The problem
Android 17 makes local network protections mandatory for apps targeting API 37 — which TrackerControl does (
targetSdk 37). Traffic to local network addresses now needs theACCESS_LOCAL_NETWORKruntime permission (NEARBY_DEVICESgroup); without it TCP connections time out and UDP fails withEPERM.Traffic other apps send to the LAN is unaffected by our permission state: those are their own sockets, and
VpnRouteskeeps RFC 1918 ranges out of the tun, so that traffic never reaches us. What breaks is traffic TrackerControl itself sends to the local network:getConfig()deliberately adds a/32host route for site-local resolvers so they stay inside the tunnel and pass through the filtering path — which means the query is re-sent from TrackerControl's own socket. With the permission missing that socket is blocked, and the device ends up with no working name resolution at all.0.0.0.0/0route puts LAN traffic back inside the tun.The change
android.permission.ACCESS_LOCAL_NETWORKin the manifest.net.kollnig.missioncontrol.LocalNetworkAccess: is enforcement active (API ≥ 37), is the permission granted, and does the current configuration actually point TrackerControl at the LAN (the four cases above, address literals only — resolving a hostname here would mean a lookup on the main thread).dns,dns2,doh_enabled,doh_endpoint,tcp_mss_clamp,wg_enabled,wg_config), so the user grants it at the moment they configure the thing that needs it. Asked at most once per visit — writing back a trimmed value re-enters the listener, and a second request while the dialog is up is dropped by the framework.ServiceSinkholelogs a warning when the tunnel comes up in that state, so the failure is visible in a bug report.Deliberately not treated as needing the permission: the system's own resolvers. Android exempts port 53 traffic to the network's DNS servers, so the very common "router is the DNS server" setup keeps working, and users who never point TrackerControl at the LAN are never prompted. Below Android 17,
isEnforced()short-circuits — no checks, no banner, no config parsing.This is a breakage-recovery fix rather than a new knob: no preference is added, and the permission is only ever requested for a configuration the user already chose.
NetGuard upstream
Checked NetGuard (M66B/NetGuard) for the same fix and for anything else worth merging since our last sync:
ACCESS_LOCAL_NETWORKand it still targets SDK 36 (Updated to SDK 36, 31 Jul 2026), so it gets the implicit legacy grant and the enforcement never bites. Nothing to merge — the work here is ours.Updated whois link(552ef140) — dnslytics moved its lookup tosearch.dnslytics.com/ip/<ip>; ourActivityLogstill used the old/whois-lookup/URL (second commit).Added AP state receiver(761450e1),Buffered settings output(8a60a7b3).Allow dynamic tethering network interface(b1d598ce) adds dynamicap_br_wlan*exclusions to NetGuard's route list; ourVpnRoutesalready excludes all of RFC 1918 by default, and tethering downstreams are handled by the tethering compatibility mode added in Route the full tunnel in tethering compatibility mode (#699) #700. The SDK 36 / insets / action-bar-height work (0a95c6ea, f2ccd1df, 7b152b49, ce67398e and the accompanying refactors) is NetGuard's route to edge-to-edge on API 36; we already target 37 with our own per-activity insets handling, so adopting it would be churn against a codebase that has moved past it.Testing
LocalNetworkAccessTest(Robolectric) covers the configuration detection: public vs. private custom DNS, IPv6 ULA and loopback, DoH endpoint gated ondoh_enabledincluding a bracketed IPv6 literal, tethering compatibility mode, local vs. remote vs. hostname WireGuard endpoints, and malformed input. The SDK gate itself is not covered — Robolectric runs on API 36, below the enforcement level.Not verified on a device: this environment has no Android SDK, so the build and the Robolectric run are left to CI. The permission prompt and banner flow on an Android 17 device have not been exercised, and I would like the reporter to confirm that granting the permission restores their LAN DNS setup before this leaves draft.
Generated by Claude Code