Merge "Support ignoring validation failures after roam."
diff --git a/service/ServiceConnectivityResources/res/values/config.xml b/service/ServiceConnectivityResources/res/values/config.xml
index faa9998..1af00c7 100644
--- a/service/ServiceConnectivityResources/res/values/config.xml
+++ b/service/ServiceConnectivityResources/res/values/config.xml
@@ -168,4 +168,15 @@
<!-- Regex of wired ethernet ifaces -->
<string translatable="false" name="config_ethernet_iface_regex">eth\\d</string>
+
+ <!-- Ignores Wi-Fi validation failures after roam.
+ If validation fails on a Wi-Fi network after a roam to a new BSSID,
+ assume that the roam temporarily disrupted network connectivity, and
+ ignore all failures until this time has passed.
+ NetworkMonitor will continue to attempt validation, and if it fails after this time has passed,
+ the network will be marked unvalidated.
+
+ Only supported up to S. On T+, the Wi-Fi code should use destroyAndAwaitReplacement in order
+ to ensure that apps see the network disconnect and reconnect. -->
+ <integer translatable="false" name="config_validationFailureAfterRoamIgnoreTimeMillis">-1</integer>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values/overlayable.xml b/service/ServiceConnectivityResources/res/values/overlayable.xml
index 9fa6a30..b92dd08 100644
--- a/service/ServiceConnectivityResources/res/values/overlayable.xml
+++ b/service/ServiceConnectivityResources/res/values/overlayable.xml
@@ -40,6 +40,7 @@
<item type="string" name="config_ethernet_tcp_buffers"/>
<item type="array" name="config_ethernet_interfaces"/>
<item type="string" name="config_ethernet_iface_regex"/>
+ <item type="integer" name="config_validationFailureAfterRoamIgnoreTimeMillis" />
</policy>
</overlayable>
</resources>
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 221b65d..d647664 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.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 @@
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 @@
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 @@
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 @@
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 @@
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 cbfc4f7..b73e2cc 100644
--- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
@@ -192,6 +192,8 @@
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 6d802af..6eec2eb 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.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.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 @@
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);
+ }
}