Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@
<!-- http://developer.android.com/guide/topics/security/permissions.html#normal-dangerous -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<!--
Android 17 local network protections: reaching a DNS server, Secure DNS
resolver or WireGuard peer on the user's own network needs this runtime
permission once the app targets API 37. Only requested when a setting
actually points TrackerControl at the local network (see
net.kollnig.missioncontrol.LocalNetworkAccess).
-->
<uses-permission android:name="android.permission.ACCESS_LOCAL_NETWORK" />

<!-- https://developer.android.com/preview/privacy/package-visibility -->
<uses-permission
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/eu/faircode/netguard/ActivityLog.java
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ protected void onCreate(Bundle savedInstanceState) {
popup.getMenu().findItem(R.id.menu_protocol).setTitle(Util.getProtocolName(protocol, version, false));

// Whois
final Intent lookupIP = new Intent(Intent.ACTION_VIEW, Uri.parse("https://www.dnslytics.com/whois-lookup/" + ip));
final Intent lookupIP = new Intent(Intent.ACTION_VIEW, Uri.parse("https://search.dnslytics.com/ip/" + ip));
if (pm.resolveActivity(lookupIP, 0) == null)
popup.getMenu().removeItem(R.id.menu_whois);
else
Expand Down
41 changes: 41 additions & 0 deletions app/src/main/java/eu/faircode/netguard/ActivityMain.java
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@

import net.kollnig.missioncontrol.ActivityOnboarding;
import net.kollnig.missioncontrol.Common;
import net.kollnig.missioncontrol.LocalNetworkAccess;
import net.kollnig.missioncontrol.R;
import net.kollnig.missioncontrol.TimelineFragment;
import net.kollnig.missioncontrol.data.Tracker;
Expand Down Expand Up @@ -140,6 +141,7 @@ public class ActivityMain extends AppCompatActivity implements SharedPreferences
public static final int REQUEST_DETAILS_UPDATED = 4;

private static final int REQUEST_NOTIFICATIONS = 5;
private static final int REQUEST_LOCAL_NETWORK = 6;

private static final int REQUEST_EXPORT = 10;

Expand Down Expand Up @@ -453,6 +455,18 @@ public void onClick(View v) {
}
});

// Local network permission (Android 17+), shown when the configuration
// needs it: without it, a LAN DNS server or WireGuard peer is
// unreachable and the user is left without working name resolution.
TextView tvLocalNetwork = findViewById(R.id.tvLocalNetwork);
tvLocalNetwork.setVisibility(View.GONE);
tvLocalNetwork.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
requestPermissions(new String[] { LocalNetworkAccess.PERMISSION }, REQUEST_LOCAL_NETWORK);
}
});

// Application list
RecyclerView rvApplication = findViewById(R.id.rvApplication);
rvApplication.setHasFixedSize(false);
Expand Down Expand Up @@ -548,6 +562,11 @@ protected void onResume() {
if (tvNotifications != null)
tvNotifications.setVisibility(canNotify ? View.GONE : View.VISIBLE);

TextView tvLocalNetwork = findViewById(R.id.tvLocalNetwork);
if (tvLocalNetwork != null)
tvLocalNetwork.setVisibility(
LocalNetworkAccess.isMissing(this) ? View.VISIBLE : View.GONE);

super.onResume();
}

Expand Down Expand Up @@ -774,6 +793,28 @@ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permis
} catch (Throwable ex) {
Log.e(TAG, ex + "\n" + ex.getStackTrace());
}
} else if (requestCode == REQUEST_LOCAL_NETWORK) {
boolean granted = (grantResults.length > 0 &&
grantResults[0] == PackageManager.PERMISSION_GRANTED);
TextView tvLocalNetwork = findViewById(R.id.tvLocalNetwork);
if (tvLocalNetwork != null)
tvLocalNetwork.setVisibility(granted ? View.GONE : View.VISIBLE);
if (granted)
// The tunnel keeps its sockets across a reload, but the native
// engine reopens them, so LAN destinations become reachable.
ServiceSinkhole.reload("permission granted", this, false);
else if (!ActivityCompat.shouldShowRequestPermissionRationale(this,
LocalNetworkAccess.PERMISSION))
// Permanently denied: the prompt no longer appears, so send the
// user to the permission screen instead.
try {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", getPackageName(), null);
intent.setData(uri);
startActivity(intent);
} catch (Throwable ex) {
Log.e(TAG, ex + "\n" + ex.getStackTrace());
}
}
}

Expand Down
17 changes: 17 additions & 0 deletions app/src/main/java/eu/faircode/netguard/ActivitySettings.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
import androidx.work.WorkManager;

import net.kollnig.missioncontrol.BuildConfig;
import net.kollnig.missioncontrol.LocalNetworkAccess;
import net.kollnig.missioncontrol.R;
import net.kollnig.missioncontrol.data.BlockingMode;
import net.kollnig.missioncontrol.data.InternetBlocklist;
Expand Down Expand Up @@ -127,6 +128,9 @@ public void run() {
private static final int REQUEST_EXPORT = 1;
private static final int REQUEST_IMPORT = 2;
private static final int REQUEST_CALL = 5;
private static final int REQUEST_LOCAL_NETWORK = 6;

private boolean requestedLocalNetwork = false;

private static final Intent INTENT_VPN_SETTINGS = new Intent("android.net.vpn.SETTINGS");

Expand Down Expand Up @@ -966,6 +970,19 @@ else if ("domain_based_blocking".equals(name)) {
TrackerList.reloadTrackerData(this);
}

// Android 17 blocks traffic to local network addresses unless the user
// grants ACCESS_LOCAL_NETWORK. Ask as soon as a setting starts pointing
// TrackerControl at the LAN — a custom DNS server, a local Secure DNS
// resolver, a WireGuard peer at home, or the full-tunnel tethering
// mode — rather than letting name resolution fail silently (#701).
// Asked at most once per visit: writing back a trimmed value re-enters
// this listener, and a second request while the dialog is up is dropped
// by the framework.
if (!requestedLocalNetwork && LocalNetworkAccess.isRelevantSetting(name)
&& LocalNetworkAccess.isMissing(this)) {
requestedLocalNetwork = true;
requestPermissions(new String[] { LocalNetworkAccess.PERMISSION }, REQUEST_LOCAL_NETWORK);
}
}

private CharSequence getWireGuardStatusSummary(SharedPreferences prefs) {
Expand Down
7 changes: 7 additions & 0 deletions app/src/main/java/eu/faircode/netguard/ServiceSinkhole.java
Original file line number Diff line number Diff line change
Expand Up @@ -1660,6 +1660,13 @@ private Builder getBuilder(List<Rule> listAllowed, List<Rule> listRule) {
Log.e(TAG, "addRoute DNS " + dns + ": " + ex);
}

// Android 17 refuses local network traffic without ACCESS_LOCAL_NETWORK:
// TCP times out, UDP fails with EPERM. A LAN resolver routed into the tun
// above is re-sent from our own socket, so it goes silent (#701).
if (net.kollnig.missioncontrol.LocalNetworkAccess.isMissing(ServiceSinkhole.this))
Log.w(TAG, "Local network access not granted: configured LAN destinations" +
" (custom DNS, Secure DNS resolver, WireGuard peer) are unreachable");

// Dynamically exclude carrier ePDG IPs so Wi-Fi calling works globally.
// ePDG domains follow 3GPP standard: epdg.epc.mnc{MNC}.mcc{MCC}.pub.3gppnetwork.org
// TC excludes itself from the VPN (addDisallowedApplication), so this DNS resolution
Expand Down
Loading