Remove NAPT from PacketForwarder
Consider below scenario:
Internal address: 1.2.3.4
External address: 5.6.7.8
Remote address: 8.8.8.8
When a packet was sent from 1.2.3.4 to 8.8.8.8, the current design
translate it to a packet from 8.8.8.8 to 5.6.7.8 and foward it
to the external interface. However, when the response is received
from the external interface, there is a need to remember what is
the original address to forward to. Thus, an address translation
mechanism was introduced in current design.
This change simplify PacketForwarder by removing the address
translation.
Consider, if the 2 interfaces are using the same address.
Internal address: 1.2.3.4
External address: 1.2.3.4
Remote address: 8.8.8.8
When a packet was sent from 1.2.3.4 to 8.8.8.8, simply swap
source and destination and and foward it to the external
interface. When there is a response from the external interface,
the same process can be done, this removes the need of address
translation.
Test: atest NetworkStackIntegrationTests:android.net.NetworkStatsIntegrationTest
Test: atest CtsHostsideNetworkTests:com.android.cts.net.HostsideVpnTests
Bug: N/A
Change-Id: I86f2e774e9112905e5255b5a28c927562a42ab84
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/NatExternalPacketForwarder.kt b/staticlibs/testutils/devicetests/com/android/testutils/NatExternalPacketForwarder.kt
deleted file mode 100644
index d7961a0..0000000
--- a/staticlibs/testutils/devicetests/com/android/testutils/NatExternalPacketForwarder.kt
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2023 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
-import java.net.InetAddress
-
-/**
- * A class that forwards packets from the external {@link TestNetworkInterface} to the internal
- * {@link TestNetworkInterface} with NAT. See {@link NatPacketForwarderBase} for detail.
- */
-class NatExternalPacketForwarder(
- srcFd: FileDescriptor,
- mtu: Int,
- dstFd: FileDescriptor,
- extAddr: InetAddress,
- natMap: PacketBridge.NatMap
-) : NatPacketForwarderBase(srcFd, mtu, dstFd, extAddr, natMap) {
-
- /**
- * Rewrite addresses, ports and fix up checksums for packets received on the external
- * interface.
- *
- * Incoming response from external interface which is being forwarded to the internal
- * interface with translated address, e.g. 1.2.3.4:80 -> 8.8.8.8:1234
- * will be translated into 8.8.8.8:80 -> 192.168.1.1:5678.
- *
- * For packets that are not an incoming response, do not forward them to the
- * internal interface.
- */
- override fun preparePacketForForwarding(buf: ByteArray, len: Int, version: Int, proto: Int) {
- val (addrPos, addrLen) = getAddressPositionAndLength(version)
-
- // TODO: support one external address per ip version.
- val extAddrBuf = mExtAddr.address
- if (addrLen != extAddrBuf.size) throw IllegalStateException("Packet IP version mismatch")
-
- // Get internal address by port.
- val transportOffset =
- if (version == 4) PacketReflector.IPV4_HEADER_LENGTH
- else PacketReflector.IPV6_HEADER_LENGTH
- val dstPort = getPortAt(buf, transportOffset + DESTINATION_PORT_OFFSET)
- val intAddrInfo = synchronized(mNatMap) { mNatMap.fromExternalPort(dstPort) }
- // No mapping, skip. This usually happens if the connection is initiated directly on
- // the external interface, e.g. DNS64 resolution, network validation, etc.
- if (intAddrInfo == null) return
-
- val intAddrBuf = intAddrInfo.address.address
- val intPort = intAddrInfo.port
-
- // Copy the original destination to into the source address.
- for (i in 0 until addrLen) {
- buf[addrPos + i] = buf[addrPos + addrLen + i]
- }
-
- // Copy the internal address into the destination address.
- for (i in 0 until addrLen) {
- buf[addrPos + addrLen + i] = intAddrBuf[i]
- }
-
- // Copy the internal port into the destination port.
- setPortAt(intPort, buf, transportOffset + DESTINATION_PORT_OFFSET)
-
- // Fix IP and Transport layer checksum.
- fixPacketChecksum(buf, len, version, proto.toByte())
- }
-}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/NatInternalPacketForwarder.kt b/staticlibs/testutils/devicetests/com/android/testutils/NatInternalPacketForwarder.kt
deleted file mode 100644
index fa39d19..0000000
--- a/staticlibs/testutils/devicetests/com/android/testutils/NatInternalPacketForwarder.kt
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2023 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
-import java.net.InetAddress
-
-/**
- * A class that forwards packets from the internal {@link TestNetworkInterface} to the external
- * {@link TestNetworkInterface} with NAT. See {@link NatPacketForwarderBase} for detail.
- */
-class NatInternalPacketForwarder(
- srcFd: FileDescriptor,
- mtu: Int,
- dstFd: FileDescriptor,
- extAddr: InetAddress,
- natMap: PacketBridge.NatMap
-) : NatPacketForwarderBase(srcFd, mtu, dstFd, extAddr, natMap) {
-
- /**
- * Rewrite addresses, ports and fix up checksums for packets received on the internal
- * interface.
- *
- * Outgoing packet from the internal interface which is being forwarded to the
- * external interface with translated address, e.g. 192.168.1.1:5678 -> 8.8.8.8:80
- * will be translated into 8.8.8.8:1234 -> 1.2.3.4:80.
- *
- * The external port, e.g. 1234 in the above example, is the port number assigned by
- * the forwarder when creating the mapping to identify the source address and port when
- * the response is coming from the external interface. See {@link PacketBridge.NatMap}
- * for detail.
- */
- override fun preparePacketForForwarding(buf: ByteArray, len: Int, version: Int, proto: Int) {
- val (addrPos, addrLen) = getAddressPositionAndLength(version)
-
- // TODO: support one external address per ip version.
- val extAddrBuf = mExtAddr.address
- if (addrLen != extAddrBuf.size) throw IllegalStateException("Packet IP version mismatch")
-
- val srcAddr = getInetAddressAt(buf, addrPos, addrLen)
-
- // Copy the original destination to into the source address.
- for (i in 0 until addrLen) {
- buf[addrPos + i] = buf[addrPos + addrLen + i]
- }
-
- // Copy the external address into the destination address.
- for (i in 0 until addrLen) {
- buf[addrPos + addrLen + i] = extAddrBuf[i]
- }
-
- // Add an entry to NAT mapping table.
- val transportOffset =
- if (version == 4) PacketReflector.IPV4_HEADER_LENGTH
- else PacketReflector.IPV6_HEADER_LENGTH
- val srcPort = getPortAt(buf, transportOffset)
- val extPort = synchronized(mNatMap) { mNatMap.toExternalPort(srcAddr, srcPort, proto) }
- // Copy the external port to into the source port.
- setPortAt(extPort, buf, transportOffset)
-
- // Fix IP and Transport layer checksum.
- fixPacketChecksum(buf, len, version, proto.toByte())
- }
-}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/PacketBridge.kt b/staticlibs/testutils/devicetests/com/android/testutils/PacketBridge.kt
index d50f78a..0d0cc06 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/PacketBridge.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/PacketBridge.kt
@@ -16,6 +16,7 @@
package com.android.testutils
+import android.annotation.SuppressLint
import android.content.Context
import android.net.ConnectivityManager
import android.net.LinkAddress
@@ -31,29 +32,27 @@
import java.net.InetAddress
import libcore.io.IoUtils
-private const val MIN_PORT_NUMBER = 1025
-private const val MAX_PORT_NUMBER = 65535
-
/**
- * A class that set up two {@link TestNetworkInterface} with NAT, and forward packets between them.
+ * A class that set up two {@link TestNetworkInterface}, and forward packets between them.
*
- * See {@link NatPacketForwarder} for more detailed information.
+ * See {@link PacketForwarder} for more detailed information.
*/
+// TODO: Support multiple addresses.
class PacketBridge(
context: Context,
- internalAddr: LinkAddress,
- externalAddr: LinkAddress,
+ address: LinkAddress,
dnsAddr: InetAddress
) {
- private val natMap = NatMap()
private val binder = Binder()
private val cm = context.getSystemService(ConnectivityManager::class.java)!!
private val tnm = context.getSystemService(TestNetworkManager::class.java)!!
- // Create test networks.
- private val internalIface = tnm.createTunInterface(listOf(internalAddr))
- private val externalIface = tnm.createTunInterface(listOf(externalAddr))
+ // Create test networks. The needed permissions should be supplied by the callers.
+ @SuppressLint("MissingPermission")
+ private val internalIface = tnm.createTunInterface(listOf(address))
+ @SuppressLint("MissingPermission")
+ private val externalIface = tnm.createTunInterface(listOf(address))
// Register test networks to ConnectivityService.
private val internalNetworkCallback: TestableNetworkCallback
@@ -61,32 +60,20 @@
val internalNetwork: Network
val externalNetwork: Network
init {
- val (inCb, inNet) = createTestNetwork(internalIface, internalAddr, dnsAddr)
- val (exCb, exNet) = createTestNetwork(externalIface, externalAddr, dnsAddr)
+ val (inCb, inNet) = createTestNetwork(internalIface, address, dnsAddr)
+ val (exCb, exNet) = createTestNetwork(externalIface, address, dnsAddr)
internalNetworkCallback = inCb
externalNetworkCallback = exCb
internalNetwork = inNet
externalNetwork = exNet
}
- // Setup the packet bridge.
+ // Set up the packet bridge.
private val internalFd = internalIface.fileDescriptor.fileDescriptor
private val externalFd = externalIface.fileDescriptor.fileDescriptor
- private val pr1 = NatInternalPacketForwarder(
- internalFd,
- 1500,
- externalFd,
- externalAddr.address,
- natMap
- )
- private val pr2 = NatExternalPacketForwarder(
- externalFd,
- 1500,
- internalFd,
- externalAddr.address,
- natMap
- )
+ private val pr1 = PacketForwarder(internalFd, 1500, externalFd)
+ private val pr2 = PacketForwarder(externalFd, 1500, internalFd)
fun start() {
IoUtils.setBlocking(internalFd, true /* blocking */)
@@ -130,44 +117,4 @@
val network = testCb.expect<Available>().network
return testCb to network
}
-
- /**
- * A helper class to maintain the mappings between internal addresses/ports and external
- * ports.
- *
- * This class assigns an unused external port number if the mapping between
- * srcaddress:srcport:protocol and the external port does not exist yet.
- *
- * Note that this class is not thread-safe. The instance of the class needs to be
- * synchronized in the callers when being used in multiple threads.
- */
- class NatMap {
- data class AddressInfo(val address: InetAddress, val port: Int, val protocol: Int)
-
- private val mToExternalPort = HashMap<AddressInfo, Int>()
- private val mFromExternalPort = HashMap<Int, AddressInfo>()
-
- // Skip well-known port 0~1024.
- private var nextExternalPort = MIN_PORT_NUMBER
-
- fun toExternalPort(addr: InetAddress, port: Int, protocol: Int): Int {
- val info = AddressInfo(addr, port, protocol)
- val extPort: Int
- if (!mToExternalPort.containsKey(info)) {
- extPort = nextExternalPort++
- if (nextExternalPort > MAX_PORT_NUMBER) {
- throw IllegalStateException("Available ports are exhausted")
- }
- mToExternalPort[info] = extPort
- mFromExternalPort[extPort] = info
- } else {
- extPort = mToExternalPort[info]!!
- }
- return extPort
- }
-
- fun fromExternalPort(port: Int): AddressInfo? {
- return mFromExternalPort[port]
- }
- }
}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/NatPacketForwarderBase.java b/staticlibs/testutils/devicetests/com/android/testutils/PacketForwarder.java
similarity index 62%
rename from staticlibs/testutils/devicetests/com/android/testutils/NatPacketForwarderBase.java
rename to staticlibs/testutils/devicetests/com/android/testutils/PacketForwarder.java
index 0a2b5d4..d8efb7d 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/NatPacketForwarderBase.java
+++ b/staticlibs/testutils/devicetests/com/android/testutils/PacketForwarder.java
@@ -30,16 +30,13 @@
import android.system.Os;
import android.util.Log;
-import androidx.annotation.GuardedBy;
-
import java.io.FileDescriptor;
import java.io.IOException;
-import java.net.InetAddress;
import java.util.Objects;
/**
* A class that forwards packets from a {@link TestNetworkInterface} to another
- * {@link TestNetworkInterface} with NAT.
+ * {@link TestNetworkInterface}.
*
* For testing purposes, a {@link TestNetworkInterface} provides a {@link FileDescriptor}
* which allows content injection on the test network. However, this could be hard to use
@@ -54,30 +51,14 @@
*
* To make it work, an internal interface and an external interface are defined, where
* the client might send packets from the internal interface which are originated from
- * multiple addresses to a server that listens on the external address.
- *
- * When forwarding the outgoing packet on the internal interface, a simple NAT mechanism
- * is implemented during forwarding, which will swap the source and destination,
- * but replacing the source address with the external address,
- * e.g. 192.168.1.1:1234 -> 8.8.8.8:80 will be translated into 8.8.8.8:1234 -> 1.2.3.4:80.
- *
- * For the above example, a client who sends http request will have a hallucination that
- * it is talking to a remote server at 8.8.8.8. Also, the server listens on 1.2.3.4 will
- * have a different hallucination that the request is sent from a remote client at 8.8.8.8,
- * to a local address 1.2.3.4.
- *
- * And a NAT mapping is created at the time when the outgoing packet is forwarded.
- * With a different internal source port, the instance learned that when a response with the
- * destination port 1234, it should forward the packet to the internal address 192.168.1.1.
+ * multiple addresses to a server that listens on the different port.
*
* For the incoming packet received from external interface, for example a http response sent
* from the http server, the same mechanism is applied but in a different direction,
- * where the source and destination will be swapped, and the source address will be replaced
- * with the internal address, which is obtained from the NAT mapping described above.
+ * where the source and destination will be swapped.
*/
-public abstract class NatPacketForwarderBase extends Thread {
- private static final String TAG = "NatPacketForwarder";
- static final int DESTINATION_PORT_OFFSET = 2;
+public class PacketForwarder extends Thread {
+ private static final String TAG = "PacketForwarder";
// The source fd to read packets from.
@NonNull
@@ -88,27 +69,12 @@
// The destination fd to write packets to.
@NonNull
final FileDescriptor mDstFd;
- // The NAT mapping table shared between two NatPacketForwarder instances to map from
- // the source port to the associated internal address. The map can be read/write from two
- // different threads on any given time whenever receiving packets on the
- // {@link TestNetworkInterface}. Thus, synchronize on the object when reading/writing is needed.
- @GuardedBy("mNatMap")
- @NonNull
- final PacketBridge.NatMap mNatMap;
- // The address of the external interface. See {@link NatPacketForwarder}.
- @NonNull
- final InetAddress mExtAddr;
/**
- * Construct a {@link NatPacketForwarderBase}.
+ * Construct a {@link PacketForwarder}.
*
* This class reads packets from {@code srcFd} of a {@link TestNetworkInterface}, and
- * forwards them to the {@code dstFd} of another {@link TestNetworkInterface} with
- * NAT applied. See {@link NatPacketForwarderBase}.
- *
- * To apply NAT, the address of the external interface needs to be supplied through
- * {@code extAddr} to identify the external interface. And a shared NAT mapping table,
- * {@code natMap} is needed to be shared between these two instances.
+ * forwards them to the {@code dstFd} of another {@link TestNetworkInterface}.
*
* Note that this class is not useful if the instance is not managed by a
* {@link PacketBridge} to set up a two-way communication.
@@ -116,29 +82,15 @@
* @param srcFd {@link FileDescriptor} to read packets from.
* @param mtu MTU of the test network.
* @param dstFd {@link FileDescriptor} to write packets to.
- * @param extAddr the external address, which is the address of the external interface.
- * See {@link NatPacketForwarderBase}.
- * @param natMap the NAT mapping table shared between two {@link NatPacketForwarderBase}
- * instance.
*/
- public NatPacketForwarderBase(@NonNull FileDescriptor srcFd, int mtu,
- @NonNull FileDescriptor dstFd, @NonNull InetAddress extAddr,
- @NonNull PacketBridge.NatMap natMap) {
+ public PacketForwarder(@NonNull FileDescriptor srcFd, int mtu,
+ @NonNull FileDescriptor dstFd) {
super(TAG);
mSrcFd = Objects.requireNonNull(srcFd);
mBuf = new byte[mtu];
mDstFd = Objects.requireNonNull(dstFd);
- mExtAddr = Objects.requireNonNull(extAddr);
- mNatMap = Objects.requireNonNull(natMap);
}
- /**
- * A method to prepare forwarding packets between two instances of {@link TestNetworkInterface},
- * which includes re-write addresses, ports and fix up checksums.
- * Subclasses should override this method to implement a simple NAT.
- */
- abstract void preparePacketForForwarding(@NonNull byte[] buf, int len, int version, int proto);
-
private void forwardPacket(@NonNull byte[] buf, int len) {
try {
Os.write(mDstFd, buf, 0, len);
@@ -190,8 +142,9 @@
if (len < ipHdrLen + transportHdrLen) {
throw new IllegalStateException("Unexpected buffer length: " + len);
}
- // Re-write addresses, ports and fix up checksums.
- preparePacketForForwarding(mBuf, len, version, proto);
+ // Swap addresses.
+ PacketReflectorUtil.swapAddresses(mBuf, version);
+
// Send the packet to the destination fd.
forwardPacket(mBuf, len);
}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/PacketReflector.java b/staticlibs/testutils/devicetests/com/android/testutils/PacketReflector.java
index 69392d4..ce20d67 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/PacketReflector.java
+++ b/staticlibs/testutils/devicetests/com/android/testutils/PacketReflector.java
@@ -87,31 +87,6 @@
mBuf = new byte[mtu];
}
- private static void swapBytes(@NonNull byte[] buf, int pos1, int pos2, int len) {
- for (int i = 0; i < len; i++) {
- byte b = buf[pos1 + i];
- buf[pos1 + i] = buf[pos2 + i];
- buf[pos2 + i] = b;
- }
- }
-
- private static void swapAddresses(@NonNull byte[] buf, int version) {
- int addrPos, addrLen;
- switch (version) {
- case 4:
- addrPos = IPV4_ADDR_OFFSET;
- addrLen = IPV4_ADDR_LENGTH;
- break;
- case 6:
- addrPos = IPV6_ADDR_OFFSET;
- addrLen = IPV6_ADDR_LENGTH;
- break;
- default:
- throw new IllegalArgumentException();
- }
- swapBytes(buf, addrPos, addrPos + addrLen, addrLen);
- }
-
// Reflect TCP packets: swap the source and destination addresses, but don't change the ports.
// This is used by the test to "connect to itself" through the VPN.
private void processTcpPacket(@NonNull byte[] buf, int version, int len, int hdrLen) {
@@ -120,7 +95,7 @@
}
// Swap src and dst IP addresses.
- swapAddresses(buf, version);
+ PacketReflectorUtil.swapAddresses(buf, version);
// Send the packet back.
writePacket(buf, len);
@@ -134,11 +109,11 @@
}
// Swap src and dst IP addresses.
- swapAddresses(buf, version);
+ PacketReflectorUtil.swapAddresses(buf, version);
// Swap dst and src ports.
int portOffset = hdrLen;
- swapBytes(buf, portOffset, portOffset + 2, 2);
+ PacketReflectorUtil.swapBytes(buf, portOffset, portOffset + 2, 2);
// Send the packet back.
writePacket(buf, len);
@@ -160,7 +135,7 @@
// Swap src and dst IP addresses, and send the packet back.
// This effectively pings the device to see if it replies.
- swapAddresses(buf, version);
+ PacketReflectorUtil.swapAddresses(buf, version);
writePacket(buf, len);
// The device should have replied, and buf should now contain a ping response.
@@ -202,7 +177,7 @@
}
// Now swap the addresses again and reflect the packet. This sends a ping reply.
- swapAddresses(buf, version);
+ PacketReflectorUtil.swapAddresses(buf, version);
writePacket(buf, len);
}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/PacketReflectorUtil.kt b/staticlibs/testutils/devicetests/com/android/testutils/PacketReflectorUtil.kt
index 498b1a3..ad259c5 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/PacketReflectorUtil.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/PacketReflectorUtil.kt
@@ -112,3 +112,28 @@
else -> throw IllegalArgumentException("Unsupported protocol: $protocol")
}
}
+
+fun swapBytes(buf: ByteArray, pos1: Int, pos2: Int, len: Int) {
+ for (i in 0 until len) {
+ val b = buf[pos1 + i]
+ buf[pos1 + i] = buf[pos2 + i]
+ buf[pos2 + i] = b
+ }
+}
+
+fun swapAddresses(buf: ByteArray, version: Int) {
+ val addrPos: Int
+ val addrLen: Int
+ when (version) {
+ 4 -> {
+ addrPos = PacketReflector.IPV4_ADDR_OFFSET
+ addrLen = PacketReflector.IPV4_ADDR_LENGTH
+ }
+ 6 -> {
+ addrPos = PacketReflector.IPV6_ADDR_OFFSET
+ addrLen = PacketReflector.IPV6_ADDR_LENGTH
+ }
+ else -> throw java.lang.IllegalArgumentException()
+ }
+ swapBytes(buf, addrPos, addrPos + addrLen, addrLen)
+}