Merge "Add basic resolution of Private DNS hostname" am: a3bf36f050 am: 4218c522a7
am: 0b250b6132
Change-Id: Iacdad88b49aff3e34afc93acfb8d3eeca9745c43
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 77521df..9574545 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -131,6 +131,7 @@
import com.android.server.am.BatteryStatsService;
import com.android.server.connectivity.DataConnectionStats;
import com.android.server.connectivity.DnsManager;
+import com.android.server.connectivity.DnsManager.PrivateDnsConfig;
import com.android.server.connectivity.IpConnectivityMetrics;
import com.android.server.connectivity.KeepaliveTracker;
import com.android.server.connectivity.LingerMonitor;
@@ -400,6 +401,9 @@
*/
private static final int EVENT_REVALIDATE_NETWORK = 36;
+ // Handle changes in Private DNS settings.
+ private static final int EVENT_PRIVATE_DNS_SETTINGS_CHANGED = 37;
+
private static String eventName(int what) {
return sMagicDecoderRing.get(what, Integer.toString(what));
}
@@ -886,6 +890,7 @@
mMultinetworkPolicyTracker.start();
mDnsManager = new DnsManager(mContext, mNetd, mSystemProperties);
+ registerPrivateDnsSettingsCallbacks();
}
private Tethering makeTethering() {
@@ -946,6 +951,12 @@
EVENT_CONFIGURE_MOBILE_DATA_ALWAYS_ON);
}
+ private void registerPrivateDnsSettingsCallbacks() {
+ for (Uri u : DnsManager.getPrivateDnsSettingsUris()) {
+ mSettingsObserver.observe(u, EVENT_PRIVATE_DNS_SETTINGS_CHANGED);
+ }
+ }
+
private synchronized int nextNetworkRequestId() {
return mNextNetworkRequestId++;
}
@@ -2114,36 +2125,59 @@
synchronized (mNetworkForNetId) {
nai = mNetworkForNetId.get(msg.arg2);
}
- if (nai != null) {
- final boolean valid =
- (msg.arg1 == NetworkMonitor.NETWORK_TEST_RESULT_VALID);
- final boolean wasValidated = nai.lastValidated;
- final boolean wasDefault = isDefaultNetwork(nai);
- if (DBG) log(nai.name() + " validation " + (valid ? "passed" : "failed") +
- (msg.obj == null ? "" : " with redirect to " + (String)msg.obj));
- if (valid != nai.lastValidated) {
- if (wasDefault) {
- metricsLogger().defaultNetworkMetrics().logDefaultNetworkValidity(
- SystemClock.elapsedRealtime(), valid);
- }
- final int oldScore = nai.getCurrentScore();
- nai.lastValidated = valid;
- nai.everValidated |= valid;
- updateCapabilities(oldScore, nai, nai.networkCapabilities);
- // If score has changed, rebroadcast to NetworkFactories. b/17726566
- if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai);
+ if (nai == null) break;
+
+ final boolean valid = (msg.arg1 == NetworkMonitor.NETWORK_TEST_RESULT_VALID);
+ final boolean wasValidated = nai.lastValidated;
+ final boolean wasDefault = isDefaultNetwork(nai);
+
+ final PrivateDnsConfig privateDnsCfg = (msg.obj instanceof PrivateDnsConfig)
+ ? (PrivateDnsConfig) msg.obj : null;
+ final String redirectUrl = (msg.obj instanceof String) ? (String) msg.obj : "";
+
+ final boolean reevaluationRequired;
+ final String logMsg;
+ if (valid) {
+ reevaluationRequired = updatePrivateDns(nai, privateDnsCfg);
+ logMsg = (DBG && (privateDnsCfg != null))
+ ? " with " + privateDnsCfg.toString() : "";
+ } else {
+ reevaluationRequired = false;
+ logMsg = (DBG && !TextUtils.isEmpty(redirectUrl))
+ ? " with redirect to " + redirectUrl : "";
+ }
+ if (DBG) {
+ log(nai.name() + " validation " + (valid ? "passed" : "failed") + logMsg);
+ }
+ // If there is a change in Private DNS configuration,
+ // trigger reevaluation of the network to test it.
+ if (reevaluationRequired) {
+ nai.networkMonitor.sendMessage(
+ NetworkMonitor.CMD_FORCE_REEVALUATION, Process.SYSTEM_UID);
+ break;
+ }
+ if (valid != nai.lastValidated) {
+ if (wasDefault) {
+ metricsLogger().defaultNetworkMetrics().logDefaultNetworkValidity(
+ SystemClock.elapsedRealtime(), valid);
}
- updateInetCondition(nai);
- // Let the NetworkAgent know the state of its network
- Bundle redirectUrlBundle = new Bundle();
- redirectUrlBundle.putString(NetworkAgent.REDIRECT_URL_KEY, (String)msg.obj);
- nai.asyncChannel.sendMessage(
- NetworkAgent.CMD_REPORT_NETWORK_STATUS,
- (valid ? NetworkAgent.VALID_NETWORK : NetworkAgent.INVALID_NETWORK),
- 0, redirectUrlBundle);
- if (wasValidated && !nai.lastValidated) {
- handleNetworkUnvalidated(nai);
- }
+ final int oldScore = nai.getCurrentScore();
+ nai.lastValidated = valid;
+ nai.everValidated |= valid;
+ updateCapabilities(oldScore, nai, nai.networkCapabilities);
+ // If score has changed, rebroadcast to NetworkFactories. b/17726566
+ if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai);
+ }
+ updateInetCondition(nai);
+ // Let the NetworkAgent know the state of its network
+ Bundle redirectUrlBundle = new Bundle();
+ redirectUrlBundle.putString(NetworkAgent.REDIRECT_URL_KEY, redirectUrl);
+ nai.asyncChannel.sendMessage(
+ NetworkAgent.CMD_REPORT_NETWORK_STATUS,
+ (valid ? NetworkAgent.VALID_NETWORK : NetworkAgent.INVALID_NETWORK),
+ 0, redirectUrlBundle);
+ if (wasValidated && !nai.lastValidated) {
+ handleNetworkUnvalidated(nai);
}
break;
}
@@ -2183,6 +2217,21 @@
}
break;
}
+ case NetworkMonitor.EVENT_PRIVATE_DNS_CONFIG_RESOLVED: {
+ final NetworkAgentInfo nai;
+ synchronized (mNetworkForNetId) {
+ nai = mNetworkForNetId.get(msg.arg2);
+ }
+ if (nai == null) break;
+
+ final PrivateDnsConfig cfg = (PrivateDnsConfig) msg.obj;
+ final boolean reevaluationRequired = updatePrivateDns(nai, cfg);
+ if (nai.lastValidated && reevaluationRequired) {
+ nai.networkMonitor.sendMessage(
+ NetworkMonitor.CMD_FORCE_REEVALUATION, Process.SYSTEM_UID);
+ }
+ break;
+ }
}
return true;
}
@@ -2218,6 +2267,63 @@
}
}
+ private void handlePrivateDnsSettingsChanged() {
+ final PrivateDnsConfig cfg = mDnsManager.getPrivateDnsConfig();
+
+ for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
+ // Private DNS only ever applies to networks that might provide
+ // Internet access and therefore also require validation.
+ if (!NetworkMonitor.isValidationRequired(
+ mDefaultRequest.networkCapabilities, nai.networkCapabilities)) {
+ continue;
+ }
+
+ // Notify the NetworkMonitor thread in case it needs to cancel or
+ // schedule DNS resolutions. If a DNS resolution is required the
+ // result will be sent back to us.
+ nai.networkMonitor.notifyPrivateDnsSettingsChanged(cfg);
+
+ if (!cfg.inStrictMode()) {
+ // No strict mode hostname DNS resolution needed, so just update
+ // DNS settings directly. In opportunistic and "off" modes this
+ // just reprograms netd with the network-supplied DNS servers
+ // (and of course the boolean of whether or not to attempt TLS).
+ //
+ // TODO: Consider code flow parity with strict mode, i.e. having
+ // NetworkMonitor relay the PrivateDnsConfig back to us and then
+ // performing this call at that time.
+ updatePrivateDns(nai, cfg);
+ }
+ }
+ }
+
+ private boolean updatePrivateDns(NetworkAgentInfo nai, PrivateDnsConfig newCfg) {
+ final boolean reevaluationRequired = true;
+ final boolean dontReevaluate = false;
+
+ final PrivateDnsConfig oldCfg = mDnsManager.updatePrivateDns(nai.network, newCfg);
+ updateDnses(nai.linkProperties, null, nai.network.netId);
+
+ if (newCfg == null) {
+ if (oldCfg == null) return dontReevaluate;
+ return oldCfg.useTls ? reevaluationRequired : dontReevaluate;
+ }
+
+ if (oldCfg == null) {
+ return newCfg.useTls ? reevaluationRequired : dontReevaluate;
+ }
+
+ if (oldCfg.useTls != newCfg.useTls) {
+ return reevaluationRequired;
+ }
+
+ if (newCfg.inStrictMode() && !Objects.equals(oldCfg.hostname, newCfg.hostname)) {
+ return reevaluationRequired;
+ }
+
+ return dontReevaluate;
+ }
+
private void updateLingerState(NetworkAgentInfo nai, long now) {
// 1. Update the linger timer. If it's changed, reschedule or cancel the alarm.
// 2. If the network was lingering and there are now requests, unlinger it.
@@ -2352,6 +2458,7 @@
} catch (Exception e) {
loge("Exception removing network: " + e);
}
+ mDnsManager.removeNetwork(nai.network);
}
synchronized (mNetworkForNetId) {
mNetIdInUse.delete(nai.network.netId);
@@ -2898,6 +3005,9 @@
handleReportNetworkConnectivity((Network) msg.obj, msg.arg1, toBool(msg.arg2));
break;
}
+ case EVENT_PRIVATE_DNS_SETTINGS_CHANGED:
+ handlePrivateDnsSettingsChanged();
+ break;
}
}
}
@@ -4587,11 +4697,12 @@
final NetworkAgentInfo defaultNai = getDefaultNetwork();
final boolean isDefaultNetwork = (defaultNai != null && defaultNai.network.netId == netId);
- Collection<InetAddress> dnses = newLp.getDnsServers();
- if (DBG) log("Setting DNS servers for network " + netId + " to " + dnses);
+ if (DBG) {
+ final Collection<InetAddress> dnses = newLp.getDnsServers();
+ log("Setting DNS servers for network " + netId + " to " + dnses);
+ }
try {
- mDnsManager.setDnsConfigurationForNetwork(
- netId, dnses, newLp.getDomains(), isDefaultNetwork);
+ mDnsManager.setDnsConfigurationForNetwork(netId, newLp, isDefaultNetwork);
} catch (Exception e) {
loge("Exception in setDnsConfigurationForNetwork: " + e);
}
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 8f646e7..88ae224 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -21,9 +21,6 @@
import static android.Manifest.permission.NETWORK_SETTINGS;
import static android.Manifest.permission.NETWORK_STACK;
import static android.Manifest.permission.SHUTDOWN;
-import static android.net.ConnectivityManager.PRIVATE_DNS_DEFAULT_MODE;
-import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
-import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_DOZABLE;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_NONE;
@@ -1957,15 +1954,6 @@
}
}
- private static boolean shouldUseTls(ContentResolver cr) {
- String privateDns = Settings.Global.getString(cr, Settings.Global.PRIVATE_DNS_MODE);
- if (TextUtils.isEmpty(privateDns)) {
- privateDns = PRIVATE_DNS_DEFAULT_MODE;
- }
- return privateDns.equals(PRIVATE_DNS_MODE_OPPORTUNISTIC) ||
- privateDns.startsWith(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME);
- }
-
@Override
public void addVpnUidRanges(int netId, UidRange[] ranges) {
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
diff --git a/services/core/java/com/android/server/connectivity/DnsManager.java b/services/core/java/com/android/server/connectivity/DnsManager.java
index a4170ce..a1c54bd 100644
--- a/services/core/java/com/android/server/connectivity/DnsManager.java
+++ b/services/core/java/com/android/server/connectivity/DnsManager.java
@@ -17,6 +17,7 @@
package com.android.server.connectivity;
import static android.net.ConnectivityManager.PRIVATE_DNS_DEFAULT_MODE;
+import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
import static android.provider.Settings.Global.DNS_RESOLVER_MIN_SAMPLES;
@@ -29,19 +30,32 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.net.LinkProperties;
+import android.net.Network;
import android.net.NetworkUtils;
+import android.net.Uri;
import android.os.Binder;
import android.os.INetworkManagementService;
import android.os.Handler;
import android.os.UserHandle;
import android.provider.Settings;
+import android.system.GaiException;
+import android.system.OsConstants;
+import android.system.StructAddrinfo;
import android.text.TextUtils;
import android.util.Slog;
import com.android.server.connectivity.MockableSystemProperties;
+import libcore.io.Libcore;
+
import java.net.InetAddress;
+import java.util.Arrays;
import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.StringJoiner;
/**
@@ -61,10 +75,86 @@
private static final int DNS_RESOLVER_DEFAULT_MIN_SAMPLES = 8;
private static final int DNS_RESOLVER_DEFAULT_MAX_SAMPLES = 64;
+ public static class PrivateDnsConfig {
+ public final boolean useTls;
+ public final String hostname;
+ public final InetAddress[] ips;
+
+ public PrivateDnsConfig() {
+ this(false);
+ }
+
+ public PrivateDnsConfig(boolean useTls) {
+ this.useTls = useTls;
+ this.hostname = "";
+ this.ips = new InetAddress[0];
+ }
+
+ public PrivateDnsConfig(String hostname, InetAddress[] ips) {
+ this.useTls = !TextUtils.isEmpty(hostname);
+ this.hostname = useTls ? hostname : "";
+ this.ips = (ips != null) ? ips : new InetAddress[0];
+ }
+
+ public PrivateDnsConfig(PrivateDnsConfig cfg) {
+ useTls = cfg.useTls;
+ hostname = cfg.hostname;
+ ips = cfg.ips;
+ }
+
+ public boolean inStrictMode() {
+ return useTls && !TextUtils.isEmpty(hostname);
+ }
+
+ public String toString() {
+ return PrivateDnsConfig.class.getSimpleName() +
+ "{" + useTls + ":" + hostname + "/" + Arrays.toString(ips) + "}";
+ }
+ }
+
+ public static PrivateDnsConfig getPrivateDnsConfig(ContentResolver cr) {
+ final String mode = getPrivateDnsMode(cr);
+
+ final boolean useTls = !TextUtils.isEmpty(mode) && !PRIVATE_DNS_MODE_OFF.equals(mode);
+
+ if (PRIVATE_DNS_MODE_PROVIDER_HOSTNAME.equals(mode)) {
+ final String specifier = getStringSetting(cr, PRIVATE_DNS_SPECIFIER);
+ return new PrivateDnsConfig(specifier, null);
+ }
+
+ return new PrivateDnsConfig(useTls);
+ }
+
+ public static PrivateDnsConfig tryBlockingResolveOf(Network network, String name) {
+ final StructAddrinfo hints = new StructAddrinfo();
+ // Unnecessary, but expressly no AI_ADDRCONFIG.
+ hints.ai_flags = 0;
+ // Fetch all IP addresses at once to minimize re-resolution.
+ hints.ai_family = OsConstants.AF_UNSPEC;
+ hints.ai_socktype = OsConstants.SOCK_DGRAM;
+
+ try {
+ final InetAddress[] ips = Libcore.os.android_getaddrinfo(name, hints, network.netId);
+ if (ips != null && ips.length > 0) {
+ return new PrivateDnsConfig(name, ips);
+ }
+ } catch (GaiException ignored) {}
+
+ return null;
+ }
+
+ public static Uri[] getPrivateDnsSettingsUris() {
+ final Uri[] uris = new Uri[2];
+ uris[0] = Settings.Global.getUriFor(PRIVATE_DNS_MODE);
+ uris[1] = Settings.Global.getUriFor(PRIVATE_DNS_SPECIFIER);
+ return uris;
+ }
+
private final Context mContext;
private final ContentResolver mContentResolver;
private final INetworkManagementService mNMS;
private final MockableSystemProperties mSystemProperties;
+ private final Map<Integer, PrivateDnsConfig> mPrivateDnsMap;
private int mNumDnsEntries;
private int mSampleValidity;
@@ -79,44 +169,55 @@
mContentResolver = mContext.getContentResolver();
mNMS = nms;
mSystemProperties = sp;
+ mPrivateDnsMap = new HashMap<>();
// TODO: Create and register ContentObservers to track every setting
// used herein, posting messages to respond to changes.
}
- public boolean isPrivateDnsInStrictMode() {
- return !TextUtils.isEmpty(mPrivateDnsMode) &&
- mPrivateDnsMode.startsWith(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME) &&
- !TextUtils.isEmpty(mPrivateDnsSpecifier);
+ public PrivateDnsConfig getPrivateDnsConfig() {
+ return getPrivateDnsConfig(mContentResolver);
+ }
+
+ public void removeNetwork(Network network) {
+ mPrivateDnsMap.remove(network.netId);
+ }
+
+ public PrivateDnsConfig updatePrivateDns(Network network, PrivateDnsConfig cfg) {
+ Slog.w(TAG, "updatePrivateDns(" + network + ", " + cfg + ")");
+ return (cfg != null)
+ ? mPrivateDnsMap.put(network.netId, cfg)
+ : mPrivateDnsMap.remove(network);
}
public void setDnsConfigurationForNetwork(
- int netId, Collection<InetAddress> servers, String domains, boolean isDefaultNetwork) {
- updateParametersSettings();
- updatePrivateDnsSettings();
+ int netId, LinkProperties lp, boolean isDefaultNetwork) {
+ // We only use the PrivateDnsConfig data pushed to this class instance
+ // from ConnectivityService because it works in coordination with
+ // NetworkMonitor to decide which networks need validation and runs the
+ // blocking calls to resolve Private DNS strict mode hostnames.
+ //
+ // At this time we do attempt to enable Private DNS on non-Internet
+ // networks like IMS.
+ final PrivateDnsConfig privateDnsCfg = mPrivateDnsMap.get(netId);
- final String[] serverStrs = NetworkUtils.makeStrings(servers);
- final String[] domainStrs = (domains == null) ? new String[0] : domains.split(" ");
+ final boolean useTls = (privateDnsCfg != null) && privateDnsCfg.useTls;
+ final boolean strictMode = (privateDnsCfg != null) && privateDnsCfg.inStrictMode();
+ final String tlsHostname = strictMode ? privateDnsCfg.hostname : "";
+
+ final String[] serverStrs = NetworkUtils.makeStrings(
+ strictMode ? Arrays.stream(privateDnsCfg.ips)
+ .filter((ip) -> lp.isReachable(ip))
+ .collect(Collectors.toList())
+ : lp.getDnsServers());
+ final String[] domainStrs = getDomainStrings(lp.getDomains());
+
+ updateParametersSettings();
final int[] params = { mSampleValidity, mSuccessThreshold, mMinSamples, mMaxSamples };
- final boolean useTls = shouldUseTls(mPrivateDnsMode);
- // TODO: Populate tlsHostname once it's decided how the hostname's IP
- // addresses will be resolved:
- //
- // [1] network-provided DNS servers are included here with the
- // hostname and netd will use the network-provided servers to
- // resolve the hostname and fix up its internal structures, or
- //
- // [2] network-provided DNS servers are included here without the
- // hostname, the ConnectivityService layer resolves the given
- // hostname, and then reconfigures netd with this information.
- //
- // In practice, there will always be a need for ConnectivityService or
- // the captive portal app to use the network-provided services to make
- // some queries. This argues in favor of [1], in concert with another
- // mechanism, perhaps setting a high bit in the netid, to indicate
- // via existing DNS APIs which set of servers (network-provided or
- // non-network-provided private DNS) should be queried.
- final String tlsHostname = "";
+
+ Slog.d(TAG, String.format("setDnsConfigurationForNetwork(%d, %s, %s, %s, %s, %s)",
+ netId, Arrays.toString(serverStrs), Arrays.toString(domainStrs),
+ Arrays.toString(params), useTls, tlsHostname));
try {
mNMS.setDnsConfigurationForNetwork(
netId, serverStrs, domainStrs, params, useTls, tlsHostname);
@@ -129,7 +230,7 @@
// default network, and we should just set net.dns1 to ::1, not least
// because applications attempting to use net.dns resolvers will bypass
// the privacy protections of things like DNS-over-TLS.
- if (isDefaultNetwork) setDefaultDnsSystemProperties(servers);
+ if (isDefaultNetwork) setDefaultDnsSystemProperties(lp.getDnsServers());
flushVmDnsCache();
}
@@ -163,11 +264,6 @@
}
}
- private void updatePrivateDnsSettings() {
- mPrivateDnsMode = getStringSetting(PRIVATE_DNS_MODE);
- mPrivateDnsSpecifier = getStringSetting(PRIVATE_DNS_SPECIFIER);
- }
-
private void updateParametersSettings() {
mSampleValidity = getIntSetting(
DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS,
@@ -198,10 +294,6 @@
}
}
- private String getStringSetting(String which) {
- return Settings.Global.getString(mContentResolver, which);
- }
-
private int getIntSetting(String which, int dflt) {
return Settings.Global.getInt(mContentResolver, which, dflt);
}
@@ -216,11 +308,16 @@
}
}
- private static boolean shouldUseTls(String mode) {
- if (TextUtils.isEmpty(mode)) {
- mode = PRIVATE_DNS_DEFAULT_MODE;
- }
- return mode.equals(PRIVATE_DNS_MODE_OPPORTUNISTIC) ||
- mode.startsWith(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME);
+ private static String getPrivateDnsMode(ContentResolver cr) {
+ final String mode = getStringSetting(cr, PRIVATE_DNS_MODE);
+ return !TextUtils.isEmpty(mode) ? mode : PRIVATE_DNS_DEFAULT_MODE;
+ }
+
+ private static String getStringSetting(ContentResolver cr, String which) {
+ return Settings.Global.getString(cr, which);
+ }
+
+ private static String[] getDomainStrings(String domains) {
+ return (TextUtils.isEmpty(domains)) ? new String[0] : domains.split(" ");
}
}
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
index 7684030..ed26858 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
@@ -29,6 +29,7 @@
import android.net.ConnectivityManager;
import android.net.ICaptivePortal;
import android.net.Network;
+import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.ProxyInfo;
import android.net.TrafficStats;
@@ -215,6 +216,15 @@
*/
private static final int CMD_CAPTIVE_PORTAL_RECHECK = BASE + 12;
+ /**
+ * ConnectivityService notifies NetworkMonitor of settings changes to
+ * Private DNS. If a DNS resolution is required, e.g. for DNS-over-TLS in
+ * strict mode, then an event is sent back to ConnectivityService with the
+ * result of the resolution attempt.
+ */
+ private static final int CMD_PRIVATE_DNS_SETTINGS_CHANGED = BASE + 13;
+ public static final int EVENT_PRIVATE_DNS_CONFIG_RESOLVED = BASE + 14;
+
// Start mReevaluateDelayMs at this value and double.
private static final int INITIAL_REEVALUATE_DELAY_MS = 1000;
private static final int MAX_REEVALUATE_DELAY_MS = 10*60*1000;
@@ -230,6 +240,12 @@
private static final int NUM_VALIDATION_LOG_LINES = 20;
+ public static boolean isValidationRequired(
+ NetworkCapabilities dfltNetCap, NetworkCapabilities nc) {
+ // TODO: Consider requiring validation for DUN networks.
+ return dfltNetCap.satisfiedByNetworkCapabilities(nc);
+ }
+
private final Context mContext;
private final Handler mConnectivityServiceHandler;
private final NetworkAgentInfo mNetworkAgentInfo;
@@ -261,6 +277,8 @@
public boolean systemReady = false;
+ private DnsManager.PrivateDnsConfig mPrivateDnsCfg = null;
+
private final State mDefaultState = new DefaultState();
private final State mValidatedState = new ValidatedState();
private final State mMaybeNotifyState = new MaybeNotifyState();
@@ -342,6 +360,11 @@
return 0 == mValidations ? ValidationStage.FIRST_VALIDATION : ValidationStage.REVALIDATION;
}
+ private boolean isValidationRequired() {
+ return isValidationRequired(
+ mDefaultRequest.networkCapabilities, mNetworkAgentInfo.networkCapabilities);
+ }
+
// DefaultState is the parent of all States. It exists only to handle CMD_* messages but
// does not entail any real state (hence no enter() or exit() routines).
private class DefaultState extends State {
@@ -405,6 +428,18 @@
break;
}
return HANDLED;
+ case CMD_PRIVATE_DNS_SETTINGS_CHANGED:
+ if (isValidationRequired()) {
+ // This performs a blocking DNS resolution of the
+ // strict mode hostname, if required.
+ resolvePrivateDnsConfig((DnsManager.PrivateDnsConfig) message.obj);
+ if ((mPrivateDnsCfg != null) && mPrivateDnsCfg.inStrictMode()) {
+ mConnectivityServiceHandler.sendMessage(obtainMessage(
+ EVENT_PRIVATE_DNS_CONFIG_RESOLVED, 0, mNetId,
+ new DnsManager.PrivateDnsConfig(mPrivateDnsCfg)));
+ }
+ }
+ return HANDLED;
default:
return HANDLED;
}
@@ -421,7 +456,7 @@
maybeLogEvaluationResult(
networkEventType(validationStage(), EvaluationResult.VALIDATED));
mConnectivityServiceHandler.sendMessage(obtainMessage(EVENT_NETWORK_TESTED,
- NETWORK_TEST_RESULT_VALID, mNetId, null));
+ NETWORK_TEST_RESULT_VALID, mNetId, mPrivateDnsCfg));
mValidations++;
}
@@ -567,9 +602,9 @@
// the network so don't bother validating here. Furthermore sending HTTP
// packets over the network may be undesirable, for example an extremely
// expensive metered network, or unwanted leaking of the User Agent string.
- if (!mDefaultRequest.networkCapabilities.satisfiedByNetworkCapabilities(
- mNetworkAgentInfo.networkCapabilities)) {
+ if (!isValidationRequired()) {
validationLog("Network would not satisfy default request, not validating");
+ mPrivateDnsCfg = null;
transitionTo(mValidatedState);
return HANDLED;
}
@@ -582,6 +617,7 @@
// if this is found to cause problems.
CaptivePortalProbeResult probeResult = isCaptivePortal();
if (probeResult.isSuccessful()) {
+ resolvePrivateDnsConfig();
transitionTo(mValidatedState);
} else if (probeResult.isPortal()) {
mConnectivityServiceHandler.sendMessage(obtainMessage(EVENT_NETWORK_TESTED,
@@ -1045,6 +1081,44 @@
return null;
}
+ public void notifyPrivateDnsSettingsChanged(DnsManager.PrivateDnsConfig newCfg) {
+ // Cancel any outstanding resolutions.
+ removeMessages(CMD_PRIVATE_DNS_SETTINGS_CHANGED);
+ // Send the update to the proper thread.
+ sendMessage(CMD_PRIVATE_DNS_SETTINGS_CHANGED, newCfg);
+ }
+
+ private void resolvePrivateDnsConfig() {
+ resolvePrivateDnsConfig(DnsManager.getPrivateDnsConfig(mContext.getContentResolver()));
+ }
+
+ private void resolvePrivateDnsConfig(DnsManager.PrivateDnsConfig cfg) {
+ // Nothing to do.
+ if (cfg == null) {
+ mPrivateDnsCfg = null;
+ return;
+ }
+
+ // No DNS resolution required.
+ if (!cfg.inStrictMode()) {
+ mPrivateDnsCfg = cfg;
+ return;
+ }
+
+ if ((mPrivateDnsCfg != null) && mPrivateDnsCfg.inStrictMode() &&
+ (mPrivateDnsCfg.ips.length > 0) && mPrivateDnsCfg.hostname.equals(cfg.hostname)) {
+ // We have already resolved this strict mode hostname. Assume that
+ // Private DNS services won't be changing serving IP addresses very
+ // frequently and save ourselves one re-resolve.
+ return;
+ }
+
+ mPrivateDnsCfg = cfg;
+ final DnsManager.PrivateDnsConfig resolvedCfg = DnsManager.tryBlockingResolveOf(
+ mNetwork, mPrivateDnsCfg.hostname);
+ if (resolvedCfg != null) mPrivateDnsCfg = resolvedCfg;
+ }
+
/**
* @param responseReceived - whether or not we received a valid HTTP response to our request.
* If false, isCaptivePortal and responseTimestampMs are ignored