Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4dc410d5 authored by lucaslin's avatar lucaslin Committed by Cherrypicker Worker
Browse files

Add a retry mechanism when error is recoverable

Have a retry delay arrays for getting the next retry delay, and
the max retry delay is 900 seconds, when the max retry delay is
reached, the retry mechanism will always use it delay the retry.

Bug: 229350078
Test: atest FrameworksNetTests:VpnTest
Change-Id: If0fe30f0b98b83a723e14ea8f6cd9a96c54e1691
(cherry picked from commit b4e39c87)
Merged-In: If0fe30f0b98b83a723e14ea8f6cd9a96c54e1691
parent 92d5b2fb
Loading
Loading
Loading
Loading
+123 −25
Original line number Diff line number Diff line
@@ -193,11 +193,19 @@ public class Vpn {
    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
    // 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 String LOCKDOWN_ALLOWLIST_SETTING_NAME =
            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.
     *
@@ -476,6 +484,20 @@ public class Vpn {
                        "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,
@@ -2672,6 +2694,7 @@ public class Vpn {
        private final ScheduledThreadPoolExecutor mExecutor = new ScheduledThreadPoolExecutor(1);

        @Nullable private ScheduledFuture<?> mScheduledHandleNetworkLostTimeout;
        @Nullable private ScheduledFuture<?> mScheduledHandleRetryIkeSessionTimeout;

        /** Signal to ensure shutdown is honored even if a new Network is connected. */
        private boolean mIsRunning = true;
@@ -2695,6 +2718,14 @@ public class Vpn {
        // mMobikeEnabled can only be updated after IKE AUTH is finished.
        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) {
            super(TAG);
            mProfile = profile;
@@ -2771,6 +2802,7 @@ public class Vpn {
                    ikeConfiguration.isIkeExtensionEnabled(
                            IkeSessionConfiguration.EXTENSION_TYPE_MOBIKE);
            onIkeConnectionInfoChanged(token, ikeConfiguration.getIkeSessionConnectionInfo());
            mRetryCount = 0;
        }

        /**
@@ -2967,16 +2999,42 @@ public class Vpn {
        public void onDefaultNetworkChanged(@NonNull Network 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();

            try {
            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 {
                if (mSession != null && mMobikeEnabled) {
                    // IKE session can schedule a migration event only when IKE AUTH is finished
                    // and mMobikeEnabled is true.
@@ -2985,11 +3043,13 @@ public class Vpn {
                            "Migrate IKE Session with token "
                                    + mCurrentToken
                                    + " to network "
                                    + network);
                    mSession.setNetwork(network);
                                    + underlyingNetwork);
                    mSession.setNetwork(underlyingNetwork);
                    return;
                }

                Log.d(TAG, "Start new IKE session on network " + underlyingNetwork);

                // Clear mInterface to prevent Ikev2VpnRunner being cleared when
                // interfaceRemoved() is called.
                mInterface = null;
@@ -3005,12 +3065,12 @@ public class Vpn {
                final ChildSessionParams childSessionParams;
                if (ikeTunConnParams != null) {
                    final IkeSessionParams.Builder builder = new IkeSessionParams.Builder(
                            ikeTunConnParams.getIkeSessionParams()).setNetwork(network);
                            ikeTunConnParams.getIkeSessionParams()).setNetwork(underlyingNetwork);
                    ikeSessionParams = builder.build();
                    childSessionParams = ikeTunConnParams.getTunnelModeChildSessionParams();
                } else {
                    ikeSessionParams = VpnIkev2Utils.buildIkeSessionParams(
                            mContext, mProfile, network);
                            mContext, mProfile, underlyingNetwork);
                    childSessionParams = VpnIkev2Utils.buildChildSessionParams(
                            mProfile.getAllowedAlgorithms());
                }
@@ -3025,7 +3085,7 @@ public class Vpn {
                // called. Thus it is safe to build a mTunnelIface before IKE setup.
                mTunnelIface =
                        mIpSecManager.createIpSecTunnelInterface(
                                address /* unused */, address /* unused */, network);
                                address /* unused */, address /* unused */, underlyingNetwork);
                NetdUtils.setInterfaceUp(mNetd, mTunnelIface.getInterfaceName());

                final int token = ++mCurrentToken;
@@ -3046,6 +3106,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 */
        public void onDefaultNetworkCapabilitiesChanged(@NonNull NetworkCapabilities nc) {
            mUnderlyingNetworkCapabilities = nc;
@@ -3067,6 +3143,11 @@ public class Vpn {
         * consistency of the Ikev2VpnRunner fields.
         */
        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)) {
                Log.d(TAG, "onDefaultNetworkLost called for obsolete network " + network);

@@ -3076,6 +3157,8 @@ public class Vpn {
                // or an error was encountered somewhere else). In both cases, all resources and
                // sessions are torn down via resetIkeState().
                return;
            } else {
                mActiveNetwork = null;
            }

            if (mScheduledHandleNetworkLostTimeout != null
@@ -3088,7 +3171,7 @@ public class Vpn {
                        TAG,
                        "Unexpected error in onDefaultNetworkLost. Tear down session",
                        exception);
                handleSessionLost(exception);
                handleSessionLost(exception, network);
                return;
            }

@@ -3106,13 +3189,13 @@ public class Vpn {
                mScheduledHandleNetworkLostTimeout =
                        mExecutor.schedule(
                                () -> {
                                    handleSessionLost(null);
                                    handleSessionLost(null, network);
                                },
                                NETWORK_LOST_TIMEOUT_MS,
                                TimeUnit.MILLISECONDS);
            } else {
                Log.d(TAG, "Call handleSessionLost for losing network " + network);
                handleSessionLost(null);
                handleSessionLost(null, network);
            }
        }

@@ -3125,6 +3208,20 @@ public class Vpn {
                // mExecutor who has only one thread.
                Log.d(TAG, "Cancel the task for handling network lost timeout");
                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;
            }
        }

@@ -3160,10 +3257,10 @@ public class Vpn {
                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
            // already terminated due to other failures.
            cancelHandleNetworkLostTimeout();
@@ -3187,7 +3284,7 @@ public class Vpn {
                                        VpnManager.ERROR_CLASS_NOT_RECOVERABLE,
                                        ikeException.getErrorType(),
                                        getPackage(), mSessionKey, makeVpnProfileStateLocked(),
                                        mActiveNetwork,
                                        network,
                                        getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
                                                mUnderlyingNetworkCapabilities),
                                        getRedactedLinkPropertiesOfUnderlyingNetwork(
@@ -3205,7 +3302,7 @@ public class Vpn {
                                        VpnManager.ERROR_CLASS_RECOVERABLE,
                                        ikeException.getErrorType(),
                                        getPackage(), mSessionKey, makeVpnProfileStateLocked(),
                                        mActiveNetwork,
                                        network,
                                        getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
                                                mUnderlyingNetworkCapabilities),
                                        getRedactedLinkPropertiesOfUnderlyingNetwork(
@@ -3224,7 +3321,7 @@ public class Vpn {
                                VpnManager.ERROR_CLASS_RECOVERABLE,
                                VpnManager.ERROR_CODE_NETWORK_LOST,
                                getPackage(), mSessionKey, makeVpnProfileStateLocked(),
                                mActiveNetwork,
                                network,
                                getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
                                        mUnderlyingNetworkCapabilities),
                                getRedactedLinkPropertiesOfUnderlyingNetwork(
@@ -3239,7 +3336,7 @@ public class Vpn {
                                    VpnManager.ERROR_CLASS_RECOVERABLE,
                                    VpnManager.ERROR_CODE_NETWORK_UNKNOWN_HOST,
                                    getPackage(), mSessionKey, makeVpnProfileStateLocked(),
                                    mActiveNetwork,
                                    network,
                                    getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
                                            mUnderlyingNetworkCapabilities),
                                    getRedactedLinkPropertiesOfUnderlyingNetwork(
@@ -3253,7 +3350,7 @@ public class Vpn {
                                    VpnManager.ERROR_CLASS_RECOVERABLE,
                                    VpnManager.ERROR_CODE_NETWORK_PROTOCOL_TIMEOUT,
                                    getPackage(), mSessionKey, makeVpnProfileStateLocked(),
                                    mActiveNetwork,
                                    network,
                                    getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
                                            mUnderlyingNetworkCapabilities),
                                    getRedactedLinkPropertiesOfUnderlyingNetwork(
@@ -3267,7 +3364,7 @@ public class Vpn {
                                    VpnManager.ERROR_CLASS_RECOVERABLE,
                                    VpnManager.ERROR_CODE_NETWORK_IO,
                                    getPackage(), mSessionKey, makeVpnProfileStateLocked(),
                                    mActiveNetwork,
                                    network,
                                    getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
                                            mUnderlyingNetworkCapabilities),
                                    getRedactedLinkPropertiesOfUnderlyingNetwork(
@@ -3277,9 +3374,10 @@ public class Vpn {
                } else if (exception != null) {
                    Log.wtf(TAG, "onSessionLost: exception = " + exception);
                }

                scheduleRetryNewIkeSession();
            }

            mActiveNetwork = null;
            mUnderlyingNetworkCapabilities = null;
            mUnderlyingLinkProperties = null;