Merge "Remove DefaultState in NsdService" 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/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 8d67221..f275d49 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -66,6 +66,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.MessageUtils;
import com.android.internal.util.State;
import com.android.modules.utils.build.SdkLevel;
@@ -92,6 +93,7 @@
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@@ -237,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;
@@ -298,6 +301,8 @@
private int mLastIPv6UpstreamIfindex = 0;
private boolean mUpstreamSupportsBpf = false;
+ @NonNull
+ private Set<IpPrefix> mLastIPv6UpstreamPrefixes = Collections.emptySet();
private class MyNeighborEventConsumer implements IpNeighborMonitor.NeighborEventConsumer {
public void accept(NeighborEvent e) {
@@ -510,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);
+ }
}
}
@@ -782,13 +792,8 @@
if (params.hasDefaultRoute) params.hopLimit = getHopLimit(upstreamIface, ttlAdjustment);
- for (LinkAddress linkAddr : v6only.getLinkAddresses()) {
- if (linkAddr.getPrefixLength() != RFC7421_PREFIX_LENGTH) continue;
-
- final IpPrefix prefix = new IpPrefix(
- linkAddr.getAddress(), linkAddr.getPrefixLength());
- params.prefixes.add(prefix);
-
+ params.prefixes = getTetherableIpv6Prefixes(v6only);
+ for (IpPrefix prefix : params.prefixes) {
final Inet6Address dnsServer = getLocalDnsIpFor(prefix);
if (dnsServer != null) {
params.dnses.add(dnsServer);
@@ -808,9 +813,12 @@
// Not support BPF on virtual upstream interface
final boolean upstreamSupportsBpf = upstreamIface != null && !isVcnInterface(upstreamIface);
- updateIpv6ForwardingRules(mLastIPv6UpstreamIfindex, upstreamIfIndex, upstreamSupportsBpf);
+ final Set<IpPrefix> upstreamPrefixes = params != null ? params.prefixes : Set.of();
+ updateIpv6ForwardingRules(mLastIPv6UpstreamIfindex, mLastIPv6UpstreamPrefixes,
+ upstreamIfIndex, upstreamPrefixes, upstreamSupportsBpf);
mLastIPv6LinkProperties = v6only;
mLastIPv6UpstreamIfindex = upstreamIfIndex;
+ mLastIPv6UpstreamPrefixes = upstreamPrefixes;
mUpstreamSupportsBpf = upstreamSupportsBpf;
if (mDadProxy != null) {
mDadProxy.setUpstreamIface(upstreamIfaceParams);
@@ -839,38 +847,7 @@
}
} catch (ServiceSpecificException | RemoteException e) {
mLog.e("Failed to add " + mIfaceName + " to local table: ", e);
- }
- }
-
- private void addInterfaceForward(@NonNull final String fromIface,
- @NonNull final String toIface) throws ServiceSpecificException, RemoteException {
- if (null != mRoutingCoordinator.value) {
- mRoutingCoordinator.value.addInterfaceForward(fromIface, toIface);
- } else {
- mNetd.tetherAddForward(fromIface, toIface);
- mNetd.ipfwdAddInterfaceForward(fromIface, toIface);
- }
- }
-
- private void removeInterfaceForward(@NonNull final String fromIface,
- @NonNull final String toIface) {
- if (null != mRoutingCoordinator.value) {
- try {
- mRoutingCoordinator.value.removeInterfaceForward(fromIface, toIface);
- } catch (ServiceSpecificException e) {
- mLog.e("Exception in removeInterfaceForward", e);
- }
- } else {
- try {
- mNetd.ipfwdRemoveInterfaceForward(fromIface, toIface);
- } catch (RemoteException | ServiceSpecificException e) {
- mLog.e("Exception in ipfwdRemoveInterfaceForward", e);
- }
- try {
- mNetd.tetherRemoveForward(fromIface, toIface);
- } catch (RemoteException | ServiceSpecificException e) {
- mLog.e("Exception in disableNat", e);
- }
+ return;
}
}
@@ -959,14 +936,17 @@
return supportsBpf ? ifindex : NO_UPSTREAM;
}
- // Handles updates to IPv6 forwarding rules if the upstream changes.
- private void updateIpv6ForwardingRules(int prevUpstreamIfindex, int upstreamIfindex,
- boolean upstreamSupportsBpf) {
+ // Handles updates to IPv6 forwarding rules if the upstream or its prefixes change.
+ private void updateIpv6ForwardingRules(int prevUpstreamIfindex,
+ @NonNull Set<IpPrefix> prevUpstreamPrefixes, int upstreamIfindex,
+ @NonNull Set<IpPrefix> upstreamPrefixes, boolean upstreamSupportsBpf) {
// If the upstream interface has changed, remove all rules and re-add them with the new
// upstream interface. If upstream is a virtual network, treated as no upstream.
- if (prevUpstreamIfindex != upstreamIfindex) {
+ if (prevUpstreamIfindex != upstreamIfindex
+ || !prevUpstreamPrefixes.equals(upstreamPrefixes)) {
mBpfCoordinator.updateAllIpv6Rules(this, this.mInterfaceParams,
- getInterfaceIndexForRule(upstreamIfindex, upstreamSupportsBpf));
+ getInterfaceIndexForRule(upstreamIfindex, upstreamSupportsBpf),
+ upstreamPrefixes);
}
}
@@ -1151,7 +1131,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);
@@ -1241,6 +1233,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;
}
@@ -1371,7 +1366,7 @@
for (String ifname : mUpstreamIfaceSet.ifnames) cleanupUpstreamInterface(ifname);
mUpstreamIfaceSet = null;
mBpfCoordinator.updateAllIpv6Rules(
- IpServer.this, IpServer.this.mInterfaceParams, NO_UPSTREAM);
+ IpServer.this, IpServer.this.mInterfaceParams, NO_UPSTREAM, Set.of());
}
private void cleanupUpstreamInterface(String upstreamIface) {
@@ -1380,7 +1375,16 @@
// to remove their rules, which generates errors.
// Just do the best we can.
mBpfCoordinator.maybeDetachProgram(mIfaceName, upstreamIface);
- removeInterfaceForward(mIfaceName, upstreamIface);
+ try {
+ mNetd.ipfwdRemoveInterfaceForward(mIfaceName, upstreamIface);
+ } catch (RemoteException | ServiceSpecificException e) {
+ mLog.e("Exception in ipfwdRemoveInterfaceForward: " + e.toString());
+ }
+ try {
+ mNetd.tetherRemoveForward(mIfaceName, upstreamIface);
+ } catch (RemoteException | ServiceSpecificException e) {
+ mLog.e("Exception in disableNat: " + e.toString());
+ }
}
@Override
@@ -1436,9 +1440,10 @@
mBpfCoordinator.maybeAttachProgram(mIfaceName, ifname);
try {
- addInterfaceForward(mIfaceName, ifname);
+ mNetd.tetherAddForward(mIfaceName, ifname);
+ mNetd.ipfwdAddInterfaceForward(mIfaceName, ifname);
} catch (RemoteException | ServiceSpecificException e) {
- mLog.e("Exception enabling iface forward", e);
+ mLog.e("Exception enabling NAT: " + e.toString());
cleanupUpstream();
mLastError = TETHER_ERROR_ENABLE_FORWARDING_ERROR;
transitionTo(mInitialState);
@@ -1548,4 +1553,21 @@
}
return random;
}
+
+ /** Get IPv6 prefixes from LinkProperties */
+ @NonNull
+ @VisibleForTesting
+ static HashSet<IpPrefix> getTetherableIpv6Prefixes(@NonNull Collection<LinkAddress> addrs) {
+ final HashSet<IpPrefix> prefixes = new HashSet<>();
+ for (LinkAddress linkAddr : addrs) {
+ if (linkAddr.getPrefixLength() != RFC7421_PREFIX_LENGTH) continue;
+ prefixes.add(new IpPrefix(linkAddr.getAddress(), RFC7421_PREFIX_LENGTH));
+ }
+ return prefixes;
+ }
+
+ @NonNull
+ private HashSet<IpPrefix> getTetherableIpv6Prefixes(@NonNull LinkProperties lp) {
+ return getTetherableIpv6Prefixes(lp.getLinkAddresses());
+ }
}
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index 46c815f..2b14a42 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -28,6 +28,7 @@
import static android.system.OsConstants.ETH_P_IPV6;
import static com.android.net.module.util.NetworkStackConstants.IPV4_MIN_MTU;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_LEN;
import static com.android.net.module.util.ip.ConntrackMonitor.ConntrackEvent;
import static com.android.networkstack.tethering.BpfUtils.DOWNSTREAM;
import static com.android.networkstack.tethering.BpfUtils.UPSTREAM;
@@ -90,7 +91,6 @@
import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -123,7 +123,6 @@
private static final int DUMP_TIMEOUT_MS = 10_000;
private static final MacAddress NULL_MAC_ADDRESS = MacAddress.fromString(
"00:00:00:00:00:00");
- private static final IpPrefix IPV6_ZERO_PREFIX64 = new IpPrefix("::/64");
private static final String TETHER_DOWNSTREAM4_MAP_PATH = makeMapPath(DOWNSTREAM, 4);
private static final String TETHER_UPSTREAM4_MAP_PATH = makeMapPath(UPSTREAM, 4);
private static final String TETHER_DOWNSTREAM6_FS_PATH = makeMapPath(DOWNSTREAM, 6);
@@ -768,7 +767,8 @@
* Note that this can be only called on handler thread.
*/
public void updateAllIpv6Rules(@NonNull final IpServer ipServer,
- final InterfaceParams interfaceParams, int newUpstreamIfindex) {
+ final InterfaceParams interfaceParams, int newUpstreamIfindex,
+ @NonNull final Set<IpPrefix> newUpstreamPrefixes) {
if (!isUsingBpf()) return;
// Remove IPv6 downstream rules. Remove the old ones before adding the new rules, otherwise
@@ -791,9 +791,11 @@
// Add new upstream rules.
if (newUpstreamIfindex != 0 && interfaceParams != null && interfaceParams.macAddr != null) {
- addIpv6UpstreamRule(ipServer, new Ipv6UpstreamRule(
- newUpstreamIfindex, interfaceParams.index, IPV6_ZERO_PREFIX64,
- interfaceParams.macAddr, NULL_MAC_ADDRESS, NULL_MAC_ADDRESS));
+ for (final IpPrefix ipPrefix : newUpstreamPrefixes) {
+ addIpv6UpstreamRule(ipServer, new Ipv6UpstreamRule(
+ newUpstreamIfindex, interfaceParams.index, ipPrefix,
+ interfaceParams.macAddr, NULL_MAC_ADDRESS, NULL_MAC_ADDRESS));
+ }
}
// Add updated downstream rules.
@@ -1256,10 +1258,24 @@
pw.decreaseIndent();
}
+ private IpPrefix longToPrefix(long ip64) {
+ final ByteBuffer prefixBuffer = ByteBuffer.allocate(IPV6_ADDR_LEN);
+ prefixBuffer.putLong(ip64);
+ IpPrefix sourcePrefix;
+ try {
+ sourcePrefix = new IpPrefix(InetAddress.getByAddress(prefixBuffer.array()), 64);
+ } catch (UnknownHostException e) {
+ // Cannot happen. InetAddress.getByAddress can only throw an exception if the byte array
+ // is the wrong length, but we allocate it with fixed length IPV6_ADDR_LEN.
+ throw new IllegalArgumentException("Invalid IPv6 address");
+ }
+ return sourcePrefix;
+ }
+
private String ipv6UpstreamRuleToString(TetherUpstream6Key key, Tether6Value value) {
- return String.format("%d(%s) [%s] -> %d(%s) %04x [%s] [%s]",
- key.iif, getIfName(key.iif), key.dstMac, value.oif, getIfName(value.oif),
- value.ethProto, value.ethSrcMac, value.ethDstMac);
+ return String.format("%d(%s) [%s] [%s] -> %d(%s) %04x [%s] [%s]",
+ key.iif, getIfName(key.iif), key.dstMac, longToPrefix(key.src64), value.oif,
+ getIfName(value.oif), value.ethProto, value.ethSrcMac, value.ethDstMac);
}
private void dumpIpv6UpstreamRules(IndentingPrintWriter pw) {
@@ -1309,8 +1325,8 @@
// TODO: use dump utils with headerline and lambda which prints key and value to reduce
// duplicate bpf map dump code.
private void dumpBpfForwardingRulesIpv6(IndentingPrintWriter pw) {
- pw.println("IPv6 Upstream: iif(iface) [inDstMac] -> oif(iface) etherType [outSrcMac] "
- + "[outDstMac]");
+ pw.println("IPv6 Upstream: iif(iface) [inDstMac] [sourcePrefix] -> oif(iface) etherType "
+ + "[outSrcMac] [outDstMac]");
pw.increaseIndent();
dumpIpv6UpstreamRules(pw);
pw.decreaseIndent();
@@ -1554,8 +1570,7 @@
*/
@NonNull
public TetherUpstream6Key makeTetherUpstream6Key() {
- byte[] prefixBytes = Arrays.copyOf(sourcePrefix.getRawAddress(), 8);
- long prefix64 = ByteBuffer.wrap(prefixBytes).order(ByteOrder.BIG_ENDIAN).getLong();
+ long prefix64 = ByteBuffer.wrap(sourcePrefix.getRawAddress()).getLong();
return new TetherUpstream6Key(downstreamIfindex, inDstMac, prefix64);
}
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/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index 9724bd4..08fca4a 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -34,6 +34,7 @@
import static android.net.ip.IpServer.STATE_LOCAL_ONLY;
import static android.net.ip.IpServer.STATE_TETHERED;
import static android.net.ip.IpServer.STATE_UNAVAILABLE;
+import static android.net.ip.IpServer.getTetherableIpv6Prefixes;
import static android.system.OsConstants.ETH_P_IPV6;
import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
@@ -95,6 +96,8 @@
import android.os.RemoteException;
import android.os.test.TestLooper;
import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -145,12 +148,15 @@
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.verification.VerificationMode;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
+import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.List;
+import java.util.Set;
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -188,6 +194,18 @@
private final LinkAddress mTestAddress = new LinkAddress("192.168.42.5/24");
private final IpPrefix mBluetoothPrefix = new IpPrefix("192.168.44.0/24");
+ private static final Set<LinkAddress> NO_ADDRESSES = Set.of();
+ private static final Set<IpPrefix> NO_PREFIXES = Set.of();
+ private static final Set<LinkAddress> UPSTREAM_ADDRESSES =
+ Set.of(new LinkAddress("2001:db8:0:1234::168/64"));
+ private static final Set<IpPrefix> UPSTREAM_PREFIXES =
+ Set.of(new IpPrefix("2001:db8:0:1234::/64"));
+ private static final Set<LinkAddress> UPSTREAM_ADDRESSES2 = Set.of(
+ new LinkAddress("2001:db8:0:1234::168/64"),
+ new LinkAddress("2001:db8:0:abcd::168/64"));
+ private static final Set<IpPrefix> UPSTREAM_PREFIXES2 = Set.of(
+ new IpPrefix("2001:db8:0:1234::/64"), new IpPrefix("2001:db8:0:abcd::/64"));
+
@Mock private INetd mNetd;
@Mock private IpServer.Callback mCallback;
@Mock private SharedLog mSharedLog;
@@ -214,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;
@@ -252,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();
@@ -271,24 +284,27 @@
private void initTetheredStateMachine(int interfaceType, String upstreamIface)
throws Exception {
- initTetheredStateMachine(interfaceType, upstreamIface, false,
+ initTetheredStateMachine(interfaceType, upstreamIface, NO_ADDRESSES, false,
DEFAULT_USING_BPF_OFFLOAD);
}
private void initTetheredStateMachine(int interfaceType, String upstreamIface,
- boolean usingLegacyDhcp, boolean usingBpfOffload) throws Exception {
+ Set<LinkAddress> upstreamAddresses, boolean usingLegacyDhcp, boolean usingBpfOffload)
+ throws Exception {
initStateMachine(interfaceType, usingLegacyDhcp, usingBpfOffload);
dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
if (upstreamIface != null) {
LinkProperties lp = new LinkProperties();
lp.setInterfaceName(upstreamIface);
+ lp.setLinkAddresses(upstreamAddresses);
dispatchTetherConnectionChanged(upstreamIface, lp, 0);
- if (usingBpfOffload) {
+ if (usingBpfOffload && !lp.getLinkAddresses().isEmpty()) {
+ Set<IpPrefix> upstreamPrefixes = getTetherableIpv6Prefixes(lp.getLinkAddresses());
InterfaceParams interfaceParams = mDependencies.getInterfaceParams(upstreamIface);
assertNotNull("missing upstream interface: " + upstreamIface, interfaceParams);
verify(mBpfCoordinator).updateAllIpv6Rules(
- mIpServer, TEST_IFACE_PARAMS, interfaceParams.index);
- verifyStartUpstreamIpv6Forwarding(null, interfaceParams.index);
+ mIpServer, TEST_IFACE_PARAMS, interfaceParams.index, upstreamPrefixes);
+ verifyStartUpstreamIpv6Forwarding(null, interfaceParams.index, upstreamPrefixes);
} else {
verifyNoUpstreamIpv6ForwardingChange(null);
}
@@ -322,23 +338,13 @@
when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(DEFAULT_USING_BPF_OFFLOAD);
when(mTetherConfig.useLegacyDhcpServer()).thenReturn(false /* default value */);
- // Simulate the behavior of RoutingCoordinator
- if (null != mRoutingCoordinatorManager.value) {
- doAnswer(it -> {
- final String fromIface = (String) it.getArguments()[0];
- final String toIface = (String) it.getArguments()[1];
- mNetd.tetherAddForward(fromIface, toIface);
- mNetd.ipfwdAddInterfaceForward(fromIface, toIface);
- return null;
- }).when(mRoutingCoordinatorManager.value).addInterfaceForward(any(), any());
- doAnswer(it -> {
- final String fromIface = (String) it.getArguments()[0];
- final String toIface = (String) it.getArguments()[1];
- mNetd.ipfwdRemoveInterfaceForward(fromIface, toIface);
- mNetd.tetherRemoveForward(fromIface, toIface);
- 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() {
@@ -411,18 +417,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(
@@ -664,10 +671,7 @@
inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mBpfCoordinator).updateAllIpv6Rules(
- mIpServer, TEST_IFACE_PARAMS, NO_UPSTREAM);
- if (!mBpfDeps.isAtLeastS()) {
- inOrder.verify(mNetd).tetherOffloadGetAndClearStats(UPSTREAM_IFINDEX);
- }
+ mIpServer, TEST_IFACE_PARAMS, NO_UPSTREAM, NO_PREFIXES);
// When tethering stops, upstream interface is set to zero and thus clearing all upstream
// rules. Downstream rules are needed to be cleared explicitly by calling
// BpfCoordinator#clearAllIpv6Rules in TetheredState#exit.
@@ -857,8 +861,8 @@
@Test
public void doesNotStartDhcpServerIfDisabled() throws Exception {
- initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, true /* usingLegacyDhcp */,
- DEFAULT_USING_BPF_OFFLOAD);
+ initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, NO_ADDRESSES,
+ true /* usingLegacyDhcp */, DEFAULT_USING_BPF_OFFLOAD);
dispatchTetherConnectionChanged(UPSTREAM_IFACE);
verify(mDependencies, never()).makeDhcpServer(any(), any(), any());
@@ -963,11 +967,19 @@
TEST_IFACE_PARAMS.macAddr, ETH_P_IPV6, NetworkStackConstants.ETHER_MTU);
}
+ private static long prefixToLong(IpPrefix prefix) {
+ return ByteBuffer.wrap(prefix.getRawAddress()).getLong();
+ }
+
private <T> T verifyWithOrder(@Nullable InOrder inOrder, @NonNull T t) {
+ return verifyWithOrder(inOrder, t, times(1));
+ }
+
+ private <T> T verifyWithOrder(@Nullable InOrder inOrder, @NonNull T t, VerificationMode mode) {
if (inOrder != null) {
- return inOrder.verify(t);
+ return inOrder.verify(t, mode);
} else {
- return verify(t);
+ return verify(t, mode);
}
}
@@ -1027,23 +1039,49 @@
}
}
- private void verifyStartUpstreamIpv6Forwarding(@Nullable InOrder inOrder, int upstreamIfindex)
- throws Exception {
+ private void verifyStartUpstreamIpv6Forwarding(@Nullable InOrder inOrder, int upstreamIfindex,
+ @NonNull Set<IpPrefix> upstreamPrefixes) throws Exception {
if (!mBpfDeps.isAtLeastS()) return;
- final TetherUpstream6Key key = new TetherUpstream6Key(TEST_IFACE_PARAMS.index,
- TEST_IFACE_PARAMS.macAddr, 0);
- final Tether6Value value = new Tether6Value(upstreamIfindex,
- MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS,
- ETH_P_IPV6, NetworkStackConstants.ETHER_MTU);
- verifyWithOrder(inOrder, mBpfUpstream6Map).insertEntry(key, value);
+ ArrayMap<TetherUpstream6Key, Tether6Value> expected = new ArrayMap<>();
+ for (IpPrefix upstreamPrefix : upstreamPrefixes) {
+ long prefix64 = prefixToLong(upstreamPrefix);
+ final TetherUpstream6Key key = new TetherUpstream6Key(TEST_IFACE_PARAMS.index,
+ TEST_IFACE_PARAMS.macAddr, prefix64);
+ final Tether6Value value = new Tether6Value(upstreamIfindex,
+ MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS,
+ ETH_P_IPV6, NetworkStackConstants.ETHER_MTU);
+ expected.put(key, value);
+ }
+ ArgumentCaptor<TetherUpstream6Key> keyCaptor =
+ ArgumentCaptor.forClass(TetherUpstream6Key.class);
+ ArgumentCaptor<Tether6Value> valueCaptor =
+ ArgumentCaptor.forClass(Tether6Value.class);
+ verifyWithOrder(inOrder, mBpfUpstream6Map, times(expected.size())).insertEntry(
+ keyCaptor.capture(), valueCaptor.capture());
+ List<TetherUpstream6Key> keys = keyCaptor.getAllValues();
+ List<Tether6Value> values = valueCaptor.getAllValues();
+ ArrayMap<TetherUpstream6Key, Tether6Value> captured = new ArrayMap<>();
+ for (int i = 0; i < keys.size(); i++) {
+ captured.put(keys.get(i), values.get(i));
+ }
+ assertEquals(expected, captured);
}
- private void verifyStopUpstreamIpv6Forwarding(@Nullable InOrder inOrder)
- throws Exception {
+ private void verifyStopUpstreamIpv6Forwarding(@Nullable InOrder inOrder,
+ @NonNull Set<IpPrefix> upstreamPrefixes) throws Exception {
if (!mBpfDeps.isAtLeastS()) return;
- final TetherUpstream6Key key = new TetherUpstream6Key(TEST_IFACE_PARAMS.index,
- TEST_IFACE_PARAMS.macAddr, 0);
- verifyWithOrder(inOrder, mBpfUpstream6Map).deleteEntry(key);
+ Set<TetherUpstream6Key> expected = new ArraySet<>();
+ for (IpPrefix upstreamPrefix : upstreamPrefixes) {
+ long prefix64 = prefixToLong(upstreamPrefix);
+ final TetherUpstream6Key key = new TetherUpstream6Key(TEST_IFACE_PARAMS.index,
+ TEST_IFACE_PARAMS.macAddr, prefix64);
+ expected.add(key);
+ }
+ ArgumentCaptor<TetherUpstream6Key> keyCaptor =
+ ArgumentCaptor.forClass(TetherUpstream6Key.class);
+ verifyWithOrder(inOrder, mBpfUpstream6Map, times(expected.size())).deleteEntry(
+ keyCaptor.capture());
+ assertEquals(expected, new ArraySet(keyCaptor.getAllValues()));
}
private void verifyNoUpstreamIpv6ForwardingChange(@Nullable InOrder inOrder) throws Exception {
@@ -1084,8 +1122,8 @@
@Test
public void addRemoveipv6ForwardingRules() throws Exception {
- initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, false /* usingLegacyDhcp */,
- DEFAULT_USING_BPF_OFFLOAD);
+ initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, UPSTREAM_ADDRESSES,
+ false /* usingLegacyDhcp */, DEFAULT_USING_BPF_OFFLOAD);
final int myIfindex = TEST_IFACE_PARAMS.index;
final int notMyIfindex = myIfindex - 1;
@@ -1146,7 +1184,7 @@
UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighB, macNull);
resetNetdBpfMapAndCoordinator();
- // Upstream changes result in updating the rules.
+ // Upstream interface changes result in updating the rules.
recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA);
recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB);
resetNetdBpfMapAndCoordinator();
@@ -1154,14 +1192,36 @@
InOrder inOrder = inOrder(mNetd, mBpfDownstream6Map, mBpfUpstream6Map);
LinkProperties lp = new LinkProperties();
lp.setInterfaceName(UPSTREAM_IFACE2);
+ lp.setLinkAddresses(UPSTREAM_ADDRESSES);
dispatchTetherConnectionChanged(UPSTREAM_IFACE2, lp, -1);
- verify(mBpfCoordinator).updateAllIpv6Rules(mIpServer, TEST_IFACE_PARAMS, UPSTREAM_IFINDEX2);
+ verify(mBpfCoordinator).updateAllIpv6Rules(
+ mIpServer, TEST_IFACE_PARAMS, UPSTREAM_IFINDEX2, UPSTREAM_PREFIXES);
verifyTetherOffloadRuleRemove(inOrder,
UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighA, macA);
verifyTetherOffloadRuleRemove(inOrder,
UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighB, macB);
- verifyStopUpstreamIpv6Forwarding(inOrder);
- verifyStartUpstreamIpv6Forwarding(inOrder, UPSTREAM_IFINDEX2);
+ verifyStopUpstreamIpv6Forwarding(inOrder, UPSTREAM_PREFIXES);
+ verifyStartUpstreamIpv6Forwarding(inOrder, UPSTREAM_IFINDEX2, UPSTREAM_PREFIXES);
+ verifyTetherOffloadRuleAdd(inOrder,
+ UPSTREAM_IFINDEX2, UPSTREAM_IFACE_PARAMS2.macAddr, neighA, macA);
+ verifyTetherOffloadRuleAdd(inOrder,
+ UPSTREAM_IFINDEX2, UPSTREAM_IFACE_PARAMS2.macAddr, neighB, macB);
+ verifyNoUpstreamIpv6ForwardingChange(inOrder);
+ resetNetdBpfMapAndCoordinator();
+
+ // Upstream link addresses change result in updating the rules.
+ LinkProperties lp2 = new LinkProperties();
+ lp2.setInterfaceName(UPSTREAM_IFACE2);
+ lp2.setLinkAddresses(UPSTREAM_ADDRESSES2);
+ dispatchTetherConnectionChanged(UPSTREAM_IFACE2, lp2, -1);
+ verify(mBpfCoordinator).updateAllIpv6Rules(
+ mIpServer, TEST_IFACE_PARAMS, UPSTREAM_IFINDEX2, UPSTREAM_PREFIXES2);
+ verifyTetherOffloadRuleRemove(inOrder,
+ UPSTREAM_IFINDEX2, UPSTREAM_IFACE_PARAMS2.macAddr, neighA, macA);
+ verifyTetherOffloadRuleRemove(inOrder,
+ UPSTREAM_IFINDEX2, UPSTREAM_IFACE_PARAMS2.macAddr, neighB, macB);
+ verifyStopUpstreamIpv6Forwarding(inOrder, UPSTREAM_PREFIXES);
+ verifyStartUpstreamIpv6Forwarding(inOrder, UPSTREAM_IFINDEX2, UPSTREAM_PREFIXES2);
verifyTetherOffloadRuleAdd(inOrder,
UPSTREAM_IFINDEX2, UPSTREAM_IFACE_PARAMS2.macAddr, neighA, macA);
verifyTetherOffloadRuleAdd(inOrder,
@@ -1175,8 +1235,8 @@
// - processMessage CMD_IPV6_TETHER_UPDATE for the IPv6 upstream is lost.
// See dispatchTetherConnectionChanged.
verify(mBpfCoordinator, times(2)).updateAllIpv6Rules(
- mIpServer, TEST_IFACE_PARAMS, NO_UPSTREAM);
- verifyStopUpstreamIpv6Forwarding(inOrder);
+ mIpServer, TEST_IFACE_PARAMS, NO_UPSTREAM, NO_PREFIXES);
+ verifyStopUpstreamIpv6Forwarding(inOrder, UPSTREAM_PREFIXES2);
verifyTetherOffloadRuleRemove(null,
UPSTREAM_IFINDEX2, UPSTREAM_IFACE_PARAMS2.macAddr, neighA, macA);
verifyTetherOffloadRuleRemove(null,
@@ -1206,7 +1266,7 @@
// with an upstream of NO_UPSTREAM are reapplied.
lp.setInterfaceName(UPSTREAM_IFACE);
dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp, -1);
- verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX);
+ verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX, UPSTREAM_PREFIXES);
verify(mBpfCoordinator).addIpv6DownstreamRule(
mIpServer, makeDownstreamRule(UPSTREAM_IFINDEX, neighA, macA));
verifyTetherOffloadRuleAdd(null,
@@ -1220,16 +1280,17 @@
// If upstream IPv6 connectivity is lost, rules are removed.
resetNetdBpfMapAndCoordinator();
dispatchTetherConnectionChanged(UPSTREAM_IFACE, null, 0);
- verify(mBpfCoordinator).updateAllIpv6Rules(mIpServer, TEST_IFACE_PARAMS, NO_UPSTREAM);
+ verify(mBpfCoordinator).updateAllIpv6Rules(
+ mIpServer, TEST_IFACE_PARAMS, NO_UPSTREAM, NO_PREFIXES);
verifyTetherOffloadRuleRemove(null,
UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighB, macB);
- verifyStopUpstreamIpv6Forwarding(null);
+ verifyStopUpstreamIpv6Forwarding(null, UPSTREAM_PREFIXES);
// When upstream IPv6 connectivity comes back, upstream rules are added and downstream rules
// are reapplied.
lp.setInterfaceName(UPSTREAM_IFACE);
dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp, -1);
- verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX);
+ verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX, UPSTREAM_PREFIXES);
verify(mBpfCoordinator).addIpv6DownstreamRule(
mIpServer, makeDownstreamRule(UPSTREAM_IFINDEX, neighA, macA));
verifyTetherOffloadRuleAdd(null,
@@ -1244,7 +1305,7 @@
mIpServer.stop();
mLooper.dispatchAll();
verify(mBpfCoordinator).clearAllIpv6Rules(mIpServer);
- verifyStopUpstreamIpv6Forwarding(null);
+ verifyStopUpstreamIpv6Forwarding(null, UPSTREAM_PREFIXES);
verifyTetherOffloadRuleRemove(null,
UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighA, macA);
verifyTetherOffloadRuleRemove(null,
@@ -1269,8 +1330,8 @@
// [1] Enable BPF offload.
// A neighbor that is added or deleted causes the rule to be added or removed.
- initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, false /* usingLegacyDhcp */,
- true /* usingBpfOffload */);
+ initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, UPSTREAM_ADDRESSES,
+ false /* usingLegacyDhcp */, true /* usingBpfOffload */);
resetNetdBpfMapAndCoordinator();
recvNewNeigh(myIfindex, neigh, NUD_REACHABLE, macA);
@@ -1290,15 +1351,17 @@
// Upstream IPv6 connectivity change causes upstream rules change.
LinkProperties lp2 = new LinkProperties();
lp2.setInterfaceName(UPSTREAM_IFACE2);
+ lp2.setLinkAddresses(UPSTREAM_ADDRESSES2);
dispatchTetherConnectionChanged(UPSTREAM_IFACE2, lp2, 0);
- verify(mBpfCoordinator).updateAllIpv6Rules(mIpServer, TEST_IFACE_PARAMS, UPSTREAM_IFINDEX2);
- verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX2);
+ verify(mBpfCoordinator).updateAllIpv6Rules(
+ mIpServer, TEST_IFACE_PARAMS, UPSTREAM_IFINDEX2, UPSTREAM_PREFIXES2);
+ verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX2, UPSTREAM_PREFIXES2);
resetNetdBpfMapAndCoordinator();
// [2] Disable BPF offload.
// A neighbor that is added or deleted doesn’t cause the rule to be added or removed.
- initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, false /* usingLegacyDhcp */,
- false /* usingBpfOffload */);
+ initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, UPSTREAM_ADDRESSES,
+ false /* usingLegacyDhcp */, false /* usingBpfOffload */);
resetNetdBpfMapAndCoordinator();
recvNewNeigh(myIfindex, neigh, NUD_REACHABLE, macA);
@@ -1318,8 +1381,8 @@
@Test
public void doesNotStartIpNeighborMonitorIfBpfOffloadDisabled() throws Exception {
- initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, false /* usingLegacyDhcp */,
- false /* usingBpfOffload */);
+ initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, UPSTREAM_ADDRESSES,
+ false /* usingLegacyDhcp */, false /* usingBpfOffload */);
// IP neighbor monitor doesn't start if BPF offload is disabled.
verify(mIpNeighborMonitor, never()).start();
@@ -1601,8 +1664,8 @@
// TODO: move to BpfCoordinatorTest once IpNeighborMonitor is migrated to BpfCoordinator.
@Test
public void addRemoveTetherClient() throws Exception {
- initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, false /* usingLegacyDhcp */,
- DEFAULT_USING_BPF_OFFLOAD);
+ initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, UPSTREAM_ADDRESSES,
+ false /* usingLegacyDhcp */, DEFAULT_USING_BPF_OFFLOAD);
final int myIfindex = TEST_IFACE_PARAMS.index;
final int notMyIfindex = myIfindex - 1;
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
index 601f587..7fbb670 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
@@ -55,6 +55,7 @@
import static com.android.networkstack.tethering.BpfUtils.DOWNSTREAM;
import static com.android.networkstack.tethering.BpfUtils.UPSTREAM;
import static com.android.networkstack.tethering.TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS;
+import static com.android.testutils.MiscAsserts.assertSameElements;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -73,6 +74,7 @@
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -92,6 +94,7 @@
import android.os.Build;
import android.os.Handler;
import android.os.test.TestLooper;
+import android.util.ArrayMap;
import android.util.SparseArray;
import androidx.annotation.NonNull;
@@ -136,16 +139,20 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.MockitoSession;
+import org.mockito.verification.VerificationMode;
import java.io.StringWriter;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
+import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
+import java.util.List;
import java.util.Map;
+import java.util.Set;
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -159,7 +166,7 @@
private static final int TEST_NET_ID = 24;
private static final int TEST_NET_ID2 = 25;
- private static final int INVALID_IFINDEX = 0;
+ private static final int NO_UPSTREAM = 0;
private static final int UPSTREAM_IFINDEX = 1001;
private static final int UPSTREAM_XLAT_IFINDEX = 1002;
private static final int UPSTREAM_IFINDEX2 = 1003;
@@ -178,8 +185,17 @@
private static final MacAddress MAC_A = MacAddress.fromString("00:00:00:00:00:0a");
private static final MacAddress MAC_B = MacAddress.fromString("11:22:33:00:00:0b");
- private static final InetAddress NEIGH_A = InetAddresses.parseNumericAddress("2001:db8::1");
- private static final InetAddress NEIGH_B = InetAddresses.parseNumericAddress("2001:db8::2");
+ private static final IpPrefix UPSTREAM_PREFIX = new IpPrefix("2001:db8:0:1234::/64");
+ private static final IpPrefix UPSTREAM_PREFIX2 = new IpPrefix("2001:db8:0:abcd::/64");
+ private static final Set<IpPrefix> UPSTREAM_PREFIXES = Set.of(UPSTREAM_PREFIX);
+ private static final Set<IpPrefix> UPSTREAM_PREFIXES2 =
+ Set.of(UPSTREAM_PREFIX, UPSTREAM_PREFIX2);
+ private static final Set<IpPrefix> NO_PREFIXES = Set.of();
+
+ private static final InetAddress NEIGH_A =
+ InetAddresses.parseNumericAddress("2001:db8:0:1234::1");
+ private static final InetAddress NEIGH_B =
+ InetAddresses.parseNumericAddress("2001:db8:0:1234::2");
private static final Inet4Address REMOTE_ADDR =
(Inet4Address) InetAddresses.parseNumericAddress("140.112.8.116");
@@ -195,7 +211,6 @@
private static final Inet4Address XLAT_LOCAL_IPV4ADDR =
(Inet4Address) InetAddresses.parseNumericAddress("192.0.0.46");
private static final IpPrefix NAT64_IP_PREFIX = new IpPrefix("64:ff9b::/96");
- private static final IpPrefix IPV6_ZERO_PREFIX = new IpPrefix("::/64");
// Generally, public port and private port are the same in the NAT conntrack message.
// TODO: consider using different private port and public port for testing.
@@ -624,10 +639,14 @@
}
private <T> T verifyWithOrder(@Nullable InOrder inOrder, @NonNull T t) {
+ return verifyWithOrder(inOrder, t, times(1));
+ }
+
+ private <T> T verifyWithOrder(@Nullable InOrder inOrder, @NonNull T t, VerificationMode mode) {
if (inOrder != null) {
- return inOrder.verify(t);
+ return inOrder.verify(t, mode);
} else {
- return verify(t);
+ return verify(t, mode);
}
}
@@ -667,6 +686,28 @@
rule.makeTetherUpstream6Key(), rule.makeTether6Value());
}
+ private void verifyAddUpstreamRules(@Nullable InOrder inOrder,
+ @NonNull Set<Ipv6UpstreamRule> rules) throws Exception {
+ if (!mDeps.isAtLeastS()) return;
+ ArrayMap<TetherUpstream6Key, Tether6Value> expected = new ArrayMap<>();
+ for (Ipv6UpstreamRule rule : rules) {
+ expected.put(rule.makeTetherUpstream6Key(), rule.makeTether6Value());
+ }
+ ArgumentCaptor<TetherUpstream6Key> keyCaptor =
+ ArgumentCaptor.forClass(TetherUpstream6Key.class);
+ ArgumentCaptor<Tether6Value> valueCaptor =
+ ArgumentCaptor.forClass(Tether6Value.class);
+ verifyWithOrder(inOrder, mBpfUpstream6Map, times(expected.size())).insertEntry(
+ keyCaptor.capture(), valueCaptor.capture());
+ List<TetherUpstream6Key> keys = keyCaptor.getAllValues();
+ List<Tether6Value> values = valueCaptor.getAllValues();
+ ArrayMap<TetherUpstream6Key, Tether6Value> captured = new ArrayMap<>();
+ for (int i = 0; i < keys.size(); i++) {
+ captured.put(keys.get(i), values.get(i));
+ }
+ assertEquals(expected, captured);
+ }
+
private void verifyAddDownstreamRule(@Nullable InOrder inOrder,
@NonNull Ipv6DownstreamRule rule) throws Exception {
if (mDeps.isAtLeastS()) {
@@ -697,6 +738,20 @@
rule.makeTetherUpstream6Key());
}
+ private void verifyRemoveUpstreamRules(@Nullable InOrder inOrder,
+ @NonNull Set<Ipv6UpstreamRule> rules) throws Exception {
+ if (!mDeps.isAtLeastS()) return;
+ List<TetherUpstream6Key> expected = new ArrayList<>();
+ for (Ipv6UpstreamRule rule : rules) {
+ expected.add(rule.makeTetherUpstream6Key());
+ }
+ ArgumentCaptor<TetherUpstream6Key> keyCaptor =
+ ArgumentCaptor.forClass(TetherUpstream6Key.class);
+ verifyWithOrder(inOrder, mBpfUpstream6Map, times(expected.size())).deleteEntry(
+ keyCaptor.capture());
+ assertSameElements(expected, keyCaptor.getAllValues());
+ }
+
private void verifyRemoveDownstreamRule(@Nullable InOrder inOrder,
@NonNull final Ipv6DownstreamRule rule) throws Exception {
if (mDeps.isAtLeastS()) {
@@ -785,10 +840,11 @@
final InOrder inOrder = inOrder(mNetd, mBpfUpstream6Map, mBpfDownstream6Map, mBpfLimitMap,
mBpfStatsMap);
final Ipv6UpstreamRule upstreamRule = buildTestUpstreamRule(
- mobileIfIndex, DOWNSTREAM_IFINDEX, DOWNSTREAM_MAC);
+ mobileIfIndex, DOWNSTREAM_IFINDEX, UPSTREAM_PREFIX, DOWNSTREAM_MAC);
final Ipv6DownstreamRule downstreamRule = buildTestDownstreamRule(
mobileIfIndex, NEIGH_A, MAC_A);
- coordinator.updateAllIpv6Rules(mIpServer, DOWNSTREAM_IFACE_PARAMS, mobileIfIndex);
+ coordinator.updateAllIpv6Rules(
+ mIpServer, DOWNSTREAM_IFACE_PARAMS, mobileIfIndex, UPSTREAM_PREFIXES);
verifyTetherOffloadSetInterfaceQuota(inOrder, mobileIfIndex, QUOTA_UNLIMITED,
true /* isInit */);
verifyAddUpstreamRule(inOrder, upstreamRule);
@@ -798,7 +854,8 @@
// Removing the last rule on current upstream immediately sends the cleanup stuff to BPF.
updateStatsEntryForTetherOffloadGetAndClearStats(
buildTestTetherStatsParcel(mobileIfIndex, 0, 0, 0, 0));
- coordinator.updateAllIpv6Rules(mIpServer, DOWNSTREAM_IFACE_PARAMS, 0);
+ coordinator.updateAllIpv6Rules(
+ mIpServer, DOWNSTREAM_IFACE_PARAMS, NO_UPSTREAM, NO_PREFIXES);
verifyRemoveDownstreamRule(inOrder, downstreamRule);
verifyRemoveUpstreamRule(inOrder, upstreamRule);
verifyTetherOffloadGetAndClearStats(inOrder, mobileIfIndex);
@@ -998,11 +1055,10 @@
}
@NonNull
- private static Ipv6UpstreamRule buildTestUpstreamRule(
- int upstreamIfindex, int downstreamIfindex, @NonNull MacAddress inDstMac) {
- return new Ipv6UpstreamRule(upstreamIfindex, downstreamIfindex,
- IPV6_ZERO_PREFIX, inDstMac, MacAddress.ALL_ZEROS_ADDRESS,
- MacAddress.ALL_ZEROS_ADDRESS);
+ private static Ipv6UpstreamRule buildTestUpstreamRule(int upstreamIfindex,
+ int downstreamIfindex, @NonNull IpPrefix sourcePrefix, @NonNull MacAddress inDstMac) {
+ return new Ipv6UpstreamRule(upstreamIfindex, downstreamIfindex, sourcePrefix, inDstMac,
+ MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS);
}
@NonNull
@@ -1054,9 +1110,10 @@
// Set the unlimited quota as default if the service has never applied a data limit for a
// given upstream. Note that the data limit only be applied on an upstream which has rules.
final Ipv6UpstreamRule rule = buildTestUpstreamRule(
- mobileIfIndex, DOWNSTREAM_IFINDEX, DOWNSTREAM_MAC);
+ mobileIfIndex, DOWNSTREAM_IFINDEX, UPSTREAM_PREFIX, DOWNSTREAM_MAC);
final InOrder inOrder = inOrder(mNetd, mBpfUpstream6Map, mBpfLimitMap, mBpfStatsMap);
- coordinator.updateAllIpv6Rules(mIpServer, DOWNSTREAM_IFACE_PARAMS, mobileIfIndex);
+ coordinator.updateAllIpv6Rules(
+ mIpServer, DOWNSTREAM_IFACE_PARAMS, mobileIfIndex, UPSTREAM_PREFIXES);
verifyTetherOffloadSetInterfaceQuota(inOrder, mobileIfIndex, QUOTA_UNLIMITED,
true /* isInit */);
verifyAddUpstreamRule(inOrder, rule);
@@ -1104,28 +1161,32 @@
// Adding the first rule on current upstream immediately sends the quota to BPF.
final Ipv6UpstreamRule ruleA = buildTestUpstreamRule(
- mobileIfIndex, DOWNSTREAM_IFINDEX, DOWNSTREAM_MAC);
- coordinator.updateAllIpv6Rules(mIpServer, DOWNSTREAM_IFACE_PARAMS, mobileIfIndex);
+ mobileIfIndex, DOWNSTREAM_IFINDEX, UPSTREAM_PREFIX, DOWNSTREAM_MAC);
+ coordinator.updateAllIpv6Rules(
+ mIpServer, DOWNSTREAM_IFACE_PARAMS, mobileIfIndex, UPSTREAM_PREFIXES);
verifyTetherOffloadSetInterfaceQuota(inOrder, mobileIfIndex, limit, true /* isInit */);
verifyAddUpstreamRule(inOrder, ruleA);
inOrder.verifyNoMoreInteractions();
// Adding the second rule on current upstream does not send the quota to BPF.
final Ipv6UpstreamRule ruleB = buildTestUpstreamRule(
- mobileIfIndex, DOWNSTREAM_IFINDEX2, DOWNSTREAM_MAC2);
- coordinator.updateAllIpv6Rules(mIpServer2, DOWNSTREAM_IFACE_PARAMS2, mobileIfIndex);
+ mobileIfIndex, DOWNSTREAM_IFINDEX2, UPSTREAM_PREFIX, DOWNSTREAM_MAC2);
+ coordinator.updateAllIpv6Rules(
+ mIpServer2, DOWNSTREAM_IFACE_PARAMS2, mobileIfIndex, UPSTREAM_PREFIXES);
verifyAddUpstreamRule(inOrder, ruleB);
verifyNeverTetherOffloadSetInterfaceQuota(inOrder);
// Removing the second rule on current upstream does not send the quota to BPF.
- coordinator.updateAllIpv6Rules(mIpServer2, DOWNSTREAM_IFACE_PARAMS2, 0);
+ coordinator.updateAllIpv6Rules(
+ mIpServer2, DOWNSTREAM_IFACE_PARAMS2, NO_UPSTREAM, NO_PREFIXES);
verifyRemoveUpstreamRule(inOrder, ruleB);
verifyNeverTetherOffloadSetInterfaceQuota(inOrder);
// Removing the last rule on current upstream immediately sends the cleanup stuff to BPF.
updateStatsEntryForTetherOffloadGetAndClearStats(
buildTestTetherStatsParcel(mobileIfIndex, 0, 0, 0, 0));
- coordinator.updateAllIpv6Rules(mIpServer, DOWNSTREAM_IFACE_PARAMS, 0);
+ coordinator.updateAllIpv6Rules(
+ mIpServer, DOWNSTREAM_IFACE_PARAMS, NO_UPSTREAM, NO_PREFIXES);
verifyRemoveUpstreamRule(inOrder, ruleA);
verifyTetherOffloadGetAndClearStats(inOrder, mobileIfIndex);
inOrder.verifyNoMoreInteractions();
@@ -1157,13 +1218,14 @@
// [1] Adding rules on the upstream Ethernet.
// Note that the default data limit is applied after the first rule is added.
final Ipv6UpstreamRule ethernetUpstreamRule = buildTestUpstreamRule(
- ethIfIndex, DOWNSTREAM_IFINDEX, DOWNSTREAM_MAC);
+ ethIfIndex, DOWNSTREAM_IFINDEX, UPSTREAM_PREFIX, DOWNSTREAM_MAC);
final Ipv6DownstreamRule ethernetRuleA = buildTestDownstreamRule(
ethIfIndex, NEIGH_A, MAC_A);
final Ipv6DownstreamRule ethernetRuleB = buildTestDownstreamRule(
ethIfIndex, NEIGH_B, MAC_B);
- coordinator.updateAllIpv6Rules(mIpServer, DOWNSTREAM_IFACE_PARAMS, ethIfIndex);
+ coordinator.updateAllIpv6Rules(
+ mIpServer, DOWNSTREAM_IFACE_PARAMS, ethIfIndex, UPSTREAM_PREFIXES);
verifyTetherOffloadSetInterfaceQuota(inOrder, ethIfIndex, QUOTA_UNLIMITED,
true /* isInit */);
verifyAddUpstreamRule(inOrder, ethernetUpstreamRule);
@@ -1174,7 +1236,9 @@
// [2] Update the existing rules from Ethernet to cellular.
final Ipv6UpstreamRule mobileUpstreamRule = buildTestUpstreamRule(
- mobileIfIndex, DOWNSTREAM_IFINDEX, DOWNSTREAM_MAC);
+ mobileIfIndex, DOWNSTREAM_IFINDEX, UPSTREAM_PREFIX, DOWNSTREAM_MAC);
+ final Ipv6UpstreamRule mobileUpstreamRule2 = buildTestUpstreamRule(
+ mobileIfIndex, DOWNSTREAM_IFINDEX, UPSTREAM_PREFIX2, DOWNSTREAM_MAC);
final Ipv6DownstreamRule mobileRuleA = buildTestDownstreamRule(
mobileIfIndex, NEIGH_A, MAC_A);
final Ipv6DownstreamRule mobileRuleB = buildTestDownstreamRule(
@@ -1183,15 +1247,16 @@
buildTestTetherStatsParcel(ethIfIndex, 10, 20, 30, 40));
// Update the existing rules for upstream changes. The rules are removed and re-added one
- // by one for updating upstream interface index by #tetherOffloadRuleUpdate.
- coordinator.updateAllIpv6Rules(mIpServer, DOWNSTREAM_IFACE_PARAMS, mobileIfIndex);
+ // by one for updating upstream interface index and prefixes by #tetherOffloadRuleUpdate.
+ coordinator.updateAllIpv6Rules(
+ mIpServer, DOWNSTREAM_IFACE_PARAMS, mobileIfIndex, UPSTREAM_PREFIXES2);
verifyRemoveDownstreamRule(inOrder, ethernetRuleA);
verifyRemoveDownstreamRule(inOrder, ethernetRuleB);
verifyRemoveUpstreamRule(inOrder, ethernetUpstreamRule);
verifyTetherOffloadGetAndClearStats(inOrder, ethIfIndex);
verifyTetherOffloadSetInterfaceQuota(inOrder, mobileIfIndex, QUOTA_UNLIMITED,
true /* isInit */);
- verifyAddUpstreamRule(inOrder, mobileUpstreamRule);
+ verifyAddUpstreamRules(inOrder, Set.of(mobileUpstreamRule, mobileUpstreamRule2));
verifyAddDownstreamRule(inOrder, mobileRuleA);
verifyAddDownstreamRule(inOrder, mobileRuleB);
@@ -1201,7 +1266,7 @@
coordinator.clearAllIpv6Rules(mIpServer);
verifyRemoveDownstreamRule(inOrder, mobileRuleA);
verifyRemoveDownstreamRule(inOrder, mobileRuleB);
- verifyRemoveUpstreamRule(inOrder, mobileUpstreamRule);
+ verifyRemoveUpstreamRules(inOrder, Set.of(mobileUpstreamRule, mobileUpstreamRule2));
verifyTetherOffloadGetAndClearStats(inOrder, mobileIfIndex);
// [4] Force pushing stats update to verify that the last diff of stats is reported on all
@@ -1264,8 +1329,8 @@
assertEquals(1, rules.size());
// The rule can't be updated.
- coordinator.updateAllIpv6Rules(
- mIpServer, DOWNSTREAM_IFACE_PARAMS, rule.upstreamIfindex + 1 /* new */);
+ coordinator.updateAllIpv6Rules(mIpServer, DOWNSTREAM_IFACE_PARAMS,
+ rule.upstreamIfindex + 1 /* new */, UPSTREAM_PREFIXES);
verifyNeverRemoveDownstreamRule();
verifyNeverAddDownstreamRule();
rules = coordinator.getIpv6DownstreamRulesForTesting().get(mIpServer);
@@ -1561,12 +1626,12 @@
//
// @param coordinator BpfCoordinator instance.
// @param upstreamIfindex upstream interface index. can be the following values.
- // INVALID_IFINDEX: no upstream interface
+ // NO_UPSTREAM: no upstream interface
// UPSTREAM_IFINDEX: CELLULAR (raw ip interface)
// UPSTREAM_IFINDEX2: WIFI (ethernet interface)
private void setUpstreamInformationTo(final BpfCoordinator coordinator,
@Nullable Integer upstreamIfindex) {
- if (upstreamIfindex == INVALID_IFINDEX) {
+ if (upstreamIfindex == NO_UPSTREAM) {
coordinator.updateUpstreamNetworkState(null);
return;
}
@@ -1706,7 +1771,8 @@
final BpfCoordinator coordinator = makeBpfCoordinator();
coordinator.maybeAddUpstreamToLookupTable(UPSTREAM_IFINDEX, UPSTREAM_IFACE);
- coordinator.updateAllIpv6Rules(mIpServer, DOWNSTREAM_IFACE_PARAMS, UPSTREAM_IFINDEX);
+ coordinator.updateAllIpv6Rules(
+ mIpServer, DOWNSTREAM_IFACE_PARAMS, UPSTREAM_IFINDEX, UPSTREAM_PREFIXES);
verify(mBpfDevMap).updateEntry(eq(new TetherDevKey(UPSTREAM_IFINDEX)),
eq(new TetherDevValue(UPSTREAM_IFINDEX)));
verify(mBpfDevMap).updateEntry(eq(new TetherDevKey(DOWNSTREAM_IFINDEX)),
@@ -1715,7 +1781,8 @@
// Adding the second downstream, only the second downstream ifindex is added to DevMap,
// the existing upstream ifindex won't be added again.
- coordinator.updateAllIpv6Rules(mIpServer2, DOWNSTREAM_IFACE_PARAMS2, UPSTREAM_IFINDEX);
+ coordinator.updateAllIpv6Rules(
+ mIpServer2, DOWNSTREAM_IFACE_PARAMS2, UPSTREAM_IFINDEX, UPSTREAM_PREFIXES);
verify(mBpfDevMap).updateEntry(eq(new TetherDevKey(DOWNSTREAM_IFINDEX2)),
eq(new TetherDevValue(DOWNSTREAM_IFINDEX2)));
verify(mBpfDevMap, never()).updateEntry(eq(new TetherDevKey(UPSTREAM_IFINDEX)),
@@ -1996,6 +2063,11 @@
100 /* nonzero, CT_NEW */);
}
+ private static long prefixToLong(IpPrefix prefix) {
+ byte[] prefixBytes = Arrays.copyOf(prefix.getRawAddress(), 8);
+ return ByteBuffer.wrap(prefixBytes).getLong();
+ }
+
void checkRule4ExistInUpstreamDownstreamMap() throws Exception {
assertEquals(UPSTREAM4_RULE_VALUE_A, mBpfUpstream4Map.getValue(UPSTREAM4_RULE_KEY_A));
assertEquals(DOWNSTREAM4_RULE_VALUE_A, mBpfDownstream4Map.getValue(
@@ -2113,7 +2185,7 @@
// [3] Switch upstream from the first upstream (rawip, bpf supported) to no upstream. Clear
// all rules.
- setUpstreamInformationTo(coordinator, INVALID_IFINDEX);
+ setUpstreamInformationTo(coordinator, NO_UPSTREAM);
checkRule4NotExistInUpstreamDownstreamMap();
// Client information should be not deleted.
@@ -2180,14 +2252,15 @@
public void testIpv6ForwardingRuleToString() throws Exception {
final Ipv6DownstreamRule downstreamRule = buildTestDownstreamRule(UPSTREAM_IFINDEX, NEIGH_A,
MAC_A);
- assertEquals("upstreamIfindex: 1001, downstreamIfindex: 2001, address: 2001:db8::1, "
+ assertEquals("upstreamIfindex: 1001, downstreamIfindex: 2001, address: 2001:db8:0:1234::1, "
+ "srcMac: 12:34:56:78:90:ab, dstMac: 00:00:00:00:00:0a",
downstreamRule.toString());
final Ipv6UpstreamRule upstreamRule = buildTestUpstreamRule(
- UPSTREAM_IFINDEX, DOWNSTREAM_IFINDEX, DOWNSTREAM_MAC);
- assertEquals("upstreamIfindex: 1001, downstreamIfindex: 2001, sourcePrefix: ::/64, "
- + "inDstMac: 12:34:56:78:90:ab, outSrcMac: 00:00:00:00:00:00, "
- + "outDstMac: 00:00:00:00:00:00", upstreamRule.toString());
+ UPSTREAM_IFINDEX, DOWNSTREAM_IFINDEX, UPSTREAM_PREFIX, DOWNSTREAM_MAC);
+ assertEquals("upstreamIfindex: 1001, downstreamIfindex: 2001, "
+ + "sourcePrefix: 2001:db8:0:1234::/64, inDstMac: 12:34:56:78:90:ab, "
+ + "outSrcMac: 00:00:00:00:00:00, outDstMac: 00:00:00:00:00:00",
+ upstreamRule.toString());
}
private void verifyDump(@NonNull final BpfCoordinator coordinator) {
@@ -2237,8 +2310,9 @@
final Ipv6DownstreamRule rule = buildTestDownstreamRule(UPSTREAM_IFINDEX, NEIGH_A, MAC_A);
mBpfDownstream6Map.insertEntry(rule.makeTetherDownstream6Key(), rule.makeTether6Value());
+ final long prefix64 = prefixToLong(UPSTREAM_PREFIX);
final TetherUpstream6Key upstream6Key = new TetherUpstream6Key(DOWNSTREAM_IFINDEX,
- DOWNSTREAM_MAC, 0);
+ DOWNSTREAM_MAC, prefix64);
final Tether6Value upstream6Value = new Tether6Value(UPSTREAM_IFINDEX,
MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS,
ETH_P_IPV6, NetworkStackConstants.ETHER_MTU);
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/bpf_progs/offload.c b/bpf_progs/offload.c
index 35b8eea..90f96a1 100644
--- a/bpf_progs/offload.c
+++ b/bpf_progs/offload.c
@@ -198,7 +198,8 @@
TetherUpstream6Key ku = {
.iif = skb->ifindex,
- .src64 = 0,
+ // Retrieve the first 64 bits of the source IPv6 address in network order
+ .src64 = *(uint64_t*)&(ip6->saddr.s6_addr32[0]),
};
if (is_ethernet) __builtin_memcpy(stream.down ? kd.dstMac : ku.dstMac, eth->h_dest, ETH_ALEN);
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..eabcf3c
--- /dev/null
+++ b/common/src/com/android/net/module/util/bpf/IngressDiscardKey.java
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+import java.net.Inet6Address;
+
+/** 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;
+ }
+}
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/src/android/net/BpfNetMapsConstants.java b/framework/src/android/net/BpfNetMapsConstants.java
index e0527f5..8086809 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,14 @@
"/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 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 +75,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 +90,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..9bce9cd 100644
--- a/framework/src/android/net/BpfNetMapsReader.java
+++ b/framework/src/android/net/BpfNetMapsReader.java
@@ -57,10 +57,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 +208,33 @@
"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.
+ *
+ * @return True if the network is blocked. Otherwise, false.
+ * @throws ServiceSpecificException if the read fails.
+ *
+ * @hide
+ */
+ public boolean isUidBlockedByFirewallChains(final int uid) {
+ 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);
+ return blockedByAllowChains || blockedByDenyChains;
+ }
}
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..ad76012 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -6198,6 +6198,36 @@
}
}
+ /**
+ * Return whether the network is blocked for the given uid.
+ *
+ * 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.
+ * @return True if all networking 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.
+ // TODO: Expose api when ready.
+ // @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) {
+ final BpfNetMapsReader reader = BpfNetMapsReader.getInstance();
+
+ return reader.isUidBlockedByFirewallChains(uid);
+
+ // TODO: If isNetworkMetered is true, check the data saver switch, penalty box
+ // and happy box rules.
+ }
+
/** @hide */
public IBinder getCompanionDeviceManagerProxyService() {
try {
diff --git a/framework/src/android/net/IRoutingCoordinator.aidl b/framework/src/android/net/IRoutingCoordinator.aidl
index cf02ec4..a5cda98 100644
--- a/framework/src/android/net/IRoutingCoordinator.aidl
+++ b/framework/src/android/net/IRoutingCoordinator.aidl
@@ -72,24 +72,4 @@
* unix errno.
*/
void removeInterfaceFromNetwork(int netId, in String iface);
-
- /**
- * Add forwarding ip rule
- *
- * @param fromIface interface name to add forwarding ip rule
- * @param toIface interface name to add forwarding ip rule
- * @throws ServiceSpecificException in case of failure, with an error code indicating the
- * cause of the failure.
- */
- void addInterfaceForward(in String fromIface, in String toIface);
-
- /**
- * Remove forwarding ip rule
- *
- * @param fromIface interface name to remove forwarding ip rule
- * @param toIface interface name to remove forwarding ip rule
- * @throws ServiceSpecificException in case of failure, with an error code indicating the
- * cause of the failure.
- */
- void removeInterfaceForward(in String fromIface, in String toIface);
}
diff --git a/framework/src/android/net/RoutingCoordinatorManager.java b/framework/src/android/net/RoutingCoordinatorManager.java
index a9e7eef..5576cb0 100644
--- a/framework/src/android/net/RoutingCoordinatorManager.java
+++ b/framework/src/android/net/RoutingCoordinatorManager.java
@@ -123,36 +123,4 @@
throw e.rethrowFromSystemServer();
}
}
-
- /**
- * Add forwarding ip rule
- *
- * @param fromIface interface name to add forwarding ip rule
- * @param toIface interface name to add forwarding ip rule
- * @throws ServiceSpecificException in case of failure, with an error code indicating the
- * cause of the failure.
- */
- public void addInterfaceForward(final String fromIface, final String toIface) {
- try {
- mService.addInterfaceForward(fromIface, toIface);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
- * Remove forwarding ip rule
- *
- * @param fromIface interface name to remove forwarding ip rule
- * @param toIface interface name to remove forwarding ip rule
- * @throws ServiceSpecificException in case of failure, with an error code indicating the
- * cause of the failure.
- */
- public void removeInterfaceForward(final String fromIface, final String toIface) {
- try {
- mService.removeInterfaceForward(fromIface, toIface);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
}
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/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..14ab2a1 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -19,6 +19,10 @@
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.LOCKDOWN_VPN_MATCH;
@@ -28,6 +32,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;
@@ -130,6 +135,8 @@
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 final List<Pair<Integer, String>> PERMISSION_LIST = Arrays.asList(
Pair.create(PERMISSION_INTERNET, "PERMISSION_INTERNET"),
@@ -177,6 +184,14 @@
sCookieTagMap = cookieTagMap;
}
+ /**
+ * Set dataSaverEnabledMap for test.
+ */
+ @VisibleForTesting
+ public static void setDataSaverEnabledMapForTest(IBpfMap<S32, U8> dataSaverEnabledMap) {
+ sDataSaverEnabledMap = dataSaverEnabledMap;
+ }
+
private static IBpfMap<S32, U32> getConfigurationMap() {
try {
return new BpfMap<>(
@@ -213,6 +228,15 @@
}
}
+ 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 void initBpfMaps() {
if (sConfigurationMap == null) {
sConfigurationMap = getConfigurationMap();
@@ -246,6 +270,15 @@
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);
+ }
}
/**
@@ -320,29 +353,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));
@@ -926,6 +936,27 @@
}
}
+ /**
+ * 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));
+ }
+ }
+
/** Register callback for statsd to pull atom. */
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
public void setPullAtomCallback(final Context context) {
@@ -1008,6 +1039,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 +1098,8 @@
});
BpfDump.dumpMap(sUidPermissionMap, pw, "sUidPermissionMap",
(uid, permission) -> uid.val + " " + permissionToString(permission.val));
+
+ dumpDataSaverConfig(pw);
pw.decreaseIndent();
}
}
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index c4cb4c7..f52a1a2 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;
@@ -5074,8 +5075,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());
@@ -5273,7 +5274,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 +5299,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
@@ -8898,9 +8910,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);
@@ -12557,6 +12568,7 @@
}
}
+ @TargetApi(Build.VERSION_CODES.TIRAMISU)
@Override
public void setDataSaverEnabled(final boolean enable) {
enforceNetworkStackOrSettingsPermission();
@@ -12569,6 +12581,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 +12690,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/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/RoutingCoordinatorService.java b/service/src/com/android/server/connectivity/RoutingCoordinatorService.java
index 92ea610..50e84d4 100644
--- a/service/src/com/android/server/connectivity/RoutingCoordinatorService.java
+++ b/service/src/com/android/server/connectivity/RoutingCoordinatorService.java
@@ -19,16 +19,12 @@
import static com.android.net.module.util.NetdUtils.toRouteInfoParcel;
import android.annotation.NonNull;
+import android.content.Context;
import android.net.INetd;
import android.net.IRoutingCoordinator;
import android.net.RouteInfo;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
-import android.util.ArraySet;
-
-import com.android.internal.annotations.GuardedBy;
-
-import java.util.Objects;
/**
* Class to coordinate routing across multiple clients.
@@ -119,89 +115,4 @@
throws ServiceSpecificException, RemoteException {
mNetd.networkRemoveInterface(netId, iface);
}
-
- private final Object mIfacesLock = new Object();
- private static final class ForwardingPair {
- @NonNull public final String fromIface;
- @NonNull public final String toIface;
- ForwardingPair(@NonNull final String fromIface, @NonNull final String toIface) {
- this.fromIface = fromIface;
- this.toIface = toIface;
- }
-
- @Override
- public boolean equals(final Object o) {
- if (this == o) return true;
- if (!(o instanceof ForwardingPair)) return false;
-
- final ForwardingPair that = (ForwardingPair) o;
-
- return fromIface.equals(that.fromIface) && toIface.equals(that.toIface);
- }
-
- @Override
- public int hashCode() {
- int result = fromIface.hashCode();
- result = 2 * result + toIface.hashCode();
- return result;
- }
- }
-
- @GuardedBy("mIfacesLock")
- private final ArraySet<ForwardingPair> mForwardedInterfaces = new ArraySet<>();
-
- /**
- * Add forwarding ip rule
- *
- * @param fromIface interface name to add forwarding ip rule
- * @param toIface interface name to add forwarding ip rule
- * @throws ServiceSpecificException in case of failure, with an error code indicating the
- * cause of the failure.
- */
- public void addInterfaceForward(final String fromIface, final String toIface)
- throws ServiceSpecificException, RemoteException {
- Objects.requireNonNull(fromIface);
- Objects.requireNonNull(toIface);
- synchronized (mIfacesLock) {
- if (mForwardedInterfaces.size() == 0) {
- mNetd.ipfwdEnableForwarding("RoutingCoordinator");
- }
- final ForwardingPair fwp = new ForwardingPair(fromIface, toIface);
- if (mForwardedInterfaces.contains(fwp)) {
- throw new IllegalStateException("Forward already exists between ifaces "
- + fromIface + " → " + toIface);
- }
- mForwardedInterfaces.add(fwp);
- // Enables NAT for v4 and filters packets from unknown interfaces
- mNetd.tetherAddForward(fromIface, toIface);
- mNetd.ipfwdAddInterfaceForward(fromIface, toIface);
- }
- }
-
- /**
- * Remove forwarding ip rule
- *
- * @param fromIface interface name to remove forwarding ip rule
- * @param toIface interface name to remove forwarding ip rule
- * @throws ServiceSpecificException in case of failure, with an error code indicating the
- * cause of the failure.
- */
- public void removeInterfaceForward(final String fromIface, final String toIface)
- throws ServiceSpecificException, RemoteException {
- Objects.requireNonNull(fromIface);
- Objects.requireNonNull(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);
- }
- mForwardedInterfaces.remove(fwp);
- mNetd.ipfwdRemoveInterfaceForward(fromIface, toIface);
- mNetd.tetherRemoveForward(fromIface, toIface);
- 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/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/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/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/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/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..5893de7 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
@@ -752,27 +752,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/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/unit/java/android/net/BpfNetMapsReaderTest.kt b/tests/unit/java/android/net/BpfNetMapsReaderTest.kt
index facb932..86a0acb 100644
--- a/tests/unit/java/android/net/BpfNetMapsReaderTest.kt
+++ b/tests/unit/java/android/net/BpfNetMapsReaderTest.kt
@@ -16,23 +16,31 @@
package android.net
+import android.net.BpfNetMapsConstants.DOZABLE_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 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 +74,75 @@
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))
+ }
+
+ @Test
+ fun testIsUidNetworkingBlockedByFirewallChains_allowChain() {
+ // With everything disabled by default, verify the return value is false.
+ testConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, U32(0))
+ assertFalse(bpfNetMapsReader.isUidBlockedByFirewallChains(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(bpfNetMapsReader.isUidBlockedByFirewallChains(TEST_UID1))
+ assertTrue(bpfNetMapsReader.isUidBlockedByFirewallChains(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(bpfNetMapsReader.isUidBlockedByFirewallChains(TEST_UID1))
+ assertTrue(bpfNetMapsReader.isUidBlockedByFirewallChains(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(bpfNetMapsReader.isUidBlockedByFirewallChains(TEST_UID1))
+ assertFalse(bpfNetMapsReader.isUidBlockedByFirewallChains(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(bpfNetMapsReader.isUidBlockedByFirewallChains(TEST_UID1))
+ assertFalse(bpfNetMapsReader.isUidBlockedByFirewallChains(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(bpfNetMapsReader.isUidBlockedByFirewallChains(TEST_UID1))
+ }
}
diff --git a/tests/unit/java/com/android/server/BpfNetMapsTest.java b/tests/unit/java/com/android/server/BpfNetMapsTest.java
index da5f7e1..1dfc8c0 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;
@@ -116,20 +121,16 @@
private static final int NO_IIF = 0;
private static final int NULL_IIF = 0;
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,6 +142,7 @@
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);
@Before
public void setUp() throws Exception {
@@ -155,6 +157,8 @@
BpfNetMaps.setUidOwnerMapForTest(mUidOwnerMap);
BpfNetMaps.setUidPermissionMapForTest(mUidPermissionMap);
BpfNetMaps.setCookieTagMapForTest(mCookieTagMap);
+ BpfNetMaps.setDataSaverEnabledMapForTest(mDataSaverEnabledMap);
+ mDataSaverEnabledMap.updateEntry(DATA_SAVER_ENABLED_KEY, new U8(DATA_SAVER_DISABLED));
mBpfNetMaps = new BpfNetMaps(mContext, mNetd, mDeps);
}
@@ -611,7 +615,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 +623,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 +703,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 +750,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 +1159,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 +1202,23 @@
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);
+ }
+ }
}
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 11cece1..aeb4028 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);
@@ -10268,7 +10210,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 +10526,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 +12535,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 +13171,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 +13183,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.
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/RoutingCoordinatorServiceTest.kt b/tests/unit/java/com/android/server/connectivity/RoutingCoordinatorServiceTest.kt
deleted file mode 100644
index 8adf309..0000000
--- a/tests/unit/java/com/android/server/connectivity/RoutingCoordinatorServiceTest.kt
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * 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.connectivity
-
-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
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.any
-import org.mockito.Mockito.inOrder
-import org.mockito.Mockito.mock
-import kotlin.test.assertFailsWith
-
-@RunWith(DevSdkIgnoreRunner::class)
-@IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
-class RoutingCoordinatorServiceTest {
- val mNetd = mock(INetd::class.java)
- val mService = RoutingCoordinatorService(mNetd)
-
- @Test
- fun testInterfaceForward() {
- val inOrder = inOrder(mNetd)
-
- mService.addInterfaceForward("from1", "to1")
- inOrder.verify(mNetd).ipfwdEnableForwarding(any())
- inOrder.verify(mNetd).tetherAddForward("from1", "to1")
- inOrder.verify(mNetd).ipfwdAddInterfaceForward("from1", "to1")
-
- mService.addInterfaceForward("from2", "to1")
- inOrder.verify(mNetd).tetherAddForward("from2", "to1")
- inOrder.verify(mNetd).ipfwdAddInterfaceForward("from2", "to1")
-
- assertFailsWith<IllegalStateException> {
- // Can't add the same pair again
- mService.addInterfaceForward("from2", "to1")
- }
-
- mService.removeInterfaceForward("from1", "to1")
- inOrder.verify(mNetd).ipfwdRemoveInterfaceForward("from1", "to1")
- inOrder.verify(mNetd).tetherRemoveForward("from1", "to1")
-
- mService.removeInterfaceForward("from2", "to1")
- inOrder.verify(mNetd).ipfwdRemoveInterfaceForward("from2", "to1")
- inOrder.verify(mNetd).tetherRemoveForward("from2", "to1")
-
- inOrder.verify(mNetd).ipfwdDisableForwarding(any())
- }
-}
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..d40c035
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivityservice/CSDestroyedNetworkTests.kt
@@ -0,0 +1,56 @@
+/*
+ * 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 com.android.testutils.RecorderCallback.CallbackEntry.Lost
+import com.android.testutils.TestableNetworkCallback
+import org.junit.Test
+
+private const val LONG_TIMEOUT_MS = 5_000
+
+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/base/CSAgentWrapper.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt
index 094ded3..8860895 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,6 @@
cb.eventuallyExpect<Lost> { it.network == agent.network }
}
+ fun unregisterAfterReplacement(timeoutMs: Int) = agent.unregisterAfterReplacement(timeoutMs)
fun sendLocalNetworkConfig(lnc: LocalNetworkConfig) = agent.sendLocalNetworkConfig(lnc)
}