Add helper method to generate a v6 NAT-T keepalive packet

This is a part of preliminary work for sending a v6 NAT-T
keepalive packet in a v6 only network. This commit
release the constraint on constructing a v6 NAT-T keepalive
packet.

Bug: 196453719
Test: Manually connect to v6 only network and check if the
      keepalive packets are sent with subsequent commit
Test: atest FrameworksNetTests with the updated test
Change-Id: I9dc96e840265c5dee6b9e3ef2b3901a0aeb4ff9e
diff --git a/framework/src/android/net/NattKeepalivePacketData.java b/framework/src/android/net/NattKeepalivePacketData.java
index a18e713..a524859 100644
--- a/framework/src/android/net/NattKeepalivePacketData.java
+++ b/framework/src/android/net/NattKeepalivePacketData.java
@@ -29,7 +29,9 @@
 import com.android.net.module.util.IpUtils;
 
 import java.net.Inet4Address;
+import java.net.Inet6Address;
 import java.net.InetAddress;
+import java.net.UnknownHostException;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.util.Objects;
@@ -38,6 +40,7 @@
 @SystemApi
 public final class NattKeepalivePacketData extends KeepalivePacketData implements Parcelable {
     private static final int IPV4_HEADER_LENGTH = 20;
+    private static final int IPV6_HEADER_LENGTH = 40;
     private static final int UDP_HEADER_LENGTH = 8;
 
     // This should only be constructed via static factory methods, such as
@@ -59,13 +62,25 @@
             throw new InvalidPacketException(ERROR_INVALID_PORT);
         }
 
-        if (!(srcAddress instanceof Inet4Address) || !(dstAddress instanceof Inet4Address)) {
+        // Convert IPv4 mapped v6 address to v4 if any.
+        final InetAddress srcAddr, dstAddr;
+        try {
+            srcAddr = InetAddress.getByAddress(srcAddress.getAddress());
+            dstAddr = InetAddress.getByAddress(dstAddress.getAddress());
+        } catch (UnknownHostException e) {
             throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS);
         }
 
-        return nattKeepalivePacketv4(
-                (Inet4Address) srcAddress, srcPort,
-                (Inet4Address) dstAddress, dstPort);
+        if (srcAddr instanceof Inet4Address && dstAddr instanceof Inet4Address) {
+            return nattKeepalivePacketv4(
+                    (Inet4Address) srcAddr, srcPort, (Inet4Address) dstAddr, dstPort);
+        } else if (srcAddr instanceof Inet6Address && dstAddress instanceof Inet6Address) {
+            return nattKeepalivePacketv6(
+                    (Inet6Address) srcAddr, srcPort, (Inet6Address) dstAddr, dstPort);
+        } else {
+            // Destination address and source address should be the same IP family.
+            throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS);
+        }
     }
 
     private static NattKeepalivePacketData nattKeepalivePacketv4(
@@ -82,14 +97,14 @@
         // /proc/sys/net/ipv4/ip_default_ttl. Use hard-coded 64 for simplicity.
         buf.put((byte) 64);                                 // TTL
         buf.put((byte) OsConstants.IPPROTO_UDP);
-        int ipChecksumOffset = buf.position();
+        final int ipChecksumOffset = buf.position();
         buf.putShort((short) 0);                            // IP checksum
         buf.put(srcAddress.getAddress());
         buf.put(dstAddress.getAddress());
         buf.putShort((short) srcPort);
         buf.putShort((short) dstPort);
         buf.putShort((short) (UDP_HEADER_LENGTH + 1));      // UDP length
-        int udpChecksumOffset = buf.position();
+        final int udpChecksumOffset = buf.position();
         buf.putShort((short) 0);                            // UDP checksum
         buf.put((byte) 0xff);                               // NAT-T keepalive
         buf.putShort(ipChecksumOffset, IpUtils.ipChecksum(buf, 0));
@@ -98,6 +113,30 @@
         return new NattKeepalivePacketData(srcAddress, srcPort, dstAddress, dstPort, buf.array());
     }
 
+    private static NattKeepalivePacketData nattKeepalivePacketv6(
+            Inet6Address srcAddress, int srcPort, Inet6Address dstAddress, int dstPort)
+            throws InvalidPacketException {
+        final ByteBuffer buf = ByteBuffer.allocate(IPV6_HEADER_LENGTH + UDP_HEADER_LENGTH + 1);
+        buf.order(ByteOrder.BIG_ENDIAN);
+        buf.putInt(0x60000000);                         // IP version, traffic class and flow label
+        buf.putShort((short) (UDP_HEADER_LENGTH + 1));  // Payload length
+        buf.put((byte) OsConstants.IPPROTO_UDP);        // Next header
+        // For native ipv6, this hop limit value should use the per interface v6 hoplimit sysctl.
+        // For 464xlat, this value should use the v4 ttl sysctl.
+        // Either way, for simplicity, just hard code 64.
+        buf.put((byte) 64);                             // Hop limit
+        buf.put(srcAddress.getAddress());
+        buf.put(dstAddress.getAddress());
+        // UDP
+        buf.putShort((short) srcPort);
+        buf.putShort((short) dstPort);
+        buf.putShort((short) (UDP_HEADER_LENGTH + 1));  // UDP length = Payload length
+        final int udpChecksumOffset = buf.position();
+        buf.putShort((short) 0);                        // UDP checksum
+        buf.put((byte) 0xff);                           // NAT-T keepalive. 1 byte of data
+        buf.putShort(udpChecksumOffset, IpUtils.udpChecksum(buf, 0, IPV6_HEADER_LENGTH));
+        return new NattKeepalivePacketData(srcAddress, srcPort, dstAddress, dstPort, buf.array());
+    }
     /** Parcelable Implementation */
     public int describeContents() {
         return 0;
diff --git a/tests/common/java/android/net/NattKeepalivePacketDataTest.kt b/tests/common/java/android/net/NattKeepalivePacketDataTest.kt
index 36b9a91..1f04fb8 100644
--- a/tests/common/java/android/net/NattKeepalivePacketDataTest.kt
+++ b/tests/common/java/android/net/NattKeepalivePacketDataTest.kt
@@ -46,6 +46,8 @@
     private val TEST_SRC_ADDRV4 = "198.168.0.2".address()
     private val TEST_DST_ADDRV4 = "198.168.0.1".address()
     private val TEST_ADDRV6 = "2001:db8::1".address()
+    private val TEST_ADDRV4MAPPEDV6 = "::ffff:1.2.3.4".address()
+    private val TEST_ADDRV4 = "1.2.3.4".address()
 
     private fun String.address() = InetAddresses.parseNumericAddress(this)
     private fun nattKeepalivePacket(
@@ -83,6 +85,28 @@
         }
     }
 
+    @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+    fun testConstructor_afterR() {
+        // v4 mapped v6 will be translated to a v4 address.
+        assertFailsWith<InvalidPacketException> {
+            nattKeepalivePacket(srcAddress = TEST_ADDRV6, dstAddress = TEST_ADDRV4MAPPEDV6)
+        }
+        assertFailsWith<InvalidPacketException> {
+            nattKeepalivePacket(srcAddress = TEST_ADDRV4MAPPEDV6, dstAddress = TEST_ADDRV6)
+        }
+
+        // Both src and dst address will be v4 after translation, so it won't cause exception.
+        val packet1 = nattKeepalivePacket(
+            dstAddress = TEST_ADDRV4MAPPEDV6, srcAddress = TEST_ADDRV4MAPPEDV6)
+        assertEquals(TEST_ADDRV4, packet1.srcAddress)
+        assertEquals(TEST_ADDRV4, packet1.dstAddress)
+
+        // Packet with v6 src and v6 dst address is valid.
+        val packet2 = nattKeepalivePacket(srcAddress = TEST_ADDRV6, dstAddress = TEST_ADDRV6)
+        assertEquals(TEST_ADDRV6, packet2.srcAddress)
+        assertEquals(TEST_ADDRV6, packet2.dstAddress)
+    }
+
     @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
     fun testParcel() {
         assertParcelingIsLossless(nattKeepalivePacket())
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 43c6225..10b70b6 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -6853,10 +6853,6 @@
         ka = mCm.startNattKeepalive(myNet, validKaInterval, callback, myIPv6, 1234, dstIPv4);
         callback.expectError(PacketKeepalive.ERROR_INVALID_IP_ADDRESS);
 
-        // NAT-T is only supported for IPv4.
-        ka = mCm.startNattKeepalive(myNet, validKaInterval, callback, myIPv6, 1234, dstIPv6);
-        callback.expectError(PacketKeepalive.ERROR_INVALID_IP_ADDRESS);
-
         ka = mCm.startNattKeepalive(myNet, validKaInterval, callback, myIPv4, 123456, dstIPv4);
         callback.expectError(PacketKeepalive.ERROR_INVALID_PORT);
 
@@ -7007,13 +7003,6 @@
             callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS);
         }
 
-        // NAT-T is only supported for IPv4.
-        try (SocketKeepalive ka = mCm.createSocketKeepalive(
-                myNet, testSocket, myIPv6, dstIPv6, executor, callback)) {
-            ka.start(validKaInterval);
-            callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS);
-        }
-
         // Basic check before testing started keepalive.
         try (SocketKeepalive ka = mCm.createSocketKeepalive(
                 myNet, testSocket, myIPv4, dstIPv4, executor, callback)) {