diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 5ec57cd2d9f70063e6f834155b292f3c1eac7ef6..e053c53c7afd05d0bafaed7bebf8406fe0eca819 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -2586,13 +2586,13 @@ public class Vpn {
 
         void onDefaultNetworkLinkPropertiesChanged(@NonNull LinkProperties lp);
 
-        void onChildOpened(
-                @NonNull Network network, @NonNull ChildSessionConfiguration childConfig);
+        void onDefaultNetworkLost(@NonNull Network network);
 
-        void onChildTransformCreated(
-                @NonNull Network network, @NonNull IpSecTransform transform, int direction);
+        void onChildOpened(int token, @NonNull ChildSessionConfiguration childConfig);
 
-        void onSessionLost(@NonNull Network network, @Nullable Exception exception);
+        void onChildTransformCreated(int token, @NonNull IpSecTransform transform, int direction);
+
+        void onSessionLost(int token, @Nullable Exception exception);
     }
 
     /**
@@ -2639,6 +2639,13 @@ public class Vpn {
         /** Signal to ensure shutdown is honored even if a new Network is connected. */
         private boolean mIsRunning = true;
 
+        /**
+         * The token used by the primary/current/active IKE session.
+         *
+         * <p>This token MUST be updated when the VPN switches to use a new IKE session.
+         */
+        private int mCurrentToken = -1;
+
         @Nullable private IpSecTunnelInterface mTunnelIface;
         @Nullable private IkeSession mSession;
         @Nullable private Network mActiveNetwork;
@@ -2692,22 +2699,25 @@ public class Vpn {
             return Objects.equals(mActiveNetwork, network) && mIsRunning;
         }
 
+        private boolean isActiveToken(int token) {
+            return (mCurrentToken == token) && mIsRunning;
+        }
+
         /**
          * Called when an IKE Child session has been opened, signalling completion of the startup.
          *
          * <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 onChildOpened(
-                @NonNull Network network, @NonNull ChildSessionConfiguration childConfig) {
-            if (!isActiveNetwork(network)) {
-                Log.d(TAG, "onOpened called for obsolete network " + network);
+        public void onChildOpened(int token, @NonNull ChildSessionConfiguration childConfig) {
+            if (!isActiveToken(token)) {
+                Log.d(TAG, "onChildOpened called for obsolete token " + token);
 
                 // Do nothing; this signals that either: (1) a new/better Network was found,
-                // and the Ikev2VpnRunner has switched to it in onDefaultNetworkChanged, or (2) this
-                // IKE session was already shut down (exited, or an error was encountered somewhere
-                // else). In both cases, all resources and sessions are torn down via
-                // resetIkeState().
+                // and the Ikev2VpnRunner has switched to it by restarting a new IKE session in
+                // onDefaultNetworkChanged, or (2) this IKE session was already shut down (exited,
+                // or an error was encountered somewhere else). In both cases, all resources and
+                // sessions are torn down via resetIkeState().
                 return;
             }
 
@@ -2744,7 +2754,7 @@ public class Vpn {
                     mConfig.dnsServers.clear();
                     mConfig.dnsServers.addAll(dnsAddrStrings);
 
-                    mConfig.underlyingNetworks = new Network[] {network};
+                    mConfig.underlyingNetworks = new Network[] {mActiveNetwork};
 
                     mConfig.disallowedApplications = getAppExclusionList(mPackage);
 
@@ -2760,7 +2770,8 @@ public class Vpn {
                         return; // Link properties are already sent.
                     } else {
                         // Underlying networks also set in agentConnect()
-                        networkAgent.setUnderlyingNetworks(Collections.singletonList(network));
+                        networkAgent.setUnderlyingNetworks(
+                                Collections.singletonList(mActiveNetwork));
                     }
 
                     lp = makeLinkProperties(); // Accesses VPN instance fields; must be locked
@@ -2768,8 +2779,8 @@ public class Vpn {
 
                 networkAgent.sendLinkProperties(lp);
             } catch (Exception e) {
-                Log.d(TAG, "Error in ChildOpened for network " + network, e);
-                onSessionLost(network, e);
+                Log.d(TAG, "Error in ChildOpened for token " + token, e);
+                onSessionLost(token, e);
             }
         }
 
@@ -2781,15 +2792,15 @@ public class Vpn {
          * consistency of the Ikev2VpnRunner fields.
          */
         public void onChildTransformCreated(
-                @NonNull Network network, @NonNull IpSecTransform transform, int direction) {
-            if (!isActiveNetwork(network)) {
-                Log.d(TAG, "ChildTransformCreated for obsolete network " + network);
+                int token, @NonNull IpSecTransform transform, int direction) {
+            if (!isActiveToken(token)) {
+                Log.d(TAG, "ChildTransformCreated for obsolete token " + token);
 
                 // Do nothing; this signals that either: (1) a new/better Network was found,
-                // and the Ikev2VpnRunner has switched to it in onDefaultNetworkChanged, or (2) this
-                // IKE session was already shut down (exited, or an error was encountered somewhere
-                // else). In both cases, all resources and sessions are torn down via
-                // resetIkeState().
+                // and the Ikev2VpnRunner has switched to it by restarting a new IKE session in
+                // onDefaultNetworkChanged, or (2) this IKE session was already shut down (exited,
+                // or an error was encountered somewhere else). In both cases, all resources and
+                // sessions are torn down via resetIkeState().
                 return;
             }
 
@@ -2798,8 +2809,8 @@ public class Vpn {
                 // them alive for us
                 mIpSecManager.applyTunnelModeTransform(mTunnelIface, direction, transform);
             } catch (IOException e) {
-                Log.d(TAG, "Transform application failed for network " + network, e);
-                onSessionLost(network, e);
+                Log.d(TAG, "Transform application failed for token " + token, e);
+                onSessionLost(token, e);
             }
         }
 
@@ -2857,19 +2868,21 @@ public class Vpn {
                                 network);
                 NetdUtils.setInterfaceUp(mNetd, mTunnelIface.getInterfaceName());
 
-                mSession = mIkev2SessionCreator.createIkeSession(
-                        mContext,
-                        ikeSessionParams,
-                        childSessionParams,
-                        mExecutor,
-                        new VpnIkev2Utils.IkeSessionCallbackImpl(
-                                TAG, IkeV2VpnRunner.this, network),
-                        new VpnIkev2Utils.ChildSessionCallbackImpl(
-                                TAG, IkeV2VpnRunner.this, network));
-                Log.d(TAG, "Ike Session started for network " + network);
+                final int token = ++mCurrentToken;
+                mSession =
+                        mIkev2SessionCreator.createIkeSession(
+                                mContext,
+                                ikeSessionParams,
+                                childSessionParams,
+                                mExecutor,
+                                new VpnIkev2Utils.IkeSessionCallbackImpl(
+                                        TAG, IkeV2VpnRunner.this, token),
+                                new VpnIkev2Utils.ChildSessionCallbackImpl(
+                                        TAG, IkeV2VpnRunner.this, token));
+                Log.d(TAG, "IKE session started for token " + token);
             } catch (Exception e) {
-                Log.i(TAG, "Setup failed for network " + network + ". Aborting", e);
-                onSessionLost(network, e);
+                Log.i(TAG, "Setup failed for token " + mCurrentToken + ". Aborting", e);
+                onSessionLost(mCurrentToken, e);
             }
         }
 
@@ -2883,6 +2896,29 @@ public class Vpn {
             mUnderlyingLinkProperties = lp;
         }
 
+        /**
+         * Handles loss of the default underlying network
+         *
+         * <p>The 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
+         * consistency of the Ikev2VpnRunner fields.
+         */
+        public void onDefaultNetworkLost(@NonNull Network network) {
+            if (!isActiveNetwork(network)) {
+                Log.d(TAG, "onDefaultNetworkLost called for obsolete network " + network);
+
+                // Do nothing; this signals that either: (1) a new/better Network was found,
+                // and the Ikev2VpnRunner has switched to it by restarting a new IKE session in
+                // onDefaultNetworkChanged, or (2) this IKE session was already shut down (exited,
+                // or an error was encountered somewhere else). In both cases, all resources and
+                // sessions are torn down via resetIkeState().
+                return;
+            }
+
+            handleSessionLost(null);
+        }
+
         /** Marks the state as FAILED, and disconnects. */
         private void markFailedAndDisconnect(Exception exception) {
             synchronized (Vpn.this) {
@@ -2901,18 +2937,22 @@ public class Vpn {
          * <p>This method MUST always be called on the mExecutor thread in order to ensure
          * consistency of the Ikev2VpnRunner fields.
          */
-        public void onSessionLost(@NonNull Network network, @Nullable Exception exception) {
-            if (!isActiveNetwork(network)) {
-                Log.d(TAG, "onSessionLost() called for obsolete network " + network);
+        public void onSessionLost(int token, @Nullable Exception exception) {
+            if (!isActiveToken(token)) {
+                Log.d(TAG, "onSessionLost() called for obsolete token " + token);
 
                 // Do nothing; this signals that either: (1) a new/better Network was found,
-                // and the Ikev2VpnRunner has switched to it in onDefaultNetworkChanged, or (2) this
-                // IKE session was already shut down (exited, or an error was encountered somewhere
-                // else). In both cases, all resources and sessions are torn down via
-                // onSessionLost() and resetIkeState().
+                // and the Ikev2VpnRunner has switched to it by restarting a new IKE session in
+                // onDefaultNetworkChanged, or (2) this IKE session was already shut down (exited,
+                // or an error was encountered somewhere else). In both cases, all resources and
+                // sessions are torn down via resetIkeState().
                 return;
             }
 
+            handleSessionLost(exception);
+        }
+
+        private void handleSessionLost(@Nullable Exception exception) {
             synchronized (Vpn.this) {
                 if (exception instanceof IkeProtocolException) {
                     final IkeProtocolException ikeException = (IkeProtocolException) exception;
@@ -3030,7 +3070,7 @@ public class Vpn {
 
             // Close all obsolete state, but keep VPN alive incase a usable network comes up.
             // (Mirrors VpnService behavior)
-            Log.d(TAG, "Resetting state for network: " + network);
+            Log.d(TAG, "Resetting state for token: " + mCurrentToken);
 
             synchronized (Vpn.this) {
                 // Since this method handles non-fatal errors only, set mInterface to null to
diff --git a/services/core/java/com/android/server/connectivity/VpnIkev2Utils.java b/services/core/java/com/android/server/connectivity/VpnIkev2Utils.java
index e1e488db679f78530bf43812d2226c46ee85a519..3e49afac317b03b17e012bf0d5cba8060e6f27f9 100644
--- a/services/core/java/com/android/server/connectivity/VpnIkev2Utils.java
+++ b/services/core/java/com/android/server/connectivity/VpnIkev2Utils.java
@@ -298,35 +298,35 @@ public class VpnIkev2Utils {
     static class IkeSessionCallbackImpl implements IkeSessionCallback {
         private final String mTag;
         private final Vpn.IkeV2VpnRunnerCallback mCallback;
-        private final Network mNetwork;
+        private final int mToken;
 
-        IkeSessionCallbackImpl(String tag, Vpn.IkeV2VpnRunnerCallback callback, Network network) {
+        IkeSessionCallbackImpl(String tag, Vpn.IkeV2VpnRunnerCallback callback, int token) {
             mTag = tag;
             mCallback = callback;
-            mNetwork = network;
+            mToken = token;
         }
 
         @Override
         public void onOpened(@NonNull IkeSessionConfiguration ikeSessionConfig) {
-            Log.d(mTag, "IkeOpened for network " + mNetwork);
+            Log.d(mTag, "IkeOpened for token " + mToken);
             // Nothing to do here.
         }
 
         @Override
         public void onClosed() {
-            Log.d(mTag, "IkeClosed for network " + mNetwork);
-            mCallback.onSessionLost(mNetwork, null); // Server requested session closure. Retry?
+            Log.d(mTag, "IkeClosed for token " + mToken);
+            mCallback.onSessionLost(mToken, null); // Server requested session closure. Retry?
         }
 
         @Override
         public void onClosedExceptionally(@NonNull IkeException exception) {
-            Log.d(mTag, "IkeClosedExceptionally for network " + mNetwork, exception);
-            mCallback.onSessionLost(mNetwork, exception);
+            Log.d(mTag, "IkeClosedExceptionally for token " + mToken, exception);
+            mCallback.onSessionLost(mToken, exception);
         }
 
         @Override
         public void onError(@NonNull IkeProtocolException exception) {
-            Log.d(mTag, "IkeError for network " + mNetwork, exception);
+            Log.d(mTag, "IkeError for token " + mToken, exception);
             // Non-fatal, log and continue.
         }
     }
@@ -334,36 +334,36 @@ public class VpnIkev2Utils {
     static class ChildSessionCallbackImpl implements ChildSessionCallback {
         private final String mTag;
         private final Vpn.IkeV2VpnRunnerCallback mCallback;
-        private final Network mNetwork;
+        private final int mToken;
 
-        ChildSessionCallbackImpl(String tag, Vpn.IkeV2VpnRunnerCallback callback, Network network) {
+        ChildSessionCallbackImpl(String tag, Vpn.IkeV2VpnRunnerCallback callback, int token) {
             mTag = tag;
             mCallback = callback;
-            mNetwork = network;
+            mToken = token;
         }
 
         @Override
         public void onOpened(@NonNull ChildSessionConfiguration childConfig) {
-            Log.d(mTag, "ChildOpened for network " + mNetwork);
-            mCallback.onChildOpened(mNetwork, childConfig);
+            Log.d(mTag, "ChildOpened for token " + mToken);
+            mCallback.onChildOpened(mToken, childConfig);
         }
 
         @Override
         public void onClosed() {
-            Log.d(mTag, "ChildClosed for network " + mNetwork);
-            mCallback.onSessionLost(mNetwork, null);
+            Log.d(mTag, "ChildClosed for token " + mToken);
+            mCallback.onSessionLost(mToken, null);
         }
 
         @Override
         public void onClosedExceptionally(@NonNull IkeException exception) {
-            Log.d(mTag, "ChildClosedExceptionally for network " + mNetwork, exception);
-            mCallback.onSessionLost(mNetwork, exception);
+            Log.d(mTag, "ChildClosedExceptionally for token " + mToken, exception);
+            mCallback.onSessionLost(mToken, exception);
         }
 
         @Override
         public void onIpSecTransformCreated(@NonNull IpSecTransform transform, int direction) {
-            Log.d(mTag, "ChildTransformCreated; Direction: " + direction + "; network " + mNetwork);
-            mCallback.onChildTransformCreated(mNetwork, transform, direction);
+            Log.d(mTag, "ChildTransformCreated; Direction: " + direction + "; token " + mToken);
+            mCallback.onChildTransformCreated(mToken, transform, direction);
         }
 
         @Override
@@ -371,8 +371,7 @@ public class VpnIkev2Utils {
             // Nothing to be done; no references to the IpSecTransform are held by the
             // Ikev2VpnRunner (or this callback class), and this transform will be closed by the
             // IKE library.
-            Log.d(mTag,
-                    "ChildTransformDeleted; Direction: " + direction + "; for network " + mNetwork);
+            Log.d(mTag, "ChildTransformDeleted; Direction: " + direction + "; for token " + mToken);
         }
     }
 
@@ -412,8 +411,8 @@ public class VpnIkev2Utils {
 
         @Override
         public void onLost(@NonNull Network network) {
-            Log.d(mTag, "Tearing down; lost network: " + network);
-            mExecutor.execute(() -> mCallback.onSessionLost(network, null));
+            Log.d(mTag, "onLost called for network: " + network);
+            mExecutor.execute(() -> mCallback.onDefaultNetworkLost(network));
         }
     }