Merge "[Thread] add a script for make-pretty" into main
diff --git a/DnsResolver/Android.bp b/DnsResolver/Android.bp
new file mode 100644
index 0000000..d133034
--- /dev/null
+++ b/DnsResolver/Android.bp
@@ -0,0 +1,83 @@
+//
+// Copyright (C) 2023 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library {
+ name: "libcom.android.tethering.dns_helper",
+ version_script: "libcom.android.tethering.dns_helper.map.txt",
+ stubs: {
+ versions: [
+ "1",
+ ],
+ symbol_file: "libcom.android.tethering.dns_helper.map.txt",
+ },
+ defaults: ["netd_defaults"],
+ header_libs: [
+ "bpf_connectivity_headers",
+ "libcutils_headers",
+ ],
+ srcs: [
+ "DnsBpfHelper.cpp",
+ "DnsHelper.cpp",
+ ],
+ static_libs: [
+ "libmodules-utils-build",
+ ],
+ shared_libs: [
+ "libbase",
+ ],
+ export_include_dirs: ["include"],
+ header_abi_checker: {
+ enabled: true,
+ symbol_file: "libcom.android.tethering.dns_helper.map.txt",
+ },
+ sanitize: {
+ cfi: true,
+ },
+ apex_available: ["com.android.tethering"],
+ min_sdk_version: "30",
+}
+
+cc_test {
+ name: "dns_helper_unit_test",
+ defaults: ["netd_defaults"],
+ test_suites: ["general-tests", "mts-tethering"],
+ test_config_template: ":net_native_test_config_template",
+ header_libs: [
+ "bpf_connectivity_headers",
+ ],
+ srcs: [
+ "DnsBpfHelperTest.cpp",
+ ],
+ static_libs: [
+ "libcom.android.tethering.dns_helper",
+ ],
+ shared_libs: [
+ "libbase",
+ "libcutils",
+ ],
+ compile_multilib: "both",
+ multilib: {
+ lib32: {
+ suffix: "32",
+ },
+ lib64: {
+ suffix: "64",
+ },
+ },
+}
diff --git a/DnsResolver/DnsBpfHelper.cpp b/DnsResolver/DnsBpfHelper.cpp
new file mode 100644
index 0000000..37c46ca
--- /dev/null
+++ b/DnsResolver/DnsBpfHelper.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#define LOG_TAG "DnsBpfHelper"
+
+#include "DnsBpfHelper.h"
+
+#include <android-base/logging.h>
+#include <android-modules-utils/sdk_level.h>
+
+namespace android {
+namespace net {
+
+#define RETURN_IF_RESULT_NOT_OK(result) \
+ do { \
+ if (!result.ok()) { \
+ LOG(ERROR) << "L" << __LINE__ << " " << __func__ << ": " << strerror(result.error().code()); \
+ return result.error(); \
+ } \
+ } while (0)
+
+base::Result<void> DnsBpfHelper::init() {
+ if (!android::modules::sdklevel::IsAtLeastT()) {
+ LOG(ERROR) << __func__ << ": Unsupported before Android T.";
+ return base::Error(EOPNOTSUPP);
+ }
+
+ 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));
+ return {};
+}
+
+base::Result<bool> DnsBpfHelper::isUidNetworkingBlocked(uid_t uid, bool metered) {
+ if (is_system_uid(uid)) return false;
+ if (!mConfigurationMap.isValid() || !mUidOwnerMap.isValid()) {
+ LOG(ERROR) << __func__
+ << ": BPF maps are not ready. Forgot to call ADnsHelper_init?";
+ return base::Error(EUNATCH);
+ }
+
+ auto enabledRules = mConfigurationMap.readValue(UID_RULES_CONFIGURATION_KEY);
+ RETURN_IF_RESULT_NOT_OK(enabledRules);
+
+ auto value = mUidOwnerMap.readValue(uid);
+ uint32_t uidRules = value.ok() ? value.value().rule : 0;
+
+ // For doze mode, battery saver, low power standby.
+ if (isBlockedByUidRules(enabledRules.value(), uidRules)) return true;
+
+ // For data saver.
+ if (!metered) return false;
+
+ // The background data setting (PENALTY_BOX_MATCH) and unrestricted data usage setting
+ // (HAPPY_BOX_MATCH) for individual apps override the system wide Data Saver setting.
+ if (uidRules & PENALTY_BOX_MATCH) return true;
+ if (uidRules & HAPPY_BOX_MATCH) return false;
+
+ auto dataSaverSetting = mDataSaverEnabledMap.readValue(DATA_SAVER_ENABLED_KEY);
+ RETURN_IF_RESULT_NOT_OK(dataSaverSetting);
+ return dataSaverSetting.value();
+}
+
+} // namespace net
+} // namespace android
diff --git a/DnsResolver/DnsBpfHelper.h b/DnsResolver/DnsBpfHelper.h
new file mode 100644
index 0000000..f1c3992
--- /dev/null
+++ b/DnsResolver/DnsBpfHelper.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#pragma once
+
+#include <android-base/result.h>
+
+#include "bpf/BpfMap.h"
+#include "netd.h"
+
+namespace android {
+namespace net {
+
+class DnsBpfHelper {
+ public:
+ DnsBpfHelper() = default;
+ DnsBpfHelper(const DnsBpfHelper&) = delete;
+ DnsBpfHelper& operator=(const DnsBpfHelper&) = delete;
+
+ base::Result<void> init();
+ base::Result<bool> isUidNetworkingBlocked(uid_t uid, bool metered);
+
+ private:
+ android::bpf::BpfMapRO<uint32_t, uint32_t> mConfigurationMap;
+ android::bpf::BpfMapRO<uint32_t, UidOwnerValue> mUidOwnerMap;
+ android::bpf::BpfMapRO<uint32_t, bool> mDataSaverEnabledMap;
+
+ // For testing
+ friend class DnsBpfHelperTest;
+};
+
+} // namespace net
+} // namespace android
diff --git a/DnsResolver/DnsBpfHelperTest.cpp b/DnsResolver/DnsBpfHelperTest.cpp
new file mode 100644
index 0000000..67b5b95
--- /dev/null
+++ b/DnsResolver/DnsBpfHelperTest.cpp
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#include <gtest/gtest.h>
+#include <private/android_filesystem_config.h>
+
+#define BPF_MAP_MAKE_VISIBLE_FOR_TESTING
+#include "DnsBpfHelper.h"
+
+using namespace android::bpf; // NOLINT(google-build-using-namespace): exempted
+
+namespace android {
+namespace net {
+
+constexpr int TEST_MAP_SIZE = 2;
+
+#define ASSERT_VALID(x) ASSERT_TRUE((x).isValid())
+
+class DnsBpfHelperTest : public ::testing::Test {
+ protected:
+ DnsBpfHelper mDnsBpfHelper;
+ BpfMap<uint32_t, uint32_t> mFakeConfigurationMap;
+ BpfMap<uint32_t, UidOwnerValue> mFakeUidOwnerMap;
+ BpfMap<uint32_t, bool> mFakeDataSaverEnabledMap;
+
+ void SetUp() {
+ mFakeConfigurationMap.resetMap(BPF_MAP_TYPE_ARRAY, CONFIGURATION_MAP_SIZE);
+ ASSERT_VALID(mFakeConfigurationMap);
+
+ mFakeUidOwnerMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE);
+ ASSERT_VALID(mFakeUidOwnerMap);
+
+ mFakeDataSaverEnabledMap.resetMap(BPF_MAP_TYPE_ARRAY, DATA_SAVER_ENABLED_MAP_SIZE);
+ ASSERT_VALID(mFakeDataSaverEnabledMap);
+
+ mDnsBpfHelper.mConfigurationMap = mFakeConfigurationMap;
+ ASSERT_VALID(mDnsBpfHelper.mConfigurationMap);
+ mDnsBpfHelper.mUidOwnerMap = mFakeUidOwnerMap;
+ ASSERT_VALID(mDnsBpfHelper.mUidOwnerMap);
+ mDnsBpfHelper.mDataSaverEnabledMap = mFakeDataSaverEnabledMap;
+ ASSERT_VALID(mDnsBpfHelper.mDataSaverEnabledMap);
+ }
+
+ void ResetAllMaps() {
+ mDnsBpfHelper.mConfigurationMap.reset();
+ mDnsBpfHelper.mUidOwnerMap.reset();
+ mDnsBpfHelper.mDataSaverEnabledMap.reset();
+ }
+};
+
+TEST_F(DnsBpfHelperTest, IsUidNetworkingBlocked) {
+ struct TestConfig {
+ const uid_t uid;
+ const uint32_t enabledRules;
+ const uint32_t uidRules;
+ const int expectedResult;
+ std::string toString() const {
+ return fmt::format(
+ "uid: {}, enabledRules: {}, uidRules: {}, expectedResult: {}",
+ uid, enabledRules, uidRules, expectedResult);
+ }
+ } testConfigs[] = {
+ // clang-format off
+ // No rule enabled:
+ // uid, enabledRules, uidRules, expectedResult
+ {AID_APP_START, NO_MATCH, NO_MATCH, false},
+
+ // An allowlist rule:
+ {AID_APP_START, NO_MATCH, DOZABLE_MATCH, false},
+ {AID_APP_START, DOZABLE_MATCH, NO_MATCH, true},
+ {AID_APP_START, DOZABLE_MATCH, DOZABLE_MATCH, false},
+ // A denylist rule
+ {AID_APP_START, NO_MATCH, STANDBY_MATCH, false},
+ {AID_APP_START, STANDBY_MATCH, NO_MATCH, false},
+ {AID_APP_START, STANDBY_MATCH, STANDBY_MATCH, true},
+
+ // Multiple rules enabled:
+ // Match only part of the enabled allowlist rules.
+ {AID_APP_START, DOZABLE_MATCH|POWERSAVE_MATCH, DOZABLE_MATCH, true},
+ {AID_APP_START, DOZABLE_MATCH|POWERSAVE_MATCH, POWERSAVE_MATCH, true},
+ // Match all of the enabled allowlist rules.
+ {AID_APP_START, DOZABLE_MATCH|POWERSAVE_MATCH, DOZABLE_MATCH|POWERSAVE_MATCH, false},
+ // Match allowlist.
+ {AID_APP_START, DOZABLE_MATCH|STANDBY_MATCH, DOZABLE_MATCH, false},
+ // Match no rule.
+ {AID_APP_START, DOZABLE_MATCH|STANDBY_MATCH, NO_MATCH, true},
+ {AID_APP_START, DOZABLE_MATCH|POWERSAVE_MATCH, NO_MATCH, true},
+
+ // System UID: always unblocked.
+ {AID_SYSTEM, NO_MATCH, NO_MATCH, false},
+ {AID_SYSTEM, NO_MATCH, DOZABLE_MATCH, false},
+ {AID_SYSTEM, DOZABLE_MATCH, NO_MATCH, false},
+ {AID_SYSTEM, DOZABLE_MATCH, DOZABLE_MATCH, false},
+ {AID_SYSTEM, NO_MATCH, STANDBY_MATCH, false},
+ {AID_SYSTEM, STANDBY_MATCH, NO_MATCH, false},
+ {AID_SYSTEM, STANDBY_MATCH, STANDBY_MATCH, false},
+ {AID_SYSTEM, DOZABLE_MATCH|POWERSAVE_MATCH, DOZABLE_MATCH, false},
+ {AID_SYSTEM, DOZABLE_MATCH|POWERSAVE_MATCH, POWERSAVE_MATCH, false},
+ {AID_SYSTEM, DOZABLE_MATCH|POWERSAVE_MATCH, DOZABLE_MATCH|POWERSAVE_MATCH, false},
+ {AID_SYSTEM, DOZABLE_MATCH|STANDBY_MATCH, DOZABLE_MATCH, false},
+ {AID_SYSTEM, DOZABLE_MATCH|STANDBY_MATCH, NO_MATCH, false},
+ {AID_SYSTEM, DOZABLE_MATCH|POWERSAVE_MATCH, NO_MATCH, false},
+ // clang-format on
+ };
+
+ for (const auto& config : testConfigs) {
+ SCOPED_TRACE(config.toString());
+
+ // Setup maps.
+ EXPECT_RESULT_OK(mFakeConfigurationMap.writeValue(UID_RULES_CONFIGURATION_KEY,
+ config.enabledRules, BPF_EXIST));
+ EXPECT_RESULT_OK(mFakeUidOwnerMap.writeValue(config.uid, {.iif = 0, .rule = config.uidRules},
+ BPF_ANY));
+
+ // Verify the function.
+ auto result = mDnsBpfHelper.isUidNetworkingBlocked(config.uid, /*metered=*/false);
+ EXPECT_TRUE(result.ok());
+ EXPECT_EQ(config.expectedResult, result.value());
+ }
+}
+
+TEST_F(DnsBpfHelperTest, IsUidNetworkingBlocked_uninitialized) {
+ ResetAllMaps();
+
+ auto result = mDnsBpfHelper.isUidNetworkingBlocked(AID_APP_START, /*metered=*/false);
+ EXPECT_FALSE(result.ok());
+ EXPECT_EQ(EUNATCH, result.error().code());
+
+ result = mDnsBpfHelper.isUidNetworkingBlocked(AID_SYSTEM, /*metered=*/false);
+ EXPECT_TRUE(result.ok());
+ EXPECT_FALSE(result.value());
+}
+
+// Verify DataSaver on metered network.
+TEST_F(DnsBpfHelperTest, IsUidNetworkingBlocked_metered) {
+ struct TestConfig {
+ const uint32_t enabledRules; // Settings in configuration map.
+ const bool dataSaverEnabled; // Settings in data saver enabled map.
+ const uint32_t uidRules; // Settings in uid owner map.
+ const int blocked; // Whether the UID is expected to be networking blocked or not.
+ std::string toString() const {
+ return fmt::format(
+ ", enabledRules: {}, dataSaverEnabled: {}, uidRules: {}, expect blocked: {}",
+ enabledRules, dataSaverEnabled, uidRules, blocked);
+ }
+ } testConfigs[]{
+ // clang-format off
+ // enabledRules, dataSaverEnabled, uidRules, blocked
+ {NO_MATCH, false, NO_MATCH, false},
+ {NO_MATCH, false, PENALTY_BOX_MATCH, true},
+ {NO_MATCH, false, HAPPY_BOX_MATCH, false},
+ {NO_MATCH, false, PENALTY_BOX_MATCH|HAPPY_BOX_MATCH, true},
+ {NO_MATCH, true, NO_MATCH, true},
+ {NO_MATCH, true, PENALTY_BOX_MATCH, true},
+ {NO_MATCH, true, HAPPY_BOX_MATCH, false},
+ {NO_MATCH, true, PENALTY_BOX_MATCH|HAPPY_BOX_MATCH, true},
+ {STANDBY_MATCH, false, STANDBY_MATCH, true},
+ {STANDBY_MATCH, false, STANDBY_MATCH|PENALTY_BOX_MATCH, true},
+ {STANDBY_MATCH, false, STANDBY_MATCH|HAPPY_BOX_MATCH, true},
+ {STANDBY_MATCH, false, STANDBY_MATCH|PENALTY_BOX_MATCH|HAPPY_BOX_MATCH, true},
+ {STANDBY_MATCH, true, STANDBY_MATCH, true},
+ {STANDBY_MATCH, true, STANDBY_MATCH|PENALTY_BOX_MATCH, true},
+ {STANDBY_MATCH, true, STANDBY_MATCH|HAPPY_BOX_MATCH, true},
+ {STANDBY_MATCH, true, STANDBY_MATCH|PENALTY_BOX_MATCH|HAPPY_BOX_MATCH, true},
+ // clang-format on
+ };
+
+ for (const auto& config : testConfigs) {
+ SCOPED_TRACE(config.toString());
+
+ // Setup maps.
+ EXPECT_RESULT_OK(mFakeConfigurationMap.writeValue(UID_RULES_CONFIGURATION_KEY,
+ config.enabledRules, BPF_EXIST));
+ EXPECT_RESULT_OK(mFakeDataSaverEnabledMap.writeValue(DATA_SAVER_ENABLED_KEY,
+ config.dataSaverEnabled, BPF_EXIST));
+ EXPECT_RESULT_OK(mFakeUidOwnerMap.writeValue(AID_APP_START, {.iif = 0, .rule = config.uidRules},
+ BPF_ANY));
+
+ // Verify the function.
+ auto result = mDnsBpfHelper.isUidNetworkingBlocked(AID_APP_START, /*metered=*/true);
+ EXPECT_RESULT_OK(result);
+ EXPECT_EQ(config.blocked, result.value());
+ }
+}
+
+} // namespace net
+} // namespace android
diff --git a/DnsResolver/DnsHelper.cpp b/DnsResolver/DnsHelper.cpp
new file mode 100644
index 0000000..3372908
--- /dev/null
+++ b/DnsResolver/DnsHelper.cpp
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#include <errno.h>
+
+#include "DnsBpfHelper.h"
+#include "DnsHelperPublic.h"
+
+static android::net::DnsBpfHelper sDnsBpfHelper;
+
+int ADnsHelper_init() {
+ auto result = sDnsBpfHelper.init();
+ if (!result.ok()) return -result.error().code();
+
+ return 0;
+}
+
+int ADnsHelper_isUidNetworkingBlocked(uid_t uid, bool metered) {
+ auto result = sDnsBpfHelper.isUidNetworkingBlocked(uid, metered);
+ if (!result.ok()) return -result.error().code();
+
+ // bool -> int conversion.
+ return result.value();
+}
diff --git a/DnsResolver/include/DnsHelperPublic.h b/DnsResolver/include/DnsHelperPublic.h
new file mode 100644
index 0000000..7c9fc9e
--- /dev/null
+++ b/DnsResolver/include/DnsHelperPublic.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+__BEGIN_DECLS
+
+/*
+ * Perform any required initialization - including opening any required BPF maps. This function
+ * needs to be called before using other functions of this library.
+ *
+ * Returns 0 on success, a negative POSIX error code (see errno.h) on other failures.
+ */
+int ADnsHelper_init();
+
+/*
+ * The function reads bpf maps and returns whether the given uid has blocked networking or not. The
+ * function is supported starting from Android T.
+ *
+ * |uid| is a Linux/Android UID to be queried. It is a combination of UserID and AppID.
+ * |metered| indicates whether the uid is currently using a billing network.
+ *
+ * Returns 0(false)/1(true) on success, a negative POSIX error code (see errno.h) on other failures.
+ */
+int ADnsHelper_isUidNetworkingBlocked(uid_t uid, bool metered);
+
+__END_DECLS
diff --git a/DnsResolver/libcom.android.tethering.dns_helper.map.txt b/DnsResolver/libcom.android.tethering.dns_helper.map.txt
new file mode 100644
index 0000000..3c965a2
--- /dev/null
+++ b/DnsResolver/libcom.android.tethering.dns_helper.map.txt
@@ -0,0 +1,27 @@
+#
+# Copyright (C) 2023 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.
+#
+
+# This lists the entry points visible to applications that use the
+# libcom.android.tethering.dns_helper library. Other entry points present in
+# the library won't be usable.
+
+LIBCOM_ANDROID_TETHERING_DNS_HELPER {
+ global:
+ ADnsHelper_init; # apex
+ ADnsHelper_isUidNetworkingBlocked; # apex
+ local:
+ *;
+};
diff --git a/OWNERS b/OWNERS
index 649efda..b2176cc 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,3 +1,4 @@
+# Bug component: 31808
set noparent
file:platform/packages/modules/Connectivity:main:/OWNERS_core_networking
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 46308af..520124d 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -131,6 +131,9 @@
"keywords": ["netd-device-kernel-4.9", "netd-device-kernel-4.14"]
},
{
+ "name": "dns_helper_unit_test"
+ },
+ {
"name": "traffic_controller_unit_test",
"keywords": ["netd-device-kernel-4.9", "netd-device-kernel-4.14"]
},
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index e69b872..dd60be7 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -226,6 +226,7 @@
"com.android.tethering",
],
native_shared_libs: [
+ "libcom.android.tethering.dns_helper",
"libnetd_updatable",
],
}
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index cd8eac8..ee44f3c 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -83,6 +83,7 @@
"libandroid_net_connectivity_com_android_net_module_util_jni",
],
native_shared_libs: [
+ "libcom.android.tethering.dns_helper",
"libcom.android.tethering.connectivity_native",
"libnetd_updatable",
],
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index c78893a..79d9a23 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -239,6 +239,7 @@
public static final int CMD_NEW_PREFIX_REQUEST = BASE_IPSERVER + 12;
// request from PrivateAddressCoordinator to restart tethering.
public static final int CMD_NOTIFY_PREFIX_CONFLICT = BASE_IPSERVER + 13;
+ public static final int CMD_SERVICE_FAILED_TO_START = BASE_IPSERVER + 14;
private final State mInitialState;
private final State mLocalHotspotState;
@@ -514,7 +515,12 @@
private void handleError() {
mLastError = TETHER_ERROR_DHCPSERVER_ERROR;
- transitionTo(mInitialState);
+ if (USE_SYNC_SM) {
+ sendMessage(CMD_SERVICE_FAILED_TO_START, TETHER_ERROR_DHCPSERVER_ERROR);
+ } else {
+ sendMessageAtFrontOfQueueToAsyncSM(CMD_SERVICE_FAILED_TO_START,
+ TETHER_ERROR_DHCPSERVER_ERROR);
+ }
}
}
@@ -1156,7 +1162,19 @@
startServingInterface();
if (mLastError != TETHER_ERROR_NO_ERROR) {
- transitionTo(mInitialState);
+ // This will transition to InitialState right away, regardless of whether any
+ // message is already waiting in the StateMachine queue (including maybe some
+ // message to go to InitialState). InitialState will then process any pending
+ // message (and generally ignores them). It is difficult to know for sure whether
+ // this is correct in all cases, but this is equivalent to what IpServer was doing
+ // in previous versions of the mainline module.
+ // TODO : remove sendMessageAtFrontOfQueueToAsyncSM after migrating to the Sync
+ // StateMachine.
+ if (USE_SYNC_SM) {
+ sendSelfMessageToSyncSM(CMD_SERVICE_FAILED_TO_START, mLastError);
+ } else {
+ sendMessageAtFrontOfQueueToAsyncSM(CMD_SERVICE_FAILED_TO_START, mLastError);
+ }
}
if (DBG) Log.d(TAG, getStateString(mDesiredInterfaceState) + " serve " + mIfaceName);
@@ -1246,6 +1264,9 @@
mCallback.requestEnableTethering(mInterfaceType, false /* enabled */);
transitionTo(mWaitingForRestartState);
break;
+ case CMD_SERVICE_FAILED_TO_START:
+ mLog.e("start serving fail, error: " + message.arg1);
+ transitionTo(mInitialState);
default:
return false;
}
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index b7607ef..996ee11 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -1687,6 +1687,8 @@
static final int EVENT_IFACE_UPDATE_LINKPROPERTIES = BASE_MAIN_SM + 7;
// Events from EntitlementManager to choose upstream again.
static final int EVENT_UPSTREAM_PERMISSION_CHANGED = BASE_MAIN_SM + 8;
+ // Internal request from IpServer to enable or disable downstream.
+ static final int EVENT_REQUEST_CHANGE_DOWNSTREAM = BASE_MAIN_SM + 9;
private final State mInitialState;
private final State mTetherModeAliveState;
@@ -2186,6 +2188,12 @@
}
break;
}
+ case EVENT_REQUEST_CHANGE_DOWNSTREAM: {
+ final int tetheringType = message.arg1;
+ final Boolean enabled = (Boolean) message.obj;
+ enableTetheringInternal(tetheringType, enabled, null);
+ break;
+ }
default:
retValue = false;
break;
@@ -2743,7 +2751,8 @@
@Override
public void requestEnableTethering(int tetheringType, boolean enabled) {
- enableTetheringInternal(tetheringType, enabled, null);
+ mTetherMainSM.sendMessage(TetherMainSM.EVENT_REQUEST_CHANGE_DOWNSTREAM,
+ tetheringType, 0, enabled ? Boolean.TRUE : Boolean.FALSE);
}
};
}
diff --git a/Tethering/src/com/android/networkstack/tethering/util/StateMachineShim.java b/Tethering/src/com/android/networkstack/tethering/util/StateMachineShim.java
index fc432f7..078a35f 100644
--- a/Tethering/src/com/android/networkstack/tethering/util/StateMachineShim.java
+++ b/Tethering/src/com/android/networkstack/tethering/util/StateMachineShim.java
@@ -29,7 +29,7 @@
/** A wrapper to decide whether use synchronous state machine for tethering. */
public class StateMachineShim {
// Exactly one of mAsyncSM or mSyncSM is non-null.
- private final StateMachine mAsyncSM;
+ private final AsyncStateMachine mAsyncSM;
private final SyncStateMachine mSyncSM;
/**
@@ -149,6 +149,21 @@
}
/**
+ * Enqueue a message to the front of the queue.
+ * Protected, may only be called by instances of async state machine.
+ *
+ * Message is ignored if state machine has quit.
+ */
+ protected void sendMessageAtFrontOfQueueToAsyncSM(int what, int arg1) {
+ if (mSyncSM != null) {
+ throw new IllegalStateException("sendMessageAtFrontOfQueue can only be used with"
+ + " async SM");
+ }
+
+ mAsyncSM.sendMessageAtFrontOfQueueToAsyncSM(what, arg1);
+ }
+
+ /**
* Send self message.
* This can only be used with sync state machine, so this will throw if using async state
* machine.
@@ -172,5 +187,10 @@
public AsyncStateMachine(final String name, final Looper looper) {
super(name, looper);
}
+
+ /** Enqueue a message to the front of the queue for this state machine. */
+ public void sendMessageAtFrontOfQueueToAsyncSM(int what, int arg1) {
+ sendMessageAtFrontOfQueue(what, arg1);
+ }
}
}
diff --git a/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java b/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
index 0702aa7..1c4a662 100644
--- a/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
+++ b/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
@@ -366,6 +366,11 @@
private volatile Collection<TetheredClient> mClients = null;
private volatile Network mUpstream = null;
+ // The dnsmasq in R might block netd for 20 seconds, which can also block tethering
+ // enable/disable for 20 seconds. To fix this, changing the timeouts from 5 seconds to 30
+ // seconds. See b/289881008.
+ private static final int EXPANDED_TIMEOUT_MS = 30000;
+
MyTetheringEventCallback(TetheringManager tm, String iface) {
this(tm, iface, null);
mAcceptAnyUpstream = true;
@@ -424,13 +429,13 @@
}
public void awaitInterfaceTethered() throws Exception {
- assertTrue("Ethernet not tethered after " + TIMEOUT_MS + "ms",
- mTetheringStartedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ assertTrue("Ethernet not tethered after " + EXPANDED_TIMEOUT_MS + "ms",
+ mTetheringStartedLatch.await(EXPANDED_TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
public void awaitInterfaceLocalOnly() throws Exception {
- assertTrue("Ethernet not local-only after " + TIMEOUT_MS + "ms",
- mLocalOnlyStartedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ assertTrue("Ethernet not local-only after " + EXPANDED_TIMEOUT_MS + "ms",
+ mLocalOnlyStartedLatch.await(EXPANDED_TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
// Used to check if the callback has registered. When the callback is registered,
@@ -444,8 +449,9 @@
}
public void awaitCallbackRegistered() throws Exception {
- if (!mCallbackRegisteredLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
- fail("Did not receive callback registered signal after " + TIMEOUT_MS + "ms");
+ if (!mCallbackRegisteredLatch.await(EXPANDED_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ fail("Did not receive callback registered signal after " + EXPANDED_TIMEOUT_MS
+ + "ms");
}
}
@@ -457,11 +463,11 @@
if (!mInterfaceWasTethered && !mInterfaceWasLocalOnly) return;
if (mInterfaceWasTethered) {
- assertTrue(mIface + " not untethered after " + TIMEOUT_MS + "ms",
- mTetheringStoppedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ assertTrue(mIface + " not untethered after " + EXPANDED_TIMEOUT_MS + "ms",
+ mTetheringStoppedLatch.await(EXPANDED_TIMEOUT_MS, TimeUnit.MILLISECONDS));
} else if (mInterfaceWasLocalOnly) {
- assertTrue(mIface + " not untethered after " + TIMEOUT_MS + "ms",
- mLocalOnlyStoppedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ assertTrue(mIface + " not untethered after " + EXPANDED_TIMEOUT_MS + "ms",
+ mLocalOnlyStoppedLatch.await(EXPANDED_TIMEOUT_MS, TimeUnit.MILLISECONDS));
} else {
fail(mIface + " cannot be both tethered and local-only. Update this test class.");
}
@@ -488,8 +494,9 @@
}
public Collection<TetheredClient> awaitClientConnected() throws Exception {
- assertTrue("Did not receive client connected callback after " + TIMEOUT_MS + "ms",
- mClientConnectedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ assertTrue("Did not receive client connected callback after "
+ + EXPANDED_TIMEOUT_MS + "ms",
+ mClientConnectedLatch.await(EXPANDED_TIMEOUT_MS, TimeUnit.MILLISECONDS));
return mClients;
}
@@ -506,10 +513,10 @@
}
public Network awaitUpstreamChanged(boolean throwTimeoutException) throws Exception {
- if (!mUpstreamLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ if (!mUpstreamLatch.await(EXPANDED_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
final String errorMessage = "Did not receive upstream "
+ (mAcceptAnyUpstream ? "any" : mExpectedUpstream)
- + " callback after " + TIMEOUT_MS + "ms";
+ + " callback after " + EXPANDED_TIMEOUT_MS + "ms";
if (throwTimeoutException) {
throw new TimeoutException(errorMessage);
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index 076fde3..4949eaa 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -47,7 +47,7 @@
import android.util.Log;
import androidx.annotation.NonNull;
-import androidx.test.filters.MediumTest;
+import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.net.module.util.Ipv6Utils;
@@ -79,7 +79,7 @@
import java.util.concurrent.TimeoutException;
@RunWith(AndroidJUnit4.class)
-@MediumTest
+@LargeTest
public class EthernetTetheringTest extends EthernetTetheringTestBase {
@Rule
public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index bc970e4..98b624b 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -232,8 +232,8 @@
@Captor private ArgumentCaptor<DhcpServingParamsParcel> mDhcpParamsCaptor;
- private final TestLooper mLooper = new TestLooper();
- private final Handler mHandler = new Handler(mLooper.getLooper());
+ private TestLooper mLooper;
+ private Handler mHandler;
private final ArgumentCaptor<LinkProperties> mLinkPropertiesCaptor =
ArgumentCaptor.forClass(LinkProperties.class);
private IpServer mIpServer;
@@ -270,12 +270,7 @@
when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(usingBpfOffload);
when(mTetherConfig.useLegacyDhcpServer()).thenReturn(usingLegacyDhcp);
when(mTetherConfig.getP2pLeasesSubnetPrefixLength()).thenReturn(P2P_SUBNET_PREFIX_LENGTH);
- // Recreate mBpfCoordinator again here because mTetherConfig has changed
- mBpfCoordinator = spy(new BpfCoordinator(mBpfDeps));
- mIpServer = new IpServer(
- IFACE_NAME, mHandler, interfaceType, mSharedLog, mNetd, mBpfCoordinator,
- mRoutingCoordinatorManager, mCallback, mTetherConfig, mAddressCoordinator,
- mTetheringMetrics, mDependencies);
+ mIpServer = createIpServer(interfaceType);
mIpServer.start();
mNeighborEventConsumer = neighborCaptor.getValue();
@@ -360,6 +355,14 @@
return null;
}).when(mRoutingCoordinatorManager.value).removeInterfaceForward(any(), any());
}
+
+ setUpDhcpServer();
+ }
+
+ // In order to interact with syncSM from the test, IpServer must be created in test thread.
+ private IpServer createIpServer(final int interfaceType) {
+ mLooper = new TestLooper();
+ mHandler = new Handler(mLooper.getLooper());
mBpfDeps = new BpfCoordinator.Dependencies() {
@NonNull
public Handler getHandler() {
@@ -432,18 +435,19 @@
return mBpfErrorMap;
}
};
- mBpfCoordinator = spy(new BpfCoordinator(mBpfDeps));
- setUpDhcpServer();
+ mBpfCoordinator = spy(new BpfCoordinator(mBpfDeps));
+ return new IpServer(IFACE_NAME, mHandler, interfaceType, mSharedLog, mNetd, mBpfCoordinator,
+ mRoutingCoordinatorManager, mCallback, mTetherConfig, mAddressCoordinator,
+ mTetheringMetrics, mDependencies);
+
}
@Test
- public void startsOutAvailable() {
+ public void startsOutAvailable() throws Exception {
when(mDependencies.getIpNeighborMonitor(any(), any(), any()))
.thenReturn(mIpNeighborMonitor);
- mIpServer = new IpServer(IFACE_NAME, mHandler, TETHERING_BLUETOOTH, mSharedLog,
- mNetd, mBpfCoordinator, mRoutingCoordinatorManager, mCallback, mTetherConfig,
- mAddressCoordinator, mTetheringMetrics, mDependencies);
+ mIpServer = createIpServer(TETHERING_BLUETOOTH);
mIpServer.start();
mLooper.dispatchAll();
verify(mCallback).updateInterfaceState(
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index ba39f22..6eba590 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -302,7 +302,7 @@
// Like so many Android system APIs, these cannot be mocked because it is marked final.
// We have to use the real versions.
private final PersistableBundle mCarrierConfig = new PersistableBundle();
- private final TestLooper mLooper = new TestLooper();
+ private TestLooper mLooper;
private Vector<Intent> mIntents;
private BroadcastInterceptingContext mServiceContext;
@@ -680,7 +680,14 @@
mCm = spy(new TestConnectivityManager(mServiceContext, mock(IConnectivityManager.class)));
- mTethering = makeTethering();
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)).thenReturn(true);
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT)).thenReturn(true);
+ }
+
+ // In order to interact with syncSM from the test, tethering must be created in test thread.
+ private void initTetheringOnTestThread() throws Exception {
+ mLooper = new TestLooper();
+ mTethering = new Tethering(mTetheringDependencies);
verify(mStatsManager, times(1)).registerNetworkStatsProvider(anyString(), any());
verify(mNetd).registerUnsolicitedEventListener(any());
verifyDefaultNetworkRequestFiled();
@@ -704,9 +711,6 @@
localOnlyCallbackCaptor.capture());
mLocalOnlyHotspotCallback = localOnlyCallbackCaptor.getValue();
}
-
- when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)).thenReturn(true);
- when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT)).thenReturn(true);
}
private void setTetheringSupported(final boolean supported) {
@@ -738,10 +742,6 @@
doReturn(upstreamState).when(mUpstreamNetworkMonitor).selectPreferredUpstreamType(any());
}
- private Tethering makeTethering() {
- return new Tethering(mTetheringDependencies);
- }
-
private TetheringRequestParcel createTetheringRequestParcel(final int type) {
return createTetheringRequestParcel(type, null, null, false, CONNECTIVITY_SCOPE_GLOBAL);
}
@@ -885,6 +885,7 @@
public void failingLocalOnlyHotspotLegacyApBroadcast(
boolean emulateInterfaceStatusChanged) throws Exception {
+ initTetheringOnTestThread();
// Emulate externally-visible WifiManager effects, causing the
// per-interface state machine to start up, and telling us that
// hotspot mode is to be started.
@@ -936,6 +937,7 @@
@Test
public void testUsbConfiguredBroadcastStartsTethering() throws Exception {
+ initTetheringOnTestThread();
UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState();
initTetheringUpstream(upstreamState);
prepareUsbTethering();
@@ -1012,6 +1014,7 @@
public void workingLocalOnlyHotspotEnrichedApBroadcast(
boolean emulateInterfaceStatusChanged) throws Exception {
+ initTetheringOnTestThread();
// Emulate externally-visible WifiManager effects, causing the
// per-interface state machine to start up, and telling us that
// hotspot mode is to be started.
@@ -1075,6 +1078,7 @@
@Test
public void workingMobileUsbTethering_IPv4() throws Exception {
+ initTetheringOnTestThread();
UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState();
runUsbTethering(upstreamState);
@@ -1089,7 +1093,8 @@
}
@Test
- public void workingMobileUsbTethering_IPv4LegacyDhcp() {
+ public void workingMobileUsbTethering_IPv4LegacyDhcp() throws Exception {
+ initTetheringOnTestThread();
when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
true);
sendConfigurationChanged();
@@ -1102,6 +1107,7 @@
@Test
public void workingMobileUsbTethering_IPv6() throws Exception {
+ initTetheringOnTestThread();
UpstreamNetworkState upstreamState = buildMobileIPv6UpstreamState();
runUsbTethering(upstreamState);
@@ -1117,6 +1123,7 @@
@Test
public void workingMobileUsbTethering_DualStack() throws Exception {
+ initTetheringOnTestThread();
UpstreamNetworkState upstreamState = buildMobileDualStackUpstreamState();
runUsbTethering(upstreamState);
@@ -1134,6 +1141,7 @@
@Test
public void workingMobileUsbTethering_MultipleUpstreams() throws Exception {
+ initTetheringOnTestThread();
UpstreamNetworkState upstreamState = buildMobile464xlatUpstreamState();
runUsbTethering(upstreamState);
@@ -1153,6 +1161,7 @@
@Test
public void workingMobileUsbTethering_v6Then464xlat() throws Exception {
+ initTetheringOnTestThread();
when(mResources.getInteger(R.integer.config_tether_usb_functions)).thenReturn(
TetheringConfiguration.TETHER_USB_NCM_FUNCTION);
when(mResources.getStringArray(R.array.config_tether_usb_regexs))
@@ -1194,6 +1203,7 @@
@Test
public void configTetherUpstreamAutomaticIgnoresConfigTetherUpstreamTypes() throws Exception {
+ initTetheringOnTestThread();
when(mResources.getBoolean(R.bool.config_tether_upstream_automatic)).thenReturn(true);
sendConfigurationChanged();
@@ -1242,6 +1252,7 @@
}
private void verifyAutomaticUpstreamSelection(boolean configAutomatic) throws Exception {
+ initTetheringOnTestThread();
TestNetworkAgent mobile = new TestNetworkAgent(mCm, buildMobileDualStackUpstreamState());
TestNetworkAgent wifi = new TestNetworkAgent(mCm, buildWifiUpstreamState());
InOrder inOrder = inOrder(mCm, mUpstreamNetworkMonitor);
@@ -1341,6 +1352,7 @@
@Test
@IgnoreAfter(Build.VERSION_CODES.TIRAMISU)
public void testLegacyUpstreamSelection() throws Exception {
+ initTetheringOnTestThread();
TestNetworkAgent mobile = new TestNetworkAgent(mCm, buildMobileDualStackUpstreamState());
TestNetworkAgent wifi = new TestNetworkAgent(mCm, buildWifiUpstreamState());
InOrder inOrder = inOrder(mCm, mUpstreamNetworkMonitor);
@@ -1491,6 +1503,7 @@
// +-------+-------+-------+-------+-------+
//
private void verifyChooseDunUpstreamByAutomaticMode(boolean configAutomatic) throws Exception {
+ initTetheringOnTestThread();
// Enable automatic upstream selection.
TestNetworkAgent mobile = new TestNetworkAgent(mCm, buildMobileDualStackUpstreamState());
TestNetworkAgent wifi = new TestNetworkAgent(mCm, buildWifiUpstreamState());
@@ -1551,6 +1564,7 @@
//
@Test
public void testChooseDunUpstreamByAutomaticMode_defaultNetworkWifi() throws Exception {
+ initTetheringOnTestThread();
TestNetworkAgent mobile = new TestNetworkAgent(mCm, buildMobileDualStackUpstreamState());
TestNetworkAgent wifi = new TestNetworkAgent(mCm, buildWifiUpstreamState());
TestNetworkAgent dun = new TestNetworkAgent(mCm, buildDunUpstreamState());
@@ -1602,6 +1616,7 @@
//
@Test
public void testChooseDunUpstreamByAutomaticMode_loseDefaultNetworkWifi() throws Exception {
+ initTetheringOnTestThread();
TestNetworkAgent wifi = new TestNetworkAgent(mCm, buildWifiUpstreamState());
TestNetworkAgent dun = new TestNetworkAgent(mCm, buildDunUpstreamState());
final InOrder inOrder = inOrder(mCm, mUpstreamNetworkMonitor);
@@ -1643,6 +1658,7 @@
//
@Test
public void testChooseDunUpstreamByAutomaticMode_defaultNetworkCell() throws Exception {
+ initTetheringOnTestThread();
TestNetworkAgent mobile = new TestNetworkAgent(mCm, buildMobileDualStackUpstreamState());
TestNetworkAgent dun = new TestNetworkAgent(mCm, buildDunUpstreamState());
final InOrder inOrder = inOrder(mCm, mUpstreamNetworkMonitor);
@@ -1687,6 +1703,7 @@
//
@Test
public void testChooseDunUpstreamByAutomaticMode_loseAndRegainDun() throws Exception {
+ initTetheringOnTestThread();
TestNetworkAgent dun = new TestNetworkAgent(mCm, buildDunUpstreamState());
final InOrder inOrder = inOrder(mCm, mUpstreamNetworkMonitor);
setupDunUpstreamTest(true /* configAutomatic */, inOrder);
@@ -1728,6 +1745,7 @@
@Test
public void testChooseDunUpstreamByAutomaticMode_switchDefaultFromWifiToCell()
throws Exception {
+ initTetheringOnTestThread();
TestNetworkAgent mobile = new TestNetworkAgent(mCm, buildMobileDualStackUpstreamState());
TestNetworkAgent wifi = new TestNetworkAgent(mCm, buildWifiUpstreamState());
TestNetworkAgent dun = new TestNetworkAgent(mCm, buildDunUpstreamState());
@@ -1765,6 +1783,7 @@
@Test
@IgnoreAfter(Build.VERSION_CODES.TIRAMISU)
public void testChooseDunUpstreamByLegacyMode() throws Exception {
+ initTetheringOnTestThread();
// Enable Legacy upstream selection.
TestNetworkAgent mobile = new TestNetworkAgent(mCm, buildMobileDualStackUpstreamState());
TestNetworkAgent wifi = new TestNetworkAgent(mCm, buildWifiUpstreamState());
@@ -1857,6 +1876,7 @@
@Test
public void workingNcmTethering() throws Exception {
+ initTetheringOnTestThread();
runNcmTethering();
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
@@ -1864,7 +1884,8 @@
}
@Test
- public void workingNcmTethering_LegacyDhcp() {
+ public void workingNcmTethering_LegacyDhcp() throws Exception {
+ initTetheringOnTestThread();
when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
true);
sendConfigurationChanged();
@@ -1886,6 +1907,7 @@
// TODO: Test with and without interfaceStatusChanged().
@Test
public void failingWifiTetheringLegacyApBroadcast() throws Exception {
+ initTetheringOnTestThread();
when(mWifiManager.startTetheredHotspot(any(SoftApConfiguration.class))).thenReturn(true);
// Emulate pressing the WiFi tethering button.
@@ -1914,6 +1936,7 @@
// TODO: Test with and without interfaceStatusChanged().
@Test
public void workingWifiTetheringEnrichedApBroadcast() throws Exception {
+ initTetheringOnTestThread();
when(mWifiManager.startTetheredHotspot(any(SoftApConfiguration.class))).thenReturn(true);
// Emulate pressing the WiFi tethering button.
@@ -1962,6 +1985,7 @@
// TODO: Test with and without interfaceStatusChanged().
@Test
public void failureEnablingIpForwarding() throws Exception {
+ initTetheringOnTestThread();
when(mWifiManager.startTetheredHotspot(any(SoftApConfiguration.class))).thenReturn(true);
doThrow(new RemoteException()).when(mNetd).ipfwdEnableForwarding(TETHERING_NAME);
@@ -2109,7 +2133,8 @@
}
@Test
- public void testUntetherUsbWhenRestrictionIsOn() {
+ public void testUntetherUsbWhenRestrictionIsOn() throws Exception {
+ initTetheringOnTestThread();
// Start usb tethering and check that usb interface is tethered.
final UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState();
runUsbTethering(upstreamState);
@@ -2286,6 +2311,7 @@
@Test
public void testRegisterTetheringEventCallback() throws Exception {
+ initTetheringOnTestThread();
TestTetheringEventCallback callback = new TestTetheringEventCallback();
TestTetheringEventCallback callback2 = new TestTetheringEventCallback();
final TetheringInterface wifiIface = new TetheringInterface(
@@ -2350,6 +2376,7 @@
@Test
public void testReportFailCallbackIfOffloadNotSupported() throws Exception {
+ initTetheringOnTestThread();
final UpstreamNetworkState upstreamState = buildMobileDualStackUpstreamState();
TestTetheringEventCallback callback = new TestTetheringEventCallback();
mTethering.registerTetheringEventCallback(callback);
@@ -2389,6 +2416,7 @@
@Test
public void testMultiSimAware() throws Exception {
+ initTetheringOnTestThread();
final TetheringConfiguration initailConfig = mTethering.getTetheringConfiguration();
assertEquals(INVALID_SUBSCRIPTION_ID, initailConfig.activeDataSubId);
@@ -2401,6 +2429,7 @@
@Test
public void testNoDuplicatedEthernetRequest() throws Exception {
+ initTetheringOnTestThread();
final TetheredInterfaceRequest mockRequest = mock(TetheredInterfaceRequest.class);
when(mEm.requestTetheredInterface(any(), any())).thenReturn(mockRequest);
mTethering.startTethering(createTetheringRequestParcel(TETHERING_ETHERNET), TEST_CALLER_PKG,
@@ -2421,6 +2450,7 @@
private void workingWifiP2pGroupOwner(
boolean emulateInterfaceStatusChanged) throws Exception {
+ initTetheringOnTestThread();
if (emulateInterfaceStatusChanged) {
mTethering.interfaceStatusChanged(TEST_P2P_IFNAME, true);
}
@@ -2460,6 +2490,7 @@
private void workingWifiP2pGroupClient(
boolean emulateInterfaceStatusChanged) throws Exception {
+ initTetheringOnTestThread();
if (emulateInterfaceStatusChanged) {
mTethering.interfaceStatusChanged(TEST_P2P_IFNAME, true);
}
@@ -2500,6 +2531,7 @@
private void workingWifiP2pGroupOwnerLegacyMode(
boolean emulateInterfaceStatusChanged) throws Exception {
+ initTetheringOnTestThread();
// change to legacy mode and update tethering information by chaning SIM
when(mResources.getStringArray(R.array.config_tether_wifi_p2p_regexs))
.thenReturn(new String[]{});
@@ -2549,7 +2581,8 @@
}
@Test
- public void testDataSaverChanged() {
+ public void testDataSaverChanged() throws Exception {
+ initTetheringOnTestThread();
// Start Tethering.
final UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState();
runUsbTethering(upstreamState);
@@ -2604,6 +2637,7 @@
@Test
public void testMultipleStartTethering() throws Exception {
+ initTetheringOnTestThread();
final LinkAddress serverLinkAddr = new LinkAddress("192.168.20.1/24");
final LinkAddress clientLinkAddr = new LinkAddress("192.168.20.42/24");
final String serverAddr = "192.168.20.1";
@@ -2647,6 +2681,7 @@
@Test
public void testRequestStaticIp() throws Exception {
+ initTetheringOnTestThread();
when(mResources.getInteger(R.integer.config_tether_usb_functions)).thenReturn(
TetheringConfiguration.TETHER_USB_NCM_FUNCTION);
when(mResources.getStringArray(R.array.config_tether_usb_regexs))
@@ -2676,7 +2711,8 @@
}
@Test
- public void testUpstreamNetworkChanged() {
+ public void testUpstreamNetworkChanged() throws Exception {
+ initTetheringOnTestThread();
final Tethering.TetherMainSM stateMachine = (Tethering.TetherMainSM)
mTetheringDependencies.mUpstreamNetworkMonitorSM;
final InOrder inOrder = inOrder(mNotificationUpdater);
@@ -2718,7 +2754,8 @@
}
@Test
- public void testUpstreamCapabilitiesChanged() {
+ public void testUpstreamCapabilitiesChanged() throws Exception {
+ initTetheringOnTestThread();
final Tethering.TetherMainSM stateMachine = (Tethering.TetherMainSM)
mTetheringDependencies.mUpstreamNetworkMonitorSM;
final InOrder inOrder = inOrder(mNotificationUpdater);
@@ -2753,6 +2790,7 @@
@Test
public void testUpstreamCapabilitiesChanged_startStopTethering() throws Exception {
+ initTetheringOnTestThread();
final TestNetworkAgent wifi = new TestNetworkAgent(mCm, buildWifiUpstreamState());
// Start USB tethering with no current upstream.
@@ -2774,6 +2812,7 @@
@Test
public void testDumpTetheringLog() throws Exception {
+ initTetheringOnTestThread();
final FileDescriptor mockFd = mock(FileDescriptor.class);
final PrintWriter mockPw = mock(PrintWriter.class);
runUsbTethering(null);
@@ -2787,6 +2826,7 @@
@Test
public void testExemptFromEntitlementCheck() throws Exception {
+ initTetheringOnTestThread();
setupForRequiredProvisioning();
final TetheringRequestParcel wifiNotExemptRequest =
createTetheringRequestParcel(TETHERING_WIFI, null, null, false,
@@ -2877,31 +2917,42 @@
@Test
public void testHandleIpConflict() throws Exception {
+ initTetheringOnTestThread();
final Network wifiNetwork = new Network(200);
final Network[] allNetworks = { wifiNetwork };
doReturn(allNetworks).when(mCm).getAllNetworks();
+ InOrder inOrder = inOrder(mUsbManager, mNetd);
runUsbTethering(null);
+
+ inOrder.verify(mNetd).tetherInterfaceAdd(TEST_RNDIS_IFNAME);
+
final ArgumentCaptor<InterfaceConfigurationParcel> ifaceConfigCaptor =
ArgumentCaptor.forClass(InterfaceConfigurationParcel.class);
verify(mNetd).interfaceSetCfg(ifaceConfigCaptor.capture());
final String ipv4Address = ifaceConfigCaptor.getValue().ipv4Addr;
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
any(), any());
- reset(mUsbManager);
// Cause a prefix conflict by assigning a /30 out of the downstream's /24 to the upstream.
updateV4Upstream(new LinkAddress(InetAddresses.parseNumericAddress(ipv4Address), 30),
wifiNetwork, TEST_WIFI_IFNAME, TRANSPORT_WIFI);
// verify turn off usb tethering
- verify(mUsbManager).setCurrentFunctions(UsbManager.FUNCTION_NONE);
+ inOrder.verify(mUsbManager).setCurrentFunctions(UsbManager.FUNCTION_NONE);
sendUsbBroadcast(true, true, -1 /* function */);
mLooper.dispatchAll();
+ inOrder.verify(mNetd).tetherInterfaceRemove(TEST_RNDIS_IFNAME);
+
// verify restart usb tethering
- verify(mUsbManager).setCurrentFunctions(UsbManager.FUNCTION_RNDIS);
+ inOrder.verify(mUsbManager).setCurrentFunctions(UsbManager.FUNCTION_RNDIS);
+
+ sendUsbBroadcast(true, true, TETHER_USB_RNDIS_FUNCTION);
+ mLooper.dispatchAll();
+ inOrder.verify(mNetd).tetherInterfaceAdd(TEST_RNDIS_IFNAME);
}
@Test
public void testNoAddressAvailable() throws Exception {
+ initTetheringOnTestThread();
final Network wifiNetwork = new Network(200);
final Network btNetwork = new Network(201);
final Network mobileNetwork = new Network(202);
@@ -2963,6 +3014,7 @@
@Test
public void testProvisioningNeededButUnavailable() throws Exception {
+ initTetheringOnTestThread();
assertTrue(mTethering.isTetheringSupported());
verify(mPackageManager, never()).getPackageInfo(PROVISIONING_APP_NAME[0], GET_ACTIVITIES);
@@ -2980,6 +3032,7 @@
@Test
public void testUpdateConnectedClients() throws Exception {
+ initTetheringOnTestThread();
TestTetheringEventCallback callback = new TestTetheringEventCallback();
runAsShell(NETWORK_SETTINGS, () -> {
mTethering.registerTetheringEventCallback(callback);
@@ -3029,6 +3082,7 @@
@Test
@IgnoreUpTo(Build.VERSION_CODES.S_V2)
public void testUpdateConnectedClientsForLocalOnlyHotspot() throws Exception {
+ initTetheringOnTestThread();
TestTetheringEventCallback callback = new TestTetheringEventCallback();
runAsShell(NETWORK_SETTINGS, () -> {
mTethering.registerTetheringEventCallback(callback);
@@ -3061,6 +3115,7 @@
@Test
@IgnoreUpTo(Build.VERSION_CODES.S_V2)
public void testConnectedClientsForSapAndLohsConcurrency() throws Exception {
+ initTetheringOnTestThread();
TestTetheringEventCallback callback = new TestTetheringEventCallback();
runAsShell(NETWORK_SETTINGS, () -> {
mTethering.registerTetheringEventCallback(callback);
@@ -3186,6 +3241,7 @@
@Test
public void testBluetoothTethering() throws Exception {
+ initTetheringOnTestThread();
// Switch to @IgnoreUpTo(Build.VERSION_CODES.S_V2) when it is available for AOSP.
assumeTrue(isAtLeastT());
@@ -3222,6 +3278,7 @@
@Test
public void testBluetoothTetheringBeforeT() throws Exception {
+ initTetheringOnTestThread();
// Switch to @IgnoreAfter(Build.VERSION_CODES.S_V2) when it is available for AOSP.
assumeFalse(isAtLeastT());
@@ -3269,6 +3326,7 @@
@Test
public void testBluetoothServiceDisconnects() throws Exception {
+ initTetheringOnTestThread();
final ResultListener result = new ResultListener(TETHER_ERROR_NO_ERROR);
mockBluetoothSettings(true /* bluetoothOn */, true /* tetheringOn */);
mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH),
@@ -3423,6 +3481,7 @@
@Test
public void testUsbFunctionConfigurationChange() throws Exception {
+ initTetheringOnTestThread();
// Run TETHERING_NCM.
runNcmTethering();
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
@@ -3481,6 +3540,7 @@
@Test
public void testTetheringSupported() throws Exception {
+ initTetheringOnTestThread();
final ArraySet<Integer> expectedTypes = getAllSupportedTetheringTypes();
// Check tethering is supported after initialization.
TestTetheringEventCallback callback = new TestTetheringEventCallback();
@@ -3553,6 +3613,7 @@
@Test
public void testIpv4AddressForSapAndLohsConcurrency() throws Exception {
+ initTetheringOnTestThread();
mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/util/StateMachineShimTest.kt b/Tethering/tests/unit/src/com/android/networkstack/tethering/util/StateMachineShimTest.kt
index 2c4df76..f8e98e3 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/util/StateMachineShimTest.kt
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/util/StateMachineShimTest.kt
@@ -78,6 +78,10 @@
shimUsingSyncSM.sendMessageDelayedToAsyncSM(what, 1000 /* delayMillis */)
}
+ assertFailsWith(IllegalStateException::class) {
+ shimUsingSyncSM.sendMessageAtFrontOfQueueToAsyncSM(what, arg1)
+ }
+
shimUsingSyncSM.sendSelfMessageToSyncSM(what, obj)
inOrder.verify(mSyncSM).sendSelfMessage(what, 0, 0, obj)
@@ -119,6 +123,9 @@
shimUsingAsyncSM.sendMessageDelayedToAsyncSM(what, 1000 /* delayMillis */)
inOrder.verify(mAsyncSM).sendMessageDelayed(what, 1000)
+ shimUsingAsyncSM.sendMessageAtFrontOfQueueToAsyncSM(what, arg1)
+ inOrder.verify(mAsyncSM).sendMessageAtFrontOfQueueToAsyncSM(what, arg1)
+
assertFailsWith(IllegalStateException::class) {
shimUsingAsyncSM.sendSelfMessageToSyncSM(what, obj)
}
diff --git a/bpf_progs/Android.bp b/bpf_progs/Android.bp
index b3f8ed6..cdf47e7 100644
--- a/bpf_progs/Android.bp
+++ b/bpf_progs/Android.bp
@@ -45,6 +45,7 @@
"com.android.tethering",
],
visibility: [
+ "//packages/modules/Connectivity/DnsResolver",
"//packages/modules/Connectivity/netd",
"//packages/modules/Connectivity/service",
"//packages/modules/Connectivity/service/native/libs/libclat",
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
index 9017976..f223dd1 100644
--- a/bpf_progs/netd.c
+++ b/bpf_progs/netd.c
@@ -92,7 +92,7 @@
DEFINE_BPF_MAP_RO_NETD(stats_map_A, HASH, StatsKey, StatsValue, STATS_MAP_SIZE)
DEFINE_BPF_MAP_RO_NETD(stats_map_B, HASH, StatsKey, StatsValue, STATS_MAP_SIZE)
DEFINE_BPF_MAP_NO_NETD(iface_stats_map, HASH, uint32_t, StatsValue, IFACE_STATS_MAP_SIZE)
-DEFINE_BPF_MAP_NO_NETD(uid_owner_map, HASH, uint32_t, UidOwnerValue, UID_OWNER_MAP_SIZE)
+DEFINE_BPF_MAP_RO_NETD(uid_owner_map, HASH, uint32_t, UidOwnerValue, UID_OWNER_MAP_SIZE)
DEFINE_BPF_MAP_RO_NETD(uid_permission_map, HASH, uint32_t, uint8_t, UID_OWNER_MAP_SIZE)
DEFINE_BPF_MAP_NO_NETD(ingress_discard_map, HASH, IngressDiscardKey, IngressDiscardValue,
INGRESS_DISCARD_MAP_SIZE)
@@ -112,6 +112,9 @@
BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, LOAD_ON_ENG,
LOAD_ON_USER, LOAD_ON_USERDEBUG);
+DEFINE_BPF_MAP_RO_NETD(data_saver_enabled_map, ARRAY, uint32_t, bool,
+ DATA_SAVER_ENABLED_MAP_SIZE)
+
// iptables xt_bpf programs need to be usable by both netd and netutils_wrappers
// selinux contexts, because even non-xt_bpf iptables mutations are implemented as
// a full table dump, followed by an update in userspace, and then a reload into the kernel,
@@ -142,12 +145,6 @@
BPFLOADER_MIN_VER, BPFLOADER_MAX_VER, MANDATORY, \
"fs_bpf_net_shared", "", LOAD_ON_ENG, LOAD_ON_USER, LOAD_ON_USERDEBUG)
-static __always_inline int is_system_uid(uint32_t uid) {
- // MIN_SYSTEM_UID is AID_ROOT == 0, so uint32_t is *always* >= 0
- // MAX_SYSTEM_UID is AID_NOBODY == 9999, while AID_APP_START == 10000
- return (uid < AID_APP_START);
-}
-
/*
* Note: this blindly assumes an MTU of 1500, and that packets > MTU are always TCP,
* and that TCP is using the Linux default settings with TCP timestamp option enabled
diff --git a/bpf_progs/netd.h b/bpf_progs/netd.h
index 4958040..d1fc58d 100644
--- a/bpf_progs/netd.h
+++ b/bpf_progs/netd.h
@@ -16,6 +16,7 @@
#pragma once
+#include <cutils/android_filesystem_config.h>
#include <linux/if.h>
#include <linux/if_ether.h>
#include <linux/in.h>
@@ -125,6 +126,7 @@
static const int UID_OWNER_MAP_SIZE = 4000;
static const int INGRESS_DISCARD_MAP_SIZE = 100;
static const int PACKET_TRACE_BUF_SIZE = 32 * 1024;
+static const int DATA_SAVER_ENABLED_MAP_SIZE = 1;
#ifdef __cplusplus
@@ -171,6 +173,7 @@
#define INGRESS_DISCARD_MAP_PATH BPF_NETD_PATH "map_netd_ingress_discard_map"
#define PACKET_TRACE_RINGBUF_PATH BPF_NETD_PATH "map_netd_packet_trace_ringbuf"
#define PACKET_TRACE_ENABLED_MAP_PATH BPF_NETD_PATH "map_netd_packet_trace_enabled_map"
+#define DATA_SAVER_ENABLED_MAP_PATH BPF_NETD_PATH "map_netd_data_saver_enabled_map"
#endif // __cplusplus
@@ -233,6 +236,8 @@
#define UID_RULES_CONFIGURATION_KEY 0
// Entry in the configuration map that stores which stats map is currently in use.
#define CURRENT_STATS_MAP_CONFIGURATION_KEY 1
+// Entry in the data saver enabled map that stores whether data saver is enabled or not.
+#define DATA_SAVER_ENABLED_KEY 0
#undef STRUCT_SIZE
@@ -249,3 +254,9 @@
static inline bool isBlockedByUidRules(BpfConfig enabledRules, uint32_t uidRules) {
return enabledRules & (DROP_IF_SET | DROP_IF_UNSET) & (uidRules ^ DROP_IF_UNSET);
}
+
+static inline bool is_system_uid(uint32_t uid) {
+ // MIN_SYSTEM_UID is AID_ROOT == 0, so uint32_t is *always* >= 0
+ // MAX_SYSTEM_UID is AID_NOBODY == 9999, while AID_APP_START == 10000
+ return (uid < AID_APP_START);
+}
diff --git a/common/flags.aconfig b/common/flags.aconfig
index 7235202..0c46b48 100644
--- a/common/flags.aconfig
+++ b/common/flags.aconfig
@@ -27,3 +27,10 @@
description: "Set data saver through ConnectivityManager API"
bug: "297836825"
}
+
+flag {
+ name: "support_is_uid_networking_blocked"
+ namespace: "android_core_networking"
+ description: "This flag controls whether isUidNetworkingBlocked is supported"
+ bug: "297836825"
+}
diff --git a/common/src/com/android/net/module/util/bpf/IngressDiscardKey.java b/common/src/com/android/net/module/util/bpf/IngressDiscardKey.java
new file mode 100644
index 0000000..9fefb52
--- /dev/null
+++ b/common/src/com/android/net/module/util/bpf/IngressDiscardKey.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 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.bpf;
+
+import com.android.net.module.util.InetAddressUtils;
+import com.android.net.module.util.Struct;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+
+/** Key type for ingress discard map */
+public class IngressDiscardKey extends Struct {
+ // The destination ip of the incoming packet. IPv4 uses IPv4-mapped IPv6 address.
+ @Field(order = 0, type = Type.Ipv6Address)
+ public final Inet6Address dstAddr;
+
+ public IngressDiscardKey(final Inet6Address dstAddr) {
+ this.dstAddr = dstAddr;
+ }
+
+ private static Inet6Address getInet6Address(final InetAddress addr) {
+ return (addr instanceof Inet4Address)
+ ? InetAddressUtils.v4MappedV6Address((Inet4Address) addr)
+ : (Inet6Address) addr;
+ }
+
+ public IngressDiscardKey(final InetAddress dstAddr) {
+ this(getInet6Address(dstAddr));
+ }
+}
diff --git a/common/src/com/android/net/module/util/bpf/IngressDiscardValue.java b/common/src/com/android/net/module/util/bpf/IngressDiscardValue.java
new file mode 100644
index 0000000..7df3620
--- /dev/null
+++ b/common/src/com/android/net/module/util/bpf/IngressDiscardValue.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 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.bpf;
+
+import com.android.net.module.util.Struct;
+
+/** Value type for ingress discard map */
+public class IngressDiscardValue extends Struct {
+ // Allowed interface indexes.
+ // Use the same value for iif1 and iif2 if there is only a single allowed interface index.
+ @Field(order = 0, type = Type.S32)
+ public final int iif1;
+ @Field(order = 1, type = Type.S32)
+ public final int iif2;
+
+ public IngressDiscardValue(final int iif1, final int iif2) {
+ this.iif1 = iif1;
+ this.iif2 = iif2;
+ }
+}
diff --git a/framework-t/udc-extended-api/system-current.txt b/framework-t/udc-extended-api/system-current.txt
index 1549089..6f0119e 100644
--- a/framework-t/udc-extended-api/system-current.txt
+++ b/framework-t/udc-extended-api/system-current.txt
@@ -305,6 +305,7 @@
ctor public NetworkStats(long, int);
method @NonNull public android.net.NetworkStats add(@NonNull android.net.NetworkStats);
method @NonNull public android.net.NetworkStats addEntry(@NonNull android.net.NetworkStats.Entry);
+ method public android.net.NetworkStats clone();
method public int describeContents();
method @NonNull public java.util.Iterator<android.net.NetworkStats.Entry> iterator();
method @NonNull public android.net.NetworkStats subtract(@NonNull android.net.NetworkStats);
diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt
index 782e20a..4d55067 100644
--- a/framework/api/module-lib-current.txt
+++ b/framework/api/module-lib-current.txt
@@ -14,6 +14,7 @@
method @NonNull public static android.util.Range<java.lang.Integer> getIpSecNetIdRange();
method @Nullable @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public android.net.LinkProperties getRedactedLinkPropertiesForPackage(@NonNull android.net.LinkProperties, int, @NonNull String);
method @Nullable @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public android.net.NetworkCapabilities getRedactedNetworkCapabilitiesForPackage(@NonNull android.net.NetworkCapabilities, int, @NonNull String);
+ method @FlaggedApi("com.android.net.flags.support_is_uid_networking_blocked") @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public boolean isUidNetworkingBlocked(int, boolean);
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerDefaultNetworkCallbackForUid(int, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void removeUidFromMeteredNetworkAllowList(int);
diff --git a/framework/jarjar-excludes.txt b/framework/jarjar-excludes.txt
index bc3c8d1..bd513d2 100644
--- a/framework/jarjar-excludes.txt
+++ b/framework/jarjar-excludes.txt
@@ -37,9 +37,3 @@
# This is required since android.net.http contains api classes and hidden classes.
# TODO: Remove this after hidden classes are moved to different package
android\.net\.http\..+
-
-# TODO: OffloadServiceInfo is being added as an API, but wasn't an API yet in the first module
-# versions targeting U. Do not jarjar it such versions so that tests do not have to cover both
-# cases. This will be removed in an upcoming change marking it as API.
-android\.net\.nsd\.OffloadServiceInfo(\$.+)?
-android\.net\.nsd\.OffloadEngine(\$.+)?
diff --git a/framework/src/android/net/BpfNetMapsConstants.java b/framework/src/android/net/BpfNetMapsConstants.java
index e0527f5..c784597 100644
--- a/framework/src/android/net/BpfNetMapsConstants.java
+++ b/framework/src/android/net/BpfNetMapsConstants.java
@@ -16,6 +16,15 @@
package android.net;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_1;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_2;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_3;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY;
+
import android.util.Pair;
import com.android.net.module.util.Struct;
@@ -43,8 +52,16 @@
"/sys/fs/bpf/netd_shared/map_netd_uid_permission_map";
public static final String COOKIE_TAG_MAP_PATH =
"/sys/fs/bpf/netd_shared/map_netd_cookie_tag_map";
+ public static final String DATA_SAVER_ENABLED_MAP_PATH =
+ "/sys/fs/bpf/netd_shared/map_netd_data_saver_enabled_map";
+ public static final String INGRESS_DISCARD_MAP_PATH =
+ "/sys/fs/bpf/netd_shared/map_netd_ingress_discard_map";
public static final Struct.S32 UID_RULES_CONFIGURATION_KEY = new Struct.S32(0);
public static final Struct.S32 CURRENT_STATS_MAP_CONFIGURATION_KEY = new Struct.S32(1);
+ public static final Struct.S32 DATA_SAVER_ENABLED_KEY = new Struct.S32(0);
+
+ public static final short DATA_SAVER_DISABLED = 0;
+ public static final short DATA_SAVER_ENABLED = 1;
// LINT.IfChange(match_type)
public static final long NO_MATCH = 0;
@@ -60,7 +77,6 @@
public static final long OEM_DENY_1_MATCH = (1 << 9);
public static final long OEM_DENY_2_MATCH = (1 << 10);
public static final long OEM_DENY_3_MATCH = (1 << 11);
- // LINT.ThenChange(../../../../bpf_progs/netd.h)
public static final List<Pair<Long, String>> MATCH_LIST = Arrays.asList(
Pair.create(HAPPY_BOX_MATCH, "HAPPY_BOX_MATCH"),
@@ -76,4 +92,29 @@
Pair.create(OEM_DENY_2_MATCH, "OEM_DENY_2_MATCH"),
Pair.create(OEM_DENY_3_MATCH, "OEM_DENY_3_MATCH")
);
+
+ /**
+ * List of all firewall allow chains.
+ *
+ * Allow chains mean the firewall denies all uids by default, uids must be explicitly allowed.
+ */
+ public static final List<Integer> ALLOW_CHAINS = List.of(
+ FIREWALL_CHAIN_DOZABLE,
+ FIREWALL_CHAIN_POWERSAVE,
+ FIREWALL_CHAIN_RESTRICTED,
+ FIREWALL_CHAIN_LOW_POWER_STANDBY
+ );
+
+ /**
+ * List of all firewall deny chains.
+ *
+ * Deny chains mean the firewall allows all uids by default, uids must be explicitly denied.
+ */
+ public static final List<Integer> DENY_CHAINS = List.of(
+ FIREWALL_CHAIN_STANDBY,
+ FIREWALL_CHAIN_OEM_DENY_1,
+ FIREWALL_CHAIN_OEM_DENY_2,
+ FIREWALL_CHAIN_OEM_DENY_3
+ );
+ // LINT.ThenChange(../../../../bpf_progs/netd.h)
}
diff --git a/framework/src/android/net/BpfNetMapsReader.java b/framework/src/android/net/BpfNetMapsReader.java
index 49e874a..37c58f0 100644
--- a/framework/src/android/net/BpfNetMapsReader.java
+++ b/framework/src/android/net/BpfNetMapsReader.java
@@ -17,6 +17,8 @@
package android.net;
import static android.net.BpfNetMapsConstants.CONFIGURATION_MAP_PATH;
+import static android.net.BpfNetMapsConstants.HAPPY_BOX_MATCH;
+import static android.net.BpfNetMapsConstants.PENALTY_BOX_MATCH;
import static android.net.BpfNetMapsConstants.UID_OWNER_MAP_PATH;
import static android.net.BpfNetMapsConstants.UID_RULES_CONFIGURATION_KEY;
import static android.net.BpfNetMapsUtils.getMatchByFirewallChain;
@@ -57,10 +59,42 @@
private final IBpfMap<S32, UidOwnerValue> mUidOwnerMap;
private final Dependencies mDeps;
- public BpfNetMapsReader() {
+ // Bitmaps for calculating whether a given uid is blocked by firewall chains.
+ private static final long sMaskDropIfSet;
+ private static final long sMaskDropIfUnset;
+
+ static {
+ long maskDropIfSet = 0L;
+ long maskDropIfUnset = 0L;
+
+ for (int chain : BpfNetMapsConstants.ALLOW_CHAINS) {
+ final long match = getMatchByFirewallChain(chain);
+ maskDropIfUnset |= match;
+ }
+ for (int chain : BpfNetMapsConstants.DENY_CHAINS) {
+ final long match = getMatchByFirewallChain(chain);
+ maskDropIfSet |= match;
+ }
+ sMaskDropIfSet = maskDropIfSet;
+ sMaskDropIfUnset = maskDropIfUnset;
+ }
+
+ private static class SingletonHolder {
+ static final BpfNetMapsReader sInstance = new BpfNetMapsReader();
+ }
+
+ @NonNull
+ public static BpfNetMapsReader getInstance() {
+ return SingletonHolder.sInstance;
+ }
+
+ private BpfNetMapsReader() {
this(new Dependencies());
}
+ // While the production code uses the singleton to optimize for performance and deal with
+ // concurrent access, the test needs to use a non-static approach for dependency injection and
+ // mocking virtual bpf maps.
@VisibleForTesting
public BpfNetMapsReader(@NonNull Dependencies deps) {
if (!SdkLevel.isAtLeastT()) {
@@ -176,4 +210,43 @@
"Unable to get uid rule status: " + Os.strerror(e.errno));
}
}
+
+ /**
+ * Return whether the network is blocked by firewall chains for the given uid.
+ *
+ * @param uid The target uid.
+ * @param isNetworkMetered Whether the target network is metered.
+ * @param isDataSaverEnabled Whether the data saver is enabled.
+ *
+ * @return True if the network is blocked. Otherwise, false.
+ * @throws ServiceSpecificException if the read fails.
+ *
+ * @hide
+ */
+ public boolean isUidNetworkingBlocked(final int uid, boolean isNetworkMetered,
+ boolean isDataSaverEnabled) {
+ throwIfPreT("isUidBlockedByFirewallChains is not available on pre-T devices");
+
+ final long uidRuleConfig;
+ final long uidMatch;
+ try {
+ uidRuleConfig = mConfigurationMap.getValue(UID_RULES_CONFIGURATION_KEY).val;
+ final UidOwnerValue value = mUidOwnerMap.getValue(new S32(uid));
+ uidMatch = (value != null) ? value.rule : 0L;
+ } catch (ErrnoException e) {
+ throw new ServiceSpecificException(e.errno,
+ "Unable to get firewall chain status: " + Os.strerror(e.errno));
+ }
+
+ final boolean blockedByAllowChains = 0 != (uidRuleConfig & ~uidMatch & sMaskDropIfUnset);
+ final boolean blockedByDenyChains = 0 != (uidRuleConfig & uidMatch & sMaskDropIfSet);
+ if (blockedByAllowChains || blockedByDenyChains) {
+ return true;
+ }
+
+ if (!isNetworkMetered) return false;
+ if ((uidMatch & PENALTY_BOX_MATCH) != 0) return true;
+ if ((uidMatch & HAPPY_BOX_MATCH) != 0) return false;
+ return isDataSaverEnabled;
+ }
}
diff --git a/framework/src/android/net/BpfNetMapsUtils.java b/framework/src/android/net/BpfNetMapsUtils.java
index 28d5891..e9c9137 100644
--- a/framework/src/android/net/BpfNetMapsUtils.java
+++ b/framework/src/android/net/BpfNetMapsUtils.java
@@ -16,6 +16,8 @@
package android.net;
+import static android.net.BpfNetMapsConstants.ALLOW_CHAINS;
+import static android.net.BpfNetMapsConstants.DENY_CHAINS;
import static android.net.BpfNetMapsConstants.DOZABLE_MATCH;
import static android.net.BpfNetMapsConstants.LOW_POWER_STANDBY_MATCH;
import static android.net.BpfNetMapsConstants.MATCH_LIST;
@@ -82,26 +84,18 @@
}
/**
- * Get if the chain is allow list or not.
+ * Get whether the chain is an allow-list or a deny-list.
*
* ALLOWLIST means the firewall denies all by default, uids must be explicitly allowed
- * DENYLIST means the firewall allows all by default, uids must be explicitly denyed
+ * DENYLIST means the firewall allows all by default, uids must be explicitly denied
*/
public static boolean isFirewallAllowList(final int chain) {
- switch (chain) {
- case FIREWALL_CHAIN_DOZABLE:
- case FIREWALL_CHAIN_POWERSAVE:
- case FIREWALL_CHAIN_RESTRICTED:
- case FIREWALL_CHAIN_LOW_POWER_STANDBY:
- return true;
- case FIREWALL_CHAIN_STANDBY:
- case FIREWALL_CHAIN_OEM_DENY_1:
- case FIREWALL_CHAIN_OEM_DENY_2:
- case FIREWALL_CHAIN_OEM_DENY_3:
- return false;
- default:
- throw new ServiceSpecificException(EINVAL, "Invalid firewall chain: " + chain);
+ if (ALLOW_CHAINS.contains(chain)) {
+ return true;
+ } else if (DENY_CHAINS.contains(chain)) {
+ return false;
}
+ throw new ServiceSpecificException(EINVAL, "Invalid firewall chain: " + chain);
}
/**
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index 32058a4..eb8f8c3 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -16,6 +16,8 @@
package android.net;
import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+import static android.content.pm.ApplicationInfo.FLAG_PERSISTENT;
+import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
import static android.net.NetworkRequest.Type.BACKGROUND_REQUEST;
import static android.net.NetworkRequest.Type.LISTEN;
@@ -25,6 +27,8 @@
import static android.net.NetworkRequest.Type.TRACK_SYSTEM_DEFAULT;
import static android.net.QosCallback.QosCallbackRegistrationException;
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
+
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
@@ -37,12 +41,16 @@
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.annotation.TargetApi;
+import android.app.Application;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManager;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.net.ConnectivityDiagnosticsManager.DataStallReport.DetectionMethod;
import android.net.IpSecManager.UdpEncapsulationSocket;
import android.net.SocketKeepalive.Callback;
@@ -74,6 +82,7 @@
import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import libcore.net.event.NetworkEventDispatcher;
@@ -95,6 +104,7 @@
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* Class that answers queries about the state of network connectivity. It also
@@ -123,6 +133,8 @@
public static class Flags {
static final String SET_DATA_SAVER_VIA_CM =
"com.android.net.flags.set_data_saver_via_cm";
+ static final String SUPPORT_IS_UID_NETWORKING_BLOCKED =
+ "com.android.net.flags.support_is_uid_networking_blocked";
}
/**
@@ -6198,6 +6210,125 @@
}
}
+ /**
+ * Helper class to track data saver status.
+ *
+ * The class will fetch current data saver status from {@link NetworkPolicyManager} when
+ * initialized, and listening for status changed intent to cache the latest status.
+ *
+ * @hide
+ */
+ @TargetApi(Build.VERSION_CODES.TIRAMISU) // RECEIVER_NOT_EXPORTED requires T.
+ @VisibleForTesting(visibility = PRIVATE)
+ public static class DataSaverStatusTracker extends BroadcastReceiver {
+ private static final Object sDataSaverStatusTrackerLock = new Object();
+
+ private static volatile DataSaverStatusTracker sInstance;
+
+ /**
+ * Gets a static instance of the class.
+ *
+ * @param context A {@link Context} for initialization. Note that since the data saver
+ * status is global on a device, passing any context is equivalent.
+ * @return The static instance of a {@link DataSaverStatusTracker}.
+ */
+ public static DataSaverStatusTracker getInstance(@NonNull Context context) {
+ if (sInstance == null) {
+ synchronized (sDataSaverStatusTrackerLock) {
+ if (sInstance == null) {
+ sInstance = new DataSaverStatusTracker(context);
+ }
+ }
+ }
+ return sInstance;
+ }
+
+ private final NetworkPolicyManager mNpm;
+ // The value updates on the caller's binder thread or UI thread.
+ private final AtomicBoolean mIsDataSaverEnabled;
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public DataSaverStatusTracker(final Context context) {
+ // To avoid leaks, take the application context.
+ final Context appContext;
+ if (context instanceof Application) {
+ appContext = context;
+ } else {
+ appContext = context.getApplicationContext();
+ }
+
+ if ((appContext.getApplicationInfo().flags & FLAG_PERSISTENT) == 0
+ && (appContext.getApplicationInfo().flags & FLAG_SYSTEM) == 0) {
+ throw new IllegalStateException("Unexpected caller: "
+ + appContext.getApplicationInfo().packageName);
+ }
+
+ mNpm = appContext.getSystemService(NetworkPolicyManager.class);
+ final IntentFilter filter = new IntentFilter(
+ ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED);
+ // The receiver should not receive broadcasts from other Apps.
+ appContext.registerReceiver(this, filter, Context.RECEIVER_NOT_EXPORTED);
+ mIsDataSaverEnabled = new AtomicBoolean();
+ updateDataSaverEnabled();
+ }
+
+ // Runs on caller's UI thread.
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED)) {
+ updateDataSaverEnabled();
+ } else {
+ throw new IllegalStateException("Unexpected intent " + intent);
+ }
+ }
+
+ public boolean getDataSaverEnabled() {
+ return mIsDataSaverEnabled.get();
+ }
+
+ private void updateDataSaverEnabled() {
+ // Uid doesn't really matter, but use a fixed UID to make things clearer.
+ final int dataSaverForCallerUid = mNpm.getRestrictBackgroundStatus(Process.SYSTEM_UID);
+ mIsDataSaverEnabled.set(dataSaverForCallerUid
+ != ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED);
+ }
+ }
+
+ /**
+ * Return whether the network is blocked for the given uid and metered condition.
+ *
+ * Similar to {@link NetworkPolicyManager#isUidNetworkingBlocked}, but directly reads the BPF
+ * maps and therefore considerably faster. For use by the NetworkStack process only.
+ *
+ * @param uid The target uid.
+ * @param isNetworkMetered Whether the target network is metered.
+ *
+ * @return True if all networking with the given condition is blocked. Otherwise, false.
+ * @throws IllegalStateException if the map cannot be opened.
+ * @throws ServiceSpecificException if the read fails.
+ * @hide
+ */
+ // This isn't protected by a standard Android permission since it can't
+ // afford to do IPC for performance reasons. Instead, the access control
+ // is provided by linux file group permission AID_NET_BW_ACCT and the
+ // selinux context fs_bpf_net*.
+ // Only the system server process and the network stack have access.
+ @FlaggedApi(Flags.SUPPORT_IS_UID_NETWORKING_BLOCKED)
+ @SystemApi(client = MODULE_LIBRARIES)
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU) // BPF maps were only mainlined in T
+ @RequiresPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK)
+ public boolean isUidNetworkingBlocked(int uid, boolean isNetworkMetered) {
+ final BpfNetMapsReader reader = BpfNetMapsReader.getInstance();
+
+ final boolean isDataSaverEnabled;
+ // TODO: For U-QPR3+ devices, get data saver status from bpf configuration map directly.
+ final DataSaverStatusTracker dataSaverStatusTracker =
+ DataSaverStatusTracker.getInstance(mContext);
+ isDataSaverEnabled = dataSaverStatusTracker.getDataSaverEnabled();
+
+ return reader.isUidNetworkingBlocked(uid, isNetworkMetered, isDataSaverEnabled);
+ }
+
/** @hide */
public IBinder getCompanionDeviceManagerProxyService() {
try {
diff --git a/framework/src/android/net/NetworkScore.java b/framework/src/android/net/NetworkScore.java
index 00382f6..935dea1 100644
--- a/framework/src/android/net/NetworkScore.java
+++ b/framework/src/android/net/NetworkScore.java
@@ -46,7 +46,7 @@
KEEP_CONNECTED_NONE,
KEEP_CONNECTED_FOR_HANDOVER,
KEEP_CONNECTED_FOR_TEST,
- KEEP_CONNECTED_DOWNSTREAM_NETWORK
+ KEEP_CONNECTED_LOCAL_NETWORK
})
public @interface KeepConnectedReason { }
@@ -67,10 +67,10 @@
public static final int KEEP_CONNECTED_FOR_TEST = 2;
/**
* Keep this network connected even if there is no outstanding request for it, because
- * it is a downstream network.
+ * it is a local network.
* @hide
*/
- public static final int KEEP_CONNECTED_DOWNSTREAM_NETWORK = 3;
+ public static final int KEEP_CONNECTED_LOCAL_NETWORK = 3;
// Agent-managed policies
// This network should lose to a wifi that has ever been validated
diff --git a/framework/udc-extended-api/system-current.txt b/framework/udc-extended-api/system-current.txt
index 4a2ed8a..e812024 100644
--- a/framework/udc-extended-api/system-current.txt
+++ b/framework/udc-extended-api/system-current.txt
@@ -94,6 +94,7 @@
}
public final class DscpPolicy implements android.os.Parcelable {
+ method public int describeContents();
method @Nullable public java.net.InetAddress getDestinationAddress();
method @Nullable public android.util.Range<java.lang.Integer> getDestinationPortRange();
method public int getDscpValue();
@@ -101,6 +102,7 @@
method public int getProtocol();
method @Nullable public java.net.InetAddress getSourceAddress();
method public int getSourcePort();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.net.DscpPolicy> CREATOR;
field public static final int PROTOCOL_ANY = -1; // 0xffffffff
field public static final int SOURCE_PORT_ANY = -1; // 0xffffffff
diff --git a/service-t/native/libs/libnetworkstats/NetworkTracePoller.cpp b/service-t/native/libs/libnetworkstats/NetworkTracePoller.cpp
index 80c315a..450f380 100644
--- a/service-t/native/libs/libnetworkstats/NetworkTracePoller.cpp
+++ b/service-t/native/libs/libnetworkstats/NetworkTracePoller.cpp
@@ -25,9 +25,15 @@
#include <perfetto/tracing/platform.h>
#include <perfetto/tracing/tracing.h>
+#include <unordered_map>
+#include <unordered_set>
+
+#include "netdbpf/BpfNetworkStats.h"
+
namespace android {
namespace bpf {
namespace internal {
+using ::android::base::StringPrintf;
void NetworkTracePoller::PollAndSchedule(perfetto::base::TaskRunner* runner,
uint32_t poll_ms) {
@@ -116,6 +122,28 @@
return res.ok();
}
+void NetworkTracePoller::TraceIfaces(const std::vector<PacketTrace>& packets) {
+ if (packets.empty()) return;
+
+ std::unordered_set<uint32_t> uniqueIfindex;
+ for (const PacketTrace& pkt : packets) {
+ uniqueIfindex.insert(pkt.ifindex);
+ }
+
+ for (uint32_t ifindex : uniqueIfindex) {
+ char ifname[IF_NAMESIZE] = {};
+ if (if_indextoname(ifindex, ifname) != ifname) continue;
+
+ StatsValue stats = {};
+ if (bpfGetIfIndexStats(ifindex, &stats) != 0) continue;
+
+ std::string rxTrack = StringPrintf("%s [%d] Rx Bytes", ifname, ifindex);
+ std::string txTrack = StringPrintf("%s [%d] Tx Bytes", ifname, ifindex);
+ ATRACE_INT64(rxTrack.c_str(), stats.rxBytes);
+ ATRACE_INT64(txTrack.c_str(), stats.txBytes);
+ }
+}
+
bool NetworkTracePoller::ConsumeAll() {
std::scoped_lock<std::mutex> lock(mMutex);
return ConsumeAllLocked();
@@ -137,6 +165,7 @@
ATRACE_INT("NetworkTracePackets", packets.size());
+ TraceIfaces(packets);
mCallback(packets);
return true;
diff --git a/service-t/native/libs/libnetworkstats/include/netdbpf/NetworkTracePoller.h b/service-t/native/libs/libnetworkstats/include/netdbpf/NetworkTracePoller.h
index 8433934..092ab64 100644
--- a/service-t/native/libs/libnetworkstats/include/netdbpf/NetworkTracePoller.h
+++ b/service-t/native/libs/libnetworkstats/include/netdbpf/NetworkTracePoller.h
@@ -61,6 +61,11 @@
void PollAndSchedule(perfetto::base::TaskRunner* runner, uint32_t poll_ms);
bool ConsumeAllLocked() REQUIRES(mMutex);
+ // Record sparse iface stats via atrace. This queries the per-iface stats maps
+ // for any iface present in the vector of packets. This is inexact, but should
+ // have sufficient coverage given these are cumulative counters.
+ void TraceIfaces(const std::vector<PacketTrace>& packets) REQUIRES(mMutex);
+
std::mutex mMutex;
// Records the number of successfully started active sessions so that only the
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index cc3f019..c74f229 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -519,9 +519,9 @@
}
}
+ // TODO: Use a Handler instead of a StateMachine since there are no state changes.
private class NsdStateMachine extends StateMachine {
- private final DefaultState mDefaultState = new DefaultState();
private final EnabledState mEnabledState = new EnabledState();
@Override
@@ -591,124 +591,12 @@
NsdStateMachine(String name, Handler handler) {
super(name, handler);
- addState(mDefaultState);
- addState(mEnabledState, mDefaultState);
+ addState(mEnabledState);
State initialState = mEnabledState;
setInitialState(initialState);
setLogRecSize(25);
}
- class DefaultState extends State {
- @Override
- public boolean processMessage(Message msg) {
- final ClientInfo cInfo;
- final int clientRequestId = msg.arg2;
- switch (msg.what) {
- case NsdManager.REGISTER_CLIENT:
- final ConnectorArgs arg = (ConnectorArgs) msg.obj;
- final INsdManagerCallback cb = arg.callback;
- try {
- cb.asBinder().linkToDeath(arg.connector, 0);
- final String tag = "Client" + arg.uid + "-" + mClientNumberId++;
- final NetworkNsdReportedMetrics metrics =
- mDeps.makeNetworkNsdReportedMetrics(
- (int) mClock.elapsedRealtime());
- cInfo = new ClientInfo(cb, arg.uid, arg.useJavaBackend,
- mServiceLogs.forSubComponent(tag), metrics);
- mClients.put(arg.connector, cInfo);
- } catch (RemoteException e) {
- Log.w(TAG, "Client request id " + clientRequestId
- + " has already died");
- }
- break;
- case NsdManager.UNREGISTER_CLIENT:
- final NsdServiceConnector connector = (NsdServiceConnector) msg.obj;
- cInfo = mClients.remove(connector);
- if (cInfo != null) {
- cInfo.expungeAllRequests();
- if (cInfo.isPreSClient()) {
- mLegacyClientCount -= 1;
- }
- }
- maybeStopMonitoringSocketsIfNoActiveRequest();
- maybeScheduleStop();
- break;
- case NsdManager.DISCOVER_SERVICES:
- cInfo = getClientInfoForReply(msg);
- if (cInfo != null) {
- cInfo.onDiscoverServicesFailedImmediately(clientRequestId,
- NsdManager.FAILURE_INTERNAL_ERROR, true /* isLegacy */);
- }
- break;
- case NsdManager.STOP_DISCOVERY:
- cInfo = getClientInfoForReply(msg);
- if (cInfo != null) {
- cInfo.onStopDiscoveryFailed(
- clientRequestId, NsdManager.FAILURE_INTERNAL_ERROR);
- }
- break;
- case NsdManager.REGISTER_SERVICE:
- cInfo = getClientInfoForReply(msg);
- if (cInfo != null) {
- cInfo.onRegisterServiceFailedImmediately(clientRequestId,
- NsdManager.FAILURE_INTERNAL_ERROR, true /* isLegacy */);
- }
- break;
- case NsdManager.UNREGISTER_SERVICE:
- cInfo = getClientInfoForReply(msg);
- if (cInfo != null) {
- cInfo.onUnregisterServiceFailed(
- clientRequestId, NsdManager.FAILURE_INTERNAL_ERROR);
- }
- break;
- case NsdManager.RESOLVE_SERVICE:
- cInfo = getClientInfoForReply(msg);
- if (cInfo != null) {
- cInfo.onResolveServiceFailedImmediately(clientRequestId,
- NsdManager.FAILURE_INTERNAL_ERROR, true /* isLegacy */);
- }
- break;
- case NsdManager.STOP_RESOLUTION:
- cInfo = getClientInfoForReply(msg);
- if (cInfo != null) {
- cInfo.onStopResolutionFailed(
- clientRequestId, NsdManager.FAILURE_OPERATION_NOT_RUNNING);
- }
- break;
- case NsdManager.REGISTER_SERVICE_CALLBACK:
- cInfo = getClientInfoForReply(msg);
- if (cInfo != null) {
- cInfo.onServiceInfoCallbackRegistrationFailed(
- clientRequestId, NsdManager.FAILURE_BAD_PARAMETERS);
- }
- break;
- case NsdManager.DAEMON_CLEANUP:
- maybeStopDaemon();
- break;
- // This event should be only sent by the legacy (target SDK < S) clients.
- // Mark the sending client as legacy.
- case NsdManager.DAEMON_STARTUP:
- cInfo = getClientInfoForReply(msg);
- if (cInfo != null) {
- cancelStop();
- cInfo.setPreSClient();
- mLegacyClientCount += 1;
- maybeStartDaemon();
- }
- break;
- default:
- Log.e(TAG, "Unhandled " + msg);
- return NOT_HANDLED;
- }
- return HANDLED;
- }
-
- private ClientInfo getClientInfoForReply(Message msg) {
- final ListenerArgs args = (ListenerArgs) msg.obj;
- return mClients.get(args.connector);
- }
- }
-
class EnabledState extends State {
@Override
public void enter() {
@@ -793,6 +681,11 @@
removeRequestMap(clientRequestId, transactionId, clientInfo);
}
+ private ClientInfo getClientInfoForReply(Message msg) {
+ final ListenerArgs args = (ListenerArgs) msg.obj;
+ return mClients.get(args.connector);
+ }
+
@Override
public boolean processMessage(Message msg) {
final ClientInfo clientInfo;
@@ -1214,7 +1107,51 @@
case NsdManager.UNREGISTER_OFFLOAD_ENGINE:
mOffloadEngines.unregister((IOffloadEngine) msg.obj);
break;
+ case NsdManager.REGISTER_CLIENT:
+ final ConnectorArgs arg = (ConnectorArgs) msg.obj;
+ final INsdManagerCallback cb = arg.callback;
+ try {
+ cb.asBinder().linkToDeath(arg.connector, 0);
+ final String tag = "Client" + arg.uid + "-" + mClientNumberId++;
+ final NetworkNsdReportedMetrics metrics =
+ mDeps.makeNetworkNsdReportedMetrics(
+ (int) mClock.elapsedRealtime());
+ clientInfo = new ClientInfo(cb, arg.uid, arg.useJavaBackend,
+ mServiceLogs.forSubComponent(tag), metrics);
+ mClients.put(arg.connector, clientInfo);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Client request id " + clientRequestId
+ + " has already died");
+ }
+ break;
+ case NsdManager.UNREGISTER_CLIENT:
+ final NsdServiceConnector connector = (NsdServiceConnector) msg.obj;
+ clientInfo = mClients.remove(connector);
+ if (clientInfo != null) {
+ clientInfo.expungeAllRequests();
+ if (clientInfo.isPreSClient()) {
+ mLegacyClientCount -= 1;
+ }
+ }
+ maybeStopMonitoringSocketsIfNoActiveRequest();
+ maybeScheduleStop();
+ break;
+ case NsdManager.DAEMON_CLEANUP:
+ maybeStopDaemon();
+ break;
+ // This event should be only sent by the legacy (target SDK < S) clients.
+ // Mark the sending client as legacy.
+ case NsdManager.DAEMON_STARTUP:
+ clientInfo = getClientInfoForReply(msg);
+ if (clientInfo != null) {
+ cancelStop();
+ clientInfo.setPreSClient();
+ mLegacyClientCount += 1;
+ maybeStartDaemon();
+ }
+ break;
default:
+ Log.wtf(TAG, "Unhandled " + msg);
return NOT_HANDLED;
}
return HANDLED;
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
index e07d380..42a6b0d 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
@@ -68,6 +68,8 @@
@NonNull
private final SharedLog mSharedLog;
+ @NonNull
+ private final byte[] mPacketCreationBuffer;
/**
* Callbacks called by {@link MdnsInterfaceAdvertiser} to report status updates.
@@ -205,6 +207,7 @@
mCbHandler = new Handler(looper);
mReplySender = deps.makeReplySender(sharedLog.getTag(), looper, socket,
packetCreationBuffer, sharedLog);
+ mPacketCreationBuffer = packetCreationBuffer;
mAnnouncer = deps.makeMdnsAnnouncer(sharedLog.getTag(), looper, mReplySender,
mAnnouncingCallback, sharedLog);
mProber = deps.makeMdnsProber(sharedLog.getTag(), looper, mReplySender, mProbingCallback,
@@ -390,12 +393,13 @@
* @param serviceId The serviceId.
* @return the raw offload payload
*/
+ @NonNull
public byte[] getRawOffloadPayload(int serviceId) {
try {
- return MdnsUtils.createRawDnsPacket(mReplySender.getPacketCreationBuffer(),
+ return MdnsUtils.createRawDnsPacket(mPacketCreationBuffer,
mRecordRepository.getOffloadPacket(serviceId));
} catch (IOException | IllegalArgumentException e) {
- mSharedLog.wtf("Cannot create rawOffloadPacket: " + e.getMessage());
+ mSharedLog.wtf("Cannot create rawOffloadPacket: ", e);
return new byte[0];
}
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsReplySender.java b/service-t/src/com/android/server/connectivity/mdns/MdnsReplySender.java
index abf5d99..ea3af5e 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsReplySender.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsReplySender.java
@@ -99,11 +99,6 @@
return PACKET_SENT;
}
- /** Get the packetCreationBuffer */
- public byte[] getPacketCreationBuffer() {
- return mPacketCreationBuffer;
- }
-
/**
* Cancel all pending sends.
*/
diff --git a/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java b/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
index d0f3d9a..4d79f9d 100644
--- a/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
+++ b/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
@@ -35,6 +35,7 @@
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
@@ -211,9 +212,7 @@
}
final int len = writer.getWritePosition();
- final byte[] outBuffer = new byte[len];
- System.arraycopy(packetCreationBuffer, 0, outBuffer, 0, len);
- return outBuffer;
+ return Arrays.copyOfRange(packetCreationBuffer, 0, len);
}
/**
diff --git a/service-t/src/com/android/server/net/NetworkStatsObservers.java b/service-t/src/com/android/server/net/NetworkStatsObservers.java
index 1cd670a..21cf351 100644
--- a/service-t/src/com/android/server/net/NetworkStatsObservers.java
+++ b/service-t/src/com/android/server/net/NetworkStatsObservers.java
@@ -142,6 +142,11 @@
@VisibleForTesting
protected Looper getHandlerLooperLocked() {
+ // TODO: Currently, callbacks are dispatched on this thread if the caller register
+ // callback without supplying a Handler. To ensure that the service handler thread
+ // is not blocked by client code, the observers must create their own thread. Once
+ // all callbacks are dispatched outside of the handler thread, the service handler
+ // thread can be used here.
HandlerThread handlerThread = new HandlerThread(TAG);
handlerThread.start();
return handlerThread.getLooper();
diff --git a/service/Android.bp b/service/Android.bp
index 250693f..7def200 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -185,7 +185,7 @@
"androidx.annotation_annotation",
"connectivity-net-module-utils-bpf",
"connectivity_native_aidl_interface-lateststable-java",
- "dnsresolver_aidl_interface-V11-java",
+ "dnsresolver_aidl_interface-V12-java",
"modules-utils-shell-command-handler",
"net-utils-device-common",
"net-utils-device-common-ip",
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index 671c4ac..f20159c 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -19,8 +19,13 @@
import static android.net.BpfNetMapsConstants.CONFIGURATION_MAP_PATH;
import static android.net.BpfNetMapsConstants.COOKIE_TAG_MAP_PATH;
import static android.net.BpfNetMapsConstants.CURRENT_STATS_MAP_CONFIGURATION_KEY;
+import static android.net.BpfNetMapsConstants.DATA_SAVER_DISABLED;
+import static android.net.BpfNetMapsConstants.DATA_SAVER_ENABLED;
+import static android.net.BpfNetMapsConstants.DATA_SAVER_ENABLED_KEY;
+import static android.net.BpfNetMapsConstants.DATA_SAVER_ENABLED_MAP_PATH;
import static android.net.BpfNetMapsConstants.HAPPY_BOX_MATCH;
import static android.net.BpfNetMapsConstants.IIF_MATCH;
+import static android.net.BpfNetMapsConstants.INGRESS_DISCARD_MAP_PATH;
import static android.net.BpfNetMapsConstants.LOCKDOWN_VPN_MATCH;
import static android.net.BpfNetMapsConstants.PENALTY_BOX_MATCH;
import static android.net.BpfNetMapsConstants.UID_OWNER_MAP_PATH;
@@ -28,6 +33,7 @@
import static android.net.BpfNetMapsConstants.UID_RULES_CONFIGURATION_KEY;
import static android.net.BpfNetMapsUtils.PRE_T;
import static android.net.BpfNetMapsUtils.getMatchByFirewallChain;
+import static android.net.BpfNetMapsUtils.isFirewallAllowList;
import static android.net.BpfNetMapsUtils.matchToString;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
@@ -81,9 +87,12 @@
import com.android.net.module.util.Struct.U8;
import com.android.net.module.util.bpf.CookieTagMapKey;
import com.android.net.module.util.bpf.CookieTagMapValue;
+import com.android.net.module.util.bpf.IngressDiscardKey;
+import com.android.net.module.util.bpf.IngressDiscardValue;
import java.io.FileDescriptor;
import java.io.IOException;
+import java.net.InetAddress;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
@@ -130,6 +139,9 @@
private static IBpfMap<S32, UidOwnerValue> sUidOwnerMap = null;
private static IBpfMap<S32, U8> sUidPermissionMap = null;
private static IBpfMap<CookieTagMapKey, CookieTagMapValue> sCookieTagMap = null;
+ // TODO: Add BOOL class and replace U8?
+ private static IBpfMap<S32, U8> sDataSaverEnabledMap = null;
+ private static IBpfMap<IngressDiscardKey, IngressDiscardValue> sIngressDiscardMap = null;
private static final List<Pair<Integer, String>> PERMISSION_LIST = Arrays.asList(
Pair.create(PERMISSION_INTERNET, "PERMISSION_INTERNET"),
@@ -177,6 +189,23 @@
sCookieTagMap = cookieTagMap;
}
+ /**
+ * Set dataSaverEnabledMap for test.
+ */
+ @VisibleForTesting
+ public static void setDataSaverEnabledMapForTest(IBpfMap<S32, U8> dataSaverEnabledMap) {
+ sDataSaverEnabledMap = dataSaverEnabledMap;
+ }
+
+ /**
+ * Set ingressDiscardMap for test.
+ */
+ @VisibleForTesting
+ public static void setIngressDiscardMapForTest(
+ IBpfMap<IngressDiscardKey, IngressDiscardValue> ingressDiscardMap) {
+ sIngressDiscardMap = ingressDiscardMap;
+ }
+
private static IBpfMap<S32, U32> getConfigurationMap() {
try {
return new BpfMap<>(
@@ -213,6 +242,24 @@
}
}
+ private static IBpfMap<S32, U8> getDataSaverEnabledMap() {
+ try {
+ return new BpfMap<>(
+ DATA_SAVER_ENABLED_MAP_PATH, BpfMap.BPF_F_RDWR, S32.class, U8.class);
+ } catch (ErrnoException e) {
+ throw new IllegalStateException("Cannot open data saver enabled map", e);
+ }
+ }
+
+ private static IBpfMap<IngressDiscardKey, IngressDiscardValue> getIngressDiscardMap() {
+ try {
+ return new BpfMap<>(INGRESS_DISCARD_MAP_PATH, BpfMap.BPF_F_RDWR,
+ IngressDiscardKey.class, IngressDiscardValue.class);
+ } catch (ErrnoException e) {
+ throw new IllegalStateException("Cannot open ingress discard map", e);
+ }
+ }
+
private static void initBpfMaps() {
if (sConfigurationMap == null) {
sConfigurationMap = getConfigurationMap();
@@ -246,6 +293,24 @@
if (sCookieTagMap == null) {
sCookieTagMap = getCookieTagMap();
}
+
+ if (sDataSaverEnabledMap == null) {
+ sDataSaverEnabledMap = getDataSaverEnabledMap();
+ }
+ try {
+ sDataSaverEnabledMap.updateEntry(DATA_SAVER_ENABLED_KEY, new U8(DATA_SAVER_DISABLED));
+ } catch (ErrnoException e) {
+ throw new IllegalStateException("Failed to initialize data saver configuration", e);
+ }
+
+ if (sIngressDiscardMap == null) {
+ sIngressDiscardMap = getIngressDiscardMap();
+ }
+ try {
+ sIngressDiscardMap.clear();
+ } catch (ErrnoException e) {
+ throw new IllegalStateException("Failed to initialize ingress discard map", e);
+ }
}
/**
@@ -283,6 +348,13 @@
}
/**
+ * Get interface name
+ */
+ public String getIfName(final int ifIndex) {
+ return Os.if_indextoname(ifIndex);
+ }
+
+ /**
* Call synchronize_rcu()
*/
public int synchronizeKernelRCU() {
@@ -320,29 +392,6 @@
mDeps = deps;
}
- /**
- * Get if the chain is allow list or not.
- *
- * ALLOWLIST means the firewall denies all by default, uids must be explicitly allowed
- * DENYLIST means the firewall allows all by default, uids must be explicitly denyed
- */
- public boolean isFirewallAllowList(final int chain) {
- switch (chain) {
- case FIREWALL_CHAIN_DOZABLE:
- case FIREWALL_CHAIN_POWERSAVE:
- case FIREWALL_CHAIN_RESTRICTED:
- case FIREWALL_CHAIN_LOW_POWER_STANDBY:
- return true;
- case FIREWALL_CHAIN_STANDBY:
- case FIREWALL_CHAIN_OEM_DENY_1:
- case FIREWALL_CHAIN_OEM_DENY_2:
- case FIREWALL_CHAIN_OEM_DENY_3:
- return false;
- default:
- throw new ServiceSpecificException(EINVAL, "Invalid firewall chain: " + chain);
- }
- }
-
private void maybeThrow(final int err, final String msg) {
if (err != 0) {
throw new ServiceSpecificException(err, msg + ": " + Os.strerror(err));
@@ -543,7 +592,7 @@
private Set<Integer> asSet(final int[] uids) {
final Set<Integer> uidSet = new ArraySet<>();
- for (final int uid: uids) {
+ for (final int uid : uids) {
uidSet.add(uid);
}
return uidSet;
@@ -926,6 +975,66 @@
}
}
+ /**
+ * Set Data Saver enabled or disabled
+ *
+ * @param enable whether Data Saver is enabled or disabled.
+ * @throws UnsupportedOperationException if called on pre-T devices.
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+ public void setDataSaverEnabled(boolean enable) {
+ throwIfPreT("setDataSaverEnabled is not available on pre-T devices");
+
+ try {
+ final short config = enable ? DATA_SAVER_ENABLED : DATA_SAVER_DISABLED;
+ sDataSaverEnabledMap.updateEntry(DATA_SAVER_ENABLED_KEY, new U8(config));
+ } catch (ErrnoException e) {
+ throw new ServiceSpecificException(e.errno, "Unable to set data saver: "
+ + Os.strerror(e.errno));
+ }
+ }
+
+ /**
+ * Set ingress discard rule
+ *
+ * @param address target address to set the ingress discard rule
+ * @param iface allowed interface
+ */
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+ public void setIngressDiscardRule(final InetAddress address, final String iface) {
+ throwIfPreT("setIngressDiscardRule is not available on pre-T devices");
+ final int ifIndex = mDeps.getIfIndex(iface);
+ if (ifIndex == 0) {
+ Log.e(TAG, "Failed to get if index, skip setting ingress discard rule for " + address
+ + "(" + iface + ")");
+ return;
+ }
+ try {
+ sIngressDiscardMap.updateEntry(new IngressDiscardKey(address),
+ new IngressDiscardValue(ifIndex, ifIndex));
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Failed to set ingress discard rule for " + address + "("
+ + iface + "), " + e);
+ }
+ }
+
+ /**
+ * Remove ingress discard rule
+ *
+ * @param address target address to remove the ingress discard rule
+ */
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+ public void removeIngressDiscardRule(final InetAddress address) {
+ throwIfPreT("removeIngressDiscardRule is not available on pre-T devices");
+ try {
+ sIngressDiscardMap.deleteEntry(new IngressDiscardKey(address));
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Failed to remove ingress discard rule for " + address + ", " + e);
+ }
+ }
+
/** Register callback for statsd to pull atom. */
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
public void setPullAtomCallback(final Context context) {
@@ -1008,6 +1117,15 @@
}
}
+ private void dumpDataSaverConfig(final IndentingPrintWriter pw) {
+ try {
+ final short config = sDataSaverEnabledMap.getValue(DATA_SAVER_ENABLED_KEY).val;
+ // Any non-zero value converted from short to boolean is true by convention.
+ pw.println("sDataSaverEnabledMap: " + (config != DATA_SAVER_DISABLED));
+ } catch (ErrnoException e) {
+ pw.println("Failed to read data saver configuration: " + e);
+ }
+ }
/**
* Dump BPF maps
*
@@ -1058,6 +1176,11 @@
});
BpfDump.dumpMap(sUidPermissionMap, pw, "sUidPermissionMap",
(uid, permission) -> uid.val + " " + permissionToString(permission.val));
+ BpfDump.dumpMap(sIngressDiscardMap, pw, "sIngressDiscardMap",
+ (key, value) -> "[" + key.dstAddr + "]: "
+ + value.iif1 + "(" + mDeps.getIfName(value.iif1) + "), "
+ + value.iif2 + "(" + mDeps.getIfName(value.iif2) + ")");
+ dumpDataSaverConfig(pw);
pw.decreaseIndent();
}
}
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index c4cb4c7..50b4134 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -136,6 +136,7 @@
import android.content.pm.PackageManager;
import android.content.res.XmlResourceParser;
import android.database.ContentObserver;
+import android.net.BpfNetMapsUtils;
import android.net.CaptivePortal;
import android.net.CaptivePortalData;
import android.net.ConnectionInfo;
@@ -4155,7 +4156,14 @@
switch (msg.what) {
case NetworkAgent.EVENT_NETWORK_CAPABILITIES_CHANGED: {
- nai.setDeclaredCapabilities((NetworkCapabilities) arg.second);
+ final NetworkCapabilities proposed = (NetworkCapabilities) arg.second;
+ if (!nai.respectsNcStructuralConstraints(proposed)) {
+ Log.wtf(TAG, "Agent " + nai + " violates nc structural constraints : "
+ + nai.networkCapabilities + " -> " + proposed);
+ disconnectAndDestroyNetwork(nai);
+ return;
+ }
+ nai.setDeclaredCapabilities(proposed);
final NetworkCapabilities sanitized =
nai.getDeclaredCapabilitiesSanitized(mCarrierPrivilegeAuthenticator);
maybeUpdateWifiRoamTimestamp(nai, sanitized);
@@ -4175,7 +4183,7 @@
}
case NetworkAgent.EVENT_LOCAL_NETWORK_CONFIG_CHANGED: {
final LocalNetworkConfig config = (LocalNetworkConfig) arg.second;
- updateLocalNetworkConfig(nai, config);
+ updateLocalNetworkConfig(nai, nai.localNetworkConfig, config);
break;
}
case NetworkAgent.EVENT_NETWORK_SCORE_CHANGED: {
@@ -4936,6 +4944,17 @@
mDefaultInetConditionPublished = 0;
}
notifyIfacesChangedForNetworkStats();
+ // If this was a local network forwarded to some upstream, or if some local network was
+ // forwarded to this nai, then disable forwarding rules now.
+ maybeDisableForwardRulesForDisconnectingNai(nai);
+ // If this is a local network with an upstream selector, remove the associated network
+ // request.
+ if (nai.isLocalNetwork()) {
+ final NetworkRequest selector = nai.localNetworkConfig.getUpstreamSelector();
+ if (null != selector) {
+ handleRemoveNetworkRequest(mNetworkRequests.get(selector));
+ }
+ }
// TODO - we shouldn't send CALLBACK_LOST to requests that can be satisfied
// by other networks that are already connected. Perhaps that can be done by
// sending all CALLBACK_LOST messages (for requests, not listens) at the end
@@ -5049,6 +5068,48 @@
mNetIdManager.releaseNetId(nai.network.getNetId());
}
+ private void maybeDisableForwardRulesForDisconnectingNai(
+ @NonNull final NetworkAgentInfo disconnecting) {
+ // Step 1 : maybe this network was the upstream for one or more local networks.
+ for (final NetworkAgentInfo local : mNetworkAgentInfos) {
+ if (!local.isLocalNetwork()) continue;
+ final NetworkRequest selector = local.localNetworkConfig.getUpstreamSelector();
+ if (null == selector) continue;
+ final NetworkRequestInfo nri = mNetworkRequests.get(selector);
+ // null == nri can happen while disconnecting a network, because destroyNetwork() is
+ // called after removing all associated NRIs from mNetworkRequests.
+ if (null == nri) continue;
+ final NetworkAgentInfo satisfier = nri.getSatisfier();
+ if (disconnecting != satisfier) continue;
+ removeLocalNetworkUpstream(local, disconnecting);
+ }
+
+ // Step 2 : maybe this is a local network that had an upstream.
+ if (!disconnecting.isLocalNetwork()) return;
+ final NetworkRequest selector = disconnecting.localNetworkConfig.getUpstreamSelector();
+ if (null == selector) return;
+ final NetworkRequestInfo nri = mNetworkRequests.get(selector);
+ // As above null == nri can happen while disconnecting a network, because destroyNetwork()
+ // is called after removing all associated NRIs from mNetworkRequests.
+ if (null == nri) return;
+ final NetworkAgentInfo satisfier = nri.getSatisfier();
+ if (null == satisfier) return;
+ removeLocalNetworkUpstream(disconnecting, satisfier);
+ }
+
+ private void removeLocalNetworkUpstream(@NonNull final NetworkAgentInfo localAgent,
+ @NonNull final NetworkAgentInfo upstream) {
+ try {
+ mRoutingCoordinatorService.removeInterfaceForward(
+ localAgent.linkProperties.getInterfaceName(),
+ upstream.linkProperties.getInterfaceName());
+ } catch (RemoteException e) {
+ loge("Couldn't remove interface forward for "
+ + localAgent.linkProperties.getInterfaceName() + " to "
+ + upstream.linkProperties.getInterfaceName() + " while disconnecting");
+ }
+ }
+
private boolean createNativeNetwork(@NonNull NetworkAgentInfo nai) {
try {
// This should never fail. Specifying an already in use NetID will cause failure.
@@ -5063,10 +5124,9 @@
!nai.networkAgentConfig.allowBypass /* secure */,
getVpnType(nai), nai.networkAgentConfig.excludeLocalRouteVpn);
} else {
- final boolean hasLocalCap =
- nai.networkCapabilities.hasCapability(NET_CAPABILITY_LOCAL_NETWORK);
config = new NativeNetworkConfig(nai.network.getNetId(),
- hasLocalCap ? NativeNetworkType.PHYSICAL_LOCAL : NativeNetworkType.PHYSICAL,
+ nai.isLocalNetwork() ? NativeNetworkType.PHYSICAL_LOCAL
+ : NativeNetworkType.PHYSICAL,
getNetworkPermission(nai.networkCapabilities),
false /* secure */,
VpnManager.TYPE_VPN_NONE,
@@ -5074,8 +5134,8 @@
}
mNetd.networkCreate(config);
mDnsResolver.createNetworkCache(nai.network.getNetId());
- mDnsManager.updateTransportsForNetwork(nai.network.getNetId(),
- nai.networkCapabilities.getTransportTypes());
+ mDnsManager.updateCapabilitiesForNetwork(nai.network.getNetId(),
+ nai.networkCapabilities);
return true;
} catch (RemoteException | ServiceSpecificException e) {
loge("Error creating network " + nai.toShortString() + ": " + e.getMessage());
@@ -5087,6 +5147,9 @@
if (mDscpPolicyTracker != null) {
mDscpPolicyTracker.removeAllDscpPolicies(nai, false);
}
+ // Remove any forwarding rules to and from the interface for this network, since
+ // the interface is going to go away.
+ maybeDisableForwardRulesForDisconnectingNai(nai);
try {
mNetd.networkDestroy(nai.network.getNetId());
} catch (RemoteException | ServiceSpecificException e) {
@@ -5273,7 +5336,14 @@
private boolean isNetworkPotentialSatisfier(
@NonNull final NetworkAgentInfo candidate, @NonNull final NetworkRequestInfo nri) {
- // listen requests won't keep up a network satisfying it. If this is not a multilayer
+ // While destroyed network sometimes satisfy requests (including occasionally newly
+ // satisfying requests), *potential* satisfiers are networks that might beat a current
+ // champion if they validate. As such, a destroyed network is never a potential satisfier,
+ // because it's never a good idea to keep a destroyed network in case it validates.
+ // For example, declaring it a potential satisfier would keep an unvalidated destroyed
+ // candidate after it's been replaced by another unvalidated network.
+ if (candidate.isDestroyed()) return false;
+ // Listen requests won't keep up a network satisfying it. If this is not a multilayer
// request, return immediately. For multilayer requests, check to see if any of the
// multilayer requests may have a potential satisfier.
if (!nri.isMultilayerRequest() && (nri.mRequests.get(0).isListen()
@@ -5291,8 +5361,12 @@
if (req.isListen() || req.isListenForBest()) {
continue;
}
- // If this Network is already the best Network for a request, or if
- // there is hope for it to become one if it validated, then it is needed.
+ // If there is hope for this network might validate and subsequently become the best
+ // network for that request, then it is needed. Note that this network can't already
+ // be the best for this request, or it would be the current satisfier, and therefore
+ // there would be no need to call this method to find out if it is a *potential*
+ // satisfier ("unneeded", the only caller, only calls this if this network currently
+ // satisfies no request).
if (candidate.satisfies(req)) {
// As soon as a network is found that satisfies a request, return. Specifically for
// multilayer requests, returning as soon as a NetworkAgentInfo satisfies a request
@@ -8245,6 +8319,9 @@
e.rethrowAsRuntimeException();
}
+ if (nai.isLocalNetwork()) {
+ updateLocalNetworkConfig(nai, null /* oldConfig */, nai.localNetworkConfig);
+ }
nai.notifyRegistered();
NetworkInfo networkInfo = nai.networkInfo;
updateNetworkInfo(nai, networkInfo);
@@ -8898,9 +8975,8 @@
// This network might have been underlying another network. Propagate its capabilities.
propagateUnderlyingNetworkCapabilities(nai.network);
- if (!newNc.equalsTransportTypes(prevNc)) {
- mDnsManager.updateTransportsForNetwork(
- nai.network.getNetId(), newNc.getTransportTypes());
+ if (meteredChanged || !newNc.equalsTransportTypes(prevNc)) {
+ mDnsManager.updateCapabilitiesForNetwork(nai.network.getNetId(), newNc);
}
maybeSendProxyBroadcast(nai, prevNc, newNc);
@@ -8911,14 +8987,67 @@
updateCapabilities(nai.getScore(), nai, nai.networkCapabilities);
}
+ // oldConfig is null iff this is the original registration of the local network config
private void updateLocalNetworkConfig(@NonNull final NetworkAgentInfo nai,
- @NonNull final LocalNetworkConfig config) {
- if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_LOCAL_NETWORK)) {
+ @Nullable final LocalNetworkConfig oldConfig,
+ @NonNull final LocalNetworkConfig newConfig) {
+ if (!nai.isLocalNetwork()) {
Log.wtf(TAG, "Ignoring update of a local network info on non-local network " + nai);
return;
}
- // TODO : actually apply the diff.
- nai.localNetworkConfig = config;
+
+ final LocalNetworkConfig.Builder configBuilder = new LocalNetworkConfig.Builder();
+ // TODO : apply the diff for multicast routing.
+ configBuilder.setUpstreamMulticastRoutingConfig(
+ newConfig.getUpstreamMulticastRoutingConfig());
+ configBuilder.setDownstreamMulticastRoutingConfig(
+ newConfig.getDownstreamMulticastRoutingConfig());
+
+ final NetworkRequest oldRequest =
+ (null == oldConfig) ? null : oldConfig.getUpstreamSelector();
+ final NetworkCapabilities oldCaps =
+ (null == oldRequest) ? null : oldRequest.networkCapabilities;
+ final NetworkRequestInfo oldNri =
+ null == oldRequest ? null : mNetworkRequests.get(oldRequest);
+ final NetworkAgentInfo oldSatisfier =
+ null == oldNri ? null : oldNri.getSatisfier();
+ final NetworkRequest newRequest = newConfig.getUpstreamSelector();
+ final NetworkCapabilities newCaps =
+ (null == newRequest) ? null : newRequest.networkCapabilities;
+ final boolean requestUpdated = !Objects.equals(newCaps, oldCaps);
+ if (null != oldRequest && requestUpdated) {
+ handleRemoveNetworkRequest(mNetworkRequests.get(oldRequest));
+ if (null == newRequest && null != oldSatisfier) {
+ // If there is an old satisfier, but no new request, then remove the old upstream.
+ removeLocalNetworkUpstream(nai, oldSatisfier);
+ nai.localNetworkConfig = configBuilder.build();
+ return;
+ }
+ }
+ if (null != newRequest && requestUpdated) {
+ // File the new request if :
+ // - it has changed (requestUpdated), or
+ // - it's the first time this local info (null == oldConfig)
+ // is updated and the request has not been filed yet.
+ // Requests for local info are always LISTEN_FOR_BEST, because they have at most one
+ // upstream (the best) but never request it to be brought up.
+ final NetworkRequest nr = new NetworkRequest(newCaps, ConnectivityManager.TYPE_NONE,
+ nextNetworkRequestId(), LISTEN_FOR_BEST);
+ configBuilder.setUpstreamSelector(nr);
+ final NetworkRequestInfo nri = new NetworkRequestInfo(
+ nai.creatorUid, nr, null /* messenger */, null /* binder */,
+ 0 /* callbackFlags */, null /* attributionTag */);
+ if (null != oldSatisfier) {
+ // Set the old satisfier in the new NRI so that the rematch will see any changes
+ nri.setSatisfier(oldSatisfier, nr);
+ }
+ nai.localNetworkConfig = configBuilder.build();
+ handleRegisterNetworkRequest(nri);
+ } else {
+ configBuilder.setUpstreamSelector(oldRequest);
+ nai.localNetworkConfig = configBuilder.build();
+ }
+
}
/**
@@ -9700,7 +9829,8 @@
if (VDBG) log("rematch for " + newSatisfier.toShortString());
if (null != previousRequest && null != previousSatisfier) {
if (VDBG || DDBG) {
- log(" accepting network in place of " + previousSatisfier.toShortString());
+ log(" accepting network in place of " + previousSatisfier.toShortString()
+ + " for " + newRequest);
}
previousSatisfier.removeRequest(previousRequest.requestId);
if (canSupportGracefulNetworkSwitch(previousSatisfier, newSatisfier)
@@ -9719,7 +9849,7 @@
previousSatisfier.lingerRequest(previousRequest.requestId, now);
}
} else {
- if (VDBG || DDBG) log(" accepting network in place of null");
+ if (VDBG || DDBG) log(" accepting network in place of null for " + newRequest);
}
// To prevent constantly CPU wake up for nascent timer, if a network comes up
@@ -9835,6 +9965,14 @@
}
}
+ private boolean hasSameInterfaceName(@Nullable final NetworkAgentInfo nai1,
+ @Nullable final NetworkAgentInfo nai2) {
+ if (null == nai1) return null == nai2;
+ if (null == nai2) return false;
+ return nai1.linkProperties.getInterfaceName()
+ .equals(nai2.linkProperties.getInterfaceName());
+ }
+
private void applyNetworkReassignment(@NonNull final NetworkReassignment changes,
final long now) {
final Collection<NetworkAgentInfo> nais = mNetworkAgentInfos;
@@ -9908,6 +10046,39 @@
notifyNetworkLosing(nai, now);
}
+ // Update forwarding rules for the upstreams of local networks. Do this after sending
+ // onAvailable so that clients understand what network this is about.
+ for (final NetworkAgentInfo nai : mNetworkAgentInfos) {
+ if (!nai.isLocalNetwork()) continue;
+ final NetworkRequest nr = nai.localNetworkConfig.getUpstreamSelector();
+ if (null == nr) continue; // No upstream for this local network
+ final NetworkRequestInfo nri = mNetworkRequests.get(nr);
+ final NetworkReassignment.RequestReassignment change = changes.getReassignment(nri);
+ if (null == change) continue; // No change in upstreams for this network
+ final String fromIface = nai.linkProperties.getInterfaceName();
+ if (!hasSameInterfaceName(change.mOldNetwork, change.mNewNetwork)
+ || change.mOldNetwork.isDestroyed()) {
+ // There can be a change with the same interface name if the new network is the
+ // replacement for the old network that was unregisteredAfterReplacement.
+ try {
+ if (null != change.mOldNetwork) {
+ mRoutingCoordinatorService.removeInterfaceForward(fromIface,
+ change.mOldNetwork.linkProperties.getInterfaceName());
+ }
+ // If the new upstream is already destroyed, there is no point in setting up
+ // a forward (in fact, it might forward to the interface for some new network !)
+ // Later when the upstream disconnects CS will try to remove the forward, which
+ // is ignored with a benign log by RoutingCoordinatorService.
+ if (null != change.mNewNetwork && !change.mNewNetwork.isDestroyed()) {
+ mRoutingCoordinatorService.addInterfaceForward(fromIface,
+ change.mNewNetwork.linkProperties.getInterfaceName());
+ }
+ } catch (final RemoteException e) {
+ loge("Can't update forwarding rules", e);
+ }
+ }
+ }
+
updateLegacyTypeTrackerAndVpnLockdownForRematch(changes, nais);
// Tear down all unneeded networks.
@@ -12557,6 +12728,7 @@
}
}
+ @TargetApi(Build.VERSION_CODES.TIRAMISU)
@Override
public void setDataSaverEnabled(final boolean enable) {
enforceNetworkStackOrSettingsPermission();
@@ -12569,6 +12741,12 @@
// Lack of permission or binder errors.
throw new IllegalStateException(e);
}
+
+ try {
+ mBpfNetMaps.setDataSaverEnabled(enable);
+ } catch (ServiceSpecificException | UnsupportedOperationException e) {
+ Log.e(TAG, "Failed to set data saver " + enable + " : " + e);
+ }
}
@Override
@@ -12672,7 +12850,7 @@
private void closeSocketsForFirewallChainLocked(final int chain)
throws ErrnoException, SocketException, InterruptedIOException {
- if (mBpfNetMaps.isFirewallAllowList(chain)) {
+ if (BpfNetMapsUtils.isFirewallAllowList(chain)) {
// Allowlist means the firewall denies all by default, uids must be explicitly allowed
// So, close all non-system socket owned by uids that are not explicitly allowed
Set<Range<Integer>> ranges = new ArraySet<>();
diff --git a/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java b/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java
index 11345d3..bba132f 100644
--- a/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java
+++ b/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java
@@ -692,8 +692,10 @@
/**
* Dump AutomaticOnOffKeepaliveTracker state.
+ * This should be only be called in ConnectivityService handler thread.
*/
public void dump(IndentingPrintWriter pw) {
+ ensureRunningOnHandlerThread();
mKeepaliveTracker.dump(pw);
// Reading DeviceConfig will check if the calling uid and calling package name are the same.
// Clear calling identity to align the calling uid and package so that it won't fail if cts
@@ -712,6 +714,9 @@
pw.increaseIndent();
mEventLog.reverseDump(pw);
pw.decreaseIndent();
+
+ pw.println();
+ mKeepaliveStatsTracker.dump(pw);
}
/**
@@ -895,7 +900,7 @@
public FileDescriptor createConnectedNetlinkSocket()
throws ErrnoException, SocketException {
final FileDescriptor fd = NetlinkUtils.createNetLinkInetDiagSocket();
- NetlinkUtils.connectSocketToNetlink(fd);
+ NetlinkUtils.connectToKernel(fd);
Os.setsockoptTimeval(fd, SOL_SOCKET, SO_SNDTIMEO,
StructTimeval.fromMillis(IO_TIMEOUT_MS));
return fd;
diff --git a/service/src/com/android/server/connectivity/DnsManager.java b/service/src/com/android/server/connectivity/DnsManager.java
index 1493cae..894bcc4 100644
--- a/service/src/com/android/server/connectivity/DnsManager.java
+++ b/service/src/com/android/server/connectivity/DnsManager.java
@@ -38,6 +38,7 @@
import android.net.InetAddresses;
import android.net.LinkProperties;
import android.net.Network;
+import android.net.NetworkCapabilities;
import android.net.ResolverParamsParcel;
import android.net.Uri;
import android.net.shared.PrivateDnsConfig;
@@ -251,7 +252,7 @@
// TODO: Replace the Map with SparseArrays.
private final Map<Integer, PrivateDnsValidationStatuses> mPrivateDnsValidationMap;
private final Map<Integer, LinkProperties> mLinkPropertiesMap;
- private final Map<Integer, int[]> mTransportsMap;
+ private final Map<Integer, NetworkCapabilities> mNetworkCapabilitiesMap;
private int mSampleValidity;
private int mSuccessThreshold;
@@ -265,7 +266,7 @@
mPrivateDnsMap = new ConcurrentHashMap<>();
mPrivateDnsValidationMap = new HashMap<>();
mLinkPropertiesMap = new HashMap<>();
- mTransportsMap = new HashMap<>();
+ mNetworkCapabilitiesMap = new HashMap<>();
// TODO: Create and register ContentObservers to track every setting
// used herein, posting messages to respond to changes.
@@ -278,7 +279,7 @@
public void removeNetwork(Network network) {
mPrivateDnsMap.remove(network.getNetId());
mPrivateDnsValidationMap.remove(network.getNetId());
- mTransportsMap.remove(network.getNetId());
+ mNetworkCapabilitiesMap.remove(network.getNetId());
mLinkPropertiesMap.remove(network.getNetId());
}
@@ -325,13 +326,17 @@
}
/**
- * When creating a new network or transport types are changed in a specific network,
- * transport types are always saved to a hashMap before update dns config.
- * When destroying network, the specific network will be removed from the hashMap.
- * The hashMap is always accessed on the same thread.
+ * Update {@link NetworkCapabilities} stored in this instance.
+ *
+ * In order to ensure that the resolver has access to necessary information when other events
+ * occur, capabilities are always saved to a hashMap before updating the DNS configuration
+ * whenever a new network is created, transport types are modified, or metered capabilities are
+ * altered for a network. When a network is destroyed, the corresponding entry is removed from
+ * the hashMap. To prevent concurrency issues, the hashMap should always be accessed from the
+ * same thread.
*/
- public void updateTransportsForNetwork(int netId, @NonNull int[] transportTypes) {
- mTransportsMap.put(netId, transportTypes);
+ public void updateCapabilitiesForNetwork(int netId, @NonNull final NetworkCapabilities nc) {
+ mNetworkCapabilitiesMap.put(netId, nc);
sendDnsConfigurationForNetwork(netId);
}
@@ -351,8 +356,8 @@
*/
public void sendDnsConfigurationForNetwork(int netId) {
final LinkProperties lp = mLinkPropertiesMap.get(netId);
- final int[] transportTypes = mTransportsMap.get(netId);
- if (lp == null || transportTypes == null) return;
+ final NetworkCapabilities nc = mNetworkCapabilitiesMap.get(netId);
+ if (lp == null || nc == null) return;
updateParametersSettings();
final ResolverParamsParcel paramsParcel = new ResolverParamsParcel();
@@ -383,7 +388,8 @@
.collect(Collectors.toList()))
: useTls ? paramsParcel.servers // Opportunistic
: new String[0]; // Off
- paramsParcel.transportTypes = transportTypes;
+ paramsParcel.transportTypes = nc.getTransportTypes();
+ paramsParcel.meteredNetwork = nc.isMetered();
// Prepare to track the validation status of the DNS servers in the
// resolver config when private DNS is in opportunistic or strict mode.
if (useTls) {
@@ -397,12 +403,13 @@
}
Log.d(TAG, String.format("sendDnsConfigurationForNetwork(%d, %s, %s, %d, %d, %d, %d, "
- + "%d, %d, %s, %s)", paramsParcel.netId, Arrays.toString(paramsParcel.servers),
- Arrays.toString(paramsParcel.domains), paramsParcel.sampleValiditySeconds,
- paramsParcel.successThreshold, paramsParcel.minSamples,
- paramsParcel.maxSamples, paramsParcel.baseTimeoutMsec,
+ + "%d, %d, %s, %s, %s, %b)", paramsParcel.netId,
+ Arrays.toString(paramsParcel.servers), Arrays.toString(paramsParcel.domains),
+ paramsParcel.sampleValiditySeconds, paramsParcel.successThreshold,
+ paramsParcel.minSamples, paramsParcel.maxSamples, paramsParcel.baseTimeoutMsec,
paramsParcel.retryCount, paramsParcel.tlsName,
- Arrays.toString(paramsParcel.tlsServers)));
+ Arrays.toString(paramsParcel.tlsServers),
+ Arrays.toString(paramsParcel.transportTypes), paramsParcel.meteredNetwork));
try {
mDnsResolver.setResolverConfiguration(paramsParcel);
diff --git a/service/src/com/android/server/connectivity/KeepaliveStatsTracker.java b/service/src/com/android/server/connectivity/KeepaliveStatsTracker.java
index 0c2ed18..7a8b41b 100644
--- a/service/src/com/android/server/connectivity/KeepaliveStatsTracker.java
+++ b/service/src/com/android/server/connectivity/KeepaliveStatsTracker.java
@@ -34,6 +34,7 @@
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
+import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseIntArray;
@@ -73,6 +74,9 @@
public class KeepaliveStatsTracker {
private static final String TAG = KeepaliveStatsTracker.class.getSimpleName();
private static final int INVALID_KEEPALIVE_ID = -1;
+ // 1 hour acceptable deviation in metrics collection duration time.
+ private static final long MAX_EXPECTED_DURATION_MS =
+ AutomaticOnOffKeepaliveTracker.METRICS_COLLECTION_DURATION_MS + 1 * 60 * 60 * 1_000L;
@NonNull private final Handler mConnectivityServiceHandler;
@NonNull private final Dependencies mDependencies;
@@ -709,6 +713,36 @@
return mEnabled.get();
}
+ /**
+ * Checks the DailykeepaliveInfoReported for the following:
+ * 1. total active durations/lifetimes <= total registered durations/lifetimes.
+ * 2. Total time in Durations == total time in Carrier lifetime stats
+ * 3. The total elapsed real time spent is within expectations.
+ */
+ @VisibleForTesting
+ public boolean allMetricsExpected(DailykeepaliveInfoReported dailyKeepaliveInfoReported) {
+ int totalRegistered = 0;
+ int totalActiveDurations = 0;
+ int totalTimeSpent = 0;
+ for (DurationForNumOfKeepalive durationForNumOfKeepalive: dailyKeepaliveInfoReported
+ .getDurationPerNumOfKeepalive().getDurationForNumOfKeepaliveList()) {
+ final int n = durationForNumOfKeepalive.getNumOfKeepalive();
+ totalRegistered += durationForNumOfKeepalive.getKeepaliveRegisteredDurationsMsec() * n;
+ totalActiveDurations += durationForNumOfKeepalive.getKeepaliveActiveDurationsMsec() * n;
+ totalTimeSpent += durationForNumOfKeepalive.getKeepaliveRegisteredDurationsMsec();
+ }
+ int totalLifetimes = 0;
+ int totalActiveLifetimes = 0;
+ for (KeepaliveLifetimeForCarrier keepaliveLifetimeForCarrier: dailyKeepaliveInfoReported
+ .getKeepaliveLifetimePerCarrier().getKeepaliveLifetimeForCarrierList()) {
+ totalLifetimes += keepaliveLifetimeForCarrier.getLifetimeMsec();
+ totalActiveLifetimes += keepaliveLifetimeForCarrier.getActiveLifetimeMsec();
+ }
+ return totalActiveDurations <= totalRegistered && totalActiveLifetimes <= totalLifetimes
+ && totalLifetimes == totalRegistered && totalActiveLifetimes == totalActiveDurations
+ && totalTimeSpent <= MAX_EXPECTED_DURATION_MS;
+ }
+
/** Writes the stored metrics to ConnectivityStatsLog and resets. */
public void writeAndResetMetrics() {
ensureRunningOnHandlerThread();
@@ -724,9 +758,21 @@
}
final DailykeepaliveInfoReported dailyKeepaliveInfoReported = buildAndResetMetrics();
+ if (!allMetricsExpected(dailyKeepaliveInfoReported)) {
+ Log.wtf(TAG, "Unexpected metrics values: " + dailyKeepaliveInfoReported.toString());
+ }
mDependencies.writeStats(dailyKeepaliveInfoReported);
}
+ /** Dump KeepaliveStatsTracker state. */
+ public void dump(IndentingPrintWriter pw) {
+ ensureRunningOnHandlerThread();
+ pw.println("KeepaliveStatsTracker enabled: " + isEnabled());
+ pw.increaseIndent();
+ pw.println(buildKeepaliveMetrics().toString());
+ pw.decreaseIndent();
+ }
+
private void ensureRunningOnHandlerThread() {
if (mConnectivityServiceHandler.getLooper().getThread() != Thread.currentThread()) {
throw new IllegalStateException(
diff --git a/service/src/com/android/server/connectivity/NetworkAgentInfo.java b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
index b0ad978..7cd3cc8 100644
--- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
@@ -17,6 +17,7 @@
package com.android.server.connectivity;
import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
@@ -428,12 +429,28 @@
private final boolean mHasAutomotiveFeature;
/**
+ * Checks that a proposed update to the NCs of this NAI satisfies structural constraints.
+ *
+ * Some changes to NetworkCapabilities are structurally not supported by the stack, and
+ * NetworkAgents are absolutely never allowed to try and do them. When one of these is
+ * violated, this method returns false, which has ConnectivityService disconnect the network ;
+ * this is meant to guarantee that no implementor ever tries to do this.
+ */
+ public boolean respectsNcStructuralConstraints(@NonNull final NetworkCapabilities proposedNc) {
+ if (networkCapabilities.hasCapability(NET_CAPABILITY_LOCAL_NETWORK)
+ != proposedNc.hasCapability(NET_CAPABILITY_LOCAL_NETWORK)) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
* Sets the capabilities sent by the agent for later retrieval.
- *
- * This method does not sanitize the capabilities ; instead, use
- * {@link #getDeclaredCapabilitiesSanitized} to retrieve a sanitized
- * copy of the capabilities as they were passed here.
- *
+ * <p>
+ * This method does not sanitize the capabilities before storing them ; instead, use
+ * {@link #getDeclaredCapabilitiesSanitized} to retrieve a sanitized copy of the capabilities
+ * as they were passed here.
+ * <p>
* This method makes a defensive copy to avoid issues where the passed object is later mutated.
*
* @param caps the caps sent by the agent
@@ -1241,6 +1258,11 @@
return networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN);
}
+ /** Whether this network is a local network */
+ public boolean isLocalNetwork() {
+ return networkCapabilities.hasCapability(NET_CAPABILITY_LOCAL_NETWORK);
+ }
+
/**
* Whether this network should propagate the capabilities from its underlying networks.
* Currently only true for VPNs.
diff --git a/service/src/com/android/server/connectivity/RoutingCoordinatorService.java b/service/src/com/android/server/connectivity/RoutingCoordinatorService.java
index 92ea610..3350d2d 100644
--- a/service/src/com/android/server/connectivity/RoutingCoordinatorService.java
+++ b/service/src/com/android/server/connectivity/RoutingCoordinatorService.java
@@ -25,6 +25,7 @@
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.util.ArraySet;
+import android.util.Log;
import com.android.internal.annotations.GuardedBy;
@@ -41,6 +42,7 @@
* synchronization.
*/
public class RoutingCoordinatorService extends IRoutingCoordinator.Stub {
+ private static final String TAG = RoutingCoordinatorService.class.getSimpleName();
private final INetd mNetd;
public RoutingCoordinatorService(@NonNull INetd netd) {
@@ -162,6 +164,7 @@
throws ServiceSpecificException, RemoteException {
Objects.requireNonNull(fromIface);
Objects.requireNonNull(toIface);
+ Log.i(TAG, "Adding interface forward " + fromIface + " → " + toIface);
synchronized (mIfacesLock) {
if (mForwardedInterfaces.size() == 0) {
mNetd.ipfwdEnableForwarding("RoutingCoordinator");
@@ -190,15 +193,32 @@
throws ServiceSpecificException, RemoteException {
Objects.requireNonNull(fromIface);
Objects.requireNonNull(toIface);
+ Log.i(TAG, "Removing interface forward " + fromIface + " → " + toIface);
synchronized (mIfacesLock) {
final ForwardingPair fwp = new ForwardingPair(fromIface, toIface);
if (!mForwardedInterfaces.contains(fwp)) {
- throw new IllegalStateException("No forward set up between interfaces "
- + fromIface + " → " + toIface);
+ // This can happen when an upstream was unregisteredAfterReplacement. The forward
+ // is removed immediately when the upstream is destroyed, but later when the
+ // network actually disconnects CS does not know that and it asks for removal
+ // again.
+ // This can also happen if the network was destroyed before being set as an
+ // upstream, because then CS does not set up the forward rules seeing how the
+ // interface was removed anyway.
+ // Either way, this is benign.
+ Log.i(TAG, "No forward set up between interfaces " + fromIface + " → " + toIface);
+ return;
}
mForwardedInterfaces.remove(fwp);
- mNetd.ipfwdRemoveInterfaceForward(fromIface, toIface);
- mNetd.tetherRemoveForward(fromIface, toIface);
+ try {
+ mNetd.ipfwdRemoveInterfaceForward(fromIface, toIface);
+ } catch (RemoteException | ServiceSpecificException e) {
+ Log.e(TAG, "Exception in ipfwdRemoveInterfaceForward", e);
+ }
+ try {
+ mNetd.tetherRemoveForward(fromIface, toIface);
+ } catch (RemoteException | ServiceSpecificException e) {
+ Log.e(TAG, "Exception in tetherRemoveForward", e);
+ }
if (mForwardedInterfaces.size() == 0) {
mNetd.ipfwdDisableForwarding("RoutingCoordinator");
}
diff --git a/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java b/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java
index d99eedc..1d8b4eb 100644
--- a/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java
+++ b/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java
@@ -258,7 +258,7 @@
}
/** Add or remove |route|. */
- public static void modifyRoute(final INetd netd, final ModifyOperation op, final int netId,
+ private static void modifyRoute(final INetd netd, final ModifyOperation op, final int netId,
final RouteInfo route) {
final String ifName = route.getInterface();
final String dst = route.getDestination().toString();
diff --git a/staticlibs/device/com/android/net/module/util/FeatureVersions.java b/staticlibs/device/com/android/net/module/util/FeatureVersions.java
index 149756c..d5f8124 100644
--- a/staticlibs/device/com/android/net/module/util/FeatureVersions.java
+++ b/staticlibs/device/com/android/net/module/util/FeatureVersions.java
@@ -42,4 +42,10 @@
// M-2023-Sept on July 3rd, 2023.
public static final long FEATURE_CLAT_ADDRESS_TRANSLATE =
NETWORK_STACK_MODULE_ID + 34_09_00_000L;
+
+ // IS_UID_NETWORKING_BLOCKED is a feature in ConnectivityManager,
+ // which provides an API to access BPF maps to check whether the networking is blocked
+ // by BPF for the given uid and conditions, introduced in version M-2024-Feb on Nov 6, 2023.
+ public static final long FEATURE_IS_UID_NETWORKING_BLOCKED =
+ CONNECTIVITY_MODULE_ID + 34_14_00_000L;
}
diff --git a/staticlibs/device/com/android/net/module/util/Struct.java b/staticlibs/device/com/android/net/module/util/Struct.java
index dc0d19b..ff7a711 100644
--- a/staticlibs/device/com/android/net/module/util/Struct.java
+++ b/staticlibs/device/com/android/net/module/util/Struct.java
@@ -422,7 +422,14 @@
final byte[] address = new byte[isIpv6 ? 16 : 4];
buf.get(address);
try {
- value = InetAddress.getByAddress(address);
+ if (isIpv6) {
+ // Using Inet6Address.getByAddress since InetAddress.getByAddress converts
+ // v4-mapped v6 address to v4 address internally and returns Inet4Address.
+ value = Inet6Address.getByAddress(
+ null /* host */, address, -1 /* scope_id */);
+ } else {
+ value = InetAddress.getByAddress(address);
+ }
} catch (UnknownHostException e) {
throw new IllegalArgumentException("illegal length of IP address", e);
}
diff --git a/staticlibs/device/com/android/net/module/util/ip/NetlinkMonitor.java b/staticlibs/device/com/android/net/module/util/ip/NetlinkMonitor.java
index f882483..15a4633 100644
--- a/staticlibs/device/com/android/net/module/util/ip/NetlinkMonitor.java
+++ b/staticlibs/device/com/android/net/module/util/ip/NetlinkMonitor.java
@@ -109,7 +109,7 @@
}
}
Os.bind(fd, makeNetlinkSocketAddress(0, mBindGroups));
- NetlinkUtils.connectSocketToNetlink(fd);
+ NetlinkUtils.connectToKernel(fd);
if (DBG) {
final SocketAddress nlAddr = Os.getsockname(fd);
diff --git a/staticlibs/device/com/android/net/module/util/netlink/InetDiagMessage.java b/staticlibs/device/com/android/net/module/util/netlink/InetDiagMessage.java
index f8b4716..4f76577 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/InetDiagMessage.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/InetDiagMessage.java
@@ -33,7 +33,7 @@
import static com.android.net.module.util.netlink.NetlinkUtils.DEFAULT_RECV_BUFSIZE;
import static com.android.net.module.util.netlink.NetlinkUtils.IO_TIMEOUT_MS;
import static com.android.net.module.util.netlink.NetlinkUtils.TCP_ALIVE_STATE_FILTER;
-import static com.android.net.module.util.netlink.NetlinkUtils.connectSocketToNetlink;
+import static com.android.net.module.util.netlink.NetlinkUtils.connectToKernel;
import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_DUMP;
import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST;
@@ -266,7 +266,7 @@
FileDescriptor fd = null;
try {
fd = NetlinkUtils.netlinkSocketForProto(NETLINK_INET_DIAG);
- NetlinkUtils.connectSocketToNetlink(fd);
+ connectToKernel(fd);
uid = lookupUid(protocol, local, remote, fd);
} catch (ErrnoException | SocketException | IllegalArgumentException
| InterruptedIOException e) {
@@ -426,8 +426,8 @@
try {
dumpFd = NetlinkUtils.createNetLinkInetDiagSocket();
destroyFd = NetlinkUtils.createNetLinkInetDiagSocket();
- connectSocketToNetlink(dumpFd);
- connectSocketToNetlink(destroyFd);
+ connectToKernel(dumpFd);
+ connectToKernel(destroyFd);
for (int family : List.of(AF_INET, AF_INET6)) {
try {
diff --git a/staticlibs/device/com/android/net/module/util/netlink/NetlinkUtils.java b/staticlibs/device/com/android/net/module/util/netlink/NetlinkUtils.java
index 33bd36d..f1f30d3 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/NetlinkUtils.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/NetlinkUtils.java
@@ -153,7 +153,7 @@
final FileDescriptor fd = netlinkSocketForProto(nlProto);
try {
- connectSocketToNetlink(fd);
+ connectToKernel(fd);
sendMessage(fd, msg, 0, msg.length, IO_TIMEOUT_MS);
receiveNetlinkAck(fd);
} catch (InterruptedIOException e) {
@@ -244,7 +244,7 @@
* @throws ErrnoException if the {@code fd} could not connect to kernel successfully
* @throws SocketException if there is an error accessing a socket.
*/
- public static void connectSocketToNetlink(FileDescriptor fd)
+ public static void connectToKernel(@NonNull FileDescriptor fd)
throws ErrnoException, SocketException {
Os.connect(fd, makeNetlinkSocketAddress(0, 0));
}
diff --git a/staticlibs/framework/com/android/net/module/util/InetAddressUtils.java b/staticlibs/framework/com/android/net/module/util/InetAddressUtils.java
index 40fc59f..4b27a97 100644
--- a/staticlibs/framework/com/android/net/module/util/InetAddressUtils.java
+++ b/staticlibs/framework/com/android/net/module/util/InetAddressUtils.java
@@ -21,6 +21,7 @@
import android.util.Log;
+import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
@@ -32,6 +33,7 @@
public class InetAddressUtils {
private static final String TAG = InetAddressUtils.class.getSimpleName();
+ private static final int INET4_ADDR_LENGTH = 4;
private static final int INET6_ADDR_LENGTH = 16;
/**
@@ -93,5 +95,29 @@
}
}
+ /**
+ * Create a v4-mapped v6 address from v4 address
+ *
+ * @param v4Addr Inet4Address which is converted to v4-mapped v6 address
+ * @return v4-mapped v6 address
+ */
+ public static Inet6Address v4MappedV6Address(@NonNull final Inet4Address v4Addr) {
+ final byte[] v6AddrBytes = new byte[INET6_ADDR_LENGTH];
+ v6AddrBytes[10] = (byte) 0xFF;
+ v6AddrBytes[11] = (byte) 0xFF;
+ System.arraycopy(v4Addr.getAddress(), 0 /* srcPos */, v6AddrBytes, 12 /* dstPos */,
+ INET4_ADDR_LENGTH);
+ try {
+ // Using Inet6Address.getByAddress since InetAddress.getByAddress converts v4-mapped v6
+ // address to v4 address internally and returns Inet4Address
+ return Inet6Address.getByAddress(null /* host */, v6AddrBytes, -1 /* scope_id */);
+ } catch (UnknownHostException impossible) {
+ // getByAddress throws UnknownHostException when the argument is the invalid length
+ // but INET6_ADDR_LENGTH(16) is the valid length.
+ Log.wtf(TAG, "Failed to generate v4-mapped v6 address from " + v4Addr, impossible);
+ return null;
+ }
+ }
+
private InetAddressUtils() {}
}
diff --git a/staticlibs/netd/libnetdutils/Utils.cpp b/staticlibs/netd/libnetdutils/Utils.cpp
index 16ec882..9b0b3e0 100644
--- a/staticlibs/netd/libnetdutils/Utils.cpp
+++ b/staticlibs/netd/libnetdutils/Utils.cpp
@@ -16,6 +16,7 @@
*/
#include <map>
+#include <vector>
#include <net/if.h>
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/InetAddressUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/InetAddressUtilsTest.java
index bb2b933..66427fc 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/InetAddressUtilsTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/InetAddressUtilsTest.java
@@ -18,6 +18,7 @@
import static junit.framework.Assert.assertEquals;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -30,6 +31,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
@@ -92,4 +94,17 @@
assertEquals(localAddrStr + "%" + scopeId, updatedLocalAddr.getHostAddress());
assertEquals(scopeId, updatedLocalAddr.getScopeId());
}
+
+ @Test
+ public void testV4MappedV6Address() throws Exception {
+ final Inet4Address v4Addr = (Inet4Address) InetAddress.getByName("192.0.2.1");
+ final Inet6Address v4MappedV6Address = InetAddressUtils.v4MappedV6Address(v4Addr);
+ final byte[] expectedAddrBytes = new byte[]{
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0xff, (byte) 0xff,
+ (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01,
+ };
+ assertArrayEquals(expectedAddrBytes, v4MappedV6Address.getAddress());
+ }
}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/StructTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/StructTest.java
index b4da043..a39b7a3 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/StructTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/StructTest.java
@@ -765,6 +765,14 @@
msg.writeToBytes(ByteOrder.BIG_ENDIAN));
}
+ @Test
+ public void testV4MappedV6Address() {
+ final IpAddressMessage msg = doParsingMessageTest("c0a86401"
+ + "00000000000000000000ffffc0a86401", IpAddressMessage.class, ByteOrder.BIG_ENDIAN);
+ assertEquals(TEST_IPV4_ADDRESS, msg.ipv4Address);
+ assertEquals(InetAddressUtils.v4MappedV6Address(TEST_IPV4_ADDRESS), msg.ipv6Address);
+ }
+
public static class WrongIpAddressType extends Struct {
@Field(order = 0, type = Type.Ipv4Address) public byte[] ipv4Address;
@Field(order = 1, type = Type.Ipv6Address) public byte[] ipv6Address;
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkUtilsTest.java
index 5e9b004..5a231fc 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkUtilsTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkUtilsTest.java
@@ -68,7 +68,7 @@
final FileDescriptor fd = NetlinkUtils.netlinkSocketForProto(NETLINK_ROUTE);
assertNotNull(fd);
- NetlinkUtils.connectSocketToNetlink(fd);
+ NetlinkUtils.connectToKernel(fd);
final NetlinkSocketAddress localAddr = (NetlinkSocketAddress) Os.getsockname(fd);
assertNotNull(localAddr);
@@ -153,7 +153,7 @@
final FileDescriptor fd = NetlinkUtils.netlinkSocketForProto(NETLINK_ROUTE);
assertNotNull(fd);
- NetlinkUtils.connectSocketToNetlink(fd);
+ NetlinkUtils.connectToKernel(fd);
final NetlinkSocketAddress localAddr = (NetlinkSocketAddress) Os.getsockname(fd);
assertNotNull(localAddr);
diff --git a/staticlibs/tests/unit/src/com/android/testutils/HandlerUtilsTest.kt b/staticlibs/tests/unit/src/com/android/testutils/HandlerUtilsTest.kt
index 0f6fa48..440b836 100644
--- a/staticlibs/tests/unit/src/com/android/testutils/HandlerUtilsTest.kt
+++ b/staticlibs/tests/unit/src/com/android/testutils/HandlerUtilsTest.kt
@@ -27,7 +27,7 @@
import org.junit.runners.JUnit4
private const val ATTEMPTS = 50 // Causes testWaitForIdle to take about 150ms on aosp_crosshatch-eng
-private const val TIMEOUT_MS = 200
+private const val TIMEOUT_MS = 1000
@RunWith(JUnit4::class)
class HandlerUtilsTest {
diff --git a/tests/common/java/android/net/CaptivePortalDataTest.kt b/tests/common/java/android/net/CaptivePortalDataTest.kt
index f927380..67a523c 100644
--- a/tests/common/java/android/net/CaptivePortalDataTest.kt
+++ b/tests/common/java/android/net/CaptivePortalDataTest.kt
@@ -19,21 +19,20 @@
import android.os.Build
import androidx.test.filters.SmallTest
import com.android.modules.utils.build.SdkLevel
-import com.android.testutils.assertParcelingIsLossless
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.assertParcelingIsLossless
+import kotlin.test.assertEquals
+import kotlin.test.assertNotEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import kotlin.test.assertEquals
-import kotlin.test.assertNotEquals
@SmallTest
@RunWith(DevSdkIgnoreRunner::class)
-@IgnoreUpTo(Build.VERSION_CODES.Q)
class CaptivePortalDataTest {
@Rule @JvmField
val ignoreRule = DevSdkIgnoreRule()
diff --git a/tests/common/java/android/net/KeepalivePacketDataTest.kt b/tests/common/java/android/net/KeepalivePacketDataTest.kt
index 403d6b5..97a45fc 100644
--- a/tests/common/java/android/net/KeepalivePacketDataTest.kt
+++ b/tests/common/java/android/net/KeepalivePacketDataTest.kt
@@ -17,27 +17,20 @@
import android.net.InvalidPacketException.ERROR_INVALID_IP_ADDRESS
import android.net.InvalidPacketException.ERROR_INVALID_PORT
-import android.os.Build
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
-import com.android.testutils.DevSdkIgnoreRule
-import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.NonNullTestUtils
import java.net.InetAddress
import java.util.Arrays
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Assert.fail
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@SmallTest
class KeepalivePacketDataTest {
- @Rule @JvmField
- val ignoreRule: DevSdkIgnoreRule = DevSdkIgnoreRule()
-
private val INVALID_PORT = 65537
private val TEST_DST_PORT = 4244
private val TEST_SRC_PORT = 4243
@@ -60,7 +53,6 @@
NonNullTestUtils.nullUnsafe(dstAddress), dstPort, data)
@Test
- @IgnoreUpTo(Build.VERSION_CODES.Q)
fun testConstructor() {
try {
TestKeepalivePacketData(srcAddress = null)
@@ -99,22 +91,17 @@
}
@Test
- @IgnoreUpTo(Build.VERSION_CODES.Q)
fun testSrcAddress() = assertEquals(TEST_SRC_ADDRV4, TestKeepalivePacketData().srcAddress)
@Test
- @IgnoreUpTo(Build.VERSION_CODES.Q)
fun testDstAddress() = assertEquals(TEST_DST_ADDRV4, TestKeepalivePacketData().dstAddress)
@Test
- @IgnoreUpTo(Build.VERSION_CODES.Q)
fun testSrcPort() = assertEquals(TEST_SRC_PORT, TestKeepalivePacketData().srcPort)
@Test
- @IgnoreUpTo(Build.VERSION_CODES.Q)
fun testDstPort() = assertEquals(TEST_DST_PORT, TestKeepalivePacketData().dstPort)
@Test
- @IgnoreUpTo(Build.VERSION_CODES.Q)
fun testPacket() = assertTrue(Arrays.equals(TESTBYTES, TestKeepalivePacketData().packet))
}
diff --git a/tests/common/java/android/net/LinkPropertiesTest.java b/tests/common/java/android/net/LinkPropertiesTest.java
index d2e7c99..8f14572 100644
--- a/tests/common/java/android/net/LinkPropertiesTest.java
+++ b/tests/common/java/android/net/LinkPropertiesTest.java
@@ -134,13 +134,10 @@
assertFalse(lp.isIpv4Provisioned());
assertFalse(lp.isIpv6Provisioned());
assertFalse(lp.isPrivateDnsActive());
-
- if (SdkLevel.isAtLeastR()) {
- assertNull(lp.getDhcpServerAddress());
- assertFalse(lp.isWakeOnLanSupported());
- assertNull(lp.getCaptivePortalApiUrl());
- assertNull(lp.getCaptivePortalData());
- }
+ assertNull(lp.getDhcpServerAddress());
+ assertFalse(lp.isWakeOnLanSupported());
+ assertNull(lp.getCaptivePortalApiUrl());
+ assertNull(lp.getCaptivePortalData());
}
private LinkProperties makeTestObject() {
@@ -162,12 +159,10 @@
lp.setMtu(MTU);
lp.setTcpBufferSizes(TCP_BUFFER_SIZES);
lp.setNat64Prefix(new IpPrefix("2001:db8:0:64::/96"));
- if (SdkLevel.isAtLeastR()) {
- lp.setDhcpServerAddress(DHCPSERVER);
- lp.setWakeOnLanSupported(true);
- lp.setCaptivePortalApiUrl(CAPPORT_API_URL);
- lp.setCaptivePortalData((CaptivePortalData) getCaptivePortalData());
- }
+ lp.setDhcpServerAddress(DHCPSERVER);
+ lp.setWakeOnLanSupported(true);
+ lp.setCaptivePortalApiUrl(CAPPORT_API_URL);
+ lp.setCaptivePortalData((CaptivePortalData) getCaptivePortalData());
return lp;
}
@@ -206,19 +201,17 @@
assertTrue(source.isIdenticalTcpBufferSizes(target));
assertTrue(target.isIdenticalTcpBufferSizes(source));
- if (SdkLevel.isAtLeastR()) {
- assertTrue(source.isIdenticalDhcpServerAddress(target));
- assertTrue(source.isIdenticalDhcpServerAddress(source));
+ assertTrue(source.isIdenticalDhcpServerAddress(target));
+ assertTrue(source.isIdenticalDhcpServerAddress(source));
- assertTrue(source.isIdenticalWakeOnLan(target));
- assertTrue(target.isIdenticalWakeOnLan(source));
+ assertTrue(source.isIdenticalWakeOnLan(target));
+ assertTrue(target.isIdenticalWakeOnLan(source));
- assertTrue(source.isIdenticalCaptivePortalApiUrl(target));
- assertTrue(target.isIdenticalCaptivePortalApiUrl(source));
+ assertTrue(source.isIdenticalCaptivePortalApiUrl(target));
+ assertTrue(target.isIdenticalCaptivePortalApiUrl(source));
- assertTrue(source.isIdenticalCaptivePortalData(target));
- assertTrue(target.isIdenticalCaptivePortalData(source));
- }
+ assertTrue(source.isIdenticalCaptivePortalData(target));
+ assertTrue(target.isIdenticalCaptivePortalData(source));
// Check result of equals().
assertTrue(source.equals(target));
@@ -1017,7 +1010,7 @@
assertParcelingIsLossless(source);
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ @Test
public void testLinkPropertiesParcelable() throws Exception {
final LinkProperties source = makeLinkPropertiesForParceling();
@@ -1035,7 +1028,7 @@
}
// Parceling of the scope was broken until Q-QPR2
- @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ @Test
public void testLinkLocalDnsServerParceling() throws Exception {
final String strAddress = "fe80::1%lo";
final LinkProperties lp = new LinkProperties();
@@ -1158,7 +1151,7 @@
assertFalse(lp.isPrivateDnsActive());
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ @Test
public void testDhcpServerAddress() {
final LinkProperties lp = makeTestObject();
assertEquals(DHCPSERVER, lp.getDhcpServerAddress());
@@ -1167,7 +1160,7 @@
assertNull(lp.getDhcpServerAddress());
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ @Test
public void testWakeOnLanSupported() {
final LinkProperties lp = makeTestObject();
assertTrue(lp.isWakeOnLanSupported());
@@ -1176,7 +1169,7 @@
assertFalse(lp.isWakeOnLanSupported());
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ @Test
public void testCaptivePortalApiUrl() {
final LinkProperties lp = makeTestObject();
assertEquals(CAPPORT_API_URL, lp.getCaptivePortalApiUrl());
@@ -1185,7 +1178,7 @@
assertNull(lp.getCaptivePortalApiUrl());
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ @Test
public void testCaptivePortalData() {
final LinkProperties lp = makeTestObject();
assertEquals(getCaptivePortalData(), lp.getCaptivePortalData());
@@ -1238,7 +1231,7 @@
assertTrue(Ipv6.hasIpv6DnsServer());
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ @Test
public void testHasIpv4UnreachableDefaultRoute() {
final LinkProperties lp = makeTestObject();
assertFalse(lp.hasIpv4UnreachableDefaultRoute());
@@ -1249,7 +1242,7 @@
assertFalse(lp.hasIpv6UnreachableDefaultRoute());
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ @Test
public void testHasIpv6UnreachableDefaultRoute() {
final LinkProperties lp = makeTestObject();
assertFalse(lp.hasIpv6UnreachableDefaultRoute());
diff --git a/tests/common/java/android/net/MatchAllNetworkSpecifierTest.kt b/tests/common/java/android/net/MatchAllNetworkSpecifierTest.kt
index 4a4859d..70adbd7 100644
--- a/tests/common/java/android/net/MatchAllNetworkSpecifierTest.kt
+++ b/tests/common/java/android/net/MatchAllNetworkSpecifierTest.kt
@@ -52,7 +52,6 @@
}
@Test
- @IgnoreUpTo(Build.VERSION_CODES.Q)
@IgnoreAfter(Build.VERSION_CODES.R)
// Only run this test on Android R.
// The method - satisfiedBy() has changed to canBeSatisfiedBy() starting from Android R, so the
diff --git a/tests/common/java/android/net/NattKeepalivePacketDataTest.kt b/tests/common/java/android/net/NattKeepalivePacketDataTest.kt
index e5806a6..1148eff 100644
--- a/tests/common/java/android/net/NattKeepalivePacketDataTest.kt
+++ b/tests/common/java/android/net/NattKeepalivePacketDataTest.kt
@@ -82,7 +82,7 @@
dstPort: Int = NATT_PORT
) = NattKeepalivePacketData.nattKeepalivePacket(srcAddress, srcPort, dstAddress, dstPort)
- @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ @Test
fun testConstructor() {
assertFailsWith<InvalidPacketException>(
"Dst port is not NATT port should cause exception") {
@@ -132,12 +132,12 @@
assertEquals(TEST_ADDRV6, packet2.dstAddress)
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ @Test
fun testParcel() {
assertParcelingIsLossless(nattKeepalivePacket())
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ @Test
fun testEquals() {
assertEqualBothWays(nattKeepalivePacket(), nattKeepalivePacket())
assertNotEquals(nattKeepalivePacket(dstAddress = TEST_SRC_ADDRV4), nattKeepalivePacket())
@@ -146,7 +146,7 @@
assertNotEquals(nattKeepalivePacket(srcPort = TEST_PORT2), nattKeepalivePacket())
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ @Test
fun testHashCode() {
assertEquals(nattKeepalivePacket().hashCode(), nattKeepalivePacket().hashCode())
}
diff --git a/tests/common/java/android/net/NetworkAgentConfigTest.kt b/tests/common/java/android/net/NetworkAgentConfigTest.kt
index c05cdbd..d640a73 100644
--- a/tests/common/java/android/net/NetworkAgentConfigTest.kt
+++ b/tests/common/java/android/net/NetworkAgentConfigTest.kt
@@ -16,19 +16,15 @@
package android.net
-import android.os.Build
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.modules.utils.build.SdkLevel.isAtLeastS
import com.android.modules.utils.build.SdkLevel.isAtLeastT
import com.android.testutils.ConnectivityModuleTest
-import com.android.testutils.DevSdkIgnoreRule
-import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.assertParcelingIsLossless
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -36,10 +32,7 @@
@SmallTest
@ConnectivityModuleTest
class NetworkAgentConfigTest {
- @Rule @JvmField
- val ignoreRule = DevSdkIgnoreRule()
-
- @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ @Test
fun testParcelNetworkAgentConfig() {
val config = NetworkAgentConfig.Builder().apply {
setExplicitlySelected(true)
@@ -58,7 +51,7 @@
assertParcelingIsLossless(config)
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ @Test
fun testBuilder() {
val testExtraInfo = "mylegacyExtraInfo"
val config = NetworkAgentConfig.Builder().apply {
diff --git a/tests/common/java/android/net/NetworkCapabilitiesTest.java b/tests/common/java/android/net/NetworkCapabilitiesTest.java
index bec9a4a..3a3459b 100644
--- a/tests/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/common/java/android/net/NetworkCapabilitiesTest.java
@@ -61,7 +61,6 @@
import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
import static android.os.Process.INVALID_UID;
-import static com.android.modules.utils.build.SdkLevel.isAtLeastR;
import static com.android.modules.utils.build.SdkLevel.isAtLeastS;
import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
import static com.android.modules.utils.build.SdkLevel.isAtLeastV;
@@ -382,10 +381,9 @@
netCap.setSubscriptionIds(Set.of(TEST_SUBID1, TEST_SUBID2));
netCap.setUids(uids);
}
- if (isAtLeastR()) {
- netCap.setOwnerUid(123);
- netCap.setAdministratorUids(new int[] {5, 11});
- }
+
+ netCap.setOwnerUid(123);
+ netCap.setAdministratorUids(new int[] {5, 11});
assertParcelingIsLossless(netCap);
netCap.setSSID(TEST_SSID);
testParcelSane(netCap);
@@ -397,10 +395,8 @@
.addCapability(NET_CAPABILITY_INTERNET)
.addCapability(NET_CAPABILITY_EIMS)
.addCapability(NET_CAPABILITY_NOT_METERED);
- if (isAtLeastR()) {
- netCap.setRequestorPackageName("com.android.test");
- netCap.setRequestorUid(9304);
- }
+ netCap.setRequestorPackageName("com.android.test");
+ netCap.setRequestorUid(9304);
assertParcelingIsLossless(netCap);
netCap.setSSID(TEST_SSID);
testParcelSane(netCap);
@@ -820,16 +816,12 @@
assertTrue(nc2.hasForbiddenCapability(NET_CAPABILITY_NOT_ROAMING));
}
- if (isAtLeastR()) {
- assertTrue(TEST_SSID.equals(nc2.getSsid()));
- }
-
+ assertTrue(TEST_SSID.equals(nc2.getSsid()));
nc1.setSSID(DIFFERENT_TEST_SSID);
nc2.set(nc1);
assertEquals(nc1, nc2);
- if (isAtLeastR()) {
- assertTrue(DIFFERENT_TEST_SSID.equals(nc2.getSsid()));
- }
+ assertTrue(DIFFERENT_TEST_SSID.equals(nc2.getSsid()));
+
if (isAtLeastS()) {
nc1.setUids(uidRanges(10, 13));
} else {
diff --git a/tests/common/java/android/net/NetworkProviderTest.kt b/tests/common/java/android/net/NetworkProviderTest.kt
index c6a7346..0d35960 100644
--- a/tests/common/java/android/net/NetworkProviderTest.kt
+++ b/tests/common/java/android/net/NetworkProviderTest.kt
@@ -39,6 +39,12 @@
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.DevSdkIgnoreRunner
import com.android.testutils.TestableNetworkOfferCallback
+import java.util.UUID
+import java.util.concurrent.Executor
+import java.util.concurrent.RejectedExecutionException
+import kotlin.test.assertEquals
+import kotlin.test.assertNotEquals
+import kotlin.test.fail
import org.junit.After
import org.junit.Before
import org.junit.Rule
@@ -47,12 +53,6 @@
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.mock
import org.mockito.Mockito.verifyNoMoreInteractions
-import java.util.UUID
-import java.util.concurrent.Executor
-import java.util.concurrent.RejectedExecutionException
-import kotlin.test.assertEquals
-import kotlin.test.assertNotEquals
-import kotlin.test.fail
private const val DEFAULT_TIMEOUT_MS = 5000L
private const val DEFAULT_NO_CALLBACK_TIMEOUT_MS = 200L
@@ -62,7 +62,6 @@
private val PROVIDER_NAME = "NetworkProviderTest"
@RunWith(DevSdkIgnoreRunner::class)
-@IgnoreUpTo(Build.VERSION_CODES.Q)
@ConnectivityModuleTest
class NetworkProviderTest {
@Rule @JvmField
diff --git a/tests/common/java/android/net/NetworkSpecifierTest.kt b/tests/common/java/android/net/NetworkSpecifierTest.kt
index b960417..7edb474 100644
--- a/tests/common/java/android/net/NetworkSpecifierTest.kt
+++ b/tests/common/java/android/net/NetworkSpecifierTest.kt
@@ -15,21 +15,18 @@
*/
package android.net
-import android.os.Build
import androidx.test.filters.SmallTest
import com.android.testutils.ConnectivityModuleTest
-import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.DevSdkIgnoreRunner
-import org.junit.Test
-import org.junit.runner.RunWith
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNotEquals
import kotlin.test.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
@SmallTest
@RunWith(DevSdkIgnoreRunner::class)
-@IgnoreUpTo(Build.VERSION_CODES.Q)
@ConnectivityModuleTest
class NetworkSpecifierTest {
private class TestNetworkSpecifier(
diff --git a/tests/common/java/android/net/NetworkStackTest.java b/tests/common/java/android/net/NetworkStackTest.java
index f8f9c72..13550f9 100644
--- a/tests/common/java/android/net/NetworkStackTest.java
+++ b/tests/common/java/android/net/NetworkStackTest.java
@@ -17,16 +17,11 @@
import static org.junit.Assert.assertEquals;
-import android.os.Build;
import android.os.IBinder;
import androidx.test.runner.AndroidJUnit4;
-import com.android.testutils.DevSdkIgnoreRule;
-import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
-
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -34,16 +29,13 @@
@RunWith(AndroidJUnit4.class)
public class NetworkStackTest {
- @Rule
- public DevSdkIgnoreRule mDevSdkIgnoreRule = new DevSdkIgnoreRule();
-
@Mock private IBinder mConnectorBinder;
@Before public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ @Test
public void testGetService() {
NetworkStack.setServiceForTest(mConnectorBinder);
assertEquals(NetworkStack.getService(), mConnectorBinder);
diff --git a/tests/common/java/android/net/NetworkTest.java b/tests/common/java/android/net/NetworkTest.java
index c102cb3..86d2463 100644
--- a/tests/common/java/android/net/NetworkTest.java
+++ b/tests/common/java/android/net/NetworkTest.java
@@ -161,8 +161,7 @@
assertEquals(16290598925L, three.getNetworkHandle());
}
- // getNetId() did not exist in Q
- @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ @Test
public void testGetNetId() {
assertEquals(1234, new Network(1234).getNetId());
assertEquals(2345, new Network(2345, true).getNetId());
diff --git a/tests/common/java/android/net/RouteInfoTest.java b/tests/common/java/android/net/RouteInfoTest.java
index 5b28b84..154dc4c 100644
--- a/tests/common/java/android/net/RouteInfoTest.java
+++ b/tests/common/java/android/net/RouteInfoTest.java
@@ -31,17 +31,11 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import android.os.Build;
-
-import androidx.core.os.BuildCompat;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.testutils.ConnectivityModuleTest;
-import com.android.testutils.DevSdkIgnoreRule;
-import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -53,9 +47,6 @@
@SmallTest
@ConnectivityModuleTest
public class RouteInfoTest {
- @Rule
- public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
-
private static final int INVALID_ROUTE_TYPE = -1;
private InetAddress Address(String addr) {
@@ -66,11 +57,6 @@
return new IpPrefix(prefix);
}
- private static boolean isAtLeastR() {
- // BuildCompat.isAtLeastR is documented to return false on release SDKs (including R)
- return Build.VERSION.SDK_INT > Build.VERSION_CODES.Q || BuildCompat.isAtLeastR();
- }
-
@Test
public void testConstructor() {
RouteInfo r;
@@ -204,130 +190,108 @@
assertTrue(r.isDefaultRoute());
assertTrue(r.isIPv4Default());
assertFalse(r.isIPv6Default());
- if (isAtLeastR()) {
- assertFalse(r.isIPv4UnreachableDefault());
- assertFalse(r.isIPv6UnreachableDefault());
- }
+
+ assertFalse(r.isIPv4UnreachableDefault());
+ assertFalse(r.isIPv6UnreachableDefault());
+
r = new RouteInfo(Prefix("::/0"), Address("::"), "wlan0");
assertFalse(r.isHostRoute());
assertTrue(r.isDefaultRoute());
assertFalse(r.isIPv4Default());
assertTrue(r.isIPv6Default());
- if (isAtLeastR()) {
- assertFalse(r.isIPv4UnreachableDefault());
- assertFalse(r.isIPv6UnreachableDefault());
- }
+ assertFalse(r.isIPv4UnreachableDefault());
+ assertFalse(r.isIPv6UnreachableDefault());
r = new RouteInfo(Prefix("192.0.2.0/24"), null, "wlan0");
assertFalse(r.isHostRoute());
assertFalse(r.isDefaultRoute());
assertFalse(r.isIPv4Default());
assertFalse(r.isIPv6Default());
- if (isAtLeastR()) {
- assertFalse(r.isIPv4UnreachableDefault());
- assertFalse(r.isIPv6UnreachableDefault());
- }
+ assertFalse(r.isIPv4UnreachableDefault());
+ assertFalse(r.isIPv6UnreachableDefault());
r = new RouteInfo(Prefix("2001:db8::/48"), null, "wlan0");
assertFalse(r.isHostRoute());
assertFalse(r.isDefaultRoute());
assertFalse(r.isIPv4Default());
assertFalse(r.isIPv6Default());
- if (isAtLeastR()) {
- assertFalse(r.isIPv4UnreachableDefault());
- assertFalse(r.isIPv6UnreachableDefault());
- }
+
+ assertFalse(r.isIPv4UnreachableDefault());
+ assertFalse(r.isIPv6UnreachableDefault());
r = new RouteInfo(Prefix("192.0.2.0/32"), Address("0.0.0.0"), "wlan0");
assertTrue(r.isHostRoute());
assertFalse(r.isDefaultRoute());
assertFalse(r.isIPv4Default());
assertFalse(r.isIPv6Default());
- if (isAtLeastR()) {
- assertFalse(r.isIPv4UnreachableDefault());
- assertFalse(r.isIPv6UnreachableDefault());
- }
+
+ assertFalse(r.isIPv4UnreachableDefault());
+ assertFalse(r.isIPv6UnreachableDefault());
r = new RouteInfo(Prefix("2001:db8::/128"), Address("::"), "wlan0");
assertTrue(r.isHostRoute());
assertFalse(r.isDefaultRoute());
assertFalse(r.isIPv4Default());
assertFalse(r.isIPv6Default());
- if (isAtLeastR()) {
- assertFalse(r.isIPv4UnreachableDefault());
- assertFalse(r.isIPv6UnreachableDefault());
- }
+ assertFalse(r.isIPv4UnreachableDefault());
+ assertFalse(r.isIPv6UnreachableDefault());
r = new RouteInfo(Prefix("192.0.2.0/32"), null, "wlan0");
assertTrue(r.isHostRoute());
assertFalse(r.isDefaultRoute());
assertFalse(r.isIPv4Default());
assertFalse(r.isIPv6Default());
- if (isAtLeastR()) {
- assertFalse(r.isIPv4UnreachableDefault());
- assertFalse(r.isIPv6UnreachableDefault());
- }
+ assertFalse(r.isIPv4UnreachableDefault());
+ assertFalse(r.isIPv6UnreachableDefault());
r = new RouteInfo(Prefix("2001:db8::/128"), null, "wlan0");
assertTrue(r.isHostRoute());
assertFalse(r.isDefaultRoute());
assertFalse(r.isIPv4Default());
assertFalse(r.isIPv6Default());
- if (isAtLeastR()) {
- assertFalse(r.isIPv4UnreachableDefault());
- assertFalse(r.isIPv6UnreachableDefault());
- }
+ assertFalse(r.isIPv4UnreachableDefault());
+ assertFalse(r.isIPv6UnreachableDefault());
r = new RouteInfo(Prefix("::/128"), Address("fe80::"), "wlan0");
assertTrue(r.isHostRoute());
assertFalse(r.isDefaultRoute());
assertFalse(r.isIPv4Default());
assertFalse(r.isIPv6Default());
- if (isAtLeastR()) {
- assertFalse(r.isIPv4UnreachableDefault());
- assertFalse(r.isIPv6UnreachableDefault());
- }
+ assertFalse(r.isIPv4UnreachableDefault());
+ assertFalse(r.isIPv6UnreachableDefault());
r = new RouteInfo(Prefix("0.0.0.0/32"), Address("192.0.2.1"), "wlan0");
assertTrue(r.isHostRoute());
assertFalse(r.isDefaultRoute());
assertFalse(r.isIPv4Default());
assertFalse(r.isIPv6Default());
- if (isAtLeastR()) {
- assertFalse(r.isIPv4UnreachableDefault());
- assertFalse(r.isIPv6UnreachableDefault());
- }
+ assertFalse(r.isIPv4UnreachableDefault());
+ assertFalse(r.isIPv6UnreachableDefault());
r = new RouteInfo(Prefix("0.0.0.0/32"), Address("192.0.2.1"), "wlan0");
assertTrue(r.isHostRoute());
assertFalse(r.isDefaultRoute());
assertFalse(r.isIPv4Default());
assertFalse(r.isIPv6Default());
- if (isAtLeastR()) {
- assertFalse(r.isIPv4UnreachableDefault());
- assertFalse(r.isIPv6UnreachableDefault());
- }
+ assertFalse(r.isIPv4UnreachableDefault());
+ assertFalse(r.isIPv6UnreachableDefault());
r = new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE);
assertFalse(r.isHostRoute());
assertFalse(r.isDefaultRoute());
assertFalse(r.isIPv4Default());
assertFalse(r.isIPv6Default());
- if (isAtLeastR()) {
- assertTrue(r.isIPv4UnreachableDefault());
- assertFalse(r.isIPv6UnreachableDefault());
- }
+ assertTrue(r.isIPv4UnreachableDefault());
+ assertFalse(r.isIPv6UnreachableDefault());
r = new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE);
assertFalse(r.isHostRoute());
assertFalse(r.isDefaultRoute());
assertFalse(r.isIPv4Default());
assertFalse(r.isIPv6Default());
- if (isAtLeastR()) {
- assertFalse(r.isIPv4UnreachableDefault());
- assertTrue(r.isIPv6UnreachableDefault());
- }
+ assertFalse(r.isIPv4UnreachableDefault());
+ assertTrue(r.isIPv6UnreachableDefault());
}
@Test
@@ -376,14 +340,14 @@
assertParcelingIsLossless(r);
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ @Test
public void testMtuParceling() {
final RouteInfo r = new RouteInfo(Prefix("ff02::1/128"), Address("2001:db8::"), "testiface",
RTN_UNREACHABLE, 1450 /* mtu */);
assertParcelingIsLossless(r);
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ @Test
public void testMtu() {
RouteInfo r;
r = new RouteInfo(Prefix("0.0.0.0/0"), Address("0.0.0.0"), "wlan0",
@@ -394,7 +358,7 @@
assertEquals(0, r.getMtu());
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ @Test
public void testRouteKey() {
RouteInfo.RouteKey k1, k2;
// Only prefix, null gateway and null interface
diff --git a/tests/common/java/android/net/netstats/NetworkStatsApiTest.kt b/tests/common/java/android/net/netstats/NetworkStatsApiTest.kt
index c90b1aa..8cef6aa 100644
--- a/tests/common/java/android/net/netstats/NetworkStatsApiTest.kt
+++ b/tests/common/java/android/net/netstats/NetworkStatsApiTest.kt
@@ -28,25 +28,18 @@
import android.net.NetworkStats.SET_DEFAULT
import android.net.NetworkStats.SET_FOREGROUND
import android.net.NetworkStats.TAG_NONE
-import android.os.Build
import androidx.test.filters.SmallTest
-import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.assertNetworkStatsEquals
import com.android.testutils.assertParcelingIsLossless
+import kotlin.test.assertEquals
import org.junit.Before
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
-import kotlin.test.assertEquals
@RunWith(JUnit4::class)
@SmallTest
class NetworkStatsApiTest {
- @Rule
- @JvmField
- val ignoreRule = DevSdkIgnoreRule(ignoreClassUpTo = Build.VERSION_CODES.Q)
-
private val testStatsEmpty = NetworkStats(0L, 0)
// Note that these variables need to be initialized outside of constructor, initialize
diff --git a/tests/common/java/android/net/util/SocketUtilsTest.kt b/tests/common/java/android/net/util/SocketUtilsTest.kt
index aaf97f3..520cf07 100644
--- a/tests/common/java/android/net/util/SocketUtilsTest.kt
+++ b/tests/common/java/android/net/util/SocketUtilsTest.kt
@@ -16,7 +16,6 @@
package android.net.util
-import android.os.Build
import android.system.NetlinkSocketAddress
import android.system.Os
import android.system.OsConstants.AF_INET
@@ -27,13 +26,10 @@
import android.system.PacketSocketAddress
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
-import com.android.testutils.DevSdkIgnoreRule
-import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Assert.fail
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -44,9 +40,6 @@
@RunWith(AndroidJUnit4::class)
@SmallTest
class SocketUtilsTest {
- @Rule @JvmField
- val ignoreRule = DevSdkIgnoreRule()
-
@Test
fun testMakeNetlinkSocketAddress() {
val nlAddress = SocketUtils.makeNetlinkSocketAddress(TEST_PORT, RTMGRP_NEIGH)
@@ -67,7 +60,7 @@
assertTrue("Not PacketSocketAddress object", pkAddress2 is PacketSocketAddress)
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ @Test
fun testMakePacketSocketAddress() {
val pkAddress = SocketUtils.makePacketSocketAddress(
ETH_P_ALL, TEST_INDEX, ByteArray(6) { FF_BYTE })
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index d92fb01..bb32052 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -37,6 +37,7 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.Instrumentation;
import android.app.NotificationManager;
@@ -438,6 +439,42 @@
}
/**
+ * Asserts whether the network is blocked by accessing bpf maps if command-line tool supports.
+ */
+ void assertNetworkAccessBlockedByBpf(boolean expectBlocked, int uid, boolean metered) {
+ final String result;
+ try {
+ result = executeShellCommand(
+ "cmd network_stack is-uid-networking-blocked " + uid + " " + metered);
+ } catch (AssertionError e) {
+ // If NetworkStack is too old to support this command, ignore and continue
+ // this test to verify other parts.
+ if (e.getMessage().contains("No shell command implementation.")) {
+ return;
+ }
+ throw e;
+ }
+
+ // Tethering module is too old.
+ if (result.contains("API is unsupported")) {
+ return;
+ }
+
+ assertEquals(expectBlocked, parseBooleanOrThrow(result.trim()));
+ }
+
+ /**
+ * Similar to {@link Boolean#parseBoolean} but throws when the input
+ * is unexpected instead of returning false.
+ */
+ private static boolean parseBooleanOrThrow(@NonNull String s) {
+ // Don't use Boolean.parseBoolean
+ if ("true".equalsIgnoreCase(s)) return true;
+ if ("false".equalsIgnoreCase(s)) return false;
+ throw new IllegalArgumentException("Unexpected: " + s);
+ }
+
+ /**
* Checks whether the network is available as expected.
*
* @return error message with the mismatch (or empty if assertion passed).
@@ -752,27 +789,24 @@
assertDelayedShellCommand("dumpsys deviceidle get deep", enabled ? "IDLE" : "ACTIVE");
}
- protected void setAppIdle(boolean enabled) throws Exception {
+ protected void setAppIdle(boolean isIdle) throws Exception {
+ setAppIdleNoAssert(isIdle);
+ assertAppIdle(isIdle);
+ }
+
+ protected void setAppIdleNoAssert(boolean isIdle) throws Exception {
if (!isAppStandbySupported()) {
return;
}
- Log.i(TAG, "Setting app idle to " + enabled);
- executeSilentShellCommand("am set-inactive " + TEST_APP2_PKG + " " + enabled );
- assertAppIdle(enabled);
+ Log.i(TAG, "Setting app idle to " + isIdle);
+ final String bucketName = isIdle ? "rare" : "active";
+ executeSilentShellCommand("am set-standby-bucket " + TEST_APP2_PKG + " " + bucketName);
}
- protected void setAppIdleNoAssert(boolean enabled) throws Exception {
- if (!isAppStandbySupported()) {
- return;
- }
- Log.i(TAG, "Setting app idle to " + enabled);
- executeSilentShellCommand("am set-inactive " + TEST_APP2_PKG + " " + enabled );
- }
-
- protected void assertAppIdle(boolean enabled) throws Exception {
+ protected void assertAppIdle(boolean isIdle) throws Exception {
try {
assertDelayedShellCommand("am get-inactive " + TEST_APP2_PKG,
- 30 /* maxTries */, 1 /* napTimeSeconds */, "Idle=" + enabled);
+ 30 /* maxTries */, 1 /* napTimeSeconds */, "Idle=" + isIdle);
} catch (Throwable e) {
throw e;
}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
index 0715e32..ab3cf14 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
@@ -236,16 +236,19 @@
// Enable restrict background
setRestrictBackground(true);
assertBackgroundNetworkAccess(false);
+ assertNetworkAccessBlockedByBpf(true, mUid, true /* metered */);
mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, true);
// Add to whitelist
addRestrictBackgroundWhitelist(mUid);
assertBackgroundNetworkAccess(true);
+ assertNetworkAccessBlockedByBpf(false, mUid, true /* metered */);
mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, false);
// Remove from whitelist
removeRestrictBackgroundWhitelist(mUid);
assertBackgroundNetworkAccess(false);
+ assertNetworkAccessBlockedByBpf(true, mUid, true /* metered */);
mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, true);
} finally {
mMeterednessConfiguration.resetNetworkMeteredness();
@@ -257,11 +260,13 @@
true /* hasCapability */, NET_CAPABILITY_NOT_METERED);
try {
assertBackgroundNetworkAccess(true);
+ assertNetworkAccessBlockedByBpf(false, mUid, false /* metered */);
mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, false);
// Disable restrict background, should not trigger callback
setRestrictBackground(false);
assertBackgroundNetworkAccess(true);
+ assertNetworkAccessBlockedByBpf(false, mUid, false /* metered */);
} finally {
mMeterednessConfiguration.resetNetworkMeteredness();
}
@@ -275,11 +280,13 @@
setBatterySaverMode(true);
assertBackgroundNetworkAccess(false);
mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, true);
+ assertNetworkAccessBlockedByBpf(true, mUid, true /* metered */);
// Disable Power Saver
setBatterySaverMode(false);
assertBackgroundNetworkAccess(true);
mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, false);
+ assertNetworkAccessBlockedByBpf(false, mUid, true /* metered */);
} finally {
mMeterednessConfiguration.resetNetworkMeteredness();
}
@@ -293,11 +300,13 @@
setBatterySaverMode(true);
assertBackgroundNetworkAccess(false);
mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, true);
+ assertNetworkAccessBlockedByBpf(true, mUid, false /* metered */);
// Disable Power Saver
setBatterySaverMode(false);
assertBackgroundNetworkAccess(true);
mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, false);
+ assertNetworkAccessBlockedByBpf(false, mUid, false /* metered */);
} finally {
mMeterednessConfiguration.resetNetworkMeteredness();
}
diff --git a/tests/mts/bpf_existence_test.cpp b/tests/mts/bpf_existence_test.cpp
index cff4d6f..51a4eca 100644
--- a/tests/mts/bpf_existence_test.cpp
+++ b/tests/mts/bpf_existence_test.cpp
@@ -94,6 +94,7 @@
NETD "map_netd_app_uid_stats_map",
NETD "map_netd_configuration_map",
NETD "map_netd_cookie_tag_map",
+ NETD "map_netd_data_saver_enabled_map",
NETD "map_netd_iface_index_name_map",
NETD "map_netd_iface_stats_map",
NETD "map_netd_ingress_discard_map",
diff --git a/tests/native/utilities/firewall.cpp b/tests/native/utilities/firewall.cpp
index e4669cb..22f83e8 100644
--- a/tests/native/utilities/firewall.cpp
+++ b/tests/native/utilities/firewall.cpp
@@ -27,6 +27,12 @@
result = mUidOwnerMap.init(UID_OWNER_MAP_PATH);
EXPECT_RESULT_OK(result) << "init mUidOwnerMap failed";
+
+ // Do not check whether DATA_SAVER_ENABLED_MAP_PATH init succeeded or failed since the map is
+ // defined in tethering module, but the user of this class may be in other modules. For example,
+ // DNS resolver tests statically link to this class. But when running MTS, the test infra
+ // installs only DNS resolver module without installing tethering module together.
+ mDataSaverEnabledMap.init(DATA_SAVER_ENABLED_MAP_PATH);
}
Firewall* Firewall::getInstance() {
@@ -116,3 +122,28 @@
}
return {};
}
+
+Result<bool> Firewall::getDataSaverSetting() {
+ std::lock_guard guard(mMutex);
+ if (!mDataSaverEnabledMap.isValid()) {
+ return Errorf("init mDataSaverEnabledMap failed");
+ }
+
+ auto dataSaverSetting = mDataSaverEnabledMap.readValue(DATA_SAVER_ENABLED_KEY);
+ if (!dataSaverSetting.ok()) {
+ return Errorf("Cannot read the data saver setting: {}", dataSaverSetting.error().message());
+ }
+ return dataSaverSetting;
+}
+
+Result<void> Firewall::setDataSaver(bool enabled) {
+ std::lock_guard guard(mMutex);
+ if (!mDataSaverEnabledMap.isValid()) {
+ return Errorf("init mDataSaverEnabledMap failed");
+ }
+
+ auto res = mDataSaverEnabledMap.writeValue(DATA_SAVER_ENABLED_KEY, enabled, BPF_EXIST);
+ if (!res.ok()) return Errorf("Failed to set data saver: {}", res.error().message());
+
+ return {};
+}
diff --git a/tests/native/utilities/firewall.h b/tests/native/utilities/firewall.h
index 1e7e987..b3d69bf 100644
--- a/tests/native/utilities/firewall.h
+++ b/tests/native/utilities/firewall.h
@@ -33,9 +33,11 @@
Result<void> removeRule(uint32_t uid, UidOwnerMatchType match) EXCLUDES(mMutex);
Result<void> addUidInterfaceRules(const std::string& ifName, const std::vector<int32_t>& uids);
Result<void> removeUidInterfaceRules(const std::vector<int32_t>& uids);
-
+ Result<bool> getDataSaverSetting();
+ Result<void> setDataSaver(bool enabled);
private:
BpfMap<uint32_t, uint32_t> mConfigurationMap GUARDED_BY(mMutex);
BpfMap<uint32_t, UidOwnerValue> mUidOwnerMap GUARDED_BY(mMutex);
+ BpfMap<uint32_t, bool> mDataSaverEnabledMap GUARDED_BY(mMutex);
std::mutex mMutex;
};
diff --git a/tests/unit/java/android/net/BpfNetMapsReaderTest.kt b/tests/unit/java/android/net/BpfNetMapsReaderTest.kt
index facb932..258e422 100644
--- a/tests/unit/java/android/net/BpfNetMapsReaderTest.kt
+++ b/tests/unit/java/android/net/BpfNetMapsReaderTest.kt
@@ -16,23 +16,34 @@
package android.net
+import android.net.BpfNetMapsConstants.DOZABLE_MATCH
+import android.net.BpfNetMapsConstants.HAPPY_BOX_MATCH
+import android.net.BpfNetMapsConstants.PENALTY_BOX_MATCH
+import android.net.BpfNetMapsConstants.STANDBY_MATCH
import android.net.BpfNetMapsConstants.UID_RULES_CONFIGURATION_KEY
import android.net.BpfNetMapsUtils.getMatchByFirewallChain
-import android.os.Build
+import android.os.Build.VERSION_CODES
import com.android.net.module.util.IBpfMap
import com.android.net.module.util.Struct.S32
import com.android.net.module.util.Struct.U32
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.DevSdkIgnoreRunner
import com.android.testutils.TestBpfMap
+import java.lang.reflect.Modifier
+import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
+private const val TEST_UID1 = 1234
+private const val TEST_UID2 = TEST_UID1 + 1
+private const val TEST_UID3 = TEST_UID2 + 1
+private const val NO_IIF = 0
+
// pre-T devices does not support Bpf.
@RunWith(DevSdkIgnoreRunner::class)
-@IgnoreUpTo(Build.VERSION_CODES.S_V2)
+@IgnoreUpTo(VERSION_CODES.S_V2)
class BpfNetMapsReaderTest {
private val testConfigurationMap: IBpfMap<S32, U32> = TestBpfMap()
private val testUidOwnerMap: IBpfMap<S32, UidOwnerValue> = TestBpfMap()
@@ -66,4 +77,126 @@
doTestIsChainEnabled(ConnectivityManager.FIREWALL_CHAIN_RESTRICTED)
doTestIsChainEnabled(ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY)
}
+
+ @Test
+ fun testFirewallChainList() {
+ // Verify that when a firewall chain constant is added, it should also be included in
+ // firewall chain list.
+ val declaredChains = ConnectivityManager::class.java.declaredFields.filter {
+ Modifier.isStatic(it.modifiers) && it.name.startsWith("FIREWALL_CHAIN_")
+ }
+ // Verify the size matches, this also verifies no common item in allow and deny chains.
+ assertEquals(BpfNetMapsConstants.ALLOW_CHAINS.size +
+ BpfNetMapsConstants.DENY_CHAINS.size, declaredChains.size)
+ declaredChains.forEach {
+ assertTrue(BpfNetMapsConstants.ALLOW_CHAINS.contains(it.get(null)) ||
+ BpfNetMapsConstants.DENY_CHAINS.contains(it.get(null)))
+ }
+ }
+
+ private fun mockChainEnabled(chain: Int, enabled: Boolean) {
+ val config = testConfigurationMap.getValue(UID_RULES_CONFIGURATION_KEY).`val`
+ val newConfig = if (enabled) {
+ config or getMatchByFirewallChain(chain)
+ } else {
+ config and getMatchByFirewallChain(chain).inv()
+ }
+ testConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, U32(newConfig))
+ }
+
+ fun isUidNetworkingBlocked(uid: Int, metered: Boolean = false, dataSaver: Boolean = false) =
+ bpfNetMapsReader.isUidNetworkingBlocked(uid, metered, dataSaver)
+
+ @Test
+ fun testIsUidNetworkingBlockedByFirewallChains_allowChain() {
+ // With everything disabled by default, verify the return value is false.
+ testConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, U32(0))
+ assertFalse(isUidNetworkingBlocked(TEST_UID1))
+
+ // Enable dozable chain but does not provide allowed list. Verify the network is blocked
+ // for all uids.
+ mockChainEnabled(ConnectivityManager.FIREWALL_CHAIN_DOZABLE, true)
+ assertTrue(isUidNetworkingBlocked(TEST_UID1))
+ assertTrue(isUidNetworkingBlocked(TEST_UID2))
+
+ // Add uid1 to dozable allowed list. Verify the network is not blocked for uid1, while
+ // uid2 is blocked.
+ testUidOwnerMap.updateEntry(S32(TEST_UID1), UidOwnerValue(NO_IIF, DOZABLE_MATCH))
+ assertFalse(isUidNetworkingBlocked(TEST_UID1))
+ assertTrue(isUidNetworkingBlocked(TEST_UID2))
+ }
+
+ @Test
+ fun testIsUidNetworkingBlockedByFirewallChains_denyChain() {
+ // Enable standby chain but does not provide denied list. Verify the network is allowed
+ // for all uids.
+ testConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, U32(0))
+ mockChainEnabled(ConnectivityManager.FIREWALL_CHAIN_STANDBY, true)
+ assertFalse(isUidNetworkingBlocked(TEST_UID1))
+ assertFalse(isUidNetworkingBlocked(TEST_UID2))
+
+ // Add uid1 to standby allowed list. Verify the network is blocked for uid1, while
+ // uid2 is not blocked.
+ testUidOwnerMap.updateEntry(S32(TEST_UID1), UidOwnerValue(NO_IIF, STANDBY_MATCH))
+ assertTrue(isUidNetworkingBlocked(TEST_UID1))
+ assertFalse(isUidNetworkingBlocked(TEST_UID2))
+ }
+
+ @Test
+ fun testIsUidNetworkingBlockedByFirewallChains_blockedWithAllowed() {
+ // Uids blocked by powersave chain but allowed by standby chain, verify the blocking
+ // takes higher priority.
+ testConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, U32(0))
+ mockChainEnabled(ConnectivityManager.FIREWALL_CHAIN_POWERSAVE, true)
+ mockChainEnabled(ConnectivityManager.FIREWALL_CHAIN_STANDBY, true)
+ assertTrue(isUidNetworkingBlocked(TEST_UID1))
+ }
+
+ @IgnoreUpTo(VERSION_CODES.S_V2)
+ @Test
+ fun testIsUidNetworkingBlockedByDataSaver() {
+ // With everything disabled by default, verify the return value is false.
+ testConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, U32(0))
+ assertFalse(isUidNetworkingBlocked(TEST_UID1, metered = true))
+
+ // Add uid1 to penalty box, verify the network is blocked for uid1, while uid2 is not
+ // affected.
+ testUidOwnerMap.updateEntry(S32(TEST_UID1), UidOwnerValue(NO_IIF, PENALTY_BOX_MATCH))
+ assertTrue(isUidNetworkingBlocked(TEST_UID1, metered = true))
+ assertFalse(isUidNetworkingBlocked(TEST_UID2, metered = true))
+
+ // Enable data saver, verify the network is blocked for uid1, uid2, but uid3 in happy box
+ // is not affected.
+ testUidOwnerMap.updateEntry(S32(TEST_UID3), UidOwnerValue(NO_IIF, HAPPY_BOX_MATCH))
+ assertTrue(isUidNetworkingBlocked(TEST_UID1, metered = true, dataSaver = true))
+ assertTrue(isUidNetworkingBlocked(TEST_UID2, metered = true, dataSaver = true))
+ assertFalse(isUidNetworkingBlocked(TEST_UID3, metered = true, dataSaver = true))
+
+ // Add uid1 to happy box as well, verify nothing is changed because penalty box has higher
+ // priority.
+ testUidOwnerMap.updateEntry(
+ S32(TEST_UID1),
+ UidOwnerValue(NO_IIF, PENALTY_BOX_MATCH or HAPPY_BOX_MATCH)
+ )
+ assertTrue(isUidNetworkingBlocked(TEST_UID1, metered = true, dataSaver = true))
+ assertTrue(isUidNetworkingBlocked(TEST_UID2, metered = true, dataSaver = true))
+ assertFalse(isUidNetworkingBlocked(TEST_UID3, metered = true, dataSaver = true))
+
+ // Enable doze mode, verify uid3 is blocked even if it is in happy box.
+ mockChainEnabled(ConnectivityManager.FIREWALL_CHAIN_DOZABLE, true)
+ assertTrue(isUidNetworkingBlocked(TEST_UID1, metered = true, dataSaver = true))
+ assertTrue(isUidNetworkingBlocked(TEST_UID2, metered = true, dataSaver = true))
+ assertTrue(isUidNetworkingBlocked(TEST_UID3, metered = true, dataSaver = true))
+
+ // Disable doze mode and data saver, only uid1 which is in penalty box is blocked.
+ mockChainEnabled(ConnectivityManager.FIREWALL_CHAIN_DOZABLE, false)
+ assertTrue(isUidNetworkingBlocked(TEST_UID1, metered = true))
+ assertFalse(isUidNetworkingBlocked(TEST_UID2, metered = true))
+ assertFalse(isUidNetworkingBlocked(TEST_UID3, metered = true))
+
+ // Make the network non-metered, nothing is blocked.
+ assertFalse(isUidNetworkingBlocked(TEST_UID1))
+ assertFalse(isUidNetworkingBlocked(TEST_UID2))
+ assertFalse(isUidNetworkingBlocked(TEST_UID3))
+ }
}
diff --git a/tests/unit/java/android/net/ConnectivityManagerTest.java b/tests/unit/java/android/net/ConnectivityManagerTest.java
index 45a9dbc..b8c5447 100644
--- a/tests/unit/java/android/net/ConnectivityManagerTest.java
+++ b/tests/unit/java/android/net/ConnectivityManagerTest.java
@@ -16,6 +16,13 @@
package android.net;
+import static android.content.Context.RECEIVER_NOT_EXPORTED;
+import static android.content.pm.ApplicationInfo.FLAG_PERSISTENT;
+import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
+import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
+import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
+import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
+import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED;
import static android.net.ConnectivityManager.TYPE_NONE;
import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
@@ -39,6 +46,7 @@
import static com.android.testutils.MiscAsserts.assertThrows;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
@@ -51,6 +59,7 @@
import static org.mockito.Mockito.after;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -61,7 +70,10 @@
import android.app.PendingIntent;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
+import android.net.ConnectivityManager.DataSaverStatusTracker;
import android.net.ConnectivityManager.NetworkCallback;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
@@ -95,6 +107,7 @@
@Mock Context mCtx;
@Mock IConnectivityManager mService;
+ @Mock NetworkPolicyManager mNpm;
@Before
public void setUp() {
@@ -510,4 +523,54 @@
assertNull("ConnectivityManager weak reference still not null after " + attempts
+ " attempts", ref.get());
}
+
+ @Test
+ public void testDataSaverStatusTracker() {
+ mockService(NetworkPolicyManager.class, Context.NETWORK_POLICY_SERVICE, mNpm);
+ // Mock proper application info.
+ doReturn(mCtx).when(mCtx).getApplicationContext();
+ final ApplicationInfo mockAppInfo = new ApplicationInfo();
+ mockAppInfo.flags = FLAG_PERSISTENT | FLAG_SYSTEM;
+ doReturn(mockAppInfo).when(mCtx).getApplicationInfo();
+ // Enable data saver.
+ doReturn(RESTRICT_BACKGROUND_STATUS_ENABLED).when(mNpm)
+ .getRestrictBackgroundStatus(anyInt());
+
+ final DataSaverStatusTracker tracker = new DataSaverStatusTracker(mCtx);
+ // Verify the data saver status is correct right after initialization.
+ assertTrue(tracker.getDataSaverEnabled());
+
+ // Verify the tracker register receiver with expected intent filter.
+ final ArgumentCaptor<IntentFilter> intentFilterCaptor =
+ ArgumentCaptor.forClass(IntentFilter.class);
+ verify(mCtx).registerReceiver(
+ any(), intentFilterCaptor.capture(), eq(RECEIVER_NOT_EXPORTED));
+ assertEquals(ACTION_RESTRICT_BACKGROUND_CHANGED,
+ intentFilterCaptor.getValue().getAction(0));
+
+ // Mock data saver status changed event and verify the tracker tracks the
+ // status accordingly.
+ doReturn(RESTRICT_BACKGROUND_STATUS_DISABLED).when(mNpm)
+ .getRestrictBackgroundStatus(anyInt());
+ tracker.onReceive(mCtx, new Intent(ACTION_RESTRICT_BACKGROUND_CHANGED));
+ assertFalse(tracker.getDataSaverEnabled());
+
+ doReturn(RESTRICT_BACKGROUND_STATUS_WHITELISTED).when(mNpm)
+ .getRestrictBackgroundStatus(anyInt());
+ tracker.onReceive(mCtx, new Intent(ACTION_RESTRICT_BACKGROUND_CHANGED));
+ assertTrue(tracker.getDataSaverEnabled());
+ }
+
+ private <T> void mockService(Class<T> clazz, String name, T service) {
+ doReturn(service).when(mCtx).getSystemService(name);
+ doReturn(name).when(mCtx).getSystemServiceName(clazz);
+
+ // If the test suite uses the inline mock maker library, such as for coverage tests,
+ // then the final version of getSystemService must also be mocked, as the real
+ // method will not be called by the test and null object is returned since no mock.
+ // Otherwise, mocking a final method will fail the test.
+ if (mCtx.getSystemService(clazz) == null) {
+ doReturn(service).when(mCtx).getSystemService(clazz);
+ }
+ }
}
diff --git a/tests/unit/java/com/android/server/BpfNetMapsTest.java b/tests/unit/java/com/android/server/BpfNetMapsTest.java
index da5f7e1..1e08fcc 100644
--- a/tests/unit/java/com/android/server/BpfNetMapsTest.java
+++ b/tests/unit/java/com/android/server/BpfNetMapsTest.java
@@ -16,7 +16,12 @@
package com.android.server;
+import static android.net.BpfNetMapsConstants.ALLOW_CHAINS;
import static android.net.BpfNetMapsConstants.CURRENT_STATS_MAP_CONFIGURATION_KEY;
+import static android.net.BpfNetMapsConstants.DATA_SAVER_ENABLED_KEY;
+import static android.net.BpfNetMapsConstants.DATA_SAVER_DISABLED;
+import static android.net.BpfNetMapsConstants.DATA_SAVER_ENABLED;
+import static android.net.BpfNetMapsConstants.DENY_CHAINS;
import static android.net.BpfNetMapsConstants.DOZABLE_MATCH;
import static android.net.BpfNetMapsConstants.HAPPY_BOX_MATCH;
import static android.net.BpfNetMapsConstants.IIF_MATCH;
@@ -66,6 +71,7 @@
import android.content.Context;
import android.net.BpfNetMapsUtils;
import android.net.INetd;
+import android.net.InetAddresses;
import android.net.UidOwnerValue;
import android.os.Build;
import android.os.ServiceSpecificException;
@@ -82,6 +88,8 @@
import com.android.net.module.util.Struct.U8;
import com.android.net.module.util.bpf.CookieTagMapKey;
import com.android.net.module.util.bpf.CookieTagMapValue;
+import com.android.net.module.util.bpf.IngressDiscardKey;
+import com.android.net.module.util.bpf.IngressDiscardValue;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
@@ -97,6 +105,8 @@
import java.io.FileDescriptor;
import java.io.StringWriter;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
import java.util.ArrayList;
import java.util.List;
@@ -115,21 +125,21 @@
private static final int TEST_IF_INDEX = 7;
private static final int NO_IIF = 0;
private static final int NULL_IIF = 0;
+ private static final Inet4Address TEST_V4_ADDRESS =
+ (Inet4Address) InetAddresses.parseNumericAddress("192.0.2.1");
+ private static final Inet6Address TEST_V6_ADDRESS =
+ (Inet6Address) InetAddresses.parseNumericAddress("2001:db8::1");
private static final String CHAINNAME = "fw_dozable";
- private static final List<Integer> FIREWALL_CHAINS = List.of(
- FIREWALL_CHAIN_DOZABLE,
- FIREWALL_CHAIN_STANDBY,
- FIREWALL_CHAIN_POWERSAVE,
- FIREWALL_CHAIN_RESTRICTED,
- FIREWALL_CHAIN_LOW_POWER_STANDBY,
- FIREWALL_CHAIN_OEM_DENY_1,
- FIREWALL_CHAIN_OEM_DENY_2,
- FIREWALL_CHAIN_OEM_DENY_3
- );
private static final long STATS_SELECT_MAP_A = 0;
private static final long STATS_SELECT_MAP_B = 1;
+ private static final List<Integer> FIREWALL_CHAINS = new ArrayList<>();
+ static {
+ FIREWALL_CHAINS.addAll(ALLOW_CHAINS);
+ FIREWALL_CHAINS.addAll(DENY_CHAINS);
+ }
+
private BpfNetMaps mBpfNetMaps;
@Mock INetd mNetd;
@@ -141,11 +151,15 @@
private final IBpfMap<S32, U8> mUidPermissionMap = new TestBpfMap<>(S32.class, U8.class);
private final IBpfMap<CookieTagMapKey, CookieTagMapValue> mCookieTagMap =
spy(new TestBpfMap<>(CookieTagMapKey.class, CookieTagMapValue.class));
+ private final IBpfMap<S32, U8> mDataSaverEnabledMap = new TestBpfMap<>(S32.class, U8.class);
+ private final IBpfMap<IngressDiscardKey, IngressDiscardValue> mIngressDiscardMap =
+ new TestBpfMap<>(IngressDiscardKey.class, IngressDiscardValue.class);
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
doReturn(TEST_IF_INDEX).when(mDeps).getIfIndex(TEST_IF_NAME);
+ doReturn(TEST_IF_NAME).when(mDeps).getIfName(TEST_IF_INDEX);
doReturn(0).when(mDeps).synchronizeKernelRCU();
BpfNetMaps.setEnableJavaBpfMapForTest(true /* enable */);
BpfNetMaps.setConfigurationMapForTest(mConfigurationMap);
@@ -155,6 +169,9 @@
BpfNetMaps.setUidOwnerMapForTest(mUidOwnerMap);
BpfNetMaps.setUidPermissionMapForTest(mUidPermissionMap);
BpfNetMaps.setCookieTagMapForTest(mCookieTagMap);
+ BpfNetMaps.setDataSaverEnabledMapForTest(mDataSaverEnabledMap);
+ mDataSaverEnabledMap.updateEntry(DATA_SAVER_ENABLED_KEY, new U8(DATA_SAVER_DISABLED));
+ BpfNetMaps.setIngressDiscardMapForTest(mIngressDiscardMap);
mBpfNetMaps = new BpfNetMaps(mContext, mNetd, mDeps);
}
@@ -611,7 +628,7 @@
mUidOwnerMap.updateEntry(new S32(TEST_UID), new UidOwnerValue(TEST_IF_INDEX, IIF_MATCH));
for (final int chain: testChains) {
- final int ruleToAddMatch = mBpfNetMaps.isFirewallAllowList(chain)
+ final int ruleToAddMatch = BpfNetMapsUtils.isFirewallAllowList(chain)
? FIREWALL_RULE_ALLOW : FIREWALL_RULE_DENY;
mBpfNetMaps.setUidRule(chain, TEST_UID, ruleToAddMatch);
}
@@ -619,7 +636,7 @@
checkUidOwnerValue(TEST_UID, TEST_IF_INDEX, IIF_MATCH | getMatch(testChains));
for (final int chain: testChains) {
- final int ruleToRemoveMatch = mBpfNetMaps.isFirewallAllowList(chain)
+ final int ruleToRemoveMatch = BpfNetMapsUtils.isFirewallAllowList(chain)
? FIREWALL_RULE_DENY : FIREWALL_RULE_ALLOW;
mBpfNetMaps.setUidRule(chain, TEST_UID, ruleToRemoveMatch);
}
@@ -699,11 +716,11 @@
for (final int chain: FIREWALL_CHAINS) {
final String testCase = "EnabledChains: " + enableChains + " CheckedChain: " + chain;
if (enableChains.contains(chain)) {
- final int expectedRule = mBpfNetMaps.isFirewallAllowList(chain)
+ final int expectedRule = BpfNetMapsUtils.isFirewallAllowList(chain)
? FIREWALL_RULE_ALLOW : FIREWALL_RULE_DENY;
assertEquals(testCase, expectedRule, mBpfNetMaps.getUidRule(chain, TEST_UID));
} else {
- final int expectedRule = mBpfNetMaps.isFirewallAllowList(chain)
+ final int expectedRule = BpfNetMapsUtils.isFirewallAllowList(chain)
? FIREWALL_RULE_DENY : FIREWALL_RULE_ALLOW;
assertEquals(testCase, expectedRule, mBpfNetMaps.getUidRule(chain, TEST_UID));
}
@@ -746,7 +763,7 @@
public void testGetUidRuleNoEntry() throws Exception {
mUidOwnerMap.clear();
for (final int chain: FIREWALL_CHAINS) {
- final int expectedRule = mBpfNetMaps.isFirewallAllowList(chain)
+ final int expectedRule = BpfNetMapsUtils.isFirewallAllowList(chain)
? FIREWALL_RULE_DENY : FIREWALL_RULE_ALLOW;
assertEquals(expectedRule, mBpfNetMaps.getUidRule(chain, TEST_UID));
}
@@ -1155,6 +1172,21 @@
assertDumpContains(getDump(), "cookie=123 tag=0x789 uid=456");
}
+ private void doTestDumpDataSaverConfig(final short value, final boolean expected)
+ throws Exception {
+ mDataSaverEnabledMap.updateEntry(DATA_SAVER_ENABLED_KEY, new U8(value));
+ assertDumpContains(getDump(),
+ "sDataSaverEnabledMap: " + expected);
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testDumpDataSaverConfig() throws Exception {
+ doTestDumpDataSaverConfig(DATA_SAVER_DISABLED, false);
+ doTestDumpDataSaverConfig(DATA_SAVER_ENABLED, true);
+ doTestDumpDataSaverConfig((short) 2, true);
+ }
+
@Test
public void testGetUids() throws ErrnoException {
final int uid0 = TEST_UIDS[0];
@@ -1183,4 +1215,73 @@
assertThrows(expected,
() -> mBpfNetMaps.getUidsWithAllowRuleOnAllowListChain(FIREWALL_CHAIN_OEM_DENY_1));
}
+
+ @Test
+ @IgnoreAfter(Build.VERSION_CODES.S_V2)
+ public void testSetDataSaverEnabledBeforeT() {
+ for (boolean enable : new boolean[]{true, false}) {
+ assertThrows(UnsupportedOperationException.class,
+ () -> mBpfNetMaps.setDataSaverEnabled(enable));
+ }
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testSetDataSaverEnabled() throws Exception {
+ for (boolean enable : new boolean[]{true, false}) {
+ mBpfNetMaps.setDataSaverEnabled(enable);
+ assertEquals(enable ? DATA_SAVER_ENABLED : DATA_SAVER_DISABLED,
+ mDataSaverEnabledMap.getValue(DATA_SAVER_ENABLED_KEY).val);
+ }
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testSetIngressDiscardRule_V4address() throws Exception {
+ mBpfNetMaps.setIngressDiscardRule(TEST_V4_ADDRESS, TEST_IF_NAME);
+ final IngressDiscardValue val = mIngressDiscardMap.getValue(new IngressDiscardKey(
+ TEST_V4_ADDRESS));
+ assertEquals(TEST_IF_INDEX, val.iif1);
+ assertEquals(TEST_IF_INDEX, val.iif2);
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testSetIngressDiscardRule_V6address() throws Exception {
+ mBpfNetMaps.setIngressDiscardRule(TEST_V6_ADDRESS, TEST_IF_NAME);
+ final IngressDiscardValue val =
+ mIngressDiscardMap.getValue(new IngressDiscardKey(TEST_V6_ADDRESS));
+ assertEquals(TEST_IF_INDEX, val.iif1);
+ assertEquals(TEST_IF_INDEX, val.iif2);
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testRemoveIngressDiscardRule() throws Exception {
+ mBpfNetMaps.setIngressDiscardRule(TEST_V4_ADDRESS, TEST_IF_NAME);
+ mBpfNetMaps.setIngressDiscardRule(TEST_V6_ADDRESS, TEST_IF_NAME);
+ final IngressDiscardKey v4Key = new IngressDiscardKey(TEST_V4_ADDRESS);
+ final IngressDiscardKey v6Key = new IngressDiscardKey(TEST_V6_ADDRESS);
+ assertTrue(mIngressDiscardMap.containsKey(v4Key));
+ assertTrue(mIngressDiscardMap.containsKey(v6Key));
+
+ mBpfNetMaps.removeIngressDiscardRule(TEST_V4_ADDRESS);
+ assertFalse(mIngressDiscardMap.containsKey(v4Key));
+ assertTrue(mIngressDiscardMap.containsKey(v6Key));
+
+ mBpfNetMaps.removeIngressDiscardRule(TEST_V6_ADDRESS);
+ assertFalse(mIngressDiscardMap.containsKey(v4Key));
+ assertFalse(mIngressDiscardMap.containsKey(v6Key));
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testDumpIngressDiscardRule() throws Exception {
+ mBpfNetMaps.setIngressDiscardRule(TEST_V4_ADDRESS, TEST_IF_NAME);
+ mBpfNetMaps.setIngressDiscardRule(TEST_V6_ADDRESS, TEST_IF_NAME);
+ final String dump = getDump();
+ assertDumpContains(dump, TEST_V4_ADDRESS.getHostAddress());
+ assertDumpContains(dump, TEST_V6_ADDRESS.getHostAddress());
+ assertDumpContains(dump, TEST_IF_INDEX + "(" + TEST_IF_NAME + ")");
+ }
}
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 11cece1..aae37e5 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -417,7 +417,6 @@
import com.android.server.connectivity.QosCallbackTracker;
import com.android.server.connectivity.TcpKeepaliveController;
import com.android.server.connectivity.UidRangeUtils;
-import com.android.server.connectivity.Vpn;
import com.android.server.connectivity.VpnProfileStore;
import com.android.server.net.NetworkPinner;
import com.android.testutils.DevSdkIgnoreRule;
@@ -1497,13 +1496,8 @@
return uidRangesForUids(CollectionUtils.toIntArray(uids));
}
- private static Looper startHandlerThreadAndReturnLooper() {
- final HandlerThread handlerThread = new HandlerThread("MockVpnThread");
- handlerThread.start();
- return handlerThread.getLooper();
- }
-
- private class MockVpn extends Vpn implements TestableNetworkCallback.HasNetwork {
+ // Helper class to mock vpn interaction.
+ private class MockVpn implements TestableNetworkCallback.HasNetwork {
// Note : Please do not add any new instrumentation here. If you need new instrumentation,
// please add it in CSTest and use subclasses of CSTest instead of adding more
// tools in ConnectivityServiceTest.
@@ -1511,45 +1505,23 @@
// Careful ! This is different from mNetworkAgent, because MockNetworkAgent does
// not inherit from NetworkAgent.
private TestNetworkAgentWrapper mMockNetworkAgent;
+ // Initialize a stored NetworkCapabilities following the defaults of VPN. The TransportInfo
+ // should at least be updated to a valid VPN type before usage, see registerAgent(...).
+ private NetworkCapabilities mNetworkCapabilities = new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_VPN)
+ .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
+ .setTransportInfo(new VpnTransportInfo(
+ VpnManager.TYPE_VPN_NONE,
+ null /* sessionId */,
+ false /* bypassable */,
+ false /* longLivedTcpConnectionsExpensive */))
+ .build();
private boolean mAgentRegistered = false;
private int mVpnType = VpnManager.TYPE_VPN_SERVICE;
- private UnderlyingNetworkInfo mUnderlyingNetworkInfo;
private String mSessionKey;
- public MockVpn(int userId) {
- super(startHandlerThreadAndReturnLooper(), mServiceContext,
- new Dependencies() {
- @Override
- public boolean isCallerSystem() {
- return true;
- }
-
- @Override
- public DeviceIdleInternal getDeviceIdleInternal() {
- return mDeviceIdleInternal;
- }
- },
- mNetworkManagementService, mMockNetd, userId, mVpnProfileStore,
- new SystemServices(mServiceContext) {
- @Override
- public String settingsSecureGetStringForUser(String key, int userId) {
- switch (key) {
- // Settings keys not marked as @Readable are not readable from
- // non-privileged apps, unless marked as testOnly=true
- // (atest refuses to install testOnly=true apps), even if mocked
- // in the content provider, because
- // Settings.Secure.NameValueCache#getStringForUser checks the key
- // before querying the mock settings provider.
- case Settings.Secure.ALWAYS_ON_VPN_APP:
- return null;
- default:
- return super.settingsSecureGetStringForUser(key, userId);
- }
- }
- }, new Ikev2SessionCreator());
- }
-
public void setUids(Set<UidRange> uids) {
mNetworkCapabilities.setUids(UidRange.toIntRanges(uids));
if (mAgentRegistered) {
@@ -1561,7 +1533,6 @@
mVpnType = vpnType;
}
- @Override
public Network getNetwork() {
return (mMockNetworkAgent == null) ? null : mMockNetworkAgent.getNetwork();
}
@@ -1570,7 +1541,6 @@
return null == mMockNetworkAgent ? null : mMockNetworkAgent.getNetworkAgentConfig();
}
- @Override
public int getActiveVpnType() {
return mVpnType;
}
@@ -1584,14 +1554,11 @@
private void registerAgent(boolean isAlwaysMetered, Set<UidRange> uids, LinkProperties lp)
throws Exception {
if (mAgentRegistered) throw new IllegalStateException("already registered");
- updateState(NetworkInfo.DetailedState.CONNECTING, "registerAgent");
- mConfig = new VpnConfig();
- mConfig.session = "MySession12345";
+ final String session = "MySession12345";
setUids(uids);
if (!isAlwaysMetered) mNetworkCapabilities.addCapability(NET_CAPABILITY_NOT_METERED);
- mInterface = VPN_IFNAME;
mNetworkCapabilities.setTransportInfo(new VpnTransportInfo(getActiveVpnType(),
- mConfig.session));
+ session));
mMockNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN, lp,
mNetworkCapabilities);
mMockNetworkAgent.waitForIdle(TIMEOUT_MS);
@@ -1605,9 +1572,7 @@
mAgentRegistered = true;
verify(mMockNetd).networkCreate(nativeNetworkConfigVpn(getNetwork().netId,
!mMockNetworkAgent.isBypassableVpn(), mVpnType));
- updateState(NetworkInfo.DetailedState.CONNECTED, "registerAgent");
mNetworkCapabilities.set(mMockNetworkAgent.getNetworkCapabilities());
- mNetworkAgent = mMockNetworkAgent.getNetworkAgent();
}
private void registerAgent(Set<UidRange> uids) throws Exception {
@@ -1667,23 +1632,20 @@
public void disconnect() {
if (mMockNetworkAgent != null) {
mMockNetworkAgent.disconnect();
- updateState(NetworkInfo.DetailedState.DISCONNECTED, "disconnect");
}
mAgentRegistered = false;
setUids(null);
// Remove NET_CAPABILITY_INTERNET or MockNetworkAgent will refuse to connect later on.
mNetworkCapabilities.removeCapability(NET_CAPABILITY_INTERNET);
- mInterface = null;
}
- private synchronized void startLegacyVpn() {
- updateState(DetailedState.CONNECTING, "startLegacyVpn");
+ private void startLegacyVpn() {
+ // Do nothing.
}
// Mock the interaction of IkeV2VpnRunner start. In the context of ConnectivityService,
// setVpnDefaultForUids() is the main interaction and a sessionKey is stored.
- private synchronized void startPlatformVpn() {
- updateState(DetailedState.CONNECTING, "startPlatformVpn");
+ private void startPlatformVpn() {
mSessionKey = UUID.randomUUID().toString();
// Assuming no disallowed applications
final Set<Range<Integer>> ranges = UidRange.toIntRanges(Set.of(PRIMARY_UIDRANGE));
@@ -1692,7 +1654,6 @@
waitForIdle();
}
- @Override
public void startLegacyVpnPrivileged(VpnProfile profile,
@Nullable Network underlying, @NonNull LinkProperties egress) {
switch (profile.type) {
@@ -1714,8 +1675,7 @@
}
}
- @Override
- public synchronized void stopVpnRunnerPrivileged() {
+ public void stopVpnRunnerPrivileged() {
if (mSessionKey != null) {
// Clear vpn network preference.
mCm.setVpnDefaultForUids(mSessionKey, Collections.EMPTY_LIST);
@@ -1724,20 +1684,7 @@
disconnect();
}
- @Override
- public synchronized UnderlyingNetworkInfo getUnderlyingNetworkInfo() {
- if (mUnderlyingNetworkInfo != null) return mUnderlyingNetworkInfo;
-
- return super.getUnderlyingNetworkInfo();
- }
-
- private synchronized void setUnderlyingNetworkInfo(
- UnderlyingNetworkInfo underlyingNetworkInfo) {
- mUnderlyingNetworkInfo = underlyingNetworkInfo;
- }
-
- @Override
- public synchronized boolean setUnderlyingNetworks(@Nullable Network[] networks) {
+ public boolean setUnderlyingNetworks(@Nullable Network[] networks) {
if (!mAgentRegistered) return false;
mMockNetworkAgent.setUnderlyingNetworks(
(networks == null) ? null : Arrays.asList(networks));
@@ -1774,11 +1721,6 @@
waitForIdle();
}
- private void mockVpn(int uid) {
- int userId = UserHandle.getUserId(uid);
- mMockVpn = new MockVpn(userId);
- }
-
private void mockUidNetworkingBlocked() {
doAnswer(i -> isUidBlocked(mBlockedReasons, i.getArgument(1))
).when(mNetworkPolicyManager).isUidNetworkingBlocked(anyInt(), anyBoolean());
@@ -1990,7 +1932,7 @@
mService.systemReadyInternal();
verify(mMockDnsResolver).registerUnsolicitedEventListener(any());
- mockVpn(Process.myUid());
+ mMockVpn = new MockVpn();
mCm.bindProcessToNetwork(null);
mQosCallbackTracker = mock(QosCallbackTracker.class);
@@ -4346,7 +4288,9 @@
testFactory.terminate();
testFactory.assertNoRequestChanged();
if (networkCallback != null) mCm.unregisterNetworkCallback(networkCallback);
- handlerThread.quit();
+
+ handlerThread.quitSafely();
+ handlerThread.join();
}
@Test
@@ -4407,6 +4351,8 @@
expectNoRequestChanged(testFactoryAll); // still seeing the request
mWiFiAgent.disconnect();
+ handlerThread.quitSafely();
+ handlerThread.join();
}
@Test
@@ -4440,7 +4386,8 @@
}
}
}
- handlerThread.quit();
+ handlerThread.quitSafely();
+ handlerThread.join();
}
@Test
@@ -6061,7 +6008,8 @@
testFactory.assertNoRequestChanged();
} finally {
mCm.unregisterNetworkCallback(cellNetworkCallback);
- handlerThread.quit();
+ handlerThread.quitSafely();
+ handlerThread.join();
}
}
@@ -6651,7 +6599,8 @@
}
} finally {
testFactory.terminate();
- handlerThread.quit();
+ handlerThread.quitSafely();
+ handlerThread.join();
}
}
@@ -10268,7 +10217,6 @@
// Init lockdown state to simulate LockdownVpnTracker behavior.
mCm.setLegacyLockdownVpnEnabled(true);
- mMockVpn.setEnableTeardown(false);
final List<Range<Integer>> ranges =
intRangesPrimaryExcludingUids(Collections.EMPTY_LIST /* excludedeUids */);
mCm.setRequireVpnForUids(true /* requireVpn */, ranges);
@@ -10585,13 +10533,11 @@
final boolean allowlist = true;
final boolean denylist = false;
- doReturn(true).when(mBpfNetMaps).isFirewallAllowList(anyInt());
doTestSetFirewallChainEnabledCloseSocket(FIREWALL_CHAIN_DOZABLE, allowlist);
doTestSetFirewallChainEnabledCloseSocket(FIREWALL_CHAIN_POWERSAVE, allowlist);
doTestSetFirewallChainEnabledCloseSocket(FIREWALL_CHAIN_RESTRICTED, allowlist);
doTestSetFirewallChainEnabledCloseSocket(FIREWALL_CHAIN_LOW_POWER_STANDBY, allowlist);
- doReturn(false).when(mBpfNetMaps).isFirewallAllowList(anyInt());
doTestSetFirewallChainEnabledCloseSocket(FIREWALL_CHAIN_STANDBY, denylist);
doTestSetFirewallChainEnabledCloseSocket(FIREWALL_CHAIN_OEM_DENY_1, denylist);
doTestSetFirewallChainEnabledCloseSocket(FIREWALL_CHAIN_OEM_DENY_2, denylist);
@@ -12596,9 +12542,6 @@
mMockVpn.establish(new LinkProperties(), vpnOwnerUid, vpnRange);
assertVpnUidRangesUpdated(true, vpnRange, vpnOwnerUid);
- final UnderlyingNetworkInfo underlyingNetworkInfo =
- new UnderlyingNetworkInfo(vpnOwnerUid, VPN_IFNAME, new ArrayList<>());
- mMockVpn.setUnderlyingNetworkInfo(underlyingNetworkInfo);
mDeps.setConnectionOwnerUid(42);
}
@@ -13235,7 +13178,7 @@
}
@Test
- public void testDumpDoesNotCrash() {
+ public void testDumpDoesNotCrash() throws Exception {
mServiceContext.setPermission(DUMP, PERMISSION_GRANTED);
// Filing a couple requests prior to testing the dump.
final TestNetworkCallback genericNetworkCallback = new TestNetworkCallback();
@@ -13247,6 +13190,44 @@
mCm.registerNetworkCallback(genericRequest, genericNetworkCallback);
mCm.registerNetworkCallback(wifiRequest, wifiNetworkCallback);
+ // NetworkProvider
+ final NetworkProvider wifiProvider = new NetworkProvider(mServiceContext,
+ mCsHandlerThread.getLooper(), "Wifi provider");
+ mCm.registerNetworkProvider(wifiProvider);
+
+ // NetworkAgent
+ final LinkProperties wifiLp = new LinkProperties();
+ wifiLp.setInterfaceName(WIFI_IFNAME);
+ mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp);
+ mWiFiAgent.connect(true);
+
+ // NetworkOffer
+ final NetworkScore wifiScore = new NetworkScore.Builder().build();
+ final NetworkCapabilities wifiCaps = new NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ .build();
+ final TestableNetworkOfferCallback wifiCallback = new TestableNetworkOfferCallback(
+ TIMEOUT_MS /* timeout */, TEST_CALLBACK_TIMEOUT_MS /* noCallbackTimeout */);
+ wifiProvider.registerNetworkOffer(wifiScore, wifiCaps, r -> r.run(), wifiCallback);
+
+ // Profile preferences
+ final UserHandle testHandle = setupEnterpriseNetwork();
+ final TestNetworkAgentWrapper workAgent = makeEnterpriseNetworkAgent();
+ workAgent.connect(true);
+ mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_ENTERPRISE,
+ null /* executor */, null /* listener */);
+
+ // OEM preferences
+ @OemNetworkPreferences.OemNetworkPreference final int networkPref =
+ OEM_NETWORK_PREFERENCE_OEM_PAID;
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true);
+ setOemNetworkPreference(networkPref, TEST_PACKAGE_NAME);
+
+ // Mobile data preferred UIDs
+ setAndUpdateMobileDataPreferredUids(Set.of(TEST_PACKAGE_UID));
+
verifyDump(new String[0]);
// Verify dump with arguments.
@@ -15340,6 +15321,8 @@
expectNoRequestChanged(oemPaidFactory);
internetFactory.expectRequestAdd();
mCm.unregisterNetworkCallback(wifiCallback);
+ handlerThread.quitSafely();
+ handlerThread.join();
}
/**
@@ -15704,6 +15687,8 @@
assertTrue(testFactory.getMyStartRequested());
} finally {
testFactory.terminate();
+ handlerThread.quitSafely();
+ handlerThread.join();
}
}
diff --git a/tests/unit/java/com/android/server/connectivity/AutomaticOnOffKeepaliveTrackerTest.java b/tests/unit/java/com/android/server/connectivity/AutomaticOnOffKeepaliveTrackerTest.java
index 986c389..8e19c01 100644
--- a/tests/unit/java/com/android/server/connectivity/AutomaticOnOffKeepaliveTrackerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/AutomaticOnOffKeepaliveTrackerTest.java
@@ -77,6 +77,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.server.connectivity.AutomaticOnOffKeepaliveTracker.AutomaticOnOffKeepalive;
import com.android.server.connectivity.KeepaliveTracker.KeepaliveInfo;
import com.android.testutils.DevSdkIgnoreRule;
@@ -94,6 +95,7 @@
import org.mockito.MockitoAnnotations;
import java.io.FileDescriptor;
+import java.io.StringWriter;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.Socket;
@@ -974,4 +976,19 @@
// The keepalive should be removed in AutomaticOnOffKeepaliveTracker.
assertNull(getAutoKiForBinder(testInfo.binder));
}
+
+ @Test
+ public void testDumpDoesNotCrash() throws Exception {
+ final TestKeepaliveInfo testInfo1 = doStartNattKeepalive();
+ final TestKeepaliveInfo testInfo2 = doStartNattKeepalive();
+ checkAndProcessKeepaliveStart(TEST_SLOT, testInfo1.kpd);
+ checkAndProcessKeepaliveStart(TEST_SLOT + 1, testInfo2.kpd);
+ final AutomaticOnOffKeepalive autoKi1 = getAutoKiForBinder(testInfo1.binder);
+ doPauseKeepalive(autoKi1);
+
+ final StringWriter stringWriter = new StringWriter();
+ final IndentingPrintWriter pw = new IndentingPrintWriter(stringWriter, " ");
+ visibleOnHandlerThread(mTestHandler, () -> mAOOKeepaliveTracker.dump(pw));
+ assertFalse(stringWriter.toString().isEmpty());
+ }
}
diff --git a/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java b/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java
index 24aecdb..545ed16 100644
--- a/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java
@@ -139,7 +139,8 @@
assertEquals(actual.tlsConnectTimeoutMs, expected.tlsConnectTimeoutMs);
assertResolverOptionsEquals(actual.resolverOptions, expected.resolverOptions);
assertContainsExactly(actual.transportTypes, expected.transportTypes);
- assertFieldCountEquals(16, ResolverParamsParcel.class);
+ assertEquals(actual.meteredNetwork, expected.meteredNetwork);
+ assertFieldCountEquals(17, ResolverParamsParcel.class);
}
@Before
@@ -169,10 +170,12 @@
lp.addDnsServer(InetAddress.getByName("4.4.4.4"));
// Send a validation event that is tracked on the alternate netId
- mDnsManager.updateTransportsForNetwork(TEST_NETID, TEST_TRANSPORT_TYPES);
+ final NetworkCapabilities nc = new NetworkCapabilities();
+ nc.setTransportTypes(TEST_TRANSPORT_TYPES);
+ mDnsManager.updateCapabilitiesForNetwork(TEST_NETID, nc);
mDnsManager.noteDnsServersForNetwork(TEST_NETID, lp);
mDnsManager.flushVmDnsCache();
- mDnsManager.updateTransportsForNetwork(TEST_NETID_ALTERNATE, TEST_TRANSPORT_TYPES);
+ mDnsManager.updateCapabilitiesForNetwork(TEST_NETID_ALTERNATE, nc);
mDnsManager.noteDnsServersForNetwork(TEST_NETID_ALTERNATE, lp);
mDnsManager.flushVmDnsCache();
mDnsManager.updatePrivateDnsValidation(
@@ -205,7 +208,7 @@
InetAddress.parseNumericAddress("6.6.6.6"),
InetAddress.parseNumericAddress("2001:db8:66:66::1")
}));
- mDnsManager.updateTransportsForNetwork(TEST_NETID, TEST_TRANSPORT_TYPES);
+ mDnsManager.updateCapabilitiesForNetwork(TEST_NETID, nc);
mDnsManager.noteDnsServersForNetwork(TEST_NETID, lp);
mDnsManager.flushVmDnsCache();
fixedLp = new LinkProperties(lp);
@@ -242,7 +245,9 @@
// be tracked.
LinkProperties lp = new LinkProperties();
lp.addDnsServer(InetAddress.getByName("3.3.3.3"));
- mDnsManager.updateTransportsForNetwork(TEST_NETID, TEST_TRANSPORT_TYPES);
+ final NetworkCapabilities nc = new NetworkCapabilities();
+ nc.setTransportTypes(TEST_TRANSPORT_TYPES);
+ mDnsManager.updateCapabilitiesForNetwork(TEST_NETID, nc);
mDnsManager.noteDnsServersForNetwork(TEST_NETID, lp);
mDnsManager.flushVmDnsCache();
mDnsManager.updatePrivateDnsValidation(
@@ -256,7 +261,7 @@
// Validation event has untracked netId
mDnsManager.updatePrivateDns(new Network(TEST_NETID),
mDnsManager.getPrivateDnsConfig());
- mDnsManager.updateTransportsForNetwork(TEST_NETID, TEST_TRANSPORT_TYPES);
+ mDnsManager.updateCapabilitiesForNetwork(TEST_NETID, nc);
mDnsManager.noteDnsServersForNetwork(TEST_NETID, lp);
mDnsManager.flushVmDnsCache();
mDnsManager.updatePrivateDnsValidation(
@@ -307,7 +312,7 @@
ConnectivitySettingsManager.setPrivateDnsMode(mCtx, PRIVATE_DNS_MODE_OFF);
mDnsManager.updatePrivateDns(new Network(TEST_NETID),
mDnsManager.getPrivateDnsConfig());
- mDnsManager.updateTransportsForNetwork(TEST_NETID, TEST_TRANSPORT_TYPES);
+ mDnsManager.updateCapabilitiesForNetwork(TEST_NETID, nc);
mDnsManager.noteDnsServersForNetwork(TEST_NETID, lp);
mDnsManager.flushVmDnsCache();
mDnsManager.updatePrivateDnsValidation(
@@ -352,7 +357,9 @@
lp.setInterfaceName(TEST_IFACENAME);
lp.addDnsServer(InetAddress.getByName("3.3.3.3"));
lp.addDnsServer(InetAddress.getByName("4.4.4.4"));
- mDnsManager.updateTransportsForNetwork(TEST_NETID, TEST_TRANSPORT_TYPES);
+ final NetworkCapabilities nc = new NetworkCapabilities();
+ nc.setTransportTypes(TEST_TRANSPORT_TYPES);
+ mDnsManager.updateCapabilitiesForNetwork(TEST_NETID, nc);
mDnsManager.noteDnsServersForNetwork(TEST_NETID, lp);
mDnsManager.flushVmDnsCache();
@@ -373,6 +380,7 @@
expectedParams.tlsServers = new String[]{"3.3.3.3", "4.4.4.4"};
expectedParams.transportTypes = TEST_TRANSPORT_TYPES;
expectedParams.resolverOptions = null;
+ expectedParams.meteredNetwork = true;
assertResolverParamsEquals(actualParams, expectedParams);
}
diff --git a/tests/unit/java/com/android/server/connectivity/KeepaliveStatsTrackerTest.java b/tests/unit/java/com/android/server/connectivity/KeepaliveStatsTrackerTest.java
index 90a0edd..1b964e2 100644
--- a/tests/unit/java/com/android/server/connectivity/KeepaliveStatsTrackerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/KeepaliveStatsTrackerTest.java
@@ -37,6 +37,7 @@
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.content.BroadcastReceiver;
@@ -1293,5 +1294,18 @@
expectRegisteredDurations,
expectActiveDurations,
new KeepaliveCarrierStats[0]);
+
+ assertTrue(mKeepaliveStatsTracker.allMetricsExpected(dailyKeepaliveInfoReported));
+
+ // Write time after 26 hours.
+ final int writeTime2 = 26 * 60 * 60 * 1000;
+ setElapsedRealtime(writeTime2);
+
+ visibleOnHandlerThread(mTestHandler, () -> mKeepaliveStatsTracker.writeAndResetMetrics());
+ verify(mDependencies, times(2)).writeStats(dailyKeepaliveInfoReportedCaptor.capture());
+ final DailykeepaliveInfoReported dailyKeepaliveInfoReported2 =
+ dailyKeepaliveInfoReportedCaptor.getValue();
+
+ assertFalse(mKeepaliveStatsTracker.allMetricsExpected(dailyKeepaliveInfoReported2));
}
}
diff --git a/tests/unit/java/com/android/server/connectivity/RoutingCoordinatorServiceTest.kt b/tests/unit/java/com/android/server/connectivity/RoutingCoordinatorServiceTest.kt
index 8adf309..12758c6 100644
--- a/tests/unit/java/com/android/server/connectivity/RoutingCoordinatorServiceTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/RoutingCoordinatorServiceTest.kt
@@ -18,7 +18,6 @@
import android.net.INetd
import android.os.Build
-import androidx.test.platform.app.InstrumentationRegistry
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.DevSdkIgnoreRunner
import org.junit.Test
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSDestroyedNetworkTests.kt b/tests/unit/java/com/android/server/connectivityservice/CSDestroyedNetworkTests.kt
new file mode 100644
index 0000000..572c7bb
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivityservice/CSDestroyedNetworkTests.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2023 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
+
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.net.NetworkRequest
+import android.os.Build
+import androidx.test.filters.SmallTest
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.RecorderCallback.CallbackEntry.Lost
+import com.android.testutils.TestableNetworkCallback
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private const val LONG_TIMEOUT_MS = 5_000
+
+@RunWith(DevSdkIgnoreRunner::class)
+@SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+class CSDestroyedNetworkTests : CSTest() {
+ @Test
+ fun testDestroyNetworkNotKeptWhenUnvalidated() {
+ val nc = NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .build()
+
+ val nr = NetworkRequest.Builder()
+ .clearCapabilities()
+ .addTransportType(TRANSPORT_WIFI)
+ .build()
+ val cbRequest = TestableNetworkCallback()
+ val cbCallback = TestableNetworkCallback()
+ cm.requestNetwork(nr, cbRequest)
+ cm.registerNetworkCallback(nr, cbCallback)
+
+ val firstAgent = Agent(nc = nc)
+ firstAgent.connect()
+ cbCallback.expectAvailableCallbacks(firstAgent.network, validated = false)
+
+ firstAgent.unregisterAfterReplacement(LONG_TIMEOUT_MS)
+
+ val secondAgent = Agent(nc = nc)
+ secondAgent.connect()
+ cbCallback.expectAvailableCallbacks(secondAgent.network, validated = false)
+
+ cbCallback.expect<Lost>(timeoutMs = 500) { it.network == firstAgent.network }
+ }
+}
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSKeepConnectedTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSKeepConnectedTest.kt
index 6220e76..2126a09 100644
--- a/tests/unit/java/com/android/server/connectivityservice/CSKeepConnectedTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/CSKeepConnectedTest.kt
@@ -22,7 +22,7 @@
import android.net.NetworkCapabilities.TRANSPORT_WIFI
import android.net.NetworkRequest
import android.net.NetworkScore
-import android.net.NetworkScore.KEEP_CONNECTED_DOWNSTREAM_NETWORK
+import android.net.NetworkScore.KEEP_CONNECTED_LOCAL_NETWORK
import android.net.NetworkScore.KEEP_CONNECTED_FOR_TEST
import android.os.Build
import androidx.test.filters.SmallTest
@@ -45,7 +45,7 @@
.addCapability(NET_CAPABILITY_LOCAL_NETWORK)
.build()
val keepConnectedAgent = Agent(nc = nc, score = FromS(NetworkScore.Builder()
- .setKeepConnectedReason(KEEP_CONNECTED_DOWNSTREAM_NETWORK)
+ .setKeepConnectedReason(KEEP_CONNECTED_LOCAL_NETWORK)
.build()),
lnc = LocalNetworkConfig.Builder().build())
val dontKeepConnectedAgent = Agent(nc = nc, lnc = LocalNetworkConfig.Builder().build())
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentTests.kt b/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentTests.kt
index bd3efa9..3a76ad0 100644
--- a/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentTests.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentTests.kt
@@ -21,27 +21,42 @@
import android.net.LinkProperties
import android.net.LocalNetworkConfig
import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_DUN
+import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
import android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
import android.net.NetworkCapabilities.TRANSPORT_WIFI
import android.net.NetworkRequest
+import android.net.NetworkScore
+import android.net.NetworkScore.KEEP_CONNECTED_FOR_TEST
+import android.net.NetworkScore.KEEP_CONNECTED_LOCAL_NETWORK
import android.net.RouteInfo
import android.os.Build
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRunner
-import com.android.testutils.RecorderCallback
import com.android.testutils.RecorderCallback.CallbackEntry.Available
import com.android.testutils.RecorderCallback.CallbackEntry.BlockedStatus
import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged
import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
+import com.android.testutils.RecorderCallback.CallbackEntry.Lost
import com.android.testutils.TestableNetworkCallback
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.never
+import org.mockito.Mockito.timeout
+import org.mockito.Mockito.verify
import kotlin.test.assertFailsWith
+private const val TIMEOUT_MS = 200L
+private const val MEDIUM_TIMEOUT_MS = 1_000L
+private const val LONG_TIMEOUT_MS = 5_000
+
private fun nc(transport: Int, vararg caps: Int) = NetworkCapabilities.Builder().apply {
addTransportType(transport)
caps.forEach {
@@ -60,11 +75,19 @@
addRoute(RouteInfo(IpPrefix("0.0.0.0/0"), null, null))
}
+// This allows keeping all the networks connected without having to file individual requests
+// for them.
+private fun keepScore() = FromS(
+ NetworkScore.Builder().setKeepConnectedReason(KEEP_CONNECTED_FOR_TEST).build()
+)
+
@RunWith(DevSdkIgnoreRunner::class)
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
class CSLocalAgentTests : CSTest() {
@Test
fun testBadAgents() {
+ deps.setBuildSdk(VERSION_V)
+
assertFailsWith<IllegalArgumentException> {
Agent(nc = NetworkCapabilities.Builder()
.addCapability(NET_CAPABILITY_LOCAL_NETWORK)
@@ -78,6 +101,41 @@
}
@Test
+ fun testStructuralConstraintViolation() {
+ deps.setBuildSdk(VERSION_V)
+
+ val cb = TestableNetworkCallback()
+ cm.requestNetwork(NetworkRequest.Builder()
+ .clearCapabilities()
+ .build(),
+ cb)
+ val agent = Agent(nc = NetworkCapabilities.Builder()
+ .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
+ .build(),
+ lnc = LocalNetworkConfig.Builder().build())
+ agent.connect()
+ cb.expect<Available>(agent.network)
+ cb.expect<CapabilitiesChanged>(agent.network)
+ cb.expect<LinkPropertiesChanged>(agent.network)
+ cb.expect<BlockedStatus>(agent.network)
+ agent.sendNetworkCapabilities(NetworkCapabilities.Builder().build())
+ cb.expect<Lost>(agent.network)
+
+ val agent2 = Agent(nc = NetworkCapabilities.Builder()
+ .build(),
+ lnc = null)
+ agent2.connect()
+ cb.expect<Available>(agent2.network)
+ cb.expect<CapabilitiesChanged>(agent2.network)
+ cb.expect<LinkPropertiesChanged>(agent2.network)
+ cb.expect<BlockedStatus>(agent2.network)
+ agent2.sendNetworkCapabilities(NetworkCapabilities.Builder()
+ .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
+ .build())
+ cb.expect<Lost>(agent2.network)
+ }
+
+ @Test
fun testUpdateLocalAgentConfig() {
deps.setBuildSdk(VERSION_V)
@@ -88,7 +146,8 @@
cb)
// Set up a local agent that should forward its traffic to the best DUN upstream.
- val localAgent = Agent(nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_LOCAL_NETWORK),
+ val localAgent = Agent(
+ nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_LOCAL_NETWORK),
lp = lp("local0"),
lnc = LocalNetworkConfig.Builder().build(),
)
@@ -108,4 +167,242 @@
localAgent.disconnect()
}
+
+ @Test
+ fun testUnregisterUpstreamAfterReplacement_SameIfaceName() {
+ doTestUnregisterUpstreamAfterReplacement(true)
+ }
+
+ @Test
+ fun testUnregisterUpstreamAfterReplacement_DifferentIfaceName() {
+ doTestUnregisterUpstreamAfterReplacement(false)
+ }
+
+ fun doTestUnregisterUpstreamAfterReplacement(sameIfaceName: Boolean) {
+ deps.setBuildSdk(VERSION_V)
+ val cb = TestableNetworkCallback()
+ cm.registerNetworkCallback(NetworkRequest.Builder().clearCapabilities().build(), cb)
+
+ // Set up a local agent that should forward its traffic to the best wifi upstream.
+ val localAgent = Agent(nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_LOCAL_NETWORK),
+ lp = lp("local0"),
+ lnc = LocalNetworkConfig.Builder()
+ .setUpstreamSelector(NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .build())
+ .build(),
+ score = FromS(NetworkScore.Builder()
+ .setKeepConnectedReason(KEEP_CONNECTED_LOCAL_NETWORK)
+ .build())
+ )
+ localAgent.connect()
+
+ cb.expectAvailableCallbacks(localAgent.network, validated = false)
+
+ val wifiAgent = Agent(lp = lp("wifi0"),
+ nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET))
+ wifiAgent.connect()
+
+ cb.expectAvailableCallbacks(wifiAgent.network, validated = false)
+
+ clearInvocations(netd)
+ val inOrder = inOrder(netd)
+ wifiAgent.unregisterAfterReplacement(LONG_TIMEOUT_MS)
+ waitForIdle()
+ inOrder.verify(netd).ipfwdRemoveInterfaceForward("local0", "wifi0")
+ inOrder.verify(netd).networkDestroy(wifiAgent.network.netId)
+
+ val wifiIface2 = if (sameIfaceName) "wifi0" else "wifi1"
+ val wifiAgent2 = Agent(lp = lp(wifiIface2),
+ nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET))
+ wifiAgent2.connect()
+
+ cb.expectAvailableCallbacks(wifiAgent2.network, validated = false)
+ cb.expect<Lost> { it.network == wifiAgent.network }
+
+ inOrder.verify(netd).ipfwdAddInterfaceForward("local0", wifiIface2)
+ if (sameIfaceName) {
+ inOrder.verify(netd, never()).ipfwdRemoveInterfaceForward(any(), any())
+ }
+ }
+
+ @Test
+ fun testUnregisterUpstreamAfterReplacement_neverReplaced() {
+ deps.setBuildSdk(VERSION_V)
+ val cb = TestableNetworkCallback()
+ cm.registerNetworkCallback(NetworkRequest.Builder().clearCapabilities().build(), cb)
+
+ // Set up a local agent that should forward its traffic to the best wifi upstream.
+ val localAgent = Agent(nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_LOCAL_NETWORK),
+ lp = lp("local0"),
+ lnc = LocalNetworkConfig.Builder()
+ .setUpstreamSelector(NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .build())
+ .build(),
+ score = FromS(NetworkScore.Builder()
+ .setKeepConnectedReason(KEEP_CONNECTED_LOCAL_NETWORK)
+ .build())
+ )
+ localAgent.connect()
+
+ cb.expectAvailableCallbacks(localAgent.network, validated = false)
+
+ val wifiAgent = Agent(lp = lp("wifi0"),
+ nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET))
+ wifiAgent.connect()
+
+ cb.expectAvailableCallbacksUnvalidated(wifiAgent)
+
+ clearInvocations(netd)
+ wifiAgent.unregisterAfterReplacement(TIMEOUT_MS.toInt())
+ waitForIdle()
+ verify(netd).networkDestroy(wifiAgent.network.netId)
+ verify(netd).ipfwdRemoveInterfaceForward("local0", "wifi0")
+
+ cb.expect<Lost> { it.network == wifiAgent.network }
+ }
+
+ @Test
+ fun testUnregisterLocalAgentAfterReplacement() {
+ deps.setBuildSdk(VERSION_V)
+
+ val localCb = TestableNetworkCallback()
+ cm.requestNetwork(NetworkRequest.Builder().clearCapabilities()
+ .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
+ .build(),
+ localCb)
+
+ val cb = TestableNetworkCallback()
+ cm.registerNetworkCallback(NetworkRequest.Builder().clearCapabilities().build(), cb)
+
+ val localNc = nc(TRANSPORT_WIFI, NET_CAPABILITY_LOCAL_NETWORK)
+ val lnc = LocalNetworkConfig.Builder()
+ .setUpstreamSelector(NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .build())
+ .build()
+ val localScore = FromS(NetworkScore.Builder().build())
+
+ // Set up a local agent that should forward its traffic to the best wifi upstream.
+ val localAgent = Agent(nc = localNc, lp = lp("local0"), lnc = lnc, score = localScore)
+ localAgent.connect()
+
+ localCb.expectAvailableCallbacks(localAgent.network, validated = false)
+ cb.expectAvailableCallbacks(localAgent.network, validated = false)
+
+ val wifiAgent = Agent(lp = lp("wifi0"), nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET))
+ wifiAgent.connect()
+
+ cb.expectAvailableCallbacksUnvalidated(wifiAgent)
+
+ verify(netd).ipfwdAddInterfaceForward("local0", "wifi0")
+
+ localAgent.unregisterAfterReplacement(LONG_TIMEOUT_MS)
+
+ val localAgent2 = Agent(nc = localNc, lp = lp("local0"), lnc = lnc, score = localScore)
+ localAgent2.connect()
+
+ localCb.expectAvailableCallbacks(localAgent2.network, validated = false)
+ cb.expectAvailableCallbacks(localAgent2.network, validated = false)
+ cb.expect<Lost> { it.network == localAgent.network }
+ }
+
+ @Test
+ fun testDestroyedNetworkAsSelectedUpstream() {
+ deps.setBuildSdk(VERSION_V)
+ val cb = TestableNetworkCallback()
+ cm.registerNetworkCallback(NetworkRequest.Builder().clearCapabilities().build(), cb)
+
+ val wifiAgent = Agent(lp = lp("wifi0"), nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET))
+ wifiAgent.connect()
+ cb.expectAvailableCallbacksUnvalidated(wifiAgent)
+
+ // Set up a local agent that should forward its traffic to the best wifi upstream.
+ val localAgent = Agent(nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_LOCAL_NETWORK),
+ lp = lp("local0"),
+ lnc = LocalNetworkConfig.Builder()
+ .setUpstreamSelector(NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .build())
+ .build(),
+ score = FromS(NetworkScore.Builder()
+ .setKeepConnectedReason(KEEP_CONNECTED_LOCAL_NETWORK)
+ .build())
+ )
+
+ // ...but destroy the wifi agent before connecting it
+ wifiAgent.unregisterAfterReplacement(LONG_TIMEOUT_MS)
+
+ localAgent.connect()
+ cb.expectAvailableCallbacks(localAgent.network, validated = false)
+
+ verify(netd).ipfwdAddInterfaceForward("local0", "wifi0")
+ verify(netd).ipfwdRemoveInterfaceForward("local0", "wifi0")
+ }
+
+ @Test
+ fun testForwardingRules() {
+ deps.setBuildSdk(VERSION_V)
+ // Set up a local agent that should forward its traffic to the best DUN upstream.
+ val lnc = LocalNetworkConfig.Builder()
+ .setUpstreamSelector(NetworkRequest.Builder()
+ .addCapability(NET_CAPABILITY_DUN)
+ .build())
+ .build()
+ val localAgent = Agent(nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_LOCAL_NETWORK),
+ lp = lp("local0"),
+ lnc = lnc,
+ score = FromS(NetworkScore.Builder()
+ .setKeepConnectedReason(KEEP_CONNECTED_LOCAL_NETWORK)
+ .build())
+ )
+ localAgent.connect()
+
+ val wifiAgent = Agent(score = keepScore(), lp = lp("wifi0"),
+ nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET))
+ val cellAgentDun = Agent(score = keepScore(), lp = lp("cell0"),
+ nc = nc(TRANSPORT_CELLULAR, NET_CAPABILITY_INTERNET, NET_CAPABILITY_DUN))
+ val wifiAgentDun = Agent(score = keepScore(), lp = lp("wifi1"),
+ nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET, NET_CAPABILITY_DUN))
+
+ val inOrder = inOrder(netd)
+ inOrder.verify(netd, never()).ipfwdAddInterfaceForward(any(), any())
+
+ wifiAgent.connect()
+ inOrder.verify(netd, never()).ipfwdAddInterfaceForward(any(), any())
+
+ cellAgentDun.connect()
+ inOrder.verify(netd).ipfwdEnableForwarding(any())
+ inOrder.verify(netd).ipfwdAddInterfaceForward("local0", "cell0")
+
+ wifiAgentDun.connect()
+ inOrder.verify(netd).ipfwdRemoveInterfaceForward("local0", "cell0")
+ inOrder.verify(netd).ipfwdAddInterfaceForward("local0", "wifi1")
+
+ // Make sure sending the same config again doesn't do anything
+ repeat(5) {
+ localAgent.sendLocalNetworkConfig(lnc)
+ }
+ inOrder.verifyNoMoreInteractions()
+
+ wifiAgentDun.disconnect()
+ inOrder.verify(netd).ipfwdRemoveInterfaceForward("local0", "wifi1")
+ // This can take a little bit of time because it needs to wait for the rematch
+ inOrder.verify(netd, timeout(MEDIUM_TIMEOUT_MS)).ipfwdAddInterfaceForward("local0", "cell0")
+
+ cellAgentDun.disconnect()
+ inOrder.verify(netd).ipfwdRemoveInterfaceForward("local0", "cell0")
+ inOrder.verify(netd).ipfwdDisableForwarding(any())
+
+ val wifiAgentDun2 = Agent(score = keepScore(), lp = lp("wifi2"),
+ nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET, NET_CAPABILITY_DUN))
+ wifiAgentDun2.connect()
+ inOrder.verify(netd).ipfwdEnableForwarding(any())
+ inOrder.verify(netd).ipfwdAddInterfaceForward("local0", "wifi2")
+
+ localAgent.disconnect()
+ inOrder.verify(netd).ipfwdRemoveInterfaceForward("local0", "wifi2")
+ inOrder.verify(netd).ipfwdDisableForwarding(any())
+ }
}
diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt
index 094ded3..013a749 100644
--- a/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt
@@ -34,7 +34,6 @@
import android.net.NetworkTestResultParcelable
import android.net.networkstack.NetworkStackClientBase
import android.os.HandlerThread
-import com.android.modules.utils.build.SdkLevel
import com.android.testutils.RecorderCallback.CallbackEntry.Available
import com.android.testutils.RecorderCallback.CallbackEntry.Lost
import com.android.testutils.TestableNetworkCallback
@@ -168,5 +167,8 @@
cb.eventuallyExpect<Lost> { it.network == agent.network }
}
+ fun unregisterAfterReplacement(timeoutMs: Int) = agent.unregisterAfterReplacement(timeoutMs)
+
fun sendLocalNetworkConfig(lnc: LocalNetworkConfig) = agent.sendLocalNetworkConfig(lnc)
+ fun sendNetworkCapabilities(nc: NetworkCapabilities) = agent.sendNetworkCapabilities(nc)
}
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java b/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
index c477b2c..e62ac74 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
@@ -125,7 +125,7 @@
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mObserverHandlerThread = new HandlerThread("HandlerThread");
+ mObserverHandlerThread = new HandlerThread("NetworkStatsObserversTest");
mObserverHandlerThread.start();
final Looper observerLooper = mObserverHandlerThread.getLooper();
mStatsObservers = new NetworkStatsObservers() {
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index e8d5c66..92a5b64 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -120,6 +120,7 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
+import android.os.Looper;
import android.os.PowerManager;
import android.os.SimpleClock;
import android.provider.Settings;
@@ -284,6 +285,7 @@
private @Mock PersistentInt mImportLegacyFallbacksCounter;
private @Mock Resources mResources;
private Boolean mIsDebuggable;
+ private HandlerThread mObserverHandlerThread;
private class MockContext extends BroadcastInterceptingContext {
private final Context mBaseContext;
@@ -365,10 +367,23 @@
PowerManager.WakeLock wakeLock =
powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
- mHandlerThread = new HandlerThread("HandlerThread");
+ mHandlerThread = new HandlerThread("NetworkStatsServiceTest-HandlerThread");
final NetworkStatsService.Dependencies deps = makeDependencies();
+ // Create a separate thread for observers to run on. This thread cannot be the same
+ // as the handler thread, because the observer callback is fired on this thread, and
+ // it should not be blocked by client code. Additionally, creating the observers
+ // object requires a looper, which can only be obtained after a thread has been started.
+ mObserverHandlerThread = new HandlerThread("NetworkStatsServiceTest-ObserversThread");
+ mObserverHandlerThread.start();
+ final Looper observerLooper = mObserverHandlerThread.getLooper();
+ final NetworkStatsObservers statsObservers = new NetworkStatsObservers() {
+ @Override
+ protected Looper getHandlerLooperLocked() {
+ return observerLooper;
+ }
+ };
mService = new NetworkStatsService(mServiceContext, mNetd, mAlarmManager, wakeLock,
- mClock, mSettings, mStatsFactory, new NetworkStatsObservers(), deps);
+ mClock, mSettings, mStatsFactory, statsObservers, deps);
mElapsedRealtime = 0L;
@@ -545,8 +560,14 @@
mSession.close();
mService = null;
- mHandlerThread.quitSafely();
- mHandlerThread.join();
+ if (mHandlerThread != null) {
+ mHandlerThread.quitSafely();
+ mHandlerThread.join();
+ }
+ if (mObserverHandlerThread != null) {
+ mObserverHandlerThread.quitSafely();
+ mObserverHandlerThread.join();
+ }
}
private void initWifiStats(NetworkStateSnapshot snapshot) throws Exception {