Merge "Add ipsec-module-sdk build target."
diff --git a/api/current.txt b/api/current.txt
index 9673b33..b76a971 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -33,6 +33,11 @@
 
   public abstract static class EapSessionConfig.EapMethodConfig {
     method public int getMethodType();
+    field public static final int EAP_TYPE_AKA = 23; // 0x17
+    field public static final int EAP_TYPE_AKA_PRIME = 50; // 0x32
+    field public static final int EAP_TYPE_MSCHAP_V2 = 26; // 0x1a
+    field public static final int EAP_TYPE_SIM = 18; // 0x12
+    field public static final int EAP_TYPE_TTLS = 21; // 0x15
   }
 
   public static class EapSessionConfig.EapMsChapV2Config extends android.net.eap.EapSessionConfig.EapMethodConfig {
diff --git a/src/java/android/net/eap/EapSessionConfig.java b/src/java/android/net/eap/EapSessionConfig.java
index ff42b6f..2a09cd3 100644
--- a/src/java/android/net/eap/EapSessionConfig.java
+++ b/src/java/android/net/eap/EapSessionConfig.java
@@ -377,7 +377,6 @@
          * @see <a href="https://tools.ietf.org/html/rfc4186">RFC 4186, Extensible Authentication
          *     Protocol Method for Global System for Mobile Communications (GSM) Subscriber Identity
          *     Modules (EAP-SIM)</a>
-         * @hide
          */
         public static final int EAP_TYPE_SIM = 18;
 
@@ -390,7 +389,6 @@
          * @see <a href="https://tools.ietf.org/html/rfc5281">RFC 5281, Extensible Authentication
          *     Protocol Tunneled Transport Layer Security Authenticated Protocol Version 0
          *     (EAP-TTLSv0)</a>
-         * @hide
          */
         public static final int EAP_TYPE_TTLS = 21;
 
@@ -402,7 +400,6 @@
          *
          * @see <a href="https://tools.ietf.org/html/rfc4187">RFC 4187, Extensible Authentication
          *     Protocol Method for 3rd Generation Authentication and Key Agreement (EAP-AKA)</a>
-         * @hide
          */
         public static final int EAP_TYPE_AKA = 23;
 
@@ -414,7 +411,6 @@
          *
          * @see <a href="https://tools.ietf.org/html/draft-kamath-pppext-eap-mschapv2-02">Microsoft
          *     EAP CHAP Extensions Draft (EAP MSCHAPv2)</a>
-         * @hide
          */
         public static final int EAP_TYPE_MSCHAP_V2 = 26;
 
@@ -427,7 +423,6 @@
          * @see <a href="https://tools.ietf.org/html/rfc5448">RFC 5448, Improved Extensible
          *     Authentication Protocol Method for 3rd Generation Authentication and Key Agreement
          *     (EAP-AKA')</a>
-         * @hide
          */
         public static final int EAP_TYPE_AKA_PRIME = 50;
 
diff --git a/tests/cts/src/android/ipsec/ike/cts/IkeSessionDigitalSignatureTest.java b/tests/cts/src/android/ipsec/ike/cts/IkeSessionDigitalSignatureTest.java
index b66316a..b616661 100644
--- a/tests/cts/src/android/ipsec/ike/cts/IkeSessionDigitalSignatureTest.java
+++ b/tests/cts/src/android/ipsec/ike/cts/IkeSessionDigitalSignatureTest.java
@@ -150,7 +150,7 @@
     private IkeSession openIkeSessionWithRemoteAddress(InetAddress remoteAddress) {
         IkeSessionParams ikeParams =
                 new IkeSessionParams.Builder(sContext)
-                        .setNetwork(mTunNetwork)
+                        .setNetwork(mTunNetworkContext.tunNetwork)
                         .setServerHostname(remoteAddress.getHostAddress())
                         .addSaProposal(SaProposalTest.buildIkeSaProposalWithNormalModeCipher())
                         .addSaProposal(SaProposalTest.buildIkeSaProposalWithCombinedModeCipher())
diff --git a/tests/cts/src/android/ipsec/ike/cts/IkeSessionMobikeTest.java b/tests/cts/src/android/ipsec/ike/cts/IkeSessionMobikeTest.java
new file mode 100644
index 0000000..ce27be7
--- /dev/null
+++ b/tests/cts/src/android/ipsec/ike/cts/IkeSessionMobikeTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.ipsec.ike.cts;
+
+import static org.junit.Assert.assertFalse;
+
+import android.net.ipsec.ike.IkeSession;
+import android.net.ipsec.ike.IkeSessionConfiguration;
+import android.net.ipsec.ike.IkeSessionParams;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.InetAddress;
+
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "MANAGE_IPSEC_TUNNELS permission can't be granted to instant apps")
+public class IkeSessionMobikeTest extends IkeSessionPskTestBase {
+    private TunNetworkContext mSecondaryTunNetworkContext;
+
+    private InetAddress mSecondaryLocalAddr;
+
+    private IkeSession mIkeSession;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mSecondaryLocalAddr = getNextAvailableIpv4AddressLocal();
+
+        mSecondaryTunNetworkContext = new TunNetworkContext(mSecondaryLocalAddr);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mSecondaryTunNetworkContext.tearDown();
+
+        if (mIkeSession != null) {
+            mIkeSession.kill();
+        }
+
+        super.tearDown();
+    }
+
+    @Override
+    protected IkeSessionParams getIkeSessionParams(InetAddress remoteAddress) {
+        return createIkeParamsBuilderBase(remoteAddress)
+                .addIkeOption(IkeSessionParams.IKE_OPTION_MOBIKE)
+                .build();
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testSetNetworkWithoutMobikeEnabled() throws Exception {
+        if (!hasTunnelsFeature()) return;
+
+        final String ikeInitResp =
+                "46B8ECA1E0D72A18B45427679F9245D421202220000000000000015022000030"
+                        + "0000002C010100040300000C0100000C800E0080030000080300000203000008"
+                        + "0200000200000008040000022800008800020000A7AA3435D088EC1A2B7C2A47"
+                        + "1FA1B85F1066C9B2006E7C353FB5B5FDBC2A88347ED2C6F5B7A265D03AE34039"
+                        + "6AAC0145CFCC93F8BDB219DDFF22A603B8856A5DC59B6FAB7F17C5660CF38670"
+                        + "8794FC72F273ADEB7A4F316519794AED6F8AB61F95DFB360FAF18C6C8CABE471"
+                        + "6E18FE215348C2E582171A57FC41146B16C4AFE429000024A634B61C0E5C90C6"
+                        + "8D8818B0955B125A9B1DF47BBD18775710792E651083105C2900001C00004004"
+                        + "406FA3C5685A16B9B72C7F2EEE9993462C619ABE2900001C00004005AF905A87"
+                        + "0A32222AA284A7070585601208A282F0290000080000402E290000100000402F"
+                        + "00020003000400050000000800004014";
+        final String IkeAuthRespWithoutMobikeSupport =
+                "46B8ECA1E0D72A18B45427679F9245D42E20232000000001000000EC240000D0"
+                        + "0D06D37198F3F0962DE8170D66F1A9008267F98CDD956D984BDCED2FC7FAF84A"
+                        + "A6664EF25049B46B93C9ED420488E0C172AA6635BF4011C49792EF2B88FE7190"
+                        + "E8859FEEF51724FD20C46E7B9A9C3DC4708EF7005707A18AB747C903ABCEAC5C"
+                        + "6ECF5A5FC13633DCE3844A920ED10EF202F115DBFBB5D6D2D7AB1F34EB08DE7C"
+                        + "A54DCE0A3A582753345CA2D05A0EFDB9DC61E81B2483B7D13EEE0A815D37252C"
+                        + "23D2F29E9C30658227D2BB0C9E1A481EAA80BC6BE9006BEDC13E925A755A0290"
+                        + "AEC4164D29997F52ED7DCC2E";
+
+        // Open IKE Session
+        mIkeSession = openIkeSessionWithTunnelModeChild(mRemoteAddress);
+        performSetupIkeAndFirstChildBlocking(ikeInitResp, IkeAuthRespWithoutMobikeSupport);
+
+        verifyIkeSessionSetupBlocking();
+
+        final IkeSessionConfiguration ikeConfig = mIkeSessionCallback.awaitIkeConfig();
+        assertFalse(ikeConfig.isIkeExtensionEnabled(IkeSessionConfiguration.EXTENSION_TYPE_MOBIKE));
+
+        // manually change network when MOBIKE is not enabled
+        mIkeSession.setNetwork(mSecondaryTunNetworkContext.tunNetwork);
+    }
+
+    /** The MOBIKE spec explicitly disallows Transport mode. */
+    @Test(expected = IllegalArgumentException.class)
+    public void testStartSessionWithMobikeAndTransportMode() {
+        mIkeSession = openIkeSessionWithTransportModeChild(mRemoteAddress);
+    }
+}
diff --git a/tests/cts/src/android/ipsec/ike/cts/IkeSessionMschapV2Test.java b/tests/cts/src/android/ipsec/ike/cts/IkeSessionMschapV2Test.java
index 07584c5..a7a0904 100644
--- a/tests/cts/src/android/ipsec/ike/cts/IkeSessionMschapV2Test.java
+++ b/tests/cts/src/android/ipsec/ike/cts/IkeSessionMschapV2Test.java
@@ -148,7 +148,7 @@
     private IkeSession openIkeSessionWithRemoteAddress(InetAddress remoteAddress) {
         IkeSessionParams ikeParams =
                 new IkeSessionParams.Builder(sContext)
-                        .setNetwork(mTunNetwork)
+                        .setNetwork(mTunNetworkContext.tunNetwork)
                         .setServerHostname(remoteAddress.getHostAddress())
                         .addSaProposal(SaProposalTest.buildIkeSaProposalWithNormalModeCipher())
                         .addSaProposal(SaProposalTest.buildIkeSaProposalWithCombinedModeCipher())
@@ -171,30 +171,30 @@
         // Open IKE Session
         IkeSession ikeSession = openIkeSessionWithRemoteAddress(mRemoteAddress);
         int expectedMsgId = 0;
-        mTunUtils.awaitReqAndInjectResp(
+        mTunNetworkContext.tunUtils.awaitReqAndInjectResp(
                 IKE_DETERMINISTIC_INITIATOR_SPI,
                 expectedMsgId++,
                 false /* expectedUseEncap */,
                 IKE_INIT_RESP);
 
-        mTunUtils.awaitReqAndInjectResp(
+        mTunNetworkContext.tunUtils.awaitReqAndInjectResp(
                 IKE_DETERMINISTIC_INITIATOR_SPI,
                 expectedMsgId++,
                 true /* expectedUseEncap */,
                 IKE_AUTH_RESP_1_FRAG_1,
                 IKE_AUTH_RESP_1_FRAG_2);
 
-        mTunUtils.awaitReqAndInjectResp(
+        mTunNetworkContext.tunUtils.awaitReqAndInjectResp(
                 IKE_DETERMINISTIC_INITIATOR_SPI,
                 expectedMsgId++,
                 true /* expectedUseEncap */,
                 IKE_AUTH_RESP_2);
-        mTunUtils.awaitReqAndInjectResp(
+        mTunNetworkContext.tunUtils.awaitReqAndInjectResp(
                 IKE_DETERMINISTIC_INITIATOR_SPI,
                 expectedMsgId++,
                 true /* expectedUseEncap */,
                 IKE_AUTH_RESP_3);
-        mTunUtils.awaitReqAndInjectResp(
+        mTunNetworkContext.tunUtils.awaitReqAndInjectResp(
                 IKE_DETERMINISTIC_INITIATOR_SPI,
                 expectedMsgId++,
                 true /* expectedUseEncap */,
diff --git a/tests/cts/src/android/ipsec/ike/cts/IkeSessionParamsTest.java b/tests/cts/src/android/ipsec/ike/cts/IkeSessionParamsTest.java
index 3f26788..e4d11fe 100644
--- a/tests/cts/src/android/ipsec/ike/cts/IkeSessionParamsTest.java
+++ b/tests/cts/src/android/ipsec/ike/cts/IkeSessionParamsTest.java
@@ -49,7 +49,6 @@
 
 import com.android.internal.net.ipsec.test.ike.testutils.CertUtils;
 
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -110,8 +109,7 @@
 
     @Before
     public void setUp() throws Exception {
-        // This address is never used except for setting up the test network
-        setUpTestNetwork(IPV4_ADDRESS_LOCAL);
+        super.setUp();
 
         mServerCaCert = CertUtils.createCertFromPemFile("server-a-self-signed-ca.pem");
         mClientEndCert = CertUtils.createCertFromPemFile("client-a-end-cert.pem");
@@ -122,11 +120,6 @@
         mClientPrivateKey = CertUtils.createRsaPrivateKeyFromKeyFile("client-a-private-key.key");
     }
 
-    @After
-    public void tearDown() throws Exception {
-        tearDownTestNetwork();
-    }
-
     private static EapSessionConfig.Builder createEapOnlySafeMethodsBuilder() {
         return new EapSessionConfig.Builder()
                 .setEapIdentity(EAP_IDENTITY)
@@ -144,7 +137,7 @@
      */
     private IkeSessionParams.Builder createIkeParamsBuilderMinimum() {
         return new IkeSessionParams.Builder(sContext)
-                .setNetwork(mTunNetwork)
+                .setNetwork(mTunNetworkContext.tunNetwork)
                 .setServerHostname(IPV4_ADDRESS_REMOTE.getHostAddress())
                 .addSaProposal(SA_PROPOSAL)
                 .setLocalIdentification(LOCAL_ID)
@@ -158,7 +151,7 @@
      * @see #createIkeParamsBuilderMinimum
      */
     private void verifyIkeParamsMinimum(IkeSessionParams sessionParams) {
-        assertEquals(mTunNetwork, sessionParams.getNetwork());
+        assertEquals(mTunNetworkContext.tunNetwork, sessionParams.getNetwork());
         assertEquals(IPV4_ADDRESS_REMOTE.getHostAddress(), sessionParams.getServerHostname());
         assertEquals(Arrays.asList(SA_PROPOSAL), sessionParams.getSaProposals());
         assertEquals(LOCAL_ID, sessionParams.getLocalIdentification());
@@ -292,7 +285,7 @@
      */
     private IkeSessionParams.Builder createIkeParamsBuilderMinimumWithoutAuth() {
         return new IkeSessionParams.Builder(sContext)
-                .setNetwork(mTunNetwork)
+                .setNetwork(mTunNetworkContext.tunNetwork)
                 .setServerHostname(IPV4_ADDRESS_REMOTE.getHostAddress())
                 .addSaProposal(SA_PROPOSAL)
                 .setLocalIdentification(LOCAL_ID)
@@ -306,13 +299,22 @@
      * @see #createIkeParamsBuilderMinimumWithoutAuth
      */
     private void verifyIkeParamsMinimumWithoutAuth(IkeSessionParams sessionParams) {
-        assertEquals(mTunNetwork, sessionParams.getNetwork());
+        assertEquals(mTunNetworkContext.tunNetwork, sessionParams.getNetwork());
         assertEquals(IPV4_ADDRESS_REMOTE.getHostAddress(), sessionParams.getServerHostname());
         assertEquals(Arrays.asList(SA_PROPOSAL), sessionParams.getSaProposals());
         assertEquals(LOCAL_ID, sessionParams.getLocalIdentification());
         assertEquals(REMOTE_ID, sessionParams.getRemoteIdentification());
     }
 
+    private void verifyIkeParamsWithPsk(IkeSessionParams sessionParams) {
+        IkeAuthConfig localConfig = sessionParams.getLocalAuthConfig();
+        assertTrue(localConfig instanceof IkeAuthPskConfig);
+        assertArrayEquals(IKE_PSK, ((IkeAuthPskConfig) localConfig).getPsk());
+        IkeAuthConfig remoteConfig = sessionParams.getRemoteAuthConfig();
+        assertTrue(remoteConfig instanceof IkeAuthPskConfig);
+        assertArrayEquals(IKE_PSK, ((IkeAuthPskConfig) remoteConfig).getPsk());
+    }
+
     @Test
     public void testBuildWithPsk() throws Exception {
         IkeSessionParams sessionParams =
@@ -320,12 +322,21 @@
 
         verifyIkeParamsMinimumWithoutAuth(sessionParams);
 
-        IkeAuthConfig localConfig = sessionParams.getLocalAuthConfig();
-        assertTrue(localConfig instanceof IkeAuthPskConfig);
-        assertArrayEquals(IKE_PSK, ((IkeAuthPskConfig) localConfig).getPsk());
-        IkeAuthConfig remoteConfig = sessionParams.getRemoteAuthConfig();
-        assertTrue(remoteConfig instanceof IkeAuthPskConfig);
-        assertArrayEquals(IKE_PSK, ((IkeAuthPskConfig) remoteConfig).getPsk());
+        verifyIkeParamsWithPsk(sessionParams);
+    }
+
+    @Test
+    public void testBuildWithPskMobikeEnabled() throws Exception {
+        IkeSessionParams sessionParams =
+                createIkeParamsBuilderMinimumWithoutAuth()
+                        .setAuthPsk(IKE_PSK)
+                        .addIkeOption(IkeSessionParams.IKE_OPTION_MOBIKE)
+                        .build();
+
+        verifyIkeParamsMinimumWithoutAuth(sessionParams);
+
+        verifyIkeParamsWithPsk(sessionParams);
+        assertTrue(sessionParams.hasIkeOption(IkeSessionParams.IKE_OPTION_MOBIKE));
     }
 
     @Test
diff --git a/tests/cts/src/android/ipsec/ike/cts/IkeSessionPskTest.java b/tests/cts/src/android/ipsec/ike/cts/IkeSessionPskTest.java
index b9b710d..9e8e722 100644
--- a/tests/cts/src/android/ipsec/ike/cts/IkeSessionPskTest.java
+++ b/tests/cts/src/android/ipsec/ike/cts/IkeSessionPskTest.java
@@ -16,7 +16,6 @@
 
 package android.ipsec.ike.cts;
 
-import static android.app.AppOpsManager.OP_MANAGE_IPSEC_TUNNELS;
 import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED;
 import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_NO_PROPOSAL_CHOSEN;
 import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_TS_UNACCEPTABLE;
@@ -25,8 +24,6 @@
 import static org.junit.Assert.assertEquals;
 
 import android.net.LinkAddress;
-import android.net.ipsec.ike.ChildSessionParams;
-import android.net.ipsec.ike.IkeFqdnIdentification;
 import android.net.ipsec.ike.IkeSession;
 import android.net.ipsec.ike.IkeSessionParams;
 import android.net.ipsec.ike.exceptions.IkeProtocolException;
@@ -34,8 +31,6 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -45,7 +40,7 @@
 
 @RunWith(AndroidJUnit4.class)
 @AppModeFull(reason = "MANAGE_IPSEC_TUNNELS permission can't be granted to instant apps")
-public class IkeSessionPskTest extends IkeSessionTestBase {
+public class IkeSessionPskTest extends IkeSessionPskTestBase {
     // Test vectors for success workflow
     private static final String SUCCESS_IKE_INIT_RESP =
             "46B8ECA1E0D72A18B45427679F9245D421202220000000000000015022000030"
@@ -85,47 +80,9 @@
                     + "9352D71100777B00ABCC6BD7DBEA697827FFAAA48DF9A54D1D68161939F5DC8"
                     + "6743A7CEB2BE34AC00095A5B8";
 
-    private IkeSession openIkeSessionWithTunnelModeChild(InetAddress remoteAddress) {
-        return openIkeSession(remoteAddress, buildTunnelModeChildSessionParams());
-    }
-
-    private IkeSession openIkeSessionWithTransportModeChild(InetAddress remoteAddress) {
-        return openIkeSession(remoteAddress, buildTransportModeChildParamsWithDefaultTs());
-    }
-
-    private IkeSession openIkeSession(InetAddress remoteAddress, ChildSessionParams childParams) {
-        IkeSessionParams ikeParams =
-                new IkeSessionParams.Builder(sContext)
-                        .setNetwork(mTunNetwork)
-                        .setServerHostname(remoteAddress.getHostAddress())
-                        .addSaProposal(SaProposalTest.buildIkeSaProposalWithNormalModeCipher())
-                        .addSaProposal(SaProposalTest.buildIkeSaProposalWithCombinedModeCipher())
-                        .setLocalIdentification(new IkeFqdnIdentification(LOCAL_HOSTNAME))
-                        .setRemoteIdentification(new IkeFqdnIdentification(REMOTE_HOSTNAME))
-                        .setAuthPsk(IKE_PSK)
-                        .build();
-        return new IkeSession(
-                sContext,
-                ikeParams,
-                childParams,
-                mUserCbExecutor,
-                mIkeSessionCallback,
-                mFirstChildSessionCallback);
-    }
-
-    @BeforeClass
-    public static void setUpTunnelPermissionBeforeClass() throws Exception {
-        // Under normal circumstances, the MANAGE_IPSEC_TUNNELS appop would be auto-granted, and
-        // a standard permission is insufficient. So we shell out the appop, to give us the
-        // right appop permissions.
-        setAppOp(OP_MANAGE_IPSEC_TUNNELS, true);
-    }
-
-    // This method is guaranteed to run in subclasses and will run after subclasses' @AfterClass
-    // methods.
-    @AfterClass
-    public static void tearDownTunnelPermissionAfterClass() throws Exception {
-        setAppOp(OP_MANAGE_IPSEC_TUNNELS, false);
+    @Override
+    protected IkeSessionParams getIkeSessionParams(InetAddress remoteAddress) {
+        return createIkeParamsBuilderBase(remoteAddress).build();
     }
 
     @Test
@@ -155,7 +112,7 @@
         // Open additional Child Session
         TestChildSessionCallback additionalChildCb = new TestChildSessionCallback();
         ikeSession.openChildSession(buildTunnelModeChildSessionParams(), additionalChildCb);
-        mTunUtils.awaitReqAndInjectResp(
+        mTunNetworkContext.tunUtils.awaitReqAndInjectResp(
                 IKE_DETERMINISTIC_INITIATOR_SPI,
                 expectedMsgId++,
                 true /* expectedUseEncap */,
@@ -175,7 +132,7 @@
 
         // Close additional Child Session
         ikeSession.closeChildSession(additionalChildCb);
-        mTunUtils.awaitReqAndInjectResp(
+        mTunNetworkContext.tunUtils.awaitReqAndInjectResp(
                 IKE_DETERMINISTIC_INITIATOR_SPI,
                 expectedMsgId++,
                 true /* expectedUseEncap */,
@@ -227,8 +184,8 @@
 
         // Teardown current test network that uses IPv4 address and set up new network with IPv6
         // address.
-        tearDownTestNetwork();
-        setUpTestNetwork(mLocalAddress);
+        mTunNetworkContext.tearDown();
+        mTunNetworkContext = new TunNetworkContext(mLocalAddress);
 
         // Open IKE Session
         IkeSession ikeSession = openIkeSessionWithTunnelModeChild(mRemoteAddress);
@@ -283,7 +240,7 @@
         // Open IKE Session
         IkeSession ikeSession = openIkeSessionWithTransportModeChild(mRemoteAddress);
         int expectedMsgId = 0;
-        mTunUtils.awaitReqAndInjectResp(
+        mTunNetworkContext.tunUtils.awaitReqAndInjectResp(
                 IKE_DETERMINISTIC_INITIATOR_SPI,
                 expectedMsgId++,
                 false /* expectedUseEncap */,
diff --git a/tests/cts/src/android/ipsec/ike/cts/IkeSessionPskTestBase.java b/tests/cts/src/android/ipsec/ike/cts/IkeSessionPskTestBase.java
new file mode 100644
index 0000000..580c0e5
--- /dev/null
+++ b/tests/cts/src/android/ipsec/ike/cts/IkeSessionPskTestBase.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.ipsec.ike.cts;
+
+import static android.app.AppOpsManager.OP_MANAGE_IPSEC_TUNNELS;
+
+import android.net.ipsec.ike.ChildSessionParams;
+import android.net.ipsec.ike.IkeFqdnIdentification;
+import android.net.ipsec.ike.IkeSession;
+import android.net.ipsec.ike.IkeSessionParams;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+
+import java.net.InetAddress;
+
+abstract class IkeSessionPskTestBase extends IkeSessionTestBase {
+    @BeforeClass
+    public static void setUpTunnelPermissionBeforeClass() throws Exception {
+        // Under normal circumstances, the MANAGE_IPSEC_TUNNELS appop would be auto-granted, and
+        // a standard permission is insufficient. So we shell out the appop, to give us the
+        // right appop permissions.
+        setAppOp(OP_MANAGE_IPSEC_TUNNELS, true);
+    }
+
+    // This method is guaranteed to run in subclasses and will run after subclasses' @AfterClass
+    // methods.
+    @AfterClass
+    public static void tearDownTunnelPermissionAfterClass() throws Exception {
+        setAppOp(OP_MANAGE_IPSEC_TUNNELS, false);
+    }
+
+    protected IkeSession openIkeSessionWithTunnelModeChild(InetAddress remoteAddress) {
+        return openIkeSession(remoteAddress, buildTunnelModeChildSessionParams());
+    }
+
+    protected IkeSession openIkeSessionWithTransportModeChild(InetAddress remoteAddress) {
+        return openIkeSession(remoteAddress, buildTransportModeChildParamsWithDefaultTs());
+    }
+
+    protected IkeSessionParams.Builder createIkeParamsBuilderBase(InetAddress remoteAddress) {
+        return new IkeSessionParams.Builder(sContext)
+                .setNetwork(mTunNetworkContext.tunNetwork)
+                .setServerHostname(remoteAddress.getHostAddress())
+                .addSaProposal(SaProposalTest.buildIkeSaProposalWithNormalModeCipher())
+                .addSaProposal(SaProposalTest.buildIkeSaProposalWithCombinedModeCipher())
+                .setLocalIdentification(new IkeFqdnIdentification(LOCAL_HOSTNAME))
+                .setRemoteIdentification(new IkeFqdnIdentification(REMOTE_HOSTNAME))
+                .setAuthPsk(IKE_PSK);
+    }
+
+    protected abstract IkeSessionParams getIkeSessionParams(InetAddress remoteAddress);
+
+    private IkeSession openIkeSession(InetAddress remoteAddress, ChildSessionParams childParams) {
+        return new IkeSession(
+                sContext,
+                getIkeSessionParams(remoteAddress),
+                childParams,
+                mUserCbExecutor,
+                mIkeSessionCallback,
+                mFirstChildSessionCallback);
+    }
+}
diff --git a/tests/cts/src/android/ipsec/ike/cts/IkeSessionRekeyTest.java b/tests/cts/src/android/ipsec/ike/cts/IkeSessionRekeyTest.java
index 0ca12ad..4ec32f8 100644
--- a/tests/cts/src/android/ipsec/ike/cts/IkeSessionRekeyTest.java
+++ b/tests/cts/src/android/ipsec/ike/cts/IkeSessionRekeyTest.java
@@ -53,7 +53,7 @@
     private IkeSession openIkeSessionWithRemoteAddress(InetAddress remoteAddress) {
         IkeSessionParams ikeParams =
                 new IkeSessionParams.Builder(sContext)
-                        .setNetwork(mTunNetwork)
+                        .setNetwork(mTunNetworkContext.tunNetwork)
                         .setServerHostname(remoteAddress.getHostAddress())
                         .addSaProposal(SaProposalTest.buildIkeSaProposalWithNormalModeCipher())
                         .addSaProposal(SaProposalTest.buildIkeSaProposalWithCombinedModeCipher())
@@ -149,11 +149,13 @@
         verifyCreateIpSecTransformPair(firstTransformRecordA, firstTransformRecordB);
 
         // Inject rekey IKE requests
-        mTunUtils.injectPacket(buildInboundPkt(localRemotePorts, rekeyIkeCreateReq));
-        mTunUtils.awaitResp(
+        mTunNetworkContext.tunUtils.injectPacket(
+                buildInboundPkt(localRemotePorts, rekeyIkeCreateReq));
+        mTunNetworkContext.tunUtils.awaitResp(
                 IKE_DETERMINISTIC_INITIATOR_SPI, expectedRespMsgId++, true /* expectedUseEncap */);
-        mTunUtils.injectPacket(buildInboundPkt(localRemotePorts, rekeyIkeDeleteReq));
-        mTunUtils.awaitResp(
+        mTunNetworkContext.tunUtils.injectPacket(
+                buildInboundPkt(localRemotePorts, rekeyIkeDeleteReq));
+        mTunNetworkContext.tunUtils.awaitResp(
                 IKE_DETERMINISTIC_INITIATOR_SPI, expectedRespMsgId++, true /* expectedUseEncap */);
 
         // IKE has been rekeyed, reset message IDs
@@ -161,8 +163,8 @@
         expectedRespMsgId = 0;
 
         // Inject delete IKE request
-        mTunUtils.injectPacket(buildInboundPkt(localRemotePorts, deleteIkeReq));
-        mTunUtils.awaitResp(
+        mTunNetworkContext.tunUtils.injectPacket(buildInboundPkt(localRemotePorts, deleteIkeReq));
+        mTunNetworkContext.tunUtils.awaitResp(
                 newIkeDeterministicInitSpi, expectedRespMsgId++, true /* expectedUseEncap */);
 
         verifyDeleteIpSecTransformPair(
@@ -236,11 +238,13 @@
         verifyCreateIpSecTransformPair(oldTransformRecordA, oldTransformRecordB);
 
         // Inject rekey Child requests
-        mTunUtils.injectPacket(buildInboundPkt(localRemotePorts, rekeyChildCreateReq));
-        mTunUtils.awaitResp(
+        mTunNetworkContext.tunUtils.injectPacket(
+                buildInboundPkt(localRemotePorts, rekeyChildCreateReq));
+        mTunNetworkContext.tunUtils.awaitResp(
                 IKE_DETERMINISTIC_INITIATOR_SPI, expectedRespMsgId++, true /* expectedUseEncap */);
-        mTunUtils.injectPacket(buildInboundPkt(localRemotePorts, rekeyChildDeleteReq));
-        mTunUtils.awaitResp(
+        mTunNetworkContext.tunUtils.injectPacket(
+                buildInboundPkt(localRemotePorts, rekeyChildDeleteReq));
+        mTunNetworkContext.tunUtils.awaitResp(
                 IKE_DETERMINISTIC_INITIATOR_SPI, expectedRespMsgId++, true /* expectedUseEncap */);
 
         // Verify IpSecTransforms are renewed
@@ -253,8 +257,8 @@
                 mFirstChildSessionCallback, oldTransformRecordA, oldTransformRecordB);
 
         // Inject delete IKE request
-        mTunUtils.injectPacket(buildInboundPkt(localRemotePorts, deleteIkeReq));
-        mTunUtils.awaitResp(
+        mTunNetworkContext.tunUtils.injectPacket(buildInboundPkt(localRemotePorts, deleteIkeReq));
+        mTunNetworkContext.tunUtils.awaitResp(
                 IKE_DETERMINISTIC_INITIATOR_SPI, expectedRespMsgId++, true /* expectedUseEncap */);
 
         verifyDeleteIpSecTransformPair(
diff --git a/tests/cts/src/android/ipsec/ike/cts/IkeSessionTestBase.java b/tests/cts/src/android/ipsec/ike/cts/IkeSessionTestBase.java
index 745d8fb..12a53bb 100644
--- a/tests/cts/src/android/ipsec/ike/cts/IkeSessionTestBase.java
+++ b/tests/cts/src/android/ipsec/ike/cts/IkeSessionTestBase.java
@@ -47,7 +47,6 @@
 import android.net.ipsec.ike.TransportModeChildSessionParams;
 import android.net.ipsec.ike.TunnelModeChildSessionParams;
 import android.net.ipsec.ike.exceptions.IkeException;
-import android.net.ipsec.ike.exceptions.IkeProtocolException;
 import android.os.Binder;
 import android.os.ParcelFileDescriptor;
 import android.platform.test.annotations.AppModeFull;
@@ -146,10 +145,7 @@
     private static final byte[] NEXT_AVAILABLE_IP4_ADDR_LOCAL = INITIAL_AVAILABLE_IP4_ADDR_LOCAL;
     private static final byte[] NEXT_AVAILABLE_IP4_ADDR_REMOTE = INITIAL_AVAILABLE_IP4_ADDR_REMOTE;
 
-    ParcelFileDescriptor mTunFd;
-    TestNetworkCallback mTunNetworkCallback;
-    Network mTunNetwork;
-    IkeTunUtils mTunUtils;
+    TunNetworkContext mTunNetworkContext;
 
     InetAddress mLocalAddress;
     InetAddress mRemoteAddress;
@@ -181,7 +177,7 @@
     public void setUp() throws Exception {
         mLocalAddress = getNextAvailableIpv4AddressLocal();
         mRemoteAddress = getNextAvailableIpv4AddressRemote();
-        setUpTestNetwork(mLocalAddress);
+        mTunNetworkContext = new TunNetworkContext(mLocalAddress);
 
         mUserCbExecutor = Executors.newSingleThreadExecutor();
         mIkeSessionCallback = new TestIkeSessionCallback();
@@ -190,28 +186,41 @@
 
     @After
     public void tearDown() throws Exception {
-        tearDownTestNetwork();
+        mTunNetworkContext.tearDown();
     }
 
-    void setUpTestNetwork(InetAddress localAddr) throws Exception {
-        int prefixLen = localAddr instanceof Inet4Address ? IP4_PREFIX_LEN : IP6_PREFIX_LEN;
+    protected static class TunNetworkContext {
+        public final ParcelFileDescriptor tunFd;
+        public final TestNetworkCallback tunNetworkCallback;
+        public final Network tunNetwork;
+        public final IkeTunUtils tunUtils;
 
-        TestNetworkInterface testIface =
-                sTNM.createTunInterface(new LinkAddress[] {new LinkAddress(localAddr, prefixLen)});
+        public TunNetworkContext(InetAddress... addresses) throws Exception {
+            final LinkAddress[] linkAddresses = new LinkAddress[addresses.length];
+            for (int i = 0; i < linkAddresses.length; i++) {
+                InetAddress addr = addresses[i];
+                if (addr instanceof Inet4Address) {
+                    linkAddresses[i] = new LinkAddress(addr, IP4_PREFIX_LEN);
+                } else {
+                    linkAddresses[i] = new LinkAddress(addr, IP6_PREFIX_LEN);
+                }
+            }
+            final TestNetworkInterface testIface = sTNM.createTunInterface(linkAddresses);
 
-        mTunFd = testIface.getFileDescriptor();
-        mTunNetworkCallback =
-                TestNetworkUtils.setupAndGetTestNetwork(
-                        sCM, sTNM, testIface.getInterfaceName(), new Binder());
-        mTunNetwork = mTunNetworkCallback.getNetworkBlocking();
-        mTunUtils = new IkeTunUtils(mTunFd);
-    }
+            tunFd = testIface.getFileDescriptor();
+            tunNetworkCallback =
+                    TestNetworkUtils.setupAndGetTestNetwork(
+                            sCM, sTNM, testIface.getInterfaceName(), new Binder());
+            tunNetwork = tunNetworkCallback.getNetworkBlocking();
+            tunUtils = new IkeTunUtils(tunFd);
+        }
 
-    void tearDownTestNetwork() throws Exception {
-        sCM.unregisterNetworkCallback(mTunNetworkCallback);
+        public void tearDown() throws Exception {
+            sCM.unregisterNetworkCallback(tunNetworkCallback);
 
-        sTNM.teardownTestNetwork(mTunNetwork);
-        mTunFd.close();
+            sTNM.teardownTestNetwork(tunNetwork);
+            tunFd.close();
+        }
     }
 
     static void setAppOp(int appop, boolean allow) {
@@ -317,14 +326,15 @@
             boolean expectedAuthUseEncap,
             String... ikeAuthRespHexes)
             throws Exception {
-        mTunUtils.awaitReqAndInjectResp(
+        mTunNetworkContext.tunUtils.awaitReqAndInjectResp(
                 IKE_DETERMINISTIC_INITIATOR_SPI,
                 0 /* expectedMsgId */,
                 false /* expectedUseEncap */,
                 ikeInitRespHex);
 
         byte[] ikeAuthReqPkt =
-                mTunUtils
+                mTunNetworkContext
+                        .tunUtils
                         .awaitReqAndInjectResp(
                                 IKE_DETERMINISTIC_INITIATOR_SPI,
                                 1 /* expectedMsgId */,
@@ -341,7 +351,7 @@
 
     void performCloseIkeBlocking(
             int expectedMsgId, boolean expectedUseEncap, String deleteIkeRespHex) throws Exception {
-        mTunUtils.awaitReqAndInjectResp(
+        mTunNetworkContext.tunUtils.awaitReqAndInjectResp(
                 IKE_DETERMINISTIC_INITIATOR_SPI, expectedMsgId, expectedUseEncap, deleteIkeRespHex);
     }
 
@@ -352,9 +362,11 @@
         private CompletableFuture<Boolean> mFutureOnClosedCall = new CompletableFuture<>();
         private CompletableFuture<IkeException> mFutureOnClosedException =
                 new CompletableFuture<>();
+        private CompletableFuture<IkeSessionConnectionInfo> mFutureConnectionConfig =
+                new CompletableFuture<>();
 
         private int mOnErrorExceptionsCount = 0;
-        private ArrayTrackRecord<IkeProtocolException> mOnErrorExceptionsTrackRecord =
+        private ArrayTrackRecord<IkeException> mOnErrorExceptionsTrackRecord =
                 new ArrayTrackRecord<>();
 
         @Override
@@ -373,10 +385,16 @@
         }
 
         @Override
-        public void onError(@NonNull IkeProtocolException exception) {
+        public void onError(@NonNull IkeException exception) {
             mOnErrorExceptionsTrackRecord.add(exception);
         }
 
+        @Override
+        public void onIkeSessionConnectionInfoChanged(
+                @NonNull IkeSessionConnectionInfo connectionInfo) {
+            mFutureConnectionConfig.complete(connectionInfo);
+        }
+
         public IkeSessionConfiguration awaitIkeConfig() throws Exception {
             return mFutureIkeConfig.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
         }
@@ -385,7 +403,7 @@
             return mFutureOnClosedException.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
         }
 
-        public IkeProtocolException awaitNextOnErrorException() {
+        public IkeException awaitNextOnErrorException() {
             return mOnErrorExceptionsTrackRecord.poll(
                     (long) TIMEOUT_MS,
                     mOnErrorExceptionsCount++,
@@ -397,6 +415,10 @@
         public void awaitOnClosed() throws Exception {
             mFutureOnClosedCall.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
         }
+
+        public IkeSessionConnectionInfo awaitOnIkeSessionConnectionInfoChanged() throws Exception {
+            return mFutureConnectionConfig.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        }
     }
 
     /** Testing callback that allows caller to block current thread until a method get called */
@@ -408,9 +430,12 @@
                 new CompletableFuture<>();
 
         private int mCreatedIpSecTransformCount = 0;
+        private int mMigratedIpSecTransformCount = 0;
         private int mDeletedIpSecTransformCount = 0;
         private ArrayTrackRecord<IpSecTransformCallRecord> mCreatedIpSecTransformsTrackRecord =
                 new ArrayTrackRecord<>();
+        private ArrayTrackRecord<IpSecTransformCallRecord[]> mMigratedIpSecTransformsTrackRecord =
+                new ArrayTrackRecord<>();
         private ArrayTrackRecord<IpSecTransformCallRecord> mDeletedIpSecTransformsTrackRecord =
                 new ArrayTrackRecord<>();
 
@@ -436,6 +461,17 @@
         }
 
         @Override
+        public void onIpSecTransformsMigrated(
+                IpSecTransform inIpSecTransform, IpSecTransform outIpSecTransform) {
+            IpSecTransformCallRecord inRecord =
+                    new IpSecTransformCallRecord(inIpSecTransform, IpSecManager.DIRECTION_IN);
+            IpSecTransformCallRecord outRecord =
+                    new IpSecTransformCallRecord(outIpSecTransform, IpSecManager.DIRECTION_OUT);
+            mMigratedIpSecTransformsTrackRecord.add(
+                    new IpSecTransformCallRecord[] {inRecord, outRecord});
+        }
+
+        @Override
         public void onIpSecTransformDeleted(@NonNull IpSecTransform ipSecTransform, int direction) {
             mDeletedIpSecTransformsTrackRecord.add(
                     new IpSecTransformCallRecord(ipSecTransform, direction));
@@ -458,6 +494,15 @@
                     });
         }
 
+        public IpSecTransformCallRecord[] awaitNextMigratedIpSecTransform() {
+            return mMigratedIpSecTransformsTrackRecord.poll(
+                    (long) TIMEOUT_MS,
+                    mMigratedIpSecTransformCount++,
+                    (transform) -> {
+                        return true;
+                    });
+        }
+
         public IpSecTransformCallRecord awaitNextDeletedIpSecTransform() {
             return mDeletedIpSecTransformsTrackRecord.poll(
                     (long) TIMEOUT_MS,
@@ -511,7 +556,7 @@
         assertNotNull(ikeConnectInfo);
         assertEquals(mLocalAddress, ikeConnectInfo.getLocalAddress());
         assertEquals(mRemoteAddress, ikeConnectInfo.getRemoteAddress());
-        assertEquals(mTunNetwork, ikeConnectInfo.getNetwork());
+        assertEquals(mTunNetworkContext.tunNetwork, ikeConnectInfo.getNetwork());
     }
 
     void verifyChildSessionSetupBlocking(