Add fallback logic and enable XFRM-I support in netd

This patch adds fallback logic, checking for XFRM-I kernel support, and
switching to use XFRM-I if supported. Fallbacks to VTIs are provided for
backward compatibility with 4.4 kernels. Parameters for VTI versus
XFRM-I are selected based on the kernel support for XFRM interfaces.

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

Bug: 77856928
Test: Binder tests updated, passing. CTS & unit tests also passing
Change-Id: Idf90adeec0d499fe4d566e4203f0eabb2b94fffa
diff --git a/server/XfrmController.cpp b/server/XfrmController.cpp
index 297a4b0..cf4d367 100644
--- a/server/XfrmController.cpp
+++ b/server/XfrmController.cpp
@@ -31,6 +31,7 @@
 #include <inttypes.h>
 
 #include <arpa/inet.h>
+#include <net/if.h>
 #include <netinet/in.h>
 
 #include <sys/socket.h>
@@ -53,6 +54,7 @@
 #include <log/log.h>
 #include <log/log_properties.h>
 #include <logwrap/logwrap.h>
+#include "DumpWriter.h"
 #include "Fwmark.h"
 #include "InterfaceController.h"
 #include "NetdConstants.h"
@@ -60,6 +62,9 @@
 #include "Permission.h"
 #include "ResponseCode.h"
 #include "XfrmController.h"
+#include "android-base/stringprintf.h"
+#include "android-base/strings.h"
+#include "android-base/unique_fd.h"
 #include "netdutils/Fd.h"
 #include "netdutils/Slice.h"
 #include "netdutils/Syscalls.h"
@@ -93,6 +98,9 @@
 constexpr const char* INFO_KIND_VTI6 = "vti6";
 constexpr const char* INFO_KIND_XFRMI = "xfrm";
 constexpr int INFO_KIND_MAX_LEN = 8;
+constexpr int LOOPBACK_IFINDEX = 1;
+
+bool mIsXfrmIntfSupported = false;
 
 static inline bool isEngBuild() {
     static const std::string sBuildType = android::base::GetProperty("ro.build.type", "user");
@@ -386,8 +394,15 @@
 //
 XfrmController::XfrmController(void) {}
 
+// Test-only constructor allowing override of XFRM Interface support checks
+XfrmController::XfrmController(bool xfrmIntfSupport) {
+    mIsXfrmIntfSupported = xfrmIntfSupport;
+}
+
 netdutils::Status XfrmController::Init() {
     RETURN_IF_NOT_OK(flushInterfaces());
+    mIsXfrmIntfSupported = isXfrmIntfSupported();
+
     XfrmSocketImpl sock;
     RETURN_IF_NOT_OK(sock.open());
     RETURN_IF_NOT_OK(flushSaDb(sock));
@@ -424,6 +439,18 @@
     return s.sendMessage(XFRM_MSG_FLUSHPOLICY, NETLINK_REQUEST_FLAGS, 0, &iov);
 }
 
+bool XfrmController::isXfrmIntfSupported() {
+    const char* IPSEC_TEST_INTF_NAME = "ipsec_test";
+    const int32_t XFRM_TEST_IF_ID = 0xFFFF;
+
+    bool errored = false;
+    errored |=
+            ipSecAddXfrmInterface(IPSEC_TEST_INTF_NAME, XFRM_TEST_IF_ID, NETLINK_ROUTE_CREATE_FLAGS)
+                    .code();
+    errored |= ipSecRemoveTunnelInterface(IPSEC_TEST_INTF_NAME).code();
+    return !errored;
+}
+
 netdutils::Status XfrmController::ipSecSetEncapSocketOwner(const android::base::unique_fd& socket,
                                                            int newUid, uid_t callerUid) {
     ALOGD("XfrmController:%s, line=%d", __FUNCTION__, __LINE__);
@@ -656,9 +683,13 @@
                                                      XfrmCommonInfo* info) {
     info->transformId = transformId;
     info->spi = htonl(spi);
-    info->mark.v = markValue;
-    info->mark.m = markMask;
-    info->xfrm_if_id = xfrmInterfaceId;
+
+    if (mIsXfrmIntfSupported) {
+        info->xfrm_if_id = xfrmInterfaceId;
+    } else {
+        info->mark.v = markValue;
+        info->mark.m = markMask;
+    }
 
     return netdutils::status::ok;
 }
@@ -904,11 +935,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
+    if (record.mode != XfrmMode::TUNNEL &&
+        (record.xfrm_if_id != 0 || record.netId != 0 || record.mark.v != 0 || record.mark.m != 0)) {
         return netdutils::statusFromErrno(EINVAL,
-                                          "xfrm_if_id parameter invalid for non "
-                                          "tunnel-mode transform");
+                                          "xfrm_if_id, mark and netid parameters invalid "
+                                          "for non tunnel-mode transform");
+    } else if (record.mode == XfrmMode::TUNNEL && !mIsXfrmIntfSupported && record.xfrm_if_id != 0) {
+        return netdutils::statusFromErrno(EINVAL, "xfrm_if_id set for VTI Security Association");
     }
 
     int len;
@@ -1255,6 +1288,11 @@
 }
 
 int XfrmController::fillNlAttrXfrmMark(const XfrmCommonInfo& record, nlattr_xfrm_mark* mark) {
+    // Do not set if we were not given a mark
+    if (record.mark.v == 0 && record.mark.m == 0) {
+        return 0;
+    }
+
     mark->mark.v = record.mark.v; // set to 0 if it's not used
     mark->mark.m = record.mark.m; // set to 0 if it's not used
     int len = NLA_HDRLEN + sizeof(xfrm_mark);
@@ -1325,12 +1363,15 @@
 
     uint16_t flags = isUpdate ? NETLINK_REQUEST_FLAGS : NETLINK_ROUTE_CREATE_FLAGS;
 
-    return ipSecAddVirtualTunnelInterface(deviceName, localAddress, remoteAddress, ikey, okey,
-                                          flags);
+    if (mIsXfrmIntfSupported) {
+        return ipSecAddXfrmInterface(deviceName, interfaceId, flags);
+    } else {
+        return ipSecAddVirtualTunnelInterface(deviceName, localAddress, remoteAddress, ikey, okey,
+                                              flags);
+    }
 }
 
 netdutils::Status XfrmController::ipSecAddXfrmInterface(const std::string& deviceName,
-                                                        int32_t underlyingInterface,
                                                         int32_t interfaceId, uint16_t flags) {
     ALOGD("XfrmController::%s, line=%d", __FUNCTION__, __LINE__);
 
@@ -1391,7 +1432,10 @@
                                                  .nla_len = RTA_LENGTH(sizeof(uint32_t)),
                                                  .nla_type = IFLA_XFRM_LINK,
                                          },
-                                 .xfrmLink = static_cast<uint32_t>(underlyingInterface),
+                                 //   Always use LOOPBACK_IFINDEX, since we use output marks for
+                                 //   route lookup instead. The use case of having a Network with
+                                 //   loopback in it is unsupported in tunnel mode.
+                                 .xfrmLink = static_cast<uint32_t>(LOOPBACK_IFINDEX),
 
                                  .xfrmIfIdNla =
                                          {
@@ -1575,5 +1619,13 @@
     return netdutils::statusFromErrno(ret, "Error in deleting IpSec interface " + deviceName);
 }
 
+void XfrmController::dump(DumpWriter& dw) {
+    ScopedIndent indentForXfrmController(dw);
+    dw.println("XfrmController");
+
+    ScopedIndent indentForXfrmISupport(dw);
+    dw.println("XFRM-I support: %d", mIsXfrmIntfSupported);
+}
+
 } // namespace net
 } // namespace android