Merge "Release ClientNetworkRequest before marking request unfulfillable" into main
diff --git a/bpf/loader/NetBpfLoad.cpp b/bpf/loader/NetBpfLoad.cpp
index f11b9a3..d70a2c8 100644
--- a/bpf/loader/NetBpfLoad.cpp
+++ b/bpf/loader/NetBpfLoad.cpp
@@ -856,20 +856,24 @@
ret = getSectionSymNames(elfFile, "maps", mapNames);
if (ret) return ret;
- ret = readSectionByName(".BTF", elfFile, btfData);
- if (ret) {
- ALOGE("Failed to read .BTF section, ret:%d", ret);
- return ret;
- }
- struct btf *btf = btf__new(btfData.data(), btfData.size());
- if (btf == NULL) {
- ALOGE("btf__new failed, errno: %d", errno);
- return -errno;
- }
- auto scopeGuard = base::make_scope_guard([btf] { btf__free(btf); });
+ struct btf *btf = NULL;
+ auto scopeGuard = base::make_scope_guard([btf] { if (btf) btf__free(btf); });
+ if (isAtLeastKernelVersion(4, 18, 0)) {
+ // On Linux Kernels older than 4.18 BPF_BTF_LOAD command doesn't exist.
+ ret = readSectionByName(".BTF", elfFile, btfData);
+ if (ret) {
+ ALOGE("Failed to read .BTF section, ret:%d", ret);
+ return ret;
+ }
+ struct btf *btf = btf__new(btfData.data(), btfData.size());
+ if (btf == NULL) {
+ ALOGE("btf__new failed, errno: %d", errno);
+ return -errno;
+ }
- ret = loadBtf(elfFile, btf);
- if (ret) return ret;
+ ret = loadBtf(elfFile, btf);
+ if (ret) return ret;
+ }
unsigned kvers = kernelVersion();
@@ -997,7 +1001,7 @@
if (isAtLeastKernelVersion(4, 15, 0))
strlcpy(req.map_name, mapNames[i].c_str(), sizeof(req.map_name));
- bool haveBtf = isBtfSupported(type);
+ bool haveBtf = btf && isBtfSupported(type);
if (haveBtf) {
uint32_t kTid, vTid;
ret = getKeyValueTids(btf, mapNames[i].c_str(), md[i].key_size,
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/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/connectivity/DscpPolicyTracker.java b/service/src/com/android/server/connectivity/DscpPolicyTracker.java
index 9c2b9e8..857d705 100644
--- a/service/src/com/android/server/connectivity/DscpPolicyTracker.java
+++ b/service/src/com/android/server/connectivity/DscpPolicyTracker.java
@@ -233,6 +233,11 @@
*/
public void addDscpPolicy(NetworkAgentInfo nai, DscpPolicy policy) {
String iface = nai.linkProperties.getInterfaceName();
+ if (null == iface) {
+ Log.e(TAG, "DSCP policies are not supported on null interfaces.");
+ sendStatus(nai, policy.getPolicyId(), DSCP_POLICY_STATUS_REQUEST_DECLINED);
+ return;
+ }
if (!isEthernet(iface)) {
Log.e(TAG, "DSCP policies are not supported on raw IP interfaces.");
sendStatus(nai, policy.getPolicyId(), DSCP_POLICY_STATUS_REQUEST_DECLINED);
diff --git a/staticlibs/device/com/android/net/module/util/TcUtils.java b/staticlibs/device/com/android/net/module/util/TcUtils.java
index a6b222f..eb119c8 100644
--- a/staticlibs/device/com/android/net/module/util/TcUtils.java
+++ b/staticlibs/device/com/android/net/module/util/TcUtils.java
@@ -16,6 +16,8 @@
package com.android.net.module.util;
+import androidx.annotation.NonNull;
+
import java.io.IOException;
/**
@@ -33,7 +35,7 @@
* @return true if the interface uses an ethernet L2 header.
* @throws IOException
*/
- public static native boolean isEthernet(String iface) throws IOException;
+ public static native boolean isEthernet(@NonNull String iface) throws IOException;
/**
* Attach a tc bpf filter.
diff --git a/staticlibs/native/bpfmapjni/com_android_net_module_util_TcUtils.cpp b/staticlibs/native/bpfmapjni/com_android_net_module_util_TcUtils.cpp
index 2a587b6..22b084c 100644
--- a/staticlibs/native/bpfmapjni/com_android_net_module_util_TcUtils.cpp
+++ b/staticlibs/native/bpfmapjni/com_android_net_module_util_TcUtils.cpp
@@ -32,6 +32,10 @@
static jboolean com_android_net_module_util_TcUtils_isEthernet(JNIEnv *env,
jclass clazz,
jstring iface) {
+ if (nullptr == iface) {
+ jniThrowNullPointerException(env, "iface is null");
+ return false;
+ }
ScopedUtfChars interface(env, iface);
bool result = false;
int error = isEthernet(interface.c_str(), result);
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/cts/net/src/android/net/cts/DscpPolicyTest.kt b/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
index ceccf0b..df4dab5 100644
--- a/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
+++ b/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
@@ -757,6 +757,25 @@
assertEquals(IPPROTO_UDP, policy2.protocol)
assertParcelingIsLossless(policy2)
}
+
+ @Test
+ fun testSendDscpPolicyWithoutInterfaceName() {
+ val nc = NetworkCapabilities().apply {
+ addTransportType(TRANSPORT_TEST)
+ }
+ val agent = TestableNetworkAgent(
+ realContext,
+ handlerThread.looper,
+ nc,
+ LinkProperties() /* note: no interface name */,
+ NetworkAgentConfig.Builder().build()
+ )
+ agentsToCleanUp.add(agent)
+ runAsShell(MANAGE_TEST_NETWORKS) { agent.register() }
+ // Without the fix, this will crash the system with SIGSEGV.
+ agent.sendAddDscpPolicy(DscpPolicy.Builder(1, 1).build())
+ agent.expectCallback<OnDscpPolicyStatusUpdated>()
+ }
}
private fun ByteBuffer.readAsArray(): ByteArray {
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>