Merge "Add createGetLinkMessage() at RtNetlinkLinkMessage" into main
diff --git a/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java b/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
index 528991f..92df384 100644
--- a/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
@@ -32,7 +32,9 @@
 import android.net.ConnectivityManager;
 import android.net.IpPrefix;
 import android.net.LinkAddress;
+import android.net.LinkProperties;
 import android.net.Network;
+import android.net.NetworkCapabilities;
 import android.net.ip.IpServer;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -100,26 +102,26 @@
     }
 
     /**
-     * Record a new upstream IpPrefix which may conflict with tethering downstreams.
-     * The downstreams will be notified if a conflict is found. When updateUpstreamPrefix is called,
+     * Record a new upstream IpPrefix which may conflict with tethering downstreams. The downstreams
+     * will be notified if a conflict is found. When updateUpstreamPrefix is called,
      * UpstreamNetworkState must have an already populated LinkProperties.
      */
-    public void updateUpstreamPrefix(final UpstreamNetworkState ns) {
+    public void updateUpstreamPrefix(
+            final LinkProperties lp, final NetworkCapabilities nc, final Network network) {
         // Do not support VPN as upstream. Normally, networkCapabilities is not expected to be null,
         // but just checking to be sure.
-        if (ns.networkCapabilities != null && ns.networkCapabilities.hasTransport(TRANSPORT_VPN)) {
-            removeUpstreamPrefix(ns.network);
+        if (nc != null && nc.hasTransport(TRANSPORT_VPN)) {
+            removeUpstreamPrefix(network);
             return;
         }
 
-        final ArrayList<IpPrefix> ipv4Prefixes = getIpv4Prefixes(
-                ns.linkProperties.getAllLinkAddresses());
+        final ArrayList<IpPrefix> ipv4Prefixes = getIpv4Prefixes(lp.getAllLinkAddresses());
         if (ipv4Prefixes.isEmpty()) {
-            removeUpstreamPrefix(ns.network);
+            removeUpstreamPrefix(network);
             return;
         }
 
-        mUpstreamPrefixMap.put(ns.network, ipv4Prefixes);
+        mUpstreamPrefixMap.put(network, ipv4Prefixes);
         handleMaybePrefixConflict(ipv4Prefixes);
     }
 
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 1938a08..49bc86e 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -2004,7 +2004,8 @@
             final UpstreamNetworkState ns = (UpstreamNetworkState) o;
             switch (arg1) {
                 case UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES:
-                    mPrivateAddressCoordinator.updateUpstreamPrefix(ns);
+                    mPrivateAddressCoordinator.updateUpstreamPrefix(
+                            ns.linkProperties, ns.networkCapabilities, ns.network);
                     break;
                 case UpstreamNetworkMonitor.EVENT_ON_LOST:
                     mPrivateAddressCoordinator.removeUpstreamPrefix(ns.network);
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
index 2298a1a..f22803d 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
@@ -118,6 +118,11 @@
         return address;
     }
 
+    private void updateUpstreamPrefix(UpstreamNetworkState ns) {
+        mPrivateAddressCoordinator.updateUpstreamPrefix(
+                ns.linkProperties, ns.networkCapabilities, ns.network);
+    }
+
     @Test
     public void testRequestDownstreamAddressWithoutUsingLastAddress() throws Exception {
         final IpPrefix bluetoothPrefix = asIpPrefix(mBluetoothAddress);
@@ -234,7 +239,7 @@
         final UpstreamNetworkState wifiUpstream = buildUpstreamNetworkState(mWifiNetwork,
                 new LinkAddress("192.168.88.23/16"), null,
                 makeNetworkCapabilities(TRANSPORT_WIFI));
-        mPrivateAddressCoordinator.updateUpstreamPrefix(wifiUpstream);
+        updateUpstreamPrefix(wifiUpstream);
         verify(mHotspotIpServer).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
         verify(mUsbIpServer).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
     }
@@ -276,47 +281,47 @@
         // and the UpstreamNetworkState update, just make sure no crash in this case.
         final UpstreamNetworkState noCapUpstream = buildUpstreamNetworkState(mMobileNetwork,
                 new LinkAddress("10.0.0.8/24"), null, null);
-        mPrivateAddressCoordinator.updateUpstreamPrefix(noCapUpstream);
+        updateUpstreamPrefix(noCapUpstream);
         verify(mHotspotIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
         // - test mobile upstream with no address.
         final UpstreamNetworkState noAddress = buildUpstreamNetworkState(mMobileNetwork,
                 null, null, makeNetworkCapabilities(TRANSPORT_CELLULAR));
-        mPrivateAddressCoordinator.updateUpstreamPrefix(noCapUpstream);
+        updateUpstreamPrefix(noCapUpstream);
         verify(mHotspotIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
         // - Update v6 only mobile network, hotspot prefix should not be removed.
         final UpstreamNetworkState v6OnlyMobile = buildUpstreamNetworkState(mMobileNetwork,
                 null, new LinkAddress("2001:db8::/64"),
                 makeNetworkCapabilities(TRANSPORT_CELLULAR));
-        mPrivateAddressCoordinator.updateUpstreamPrefix(v6OnlyMobile);
+        updateUpstreamPrefix(v6OnlyMobile);
         verify(mHotspotIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
         mPrivateAddressCoordinator.removeUpstreamPrefix(mMobileNetwork);
         // - Update v4 only mobile network, hotspot prefix should not be removed.
         final UpstreamNetworkState v4OnlyMobile = buildUpstreamNetworkState(mMobileNetwork,
                 new LinkAddress("10.0.0.8/24"), null,
                 makeNetworkCapabilities(TRANSPORT_CELLULAR));
-        mPrivateAddressCoordinator.updateUpstreamPrefix(v4OnlyMobile);
+        updateUpstreamPrefix(v4OnlyMobile);
         verify(mHotspotIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
         // - Update v4v6 mobile network, hotspot prefix should not be removed.
         final UpstreamNetworkState v4v6Mobile = buildUpstreamNetworkState(mMobileNetwork,
                 new LinkAddress("10.0.0.8/24"), new LinkAddress("2001:db8::/64"),
                 makeNetworkCapabilities(TRANSPORT_CELLULAR));
-        mPrivateAddressCoordinator.updateUpstreamPrefix(v4v6Mobile);
+        updateUpstreamPrefix(v4v6Mobile);
         verify(mHotspotIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
         // - Update v6 only wifi network, hotspot prefix should not be removed.
         final UpstreamNetworkState v6OnlyWifi = buildUpstreamNetworkState(mWifiNetwork,
                 null, new LinkAddress("2001:db8::/64"), makeNetworkCapabilities(TRANSPORT_WIFI));
-        mPrivateAddressCoordinator.updateUpstreamPrefix(v6OnlyWifi);
+        updateUpstreamPrefix(v6OnlyWifi);
         verify(mHotspotIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
         mPrivateAddressCoordinator.removeUpstreamPrefix(mWifiNetwork);
         // - Update vpn network, it conflict with hotspot prefix but VPN networks are ignored.
         final UpstreamNetworkState v4OnlyVpn = buildUpstreamNetworkState(mVpnNetwork,
                 new LinkAddress("192.168.43.5/24"), null, makeNetworkCapabilities(TRANSPORT_VPN));
-        mPrivateAddressCoordinator.updateUpstreamPrefix(v4OnlyVpn);
+        updateUpstreamPrefix(v4OnlyVpn);
         verify(mHotspotIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
         // - Update v4 only wifi network, it conflict with hotspot prefix.
         final UpstreamNetworkState v4OnlyWifi = buildUpstreamNetworkState(mWifiNetwork,
                 new LinkAddress("192.168.43.5/24"), null, makeNetworkCapabilities(TRANSPORT_WIFI));
-        mPrivateAddressCoordinator.updateUpstreamPrefix(v4OnlyWifi);
+        updateUpstreamPrefix(v4OnlyWifi);
         verify(mHotspotIpServer).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
         reset(mHotspotIpServer);
         // - Restart hotspot again and its prefix is different previous.
@@ -325,7 +330,7 @@
                 CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
         final IpPrefix hotspotPrefix2 = asIpPrefix(hotspotAddr2);
         assertNotEquals(hotspotPrefix, hotspotPrefix2);
-        mPrivateAddressCoordinator.updateUpstreamPrefix(v4OnlyWifi);
+        updateUpstreamPrefix(v4OnlyWifi);
         verify(mHotspotIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
         // - Usb tethering can be enabled and its prefix is different with conflict one.
         final LinkAddress usbAddr = requestDownstreamAddress(mUsbIpServer,
@@ -352,7 +357,7 @@
         final UpstreamNetworkState wifiUpstream = buildUpstreamNetworkState(mWifiNetwork,
                 new LinkAddress("192.168.134.13/26"), null,
                 makeNetworkCapabilities(TRANSPORT_WIFI));
-        mPrivateAddressCoordinator.updateUpstreamPrefix(wifiUpstream);
+        updateUpstreamPrefix(wifiUpstream);
 
         // Check whether return address is next prefix of 192.168.134.0/24.
         final LinkAddress addr1 = requestDownstreamAddress(mHotspotIpServer,
@@ -361,7 +366,7 @@
         final UpstreamNetworkState wifiUpstream2 = buildUpstreamNetworkState(mWifiNetwork,
                 new LinkAddress("192.168.149.16/19"), null,
                 makeNetworkCapabilities(TRANSPORT_WIFI));
-        mPrivateAddressCoordinator.updateUpstreamPrefix(wifiUpstream2);
+        updateUpstreamPrefix(wifiUpstream2);
 
 
         // The conflict range is 128 ~ 159, so the address is 192.168.160.5/24.
@@ -376,8 +381,8 @@
         final UpstreamNetworkState mobileUpstream2 = buildUpstreamNetworkState(mMobileNetwork2,
                 new LinkAddress("192.168.170.7/19"), null,
                 makeNetworkCapabilities(TRANSPORT_CELLULAR));
-        mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream);
-        mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream2);
+        updateUpstreamPrefix(mobileUpstream);
+        updateUpstreamPrefix(mobileUpstream2);
 
         // The conflict range are 128 ~ 159 and 159 ~ 191, so the address is 192.168.192.5/24.
         final LinkAddress addr3 = requestDownstreamAddress(mHotspotIpServer,
@@ -386,7 +391,7 @@
         final UpstreamNetworkState mobileUpstream3 = buildUpstreamNetworkState(mMobileNetwork3,
                 new LinkAddress("192.168.188.133/17"), null,
                 makeNetworkCapabilities(TRANSPORT_CELLULAR));
-        mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream3);
+        updateUpstreamPrefix(mobileUpstream3);
 
         // Conflict range: 128 ~ 255. The next available address is 192.168.0.5 because
         // 192.168.134/24 ~ 192.168.255.255/24 is not available.
@@ -396,7 +401,7 @@
         final UpstreamNetworkState mobileUpstream4 = buildUpstreamNetworkState(mMobileNetwork4,
                 new LinkAddress("192.168.3.59/21"), null,
                 makeNetworkCapabilities(TRANSPORT_CELLULAR));
-        mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream4);
+        updateUpstreamPrefix(mobileUpstream4);
 
         // Conflict ranges: 128 ~ 255 and 0 ~ 7, so the address is 192.168.8.5/24.
         final LinkAddress addr5 = requestDownstreamAddress(mHotspotIpServer,
@@ -405,7 +410,7 @@
         final UpstreamNetworkState mobileUpstream5 = buildUpstreamNetworkState(mMobileNetwork5,
                 new LinkAddress("192.168.68.43/21"), null,
                 makeNetworkCapabilities(TRANSPORT_CELLULAR));
-        mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream5);
+        updateUpstreamPrefix(mobileUpstream5);
 
         // Update an upstream that does *not* conflict, check whether return the same address
         // 192.168.5/24.
@@ -415,7 +420,7 @@
         final UpstreamNetworkState mobileUpstream6 = buildUpstreamNetworkState(mMobileNetwork6,
                 new LinkAddress("192.168.10.97/21"), null,
                 makeNetworkCapabilities(TRANSPORT_CELLULAR));
-        mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream6);
+        updateUpstreamPrefix(mobileUpstream6);
 
         // Conflict ranges: 0 ~ 15 and 128 ~ 255, so the address is 192.168.16.5/24.
         final LinkAddress addr7 = requestDownstreamAddress(mHotspotIpServer,
@@ -424,7 +429,7 @@
         final UpstreamNetworkState mobileUpstream7 = buildUpstreamNetworkState(mMobileNetwork6,
                 new LinkAddress("192.168.0.0/17"), null,
                 makeNetworkCapabilities(TRANSPORT_CELLULAR));
-        mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream7);
+        updateUpstreamPrefix(mobileUpstream7);
 
         // Choose prefix from next range(172.16.0.0/12) when no available prefix in 192.168.0.0/16.
         final LinkAddress addr8 = requestDownstreamAddress(mHotspotIpServer,
@@ -443,7 +448,7 @@
         final UpstreamNetworkState wifiUpstream = buildUpstreamNetworkState(mWifiNetwork,
                 new LinkAddress("192.168.88.23/17"), null,
                 makeNetworkCapabilities(TRANSPORT_WIFI));
-        mPrivateAddressCoordinator.updateUpstreamPrefix(wifiUpstream);
+        updateUpstreamPrefix(wifiUpstream);
         verifyNotifyConflictAndRelease(mHotspotIpServer);
 
         // Check whether return address is next address of prefix 192.168.128.0/17.
@@ -453,7 +458,7 @@
         final UpstreamNetworkState mobileUpstream = buildUpstreamNetworkState(mMobileNetwork,
                 new LinkAddress("192.1.2.3/8"), null,
                 makeNetworkCapabilities(TRANSPORT_CELLULAR));
-        mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream);
+        updateUpstreamPrefix(mobileUpstream);
         verifyNotifyConflictAndRelease(mHotspotIpServer);
 
         // Check whether return address is under prefix 172.16.0.0/12.
@@ -463,7 +468,7 @@
         final UpstreamNetworkState mobileUpstream2 = buildUpstreamNetworkState(mMobileNetwork2,
                 new LinkAddress("172.28.123.100/14"), null,
                 makeNetworkCapabilities(TRANSPORT_CELLULAR));
-        mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream2);
+        updateUpstreamPrefix(mobileUpstream2);
         verifyNotifyConflictAndRelease(mHotspotIpServer);
 
         // 172.28.0.0 ~ 172.31.255.255 is not available.
@@ -479,7 +484,7 @@
         final UpstreamNetworkState mobileUpstream3 = buildUpstreamNetworkState(mMobileNetwork3,
                 new LinkAddress("172.16.0.1/24"), null,
                 makeNetworkCapabilities(TRANSPORT_CELLULAR));
-        mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream3);
+        updateUpstreamPrefix(mobileUpstream3);
         verifyNotifyConflictAndRelease(mHotspotIpServer);
         verify(mUsbIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
 
@@ -490,7 +495,7 @@
         final UpstreamNetworkState mobileUpstream4 = buildUpstreamNetworkState(mMobileNetwork4,
                 new LinkAddress("172.16.0.1/13"), null,
                 makeNetworkCapabilities(TRANSPORT_CELLULAR));
-        mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream4);
+        updateUpstreamPrefix(mobileUpstream4);
         verifyNotifyConflictAndRelease(mHotspotIpServer);
         verifyNotifyConflictAndRelease(mUsbIpServer);
 
@@ -505,7 +510,7 @@
         final UpstreamNetworkState mobileUpstream5 = buildUpstreamNetworkState(mMobileNetwork5,
                 new LinkAddress("172.24.0.1/12"), null,
                 makeNetworkCapabilities(TRANSPORT_CELLULAR));
-        mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream5);
+        updateUpstreamPrefix(mobileUpstream5);
         verifyNotifyConflictAndRelease(mHotspotIpServer);
         verifyNotifyConflictAndRelease(mUsbIpServer);
 
diff --git a/bpf/loader/netbpfload.rc b/bpf/loader/netbpfload.rc
index e1af47f..10bfbb2 100644
--- a/bpf/loader/netbpfload.rc
+++ b/bpf/loader/netbpfload.rc
@@ -1,22 +1,3 @@
-# zygote-start is what officially starts netd (see //system/core/rootdir/init.rc)
-# However, on some hardware it's started from post-fs-data as well, which is just
-# a tad earlier.  There's no benefit to that though, since on 4.9+ P+ devices netd
-# will just block until bpfloader finishes and sets the bpf.progs_loaded property.
-#
-# It is important that we start bpfloader after:
-#   - /sys/fs/bpf is already mounted,
-#   - apex (incl. rollback) is initialized (so that in the future we can load bpf
-#     programs shipped as part of apex mainline modules)
-#   - logd is ready for us to log stuff
-#
-# At the same time we want to be as early as possible to reduce races and thus
-# failures (before memory is fragmented, and cpu is busy running tons of other
-# stuff) and we absolutely want to be before netd and the system boot slot is
-# considered to have booted successfully.
-#
-on load_bpf_programs
-    exec_start bpfloader
-
 # Note: This will actually execute /apex/com.android.tethering/bin/netbpfload
 # by virtue of 'service bpfloader' being overridden by the apex shipped .rc
 # Warning: most of the below settings are irrelevant unless the apex is missing.
diff --git a/bpf/netd/BpfHandler.cpp b/bpf/netd/BpfHandler.cpp
index 15ab19c..5dea851 100644
--- a/bpf/netd/BpfHandler.cpp
+++ b/bpf/netd/BpfHandler.cpp
@@ -114,6 +114,11 @@
                                     cg_fd, BPF_CGROUP_INET_SOCK_CREATE));
     }
 
+    if (bpf::isAtLeastKernelVersion(5, 10, 0)) {
+        RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_INET_RELEASE_PROG_PATH,
+                                    cg_fd, BPF_CGROUP_INET_SOCK_RELEASE));
+    }
+
     if (modules::sdklevel::IsAtLeastV()) {
         RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_CONNECT4_PROG_PATH,
                                     cg_fd, BPF_CGROUP_INET4_CONNECT));
@@ -134,11 +139,6 @@
             RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_SETSOCKOPT_PROG_PATH,
                                         cg_fd, BPF_CGROUP_SETSOCKOPT));
         }
-
-        if (bpf::isAtLeastKernelVersion(5, 10, 0)) {
-            RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_INET_RELEASE_PROG_PATH,
-                                        cg_fd, BPF_CGROUP_INET_SOCK_RELEASE));
-        }
     }
 
     if (bpf::isAtLeastKernelVersion(4, 19, 0)) {
@@ -156,6 +156,10 @@
         if (bpf::queryProgram(cg_fd, BPF_CGROUP_INET6_BIND) <= 0) abort();
     }
 
+    if (bpf::isAtLeastKernelVersion(5, 10, 0)) {
+        if (bpf::queryProgram(cg_fd, BPF_CGROUP_INET_SOCK_RELEASE) <= 0) abort();
+    }
+
     if (modules::sdklevel::IsAtLeastV()) {
         if (bpf::queryProgram(cg_fd, BPF_CGROUP_INET4_CONNECT) <= 0) abort();
         if (bpf::queryProgram(cg_fd, BPF_CGROUP_INET6_CONNECT) <= 0) abort();
@@ -168,10 +172,6 @@
             if (bpf::queryProgram(cg_fd, BPF_CGROUP_GETSOCKOPT) <= 0) abort();
             if (bpf::queryProgram(cg_fd, BPF_CGROUP_SETSOCKOPT) <= 0) abort();
         }
-
-        if (bpf::isAtLeastKernelVersion(5, 10, 0)) {
-            if (bpf::queryProgram(cg_fd, BPF_CGROUP_INET_SOCK_RELEASE) <= 0) abort();
-        }
     }
 
     return netdutils::status::ok;
diff --git a/bpf/progs/netd.c b/bpf/progs/netd.c
index 8804ad5..cbe856d 100644
--- a/bpf/progs/netd.c
+++ b/bpf/progs/netd.c
@@ -645,8 +645,8 @@
     return (get_app_permissions() & BPF_PERMISSION_INTERNET) ? BPF_ALLOW : BPF_DISALLOW;
 }
 
-DEFINE_NETD_V_BPF_PROG_KVER("cgroupsockrelease/inet_release", AID_ROOT, AID_ROOT,
-                            inet_socket_release, KVER_5_10)
+DEFINE_NETD_BPF_PROG_KVER("cgroupsockrelease/inet_release", AID_ROOT, AID_ROOT,
+                          inet_socket_release, KVER_5_10)
 (struct bpf_sock* sk) {
     uint64_t cookie = bpf_get_sk_cookie(sk);
     if (cookie) bpf_cookie_tag_map_delete_elem(&cookie);
diff --git a/bpf/tests/mts/bpf_existence_test.cpp b/bpf/tests/mts/bpf_existence_test.cpp
index eaa6373..0b5b7be 100644
--- a/bpf/tests/mts/bpf_existence_test.cpp
+++ b/bpf/tests/mts/bpf_existence_test.cpp
@@ -123,6 +123,11 @@
     NETD "prog_netd_bind6_inet6_bind",
 };
 
+// Provided by *current* mainline module for T+ devices with 5.10+ kernels
+static const set<string> MAINLINE_FOR_T_5_10_PLUS = {
+    NETD "prog_netd_cgroupsockrelease_inet_release",
+};
+
 // Provided by *current* mainline module for T+ devices with 5.15+ kernels
 static const set<string> MAINLINE_FOR_T_5_15_PLUS = {
     SHARED "prog_dscpPolicy_schedcls_set_dscp_ether",
@@ -154,11 +159,6 @@
     NETD "prog_netd_setsockopt_prog",
 };
 
-// Provided by *current* mainline module for V+ devices with 5.10+ kernels
-static const set<string> MAINLINE_FOR_V_5_10_PLUS = {
-    NETD "prog_netd_cgroupsockrelease_inet_release",
-};
-
 static void addAll(set<string>& a, const set<string>& b) {
     a.insert(b.begin(), b.end());
 }
@@ -196,6 +196,7 @@
     DO_EXPECT(IsAtLeastT(), MAINLINE_FOR_T_PLUS);
     DO_EXPECT(IsAtLeastT() && isAtLeastKernelVersion(4, 14, 0), MAINLINE_FOR_T_4_14_PLUS);
     DO_EXPECT(IsAtLeastT() && isAtLeastKernelVersion(4, 19, 0), MAINLINE_FOR_T_4_19_PLUS);
+    DO_EXPECT(IsAtLeastT() && isAtLeastKernelVersion(5, 10, 0), MAINLINE_FOR_T_5_10_PLUS);
     DO_EXPECT(IsAtLeastT() && isAtLeastKernelVersion(5, 15, 0), MAINLINE_FOR_T_5_15_PLUS);
 
     // U requires Linux Kernel 4.14+, but nothing (as yet) added or removed in U.
@@ -207,7 +208,6 @@
     if (IsAtLeastV()) ASSERT_TRUE(isAtLeastKernelVersion(4, 19, 0));
     DO_EXPECT(IsAtLeastV(), MAINLINE_FOR_V_PLUS);
     DO_EXPECT(IsAtLeastV() && isAtLeastKernelVersion(5, 4, 0), MAINLINE_FOR_V_5_4_PLUS);
-    DO_EXPECT(IsAtLeastV() && isAtLeastKernelVersion(5, 10, 0), MAINLINE_FOR_V_5_10_PLUS);
 
     for (const auto& file : mustExist) {
         EXPECT_EQ(0, access(file.c_str(), R_OK)) << file << " does not exist";
diff --git a/nearby/framework/java/android/nearby/NearbyManager.java b/nearby/framework/java/android/nearby/NearbyManager.java
index 39adee3..150394b 100644
--- a/nearby/framework/java/android/nearby/NearbyManager.java
+++ b/nearby/framework/java/android/nearby/NearbyManager.java
@@ -123,9 +123,12 @@
 
     private static final int POWERED_OFF_FINDING_EID_LENGTH = 20;
 
-    private static final String POWER_OFF_FINDING_SUPPORTED_PROPERTY =
+    private static final String POWER_OFF_FINDING_SUPPORTED_PROPERTY_RO =
             "ro.bluetooth.finder.supported";
 
+    private static final String POWER_OFF_FINDING_SUPPORTED_PROPERTY_PERSIST =
+            "persist.bluetooth.finder.supported";
+
     /**
      * TODO(b/286137024): Remove this when CTS R5 is rolled out.
      * Whether allows Fast Pair to scan.
@@ -618,7 +621,9 @@
     }
 
     private boolean isPoweredOffFindingSupported() {
-        return Boolean.parseBoolean(SystemProperties.get(POWER_OFF_FINDING_SUPPORTED_PROPERTY));
+        return Boolean.parseBoolean(SystemProperties.get(POWER_OFF_FINDING_SUPPORTED_PROPERTY_RO))
+                || Boolean.parseBoolean(SystemProperties.get(
+                        POWER_OFF_FINDING_SUPPORTED_PROPERTY_PERSIST));
     }
 
     private boolean areLocationAndBluetoothEnabled() {
diff --git a/staticlibs/device/com/android/net/module/util/SyncStateMachine.java b/staticlibs/device/com/android/net/module/util/SyncStateMachine.java
index da184d3..fc0161b 100644
--- a/staticlibs/device/com/android/net/module/util/SyncStateMachine.java
+++ b/staticlibs/device/com/android/net/module/util/SyncStateMachine.java
@@ -225,7 +225,8 @@
             consideredState = mStateInfo.get(consideredState.parent);
         }
         if (null == consideredState) {
-            Log.wtf(mName, "Message " + msg.what + " was not handled");
+            final String state = mCurrentState == null ? "null" : mCurrentState.getName();
+            Log.wtf(mName, "Message " + msg.what + " was not handled. Current state: " + state);
         }
 
         performTransitions();
diff --git a/staticlibs/tests/unit/host/python/apf_utils_test.py b/staticlibs/tests/unit/host/python/apf_utils_test.py
index b5a941b..96b967b 100644
--- a/staticlibs/tests/unit/host/python/apf_utils_test.py
+++ b/staticlibs/tests/unit/host/python/apf_utils_test.py
@@ -25,6 +25,8 @@
     get_apf_capabilities,
     get_apf_counter,
     get_apf_counters_from_dumpsys,
+    get_ipv4_address,
+    get_ipv6_address,
     get_hardware_address,
     is_send_raw_packet_downstream_supported,
     send_raw_packet_downstream,
@@ -112,6 +114,46 @@
       get_hardware_address(self.mock_ad, "wlan0")
 
   @patch("net_tests_utils.host.python.adb_utils.adb_shell")
+  def test_get_ipv4_address_success(
+      self, mock_adb_shell: MagicMock
+  ) -> None:
+    mock_adb_shell.return_value = """
+54: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
+inet 192.168.195.162/24 brd 192.168.195.255 scope global wlan0
+valid_lft forever preferred_lft forever
+"""
+    ip_address = get_ipv4_address(self.mock_ad, "wlan0")
+    asserts.assert_equal(ip_address, "192.168.195.162")
+
+  @patch("net_tests_utils.host.python.adb_utils.adb_shell")
+  def test_get_ipv4_address_not_found(
+      self, mock_adb_shell: MagicMock
+  ) -> None:
+     mock_adb_shell.return_value = ""
+     with asserts.assert_raises(PatternNotFoundException):
+       get_ipv4_address(self.mock_ad, "wlan0")
+
+  @patch("net_tests_utils.host.python.adb_utils.adb_shell")
+  def test_get_ipv6_address_success(
+      self, mock_adb_shell: MagicMock
+  ) -> None:
+    mock_adb_shell.return_value = """
+54: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
+inet6 fe80::10a3:5dff:fe52:de32/64 scope link
+valid_lft forever preferred_lft forever
+"""
+    ip_address = get_ipv6_address(self.mock_ad, "wlan0")
+    asserts.assert_equal(ip_address, "fe80::10a3:5dff:fe52:de32")
+
+  @patch("net_tests_utils.host.python.adb_utils.adb_shell")
+  def test_get_ipv6_address_not_found(
+          self, mock_adb_shell: MagicMock
+  ) -> None:
+      mock_adb_shell.return_value = ""
+      with asserts.assert_raises(PatternNotFoundException):
+          get_ipv6_address(self.mock_ad, "wlan0")
+
+  @patch("net_tests_utils.host.python.adb_utils.adb_shell")
   def test_send_raw_packet_downstream_success(
       self, mock_adb_shell: MagicMock
   ) -> None:
diff --git a/staticlibs/tests/unit/host/python/packet_utils_test.py b/staticlibs/tests/unit/host/python/packet_utils_test.py
new file mode 100644
index 0000000..8ad9576
--- /dev/null
+++ b/staticlibs/tests/unit/host/python/packet_utils_test.py
@@ -0,0 +1,72 @@
+#  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.
+
+from mobly import asserts
+from mobly import base_test
+from net_tests_utils.host.python import packet_utils
+
+class TestPacketUtils(base_test.BaseTestClass):
+    def test_unicast_arp_request(self):
+        # Using scapy to generate unicast arp request packet:
+        #   eth = Ether(src="00:01:02:03:04:05", dst="01:02:03:04:05:06")
+        #   arp = ARP(op=1, pdst="192.168.1.1", hwsrc="00:01:02:03:04:05", psrc="192.168.1.2")
+        #   pkt = eth/arp
+        expect_arp_request = """
+            01020304050600010203040508060001080006040001000102030405c0a80102000000000000c0a80101
+        """.upper().replace(" ", "").replace("\n", "")
+        arp_request = packet_utils.construct_arp_packet(
+            src_mac="00:01:02:03:04:05",
+            dst_mac="01:02:03:04:05:06",
+            src_ip="192.168.1.2",
+            dst_ip="192.168.1.1",
+            op=packet_utils.ARP_REQUEST_OP
+        )
+        asserts.assert_equal(expect_arp_request, arp_request)
+
+    def test_broadcast_arp_request(self):
+        # Using scapy to generate unicast arp request packet:
+        #   eth = Ether(src="00:01:02:03:04:05", dst="FF:FF:FF:FF:FF:FF")
+        #   arp = ARP(op=1, pdst="192.168.1.1", hwsrc="00:01:02:03:04:05", psrc="192.168.1.2")
+        #   pkt = eth/arp
+        expect_arp_request = """
+            ffffffffffff00010203040508060001080006040001000102030405c0a80102000000000000c0a80101
+        """.upper().replace(" ", "").replace("\n", "")
+        arp_request = packet_utils.construct_arp_packet(
+            src_mac="00:01:02:03:04:05",
+            dst_mac=packet_utils.ETHER_BROADCAST_MAC_ADDRESS,
+            src_ip="192.168.1.2",
+            dst_ip="192.168.1.1",
+            op=packet_utils.ARP_REQUEST_OP
+        )
+        asserts.assert_equal(expect_arp_request, arp_request)
+
+    def test_arp_reply(self):
+        # Using scapy to generate unicast arp request packet:
+        #   eth = Ether(src="01:02:03:04:05:06", dst="00:01:02:03:04:05")
+        #   arp = ARP(op=2, pdst="192.168.1.2", \
+        #             hwsrc="01:02:03:04:05:06", \
+        #             psrc="192.168.1.1", \
+        #             hwdst="00:01:02:03:04:05")
+        #   pkt = eth/arp
+        expect_arp_reply = """
+            00010203040501020304050608060001080006040002010203040506c0a80101000102030405c0a80102
+        """.upper().replace(" ", "").replace("\n", "")
+        arp_reply = packet_utils.construct_arp_packet(
+            src_mac="01:02:03:04:05:06",
+            dst_mac="00:01:02:03:04:05",
+            src_ip="192.168.1.1",
+            dst_ip="192.168.1.2",
+            op=packet_utils.ARP_REPLY_OP
+        )
+        asserts.assert_equal(expect_arp_reply, arp_reply)
diff --git a/staticlibs/tests/unit/host/python/run_tests.py b/staticlibs/tests/unit/host/python/run_tests.py
index fa6a310..498dbaf 100644
--- a/staticlibs/tests/unit/host/python/run_tests.py
+++ b/staticlibs/tests/unit/host/python/run_tests.py
@@ -18,6 +18,7 @@
 from host.python.adb_utils_test import TestAdbUtils
 from host.python.apf_utils_test import TestApfUtils
 from host.python.assert_utils_test import TestAssertUtils
+from host.python.packet_utils_test import TestPacketUtils
 from mobly import suite_runner
 
 
@@ -31,5 +32,5 @@
   sys.argv.pop(1)
   # TODO: make the tests can be executed without manually list classes.
   suite_runner.run_suite(
-      [TestAssertUtils, TestAdbUtils, TestApfUtils], sys.argv
+      [TestAssertUtils, TestAdbUtils, TestApfUtils, TestPacketUtils], sys.argv
   )
diff --git a/staticlibs/testutils/host/python/apf_utils.py b/staticlibs/testutils/host/python/apf_utils.py
index c3330d2..b312bcf 100644
--- a/staticlibs/testutils/host/python/apf_utils.py
+++ b/staticlibs/testutils/host/python/apf_utils.py
@@ -83,6 +83,80 @@
   ad.log.debug("Getting apf counters: " + str(result))
   return result
 
+def get_ipv4_address(
+    ad: android_device.AndroidDevice, iface_name: str
+) -> str:
+  """Retrieves the IPv4 address of a given interface on an Android device.
+
+  This function executes an ADB shell command (`ip -4 address show`) to get the
+  network interface information and extracts the IPv4 address from the output.
+  If devices has multiple IPv4 addresses, return the first one.
+  If devices have no IPv4 address, raise PatternNotFoundException.
+
+  Args:
+      ad: The Android device object.
+      iface_name: The name of the network interface (e.g., "wlan0").
+
+  Returns:
+      The IPv4 address of the interface as a string.
+
+  Raises:
+      PatternNotFoundException: If the IPv4 address is not found in the command
+      output.
+  """
+  # output format:
+  # 54: wlan2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
+  # inet 192.168.195.162/24 brd 192.168.195.255 scope global wlan2
+  # valid_lft forever preferred_lft forever
+  output = adb_utils.adb_shell(ad, f"ip -4 address show {iface_name}")
+  pattern = r"inet\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\/\d+"
+  match = re.search(pattern, output)
+
+  if match:
+    return match.group(1)  # Extract the IPv4 address string.
+  else:
+    raise PatternNotFoundException(
+      "Cannot get hardware address for " + iface_name
+    )
+
+def get_ipv6_address(
+    ad: android_device.AndroidDevice, iface_name: str
+) -> str:
+  """Retrieves the IPv6 address of a given interface on an Android device.
+
+  This function executes an ADB shell command (`ip -6 address show`) to get the
+  network interface information and extracts the IPv6 address from the output.
+  If devices has multiple IPv6 addresses, return the first one.
+  If devices have no IPv6 address, raise PatternNotFoundException.
+
+  Args:
+      ad: The Android device object.
+      iface_name: The name of the network interface (e.g., "wlan0").
+
+  Returns:
+      The IPv6 address of the interface as a string.
+
+  Raises:
+      PatternNotFoundException: If the IPv6 address is not found in the command
+      output.
+  """
+  # output format
+  # 54: wlan2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
+  # inet6 fe80::10a3:5dff:fe52:de32/64 scope link
+  # valid_lft forever preferred_lft forever
+  output = adb_utils.adb_shell(ad, f"ip -6 address show {iface_name}")
+  if output is "":
+    raise PatternNotFoundException(
+      "Cannot get ipv6 address for " + iface_name
+    )
+  pattern = r"inet6\s+([0-9a-fA-F:]+)\/\d+"
+  match = re.search(pattern, output)
+  if match:
+    return match.group(1)  # Extract the IPv6 address string.
+  else:
+    raise PatternNotFoundException(
+      "Cannot get IPv6 address for " + iface_name
+    )
 
 def get_hardware_address(
     ad: android_device.AndroidDevice, iface_name: str
diff --git a/staticlibs/testutils/host/python/packet_utils.py b/staticlibs/testutils/host/python/packet_utils.py
new file mode 100644
index 0000000..b613f03
--- /dev/null
+++ b/staticlibs/testutils/host/python/packet_utils.py
@@ -0,0 +1,70 @@
+#  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.
+from ipaddress import IPv4Address
+from socket import inet_aton
+
+ETHER_BROADCAST_MAC_ADDRESS = "FF:FF:FF:FF:FF:FF"
+ARP_REQUEST_OP = 1
+ARP_REPLY_OP = 2
+
+"""
+This variable defines a template for constructing ARP packets in hexadecimal format.
+It's used to provide the common fields for ARP packet, and replaced needed fields when constructing
+"""
+ARP_TEMPLATE = (
+    # Ether Header (14 bytes)
+    "{dst_mac}" + # DA
+    "{src_mac}" + # SA
+    "0806" + # ARP
+    # ARP Header (28 bytes)
+    "0001" + # Hardware type (Ethernet)
+    "0800" + # Protocol type (IPv4)
+    "06" + # hardware address length
+    "04" + # protocol address length
+    "{opcode}" + # opcode
+    "{sender_mac}" + # sender MAC
+    "{sender_ip}" + # sender IP
+    "{target_mac}" + # target MAC
+    "{target_ip}" # target IP
+)
+
+def construct_arp_packet(src_mac, dst_mac, src_ip, dst_ip, op) -> str:
+    """Constructs an ARP packet as a hexadecimal string.
+
+    This function creates an ARP packet by filling in the required fields
+    in a predefined ARP packet template.
+
+    Args:
+    src_mac: The MAC address of the sender. (e.g. "11:22:33:44:55:66")
+    dst_mac: The MAC address of the recipient. (e.g. "aa:bb:cc:dd:ee:ff")
+    src_ip: The IP address of the sender. (e.g. "1.1.1.1")
+    dst_ip: The IP address of the target machine. (e.g. "2.2.2.2")
+    op: The op code of the ARP packet, refer to ARP_*_OP
+
+    Returns:
+    A string representing the ARP packet in hexadecimal format.
+    """
+    # Replace the needed fields from packet template
+    arp_pkt = ARP_TEMPLATE.format(
+            dst_mac=dst_mac.replace(":",""),
+            src_mac=src_mac.replace(":",""),
+            opcode=str(op).rjust(4, "0"),
+            sender_mac=src_mac.replace(":",""),
+            sender_ip=inet_aton(src_ip).hex(),
+            target_mac=("000000000000" if op == ARP_REQUEST_OP else dst_mac.replace(":", "")),
+            target_ip=inet_aton(dst_ip).hex()
+    )
+
+    # always convert to upper case hex string
+    return arp_pkt.upper()
\ No newline at end of file
diff --git a/tests/cts/hostside/Android.bp b/tests/cts/hostside/Android.bp
index 14d5d54..97be91a 100644
--- a/tests/cts/hostside/Android.bp
+++ b/tests/cts/hostside/Android.bp
@@ -12,17 +12,27 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-next_app_data = [":CtsHostsideNetworkTestsAppNext"]
-
-// The above line is put in place to prevent any future automerger merge conflict between aosp,
-// downstream branches. The CtsHostsideNetworkTestsAppNext target will not exist in
-// some downstream branches, but it should exist in aosp and some downstream branches.
-
 package {
     default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
+java_defaults {
+    name: "CtsHostsideNetworkTestsAllAppDefaults",
+    platform_apis: true,
+    min_sdk_version: "30",
+    // Set target SDK to 10000 so that all the test helper apps are always subject to the most
+    // recent (and possibly most restrictive) target SDK behaviour. Also, this matches the target
+    // SDK of the tests themselves, and of other tests such as CtsNetTestCases.
+    // Note that some of the test helper apps (e.g., CtsHostsideNetworkCapTestsAppSdk33) override
+    // this with older SDK versions.
+    // Also note that unlike android_test targets, "current" does not work: the target SDK is set to
+    // something like "VanillaIceCream" instead of 100000. This means that the tests will not run on
+    // released devices with errors such as "Requires development platform VanillaIceCream but this
+    // is a release platform".
+    target_sdk_version: "10000",
+}
+
 java_test_host {
     name: "CtsHostsideNetworkTests",
     defaults: ["cts_defaults"],
@@ -52,6 +62,6 @@
         ":CtsHostsideNetworkCapTestsAppWithoutProperty",
         ":CtsHostsideNetworkCapTestsAppWithProperty",
         ":CtsHostsideNetworkCapTestsAppSdk33",
-    ] + next_app_data,
+    ],
     per_testcase_directory: true,
 }
diff --git a/tests/cts/hostside/app/Android.bp b/tests/cts/hostside/app/Android.bp
index 2ca9adb..7fff1c2 100644
--- a/tests/cts/hostside/app/Android.bp
+++ b/tests/cts/hostside/app/Android.bp
@@ -19,9 +19,13 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-java_defaults {
-    name: "CtsHostsideNetworkTestsAppDefaults",
-    platform_apis: true,
+android_test_helper_app {
+    name: "CtsHostsideNetworkTestsApp",
+    defaults: [
+        "cts_support_defaults",
+        "framework-connectivity-test-defaults",
+        "CtsHostsideNetworkTestsAllAppDefaults",
+    ],
     static_libs: [
         "CtsHostsideNetworkTestsAidl",
         "androidx.test.ext.junit",
@@ -39,35 +43,4 @@
     srcs: [
         "src/**/*.java",
     ],
-    // Tag this module as a cts test artifact
-    test_suites: [
-        "general-tests",
-        "sts",
-    ],
-    min_sdk_version: "30",
-}
-
-android_test_helper_app {
-    name: "CtsHostsideNetworkTestsApp",
-    defaults: [
-        "cts_support_defaults",
-        "framework-connectivity-test-defaults",
-        "CtsHostsideNetworkTestsAppDefaults",
-    ],
-    static_libs: [
-        "NetworkStackApiStableShims",
-    ],
-}
-
-android_test_helper_app {
-    name: "CtsHostsideNetworkTestsAppNext",
-    defaults: [
-        "cts_support_defaults",
-        "framework-connectivity-test-defaults",
-        "CtsHostsideNetworkTestsAppDefaults",
-        "ConnectivityNextEnableDefaults",
-    ],
-    static_libs: [
-        "NetworkStackApiCurrentShims",
-    ],
 }
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyVpnService.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyVpnService.java
index fe522a0..a39a8d0 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyVpnService.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyVpnService.java
@@ -29,9 +29,6 @@
 import android.util.Pair;
 
 import com.android.modules.utils.build.SdkLevel;
-import com.android.networkstack.apishim.VpnServiceBuilderShimImpl;
-import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
-import com.android.networkstack.apishim.common.VpnServiceBuilderShim;
 import com.android.testutils.PacketReflector;
 
 import java.io.IOException;
@@ -102,8 +99,7 @@
     }
 
     private void start(String packageName, Intent intent) {
-        Builder builder = new Builder();
-        VpnServiceBuilderShim vpnServiceBuilderShim = VpnServiceBuilderShimImpl.newInstance();
+        VpnService.Builder builder = new VpnService.Builder();
 
         final String addresses = parseIpAndMaskListArgument(packageName, intent, "addresses",
                 builder::addAddress);
@@ -112,11 +108,7 @@
         if (SdkLevel.isAtLeastT() && intent.getBooleanExtra(packageName + ".addRoutesByIpPrefix",
                 false)) {
             addedRoutes = parseIpPrefixListArgument(packageName, intent, "routes", (prefix) -> {
-                try {
-                    vpnServiceBuilderShim.addRoute(builder, prefix);
-                } catch (UnsupportedApiLevelException e) {
-                    throw new RuntimeException(e);
-                }
+                builder.addRoute(prefix);
             });
         } else {
             addedRoutes = parseIpAndMaskListArgument(packageName, intent, "routes",
@@ -127,11 +119,7 @@
         if (SdkLevel.isAtLeastT()) {
             excludedRoutes = parseIpPrefixListArgument(packageName, intent, "excludedRoutes",
                     (prefix) -> {
-                        try {
-                            vpnServiceBuilderShim.excludeRoute(builder, prefix);
-                        } catch (UnsupportedApiLevelException e) {
-                            throw new RuntimeException(e);
-                        }
+                        builder.excludeRoute(prefix);
                     });
         }
 
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
index d7631eb..d05a8d0 100755
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
@@ -20,8 +20,11 @@
 import static android.Manifest.permission.NETWORK_SETTINGS;
 import static android.Manifest.permission.READ_DEVICE_CONFIG;
 import static android.Manifest.permission.WRITE_DEVICE_CONFIG;
+import static android.content.Context.RECEIVER_EXPORTED;
 import static android.content.pm.PackageManager.FEATURE_TELEPHONY;
 import static android.content.pm.PackageManager.FEATURE_WIFI;
+import static android.net.ConnectivityManager.BLOCKED_REASON_LOCKDOWN_VPN;
+import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
 import static android.net.ConnectivityManager.TYPE_VPN;
 import static android.net.NetworkCapabilities.TRANSPORT_TEST;
@@ -46,9 +49,6 @@
 import static com.android.cts.net.hostside.VpnTest.TestSocketKeepaliveCallback.CallbackType.ON_RESUMED;
 import static com.android.cts.net.hostside.VpnTest.TestSocketKeepaliveCallback.CallbackType.ON_STARTED;
 import static com.android.cts.net.hostside.VpnTest.TestSocketKeepaliveCallback.CallbackType.ON_STOPPED;
-import static com.android.networkstack.apishim.ConstantsShim.BLOCKED_REASON_LOCKDOWN_VPN;
-import static com.android.networkstack.apishim.ConstantsShim.BLOCKED_REASON_NONE;
-import static com.android.networkstack.apishim.ConstantsShim.RECEIVER_EXPORTED;
 import static com.android.testutils.Cleanup.testAndCleanup;
 import static com.android.testutils.RecorderCallback.CallbackEntry.BLOCKED_STATUS_INT;
 import static com.android.testutils.TestPermissionUtil.runAsShell;
diff --git a/tests/cts/hostside/app2/Android.bp b/tests/cts/hostside/app2/Android.bp
index cb55c7b..05abcdd 100644
--- a/tests/cts/hostside/app2/Android.bp
+++ b/tests/cts/hostside/app2/Android.bp
@@ -21,20 +21,14 @@
 
 android_test_helper_app {
     name: "CtsHostsideNetworkTestsApp2",
-    defaults: ["cts_support_defaults"],
-    platform_apis: true,
+    defaults: [
+        "cts_support_defaults",
+        "CtsHostsideNetworkTestsAllAppDefaults",
+    ],
     static_libs: [
         "androidx.annotation_annotation",
         "CtsHostsideNetworkTestsAidl",
-        "NetworkStackApiStableShims",
     ],
     srcs: ["src/**/*.java"],
-    // Tag this module as a cts test artifact
-    test_suites: [
-        "cts",
-        "general-tests",
-        "sts",
-    ],
     sdk_version: "test_current",
-    min_sdk_version: "30",
 }
diff --git a/tests/cts/hostside/networkslicingtestapp/Android.bp b/tests/cts/hostside/networkslicingtestapp/Android.bp
index 79ad2e2..0eed51c 100644
--- a/tests/cts/hostside/networkslicingtestapp/Android.bp
+++ b/tests/cts/hostside/networkslicingtestapp/Android.bp
@@ -21,7 +21,6 @@
 
 java_defaults {
     name: "CtsHostsideNetworkCapTestsAppDefaults",
-    platform_apis: true,
     static_libs: [
         "androidx.test.ext.junit",
         "androidx.test.rules",
@@ -29,13 +28,6 @@
         "cts-net-utils",
     ],
     srcs: ["src/**/*.java"],
-    // Tag this module as a cts test artifact
-    test_suites: [
-        "cts",
-        "general-tests",
-        "sts",
-    ],
-    min_sdk_version: "30",
 }
 
 android_test_helper_app {
@@ -43,6 +35,7 @@
     defaults: [
         "cts_support_defaults",
         "CtsHostsideNetworkCapTestsAppDefaults",
+        "CtsHostsideNetworkTestsAllAppDefaults",
     ],
     manifest: "AndroidManifestWithoutProperty.xml",
     sdk_version: "test_current",
@@ -53,6 +46,7 @@
     defaults: [
         "cts_support_defaults",
         "CtsHostsideNetworkCapTestsAppDefaults",
+        "CtsHostsideNetworkTestsAllAppDefaults",
     ],
     manifest: "AndroidManifestWithProperty.xml",
     sdk_version: "test_current",
@@ -63,6 +57,7 @@
     defaults: [
         "cts_support_defaults",
         "CtsHostsideNetworkCapTestsAppDefaults",
+        "CtsHostsideNetworkTestsAllAppDefaults",
     ],
     target_sdk_version: "33",
     manifest: "AndroidManifestWithoutProperty.xml",
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
index 69d61b3..e222ff6 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
@@ -19,7 +19,6 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 
-import com.android.modules.utils.build.testing.DeviceSdkLevel;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.invoker.TestInformation;
 import com.android.tradefed.targetprep.BuildError;
@@ -36,17 +35,13 @@
 abstract class HostsideNetworkTestCase extends BaseHostJUnit4Test {
     protected static final String TEST_PKG = "com.android.cts.net.hostside";
     protected static final String TEST_APK = "CtsHostsideNetworkTestsApp.apk";
-    protected static final String TEST_APK_NEXT = "CtsHostsideNetworkTestsAppNext.apk";
     protected static final String TEST_APP2_PKG = "com.android.cts.net.hostside.app2";
     protected static final String TEST_APP2_APK = "CtsHostsideNetworkTestsApp2.apk";
 
     @BeforeClassWithInfo
     public static void setUpOnceBase(TestInformation testInfo) throws Exception {
-        DeviceSdkLevel deviceSdkLevel = new DeviceSdkLevel(testInfo.getDevice());
-        String testApk = deviceSdkLevel.isDeviceAtLeastV() ? TEST_APK_NEXT : TEST_APK;
-
         uninstallPackage(testInfo, TEST_PKG, false);
-        installPackage(testInfo, testApk);
+        installPackage(testInfo, TEST_APK);
     }
 
     @AfterClassWithInfo
diff --git a/tests/cts/hostside/src/com/android/cts/net/ProcNetTest.java b/tests/cts/hostside/src/com/android/cts/net/ProcNetTest.java
index fa68e3e..ae572e6 100644
--- a/tests/cts/hostside/src/com/android/cts/net/ProcNetTest.java
+++ b/tests/cts/hostside/src/com/android/cts/net/ProcNetTest.java
@@ -166,6 +166,8 @@
      */
     @Test
     public void testRouterSolicitations() throws Exception {
+        assumeTrue(new DeviceSdkLevel(mDevice).isDeviceAtLeastU());
+
         for (String interfaceDir : mSysctlDirs) {
             String path = IPV6_SYSCTL_DIR + "/" + interfaceDir + "/" + "router_solicitations";
             int value = readIntFromPath(path);
@@ -186,8 +188,7 @@
      */
     @Test
     public void testCongestionControl() throws Exception {
-        final DeviceSdkLevel dsl = new DeviceSdkLevel(mDevice);
-        assumeTrue(dsl.isDeviceAtLeastV());
+        assumeTrue(new DeviceSdkLevel(mDevice).isDeviceAtLeastV());
 
         String path = "/proc/sys/net/ipv4/tcp_congestion_control";
         String value = mDevice.executeAdbCommand("shell", "cat", path).trim();
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
index f7da644..57fea34 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
@@ -1264,6 +1264,7 @@
         LOG.i("Infra link state changed: " + mInfraLinkState + " -> " + newInfraLinkState);
 
         setInfraLinkInterfaceName(newInfraLinkState.interfaceName);
+        setInfraLinkNat64Prefix(newInfraLinkState.nat64Prefix);
         mInfraLinkState = newInfraLinkState;
     }
 
@@ -1290,6 +1291,19 @@
         }
     }
 
+    private void setInfraLinkNat64Prefix(@Nullable String newNat64Prefix) {
+        if (Objects.equals(mInfraLinkState.nat64Prefix, newNat64Prefix)) {
+            return;
+        }
+        try {
+            getOtDaemon()
+                    .setInfraLinkNat64Prefix(
+                            newNat64Prefix, new LoggingOtStatusReceiver("setInfraLinkNat64Prefix"));
+        } catch (RemoteException | ThreadNetworkException e) {
+            LOG.e("Failed to set infra link NAT64 prefix " + newNat64Prefix, e);
+        }
+    }
+
     private void handleThreadInterfaceStateChanged(boolean isUp) {
         try {
             mTunIfController.setInterfaceUp(isUp);
@@ -1427,7 +1441,13 @@
         if (linkProperties == null) {
             return newInfraLinkStateBuilder();
         }
-        return new InfraLinkState.Builder().setInterfaceName(linkProperties.getInterfaceName());
+        String nat64Prefix = null;
+        if (linkProperties.getNat64Prefix() != null) {
+            nat64Prefix = linkProperties.getNat64Prefix().toString();
+        }
+        return new InfraLinkState.Builder()
+                .setInterfaceName(linkProperties.getInterfaceName())
+                .setNat64Prefix(nat64Prefix);
     }
 
     private static final class CallbackMetadata {
diff --git a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
index 103282a..4a8462d8 100644
--- a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
+++ b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
@@ -46,8 +46,11 @@
 import static java.util.Objects.requireNonNull;
 
 import android.content.Context;
+import android.net.InetAddresses;
 import android.net.IpPrefix;
 import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.RouteInfo;
 import android.net.thread.utils.FullThreadDevice;
 import android.net.thread.utils.InfraNetworkDevice;
 import android.net.thread.utils.IntegrationTestUtils;
@@ -99,6 +102,11 @@
     private static final Inet4Address IPV4_SERVER_ADDR =
             (Inet4Address) parseNumericAddress("8.8.8.8");
     private static final String NAT64_CIDR = "192.168.255.0/24";
+    private static final IpPrefix DHCP6_PD_PREFIX = new IpPrefix("2001:db8::/64");
+    private static final IpPrefix AIL_NAT64_PREFIX = new IpPrefix("2001:db8:1234::/96");
+    private static final Inet6Address AIL_NAT64_SYNTHESIZED_SERVER_ADDR =
+            (Inet6Address) parseNumericAddress("2001:db8:1234::8.8.8.8");
+    private static final Duration UPDATE_NAT64_PREFIX_TIMEOUT = Duration.ofSeconds(10);
 
     @Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule();
 
@@ -623,13 +631,50 @@
         // TODO: enable NAT64 via ThreadNetworkController API instead of ot-ctl
         mOtCtl.setNat64Cidr(NAT64_CIDR);
         mOtCtl.setNat64Enabled(true);
-        waitFor(() -> mOtCtl.hasNat64PrefixInNetdata(), Duration.ofSeconds(10));
+        waitFor(() -> mOtCtl.hasNat64PrefixInNetdata(), UPDATE_NAT64_PREFIX_TIMEOUT);
 
         ftd.ping(IPV4_SERVER_ADDR);
 
         assertNotNull(pollForIcmpPacketOnInfraNetwork(ICMP_ECHO, null, IPV4_SERVER_ADDR));
     }
 
+    @Test
+    public void nat64_withAilNat64Prefix_threadDevicePingIpv4InfraDevice_outboundPacketIsForwarded()
+            throws Exception {
+        tearDownInfraNetwork();
+        LinkProperties lp = new LinkProperties();
+        // NAT64 feature requires the infra network to have an IPv4 default route.
+        lp.addRoute(
+                new RouteInfo(
+                        new IpPrefix("0.0.0.0/0") /* destination */,
+                        null /* gateway */,
+                        null /* iface */,
+                        RouteInfo.RTN_UNICAST,
+                        1500 /* mtu */));
+        lp.addRoute(
+                new RouteInfo(
+                        new IpPrefix("::/0") /* destination */,
+                        null /* gateway */,
+                        null /* iface */,
+                        RouteInfo.RTN_UNICAST,
+                        1500 /* mtu */));
+        lp.setNat64Prefix(AIL_NAT64_PREFIX);
+        mInfraNetworkTracker = IntegrationTestUtils.setUpInfraNetwork(mContext, mController, lp);
+        mInfraNetworkReader = newPacketReader(mInfraNetworkTracker.getTestIface(), mHandler);
+        FullThreadDevice ftd = mFtds.get(0);
+        joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
+        // TODO: enable NAT64 via ThreadNetworkController API instead of ot-ctl
+        mOtCtl.setNat64Enabled(true);
+        mOtCtl.addPrefixInNetworkData(DHCP6_PD_PREFIX, "paros", "med");
+        waitFor(() -> mOtCtl.hasNat64PrefixInNetdata(), UPDATE_NAT64_PREFIX_TIMEOUT);
+
+        ftd.ping(IPV4_SERVER_ADDR);
+
+        assertNotNull(
+                pollForIcmpPacketOnInfraNetwork(
+                        ICMPV6_ECHO_REQUEST_TYPE, null, AIL_NAT64_SYNTHESIZED_SERVER_ADDR));
+    }
+
     private void setUpInfraNetwork() throws Exception {
         mInfraNetworkTracker = IntegrationTestUtils.setUpInfraNetwork(mContext, mController);
     }
diff --git a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.kt b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.kt
index fa9855e..3df74b0 100644
--- a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.kt
+++ b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.kt
@@ -551,6 +551,22 @@
         )
     }
 
+    private fun defaultLinkProperties(): LinkProperties {
+        val lp = LinkProperties()
+        // TODO: use a fake DNS server
+        lp.setDnsServers(listOf(parseNumericAddress("8.8.8.8")))
+        // NAT64 feature requires the infra network to have an IPv4 default route.
+        lp.addRoute(
+            RouteInfo(
+                IpPrefix("0.0.0.0/0") /* destination */,
+                null /* gateway */,
+                null /* iface */,
+                RouteInfo.RTN_UNICAST, 1500 /* mtu */
+            )
+        )
+        return lp
+    }
+
     @JvmStatic
     @JvmOverloads
     fun startInfraDeviceAndWaitForOnLinkAddr(
@@ -564,23 +580,13 @@
     }
 
     @JvmStatic
+    @JvmOverloads
     @Throws(java.lang.Exception::class)
     fun setUpInfraNetwork(
-        context: Context, controller: ThreadNetworkControllerWrapper
+        context: Context,
+        controller: ThreadNetworkControllerWrapper,
+        lp: LinkProperties = defaultLinkProperties()
     ): TestNetworkTracker {
-        val lp = LinkProperties()
-
-        // TODO: use a fake DNS server
-        lp.setDnsServers(listOf(parseNumericAddress("8.8.8.8")))
-        // NAT64 feature requires the infra network to have an IPv4 default route.
-        lp.addRoute(
-            RouteInfo(
-                IpPrefix("0.0.0.0/0") /* destination */,
-                null /* gateway */,
-                null /* iface */,
-                RouteInfo.RTN_UNICAST, 1500 /* mtu */
-            )
-        )
         val infraNetworkTracker: TestNetworkTracker =
             runAsShell(
                 MANAGE_TEST_NETWORKS,
diff --git a/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java b/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java
index 15a3f5c..046d9bf 100644
--- a/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java
+++ b/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java
@@ -128,6 +128,12 @@
         return false;
     }
 
+    /** Adds a prefix in the Network Data. */
+    public void addPrefixInNetworkData(IpPrefix ipPrefix, String flags, String preference) {
+        executeCommand("prefix add " + ipPrefix + " " + flags + " " + preference);
+        executeCommand("netdata register");
+    }
+
     public String executeCommand(String cmd) {
         return SystemUtil.runShellCommand(OT_CTL + " " + cmd);
     }