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

Commit 089c119f authored by Benedict Wong's avatar Benedict Wong Committed by Gerrit Code Review
Browse files

Merge "Enable MOBIKE in IKEv2 VPN"

parents 6d1cecb3 14052962
Loading
Loading
Loading
Loading
+219 −17
Original line number Original line Diff line number Diff line
@@ -86,6 +86,8 @@ import android.net.ipsec.ike.ChildSessionConfiguration;
import android.net.ipsec.ike.ChildSessionParams;
import android.net.ipsec.ike.ChildSessionParams;
import android.net.ipsec.ike.IkeSession;
import android.net.ipsec.ike.IkeSession;
import android.net.ipsec.ike.IkeSessionCallback;
import android.net.ipsec.ike.IkeSessionCallback;
import android.net.ipsec.ike.IkeSessionConfiguration;
import android.net.ipsec.ike.IkeSessionConnectionInfo;
import android.net.ipsec.ike.IkeSessionParams;
import android.net.ipsec.ike.IkeSessionParams;
import android.net.ipsec.ike.IkeTunnelConnectionParams;
import android.net.ipsec.ike.IkeTunnelConnectionParams;
import android.net.ipsec.ike.exceptions.IkeNetworkLostException;
import android.net.ipsec.ike.exceptions.IkeNetworkLostException;
@@ -168,9 +170,10 @@ import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicInteger;


/**
/**
@@ -2588,10 +2591,20 @@ public class Vpn {


        void onDefaultNetworkLost(@NonNull Network network);
        void onDefaultNetworkLost(@NonNull Network network);


        void onIkeOpened(int token, @NonNull IkeSessionConfiguration ikeConfiguration);

        void onIkeConnectionInfoChanged(
                int token, @NonNull IkeSessionConnectionInfo ikeConnectionInfo);

        void onChildOpened(int token, @NonNull ChildSessionConfiguration childConfig);
        void onChildOpened(int token, @NonNull ChildSessionConfiguration childConfig);


        void onChildTransformCreated(int token, @NonNull IpSecTransform transform, int direction);
        void onChildTransformCreated(int token, @NonNull IpSecTransform transform, int direction);


        void onChildMigrated(
                int token,
                @NonNull IpSecTransform inTransform,
                @NonNull IpSecTransform outTransform);

        void onSessionLost(int token, @Nullable Exception exception);
        void onSessionLost(int token, @Nullable Exception exception);
    }
    }


@@ -2623,6 +2636,10 @@ public class Vpn {
    class IkeV2VpnRunner extends VpnRunner implements IkeV2VpnRunnerCallback {
    class IkeV2VpnRunner extends VpnRunner implements IkeV2VpnRunnerCallback {
        @NonNull private static final String TAG = "IkeV2VpnRunner";
        @NonNull private static final String TAG = "IkeV2VpnRunner";


        // 5 seconds grace period before tearing down the IKE Session in case new default network
        // will come up
        private static final long NETWORK_LOST_TIMEOUT_MS = 5000L;

        @NonNull private final IpSecManager mIpSecManager;
        @NonNull private final IpSecManager mIpSecManager;
        @NonNull private final Ikev2VpnProfile mProfile;
        @NonNull private final Ikev2VpnProfile mProfile;
        @NonNull private final ConnectivityManager.NetworkCallback mNetworkCallback;
        @NonNull private final ConnectivityManager.NetworkCallback mNetworkCallback;
@@ -2634,7 +2651,10 @@ public class Vpn {
         * of the mutable Ikev2VpnRunner fields. The Ikev2VpnRunner is built mostly lock-free by
         * of the mutable Ikev2VpnRunner fields. The Ikev2VpnRunner is built mostly lock-free by
         * virtue of everything being serialized on this executor.
         * virtue of everything being serialized on this executor.
         */
         */
        @NonNull private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
        @NonNull
        private final ScheduledThreadPoolExecutor mExecutor = new ScheduledThreadPoolExecutor(1);

        @Nullable private ScheduledFuture<?> mScheduledHandleNetworkLostTimeout;


        /** 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;
@@ -2647,18 +2667,35 @@ public class Vpn {
        private int mCurrentToken = -1;
        private int mCurrentToken = -1;


        @Nullable private IpSecTunnelInterface mTunnelIface;
        @Nullable private IpSecTunnelInterface mTunnelIface;
        @Nullable private IkeSession mSession;
        @Nullable private Network mActiveNetwork;
        @Nullable private Network mActiveNetwork;
        @Nullable private NetworkCapabilities mUnderlyingNetworkCapabilities;
        @Nullable private NetworkCapabilities mUnderlyingNetworkCapabilities;
        @Nullable private LinkProperties mUnderlyingLinkProperties;
        @Nullable private LinkProperties mUnderlyingLinkProperties;
        private final String mSessionKey;
        private final String mSessionKey;


        @Nullable private IkeSession mSession;
        @Nullable private IkeSessionConnectionInfo mIkeConnectionInfo;

        // mMobikeEnabled can only be updated after IKE AUTH is finished.
        private boolean mMobikeEnabled = false;

        IkeV2VpnRunner(@NonNull Ikev2VpnProfile profile) {
        IkeV2VpnRunner(@NonNull Ikev2VpnProfile profile) {
            super(TAG);
            super(TAG);
            mProfile = profile;
            mProfile = profile;
            mIpSecManager = (IpSecManager) mContext.getSystemService(Context.IPSEC_SERVICE);
            mIpSecManager = (IpSecManager) mContext.getSystemService(Context.IPSEC_SERVICE);
            mNetworkCallback = new VpnIkev2Utils.Ikev2VpnNetworkCallback(TAG, this, mExecutor);
            mNetworkCallback = new VpnIkev2Utils.Ikev2VpnNetworkCallback(TAG, this, mExecutor);
            mSessionKey = UUID.randomUUID().toString();
            mSessionKey = UUID.randomUUID().toString();

            // Set the policy so that cancelled tasks will be removed from the work queue
            mExecutor.setRemoveOnCancelPolicy(true);

            // Set the policy so that all delayed tasks will not be executed
            mExecutor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);

            // To avoid hitting RejectedExecutionException upon shutdown of the mExecutor */
            mExecutor.setRejectedExecutionHandler(
                    (r, executor) -> {
                        Log.d(TAG, "Runnable " + r + " rejected by the mExecutor");
                    });
        }
        }


        @Override
        @Override
@@ -2701,6 +2738,44 @@ public class Vpn {
            return (mCurrentToken == token) && mIsRunning;
            return (mCurrentToken == token) && mIsRunning;
        }
        }


        /**
         * Called when an IKE session has been opened
         *
         * <p>This method is only ever called once per IkeSession, and MUST run on the mExecutor
         * thread in order to ensure consistency of the Ikev2VpnRunner fields.
         */
        public void onIkeOpened(int token, @NonNull IkeSessionConfiguration ikeConfiguration) {
            if (!isActiveToken(token)) {
                Log.d(TAG, "onIkeOpened called for obsolete token " + token);
                return;
            }

            mMobikeEnabled =
                    ikeConfiguration.isIkeExtensionEnabled(
                            IkeSessionConfiguration.EXTENSION_TYPE_MOBIKE);
            onIkeConnectionInfoChanged(token, ikeConfiguration.getIkeSessionConnectionInfo());
        }

        /**
         * Called when an IKE session's {@link IkeSessionConnectionInfo} is available or updated
         *
         * <p>This callback is usually fired when an IKE session has been opened or migrated.
         *
         * <p>This method is called multiple times over the lifetime of an IkeSession, and MUST run
         * on the mExecutor thread in order to ensure consistency of the Ikev2VpnRunner fields.
         */
        public void onIkeConnectionInfoChanged(
                int token, @NonNull IkeSessionConnectionInfo ikeConnectionInfo) {
            if (!isActiveToken(token)) {
                Log.d(TAG, "onIkeConnectionInfoChanged called for obsolete token " + token);
                return;
            }

            // The update on VPN and the IPsec tunnel will be done when migration is fully complete
            // in onChildMigrated
            mIkeConnectionInfo = ikeConnectionInfo;
        }

        /**
        /**
         * Called when an IKE Child session has been opened, signalling completion of the startup.
         * Called when an IKE Child session has been opened, signalling completion of the startup.
         *
         *
@@ -2734,6 +2809,11 @@ public class Vpn {
                    dnsAddrStrings.add(addr.getHostAddress());
                    dnsAddrStrings.add(addr.getHostAddress());
                }
                }


                // The actual network of this IKE session has been set up with is
                // mIkeConnectionInfo.getNetwork() instead of mActiveNetwork because
                // mActiveNetwork might have been updated after the setup was triggered.
                final Network network = mIkeConnectionInfo.getNetwork();

                final NetworkAgent networkAgent;
                final NetworkAgent networkAgent;
                final LinkProperties lp;
                final LinkProperties lp;


@@ -2752,7 +2832,7 @@ public class Vpn {
                    mConfig.dnsServers.clear();
                    mConfig.dnsServers.clear();
                    mConfig.dnsServers.addAll(dnsAddrStrings);
                    mConfig.dnsServers.addAll(dnsAddrStrings);


                    mConfig.underlyingNetworks = new Network[] {mActiveNetwork};
                    mConfig.underlyingNetworks = new Network[] {network};


                    mConfig.disallowedApplications = getAppExclusionList(mPackage);
                    mConfig.disallowedApplications = getAppExclusionList(mPackage);


@@ -2768,8 +2848,7 @@ public class Vpn {
                        return; // Link properties are already sent.
                        return; // Link properties are already sent.
                    } else {
                    } else {
                        // Underlying networks also set in agentConnect()
                        // Underlying networks also set in agentConnect()
                        networkAgent.setUnderlyingNetworks(
                        networkAgent.setUnderlyingNetworks(Collections.singletonList(network));
                                Collections.singletonList(mActiveNetwork));
                    }
                    }


                    lp = makeLinkProperties(); // Accesses VPN instance fields; must be locked
                    lp = makeLinkProperties(); // Accesses VPN instance fields; must be locked
@@ -2786,7 +2865,7 @@ public class Vpn {
         * Called when an IPsec transform has been created, and should be applied.
         * Called when an IPsec transform has been created, and should be applied.
         *
         *
         * <p>This method is called multiple times over the lifetime of an IkeSession (or default
         * <p>This method is called multiple times over the lifetime of an IkeSession (or default
         * network), and is MUST always be called on the mExecutor thread in order to ensure
         * network), and MUST always be called on the mExecutor thread in order to ensure
         * consistency of the Ikev2VpnRunner fields.
         * consistency of the Ikev2VpnRunner fields.
         */
         */
        public void onChildTransformCreated(
        public void onChildTransformCreated(
@@ -2812,17 +2891,66 @@ public class Vpn {
            }
            }
        }
        }


        /**
         * Called when an IPsec transform has been created, and should be re-applied.
         *
         * <p>This method is called multiple times over the lifetime of an IkeSession (or default
         * network), and MUST always be called on the mExecutor thread in order to ensure
         * consistency of the Ikev2VpnRunner fields.
         */
        public void onChildMigrated(
                int token,
                @NonNull IpSecTransform inTransform,
                @NonNull IpSecTransform outTransform) {
            if (!isActiveToken(token)) {
                Log.d(TAG, "onChildMigrated for obsolete token " + token);
                return;
            }

            // The actual network of this IKE session has migrated to is
            // mIkeConnectionInfo.getNetwork() instead of mActiveNetwork because mActiveNetwork
            // might have been updated after the migration was triggered.
            final Network network = mIkeConnectionInfo.getNetwork();

            try {
                synchronized (Vpn.this) {
                    mConfig.underlyingNetworks = new Network[] {network};
                    mNetworkCapabilities =
                            new NetworkCapabilities.Builder(mNetworkCapabilities)
                                    .setUnderlyingNetworks(Collections.singletonList(network))
                                    .build();
                    mNetworkAgent.setUnderlyingNetworks(Collections.singletonList(network));
                }

                mTunnelIface.setUnderlyingNetwork(network);

                // Transforms do not need to be persisted; the IkeSession will keep them alive for
                // us
                mIpSecManager.applyTunnelModeTransform(
                        mTunnelIface, IpSecManager.DIRECTION_IN, inTransform);
                mIpSecManager.applyTunnelModeTransform(
                        mTunnelIface, IpSecManager.DIRECTION_OUT, outTransform);
            } catch (IOException e) {
                Log.d(TAG, "Transform application failed for token " + token, e);
                onSessionLost(token, e);
            }
        }

        /**
        /**
         * Called when a new default network is connected.
         * Called when a new default network is connected.
         *
         *
         * <p>The Ikev2VpnRunner will unconditionally switch to the new network, killing the old IKE
         * <p>The Ikev2VpnRunner will unconditionally switch to the new network. If the IKE session
         * state in the process, and starting a new IkeSession instance.
         * has mobility, Ikev2VpnRunner will migrate the existing IkeSession to the new network.
         * Otherwise, Ikev2VpnRunner will kill the old IKE state, and start a new IkeSession
         * instance.
         *
         *
         * <p>This method MUST always be called on the mExecutor thread in order to ensure
         * <p>This method MUST always be called on the mExecutor thread in order to ensure
         * consistency of the Ikev2VpnRunner fields.
         * consistency of the Ikev2VpnRunner fields.
         */
         */
        public void onDefaultNetworkChanged(@NonNull Network network) {
        public void onDefaultNetworkChanged(@NonNull Network network) {
            Log.d(TAG, "Starting IKEv2/IPsec session on new network: " + network);
            Log.d(TAG, "onDefaultNetworkChanged: " + network);

            cancelHandleNetworkLostTimeout();


            try {
            try {
                if (!mIsRunning) {
                if (!mIsRunning) {
@@ -2830,13 +2958,27 @@ public class Vpn {
                    return; // VPN has been shut down.
                    return; // VPN has been shut down.
                }
                }


                mActiveNetwork = network;

                if (mSession != null && mMobikeEnabled) {
                    // IKE session can schedule a migration event only when IKE AUTH is finished
                    // and mMobikeEnabled is true.
                    Log.d(
                            TAG,
                            "Migrate IKE Session with token "
                                    + mCurrentToken
                                    + " to network "
                                    + network);
                    mSession.setNetwork(network);
                    return;
                }

                // 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;
                // Without MOBIKE, we have no way to seamlessly migrate. Close on old
                // Without MOBIKE, we have no way to seamlessly migrate. Close on old
                // (non-default) network, and start the new one.
                // (non-default) network, and start the new one.
                resetIkeState();
                resetIkeState();
                mActiveNetwork = network;


                // Get Ike options from IkeTunnelConnectionParams if it's available in the
                // Get Ike options from IkeTunnelConnectionParams if it's available in the
                // profile.
                // profile.
@@ -2859,11 +3001,14 @@ public class Vpn {
                // TODO: Remove the need for adding two unused addresses with
                // TODO: Remove the need for adding two unused addresses with
                // IPsec tunnels.
                // IPsec tunnels.
                final InetAddress address = InetAddress.getLocalHost();
                final InetAddress address = InetAddress.getLocalHost();

                // When onChildOpened is called and transforms are applied, it is
                // guaranteed that the underlying network is still "network", because the
                // all the network switch events will be deferred before onChildOpened is
                // called. Thus it is safe to build a mTunnelIface before IKE setup.
                mTunnelIface =
                mTunnelIface =
                        mIpSecManager.createIpSecTunnelInterface(
                        mIpSecManager.createIpSecTunnelInterface(
                                address /* unused */,
                                address /* unused */, address /* unused */, network);
                                address /* unused */,
                                network);
                NetdUtils.setInterfaceUp(mNetd, mTunnelIface.getInterfaceName());
                NetdUtils.setInterfaceUp(mNetd, mTunnelIface.getInterfaceName());


                final int token = ++mCurrentToken;
                final int token = ++mCurrentToken;
@@ -2897,7 +3042,9 @@ public class Vpn {
        /**
        /**
         * Handles loss of the default underlying network
         * Handles loss of the default underlying network
         *
         *
         * <p>The Ikev2VpnRunner will kill the IKE session and reset the VPN.
         * <p>If the IKE Session has mobility, Ikev2VpnRunner will schedule a teardown event with a
         * delay so that the IKE Session can migrate if a new network is available soon. Otherwise,
         * Ikev2VpnRunner will kill the IKE session and reset the VPN.
         *
         *
         * <p>This method MUST always be called on the mExecutor thread in order to ensure
         * <p>This method MUST always be called on the mExecutor thread in order to ensure
         * consistency of the Ikev2VpnRunner fields.
         * consistency of the Ikev2VpnRunner fields.
@@ -2914,8 +3061,55 @@ public class Vpn {
                return;
                return;
            }
            }


            if (mScheduledHandleNetworkLostTimeout != null
                    && !mScheduledHandleNetworkLostTimeout.isCancelled()
                    && !mScheduledHandleNetworkLostTimeout.isDone()) {
                final IllegalStateException exception =
                        new IllegalStateException(
                                "Found a pending mScheduledHandleNetworkLostTimeout");
                Log.i(
                        TAG,
                        "Unexpected error in onDefaultNetworkLost. Tear down session",
                        exception);
                handleSessionLost(exception);
                return;
            }

            if (mSession != null && mMobikeEnabled) {
                Log.d(
                        TAG,
                        "IKE Session has mobility. Delay handleSessionLost for losing network "
                                + network
                                + "on session with token "
                                + mCurrentToken);

                // Delay the teardown in case a new network will be available soon. For example,
                // during handover between two WiFi networks, Android will disconnect from the
                // first WiFi and then connects to the second WiFi.
                mScheduledHandleNetworkLostTimeout =
                        mExecutor.schedule(
                                () -> {
                                    handleSessionLost(null);
                                },
                                NETWORK_LOST_TIMEOUT_MS,
                                TimeUnit.MILLISECONDS);
            } else {
                Log.d(TAG, "Call handleSessionLost for losing network " + network);
                handleSessionLost(null);
                handleSessionLost(null);
            }
            }
        }

        private void cancelHandleNetworkLostTimeout() {
            if (mScheduledHandleNetworkLostTimeout != null
                    && !mScheduledHandleNetworkLostTimeout.isDone()) {
                // It does not matter what to put in #cancel(boolean), because it is impossible
                // that the task tracked by mScheduledHandleNetworkLostTimeout 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 network lost timeout");
                mScheduledHandleNetworkLostTimeout.cancel(false /* mayInterruptIfRunning */);
            }
        }


        /** Marks the state as FAILED, and disconnects. */
        /** Marks the state as FAILED, and disconnects. */
        private void markFailedAndDisconnect(Exception exception) {
        private void markFailedAndDisconnect(Exception exception) {
@@ -2936,6 +3130,8 @@ public class Vpn {
         * consistency of the Ikev2VpnRunner fields.
         * consistency of the Ikev2VpnRunner fields.
         */
         */
        public void onSessionLost(int token, @Nullable Exception exception) {
        public void onSessionLost(int token, @Nullable Exception exception) {
            Log.d(TAG, "onSessionLost() called for token " + token);

            if (!isActiveToken(token)) {
            if (!isActiveToken(token)) {
                Log.d(TAG, "onSessionLost() called for obsolete token " + token);
                Log.d(TAG, "onSessionLost() called for obsolete token " + token);


@@ -2951,6 +3147,10 @@ public class Vpn {
        }
        }


        private void handleSessionLost(@Nullable Exception exception) {
        private void handleSessionLost(@Nullable Exception exception) {
            // Cancel mScheduledHandleNetworkLostTimeout if the session it is going to terminate is
            // already terminated due to other failures.
            cancelHandleNetworkLostTimeout();

            synchronized (Vpn.this) {
            synchronized (Vpn.this) {
                if (exception instanceof IkeProtocolException) {
                if (exception instanceof IkeProtocolException) {
                    final IkeProtocolException ikeException = (IkeProtocolException) exception;
                    final IkeProtocolException ikeException = (IkeProtocolException) exception;
@@ -3113,6 +3313,8 @@ public class Vpn {
                mSession.kill(); // Kill here to make sure all resources are released immediately
                mSession.kill(); // Kill here to make sure all resources are released immediately
                mSession = null;
                mSession = null;
            }
            }
            mIkeConnectionInfo = null;
            mMobikeEnabled = false;
        }
        }


        /**
        /**
+19 −2
Original line number Original line Diff line number Diff line
@@ -68,6 +68,7 @@ import android.net.ipsec.ike.IkeRfc822AddrIdentification;
import android.net.ipsec.ike.IkeSaProposal;
import android.net.ipsec.ike.IkeSaProposal;
import android.net.ipsec.ike.IkeSessionCallback;
import android.net.ipsec.ike.IkeSessionCallback;
import android.net.ipsec.ike.IkeSessionConfiguration;
import android.net.ipsec.ike.IkeSessionConfiguration;
import android.net.ipsec.ike.IkeSessionConnectionInfo;
import android.net.ipsec.ike.IkeSessionParams;
import android.net.ipsec.ike.IkeSessionParams;
import android.net.ipsec.ike.IkeTrafficSelector;
import android.net.ipsec.ike.IkeTrafficSelector;
import android.net.ipsec.ike.TunnelModeChildSessionParams;
import android.net.ipsec.ike.TunnelModeChildSessionParams;
@@ -107,6 +108,7 @@ public class VpnIkev2Utils {
                new IkeSessionParams.Builder(context)
                new IkeSessionParams.Builder(context)
                        .setServerHostname(profile.getServerAddr())
                        .setServerHostname(profile.getServerAddr())
                        .setNetwork(network)
                        .setNetwork(network)
                        .addIkeOption(IkeSessionParams.IKE_OPTION_MOBIKE)
                        .setLocalIdentification(localId)
                        .setLocalIdentification(localId)
                        .setRemoteIdentification(remoteId);
                        .setRemoteIdentification(remoteId);
        setIkeAuth(profile, ikeOptionsBuilder);
        setIkeAuth(profile, ikeOptionsBuilder);
@@ -309,7 +311,7 @@ public class VpnIkev2Utils {
        @Override
        @Override
        public void onOpened(@NonNull IkeSessionConfiguration ikeSessionConfig) {
        public void onOpened(@NonNull IkeSessionConfiguration ikeSessionConfig) {
            Log.d(mTag, "IkeOpened for token " + mToken);
            Log.d(mTag, "IkeOpened for token " + mToken);
            // Nothing to do here.
            mCallback.onIkeOpened(mToken, ikeSessionConfig);
        }
        }


        @Override
        @Override
@@ -329,6 +331,13 @@ public class VpnIkev2Utils {
            Log.d(mTag, "IkeError for token " + mToken, exception);
            Log.d(mTag, "IkeError for token " + mToken, exception);
            // Non-fatal, log and continue.
            // Non-fatal, log and continue.
        }
        }

        @Override
        public void onIkeSessionConnectionInfoChanged(
                @NonNull IkeSessionConnectionInfo connectionInfo) {
            Log.d(mTag, "onIkeSessionConnectionInfoChanged for token " + mToken);
            mCallback.onIkeConnectionInfoChanged(mToken, connectionInfo);
        }
    }
    }


    static class ChildSessionCallbackImpl implements ChildSessionCallback {
    static class ChildSessionCallbackImpl implements ChildSessionCallback {
@@ -373,6 +382,14 @@ public class VpnIkev2Utils {
            // IKE library.
            // IKE library.
            Log.d(mTag, "ChildTransformDeleted; Direction: " + direction + "; for token " + mToken);
            Log.d(mTag, "ChildTransformDeleted; Direction: " + direction + "; for token " + mToken);
        }
        }

        @Override
        public void onIpSecTransformsMigrated(
                @NonNull IpSecTransform inIpSecTransform,
                @NonNull IpSecTransform outIpSecTransform) {
            Log.d(mTag, "ChildTransformsMigrated; token " + mToken);
            mCallback.onChildMigrated(mToken, inIpSecTransform, outIpSecTransform);
        }
    }
    }


    static class Ikev2VpnNetworkCallback extends NetworkCallback {
    static class Ikev2VpnNetworkCallback extends NetworkCallback {
@@ -389,7 +406,7 @@ public class VpnIkev2Utils {


        @Override
        @Override
        public void onAvailable(@NonNull Network network) {
        public void onAvailable(@NonNull Network network) {
            Log.d(mTag, "Starting IKEv2/IPsec session on new network: " + network);
            Log.d(mTag, "onAvailable called for network: " + network);
            mExecutor.execute(() -> mCallback.onDefaultNetworkChanged(network));
            mExecutor.execute(() -> mCallback.onDefaultNetworkChanged(network));
        }
        }