1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.connectivity; 18 19 import static android.net.CaptivePortal.APP_RETURN_DISMISSED; 20 import static android.net.CaptivePortal.APP_RETURN_UNWANTED; 21 import static android.net.CaptivePortal.APP_RETURN_WANTED_AS_IS; 22 import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_PROBE_SPEC; 23 import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL; 24 import static android.net.metrics.ValidationProbeEvent.PROBE_FALLBACK; 25 26 import android.annotation.Nullable; 27 import android.app.PendingIntent; 28 import android.content.BroadcastReceiver; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.content.IntentFilter; 32 import android.net.CaptivePortal; 33 import android.net.ConnectivityManager; 34 import android.net.ICaptivePortal; 35 import android.net.Network; 36 import android.net.NetworkCapabilities; 37 import android.net.NetworkRequest; 38 import android.net.ProxyInfo; 39 import android.net.TrafficStats; 40 import android.net.Uri; 41 import android.net.captiveportal.CaptivePortalProbeResult; 42 import android.net.captiveportal.CaptivePortalProbeSpec; 43 import android.net.dns.ResolvUtil; 44 import android.net.metrics.IpConnectivityLog; 45 import android.net.metrics.NetworkEvent; 46 import android.net.metrics.ValidationProbeEvent; 47 import android.net.util.Stopwatch; 48 import android.net.wifi.WifiInfo; 49 import android.net.wifi.WifiManager; 50 import android.os.Handler; 51 import android.os.Message; 52 import android.os.SystemClock; 53 import android.os.UserHandle; 54 import android.provider.Settings; 55 import android.telephony.CellIdentityCdma; 56 import android.telephony.CellIdentityGsm; 57 import android.telephony.CellIdentityLte; 58 import android.telephony.CellIdentityWcdma; 59 import android.telephony.CellInfo; 60 import android.telephony.CellInfoCdma; 61 import android.telephony.CellInfoGsm; 62 import android.telephony.CellInfoLte; 63 import android.telephony.CellInfoWcdma; 64 import android.telephony.TelephonyManager; 65 import android.text.TextUtils; 66 import android.util.LocalLog; 67 import android.util.LocalLog.ReadOnlyLocalLog; 68 import android.util.Log; 69 70 import com.android.internal.annotations.VisibleForTesting; 71 import com.android.internal.util.ArrayUtils; 72 import com.android.internal.util.Protocol; 73 import com.android.internal.util.State; 74 import com.android.internal.util.StateMachine; 75 import com.android.server.connectivity.DnsManager.PrivateDnsConfig; 76 77 import java.io.IOException; 78 import java.net.HttpURLConnection; 79 import java.net.InetAddress; 80 import java.net.MalformedURLException; 81 import java.net.URL; 82 import java.net.UnknownHostException; 83 import java.util.ArrayList; 84 import java.util.Arrays; 85 import java.util.Collections; 86 import java.util.LinkedHashMap; 87 import java.util.List; 88 import java.util.Random; 89 import java.util.UUID; 90 import java.util.concurrent.CountDownLatch; 91 import java.util.concurrent.TimeUnit; 92 93 /** 94 * {@hide} 95 */ 96 public class NetworkMonitor extends StateMachine { 97 private static final String TAG = NetworkMonitor.class.getSimpleName(); 98 private static final boolean DBG = true; 99 private static final boolean VDBG = false; 100 101 // Default configuration values for captive portal detection probes. 102 // TODO: append a random length parameter to the default HTTPS url. 103 // TODO: randomize browser version ids in the default User-Agent String. 104 private static final String DEFAULT_HTTPS_URL = "https://www.google.com/generate_204"; 105 private static final String DEFAULT_HTTP_URL = 106 "http://connectivitycheck.gstatic.com/generate_204"; 107 private static final String DEFAULT_FALLBACK_URL = "http://www.google.com/gen_204"; 108 private static final String DEFAULT_OTHER_FALLBACK_URLS = 109 "http://play.googleapis.com/generate_204"; 110 private static final String DEFAULT_USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) " 111 + "AppleWebKit/537.36 (KHTML, like Gecko) " 112 + "Chrome/60.0.3112.32 Safari/537.36"; 113 114 private static final int SOCKET_TIMEOUT_MS = 10000; 115 private static final int PROBE_TIMEOUT_MS = 3000; 116 117 static enum EvaluationResult { 118 VALIDATED(true), 119 CAPTIVE_PORTAL(false); 120 final boolean isValidated; EvaluationResult(boolean isValidated)121 EvaluationResult(boolean isValidated) { 122 this.isValidated = isValidated; 123 } 124 } 125 126 static enum ValidationStage { 127 FIRST_VALIDATION(true), 128 REVALIDATION(false); 129 final boolean isFirstValidation; ValidationStage(boolean isFirstValidation)130 ValidationStage(boolean isFirstValidation) { 131 this.isFirstValidation = isFirstValidation; 132 } 133 } 134 135 // After a network has been tested this result can be sent with EVENT_NETWORK_TESTED. 136 // The network should be used as a default internet connection. It was found to be: 137 // 1. a functioning network providing internet access, or 138 // 2. a captive portal and the user decided to use it as is. 139 public static final int NETWORK_TEST_RESULT_VALID = 0; 140 // After a network has been tested this result can be sent with EVENT_NETWORK_TESTED. 141 // The network should not be used as a default internet connection. It was found to be: 142 // 1. a captive portal and the user is prompted to sign-in, or 143 // 2. a captive portal and the user did not want to use it, or 144 // 3. a broken network (e.g. DNS failed, connect failed, HTTP request failed). 145 public static final int NETWORK_TEST_RESULT_INVALID = 1; 146 147 private static final int BASE = Protocol.BASE_NETWORK_MONITOR; 148 149 /** 150 * Inform NetworkMonitor that their network is connected. 151 * Initiates Network Validation. 152 */ 153 public static final int CMD_NETWORK_CONNECTED = BASE + 1; 154 155 /** 156 * Inform ConnectivityService that the network has been tested. 157 * obj = String representing URL that Internet probe was redirect to, if it was redirected. 158 * arg1 = One of the NETWORK_TESTED_RESULT_* constants. 159 * arg2 = NetID. 160 */ 161 public static final int EVENT_NETWORK_TESTED = BASE + 2; 162 163 /** 164 * Message to self indicating it's time to evaluate a network's connectivity. 165 * arg1 = Token to ignore old messages. 166 */ 167 private static final int CMD_REEVALUATE = BASE + 6; 168 169 /** 170 * Inform NetworkMonitor that the network has disconnected. 171 */ 172 public static final int CMD_NETWORK_DISCONNECTED = BASE + 7; 173 174 /** 175 * Force evaluation even if it has succeeded in the past. 176 * arg1 = UID responsible for requesting this reeval. Will be billed for data. 177 */ 178 private static final int CMD_FORCE_REEVALUATION = BASE + 8; 179 180 /** 181 * Message to self indicating captive portal app finished. 182 * arg1 = one of: APP_RETURN_DISMISSED, 183 * APP_RETURN_UNWANTED, 184 * APP_RETURN_WANTED_AS_IS 185 * obj = mCaptivePortalLoggedInResponseToken as String 186 */ 187 private static final int CMD_CAPTIVE_PORTAL_APP_FINISHED = BASE + 9; 188 189 /** 190 * Request ConnectivityService display provisioning notification. 191 * arg1 = Whether to make the notification visible. 192 * arg2 = NetID. 193 * obj = Intent to be launched when notification selected by user, null if !arg1. 194 */ 195 public static final int EVENT_PROVISIONING_NOTIFICATION = BASE + 10; 196 197 /** 198 * Message indicating sign-in app should be launched. 199 * Sent by mLaunchCaptivePortalAppBroadcastReceiver when the 200 * user touches the sign in notification, or sent by 201 * ConnectivityService when the user touches the "sign into 202 * network" button in the wifi access point detail page. 203 */ 204 public static final int CMD_LAUNCH_CAPTIVE_PORTAL_APP = BASE + 11; 205 206 /** 207 * Retest network to see if captive portal is still in place. 208 * arg1 = UID responsible for requesting this reeval. Will be billed for data. 209 * 0 indicates self-initiated, so nobody to blame. 210 */ 211 private static final int CMD_CAPTIVE_PORTAL_RECHECK = BASE + 12; 212 213 /** 214 * ConnectivityService notifies NetworkMonitor of settings changes to 215 * Private DNS. If a DNS resolution is required, e.g. for DNS-over-TLS in 216 * strict mode, then an event is sent back to ConnectivityService with the 217 * result of the resolution attempt. 218 * 219 * A separate message is used to trigger (re)evaluation of the Private DNS 220 * configuration, so that the message can be handled as needed in different 221 * states, including being ignored until after an ongoing captive portal 222 * validation phase is completed. 223 */ 224 private static final int CMD_PRIVATE_DNS_SETTINGS_CHANGED = BASE + 13; 225 public static final int EVENT_PRIVATE_DNS_CONFIG_RESOLVED = BASE + 14; 226 private static final int CMD_EVALUATE_PRIVATE_DNS = BASE + 15; 227 228 // Start mReevaluateDelayMs at this value and double. 229 private static final int INITIAL_REEVALUATE_DELAY_MS = 1000; 230 private static final int MAX_REEVALUATE_DELAY_MS = 10*60*1000; 231 // Before network has been evaluated this many times, ignore repeated reevaluate requests. 232 private static final int IGNORE_REEVALUATE_ATTEMPTS = 5; 233 private int mReevaluateToken = 0; 234 private static final int NO_UID = 0; 235 private static final int INVALID_UID = -1; 236 private int mUidResponsibleForReeval = INVALID_UID; 237 // Stop blaming UID that requested re-evaluation after this many attempts. 238 private static final int BLAME_FOR_EVALUATION_ATTEMPTS = 5; 239 // Delay between reevaluations once a captive portal has been found. 240 private static final int CAPTIVE_PORTAL_REEVALUATE_DELAY_MS = 10*60*1000; 241 242 private static final int NUM_VALIDATION_LOG_LINES = 20; 243 244 private String mPrivateDnsProviderHostname = ""; 245 isValidationRequired( NetworkCapabilities dfltNetCap, NetworkCapabilities nc)246 public static boolean isValidationRequired( 247 NetworkCapabilities dfltNetCap, NetworkCapabilities nc) { 248 // TODO: Consider requiring validation for DUN networks. 249 return dfltNetCap.satisfiedByNetworkCapabilities(nc); 250 } 251 252 private final Context mContext; 253 private final Handler mConnectivityServiceHandler; 254 private final NetworkAgentInfo mNetworkAgentInfo; 255 private final Network mNetwork; 256 private final int mNetId; 257 private final TelephonyManager mTelephonyManager; 258 private final WifiManager mWifiManager; 259 private final NetworkRequest mDefaultRequest; 260 private final IpConnectivityLog mMetricsLog; 261 private final NetworkMonitorSettings mSettings; 262 263 // Configuration values for captive portal detection probes. 264 private final String mCaptivePortalUserAgent; 265 private final URL mCaptivePortalHttpsUrl; 266 private final URL mCaptivePortalHttpUrl; 267 private final URL[] mCaptivePortalFallbackUrls; 268 @Nullable 269 private final CaptivePortalProbeSpec[] mCaptivePortalFallbackSpecs; 270 271 @VisibleForTesting 272 protected boolean mIsCaptivePortalCheckEnabled; 273 274 private boolean mUseHttps; 275 // The total number of captive portal detection attempts for this NetworkMonitor instance. 276 private int mValidations = 0; 277 278 // Set if the user explicitly selected "Do not use this network" in captive portal sign-in app. 279 private boolean mUserDoesNotWant = false; 280 // Avoids surfacing "Sign in to network" notification. 281 private boolean mDontDisplaySigninNotification = false; 282 283 public boolean systemReady = false; 284 285 private final State mDefaultState = new DefaultState(); 286 private final State mValidatedState = new ValidatedState(); 287 private final State mMaybeNotifyState = new MaybeNotifyState(); 288 private final State mEvaluatingState = new EvaluatingState(); 289 private final State mCaptivePortalState = new CaptivePortalState(); 290 private final State mEvaluatingPrivateDnsState = new EvaluatingPrivateDnsState(); 291 292 private CustomIntentReceiver mLaunchCaptivePortalAppBroadcastReceiver = null; 293 294 private final LocalLog validationLogs = new LocalLog(NUM_VALIDATION_LOG_LINES); 295 296 private final Stopwatch mEvaluationTimer = new Stopwatch(); 297 298 // This variable is set before transitioning to the mCaptivePortalState. 299 private CaptivePortalProbeResult mLastPortalProbeResult = CaptivePortalProbeResult.FAILED; 300 301 private int mNextFallbackUrlIndex = 0; 302 NetworkMonitor(Context context, Handler handler, NetworkAgentInfo networkAgentInfo, NetworkRequest defaultRequest)303 public NetworkMonitor(Context context, Handler handler, NetworkAgentInfo networkAgentInfo, 304 NetworkRequest defaultRequest) { 305 this(context, handler, networkAgentInfo, defaultRequest, new IpConnectivityLog(), 306 NetworkMonitorSettings.DEFAULT); 307 } 308 309 @VisibleForTesting NetworkMonitor(Context context, Handler handler, NetworkAgentInfo networkAgentInfo, NetworkRequest defaultRequest, IpConnectivityLog logger, NetworkMonitorSettings settings)310 protected NetworkMonitor(Context context, Handler handler, NetworkAgentInfo networkAgentInfo, 311 NetworkRequest defaultRequest, IpConnectivityLog logger, 312 NetworkMonitorSettings settings) { 313 // Add suffix indicating which NetworkMonitor we're talking about. 314 super(TAG + networkAgentInfo.name()); 315 316 // Logs with a tag of the form given just above, e.g. 317 // <timestamp> 862 2402 D NetworkMonitor/NetworkAgentInfo [WIFI () - 100]: ... 318 setDbg(VDBG); 319 320 mContext = context; 321 mMetricsLog = logger; 322 mConnectivityServiceHandler = handler; 323 mSettings = settings; 324 mNetworkAgentInfo = networkAgentInfo; 325 mNetwork = new OneAddressPerFamilyNetwork(networkAgentInfo.network()); 326 mNetId = mNetwork.netId; 327 mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); 328 mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); 329 mDefaultRequest = defaultRequest; 330 331 addState(mDefaultState); 332 addState(mMaybeNotifyState, mDefaultState); 333 addState(mEvaluatingState, mMaybeNotifyState); 334 addState(mCaptivePortalState, mMaybeNotifyState); 335 addState(mEvaluatingPrivateDnsState, mDefaultState); 336 addState(mValidatedState, mDefaultState); 337 setInitialState(mDefaultState); 338 339 mIsCaptivePortalCheckEnabled = getIsCaptivePortalCheckEnabled(); 340 mUseHttps = getUseHttpsValidation(); 341 mCaptivePortalUserAgent = getCaptivePortalUserAgent(); 342 mCaptivePortalHttpsUrl = makeURL(getCaptivePortalServerHttpsUrl()); 343 mCaptivePortalHttpUrl = makeURL(getCaptivePortalServerHttpUrl(settings, context)); 344 mCaptivePortalFallbackUrls = makeCaptivePortalFallbackUrls(); 345 mCaptivePortalFallbackSpecs = makeCaptivePortalFallbackProbeSpecs(); 346 347 start(); 348 } 349 forceReevaluation(int responsibleUid)350 public void forceReevaluation(int responsibleUid) { 351 sendMessage(CMD_FORCE_REEVALUATION, responsibleUid, 0); 352 } 353 notifyPrivateDnsSettingsChanged(PrivateDnsConfig newCfg)354 public void notifyPrivateDnsSettingsChanged(PrivateDnsConfig newCfg) { 355 // Cancel any outstanding resolutions. 356 removeMessages(CMD_PRIVATE_DNS_SETTINGS_CHANGED); 357 // Send the update to the proper thread. 358 sendMessage(CMD_PRIVATE_DNS_SETTINGS_CHANGED, newCfg); 359 } 360 361 @Override log(String s)362 protected void log(String s) { 363 if (DBG) Log.d(TAG + "/" + mNetworkAgentInfo.name(), s); 364 } 365 validationLog(int probeType, Object url, String msg)366 private void validationLog(int probeType, Object url, String msg) { 367 String probeName = ValidationProbeEvent.getProbeName(probeType); 368 validationLog(String.format("%s %s %s", probeName, url, msg)); 369 } 370 validationLog(String s)371 private void validationLog(String s) { 372 if (DBG) log(s); 373 validationLogs.log(s); 374 } 375 getValidationLogs()376 public ReadOnlyLocalLog getValidationLogs() { 377 return validationLogs.readOnlyLocalLog(); 378 } 379 validationStage()380 private ValidationStage validationStage() { 381 return 0 == mValidations ? ValidationStage.FIRST_VALIDATION : ValidationStage.REVALIDATION; 382 } 383 isValidationRequired()384 private boolean isValidationRequired() { 385 return isValidationRequired( 386 mDefaultRequest.networkCapabilities, mNetworkAgentInfo.networkCapabilities); 387 } 388 389 notifyNetworkTestResultInvalid(Object obj)390 private void notifyNetworkTestResultInvalid(Object obj) { 391 mConnectivityServiceHandler.sendMessage(obtainMessage( 392 EVENT_NETWORK_TESTED, NETWORK_TEST_RESULT_INVALID, mNetId, obj)); 393 } 394 395 // DefaultState is the parent of all States. It exists only to handle CMD_* messages but 396 // does not entail any real state (hence no enter() or exit() routines). 397 private class DefaultState extends State { 398 @Override processMessage(Message message)399 public boolean processMessage(Message message) { 400 switch (message.what) { 401 case CMD_NETWORK_CONNECTED: 402 logNetworkEvent(NetworkEvent.NETWORK_CONNECTED); 403 transitionTo(mEvaluatingState); 404 return HANDLED; 405 case CMD_NETWORK_DISCONNECTED: 406 logNetworkEvent(NetworkEvent.NETWORK_DISCONNECTED); 407 if (mLaunchCaptivePortalAppBroadcastReceiver != null) { 408 mContext.unregisterReceiver(mLaunchCaptivePortalAppBroadcastReceiver); 409 mLaunchCaptivePortalAppBroadcastReceiver = null; 410 } 411 quit(); 412 return HANDLED; 413 case CMD_FORCE_REEVALUATION: 414 case CMD_CAPTIVE_PORTAL_RECHECK: 415 log("Forcing reevaluation for UID " + message.arg1); 416 mUidResponsibleForReeval = message.arg1; 417 transitionTo(mEvaluatingState); 418 return HANDLED; 419 case CMD_CAPTIVE_PORTAL_APP_FINISHED: 420 log("CaptivePortal App responded with " + message.arg1); 421 422 // If the user has seen and acted on a captive portal notification, and the 423 // captive portal app is now closed, disable HTTPS probes. This avoids the 424 // following pathological situation: 425 // 426 // 1. HTTP probe returns a captive portal, HTTPS probe fails or times out. 427 // 2. User opens the app and logs into the captive portal. 428 // 3. HTTP starts working, but HTTPS still doesn't work for some other reason - 429 // perhaps due to the network blocking HTTPS? 430 // 431 // In this case, we'll fail to validate the network even after the app is 432 // dismissed. There is now no way to use this network, because the app is now 433 // gone, so the user cannot select "Use this network as is". 434 mUseHttps = false; 435 436 switch (message.arg1) { 437 case APP_RETURN_DISMISSED: 438 sendMessage(CMD_FORCE_REEVALUATION, NO_UID, 0); 439 break; 440 case APP_RETURN_WANTED_AS_IS: 441 mDontDisplaySigninNotification = true; 442 // TODO: Distinguish this from a network that actually validates. 443 // Displaying the "x" on the system UI icon may still be a good idea. 444 transitionTo(mEvaluatingPrivateDnsState); 445 break; 446 case APP_RETURN_UNWANTED: 447 mDontDisplaySigninNotification = true; 448 mUserDoesNotWant = true; 449 notifyNetworkTestResultInvalid(null); 450 // TODO: Should teardown network. 451 mUidResponsibleForReeval = 0; 452 transitionTo(mEvaluatingState); 453 break; 454 } 455 return HANDLED; 456 case CMD_PRIVATE_DNS_SETTINGS_CHANGED: { 457 final PrivateDnsConfig cfg = (PrivateDnsConfig) message.obj; 458 if (!isValidationRequired() || cfg == null || !cfg.inStrictMode()) { 459 // No DNS resolution required. 460 // 461 // We don't force any validation in opportunistic mode 462 // here. Opportunistic mode nameservers are validated 463 // separately within netd. 464 // 465 // Reset Private DNS settings state. 466 mPrivateDnsProviderHostname = ""; 467 break; 468 } 469 470 mPrivateDnsProviderHostname = cfg.hostname; 471 472 // DNS resolutions via Private DNS strict mode block for a 473 // few seconds (~4.2) checking for any IP addresses to 474 // arrive and validate. Initiating a (re)evaluation now 475 // should not significantly alter the validation outcome. 476 // 477 // No matter what: enqueue a validation request; one of 478 // three things can happen with this request: 479 // [1] ignored (EvaluatingState or CaptivePortalState) 480 // [2] transition to EvaluatingPrivateDnsState 481 // (DefaultState and ValidatedState) 482 // [3] handled (EvaluatingPrivateDnsState) 483 // 484 // The Private DNS configuration to be evaluated will: 485 // [1] be skipped (not in strict mode), or 486 // [2] validate (huzzah), or 487 // [3] encounter some problem (invalid hostname, 488 // no resolved IP addresses, IPs unreachable, 489 // port 853 unreachable, port 853 is not running a 490 // DNS-over-TLS server, et cetera). 491 sendMessage(CMD_EVALUATE_PRIVATE_DNS); 492 break; 493 } 494 default: 495 break; 496 } 497 return HANDLED; 498 } 499 } 500 501 // Being in the ValidatedState State indicates a Network is: 502 // - Successfully validated, or 503 // - Wanted "as is" by the user, or 504 // - Does not satisfy the default NetworkRequest and so validation has been skipped. 505 private class ValidatedState extends State { 506 @Override enter()507 public void enter() { 508 maybeLogEvaluationResult( 509 networkEventType(validationStage(), EvaluationResult.VALIDATED)); 510 mConnectivityServiceHandler.sendMessage(obtainMessage(EVENT_NETWORK_TESTED, 511 NETWORK_TEST_RESULT_VALID, mNetId, null)); 512 mValidations++; 513 } 514 515 @Override processMessage(Message message)516 public boolean processMessage(Message message) { 517 switch (message.what) { 518 case CMD_NETWORK_CONNECTED: 519 transitionTo(mValidatedState); 520 break; 521 case CMD_EVALUATE_PRIVATE_DNS: 522 transitionTo(mEvaluatingPrivateDnsState); 523 break; 524 default: 525 return NOT_HANDLED; 526 } 527 return HANDLED; 528 } 529 } 530 531 // Being in the MaybeNotifyState State indicates the user may have been notified that sign-in 532 // is required. This State takes care to clear the notification upon exit from the State. 533 private class MaybeNotifyState extends State { 534 @Override processMessage(Message message)535 public boolean processMessage(Message message) { 536 switch (message.what) { 537 case CMD_LAUNCH_CAPTIVE_PORTAL_APP: 538 final Intent intent = new Intent( 539 ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN); 540 // OneAddressPerFamilyNetwork is not parcelable across processes. 541 intent.putExtra(ConnectivityManager.EXTRA_NETWORK, new Network(mNetwork)); 542 intent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL, 543 new CaptivePortal(new ICaptivePortal.Stub() { 544 @Override 545 public void appResponse(int response) { 546 if (response == APP_RETURN_WANTED_AS_IS) { 547 mContext.enforceCallingPermission( 548 android.Manifest.permission.CONNECTIVITY_INTERNAL, 549 "CaptivePortal"); 550 } 551 sendMessage(CMD_CAPTIVE_PORTAL_APP_FINISHED, response); 552 } 553 })); 554 final CaptivePortalProbeResult probeRes = mLastPortalProbeResult; 555 intent.putExtra(EXTRA_CAPTIVE_PORTAL_URL, probeRes.detectUrl); 556 if (probeRes.probeSpec != null) { 557 final String encodedSpec = probeRes.probeSpec.getEncodedSpec(); 558 intent.putExtra(EXTRA_CAPTIVE_PORTAL_PROBE_SPEC, encodedSpec); 559 } 560 intent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_USER_AGENT, 561 mCaptivePortalUserAgent); 562 intent.setFlags( 563 Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK); 564 mContext.startActivityAsUser(intent, UserHandle.CURRENT); 565 return HANDLED; 566 default: 567 return NOT_HANDLED; 568 } 569 } 570 571 @Override exit()572 public void exit() { 573 Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 0, mNetId, null); 574 mConnectivityServiceHandler.sendMessage(message); 575 } 576 } 577 578 // Being in the EvaluatingState State indicates the Network is being evaluated for internet 579 // connectivity, or that the user has indicated that this network is unwanted. 580 private class EvaluatingState extends State { 581 private int mReevaluateDelayMs; 582 private int mAttempts; 583 584 @Override enter()585 public void enter() { 586 // If we have already started to track time spent in EvaluatingState 587 // don't reset the timer due simply to, say, commands or events that 588 // cause us to exit and re-enter EvaluatingState. 589 if (!mEvaluationTimer.isStarted()) { 590 mEvaluationTimer.start(); 591 } 592 sendMessage(CMD_REEVALUATE, ++mReevaluateToken, 0); 593 if (mUidResponsibleForReeval != INVALID_UID) { 594 TrafficStats.setThreadStatsUid(mUidResponsibleForReeval); 595 mUidResponsibleForReeval = INVALID_UID; 596 } 597 mReevaluateDelayMs = INITIAL_REEVALUATE_DELAY_MS; 598 mAttempts = 0; 599 } 600 601 @Override processMessage(Message message)602 public boolean processMessage(Message message) { 603 switch (message.what) { 604 case CMD_REEVALUATE: 605 if (message.arg1 != mReevaluateToken || mUserDoesNotWant) 606 return HANDLED; 607 // Don't bother validating networks that don't satisfy the default request. 608 // This includes: 609 // - VPNs which can be considered explicitly desired by the user and the 610 // user's desire trumps whether the network validates. 611 // - Networks that don't provide Internet access. It's unclear how to 612 // validate such networks. 613 // - Untrusted networks. It's unsafe to prompt the user to sign-in to 614 // such networks and the user didn't express interest in connecting to 615 // such networks (an app did) so the user may be unhappily surprised when 616 // asked to sign-in to a network they didn't want to connect to in the 617 // first place. Validation could be done to adjust the network scores 618 // however these networks are app-requested and may not be intended for 619 // general usage, in which case general validation may not be an accurate 620 // measure of the network's quality. Only the app knows how to evaluate 621 // the network so don't bother validating here. Furthermore sending HTTP 622 // packets over the network may be undesirable, for example an extremely 623 // expensive metered network, or unwanted leaking of the User Agent string. 624 if (!isValidationRequired()) { 625 validationLog("Network would not satisfy default request, not validating"); 626 transitionTo(mValidatedState); 627 return HANDLED; 628 } 629 mAttempts++; 630 // Note: This call to isCaptivePortal() could take up to a minute. Resolving the 631 // server's IP addresses could hit the DNS timeout, and attempting connections 632 // to each of the server's several IP addresses (currently one IPv4 and one 633 // IPv6) could each take SOCKET_TIMEOUT_MS. During this time this StateMachine 634 // will be unresponsive. isCaptivePortal() could be executed on another Thread 635 // if this is found to cause problems. 636 CaptivePortalProbeResult probeResult = isCaptivePortal(); 637 if (probeResult.isSuccessful()) { 638 // Transit EvaluatingPrivateDnsState to get to Validated 639 // state (even if no Private DNS validation required). 640 transitionTo(mEvaluatingPrivateDnsState); 641 } else if (probeResult.isPortal()) { 642 notifyNetworkTestResultInvalid(probeResult.redirectUrl); 643 mLastPortalProbeResult = probeResult; 644 transitionTo(mCaptivePortalState); 645 } else { 646 final Message msg = obtainMessage(CMD_REEVALUATE, ++mReevaluateToken, 0); 647 sendMessageDelayed(msg, mReevaluateDelayMs); 648 logNetworkEvent(NetworkEvent.NETWORK_VALIDATION_FAILED); 649 notifyNetworkTestResultInvalid(probeResult.redirectUrl); 650 if (mAttempts >= BLAME_FOR_EVALUATION_ATTEMPTS) { 651 // Don't continue to blame UID forever. 652 TrafficStats.clearThreadStatsUid(); 653 } 654 mReevaluateDelayMs *= 2; 655 if (mReevaluateDelayMs > MAX_REEVALUATE_DELAY_MS) { 656 mReevaluateDelayMs = MAX_REEVALUATE_DELAY_MS; 657 } 658 } 659 return HANDLED; 660 case CMD_FORCE_REEVALUATION: 661 // Before IGNORE_REEVALUATE_ATTEMPTS attempts are made, 662 // ignore any re-evaluation requests. After, restart the 663 // evaluation process via EvaluatingState#enter. 664 return (mAttempts < IGNORE_REEVALUATE_ATTEMPTS) ? HANDLED : NOT_HANDLED; 665 default: 666 return NOT_HANDLED; 667 } 668 } 669 670 @Override exit()671 public void exit() { 672 TrafficStats.clearThreadStatsUid(); 673 } 674 } 675 676 // BroadcastReceiver that waits for a particular Intent and then posts a message. 677 private class CustomIntentReceiver extends BroadcastReceiver { 678 private final int mToken; 679 private final int mWhat; 680 private final String mAction; CustomIntentReceiver(String action, int token, int what)681 CustomIntentReceiver(String action, int token, int what) { 682 mToken = token; 683 mWhat = what; 684 mAction = action + "_" + mNetId + "_" + token; 685 mContext.registerReceiver(this, new IntentFilter(mAction)); 686 } getPendingIntent()687 public PendingIntent getPendingIntent() { 688 final Intent intent = new Intent(mAction); 689 intent.setPackage(mContext.getPackageName()); 690 return PendingIntent.getBroadcast(mContext, 0, intent, 0); 691 } 692 @Override onReceive(Context context, Intent intent)693 public void onReceive(Context context, Intent intent) { 694 if (intent.getAction().equals(mAction)) sendMessage(obtainMessage(mWhat, mToken)); 695 } 696 } 697 698 // Being in the CaptivePortalState State indicates a captive portal was detected and the user 699 // has been shown a notification to sign-in. 700 private class CaptivePortalState extends State { 701 private static final String ACTION_LAUNCH_CAPTIVE_PORTAL_APP = 702 "android.net.netmon.launchCaptivePortalApp"; 703 704 @Override enter()705 public void enter() { 706 maybeLogEvaluationResult( 707 networkEventType(validationStage(), EvaluationResult.CAPTIVE_PORTAL)); 708 // Don't annoy user with sign-in notifications. 709 if (mDontDisplaySigninNotification) return; 710 // Create a CustomIntentReceiver that sends us a 711 // CMD_LAUNCH_CAPTIVE_PORTAL_APP message when the user 712 // touches the notification. 713 if (mLaunchCaptivePortalAppBroadcastReceiver == null) { 714 // Wait for result. 715 mLaunchCaptivePortalAppBroadcastReceiver = new CustomIntentReceiver( 716 ACTION_LAUNCH_CAPTIVE_PORTAL_APP, new Random().nextInt(), 717 CMD_LAUNCH_CAPTIVE_PORTAL_APP); 718 } 719 // Display the sign in notification. 720 Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 1, mNetId, 721 mLaunchCaptivePortalAppBroadcastReceiver.getPendingIntent()); 722 mConnectivityServiceHandler.sendMessage(message); 723 // Retest for captive portal occasionally. 724 sendMessageDelayed(CMD_CAPTIVE_PORTAL_RECHECK, 0 /* no UID */, 725 CAPTIVE_PORTAL_REEVALUATE_DELAY_MS); 726 mValidations++; 727 } 728 729 @Override exit()730 public void exit() { 731 removeMessages(CMD_CAPTIVE_PORTAL_RECHECK); 732 } 733 } 734 735 private class EvaluatingPrivateDnsState extends State { 736 private int mPrivateDnsReevalDelayMs; 737 private PrivateDnsConfig mPrivateDnsConfig; 738 739 @Override enter()740 public void enter() { 741 mPrivateDnsReevalDelayMs = INITIAL_REEVALUATE_DELAY_MS; 742 mPrivateDnsConfig = null; 743 sendMessage(CMD_EVALUATE_PRIVATE_DNS); 744 } 745 746 @Override processMessage(Message msg)747 public boolean processMessage(Message msg) { 748 switch (msg.what) { 749 case CMD_EVALUATE_PRIVATE_DNS: 750 if (inStrictMode()) { 751 if (!isStrictModeHostnameResolved()) { 752 resolveStrictModeHostname(); 753 754 if (isStrictModeHostnameResolved()) { 755 notifyPrivateDnsConfigResolved(); 756 } else { 757 handlePrivateDnsEvaluationFailure(); 758 break; 759 } 760 } 761 762 // Look up a one-time hostname, to bypass caching. 763 // 764 // Note that this will race with ConnectivityService 765 // code programming the DNS-over-TLS server IP addresses 766 // into netd (if invoked, above). If netd doesn't know 767 // the IP addresses yet, or if the connections to the IP 768 // addresses haven't yet been validated, netd will block 769 // for up to a few seconds before failing the lookup. 770 if (!sendPrivateDnsProbe()) { 771 handlePrivateDnsEvaluationFailure(); 772 break; 773 } 774 } 775 776 // All good! 777 transitionTo(mValidatedState); 778 break; 779 default: 780 return NOT_HANDLED; 781 } 782 return HANDLED; 783 } 784 inStrictMode()785 private boolean inStrictMode() { 786 return !TextUtils.isEmpty(mPrivateDnsProviderHostname); 787 } 788 isStrictModeHostnameResolved()789 private boolean isStrictModeHostnameResolved() { 790 return (mPrivateDnsConfig != null) && 791 mPrivateDnsConfig.hostname.equals(mPrivateDnsProviderHostname) && 792 (mPrivateDnsConfig.ips.length > 0); 793 } 794 resolveStrictModeHostname()795 private void resolveStrictModeHostname() { 796 try { 797 // Do a blocking DNS resolution using the network-assigned nameservers. 798 // Do not set AI_ADDRCONFIG in ai_flags so we get all address families in advance. 799 final InetAddress[] ips = ResolvUtil.blockingResolveAllLocally( 800 mNetwork, mPrivateDnsProviderHostname, 0 /* aiFlags */); 801 mPrivateDnsConfig = new PrivateDnsConfig(mPrivateDnsProviderHostname, ips); 802 } catch (UnknownHostException uhe) { 803 mPrivateDnsConfig = null; 804 } 805 } 806 notifyPrivateDnsConfigResolved()807 private void notifyPrivateDnsConfigResolved() { 808 mConnectivityServiceHandler.sendMessage(obtainMessage( 809 EVENT_PRIVATE_DNS_CONFIG_RESOLVED, 0, mNetId, mPrivateDnsConfig)); 810 } 811 handlePrivateDnsEvaluationFailure()812 private void handlePrivateDnsEvaluationFailure() { 813 notifyNetworkTestResultInvalid(null); 814 815 // Queue up a re-evaluation with backoff. 816 // 817 // TODO: Consider abandoning this state after a few attempts and 818 // transitioning back to EvaluatingState, to perhaps give ourselves 819 // the opportunity to (re)detect a captive portal or something. 820 sendMessageDelayed(CMD_EVALUATE_PRIVATE_DNS, mPrivateDnsReevalDelayMs); 821 mPrivateDnsReevalDelayMs *= 2; 822 if (mPrivateDnsReevalDelayMs > MAX_REEVALUATE_DELAY_MS) { 823 mPrivateDnsReevalDelayMs = MAX_REEVALUATE_DELAY_MS; 824 } 825 } 826 sendPrivateDnsProbe()827 private boolean sendPrivateDnsProbe() { 828 // q.v. system/netd/server/dns/DnsTlsTransport.cpp 829 final String ONE_TIME_HOSTNAME_SUFFIX = "-dnsotls-ds.metric.gstatic.com"; 830 final String host = UUID.randomUUID().toString().substring(0, 8) + 831 ONE_TIME_HOSTNAME_SUFFIX; 832 try { 833 final InetAddress[] ips = mNetworkAgentInfo.network().getAllByName(host); 834 return (ips != null && ips.length > 0); 835 } catch (UnknownHostException uhe) {} 836 return false; 837 } 838 } 839 840 // Limits the list of IP addresses returned by getAllByName or tried by openConnection to at 841 // most one per address family. This ensures we only wait up to 20 seconds for TCP connections 842 // to complete, regardless of how many IP addresses a host has. 843 private static class OneAddressPerFamilyNetwork extends Network { OneAddressPerFamilyNetwork(Network network)844 public OneAddressPerFamilyNetwork(Network network) { 845 super(network); 846 } 847 848 @Override getAllByName(String host)849 public InetAddress[] getAllByName(String host) throws UnknownHostException { 850 // Always bypass Private DNS. 851 final List<InetAddress> addrs = Arrays.asList( 852 ResolvUtil.blockingResolveAllLocally(this, host)); 853 854 // Ensure the address family of the first address is tried first. 855 LinkedHashMap<Class, InetAddress> addressByFamily = new LinkedHashMap<>(); 856 addressByFamily.put(addrs.get(0).getClass(), addrs.get(0)); 857 Collections.shuffle(addrs); 858 859 for (InetAddress addr : addrs) { 860 addressByFamily.put(addr.getClass(), addr); 861 } 862 863 return addressByFamily.values().toArray(new InetAddress[addressByFamily.size()]); 864 } 865 } 866 getIsCaptivePortalCheckEnabled()867 public boolean getIsCaptivePortalCheckEnabled() { 868 String symbol = Settings.Global.CAPTIVE_PORTAL_MODE; 869 int defaultValue = Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT; 870 int mode = mSettings.getSetting(mContext, symbol, defaultValue); 871 return mode != Settings.Global.CAPTIVE_PORTAL_MODE_IGNORE; 872 } 873 getUseHttpsValidation()874 public boolean getUseHttpsValidation() { 875 return mSettings.getSetting(mContext, Settings.Global.CAPTIVE_PORTAL_USE_HTTPS, 1) == 1; 876 } 877 getWifiScansAlwaysAvailableDisabled()878 public boolean getWifiScansAlwaysAvailableDisabled() { 879 return mSettings.getSetting(mContext, Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 0; 880 } 881 getCaptivePortalServerHttpsUrl()882 private String getCaptivePortalServerHttpsUrl() { 883 return mSettings.getSetting(mContext, 884 Settings.Global.CAPTIVE_PORTAL_HTTPS_URL, DEFAULT_HTTPS_URL); 885 } 886 887 // Static for direct access by ConnectivityService getCaptivePortalServerHttpUrl(Context context)888 public static String getCaptivePortalServerHttpUrl(Context context) { 889 return getCaptivePortalServerHttpUrl(NetworkMonitorSettings.DEFAULT, context); 890 } 891 getCaptivePortalServerHttpUrl( NetworkMonitorSettings settings, Context context)892 public static String getCaptivePortalServerHttpUrl( 893 NetworkMonitorSettings settings, Context context) { 894 return settings.getSetting( 895 context, Settings.Global.CAPTIVE_PORTAL_HTTP_URL, DEFAULT_HTTP_URL); 896 } 897 makeCaptivePortalFallbackUrls()898 private URL[] makeCaptivePortalFallbackUrls() { 899 try { 900 String separator = ","; 901 String firstUrl = mSettings.getSetting(mContext, 902 Settings.Global.CAPTIVE_PORTAL_FALLBACK_URL, DEFAULT_FALLBACK_URL); 903 String joinedUrls = firstUrl + separator + mSettings.getSetting(mContext, 904 Settings.Global.CAPTIVE_PORTAL_OTHER_FALLBACK_URLS, 905 DEFAULT_OTHER_FALLBACK_URLS); 906 List<URL> urls = new ArrayList<>(); 907 for (String s : joinedUrls.split(separator)) { 908 URL u = makeURL(s); 909 if (u == null) { 910 continue; 911 } 912 urls.add(u); 913 } 914 if (urls.isEmpty()) { 915 Log.e(TAG, String.format("could not create any url from %s", joinedUrls)); 916 } 917 return urls.toArray(new URL[urls.size()]); 918 } catch (Exception e) { 919 // Don't let a misconfiguration bootloop the system. 920 Log.e(TAG, "Error parsing configured fallback URLs", e); 921 return new URL[0]; 922 } 923 } 924 makeCaptivePortalFallbackProbeSpecs()925 private CaptivePortalProbeSpec[] makeCaptivePortalFallbackProbeSpecs() { 926 try { 927 final String settingsValue = mSettings.getSetting( 928 mContext, Settings.Global.CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS, null); 929 // Probe specs only used if configured in settings 930 if (TextUtils.isEmpty(settingsValue)) { 931 return null; 932 } 933 934 return CaptivePortalProbeSpec.parseCaptivePortalProbeSpecs(settingsValue); 935 } catch (Exception e) { 936 // Don't let a misconfiguration bootloop the system. 937 Log.e(TAG, "Error parsing configured fallback probe specs", e); 938 return null; 939 } 940 } 941 getCaptivePortalUserAgent()942 private String getCaptivePortalUserAgent() { 943 return mSettings.getSetting(mContext, 944 Settings.Global.CAPTIVE_PORTAL_USER_AGENT, DEFAULT_USER_AGENT); 945 } 946 nextFallbackUrl()947 private URL nextFallbackUrl() { 948 if (mCaptivePortalFallbackUrls.length == 0) { 949 return null; 950 } 951 int idx = Math.abs(mNextFallbackUrlIndex) % mCaptivePortalFallbackUrls.length; 952 mNextFallbackUrlIndex += new Random().nextInt(); // randomely change url without memory. 953 return mCaptivePortalFallbackUrls[idx]; 954 } 955 nextFallbackSpec()956 private CaptivePortalProbeSpec nextFallbackSpec() { 957 if (ArrayUtils.isEmpty(mCaptivePortalFallbackSpecs)) { 958 return null; 959 } 960 // Randomly change spec without memory. Also randomize the first attempt. 961 final int idx = Math.abs(new Random().nextInt()) % mCaptivePortalFallbackSpecs.length; 962 return mCaptivePortalFallbackSpecs[idx]; 963 } 964 965 @VisibleForTesting isCaptivePortal()966 protected CaptivePortalProbeResult isCaptivePortal() { 967 if (!mIsCaptivePortalCheckEnabled) { 968 validationLog("Validation disabled."); 969 return CaptivePortalProbeResult.SUCCESS; 970 } 971 972 URL pacUrl = null; 973 URL httpsUrl = mCaptivePortalHttpsUrl; 974 URL httpUrl = mCaptivePortalHttpUrl; 975 976 // On networks with a PAC instead of fetching a URL that should result in a 204 977 // response, we instead simply fetch the PAC script. This is done for a few reasons: 978 // 1. At present our PAC code does not yet handle multiple PACs on multiple networks 979 // until something like https://android-review.googlesource.com/#/c/115180/ lands. 980 // Network.openConnection() will ignore network-specific PACs and instead fetch 981 // using NO_PROXY. If a PAC is in place, the only fetch we know will succeed with 982 // NO_PROXY is the fetch of the PAC itself. 983 // 2. To proxy the generate_204 fetch through a PAC would require a number of things 984 // happen before the fetch can commence, namely: 985 // a) the PAC script be fetched 986 // b) a PAC script resolver service be fired up and resolve the captive portal 987 // server. 988 // Network validation could be delayed until these prerequisities are satisifed or 989 // could simply be left to race them. Neither is an optimal solution. 990 // 3. PAC scripts are sometimes used to block or restrict Internet access and may in 991 // fact block fetching of the generate_204 URL which would lead to false negative 992 // results for network validation. 993 final ProxyInfo proxyInfo = mNetworkAgentInfo.linkProperties.getHttpProxy(); 994 if (proxyInfo != null && !Uri.EMPTY.equals(proxyInfo.getPacFileUrl())) { 995 pacUrl = makeURL(proxyInfo.getPacFileUrl().toString()); 996 if (pacUrl == null) { 997 return CaptivePortalProbeResult.FAILED; 998 } 999 } 1000 1001 if ((pacUrl == null) && (httpUrl == null || httpsUrl == null)) { 1002 return CaptivePortalProbeResult.FAILED; 1003 } 1004 1005 long startTime = SystemClock.elapsedRealtime(); 1006 1007 final CaptivePortalProbeResult result; 1008 if (pacUrl != null) { 1009 result = sendDnsAndHttpProbes(null, pacUrl, ValidationProbeEvent.PROBE_PAC); 1010 } else if (mUseHttps) { 1011 result = sendParallelHttpProbes(proxyInfo, httpsUrl, httpUrl); 1012 } else { 1013 result = sendDnsAndHttpProbes(proxyInfo, httpUrl, ValidationProbeEvent.PROBE_HTTP); 1014 } 1015 1016 long endTime = SystemClock.elapsedRealtime(); 1017 1018 sendNetworkConditionsBroadcast(true /* response received */, 1019 result.isPortal() /* isCaptivePortal */, 1020 startTime, endTime); 1021 1022 return result; 1023 } 1024 1025 /** 1026 * Do a DNS resolution and URL fetch on a known web server to see if we get the data we expect. 1027 * @return a CaptivePortalProbeResult inferred from the HTTP response. 1028 */ sendDnsAndHttpProbes(ProxyInfo proxy, URL url, int probeType)1029 private CaptivePortalProbeResult sendDnsAndHttpProbes(ProxyInfo proxy, URL url, int probeType) { 1030 // Pre-resolve the captive portal server host so we can log it. 1031 // Only do this if HttpURLConnection is about to, to avoid any potentially 1032 // unnecessary resolution. 1033 final String host = (proxy != null) ? proxy.getHost() : url.getHost(); 1034 sendDnsProbe(host); 1035 return sendHttpProbe(url, probeType, null); 1036 } 1037 1038 /** Do a DNS resolution of the given server. */ sendDnsProbe(String host)1039 private void sendDnsProbe(String host) { 1040 if (TextUtils.isEmpty(host)) { 1041 return; 1042 } 1043 1044 final String name = ValidationProbeEvent.getProbeName(ValidationProbeEvent.PROBE_DNS); 1045 final Stopwatch watch = new Stopwatch().start(); 1046 int result; 1047 String connectInfo; 1048 try { 1049 InetAddress[] addresses = mNetwork.getAllByName(host); 1050 StringBuffer buffer = new StringBuffer(); 1051 for (InetAddress address : addresses) { 1052 buffer.append(',').append(address.getHostAddress()); 1053 } 1054 result = ValidationProbeEvent.DNS_SUCCESS; 1055 connectInfo = "OK " + buffer.substring(1); 1056 } catch (UnknownHostException e) { 1057 result = ValidationProbeEvent.DNS_FAILURE; 1058 connectInfo = "FAIL"; 1059 } 1060 final long latency = watch.stop(); 1061 validationLog(ValidationProbeEvent.PROBE_DNS, host, 1062 String.format("%dms %s", latency, connectInfo)); 1063 logValidationProbe(latency, ValidationProbeEvent.PROBE_DNS, result); 1064 } 1065 1066 /** 1067 * Do a URL fetch on a known web server to see if we get the data we expect. 1068 * @return a CaptivePortalProbeResult inferred from the HTTP response. 1069 */ 1070 @VisibleForTesting sendHttpProbe(URL url, int probeType, @Nullable CaptivePortalProbeSpec probeSpec)1071 protected CaptivePortalProbeResult sendHttpProbe(URL url, int probeType, 1072 @Nullable CaptivePortalProbeSpec probeSpec) { 1073 HttpURLConnection urlConnection = null; 1074 int httpResponseCode = CaptivePortalProbeResult.FAILED_CODE; 1075 String redirectUrl = null; 1076 final Stopwatch probeTimer = new Stopwatch().start(); 1077 final int oldTag = TrafficStats.getAndSetThreadStatsTag(TrafficStats.TAG_SYSTEM_PROBE); 1078 try { 1079 urlConnection = (HttpURLConnection) mNetwork.openConnection(url); 1080 urlConnection.setInstanceFollowRedirects(probeType == ValidationProbeEvent.PROBE_PAC); 1081 urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS); 1082 urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS); 1083 urlConnection.setUseCaches(false); 1084 if (mCaptivePortalUserAgent != null) { 1085 urlConnection.setRequestProperty("User-Agent", mCaptivePortalUserAgent); 1086 } 1087 // cannot read request header after connection 1088 String requestHeader = urlConnection.getRequestProperties().toString(); 1089 1090 // Time how long it takes to get a response to our request 1091 long requestTimestamp = SystemClock.elapsedRealtime(); 1092 1093 httpResponseCode = urlConnection.getResponseCode(); 1094 redirectUrl = urlConnection.getHeaderField("location"); 1095 1096 // Time how long it takes to get a response to our request 1097 long responseTimestamp = SystemClock.elapsedRealtime(); 1098 1099 validationLog(probeType, url, "time=" + (responseTimestamp - requestTimestamp) + "ms" + 1100 " ret=" + httpResponseCode + 1101 " request=" + requestHeader + 1102 " headers=" + urlConnection.getHeaderFields()); 1103 // NOTE: We may want to consider an "HTTP/1.0 204" response to be a captive 1104 // portal. The only example of this seen so far was a captive portal. For 1105 // the time being go with prior behavior of assuming it's not a captive 1106 // portal. If it is considered a captive portal, a different sign-in URL 1107 // is needed (i.e. can't browse a 204). This could be the result of an HTTP 1108 // proxy server. 1109 if (httpResponseCode == 200) { 1110 if (probeType == ValidationProbeEvent.PROBE_PAC) { 1111 validationLog( 1112 probeType, url, "PAC fetch 200 response interpreted as 204 response."); 1113 httpResponseCode = CaptivePortalProbeResult.SUCCESS_CODE; 1114 } else if (urlConnection.getContentLengthLong() == 0) { 1115 // Consider 200 response with "Content-length=0" to not be a captive portal. 1116 // There's no point in considering this a captive portal as the user cannot 1117 // sign-in to an empty page. Probably the result of a broken transparent proxy. 1118 // See http://b/9972012. 1119 validationLog(probeType, url, 1120 "200 response with Content-length=0 interpreted as 204 response."); 1121 httpResponseCode = CaptivePortalProbeResult.SUCCESS_CODE; 1122 } else if (urlConnection.getContentLengthLong() == -1) { 1123 // When no Content-length (default value == -1), attempt to read a byte from the 1124 // response. Do not use available() as it is unreliable. See http://b/33498325. 1125 if (urlConnection.getInputStream().read() == -1) { 1126 validationLog( 1127 probeType, url, "Empty 200 response interpreted as 204 response."); 1128 httpResponseCode = CaptivePortalProbeResult.SUCCESS_CODE; 1129 } 1130 } 1131 } 1132 } catch (IOException e) { 1133 validationLog(probeType, url, "Probe failed with exception " + e); 1134 if (httpResponseCode == CaptivePortalProbeResult.FAILED_CODE) { 1135 // TODO: Ping gateway and DNS server and log results. 1136 } 1137 } finally { 1138 if (urlConnection != null) { 1139 urlConnection.disconnect(); 1140 } 1141 TrafficStats.setThreadStatsTag(oldTag); 1142 } 1143 logValidationProbe(probeTimer.stop(), probeType, httpResponseCode); 1144 1145 if (probeSpec == null) { 1146 return new CaptivePortalProbeResult(httpResponseCode, redirectUrl, url.toString()); 1147 } else { 1148 return probeSpec.getResult(httpResponseCode, redirectUrl); 1149 } 1150 } 1151 sendParallelHttpProbes( ProxyInfo proxy, URL httpsUrl, URL httpUrl)1152 private CaptivePortalProbeResult sendParallelHttpProbes( 1153 ProxyInfo proxy, URL httpsUrl, URL httpUrl) { 1154 // Number of probes to wait for. If a probe completes with a conclusive answer 1155 // it shortcuts the latch immediately by forcing the count to 0. 1156 final CountDownLatch latch = new CountDownLatch(2); 1157 1158 final class ProbeThread extends Thread { 1159 private final boolean mIsHttps; 1160 private volatile CaptivePortalProbeResult mResult = CaptivePortalProbeResult.FAILED; 1161 1162 public ProbeThread(boolean isHttps) { 1163 mIsHttps = isHttps; 1164 } 1165 1166 public CaptivePortalProbeResult result() { 1167 return mResult; 1168 } 1169 1170 @Override 1171 public void run() { 1172 if (mIsHttps) { 1173 mResult = 1174 sendDnsAndHttpProbes(proxy, httpsUrl, ValidationProbeEvent.PROBE_HTTPS); 1175 } else { 1176 mResult = sendDnsAndHttpProbes(proxy, httpUrl, ValidationProbeEvent.PROBE_HTTP); 1177 } 1178 if ((mIsHttps && mResult.isSuccessful()) || (!mIsHttps && mResult.isPortal())) { 1179 // Stop waiting immediately if https succeeds or if http finds a portal. 1180 while (latch.getCount() > 0) { 1181 latch.countDown(); 1182 } 1183 } 1184 // Signal this probe has completed. 1185 latch.countDown(); 1186 } 1187 } 1188 1189 final ProbeThread httpsProbe = new ProbeThread(true); 1190 final ProbeThread httpProbe = new ProbeThread(false); 1191 1192 try { 1193 httpsProbe.start(); 1194 httpProbe.start(); 1195 latch.await(PROBE_TIMEOUT_MS, TimeUnit.MILLISECONDS); 1196 } catch (InterruptedException e) { 1197 validationLog("Error: probes wait interrupted!"); 1198 return CaptivePortalProbeResult.FAILED; 1199 } 1200 1201 final CaptivePortalProbeResult httpsResult = httpsProbe.result(); 1202 final CaptivePortalProbeResult httpResult = httpProbe.result(); 1203 1204 // Look for a conclusive probe result first. 1205 if (httpResult.isPortal()) { 1206 return httpResult; 1207 } 1208 // httpsResult.isPortal() is not expected, but check it nonetheless. 1209 if (httpsResult.isPortal() || httpsResult.isSuccessful()) { 1210 return httpsResult; 1211 } 1212 // If a fallback method exists, use it to retry portal detection. 1213 // If we have new-style probe specs, use those. Otherwise, use the fallback URLs. 1214 final CaptivePortalProbeSpec probeSpec = nextFallbackSpec(); 1215 final URL fallbackUrl = (probeSpec != null) ? probeSpec.getUrl() : nextFallbackUrl(); 1216 if (fallbackUrl != null) { 1217 CaptivePortalProbeResult result = sendHttpProbe(fallbackUrl, PROBE_FALLBACK, probeSpec); 1218 if (result.isPortal()) { 1219 return result; 1220 } 1221 } 1222 // Otherwise wait until http and https probes completes and use their results. 1223 try { 1224 httpProbe.join(); 1225 if (httpProbe.result().isPortal()) { 1226 return httpProbe.result(); 1227 } 1228 httpsProbe.join(); 1229 return httpsProbe.result(); 1230 } catch (InterruptedException e) { 1231 validationLog("Error: http or https probe wait interrupted!"); 1232 return CaptivePortalProbeResult.FAILED; 1233 } 1234 } 1235 makeURL(String url)1236 private URL makeURL(String url) { 1237 if (url != null) { 1238 try { 1239 return new URL(url); 1240 } catch (MalformedURLException e) { 1241 validationLog("Bad URL: " + url); 1242 } 1243 } 1244 return null; 1245 } 1246 1247 /** 1248 * @param responseReceived - whether or not we received a valid HTTP response to our request. 1249 * If false, isCaptivePortal and responseTimestampMs are ignored 1250 * TODO: This should be moved to the transports. The latency could be passed to the transports 1251 * along with the captive portal result. Currently the TYPE_MOBILE broadcasts appear unused so 1252 * perhaps this could just be added to the WiFi transport only. 1253 */ sendNetworkConditionsBroadcast(boolean responseReceived, boolean isCaptivePortal, long requestTimestampMs, long responseTimestampMs)1254 private void sendNetworkConditionsBroadcast(boolean responseReceived, boolean isCaptivePortal, 1255 long requestTimestampMs, long responseTimestampMs) { 1256 if (getWifiScansAlwaysAvailableDisabled()) { 1257 return; 1258 } 1259 1260 if (!systemReady) { 1261 return; 1262 } 1263 1264 Intent latencyBroadcast = 1265 new Intent(ConnectivityConstants.ACTION_NETWORK_CONDITIONS_MEASURED); 1266 switch (mNetworkAgentInfo.networkInfo.getType()) { 1267 case ConnectivityManager.TYPE_WIFI: 1268 WifiInfo currentWifiInfo = mWifiManager.getConnectionInfo(); 1269 if (currentWifiInfo != null) { 1270 // NOTE: getSSID()'s behavior changed in API 17; before that, SSIDs were not 1271 // surrounded by double quotation marks (thus violating the Javadoc), but this 1272 // was changed to match the Javadoc in API 17. Since clients may have started 1273 // sanitizing the output of this method since API 17 was released, we should 1274 // not change it here as it would become impossible to tell whether the SSID is 1275 // simply being surrounded by quotes due to the API, or whether those quotes 1276 // are actually part of the SSID. 1277 latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_SSID, 1278 currentWifiInfo.getSSID()); 1279 latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_BSSID, 1280 currentWifiInfo.getBSSID()); 1281 } else { 1282 if (VDBG) logw("network info is TYPE_WIFI but no ConnectionInfo found"); 1283 return; 1284 } 1285 break; 1286 case ConnectivityManager.TYPE_MOBILE: 1287 latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_NETWORK_TYPE, 1288 mTelephonyManager.getNetworkType()); 1289 List<CellInfo> info = mTelephonyManager.getAllCellInfo(); 1290 if (info == null) return; 1291 int numRegisteredCellInfo = 0; 1292 for (CellInfo cellInfo : info) { 1293 if (cellInfo.isRegistered()) { 1294 numRegisteredCellInfo++; 1295 if (numRegisteredCellInfo > 1) { 1296 if (VDBG) logw("more than one registered CellInfo." + 1297 " Can't tell which is active. Bailing."); 1298 return; 1299 } 1300 if (cellInfo instanceof CellInfoCdma) { 1301 CellIdentityCdma cellId = ((CellInfoCdma) cellInfo).getCellIdentity(); 1302 latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_CELL_ID, cellId); 1303 } else if (cellInfo instanceof CellInfoGsm) { 1304 CellIdentityGsm cellId = ((CellInfoGsm) cellInfo).getCellIdentity(); 1305 latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_CELL_ID, cellId); 1306 } else if (cellInfo instanceof CellInfoLte) { 1307 CellIdentityLte cellId = ((CellInfoLte) cellInfo).getCellIdentity(); 1308 latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_CELL_ID, cellId); 1309 } else if (cellInfo instanceof CellInfoWcdma) { 1310 CellIdentityWcdma cellId = ((CellInfoWcdma) cellInfo).getCellIdentity(); 1311 latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_CELL_ID, cellId); 1312 } else { 1313 if (VDBG) logw("Registered cellinfo is unrecognized"); 1314 return; 1315 } 1316 } 1317 } 1318 break; 1319 default: 1320 return; 1321 } 1322 latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_CONNECTIVITY_TYPE, 1323 mNetworkAgentInfo.networkInfo.getType()); 1324 latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_RESPONSE_RECEIVED, 1325 responseReceived); 1326 latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_REQUEST_TIMESTAMP_MS, 1327 requestTimestampMs); 1328 1329 if (responseReceived) { 1330 latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_IS_CAPTIVE_PORTAL, 1331 isCaptivePortal); 1332 latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_RESPONSE_TIMESTAMP_MS, 1333 responseTimestampMs); 1334 } 1335 mContext.sendBroadcastAsUser(latencyBroadcast, UserHandle.CURRENT, 1336 ConnectivityConstants.PERMISSION_ACCESS_NETWORK_CONDITIONS); 1337 } 1338 logNetworkEvent(int evtype)1339 private void logNetworkEvent(int evtype) { 1340 int[] transports = mNetworkAgentInfo.networkCapabilities.getTransportTypes(); 1341 mMetricsLog.log(mNetId, transports, new NetworkEvent(evtype)); 1342 } 1343 networkEventType(ValidationStage s, EvaluationResult r)1344 private int networkEventType(ValidationStage s, EvaluationResult r) { 1345 if (s.isFirstValidation) { 1346 if (r.isValidated) { 1347 return NetworkEvent.NETWORK_FIRST_VALIDATION_SUCCESS; 1348 } else { 1349 return NetworkEvent.NETWORK_FIRST_VALIDATION_PORTAL_FOUND; 1350 } 1351 } else { 1352 if (r.isValidated) { 1353 return NetworkEvent.NETWORK_REVALIDATION_SUCCESS; 1354 } else { 1355 return NetworkEvent.NETWORK_REVALIDATION_PORTAL_FOUND; 1356 } 1357 } 1358 } 1359 maybeLogEvaluationResult(int evtype)1360 private void maybeLogEvaluationResult(int evtype) { 1361 if (mEvaluationTimer.isRunning()) { 1362 int[] transports = mNetworkAgentInfo.networkCapabilities.getTransportTypes(); 1363 mMetricsLog.log(mNetId, transports, new NetworkEvent(evtype, mEvaluationTimer.stop())); 1364 mEvaluationTimer.reset(); 1365 } 1366 } 1367 logValidationProbe(long durationMs, int probeType, int probeResult)1368 private void logValidationProbe(long durationMs, int probeType, int probeResult) { 1369 int[] transports = mNetworkAgentInfo.networkCapabilities.getTransportTypes(); 1370 boolean isFirstValidation = validationStage().isFirstValidation; 1371 ValidationProbeEvent ev = new ValidationProbeEvent(); 1372 ev.probeType = ValidationProbeEvent.makeProbeType(probeType, isFirstValidation); 1373 ev.returnCode = probeResult; 1374 ev.durationMs = durationMs; 1375 mMetricsLog.log(mNetId, transports, ev); 1376 } 1377 1378 @VisibleForTesting 1379 public interface NetworkMonitorSettings { getSetting(Context context, String symbol, int defaultValue)1380 int getSetting(Context context, String symbol, int defaultValue); getSetting(Context context, String symbol, String defaultValue)1381 String getSetting(Context context, String symbol, String defaultValue); 1382 1383 static NetworkMonitorSettings DEFAULT = new DefaultNetworkMonitorSettings(); 1384 } 1385 1386 @VisibleForTesting 1387 public static class DefaultNetworkMonitorSettings implements NetworkMonitorSettings { getSetting(Context context, String symbol, int defaultValue)1388 public int getSetting(Context context, String symbol, int defaultValue) { 1389 return Settings.Global.getInt(context.getContentResolver(), symbol, defaultValue); 1390 } 1391 getSetting(Context context, String symbol, String defaultValue)1392 public String getSetting(Context context, String symbol, String defaultValue) { 1393 final String value = Settings.Global.getString(context.getContentResolver(), symbol); 1394 return value != null ? value : defaultValue; 1395 } 1396 } 1397 } 1398