[XFRM_MSG_NEWSA] Support xfrm_stats and xfrm_usersa_info
Bug: 308011229
Test: atest NetworkStaticLibTests:com.android.net.moduletests.util.netlink
(new tests added)
Change-Id: I8641cf36a790d7b15fbe41b4da328c1d91fcec1e
diff --git a/staticlibs/device/com/android/net/module/util/netlink/xfrm/StructXfrmStats.java b/staticlibs/device/com/android/net/module/util/netlink/xfrm/StructXfrmStats.java
new file mode 100644
index 0000000..be13293
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/xfrm/StructXfrmStats.java
@@ -0,0 +1,61 @@
+/*
+ * 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 com.android.net.module.util.Struct;
+
+/**
+ * Struct xfrm_lifetime_cur
+ *
+ * <p>see include/uapi/linux/xfrm.h
+ *
+ * <pre>
+ * struct xfrm_stats {
+ * __u32 replay_window;
+ * __u32 replay;
+ * __u32 integrity_failed;
+ * };
+ * </pre>
+ *
+ * @hide
+ */
+public class StructXfrmStats extends Struct {
+ public static final int STRUCT_SIZE = 12;
+
+ /** Number of packets that fall out of the replay window */
+ @Field(order = 0, type = Type.U32)
+ public final long replayWindow;
+
+ /** Number of replayed packets */
+ @Field(order = 1, type = Type.U32)
+ public final long replay;
+
+ /** Number of packets that failed authentication */
+ @Field(order = 2, type = Type.U32)
+ public final long integrityFailed;
+
+ // Constructor that allows Strutc.parse(Class<T>, ByteBuffer) to work
+ public StructXfrmStats(long replayWindow, long replay, long integrityFailed) {
+ this.replayWindow = replayWindow;
+ this.replay = replay;
+ this.integrityFailed = integrityFailed;
+ }
+
+ // Constructor to build a new message
+ public StructXfrmStats() {
+ this(0, 0, 0);
+ }
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/xfrm/StructXfrmUsersaInfo.java b/staticlibs/device/com/android/net/module/util/netlink/xfrm/StructXfrmUsersaInfo.java
new file mode 100644
index 0000000..740a801
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/xfrm/StructXfrmUsersaInfo.java
@@ -0,0 +1,215 @@
+/*
+ * 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 android.system.OsConstants;
+
+import androidx.annotation.NonNull;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Computed;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.math.BigInteger;
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * Struct xfrm_usersa_info
+ *
+ * <p>see include/uapi/linux/xfrm.h
+ *
+ * <pre>
+ * struct xfrm_usersa_info {
+ * struct xfrm_selector sel;
+ * struct xfrm_id id;
+ * xfrm_address_t saddr;
+ * struct xfrm_lifetime_cfg lft;
+ * struct xfrm_lifetime_cur curlft;
+ * struct xfrm_stats stats;
+ * __u32 seq;
+ * __u32 reqid;
+ * __u16 family;
+ * __u8 mode;
+ * __u8 replay_window;
+ * __u8 flags;
+ * };
+ * </pre>
+ *
+ * @hide
+ */
+public class StructXfrmUsersaInfo extends Struct {
+ private static final int NESTED_STRUCTS_SIZE =
+ StructXfrmSelector.STRUCT_SIZE
+ + StructXfrmId.STRUCT_SIZE
+ + StructXfrmAddressT.STRUCT_SIZE
+ + StructXfrmLifetimeCfg.STRUCT_SIZE
+ + StructXfrmLifetimeCur.STRUCT_SIZE
+ + StructXfrmStats.STRUCT_SIZE;
+
+ public static final int STRUCT_SIZE = NESTED_STRUCTS_SIZE + 20;
+
+ @Computed private final StructXfrmSelector mXfrmSelector;
+ @Computed private final StructXfrmId mXfrmId;
+ @Computed private final StructXfrmAddressT mSourceXfrmAddressT;
+ @Computed private final StructXfrmLifetimeCfg mXfrmLifetime;
+ @Computed private final StructXfrmLifetimeCur mXfrmCurrentLifetime;
+ @Computed private final StructXfrmStats mXfrmStats;
+
+ @Field(order = 0, type = Type.ByteArray, arraysize = NESTED_STRUCTS_SIZE)
+ public final byte[] nestedStructs;
+
+ @Field(order = 1, type = Type.U32)
+ public final long seq;
+
+ @Field(order = 2, type = Type.U32)
+ public final long reqId;
+
+ @Field(order = 3, type = Type.U16)
+ public final int family;
+
+ @Field(order = 4, type = Type.U8)
+ public final short mode;
+
+ @Field(order = 5, type = Type.U8)
+ public final short replayWindowLegacy;
+
+ @Field(order = 6, type = Type.U8, padding = 7)
+ public final short flags;
+
+ // Constructor that allows Strutc.parse(Class<T>, ByteBuffer) to work
+ public StructXfrmUsersaInfo(
+ @NonNull final byte[] nestedStructs,
+ long seq,
+ long reqId,
+ int family,
+ short mode,
+ short replayWindowLegacy,
+ short flags) {
+ this.nestedStructs = nestedStructs.clone();
+ this.seq = seq;
+ this.reqId = reqId;
+ this.family = family;
+ this.mode = mode;
+ this.replayWindowLegacy = replayWindowLegacy;
+ this.flags = flags;
+
+ final ByteBuffer nestedStructsBuff = ByteBuffer.wrap(nestedStructs);
+ nestedStructsBuff.order(ByteOrder.nativeOrder());
+
+ // The parsing order matters
+ mXfrmSelector = Struct.parse(StructXfrmSelector.class, nestedStructsBuff);
+ mXfrmId = Struct.parse(StructXfrmId.class, nestedStructsBuff);
+ mSourceXfrmAddressT = Struct.parse(StructXfrmAddressT.class, nestedStructsBuff);
+ mXfrmLifetime = Struct.parse(StructXfrmLifetimeCfg.class, nestedStructsBuff);
+ mXfrmCurrentLifetime = Struct.parse(StructXfrmLifetimeCur.class, nestedStructsBuff);
+ mXfrmStats = Struct.parse(StructXfrmStats.class, nestedStructsBuff);
+ }
+
+ // Constructor to build a new message for TESTING
+ StructXfrmUsersaInfo(
+ @NonNull final InetAddress dAddr,
+ @NonNull final InetAddress sAddr,
+ @NonNull final BigInteger addTime,
+ int selectorFamily,
+ long spi,
+ long seq,
+ long reqId,
+ short proto,
+ short mode,
+ short replayWindowLegacy,
+ short flags) {
+ this.seq = seq;
+ this.reqId = reqId;
+ this.family = dAddr instanceof Inet4Address ? OsConstants.AF_INET : OsConstants.AF_INET6;
+ this.mode = mode;
+ this.replayWindowLegacy = replayWindowLegacy;
+ this.flags = flags;
+
+ mXfrmSelector = new StructXfrmSelector(selectorFamily);
+ mXfrmId = new StructXfrmId(dAddr, spi, proto);
+ mSourceXfrmAddressT = new StructXfrmAddressT(sAddr);
+ mXfrmLifetime = new StructXfrmLifetimeCfg();
+ mXfrmCurrentLifetime =
+ new StructXfrmLifetimeCur(
+ BigInteger.ZERO, BigInteger.ZERO, addTime, BigInteger.ZERO);
+ mXfrmStats = new StructXfrmStats();
+
+ final ByteBuffer nestedStructsBuff = ByteBuffer.allocate(NESTED_STRUCTS_SIZE);
+ nestedStructsBuff.order(ByteOrder.nativeOrder());
+
+ mXfrmSelector.writeToByteBuffer(nestedStructsBuff);
+ mXfrmId.writeToByteBuffer(nestedStructsBuff);
+ mSourceXfrmAddressT.writeToByteBuffer(nestedStructsBuff);
+ mXfrmLifetime.writeToByteBuffer(nestedStructsBuff);
+ mXfrmCurrentLifetime.writeToByteBuffer(nestedStructsBuff);
+ mXfrmStats.writeToByteBuffer(nestedStructsBuff);
+
+ this.nestedStructs = nestedStructsBuff.array();
+ }
+
+ // Constructor to build a new message
+ public StructXfrmUsersaInfo(
+ @NonNull final InetAddress dAddr,
+ @NonNull final InetAddress sAddr,
+ long spi,
+ long seq,
+ long reqId,
+ short proto,
+ short mode,
+ short replayWindowLegacy,
+ short flags) {
+ // Use AF_UNSPEC for all SAs selectors. In transport mode, kernel picks selector family
+ // based on usersa->family, while in tunnel mode, the XFRM_STATE_AF_UNSPEC flag allows
+ // dual-stack SAs.
+ this(
+ dAddr,
+ sAddr,
+ BigInteger.ZERO,
+ OsConstants.AF_UNSPEC,
+ spi,
+ seq,
+ reqId,
+ proto,
+ mode,
+ replayWindowLegacy,
+ flags);
+ }
+
+ /** Return the destination address */
+ public InetAddress getDestAddress() {
+ return mXfrmId.getDestAddress(family);
+ }
+
+ /** Return the source address */
+ public InetAddress getSrcAddress() {
+ return mSourceXfrmAddressT.getAddress(family);
+ }
+
+ /** Return the SPI */
+ public long getSpi() {
+ return mXfrmId.spi;
+ }
+
+ /** Return the current lifetime */
+ public StructXfrmLifetimeCur getCurrentLifetime() {
+ return mXfrmCurrentLifetime;
+ }
+}
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 9773cd6..14a3a0f 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
@@ -41,6 +41,9 @@
public static final short XFRM_MSG_NEWSA = 16;
public static final short XFRM_MSG_GETSA = 18;
+ public static final int XFRM_MODE_TRANSPORT = 0;
+ public static final int XFRM_MODE_TUNNEL = 1;
+
public static final BigInteger XFRM_INF = new BigInteger("FFFFFFFFFFFFFFFF", 16);
public XfrmNetlinkMessage(@NonNull final StructNlMsgHdr header) {
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/xfrm/StructXfrmUsersaInfoTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/xfrm/StructXfrmUsersaInfoTest.java
new file mode 100644
index 0000000..94161ff
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/xfrm/StructXfrmUsersaInfoTest.java
@@ -0,0 +1,127 @@
+/*
+ * 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.XFRM_MODE_TRANSPORT;
+
+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 org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.math.BigInteger;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Calendar;
+import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class StructXfrmUsersaInfoTest {
+ private static final String EXPECTED_HEX_STRING =
+ "00000000000000000000000000000000"
+ + "00000000000000000000000000000000"
+ + "00000000000000000A00000000000000"
+ + "000000000000000020010DB800000000"
+ + "0000000000000111AABBCCDD32000000"
+ + "20010DB8000000000000000000000222"
+ + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+ + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+ + "00000000000000000000000000000000"
+ + "00000000000000000000000000000000"
+ + "00000000000000000000000000000000"
+ + "FD464C65000000000000000000000000"
+ + "00000000000000000000000000000000"
+ + "024000000A0000000000000000000000";
+ private static final byte[] EXPECTED_HEX = HexDump.hexStringToByteArray(EXPECTED_HEX_STRING);
+
+ private static final InetAddress DEST_ADDRESS =
+ InetAddresses.parseNumericAddress("2001:db8::111");
+ private static final InetAddress SOURCE_ADDRESS =
+ InetAddresses.parseNumericAddress("2001:db8::222");
+ private static final BigInteger ADD_TIME;
+ private static final int SELECTOR_FAMILY = OsConstants.AF_INET6;
+ private static final int FAMILY = OsConstants.AF_INET6;
+ private static final long SPI = 0xaabbccddL;
+ private static final long SEQ = 0L;
+ private static final long REQ_ID = 16386L;
+ private static final short PROTO = IPPROTO_ESP;
+ private static final short MODE = XFRM_MODE_TRANSPORT;
+ private static final short REPLAY_WINDOW_LEGACY = 0;
+ private static final short FLAGS = 0;
+
+ static {
+ final Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
+ cal.set(2023, Calendar.NOVEMBER, 9, 2, 42, 05);
+ final long timestampSeconds = TimeUnit.MILLISECONDS.toSeconds(cal.getTimeInMillis());
+ ADD_TIME = BigInteger.valueOf(timestampSeconds);
+ }
+
+ @Test
+ public void testEncode() throws Exception {
+ final StructXfrmUsersaInfo struct =
+ new StructXfrmUsersaInfo(
+ DEST_ADDRESS,
+ SOURCE_ADDRESS,
+ ADD_TIME,
+ SELECTOR_FAMILY,
+ SPI,
+ SEQ,
+ REQ_ID,
+ PROTO,
+ MODE,
+ REPLAY_WINDOW_LEGACY,
+ FLAGS);
+
+ final ByteBuffer buffer = ByteBuffer.allocate(EXPECTED_HEX.length);
+ buffer.order(ByteOrder.nativeOrder());
+ struct.writeToByteBuffer(buffer);
+
+ assertArrayEquals(EXPECTED_HEX, buffer.array());
+ }
+
+ @Test
+ public void testDecode() throws Exception {
+ final ByteBuffer buffer = ByteBuffer.wrap(EXPECTED_HEX);
+ buffer.order(ByteOrder.nativeOrder());
+
+ final StructXfrmUsersaInfo struct =
+ StructXfrmUsersaInfo.parse(StructXfrmUsersaInfo.class, buffer);
+
+ assertEquals(DEST_ADDRESS, struct.getDestAddress());
+ assertEquals(SOURCE_ADDRESS, struct.getSrcAddress());
+ assertEquals(SPI, struct.getSpi());
+ assertEquals(SEQ, struct.seq);
+ assertEquals(REQ_ID, struct.reqId);
+ assertEquals(FAMILY, struct.family);
+ assertEquals(MODE, struct.mode);
+ assertEquals(REPLAY_WINDOW_LEGACY, struct.replayWindowLegacy);
+ assertEquals(FLAGS, struct.flags);
+ }
+}