OSDN Git Service

Add basic resolution of Private DNS hostname
authorErik Kline <ek@google.com>
Thu, 11 Jan 2018 12:07:29 +0000 (21:07 +0900)
committerErik Kline <ek@google.com>
Wed, 17 Jan 2018 14:17:08 +0000 (23:17 +0900)
Test: as follows
    - built
    - flashed
    - booted
Bug: 34953048
Bug: 64133961
Change-Id: I0a2289ea51a9294352c91d2ead23730d9b0333a5

services/core/java/com/android/server/ConnectivityService.java
services/core/java/com/android/server/NetworkManagementService.java
services/core/java/com/android/server/connectivity/DnsManager.java
services/core/java/com/android/server/connectivity/NetworkMonitor.java

index ed1e011..56d7e7b 100644 (file)
@@ -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);
         }
index 8f646e7..88ae224 100644 (file)
@@ -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);
index a4170ce..a1c54bd 100644 (file)
@@ -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(" ");
     }
 }
index 7684030..ed26858 100644 (file)
@@ -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