diff --git a/framework/src/android/net/NattKeepalivePacketData.java b/framework/src/android/net/NattKeepalivePacketData.java index a18e713ae4..a524859ca8 100644 --- a/framework/src/android/net/NattKeepalivePacketData.java +++ b/framework/src/android/net/NattKeepalivePacketData.java @@ -29,7 +29,9 @@ import android.system.OsConstants; 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 @@ import java.util.Objects; @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 @@ public final class NattKeepalivePacketData extends KeepalivePacketData implement 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 @@ public final class NattKeepalivePacketData extends KeepalivePacketData implement // /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 @@ public final class NattKeepalivePacketData extends KeepalivePacketData implement 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 36b9a91ffa..1f04fb883a 100644 --- a/tests/common/java/android/net/NattKeepalivePacketDataTest.kt +++ b/tests/common/java/android/net/NattKeepalivePacketDataTest.kt @@ -46,6 +46,8 @@ class NattKeepalivePacketDataTest { 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 @@ class NattKeepalivePacketDataTest { } } + @Test @IgnoreUpTo(Build.VERSION_CODES.R) + fun testConstructor_afterR() { + // v4 mapped v6 will be translated to a v4 address. + assertFailsWith { + nattKeepalivePacket(srcAddress = TEST_ADDRV6, dstAddress = TEST_ADDRV4MAPPEDV6) + } + assertFailsWith { + 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 43c6225bf1..10b70b6d0b 100755 --- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java @@ -6853,10 +6853,6 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { 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)) {