Merge "Add testTetherClatBpfOffloadUdp to TetheringTest" into main
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index 9cdba2f..c8aab88 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -33,6 +33,8 @@
 import static com.android.net.module.util.HexDump.dumpHexString;
 import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REPLY_TYPE;
 import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_HEADER_MIN_LEN;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN;
 import static com.android.testutils.DeviceInfoUtils.KVersion;
 import static com.android.testutils.TestPermissionUtil.runAsShell;
 
@@ -62,6 +64,10 @@
 import com.android.net.module.util.BpfDump;
 import com.android.net.module.util.Ipv6Utils;
 import com.android.net.module.util.Struct;
+import com.android.net.module.util.bpf.ClatEgress4Key;
+import com.android.net.module.util.bpf.ClatEgress4Value;
+import com.android.net.module.util.bpf.ClatIngress6Key;
+import com.android.net.module.util.bpf.ClatIngress6Value;
 import com.android.net.module.util.bpf.Tether4Key;
 import com.android.net.module.util.bpf.Tether4Value;
 import com.android.net.module.util.bpf.TetherStatsKey;
@@ -122,6 +128,8 @@
     private static final int TX_UDP_PACKET_SIZE = 30;
     private static final int TX_UDP_PACKET_COUNT = 123;
 
+    private static final String DUMPSYS_CLAT_RAWMAP_EGRESS4_ARG = "clatEgress4RawBpfMap";
+    private static final String DUMPSYS_CLAT_RAWMAP_INGRESS6_ARG = "clatIngress6RawBpfMap";
     private static final String DUMPSYS_TETHERING_RAWMAP_ARG = "bpfRawMap";
     private static final String DUMPSYS_RAWMAP_ARG_STATS = "--stats";
     private static final String DUMPSYS_RAWMAP_ARG_UPSTREAM4 = "--upstream4";
@@ -901,11 +909,10 @@
 
     @NonNull
     private <K extends Struct, V extends Struct> HashMap<K, V> dumpAndParseRawMap(
-            Class<K> keyClass, Class<V> valueClass, @NonNull String mapArg)
+            Class<K> keyClass, Class<V> valueClass, @NonNull String service, @NonNull String[] args)
             throws Exception {
-        final String[] args = new String[] {DUMPSYS_TETHERING_RAWMAP_ARG, mapArg};
         final String rawMapStr = runAsShell(DUMP, () ->
-                DumpTestUtils.dumpService(Context.TETHERING_SERVICE, args));
+                DumpTestUtils.dumpService(service, args));
         final HashMap<K, V> map = new HashMap<>();
 
         for (final String line : rawMapStr.split(LINE_DELIMITER)) {
@@ -918,10 +925,10 @@
 
     @Nullable
     private <K extends Struct, V extends Struct> HashMap<K, V> pollRawMapFromDump(
-            Class<K> keyClass, Class<V> valueClass, @NonNull String mapArg)
+            Class<K> keyClass, Class<V> valueClass, @NonNull String service, @NonNull String[] args)
             throws Exception {
         for (int retryCount = 0; retryCount < DUMP_POLLING_MAX_RETRY; retryCount++) {
-            final HashMap<K, V> map = dumpAndParseRawMap(keyClass, valueClass, mapArg);
+            final HashMap<K, V> map = dumpAndParseRawMap(keyClass, valueClass, service, args);
             if (!map.isEmpty()) return map;
 
             Thread.sleep(DUMP_POLLING_INTERVAL_MS);
@@ -977,8 +984,10 @@
         Thread.sleep(UDP_STREAM_SLACK_MS);
 
         // [1] Verify IPv4 upstream rule map.
+        final String[] upstreamArgs = new String[] {DUMPSYS_TETHERING_RAWMAP_ARG,
+                DUMPSYS_RAWMAP_ARG_UPSTREAM4};
         final HashMap<Tether4Key, Tether4Value> upstreamMap = pollRawMapFromDump(
-                Tether4Key.class, Tether4Value.class, DUMPSYS_RAWMAP_ARG_UPSTREAM4);
+                Tether4Key.class, Tether4Value.class, Context.TETHERING_SERVICE, upstreamArgs);
         assertNotNull(upstreamMap);
         assertEquals(1, upstreamMap.size());
 
@@ -1017,8 +1026,10 @@
         }
 
         // Dump stats map to verify.
+        final String[] statsArgs = new String[] {DUMPSYS_TETHERING_RAWMAP_ARG,
+                DUMPSYS_RAWMAP_ARG_STATS};
         final HashMap<TetherStatsKey, TetherStatsValue> statsMap = pollRawMapFromDump(
-                TetherStatsKey.class, TetherStatsValue.class, DUMPSYS_RAWMAP_ARG_STATS);
+                TetherStatsKey.class, TetherStatsValue.class, Context.TETHERING_SERVICE, statsArgs);
         assertNotNull(statsMap);
         assertEquals(1, statsMap.size());
 
@@ -1053,4 +1064,89 @@
 
         runUdp4Test();
     }
+
+    private ClatEgress4Value getClatEgress4Value() throws Exception {
+        // Command: dumpsys connectivity clatEgress4RawBpfMap
+        final String[] args = new String[] {DUMPSYS_CLAT_RAWMAP_EGRESS4_ARG};
+        final HashMap<ClatEgress4Key, ClatEgress4Value> egress4Map = pollRawMapFromDump(
+                ClatEgress4Key.class, ClatEgress4Value.class, Context.CONNECTIVITY_SERVICE, args);
+        assertNotNull(egress4Map);
+        assertEquals(1, egress4Map.size());
+        return egress4Map.entrySet().iterator().next().getValue();
+    }
+
+    private ClatIngress6Value getClatIngress6Value() throws Exception {
+        // Command: dumpsys connectivity clatIngress6RawBpfMap
+        final String[] args = new String[] {DUMPSYS_CLAT_RAWMAP_INGRESS6_ARG};
+        final HashMap<ClatIngress6Key, ClatIngress6Value> ingress6Map = pollRawMapFromDump(
+                ClatIngress6Key.class, ClatIngress6Value.class, Context.CONNECTIVITY_SERVICE, args);
+        assertNotNull(ingress6Map);
+        assertEquals(1, ingress6Map.size());
+        return ingress6Map.entrySet().iterator().next().getValue();
+    }
+
+    /**
+     * Test network topology:
+     *
+     *            public network (rawip)                 private network
+     *                      |         UE (CLAT support)         |
+     * +---------------+    V    +------------+------------+    V    +------------+
+     * | NAT64 Gateway +---------+  Upstream  | Downstream +---------+   Client   |
+     * +---------------+         +------------+------------+         +------------+
+     * remote ip                 public ip                           private ip
+     * [64:ff9b::808:808]:443    [clat ipv6]:9876                    [TetheredDevice ipv4]:9876
+     *
+     * Note that CLAT IPv6 address is generated by ClatCoordinator. Get the CLAT IPv6 address by
+     * sending out an IPv4 packet and extracting the source address from CLAT translated IPv6
+     * packet.
+     */
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testTetherClatBpfOffloadUdp() throws Exception {
+        assumeKernelSupportBpfOffloadUdpV4();
+
+        // CLAT only starts on IPv6 only network.
+        final TetheringTester tester = initTetheringTester(toList(TEST_IP6_ADDR),
+                toList(TEST_IP6_DNS));
+        final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, true /* hasIpv6 */);
+
+        // Get CLAT IPv6 address.
+        final Inet6Address clatIp6 = getClatIpv6Address(tester, tethered);
+
+        // Get current values before sending packets.
+        final ClatEgress4Value oldEgress4 = getClatEgress4Value();
+        final ClatIngress6Value oldIngress6 = getClatIngress6Value();
+
+        // Send an IPv4 UDP packet in original direction.
+        // IPv4 packet -- CLAT translation --> IPv6 packet
+        for (int i = 0; i < TX_UDP_PACKET_COUNT; i++) {
+            sendUploadPacketUdp(tethered.macAddr, tethered.routerMacAddr, tethered.ipv4Addr,
+                    REMOTE_IP4_ADDR, tester, true /* is4To6 */);
+        }
+
+        // Send an IPv6 UDP packet in reply direction.
+        // IPv6 packet -- CLAT translation --> IPv4 packet
+        for (int i = 0; i < RX_UDP_PACKET_COUNT; i++) {
+            sendDownloadPacketUdp(REMOTE_NAT64_ADDR, clatIp6, tester, true /* is6To4 */);
+        }
+
+        // After sending test packets, get stats again to verify their differences.
+        final ClatEgress4Value newEgress4 = getClatEgress4Value();
+        final ClatIngress6Value newIngress6 = getClatIngress6Value();
+
+        assertEquals(RX_UDP_PACKET_COUNT, newIngress6.packets - oldIngress6.packets);
+        assertEquals(RX_UDP_PACKET_COUNT * RX_UDP_PACKET_SIZE,
+                newIngress6.bytes - oldIngress6.bytes);
+        assertEquals(TX_UDP_PACKET_COUNT, newEgress4.packets - oldEgress4.packets);
+        // The increase in egress traffic equals the expected size of the translated UDP packets.
+        // Calculation:
+        // - Original UDP packet was TX_UDP_PACKET_SIZE bytes (IPv4 header + UDP header + payload).
+        // - After CLAT translation, each packet is now:
+        //     IPv6 header + unchanged UDP header + unchanged payload
+        // Therefore, the total size of the translated UDP packet should be:
+        //     TX_UDP_PACKET_SIZE + IPV6_HEADER_LEN - IPV4_HEADER_MIN_LEN
+        assertEquals(
+                TX_UDP_PACKET_COUNT * (TX_UDP_PACKET_SIZE + IPV6_HEADER_LEN - IPV4_HEADER_MIN_LEN),
+                newEgress4.bytes - oldEgress4.bytes);
+    }
 }