Merge "Support ignoring validation failures after roam."

This commit is contained in:
Lorenzo Colitti
2022-03-18 03:15:49 +00:00
committed by Gerrit Code Review
5 changed files with 148 additions and 4 deletions

View File

@@ -168,4 +168,15 @@
<!-- Regex of wired ethernet ifaces --> <!-- Regex of wired ethernet ifaces -->
<string translatable="false" name="config_ethernet_iface_regex">eth\\d</string> <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> </resources>

View File

@@ -40,6 +40,7 @@
<item type="string" name="config_ethernet_tcp_buffers"/> <item type="string" name="config_ethernet_tcp_buffers"/>
<item type="array" name="config_ethernet_interfaces"/> <item type="array" name="config_ethernet_interfaces"/>
<item type="string" name="config_ethernet_iface_regex"/> <item type="string" name="config_ethernet_iface_regex"/>
<item type="integer" name="config_validationFailureAfterRoamIgnoreTimeMillis" />
</policy> </policy>
</overlayable> </overlayable>
</resources> </resources>

View File

@@ -199,6 +199,7 @@ import android.net.resolv.aidl.Nat64PrefixEventParcel;
import android.net.resolv.aidl.PrivateDnsValidationEventParcel; import android.net.resolv.aidl.PrivateDnsValidationEventParcel;
import android.net.shared.PrivateDnsConfig; import android.net.shared.PrivateDnsConfig;
import android.net.util.MultinetworkPolicyTracker; import android.net.util.MultinetworkPolicyTracker;
import android.net.wifi.WifiInfo;
import android.os.BatteryStatsManager; import android.os.BatteryStatsManager;
import android.os.Binder; import android.os.Binder;
import android.os.Build; 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_LINGER_DELAY_MS = 30_000;
private static final int DEFAULT_NASCENT_DELAY_MS = 5_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. // The maximum number of network request allowed per uid before an exception is thrown.
@VisibleForTesting @VisibleForTesting
static final int MAX_NETWORK_REQUESTS_PER_UID = 100; 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: { case NetworkAgent.EVENT_NETWORK_CAPABILITIES_CHANGED: {
final NetworkCapabilities networkCapabilities = new NetworkCapabilities( final NetworkCapabilities networkCapabilities = new NetworkCapabilities(
(NetworkCapabilities) arg.second); (NetworkCapabilities) arg.second);
maybeUpdateWifiRoamTimestamp(nai, networkCapabilities);
processCapabilitiesFromAgent(nai, networkCapabilities); processCapabilitiesFromAgent(nai, networkCapabilities);
updateCapabilities(nai.getCurrentScore(), nai, networkCapabilities); updateCapabilities(nai.getCurrentScore(), nai, networkCapabilities);
break; break;
@@ -3790,15 +3795,22 @@ public class ConnectivityService extends IConnectivityManager.Stub
private void handleNetworkTested( private void handleNetworkTested(
@NonNull NetworkAgentInfo nai, int testResult, @NonNull String redirectUrl) { @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; final boolean wasPartial = nai.partialConnectivity;
nai.partialConnectivity = ((testResult & NETWORK_VALIDATION_RESULT_PARTIAL) != 0); nai.partialConnectivity = ((testResult & NETWORK_VALIDATION_RESULT_PARTIAL) != 0);
final boolean partialConnectivityChanged = final boolean partialConnectivityChanged =
(wasPartial != nai.partialConnectivity); (wasPartial != nai.partialConnectivity);
final boolean valid = ((testResult & NETWORK_VALIDATION_RESULT_VALID) != 0);
final boolean wasValidated = nai.lastValidated;
final boolean wasDefault = isDefaultNetwork(nai);
if (DBG) { if (DBG) {
final String logMsg = !TextUtils.isEmpty(redirectUrl) final String logMsg = !TextUtils.isEmpty(redirectUrl)
? " with redirect to " + redirectUrl ? " with redirect to " + redirectUrl
@@ -4197,6 +4209,23 @@ public class ConnectivityService extends IConnectivityManager.Stub
return nai.created && !nai.destroyed; 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) { private void handleNetworkAgentDisconnected(Message msg) {
NetworkAgentInfo nai = (NetworkAgentInfo) msg.obj; NetworkAgentInfo nai = (NetworkAgentInfo) msg.obj;
disconnectAndDestroyNetwork(nai); disconnectAndDestroyNetwork(nai);
@@ -9613,6 +9642,18 @@ public class ConnectivityService extends IConnectivityManager.Stub
return ((VpnTransportInfo) ti).getType(); 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. * @param connectionInfo the connection to resolve.
* @return {@code uid} if the connection is found and the app has permission to observe it * @return {@code uid} if the connection is found and the app has permission to observe it

View File

@@ -192,6 +192,8 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo>, NetworkRa
public boolean everConnected; public boolean everConnected;
// Whether this network has been destroyed and is being kept temporarily until it is replaced. // Whether this network has been destroyed and is being kept temporarily until it is replaced.
public boolean destroyed; 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 // 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. // default NetworkRequest in which case validation will not be attempted.

View File

@@ -160,6 +160,7 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue; import static org.junit.Assume.assumeTrue;
import static org.junit.Assume.assumeFalse;
import static org.mockito.AdditionalMatchers.aryEq; import static org.mockito.AdditionalMatchers.aryEq;
import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyLong; 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.NetworkMonitorUtils;
import android.net.shared.PrivateDnsConfig; import android.net.shared.PrivateDnsConfig;
import android.net.util.MultinetworkPolicyTracker; import android.net.util.MultinetworkPolicyTracker;
import android.net.wifi.WifiInfo;
import android.os.BadParcelableException; import android.os.BadParcelableException;
import android.os.BatteryStatsManager; import android.os.BatteryStatsManager;
import android.os.Binder; import android.os.Binder;
@@ -15572,4 +15574,91 @@ public class ConnectivityServiceTest {
assertNull(readHead.poll(TEST_CALLBACK_TIMEOUT_MS, it -> true)); 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);
}
} }