diff --git a/DnsResolver/Android.bp b/DnsResolver/Android.bp new file mode 100644 index 0000000000..d1330343f3 --- /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 0000000000..fbe4dc30e4 --- /dev/null +++ b/DnsResolver/DnsBpfHelper.cpp @@ -0,0 +1,76 @@ +/* + * 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 +#include + +namespace android { +namespace net { + +base::Result DnsBpfHelper::init() { + if (!android::modules::sdklevel::IsAtLeastT()) { + LOG(ERROR) << __func__ << ": Unsupported before Android T."; + return base::Error(EOPNOTSUPP); + } + + auto result = mConfigurationMap.init(CONFIGURATION_MAP_PATH); + if (!result.ok()) { + LOG(ERROR) << __func__ << ": Failed to init configuration_map: " + << strerror(result.error().code()); + return result; + } + + result = mUidOwnerMap.init(UID_OWNER_MAP_PATH); + if (!result.ok()) { + LOG(ERROR) << __func__ << ": Failed to init uid_owner_map: " + << strerror(result.error().code()); + } + return result; +} + +base::Result DnsBpfHelper::isUidNetworkingBlocked(uid_t uid, bool) { + 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); + if (!enabledRules.ok()) { + LOG(ERROR) << __func__ + << ": Failed to read enabled rules from configuration_map: " + << strerror(enabledRules.error().code()); + return enabledRules.error(); + } + + auto value = mUidOwnerMap.readValue(uid); + uint32_t uidRules = value.ok() ? value.value().rule : 0; + + if (isBlockedByUidRules(enabledRules.value(), uidRules)) return true; + + // TODO: Read data saver settings from bpf maps. For metered network, check penalty box, happy box + // and data saver settings. + + return false; +} + +} // namespace net +} // namespace android diff --git a/DnsResolver/DnsBpfHelper.h b/DnsResolver/DnsBpfHelper.h new file mode 100644 index 0000000000..5520d2998d --- /dev/null +++ b/DnsResolver/DnsBpfHelper.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#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 init(); + base::Result isUidNetworkingBlocked(uid_t uid, bool metered); + + private: + android::bpf::BpfMapRO mConfigurationMap; + android::bpf::BpfMapRO mUidOwnerMap; + + // For testing + friend class DnsBpfHelperTest; +}; + +} // namespace net +} // namespace android diff --git a/DnsResolver/DnsBpfHelperTest.cpp b/DnsResolver/DnsBpfHelperTest.cpp new file mode 100644 index 0000000000..9bea72903b --- /dev/null +++ b/DnsResolver/DnsBpfHelperTest.cpp @@ -0,0 +1,141 @@ +/* + * 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 +#include + +#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 mFakeConfigurationMap; + BpfMap mFakeUidOwnerMap; + + 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); + + mDnsBpfHelper.mConfigurationMap = mFakeConfigurationMap; + ASSERT_VALID(mDnsBpfHelper.mConfigurationMap); + mDnsBpfHelper.mUidOwnerMap = mFakeUidOwnerMap; + ASSERT_VALID(mDnsBpfHelper.mUidOwnerMap); + } + + void ResetAllMaps() { + mDnsBpfHelper.mConfigurationMap.reset(); + mDnsBpfHelper.mUidOwnerMap.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()); +} + +} // namespace net +} // namespace android diff --git a/DnsResolver/DnsHelper.cpp b/DnsResolver/DnsHelper.cpp new file mode 100644 index 0000000000..3372908add --- /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 + +#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 0000000000..7c9fc9e58e --- /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 +#include + +__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 0000000000..3c965a212c --- /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/Tethering/Android.bp b/Tethering/Android.bp index e69b872ce1..dd60be7c78 100644 --- a/Tethering/Android.bp +++ b/Tethering/Android.bp @@ -226,6 +226,7 @@ sdk { "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 cd8eac88bc..ee44f3ccb4 100644 --- a/Tethering/apex/Android.bp +++ b/Tethering/apex/Android.bp @@ -83,6 +83,7 @@ apex { "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/bpf_progs/Android.bp b/bpf_progs/Android.bp index b3f8ed6160..cdf47e7da5 100644 --- a/bpf_progs/Android.bp +++ b/bpf_progs/Android.bp @@ -45,6 +45,7 @@ cc_library_headers { "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 5759df7a33..f223dd15e5 100644 --- a/bpf_progs/netd.c +++ b/bpf_progs/netd.c @@ -92,7 +92,7 @@ DEFINE_BPF_MAP_NO_NETD(app_uid_stats_map, HASH, uint32_t, StatsValue, APP_STATS_ 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)