diff options
| author | 2018-01-11 21:07:29 +0900 | |
|---|---|---|
| committer | 2018-01-17 23:17:08 +0900 | |
| commit | a24d459a5d60c706472f9b620d079cd0a40a7279 (patch) | |
| tree | 43551060a1bc133aae00a1251a9a4f3f4527f94b | |
| parent | fa6187510a272413cb22b4590ba62d1977539847 (diff) | |
Add basic resolution of Private DNS hostname
Test: as follows
- built
- flashed
- booted
Bug: 34953048
Bug: 64133961
Change-Id: I0a2289ea51a9294352c91d2ead23730d9b0333a5
4 files changed, 362 insertions, 92 deletions
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index ed1e0113b5c3..56d7e7b9304a 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.internal.util.XmlUtils; 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; @@ -399,6 +400,9 @@ public class ConnectivityService extends IConnectivityManager.Stub */ 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)); } @@ -863,6 +867,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mMultinetworkPolicyTracker.start(); mDnsManager = new DnsManager(mContext, mNetd, mSystemProperties); + registerPrivateDnsSettingsCallbacks(); } private Tethering makeTethering() { @@ -923,6 +928,12 @@ public class ConnectivityService extends IConnectivityManager.Stub 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++; } @@ -2086,36 +2097,59 @@ public class ConnectivityService extends IConnectivityManager.Stub 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; } @@ -2155,6 +2189,21 @@ public class ConnectivityService extends IConnectivityManager.Stub } 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; } @@ -2190,6 +2239,63 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + 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. @@ -2324,6 +2430,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } catch (Exception e) { loge("Exception removing network: " + e); } + mDnsManager.removeNetwork(nai.network); } synchronized (mNetworkForNetId) { mNetIdInUse.delete(nai.network.netId); @@ -2870,6 +2977,9 @@ public class ConnectivityService extends IConnectivityManager.Stub handleReportNetworkConnectivity((Network) msg.obj, msg.arg1, toBool(msg.arg2)); break; } + case EVENT_PRIVATE_DNS_SETTINGS_CHANGED: + handlePrivateDnsSettingsChanged(); + break; } } } @@ -4559,11 +4669,12 @@ public class ConnectivityService extends IConnectivityManager.Stub 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 8f646e75862c..88ae22477a9d 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.DUMP; 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 @@ public class NetworkManagementService extends INetworkManagementService.Stub } } - 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 a4170cede361..a1c54bd4885b 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 static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER; 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 @@ public class DnsManager { 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 @@ public class DnsManager { 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 boolean useTls = (privateDnsCfg != null) && privateDnsCfg.useTls; + final boolean strictMode = (privateDnsCfg != null) && privateDnsCfg.inStrictMode(); + final String tlsHostname = strictMode ? privateDnsCfg.hostname : ""; - final String[] serverStrs = NetworkUtils.makeStrings(servers); - final String[] domainStrs = (domains == null) ? new String[0] : domains.split(" "); + 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 @@ public class DnsManager { // 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 @@ public class DnsManager { } } - 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 @@ public class DnsManager { } } - 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 @@ public class DnsManager { } } - 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 768403024353..ed268581b50c 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.CaptivePortal; 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 @@ public class NetworkMonitor extends StateMachine { */ 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 @@ public class NetworkMonitor extends StateMachine { 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 class NetworkMonitor extends StateMachine { 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 @@ public class NetworkMonitor extends StateMachine { 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 @@ public class NetworkMonitor extends StateMachine { 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 @@ public class NetworkMonitor extends StateMachine { 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 @@ public class NetworkMonitor extends StateMachine { // 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 @@ public class NetworkMonitor extends StateMachine { // 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 @@ public class NetworkMonitor extends StateMachine { 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 |