Merge "Register VCN service in ConnectivityServiceInitializer" into main
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index b50831d..e37c5db 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -708,8 +708,7 @@
             // If tethering is already enabled with a different request,
             // disable before re-enabling.
             if (unfinishedRequest != null && !unfinishedRequest.equalsIgnoreUidPackage(request)) {
-                enableTetheringInternal(false /* disabled */, unfinishedRequest, null);
-                mEntitlementMgr.stopProvisioningIfNeeded(type);
+                stopTetheringInternal(type);
             }
             mPendingTetheringRequests.put(type, request);
 
@@ -1152,7 +1151,10 @@
             } catch (RemoteException e) { }
         }
 
-        final TetheringRequest request = createLegacyGlobalScopeTetheringRequest(type);
+        TetheringRequest request = getPendingTetheringRequest(type);
+        if (request == null) {
+            request = createLegacyGlobalScopeTetheringRequest(type);
+        }
         int result = tetherInternal(request, iface);
         switch (type) {
             case TETHERING_WIFI:
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
index b3e9c1b..3c91a1b 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
@@ -130,9 +130,6 @@
     public static final String TETHER_ENABLE_WEAR_TETHERING =
             "tether_enable_wear_tethering";
 
-    public static final String TETHER_FORCE_RANDOM_PREFIX_BASE_SELECTION =
-            "tether_force_random_prefix_base_selection";
-
     public static final String TETHER_ENABLE_SYNC_SM = "tether_enable_sync_sm";
 
     /**
@@ -142,7 +139,7 @@
     public static final int DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS = 5000;
 
     /** A flag for using synchronous or asynchronous state machine. */
-    public static boolean USE_SYNC_SM = false;
+    public static boolean USE_SYNC_SM = true;
 
     /**
      * A feature flag to control whether the active sessions metrics should be enabled.
@@ -195,6 +192,10 @@
             return DeviceConfigUtils.isTetheringFeatureEnabled(context, name);
         }
 
+        boolean isFeatureNotChickenedOut(@NonNull Context context, @NonNull String name) {
+            return DeviceConfigUtils.isTetheringFeatureNotChickenedOut(context, name);
+        }
+
         boolean getDeviceConfigBoolean(@NonNull String namespace, @NonNull String name,
                 boolean defaultValue) {
             return DeviceConfig.getBoolean(namespace, name, defaultValue);
@@ -394,7 +395,7 @@
      * use the async state machine.
      */
     public void readEnableSyncSM(final Context ctx) {
-        USE_SYNC_SM = mDeps.isFeatureEnabled(ctx, TETHER_ENABLE_SYNC_SM);
+        USE_SYNC_SM = mDeps.isFeatureNotChickenedOut(ctx, TETHER_ENABLE_SYNC_SM);
     }
 
     /** Does the dumping.*/
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/FakeTetheringConfiguration.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/FakeTetheringConfiguration.java
index 087be26..c97fa3d 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/FakeTetheringConfiguration.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/FakeTetheringConfiguration.java
@@ -33,6 +33,11 @@
             }
 
             @Override
+            boolean isFeatureNotChickenedOut(@NonNull Context context, @NonNull String name) {
+                return true;
+            }
+
+            @Override
             boolean getDeviceConfigBoolean(@NonNull String namespace, @NonNull String name,
                     boolean defaultValue) {
                 return defaultValue;
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 f9e3a6a..ada88fb 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
@@ -26,7 +26,6 @@
 import static android.net.TetheringManager.TETHERING_WIFI_P2P;
 import static android.net.ip.IpServer.CMD_NOTIFY_PREFIX_CONFLICT;
 
-import static com.android.net.module.util.PrivateAddressCoordinator.TETHER_FORCE_RANDOM_PREFIX_BASE_SELECTION;
 import static com.android.networkstack.tethering.util.PrefixUtils.asIpPrefix;
 
 import static org.junit.Assert.assertEquals;
@@ -51,6 +50,7 @@
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.ip.IpServer;
+import android.os.Build;
 import android.os.IBinder;
 
 import androidx.test.filters.SmallTest;
@@ -58,8 +58,10 @@
 
 import com.android.net.module.util.IIpv4PrefixRequest;
 import com.android.net.module.util.PrivateAddressCoordinator;
+import com.android.testutils.DevSdkIgnoreRule;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -71,6 +73,9 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public final class PrivateAddressCoordinatorTest {
+    @Rule
+    public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
+
     private static final String TEST_IFNAME = "test0";
 
     @Mock private IpServer mHotspotIpServer;
@@ -231,11 +236,9 @@
         assertEquals(usbAddress, newUsbAddress);
 
         final UpstreamNetworkState wifiUpstream = buildUpstreamNetworkState(mWifiNetwork,
-                new LinkAddress("192.168.88.23/16"), null,
-                makeNetworkCapabilities(TRANSPORT_WIFI));
+                hotspotAddress, null, makeNetworkCapabilities(TRANSPORT_WIFI));
         updateUpstreamPrefix(wifiUpstream);
         verify(mHotspotIpServer).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
-        verify(mUsbIpServer).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
     }
 
     private UpstreamNetworkState buildUpstreamNetworkState(final Network network,
@@ -323,10 +326,9 @@
         assertFalse(localHotspotPrefix.containsPrefix(hotspotPrefix));
     }
 
+    @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
     @Test
     public void testStartedPrefixRange() throws Exception {
-        when(mDeps.isFeatureEnabled(TETHER_FORCE_RANDOM_PREFIX_BASE_SELECTION)).thenReturn(true);
-
         startedPrefixBaseTest("192.168.0.0/16", 0);
 
         startedPrefixBaseTest("192.168.0.0/16", 1);
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
index dd51c7a..0159573 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
@@ -160,6 +160,11 @@
         }
 
         @Override
+        boolean isFeatureNotChickenedOut(@NonNull Context context, @NonNull String name) {
+            return isMockFlagEnabled(name, true /* defaultEnabled */);
+        }
+
+        @Override
         boolean getDeviceConfigBoolean(@NonNull String namespace, @NonNull String name,
                 boolean defaultValue) {
             // Flags should use isFeatureEnabled instead of getBoolean; see comments in
@@ -767,9 +772,9 @@
 
     @Test
     public void testEnableSyncSMFlag() throws Exception {
-        // Test default disabled
+        // Test default enabled
         setTetherEnableSyncSMFlagEnabled(null);
-        assertEnableSyncSM(false);
+        assertEnableSyncSM(true);
 
         setTetherEnableSyncSMFlagEnabled(true);
         assertEnableSyncSM(true);
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index e1c2db9..50ecfe1 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -2879,6 +2879,44 @@
     }
 
     @Test
+    @IgnoreAfter(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    public void testRequestStaticIpLegacyTether() throws Exception {
+        initTetheringOnTestThread();
+
+        // Call startTethering with static ip
+        final LinkAddress serverLinkAddr = new LinkAddress("192.168.0.123/24");
+        final LinkAddress clientLinkAddr = new LinkAddress("192.168.0.42/24");
+        final String serverAddr = "192.168.0.123";
+        final int clientAddrParceled = 0xc0a8002a;
+        final ArgumentCaptor<DhcpServingParamsParcel> dhcpParamsCaptor =
+                ArgumentCaptor.forClass(DhcpServingParamsParcel.class);
+        when(mWifiManager.startTetheredHotspot(any())).thenReturn(true);
+        mTethering.startTethering(createTetheringRequest(TETHERING_WIFI,
+                        serverLinkAddr, clientLinkAddr, false, CONNECTIVITY_SCOPE_GLOBAL, null),
+                TEST_CALLER_PKG, null);
+        mLooper.dispatchAll();
+        verify(mWifiManager, times(1)).startTetheredHotspot(any());
+        mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
+
+        // Call legacyTether on the interface before the link layer event comes back.
+        // This happens, for example, in pre-T bluetooth tethering: Settings calls startTethering,
+        // and then the bluetooth code calls the tether() API.
+        final ResultListener tetherResult = new ResultListener(TETHER_ERROR_NO_ERROR);
+        mTethering.legacyTether(TEST_WLAN_IFNAME, tetherResult);
+        mLooper.dispatchAll();
+        tetherResult.assertHasResult();
+
+        // Verify that the static ip set in startTethering is used
+        verify(mNetd).interfaceSetCfg(argThat(cfg -> serverAddr.equals(cfg.ipv4Addr)));
+        verify(mIpServerDependencies, times(1)).makeDhcpServer(any(), dhcpParamsCaptor.capture(),
+                any());
+        final DhcpServingParamsParcel params = dhcpParamsCaptor.getValue();
+        assertEquals(serverAddr, intToInet4AddressHTH(params.serverAddr).getHostAddress());
+        assertEquals(24, params.serverAddrPrefixLength);
+        assertEquals(clientAddrParceled, params.singleClientAddr);
+    }
+
+    @Test
     public void testUpstreamNetworkChanged() throws Exception {
         initTetheringOnTestThread();
         final InOrder inOrder = inOrder(mNotificationUpdater);
diff --git a/bpf/headers/include/bpf/BpfUtils.h b/bpf/headers/include/bpf/BpfUtils.h
index 9e8b2c7..ed08e1a 100644
--- a/bpf/headers/include/bpf/BpfUtils.h
+++ b/bpf/headers/include/bpf/BpfUtils.h
@@ -26,6 +26,7 @@
 #include <sys/socket.h>
 #include <sys/utsname.h>
 
+#include <android-base/properties.h>
 #include <log/log.h>
 
 #include "KernelUtils.h"
@@ -33,6 +34,16 @@
 namespace android {
 namespace bpf {
 
+const bool unreleased = (base::GetProperty("ro.build.version.codename", "REL") != "REL");
+const int api_level = unreleased ? 10000 : android_get_device_api_level();
+const bool isAtLeastR = (api_level >= 30);
+const bool isAtLeastS = (api_level >= 31);
+// Sv2 is 32
+const bool isAtLeastT = (api_level >= 33);
+const bool isAtLeastU = (api_level >= 34);
+const bool isAtLeastV = (api_level >= 35);
+const bool isAtLeast25Q2 = (api_level >= 36);
+
 // See kernel's net/core/sock_diag.c __sock_gen_cookie()
 // the implementation of which guarantees 0 will never be returned,
 // primarily because 0 is used to mean not yet initialized,
diff --git a/bpf/headers/include/bpf_map_def.h b/bpf/headers/include/bpf_map_def.h
index e95ca5f..2e5afca 100644
--- a/bpf/headers/include/bpf_map_def.h
+++ b/bpf/headers/include/bpf_map_def.h
@@ -163,7 +163,7 @@
     enum bpf_map_type type;
     unsigned int key_size;
     unsigned int value_size;
-    int max_entries;  // negative means BPF_F_NO_PREALLOC, but *might* not work with S
+    unsigned int max_entries;
     unsigned int map_flags;
 
     // The following are not supported by the Android bpfloader:
diff --git a/bpf/loader/NetBpfLoad.cpp b/bpf/loader/NetBpfLoad.cpp
index 40d1281..9486e75 100644
--- a/bpf/loader/NetBpfLoad.cpp
+++ b/bpf/loader/NetBpfLoad.cpp
@@ -1414,37 +1414,6 @@
 static int doLoad(char** argv, char * const envp[]) {
     const bool runningAsRoot = !getuid();  // true iff U QPR3 or V+
 
-    // Any released device will have codename REL instead of a 'real' codename.
-    // For safety: default to 'REL' so we default to unreleased=false on failure.
-    const bool unreleased = (GetProperty("ro.build.version.codename", "REL") != "REL");
-
-    // goog/main device_api_level is bumped *way* before aosp/main api level
-    // (the latter only gets bumped during the push of goog/main to aosp/main)
-    //
-    // Since we develop in AOSP, we want it to behave as if it was bumped too.
-    //
-    // Note that AOSP doesn't really have a good api level (for example during
-    // early V dev cycle, it would have *all* of T, some but not all of U, and some V).
-    // One could argue that for our purposes AOSP api level should be infinite or 10000.
-    //
-    // This could also cause api to be increased in goog/main or other branches,
-    // but I can't imagine a case where this would be a problem: the problem
-    // is rather a too low api level, rather than some ill defined high value.
-    // For example as I write this aosp is 34/U, and goog is 35/V,
-    // we want to treat both goog & aosp as 35/V, but it's harmless if we
-    // treat goog as 36 because that value isn't yet defined to mean anything,
-    // and we thus never compare against it.
-    //
-    // Also note that 'android_get_device_api_level()' is what the
-    //   //system/core/init/apex_init_util.cpp
-    // apex init .XXrc parsing code uses for XX filtering, and that code
-    // (now) similarly uses __ANDROID_API_FUTURE__ for non 'REL' codenames.
-    const int api_level = unreleased ? __ANDROID_API_FUTURE__ : android_get_device_api_level();
-    const bool isAtLeastT = (api_level >= __ANDROID_API_T__);
-    const bool isAtLeastU = (api_level >= __ANDROID_API_U__);
-    const bool isAtLeastV = (api_level >= __ANDROID_API_V__);
-    const bool isAtLeast25Q2 = (api_level > __ANDROID_API_V__);  // TODO: fix >
-
     const int first_api_level = GetIntProperty("ro.board.first_api_level", api_level);
 
     // last in U QPR2 beta1
@@ -1591,7 +1560,7 @@
         if (isArm() && (isTV() || isWear())) {
             // exempt Arm TV or Wear devices (arm32 ABI is far less problematic than x86-32)
             ALOGW("[Arm TV/Wear] 32-bit userspace unsupported on 6.2+ kernels.");
-        } else if (first_api_level <= __ANDROID_API_T__ && isArm()) {
+        } else if (first_api_level <= 33 /*T*/ && isArm()) {
             // also exempt Arm devices upgrading with major kernel rev from T-
             // might possibly be better for them to run with a newer kernel...
             ALOGW("[Arm KernelUpRev] 32-bit userspace unsupported on 6.2+ kernels.");
diff --git a/bpf/netd/BpfHandler.cpp b/bpf/netd/BpfHandler.cpp
index 125f26b..e3e508b 100644
--- a/bpf/netd/BpfHandler.cpp
+++ b/bpf/netd/BpfHandler.cpp
@@ -22,7 +22,6 @@
 #include <inttypes.h>
 
 #include <android-base/unique_fd.h>
-#include <android-modules-utils/sdk_level.h>
 #include <bpf/WaitForProgsLoaded.h>
 #include <log/log.h>
 #include <netdutils/UidConstants.h>
@@ -37,6 +36,10 @@
 using base::WaitForProperty;
 using bpf::getSocketCookie;
 using bpf::isAtLeastKernelVersion;
+using bpf::isAtLeastT;
+using bpf::isAtLeastU;
+using bpf::isAtLeastV;
+using bpf::isAtLeast25Q2;
 using bpf::queryProgram;
 using bpf::retrieveProgram;
 using netdutils::Status;
@@ -72,18 +75,11 @@
     return netdutils::status::ok;
 }
 
-// Checks if the device is running on release version of Android 25Q2 or newer.
-static bool isAtLeast25Q2() {
-    return android_get_device_api_level() >= 36 ||
-           (android_get_device_api_level() == 35 &&
-            modules::sdklevel::detail::IsAtLeastPreReleaseCodename("Baklava"));
-}
-
 static Status initPrograms(const char* cg2_path) {
     if (!cg2_path) return Status("cg2_path is NULL");
 
     // This code was mainlined in T, so this should be trivially satisfied.
-    if (!modules::sdklevel::IsAtLeastT()) return Status("S- platform is unsupported");
+    if (!isAtLeastT) return Status("S- platform is unsupported");
 
     // S requires eBPF support which was only added in 4.9, so this should be satisfied.
     if (!isAtLeastKernelVersion(4, 9, 0)) {
@@ -91,22 +87,22 @@
     }
 
     // U bumps the kernel requirement up to 4.14
-    if (modules::sdklevel::IsAtLeastU() && !isAtLeastKernelVersion(4, 14, 0)) {
+    if (isAtLeastU && !isAtLeastKernelVersion(4, 14, 0)) {
         return Status("U+ platform with kernel version < 4.14.0 is unsupported");
     }
 
     // U mandates this mount point (though it should also be the case on T)
-    if (modules::sdklevel::IsAtLeastU() && !!strcmp(cg2_path, "/sys/fs/cgroup")) {
+    if (isAtLeastU && !!strcmp(cg2_path, "/sys/fs/cgroup")) {
         return Status("U+ platform with cg2_path != /sys/fs/cgroup is unsupported");
     }
 
     // V bumps the kernel requirement up to 4.19
-    if (modules::sdklevel::IsAtLeastV() && !isAtLeastKernelVersion(4, 19, 0)) {
+    if (isAtLeastV && !isAtLeastKernelVersion(4, 19, 0)) {
         return Status("V+ platform with kernel version < 4.19.0 is unsupported");
     }
 
     // 25Q2 bumps the kernel requirement up to 5.4
-    if (isAtLeast25Q2() && !isAtLeastKernelVersion(5, 4, 0)) {
+    if (isAtLeast25Q2 && !isAtLeastKernelVersion(5, 4, 0)) {
         return Status("25Q2+ platform with kernel version < 5.4.0 is unsupported");
     }
 
@@ -135,7 +131,7 @@
                                     cg_fd, BPF_CGROUP_INET_SOCK_RELEASE));
     }
 
-    if (modules::sdklevel::IsAtLeastV()) {
+    if (isAtLeastV) {
         // V requires 4.19+, so technically this 2nd 'if' is not required, but it
         // doesn't hurt us to try to support AOSP forks that try to support older kernels.
         if (isAtLeastKernelVersion(4, 19, 0)) {
@@ -180,7 +176,7 @@
         if (queryProgram(cg_fd, BPF_CGROUP_INET_SOCK_RELEASE) <= 0) abort();
     }
 
-    if (modules::sdklevel::IsAtLeastV()) {
+    if (isAtLeastV) {
         // V requires 4.19+, so technically this 2nd 'if' is not required, but it
         // doesn't hurt us to try to support AOSP forks that try to support older kernels.
         if (isAtLeastKernelVersion(4, 19, 0)) {
@@ -266,14 +262,13 @@
     // ...unless someone changed 'exec_start bpfloader' to 'start bpfloader'
     // in the rc file.
     //
-    // TODO: should be: if (!modules::sdklevel::IsAtLeastW())
-    if (android_get_device_api_level() <= __ANDROID_API_V__) waitForBpf();
+    if (!isAtLeast25Q2) waitForBpf();
 
     RETURN_IF_NOT_OK(initPrograms(cg2_path));
     RETURN_IF_NOT_OK(initMaps());
 
-    if (android_get_device_api_level() > __ANDROID_API_V__) {
-        // make sure netd can create & write maps.  sepolicy is V+, but enough to enforce on 25Q2+
+    if (isAtLeast25Q2) {
+        // Make sure netd can create & write maps.  sepolicy is V+, but enough to enforce on 25Q2+
         int key = 1;
         int value = 123;
         unique_fd map(bpf::createMap(BPF_MAP_TYPE_ARRAY, sizeof(key), sizeof(value), 2, 0));
diff --git a/bpf/progs/clatd.c b/bpf/progs/clatd.c
index 2d4551e..2bb9d6f 100644
--- a/bpf/progs/clatd.c
+++ b/bpf/progs/clatd.c
@@ -288,6 +288,9 @@
     // We cannot handle IP options, just standard 20 byte == 5 dword minimal IPv4 header
     if (ip4->ihl != 5) return TC_ACT_PIPE;
 
+    // Packet must not be multicast
+    if ((ip4->daddr & 0xf0000000) == 0xe0000000) return TC_ACT_PIPE;
+
     // Calculate the IPv4 one's complement checksum of the IPv4 header.
     __wsum sum4 = 0;
     for (unsigned i = 0; i < sizeof(*ip4) / sizeof(__u16); ++i) {
diff --git a/bpf/tests/mts/bpf_existence_test.cpp b/bpf/tests/mts/bpf_existence_test.cpp
index 0ecda3d..75fb8e9 100644
--- a/bpf/tests/mts/bpf_existence_test.cpp
+++ b/bpf/tests/mts/bpf_existence_test.cpp
@@ -31,6 +31,12 @@
 using std::string;
 
 using android::bpf::isAtLeastKernelVersion;
+using android::bpf::isAtLeastR;
+using android::bpf::isAtLeastS;
+using android::bpf::isAtLeastT;
+using android::bpf::isAtLeastU;
+using android::bpf::isAtLeastV;
+using android::bpf::isAtLeast25Q2;
 
 #define PLATFORM "/sys/fs/bpf/"
 #define TETHERING "/sys/fs/bpf/tethering/"
@@ -42,16 +48,6 @@
 class BpfExistenceTest : public ::testing::Test {
 };
 
-const bool unreleased = (android::base::GetProperty("ro.build.version.codename", "REL") != "REL");
-const int api_level = unreleased ? 10000 : android_get_device_api_level();
-const bool isAtLeastR = (api_level >= 30);
-const bool isAtLeastS = (api_level >= 31);
-// Sv2 is 32
-const bool isAtLeastT = (api_level >= 33);
-const bool isAtLeastU = (api_level >= 34);
-const bool isAtLeastV = (api_level >= 35);
-const bool isAtLeast25Q2 = (api_level >= 36);
-
 // Part of Android R platform (for 4.9+), but mainlined in S
 static const set<string> PLATFORM_ONLY_IN_R = {
     PLATFORM "map_offload_tether_ingress_map",
diff --git a/clatd/ipv4.c b/clatd/ipv4.c
index 2be02e3..81bf87b 100644
--- a/clatd/ipv4.c
+++ b/clatd/ipv4.c
@@ -85,6 +85,11 @@
     return 0;
   }
 
+  if ((header->daddr & 0xf0000000) == 0xe0000000) {
+    logmsg_dbg(ANDROID_LOG_INFO, "ip_packet/daddr is multicast: %x", header->daddr);
+    return 0;
+  }
+
   /* rfc6145 - If any IPv4 options are present in the IPv4 packet, they MUST be
    * ignored and the packet translated normally; there is no attempt to
    * translate the options.
diff --git a/framework/Android.bp b/framework/Android.bp
index f66bc60..ab3af9a 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -295,7 +295,6 @@
         ":framework-connectivity-t-pre-jarjar{.jar}",
         ":framework-connectivity.stubs.module_lib{.jar}",
         ":framework-connectivity-t.stubs.module_lib{.jar}",
-        ":framework-connectivity-module-api-stubs-including-flagged{.jar}",
         "jarjar-excludes.txt",
     ],
     tools: [
@@ -308,7 +307,6 @@
         "--prefix android.net.connectivity " +
         "--apistubs $(location :framework-connectivity.stubs.module_lib{.jar}) " +
         "--apistubs $(location :framework-connectivity-t.stubs.module_lib{.jar}) " +
-        "--apistubs $(location :framework-connectivity-module-api-stubs-including-flagged{.jar}) " +
         // Make a ":"-separated list. There will be an extra ":" but empty items are ignored.
         "--unsupportedapi $$(printf ':%s' $(locations :connectivity-hiddenapi-files)) " +
         "--excludes $(location jarjar-excludes.txt) " +
@@ -320,35 +318,6 @@
     ],
 }
 
-droidstubs {
-    name: "framework-connectivity-module-api-stubs-including-flagged-droidstubs",
-    srcs: [
-        ":framework-connectivity-sources",
-        ":framework-connectivity-tiramisu-updatable-sources",
-        ":framework-networksecurity-sources",
-        ":framework-nearby-java-sources",
-        ":framework-thread-sources",
-    ],
-    flags: [
-        "--show-for-stub-purposes-annotation android.annotation.SystemApi" +
-            "\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\)",
-        "--show-for-stub-purposes-annotation android.annotation.SystemApi" +
-            "\\(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES\\)",
-    ],
-    aidl: {
-        include_dirs: [
-            "packages/modules/Connectivity/framework/aidl-export",
-            "packages/modules/Connectivity/Tethering/common/TetheringLib/src",
-            "frameworks/native/aidl/binder", // For PersistableBundle.aidl
-        ],
-    },
-}
-
-java_library {
-    name: "framework-connectivity-module-api-stubs-including-flagged",
-    srcs: [":framework-connectivity-module-api-stubs-including-flagged-droidstubs"],
-}
-
 // Library providing limited APIs within the connectivity module, so that R+ components like
 // Tethering have a controlled way to depend on newer components like framework-connectivity that
 // are not loaded on R.
diff --git a/framework/jni/android_net_NetworkUtils.cpp b/framework/jni/android_net_NetworkUtils.cpp
index 3779a00..7404f32 100644
--- a/framework/jni/android_net_NetworkUtils.cpp
+++ b/framework/jni/android_net_NetworkUtils.cpp
@@ -23,9 +23,9 @@
 #include <netinet/in.h>
 #include <string.h>
 
+#include <DnsProxydProtocol.h> // NETID_USE_LOCAL_NAMESERVERS
 #include <bpf/BpfClassic.h>
 #include <bpf/KernelUtils.h>
-#include <DnsProxydProtocol.h> // NETID_USE_LOCAL_NAMESERVERS
 #include <nativehelper/JNIPlatformHelp.h>
 #include <nativehelper/ScopedPrimitiveArray.h>
 #include <utils/Log.h>
@@ -259,6 +259,21 @@
     return bpf::isX86();
 }
 
+static jlong android_net_utils_getSocketCookie(JNIEnv *env, jclass clazz,
+                                               jobject javaFd) {
+    int sock = AFileDescriptor_getFd(env, javaFd);
+    uint64_t cookie = 0;
+    socklen_t cookie_len = sizeof(cookie);
+    if (getsockopt(sock, SOL_SOCKET, SO_COOKIE, &cookie, &cookie_len)) {
+        // Failure is almost certainly either EBADF or ENOTSOCK
+        jniThrowErrnoException(env, "getSocketCookie", errno);
+    } else if (cookie_len != sizeof(cookie)) {
+        // This probably cannot actually happen, but...
+        jniThrowErrnoException(env, "getSocketCookie", 523); // EBADCOOKIE
+    }
+    return static_cast<jlong>(cookie);
+}
+
 // ----------------------------------------------------------------------------
 
 /*
@@ -283,6 +298,7 @@
     (void*) android_net_utils_setsockoptBytes},
     { "isKernel64Bit", "()Z", (void*) android_net_utils_isKernel64Bit },
     { "isKernelX86", "()Z", (void*) android_net_utils_isKernelX86 },
+    { "getSocketCookie", "(Ljava/io/FileDescriptor;)J", (void*) android_net_utils_getSocketCookie },
 };
 // clang-format on
 
diff --git a/framework/src/android/net/L2capNetworkSpecifier.java b/framework/src/android/net/L2capNetworkSpecifier.java
index cfc9ed9..93f9352 100644
--- a/framework/src/android/net/L2capNetworkSpecifier.java
+++ b/framework/src/android/net/L2capNetworkSpecifier.java
@@ -170,6 +170,51 @@
         return mPsm;
     }
 
+    /**
+     * Checks whether the given L2capNetworkSpecifier is valid as part of a server network
+     * reservation request.
+     *
+     * @hide
+     */
+    public boolean isValidServerReservationSpecifier() {
+        // The ROLE_SERVER offer can be satisfied by a ROLE_ANY request.
+        if (mRole != ROLE_SERVER) return false;
+
+        // HEADER_COMPRESSION_ANY is never valid in a request.
+        if (mHeaderCompression == HEADER_COMPRESSION_ANY) return false;
+
+        // Remote address must be null for ROLE_SERVER requests.
+        if (mRemoteAddress != null) return false;
+
+        // reservation must allocate a PSM, so only PSM_ANY can be passed.
+        if (mPsm != PSM_ANY) return false;
+
+        return true;
+    }
+
+    /**
+     * Checks whether the given L2capNetworkSpecifier is valid as part of a client network request.
+     *
+     * @hide
+     */
+    public boolean isValidClientRequestSpecifier() {
+        // The ROLE_CLIENT offer can be satisfied by a ROLE_ANY request.
+        if (mRole != ROLE_CLIENT) return false;
+
+        // HEADER_COMPRESSION_ANY is never valid in a request.
+        if (mHeaderCompression == HEADER_COMPRESSION_ANY) return false;
+
+        // Remote address must not be null for ROLE_CLIENT requests.
+        if (mRemoteAddress == null) return false;
+
+        // Client network requests require a PSM to be specified.
+        // Ensure the PSM is within the valid range of dynamic BLE L2CAP values.
+        if (mPsm < 0x80) return false;
+        if (mPsm > 0xFF) return false;
+
+        return true;
+    }
+
     /** A builder class for L2capNetworkSpecifier. */
     public static final class Builder {
         @Role
diff --git a/framework/src/android/net/NetworkAgentConfig.java b/framework/src/android/net/NetworkAgentConfig.java
index deaa734..da12a0a 100644
--- a/framework/src/android/net/NetworkAgentConfig.java
+++ b/framework/src/android/net/NetworkAgentConfig.java
@@ -272,27 +272,6 @@
         return mVpnRequiresValidation;
     }
 
-    /**
-     * Whether the native network creation should be skipped.
-     *
-     * If set, the native network and routes should be maintained by the caller.
-     *
-     * @hide
-     */
-    private boolean mSkipNativeNetworkCreation = false;
-
-
-    /**
-     * @return Whether the native network creation should be skipped.
-     * @hide
-     */
-    // TODO: Expose API when ready.
-    // @FlaggedApi(Flags.FLAG_TETHERING_NETWORK_AGENT)
-    // @SystemApi(client = MODULE_LIBRARIES) when ready.
-    public boolean shouldSkipNativeNetworkCreation() {
-        return mSkipNativeNetworkCreation;
-    }
-
     /** @hide */
     public NetworkAgentConfig() {
     }
@@ -314,7 +293,6 @@
             mLegacyExtraInfo = nac.mLegacyExtraInfo;
             excludeLocalRouteVpn = nac.excludeLocalRouteVpn;
             mVpnRequiresValidation = nac.mVpnRequiresValidation;
-            mSkipNativeNetworkCreation = nac.mSkipNativeNetworkCreation;
         }
     }
 
@@ -506,26 +484,6 @@
         }
 
         /**
-         * Sets the native network creation should be skipped.
-         *
-         * @return this builder, to facilitate chaining.
-         * @hide
-         */
-        @NonNull
-        // TODO: Expose API when ready.
-        // @FlaggedApi(Flags.FLAG_TETHERING_NETWORK_AGENT)
-        // @SystemApi(client = MODULE_LIBRARIES) when ready.
-        public Builder setSkipNativeNetworkCreation(boolean skipNativeNetworkCreation) {
-            if (!SdkLevel.isAtLeastV()) {
-                // Local agents are supported starting on U on TVs and on V on everything else.
-                // Thus, only support this flag on V+.
-                throw new UnsupportedOperationException("Method is not supported");
-            }
-            mConfig.mSkipNativeNetworkCreation = skipNativeNetworkCreation;
-            return this;
-        }
-
-        /**
          * Returns the constructed {@link NetworkAgentConfig} object.
          */
         @NonNull
@@ -552,8 +510,7 @@
                 && Objects.equals(legacySubTypeName, that.legacySubTypeName)
                 && Objects.equals(mLegacyExtraInfo, that.mLegacyExtraInfo)
                 && excludeLocalRouteVpn == that.excludeLocalRouteVpn
-                && mVpnRequiresValidation == that.mVpnRequiresValidation
-                && mSkipNativeNetworkCreation == that.mSkipNativeNetworkCreation;
+                && mVpnRequiresValidation == that.mVpnRequiresValidation;
     }
 
     @Override
@@ -561,8 +518,7 @@
         return Objects.hash(allowBypass, explicitlySelected, acceptUnvalidated,
                 acceptPartialConnectivity, provisioningNotificationDisabled, subscriberId,
                 skip464xlat, legacyType, legacySubType, legacyTypeName, legacySubTypeName,
-                mLegacyExtraInfo, excludeLocalRouteVpn, mVpnRequiresValidation,
-                mSkipNativeNetworkCreation);
+                mLegacyExtraInfo, excludeLocalRouteVpn, mVpnRequiresValidation);
     }
 
     @Override
@@ -583,7 +539,6 @@
                 + ", legacyExtraInfo = '" + mLegacyExtraInfo + '\''
                 + ", excludeLocalRouteVpn = '" + excludeLocalRouteVpn + '\''
                 + ", vpnRequiresValidation = '" + mVpnRequiresValidation + '\''
-                + ", skipNativeNetworkCreation = '" + mSkipNativeNetworkCreation + '\''
                 + "}";
     }
 
@@ -608,35 +563,33 @@
         out.writeString(mLegacyExtraInfo);
         out.writeInt(excludeLocalRouteVpn ? 1 : 0);
         out.writeInt(mVpnRequiresValidation ? 1 : 0);
-        out.writeInt(mSkipNativeNetworkCreation ? 1 : 0);
     }
 
     public static final @NonNull Creator<NetworkAgentConfig> CREATOR =
             new Creator<NetworkAgentConfig>() {
-                @Override
-                public NetworkAgentConfig createFromParcel(Parcel in) {
-                    NetworkAgentConfig networkAgentConfig = new NetworkAgentConfig();
-                    networkAgentConfig.allowBypass = in.readInt() != 0;
-                    networkAgentConfig.explicitlySelected = in.readInt() != 0;
-                    networkAgentConfig.acceptUnvalidated = in.readInt() != 0;
-                    networkAgentConfig.acceptPartialConnectivity = in.readInt() != 0;
-                    networkAgentConfig.subscriberId = in.readString();
-                    networkAgentConfig.provisioningNotificationDisabled = in.readInt() != 0;
-                    networkAgentConfig.skip464xlat = in.readInt() != 0;
-                    networkAgentConfig.legacyType = in.readInt();
-                    networkAgentConfig.legacyTypeName = in.readString();
-                    networkAgentConfig.legacySubType = in.readInt();
-                    networkAgentConfig.legacySubTypeName = in.readString();
-                    networkAgentConfig.mLegacyExtraInfo = in.readString();
-                    networkAgentConfig.excludeLocalRouteVpn = in.readInt() != 0;
-                    networkAgentConfig.mVpnRequiresValidation = in.readInt() != 0;
-                    networkAgentConfig.mSkipNativeNetworkCreation = in.readInt() != 0;
-                    return networkAgentConfig;
-                }
+        @Override
+        public NetworkAgentConfig createFromParcel(Parcel in) {
+            NetworkAgentConfig networkAgentConfig = new NetworkAgentConfig();
+            networkAgentConfig.allowBypass = in.readInt() != 0;
+            networkAgentConfig.explicitlySelected = in.readInt() != 0;
+            networkAgentConfig.acceptUnvalidated = in.readInt() != 0;
+            networkAgentConfig.acceptPartialConnectivity = in.readInt() != 0;
+            networkAgentConfig.subscriberId = in.readString();
+            networkAgentConfig.provisioningNotificationDisabled = in.readInt() != 0;
+            networkAgentConfig.skip464xlat = in.readInt() != 0;
+            networkAgentConfig.legacyType = in.readInt();
+            networkAgentConfig.legacyTypeName = in.readString();
+            networkAgentConfig.legacySubType = in.readInt();
+            networkAgentConfig.legacySubTypeName = in.readString();
+            networkAgentConfig.mLegacyExtraInfo = in.readString();
+            networkAgentConfig.excludeLocalRouteVpn = in.readInt() != 0;
+            networkAgentConfig.mVpnRequiresValidation = in.readInt() != 0;
+            return networkAgentConfig;
+        }
 
-                @Override
-                public NetworkAgentConfig[] newArray(int size) {
-                    return new NetworkAgentConfig[size];
-                }
-            };
+        @Override
+        public NetworkAgentConfig[] newArray(int size) {
+            return new NetworkAgentConfig[size];
+        }
+    };
 }
diff --git a/framework/src/android/net/NetworkUtils.java b/framework/src/android/net/NetworkUtils.java
index 18feb84..6b2eb08 100644
--- a/framework/src/android/net/NetworkUtils.java
+++ b/framework/src/android/net/NetworkUtils.java
@@ -443,4 +443,13 @@
 
     /** Returns whether the Linux Kernel is x86 */
     public static native boolean isKernelX86();
+
+    /**
+     * Returns socket cookie.
+     *
+     * @param fd The socket file descriptor
+     * @return The socket cookie.
+     * @throws ErrnoException if retrieving the socket cookie fails.
+     */
+    public static native long getSocketCookie(FileDescriptor fd) throws ErrnoException;
 }
diff --git a/framework/src/android/net/TestNetworkManager.java b/framework/src/android/net/TestNetworkManager.java
index caeef48..cbc7a4f 100644
--- a/framework/src/android/net/TestNetworkManager.java
+++ b/framework/src/android/net/TestNetworkManager.java
@@ -198,45 +198,6 @@
     }
 
     /**
-     * Create a tap interface for testing purposes
-     *
-     * @param linkAddrs an array of LinkAddresses to assign to the TAP interface
-     * @return A TestNetworkInterface representing the underlying TAP interface. Close the contained
-     *     ParcelFileDescriptor to tear down the TAP interface.
-     * @hide
-     */
-    @RequiresPermission(Manifest.permission.MANAGE_TEST_NETWORKS)
-    @NonNull
-    public TestNetworkInterface createTapInterface(@NonNull LinkAddress[] linkAddrs) {
-        try {
-            return mService.createInterface(TAP, CARRIER_UP, BRING_UP, USE_IPV6_PROV_DELAY,
-                    linkAddrs, null /* iface */);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Create a tap interface for testing purposes
-     *
-     * @param bringUp whether to bring up the interface before returning it.
-     *
-     * @return A ParcelFileDescriptor of the underlying TAP interface. Close this to tear down the
-     *     TAP interface.
-     * @hide
-     */
-    @RequiresPermission(Manifest.permission.MANAGE_TEST_NETWORKS)
-    @NonNull
-    public TestNetworkInterface createTapInterface(boolean bringUp) {
-        try {
-            return mService.createInterface(TAP, CARRIER_UP, bringUp, USE_IPV6_PROV_DELAY,
-                    NO_ADDRS, null /* iface */);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * Create a tap interface with a given interface name for testing purposes
      *
      * @param bringUp whether to bring up the interface before returning it.
@@ -260,26 +221,6 @@
     }
 
     /**
-     * Create a tap interface with or without carrier for testing purposes.
-     *
-     * Note: setting carrierUp = false is not supported until kernel version 6.0.
-     *
-     * @param carrierUp whether the created interface has a carrier or not.
-     * @param bringUp whether to bring up the interface before returning it.
-     * @hide
-     */
-    @RequiresPermission(Manifest.permission.MANAGE_TEST_NETWORKS)
-    @NonNull
-    public TestNetworkInterface createTapInterface(boolean carrierUp, boolean bringUp) {
-        try {
-            return mService.createInterface(TAP, carrierUp, bringUp, USE_IPV6_PROV_DELAY, NO_ADDRS,
-                    null /* iface */);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * Create a tap interface for testing purposes.
      *
      * Note: setting carrierUp = false is not supported until kernel version 6.0.
@@ -302,27 +243,6 @@
     }
 
     /**
-     * Create a tap interface for testing purposes.
-     *
-     * @param disableIpv6ProvisioningDelay whether to disable DAD and RS delay.
-     * @param linkAddrs an array of LinkAddresses to assign to the TAP interface
-     * @return A TestNetworkInterface representing the underlying TAP interface. Close the contained
-     *     ParcelFileDescriptor to tear down the TAP interface.
-     * @hide
-     */
-    @RequiresPermission(Manifest.permission.MANAGE_TEST_NETWORKS)
-    @NonNull
-    public TestNetworkInterface createTapInterface(boolean disableIpv6ProvisioningDelay,
-            @NonNull LinkAddress[] linkAddrs) {
-        try {
-            return mService.createInterface(TAP, CARRIER_UP, BRING_UP, disableIpv6ProvisioningDelay,
-                    linkAddrs, null /* iface */);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * Enable / disable carrier on TestNetworkInterface
      *
      * Note: TUNSETCARRIER is not supported until kernel version 5.0.
diff --git a/networksecurity/TEST_MAPPING b/networksecurity/TEST_MAPPING
index f75bf9a..448ee84 100644
--- a/networksecurity/TEST_MAPPING
+++ b/networksecurity/TEST_MAPPING
@@ -1,9 +1,4 @@
 {
-  "tethering-mainline-presubmit": [
-    {
-      "name": "NetworkSecurityUnitTests"
-    }
-  ],
   "presubmit": [
     {
       "name": "CtsNetSecConfigCertificateTransparencyTestCases"
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java
index 1212e29..d91bd11 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java
@@ -564,7 +564,6 @@
             // Never try mDNS on cellular, or on interfaces with incompatible flags
             if (CollectionUtils.contains(transports, TRANSPORT_CELLULAR)
                     || iface.isLoopback()
-                    || iface.isPointToPoint()
                     || iface.isVirtual()
                     || !iface.isUp()) {
                 return false;
diff --git a/service/ServiceConnectivityResources/OWNERS b/service/ServiceConnectivityResources/OWNERS
index df41ff2..c3c08ee 100644
--- a/service/ServiceConnectivityResources/OWNERS
+++ b/service/ServiceConnectivityResources/OWNERS
@@ -1,2 +1,3 @@
+per-file res/raw/ct_public_keys.pem = file:platform/packages/modules/Connectivity:main:/networksecurity/OWNERS
 per-file res/values/config_thread.xml = file:platform/packages/modules/Connectivity:main:/thread/OWNERS
 per-file res/values/overlayable.xml = file:platform/packages/modules/Connectivity:main:/thread/OWNERS
diff --git a/service/ServiceConnectivityResources/res/raw/ct_public_keys.pem b/service/ServiceConnectivityResources/res/raw/ct_public_keys.pem
index 80dccbe..8a5ebbf 100644
--- a/service/ServiceConnectivityResources/res/raw/ct_public_keys.pem
+++ b/service/ServiceConnectivityResources/res/raw/ct_public_keys.pem
@@ -1,4 +1,18 @@
 -----BEGIN PUBLIC KEY-----
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAmDwwE2FRpVJlw58fo5Ra
+Fsocb7DP3FJwwuaghXL3xPtyZisDDXIpfVG+UwDPyIGrRuYzeu9pjZ/0xGSYSPZ0
+l/H8L2XurInoAbj+Z370HB7W3njIOqG9rw5N6u/xT4nscBj1HKeUwh+Hwc0F1UHS
+MP8J32nWAfVepHrte3Jy+w/V7BId6WjJmxtI9OoJ7WTsoTeD+jLANUJWtpbx0p1L
+OAy70BlHbB0UvAJdMH149qi7Y9KaJ74Ea2ofKY43NWGgWfR+fY6V7CCfUXCOgvNM
+qq5QGyMnFKrlP0XkoOaVJkK92VEtyNff8KUXik2ZyUzhNkg4ZplCrhESWeykckB/
+mdZpVc45KZ+6Sx3U+FF30eRwlu2Nw2h1KKHzYfa6M1bcy1f/xw+IDq4R+1rR7sPb
+J2mMKz0OPeCXwGEXWzBuMOs0IQu6gyNdyVZcRSyQ+LcUzvEwksLP6G/ycqmwVfdw
+JE28k3MPUR3IxnMDQrdcZb7M7kjBoykKW3jQfwlEoK4EcNQbMXVn8Ws8rcwgQcQJ
+MjjQnbISojsJYo2fG+TE6d9rORB6CYVzICOj4YguXm4LO89cYQlR600W32pP5y3o
+3/yAd9OjsKrNfREDlcCXUx1APc7gOF351RFdHlDI0+RF/pIHbH3sww3VMCJ+tjst
+ZldgJk9yaz0cvOdKyVWC83ECAwEAAQ==
+-----END PUBLIC KEY-----
+-----BEGIN PUBLIC KEY-----
 MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnmb1lacOnP5H1bwb06mG
 fEUeC9PZRwNQskSs9KaWrpfrSkLKuHXkVCbgeagbUR/Sh1OeIhyJRSS0PLCO0JjC
 UpGhYMrIGRgEET4IrP9f8aMFqxxxBUEanI+OxAhIJlP9tiWfGdKAASYcxg/DyXXz
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index 7c0c223..36c0cf9 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -906,7 +906,12 @@
             final InetAddress address, final int protocol, final int remotePort,
             final boolean isAllowed) {
         throwIfPre25Q2("addLocalNetAccess is not available on pre-B devices");
-        final int ifIndex = mDeps.getIfIndex(iface);
+        final int ifIndex;
+        if (iface == null) {
+            ifIndex = 0;
+        } else {
+            ifIndex = mDeps.getIfIndex(iface);
+        }
         if (ifIndex == 0) {
             Log.e(TAG, "Failed to get if index, skip addLocalNetAccess for " + address
                     + "(" + iface + ")");
@@ -935,7 +940,12 @@
     public void removeLocalNetAccess(final int lpmBitlen, final String iface,
             final InetAddress address, final int protocol, final int remotePort) {
         throwIfPre25Q2("removeLocalNetAccess is not available on pre-B devices");
-        final int ifIndex = mDeps.getIfIndex(iface);
+        final int ifIndex;
+        if (iface == null) {
+            ifIndex = 0;
+        } else {
+            ifIndex = mDeps.getIfIndex(iface);
+        }
         if (ifIndex == 0) {
             Log.e(TAG, "Failed to get if index, skip removeLocalNetAccess for " + address
                     + "(" + iface + ")");
@@ -966,7 +976,12 @@
     public boolean getLocalNetAccess(final int lpmBitlen, final String iface,
             final InetAddress address, final int protocol, final int remotePort) {
         throwIfPre25Q2("getLocalNetAccess is not available on pre-B devices");
-        final int ifIndex = mDeps.getIfIndex(iface);
+        final int ifIndex;
+        if (iface == null) {
+            ifIndex = 0;
+        } else {
+            ifIndex = mDeps.getIfIndex(iface);
+        }
         if (ifIndex == 0) {
             Log.e(TAG, "Failed to get if index, returning default from getLocalNetAccess for "
                     + address + "(" + iface + ")");
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 6dbb1d8..2c6390f 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -423,14 +423,14 @@
 import java.util.concurrent.CopyOnWriteArraySet;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 
 /**
  * @hide
  */
-public class ConnectivityService extends IConnectivityManager.Stub
-        implements PendingIntent.OnFinished {
+public class ConnectivityService extends IConnectivityManager.Stub {
     private static final String TAG = ConnectivityService.class.getSimpleName();
 
     private static final String DIAG_ARG = "--diag";
@@ -1913,11 +1913,12 @@
                 && mDeps.isFeatureEnabled(context, REQUEST_RESTRICTED_WIFI);
         mBackgroundFirewallChainEnabled = mDeps.isAtLeastV() && mDeps.isFeatureNotChickenedOut(
                 context, ConnectivityFlags.BACKGROUND_FIREWALL_CHAIN);
-        mUseDeclaredMethodsForCallbacksEnabled = mDeps.isFeatureEnabled(context,
-                ConnectivityFlags.USE_DECLARED_METHODS_FOR_CALLBACKS);
+        mUseDeclaredMethodsForCallbacksEnabled =
+                mDeps.isFeatureNotChickenedOut(context,
+                        ConnectivityFlags.USE_DECLARED_METHODS_FOR_CALLBACKS);
         // registerUidFrozenStateChangedCallback is only available on U+
         mQueueCallbacksForFrozenApps = mDeps.isAtLeastU()
-                && mDeps.isFeatureEnabled(context, QUEUE_CALLBACKS_FOR_FROZEN_APPS);
+                && mDeps.isFeatureNotChickenedOut(context, QUEUE_CALLBACKS_FOR_FROZEN_APPS);
         mCarrierPrivilegeAuthenticator = mDeps.makeCarrierPrivilegeAuthenticator(
                 mContext, mTelephonyManager, mRequestRestrictedWifiEnabled,
                 this::handleUidCarrierPrivilegesLost, mHandler);
@@ -9809,8 +9810,8 @@
         }
 
         // The both list contain current link properties + stacked links for new and old LP.
-        List<LinkProperties> newLinkProperties = new ArrayList<>();
-        List<LinkProperties> oldLinkProperties = new ArrayList<>();
+        final List<LinkProperties> newLinkProperties = new ArrayList<>();
+        final List<LinkProperties> oldLinkProperties = new ArrayList<>();
 
         if (newLp != null) {
             newLinkProperties.add(newLp);
@@ -9823,13 +9824,13 @@
 
         // map contains interface name to list of local network prefixes added because of change
         // in link properties
-        Map<String, List<IpPrefix>> prefixesAddedForInterface = new ArrayMap<>();
+        final Map<String, List<IpPrefix>> prefixesAddedForInterface = new ArrayMap<>();
 
         final CompareResult<LinkProperties> linkPropertiesDiff = new CompareResult<>(
                 oldLinkProperties, newLinkProperties);
 
         for (LinkProperties linkProperty : linkPropertiesDiff.added) {
-            List<IpPrefix> unicastLocalPrefixesToBeAdded = new ArrayList<>();
+            final List<IpPrefix> unicastLocalPrefixesToBeAdded = new ArrayList<>();
             for (LinkAddress linkAddress : linkProperty.getLinkAddresses()) {
                 unicastLocalPrefixesToBeAdded.addAll(
                         getLocalNetworkPrefixesForAddress(linkAddress));
@@ -9837,7 +9838,7 @@
             addLocalAddressesToBpfMap(linkProperty.getInterfaceName(),
                     unicastLocalPrefixesToBeAdded, linkProperty);
 
-            // adding iterface name -> ip prefixes that we added to map
+            // populating interface name -> ip prefixes which were added to local_net_access map.
             if (!prefixesAddedForInterface.containsKey(linkProperty.getInterfaceName())) {
                 prefixesAddedForInterface.put(linkProperty.getInterfaceName(), new ArrayList<>());
             }
@@ -9846,9 +9847,9 @@
         }
 
         for (LinkProperties linkProperty : linkPropertiesDiff.removed) {
-            List<IpPrefix> unicastLocalPrefixesToBeRemoved = new ArrayList<>();
-            List<IpPrefix> unicastLocalPrefixesAdded = prefixesAddedForInterface.getOrDefault(
-                    linkProperty.getInterfaceName(), new ArrayList<>());
+            final List<IpPrefix> unicastLocalPrefixesToBeRemoved = new ArrayList<>();
+            final List<IpPrefix> unicastLocalPrefixesAdded = prefixesAddedForInterface.getOrDefault(
+                    linkProperty.getInterfaceName(), Collections.emptyList());
 
             for (LinkAddress linkAddress : linkProperty.getLinkAddresses()) {
                 unicastLocalPrefixesToBeRemoved.addAll(
@@ -9856,8 +9857,8 @@
             }
 
             // This is to ensure if 10.0.10.0/24 was added and 10.0.11.0/24 was removed both will
-            // still populate the same prefix of 10.0.0.0/8, which mean we should not remove the
-            // prefix because of removal of 10.0.11.0/24
+            // still populate the same prefix of 10.0.0.0/8, which mean 10.0.0.0/8 should not be
+            // removed due to removal of 10.0.11.0/24
             unicastLocalPrefixesToBeRemoved.removeAll(unicastLocalPrefixesAdded);
 
             removeLocalAddressesFromBpfMap(linkProperty.getInterfaceName(),
@@ -10935,10 +10936,42 @@
         // else not handled
     }
 
+    /**
+     * A small class to manage releasing a lock exactly once even if releaseLock is called
+     * multiple times. See b/390043283
+     * PendingIntent#send throws CanceledException in various cases. In some of them it will
+     * still call onSendFinished, in others it won't and the client can't know. This class
+     * keeps a ref to the wakelock that it releases exactly once, thanks to Atomics semantics.
+     */
+    private class WakeLockOnFinishedReceiver implements PendingIntent.OnFinished {
+        private final AtomicReference<PowerManager.WakeLock> mLock;
+        WakeLockOnFinishedReceiver(@NonNull final PowerManager.WakeLock lock) {
+            mLock = new AtomicReference<>(lock);
+            lock.acquire();
+        }
+
+        public void releaseLock() {
+            final PowerManager.WakeLock lock = mLock.getAndSet(null);
+            if (null != lock) lock.release();
+        }
+
+        @Override
+        public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode,
+                String resultData, Bundle resultExtras) {
+            if (DBG) log("Finished sending " + pendingIntent);
+            releaseLock();
+            releasePendingNetworkRequestWithDelay(pendingIntent);
+        }
+    }
+
     // TODO(b/193460475): Remove when tooling supports SystemApi to public API.
     @SuppressLint("NewApi")
     private void sendIntent(PendingIntent pendingIntent, Intent intent) {
-        mPendingIntentWakeLock.acquire();
+        // Since the receiver will take the lock exactly once and release it exactly once, it
+        // is safe to pass the same wakelock to all receivers and avoid creating a new lock
+        // every time.
+        final WakeLockOnFinishedReceiver receiver =
+                new WakeLockOnFinishedReceiver(mPendingIntentWakeLock);
         try {
             if (DBG) log("Sending " + pendingIntent);
             final BroadcastOptions options = BroadcastOptions.makeBasic();
@@ -10947,25 +10980,14 @@
                 // utilizing the PendingIntent as a backdoor to do this.
                 options.setPendingIntentBackgroundActivityLaunchAllowed(false);
             }
-            pendingIntent.send(mContext, 0, intent, this /* onFinished */, null /* Handler */,
+            pendingIntent.send(mContext, 0, intent, receiver, null /* Handler */,
                     null /* requiredPermission */,
                     mDeps.isAtLeastT() ? options.toBundle() : null);
         } catch (PendingIntent.CanceledException e) {
             if (DBG) log(pendingIntent + " was not sent, it had been canceled.");
-            mPendingIntentWakeLock.release();
+            receiver.releaseLock();
             releasePendingNetworkRequest(pendingIntent);
         }
-        // ...otherwise, mPendingIntentWakeLock.release() gets called by onSendFinished()
-    }
-
-    @Override
-    public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode,
-            String resultData, Bundle resultExtras) {
-        if (DBG) log("Finished sending " + pendingIntent);
-        mPendingIntentWakeLock.release();
-        // Release with a delay so the receiving client has an opportunity to put in its
-        // own request.
-        releasePendingNetworkRequestWithDelay(pendingIntent);
     }
 
     @Nullable
diff --git a/service/src/com/android/server/L2capNetworkProvider.java b/service/src/com/android/server/L2capNetworkProvider.java
index 814a068..0352ad5 100644
--- a/service/src/com/android/server/L2capNetworkProvider.java
+++ b/service/src/com/android/server/L2capNetworkProvider.java
@@ -18,7 +18,6 @@
 
 import static android.content.pm.PackageManager.FEATURE_BLUETOOTH_LE;
 import static android.net.L2capNetworkSpecifier.HEADER_COMPRESSION_ANY;
-import static android.net.L2capNetworkSpecifier.PSM_ANY;
 import static android.net.L2capNetworkSpecifier.ROLE_CLIENT;
 import static android.net.L2capNetworkSpecifier.ROLE_SERVER;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED;
@@ -61,6 +60,7 @@
 import com.android.net.module.util.HandlerUtils;
 import com.android.net.module.util.ServiceConnectivityJni;
 import com.android.server.net.L2capNetwork;
+import com.android.server.net.L2capNetwork.L2capIpClient;
 import com.android.server.net.L2capPacketForwarder;
 
 import java.io.IOException;
@@ -126,23 +126,6 @@
             CAPABILITIES = caps;
         }
 
-        // TODO: consider moving this into L2capNetworkSpecifier as #isValidServerReservation().
-        private boolean isValidL2capServerSpecifier(L2capNetworkSpecifier l2capSpec) {
-            // The ROLE_SERVER offer can be satisfied by a ROLE_ANY request.
-            if (l2capSpec.getRole() != ROLE_SERVER) return false;
-
-            // HEADER_COMPRESSION_ANY is never valid in a request.
-            if (l2capSpec.getHeaderCompression() == HEADER_COMPRESSION_ANY) return false;
-
-            // remoteAddr must be null for ROLE_SERVER requests.
-            if (l2capSpec.getRemoteAddress() != null) return false;
-
-            // reservation must allocate a PSM, so only PSM_ANY can be passed.
-            if (l2capSpec.getPsm() != PSM_ANY) return false;
-
-            return true;
-        }
-
         @Override
         public void onNetworkNeeded(NetworkRequest request) {
             // The NetworkSpecifier is guaranteed to be either null or an L2capNetworkSpecifier, so
@@ -150,7 +133,7 @@
             final L2capNetworkSpecifier specifier =
                     (L2capNetworkSpecifier) request.getNetworkSpecifier();
             if (specifier == null) return;
-            if (!isValidL2capServerSpecifier(specifier)) {
+            if (!specifier.isValidServerReservationSpecifier()) {
                 Log.i(TAG, "Ignoring invalid reservation request: " + request);
                 return;
             }
@@ -270,7 +253,6 @@
         private class AcceptThread extends Thread {
             private static final int TIMEOUT_MS = 500;
             private final BluetoothServerSocket mServerSocket;
-            private volatile boolean mIsRunning = true;
 
             public AcceptThread(BluetoothServerSocket serverSocket) {
                 super("L2capNetworkProvider-AcceptThread");
@@ -294,16 +276,17 @@
 
             @Override
             public void run() {
-                while (mIsRunning) {
+                while (true) {
                     final BluetoothSocket connectedSocket;
                     try {
                         connectedSocket = mServerSocket.accept();
                     } catch (IOException e) {
-                        // BluetoothServerSocket was closed().
-                        if (!mIsRunning) return;
-
-                        // Else, BluetoothServerSocket encountered exception.
-                        Log.e(TAG, "BluetoothServerSocket#accept failed", e);
+                        // Note calling BluetoothServerSocket#close() also triggers an IOException
+                        // which is indistinguishable from any other exceptional behavior.
+                        // postDestroyAndUnregisterReservedOffer() is always safe to call as it
+                        // first checks whether the offer still exists; so if the
+                        // BluetoothServerSocket was closed (i.e. on tearDown()) this is a noop.
+                        Log.w(TAG, "BluetoothServerSocket closed or #accept failed", e);
                         postDestroyAndUnregisterReservedOffer();
                         return; // stop running immediately on error
                     }
@@ -313,7 +296,6 @@
 
             public void tearDown() {
                 HandlerUtils.ensureRunningOnHandlerThread(mHandler);
-                mIsRunning = false;
                 try {
                     // BluetoothServerSocket.close() is thread-safe.
                     mServerSocket.close();
@@ -434,7 +416,6 @@
         private class ConnectThread extends Thread {
             private final L2capNetworkSpecifier mSpecifier;
             private final BluetoothSocket mSocket;
-            private volatile boolean mIsAborted = false;
 
             public ConnectThread(L2capNetworkSpecifier specifier, BluetoothSocket socket) {
                 super("L2capNetworkProvider-ConnectThread");
@@ -451,11 +432,12 @@
                         if (!success) closeBluetoothSocket(mSocket);
                     });
                 } catch (IOException e) {
-                    Log.e(TAG, "Failed to connect", e);
-                    if (mIsAborted) return;
-
+                    Log.w(TAG, "BluetoothSocket was closed or #connect failed", e);
+                    // It is safe to call BluetoothSocket#close() multiple times.
                     closeBluetoothSocket(mSocket);
                     mHandler.post(() -> {
+                        // Note that if the Socket was closed, this call is a noop as the
+                        // ClientNetworkRequest has already been removed.
                         declareAllNetworkRequestsUnfulfillable(mSpecifier);
                     });
                 }
@@ -463,7 +445,6 @@
 
             public void abort() {
                 HandlerUtils.ensureRunningOnHandlerThread(mHandler);
-                mIsAborted = true;
                 // Closing the BluetoothSocket is the only way to unblock connect() because it calls
                 // shutdown on the underlying (connected) SOCK_SEQPACKET.
                 // It is safe to call BluetoothSocket#close() multiple times.
@@ -510,24 +491,6 @@
             return true;
         }
 
-        private boolean isValidL2capClientSpecifier(L2capNetworkSpecifier l2capSpec) {
-            // The ROLE_CLIENT offer can be satisfied by a ROLE_ANY request.
-            if (l2capSpec.getRole() != ROLE_CLIENT) return false;
-
-            // HEADER_COMPRESSION_ANY is never valid in a request.
-            if (l2capSpec.getHeaderCompression() == HEADER_COMPRESSION_ANY) return false;
-
-            // remoteAddr must not be null for ROLE_CLIENT requests.
-            if (l2capSpec.getRemoteAddress() == null) return false;
-
-            // Client network requests require a PSM to be specified.
-            // Ensure the PSM is within the valid range of dynamic BLE L2CAP values.
-            if (l2capSpec.getPsm() < 0x80) return false;
-            if (l2capSpec.getPsm() > 0xFF) return false;
-
-            return true;
-        }
-
         @Override
         public void onNetworkNeeded(NetworkRequest request) {
             // The NetworkSpecifier is guaranteed to be either null or an L2capNetworkSpecifier, so
@@ -535,7 +498,7 @@
             final L2capNetworkSpecifier requestSpecifier =
                     (L2capNetworkSpecifier) request.getNetworkSpecifier();
             if (requestSpecifier == null) return;
-            if (!isValidL2capClientSpecifier(requestSpecifier)) {
+            if (!requestSpecifier.isValidClientRequestSpecifier()) {
                 Log.i(TAG, "Ignoring invalid client request: " + request);
                 return;
             }
@@ -680,6 +643,11 @@
                 L2capPacketForwarder.ICallback cb) {
             return new L2capPacketForwarder(handler, tunFd, socket, compressHeaders, cb);
         }
+
+        /** Create an L2capIpClient */
+        public L2capIpClient createL2capIpClient(String logTag, Context context, String ifname) {
+            return new L2capIpClient(logTag, context, ifname);
+        }
     }
 
     public L2capNetworkProvider(Context context) {
diff --git a/service/src/com/android/server/net/L2capNetwork.java b/service/src/com/android/server/net/L2capNetwork.java
index c7417f9..ca155db 100644
--- a/service/src/com/android/server/net/L2capNetwork.java
+++ b/service/src/com/android/server/net/L2capNetwork.java
@@ -52,7 +52,7 @@
      *
      * Note that the IpClient does not need to be stopped.
      */
-    private static class L2capIpClient extends IpClientCallbacks {
+    public static class L2capIpClient extends IpClientCallbacks {
         private final String mLogTag;
         private final ConditionVariable mOnIpClientCreatedCv = new ConditionVariable(false);
         private final ConditionVariable mOnProvisioningSuccessCv = new ConditionVariable(false);
@@ -61,7 +61,7 @@
         @Nullable
         private volatile LinkProperties mLinkProperties;
 
-        L2capIpClient(String logTag, Context context, String ifname) {
+        public L2capIpClient(String logTag, Context context, String ifname) {
             mLogTag = logTag;
             IpClientUtil.makeIpClient(context, ifname, this);
         }
@@ -157,7 +157,7 @@
         // LinkProperties) or fails (and returns null).
         // Note that since L2capNetwork is using IPv6 link-local provisioning the most likely
         // (only?) failure mode is due to the interface disappearing.
-        final LinkProperties lp = new L2capIpClient(logTag, context, ifname).start();
+        final LinkProperties lp = deps.createL2capIpClient(logTag, context, ifname).start();
         if (lp == null) return null;
 
         return new L2capNetwork(
diff --git a/service/src/com/android/server/net/L2capPacketForwarder.java b/service/src/com/android/server/net/L2capPacketForwarder.java
index 737cb9c..8420d60 100644
--- a/service/src/com/android/server/net/L2capPacketForwarder.java
+++ b/service/src/com/android/server/net/L2capPacketForwarder.java
@@ -109,10 +109,10 @@
         public int read(byte[] bytes, int off, int len) throws IOException {
             // Note: EINTR is handled internally and automatically triggers a retry loop.
             int bytesRead = mInputStream.read(bytes, off, len);
-            if (bytesRead > MTU) {
+            if (bytesRead < 0 || bytesRead > MTU) {
                 // Don't try to recover, just trigger network teardown. This might indicate a bug in
                 // the Bluetooth stack.
-                throw new IOException("Packet exceeds MTU");
+                throw new IOException("Packet exceeds MTU or reached EOF. Read: " + bytesRead);
             }
             return bytesRead;
         }
diff --git a/staticlibs/device/com/android/net/module/util/PrivateAddressCoordinator.java b/staticlibs/device/com/android/net/module/util/PrivateAddressCoordinator.java
index bb95585..2ce5b86 100644
--- a/staticlibs/device/com/android/net/module/util/PrivateAddressCoordinator.java
+++ b/staticlibs/device/com/android/net/module/util/PrivateAddressCoordinator.java
@@ -33,12 +33,14 @@
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
+import android.os.Build;
 import android.os.RemoteException;
 import android.util.ArrayMap;
 
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.build.SdkLevel;
 
 import java.io.PrintWriter;
 import java.net.Inet4Address;
@@ -67,9 +69,6 @@
     // WARNING: Keep in sync with chooseDownstreamAddress
     public static final int PREFIX_LENGTH = 24;
 
-    public static final String TETHER_FORCE_RANDOM_PREFIX_BASE_SELECTION =
-            "tether_force_random_prefix_base_selection";
-
     // Upstream monitor would be stopped when tethering is down. When tethering restart, downstream
     // address may be requested before coordinator get current upstream notification. To ensure
     // coordinator do not select conflict downstream prefix, mUpstreamPrefixMap would not be cleared
@@ -258,8 +257,15 @@
         return null;
     }
 
+    // TODO: Remove this method when SdkLevel.isAtLeastB() is fixed, aosp is at sdk level 36 or use
+    //  NetworkStackUtils.isAtLeast25Q2 when it is moved to a static lib.
+    public static boolean isAtLeast25Q2() {
+        return SdkLevel.isAtLeastB()  || (SdkLevel.isAtLeastV()
+                && "Baklava".equals(Build.VERSION.CODENAME));
+    }
+
     private int getRandomPrefixIndex() {
-        if (!mDeps.isFeatureEnabled(TETHER_FORCE_RANDOM_PREFIX_BASE_SELECTION)) return 0;
+        if (!isAtLeast25Q2()) return 0;
 
         final int random = getRandomInt() & 0xffffff;
         // This is to select the starting prefix range (/8, /12, or /16) instead of the actual
diff --git a/staticlibs/device/com/android/net/module/util/netlink/InetDiagMessage.java b/staticlibs/device/com/android/net/module/util/netlink/InetDiagMessage.java
index fecaa09..c9a89ec 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/InetDiagMessage.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/InetDiagMessage.java
@@ -309,16 +309,18 @@
     }
 
     private static void sendNetlinkDestroyRequest(FileDescriptor fd, int proto,
-            InetDiagMessage diagMsg) throws InterruptedIOException, ErrnoException {
+            StructInetDiagSockId id, short family, int state)
+            throws InterruptedIOException, ErrnoException {
+        // TODO: Investigate if it's fine to always set 0 to state and remove state from the arg
         final byte[] destroyMsg = InetDiagMessage.inetDiagReqV2(
                 proto,
-                diagMsg.inetDiagMsg.id,
-                diagMsg.inetDiagMsg.idiag_family,
+                id,
+                family,
                 SOCK_DESTROY,
                 (short) (StructNlMsgHdr.NLM_F_REQUEST | StructNlMsgHdr.NLM_F_ACK),
                 0 /* pad */,
                 0 /* idiagExt */,
-                1 << diagMsg.inetDiagMsg.idiag_state
+                state
         );
         NetlinkUtils.sendMessage(fd, destroyMsg, 0, destroyMsg.length, IO_TIMEOUT_MS);
         NetlinkUtils.receiveNetlinkAck(fd);
@@ -343,7 +345,8 @@
         Consumer<InetDiagMessage> handleNlDumpMsg = (diagMsg) -> {
             if (filter.test(diagMsg)) {
                 try {
-                    sendNetlinkDestroyRequest(destroyFd, proto, diagMsg);
+                    sendNetlinkDestroyRequest(destroyFd, proto, diagMsg.inetDiagMsg.id,
+                            diagMsg.inetDiagMsg.idiag_family, 1 << diagMsg.inetDiagMsg.idiag_state);
                     destroyedSockets.getAndIncrement();
                 } catch (InterruptedIOException | ErrnoException e) {
                     if (!(e instanceof ErrnoException
@@ -484,6 +487,30 @@
         Log.d(TAG, "Destroyed live tcp sockets for uids=" + ownerUids + " in " + durationMs + "ms");
     }
 
+    /**
+     * Close the udp socket which can be uniquely identified with the cookie and other information.
+     */
+    public static void destroyUdpSocket(final InetSocketAddress src, final InetSocketAddress dst,
+            final int ifIndex, final long cookie)
+            throws ErrnoException, SocketException, InterruptedIOException {
+        FileDescriptor fd = null;
+
+        try {
+            fd = NetlinkUtils.createNetLinkInetDiagSocket();
+            connectToKernel(fd);
+            final int family = (src.getAddress() instanceof Inet6Address) ? AF_INET6 : AF_INET;
+            final StructInetDiagSockId id = new StructInetDiagSockId(
+                    src,
+                    dst,
+                    ifIndex,
+                    cookie
+            );
+            sendNetlinkDestroyRequest(fd, IPPROTO_UDP, id, (short) family, 0 /* state */);
+        } finally {
+            closeSocketQuietly(fd);
+        }
+    }
+
     @Override
     public String toString() {
         return "InetDiagMessage{ "
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/PollingUtils.kt b/staticlibs/testutils/devicetests/com/android/testutils/PollingUtils.kt
new file mode 100644
index 0000000..a6e7ead
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/PollingUtils.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2025 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
+
+private const val POLLING_INTERVAL_MS: Int = 100
+
+/** Calls condition() until it returns true or timeout occurs. */
+fun pollingCheck(timeout_ms: Long, condition: () -> Boolean): Boolean {
+    var polling_time = 0
+    do {
+        Thread.sleep(POLLING_INTERVAL_MS.toLong())
+        polling_time += POLLING_INTERVAL_MS
+        if (condition()) return true
+    } while (polling_time < timeout_ms)
+    return false
+}
diff --git a/tests/common/java/android/net/NetworkAgentConfigTest.kt b/tests/common/java/android/net/NetworkAgentConfigTest.kt
index fe869f8..d640a73 100644
--- a/tests/common/java/android/net/NetworkAgentConfigTest.kt
+++ b/tests/common/java/android/net/NetworkAgentConfigTest.kt
@@ -20,7 +20,6 @@
 import androidx.test.runner.AndroidJUnit4
 import com.android.modules.utils.build.SdkLevel.isAtLeastS
 import com.android.modules.utils.build.SdkLevel.isAtLeastT
-import com.android.modules.utils.build.SdkLevel.isAtLeastV
 import com.android.testutils.ConnectivityModuleTest
 import com.android.testutils.assertParcelingIsLossless
 import org.junit.Assert.assertEquals
@@ -48,9 +47,6 @@
                 setLocalRoutesExcludedForVpn(true)
                 setVpnRequiresValidation(true)
             }
-            if (isAtLeastV()) {
-                setSkipNativeNetworkCreation(true)
-            }
         }.build()
         assertParcelingIsLossless(config)
     }
@@ -75,9 +71,6 @@
                 setLocalRoutesExcludedForVpn(true)
                 setVpnRequiresValidation(true)
             }
-            if (isAtLeastV()) {
-                setSkipNativeNetworkCreation(true)
-            }
         }.build()
 
         assertTrue(config.isExplicitlySelected())
@@ -86,9 +79,6 @@
         assertFalse(config.isPartialConnectivityAcceptable())
         assertTrue(config.isUnvalidatedConnectivityAcceptable())
         assertEquals("TEST_NETWORK", config.getLegacyTypeName())
-        if (isAtLeastV()) {
-            assertTrue(config.shouldSkipNativeNetworkCreation())
-        }
         if (isAtLeastT()) {
             assertTrue(config.areLocalRoutesExcludedForVpn())
             assertTrue(config.isVpnValidationRequired())
diff --git a/tests/cts/hostside/Android.bp b/tests/cts/hostside/Android.bp
index 0ac9ce1..0b4375a 100644
--- a/tests/cts/hostside/Android.bp
+++ b/tests/cts/hostside/Android.bp
@@ -27,7 +27,7 @@
     // 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
+    // something like "VanillaIceCream" instead of 10000. 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",
diff --git a/tests/cts/multidevices/Android.bp b/tests/cts/multidevices/Android.bp
index a082a95..c730b86 100644
--- a/tests/cts/multidevices/Android.bp
+++ b/tests/cts/multidevices/Android.bp
@@ -42,9 +42,4 @@
         // Package the snippet with the mobly test
         ":connectivity_multi_devices_snippet",
     ],
-    version: {
-        py3: {
-            embedded_launcher: true,
-        },
-    },
 }
diff --git a/tests/cts/multidevices/snippet/Wifip2pMultiDevicesSnippet.kt b/tests/cts/multidevices/snippet/Wifip2pMultiDevicesSnippet.kt
index f8c9351..3816537 100644
--- a/tests/cts/multidevices/snippet/Wifip2pMultiDevicesSnippet.kt
+++ b/tests/cts/multidevices/snippet/Wifip2pMultiDevicesSnippet.kt
@@ -21,8 +21,8 @@
 import android.content.Context
 import android.content.Intent
 import android.content.IntentFilter
+import android.content.pm.PackageManager.FEATURE_WIFI_DIRECT
 import android.net.MacAddress
-import android.net.wifi.WifiManager
 import android.net.wifi.p2p.WifiP2pConfig
 import android.net.wifi.p2p.WifiP2pDevice
 import android.net.wifi.p2p.WifiP2pDeviceList
@@ -44,10 +44,6 @@
 
 class Wifip2pMultiDevicesSnippet : Snippet {
     private val context by lazy { InstrumentationRegistry.getInstrumentation().getTargetContext() }
-    private val wifiManager by lazy {
-        context.getSystemService(WifiManager::class.java)
-                ?: fail("Could not get WifiManager service")
-    }
     private val wifip2pManager by lazy {
         context.getSystemService(WifiP2pManager::class.java)
                 ?: fail("Could not get WifiP2pManager service")
@@ -84,7 +80,7 @@
     }
 
     @Rpc(description = "Check whether the device supports Wi-Fi P2P.")
-    fun isP2pSupported() = wifiManager.isP2pSupported()
+    fun isP2pSupported() = context.packageManager.hasSystemFeature(FEATURE_WIFI_DIRECT)
 
     @Rpc(description = "Start Wi-Fi P2P")
     fun startWifiP2p() {
diff --git a/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt b/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
index c6a1b09..2a372ce 100644
--- a/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
+++ b/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
@@ -86,6 +86,7 @@
 import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
 import com.android.testutils.SkipPresubmit
 import com.android.testutils.TestableNetworkCallback
+import com.android.testutils.pollingCheck
 import com.android.testutils.waitForIdle
 import com.google.common.truth.Expect
 import com.google.common.truth.Truth.assertThat
@@ -111,7 +112,6 @@
 
 private const val TAG = "ApfIntegrationTest"
 private const val TIMEOUT_MS = 2000L
-private const val POLLING_INTERVAL_MS: Int = 100
 private const val RCV_BUFFER_SIZE = 1480
 private const val PING_HEADER_LENGTH = 8
 
@@ -129,16 +129,6 @@
         private val powerManager = context.getSystemService(PowerManager::class.java)!!
         private val wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG)
 
-        fun pollingCheck(condition: () -> Boolean, timeout_ms: Int): Boolean {
-            var polling_time = 0
-            do {
-                Thread.sleep(POLLING_INTERVAL_MS.toLong())
-                polling_time += POLLING_INTERVAL_MS
-                if (condition()) return true
-            } while (polling_time < timeout_ms)
-            return false
-        }
-
         fun turnScreenOff() {
             if (!wakeLock.isHeld()) wakeLock.acquire()
             runShellCommandOrThrow("input keyevent KEYCODE_SLEEP")
@@ -160,7 +150,9 @@
                 // This is a workaround for b/366037029.
                 Thread.sleep(2000L)
             } else {
-                val result = pollingCheck({ powerManager.isInteractive() }, timeout_ms = 2000)
+                val result = pollingCheck(timeout_ms = 2000) {
+                    powerManager.isInteractive()
+                }
                 assertThat(result).isEqualTo(interactive)
             }
         }
@@ -575,6 +567,13 @@
 
         val program = gen.generate()
         assertThat(program.size).isLessThan(counterRegion)
+        val randomProgram = ByteArray(1) { 0 } +
+                ByteArray(counterRegion - 1).also { Random.nextBytes(it) }
+        // There are known firmware bugs where they calculate the number of non-zero bytes within
+        // the program to determine the program length. Modify the test to first install a longer
+        // program before installing a program that do the program length check. This should help us
+        // catch these types of firmware bugs in CTS. (b/395545572)
+        installAndVerifyProgram(randomProgram)
         installAndVerifyProgram(program)
 
         // Trigger the program by sending a ping and waiting on the reply.
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index aa7d618..87c2b9e 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -90,7 +90,6 @@
 import static android.net.NetworkCapabilities.TRANSPORT_VPN;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
-import static android.net.cts.util.CtsNetUtils.ConnectivityActionReceiver;
 import static android.net.cts.util.CtsNetUtils.HTTP_PORT;
 import static android.net.cts.util.CtsNetUtils.NETWORK_CALLBACK_ACTION;
 import static android.net.cts.util.CtsNetUtils.TEST_HOST;
@@ -111,6 +110,7 @@
 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.networkstack.apishim.ConstantsShim.RECEIVER_NOT_EXPORTED;
 import static com.android.testutils.Cleanup.testAndCleanup;
 import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
 import static com.android.testutils.MiscAsserts.assertEventuallyTrue;
@@ -178,6 +178,7 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.ConditionVariable;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.MessageQueue;
@@ -1229,42 +1230,43 @@
      * {@link #testRegisterNetworkCallback} except that a {@code PendingIntent} is used instead
      * of a {@code NetworkCallback}.
      */
-    @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
     @Test
+    // This test is flaky before aosp/3482151 which fixed the issue in the ConnectivityService
+    // code. Unfortunately this means T can't be fixed, so don't run this test with a module
+    // that hasn't been updated.
+    @ConnectivityModuleTest
     public void testRegisterNetworkCallback_withPendingIntent() {
-        assumeTrue(mPackageManager.hasSystemFeature(FEATURE_WIFI));
+        final ConditionVariable received = new ConditionVariable();
 
-        // Create a ConnectivityActionReceiver that has an IntentFilter for our locally defined
-        // action, NETWORK_CALLBACK_ACTION.
-        final IntentFilter filter = new IntentFilter();
-        filter.addAction(NETWORK_CALLBACK_ACTION);
+        // Register a callback with intent and a request for any Internet-providing network,
+        // which should match the currently connected network.
+        final BroadcastReceiver receiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(final Context context, final Intent intent) {
+                received.open();
+            }
+        };
 
-        final ConnectivityActionReceiver receiver = new ConnectivityActionReceiver(
-                mCm, ConnectivityManager.TYPE_WIFI, NetworkInfo.State.CONNECTED);
-        final int flags = SdkLevel.isAtLeastT() ? RECEIVER_EXPORTED : 0;
-        mContext.registerReceiver(receiver, filter, flags);
+        final int flags = SdkLevel.isAtLeastT() ? RECEIVER_NOT_EXPORTED : 0;
+        mContext.registerReceiver(receiver, new IntentFilter(NETWORK_CALLBACK_ACTION), flags);
 
         // Create a broadcast PendingIntent for NETWORK_CALLBACK_ACTION.
         final Intent intent = new Intent(NETWORK_CALLBACK_ACTION)
                 .setPackage(mContext.getPackageName());
-        // While ConnectivityService would put extra info such as network or request id before
-        // broadcasting the inner intent. The MUTABLE flag needs to be added accordingly.
         final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0 /*requestCode*/,
                 intent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE);
 
-        // We will register for a WIFI network being available or lost.
-        mCm.registerNetworkCallback(makeWifiNetworkRequest(), pendingIntent);
+        // Register for a network providing Internet being available or lost.
+        final NetworkRequest nr = new NetworkRequest.Builder()
+                .addCapability(NET_CAPABILITY_INTERNET)
+                .build();
+        mCm.registerNetworkCallback(nr, pendingIntent);
 
         try {
-            mCtsNetUtils.ensureWifiConnected();
-
-            // Now we expect to get the Intent delivered notifying of the availability of the wifi
-            // network even if it was already connected as a state-based action when the callback
-            // is registered.
-            assertTrue("Did not receive expected Intent " + intent + " for TRANSPORT_WIFI",
-                    receiver.waitForState());
-        } catch (InterruptedException e) {
-            fail("Broadcast receiver or NetworkCallback wait was interrupted.");
+            // Wait for delivery of the Intent notifying of the availability of the
+            // INTERNET-providing network. Test setup makes sure it's already connected.
+            assertTrue("Did not receive expected Intent " + intent + " for INTERNET",
+                    received.block(NETWORK_CALLBACK_TIMEOUT_MS));
         } finally {
             mCm.unregisterNetworkCallback(pendingIntent);
             pendingIntent.cancel();
@@ -1272,6 +1274,33 @@
         }
     }
 
+    // Up to R ConnectivityService can't be updated through mainline, and there was a bug
+    // where registering a callback with a canceled pending intent would crash the system.
+    @Test
+    // Running this test without aosp/3482151 will likely crash the device.
+    @ConnectivityModuleTest
+    @IgnoreUpTo(Build.VERSION_CODES.R)
+    public void testRegisterNetworkCallback_pendingIntent_classNotFound() {
+        final Intent intent = new Intent()
+                .setClassName(mContext.getPackageName(), "NonExistent");
+        final PendingIntent pi = PendingIntent.getActivity(mContext, /* requestCode */ 1,
+                intent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE);
+
+        final NetworkRequest nr = new NetworkRequest.Builder()
+                .addCapability(NET_CAPABILITY_INTERNET)
+                .build();
+        try {
+            // Before the fix delivered through Mainline, this used to crash the system, because
+            // trying to send the pending intent would throw which would prompt ConnectivityService
+            // to release the wake lock, but it would still send a finished notification at which
+            // point CS would try to release the wake lock again and crash.
+            mCm.registerNetworkCallback(nr, pi);
+        } finally {
+            mCm.unregisterNetworkCallback(pi);
+            pi.cancel();
+        }
+    }
+
     private void runIdenticalPendingIntentsRequestTest(boolean useListen) throws Exception {
         assumeTrue(mPackageManager.hasSystemFeature(FEATURE_WIFI));
 
@@ -1377,12 +1406,20 @@
     }
 
     @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+    // This test is flaky before aosp/3482151 which fixed the issue in the ConnectivityService
+    // code. Unfortunately this means T can't be fixed, so don't run this test with a module
+    // that hasn't been updated.
+    @ConnectivityModuleTest
     @Test
     public void testRegisterNetworkRequest_identicalPendingIntents() throws Exception {
         runIdenticalPendingIntentsRequestTest(false /* useListen */);
     }
 
     @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+    // This test is flaky before aosp/3482151 which fixed the issue in the ConnectivityService
+    // code. Unfortunately this means T can't be fixed, so don't run this test with a module
+    // that hasn't been updated.
+    @ConnectivityModuleTest
     @Test
     public void testRegisterNetworkCallback_identicalPendingIntents() throws Exception {
         runIdenticalPendingIntentsRequestTest(true /* useListen */);
diff --git a/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt b/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
index 1de4cf9..ceccf0b 100644
--- a/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
+++ b/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
@@ -44,6 +44,7 @@
 import android.net.RouteInfo
 import android.net.TestNetworkInterface
 import android.net.TestNetworkManager
+import android.net.TestNetworkManager.TestInterfaceRequest
 import android.net.cts.util.CtsNetUtils.TestNetworkCallback
 import android.os.HandlerThread
 import android.os.SystemClock
@@ -164,7 +165,11 @@
 
             // Only statically configure the IPv4 address; for IPv6, use the SLAAC generated
             // address.
-            iface = tnm.createTapInterface(arrayOf(LinkAddress(LOCAL_IPV4_ADDRESS, IP4_PREFIX_LEN)))
+            val req = TestInterfaceRequest.Builder()
+                    .setTap()
+                    .addLinkAddress(LinkAddress(LOCAL_IPV4_ADDRESS, IP4_PREFIX_LEN))
+                    .build()
+            iface = tnm.createTestInterface(req)
             assertNotNull(iface)
         }
 
diff --git a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
index 06f2075..9f32132 100644
--- a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
@@ -51,6 +51,7 @@
 import android.net.StaticIpConfiguration
 import android.net.TestNetworkInterface
 import android.net.TestNetworkManager
+import android.net.TestNetworkManager.TestInterfaceRequest
 import android.net.cts.EthernetManagerTest.EthernetStateListener.CallbackEntry.EthernetStateChanged
 import android.net.cts.EthernetManagerTest.EthernetStateListener.CallbackEntry.InterfaceStateChanged
 import android.os.Build
@@ -169,7 +170,12 @@
                 // false, it is subsequently disabled. This means that the interface may briefly get
                 // link. With IPv6 provisioning delays (RS delay and DAD) disabled, this can cause
                 // tests that expect no network to come up when hasCarrier is false to become flaky.
-                tnm.createTapInterface(hasCarrier, false /* bringUp */)
+                val req = TestInterfaceRequest.Builder()
+                        .setTap()
+                        .setHasCarrier(hasCarrier)
+                        .setBringUp(false)
+                        .build()
+                tnm.createTestInterface(req)
             }
             val mtu = tapInterface.mtu
             packetReader = PollPacketReader(
diff --git a/tests/cts/net/src/android/net/cts/NetworkReservationTest.kt b/tests/cts/net/src/android/net/cts/NetworkReservationTest.kt
index a9af34f..f43b927 100644
--- a/tests/cts/net/src/android/net/cts/NetworkReservationTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkReservationTest.kt
@@ -19,6 +19,8 @@
 import android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS
 import android.Manifest.permission.MANAGE_TEST_NETWORKS
 import android.Manifest.permission.NETWORK_SETTINGS
+import android.bluetooth.BluetoothManager
+import android.content.pm.PackageManager.FEATURE_BLUETOOTH_LE
 import android.net.ConnectivityManager
 import android.net.L2capNetworkSpecifier
 import android.net.L2capNetworkSpecifier.HEADER_COMPRESSION_6LOWPAN
@@ -41,6 +43,7 @@
 import android.os.HandlerThread
 import android.platform.test.annotations.AppModeFull
 import androidx.test.platform.app.InstrumentationRegistry
+import com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow
 import com.android.testutils.ConnectivityModuleTest
 import com.android.testutils.DevSdkIgnoreRule
 import com.android.testutils.DevSdkIgnoreRunner
@@ -48,12 +51,15 @@
 import com.android.testutils.RecorderCallback.CallbackEntry.Unavailable
 import com.android.testutils.TestableNetworkCallback
 import com.android.testutils.TestableNetworkOfferCallback
+import com.android.testutils.pollingCheck
 import com.android.testutils.runAsShell
 import kotlin.test.assertContains
 import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
 import kotlin.test.assertNull
 import kotlin.test.assertTrue
 import org.junit.After
+import org.junit.Assume.assumeTrue
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -93,6 +99,8 @@
     private val provider = NetworkProvider(context, handlerThread.looper, TAG)
 
     private val registeredCallbacks = ArrayList<TestableNetworkCallback>()
+    private val bm = context.getSystemService(BluetoothManager::class.java)!!
+    private var disableBluetoothInTearDown = false
 
     @Before
     fun setUp() {
@@ -101,6 +109,34 @@
         }
     }
 
+    private fun enableBluetooth() {
+        val adapter = bm.adapter
+        assertNotNull(adapter)
+        if (adapter.isEnabled()) return
+
+        runShellCommandOrThrow("svc bluetooth enable")
+        val bluetoothEnabled = pollingCheck(TIMEOUT_MS) {
+            adapter.isEnabled()
+        }
+        assertTrue(bluetoothEnabled)
+        // Only disable Bluetooth in tear down when it hasn't already been enabled.
+        disableBluetoothInTearDown = true
+    }
+
+    private fun disableBluetooth() {
+        // adapter can't actually be null here, because this function does not run unless
+        // disableBluetoothInTearDown is true. Just in case, refrain from throwing an exception in
+        // tearDown.
+        val adapter = bm.adapter
+        if (adapter == null) return
+
+        runShellCommandOrThrow("svc bluetooth disable")
+        // Wait for #isEnabled() to return false; ignore failures.
+        pollingCheck(TIMEOUT_MS) {
+            !adapter.isEnabled()
+        }
+    }
+
     @After
     fun tearDown() {
         registeredCallbacks.forEach { cm.unregisterNetworkCallback(it) }
@@ -110,6 +146,10 @@
         }
         handlerThread.quitSafely()
         handlerThread.join()
+
+        if (disableBluetoothInTearDown) {
+            disableBluetooth()
+        }
     }
 
     fun NetworkCapabilities.copyWithReservationId(resId: Int) = NetworkCapabilities(this).also {
@@ -158,6 +198,9 @@
 
     @Test
     fun testReserveL2capNetwork() {
+        assumeTrue(context.packageManager.hasSystemFeature(FEATURE_BLUETOOTH_LE))
+        enableBluetooth()
+
         val l2capReservationSpecifier = L2capNetworkSpecifier.Builder()
                 .setRole(ROLE_SERVER)
                 .setHeaderCompression(HEADER_COMPRESSION_6LOWPAN)
diff --git a/tests/cts/net/util/java/android/net/cts/util/CtsTetheringUtils.java b/tests/cts/net/util/java/android/net/cts/util/CtsTetheringUtils.java
index 75b2814..27cba3a 100644
--- a/tests/cts/net/util/java/android/net/cts/util/CtsTetheringUtils.java
+++ b/tests/cts/net/util/java/android/net/cts/util/CtsTetheringUtils.java
@@ -160,11 +160,15 @@
         @Override
         public void onStopTetheringSucceeded() {
             mHistory.add(new CallbackValue.OnStopTetheringSucceeded());
+            // Call the parent method so that the coverage linter sees it: http://b/385014495
+            TetheringManager.StopTetheringCallback.super.onStopTetheringSucceeded();
         }
 
         @Override
         public void onStopTetheringFailed(final int error) {
             mHistory.add(new CallbackValue.OnStopTetheringFailed(error));
+            // Call the parent method so that the coverage linter sees it: http://b/385014495
+            TetheringManager.StopTetheringCallback.super.onStopTetheringFailed(error);
         }
 
         /**
diff --git a/tests/unit/java/com/android/server/BpfNetMapsTest.java b/tests/unit/java/com/android/server/BpfNetMapsTest.java
index fd92672..caf1765 100644
--- a/tests/unit/java/com/android/server/BpfNetMapsTest.java
+++ b/tests/unit/java/com/android/server/BpfNetMapsTest.java
@@ -266,6 +266,18 @@
 
     @Test
     @IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    public void testAddLocalNetAccessWithNullInterfaceAfterV() throws Exception {
+        assertTrue(mLocalNetAccessMap.isEmpty());
+
+        mBpfNetMaps.addLocalNetAccess(160, null,
+                Inet4Address.getByName("196.68.0.0"), 0, 0, true);
+
+        // As we tried to add null interface, it would be skipped and map should be empty.
+        assertTrue(mLocalNetAccessMap.isEmpty());
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
     public void testAddLocalNetAccessAfterVWithIncorrectInterface() throws Exception {
         assertTrue(mLocalNetAccessMap.isEmpty());
 
@@ -303,6 +315,13 @@
     }
 
     @Test
+    @IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    public void testGetLocalNetAccessWithNullInterfaceAfterV() throws Exception {
+        assertTrue(mBpfNetMaps.getLocalNetAccess(160, null,
+                Inet4Address.getByName("100.68.0.0"), 0, 0));
+    }
+
+    @Test
     @IgnoreAfter(Build.VERSION_CODES.VANILLA_ICE_CREAM)
     public void testRemoveLocalNetAccessBeforeV() {
         assertThrows(UnsupportedOperationException.class, () ->
@@ -350,6 +369,25 @@
     }
 
     @Test
+    @IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    public void testRemoveLocalNetAccessAfterVWithNullInterface() throws Exception {
+        assertTrue(mLocalNetAccessMap.isEmpty());
+
+        mBpfNetMaps.addLocalNetAccess(160, TEST_IF_NAME,
+                Inet4Address.getByName("196.68.0.0"), 0, 0, true);
+
+        assertNotNull(mLocalNetAccessMap.getValue(new LocalNetAccessKey(160, TEST_IF_INDEX,
+                Inet4Address.getByName("196.68.0.0"), 0, 0)));
+        assertNull(mLocalNetAccessMap.getValue(new LocalNetAccessKey(160, TEST_IF_INDEX,
+                Inet4Address.getByName("100.68.0.0"), 0, 0)));
+
+        mBpfNetMaps.removeLocalNetAccess(160, null,
+                Inet4Address.getByName("196.68.0.0"), 0, 0);
+        assertNotNull(mLocalNetAccessMap.getValue(new LocalNetAccessKey(160, TEST_IF_INDEX,
+                Inet4Address.getByName("196.68.0.0"), 0, 0)));
+    }
+
+    @Test
     @IgnoreAfter(Build.VERSION_CODES.VANILLA_ICE_CREAM)
     public void testAddUidToLocalNetBlockMapBeforeV() {
         assertThrows(UnsupportedOperationException.class, () ->
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index b7aa387..19a41d8 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -154,7 +154,6 @@
 import static android.net.Proxy.PROXY_CHANGE_ACTION;
 import static android.net.RouteInfo.RTN_UNREACHABLE;
 import static android.net.connectivity.ConnectivityCompatChanges.NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION;
-import static android.net.connectivity.ConnectivityCompatChanges.RESTRICT_LOCAL_NETWORK;
 import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.PREFIX_OPERATION_ADDED;
 import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.PREFIX_OPERATION_REMOVED;
 import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_FAILURE;
@@ -164,10 +163,7 @@
 import static android.telephony.DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH;
 import static android.telephony.DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
 
-import static com.android.server.ConnectivityService.ALLOW_SATALLITE_NETWORK_FALLBACK;
 import static com.android.net.module.util.DeviceConfigUtils.TETHERING_MODULE_NAME;
-import static com.android.server.ConnectivityService.ALLOW_SYSUI_CONNECTIVITY_REPORTS;
-import static com.android.server.ConnectivityService.KEY_DESTROY_FROZEN_SOCKETS_VERSION;
 import static com.android.server.ConnectivityService.MAX_NETWORK_REQUESTS_PER_SYSTEM_UID;
 import static com.android.server.ConnectivityService.PREFERENCE_ORDER_MOBILE_DATA_PREFERERRED;
 import static com.android.server.ConnectivityService.PREFERENCE_ORDER_OEM;
@@ -178,9 +174,6 @@
 import static com.android.server.ConnectivityServiceTestUtils.transportToLegacyType;
 import static com.android.server.NetworkAgentWrapper.CallbackType.OnQosCallbackRegister;
 import static com.android.server.NetworkAgentWrapper.CallbackType.OnQosCallbackUnregister;
-import static com.android.server.connectivity.ConnectivityFlags.BACKGROUND_FIREWALL_CHAIN;
-import static com.android.server.connectivity.ConnectivityFlags.DELAY_DESTROY_SOCKETS;
-import static com.android.server.connectivity.ConnectivityFlags.INGRESS_TO_VPN_ADDRESS_FILTERING;
 import static com.android.testutils.Cleanup.testAndCleanup;
 import static com.android.testutils.ConcurrentUtils.await;
 import static com.android.testutils.ConcurrentUtils.durationOf;
@@ -258,7 +251,6 @@
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.admin.DevicePolicyManager;
-import android.app.compat.CompatChanges;
 import android.app.usage.NetworkStatsManager;
 import android.compat.testing.PlatformCompatChangeRule;
 import android.content.BroadcastReceiver;
@@ -2187,28 +2179,30 @@
                 case ConnectivityFlags.REQUEST_RESTRICTED_WIFI:
                 case ConnectivityFlags.USE_DECLARED_METHODS_FOR_CALLBACKS:
                 case ConnectivityFlags.QUEUE_CALLBACKS_FOR_FROZEN_APPS:
-                case KEY_DESTROY_FROZEN_SOCKETS_VERSION:
+                case ConnectivityFlags.BACKGROUND_FIREWALL_CHAIN:
+                case ConnectivityService.KEY_DESTROY_FROZEN_SOCKETS_VERSION:
                     return true;
                 default:
-                    return super.isFeatureEnabled(context, name);
+                    // This is a unit test and must never depend on actual device flag values.
+                    throw new UnsupportedOperationException("Unknown flag " + name
+                            + ", update this test");
             }
         }
 
         @Override
         public boolean isFeatureNotChickenedOut(Context context, String name) {
             switch (name) {
-                case ALLOW_SYSUI_CONNECTIVITY_REPORTS:
-                    return true;
-                case ALLOW_SATALLITE_NETWORK_FALLBACK:
-                    return true;
-                case INGRESS_TO_VPN_ADDRESS_FILTERING:
-                    return true;
-                case BACKGROUND_FIREWALL_CHAIN:
-                    return true;
-                case DELAY_DESTROY_SOCKETS:
+                case ConnectivityService.ALLOW_SYSUI_CONNECTIVITY_REPORTS:
+                case ConnectivityService.ALLOW_SATALLITE_NETWORK_FALLBACK:
+                case ConnectivityFlags.INGRESS_TO_VPN_ADDRESS_FILTERING:
+                case ConnectivityFlags.BACKGROUND_FIREWALL_CHAIN:
+                case ConnectivityFlags.DELAY_DESTROY_SOCKETS:
+                case ConnectivityFlags.USE_DECLARED_METHODS_FOR_CALLBACKS:
+                case ConnectivityFlags.QUEUE_CALLBACKS_FOR_FROZEN_APPS:
                     return true;
                 default:
-                    return super.isFeatureNotChickenedOut(context, name);
+                    throw new UnsupportedOperationException("Unknown flag " + name
+                            + ", update this test");
             }
         }
 
@@ -2449,6 +2443,10 @@
 
     @After
     public void tearDown() throws Exception {
+        // Don't attempt to tear down if setUp didn't even get as far as creating the service.
+        // Otherwise, exceptions here will mask the actual exception in setUp, making failures
+        // harder to diagnose.
+        if (mService == null) return;
         unregisterDefaultNetworkCallbacks();
         maybeTearDownEnterpriseNetwork();
         setAlwaysOnNetworks(false);
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java
index 1cc9985..f763bae 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java
@@ -610,6 +610,7 @@
 
     @Test
     public void testSocketCreatedForMulticastInterface() throws Exception {
+        doReturn(true).when(mTestNetworkIfaceWrapper).isPointToPoint();
         doReturn(true).when(mTestNetworkIfaceWrapper).supportsMulticast();
         startMonitoringSockets();
 
@@ -621,18 +622,6 @@
     }
 
     @Test
-    public void testNoSocketCreatedForPTPInterface() throws Exception {
-        doReturn(true).when(mTestNetworkIfaceWrapper).isPointToPoint();
-        startMonitoringSockets();
-
-        final TestSocketCallback testCallback = new TestSocketCallback();
-        runOnHandler(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
-
-        postNetworkAvailable(TRANSPORT_BLUETOOTH);
-        testCallback.expectedNoCallback();
-    }
-
-    @Test
     public void testNoSocketCreatedForVPNInterface() throws Exception {
         // VPN interfaces generally also have IFF_POINTOPOINT, but even if they don't, they should
         // not be included even with TRANSPORT_WIFI.
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSL2capProviderTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSL2capProviderTest.kt
index 489c3ad..babcba9 100644
--- a/tests/unit/java/com/android/server/connectivityservice/CSL2capProviderTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/CSL2capProviderTest.kt
@@ -17,25 +17,37 @@
 package com.android.server
 
 import android.bluetooth.BluetoothAdapter
-import android.bluetooth.BluetoothManager
+import android.bluetooth.BluetoothDevice
 import android.bluetooth.BluetoothServerSocket
 import android.bluetooth.BluetoothSocket
+import android.net.INetworkMonitor
+import android.net.INetworkMonitorCallbacks
+import android.net.IpPrefix
 import android.net.L2capNetworkSpecifier
 import android.net.L2capNetworkSpecifier.HEADER_COMPRESSION_6LOWPAN
 import android.net.L2capNetworkSpecifier.HEADER_COMPRESSION_NONE
+import android.net.L2capNetworkSpecifier.ROLE_CLIENT
 import android.net.L2capNetworkSpecifier.ROLE_SERVER
+import android.net.LinkAddress
+import android.net.LinkProperties
+import android.net.MacAddress
 import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED
 import android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED
 import android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH
 import android.net.NetworkRequest
 import android.net.NetworkSpecifier
+import android.net.RouteInfo
 import android.os.Build
 import android.os.HandlerThread
+import android.os.ParcelFileDescriptor
+import com.android.server.net.L2capNetwork.L2capIpClient
+import com.android.server.net.L2capPacketForwarder
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
 import com.android.testutils.DevSdkIgnoreRunner
 import com.android.testutils.RecorderCallback.CallbackEntry.Reserved
 import com.android.testutils.RecorderCallback.CallbackEntry.Unavailable
 import com.android.testutils.TestableNetworkCallback
+import com.android.testutils.anyNetwork
 import com.android.testutils.waitForIdle
 import java.io.IOException
 import java.util.Optional
@@ -47,10 +59,13 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.ArgumentMatchers.isNull
 import org.mockito.Mockito.doAnswer
 import org.mockito.Mockito.doReturn
 import org.mockito.Mockito.doThrow
 import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
 
 private const val PSM = 0x85
 private val REMOTE_MAC = byteArrayOf(1, 2, 3, 4, 5, 6)
@@ -64,10 +79,17 @@
 @IgnoreUpTo(Build.VERSION_CODES.R)
 @DevSdkIgnoreRunner.MonitorThreadLeak
 class CSL2capProviderTest : CSTest() {
+    private val networkMonitor = mock<INetworkMonitor>()
+
     private val btAdapter = mock<BluetoothAdapter>()
+    private val btDevice = mock<BluetoothDevice>()
     private val btServerSocket = mock<BluetoothServerSocket>()
     private val btSocket = mock<BluetoothSocket>()
+    private val tunInterface = mock<ParcelFileDescriptor>()
+    private val l2capIpClient = mock<L2capIpClient>()
+    private val packetForwarder = mock<L2capPacketForwarder>()
     private val providerDeps = mock<L2capNetworkProvider.Dependencies>()
+
     // BlockingQueue does not support put(null) operations, as null is used as an internal sentinel
     // value. Therefore, use Optional<BluetoothSocket> where an empty optional signals the
     // BluetoothServerSocket#close() operation.
@@ -84,6 +106,8 @@
         doReturn(btAdapter).`when`(bluetoothManager).getAdapter()
         doReturn(btServerSocket).`when`(btAdapter).listenUsingInsecureL2capChannel()
         doReturn(PSM).`when`(btServerSocket).getPsm()
+        doReturn(btDevice).`when`(btAdapter).getRemoteDevice(eq(REMOTE_MAC))
+        doReturn(btSocket).`when`(btDevice).createInsecureL2capChannel(eq(PSM))
 
         doAnswer {
             val sock = acceptQueue.take()
@@ -96,6 +120,30 @@
         }.`when`(btServerSocket).close()
 
         doReturn(handlerThread).`when`(providerDeps).getHandlerThread()
+        doReturn(tunInterface).`when`(providerDeps).createTunInterface(any())
+        doReturn(packetForwarder).`when`(providerDeps)
+                .createL2capPacketForwarder(any(), any(), any(), any(), any())
+        doReturn(l2capIpClient).`when`(providerDeps).createL2capIpClient(any(), any(), any())
+
+        val lp = LinkProperties()
+        val ifname = "l2cap-tun0"
+        lp.setInterfaceName(ifname)
+        lp.addLinkAddress(LinkAddress("fe80::1/64"))
+        lp.addRoute(RouteInfo(IpPrefix("fe80::/64"), null /* nextHop */, ifname))
+        doReturn(lp).`when`(l2capIpClient).start()
+
+        // Note: In order to properly register a NetworkAgent, a NetworkMonitor must be created for
+        // the agent. CSAgentWrapper already does some of this, but requires adding additional
+        // Dependencies to the production code. Create a mocked NM inside this test instead.
+        doAnswer { i ->
+            val cb = i.arguments[2] as INetworkMonitorCallbacks
+            cb.onNetworkMonitorCreated(networkMonitor)
+        }.`when`(networkStack).makeNetworkMonitor(
+                any() /* network */,
+                isNull() /* name */,
+                any() /* callbacks */
+        )
+
         provider = L2capNetworkProvider(providerDeps, context)
         provider.start()
     }
@@ -241,4 +289,109 @@
         cb2.expect<Reserved>()
         cb2.assertNoCallback()
     }
+
+    @Test
+    fun testServerNetwork() {
+        val specifier = L2capNetworkSpecifier.Builder()
+                .setRole(ROLE_SERVER)
+                .setHeaderCompression(HEADER_COMPRESSION_6LOWPAN)
+                .build()
+        val nr = REQUEST.copyWithSpecifier(specifier)
+        val cb = reserveNetwork(nr)
+        cb.expect<Reserved>()
+
+        // Unblock BluetoothServerSocket#accept()
+        doReturn(true).`when`(btSocket).isConnected()
+        acceptQueue.put(Optional.of(btSocket))
+
+        cb.expectAvailableCallbacks(anyNetwork(), validated = false)
+        cb.assertNoCallback()
+        // Verify that packet forwarding was started.
+        // TODO: stop mocking L2capPacketForwarder.
+        verify(providerDeps).createL2capPacketForwarder(any(), any(), any(), any(), any())
+    }
+
+    @Test
+    fun testBluetoothException_createInsecureL2capChannelThrows() {
+        doThrow(IOException()).`when`(btDevice).createInsecureL2capChannel(any())
+
+        val specifier = L2capNetworkSpecifier.Builder()
+                .setRole(ROLE_CLIENT)
+                .setHeaderCompression(HEADER_COMPRESSION_NONE)
+                .setRemoteAddress(MacAddress.fromBytes(REMOTE_MAC))
+                .setPsm(PSM)
+                .build()
+        val nr = REQUEST.copyWithSpecifier(specifier)
+        val cb = requestNetwork(nr)
+
+        cb.expect<Unavailable>()
+    }
+
+    @Test
+    fun testBluetoothException_bluetoothSocketConnectThrows() {
+        doThrow(IOException()).`when`(btSocket).connect()
+
+        val specifier = L2capNetworkSpecifier.Builder()
+                .setRole(ROLE_CLIENT)
+                .setHeaderCompression(HEADER_COMPRESSION_NONE)
+                .setRemoteAddress(MacAddress.fromBytes(REMOTE_MAC))
+                .setPsm(PSM)
+                .build()
+        val nr = REQUEST.copyWithSpecifier(specifier)
+        val cb = requestNetwork(nr)
+
+        cb.expect<Unavailable>()
+    }
+
+    @Test
+    fun testClientNetwork() {
+        val specifier = L2capNetworkSpecifier.Builder()
+                .setRole(ROLE_CLIENT)
+                .setHeaderCompression(HEADER_COMPRESSION_NONE)
+                .setRemoteAddress(MacAddress.fromBytes(REMOTE_MAC))
+                .setPsm(PSM)
+                .build()
+        val nr = REQUEST.copyWithSpecifier(specifier)
+        val cb = requestNetwork(nr)
+        cb.expectAvailableCallbacks(anyNetwork(), validated = false)
+    }
+
+    @Test
+    fun testClientNetwork_headerCompressionMismatch() {
+        var specifier = L2capNetworkSpecifier.Builder()
+                .setRole(ROLE_CLIENT)
+                .setHeaderCompression(HEADER_COMPRESSION_NONE)
+                .setRemoteAddress(MacAddress.fromBytes(REMOTE_MAC))
+                .setPsm(PSM)
+                .build()
+        var nr = REQUEST.copyWithSpecifier(specifier)
+        val cb = requestNetwork(nr)
+        cb.expectAvailableCallbacks(anyNetwork(), validated = false)
+
+        specifier = L2capNetworkSpecifier.Builder()
+                .setRole(ROLE_CLIENT)
+                .setHeaderCompression(HEADER_COMPRESSION_6LOWPAN)
+                .setRemoteAddress(MacAddress.fromBytes(REMOTE_MAC))
+                .setPsm(PSM)
+                .build()
+        nr = REQUEST.copyWithSpecifier(specifier)
+        val cb2 = requestNetwork(nr)
+        cb2.expect<Unavailable>()
+    }
+
+    @Test
+    fun testClientNetwork_multipleRequests() {
+        val specifier = L2capNetworkSpecifier.Builder()
+                .setRole(ROLE_CLIENT)
+                .setHeaderCompression(HEADER_COMPRESSION_NONE)
+                .setRemoteAddress(MacAddress.fromBytes(REMOTE_MAC))
+                .setPsm(PSM)
+                .build()
+        val nr = REQUEST.copyWithSpecifier(specifier)
+        val cb = requestNetwork(nr)
+        cb.expectAvailableCallbacks(anyNetwork(), validated = false)
+
+        val cb2 = requestNetwork(nr)
+        cb2.expectAvailableCallbacks(anyNetwork(), validated = false)
+    }
 }
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSLocalNetworkProtectionTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSLocalNetworkProtectionTest.kt
index 5bf6e04..84c9835 100644
--- a/tests/unit/java/com/android/server/connectivityservice/CSLocalNetworkProtectionTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/CSLocalNetworkProtectionTest.kt
@@ -38,6 +38,7 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mockito.never
+import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 
 private const val LONG_TIMEOUT_MS = 5_000
@@ -45,6 +46,7 @@
 private const val PREFIX_LENGTH_IPV6 = 32
 private const val WIFI_IFNAME = "wlan0"
 private const val WIFI_IFNAME_2 = "wlan1"
+private const val WIFI_IFNAME_3 = "wlan2"
 
 private val wifiNc = NetworkCapabilities.Builder()
         .addTransportType(TRANSPORT_WIFI)
@@ -78,6 +80,20 @@
         LOCAL_IPV6_IP_ADDRESS_PREFIX.getPrefixLength()
     )
 
+    private val LOCAL_IPV6_IP_ADDRESS_2_PREFIX =
+            IpPrefix("2601:19b:67f:e220:1cf1:35ff:fe8c:db87/64")
+    private val LOCAL_IPV6_LINK_ADDRESS_2 = LinkAddress(
+            LOCAL_IPV6_IP_ADDRESS_2_PREFIX.getAddress(),
+            LOCAL_IPV6_IP_ADDRESS_2_PREFIX.getPrefixLength()
+    )
+
+    private val LOCAL_IPV6_IP_ADDRESS_3_PREFIX =
+            IpPrefix("fe80::/10")
+    private val LOCAL_IPV6_LINK_ADDRESS_3 = LinkAddress(
+            LOCAL_IPV6_IP_ADDRESS_3_PREFIX.getAddress(),
+            LOCAL_IPV6_IP_ADDRESS_3_PREFIX.getPrefixLength()
+    )
+
     private val LOCAL_IPV4_IP_ADDRESS_PREFIX_1 = IpPrefix("10.0.0.184/24")
     private val LOCAL_IPV4_LINK_ADDRRESS_1 =
         LinkAddress(
@@ -190,7 +206,7 @@
     }
 
     @Test
-    fun testStackedLinkPropertiesWithDifferentLinkAddresses_AddressAddedInBpfMap() {
+    fun testAddingThenRemovingStackedLinkProperties_AddressAddedThenRemovedInBpfMap() {
         val nr = nr(TRANSPORT_WIFI)
         val cb = TestableNetworkCallback()
         cm.requestNetwork(nr, cb)
@@ -230,49 +246,6 @@
         )
         // As both addresses are in stacked links, so no address should be removed from the map.
         verify(bpfNetMaps, never()).removeLocalNetAccess(any(), any(), any(), any(), any())
-    }
-
-    @Test
-    fun testRemovingStackedLinkProperties_AddressRemovedInBpfMap() {
-        val nr = nr(TRANSPORT_WIFI)
-        val cb = TestableNetworkCallback()
-        cm.requestNetwork(nr, cb)
-
-        val wifiLp = lp(WIFI_IFNAME, LOCAL_IPV6_LINK_ADDRESS)
-        val wifiLp2 = lp(WIFI_IFNAME_2, LOCAL_IPV4_LINK_ADDRRESS_1)
-        // populating stacked link
-        wifiLp.addStackedLink(wifiLp2)
-        val wifiAgent = Agent(nc = wifiNc, lp = wifiLp)
-        wifiAgent.connect()
-        cb.expectAvailableCallbacks(wifiAgent.network, validated = false)
-
-        // Multicast and Broadcast address should always be populated in local_net_access map
-        verifyPopulationOfMulticastAndBroadcastAddress()
-        // Verifying IPv6 address should be populated in local_net_access map
-        verify(bpfNetMaps).addLocalNetAccess(
-                eq(PREFIX_LENGTH_IPV6 + LOCAL_IPV6_IP_ADDRESS_PREFIX.getPrefixLength()),
-                eq(WIFI_IFNAME),
-                eq(LOCAL_IPV6_IP_ADDRESS_PREFIX.getAddress()),
-                eq(0),
-                eq(0),
-                eq(false)
-        )
-
-        // Multicast and Broadcast address should always be populated on stacked link
-        // in local_net_access map
-        verifyPopulationOfMulticastAndBroadcastAddress(WIFI_IFNAME_2)
-        // Verifying IPv4 matching prefix(10.0.0.0/8) should be populated as part of stacked link
-        // in local_net_access map
-        verify(bpfNetMaps).addLocalNetAccess(
-                eq(PREFIX_LENGTH_IPV4 + 8),
-                eq(WIFI_IFNAME_2),
-                eq(InetAddresses.parseNumericAddress("10.0.0.0")),
-                eq(0),
-                eq(0),
-                eq(false)
-        )
-        // As both addresses are in stacked links, so no address should be removed from the map.
-        verify(bpfNetMaps, never()).removeLocalNetAccess(any(), any(), any(), any(), any())
 
         // replacing link properties without stacked links
         val wifiLp_3 = lp(WIFI_IFNAME, LOCAL_IPV6_LINK_ADDRESS)
@@ -290,6 +263,107 @@
     }
 
     @Test
+    fun testChangeStackedLinkProperties_AddressReplacedBpfMap() {
+        val nr = nr(TRANSPORT_WIFI)
+        val cb = TestableNetworkCallback()
+        cm.requestNetwork(nr, cb)
+
+        val wifiLp = lp(WIFI_IFNAME, LOCAL_IPV6_LINK_ADDRESS)
+        val wifiLp2 = lp(WIFI_IFNAME_2, LOCAL_IPV4_LINK_ADDRRESS_1)
+        // populating stacked link
+        wifiLp.addStackedLink(wifiLp2)
+        val wifiAgent = Agent(nc = wifiNc, lp = wifiLp)
+        wifiAgent.connect()
+        cb.expectAvailableCallbacks(wifiAgent.network, validated = false)
+
+        // Multicast and Broadcast address should always be populated in local_net_access map
+        verifyPopulationOfMulticastAndBroadcastAddress()
+        // Verifying IPv6 address should be populated in local_net_access map
+        verify(bpfNetMaps).addLocalNetAccess(
+                eq(PREFIX_LENGTH_IPV6 + LOCAL_IPV6_IP_ADDRESS_PREFIX.getPrefixLength()),
+                eq(WIFI_IFNAME),
+                eq(LOCAL_IPV6_IP_ADDRESS_PREFIX.getAddress()),
+                eq(0),
+                eq(0),
+                eq(false)
+        )
+
+        // Multicast and Broadcast address should always be populated on stacked link
+        // in local_net_access map
+        verifyPopulationOfMulticastAndBroadcastAddress(WIFI_IFNAME_2)
+        // Verifying IPv4 matching prefix(10.0.0.0/8) should be populated as part of stacked link
+        // in local_net_access map
+        verify(bpfNetMaps).addLocalNetAccess(
+                eq(PREFIX_LENGTH_IPV4 + 8),
+                eq(WIFI_IFNAME_2),
+                eq(InetAddresses.parseNumericAddress("10.0.0.0")),
+                eq(0),
+                eq(0),
+                eq(false)
+        )
+        // As both addresses are in stacked links, so no address should be removed from the map.
+        verify(bpfNetMaps, never()).removeLocalNetAccess(any(), any(), any(), any(), any())
+
+        // replacing link properties multiple stacked links
+        val wifiLp_3 = lp(WIFI_IFNAME, LOCAL_IPV6_LINK_ADDRESS_2)
+        val wifiLp_4 = lp(WIFI_IFNAME_2, LOCAL_IPV4_LINK_ADDRRESS_2)
+        val wifiLp_5 = lp(WIFI_IFNAME_3, LOCAL_IPV6_LINK_ADDRESS_3)
+        wifiLp_3.addStackedLink(wifiLp_4)
+        wifiLp_3.addStackedLink(wifiLp_5)
+        wifiAgent.sendLinkProperties(wifiLp_3)
+        cb.expect<LinkPropertiesChanged>(wifiAgent.network)
+
+        // Multicast and Broadcast address should always be populated on stacked link
+        // in local_net_access map
+        verifyPopulationOfMulticastAndBroadcastAddress(WIFI_IFNAME_3)
+        // Verifying new base IPv6 address should be populated in local_net_access map
+        verify(bpfNetMaps).addLocalNetAccess(
+                eq(PREFIX_LENGTH_IPV6 + LOCAL_IPV6_IP_ADDRESS_2_PREFIX.getPrefixLength()),
+                eq(WIFI_IFNAME),
+                eq(LOCAL_IPV6_IP_ADDRESS_2_PREFIX.getAddress()),
+                eq(0),
+                eq(0),
+                eq(false)
+        )
+        // Verifying IPv4 matching prefix(10.0.0.0/8) should be populated as part of stacked link
+        // in local_net_access map
+        verify(bpfNetMaps, times(2)).addLocalNetAccess(
+                eq(PREFIX_LENGTH_IPV4 + 8),
+                eq(WIFI_IFNAME_2),
+                eq(InetAddresses.parseNumericAddress("10.0.0.0")),
+                eq(0),
+                eq(0),
+                eq(false)
+        )
+        // Verifying newly stacked IPv6 address should be populated in local_net_access map
+        verify(bpfNetMaps).addLocalNetAccess(
+                eq(PREFIX_LENGTH_IPV6 + LOCAL_IPV6_IP_ADDRESS_3_PREFIX.getPrefixLength()),
+                eq(WIFI_IFNAME_3),
+                eq(LOCAL_IPV6_IP_ADDRESS_3_PREFIX.getAddress()),
+                eq(0),
+                eq(0),
+                eq(false)
+        )
+        // Verifying old base IPv6 address should be removed from local_net_access map
+        verify(bpfNetMaps).removeLocalNetAccess(
+                eq(PREFIX_LENGTH_IPV6 + LOCAL_IPV6_IP_ADDRESS_PREFIX.getPrefixLength()),
+                eq(WIFI_IFNAME),
+                eq(LOCAL_IPV6_IP_ADDRESS_PREFIX.getAddress()),
+                eq(0),
+                eq(0)
+        )
+        // As both stacked links is had same prefix, 10.0.0.0/8 should not be removed from
+        // local_net_access map.
+        verify(bpfNetMaps, never()).removeLocalNetAccess(
+                eq(PREFIX_LENGTH_IPV4 + 8),
+                eq(WIFI_IFNAME_2),
+                eq(InetAddresses.parseNumericAddress("10.0.0.0")),
+                eq(0),
+                eq(0)
+        )
+    }
+
+    @Test
     fun testChangeLinkPropertiesWithLinkAddressesInSameRange_AddressIntactInBpfMap() {
         val nr = nr(TRANSPORT_WIFI)
         val cb = TestableNetworkCallback()
diff --git a/tests/unit/java/com/android/server/net/HeaderCompressionUtilsTest.kt b/tests/unit/java/com/android/server/net/HeaderCompressionUtilsTest.kt
index 8431194..7ebe384 100644
--- a/tests/unit/java/com/android/server/net/HeaderCompressionUtilsTest.kt
+++ b/tests/unit/java/com/android/server/net/HeaderCompressionUtilsTest.kt
@@ -17,12 +17,14 @@
 package com.android.server.net
 
 import android.os.Build
+import com.android.internal.util.HexDump
 import com.android.testutils.ConnectivityModuleTest
 import com.android.testutils.DevSdkIgnoreRule
 import com.android.testutils.DevSdkIgnoreRunner
-import com.android.internal.util.HexDump
 import com.google.common.truth.Truth.assertThat
-
+import java.io.IOException
+import java.nio.BufferUnderflowException
+import kotlin.test.assertFailsWith
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -184,6 +186,83 @@
     }
 
     @Test
+    fun testHeaderDecompression_invalidPacket() {
+        // 1-byte packet
+        var input = "60"
+        assertFailsWith(BufferUnderflowException::class) { decompressHex(input) }
+
+        // Short packet -- incomplete header
+        // TF: 11, NH: 0, HLIM: 11, CID: 0, SAC: 0, SAM: 10, M: 1, DAC: 0, DAM: 11
+        input  = "7b2b" +
+                 "44" +                               // next header
+                 "1234"                               // source
+        assertFailsWith(BufferUnderflowException::class) { decompressHex(input) }
+
+        // Packet starts with 0b111 instead of 0b011
+        // TF: 11, NH: 0, HLIM: 11, CID: 0, SAC: 0, SAM: 10, M: 1, DAC: 0, DAM: 11
+        input  = "fb2b" +
+                 "44" +                               // next header
+                 "1234" +                             // source
+                 "89" +                               // dest
+                 "abcdef01"                           // payload
+        assertFailsWith(IOException::class) { decompressHex(input) }
+
+        // Illegal option NH = 1. Note that the packet is not valid as the code should throw as soon
+        // as the illegal option is encountered.
+        // TF: 11, NH: 1, HLIM: 11, CID: 0, SAC: 0, SAM: 10, M: 1, DAC: 0, DAM: 11
+        input  = "7f2b" +
+                 "1234" +                             // source
+                 "89" +                               // dest
+                 "e0"                                 // Hop-by-hop options NHC
+        assertFailsWith(IOException::class) { decompressHex(input) }
+
+        // Illegal option CID = 1.
+        // TF: 11, NH: 0, HLIM: 11, CID: 1, SAC: 0, SAM: 10, M: 1, DAC: 0, DAM: 11
+        input  = "7bab00" +
+                 "1234" +                             // source
+                 "89" +                               // dest
+                 "e0"                                 // Hop-by-hop options NHC
+        assertFailsWith(IOException::class) { decompressHex(input) }
+
+        // Illegal option SAC = 1.
+        // TF: 11, NH: 0, HLIM: 11, CID: 0, SAC: 1, SAM: 10, M: 1, DAC: 0, DAM: 11
+        input  = "7b6b" +
+                 "1234" +                             // source
+                 "89" +                               // dest
+                 "e0"                                 // Hop-by-hop options NHC
+        assertFailsWith(IOException::class) { decompressHex(input) }
+
+        // Illegal option DAC = 1.
+        // TF: 10, NH: 0, HLIM: 10, CID: 0, SAC: 0, SAM: 10, M: 0, DAC: 1, DAM: 10
+        input  = "7226" +
+                 "cc" +                               // traffic class
+                 "43" +                               // next header
+                 "1234" +                             // source
+                 "abcd" +                             // dest
+                 "abcdef"                             // payload
+        assertFailsWith(IOException::class) { decompressHex(input) }
+
+
+        // Unsupported option DAM = 11
+        // TF: 10, NH: 0, HLIM: 10, CID: 0, SAC: 0, SAM: 10, M: 0, DAC: 0, DAM: 11
+        input  = "7223" +
+                 "cc" +                               // traffic class
+                 "43" +                               // next header
+                 "1234" +                             // source
+                 "abcdef"                             // payload
+        assertFailsWith(IOException::class) { decompressHex(input) }
+
+        // Unsupported option SAM = 11
+        // TF: 10, NH: 0, HLIM: 10, CID: 0, SAC: 0, SAM: 11, M: 0, DAC: 0, DAM: 10
+        input  = "7232" +
+                 "cc" +                               // traffic class
+                 "43" +                               // next header
+                 "abcd" +                             // dest
+                 "abcdef"                             // payload
+        assertFailsWith(IOException::class) { decompressHex(input) }
+    }
+
+    @Test
     fun testHeaderCompression() {
         val input  = "60120304000011fffe800000000000000000000000000001fe800000000000000000000000000002"
         val output = "60000102030411fffe800000000000000000000000000001fe800000000000000000000000000002"
diff --git a/tests/unit/jni/Android.bp b/tests/unit/jni/Android.bp
index 1a833e1..1e9db03 100644
--- a/tests/unit/jni/Android.bp
+++ b/tests/unit/jni/Android.bp
@@ -22,6 +22,7 @@
     ],
 
     shared_libs: [
+        "libbase",
         "liblog",
         "libnativehelper",
         "libnetdutils",
diff --git a/thread/tests/cts/Android.bp b/thread/tests/cts/Android.bp
index 2630d21..901dee7 100644
--- a/thread/tests/cts/Android.bp
+++ b/thread/tests/cts/Android.bp
@@ -51,7 +51,6 @@
     libs: [
         "android.test.base.stubs",
         "android.test.runner.stubs",
-        "framework-connectivity-module-api-stubs-including-flagged",
     ],
     // Test coverage system runs on different devices. Need to
     // compile for all architectures.
diff --git a/thread/tests/cts/AndroidTest.xml b/thread/tests/cts/AndroidTest.xml
index e954d3b..89d2ce5 100644
--- a/thread/tests/cts/AndroidTest.xml
+++ b/thread/tests/cts/AndroidTest.xml
@@ -57,13 +57,4 @@
         <option name="exclude-annotation" value="org.junit.Ignore"/>
     </test>
 
-    <!--
-        This doesn't override a read-only flag, to run the tests locally with `epskc_enabled` flag
-        enabled, set the flag to `is_fixed_read_only: false`. This should be removed after the
-        `epskc_enabled` flag is rolled out.
-    -->
-    <target_preparer class="com.android.tradefed.targetprep.FeatureFlagTargetPreparer">
-        <option name="flag-value"
-                value="thread_network/com.android.net.thread.flags.epskc_enabled=true"/>
-    </target_preparer>
 </configuration>
diff --git a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
index 2d487ca..a979721 100644
--- a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
+++ b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
@@ -1296,6 +1296,7 @@
     }
 
     @Test
+    @Ignore("b/333649897, b/332195449: The 3 meshcop tests are flaky in different environments")
     public void meshcopService_threadEnabledButNotJoined_discoveredButNoNetwork() throws Exception {
         setUpTestNetwork();
 
@@ -1348,6 +1349,7 @@
     }
 
     @Test
+    @Ignore("b/333649897, b/332195449: The 3 meshcop tests are flaky in different environments")
     public void meshcopService_threadDisabled_notDiscovered() throws Exception {
         setUpTestNetwork();
         CompletableFuture<NsdServiceInfo> serviceLostFuture = new CompletableFuture<>();
diff --git a/thread/tests/integration/src/android/net/thread/InternetAccessTest.kt b/thread/tests/integration/src/android/net/thread/InternetAccessTest.kt
index 3c9aa07..46d4708 100644
--- a/thread/tests/integration/src/android/net/thread/InternetAccessTest.kt
+++ b/thread/tests/integration/src/android/net/thread/InternetAccessTest.kt
@@ -167,6 +167,8 @@
         val ftd = ftds[0]
         joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET)
         dnsServer.start()
+        ftd.autoStartSrpClient()
+        ftd.waitForSrpServer()
 
         val ipv4Addresses =
             ftd.resolveHost("google.com", TYPE_A).map { extractIpv4AddressFromMappedAddress(it) }
@@ -181,6 +183,8 @@
         val ftd = ftds[0]
         joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET)
         dnsServer.start()
+        ftd.autoStartSrpClient()
+        ftd.waitForSrpServer()
 
         assertThat(ftd.resolveHost("google.com", TYPE_A)).isEmpty()
         assertThat(ftd.resolveHost("google.com", TYPE_AAAA)).isEmpty()
diff --git a/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java b/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java
index 6c2a9bb..f959ccf 100644
--- a/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java
+++ b/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java
@@ -113,8 +113,8 @@
 
     @Before
     public void setUp() throws Exception {
-        mOtCtl.factoryReset();
         mController.setEnabledAndWait(true);
+        mController.leaveAndWait();
         mController.joinAndWait(DEFAULT_DATASET);
         mNsdManager = mContext.getSystemService(NsdManager.class);
 
diff --git a/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java b/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
index 7a5895f..2641a77 100644
--- a/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
+++ b/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
@@ -132,10 +132,6 @@
         mOtCtl = new OtDaemonController();
         mController.setEnabledAndWait(true);
         mController.leaveAndWait();
-
-        // TODO: b/323301831 - This is a workaround to avoid unnecessary delay to re-form a network
-        mOtCtl.factoryReset();
-
         mFtd = new FullThreadDevice(10 /* nodeId */);
     }
 
@@ -352,7 +348,6 @@
         mOtCtl.executeCommand("netdata register");
 
         mController.leaveAndWait();
-        mOtCtl.factoryReset();
         mController.joinAndWait(DEFAULT_DATASET);
 
         LinkProperties lp = cm.getLinkProperties(getThreadNetwork(CALLBACK_TIMEOUT));
diff --git a/thread/tests/integration/src/android/net/thread/ThreadNetworkShellCommandTest.java b/thread/tests/integration/src/android/net/thread/ThreadNetworkShellCommandTest.java
index 2f0ab34..ac688dd 100644
--- a/thread/tests/integration/src/android/net/thread/ThreadNetworkShellCommandTest.java
+++ b/thread/tests/integration/src/android/net/thread/ThreadNetworkShellCommandTest.java
@@ -66,11 +66,7 @@
 
     @Before
     public void setUp() throws Exception {
-        // TODO(b/366141754): The current implementation of "thread_network ot-ctl factoryreset"
-        // results in timeout error.
-        // A future fix will provide proper support for factoryreset, allowing us to replace the
-        // legacy "ot-ctl".
-        mOtCtl.factoryReset();
+        mController.leaveAndWait();
 
         mFtd = new FullThreadDevice(10 /* nodeId */);
         ensureThreadEnabled();
diff --git a/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java b/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java
index 209eed6..38961a3 100644
--- a/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java
+++ b/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java
@@ -521,7 +521,7 @@
     }
 
     /** Waits for an SRP server to be present in Network Data */
-    private void waitForSrpServer() throws TimeoutException {
+    public void waitForSrpServer() throws TimeoutException {
         // CLI output:
         // > srp client server
         // [fd64:db12:25f4:7e0b:1bfc:6344:25ac:2dd7]:53538
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 801e21e..f00c9cd 100644
--- a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.kt
+++ b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.kt
@@ -603,11 +603,12 @@
     /** Enables Thread and joins the specified Thread network. */
     @JvmStatic
     fun enableThreadAndJoinNetwork(dataset: ActiveOperationalDataset) {
-        // TODO: b/323301831 - This is a workaround to avoid unnecessary delay to re-form a network
-        OtDaemonController().factoryReset();
-
         val context: Context = requireNotNull(ApplicationProvider.getApplicationContext());
         val controller = requireNotNull(ThreadNetworkControllerWrapper.newInstance(context));
+
+        // TODO: b/323301831 - This is a workaround to avoid unnecessary delay to re-form a network
+        controller.leaveAndWait();
+
         controller.setEnabledAndWait(true);
         controller.joinAndWait(dataset);
     }
diff --git a/thread/tests/multidevices/Android.bp b/thread/tests/multidevices/Android.bp
index 050caa8..1d2ae62 100644
--- a/thread/tests/multidevices/Android.bp
+++ b/thread/tests/multidevices/Android.bp
@@ -35,9 +35,4 @@
         "mts-tethering",
         "general-tests",
     ],
-    version: {
-        py3: {
-            embedded_launcher: true,
-        },
-    },
 }