Merge "Add a retry mechanism when error is recoverable" am: b02eee5c35 am: 6059e6c37c

Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/2087826

Change-Id: I24f496dbe4285341afa9500e4c1d0fe0cae95439
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Lucas Lin
2022-06-09 18:27:40 +00:00
committed by Automerger Merge Worker

View File

@@ -193,11 +193,19 @@ public class Vpn {
private static final long VPN_LAUNCH_IDLE_ALLOWLIST_DURATION_MS = 60 * 1000; private static final long VPN_LAUNCH_IDLE_ALLOWLIST_DURATION_MS = 60 * 1000;
// Length of time (in milliseconds) that an app registered for VpnManager events is placed on // Length of time (in milliseconds) that an app registered for VpnManager events is placed on
// the device idle allowlist each time the a VpnManager event is fired. // the device idle allowlist each time the VpnManager event is fired.
private static final long VPN_MANAGER_EVENT_ALLOWLIST_DURATION_MS = 30 * 1000; private static final long VPN_MANAGER_EVENT_ALLOWLIST_DURATION_MS = 30 * 1000;
private static final String LOCKDOWN_ALLOWLIST_SETTING_NAME = private static final String LOCKDOWN_ALLOWLIST_SETTING_NAME =
Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST; Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST;
/**
* The retries for consecutive failures.
*
* <p>If retries have exceeded the length of this array, the last entry in the array will be
* used as a repeating interval.
*/
private static final long[] IKEV2_VPN_RETRY_DELAYS_SEC = {1L, 2L, 5L, 30L, 60L, 300L, 900L};
/** /**
* Largest profile size allowable for Platform VPNs. * Largest profile size allowable for Platform VPNs.
* *
@@ -476,6 +484,20 @@ public class Vpn {
"Cannot set tunnel's fd as blocking=" + blocking, e); "Cannot set tunnel's fd as blocking=" + blocking, e);
} }
} }
/**
* Retrieves the next retry delay
*
* <p>If retries have exceeded the IKEV2_VPN_RETRY_DELAYS_SEC, the last entry in
* the array will be used as a repeating interval.
*/
public long getNextRetryDelaySeconds(int retryCount) {
if (retryCount >= IKEV2_VPN_RETRY_DELAYS_SEC.length) {
return IKEV2_VPN_RETRY_DELAYS_SEC[IKEV2_VPN_RETRY_DELAYS_SEC.length - 1];
} else {
return IKEV2_VPN_RETRY_DELAYS_SEC[retryCount];
}
}
} }
public Vpn(Looper looper, Context context, INetworkManagementService netService, INetd netd, public Vpn(Looper looper, Context context, INetworkManagementService netService, INetd netd,
@@ -2674,6 +2696,7 @@ public class Vpn {
private final ScheduledThreadPoolExecutor mExecutor = new ScheduledThreadPoolExecutor(1); private final ScheduledThreadPoolExecutor mExecutor = new ScheduledThreadPoolExecutor(1);
@Nullable private ScheduledFuture<?> mScheduledHandleNetworkLostTimeout; @Nullable private ScheduledFuture<?> mScheduledHandleNetworkLostTimeout;
@Nullable private ScheduledFuture<?> mScheduledHandleRetryIkeSessionTimeout;
/** Signal to ensure shutdown is honored even if a new Network is connected. */ /** Signal to ensure shutdown is honored even if a new Network is connected. */
private boolean mIsRunning = true; private boolean mIsRunning = true;
@@ -2697,6 +2720,14 @@ public class Vpn {
// mMobikeEnabled can only be updated after IKE AUTH is finished. // mMobikeEnabled can only be updated after IKE AUTH is finished.
private boolean mMobikeEnabled = false; private boolean mMobikeEnabled = false;
/**
* The number of attempts since the last successful connection.
*
* <p>This variable controls the retry delay, and is reset when a new IKE session is
* opened or when there is a new default network.
*/
private int mRetryCount = 0;
IkeV2VpnRunner(@NonNull Ikev2VpnProfile profile) { IkeV2VpnRunner(@NonNull Ikev2VpnProfile profile) {
super(TAG); super(TAG);
mProfile = profile; mProfile = profile;
@@ -2773,6 +2804,7 @@ public class Vpn {
ikeConfiguration.isIkeExtensionEnabled( ikeConfiguration.isIkeExtensionEnabled(
IkeSessionConfiguration.EXTENSION_TYPE_MOBIKE); IkeSessionConfiguration.EXTENSION_TYPE_MOBIKE);
onIkeConnectionInfoChanged(token, ikeConfiguration.getIkeSessionConnectionInfo()); onIkeConnectionInfoChanged(token, ikeConfiguration.getIkeSessionConnectionInfo());
mRetryCount = 0;
} }
/** /**
@@ -2969,16 +3001,42 @@ public class Vpn {
public void onDefaultNetworkChanged(@NonNull Network network) { public void onDefaultNetworkChanged(@NonNull Network network) {
Log.d(TAG, "onDefaultNetworkChanged: " + network); Log.d(TAG, "onDefaultNetworkChanged: " + network);
// If there is a new default network brought up, cancel the retry task to prevent
// establishing an unnecessary IKE session.
cancelRetryNewIkeSessionFuture();
// If there is a new default network brought up, cancel the obsolete reset and retry
// task.
cancelHandleNetworkLostTimeout(); cancelHandleNetworkLostTimeout();
if (!mIsRunning) {
Log.d(TAG, "onDefaultNetworkChanged after exit");
return; // VPN has been shut down.
}
mActiveNetwork = network;
mRetryCount = 0;
startOrMigrateIkeSession(network);
}
/**
* Start a new IKE session.
*
* <p>This method MUST always be called on the mExecutor thread in order to ensure
* consistency of the Ikev2VpnRunner fields.
*
* @param underlyingNetwork if the value is {@code null}, which means there is no active
* network can be used, do nothing and return immediately. Otherwise, use the
* given network to start a new IKE session.
*/
private void startOrMigrateIkeSession(@Nullable Network underlyingNetwork) {
if (underlyingNetwork == null) {
Log.d(TAG, "There is no active network for starting an IKE session");
return;
}
try { try {
if (!mIsRunning) {
Log.d(TAG, "onDefaultNetworkChanged after exit");
return; // VPN has been shut down.
}
mActiveNetwork = network;
if (mSession != null && mMobikeEnabled) { if (mSession != null && mMobikeEnabled) {
// IKE session can schedule a migration event only when IKE AUTH is finished // IKE session can schedule a migration event only when IKE AUTH is finished
// and mMobikeEnabled is true. // and mMobikeEnabled is true.
@@ -2987,11 +3045,13 @@ public class Vpn {
"Migrate IKE Session with token " "Migrate IKE Session with token "
+ mCurrentToken + mCurrentToken
+ " to network " + " to network "
+ network); + underlyingNetwork);
mSession.setNetwork(network); mSession.setNetwork(underlyingNetwork);
return; return;
} }
Log.d(TAG, "Start new IKE session on network " + underlyingNetwork);
// Clear mInterface to prevent Ikev2VpnRunner being cleared when // Clear mInterface to prevent Ikev2VpnRunner being cleared when
// interfaceRemoved() is called. // interfaceRemoved() is called.
mInterface = null; mInterface = null;
@@ -3007,12 +3067,12 @@ public class Vpn {
final ChildSessionParams childSessionParams; final ChildSessionParams childSessionParams;
if (ikeTunConnParams != null) { if (ikeTunConnParams != null) {
final IkeSessionParams.Builder builder = new IkeSessionParams.Builder( final IkeSessionParams.Builder builder = new IkeSessionParams.Builder(
ikeTunConnParams.getIkeSessionParams()).setNetwork(network); ikeTunConnParams.getIkeSessionParams()).setNetwork(underlyingNetwork);
ikeSessionParams = builder.build(); ikeSessionParams = builder.build();
childSessionParams = ikeTunConnParams.getTunnelModeChildSessionParams(); childSessionParams = ikeTunConnParams.getTunnelModeChildSessionParams();
} else { } else {
ikeSessionParams = VpnIkev2Utils.buildIkeSessionParams( ikeSessionParams = VpnIkev2Utils.buildIkeSessionParams(
mContext, mProfile, network); mContext, mProfile, underlyingNetwork);
childSessionParams = VpnIkev2Utils.buildChildSessionParams( childSessionParams = VpnIkev2Utils.buildChildSessionParams(
mProfile.getAllowedAlgorithms()); mProfile.getAllowedAlgorithms());
} }
@@ -3027,7 +3087,7 @@ public class Vpn {
// called. Thus it is safe to build a mTunnelIface before IKE setup. // called. Thus it is safe to build a mTunnelIface before IKE setup.
mTunnelIface = mTunnelIface =
mIpSecManager.createIpSecTunnelInterface( mIpSecManager.createIpSecTunnelInterface(
address /* unused */, address /* unused */, network); address /* unused */, address /* unused */, underlyingNetwork);
NetdUtils.setInterfaceUp(mNetd, mTunnelIface.getInterfaceName()); NetdUtils.setInterfaceUp(mNetd, mTunnelIface.getInterfaceName());
final int token = ++mCurrentToken; final int token = ++mCurrentToken;
@@ -3048,6 +3108,22 @@ public class Vpn {
} }
} }
private void scheduleRetryNewIkeSession() {
final long retryDelay = mDeps.getNextRetryDelaySeconds(mRetryCount++);
Log.d(TAG, "Retry new IKE session after " + retryDelay + " seconds.");
// If the default network is lost during the retry delay, the mActiveNetwork will be
// null, and the new IKE session won't be established until there is a new default
// network bringing up.
mScheduledHandleRetryIkeSessionTimeout =
mExecutor.schedule(() -> {
startOrMigrateIkeSession(mActiveNetwork);
// Reset mScheduledHandleRetryIkeSessionTimeout since it's already run on
// executor thread.
mScheduledHandleRetryIkeSessionTimeout = null;
}, retryDelay, TimeUnit.SECONDS);
}
/** Called when the NetworkCapabilities of underlying network is changed */ /** Called when the NetworkCapabilities of underlying network is changed */
public void onDefaultNetworkCapabilitiesChanged(@NonNull NetworkCapabilities nc) { public void onDefaultNetworkCapabilitiesChanged(@NonNull NetworkCapabilities nc) {
mUnderlyingNetworkCapabilities = nc; mUnderlyingNetworkCapabilities = nc;
@@ -3069,6 +3145,11 @@ public class Vpn {
* consistency of the Ikev2VpnRunner fields. * consistency of the Ikev2VpnRunner fields.
*/ */
public void onDefaultNetworkLost(@NonNull Network network) { public void onDefaultNetworkLost(@NonNull Network network) {
// If the default network is torn down, there is no need to call
// startOrMigrateIkeSession() since it will always check if there is an active network
// can be used or not.
cancelRetryNewIkeSessionFuture();
if (!isActiveNetwork(network)) { if (!isActiveNetwork(network)) {
Log.d(TAG, "onDefaultNetworkLost called for obsolete network " + network); Log.d(TAG, "onDefaultNetworkLost called for obsolete network " + network);
@@ -3078,6 +3159,8 @@ public class Vpn {
// or an error was encountered somewhere else). In both cases, all resources and // or an error was encountered somewhere else). In both cases, all resources and
// sessions are torn down via resetIkeState(). // sessions are torn down via resetIkeState().
return; return;
} else {
mActiveNetwork = null;
} }
if (mScheduledHandleNetworkLostTimeout != null if (mScheduledHandleNetworkLostTimeout != null
@@ -3090,7 +3173,7 @@ public class Vpn {
TAG, TAG,
"Unexpected error in onDefaultNetworkLost. Tear down session", "Unexpected error in onDefaultNetworkLost. Tear down session",
exception); exception);
handleSessionLost(exception); handleSessionLost(exception, network);
return; return;
} }
@@ -3099,7 +3182,7 @@ public class Vpn {
TAG, TAG,
"IKE Session has mobility. Delay handleSessionLost for losing network " "IKE Session has mobility. Delay handleSessionLost for losing network "
+ network + network
+ "on session with token " + " on session with token "
+ mCurrentToken); + mCurrentToken);
// Delay the teardown in case a new network will be available soon. For example, // Delay the teardown in case a new network will be available soon. For example,
@@ -3108,13 +3191,13 @@ public class Vpn {
mScheduledHandleNetworkLostTimeout = mScheduledHandleNetworkLostTimeout =
mExecutor.schedule( mExecutor.schedule(
() -> { () -> {
handleSessionLost(null); handleSessionLost(null, network);
}, },
NETWORK_LOST_TIMEOUT_MS, NETWORK_LOST_TIMEOUT_MS,
TimeUnit.MILLISECONDS); TimeUnit.MILLISECONDS);
} else { } else {
Log.d(TAG, "Call handleSessionLost for losing network " + network); Log.d(TAG, "Call handleSessionLost for losing network " + network);
handleSessionLost(null); handleSessionLost(null, network);
} }
} }
@@ -3127,6 +3210,20 @@ public class Vpn {
// mExecutor who has only one thread. // mExecutor who has only one thread.
Log.d(TAG, "Cancel the task for handling network lost timeout"); Log.d(TAG, "Cancel the task for handling network lost timeout");
mScheduledHandleNetworkLostTimeout.cancel(false /* mayInterruptIfRunning */); mScheduledHandleNetworkLostTimeout.cancel(false /* mayInterruptIfRunning */);
mScheduledHandleNetworkLostTimeout = null;
}
}
private void cancelRetryNewIkeSessionFuture() {
if (mScheduledHandleRetryIkeSessionTimeout != null
&& !mScheduledHandleRetryIkeSessionTimeout.isDone()) {
// It does not matter what to put in #cancel(boolean), because it is impossible
// that the task tracked by mScheduledHandleRetryIkeSessionTimeout is
// in-progress since both that task and onDefaultNetworkChanged are submitted to
// mExecutor who has only one thread.
Log.d(TAG, "Cancel the task for handling new ike session timeout");
mScheduledHandleRetryIkeSessionTimeout.cancel(false /* mayInterruptIfRunning */);
mScheduledHandleRetryIkeSessionTimeout = null;
} }
} }
@@ -3162,10 +3259,10 @@ public class Vpn {
return; return;
} }
handleSessionLost(exception); handleSessionLost(exception, mActiveNetwork);
} }
private void handleSessionLost(@Nullable Exception exception) { private void handleSessionLost(@Nullable Exception exception, @Nullable Network network) {
// Cancel mScheduledHandleNetworkLostTimeout if the session it is going to terminate is // Cancel mScheduledHandleNetworkLostTimeout if the session it is going to terminate is
// already terminated due to other failures. // already terminated due to other failures.
cancelHandleNetworkLostTimeout(); cancelHandleNetworkLostTimeout();
@@ -3189,7 +3286,7 @@ public class Vpn {
VpnManager.ERROR_CLASS_NOT_RECOVERABLE, VpnManager.ERROR_CLASS_NOT_RECOVERABLE,
ikeException.getErrorType(), ikeException.getErrorType(),
getPackage(), mSessionKey, makeVpnProfileStateLocked(), getPackage(), mSessionKey, makeVpnProfileStateLocked(),
mActiveNetwork, network,
getRedactedNetworkCapabilitiesOfUnderlyingNetwork( getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
mUnderlyingNetworkCapabilities), mUnderlyingNetworkCapabilities),
getRedactedLinkPropertiesOfUnderlyingNetwork( getRedactedLinkPropertiesOfUnderlyingNetwork(
@@ -3207,7 +3304,7 @@ public class Vpn {
VpnManager.ERROR_CLASS_RECOVERABLE, VpnManager.ERROR_CLASS_RECOVERABLE,
ikeException.getErrorType(), ikeException.getErrorType(),
getPackage(), mSessionKey, makeVpnProfileStateLocked(), getPackage(), mSessionKey, makeVpnProfileStateLocked(),
mActiveNetwork, network,
getRedactedNetworkCapabilitiesOfUnderlyingNetwork( getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
mUnderlyingNetworkCapabilities), mUnderlyingNetworkCapabilities),
getRedactedLinkPropertiesOfUnderlyingNetwork( getRedactedLinkPropertiesOfUnderlyingNetwork(
@@ -3226,7 +3323,7 @@ public class Vpn {
VpnManager.ERROR_CLASS_RECOVERABLE, VpnManager.ERROR_CLASS_RECOVERABLE,
VpnManager.ERROR_CODE_NETWORK_LOST, VpnManager.ERROR_CODE_NETWORK_LOST,
getPackage(), mSessionKey, makeVpnProfileStateLocked(), getPackage(), mSessionKey, makeVpnProfileStateLocked(),
mActiveNetwork, network,
getRedactedNetworkCapabilitiesOfUnderlyingNetwork( getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
mUnderlyingNetworkCapabilities), mUnderlyingNetworkCapabilities),
getRedactedLinkPropertiesOfUnderlyingNetwork( getRedactedLinkPropertiesOfUnderlyingNetwork(
@@ -3241,7 +3338,7 @@ public class Vpn {
VpnManager.ERROR_CLASS_RECOVERABLE, VpnManager.ERROR_CLASS_RECOVERABLE,
VpnManager.ERROR_CODE_NETWORK_UNKNOWN_HOST, VpnManager.ERROR_CODE_NETWORK_UNKNOWN_HOST,
getPackage(), mSessionKey, makeVpnProfileStateLocked(), getPackage(), mSessionKey, makeVpnProfileStateLocked(),
mActiveNetwork, network,
getRedactedNetworkCapabilitiesOfUnderlyingNetwork( getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
mUnderlyingNetworkCapabilities), mUnderlyingNetworkCapabilities),
getRedactedLinkPropertiesOfUnderlyingNetwork( getRedactedLinkPropertiesOfUnderlyingNetwork(
@@ -3255,7 +3352,7 @@ public class Vpn {
VpnManager.ERROR_CLASS_RECOVERABLE, VpnManager.ERROR_CLASS_RECOVERABLE,
VpnManager.ERROR_CODE_NETWORK_PROTOCOL_TIMEOUT, VpnManager.ERROR_CODE_NETWORK_PROTOCOL_TIMEOUT,
getPackage(), mSessionKey, makeVpnProfileStateLocked(), getPackage(), mSessionKey, makeVpnProfileStateLocked(),
mActiveNetwork, network,
getRedactedNetworkCapabilitiesOfUnderlyingNetwork( getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
mUnderlyingNetworkCapabilities), mUnderlyingNetworkCapabilities),
getRedactedLinkPropertiesOfUnderlyingNetwork( getRedactedLinkPropertiesOfUnderlyingNetwork(
@@ -3269,7 +3366,7 @@ public class Vpn {
VpnManager.ERROR_CLASS_RECOVERABLE, VpnManager.ERROR_CLASS_RECOVERABLE,
VpnManager.ERROR_CODE_NETWORK_IO, VpnManager.ERROR_CODE_NETWORK_IO,
getPackage(), mSessionKey, makeVpnProfileStateLocked(), getPackage(), mSessionKey, makeVpnProfileStateLocked(),
mActiveNetwork, network,
getRedactedNetworkCapabilitiesOfUnderlyingNetwork( getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
mUnderlyingNetworkCapabilities), mUnderlyingNetworkCapabilities),
getRedactedLinkPropertiesOfUnderlyingNetwork( getRedactedLinkPropertiesOfUnderlyingNetwork(
@@ -3279,9 +3376,10 @@ public class Vpn {
} else if (exception != null) { } else if (exception != null) {
Log.wtf(TAG, "onSessionLost: exception = " + exception); Log.wtf(TAG, "onSessionLost: exception = " + exception);
} }
scheduleRetryNewIkeSession();
} }
mActiveNetwork = null;
mUnderlyingNetworkCapabilities = null; mUnderlyingNetworkCapabilities = null;
mUnderlyingLinkProperties = null; mUnderlyingLinkProperties = null;