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