Utility improvements for handling V6 encap and XFRM_MIGRATE

This commit is a preparation CL for testing the ability to update
IpSecTransform with new addresses. This commit adds three supports
in utility classes:
- Support verifying outbound v6 encap packets
- Allow awaitEspPacketNoPlaintext to wait for a packet with expected
  length until timeout. Previously this method would fail
  immediately when it received an ESP packet with incorrect length.
  This old behavior would make the tests flaky since the test device
  might send ESP containing RA message instead of the test data.
- Support checking the existence of XFRM_MIGRATE feature

Bug: 266149275
Test: atest CtsNetTestCases
Change-Id: Ia50aeda8d134c5edd2d1b86bf43a521084ce9866
diff --git a/tests/cts/net/src/android/net/cts/TunUtils.java b/tests/cts/net/src/android/net/cts/TunUtils.java
index 0377160..268d8d2 100644
--- a/tests/cts/net/src/android/net/cts/TunUtils.java
+++ b/tests/cts/net/src/android/net/cts/TunUtils.java
@@ -22,7 +22,6 @@
 import static android.net.cts.PacketUtils.UDP_HDRLEN;
 import static android.system.OsConstants.IPPROTO_UDP;
 
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
 import android.os.ParcelFileDescriptor;
@@ -32,6 +31,7 @@
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -140,10 +140,8 @@
     public byte[] awaitEspPacketNoPlaintext(
             int spi, byte[] plaintext, boolean useEncap, int expectedPacketSize) throws Exception {
         final byte[] espPkt = awaitPacket(
-                (pkt) -> isEspFailIfSpecifiedPlaintextFound(pkt, spi, useEncap, plaintext));
-
-        // Validate packet size
-        assertEquals(expectedPacketSize, espPkt.length);
+            (pkt) -> expectedPacketSize == pkt.length
+                    && isEspFailIfSpecifiedPlaintextFound(pkt, spi, useEncap, plaintext));
 
         return espPkt; // We've found the packet we're looking for.
     }
@@ -153,11 +151,11 @@
     }
 
     private static boolean isSpiEqual(byte[] pkt, int espOffset, int spi) {
-        // Check SPI byte by byte.
-        return pkt[espOffset] == (byte) ((spi >>> 24) & 0xff)
-                && pkt[espOffset + 1] == (byte) ((spi >>> 16) & 0xff)
-                && pkt[espOffset + 2] == (byte) ((spi >>> 8) & 0xff)
-                && pkt[espOffset + 3] == (byte) (spi & 0xff);
+        ByteBuffer buffer = ByteBuffer.wrap(pkt);
+        buffer.get(new byte[espOffset]); // Skip IP, UDP header
+        int actualSpi = buffer.getInt();
+
+        return actualSpi == spi;
     }
 
     /**
@@ -180,8 +178,13 @@
 
     private static boolean isEsp(byte[] pkt, int spi, boolean encap) {
         if (isIpv6(pkt)) {
-            // IPv6 UDP encap not supported by kernels; assume non-encap.
-            return pkt[IP6_PROTO_OFFSET] == IPPROTO_ESP && isSpiEqual(pkt, IP6_HDRLEN, spi);
+            if (encap) {
+                return pkt[IP6_PROTO_OFFSET] == IPPROTO_UDP
+                        && isSpiEqual(pkt, IP6_HDRLEN + UDP_HDRLEN, spi);
+            } else {
+                return pkt[IP6_PROTO_OFFSET] == IPPROTO_ESP && isSpiEqual(pkt, IP6_HDRLEN, spi);
+            }
+
         } else {
             // Use default IPv4 header length (assuming no options)
             if (encap) {
diff --git a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
index df3a4aa..d817630 100644
--- a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
+++ b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
@@ -75,6 +75,13 @@
 
 public final class CtsNetUtils {
     private static final String TAG = CtsNetUtils.class.getSimpleName();
+
+    // Redefine this flag here so that IPsec code shipped in a mainline module can build on old
+    // platforms before FEATURE_IPSEC_TUNNEL_MIGRATION API is released.
+    // TODO: b/275378783 Remove this flag and use the platform API when it is available.
+    private static final String FEATURE_IPSEC_TUNNEL_MIGRATION =
+            "android.software.ipsec_tunnel_migration";
+
     private static final int SOCKET_TIMEOUT_MS = 2000;
     private static final int PRIVATE_DNS_PROBE_MS = 1_000;
 
@@ -115,6 +122,11 @@
                 || getFirstApiLevel() >= Build.VERSION_CODES.Q;
     }
 
+    /** Checks if FEATURE_IPSEC_TUNNEL_MIGRATION is enabled on the device */
+    public boolean hasIpsecTunnelMigrateFeature() {
+        return mContext.getPackageManager().hasSystemFeature(FEATURE_IPSEC_TUNNEL_MIGRATION);
+    }
+
     /**
      * Sets the given appop using shell commands
      *