From c5e280590abcf376903ab6fb3da6e02d6784aecb Mon Sep 17 00:00:00 2001
From: Yan Yan <evitayan@google.com>
Date: Fri, 10 Nov 2023 02:24:29 +0000
Subject: [PATCH] [XFRM_MSG_GETSA] Support XFRM_MSG_GETSA message

Bug: 308011229
Test: atest NetworkStaticLibTests:com.android.net.moduletests.util.netlink
      (new tests added)
Change-Id: I42e423c09ffb733a809636360468c963c7864872
---
 .../module/util/netlink/NetlinkMessage.java   |  22 ++++
 .../module/util/netlink/StructNlMsgHdr.java   |   8 ++
 .../netlink/xfrm/XfrmNetlinkGetSaMessage.java | 116 ++++++++++++++++++
 .../util/netlink/xfrm/XfrmNetlinkMessage.java |  40 +++++-
 .../netlink/xfrm/StructXfrmUsersaIdTest.java  |   2 +-
 .../xfrm/XfrmNetlinkGetSaMessageTest.java     |  75 +++++++++++
 6 files changed, 260 insertions(+), 3 deletions(-)
 create mode 100644 staticlibs/device/com/android/net/module/util/netlink/xfrm/XfrmNetlinkGetSaMessage.java
 create mode 100644 staticlibs/tests/unit/src/com/android/net/module/util/netlink/xfrm/XfrmNetlinkGetSaMessageTest.java

diff --git a/staticlibs/device/com/android/net/module/util/netlink/NetlinkMessage.java b/staticlibs/device/com/android/net/module/util/netlink/NetlinkMessage.java
index 9e1e26e760..111e0bae9a 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/NetlinkMessage.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/NetlinkMessage.java
@@ -16,12 +16,17 @@
 
 package com.android.net.module.util.netlink;
 
+import static com.android.net.module.util.netlink.xfrm.XfrmNetlinkMessage.NETLINK_XFRM;
+
 import android.system.OsConstants;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.net.module.util.netlink.xfrm.XfrmNetlinkMessage;
+
 import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
 
 /**
  * NetlinkMessage base class for other, more specific netlink message types.
@@ -75,6 +80,8 @@ public class NetlinkMessage {
             parsed = parseInetDiagMessage(nlmsghdr, byteBuffer);
         } else if (nlFamily == OsConstants.NETLINK_NETFILTER) {
             parsed = parseNfMessage(nlmsghdr, byteBuffer);
+        } else if (nlFamily == NETLINK_XFRM) {
+            parsed = parseXfrmMessage(nlmsghdr, byteBuffer);
         } else {
             parsed = null;
         }
@@ -168,4 +175,19 @@ public class NetlinkMessage {
             default: return null;
         }
     }
+
+    @Nullable
+    private static NetlinkMessage parseXfrmMessage(
+            @NonNull final StructNlMsgHdr nlmsghdr, @NonNull final ByteBuffer byteBuffer) {
+        return (NetlinkMessage) XfrmNetlinkMessage.parseXfrmInternal(nlmsghdr, byteBuffer);
+    }
+
+    /** A convenient method to create a ByteBuffer for encoding a new message */
+    protected static ByteBuffer newNlMsgByteBuffer(int payloadLen) {
+        final int length = StructNlMsgHdr.STRUCT_SIZE + payloadLen;
+        final ByteBuffer byteBuffer = ByteBuffer.allocate(length);
+        byteBuffer.order(ByteOrder.nativeOrder());
+
+        return byteBuffer;
+    }
 }
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructNlMsgHdr.java b/staticlibs/device/com/android/net/module/util/netlink/StructNlMsgHdr.java
index 5052cb8a06..ff37639283 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/StructNlMsgHdr.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructNlMsgHdr.java
@@ -128,6 +128,14 @@ public class StructNlMsgHdr {
         nlmsg_pid = 0;
     }
 
+    public StructNlMsgHdr(int payloadLen, short type, short flags, int seq) {
+        nlmsg_len = StructNlMsgHdr.STRUCT_SIZE + payloadLen;
+        nlmsg_type = type;
+        nlmsg_flags = flags;
+        nlmsg_seq = seq;
+        nlmsg_pid = 0;
+    }
+
     /**
      * Write netlink message header to ByteBuffer.
      */
diff --git a/staticlibs/device/com/android/net/module/util/netlink/xfrm/XfrmNetlinkGetSaMessage.java b/staticlibs/device/com/android/net/module/util/netlink/xfrm/XfrmNetlinkGetSaMessage.java
new file mode 100644
index 0000000000..680a7ca89e
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/xfrm/XfrmNetlinkGetSaMessage.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util.netlink.xfrm;
+
+import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST;
+import static com.android.net.module.util.netlink.xfrm.XfrmNetlinkMessage.XFRM_MSG_GETSA;
+
+import android.system.OsConstants;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.netlink.StructNlMsgHdr;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+
+/**
+ * An XfrmNetlinkMessage subclass for XFRM_MSG_GETSA messages.
+ *
+ * <p>see include/uapi/linux/xfrm.h
+ *
+ * <p>XFRM_MSG_GETSA syntax
+ *
+ * <ul>
+ *   <li>TLV: xfrm_usersa_id
+ *   <li>Optional Attributes: XFRMA_MARK, XFRMA_SRCADDR
+ * </ul>
+ *
+ * @hide
+ */
+public class XfrmNetlinkGetSaMessage extends XfrmNetlinkMessage {
+    @NonNull private final StructXfrmUsersaId mXfrmUsersaId;
+
+    private XfrmNetlinkGetSaMessage(
+            @NonNull final StructNlMsgHdr header, @NonNull final StructXfrmUsersaId xfrmUsersaId) {
+        super(header);
+        mXfrmUsersaId = xfrmUsersaId;
+    }
+
+    private XfrmNetlinkGetSaMessage(
+            @NonNull final StructNlMsgHdr header,
+            @NonNull final InetAddress destAddress,
+            long spi,
+            short proto) {
+        super(header);
+
+        final int family =
+                destAddress instanceof Inet4Address ? OsConstants.AF_INET : OsConstants.AF_INET6;
+        mXfrmUsersaId = new StructXfrmUsersaId(destAddress, spi, family, proto);
+    }
+
+    @Override
+    protected void packPayload(@NonNull final ByteBuffer byteBuffer) {
+        mXfrmUsersaId.writeToByteBuffer(byteBuffer);
+    }
+
+    /**
+     * Parse XFRM_MSG_GETSA message from ByteBuffer.
+     *
+     * <p>This method should be called from NetlinkMessage#parse(ByteBuffer, int) for generic
+     * message validation and processing
+     *
+     * @param nlmsghdr netlink message header.
+     * @param byteBuffer the ByteBuffer instance that wraps the raw netlink message bytes. MUST be
+     *                   host order
+     */
+    @Nullable
+    static XfrmNetlinkGetSaMessage parseInternal(
+            @NonNull final StructNlMsgHdr nlmsghdr, @NonNull final ByteBuffer byteBuffer) {
+        final StructXfrmUsersaId xfrmUsersaId = Struct.parse(StructXfrmUsersaId.class, byteBuffer);
+        if (xfrmUsersaId == null) {
+            return null;
+        }
+
+        // Attributes not supported. Don't bother handling them.
+
+        return new XfrmNetlinkGetSaMessage(nlmsghdr, xfrmUsersaId);
+    }
+
+    /** A convenient method to create a XFRM_MSG_GETSA message. */
+    public static byte[] newXfrmNetlinkGetSaMessage(
+            @NonNull final InetAddress destAddress, long spi, short proto) {
+        final int payloadLen = StructXfrmUsersaId.STRUCT_SIZE;
+
+        final StructNlMsgHdr nlmsghdr =
+                new StructNlMsgHdr(payloadLen, XFRM_MSG_GETSA, NLM_F_REQUEST, 0);
+        final XfrmNetlinkGetSaMessage message =
+                new XfrmNetlinkGetSaMessage(nlmsghdr, destAddress, spi, proto);
+
+        final ByteBuffer byteBuffer = newNlMsgByteBuffer(payloadLen);
+        message.pack(byteBuffer);
+
+        return byteBuffer.array();
+    }
+
+    public StructXfrmUsersaId getStructXfrmUsersaId() {
+        return mXfrmUsersaId;
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/xfrm/XfrmNetlinkMessage.java b/staticlibs/device/com/android/net/module/util/netlink/xfrm/XfrmNetlinkMessage.java
index ee34e5716c..e15342b061 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/xfrm/XfrmNetlinkMessage.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/xfrm/XfrmNetlinkMessage.java
@@ -17,10 +17,13 @@
 package com.android.net.module.util.netlink.xfrm;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import com.android.net.module.util.netlink.NetlinkMessage;
 import com.android.net.module.util.netlink.StructNlMsgHdr;
 
+import java.nio.ByteBuffer;
+
 /** Base calss for XFRM netlink messages */
 // Developer notes: The Linux kernel includes a number of XFRM structs that are not standard netlink
 // attributes (e.g., xfrm_usersa_id). These structs are unlikely to change size, so this XFRM
@@ -28,12 +31,45 @@ import com.android.net.module.util.netlink.StructNlMsgHdr;
 // struct size changes, it should be caught by CTS and then developers should add
 // kernel-version-based behvaiours.
 public abstract class XfrmNetlinkMessage extends NetlinkMessage {
-    // TODO: STOPSHIP: b/308011229 Remove it when OsConstants.IPPROTO_ESP is exposed
+    // TODO: b/312498032 Remove it when OsConstants.IPPROTO_ESP is stable
     public static final int IPPROTO_ESP = 50;
+    // TODO: b/312498032 Remove it when OsConstants.NETLINK_XFRM is stable
+    public static final int NETLINK_XFRM = 6;
+
+    /* see include/uapi/linux/xfrm.h */
+    public static final short XFRM_MSG_NEWSA = 16;
+    public static final short XFRM_MSG_GETSA = 18;
 
     public XfrmNetlinkMessage(@NonNull final StructNlMsgHdr header) {
         super(header);
     }
 
-    // TODO: Add the support for parsing messages
+    /**
+     * Parse XFRM message from ByteBuffer.
+     *
+     * <p>This method should be called from NetlinkMessage#parse(ByteBuffer, int) for generic
+     * message validation and processing
+     *
+     * @param nlmsghdr netlink message header.
+     * @param byteBuffer the ByteBuffer instance that wraps the raw netlink message bytes. MUST be
+     *                   host order
+     */
+    @Nullable
+    public static XfrmNetlinkMessage parseXfrmInternal(
+            @NonNull final StructNlMsgHdr nlmsghdr, @NonNull final ByteBuffer byteBuffer) {
+        switch (nlmsghdr.nlmsg_type) {
+            case XFRM_MSG_GETSA:
+                return XfrmNetlinkGetSaMessage.parseInternal(nlmsghdr, byteBuffer);
+            default:
+                return null;
+        }
+    }
+
+    protected abstract void packPayload(@NonNull final ByteBuffer byteBuffer);
+
+    /** Write a XFRM message to {@link ByteBuffer}. */
+    public void pack(@NonNull final ByteBuffer byteBuffer) {
+        getHeader().pack(byteBuffer);
+        packPayload(byteBuffer);
+    }
 }
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/xfrm/StructXfrmUsersaIdTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/xfrm/StructXfrmUsersaIdTest.java
index 52fd591247..b659f62a92 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/xfrm/StructXfrmUsersaIdTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/xfrm/StructXfrmUsersaIdTest.java
@@ -52,7 +52,7 @@ public class StructXfrmUsersaIdTest {
     public void testEncode() throws Exception {
         final StructXfrmUsersaId struct = new StructXfrmUsersaId(DEST_ADDRESS, SPI, FAMILY, PROTO);
 
-        ByteBuffer buffer = ByteBuffer.allocate(EXPECTED_HEX.length);
+        final ByteBuffer buffer = ByteBuffer.allocate(EXPECTED_HEX.length);
         buffer.order(ByteOrder.nativeOrder());
         struct.writeToByteBuffer(buffer);
 
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/xfrm/XfrmNetlinkGetSaMessageTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/xfrm/XfrmNetlinkGetSaMessageTest.java
new file mode 100644
index 0000000000..0ab36e7604
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/xfrm/XfrmNetlinkGetSaMessageTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util.netlink.xfrm;
+
+import static com.android.net.module.util.netlink.xfrm.XfrmNetlinkMessage.IPPROTO_ESP;
+import static com.android.net.module.util.netlink.xfrm.XfrmNetlinkMessage.NETLINK_XFRM;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import android.net.InetAddresses;
+import android.system.OsConstants;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.net.module.util.HexDump;
+import com.android.net.module.util.netlink.NetlinkMessage;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class XfrmNetlinkGetSaMessageTest {
+    private static final String EXPECTED_HEX_STRING =
+            "28000000120001000000000000000000"
+                    + "C0000201000000000000000000000000"
+                    + "7768440002003200";
+    private static final byte[] EXPECTED_HEX = HexDump.hexStringToByteArray(EXPECTED_HEX_STRING);
+    private static final InetAddress DEST_ADDRESS = InetAddresses.parseNumericAddress("192.0.2.1");
+    private static final long SPI = 0x77684400;
+    private static final int FAMILY = OsConstants.AF_INET;
+    private static final short PROTO = IPPROTO_ESP;
+
+    @Test
+    public void testEncode() throws Exception {
+        final byte[] result =
+                XfrmNetlinkGetSaMessage.newXfrmNetlinkGetSaMessage(DEST_ADDRESS, SPI, PROTO);
+        assertArrayEquals(EXPECTED_HEX, result);
+    }
+
+    @Test
+    public void testDecode() throws Exception {
+        final ByteBuffer buffer = ByteBuffer.wrap(EXPECTED_HEX);
+        buffer.order(ByteOrder.nativeOrder());
+        final XfrmNetlinkGetSaMessage message =
+                (XfrmNetlinkGetSaMessage) NetlinkMessage.parse(buffer, NETLINK_XFRM);
+        final StructXfrmUsersaId struct = message.getStructXfrmUsersaId();
+
+        assertEquals(DEST_ADDRESS, struct.getDestAddress());
+        assertEquals(SPI, struct.spi);
+        assertEquals(FAMILY, struct.family);
+        assertEquals(PROTO, struct.proto);
+        assertEquals(0, buffer.remaining());
+    }
+}
-- 
GitLab