Add XFRM-I support to XfrmController

This patch adds support for creating and managing XFRM interfaces,
adding xfrm_if_id parameters to all relevant netlink calls.

This is part of a patch set to enable XFRM-I support, with automatic
fallbacks to VTI in XfrmController (2/3)

Bug: 77856928
Test: Xfrm, Binder tests updated, passing
Change-Id: I09869e6a0000384c9c4d0aef1de4d5434c33374a
diff --git a/server/NetdNativeService.cpp b/server/NetdNativeService.cpp
index 7bcc392..7e1965b 100644
--- a/server/NetdNativeService.cpp
+++ b/server/NetdNativeService.cpp
@@ -759,46 +759,32 @@
 }
 
 binder::Status NetdNativeService::ipSecAddSecurityAssociation(
-        int32_t transformId,
-        int32_t mode,
-        const std::string& sourceAddress,
-        const std::string& destinationAddress,
-        int32_t underlyingNetId,
-        int32_t spi,
-        int32_t markValue,
-        int32_t markMask,
-        const std::string& authAlgo, const std::vector<uint8_t>& authKey, int32_t authTruncBits,
-        const std::string& cryptAlgo, const std::vector<uint8_t>& cryptKey, int32_t cryptTruncBits,
-        const std::string& aeadAlgo, const std::vector<uint8_t>& aeadKey, int32_t aeadIcvBits,
-        int32_t encapType,
-        int32_t encapLocalPort,
-        int32_t encapRemotePort) {
+        int32_t transformId, int32_t mode, const std::string& sourceAddress,
+        const std::string& destinationAddress, int32_t underlyingNetId, int32_t spi,
+        int32_t markValue, int32_t markMask, const std::string& authAlgo,
+        const std::vector<uint8_t>& authKey, int32_t authTruncBits, const std::string& cryptAlgo,
+        const std::vector<uint8_t>& cryptKey, int32_t cryptTruncBits, const std::string& aeadAlgo,
+        const std::vector<uint8_t>& aeadKey, int32_t aeadIcvBits, int32_t encapType,
+        int32_t encapLocalPort, int32_t encapRemotePort, int32_t interfaceId) {
     // Necessary locking done in IpSecService and kernel
     ENFORCE_PERMISSION(CONNECTIVITY_INTERNAL);
     gLog.log("ipSecAddSecurityAssociation()");
     return asBinderStatus(gCtls->xfrmCtrl.ipSecAddSecurityAssociation(
             transformId, mode, sourceAddress, destinationAddress, underlyingNetId, spi, markValue,
             markMask, authAlgo, authKey, authTruncBits, cryptAlgo, cryptKey, cryptTruncBits,
-            aeadAlgo, aeadKey, aeadIcvBits, encapType, encapLocalPort, encapRemotePort));
+            aeadAlgo, aeadKey, aeadIcvBits, encapType, encapLocalPort, encapRemotePort,
+            interfaceId));
 }
 
 binder::Status NetdNativeService::ipSecDeleteSecurityAssociation(
-        int32_t transformId,
-        const std::string& sourceAddress,
-        const std::string& destinationAddress,
-        int32_t spi,
-        int32_t markValue,
-        int32_t markMask) {
+        int32_t transformId, const std::string& sourceAddress,
+        const std::string& destinationAddress, int32_t spi, int32_t markValue, int32_t markMask,
+        int32_t interfaceId) {
     // Necessary locking done in IpSecService and kernel
     ENFORCE_PERMISSION(CONNECTIVITY_INTERNAL);
     gLog.log("ipSecDeleteSecurityAssociation()");
     return asBinderStatus(gCtls->xfrmCtrl.ipSecDeleteSecurityAssociation(
-                    transformId,
-                    sourceAddress,
-                    destinationAddress,
-                    spi,
-                    markValue,
-                    markMask));
+            transformId, sourceAddress, destinationAddress, spi, markValue, markMask, interfaceId));
 }
 
 binder::Status NetdNativeService::ipSecApplyTransportModeTransform(
@@ -834,51 +820,49 @@
                                                          const std::string& tmplSrcAddress,
                                                          const std::string& tmplDstAddress,
                                                          int32_t spi, int32_t markValue,
-                                                         int32_t markMask) {
+                                                         int32_t markMask, int32_t interfaceId) {
     // Necessary locking done in IpSecService and kernel
     ENFORCE_PERMISSION(NETWORK_STACK);
     gLog.log("ipSecAddSecurityPolicy()");
     return asBinderStatus(gCtls->xfrmCtrl.ipSecAddSecurityPolicy(
             transformId, selAddrFamily, direction, tmplSrcAddress, tmplDstAddress, spi, markValue,
-            markMask));
+            markMask, interfaceId));
 }
 
-binder::Status NetdNativeService::ipSecUpdateSecurityPolicy(int32_t transformId,
-                                                            int32_t selAddrFamily,
-                                                            int32_t direction,
-                                                            const std::string& tmplSrcAddress,
-                                                            const std::string& tmplDstAddress,
-                                                            int32_t spi, int32_t markValue,
-                                                            int32_t markMask) {
+binder::Status NetdNativeService::ipSecUpdateSecurityPolicy(
+        int32_t transformId, int32_t selAddrFamily, int32_t direction,
+        const std::string& tmplSrcAddress, const std::string& tmplDstAddress, int32_t spi,
+        int32_t markValue, int32_t markMask, int32_t interfaceId) {
     // Necessary locking done in IpSecService and kernel
     ENFORCE_PERMISSION(NETWORK_STACK);
     gLog.log("ipSecAddSecurityPolicy()");
     return asBinderStatus(gCtls->xfrmCtrl.ipSecUpdateSecurityPolicy(
             transformId, selAddrFamily, direction, tmplSrcAddress, tmplDstAddress, spi, markValue,
-            markMask));
+            markMask, interfaceId));
 }
 
 binder::Status NetdNativeService::ipSecDeleteSecurityPolicy(int32_t transformId,
                                                             int32_t selAddrFamily,
                                                             int32_t direction, int32_t markValue,
-                                                            int32_t markMask) {
+                                                            int32_t markMask, int32_t interfaceId) {
     // Necessary locking done in IpSecService and kernel
     ENFORCE_PERMISSION(NETWORK_STACK);
     gLog.log("ipSecAddSecurityPolicy()");
     return asBinderStatus(gCtls->xfrmCtrl.ipSecDeleteSecurityPolicy(
-            transformId, selAddrFamily, direction, markValue, markMask));
+            transformId, selAddrFamily, direction, markValue, markMask, interfaceId));
 }
 
 binder::Status NetdNativeService::ipSecAddTunnelInterface(const std::string& deviceName,
                                                           const std::string& localAddress,
                                                           const std::string& remoteAddress,
-                                                          int32_t iKey, int32_t oKey) {
+                                                          int32_t iKey, int32_t oKey,
+                                                          int32_t interfaceId) {
     // Necessary locking done in IpSecService and kernel
     ENFORCE_PERMISSION(NETWORK_STACK);
     auto entry = gLog.newEntry().prettyFunction(__PRETTY_FUNCTION__);
 
     netdutils::Status result = gCtls->xfrmCtrl.ipSecAddTunnelInterface(
-            deviceName, localAddress, remoteAddress, iKey, oKey, false);
+            deviceName, localAddress, remoteAddress, iKey, oKey, interfaceId, false);
     RETURN_BINDER_STATUS_IF_NOT_OK(entry, result);
 
     gLog.log(entry.returns(result).withAutomaticDuration());
@@ -888,13 +872,14 @@
 binder::Status NetdNativeService::ipSecUpdateTunnelInterface(const std::string& deviceName,
                                                              const std::string& localAddress,
                                                              const std::string& remoteAddress,
-                                                             int32_t iKey, int32_t oKey) {
+                                                             int32_t iKey, int32_t oKey,
+                                                             int32_t interfaceId) {
     // Necessary locking done in IpSecService and kernel
     ENFORCE_PERMISSION(NETWORK_STACK);
     auto entry = gLog.newEntry().prettyFunction(__PRETTY_FUNCTION__);
 
     netdutils::Status result = gCtls->xfrmCtrl.ipSecAddTunnelInterface(
-            deviceName, localAddress, remoteAddress, iKey, oKey, true);
+            deviceName, localAddress, remoteAddress, iKey, oKey, interfaceId, true);
     RETURN_BINDER_STATUS_IF_NOT_OK(entry, result);
 
     gLog.log(entry.returns(result).withAutomaticDuration());
diff --git a/server/NetdNativeService.h b/server/NetdNativeService.h
index dcac193..4b86f59 100644
--- a/server/NetdNativeService.h
+++ b/server/NetdNativeService.h
@@ -166,34 +166,20 @@
             int32_t* outSpi);
 
     binder::Status ipSecAddSecurityAssociation(
-            int32_t transformId,
-            int32_t mode,
-            const std::string& sourceAddress,
-            const std::string& destinationAddress,
-            int32_t underlyingNetId,
-            int32_t spi,
-            int32_t markValue,
-            int32_t markMask,
-            const std::string& authAlgo,
-            const std::vector<uint8_t>& authKey,
-            int32_t authTruncBits,
-            const std::string& cryptAlgo,
-            const std::vector<uint8_t>& cryptKey,
-            int32_t cryptTruncBits,
-            const std::string& aeadAlgo,
-            const std::vector<uint8_t>& aeadKey,
-            int32_t aeadIcvBits,
-            int32_t encapType,
-            int32_t encapLocalPort,
-            int32_t encapRemotePort);
+            int32_t transformId, int32_t mode, const std::string& sourceAddress,
+            const std::string& destinationAddress, int32_t underlyingNetId, int32_t spi,
+            int32_t markValue, int32_t markMask, const std::string& authAlgo,
+            const std::vector<uint8_t>& authKey, int32_t authTruncBits,
+            const std::string& cryptAlgo, const std::vector<uint8_t>& cryptKey,
+            int32_t cryptTruncBits, const std::string& aeadAlgo,
+            const std::vector<uint8_t>& aeadKey, int32_t aeadIcvBits, int32_t encapType,
+            int32_t encapLocalPort, int32_t encapRemotePort, int32_t interfaceId);
 
-    binder::Status ipSecDeleteSecurityAssociation(
-            int32_t transformId,
-            const std::string& sourceAddress,
-            const std::string& destinationAddress,
-            int32_t spi,
-            int32_t markValue,
-            int32_t markMask);
+    binder::Status ipSecDeleteSecurityAssociation(int32_t transformId,
+                                                  const std::string& sourceAddress,
+                                                  const std::string& destinationAddress,
+                                                  int32_t spi, int32_t markValue, int32_t markMask,
+                                                  int32_t interfaceId);
 
     binder::Status ipSecApplyTransportModeTransform(
             const android::base::unique_fd& socket,
@@ -209,28 +195,29 @@
     binder::Status ipSecAddSecurityPolicy(int32_t transformId, int32_t selAddrFamily,
                                           int32_t direction, const std::string& tmplSrcAddress,
                                           const std::string& tmplDstAddress, int32_t spi,
-                                          int32_t markValue, int32_t markMask);
+                                          int32_t markValue, int32_t markMask, int32_t interfaceId);
 
     binder::Status ipSecUpdateSecurityPolicy(int32_t transformId, int32_t selAddrFamily,
                                              int32_t direction, const std::string& tmplSrcAddress,
                                              const std::string& tmplDstAddress, int32_t spi,
-                                             int32_t markValue, int32_t markMask);
+                                             int32_t markValue, int32_t markMask,
+                                             int32_t interfaceId);
 
     binder::Status ipSecDeleteSecurityPolicy(int32_t transformId, int32_t selAddrFamily,
-                                             int32_t direction, int32_t markValue,
-                                             int32_t markMask);
+                                             int32_t direction, int32_t markValue, int32_t markMask,
+                                             int32_t interfaceId);
 
     binder::Status trafficCheckBpfStatsEnable(bool* ret) override;
 
     binder::Status ipSecAddTunnelInterface(const std::string& deviceName,
                                            const std::string& localAddress,
                                            const std::string& remoteAddress, int32_t iKey,
-                                           int32_t oKey);
+                                           int32_t oKey, int32_t interfaceId);
 
     binder::Status ipSecUpdateTunnelInterface(const std::string& deviceName,
                                               const std::string& localAddress,
                                               const std::string& remoteAddress, int32_t iKey,
-                                              int32_t oKey);
+                                              int32_t oKey, int32_t interfaceId);
 
     binder::Status ipSecRemoveTunnelInterface(const std::string& deviceName);
 
diff --git a/server/XfrmController.cpp b/server/XfrmController.cpp
index 18181e0..297a4b0 100644
--- a/server/XfrmController.cpp
+++ b/server/XfrmController.cpp
@@ -469,7 +469,7 @@
 
     XfrmSaInfo saInfo{};
     netdutils::Status ret = fillXfrmCommonInfo(sourceAddress, destinationAddress, INVALID_SPI, 0, 0,
-                                               transformId, &saInfo);
+                                               transformId, 0, &saInfo);
     if (!isOk(ret)) {
         return ret;
     }
@@ -497,12 +497,13 @@
 }
 
 netdutils::Status XfrmController::ipSecAddSecurityAssociation(
-    int32_t transformId, int32_t mode, const std::string& sourceAddress,
-    const std::string& destinationAddress, int32_t underlyingNetId, int32_t spi, int32_t markValue,
-    int32_t markMask, const std::string& authAlgo, const std::vector<uint8_t>& authKey,
-    int32_t authTruncBits, const std::string& cryptAlgo, const std::vector<uint8_t>& cryptKey,
-    int32_t cryptTruncBits, const std::string& aeadAlgo, const std::vector<uint8_t>& aeadKey,
-    int32_t aeadIcvBits, int32_t encapType, int32_t encapLocalPort, int32_t encapRemotePort) {
+        int32_t transformId, int32_t mode, const std::string& sourceAddress,
+        const std::string& destinationAddress, int32_t underlyingNetId, int32_t spi,
+        int32_t markValue, int32_t markMask, const std::string& authAlgo,
+        const std::vector<uint8_t>& authKey, int32_t authTruncBits, const std::string& cryptAlgo,
+        const std::vector<uint8_t>& cryptKey, int32_t cryptTruncBits, const std::string& aeadAlgo,
+        const std::vector<uint8_t>& aeadKey, int32_t aeadIcvBits, int32_t encapType,
+        int32_t encapLocalPort, int32_t encapRemotePort, int32_t xfrmInterfaceId) {
     ALOGD("XfrmController::%s, line=%d", __FUNCTION__, __LINE__);
     ALOGD("transformId=%d", transformId);
     ALOGD("mode=%d", mode);
@@ -521,10 +522,11 @@
     ALOGD("encapType=%d", encapType);
     ALOGD("encapLocalPort=%d", encapLocalPort);
     ALOGD("encapRemotePort=%d", encapRemotePort);
+    ALOGD("xfrmInterfaceId=%d", xfrmInterfaceId);
 
     XfrmSaInfo saInfo{};
     netdutils::Status ret = fillXfrmCommonInfo(sourceAddress, destinationAddress, spi, markValue,
-                                               markMask, transformId, &saInfo);
+                                               markMask, transformId, xfrmInterfaceId, &saInfo);
     if (!isOk(ret)) {
         return ret;
     }
@@ -583,8 +585,9 @@
 }
 
 netdutils::Status XfrmController::ipSecDeleteSecurityAssociation(
-    int32_t transformId, const std::string& sourceAddress, const std::string& destinationAddress,
-    int32_t spi, int32_t markValue, int32_t markMask) {
+        int32_t transformId, const std::string& sourceAddress,
+        const std::string& destinationAddress, int32_t spi, int32_t markValue, int32_t markMask,
+        int32_t xfrmInterfaceId) {
     ALOGD("XfrmController:%s, line=%d", __FUNCTION__, __LINE__);
     ALOGD("transformId=%d", transformId);
     ALOGD("sourceAddress=%s", sourceAddress.c_str());
@@ -592,10 +595,11 @@
     ALOGD("spi=%0.8x", spi);
     ALOGD("markValue=%x", markValue);
     ALOGD("markMask=%x", markMask);
+    ALOGD("xfrmInterfaceId=%d", xfrmInterfaceId);
 
     XfrmSaInfo saInfo{};
     netdutils::Status ret = fillXfrmCommonInfo(sourceAddress, destinationAddress, spi, markValue,
-                                               markMask, transformId, &saInfo);
+                                               markMask, transformId, xfrmInterfaceId, &saInfo);
     if (!isOk(ret)) {
         return ret;
     }
@@ -619,6 +623,7 @@
                                                      const std::string& destinationAddress,
                                                      int32_t spi, int32_t markValue,
                                                      int32_t markMask, int32_t transformId,
+                                                     int32_t xfrmInterfaceId,
                                                      XfrmCommonInfo* info) {
     // Use the addresses to determine the address family and do validation
     xfrm_address_t sourceXfrmAddr{}, destXfrmAddr{};
@@ -642,16 +647,18 @@
     info->dstAddr = destXfrmAddr;
     info->srcAddr = sourceXfrmAddr;
 
-    return fillXfrmCommonInfo(spi, markValue, markMask, transformId, info);
+    return fillXfrmCommonInfo(spi, markValue, markMask, transformId, xfrmInterfaceId, info);
 }
 
 netdutils::Status XfrmController::fillXfrmCommonInfo(int32_t spi, int32_t markValue,
                                                      int32_t markMask, int32_t transformId,
+                                                     int32_t xfrmInterfaceId,
                                                      XfrmCommonInfo* info) {
     info->transformId = transformId;
     info->spi = htonl(spi);
     info->mark.v = markValue;
     info->mark.m = markMask;
+    info->xfrm_if_id = xfrmInterfaceId;
 
     return netdutils::status::ok;
 }
@@ -674,8 +681,8 @@
     struct sockaddr_storage saddr = ret.value();
 
     XfrmSpInfo spInfo{};
-    netdutils::Status status =
-            fillXfrmCommonInfo(sourceAddress, destinationAddress, spi, 0, 0, transformId, &spInfo);
+    netdutils::Status status = fillXfrmCommonInfo(sourceAddress, destinationAddress, spi, 0, 0,
+                                                  transformId, 0, &spInfo);
     if (!isOk(status)) {
         ALOGE("Couldn't build SA ID %s", __FUNCTION__);
         return status;
@@ -760,38 +767,37 @@
     return status;
 }
 
-netdutils::Status XfrmController::ipSecAddSecurityPolicy(int32_t transformId, int32_t selAddrFamily,
-                                                         int32_t direction,
-                                                         const std::string& tmplSrcAddress,
-                                                         const std::string& tmplDstAddress,
-                                                         int32_t spi, int32_t markValue,
-                                                         int32_t markMask) {
+netdutils::Status XfrmController::ipSecAddSecurityPolicy(
+        int32_t transformId, int32_t selAddrFamily, int32_t direction,
+        const std::string& tmplSrcAddress, const std::string& tmplDstAddress, int32_t spi,
+        int32_t markValue, int32_t markMask, int32_t xfrmInterfaceId) {
     return processSecurityPolicy(transformId, selAddrFamily, direction, tmplSrcAddress,
-                                 tmplDstAddress, spi, markValue, markMask, XFRM_MSG_NEWPOLICY);
+                                 tmplDstAddress, spi, markValue, markMask, xfrmInterfaceId,
+                                 XFRM_MSG_NEWPOLICY);
 }
 
 netdutils::Status XfrmController::ipSecUpdateSecurityPolicy(
         int32_t transformId, int32_t selAddrFamily, int32_t direction,
         const std::string& tmplSrcAddress, const std::string& tmplDstAddress, int32_t spi,
-        int32_t markValue, int32_t markMask) {
+        int32_t markValue, int32_t markMask, int32_t xfrmInterfaceId) {
     return processSecurityPolicy(transformId, selAddrFamily, direction, tmplSrcAddress,
-                                 tmplDstAddress, spi, markValue, markMask, XFRM_MSG_UPDPOLICY);
+                                 tmplDstAddress, spi, markValue, markMask, xfrmInterfaceId,
+                                 XFRM_MSG_UPDPOLICY);
 }
 
 netdutils::Status XfrmController::ipSecDeleteSecurityPolicy(int32_t transformId,
                                                             int32_t selAddrFamily,
                                                             int32_t direction, int32_t markValue,
-                                                            int32_t markMask) {
+                                                            int32_t markMask,
+                                                            int32_t xfrmInterfaceId) {
     return processSecurityPolicy(transformId, selAddrFamily, direction, "", "", 0, markValue,
-                                 markMask, XFRM_MSG_DELPOLICY);
+                                 markMask, xfrmInterfaceId, XFRM_MSG_DELPOLICY);
 }
 
-netdutils::Status XfrmController::processSecurityPolicy(int32_t transformId, int32_t selAddrFamily,
-                                                        int32_t direction,
-                                                        const std::string& tmplSrcAddress,
-                                                        const std::string& tmplDstAddress,
-                                                        int32_t spi, int32_t markValue,
-                                                        int32_t markMask, int32_t msgType) {
+netdutils::Status XfrmController::processSecurityPolicy(
+        int32_t transformId, int32_t selAddrFamily, int32_t direction,
+        const std::string& tmplSrcAddress, const std::string& tmplDstAddress, int32_t spi,
+        int32_t markValue, int32_t markMask, int32_t xfrmInterfaceId, int32_t msgType) {
     ALOGD("XfrmController::%s, line=%d", __FUNCTION__, __LINE__);
     ALOGD("selAddrFamily=%s", selAddrFamily == AF_INET6 ? "AF_INET6" : "AF_INET");
     ALOGD("transformId=%d", transformId);
@@ -802,6 +808,7 @@
     ALOGD("markValue=%d", markValue);
     ALOGD("markMask=%d", markMask);
     ALOGD("msgType=%d", msgType);
+    ALOGD("xfrmInterfaceId=%d", xfrmInterfaceId);
 
     XfrmSpInfo spInfo{};
     spInfo.mode = XfrmMode::TUNNEL;
@@ -816,12 +823,13 @@
     spInfo.selAddrFamily = selAddrFamily;
 
     if (msgType == XFRM_MSG_DELPOLICY) {
-        RETURN_IF_NOT_OK(fillXfrmCommonInfo(spi, markValue, markMask, transformId, &spInfo));
+        RETURN_IF_NOT_OK(fillXfrmCommonInfo(spi, markValue, markMask, transformId, xfrmInterfaceId,
+                                            &spInfo));
 
         return deleteTunnelModeSecurityPolicy(spInfo, sock, static_cast<XfrmDirection>(direction));
     } else {
         RETURN_IF_NOT_OK(fillXfrmCommonInfo(tmplSrcAddress, tmplDstAddress, spi, markValue,
-                                            markMask, transformId, &spInfo));
+                                            markMask, transformId, xfrmInterfaceId, &spInfo));
 
         return updateTunnelModeSecurityPolicy(spInfo, sock, static_cast<XfrmDirection>(direction),
                                               msgType);
@@ -843,6 +851,7 @@
     nlattr_xfrm_mark xfrmmark{};
     nlattr_xfrm_output_mark xfrmoutputmark{};
     nlattr_encap_tmpl encap{};
+    nlattr_xfrm_interface_id xfrm_if_id{};
 
     enum {
         NLMSG_HDR,
@@ -860,24 +869,28 @@
         OUTPUT_MARK_PAD,
         ENCAP,
         ENCAP_PAD,
+        INTF_ID,
+        INTF_ID_PAD,
     };
 
     std::vector<iovec> iov = {
-        {nullptr, 0},            // reserved for the eventual addition of a NLMSG_HDR
-        {&usersa, 0},         // main usersa_info struct
-        {kPadBytes, 0},       // up to NLMSG_ALIGNTO pad bytes of padding
-        {&crypt, 0},          // adjust size if crypt algo is present
-        {kPadBytes, 0},       // up to NLATTR_ALIGNTO pad bytes
-        {&auth, 0},           // adjust size if auth algo is present
-        {kPadBytes, 0},       // up to NLATTR_ALIGNTO pad bytes
-        {&aead, 0},           // adjust size if aead algo is present
-        {kPadBytes, 0},       // up to NLATTR_ALIGNTO pad bytes
-        {&xfrmmark, 0},       // adjust size if xfrm mark is present
-        {kPadBytes, 0},       // up to NLATTR_ALIGNTO pad bytes
-        {&xfrmoutputmark, 0}, // adjust size if xfrm output mark is present
-        {kPadBytes, 0},       // up to NLATTR_ALIGNTO pad bytes
-        {&encap, 0},          // adjust size if encapsulating
-        {kPadBytes, 0},       // up to NLATTR_ALIGNTO pad bytes
+            {nullptr, 0},          // reserved for the eventual addition of a NLMSG_HDR
+            {&usersa, 0},          // main usersa_info struct
+            {kPadBytes, 0},        // up to NLMSG_ALIGNTO pad bytes of padding
+            {&crypt, 0},           // adjust size if crypt algo is present
+            {kPadBytes, 0},        // up to NLATTR_ALIGNTO pad bytes
+            {&auth, 0},            // adjust size if auth algo is present
+            {kPadBytes, 0},        // up to NLATTR_ALIGNTO pad bytes
+            {&aead, 0},            // adjust size if aead algo is present
+            {kPadBytes, 0},        // up to NLATTR_ALIGNTO pad bytes
+            {&xfrmmark, 0},        // adjust size if xfrm mark is present
+            {kPadBytes, 0},        // up to NLATTR_ALIGNTO pad bytes
+            {&xfrmoutputmark, 0},  // adjust size if xfrm output mark is present
+            {kPadBytes, 0},        // up to NLATTR_ALIGNTO pad bytes
+            {&encap, 0},           // adjust size if encapsulating
+            {kPadBytes, 0},        // up to NLATTR_ALIGNTO pad bytes
+            {&xfrm_if_id, 0},      // adjust size if interface ID is present
+            {kPadBytes, 0},        // up to NLATTR_ALIGNTO pad bytes
     };
 
     if (!record.aead.name.empty() && (!record.auth.name.empty() || !record.crypt.name.empty())) {
@@ -891,6 +904,13 @@
         return netdutils::statusFromErrno(EINVAL, "Key length invalid; exceeds MAX_KEY_LENGTH");
     }
 
+    if (record.mode != XfrmMode::TUNNEL && record.xfrm_if_id != 0) {
+        // TODO: Also throw errors if output mark or mark supplied
+        return netdutils::statusFromErrno(EINVAL,
+                                          "xfrm_if_id parameter invalid for non "
+                                          "tunnel-mode transform");
+    }
+
     int len;
     len = iov[USERSA].iov_len = fillUserSaInfo(record, &usersa);
     iov[USERSA_PAD].iov_len = NLMSG_ALIGN(len) - len;
@@ -913,6 +933,9 @@
     len = iov[ENCAP].iov_len = fillNlAttrXfrmEncapTmpl(record, &encap);
     iov[ENCAP_PAD].iov_len = NLA_ALIGN(len) - len;
 
+    len = iov[INTF_ID].iov_len = fillNlAttrXfrmIntfId(record.xfrm_if_id, &xfrm_if_id);
+    iov[INTF_ID_PAD].iov_len = NLA_ALIGN(len) - len;
+
     return sock.sendMessage(XFRM_MSG_UPDSA, NETLINK_REQUEST_FLAGS, 0, &iov);
 }
 
@@ -1026,15 +1049,18 @@
                                                             const XfrmSocket& sock) {
     xfrm_usersa_id said{};
     nlattr_xfrm_mark xfrmmark{};
+    nlattr_xfrm_interface_id xfrm_if_id{};
 
-    enum { NLMSG_HDR, USERSAID, USERSAID_PAD, MARK, MARK_PAD };
+    enum { NLMSG_HDR, USERSAID, USERSAID_PAD, MARK, MARK_PAD, INTF_ID, INTF_ID_PAD };
 
     std::vector<iovec> iov = {
-        {nullptr, 0},      // reserved for the eventual addition of a NLMSG_HDR
-        {&said, 0},     // main usersa_info struct
-        {kPadBytes, 0}, // up to NLMSG_ALIGNTO pad bytes of padding
-        {&xfrmmark, 0}, // adjust size if xfrm mark is present
-        {kPadBytes, 0}, // up to NLATTR_ALIGNTO pad bytes
+            {nullptr, 0},      // reserved for the eventual addition of a NLMSG_HDR
+            {&said, 0},        // main usersa_info struct
+            {kPadBytes, 0},    // up to NLMSG_ALIGNTO pad bytes of padding
+            {&xfrmmark, 0},    // adjust size if xfrm mark is present
+            {kPadBytes, 0},    // up to NLATTR_ALIGNTO pad bytes
+            {&xfrm_if_id, 0},  // adjust size if interface ID is present
+            {kPadBytes, 0},    // up to NLATTR_ALIGNTO pad bytes
     };
 
     int len;
@@ -1044,6 +1070,9 @@
     len = iov[MARK].iov_len = fillNlAttrXfrmMark(record, &xfrmmark);
     iov[MARK_PAD].iov_len = NLA_ALIGN(len) - len;
 
+    len = iov[INTF_ID].iov_len = fillNlAttrXfrmIntfId(record.xfrm_if_id, &xfrm_if_id);
+    iov[INTF_ID_PAD].iov_len = NLA_ALIGN(len) - len;
+
     return sock.sendMessage(XFRM_MSG_DELSA, NETLINK_REQUEST_FLAGS, 0, &iov);
 }
 
@@ -1102,6 +1131,7 @@
     xfrm_userpolicy_info userpolicy{};
     nlattr_user_tmpl usertmpl{};
     nlattr_xfrm_mark xfrmmark{};
+    nlattr_xfrm_interface_id xfrm_if_id{};
 
     enum {
         NLMSG_HDR,
@@ -1111,16 +1141,20 @@
         USERTMPL_PAD,
         MARK,
         MARK_PAD,
+        INTF_ID,
+        INTF_ID_PAD,
     };
 
     std::vector<iovec> iov = {
-        {nullptr, 0},        // reserved for the eventual addition of a NLMSG_HDR
-        {&userpolicy, 0}, // main xfrm_userpolicy_info struct
-        {kPadBytes, 0},   // up to NLMSG_ALIGNTO pad bytes of padding
-        {&usertmpl, 0},   // adjust size if xfrm_user_tmpl struct is present
-        {kPadBytes, 0},   // up to NLATTR_ALIGNTO pad bytes
-        {&xfrmmark, 0},   // adjust size if xfrm mark is present
-        {kPadBytes, 0},   // up to NLATTR_ALIGNTO pad bytes
+            {nullptr, 0},      // reserved for the eventual addition of a NLMSG_HDR
+            {&userpolicy, 0},  // main xfrm_userpolicy_info struct
+            {kPadBytes, 0},    // up to NLMSG_ALIGNTO pad bytes of padding
+            {&usertmpl, 0},    // adjust size if xfrm_user_tmpl struct is present
+            {kPadBytes, 0},    // up to NLATTR_ALIGNTO pad bytes
+            {&xfrmmark, 0},    // adjust size if xfrm mark is present
+            {kPadBytes, 0},    // up to NLATTR_ALIGNTO pad bytes
+            {&xfrm_if_id, 0},  // adjust size if interface ID is present
+            {kPadBytes, 0},    // up to NLATTR_ALIGNTO pad bytes
     };
 
     int len;
@@ -1133,6 +1167,9 @@
     len = iov[MARK].iov_len = fillNlAttrXfrmMark(record, &xfrmmark);
     iov[MARK_PAD].iov_len = NLA_ALIGN(len) - len;
 
+    len = iov[INTF_ID].iov_len = fillNlAttrXfrmIntfId(record.xfrm_if_id, &xfrm_if_id);
+    iov[INTF_ID_PAD].iov_len = NLA_ALIGN(len) - len;
+
     return sock.sendMessage(msgType, NETLINK_REQUEST_FLAGS, 0, &iov);
 }
 
@@ -1141,6 +1178,7 @@
                                                                  XfrmDirection direction) {
     xfrm_userpolicy_id policyid{};
     nlattr_xfrm_mark xfrmmark{};
+    nlattr_xfrm_interface_id xfrm_if_id{};
 
     enum {
         NLMSG_HDR,
@@ -1148,14 +1186,18 @@
         USERPOLICYID_PAD,
         MARK,
         MARK_PAD,
+        INTF_ID,
+        INTF_ID_PAD,
     };
 
     std::vector<iovec> iov = {
-        {nullptr, 0},      // reserved for the eventual addition of a NLMSG_HDR
-        {&policyid, 0}, // main xfrm_userpolicy_id struct
-        {kPadBytes, 0}, // up to NLMSG_ALIGNTO pad bytes of padding
-        {&xfrmmark, 0}, // adjust size if xfrm mark is present
-        {kPadBytes, 0}, // up to NLATTR_ALIGNTO pad bytes
+            {nullptr, 0},      // reserved for the eventual addition of a NLMSG_HDR
+            {&policyid, 0},    // main xfrm_userpolicy_id struct
+            {kPadBytes, 0},    // up to NLMSG_ALIGNTO pad bytes of padding
+            {&xfrmmark, 0},    // adjust size if xfrm mark is present
+            {kPadBytes, 0},    // up to NLATTR_ALIGNTO pad bytes
+            {&xfrm_if_id, 0},  // adjust size if interface ID is present
+            {kPadBytes, 0},    // up to NLATTR_ALIGNTO pad bytes
     };
 
     int len = iov[USERPOLICYID].iov_len = fillUserPolicyId(record, direction, &policyid);
@@ -1164,6 +1206,9 @@
     len = iov[MARK].iov_len = fillNlAttrXfrmMark(record, &xfrmmark);
     iov[MARK_PAD].iov_len = NLA_ALIGN(len) - len;
 
+    len = iov[INTF_ID].iov_len = fillNlAttrXfrmIntfId(record.xfrm_if_id, &xfrm_if_id);
+    iov[INTF_ID_PAD].iov_len = NLA_ALIGN(len) - len;
+
     return sock.sendMessage(XFRM_MSG_DELPOLICY, NETLINK_REQUEST_FLAGS, 0, &iov);
 }
 
@@ -1268,13 +1313,14 @@
                                                           const std::string& localAddress,
                                                           const std::string& remoteAddress,
                                                           int32_t ikey, int32_t okey,
-                                                          bool isUpdate) {
+                                                          int32_t interfaceId, bool isUpdate) {
     ALOGD("XfrmController::%s, line=%d", __FUNCTION__, __LINE__);
     ALOGD("deviceName=%s", deviceName.c_str());
     ALOGD("localAddress=%s", localAddress.c_str());
     ALOGD("remoteAddress=%s", remoteAddress.c_str());
     ALOGD("ikey=%0.8x", ikey);
     ALOGD("okey=%0.8x", okey);
+    ALOGD("interfaceId=%0.8x", interfaceId);
     ALOGD("isUpdate=%d", isUpdate);
 
     uint16_t flags = isUpdate ? NETLINK_REQUEST_FLAGS : NETLINK_ROUTE_CREATE_FLAGS;
diff --git a/server/XfrmController.h b/server/XfrmController.h
index b2f0bff..9208f54 100644
--- a/server/XfrmController.h
+++ b/server/XfrmController.h
@@ -113,6 +113,7 @@
     int transformId; // requestId
     int spi;
     xfrm_mark mark;
+    int xfrm_if_id;
 };
 
 struct XfrmSaInfo : XfrmCommonInfo {
@@ -144,19 +145,21 @@
                                               int32_t* outSpi);
 
     static netdutils::Status ipSecAddSecurityAssociation(
-        int32_t transformId, int32_t mode, const std::string& sourceAddress,
-        const std::string& destinationAddress, int32_t underlyingNetId, int32_t spi,
-        int32_t markValue, int32_t markMask, const std::string& authAlgo,
-        const std::vector<uint8_t>& authKey, int32_t authTruncBits, const std::string& cryptAlgo,
-        const std::vector<uint8_t>& cryptKey, int32_t cryptTruncBits, const std::string& aeadAlgo,
-        const std::vector<uint8_t>& aeadKey, int32_t aeadIcvBits, int32_t encapType,
-        int32_t encapLocalPort, int32_t encapRemotePort);
+            int32_t transformId, int32_t mode, const std::string& sourceAddress,
+            const std::string& destinationAddress, int32_t underlyingNetId, int32_t spi,
+            int32_t markValue, int32_t markMask, const std::string& authAlgo,
+            const std::vector<uint8_t>& authKey, int32_t authTruncBits,
+            const std::string& cryptAlgo, const std::vector<uint8_t>& cryptKey,
+            int32_t cryptTruncBits, const std::string& aeadAlgo,
+            const std::vector<uint8_t>& aeadKey, int32_t aeadIcvBits, int32_t encapType,
+            int32_t encapLocalPort, int32_t encapRemotePort, int32_t xfrmInterfaceId);
 
     static netdutils::Status ipSecDeleteSecurityAssociation(int32_t transformId,
                                                             const std::string& sourceAddress,
                                                             const std::string& destinationAddress,
                                                             int32_t spi, int32_t markValue,
-                                                            int32_t markMask);
+                                                            int32_t markMask,
+                                                            int32_t xfrmInterfaceId);
 
     static netdutils::Status
     ipSecApplyTransportModeTransform(const android::base::unique_fd& socket, int32_t transformId,
@@ -170,23 +173,25 @@
                                                     int32_t direction,
                                                     const std::string& tmplSrcAddress,
                                                     const std::string& tmplDstAddress, int32_t spi,
-                                                    int32_t markValue, int32_t markMask);
+                                                    int32_t markValue, int32_t markMask,
+                                                    int32_t xfrmInterfaceId);
 
     static netdutils::Status ipSecUpdateSecurityPolicy(int32_t transformId, int32_t selAddrFamily,
                                                        int32_t direction,
                                                        const std::string& tmplSrcAddress,
                                                        const std::string& tmplDstAddress,
                                                        int32_t spi, int32_t markValue,
-                                                       int32_t markMask);
+                                                       int32_t markMask, int32_t xfrmInterfaceId);
 
     static netdutils::Status ipSecDeleteSecurityPolicy(int32_t transformId, int32_t selAddrFamily,
                                                        int32_t direction, int32_t markValue,
-                                                       int32_t markMask);
+                                                       int32_t markMask, int32_t xfrmInterfaceId);
 
     static netdutils::Status ipSecAddTunnelInterface(const std::string& deviceName,
                                                      const std::string& localAddress,
                                                      const std::string& remoteAddress, int32_t ikey,
-                                                     int32_t okey, bool isUpdate);
+                                                     int32_t okey, int32_t interfaceId,
+                                                     bool isUpdate);
 
     static netdutils::Status ipSecRemoveTunnelInterface(const std::string& deviceName);
 
@@ -327,9 +332,11 @@
     static netdutils::Status fillXfrmCommonInfo(const std::string& sourceAddress,
                                                 const std::string& destinationAddress, int32_t spi,
                                                 int32_t markValue, int32_t markMask,
-                                                int32_t transformId, XfrmCommonInfo* info);
+                                                int32_t transformId, int32_t xfrmInterfaceId,
+                                                XfrmCommonInfo* info);
     static netdutils::Status fillXfrmCommonInfo(int32_t spi, int32_t markValue, int32_t markMask,
-                                                int32_t transformId, XfrmCommonInfo* info);
+                                                int32_t transformId, int32_t xfrmInterfaceId,
+                                                XfrmCommonInfo* info);
 
     // Top level functions for managing a Transport Mode Transform
     static netdutils::Status addTransportModeTransform(const XfrmSaInfo& record);
@@ -374,7 +381,7 @@
                                                    const std::string& tmplSrcAddress,
                                                    const std::string& tmplDstAddress, int32_t spi,
                                                    int32_t markValue, int32_t markMask,
-                                                   int32_t msgType);
+                                                   int32_t xfrmInterfaceId, int32_t msgType);
     static netdutils::Status updateTunnelModeSecurityPolicy(const XfrmSpInfo& record,
                                                             const XfrmSocket& sock,
                                                             XfrmDirection direction,
diff --git a/server/XfrmControllerTest.cpp b/server/XfrmControllerTest.cpp
index 5539dab..be819d8 100644
--- a/server/XfrmControllerTest.cpp
+++ b/server/XfrmControllerTest.cpp
@@ -103,7 +103,8 @@
 static constexpr int DROID_SPI = 0xD1201D;
 static constexpr size_t KEY_LENGTH = 32;
 static constexpr int NLMSG_DEFAULTSIZE = 8192;
-static constexpr uint16_t TEST_XFRM_OUTPUT_MARK = 0x512;
+static constexpr uint32_t TEST_XFRM_UNDERLYING_NET = 0x512;
+static constexpr uint32_t TEST_XFRM_IF_ID = 0x1234;
 static constexpr uint32_t TEST_XFRM_MARK = 0x123;
 static constexpr uint32_t TEST_XFRM_MASK = 0xFFFFFFFF;
 
@@ -277,10 +278,16 @@
 
     // Calculate the length of the expected netlink message.
     size_t expectedMsgLength =
-        NLMSG_HDRLEN + NLMSG_ALIGN(sizeof(xfrm_usersa_info)) +
-        NLA_ALIGN(offsetof(XfrmController::nlattr_algo_crypt, key) + KEY_LENGTH) +
-        NLA_ALIGN(offsetof(XfrmController::nlattr_algo_auth, key) + KEY_LENGTH) +
-        NLA_ALIGN(sizeof(XfrmController::nlattr_xfrm_mark));
+            NLMSG_HDRLEN + NLMSG_ALIGN(sizeof(xfrm_usersa_info)) +
+            NLA_ALIGN(offsetof(XfrmController::nlattr_algo_crypt, key) + KEY_LENGTH) +
+            NLA_ALIGN(offsetof(XfrmController::nlattr_algo_auth, key) + KEY_LENGTH) +
+            NLA_ALIGN(sizeof(XfrmController::nlattr_xfrm_mark));
+
+    uint32_t testIfId = 0;
+    if (mode == XfrmMode::TUNNEL) {
+        expectedMsgLength += NLA_ALIGN(sizeof(XfrmController::nlattr_xfrm_interface_id));
+        testIfId = TEST_XFRM_IF_ID;
+    }
 
     if (underlying_netid) {
         expectedMsgLength += NLA_ALIGN(sizeof(XfrmController::nlattr_xfrm_output_mark));
@@ -294,12 +301,13 @@
 
     XfrmController ctrl;
     Status res = ctrl.ipSecAddSecurityAssociation(
-        1 /* resourceId */, static_cast<int>(mode), localAddr, remoteAddr,
-        underlying_netid /* underlying netid */, DROID_SPI, TEST_XFRM_MARK /* mark */,
-        TEST_XFRM_MASK /* mask */, "hmac(sha256)" /* auth algo */,
-        authKey, 128 /* auth trunc length */, "cbc(aes)" /* encryption algo */,
-        cryptKey, 0 /* crypt trunc length? */, "" /* AEAD algo */, {}, 0,
-        static_cast<int>(XfrmEncapType::NONE), 0 /* local port */, 0 /* remote port */);
+            1 /* resourceId */, static_cast<int>(mode), localAddr, remoteAddr,
+            underlying_netid /* underlying netid */, DROID_SPI, TEST_XFRM_MARK /* mark */,
+            TEST_XFRM_MASK /* mask */, "hmac(sha256)" /* auth algo */, authKey,
+            128 /* auth trunc length */, "cbc(aes)" /* encryption algo */, cryptKey,
+            0 /* crypt trunc length? */, "" /* AEAD algo */, {}, 0,
+            static_cast<int>(XfrmEncapType::NONE), 0 /* local port */, 0 /* remote port */,
+            testIfId /* xfrm_if_id */);
 
     EXPECT_TRUE(isOk(res)) << res;
     EXPECT_EQ(expectedMsgLength, nlMsgBuf.size());
@@ -334,8 +342,9 @@
     XfrmController::nlattr_algo_auth authAlgo{};
     XfrmController::nlattr_xfrm_mark mark{};
     XfrmController::nlattr_xfrm_output_mark outputmark{};
-    auto attrHandler = [&encryptAlgo, &authAlgo, &mark, &outputmark](const nlattr& attr,
-                                                                     const Slice& attr_payload) {
+    XfrmController::nlattr_xfrm_interface_id xfrm_if_id{};
+    auto attrHandler = [&encryptAlgo, &authAlgo, &mark, &outputmark, &xfrm_if_id](
+                               const nlattr& attr, const Slice& attr_payload) {
         Slice buf = attr_payload;
         if (attr.nla_type == XFRMA_ALG_CRYPT) {
             encryptAlgo.hdr = attr;
@@ -353,6 +362,9 @@
         } else if (attr.nla_type == XFRMA_OUTPUT_MARK) {
             mark.hdr = attr;
             netdutils::extract(buf, outputmark.outputMark);
+        } else if (attr.nla_type == XFRMA_IF_ID) {
+            xfrm_if_id.hdr = attr;
+            netdutils::extract(buf, xfrm_if_id.if_id);
         } else {
             FAIL() << "Unexpected nlattr type: " << attr.nla_type;
         }
@@ -366,6 +378,8 @@
                         reinterpret_cast<void*>(&authAlgo.key), KEY_LENGTH));
     EXPECT_EQ(TEST_XFRM_MARK, mark.mark.v);
     EXPECT_EQ(TEST_XFRM_MASK, mark.mark.m);
+    EXPECT_EQ(testIfId, xfrm_if_id.if_id);
+
     if (underlying_netid) {
         Fwmark fwmark;
         fwmark.intValue = outputmark.outputMark;
@@ -389,7 +403,7 @@
 TEST_P(XfrmControllerParameterizedTest, TestTunnelModeIpSecAddSecurityAssociationWithOutputMark) {
     const int version = GetParam();
     testIpSecAddSecurityAssociation(version, mockSyscalls, XfrmMode::TUNNEL,
-                                    TEST_XFRM_OUTPUT_MARK);
+                                    TEST_XFRM_UNDERLYING_NET);
 }
 
 TEST_F(XfrmControllerTest, TestIpSecAddSecurityAssociationIPv4Encap) {
@@ -403,9 +417,9 @@
 
     XfrmController ctrl;
     Status res = ctrl.ipSecAddSecurityAssociation(
-        1, static_cast<int>(XfrmMode::TRANSPORT),
-        LOCALHOST_V6, TEST_ADDR_V6, 0, DROID_SPI, 0, 0, "hmac(sha256)", {}, 128, "cbc(aes)",
-        {}, 0, "", {}, 0, static_cast<int>(XfrmEncapType::ESPINUDP_NON_IKE), 0, 0);
+            1, static_cast<int>(XfrmMode::TRANSPORT), LOCALHOST_V6, TEST_ADDR_V6, 0, DROID_SPI, 0,
+            0, "hmac(sha256)", {}, 128, "cbc(aes)", {}, 0, "", {}, 0,
+            static_cast<int>(XfrmEncapType::ESPINUDP_NON_IKE), 0, 0, 0);
 
     EXPECT_FALSE(isOk(res)) << "IPv6 UDP encap not rejected";
 }
@@ -508,7 +522,8 @@
     Slice responseSlice = netdutils::makeSlice(response);
 
     size_t expectedMsgLength = NLMSG_HDRLEN + NLMSG_ALIGN(sizeof(xfrm_usersa_id)) +
-                               NLA_ALIGN(sizeof(XfrmController::nlattr_xfrm_mark));
+                               NLA_ALIGN(sizeof(XfrmController::nlattr_xfrm_mark)) +
+                               NLA_ALIGN(sizeof(XfrmController::nlattr_xfrm_interface_id));
 
     std::vector<uint8_t> nlMsgBuf;
     EXPECT_CALL(mockSyscalls, writev(_, _))
@@ -518,7 +533,8 @@
 
     XfrmController ctrl;
     Status res = ctrl.ipSecDeleteSecurityAssociation(1 /* resourceId */, localAddr, remoteAddr,
-                                                     DROID_SPI, TEST_XFRM_MARK, TEST_XFRM_MASK);
+                                                     DROID_SPI, TEST_XFRM_MARK, TEST_XFRM_MASK,
+                                                     TEST_XFRM_IF_ID);
 
     EXPECT_TRUE(isOk(res)) << res;
     EXPECT_EQ(expectedMsgLength, nlMsgBuf.size());
@@ -526,11 +542,24 @@
     Slice nlMsgSlice = netdutils::makeSlice(nlMsgBuf);
     nlMsgSlice = netdutils::drop(nlMsgSlice, NLMSG_HDRLEN);
 
+    // Extract and check the usersa_id
     xfrm_usersa_id said{};
     netdutils::extract(nlMsgSlice, said);
-
+    nlMsgSlice = drop(nlMsgSlice, sizeof(xfrm_usersa_id));
     EXPECT_EQ(htonl(DROID_SPI), said.spi);
     expectAddressEquals(family, remoteAddr, said.daddr);
+
+    // Extract and check the mark.
+    XfrmController::nlattr_xfrm_mark mark{};
+    netdutils::extract(nlMsgSlice, mark);
+    nlMsgSlice = drop(nlMsgSlice, sizeof(XfrmController::nlattr_xfrm_mark));
+    EXPECT_EQ(TEST_XFRM_MARK, mark.mark.v);
+    EXPECT_EQ(TEST_XFRM_MASK, mark.mark.m);
+
+    // Extract and check the interface id.
+    XfrmController::nlattr_xfrm_interface_id xfrm_if_id{};
+    netdutils::extract(nlMsgSlice, xfrm_if_id);
+    EXPECT_EQ(TEST_XFRM_IF_ID, xfrm_if_id.if_id);
 }
 
 TEST_P(XfrmControllerParameterizedTest, TestIpSecAddSecurityPolicy) {
@@ -545,7 +574,8 @@
 
     size_t expectedMsgLength = NLMSG_HDRLEN + NLMSG_ALIGN(sizeof(xfrm_userpolicy_info)) +
                                NLMSG_ALIGN(sizeof(XfrmController::nlattr_user_tmpl)) +
-                               NLMSG_ALIGN(sizeof(XfrmController::nlattr_xfrm_mark));
+                               NLMSG_ALIGN(sizeof(XfrmController::nlattr_xfrm_mark)) +
+                               NLMSG_ALIGN(sizeof(XfrmController::nlattr_xfrm_interface_id));
 
     std::vector<uint8_t> nlMsgBuf;
     EXPECT_CALL(mockSyscalls, writev(_, _))
@@ -556,7 +586,7 @@
     XfrmController ctrl;
     Status res = ctrl.ipSecAddSecurityPolicy(
             1 /* resourceId */, family, static_cast<int>(XfrmDirection::OUT), localAddr, remoteAddr,
-            0 /* SPI */, TEST_XFRM_MARK, TEST_XFRM_MASK);
+            0 /* SPI */, TEST_XFRM_MARK, TEST_XFRM_MASK, TEST_XFRM_IF_ID);
 
     EXPECT_TRUE(isOk(res)) << res;
     EXPECT_EQ(expectedMsgLength, nlMsgBuf.size());
@@ -578,7 +608,9 @@
     // Extract and check the user tmpl and mark.
     XfrmController::nlattr_user_tmpl usertmpl{};
     XfrmController::nlattr_xfrm_mark mark{};
-    auto attrHandler = [&usertmpl, &mark](const nlattr& attr, const Slice& attr_payload) {
+    XfrmController::nlattr_xfrm_interface_id xfrm_if_id{};
+    auto attrHandler = [&usertmpl, &mark, &xfrm_if_id](const nlattr& attr,
+                                                       const Slice& attr_payload) {
         Slice buf = attr_payload;
         if (attr.nla_type == XFRMA_TMPL) {
             usertmpl.hdr = attr;
@@ -586,6 +618,9 @@
         } else if (attr.nla_type == XFRMA_MARK) {
             mark.hdr = attr;
             netdutils::extract(buf, mark.mark);
+        } else if (attr.nla_type == XFRMA_IF_ID) {
+            mark.hdr = attr;
+            netdutils::extract(buf, xfrm_if_id.if_id);
         } else {
             FAIL() << "Unexpected nlattr type: " << attr.nla_type;
         }
@@ -595,7 +630,7 @@
     expectAddressEquals(family, remoteAddr, usertmpl.tmpl.id.daddr);
     EXPECT_EQ(TEST_XFRM_MARK, mark.mark.v);
     EXPECT_EQ(TEST_XFRM_MASK, mark.mark.m);
-
+    EXPECT_EQ(TEST_XFRM_IF_ID, xfrm_if_id.if_id);
 }
 
 TEST_P(XfrmControllerParameterizedTest, TestIpSecUpdateSecurityPolicy) {
@@ -610,7 +645,8 @@
 
     size_t expectedMsgLength = NLMSG_HDRLEN + NLMSG_ALIGN(sizeof(xfrm_userpolicy_info)) +
                                NLMSG_ALIGN(sizeof(XfrmController::nlattr_user_tmpl)) +
-                               NLMSG_ALIGN(sizeof(XfrmController::nlattr_xfrm_mark));
+                               NLMSG_ALIGN(sizeof(XfrmController::nlattr_xfrm_mark)) +
+                               NLMSG_ALIGN(sizeof(XfrmController::nlattr_xfrm_interface_id));
 
     std::vector<uint8_t> nlMsgBuf;
     EXPECT_CALL(mockSyscalls, writev(_, _))
@@ -621,7 +657,7 @@
     XfrmController ctrl;
     Status res = ctrl.ipSecUpdateSecurityPolicy(
             1 /* resourceId */, family, static_cast<int>(XfrmDirection::OUT), localAddr, remoteAddr,
-            0 /* SPI */, 0 /* Mark */, 0 /* Mask */);
+            0 /* SPI */, 0 /* Mark */, 0 /* Mask */, TEST_XFRM_IF_ID /* xfrm_if_id */);
 
     EXPECT_TRUE(isOk(res)) << res;
     EXPECT_EQ(expectedMsgLength, nlMsgBuf.size());
@@ -643,7 +679,8 @@
     Slice responseSlice = netdutils::makeSlice(response);
 
     size_t expectedMsgLength = NLMSG_HDRLEN + NLMSG_ALIGN(sizeof(xfrm_userpolicy_id)) +
-                               NLMSG_ALIGN(sizeof(XfrmController::nlattr_xfrm_mark));
+                               NLMSG_ALIGN(sizeof(XfrmController::nlattr_xfrm_mark)) +
+                               NLMSG_ALIGN(sizeof(XfrmController::nlattr_xfrm_interface_id));
 
     std::vector<uint8_t> nlMsgBuf;
     EXPECT_CALL(mockSyscalls, writev(_, _))
@@ -654,7 +691,7 @@
     XfrmController ctrl;
     Status res = ctrl.ipSecDeleteSecurityPolicy(1 /* resourceId */, family,
                                                 static_cast<int>(XfrmDirection::OUT),
-                                                TEST_XFRM_MARK, TEST_XFRM_MASK);
+                                                TEST_XFRM_MARK, TEST_XFRM_MASK, TEST_XFRM_IF_ID);
 
     EXPECT_TRUE(isOk(res)) << res;
     EXPECT_EQ(expectedMsgLength, nlMsgBuf.size());
@@ -669,12 +706,18 @@
 
     // Drop the user policy id.
     nlMsgSlice = drop(nlMsgSlice, NLA_ALIGN(sizeof(xfrm_userpolicy_id)));
+
     // Extract and check the mark.
     XfrmController::nlattr_xfrm_mark mark{};
     netdutils::extract(nlMsgSlice, mark);
+    nlMsgSlice = drop(nlMsgSlice, sizeof(XfrmController::nlattr_xfrm_mark));
     EXPECT_EQ(TEST_XFRM_MARK, mark.mark.v);
     EXPECT_EQ(TEST_XFRM_MASK, mark.mark.m);
 
+    // Extract and check the interface id.
+    XfrmController::nlattr_xfrm_interface_id xfrm_if_id{};
+    netdutils::extract(nlMsgSlice, xfrm_if_id);
+    EXPECT_EQ(TEST_XFRM_IF_ID, xfrm_if_id.if_id);
 }
 
 // TODO: Add tests for VTIs, ensuring that we are sending the correct data over netlink.
diff --git a/server/binder/android/net/INetd.aidl b/server/binder/android/net/INetd.aidl
index ad631e4..48d064f 100644
--- a/server/binder/android/net/INetd.aidl
+++ b/server/binder/android/net/INetd.aidl
@@ -342,7 +342,7 @@
     * @param mode either Transport or Tunnel mode
     * @param sourceAddress InetAddress as string for the sending endpoint
     * @param destinationAddress InetAddress as string for the receiving endpoint
-    * @param underlyingNetId the netId of the network to which the SA is applied
+    * @param underlyingNetId the netId of the network to which the SA is applied.
     * @param spi a 32-bit unique ID allocated to the user
     * @param markValue a 32-bit unique ID chosen by the user
     * @param markMask a 32-bit mask chosen by the user
@@ -359,6 +359,8 @@
     * @param encapType encapsulation type used (if any) for the udp encap socket
     * @param encapLocalPort the port number on the host to be used in encap packets
     * @param encapRemotePort the port number of the remote to be used for encap packets
+    * @param interfaceId the identifier for the IPsec tunnel interface.
+    *        Only accepted for tunnel mode SAs.
     */
     void ipSecAddSecurityAssociation(
             int transformId,
@@ -374,7 +376,8 @@
             in @utf8InCpp String aeadAlgo, in byte[] aeadKey, in int aeadIcvBits,
             int encapType,
             int encapLocalPort,
-            int encapRemotePort);
+            int encapRemotePort,
+            int interfaceId);
 
    /**
     * Delete a previously created security association identified by the provided parameters
@@ -385,6 +388,7 @@
     * @param spi a requested 32-bit unique ID allocated to the user
     * @param markValue a 32-bit unique ID chosen by the user
     * @param markMask a 32-bit mask chosen by the user
+    * @param interfaceId the identifier for the IPsec tunnel interface.
     */
     void ipSecDeleteSecurityAssociation(
             int transformId,
@@ -392,7 +396,8 @@
             in @utf8InCpp String destinationAddress,
             int spi,
             int markValue,
-            int markMask);
+            int markMask,
+            int interfaceId);
 
    /**
     * Apply a previously created SA to a specified socket, starting IPsec on that socket
@@ -432,6 +437,7 @@
     * @param spi a 32-bit unique ID allocated to the user
     * @param markValue a 32-bit unique ID chosen by the user
     * @param markMask a 32-bit mask chosen by the user
+    * @param interfaceId the identifier for the IPsec tunnel interface.
     */
     void ipSecAddSecurityPolicy(
             int transformId,
@@ -441,7 +447,8 @@
             in @utf8InCpp String tmplDstAddress,
             int spi,
             int markValue,
-            int markMask);
+            int markMask,
+            int interfaceId);
 
    /**
     * Updates an IPsec global policy.
@@ -454,6 +461,7 @@
     * @param spi a 32-bit unique ID allocated to the user
     * @param markValue a 32-bit unique ID chosen by the user
     * @param markMask a 32-bit mask chosen by the user
+    * @param interfaceId the identifier for the IPsec tunnel interface.
     */
     void ipSecUpdateSecurityPolicy(
             int transformId,
@@ -463,7 +471,8 @@
             in @utf8InCpp String tmplDstAddress,
             int spi,
             int markValue,
-            int markMask);
+            int markMask,
+            int interfaceId);
 
    /**
     * Deletes an IPsec global policy.
@@ -476,13 +485,15 @@
     * @param direction DIRECTION_IN or DIRECTION_OUT
     * @param markValue a 32-bit unique ID chosen by the user
     * @param markMask a 32-bit mask chosen by the user
+    * @param interfaceId the identifier for the IPsec tunnel interface.
     */
     void ipSecDeleteSecurityPolicy(
             int transformId,
             int selAddrFamily,
             int direction,
             int markValue,
-            int markMask);
+            int markMask,
+            int interfaceId);
 
     // This could not be declared as @uft8InCpp; thus, when used in native code it must be
     // converted from a UTF-16 string to an ASCII string.
@@ -496,13 +507,15 @@
     * @param remoteAddress InetAddress as string for the remote endpoint
     * @param iKey, to match Policies and SAs for input packets.
     * @param oKey, to match Policies and SAs for output packets.
+    * @param interfaceId the identifier for the IPsec tunnel interface.
     */
     void ipSecAddTunnelInterface(
             in @utf8InCpp String deviceName,
             in @utf8InCpp String localAddress,
             in @utf8InCpp String remoteAddress,
             int iKey,
-            int oKey);
+            int oKey,
+            int interfaceId);
 
    /**
     * Update a IPsec Tunnel Interface.
@@ -512,13 +525,15 @@
     * @param remoteAddress InetAddress as string for the remote endpoint
     * @param iKey, to match Policies and SAs for input packets.
     * @param oKey, to match Policies and SAs for output packets.
+    * @param interfaceId the identifier for the IPsec tunnel interface.
     */
     void ipSecUpdateTunnelInterface(
             in @utf8InCpp String deviceName,
             in @utf8InCpp String localAddress,
             in @utf8InCpp String remoteAddress,
             int iKey,
-            int oKey);
+            int oKey,
+            int interfaceId);
 
    /**
     * Removes a IPsec Tunnel Interface.