1 /* 2 * Copyright (C) 2014 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.CaptivePortal.APP_RETURN_DISMISSED; 20 import static android.net.CaptivePortal.APP_RETURN_UNWANTED; 21 import static android.net.CaptivePortal.APP_RETURN_WANTED_AS_IS; 22 import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_PROBE_SPEC; 23 import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL; 24 import static android.net.ConnectivityManager.TYPE_MOBILE; 25 import static android.net.ConnectivityManager.TYPE_WIFI; 26 import static android.net.DnsResolver.FLAG_EMPTY; 27 import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_INVALID; 28 import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY; 29 import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_VALID; 30 import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_DNS; 31 import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_FALLBACK; 32 import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTP; 33 import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTPS; 34 import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_PRIVDNS; 35 import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL; 36 import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID; 37 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; 38 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; 39 import static android.net.NetworkCapabilities.TRANSPORT_WIFI; 40 import static android.net.captiveportal.CaptivePortalProbeSpec.parseCaptivePortalProbeSpecs; 41 import static android.net.metrics.ValidationProbeEvent.DNS_FAILURE; 42 import static android.net.metrics.ValidationProbeEvent.DNS_SUCCESS; 43 import static android.net.metrics.ValidationProbeEvent.PROBE_FALLBACK; 44 import static android.net.metrics.ValidationProbeEvent.PROBE_PRIVDNS; 45 import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD; 46 import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_EVALUATION_TYPE; 47 import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_MIN_EVALUATE_INTERVAL; 48 import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_VALID_DNS_TIME_THRESHOLD; 49 import static android.net.util.DataStallUtils.DATA_STALL_EVALUATION_TYPE_DNS; 50 import static android.net.util.DataStallUtils.DEFAULT_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD; 51 import static android.net.util.DataStallUtils.DEFAULT_DATA_STALL_EVALUATION_TYPES; 52 import static android.net.util.DataStallUtils.DEFAULT_DATA_STALL_MIN_EVALUATE_TIME_MS; 53 import static android.net.util.DataStallUtils.DEFAULT_DATA_STALL_VALID_DNS_TIME_THRESHOLD_MS; 54 import static android.net.util.DataStallUtils.DEFAULT_DNS_LOG_SIZE; 55 import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS; 56 import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_FALLBACK_URL; 57 import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_HTTPS_URL; 58 import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_HTTP_URL; 59 import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_MODE; 60 import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_MODE_IGNORE; 61 import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_MODE_PROMPT; 62 import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_OTHER_FALLBACK_URLS; 63 import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_USER_AGENT; 64 import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_USE_HTTPS; 65 import static android.net.util.NetworkStackUtils.NAMESPACE_CONNECTIVITY; 66 import static android.net.util.NetworkStackUtils.isEmpty; 67 68 import static com.android.networkstack.util.DnsUtils.TYPE_ADDRCONFIG; 69 70 import android.annotation.NonNull; 71 import android.annotation.Nullable; 72 import android.app.PendingIntent; 73 import android.content.BroadcastReceiver; 74 import android.content.Context; 75 import android.content.Intent; 76 import android.content.IntentFilter; 77 import android.content.res.Resources; 78 import android.net.ConnectivityManager; 79 import android.net.DnsResolver; 80 import android.net.INetworkMonitorCallbacks; 81 import android.net.LinkProperties; 82 import android.net.Network; 83 import android.net.NetworkCapabilities; 84 import android.net.ProxyInfo; 85 import android.net.TrafficStats; 86 import android.net.Uri; 87 import android.net.captiveportal.CaptivePortalProbeResult; 88 import android.net.captiveportal.CaptivePortalProbeSpec; 89 import android.net.metrics.IpConnectivityLog; 90 import android.net.metrics.NetworkEvent; 91 import android.net.metrics.ValidationProbeEvent; 92 import android.net.shared.NetworkMonitorUtils; 93 import android.net.shared.PrivateDnsConfig; 94 import android.net.util.NetworkStackUtils; 95 import android.net.util.SharedLog; 96 import android.net.util.Stopwatch; 97 import android.net.wifi.WifiInfo; 98 import android.net.wifi.WifiManager; 99 import android.os.Build; 100 import android.os.Bundle; 101 import android.os.Message; 102 import android.os.RemoteException; 103 import android.os.SystemClock; 104 import android.os.UserHandle; 105 import android.provider.Settings; 106 import android.telephony.AccessNetworkConstants; 107 import android.telephony.CellSignalStrength; 108 import android.telephony.NetworkRegistrationInfo; 109 import android.telephony.ServiceState; 110 import android.telephony.SignalStrength; 111 import android.telephony.TelephonyManager; 112 import android.text.TextUtils; 113 import android.util.Log; 114 import android.util.Pair; 115 116 import androidx.annotation.ArrayRes; 117 import androidx.annotation.StringRes; 118 119 import com.android.internal.annotations.VisibleForTesting; 120 import com.android.internal.util.RingBufferIndices; 121 import com.android.internal.util.State; 122 import com.android.internal.util.StateMachine; 123 import com.android.internal.util.TrafficStatsConstants; 124 import com.android.networkstack.R; 125 import com.android.networkstack.metrics.DataStallDetectionStats; 126 import com.android.networkstack.metrics.DataStallStatsUtils; 127 import com.android.networkstack.util.DnsUtils; 128 129 import java.io.IOException; 130 import java.net.HttpURLConnection; 131 import java.net.InetAddress; 132 import java.net.MalformedURLException; 133 import java.net.URL; 134 import java.net.UnknownHostException; 135 import java.util.ArrayList; 136 import java.util.Arrays; 137 import java.util.Collections; 138 import java.util.LinkedHashMap; 139 import java.util.List; 140 import java.util.Random; 141 import java.util.UUID; 142 import java.util.concurrent.CountDownLatch; 143 import java.util.concurrent.TimeUnit; 144 import java.util.function.Function; 145 146 /** 147 * {@hide} 148 */ 149 public class NetworkMonitor extends StateMachine { 150 private static final String TAG = NetworkMonitor.class.getSimpleName(); 151 private static final boolean DBG = true; 152 private static final boolean VDBG = false; 153 private static final boolean VDBG_STALL = Log.isLoggable(TAG, Log.DEBUG); 154 private static final String DEFAULT_USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) " 155 + "AppleWebKit/537.36 (KHTML, like Gecko) " 156 + "Chrome/60.0.3112.32 Safari/537.36"; 157 158 @VisibleForTesting 159 static final String CONFIG_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT = 160 "captive_portal_dns_probe_timeout"; 161 162 private static final int SOCKET_TIMEOUT_MS = 10000; 163 private static final int PROBE_TIMEOUT_MS = 3000; 164 165 enum EvaluationResult { 166 VALIDATED(true), 167 CAPTIVE_PORTAL(false); 168 final boolean mIsValidated; EvaluationResult(boolean isValidated)169 EvaluationResult(boolean isValidated) { 170 this.mIsValidated = isValidated; 171 } 172 } 173 174 enum ValidationStage { 175 FIRST_VALIDATION(true), 176 REVALIDATION(false); 177 final boolean mIsFirstValidation; ValidationStage(boolean isFirstValidation)178 ValidationStage(boolean isFirstValidation) { 179 this.mIsFirstValidation = isFirstValidation; 180 } 181 } 182 183 /** 184 * ConnectivityService has sent a notification to indicate that network has connected. 185 * Initiates Network Validation. 186 */ 187 private static final int CMD_NETWORK_CONNECTED = 1; 188 189 /** 190 * Message to self indicating it's time to evaluate a network's connectivity. 191 * arg1 = Token to ignore old messages. 192 */ 193 private static final int CMD_REEVALUATE = 6; 194 195 /** 196 * ConnectivityService has sent a notification to indicate that network has disconnected. 197 */ 198 private static final int CMD_NETWORK_DISCONNECTED = 7; 199 200 /** 201 * Force evaluation even if it has succeeded in the past. 202 * arg1 = UID responsible for requesting this reeval. Will be billed for data. 203 */ 204 private static final int CMD_FORCE_REEVALUATION = 8; 205 206 /** 207 * Message to self indicating captive portal app finished. 208 * arg1 = one of: APP_RETURN_DISMISSED, 209 * APP_RETURN_UNWANTED, 210 * APP_RETURN_WANTED_AS_IS 211 * obj = mCaptivePortalLoggedInResponseToken as String 212 */ 213 private static final int CMD_CAPTIVE_PORTAL_APP_FINISHED = 9; 214 215 /** 216 * Message indicating sign-in app should be launched. 217 * Sent by mLaunchCaptivePortalAppBroadcastReceiver when the 218 * user touches the sign in notification, or sent by 219 * ConnectivityService when the user touches the "sign into 220 * network" button in the wifi access point detail page. 221 */ 222 private static final int CMD_LAUNCH_CAPTIVE_PORTAL_APP = 11; 223 224 /** 225 * Retest network to see if captive portal is still in place. 226 * arg1 = UID responsible for requesting this reeval. Will be billed for data. 227 * 0 indicates self-initiated, so nobody to blame. 228 */ 229 private static final int CMD_CAPTIVE_PORTAL_RECHECK = 12; 230 231 /** 232 * ConnectivityService notifies NetworkMonitor of settings changes to 233 * Private DNS. If a DNS resolution is required, e.g. for DNS-over-TLS in 234 * strict mode, then an event is sent back to ConnectivityService with the 235 * result of the resolution attempt. 236 * 237 * A separate message is used to trigger (re)evaluation of the Private DNS 238 * configuration, so that the message can be handled as needed in different 239 * states, including being ignored until after an ongoing captive portal 240 * validation phase is completed. 241 */ 242 private static final int CMD_PRIVATE_DNS_SETTINGS_CHANGED = 13; 243 private static final int CMD_EVALUATE_PRIVATE_DNS = 15; 244 245 /** 246 * Message to self indicating captive portal detection is completed. 247 * obj = CaptivePortalProbeResult for detection result; 248 */ 249 private static final int CMD_PROBE_COMPLETE = 16; 250 251 /** 252 * ConnectivityService notifies NetworkMonitor of DNS query responses event. 253 * arg1 = returncode in OnDnsEvent which indicates the response code for the DNS query. 254 */ 255 private static final int EVENT_DNS_NOTIFICATION = 17; 256 257 /** 258 * ConnectivityService notifies NetworkMonitor that the user accepts partial connectivity and 259 * NetworkMonitor should ignore the https probe. 260 */ 261 private static final int EVENT_ACCEPT_PARTIAL_CONNECTIVITY = 18; 262 263 /** 264 * ConnectivityService notifies NetworkMonitor of changed LinkProperties. 265 * obj = new LinkProperties. 266 */ 267 private static final int EVENT_LINK_PROPERTIES_CHANGED = 19; 268 269 /** 270 * ConnectivityService notifies NetworkMonitor of changed NetworkCapabilities. 271 * obj = new NetworkCapabilities. 272 */ 273 private static final int EVENT_NETWORK_CAPABILITIES_CHANGED = 20; 274 275 // Start mReevaluateDelayMs at this value and double. 276 private static final int INITIAL_REEVALUATE_DELAY_MS = 1000; 277 private static final int MAX_REEVALUATE_DELAY_MS = 10 * 60 * 1000; 278 // Before network has been evaluated this many times, ignore repeated reevaluate requests. 279 private static final int IGNORE_REEVALUATE_ATTEMPTS = 5; 280 private int mReevaluateToken = 0; 281 private static final int NO_UID = 0; 282 private static final int INVALID_UID = -1; 283 private int mUidResponsibleForReeval = INVALID_UID; 284 // Stop blaming UID that requested re-evaluation after this many attempts. 285 private static final int BLAME_FOR_EVALUATION_ATTEMPTS = 5; 286 // Delay between reevaluations once a captive portal has been found. 287 private static final int CAPTIVE_PORTAL_REEVALUATE_DELAY_MS = 10 * 60 * 1000; 288 private static final int NETWORK_VALIDATION_RESULT_INVALID = 0; 289 private String mPrivateDnsProviderHostname = ""; 290 291 private final Context mContext; 292 private final INetworkMonitorCallbacks mCallback; 293 private final int mCallbackVersion; 294 private final Network mCleartextDnsNetwork; 295 private final Network mNetwork; 296 private final TelephonyManager mTelephonyManager; 297 private final WifiManager mWifiManager; 298 private final ConnectivityManager mCm; 299 private final IpConnectivityLog mMetricsLog; 300 private final Dependencies mDependencies; 301 private final DataStallStatsUtils mDetectionStatsUtils; 302 303 // Configuration values for captive portal detection probes. 304 private final String mCaptivePortalUserAgent; 305 private final URL mCaptivePortalHttpsUrl; 306 private final URL mCaptivePortalHttpUrl; 307 private final URL[] mCaptivePortalFallbackUrls; 308 @Nullable 309 private final CaptivePortalProbeSpec[] mCaptivePortalFallbackSpecs; 310 311 private NetworkCapabilities mNetworkCapabilities; 312 private LinkProperties mLinkProperties; 313 314 @VisibleForTesting 315 protected boolean mIsCaptivePortalCheckEnabled; 316 317 private boolean mUseHttps; 318 // The total number of captive portal detection attempts for this NetworkMonitor instance. 319 private int mValidations = 0; 320 321 // Set if the user explicitly selected "Do not use this network" in captive portal sign-in app. 322 private boolean mUserDoesNotWant = false; 323 // Avoids surfacing "Sign in to network" notification. 324 private boolean mDontDisplaySigninNotification = false; 325 326 private final State mDefaultState = new DefaultState(); 327 private final State mValidatedState = new ValidatedState(); 328 private final State mMaybeNotifyState = new MaybeNotifyState(); 329 private final State mEvaluatingState = new EvaluatingState(); 330 private final State mCaptivePortalState = new CaptivePortalState(); 331 private final State mEvaluatingPrivateDnsState = new EvaluatingPrivateDnsState(); 332 private final State mProbingState = new ProbingState(); 333 private final State mWaitingForNextProbeState = new WaitingForNextProbeState(); 334 335 private CustomIntentReceiver mLaunchCaptivePortalAppBroadcastReceiver = null; 336 337 private final SharedLog mValidationLogs; 338 339 private final Stopwatch mEvaluationTimer = new Stopwatch(); 340 341 // This variable is set before transitioning to the mCaptivePortalState. 342 private CaptivePortalProbeResult mLastPortalProbeResult = CaptivePortalProbeResult.FAILED; 343 344 // Random generator to select fallback URL index 345 private final Random mRandom; 346 private int mNextFallbackUrlIndex = 0; 347 348 349 private int mReevaluateDelayMs = INITIAL_REEVALUATE_DELAY_MS; 350 private int mEvaluateAttempts = 0; 351 private volatile int mProbeToken = 0; 352 private final int mConsecutiveDnsTimeoutThreshold; 353 private final int mDataStallMinEvaluateTime; 354 private final int mDataStallValidDnsTimeThreshold; 355 private final int mDataStallEvaluationType; 356 private final DnsStallDetector mDnsStallDetector; 357 private long mLastProbeTime; 358 // Set to true if data stall is suspected and reset to false after metrics are sent to statsd. 359 private boolean mCollectDataStallMetrics; 360 private boolean mAcceptPartialConnectivity = false; 361 private final EvaluationState mEvaluationState = new EvaluationState(); 362 getCallbackVersion(INetworkMonitorCallbacks cb)363 private int getCallbackVersion(INetworkMonitorCallbacks cb) { 364 int version; 365 try { 366 version = cb.getInterfaceVersion(); 367 } catch (RemoteException e) { 368 version = 0; 369 } 370 if (version == Build.VERSION_CODES.CUR_DEVELOPMENT) version = 0; 371 return version; 372 } 373 NetworkMonitor(Context context, INetworkMonitorCallbacks cb, Network network, SharedLog validationLog)374 public NetworkMonitor(Context context, INetworkMonitorCallbacks cb, Network network, 375 SharedLog validationLog) { 376 this(context, cb, network, new IpConnectivityLog(), validationLog, 377 Dependencies.DEFAULT, new DataStallStatsUtils()); 378 } 379 380 @VisibleForTesting NetworkMonitor(Context context, INetworkMonitorCallbacks cb, Network network, IpConnectivityLog logger, SharedLog validationLogs, Dependencies deps, DataStallStatsUtils detectionStatsUtils)381 protected NetworkMonitor(Context context, INetworkMonitorCallbacks cb, Network network, 382 IpConnectivityLog logger, SharedLog validationLogs, 383 Dependencies deps, DataStallStatsUtils detectionStatsUtils) { 384 // Add suffix indicating which NetworkMonitor we're talking about. 385 super(TAG + "/" + network.toString()); 386 387 // Logs with a tag of the form given just above, e.g. 388 // <timestamp> 862 2402 D NetworkMonitor/NetworkAgentInfo [WIFI () - 100]: ... 389 setDbg(VDBG); 390 391 mContext = context; 392 mMetricsLog = logger; 393 mValidationLogs = validationLogs; 394 mCallback = cb; 395 mCallbackVersion = getCallbackVersion(cb); 396 mDependencies = deps; 397 mDetectionStatsUtils = detectionStatsUtils; 398 mNetwork = network; 399 mCleartextDnsNetwork = deps.getPrivateDnsBypassNetwork(network); 400 mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); 401 mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); 402 mCm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 403 404 // CHECKSTYLE:OFF IndentationCheck 405 addState(mDefaultState); 406 addState(mMaybeNotifyState, mDefaultState); 407 addState(mEvaluatingState, mMaybeNotifyState); 408 addState(mProbingState, mEvaluatingState); 409 addState(mWaitingForNextProbeState, mEvaluatingState); 410 addState(mCaptivePortalState, mMaybeNotifyState); 411 addState(mEvaluatingPrivateDnsState, mDefaultState); 412 addState(mValidatedState, mDefaultState); 413 setInitialState(mDefaultState); 414 // CHECKSTYLE:ON IndentationCheck 415 416 mIsCaptivePortalCheckEnabled = getIsCaptivePortalCheckEnabled(); 417 mUseHttps = getUseHttpsValidation(); 418 mCaptivePortalUserAgent = getCaptivePortalUserAgent(); 419 mCaptivePortalHttpsUrl = makeURL(getCaptivePortalServerHttpsUrl()); 420 mCaptivePortalHttpUrl = makeURL(getCaptivePortalServerHttpUrl()); 421 mCaptivePortalFallbackUrls = makeCaptivePortalFallbackUrls(); 422 mCaptivePortalFallbackSpecs = makeCaptivePortalFallbackProbeSpecs(); 423 mRandom = deps.getRandom(); 424 // TODO: Evaluate to move data stall configuration to a specific class. 425 mConsecutiveDnsTimeoutThreshold = getConsecutiveDnsTimeoutThreshold(); 426 mDnsStallDetector = new DnsStallDetector(mConsecutiveDnsTimeoutThreshold); 427 mDataStallMinEvaluateTime = getDataStallMinEvaluateTime(); 428 mDataStallValidDnsTimeThreshold = getDataStallValidDnsTimeThreshold(); 429 mDataStallEvaluationType = getDataStallEvaluationType(); 430 431 // Provide empty LinkProperties and NetworkCapabilities to make sure they are never null, 432 // even before notifyNetworkConnected. 433 mLinkProperties = new LinkProperties(); 434 mNetworkCapabilities = new NetworkCapabilities(null); 435 } 436 437 /** 438 * ConnectivityService notifies NetworkMonitor that the user already accepted partial 439 * connectivity previously, so NetworkMonitor can validate the network even if it has partial 440 * connectivity. 441 */ setAcceptPartialConnectivity()442 public void setAcceptPartialConnectivity() { 443 sendMessage(EVENT_ACCEPT_PARTIAL_CONNECTIVITY); 444 } 445 446 /** 447 * Request the NetworkMonitor to reevaluate the network. 448 */ forceReevaluation(int responsibleUid)449 public void forceReevaluation(int responsibleUid) { 450 sendMessage(CMD_FORCE_REEVALUATION, responsibleUid, 0); 451 } 452 453 /** 454 * Send a notification to NetworkMonitor indicating that there was a DNS query response event. 455 * @param returnCode the DNS return code of the response. 456 */ notifyDnsResponse(int returnCode)457 public void notifyDnsResponse(int returnCode) { 458 sendMessage(EVENT_DNS_NOTIFICATION, returnCode); 459 } 460 461 /** 462 * Send a notification to NetworkMonitor indicating that private DNS settings have changed. 463 * @param newCfg The new private DNS configuration. 464 */ notifyPrivateDnsSettingsChanged(PrivateDnsConfig newCfg)465 public void notifyPrivateDnsSettingsChanged(PrivateDnsConfig newCfg) { 466 // Cancel any outstanding resolutions. 467 removeMessages(CMD_PRIVATE_DNS_SETTINGS_CHANGED); 468 // Send the update to the proper thread. 469 sendMessage(CMD_PRIVATE_DNS_SETTINGS_CHANGED, newCfg); 470 } 471 472 /** 473 * Send a notification to NetworkMonitor indicating that the network is now connected. 474 */ notifyNetworkConnected(LinkProperties lp, NetworkCapabilities nc)475 public void notifyNetworkConnected(LinkProperties lp, NetworkCapabilities nc) { 476 sendMessage(CMD_NETWORK_CONNECTED, new Pair<>( 477 new LinkProperties(lp), new NetworkCapabilities(nc))); 478 } 479 updateConnectedNetworkAttributes(Message connectedMsg)480 private void updateConnectedNetworkAttributes(Message connectedMsg) { 481 final Pair<LinkProperties, NetworkCapabilities> attrs = 482 (Pair<LinkProperties, NetworkCapabilities>) connectedMsg.obj; 483 mLinkProperties = attrs.first; 484 mNetworkCapabilities = attrs.second; 485 } 486 487 /** 488 * Send a notification to NetworkMonitor indicating that the network is now disconnected. 489 */ notifyNetworkDisconnected()490 public void notifyNetworkDisconnected() { 491 sendMessage(CMD_NETWORK_DISCONNECTED); 492 } 493 494 /** 495 * Send a notification to NetworkMonitor indicating that link properties have changed. 496 */ notifyLinkPropertiesChanged(final LinkProperties lp)497 public void notifyLinkPropertiesChanged(final LinkProperties lp) { 498 sendMessage(EVENT_LINK_PROPERTIES_CHANGED, new LinkProperties(lp)); 499 } 500 501 /** 502 * Send a notification to NetworkMonitor indicating that network capabilities have changed. 503 */ notifyNetworkCapabilitiesChanged(final NetworkCapabilities nc)504 public void notifyNetworkCapabilitiesChanged(final NetworkCapabilities nc) { 505 sendMessage(EVENT_NETWORK_CAPABILITIES_CHANGED, new NetworkCapabilities(nc)); 506 } 507 508 /** 509 * Request the captive portal application to be launched. 510 */ launchCaptivePortalApp()511 public void launchCaptivePortalApp() { 512 sendMessage(CMD_LAUNCH_CAPTIVE_PORTAL_APP); 513 } 514 515 /** 516 * Notify that the captive portal app was closed with the provided response code. 517 */ notifyCaptivePortalAppFinished(int response)518 public void notifyCaptivePortalAppFinished(int response) { 519 sendMessage(CMD_CAPTIVE_PORTAL_APP_FINISHED, response); 520 } 521 522 @Override log(String s)523 protected void log(String s) { 524 if (DBG) Log.d(TAG + "/" + mCleartextDnsNetwork.toString(), s); 525 } 526 validationLog(int probeType, Object url, String msg)527 private void validationLog(int probeType, Object url, String msg) { 528 String probeName = ValidationProbeEvent.getProbeName(probeType); 529 validationLog(String.format("%s %s %s", probeName, url, msg)); 530 } 531 validationLog(String s)532 private void validationLog(String s) { 533 if (DBG) log(s); 534 mValidationLogs.log(s); 535 } 536 validationStage()537 private ValidationStage validationStage() { 538 return 0 == mValidations ? ValidationStage.FIRST_VALIDATION : ValidationStage.REVALIDATION; 539 } 540 isValidationRequired()541 private boolean isValidationRequired() { 542 return NetworkMonitorUtils.isValidationRequired(mNetworkCapabilities); 543 } 544 isPrivateDnsValidationRequired()545 private boolean isPrivateDnsValidationRequired() { 546 return NetworkMonitorUtils.isPrivateDnsValidationRequired(mNetworkCapabilities); 547 } 548 notifyNetworkTested(int result, @Nullable String redirectUrl)549 private void notifyNetworkTested(int result, @Nullable String redirectUrl) { 550 try { 551 mCallback.notifyNetworkTested(result, redirectUrl); 552 } catch (RemoteException e) { 553 Log.e(TAG, "Error sending network test result", e); 554 } 555 } 556 showProvisioningNotification(String action)557 private void showProvisioningNotification(String action) { 558 try { 559 mCallback.showProvisioningNotification(action, mContext.getPackageName()); 560 } catch (RemoteException e) { 561 Log.e(TAG, "Error showing provisioning notification", e); 562 } 563 } 564 hideProvisioningNotification()565 private void hideProvisioningNotification() { 566 try { 567 mCallback.hideProvisioningNotification(); 568 } catch (RemoteException e) { 569 Log.e(TAG, "Error hiding provisioning notification", e); 570 } 571 } 572 573 // DefaultState is the parent of all States. It exists only to handle CMD_* messages but 574 // does not entail any real state (hence no enter() or exit() routines). 575 private class DefaultState extends State { 576 @Override processMessage(Message message)577 public boolean processMessage(Message message) { 578 switch (message.what) { 579 case CMD_NETWORK_CONNECTED: 580 updateConnectedNetworkAttributes(message); 581 logNetworkEvent(NetworkEvent.NETWORK_CONNECTED); 582 transitionTo(mEvaluatingState); 583 return HANDLED; 584 case CMD_NETWORK_DISCONNECTED: 585 logNetworkEvent(NetworkEvent.NETWORK_DISCONNECTED); 586 quit(); 587 return HANDLED; 588 case CMD_FORCE_REEVALUATION: 589 case CMD_CAPTIVE_PORTAL_RECHECK: 590 final int dnsCount = mDnsStallDetector.getConsecutiveTimeoutCount(); 591 validationLog("Forcing reevaluation for UID " + message.arg1 592 + ". Dns signal count: " + dnsCount); 593 mUidResponsibleForReeval = message.arg1; 594 transitionTo(mEvaluatingState); 595 return HANDLED; 596 case CMD_CAPTIVE_PORTAL_APP_FINISHED: 597 log("CaptivePortal App responded with " + message.arg1); 598 599 // If the user has seen and acted on a captive portal notification, and the 600 // captive portal app is now closed, disable HTTPS probes. This avoids the 601 // following pathological situation: 602 // 603 // 1. HTTP probe returns a captive portal, HTTPS probe fails or times out. 604 // 2. User opens the app and logs into the captive portal. 605 // 3. HTTP starts working, but HTTPS still doesn't work for some other reason - 606 // perhaps due to the network blocking HTTPS? 607 // 608 // In this case, we'll fail to validate the network even after the app is 609 // dismissed. There is now no way to use this network, because the app is now 610 // gone, so the user cannot select "Use this network as is". 611 mUseHttps = false; 612 613 switch (message.arg1) { 614 case APP_RETURN_DISMISSED: 615 sendMessage(CMD_FORCE_REEVALUATION, NO_UID, 0); 616 break; 617 case APP_RETURN_WANTED_AS_IS: 618 mDontDisplaySigninNotification = true; 619 // TODO: Distinguish this from a network that actually validates. 620 // Displaying the "x" on the system UI icon may still be a good idea. 621 transitionTo(mEvaluatingPrivateDnsState); 622 break; 623 case APP_RETURN_UNWANTED: 624 mDontDisplaySigninNotification = true; 625 mUserDoesNotWant = true; 626 mEvaluationState.reportEvaluationResult( 627 NETWORK_VALIDATION_RESULT_INVALID, null); 628 // TODO: Should teardown network. 629 mUidResponsibleForReeval = 0; 630 transitionTo(mEvaluatingState); 631 break; 632 } 633 return HANDLED; 634 case CMD_PRIVATE_DNS_SETTINGS_CHANGED: { 635 final PrivateDnsConfig cfg = (PrivateDnsConfig) message.obj; 636 if (!isPrivateDnsValidationRequired() || cfg == null || !cfg.inStrictMode()) { 637 // No DNS resolution required. 638 // 639 // We don't force any validation in opportunistic mode 640 // here. Opportunistic mode nameservers are validated 641 // separately within netd. 642 // 643 // Reset Private DNS settings state. 644 mPrivateDnsProviderHostname = ""; 645 break; 646 } 647 648 mPrivateDnsProviderHostname = cfg.hostname; 649 650 // DNS resolutions via Private DNS strict mode block for a 651 // few seconds (~4.2) checking for any IP addresses to 652 // arrive and validate. Initiating a (re)evaluation now 653 // should not significantly alter the validation outcome. 654 // 655 // No matter what: enqueue a validation request; one of 656 // three things can happen with this request: 657 // [1] ignored (EvaluatingState or CaptivePortalState) 658 // [2] transition to EvaluatingPrivateDnsState 659 // (DefaultState and ValidatedState) 660 // [3] handled (EvaluatingPrivateDnsState) 661 // 662 // The Private DNS configuration to be evaluated will: 663 // [1] be skipped (not in strict mode), or 664 // [2] validate (huzzah), or 665 // [3] encounter some problem (invalid hostname, 666 // no resolved IP addresses, IPs unreachable, 667 // port 853 unreachable, port 853 is not running a 668 // DNS-over-TLS server, et cetera). 669 sendMessage(CMD_EVALUATE_PRIVATE_DNS); 670 break; 671 } 672 case EVENT_DNS_NOTIFICATION: 673 mDnsStallDetector.accumulateConsecutiveDnsTimeoutCount(message.arg1); 674 break; 675 // Set mAcceptPartialConnectivity to true and if network start evaluating or 676 // re-evaluating and get the result of partial connectivity, ProbingState will 677 // disable HTTPS probe and transition to EvaluatingPrivateDnsState. 678 case EVENT_ACCEPT_PARTIAL_CONNECTIVITY: 679 maybeDisableHttpsProbing(true /* acceptPartial */); 680 break; 681 case EVENT_LINK_PROPERTIES_CHANGED: 682 mLinkProperties = (LinkProperties) message.obj; 683 break; 684 case EVENT_NETWORK_CAPABILITIES_CHANGED: 685 mNetworkCapabilities = (NetworkCapabilities) message.obj; 686 break; 687 default: 688 break; 689 } 690 return HANDLED; 691 } 692 } 693 694 // Being in the ValidatedState State indicates a Network is: 695 // - Successfully validated, or 696 // - Wanted "as is" by the user, or 697 // - Does not satisfy the default NetworkRequest and so validation has been skipped. 698 private class ValidatedState extends State { 699 @Override enter()700 public void enter() { 701 maybeLogEvaluationResult( 702 networkEventType(validationStage(), EvaluationResult.VALIDATED)); 703 // If the user has accepted partial connectivity and HTTPS probing is disabled, then 704 // mark the network as validated and partial so that settings can keep informing the 705 // user that the connection is limited. 706 int result = NETWORK_VALIDATION_RESULT_VALID; 707 if (!mUseHttps && mAcceptPartialConnectivity) { 708 result |= NETWORK_VALIDATION_RESULT_PARTIAL; 709 } 710 mEvaluationState.reportEvaluationResult(result, null /* redirectUrl */); 711 mValidations++; 712 } 713 714 @Override processMessage(Message message)715 public boolean processMessage(Message message) { 716 switch (message.what) { 717 case CMD_NETWORK_CONNECTED: 718 updateConnectedNetworkAttributes(message); 719 transitionTo(mValidatedState); 720 break; 721 case CMD_EVALUATE_PRIVATE_DNS: 722 transitionTo(mEvaluatingPrivateDnsState); 723 break; 724 case EVENT_DNS_NOTIFICATION: 725 mDnsStallDetector.accumulateConsecutiveDnsTimeoutCount(message.arg1); 726 if (isDataStall()) { 727 mCollectDataStallMetrics = true; 728 validationLog("Suspecting data stall, reevaluate"); 729 transitionTo(mEvaluatingState); 730 } 731 break; 732 default: 733 return NOT_HANDLED; 734 } 735 return HANDLED; 736 } 737 } 738 writeDataStallStats(@onNull final CaptivePortalProbeResult result)739 private void writeDataStallStats(@NonNull final CaptivePortalProbeResult result) { 740 /* 741 * Collect data stall detection level information for each transport type. Collect type 742 * specific information for cellular and wifi only currently. Generate 743 * DataStallDetectionStats for each transport type. E.g., if a network supports both 744 * TRANSPORT_WIFI and TRANSPORT_VPN, two DataStallDetectionStats will be generated. 745 */ 746 final int[] transports = mNetworkCapabilities.getTransportTypes(); 747 748 for (int i = 0; i < transports.length; i++) { 749 DataStallStatsUtils.write(buildDataStallDetectionStats(transports[i]), result); 750 } 751 mCollectDataStallMetrics = false; 752 } 753 754 @VisibleForTesting buildDataStallDetectionStats(int transport)755 protected DataStallDetectionStats buildDataStallDetectionStats(int transport) { 756 final DataStallDetectionStats.Builder stats = new DataStallDetectionStats.Builder(); 757 if (VDBG_STALL) log("collectDataStallMetrics: type=" + transport); 758 stats.setEvaluationType(DATA_STALL_EVALUATION_TYPE_DNS); 759 stats.setNetworkType(transport); 760 switch (transport) { 761 case NetworkCapabilities.TRANSPORT_WIFI: 762 // TODO: Update it if status query in dual wifi is supported. 763 final WifiInfo wifiInfo = mWifiManager.getConnectionInfo(); 764 stats.setWiFiData(wifiInfo); 765 break; 766 case NetworkCapabilities.TRANSPORT_CELLULAR: 767 final boolean isRoaming = !mNetworkCapabilities.hasCapability( 768 NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING); 769 final SignalStrength ss = mTelephonyManager.getSignalStrength(); 770 // TODO(b/120452078): Support multi-sim. 771 stats.setCellData( 772 mTelephonyManager.getDataNetworkType(), 773 isRoaming, 774 mTelephonyManager.getNetworkOperator(), 775 mTelephonyManager.getSimOperator(), 776 (ss != null) 777 ? ss.getLevel() : CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN); 778 break; 779 default: 780 // No transport type specific information for the other types. 781 break; 782 } 783 addDnsEvents(stats); 784 785 return stats.build(); 786 } 787 788 @VisibleForTesting addDnsEvents(@onNull final DataStallDetectionStats.Builder stats)789 protected void addDnsEvents(@NonNull final DataStallDetectionStats.Builder stats) { 790 final int size = mDnsStallDetector.mResultIndices.size(); 791 for (int i = 1; i <= DEFAULT_DNS_LOG_SIZE && i <= size; i++) { 792 final int index = mDnsStallDetector.mResultIndices.indexOf(size - i); 793 stats.addDnsEvent(mDnsStallDetector.mDnsEvents[index].mReturnCode, 794 mDnsStallDetector.mDnsEvents[index].mTimeStamp); 795 } 796 } 797 798 799 // Being in the MaybeNotifyState State indicates the user may have been notified that sign-in 800 // is required. This State takes care to clear the notification upon exit from the State. 801 private class MaybeNotifyState extends State { 802 @Override processMessage(Message message)803 public boolean processMessage(Message message) { 804 switch (message.what) { 805 case CMD_LAUNCH_CAPTIVE_PORTAL_APP: 806 final Bundle appExtras = new Bundle(); 807 // OneAddressPerFamilyNetwork is not parcelable across processes. 808 final Network network = new Network(mCleartextDnsNetwork); 809 appExtras.putParcelable(ConnectivityManager.EXTRA_NETWORK, network); 810 final CaptivePortalProbeResult probeRes = mLastPortalProbeResult; 811 appExtras.putString(EXTRA_CAPTIVE_PORTAL_URL, probeRes.detectUrl); 812 if (probeRes.probeSpec != null) { 813 final String encodedSpec = probeRes.probeSpec.getEncodedSpec(); 814 appExtras.putString(EXTRA_CAPTIVE_PORTAL_PROBE_SPEC, encodedSpec); 815 } 816 appExtras.putString(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_USER_AGENT, 817 mCaptivePortalUserAgent); 818 mCm.startCaptivePortalApp(network, appExtras); 819 return HANDLED; 820 default: 821 return NOT_HANDLED; 822 } 823 } 824 825 @Override exit()826 public void exit() { 827 if (mLaunchCaptivePortalAppBroadcastReceiver != null) { 828 mContext.unregisterReceiver(mLaunchCaptivePortalAppBroadcastReceiver); 829 mLaunchCaptivePortalAppBroadcastReceiver = null; 830 } 831 hideProvisioningNotification(); 832 } 833 } 834 835 // Being in the EvaluatingState State indicates the Network is being evaluated for internet 836 // connectivity, or that the user has indicated that this network is unwanted. 837 private class EvaluatingState extends State { 838 @Override enter()839 public void enter() { 840 // If we have already started to track time spent in EvaluatingState 841 // don't reset the timer due simply to, say, commands or events that 842 // cause us to exit and re-enter EvaluatingState. 843 if (!mEvaluationTimer.isStarted()) { 844 mEvaluationTimer.start(); 845 } 846 sendMessage(CMD_REEVALUATE, ++mReevaluateToken, 0); 847 if (mUidResponsibleForReeval != INVALID_UID) { 848 TrafficStats.setThreadStatsUid(mUidResponsibleForReeval); 849 mUidResponsibleForReeval = INVALID_UID; 850 } 851 mReevaluateDelayMs = INITIAL_REEVALUATE_DELAY_MS; 852 mEvaluateAttempts = 0; 853 // Reset all current probe results to zero, but retain current validation state until 854 // validation succeeds or fails. 855 mEvaluationState.clearProbeResults(); 856 } 857 858 @Override processMessage(Message message)859 public boolean processMessage(Message message) { 860 switch (message.what) { 861 case CMD_REEVALUATE: 862 if (message.arg1 != mReevaluateToken || mUserDoesNotWant) { 863 return HANDLED; 864 } 865 // Don't bother validating networks that don't satisfy the default request. 866 // This includes: 867 // - VPNs which can be considered explicitly desired by the user and the 868 // user's desire trumps whether the network validates. 869 // - Networks that don't provide Internet access. It's unclear how to 870 // validate such networks. 871 // - Untrusted networks. It's unsafe to prompt the user to sign-in to 872 // such networks and the user didn't express interest in connecting to 873 // such networks (an app did) so the user may be unhappily surprised when 874 // asked to sign-in to a network they didn't want to connect to in the 875 // first place. Validation could be done to adjust the network scores 876 // however these networks are app-requested and may not be intended for 877 // general usage, in which case general validation may not be an accurate 878 // measure of the network's quality. Only the app knows how to evaluate 879 // the network so don't bother validating here. Furthermore sending HTTP 880 // packets over the network may be undesirable, for example an extremely 881 // expensive metered network, or unwanted leaking of the User Agent string. 882 // 883 // On networks that need to support private DNS in strict mode (e.g., VPNs, but 884 // not networks that don't provide Internet access), we still need to perform 885 // private DNS server resolution. 886 if (!isValidationRequired()) { 887 if (isPrivateDnsValidationRequired()) { 888 validationLog("Network would not satisfy default request, " 889 + "resolving private DNS"); 890 transitionTo(mEvaluatingPrivateDnsState); 891 } else { 892 validationLog("Network would not satisfy default request, " 893 + "not validating"); 894 transitionTo(mValidatedState); 895 } 896 return HANDLED; 897 } 898 mEvaluateAttempts++; 899 900 transitionTo(mProbingState); 901 return HANDLED; 902 case CMD_FORCE_REEVALUATION: 903 // Before IGNORE_REEVALUATE_ATTEMPTS attempts are made, 904 // ignore any re-evaluation requests. After, restart the 905 // evaluation process via EvaluatingState#enter. 906 return (mEvaluateAttempts < IGNORE_REEVALUATE_ATTEMPTS) ? HANDLED : NOT_HANDLED; 907 // Disable HTTPS probe and transition to EvaluatingPrivateDnsState because: 908 // 1. Network is connected and finish the network validation. 909 // 2. NetworkMonitor detects network is partial connectivity and user accepts it. 910 case EVENT_ACCEPT_PARTIAL_CONNECTIVITY: 911 maybeDisableHttpsProbing(true /* acceptPartial */); 912 transitionTo(mEvaluatingPrivateDnsState); 913 return HANDLED; 914 default: 915 return NOT_HANDLED; 916 } 917 } 918 919 @Override exit()920 public void exit() { 921 TrafficStats.clearThreadStatsUid(); 922 } 923 } 924 925 // BroadcastReceiver that waits for a particular Intent and then posts a message. 926 private class CustomIntentReceiver extends BroadcastReceiver { 927 private final int mToken; 928 private final int mWhat; 929 private final String mAction; CustomIntentReceiver(String action, int token, int what)930 CustomIntentReceiver(String action, int token, int what) { 931 mToken = token; 932 mWhat = what; 933 mAction = action + "_" + mCleartextDnsNetwork.getNetworkHandle() + "_" + token; 934 mContext.registerReceiver(this, new IntentFilter(mAction)); 935 } getPendingIntent()936 public PendingIntent getPendingIntent() { 937 final Intent intent = new Intent(mAction); 938 intent.setPackage(mContext.getPackageName()); 939 return PendingIntent.getBroadcast(mContext, 0, intent, 0); 940 } 941 @Override onReceive(Context context, Intent intent)942 public void onReceive(Context context, Intent intent) { 943 if (intent.getAction().equals(mAction)) sendMessage(obtainMessage(mWhat, mToken)); 944 } 945 } 946 947 // Being in the CaptivePortalState State indicates a captive portal was detected and the user 948 // has been shown a notification to sign-in. 949 private class CaptivePortalState extends State { 950 private static final String ACTION_LAUNCH_CAPTIVE_PORTAL_APP = 951 "android.net.netmon.launchCaptivePortalApp"; 952 953 @Override enter()954 public void enter() { 955 maybeLogEvaluationResult( 956 networkEventType(validationStage(), EvaluationResult.CAPTIVE_PORTAL)); 957 // Don't annoy user with sign-in notifications. 958 if (mDontDisplaySigninNotification) return; 959 // Create a CustomIntentReceiver that sends us a 960 // CMD_LAUNCH_CAPTIVE_PORTAL_APP message when the user 961 // touches the notification. 962 if (mLaunchCaptivePortalAppBroadcastReceiver == null) { 963 // Wait for result. 964 mLaunchCaptivePortalAppBroadcastReceiver = new CustomIntentReceiver( 965 ACTION_LAUNCH_CAPTIVE_PORTAL_APP, new Random().nextInt(), 966 CMD_LAUNCH_CAPTIVE_PORTAL_APP); 967 // Display the sign in notification. 968 // Only do this once for every time we enter MaybeNotifyState. b/122164725 969 showProvisioningNotification(mLaunchCaptivePortalAppBroadcastReceiver.mAction); 970 } 971 // Retest for captive portal occasionally. 972 sendMessageDelayed(CMD_CAPTIVE_PORTAL_RECHECK, 0 /* no UID */, 973 CAPTIVE_PORTAL_REEVALUATE_DELAY_MS); 974 mValidations++; 975 } 976 977 @Override exit()978 public void exit() { 979 removeMessages(CMD_CAPTIVE_PORTAL_RECHECK); 980 } 981 } 982 983 private class EvaluatingPrivateDnsState extends State { 984 private int mPrivateDnsReevalDelayMs; 985 private PrivateDnsConfig mPrivateDnsConfig; 986 987 @Override enter()988 public void enter() { 989 mPrivateDnsReevalDelayMs = INITIAL_REEVALUATE_DELAY_MS; 990 mPrivateDnsConfig = null; 991 sendMessage(CMD_EVALUATE_PRIVATE_DNS); 992 } 993 994 @Override processMessage(Message msg)995 public boolean processMessage(Message msg) { 996 switch (msg.what) { 997 case CMD_EVALUATE_PRIVATE_DNS: 998 if (inStrictMode()) { 999 if (!isStrictModeHostnameResolved()) { 1000 resolveStrictModeHostname(); 1001 1002 if (isStrictModeHostnameResolved()) { 1003 notifyPrivateDnsConfigResolved(); 1004 } else { 1005 handlePrivateDnsEvaluationFailure(); 1006 break; 1007 } 1008 } 1009 1010 // Look up a one-time hostname, to bypass caching. 1011 // 1012 // Note that this will race with ConnectivityService 1013 // code programming the DNS-over-TLS server IP addresses 1014 // into netd (if invoked, above). If netd doesn't know 1015 // the IP addresses yet, or if the connections to the IP 1016 // addresses haven't yet been validated, netd will block 1017 // for up to a few seconds before failing the lookup. 1018 if (!sendPrivateDnsProbe()) { 1019 handlePrivateDnsEvaluationFailure(); 1020 break; 1021 } 1022 } 1023 1024 // All good! 1025 transitionTo(mValidatedState); 1026 break; 1027 default: 1028 return NOT_HANDLED; 1029 } 1030 return HANDLED; 1031 } 1032 inStrictMode()1033 private boolean inStrictMode() { 1034 return !TextUtils.isEmpty(mPrivateDnsProviderHostname); 1035 } 1036 isStrictModeHostnameResolved()1037 private boolean isStrictModeHostnameResolved() { 1038 return (mPrivateDnsConfig != null) 1039 && mPrivateDnsConfig.hostname.equals(mPrivateDnsProviderHostname) 1040 && (mPrivateDnsConfig.ips.length > 0); 1041 } 1042 resolveStrictModeHostname()1043 private void resolveStrictModeHostname() { 1044 try { 1045 // Do a blocking DNS resolution using the network-assigned nameservers. 1046 final InetAddress[] ips = DnsUtils.getAllByName(mDependencies.getDnsResolver(), 1047 mCleartextDnsNetwork, mPrivateDnsProviderHostname, getDnsProbeTimeout()); 1048 mPrivateDnsConfig = new PrivateDnsConfig(mPrivateDnsProviderHostname, ips); 1049 validationLog("Strict mode hostname resolved: " + mPrivateDnsConfig); 1050 } catch (UnknownHostException uhe) { 1051 mPrivateDnsConfig = null; 1052 validationLog("Strict mode hostname resolution failed: " + uhe.getMessage()); 1053 } 1054 mEvaluationState.noteProbeResult(NETWORK_VALIDATION_PROBE_PRIVDNS, 1055 (mPrivateDnsConfig != null) /* succeeded */); 1056 } 1057 notifyPrivateDnsConfigResolved()1058 private void notifyPrivateDnsConfigResolved() { 1059 try { 1060 mCallback.notifyPrivateDnsConfigResolved(mPrivateDnsConfig.toParcel()); 1061 } catch (RemoteException e) { 1062 Log.e(TAG, "Error sending private DNS config resolved notification", e); 1063 } 1064 } 1065 handlePrivateDnsEvaluationFailure()1066 private void handlePrivateDnsEvaluationFailure() { 1067 mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_INVALID, 1068 null /* redirectUrl */); 1069 // Queue up a re-evaluation with backoff. 1070 // 1071 // TODO: Consider abandoning this state after a few attempts and 1072 // transitioning back to EvaluatingState, to perhaps give ourselves 1073 // the opportunity to (re)detect a captive portal or something. 1074 // 1075 // TODO: distinguish between CMD_EVALUATE_PRIVATE_DNS messages that are caused by server 1076 // lookup failures (which should continue to do exponential backoff) and 1077 // CMD_EVALUATE_PRIVATE_DNS messages that are caused by user reconfiguration (which 1078 // should be processed immediately. 1079 sendMessageDelayed(CMD_EVALUATE_PRIVATE_DNS, mPrivateDnsReevalDelayMs); 1080 mPrivateDnsReevalDelayMs *= 2; 1081 if (mPrivateDnsReevalDelayMs > MAX_REEVALUATE_DELAY_MS) { 1082 mPrivateDnsReevalDelayMs = MAX_REEVALUATE_DELAY_MS; 1083 } 1084 } 1085 sendPrivateDnsProbe()1086 private boolean sendPrivateDnsProbe() { 1087 // q.v. system/netd/server/dns/DnsTlsTransport.cpp 1088 final String oneTimeHostnameSuffix = "-dnsotls-ds.metric.gstatic.com"; 1089 final String host = UUID.randomUUID().toString().substring(0, 8) 1090 + oneTimeHostnameSuffix; 1091 final Stopwatch watch = new Stopwatch().start(); 1092 boolean success = false; 1093 long time; 1094 try { 1095 final InetAddress[] ips = mNetwork.getAllByName(host); 1096 time = watch.stop(); 1097 final String strIps = Arrays.toString(ips); 1098 success = (ips != null && ips.length > 0); 1099 validationLog(PROBE_PRIVDNS, host, String.format("%dms: %s", time, strIps)); 1100 } catch (UnknownHostException uhe) { 1101 time = watch.stop(); 1102 validationLog(PROBE_PRIVDNS, host, 1103 String.format("%dms - Error: %s", time, uhe.getMessage())); 1104 } 1105 logValidationProbe(time, PROBE_PRIVDNS, success ? DNS_SUCCESS : DNS_FAILURE); 1106 mEvaluationState.noteProbeResult(NETWORK_VALIDATION_PROBE_PRIVDNS, success); 1107 return success; 1108 } 1109 } 1110 1111 private class ProbingState extends State { 1112 private Thread mThread; 1113 1114 @Override enter()1115 public void enter() { 1116 if (mEvaluateAttempts >= BLAME_FOR_EVALUATION_ATTEMPTS) { 1117 //Don't continue to blame UID forever. 1118 TrafficStats.clearThreadStatsUid(); 1119 } 1120 1121 final int token = ++mProbeToken; 1122 mThread = new Thread(() -> sendMessage(obtainMessage(CMD_PROBE_COMPLETE, token, 0, 1123 isCaptivePortal()))); 1124 mThread.start(); 1125 } 1126 1127 @Override processMessage(Message message)1128 public boolean processMessage(Message message) { 1129 switch (message.what) { 1130 case CMD_PROBE_COMPLETE: 1131 // Ensure that CMD_PROBE_COMPLETE from stale threads are ignored. 1132 if (message.arg1 != mProbeToken) { 1133 return HANDLED; 1134 } 1135 1136 final CaptivePortalProbeResult probeResult = 1137 (CaptivePortalProbeResult) message.obj; 1138 mLastProbeTime = SystemClock.elapsedRealtime(); 1139 1140 if (mCollectDataStallMetrics) { 1141 writeDataStallStats(probeResult); 1142 } 1143 1144 if (probeResult.isSuccessful()) { 1145 // Transit EvaluatingPrivateDnsState to get to Validated 1146 // state (even if no Private DNS validation required). 1147 transitionTo(mEvaluatingPrivateDnsState); 1148 } else if (probeResult.isPortal()) { 1149 mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_INVALID, 1150 probeResult.redirectUrl); 1151 mLastPortalProbeResult = probeResult; 1152 transitionTo(mCaptivePortalState); 1153 } else if (probeResult.isPartialConnectivity()) { 1154 mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_PARTIAL, 1155 null /* redirectUrl */); 1156 // Check if disable https probing needed. 1157 maybeDisableHttpsProbing(mAcceptPartialConnectivity); 1158 if (mAcceptPartialConnectivity) { 1159 transitionTo(mEvaluatingPrivateDnsState); 1160 } else { 1161 transitionTo(mWaitingForNextProbeState); 1162 } 1163 } else { 1164 logNetworkEvent(NetworkEvent.NETWORK_VALIDATION_FAILED); 1165 mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_INVALID, 1166 null /* redirectUrl */); 1167 transitionTo(mWaitingForNextProbeState); 1168 } 1169 return HANDLED; 1170 case EVENT_DNS_NOTIFICATION: 1171 case EVENT_ACCEPT_PARTIAL_CONNECTIVITY: 1172 // Leave the event to DefaultState. 1173 return NOT_HANDLED; 1174 default: 1175 // Wait for probe result and defer events to next state by default. 1176 deferMessage(message); 1177 return HANDLED; 1178 } 1179 } 1180 1181 @Override exit()1182 public void exit() { 1183 if (mThread.isAlive()) { 1184 mThread.interrupt(); 1185 } 1186 mThread = null; 1187 } 1188 } 1189 1190 // Being in the WaitingForNextProbeState indicates that evaluating probes failed and state is 1191 // transited from ProbingState. This ensures that the state machine is only in ProbingState 1192 // while a probe is in progress, not while waiting to perform the next probe. That allows 1193 // ProbingState to defer most messages until the probe is complete, which keeps the code simple 1194 // and matches the pre-Q behaviour where probes were a blocking operation performed on the state 1195 // machine thread. 1196 private class WaitingForNextProbeState extends State { 1197 @Override enter()1198 public void enter() { 1199 scheduleNextProbe(); 1200 } 1201 scheduleNextProbe()1202 private void scheduleNextProbe() { 1203 final Message msg = obtainMessage(CMD_REEVALUATE, ++mReevaluateToken, 0); 1204 sendMessageDelayed(msg, mReevaluateDelayMs); 1205 mReevaluateDelayMs *= 2; 1206 if (mReevaluateDelayMs > MAX_REEVALUATE_DELAY_MS) { 1207 mReevaluateDelayMs = MAX_REEVALUATE_DELAY_MS; 1208 } 1209 } 1210 1211 @Override processMessage(Message message)1212 public boolean processMessage(Message message) { 1213 return NOT_HANDLED; 1214 } 1215 } 1216 1217 // Limits the list of IP addresses returned by getAllByName or tried by openConnection to at 1218 // most one per address family. This ensures we only wait up to 20 seconds for TCP connections 1219 // to complete, regardless of how many IP addresses a host has. 1220 private static class OneAddressPerFamilyNetwork extends Network { OneAddressPerFamilyNetwork(Network network)1221 OneAddressPerFamilyNetwork(Network network) { 1222 // Always bypass Private DNS. 1223 super(network.getPrivateDnsBypassingCopy()); 1224 } 1225 1226 @Override getAllByName(String host)1227 public InetAddress[] getAllByName(String host) throws UnknownHostException { 1228 final List<InetAddress> addrs = Arrays.asList(super.getAllByName(host)); 1229 1230 // Ensure the address family of the first address is tried first. 1231 LinkedHashMap<Class, InetAddress> addressByFamily = new LinkedHashMap<>(); 1232 addressByFamily.put(addrs.get(0).getClass(), addrs.get(0)); 1233 Collections.shuffle(addrs); 1234 1235 for (InetAddress addr : addrs) { 1236 addressByFamily.put(addr.getClass(), addr); 1237 } 1238 1239 return addressByFamily.values().toArray(new InetAddress[addressByFamily.size()]); 1240 } 1241 } 1242 getIsCaptivePortalCheckEnabled()1243 private boolean getIsCaptivePortalCheckEnabled() { 1244 String symbol = CAPTIVE_PORTAL_MODE; 1245 int defaultValue = CAPTIVE_PORTAL_MODE_PROMPT; 1246 int mode = mDependencies.getSetting(mContext, symbol, defaultValue); 1247 return mode != CAPTIVE_PORTAL_MODE_IGNORE; 1248 } 1249 getUseHttpsValidation()1250 private boolean getUseHttpsValidation() { 1251 return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY, 1252 CAPTIVE_PORTAL_USE_HTTPS, 1) == 1; 1253 } 1254 getCaptivePortalServerHttpsUrl()1255 private String getCaptivePortalServerHttpsUrl() { 1256 return getSettingFromResource(mContext, R.string.config_captive_portal_https_url, 1257 R.string.default_captive_portal_https_url, CAPTIVE_PORTAL_HTTPS_URL); 1258 } 1259 getDnsProbeTimeout()1260 private int getDnsProbeTimeout() { 1261 return getIntSetting(mContext, R.integer.config_captive_portal_dns_probe_timeout, 1262 CONFIG_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT, 1263 R.integer.default_captive_portal_dns_probe_timeout); 1264 } 1265 1266 /** 1267 * Gets an integer setting from resources or device config 1268 * 1269 * configResource is used if set, followed by device config if set, followed by defaultResource. 1270 * If none of these are set then an exception is thrown. 1271 * 1272 * TODO: move to a common location such as a ConfigUtils class. 1273 * TODO(b/130324939): test that the resources can be overlayed by an RRO package. 1274 */ 1275 @VisibleForTesting getIntSetting(@onNull final Context context, @StringRes int configResource, @NonNull String symbol, @StringRes int defaultResource)1276 int getIntSetting(@NonNull final Context context, @StringRes int configResource, 1277 @NonNull String symbol, @StringRes int defaultResource) { 1278 final Resources res = context.getResources(); 1279 try { 1280 return res.getInteger(configResource); 1281 } catch (Resources.NotFoundException e) { 1282 return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY, 1283 symbol, res.getInteger(defaultResource)); 1284 } 1285 } 1286 1287 /** 1288 * Get the captive portal server HTTP URL that is configured on the device. 1289 * 1290 * NetworkMonitor does not use {@link ConnectivityManager#getCaptivePortalServerUrl()} as 1291 * it has its own updatable strategies to detect captive portals. The framework only advises 1292 * on one URL that can be used, while NetworkMonitor may implement more complex logic. 1293 */ getCaptivePortalServerHttpUrl()1294 public String getCaptivePortalServerHttpUrl() { 1295 return getSettingFromResource(mContext, R.string.config_captive_portal_http_url, 1296 R.string.default_captive_portal_http_url, CAPTIVE_PORTAL_HTTP_URL); 1297 } 1298 getConsecutiveDnsTimeoutThreshold()1299 private int getConsecutiveDnsTimeoutThreshold() { 1300 return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY, 1301 CONFIG_DATA_STALL_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD, 1302 DEFAULT_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD); 1303 } 1304 getDataStallMinEvaluateTime()1305 private int getDataStallMinEvaluateTime() { 1306 return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY, 1307 CONFIG_DATA_STALL_MIN_EVALUATE_INTERVAL, 1308 DEFAULT_DATA_STALL_MIN_EVALUATE_TIME_MS); 1309 } 1310 getDataStallValidDnsTimeThreshold()1311 private int getDataStallValidDnsTimeThreshold() { 1312 return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY, 1313 CONFIG_DATA_STALL_VALID_DNS_TIME_THRESHOLD, 1314 DEFAULT_DATA_STALL_VALID_DNS_TIME_THRESHOLD_MS); 1315 } 1316 getDataStallEvaluationType()1317 private int getDataStallEvaluationType() { 1318 return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY, 1319 CONFIG_DATA_STALL_EVALUATION_TYPE, 1320 DEFAULT_DATA_STALL_EVALUATION_TYPES); 1321 } 1322 makeCaptivePortalFallbackUrls()1323 private URL[] makeCaptivePortalFallbackUrls() { 1324 try { 1325 final String firstUrl = mDependencies.getSetting(mContext, CAPTIVE_PORTAL_FALLBACK_URL, 1326 null); 1327 1328 final URL[] settingProviderUrls; 1329 if (!TextUtils.isEmpty(firstUrl)) { 1330 final String otherUrls = mDependencies.getDeviceConfigProperty( 1331 NAMESPACE_CONNECTIVITY, CAPTIVE_PORTAL_OTHER_FALLBACK_URLS, ""); 1332 // otherUrls may be empty, but .split() ignores trailing empty strings 1333 final String separator = ","; 1334 final String[] urls = (firstUrl + separator + otherUrls).split(separator); 1335 settingProviderUrls = convertStrings(urls, this::makeURL, new URL[0]); 1336 } else { 1337 settingProviderUrls = new URL[0]; 1338 } 1339 1340 return getArrayConfig(settingProviderUrls, R.array.config_captive_portal_fallback_urls, 1341 R.array.default_captive_portal_fallback_urls, this::makeURL); 1342 } catch (Exception e) { 1343 // Don't let a misconfiguration bootloop the system. 1344 Log.e(TAG, "Error parsing configured fallback URLs", e); 1345 return new URL[0]; 1346 } 1347 } 1348 makeCaptivePortalFallbackProbeSpecs()1349 private CaptivePortalProbeSpec[] makeCaptivePortalFallbackProbeSpecs() { 1350 try { 1351 final String settingsValue = mDependencies.getDeviceConfigProperty( 1352 NAMESPACE_CONNECTIVITY, CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS, null); 1353 1354 final CaptivePortalProbeSpec[] emptySpecs = new CaptivePortalProbeSpec[0]; 1355 final CaptivePortalProbeSpec[] providerValue = TextUtils.isEmpty(settingsValue) 1356 ? emptySpecs 1357 : parseCaptivePortalProbeSpecs(settingsValue).toArray(emptySpecs); 1358 1359 return getArrayConfig(providerValue, R.array.config_captive_portal_fallback_probe_specs, 1360 R.array.default_captive_portal_fallback_probe_specs, 1361 CaptivePortalProbeSpec::parseSpecOrNull); 1362 } catch (Exception e) { 1363 // Don't let a misconfiguration bootloop the system. 1364 Log.e(TAG, "Error parsing configured fallback probe specs", e); 1365 return null; 1366 } 1367 } 1368 1369 /** 1370 * Read a setting from a resource or the settings provider. 1371 * 1372 * <p>The configuration resource is prioritized, then the provider value, then the default 1373 * resource value. 1374 * @param context The context 1375 * @param configResource The resource id for the configuration parameter 1376 * @param defaultResource The resource id for the default value 1377 * @param symbol The symbol in the settings provider 1378 * @return The best available value 1379 */ 1380 @NonNull getSettingFromResource(@onNull final Context context, @StringRes int configResource, @StringRes int defaultResource, @NonNull String symbol)1381 private String getSettingFromResource(@NonNull final Context context, 1382 @StringRes int configResource, @StringRes int defaultResource, 1383 @NonNull String symbol) { 1384 final Resources res = context.getResources(); 1385 String setting = res.getString(configResource); 1386 1387 if (!TextUtils.isEmpty(setting)) return setting; 1388 1389 setting = mDependencies.getSetting(context, symbol, null); 1390 if (!TextUtils.isEmpty(setting)) return setting; 1391 1392 return res.getString(defaultResource); 1393 } 1394 1395 /** 1396 * Get an array configuration from resources or the settings provider. 1397 * 1398 * <p>The configuration resource is prioritized, then the provider values, then the default 1399 * resource values. 1400 * @param providerValue Values obtained from the setting provider. 1401 * @param configResId ID of the configuration resource. 1402 * @param defaultResId ID of the default resource. 1403 * @param resourceConverter Converter from the resource strings to stored setting class. Null 1404 * return values are ignored. 1405 */ getArrayConfig(@onNull T[] providerValue, @ArrayRes int configResId, @ArrayRes int defaultResId, @NonNull Function<String, T> resourceConverter)1406 private <T> T[] getArrayConfig(@NonNull T[] providerValue, @ArrayRes int configResId, 1407 @ArrayRes int defaultResId, @NonNull Function<String, T> resourceConverter) { 1408 final Resources res = mContext.getResources(); 1409 String[] configValue = res.getStringArray(configResId); 1410 1411 if (configValue.length == 0) { 1412 if (providerValue.length > 0) { 1413 return providerValue; 1414 } 1415 1416 configValue = res.getStringArray(defaultResId); 1417 } 1418 1419 return convertStrings(configValue, resourceConverter, Arrays.copyOf(providerValue, 0)); 1420 } 1421 1422 /** 1423 * Convert a String array to an array of some other type using the specified converter. 1424 * 1425 * <p>Any null value, or value for which the converter throws a {@link RuntimeException}, will 1426 * not be added to the output array, so the output array may be smaller than the input. 1427 */ convertStrings( @onNull String[] strings, Function<String, T> converter, T[] emptyArray)1428 private <T> T[] convertStrings( 1429 @NonNull String[] strings, Function<String, T> converter, T[] emptyArray) { 1430 final ArrayList<T> convertedValues = new ArrayList<>(strings.length); 1431 for (String configString : strings) { 1432 T convertedValue = null; 1433 try { 1434 convertedValue = converter.apply(configString); 1435 } catch (Exception e) { 1436 Log.e(TAG, "Error parsing configuration", e); 1437 // Fall through 1438 } 1439 if (convertedValue != null) { 1440 convertedValues.add(convertedValue); 1441 } 1442 } 1443 return convertedValues.toArray(emptyArray); 1444 } 1445 getCaptivePortalUserAgent()1446 private String getCaptivePortalUserAgent() { 1447 return mDependencies.getDeviceConfigProperty(NAMESPACE_CONNECTIVITY, 1448 CAPTIVE_PORTAL_USER_AGENT, DEFAULT_USER_AGENT); 1449 } 1450 nextFallbackUrl()1451 private URL nextFallbackUrl() { 1452 if (mCaptivePortalFallbackUrls.length == 0) { 1453 return null; 1454 } 1455 int idx = Math.abs(mNextFallbackUrlIndex) % mCaptivePortalFallbackUrls.length; 1456 mNextFallbackUrlIndex += mRandom.nextInt(); // randomly change url without memory. 1457 return mCaptivePortalFallbackUrls[idx]; 1458 } 1459 nextFallbackSpec()1460 private CaptivePortalProbeSpec nextFallbackSpec() { 1461 if (isEmpty(mCaptivePortalFallbackSpecs)) { 1462 return null; 1463 } 1464 // Randomly change spec without memory. Also randomize the first attempt. 1465 final int idx = Math.abs(mRandom.nextInt()) % mCaptivePortalFallbackSpecs.length; 1466 return mCaptivePortalFallbackSpecs[idx]; 1467 } 1468 1469 @VisibleForTesting isCaptivePortal()1470 protected CaptivePortalProbeResult isCaptivePortal() { 1471 if (!mIsCaptivePortalCheckEnabled) { 1472 validationLog("Validation disabled."); 1473 return CaptivePortalProbeResult.SUCCESS; 1474 } 1475 1476 URL pacUrl = null; 1477 URL httpsUrl = mCaptivePortalHttpsUrl; 1478 URL httpUrl = mCaptivePortalHttpUrl; 1479 1480 // On networks with a PAC instead of fetching a URL that should result in a 204 1481 // response, we instead simply fetch the PAC script. This is done for a few reasons: 1482 // 1. At present our PAC code does not yet handle multiple PACs on multiple networks 1483 // until something like https://android-review.googlesource.com/#/c/115180/ lands. 1484 // Network.openConnection() will ignore network-specific PACs and instead fetch 1485 // using NO_PROXY. If a PAC is in place, the only fetch we know will succeed with 1486 // NO_PROXY is the fetch of the PAC itself. 1487 // 2. To proxy the generate_204 fetch through a PAC would require a number of things 1488 // happen before the fetch can commence, namely: 1489 // a) the PAC script be fetched 1490 // b) a PAC script resolver service be fired up and resolve the captive portal 1491 // server. 1492 // Network validation could be delayed until these prerequisities are satisifed or 1493 // could simply be left to race them. Neither is an optimal solution. 1494 // 3. PAC scripts are sometimes used to block or restrict Internet access and may in 1495 // fact block fetching of the generate_204 URL which would lead to false negative 1496 // results for network validation. 1497 final ProxyInfo proxyInfo = mLinkProperties.getHttpProxy(); 1498 if (proxyInfo != null && !Uri.EMPTY.equals(proxyInfo.getPacFileUrl())) { 1499 pacUrl = makeURL(proxyInfo.getPacFileUrl().toString()); 1500 if (pacUrl == null) { 1501 return CaptivePortalProbeResult.FAILED; 1502 } 1503 } 1504 1505 if ((pacUrl == null) && (httpUrl == null || httpsUrl == null)) { 1506 return CaptivePortalProbeResult.FAILED; 1507 } 1508 1509 long startTime = SystemClock.elapsedRealtime(); 1510 1511 final CaptivePortalProbeResult result; 1512 if (pacUrl != null) { 1513 result = sendDnsAndHttpProbes(null, pacUrl, ValidationProbeEvent.PROBE_PAC); 1514 reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, result); 1515 } else if (mUseHttps) { 1516 // Probe results are reported inside sendParallelHttpProbes. 1517 result = sendParallelHttpProbes(proxyInfo, httpsUrl, httpUrl); 1518 } else { 1519 result = sendDnsAndHttpProbes(proxyInfo, httpUrl, ValidationProbeEvent.PROBE_HTTP); 1520 reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, result); 1521 } 1522 1523 long endTime = SystemClock.elapsedRealtime(); 1524 1525 sendNetworkConditionsBroadcast(true /* response received */, 1526 result.isPortal() /* isCaptivePortal */, 1527 startTime, endTime); 1528 1529 log("isCaptivePortal: isSuccessful()=" + result.isSuccessful() 1530 + " isPortal()=" + result.isPortal() 1531 + " RedirectUrl=" + result.redirectUrl 1532 + " isPartialConnectivity()=" + result.isPartialConnectivity() 1533 + " Time=" + (endTime - startTime) + "ms"); 1534 1535 return result; 1536 } 1537 1538 /** 1539 * Do a DNS resolution and URL fetch on a known web server to see if we get the data we expect. 1540 * @return a CaptivePortalProbeResult inferred from the HTTP response. 1541 */ sendDnsAndHttpProbes(ProxyInfo proxy, URL url, int probeType)1542 private CaptivePortalProbeResult sendDnsAndHttpProbes(ProxyInfo proxy, URL url, int probeType) { 1543 // Pre-resolve the captive portal server host so we can log it. 1544 // Only do this if HttpURLConnection is about to, to avoid any potentially 1545 // unnecessary resolution. 1546 final String host = (proxy != null) ? proxy.getHost() : url.getHost(); 1547 // This method cannot safely report probe results because it might not be running on the 1548 // state machine thread. Reporting results here would cause races and potentially send 1549 // information to callers that does not make sense because the state machine has already 1550 // changed state. 1551 sendDnsProbe(host); 1552 return sendHttpProbe(url, probeType, null); 1553 } 1554 1555 /** Do a DNS lookup for the given server, or throw UnknownHostException after timeoutMs */ 1556 @VisibleForTesting sendDnsProbeWithTimeout(String host, int timeoutMs)1557 protected InetAddress[] sendDnsProbeWithTimeout(String host, int timeoutMs) 1558 throws UnknownHostException { 1559 return DnsUtils.getAllByName(mDependencies.getDnsResolver(), mCleartextDnsNetwork, host, 1560 TYPE_ADDRCONFIG, FLAG_EMPTY, timeoutMs); 1561 } 1562 1563 /** Do a DNS resolution of the given server. */ sendDnsProbe(String host)1564 private void sendDnsProbe(String host) { 1565 if (TextUtils.isEmpty(host)) { 1566 return; 1567 } 1568 1569 final String name = ValidationProbeEvent.getProbeName(ValidationProbeEvent.PROBE_DNS); 1570 final Stopwatch watch = new Stopwatch().start(); 1571 int result; 1572 String connectInfo; 1573 try { 1574 InetAddress[] addresses = sendDnsProbeWithTimeout(host, getDnsProbeTimeout()); 1575 StringBuffer buffer = new StringBuffer(); 1576 for (InetAddress address : addresses) { 1577 buffer.append(',').append(address.getHostAddress()); 1578 } 1579 result = ValidationProbeEvent.DNS_SUCCESS; 1580 connectInfo = "OK " + buffer.substring(1); 1581 } catch (UnknownHostException e) { 1582 result = ValidationProbeEvent.DNS_FAILURE; 1583 connectInfo = "FAIL"; 1584 } 1585 final long latency = watch.stop(); 1586 validationLog(ValidationProbeEvent.PROBE_DNS, host, 1587 String.format("%dms %s", latency, connectInfo)); 1588 logValidationProbe(latency, ValidationProbeEvent.PROBE_DNS, result); 1589 } 1590 1591 /** 1592 * Do a URL fetch on a known web server to see if we get the data we expect. 1593 * @return a CaptivePortalProbeResult inferred from the HTTP response. 1594 */ 1595 @VisibleForTesting sendHttpProbe(URL url, int probeType, @Nullable CaptivePortalProbeSpec probeSpec)1596 protected CaptivePortalProbeResult sendHttpProbe(URL url, int probeType, 1597 @Nullable CaptivePortalProbeSpec probeSpec) { 1598 HttpURLConnection urlConnection = null; 1599 int httpResponseCode = CaptivePortalProbeResult.FAILED_CODE; 1600 String redirectUrl = null; 1601 final Stopwatch probeTimer = new Stopwatch().start(); 1602 final int oldTag = TrafficStats.getAndSetThreadStatsTag( 1603 TrafficStatsConstants.TAG_SYSTEM_PROBE); 1604 try { 1605 urlConnection = (HttpURLConnection) mCleartextDnsNetwork.openConnection(url); 1606 urlConnection.setInstanceFollowRedirects(probeType == ValidationProbeEvent.PROBE_PAC); 1607 urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS); 1608 urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS); 1609 urlConnection.setRequestProperty("Connection", "close"); 1610 urlConnection.setUseCaches(false); 1611 if (mCaptivePortalUserAgent != null) { 1612 urlConnection.setRequestProperty("User-Agent", mCaptivePortalUserAgent); 1613 } 1614 // cannot read request header after connection 1615 String requestHeader = urlConnection.getRequestProperties().toString(); 1616 1617 // Time how long it takes to get a response to our request 1618 long requestTimestamp = SystemClock.elapsedRealtime(); 1619 1620 httpResponseCode = urlConnection.getResponseCode(); 1621 redirectUrl = urlConnection.getHeaderField("location"); 1622 1623 // Time how long it takes to get a response to our request 1624 long responseTimestamp = SystemClock.elapsedRealtime(); 1625 1626 validationLog(probeType, url, "time=" + (responseTimestamp - requestTimestamp) + "ms" 1627 + " ret=" + httpResponseCode 1628 + " request=" + requestHeader 1629 + " headers=" + urlConnection.getHeaderFields()); 1630 // NOTE: We may want to consider an "HTTP/1.0 204" response to be a captive 1631 // portal. The only example of this seen so far was a captive portal. For 1632 // the time being go with prior behavior of assuming it's not a captive 1633 // portal. If it is considered a captive portal, a different sign-in URL 1634 // is needed (i.e. can't browse a 204). This could be the result of an HTTP 1635 // proxy server. 1636 if (httpResponseCode == 200) { 1637 long contentLength = urlConnection.getContentLengthLong(); 1638 if (probeType == ValidationProbeEvent.PROBE_PAC) { 1639 validationLog( 1640 probeType, url, "PAC fetch 200 response interpreted as 204 response."); 1641 httpResponseCode = CaptivePortalProbeResult.SUCCESS_CODE; 1642 } else if (contentLength == -1) { 1643 // When no Content-length (default value == -1), attempt to read a byte 1644 // from the response. Do not use available() as it is unreliable. 1645 // See http://b/33498325. 1646 if (urlConnection.getInputStream().read() == -1) { 1647 validationLog(probeType, url, 1648 "Empty 200 response interpreted as failed response."); 1649 httpResponseCode = CaptivePortalProbeResult.FAILED_CODE; 1650 } 1651 } else if (contentLength <= 4) { 1652 // Consider 200 response with "Content-length <= 4" to not be a captive 1653 // portal. There's no point in considering this a captive portal as the 1654 // user cannot sign-in to an empty page. Probably the result of a broken 1655 // transparent proxy. See http://b/9972012 and http://b/122999481. 1656 validationLog(probeType, url, "200 response with Content-length <= 4" 1657 + " interpreted as failed response."); 1658 httpResponseCode = CaptivePortalProbeResult.FAILED_CODE; 1659 } 1660 } 1661 } catch (IOException e) { 1662 validationLog(probeType, url, "Probe failed with exception " + e); 1663 if (httpResponseCode == CaptivePortalProbeResult.FAILED_CODE) { 1664 // TODO: Ping gateway and DNS server and log results. 1665 } 1666 } finally { 1667 if (urlConnection != null) { 1668 urlConnection.disconnect(); 1669 } 1670 TrafficStats.setThreadStatsTag(oldTag); 1671 } 1672 logValidationProbe(probeTimer.stop(), probeType, httpResponseCode); 1673 1674 if (probeSpec == null) { 1675 return new CaptivePortalProbeResult(httpResponseCode, redirectUrl, url.toString()); 1676 } else { 1677 return probeSpec.getResult(httpResponseCode, redirectUrl); 1678 } 1679 } 1680 sendParallelHttpProbes( ProxyInfo proxy, URL httpsUrl, URL httpUrl)1681 private CaptivePortalProbeResult sendParallelHttpProbes( 1682 ProxyInfo proxy, URL httpsUrl, URL httpUrl) { 1683 // Number of probes to wait for. If a probe completes with a conclusive answer 1684 // it shortcuts the latch immediately by forcing the count to 0. 1685 final CountDownLatch latch = new CountDownLatch(2); 1686 1687 final class ProbeThread extends Thread { 1688 private final boolean mIsHttps; 1689 private volatile CaptivePortalProbeResult mResult = CaptivePortalProbeResult.FAILED; 1690 1691 ProbeThread(boolean isHttps) { 1692 mIsHttps = isHttps; 1693 } 1694 1695 public CaptivePortalProbeResult result() { 1696 return mResult; 1697 } 1698 1699 @Override 1700 public void run() { 1701 if (mIsHttps) { 1702 mResult = 1703 sendDnsAndHttpProbes(proxy, httpsUrl, ValidationProbeEvent.PROBE_HTTPS); 1704 } else { 1705 mResult = sendDnsAndHttpProbes(proxy, httpUrl, ValidationProbeEvent.PROBE_HTTP); 1706 } 1707 if ((mIsHttps && mResult.isSuccessful()) || (!mIsHttps && mResult.isPortal())) { 1708 // Stop waiting immediately if https succeeds or if http finds a portal. 1709 while (latch.getCount() > 0) { 1710 latch.countDown(); 1711 } 1712 } 1713 // Signal this probe has completed. 1714 latch.countDown(); 1715 } 1716 } 1717 1718 final ProbeThread httpsProbe = new ProbeThread(true); 1719 final ProbeThread httpProbe = new ProbeThread(false); 1720 1721 try { 1722 httpsProbe.start(); 1723 httpProbe.start(); 1724 latch.await(PROBE_TIMEOUT_MS, TimeUnit.MILLISECONDS); 1725 } catch (InterruptedException e) { 1726 validationLog("Error: probes wait interrupted!"); 1727 return CaptivePortalProbeResult.FAILED; 1728 } 1729 1730 final CaptivePortalProbeResult httpsResult = httpsProbe.result(); 1731 final CaptivePortalProbeResult httpResult = httpProbe.result(); 1732 1733 // Look for a conclusive probe result first. 1734 if (httpResult.isPortal()) { 1735 reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, httpResult); 1736 return httpResult; 1737 } 1738 // httpsResult.isPortal() is not expected, but check it nonetheless. 1739 if (httpsResult.isPortal() || httpsResult.isSuccessful()) { 1740 reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTPS, httpsResult); 1741 return httpsResult; 1742 } 1743 // If a fallback method exists, use it to retry portal detection. 1744 // If we have new-style probe specs, use those. Otherwise, use the fallback URLs. 1745 final CaptivePortalProbeSpec probeSpec = nextFallbackSpec(); 1746 final URL fallbackUrl = (probeSpec != null) ? probeSpec.getUrl() : nextFallbackUrl(); 1747 CaptivePortalProbeResult fallbackProbeResult = null; 1748 if (fallbackUrl != null) { 1749 fallbackProbeResult = sendHttpProbe(fallbackUrl, PROBE_FALLBACK, probeSpec); 1750 reportHttpProbeResult(NETWORK_VALIDATION_PROBE_FALLBACK, fallbackProbeResult); 1751 if (fallbackProbeResult.isPortal()) { 1752 return fallbackProbeResult; 1753 } 1754 } 1755 // Otherwise wait until http and https probes completes and use their results. 1756 try { 1757 httpProbe.join(); 1758 reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, httpProbe.result()); 1759 1760 if (httpProbe.result().isPortal()) { 1761 return httpProbe.result(); 1762 } 1763 1764 httpsProbe.join(); 1765 reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTPS, httpsProbe.result()); 1766 1767 final boolean isHttpSuccessful = 1768 (httpProbe.result().isSuccessful() 1769 || (fallbackProbeResult != null && fallbackProbeResult.isSuccessful())); 1770 if (httpsProbe.result().isFailed() && isHttpSuccessful) { 1771 return CaptivePortalProbeResult.PARTIAL; 1772 } 1773 return httpsProbe.result(); 1774 } catch (InterruptedException e) { 1775 validationLog("Error: http or https probe wait interrupted!"); 1776 return CaptivePortalProbeResult.FAILED; 1777 } 1778 } 1779 makeURL(String url)1780 private URL makeURL(String url) { 1781 if (url != null) { 1782 try { 1783 return new URL(url); 1784 } catch (MalformedURLException e) { 1785 validationLog("Bad URL: " + url); 1786 } 1787 } 1788 return null; 1789 } 1790 1791 /** 1792 * @param responseReceived - whether or not we received a valid HTTP response to our request. 1793 * If false, isCaptivePortal and responseTimestampMs are ignored 1794 * TODO: This should be moved to the transports. The latency could be passed to the transports 1795 * along with the captive portal result. Currently the TYPE_MOBILE broadcasts appear unused so 1796 * perhaps this could just be added to the WiFi transport only. 1797 */ sendNetworkConditionsBroadcast(boolean responseReceived, boolean isCaptivePortal, long requestTimestampMs, long responseTimestampMs)1798 private void sendNetworkConditionsBroadcast(boolean responseReceived, boolean isCaptivePortal, 1799 long requestTimestampMs, long responseTimestampMs) { 1800 Intent latencyBroadcast = 1801 new Intent(NetworkMonitorUtils.ACTION_NETWORK_CONDITIONS_MEASURED); 1802 if (mNetworkCapabilities.hasTransport(TRANSPORT_WIFI)) { 1803 if (!mWifiManager.isScanAlwaysAvailable()) { 1804 return; 1805 } 1806 1807 WifiInfo currentWifiInfo = mWifiManager.getConnectionInfo(); 1808 if (currentWifiInfo != null) { 1809 // NOTE: getSSID()'s behavior changed in API 17; before that, SSIDs were not 1810 // surrounded by double quotation marks (thus violating the Javadoc), but this 1811 // was changed to match the Javadoc in API 17. Since clients may have started 1812 // sanitizing the output of this method since API 17 was released, we should 1813 // not change it here as it would become impossible to tell whether the SSID is 1814 // simply being surrounded by quotes due to the API, or whether those quotes 1815 // are actually part of the SSID. 1816 latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_SSID, 1817 currentWifiInfo.getSSID()); 1818 latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_BSSID, 1819 currentWifiInfo.getBSSID()); 1820 } else { 1821 if (VDBG) logw("network info is TYPE_WIFI but no ConnectionInfo found"); 1822 return; 1823 } 1824 latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CONNECTIVITY_TYPE, TYPE_WIFI); 1825 } else if (mNetworkCapabilities.hasTransport(TRANSPORT_CELLULAR)) { 1826 // TODO(b/123893112): Support multi-sim. 1827 latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_NETWORK_TYPE, 1828 mTelephonyManager.getNetworkType()); 1829 final ServiceState dataSs = mTelephonyManager.getServiceState(); 1830 if (dataSs == null) { 1831 logw("failed to retrieve ServiceState"); 1832 return; 1833 } 1834 // See if the data sub is registered for PS services on cell. 1835 final NetworkRegistrationInfo nri = dataSs.getNetworkRegistrationInfo( 1836 NetworkRegistrationInfo.DOMAIN_PS, 1837 AccessNetworkConstants.TRANSPORT_TYPE_WWAN); 1838 latencyBroadcast.putExtra( 1839 NetworkMonitorUtils.EXTRA_CELL_ID, 1840 nri == null ? null : nri.getCellIdentity()); 1841 latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CONNECTIVITY_TYPE, TYPE_MOBILE); 1842 } else { 1843 return; 1844 } 1845 latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_RESPONSE_RECEIVED, 1846 responseReceived); 1847 latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_REQUEST_TIMESTAMP_MS, 1848 requestTimestampMs); 1849 1850 if (responseReceived) { 1851 latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_IS_CAPTIVE_PORTAL, 1852 isCaptivePortal); 1853 latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_RESPONSE_TIMESTAMP_MS, 1854 responseTimestampMs); 1855 } 1856 mContext.sendBroadcastAsUser(latencyBroadcast, UserHandle.CURRENT, 1857 NetworkMonitorUtils.PERMISSION_ACCESS_NETWORK_CONDITIONS); 1858 } 1859 logNetworkEvent(int evtype)1860 private void logNetworkEvent(int evtype) { 1861 int[] transports = mNetworkCapabilities.getTransportTypes(); 1862 mMetricsLog.log(mCleartextDnsNetwork, transports, new NetworkEvent(evtype)); 1863 } 1864 networkEventType(ValidationStage s, EvaluationResult r)1865 private int networkEventType(ValidationStage s, EvaluationResult r) { 1866 if (s.mIsFirstValidation) { 1867 if (r.mIsValidated) { 1868 return NetworkEvent.NETWORK_FIRST_VALIDATION_SUCCESS; 1869 } else { 1870 return NetworkEvent.NETWORK_FIRST_VALIDATION_PORTAL_FOUND; 1871 } 1872 } else { 1873 if (r.mIsValidated) { 1874 return NetworkEvent.NETWORK_REVALIDATION_SUCCESS; 1875 } else { 1876 return NetworkEvent.NETWORK_REVALIDATION_PORTAL_FOUND; 1877 } 1878 } 1879 } 1880 maybeLogEvaluationResult(int evtype)1881 private void maybeLogEvaluationResult(int evtype) { 1882 if (mEvaluationTimer.isRunning()) { 1883 int[] transports = mNetworkCapabilities.getTransportTypes(); 1884 mMetricsLog.log(mCleartextDnsNetwork, transports, 1885 new NetworkEvent(evtype, mEvaluationTimer.stop())); 1886 mEvaluationTimer.reset(); 1887 } 1888 } 1889 logValidationProbe(long durationMs, int probeType, int probeResult)1890 private void logValidationProbe(long durationMs, int probeType, int probeResult) { 1891 int[] transports = mNetworkCapabilities.getTransportTypes(); 1892 boolean isFirstValidation = validationStage().mIsFirstValidation; 1893 ValidationProbeEvent ev = new ValidationProbeEvent.Builder() 1894 .setProbeType(probeType, isFirstValidation) 1895 .setReturnCode(probeResult) 1896 .setDurationMs(durationMs) 1897 .build(); 1898 mMetricsLog.log(mCleartextDnsNetwork, transports, ev); 1899 } 1900 1901 @VisibleForTesting 1902 static class Dependencies { getPrivateDnsBypassNetwork(Network network)1903 public Network getPrivateDnsBypassNetwork(Network network) { 1904 return new OneAddressPerFamilyNetwork(network); 1905 } 1906 getDnsResolver()1907 public DnsResolver getDnsResolver() { 1908 return DnsResolver.getInstance(); 1909 } 1910 getRandom()1911 public Random getRandom() { 1912 return new Random(); 1913 } 1914 1915 /** 1916 * Get the value of a global integer setting. 1917 * @param symbol Name of the setting 1918 * @param defaultValue Value to return if the setting is not defined. 1919 */ getSetting(Context context, String symbol, int defaultValue)1920 public int getSetting(Context context, String symbol, int defaultValue) { 1921 return Settings.Global.getInt(context.getContentResolver(), symbol, defaultValue); 1922 } 1923 1924 /** 1925 * Get the value of a global String setting. 1926 * @param symbol Name of the setting 1927 * @param defaultValue Value to return if the setting is not defined. 1928 */ getSetting(Context context, String symbol, String defaultValue)1929 public String getSetting(Context context, String symbol, String defaultValue) { 1930 final String value = Settings.Global.getString(context.getContentResolver(), symbol); 1931 return value != null ? value : defaultValue; 1932 } 1933 1934 /** 1935 * Look up the value of a property in DeviceConfig. 1936 * @param namespace The namespace containing the property to look up. 1937 * @param name The name of the property to look up. 1938 * @param defaultValue The value to return if the property does not exist or has no non-null 1939 * value. 1940 * @return the corresponding value, or defaultValue if none exists. 1941 */ 1942 @Nullable getDeviceConfigProperty(@onNull String namespace, @NonNull String name, @Nullable String defaultValue)1943 public String getDeviceConfigProperty(@NonNull String namespace, @NonNull String name, 1944 @Nullable String defaultValue) { 1945 return NetworkStackUtils.getDeviceConfigProperty(namespace, name, defaultValue); 1946 } 1947 1948 /** 1949 * Look up the value of a property in DeviceConfig. 1950 * @param namespace The namespace containing the property to look up. 1951 * @param name The name of the property to look up. 1952 * @param defaultValue The value to return if the property does not exist or has no non-null 1953 * value. 1954 * @return the corresponding value, or defaultValue if none exists. 1955 */ getDeviceConfigPropertyInt(@onNull String namespace, @NonNull String name, int defaultValue)1956 public int getDeviceConfigPropertyInt(@NonNull String namespace, @NonNull String name, 1957 int defaultValue) { 1958 return NetworkStackUtils.getDeviceConfigPropertyInt(namespace, name, defaultValue); 1959 } 1960 1961 public static final Dependencies DEFAULT = new Dependencies(); 1962 } 1963 1964 /** 1965 * Methods in this class perform no locking because all accesses are performed on the state 1966 * machine's thread. Need to consider the thread safety if it ever could be accessed outside the 1967 * state machine. 1968 */ 1969 @VisibleForTesting 1970 protected class DnsStallDetector { 1971 private int mConsecutiveTimeoutCount = 0; 1972 private int mSize; 1973 final DnsResult[] mDnsEvents; 1974 final RingBufferIndices mResultIndices; 1975 DnsStallDetector(int size)1976 DnsStallDetector(int size) { 1977 mSize = Math.max(DEFAULT_DNS_LOG_SIZE, size); 1978 mDnsEvents = new DnsResult[mSize]; 1979 mResultIndices = new RingBufferIndices(mSize); 1980 } 1981 1982 @VisibleForTesting accumulateConsecutiveDnsTimeoutCount(int code)1983 protected void accumulateConsecutiveDnsTimeoutCount(int code) { 1984 final DnsResult result = new DnsResult(code); 1985 mDnsEvents[mResultIndices.add()] = result; 1986 if (result.isTimeout()) { 1987 mConsecutiveTimeoutCount++; 1988 } else { 1989 // Keep the event in mDnsEvents without clearing it so that there are logs to do the 1990 // simulation and analysis. 1991 mConsecutiveTimeoutCount = 0; 1992 } 1993 } 1994 isDataStallSuspected(int timeoutCountThreshold, int validTime)1995 private boolean isDataStallSuspected(int timeoutCountThreshold, int validTime) { 1996 if (timeoutCountThreshold <= 0) { 1997 Log.wtf(TAG, "Timeout count threshold should be larger than 0."); 1998 return false; 1999 } 2000 2001 // Check if the consecutive timeout count reach the threshold or not. 2002 if (mConsecutiveTimeoutCount < timeoutCountThreshold) { 2003 return false; 2004 } 2005 2006 // Check if the target dns event index is valid or not. 2007 final int firstConsecutiveTimeoutIndex = 2008 mResultIndices.indexOf(mResultIndices.size() - timeoutCountThreshold); 2009 2010 // If the dns timeout events happened long time ago, the events are meaningless for 2011 // data stall evaluation. Thus, check if the first consecutive timeout dns event 2012 // considered in the evaluation happened in defined threshold time. 2013 final long now = SystemClock.elapsedRealtime(); 2014 final long firstTimeoutTime = now - mDnsEvents[firstConsecutiveTimeoutIndex].mTimeStamp; 2015 return (firstTimeoutTime < validTime); 2016 } 2017 getConsecutiveTimeoutCount()2018 int getConsecutiveTimeoutCount() { 2019 return mConsecutiveTimeoutCount; 2020 } 2021 } 2022 2023 private static class DnsResult { 2024 // TODO: Need to move the DNS return code definition to a specific class once unify DNS 2025 // response code is done. 2026 private static final int RETURN_CODE_DNS_TIMEOUT = 255; 2027 2028 private final long mTimeStamp; 2029 private final int mReturnCode; 2030 DnsResult(int code)2031 DnsResult(int code) { 2032 mTimeStamp = SystemClock.elapsedRealtime(); 2033 mReturnCode = code; 2034 } 2035 isTimeout()2036 private boolean isTimeout() { 2037 return mReturnCode == RETURN_CODE_DNS_TIMEOUT; 2038 } 2039 } 2040 2041 2042 @VisibleForTesting getDnsStallDetector()2043 protected DnsStallDetector getDnsStallDetector() { 2044 return mDnsStallDetector; 2045 } 2046 dataStallEvaluateTypeEnabled(int type)2047 private boolean dataStallEvaluateTypeEnabled(int type) { 2048 return (mDataStallEvaluationType & type) != 0; 2049 } 2050 2051 @VisibleForTesting getLastProbeTime()2052 protected long getLastProbeTime() { 2053 return mLastProbeTime; 2054 } 2055 2056 @VisibleForTesting isDataStall()2057 protected boolean isDataStall() { 2058 boolean result = false; 2059 // Reevaluation will generate traffic. Thus, set a minimal reevaluation timer to limit the 2060 // possible traffic cost in metered network. 2061 if (!mNetworkCapabilities.hasCapability(NET_CAPABILITY_NOT_METERED) 2062 && (SystemClock.elapsedRealtime() - getLastProbeTime() 2063 < mDataStallMinEvaluateTime)) { 2064 return false; 2065 } 2066 2067 // Check dns signal. Suspect it may be a data stall if both : 2068 // 1. The number of consecutive DNS query timeouts >= mConsecutiveDnsTimeoutThreshold. 2069 // 2. Those consecutive DNS queries happened in the last mValidDataStallDnsTimeThreshold ms. 2070 if (dataStallEvaluateTypeEnabled(DATA_STALL_EVALUATION_TYPE_DNS)) { 2071 if (mDnsStallDetector.isDataStallSuspected(mConsecutiveDnsTimeoutThreshold, 2072 mDataStallValidDnsTimeThreshold)) { 2073 result = true; 2074 logNetworkEvent(NetworkEvent.NETWORK_CONSECUTIVE_DNS_TIMEOUT_FOUND); 2075 } 2076 } 2077 2078 if (VDBG_STALL) { 2079 log("isDataStall: result=" + result + ", consecutive dns timeout count=" 2080 + mDnsStallDetector.getConsecutiveTimeoutCount()); 2081 } 2082 2083 return result; 2084 } 2085 2086 // Class to keep state of evaluation results and probe results. 2087 // 2088 // The main purpose was to ensure NetworkMonitor can notify ConnectivityService of probe results 2089 // as soon as they happen, without triggering any other changes. This requires keeping state on 2090 // the most recent evaluation result. Calling noteProbeResult will ensure that the results 2091 // reported to ConnectivityService contain the previous evaluation result, and thus won't 2092 // trigger a validation or partial connectivity state change. 2093 // 2094 // Note that this class is not currently being used for this purpose. The reason is that some 2095 // of the system behaviour triggered by reporting network validation - notably, NetworkAgent 2096 // behaviour - depends not only on the value passed by notifyNetworkTested, but also on the 2097 // fact that notifyNetworkTested was called. For example, telephony triggers network recovery 2098 // any time it is told that validation failed, i.e., if the result does not contain 2099 // NETWORK_VALIDATION_RESULT_VALID. But with this scheme, the first two or three validation 2100 // reports are all failures, because they are "HTTP succeeded but validation not yet passed", 2101 // "HTTP and HTTPS succeeded but validation not yet passed", etc. 2102 @VisibleForTesting 2103 protected class EvaluationState { 2104 // The latest validation result for this network. This is a bitmask of 2105 // INetworkMonitor.NETWORK_VALIDATION_RESULT_* constants. 2106 private int mEvaluationResult = NETWORK_VALIDATION_RESULT_INVALID; 2107 // Indicates which probes have completed since clearProbeResults was called. 2108 // This is a bitmask of INetworkMonitor.NETWORK_VALIDATION_PROBE_* constants. 2109 private int mProbeResults = 0; 2110 // The latest redirect URL. 2111 private String mRedirectUrl; 2112 clearProbeResults()2113 protected void clearProbeResults() { 2114 mProbeResults = 0; 2115 } 2116 2117 // Probe result for http probe should be updated from reportHttpProbeResult(). noteProbeResult(int probeResult, boolean succeeded)2118 protected void noteProbeResult(int probeResult, boolean succeeded) { 2119 if (succeeded) { 2120 mProbeResults |= probeResult; 2121 } else { 2122 mProbeResults &= ~probeResult; 2123 } 2124 } 2125 reportEvaluationResult(int result, @Nullable String redirectUrl)2126 protected void reportEvaluationResult(int result, @Nullable String redirectUrl) { 2127 mEvaluationResult = result; 2128 mRedirectUrl = redirectUrl; 2129 notifyNetworkTested(getNetworkTestResult(), mRedirectUrl); 2130 } 2131 getNetworkTestResult()2132 protected int getNetworkTestResult() { 2133 if (mCallbackVersion < 3) { 2134 if ((mEvaluationResult & NETWORK_VALIDATION_RESULT_VALID) != 0) { 2135 return NETWORK_TEST_RESULT_VALID; 2136 } 2137 if ((mEvaluationResult & NETWORK_VALIDATION_RESULT_PARTIAL) != 0) { 2138 return NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY; 2139 } 2140 return NETWORK_TEST_RESULT_INVALID; 2141 } 2142 return mEvaluationResult | mProbeResults; 2143 } 2144 } 2145 2146 @VisibleForTesting getEvaluationState()2147 protected EvaluationState getEvaluationState() { 2148 return mEvaluationState; 2149 } 2150 maybeDisableHttpsProbing(boolean acceptPartial)2151 private void maybeDisableHttpsProbing(boolean acceptPartial) { 2152 mAcceptPartialConnectivity = acceptPartial; 2153 // Ignore https probe in next validation if user accept partial connectivity on a partial 2154 // connectivity network. 2155 if (((mEvaluationState.getNetworkTestResult() & NETWORK_VALIDATION_RESULT_PARTIAL) != 0) 2156 && mAcceptPartialConnectivity) { 2157 mUseHttps = false; 2158 } 2159 } 2160 2161 // Report HTTP, HTTP or FALLBACK probe result. 2162 @VisibleForTesting reportHttpProbeResult(int probeResult, @NonNull final CaptivePortalProbeResult result)2163 protected void reportHttpProbeResult(int probeResult, 2164 @NonNull final CaptivePortalProbeResult result) { 2165 boolean succeeded = result.isSuccessful(); 2166 // The success of a HTTP probe does not tell us whether the DNS probe succeeded. 2167 // The DNS and HTTP probes run one after the other in sendDnsAndHttpProbes, and that 2168 // method cannot report the result of the DNS probe because that it could be running 2169 // on a different thread which is racing with the main state machine thread. So, if 2170 // an HTTP or HTTPS probe succeeded, assume that the DNS probe succeeded. But if an 2171 // HTTP or HTTPS probe failed, don't assume that DNS is not working. 2172 // TODO: fix this. 2173 if (succeeded) { 2174 probeResult |= NETWORK_VALIDATION_PROBE_DNS; 2175 } 2176 mEvaluationState.noteProbeResult(probeResult, succeeded); 2177 } 2178 } 2179