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