Merge "no-op: Use helper class to handle pending requests" into main
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index 3e7c697..2878f79 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -102,9 +102,7 @@
"dscpPolicy.o",
"netd.o",
"offload.o",
- "offload@mainline.o",
"test.o",
- "test@mainline.o",
],
apps: [
"ServiceConnectivityResources",
@@ -112,6 +110,7 @@
prebuilts: [
"current_sdkinfo",
"netbpfload.31rc",
+ "netbpfload.33rc",
"netbpfload.35rc",
"ot-daemon.34rc",
],
diff --git a/bpf/dns_helper/DnsBpfHelper.cpp b/bpf/dns_helper/DnsBpfHelper.cpp
index 0719ade..cf2fa2b 100644
--- a/bpf/dns_helper/DnsBpfHelper.cpp
+++ b/bpf/dns_helper/DnsBpfHelper.cpp
@@ -32,12 +32,44 @@
} \
} while (0)
+// copied from BpfHandler.cpp
+static bool mainlineNetBpfLoadDone() {
+ return !access("/sys/fs/bpf/netd_shared/mainline_done", F_OK);
+}
+
+// copied from BpfHandler.cpp
+static inline void waitForNetProgsLoaded() {
+ // infinite loop until success with 5/10/20/40/60/60/60... delay
+ for (int delay = 5;; delay *= 2) {
+ if (delay > 60) delay = 60;
+ if (base::WaitForProperty("init.svc.mdnsd_netbpfload", "stopped", std::chrono::seconds(delay))
+ && mainlineNetBpfLoadDone()) return;
+ LOG(WARNING) << "Waited " << delay << "s for init.svc.mdnsd_netbpfload=stopped, still waiting.";
+ }
+}
+
base::Result<void> DnsBpfHelper::init() {
- if (!android::modules::sdklevel::IsAtLeastT()) {
- LOG(ERROR) << __func__ << ": Unsupported before Android T.";
+ if (!android::modules::sdklevel::IsAtLeastS()) {
+ LOG(ERROR) << __func__ << ": Unsupported before Android S.";
return base::Error(EOPNOTSUPP);
}
+ if (!android::modules::sdklevel::IsAtLeastT()) {
+ LOG(INFO) << "performing Android S mainline NetBpfload magic!";
+ if (!mainlineNetBpfLoadDone()) {
+ // We're on S/Sv2 & it's the first time netd is starting up (unless crashlooping)
+ if (!base::SetProperty("ctl.start", "mdnsd_netbpfload")) {
+ LOG(ERROR) << "Failed to set property ctl.start=mdnsd_netbpfload, see dmesg for reason.";
+ return base::Error(ENOEXEC);
+ }
+
+ LOG(INFO) << "Waiting for Networking BPF programs";
+ waitForNetProgsLoaded();
+ LOG(INFO) << "Networking BPF programs are loaded";
+ }
+ return {};
+ }
+
RETURN_IF_RESULT_NOT_OK(mConfigurationMap.init(CONFIGURATION_MAP_PATH));
RETURN_IF_RESULT_NOT_OK(mUidOwnerMap.init(UID_OWNER_MAP_PATH));
RETURN_IF_RESULT_NOT_OK(mDataSaverEnabledMap.init(DATA_SAVER_ENABLED_MAP_PATH));
diff --git a/bpf/loader/Android.bp b/bpf/loader/Android.bp
index edb260c..780fe20 100644
--- a/bpf/loader/Android.bp
+++ b/bpf/loader/Android.bp
@@ -56,15 +56,26 @@
installable: false,
}
-// Versioned netbpfload init rc: init system will process it only on api S/31+ devices
+// Versioned netbpfload init rc: init system will process it only on api R/30 S/31 Sv2/32 devices
// Note: R[30] S[31] Sv2[32] T[33] U[34] V[35])
//
// For details of versioned rc files see:
// https://android.googlesource.com/platform/system/core/+/HEAD/init/README.md#versioned-rc-files-within-apexs
+//
+// However, .Xrc versioning doesn't work on S, so we use unversioned, and thus *do* trigger on R,
+// luckily nothing ever uses the new service on R, so you can think of it as being S/Sv2 only
prebuilt_etc {
name: "netbpfload.31rc",
src: "netbpfload.31rc",
- filename: "netbpfload.31rc",
+ filename: "netbpfload.rc", // intentional: .31rc wouldn't take effect on S
+ installable: false,
+}
+
+// Versioned netbpfload init rc: init system will process it only on api T/33+ devices
+prebuilt_etc {
+ name: "netbpfload.33rc",
+ src: "netbpfload.33rc",
+ filename: "netbpfload.33rc",
installable: false,
}
diff --git a/bpf/loader/NetBpfLoad.cpp b/bpf/loader/NetBpfLoad.cpp
index 9c62e74..bad506f 100644
--- a/bpf/loader/NetBpfLoad.cpp
+++ b/bpf/loader/NetBpfLoad.cpp
@@ -122,6 +122,7 @@
struct Location {
const char* const dir = "";
const char* const prefix = "";
+ const bool t_plus = true;
};
// Returns the build type string (from ro.build.type).
@@ -1216,8 +1217,9 @@
const Location locations[] = {
// S+ Tethering mainline module (network_stack): tether offload
{
- .dir = BPFROOT "/",
+ .dir = BPFROOT "/tethering/",
.prefix = "tethering/",
+ .t_plus = false,
},
// T+ Tethering mainline module (shared with netd & system server)
// netutils_wrapper (for iptables xt_bpf) has access to programs
@@ -1412,6 +1414,13 @@
}
static int doLoad(char** argv, char * const envp[]) {
+ if (!isAtLeastS) {
+ ALOGE("Impossible - not reachable on Android <S.");
+ // for safety, we don't fail, this is a just-in-case workaround
+ // for any possible busted 'optimized' start everything vendor init hacks on R
+ return 0;
+ }
+
const bool runningAsRoot = !getuid(); // true iff U QPR3 or V+
const int first_api_level = GetIntProperty("ro.board.first_api_level", api_level);
@@ -1446,14 +1455,9 @@
logTetheringApexVersion();
- if (!isAtLeastT) {
- ALOGE("Impossible - not reachable on Android <T.");
- return 1;
- }
-
// both S and T require kernel 4.9 (and eBpf support)
- if (isAtLeastT && !isAtLeastKernelVersion(4, 9, 0)) {
- ALOGE("Android T requires kernel 4.9.");
+ if (!isAtLeastKernelVersion(4, 9, 0)) {
+ ALOGE("Android S & T require kernel 4.9.");
return 1;
}
@@ -1622,18 +1626,22 @@
// which could otherwise fail with ENOENT during object pinning or renaming,
// due to ordering issues)
for (const auto& location : locations) {
+ if (location.t_plus && !isAtLeastT) continue;
if (createSysFsBpfSubDir(location.prefix)) return 1;
}
- // Note: there's no actual src dir for fs_bpf_loader .o's,
- // so it is not listed in 'locations[].prefix'.
- // This is because this is primarily meant for triggering genfscon rules,
- // and as such this will likely always be the case.
- // Thus we need to manually create the /sys/fs/bpf/loader subdirectory.
- if (createSysFsBpfSubDir("loader")) return 1;
+ if (isAtLeastT) {
+ // Note: there's no actual src dir for fs_bpf_loader .o's,
+ // so it is not listed in 'locations[].prefix'.
+ // This is because this is primarily meant for triggering genfscon rules,
+ // and as such this will likely always be the case.
+ // Thus we need to manually create the /sys/fs/bpf/loader subdirectory.
+ if (createSysFsBpfSubDir("loader")) return 1;
+ }
// Load all ELF objects, create programs and maps, and pin them
for (const auto& location : locations) {
+ if (location.t_plus && !isAtLeastT) continue;
if (loadAllElfObjects(bpfloader_ver, location) != 0) {
ALOGE("=== CRITICAL FAILURE LOADING BPF PROGRAMS FROM %s ===", location.dir);
ALOGE("If this triggers reliably, you're probably missing kernel options or patches.");
@@ -1654,6 +1662,9 @@
return 1;
}
+ // on S we haven't created this subdir yet, but we need it for 'mainline_done' flag below
+ if (!isAtLeastT && createSysFsBpfSubDir("netd_shared")) return 1;
+
// leave a flag that we're done
if (createSysFsBpfSubDir("netd_shared/mainline_done")) return 1;
@@ -1688,7 +1699,12 @@
} // namespace android
int main(int argc, char** argv, char * const envp[]) {
- InitLogging(argv, &KernelLogger);
+ if (android::bpf::isAtLeastT) {
+ InitLogging(argv, &KernelLogger);
+ } else {
+ // S lacks the sepolicy to make non-root uid KernelLogger viable
+ InitLogging(argv);
+ }
if (argc == 2 && !strcmp(argv[1], "done")) {
// we're being re-exec'ed from platform bpfloader to 'finalize' things
diff --git a/bpf/loader/netbpfload.31rc b/bpf/loader/netbpfload.31rc
index 6bc488e..bca7dc8 100644
--- a/bpf/loader/netbpfload.31rc
+++ b/bpf/loader/netbpfload.31rc
@@ -1,22 +1,13 @@
-# This file takes effect only on S, T and U (on V netbpfload.35rc takes priority).
+# This file takes effect only on S and Sv2
+# (Note: it does take effect on R as well, but isn't actually used)
#
-# The service is started from netd's
-# S: dnsresolver call into ADnsHelper_init
-# T/U: libnetd_updatable shared library
+# The service is started from netd's dnsresolver call into ADnsHelper_init()
# on initial (boot time) startup of netd.
-#
-# However we never start this service on U QPR3.
-#
-# This is due to lack of a need: U QPR2 split the previously single
-# platform bpfloader into platform netbpfload -> platform bpfloader.
-# U QPR3 made the platform netbpfload unconditionally exec apex netbpfload,
-# so by the time U QPR3's netd runs, apex netbpfload is already done.
service mdnsd_netbpfload /apex/com.android.tethering/bin/netbpfload
capabilities CHOWN SYS_ADMIN NET_ADMIN
group system root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw
user system
- file /dev/kmsg w
rlimit memlock 1073741824 1073741824
oneshot
reboot_on_failure reboot,netbpfload-failed
diff --git a/bpf/loader/netbpfload.33rc b/bpf/loader/netbpfload.33rc
new file mode 100644
index 0000000..eb937dd
--- /dev/null
+++ b/bpf/loader/netbpfload.33rc
@@ -0,0 +1,20 @@
+# This file takes effect only on T and U (on V netbpfload.35rc takes priority).
+#
+# The service is started from netd's libnetd_updatable shared library
+# on initial (boot time) startup of netd.
+#
+# However we never start this service on U QPR3.
+#
+# This is due to lack of a need: U QPR2 split the previously single
+# platform bpfloader into platform netbpfload -> platform bpfloader.
+# U QPR3 made the platform netbpfload unconditionally exec apex netbpfload,
+# so by the time U QPR3's netd runs, apex netbpfload is already done.
+
+service mdnsd_netbpfload /apex/com.android.tethering/bin/netbpfload
+ capabilities CHOWN SYS_ADMIN NET_ADMIN
+ group system root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw
+ user system
+ file /dev/kmsg w
+ rlimit memlock 1073741824 1073741824
+ oneshot
+ reboot_on_failure reboot,netbpfload-failed
diff --git a/bpf/netd/BpfHandler.cpp b/bpf/netd/BpfHandler.cpp
index e3e508b..d41aa81 100644
--- a/bpf/netd/BpfHandler.cpp
+++ b/bpf/netd/BpfHandler.cpp
@@ -341,8 +341,8 @@
if (chargeUid == AID_CLAT) return -EPERM;
// The socket destroy listener only monitors on the group {INET_TCP, INET_UDP, INET6_TCP,
- // INET6_UDP}. Tagging listener unsupported socket causes that the tag can't be removed from
- // tag map automatically. Eventually, the tag map may run out of space because of dead tag
+ // INET6_UDP}. Tagging listener unsupported sockets (on <5.10) means the tag cannot be
+ // removed from tag map automatically. Eventually, it may run out of space due to dead tag
// entries. Note that although tagSocket() of net client has already denied the family which
// is neither AF_INET nor AF_INET6, the family validation is still added here just in case.
// See tagSocket in system/netd/client/NetdClient.cpp and
@@ -360,15 +360,19 @@
return -EAFNOSUPPORT;
}
- int socketProto;
- socklen_t protoLen = sizeof(socketProto);
- if (getsockopt(sockFd, SOL_SOCKET, SO_PROTOCOL, &socketProto, &protoLen)) {
- ALOGE("Failed to getsockopt SO_PROTOCOL: %s, fd: %d", strerror(errno), sockFd);
- return -errno;
- }
- if (socketProto != IPPROTO_UDP && socketProto != IPPROTO_TCP) {
- ALOGV("Unsupported protocol: %d", socketProto);
- return -EPROTONOSUPPORT;
+ // On 5.10+ the BPF_CGROUP_INET_SOCK_RELEASE hook takes care of cookie tag map cleanup
+ // during socket destruction. As such the socket destroy listener is superfluous.
+ if (!isAtLeastKernelVersion(5, 10, 0)) {
+ int socketProto;
+ socklen_t protoLen = sizeof(socketProto);
+ if (getsockopt(sockFd, SOL_SOCKET, SO_PROTOCOL, &socketProto, &protoLen)) {
+ ALOGE("Failed to getsockopt SO_PROTOCOL: %s, fd: %d", strerror(errno), sockFd);
+ return -errno;
+ }
+ if (socketProto != IPPROTO_UDP && socketProto != IPPROTO_TCP) {
+ ALOGV("Unsupported protocol: %d", socketProto);
+ return -EPROTONOSUPPORT;
+ }
}
uint64_t sock_cookie = getSocketCookie(sockFd);
diff --git a/bpf/netd/BpfHandlerTest.cpp b/bpf/netd/BpfHandlerTest.cpp
index b38fa16..4002b4c 100644
--- a/bpf/netd/BpfHandlerTest.cpp
+++ b/bpf/netd/BpfHandlerTest.cpp
@@ -191,7 +191,11 @@
int rawSocket = socket(AF_INET, SOCK_RAW | SOCK_CLOEXEC, IPPROTO_RAW);
EXPECT_LE(0, rawSocket);
EXPECT_NE(NONEXISTENT_COOKIE, getSocketCookie(rawSocket));
- EXPECT_EQ(-EPROTONOSUPPORT, mBh.tagSocket(rawSocket, TEST_TAG, TEST_UID, TEST_UID));
+ if (isAtLeastKernelVersion(5, 10, 0)) {
+ EXPECT_EQ(0, mBh.tagSocket(rawSocket, TEST_TAG, TEST_UID, TEST_UID));
+ } else {
+ EXPECT_EQ(-EPROTONOSUPPORT, mBh.tagSocket(rawSocket, TEST_TAG, TEST_UID, TEST_UID));
+ }
}
TEST_F(BpfHandlerTest, TestTagSocketWithoutPermission) {
diff --git a/bpf/progs/Android.bp b/bpf/progs/Android.bp
index 20d194c..2bfe613 100644
--- a/bpf/progs/Android.bp
+++ b/bpf/progs/Android.bp
@@ -69,32 +69,16 @@
sub_dir: "net_shared",
}
-// Ships to Android S, the bpfloader of which fails to parse BTF enabled .o's.
bpf {
name: "offload.o",
srcs: ["offload.c"],
- btf: false,
+ sub_dir: "tethering",
}
-// This version ships to Android T+ which uses mainline netbpfload.
-bpf {
- name: "offload@mainline.o",
- srcs: ["offload@mainline.c"],
- cflags: ["-DMAINLINE"],
-}
-
-// Ships to Android S, the bpfloader of which fails to parse BTF enabled .o's.
bpf {
name: "test.o",
srcs: ["test.c"],
- btf: false,
-}
-
-// This version ships to Android T+ which uses mainline netbpfload.
-bpf {
- name: "test@mainline.o",
- srcs: ["test@mainline.c"],
- cflags: ["-DMAINLINE"],
+ sub_dir: "tethering",
}
bpf {
diff --git a/bpf/progs/offload.c b/bpf/progs/offload.c
index 0f23844..b34fe6f 100644
--- a/bpf/progs/offload.c
+++ b/bpf/progs/offload.c
@@ -14,16 +14,8 @@
* limitations under the License.
*/
-#ifdef MAINLINE
-// BTF is incompatible with bpfloaders < v0.10, hence for S (v0.2) we must
-// ship a different file than for later versions, but we need bpfloader v0.25+
-// for obj@ver.o support
-#define BPFLOADER_MIN_VER BPFLOADER_MAINLINE_T_VERSION
-#else /* MAINLINE */
-// The resulting .o needs to load on the Android S bpfloader
-#define BPFLOADER_MIN_VER BPFLOADER_S_VERSION
-#define BPFLOADER_MAX_VER BPFLOADER_T_VERSION
-#endif /* MAINLINE */
+// The resulting .o needs to load on Android S+
+#define BPFLOADER_MIN_VER BPFLOADER_MAINLINE_S_VERSION
#include "bpf_net_helpers.h"
#include "offload.h"
diff --git a/bpf/progs/test.c b/bpf/progs/test.c
index 8585118..4dba6b9 100644
--- a/bpf/progs/test.c
+++ b/bpf/progs/test.c
@@ -14,16 +14,8 @@
* limitations under the License.
*/
-#ifdef MAINLINE
-// BTF is incompatible with bpfloaders < v0.10, hence for S (v0.2) we must
-// ship a different file than for later versions, but we need bpfloader v0.25+
-// for obj@ver.o support
-#define BPFLOADER_MIN_VER BPFLOADER_MAINLINE_T_VERSION
-#else /* MAINLINE */
-// The resulting .o needs to load on the Android S bpfloader
-#define BPFLOADER_MIN_VER BPFLOADER_S_VERSION
-#define BPFLOADER_MAX_VER BPFLOADER_T_VERSION
-#endif /* MAINLINE */
+// The resulting .o needs to load on Android S+
+#define BPFLOADER_MIN_VER BPFLOADER_MAINLINE_S_VERSION
// This is non production code, only used for testing
// Needed because the bitmap array definition is non-kosher for pre-T OS devices.
diff --git a/bpf/tests/mts/bpf_existence_test.cpp b/bpf/tests/mts/bpf_existence_test.cpp
index 75fb8e9..4d5f9b5 100644
--- a/bpf/tests/mts/bpf_existence_test.cpp
+++ b/bpf/tests/mts/bpf_existence_test.cpp
@@ -196,7 +196,12 @@
// S requires Linux Kernel 4.9+ and thus requires eBPF support.
if (isAtLeastS) ASSERT_TRUE(isAtLeastKernelVersion(4, 9, 0));
- DO_EXPECT(isAtLeastS, MAINLINE_FOR_S_PLUS);
+
+ // on S without a new enough DnsResolver apex, NetBpfLoad doesn't get triggered,
+ // and thus no mainline programs get loaded.
+ bool mainlineBpfCapableResolve = !access("/apex/com.android.resolv/NetBpfLoad-S.flag", F_OK);
+ bool mainlineNetBpfLoad = isAtLeastT || mainlineBpfCapableResolve;
+ DO_EXPECT(isAtLeastS && mainlineNetBpfLoad, MAINLINE_FOR_S_PLUS);
// Nothing added or removed in SCv2.
diff --git a/framework/src/android/net/connectivity/ConnectivityCompatChanges.java b/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
index 317854b..2261c69 100644
--- a/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
+++ b/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
@@ -100,9 +100,9 @@
public static final long ENABLE_MATCH_LOCAL_NETWORK = 319212206L;
/**
- * On Android {@link android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM} or higher releases,
- * network access from apps targeting Android 36 or higher that do not have the
- * {@link android.Manifest.permission#INTERNET} permission is considered blocked.
+ * On Android versions starting from 37, network access from apps targeting
+ * Android 37 or higher, that do not have the {@link android.Manifest.permission#INTERNET}
+ * permission, is considered blocked.
* This results in API behaviors change for apps without
* {@link android.Manifest.permission#INTERNET} permission.
* {@link android.net.NetworkInfo} returned from {@link android.net.ConnectivityManager} APIs
@@ -115,10 +115,12 @@
* network access from apps without {@link android.Manifest.permission#INTERNET} permission is
* considered not blocked even though apps cannot access any networks.
*
+ * TODO: b/400903101 - Update the target SDK version once it's finalized.
+ *
* @hide
*/
@ChangeId
- @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ @EnabledAfter(targetSdkVersion = 36)
public static final long NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION = 333340911L;
/**
diff --git a/networksecurity/service/Android.bp b/networksecurity/service/Android.bp
index d7aacdb..3c964e5 100644
--- a/networksecurity/service/Android.bp
+++ b/networksecurity/service/Android.bp
@@ -32,6 +32,7 @@
"framework-connectivity-pre-jarjar",
"service-connectivity-pre-jarjar",
"framework-statsd.stubs.module_lib",
+ "ServiceConnectivityResources",
],
static_libs: [
diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyJob.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyJob.java
index e6f1379..f1b9a4f 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyJob.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyJob.java
@@ -38,6 +38,7 @@
private final DataStore mDataStore;
private final CertificateTransparencyDownloader mCertificateTransparencyDownloader;
private final CompatibilityVersion mCompatVersion;
+ private final SignatureVerifier mSignatureVerifier;
private final AlarmManager mAlarmManager;
private final PendingIntent mPendingIntent;
@@ -49,11 +50,13 @@
Context context,
DataStore dataStore,
CertificateTransparencyDownloader certificateTransparencyDownloader,
- CompatibilityVersion compatVersion) {
+ CompatibilityVersion compatVersion,
+ SignatureVerifier signatureVerifier) {
mContext = context;
mDataStore = dataStore;
mCertificateTransparencyDownloader = certificateTransparencyDownloader;
mCompatVersion = compatVersion;
+ mSignatureVerifier = signatureVerifier;
mAlarmManager = context.getSystemService(AlarmManager.class);
mPendingIntent =
@@ -127,6 +130,7 @@
private void startDependencies() {
mDataStore.load();
mCertificateTransparencyDownloader.addCompatibilityVersion(mCompatVersion);
+ mSignatureVerifier.loadAllowedKeys();
mContext.registerReceiver(
mCertificateTransparencyDownloader,
new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE),
@@ -139,6 +143,7 @@
private void stopDependencies() {
mContext.unregisterReceiver(mCertificateTransparencyDownloader);
+ mSignatureVerifier.clearAllowedKeys();
mCertificateTransparencyDownloader.clearCompatibilityVersions();
mDataStore.delete();
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 a71ff7c..2e910b2 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java
@@ -52,6 +52,7 @@
public CertificateTransparencyService(Context context) {
DataStore dataStore = new DataStore(Config.PREFERENCES_FILE);
+ SignatureVerifier signatureVerifier = new SignatureVerifier(context);
mCertificateTransparencyJob =
new CertificateTransparencyJob(
context,
@@ -60,13 +61,14 @@
context,
dataStore,
new DownloadHelper(context),
- new SignatureVerifier(context),
+ signatureVerifier,
new CertificateTransparencyLoggerImpl(dataStore)),
new CompatibilityVersion(
Config.COMPATIBILITY_VERSION,
Config.URL_SIGNATURE,
Config.URL_LOG_LIST,
- Config.CT_ROOT_DIRECTORY_PATH));
+ Config.CT_ROOT_DIRECTORY_PATH),
+ signatureVerifier);
}
/**
diff --git a/networksecurity/service/src/com/android/server/net/ct/PemReader.java b/networksecurity/service/src/com/android/server/net/ct/PemReader.java
new file mode 100644
index 0000000..56b3973
--- /dev/null
+++ b/networksecurity/service/src/com/android/server/net/ct/PemReader.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2025 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 java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.PublicKey;
+import java.security.spec.KeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.Collection;
+
+/** Utility class to read keys in PEM format. */
+class PemReader {
+
+ private static final String BEGIN = "-----BEGIN";
+ private static final String END = "-----END";
+
+ /**
+ * Parse the provided input stream and return the list of keys from the stream.
+ *
+ * @param input the input stream
+ * @return the keys
+ */
+ public static Collection<PublicKey> readKeysFrom(InputStream input)
+ throws IOException, GeneralSecurityException {
+ KeyFactory instance = KeyFactory.getInstance("RSA");
+ Collection<PublicKey> keys = new ArrayList<>();
+
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(input))) {
+ String line = reader.readLine();
+ while (line != null) {
+ if (line.startsWith(BEGIN)) {
+ keys.add(instance.generatePublic(readNextKey(reader)));
+ } else {
+ throw new IOException("Unexpected line in the reader: " + line);
+ }
+ line = reader.readLine();
+ }
+ } catch (IllegalArgumentException e) {
+ throw new GeneralSecurityException("Invalid public key base64 encoding", e);
+ }
+
+ return keys;
+ }
+
+ private static KeySpec readNextKey(BufferedReader reader) throws IOException {
+ StringBuilder publicKeyBuilder = new StringBuilder();
+
+ String line = reader.readLine();
+ while (line != null) {
+ if (line.startsWith(END)) {
+ return new X509EncodedKeySpec(
+ Base64.getDecoder().decode(publicKeyBuilder.toString()));
+ } else {
+ publicKeyBuilder.append(line);
+ }
+ line = reader.readLine();
+ }
+
+ throw new IOException("Unexpected end of the reader");
+ }
+}
diff --git a/networksecurity/service/src/com/android/server/net/ct/SignatureVerifier.java b/networksecurity/service/src/com/android/server/net/ct/SignatureVerifier.java
index 6040ef6..87a4973 100644
--- a/networksecurity/service/src/com/android/server/net/ct/SignatureVerifier.java
+++ b/networksecurity/service/src/com/android/server/net/ct/SignatureVerifier.java
@@ -30,6 +30,9 @@
import androidx.annotation.VisibleForTesting;
+import com.android.connectivity.resources.R;
+import com.android.server.connectivity.ConnectivityResources;
+
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
@@ -39,21 +42,39 @@
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
+import java.util.HashSet;
import java.util.Optional;
+import java.util.Set;
/** Verifier of the log list signature. */
@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
public class SignatureVerifier {
- private final Context mContext;
private static final String TAG = "SignatureVerifier";
+ private final Context mContext;
+
@NonNull private Optional<PublicKey> mPublicKey = Optional.empty();
+ private final Set<PublicKey> mAllowedKeys = new HashSet<>();
+
public SignatureVerifier(Context context) {
mContext = context;
}
+ void loadAllowedKeys() {
+ try (InputStream input =
+ new ConnectivityResources(mContext).get().openRawResource(R.raw.ct_public_keys)) {
+ mAllowedKeys.addAll(PemReader.readKeysFrom(input));
+ } catch (GeneralSecurityException | IOException e) {
+ Log.e(TAG, "Error loading public keys", e);
+ }
+ }
+
+ void clearAllowedKeys() {
+ mAllowedKeys.clear();
+ }
+
@VisibleForTesting
Optional<PublicKey> getPublicKey() {
return mPublicKey;
@@ -82,7 +103,11 @@
}
@VisibleForTesting
- void setPublicKey(PublicKey publicKey) {
+ void setPublicKey(PublicKey publicKey) throws GeneralSecurityException {
+ if (!mAllowedKeys.contains(publicKey)) {
+ // TODO(b/400704086): add logging for this failure.
+ throw new GeneralSecurityException("Public key not in allowlist");
+ }
mPublicKey = Optional.of(publicKey);
}
@@ -105,21 +130,18 @@
byte[] signatureBytes = signatureStream.readAllBytes();
statusBuilder.setSignature(new String(signatureBytes));
- try {
- byte[] decodedSigBytes = Base64.getDecoder().decode(signatureBytes);
- if (!verifier.verify(decodedSigBytes)) {
- // Leave the UpdateState as UNKNOWN_STATE if successful as there are other
- // potential failures past the signature verification step
- statusBuilder.setState(SIGNATURE_VERIFICATION_FAILED);
- }
- } catch (IllegalArgumentException e) {
- Log.w(TAG, "Invalid signature base64 encoding", e);
- statusBuilder.setState(SIGNATURE_INVALID);
- return statusBuilder.build();
+ if (!verifier.verify(Base64.getDecoder().decode(signatureBytes))) {
+ // Leave the UpdateState as UNKNOWN_STATE if successful as there are other
+ // potential failures past the signature verification step
+ statusBuilder.setState(SIGNATURE_VERIFICATION_FAILED);
}
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "Invalid signature base64 encoding", e);
+ statusBuilder.setState(SIGNATURE_INVALID);
+ return statusBuilder.build();
} catch (InvalidKeyException e) {
- Log.e(TAG, "Signature invalid for log list verification", e);
+ Log.e(TAG, "Key invalid for log list verification", e);
statusBuilder.setState(SIGNATURE_INVALID);
return statusBuilder.build();
} catch (IOException | GeneralSecurityException e) {
@@ -135,4 +157,9 @@
return statusBuilder.build();
}
+
+ @VisibleForTesting
+ boolean addAllowedKey(PublicKey publicKey) {
+ return mAllowedKeys.add(publicKey);
+ }
}
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 2af0122..22dc6ab 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
@@ -109,6 +109,7 @@
mContext.getFilesDir());
prepareDownloadManager();
+ mSignatureVerifier.addAllowedKey(mPublicKey);
mDataStore.load();
mCertificateTransparencyDownloader.addCompatibilityVersion(mCompatVersion);
}
@@ -165,6 +166,22 @@
@Test
public void
+ testDownloader_publicKeyDownloadSuccess_publicKeyNotAllowed_doNotStartMetadataDownload()
+ throws Exception {
+ mCertificateTransparencyDownloader.startPublicKeyDownload();
+ PublicKey notAllowed = KeyPairGenerator.getInstance("RSA").generateKeyPair().getPublic();
+
+ assertThat(mSignatureVerifier.getPublicKey()).isEmpty();
+ assertThat(mCertificateTransparencyDownloader.hasMetadataDownloadId()).isFalse();
+ mCertificateTransparencyDownloader.onReceive(
+ mContext, makePublicKeyDownloadCompleteIntent(writePublicKeyToFile(notAllowed)));
+
+ assertThat(mSignatureVerifier.getPublicKey()).isEmpty();
+ assertThat(mCertificateTransparencyDownloader.hasMetadataDownloadId()).isFalse();
+ }
+
+ @Test
+ public void
testDownloader_publicKeyDownloadSuccess_updatePublicKeyFail_doNotStartMetadataDownload()
throws Exception {
mCertificateTransparencyDownloader.startPublicKeyDownload();
@@ -197,8 +214,7 @@
}
@Test
- public void testDownloader_publicKeyDownloadFail_logsFailure()
- throws Exception {
+ public void testDownloader_publicKeyDownloadFail_logsFailure() throws Exception {
mCertificateTransparencyDownloader.startPublicKeyDownload();
mCertificateTransparencyDownloader.onReceive(
@@ -243,8 +259,7 @@
}
@Test
- public void testDownloader_metadataDownloadFail_logsFailure()
- throws Exception {
+ public void testDownloader_metadataDownloadFail_logsFailure() throws Exception {
mCertificateTransparencyDownloader.startMetadataDownload();
mCertificateTransparencyDownloader.onReceive(
@@ -294,8 +309,7 @@
}
@Test
- public void testDownloader_contentDownloadFail_logsFailure()
- throws Exception {
+ public void testDownloader_contentDownloadFail_logsFailure() throws Exception {
mCertificateTransparencyDownloader.startContentDownload(mCompatVersion);
mCertificateTransparencyDownloader.onReceive(
@@ -329,9 +343,8 @@
}
@Test
- public void
- testDownloader_contentDownloadSuccess_noPublicKeyFound_logsSingleFailure()
- throws Exception {
+ public void testDownloader_contentDownloadSuccess_noPublicKeyFound_logsSingleFailure()
+ throws Exception {
File logListFile = makeLogListFile("456");
File metadataFile = sign(logListFile);
mSignatureVerifier.setPublicKey(mPublicKey);
@@ -351,16 +364,17 @@
}
@Test
- public void
- testDownloader_contentDownloadSuccess_wrongSignatureAlgo_logsSingleFailure()
- throws Exception {
+ public void testDownloader_contentDownloadSuccess_wrongSignatureAlgo_logsSingleFailure()
+ throws Exception {
// Arrange
File logListFile = makeLogListFile("456");
File metadataFile = sign(logListFile);
// Set the key to be deliberately wrong by using diff algorithm
- KeyPairGenerator instance = KeyPairGenerator.getInstance("EC");
- mSignatureVerifier.setPublicKey(instance.generateKeyPair().getPublic());
+ PublicKey wrongAlgorithmKey =
+ KeyPairGenerator.getInstance("EC").generateKeyPair().getPublic();
+ mSignatureVerifier.addAllowedKey(wrongAlgorithmKey);
+ mSignatureVerifier.setPublicKey(wrongAlgorithmKey);
// Act
mCertificateTransparencyDownloader.startMetadataDownload();
@@ -377,16 +391,15 @@
}
@Test
- public void
- testDownloader_contentDownloadSuccess_signatureNotVerified_logsSingleFailure()
- throws Exception {
+ public void testDownloader_contentDownloadSuccess_signatureNotVerified_logsSingleFailure()
+ throws Exception {
// Arrange
File logListFile = makeLogListFile("456");
- File metadataFile = sign(logListFile);
+ mSignatureVerifier.setPublicKey(mPublicKey);
- // Set the key to be deliberately wrong by using diff key pair
+ // Sign the list with a disallowed key pair
KeyPairGenerator instance = KeyPairGenerator.getInstance("RSA");
- mSignatureVerifier.setPublicKey(instance.generateKeyPair().getPublic());
+ File metadataFile = sign(logListFile, instance.generateKeyPair().getPrivate());
// Act
mCertificateTransparencyDownloader.startMetadataDownload();
@@ -405,9 +418,7 @@
}
@Test
- public void
- testDownloader_contentDownloadSuccess_installFail_logsFailure()
- throws Exception {
+ public void testDownloader_contentDownloadSuccess_installFail_logsFailure() throws Exception {
File invalidLogListFile = writeToFile("not_a_json_log_list".getBytes());
File metadataFile = sign(invalidLogListFile);
mSignatureVerifier.setPublicKey(mPublicKey);
@@ -615,9 +626,14 @@
}
private File sign(File file) throws IOException, GeneralSecurityException {
+ return sign(file, mPrivateKey);
+ }
+
+ private File sign(File file, PrivateKey privateKey)
+ throws IOException, GeneralSecurityException {
File signatureFile = File.createTempFile("log_list-metadata", "sig");
Signature signer = Signature.getInstance("SHA256withRSA");
- signer.initSign(mPrivateKey);
+ signer.initSign(privateKey);
try (InputStream fileStream = new FileInputStream(file);
OutputStream outputStream = new FileOutputStream(signatureFile)) {
diff --git a/networksecurity/tests/unit/src/com/android/server/net/ct/PemReaderTest.java b/networksecurity/tests/unit/src/com/android/server/net/ct/PemReaderTest.java
new file mode 100644
index 0000000..08629db
--- /dev/null
+++ b/networksecurity/tests/unit/src/com/android/server/net/ct/PemReaderTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2025 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 static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.GeneralSecurityException;
+import java.security.KeyPairGenerator;
+import java.security.PublicKey;
+import java.util.Base64;
+
+/** Tests for the {@link PemReader}. */
+@RunWith(JUnit4.class)
+public class PemReaderTest {
+
+ @Test
+ public void testReadKeys_singleKey() throws GeneralSecurityException, IOException {
+ PublicKey key = KeyPairGenerator.getInstance("RSA").generateKeyPair().getPublic();
+
+ assertThat(PemReader.readKeysFrom(toInputStream(key))).containsExactly(key);
+ }
+
+ @Test
+ public void testReadKeys_multipleKeys() throws GeneralSecurityException, IOException {
+ KeyPairGenerator instance = KeyPairGenerator.getInstance("RSA");
+ PublicKey key1 = instance.generateKeyPair().getPublic();
+ PublicKey key2 = instance.generateKeyPair().getPublic();
+
+ assertThat(PemReader.readKeysFrom(toInputStream(key1, key2))).containsExactly(key1, key2);
+ }
+
+ @Test
+ public void testReadKeys_notSupportedKeyType() throws GeneralSecurityException {
+ PublicKey key = KeyPairGenerator.getInstance("EC").generateKeyPair().getPublic();
+
+ assertThrows(
+ GeneralSecurityException.class, () -> PemReader.readKeysFrom(toInputStream(key)));
+ }
+
+ @Test
+ public void testReadKeys_notBase64EncodedKey() throws GeneralSecurityException {
+ InputStream inputStream =
+ new ByteArrayInputStream(
+ (""
+ + "-----BEGIN PUBLIC KEY-----\n"
+ + KeyPairGenerator.getInstance("RSA")
+ .generateKeyPair()
+ .getPublic()
+ .toString()
+ + "\n-----END PUBLIC KEY-----\n")
+ .getBytes());
+
+ assertThrows(GeneralSecurityException.class, () -> PemReader.readKeysFrom(inputStream));
+ }
+
+ @Test
+ public void testReadKeys_noPemBegin() throws GeneralSecurityException {
+ PublicKey key = KeyPairGenerator.getInstance("RSA").generateKeyPair().getPublic();
+ String base64Key = Base64.getEncoder().encodeToString(key.getEncoded());
+ String pemNoBegin = base64Key + "\n-----END PUBLIC KEY-----\n";
+
+ assertThrows(
+ IOException.class,
+ () -> PemReader.readKeysFrom(new ByteArrayInputStream(pemNoBegin.getBytes())));
+ }
+
+ @Test
+ public void testReadKeys_noPemEnd() throws GeneralSecurityException {
+ PublicKey key = KeyPairGenerator.getInstance("RSA").generateKeyPair().getPublic();
+ String base64Key = Base64.getEncoder().encodeToString(key.getEncoded());
+ String pemNoEnd = "-----BEGIN PUBLIC KEY-----\n" + base64Key;
+
+ assertThrows(
+ IOException.class,
+ () -> PemReader.readKeysFrom(new ByteArrayInputStream(pemNoEnd.getBytes())));
+ }
+
+ private InputStream toInputStream(PublicKey... keys) {
+ StringBuilder builder = new StringBuilder();
+
+ for (PublicKey key : keys) {
+ builder.append("-----BEGIN PUBLIC KEY-----\n")
+ .append(Base64.getEncoder().encodeToString(key.getEncoded()))
+ .append("\n-----END PUBLIC KEY-----\n");
+ }
+
+ return new ByteArrayInputStream(builder.toString().getBytes());
+ }
+}
diff --git a/service/src/com/android/server/connectivity/MulticastRoutingCoordinatorService.java b/service/src/com/android/server/connectivity/MulticastRoutingCoordinatorService.java
index af4aee5..c4de80f 100644
--- a/service/src/com/android/server/connectivity/MulticastRoutingCoordinatorService.java
+++ b/service/src/com/android/server/connectivity/MulticastRoutingCoordinatorService.java
@@ -21,6 +21,7 @@
import static android.net.MulticastRoutingConfig.FORWARD_WITH_MIN_SCOPE;
import static android.system.OsConstants.AF_INET6;
import static android.system.OsConstants.EADDRINUSE;
+import static android.system.OsConstants.EADDRNOTAVAIL;
import static android.system.OsConstants.IPPROTO_ICMPV6;
import static android.system.OsConstants.IPPROTO_IPV6;
import static android.system.OsConstants.SOCK_CLOEXEC;
@@ -258,6 +259,10 @@
mDependencies.setsockoptMrt6DelMif(mMulticastRoutingFd, virtualIndex);
Log.d(TAG, "Removed mifi " + virtualIndex + " from MIF");
} catch (ErrnoException e) {
+ if (e.errno == EADDRNOTAVAIL) {
+ Log.w(TAG, "multicast virtual interface " + virtualIndex + " already removed", e);
+ return;
+ }
Log.e(TAG, "failed to remove multicast virtual interface" + virtualIndex, e);
}
}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/ConnectivityDiagnosticsCollector.kt b/staticlibs/testutils/devicetests/com/android/testutils/ConnectivityDiagnosticsCollector.kt
index c7d6850..4b9429b 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/ConnectivityDiagnosticsCollector.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/ConnectivityDiagnosticsCollector.kt
@@ -430,19 +430,32 @@
* @param dumpsysCmd The dumpsys command to run (for example "connectivity").
* @param exceptionContext An exception to write a stacktrace to the dump for context.
*/
- fun collectDumpsys(dumpsysCmd: String, exceptionContext: Throwable? = null) {
- Log.i(TAG, "Collecting dumpsys $dumpsysCmd for test artifacts")
+ fun collectDumpsys(dumpsysCmd: String, exceptionContext: Throwable? = null) =
+ collectCommandOutput("dumpsys $dumpsysCmd", exceptionContext = exceptionContext)
+
+ /**
+ * Add the output of a command to the test data dump.
+ *
+ * <p>The output will be collected immediately, and exported to a test artifact file when the
+ * test ends.
+ * @param cmd The command to run. Stdout of the command will be collected.
+ * @param shell The shell to run the command in.
+ * @param exceptionContext An exception to write a stacktrace to the dump for context.
+ */
+ fun collectCommandOutput(
+ cmd: String,
+ shell: String = "sh",
+ exceptionContext: Throwable? = null
+ ) {
+ Log.i(TAG, "Collecting '$cmd' for test artifacts")
PrintWriter(buffer).let {
- it.println("--- Dumpsys $dumpsysCmd at ${ZonedDateTime.now()} ---")
+ it.println("--- $cmd at ${ZonedDateTime.now()} ---")
maybeWriteExceptionContext(it, exceptionContext)
it.flush()
}
- ParcelFileDescriptor.AutoCloseInputStream(
- InstrumentationRegistry.getInstrumentation().uiAutomation.executeShellCommand(
- "dumpsys $dumpsysCmd"
- )
- ).use {
- it.copyTo(buffer)
+
+ runCommandInShell(cmd, shell) { stdout, _ ->
+ stdout.copyTo(buffer)
}
}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/ShellUtil.kt b/staticlibs/testutils/devicetests/com/android/testutils/ShellUtil.kt
new file mode 100644
index 0000000..fadc2ab
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/ShellUtil.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2025 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.
+ */
+
+@file:JvmName("ShellUtil")
+
+package com.android.testutils
+
+import android.app.UiAutomation
+import android.os.ParcelFileDescriptor.AutoCloseInputStream
+import android.os.ParcelFileDescriptor.AutoCloseOutputStream
+import androidx.test.platform.app.InstrumentationRegistry
+import java.io.InputStream
+
+/**
+ * Run a command in a shell.
+ *
+ * Compared to [UiAutomation.executeShellCommand], this allows running commands with pipes and
+ * redirections. [UiAutomation.executeShellCommand] splits the command on spaces regardless of
+ * quotes, so it is not able to run commands like `sh -c "echo 123 > some_file"`.
+ *
+ * @param cmd Shell command to run.
+ * @param shell Command used to run the shell.
+ * @param outputProcessor Function taking stdout, stderr as argument. The streams will be closed
+ * when this function returns.
+ * @return Result of [outputProcessor].
+ */
+fun <T> runCommandInShell(
+ cmd: String,
+ shell: String = "sh",
+ outputProcessor: (InputStream, InputStream) -> T,
+): T {
+ val (stdout, stdin, stderr) = InstrumentationRegistry.getInstrumentation().uiAutomation
+ .executeShellCommandRwe(shell)
+ AutoCloseOutputStream(stdin).bufferedWriter().use { it.write(cmd) }
+ AutoCloseInputStream(stdout).use { outStream ->
+ AutoCloseInputStream(stderr).use { errStream ->
+ return outputProcessor(outStream, errStream)
+ }
+ }
+}
+
+/**
+ * Run a command in a shell.
+ *
+ * Overload of [runCommandInShell] that reads and returns stdout as String.
+ */
+fun runCommandInShell(
+ cmd: String,
+ shell: String = "sh",
+) = runCommandInShell(cmd, shell) { stdout, _ ->
+ stdout.reader().use { it.readText() }
+}
+
+/**
+ * Run a command in a root shell.
+ *
+ * This is generally only usable on devices on which [DeviceInfoUtils.isDebuggable] is true.
+ * @see runCommandInShell
+ */
+fun runCommandInRootShell(
+ cmd: String
+) = runCommandInShell(cmd, shell = "su root sh")
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceCacheTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceCacheTest.kt
index 0a8f108..976dfa9 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceCacheTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceCacheTest.kt
@@ -208,7 +208,10 @@
@Test
fun testServiceExpiredAndSendCallbacks() {
val serviceCache = MdnsServiceCache(
- thread.looper, makeFlags(isExpiredServicesRemovalEnabled = true), clock)
+ thread.looper,
+ makeFlags(isExpiredServicesRemovalEnabled = true),
+ clock
+ )
// Register service expired callbacks
val callback1 = ExpiredRecord()
val callback2 = ExpiredRecord()
@@ -218,12 +221,21 @@
doReturn(TEST_ELAPSED_REALTIME_MS).`when`(clock).elapsedRealtime()
// Add multiple services with different ttl time.
- addOrUpdateService(serviceCache, cacheKey1, createResponse(SERVICE_NAME_1, SERVICE_TYPE_1,
- DEFAULT_TTL_TIME_MS))
- addOrUpdateService(serviceCache, cacheKey1, createResponse(SERVICE_NAME_2, SERVICE_TYPE_1,
- DEFAULT_TTL_TIME_MS + 20L))
- addOrUpdateService(serviceCache, cacheKey2, createResponse(SERVICE_NAME_3, SERVICE_TYPE_2,
- DEFAULT_TTL_TIME_MS + 10L))
+ addOrUpdateService(serviceCache, cacheKey1, createResponse(
+ SERVICE_NAME_1,
+ SERVICE_TYPE_1,
+ DEFAULT_TTL_TIME_MS
+ ))
+ addOrUpdateService(serviceCache, cacheKey1, createResponse(
+ SERVICE_NAME_2,
+ SERVICE_TYPE_1,
+ DEFAULT_TTL_TIME_MS + 20L
+ ))
+ addOrUpdateService(serviceCache, cacheKey2, createResponse(
+ SERVICE_NAME_3,
+ SERVICE_TYPE_2,
+ DEFAULT_TTL_TIME_MS + 10L
+ ))
// Check the service expiration immediately. Should be no callback.
assertEquals(2, getServices(serviceCache, cacheKey1).size)
@@ -252,16 +264,25 @@
@Test
fun testRemoveExpiredServiceWhenGetting() {
val serviceCache = MdnsServiceCache(
- thread.looper, makeFlags(isExpiredServicesRemovalEnabled = true), clock)
+ thread.looper,
+ makeFlags(isExpiredServicesRemovalEnabled = true),
+ clock
+ )
doReturn(TEST_ELAPSED_REALTIME_MS).`when`(clock).elapsedRealtime()
- addOrUpdateService(serviceCache, cacheKey1,
- createResponse(SERVICE_NAME_1, SERVICE_TYPE_1, 1L /* ttlTime */))
+ addOrUpdateService(
+ serviceCache,
+ cacheKey1,
+ createResponse(SERVICE_NAME_1, SERVICE_TYPE_1, 1L /* ttlTime */)
+ )
doReturn(TEST_ELAPSED_REALTIME_MS + 2L).`when`(clock).elapsedRealtime()
assertNull(getService(serviceCache, SERVICE_NAME_1, cacheKey1))
- addOrUpdateService(serviceCache, cacheKey2,
- createResponse(SERVICE_NAME_2, SERVICE_TYPE_2, 3L /* ttlTime */))
+ addOrUpdateService(
+ serviceCache,
+ cacheKey2,
+ createResponse(SERVICE_NAME_2, SERVICE_TYPE_2, 3L /* ttlTime */)
+ )
doReturn(TEST_ELAPSED_REALTIME_MS + 4L).`when`(clock).elapsedRealtime()
assertEquals(0, getServices(serviceCache, cacheKey2).size)
}
@@ -334,8 +355,11 @@
): MdnsResponse {
val serviceName = "$serviceInstanceName.$serviceType".split(".").toTypedArray()
val response = MdnsResponse(
- 0 /* now */, "$serviceInstanceName.$serviceType".split(".").toTypedArray(),
- socketKey.interfaceIndex, socketKey.network)
+ 0 /* now */,
+ "$serviceInstanceName.$serviceType".split(".").toTypedArray(),
+ socketKey.interfaceIndex,
+ socketKey.network
+ )
// Set PTR record
val pointerRecord = MdnsPointerRecord(
@@ -343,7 +367,8 @@
TEST_ELAPSED_REALTIME_MS /* receiptTimeMillis */,
false /* cacheFlush */,
ttlTime /* ttlMillis */,
- serviceName)
+ serviceName
+ )
response.addPointerRecord(pointerRecord)
// Set SRV record.
@@ -355,7 +380,8 @@
0 /* servicePriority */,
0 /* serviceWeight */,
12345 /* port */,
- arrayOf("hostname"))
+ arrayOf("hostname")
+ )
response.serviceRecord = serviceRecord
return response
}