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
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 0000000..36eb795
--- /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<Int, Int>
+) : 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 0000000..58829dc
--- /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<Int, Int>
+) : 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 1a2cc88..0b736d1 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/PacketBridge.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/PacketBridge.kt
@@ -40,7 +40,8 @@
 class PacketBridge(
     context: Context,
     addresses: List<LinkAddress>,
-    dnsAddr: InetAddress
+    dnsAddr: InetAddress,
+    portMapping: List<Pair<Int, Int>>
 ) {
     private val binder = Binder()
 
@@ -56,6 +57,10 @@
     // Register test networks to ConnectivityService.
     private val internalNetworkCallback: TestableNetworkCallback
     private val externalNetworkCallback: TestableNetworkCallback
+
+    private val internalForwardMap = HashMap<Int, Int>()
+    private val externalForwardMap = HashMap<Int, Int>()
+
     val internalNetwork: Network
     val externalNetwork: Network
     init {
@@ -65,14 +70,28 @@
         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 d8efb7d..5c79eb0 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/PacketForwarder.java
+++ b/staticlibs/testutils/devicetests/com/android/testutils/PacketForwarderBase.java
@@ -32,6 +32,7 @@
 
 import java.io.FileDescriptor;
 import java.io.IOException;
+import java.util.Map;
 import java.util.Objects;
 
 /**
@@ -57,8 +58,9 @@
  * 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 @@
     @NonNull
     final FileDescriptor mDstFd;
 
+    @NonNull
+    final Map<Integer, Integer> 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 @@
      * @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<Integer, Integer> 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 @@
         }
     }
 
-    // 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 @@
         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 740bf63..f1f0c1c 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/TestHttpServer.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/TestHttpServer.kt
@@ -25,8 +25,10 @@
  * 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<Request, Response>()