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 
17 package com.android.server.connectivity;
18 
19 import static android.net.ConnectivityManager.PRIVATE_DNS_DEFAULT_MODE_FALLBACK;
20 import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
21 import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
22 import static android.provider.Settings.Global.DNS_RESOLVER_MAX_SAMPLES;
23 import static android.provider.Settings.Global.DNS_RESOLVER_MIN_SAMPLES;
24 import static android.provider.Settings.Global.DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS;
25 import static android.provider.Settings.Global.DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT;
26 import static android.provider.Settings.Global.PRIVATE_DNS_DEFAULT_MODE;
27 import static android.provider.Settings.Global.PRIVATE_DNS_MODE;
28 import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
29 
30 import android.annotation.NonNull;
31 import android.content.ContentResolver;
32 import android.content.Context;
33 import android.content.Intent;
34 import android.net.IDnsResolver;
35 import android.net.LinkProperties;
36 import android.net.Network;
37 import android.net.NetworkUtils;
38 import android.net.ResolverOptionsParcel;
39 import android.net.ResolverParamsParcel;
40 import android.net.Uri;
41 import android.net.shared.PrivateDnsConfig;
42 import android.os.Binder;
43 import android.os.RemoteException;
44 import android.os.ServiceSpecificException;
45 import android.os.UserHandle;
46 import android.provider.Settings;
47 import android.text.TextUtils;
48 import android.util.Pair;
49 import android.util.Slog;
50 
51 import java.net.InetAddress;
52 import java.util.Arrays;
53 import java.util.Collection;
54 import java.util.Collections;
55 import java.util.HashMap;
56 import java.util.HashSet;
57 import java.util.Iterator;
58 import java.util.Map;
59 import java.util.Set;
60 import java.util.concurrent.ConcurrentHashMap;
61 import java.util.stream.Collectors;
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. However, an exceptional
69  * case is getPrivateDnsConfig(Network) which is exclusively for
70  * ConnectivityService#dumpNetworkDiagnostics() on a random binder thread.
71  *
72  * [ Private DNS ]
73  * The code handling Private DNS is spread across several components, but this
74  * seems like the least bad place to collect all the observations.
75  *
76  * Private DNS handling and updating occurs in response to several different
77  * events. Each is described here with its corresponding intended handling.
78  *
79  * [A] Event: A new network comes up.
80  * Mechanics:
81  *     [1] ConnectivityService gets notifications from NetworkAgents.
82  *     [2] in updateNetworkInfo(), the first time the NetworkAgent goes into
83  *         into CONNECTED state, the Private DNS configuration is retrieved,
84  *         programmed, and strict mode hostname resolution (if applicable) is
85  *         enqueued in NetworkAgent's NetworkMonitor, via a call to
86  *         handlePerNetworkPrivateDnsConfig().
87  *     [3] Re-resolution of strict mode hostnames that fail to return any
88  *         IP addresses happens inside NetworkMonitor; it sends itself a
89  *         delayed CMD_EVALUATE_PRIVATE_DNS message in a simple backoff
90  *         schedule.
91  *     [4] Successfully resolved hostnames are sent to ConnectivityService
92  *         inside an EVENT_PRIVATE_DNS_CONFIG_RESOLVED message. The resolved
93  *         IP addresses are programmed into netd via:
94  *
95  *             updatePrivateDns() -> updateDnses()
96  *
97  *         both of which make calls into DnsManager.
98  *     [5] Upon a successful hostname resolution NetworkMonitor initiates a
99  *         validation attempt in the form of a lookup for a one-time hostname
100  *         that uses Private DNS.
101  *
102  * [B] Event: Private DNS settings are changed.
103  * Mechanics:
104  *     [1] ConnectivityService gets notifications from its SettingsObserver.
105  *     [2] handlePrivateDnsSettingsChanged() is called, which calls
106  *         handlePerNetworkPrivateDnsConfig() and the process proceeds
107  *         as if from A.3 above.
108  *
109  * [C] Event: An application calls ConnectivityManager#reportBadNetwork().
110  * Mechanics:
111  *     [1] NetworkMonitor is notified and initiates a reevaluation, which
112  *         always bypasses Private DNS.
113  *     [2] Once completed, NetworkMonitor checks if strict mode is in operation
114  *         and if so enqueues another evaluation of Private DNS, as if from
115  *         step A.5 above.
116  *
117  * @hide
118  */
119 public class DnsManager {
120     private static final String TAG = DnsManager.class.getSimpleName();
121     private static final PrivateDnsConfig PRIVATE_DNS_OFF = new PrivateDnsConfig();
122 
123     /* Defaults for resolver parameters. */
124     private static final int DNS_RESOLVER_DEFAULT_SAMPLE_VALIDITY_SECONDS = 1800;
125     private static final int DNS_RESOLVER_DEFAULT_SUCCESS_THRESHOLD_PERCENT = 25;
126     private static final int DNS_RESOLVER_DEFAULT_MIN_SAMPLES = 8;
127     private static final int DNS_RESOLVER_DEFAULT_MAX_SAMPLES = 64;
128 
getPrivateDnsConfig(ContentResolver cr)129     public static PrivateDnsConfig getPrivateDnsConfig(ContentResolver cr) {
130         final String mode = getPrivateDnsMode(cr);
131 
132         final boolean useTls = !TextUtils.isEmpty(mode) && !PRIVATE_DNS_MODE_OFF.equals(mode);
133 
134         if (PRIVATE_DNS_MODE_PROVIDER_HOSTNAME.equals(mode)) {
135             final String specifier = getStringSetting(cr, PRIVATE_DNS_SPECIFIER);
136             return new PrivateDnsConfig(specifier, null);
137         }
138 
139         return new PrivateDnsConfig(useTls);
140     }
141 
getPrivateDnsSettingsUris()142     public static Uri[] getPrivateDnsSettingsUris() {
143         return new Uri[]{
144             Settings.Global.getUriFor(PRIVATE_DNS_DEFAULT_MODE),
145             Settings.Global.getUriFor(PRIVATE_DNS_MODE),
146             Settings.Global.getUriFor(PRIVATE_DNS_SPECIFIER),
147         };
148     }
149 
150     public static class PrivateDnsValidationUpdate {
151         final public int netId;
152         final public InetAddress ipAddress;
153         final public String hostname;
154         final public boolean validated;
155 
PrivateDnsValidationUpdate(int netId, InetAddress ipAddress, String hostname, boolean validated)156         public PrivateDnsValidationUpdate(int netId, InetAddress ipAddress,
157                 String hostname, boolean validated) {
158             this.netId = netId;
159             this.ipAddress = ipAddress;
160             this.hostname = hostname;
161             this.validated = validated;
162         }
163     }
164 
165     private static class PrivateDnsValidationStatuses {
166         enum ValidationStatus {
167             IN_PROGRESS,
168             FAILED,
169             SUCCEEDED
170         }
171 
172         // Validation statuses of <hostname, ipAddress> pairs for a single netId
173         // Caution : not thread-safe. As mentioned in the top file comment, all
174         // methods of this class must only be called on ConnectivityService's thread.
175         private Map<Pair<String, InetAddress>, ValidationStatus> mValidationMap;
176 
PrivateDnsValidationStatuses()177         private PrivateDnsValidationStatuses() {
178             mValidationMap = new HashMap<>();
179         }
180 
hasValidatedServer()181         private boolean hasValidatedServer() {
182             for (ValidationStatus status : mValidationMap.values()) {
183                 if (status == ValidationStatus.SUCCEEDED) {
184                     return true;
185                 }
186             }
187             return false;
188         }
189 
updateTrackedDnses(String[] ipAddresses, String hostname)190         private void updateTrackedDnses(String[] ipAddresses, String hostname) {
191             Set<Pair<String, InetAddress>> latestDnses = new HashSet<>();
192             for (String ipAddress : ipAddresses) {
193                 try {
194                     latestDnses.add(new Pair(hostname,
195                             InetAddress.parseNumericAddress(ipAddress)));
196                 } catch (IllegalArgumentException e) {}
197             }
198             // Remove <hostname, ipAddress> pairs that should not be tracked.
199             for (Iterator<Map.Entry<Pair<String, InetAddress>, ValidationStatus>> it =
200                     mValidationMap.entrySet().iterator(); it.hasNext(); ) {
201                 Map.Entry<Pair<String, InetAddress>, ValidationStatus> entry = it.next();
202                 if (!latestDnses.contains(entry.getKey())) {
203                     it.remove();
204                 }
205             }
206             // Add new <hostname, ipAddress> pairs that should be tracked.
207             for (Pair<String, InetAddress> p : latestDnses) {
208                 if (!mValidationMap.containsKey(p)) {
209                     mValidationMap.put(p, ValidationStatus.IN_PROGRESS);
210                 }
211             }
212         }
213 
updateStatus(PrivateDnsValidationUpdate update)214         private void updateStatus(PrivateDnsValidationUpdate update) {
215             Pair<String, InetAddress> p = new Pair(update.hostname,
216                     update.ipAddress);
217             if (!mValidationMap.containsKey(p)) {
218                 return;
219             }
220             if (update.validated) {
221                 mValidationMap.put(p, ValidationStatus.SUCCEEDED);
222             } else {
223                 mValidationMap.put(p, ValidationStatus.FAILED);
224             }
225         }
226 
fillInValidatedPrivateDns(LinkProperties lp)227         private LinkProperties fillInValidatedPrivateDns(LinkProperties lp) {
228             lp.setValidatedPrivateDnsServers(Collections.EMPTY_LIST);
229             mValidationMap.forEach((key, value) -> {
230                     if (value == ValidationStatus.SUCCEEDED) {
231                         lp.addValidatedPrivateDnsServer(key.second);
232                     }
233                 });
234             return lp;
235         }
236     }
237 
238     private final Context mContext;
239     private final ContentResolver mContentResolver;
240     private final IDnsResolver mDnsResolver;
241     private final MockableSystemProperties mSystemProperties;
242     private final ConcurrentHashMap<Integer, PrivateDnsConfig> mPrivateDnsMap;
243     // TODO: Replace the Map with SparseArrays.
244     private final Map<Integer, PrivateDnsValidationStatuses> mPrivateDnsValidationMap;
245     private final Map<Integer, LinkProperties> mLinkPropertiesMap;
246     private final Map<Integer, int[]> mTransportsMap;
247 
248     private int mNumDnsEntries;
249     private int mSampleValidity;
250     private int mSuccessThreshold;
251     private int mMinSamples;
252     private int mMaxSamples;
253 
DnsManager(Context ctx, IDnsResolver dnsResolver, MockableSystemProperties sp)254     public DnsManager(Context ctx, IDnsResolver dnsResolver, MockableSystemProperties sp) {
255         mContext = ctx;
256         mContentResolver = mContext.getContentResolver();
257         mDnsResolver = dnsResolver;
258         mSystemProperties = sp;
259         mPrivateDnsMap = new ConcurrentHashMap<>();
260         mPrivateDnsValidationMap = new HashMap<>();
261         mLinkPropertiesMap = new HashMap<>();
262         mTransportsMap = new HashMap<>();
263 
264         // TODO: Create and register ContentObservers to track every setting
265         // used herein, posting messages to respond to changes.
266     }
267 
getPrivateDnsConfig()268     public PrivateDnsConfig getPrivateDnsConfig() {
269         return getPrivateDnsConfig(mContentResolver);
270     }
271 
removeNetwork(Network network)272     public void removeNetwork(Network network) {
273         mPrivateDnsMap.remove(network.netId);
274         mPrivateDnsValidationMap.remove(network.netId);
275         mTransportsMap.remove(network.netId);
276         mLinkPropertiesMap.remove(network.netId);
277     }
278 
279     // This is exclusively called by ConnectivityService#dumpNetworkDiagnostics() which
280     // is not on the ConnectivityService handler thread.
getPrivateDnsConfig(@onNull Network network)281     public PrivateDnsConfig getPrivateDnsConfig(@NonNull Network network) {
282         return mPrivateDnsMap.getOrDefault(network.netId, PRIVATE_DNS_OFF);
283     }
284 
updatePrivateDns(Network network, PrivateDnsConfig cfg)285     public PrivateDnsConfig updatePrivateDns(Network network, PrivateDnsConfig cfg) {
286         Slog.w(TAG, "updatePrivateDns(" + network + ", " + cfg + ")");
287         return (cfg != null)
288                 ? mPrivateDnsMap.put(network.netId, cfg)
289                 : mPrivateDnsMap.remove(network.netId);
290     }
291 
updatePrivateDnsStatus(int netId, LinkProperties lp)292     public void updatePrivateDnsStatus(int netId, LinkProperties lp) {
293         // Use the PrivateDnsConfig data pushed to this class instance
294         // from ConnectivityService.
295         final PrivateDnsConfig privateDnsCfg = mPrivateDnsMap.getOrDefault(netId,
296                 PRIVATE_DNS_OFF);
297 
298         final boolean useTls = privateDnsCfg.useTls;
299         final PrivateDnsValidationStatuses statuses =
300                 useTls ? mPrivateDnsValidationMap.get(netId) : null;
301         final boolean validated = (null != statuses) && statuses.hasValidatedServer();
302         final boolean strictMode = privateDnsCfg.inStrictMode();
303         final String tlsHostname = strictMode ? privateDnsCfg.hostname : null;
304         final boolean usingPrivateDns = strictMode || validated;
305 
306         lp.setUsePrivateDns(usingPrivateDns);
307         lp.setPrivateDnsServerName(tlsHostname);
308         if (usingPrivateDns && null != statuses) {
309             statuses.fillInValidatedPrivateDns(lp);
310         } else {
311             lp.setValidatedPrivateDnsServers(Collections.EMPTY_LIST);
312         }
313     }
314 
updatePrivateDnsValidation(PrivateDnsValidationUpdate update)315     public void updatePrivateDnsValidation(PrivateDnsValidationUpdate update) {
316         final PrivateDnsValidationStatuses statuses =
317                 mPrivateDnsValidationMap.get(update.netId);
318         if (statuses == null) return;
319         statuses.updateStatus(update);
320     }
321 
322     /**
323      * When creating a new network or transport types are changed in a specific network,
324      * transport types are always saved to a hashMap before update dns config.
325      * When destroying network, the specific network will be removed from the hashMap.
326      * The hashMap is always accessed on the same thread.
327      */
updateTransportsForNetwork(int netId, @NonNull int[] transportTypes)328     public void updateTransportsForNetwork(int netId, @NonNull int[] transportTypes) {
329         mTransportsMap.put(netId, transportTypes);
330         sendDnsConfigurationForNetwork(netId);
331     }
332 
333     /**
334      * When {@link LinkProperties} are changed in a specific network, they are
335      * always saved to a hashMap before update dns config.
336      * When destroying network, the specific network will be removed from the hashMap.
337      * The hashMap is always accessed on the same thread.
338      */
noteDnsServersForNetwork(int netId, @NonNull LinkProperties lp)339     public void noteDnsServersForNetwork(int netId, @NonNull LinkProperties lp) {
340         mLinkPropertiesMap.put(netId, lp);
341         sendDnsConfigurationForNetwork(netId);
342     }
343 
344     /**
345      * Send dns configuration parameters to resolver for a given network.
346      */
sendDnsConfigurationForNetwork(int netId)347     public void sendDnsConfigurationForNetwork(int netId) {
348         final LinkProperties lp = mLinkPropertiesMap.get(netId);
349         final int[] transportTypes = mTransportsMap.get(netId);
350         if (lp == null || transportTypes == null) return;
351         updateParametersSettings();
352         final ResolverParamsParcel paramsParcel = new ResolverParamsParcel();
353 
354         // We only use the PrivateDnsConfig data pushed to this class instance
355         // from ConnectivityService because it works in coordination with
356         // NetworkMonitor to decide which networks need validation and runs the
357         // blocking calls to resolve Private DNS strict mode hostnames.
358         //
359         // At this time we do not attempt to enable Private DNS on non-Internet
360         // networks like IMS.
361         final PrivateDnsConfig privateDnsCfg = mPrivateDnsMap.getOrDefault(netId,
362                 PRIVATE_DNS_OFF);
363         final boolean useTls = privateDnsCfg.useTls;
364         final boolean strictMode = privateDnsCfg.inStrictMode();
365 
366         paramsParcel.netId = netId;
367         paramsParcel.sampleValiditySeconds = mSampleValidity;
368         paramsParcel.successThreshold = mSuccessThreshold;
369         paramsParcel.minSamples = mMinSamples;
370         paramsParcel.maxSamples = mMaxSamples;
371         paramsParcel.servers =
372                 NetworkUtils.makeStrings(lp.getDnsServers());
373         paramsParcel.domains = getDomainStrings(lp.getDomains());
374         paramsParcel.tlsName = strictMode ? privateDnsCfg.hostname : "";
375         paramsParcel.tlsServers =
376                 strictMode ? NetworkUtils.makeStrings(
377                         Arrays.stream(privateDnsCfg.ips)
378                               .filter((ip) -> lp.isReachable(ip))
379                               .collect(Collectors.toList()))
380                 : useTls ? paramsParcel.servers  // Opportunistic
381                 : new String[0];            // Off
382         paramsParcel.resolverOptions = new ResolverOptionsParcel();
383         paramsParcel.transportTypes = transportTypes;
384         // Prepare to track the validation status of the DNS servers in the
385         // resolver config when private DNS is in opportunistic or strict mode.
386         if (useTls) {
387             if (!mPrivateDnsValidationMap.containsKey(netId)) {
388                 mPrivateDnsValidationMap.put(netId, new PrivateDnsValidationStatuses());
389             }
390             mPrivateDnsValidationMap.get(netId).updateTrackedDnses(paramsParcel.tlsServers,
391                     paramsParcel.tlsName);
392         } else {
393             mPrivateDnsValidationMap.remove(netId);
394         }
395 
396         Slog.d(TAG, String.format("sendDnsConfigurationForNetwork(%d, %s, %s, %d, %d, %d, %d, "
397                 + "%d, %d, %s, %s)", paramsParcel.netId, Arrays.toString(paramsParcel.servers),
398                 Arrays.toString(paramsParcel.domains), paramsParcel.sampleValiditySeconds,
399                 paramsParcel.successThreshold, paramsParcel.minSamples,
400                 paramsParcel.maxSamples, paramsParcel.baseTimeoutMsec,
401                 paramsParcel.retryCount, paramsParcel.tlsName,
402                 Arrays.toString(paramsParcel.tlsServers)));
403 
404         try {
405             mDnsResolver.setResolverConfiguration(paramsParcel);
406         } catch (RemoteException | ServiceSpecificException e) {
407             Slog.e(TAG, "Error setting DNS configuration: " + e);
408             return;
409         }
410     }
411 
setDefaultDnsSystemProperties(Collection<InetAddress> dnses)412     public void setDefaultDnsSystemProperties(Collection<InetAddress> dnses) {
413         int last = 0;
414         for (InetAddress dns : dnses) {
415             ++last;
416             setNetDnsProperty(last, dns.getHostAddress());
417         }
418         for (int i = last + 1; i <= mNumDnsEntries; ++i) {
419             setNetDnsProperty(i, "");
420         }
421         mNumDnsEntries = last;
422     }
423 
424     /**
425      * Flush DNS caches and events work before boot has completed.
426      */
flushVmDnsCache()427     public 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 
updateParametersSettings()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 
getIntSetting(String which, int dflt)475     private int getIntSetting(String which, int dflt) {
476         return Settings.Global.getInt(mContentResolver, which, dflt);
477     }
478 
setNetDnsProperty(int which, String value)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 
getPrivateDnsMode(ContentResolver cr)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 
getStringSetting(ContentResolver cr, String which)496     private static String getStringSetting(ContentResolver cr, String which) {
497         return Settings.Global.getString(cr, which);
498     }
499 
getDomainStrings(String domains)500     private static String[] getDomainStrings(String domains) {
501         return (TextUtils.isEmpty(domains)) ? new String[0] : domains.split(" ");
502     }
503 }
504