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