Merge "Use scapy to generate ARP packet in multi-device test" into main
diff --git a/bpf/loader/netbpfload.rc b/bpf/loader/netbpfload.rc
index 10bfbb2..4cc6284 100644
--- a/bpf/loader/netbpfload.rc
+++ b/bpf/loader/netbpfload.rc
@@ -1,3 +1,5 @@
+# 2025 2 36 0 0 # 25q2 sdk/api level 36.0 - Android 16 Baklava QPR0
+
 # Note: This will actually execute /apex/com.android.tethering/bin/netbpfload
 # by virtue of 'service bpfloader' being overridden by the apex shipped .rc
 # Warning: most of the below settings are irrelevant unless the apex is missing.
diff --git a/bpf/netd/BpfHandler.cpp b/bpf/netd/BpfHandler.cpp
index d41aa81..680c05e 100644
--- a/bpf/netd/BpfHandler.cpp
+++ b/bpf/netd/BpfHandler.cpp
@@ -268,6 +268,16 @@
     RETURN_IF_NOT_OK(initMaps());
 
     if (isAtLeast25Q2) {
+        struct rlimit limit = {
+            .rlim_cur = 1u << 30,  // 1 GiB
+            .rlim_max = 1u << 30,  // 1 GiB
+        };
+        // 25Q2 netd.rc includes "rlimit memlock 1073741824 1073741824"
+        // so this should be a no-op, and thus just succeed.
+        // make sure it isn't lowered in platform netd.rc...
+        if (setrlimit(RLIMIT_MEMLOCK, &limit))
+            return statusFromErrno(errno, "Failed to set 1GiB RLIMIT_MEMLOCK");
+
         // Make sure netd can create & write maps.  sepolicy is V+, but enough to enforce on 25Q2+
         int key = 1;
         int value = 123;
diff --git a/service-t/src/com/android/server/ethernet/EthernetTracker.java b/service-t/src/com/android/server/ethernet/EthernetTracker.java
index 9b3c7ba..48467ed 100644
--- a/service-t/src/com/android/server/ethernet/EthernetTracker.java
+++ b/service-t/src/com/android/server/ethernet/EthernetTracker.java
@@ -755,7 +755,17 @@
     private void parseEthernetConfig(String configString) {
         final EthernetTrackerConfig config = createEthernetTrackerConfig(configString);
         NetworkCapabilities nc;
-        if (TextUtils.isEmpty(config.mCapabilities)) {
+        // Starting with Android B (API level 36), we provide default NetworkCapabilities
+        // for Ethernet interfaces when no explicit capabilities are specified in the
+        // configuration string. This change is made to ensure consistent and expected
+        // network behavior for Ethernet devices.
+        //
+        // It's possible that OEMs or device manufacturers may have relied on the previous
+        // behavior (where interfaces without specified capabilities would have minimal
+        // capabilities) to prevent certain Ethernet interfaces from becoming
+        // the default network. To avoid breaking existing device configurations, this
+        // change is gated by the SDK level.
+        if (SdkLevel.isAtLeastB() && TextUtils.isEmpty(config.mCapabilities)) {
             boolean isTestIface = config.mIface.matches(TEST_IFACE_REGEXP);
             nc = createDefaultNetworkCapabilities(isTestIface, config.mTransport);
         } else {
diff --git a/service/src/com/android/server/L2capNetworkProvider.java b/service/src/com/android/server/L2capNetworkProvider.java
index 0352ad5..149979f 100644
--- a/service/src/com/android/server/L2capNetworkProvider.java
+++ b/service/src/com/android/server/L2capNetworkProvider.java
@@ -597,10 +597,12 @@
             final ClientRequestInfo cri = mClientNetworkRequests.get(specifier);
             if (cri == null) return;
 
+            // Release ClientNetworkRequest before sending onUnavailable() to ensure the app
+            // first receives an onLost() callback if a network had been created.
+            releaseClientNetworkRequest(cri);
             for (NetworkRequest request : cri.requests) {
                 mProvider.declareNetworkRequestUnfulfillable(request);
             }
-            releaseClientNetworkRequest(cri);
         }
     }
 
diff --git a/staticlibs/testutils/host/python/apf_utils.py b/staticlibs/testutils/host/python/apf_utils.py
index fa8a1da..4835c23 100644
--- a/staticlibs/testutils/host/python/apf_utils.py
+++ b/staticlibs/testutils/host/python/apf_utils.py
@@ -439,10 +439,10 @@
     def wrapper(self, *args, **kwargs):
       asserts.abort_class_if(
         (not hasattr(self, 'client')) or (not hasattr(self.client, 'isAtLeastB')),
-        "client device is not B+"
+        "no valid client attribute"
       )
 
-      asserts.skip_if(not self.client.isAtLeastB(), "not B+")
+      asserts.abort_class_if(not self.client.isAtLeastB(), "client device is not Android B+")
       return test_function(self, *args, **kwargs)
     return wrapper
   return decorator
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSL2capProviderTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSL2capProviderTest.kt
index babcba9..ee5b4ee 100644
--- a/tests/unit/java/com/android/server/connectivityservice/CSL2capProviderTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/CSL2capProviderTest.kt
@@ -38,12 +38,14 @@
 import android.net.NetworkSpecifier
 import android.net.RouteInfo
 import android.os.Build
+import android.os.Handler
 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.Lost
 import com.android.testutils.RecorderCallback.CallbackEntry.Reserved
 import com.android.testutils.RecorderCallback.CallbackEntry.Unavailable
 import com.android.testutils.TestableNetworkCallback
@@ -59,6 +61,7 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.ArgumentMatchers.isNull
 import org.mockito.Mockito.doAnswer
@@ -394,4 +397,34 @@
         val cb2 = requestNetwork(nr)
         cb2.expectAvailableCallbacks(anyNetwork(), validated = false)
     }
+
+    /** Test to ensure onLost() is sent before onUnavailable() when the network is torn down. */
+    @Test
+    fun testClientNetwork_gracefulTearDown() {
+        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)
+
+        // Capture the L2capPacketForwarder callback object to tear down the network.
+        val handlerCaptor = ArgumentCaptor.forClass(Handler::class.java)
+        val forwarderCbCaptor = ArgumentCaptor.forClass(L2capPacketForwarder.ICallback::class.java)
+        verify(providerDeps).createL2capPacketForwarder(
+                handlerCaptor.capture(), any(), any(), any(), forwarderCbCaptor.capture())
+        val handler = handlerCaptor.value
+        val forwarderCb = forwarderCbCaptor.value
+
+        // Trigger a forwarding error
+        handler.post { forwarderCb.onError() }
+        handler.waitForIdle(HANDLER_TIMEOUT_MS)
+
+        cb.expect<Lost>()
+        cb.expect<Unavailable>()
+    }
 }