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