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.content.Intent.ACTION_CONFIGURATION_CHANGED; 20 import static android.net.CaptivePortal.APP_RETURN_DISMISSED; 21 import static android.net.CaptivePortal.APP_RETURN_UNWANTED; 22 import static android.net.CaptivePortal.APP_RETURN_WANTED_AS_IS; 23 import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_PROBE_SPEC; 24 import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL; 25 import static android.net.DnsResolver.FLAG_EMPTY; 26 import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_INVALID; 27 import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY; 28 import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_VALID; 29 import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_DNS; 30 import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_FALLBACK; 31 import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTP; 32 import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTPS; 33 import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_PRIVDNS; 34 import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL; 35 import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_SKIPPED; 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.NET_CAPABILITY_NOT_RESTRICTED; 39 import static android.net.captiveportal.CaptivePortalProbeSpec.parseCaptivePortalProbeSpecs; 40 import static android.net.metrics.ValidationProbeEvent.DNS_FAILURE; 41 import static android.net.metrics.ValidationProbeEvent.DNS_SUCCESS; 42 import static android.net.metrics.ValidationProbeEvent.PROBE_FALLBACK; 43 import static android.net.metrics.ValidationProbeEvent.PROBE_PRIVDNS; 44 import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD; 45 import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_EVALUATION_TYPE; 46 import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_MIN_EVALUATE_INTERVAL; 47 import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_TCP_POLLING_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.DATA_STALL_EVALUATION_TYPE_NONE; 51 import static android.net.util.DataStallUtils.DATA_STALL_EVALUATION_TYPE_TCP; 52 import static android.net.util.DataStallUtils.DEFAULT_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD; 53 import static android.net.util.DataStallUtils.DEFAULT_DATA_STALL_EVALUATION_TYPES; 54 import static android.net.util.DataStallUtils.DEFAULT_DATA_STALL_MIN_EVALUATE_TIME_MS; 55 import static android.net.util.DataStallUtils.DEFAULT_DATA_STALL_VALID_DNS_TIME_THRESHOLD_MS; 56 import static android.net.util.DataStallUtils.DEFAULT_DNS_LOG_SIZE; 57 import static android.net.util.DataStallUtils.DEFAULT_TCP_POLLING_INTERVAL_MS; 58 import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY; 59 60 import static com.android.modules.utils.build.SdkLevel.isAtLeastU; 61 import static com.android.net.module.util.CollectionUtils.isEmpty; 62 import static com.android.net.module.util.ConnectivityUtils.isIPv6ULA; 63 import static com.android.net.module.util.DeviceConfigUtils.getResBooleanConfig; 64 import static com.android.net.module.util.NetworkStackConstants.TEST_CAPTIVE_PORTAL_HTTPS_URL; 65 import static com.android.net.module.util.NetworkStackConstants.TEST_CAPTIVE_PORTAL_HTTP_URL; 66 import static com.android.net.module.util.NetworkStackConstants.TEST_URL_EXPIRATION_TIME; 67 import static com.android.networkstack.apishim.ConstantsShim.DETECTION_METHOD_DNS_EVENTS; 68 import static com.android.networkstack.apishim.ConstantsShim.DETECTION_METHOD_TCP_METRICS; 69 import static com.android.networkstack.apishim.ConstantsShim.RECEIVER_NOT_EXPORTED; 70 import static com.android.networkstack.apishim.ConstantsShim.TRANSPORT_TEST; 71 import static com.android.networkstack.util.DnsUtils.PRIVATE_DNS_PROBE_HOST_SUFFIX; 72 import static com.android.networkstack.util.DnsUtils.TYPE_ADDRCONFIG; 73 import static com.android.networkstack.util.NetworkStackUtils.CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS; 74 import static com.android.networkstack.util.NetworkStackUtils.CAPTIVE_PORTAL_FALLBACK_URL; 75 import static com.android.networkstack.util.NetworkStackUtils.CAPTIVE_PORTAL_HTTPS_URL; 76 import static com.android.networkstack.util.NetworkStackUtils.CAPTIVE_PORTAL_HTTP_URL; 77 import static com.android.networkstack.util.NetworkStackUtils.CAPTIVE_PORTAL_MODE; 78 import static com.android.networkstack.util.NetworkStackUtils.CAPTIVE_PORTAL_MODE_IGNORE; 79 import static com.android.networkstack.util.NetworkStackUtils.CAPTIVE_PORTAL_MODE_PROMPT; 80 import static com.android.networkstack.util.NetworkStackUtils.CAPTIVE_PORTAL_OTHER_FALLBACK_URLS; 81 import static com.android.networkstack.util.NetworkStackUtils.CAPTIVE_PORTAL_OTHER_HTTPS_URLS; 82 import static com.android.networkstack.util.NetworkStackUtils.CAPTIVE_PORTAL_OTHER_HTTP_URLS; 83 import static com.android.networkstack.util.NetworkStackUtils.CAPTIVE_PORTAL_USER_AGENT; 84 import static com.android.networkstack.util.NetworkStackUtils.CAPTIVE_PORTAL_USE_HTTPS; 85 import static com.android.networkstack.util.NetworkStackUtils.DEFAULT_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT; 86 import static com.android.networkstack.util.NetworkStackUtils.DEFAULT_CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS; 87 import static com.android.networkstack.util.NetworkStackUtils.DEFAULT_CAPTIVE_PORTAL_HTTPS_URLS; 88 import static com.android.networkstack.util.NetworkStackUtils.DEFAULT_CAPTIVE_PORTAL_HTTP_URLS; 89 import static com.android.networkstack.util.NetworkStackUtils.DNS_PROBE_PRIVATE_IP_NO_INTERNET_VERSION; 90 91 import android.app.PendingIntent; 92 import android.content.BroadcastReceiver; 93 import android.content.Context; 94 import android.content.Intent; 95 import android.content.IntentFilter; 96 import android.content.pm.PackageManager; 97 import android.content.res.Configuration; 98 import android.content.res.Resources; 99 import android.net.ConnectivityManager; 100 import android.net.DataStallReportParcelable; 101 import android.net.DnsResolver; 102 import android.net.INetworkMonitorCallbacks; 103 import android.net.LinkProperties; 104 import android.net.Network; 105 import android.net.NetworkCapabilities; 106 import android.net.NetworkTestResultParcelable; 107 import android.net.ProxyInfo; 108 import android.net.TrafficStats; 109 import android.net.Uri; 110 import android.net.captiveportal.CapportApiProbeResult; 111 import android.net.captiveportal.CaptivePortalProbeResult; 112 import android.net.captiveportal.CaptivePortalProbeSpec; 113 import android.net.metrics.IpConnectivityLog; 114 import android.net.metrics.NetworkEvent; 115 import android.net.metrics.ValidationProbeEvent; 116 import android.net.networkstack.aidl.NetworkMonitorParameters; 117 import android.net.shared.PrivateDnsConfig; 118 import android.net.util.DataStallUtils.EvaluationType; 119 import android.net.util.Stopwatch; 120 import android.net.wifi.WifiInfo; 121 import android.net.wifi.WifiManager; 122 import android.os.Build; 123 import android.os.Bundle; 124 import android.os.Message; 125 import android.os.Process; 126 import android.os.RemoteException; 127 import android.os.SystemClock; 128 import android.provider.DeviceConfig; 129 import android.provider.Settings; 130 import android.stats.connectivity.ProbeResult; 131 import android.stats.connectivity.ProbeType; 132 import android.telephony.CellIdentityNr; 133 import android.telephony.CellInfo; 134 import android.telephony.CellInfoGsm; 135 import android.telephony.CellInfoLte; 136 import android.telephony.CellInfoNr; 137 import android.telephony.CellInfoTdscdma; 138 import android.telephony.CellInfoWcdma; 139 import android.telephony.CellSignalStrength; 140 import android.telephony.SignalStrength; 141 import android.telephony.TelephonyManager; 142 import android.text.TextUtils; 143 import android.util.Log; 144 import android.util.SparseArray; 145 146 import androidx.annotation.ArrayRes; 147 import androidx.annotation.IntegerRes; 148 import androidx.annotation.NonNull; 149 import androidx.annotation.Nullable; 150 import androidx.annotation.StringRes; 151 import androidx.annotation.VisibleForTesting; 152 153 import com.android.internal.annotations.GuardedBy; 154 import com.android.internal.util.RingBufferIndices; 155 import com.android.internal.util.State; 156 import com.android.internal.util.StateMachine; 157 import com.android.modules.utils.build.SdkLevel; 158 import com.android.net.module.util.DeviceConfigUtils; 159 import com.android.net.module.util.NetworkMonitorUtils; 160 import com.android.net.module.util.NetworkStackConstants; 161 import com.android.net.module.util.SharedLog; 162 import com.android.networkstack.NetworkStackNotifier; 163 import com.android.networkstack.R; 164 import com.android.networkstack.apishim.CaptivePortalDataShimImpl; 165 import com.android.networkstack.apishim.NetworkAgentConfigShimImpl; 166 import com.android.networkstack.apishim.NetworkInformationShimImpl; 167 import com.android.networkstack.apishim.api29.ConstantsShim; 168 import com.android.networkstack.apishim.common.CaptivePortalDataShim; 169 import com.android.networkstack.apishim.common.NetworkAgentConfigShim; 170 import com.android.networkstack.apishim.common.NetworkInformationShim; 171 import com.android.networkstack.apishim.common.ShimUtils; 172 import com.android.networkstack.apishim.common.UnsupportedApiLevelException; 173 import com.android.networkstack.metrics.DataStallDetectionStats; 174 import com.android.networkstack.metrics.DataStallStatsUtils; 175 import com.android.networkstack.metrics.NetworkValidationMetrics; 176 import com.android.networkstack.netlink.TcpSocketTracker; 177 import com.android.networkstack.util.DnsUtils; 178 import com.android.networkstack.util.NetworkStackUtils; 179 import com.android.server.NetworkStackService.NetworkStackServiceManager; 180 181 import org.json.JSONException; 182 import org.json.JSONObject; 183 184 import java.io.BufferedInputStream; 185 import java.io.IOException; 186 import java.io.InputStream; 187 import java.io.InputStreamReader; 188 import java.io.InterruptedIOException; 189 import java.net.HttpURLConnection; 190 import java.net.InetAddress; 191 import java.net.MalformedURLException; 192 import java.net.URL; 193 import java.net.UnknownHostException; 194 import java.nio.charset.Charset; 195 import java.nio.charset.StandardCharsets; 196 import java.util.ArrayList; 197 import java.util.Arrays; 198 import java.util.Collections; 199 import java.util.HashMap; 200 import java.util.LinkedHashMap; 201 import java.util.List; 202 import java.util.Map; 203 import java.util.Objects; 204 import java.util.Random; 205 import java.util.StringJoiner; 206 import java.util.UUID; 207 import java.util.concurrent.CompletionService; 208 import java.util.concurrent.CountDownLatch; 209 import java.util.concurrent.ExecutionException; 210 import java.util.concurrent.ExecutorCompletionService; 211 import java.util.concurrent.ExecutorService; 212 import java.util.concurrent.Executors; 213 import java.util.concurrent.Future; 214 import java.util.concurrent.TimeUnit; 215 import java.util.concurrent.atomic.AtomicInteger; 216 import java.util.function.Function; 217 import java.util.regex.Matcher; 218 import java.util.regex.Pattern; 219 import java.util.regex.PatternSyntaxException; 220 221 /** 222 * {@hide} 223 */ 224 public class NetworkMonitor extends StateMachine { 225 private static final String TAG = NetworkMonitor.class.getSimpleName(); 226 private static final boolean DBG = true; 227 private static final boolean VDBG = false; 228 private static final boolean DDBG_STALL = false; 229 private static final boolean VDBG_STALL = Log.isLoggable(TAG, Log.DEBUG); 230 private static final String DEFAULT_USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) " 231 + "AppleWebKit/537.36 (KHTML, like Gecko) " 232 + "Chrome/60.0.3112.32 Safari/537.36"; 233 234 @VisibleForTesting 235 static final String CONFIG_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT = 236 "captive_portal_dns_probe_timeout"; 237 238 private static final int SOCKET_TIMEOUT_MS = 10000; 239 private static final int PROBE_TIMEOUT_MS = 3000; 240 private static final long TEST_URL_EXPIRATION_MS = TimeUnit.MINUTES.toMillis(10); 241 242 private static final int UNSET_MCC_OR_MNC = -1; 243 244 private static final int CAPPORT_API_MAX_JSON_LENGTH = 4096; 245 private static final String ACCEPT_HEADER = "Accept"; 246 private static final String CONTENT_TYPE_HEADER = "Content-Type"; 247 private static final String CAPPORT_API_CONTENT_TYPE = "application/captive+json"; 248 249 enum EvaluationResult { 250 VALIDATED(true), 251 CAPTIVE_PORTAL(false); 252 final boolean mIsValidated; EvaluationResult(boolean isValidated)253 EvaluationResult(boolean isValidated) { 254 this.mIsValidated = isValidated; 255 } 256 } 257 258 enum ValidationStage { 259 FIRST_VALIDATION(true), 260 REVALIDATION(false); 261 final boolean mIsFirstValidation; ValidationStage(boolean isFirstValidation)262 ValidationStage(boolean isFirstValidation) { 263 this.mIsFirstValidation = isFirstValidation; 264 } 265 } 266 267 @VisibleForTesting 268 protected static final class MccMncOverrideInfo { 269 public final int mcc; 270 public final int mnc; MccMncOverrideInfo(int mcc, int mnc)271 MccMncOverrideInfo(int mcc, int mnc) { 272 this.mcc = mcc; 273 this.mnc = mnc; 274 } 275 } 276 277 @VisibleForTesting 278 protected static final SparseArray<MccMncOverrideInfo> sCarrierIdToMccMnc = new SparseArray<>(); 279 280 static { 281 // CTC 282 // This is a wrong config, but it may need to be here for a while since the 283 // carrier_list.textpb in OEM side may still wrong. 284 // TODO: Remove this wrong config when the carrier_list.textpb is corrected everywhere. 285 sCarrierIdToMccMnc.put(1854, new MccMncOverrideInfo(460, 03)); 286 // China telecom. 287 sCarrierIdToMccMnc.put(2237, new MccMncOverrideInfo(460, 03)); 288 } 289 290 /** 291 * ConnectivityService has sent a notification to indicate that network has connected. 292 * Initiates Network Validation. 293 */ 294 private static final int CMD_NETWORK_CONNECTED = 1; 295 296 /** 297 * Message to self indicating it's time to evaluate a network's connectivity. 298 * arg1 = Token to ignore old messages. 299 */ 300 private static final int CMD_REEVALUATE = 6; 301 302 /** 303 * ConnectivityService has sent a notification to indicate that network has disconnected. 304 */ 305 private static final int CMD_NETWORK_DISCONNECTED = 7; 306 307 /** 308 * Force evaluation even if it has succeeded in the past. 309 * arg1 = UID responsible for requesting this reeval. Will be billed for data. 310 */ 311 private static final int CMD_FORCE_REEVALUATION = 8; 312 313 /** 314 * Message to self indicating captive portal app finished. 315 * arg1 = one of: APP_RETURN_DISMISSED, 316 * APP_RETURN_UNWANTED, 317 * APP_RETURN_WANTED_AS_IS 318 * obj = mCaptivePortalLoggedInResponseToken as String 319 */ 320 private static final int CMD_CAPTIVE_PORTAL_APP_FINISHED = 9; 321 322 /** 323 * Message indicating sign-in app should be launched. 324 * Sent by mLaunchCaptivePortalAppBroadcastReceiver when the 325 * user touches the sign in notification, or sent by 326 * ConnectivityService when the user touches the "sign into 327 * network" button in the wifi access point detail page. 328 */ 329 private static final int CMD_LAUNCH_CAPTIVE_PORTAL_APP = 11; 330 331 /** 332 * Retest network to see if captive portal is still in place. 333 * arg1 = UID responsible for requesting this reeval. Will be billed for data. 334 * 0 indicates self-initiated, so nobody to blame. 335 */ 336 private static final int CMD_CAPTIVE_PORTAL_RECHECK = 12; 337 338 /** 339 * ConnectivityService notifies NetworkMonitor of settings changes to 340 * Private DNS. If a DNS resolution is required, e.g. for DNS-over-TLS in 341 * strict mode, then an event is sent back to ConnectivityService with the 342 * result of the resolution attempt. 343 * 344 * A separate message is used to trigger (re)evaluation of the Private DNS 345 * configuration, so that the message can be handled as needed in different 346 * states, including being ignored until after an ongoing captive portal 347 * validation phase is completed. 348 */ 349 private static final int CMD_PRIVATE_DNS_SETTINGS_CHANGED = 13; 350 private static final int CMD_EVALUATE_PRIVATE_DNS = 15; 351 352 /** 353 * Message to self indicating captive portal detection is completed. 354 * obj = CaptivePortalProbeResult for detection result; 355 */ 356 private static final int CMD_PROBE_COMPLETE = 16; 357 358 /** 359 * ConnectivityService notifies NetworkMonitor of DNS query responses event. 360 * arg1 = returncode in OnDnsEvent which indicates the response code for the DNS query. 361 */ 362 private static final int EVENT_DNS_NOTIFICATION = 17; 363 364 /** 365 * ConnectivityService notifies NetworkMonitor that the user accepts partial connectivity and 366 * NetworkMonitor should ignore the https probe. 367 */ 368 private static final int EVENT_ACCEPT_PARTIAL_CONNECTIVITY = 18; 369 370 /** 371 * ConnectivityService notifies NetworkMonitor of changed LinkProperties. 372 * obj = new LinkProperties. 373 */ 374 private static final int EVENT_LINK_PROPERTIES_CHANGED = 19; 375 376 /** 377 * ConnectivityService notifies NetworkMonitor of changed NetworkCapabilities. 378 * obj = new NetworkCapabilities. 379 */ 380 private static final int EVENT_NETWORK_CAPABILITIES_CHANGED = 20; 381 382 /** 383 * Message to self to poll current tcp status from kernel. 384 */ 385 private static final int EVENT_POLL_TCPINFO = 21; 386 387 /** 388 * Message to self to do the bandwidth check in EvaluatingBandwidthState. 389 */ 390 private static final int CMD_EVALUATE_BANDWIDTH = 22; 391 392 /** 393 * Message to self to know the bandwidth check is completed. 394 */ 395 private static final int CMD_BANDWIDTH_CHECK_COMPLETE = 23; 396 397 /** 398 * Message to self to know the bandwidth check has timed out. 399 */ 400 private static final int CMD_BANDWIDTH_CHECK_TIMEOUT = 24; 401 402 /** 403 * Message to self to notify resource configuration is changed. 404 */ 405 private static final int EVENT_RESOURCE_CONFIG_CHANGED = 25; 406 407 // Start mReevaluateDelayMs at this value and double. 408 @VisibleForTesting 409 static final int INITIAL_REEVALUATE_DELAY_MS = 1000; 410 private static final int MAX_REEVALUATE_DELAY_MS = 10 * 60 * 1000; 411 // Default timeout of evaluating network bandwidth. 412 private static final int DEFAULT_EVALUATING_BANDWIDTH_TIMEOUT_MS = 10_000; 413 // Before network has been evaluated this many times, ignore repeated reevaluate requests. 414 private static final int IGNORE_REEVALUATE_ATTEMPTS = 5; 415 private int mReevaluateToken = 0; 416 private static final int NO_UID = 0; 417 private static final int INVALID_UID = -1; 418 private int mUidResponsibleForReeval = INVALID_UID; 419 // Stop blaming UID that requested re-evaluation after this many attempts. 420 private static final int BLAME_FOR_EVALUATION_ATTEMPTS = 5; 421 // Delay between reevaluations once a captive portal has been found. 422 private static final int CAPTIVE_PORTAL_REEVALUATE_DELAY_MS = 10 * 60 * 1000; 423 private static final int NETWORK_VALIDATION_RESULT_INVALID = 0; 424 // Max thread pool size for parallel probing. Fixed thread pool size to control the thread 425 // number used for either HTTP or HTTPS probing. 426 @VisibleForTesting 427 static final int MAX_PROBE_THREAD_POOL_SIZE = 5; 428 private String mPrivateDnsProviderHostname = ""; 429 430 private final Context mContext; 431 private final INetworkMonitorCallbacks mCallback; 432 private final int mCallbackVersion; 433 private final Network mCleartextDnsNetwork; 434 @NonNull 435 private final Network mNetwork; 436 private final TelephonyManager mTelephonyManager; 437 private final WifiManager mWifiManager; 438 private final ConnectivityManager mCm; 439 @Nullable 440 private final NetworkStackNotifier mNotifier; 441 private final IpConnectivityLog mMetricsLog; 442 private final Dependencies mDependencies; 443 private final TcpSocketTracker mTcpTracker; 444 // Configuration values for captive portal detection probes. 445 private final String mCaptivePortalUserAgent; 446 // Configuration values in setting providers for captive portal detection probes 447 private final String mCaptivePortalHttpsUrlFromSetting; 448 private final String mCaptivePortalHttpUrlFromSetting; 449 @Nullable 450 private final URL mTestCaptivePortalHttpsUrl; 451 @Nullable 452 private final URL mTestCaptivePortalHttpUrl; 453 @Nullable 454 private final CaptivePortalProbeSpec[] mCaptivePortalFallbackSpecs; 455 456 // The probing URLs may be updated after constructor if system notifies configuration changed. 457 // Thus, these probing URLs should only be accessed in the StateMachine thread. 458 @NonNull 459 private URL[] mCaptivePortalFallbackUrls; 460 @NonNull 461 private URL[] mCaptivePortalHttpUrls; 462 @NonNull 463 private URL[] mCaptivePortalHttpsUrls; 464 465 // Configuration values for network bandwidth check. 466 @Nullable 467 private final String mEvaluatingBandwidthUrl; 468 private final int mMaxRetryTimerMs; 469 private final int mEvaluatingBandwidthTimeoutMs; 470 private final AtomicInteger mNextEvaluatingBandwidthThreadId = new AtomicInteger(1); 471 472 @NonNull 473 private NetworkAgentConfigShim mNetworkAgentConfig; 474 @NonNull 475 private NetworkCapabilities mNetworkCapabilities; 476 @NonNull 477 private LinkProperties mLinkProperties; 478 479 private final boolean mIsCaptivePortalCheckEnabled; 480 481 private boolean mUseHttps; 482 /** 483 * The total number of completed validation attempts (network validated or a captive portal was 484 * detected) for this NetworkMonitor instance. 485 * This does not include attempts that were interrupted, retried or finished with a result that 486 * is not success or portal. See {@code mValidationIndex} in {@link NetworkValidationMetrics} 487 * for a count of all attempts. 488 * TODO: remove when removing legacy metrics. 489 */ 490 private int mValidations = 0; 491 492 // Set if the user explicitly selected "Do not use this network" in captive portal sign-in app. 493 private boolean mUserDoesNotWant = false; 494 // Avoids surfacing "Sign in to network" notification. 495 private boolean mDontDisplaySigninNotification = false; 496 // Set to true once the evaluating network bandwidth is passed or the captive portal respond 497 // APP_RETURN_WANTED_AS_IS which means the user wants to use this network anyway. 498 @VisibleForTesting 499 protected boolean mIsBandwidthCheckPassedOrIgnored = false; 500 501 private final State mDefaultState = new DefaultState(); 502 private final State mValidatedState = new ValidatedState(); 503 private final State mMaybeNotifyState = new MaybeNotifyState(); 504 private final State mEvaluatingState = new EvaluatingState(); 505 private final State mCaptivePortalState = new CaptivePortalState(); 506 private final State mEvaluatingPrivateDnsState = new EvaluatingPrivateDnsState(); 507 private final State mProbingState = new ProbingState(); 508 private final State mWaitingForNextProbeState = new WaitingForNextProbeState(); 509 private final State mEvaluatingBandwidthState = new EvaluatingBandwidthState(); 510 511 private CustomIntentReceiver mLaunchCaptivePortalAppBroadcastReceiver = null; 512 513 private final SharedLog mValidationLogs; 514 515 private final Stopwatch mEvaluationTimer = new Stopwatch(); 516 517 // This variable is set before transitioning to the mCaptivePortalState. 518 private CaptivePortalProbeResult mLastPortalProbeResult = 519 CaptivePortalProbeResult.failed(CaptivePortalProbeResult.PROBE_UNKNOWN); 520 521 // Random generator to select fallback URL index 522 private final Random mRandom; 523 private int mNextFallbackUrlIndex = 0; 524 525 526 private int mReevaluateDelayMs = INITIAL_REEVALUATE_DELAY_MS; 527 private int mEvaluateAttempts = 0; 528 private volatile int mProbeToken = 0; 529 private final int mConsecutiveDnsTimeoutThreshold; 530 private final int mDataStallMinEvaluateTime; 531 private final int mDataStallValidDnsTimeThreshold; 532 private final int mDataStallEvaluationType; 533 @Nullable 534 private final DnsStallDetector mDnsStallDetector; 535 private long mLastProbeTime; 536 // A bitmask of signals causing a data stall to be suspected. Reset to 537 // {@link DataStallUtils#DATA_STALL_EVALUATION_TYPE_NONE} after metrics are sent to statsd. 538 private @EvaluationType int mDataStallTypeToCollect; 539 private boolean mAcceptPartialConnectivity = false; 540 private final EvaluationState mEvaluationState = new EvaluationState(); 541 @NonNull 542 private final BroadcastReceiver mConfigurationReceiver; 543 private final boolean mPrivateIpNoInternetEnabled; 544 545 private final boolean mMetricsEnabled; 546 @NonNull 547 private final NetworkInformationShim mInfoShim = NetworkInformationShimImpl.newInstance(); 548 549 // The validation metrics are accessed by individual probe threads, and by the StateMachine 550 // thread. All accesses must be synchronized to make sure the StateMachine thread can see 551 // reports from all probes. 552 // TODO: as that most usage is in the StateMachine thread and probes only add their probe 553 // events, consider having probes return their stats to the StateMachine, and only access this 554 // member on the StateMachine thread without synchronization. 555 @GuardedBy("mNetworkValidationMetrics") 556 private final NetworkValidationMetrics mNetworkValidationMetrics = 557 new NetworkValidationMetrics(); 558 getCallbackVersion(INetworkMonitorCallbacks cb)559 private int getCallbackVersion(INetworkMonitorCallbacks cb) { 560 int version; 561 try { 562 version = cb.getInterfaceVersion(); 563 } catch (RemoteException e) { 564 version = 0; 565 } 566 // The AIDL was freezed from Q beta 5 but it's unfreezing from R before releasing. In order 567 // to distinguish the behavior between R and Q beta 5 and before Q beta 5, add SDK and 568 // CODENAME check here. Basically, it's only expected to return 0 for Q beta 4 and below 569 // because the test result has changed. 570 if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q 571 && Build.VERSION.CODENAME.equals("REL") 572 && version == Build.VERSION_CODES.CUR_DEVELOPMENT) version = 0; 573 return version; 574 } 575 NetworkMonitor(Context context, INetworkMonitorCallbacks cb, Network network, SharedLog validationLog, @NonNull NetworkStackServiceManager serviceManager)576 public NetworkMonitor(Context context, INetworkMonitorCallbacks cb, Network network, 577 SharedLog validationLog, @NonNull NetworkStackServiceManager serviceManager) { 578 this(context, cb, network, new IpConnectivityLog(), validationLog, serviceManager, 579 Dependencies.DEFAULT, getTcpSocketTrackerOrNull(context, network, 580 Dependencies.DEFAULT)); 581 } 582 583 @VisibleForTesting NetworkMonitor(Context context, INetworkMonitorCallbacks cb, Network network, IpConnectivityLog logger, SharedLog validationLogs, @NonNull NetworkStackServiceManager serviceManager, Dependencies deps, @Nullable TcpSocketTracker tst)584 public NetworkMonitor(Context context, INetworkMonitorCallbacks cb, Network network, 585 IpConnectivityLog logger, SharedLog validationLogs, 586 @NonNull NetworkStackServiceManager serviceManager, Dependencies deps, 587 @Nullable TcpSocketTracker tst) { 588 // Add suffix indicating which NetworkMonitor we're talking about. 589 super(TAG + "/" + network.toString()); 590 591 // Logs with a tag of the form given just above, e.g. 592 // <timestamp> 862 2402 D NetworkMonitor/NetworkAgentInfo [WIFI () - 100]: ... 593 setDbg(VDBG); 594 595 mContext = context; 596 mMetricsLog = logger; 597 mValidationLogs = validationLogs; 598 mCallback = cb; 599 mCallbackVersion = getCallbackVersion(cb); 600 mDependencies = deps; 601 mNetwork = network; 602 mCleartextDnsNetwork = deps.getPrivateDnsBypassNetwork(network); 603 mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); 604 mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); 605 mCm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 606 mNotifier = serviceManager.getNotifier(); 607 608 // CHECKSTYLE:OFF IndentationCheck 609 addState(mDefaultState); 610 addState(mMaybeNotifyState, mDefaultState); 611 addState(mEvaluatingState, mMaybeNotifyState); 612 addState(mProbingState, mEvaluatingState); 613 addState(mWaitingForNextProbeState, mEvaluatingState); 614 addState(mCaptivePortalState, mMaybeNotifyState); 615 addState(mEvaluatingPrivateDnsState, mDefaultState); 616 addState(mEvaluatingBandwidthState, mDefaultState); 617 addState(mValidatedState, mDefaultState); 618 setInitialState(mDefaultState); 619 // CHECKSTYLE:ON IndentationCheck 620 621 mCaptivePortalHttpsUrlFromSetting = 622 mDependencies.getSetting(context, CAPTIVE_PORTAL_HTTPS_URL, null); 623 mCaptivePortalHttpUrlFromSetting = 624 mDependencies.getSetting(context, CAPTIVE_PORTAL_HTTP_URL, null); 625 mTestCaptivePortalHttpsUrl = 626 getTestUrl(TEST_CAPTIVE_PORTAL_HTTPS_URL, validationLogs, deps); 627 mTestCaptivePortalHttpUrl = getTestUrl(TEST_CAPTIVE_PORTAL_HTTP_URL, validationLogs, deps); 628 mIsCaptivePortalCheckEnabled = getIsCaptivePortalCheckEnabled(context, deps); 629 mPrivateIpNoInternetEnabled = getIsPrivateIpNoInternetEnabled(); 630 mMetricsEnabled = deps.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY, 631 NetworkStackUtils.VALIDATION_METRICS_VERSION, true /* defaultEnabled */); 632 mUseHttps = getUseHttpsValidation(); 633 mCaptivePortalUserAgent = getCaptivePortalUserAgent(); 634 mCaptivePortalFallbackSpecs = 635 makeCaptivePortalFallbackProbeSpecs(getCustomizedContextOrDefault()); 636 mRandom = deps.getRandom(); 637 // TODO: Evaluate to move data stall configuration to a specific class. 638 mConsecutiveDnsTimeoutThreshold = getConsecutiveDnsTimeoutThreshold(); 639 mDataStallMinEvaluateTime = getDataStallMinEvaluateTime(); 640 mDataStallValidDnsTimeThreshold = getDataStallValidDnsTimeThreshold(); 641 mDataStallEvaluationType = getDataStallEvaluationType(); 642 mDnsStallDetector = initDnsStallDetectorIfRequired(mIsCaptivePortalCheckEnabled, 643 mDataStallEvaluationType, mConsecutiveDnsTimeoutThreshold); 644 mTcpTracker = tst; 645 // Read the configurations of evaluating network bandwidth. 646 mEvaluatingBandwidthUrl = getResStringConfig(mContext, 647 R.string.config_evaluating_bandwidth_url, null); 648 mMaxRetryTimerMs = getResIntConfig(mContext, 649 R.integer.config_evaluating_bandwidth_max_retry_timer_ms, 650 MAX_REEVALUATE_DELAY_MS); 651 mEvaluatingBandwidthTimeoutMs = getResIntConfig(mContext, 652 R.integer.config_evaluating_bandwidth_timeout_ms, 653 DEFAULT_EVALUATING_BANDWIDTH_TIMEOUT_MS); 654 mConfigurationReceiver = new BroadcastReceiver() { 655 @Override 656 public void onReceive(Context context, Intent intent) { 657 if (ACTION_CONFIGURATION_CHANGED.equals(intent.getAction())) { 658 sendMessage(EVENT_RESOURCE_CONFIG_CHANGED); 659 } 660 } 661 }; 662 // Provide empty LinkProperties and NetworkCapabilities to make sure they are never null, 663 // even before notifyNetworkConnected. 664 mLinkProperties = new LinkProperties(); 665 mNetworkCapabilities = new NetworkCapabilities(null); 666 mNetworkAgentConfig = NetworkAgentConfigShimImpl.newInstance(null); 667 } 668 669 /** 670 * ConnectivityService notifies NetworkMonitor that the user already accepted partial 671 * connectivity previously, so NetworkMonitor can validate the network even if it has partial 672 * connectivity. 673 */ setAcceptPartialConnectivity()674 public void setAcceptPartialConnectivity() { 675 sendMessage(EVENT_ACCEPT_PARTIAL_CONNECTIVITY); 676 } 677 678 /** 679 * Request the NetworkMonitor to reevaluate the network. 680 * 681 * TODO : refactor reevaluation to introduce rate limiting. If the system finds a network is 682 * validated but some app can't access their server, or the network is behind a captive portal 683 * that only lets the validation URL through, apps may be calling reportNetworkConnectivity 684 * often, causing many revalidation attempts. Meanwhile, reevaluation attempts that result 685 * from actions that may affect the validation status (e.g. the user just logged in through 686 * the captive portal app) should never be skipped because of the rate limitation. 687 */ forceReevaluation(int responsibleUid)688 public void forceReevaluation(int responsibleUid) { 689 sendMessage(CMD_FORCE_REEVALUATION, responsibleUid, 0); 690 } 691 692 /** 693 * Send a notification to NetworkMonitor indicating that there was a DNS query response event. 694 * @param returnCode the DNS return code of the response. 695 */ notifyDnsResponse(int returnCode)696 public void notifyDnsResponse(int returnCode) { 697 sendMessage(EVENT_DNS_NOTIFICATION, returnCode); 698 } 699 700 /** 701 * Send a notification to NetworkMonitor indicating that private DNS settings have changed. 702 * @param newCfg The new private DNS configuration. 703 */ notifyPrivateDnsSettingsChanged(PrivateDnsConfig newCfg)704 public void notifyPrivateDnsSettingsChanged(PrivateDnsConfig newCfg) { 705 // Cancel any outstanding resolutions. 706 removeMessages(CMD_PRIVATE_DNS_SETTINGS_CHANGED); 707 // Send the update to the proper thread. 708 sendMessage(CMD_PRIVATE_DNS_SETTINGS_CHANGED, newCfg); 709 } 710 711 /** 712 * Send a notification to NetworkMonitor indicating that the network is now connected. 713 * @Deprecated use notifyNetworkConnectedParcel. This method is called on S-. 714 */ notifyNetworkConnected(LinkProperties lp, NetworkCapabilities nc)715 public void notifyNetworkConnected(LinkProperties lp, NetworkCapabilities nc) { 716 final NetworkMonitorParameters params = new NetworkMonitorParameters(); 717 params.linkProperties = lp; 718 params.networkCapabilities = nc; 719 notifyNetworkConnectedParcel(params); 720 } 721 722 /** 723 * Send a notification to NetworkMonitor indicating that the network is now connected. 724 * Called in S when the Connectivity module is recent enough, or in T+ in all cases. 725 */ notifyNetworkConnectedParcel(NetworkMonitorParameters params)726 public void notifyNetworkConnectedParcel(NetworkMonitorParameters params) { 727 sendMessage(CMD_NETWORK_CONNECTED, params); 728 } 729 updateConnectedNetworkAttributes(Message connectedMsg)730 private void updateConnectedNetworkAttributes(Message connectedMsg) { 731 final NetworkMonitorParameters params = (NetworkMonitorParameters) connectedMsg.obj; 732 mNetworkAgentConfig = NetworkAgentConfigShimImpl.newInstance(params.networkAgentConfig); 733 mLinkProperties = params.linkProperties; 734 mNetworkCapabilities = params.networkCapabilities; 735 suppressNotificationIfNetworkRestricted(); 736 } 737 738 /** 739 * Send a notification to NetworkMonitor indicating that the network is now disconnected. 740 */ notifyNetworkDisconnected()741 public void notifyNetworkDisconnected() { 742 sendMessage(CMD_NETWORK_DISCONNECTED); 743 } 744 745 /** 746 * Send a notification to NetworkMonitor indicating that link properties have changed. 747 */ notifyLinkPropertiesChanged(final LinkProperties lp)748 public void notifyLinkPropertiesChanged(final LinkProperties lp) { 749 sendMessage(EVENT_LINK_PROPERTIES_CHANGED, new LinkProperties(lp)); 750 } 751 752 /** 753 * Send a notification to NetworkMonitor indicating that network capabilities have changed. 754 */ notifyNetworkCapabilitiesChanged(final NetworkCapabilities nc)755 public void notifyNetworkCapabilitiesChanged(final NetworkCapabilities nc) { 756 sendMessage(EVENT_NETWORK_CAPABILITIES_CHANGED, new NetworkCapabilities(nc)); 757 } 758 759 /** 760 * Request the captive portal application to be launched. 761 */ launchCaptivePortalApp()762 public void launchCaptivePortalApp() { 763 sendMessage(CMD_LAUNCH_CAPTIVE_PORTAL_APP); 764 } 765 766 /** 767 * Notify that the captive portal app was closed with the provided response code. 768 */ notifyCaptivePortalAppFinished(int response)769 public void notifyCaptivePortalAppFinished(int response) { 770 sendMessage(CMD_CAPTIVE_PORTAL_APP_FINISHED, response); 771 } 772 773 @Override log(String s)774 protected void log(String s) { 775 if (DBG) Log.d(TAG + "/" + mCleartextDnsNetwork.toString(), s); 776 } 777 validationLog(int probeType, Object url, String msg)778 private void validationLog(int probeType, Object url, String msg) { 779 String probeName = ValidationProbeEvent.getProbeName(probeType); 780 validationLog(String.format("%s %s %s", probeName, url, msg)); 781 } 782 validationLog(String s)783 private void validationLog(String s) { 784 if (DBG) log(s); 785 mValidationLogs.log(s); 786 } 787 validationStage()788 private ValidationStage validationStage() { 789 return 0 == mValidations ? ValidationStage.FIRST_VALIDATION : ValidationStage.REVALIDATION; 790 } 791 isValidationRequired()792 private boolean isValidationRequired() { 793 final boolean dunValidationRequired = isAtLeastU() 794 || mContext.getResources().getBoolean(R.bool.config_validate_dun_networks); 795 return NetworkMonitorUtils.isValidationRequired(dunValidationRequired, 796 mNetworkAgentConfig.isVpnValidationRequired(), mNetworkCapabilities); 797 } 798 isDataStallDetectionRequired()799 private boolean isDataStallDetectionRequired() { 800 return mIsCaptivePortalCheckEnabled && isValidationRequired(); 801 } 802 isPrivateDnsValidationRequired()803 private boolean isPrivateDnsValidationRequired() { 804 return NetworkMonitorUtils.isPrivateDnsValidationRequired(mNetworkCapabilities); 805 } 806 suppressNotificationIfNetworkRestricted()807 private void suppressNotificationIfNetworkRestricted() { 808 if (!mNetworkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)) { 809 mDontDisplaySigninNotification = true; 810 } 811 } 812 notifyNetworkTested(NetworkTestResultParcelable result)813 private void notifyNetworkTested(NetworkTestResultParcelable result) { 814 try { 815 if (mCallbackVersion <= 5) { 816 mCallback.notifyNetworkTested( 817 getLegacyTestResult(result.result, result.probesSucceeded), 818 result.redirectUrl); 819 } else { 820 mCallback.notifyNetworkTestedWithExtras(result); 821 } 822 } catch (RemoteException e) { 823 Log.e(TAG, "Error sending network test result", e); 824 } 825 } 826 827 /** 828 * Get the test result that was used as an int up to interface version 5. 829 * 830 * <p>For callback version < 3 (only used in Q beta preview builds), the int represented one of 831 * the NETWORK_TEST_RESULT_* constants. 832 * 833 * <p>Q released with version 3, which used a single int for both the evaluation result bitmask, 834 * and the probesSucceeded bitmask. 835 */ getLegacyTestResult(int evaluationResult, int probesSucceeded)836 protected int getLegacyTestResult(int evaluationResult, int probesSucceeded) { 837 if (mCallbackVersion < 3) { 838 if ((evaluationResult & NETWORK_VALIDATION_RESULT_VALID) != 0) { 839 return NETWORK_TEST_RESULT_VALID; 840 } 841 if ((evaluationResult & NETWORK_VALIDATION_RESULT_PARTIAL) != 0) { 842 return NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY; 843 } 844 return NETWORK_TEST_RESULT_INVALID; 845 } 846 847 return evaluationResult | probesSucceeded; 848 } 849 notifyProbeStatusChanged(int probesCompleted, int probesSucceeded)850 private void notifyProbeStatusChanged(int probesCompleted, int probesSucceeded) { 851 try { 852 mCallback.notifyProbeStatusChanged(probesCompleted, probesSucceeded); 853 } catch (RemoteException e) { 854 Log.e(TAG, "Error sending probe status", e); 855 } 856 } 857 showProvisioningNotification(String action)858 private void showProvisioningNotification(String action) { 859 try { 860 mCallback.showProvisioningNotification(action, mContext.getPackageName()); 861 } catch (RemoteException e) { 862 Log.e(TAG, "Error showing provisioning notification", e); 863 } 864 } 865 hideProvisioningNotification()866 private void hideProvisioningNotification() { 867 try { 868 mCallback.hideProvisioningNotification(); 869 } catch (RemoteException e) { 870 Log.e(TAG, "Error hiding provisioning notification", e); 871 } 872 } 873 notifyDataStallSuspected(@onNull DataStallReportParcelable p)874 private void notifyDataStallSuspected(@NonNull DataStallReportParcelable p) { 875 try { 876 mCallback.notifyDataStallSuspected(p); 877 } catch (RemoteException e) { 878 Log.e(TAG, "Error sending notification for suspected data stall", e); 879 } 880 } 881 startMetricsCollection()882 private void startMetricsCollection() { 883 if (!mMetricsEnabled) return; 884 try { 885 synchronized (mNetworkValidationMetrics) { 886 mNetworkValidationMetrics.startCollection(mNetworkCapabilities); 887 } 888 } catch (Exception e) { 889 Log.wtf(TAG, "Error resetting validation metrics", e); 890 } 891 } 892 recordProbeEventMetrics(ProbeType type, long latencyMicros, ProbeResult result, CaptivePortalDataShim capportData)893 private void recordProbeEventMetrics(ProbeType type, long latencyMicros, ProbeResult result, 894 CaptivePortalDataShim capportData) { 895 if (!mMetricsEnabled) return; 896 try { 897 synchronized (mNetworkValidationMetrics) { 898 mNetworkValidationMetrics.addProbeEvent(type, latencyMicros, result, capportData); 899 } 900 } catch (Exception e) { 901 Log.wtf(TAG, "Error recording probe event", e); 902 } 903 } 904 recordValidationResult(int result, String redirectUrl)905 private void recordValidationResult(int result, String redirectUrl) { 906 if (!mMetricsEnabled) return; 907 try { 908 synchronized (mNetworkValidationMetrics) { 909 mNetworkValidationMetrics.setValidationResult(result, redirectUrl); 910 } 911 } catch (Exception e) { 912 Log.wtf(TAG, "Error recording validation result", e); 913 } 914 } 915 maybeStopCollectionAndSendMetrics()916 private void maybeStopCollectionAndSendMetrics() { 917 if (!mMetricsEnabled) return; 918 try { 919 synchronized (mNetworkValidationMetrics) { 920 mNetworkValidationMetrics.maybeStopCollectionAndSend(); 921 } 922 } catch (Exception e) { 923 Log.wtf(TAG, "Error sending validation stats", e); 924 } 925 } 926 927 // DefaultState is the parent of all States. It exists only to handle CMD_* messages but 928 // does not entail any real state (hence no enter() or exit() routines). 929 private class DefaultState extends State { 930 @Override enter()931 public void enter() { 932 // Register configuration broadcast here instead of constructor to prevent start() was 933 // not called yet when the broadcast is received and cause crash. 934 mContext.registerReceiver(mConfigurationReceiver, 935 new IntentFilter(ACTION_CONFIGURATION_CHANGED)); 936 checkAndRenewResourceConfig(); 937 Log.d(TAG, "Starting on network " + mNetwork 938 + " with capport HTTPS URL " + Arrays.toString(mCaptivePortalHttpsUrls) 939 + " and HTTP URL " + Arrays.toString(mCaptivePortalHttpUrls)); 940 } 941 942 @Override processMessage(Message message)943 public boolean processMessage(Message message) { 944 switch (message.what) { 945 case CMD_NETWORK_CONNECTED: 946 updateConnectedNetworkAttributes(message); 947 logNetworkEvent(NetworkEvent.NETWORK_CONNECTED); 948 transitionTo(mEvaluatingState); 949 return HANDLED; 950 case CMD_NETWORK_DISCONNECTED: 951 maybeStopCollectionAndSendMetrics(); 952 logNetworkEvent(NetworkEvent.NETWORK_DISCONNECTED); 953 if (mTcpTracker != null) { 954 mTcpTracker.quit(); 955 } 956 quit(); 957 return HANDLED; 958 case CMD_FORCE_REEVALUATION: 959 case CMD_CAPTIVE_PORTAL_RECHECK: 960 if (getCurrentState() == mDefaultState) { 961 // Before receiving CMD_NETWORK_CONNECTED (when still in mDefaultState), 962 // requests to reevaluate are not valid: drop them. 963 return HANDLED; 964 } 965 String msg = "Forcing reevaluation for UID " + message.arg1; 966 final DnsStallDetector dsd = getDnsStallDetector(); 967 if (dsd != null) { 968 msg += ". Dns signal count: " + dsd.getConsecutiveTimeoutCount(); 969 } 970 validationLog(msg); 971 mUidResponsibleForReeval = message.arg1; 972 transitionTo(mEvaluatingState); 973 return HANDLED; 974 case CMD_CAPTIVE_PORTAL_APP_FINISHED: 975 log("CaptivePortal App responded with " + message.arg1); 976 977 // If the user has seen and acted on a captive portal notification, and the 978 // captive portal app is now closed, disable HTTPS probes. This avoids the 979 // following pathological situation: 980 // 981 // 1. HTTP probe returns a captive portal, HTTPS probe fails or times out. 982 // 2. User opens the app and logs into the captive portal. 983 // 3. HTTP starts working, but HTTPS still doesn't work for some other reason - 984 // perhaps due to the network blocking HTTPS? 985 // 986 // In this case, we'll fail to validate the network even after the app is 987 // dismissed. There is now no way to use this network, because the app is now 988 // gone, so the user cannot select "Use this network as is". 989 mUseHttps = false; 990 991 switch (message.arg1) { 992 case APP_RETURN_DISMISSED: 993 sendMessage(CMD_FORCE_REEVALUATION, NO_UID, 0); 994 break; 995 case APP_RETURN_WANTED_AS_IS: 996 mDontDisplaySigninNotification = true; 997 // If the user wants to use this network anyway, there is no need to 998 // perform the bandwidth check even if configured. 999 mIsBandwidthCheckPassedOrIgnored = true; 1000 // If the user wants to use this network anyway, it should always 1001 // be reported as validated, but other checks still need to be 1002 // done. For example, it should still validate strict private DNS and 1003 // show a notification if not available, because the network will 1004 // be unusable for this additional reason. 1005 mEvaluationState.setCaptivePortalWantedAsIs(); 1006 // A successful evaluation result should be reported immediately, so 1007 // that the network stack may immediately use the validation in ranking 1008 // without waiting for a possibly long private DNS or bandwidth eval 1009 // step. 1010 mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_VALID, 1011 null); 1012 // TODO: Distinguish this from a network that actually validates. 1013 // Displaying the "x" on the system UI icon may still be a good idea. 1014 transitionTo(mEvaluatingPrivateDnsState); 1015 break; 1016 case APP_RETURN_UNWANTED: 1017 mDontDisplaySigninNotification = true; 1018 mUserDoesNotWant = true; 1019 mEvaluationState.reportEvaluationResult( 1020 NETWORK_VALIDATION_RESULT_INVALID, null); 1021 1022 mUidResponsibleForReeval = 0; 1023 transitionTo(mEvaluatingState); 1024 break; 1025 } 1026 return HANDLED; 1027 case CMD_PRIVATE_DNS_SETTINGS_CHANGED: { 1028 final PrivateDnsConfig cfg = (PrivateDnsConfig) message.obj; 1029 if (!isPrivateDnsValidationRequired() || cfg == null || !cfg.inStrictMode()) { 1030 // No DNS resolution required. 1031 // 1032 // We don't force any validation in opportunistic mode 1033 // here. Opportunistic mode nameservers are validated 1034 // separately within netd. 1035 // 1036 // Reset Private DNS settings state. 1037 mPrivateDnsProviderHostname = ""; 1038 break; 1039 } 1040 1041 mPrivateDnsProviderHostname = cfg.hostname; 1042 1043 // DNS resolutions via Private DNS strict mode block for a 1044 // few seconds (~4.2) checking for any IP addresses to 1045 // arrive and validate. Initiating a (re)evaluation now 1046 // should not significantly alter the validation outcome. 1047 // 1048 // No matter what: enqueue a validation request; one of 1049 // three things can happen with this request: 1050 // [1] ignored (EvaluatingState or CaptivePortalState) 1051 // [2] transition to EvaluatingPrivateDnsState 1052 // (DefaultState and ValidatedState) 1053 // [3] handled (EvaluatingPrivateDnsState) 1054 // 1055 // The Private DNS configuration to be evaluated will: 1056 // [1] be skipped (not in strict mode), or 1057 // [2] validate (huzzah), or 1058 // [3] encounter some problem (invalid hostname, 1059 // no resolved IP addresses, IPs unreachable, 1060 // port 853 unreachable, port 853 is not running a 1061 // DNS-over-TLS server, et cetera). 1062 // Cancel any outstanding CMD_EVALUATE_PRIVATE_DNS. 1063 removeMessages(CMD_EVALUATE_PRIVATE_DNS); 1064 sendMessage(CMD_EVALUATE_PRIVATE_DNS); 1065 break; 1066 } 1067 case EVENT_DNS_NOTIFICATION: 1068 final DnsStallDetector detector = getDnsStallDetector(); 1069 if (detector != null) { 1070 detector.accumulateConsecutiveDnsTimeoutCount(message.arg1); 1071 } 1072 break; 1073 // Set mAcceptPartialConnectivity to true and if network start evaluating or 1074 // re-evaluating and get the result of partial connectivity, ProbingState will 1075 // disable HTTPS probe and transition to EvaluatingPrivateDnsState. 1076 case EVENT_ACCEPT_PARTIAL_CONNECTIVITY: 1077 maybeDisableHttpsProbing(true /* acceptPartial */); 1078 break; 1079 case EVENT_LINK_PROPERTIES_CHANGED: 1080 final Uri oldCapportUrl = getCaptivePortalApiUrl(mLinkProperties); 1081 mLinkProperties = (LinkProperties) message.obj; 1082 final Uri newCapportUrl = getCaptivePortalApiUrl(mLinkProperties); 1083 if (!Objects.equals(oldCapportUrl, newCapportUrl)) { 1084 sendMessage(CMD_FORCE_REEVALUATION, NO_UID, 0); 1085 } 1086 break; 1087 case EVENT_NETWORK_CAPABILITIES_CHANGED: 1088 final NetworkCapabilities newCap = (NetworkCapabilities) message.obj; 1089 // Reevaluate network if underlying network changes on the validation required 1090 // VPN. 1091 if (isVpnUnderlyingNetworkChangeReevaluationRequired( 1092 newCap, mNetworkCapabilities)) { 1093 sendMessage(CMD_FORCE_REEVALUATION, NO_UID, 0); 1094 } 1095 1096 mNetworkCapabilities = newCap; 1097 suppressNotificationIfNetworkRestricted(); 1098 break; 1099 case EVENT_RESOURCE_CONFIG_CHANGED: 1100 // RRO generation does not happen during package installation and instead after 1101 // the OMS receives the PACKAGE_ADDED event, there is a delay where the old 1102 // idmap is used with the new target package resulting in the incorrect overlay 1103 // is used. Renew the resource if a configuration change is received. 1104 // TODO: Remove it once design to generate the idmaps during package 1105 // installation in overlay manager and package manager is ready. 1106 if (checkAndRenewResourceConfig()) { 1107 sendMessage(CMD_FORCE_REEVALUATION, NO_UID, 1 /* forceAccept */); 1108 } 1109 break; 1110 default: 1111 break; 1112 } 1113 return HANDLED; 1114 } 1115 isVpnUnderlyingNetworkChangeReevaluationRequired( final NetworkCapabilities newCap, final NetworkCapabilities oldCap)1116 private boolean isVpnUnderlyingNetworkChangeReevaluationRequired( 1117 final NetworkCapabilities newCap, final NetworkCapabilities oldCap) { 1118 return !newCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) 1119 && isValidationRequired() 1120 && !Objects.equals(mInfoShim.getUnderlyingNetworks(newCap), 1121 mInfoShim.getUnderlyingNetworks(oldCap)); 1122 } 1123 1124 @Override exit()1125 public void exit() { 1126 mContext.unregisterReceiver(mConfigurationReceiver); 1127 } 1128 } 1129 1130 // Being in the ValidatedState State indicates a Network is: 1131 // - Successfully validated, or 1132 // - Wanted "as is" by the user, or 1133 // - Does not satisfy the default NetworkRequest and so validation has been skipped. 1134 private class ValidatedState extends State { 1135 @Override enter()1136 public void enter() { 1137 maybeLogEvaluationResult( 1138 networkEventType(validationStage(), EvaluationResult.VALIDATED)); 1139 // If the user has accepted partial connectivity and HTTPS probing is disabled, then 1140 // mark the network as validated and partial so that settings can keep informing the 1141 // user that the connection is limited. 1142 int result = NETWORK_VALIDATION_RESULT_VALID; 1143 if (!mUseHttps && mAcceptPartialConnectivity) { 1144 result |= NETWORK_VALIDATION_RESULT_PARTIAL; 1145 } 1146 mEvaluationState.reportEvaluationResult(result, null /* redirectUrl */); 1147 mValidations++; 1148 initSocketTrackingIfRequired(); 1149 // start periodical polling. 1150 sendTcpPollingEvent(); 1151 maybeStopCollectionAndSendMetrics(); 1152 } 1153 initSocketTrackingIfRequired()1154 private void initSocketTrackingIfRequired() { 1155 if (!isDataStallDetectionRequired()) return; 1156 1157 final TcpSocketTracker tst = getTcpSocketTracker(); 1158 if (tst != null) { 1159 tst.pollSocketsInfo(); 1160 } 1161 } 1162 1163 @Override processMessage(Message message)1164 public boolean processMessage(Message message) { 1165 switch (message.what) { 1166 case CMD_NETWORK_CONNECTED: 1167 updateConnectedNetworkAttributes(message); 1168 transitionTo(mValidatedState); 1169 break; 1170 case CMD_EVALUATE_PRIVATE_DNS: 1171 // TODO: this causes reevaluation of a single probe that is not counted in 1172 // metrics. Add support for such reevaluation probes in metrics, and log them 1173 // separately. 1174 transitionTo(mEvaluatingPrivateDnsState); 1175 break; 1176 case EVENT_DNS_NOTIFICATION: 1177 final DnsStallDetector dsd = getDnsStallDetector(); 1178 if (dsd == null) break; 1179 1180 dsd.accumulateConsecutiveDnsTimeoutCount(message.arg1); 1181 if (evaluateDataStall()) { 1182 transitionTo(mEvaluatingState); 1183 } 1184 break; 1185 case EVENT_POLL_TCPINFO: 1186 final TcpSocketTracker tst = getTcpSocketTracker(); 1187 if (tst == null) break; 1188 // Transit if retrieve socket info is succeeded and suspected as a stall. 1189 if (tst.pollSocketsInfo() && evaluateDataStall()) { 1190 transitionTo(mEvaluatingState); 1191 } else { 1192 sendTcpPollingEvent(); 1193 } 1194 break; 1195 default: 1196 return NOT_HANDLED; 1197 } 1198 return HANDLED; 1199 } 1200 evaluateDataStall()1201 boolean evaluateDataStall() { 1202 if (isDataStall()) { 1203 validationLog("Suspecting data stall, reevaluate"); 1204 return true; 1205 } 1206 return false; 1207 } 1208 1209 @Override exit()1210 public void exit() { 1211 // Not useful for non-ValidatedState. 1212 removeMessages(EVENT_POLL_TCPINFO); 1213 } 1214 } 1215 1216 @VisibleForTesting sendTcpPollingEvent()1217 void sendTcpPollingEvent() { 1218 if (isDataStallDetectionRequired()) { 1219 sendMessageDelayed(EVENT_POLL_TCPINFO, getTcpPollingInterval()); 1220 } 1221 } 1222 maybeWriteDataStallStats(@onNull final CaptivePortalProbeResult result)1223 private void maybeWriteDataStallStats(@NonNull final CaptivePortalProbeResult result) { 1224 if (mDataStallTypeToCollect == DATA_STALL_EVALUATION_TYPE_NONE) return; 1225 /* 1226 * Collect data stall detection level information for each transport type. Collect type 1227 * specific information for cellular and wifi only currently. Generate 1228 * DataStallDetectionStats for each transport type. E.g., if a network supports both 1229 * TRANSPORT_WIFI and TRANSPORT_VPN, two DataStallDetectionStats will be generated. 1230 */ 1231 final int[] transports = mNetworkCapabilities.getTransportTypes(); 1232 for (int i = 0; i < transports.length; i++) { 1233 final DataStallDetectionStats stats = 1234 buildDataStallDetectionStats(transports[i], mDataStallTypeToCollect); 1235 mDependencies.writeDataStallDetectionStats(stats, result); 1236 } 1237 mDataStallTypeToCollect = DATA_STALL_EVALUATION_TYPE_NONE; 1238 } 1239 1240 @VisibleForTesting buildDataStallDetectionStats(int transport, @EvaluationType int evaluationType)1241 protected DataStallDetectionStats buildDataStallDetectionStats(int transport, 1242 @EvaluationType int evaluationType) { 1243 final DataStallDetectionStats.Builder stats = new DataStallDetectionStats.Builder(); 1244 if (VDBG_STALL) { 1245 log("collectDataStallMetrics: type=" + transport + ", evaluation=" + evaluationType); 1246 } 1247 stats.setEvaluationType(evaluationType); 1248 stats.setNetworkType(transport); 1249 switch (transport) { 1250 case NetworkCapabilities.TRANSPORT_WIFI: 1251 // TODO: Update it if status query in dual wifi is supported. 1252 final WifiInfo wifiInfo = mWifiManager.getConnectionInfo(); 1253 stats.setWiFiData(wifiInfo); 1254 break; 1255 case NetworkCapabilities.TRANSPORT_CELLULAR: 1256 final boolean isRoaming = !mNetworkCapabilities.hasCapability( 1257 NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING); 1258 final SignalStrength ss = mTelephonyManager.getSignalStrength(); 1259 // TODO(b/120452078): Support multi-sim. 1260 stats.setCellData( 1261 mTelephonyManager.getDataNetworkType(), 1262 isRoaming, 1263 mTelephonyManager.getNetworkOperator(), 1264 mTelephonyManager.getSimOperator(), 1265 (ss != null) 1266 ? ss.getLevel() : CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN); 1267 break; 1268 default: 1269 // No transport type specific information for the other types. 1270 break; 1271 } 1272 1273 addDnsEvents(stats); 1274 addTcpStats(stats); 1275 1276 return stats.build(); 1277 } 1278 addTcpStats(@onNull final DataStallDetectionStats.Builder stats)1279 private void addTcpStats(@NonNull final DataStallDetectionStats.Builder stats) { 1280 final TcpSocketTracker tst = getTcpSocketTracker(); 1281 if (tst == null) return; 1282 1283 stats.setTcpSentSinceLastRecv(tst.getSentSinceLastRecv()); 1284 stats.setTcpFailRate(tst.getLatestPacketFailPercentage()); 1285 } 1286 1287 @VisibleForTesting addDnsEvents(@onNull final DataStallDetectionStats.Builder stats)1288 protected void addDnsEvents(@NonNull final DataStallDetectionStats.Builder stats) { 1289 final DnsStallDetector dsd = getDnsStallDetector(); 1290 if (dsd == null) return; 1291 1292 final int size = dsd.mResultIndices.size(); 1293 for (int i = 1; i <= DEFAULT_DNS_LOG_SIZE && i <= size; i++) { 1294 final int index = dsd.mResultIndices.indexOf(size - i); 1295 stats.addDnsEvent(dsd.mDnsEvents[index].mReturnCode, dsd.mDnsEvents[index].mTimeStamp); 1296 } 1297 } 1298 1299 1300 // Being in the MaybeNotifyState State indicates the user may have been notified that sign-in 1301 // is required. This State takes care to clear the notification upon exit from the State. 1302 private class MaybeNotifyState extends State { 1303 @Override processMessage(Message message)1304 public boolean processMessage(Message message) { 1305 switch (message.what) { 1306 case CMD_LAUNCH_CAPTIVE_PORTAL_APP: 1307 final Bundle appExtras = new Bundle(); 1308 // OneAddressPerFamilyNetwork is not parcelable across processes. 1309 final Network network = new Network(mCleartextDnsNetwork); 1310 appExtras.putParcelable(ConnectivityManager.EXTRA_NETWORK, network); 1311 final CaptivePortalProbeResult probeRes = mLastPortalProbeResult; 1312 // Use redirect URL from AP if exists. 1313 final String portalUrl = 1314 (useRedirectUrlForPortal() && makeURL(probeRes.redirectUrl) != null) 1315 ? probeRes.redirectUrl : probeRes.detectUrl; 1316 appExtras.putString(EXTRA_CAPTIVE_PORTAL_URL, portalUrl); 1317 if (probeRes.probeSpec != null) { 1318 final String encodedSpec = probeRes.probeSpec.getEncodedSpec(); 1319 appExtras.putString(EXTRA_CAPTIVE_PORTAL_PROBE_SPEC, encodedSpec); 1320 } 1321 appExtras.putString(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_USER_AGENT, 1322 mCaptivePortalUserAgent); 1323 if (mNotifier != null) { 1324 mNotifier.notifyCaptivePortalValidationPending(network); 1325 } 1326 mCm.startCaptivePortalApp(network, appExtras); 1327 return HANDLED; 1328 default: 1329 return NOT_HANDLED; 1330 } 1331 } 1332 useRedirectUrlForPortal()1333 private boolean useRedirectUrlForPortal() { 1334 // It must match the conditions in CaptivePortalLogin in which the redirect URL is not 1335 // used to validate that the portal is gone. 1336 return ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q); 1337 } 1338 1339 @Override exit()1340 public void exit() { 1341 if (mLaunchCaptivePortalAppBroadcastReceiver != null) { 1342 mContext.unregisterReceiver(mLaunchCaptivePortalAppBroadcastReceiver); 1343 mLaunchCaptivePortalAppBroadcastReceiver = null; 1344 } 1345 hideProvisioningNotification(); 1346 } 1347 } 1348 1349 // Being in the EvaluatingState State indicates the Network is being evaluated for internet 1350 // connectivity, or that the user has indicated that this network is unwanted. 1351 private class EvaluatingState extends State { 1352 private Uri mEvaluatingCapportUrl; 1353 1354 @Override enter()1355 public void enter() { 1356 // If we have already started to track time spent in EvaluatingState 1357 // don't reset the timer due simply to, say, commands or events that 1358 // cause us to exit and re-enter EvaluatingState. 1359 if (!mEvaluationTimer.isStarted()) { 1360 mEvaluationTimer.start(); 1361 } 1362 1363 // Check if the network is captive with Terms & Conditions page. The first network 1364 // evaluation for captive networks with T&Cs returns early but NetworkMonitor will then 1365 // keep checking for connectivity to determine when the T&Cs are cleared. 1366 if (isTermsAndConditionsCaptive(mInfoShim.getCaptivePortalData(mLinkProperties)) 1367 && mValidations == 0) { 1368 mLastPortalProbeResult = new CaptivePortalProbeResult( 1369 CaptivePortalProbeResult.PORTAL_CODE, 1370 mLinkProperties.getCaptivePortalData().getUserPortalUrl() 1371 .toString(), null, 1372 CaptivePortalProbeResult.PROBE_UNKNOWN); 1373 mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_INVALID, 1374 mLastPortalProbeResult.redirectUrl); 1375 transitionTo(mCaptivePortalState); 1376 return; 1377 } 1378 sendMessage(CMD_REEVALUATE, ++mReevaluateToken, 0); 1379 if (mUidResponsibleForReeval != INVALID_UID) { 1380 TrafficStats.setThreadStatsUid(mUidResponsibleForReeval); 1381 mUidResponsibleForReeval = INVALID_UID; 1382 } 1383 mReevaluateDelayMs = INITIAL_REEVALUATE_DELAY_MS; 1384 mEvaluateAttempts = 0; 1385 mEvaluatingCapportUrl = getCaptivePortalApiUrl(mLinkProperties); 1386 // Reset all current probe results to zero, but retain current validation state until 1387 // validation succeeds or fails. 1388 mEvaluationState.clearProbeResults(); 1389 } 1390 1391 @Override processMessage(Message message)1392 public boolean processMessage(Message message) { 1393 switch (message.what) { 1394 case CMD_REEVALUATE: 1395 if (message.arg1 != mReevaluateToken || mUserDoesNotWant) { 1396 return HANDLED; 1397 } 1398 // Don't bother validating networks that don't satisfy the default request. 1399 // This includes: 1400 // - VPNs which can be considered explicitly desired by the user and the 1401 // user's desire trumps whether the network validates. 1402 // - Networks that don't provide Internet access. It's unclear how to 1403 // validate such networks. 1404 // - Untrusted networks. It's unsafe to prompt the user to sign-in to 1405 // such networks and the user didn't express interest in connecting to 1406 // such networks (an app did) so the user may be unhappily surprised when 1407 // asked to sign-in to a network they didn't want to connect to in the 1408 // first place. Validation could be done to adjust the network scores 1409 // however these networks are app-requested and may not be intended for 1410 // general usage, in which case general validation may not be an accurate 1411 // measure of the network's quality. Only the app knows how to evaluate 1412 // the network so don't bother validating here. Furthermore sending HTTP 1413 // packets over the network may be undesirable, for example an extremely 1414 // expensive metered network, or unwanted leaking of the User Agent string. 1415 // Also don't bother validating networks that the user already said they 1416 // wanted as-is. 1417 // 1418 // On networks that need to support private DNS in strict mode (e.g., VPNs, but 1419 // not networks that don't provide Internet access), we still need to perform 1420 // private DNS server resolution. 1421 if (mEvaluationState.isCaptivePortalWantedAsIs() 1422 && isPrivateDnsValidationRequired()) { 1423 // Captive portals can only be detected on networks that validate both 1424 // validation and private DNS validation. 1425 validationLog("Captive portal is used as is, resolving private DNS"); 1426 transitionTo(mEvaluatingPrivateDnsState); 1427 return HANDLED; 1428 } else if (!isValidationRequired()) { 1429 if (isPrivateDnsValidationRequired()) { 1430 validationLog("Network would not satisfy default request, " 1431 + "resolving private DNS"); 1432 transitionTo(mEvaluatingPrivateDnsState); 1433 } else { 1434 validationLog("Network would not satisfy default request, " 1435 + "not validating"); 1436 transitionTo(mValidatedState); 1437 } 1438 return HANDLED; 1439 } 1440 mEvaluateAttempts++; 1441 1442 transitionTo(mProbingState); 1443 return HANDLED; 1444 case CMD_FORCE_REEVALUATION: 1445 // The evaluation process restarts via EvaluatingState#enter. 1446 final boolean forceAccept = (message.arg2 != 0); 1447 return forceAccept || shouldAcceptForceRevalidation() 1448 ? NOT_HANDLED : HANDLED; 1449 // Disable HTTPS probe and transition to EvaluatingPrivateDnsState because: 1450 // 1. Network is connected and finish the network validation. 1451 // 2. NetworkMonitor detects network is partial connectivity and user accepts it. 1452 case EVENT_ACCEPT_PARTIAL_CONNECTIVITY: 1453 maybeDisableHttpsProbing(true /* acceptPartial */); 1454 transitionTo(mEvaluatingPrivateDnsState); 1455 return HANDLED; 1456 default: 1457 return NOT_HANDLED; 1458 } 1459 } 1460 shouldAcceptForceRevalidation()1461 private boolean shouldAcceptForceRevalidation() { 1462 // If the captive portal URL has changed since the last evaluation attempt, always 1463 // revalidate. Otherwise, ignore any re-evaluation requests before 1464 // IGNORE_REEVALUATE_ATTEMPTS are made. 1465 return mEvaluateAttempts >= IGNORE_REEVALUATE_ATTEMPTS 1466 || !Objects.equals( 1467 mEvaluatingCapportUrl, getCaptivePortalApiUrl(mLinkProperties)); 1468 } 1469 1470 @Override exit()1471 public void exit() { 1472 TrafficStats.clearThreadStatsUid(); 1473 } 1474 } 1475 1476 // BroadcastReceiver that waits for a particular Intent and then posts a message. 1477 private class CustomIntentReceiver extends BroadcastReceiver { 1478 private final int mToken; 1479 private final int mWhat; 1480 private final String mAction; CustomIntentReceiver(String action, int token, int what)1481 CustomIntentReceiver(String action, int token, int what) { 1482 mToken = token; 1483 mWhat = what; 1484 mAction = action + "_" + mCleartextDnsNetwork.getNetworkHandle() + "_" + token; 1485 final int flags = SdkLevel.isAtLeastT() ? RECEIVER_NOT_EXPORTED : 0; 1486 mContext.registerReceiver(this, new IntentFilter(mAction), flags); 1487 } getPendingIntent()1488 public PendingIntent getPendingIntent() { 1489 final Intent intent = new Intent(mAction); 1490 intent.setPackage(mContext.getPackageName()); 1491 return PendingIntent.getBroadcast(mContext, 0, intent, 0); 1492 } 1493 @Override onReceive(Context context, Intent intent)1494 public void onReceive(Context context, Intent intent) { 1495 if (intent.getAction().equals(mAction)) sendMessage(obtainMessage(mWhat, mToken)); 1496 } 1497 } 1498 1499 // Being in the CaptivePortalState State indicates a captive portal was detected and the user 1500 // has been shown a notification to sign-in. 1501 private class CaptivePortalState extends State { 1502 private static final String ACTION_LAUNCH_CAPTIVE_PORTAL_APP = 1503 "android.net.netmon.launchCaptivePortalApp"; 1504 1505 @Override enter()1506 public void enter() { 1507 maybeLogEvaluationResult( 1508 networkEventType(validationStage(), EvaluationResult.CAPTIVE_PORTAL)); 1509 // Don't annoy user with sign-in notifications. 1510 if (mDontDisplaySigninNotification) return; 1511 // Create a CustomIntentReceiver that sends us a 1512 // CMD_LAUNCH_CAPTIVE_PORTAL_APP message when the user 1513 // touches the notification. 1514 if (mLaunchCaptivePortalAppBroadcastReceiver == null) { 1515 // Wait for result. 1516 mLaunchCaptivePortalAppBroadcastReceiver = new CustomIntentReceiver( 1517 ACTION_LAUNCH_CAPTIVE_PORTAL_APP, new Random().nextInt(), 1518 CMD_LAUNCH_CAPTIVE_PORTAL_APP); 1519 // Display the sign in notification. 1520 // Only do this once for every time we enter MaybeNotifyState. b/122164725 1521 showProvisioningNotification(mLaunchCaptivePortalAppBroadcastReceiver.mAction); 1522 } 1523 // Retest for captive portal occasionally. 1524 sendMessageDelayed(CMD_CAPTIVE_PORTAL_RECHECK, 0 /* no UID */, 1525 CAPTIVE_PORTAL_REEVALUATE_DELAY_MS); 1526 mValidations++; 1527 maybeStopCollectionAndSendMetrics(); 1528 } 1529 1530 @Override exit()1531 public void exit() { 1532 removeMessages(CMD_CAPTIVE_PORTAL_RECHECK); 1533 } 1534 } 1535 1536 private class EvaluatingPrivateDnsState extends State { 1537 private int mPrivateDnsReevalDelayMs; 1538 private PrivateDnsConfig mPrivateDnsConfig; 1539 1540 @Override enter()1541 public void enter() { 1542 mPrivateDnsReevalDelayMs = INITIAL_REEVALUATE_DELAY_MS; 1543 mPrivateDnsConfig = null; 1544 sendMessage(CMD_EVALUATE_PRIVATE_DNS); 1545 } 1546 1547 @Override processMessage(Message msg)1548 public boolean processMessage(Message msg) { 1549 switch (msg.what) { 1550 case CMD_EVALUATE_PRIVATE_DNS: 1551 if (inStrictMode()) { 1552 if (!isStrictModeHostnameResolved()) { 1553 resolveStrictModeHostname(); 1554 1555 if (isStrictModeHostnameResolved()) { 1556 notifyPrivateDnsConfigResolved(); 1557 } else { 1558 handlePrivateDnsEvaluationFailure(); 1559 // The private DNS probe fails-fast if the server hostname cannot 1560 // be resolved. Record it as a failure with zero latency. 1561 // TODO: refactor this together with the probe recorded in 1562 // sendPrivateDnsProbe, so logging is symmetric / easier to follow. 1563 recordProbeEventMetrics(ProbeType.PT_PRIVDNS, 0 /* latency */, 1564 ProbeResult.PR_FAILURE, null /* capportData */); 1565 break; 1566 } 1567 } 1568 1569 // Look up a one-time hostname, to bypass caching. 1570 // 1571 // Note that this will race with ConnectivityService 1572 // code programming the DNS-over-TLS server IP addresses 1573 // into netd (if invoked, above). If netd doesn't know 1574 // the IP addresses yet, or if the connections to the IP 1575 // addresses haven't yet been validated, netd will block 1576 // for up to a few seconds before failing the lookup. 1577 if (!sendPrivateDnsProbe()) { 1578 handlePrivateDnsEvaluationFailure(); 1579 break; 1580 } 1581 handlePrivateDnsEvaluationSuccess(); 1582 } else { 1583 mEvaluationState.removeProbeResult(NETWORK_VALIDATION_PROBE_PRIVDNS); 1584 } 1585 1586 if (needEvaluatingBandwidth()) { 1587 transitionTo(mEvaluatingBandwidthState); 1588 } else { 1589 // All good! 1590 transitionTo(mValidatedState); 1591 } 1592 break; 1593 case CMD_PRIVATE_DNS_SETTINGS_CHANGED: 1594 // When settings change the reevaluation timer must be reset. 1595 mPrivateDnsReevalDelayMs = INITIAL_REEVALUATE_DELAY_MS; 1596 // Let the message bubble up and be handled by parent states as usual. 1597 return NOT_HANDLED; 1598 default: 1599 return NOT_HANDLED; 1600 } 1601 return HANDLED; 1602 } 1603 inStrictMode()1604 private boolean inStrictMode() { 1605 return !TextUtils.isEmpty(mPrivateDnsProviderHostname); 1606 } 1607 isStrictModeHostnameResolved()1608 private boolean isStrictModeHostnameResolved() { 1609 return (mPrivateDnsConfig != null) 1610 && mPrivateDnsConfig.hostname.equals(mPrivateDnsProviderHostname) 1611 && (mPrivateDnsConfig.ips.length > 0); 1612 } 1613 resolveStrictModeHostname()1614 private void resolveStrictModeHostname() { 1615 try { 1616 // Do a blocking DNS resolution using the network-assigned nameservers. 1617 final InetAddress[] ips = DnsUtils.getAllByName(mDependencies.getDnsResolver(), 1618 mCleartextDnsNetwork, mPrivateDnsProviderHostname, getDnsProbeTimeout(), 1619 str -> validationLog("Strict mode hostname resolution " + str)); 1620 mPrivateDnsConfig = new PrivateDnsConfig(mPrivateDnsProviderHostname, ips); 1621 } catch (UnknownHostException uhe) { 1622 mPrivateDnsConfig = null; 1623 } 1624 } 1625 notifyPrivateDnsConfigResolved()1626 private void notifyPrivateDnsConfigResolved() { 1627 try { 1628 mCallback.notifyPrivateDnsConfigResolved(mPrivateDnsConfig.toParcel()); 1629 } catch (RemoteException e) { 1630 Log.e(TAG, "Error sending private DNS config resolved notification", e); 1631 } 1632 } 1633 handlePrivateDnsEvaluationSuccess()1634 private void handlePrivateDnsEvaluationSuccess() { 1635 mEvaluationState.noteProbeResult(NETWORK_VALIDATION_PROBE_PRIVDNS, 1636 true /* succeeded */); 1637 } 1638 handlePrivateDnsEvaluationFailure()1639 private void handlePrivateDnsEvaluationFailure() { 1640 mEvaluationState.noteProbeResult(NETWORK_VALIDATION_PROBE_PRIVDNS, 1641 false /* succeeded */); 1642 mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_INVALID, 1643 null /* redirectUrl */); 1644 // Queue up a re-evaluation with backoff. 1645 // 1646 // TODO: Consider abandoning this state after a few attempts and 1647 // transitioning back to EvaluatingState, to perhaps give ourselves 1648 // the opportunity to (re)detect a captive portal or something. 1649 // 1650 sendMessageDelayed(CMD_EVALUATE_PRIVATE_DNS, mPrivateDnsReevalDelayMs); 1651 mPrivateDnsReevalDelayMs *= 2; 1652 if (mPrivateDnsReevalDelayMs > MAX_REEVALUATE_DELAY_MS) { 1653 mPrivateDnsReevalDelayMs = MAX_REEVALUATE_DELAY_MS; 1654 } 1655 } 1656 sendPrivateDnsProbe()1657 private boolean sendPrivateDnsProbe() { 1658 final String host = UUID.randomUUID().toString().substring(0, 8) 1659 + PRIVATE_DNS_PROBE_HOST_SUFFIX; 1660 final Stopwatch watch = new Stopwatch().start(); 1661 boolean success = false; 1662 long time; 1663 try { 1664 final InetAddress[] ips = mNetwork.getAllByName(host); 1665 time = watch.stop(); 1666 final String strIps = Arrays.toString(ips); 1667 success = (ips != null && ips.length > 0); 1668 validationLog(PROBE_PRIVDNS, host, String.format("%dus: %s", time, strIps)); 1669 } catch (UnknownHostException uhe) { 1670 time = watch.stop(); 1671 validationLog(PROBE_PRIVDNS, host, 1672 String.format("%dus - Error: %s", time, uhe.getMessage())); 1673 } 1674 recordProbeEventMetrics(ProbeType.PT_PRIVDNS, time, success ? ProbeResult.PR_SUCCESS : 1675 ProbeResult.PR_FAILURE, null /* capportData */); 1676 logValidationProbe(time, PROBE_PRIVDNS, success ? DNS_SUCCESS : DNS_FAILURE); 1677 return success; 1678 } 1679 } 1680 1681 private class ProbingState extends State { 1682 private Thread mThread; 1683 1684 @Override enter()1685 public void enter() { 1686 // When starting a full probe cycle here, record any pending stats (for example if 1687 // CMD_FORCE_REEVALUATE was called before evaluation finished, as can happen in 1688 // EvaluatingPrivateDnsState). 1689 maybeStopCollectionAndSendMetrics(); 1690 // Restart the metrics collection timers. Metrics will be stopped and sent when the 1691 // validation attempt finishes (as success, failure or portal), or if it is interrupted 1692 // (by being restarted or if NetworkMonitor stops). 1693 startMetricsCollection(); 1694 if (mEvaluateAttempts >= BLAME_FOR_EVALUATION_ATTEMPTS) { 1695 //Don't continue to blame UID forever. 1696 TrafficStats.clearThreadStatsUid(); 1697 } 1698 1699 final int token = ++mProbeToken; 1700 final ValidationProperties deps = new ValidationProperties(mNetworkCapabilities); 1701 final URL fallbackUrl = nextFallbackUrl(); 1702 final URL[] httpsUrls = Arrays.copyOf( 1703 mCaptivePortalHttpsUrls, mCaptivePortalHttpsUrls.length); 1704 final URL[] httpUrls = Arrays.copyOf( 1705 mCaptivePortalHttpUrls, mCaptivePortalHttpUrls.length); 1706 mThread = new Thread(() -> sendMessage(obtainMessage(CMD_PROBE_COMPLETE, token, 0, 1707 isCaptivePortal(deps, httpsUrls, httpUrls, fallbackUrl)))); 1708 mThread.start(); 1709 } 1710 1711 @Override processMessage(Message message)1712 public boolean processMessage(Message message) { 1713 switch (message.what) { 1714 case CMD_PROBE_COMPLETE: 1715 // Ensure that CMD_PROBE_COMPLETE from stale threads are ignored. 1716 if (message.arg1 != mProbeToken) { 1717 return HANDLED; 1718 } 1719 1720 final CaptivePortalProbeResult probeResult = 1721 (CaptivePortalProbeResult) message.obj; 1722 mLastProbeTime = SystemClock.elapsedRealtime(); 1723 1724 maybeWriteDataStallStats(probeResult); 1725 1726 if (probeResult.isSuccessful()) { 1727 // Transit EvaluatingPrivateDnsState to get to Validated 1728 // state (even if no Private DNS validation required). 1729 transitionTo(mEvaluatingPrivateDnsState); 1730 } else if (isTermsAndConditionsCaptive( 1731 mInfoShim.getCaptivePortalData(mLinkProperties))) { 1732 mLastPortalProbeResult = new CaptivePortalProbeResult( 1733 CaptivePortalProbeResult.PORTAL_CODE, 1734 mLinkProperties.getCaptivePortalData().getUserPortalUrl() 1735 .toString(), null, 1736 CaptivePortalProbeResult.PROBE_UNKNOWN); 1737 mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_INVALID, 1738 mLastPortalProbeResult.redirectUrl); 1739 transitionTo(mCaptivePortalState); 1740 } else if (probeResult.isPortal()) { 1741 mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_INVALID, 1742 probeResult.redirectUrl); 1743 mLastPortalProbeResult = probeResult; 1744 transitionTo(mCaptivePortalState); 1745 } else if (probeResult.isPartialConnectivity()) { 1746 mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_PARTIAL, 1747 null /* redirectUrl */); 1748 maybeDisableHttpsProbing(mAcceptPartialConnectivity); 1749 if (mAcceptPartialConnectivity) { 1750 transitionTo(mEvaluatingPrivateDnsState); 1751 } else { 1752 transitionTo(mWaitingForNextProbeState); 1753 } 1754 } else { 1755 logNetworkEvent(NetworkEvent.NETWORK_VALIDATION_FAILED); 1756 mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_INVALID, 1757 null /* redirectUrl */); 1758 transitionTo(mWaitingForNextProbeState); 1759 } 1760 return HANDLED; 1761 case EVENT_DNS_NOTIFICATION: 1762 case EVENT_ACCEPT_PARTIAL_CONNECTIVITY: 1763 // Leave the event to DefaultState. 1764 return NOT_HANDLED; 1765 default: 1766 // Wait for probe result and defer events to next state by default. 1767 deferMessage(message); 1768 return HANDLED; 1769 } 1770 } 1771 1772 @Override exit()1773 public void exit() { 1774 if (mThread.isAlive()) { 1775 mThread.interrupt(); 1776 } 1777 mThread = null; 1778 } 1779 } 1780 1781 // Being in the WaitingForNextProbeState indicates that evaluating probes failed and state is 1782 // transited from ProbingState. This ensures that the state machine is only in ProbingState 1783 // while a probe is in progress, not while waiting to perform the next probe. That allows 1784 // ProbingState to defer most messages until the probe is complete, which keeps the code simple 1785 // and matches the pre-Q behaviour where probes were a blocking operation performed on the state 1786 // machine thread. 1787 private class WaitingForNextProbeState extends State { 1788 @Override enter()1789 public void enter() { 1790 // Send metrics for this evaluation attempt. Metrics collection (and its timers) will be 1791 // restarted when the next probe starts. 1792 maybeStopCollectionAndSendMetrics(); 1793 scheduleNextProbe(); 1794 } 1795 scheduleNextProbe()1796 private void scheduleNextProbe() { 1797 final Message msg = obtainMessage(CMD_REEVALUATE, ++mReevaluateToken, 0); 1798 sendMessageDelayed(msg, mReevaluateDelayMs); 1799 mReevaluateDelayMs *= 2; 1800 if (mReevaluateDelayMs > MAX_REEVALUATE_DELAY_MS) { 1801 mReevaluateDelayMs = MAX_REEVALUATE_DELAY_MS; 1802 } 1803 } 1804 1805 @Override processMessage(Message message)1806 public boolean processMessage(Message message) { 1807 return NOT_HANDLED; 1808 } 1809 } 1810 1811 private final class EvaluatingBandwidthThread extends Thread { 1812 final int mThreadId; 1813 EvaluatingBandwidthThread(int id)1814 EvaluatingBandwidthThread(int id) { 1815 mThreadId = id; 1816 } 1817 1818 @Override run()1819 public void run() { 1820 HttpURLConnection urlConnection = null; 1821 try { 1822 final URL url = makeURL(mEvaluatingBandwidthUrl); 1823 urlConnection = makeProbeConnection(url, true /* followRedirects */); 1824 // In order to exclude the time of DNS lookup, send the delay message of timeout 1825 // here. 1826 sendMessageDelayed(CMD_BANDWIDTH_CHECK_TIMEOUT, mEvaluatingBandwidthTimeoutMs); 1827 readContentFromDownloadUrl(urlConnection); 1828 } catch (InterruptedIOException e) { 1829 // There is a timing issue that someone triggers the forcing reevaluation when 1830 // executing the getInputStream(). The InterruptedIOException is thrown by 1831 // Timeout#throwIfReached, it will reset the interrupt flag of Thread. So just 1832 // return and wait for the bandwidth reevaluation, otherwise the 1833 // CMD_BANDWIDTH_CHECK_COMPLETE will be sent. 1834 validationLog("The thread is interrupted when executing the getInputStream()," 1835 + " return and wait for the bandwidth reevaluation"); 1836 return; 1837 } catch (IOException e) { 1838 validationLog("Evaluating bandwidth failed: " + e + ", if the thread is not" 1839 + " interrupted, transition to validated state directly to make sure user" 1840 + " can use wifi normally."); 1841 } finally { 1842 if (urlConnection != null) { 1843 urlConnection.disconnect(); 1844 } 1845 } 1846 // Don't send CMD_BANDWIDTH_CHECK_COMPLETE if the IO is interrupted or timeout. 1847 // Only send CMD_BANDWIDTH_CHECK_COMPLETE when the download is finished normally. 1848 // Add a serial number for CMD_BANDWIDTH_CHECK_COMPLETE to prevent handling the obsolete 1849 // CMD_BANDWIDTH_CHECK_COMPLETE. 1850 if (!isInterrupted()) sendMessage(CMD_BANDWIDTH_CHECK_COMPLETE, mThreadId); 1851 } 1852 readContentFromDownloadUrl(@onNull final HttpURLConnection conn)1853 private void readContentFromDownloadUrl(@NonNull final HttpURLConnection conn) 1854 throws IOException { 1855 final byte[] buffer = new byte[1000]; 1856 final InputStream is = conn.getInputStream(); 1857 while (!isInterrupted() && is.read(buffer) > 0) { /* read again */ } 1858 } 1859 } 1860 1861 private class EvaluatingBandwidthState extends State { 1862 private EvaluatingBandwidthThread mEvaluatingBandwidthThread; 1863 private int mRetryBandwidthDelayMs; 1864 private int mCurrentThreadId; 1865 1866 @Override enter()1867 public void enter() { 1868 mRetryBandwidthDelayMs = getResIntConfig(mContext, 1869 R.integer.config_evaluating_bandwidth_min_retry_timer_ms, 1870 INITIAL_REEVALUATE_DELAY_MS); 1871 sendMessage(CMD_EVALUATE_BANDWIDTH); 1872 } 1873 1874 @Override processMessage(Message msg)1875 public boolean processMessage(Message msg) { 1876 switch (msg.what) { 1877 case CMD_EVALUATE_BANDWIDTH: 1878 mCurrentThreadId = mNextEvaluatingBandwidthThreadId.getAndIncrement(); 1879 mEvaluatingBandwidthThread = new EvaluatingBandwidthThread(mCurrentThreadId); 1880 mEvaluatingBandwidthThread.start(); 1881 break; 1882 case CMD_BANDWIDTH_CHECK_COMPLETE: 1883 // Only handle the CMD_BANDWIDTH_CHECK_COMPLETE which is sent by the newest 1884 // EvaluatingBandwidthThread. 1885 if (mCurrentThreadId == msg.arg1) { 1886 mIsBandwidthCheckPassedOrIgnored = true; 1887 transitionTo(mValidatedState); 1888 } 1889 break; 1890 case CMD_BANDWIDTH_CHECK_TIMEOUT: 1891 validationLog("Evaluating bandwidth timeout!"); 1892 mEvaluatingBandwidthThread.interrupt(); 1893 scheduleReevaluatingBandwidth(); 1894 break; 1895 default: 1896 return NOT_HANDLED; 1897 } 1898 return HANDLED; 1899 } 1900 scheduleReevaluatingBandwidth()1901 private void scheduleReevaluatingBandwidth() { 1902 sendMessageDelayed(obtainMessage(CMD_EVALUATE_BANDWIDTH), mRetryBandwidthDelayMs); 1903 mRetryBandwidthDelayMs *= 2; 1904 if (mRetryBandwidthDelayMs > mMaxRetryTimerMs) { 1905 mRetryBandwidthDelayMs = mMaxRetryTimerMs; 1906 } 1907 } 1908 1909 @Override exit()1910 public void exit() { 1911 mEvaluatingBandwidthThread.interrupt(); 1912 removeMessages(CMD_EVALUATE_BANDWIDTH); 1913 removeMessages(CMD_BANDWIDTH_CHECK_TIMEOUT); 1914 } 1915 } 1916 1917 // Limits the list of IP addresses returned by getAllByName or tried by openConnection to at 1918 // most one per address family. This ensures we only wait up to 20 seconds for TCP connections 1919 // to complete, regardless of how many IP addresses a host has. 1920 private static class OneAddressPerFamilyNetwork extends Network { OneAddressPerFamilyNetwork(Network network)1921 OneAddressPerFamilyNetwork(Network network) { 1922 // Always bypass Private DNS. 1923 super(network.getPrivateDnsBypassingCopy()); 1924 } 1925 1926 @Override getAllByName(String host)1927 public InetAddress[] getAllByName(String host) throws UnknownHostException { 1928 final List<InetAddress> addrs = Arrays.asList(super.getAllByName(host)); 1929 1930 // Ensure the address family of the first address is tried first. 1931 LinkedHashMap<Class, InetAddress> addressByFamily = new LinkedHashMap<>(); 1932 addressByFamily.put(addrs.get(0).getClass(), addrs.get(0)); 1933 Collections.shuffle(addrs); 1934 1935 for (InetAddress addr : addrs) { 1936 addressByFamily.put(addr.getClass(), addr); 1937 } 1938 1939 return addressByFamily.values().toArray(new InetAddress[addressByFamily.size()]); 1940 } 1941 } 1942 1943 @VisibleForTesting onlyWifiTransport()1944 boolean onlyWifiTransport() { 1945 int[] transportTypes = mNetworkCapabilities.getTransportTypes(); 1946 return transportTypes.length == 1 1947 && transportTypes[0] == NetworkCapabilities.TRANSPORT_WIFI; 1948 } 1949 1950 @VisibleForTesting needEvaluatingBandwidth()1951 boolean needEvaluatingBandwidth() { 1952 if (mIsBandwidthCheckPassedOrIgnored 1953 || TextUtils.isEmpty(mEvaluatingBandwidthUrl) 1954 || !mNetworkCapabilities.hasCapability(NET_CAPABILITY_NOT_METERED) 1955 || !onlyWifiTransport()) { 1956 return false; 1957 } 1958 1959 return true; 1960 } 1961 getIsCaptivePortalCheckEnabled(@onNull Context context, @NonNull Dependencies dependencies)1962 private static boolean getIsCaptivePortalCheckEnabled(@NonNull Context context, 1963 @NonNull Dependencies dependencies) { 1964 String symbol = CAPTIVE_PORTAL_MODE; 1965 int defaultValue = CAPTIVE_PORTAL_MODE_PROMPT; 1966 int mode = dependencies.getSetting(context, symbol, defaultValue); 1967 return mode != CAPTIVE_PORTAL_MODE_IGNORE; 1968 } 1969 getIsPrivateIpNoInternetEnabled()1970 private boolean getIsPrivateIpNoInternetEnabled() { 1971 return mDependencies.isFeatureEnabled(mContext, DNS_PROBE_PRIVATE_IP_NO_INTERNET_VERSION) 1972 || mContext.getResources().getBoolean( 1973 R.bool.config_force_dns_probe_private_ip_no_internet); 1974 } 1975 getUseHttpsValidation()1976 private boolean getUseHttpsValidation() { 1977 return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY, 1978 CAPTIVE_PORTAL_USE_HTTPS, 1) == 1; 1979 } 1980 1981 @Nullable getMccFromCellInfo(final CellInfo cell)1982 private String getMccFromCellInfo(final CellInfo cell) { 1983 if (cell instanceof CellInfoGsm) { 1984 return ((CellInfoGsm) cell).getCellIdentity().getMccString(); 1985 } else if (cell instanceof CellInfoLte) { 1986 return ((CellInfoLte) cell).getCellIdentity().getMccString(); 1987 } else if (cell instanceof CellInfoWcdma) { 1988 return ((CellInfoWcdma) cell).getCellIdentity().getMccString(); 1989 } else if (cell instanceof CellInfoTdscdma) { 1990 return ((CellInfoTdscdma) cell).getCellIdentity().getMccString(); 1991 } else if (cell instanceof CellInfoNr) { 1992 return ((CellIdentityNr) ((CellInfoNr) cell).getCellIdentity()).getMccString(); 1993 } else { 1994 return null; 1995 } 1996 } 1997 1998 /** 1999 * Return location mcc. 2000 */ 2001 @VisibleForTesting 2002 @Nullable getLocationMcc()2003 protected String getLocationMcc() { 2004 // Adding this check is because the new permission won't be granted by mainline update, 2005 // the new permission only be granted by OTA for current design. Tracking: b/145774617. 2006 if (mContext.checkPermission(android.Manifest.permission.ACCESS_FINE_LOCATION, 2007 Process.myPid(), Process.myUid()) 2008 == PackageManager.PERMISSION_DENIED) { 2009 log("getLocationMcc : NetworkStack does not hold ACCESS_FINE_LOCATION"); 2010 return null; 2011 } 2012 try { 2013 final List<CellInfo> cells = mTelephonyManager.getAllCellInfo(); 2014 if (cells == null) { 2015 log("CellInfo is null"); 2016 return null; 2017 } 2018 final Map<String, Integer> countryCodeMap = new HashMap<>(); 2019 int maxCount = 0; 2020 for (final CellInfo cell : cells) { 2021 final String mcc = getMccFromCellInfo(cell); 2022 if (mcc != null) { 2023 final int count = countryCodeMap.getOrDefault(mcc, 0) + 1; 2024 countryCodeMap.put(mcc, count); 2025 } 2026 } 2027 // Return the MCC which occurs most. 2028 if (countryCodeMap.size() <= 0) return null; 2029 return Collections.max(countryCodeMap.entrySet(), 2030 (e1, e2) -> e1.getValue().compareTo(e2.getValue())).getKey(); 2031 } catch (SecurityException e) { 2032 log("Permission is not granted:" + e); 2033 return null; 2034 } 2035 } 2036 2037 /** 2038 * Return a matched MccMncOverrideInfo if carrier id and sim mccmnc are matching a record in 2039 * sCarrierIdToMccMnc. 2040 */ 2041 @VisibleForTesting 2042 @Nullable getMccMncOverrideInfo()2043 MccMncOverrideInfo getMccMncOverrideInfo() { 2044 final int carrierId = mTelephonyManager.getSimCarrierId(); 2045 return sCarrierIdToMccMnc.get(carrierId); 2046 } 2047 getContextByMccMnc(final int mcc, final int mnc)2048 private Context getContextByMccMnc(final int mcc, final int mnc) { 2049 final Configuration config = mContext.getResources().getConfiguration(); 2050 if (mcc != UNSET_MCC_OR_MNC) config.mcc = mcc; 2051 if (mnc != UNSET_MCC_OR_MNC) config.mnc = mnc; 2052 return mContext.createConfigurationContext(config); 2053 } 2054 2055 @VisibleForTesting getCustomizedContextOrDefault()2056 protected Context getCustomizedContextOrDefault() { 2057 // Return customized context if carrier id can match a record in sCarrierIdToMccMnc. 2058 final MccMncOverrideInfo overrideInfo = getMccMncOverrideInfo(); 2059 if (overrideInfo != null) { 2060 log("Return customized context by MccMncOverrideInfo."); 2061 return getContextByMccMnc(overrideInfo.mcc, overrideInfo.mnc); 2062 } 2063 2064 // Use neighbor mcc feature only works when the config_no_sim_card_uses_neighbor_mcc is 2065 // true and there is no sim card inserted. 2066 final boolean useNeighborResource = 2067 getResBooleanConfig(mContext, R.bool.config_no_sim_card_uses_neighbor_mcc, false); 2068 if (!useNeighborResource 2069 || TelephonyManager.SIM_STATE_READY == mTelephonyManager.getSimState()) { 2070 if (useNeighborResource) log("Sim state is ready, return original context."); 2071 return mContext; 2072 } 2073 2074 final String mcc = getLocationMcc(); 2075 if (TextUtils.isEmpty(mcc)) { 2076 log("Return original context due to getting mcc failed."); 2077 return mContext; 2078 } 2079 2080 return getContextByMccMnc(Integer.parseInt(mcc), UNSET_MCC_OR_MNC); 2081 } 2082 2083 @Nullable getTestUrl(@onNull String key, @NonNull SharedLog log, @NonNull Dependencies deps)2084 private static URL getTestUrl(@NonNull String key, @NonNull SharedLog log, 2085 @NonNull Dependencies deps) { 2086 final String strExpiration = deps.getDeviceConfigProperty(NAMESPACE_CONNECTIVITY, 2087 TEST_URL_EXPIRATION_TIME, null); 2088 if (strExpiration == null) return null; 2089 2090 final long expTime; 2091 try { 2092 expTime = Long.parseUnsignedLong(strExpiration); 2093 } catch (NumberFormatException e) { 2094 log.e("Invalid test URL expiration time format", e); 2095 return null; 2096 } 2097 2098 final long now = System.currentTimeMillis(); 2099 if (expTime < now || (expTime - now) > TEST_URL_EXPIRATION_MS) { 2100 log.w("Skipping test URL with expiration " + expTime + ", now " + now); 2101 return null; 2102 } 2103 2104 final String strUrl = deps.getDeviceConfigProperty(NAMESPACE_CONNECTIVITY, 2105 key, null /* defaultValue */); 2106 if (!isValidTestUrl(strUrl)) { 2107 log.w("Skipping invalid test URL " + strUrl); 2108 return null; 2109 } 2110 return makeURL(strUrl, log); 2111 } 2112 getCaptivePortalServerHttpsUrl(@onNull Context context)2113 private String getCaptivePortalServerHttpsUrl(@NonNull Context context) { 2114 return getSettingFromResource(context, 2115 R.string.config_captive_portal_https_url, mCaptivePortalHttpsUrlFromSetting, 2116 context.getResources().getString( 2117 R.string.default_captive_portal_https_url)); 2118 } 2119 isValidTestUrl(@ullable String url)2120 private static boolean isValidTestUrl(@Nullable String url) { 2121 if (TextUtils.isEmpty(url)) return false; 2122 2123 try { 2124 // Only accept test URLs on localhost 2125 return Uri.parse(url).getHost().equals("localhost"); 2126 } catch (Throwable e) { 2127 Log.wtf(TAG, "Error parsing test URL", e); 2128 return false; 2129 } 2130 } 2131 getDnsProbeTimeout()2132 private int getDnsProbeTimeout() { 2133 return getIntSetting(mContext, R.integer.config_captive_portal_dns_probe_timeout, 2134 CONFIG_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT, DEFAULT_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT); 2135 } 2136 2137 /** 2138 * Gets an integer setting from resources or device config 2139 * 2140 * configResource is used if set, followed by device config if set, followed by defaultValue. 2141 * If none of these are set then an exception is thrown. 2142 * 2143 * TODO: move to a common location such as a ConfigUtils class. 2144 * TODO(b/130324939): test that the resources can be overlayed by an RRO package. 2145 */ 2146 @VisibleForTesting getIntSetting(@onNull final Context context, @StringRes int configResource, @NonNull String symbol, int defaultValue)2147 int getIntSetting(@NonNull final Context context, @StringRes int configResource, 2148 @NonNull String symbol, int defaultValue) { 2149 final Resources res = context.getResources(); 2150 try { 2151 return res.getInteger(configResource); 2152 } catch (Resources.NotFoundException e) { 2153 return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY, 2154 symbol, defaultValue); 2155 } 2156 } 2157 2158 /** 2159 * Gets integer config from resources. 2160 */ 2161 @VisibleForTesting getResIntConfig(@onNull final Context context, @IntegerRes final int configResource, final int defaultValue)2162 int getResIntConfig(@NonNull final Context context, 2163 @IntegerRes final int configResource, final int defaultValue) { 2164 final Resources res = context.getResources(); 2165 try { 2166 return res.getInteger(configResource); 2167 } catch (Resources.NotFoundException e) { 2168 return defaultValue; 2169 } 2170 } 2171 2172 /** 2173 * Gets string config from resources. 2174 */ 2175 @VisibleForTesting getResStringConfig(@onNull final Context context, @StringRes final int configResource, @Nullable final String defaultValue)2176 String getResStringConfig(@NonNull final Context context, 2177 @StringRes final int configResource, @Nullable final String defaultValue) { 2178 final Resources res = context.getResources(); 2179 try { 2180 return res.getString(configResource); 2181 } catch (Resources.NotFoundException e) { 2182 return defaultValue; 2183 } 2184 } 2185 2186 /** 2187 * Get the captive portal server HTTP URL that is configured on the device. 2188 * 2189 * NetworkMonitor does not use {@link ConnectivityManager#getCaptivePortalServerUrl()} as 2190 * it has its own updatable strategies to detect captive portals. The framework only advises 2191 * on one URL that can be used, while NetworkMonitor may implement more complex logic. 2192 */ getCaptivePortalServerHttpUrl(@onNull Context context)2193 public String getCaptivePortalServerHttpUrl(@NonNull Context context) { 2194 return getSettingFromResource(context, 2195 R.string.config_captive_portal_http_url, mCaptivePortalHttpUrlFromSetting, 2196 context.getResources().getString( 2197 R.string.default_captive_portal_http_url)); 2198 } 2199 getConsecutiveDnsTimeoutThreshold()2200 private int getConsecutiveDnsTimeoutThreshold() { 2201 return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY, 2202 CONFIG_DATA_STALL_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD, 2203 DEFAULT_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD); 2204 } 2205 getDataStallMinEvaluateTime()2206 private int getDataStallMinEvaluateTime() { 2207 return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY, 2208 CONFIG_DATA_STALL_MIN_EVALUATE_INTERVAL, 2209 DEFAULT_DATA_STALL_MIN_EVALUATE_TIME_MS); 2210 } 2211 getDataStallValidDnsTimeThreshold()2212 private int getDataStallValidDnsTimeThreshold() { 2213 return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY, 2214 CONFIG_DATA_STALL_VALID_DNS_TIME_THRESHOLD, 2215 DEFAULT_DATA_STALL_VALID_DNS_TIME_THRESHOLD_MS); 2216 } 2217 2218 @VisibleForTesting getDataStallEvaluationType()2219 int getDataStallEvaluationType() { 2220 return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY, 2221 CONFIG_DATA_STALL_EVALUATION_TYPE, 2222 DEFAULT_DATA_STALL_EVALUATION_TYPES); 2223 } 2224 getTcpPollingInterval()2225 private int getTcpPollingInterval() { 2226 return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY, 2227 CONFIG_DATA_STALL_TCP_POLLING_INTERVAL, 2228 DEFAULT_TCP_POLLING_INTERVAL_MS); 2229 } 2230 2231 @VisibleForTesting makeCaptivePortalFallbackUrls(@onNull Context context)2232 URL[] makeCaptivePortalFallbackUrls(@NonNull Context context) { 2233 try { 2234 final String firstUrl = mDependencies.getSetting(mContext, CAPTIVE_PORTAL_FALLBACK_URL, 2235 null); 2236 final URL[] settingProviderUrls = 2237 combineCaptivePortalUrls(firstUrl, CAPTIVE_PORTAL_OTHER_FALLBACK_URLS); 2238 return getProbeUrlArrayConfig(context, settingProviderUrls, 2239 R.array.config_captive_portal_fallback_urls, 2240 R.array.default_captive_portal_fallback_urls, 2241 this::makeURL); 2242 } catch (Exception e) { 2243 // Don't let a misconfiguration bootloop the system. 2244 Log.e(TAG, "Error parsing configured fallback URLs", e); 2245 return new URL[0]; 2246 } 2247 } 2248 makeCaptivePortalFallbackProbeSpecs(@onNull Context context)2249 private CaptivePortalProbeSpec[] makeCaptivePortalFallbackProbeSpecs(@NonNull Context context) { 2250 try { 2251 final String settingsValue = mDependencies.getDeviceConfigProperty( 2252 NAMESPACE_CONNECTIVITY, CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS, null); 2253 2254 final CaptivePortalProbeSpec[] emptySpecs = new CaptivePortalProbeSpec[0]; 2255 final CaptivePortalProbeSpec[] providerValue = TextUtils.isEmpty(settingsValue) 2256 ? emptySpecs 2257 : parseCaptivePortalProbeSpecs(settingsValue).toArray(emptySpecs); 2258 2259 return getProbeUrlArrayConfig(context, providerValue, 2260 R.array.config_captive_portal_fallback_probe_specs, 2261 DEFAULT_CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS, 2262 CaptivePortalProbeSpec::parseSpecOrNull); 2263 } catch (Exception e) { 2264 // Don't let a misconfiguration bootloop the system. 2265 Log.e(TAG, "Error parsing configured fallback probe specs", e); 2266 return null; 2267 } 2268 } 2269 makeCaptivePortalHttpsUrls(@onNull Context context)2270 private URL[] makeCaptivePortalHttpsUrls(@NonNull Context context) { 2271 if (mTestCaptivePortalHttpsUrl != null) return new URL[] { mTestCaptivePortalHttpsUrl }; 2272 2273 final String firstUrl = getCaptivePortalServerHttpsUrl(context); 2274 try { 2275 final URL[] settingProviderUrls = 2276 combineCaptivePortalUrls(firstUrl, CAPTIVE_PORTAL_OTHER_HTTPS_URLS); 2277 // firstUrl will at least be default configuration, so default value in 2278 // getProbeUrlArrayConfig is actually never used. 2279 return getProbeUrlArrayConfig(context, settingProviderUrls, 2280 R.array.config_captive_portal_https_urls, 2281 DEFAULT_CAPTIVE_PORTAL_HTTPS_URLS, this::makeURL); 2282 } catch (Exception e) { 2283 // Don't let a misconfiguration bootloop the system. 2284 Log.e(TAG, "Error parsing configured https URLs", e); 2285 // Ensure URL aligned with legacy configuration. 2286 return new URL[]{makeURL(firstUrl)}; 2287 } 2288 } 2289 makeCaptivePortalHttpUrls(@onNull Context context)2290 private URL[] makeCaptivePortalHttpUrls(@NonNull Context context) { 2291 if (mTestCaptivePortalHttpUrl != null) return new URL[] { mTestCaptivePortalHttpUrl }; 2292 2293 final String firstUrl = getCaptivePortalServerHttpUrl(context); 2294 try { 2295 final URL[] settingProviderUrls = 2296 combineCaptivePortalUrls(firstUrl, CAPTIVE_PORTAL_OTHER_HTTP_URLS); 2297 // firstUrl will at least be default configuration, so default value in 2298 // getProbeUrlArrayConfig is actually never used. 2299 return getProbeUrlArrayConfig(context, settingProviderUrls, 2300 R.array.config_captive_portal_http_urls, 2301 DEFAULT_CAPTIVE_PORTAL_HTTP_URLS, this::makeURL); 2302 } catch (Exception e) { 2303 // Don't let a misconfiguration bootloop the system. 2304 Log.e(TAG, "Error parsing configured http URLs", e); 2305 // Ensure URL aligned with legacy configuration. 2306 return new URL[]{makeURL(firstUrl)}; 2307 } 2308 } 2309 combineCaptivePortalUrls(final String firstUrl, final String propertyName)2310 private URL[] combineCaptivePortalUrls(final String firstUrl, final String propertyName) { 2311 if (TextUtils.isEmpty(firstUrl)) return new URL[0]; 2312 2313 final String otherUrls = mDependencies.getDeviceConfigProperty( 2314 NAMESPACE_CONNECTIVITY, propertyName, ""); 2315 // otherUrls may be empty, but .split() ignores trailing empty strings 2316 final String separator = ","; 2317 final String[] urls = (firstUrl + separator + otherUrls).split(separator); 2318 return convertStrings(urls, this::makeURL, new URL[0]); 2319 } 2320 2321 /** 2322 * Read a setting from a resource or the settings provider. 2323 * 2324 * <p>The configuration resource is prioritized, then the provider value. 2325 * @param context The context 2326 * @param configResource The resource id for the configuration parameter 2327 * @param settingValue The value in the settings provider 2328 * @param defaultValue The default value 2329 * @return The best available value 2330 */ 2331 @Nullable getSettingFromResource(@onNull final Context context, @StringRes int configResource, @NonNull String settingValue, @NonNull String defaultValue)2332 private String getSettingFromResource(@NonNull final Context context, 2333 @StringRes int configResource, @NonNull String settingValue, 2334 @NonNull String defaultValue) { 2335 final Resources res = context.getResources(); 2336 String setting = res.getString(configResource); 2337 2338 if (!TextUtils.isEmpty(setting)) return setting; 2339 2340 if (!TextUtils.isEmpty(settingValue)) return settingValue; 2341 2342 return defaultValue; 2343 } 2344 2345 /** 2346 * Get an array configuration from resources or the settings provider. 2347 * 2348 * <p>The configuration resource is prioritized, then the provider values, then the default 2349 * resource values. 2350 * 2351 * @param context The Context 2352 * @param providerValue Values obtained from the setting provider. 2353 * @param configResId ID of the configuration resource. 2354 * @param defaultResId ID of the default resource. 2355 * @param resourceConverter Converter from the resource strings to stored setting class. Null 2356 * return values are ignored. 2357 */ getProbeUrlArrayConfig(@onNull Context context, @NonNull T[] providerValue, @ArrayRes int configResId, @ArrayRes int defaultResId, @NonNull Function<String, T> resourceConverter)2358 private <T> T[] getProbeUrlArrayConfig(@NonNull Context context, @NonNull T[] providerValue, 2359 @ArrayRes int configResId, @ArrayRes int defaultResId, 2360 @NonNull Function<String, T> resourceConverter) { 2361 final Resources res = context.getResources(); 2362 return getProbeUrlArrayConfig(context, providerValue, configResId, 2363 res.getStringArray(defaultResId), resourceConverter); 2364 } 2365 2366 /** 2367 * Get an array configuration from resources or the settings provider. 2368 * 2369 * <p>The configuration resource is prioritized, then the provider values, then the default 2370 * resource values. 2371 * 2372 * @param context The Context 2373 * @param providerValue Values obtained from the setting provider. 2374 * @param configResId ID of the configuration resource. 2375 * @param defaultConfig Values of default configuration. 2376 * @param resourceConverter Converter from the resource strings to stored setting class. Null 2377 * return values are ignored. 2378 */ getProbeUrlArrayConfig(@onNull Context context, @NonNull T[] providerValue, @ArrayRes int configResId, String[] defaultConfig, @NonNull Function<String, T> resourceConverter)2379 private <T> T[] getProbeUrlArrayConfig(@NonNull Context context, @NonNull T[] providerValue, 2380 @ArrayRes int configResId, String[] defaultConfig, 2381 @NonNull Function<String, T> resourceConverter) { 2382 final Resources res = context.getResources(); 2383 String[] configValue = res.getStringArray(configResId); 2384 2385 if (configValue.length == 0) { 2386 if (providerValue.length > 0) { 2387 return providerValue; 2388 } 2389 2390 configValue = defaultConfig; 2391 } 2392 2393 return convertStrings(configValue, resourceConverter, Arrays.copyOf(providerValue, 0)); 2394 } 2395 2396 /** 2397 * Convert a String array to an array of some other type using the specified converter. 2398 * 2399 * <p>Any null value, or value for which the converter throws a {@link RuntimeException}, will 2400 * not be added to the output array, so the output array may be smaller than the input. 2401 */ convertStrings( @onNull String[] strings, Function<String, T> converter, T[] emptyArray)2402 private <T> T[] convertStrings( 2403 @NonNull String[] strings, Function<String, T> converter, T[] emptyArray) { 2404 final ArrayList<T> convertedValues = new ArrayList<>(strings.length); 2405 for (String configString : strings) { 2406 T convertedValue = null; 2407 try { 2408 convertedValue = converter.apply(configString); 2409 } catch (Exception e) { 2410 Log.e(TAG, "Error parsing configuration", e); 2411 // Fall through 2412 } 2413 if (convertedValue != null) { 2414 convertedValues.add(convertedValue); 2415 } 2416 } 2417 return convertedValues.toArray(emptyArray); 2418 } 2419 getCaptivePortalUserAgent()2420 private String getCaptivePortalUserAgent() { 2421 return mDependencies.getDeviceConfigProperty(NAMESPACE_CONNECTIVITY, 2422 CAPTIVE_PORTAL_USER_AGENT, DEFAULT_USER_AGENT); 2423 } 2424 nextFallbackUrl()2425 private URL nextFallbackUrl() { 2426 if (mCaptivePortalFallbackUrls.length == 0) { 2427 return null; 2428 } 2429 int idx = Math.abs(mNextFallbackUrlIndex) % mCaptivePortalFallbackUrls.length; 2430 mNextFallbackUrlIndex += mRandom.nextInt(); // randomly change url without memory. 2431 return mCaptivePortalFallbackUrls[idx]; 2432 } 2433 nextFallbackSpec()2434 private CaptivePortalProbeSpec nextFallbackSpec() { 2435 if (isEmpty(mCaptivePortalFallbackSpecs)) { 2436 return null; 2437 } 2438 // Randomly change spec without memory. Also randomize the first attempt. 2439 final int idx = Math.abs(mRandom.nextInt()) % mCaptivePortalFallbackSpecs.length; 2440 return mCaptivePortalFallbackSpecs[idx]; 2441 } 2442 2443 /** 2444 * Validation properties that can be accessed by the evaluation thread in a thread-safe way. 2445 * 2446 * Parameters such as LinkProperties and NetworkCapabilities cannot be accessed by the 2447 * evaluation thread directly, as they are managed in the state machine thread and not 2448 * synchronized. This class provides a copy of the required data that is not modified and can be 2449 * used safely by the evaluation thread. 2450 */ 2451 private static class ValidationProperties { 2452 // TODO: add other properties that are needed for evaluation and currently extracted in a 2453 // non-thread-safe way from LinkProperties, NetworkCapabilities, etc. 2454 private final boolean mIsTestNetwork; 2455 ValidationProperties(NetworkCapabilities nc)2456 ValidationProperties(NetworkCapabilities nc) { 2457 this.mIsTestNetwork = nc.hasTransport(TRANSPORT_TEST); 2458 } 2459 } 2460 isCaptivePortal(ValidationProperties properties, URL[] httpsUrls, URL[] httpUrls, URL fallbackUrl)2461 private CaptivePortalProbeResult isCaptivePortal(ValidationProperties properties, 2462 URL[] httpsUrls, URL[] httpUrls, URL fallbackUrl) { 2463 if (!mIsCaptivePortalCheckEnabled) { 2464 validationLog("Validation disabled."); 2465 return CaptivePortalProbeResult.success(CaptivePortalProbeResult.PROBE_UNKNOWN); 2466 } 2467 2468 URL pacUrl = null; 2469 2470 // On networks with a PAC instead of fetching a URL that should result in a 204 2471 // response, we instead simply fetch the PAC script. This is done for a few reasons: 2472 // 1. At present our PAC code does not yet handle multiple PACs on multiple networks 2473 // until something like https://android-review.googlesource.com/#/c/115180/ lands. 2474 // Network.openConnection() will ignore network-specific PACs and instead fetch 2475 // using NO_PROXY. If a PAC is in place, the only fetch we know will succeed with 2476 // NO_PROXY is the fetch of the PAC itself. 2477 // 2. To proxy the generate_204 fetch through a PAC would require a number of things 2478 // happen before the fetch can commence, namely: 2479 // a) the PAC script be fetched 2480 // b) a PAC script resolver service be fired up and resolve the captive portal 2481 // server. 2482 // Network validation could be delayed until these prerequisities are satisifed or 2483 // could simply be left to race them. Neither is an optimal solution. 2484 // 3. PAC scripts are sometimes used to block or restrict Internet access and may in 2485 // fact block fetching of the generate_204 URL which would lead to false negative 2486 // results for network validation. 2487 final ProxyInfo proxyInfo = mLinkProperties.getHttpProxy(); 2488 if (proxyInfo != null && !Uri.EMPTY.equals(proxyInfo.getPacFileUrl())) { 2489 pacUrl = makeURL(proxyInfo.getPacFileUrl().toString()); 2490 if (pacUrl == null) { 2491 return CaptivePortalProbeResult.failed(CaptivePortalProbeResult.PROBE_UNKNOWN); 2492 } 2493 } 2494 2495 if ((pacUrl == null) && (httpUrls.length == 0 || httpsUrls.length == 0 2496 || httpUrls[0] == null || httpsUrls[0] == null)) { 2497 return CaptivePortalProbeResult.failed(CaptivePortalProbeResult.PROBE_UNKNOWN); 2498 } 2499 2500 long startTime = SystemClock.elapsedRealtime(); 2501 2502 final CaptivePortalProbeResult result; 2503 if (pacUrl != null) { 2504 result = sendDnsAndHttpProbes(null, pacUrl, ValidationProbeEvent.PROBE_PAC); 2505 reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, result); 2506 } else if (mUseHttps && httpsUrls.length == 1 && httpUrls.length == 1) { 2507 // Probe results are reported inside sendHttpAndHttpsParallelWithFallbackProbes. 2508 result = sendHttpAndHttpsParallelWithFallbackProbes(properties, proxyInfo, 2509 httpsUrls[0], httpUrls[0], fallbackUrl); 2510 } else if (mUseHttps) { 2511 // Support result aggregation from multiple Urls. 2512 result = sendMultiParallelHttpAndHttpsProbes(properties, proxyInfo, httpsUrls, 2513 httpUrls); 2514 } else { 2515 result = sendDnsAndHttpProbes(proxyInfo, httpUrls[0], ValidationProbeEvent.PROBE_HTTP); 2516 reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, result); 2517 } 2518 2519 long endTime = SystemClock.elapsedRealtime(); 2520 2521 log("isCaptivePortal: isSuccessful()=" + result.isSuccessful() 2522 + " isPortal()=" + result.isPortal() 2523 + " RedirectUrl=" + result.redirectUrl 2524 + " isPartialConnectivity()=" + result.isPartialConnectivity() 2525 + " Time=" + (endTime - startTime) + "ms"); 2526 2527 return result; 2528 } 2529 2530 /** 2531 * Do a DNS resolution and URL fetch on a known web server to see if we get the data we expect. 2532 * @return a CaptivePortalProbeResult inferred from the HTTP response. 2533 */ sendDnsAndHttpProbes(ProxyInfo proxy, URL url, int probeType)2534 private CaptivePortalProbeResult sendDnsAndHttpProbes(ProxyInfo proxy, URL url, int probeType) { 2535 // Pre-resolve the captive portal server host so we can log it. 2536 // Only do this if HttpURLConnection is about to, to avoid any potentially 2537 // unnecessary resolution. 2538 final String host = (proxy != null) ? proxy.getHost() : url.getHost(); 2539 // This method cannot safely report probe results because it might not be running on the 2540 // state machine thread. Reporting results here would cause races and potentially send 2541 // information to callers that does not make sense because the state machine has already 2542 // changed state. 2543 final InetAddress[] resolvedAddr = sendDnsProbe(host); 2544 // The private IP logic only applies to captive portal detection (the HTTP probe), not 2545 // network validation (the HTTPS probe, which would likely fail anyway) or the PAC probe. 2546 if (mPrivateIpNoInternetEnabled && probeType == ValidationProbeEvent.PROBE_HTTP 2547 && (proxy == null) && hasPrivateIpAddress(resolvedAddr)) { 2548 recordProbeEventMetrics(NetworkValidationMetrics.probeTypeToEnum(probeType), 2549 0 /* latency */, ProbeResult.PR_PRIVATE_IP_DNS, null /* capportData */); 2550 return CaptivePortalProbeResult.PRIVATE_IP; 2551 } 2552 return sendHttpProbe(url, probeType, null); 2553 } 2554 2555 /** Do a DNS lookup for the given server, or throw UnknownHostException after timeoutMs */ 2556 @VisibleForTesting sendDnsProbeWithTimeout(String host, int timeoutMs)2557 protected InetAddress[] sendDnsProbeWithTimeout(String host, int timeoutMs) 2558 throws UnknownHostException { 2559 return DnsUtils.getAllByName(mDependencies.getDnsResolver(), mCleartextDnsNetwork, host, 2560 TYPE_ADDRCONFIG, FLAG_EMPTY, timeoutMs, 2561 str -> validationLog(ValidationProbeEvent.PROBE_DNS, host, str)); 2562 } 2563 2564 /** Do a DNS resolution of the given server. */ sendDnsProbe(String host)2565 private InetAddress[] sendDnsProbe(String host) { 2566 if (TextUtils.isEmpty(host)) { 2567 return null; 2568 } 2569 2570 final Stopwatch watch = new Stopwatch().start(); 2571 int result; 2572 InetAddress[] addresses; 2573 try { 2574 addresses = sendDnsProbeWithTimeout(host, getDnsProbeTimeout()); 2575 result = ValidationProbeEvent.DNS_SUCCESS; 2576 } catch (UnknownHostException e) { 2577 addresses = null; 2578 result = ValidationProbeEvent.DNS_FAILURE; 2579 } 2580 final long latency = watch.stop(); 2581 recordProbeEventMetrics(ProbeType.PT_DNS, latency, 2582 (result == ValidationProbeEvent.DNS_SUCCESS) ? ProbeResult.PR_SUCCESS : 2583 ProbeResult.PR_FAILURE, null /* capportData */); 2584 logValidationProbe(latency, ValidationProbeEvent.PROBE_DNS, result); 2585 return addresses; 2586 } 2587 2588 /** 2589 * Check if any of the provided IP addresses include a private IP. 2590 * @return true if an IP address is private. 2591 */ hasPrivateIpAddress(@ullable InetAddress[] addresses)2592 private static boolean hasPrivateIpAddress(@Nullable InetAddress[] addresses) { 2593 if (addresses == null) { 2594 return false; 2595 } 2596 for (InetAddress address : addresses) { 2597 if (address.isLinkLocalAddress() || address.isSiteLocalAddress() 2598 || isIPv6ULA(address)) { 2599 return true; 2600 } 2601 } 2602 return false; 2603 } 2604 2605 /** 2606 * Do a URL fetch on a known web server to see if we get the data we expect. 2607 * @return a CaptivePortalProbeResult inferred from the HTTP response. 2608 */ 2609 @VisibleForTesting sendHttpProbe(URL url, int probeType, @Nullable CaptivePortalProbeSpec probeSpec)2610 protected CaptivePortalProbeResult sendHttpProbe(URL url, int probeType, 2611 @Nullable CaptivePortalProbeSpec probeSpec) { 2612 HttpURLConnection urlConnection = null; 2613 int httpResponseCode = CaptivePortalProbeResult.FAILED_CODE; 2614 String redirectUrl = null; 2615 final Stopwatch probeTimer = new Stopwatch().start(); 2616 final int oldTag = TrafficStats.getAndSetThreadStatsTag( 2617 NetworkStackConstants.TAG_SYSTEM_PROBE); 2618 try { 2619 // Follow redirects for PAC probes as such probes verify connectivity by fetching the 2620 // PAC proxy file, which may be configured behind a redirect. 2621 final boolean followRedirect = probeType == ValidationProbeEvent.PROBE_PAC; 2622 urlConnection = makeProbeConnection(url, followRedirect); 2623 // cannot read request header after connection 2624 String requestHeader = urlConnection.getRequestProperties().toString(); 2625 2626 // Time how long it takes to get a response to our request 2627 long requestTimestamp = SystemClock.elapsedRealtime(); 2628 2629 httpResponseCode = urlConnection.getResponseCode(); 2630 redirectUrl = urlConnection.getHeaderField("location"); 2631 2632 // Time how long it takes to get a response to our request 2633 long responseTimestamp = SystemClock.elapsedRealtime(); 2634 2635 validationLog(probeType, url, "time=" + (responseTimestamp - requestTimestamp) + "ms" 2636 + " ret=" + httpResponseCode 2637 + " request=" + requestHeader 2638 + " headers=" + urlConnection.getHeaderFields()); 2639 // NOTE: We may want to consider an "HTTP/1.0 204" response to be a captive 2640 // portal. The only example of this seen so far was a captive portal. For 2641 // the time being go with prior behavior of assuming it's not a captive 2642 // portal. If it is considered a captive portal, a different sign-in URL 2643 // is needed (i.e. can't browse a 204). This could be the result of an HTTP 2644 // proxy server. 2645 if (httpResponseCode == 200) { 2646 long contentLength = urlConnection.getContentLengthLong(); 2647 if (probeType == ValidationProbeEvent.PROBE_PAC) { 2648 validationLog( 2649 probeType, url, "PAC fetch 200 response interpreted as 204 response."); 2650 httpResponseCode = CaptivePortalProbeResult.SUCCESS_CODE; 2651 } else if (contentLength == -1) { 2652 // When no Content-length (default value == -1), attempt to read a byte 2653 // from the response. Do not use available() as it is unreliable. 2654 // See http://b/33498325. 2655 if (urlConnection.getInputStream().read() == -1) { 2656 validationLog(probeType, url, 2657 "Empty 200 response interpreted as failed response."); 2658 httpResponseCode = CaptivePortalProbeResult.FAILED_CODE; 2659 } 2660 } else if (matchesHttpContentLength(contentLength)) { 2661 final InputStream is = new BufferedInputStream(urlConnection.getInputStream()); 2662 final String content = readAsString(is, (int) contentLength, 2663 extractCharset(urlConnection.getContentType())); 2664 if (matchesHttpContent(content, 2665 R.string.config_network_validation_failed_content_regexp)) { 2666 httpResponseCode = CaptivePortalProbeResult.FAILED_CODE; 2667 } else if (matchesHttpContent(content, 2668 R.string.config_network_validation_success_content_regexp)) { 2669 httpResponseCode = CaptivePortalProbeResult.SUCCESS_CODE; 2670 } 2671 2672 if (httpResponseCode != 200) { 2673 validationLog(probeType, url, "200 response with Content-length =" 2674 + contentLength + ", content matches custom regexp, interpreted" 2675 + " as " + httpResponseCode 2676 + " response."); 2677 } 2678 } else if (contentLength <= 4) { 2679 // Consider 200 response with "Content-length <= 4" to not be a captive 2680 // portal. There's no point in considering this a captive portal as the 2681 // user cannot sign-in to an empty page. Probably the result of a broken 2682 // transparent proxy. See http://b/9972012 and http://b/122999481. 2683 validationLog(probeType, url, "200 response with Content-length <= 4" 2684 + " interpreted as failed response."); 2685 httpResponseCode = CaptivePortalProbeResult.FAILED_CODE; 2686 } 2687 } 2688 } catch (IOException e) { 2689 validationLog(probeType, url, "Probe failed with exception " + e); 2690 if (httpResponseCode == CaptivePortalProbeResult.FAILED_CODE) { 2691 // TODO: Ping gateway and DNS server and log results. 2692 } 2693 } finally { 2694 if (urlConnection != null) { 2695 urlConnection.disconnect(); 2696 } 2697 TrafficStats.setThreadStatsTag(oldTag); 2698 } 2699 logValidationProbe(probeTimer.stop(), probeType, httpResponseCode); 2700 2701 final CaptivePortalProbeResult probeResult; 2702 if (probeSpec == null) { 2703 if (CaptivePortalProbeResult.isPortalCode(httpResponseCode) 2704 && TextUtils.isEmpty(redirectUrl) 2705 && ShimUtils.isAtLeastS()) { 2706 // If a portal is a non-redirect portal (often portals that return HTTP 200 with a 2707 // login page for all HTTP requests), report the probe URL as the login URL starting 2708 // from S (b/172048052). This avoids breaking assumptions that 2709 // [is a portal] is equivalent to [there is a login URL]. 2710 redirectUrl = url.toString(); 2711 } 2712 probeResult = new CaptivePortalProbeResult(httpResponseCode, redirectUrl, 2713 url.toString(), 1 << probeType); 2714 } else { 2715 probeResult = probeSpec.getResult(httpResponseCode, redirectUrl); 2716 } 2717 recordProbeEventMetrics(NetworkValidationMetrics.probeTypeToEnum(probeType), 2718 probeTimer.stop(), NetworkValidationMetrics.httpProbeResultToEnum(probeResult), 2719 null /* capportData */); 2720 return probeResult; 2721 } 2722 2723 @VisibleForTesting matchesHttpContent(final String content, @StringRes final int configResource)2724 boolean matchesHttpContent(final String content, @StringRes final int configResource) { 2725 final String resString = getResStringConfig(mContext, configResource, ""); 2726 try { 2727 return content.matches(resString); 2728 } catch (PatternSyntaxException e) { 2729 Log.e(TAG, "Pattern syntax exception occurs when matching the resource=" + resString, 2730 e); 2731 return false; 2732 } 2733 } 2734 2735 @VisibleForTesting matchesHttpContentLength(final long contentLength)2736 boolean matchesHttpContentLength(final long contentLength) { 2737 // Consider that the Resources#getInteger() is returning an integer, so if the contentLength 2738 // is lower or equal to 0 or higher than Integer.MAX_VALUE, then it's an invalid value. 2739 if (contentLength <= 0) return false; 2740 if (contentLength > Integer.MAX_VALUE) { 2741 logw("matchesHttpContentLength : Get invalid contentLength = " + contentLength); 2742 return false; 2743 } 2744 return (contentLength > getResIntConfig(mContext, 2745 R.integer.config_min_matches_http_content_length, Integer.MAX_VALUE) 2746 && 2747 contentLength < getResIntConfig(mContext, 2748 R.integer.config_max_matches_http_content_length, 0)); 2749 } 2750 makeProbeConnection(URL url, boolean followRedirects)2751 private HttpURLConnection makeProbeConnection(URL url, boolean followRedirects) 2752 throws IOException { 2753 final HttpURLConnection conn = (HttpURLConnection) mCleartextDnsNetwork.openConnection(url); 2754 conn.setInstanceFollowRedirects(followRedirects); 2755 conn.setConnectTimeout(SOCKET_TIMEOUT_MS); 2756 conn.setReadTimeout(SOCKET_TIMEOUT_MS); 2757 conn.setRequestProperty("Connection", "close"); 2758 conn.setUseCaches(false); 2759 if (mCaptivePortalUserAgent != null) { 2760 conn.setRequestProperty("User-Agent", mCaptivePortalUserAgent); 2761 } 2762 return conn; 2763 } 2764 2765 @VisibleForTesting 2766 @NonNull readAsString(InputStream is, int maxLength, Charset charset)2767 protected static String readAsString(InputStream is, int maxLength, Charset charset) 2768 throws IOException { 2769 final InputStreamReader reader = new InputStreamReader(is, charset); 2770 final char[] buffer = new char[1000]; 2771 final StringBuilder builder = new StringBuilder(); 2772 int totalReadLength = 0; 2773 while (totalReadLength < maxLength) { 2774 final int availableLength = Math.min(maxLength - totalReadLength, buffer.length); 2775 final int currentLength = reader.read(buffer, 0, availableLength); 2776 if (currentLength < 0) break; // EOF 2777 2778 totalReadLength += currentLength; 2779 builder.append(buffer, 0, currentLength); 2780 } 2781 return builder.toString(); 2782 } 2783 2784 /** 2785 * Attempt to extract the {@link Charset} of the response from its Content-Type header. 2786 * 2787 * <p>If the {@link Charset} cannot be extracted, UTF-8 is returned by default. 2788 */ 2789 @VisibleForTesting 2790 @NonNull extractCharset(@ullable String contentTypeHeader)2791 protected static Charset extractCharset(@Nullable String contentTypeHeader) { 2792 if (contentTypeHeader == null) return StandardCharsets.UTF_8; 2793 // See format in https://tools.ietf.org/html/rfc7231#section-3.1.1.1 2794 final Pattern charsetPattern = Pattern.compile("; *charset=\"?([^ ;\"]+)\"?", 2795 Pattern.CASE_INSENSITIVE); 2796 final Matcher matcher = charsetPattern.matcher(contentTypeHeader); 2797 if (!matcher.find()) return StandardCharsets.UTF_8; 2798 2799 try { 2800 return Charset.forName(matcher.group(1)); 2801 } catch (IllegalArgumentException e) { 2802 return StandardCharsets.UTF_8; 2803 } 2804 } 2805 2806 private class ProbeThread extends Thread { 2807 private final CountDownLatch mLatch; 2808 private final Probe mProbe; 2809 ProbeThread(CountDownLatch latch, ValidationProperties properties, ProxyInfo proxy, URL url, int probeType, Uri captivePortalApiUrl)2810 ProbeThread(CountDownLatch latch, ValidationProperties properties, ProxyInfo proxy, URL url, 2811 int probeType, Uri captivePortalApiUrl) { 2812 mLatch = latch; 2813 mProbe = (probeType == ValidationProbeEvent.PROBE_HTTPS) 2814 ? new HttpsProbe(properties, proxy, url, captivePortalApiUrl) 2815 : new HttpProbe(properties, proxy, url, captivePortalApiUrl); 2816 mResult = CaptivePortalProbeResult.failed(probeType); 2817 } 2818 2819 private volatile CaptivePortalProbeResult mResult; 2820 result()2821 public CaptivePortalProbeResult result() { 2822 return mResult; 2823 } 2824 2825 @Override run()2826 public void run() { 2827 mResult = mProbe.sendProbe(); 2828 if (isConclusiveResult(mResult, mProbe.mCaptivePortalApiUrl)) { 2829 // Stop waiting immediately if any probe is conclusive. 2830 while (mLatch.getCount() > 0) { 2831 mLatch.countDown(); 2832 } 2833 } 2834 // Signal this probe has completed. 2835 mLatch.countDown(); 2836 } 2837 } 2838 2839 private abstract static class Probe { 2840 protected final ValidationProperties mProperties; 2841 protected final ProxyInfo mProxy; 2842 protected final URL mUrl; 2843 protected final Uri mCaptivePortalApiUrl; 2844 Probe(ValidationProperties properties, ProxyInfo proxy, URL url, Uri captivePortalApiUrl)2845 protected Probe(ValidationProperties properties, ProxyInfo proxy, URL url, 2846 Uri captivePortalApiUrl) { 2847 mProperties = properties; 2848 mProxy = proxy; 2849 mUrl = url; 2850 mCaptivePortalApiUrl = captivePortalApiUrl; 2851 } 2852 // sendProbe() is synchronous and blocks until it has the result. sendProbe()2853 protected abstract CaptivePortalProbeResult sendProbe(); 2854 } 2855 2856 final class HttpsProbe extends Probe { HttpsProbe(ValidationProperties properties, ProxyInfo proxy, URL url, Uri captivePortalApiUrl)2857 HttpsProbe(ValidationProperties properties, ProxyInfo proxy, URL url, 2858 Uri captivePortalApiUrl) { 2859 super(properties, proxy, url, captivePortalApiUrl); 2860 } 2861 2862 @Override sendProbe()2863 protected CaptivePortalProbeResult sendProbe() { 2864 return sendDnsAndHttpProbes(mProxy, mUrl, ValidationProbeEvent.PROBE_HTTPS); 2865 } 2866 } 2867 2868 final class HttpProbe extends Probe { HttpProbe(ValidationProperties properties, ProxyInfo proxy, URL url, Uri captivePortalApiUrl)2869 HttpProbe(ValidationProperties properties, ProxyInfo proxy, URL url, 2870 Uri captivePortalApiUrl) { 2871 super(properties, proxy, url, captivePortalApiUrl); 2872 } 2873 sendCapportApiProbe()2874 private CaptivePortalDataShim sendCapportApiProbe() { 2875 // TODO: consider adding metrics counters for each case returning null in this method 2876 // (cases where the API is not implemented properly). 2877 validationLog("Fetching captive portal data from " + mCaptivePortalApiUrl); 2878 2879 final String apiContent; 2880 try { 2881 final URL url = new URL(mCaptivePortalApiUrl.toString()); 2882 // Protocol must be HTTPS 2883 // (as per https://www.ietf.org/id/draft-ietf-capport-api-07.txt, #4). 2884 // Only allow HTTP on localhost, for testing. 2885 final boolean isTestLocalhostHttp = mProperties.mIsTestNetwork 2886 && "localhost".equals(url.getHost()) && "http".equals(url.getProtocol()); 2887 if (!"https".equals(url.getProtocol()) && !isTestLocalhostHttp) { 2888 validationLog("Invalid captive portal API protocol: " + url.getProtocol()); 2889 return null; 2890 } 2891 2892 final HttpURLConnection conn = makeProbeConnection( 2893 url, true /* followRedirects */); 2894 conn.setRequestProperty(ACCEPT_HEADER, CAPPORT_API_CONTENT_TYPE); 2895 final int responseCode = conn.getResponseCode(); 2896 if (responseCode != 200) { 2897 validationLog("Non-200 API response code: " + conn.getResponseCode()); 2898 return null; 2899 } 2900 final Charset charset = extractCharset(conn.getHeaderField(CONTENT_TYPE_HEADER)); 2901 if (charset != StandardCharsets.UTF_8) { 2902 validationLog("Invalid charset for capport API: " + charset); 2903 return null; 2904 } 2905 2906 apiContent = readAsString(conn.getInputStream(), 2907 CAPPORT_API_MAX_JSON_LENGTH, charset); 2908 } catch (IOException e) { 2909 validationLog("I/O error reading capport data: " + e.getMessage()); 2910 return null; 2911 } 2912 2913 try { 2914 final JSONObject info = new JSONObject(apiContent); 2915 final CaptivePortalDataShim capportData = CaptivePortalDataShimImpl.fromJson(info); 2916 if (capportData != null && capportData.isCaptive() 2917 && capportData.getUserPortalUrl() == null) { 2918 validationLog("Missing user-portal-url from capport response"); 2919 return null; 2920 } 2921 return capportData; 2922 } catch (JSONException e) { 2923 validationLog("Could not parse capport API JSON: " + e.getMessage()); 2924 return null; 2925 } catch (UnsupportedApiLevelException e) { 2926 // This should never happen because LinkProperties would not have a capport URL 2927 // before R. 2928 validationLog("Platform API too low to support capport API"); 2929 return null; 2930 } 2931 } 2932 tryCapportApiProbe()2933 private CaptivePortalDataShim tryCapportApiProbe() { 2934 if (mCaptivePortalApiUrl == null) return null; 2935 final Stopwatch capportApiWatch = new Stopwatch().start(); 2936 final CaptivePortalDataShim capportData = sendCapportApiProbe(); 2937 recordProbeEventMetrics(ProbeType.PT_CAPPORT_API, capportApiWatch.stop(), 2938 capportData == null ? ProbeResult.PR_FAILURE : ProbeResult.PR_SUCCESS, 2939 capportData); 2940 return capportData; 2941 } 2942 2943 @Override sendProbe()2944 protected CaptivePortalProbeResult sendProbe() { 2945 final CaptivePortalDataShim capportData = tryCapportApiProbe(); 2946 if (capportData != null && capportData.isCaptive()) { 2947 final String loginUrlString = capportData.getUserPortalUrl().toString(); 2948 // Starting from R (where CaptivePortalData was introduced), the captive portal app 2949 // delegates to NetworkMonitor for verifying when the network validates instead of 2950 // probing the detectUrl. So pass the detectUrl to have the portal open on that, 2951 // page; CaptivePortalLogin will not use it for probing. 2952 return new CapportApiProbeResult( 2953 CaptivePortalProbeResult.PORTAL_CODE, 2954 loginUrlString /* redirectUrl */, 2955 loginUrlString /* detectUrl */, 2956 capportData, 2957 1 << ValidationProbeEvent.PROBE_HTTP); 2958 } 2959 2960 // If the API says it's not captive, still check for HTTP connectivity. This helps 2961 // with partial connectivity detection, and a broken API saying that there is no 2962 // redirect when there is one. 2963 final CaptivePortalProbeResult res = 2964 sendDnsAndHttpProbes(mProxy, mUrl, ValidationProbeEvent.PROBE_HTTP); 2965 return mCaptivePortalApiUrl == null ? res : new CapportApiProbeResult(res, capportData); 2966 } 2967 } 2968 isConclusiveResult(@onNull CaptivePortalProbeResult result, @Nullable Uri captivePortalApiUrl)2969 private static boolean isConclusiveResult(@NonNull CaptivePortalProbeResult result, 2970 @Nullable Uri captivePortalApiUrl) { 2971 // isPortal() is not expected on the HTTPS probe, but treat the network as portal would make 2972 // sense if the probe reports portal. In case the capport API is available, the API is 2973 // authoritative on whether there is a portal, so the HTTPS probe is not enough to conclude 2974 // there is connectivity, and a determination will be made once the capport API probe 2975 // returns. Note that the API can only force the system to detect a portal even if the HTTPS 2976 // probe succeeds. It cannot force the system to detect no portal if the HTTPS probe fails. 2977 return result.isPortal() 2978 || (result.isConcludedFromHttps() && result.isSuccessful() 2979 && captivePortalApiUrl == null); 2980 } 2981 sendMultiParallelHttpAndHttpsProbes( @onNull ValidationProperties properties, @Nullable ProxyInfo proxy, @NonNull URL[] httpsUrls, @NonNull URL[] httpUrls)2982 private CaptivePortalProbeResult sendMultiParallelHttpAndHttpsProbes( 2983 @NonNull ValidationProperties properties, @Nullable ProxyInfo proxy, 2984 @NonNull URL[] httpsUrls, @NonNull URL[] httpUrls) { 2985 // If multiple URLs are required to ensure the correctness of validation, send parallel 2986 // probes to explore the result in separate probe threads and aggregate those results into 2987 // one as the final result for either HTTP or HTTPS. 2988 2989 // Number of probes to wait for. 2990 final int num = httpsUrls.length + httpUrls.length; 2991 // Fixed pool to prevent configuring too many urls to exhaust system resource. 2992 final ExecutorService executor = Executors.newFixedThreadPool( 2993 Math.min(num, MAX_PROBE_THREAD_POOL_SIZE)); 2994 final CompletionService<CaptivePortalProbeResult> ecs = 2995 new ExecutorCompletionService<CaptivePortalProbeResult>(executor); 2996 final Uri capportApiUrl = getCaptivePortalApiUrl(mLinkProperties); 2997 final List<Future<CaptivePortalProbeResult>> futures = new ArrayList<>(); 2998 2999 try { 3000 // Queue https and http probe. 3001 3002 // Each of these HTTP probes will start with probing capport API if present. So if 3003 // multiple HTTP URLs are configured, AP will send multiple identical accesses to the 3004 // capport URL. Thus, send capport API probing with one of the HTTP probe is enough. 3005 // Probe capport API with the first HTTP probe. 3006 // TODO: Have the capport probe as a different probe for cleanliness. 3007 final URL urlMaybeWithCapport = httpUrls[0]; 3008 for (final URL url : httpUrls) { 3009 futures.add(ecs.submit(() -> new HttpProbe(properties, proxy, url, 3010 url.equals(urlMaybeWithCapport) ? capportApiUrl : null).sendProbe())); 3011 } 3012 3013 for (final URL url : httpsUrls) { 3014 futures.add(ecs.submit(() -> new HttpsProbe(properties, proxy, url, capportApiUrl) 3015 .sendProbe())); 3016 } 3017 3018 final ArrayList<CaptivePortalProbeResult> completedProbes = new ArrayList<>(); 3019 for (int i = 0; i < num; i++) { 3020 completedProbes.add(ecs.take().get()); 3021 final CaptivePortalProbeResult res = evaluateCapportResult( 3022 completedProbes, httpsUrls.length, capportApiUrl != null /* hasCapport */); 3023 if (res != null) { 3024 reportProbeResult(res); 3025 return res; 3026 } 3027 } 3028 } catch (ExecutionException e) { 3029 Log.e(TAG, "Error sending probes.", e); 3030 } catch (InterruptedException e) { 3031 // Ignore interrupted probe result because result is not important to conclude the 3032 // result. 3033 } finally { 3034 // Interrupt ongoing probes since we have already gotten result from one of them. 3035 futures.forEach(future -> future.cancel(true)); 3036 executor.shutdownNow(); 3037 } 3038 3039 return CaptivePortalProbeResult.failed(ValidationProbeEvent.PROBE_HTTPS); 3040 } 3041 3042 @Nullable evaluateCapportResult( List<CaptivePortalProbeResult> probes, int numHttps, boolean hasCapport)3043 private CaptivePortalProbeResult evaluateCapportResult( 3044 List<CaptivePortalProbeResult> probes, int numHttps, boolean hasCapport) { 3045 CaptivePortalProbeResult capportResult = null; 3046 CaptivePortalProbeResult httpPortalResult = null; 3047 int httpSuccesses = 0; 3048 int httpsSuccesses = 0; 3049 int httpsFailures = 0; 3050 3051 for (CaptivePortalProbeResult probe : probes) { 3052 if (probe instanceof CapportApiProbeResult) { 3053 capportResult = probe; 3054 } else if (probe.isConcludedFromHttps()) { 3055 if (probe.isSuccessful()) httpsSuccesses++; 3056 else httpsFailures++; 3057 } else { // http probes 3058 if (probe.isPortal()) { 3059 // Unlike https probe, http probe may have redirect url information kept in the 3060 // probe result. Thus, the result can not be newly created with response code 3061 // only. If the captive portal behavior will be varied because of different 3062 // probe URLs, this means that if the portal returns different redirect URLs for 3063 // different probes and has a different behavior depending on the URL, then the 3064 // behavior of the login page may differ depending on the order in which the 3065 // probes terminate. However, NetworkMonitor does have to choose one of the 3066 // redirect URLs and right now there is no clue at all which of the probe has 3067 // the better redirect URL, so there is no telling which is best to use. 3068 // Therefore the current code just uses whichever happens to be the last one to 3069 // complete. 3070 httpPortalResult = probe; 3071 } else if (probe.isSuccessful()) { 3072 httpSuccesses++; 3073 } 3074 } 3075 } 3076 // If there is Capport url configured but the result is not available yet, wait for it. 3077 if (hasCapport && capportResult == null) return null; 3078 // Capport API saying it's a portal is authoritative. 3079 if (capportResult != null && capportResult.isPortal()) return capportResult; 3080 // Any HTTP probes saying probe portal is conclusive. 3081 if (httpPortalResult != null) return httpPortalResult; 3082 // Any HTTPS probes works then the network validates. 3083 if (httpsSuccesses > 0) { 3084 return CaptivePortalProbeResult.success(1 << ValidationProbeEvent.PROBE_HTTPS); 3085 } 3086 // All HTTPS failed and at least one HTTP succeeded, then it's partial. 3087 if (httpsFailures == numHttps && httpSuccesses > 0) { 3088 return CaptivePortalProbeResult.PARTIAL; 3089 } 3090 // Otherwise, the result is unknown yet. 3091 return null; 3092 } 3093 reportProbeResult(@onNull CaptivePortalProbeResult res)3094 private void reportProbeResult(@NonNull CaptivePortalProbeResult res) { 3095 if (res instanceof CapportApiProbeResult) { 3096 maybeReportCaptivePortalData(((CapportApiProbeResult) res).getCaptivePortalData()); 3097 } 3098 3099 // This is not a if-else case since partial connectivity will concluded from both HTTP and 3100 // HTTPS probe. Both HTTP and HTTPS result should be reported. 3101 if (res.isConcludedFromHttps()) { 3102 reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTPS, res); 3103 } 3104 3105 if (res.isConcludedFromHttp()) { 3106 reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, res); 3107 } 3108 } 3109 sendHttpAndHttpsParallelWithFallbackProbes( ValidationProperties properties, ProxyInfo proxy, URL httpsUrl, URL httpUrl, URL fallbackUrl)3110 private CaptivePortalProbeResult sendHttpAndHttpsParallelWithFallbackProbes( 3111 ValidationProperties properties, ProxyInfo proxy, URL httpsUrl, URL httpUrl, 3112 URL fallbackUrl) { 3113 // Number of probes to wait for. If a probe completes with a conclusive answer 3114 // it shortcuts the latch immediately by forcing the count to 0. 3115 final CountDownLatch latch = new CountDownLatch(2); 3116 3117 final Uri capportApiUrl = getCaptivePortalApiUrl(mLinkProperties); 3118 final ProbeThread httpsProbe = new ProbeThread(latch, properties, proxy, httpsUrl, 3119 ValidationProbeEvent.PROBE_HTTPS, capportApiUrl); 3120 final ProbeThread httpProbe = new ProbeThread(latch, properties, proxy, httpUrl, 3121 ValidationProbeEvent.PROBE_HTTP, capportApiUrl); 3122 3123 try { 3124 httpsProbe.start(); 3125 httpProbe.start(); 3126 latch.await(PROBE_TIMEOUT_MS, TimeUnit.MILLISECONDS); 3127 } catch (InterruptedException e) { 3128 validationLog("Error: probes wait interrupted!"); 3129 return CaptivePortalProbeResult.failed(CaptivePortalProbeResult.PROBE_UNKNOWN); 3130 } 3131 3132 final CaptivePortalProbeResult httpsResult = httpsProbe.result(); 3133 final CaptivePortalProbeResult httpResult = httpProbe.result(); 3134 3135 // Look for a conclusive probe result first. 3136 if (isConclusiveResult(httpResult, capportApiUrl)) { 3137 reportProbeResult(httpProbe.result()); 3138 return httpResult; 3139 } 3140 3141 if (isConclusiveResult(httpsResult, capportApiUrl)) { 3142 reportProbeResult(httpsProbe.result()); 3143 return httpsResult; 3144 } 3145 // Consider a DNS response with a private IP address on the HTTP probe as an indication that 3146 // the network is not connected to the Internet, and have the whole evaluation fail in that 3147 // case, instead of potentially detecting a captive portal. This logic only affects portal 3148 // detection, not network validation. 3149 // This only applies if the DNS probe completed within PROBE_TIMEOUT_MS, as the fallback 3150 // probe should not be delayed by this check. 3151 if (mPrivateIpNoInternetEnabled && (httpResult.isDnsPrivateIpResponse())) { 3152 validationLog("DNS response to the URL is private IP"); 3153 return CaptivePortalProbeResult.failed(1 << ValidationProbeEvent.PROBE_HTTP); 3154 } 3155 // If a fallback method exists, use it to retry portal detection. 3156 // If we have new-style probe specs, use those. Otherwise, use the fallback URLs. 3157 final CaptivePortalProbeSpec probeSpec = nextFallbackSpec(); 3158 final URL fallback = (probeSpec != null) ? probeSpec.getUrl() : fallbackUrl; 3159 CaptivePortalProbeResult fallbackProbeResult = null; 3160 if (fallback != null) { 3161 fallbackProbeResult = sendHttpProbe(fallback, PROBE_FALLBACK, probeSpec); 3162 reportHttpProbeResult(NETWORK_VALIDATION_PROBE_FALLBACK, fallbackProbeResult); 3163 if (fallbackProbeResult.isPortal()) { 3164 return fallbackProbeResult; 3165 } 3166 } 3167 // Otherwise wait until http and https probes completes and use their results. 3168 try { 3169 httpProbe.join(); 3170 reportProbeResult(httpProbe.result()); 3171 3172 if (httpProbe.result().isPortal()) { 3173 return httpProbe.result(); 3174 } 3175 3176 httpsProbe.join(); 3177 reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTPS, httpsProbe.result()); 3178 3179 if (httpsProbe.result().isFailed() && httpProbe.result().isSuccessful()) { 3180 return CaptivePortalProbeResult.PARTIAL; 3181 } 3182 return httpsProbe.result(); 3183 } catch (InterruptedException e) { 3184 validationLog("Error: http or https probe wait interrupted!"); 3185 return CaptivePortalProbeResult.failed(CaptivePortalProbeResult.PROBE_UNKNOWN); 3186 } 3187 } 3188 3189 @Nullable makeURL(@ullable String url)3190 private URL makeURL(@Nullable String url) { 3191 return makeURL(url, mValidationLogs); 3192 } 3193 3194 @Nullable makeURL(@ullable String url, @NonNull SharedLog log)3195 private static URL makeURL(@Nullable String url, @NonNull SharedLog log) { 3196 if (url != null) { 3197 try { 3198 return new URL(url); 3199 } catch (MalformedURLException e) { 3200 log.w("Bad URL: " + url); 3201 } 3202 } 3203 return null; 3204 } 3205 logNetworkEvent(int evtype)3206 private void logNetworkEvent(int evtype) { 3207 int[] transports = mNetworkCapabilities.getTransportTypes(); 3208 mMetricsLog.log(mCleartextDnsNetwork, transports, new NetworkEvent(evtype)); 3209 } 3210 networkEventType(ValidationStage s, EvaluationResult r)3211 private int networkEventType(ValidationStage s, EvaluationResult r) { 3212 if (s.mIsFirstValidation) { 3213 if (r.mIsValidated) { 3214 return NetworkEvent.NETWORK_FIRST_VALIDATION_SUCCESS; 3215 } else { 3216 return NetworkEvent.NETWORK_FIRST_VALIDATION_PORTAL_FOUND; 3217 } 3218 } else { 3219 if (r.mIsValidated) { 3220 return NetworkEvent.NETWORK_REVALIDATION_SUCCESS; 3221 } else { 3222 return NetworkEvent.NETWORK_REVALIDATION_PORTAL_FOUND; 3223 } 3224 } 3225 } 3226 maybeLogEvaluationResult(int evtype)3227 private void maybeLogEvaluationResult(int evtype) { 3228 if (mEvaluationTimer.isRunning()) { 3229 int[] transports = mNetworkCapabilities.getTransportTypes(); 3230 mMetricsLog.log(mCleartextDnsNetwork, transports, 3231 new NetworkEvent(evtype, mEvaluationTimer.stop() / 1000)); 3232 mEvaluationTimer.reset(); 3233 } 3234 } 3235 logValidationProbe(long durationUs, int probeType, int probeResult)3236 private void logValidationProbe(long durationUs, int probeType, int probeResult) { 3237 int[] transports = mNetworkCapabilities.getTransportTypes(); 3238 boolean isFirstValidation = validationStage().mIsFirstValidation; 3239 ValidationProbeEvent ev = new ValidationProbeEvent.Builder() 3240 .setProbeType(probeType, isFirstValidation) 3241 .setReturnCode(probeResult) 3242 .setDurationMs(durationUs / 1000) 3243 .build(); 3244 mMetricsLog.log(mCleartextDnsNetwork, transports, ev); 3245 } 3246 3247 @VisibleForTesting 3248 public static class Dependencies { getPrivateDnsBypassNetwork(Network network)3249 public Network getPrivateDnsBypassNetwork(Network network) { 3250 return new OneAddressPerFamilyNetwork(network); 3251 } 3252 getDnsResolver()3253 public DnsResolver getDnsResolver() { 3254 return DnsResolver.getInstance(); 3255 } 3256 getRandom()3257 public Random getRandom() { 3258 return new Random(); 3259 } 3260 3261 /** 3262 * Get the value of a global integer setting. 3263 * @param symbol Name of the setting 3264 * @param defaultValue Value to return if the setting is not defined. 3265 */ getSetting(Context context, String symbol, int defaultValue)3266 public int getSetting(Context context, String symbol, int defaultValue) { 3267 return Settings.Global.getInt(context.getContentResolver(), symbol, defaultValue); 3268 } 3269 3270 /** 3271 * Get the value of a global String setting. 3272 * @param symbol Name of the setting 3273 * @param defaultValue Value to return if the setting is not defined. 3274 */ getSetting(Context context, String symbol, String defaultValue)3275 public String getSetting(Context context, String symbol, String defaultValue) { 3276 final String value = Settings.Global.getString(context.getContentResolver(), symbol); 3277 return value != null ? value : defaultValue; 3278 } 3279 3280 /** 3281 * Look up the value of a property in DeviceConfig. 3282 * @param namespace The namespace containing the property to look up. 3283 * @param name The name of the property to look up. 3284 * @param defaultValue The value to return if the property does not exist or has no non-null 3285 * value. 3286 * @return the corresponding value, or defaultValue if none exists. 3287 */ 3288 @Nullable getDeviceConfigProperty(@onNull String namespace, @NonNull String name, @Nullable String defaultValue)3289 public String getDeviceConfigProperty(@NonNull String namespace, @NonNull String name, 3290 @Nullable String defaultValue) { 3291 return DeviceConfigUtils.getDeviceConfigProperty(namespace, name, defaultValue); 3292 } 3293 3294 /** 3295 * Look up the value of a property in DeviceConfig. 3296 * @param namespace The namespace containing the property to look up. 3297 * @param name The name of the property to look up. 3298 * @param defaultValue The value to return if the property does not exist or has no non-null 3299 * value. 3300 * @return the corresponding value, or defaultValue if none exists. 3301 */ getDeviceConfigPropertyInt(@onNull String namespace, @NonNull String name, int defaultValue)3302 public int getDeviceConfigPropertyInt(@NonNull String namespace, @NonNull String name, 3303 int defaultValue) { 3304 return DeviceConfigUtils.getDeviceConfigPropertyInt(namespace, name, defaultValue); 3305 } 3306 3307 /** 3308 * Check whether or not one experimental feature in the connectivity namespace is 3309 * enabled. 3310 * @param name Flag name of the experiment in the connectivity namespace. 3311 * @see DeviceConfigUtils#isFeatureEnabled(Context, String, String) 3312 */ isFeatureEnabled(@onNull Context context, @NonNull String name)3313 public boolean isFeatureEnabled(@NonNull Context context, @NonNull String name) { 3314 return DeviceConfigUtils.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY, name); 3315 } 3316 3317 /** 3318 * Check whether or not one specific experimental feature for a particular namespace from 3319 * {@link DeviceConfig} is enabled by comparing NetworkStack module version 3320 * {@link NetworkStack} with current version of property. If this property version is valid, 3321 * the corresponding experimental feature would be enabled, otherwise disabled. 3322 * @param context The global context information about an app environment. 3323 * @param namespace The namespace containing the property to look up. 3324 * @param name The name of the property to look up. 3325 * @param defaultEnabled The value to return if the property does not exist or its value is 3326 * null. 3327 * @return true if this feature is enabled, or false if disabled. 3328 */ isFeatureEnabled(@onNull Context context, @NonNull String namespace, @NonNull String name, boolean defaultEnabled)3329 public boolean isFeatureEnabled(@NonNull Context context, @NonNull String namespace, 3330 @NonNull String name, boolean defaultEnabled) { 3331 return DeviceConfigUtils.isFeatureEnabled(context, namespace, name, defaultEnabled); 3332 } 3333 3334 /** 3335 * Collect data stall detection level information for each transport type. Write metrics 3336 * data to statsd pipeline. 3337 * @param stats a {@link DataStallDetectionStats} that contains the detection level 3338 * information. 3339 * @param result the network reevaluation result. 3340 */ writeDataStallDetectionStats(@onNull final DataStallDetectionStats stats, @NonNull final CaptivePortalProbeResult result)3341 public void writeDataStallDetectionStats(@NonNull final DataStallDetectionStats stats, 3342 @NonNull final CaptivePortalProbeResult result) { 3343 DataStallStatsUtils.write(stats, result); 3344 } 3345 3346 public static final Dependencies DEFAULT = new Dependencies(); 3347 } 3348 3349 /** 3350 * Methods in this class perform no locking because all accesses are performed on the state 3351 * machine's thread. Need to consider the thread safety if it ever could be accessed outside the 3352 * state machine. 3353 */ 3354 @VisibleForTesting 3355 protected class DnsStallDetector { 3356 private int mConsecutiveTimeoutCount = 0; 3357 private int mSize; 3358 final DnsResult[] mDnsEvents; 3359 final RingBufferIndices mResultIndices; 3360 DnsStallDetector(int size)3361 DnsStallDetector(int size) { 3362 mSize = Math.max(DEFAULT_DNS_LOG_SIZE, size); 3363 mDnsEvents = new DnsResult[mSize]; 3364 mResultIndices = new RingBufferIndices(mSize); 3365 } 3366 3367 @VisibleForTesting accumulateConsecutiveDnsTimeoutCount(int code)3368 protected void accumulateConsecutiveDnsTimeoutCount(int code) { 3369 final DnsResult result = new DnsResult(code); 3370 mDnsEvents[mResultIndices.add()] = result; 3371 if (result.isTimeout()) { 3372 mConsecutiveTimeoutCount++; 3373 } else { 3374 // Keep the event in mDnsEvents without clearing it so that there are logs to do the 3375 // simulation and analysis. 3376 mConsecutiveTimeoutCount = 0; 3377 } 3378 } 3379 isDataStallSuspected(int timeoutCountThreshold, int validTime)3380 private boolean isDataStallSuspected(int timeoutCountThreshold, int validTime) { 3381 if (timeoutCountThreshold <= 0) { 3382 Log.wtf(TAG, "Timeout count threshold should be larger than 0."); 3383 return false; 3384 } 3385 3386 // Check if the consecutive timeout count reach the threshold or not. 3387 if (mConsecutiveTimeoutCount < timeoutCountThreshold) { 3388 return false; 3389 } 3390 3391 // Check if the target dns event index is valid or not. 3392 final int firstConsecutiveTimeoutIndex = 3393 mResultIndices.indexOf(mResultIndices.size() - timeoutCountThreshold); 3394 3395 // If the dns timeout events happened long time ago, the events are meaningless for 3396 // data stall evaluation. Thus, check if the first consecutive timeout dns event 3397 // considered in the evaluation happened in defined threshold time. 3398 final long now = SystemClock.elapsedRealtime(); 3399 final long firstTimeoutTime = now - mDnsEvents[firstConsecutiveTimeoutIndex].mTimeStamp; 3400 if (DDBG_STALL) { 3401 Log.d(TAG, "DSD.isDataStallSuspected, first=" 3402 + firstTimeoutTime + ", valid=" + validTime); 3403 } 3404 return (firstTimeoutTime < validTime); 3405 } 3406 getConsecutiveTimeoutCount()3407 int getConsecutiveTimeoutCount() { 3408 return mConsecutiveTimeoutCount; 3409 } 3410 } 3411 3412 private static class DnsResult { 3413 // TODO: Need to move the DNS return code definition to a specific class once unify DNS 3414 // response code is done. 3415 private static final int RETURN_CODE_DNS_TIMEOUT = 255; 3416 3417 private final long mTimeStamp; 3418 private final int mReturnCode; 3419 DnsResult(int code)3420 DnsResult(int code) { 3421 mTimeStamp = SystemClock.elapsedRealtime(); 3422 mReturnCode = code; 3423 } 3424 isTimeout()3425 private boolean isTimeout() { 3426 return mReturnCode == RETURN_CODE_DNS_TIMEOUT; 3427 } 3428 } 3429 3430 @VisibleForTesting 3431 @Nullable getDnsStallDetector()3432 protected DnsStallDetector getDnsStallDetector() { 3433 return mDnsStallDetector; 3434 } 3435 3436 @Nullable getTcpSocketTracker()3437 private TcpSocketTracker getTcpSocketTracker() { 3438 return mTcpTracker; 3439 } 3440 dataStallEvaluateTypeEnabled(int type)3441 private boolean dataStallEvaluateTypeEnabled(int type) { 3442 return (mDataStallEvaluationType & type) != 0; 3443 } 3444 3445 @VisibleForTesting getLastProbeTime()3446 protected long getLastProbeTime() { 3447 return mLastProbeTime; 3448 } 3449 3450 @VisibleForTesting isDataStall()3451 protected boolean isDataStall() { 3452 if (!isDataStallDetectionRequired()) { 3453 return false; 3454 } 3455 3456 int typeToCollect = 0; 3457 final int notStall = -1; 3458 final StringJoiner msg = (DBG || VDBG_STALL || DDBG_STALL) ? new StringJoiner(", ") : null; 3459 // Reevaluation will generate traffic. Thus, set a minimal reevaluation timer to limit the 3460 // possible traffic cost in metered network. 3461 final long currentTime = SystemClock.elapsedRealtime(); 3462 if (!mNetworkCapabilities.hasCapability(NET_CAPABILITY_NOT_METERED) 3463 && (currentTime - getLastProbeTime() < mDataStallMinEvaluateTime)) { 3464 if (DDBG_STALL) { 3465 Log.d(TAG, "isDataStall: false, currentTime=" + currentTime 3466 + ", lastProbeTime=" + getLastProbeTime() 3467 + ", MinEvaluateTime=" + mDataStallMinEvaluateTime); 3468 } 3469 return false; 3470 } 3471 // Check TCP signal. Suspect it may be a data stall if : 3472 // 1. TCP connection fail rate(lost+retrans) is higher than threshold. 3473 // 2. Accumulate enough packets count. 3474 final TcpSocketTracker tst = getTcpSocketTracker(); 3475 if (dataStallEvaluateTypeEnabled(DATA_STALL_EVALUATION_TYPE_TCP) && tst != null) { 3476 if (tst.getLatestReceivedCount() > 0) { 3477 typeToCollect = notStall; 3478 } else if (tst.isDataStallSuspected()) { 3479 typeToCollect |= DATA_STALL_EVALUATION_TYPE_TCP; 3480 } 3481 if (DBG || VDBG_STALL || DDBG_STALL) { 3482 msg.add("tcp packets received=" + tst.getLatestReceivedCount()) 3483 .add("latest tcp fail rate=" + tst.getLatestPacketFailPercentage()); 3484 } 3485 } 3486 3487 // Check dns signal. Suspect it may be a data stall if both : 3488 // 1. The number of consecutive DNS query timeouts >= mConsecutiveDnsTimeoutThreshold. 3489 // 2. Those consecutive DNS queries happened in the last mValidDataStallDnsTimeThreshold ms. 3490 final DnsStallDetector dsd = getDnsStallDetector(); 3491 if ((typeToCollect != notStall) && (dsd != null) 3492 && dataStallEvaluateTypeEnabled(DATA_STALL_EVALUATION_TYPE_DNS)) { 3493 if (dsd.isDataStallSuspected( 3494 mConsecutiveDnsTimeoutThreshold, mDataStallValidDnsTimeThreshold)) { 3495 typeToCollect |= DATA_STALL_EVALUATION_TYPE_DNS; 3496 logNetworkEvent(NetworkEvent.NETWORK_CONSECUTIVE_DNS_TIMEOUT_FOUND); 3497 } 3498 if (DBG || VDBG_STALL || DDBG_STALL) { 3499 msg.add("consecutive dns timeout count=" + dsd.getConsecutiveTimeoutCount()); 3500 } 3501 } 3502 3503 if (typeToCollect > 0) { 3504 mDataStallTypeToCollect = typeToCollect; 3505 final DataStallReportParcelable p = new DataStallReportParcelable(); 3506 int detectionMethod = 0; 3507 p.timestampMillis = SystemClock.elapsedRealtime(); 3508 if (isDataStallTypeDetected(typeToCollect, DATA_STALL_EVALUATION_TYPE_DNS)) { 3509 detectionMethod |= DETECTION_METHOD_DNS_EVENTS; 3510 p.dnsConsecutiveTimeouts = mDnsStallDetector.getConsecutiveTimeoutCount(); 3511 } 3512 3513 if (isDataStallTypeDetected(typeToCollect, DATA_STALL_EVALUATION_TYPE_TCP)) { 3514 detectionMethod |= DETECTION_METHOD_TCP_METRICS; 3515 p.tcpPacketFailRate = tst.getLatestPacketFailPercentage(); 3516 p.tcpMetricsCollectionPeriodMillis = getTcpPollingInterval(); 3517 } 3518 p.detectionMethod = detectionMethod; 3519 notifyDataStallSuspected(p); 3520 } 3521 3522 // log only data stall suspected. 3523 if ((DBG && (typeToCollect > 0)) || VDBG_STALL || DDBG_STALL) { 3524 log("isDataStall: result=" + typeToCollect + ", " + msg); 3525 } 3526 3527 return typeToCollect > 0; 3528 } 3529 isDataStallTypeDetected(int typeToCollect, int evaluationType)3530 private static boolean isDataStallTypeDetected(int typeToCollect, int evaluationType) { 3531 return (typeToCollect & evaluationType) != 0; 3532 } 3533 // Class to keep state of evaluation results and probe results. 3534 // 3535 // The main purpose was to ensure NetworkMonitor can notify ConnectivityService of probe results 3536 // as soon as they happen, without triggering any other changes. This requires keeping state on 3537 // the most recent evaluation result. Calling noteProbeResult will ensure that the results 3538 // reported to ConnectivityService contain the previous evaluation result, and thus won't 3539 // trigger a validation or partial connectivity state change. 3540 // 3541 // Note that this class is not currently being used for this purpose. The reason is that some 3542 // of the system behaviour triggered by reporting network validation - notably, NetworkAgent 3543 // behaviour - depends not only on the value passed by notifyNetworkTested, but also on the 3544 // fact that notifyNetworkTested was called. For example, telephony triggers network recovery 3545 // any time it is told that validation failed, i.e., if the result does not contain 3546 // NETWORK_VALIDATION_RESULT_VALID. But with this scheme, the first two or three validation 3547 // reports are all failures, because they are "HTTP succeeded but validation not yet passed", 3548 // "HTTP and HTTPS succeeded but validation not yet passed", etc. 3549 // TODO : rename EvaluationState to not contain "State" in the name, as it makes this class 3550 // sound like one of the states of the state machine, which it's not. 3551 @VisibleForTesting 3552 protected class EvaluationState { 3553 // The latest validation result for this network. This is a bitmask of 3554 // INetworkMonitor.NETWORK_VALIDATION_RESULT_* constants. 3555 private int mEvaluationResult = NETWORK_VALIDATION_RESULT_INVALID; 3556 3557 3558 // Set when the captive portal app said this network should be used as is as a result 3559 // of user interaction. The valid bit represents the user's decision to override automatic 3560 // determination of whether the network has access to Internet, so in this case the 3561 // network is always reported as validated. 3562 // TODO : Make ConnectivityService aware of this state, so that it can use the network as 3563 // the default without setting the VALIDATED bit, as it's a bit of a lie. This can't be 3564 // done on Android <= R where CS can't be updated, but it is doable on S+. 3565 private boolean mCaptivePortalWantedAsIs = false; 3566 // Indicates which probes have succeeded since clearProbeResults was called. 3567 // This is a bitmask of INetworkMonitor.NETWORK_VALIDATION_PROBE_* constants. 3568 private int mProbeResults = 0; 3569 // A bitmask to record which probes are completed. 3570 private int mProbeCompleted = 0; 3571 clearProbeResults()3572 protected void clearProbeResults() { 3573 mProbeResults = 0; 3574 mProbeCompleted = 0; 3575 } 3576 maybeNotifyProbeResults(@onNull final Runnable modif)3577 private void maybeNotifyProbeResults(@NonNull final Runnable modif) { 3578 final int oldCompleted = mProbeCompleted; 3579 final int oldResults = mProbeResults; 3580 modif.run(); 3581 if (oldCompleted != mProbeCompleted || oldResults != mProbeResults) { 3582 notifyProbeStatusChanged(mProbeCompleted, mProbeResults); 3583 } 3584 } 3585 removeProbeResult(final int probeResult)3586 protected void removeProbeResult(final int probeResult) { 3587 maybeNotifyProbeResults(() -> { 3588 mProbeCompleted &= ~probeResult; 3589 mProbeResults &= ~probeResult; 3590 }); 3591 } 3592 noteProbeResult(final int probeResult, final boolean succeeded)3593 protected void noteProbeResult(final int probeResult, final boolean succeeded) { 3594 maybeNotifyProbeResults(() -> { 3595 mProbeCompleted |= probeResult; 3596 if (succeeded) { 3597 mProbeResults |= probeResult; 3598 } else { 3599 mProbeResults &= ~probeResult; 3600 } 3601 }); 3602 } 3603 setCaptivePortalWantedAsIs()3604 protected void setCaptivePortalWantedAsIs() { 3605 mCaptivePortalWantedAsIs = true; 3606 } 3607 isCaptivePortalWantedAsIs()3608 protected boolean isCaptivePortalWantedAsIs() { 3609 return mCaptivePortalWantedAsIs; 3610 } 3611 reportEvaluationResult(int result, @Nullable String redirectUrl)3612 protected void reportEvaluationResult(int result, @Nullable String redirectUrl) { 3613 if (mCaptivePortalWantedAsIs) { 3614 result = NETWORK_VALIDATION_RESULT_VALID; 3615 } else if (!isValidationRequired() && mProbeCompleted == 0 && mCallbackVersion >= 11) { 3616 // If validation is not required AND no probes were attempted, the validation was 3617 // skipped. Report this to ConnectivityService for ConnectivityDiagnostics, but only 3618 // if the platform has callback version 11+, as ConnectivityService must also know 3619 // how to understand this bit. 3620 result |= NETWORK_VALIDATION_RESULT_SKIPPED; 3621 } 3622 3623 mEvaluationResult = result; 3624 final NetworkTestResultParcelable p = new NetworkTestResultParcelable(); 3625 p.result = result; 3626 p.probesSucceeded = mProbeResults; 3627 p.probesAttempted = mProbeCompleted; 3628 p.redirectUrl = redirectUrl; 3629 p.timestampMillis = SystemClock.elapsedRealtime(); 3630 notifyNetworkTested(p); 3631 recordValidationResult(result, redirectUrl); 3632 } 3633 3634 @VisibleForTesting getEvaluationResult()3635 protected int getEvaluationResult() { 3636 return mEvaluationResult; 3637 } 3638 3639 @VisibleForTesting getProbeResults()3640 protected int getProbeResults() { 3641 return mProbeResults; 3642 } 3643 3644 @VisibleForTesting getProbeCompletedResult()3645 protected int getProbeCompletedResult() { 3646 return mProbeCompleted; 3647 } 3648 } 3649 3650 @VisibleForTesting getEvaluationState()3651 protected EvaluationState getEvaluationState() { 3652 return mEvaluationState; 3653 } 3654 maybeDisableHttpsProbing(boolean acceptPartial)3655 private void maybeDisableHttpsProbing(boolean acceptPartial) { 3656 mAcceptPartialConnectivity = acceptPartial; 3657 // Ignore https probe in next validation if user accept partial connectivity on a partial 3658 // connectivity network. 3659 if (((mEvaluationState.getEvaluationResult() & NETWORK_VALIDATION_RESULT_PARTIAL) != 0) 3660 && mAcceptPartialConnectivity) { 3661 mUseHttps = false; 3662 } 3663 } 3664 3665 // Report HTTP, HTTP or FALLBACK probe result. 3666 @VisibleForTesting reportHttpProbeResult(int probeResult, @NonNull final CaptivePortalProbeResult result)3667 protected void reportHttpProbeResult(int probeResult, 3668 @NonNull final CaptivePortalProbeResult result) { 3669 boolean succeeded = result.isSuccessful(); 3670 // The success of a HTTP probe does not tell us whether the DNS probe succeeded. 3671 // The DNS and HTTP probes run one after the other in sendDnsAndHttpProbes, and that 3672 // method cannot report the result of the DNS probe because that it could be running 3673 // on a different thread which is racing with the main state machine thread. So, if 3674 // an HTTP or HTTPS probe succeeded, assume that the DNS probe succeeded. But if an 3675 // HTTP or HTTPS probe failed, don't assume that DNS is not working. 3676 // TODO: fix this. 3677 if (succeeded) { 3678 probeResult |= NETWORK_VALIDATION_PROBE_DNS; 3679 } 3680 mEvaluationState.noteProbeResult(probeResult, succeeded); 3681 } 3682 maybeReportCaptivePortalData(@ullable CaptivePortalDataShim data)3683 private void maybeReportCaptivePortalData(@Nullable CaptivePortalDataShim data) { 3684 // Do not clear data even if it is null: access points should not stop serving the API, so 3685 // if the API disappears this is treated as a temporary failure, and previous data should 3686 // remain valid. 3687 if (data == null) return; 3688 try { 3689 data.notifyChanged(mCallback); 3690 } catch (RemoteException e) { 3691 Log.e(TAG, "Error notifying ConnectivityService of new capport data", e); 3692 } 3693 } 3694 3695 /** 3696 * Interface for logging dns results. 3697 */ 3698 public interface DnsLogFunc { 3699 /** 3700 * Log function. 3701 */ log(String s)3702 void log(String s); 3703 } 3704 3705 @Nullable getTcpSocketTrackerOrNull(Context context, Network network, Dependencies deps)3706 private static TcpSocketTracker getTcpSocketTrackerOrNull(Context context, Network network, 3707 Dependencies deps) { 3708 return (getIsCaptivePortalCheckEnabled(context, deps) 3709 && (deps.getDeviceConfigPropertyInt( 3710 NAMESPACE_CONNECTIVITY, 3711 CONFIG_DATA_STALL_EVALUATION_TYPE, 3712 DEFAULT_DATA_STALL_EVALUATION_TYPES) 3713 & DATA_STALL_EVALUATION_TYPE_TCP) != 0) 3714 ? new TcpSocketTracker(new TcpSocketTracker.Dependencies(context), network) 3715 : null; 3716 } 3717 3718 @Nullable initDnsStallDetectorIfRequired(boolean isCaptivePortalCheckEnabled, int type, int threshold)3719 private DnsStallDetector initDnsStallDetectorIfRequired(boolean isCaptivePortalCheckEnabled, 3720 int type, int threshold) { 3721 return (isCaptivePortalCheckEnabled && (type & DATA_STALL_EVALUATION_TYPE_DNS) != 0) 3722 ? new DnsStallDetector(threshold) : null; 3723 } 3724 getCaptivePortalApiUrl(LinkProperties lp)3725 private static Uri getCaptivePortalApiUrl(LinkProperties lp) { 3726 return NetworkInformationShimImpl.newInstance().getCaptivePortalApiUrl(lp); 3727 } 3728 3729 /** 3730 * Check if the network is captive with terms and conditions page 3731 * @return true if network is captive with T&C page, false otherwise 3732 */ isTermsAndConditionsCaptive(CaptivePortalDataShim captivePortalDataShim)3733 private boolean isTermsAndConditionsCaptive(CaptivePortalDataShim captivePortalDataShim) { 3734 return captivePortalDataShim != null 3735 && captivePortalDataShim.getUserPortalUrl() != null 3736 && !TextUtils.isEmpty(captivePortalDataShim.getUserPortalUrl().toString()) 3737 && captivePortalDataShim.isCaptive() 3738 && captivePortalDataShim.getUserPortalUrlSource() 3739 == ConstantsShim.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT; 3740 } 3741 checkAndRenewResourceConfig()3742 private boolean checkAndRenewResourceConfig() { 3743 boolean reevaluationNeeded = false; 3744 3745 final Context customizedContext = getCustomizedContextOrDefault(); 3746 final URL[] captivePortalHttpsUrls = makeCaptivePortalHttpsUrls(customizedContext); 3747 if (!Arrays.equals(mCaptivePortalHttpsUrls, captivePortalHttpsUrls)) { 3748 mCaptivePortalHttpsUrls = captivePortalHttpsUrls; 3749 reevaluationNeeded = true; 3750 log("checkAndRenewResourceConfig: update captive portal https urls to " 3751 + Arrays.toString(mCaptivePortalHttpsUrls)); 3752 } 3753 3754 final URL[] captivePortalHttpUrls = makeCaptivePortalHttpUrls(customizedContext); 3755 if (!Arrays.equals(mCaptivePortalHttpUrls, captivePortalHttpUrls)) { 3756 mCaptivePortalHttpUrls = captivePortalHttpUrls; 3757 reevaluationNeeded = true; 3758 log("checkAndRenewResourceConfig: update captive portal http urls to " 3759 + Arrays.toString(mCaptivePortalHttpUrls)); 3760 } 3761 3762 final URL[] captivePortalFallbackUrls = makeCaptivePortalFallbackUrls(customizedContext); 3763 if (!Arrays.equals(mCaptivePortalFallbackUrls, captivePortalFallbackUrls)) { 3764 mCaptivePortalFallbackUrls = captivePortalFallbackUrls; 3765 // Reset the index since the array is changed. 3766 mNextFallbackUrlIndex = 0; 3767 reevaluationNeeded = true; 3768 log("checkAndRenewResourceConfig: update captive portal fallback urls to" 3769 + Arrays.toString(mCaptivePortalFallbackUrls)); 3770 } 3771 3772 return reevaluationNeeded; 3773 } 3774 } 3775