diff --git a/service/ServiceConnectivityResources/res/values/config.xml b/service/ServiceConnectivityResources/res/values/config.xml index faa9998ba0..1af00c7bb1 100644 --- a/service/ServiceConnectivityResources/res/values/config.xml +++ b/service/ServiceConnectivityResources/res/values/config.xml @@ -168,4 +168,15 @@ eth\\d + + + -1 diff --git a/service/ServiceConnectivityResources/res/values/overlayable.xml b/service/ServiceConnectivityResources/res/values/overlayable.xml index 9fa6a30b5e..b92dd08e26 100644 --- a/service/ServiceConnectivityResources/res/values/overlayable.xml +++ b/service/ServiceConnectivityResources/res/values/overlayable.xml @@ -40,6 +40,7 @@ + diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java index 221b65d491..d6476647ef 100644 --- a/service/src/com/android/server/ConnectivityService.java +++ b/service/src/com/android/server/ConnectivityService.java @@ -199,6 +199,7 @@ import android.net.resolv.aidl.Nat64PrefixEventParcel; import android.net.resolv.aidl.PrivateDnsValidationEventParcel; import android.net.shared.PrivateDnsConfig; import android.net.util.MultinetworkPolicyTracker; +import android.net.wifi.WifiInfo; import android.os.BatteryStatsManager; import android.os.Binder; import android.os.Build; @@ -348,6 +349,9 @@ public class ConnectivityService extends IConnectivityManager.Stub private static final int DEFAULT_LINGER_DELAY_MS = 30_000; private static final int DEFAULT_NASCENT_DELAY_MS = 5_000; + // The maximum value for the blocking validation result, in milliseconds. + public static final int MAX_VALIDATION_FAILURE_BLOCKING_TIME_MS = 10000; + // The maximum number of network request allowed per uid before an exception is thrown. @VisibleForTesting static final int MAX_NETWORK_REQUESTS_PER_UID = 100; @@ -3543,6 +3547,7 @@ public class ConnectivityService extends IConnectivityManager.Stub case NetworkAgent.EVENT_NETWORK_CAPABILITIES_CHANGED: { final NetworkCapabilities networkCapabilities = new NetworkCapabilities( (NetworkCapabilities) arg.second); + maybeUpdateWifiRoamTimestamp(nai, networkCapabilities); processCapabilitiesFromAgent(nai, networkCapabilities); updateCapabilities(nai.getCurrentScore(), nai, networkCapabilities); break; @@ -3790,15 +3795,22 @@ public class ConnectivityService extends IConnectivityManager.Stub private void handleNetworkTested( @NonNull NetworkAgentInfo nai, int testResult, @NonNull String redirectUrl) { + final boolean valid = ((testResult & NETWORK_VALIDATION_RESULT_VALID) != 0); + if (!valid && shouldIgnoreValidationFailureAfterRoam(nai)) { + // Assume the validation failure is due to a temporary failure after roaming + // and ignore it. NetworkMonitor will continue to retry validation. If it + // continues to fail after the block timeout expires, the network will be + // marked unvalidated. If it succeeds, then validation state will not change. + return; + } + + final boolean wasValidated = nai.lastValidated; + final boolean wasDefault = isDefaultNetwork(nai); final boolean wasPartial = nai.partialConnectivity; nai.partialConnectivity = ((testResult & NETWORK_VALIDATION_RESULT_PARTIAL) != 0); final boolean partialConnectivityChanged = (wasPartial != nai.partialConnectivity); - final boolean valid = ((testResult & NETWORK_VALIDATION_RESULT_VALID) != 0); - final boolean wasValidated = nai.lastValidated; - final boolean wasDefault = isDefaultNetwork(nai); - if (DBG) { final String logMsg = !TextUtils.isEmpty(redirectUrl) ? " with redirect to " + redirectUrl @@ -4197,6 +4209,23 @@ public class ConnectivityService extends IConnectivityManager.Stub return nai.created && !nai.destroyed; } + private boolean shouldIgnoreValidationFailureAfterRoam(NetworkAgentInfo nai) { + // T+ devices should use destroyAndAwaitReplacement. + if (SdkLevel.isAtLeastT()) return false; + final long blockTimeOut = Long.valueOf(mResources.get().getInteger( + R.integer.config_validationFailureAfterRoamIgnoreTimeMillis)); + if (blockTimeOut <= MAX_VALIDATION_FAILURE_BLOCKING_TIME_MS + && blockTimeOut >= 0) { + final long currentTimeMs = SystemClock.elapsedRealtime(); + long timeSinceLastRoam = currentTimeMs - nai.lastRoamTimestamp; + if (timeSinceLastRoam <= blockTimeOut) { + log ("blocked because only " + timeSinceLastRoam + "ms after roam"); + return true; + } + } + return false; + } + private void handleNetworkAgentDisconnected(Message msg) { NetworkAgentInfo nai = (NetworkAgentInfo) msg.obj; disconnectAndDestroyNetwork(nai); @@ -9613,6 +9642,18 @@ public class ConnectivityService extends IConnectivityManager.Stub return ((VpnTransportInfo) ti).getType(); } + private void maybeUpdateWifiRoamTimestamp(NetworkAgentInfo nai, NetworkCapabilities nc) { + if (nai == null) return; + final TransportInfo prevInfo = nai.networkCapabilities.getTransportInfo(); + final TransportInfo newInfo = nc.getTransportInfo(); + if (!(prevInfo instanceof WifiInfo) || !(newInfo instanceof WifiInfo)) { + return; + } + if (!TextUtils.equals(((WifiInfo)prevInfo).getBSSID(), ((WifiInfo)newInfo).getBSSID())) { + nai.lastRoamTimestamp = SystemClock.elapsedRealtime(); + } + } + /** * @param connectionInfo the connection to resolve. * @return {@code uid} if the connection is found and the app has permission to observe it diff --git a/service/src/com/android/server/connectivity/NetworkAgentInfo.java b/service/src/com/android/server/connectivity/NetworkAgentInfo.java index cbfc4f7efe..b73e2ccb4e 100644 --- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java +++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java @@ -192,6 +192,8 @@ public class NetworkAgentInfo implements Comparable, NetworkRa 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; // 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. diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java index 6d802afbb8..6eec2eb688 100644 --- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java @@ -160,6 +160,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.junit.Assume.assumeTrue; +import static org.junit.Assume.assumeFalse; import static org.mockito.AdditionalMatchers.aryEq; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyLong; @@ -283,6 +284,7 @@ import android.net.resolv.aidl.PrivateDnsValidationEventParcel; import android.net.shared.NetworkMonitorUtils; import android.net.shared.PrivateDnsConfig; import android.net.util.MultinetworkPolicyTracker; +import android.net.wifi.WifiInfo; import android.os.BadParcelableException; import android.os.BatteryStatsManager; import android.os.Binder; @@ -15572,4 +15574,91 @@ public class ConnectivityServiceTest { assertNull(readHead.poll(TEST_CALLBACK_TIMEOUT_MS, it -> true)); } + + @Test + public void testIgnoreValidationAfterRoamDisabled() throws Exception { + assumeFalse(SdkLevel.isAtLeastT()); + // testIgnoreValidationAfterRoam off + doReturn(-1).when(mResources) + .getInteger(R.integer.config_validationFailureAfterRoamIgnoreTimeMillis); + + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + NetworkCapabilities wifiNc1 = new NetworkCapabilities() + .addTransportType(TRANSPORT_WIFI) + .setTransportInfo(new WifiInfo.Builder().setBssid("AA:AA:AA:AA:AA:AA").build()); + NetworkCapabilities wifiNc2 = new NetworkCapabilities() + .addTransportType(TRANSPORT_WIFI) + .setTransportInfo(new WifiInfo.Builder().setBssid("BB:BB:BB:BB:BB:BB").build()); + final LinkProperties wifiLp = new LinkProperties(); + wifiLp.setInterfaceName(WIFI_IFNAME); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp, wifiNc1); + mWiFiNetworkAgent.connect(true); + + // The default network will be switching to Wi-Fi Network. + final TestNetworkCallback wifiNetworkCallback = new TestNetworkCallback(); + final NetworkRequest wifiRequest = new NetworkRequest.Builder() + .addTransportType(TRANSPORT_WIFI).build(); + mCm.registerNetworkCallback(wifiRequest, wifiNetworkCallback); + wifiNetworkCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent); + registerDefaultNetworkCallbacks(); + mDefaultNetworkCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent); + + // Wi-Fi roaming from wifiNc1 to wifiNc2. + mWiFiNetworkAgent.setNetworkCapabilities(wifiNc2, true); + mWiFiNetworkAgent.setNetworkInvalid(false); + mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false); + mDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); + } + + @Test + public void testIgnoreValidationAfterRoamEnabled() throws Exception { + assumeFalse(SdkLevel.isAtLeastT()); + // testIgnoreValidationAfterRoam on + doReturn(5000).when(mResources) + .getInteger(R.integer.config_validationFailureAfterRoamIgnoreTimeMillis); + + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + NetworkCapabilities wifiNc1 = new NetworkCapabilities() + .addTransportType(TRANSPORT_WIFI) + .setTransportInfo(new WifiInfo.Builder().setBssid("AA:AA:AA:AA:AA:AA").build()); + NetworkCapabilities wifiNc2 = new NetworkCapabilities() + .addTransportType(TRANSPORT_WIFI) + .setTransportInfo(new WifiInfo.Builder().setBssid("BB:BB:BB:BB:BB:BB").build()); + final LinkProperties wifiLp = new LinkProperties(); + wifiLp.setInterfaceName(WIFI_IFNAME); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp, wifiNc1); + mWiFiNetworkAgent.connect(true); + + // The default network will be switching to Wi-Fi Network. + final TestNetworkCallback wifiNetworkCallback = new TestNetworkCallback(); + final NetworkRequest wifiRequest = new NetworkRequest.Builder() + .addTransportType(TRANSPORT_WIFI).build(); + mCm.registerNetworkCallback(wifiRequest, wifiNetworkCallback); + wifiNetworkCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent); + registerDefaultNetworkCallbacks(); + mDefaultNetworkCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent); + + // Wi-Fi roaming from wifiNc1 to wifiNc2. + mWiFiNetworkAgent.setNetworkCapabilities(wifiNc2, true); + mWiFiNetworkAgent.setNetworkInvalid(false); + mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false); + + // Network validation failed, but the result will be ignored. + assertTrue(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability( + NET_CAPABILITY_VALIDATED)); + mWiFiNetworkAgent.setNetworkValid(false); + + // Behavior of after config_validationFailureAfterRoamIgnoreTimeMillis + ConditionVariable waitForValidationBlock = new ConditionVariable(); + doReturn(50).when(mResources) + .getInteger(R.integer.config_validationFailureAfterRoamIgnoreTimeMillis); + // Wi-Fi roaming from wifiNc2 to wifiNc1. + mWiFiNetworkAgent.setNetworkCapabilities(wifiNc1, true); + mWiFiNetworkAgent.setNetworkInvalid(false); + waitForValidationBlock.block(150); + mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false); + mDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); + } }