Merge "Add a new module-only API to preload HttpEngine" into main
diff --git a/Tethering/common/TetheringLib/Android.bp b/Tethering/common/TetheringLib/Android.bp
index 2f3307a..e2498e4 100644
--- a/Tethering/common/TetheringLib/Android.bp
+++ b/Tethering/common/TetheringLib/Android.bp
@@ -33,6 +33,10 @@
// Using for test only
"//cts/tests/netlegacy22.api",
+
+ // TODO: b/374174952 Remove it when VCN CTS is moved to Connectivity/
+ "//cts/tests/tests/vcn",
+
"//external/sl4a:__subpackages__",
"//frameworks/base/core/tests/bandwidthtests",
"//frameworks/base/core/tests/benchmarks",
diff --git a/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java b/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
index cd57c8d..fb16226 100644
--- a/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
+++ b/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
@@ -167,6 +167,11 @@
} else {
mLog.e("Current user (" + currentUserId
+ ") is not allowed to perform entitlement check.");
+ // If the user is not allowed to perform an entitlement check
+ // (e.g., a non-admin user), notify the receiver immediately.
+ // This is necessary because the entitlement check app cannot
+ // be launched to conduct the check and deliver the results.
+ receiver.send(TETHER_ERROR_PROVISIONING_FAILED, null);
return null;
}
} else {
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
index 8626b18..51c2d56 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
@@ -84,6 +84,7 @@
import com.android.internal.util.test.BroadcastInterceptingContext;
import com.android.modules.utils.build.SdkLevel;
+import com.android.net.module.util.ArrayTrackRecord;
import com.android.net.module.util.SharedLog;
import com.android.testutils.DevSdkIgnoreRule;
@@ -187,8 +188,9 @@
if (intent != null) {
assertUiTetherProvisioningIntent(type, config, receiver, intent);
uiProvisionCount++;
+ // If the intent is null, the result is sent by the underlying method.
+ receiver.send(fakeEntitlementResult, null);
}
- receiver.send(fakeEntitlementResult, null);
return intent;
}
@@ -348,99 +350,43 @@
public void testRequestLastEntitlementCacheValue() throws Exception {
// 1. Entitlement check is not required.
mDeps.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
- ResultReceiver receiver = new ResultReceiver(null) {
- @Override
- protected void onReceiveResult(int resultCode, Bundle resultData) {
- assertEquals(TETHER_ERROR_NO_ERROR, resultCode);
- }
- };
- mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true);
- mLooper.dispatchAll();
+ assertLatestEntitlementResult(TETHERING_WIFI, TETHER_ERROR_NO_ERROR, true);
assertEquals(0, mDeps.uiProvisionCount);
mDeps.reset();
setupForRequiredProvisioning();
// 2. No cache value and don't need to run entitlement check.
- receiver = new ResultReceiver(null) {
- @Override
- protected void onReceiveResult(int resultCode, Bundle resultData) {
- assertEquals(TETHER_ERROR_ENTITLEMENT_UNKNOWN, resultCode);
- }
- };
- mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, false);
- mLooper.dispatchAll();
+ assertLatestEntitlementResult(TETHERING_WIFI, TETHER_ERROR_ENTITLEMENT_UNKNOWN, false);
assertEquals(0, mDeps.uiProvisionCount);
mDeps.reset();
// 3. No cache value and ui entitlement check is needed.
mDeps.fakeEntitlementResult = TETHER_ERROR_PROVISIONING_FAILED;
- receiver = new ResultReceiver(null) {
- @Override
- protected void onReceiveResult(int resultCode, Bundle resultData) {
- assertEquals(TETHER_ERROR_PROVISIONING_FAILED, resultCode);
- }
- };
- mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true);
- mLooper.dispatchAll();
+ assertLatestEntitlementResult(TETHERING_WIFI, TETHER_ERROR_PROVISIONING_FAILED, true);
assertEquals(1, mDeps.uiProvisionCount);
mDeps.reset();
// 4. Cache value is TETHER_ERROR_PROVISIONING_FAILED and don't need to run entitlement
// check.
mDeps.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
- receiver = new ResultReceiver(null) {
- @Override
- protected void onReceiveResult(int resultCode, Bundle resultData) {
- assertEquals(TETHER_ERROR_PROVISIONING_FAILED, resultCode);
- }
- };
- mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, false);
- mLooper.dispatchAll();
+ assertLatestEntitlementResult(TETHERING_WIFI, TETHER_ERROR_PROVISIONING_FAILED, false);
assertEquals(0, mDeps.uiProvisionCount);
mDeps.reset();
// 5. Cache value is TETHER_ERROR_PROVISIONING_FAILED and ui entitlement check is needed.
mDeps.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
- receiver = new ResultReceiver(null) {
- @Override
- protected void onReceiveResult(int resultCode, Bundle resultData) {
- assertEquals(TETHER_ERROR_NO_ERROR, resultCode);
- }
- };
- mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true);
- mLooper.dispatchAll();
+ assertLatestEntitlementResult(TETHERING_WIFI, TETHER_ERROR_NO_ERROR, true);
assertEquals(1, mDeps.uiProvisionCount);
mDeps.reset();
// 6. Cache value is TETHER_ERROR_NO_ERROR.
mDeps.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
- receiver = new ResultReceiver(null) {
- @Override
- protected void onReceiveResult(int resultCode, Bundle resultData) {
- assertEquals(TETHER_ERROR_NO_ERROR, resultCode);
- }
- };
- mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true);
- mLooper.dispatchAll();
+ assertLatestEntitlementResult(TETHERING_WIFI, TETHER_ERROR_NO_ERROR, true);
assertEquals(0, mDeps.uiProvisionCount);
mDeps.reset();
// 7. Test get value for other downstream type.
- receiver = new ResultReceiver(null) {
- @Override
- protected void onReceiveResult(int resultCode, Bundle resultData) {
- assertEquals(TETHER_ERROR_ENTITLEMENT_UNKNOWN, resultCode);
- }
- };
- mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_USB, receiver, false);
- mLooper.dispatchAll();
+ assertLatestEntitlementResult(TETHERING_USB, TETHER_ERROR_ENTITLEMENT_UNKNOWN, false);
assertEquals(0, mDeps.uiProvisionCount);
mDeps.reset();
// 8. Test get value for invalid downstream type.
mDeps.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
- receiver = new ResultReceiver(null) {
- @Override
- protected void onReceiveResult(int resultCode, Bundle resultData) {
- assertEquals(TETHER_ERROR_ENTITLEMENT_UNKNOWN, resultCode);
- }
- };
- mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI_P2P, receiver, true);
- mLooper.dispatchAll();
+ assertLatestEntitlementResult(TETHERING_WIFI_P2P, TETHER_ERROR_ENTITLEMENT_UNKNOWN, true);
assertEquals(0, mDeps.uiProvisionCount);
mDeps.reset();
}
@@ -660,6 +606,34 @@
doTestUiProvisioningMultiUser(false, 1);
}
+ private static class TestableResultReceiver extends ResultReceiver {
+ private static final long DEFAULT_TIMEOUT_MS = 200L;
+ private final ArrayTrackRecord<Integer>.ReadHead mHistory =
+ new ArrayTrackRecord<Integer>().newReadHead();
+
+ TestableResultReceiver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ mHistory.add(resultCode);
+ }
+
+ void expectResult(int resultCode) {
+ final int event = mHistory.poll(DEFAULT_TIMEOUT_MS, it -> true);
+ assertEquals(resultCode, event);
+ }
+ }
+
+ void assertLatestEntitlementResult(int downstreamType, int expectedCode,
+ boolean showEntitlementUi) {
+ final TestableResultReceiver receiver = new TestableResultReceiver(null);
+ mEnMgr.requestLatestTetheringEntitlementResult(downstreamType, receiver, showEntitlementUi);
+ mLooper.dispatchAll();
+ receiver.expectResult(expectedCode);
+ }
+
private void doTestUiProvisioningMultiUser(boolean isAdminUser, int expectedUiProvisionCount) {
setupForRequiredProvisioning();
doReturn(isAdminUser).when(mUserManager).isAdminUser();
@@ -671,10 +645,19 @@
mEnMgr.startProvisioningIfNeeded(TETHERING_USB, true);
mLooper.dispatchAll();
assertEquals(expectedUiProvisionCount, mDeps.uiProvisionCount);
+ if (expectedUiProvisionCount == 0) { // Failed to launch entitlement UI.
+ assertLatestEntitlementResult(TETHERING_USB, TETHER_ERROR_PROVISIONING_FAILED, false);
+ verify(mTetherProvisioningFailedListener).onTetherProvisioningFailed(TETHERING_USB,
+ FAILED_TETHERING_REASON);
+ } else {
+ assertLatestEntitlementResult(TETHERING_USB, TETHER_ERROR_NO_ERROR, false);
+ verify(mTetherProvisioningFailedListener, never()).onTetherProvisioningFailed(anyInt(),
+ anyString());
+ }
}
@Test
- public void testsetExemptedDownstreamType() throws Exception {
+ public void testSetExemptedDownstreamType() {
setupForRequiredProvisioning();
// Cellular upstream is not permitted when no entitlement result.
assertFalse(mEnMgr.isCellularUpstreamPermitted());
@@ -737,14 +720,7 @@
setupCarrierConfig(false);
setupForRequiredProvisioning();
mDeps.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
- ResultReceiver receiver = new ResultReceiver(null) {
- @Override
- protected void onReceiveResult(int resultCode, Bundle resultData) {
- assertEquals(TETHER_ERROR_PROVISIONING_FAILED, resultCode);
- }
- };
- mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, false);
- mLooper.dispatchAll();
+ assertLatestEntitlementResult(TETHERING_WIFI, TETHER_ERROR_PROVISIONING_FAILED, false);
assertEquals(0, mDeps.uiProvisionCount);
mDeps.reset();
}
diff --git a/bpf/syscall_wrappers/include/BpfSyscallWrappers.h b/bpf/syscall_wrappers/include/BpfSyscallWrappers.h
index 73cef89..a31445a 100644
--- a/bpf/syscall_wrappers/include/BpfSyscallWrappers.h
+++ b/bpf/syscall_wrappers/include/BpfSyscallWrappers.h
@@ -16,24 +16,20 @@
#pragma once
+#include <android-base/unique_fd.h>
#include <stdlib.h>
#include <unistd.h>
#include <linux/bpf.h>
#include <linux/unistd.h>
#include <sys/file.h>
-#ifdef BPF_FD_JUST_USE_INT
- #define BPF_FD_TYPE int
- #define BPF_FD_TO_U32(x) static_cast<__u32>(x)
-#else
- #include <android-base/unique_fd.h>
- #define BPF_FD_TYPE base::unique_fd&
- #define BPF_FD_TO_U32(x) static_cast<__u32>((x).get())
-#endif
namespace android {
namespace bpf {
+using ::android::base::borrowed_fd;
+using ::android::base::unique_fd;
+
inline uint64_t ptr_to_u64(const void * const x) {
return (uint64_t)(uintptr_t)x;
}
@@ -69,58 +65,59 @@
// 'inner_map_fd' is basically a template specifying {map_type, key_size, value_size, max_entries, map_flags}
// of the inner map type (and possibly only key_size/value_size actually matter?).
inline int createOuterMap(bpf_map_type map_type, uint32_t key_size, uint32_t value_size,
- uint32_t max_entries, uint32_t map_flags, const BPF_FD_TYPE inner_map_fd) {
+ uint32_t max_entries, uint32_t map_flags,
+ const borrowed_fd& inner_map_fd) {
return bpf(BPF_MAP_CREATE, {
.map_type = map_type,
.key_size = key_size,
.value_size = value_size,
.max_entries = max_entries,
.map_flags = map_flags,
- .inner_map_fd = BPF_FD_TO_U32(inner_map_fd),
+ .inner_map_fd = static_cast<__u32>(inner_map_fd.get()),
});
}
-inline int writeToMapEntry(const BPF_FD_TYPE map_fd, const void* key, const void* value,
+inline int writeToMapEntry(const borrowed_fd& map_fd, const void* key, const void* value,
uint64_t flags) {
return bpf(BPF_MAP_UPDATE_ELEM, {
- .map_fd = BPF_FD_TO_U32(map_fd),
+ .map_fd = static_cast<__u32>(map_fd.get()),
.key = ptr_to_u64(key),
.value = ptr_to_u64(value),
.flags = flags,
});
}
-inline int findMapEntry(const BPF_FD_TYPE map_fd, const void* key, void* value) {
+inline int findMapEntry(const borrowed_fd& map_fd, const void* key, void* value) {
return bpf(BPF_MAP_LOOKUP_ELEM, {
- .map_fd = BPF_FD_TO_U32(map_fd),
+ .map_fd = static_cast<__u32>(map_fd.get()),
.key = ptr_to_u64(key),
.value = ptr_to_u64(value),
});
}
-inline int deleteMapEntry(const BPF_FD_TYPE map_fd, const void* key) {
+inline int deleteMapEntry(const borrowed_fd& map_fd, const void* key) {
return bpf(BPF_MAP_DELETE_ELEM, {
- .map_fd = BPF_FD_TO_U32(map_fd),
+ .map_fd = static_cast<__u32>(map_fd.get()),
.key = ptr_to_u64(key),
});
}
-inline int getNextMapKey(const BPF_FD_TYPE map_fd, const void* key, void* next_key) {
+inline int getNextMapKey(const borrowed_fd& map_fd, const void* key, void* next_key) {
return bpf(BPF_MAP_GET_NEXT_KEY, {
- .map_fd = BPF_FD_TO_U32(map_fd),
+ .map_fd = static_cast<__u32>(map_fd.get()),
.key = ptr_to_u64(key),
.next_key = ptr_to_u64(next_key),
});
}
-inline int getFirstMapKey(const BPF_FD_TYPE map_fd, void* firstKey) {
+inline int getFirstMapKey(const borrowed_fd& map_fd, void* firstKey) {
return getNextMapKey(map_fd, NULL, firstKey);
}
-inline int bpfFdPin(const BPF_FD_TYPE map_fd, const char* pathname) {
+inline int bpfFdPin(const borrowed_fd& map_fd, const char* pathname) {
return bpf(BPF_OBJ_PIN, {
.pathname = ptr_to_u64(pathname),
- .bpf_fd = BPF_FD_TO_U32(map_fd),
+ .bpf_fd = static_cast<__u32>(map_fd.get()),
});
}
@@ -131,22 +128,15 @@
});
}
-int bpfGetFdMapId(const BPF_FD_TYPE map_fd);
+int bpfGetFdMapId(const borrowed_fd& map_fd);
inline int bpfLock(int fd, short type) {
if (fd < 0) return fd; // pass any errors straight through
#ifdef BPF_MAP_LOCKLESS_FOR_TEST
return fd;
#endif
-#ifdef BPF_FD_JUST_USE_INT
int mapId = bpfGetFdMapId(fd);
int saved_errno = errno;
-#else
- base::unique_fd ufd(fd);
- int mapId = bpfGetFdMapId(ufd);
- int saved_errno = errno;
- (void)ufd.release();
-#endif
// 4.14+ required to fetch map id, but we don't want to call isAtLeastKernelVersion
if (mapId == -1 && saved_errno == EINVAL) return fd;
if (mapId <= 0) abort(); // should not be possible
@@ -193,37 +183,35 @@
}
inline bool usableProgram(const char* pathname) {
- int fd = retrieveProgram(pathname);
- bool ok = (fd >= 0);
- if (ok) close(fd);
- return ok;
+ unique_fd fd(retrieveProgram(pathname));
+ return fd.ok();
}
-inline int attachProgram(bpf_attach_type type, const BPF_FD_TYPE prog_fd,
- const BPF_FD_TYPE cg_fd, uint32_t flags = 0) {
+inline int attachProgram(bpf_attach_type type, const borrowed_fd& prog_fd,
+ const borrowed_fd& cg_fd, uint32_t flags = 0) {
return bpf(BPF_PROG_ATTACH, {
- .target_fd = BPF_FD_TO_U32(cg_fd),
- .attach_bpf_fd = BPF_FD_TO_U32(prog_fd),
+ .target_fd = static_cast<__u32>(cg_fd.get()),
+ .attach_bpf_fd = static_cast<__u32>(prog_fd.get()),
.attach_type = type,
.attach_flags = flags,
});
}
-inline int detachProgram(bpf_attach_type type, const BPF_FD_TYPE cg_fd) {
+inline int detachProgram(bpf_attach_type type, const borrowed_fd& cg_fd) {
return bpf(BPF_PROG_DETACH, {
- .target_fd = BPF_FD_TO_U32(cg_fd),
+ .target_fd = static_cast<__u32>(cg_fd.get()),
.attach_type = type,
});
}
-inline int queryProgram(const BPF_FD_TYPE cg_fd,
+inline int queryProgram(const borrowed_fd& cg_fd,
enum bpf_attach_type attach_type,
__u32 query_flags = 0,
__u32 attach_flags = 0) {
int prog_id = -1; // equivalent to an array of one integer.
bpf_attr arg = {
.query = {
- .target_fd = BPF_FD_TO_U32(cg_fd),
+ .target_fd = static_cast<__u32>(cg_fd.get()),
.attach_type = attach_type,
.query_flags = query_flags,
.attach_flags = attach_flags,
@@ -237,21 +225,21 @@
return prog_id; // return actual id
}
-inline int detachSingleProgram(bpf_attach_type type, const BPF_FD_TYPE prog_fd,
- const BPF_FD_TYPE cg_fd) {
+inline int detachSingleProgram(bpf_attach_type type, const borrowed_fd& prog_fd,
+ const borrowed_fd& cg_fd) {
return bpf(BPF_PROG_DETACH, {
- .target_fd = BPF_FD_TO_U32(cg_fd),
- .attach_bpf_fd = BPF_FD_TO_U32(prog_fd),
+ .target_fd = static_cast<__u32>(cg_fd.get()),
+ .attach_bpf_fd = static_cast<__u32>(prog_fd.get()),
.attach_type = type,
});
}
// Available in 4.12 and later kernels.
-inline int runProgram(const BPF_FD_TYPE prog_fd, const void* data,
+inline int runProgram(const borrowed_fd& prog_fd, const void* data,
const uint32_t data_size) {
return bpf(BPF_PROG_RUN, {
.test = {
- .prog_fd = BPF_FD_TO_U32(prog_fd),
+ .prog_fd = static_cast<__u32>(prog_fd.get()),
.data_size_in = data_size,
.data_in = ptr_to_u64(data),
},
@@ -265,10 +253,10 @@
// supported/returned by the running kernel. We do this by checking it is fully
// within the bounds of the struct size as reported by the kernel.
#define DEFINE_BPF_GET_FD(TYPE, NAME, FIELD) \
-inline int bpfGetFd ## NAME(const BPF_FD_TYPE fd) { \
+inline int bpfGetFd ## NAME(const borrowed_fd& fd) { \
struct bpf_ ## TYPE ## _info info = {}; \
union bpf_attr attr = { .info = { \
- .bpf_fd = BPF_FD_TO_U32(fd), \
+ .bpf_fd = static_cast<__u32>(fd.get()), \
.info_len = sizeof(info), \
.info = ptr_to_u64(&info), \
}}; \
@@ -283,19 +271,16 @@
// All 7 of these fields are already present in Linux v4.14 (even ACK 4.14-P)
// while BPF_OBJ_GET_INFO_BY_FD is not implemented at all in v4.9 (even ACK 4.9-Q)
-DEFINE_BPF_GET_FD(map, MapType, type) // int bpfGetFdMapType(const BPF_FD_TYPE map_fd)
-DEFINE_BPF_GET_FD(map, MapId, id) // int bpfGetFdMapId(const BPF_FD_TYPE map_fd)
-DEFINE_BPF_GET_FD(map, KeySize, key_size) // int bpfGetFdKeySize(const BPF_FD_TYPE map_fd)
-DEFINE_BPF_GET_FD(map, ValueSize, value_size) // int bpfGetFdValueSize(const BPF_FD_TYPE map_fd)
-DEFINE_BPF_GET_FD(map, MaxEntries, max_entries) // int bpfGetFdMaxEntries(const BPF_FD_TYPE map_fd)
-DEFINE_BPF_GET_FD(map, MapFlags, map_flags) // int bpfGetFdMapFlags(const BPF_FD_TYPE map_fd)
-DEFINE_BPF_GET_FD(prog, ProgId, id) // int bpfGetFdProgId(const BPF_FD_TYPE prog_fd)
+DEFINE_BPF_GET_FD(map, MapType, type) // int bpfGetFdMapType(const borrowed_fd& map_fd)
+DEFINE_BPF_GET_FD(map, MapId, id) // int bpfGetFdMapId(const borrowed_fd& map_fd)
+DEFINE_BPF_GET_FD(map, KeySize, key_size) // int bpfGetFdKeySize(const borrowed_fd& map_fd)
+DEFINE_BPF_GET_FD(map, ValueSize, value_size) // int bpfGetFdValueSize(const borrowed_fd& map_fd)
+DEFINE_BPF_GET_FD(map, MaxEntries, max_entries) // int bpfGetFdMaxEntries(const borrowed_fd& map_fd)
+DEFINE_BPF_GET_FD(map, MapFlags, map_flags) // int bpfGetFdMapFlags(const borrowed_fd& map_fd)
+DEFINE_BPF_GET_FD(prog, ProgId, id) // int bpfGetFdProgId(const borrowed_fd& prog_fd)
#undef DEFINE_BPF_GET_FD
} // namespace bpf
} // namespace android
-#undef BPF_FD_TO_U32
-#undef BPF_FD_TYPE
-#undef BPF_FD_JUST_USE_INT
diff --git a/common/networksecurity_flags.aconfig b/common/networksecurity_flags.aconfig
index 6438ba4..4a83af4 100644
--- a/common/networksecurity_flags.aconfig
+++ b/common/networksecurity_flags.aconfig
@@ -8,3 +8,12 @@
bug: "319829948"
is_fixed_read_only: true
}
+
+flag {
+ name: "certificate_transparency_job"
+ is_exported: true
+ namespace: "network_security"
+ description: "Enable daily job service for certificate transparency instead of flags listener"
+ bug: "319829948"
+ is_fixed_read_only: true
+}
diff --git a/framework-t/Android.bp b/framework-t/Android.bp
index 7551b92..26fc145 100644
--- a/framework-t/Android.bp
+++ b/framework-t/Android.bp
@@ -172,6 +172,10 @@
// Tests using hidden APIs
"//cts/tests/netlegacy22.api",
"//cts/tests/tests/app.usage", // NetworkUsageStatsTest
+
+ // TODO: b/374174952 Remove it when VCN CTS is moved to Connectivity/
+ "//cts/tests/tests/vcn",
+
"//external/sl4a:__subpackages__",
"//frameworks/base/core/tests/bandwidthtests",
"//frameworks/base/core/tests/benchmarks",
diff --git a/framework/Android.bp b/framework/Android.bp
index 4353213..a93a532 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -64,6 +64,7 @@
":net-utils-framework-common-srcs",
":framework-connectivity-api-shared-srcs",
":framework-networksecurity-sources",
+ ":statslog-framework-connectivity-java-gen",
],
aidl: {
generate_get_transaction_name: true,
@@ -104,6 +105,7 @@
"androidx.annotation_annotation",
"app-compat-annotations",
"framework-connectivity-t.stubs.module_lib",
+ "framework-statsd.stubs.module_lib",
"unsupportedappusage",
],
apex_available: [
@@ -188,6 +190,10 @@
// Tests using hidden APIs
"//cts/tests/netlegacy22.api",
"//cts/tests/tests/app.usage", // NetworkUsageStatsTest
+
+ // TODO: b/374174952 Remove it when VCN CTS is moved to Connectivity/
+ "//cts/tests/tests/vcn",
+
"//external/sl4a:__subpackages__",
"//frameworks/base/core/tests/bandwidthtests",
"//frameworks/base/core/tests/benchmarks",
diff --git a/framework/src/android/net/NetworkAgent.java b/framework/src/android/net/NetworkAgent.java
index 574ab2f..cefa1ea 100644
--- a/framework/src/android/net/NetworkAgent.java
+++ b/framework/src/android/net/NetworkAgent.java
@@ -31,12 +31,14 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.Process;
import android.os.RemoteException;
import android.telephony.data.EpsBearerQosSessionAttributes;
import android.telephony.data.NrQosSessionAttributes;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.FrameworkConnectivityStatsLog;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -943,6 +945,19 @@
private void queueOrSendMessage(@NonNull RegistryAction action) {
synchronized (mPreConnectedQueue) {
+ if (mNetwork == null && !Process.isApplicationUid(Process.myUid())) {
+ // Theoretically, it should not be valid to queue messages here before
+ // registering the NetworkAgent. However, practically, with the way
+ // queueing works right now, it ends up working out just fine.
+ // Log a statistic so that we know if this is happening in the
+ // wild. The check for isApplicationUid is to prevent logging the
+ // metric from test code.
+
+ FrameworkConnectivityStatsLog.write(
+ FrameworkConnectivityStatsLog.CORE_NETWORKING_TERRIBLE_ERROR_OCCURRED,
+ FrameworkConnectivityStatsLog.CORE_NETWORKING_TERRIBLE_ERROR_OCCURRED__ERROR_TYPE__TYPE_MESSAGE_QUEUED_BEFORE_CONNECT
+ );
+ }
if (mRegistry != null) {
try {
action.execute(mRegistry);
diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java
index f86d127..bd8f7b9 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java
@@ -15,11 +15,11 @@
*/
package com.android.server.net.ct;
-import android.annotation.NonNull;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import android.annotation.RequiresApi;
import android.app.DownloadManager;
import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -31,16 +31,12 @@
import com.android.server.net.ct.DownloadHelper.DownloadStatus;
+import org.json.JSONException;
+import org.json.JSONObject;
+
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
-import java.security.InvalidKeyException;
-import java.security.KeyFactory;
-import java.security.PublicKey;
-import java.security.Signature;
-import java.security.spec.X509EncodedKeySpec;
-import java.util.Base64;
-import java.util.Optional;
/** Helper class to download certificate transparency log files. */
@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
@@ -51,30 +47,22 @@
private final Context mContext;
private final DataStore mDataStore;
private final DownloadHelper mDownloadHelper;
+ private final SignatureVerifier mSignatureVerifier;
private final CertificateTransparencyInstaller mInstaller;
- @NonNull private Optional<PublicKey> mPublicKey = Optional.empty();
-
- @VisibleForTesting
CertificateTransparencyDownloader(
Context context,
DataStore dataStore,
DownloadHelper downloadHelper,
+ SignatureVerifier signatureVerifier,
CertificateTransparencyInstaller installer) {
mContext = context;
+ mSignatureVerifier = signatureVerifier;
mDataStore = dataStore;
mDownloadHelper = downloadHelper;
mInstaller = installer;
}
- CertificateTransparencyDownloader(Context context, DataStore dataStore) {
- this(
- context,
- dataStore,
- new DownloadHelper(context),
- new CertificateTransparencyInstaller());
- }
-
void initialize() {
mInstaller.addCompatibilityVersion(Config.COMPATIBILITY_VERSION);
@@ -87,18 +75,14 @@
}
}
- void setPublicKey(String publicKey) throws GeneralSecurityException {
- mPublicKey =
- Optional.of(
- KeyFactory.getInstance("RSA")
- .generatePublic(
- new X509EncodedKeySpec(
- Base64.getDecoder().decode(publicKey))));
- }
-
- @VisibleForTesting
- void resetPublicKey() {
- mPublicKey = Optional.empty();
+ void startPublicKeyDownload(String publicKeyUrl) {
+ long downloadId = download(publicKeyUrl);
+ if (downloadId == -1) {
+ Log.e(TAG, "Metadata download request failed for " + publicKeyUrl);
+ return;
+ }
+ mDataStore.setPropertyLong(Config.PUBLIC_KEY_URL_KEY, downloadId);
+ mDataStore.store();
}
void startMetadataDownload(String metadataUrl) {
@@ -135,6 +119,11 @@
return;
}
+ if (isPublicKeyDownloadId(completedId)) {
+ handlePublicKeyDownloadCompleted(completedId);
+ return;
+ }
+
if (isMetadataDownloadId(completedId)) {
handleMetadataDownloadCompleted(completedId);
return;
@@ -145,7 +134,30 @@
return;
}
- Log.e(TAG, "Download id " + completedId + " is neither metadata nor content.");
+ Log.i(TAG, "Download id " + completedId + " is not recognized.");
+ }
+
+ private void handlePublicKeyDownloadCompleted(long downloadId) {
+ DownloadStatus status = mDownloadHelper.getDownloadStatus(downloadId);
+ if (!status.isSuccessful()) {
+ handleDownloadFailed(status);
+ return;
+ }
+
+ Uri publicKeyUri = getPublicKeyDownloadUri();
+ if (publicKeyUri == null) {
+ Log.e(TAG, "Invalid public key URI");
+ return;
+ }
+
+ try {
+ mSignatureVerifier.setPublicKeyFrom(publicKeyUri);
+ } catch (GeneralSecurityException | IOException | IllegalArgumentException e) {
+ Log.e(TAG, "Error setting the public Key", e);
+ return;
+ }
+
+ startMetadataDownload(mDataStore.getProperty(Config.METADATA_URL_PENDING));
}
private void handleMetadataDownloadCompleted(long downloadId) {
@@ -173,7 +185,7 @@
boolean success = false;
try {
- success = verify(contentUri, metadataUri);
+ success = mSignatureVerifier.verify(contentUri, metadataUri);
} catch (IOException | GeneralSecurityException e) {
Log.e(TAG, "Could not verify new log list", e);
}
@@ -182,9 +194,16 @@
return;
}
- // TODO: validate file content.
+ String version = null;
+ try (InputStream inputStream = mContext.getContentResolver().openInputStream(contentUri)) {
+ version =
+ new JSONObject(new String(inputStream.readAllBytes(), UTF_8))
+ .getString("version");
+ } catch (JSONException | IOException e) {
+ Log.e(TAG, "Could not extract version from log list", e);
+ return;
+ }
- String version = mDataStore.getProperty(Config.VERSION_PENDING);
String contentUrl = mDataStore.getProperty(Config.CONTENT_URL_PENDING);
String metadataUrl = mDataStore.getProperty(Config.METADATA_URL_PENDING);
try (InputStream inputStream = mContext.getContentResolver().openInputStream(contentUri)) {
@@ -204,25 +223,10 @@
}
private void handleDownloadFailed(DownloadStatus status) {
- Log.e(TAG, "Content download failed with " + status);
+ Log.e(TAG, "Download failed with " + status);
// TODO(378626065): Report failure via statsd.
}
- private boolean verify(Uri file, Uri signature) throws IOException, GeneralSecurityException {
- if (!mPublicKey.isPresent()) {
- throw new InvalidKeyException("Missing public key for signature verification");
- }
- Signature verifier = Signature.getInstance("SHA256withRSA");
- verifier.initVerify(mPublicKey.get());
- ContentResolver contentResolver = mContext.getContentResolver();
-
- try (InputStream fileStream = contentResolver.openInputStream(file);
- InputStream signatureStream = contentResolver.openInputStream(signature)) {
- verifier.update(fileStream.readAllBytes());
- return verifier.verify(signatureStream.readAllBytes());
- }
- }
-
private long download(String url) {
try {
return mDownloadHelper.startDownload(url);
@@ -233,6 +237,11 @@
}
@VisibleForTesting
+ boolean isPublicKeyDownloadId(long downloadId) {
+ return mDataStore.getPropertyLong(Config.PUBLIC_KEY_URL_KEY, -1) == downloadId;
+ }
+
+ @VisibleForTesting
boolean isMetadataDownloadId(long downloadId) {
return mDataStore.getPropertyLong(Config.METADATA_URL_KEY, -1) == downloadId;
}
@@ -242,6 +251,10 @@
return mDataStore.getPropertyLong(Config.CONTENT_URL_KEY, -1) == downloadId;
}
+ private Uri getPublicKeyDownloadUri() {
+ return mDownloadHelper.getUri(mDataStore.getPropertyLong(Config.PUBLIC_KEY_URL_KEY, -1));
+ }
+
private Uri getMetadataDownloadUri() {
return mDownloadHelper.getUri(mDataStore.getPropertyLong(Config.METADATA_URL_KEY, -1));
}
diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyFlagsListener.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyFlagsListener.java
index 0ae982d..f359a2a 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyFlagsListener.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyFlagsListener.java
@@ -16,7 +16,6 @@
package com.android.server.net.ct;
import android.annotation.RequiresApi;
-import android.content.Context;
import android.os.Build;
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.Properties;
@@ -33,12 +32,16 @@
private static final String TAG = "CertificateTransparencyFlagsListener";
private final DataStore mDataStore;
+ private final SignatureVerifier mSignatureVerifier;
private final CertificateTransparencyDownloader mCertificateTransparencyDownloader;
- CertificateTransparencyFlagsListener(Context context) {
- mDataStore = new DataStore(Config.PREFERENCES_FILE);
- mCertificateTransparencyDownloader =
- new CertificateTransparencyDownloader(context, mDataStore);
+ CertificateTransparencyFlagsListener(
+ DataStore dataStore,
+ SignatureVerifier signatureVerifier,
+ CertificateTransparencyDownloader certificateTransparencyDownloader) {
+ mDataStore = dataStore;
+ mSignatureVerifier = signatureVerifier;
+ mCertificateTransparencyDownloader = certificateTransparencyDownloader;
}
void initialize() {
@@ -104,8 +107,8 @@
}
try {
- mCertificateTransparencyDownloader.setPublicKey(newPublicKey);
- } catch (GeneralSecurityException e) {
+ mSignatureVerifier.setPublicKey(newPublicKey);
+ } catch (GeneralSecurityException | IllegalArgumentException e) {
Log.e(TAG, "Error setting the public Key", e);
return;
}
diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyJob.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyJob.java
new file mode 100644
index 0000000..c5d0413
--- /dev/null
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyJob.java
@@ -0,0 +1,88 @@
+/*
+ * 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 com.android.server.net.ct;
+
+import android.annotation.RequiresApi;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Build;
+import android.os.SystemClock;
+import android.util.Log;
+
+/** Implementation of the Certificate Transparency job */
+@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+public class CertificateTransparencyJob extends BroadcastReceiver {
+
+ private static final String TAG = "CertificateTransparencyJob";
+
+ private static final String ACTION_JOB_START = "com.android.server.net.ct.action.JOB_START";
+
+ private final Context mContext;
+ private final DataStore mDataStore;
+ private final CertificateTransparencyDownloader mCertificateTransparencyDownloader;
+ private final AlarmManager mAlarmManager;
+
+ /** Creates a new {@link CertificateTransparencyJob} object. */
+ public CertificateTransparencyJob(
+ Context context,
+ DataStore dataStore,
+ CertificateTransparencyDownloader certificateTransparencyDownloader) {
+ mContext = context;
+ mDataStore = dataStore;
+ mCertificateTransparencyDownloader = certificateTransparencyDownloader;
+ mAlarmManager = context.getSystemService(AlarmManager.class);
+ }
+
+ void initialize() {
+ mDataStore.load();
+ mCertificateTransparencyDownloader.initialize();
+
+ mContext.registerReceiver(
+ this, new IntentFilter(ACTION_JOB_START), Context.RECEIVER_EXPORTED);
+ mAlarmManager.setInexactRepeating(
+ AlarmManager.ELAPSED_REALTIME,
+ SystemClock.elapsedRealtime(), // schedule first job at earliest convenient time.
+ AlarmManager.INTERVAL_DAY,
+ PendingIntent.getBroadcast(
+ mContext, 0, new Intent(ACTION_JOB_START), PendingIntent.FLAG_IMMUTABLE));
+
+ if (Config.DEBUG) {
+ Log.d(TAG, "CertificateTransparencyJob scheduled successfully.");
+ }
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (!ACTION_JOB_START.equals(intent.getAction())) {
+ Log.w(TAG, "Received unexpected broadcast with action " + intent);
+ return;
+ }
+ if (Config.DEBUG) {
+ Log.d(TAG, "Starting CT daily job.");
+ }
+
+ mDataStore.setProperty(Config.CONTENT_URL_PENDING, Config.URL_LOG_LIST);
+ mDataStore.setProperty(Config.METADATA_URL_PENDING, Config.URL_SIGNATURE);
+ mDataStore.setProperty(Config.PUBLIC_KEY_URL_PENDING, Config.URL_PUBLIC_KEY);
+ mDataStore.store();
+
+ mCertificateTransparencyDownloader.startPublicKeyDownload(Config.URL_PUBLIC_KEY);
+ }
+}
diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java
index edf7c56..92b2b09 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java
@@ -29,6 +29,7 @@
public class CertificateTransparencyService extends ICertificateTransparencyManager.Stub {
private final CertificateTransparencyFlagsListener mFlagsListener;
+ private final CertificateTransparencyJob mCertificateTransparencyJob;
/**
* @return true if the CertificateTransparency service is enabled.
@@ -41,7 +42,21 @@
/** Creates a new {@link CertificateTransparencyService} object. */
public CertificateTransparencyService(Context context) {
- mFlagsListener = new CertificateTransparencyFlagsListener(context);
+ DataStore dataStore = new DataStore(Config.PREFERENCES_FILE);
+ DownloadHelper downloadHelper = new DownloadHelper(context);
+ SignatureVerifier signatureVerifier = new SignatureVerifier(context);
+ CertificateTransparencyDownloader downloader =
+ new CertificateTransparencyDownloader(
+ context,
+ dataStore,
+ downloadHelper,
+ signatureVerifier,
+ new CertificateTransparencyInstaller());
+
+ mFlagsListener =
+ new CertificateTransparencyFlagsListener(dataStore, signatureVerifier, downloader);
+ mCertificateTransparencyJob =
+ new CertificateTransparencyJob(context, dataStore, downloader);
}
/**
@@ -53,7 +68,11 @@
switch (phase) {
case SystemService.PHASE_BOOT_COMPLETED:
- mFlagsListener.initialize();
+ if (Flags.certificateTransparencyJob()) {
+ mCertificateTransparencyJob.initialize();
+ } else {
+ mFlagsListener.initialize();
+ }
break;
default:
}
diff --git a/networksecurity/service/src/com/android/server/net/ct/Config.java b/networksecurity/service/src/com/android/server/net/ct/Config.java
index 242f13a..ae30f3a 100644
--- a/networksecurity/service/src/com/android/server/net/ct/Config.java
+++ b/networksecurity/service/src/com/android/server/net/ct/Config.java
@@ -55,4 +55,13 @@
static final String METADATA_URL_PENDING = "metadata_url_pending";
static final String METADATA_URL = "metadata_url";
static final String METADATA_URL_KEY = "metadata_url_key";
+ static final String PUBLIC_KEY_URL_PENDING = "public_key_url_pending";
+ static final String PUBLIC_KEY_URL = "public_key_url";
+ static final String PUBLIC_KEY_URL_KEY = "public_key_url_key";
+
+ // URLs
+ static final String URL_BASE = "https://www.gstatic.com/android/certificate_transparency/";
+ static final String URL_LOG_LIST = URL_BASE + "log_list.json";
+ static final String URL_SIGNATURE = URL_BASE + "log_list.sig";
+ static final String URL_PUBLIC_KEY = URL_BASE + "log_list.pub";
}
diff --git a/networksecurity/service/src/com/android/server/net/ct/SignatureVerifier.java b/networksecurity/service/src/com/android/server/net/ct/SignatureVerifier.java
new file mode 100644
index 0000000..0b775ca
--- /dev/null
+++ b/networksecurity/service/src/com/android/server/net/ct/SignatureVerifier.java
@@ -0,0 +1,91 @@
+/*
+ * 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 com.android.server.net.ct;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresApi;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Build;
+
+import androidx.annotation.VisibleForTesting;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Base64;
+import java.util.Optional;
+
+/** Verifier of the log list signature. */
+@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+public class SignatureVerifier {
+
+ private final Context mContext;
+
+ @NonNull private Optional<PublicKey> mPublicKey = Optional.empty();
+
+ public SignatureVerifier(Context context) {
+ mContext = context;
+ }
+
+ @VisibleForTesting
+ Optional<PublicKey> getPublicKey() {
+ return mPublicKey;
+ }
+
+ void resetPublicKey() {
+ mPublicKey = Optional.empty();
+ }
+
+ void setPublicKeyFrom(Uri file) throws GeneralSecurityException, IOException {
+ try (InputStream fileStream = mContext.getContentResolver().openInputStream(file)) {
+ setPublicKey(new String(fileStream.readAllBytes()));
+ }
+ }
+
+ void setPublicKey(String publicKey) throws GeneralSecurityException {
+ setPublicKey(
+ KeyFactory.getInstance("RSA")
+ .generatePublic(
+ new X509EncodedKeySpec(Base64.getDecoder().decode(publicKey))));
+ }
+
+ @VisibleForTesting
+ void setPublicKey(PublicKey publicKey) {
+ mPublicKey = Optional.of(publicKey);
+ }
+
+ boolean verify(Uri file, Uri signature) throws GeneralSecurityException, IOException {
+ if (!mPublicKey.isPresent()) {
+ throw new InvalidKeyException("Missing public key for signature verification");
+ }
+ Signature verifier = Signature.getInstance("SHA256withRSA");
+ verifier.initVerify(mPublicKey.get());
+ ContentResolver contentResolver = mContext.getContentResolver();
+
+ try (InputStream fileStream = contentResolver.openInputStream(file);
+ InputStream signatureStream = contentResolver.openInputStream(signature)) {
+ verifier.update(fileStream.readAllBytes());
+ return verifier.verify(signatureStream.readAllBytes());
+ }
+ }
+}
diff --git a/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyDownloaderTest.java b/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyDownloaderTest.java
index fb55295..87d75e6 100644
--- a/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyDownloaderTest.java
+++ b/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyDownloaderTest.java
@@ -18,12 +18,14 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import android.app.DownloadManager;
import android.content.Context;
import android.content.Intent;
@@ -33,6 +35,8 @@
import com.android.server.net.ct.DownloadHelper.DownloadStatus;
+import org.json.JSONException;
+import org.json.JSONObject;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -67,8 +71,11 @@
private Context mContext;
private File mTempFile;
private DataStore mDataStore;
+ private SignatureVerifier mSignatureVerifier;
private CertificateTransparencyDownloader mCertificateTransparencyDownloader;
+ private long mNextDownloadId = 666;
+
@Before
public void setUp() throws IOException, GeneralSecurityException {
MockitoAnnotations.initMocks(this);
@@ -82,23 +89,37 @@
mTempFile = File.createTempFile("datastore-test", ".properties");
mDataStore = new DataStore(mTempFile);
mDataStore.load();
+ mSignatureVerifier = new SignatureVerifier(mContext);
mCertificateTransparencyDownloader =
new CertificateTransparencyDownloader(
- mContext, mDataStore, mDownloadHelper, mCertificateTransparencyInstaller);
+ mContext,
+ mDataStore,
+ mDownloadHelper,
+ mSignatureVerifier,
+ mCertificateTransparencyInstaller);
}
@After
public void tearDown() {
mTempFile.delete();
- mCertificateTransparencyDownloader.resetPublicKey();
+ mSignatureVerifier.resetPublicKey();
+ }
+
+ @Test
+ public void testDownloader_startPublicKeyDownload() {
+ String publicKeyUrl = "http://test-public-key.org";
+ long downloadId = preparePublicKeyDownload(publicKeyUrl);
+
+ assertThat(mCertificateTransparencyDownloader.isPublicKeyDownloadId(downloadId)).isFalse();
+ mCertificateTransparencyDownloader.startPublicKeyDownload(publicKeyUrl);
+ assertThat(mCertificateTransparencyDownloader.isPublicKeyDownloadId(downloadId)).isTrue();
}
@Test
public void testDownloader_startMetadataDownload() {
String metadataUrl = "http://test-metadata.org";
- long downloadId = 666;
- when(mDownloadHelper.startDownload(metadataUrl)).thenReturn(downloadId);
+ long downloadId = prepareMetadataDownload(metadataUrl);
assertThat(mCertificateTransparencyDownloader.isMetadataDownloadId(downloadId)).isFalse();
mCertificateTransparencyDownloader.startMetadataDownload(metadataUrl);
@@ -108,8 +129,7 @@
@Test
public void testDownloader_startContentDownload() {
String contentUrl = "http://test-content.org";
- long downloadId = 666;
- when(mDownloadHelper.startDownload(contentUrl)).thenReturn(downloadId);
+ long downloadId = prepareContentDownload(contentUrl);
assertThat(mCertificateTransparencyDownloader.isContentDownloadId(downloadId)).isFalse();
mCertificateTransparencyDownloader.startContentDownload(contentUrl);
@@ -117,16 +137,65 @@
}
@Test
- public void testDownloader_metadataDownloadSuccess_startContentDownload() {
- long metadataId = 123;
- mDataStore.setPropertyLong(Config.METADATA_URL_KEY, metadataId);
- when(mDownloadHelper.getDownloadStatus(metadataId))
- .thenReturn(makeSuccessfulDownloadStatus(metadataId));
- long contentId = 666;
- String contentUrl = "http://test-content.org";
- mDataStore.setProperty(Config.CONTENT_URL_PENDING, contentUrl);
- when(mDownloadHelper.startDownload(contentUrl)).thenReturn(contentId);
+ public void testDownloader_publicKeyDownloadSuccess_updatePublicKey_startMetadataDownload()
+ throws Exception {
+ long publicKeyId = prepareSuccessfulPublicKeyDownload(writePublicKeyToFile(mPublicKey));
+ long metadataId = prepareMetadataDownload("http://test-metadata.org");
+ assertThat(mSignatureVerifier.getPublicKey()).isEmpty();
+ assertThat(mCertificateTransparencyDownloader.isMetadataDownloadId(metadataId)).isFalse();
+ mCertificateTransparencyDownloader.onReceive(
+ mContext, makeDownloadCompleteIntent(publicKeyId));
+
+ assertThat(mSignatureVerifier.getPublicKey()).hasValue(mPublicKey);
+ assertThat(mCertificateTransparencyDownloader.isMetadataDownloadId(metadataId)).isTrue();
+ }
+
+ @Test
+ public void
+ testDownloader_publicKeyDownloadSuccess_updatePublicKeyFail_doNotStartMetadataDownload()
+ throws Exception {
+ long publicKeyId =
+ prepareSuccessfulPublicKeyDownload(
+ writeToFile("i_am_not_a_base64_encoded_public_key".getBytes()));
+ long metadataId = prepareMetadataDownload("http://test-metadata.org");
+
+ assertThat(mSignatureVerifier.getPublicKey()).isEmpty();
+ assertThat(mCertificateTransparencyDownloader.isMetadataDownloadId(metadataId)).isFalse();
+ mCertificateTransparencyDownloader.onReceive(
+ mContext, makeDownloadCompleteIntent(publicKeyId));
+
+ assertThat(mSignatureVerifier.getPublicKey()).isEmpty();
+ assertThat(mCertificateTransparencyDownloader.isMetadataDownloadId(metadataId)).isFalse();
+ verify(mDownloadHelper, never()).startDownload(anyString());
+ }
+
+ @Test
+ public void testDownloader_publicKeyDownloadFail_doNotUpdatePublicKey() throws Exception {
+ long publicKeyId =
+ prepareFailedPublicKeyDownload(
+ // Failure cases where we give up on the download.
+ DownloadManager.ERROR_INSUFFICIENT_SPACE,
+ DownloadManager.ERROR_HTTP_DATA_ERROR);
+ Intent downloadCompleteIntent = makeDownloadCompleteIntent(publicKeyId);
+ long metadataId = prepareMetadataDownload("http://test-metadata.org");
+
+ assertThat(mSignatureVerifier.getPublicKey()).isEmpty();
+ assertThat(mCertificateTransparencyDownloader.isMetadataDownloadId(metadataId)).isFalse();
+ mCertificateTransparencyDownloader.onReceive(mContext, downloadCompleteIntent);
+ mCertificateTransparencyDownloader.onReceive(mContext, downloadCompleteIntent);
+
+ assertThat(mSignatureVerifier.getPublicKey()).isEmpty();
+ assertThat(mCertificateTransparencyDownloader.isMetadataDownloadId(metadataId)).isFalse();
+ verify(mDownloadHelper, never()).startDownload(anyString());
+ }
+
+ @Test
+ public void testDownloader_metadataDownloadSuccess_startContentDownload() {
+ long metadataId = prepareSuccessfulMetadataDownload(new File("log_list.sig"));
+ long contentId = prepareContentDownload("http://test-content.org");
+
+ assertThat(mCertificateTransparencyDownloader.isContentDownloadId(contentId)).isFalse();
mCertificateTransparencyDownloader.onReceive(
mContext, makeDownloadCompleteIntent(metadataId));
@@ -135,162 +204,270 @@
@Test
public void testDownloader_metadataDownloadFail_doNotStartContentDownload() {
- long metadataId = 123;
- mDataStore.setPropertyLong(Config.METADATA_URL_KEY, metadataId);
- String contentUrl = "http://test-content.org";
- mDataStore.setProperty(Config.CONTENT_URL_PENDING, contentUrl);
+ long metadataId =
+ prepareFailedMetadataDownload(
+ // Failure cases where we give up on the download.
+ DownloadManager.ERROR_INSUFFICIENT_SPACE,
+ DownloadManager.ERROR_HTTP_DATA_ERROR);
Intent downloadCompleteIntent = makeDownloadCompleteIntent(metadataId);
- // In all these failure cases we give up on the download.
- when(mDownloadHelper.getDownloadStatus(metadataId))
- .thenReturn(
- makeHttpErrorDownloadStatus(metadataId),
- makeStorageErrorDownloadStatus(metadataId));
+ long contentId = prepareContentDownload("http://test-content.org");
+ assertThat(mCertificateTransparencyDownloader.isContentDownloadId(contentId)).isFalse();
mCertificateTransparencyDownloader.onReceive(mContext, downloadCompleteIntent);
mCertificateTransparencyDownloader.onReceive(mContext, downloadCompleteIntent);
- verify(mDownloadHelper, never()).startDownload(contentUrl);
+ assertThat(mCertificateTransparencyDownloader.isContentDownloadId(contentId)).isFalse();
+ verify(mDownloadHelper, never()).startDownload(anyString());
}
@Test
public void testDownloader_contentDownloadSuccess_installSuccess_updateDataStore()
throws Exception {
- String version = "456";
- long contentId = 666;
- File logListFile = File.createTempFile("log_list", "json");
- Uri contentUri = Uri.fromFile(logListFile);
- long metadataId = 123;
+ String newVersion = "456";
+ File logListFile = makeLogListFile(newVersion);
File metadataFile = sign(logListFile);
- Uri metadataUri = Uri.fromFile(metadataFile);
- mCertificateTransparencyDownloader.setPublicKey(
- Base64.getEncoder().encodeToString(mPublicKey.getEncoded()));
- setUpContentDownloadCompleteSuccessful(
- version, metadataId, metadataUri, contentId, contentUri);
+ mSignatureVerifier.setPublicKey(mPublicKey);
+ prepareSuccessfulMetadataDownload(metadataFile);
+ long contentId = prepareSuccessfulContentDownload(logListFile);
when(mCertificateTransparencyInstaller.install(
- eq(Config.COMPATIBILITY_VERSION), any(), eq(version)))
+ eq(Config.COMPATIBILITY_VERSION), any(), anyString()))
.thenReturn(true);
- assertThat(mDataStore.getProperty(Config.VERSION)).isNull();
- assertThat(mDataStore.getProperty(Config.CONTENT_URL)).isNull();
- assertThat(mDataStore.getProperty(Config.METADATA_URL)).isNull();
+ assertNoVersionIsInstalled();
mCertificateTransparencyDownloader.onReceive(
mContext, makeDownloadCompleteIntent(contentId));
- verify(mCertificateTransparencyInstaller, times(1))
- .install(eq(Config.COMPATIBILITY_VERSION), any(), eq(version));
- assertThat(mDataStore.getProperty(Config.VERSION)).isEqualTo(version);
- assertThat(mDataStore.getProperty(Config.CONTENT_URL)).isEqualTo(contentUri.toString());
- assertThat(mDataStore.getProperty(Config.METADATA_URL)).isEqualTo(metadataUri.toString());
+ assertInstallSuccessful(newVersion);
}
@Test
public void testDownloader_contentDownloadFail_doNotInstall() throws Exception {
- mDataStore.setProperty(Config.VERSION_PENDING, "123");
- long contentId = 666;
+ long contentId =
+ prepareFailedContentDownload(
+ // Failure cases where we give up on the download.
+ DownloadManager.ERROR_INSUFFICIENT_SPACE,
+ DownloadManager.ERROR_HTTP_DATA_ERROR);
Intent downloadCompleteIntent = makeDownloadCompleteIntent(contentId);
- // In all these failure cases we give up on the download.
- when(mDownloadHelper.getDownloadStatus(contentId))
- .thenReturn(
- makeHttpErrorDownloadStatus(contentId),
- makeStorageErrorDownloadStatus(contentId));
mCertificateTransparencyDownloader.onReceive(mContext, downloadCompleteIntent);
mCertificateTransparencyDownloader.onReceive(mContext, downloadCompleteIntent);
- mCertificateTransparencyDownloader.onReceive(mContext, downloadCompleteIntent);
verify(mCertificateTransparencyInstaller, never()).install(any(), any(), any());
- assertThat(mDataStore.getProperty(Config.VERSION)).isNull();
- assertThat(mDataStore.getProperty(Config.CONTENT_URL)).isNull();
- assertThat(mDataStore.getProperty(Config.METADATA_URL)).isNull();
+ assertNoVersionIsInstalled();
}
@Test
public void testDownloader_contentDownloadSuccess_installFail_doNotUpdateDataStore()
throws Exception {
- String version = "456";
- long contentId = 666;
- File logListFile = File.createTempFile("log_list", "json");
- Uri contentUri = Uri.fromFile(logListFile);
- long metadataId = 123;
+ File logListFile = makeLogListFile("456");
File metadataFile = sign(logListFile);
- Uri metadataUri = Uri.fromFile(metadataFile);
- setUpContentDownloadCompleteSuccessful(
- version, metadataId, metadataUri, contentId, contentUri);
+ mSignatureVerifier.setPublicKey(mPublicKey);
+ prepareSuccessfulMetadataDownload(metadataFile);
+ long contentId = prepareSuccessfulContentDownload(logListFile);
when(mCertificateTransparencyInstaller.install(
- eq(Config.COMPATIBILITY_VERSION), any(), eq(version)))
+ eq(Config.COMPATIBILITY_VERSION), any(), anyString()))
.thenReturn(false);
+ assertNoVersionIsInstalled();
mCertificateTransparencyDownloader.onReceive(
mContext, makeDownloadCompleteIntent(contentId));
- assertThat(mDataStore.getProperty(Config.VERSION)).isNull();
- assertThat(mDataStore.getProperty(Config.CONTENT_URL)).isNull();
- assertThat(mDataStore.getProperty(Config.METADATA_URL)).isNull();
+ assertNoVersionIsInstalled();
}
@Test
public void testDownloader_contentDownloadSuccess_verificationFail_doNotInstall()
- throws IOException {
- String version = "456";
- long contentId = 666;
- Uri contentUri = Uri.fromFile(File.createTempFile("log_list", "json"));
- long metadataId = 123;
- Uri metadataUri = Uri.fromFile(File.createTempFile("log_list-wrong_metadata", "sig"));
- setUpContentDownloadCompleteSuccessful(
- version, metadataId, metadataUri, contentId, contentUri);
+ throws Exception {
+ File logListFile = makeLogListFile("456");
+ File metadataFile = File.createTempFile("log_list-wrong_metadata", "sig");
+ mSignatureVerifier.setPublicKey(mPublicKey);
+ prepareSuccessfulMetadataDownload(metadataFile);
+ long contentId = prepareSuccessfulContentDownload(logListFile);
+ assertNoVersionIsInstalled();
mCertificateTransparencyDownloader.onReceive(
mContext, makeDownloadCompleteIntent(contentId));
verify(mCertificateTransparencyInstaller, never())
- .install(eq(Config.COMPATIBILITY_VERSION), any(), eq(version));
- assertThat(mDataStore.getProperty(Config.VERSION)).isNull();
- assertThat(mDataStore.getProperty(Config.CONTENT_URL)).isNull();
- assertThat(mDataStore.getProperty(Config.METADATA_URL)).isNull();
+ .install(eq(Config.COMPATIBILITY_VERSION), any(), anyString());
+ assertNoVersionIsInstalled();
}
@Test
public void testDownloader_contentDownloadSuccess_missingVerificationPublicKey_doNotInstall()
throws Exception {
- String version = "456";
- long contentId = 666;
- File logListFile = File.createTempFile("log_list", "json");
- Uri contentUri = Uri.fromFile(logListFile);
- long metadataId = 123;
+ File logListFile = makeLogListFile("456");
File metadataFile = sign(logListFile);
- Uri metadataUri = Uri.fromFile(metadataFile);
- setUpContentDownloadCompleteSuccessful(
- version, metadataId, metadataUri, contentId, contentUri);
+ mSignatureVerifier.resetPublicKey();
+ prepareSuccessfulMetadataDownload(metadataFile);
+ long contentId = prepareSuccessfulContentDownload(logListFile);
+ assertNoVersionIsInstalled();
mCertificateTransparencyDownloader.onReceive(
mContext, makeDownloadCompleteIntent(contentId));
verify(mCertificateTransparencyInstaller, never())
- .install(eq(Config.COMPATIBILITY_VERSION), any(), eq(version));
+ .install(eq(Config.COMPATIBILITY_VERSION), any(), anyString());
+ assertNoVersionIsInstalled();
+ }
+
+ @Test
+ public void testDownloader_endToEndSuccess_installNewVersion() throws Exception {
+ String newVersion = "456";
+ File logListFile = makeLogListFile(newVersion);
+ File metadataFile = sign(logListFile);
+ File publicKeyFile = writePublicKeyToFile(mPublicKey);
+
+ assertNoVersionIsInstalled();
+
+ // 1. Start download of public key.
+ String publicKeyUrl = "http://test-public-key.org";
+ long publicKeyId = preparePublicKeyDownload(publicKeyUrl);
+
+ mCertificateTransparencyDownloader.startPublicKeyDownload(publicKeyUrl);
+
+ // 2. On successful public key download, set the key and start the metatadata download.
+ setSuccessfulDownload(publicKeyId, publicKeyFile);
+ long metadataId = prepareMetadataDownload("http://test-metadata.org");
+
+ mCertificateTransparencyDownloader.onReceive(
+ mContext, makeDownloadCompleteIntent(publicKeyId));
+
+ // 3. On successful metadata download, start the content download.
+ setSuccessfulDownload(metadataId, metadataFile);
+ long contentId = prepareContentDownload("http://test-content.org");
+
+ mCertificateTransparencyDownloader.onReceive(
+ mContext, makeDownloadCompleteIntent(metadataId));
+
+ // 4. On successful content download, verify the signature and install the new version.
+ setSuccessfulDownload(contentId, logListFile);
+ when(mCertificateTransparencyInstaller.install(
+ eq(Config.COMPATIBILITY_VERSION), any(), anyString()))
+ .thenReturn(true);
+
+ mCertificateTransparencyDownloader.onReceive(
+ mContext, makeDownloadCompleteIntent(contentId));
+
+ assertInstallSuccessful(newVersion);
+ }
+
+ private void assertNoVersionIsInstalled() {
assertThat(mDataStore.getProperty(Config.VERSION)).isNull();
assertThat(mDataStore.getProperty(Config.CONTENT_URL)).isNull();
assertThat(mDataStore.getProperty(Config.METADATA_URL)).isNull();
}
+ private void assertInstallSuccessful(String version) {
+ assertThat(mDataStore.getProperty(Config.VERSION)).isEqualTo(version);
+ assertThat(mDataStore.getProperty(Config.CONTENT_URL))
+ .isEqualTo(mDataStore.getProperty(Config.CONTENT_URL_PENDING));
+ assertThat(mDataStore.getProperty(Config.METADATA_URL))
+ .isEqualTo(mDataStore.getProperty(Config.METADATA_URL_PENDING));
+ }
+
private Intent makeDownloadCompleteIntent(long downloadId) {
return new Intent(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
.putExtra(DownloadManager.EXTRA_DOWNLOAD_ID, downloadId);
}
- private void setUpContentDownloadCompleteSuccessful(
- String version, long metadataId, Uri metadataUri, long contentId, Uri contentUri)
- throws IOException {
- mDataStore.setProperty(Config.VERSION_PENDING, version);
+ private long prepareDownloadId(String url) {
+ long downloadId = mNextDownloadId++;
+ when(mDownloadHelper.startDownload(url)).thenReturn(downloadId);
+ return downloadId;
+ }
- mDataStore.setPropertyLong(Config.METADATA_URL_KEY, metadataId);
- mDataStore.setProperty(Config.METADATA_URL_PENDING, metadataUri.toString());
- when(mDownloadHelper.getUri(metadataId)).thenReturn(metadataUri);
+ private long preparePublicKeyDownload(String url) {
+ long downloadId = prepareDownloadId(url);
+ mDataStore.setProperty(Config.PUBLIC_KEY_URL_PENDING, url);
+ return downloadId;
+ }
- mDataStore.setPropertyLong(Config.CONTENT_URL_KEY, contentId);
- mDataStore.setProperty(Config.CONTENT_URL_PENDING, contentUri.toString());
- when(mDownloadHelper.getDownloadStatus(contentId))
- .thenReturn(makeSuccessfulDownloadStatus(contentId));
- when(mDownloadHelper.getUri(contentId)).thenReturn(contentUri);
+ private long prepareMetadataDownload(String url) {
+ long downloadId = prepareDownloadId(url);
+ mDataStore.setProperty(Config.METADATA_URL_PENDING, url);
+ return downloadId;
+ }
+
+ private long prepareContentDownload(String url) {
+ long downloadId = prepareDownloadId(url);
+ mDataStore.setProperty(Config.CONTENT_URL_PENDING, url);
+ return downloadId;
+ }
+
+ private long prepareSuccessfulDownload(String propertyKey) {
+ long downloadId = mNextDownloadId++;
+ mDataStore.setPropertyLong(propertyKey, downloadId);
+ when(mDownloadHelper.getDownloadStatus(downloadId))
+ .thenReturn(makeSuccessfulDownloadStatus(downloadId));
+ return downloadId;
+ }
+
+ private long prepareSuccessfulDownload(String propertyKey, File file) {
+ long downloadId = prepareSuccessfulDownload(propertyKey);
+ when(mDownloadHelper.getUri(downloadId)).thenReturn(Uri.fromFile(file));
+ return downloadId;
+ }
+
+ private long prepareSuccessfulPublicKeyDownload(File file) {
+ long downloadId = prepareSuccessfulDownload(Config.PUBLIC_KEY_URL_KEY, file);
+ mDataStore.setProperty(
+ Config.METADATA_URL_PENDING, "http://public-key-was-downloaded-here.org");
+ return downloadId;
+ }
+
+ private long prepareSuccessfulMetadataDownload(File file) {
+ long downloadId = prepareSuccessfulDownload(Config.METADATA_URL_KEY, file);
+ mDataStore.setProperty(
+ Config.METADATA_URL_PENDING, "http://metadata-was-downloaded-here.org");
+ return downloadId;
+ }
+
+ private long prepareSuccessfulContentDownload(File file) {
+ long downloadId = prepareSuccessfulDownload(Config.CONTENT_URL_KEY, file);
+ mDataStore.setProperty(
+ Config.CONTENT_URL_PENDING, "http://content-was-downloaded-here.org");
+ return downloadId;
+ }
+
+ private void setSuccessfulDownload(long downloadId, File file) {
+ when(mDownloadHelper.getDownloadStatus(downloadId))
+ .thenReturn(makeSuccessfulDownloadStatus(downloadId));
+ when(mDownloadHelper.getUri(downloadId)).thenReturn(Uri.fromFile(file));
+ }
+
+ private long prepareFailedDownload(String propertyKey, int... downloadManagerErrors) {
+ long downloadId = mNextDownloadId++;
+ mDataStore.setPropertyLong(propertyKey, downloadId);
+ DownloadStatus firstError =
+ DownloadStatus.builder()
+ .setDownloadId(downloadId)
+ .setStatus(DownloadManager.STATUS_FAILED)
+ .setReason(downloadManagerErrors[0])
+ .build();
+ DownloadStatus[] otherErrors = new DownloadStatus[downloadManagerErrors.length - 1];
+ for (int i = 1; i < downloadManagerErrors.length; i++) {
+ otherErrors[i - 1] =
+ DownloadStatus.builder()
+ .setDownloadId(downloadId)
+ .setStatus(DownloadManager.STATUS_FAILED)
+ .setReason(downloadManagerErrors[i])
+ .build();
+ }
+ when(mDownloadHelper.getDownloadStatus(downloadId)).thenReturn(firstError, otherErrors);
+ return downloadId;
+ }
+
+ private long prepareFailedPublicKeyDownload(int... downloadManagerErrors) {
+ return prepareFailedDownload(Config.PUBLIC_KEY_URL_KEY, downloadManagerErrors);
+ }
+
+ private long prepareFailedMetadataDownload(int... downloadManagerErrors) {
+ return prepareFailedDownload(Config.METADATA_URL_KEY, downloadManagerErrors);
+ }
+
+ private long prepareFailedContentDownload(int... downloadManagerErrors) {
+ return prepareFailedDownload(Config.CONTENT_URL_KEY, downloadManagerErrors);
}
private DownloadStatus makeSuccessfulDownloadStatus(long downloadId) {
@@ -300,20 +477,29 @@
.build();
}
- private DownloadStatus makeStorageErrorDownloadStatus(long downloadId) {
- return DownloadStatus.builder()
- .setDownloadId(downloadId)
- .setStatus(DownloadManager.STATUS_FAILED)
- .setReason(DownloadManager.ERROR_INSUFFICIENT_SPACE)
- .build();
+ private File writePublicKeyToFile(PublicKey publicKey)
+ throws IOException, GeneralSecurityException {
+ return writeToFile(Base64.getEncoder().encode(publicKey.getEncoded()));
}
- private DownloadStatus makeHttpErrorDownloadStatus(long downloadId) {
- return DownloadStatus.builder()
- .setDownloadId(downloadId)
- .setStatus(DownloadManager.STATUS_FAILED)
- .setReason(DownloadManager.ERROR_HTTP_DATA_ERROR)
- .build();
+ private File writeToFile(byte[] bytes) throws IOException, GeneralSecurityException {
+ File file = File.createTempFile("temp_file", "tmp");
+
+ try (OutputStream outputStream = new FileOutputStream(file)) {
+ outputStream.write(bytes);
+ }
+
+ return file;
+ }
+
+ private File makeLogListFile(String version) throws IOException, JSONException {
+ File logListFile = File.createTempFile("log_list", "json");
+
+ try (OutputStream outputStream = new FileOutputStream(logListFile)) {
+ outputStream.write(new JSONObject().put("version", version).toString().getBytes(UTF_8));
+ }
+
+ return logListFile;
}
private File sign(File file) throws IOException, GeneralSecurityException {
diff --git a/service-t/src/com/android/server/ethernet/EthernetTracker.java b/service-t/src/com/android/server/ethernet/EthernetTracker.java
index 67d0891..5228aab 100644
--- a/service-t/src/com/android/server/ethernet/EthernetTracker.java
+++ b/service-t/src/com/android/server/ethernet/EthernetTracker.java
@@ -139,8 +139,8 @@
private int mTetheringInterfaceMode = INTERFACE_MODE_CLIENT;
// Tracks whether clients were notified that the tethered interface is available
private boolean mTetheredInterfaceWasAvailable = false;
-
- private int mEthernetState = ETHERNET_STATE_ENABLED;
+ // Tracks the current state of ethernet as configured by EthernetManager#setEthernetEnabled.
+ private boolean mIsEthernetEnabled = true;
private class TetheredInterfaceRequestList extends
RemoteCallbackList<ITetheredInterfaceCallback> {
@@ -215,9 +215,6 @@
// Note: processNetlinkMessage is called on the handler thread.
@Override
protected void processNetlinkMessage(NetlinkMessage nlMsg, long whenMs) {
- // ignore all updates when ethernet is disabled.
- if (mEthernetState == ETHERNET_STATE_DISABLED) return;
-
if (nlMsg instanceof RtNetlinkLinkMessage) {
processRtNetlinkLinkMessage((RtNetlinkLinkMessage) nlMsg);
} else {
@@ -447,7 +444,7 @@
unicastInterfaceStateChange(listener, mTetheringInterface);
}
- unicastEthernetStateChange(listener, mEthernetState);
+ unicastEthernetStateChange(listener, mIsEthernetEnabled);
});
}
@@ -596,10 +593,13 @@
// Read the flags before attempting to bring up the interface. If the interface is
// already running an UP event is created after adding the interface.
config = NetdUtils.getInterfaceConfigParcel(mNetd, iface);
- if (NetdUtils.hasFlag(config, INetd.IF_STATE_DOWN)) {
+ // Only bring the interface up when ethernet is enabled.
+ if (mIsEthernetEnabled) {
// As a side-effect, NetdUtils#setInterfaceUp() also clears the interface's IPv4
// address and readds it which *could* lead to unexpected behavior in the future.
NetdUtils.setInterfaceUp(mNetd, iface);
+ } else {
+ NetdUtils.setInterfaceDown(mNetd, iface);
}
} catch (IllegalStateException e) {
// Either the system is crashing or the interface has disappeared. Just ignore the
@@ -646,6 +646,10 @@
}
private void setInterfaceAdministrativeState(String iface, boolean up, EthernetCallback cb) {
+ if (!mIsEthernetEnabled) {
+ cb.onError("Cannot enable/disable interface when ethernet is disabled");
+ return;
+ }
if (getInterfaceState(iface) == EthernetManager.STATE_ABSENT) {
cb.onError("Failed to enable/disable absent interface: " + iface);
return;
@@ -960,43 +964,51 @@
@VisibleForTesting(visibility = PACKAGE)
protected void setEthernetEnabled(boolean enabled) {
mHandler.post(() -> {
- int newState = enabled ? ETHERNET_STATE_ENABLED : ETHERNET_STATE_DISABLED;
- if (mEthernetState == newState) return;
+ if (mIsEthernetEnabled == enabled) return;
- mEthernetState = newState;
+ mIsEthernetEnabled = enabled;
- if (enabled) {
- trackAvailableInterfaces();
- } else {
- // TODO: maybe also disable server mode interface as well.
- untrackFactoryInterfaces();
+ // Interface in server mode should also be included.
+ ArrayList<String> interfaces =
+ new ArrayList<>(
+ List.of(mFactory.getAvailableInterfaces(/* includeRestricted */ true)));
+
+ if (mTetheringInterfaceMode == INTERFACE_MODE_SERVER) {
+ interfaces.add(mTetheringInterface);
}
- broadcastEthernetStateChange(mEthernetState);
+
+ for (String iface : interfaces) {
+ if (enabled) {
+ NetdUtils.setInterfaceUp(mNetd, iface);
+ } else {
+ NetdUtils.setInterfaceDown(mNetd, iface);
+ }
+ }
+ broadcastEthernetStateChange(mIsEthernetEnabled);
});
}
- private void untrackFactoryInterfaces() {
- for (String iface : mFactory.getAvailableInterfaces(true /* includeRestricted */)) {
- stopTrackingInterface(iface);
- }
+ private int isEthernetEnabledAsInt(boolean state) {
+ return state ? ETHERNET_STATE_ENABLED : ETHERNET_STATE_DISABLED;
}
private void unicastEthernetStateChange(@NonNull IEthernetServiceListener listener,
- int state) {
+ boolean enabled) {
ensureRunningOnEthernetServiceThread();
try {
- listener.onEthernetStateChanged(state);
+ listener.onEthernetStateChanged(isEthernetEnabledAsInt(enabled));
} catch (RemoteException e) {
// Do nothing here.
}
}
- private void broadcastEthernetStateChange(int state) {
+ private void broadcastEthernetStateChange(boolean enabled) {
ensureRunningOnEthernetServiceThread();
final int n = mListeners.beginBroadcast();
for (int i = 0; i < n; i++) {
try {
- mListeners.getBroadcastItem(i).onEthernetStateChanged(state);
+ mListeners.getBroadcastItem(i)
+ .onEthernetStateChanged(isEthernetEnabledAsInt(enabled));
} catch (RemoteException e) {
// Do nothing here.
}
@@ -1008,7 +1020,7 @@
postAndWaitForRunnable(() -> {
pw.println(getClass().getSimpleName());
pw.println("Ethernet State: "
- + (mEthernetState == ETHERNET_STATE_ENABLED ? "enabled" : "disabled"));
+ + (mIsEthernetEnabled ? "enabled" : "disabled"));
pw.println("Ethernet interface name filter: " + mIfaceMatch);
pw.println("Interface used for tethering: " + mTetheringInterface);
pw.println("Tethering interface mode: " + mTetheringInterfaceMode);
diff --git a/service/Android.bp b/service/Android.bp
index 567c079..fd3d4a3 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -90,7 +90,6 @@
static_libs: [
"libnet_utils_device_common_bpfjni",
"libnet_utils_device_common_bpfutils",
- "libnet_utils_device_common_timerfdjni",
],
shared_libs: [
"liblog",
@@ -126,6 +125,7 @@
"libmodules-utils-build",
"libnetjniutils",
"libnet_utils_device_common_bpfjni",
+ "libnet_utils_device_common_timerfdjni",
"netd_aidl_interface-lateststable-ndk",
],
shared_libs: [
diff --git a/service/ServiceConnectivityResources/res/values-sw/strings.xml b/service/ServiceConnectivityResources/res/values-sw/strings.xml
index 29ec013..9ff9ada 100644
--- a/service/ServiceConnectivityResources/res/values-sw/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-sw/strings.xml
@@ -25,7 +25,7 @@
<string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Hakuna intaneti"</string>
<string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Huenda data ya <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> imeisha. Gusa ili upate chaguo."</string>
<string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Huenda data yako imeisha. Gusa ili upate chaguo."</string>
- <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> haina uwezo wa kufikia intaneti"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> haina intaneti"</string>
<string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Gusa ili upate chaguo"</string>
<string name="mobile_no_internet" msgid="4087718456753201450">"Mtandao wa simu hauna uwezo wa kufikia intaneti"</string>
<string name="other_networks_no_internet" msgid="5693932964749676542">"Mtandao hauna uwezo wa kufikia intaneti"</string>
diff --git a/service/jni/onload.cpp b/service/jni/onload.cpp
index bb70d4f..8e01260 100644
--- a/service/jni/onload.cpp
+++ b/service/jni/onload.cpp
@@ -26,6 +26,8 @@
int register_android_server_net_NetworkStatsFactory(JNIEnv* env);
int register_android_server_net_NetworkStatsService(JNIEnv* env);
int register_com_android_server_ServiceManagerWrapper(JNIEnv* env);
+int register_com_android_net_module_util_TimerFdUtils(JNIEnv *env,
+ char const *class_name);
extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
JNIEnv *env;
@@ -56,6 +58,12 @@
}
}
+ if (register_com_android_net_module_util_TimerFdUtils(
+ env, "android/net/connectivity/com/android/net/module/util/"
+ "TimerFdUtils") < 0) {
+ return JNI_ERR;
+ }
+
return JNI_VERSION_1_6;
}
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index f484027..b4a3b8a 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -32,6 +32,8 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
+// This library shouldn't be used anymore (no class should be added), and per-user libraries like
+// net-utils-service-connectivity or net-utils-framework-wifi should be used instead.
java_library {
name: "net-utils-device-common",
srcs: [
@@ -725,3 +727,34 @@
],
apex_available: ["com.android.wifi"],
}
+
+genrule {
+ name: "statslog-framework-connectivity-java-gen",
+ tools: ["stats-log-api-gen"],
+ cmd: "$(location stats-log-api-gen) --java $(out) --module connectivity --javaPackage com.android.net.module.util --javaClass FrameworkConnectivityStatsLog",
+ out: ["com/android/net/module/util/FrameworkConnectivityStatsLog.java"],
+}
+
+java_library {
+ name: "net-utils-service-vcn",
+ sdk_version: "module_current",
+ min_sdk_version: "30",
+ srcs: [
+ "device/com/android/net/module/util/HandlerUtils.java",
+ ],
+ libs: [
+ "framework-annotations-lib",
+ ],
+ visibility: [
+ // TODO: b/374174952 Remove it when VCN modularization is released
+ "//frameworks/base/packages/Vcn/service-b",
+
+ "//packages/modules/Connectivity/service-b",
+ ],
+ apex_available: [
+ // TODO: b/374174952 Remove it when VCN modularization is released
+ "//apex_available:platform",
+
+ "com.android.tethering",
+ ],
+}
diff --git a/staticlibs/device/com/android/net/module/util/TimerFdUtils.java b/staticlibs/device/com/android/net/module/util/TimerFdUtils.java
new file mode 100644
index 0000000..310dbc9
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/TimerFdUtils.java
@@ -0,0 +1,79 @@
+/*
+ * 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 com.android.net.module.util;
+
+import android.os.Process;
+import android.util.Log;
+
+import java.io.IOException;
+
+/**
+ * Contains mostly timerfd functionality.
+ */
+public class TimerFdUtils {
+ static {
+ if (Process.myUid() == Process.SYSTEM_UID) {
+ // This library is part of service-connectivity.jar when in the system server,
+ // so libservice-connectivity.so is the library to load.
+ System.loadLibrary("service-connectivity");
+ } else {
+ System.loadLibrary(JniUtil.getJniLibraryName(TimerFdUtils.class.getPackage()));
+ }
+ }
+
+ private static final String TAG = TimerFdUtils.class.getSimpleName();
+
+ /**
+ * Create a timerfd.
+ *
+ * @throws IOException if the timerfd creation is failed.
+ */
+ private static native int createTimerFd() throws IOException;
+
+ /**
+ * Set given time to the timerfd.
+ *
+ * @param timeMs target time
+ * @throws IOException if setting expiration time is failed.
+ */
+ private static native void setTime(int fd, long timeMs) throws IOException;
+
+ /**
+ * Create a timerfd
+ */
+ static int createTimerFileDescriptor() {
+ try {
+ return createTimerFd();
+ } catch (IOException e) {
+ Log.e(TAG, "createTimerFd failed", e);
+ return -1;
+ }
+ }
+
+ /**
+ * Set expiration time to timerfd
+ */
+ static boolean setExpirationTime(int id, long expirationTimeMs) {
+ try {
+ setTime(id, expirationTimeMs);
+ } catch (IOException e) {
+ Log.e(TAG, "setExpirationTime failed", e);
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/DnsPacket.java b/staticlibs/framework/com/android/net/module/util/DnsPacket.java
index 63106a1..b0c5e2e 100644
--- a/staticlibs/framework/com/android/net/module/util/DnsPacket.java
+++ b/staticlibs/framework/com/android/net/module/util/DnsPacket.java
@@ -50,7 +50,7 @@
*
* @hide
*/
-public abstract class DnsPacket {
+public class DnsPacket {
/**
* Type of the canonical name for an alias. Refer to RFC 1035 section 3.2.2.
*/
@@ -515,7 +515,14 @@
protected final DnsHeader mHeader;
protected final List<DnsRecord>[] mRecords;
- protected DnsPacket(@NonNull byte[] data) throws ParseException {
+ /**
+ * Returns the list of DNS records for a given section.
+ */
+ public List<DnsRecord> getRecords(@RecordType int section) {
+ return mRecords[section];
+ }
+
+ public DnsPacket(@NonNull byte[] data) throws ParseException {
if (null == data) {
throw new ParseException("Parse header failed, null input data");
}
@@ -548,7 +555,7 @@
*
* Note that authority records section and additional records section is not supported.
*/
- protected DnsPacket(@NonNull DnsHeader header, @NonNull List<DnsRecord> qd,
+ public DnsPacket(@NonNull DnsHeader header, @NonNull List<DnsRecord> qd,
@NonNull List<DnsRecord> an) {
mHeader = Objects.requireNonNull(header);
mRecords = new List[NUM_SECTIONS];
diff --git a/staticlibs/native/bpfmapjni/Android.bp b/staticlibs/native/bpfmapjni/Android.bp
index 969ebd4..9a58a93 100644
--- a/staticlibs/native/bpfmapjni/Android.bp
+++ b/staticlibs/native/bpfmapjni/Android.bp
@@ -26,6 +26,7 @@
header_libs: [
"bpf_headers",
"jni_headers",
+ "libbase_headers",
],
shared_libs: [
"liblog",
diff --git a/staticlibs/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp b/staticlibs/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp
index 1923ceb..d862f6b 100644
--- a/staticlibs/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp
+++ b/staticlibs/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp
@@ -24,37 +24,38 @@
#include "nativehelper/scoped_primitive_array.h"
#include "nativehelper/scoped_utf_chars.h"
-#define BPF_FD_JUST_USE_INT
+#include <android-base/unique_fd.h>
#include "BpfSyscallWrappers.h"
-
#include "bpf/KernelUtils.h"
namespace android {
+using ::android::base::unique_fd;
+
static jint com_android_net_module_util_BpfMap_nativeBpfFdGet(JNIEnv *env, jclass clazz,
jstring path, jint mode, jint keySize, jint valueSize) {
ScopedUtfChars pathname(env, path);
- jint fd = -1;
+ unique_fd fd;
switch (mode) {
case 0:
- fd = bpf::mapRetrieveRW(pathname.c_str());
+ fd.reset(bpf::mapRetrieveRW(pathname.c_str()));
break;
case BPF_F_RDONLY:
- fd = bpf::mapRetrieveRO(pathname.c_str());
+ fd.reset(bpf::mapRetrieveRO(pathname.c_str()));
break;
case BPF_F_WRONLY:
- fd = bpf::mapRetrieveWO(pathname.c_str());
+ fd.reset(bpf::mapRetrieveWO(pathname.c_str()));
break;
case BPF_F_RDONLY|BPF_F_WRONLY:
- fd = bpf::mapRetrieveExclusiveRW(pathname.c_str());
+ fd.reset(bpf::mapRetrieveExclusiveRW(pathname.c_str()));
break;
default:
errno = EINVAL;
break;
}
- if (fd < 0) {
+ if (!fd.ok()) {
jniThrowErrnoException(env, "nativeBpfFdGet", errno);
return -1;
}
@@ -62,18 +63,16 @@
if (bpf::isAtLeastKernelVersion(4, 14, 0)) {
// These likely fail with -1 and set errno to EINVAL on <4.14
if (bpf::bpfGetFdKeySize(fd) != keySize) {
- close(fd);
jniThrowErrnoException(env, "nativeBpfFdGet KeySize", EBADFD);
return -1;
}
if (bpf::bpfGetFdValueSize(fd) != valueSize) {
- close(fd);
jniThrowErrnoException(env, "nativeBpfFdGet ValueSize", EBADFD);
return -1;
}
}
- return fd;
+ return fd.release();
}
static void com_android_net_module_util_BpfMap_nativeWriteToMapEntry(JNIEnv *env, jobject self,
diff --git a/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/CarrierConfigSetupTest.kt b/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/CarrierConfigSetupTest.kt
index 46e511e..78b34a8 100644
--- a/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/CarrierConfigSetupTest.kt
+++ b/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/CarrierConfigSetupTest.kt
@@ -123,7 +123,13 @@
"""telephony/com\.android\.internal\.telephony\.flags\.force_iwlan_mms:""" +
""".*ENABLED \(system\)""")
ParcelFileDescriptor.AutoCloseInputStream(
- uiAutomation.executeShellCommand("printflags")).bufferedReader().use { reader ->
+ // If the command fails (for example if printflags is missing) this will return false
+ // and the IWLAN disable will be skipped, which should be fine at it only helps with
+ // flakiness.
+ // This uses "sh -c" to cover that case as if "printflags" is used directly and the
+ // binary is missing, the remote end will crash and the InputStream EOF is never
+ // reached, so the read would hang.
+ uiAutomation.executeShellCommand("sh -c printflags")).bufferedReader().use { reader ->
return reader.lines().anyMatch {
it.contains(flagEnabledRegex)
}
diff --git a/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt b/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt
index e634f0e..8e27c62 100644
--- a/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt
+++ b/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt
@@ -16,27 +16,167 @@
package com.android.testutils.connectivitypreparer
+import android.Manifest.permission.NETWORK_SETTINGS
import android.content.pm.PackageManager.FEATURE_TELEPHONY
import android.content.pm.PackageManager.FEATURE_WIFI
+import android.net.LinkAddress
+import android.net.Network
+import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.net.NetworkRequest
+import android.net.wifi.WifiInfo
import android.telephony.TelephonyManager
+import android.util.Log
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
+import com.android.net.module.util.HexDump
+import com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_ANY
+import com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ANY
+import com.android.testutils.AutoReleaseNetworkCallbackRule
import com.android.testutils.ConnectUtil
+import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged
+import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
+import com.android.testutils.runAsShell
+import com.android.testutils.tryTest
+import java.io.IOException
+import java.net.DatagramPacket
+import java.net.DatagramSocket
+import java.util.Random
+import kotlin.test.assertFalse
+import kotlin.test.assertNotNull
import kotlin.test.assertTrue
import kotlin.test.fail
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+private const val QUIC_SOCKET_TIMEOUT_MS = 5_000
+private const val QUIC_RETRY_COUNT = 5
+
@RunWith(AndroidJUnit4::class)
class ConnectivityCheckTest {
+ @get:Rule
+ val networkCallbackRule = AutoReleaseNetworkCallbackRule()
+
+ private val logTag = ConnectivityCheckTest::class.simpleName
private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
private val pm by lazy { context.packageManager }
private val connectUtil by lazy { ConnectUtil(context) }
+ // Skip IPv6 checks on virtual devices which do not support it. Tests that require IPv6 will
+ // still fail even if the preparer does not.
+ private fun ipv6Unsupported(wifiSsid: String?) = ConnectUtil.VIRTUAL_SSIDS.contains(
+ WifiInfo.sanitizeSsid(wifiSsid))
+
@Test
fun testCheckWifiSetup() {
if (!pm.hasSystemFeature(FEATURE_WIFI)) return
connectUtil.ensureWifiValidated()
+
+ val (wifiNetwork, wifiSsid) = runAsShell(NETWORK_SETTINGS) {
+ val cb = networkCallbackRule.requestNetwork(
+ NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .build()
+ )
+ val capChanged = cb.eventuallyExpect<CapabilitiesChanged>(from = 0)
+ val network = capChanged.network
+ val ssid = capChanged.caps.ssid
+ assertFalse(ssid.isNullOrEmpty(), "No SSID for wifi network $network")
+ // Expect a global IPv6 address, and native or stacked IPv4
+ val lpChange = cb.history.poll(
+ pos = 0,
+ timeoutMs = 30_000L
+ ) {
+ it is LinkPropertiesChanged &&
+ it.network == network &&
+ it.lp.allLinkAddresses.any(LinkAddress::isIpv4) &&
+ (ipv6Unsupported(ssid) || it.lp.hasGlobalIpv6Address())
+ }
+ assertNotNull(lpChange, "Wifi network $network needs an IPv4 address" +
+ if (ipv6Unsupported(ssid)) "" else " and a global IPv6 address")
+
+ Pair(network, ssid)
+ }
+
+ // Checking QUIC is more important on Wi-Fi than cellular, as it finds firewall
+ // configuration problems on Wi-Fi, but cellular is not actionable by the test lab.
+ checkQuic(wifiNetwork, wifiSsid, ipv6 = false)
+ if (!ipv6Unsupported(wifiSsid)) {
+ checkQuic(wifiNetwork, wifiSsid, ipv6 = true)
+ }
+ }
+
+ /**
+ * Check that QUIC is working on the specified network.
+ *
+ * Some tests require QUIC (UDP), and some lab networks have been observed to not let it
+ * through due to firewalling. Ensure that devices are setup on a network that has the proper
+ * allowlists before trying to run the tests.
+ */
+ private fun checkQuic(network: Network, ssid: String, ipv6: Boolean) {
+ // Same endpoint as used in MultinetworkApiTest in CTS
+ val hostname = "connectivitycheck.android.com"
+ val targetAddrs = network.getAllByName(hostname)
+ val bindAddr = if (ipv6) IPV6_ADDR_ANY else IPV4_ADDR_ANY
+ if (targetAddrs.isEmpty()) {
+ Log.d(logTag, "No addresses found for $hostname")
+ return
+ }
+
+ val socket = DatagramSocket(0, bindAddr)
+ tryTest {
+ socket.soTimeout = QUIC_SOCKET_TIMEOUT_MS
+ network.bindSocket(socket)
+
+ // For reference see Version-Independent Properties of QUIC:
+ // https://datatracker.ietf.org/doc/html/rfc8999
+ // This packet just contains a long header with an unsupported version number, to force
+ // a version-negotiation packet in response.
+ val connectionId = ByteArray(8).apply { Random().nextBytes(this) }
+ val quicData = byteArrayOf(
+ // long header
+ 0xc0.toByte(),
+ // version number (should be an unknown version for the server)
+ 0xaa.toByte(), 0xda.toByte(), 0xca.toByte(), 0xca.toByte(),
+ // destination connection ID length
+ 0x08,
+ ) + connectionId + byteArrayOf(
+ // source connection ID length
+ 0x00,
+ ) + ByteArray(1185) // Ensure the packet is 1200 bytes long
+ val targetAddr = targetAddrs.firstOrNull { it.javaClass == bindAddr.javaClass }
+ ?: fail("No ${bindAddr.javaClass} found for $hostname " +
+ "(got ${targetAddrs.joinToString()})")
+ repeat(QUIC_RETRY_COUNT) { i ->
+ socket.send(DatagramPacket(quicData, quicData.size, targetAddr, 443))
+
+ val receivedPacket = DatagramPacket(ByteArray(1500), 1500)
+ try {
+ socket.receive(receivedPacket)
+ } catch (e: IOException) {
+ Log.d(logTag, "No response from $hostname ($targetAddr) on QUIC try $i", e)
+ return@repeat
+ }
+
+ val receivedConnectionId = receivedPacket.data.copyOfRange(7, 7 + 8)
+ if (connectionId.contentEquals(receivedConnectionId)) {
+ return@tryTest
+ } else {
+ val headerBytes = receivedPacket.data.copyOfRange(
+ 0, receivedPacket.length.coerceAtMost(15))
+ Log.d(logTag, "Received invalid connection ID on QUIC try $i: " +
+ HexDump.toHexString(headerBytes))
+ }
+ }
+ fail("QUIC is not working on SSID $ssid connecting to $targetAddr " +
+ "with local source port ${socket.localPort}: check the firewall for UDP port " +
+ "443 access."
+ )
+ } cleanup {
+ socket.close()
+ }
}
@Test
@@ -53,12 +193,16 @@
if (tm.simState == TelephonyManager.SIM_STATE_ABSENT) {
fail("The device has no SIM card inserted. $commonError")
} else if (tm.simState != TelephonyManager.SIM_STATE_READY) {
- fail("The device is not setup with a usable SIM card. Sim state was ${tm.simState}. " +
- commonError)
+ fail(
+ "The device is not setup with a usable SIM card. Sim state was ${tm.simState}. " +
+ commonError
+ )
}
- assertTrue(tm.isDataConnectivityPossible,
+ assertTrue(
+ tm.isDataConnectivityPossible,
"The device has a SIM card, but it does not supports data connectivity. " +
- "Check the data plan, and verify that mobile data is working. " + commonError)
+ "Check the data plan, and verify that mobile data is working. " + commonError
+ )
connectUtil.ensureCellularValidated()
}
}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/ConnectUtil.kt b/staticlibs/testutils/devicetests/com/android/testutils/ConnectUtil.kt
index 3857810..d60ab59 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/ConnectUtil.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/ConnectUtil.kt
@@ -53,6 +53,10 @@
private const val WIFI_ERROR_BUSY = 2
class ConnectUtil(private val context: Context) {
+ companion object {
+ @JvmStatic
+ val VIRTUAL_SSIDS = listOf("VirtWifi", "AndroidWifi")
+ }
private val TAG = ConnectUtil::class.java.simpleName
private val cm = context.getSystemService(ConnectivityManager::class.java)
@@ -207,9 +211,8 @@
*/
private fun maybeConfigureVirtualNetwork(scanResults: List<ScanResult>): WifiConfiguration? {
// Virtual wifi networks used on the emulator and cloud testing infrastructure
- val virtualSsids = listOf("VirtWifi", "AndroidWifi")
Log.d(TAG, "Wifi scan results: $scanResults")
- val virtualScanResult = scanResults.firstOrNull { virtualSsids.contains(it.SSID) }
+ val virtualScanResult = scanResults.firstOrNull { VIRTUAL_SSIDS.contains(it.SSID) }
?: return null
// Only add the virtual configuration if the virtual AP is detected in scans
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/ConnectivityDiagnosticsCollector.kt b/staticlibs/testutils/devicetests/com/android/testutils/ConnectivityDiagnosticsCollector.kt
index ea86281..9e63910 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/ConnectivityDiagnosticsCollector.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/ConnectivityDiagnosticsCollector.kt
@@ -76,11 +76,13 @@
private const val MAX_DUMPS = 20
private val TAG = ConnectivityDiagnosticsCollector::class.simpleName
+ @JvmStatic
var instance: ConnectivityDiagnosticsCollector? = null
}
private var failureHeader: String? = null
private val buffer = ByteArrayOutputStream()
+ private val failureHeaderExtras = mutableMapOf<String, Any>()
private val collectorDir: File by lazy {
createAndEmptyDirectory(COLLECTOR_DIR)
}
@@ -218,6 +220,8 @@
val canUseShell = !isAtLeastS() ||
instr.uiAutomation.getAdoptedShellPermissions().isNullOrEmpty()
val headerObj = JSONObject()
+ failureHeaderExtras.forEach { (k, v) -> headerObj.put(k, v) }
+ failureHeaderExtras.clear()
if (canUseShell) {
runAsShell(READ_PRIVILEGED_PHONE_STATE, NETWORK_SETTINGS) {
headerObj.apply {
@@ -332,6 +336,15 @@
}
}
+ /**
+ * Add a key->value attribute to the failure data, to be written to the diagnostics file.
+ *
+ * <p>This is to be called by tests that know they will fail.
+ */
+ fun addFailureAttribute(key: String, value: Any) {
+ failureHeaderExtras[key] = value
+ }
+
private fun maybeWriteExceptionContext(writer: PrintWriter, exceptionContext: Throwable?) {
if (exceptionContext == null) return
writer.println("At: ")
diff --git a/tests/common/Android.bp b/tests/common/Android.bp
index bb1009b..60a02fb 100644
--- a/tests/common/Android.bp
+++ b/tests/common/Android.bp
@@ -147,6 +147,7 @@
// meaning @hide APIs in framework-connectivity are resolved before @SystemApi
// stubs in framework
"framework-connectivity.impl",
+ "framework-connectivity-b.impl",
"framework-connectivity-t.impl",
"framework-tethering.impl",
"framework",
diff --git a/tests/cts/net/jni/NativeMultinetworkJni.cpp b/tests/cts/net/jni/NativeMultinetworkJni.cpp
index f2214a3..1d848ec 100644
--- a/tests/cts/net/jni/NativeMultinetworkJni.cpp
+++ b/tests/cts/net/jni/NativeMultinetworkJni.cpp
@@ -415,9 +415,17 @@
strlcpy(dst, buf, size);
}
+static jobject create_query_test_result(JNIEnv* env, uint16_t src_port, int attempts, int errnum) {
+ jclass clazz = env->FindClass(
+ "android/net/cts/MultinetworkApiTest$QueryTestResult");
+ jmethodID ctor = env->GetMethodID(clazz, "<init>", "(III)V");
+
+ return env->NewObject(clazz, ctor, src_port, attempts, errnum);
+}
+
extern "C"
-JNIEXPORT jint Java_android_net_cts_MultinetworkApiTest_runDatagramCheck(
- JNIEnv*, jclass, jlong nethandle) {
+JNIEXPORT jobject Java_android_net_cts_MultinetworkApiTest_runDatagramCheck(
+ JNIEnv* env, jclass, jlong nethandle, jint src_port) {
const struct addrinfo kHints = {
.ai_flags = AI_ADDRCONFIG,
.ai_family = AF_UNSPEC,
@@ -433,7 +441,7 @@
LOGD("android_getaddrinfofornetwork(%llu, %s) returned rval=%d errno=%d",
handle, kHostname, rval, errno);
freeaddrinfo(res);
- return -errno;
+ return create_query_test_result(env, 0, 0, errno);
}
// Rely upon getaddrinfo sorting the best destination to the front.
@@ -442,7 +450,7 @@
LOGD("socket(%d, %d, %d) failed, errno=%d",
res->ai_family, res->ai_socktype, res->ai_protocol, errno);
freeaddrinfo(res);
- return -errno;
+ return create_query_test_result(env, 0, 0, errno);
}
rval = android_setsocknetwork(handle, fd);
@@ -451,7 +459,31 @@
if (rval != 0) {
close(fd);
freeaddrinfo(res);
- return -errno;
+ return create_query_test_result(env, 0, 0, errno);
+ }
+
+ sockaddr_storage src_addr;
+ socklen_t src_addrlen = sizeof(src_addr);
+ if (src_port) {
+ if (res->ai_family == AF_INET6) {
+ *reinterpret_cast<sockaddr_in6*>(&src_addr) = (sockaddr_in6) {
+ .sin6_family = AF_INET6,
+ .sin6_port = htons(src_port),
+ .sin6_addr = in6addr_any,
+ };
+ } else {
+ *reinterpret_cast<sockaddr_in*>(&src_addr) = (sockaddr_in) {
+ .sin_family = AF_INET,
+ .sin_port = htons(src_port),
+ .sin_addr = { .s_addr = INADDR_ANY },
+ };
+ }
+ if (bind(fd, (sockaddr *)&src_addr, src_addrlen) != 0) {
+ LOGD("Error binding to port %d", src_port);
+ close(fd);
+ freeaddrinfo(res);
+ return create_query_test_result(env, 0, 0, errno);
+ }
}
char addrstr[kSockaddrStrLen+1];
@@ -462,19 +494,28 @@
if (rval != 0) {
close(fd);
freeaddrinfo(res);
- return -errno;
+ return create_query_test_result(env, 0, 0, errno);
}
freeaddrinfo(res);
- struct sockaddr_storage src_addr;
- socklen_t src_addrlen = sizeof(src_addr);
if (getsockname(fd, (struct sockaddr *)&src_addr, &src_addrlen) != 0) {
close(fd);
- return -errno;
+ return create_query_test_result(env, 0, 0, errno);
}
sockaddr_ntop((const struct sockaddr *)&src_addr, sizeof(src_addr), addrstr, sizeof(addrstr));
LOGD("... from %s", addrstr);
+ uint16_t socket_src_port;
+ if (res->ai_family == AF_INET6) {
+ socket_src_port = ntohs(reinterpret_cast<sockaddr_in6*>(&src_addr)->sin6_port);
+ } else if (src_addr.ss_family == AF_INET) {
+ socket_src_port = ntohs(reinterpret_cast<sockaddr_in*>(&src_addr)->sin_port);
+ } else {
+ LOGD("Invalid source address family %d", src_addr.ss_family);
+ close(fd);
+ return create_query_test_result(env, 0, 0, EAFNOSUPPORT);
+ }
+
// Don't let reads or writes block indefinitely.
const struct timeval timeo = { 2, 0 }; // 2 seconds
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo));
@@ -503,7 +544,7 @@
errnum = errno;
LOGD("send(QUIC packet) returned sent=%zd, errno=%d", sent, errnum);
close(fd);
- return -errnum;
+ return create_query_test_result(env, socket_src_port, i + 1, errnum);
}
rcvd = recv(fd, response, sizeof(response), 0);
@@ -521,18 +562,19 @@
LOGD("Does this network block UDP port %s?", kPort);
}
close(fd);
- return -EPROTO;
+ return create_query_test_result(env, socket_src_port, i + 1,
+ rcvd <= 0 ? errnum : EPROTO);
}
int conn_id_cmp = memcmp(quic_packet + 6, response + 7, 8);
if (conn_id_cmp != 0) {
LOGD("sent and received connection IDs do not match");
close(fd);
- return -EPROTO;
+ return create_query_test_result(env, socket_src_port, i + 1, EPROTO);
}
// TODO: Replace this quick 'n' dirty test with proper QUIC-capable code.
close(fd);
- return 0;
+ return create_query_test_result(env, socket_src_port, i + 1, 0);
}
diff --git a/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt b/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
index 9ac2c67..8e77b5d 100644
--- a/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
+++ b/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
@@ -19,6 +19,7 @@
package android.net.cts
+import android.Manifest.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG
import android.Manifest.permission.WRITE_DEVICE_CONFIG
import android.content.pm.PackageManager.FEATURE_WIFI
import android.net.ConnectivityManager
@@ -165,7 +166,7 @@
// created.
// APF adb cmds are only implemented in ApfFilter.java. Enable experiment to prevent
// LegacyApfFilter.java from being used.
- runAsShell(WRITE_DEVICE_CONFIG) {
+ runAsShell(WRITE_DEVICE_CONFIG, WRITE_ALLOWLISTED_DEVICE_CONFIG) {
DeviceConfig.setProperty(
NAMESPACE_CONNECTIVITY,
APF_NEW_RA_FILTER_VERSION,
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 3a8252a..88309ed 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -145,6 +145,7 @@
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.ConnectivitySettingsManager;
+import android.net.DnsResolver;
import android.net.InetAddresses;
import android.net.IpSecManager;
import android.net.IpSecManager.UdpEncapsulationSocket;
@@ -201,6 +202,7 @@
import com.android.internal.util.ArrayUtils;
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.CollectionUtils;
+import com.android.net.module.util.DnsPacket;
import com.android.networkstack.apishim.ConnectivityManagerShimImpl;
import com.android.networkstack.apishim.ConstantsShim;
import com.android.networkstack.apishim.NetworkInformationShimImpl;
@@ -249,7 +251,6 @@
import java.net.Socket;
import java.net.SocketException;
import java.net.URL;
-import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
@@ -306,6 +307,7 @@
private static final int LISTEN_ACTIVITY_TIMEOUT_MS = 30_000;
private static final int NO_CALLBACK_TIMEOUT_MS = 100;
private static final int NETWORK_REQUEST_TIMEOUT_MS = 3000;
+ private static final int DNS_REQUEST_TIMEOUT_MS = 1000;
private static final int SOCKET_TIMEOUT_MS = 100;
private static final int NUM_TRIES_MULTIPATH_PREF_CHECK = 20;
private static final long INTERVAL_MULTIPATH_PREF_CHECK_MS = 500;
@@ -861,6 +863,55 @@
});
}
+ @NonNull
+ private static String getDeviceIpv6AddressThroughDnsQuery(Network network) throws Exception {
+ final InetAddress dnsAddr = getAddrByName("ns1.google.com", AF_INET6);
+ assertNotNull("IPv6 address for ns1.google.com should not be null", dnsAddr);
+
+ try (DatagramSocket udpSocket = new DatagramSocket()) {
+ network.bindSocket(udpSocket);
+
+ final DnsPacket queryDnsPkt = new DnsPacket(
+ new DnsPacket.DnsHeader(new Random().nextInt(), DnsResolver.FLAG_EMPTY,
+ 1 /* qdcount */,
+ 0 /* ancount */),
+ List.of(DnsPacket.DnsRecord.makeQuestion("o-o.myaddr.l.google.com",
+ DnsResolver.TYPE_TXT, DnsResolver.CLASS_IN)),
+ List.of() /* an */
+ );
+ final byte[] queryDnsRawBytes = queryDnsPkt.getBytes();
+ final byte[] receiveBuffer = new byte[1500];
+ final int maxRetry = 3;
+ for (int attempt = 1; attempt <= maxRetry; ++attempt) {
+ try {
+ final DatagramPacket queryUdpPkt = new DatagramPacket(queryDnsRawBytes,
+ queryDnsRawBytes.length, dnsAddr, 53 /* port */);
+ udpSocket.send(queryUdpPkt);
+
+ final DatagramPacket replyUdpPkt = new DatagramPacket(receiveBuffer,
+ receiveBuffer.length);
+ udpSocket.setSoTimeout(DNS_REQUEST_TIMEOUT_MS);
+ udpSocket.receive(replyUdpPkt);
+ break;
+ } catch (IOException e) {
+ if (attempt == maxRetry) {
+ throw e; // If the last attempt fails, rethrow the exception.
+ } else {
+ Log.e(TAG, "DNS request failed (attempt " + attempt + ")" + e);
+ }
+ }
+ }
+
+ final DnsPacket replyDnsPkt = new DnsPacket(receiveBuffer);
+ final DnsPacket.DnsRecord answerRecord = replyDnsPkt.getRecords(
+ DnsPacket.ANSECTION).get(0);
+ final byte[] txtReplyRecord = answerRecord.getRR();
+ final byte dataLength = txtReplyRecord[0];
+ assertEquals(dataLength, txtReplyRecord.length - 1);
+ return new String(Arrays.copyOfRange(txtReplyRecord, 1, txtReplyRecord.length));
+ }
+ }
+
/**
* Tests that connections can be opened on WiFi and cellphone networks,
* and that they are made from different IP addresses.
@@ -886,8 +937,31 @@
// Verify that the IP addresses that the requests appeared to come from are actually on the
// respective networks.
- assertOnNetwork(wifiAddressString, wifiNetwork);
- assertOnNetwork(cellAddressString, cellNetwork);
+ final InetAddress wifiAddress = InetAddresses.parseNumericAddress(wifiAddressString);
+ final LinkProperties wifiLinkProperties = mCm.getLinkProperties(wifiNetwork);
+ // To make sure that the request went out on the right network, check that
+ // the IP address seen by the server is assigned to the expected network.
+ // We can only do this for IPv6 addresses, because in IPv4 we will likely
+ // have a private IPv4 address, and that won't match what the server sees.
+ if (wifiAddress instanceof Inet6Address) {
+ assertContains(wifiLinkProperties.getAddresses(), wifiAddress);
+ }
+
+ final LinkProperties cellLinkProperties = mCm.getLinkProperties(cellNetwork);
+ final InetAddress cellAddress = InetAddresses.parseNumericAddress(cellAddressString);
+ final List<InetAddress> cellNetworkAddresses = cellLinkProperties.getAddresses();
+ // In userdebug build, on cellular network, if the onNetwork check failed, we also try to
+ // re-verify it by obtaining the IP address through DNS query.
+ boolean isUserDebug = Build.isDebuggable();
+ if (cellAddress instanceof Inet6Address) {
+ if (isUserDebug && !cellNetworkAddresses.contains(cellAddress)) {
+ final InetAddress ipv6AddressThroughDns = InetAddresses.parseNumericAddress(
+ getDeviceIpv6AddressThroughDnsQuery(cellNetwork));
+ assertContains(cellNetworkAddresses, ipv6AddressThroughDns);
+ } else {
+ assertContains(cellNetworkAddresses, cellAddress);
+ }
+ }
assertFalse("Unexpectedly equal: " + wifiNetwork, wifiNetwork.equals(cellNetwork));
}
@@ -919,17 +993,6 @@
}
}
- private void assertOnNetwork(String adressString, Network network) throws UnknownHostException {
- InetAddress address = InetAddress.getByName(adressString);
- LinkProperties linkProperties = mCm.getLinkProperties(network);
- // To make sure that the request went out on the right network, check that
- // the IP address seen by the server is assigned to the expected network.
- // We can only do this for IPv6 addresses, because in IPv4 we will likely
- // have a private IPv4 address, and that won't match what the server sees.
- if (address instanceof Inet6Address) {
- assertContains(linkProperties.getAddresses(), address);
- }
- }
private static<T> void assertContains(Collection<T> collection, T element) {
assertTrue(element + " not found in " + collection, collection.contains(element));
@@ -1713,7 +1776,8 @@
}
}
- private InetAddress getAddrByName(final String hostname, final int family) throws Exception {
+ private static InetAddress getAddrByName(final String hostname, final int family)
+ throws Exception {
final InetAddress[] allAddrs = InetAddress.getAllByName(hostname);
for (InetAddress addr : allAddrs) {
if (family == AF_INET && addr instanceof Inet4Address) return addr;
diff --git a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
index 1e2a212..9be579b 100644
--- a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
@@ -145,6 +145,8 @@
private var tetheredInterfaceRequest: TetheredInterfaceRequest? = null
+ private var ethernetEnabled = true
+
private class EthernetTestInterface(
context: Context,
private val handler: Handler,
@@ -428,7 +430,7 @@
// when an interface comes up, we should always see a down cb before an up cb.
ifaceListener.eventuallyExpect(iface, STATE_LINK_DOWN, ROLE_CLIENT)
- if (hasCarrier) {
+ if (hasCarrier && ethernetEnabled) {
ifaceListener.expectCallback(iface, STATE_LINK_UP, ROLE_CLIENT)
}
return iface
@@ -514,6 +516,7 @@
private fun setEthernetEnabled(enabled: Boolean) {
runAsShell(NETWORK_SETTINGS) { em.setEthernetEnabled(enabled) }
+ ethernetEnabled = enabled
val listener = EthernetStateListener()
addEthernetStateListener(listener)
listener.eventuallyExpect(if (enabled) ETHERNET_STATE_ENABLED else ETHERNET_STATE_DISABLED)
@@ -600,26 +603,6 @@
}
}
- @Test
- fun testCallbacks_withRunningInterface() {
- assumeFalse(isAdbOverEthernet())
- // Only run this test when no non-restricted / physical interfaces are present.
- assumeNoInterfaceForTetheringAvailable()
-
- val iface = createInterface()
- val listener = EthernetStateListener()
- addInterfaceStateListener(listener)
- listener.eventuallyExpect(iface, STATE_LINK_UP, ROLE_CLIENT)
-
- // Remove running interface. The interface stays running but is no longer tracked.
- setEthernetEnabled(false)
- listener.expectCallback(iface, STATE_ABSENT, ROLE_NONE)
-
- setEthernetEnabled(true)
- listener.expectCallback(iface, STATE_LINK_UP, ROLE_CLIENT)
- listener.assertNoCallback()
- }
-
private fun assumeNoInterfaceForTetheringAvailable() {
// Interfaces that have configured NetworkCapabilities will never be used for tethering,
// see aosp/2123900.
@@ -911,6 +894,30 @@
}
@Test
+ fun testEnableDisableInterface_disableEnableEthernet() {
+ val iface = createInterface()
+ val listener = EthernetStateListener()
+ addInterfaceStateListener(listener)
+ listener.eventuallyExpect(iface, STATE_LINK_UP, ROLE_CLIENT)
+
+ // When ethernet is disabled, interface should be down and enable/disableInterface()
+ // should not bring the interfaces up.
+ setEthernetEnabled(false)
+ listener.eventuallyExpect(iface, STATE_LINK_DOWN, ROLE_CLIENT)
+ enableInterface(iface).expectError()
+ disableInterface(iface).expectError()
+ listener.assertNoCallback()
+
+ // When ethernet is enabled, enable/disableInterface() should succeed.
+ setEthernetEnabled(true)
+ listener.eventuallyExpect(iface, STATE_LINK_UP, ROLE_CLIENT)
+ disableInterface(iface).expectResult(iface.name)
+ listener.eventuallyExpect(iface, STATE_LINK_DOWN, ROLE_CLIENT)
+ enableInterface(iface).expectResult(iface.name)
+ listener.eventuallyExpect(iface, STATE_LINK_UP, ROLE_CLIENT)
+ }
+
+ @Test
fun testUpdateConfiguration_forBothIpConfigAndCapabilities() {
val iface = createInterface()
val cb = requestNetwork(ETH_REQUEST.copyWithEthernetSpecifier(iface.name))
@@ -1018,4 +1025,68 @@
cb.eventuallyExpectCapabilities(TEST_CAPS)
cb.eventuallyExpectLpForStaticConfig(STATIC_IP_CONFIGURATION.staticIpConfiguration)
}
+
+ @Test
+ fun testAddInterface_disableEnableEthernet() {
+ val listener = EthernetStateListener()
+ addInterfaceStateListener(listener)
+
+ // When ethernet is disabled, newly added interfaces should not be brought up.
+ setEthernetEnabled(false)
+ val iface = createInterface(/* hasCarrier */ true)
+ listener.eventuallyExpect(iface, STATE_LINK_DOWN, ROLE_CLIENT)
+
+ // When ethernet is re-enabled after interface is added, it will be brought up.
+ setEthernetEnabled(true)
+ listener.eventuallyExpect(iface, STATE_LINK_UP, ROLE_CLIENT)
+ }
+
+
+ @Test
+ fun testRemoveInterface_disableEnableEthernet() {
+ // Set up 2 interfaces for testing
+ val iface1 = createInterface()
+ val listener = EthernetStateListener()
+ addInterfaceStateListener(listener)
+ listener.eventuallyExpect(iface1, STATE_LINK_UP, ROLE_CLIENT)
+ val iface2 = createInterface()
+ listener.eventuallyExpect(iface2, STATE_LINK_UP, ROLE_CLIENT)
+
+ // Removing interfaces when ethernet is enabled will first send link down, then
+ // STATE_ABSENT/ROLE_NONE.
+ removeInterface(iface1)
+ listener.expectCallback(iface1, STATE_LINK_DOWN, ROLE_CLIENT)
+ listener.expectCallback(iface1, STATE_ABSENT, ROLE_NONE)
+
+ // Removing interfaces after ethernet is disabled will first send link down when ethernet is
+ // disabled, then STATE_ABSENT/ROLE_NONE when interface is removed.
+ setEthernetEnabled(false)
+ listener.expectCallback(iface2, STATE_LINK_DOWN, ROLE_CLIENT)
+ removeInterface(iface2)
+ listener.expectCallback(iface2, STATE_ABSENT, ROLE_NONE)
+ }
+
+ @Test
+ fun testSetTetheringInterfaceMode_disableEnableEthernet() {
+ val listener = EthernetStateListener()
+ addInterfaceStateListener(listener)
+
+ val iface = createInterface()
+ requestTetheredInterface().expectOnAvailable()
+ listener.eventuallyExpect(iface, STATE_LINK_UP, ROLE_SERVER)
+
+ // (b/234743836): Currently the state of server mode interfaces always returns true due to
+ // that interface state for server mode interfaces is not tracked properly.
+ // So we do not get any state change when disabling ethernet.
+ setEthernetEnabled(false)
+ listener.assertNoCallback()
+
+ // When ethernet is disabled, change interface mode will not bring the interface up.
+ releaseTetheredInterface()
+ listener.eventuallyExpect(iface, STATE_LINK_DOWN, ROLE_CLIENT)
+
+ // When ethernet is re-enabled, interface will be brought up.
+ setEthernetEnabled(true)
+ listener.eventuallyExpect(iface, STATE_LINK_UP, ROLE_CLIENT)
+ }
}
diff --git a/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java b/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
index 2c7d5c6..c67443e 100644
--- a/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
+++ b/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
@@ -39,10 +39,13 @@
import android.system.ErrnoException;
import android.system.OsConstants;
import android.util.ArraySet;
+import android.util.Log;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.net.module.util.CollectionUtils;
import com.android.testutils.AutoReleaseNetworkCallbackRule;
+import com.android.testutils.ConnectivityDiagnosticsCollector;
import com.android.testutils.DevSdkIgnoreRunner;
import com.android.testutils.DeviceConfigRule;
@@ -51,6 +54,8 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Set;
@DevSdkIgnoreRunner.RestoreDefaultNetwork
@@ -70,13 +75,34 @@
private static final String TAG = "MultinetworkNativeApiTest";
static final String GOOGLE_PRIVATE_DNS_SERVER = "dns.google";
+ public static class QueryTestResult {
+ public final int sourcePort;
+ public final int attempts;
+ public final int errNo;
+
+ public QueryTestResult(int sourcePort, int attempts, int errNo) {
+ this.sourcePort = sourcePort;
+ this.attempts = attempts;
+ this.errNo = errNo;
+ }
+
+ @Override
+ public String toString() {
+ return "QueryTestResult{"
+ + "sourcePort=" + sourcePort
+ + ", attempts=" + attempts
+ + ", errNo=" + errNo
+ + '}';
+ }
+ }
+
/**
* @return 0 on success
*/
private static native int runGetaddrinfoCheck(long networkHandle);
private static native int runSetprocnetwork(long networkHandle);
private static native int runSetsocknetwork(long networkHandle);
- private static native int runDatagramCheck(long networkHandle);
+ private static native QueryTestResult runDatagramCheck(long networkHandle, int sourcePort);
private static native void runResNapiMalformedCheck(long networkHandle);
private static native void runResNcancelCheck(long networkHandle);
private static native void runResNqueryCheck(long networkHandle);
@@ -165,14 +191,69 @@
}
}
+ private void runNativeDatagramTransmissionDiagnostics(Network network,
+ QueryTestResult failedResult) {
+ final ConnectivityDiagnosticsCollector collector = ConnectivityDiagnosticsCollector
+ .getInstance();
+ if (collector == null) {
+ Log.e(TAG, "Missing ConnectivityDiagnosticsCollector, not adding diagnostics");
+ return;
+ }
+
+ final int numReruns = 10;
+ final ArrayList<QueryTestResult> reruns = new ArrayList<>(numReruns);
+ for (int i = 0; i < numReruns; i++) {
+ final QueryTestResult rerunResult =
+ runDatagramCheck(network.getNetworkHandle(), 0 /* sourcePort */);
+ Log.d(TAG, "Rerun result " + i + ": " + rerunResult);
+ reruns.add(rerunResult);
+ }
+ // Rerun on the original port after trying the other ports, to check that the results are
+ // consistent, as opposed to the network recovering halfway through.
+ int originalPortFailedReruns = 0;
+ for (int i = 0; i < numReruns; i++) {
+ final QueryTestResult originalPortRerun = runDatagramCheck(network.getNetworkHandle(),
+ failedResult.sourcePort);
+ Log.d(TAG, "Rerun result " + i + " with original port: " + originalPortRerun);
+ if (originalPortRerun.errNo != 0) {
+ originalPortFailedReruns++;
+ }
+ }
+
+ final int noRetrySuccessResults = reruns.stream()
+ .filter(result -> result.errNo == 0 && result.attempts == 1)
+ .mapToInt(result -> 1)
+ .sum();
+ final int failedResults = reruns.stream()
+ .filter(result -> result.errNo != 0)
+ .mapToInt(result -> 1)
+ .sum();
+ collector.addFailureAttribute("numReruns", numReruns);
+ collector.addFailureAttribute("noRetrySuccessReruns", noRetrySuccessResults);
+ collector.addFailureAttribute("failedReruns", failedResults);
+ collector.addFailureAttribute("originalPortFailedReruns", originalPortFailedReruns);
+ }
+
@Test
public void testNativeDatagramTransmission() throws Exception {
for (Network network : getTestableNetworks()) {
- int errno = runDatagramCheck(network.getNetworkHandle());
- if (errno != 0) {
- throw new ErrnoException(
- "DatagramCheck on " + mCM.getNetworkInfo(network), -errno);
+ final QueryTestResult result = runDatagramCheck(network.getNetworkHandle(),
+ 0 /* sourcePort */);
+ if (result.errNo == 0) {
+ continue;
}
+ final NetworkCapabilities nc = mCM.getNetworkCapabilities(network);
+ final int[] transports = nc != null ? nc.getTransportTypes() : null;
+ if (CollectionUtils.contains(transports, TRANSPORT_WIFI)) {
+ runNativeDatagramTransmissionDiagnostics(network, result);
+ }
+
+ // Log the whole result (with source port and attempts) to logcat, but use only the
+ // errno and transport in the fail message so similar failures have consistent messages
+ final String error = "DatagramCheck on transport " + Arrays.toString(transports)
+ + " failed: " + result.errNo;
+ Log.e(TAG, error + ", result: " + result);
+ fail(error);
}
}
diff --git a/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java b/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
index fef085d..e3d7240 100644
--- a/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
@@ -63,6 +63,7 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.platform.test.annotations.AppModeFull;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
@@ -113,8 +114,8 @@
private static final String LOG_TAG = "NetworkStatsManagerTest";
- private static final String APPOPS_SET_SHELL_COMMAND = "appops set {0} {1} {2}";
- private static final String APPOPS_GET_SHELL_COMMAND = "appops get {0} {1}";
+ private static final String APPOPS_SET_SHELL_COMMAND = "appops set --user {0} {1} {2} {3}";
+ private static final String APPOPS_GET_SHELL_COMMAND = "appops get --user {0} {1} {2}";
private static final long MINUTE = 1000 * 60;
private static final int TIMEOUT_MILLIS = 15000;
@@ -329,12 +330,14 @@
}
private void setAppOpsMode(String appop, String mode) throws Exception {
- final String command = MessageFormat.format(APPOPS_SET_SHELL_COMMAND, mPkg, appop, mode);
+ final String command = MessageFormat.format(APPOPS_SET_SHELL_COMMAND,
+ UserHandle.myUserId(), mPkg, appop, mode);
SystemUtil.runShellCommand(mInstrumentation, command);
}
private String getAppOpsMode(String appop) throws Exception {
- final String command = MessageFormat.format(APPOPS_GET_SHELL_COMMAND, mPkg, appop);
+ final String command = MessageFormat.format(APPOPS_GET_SHELL_COMMAND,
+ UserHandle.myUserId(), mPkg, appop);
String result = SystemUtil.runShellCommand(mInstrumentation, command);
if (result == null) {
Log.w(LOG_TAG, "App op " + appop + " could not be read.");
diff --git a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
index 0dd2a23..173d13f 100644
--- a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
+++ b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
@@ -53,6 +53,7 @@
import android.os.Build;
import android.os.ConditionVariable;
import android.os.IBinder;
+import android.os.UserHandle;
import android.system.Os;
import android.system.OsConstants;
import android.telephony.SubscriptionManager;
@@ -145,7 +146,8 @@
for (final String pkg : new String[] {"com.android.shell", mContext.getPackageName()}) {
final String cmd =
String.format(
- "appops set %s %s %s",
+ "appops set --user %d %s %s %s",
+ UserHandle.myUserId(), // user id
pkg, // Package name
opName, // Appop
(allow ? "allow" : "deny")); // Action
diff --git a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
index 47d444f..b294d63 100644
--- a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
+++ b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
@@ -77,6 +77,7 @@
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
+import androidx.annotation.NonNull;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
@@ -225,19 +226,23 @@
}
- @Test
- public void testTetheringRequest() {
- SoftApConfiguration softApConfiguration;
+ private SoftApConfiguration createSoftApConfiguration(@NonNull String ssid) {
+ SoftApConfiguration config;
if (SdkLevel.isAtLeastT()) {
- softApConfiguration = new SoftApConfiguration.Builder()
- .setWifiSsid(WifiSsid.fromBytes(
- "This is an SSID!".getBytes(StandardCharsets.UTF_8)))
+ config = new SoftApConfiguration.Builder()
+ .setWifiSsid(WifiSsid.fromBytes(ssid.getBytes(StandardCharsets.UTF_8)))
.build();
} else {
- softApConfiguration = new SoftApConfiguration.Builder()
- .setSsid("This is an SSID!")
+ config = new SoftApConfiguration.Builder()
+ .setSsid(ssid)
.build();
}
+ return config;
+ }
+
+ @Test
+ public void testTetheringRequest() {
+ SoftApConfiguration softApConfiguration = createSoftApConfiguration("SSID");
final TetheringRequest tr = new TetheringRequest.Builder(TETHERING_WIFI)
.setSoftApConfiguration(softApConfiguration)
.build();
@@ -302,17 +307,7 @@
@Test
public void testTetheringRequestSetSoftApConfigurationFailsWhenNotWifi() {
- final SoftApConfiguration softApConfiguration;
- if (SdkLevel.isAtLeastT()) {
- softApConfiguration = new SoftApConfiguration.Builder()
- .setWifiSsid(WifiSsid.fromBytes(
- "This is an SSID!".getBytes(StandardCharsets.UTF_8)))
- .build();
- } else {
- softApConfiguration = new SoftApConfiguration.Builder()
- .setSsid("This is an SSID!")
- .build();
- }
+ final SoftApConfiguration softApConfiguration = createSoftApConfiguration("SSID");
for (int type : List.of(TETHERING_USB, TETHERING_BLUETOOTH, TETHERING_WIFI_P2P,
TETHERING_NCM, TETHERING_ETHERNET)) {
try {
@@ -326,17 +321,7 @@
@Test
public void testTetheringRequestParcelable() {
- final SoftApConfiguration softApConfiguration;
- if (SdkLevel.isAtLeastT()) {
- softApConfiguration = new SoftApConfiguration.Builder()
- .setWifiSsid(WifiSsid.fromBytes(
- "This is an SSID!".getBytes(StandardCharsets.UTF_8)))
- .build();
- } else {
- softApConfiguration = new SoftApConfiguration.Builder()
- .setSsid("This is an SSID!")
- .build();
- }
+ final SoftApConfiguration softApConfiguration = createSoftApConfiguration("SSID");
final LinkAddress localAddr = new LinkAddress("192.168.24.5/24");
final LinkAddress clientAddr = new LinkAddress("192.168.24.100/24");
final TetheringRequest withConfig = new TetheringRequest.Builder(TETHERING_WIFI)
@@ -363,9 +348,7 @@
tetherEventCallback.assumeWifiTetheringSupported(mContext);
tetherEventCallback.expectNoTetheringActive();
- SoftApConfiguration softApConfig = new SoftApConfiguration.Builder()
- .setWifiSsid(WifiSsid.fromBytes("This is an SSID!"
- .getBytes(StandardCharsets.UTF_8))).build();
+ SoftApConfiguration softApConfig = createSoftApConfiguration("SSID");
final TetheringInterface tetheredIface =
mCtsTetheringUtils.startWifiTethering(tetherEventCallback, softApConfig);
diff --git a/tests/unit/jni/android_net_frameworktests_util/onload.cpp b/tests/unit/jni/android_net_frameworktests_util/onload.cpp
index 06a3986..a0ce4f8 100644
--- a/tests/unit/jni/android_net_frameworktests_util/onload.cpp
+++ b/tests/unit/jni/android_net_frameworktests_util/onload.cpp
@@ -24,6 +24,8 @@
int register_com_android_net_module_util_BpfMap(JNIEnv* env, char const* class_name);
int register_com_android_net_module_util_TcUtils(JNIEnv* env, char const* class_name);
+int register_com_android_net_module_util_TimerFdUtils(JNIEnv *env,
+ char const *class_name);
extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
JNIEnv *env;
@@ -38,6 +40,10 @@
if (register_com_android_net_module_util_TcUtils(env,
"android/net/frameworktests/util/TcUtils") < 0) return JNI_ERR;
+ if (register_com_android_net_module_util_TimerFdUtils(
+ env, "android/net/frameworktests/util/TimerFdUtils") < 0)
+ return JNI_ERR;
+
return JNI_VERSION_1_6;
}
diff --git a/thread/TEST_MAPPING b/thread/TEST_MAPPING
index 34d67bb..40842f1 100644
--- a/thread/TEST_MAPPING
+++ b/thread/TEST_MAPPING
@@ -13,6 +13,9 @@
"postsubmit": [
{
"name": "ThreadNetworkMultiDeviceTests"
+ },
+ {
+ "name": "ThreadNetworkTrelDisabledTests"
}
]
}
diff --git a/thread/tests/integration/Android.bp b/thread/tests/integration/Android.bp
index 8f082a4..798a51e 100644
--- a/thread/tests/integration/Android.bp
+++ b/thread/tests/integration/Android.bp
@@ -62,3 +62,23 @@
],
compile_multilib: "both",
}
+
+android_test {
+ name: "ThreadNetworkTrelDisabledTests",
+ platform_apis: true,
+ manifest: "AndroidManifest.xml",
+ test_config: "AndroidTestTrelDisabled.xml",
+ defaults: [
+ "framework-connectivity-test-defaults",
+ "ThreadNetworkIntegrationTestsDefaults",
+ ],
+ test_suites: [
+ "mts-tethering",
+ "general-tests",
+ ],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
+ compile_multilib: "both",
+}
diff --git a/thread/tests/integration/AndroidTestTrelDisabled.xml b/thread/tests/integration/AndroidTestTrelDisabled.xml
new file mode 100644
index 0000000..600652a
--- /dev/null
+++ b/thread/tests/integration/AndroidTestTrelDisabled.xml
@@ -0,0 +1,57 @@
+<?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.
+ -->
+
+<configuration description="Config for Thread integration tests with TREL disabled">
+ <option name="test-tag" value="ThreadNetworkTrelDisabledTests" />
+ <option name="test-suite-tag" value="apct" />
+
+ <!--
+ Only run tests if the device under test is SDK version 34 (Android 14) or above.
+ -->
+ <object type="module_controller"
+ class="com.android.tradefed.testtype.suite.module.Sdk34ModuleController" />
+
+ <!-- Run tests in MTS only if the Tethering Mainline module is installed. -->
+ <object type="module_controller"
+ class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+ <option name="mainline-module-package-name" value="com.google.android.tethering" />
+ </object>
+
+ <object type="module_controller"
+ class="com.android.tradefed.testtype.suite.module.DeviceFeatureModuleController">
+ <option name="required-feature" value="android.hardware.thread_network" />
+ </object>
+
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
+
+ <!-- Install test -->
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="test-file-name" value="ThreadNetworkTrelDisabledTests.apk" />
+ <option name="check-min-sdk" value="true" />
+ <option name="cleanup-apks" value="true" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.thread.tests.integration" />
+ </test>
+
+ <!-- Disable TREL for integration tests -->
+ <target_preparer class="com.android.tradefed.targetprep.FeatureFlagTargetPreparer">
+ <option name="flag-value"
+ value="thread_network/TrelFeature__enabled=false"/>
+ </target_preparer>
+</configuration>
diff --git a/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java b/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java
index 15259c8..6c2a9bb 100644
--- a/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java
+++ b/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java
@@ -30,6 +30,8 @@
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
@@ -50,6 +52,7 @@
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.net.module.util.DeviceConfigUtils;
import com.android.net.module.util.HexDump;
import com.google.common.truth.Correspondence;
@@ -445,9 +448,13 @@
}
@Test
- // TODO: move this case out to BorderRoutingTest when the service discovery utilities
+ // TODO: move this case out of ServiceDiscoveryTest when the service discovery utilities
// are decoupled from this test.
public void trelFeatureFlagEnabled_trelServicePublished() throws Exception {
+ assumeTrue(
+ DeviceConfigUtils.getDeviceConfigPropertyBoolean(
+ "thread_network", "TrelFeature__enabled", false));
+
NsdServiceInfo discoveredService = discoverService(mNsdManager, "_trel._udp");
assertThat(discoveredService).isNotNull();
// Resolve service with the current TREL port, otherwise it may return stale service from
@@ -463,6 +470,17 @@
.isEqualTo(mOtCtl.getExtendedPanId().toLowerCase(Locale.ROOT));
}
+ @Test
+ // TODO: move this case out of ServiceDiscoveryTest when the service discovery utilities
+ // are decoupled from this test.
+ public void trelFeatureFlagDisabled_trelServiceNotPublished() throws Exception {
+ assumeFalse(
+ DeviceConfigUtils.getDeviceConfigPropertyBoolean(
+ "thread_network", "TrelFeature__enabled", false));
+
+ assertThrows(TimeoutException.class, () -> discoverService(mNsdManager, "_trel._udp"));
+ }
+
private void registerService(NsdServiceInfo serviceInfo, RegistrationListener listener)
throws InterruptedException, ExecutionException, TimeoutException {
mNsdManager.registerService(serviceInfo, PROTOCOL_DNS_SD, listener);
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 9af0b53..dcbb3f5 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
@@ -17,6 +17,7 @@
package com.android.server.thread;
import static android.Manifest.permission.NETWORK_SETTINGS;
+import static android.Manifest.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG;
import static android.Manifest.permission.WRITE_DEVICE_CONFIG;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
@@ -266,6 +267,7 @@
public void tearDown() throws Exception {
runAsShell(
WRITE_DEVICE_CONFIG,
+ WRITE_ALLOWLISTED_DEVICE_CONFIG,
() -> DeviceConfig.deleteProperty("thread_network", "TrelFeature__enabled"));
}
@@ -338,6 +340,7 @@
public void initialize_trelFeatureDisabled_trelDisabledAtOtDaemon() throws Exception {
runAsShell(
WRITE_DEVICE_CONFIG,
+ WRITE_ALLOWLISTED_DEVICE_CONFIG,
() ->
DeviceConfig.setProperty(
"thread_network", "TrelFeature__enabled", "false", false));
@@ -352,6 +355,7 @@
public void initialize_trelFeatureEnabled_setTrelEnabledAtOtDamon() throws Exception {
runAsShell(
WRITE_DEVICE_CONFIG,
+ WRITE_ALLOWLISTED_DEVICE_CONFIG,
() ->
DeviceConfig.setProperty(
"thread_network", "TrelFeature__enabled", "true", false));