Merge "Do not do NAT detection when using IPv6 address"
diff --git a/Android.bp b/Android.bp
index f954c1c..01c0762 100644
--- a/Android.bp
+++ b/Android.bp
@@ -14,8 +14,11 @@
 
 java_sdk_library {
     name: "android.net.ipsec.ike",
+    defaults: ["framework-module-defaults"],
     installable: true,
-    sdk_version: "module_current",
+
+    // ike is used as a shared library.
+    shared_library: true,
 
     aidl: {
         local_include_dirs: ["src/java"],
@@ -28,7 +31,6 @@
 
     libs: [
         "unsupportedappusage",
-        "framework-annotations-lib",
     ],
 
     api_packages: [
@@ -41,13 +43,13 @@
     // being overwritten by the frameworks class copies.
     jarjar_rules: "jarjar-rules-shared.txt",
 
-    plugins: ["java_api_finder"],
-
     hostdex: true, // for hiddenapi check
     apex_available: [
         "com.android.ipsec",
         "test_com.android.ipsec",
     ],
+
+    stubs_library_visibility: ["//visibility:public"],
 }
 
 filegroup {
@@ -86,32 +88,3 @@
     // being overwritten by the frameworks class copies.
     jarjar_rules: "jarjar-rules-shared.txt",
 }
-
-stubs_defaults {
-    name: "ike-stubs-defaults",
-    srcs: [":ike-api-srcs"],
-}
-
-droidstubs {
-    name: "android.net.ipsec.ike.api.sources.module_libs_api",
-    defaults: [
-        "framework-module-api-defaults-module_libs_api",
-        "ike-stubs-defaults",
-    ],
-    dist: { dest: "android.net.ipsec.ike.txt" },
-}
-
-droidstubs {
-    name: "android.net.ipsec.ike.stubs.sources.module_libs_api",
-    defaults: [
-        "framework-module-stubs-defaults-module_libs_api",
-        "ike-stubs-defaults",
-    ],
-}
-
-java_library {
-    name: "android.net.ipsec.ike.stubs.module_libs_api",
-    srcs: [":android.net.ipsec.ike.stubs.sources.module_libs_api"],
-    defaults: ["framework-module-stubs-lib-defaults-module_libs_api"],
-    dist: { dest: "android.net.ipsec.ike.jar" },
-}
diff --git a/src/java/com/android/internal/net/ipsec/ike/ChildSessionStateMachine.java b/src/java/com/android/internal/net/ipsec/ike/ChildSessionStateMachine.java
index c92f465..fd500b8 100644
--- a/src/java/com/android/internal/net/ipsec/ike/ChildSessionStateMachine.java
+++ b/src/java/com/android/internal/net/ipsec/ike/ChildSessionStateMachine.java
@@ -773,9 +773,12 @@
 
                         // TODO: Initiate deletion
                         mChildSmCallback.onChildSaDeleted(createChildResult.respSpi.getSpi());
+                        handleChildFatalError(e);
+                    } finally {
+                        // In the successful case the transform in ChildSaRecord has taken ownership
+                        // of the SPI (in IpSecService), and will keep it alive.
                         createChildResult.initSpi.close();
                         createChildResult.respSpi.close();
-                        handleChildFatalError(e);
                     }
                     break;
                 case CREATE_STATUS_CHILD_ERROR_INVALID_MSG:
@@ -882,19 +885,21 @@
 
     /** Initial state of ChildSessionStateMachine. */
     class Initial extends CreateChildLocalCreateBase {
+        List<IkePayload> mRequestPayloads;
+
         @Override
         public boolean processStateMessage(Message message) {
             switch (message.what) {
                 case CMD_HANDLE_FIRST_CHILD_EXCHANGE:
                     FirstChildNegotiationData childNegotiationData =
                             (FirstChildNegotiationData) message.obj;
-                    List<IkePayload> reqPayloads = childNegotiationData.requestPayloads;
+                    mRequestPayloads = childNegotiationData.requestPayloads;
                     List<IkePayload> respPayloads = childNegotiationData.responsePayloads;
 
                     // Negotiate Child SA. The exchangeType has been validated in
                     // IkeSessionStateMachine. Won't validate it again here.
                     validateAndBuildChild(
-                            reqPayloads,
+                            mRequestPayloads,
                             respPayloads,
                             EXCHANGE_TYPE_IKE_AUTH,
                             EXCHANGE_TYPE_IKE_AUTH,
@@ -919,6 +924,11 @@
                     return NOT_HANDLED;
             }
         }
+
+        @Override
+        public void exitState() {
+            CreateChildSaHelper.releaseSpiResources(mRequestPayloads);
+        }
     }
 
     /**
@@ -974,6 +984,11 @@
                     return NOT_HANDLED;
             }
         }
+
+        @Override
+        public void exitState() {
+            CreateChildSaHelper.releaseSpiResources(mRequestPayloads);
+        }
     }
 
     /**
@@ -1355,6 +1370,9 @@
                                     | IOException e) {
                                 // #makeChildSaRecord failed
                                 handleProcessRespOrSaCreationFailAndQuit(resp.registeredSpi, e);
+                            } finally {
+                                // In the successful case the transform in ChildSaRecord has taken
+                                // ownership of the SPI (in IpSecService), and will keep it alive.
                                 createChildResult.initSpi.close();
                                 createChildResult.respSpi.close();
                             }
@@ -1396,6 +1414,11 @@
             }
             handleChildFatalError(exception);
         }
+
+        @Override
+        public void exitState() {
+            CreateChildSaHelper.releaseSpiResources(mRequestPayloads);
+        }
     }
 
     private ChildSaProposal addDhGroupsFromChildSessionParamsIfAbsent() {
@@ -1564,12 +1587,14 @@
                             | SpiUnavailableException
                             | IOException e) {
                         // #makeChildSaRecord failed.
-                        createChildResult.initSpi.close();
-                        createChildResult.respSpi.close();
-
                         handleCreationFailureAndBackToIdle(
                                 new NoValidProposalChosenException(
                                         "Error in Child SA creation", e));
+                    } finally {
+                        // In the successful case the transform in ChildSaRecord has taken ownership
+                        // of the SPI (in IpSecService), and will keep it alive.
+                        createChildResult.initSpi.close();
+                        createChildResult.respSpi.close();
                     }
                     break;
                 case CREATE_STATUS_CHILD_ERROR_INVALID_MSG:
@@ -2046,6 +2071,19 @@
             return hasExpectedRekeyNotify;
         }
 
+        public static void releaseSpiResources(List<IkePayload> reqPayloads) {
+            if (reqPayloads == null) {
+                return;
+            }
+
+            IkeSaPayload saPayload =
+                    IkePayload.getPayloadForTypeInProvidedList(
+                            IkePayload.PAYLOAD_TYPE_SA, IkeSaPayload.class, reqPayloads);
+            if (saPayload != null) {
+                saPayload.releaseChildSpiResourcesIfExists();
+            }
+        }
+
         /** Validate the received payload list and negotiate Child SA. */
         private static CreateChildResult validateAndNegotiateChild(
                 List<IkePayload> reqPayloads,
diff --git a/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachine.java b/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachine.java
index 3592ffb..1a3bf18 100644
--- a/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachine.java
+++ b/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachine.java
@@ -35,9 +35,14 @@
 import static com.android.internal.net.ipsec.ike.message.IkeNotifyPayload.NOTIFY_TYPE_NAT_DETECTION_DESTINATION_IP;
 import static com.android.internal.net.ipsec.ike.message.IkeNotifyPayload.NOTIFY_TYPE_NAT_DETECTION_SOURCE_IP;
 import static com.android.internal.net.ipsec.ike.message.IkeNotifyPayload.NOTIFY_TYPE_REKEY_SA;
+import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_AUTH;
 import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_CP;
 import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_DELETE;
+import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_EAP;
 import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_NOTIFY;
+import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_SA;
+import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_TS_INITIATOR;
+import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_TS_RESPONDER;
 import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_VENDOR;
 import static com.android.internal.net.ipsec.ike.utils.IkeAlarmReceiver.ACTION_DELETE_CHILD;
 import static com.android.internal.net.ipsec.ike.utils.IkeAlarmReceiver.ACTION_DELETE_IKE;
@@ -127,7 +132,6 @@
 import com.android.internal.net.ipsec.ike.message.IkePayload;
 import com.android.internal.net.ipsec.ike.message.IkeSaPayload;
 import com.android.internal.net.ipsec.ike.message.IkeSaPayload.IkeProposal;
-import com.android.internal.net.ipsec.ike.message.IkeTsPayload;
 import com.android.internal.net.ipsec.ike.message.IkeVendorPayload;
 import com.android.internal.net.ipsec.ike.utils.IkeAlarmReceiver;
 import com.android.internal.net.ipsec.ike.utils.IkeSecurityParameterIndex;
@@ -947,6 +951,10 @@
             }
         }
 
+        // Release IPsec SPIs if IKE Session is terminated before receiving the IKE AUTH response
+        // that contains the first child SA proposal
+        CreateChildSaHelper.releaseSpiResources(mFirstChildReqList);
+
         if (mIkeNattKeepalive != null) {
             mIkeNattKeepalive.stop();
         }
@@ -3090,50 +3098,26 @@
 
         protected List<IkePayload> extractChildPayloadsFromMessage(IkeMessage ikeMessage)
                 throws InvalidSyntaxException {
-            IkeSaPayload saPayload =
-                    ikeMessage.getPayloadForType(IkePayload.PAYLOAD_TYPE_SA, IkeSaPayload.class);
-            IkeTsPayload tsInitPayload =
-                    ikeMessage.getPayloadForType(
-                            IkePayload.PAYLOAD_TYPE_TS_INITIATOR, IkeTsPayload.class);
-            IkeTsPayload tsRespPayload =
-                    ikeMessage.getPayloadForType(
-                            IkePayload.PAYLOAD_TYPE_TS_RESPONDER, IkeTsPayload.class);
-
-            List<IkeNotifyPayload> notifyPayloads =
-                    ikeMessage.getPayloadListForType(
-                            IkePayload.PAYLOAD_TYPE_NOTIFY, IkeNotifyPayload.class);
-
-            IkeConfigPayload configPayload =
-                    ikeMessage.getPayloadForType(
-                            IkePayload.PAYLOAD_TYPE_CP, IkeConfigPayload.class);
-
-            boolean hasErrorNotify = false;
             List<IkePayload> list = new LinkedList<>();
-            for (IkeNotifyPayload payload : notifyPayloads) {
-                if (payload.isNewChildSaNotify()) {
-                    list.add(payload);
-                    if (payload.isErrorNotify()) {
-                        hasErrorNotify = true;
-                    }
+            for (IkePayload payload : ikeMessage.ikePayloadList) {
+                switch (payload.payloadType) {
+                    case PAYLOAD_TYPE_SA: // fall through
+                    case PAYLOAD_TYPE_TS_INITIATOR: // fall through
+                    case PAYLOAD_TYPE_TS_RESPONDER: // fall through
+                    case PAYLOAD_TYPE_CP:
+                        list.add(payload);
+                        break;
+                    case PAYLOAD_TYPE_NOTIFY:
+                        if (((IkeNotifyPayload) payload).isNewChildSaNotify()) {
+                            list.add(payload);
+                        }
+                        break;
+                    default:
+                        // Ignore payloads unrelated with Child negotiation
                 }
             }
 
-            // If there is no error notification, SA, TS-initiator and TS-responder MUST all be
-            // included in this message.
-            if (!hasErrorNotify
-                    && (saPayload == null || tsInitPayload == null || tsRespPayload == null)) {
-                throw new InvalidSyntaxException(
-                        "SA, TS-Initiator or TS-Responder payload is missing.");
-            }
-
-            list.add(saPayload);
-            list.add(tsInitPayload);
-            list.add(tsRespPayload);
-
-            if (configPayload != null) {
-                list.add(configPayload);
-            }
-
+            // Payload validation is done in ChildSessionStateMachine
             return list;
         }
 
@@ -3214,12 +3198,11 @@
                             "Expected EXCHANGE_TYPE_IKE_AUTH but received: " + exchangeType);
                 }
 
+                validateIkeAuthResp(ikeMessage);
+
                 List<IkePayload> childReqList =
                         extractChildPayloadsFromMessage(mRetransmitter.getMessage());
-
                 if (mUseEap) {
-                    validateIkeAuthRespWithEapPayload(ikeMessage);
-
                     // childReqList needed after EAP completed, so persist to IkeSessionStateMachine
                     // state.
                     mFirstChildReqList = childReqList;
@@ -3227,12 +3210,12 @@
                     IkeEapPayload ikeEapPayload =
                             ikeMessage.getPayloadForType(
                                     IkePayload.PAYLOAD_TYPE_EAP, IkeEapPayload.class);
-
+                    if (ikeEapPayload == null) {
+                        throw new AuthenticationFailedException("Missing EAP payload");
+                    }
                     deferMessage(obtainMessage(CMD_EAP_START_EAP_AUTH, ikeEapPayload));
                     transitionTo(mCreateIkeLocalIkeAuthInEap);
                 } else {
-                    validateIkeAuthRespWithChildPayloads(ikeMessage);
-
                     notifyIkeSessionSetup(ikeMessage);
 
                     performFirstChildNegotiation(
@@ -3345,40 +3328,12 @@
             return buildIkeAuthReqMessage(payloadList);
         }
 
-        private void validateIkeAuthRespWithEapPayload(IkeMessage respMsg)
-                throws IkeProtocolException {
-            IkeEapPayload ikeEapPayload =
-                    respMsg.getPayloadForType(IkePayload.PAYLOAD_TYPE_EAP, IkeEapPayload.class);
-            if (ikeEapPayload == null) {
-                throw new AuthenticationFailedException("Missing EAP payload");
-            }
-
-            // TODO: check that we don't receive any ChildSaRespPayloads here
-
-            List<IkePayload> nonEapPayloads = new LinkedList<>();
-            nonEapPayloads.addAll(respMsg.ikePayloadList);
-            nonEapPayloads.remove(ikeEapPayload);
-            validateIkeAuthResp(nonEapPayloads);
-        }
-
-        private void validateIkeAuthRespWithChildPayloads(IkeMessage respMsg)
-                throws IkeProtocolException {
-            // Extract and validate existence of payloads for first Child SA setup.
-            List<IkePayload> childSaRespPayloads = extractChildPayloadsFromMessage(respMsg);
-
-            List<IkePayload> nonChildPayloads = new LinkedList<>();
-            nonChildPayloads.addAll(respMsg.ikePayloadList);
-            nonChildPayloads.removeAll(childSaRespPayloads);
-
-            validateIkeAuthResp(nonChildPayloads);
-        }
-
-        private void validateIkeAuthResp(List<IkePayload> payloadList) throws IkeProtocolException {
+        private void validateIkeAuthResp(IkeMessage authResp) throws IkeProtocolException {
             // Validate IKE Authentication
             IkeAuthPayload authPayload = null;
             List<IkeCertPayload> certPayloads = new LinkedList<>();
 
-            for (IkePayload payload : payloadList) {
+            for (IkePayload payload : authResp.ikePayloadList) {
                 switch (payload.payloadType) {
                     case IkePayload.PAYLOAD_TYPE_ID_RESPONDER:
                         mRespIdPayload = (IkeIdPayload) payload;
@@ -3400,7 +3355,18 @@
                     case IkePayload.PAYLOAD_TYPE_NOTIFY:
                         IkeNotifyPayload notifyPayload = (IkeNotifyPayload) payload;
                         if (notifyPayload.isErrorNotify()) {
-                            throw notifyPayload.validateAndBuildIkeException();
+                            if (notifyPayload.isNewChildSaNotify()
+                                    && authResp.getPayloadForType(
+                                                    PAYLOAD_TYPE_AUTH, IkeAuthPayload.class)
+                                            != null) {
+                                // If error is for creating Child and Auth payload is included, try
+                                // to do authentication first and let ChildSessionStateMachine
+                                // handle the error later.
+                                continue;
+                            } else {
+                                throw notifyPayload.validateAndBuildIkeException();
+                            }
+
                         } else {
                             // Unknown and unexpected status notifications are ignored as per
                             // RFC7296.
@@ -3410,6 +3376,12 @@
                                             + notifyPayload.notifyType);
                         }
                         break;
+                    case PAYLOAD_TYPE_SA: // Will be handled separately; fall through
+                    case PAYLOAD_TYPE_CP: // Will be handled separately; fall through
+                    case PAYLOAD_TYPE_TS_INITIATOR: // Will be handled separately; fall through
+                    case PAYLOAD_TYPE_TS_RESPONDER: // Will be handled separately; fall through
+                    case PAYLOAD_TYPE_EAP: // Will be handled separately
+                        break;
                     default:
                         logw(
                                 "Received unexpected payload in IKE AUTH response. Payload"
@@ -3695,18 +3667,11 @@
                             "Expected EXCHANGE_TYPE_IKE_AUTH but received: " + exchangeType);
                 }
 
-                // Extract and validate existence of payloads for first Child SA setup.
-                List<IkePayload> childSaRespPayloads = extractChildPayloadsFromMessage(ikeMessage);
-
-                List<IkePayload> nonChildPayloads = new LinkedList<>();
-                nonChildPayloads.addAll(ikeMessage.ikePayloadList);
-                nonChildPayloads.removeAll(childSaRespPayloads);
-
-                validateIkeAuthRespPostEap(nonChildPayloads);
-
+                validateIkeAuthRespPostEap(ikeMessage);
                 notifyIkeSessionSetup(ikeMessage);
 
-                performFirstChildNegotiation(mFirstChildReqList, childSaRespPayloads);
+                performFirstChildNegotiation(
+                        mFirstChildReqList, extractChildPayloadsFromMessage(ikeMessage));
             } catch (IkeProtocolException e) {
                 // Notify the remote because they may have set up the IKE SA.
                 sendEncryptedIkeMessage(buildIkeDeleteReq(mCurrentIkeSaRecord));
@@ -3723,11 +3688,10 @@
             handleIkeFatalError(ikeException);
         }
 
-        private void validateIkeAuthRespPostEap(List<IkePayload> payloadList)
-                throws IkeProtocolException {
+        private void validateIkeAuthRespPostEap(IkeMessage authResp) throws IkeProtocolException {
             IkeAuthPayload authPayload = null;
 
-            for (IkePayload payload : payloadList) {
+            for (IkePayload payload : authResp.ikePayloadList) {
                 switch (payload.payloadType) {
                     case IkePayload.PAYLOAD_TYPE_AUTH:
                         authPayload = (IkeAuthPayload) payload;
@@ -3735,7 +3699,18 @@
                     case IkePayload.PAYLOAD_TYPE_NOTIFY:
                         IkeNotifyPayload notifyPayload = (IkeNotifyPayload) payload;
                         if (notifyPayload.isErrorNotify()) {
-                            throw notifyPayload.validateAndBuildIkeException();
+                            if (notifyPayload.isNewChildSaNotify()
+                                    && authResp.getPayloadForType(
+                                                    PAYLOAD_TYPE_AUTH, IkeAuthPayload.class)
+                                            != null) {
+                                // If error is for creating Child and Auth payload is included, try
+                                // to do authentication first and let ChildSessionStateMachine
+                                // handle the error later.
+                                continue;
+                            } else {
+                                throw notifyPayload.validateAndBuildIkeException();
+                            }
+
                         } else {
                             // Unknown and unexpected status notifications are ignored as per
                             // RFC7296.
@@ -3745,6 +3720,11 @@
                                             + notifyPayload.notifyType);
                         }
                         break;
+                    case PAYLOAD_TYPE_SA: // Will be handled separately; fall through
+                    case PAYLOAD_TYPE_CP: // Will be handled separately; fall through
+                    case PAYLOAD_TYPE_TS_INITIATOR: // Will be handled separately; fall through
+                    case PAYLOAD_TYPE_TS_RESPONDER: // Will be handled separately; fall through
+                        break;
                     default:
                         logw(
                                 "Received unexpected payload in IKE AUTH response. Payload"
diff --git a/src/java/com/android/internal/net/ipsec/ike/message/IkeMessage.java b/src/java/com/android/internal/net/ipsec/ike/message/IkeMessage.java
index 1c5f1f6..3da649a 100644
--- a/src/java/com/android/internal/net/ipsec/ike/message/IkeMessage.java
+++ b/src/java/com/android/internal/net/ipsec/ike/message/IkeMessage.java
@@ -503,10 +503,12 @@
 
                 int fragNum = i + 1; // 1-based
 
+                int fragFirstInnerPayload =
+                        i == 0 ? firstInnerPayload : IkePayload.PAYLOAD_TYPE_NO_NEXT;
                 IkeSkfPayload skfPayload =
                         new IkeSkfPayload(
-                                ikeHeader,
-                                firstInnerPayload,
+                                skfHeader,
+                                fragFirstInnerPayload,
                                 unencryptedData,
                                 integrityMac,
                                 encryptCipher,
@@ -515,11 +517,7 @@
                                 fragNum,
                                 totalFragments);
 
-                packetList[i] =
-                        encodeHeaderAndBody(
-                                skfHeader,
-                                skfPayload,
-                                i == 0 ? firstInnerPayload : IkePayload.PAYLOAD_TYPE_NO_NEXT);
+                packetList[i] = encodeHeaderAndBody(skfHeader, skfPayload, fragFirstInnerPayload);
                 getIkeLog()
                         .d(
                                 "IkeMessage",
@@ -528,7 +526,7 @@
                                         + "/"
                                         + totalFragments
                                         + "): "
-                                        + getIkeLog().pii(packetList[0]));
+                                        + getIkeLog().pii(packetList[i]));
             }
 
             return packetList;
diff --git a/src/java/com/android/internal/net/ipsec/ike/message/IkeSaPayload.java b/src/java/com/android/internal/net/ipsec/ike/message/IkeSaPayload.java
index d2dd394..40eecf0 100644
--- a/src/java/com/android/internal/net/ipsec/ike/message/IkeSaPayload.java
+++ b/src/java/com/android/internal/net/ipsec/ike/message/IkeSaPayload.java
@@ -514,7 +514,20 @@
         Transform[] decodeTransforms(int count, ByteBuffer inputBuffer) throws IkeProtocolException;
     }
 
-    // TODO: Add another constructor for building outbound message.
+    /**
+     * Release IPsec SPI resources in the outbound Create Child request
+     *
+     * <p>This method is usually called when an IKE library fails to receive a Create Child response
+     * before it is terminated. It is also safe to call after the Create Child exchange has
+     * succeeded because the newly created IpSecTransform pair will hold the IPsec SPI resource.
+     */
+    public void releaseChildSpiResourcesIfExists() {
+        for (Proposal proposal : proposalList) {
+            if (proposal instanceof ChildProposal) {
+                proposal.releaseSpiResourceIfExists();
+            }
+        }
+    }
 
     /**
      * This class represents the common information of an IKE Proposal and a Child Proposal.
diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachineTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachineTest.java
index 66c2517..6fe4aad 100644
--- a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachineTest.java
+++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachineTest.java
@@ -18,7 +18,9 @@
 
 import static android.net.ipsec.ike.IkeSessionConfiguration.EXTENSION_TYPE_FRAGMENTATION;
 import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_EAP_ONLY_AUTH;
+import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED;
 import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_CHILD_SA_NOT_FOUND;
+import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_INTERNAL_ADDRESS_FAILURE;
 import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_INVALID_SYNTAX;
 import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_NO_ADDITIONAL_SAS;
 import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_NO_PROPOSAL_CHOSEN;
@@ -2528,6 +2530,48 @@
     }
 
     @Test
+    public void testAuthHandlesIkeErrorNotify() throws Exception {
+        mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuth);
+        verifyRetransmissionStarted();
+        resetMockIkeMessageHelper();
+
+        // Mock rejecting IKE AUTH with Authenticatio Failure notification
+        ReceivedIkePacket mockAuthFailPacket =
+                makeIkeAuthRespWithoutChildPayloads(
+                        Arrays.asList(new IkeNotifyPayload(ERROR_TYPE_AUTHENTICATION_FAILED)));
+        mIkeSessionStateMachine.sendMessage(CMD_RECEIVE_IKE_PACKET, mockAuthFailPacket);
+        mLooper.dispatchAll();
+
+        // Verify IKE Session is closed properly
+        assertNull(mIkeSessionStateMachine.getCurrentState());
+        verify(mMockIkeSessionCallback)
+                .onClosedExceptionally(any(AuthenticationFailedException.class));
+    }
+
+    @Test
+    public void testAuthHandlesCreateChildErrorNotify() throws Exception {
+        mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuth);
+        verifyRetransmissionStarted();
+        resetMockIkeMessageHelper();
+
+        // Mock rejecting IKE AUTH with a Create Child error notification
+        ReceivedIkePacket mockAuthFailPacket =
+                makeIkeAuthRespWithoutChildPayloads(
+                        Arrays.asList(new IkeNotifyPayload(ERROR_TYPE_INTERNAL_ADDRESS_FAILURE)));
+        mIkeSessionStateMachine.sendMessage(CMD_RECEIVE_IKE_PACKET, mockAuthFailPacket);
+        mLooper.dispatchAll();
+
+        // Verify IKE Session is closed properly
+        assertNull(mIkeSessionStateMachine.getCurrentState());
+
+        ArgumentCaptor<IkeProtocolException> captor =
+                ArgumentCaptor.forClass(IkeProtocolException.class);
+        verify(mMockIkeSessionCallback).onClosedExceptionally(captor.capture());
+        IkeProtocolException exception = captor.getValue();
+        assertEquals(ERROR_TYPE_INTERNAL_ADDRESS_FAILURE, exception.getErrorType());
+    }
+
+    @Test
     public void testAuthPskHandleRespWithParsingError() throws Exception {
         mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuth);
         verifyRetransmissionStarted();