Home | History | Annotate | Download | only in connectivity
      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_OPPORTUNISTIC;
     22 import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
     23 import static android.provider.Settings.Global.DNS_RESOLVER_MIN_SAMPLES;
     24 import static android.provider.Settings.Global.DNS_RESOLVER_MAX_SAMPLES;
     25 import static android.provider.Settings.Global.DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS;
     26 import static android.provider.Settings.Global.DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT;
     27 import static android.provider.Settings.Global.PRIVATE_DNS_DEFAULT_MODE;
     28 import static android.provider.Settings.Global.PRIVATE_DNS_MODE;
     29 import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
     30 
     31 import android.content.ContentResolver;
     32 import android.content.Context;
     33 import android.content.Intent;
     34 import android.net.LinkProperties;
     35 import android.net.Network;
     36 import android.net.NetworkUtils;
     37 import android.net.Uri;
     38 import android.net.dns.ResolvUtil;
     39 import android.os.Binder;
     40 import android.os.INetworkManagementService;
     41 import android.os.UserHandle;
     42 import android.provider.Settings;
     43 import android.text.TextUtils;
     44 import android.util.Pair;
     45 import android.util.Slog;
     46 
     47 import com.android.server.connectivity.MockableSystemProperties;
     48 
     49 import java.net.InetAddress;
     50 import java.net.UnknownHostException;
     51 import java.util.Arrays;
     52 import java.util.Collection;
     53 import java.util.Collections;
     54 import java.util.HashMap;
     55 import java.util.HashSet;
     56 import java.util.Iterator;
     57 import java.util.Map;
     58 import java.util.Objects;
     59 import java.util.stream.Collectors;
     60 import java.util.Set;
     61 import 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  */
    117 public 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