From 45ac04442f0318185cdad284b1c82eee74c8f182 Mon Sep 17 00:00:00 2001 From: Jimi Chen Date: Tue, 6 Feb 2024 04:24:58 +0000 Subject: [PATCH] Use custom defined DNS/HTTP port Refactor the test case to support listen custom DNS/HTTP port. Avoid binding system restricted port (<1024). Bug: 322113686 Test: atest NetworkStackIntegrationTests:android.net.NetworkStatsIntegrationTest Change-Id: I5d3b966cdadc9321ca2ee0d04a5b8f71ee79bd8a --- .../testutils/ExternalPacketForwarder.kt | 44 +++++++++++++ .../testutils/InternalPacketForwarder.kt | 43 ++++++++++++ .../com/android/testutils/PacketBridge.kt | 25 ++++++- ...orwarder.java => PacketForwarderBase.java} | 66 +++++++++++++++++-- .../com/android/testutils/TestHttpServer.kt | 4 +- 5 files changed, 171 insertions(+), 11 deletions(-) create mode 100644 staticlibs/testutils/devicetests/com/android/testutils/ExternalPacketForwarder.kt create mode 100644 staticlibs/testutils/devicetests/com/android/testutils/InternalPacketForwarder.kt rename staticlibs/testutils/devicetests/com/android/testutils/{PacketForwarder.java => PacketForwarderBase.java} (68%) diff --git a/staticlibs/testutils/devicetests/com/android/testutils/ExternalPacketForwarder.kt b/staticlibs/testutils/devicetests/com/android/testutils/ExternalPacketForwarder.kt new file mode 100644 index 0000000000..36eb795bb9 --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/ExternalPacketForwarder.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2024 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.testutils + +import java.io.FileDescriptor + +class ExternalPacketForwarder( + srcFd: FileDescriptor, + mtu: Int, + dstFd: FileDescriptor, + forwardMap: Map +) : PacketForwarderBase(srcFd, mtu, dstFd, forwardMap) { + + /** + * Prepares a packet for forwarding by potentially updating the + * source port based on the specified port remapping rules. + * + * @param buf The packet data as a byte array. + * @param version The IP version of the packet (e.g., 4 for IPv4). + */ + override fun remapPort(buf: ByteArray, version: Int) { + val transportOffset = getTransportOffset(version) + val intPort = getRemappedPort(buf, transportOffset) + + // Copy remapped source port. + if (intPort != 0) { + setPortAt(intPort, buf, transportOffset) + } + } +} diff --git a/staticlibs/testutils/devicetests/com/android/testutils/InternalPacketForwarder.kt b/staticlibs/testutils/devicetests/com/android/testutils/InternalPacketForwarder.kt new file mode 100644 index 0000000000..58829dc7bb --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/InternalPacketForwarder.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2024 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.testutils + +import java.io.FileDescriptor + +class InternalPacketForwarder( + srcFd: FileDescriptor, + mtu: Int, + dstFd: FileDescriptor, + forwardMap: Map +) : PacketForwarderBase(srcFd, mtu, dstFd, forwardMap) { + /** + * Prepares a packet for forwarding by potentially updating the + * destination port based on the specified port remapping rules. + * + * @param buf The packet data as a byte array. + * @param version The IP version of the packet (e.g., 4 for IPv4). + */ + override fun remapPort(buf: ByteArray, version: Int) { + val transportOffset = getTransportOffset(version) + DESTINATION_PORT_OFFSET + val extPort = getRemappedPort(buf, transportOffset) + + // Copy remapped destination port. + if (extPort != 0) { + setPortAt(extPort, buf, transportOffset) + } + } +} diff --git a/staticlibs/testutils/devicetests/com/android/testutils/PacketBridge.kt b/staticlibs/testutils/devicetests/com/android/testutils/PacketBridge.kt index 1a2cc88af6..0b736d1615 100644 --- a/staticlibs/testutils/devicetests/com/android/testutils/PacketBridge.kt +++ b/staticlibs/testutils/devicetests/com/android/testutils/PacketBridge.kt @@ -40,7 +40,8 @@ import libcore.io.IoUtils class PacketBridge( context: Context, addresses: List, - dnsAddr: InetAddress + dnsAddr: InetAddress, + portMapping: List> ) { private val binder = Binder() @@ -56,6 +57,10 @@ class PacketBridge( // Register test networks to ConnectivityService. private val internalNetworkCallback: TestableNetworkCallback private val externalNetworkCallback: TestableNetworkCallback + + private val internalForwardMap = HashMap() + private val externalForwardMap = HashMap() + val internalNetwork: Network val externalNetwork: Network init { @@ -65,14 +70,28 @@ class PacketBridge( externalNetworkCallback = exCb internalNetwork = inNet externalNetwork = exNet + for (mapping in portMapping) { + internalForwardMap[mapping.first] = mapping.second + externalForwardMap[mapping.second] = mapping.first + } } // Set up the packet bridge. private val internalFd = internalIface.fileDescriptor.fileDescriptor private val externalFd = externalIface.fileDescriptor.fileDescriptor - private val pr1 = PacketForwarder(internalFd, 1500, externalFd) - private val pr2 = PacketForwarder(externalFd, 1500, internalFd) + private val pr1 = InternalPacketForwarder( + internalFd, + 1500, + externalFd, + internalForwardMap + ) + private val pr2 = ExternalPacketForwarder( + externalFd, + 1500, + internalFd, + externalForwardMap + ) fun start() { IoUtils.setBlocking(internalFd, true /* blocking */) diff --git a/staticlibs/testutils/devicetests/com/android/testutils/PacketForwarder.java b/staticlibs/testutils/devicetests/com/android/testutils/PacketForwarderBase.java similarity index 68% rename from staticlibs/testutils/devicetests/com/android/testutils/PacketForwarder.java rename to staticlibs/testutils/devicetests/com/android/testutils/PacketForwarderBase.java index d8efb7d33f..5c79eb04cc 100644 --- a/staticlibs/testutils/devicetests/com/android/testutils/PacketForwarder.java +++ b/staticlibs/testutils/devicetests/com/android/testutils/PacketForwarderBase.java @@ -32,6 +32,7 @@ import android.util.Log; import java.io.FileDescriptor; import java.io.IOException; +import java.util.Map; import java.util.Objects; /** @@ -57,8 +58,9 @@ import java.util.Objects; * from the http server, the same mechanism is applied but in a different direction, * where the source and destination will be swapped. */ -public class PacketForwarder extends Thread { +public abstract class PacketForwarderBase extends Thread { private static final String TAG = "PacketForwarder"; + static final int DESTINATION_PORT_OFFSET = 2; // The source fd to read packets from. @NonNull @@ -70,8 +72,10 @@ public class PacketForwarder extends Thread { @NonNull final FileDescriptor mDstFd; + @NonNull + final Map mPortRemapRules; /** - * Construct a {@link PacketForwarder}. + * Construct a {@link PacketForwarderBase}. * * This class reads packets from {@code srcFd} of a {@link TestNetworkInterface}, and * forwards them to the {@code dstFd} of another {@link TestNetworkInterface}. @@ -82,13 +86,49 @@ public class PacketForwarder extends Thread { * @param srcFd {@link FileDescriptor} to read packets from. * @param mtu MTU of the test network. * @param dstFd {@link FileDescriptor} to write packets to. + * @param portRemapRules port remap rules */ - public PacketForwarder(@NonNull FileDescriptor srcFd, int mtu, - @NonNull FileDescriptor dstFd) { + public PacketForwarderBase(@NonNull FileDescriptor srcFd, int mtu, + @NonNull FileDescriptor dstFd, + @NonNull Map portRemapRules) { super(TAG); mSrcFd = Objects.requireNonNull(srcFd); mBuf = new byte[mtu]; mDstFd = Objects.requireNonNull(dstFd); + mPortRemapRules = Objects.requireNonNull(portRemapRules); + } + + /** + * A method to prepare forwarding packets between two instances of {@link TestNetworkInterface}, + * which includes ports mapping. + * Subclasses should override this method to implement the needed port remapping. + * For internal forwarder will remapped destination port, + * external forwarder will remapped source port. + * Example: + * An outgoing packet from the internal interface with + * source 1.2.3.4:1234 and destination 8.8.8.8:80 + * might be translated to 8.8.8.8:1234 -> 1.2.3.4:8080 before forwarding. + * An outgoing packet from the external interface with + * source 1.2.3.4:8080 and destination 8.8.8.8:1234 + * might be translated to 8.8.8.8:80 -> 1.2.3.4:1234 before forwarding. + */ + abstract void remapPort(@NonNull byte[] buf, int version); + + /** + * Retrieves a potentially remapped port number from a packet. + * + * @param buf The packet data as a byte array. + * @param transportOffset The offset within the packet where the transport layer port begins. + * @return The remapped port if a mapping exists in the internal forwarding map, + * otherwise returns 0 (indicating no remapping). + */ + int getRemappedPort(@NonNull byte[] buf, int transportOffset) { + int port = PacketReflectorUtil.getPortAt(buf, transportOffset); + return mPortRemapRules.getOrDefault(port, 0); + } + + int getTransportOffset(int version) { + return version == 4 ? IPV4_HEADER_LENGTH : IPV6_HEADER_LENGTH; } private void forwardPacket(@NonNull byte[] buf, int len) { @@ -99,7 +139,13 @@ public class PacketForwarder extends Thread { } } - // Reads one packet from mSrcFd, and writes the packet to the mDstFd for supported protocols. + /** + * Reads one packet from mSrcFd, and writes the packet to the mDestFd for supported protocols. + * This includes: + * 1.Address Swapping: Swaps source and destination IP addresses. + * 2.Port Remapping: Remap port if necessary. + * 3.Checksum Recalculation: Updates IP and transport layer checksums to reflect changes. + */ private void processPacket() { final int len = PacketReflectorUtil.readPacket(mSrcFd, mBuf); if (len < 1) { @@ -142,13 +188,19 @@ public class PacketForwarder extends Thread { if (len < ipHdrLen + transportHdrLen) { throw new IllegalStateException("Unexpected buffer length: " + len); } - // Swap addresses. + + // Swap source and destination address. PacketReflectorUtil.swapAddresses(mBuf, version); + // Remapping the port. + remapPort(mBuf, version); + + // Fix IP and Transport layer checksum. + PacketReflectorUtil.fixPacketChecksum(mBuf, len, version, proto); + // Send the packet to the destination fd. forwardPacket(mBuf, len); } - @Override public void run() { Log.i(TAG, "starting fd=" + mSrcFd + " valid=" + mSrcFd.valid()); diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestHttpServer.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestHttpServer.kt index 740bf63a10..f1f0c1c78d 100644 --- a/staticlibs/testutils/devicetests/com/android/testutils/TestHttpServer.kt +++ b/staticlibs/testutils/devicetests/com/android/testutils/TestHttpServer.kt @@ -25,8 +25,10 @@ import java.io.IOException * A minimal HTTP server running on a random available port. * * @param host The host to listen to, or null to listen on all hosts + * @param port The port to listen to, or 0 to auto select */ -class TestHttpServer(host: String? = null) : NanoHTTPD(host, 0 /* auto-select the port */) { +class TestHttpServer + @JvmOverloads constructor(host: String? = null, port: Int = 0) : NanoHTTPD(host, port) { // Map of URL path -> HTTP response code private val responses = HashMap()