Merge "Move isChainEnabled and getUidRule to BpfNetMapsUtils" into main
diff --git a/Cronet/tests/common/Android.bp b/Cronet/tests/common/Android.bp
index edeb0b3..703f544 100644
--- a/Cronet/tests/common/Android.bp
+++ b/Cronet/tests/common/Android.bp
@@ -43,6 +43,7 @@
jni_libs: [
"cronet_aml_components_cronet_android_cronet_tests__testing",
"cronet_aml_third_party_netty_tcnative_netty_tcnative_so__testing",
+ "libnativecoverage",
],
data: [":cronet_javatests_resources"],
}
diff --git a/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java b/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
index 1022b06..f696885 100644
--- a/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
+++ b/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
@@ -176,6 +176,7 @@
// tests, turn tethering on and off before running them.
MyTetheringEventCallback callback = null;
TestNetworkInterface testIface = null;
+ assumeTrue(sEm != null);
try {
// If the physical ethernet interface is available, do nothing.
if (isInterfaceForTetheringAvailable()) return;
diff --git a/bpf_progs/block.c b/bpf_progs/block.c
index 0a2b0b8..152dda6 100644
--- a/bpf_progs/block.c
+++ b/bpf_progs/block.c
@@ -19,8 +19,8 @@
#include <netinet/in.h>
#include <stdint.h>
-// The resulting .o needs to load on the Android T bpfloader
-#define BPFLOADER_MIN_VER BPFLOADER_T_VERSION
+// The resulting .o needs to load on Android T+
+#define BPFLOADER_MIN_VER BPFLOADER_MAINLINE_T_VERSION
#include "bpf_helpers.h"
diff --git a/bpf_progs/clatd.c b/bpf_progs/clatd.c
index addb02f..f83e5ae 100644
--- a/bpf_progs/clatd.c
+++ b/bpf_progs/clatd.c
@@ -30,8 +30,8 @@
#define __kernel_udphdr udphdr
#include <linux/udp.h>
-// The resulting .o needs to load on the Android T bpfloader
-#define BPFLOADER_MIN_VER BPFLOADER_T_VERSION
+// The resulting .o needs to load on Android T+
+#define BPFLOADER_MIN_VER BPFLOADER_MAINLINE_T_VERSION
#include "bpf_helpers.h"
#include "bpf_net_helpers.h"
@@ -265,6 +265,10 @@
*(struct iphdr*)data = ip;
}
+ // Count successfully translated packet
+ __sync_fetch_and_add(&v->packets, 1);
+ __sync_fetch_and_add(&v->bytes, skb->len - l2_header_size);
+
// Redirect, possibly back to same interface, so tcpdump sees packet twice.
if (v->oif) return bpf_redirect(v->oif, BPF_F_INGRESS);
@@ -416,6 +420,10 @@
// Copy over the new ipv6 header without an ethernet header.
*(struct ipv6hdr*)data = ip6;
+ // Count successfully translated packet
+ __sync_fetch_and_add(&v->packets, 1);
+ __sync_fetch_and_add(&v->bytes, skb->len);
+
// Redirect to non v4-* interface. Tcpdump only sees packet after this redirect.
return bpf_redirect(v->oif, 0 /* this is effectively BPF_F_EGRESS */);
}
diff --git a/bpf_progs/clatd.h b/bpf_progs/clatd.h
index b5f1cdc..a75798f 100644
--- a/bpf_progs/clatd.h
+++ b/bpf_progs/clatd.h
@@ -39,8 +39,10 @@
typedef struct {
uint32_t oif; // The output interface to redirect to (0 means don't redirect)
struct in_addr local4; // The destination IPv4 address
+ uint64_t packets; // Count of translated gso (large) packets
+ uint64_t bytes; // Sum of post-translation skb->len
} ClatIngress6Value;
-STRUCT_SIZE(ClatIngress6Value, 4 + 4); // 8
+STRUCT_SIZE(ClatIngress6Value, 4 + 4 + 8 + 8); // 24
typedef struct {
uint32_t iif; // The input interface index
@@ -54,7 +56,9 @@
struct in6_addr pfx96; // The destination /96 nat64 prefix, bottom 32 bits must be 0
bool oifIsEthernet; // Whether the output interface requires ethernet header
uint8_t pad[3];
+ uint64_t packets; // Count of translated gso (large) packets
+ uint64_t bytes; // Sum of post-translation skb->len
} ClatEgress4Value;
-STRUCT_SIZE(ClatEgress4Value, 4 + 2 * 16 + 1 + 3); // 40
+STRUCT_SIZE(ClatEgress4Value, 4 + 2 * 16 + 1 + 3 + 8 + 8); // 56
#undef STRUCT_SIZE
diff --git a/bpf_progs/dscpPolicy.c b/bpf_progs/dscpPolicy.c
index e845a69..ed114e4 100644
--- a/bpf_progs/dscpPolicy.c
+++ b/bpf_progs/dscpPolicy.c
@@ -27,8 +27,8 @@
#include <stdint.h>
#include <string.h>
-// The resulting .o needs to load on the Android T bpfloader
-#define BPFLOADER_MIN_VER BPFLOADER_T_VERSION
+// The resulting .o needs to load on Android T+
+#define BPFLOADER_MIN_VER BPFLOADER_MAINLINE_T_VERSION
#include "bpf_helpers.h"
#include "dscpPolicy.h"
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
index 5e401aa..dfc7699 100644
--- a/bpf_progs/netd.c
+++ b/bpf_progs/netd.c
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-// The resulting .o needs to load on the Android T bpfloader
-#define BPFLOADER_MIN_VER BPFLOADER_T_VERSION
+// The resulting .o needs to load on Android T+
+#define BPFLOADER_MIN_VER BPFLOADER_MAINLINE_T_VERSION
#include <bpf_helpers.h>
#include <linux/bpf.h>
@@ -103,13 +103,13 @@
// A single-element configuration array, packet tracing is enabled when 'true'.
DEFINE_BPF_MAP_EXT(packet_trace_enabled_map, ARRAY, uint32_t, bool, 1,
AID_ROOT, AID_SYSTEM, 0060, "fs_bpf_net_shared", "", PRIVATE,
- BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, LOAD_ON_ENG,
+ BPFLOADER_MAINLINE_U_VERSION, BPFLOADER_MAX_VER, LOAD_ON_ENG,
LOAD_ON_USER, LOAD_ON_USERDEBUG)
// A ring buffer on which packet information is pushed.
DEFINE_BPF_RINGBUF_EXT(packet_trace_ringbuf, PacketTrace, PACKET_TRACE_BUF_SIZE,
AID_ROOT, AID_SYSTEM, 0060, "fs_bpf_net_shared", "", PRIVATE,
- BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, LOAD_ON_ENG,
+ BPFLOADER_MAINLINE_U_VERSION, BPFLOADER_MAX_VER, LOAD_ON_ENG,
LOAD_ON_USER, LOAD_ON_USERDEBUG);
DEFINE_BPF_MAP_RO_NETD(data_saver_enabled_map, ARRAY, uint32_t, bool,
@@ -516,7 +516,7 @@
// This program is optional, and enables tracing on Android U+, 5.8+ on user builds.
DEFINE_BPF_PROG_EXT("cgroupskb/ingress/stats$trace_user", AID_ROOT, AID_SYSTEM,
bpf_cgroup_ingress_trace_user, KVER_5_8, KVER_INF,
- BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, OPTIONAL,
+ BPFLOADER_MAINLINE_U_VERSION, BPFLOADER_MAX_VER, OPTIONAL,
"fs_bpf_netd_readonly", "",
IGNORE_ON_ENG, LOAD_ON_USER, IGNORE_ON_USERDEBUG)
(struct __sk_buff* skb) {
@@ -526,7 +526,7 @@
// This program is required, and enables tracing on Android U+, 5.8+, userdebug/eng.
DEFINE_BPF_PROG_EXT("cgroupskb/ingress/stats$trace", AID_ROOT, AID_SYSTEM,
bpf_cgroup_ingress_trace, KVER_5_8, KVER_INF,
- BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, MANDATORY,
+ BPFLOADER_MAINLINE_U_VERSION, BPFLOADER_MAX_VER, MANDATORY,
"fs_bpf_netd_readonly", "",
LOAD_ON_ENG, IGNORE_ON_USER, LOAD_ON_USERDEBUG)
(struct __sk_buff* skb) {
@@ -548,7 +548,7 @@
// This program is optional, and enables tracing on Android U+, 5.8+ on user builds.
DEFINE_BPF_PROG_EXT("cgroupskb/egress/stats$trace_user", AID_ROOT, AID_SYSTEM,
bpf_cgroup_egress_trace_user, KVER_5_8, KVER_INF,
- BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, OPTIONAL,
+ BPFLOADER_MAINLINE_U_VERSION, BPFLOADER_MAX_VER, OPTIONAL,
"fs_bpf_netd_readonly", "",
IGNORE_ON_ENG, LOAD_ON_USER, IGNORE_ON_USERDEBUG)
(struct __sk_buff* skb) {
@@ -558,7 +558,7 @@
// This program is required, and enables tracing on Android U+, 5.8+, userdebug/eng.
DEFINE_BPF_PROG_EXT("cgroupskb/egress/stats$trace", AID_ROOT, AID_SYSTEM,
bpf_cgroup_egress_trace, KVER_5_8, KVER_INF,
- BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, MANDATORY,
+ BPFLOADER_MAINLINE_U_VERSION, BPFLOADER_MAX_VER, MANDATORY,
"fs_bpf_netd_readonly", "",
LOAD_ON_ENG, IGNORE_ON_USER, LOAD_ON_USERDEBUG)
(struct __sk_buff* skb) {
diff --git a/bpf_progs/offload.c b/bpf_progs/offload.c
index dd59dca..4f152bf 100644
--- a/bpf_progs/offload.c
+++ b/bpf_progs/offload.c
@@ -28,11 +28,11 @@
// BTF is incompatible with bpfloaders < v0.10, hence for S (v0.2) we must
// ship a different file than for later versions, but we need bpfloader v0.25+
// for obj@ver.o support
-#define BPFLOADER_MIN_VER BPFLOADER_OBJ_AT_VER_VERSION
+#define BPFLOADER_MIN_VER BPFLOADER_MAINLINE_T_VERSION
#else /* MAINLINE */
// The resulting .o needs to load on the Android S bpfloader
#define BPFLOADER_MIN_VER BPFLOADER_S_VERSION
-#define BPFLOADER_MAX_VER BPFLOADER_OBJ_AT_VER_VERSION
+#define BPFLOADER_MAX_VER BPFLOADER_T_VERSION
#endif /* MAINLINE */
// Warning: values other than AID_ROOT don't work for map uid on BpfLoader < v0.21
diff --git a/bpf_progs/test.c b/bpf_progs/test.c
index e2b8ea5..fff3512 100644
--- a/bpf_progs/test.c
+++ b/bpf_progs/test.c
@@ -22,11 +22,11 @@
// BTF is incompatible with bpfloaders < v0.10, hence for S (v0.2) we must
// ship a different file than for later versions, but we need bpfloader v0.25+
// for obj@ver.o support
-#define BPFLOADER_MIN_VER BPFLOADER_OBJ_AT_VER_VERSION
+#define BPFLOADER_MIN_VER BPFLOADER_MAINLINE_T_VERSION
#else /* MAINLINE */
// The resulting .o needs to load on the Android S bpfloader
#define BPFLOADER_MIN_VER BPFLOADER_S_VERSION
-#define BPFLOADER_MAX_VER BPFLOADER_OBJ_AT_VER_VERSION
+#define BPFLOADER_MAX_VER BPFLOADER_T_VERSION
#endif /* MAINLINE */
// Warning: values other than AID_ROOT don't work for map uid on BpfLoader < v0.21
diff --git a/common/src/com/android/net/module/util/bpf/ClatEgress4Value.java b/common/src/com/android/net/module/util/bpf/ClatEgress4Value.java
index 69fab09..71f7516 100644
--- a/common/src/com/android/net/module/util/bpf/ClatEgress4Value.java
+++ b/common/src/com/android/net/module/util/bpf/ClatEgress4Value.java
@@ -36,11 +36,24 @@
@Field(order = 3, type = Type.U8, padding = 3)
public final short oifIsEthernet; // Whether the output interface requires ethernet header
+ @Field(order = 4, type = Type.U63)
+ public final long packets; // Count of translated gso (large) packets
+
+ @Field(order = 5, type = Type.U63)
+ public final long bytes; // Sum of post-translation skb->len
+
public ClatEgress4Value(final int oif, final Inet6Address local6, final Inet6Address pfx96,
- final short oifIsEthernet) {
+ final short oifIsEthernet, final long packets, final long bytes) {
this.oif = oif;
this.local6 = local6;
this.pfx96 = pfx96;
this.oifIsEthernet = oifIsEthernet;
+ this.packets = packets;
+ this.bytes = bytes;
+ }
+
+ public ClatEgress4Value(final int oif, final Inet6Address local6, final Inet6Address pfx96,
+ final short oifIsEthernet) {
+ this(oif, local6, pfx96, oifIsEthernet, 0, 0);
}
}
diff --git a/common/src/com/android/net/module/util/bpf/ClatIngress6Value.java b/common/src/com/android/net/module/util/bpf/ClatIngress6Value.java
index fb81caa..25f737b 100644
--- a/common/src/com/android/net/module/util/bpf/ClatIngress6Value.java
+++ b/common/src/com/android/net/module/util/bpf/ClatIngress6Value.java
@@ -30,8 +30,21 @@
@Field(order = 1, type = Type.Ipv4Address)
public final Inet4Address local4; // The destination IPv4 address
- public ClatIngress6Value(final int oif, final Inet4Address local4) {
+ @Field(order = 2, type = Type.U63)
+ public final long packets; // Count of translated gso (large) packets
+
+ @Field(order = 3, type = Type.U63)
+ public final long bytes; // Sum of post-translation skb->len
+
+ public ClatIngress6Value(final int oif, final Inet4Address local4, final long packets,
+ final long bytes) {
this.oif = oif;
this.local4 = local4;
+ this.packets = packets;
+ this.bytes = bytes;
+ }
+
+ public ClatIngress6Value(final int oif, final Inet4Address local4) {
+ this(oif, local4, 0, 0);
}
}
diff --git a/netbpfload/NetBpfLoad.cpp b/netbpfload/NetBpfLoad.cpp
index c6fe9ca..83bb98c 100644
--- a/netbpfload/NetBpfLoad.cpp
+++ b/netbpfload/NetBpfLoad.cpp
@@ -257,13 +257,8 @@
logTetheringApexVersion();
- if (has_platform_bpfloader_rc && !has_platform_netbpfload_rc) {
- // Tethering apex shipped initrc file causes us to reach here
- // but we're not ready to correctly handle anything before U QPR2
- // in which the 'bpfloader' vs 'netbpfload' split happened
- const char * args[] = { platformBpfLoader, NULL, };
- execve(args[0], (char**)args, envp);
- ALOGE("exec '%s' fail: %d[%s]", platformBpfLoader, errno, strerror(errno));
+ if (!isAtLeastT) {
+ ALOGE("Impossible - not reachable on Android <T.");
return 1;
}
@@ -318,14 +313,16 @@
return 1;
}
- if (isAtLeastU) {
+ if (false && isAtLeastV) {
// Linux 5.16-rc1 changed the default to 2 (disabled but changeable),
// but we need 0 (enabled)
// (this writeFile is known to fail on at least 4.19, but always defaults to 0 on
// pre-5.13, on 5.13+ it depends on CONFIG_BPF_UNPRIV_DEFAULT_OFF)
if (writeProcSysFile("/proc/sys/kernel/unprivileged_bpf_disabled", "0\n") &&
android::bpf::isAtLeastKernelVersion(5, 13, 0)) return 1;
+ }
+ if (isAtLeastU) {
// Enable the eBPF JIT -- but do note that on 64-bit kernels it is likely
// already force enabled by the kernel config option BPF_JIT_ALWAYS_ON.
// (Note: this (open) will fail with ENOENT 'No such file or directory' if
@@ -355,8 +352,11 @@
// Thus we need to manually create the /sys/fs/bpf/loader subdirectory.
if (createSysFsBpfSubDir("loader")) return 1;
- // This is Network BpfLoader v0.42
- const unsigned int bpfloader_ver = 42u;
+ // Version of Network BpfLoader depends on the Android OS version
+ unsigned int bpfloader_ver = 42u; // [42] BPFLOADER_MAINLINE_VERSION
+ if (isAtLeastT) ++bpfloader_ver; // [43] BPFLOADER_MAINLINE_T_VERSION
+ if (isAtLeastU) ++bpfloader_ver; // [44] BPFLOADER_MAINLINE_U_VERSION
+ if (isAtLeastV) ++bpfloader_ver; // [45] BPFLOADER_MAINLINE_V_VERSION
// Load all ELF objects, create programs and maps, and pin them
for (const auto& location : locations) {
@@ -380,10 +380,15 @@
return 1;
}
- ALOGI("done, transferring control to platform bpfloader.");
+ if (false && isAtLeastV) {
+ ALOGI("done, transferring control to platform bpfloader.");
- const char * args[] = { platformBpfLoader, NULL, };
- execve(args[0], (char**)args, envp);
- ALOGE("FATAL: execve('%s'): %d[%s]", platformBpfLoader, errno, strerror(errno));
- return 1;
+ const char * args[] = { platformBpfLoader, NULL, };
+ execve(args[0], (char**)args, envp);
+ ALOGE("FATAL: execve('%s'): %d[%s]", platformBpfLoader, errno, strerror(errno));
+ return 1;
+ }
+
+ ALOGI("mainline done!");
+ return 0;
}
diff --git a/netbpfload/loader.cpp b/netbpfload/loader.cpp
index a125ffd..9dd0d2a 100644
--- a/netbpfload/loader.cpp
+++ b/netbpfload/loader.cpp
@@ -1087,7 +1087,7 @@
return 0;
}
-int loadProg(const char* elfPath, bool* isCritical, const unsigned int bpfloader_ver,
+int loadProg(const char* const elfPath, bool* const isCritical, const unsigned int bpfloader_ver,
const Location& location) {
vector<char> license;
vector<char> critical;
diff --git a/netbpfload/netbpfload.mainline.rc b/netbpfload/netbpfload.mainline.rc
index 0ac5de8..d7202f7 100644
--- a/netbpfload/netbpfload.mainline.rc
+++ b/netbpfload/netbpfload.mainline.rc
@@ -1,8 +1,16 @@
-service bpfloader /apex/com.android.tethering/bin/netbpfload
+service mdnsd_loadbpf /system/bin/bpfloader
capabilities CHOWN SYS_ADMIN NET_ADMIN
group root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw system
user root
rlimit memlock 1073741824 1073741824
oneshot
reboot_on_failure reboot,bpfloader-failed
+
+service bpfloader /apex/com.android.tethering/bin/netbpfload
+ capabilities CHOWN SYS_ADMIN NET_ADMIN
+ group system root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw
+ user system
+ rlimit memlock 1073741824 1073741824
+ oneshot
+ reboot_on_failure reboot,bpfloader-failed
override
diff --git a/netd/BpfHandler.cpp b/netd/BpfHandler.cpp
index a00c363..e6fc825 100644
--- a/netd/BpfHandler.cpp
+++ b/netd/BpfHandler.cpp
@@ -165,8 +165,35 @@
BpfHandler::BpfHandler(uint32_t perUidLimit, uint32_t totalLimit)
: mPerUidStatsEntriesLimit(perUidLimit), mTotalUidStatsEntriesLimit(totalLimit) {}
+// copied with minor changes from waitForProgsLoaded()
+// p/m/C's staticlibs/native/bpf_headers/include/bpf/WaitForProgsLoaded.h
+static inline void waitForNetProgsLoaded() {
+ // infinite loop until success with 5/10/20/40/60/60/60... delay
+ for (int delay = 5;; delay *= 2) {
+ if (delay > 60) delay = 60;
+ if (base::WaitForProperty("init.svc.bpfloader", "stopped", std::chrono::seconds(delay))
+ && !access("/sys/fs/bpf/netd_shared", F_OK))
+ return;
+ ALOGW("Waited %ds for init.svc.bpfloader=stopped, still waiting...", delay);
+ }
+}
+
Status BpfHandler::init(const char* cg2_path) {
// Make sure BPF programs are loaded before doing anything
+ ALOGI("Waiting for BPF programs");
+
+ if (true || !modules::sdklevel::IsAtLeastV()) {
+ waitForNetProgsLoaded();
+ ALOGI("Networking BPF programs are loaded");
+
+ if (!base::SetProperty("ctl.start", "mdnsd_loadbpf")) {
+ ALOGE("Failed to set property ctl.start=mdnsd_loadbpf, see dmesg for reason.");
+ abort();
+ }
+
+ ALOGI("Waiting for remaining BPF programs");
+ }
+
android::bpf::waitForProgsLoaded();
ALOGI("BPF programs are loaded");
diff --git a/service/ServiceConnectivityResources/res/values/config_thread.xml b/service/ServiceConnectivityResources/res/values/config_thread.xml
index f7e47f5..4783f2b 100644
--- a/service/ServiceConnectivityResources/res/values/config_thread.xml
+++ b/service/ServiceConnectivityResources/res/values/config_thread.xml
@@ -31,4 +31,26 @@
Thread Network regulatory purposes.
-->
<bool name="config_thread_location_use_for_country_code_enabled">true</bool>
+
+ <!-- Specifies the UTF-8 vendor name of this device. If this value is not an empty string, it
+ will be included in TXT value (key is 'vn') of the "_meshcop._udp" mDNS service which is
+ published by the Thread service. A non-empty string value must not exceed length of 24 UTF-8
+ bytes.
+ -->
+ <string translatable="false" name="config_thread_vendor_name">Android</string>
+
+ <!-- Specifies the 24 bits vendor OUI of this device. If this value is not an empty string, it
+ will be included in TXT (key is 'vo') value of the "_meshcop._udp" mDNS service which is
+ published by the Thread service. The OUI can be represented as a base-16 number of six
+ hexadecimal digits, or octets separated by hyphens or dots. For example, "ACDE48", "AC-DE-48"
+ and "AC:DE:48" are all valid representations of the same OUI value.
+ -->
+ <string translatable="false" name="config_thread_vendor_oui"></string>
+
+ <!-- Specifies the UTF-8 product model name of this device. If this value is not an empty
+ string, it will be included in TXT (key is 'mn') value of the "_meshcop._udp" mDNS service
+ which is published by the Thread service. A non-empty string value must not exceed length of 24
+ UTF-8 bytes.
+ -->
+ <string translatable="false" name="config_thread_model_name">Thread Border Router</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values/overlayable.xml b/service/ServiceConnectivityResources/res/values/overlayable.xml
index d9af5a3..158b0c8 100644
--- a/service/ServiceConnectivityResources/res/values/overlayable.xml
+++ b/service/ServiceConnectivityResources/res/values/overlayable.xml
@@ -48,6 +48,9 @@
<!-- Configuration values for ThreadNetworkService -->
<item type="bool" name="config_thread_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" />
+ <item type="string" name="config_thread_model_name" />
</policy>
</overlayable>
</resources>
diff --git a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
index c125bd6..4214bc9 100644
--- a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
+++ b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
@@ -113,7 +113,12 @@
if (!modules::sdklevel::IsAtLeastT()) return;
V("/sys/fs/bpf", S_IFDIR|S_ISVTX|0777, ROOT, ROOT, "fs_bpf", DIR);
- V("/sys/fs/bpf/net_shared", S_IFDIR|S_ISVTX|0777, ROOT, ROOT, "fs_bpf_net_shared", DIR);
+
+ if (false && modules::sdklevel::IsAtLeastV()) {
+ V("/sys/fs/bpf/net_shared", S_IFDIR|01777, ROOT, ROOT, "fs_bpf_net_shared", DIR);
+ } else {
+ V("/sys/fs/bpf/net_shared", S_IFDIR|01777, SYSTEM, SYSTEM, "fs_bpf_net_shared", DIR);
+ }
// pre-U we do not have selinux privs to getattr on bpf maps/progs
// so while the below *should* be as listed, we have no way to actually verify
diff --git a/service/src/com/android/server/connectivity/ClatCoordinator.java b/service/src/com/android/server/connectivity/ClatCoordinator.java
index daaf91d..eea16bf 100644
--- a/service/src/com/android/server/connectivity/ClatCoordinator.java
+++ b/service/src/com/android/server/connectivity/ClatCoordinator.java
@@ -847,12 +847,12 @@
if (mIngressMap.isEmpty()) {
pw.println("<empty>");
}
- pw.println("BPF ingress map: iif nat64Prefix v6Addr -> v4Addr oif");
+ pw.println("BPF ingress map: iif nat64Prefix v6Addr -> v4Addr oif (packets bytes)");
pw.increaseIndent();
mIngressMap.forEach((k, v) -> {
// TODO: print interface name
- pw.println(String.format("%d %s/96 %s -> %s %d", k.iif, k.pfx96, k.local6,
- v.local4, v.oif));
+ pw.println(String.format("%d %s/96 %s -> %s %d (%d %d)", k.iif, k.pfx96, k.local6,
+ v.local4, v.oif, v.packets, v.bytes));
});
pw.decreaseIndent();
} catch (ErrnoException e) {
@@ -870,12 +870,13 @@
if (mEgressMap.isEmpty()) {
pw.println("<empty>");
}
- pw.println("BPF egress map: iif v4Addr -> v6Addr nat64Prefix oif");
+ pw.println("BPF egress map: iif v4Addr -> v6Addr nat64Prefix oif (packets bytes)");
pw.increaseIndent();
mEgressMap.forEach((k, v) -> {
// TODO: print interface name
- pw.println(String.format("%d %s -> %s %s/96 %d %s", k.iif, k.local4, v.local6,
- v.pfx96, v.oif, v.oifIsEthernet != 0 ? "ether" : "rawip"));
+ pw.println(String.format("%d %s -> %s %s/96 %d %s (%d %d)", k.iif, k.local4,
+ v.local6, v.pfx96, v.oif, v.oifIsEthernet != 0 ? "ether" : "rawip",
+ v.packets, v.bytes));
});
pw.decreaseIndent();
} catch (ErrnoException e) {
diff --git a/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h b/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
index 92ddf44..dc7925e 100644
--- a/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
+++ b/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
@@ -37,17 +37,27 @@
#define BPFLOADER_IGNORED_ON_VERSION 33u
// Android U / 14 (api level 34) - various new program types added
-#define BPFLOADER_U_VERSION 37u
+#define BPFLOADER_U_VERSION 38u
// Android V / 15 (api level 35) - platform only
// (note: the platform bpfloader in V isn't really versioned at all,
// as there is no need as it can only load objects compiled at the
// same time as itself and the rest of the platform)
-#define BPFLOADER_V_VERSION 41u
+#define BPFLOADER_PLATFORM_VERSION 41u
-// Android Mainline - this bpfloader should eventually go back to T
+// Android Mainline - this bpfloader should eventually go back to T (or even S)
+// Note: this value (and the following +1u's) are hardcoded in NetBpfLoad.cpp
#define BPFLOADER_MAINLINE_VERSION 42u
+// Android Mainline BpfLoader when running on Android T
+#define BPFLOADER_MAINLINE_T_VERSION (BPFLOADER_MAINLINE_VERSION + 1u)
+
+// Android Mainline BpfLoader when running on Android U
+#define BPFLOADER_MAINLINE_U_VERSION (BPFLOADER_MAINLINE_T_VERSION + 1u)
+
+// Android Mainline BpfLoader when running on Android V
+#define BPFLOADER_MAINLINE_V_VERSION (BPFLOADER_MAINLINE_U_VERSION + 1u)
+
/* For mainline module use, you can #define BPFLOADER_{MIN/MAX}_VER
* before #include "bpf_helpers.h" to change which bpfloaders will
* process the resulting .o file.
@@ -57,7 +67,7 @@
* In which case it's just best to use the default.
*/
#ifndef BPFLOADER_MIN_VER
-#define BPFLOADER_MIN_VER BPFLOADER_V_VERSION
+#define BPFLOADER_MIN_VER BPFLOADER_PLATFORM_VERSION
#endif
#ifndef BPFLOADER_MAX_VER
diff --git a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
index 7af3c83..d052551 100644
--- a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
@@ -348,7 +348,9 @@
}
}
- private fun isEthernetSupported() = em != null
+ private fun isEthernetSupported() : Boolean {
+ return context.getSystemService(EthernetManager::class.java) != null
+ }
@Before
fun setUp() {
diff --git a/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java b/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
index 88044be..da7fda3 100644
--- a/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
@@ -526,13 +526,13 @@
+ "v4: /192.0.0.46, v6: /2001:db8:0:b11::464, pfx96: /64:ff9b::, "
+ "pid: 10483, cookie: 27149", dumpStrings[0].trim());
assertEquals("Forwarding rules:", dumpStrings[1].trim());
- assertEquals("BPF ingress map: iif nat64Prefix v6Addr -> v4Addr oif",
+ assertEquals("BPF ingress map: iif nat64Prefix v6Addr -> v4Addr oif (packets bytes)",
dumpStrings[2].trim());
- assertEquals("1000 /64:ff9b::/96 /2001:db8:0:b11::464 -> /192.0.0.46 1001",
+ assertEquals("1000 /64:ff9b::/96 /2001:db8:0:b11::464 -> /192.0.0.46 1001 (0 0)",
dumpStrings[3].trim());
- assertEquals("BPF egress map: iif v4Addr -> v6Addr nat64Prefix oif",
+ assertEquals("BPF egress map: iif v4Addr -> v6Addr nat64Prefix oif (packets bytes)",
dumpStrings[4].trim());
- assertEquals("1001 /192.0.0.46 -> /2001:db8:0:b11::464 /64:ff9b::/96 1000 ether",
+ assertEquals("1001 /192.0.0.46 -> /2001:db8:0:b11::464 /64:ff9b::/96 1000 ether (0 0)",
dumpStrings[5].trim());
} else {
assertEquals(1, dumpStrings.length);
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
index 09236b1..f279c5a 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
@@ -703,14 +703,13 @@
final MdnsSearchOptions searchOptions2 = MdnsSearchOptions.newBuilder()
.addSubtype("subtype2").build();
startSendAndReceive(mockListenerOne, searchOptions1);
- currentThreadExecutor.getAndClearSubmittedRunnable().run();
+ currentThreadExecutor.getAndClearLastScheduledRunnable().run();
InOrder inOrder = inOrder(mockListenerOne, mockSocketClient, mockDeps);
// Verify the query asks for subtype1
final ArgumentCaptor<DatagramPacket> subtype1QueryCaptor =
ArgumentCaptor.forClass(DatagramPacket.class);
- currentThreadExecutor.getAndClearLastScheduledRunnable().run();
// Send twice for IPv4 and IPv6
inOrder.verify(mockSocketClient, times(2)).sendPacketRequestingUnicastResponse(
subtype1QueryCaptor.capture(),
diff --git a/thread/demoapp/Android.bp b/thread/demoapp/Android.bp
index fcfd469..117b4f9 100644
--- a/thread/demoapp/Android.bp
+++ b/thread/demoapp/Android.bp
@@ -34,7 +34,19 @@
libs: [
"framework-connectivity-t",
],
+ required: [
+ "privapp-permissions-com.android.threadnetwork.demoapp",
+ ],
+ system_ext_specific: true,
certificate: "platform",
privileged: true,
platform_apis: true,
}
+
+prebuilt_etc {
+ name: "privapp-permissions-com.android.threadnetwork.demoapp",
+ src: "privapp-permissions-com.android.threadnetwork.demoapp.xml",
+ sub_dir: "permissions",
+ filename_from_src: true,
+ system_ext_specific: true,
+}
diff --git a/thread/demoapp/privapp-permissions-com.android.threadnetwork.demoapp.xml b/thread/demoapp/privapp-permissions-com.android.threadnetwork.demoapp.xml
new file mode 100644
index 0000000..1995e60
--- /dev/null
+++ b/thread/demoapp/privapp-permissions-com.android.threadnetwork.demoapp.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- The privileged permissions needed by the com.android.threadnetwork.demoapp app. -->
+<permissions>
+ <privapp-permissions package="com.android.threadnetwork.demoapp">
+ <permission name="android.permission.THREAD_NETWORK_PRIVILEGED" />
+ </privapp-permissions>
+</permissions>
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
index 1b36d2b..815a36e9 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
@@ -68,6 +68,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.InetAddresses;
import android.net.LinkAddress;
@@ -106,8 +107,10 @@
import android.util.Log;
import android.util.SparseArray;
+import com.android.connectivity.resources.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.ServiceManagerWrapper;
+import com.android.server.connectivity.ConnectivityResources;
import com.android.server.thread.openthread.BackboneRouterState;
import com.android.server.thread.openthread.BorderRouterConfigurationParcel;
import com.android.server.thread.openthread.IChannelMasksReceiver;
@@ -115,12 +118,16 @@
import com.android.server.thread.openthread.IOtDaemonCallback;
import com.android.server.thread.openthread.IOtStatusReceiver;
import com.android.server.thread.openthread.Ipv6AddressInfo;
+import com.android.server.thread.openthread.MeshcopTxtAttributes;
import com.android.server.thread.openthread.OtDaemonState;
+import libcore.util.HexEncoding;
+
import java.io.IOException;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
+import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.time.Instant;
import java.util.HashMap;
@@ -129,6 +136,7 @@
import java.util.Objects;
import java.util.Random;
import java.util.function.Supplier;
+import java.util.regex.Pattern;
/**
* Implementation of the {@link ThreadNetworkController} API.
@@ -143,6 +151,16 @@
final class ThreadNetworkControllerService extends IThreadNetworkController.Stub {
private static final String TAG = "ThreadNetworkService";
+ // The model name length in utf-8 bytes
+ private static final int MAX_MODEL_NAME_UTF8_BYTES = 24;
+
+ // The max vendor name length in utf-8 bytes
+ private static final int MAX_VENDOR_NAME_UTF8_BYTES = 24;
+
+ // This regex pattern allows "XXXXXX", "XX:XX:XX" and "XX-XX-XX" OUI formats.
+ // Note that this regex allows "XX:XX-XX" as well but we don't need to be a strict checker
+ private static final String OUI_REGEX = "^([0-9A-Fa-f]{2}[:-]?){2}([0-9A-Fa-f]{2})$";
+
// Below member fields can be accessed from both the binder and handler threads
private final Context mContext;
@@ -159,6 +177,7 @@
private final InfraInterfaceController mInfraIfController;
private final NsdPublisher mNsdPublisher;
private final OtDaemonCallbackProxy mOtDaemonCallbackProxy = new OtDaemonCallbackProxy();
+ private final ConnectivityResources mResources;
@Nullable private IOtDaemon mOtDaemon;
@Nullable private NetworkAgent mNetworkAgent;
@@ -188,7 +207,8 @@
InfraInterfaceController infraIfController,
ThreadPersistentSettings persistentSettings,
NsdPublisher nsdPublisher,
- UserManager userManager) {
+ UserManager userManager,
+ ConnectivityResources resources) {
mContext = context;
mHandler = handler;
mNetworkProvider = networkProvider;
@@ -202,6 +222,7 @@
mPersistentSettings = persistentSettings;
mNsdPublisher = nsdPublisher;
mUserManager = userManager;
+ mResources = resources;
}
public static ThreadNetworkControllerService newInstance(
@@ -222,7 +243,8 @@
new InfraInterfaceController(),
persistentSettings,
NsdPublisher.newInstance(context, handler),
- context.getSystemService(UserManager.class));
+ context.getSystemService(UserManager.class),
+ new ConnectivityResources(context));
}
private static Inet6Address bytesToInet6Address(byte[] addressBytes) {
@@ -279,7 +301,13 @@
.build();
}
- private void initializeOtDaemon() {
+ private void maybeInitializeOtDaemon() {
+ if (!isEnabled()) {
+ return;
+ }
+
+ Log.i(TAG, "Starting OT daemon...");
+
try {
getOtDaemon();
} catch (RemoteException e) {
@@ -298,23 +326,65 @@
if (otDaemon == null) {
throw new RemoteException("Internal error: failed to start OT daemon");
}
- otDaemon.initialize(mTunIfController.getTunFd(), isEnabled(), mNsdPublisher);
+
+ otDaemon.initialize(
+ mTunIfController.getTunFd(),
+ isEnabled(),
+ mNsdPublisher,
+ getMeshcopTxtAttributes(mResources.get()));
otDaemon.registerStateCallback(mOtDaemonCallbackProxy, -1);
otDaemon.asBinder().linkToDeath(() -> mHandler.post(this::onOtDaemonDied), 0);
mOtDaemon = otDaemon;
return mOtDaemon;
}
+ @VisibleForTesting
+ static MeshcopTxtAttributes getMeshcopTxtAttributes(Resources resources) {
+ final String modelName = resources.getString(R.string.config_thread_model_name);
+ final String vendorName = resources.getString(R.string.config_thread_vendor_name);
+ final String vendorOui = resources.getString(R.string.config_thread_vendor_oui);
+
+ if (!modelName.isEmpty()) {
+ if (modelName.getBytes(StandardCharsets.UTF_8).length > MAX_MODEL_NAME_UTF8_BYTES) {
+ throw new IllegalStateException(
+ "Model name is longer than "
+ + MAX_MODEL_NAME_UTF8_BYTES
+ + "utf-8 bytes: "
+ + modelName);
+ }
+ }
+
+ if (!vendorName.isEmpty()) {
+ if (vendorName.getBytes(StandardCharsets.UTF_8).length > MAX_VENDOR_NAME_UTF8_BYTES) {
+ throw new IllegalStateException(
+ "Vendor name is longer than "
+ + MAX_VENDOR_NAME_UTF8_BYTES
+ + " utf-8 bytes: "
+ + vendorName);
+ }
+ }
+
+ if (!vendorOui.isEmpty() && !Pattern.compile(OUI_REGEX).matcher(vendorOui).matches()) {
+ throw new IllegalStateException("Vendor OUI is invalid: " + vendorOui);
+ }
+
+ MeshcopTxtAttributes meshcopTxts = new MeshcopTxtAttributes();
+ meshcopTxts.modelName = modelName;
+ meshcopTxts.vendorName = vendorName;
+ meshcopTxts.vendorOui = HexEncoding.decode(vendorOui.replace("-", "").replace(":", ""));
+ return meshcopTxts;
+ }
+
private void onOtDaemonDied() {
checkOnHandlerThread();
- Log.w(TAG, "OT daemon is dead, clean up and restart it...");
+ Log.w(TAG, "OT daemon is dead, clean up...");
OperationReceiverWrapper.onOtDaemonDied();
mOtDaemonCallbackProxy.onOtDaemonDied();
mTunIfController.onOtDaemonDied();
mNsdPublisher.onOtDaemonDied();
mOtDaemon = null;
- initializeOtDaemon();
+ maybeInitializeOtDaemon();
}
public void initialize() {
@@ -332,7 +402,7 @@
requestThreadNetwork();
mUserRestricted = isThreadUserRestricted();
registerUserRestrictionsReceiver();
- initializeOtDaemon();
+ maybeInitializeOtDaemon();
});
}
@@ -891,6 +961,13 @@
String countryCode, @NonNull OperationReceiverWrapper receiver) {
checkOnHandlerThread();
+ // Fails early to avoid waking up ot-daemon by the ThreadNetworkCountryCode class
+ if (!isEnabled()) {
+ receiver.onError(
+ ERROR_THREAD_DISABLED, "Can't set country code when Thread is disabled");
+ return;
+ }
+
try {
getOtDaemon().setCountryCode(countryCode, newOtStatusReceiver(receiver));
} catch (RemoteException e) {
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkService.java b/thread/service/java/com/android/server/thread/ThreadNetworkService.java
index 5664922..37c1cf1 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkService.java
@@ -18,6 +18,8 @@
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static java.util.Objects.requireNonNull;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -66,7 +68,8 @@
// PHASE_ACTIVITY_MANAGER_READY and PHASE_THIRD_PARTY_APPS_CAN_START
mCountryCode = ThreadNetworkCountryCode.newInstance(mContext, mControllerService);
mCountryCode.initialize();
- mShellCommand = new ThreadNetworkShellCommand(mCountryCode);
+ mShellCommand =
+ new ThreadNetworkShellCommand(requireNonNull(mControllerService), mCountryCode);
}
}
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkShellCommand.java b/thread/service/java/com/android/server/thread/ThreadNetworkShellCommand.java
index c17c5a7..431232b 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkShellCommand.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkShellCommand.java
@@ -16,7 +16,10 @@
package com.android.server.thread;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.net.thread.IOperationReceiver;
+import android.net.thread.ThreadNetworkException;
import android.os.Binder;
import android.os.Process;
import android.text.TextUtils;
@@ -25,7 +28,12 @@
import com.android.modules.utils.BasicShellCommandHandler;
import java.io.PrintWriter;
+import java.time.Duration;
import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
/**
* Interprets and executes 'adb shell cmd thread_network [args]'.
@@ -37,16 +45,21 @@
* corresponding API permissions.
*/
public class ThreadNetworkShellCommand extends BasicShellCommandHandler {
- private static final String TAG = "ThreadNetworkShellCommand";
+ private static final Duration SET_ENABLED_TIMEOUT = Duration.ofSeconds(2);
// These don't require root access.
- private static final List<String> NON_PRIVILEGED_COMMANDS = List.of("help", "get-country-code");
+ private static final List<String> NON_PRIVILEGED_COMMANDS =
+ List.of("help", "get-country-code", "enable", "disable");
- @Nullable private final ThreadNetworkCountryCode mCountryCode;
+ @NonNull private final ThreadNetworkControllerService mControllerService;
+ @NonNull private final ThreadNetworkCountryCode mCountryCode;
@Nullable private PrintWriter mOutputWriter;
@Nullable private PrintWriter mErrorWriter;
- ThreadNetworkShellCommand(@Nullable ThreadNetworkCountryCode countryCode) {
+ ThreadNetworkShellCommand(
+ @NonNull ThreadNetworkControllerService controllerService,
+ @NonNull ThreadNetworkCountryCode countryCode) {
+ mControllerService = controllerService;
mCountryCode = countryCode;
}
@@ -91,14 +104,12 @@
}
switch (cmd) {
+ case "enable":
+ return setThreadEnabled(true);
+ case "disable":
+ return setThreadEnabled(false);
case "force-country-code":
boolean enabled;
-
- if (mCountryCode == null) {
- perr.println("Thread country code operations are not supported");
- return -1;
- }
-
try {
enabled = getNextArgRequiredTrueOrFalse("enabled", "disabled");
} catch (IllegalArgumentException e) {
@@ -124,11 +135,6 @@
}
return 0;
case "get-country-code":
- if (mCountryCode == null) {
- perr.println("Thread country code operations are not supported");
- return -1;
- }
-
pw.println("Thread country code = " + mCountryCode.getCountryCode());
return 0;
default:
@@ -136,6 +142,40 @@
}
}
+ private int setThreadEnabled(boolean enabled) {
+ final PrintWriter perr = getErrorWriter();
+
+ CompletableFuture<Void> setEnabledFuture = new CompletableFuture<>();
+ mControllerService.setEnabled(
+ enabled,
+ new IOperationReceiver.Stub() {
+ @Override
+ public void onSuccess() {
+ setEnabledFuture.complete(null);
+ }
+
+ @Override
+ public void onError(int errorCode, String errorMessage) {
+ setEnabledFuture.completeExceptionally(
+ new ThreadNetworkException(errorCode, errorMessage));
+ }
+ });
+
+ try {
+ setEnabledFuture.get(SET_ENABLED_TIMEOUT.toSeconds(), TimeUnit.SECONDS);
+ return 0;
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ perr.println("Failed: " + e.getMessage());
+ } catch (ExecutionException e) {
+ perr.println("Failed: " + e.getCause().getMessage());
+ } catch (TimeoutException e) {
+ perr.println("Failed: command timeout for " + SET_ENABLED_TIMEOUT);
+ }
+
+ return -1;
+ }
+
private static boolean argTrueOrFalse(String arg, String trueString, String falseString) {
if (trueString.equals(arg)) {
return true;
@@ -159,6 +199,10 @@
}
private void onHelpNonPrivileged(PrintWriter pw) {
+ pw.println(" enable");
+ pw.println(" Enables Thread radio");
+ pw.println(" disable");
+ pw.println(" Disables Thread radio");
pw.println(" get-country-code");
pw.println(" Gets country code as a two-letter string");
}
diff --git a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
index 353db10..5fe4325 100644
--- a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
+++ b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
@@ -164,15 +164,13 @@
* </pre>
*/
- // Let ftd join the network.
FullThreadDevice ftd = mFtds.get(0);
startFtdChild(ftd);
- // Infra device sends an echo request to FTD's OMR.
mInfraDevice.sendEchoRequest(ftd.getOmrAddress());
// Infra device receives an echo reply sent by FTD.
- assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, null /* srcAddress */));
+ assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd.getOmrAddress()));
}
@Test
diff --git a/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java b/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java
index 56b658d..39a1671 100644
--- a/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java
+++ b/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java
@@ -30,6 +30,7 @@
import static org.junit.Assert.assertThrows;
+import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.content.Context;
@@ -302,6 +303,17 @@
assertThrows(TimeoutException.class, () -> discoverService(mNsdManager, "_test._udp"));
}
+ @Test
+ public void meshcopOverlay_vendorAndModelNameAreSetToOverlayValue() throws Exception {
+ NsdServiceInfo discoveredService = discoverService(mNsdManager, "_meshcop._udp");
+ assertThat(discoveredService).isNotNull();
+ NsdServiceInfo meshcopService = resolveService(mNsdManager, discoveredService);
+
+ Map<String, byte[]> txtMap = meshcopService.getAttributes();
+ assertThat(txtMap.get("vn")).isEqualTo("Android".getBytes(UTF_8));
+ assertThat(txtMap.get("mn")).isEqualTo("Thread Border Router".getBytes(UTF_8));
+ }
+
private static byte[] bytes(int... byteInts) {
byte[] bytes = new byte[byteInts.length];
for (int i = 0; i < byteInts.length; ++i) {
diff --git a/thread/tests/integration/src/android/net/thread/ThreadNetworkShellCommandTest.java b/thread/tests/integration/src/android/net/thread/ThreadNetworkShellCommandTest.java
new file mode 100644
index 0000000..d24fd47
--- /dev/null
+++ b/thread/tests/integration/src/android/net/thread/ThreadNetworkShellCommandTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.
+ */
+
+package android.net.thread;
+
+import static android.net.thread.ThreadNetworkController.STATE_DISABLED;
+import static android.net.thread.ThreadNetworkController.STATE_ENABLED;
+
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.net.thread.utils.ThreadFeatureCheckerRule;
+import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresThreadFeature;
+import android.net.thread.utils.ThreadNetworkControllerWrapper;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Integration tests for {@link ThreadNetworkShellCommand}. */
+@LargeTest
+@RequiresThreadFeature
+@RunWith(AndroidJUnit4.class)
+public class ThreadNetworkShellCommandTest {
+ @Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule();
+
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ private final ThreadNetworkControllerWrapper mController =
+ ThreadNetworkControllerWrapper.newInstance(mContext);
+
+ @Test
+ public void enable_threadStateIsEnabled() throws Exception {
+ runThreadCommand("enable");
+
+ assertThat(mController.getEnabledState()).isEqualTo(STATE_ENABLED);
+ }
+
+ @Test
+ public void disable_threadStateIsDisabled() throws Exception {
+ runThreadCommand("disable");
+
+ assertThat(mController.getEnabledState()).isEqualTo(STATE_DISABLED);
+ }
+
+ @Test
+ public void forceCountryCode_setCN_getCountryCodeReturnsCN() {
+ runThreadCommand("force-country-code enabled CN");
+
+ final String result = runThreadCommand("get-country-code");
+ assertThat(result).contains("Thread country code = CN");
+ }
+
+ private static String runThreadCommand(String cmd) {
+ return runShellCommand("cmd thread_network " + cmd);
+ }
+}
diff --git a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
index 60a5f2b..830890d 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
@@ -47,6 +47,7 @@
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.NetworkAgent;
import android.net.NetworkProvider;
@@ -65,6 +66,9 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.connectivity.resources.R;
+import com.android.server.connectivity.ConnectivityResources;
+import com.android.server.thread.openthread.MeshcopTxtAttributes;
import com.android.server.thread.openthread.testing.FakeOtDaemon;
import org.junit.Before;
@@ -110,6 +114,11 @@
private static final int DEFAULT_SELECTED_CHANNEL = 11;
private static final byte[] DEFAULT_SUPPORTED_CHANNEL_MASK_ARRAY = base16().decode("001FFFE0");
+ private static final String TEST_VENDOR_OUI = "AC-DE-48";
+ private static final byte[] TEST_VENDOR_OUI_BYTES = new byte[] {(byte) 0xAC, (byte) 0xDE, 0x48};
+ private static final String TEST_VENDOR_NAME = "test vendor";
+ private static final String TEST_MODEL_NAME = "test model";
+
@Mock private ConnectivityManager mMockConnectivityManager;
@Mock private NetworkAgent mMockNetworkAgent;
@Mock private TunInterfaceController mMockTunIfController;
@@ -119,6 +128,9 @@
@Mock private NsdPublisher mMockNsdPublisher;
@Mock private UserManager mMockUserManager;
@Mock private IBinder mIBinder;
+ @Mock Resources mResources;
+ @Mock ConnectivityResources mConnectivityResources;
+
private Context mContext;
private TestLooper mTestLooper;
private FakeOtDaemon mFakeOtDaemon;
@@ -146,6 +158,14 @@
when(mMockPersistentSettings.get(any())).thenReturn(true);
when(mMockUserManager.hasUserRestriction(eq(DISALLOW_THREAD_NETWORK))).thenReturn(false);
+ when(mConnectivityResources.get()).thenReturn(mResources);
+ when(mResources.getString(eq(R.string.config_thread_vendor_name)))
+ .thenReturn(TEST_VENDOR_NAME);
+ when(mResources.getString(eq(R.string.config_thread_vendor_oui)))
+ .thenReturn(TEST_VENDOR_OUI);
+ when(mResources.getString(eq(R.string.config_thread_model_name)))
+ .thenReturn(TEST_MODEL_NAME);
+
mService =
new ThreadNetworkControllerService(
mContext,
@@ -157,7 +177,8 @@
mMockInfraIfController,
mMockPersistentSettings,
mMockNsdPublisher,
- mMockUserManager);
+ mMockUserManager,
+ mConnectivityResources);
mService.setTestNetworkAgent(mMockNetworkAgent);
}
@@ -174,6 +195,93 @@
}
@Test
+ public void initialize_vendorAndModelNameInResourcesAreSetToOtDaemon() throws Exception {
+ when(mResources.getString(eq(R.string.config_thread_vendor_name)))
+ .thenReturn(TEST_VENDOR_NAME);
+ when(mResources.getString(eq(R.string.config_thread_vendor_oui)))
+ .thenReturn(TEST_VENDOR_OUI);
+ when(mResources.getString(eq(R.string.config_thread_model_name)))
+ .thenReturn(TEST_MODEL_NAME);
+
+ mService.initialize();
+ mTestLooper.dispatchAll();
+
+ MeshcopTxtAttributes meshcopTxts = mFakeOtDaemon.getOverriddenMeshcopTxtAttributes();
+ assertThat(meshcopTxts.vendorName).isEqualTo(TEST_VENDOR_NAME);
+ assertThat(meshcopTxts.vendorOui).isEqualTo(TEST_VENDOR_OUI_BYTES);
+ assertThat(meshcopTxts.modelName).isEqualTo(TEST_MODEL_NAME);
+ }
+
+ @Test
+ public void getMeshcopTxtAttributes_emptyVendorName_accepted() {
+ when(mResources.getString(eq(R.string.config_thread_vendor_name))).thenReturn("");
+
+ MeshcopTxtAttributes meshcopTxts =
+ ThreadNetworkControllerService.getMeshcopTxtAttributes(mResources);
+
+ assertThat(meshcopTxts.vendorName).isEqualTo("");
+ }
+
+ @Test
+ public void getMeshcopTxtAttributes_tooLongVendorName_throwsIllegalStateException() {
+ when(mResources.getString(eq(R.string.config_thread_vendor_name)))
+ .thenReturn("vendor name is 25 bytes!!");
+
+ assertThrows(
+ IllegalStateException.class,
+ () -> ThreadNetworkControllerService.getMeshcopTxtAttributes(mResources));
+ }
+
+ @Test
+ public void getMeshcopTxtAttributes_tooLongModelName_throwsIllegalStateException() {
+ when(mResources.getString(eq(R.string.config_thread_model_name)))
+ .thenReturn("model name is 25 bytes!!!");
+
+ assertThrows(
+ IllegalStateException.class,
+ () -> ThreadNetworkControllerService.getMeshcopTxtAttributes(mResources));
+ }
+
+ @Test
+ public void getMeshcopTxtAttributes_emptyModelName_accepted() {
+ when(mResources.getString(eq(R.string.config_thread_model_name))).thenReturn("");
+
+ var meshcopTxts = ThreadNetworkControllerService.getMeshcopTxtAttributes(mResources);
+ assertThat(meshcopTxts.modelName).isEqualTo("");
+ }
+
+ @Test
+ public void getMeshcopTxtAttributes_invalidVendorOui_throwsIllegalStateException() {
+ assertThrows(
+ IllegalStateException.class, () -> getMeshcopTxtAttributesWithVendorOui("ABCDEFA"));
+ assertThrows(
+ IllegalStateException.class, () -> getMeshcopTxtAttributesWithVendorOui("ABCDEG"));
+ assertThrows(
+ IllegalStateException.class, () -> getMeshcopTxtAttributesWithVendorOui("ABCD"));
+ assertThrows(
+ IllegalStateException.class,
+ () -> getMeshcopTxtAttributesWithVendorOui("AB.CD.EF"));
+ }
+
+ @Test
+ public void getMeshcopTxtAttributes_validVendorOui_accepted() {
+ assertThat(getMeshcopTxtAttributesWithVendorOui("010203")).isEqualTo(new byte[] {1, 2, 3});
+ assertThat(getMeshcopTxtAttributesWithVendorOui("01-02-03"))
+ .isEqualTo(new byte[] {1, 2, 3});
+ assertThat(getMeshcopTxtAttributesWithVendorOui("01:02:03"))
+ .isEqualTo(new byte[] {1, 2, 3});
+ assertThat(getMeshcopTxtAttributesWithVendorOui("ABCDEF"))
+ .isEqualTo(new byte[] {(byte) 0xAB, (byte) 0xCD, (byte) 0xEF});
+ assertThat(getMeshcopTxtAttributesWithVendorOui("abcdef"))
+ .isEqualTo(new byte[] {(byte) 0xAB, (byte) 0xCD, (byte) 0xEF});
+ }
+
+ private byte[] getMeshcopTxtAttributesWithVendorOui(String vendorOui) {
+ when(mResources.getString(eq(R.string.config_thread_vendor_oui))).thenReturn(vendorOui);
+ return ThreadNetworkControllerService.getMeshcopTxtAttributes(mResources).vendorOui;
+ }
+
+ @Test
public void join_otDaemonRemoteFailure_returnsInternalError() throws Exception {
mService.initialize();
final IOperationReceiver mockReceiver = mock(IOperationReceiver.class);
@@ -204,13 +312,13 @@
}
@Test
- public void userRestriction_initWithUserRestricted_threadIsDisabled() {
+ public void userRestriction_initWithUserRestricted_otDaemonNotStarted() {
when(mMockUserManager.hasUserRestriction(eq(DISALLOW_THREAD_NETWORK))).thenReturn(true);
mService.initialize();
mTestLooper.dispatchAll();
- assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(STATE_DISABLED);
+ assertThat(mFakeOtDaemon.isInitialized()).isFalse();
}
@Test
diff --git a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkShellCommandTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkShellCommandTest.java
index c7e0eca..f469152 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkShellCommandTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkShellCommandTest.java
@@ -45,8 +45,8 @@
@SmallTest
public class ThreadNetworkShellCommandTest {
private static final String TAG = "ThreadNetworkShellCommandTTest";
- @Mock ThreadNetworkService mThreadNetworkService;
- @Mock ThreadNetworkCountryCode mThreadNetworkCountryCode;
+ @Mock ThreadNetworkControllerService mControllerService;
+ @Mock ThreadNetworkCountryCode mCountryCode;
@Mock PrintWriter mErrorWriter;
@Mock PrintWriter mOutputWriter;
@@ -56,7 +56,8 @@
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mThreadNetworkShellCommand = new ThreadNetworkShellCommand(mThreadNetworkCountryCode);
+ mThreadNetworkShellCommand =
+ new ThreadNetworkShellCommand(mControllerService, mCountryCode);
mThreadNetworkShellCommand.setPrintWriters(mOutputWriter, mErrorWriter);
}
@@ -68,7 +69,7 @@
@Test
public void getCountryCode_executeInUnrootedShell_allowed() {
BinderUtil.setUid(Process.SHELL_UID);
- when(mThreadNetworkCountryCode.getCountryCode()).thenReturn("US");
+ when(mCountryCode.getCountryCode()).thenReturn("US");
mThreadNetworkShellCommand.exec(
new Binder(),
@@ -91,7 +92,7 @@
new FileDescriptor(),
new String[] {"force-country-code", "enabled", "US"});
- verify(mThreadNetworkCountryCode, never()).setOverrideCountryCode(eq("US"));
+ verify(mCountryCode, never()).setOverrideCountryCode(eq("US"));
verify(mErrorWriter).println(contains("force-country-code"));
}
@@ -106,7 +107,7 @@
new FileDescriptor(),
new String[] {"force-country-code", "enabled", "US"});
- verify(mThreadNetworkCountryCode).setOverrideCountryCode(eq("US"));
+ verify(mCountryCode).setOverrideCountryCode(eq("US"));
}
@Test
@@ -120,7 +121,7 @@
new FileDescriptor(),
new String[] {"force-country-code", "disabled"});
- verify(mThreadNetworkCountryCode, never()).setOverrideCountryCode(any());
+ verify(mCountryCode, never()).setOverrideCountryCode(any());
verify(mErrorWriter).println(contains("force-country-code"));
}
@@ -135,6 +136,6 @@
new FileDescriptor(),
new String[] {"force-country-code", "disabled"});
- verify(mThreadNetworkCountryCode).clearOverrideCountryCode();
+ verify(mCountryCode).clearOverrideCountryCode();
}
}