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