DnsManager.java revision 1fcb7398d13fbab686fdc2c209105af4865757af
1/*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.connectivity;
18
19import static android.net.ConnectivityManager.PRIVATE_DNS_DEFAULT_MODE;
20import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
21import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
22import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
23import static android.provider.Settings.Global.DNS_RESOLVER_MIN_SAMPLES;
24import static android.provider.Settings.Global.DNS_RESOLVER_MAX_SAMPLES;
25import static android.provider.Settings.Global.DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS;
26import static android.provider.Settings.Global.DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT;
27import static android.provider.Settings.Global.PRIVATE_DNS_MODE;
28import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
29
30import android.content.ContentResolver;
31import android.content.Context;
32import android.content.Intent;
33import android.net.LinkProperties;
34import android.net.Network;
35import android.net.NetworkUtils;
36import android.net.Uri;
37import android.net.dns.ResolvUtil;
38import android.os.Binder;
39import android.os.INetworkManagementService;
40import android.os.UserHandle;
41import android.provider.Settings;
42import android.text.TextUtils;
43import android.util.Pair;
44import android.util.Slog;
45
46import com.android.server.connectivity.MockableSystemProperties;
47
48import java.net.InetAddress;
49import java.net.UnknownHostException;
50import java.util.Arrays;
51import java.util.Collection;
52import java.util.HashMap;
53import java.util.HashSet;
54import java.util.Iterator;
55import java.util.Map;
56import java.util.Objects;
57import java.util.stream.Collectors;
58import java.util.Set;
59import java.util.StringJoiner;
60
61
62/**
63 * Encapsulate the management of DNS settings for networks.
64 *
65 * This class it NOT designed for concurrent access. Furthermore, all non-static
66 * methods MUST be called from ConnectivityService's thread.
67 *
68 * [ Private DNS ]
69 * The code handling Private DNS is spread across several components, but this
70 * seems like the least bad place to collect all the observations.
71 *
72 * Private DNS handling and updating occurs in response to several different
73 * events. Each is described here with its corresponding intended handling.
74 *
75 * [A] Event: A new network comes up.
76 * Mechanics:
77 *     [1] ConnectivityService gets notifications from NetworkAgents.
78 *     [2] in updateNetworkInfo(), the first time the NetworkAgent goes into
79 *         into CONNECTED state, the Private DNS configuration is retrieved,
80 *         programmed, and strict mode hostname resolution (if applicable) is
81 *         enqueued in NetworkAgent's NetworkMonitor, via a call to
82 *         handlePerNetworkPrivateDnsConfig().
83 *     [3] Re-resolution of strict mode hostnames that fail to return any
84 *         IP addresses happens inside NetworkMonitor; it sends itself a
85 *         delayed CMD_EVALUATE_PRIVATE_DNS message in a simple backoff
86 *         schedule.
87 *     [4] Successfully resolved hostnames are sent to ConnectivityService
88 *         inside an EVENT_PRIVATE_DNS_CONFIG_RESOLVED message. The resolved
89 *         IP addresses are programmed into netd via:
90 *
91 *             updatePrivateDns() -> updateDnses()
92 *
93 *         both of which make calls into DnsManager.
94 *     [5] Upon a successful hostname resolution NetworkMonitor initiates a
95 *         validation attempt in the form of a lookup for a one-time hostname
96 *         that uses Private DNS.
97 *
98 * [B] Event: Private DNS settings are changed.
99 * Mechanics:
100 *     [1] ConnectivityService gets notifications from its SettingsObserver.
101 *     [2] handlePrivateDnsSettingsChanged() is called, which calls
102 *         handlePerNetworkPrivateDnsConfig() and the process proceeds
103 *         as if from A.3 above.
104 *
105 * [C] Event: An application calls ConnectivityManager#reportBadNetwork().
106 * Mechanics:
107 *     [1] NetworkMonitor is notified and initiates a reevaluation, which
108 *         always bypasses Private DNS.
109 *     [2] Once completed, NetworkMonitor checks if strict mode is in operation
110 *         and if so enqueues another evaluation of Private DNS, as if from
111 *         step A.5 above.
112 *
113 * @hide
114 */
115public class DnsManager {
116    private static final String TAG = DnsManager.class.getSimpleName();
117    private static final PrivateDnsConfig PRIVATE_DNS_OFF = new PrivateDnsConfig();
118
119    /* Defaults for resolver parameters. */
120    private static final int DNS_RESOLVER_DEFAULT_SAMPLE_VALIDITY_SECONDS = 1800;
121    private static final int DNS_RESOLVER_DEFAULT_SUCCESS_THRESHOLD_PERCENT = 25;
122    private static final int DNS_RESOLVER_DEFAULT_MIN_SAMPLES = 8;
123    private static final int DNS_RESOLVER_DEFAULT_MAX_SAMPLES = 64;
124
125    public static class PrivateDnsConfig {
126        public final boolean useTls;
127        public final String hostname;
128        public final InetAddress[] ips;
129
130        public PrivateDnsConfig() {
131            this(false);
132        }
133
134        public PrivateDnsConfig(boolean useTls) {
135            this.useTls = useTls;
136            this.hostname = "";
137            this.ips = new InetAddress[0];
138        }
139
140        public PrivateDnsConfig(String hostname, InetAddress[] ips) {
141            this.useTls = !TextUtils.isEmpty(hostname);
142            this.hostname = useTls ? hostname : "";
143            this.ips = (ips != null) ? ips : new InetAddress[0];
144        }
145
146        public PrivateDnsConfig(PrivateDnsConfig cfg) {
147            useTls = cfg.useTls;
148            hostname = cfg.hostname;
149            ips = cfg.ips;
150        }
151
152        public boolean inStrictMode() {
153            return useTls && !TextUtils.isEmpty(hostname);
154        }
155
156        public String toString() {
157            return PrivateDnsConfig.class.getSimpleName() +
158                    "{" + useTls + ":" + hostname + "/" + Arrays.toString(ips) + "}";
159        }
160    }
161
162    public static PrivateDnsConfig getPrivateDnsConfig(ContentResolver cr) {
163        final String mode = getPrivateDnsMode(cr);
164
165        final boolean useTls = !TextUtils.isEmpty(mode) && !PRIVATE_DNS_MODE_OFF.equals(mode);
166
167        if (PRIVATE_DNS_MODE_PROVIDER_HOSTNAME.equals(mode)) {
168            final String specifier = getStringSetting(cr, PRIVATE_DNS_SPECIFIER);
169            return new PrivateDnsConfig(specifier, null);
170        }
171
172        return new PrivateDnsConfig(useTls);
173    }
174
175    public static PrivateDnsConfig tryBlockingResolveOf(Network network, String name) {
176        try {
177            final InetAddress[] ips = ResolvUtil.blockingResolveAllLocally(network, name);
178            return new PrivateDnsConfig(name, ips);
179        } catch (UnknownHostException uhe) {
180            return new PrivateDnsConfig(name, null);
181        }
182    }
183
184    public static Uri[] getPrivateDnsSettingsUris() {
185        return new Uri[]{
186            Settings.Global.getUriFor(PRIVATE_DNS_MODE),
187            Settings.Global.getUriFor(PRIVATE_DNS_SPECIFIER),
188        };
189    }
190
191    public static class PrivateDnsValidationUpdate {
192        final public int netId;
193        final public InetAddress ipAddress;
194        final public String hostname;
195        final public boolean validated;
196
197        public PrivateDnsValidationUpdate(int netId, InetAddress ipAddress,
198                String hostname, boolean validated) {
199            this.netId = netId;
200            this.ipAddress = ipAddress;
201            this.hostname = hostname;
202            this.validated = validated;
203        }
204    }
205
206    private static class PrivateDnsValidationStatuses {
207        enum ValidationStatus {
208            IN_PROGRESS,
209            FAILED,
210            SUCCEEDED
211        }
212
213        // Validation statuses of <hostname, ipAddress> pairs for a single netId
214        private Map<Pair<String, InetAddress>, ValidationStatus> mValidationMap;
215
216        private PrivateDnsValidationStatuses() {
217            mValidationMap = new HashMap<>();
218        }
219
220        private boolean hasValidatedServer() {
221            for (ValidationStatus status : mValidationMap.values()) {
222                if (status == ValidationStatus.SUCCEEDED) {
223                    return true;
224                }
225            }
226            return false;
227        }
228
229        private void updateTrackedDnses(String[] ipAddresses, String hostname) {
230            Set<Pair<String, InetAddress>> latestDnses = new HashSet<>();
231            for (String ipAddress : ipAddresses) {
232                try {
233                    latestDnses.add(new Pair(hostname,
234                            InetAddress.parseNumericAddress(ipAddress)));
235                } catch (IllegalArgumentException e) {}
236            }
237            // Remove <hostname, ipAddress> pairs that should not be tracked.
238            for (Iterator<Map.Entry<Pair<String, InetAddress>, ValidationStatus>> it =
239                    mValidationMap.entrySet().iterator(); it.hasNext(); ) {
240                Map.Entry<Pair<String, InetAddress>, ValidationStatus> entry = it.next();
241                if (!latestDnses.contains(entry.getKey())) {
242                    it.remove();
243                }
244            }
245            // Add new <hostname, ipAddress> pairs that should be tracked.
246            for (Pair<String, InetAddress> p : latestDnses) {
247                if (!mValidationMap.containsKey(p)) {
248                    mValidationMap.put(p, ValidationStatus.IN_PROGRESS);
249                }
250            }
251        }
252
253        private void updateStatus(PrivateDnsValidationUpdate update) {
254            Pair<String, InetAddress> p = new Pair(update.hostname,
255                    update.ipAddress);
256            if (!mValidationMap.containsKey(p)) {
257                return;
258            }
259            if (update.validated) {
260                mValidationMap.put(p, ValidationStatus.SUCCEEDED);
261            } else {
262                mValidationMap.put(p, ValidationStatus.FAILED);
263            }
264        }
265    }
266
267    private final Context mContext;
268    private final ContentResolver mContentResolver;
269    private final INetworkManagementService mNMS;
270    private final MockableSystemProperties mSystemProperties;
271    // TODO: Replace these Maps with SparseArrays.
272    private final Map<Integer, PrivateDnsConfig> mPrivateDnsMap;
273    private final Map<Integer, PrivateDnsValidationStatuses> mPrivateDnsValidationMap;
274
275    private int mNumDnsEntries;
276    private int mSampleValidity;
277    private int mSuccessThreshold;
278    private int mMinSamples;
279    private int mMaxSamples;
280    private String mPrivateDnsMode;
281    private String mPrivateDnsSpecifier;
282
283    public DnsManager(Context ctx, INetworkManagementService nms, MockableSystemProperties sp) {
284        mContext = ctx;
285        mContentResolver = mContext.getContentResolver();
286        mNMS = nms;
287        mSystemProperties = sp;
288        mPrivateDnsMap = new HashMap<>();
289        mPrivateDnsValidationMap = new HashMap<>();
290
291        // TODO: Create and register ContentObservers to track every setting
292        // used herein, posting messages to respond to changes.
293    }
294
295    public PrivateDnsConfig getPrivateDnsConfig() {
296        return getPrivateDnsConfig(mContentResolver);
297    }
298
299    public void removeNetwork(Network network) {
300        mPrivateDnsMap.remove(network.netId);
301        mPrivateDnsValidationMap.remove(network.netId);
302    }
303
304    public PrivateDnsConfig updatePrivateDns(Network network, PrivateDnsConfig cfg) {
305        Slog.w(TAG, "updatePrivateDns(" + network + ", " + cfg + ")");
306        return (cfg != null)
307                ? mPrivateDnsMap.put(network.netId, cfg)
308                : mPrivateDnsMap.remove(network.netId);
309    }
310
311    public void updatePrivateDnsStatus(int netId, LinkProperties lp) {
312        // Use the PrivateDnsConfig data pushed to this class instance
313        // from ConnectivityService.
314        final PrivateDnsConfig privateDnsCfg = mPrivateDnsMap.getOrDefault(netId,
315                PRIVATE_DNS_OFF);
316
317        final boolean useTls = privateDnsCfg.useTls;
318        final boolean strictMode = privateDnsCfg.inStrictMode();
319        final String tlsHostname = strictMode ? privateDnsCfg.hostname : "";
320
321        if (strictMode) {
322            lp.setUsePrivateDns(true);
323            lp.setPrivateDnsServerName(tlsHostname);
324        } else if (useTls) {
325            // We are in opportunistic mode. Private DNS should be used if there
326            // is a known DNS-over-TLS validated server.
327            boolean validated = mPrivateDnsValidationMap.containsKey(netId) &&
328                    mPrivateDnsValidationMap.get(netId).hasValidatedServer();
329            lp.setUsePrivateDns(validated);
330            lp.setPrivateDnsServerName(null);
331        } else {
332            // Private DNS is disabled.
333            lp.setUsePrivateDns(false);
334            lp.setPrivateDnsServerName(null);
335        }
336    }
337
338    public void updatePrivateDnsValidation(PrivateDnsValidationUpdate update) {
339        final PrivateDnsValidationStatuses statuses =
340                mPrivateDnsValidationMap.get(update.netId);
341        if (statuses == null) return;
342        statuses.updateStatus(update);
343    }
344
345    public void setDnsConfigurationForNetwork(
346            int netId, LinkProperties lp, boolean isDefaultNetwork) {
347        final String[] assignedServers = NetworkUtils.makeStrings(lp.getDnsServers());
348        final String[] domainStrs = getDomainStrings(lp.getDomains());
349
350        updateParametersSettings();
351        final int[] params = { mSampleValidity, mSuccessThreshold, mMinSamples, mMaxSamples };
352
353        // We only use the PrivateDnsConfig data pushed to this class instance
354        // from ConnectivityService because it works in coordination with
355        // NetworkMonitor to decide which networks need validation and runs the
356        // blocking calls to resolve Private DNS strict mode hostnames.
357        //
358        // At this time we do not attempt to enable Private DNS on non-Internet
359        // networks like IMS.
360        final PrivateDnsConfig privateDnsCfg = mPrivateDnsMap.getOrDefault(netId,
361                PRIVATE_DNS_OFF);
362
363        final boolean useTls = privateDnsCfg.useTls;
364        final boolean strictMode = privateDnsCfg.inStrictMode();
365        final String tlsHostname = strictMode ? privateDnsCfg.hostname : "";
366        final String[] tlsServers =
367                strictMode ? NetworkUtils.makeStrings(
368                        Arrays.stream(privateDnsCfg.ips)
369                              .filter((ip) -> lp.isReachable(ip))
370                              .collect(Collectors.toList()))
371                : useTls ? assignedServers  // Opportunistic
372                : new String[0];            // Off
373
374        // Prepare to track the validation status of the DNS servers in the
375        // resolver config when private DNS is in opportunistic or strict mode.
376        if (useTls) {
377            if (!mPrivateDnsValidationMap.containsKey(netId)) {
378                mPrivateDnsValidationMap.put(netId, new PrivateDnsValidationStatuses());
379            }
380            mPrivateDnsValidationMap.get(netId).updateTrackedDnses(tlsServers, tlsHostname);
381        } else {
382            mPrivateDnsValidationMap.remove(netId);
383        }
384
385        Slog.d(TAG, String.format("setDnsConfigurationForNetwork(%d, %s, %s, %s, %s, %s)",
386                netId, Arrays.toString(assignedServers), Arrays.toString(domainStrs),
387                Arrays.toString(params), tlsHostname, Arrays.toString(tlsServers)));
388        try {
389            mNMS.setDnsConfigurationForNetwork(
390                    netId, assignedServers, domainStrs, params, tlsHostname, tlsServers);
391        } catch (Exception e) {
392            Slog.e(TAG, "Error setting DNS configuration: " + e);
393            return;
394        }
395
396        // TODO: netd should listen on [::1]:53 and proxy queries to the current
397        // default network, and we should just set net.dns1 to ::1, not least
398        // because applications attempting to use net.dns resolvers will bypass
399        // the privacy protections of things like DNS-over-TLS.
400        if (isDefaultNetwork) setDefaultDnsSystemProperties(lp.getDnsServers());
401        flushVmDnsCache();
402    }
403
404    public void setDefaultDnsSystemProperties(Collection<InetAddress> dnses) {
405        int last = 0;
406        for (InetAddress dns : dnses) {
407            ++last;
408            setNetDnsProperty(last, dns.getHostAddress());
409        }
410        for (int i = last + 1; i <= mNumDnsEntries; ++i) {
411            setNetDnsProperty(i, "");
412        }
413        mNumDnsEntries = last;
414    }
415
416    private void flushVmDnsCache() {
417        /*
418         * Tell the VMs to toss their DNS caches
419         */
420        final Intent intent = new Intent(Intent.ACTION_CLEAR_DNS_CACHE);
421        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
422        /*
423         * Connectivity events can happen before boot has completed ...
424         */
425        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
426        final long ident = Binder.clearCallingIdentity();
427        try {
428            mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
429        } finally {
430            Binder.restoreCallingIdentity(ident);
431        }
432    }
433
434    private void updateParametersSettings() {
435        mSampleValidity = getIntSetting(
436                DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS,
437                DNS_RESOLVER_DEFAULT_SAMPLE_VALIDITY_SECONDS);
438        if (mSampleValidity < 0 || mSampleValidity > 65535) {
439            Slog.w(TAG, "Invalid sampleValidity=" + mSampleValidity + ", using default=" +
440                    DNS_RESOLVER_DEFAULT_SAMPLE_VALIDITY_SECONDS);
441            mSampleValidity = DNS_RESOLVER_DEFAULT_SAMPLE_VALIDITY_SECONDS;
442        }
443
444        mSuccessThreshold = getIntSetting(
445                DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT,
446                DNS_RESOLVER_DEFAULT_SUCCESS_THRESHOLD_PERCENT);
447        if (mSuccessThreshold < 0 || mSuccessThreshold > 100) {
448            Slog.w(TAG, "Invalid successThreshold=" + mSuccessThreshold + ", using default=" +
449                    DNS_RESOLVER_DEFAULT_SUCCESS_THRESHOLD_PERCENT);
450            mSuccessThreshold = DNS_RESOLVER_DEFAULT_SUCCESS_THRESHOLD_PERCENT;
451        }
452
453        mMinSamples = getIntSetting(DNS_RESOLVER_MIN_SAMPLES, DNS_RESOLVER_DEFAULT_MIN_SAMPLES);
454        mMaxSamples = getIntSetting(DNS_RESOLVER_MAX_SAMPLES, DNS_RESOLVER_DEFAULT_MAX_SAMPLES);
455        if (mMinSamples < 0 || mMinSamples > mMaxSamples || mMaxSamples > 64) {
456            Slog.w(TAG, "Invalid sample count (min, max)=(" + mMinSamples + ", " + mMaxSamples +
457                    "), using default=(" + DNS_RESOLVER_DEFAULT_MIN_SAMPLES + ", " +
458                    DNS_RESOLVER_DEFAULT_MAX_SAMPLES + ")");
459            mMinSamples = DNS_RESOLVER_DEFAULT_MIN_SAMPLES;
460            mMaxSamples = DNS_RESOLVER_DEFAULT_MAX_SAMPLES;
461        }
462    }
463
464    private int getIntSetting(String which, int dflt) {
465        return Settings.Global.getInt(mContentResolver, which, dflt);
466    }
467
468    private void setNetDnsProperty(int which, String value) {
469        final String key = "net.dns" + which;
470        // Log and forget errors setting unsupported properties.
471        try {
472            mSystemProperties.set(key, value);
473        } catch (Exception e) {
474            Slog.e(TAG, "Error setting unsupported net.dns property: ", e);
475        }
476    }
477
478    private static String getPrivateDnsMode(ContentResolver cr) {
479        final String mode = getStringSetting(cr, PRIVATE_DNS_MODE);
480        return !TextUtils.isEmpty(mode) ? mode : PRIVATE_DNS_DEFAULT_MODE;
481    }
482
483    private static String getStringSetting(ContentResolver cr, String which) {
484        return Settings.Global.getString(cr, which);
485    }
486
487    private static String[] getDomainStrings(String domains) {
488        return (TextUtils.isEmpty(domains)) ? new String[0] : domains.split(" ");
489    }
490}
491