Snap for 13248265 from f22cb87e11a765234e3b1ce9fffc45a5e5588524 to 25Q2-release
Change-Id: I042b24f43d717f405d9b9268129baf45c2859914
diff --git a/common/src/com/android/net/module/util/bpf/LocalNetAccessKey.java b/common/src/com/android/net/module/util/bpf/LocalNetAccessKey.java
index 95265b9..48e8b06 100644
--- a/common/src/com/android/net/module/util/bpf/LocalNetAccessKey.java
+++ b/common/src/com/android/net/module/util/bpf/LocalNetAccessKey.java
@@ -61,12 +61,46 @@
@Override
public String toString() {
- return "LocalNetAccessKey{"
- + "lpmBitlen=" + lpmBitlen
- + ", ifIndex=" + ifIndex
- + ", remoteAddress=" + remoteAddress
- + ", protocol=" + protocol
- + ", remotePort=" + remotePort
- + "}";
+ String s = "LocalNetAccessKey{lpmBitlen=" + lpmBitlen;
+
+ long bits = lpmBitlen;
+
+ // u32 ifIndex
+ if (bits <= 0 && ifIndex != 0) s += " ??";
+ if (bits > 0 || ifIndex != 0) s += " ifIndex=" + ifIndex;
+ if (bits > 0 && bits < 32) s += "/" + bits + "[LE]";
+ bits -= 32;
+
+ // u128 remoteAddress
+ if (bits <= 0 && !remoteAddress.isAnyLocalAddress()) s += " ??";
+ if (bits > 0 || !remoteAddress.isAnyLocalAddress()) {
+ s += " remoteAddress=";
+ String ip = remoteAddress.toString();
+ if (ip.startsWith("/::ffff:")) { // technically wrong IPv4-mapped IPv6 address detection
+ s += ip.substring(8);
+ if (bits >= 96 && bits < 128) s += "/" + (bits - 96);
+ } else if (ip.startsWith("/")) {
+ s += ip.substring(1);
+ if (bits >= 0 && bits < 128) s += "/" + bits;
+ } else { // WTF, includes a hostname or what?
+ s += ip;
+ }
+ }
+ bits -= 128;
+
+ // u16 protocol
+ if (bits <= 0 && protocol != 0) s += " ??";
+ if (bits > 0 || protocol != 0) s += " protocol=" + protocol;
+ if (bits > 0 && bits < 16) s += "/" + bits + "[LE16]";
+ bits -= 16;
+
+ // be16 remotePort
+ if (bits <= 0 && remotePort != 0) s += " ??";
+ if (bits > 0 || remotePort != 0) s += " remotePort=" + remotePort;
+ if (bits > 0 && bits < 16) s += "/" + bits + "[BE16]";
+ bits -= 16;
+
+ s += "}";
+ return s;
}
}
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/ServiceConnectivityResources/res/values/overlayable.xml b/service/ServiceConnectivityResources/res/values/overlayable.xml
index 5c0ba78..f6dbf6c 100644
--- a/service/ServiceConnectivityResources/res/values/overlayable.xml
+++ b/service/ServiceConnectivityResources/res/values/overlayable.xml
@@ -48,6 +48,7 @@
<!-- Configuration values for ThreadNetworkService -->
<item type="bool" name="config_thread_default_enabled" />
+ <item type="bool" name="config_thread_border_router_default_enabled" />
<item type="bool" name="config_thread_location_use_for_country_code_enabled" />
<item type="string" name="config_thread_vendor_name" />
<item type="string" name="config_thread_vendor_oui" />
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index 9bd407d..523ffee 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -1327,7 +1327,7 @@
+ value.iif1 + "(" + mDeps.getIfName(value.iif1) + "), "
+ value.iif2 + "(" + mDeps.getIfName(value.iif2) + ")");
if (sLocalNetBlockedUidMap != null) {
- BpfDump.dumpMap(sLocalNetAccessMap, pw, "sLocalNetAccessMap",
+ BpfDump.dumpMap(sLocalNetAccessMap, pw, "sLocalNetAccessMap (default is true meaning global)",
(key, value) -> "" + key + ": " + value.val);
}
if (sLocalNetBlockedUidMap != null) {
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index dc4a35b..bfb51da 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -10005,10 +10005,12 @@
// Adds dns allow rule to LocalNetAccessMap for both TCP and UDP protocol at port 53,
// if it is a local dns (ie. it falls in the local prefix range).
if (prefix.contains(dnsServer)) {
- mBpfNetMaps.addLocalNetAccess(getIpv4MappedAddressBitLen(), iface, dnsServer,
+ mBpfNetMaps.addLocalNetAccess(32 + 128 + 16 + 16, iface, dnsServer,
IPPROTO_UDP, 53, true);
- mBpfNetMaps.addLocalNetAccess(getIpv4MappedAddressBitLen(), iface, dnsServer,
+ mBpfNetMaps.addLocalNetAccess(32 + 128 + 16 + 16, iface, dnsServer,
IPPROTO_TCP, 53, true);
+ mBpfNetMaps.addLocalNetAccess(32 + 128 + 16 + 16, iface, dnsServer,
+ IPPROTO_TCP, 853, true); // DNS over TLS
}
}
}
@@ -10027,25 +10029,17 @@
// Removes dns allow rule from LocalNetAccessMap for both TCP and UDP protocol
// at port 53, if it is a local dns (ie. it falls in the prefix range).
if (prefix.contains(dnsServer)) {
- mBpfNetMaps.removeLocalNetAccess(getIpv4MappedAddressBitLen(), iface, dnsServer,
+ mBpfNetMaps.removeLocalNetAccess(32 + 128 + 16 + 16, iface, dnsServer,
IPPROTO_UDP, 53);
- mBpfNetMaps.removeLocalNetAccess(getIpv4MappedAddressBitLen(), iface, dnsServer,
+ mBpfNetMaps.removeLocalNetAccess(32 + 128 + 16 + 16, iface, dnsServer,
IPPROTO_TCP, 53);
+ mBpfNetMaps.removeLocalNetAccess(32 + 128 + 16 + 16, iface, dnsServer,
+ IPPROTO_TCP, 853); // DNS over TLS
}
}
}
/**
- * Returns total bit length of an Ipv4 mapped address.
- */
- private int getIpv4MappedAddressBitLen() {
- final int ifaceLen = 32; // bit length of interface
- final int inetAddressLen = 32 + 96; // length of ipv4 mapped addresses
- final int portProtocolLen = 32; //16 for port + 16 for protocol;
- return ifaceLen + inetAddressLen + portProtocolLen;
- }
-
- /**
* Have netd update routes from oldLp to newLp.
* @return true if routes changed between oldLp and newLp
*/
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/apf_utils_test.py b/staticlibs/tests/unit/host/python/apf_utils_test.py
index d4753b7..55fbe58 100644
--- a/staticlibs/tests/unit/host/python/apf_utils_test.py
+++ b/staticlibs/tests/unit/host/python/apf_utils_test.py
@@ -27,6 +27,7 @@
get_apf_counters_from_dumpsys,
get_ipv4_addresses,
get_non_tentative_ipv6_addresses,
+ get_exclude_all_host_ipv6_multicast_addresses,
get_hardware_address,
is_send_raw_packet_downstream_supported,
is_packet_capture_supported,
@@ -170,6 +171,31 @@
ip_addresses = get_non_tentative_ipv6_addresses(self.mock_ad, "wlan0")
asserts.assert_equal(ip_addresses, [])
+
+ @patch("net_tests_utils.host.python.adb_utils.adb_shell")
+ def test_get_exclude_all_host_ipv6_multicast_addresses_success(
+ self, mock_adb_shell: MagicMock
+ ) -> None:
+ mock_adb_shell.return_value = """
+47: wlan0
+ inet6 ff02::1:ff99:37b0
+ inet6 ff02::1:ffb7:cba2 users 2
+ inet6 ff02::1
+ inet6 ff01::1
+"""
+ ip_addresses = get_exclude_all_host_ipv6_multicast_addresses(self.mock_ad, "wlan0")
+ asserts.assert_equal(ip_addresses,
+ ["ff02::1:ff99:37b0",
+ "ff02::1:ffb7:cba2"])
+
+ @patch("net_tests_utils.host.python.adb_utils.adb_shell")
+ def test_get_exclude_all_host_ipv6_multicast_addresses_not_found(
+ self, mock_adb_shell: MagicMock
+ ) -> None:
+ mock_adb_shell.return_value = ""
+ ip_addresses = get_exclude_all_host_ipv6_multicast_addresses(self.mock_ad, "wlan0")
+ asserts.assert_equal(ip_addresses, [])
+
@patch("net_tests_utils.host.python.adb_utils.adb_shell")
def test_send_raw_packet_downstream_success(
self, mock_adb_shell: MagicMock
diff --git a/staticlibs/testutils/host/python/apf_test_base.py b/staticlibs/testutils/host/python/apf_test_base.py
index 33b3838..6a62e21 100644
--- a/staticlibs/testutils/host/python/apf_test_base.py
+++ b/staticlibs/testutils/host/python/apf_test_base.py
@@ -70,6 +70,9 @@
# Enable doze mode to activate APF.
adb_utils.set_doze_mode(self.clientDevice, True)
+ # wait for APF to become active.
+ time.sleep(3)
+
def teardown_class(self):
adb_utils.set_doze_mode(self.clientDevice, False)
tether_utils.cleanup_tethering_for_upstream_type(
diff --git a/staticlibs/testutils/host/python/apf_utils.py b/staticlibs/testutils/host/python/apf_utils.py
index 1648d36..4835c23 100644
--- a/staticlibs/testutils/host/python/apf_utils.py
+++ b/staticlibs/testutils/host/python/apf_utils.py
@@ -148,6 +148,38 @@
else:
return []
+def get_exclude_all_host_ipv6_multicast_addresses(
+ ad: android_device.AndroidDevice, iface_name: str
+) -> list[str]:
+ """Retrieves the IPv6 multicast addresses of a given interface on an Android device.
+
+ This function executes an ADB shell command (`ip -6 maddr show`) to get the
+ network interface information and extracts the IPv6 multicast address from the output.
+ If devices have no IPv6 multicast address, raise PatternNotFoundException.
+
+ Args:
+ ad: The Android device object.
+ iface_name: The name of the network interface (e.g., "wlan0").
+
+ Returns:
+ The IPv6 multicast addresses of the interface as a list of string.
+ Return empty list if no IPv6 multicast address.
+ """
+ # output format
+ # 47: wlan0
+ # inet6 ff02::1:ff99:37b0
+ # inet6 ff02::1:ffb7:cba2 users 2
+ # inet6 ff02::1
+ # inet6 ff01::1
+ output = adb_utils.adb_shell(ad, f"ip -6 maddr show {iface_name}")
+ pattern = r"inet6\s+([a-fA-F0-9:]+)(?:\s+users\s+\d+)?"
+ matches = re.findall(pattern, output)
+
+ if matches:
+ return [addr for addr in matches if addr not in ("ff02::1", "ff01::1")]
+ else:
+ return []
+
def get_hardware_address(
ad: android_device.AndroidDevice, iface_name: str
) -> str:
@@ -407,10 +439,28 @@
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.abort_class_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
+
+def apf_ram_at_least(size):
+ def decorator(test_function):
+ @functools.wraps(test_function)
+ def wrapper(self, *args, **kwargs):
+ asserts.abort_class_if(
+ (not hasattr(self, 'clientDevice')) or (not hasattr(self, 'client_iface_name')),
+ "no valid client attribute"
+ )
+
+ caps = get_apf_capabilities(self.clientDevice, self.client_iface_name)
+ asserts.skip_if(
+ caps.apf_ram_size < size,
+ f'APF rame size {caps.apf_ram_size} < {size}'
+ )
return test_function(self, *args, **kwargs)
return wrapper
return decorator
diff --git a/tests/cts/multidevices/apfv6_test.py b/tests/cts/multidevices/apfv6_test.py
index fb45f4a..ee2368d 100644
--- a/tests/cts/multidevices/apfv6_test.py
+++ b/tests/cts/multidevices/apfv6_test.py
@@ -14,7 +14,20 @@
from mobly import asserts
from scapy.layers.inet import IP, ICMP, IPOption_Router_Alert
-from scapy.layers.inet6 import IPv6, ICMPv6EchoRequest, ICMPv6EchoReply
+from scapy.layers.inet6 import (
+ IPv6,
+ IPv6ExtHdrHopByHop,
+ ICMPv6EchoRequest,
+ ICMPv6EchoReply,
+ ICMPv6MLQuery2,
+ ICMPv6MLReport2,
+ ICMPv6MLDMultAddrRec,
+ ICMPv6NDOptSrcLLAddr,
+ ICMPv6NDOptDstLLAddr,
+ ICMPv6ND_NS,
+ ICMPv6ND_NA,
+ RouterAlert
+)
from scapy.layers.l2 import 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
@@ -87,6 +100,24 @@
arp_request, "DROPPED_ARP_REQUEST_REPLIED", arp_reply
)
+ def test_non_dad_ipv6_neighbor_solicitation_offload(self):
+ eth = Ether(src=self.server_mac_address, dst=self.client_mac_address)
+ ip = IPv6(src=self.server_ipv6_addresses[0], dst=self.client_ipv6_addresses[0])
+ icmpv6 = ICMPv6ND_NS(tgt=self.client_ipv6_addresses[0])
+ opt = ICMPv6NDOptSrcLLAddr(lladdr=self.server_mac_address)
+ neighbor_solicitation = bytes(eth/ip/icmpv6/opt).hex()
+
+ eth = Ether(src=self.client_mac_address, dst=self.server_mac_address)
+ ip = IPv6(src=self.client_ipv6_addresses[0], dst=self.server_ipv6_addresses[0])
+ icmpv6 = ICMPv6ND_NA(tgt=self.client_ipv6_addresses[0], R=1, S=1, O=1)
+ opt = ICMPv6NDOptDstLLAddr(lladdr=self.client_mac_address)
+ expected_neighbor_advertisement = bytes(eth/ip/icmpv6/opt).hex()
+ self.send_packet_and_expect_reply_received(
+ neighbor_solicitation,
+ "DROPPED_IPV6_NS_REPLIED_NON_DAD",
+ expected_neighbor_advertisement
+ )
+
@apf_utils.at_least_B()
def test_ipv4_icmp_echo_request_offload(self):
eth = Ether(src=self.server_mac_address, dst=self.client_mac_address)
@@ -103,6 +134,7 @@
)
@apf_utils.at_least_B()
+ @apf_utils.apf_ram_at_least(3000)
def test_ipv6_icmp_echo_request_offload(self):
eth = Ether(src=self.server_mac_address, dst=self.client_mac_address)
ip = IPv6(src=self.server_ipv6_addresses[0], dst=self.client_ipv6_addresses[0])
@@ -161,3 +193,28 @@
self.clientDevice,
f'ip addr del {addr}/32 dev {self.client_iface_name}'
)
+
+ @apf_utils.at_least_B()
+ @apf_utils.apf_ram_at_least(3000)
+ def test_mldv2_general_query_offload(self):
+ ether = Ether(src=self.server_mac_address, dst='33:33:00:00:00:01')
+ ip = IPv6(src=self.server_ipv6_addresses[0], dst='ff02::1', hlim=1)
+ hopOpts = IPv6ExtHdrHopByHop(options=[RouterAlert(otype=5)])
+ mld = ICMPv6MLQuery2()
+ mldv2_general_query = bytes(ether/ip/hopOpts/mld).hex()
+
+ ether = Ether(src=self.client_mac_address, dst='33:33:00:00:00:16')
+ ip = IPv6(src=self.client_ipv6_addresses[0], dst='ff02::16', hlim=1)
+
+ mcast_addrs = apf_utils.get_exclude_all_host_ipv6_multicast_addresses(
+ self.clientDevice, self.client_iface_name
+ )
+
+ mld_records = []
+ for addr in mcast_addrs:
+ mld_records.append(ICMPv6MLDMultAddrRec(dst=addr, rtype=2))
+ mld = ICMPv6MLReport2(records=mld_records)
+ expected_mldv2_report = bytes(ether/ip/hopOpts/mld).hex()
+ self.send_packet_and_expect_reply_received(
+ mldv2_general_query, "DROPPED_IPV6_MLD_V2_GENERAL_QUERY_REPLIED", expected_mldv2_report
+ )
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>()
+ }
}
diff --git a/thread/docs/build-an-android-border-router.md b/thread/docs/build-an-android-border-router.md
index f90a23b..2687e26 100644
--- a/thread/docs/build-an-android-border-router.md
+++ b/thread/docs/build-an-android-border-router.md
@@ -380,10 +380,12 @@
[config_thread.xml](https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Connectivity/service/ServiceConnectivityResources/res/values/config_thread.xml)
for the full list.
-Typically, you must change the `config_thread_vendor_name`,
-`config_thread_vendor_oui` and `config_thread_model_name` to your vendor or
-product values. Those values will be included in the `_meshcop._udp` mDNS
-service which is always advertised by a Thread Border Router.
+Typically, you must set `config_thread_border_router_default_enabled` to `true`
+to enable your device as a Thread Border Router, and change the
+`config_thread_vendor_name`, `config_thread_vendor_oui` and
+`config_thread_model_name` to your vendor or product values. Those values will
+be included in the `_meshcop._udp` mDNS service which is always advertised by a
+Thread Border Router.
To add the overlay, you need to create a new `ConnectivityOverlayOrange`
runtime_resource_overlay target for your Orange device. Create a new
@@ -436,6 +438,7 @@
```
- `config_thread.xml`:
```
+ <bool name="config_thread_border_router_default_enabled">true</bool>
<string translatable="false" name="config_thread_vendor_name">Banana Inc.</string>
<string translatable="false" name="config_thread_vendor_oui">AC:DE:48</string>
<string translatable="false" name="config_thread_model_name">Orange</string>