Merge "Add MatchNonThreadLocalNetworks flag in NetworkCapabilities" into main
diff --git a/bpf/loader/NetBpfLoad.cpp b/bpf/loader/NetBpfLoad.cpp
index d70a2c8..4c47f83 100644
--- a/bpf/loader/NetBpfLoad.cpp
+++ b/bpf/loader/NetBpfLoad.cpp
@@ -769,7 +769,7 @@
const size_t max_name = 256;
char kvTypeName[max_name];
int64_t keySize, valueSize;
- uint32_t kvId;
+ int32_t kvId;
if (snprintf(kvTypeName, max_name, "____btf_map_%s", mapName) == max_name) {
ALOGE("____btf_map_%s is too long", mapName);
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/tests/unit/host/python/packet_utils_test.py b/staticlibs/tests/unit/host/python/packet_utils_test.py
deleted file mode 100644
index 8ad9576..0000000
--- a/staticlibs/tests/unit/host/python/packet_utils_test.py
+++ /dev/null
@@ -1,72 +0,0 @@
-# Copyright (C) 2024 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from mobly import asserts
-from mobly import base_test
-from net_tests_utils.host.python import packet_utils
-
-class TestPacketUtils(base_test.BaseTestClass):
- def test_unicast_arp_request(self):
- # Using scapy to generate unicast arp request packet:
- # eth = Ether(src="00:01:02:03:04:05", dst="01:02:03:04:05:06")
- # arp = ARP(op=1, pdst="192.168.1.1", hwsrc="00:01:02:03:04:05", psrc="192.168.1.2")
- # pkt = eth/arp
- expect_arp_request = """
- 01020304050600010203040508060001080006040001000102030405c0a80102000000000000c0a80101
- """.upper().replace(" ", "").replace("\n", "")
- arp_request = packet_utils.construct_arp_packet(
- src_mac="00:01:02:03:04:05",
- dst_mac="01:02:03:04:05:06",
- src_ip="192.168.1.2",
- dst_ip="192.168.1.1",
- op=packet_utils.ARP_REQUEST_OP
- )
- asserts.assert_equal(expect_arp_request, arp_request)
-
- def test_broadcast_arp_request(self):
- # Using scapy to generate unicast arp request packet:
- # eth = Ether(src="00:01:02:03:04:05", dst="FF:FF:FF:FF:FF:FF")
- # arp = ARP(op=1, pdst="192.168.1.1", hwsrc="00:01:02:03:04:05", psrc="192.168.1.2")
- # pkt = eth/arp
- expect_arp_request = """
- ffffffffffff00010203040508060001080006040001000102030405c0a80102000000000000c0a80101
- """.upper().replace(" ", "").replace("\n", "")
- arp_request = packet_utils.construct_arp_packet(
- src_mac="00:01:02:03:04:05",
- dst_mac=packet_utils.ETHER_BROADCAST_MAC_ADDRESS,
- src_ip="192.168.1.2",
- dst_ip="192.168.1.1",
- op=packet_utils.ARP_REQUEST_OP
- )
- asserts.assert_equal(expect_arp_request, arp_request)
-
- def test_arp_reply(self):
- # Using scapy to generate unicast arp request packet:
- # eth = Ether(src="01:02:03:04:05:06", dst="00:01:02:03:04:05")
- # arp = ARP(op=2, pdst="192.168.1.2", \
- # hwsrc="01:02:03:04:05:06", \
- # psrc="192.168.1.1", \
- # hwdst="00:01:02:03:04:05")
- # pkt = eth/arp
- expect_arp_reply = """
- 00010203040501020304050608060001080006040002010203040506c0a80101000102030405c0a80102
- """.upper().replace(" ", "").replace("\n", "")
- arp_reply = packet_utils.construct_arp_packet(
- src_mac="01:02:03:04:05:06",
- dst_mac="00:01:02:03:04:05",
- src_ip="192.168.1.1",
- dst_ip="192.168.1.2",
- op=packet_utils.ARP_REPLY_OP
- )
- asserts.assert_equal(expect_arp_reply, arp_reply)
diff --git a/staticlibs/tests/unit/host/python/run_tests.py b/staticlibs/tests/unit/host/python/run_tests.py
index 498dbaf..fa6a310 100644
--- a/staticlibs/tests/unit/host/python/run_tests.py
+++ b/staticlibs/tests/unit/host/python/run_tests.py
@@ -18,7 +18,6 @@
from host.python.adb_utils_test import TestAdbUtils
from host.python.apf_utils_test import TestApfUtils
from host.python.assert_utils_test import TestAssertUtils
-from host.python.packet_utils_test import TestPacketUtils
from mobly import suite_runner
@@ -32,5 +31,5 @@
sys.argv.pop(1)
# TODO: make the tests can be executed without manually list classes.
suite_runner.run_suite(
- [TestAssertUtils, TestAdbUtils, TestApfUtils, TestPacketUtils], sys.argv
+ [TestAssertUtils, TestAdbUtils, TestApfUtils], sys.argv
)
diff --git a/staticlibs/testutils/host/python/packet_utils.py b/staticlibs/testutils/host/python/packet_utils.py
deleted file mode 100644
index b613f03..0000000
--- a/staticlibs/testutils/host/python/packet_utils.py
+++ /dev/null
@@ -1,70 +0,0 @@
-# Copyright (C) 2024 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-from ipaddress import IPv4Address
-from socket import inet_aton
-
-ETHER_BROADCAST_MAC_ADDRESS = "FF:FF:FF:FF:FF:FF"
-ARP_REQUEST_OP = 1
-ARP_REPLY_OP = 2
-
-"""
-This variable defines a template for constructing ARP packets in hexadecimal format.
-It's used to provide the common fields for ARP packet, and replaced needed fields when constructing
-"""
-ARP_TEMPLATE = (
- # Ether Header (14 bytes)
- "{dst_mac}" + # DA
- "{src_mac}" + # SA
- "0806" + # ARP
- # ARP Header (28 bytes)
- "0001" + # Hardware type (Ethernet)
- "0800" + # Protocol type (IPv4)
- "06" + # hardware address length
- "04" + # protocol address length
- "{opcode}" + # opcode
- "{sender_mac}" + # sender MAC
- "{sender_ip}" + # sender IP
- "{target_mac}" + # target MAC
- "{target_ip}" # target IP
-)
-
-def construct_arp_packet(src_mac, dst_mac, src_ip, dst_ip, op) -> str:
- """Constructs an ARP packet as a hexadecimal string.
-
- This function creates an ARP packet by filling in the required fields
- in a predefined ARP packet template.
-
- Args:
- src_mac: The MAC address of the sender. (e.g. "11:22:33:44:55:66")
- dst_mac: The MAC address of the recipient. (e.g. "aa:bb:cc:dd:ee:ff")
- src_ip: The IP address of the sender. (e.g. "1.1.1.1")
- dst_ip: The IP address of the target machine. (e.g. "2.2.2.2")
- op: The op code of the ARP packet, refer to ARP_*_OP
-
- Returns:
- A string representing the ARP packet in hexadecimal format.
- """
- # Replace the needed fields from packet template
- arp_pkt = ARP_TEMPLATE.format(
- dst_mac=dst_mac.replace(":",""),
- src_mac=src_mac.replace(":",""),
- opcode=str(op).rjust(4, "0"),
- sender_mac=src_mac.replace(":",""),
- sender_ip=inet_aton(src_ip).hex(),
- target_mac=("000000000000" if op == ARP_REQUEST_OP else dst_mac.replace(":", "")),
- target_ip=inet_aton(dst_ip).hex()
- )
-
- # always convert to upper case hex string
- return arp_pkt.upper()
\ No newline at end of file
diff --git a/tests/cts/multidevices/apfv6_test.py b/tests/cts/multidevices/apfv6_test.py
index ee2368d..2404966 100644
--- a/tests/cts/multidevices/apfv6_test.py
+++ b/tests/cts/multidevices/apfv6_test.py
@@ -28,9 +28,9 @@
ICMPv6ND_NA,
RouterAlert
)
-from scapy.layers.l2 import Ether
+from scapy.layers.l2 import ARP, Ether
from scapy.contrib.igmpv3 import IGMPv3, IGMPv3mq, IGMPv3mr, IGMPv3gr
-from net_tests_utils.host.python import apf_test_base, apf_utils, adb_utils, assert_utils, packet_utils
+from net_tests_utils.host.python import apf_test_base, apf_utils, adb_utils, assert_utils
APFV6_VERSION = 6000
ARP_OFFLOAD_REPLY_LEN = 60
@@ -53,51 +53,57 @@
super().teardown_class()
def test_unicast_arp_request_offload(self):
- arp_request = packet_utils.construct_arp_packet(
- src_mac=self.server_mac_address,
- dst_mac=self.client_mac_address,
- src_ip=self.server_ipv4_addresses[0],
- dst_ip=self.client_ipv4_addresses[0],
- op=packet_utils.ARP_REQUEST_OP
+ eth = Ether(src=self.server_mac_address, dst=self.client_mac_address)
+ arp = ARP(
+ op=1,
+ psrc=self.server_ipv4_addresses[0],
+ pdst=self.client_ipv4_addresses[0],
+ hwsrc=self.server_mac_address
)
+ arp_request = bytes(eth/arp).hex()
- arp_reply = packet_utils.construct_arp_packet(
- src_mac=self.client_mac_address,
- dst_mac=self.server_mac_address,
- src_ip=self.client_ipv4_addresses[0],
- dst_ip=self.server_ipv4_addresses[0],
- op=packet_utils.ARP_REPLY_OP
+ eth = Ether(src=self.client_mac_address, dst=self.server_mac_address)
+ arp = ARP(
+ op=2,
+ psrc=self.client_ipv4_addresses[0],
+ pdst=self.server_ipv4_addresses[0],
+ hwsrc=self.client_mac_address,
+ hwdst=self.server_mac_address
)
+ expected_arp_reply = bytes(eth/arp).hex()
# Add zero padding up to 60 bytes, since APFv6 ARP offload always sent out 60 bytes reply
- arp_reply = arp_reply.ljust(ARP_OFFLOAD_REPLY_LEN * 2, "0")
+ expected_arp_reply = expected_arp_reply.ljust(ARP_OFFLOAD_REPLY_LEN * 2, "0")
self.send_packet_and_expect_reply_received(
- arp_request, "DROPPED_ARP_REQUEST_REPLIED", arp_reply
+ arp_request, "DROPPED_ARP_REQUEST_REPLIED", expected_arp_reply
)
def test_broadcast_arp_request_offload(self):
- arp_request = packet_utils.construct_arp_packet(
- src_mac=self.server_mac_address,
- dst_mac=packet_utils.ETHER_BROADCAST_MAC_ADDRESS,
- src_ip=self.server_ipv4_addresses[0],
- dst_ip=self.client_ipv4_addresses[0],
- op=packet_utils.ARP_REQUEST_OP
+ eth = Ether(src=self.server_mac_address, dst='ff:ff:ff:ff:ff:ff')
+ arp = ARP(
+ op=1,
+ psrc=self.server_ipv4_addresses[0],
+ pdst=self.client_ipv4_addresses[0],
+ hwsrc=self.server_mac_address
)
+ arp_request = bytes(eth/arp).hex()
- arp_reply = packet_utils.construct_arp_packet(
- src_mac=self.client_mac_address,
- dst_mac=self.server_mac_address,
- src_ip=self.client_ipv4_addresses[0],
- dst_ip=self.server_ipv4_addresses[0],
- op=packet_utils.ARP_REPLY_OP
+ eth = Ether(src=self.client_mac_address, dst=self.server_mac_address)
+ arp = ARP(
+ op=2,
+ psrc=self.client_ipv4_addresses[0],
+ pdst=self.server_ipv4_addresses[0],
+ hwsrc=self.client_mac_address,
+ hwdst=self.server_mac_address
)
+ expected_arp_reply = bytes(eth/arp).hex()
# Add zero padding up to 60 bytes, since APFv6 ARP offload always sent out 60 bytes reply
- arp_reply = arp_reply.ljust(ARP_OFFLOAD_REPLY_LEN * 2, "0")
+ expected_arp_reply = expected_arp_reply.ljust(ARP_OFFLOAD_REPLY_LEN * 2, "0")
self.send_packet_and_expect_reply_received(
- arp_request, "DROPPED_ARP_REQUEST_REPLIED", arp_reply
+ arp_request, "DROPPED_ARP_REQUEST_REPLIED", expected_arp_reply
)
def test_non_dad_ipv6_neighbor_solicitation_offload(self):
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>()
+ }
}