From 254bd168a354e1835f8da1d2a9e5aab74b8d39f6 Mon Sep 17 00:00:00 2001 From: Chalard Jean Date: Thu, 25 Aug 2022 13:04:51 +0900 Subject: [PATCH] Convert booleans in NAI to timestamps These timestamps will be useful for debugging issues where debuggers need to know what happened to long-lived networks, where logcat doesn't span long enough. These timestamps are also likely to be useful in the future, in particular for metrics. Test: FrameworksNetTests Change-Id: I8f46ce92e2ab301fa32e18e714a43a4130719199 --- .../android/server/ConnectivityService.java | 125 +++++---- .../server/connectivity/LingerMonitor.java | 8 +- .../server/connectivity/Nat464Xlat.java | 2 +- .../server/connectivity/NetworkAgentInfo.java | 245 ++++++++++++++---- .../IpConnectivityMetricsTest.java | 11 +- .../connectivity/LingerMonitorTest.java | 28 +- 6 files changed, 294 insertions(+), 125 deletions(-) diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java index 359a3bde76..afb3ca23e9 100755 --- a/service/src/com/android/server/ConnectivityService.java +++ b/service/src/com/android/server/ConnectivityService.java @@ -1744,7 +1744,8 @@ public class ConnectivityService extends IConnectivityManager.Stub synchronized (mNetworkForNetId) { for (int i = 0; i < mNetworkForNetId.size(); i++) { final NetworkAgentInfo nai = mNetworkForNetId.valueAt(i); - if (nai.isVPN() && nai.everConnected && nai.networkCapabilities.appliesToUid(uid)) { + if (nai.isVPN() && nai.everConnected() + && nai.networkCapabilities.appliesToUid(uid)) { return nai; } } @@ -2478,7 +2479,7 @@ public class ConnectivityService extends IConnectivityManager.Stub final ArrayList result = new ArrayList<>(); for (Network network : getAllNetworks()) { final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network); - if (nai != null && nai.everConnected) { + if (nai != null && nai.everConnected()) { // TODO (b/73321673) : NetworkStateSnapshot contains a copy of the // NetworkCapabilities, which may contain UIDs of apps to which the // network applies. Should the UIDs be cleared so as not to leak or @@ -3531,7 +3532,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } // If the network has been destroyed, the only thing that it can do is disconnect. - if (nai.destroyed && !isDisconnectRequest(msg)) { + if (nai.isDestroyed() && !isDisconnectRequest(msg)) { return; } @@ -3560,7 +3561,7 @@ public class ConnectivityService extends IConnectivityManager.Stub break; } case NetworkAgent.EVENT_SET_EXPLICITLY_SELECTED: { - if (nai.everConnected) { + if (nai.everConnected()) { loge("ERROR: cannot call explicitlySelected on already-connected network"); // Note that if the NAI had been connected, this would affect the // score, and therefore would require re-mixing the score and performing @@ -3690,7 +3691,7 @@ public class ConnectivityService extends IConnectivityManager.Stub final int netId = msg.arg2; final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(netId); // If a network has already been destroyed, all NetworkMonitor updates are ignored. - if (nai != null && nai.destroyed) return true; + if (nai != null && nai.isDestroyed()) return true; switch (msg.what) { default: return false; @@ -3739,12 +3740,10 @@ public class ConnectivityService extends IConnectivityManager.Stub case EVENT_PROVISIONING_NOTIFICATION: { final boolean visible = toBool(msg.arg1); // If captive portal status has changed, update capabilities or disconnect. - if (nai != null && (visible != nai.lastCaptivePortalDetected)) { - nai.lastCaptivePortalDetected = visible; - nai.everCaptivePortalDetected |= visible; - if (nai.lastCaptivePortalDetected && - ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE_AVOID - == getCaptivePortalMode()) { + if (nai != null && (visible != nai.captivePortalDetected())) { + nai.setCaptivePortalDetected(visible); + if (visible && ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE_AVOID + == getCaptivePortalMode()) { if (DBG) log("Avoiding captive portal network: " + nai.toShortString()); nai.onPreventAutomaticReconnect(); teardownUnneededNetwork(nai); @@ -3796,11 +3795,10 @@ public class ConnectivityService extends IConnectivityManager.Stub return; } - final boolean wasValidated = nai.lastValidated; - final boolean wasPartial = nai.partialConnectivity; - nai.partialConnectivity = ((testResult & NETWORK_VALIDATION_RESULT_PARTIAL) != 0); - final boolean partialConnectivityChanged = - (wasPartial != nai.partialConnectivity); + final boolean wasValidated = nai.isValidated(); + final boolean wasPartial = nai.partialConnectivity(); + nai.setPartialConnectivity((testResult & NETWORK_VALIDATION_RESULT_PARTIAL) != 0); + final boolean partialConnectivityChanged = (wasPartial != nai.partialConnectivity()); if (DBG) { final String logMsg = !TextUtils.isEmpty(redirectUrl) @@ -3808,10 +3806,9 @@ public class ConnectivityService extends IConnectivityManager.Stub : ""; log(nai.toShortString() + " validation " + (valid ? "passed" : "failed") + logMsg); } - if (valid != nai.lastValidated) { + if (valid != nai.isValidated()) { final FullScore oldScore = nai.getScore(); - nai.lastValidated = valid; - nai.everValidated |= valid; + nai.setValidated(valid); updateCapabilities(oldScore, nai, nai.networkCapabilities); if (valid) { handleFreshlyValidatedNetwork(nai); @@ -3844,13 +3841,13 @@ public class ConnectivityService extends IConnectivityManager.Stub // EVENT_PROMPT_UNVALIDATED arrives, show the partial connectivity notification // immediately. Re-notify partial connectivity silently if no internet // notification already there. - if (!wasPartial && nai.partialConnectivity) { + if (!wasPartial && nai.partialConnectivity()) { // Remove delayed message if there is a pending message. mHandler.removeMessages(EVENT_PROMPT_UNVALIDATED, nai.network); handlePromptUnvalidated(nai.network); } - if (wasValidated && !nai.lastValidated) { + if (wasValidated && !nai.isValidated()) { handleNetworkUnvalidated(nai); } } @@ -4197,7 +4194,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } private static boolean shouldDestroyNativeNetwork(@NonNull NetworkAgentInfo nai) { - return nai.created && !nai.destroyed; + return nai.isCreated() && !nai.isDestroyed(); } private boolean shouldIgnoreValidationFailureAfterRoam(NetworkAgentInfo nai) { @@ -4207,8 +4204,8 @@ public class ConnectivityService extends IConnectivityManager.Stub R.integer.config_validationFailureAfterRoamIgnoreTimeMillis)); if (blockTimeOut <= MAX_VALIDATION_FAILURE_BLOCKING_TIME_MS && blockTimeOut >= 0) { - final long currentTimeMs = SystemClock.elapsedRealtime(); - long timeSinceLastRoam = currentTimeMs - nai.lastRoamTimestamp; + final long currentTimeMs = SystemClock.elapsedRealtime(); + long timeSinceLastRoam = currentTimeMs - nai.lastRoamTime; if (timeSinceLastRoam <= blockTimeOut) { log ("blocked because only " + timeSinceLastRoam + "ms after roam"); return true; @@ -4312,7 +4309,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } // Delayed teardown. - if (nai.created) { + if (nai.isCreated()) { try { mNetd.networkSetPermissionForNetwork(nai.network.netId, INetd.PERMISSION_SYSTEM); } catch (RemoteException e) { @@ -4333,7 +4330,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // for an unnecessarily long time. destroyNativeNetwork(nai); } - if (!nai.created && !SdkLevel.isAtLeastT()) { + if (!nai.isCreated() && !SdkLevel.isAtLeastT()) { // Backwards compatibility: send onNetworkDestroyed even if network was never created. // This can never run if the code above runs because shouldDestroyNativeNetwork is // false if the network was never created. @@ -4394,11 +4391,11 @@ public class ConnectivityService extends IConnectivityManager.Stub mDnsManager.removeNetwork(nai.network); // clean up tc police filters on interface. - if (nai.everConnected && canNetworkBeRateLimited(nai) && mIngressRateLimit >= 0) { + if (nai.everConnected() && canNetworkBeRateLimited(nai) && mIngressRateLimit >= 0) { mDeps.disableIngressRateLimit(nai.linkProperties.getInterfaceName()); } - nai.destroyed = true; + nai.setDestroyed(); nai.onNetworkDestroyed(); } @@ -4527,7 +4524,7 @@ public class ConnectivityService extends IConnectivityManager.Stub private boolean unneeded(NetworkAgentInfo nai, UnneededFor reason) { ensureRunningOnConnectivityServiceThread(); - if (!nai.everConnected || nai.isVPN() || nai.isInactive() + if (!nai.everConnected() || nai.isVPN() || nai.isInactive() || nai.getScore().getKeepConnectedReason() != NetworkScore.KEEP_CONNECTED_NONE) { return false; } @@ -4859,7 +4856,7 @@ public class ConnectivityService extends IConnectivityManager.Stub return; } - if (nai.everValidated) { + if (nai.everValidated()) { // The network validated while the dialog box was up. Take no action. return; } @@ -4904,7 +4901,7 @@ public class ConnectivityService extends IConnectivityManager.Stub return; } - if (nai.lastValidated) { + if (nai.isValidated()) { // The network validated while the dialog box was up. Take no action. return; } @@ -4936,12 +4933,12 @@ public class ConnectivityService extends IConnectivityManager.Stub private void handleSetAvoidUnvalidated(Network network) { NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network); - if (nai == null || nai.lastValidated) { + if (nai == null || nai.isValidated()) { // Nothing to do. The network either disconnected or revalidated. return; } - if (!nai.avoidUnvalidated) { - nai.avoidUnvalidated = true; + if (0L == nai.getAvoidUnvalidated()) { + nai.setAvoidUnvalidated(); nai.updateScoreForNetworkAgentUpdate(); rematchAllNetworksAndRequests(); } @@ -5091,7 +5088,7 @@ public class ConnectivityService extends IConnectivityManager.Stub pw.println("Network overrides:"); pw.increaseIndent(); for (NetworkAgentInfo nai : networksSortedById()) { - if (nai.avoidUnvalidated) { + if (0L != nai.getAvoidUnvalidated()) { pw.println(nai.toShortString()); } } @@ -5162,7 +5159,7 @@ public class ConnectivityService extends IConnectivityManager.Stub private boolean shouldPromptUnvalidated(NetworkAgentInfo nai) { // Don't prompt if the network is validated, and don't prompt on captive portals // because we're already prompting the user to sign in. - if (nai.everValidated || nai.everCaptivePortalDetected) { + if (nai.everValidated() || nai.everCaptivePortalDetected()) { return false; } @@ -5170,8 +5167,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // partial connectivity and selected don't ask again. This ensures that if the device // automatically connects to a network that has partial Internet access, the user will // always be able to use it, either because they've already chosen "don't ask again" or - // because we have prompt them. - if (nai.partialConnectivity && !nai.networkAgentConfig.acceptPartialConnectivity) { + // because we have prompted them. + if (nai.partialConnectivity() && !nai.networkAgentConfig.acceptPartialConnectivity) { return true; } @@ -5203,7 +5200,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // TODO: Evaluate if it's needed to wait 8 seconds for triggering notification when // NetworkMonitor detects the network is partial connectivity. Need to change the design to // popup the notification immediately when the network is partial connectivity. - if (nai.partialConnectivity) { + if (nai.partialConnectivity()) { showNetworkNotification(nai, NotificationType.PARTIAL_CONNECTIVITY); } else { showNetworkNotification(nai, NotificationType.NO_INTERNET); @@ -5557,7 +5554,7 @@ public class ConnectivityService extends IConnectivityManager.Stub return; } // Revalidate if the app report does not match our current validated state. - if (hasConnectivity == nai.lastValidated) { + if (hasConnectivity == nai.isValidated()) { mConnectivityDiagnosticsHandler.sendMessage( mConnectivityDiagnosticsHandler.obtainMessage( ConnectivityDiagnosticsHandler.EVENT_NETWORK_CONNECTIVITY_REPORTED, @@ -5571,7 +5568,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } // Validating a network that has not yet connected could result in a call to // rematchNetworkAndRequests() which is not meant to work on such networks. - if (!nai.everConnected) { + if (!nai.everConnected()) { return; } final NetworkCapabilities nc = getNetworkCapabilitiesInternal(nai); @@ -7750,7 +7747,7 @@ public class ConnectivityService extends IConnectivityManager.Stub @NonNull final NetworkCapabilities newNc) { final int oldPermission = getNetworkPermission(nai.networkCapabilities); final int newPermission = getNetworkPermission(newNc); - if (oldPermission != newPermission && nai.created && !nai.isVPN()) { + if (oldPermission != newPermission && nai.isCreated() && !nai.isVPN()) { try { mNetd.networkSetPermissionForNetwork(nai.network.getNetId(), newPermission); } catch (RemoteException | ServiceSpecificException e) { @@ -7840,9 +7837,9 @@ public class ConnectivityService extends IConnectivityManager.Stub // causing a connect/teardown loop. // TODO: remove this altogether and make it the responsibility of the NetworkProviders to // avoid connect/teardown loops. - if (nai.everConnected && - !nai.isVPN() && - !nai.networkCapabilities.satisfiedByImmutableNetworkCapabilities(nc)) { + if (nai.everConnected() + && !nai.isVPN() + && !nai.networkCapabilities.satisfiedByImmutableNetworkCapabilities(nc)) { // TODO: consider not complaining when a network agent degrades its capabilities if this // does not cause any request (that is not a listen) currently matching that agent to // stop being matched by the updated agent. @@ -7854,12 +7851,12 @@ public class ConnectivityService extends IConnectivityManager.Stub // Don't modify caller's NetworkCapabilities. final NetworkCapabilities newNc = new NetworkCapabilities(nc); - if (nai.lastValidated) { + if (nai.isValidated()) { newNc.addCapability(NET_CAPABILITY_VALIDATED); } else { newNc.removeCapability(NET_CAPABILITY_VALIDATED); } - if (nai.lastCaptivePortalDetected) { + if (nai.captivePortalDetected()) { newNc.addCapability(NET_CAPABILITY_CAPTIVE_PORTAL); } else { newNc.removeCapability(NET_CAPABILITY_CAPTIVE_PORTAL); @@ -7869,7 +7866,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } else { newNc.addCapability(NET_CAPABILITY_FOREGROUND); } - if (nai.partialConnectivity) { + if (nai.partialConnectivity()) { newNc.addCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY); } else { newNc.removeCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY); @@ -8116,7 +8113,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // that happens to prevent false alarms. final Set prevUids = prevNc == null ? null : prevNc.getUidRanges(); final Set newUids = newNc == null ? null : newNc.getUidRanges(); - if (nai.isVPN() && nai.everConnected && !UidRange.hasSameUids(prevUids, newUids) + if (nai.isVPN() && nai.everConnected() && !UidRange.hasSameUids(prevUids, newUids) && (nai.linkProperties.getHttpProxy() != null || isProxySetOnAnyDefaultNetwork())) { mProxyTracker.sendProxyBroadcast(); } @@ -8236,8 +8233,8 @@ public class ConnectivityService extends IConnectivityManager.Stub } if (VDBG || DDBG) { log("Update of LinkProperties for " + nai.toShortString() - + "; created=" + nai.created - + "; everConnected=" + nai.everConnected); + + "; created=" + nai.getCreatedTime() + + "; firstConnected=" + nai.getConnectedTime()); } // TODO: eliminate this defensive copy after confirming that updateLinkProperties does not // modify its oldLp parameter. @@ -8669,7 +8666,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } previousSatisfier.removeRequest(previousRequest.requestId); if (canSupportGracefulNetworkSwitch(previousSatisfier, newSatisfier) - && !previousSatisfier.destroyed) { + && !previousSatisfier.isDestroyed()) { // If this network switch can't be supported gracefully, the request is not // lingered. This allows letting go of the network sooner to reclaim some // performance on the new network, since the radio can't do both at the same @@ -8937,7 +8934,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // The new default network can be newly null if and only if the old default // network doesn't satisfy the default request any more because it lost a // capability. - mDefaultInetConditionPublished = newDefaultNetwork.lastValidated ? 100 : 0; + mDefaultInetConditionPublished = newDefaultNetwork.isValidated() ? 100 : 0; mLegacyTypeTracker.add( newDefaultNetwork.networkInfo.getType(), newDefaultNetwork); } @@ -8958,7 +8955,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // they may get old info. Reverse this after the old startUsing api is removed. // This is on top of the multiple intent sequencing referenced in the todo above. for (NetworkAgentInfo nai : nais) { - if (nai.everConnected) { + if (nai.everConnected()) { addNetworkToLegacyTypeTracker(nai); } } @@ -9084,12 +9081,12 @@ public class ConnectivityService extends IConnectivityManager.Stub private void updateInetCondition(NetworkAgentInfo nai) { // Don't bother updating until we've graduated to validated at least once. - if (!nai.everValidated) return; + if (!nai.everValidated()) return; // For now only update icons for the default connection. // TODO: Update WiFi and cellular icons separately. b/17237507 if (!isDefaultNetwork(nai)) return; - int newInetCondition = nai.lastValidated ? 100 : 0; + int newInetCondition = nai.isValidated() ? 100 : 0; // Don't repeat publish. if (newInetCondition == mDefaultInetConditionPublished) return; @@ -9116,7 +9113,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // SUSPENDED state is currently only overridden from CONNECTED state. In the case the // network agent is created, then goes to suspended, then goes out of suspended without // ever setting connected. Check if network agent is ever connected to update the state. - newInfo.setDetailedState(nai.everConnected + newInfo.setDetailedState(nai.everConnected() ? NetworkInfo.DetailedState.CONNECTED : NetworkInfo.DetailedState.CONNECTING, info.getReason(), @@ -9141,7 +9138,7 @@ public class ConnectivityService extends IConnectivityManager.Stub + oldInfo.getState() + " to " + state); } - if (!networkAgent.created + if (!networkAgent.isCreated() && (state == NetworkInfo.State.CONNECTED || (state == NetworkInfo.State.CONNECTING && networkAgent.isVPN()))) { @@ -9155,13 +9152,13 @@ public class ConnectivityService extends IConnectivityManager.Stub // anything happens to the network. updateCapabilitiesForNetwork(networkAgent); } - networkAgent.created = true; + networkAgent.setCreated(); networkAgent.onNetworkCreated(); updateAllowedUids(networkAgent, null, networkAgent.networkCapabilities); } - if (!networkAgent.everConnected && state == NetworkInfo.State.CONNECTED) { - networkAgent.everConnected = true; + if (!networkAgent.everConnected() && state == NetworkInfo.State.CONNECTED) { + networkAgent.setConnected(); // NetworkCapabilities need to be set before sending the private DNS config to // NetworkMonitor, otherwise NetworkMonitor cannot determine if validation is required. @@ -9245,8 +9242,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // TODO(b/122649188): send the broadcast only to VPN users. mProxyTracker.sendProxyBroadcast(); } - } else if (networkAgent.created && (oldInfo.getState() == NetworkInfo.State.SUSPENDED || - state == NetworkInfo.State.SUSPENDED)) { + } else if (networkAgent.isCreated() && (oldInfo.getState() == NetworkInfo.State.SUSPENDED + || state == NetworkInfo.State.SUSPENDED)) { mLegacyTypeTracker.update(networkAgent); } } @@ -9674,7 +9671,7 @@ public class ConnectivityService extends IConnectivityManager.Stub return; } if (!TextUtils.equals(((WifiInfo)prevInfo).getBSSID(), ((WifiInfo)newInfo).getBSSID())) { - nai.lastRoamTimestamp = SystemClock.elapsedRealtime(); + nai.lastRoamTime = SystemClock.elapsedRealtime(); } } diff --git a/service/src/com/android/server/connectivity/LingerMonitor.java b/service/src/com/android/server/connectivity/LingerMonitor.java index 032612c6f0..df34ce7b32 100644 --- a/service/src/com/android/server/connectivity/LingerMonitor.java +++ b/service/src/com/android/server/connectivity/LingerMonitor.java @@ -229,8 +229,8 @@ public class LingerMonitor { @Nullable final NetworkAgentInfo toNai) { if (VDBG) { Log.d(TAG, "noteLingerDefaultNetwork from=" + fromNai.toShortString() - + " everValidated=" + fromNai.everValidated - + " lastValidated=" + fromNai.lastValidated + + " firstValidated=" + fromNai.getFirstValidationTime() + + " lastValidated=" + fromNai.getCurrentValidationTime() + " to=" + toNai.toShortString()); } @@ -253,7 +253,7 @@ public class LingerMonitor { // 1. User connects to wireless printer. // 2. User turns on cellular data. // 3. We show a notification. - if (!fromNai.everValidated) return; + if (!fromNai.everValidated()) return; // If this network is a captive portal, don't notify. This cannot happen on initial connect // to a captive portal, because the everValidated check above will fail. However, it can @@ -286,7 +286,7 @@ public class LingerMonitor { // because its score changed. // TODO: instead of just skipping notification, keep a note of it, and show it if it becomes // unvalidated. - if (fromNai.lastValidated) return; + if (fromNai.isValidated()) return; if (!isNotificationEnabled(fromNai, toNai)) return; diff --git a/service/src/com/android/server/connectivity/Nat464Xlat.java b/service/src/com/android/server/connectivity/Nat464Xlat.java index e4ad391724..a57e992499 100644 --- a/service/src/com/android/server/connectivity/Nat464Xlat.java +++ b/service/src/com/android/server/connectivity/Nat464Xlat.java @@ -144,7 +144,7 @@ public class Nat464Xlat { && nai.netAgentConfig().skip464xlat; return (supported || isTestNetwork) && connected && isIpv6OnlyNetwork && !skip464xlat - && !nai.destroyed && (nai.networkCapabilities.hasTransport(TRANSPORT_CELLULAR) + && !nai.isDestroyed() && (nai.networkCapabilities.hasTransport(TRANSPORT_CELLULAR) ? isCellular464XlatEnabled() : true); } diff --git a/service/src/com/android/server/connectivity/NetworkAgentInfo.java b/service/src/com/android/server/connectivity/NetworkAgentInfo.java index 88a5f9c44c..d4d7d960fa 100644 --- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java +++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java @@ -189,42 +189,193 @@ public class NetworkAgentInfo implements NetworkRanker.Scoreable { // field instead. private @Nullable NetworkCapabilities mDeclaredCapabilitiesUnsanitized; - // Indicates if netd has been told to create this Network. From this point on the appropriate - // routing rules are setup and routes are added so packets can begin flowing over the Network. - // This is a sticky bit; once set it is never cleared. - public boolean created; - // Set to true after the first time this network is marked as CONNECTED. Once set, the network - // shows up in API calls, is able to satisfy NetworkRequests and can become the default network. - // This is a sticky bit; once set it is never cleared. - public boolean everConnected; - // Whether this network has been destroyed and is being kept temporarily until it is replaced. - public boolean destroyed; - // To check how long it has been since last roam. - public long lastRoamTimestamp; + // Timestamp (SystemClock.elapsedRealtime()) when netd has been told to create this Network, or + // 0 if it hasn't been done yet. + // From this point on, the appropriate routing rules are setup and routes are added so packets + // can begin flowing over the Network. + // This is a sticky value; once set != 0 it is never changed. + private long mCreatedTime; - // Set to true if this Network successfully passed validation or if it did not satisfy the - // default NetworkRequest in which case validation will not be attempted. - // This is a sticky bit; once set it is never cleared even if future validation attempts fail. - public boolean everValidated; + /** Notify this NAI that netd was just told to create this network */ + public void setCreated() { + if (0L != mCreatedTime) throw new IllegalStateException("Already created"); + mCreatedTime = SystemClock.elapsedRealtime(); + } - // The result of the last validation attempt on this network (true if validated, false if not). - public boolean lastValidated; + /** Returns whether netd was told to create this network */ + public boolean isCreated() { + return mCreatedTime != 0L; + } - // If true, becoming unvalidated will lower the network's score. This is only meaningful if the - // system is configured not to do this for certain networks, e.g., if the - // config_networkAvoidBadWifi option is set to 0 and the user has not overridden that via - // Settings.Global.NETWORK_AVOID_BAD_WIFI. - public boolean avoidUnvalidated; + // Get the time (SystemClock.elapsedRealTime) when this network was created (or 0 if never). + public long getCreatedTime() { + return mCreatedTime; + } - // Whether a captive portal was ever detected on this network. - // This is a sticky bit; once set it is never cleared. - public boolean everCaptivePortalDetected; + // Timestamp of the first time (SystemClock.elapsedRealtime()) this network is marked as + // connected, or 0 if this network has never been marked connected. Once set to non-zero, the + // network shows up in API calls, is able to satisfy NetworkRequests and can become the default + // network. + // This is a sticky value; once set != 0 it is never changed. + private long mConnectedTime; - // Whether a captive portal was found during the last network validation attempt. - public boolean lastCaptivePortalDetected; + /** Notify this NAI that this network just connected */ + public void setConnected() { + if (0L != mConnectedTime) throw new IllegalStateException("Already connected"); + mConnectedTime = SystemClock.elapsedRealtime(); + } - // Set to true when partial connectivity was detected. - public boolean partialConnectivity; + /** Return whether this network ever connected */ + public boolean everConnected() { + return mConnectedTime != 0L; + } + + // Get the time (SystemClock.elapsedRealTime()) when this network was first connected, or 0 if + // never. + public long getConnectedTime() { + return mConnectedTime; + } + + // When this network has been destroyed and is being kept temporarily until it is replaced, + // this is set to that timestamp (SystemClock.elapsedRealtime()). Zero otherwise. + private long mDestroyedTime; + + /** Notify this NAI that this network was destroyed */ + public void setDestroyed() { + if (0L != mDestroyedTime) throw new IllegalStateException("Already destroyed"); + mDestroyedTime = SystemClock.elapsedRealtime(); + } + + /** Return whether this network was destroyed */ + public boolean isDestroyed() { + return 0L != mDestroyedTime; + } + + // Timestamp of the last roaming (SystemClock.elapsedRealtime()) or 0 if never roamed. + public long lastRoamTime; + + // Timestamp (SystemClock.elapsedRealtime()) of the first time this network successfully + // passed validation or was deemed exempt of validation (see + // {@link NetworkMonitorUtils#isValidationRequired}). Zero if the network requires + // validation but never passed it successfully. + // This is a sticky value; once set it is never changed even if further validation attempts are + // made (whether they succeed or fail). + private long mFirstValidationTime; + + // Timestamp (SystemClock.elapsedRealtime()) at which the latest validation attempt succeeded, + // or 0 if the latest validation attempt failed. + private long mCurrentValidationTime; + + /** Notify this NAI that this network just finished a validation check */ + public void setValidated(final boolean validated) { + final long nowOrZero = validated ? SystemClock.elapsedRealtime() : 0L; + if (validated && 0L == mFirstValidationTime) { + mFirstValidationTime = nowOrZero; + } + mCurrentValidationTime = nowOrZero; + } + + /** + * Returns whether this network is currently validated. + * + * This is the result of the latest validation check. {@see #getCurrentValidationTime} for + * when that check was performed. + */ + public boolean isValidated() { + return 0L != mCurrentValidationTime; + } + + /** + * Returns whether this network ever passed the validation checks successfully. + * + * Note that the network may no longer be validated at this time ever if this is true. + * @see #isValidated + */ + public boolean everValidated() { + return 0L != mFirstValidationTime; + } + + // Get the time (SystemClock.elapsedRealTime()) when this network was most recently validated, + // or 0 if this network was found not to validate on the last attempt. + public long getCurrentValidationTime() { + return mCurrentValidationTime; + } + + // Get the time (SystemClock.elapsedRealTime()) when this network was validated for the first + // time (or 0 if never). + public long getFirstValidationTime() { + return mFirstValidationTime; + } + + // Timestamp (SystemClock.elapsedRealtime()) at which the user requested this network be + // avoided when unvalidated. Zero if this never happened for this network. + // This is only meaningful if the system is configured to have some cell networks yield + // to bad wifi, e.g., if the config_networkAvoidBadWifi option is set to 0 and the user has + // not overridden that via Settings.Global.NETWORK_AVOID_BAD_WIFI. + // + // Normally the system always prefers a validated network to a non-validated one, even if + // the non-validated one is cheaper. However, some cell networks may be configured by the + // setting above to yield to WiFi even if that WiFi network goes bad. When this configuration + // is active, specific networks can be marked to override this configuration so that the + // system will revert to preferring such a cell to this network when this network goes bad. This + // is achieved by calling {@link ConnectivityManager#setAvoidUnvalidated()}, and this field + // is set to non-zero when this happened to this network. + private long mAvoidUnvalidated; + + /** Set this network as being avoided when unvalidated. {@see mAvoidUnvalidated} */ + public void setAvoidUnvalidated() { + if (0L != mAvoidUnvalidated) throw new IllegalStateException("Already avoided unvalidated"); + mAvoidUnvalidated = SystemClock.elapsedRealtime(); + } + + // Get the time (SystemClock.elapsedRealTime()) when this network was set to being avoided + // when unvalidated, or 0 if this never happened. + public long getAvoidUnvalidated() { + return mAvoidUnvalidated; + } + + // Timestamp (SystemClock.elapsedRealtime()) at which a captive portal was first detected + // on this network, or zero if this never happened. + // This is a sticky value; once set != 0 it is never changed. + private long mFirstCaptivePortalDetectedTime; + + // Timestamp (SystemClock.elapsedRealtime()) at which the latest validation attempt found a + // captive portal, or zero if the latest attempt didn't find a captive portal. + private long mCurrentCaptivePortalDetectedTime; + + /** Notify this NAI that a captive portal has just been detected on this network */ + public void setCaptivePortalDetected(final boolean hasCaptivePortal) { + if (!hasCaptivePortal) { + mCurrentCaptivePortalDetectedTime = 0L; + return; + } + final long now = SystemClock.elapsedRealtime(); + if (0L == mFirstCaptivePortalDetectedTime) mFirstCaptivePortalDetectedTime = now; + mCurrentCaptivePortalDetectedTime = now; + } + + /** Return whether a captive portal has ever been detected on this network */ + public boolean everCaptivePortalDetected() { + return 0L != mFirstCaptivePortalDetectedTime; + } + + /** Return whether this network has been detected to be behind a captive portal at the moment */ + public boolean captivePortalDetected() { + return 0L != mCurrentCaptivePortalDetectedTime; + } + + // Timestamp (SystemClock.elapsedRealtime()) at which the latest validation attempt found + // partial connectivity, or zero if the latest attempt didn't find partial connectivity. + private long mPartialConnectivityTime; + + public void setPartialConnectivity(final boolean value) { + mPartialConnectivityTime = value ? SystemClock.elapsedRealtime() : 0L; + } + + /** Return whether this NAI has partial connectivity */ + public boolean partialConnectivity() { + return 0L != mPartialConnectivityTime; + } // Delay between when the network is disconnected and when the native network is destroyed. public int teardownDelayMs; @@ -820,7 +971,7 @@ public class NetworkAgentInfo implements NetworkRanker.Scoreable { final NetworkCapabilities oldNc = networkCapabilities; networkCapabilities = nc; mScore = mScore.mixInScore(networkCapabilities, networkAgentConfig, everValidatedForYield(), - yieldToBadWiFi(), destroyed); + yieldToBadWiFi(), isDestroyed()); final NetworkMonitorManager nm = mNetworkMonitor; if (nm != null) { nm.notifyNetworkCapabilitiesChanged(nc); @@ -983,13 +1134,14 @@ public class NetworkAgentInfo implements NetworkRanker.Scoreable { // Does this network satisfy request? public boolean satisfies(NetworkRequest request) { - return everConnected + return everConnected() && request.networkCapabilities.satisfiedByNetworkCapabilities(networkCapabilities); } public boolean satisfiesImmutableCapabilitiesOf(NetworkRequest request) { - return everConnected && request.networkCapabilities.satisfiedByImmutableNetworkCapabilities( - networkCapabilities); + return everConnected() + && request.networkCapabilities.satisfiedByImmutableNetworkCapabilities( + networkCapabilities); } /** Whether this network is a VPN. */ @@ -1022,7 +1174,7 @@ public class NetworkAgentInfo implements NetworkRanker.Scoreable { */ public void setScore(final NetworkScore score) { mScore = FullScore.fromNetworkScore(score, networkCapabilities, networkAgentConfig, - everValidatedForYield(), yieldToBadWiFi(), destroyed); + everValidatedForYield(), yieldToBadWiFi(), isDestroyed()); } /** @@ -1032,11 +1184,11 @@ public class NetworkAgentInfo implements NetworkRanker.Scoreable { */ public void updateScoreForNetworkAgentUpdate() { mScore = mScore.mixInScore(networkCapabilities, networkAgentConfig, - everValidatedForYield(), yieldToBadWiFi(), destroyed); + everValidatedForYield(), yieldToBadWiFi(), isDestroyed()); } private boolean everValidatedForYield() { - return everValidated && !avoidUnvalidated; + return everValidated() && 0L == mAvoidUnvalidated; } /** @@ -1323,14 +1475,17 @@ public class NetworkAgentInfo implements NetworkRanker.Scoreable { + networkInfo.toShortString() + "} " + "created=" + Instant.ofEpochMilli(mCreationTime) + " " + mScore + " " - + (created ? " created" : "") - + (destroyed ? " destroyed" : "") + + (isCreated() ? " created " + getCreatedTime() : "") + + (isDestroyed() ? " destroyed " + mDestroyedTime : "") + (isNascent() ? " nascent" : (isLingering() ? " lingering" : "")) - + (everValidated ? " everValidated" : "") - + (lastValidated ? " lastValidated" : "") - + (partialConnectivity ? " partialConnectivity" : "") - + (everCaptivePortalDetected ? " everCaptivePortal" : "") - + (lastCaptivePortalDetected ? " isCaptivePortal" : "") + + (everValidated() ? " firstValidated " + getFirstValidationTime() : "") + + (isValidated() ? " lastValidated " + getCurrentValidationTime() : "") + + (partialConnectivity() + ? " partialConnectivity " + mPartialConnectivityTime : "") + + (everCaptivePortalDetected() + ? " firstCaptivePortalDetected " + mFirstCaptivePortalDetectedTime : "") + + (captivePortalDetected() + ? " currentCaptivePortalDetected " + mCurrentCaptivePortalDetectedTime : "") + (networkAgentConfig.explicitlySelected ? " explicitlySelected" : "") + (networkAgentConfig.acceptUnvalidated ? " acceptUnvalidated" : "") + (networkAgentConfig.acceptPartialConnectivity ? " acceptPartialConnectivity" : "") @@ -1348,7 +1503,7 @@ public class NetworkAgentInfo implements NetworkRanker.Scoreable { * * This is often not enough for debugging purposes for anything complex, but the full form * is very long and hard to read, so this is useful when there isn't a lot of ambiguity. - * This represents the network with something like "[100 WIFI|VPN]" or "[108 MOBILE]". + * This represents the network with something like "[100 WIFI|VPN]" or "[108 CELLULAR]". */ public String toShortString() { return "[" + network.getNetId() + " " diff --git a/tests/unit/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/unit/java/com/android/server/connectivity/IpConnectivityMetricsTest.java index ad8613fa90..719314a18b 100644 --- a/tests/unit/java/com/android/server/connectivity/IpConnectivityMetricsTest.java +++ b/tests/unit/java/com/android/server/connectivity/IpConnectivityMetricsTest.java @@ -23,6 +23,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -47,6 +48,7 @@ import android.net.metrics.RaEvent; import android.net.metrics.ValidationProbeEvent; import android.os.Build; import android.os.Parcelable; +import android.os.SystemClock; import android.system.OsConstants; import android.test.suitebuilder.annotation.SmallTest; import android.util.Base64; @@ -138,7 +140,7 @@ public class IpConnectivityMetricsTest { private void logDefaultNetworkEvent(long timeMs, NetworkAgentInfo nai, NetworkAgentInfo oldNai) { final Network network = (nai != null) ? nai.network() : null; - final boolean validated = (nai != null) ? nai.lastValidated : false; + final boolean validated = (nai != null) ? nai.isValidated() : false; final LinkProperties lp = (nai != null) ? nai.linkProperties : null; final NetworkCapabilities nc = (nai != null) ? nai.networkCapabilities : null; @@ -614,7 +616,10 @@ public class IpConnectivityMetricsTest { when(nai.network()).thenReturn(new Network(netId)); nai.linkProperties = new LinkProperties(); nai.networkCapabilities = new NetworkCapabilities(); - nai.lastValidated = true; + nai.setValidated(true); + doReturn(true).when(nai).isValidated(); + doReturn(SystemClock.elapsedRealtime()).when(nai).getFirstValidationTime(); + doReturn(SystemClock.elapsedRealtime()).when(nai).getCurrentValidationTime(); for (int t : BitUtils.unpackBits(transports)) { nai.networkCapabilities.addTransportType(t); } @@ -629,8 +634,6 @@ public class IpConnectivityMetricsTest { return nai; } - - static void verifySerialization(String want, String output) { try { byte[] got = Base64.decode(output, Base64.DEFAULT); diff --git a/tests/unit/java/com/android/server/connectivity/LingerMonitorTest.java b/tests/unit/java/com/android/server/connectivity/LingerMonitorTest.java index 58a7c89d77..01249a11cd 100644 --- a/tests/unit/java/com/android/server/connectivity/LingerMonitorTest.java +++ b/tests/unit/java/com/android/server/connectivity/LingerMonitorTest.java @@ -272,9 +272,8 @@ public class LingerMonitorTest { public void testIgnoreNeverValidatedNetworks() { setNotificationType(LingerMonitor.NOTIFY_TYPE_TOAST); setNotificationSwitch(transition(WIFI, CELLULAR)); - NetworkAgentInfo from = wifiNai(100); + NetworkAgentInfo from = wifiNai(100, false /* setEverValidated */); NetworkAgentInfo to = cellNai(101); - from.everValidated = false; mMonitor.noteLingerDefaultNetwork(from, to); verifyNoNotifications(); @@ -286,7 +285,7 @@ public class LingerMonitorTest { setNotificationSwitch(transition(WIFI, CELLULAR)); NetworkAgentInfo from = wifiNai(100); NetworkAgentInfo to = cellNai(101); - from.lastValidated = true; + from.setValidated(true); mMonitor.noteLingerDefaultNetwork(from, to); verifyNoNotifications(); @@ -363,7 +362,8 @@ public class LingerMonitorTest { eq(NotificationType.NETWORK_SWITCH), eq(from), eq(to), any(), eq(true)); } - NetworkAgentInfo nai(int netId, int transport, int networkType, String networkTypeName) { + NetworkAgentInfo nai(int netId, int transport, int networkType, String networkTypeName, + boolean setEverValidated) { NetworkInfo info = new NetworkInfo(networkType, 0, networkTypeName, ""); NetworkCapabilities caps = new NetworkCapabilities(); caps.addCapability(0); @@ -373,18 +373,32 @@ public class LingerMonitorTest { mCtx, null, new NetworkAgentConfig.Builder().build(), mConnService, mNetd, mDnsResolver, NetworkProvider.ID_NONE, Binder.getCallingUid(), TEST_LINGER_DELAY_MS, mQosCallbackTracker, new ConnectivityService.Dependencies()); - nai.everValidated = true; + if (setEverValidated) { + // As tests in this class deal with testing lingering, most tests are interested + // in networks that can be lingered, and therefore must have validated in the past. + // Thus, pretend the network validated once, then became invalidated. + nai.setValidated(true); + nai.setValidated(false); + } return nai; } NetworkAgentInfo wifiNai(int netId) { + return wifiNai(netId, true /* setEverValidated */); + } + + NetworkAgentInfo wifiNai(int netId, boolean setEverValidated) { return nai(netId, NetworkCapabilities.TRANSPORT_WIFI, - ConnectivityManager.TYPE_WIFI, WIFI); + ConnectivityManager.TYPE_WIFI, WIFI, setEverValidated); } NetworkAgentInfo cellNai(int netId) { + return cellNai(netId, true /* setEverValidated */); + } + + NetworkAgentInfo cellNai(int netId, boolean setEverValidated) { return nai(netId, NetworkCapabilities.TRANSPORT_CELLULAR, - ConnectivityManager.TYPE_MOBILE, CELLULAR); + ConnectivityManager.TYPE_MOBILE, CELLULAR, setEverValidated); } public static class TestableLingerMonitor extends LingerMonitor {